diff --git a/app/config/app.php b/app/config/app.php new file mode 100644 index 0000000..e8071e6 --- /dev/null +++ b/app/config/app.php @@ -0,0 +1,185 @@ + true, + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | your application so that it is used when running Artisan tasks. + | + */ + + 'url' => '', + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. We have gone + | ahead and set this to a sensible default for you out of the box. + | + */ + + 'timezone' => 'Europe/Berlin', + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by the translation service provider. You are free to set this value + | to any of the locales which will be supported by the application. + | + */ + + 'locale' => 'de', + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is used by the Illuminate encrypter service and should be set + | to a random, 32 character string, otherwise these encrypted strings + | will not be safe. Please do this before deploying an application! + | + */ + + 'key' => '2qpXAWgHRibo9JD7YkmA991pdbFAWkJE', + + /* + |-------------------------------------------------------------------------- + | Autoloaded Service Providers + |-------------------------------------------------------------------------- + | + | The service providers listed here will be automatically loaded on the + | request to your application. Feel free to add your own services to + | this array to grant expanded functionality to your applications. + | + */ + + 'providers' => array( + + 'Illuminate\Foundation\Providers\ArtisanServiceProvider', + 'Illuminate\Auth\AuthServiceProvider', + 'Illuminate\Cache\CacheServiceProvider', + 'Illuminate\Foundation\Providers\CommandCreatorServiceProvider', + 'Illuminate\Session\CommandsServiceProvider', + 'Illuminate\Foundation\Providers\ComposerServiceProvider', + 'Illuminate\Routing\ControllerServiceProvider', + 'Illuminate\Cookie\CookieServiceProvider', + 'Illuminate\Database\DatabaseServiceProvider', + 'Illuminate\Encryption\EncryptionServiceProvider', + 'Illuminate\Filesystem\FilesystemServiceProvider', + 'Illuminate\Hashing\HashServiceProvider', + 'Illuminate\Html\HtmlServiceProvider', + 'Illuminate\Foundation\Providers\KeyGeneratorServiceProvider', + 'Illuminate\Log\LogServiceProvider', + 'Illuminate\Mail\MailServiceProvider', + 'Illuminate\Foundation\Providers\MaintenanceServiceProvider', + 'Illuminate\Database\MigrationServiceProvider', + 'Illuminate\Foundation\Providers\OptimizeServiceProvider', + 'Illuminate\Pagination\PaginationServiceProvider', + 'Illuminate\Foundation\Providers\PublisherServiceProvider', + 'Illuminate\Queue\QueueServiceProvider', + 'Illuminate\Redis\RedisServiceProvider', + 'Illuminate\Auth\Reminders\ReminderServiceProvider', + 'Illuminate\Foundation\Providers\RouteListServiceProvider', + 'Illuminate\Database\SeedServiceProvider', + 'Illuminate\Foundation\Providers\ServerServiceProvider', + 'Illuminate\Session\SessionServiceProvider', + 'Illuminate\Foundation\Providers\TinkerServiceProvider', + 'Illuminate\Translation\TranslationServiceProvider', + 'Illuminate\Validation\ValidationServiceProvider', + 'Illuminate\View\ViewServiceProvider', + 'Illuminate\Workbench\WorkbenchServiceProvider', + + ), + + /* + |-------------------------------------------------------------------------- + | Service Provider Manifest + |-------------------------------------------------------------------------- + | + | The service provider manifest is used by Laravel to lazy load service + | providers which are not needed for each request, as well to keep a + | list of all of the services. Here, you may set its storage spot. + | + */ + + 'manifest' => storage_path().'/meta', + + /* + |-------------------------------------------------------------------------- + | Class Aliases + |-------------------------------------------------------------------------- + | + | This array of class aliases will be registered when this application + | is started. However, feel free to register as many as you wish as + | the aliases are "lazy" loaded so they don't hinder performance. + | + */ + + 'aliases' => array( + + 'App' => 'Illuminate\Support\Facades\App', + 'Artisan' => 'Illuminate\Support\Facades\Artisan', + 'Auth' => 'Illuminate\Support\Facades\Auth', + 'Blade' => 'Illuminate\Support\Facades\Blade', + 'Cache' => 'Illuminate\Support\Facades\Cache', + 'ClassLoader' => 'Illuminate\Support\ClassLoader', + 'Config' => 'Illuminate\Support\Facades\Config', + 'Controller' => 'Illuminate\Routing\Controllers\Controller', + 'Cookie' => 'Illuminate\Support\Facades\Cookie', + 'Crypt' => 'Illuminate\Support\Facades\Crypt', + 'DB' => 'Illuminate\Support\Facades\DB', + 'Eloquent' => 'Illuminate\Database\Eloquent\Model', + 'Event' => 'Illuminate\Support\Facades\Event', + 'File' => 'Illuminate\Support\Facades\File', + 'Form' => 'Illuminate\Support\Facades\Form', + 'Hash' => 'Illuminate\Support\Facades\Hash', + 'HTML' => 'Illuminate\Support\Facades\HTML', + 'Input' => 'Illuminate\Support\Facades\Input', + 'Lang' => 'Illuminate\Support\Facades\Lang', + 'Log' => 'Illuminate\Support\Facades\Log', + 'Mail' => 'Illuminate\Support\Facades\Mail', + 'Paginator' => 'Illuminate\Support\Facades\Paginator', + 'Password' => 'Illuminate\Support\Facades\Password', + 'Queue' => 'Illuminate\Support\Facades\Queue', + 'Redirect' => 'Illuminate\Support\Facades\Redirect', + 'Redis' => 'Illuminate\Support\Facades\Redis', + 'Request' => 'Illuminate\Support\Facades\Request', + 'Response' => 'Illuminate\Support\Facades\Response', + 'Route' => 'Illuminate\Support\Facades\Route', + 'Schema' => 'Illuminate\Support\Facades\Schema', + 'Seeder' => 'Illuminate\Database\Seeder', + 'Session' => 'Illuminate\Support\Facades\Session', + 'Str' => 'Illuminate\Support\Str', + 'URL' => 'Illuminate\Support\Facades\URL', + 'Validator' => 'Illuminate\Support\Facades\Validator', + 'View' => 'Illuminate\Support\Facades\View', + + ), + +); diff --git a/app/views/index.blade.php b/app/views/index.blade.php index 75a3844..f7b3fc1 100644 --- a/app/views/index.blade.php +++ b/app/views/index.blade.php @@ -95,7 +95,7 @@ in {{ $kommentar->objekt->name }}

- {{ substr($kommentar->text, 0, 55)}}text) > 55) echo "..." ?> + {{ $kommentar->text }}

@endforeach diff --git a/old.gitignore b/old.gitignore new file mode 100644 index 0000000..0fbc6da --- /dev/null +++ b/old.gitignore @@ -0,0 +1,5 @@ +/bootstrap/compiled.php +/vendor +composer.phar +.DS_Store +/app/config/app.php diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..de35193 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ + and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/vendor/classpreloader/classpreloader/README.md b/vendor/classpreloader/classpreloader/README.md new file mode 100644 index 0000000..0ff9791 --- /dev/null +++ b/vendor/classpreloader/classpreloader/README.md @@ -0,0 +1,108 @@ +Class Preloader for PHP +======================= + +This tool is used to generate a single PHP script containing all of the classes +required for a specific use case. Using a single compiled PHP script instead of relying on autoloading can help to improve the performance of specific use cases. For example, if your application executes the same bootstrap code on every request, then you could generate a preloader (the compiled output of this tool) to reduce the cost of autoloading the required classes over and over. + +What it actually does +--------------------- + +This tool listens for each file that is autoloaded, creates a list of files, traverses the parsed PHP file using [PHPParser](https://github.com/nikic/PHP-Parser) and any visitors of a Config object, wraps the code of each file in a namespace block if necessary, and writes the contents of every autoloaded file (in order) to a single PHP file. + +Notice +------ + +This tool should only be used for specific use cases. There is a tradeoff between preloading classes and autoloading classes. The point at which it is no longer beneficial to generate a preloader is application specific. You'll need to perform your own benchmarks to determine if this tool will speed up your application. + +Installation +------------ + +Add the ClassPreloader as a dependency to your composer.json file: + +```javascript +{ + "require": { + "classpreloader/classpreloader": "1.0.0" + }, + "config": { + "bin-dir": "bin" + } +} +``` + +Using the tool +-------------- + +You use the bin/classpreloader.php compile command with a few command line flags to generate a preloader. + +`--config`: A CSV containing a list of files to combine into a classmap, or the full path to a PHP script that returns an array of classes or a `\ClassPreloader\Config` object. + +`--output`: The path to the file to store the compiled PHP code. If the directory does not exist, the tool will attempt to create it. + +`--fix_dir`: (defaults to 1) Set to 0 to not replace "__DIR__" constants with the actual directory of the original file. + +`--fix_file`: (defaults to 1) Set to 0 to not replace "__FILE__" constants with the actual location of the original file. + +Writing a config file +--------------------- + +Creating a PHP based configuration file is fairly simple. Just include the vendor/classpreloader/classpreloader/src/ClassPreloader/ClassLoader.php file and call the `ClassLoader::getIncludes()` method, passing a function as the only argument. This function should accept a `ClassLoader` object and register the passed in object's autoloader using `$loader->register()`. It is important to register the `ClassLoader` autoloader after all other autoloaders are registered. + +An array or `\ClassPreloader\Config` must be returned from the config file. You can attach custom node visitors if you need to perform any sort of translation on each matching file before writing it to the output. + +```php +register(); + $aws = Aws\Common\Aws::factory(array( + 'key' => '***', + 'secret' => '***', + 'region' => 'us-east-1' + )); + $client = $aws->get('dynamodb'); + $client->listTables()->getAll(); +}); + +// Add a regex filter that requires all classes to match the regex +// $config->addInclusiveFilter('/Foo/'); + +// Add a regex filter that requires that a class does not match the filter +// $config->addExclusiveFilter('/Foo/'); + +return $config; +``` + +You would then run the classpreloader.php script and pass in the full path to the above PHP script. + +`php bin/classpreloader.php compile --config="/path/to/the_example.php" --output="/tmp/preloader.php"` + +The above command will create a file in /tmp/preloader.php that contains every file that was autoloaded while running the snippet of code in the anonymous function. You would generate this file and include it in your production script. + +Automating the process with Composer +------------------------------------ + +You can automate the process of creating preloaders using Composer's script functionality. For example, if you wanted to automatically create a preloader each time the AWS SDK for PHP is installed, you could define a script like the following in your composer.json file: + +```javascript +{ + "require": { + "classpreloader/classpreloader": "1.0.0" + }, + "scripts": { + "post-autoload-dump": "php bin/classpreloader.php compile --config=/path/to/the_example.php --output=/path/to/preload.php" + }, + "config": { + "bin-dir": "bin" + } +} +``` + +Using the above composer.json file, each time the project's autoloader is recreated using the install or update command, the classpreloader.php file will be executed. This script would generate a preload.php containing the classes required to run the previously demonstrated "the_example.php" configuration file. diff --git a/vendor/classpreloader/classpreloader/classpreloader.php b/vendor/classpreloader/classpreloader/classpreloader.php new file mode 100755 index 0000000..5dc4da1 --- /dev/null +++ b/vendor/classpreloader/classpreloader/classpreloader.php @@ -0,0 +1,10 @@ +#! /usr/bin/env php +run(); diff --git a/vendor/classpreloader/classpreloader/composer.json b/vendor/classpreloader/classpreloader/composer.json new file mode 100644 index 0000000..ba44378 --- /dev/null +++ b/vendor/classpreloader/classpreloader/composer.json @@ -0,0 +1,30 @@ +{ + "name": "classpreloader/classpreloader", + "description":"Helps class loading performance by generating a single PHP file containing all of the autoloaded files for a specific use case", + "keywords":["autoload","class","preload"], + "license":"MIT", + + "require":{ + "php": ">=5.3.3", + "symfony/console": ">2.0", + "symfony/filesystem": ">2.0", + "symfony/finder": ">2.0", + "nikic/php-parser": "*" + }, + + "minimum-stability": "beta", + + "autoload": { + "psr-0": { + "ClassPreloader": "src/" + } + }, + + "bin": ["classpreloader.php"], + + "extra": { + "branch-alias": { + "dev-master": "1.0.0-dev" + } + } +} diff --git a/vendor/classpreloader/classpreloader/src/ClassPreloader/Application.php b/vendor/classpreloader/classpreloader/src/ClassPreloader/Application.php new file mode 100644 index 0000000..9e75916 --- /dev/null +++ b/vendor/classpreloader/classpreloader/src/ClassPreloader/Application.php @@ -0,0 +1,33 @@ +files() + ->in(__DIR__ . '/Command') + ->notName('Abstract*') + ->name('*.php'); + + // Add each command to the CLI + foreach ($finder as $file) { + $filename = str_replace('\\', '/', $file->getRealpath()); + $pos = strrpos($filename, '/ClassPreloader/') + strlen('/ClassPreloader/'); + $class = __NAMESPACE__ . '\\' + . substr(str_replace('/', '\\', substr($filename, $pos)), 0, -4); + $this->add(new $class()); + } + } +} diff --git a/vendor/classpreloader/classpreloader/src/ClassPreloader/ClassList.php b/vendor/classpreloader/classpreloader/src/ClassPreloader/ClassList.php new file mode 100644 index 0000000..aaea2b5 --- /dev/null +++ b/vendor/classpreloader/classpreloader/src/ClassPreloader/ClassList.php @@ -0,0 +1,87 @@ +clear(); + } + + /** + * Clear the contents of the list and reset the head node and current node + */ + public function clear() + { + $this->head = new ClassNode(null); + $this->current = $this->head; + } + + /** + * Traverse to the next node in the list + */ + public function next() + { + if (isset($this->current->next)) { + $this->current = $this->current->next; + } else { + $this->current->next = new ClassNode(null, $this->current); + $this->current = $this->current->next; + } + } + + /** + * Insert a value at the current position in the list. Any currently set + * value at this position will be pushed back in the list after the new + * value + * + * @param mixed $value Value to insert + */ + public function push($value) + { + if (!$this->current->value) { + $this->current->value = $value; + } else { + $temp = $this->current; + $this->current = new ClassNode($value, $temp->prev); + $this->current->next = $temp; + $temp->prev = $this->current; + if ($temp === $this->head) { + $this->head = $this->current; + } else { + $this->current->prev->next = $this->current; + } + } + } + + /** + * Traverse the ClassList and return a list of classes + * + * @return array + */ + public function getClasses() + { + $classes = array(); + $current = $this->head; + while ($current && $current->value) { + $classes[] = $current->value; + $current = $current->next; + } + + return array_filter($classes); + } +} diff --git a/vendor/classpreloader/classpreloader/src/ClassPreloader/ClassLoader.php b/vendor/classpreloader/classpreloader/src/ClassPreloader/ClassLoader.php new file mode 100644 index 0000000..33fac1d --- /dev/null +++ b/vendor/classpreloader/classpreloader/src/ClassPreloader/ClassLoader.php @@ -0,0 +1,112 @@ +classList = new ClassList(); + } + + /** + * Wrap a block of code in the autoloader and get a list of loaded classes + * + * @param \Callable $func Callable function + * + * @return Config + */ + public static function getIncludes($func) + { + $loader = new self(); + call_user_func($func, $loader); + $loader->unregister(); + + $config = new Config(); + foreach ($loader->getFilenames() as $file) { + $config->addFile($file); + } + + return $config; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register() + { + spl_autoload_register(array($this, 'loadClass'), true, true); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True, if loaded + */ + public function loadClass($class) + { + foreach (spl_autoload_functions() as $func) { + if (is_array($func) && $func[0] === $this) { + continue; + } + $this->classList->push($class); + if (call_user_func($func, $class)) { + break; + } + } + + $this->classList->next(); + + return true; + } + + /** + * Get an array of loaded file names in order of loading + * + * @return array + */ + public function getFilenames() + { + $files = array(); + foreach ($this->classList->getClasses() as $class) { + // Push interfaces before classes if not already loaded + $r = new \ReflectionClass($class); + foreach ($r->getInterfaces() as $inf) { + $name = $inf->getFileName(); + if ($name && !in_array($name, $files)) { + $files[] = $name; + } + } + $files[] = $r->getFileName(); + } + + return $files; + } +} diff --git a/vendor/classpreloader/classpreloader/src/ClassPreloader/ClassNode.php b/vendor/classpreloader/classpreloader/src/ClassPreloader/ClassNode.php new file mode 100644 index 0000000..78abb28 --- /dev/null +++ b/vendor/classpreloader/classpreloader/src/ClassPreloader/ClassNode.php @@ -0,0 +1,36 @@ +value = $value; + $this->prev = $prev; + } +} diff --git a/vendor/classpreloader/classpreloader/src/ClassPreloader/Command/PreCompileCommand.php b/vendor/classpreloader/classpreloader/src/ClassPreloader/Command/PreCompileCommand.php new file mode 100644 index 0000000..7c46d9e --- /dev/null +++ b/vendor/classpreloader/classpreloader/src/ClassPreloader/Command/PreCompileCommand.php @@ -0,0 +1,213 @@ +printer = new \PHPParser_PrettyPrinter_Zend(); + $this->parser = new \PHPParser_Parser(new \PHPParser_Lexer()); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + parent::configure(); + + $this->setName('compile') + ->setDescription('Compiles classes into a single file') + ->addOption('config', null, InputOption::VALUE_REQUIRED, 'CSV of filenames to load, or the path to a PHP script that returns an array of file names') + ->addOption('output', null, InputOption::VALUE_REQUIRED) + ->addOption('fix_dir', null, InputOption::VALUE_REQUIRED, 'Convert __DIR__ constants to the original directory of a file', 1) + ->addOption('fix_file', null, InputOption::VALUE_REQUIRED, 'Convert __FILE__ constants to the original path of a file', 1) + ->addOption('strip_comments', null, InputOption::VALUE_REQUIRED, 'Set to 1 to strip comments from each source file', 0) + ->setHelp(<<%command.name% command iterates over each script, normalizes +the file to be wrapped in namespaces, and combines each file into a single PHP +file. +EOF + ); + } + + /** + * Get the node traverser used by the command + * + * @return NodeTraverser + */ + protected function getTraverser() + { + if (!$this->traverser) { + $this->traverser = new NodeTraverser(); + if ($this->input->getOption('fix_dir')) { + $this->traverser->addVisitor(new DirVisitor($file)); + } + if ($this->input->getOption('fix_file')) { + $this->traverser->addVisitor(new FileVisitor($file)); + } + } + + return $this->traverser; + } + + /** + * Get a pretty printed string of code from a file while applying visitors + * + * @param string $file Name of the file to get code from + * @param NodeTraverser $traverser Node traverser + * + * @return string + */ + protected function getCode($file) + { + if (!is_readable($file)) { + throw new \RuntimeException("Cannot open {$file} for reading"); + } + + if ($this->input->getOption('strip_comments')) { + $content = php_strip_whitespace($file); + } else { + $content = file_get_contents($file); + } + + $stmts = $this->getTraverser() + ->traverseFile($this->parser->parse($content), $file); + $pretty = $this->printer->prettyPrint($stmts); + + // Remove the open PHP tag + if (substr($pretty, 6) == "input->getOption('output')) { + throw new \InvalidArgumentException('An output option is required'); + } + + if (!$this->input->getOption('config')) { + throw new \InvalidArgumentException('A config option is required'); + } + } + + /** + * Get a list of files in order + * + * @param mixed $config Configuration option + * + * @return array + */ + protected function getFileList($config) + { + $this->output->writeln('> Loading configuration file'); + $filesystem = new Filesystem(); + + if (strpos($config, ',')) { + return array_filter(explode(',', $config)); + } + + // Ensure absolute paths are resolved + if (!$filesystem->isAbsolutePath($config)) { + $config = getcwd() . '/' . $config; + } + + // Ensure that the config file exists + if (!file_exists($config)) { + throw new \InvalidArgumentException(sprintf('Configuration file "%s" does not exist.', $config)); + } + + $result = require $config; + + if ($result instanceof Config) { + foreach ($result->getVisitors() as $visitor) { + $this->getTraverser()->addVisitor($visitor); + } + + return $result; + } elseif (is_array($result)) { + return $result; + } + + throw new \InvalidArgumentException( + 'Config must return an array of filenames or a Config object' + ); + } + + /** + * Prepare the output file and directory + * + * @param string $outputFile The full path to the output file + */ + protected function prepareOutput($outputFile) + { + $dir = dirname($outputFile); + if (!is_dir($dir) && !mkdir($dir, 0777, true)) { + throw new \RuntimeException('Unable to create directory ' . $dir); + } + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->output = $output; + $this->validateCommand(); + $outputFile = $this->input->getOption('output'); + $config = $this->input->getOption('config'); + $files = $this->getFileList($config); + $output->writeLn('- Found ' . count($files) . ' files'); + + // Make sure that the output dir can be used or create it + $this->prepareOutput($outputFile); + + if (!$handle = fopen($input->getOption('output'), 'w')) { + throw new \RuntimeException( + "Unable to open {$outputFile} for writing" + ); + } + + // Write the first line of the output + fwrite($handle, "writeln('> Compiling classes'); + foreach ($files as $file) { + $this->output->writeln('- Writing ' . $file); + fwrite($handle, $this->getCode($file) . "\n"); + } + fclose($handle); + + $output->writeln("> Compiled loader written to {$outputFile}"); + $output->writeln('- ' . (round(filesize($outputFile) / 1024)) . ' kb'); + } +} diff --git a/vendor/classpreloader/classpreloader/src/ClassPreloader/Config.php b/vendor/classpreloader/classpreloader/src/ClassPreloader/Config.php new file mode 100644 index 0000000..ad1426d --- /dev/null +++ b/vendor/classpreloader/classpreloader/src/ClassPreloader/Config.php @@ -0,0 +1,133 @@ +filenames[] = $filename; + + return $this; + } + + /** + * Get an array of file names that satisfy any added filters + * + * @return array + */ + public function getFilenames() + { + $filenames = array(); + foreach ($this->filenames as $f) { + foreach ($this->inclusiveFilters as $filter) { + if (!preg_match($filter, $f)) { + continue 2; + } + } + foreach ($this->exclusiveFilters as $filter) { + if (preg_match($filter, $f)) { + continue 2; + } + } + $filenames[] = $f; + } + + return $filenames; + } + + /** + * Get an iterator for the filenames + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->getFilenames()); + } + + /** + * Add a filter used to filter out classes matching a specific pattern + * + * @param string $pattern Regular expression pattern + * + * @return self + */ + public function addExclusiveFilter($pattern) + { + $this->exclusiveFilters[] = $pattern; + + return $this; + } + + /** + * Add a filter used to grab only file names matching the pattern + * + * @param string $pattern Regular expression pattern + * + * @return self + */ + public function addInclusiveFilter($pattern) + { + $this->inclusiveFilters[] = $pattern; + + return $this; + } + + /** + * Add a visitor that will visit each node when traversing the node list + * of each file. + * + * @param AbstractNodeVisitor $visitor Node visitor + * + * @return self + */ + public function addVisitor(AbstractNodeVisitor $visitor) + { + $this->visitors[] = $visitor; + + return $this; + } + + /** + * Get an array of node visitors + * + * @return array + */ + public function getVisitors() + { + return $this->visitors; + } +} diff --git a/vendor/classpreloader/classpreloader/src/ClassPreloader/Parser/AbstractNodeVisitor.php b/vendor/classpreloader/classpreloader/src/ClassPreloader/Parser/AbstractNodeVisitor.php new file mode 100644 index 0000000..cd05ab9 --- /dev/null +++ b/vendor/classpreloader/classpreloader/src/ClassPreloader/Parser/AbstractNodeVisitor.php @@ -0,0 +1,48 @@ +filename = $filename; + + return $this; + } + + /** + * Get the full path to the current file being parsed + * + * @return string + */ + public function getFilename() + { + return $this->filename; + } + + /** + * Get the directory of the current file being parsed + * + * @return string + */ + public function getDir() + { + return dirname($this->getFilename()); + } +} diff --git a/vendor/classpreloader/classpreloader/src/ClassPreloader/Parser/DirVisitor.php b/vendor/classpreloader/classpreloader/src/ClassPreloader/Parser/DirVisitor.php new file mode 100644 index 0000000..24dba9e --- /dev/null +++ b/vendor/classpreloader/classpreloader/src/ClassPreloader/Parser/DirVisitor.php @@ -0,0 +1,16 @@ +getDir()); + } + } +} diff --git a/vendor/classpreloader/classpreloader/src/ClassPreloader/Parser/FileVisitor.php b/vendor/classpreloader/classpreloader/src/ClassPreloader/Parser/FileVisitor.php new file mode 100644 index 0000000..bd0ed61 --- /dev/null +++ b/vendor/classpreloader/classpreloader/src/ClassPreloader/Parser/FileVisitor.php @@ -0,0 +1,16 @@ +getFilename()); + } + } +} diff --git a/vendor/classpreloader/classpreloader/src/ClassPreloader/Parser/NodeTraverser.php b/vendor/classpreloader/classpreloader/src/ClassPreloader/Parser/NodeTraverser.php new file mode 100644 index 0000000..960988a --- /dev/null +++ b/vendor/classpreloader/classpreloader/src/ClassPreloader/Parser/NodeTraverser.php @@ -0,0 +1,21 @@ +visitors as $visitor) { + if ($visitor instanceof AbstractNodeVisitor) { + $visitor->setFilename($filename); + } + } + + return $this->traverse($nodes); + } +} diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php new file mode 100644 index 0000000..1db8d9a --- /dev/null +++ b/vendor/composer/ClassLoader.php @@ -0,0 +1,246 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0 class loader + * + * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + */ +class ClassLoader +{ + private $prefixes = array(); + private $fallbackDirs = array(); + private $useIncludePath = false; + private $classMap = array(); + + public function getPrefixes() + { + return call_user_func_array('array_merge', $this->prefixes); + } + + public function getFallbackDirs() + { + return $this->fallbackDirs; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of classes, merging with any others previously set. + * + * @param string $prefix The classes prefix + * @param array|string $paths The location(s) of the classes + * @param bool $prepend Prepend the location(s) + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirs = array_merge( + (array) $paths, + $this->fallbackDirs + ); + } else { + $this->fallbackDirs = array_merge( + $this->fallbackDirs, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixes[$first][$prefix])) { + $this->prefixes[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixes[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixes[$first][$prefix] + ); + } else { + $this->prefixes[$first][$prefix] = array_merge( + $this->prefixes[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of classes, replacing any others previously set. + * + * @param string $prefix The classes prefix + * @param array|string $paths The location(s) of the classes + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirs = (array) $paths; + + return; + } + $this->prefixes[substr($prefix, 0, 1)][$prefix] = (array) $paths; + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + include $file; + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $classPath = strtr(substr($class, 0, $pos), '\\', DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + $className = substr($class, $pos + 1); + } else { + // PEAR-like class name + $classPath = null; + $className = $class; + } + + $classPath .= strtr($className, '_', DIRECTORY_SEPARATOR) . '.php'; + + $first = $class[0]; + if (isset($this->prefixes[$first])) { + foreach ($this->prefixes[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) { + return $dir . DIRECTORY_SEPARATOR . $classPath; + } + } + } + } + } + + foreach ($this->fallbackDirs as $dir) { + if (file_exists($dir . DIRECTORY_SEPARATOR . $classPath)) { + return $dir . DIRECTORY_SEPARATOR . $classPath; + } + } + + if ($this->useIncludePath && $file = stream_resolve_include_path($classPath)) { + return $file; + } + + return $this->classMap[$class] = false; + } +} diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..81c51fc --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,1570 @@ + $baseDir . '/app/controllers/BaseController.php', + 'Carbon\\Carbon' => $vendorDir . '/nesbot/carbon/src/Carbon/Carbon.php', + 'ClassPreloader\\Application' => $vendorDir . '/classpreloader/classpreloader/src/ClassPreloader/Application.php', + 'ClassPreloader\\ClassList' => $vendorDir . '/classpreloader/classpreloader/src/ClassPreloader/ClassList.php', + 'ClassPreloader\\ClassLoader' => $vendorDir . '/classpreloader/classpreloader/src/ClassPreloader/ClassLoader.php', + 'ClassPreloader\\ClassNode' => $vendorDir . '/classpreloader/classpreloader/src/ClassPreloader/ClassNode.php', + 'ClassPreloader\\Command\\PreCompileCommand' => $vendorDir . '/classpreloader/classpreloader/src/ClassPreloader/Command/PreCompileCommand.php', + 'ClassPreloader\\Config' => $vendorDir . '/classpreloader/classpreloader/src/ClassPreloader/Config.php', + 'ClassPreloader\\Parser\\AbstractNodeVisitor' => $vendorDir . '/classpreloader/classpreloader/src/ClassPreloader/Parser/AbstractNodeVisitor.php', + 'ClassPreloader\\Parser\\DirVisitor' => $vendorDir . '/classpreloader/classpreloader/src/ClassPreloader/Parser/DirVisitor.php', + 'ClassPreloader\\Parser\\FileVisitor' => $vendorDir . '/classpreloader/classpreloader/src/ClassPreloader/Parser/FileVisitor.php', + 'ClassPreloader\\Parser\\NodeTraverser' => $vendorDir . '/classpreloader/classpreloader/src/ClassPreloader/Parser/NodeTraverser.php', + 'Comment' => $baseDir . '/app/models/Comment.php', + 'CommentsSettings' => $baseDir . '/app/database/migrations/2013_09_26_022825_comments_settings.php', + 'CreatePasswordRemindersTable' => $baseDir . '/app/database/migrations/2013_11_18_180146_create_password_reminders_table.php', + 'CreateTables' => $baseDir . '/app/database/migrations/2013_09_07_032049_create_tables.php', + 'DatabaseSeeder' => $baseDir . '/app/database/seeds/DatabaseSeeder.php', + 'Doctrine\\Common\\Annotations\\Annotation' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php', + 'Doctrine\\Common\\Annotations\\AnnotationException' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php', + 'Doctrine\\Common\\Annotations\\AnnotationReader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php', + 'Doctrine\\Common\\Annotations\\AnnotationRegistry' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php', + 'Doctrine\\Common\\Annotations\\Annotation\\Attribute' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php', + 'Doctrine\\Common\\Annotations\\Annotation\\Attributes' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php', + 'Doctrine\\Common\\Annotations\\Annotation\\Enum' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php', + 'Doctrine\\Common\\Annotations\\Annotation\\IgnoreAnnotation' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php', + 'Doctrine\\Common\\Annotations\\Annotation\\Required' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php', + 'Doctrine\\Common\\Annotations\\Annotation\\Target' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php', + 'Doctrine\\Common\\Annotations\\CachedReader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php', + 'Doctrine\\Common\\Annotations\\DocLexer' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php', + 'Doctrine\\Common\\Annotations\\DocParser' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php', + 'Doctrine\\Common\\Annotations\\FileCacheReader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php', + 'Doctrine\\Common\\Annotations\\IndexedReader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php', + 'Doctrine\\Common\\Annotations\\PhpParser' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php', + 'Doctrine\\Common\\Annotations\\Reader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php', + 'Doctrine\\Common\\Annotations\\SimpleAnnotationReader' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php', + 'Doctrine\\Common\\Annotations\\TokenParser' => $vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php', + 'Doctrine\\Common\\Cache\\ApcCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php', + 'Doctrine\\Common\\Cache\\ArrayCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php', + 'Doctrine\\Common\\Cache\\Cache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php', + 'Doctrine\\Common\\Cache\\CacheProvider' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php', + 'Doctrine\\Common\\Cache\\CouchbaseCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php', + 'Doctrine\\Common\\Cache\\FileCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php', + 'Doctrine\\Common\\Cache\\FilesystemCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php', + 'Doctrine\\Common\\Cache\\MemcacheCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php', + 'Doctrine\\Common\\Cache\\MemcachedCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php', + 'Doctrine\\Common\\Cache\\PhpFileCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php', + 'Doctrine\\Common\\Cache\\RedisCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/RedisCache.php', + 'Doctrine\\Common\\Cache\\RiakCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php', + 'Doctrine\\Common\\Cache\\WinCacheCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php', + 'Doctrine\\Common\\Cache\\XcacheCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php', + 'Doctrine\\Common\\Cache\\ZendDataCache' => $vendorDir . '/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php', + 'Doctrine\\Common\\ClassLoader' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/ClassLoader.php', + 'Doctrine\\Common\\Collections\\ArrayCollection' => $vendorDir . '/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php', + 'Doctrine\\Common\\Collections\\Collection' => $vendorDir . '/doctrine/collections/lib/Doctrine/Common/Collections/Collection.php', + 'Doctrine\\Common\\Collections\\Criteria' => $vendorDir . '/doctrine/collections/lib/Doctrine/Common/Collections/Criteria.php', + 'Doctrine\\Common\\Collections\\Expr\\ClosureExpressionVisitor' => $vendorDir . '/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php', + 'Doctrine\\Common\\Collections\\Expr\\Comparison' => $vendorDir . '/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Comparison.php', + 'Doctrine\\Common\\Collections\\Expr\\CompositeExpression' => $vendorDir . '/doctrine/collections/lib/Doctrine/Common/Collections/Expr/CompositeExpression.php', + 'Doctrine\\Common\\Collections\\Expr\\Expression' => $vendorDir . '/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Expression.php', + 'Doctrine\\Common\\Collections\\Expr\\ExpressionVisitor' => $vendorDir . '/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ExpressionVisitor.php', + 'Doctrine\\Common\\Collections\\Expr\\Value' => $vendorDir . '/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Value.php', + 'Doctrine\\Common\\Collections\\ExpressionBuilder' => $vendorDir . '/doctrine/collections/lib/Doctrine/Common/Collections/ExpressionBuilder.php', + 'Doctrine\\Common\\Collections\\Selectable' => $vendorDir . '/doctrine/collections/lib/Doctrine/Common/Collections/Selectable.php', + 'Doctrine\\Common\\CommonException' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/CommonException.php', + 'Doctrine\\Common\\Comparable' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Comparable.php', + 'Doctrine\\Common\\EventArgs' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/EventArgs.php', + 'Doctrine\\Common\\EventManager' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/EventManager.php', + 'Doctrine\\Common\\EventSubscriber' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/EventSubscriber.php', + 'Doctrine\\Common\\Inflector\\Inflector' => $vendorDir . '/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php', + 'Doctrine\\Common\\Lexer' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Lexer.php', + 'Doctrine\\Common\\Lexer\\AbstractLexer' => $vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php', + 'Doctrine\\Common\\NotifyPropertyChanged' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/NotifyPropertyChanged.php', + 'Doctrine\\Common\\Persistence\\AbstractManagerRegistry' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/AbstractManagerRegistry.php', + 'Doctrine\\Common\\Persistence\\ConnectionRegistry' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/ConnectionRegistry.php', + 'Doctrine\\Common\\Persistence\\Event\\LifecycleEventArgs' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Event/LifecycleEventArgs.php', + 'Doctrine\\Common\\Persistence\\Event\\LoadClassMetadataEventArgs' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Event/LoadClassMetadataEventArgs.php', + 'Doctrine\\Common\\Persistence\\Event\\ManagerEventArgs' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Event/ManagerEventArgs.php', + 'Doctrine\\Common\\Persistence\\Event\\OnClearEventArgs' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Event/OnClearEventArgs.php', + 'Doctrine\\Common\\Persistence\\Event\\PreUpdateEventArgs' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Event/PreUpdateEventArgs.php', + 'Doctrine\\Common\\Persistence\\ManagerRegistry' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/ManagerRegistry.php', + 'Doctrine\\Common\\Persistence\\Mapping\\AbstractClassMetadataFactory' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php', + 'Doctrine\\Common\\Persistence\\Mapping\\ClassMetadata' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ClassMetadata.php', + 'Doctrine\\Common\\Persistence\\Mapping\\ClassMetadataFactory' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ClassMetadataFactory.php', + 'Doctrine\\Common\\Persistence\\Mapping\\Driver\\AnnotationDriver' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/AnnotationDriver.php', + 'Doctrine\\Common\\Persistence\\Mapping\\Driver\\DefaultFileLocator' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/DefaultFileLocator.php', + 'Doctrine\\Common\\Persistence\\Mapping\\Driver\\FileDriver' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileDriver.php', + 'Doctrine\\Common\\Persistence\\Mapping\\Driver\\FileLocator' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileLocator.php', + 'Doctrine\\Common\\Persistence\\Mapping\\Driver\\MappingDriver' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriver.php', + 'Doctrine\\Common\\Persistence\\Mapping\\Driver\\MappingDriverChain' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriverChain.php', + 'Doctrine\\Common\\Persistence\\Mapping\\Driver\\PHPDriver' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/PHPDriver.php', + 'Doctrine\\Common\\Persistence\\Mapping\\Driver\\StaticPHPDriver' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/StaticPHPDriver.php', + 'Doctrine\\Common\\Persistence\\Mapping\\Driver\\SymfonyFileLocator' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/SymfonyFileLocator.php', + 'Doctrine\\Common\\Persistence\\Mapping\\MappingException' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/MappingException.php', + 'Doctrine\\Common\\Persistence\\Mapping\\ReflectionService' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ReflectionService.php', + 'Doctrine\\Common\\Persistence\\Mapping\\RuntimeReflectionService' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/RuntimeReflectionService.php', + 'Doctrine\\Common\\Persistence\\Mapping\\StaticReflectionService' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/StaticReflectionService.php', + 'Doctrine\\Common\\Persistence\\ObjectManager' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManager.php', + 'Doctrine\\Common\\Persistence\\ObjectManagerAware' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManagerAware.php', + 'Doctrine\\Common\\Persistence\\ObjectManagerDecorator' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManagerDecorator.php', + 'Doctrine\\Common\\Persistence\\ObjectRepository' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/ObjectRepository.php', + 'Doctrine\\Common\\Persistence\\PersistentObject' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/PersistentObject.php', + 'Doctrine\\Common\\Persistence\\Proxy' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Persistence/Proxy.php', + 'Doctrine\\Common\\PropertyChangedListener' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/PropertyChangedListener.php', + 'Doctrine\\Common\\Proxy\\AbstractProxyFactory' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php', + 'Doctrine\\Common\\Proxy\\Autoloader' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Proxy/Autoloader.php', + 'Doctrine\\Common\\Proxy\\Exception\\InvalidArgumentException' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Proxy/Exception/InvalidArgumentException.php', + 'Doctrine\\Common\\Proxy\\Exception\\ProxyException' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Proxy/Exception/ProxyException.php', + 'Doctrine\\Common\\Proxy\\Exception\\UnexpectedValueException' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Proxy/Exception/UnexpectedValueException.php', + 'Doctrine\\Common\\Proxy\\Proxy' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Proxy/Proxy.php', + 'Doctrine\\Common\\Proxy\\ProxyDefinition' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Proxy/ProxyDefinition.php', + 'Doctrine\\Common\\Proxy\\ProxyGenerator' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Proxy/ProxyGenerator.php', + 'Doctrine\\Common\\Reflection\\ClassFinderInterface' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Reflection/ClassFinderInterface.php', + 'Doctrine\\Common\\Reflection\\Psr0FindFile' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Reflection/Psr0FindFile.php', + 'Doctrine\\Common\\Reflection\\ReflectionProviderInterface' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Reflection/ReflectionProviderInterface.php', + 'Doctrine\\Common\\Reflection\\RuntimePublicReflectionProperty' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Reflection/RuntimePublicReflectionProperty.php', + 'Doctrine\\Common\\Reflection\\StaticReflectionClass' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionClass.php', + 'Doctrine\\Common\\Reflection\\StaticReflectionMethod' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionMethod.php', + 'Doctrine\\Common\\Reflection\\StaticReflectionParser' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionParser.php', + 'Doctrine\\Common\\Reflection\\StaticReflectionProperty' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionProperty.php', + 'Doctrine\\Common\\Util\\ClassUtils' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Util/ClassUtils.php', + 'Doctrine\\Common\\Util\\Debug' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Util/Debug.php', + 'Doctrine\\Common\\Util\\Inflector' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Util/Inflector.php', + 'Doctrine\\Common\\Version' => $vendorDir . '/doctrine/common/lib/Doctrine/Common/Version.php', + 'Doctrine\\DBAL\\Cache\\ArrayStatement' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Cache/ArrayStatement.php', + 'Doctrine\\DBAL\\Cache\\CacheException' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Cache/CacheException.php', + 'Doctrine\\DBAL\\Cache\\QueryCacheProfile' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Cache/QueryCacheProfile.php', + 'Doctrine\\DBAL\\Cache\\ResultCacheStatement' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Cache/ResultCacheStatement.php', + 'Doctrine\\DBAL\\Configuration' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Configuration.php', + 'Doctrine\\DBAL\\Connection' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Connection.php', + 'Doctrine\\DBAL\\ConnectionException' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/ConnectionException.php', + 'Doctrine\\DBAL\\Connections\\MasterSlaveConnection' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Connections/MasterSlaveConnection.php', + 'Doctrine\\DBAL\\DBALException' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/DBALException.php', + 'Doctrine\\DBAL\\Driver' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver.php', + 'Doctrine\\DBAL\\DriverManager' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/DriverManager.php', + 'Doctrine\\DBAL\\Driver\\Connection' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/Connection.php', + 'Doctrine\\DBAL\\Driver\\DrizzlePDOMySql\\Connection' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Connection.php', + 'Doctrine\\DBAL\\Driver\\DrizzlePDOMySql\\Driver' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Driver.php', + 'Doctrine\\DBAL\\Driver\\IBMDB2\\DB2Connection' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php', + 'Doctrine\\DBAL\\Driver\\IBMDB2\\DB2Driver' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Driver.php', + 'Doctrine\\DBAL\\Driver\\IBMDB2\\DB2Exception' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php', + 'Doctrine\\DBAL\\Driver\\IBMDB2\\DB2Statement' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php', + 'Doctrine\\DBAL\\Driver\\Mysqli\\Driver' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/Driver.php', + 'Doctrine\\DBAL\\Driver\\Mysqli\\MysqliConnection' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliConnection.php', + 'Doctrine\\DBAL\\Driver\\Mysqli\\MysqliException' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliException.php', + 'Doctrine\\DBAL\\Driver\\Mysqli\\MysqliStatement' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php', + 'Doctrine\\DBAL\\Driver\\OCI8\\Driver' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/Driver.php', + 'Doctrine\\DBAL\\Driver\\OCI8\\OCI8Connection' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Connection.php', + 'Doctrine\\DBAL\\Driver\\OCI8\\OCI8Exception' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Exception.php', + 'Doctrine\\DBAL\\Driver\\OCI8\\OCI8Statement' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php', + 'Doctrine\\DBAL\\Driver\\PDOConnection' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php', + 'Doctrine\\DBAL\\Driver\\PDOIbm\\Driver' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php', + 'Doctrine\\DBAL\\Driver\\PDOMySql\\Driver' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php', + 'Doctrine\\DBAL\\Driver\\PDOOracle\\Driver' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOOracle/Driver.php', + 'Doctrine\\DBAL\\Driver\\PDOPgSql\\Driver' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOPgSql/Driver.php', + 'Doctrine\\DBAL\\Driver\\PDOSqlite\\Driver' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlite/Driver.php', + 'Doctrine\\DBAL\\Driver\\PDOSqlsrv\\Connection' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Connection.php', + 'Doctrine\\DBAL\\Driver\\PDOSqlsrv\\Driver' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Driver.php', + 'Doctrine\\DBAL\\Driver\\PDOStatement' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php', + 'Doctrine\\DBAL\\Driver\\ResultStatement' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/ResultStatement.php', + 'Doctrine\\DBAL\\Driver\\SQLSrv\\Driver' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/Driver.php', + 'Doctrine\\DBAL\\Driver\\SQLSrv\\LastInsertId' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/LastInsertId.php', + 'Doctrine\\DBAL\\Driver\\SQLSrv\\SQLSrvConnection' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvConnection.php', + 'Doctrine\\DBAL\\Driver\\SQLSrv\\SQLSrvException' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvException.php', + 'Doctrine\\DBAL\\Driver\\SQLSrv\\SQLSrvStatement' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php', + 'Doctrine\\DBAL\\Driver\\Statement' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Driver/Statement.php', + 'Doctrine\\DBAL\\Event\\ConnectionEventArgs' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Event/ConnectionEventArgs.php', + 'Doctrine\\DBAL\\Event\\Listeners\\MysqlSessionInit' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/MysqlSessionInit.php', + 'Doctrine\\DBAL\\Event\\Listeners\\OracleSessionInit' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/OracleSessionInit.php', + 'Doctrine\\DBAL\\Event\\Listeners\\SQLSessionInit' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/SQLSessionInit.php', + 'Doctrine\\DBAL\\Event\\SchemaAlterTableAddColumnEventArgs' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableAddColumnEventArgs.php', + 'Doctrine\\DBAL\\Event\\SchemaAlterTableChangeColumnEventArgs' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableChangeColumnEventArgs.php', + 'Doctrine\\DBAL\\Event\\SchemaAlterTableEventArgs' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableEventArgs.php', + 'Doctrine\\DBAL\\Event\\SchemaAlterTableRemoveColumnEventArgs' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableRemoveColumnEventArgs.php', + 'Doctrine\\DBAL\\Event\\SchemaAlterTableRenameColumnEventArgs' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableRenameColumnEventArgs.php', + 'Doctrine\\DBAL\\Event\\SchemaColumnDefinitionEventArgs' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaColumnDefinitionEventArgs.php', + 'Doctrine\\DBAL\\Event\\SchemaCreateTableColumnEventArgs' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaCreateTableColumnEventArgs.php', + 'Doctrine\\DBAL\\Event\\SchemaCreateTableEventArgs' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaCreateTableEventArgs.php', + 'Doctrine\\DBAL\\Event\\SchemaDropTableEventArgs' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaDropTableEventArgs.php', + 'Doctrine\\DBAL\\Event\\SchemaEventArgs' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaEventArgs.php', + 'Doctrine\\DBAL\\Event\\SchemaIndexDefinitionEventArgs' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaIndexDefinitionEventArgs.php', + 'Doctrine\\DBAL\\Events' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Events.php', + 'Doctrine\\DBAL\\Id\\TableGenerator' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Id/TableGenerator.php', + 'Doctrine\\DBAL\\Id\\TableGeneratorSchemaVisitor' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Id/TableGeneratorSchemaVisitor.php', + 'Doctrine\\DBAL\\LockMode' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/LockMode.php', + 'Doctrine\\DBAL\\Logging\\DebugStack' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Logging/DebugStack.php', + 'Doctrine\\DBAL\\Logging\\EchoSQLLogger' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Logging/EchoSQLLogger.php', + 'Doctrine\\DBAL\\Logging\\LoggerChain' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Logging/LoggerChain.php', + 'Doctrine\\DBAL\\Logging\\SQLLogger' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Logging/SQLLogger.php', + 'Doctrine\\DBAL\\Platforms\\AbstractPlatform' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php', + 'Doctrine\\DBAL\\Platforms\\DB2Platform' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/DB2Platform.php', + 'Doctrine\\DBAL\\Platforms\\DrizzlePlatform' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/DrizzlePlatform.php', + 'Doctrine\\DBAL\\Platforms\\Keywords\\DB2Keywords' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/DB2Keywords.php', + 'Doctrine\\DBAL\\Platforms\\Keywords\\DrizzleKeywords' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/DrizzleKeywords.php', + 'Doctrine\\DBAL\\Platforms\\Keywords\\KeywordList' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/KeywordList.php', + 'Doctrine\\DBAL\\Platforms\\Keywords\\MsSQLKeywords' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MsSQLKeywords.php', + 'Doctrine\\DBAL\\Platforms\\Keywords\\MySQLKeywords' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQLKeywords.php', + 'Doctrine\\DBAL\\Platforms\\Keywords\\OracleKeywords' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/OracleKeywords.php', + 'Doctrine\\DBAL\\Platforms\\Keywords\\PostgreSQLKeywords' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQLKeywords.php', + 'Doctrine\\DBAL\\Platforms\\Keywords\\ReservedKeywordsValidator' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/ReservedKeywordsValidator.php', + 'Doctrine\\DBAL\\Platforms\\Keywords\\SQLServer2005Keywords' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2005Keywords.php', + 'Doctrine\\DBAL\\Platforms\\Keywords\\SQLServer2008Keywords' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2008Keywords.php', + 'Doctrine\\DBAL\\Platforms\\Keywords\\SQLServer2012Keywords' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2012Keywords.php', + 'Doctrine\\DBAL\\Platforms\\Keywords\\SQLServerKeywords' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServerKeywords.php', + 'Doctrine\\DBAL\\Platforms\\Keywords\\SQLiteKeywords' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLiteKeywords.php', + 'Doctrine\\DBAL\\Platforms\\MySqlPlatform' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php', + 'Doctrine\\DBAL\\Platforms\\OraclePlatform' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/OraclePlatform.php', + 'Doctrine\\DBAL\\Platforms\\PostgreSqlPlatform' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php', + 'Doctrine\\DBAL\\Platforms\\SQLAzurePlatform' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAzurePlatform.php', + 'Doctrine\\DBAL\\Platforms\\SQLServer2005Platform' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2005Platform.php', + 'Doctrine\\DBAL\\Platforms\\SQLServer2008Platform' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2008Platform.php', + 'Doctrine\\DBAL\\Platforms\\SQLServer2012Platform' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2012Platform.php', + 'Doctrine\\DBAL\\Platforms\\SQLServerPlatform' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php', + 'Doctrine\\DBAL\\Platforms\\SqlitePlatform' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php', + 'Doctrine\\DBAL\\Portability\\Connection' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Portability/Connection.php', + 'Doctrine\\DBAL\\Portability\\Statement' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Portability/Statement.php', + 'Doctrine\\DBAL\\Query\\Expression\\CompositeExpression' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Query/Expression/CompositeExpression.php', + 'Doctrine\\DBAL\\Query\\Expression\\ExpressionBuilder' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Query/Expression/ExpressionBuilder.php', + 'Doctrine\\DBAL\\Query\\QueryBuilder' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryBuilder.php', + 'Doctrine\\DBAL\\Query\\QueryException' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryException.php', + 'Doctrine\\DBAL\\SQLParserUtils' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/SQLParserUtils.php', + 'Doctrine\\DBAL\\SQLParserUtilsException' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/SQLParserUtilsException.php', + 'Doctrine\\DBAL\\Schema\\AbstractAsset' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/AbstractAsset.php', + 'Doctrine\\DBAL\\Schema\\AbstractSchemaManager' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/AbstractSchemaManager.php', + 'Doctrine\\DBAL\\Schema\\Column' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/Column.php', + 'Doctrine\\DBAL\\Schema\\ColumnDiff' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/ColumnDiff.php', + 'Doctrine\\DBAL\\Schema\\Comparator' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/Comparator.php', + 'Doctrine\\DBAL\\Schema\\Constraint' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/Constraint.php', + 'Doctrine\\DBAL\\Schema\\DB2SchemaManager' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/DB2SchemaManager.php', + 'Doctrine\\DBAL\\Schema\\DrizzleSchemaManager' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/DrizzleSchemaManager.php', + 'Doctrine\\DBAL\\Schema\\ForeignKeyConstraint' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/ForeignKeyConstraint.php', + 'Doctrine\\DBAL\\Schema\\Identifier' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/Identifier.php', + 'Doctrine\\DBAL\\Schema\\Index' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/Index.php', + 'Doctrine\\DBAL\\Schema\\MySqlSchemaManager' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php', + 'Doctrine\\DBAL\\Schema\\OracleSchemaManager' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/OracleSchemaManager.php', + 'Doctrine\\DBAL\\Schema\\PostgreSqlSchemaManager' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php', + 'Doctrine\\DBAL\\Schema\\SQLServerSchemaManager' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php', + 'Doctrine\\DBAL\\Schema\\Schema' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/Schema.php', + 'Doctrine\\DBAL\\Schema\\SchemaConfig' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaConfig.php', + 'Doctrine\\DBAL\\Schema\\SchemaDiff' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaDiff.php', + 'Doctrine\\DBAL\\Schema\\SchemaException' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaException.php', + 'Doctrine\\DBAL\\Schema\\Sequence' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/Sequence.php', + 'Doctrine\\DBAL\\Schema\\SqliteSchemaManager' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php', + 'Doctrine\\DBAL\\Schema\\Synchronizer\\AbstractSchemaSynchronizer' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/AbstractSchemaSynchronizer.php', + 'Doctrine\\DBAL\\Schema\\Synchronizer\\SchemaSynchronizer' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/SchemaSynchronizer.php', + 'Doctrine\\DBAL\\Schema\\Synchronizer\\SingleDatabaseSynchronizer' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/SingleDatabaseSynchronizer.php', + 'Doctrine\\DBAL\\Schema\\Table' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/Table.php', + 'Doctrine\\DBAL\\Schema\\TableDiff' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/TableDiff.php', + 'Doctrine\\DBAL\\Schema\\View' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/View.php', + 'Doctrine\\DBAL\\Schema\\Visitor\\AbstractVisitor' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/AbstractVisitor.php', + 'Doctrine\\DBAL\\Schema\\Visitor\\CreateSchemaSqlCollector' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/CreateSchemaSqlCollector.php', + 'Doctrine\\DBAL\\Schema\\Visitor\\DropSchemaSqlCollector' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/DropSchemaSqlCollector.php', + 'Doctrine\\DBAL\\Schema\\Visitor\\Graphviz' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/Graphviz.php', + 'Doctrine\\DBAL\\Schema\\Visitor\\RemoveNamespacedAssets' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/RemoveNamespacedAssets.php', + 'Doctrine\\DBAL\\Schema\\Visitor\\SchemaDiffVisitor' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/SchemaDiffVisitor.php', + 'Doctrine\\DBAL\\Schema\\Visitor\\Visitor' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/Visitor.php', + 'Doctrine\\DBAL\\Sharding\\PoolingShardConnection' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Sharding/PoolingShardConnection.php', + 'Doctrine\\DBAL\\Sharding\\PoolingShardManager' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Sharding/PoolingShardManager.php', + 'Doctrine\\DBAL\\Sharding\\SQLAzure\\SQLAzureFederationsSynchronizer' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureFederationsSynchronizer.php', + 'Doctrine\\DBAL\\Sharding\\SQLAzure\\SQLAzureShardManager' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureShardManager.php', + 'Doctrine\\DBAL\\Sharding\\SQLAzure\\Schema\\MultiTenantVisitor' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/Schema/MultiTenantVisitor.php', + 'Doctrine\\DBAL\\Sharding\\ShardChoser\\MultiTenantShardChoser' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardChoser/MultiTenantShardChoser.php', + 'Doctrine\\DBAL\\Sharding\\ShardChoser\\ShardChoser' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardChoser/ShardChoser.php', + 'Doctrine\\DBAL\\Sharding\\ShardManager' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardManager.php', + 'Doctrine\\DBAL\\Sharding\\ShardingException' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardingException.php', + 'Doctrine\\DBAL\\Statement' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Statement.php', + 'Doctrine\\DBAL\\Tools\\Console\\Command\\ImportCommand' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/ImportCommand.php', + 'Doctrine\\DBAL\\Tools\\Console\\Command\\ReservedWordsCommand' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php', + 'Doctrine\\DBAL\\Tools\\Console\\Command\\RunSqlCommand' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php', + 'Doctrine\\DBAL\\Tools\\Console\\Helper\\ConnectionHelper' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Helper/ConnectionHelper.php', + 'Doctrine\\DBAL\\Types\\ArrayType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/ArrayType.php', + 'Doctrine\\DBAL\\Types\\BigIntType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/BigIntType.php', + 'Doctrine\\DBAL\\Types\\BlobType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/BlobType.php', + 'Doctrine\\DBAL\\Types\\BooleanType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/BooleanType.php', + 'Doctrine\\DBAL\\Types\\ConversionException' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/ConversionException.php', + 'Doctrine\\DBAL\\Types\\DateTimeType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/DateTimeType.php', + 'Doctrine\\DBAL\\Types\\DateTimeTzType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/DateTimeTzType.php', + 'Doctrine\\DBAL\\Types\\DateType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/DateType.php', + 'Doctrine\\DBAL\\Types\\DecimalType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/DecimalType.php', + 'Doctrine\\DBAL\\Types\\FloatType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/FloatType.php', + 'Doctrine\\DBAL\\Types\\GuidType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/GuidType.php', + 'Doctrine\\DBAL\\Types\\IntegerType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/IntegerType.php', + 'Doctrine\\DBAL\\Types\\JsonArrayType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/JsonArrayType.php', + 'Doctrine\\DBAL\\Types\\ObjectType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/ObjectType.php', + 'Doctrine\\DBAL\\Types\\SimpleArrayType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/SimpleArrayType.php', + 'Doctrine\\DBAL\\Types\\SmallIntType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/SmallIntType.php', + 'Doctrine\\DBAL\\Types\\StringType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/StringType.php', + 'Doctrine\\DBAL\\Types\\TextType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/TextType.php', + 'Doctrine\\DBAL\\Types\\TimeType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/TimeType.php', + 'Doctrine\\DBAL\\Types\\Type' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/Type.php', + 'Doctrine\\DBAL\\Types\\VarDateTimeType' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Types/VarDateTimeType.php', + 'Doctrine\\DBAL\\Version' => $vendorDir . '/doctrine/dbal/lib/Doctrine/DBAL/Version.php', + 'Dumbo' => $baseDir . '/app/models/Dumbo.php', + 'Film' => $baseDir . '/app/models/Film.php', + 'HomeController' => $baseDir . '/app/controllers/HomeController.php', + 'IlluminateQueueClosure' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/IlluminateQueueClosure.php', + 'Illuminate\\Auth\\AuthManager' => $vendorDir . '/laravel/framework/src/Illuminate/Auth/AuthManager.php', + 'Illuminate\\Auth\\AuthServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Auth/AuthServiceProvider.php', + 'Illuminate\\Auth\\Console\\MakeRemindersCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Auth/Console/MakeRemindersCommand.php', + 'Illuminate\\Auth\\DatabaseUserProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Auth/DatabaseUserProvider.php', + 'Illuminate\\Auth\\EloquentUserProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Auth/EloquentUserProvider.php', + 'Illuminate\\Auth\\GenericUser' => $vendorDir . '/laravel/framework/src/Illuminate/Auth/GenericUser.php', + 'Illuminate\\Auth\\Guard' => $vendorDir . '/laravel/framework/src/Illuminate/Auth/Guard.php', + 'Illuminate\\Auth\\Reminders\\DatabaseReminderRepository' => $vendorDir . '/laravel/framework/src/Illuminate/Auth/Reminders/DatabaseReminderRepository.php', + 'Illuminate\\Auth\\Reminders\\PasswordBroker' => $vendorDir . '/laravel/framework/src/Illuminate/Auth/Reminders/PasswordBroker.php', + 'Illuminate\\Auth\\Reminders\\RemindableInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Auth/Reminders/RemindableInterface.php', + 'Illuminate\\Auth\\Reminders\\ReminderRepositoryInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Auth/Reminders/ReminderRepositoryInterface.php', + 'Illuminate\\Auth\\Reminders\\ReminderServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Auth/Reminders/ReminderServiceProvider.php', + 'Illuminate\\Auth\\UserInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Auth/UserInterface.php', + 'Illuminate\\Auth\\UserProviderInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Auth/UserProviderInterface.php', + 'Illuminate\\Cache\\ApcStore' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/ApcStore.php', + 'Illuminate\\Cache\\ApcWrapper' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/ApcWrapper.php', + 'Illuminate\\Cache\\ArrayStore' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/ArrayStore.php', + 'Illuminate\\Cache\\CacheManager' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/CacheManager.php', + 'Illuminate\\Cache\\CacheServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/CacheServiceProvider.php', + 'Illuminate\\Cache\\Console\\ClearCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/Console/ClearCommand.php', + 'Illuminate\\Cache\\DatabaseStore' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/DatabaseStore.php', + 'Illuminate\\Cache\\FileStore' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/FileStore.php', + 'Illuminate\\Cache\\MemcachedConnector' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/MemcachedConnector.php', + 'Illuminate\\Cache\\MemcachedStore' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/MemcachedStore.php', + 'Illuminate\\Cache\\RedisSection' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/RedisSection.php', + 'Illuminate\\Cache\\RedisStore' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/RedisStore.php', + 'Illuminate\\Cache\\Repository' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/Repository.php', + 'Illuminate\\Cache\\Section' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/Section.php', + 'Illuminate\\Cache\\StoreInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/StoreInterface.php', + 'Illuminate\\Cache\\WinCacheStore' => $vendorDir . '/laravel/framework/src/Illuminate/Cache/WinCacheStore.php', + 'Illuminate\\Config\\FileLoader' => $vendorDir . '/laravel/framework/src/Illuminate/Config/FileLoader.php', + 'Illuminate\\Config\\LoaderInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Config/LoaderInterface.php', + 'Illuminate\\Config\\Repository' => $vendorDir . '/laravel/framework/src/Illuminate/Config/Repository.php', + 'Illuminate\\Console\\Application' => $vendorDir . '/laravel/framework/src/Illuminate/Console/Application.php', + 'Illuminate\\Console\\Command' => $vendorDir . '/laravel/framework/src/Illuminate/Console/Command.php', + 'Illuminate\\Container\\BindingResolutionException' => $vendorDir . '/laravel/framework/src/Illuminate/Container/Container.php', + 'Illuminate\\Container\\Container' => $vendorDir . '/laravel/framework/src/Illuminate/Container/Container.php', + 'Illuminate\\Cookie\\CookieJar' => $vendorDir . '/laravel/framework/src/Illuminate/Cookie/CookieJar.php', + 'Illuminate\\Cookie\\CookieServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Cookie/CookieServiceProvider.php', + 'Illuminate\\Database\\Capsule\\Manager' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Capsule/Manager.php', + 'Illuminate\\Database\\Connection' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Connection.php', + 'Illuminate\\Database\\ConnectionInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Database/ConnectionInterface.php', + 'Illuminate\\Database\\ConnectionResolver' => $vendorDir . '/laravel/framework/src/Illuminate/Database/ConnectionResolver.php', + 'Illuminate\\Database\\ConnectionResolverInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Database/ConnectionResolverInterface.php', + 'Illuminate\\Database\\Connectors\\ConnectionFactory' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Connectors/ConnectionFactory.php', + 'Illuminate\\Database\\Connectors\\Connector' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Connectors/Connector.php', + 'Illuminate\\Database\\Connectors\\ConnectorInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Connectors/ConnectorInterface.php', + 'Illuminate\\Database\\Connectors\\MySqlConnector' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Connectors/MySqlConnector.php', + 'Illuminate\\Database\\Connectors\\PostgresConnector' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Connectors/PostgresConnector.php', + 'Illuminate\\Database\\Connectors\\SQLiteConnector' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Connectors/SQLiteConnector.php', + 'Illuminate\\Database\\Connectors\\SqlServerConnector' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Connectors/SqlServerConnector.php', + 'Illuminate\\Database\\Console\\Migrations\\BaseCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Console/Migrations/BaseCommand.php', + 'Illuminate\\Database\\Console\\Migrations\\InstallCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Console/Migrations/InstallCommand.php', + 'Illuminate\\Database\\Console\\Migrations\\MakeCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Console/Migrations/MakeCommand.php', + 'Illuminate\\Database\\Console\\Migrations\\MigrateCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php', + 'Illuminate\\Database\\Console\\Migrations\\RefreshCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Console/Migrations/RefreshCommand.php', + 'Illuminate\\Database\\Console\\Migrations\\ResetCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Console/Migrations/ResetCommand.php', + 'Illuminate\\Database\\Console\\Migrations\\RollbackCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Console/Migrations/RollbackCommand.php', + 'Illuminate\\Database\\Console\\SeedCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Console/SeedCommand.php', + 'Illuminate\\Database\\DatabaseManager' => $vendorDir . '/laravel/framework/src/Illuminate/Database/DatabaseManager.php', + 'Illuminate\\Database\\DatabaseServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Database/DatabaseServiceProvider.php', + 'Illuminate\\Database\\Eloquent\\Builder' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php', + 'Illuminate\\Database\\Eloquent\\Collection' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Eloquent/Collection.php', + 'Illuminate\\Database\\Eloquent\\MassAssignmentException' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Eloquent/MassAssignmentException.php', + 'Illuminate\\Database\\Eloquent\\Model' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Eloquent/Model.php', + 'Illuminate\\Database\\Eloquent\\ModelNotFoundException' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Eloquent/ModelNotFoundException.php', + 'Illuminate\\Database\\Eloquent\\Relations\\BelongsTo' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php', + 'Illuminate\\Database\\Eloquent\\Relations\\BelongsToMany' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php', + 'Illuminate\\Database\\Eloquent\\Relations\\HasMany' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasMany.php', + 'Illuminate\\Database\\Eloquent\\Relations\\HasOne' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOne.php', + 'Illuminate\\Database\\Eloquent\\Relations\\HasOneOrMany' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php', + 'Illuminate\\Database\\Eloquent\\Relations\\MorphMany' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphMany.php', + 'Illuminate\\Database\\Eloquent\\Relations\\MorphOne' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOne.php', + 'Illuminate\\Database\\Eloquent\\Relations\\MorphOneOrMany' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php', + 'Illuminate\\Database\\Eloquent\\Relations\\Pivot' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Pivot.php', + 'Illuminate\\Database\\Eloquent\\Relations\\Relation' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Relation.php', + 'Illuminate\\Database\\Grammar' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Grammar.php', + 'Illuminate\\Database\\MigrationServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Database/MigrationServiceProvider.php', + 'Illuminate\\Database\\Migrations\\DatabaseMigrationRepository' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Migrations/DatabaseMigrationRepository.php', + 'Illuminate\\Database\\Migrations\\Migration' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Migrations/Migration.php', + 'Illuminate\\Database\\Migrations\\MigrationCreator' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Migrations/MigrationCreator.php', + 'Illuminate\\Database\\Migrations\\MigrationRepositoryInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Migrations/MigrationRepositoryInterface.php', + 'Illuminate\\Database\\Migrations\\Migrator' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Migrations/Migrator.php', + 'Illuminate\\Database\\MySqlConnection' => $vendorDir . '/laravel/framework/src/Illuminate/Database/MySqlConnection.php', + 'Illuminate\\Database\\PostgresConnection' => $vendorDir . '/laravel/framework/src/Illuminate/Database/PostgresConnection.php', + 'Illuminate\\Database\\Query\\Builder' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Query/Builder.php', + 'Illuminate\\Database\\Query\\Expression' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Query/Expression.php', + 'Illuminate\\Database\\Query\\Grammars\\Grammar' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php', + 'Illuminate\\Database\\Query\\Grammars\\MySqlGrammar' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php', + 'Illuminate\\Database\\Query\\Grammars\\PostgresGrammar' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php', + 'Illuminate\\Database\\Query\\Grammars\\SQLiteGrammar' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php', + 'Illuminate\\Database\\Query\\Grammars\\SqlServerGrammar' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php', + 'Illuminate\\Database\\Query\\JoinClause' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Query/JoinClause.php', + 'Illuminate\\Database\\Query\\Processors\\PostgresProcessor' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Query/Processors/PostgresProcessor.php', + 'Illuminate\\Database\\Query\\Processors\\Processor' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Query/Processors/Processor.php', + 'Illuminate\\Database\\Query\\Processors\\SqlServerProcessor' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php', + 'Illuminate\\Database\\SQLiteConnection' => $vendorDir . '/laravel/framework/src/Illuminate/Database/SQLiteConnection.php', + 'Illuminate\\Database\\Schema\\Blueprint' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php', + 'Illuminate\\Database\\Schema\\Builder' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Schema/Builder.php', + 'Illuminate\\Database\\Schema\\Grammars\\Grammar' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Schema/Grammars/Grammar.php', + 'Illuminate\\Database\\Schema\\Grammars\\MySqlGrammar' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php', + 'Illuminate\\Database\\Schema\\Grammars\\PostgresGrammar' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php', + 'Illuminate\\Database\\Schema\\Grammars\\SQLiteGrammar' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php', + 'Illuminate\\Database\\Schema\\Grammars\\SqlServerGrammar' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php', + 'Illuminate\\Database\\Schema\\MySqlBuilder' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Schema/MySqlBuilder.php', + 'Illuminate\\Database\\SeedServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Database/SeedServiceProvider.php', + 'Illuminate\\Database\\Seeder' => $vendorDir . '/laravel/framework/src/Illuminate/Database/Seeder.php', + 'Illuminate\\Database\\SqlServerConnection' => $vendorDir . '/laravel/framework/src/Illuminate/Database/SqlServerConnection.php', + 'Illuminate\\Encryption\\DecryptException' => $vendorDir . '/laravel/framework/src/Illuminate/Encryption/Encrypter.php', + 'Illuminate\\Encryption\\Encrypter' => $vendorDir . '/laravel/framework/src/Illuminate/Encryption/Encrypter.php', + 'Illuminate\\Encryption\\EncryptionServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Encryption/EncryptionServiceProvider.php', + 'Illuminate\\Events\\Dispatcher' => $vendorDir . '/laravel/framework/src/Illuminate/Events/Dispatcher.php', + 'Illuminate\\Events\\EventServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Events/EventServiceProvider.php', + 'Illuminate\\Events\\Subscriber' => $vendorDir . '/laravel/framework/src/Illuminate/Events/Subscriber.php', + 'Illuminate\\Exception\\ExceptionDisplayerInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Exception/ExceptionDisplayerInterface.php', + 'Illuminate\\Exception\\ExceptionServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Exception/ExceptionServiceProvider.php', + 'Illuminate\\Exception\\Handler' => $vendorDir . '/laravel/framework/src/Illuminate/Exception/Handler.php', + 'Illuminate\\Exception\\SymfonyDisplayer' => $vendorDir . '/laravel/framework/src/Illuminate/Exception/SymfonyDisplayer.php', + 'Illuminate\\Exception\\WhoopsDisplayer' => $vendorDir . '/laravel/framework/src/Illuminate/Exception/WhoopsDisplayer.php', + 'Illuminate\\Filesystem\\FileNotFoundException' => $vendorDir . '/laravel/framework/src/Illuminate/Filesystem/Filesystem.php', + 'Illuminate\\Filesystem\\Filesystem' => $vendorDir . '/laravel/framework/src/Illuminate/Filesystem/Filesystem.php', + 'Illuminate\\Filesystem\\FilesystemServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Filesystem/FilesystemServiceProvider.php', + 'Illuminate\\Foundation\\AliasLoader' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/AliasLoader.php', + 'Illuminate\\Foundation\\Application' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Application.php', + 'Illuminate\\Foundation\\Artisan' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Artisan.php', + 'Illuminate\\Foundation\\AssetPublisher' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/AssetPublisher.php', + 'Illuminate\\Foundation\\Composer' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Composer.php', + 'Illuminate\\Foundation\\ConfigPublisher' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/ConfigPublisher.php', + 'Illuminate\\Foundation\\Console\\AssetPublishCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Console/AssetPublishCommand.php', + 'Illuminate\\Foundation\\Console\\AutoloadCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Console/AutoloadCommand.php', + 'Illuminate\\Foundation\\Console\\ChangesCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Console/ChangesCommand.php', + 'Illuminate\\Foundation\\Console\\ClearCompiledCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Console/ClearCompiledCommand.php', + 'Illuminate\\Foundation\\Console\\CommandMakeCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Console/CommandMakeCommand.php', + 'Illuminate\\Foundation\\Console\\ConfigPublishCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Console/ConfigPublishCommand.php', + 'Illuminate\\Foundation\\Console\\DownCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Console/DownCommand.php', + 'Illuminate\\Foundation\\Console\\KeyGenerateCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Console/KeyGenerateCommand.php', + 'Illuminate\\Foundation\\Console\\OptimizeCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Console/OptimizeCommand.php', + 'Illuminate\\Foundation\\Console\\RoutesCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Console/RoutesCommand.php', + 'Illuminate\\Foundation\\Console\\ServeCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php', + 'Illuminate\\Foundation\\Console\\TinkerCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Console/TinkerCommand.php', + 'Illuminate\\Foundation\\Console\\UpCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Console/UpCommand.php', + 'Illuminate\\Foundation\\ProviderRepository' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/ProviderRepository.php', + 'Illuminate\\Foundation\\Providers\\ArtisanServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php', + 'Illuminate\\Foundation\\Providers\\CommandCreatorServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Providers/CommandCreatorServiceProvider.php', + 'Illuminate\\Foundation\\Providers\\ComposerServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Providers/ComposerServiceProvider.php', + 'Illuminate\\Foundation\\Providers\\KeyGeneratorServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Providers/KeyGeneratorServiceProvider.php', + 'Illuminate\\Foundation\\Providers\\MaintenanceServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Providers/MaintenanceServiceProvider.php', + 'Illuminate\\Foundation\\Providers\\OptimizeServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Providers/OptimizeServiceProvider.php', + 'Illuminate\\Foundation\\Providers\\PublisherServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Providers/PublisherServiceProvider.php', + 'Illuminate\\Foundation\\Providers\\RouteListServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Providers/RouteListServiceProvider.php', + 'Illuminate\\Foundation\\Providers\\ServerServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Providers/ServerServiceProvider.php', + 'Illuminate\\Foundation\\Providers\\TinkerServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Providers/TinkerServiceProvider.php', + 'Illuminate\\Foundation\\Testing\\Client' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Testing/Client.php', + 'Illuminate\\Foundation\\Testing\\TestCase' => $vendorDir . '/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php', + 'Illuminate\\Hashing\\BcryptHasher' => $vendorDir . '/laravel/framework/src/Illuminate/Hashing/BcryptHasher.php', + 'Illuminate\\Hashing\\HashServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Hashing/HashServiceProvider.php', + 'Illuminate\\Hashing\\HasherInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Hashing/HasherInterface.php', + 'Illuminate\\Html\\FormBuilder' => $vendorDir . '/laravel/framework/src/Illuminate/Html/FormBuilder.php', + 'Illuminate\\Html\\HtmlBuilder' => $vendorDir . '/laravel/framework/src/Illuminate/Html/HtmlBuilder.php', + 'Illuminate\\Html\\HtmlServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Html/HtmlServiceProvider.php', + 'Illuminate\\Http\\JsonResponse' => $vendorDir . '/laravel/framework/src/Illuminate/Http/JsonResponse.php', + 'Illuminate\\Http\\RedirectResponse' => $vendorDir . '/laravel/framework/src/Illuminate/Http/RedirectResponse.php', + 'Illuminate\\Http\\Request' => $vendorDir . '/laravel/framework/src/Illuminate/Http/Request.php', + 'Illuminate\\Http\\Response' => $vendorDir . '/laravel/framework/src/Illuminate/Http/Response.php', + 'Illuminate\\Log\\LogServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Log/LogServiceProvider.php', + 'Illuminate\\Log\\Writer' => $vendorDir . '/laravel/framework/src/Illuminate/Log/Writer.php', + 'Illuminate\\Mail\\MailServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Mail/MailServiceProvider.php', + 'Illuminate\\Mail\\Mailer' => $vendorDir . '/laravel/framework/src/Illuminate/Mail/Mailer.php', + 'Illuminate\\Mail\\Message' => $vendorDir . '/laravel/framework/src/Illuminate/Mail/Message.php', + 'Illuminate\\Pagination\\BootstrapPresenter' => $vendorDir . '/laravel/framework/src/Illuminate/Pagination/BootstrapPresenter.php', + 'Illuminate\\Pagination\\Environment' => $vendorDir . '/laravel/framework/src/Illuminate/Pagination/Environment.php', + 'Illuminate\\Pagination\\PaginationServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Pagination/PaginationServiceProvider.php', + 'Illuminate\\Pagination\\Paginator' => $vendorDir . '/laravel/framework/src/Illuminate/Pagination/Paginator.php', + 'Illuminate\\Queue\\BeanstalkdQueue' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/BeanstalkdQueue.php', + 'Illuminate\\Queue\\Connectors\\BeanstalkdConnector' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/Connectors/BeanstalkdConnector.php', + 'Illuminate\\Queue\\Connectors\\ConnectorInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/Connectors/ConnectorInterface.php', + 'Illuminate\\Queue\\Connectors\\IronConnector' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/Connectors/IronConnector.php', + 'Illuminate\\Queue\\Connectors\\SqsConnector' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/Connectors/SqsConnector.php', + 'Illuminate\\Queue\\Connectors\\SyncConnector' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/Connectors/SyncConnector.php', + 'Illuminate\\Queue\\Console\\ListenCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/Console/ListenCommand.php', + 'Illuminate\\Queue\\Console\\SubscribeCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/Console/SubscribeCommand.php', + 'Illuminate\\Queue\\Console\\WorkCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php', + 'Illuminate\\Queue\\IronQueue' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/IronQueue.php', + 'Illuminate\\Queue\\Jobs\\BeanstalkdJob' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/Jobs/BeanstalkdJob.php', + 'Illuminate\\Queue\\Jobs\\IronJob' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/Jobs/IronJob.php', + 'Illuminate\\Queue\\Jobs\\Job' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/Jobs/Job.php', + 'Illuminate\\Queue\\Jobs\\SqsJob' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/Jobs/SqsJob.php', + 'Illuminate\\Queue\\Jobs\\SyncJob' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/Jobs/SyncJob.php', + 'Illuminate\\Queue\\Listener' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/Listener.php', + 'Illuminate\\Queue\\Queue' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/Queue.php', + 'Illuminate\\Queue\\QueueInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/QueueInterface.php', + 'Illuminate\\Queue\\QueueManager' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/QueueManager.php', + 'Illuminate\\Queue\\QueueServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/QueueServiceProvider.php', + 'Illuminate\\Queue\\SqsQueue' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/SqsQueue.php', + 'Illuminate\\Queue\\SyncQueue' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/SyncQueue.php', + 'Illuminate\\Queue\\Worker' => $vendorDir . '/laravel/framework/src/Illuminate/Queue/Worker.php', + 'Illuminate\\Redis\\Database' => $vendorDir . '/laravel/framework/src/Illuminate/Redis/Database.php', + 'Illuminate\\Redis\\RedisServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Redis/RedisServiceProvider.php', + 'Illuminate\\Routing\\Console\\MakeControllerCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Routing/Console/MakeControllerCommand.php', + 'Illuminate\\Routing\\ControllerServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Routing/ControllerServiceProvider.php', + 'Illuminate\\Routing\\Controllers\\After' => $vendorDir . '/laravel/framework/src/Illuminate/Routing/Controllers/After.php', + 'Illuminate\\Routing\\Controllers\\Before' => $vendorDir . '/laravel/framework/src/Illuminate/Routing/Controllers/Before.php', + 'Illuminate\\Routing\\Controllers\\Controller' => $vendorDir . '/laravel/framework/src/Illuminate/Routing/Controllers/Controller.php', + 'Illuminate\\Routing\\Controllers\\Filter' => $vendorDir . '/laravel/framework/src/Illuminate/Routing/Controllers/Filter.php', + 'Illuminate\\Routing\\Controllers\\FilterParser' => $vendorDir . '/laravel/framework/src/Illuminate/Routing/Controllers/FilterParser.php', + 'Illuminate\\Routing\\Controllers\\Inspector' => $vendorDir . '/laravel/framework/src/Illuminate/Routing/Controllers/Inspector.php', + 'Illuminate\\Routing\\Generators\\ControllerGenerator' => $vendorDir . '/laravel/framework/src/Illuminate/Routing/Generators/ControllerGenerator.php', + 'Illuminate\\Routing\\Redirector' => $vendorDir . '/laravel/framework/src/Illuminate/Routing/Redirector.php', + 'Illuminate\\Routing\\Route' => $vendorDir . '/laravel/framework/src/Illuminate/Routing/Route.php', + 'Illuminate\\Routing\\Router' => $vendorDir . '/laravel/framework/src/Illuminate/Routing/Router.php', + 'Illuminate\\Routing\\RoutingServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Routing/RoutingServiceProvider.php', + 'Illuminate\\Routing\\UrlGenerator' => $vendorDir . '/laravel/framework/src/Illuminate/Routing/UrlGenerator.php', + 'Illuminate\\Session\\CacheBasedSessionHandler' => $vendorDir . '/laravel/framework/src/Illuminate/Session/CacheBasedSessionHandler.php', + 'Illuminate\\Session\\CommandsServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Session/CommandsServiceProvider.php', + 'Illuminate\\Session\\Console\\MakeTableCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Session/Console/MakeTableCommand.php', + 'Illuminate\\Session\\CookieSessionHandler' => $vendorDir . '/laravel/framework/src/Illuminate/Session/CookieSessionHandler.php', + 'Illuminate\\Session\\SessionManager' => $vendorDir . '/laravel/framework/src/Illuminate/Session/SessionManager.php', + 'Illuminate\\Session\\SessionServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Session/SessionServiceProvider.php', + 'Illuminate\\Session\\Store' => $vendorDir . '/laravel/framework/src/Illuminate/Session/Store.php', + 'Illuminate\\Session\\TokenMismatchException' => $vendorDir . '/laravel/framework/src/Illuminate/Session/TokenMismatchException.php', + 'Illuminate\\Support\\ClassLoader' => $vendorDir . '/laravel/framework/src/Illuminate/Support/ClassLoader.php', + 'Illuminate\\Support\\Collection' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Collection.php', + 'Illuminate\\Support\\Contracts\\ArrayableInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Contracts/ArrayableInterface.php', + 'Illuminate\\Support\\Contracts\\JsonableInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Contracts/JsonableInterface.php', + 'Illuminate\\Support\\Contracts\\MessageProviderInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Contracts/MessageProviderInterface.php', + 'Illuminate\\Support\\Contracts\\RenderableInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Contracts/RenderableInterface.php', + 'Illuminate\\Support\\Contracts\\ResponsePreparerInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Contracts/ResponsePreparerInterface.php', + 'Illuminate\\Support\\Facades\\App' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/App.php', + 'Illuminate\\Support\\Facades\\Artisan' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Artisan.php', + 'Illuminate\\Support\\Facades\\Auth' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Auth.php', + 'Illuminate\\Support\\Facades\\Blade' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Blade.php', + 'Illuminate\\Support\\Facades\\Cache' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Cache.php', + 'Illuminate\\Support\\Facades\\Config' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Config.php', + 'Illuminate\\Support\\Facades\\Cookie' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Cookie.php', + 'Illuminate\\Support\\Facades\\Crypt' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Crypt.php', + 'Illuminate\\Support\\Facades\\DB' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/DB.php', + 'Illuminate\\Support\\Facades\\Event' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Event.php', + 'Illuminate\\Support\\Facades\\Facade' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Facade.php', + 'Illuminate\\Support\\Facades\\File' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/File.php', + 'Illuminate\\Support\\Facades\\Form' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Form.php', + 'Illuminate\\Support\\Facades\\HTML' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/HTML.php', + 'Illuminate\\Support\\Facades\\Hash' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Hash.php', + 'Illuminate\\Support\\Facades\\Input' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Input.php', + 'Illuminate\\Support\\Facades\\Lang' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Lang.php', + 'Illuminate\\Support\\Facades\\Log' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Log.php', + 'Illuminate\\Support\\Facades\\Mail' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Mail.php', + 'Illuminate\\Support\\Facades\\Paginator' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Paginator.php', + 'Illuminate\\Support\\Facades\\Password' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Password.php', + 'Illuminate\\Support\\Facades\\Queue' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Queue.php', + 'Illuminate\\Support\\Facades\\Redirect' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Redirect.php', + 'Illuminate\\Support\\Facades\\Redis' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Redis.php', + 'Illuminate\\Support\\Facades\\Request' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Request.php', + 'Illuminate\\Support\\Facades\\Response' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Response.php', + 'Illuminate\\Support\\Facades\\Route' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Route.php', + 'Illuminate\\Support\\Facades\\Schema' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Schema.php', + 'Illuminate\\Support\\Facades\\Session' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Session.php', + 'Illuminate\\Support\\Facades\\URL' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/URL.php', + 'Illuminate\\Support\\Facades\\Validator' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/Validator.php', + 'Illuminate\\Support\\Facades\\View' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Facades/View.php', + 'Illuminate\\Support\\Fluent' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Fluent.php', + 'Illuminate\\Support\\Manager' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Manager.php', + 'Illuminate\\Support\\MessageBag' => $vendorDir . '/laravel/framework/src/Illuminate/Support/MessageBag.php', + 'Illuminate\\Support\\NamespacedItemResolver' => $vendorDir . '/laravel/framework/src/Illuminate/Support/NamespacedItemResolver.php', + 'Illuminate\\Support\\Pluralizer' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Pluralizer.php', + 'Illuminate\\Support\\SerializableClosure' => $vendorDir . '/laravel/framework/src/Illuminate/Support/SerializableClosure.php', + 'Illuminate\\Support\\ServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Support/ServiceProvider.php', + 'Illuminate\\Support\\Str' => $vendorDir . '/laravel/framework/src/Illuminate/Support/Str.php', + 'Illuminate\\Translation\\FileLoader' => $vendorDir . '/laravel/framework/src/Illuminate/Translation/FileLoader.php', + 'Illuminate\\Translation\\LoaderInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Translation/LoaderInterface.php', + 'Illuminate\\Translation\\TranslationServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Translation/TranslationServiceProvider.php', + 'Illuminate\\Translation\\Translator' => $vendorDir . '/laravel/framework/src/Illuminate/Translation/Translator.php', + 'Illuminate\\Validation\\DatabasePresenceVerifier' => $vendorDir . '/laravel/framework/src/Illuminate/Validation/DatabasePresenceVerifier.php', + 'Illuminate\\Validation\\Factory' => $vendorDir . '/laravel/framework/src/Illuminate/Validation/Factory.php', + 'Illuminate\\Validation\\PresenceVerifierInterface' => $vendorDir . '/laravel/framework/src/Illuminate/Validation/PresenceVerifierInterface.php', + 'Illuminate\\Validation\\ValidationServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Validation/ValidationServiceProvider.php', + 'Illuminate\\Validation\\Validator' => $vendorDir . '/laravel/framework/src/Illuminate/Validation/Validator.php', + 'Illuminate\\View\\Compilers\\BladeCompiler' => $vendorDir . '/laravel/framework/src/Illuminate/View/Compilers/BladeCompiler.php', + 'Illuminate\\View\\Compilers\\Compiler' => $vendorDir . '/laravel/framework/src/Illuminate/View/Compilers/Compiler.php', + 'Illuminate\\View\\Compilers\\CompilerInterface' => $vendorDir . '/laravel/framework/src/Illuminate/View/Compilers/CompilerInterface.php', + 'Illuminate\\View\\Engines\\CompilerEngine' => $vendorDir . '/laravel/framework/src/Illuminate/View/Engines/CompilerEngine.php', + 'Illuminate\\View\\Engines\\Engine' => $vendorDir . '/laravel/framework/src/Illuminate/View/Engines/Engine.php', + 'Illuminate\\View\\Engines\\EngineInterface' => $vendorDir . '/laravel/framework/src/Illuminate/View/Engines/EngineInterface.php', + 'Illuminate\\View\\Engines\\EngineResolver' => $vendorDir . '/laravel/framework/src/Illuminate/View/Engines/EngineResolver.php', + 'Illuminate\\View\\Engines\\PhpEngine' => $vendorDir . '/laravel/framework/src/Illuminate/View/Engines/PhpEngine.php', + 'Illuminate\\View\\Environment' => $vendorDir . '/laravel/framework/src/Illuminate/View/Environment.php', + 'Illuminate\\View\\FileViewFinder' => $vendorDir . '/laravel/framework/src/Illuminate/View/FileViewFinder.php', + 'Illuminate\\View\\View' => $vendorDir . '/laravel/framework/src/Illuminate/View/View.php', + 'Illuminate\\View\\ViewFinderInterface' => $vendorDir . '/laravel/framework/src/Illuminate/View/ViewFinderInterface.php', + 'Illuminate\\View\\ViewServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/View/ViewServiceProvider.php', + 'Illuminate\\Workbench\\Console\\WorkbenchMakeCommand' => $vendorDir . '/laravel/framework/src/Illuminate/Workbench/Console/WorkbenchMakeCommand.php', + 'Illuminate\\Workbench\\Package' => $vendorDir . '/laravel/framework/src/Illuminate/Workbench/Package.php', + 'Illuminate\\Workbench\\PackageCreator' => $vendorDir . '/laravel/framework/src/Illuminate/Workbench/PackageCreator.php', + 'Illuminate\\Workbench\\Starter' => $vendorDir . '/laravel/framework/src/Illuminate/Workbench/Starter.php', + 'Illuminate\\Workbench\\WorkbenchServiceProvider' => $vendorDir . '/laravel/framework/src/Illuminate/Workbench/WorkbenchServiceProvider.php', + 'Monolog\\ErrorHandler' => $vendorDir . '/monolog/monolog/src/Monolog/ErrorHandler.php', + 'Monolog\\Formatter\\ChromePHPFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php', + 'Monolog\\Formatter\\FormatterInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php', + 'Monolog\\Formatter\\GelfMessageFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php', + 'Monolog\\Formatter\\JsonFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php', + 'Monolog\\Formatter\\LineFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/LineFormatter.php', + 'Monolog\\Formatter\\LogstashFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php', + 'Monolog\\Formatter\\NormalizerFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php', + 'Monolog\\Formatter\\WildfireFormatter' => $vendorDir . '/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php', + 'Monolog\\Handler\\AbstractHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AbstractHandler.php', + 'Monolog\\Handler\\AbstractProcessingHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php', + 'Monolog\\Handler\\AmqpHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/AmqpHandler.php', + 'Monolog\\Handler\\BufferHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/BufferHandler.php', + 'Monolog\\Handler\\ChromePHPHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php', + 'Monolog\\Handler\\CouchDBHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php', + 'Monolog\\Handler\\CubeHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/CubeHandler.php', + 'Monolog\\Handler\\DoctrineCouchDBHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php', + 'Monolog\\Handler\\ErrorLogHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php', + 'Monolog\\Handler\\FingersCrossedHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php', + 'Monolog\\Handler\\FingersCrossed\\ActivationStrategyInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php', + 'Monolog\\Handler\\FingersCrossed\\ChannelLevelActivationStrategy' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php', + 'Monolog\\Handler\\FingersCrossed\\ErrorLevelActivationStrategy' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php', + 'Monolog\\Handler\\FirePHPHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php', + 'Monolog\\Handler\\GelfHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/GelfHandler.php', + 'Monolog\\Handler\\GroupHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/GroupHandler.php', + 'Monolog\\Handler\\HandlerInterface' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/HandlerInterface.php', + 'Monolog\\Handler\\HipChatHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/HipChatHandler.php', + 'Monolog\\Handler\\MailHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MailHandler.php', + 'Monolog\\Handler\\MissingExtensionException' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php', + 'Monolog\\Handler\\MongoDBHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php', + 'Monolog\\Handler\\NativeMailerHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php', + 'Monolog\\Handler\\NewRelicHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php', + 'Monolog\\Handler\\NullHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/NullHandler.php', + 'Monolog\\Handler\\PushoverHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/PushoverHandler.php', + 'Monolog\\Handler\\RavenHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RavenHandler.php', + 'Monolog\\Handler\\RedisHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RedisHandler.php', + 'Monolog\\Handler\\RotatingFileHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php', + 'Monolog\\Handler\\SocketHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SocketHandler.php', + 'Monolog\\Handler\\StreamHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/StreamHandler.php', + 'Monolog\\Handler\\SwiftMailerHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php', + 'Monolog\\Handler\\SyslogHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/SyslogHandler.php', + 'Monolog\\Handler\\TestHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/TestHandler.php', + 'Monolog\\Handler\\ZendMonitorHandler' => $vendorDir . '/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php', + 'Monolog\\Logger' => $vendorDir . '/monolog/monolog/src/Monolog/Logger.php', + 'Monolog\\Processor\\IntrospectionProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php', + 'Monolog\\Processor\\MemoryPeakUsageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php', + 'Monolog\\Processor\\MemoryProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php', + 'Monolog\\Processor\\MemoryUsageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php', + 'Monolog\\Processor\\ProcessIdProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php', + 'Monolog\\Processor\\PsrLogMessageProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php', + 'Monolog\\Processor\\UidProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/UidProcessor.php', + 'Monolog\\Processor\\WebProcessor' => $vendorDir . '/monolog/monolog/src/Monolog/Processor/WebProcessor.php', + 'News' => $baseDir . '/app/models/News.php', + 'Normalizer' => $vendorDir . '/patchwork/utf8/class/Normalizer.php', + 'PHPParser_Autoloader' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Autoloader.php', + 'PHPParser_Builder' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Builder.php', + 'PHPParser_BuilderAbstract' => $vendorDir . '/nikic/php-parser/lib/PHPParser/BuilderAbstract.php', + 'PHPParser_BuilderFactory' => $vendorDir . '/nikic/php-parser/lib/PHPParser/BuilderFactory.php', + 'PHPParser_Builder_Class' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Builder/Class.php', + 'PHPParser_Builder_Function' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Builder/Function.php', + 'PHPParser_Builder_Interface' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Builder/Interface.php', + 'PHPParser_Builder_Method' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Builder/Method.php', + 'PHPParser_Builder_Param' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Builder/Param.php', + 'PHPParser_Builder_Property' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Builder/Property.php', + 'PHPParser_Comment' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Comment.php', + 'PHPParser_Comment_Doc' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Comment/Doc.php', + 'PHPParser_Error' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Error.php', + 'PHPParser_Lexer' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Lexer.php', + 'PHPParser_Lexer_Emulative' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Lexer/Emulative.php', + 'PHPParser_Node' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node.php', + 'PHPParser_NodeAbstract' => $vendorDir . '/nikic/php-parser/lib/PHPParser/NodeAbstract.php', + 'PHPParser_NodeDumper' => $vendorDir . '/nikic/php-parser/lib/PHPParser/NodeDumper.php', + 'PHPParser_NodeTraverser' => $vendorDir . '/nikic/php-parser/lib/PHPParser/NodeTraverser.php', + 'PHPParser_NodeTraverserInterface' => $vendorDir . '/nikic/php-parser/lib/PHPParser/NodeTraverserInterface.php', + 'PHPParser_NodeVisitor' => $vendorDir . '/nikic/php-parser/lib/PHPParser/NodeVisitor.php', + 'PHPParser_NodeVisitorAbstract' => $vendorDir . '/nikic/php-parser/lib/PHPParser/NodeVisitorAbstract.php', + 'PHPParser_NodeVisitor_NameResolver' => $vendorDir . '/nikic/php-parser/lib/PHPParser/NodeVisitor/NameResolver.php', + 'PHPParser_Node_Arg' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Arg.php', + 'PHPParser_Node_Const' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Const.php', + 'PHPParser_Node_Expr' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr.php', + 'PHPParser_Node_Expr_Array' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Array.php', + 'PHPParser_Node_Expr_ArrayDimFetch' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/ArrayDimFetch.php', + 'PHPParser_Node_Expr_ArrayItem' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/ArrayItem.php', + 'PHPParser_Node_Expr_Assign' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Assign.php', + 'PHPParser_Node_Expr_AssignBitwiseAnd' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/AssignBitwiseAnd.php', + 'PHPParser_Node_Expr_AssignBitwiseOr' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/AssignBitwiseOr.php', + 'PHPParser_Node_Expr_AssignBitwiseXor' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/AssignBitwiseXor.php', + 'PHPParser_Node_Expr_AssignConcat' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/AssignConcat.php', + 'PHPParser_Node_Expr_AssignDiv' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/AssignDiv.php', + 'PHPParser_Node_Expr_AssignMinus' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/AssignMinus.php', + 'PHPParser_Node_Expr_AssignMod' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/AssignMod.php', + 'PHPParser_Node_Expr_AssignMul' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/AssignMul.php', + 'PHPParser_Node_Expr_AssignPlus' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/AssignPlus.php', + 'PHPParser_Node_Expr_AssignRef' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/AssignRef.php', + 'PHPParser_Node_Expr_AssignShiftLeft' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/AssignShiftLeft.php', + 'PHPParser_Node_Expr_AssignShiftRight' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/AssignShiftRight.php', + 'PHPParser_Node_Expr_BitwiseAnd' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/BitwiseAnd.php', + 'PHPParser_Node_Expr_BitwiseNot' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/BitwiseNot.php', + 'PHPParser_Node_Expr_BitwiseOr' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/BitwiseOr.php', + 'PHPParser_Node_Expr_BitwiseXor' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/BitwiseXor.php', + 'PHPParser_Node_Expr_BooleanAnd' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/BooleanAnd.php', + 'PHPParser_Node_Expr_BooleanNot' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/BooleanNot.php', + 'PHPParser_Node_Expr_BooleanOr' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/BooleanOr.php', + 'PHPParser_Node_Expr_Cast' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Cast.php', + 'PHPParser_Node_Expr_Cast_Array' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Cast/Array.php', + 'PHPParser_Node_Expr_Cast_Bool' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Cast/Bool.php', + 'PHPParser_Node_Expr_Cast_Double' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Cast/Double.php', + 'PHPParser_Node_Expr_Cast_Int' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Cast/Int.php', + 'PHPParser_Node_Expr_Cast_Object' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Cast/Object.php', + 'PHPParser_Node_Expr_Cast_String' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Cast/String.php', + 'PHPParser_Node_Expr_Cast_Unset' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Cast/Unset.php', + 'PHPParser_Node_Expr_ClassConstFetch' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/ClassConstFetch.php', + 'PHPParser_Node_Expr_Clone' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Clone.php', + 'PHPParser_Node_Expr_Closure' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Closure.php', + 'PHPParser_Node_Expr_ClosureUse' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/ClosureUse.php', + 'PHPParser_Node_Expr_Concat' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Concat.php', + 'PHPParser_Node_Expr_ConstFetch' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/ConstFetch.php', + 'PHPParser_Node_Expr_Div' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Div.php', + 'PHPParser_Node_Expr_Empty' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Empty.php', + 'PHPParser_Node_Expr_Equal' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Equal.php', + 'PHPParser_Node_Expr_ErrorSuppress' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/ErrorSuppress.php', + 'PHPParser_Node_Expr_Eval' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Eval.php', + 'PHPParser_Node_Expr_Exit' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Exit.php', + 'PHPParser_Node_Expr_FuncCall' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/FuncCall.php', + 'PHPParser_Node_Expr_Greater' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Greater.php', + 'PHPParser_Node_Expr_GreaterOrEqual' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/GreaterOrEqual.php', + 'PHPParser_Node_Expr_Identical' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Identical.php', + 'PHPParser_Node_Expr_Include' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Include.php', + 'PHPParser_Node_Expr_Instanceof' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Instanceof.php', + 'PHPParser_Node_Expr_Isset' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Isset.php', + 'PHPParser_Node_Expr_List' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/List.php', + 'PHPParser_Node_Expr_LogicalAnd' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/LogicalAnd.php', + 'PHPParser_Node_Expr_LogicalOr' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/LogicalOr.php', + 'PHPParser_Node_Expr_LogicalXor' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/LogicalXor.php', + 'PHPParser_Node_Expr_MethodCall' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/MethodCall.php', + 'PHPParser_Node_Expr_Minus' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Minus.php', + 'PHPParser_Node_Expr_Mod' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Mod.php', + 'PHPParser_Node_Expr_Mul' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Mul.php', + 'PHPParser_Node_Expr_New' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/New.php', + 'PHPParser_Node_Expr_NotEqual' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/NotEqual.php', + 'PHPParser_Node_Expr_NotIdentical' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/NotIdentical.php', + 'PHPParser_Node_Expr_Plus' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Plus.php', + 'PHPParser_Node_Expr_PostDec' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/PostDec.php', + 'PHPParser_Node_Expr_PostInc' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/PostInc.php', + 'PHPParser_Node_Expr_PreDec' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/PreDec.php', + 'PHPParser_Node_Expr_PreInc' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/PreInc.php', + 'PHPParser_Node_Expr_Print' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Print.php', + 'PHPParser_Node_Expr_PropertyFetch' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/PropertyFetch.php', + 'PHPParser_Node_Expr_ShellExec' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/ShellExec.php', + 'PHPParser_Node_Expr_ShiftLeft' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/ShiftLeft.php', + 'PHPParser_Node_Expr_ShiftRight' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/ShiftRight.php', + 'PHPParser_Node_Expr_Smaller' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Smaller.php', + 'PHPParser_Node_Expr_SmallerOrEqual' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/SmallerOrEqual.php', + 'PHPParser_Node_Expr_StaticCall' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/StaticCall.php', + 'PHPParser_Node_Expr_StaticPropertyFetch' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/StaticPropertyFetch.php', + 'PHPParser_Node_Expr_Ternary' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Ternary.php', + 'PHPParser_Node_Expr_UnaryMinus' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/UnaryMinus.php', + 'PHPParser_Node_Expr_UnaryPlus' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/UnaryPlus.php', + 'PHPParser_Node_Expr_Variable' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Variable.php', + 'PHPParser_Node_Expr_Yield' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Expr/Yield.php', + 'PHPParser_Node_Name' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Name.php', + 'PHPParser_Node_Name_FullyQualified' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Name/FullyQualified.php', + 'PHPParser_Node_Name_Relative' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Name/Relative.php', + 'PHPParser_Node_Param' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Param.php', + 'PHPParser_Node_Scalar' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Scalar.php', + 'PHPParser_Node_Scalar_ClassConst' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Scalar/ClassConst.php', + 'PHPParser_Node_Scalar_DNumber' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Scalar/DNumber.php', + 'PHPParser_Node_Scalar_DirConst' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Scalar/DirConst.php', + 'PHPParser_Node_Scalar_Encapsed' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Scalar/Encapsed.php', + 'PHPParser_Node_Scalar_FileConst' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Scalar/FileConst.php', + 'PHPParser_Node_Scalar_FuncConst' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Scalar/FuncConst.php', + 'PHPParser_Node_Scalar_LNumber' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Scalar/LNumber.php', + 'PHPParser_Node_Scalar_LineConst' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Scalar/LineConst.php', + 'PHPParser_Node_Scalar_MethodConst' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Scalar/MethodConst.php', + 'PHPParser_Node_Scalar_NSConst' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Scalar/NSConst.php', + 'PHPParser_Node_Scalar_String' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Scalar/String.php', + 'PHPParser_Node_Scalar_TraitConst' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Scalar/TraitConst.php', + 'PHPParser_Node_Stmt' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt.php', + 'PHPParser_Node_Stmt_Break' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Break.php', + 'PHPParser_Node_Stmt_Case' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Case.php', + 'PHPParser_Node_Stmt_Catch' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Catch.php', + 'PHPParser_Node_Stmt_Class' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Class.php', + 'PHPParser_Node_Stmt_ClassConst' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/ClassConst.php', + 'PHPParser_Node_Stmt_ClassMethod' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/ClassMethod.php', + 'PHPParser_Node_Stmt_Const' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Const.php', + 'PHPParser_Node_Stmt_Continue' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Continue.php', + 'PHPParser_Node_Stmt_Declare' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Declare.php', + 'PHPParser_Node_Stmt_DeclareDeclare' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/DeclareDeclare.php', + 'PHPParser_Node_Stmt_Do' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Do.php', + 'PHPParser_Node_Stmt_Echo' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Echo.php', + 'PHPParser_Node_Stmt_Else' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Else.php', + 'PHPParser_Node_Stmt_ElseIf' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/ElseIf.php', + 'PHPParser_Node_Stmt_For' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/For.php', + 'PHPParser_Node_Stmt_Foreach' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Foreach.php', + 'PHPParser_Node_Stmt_Function' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Function.php', + 'PHPParser_Node_Stmt_Global' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Global.php', + 'PHPParser_Node_Stmt_Goto' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Goto.php', + 'PHPParser_Node_Stmt_HaltCompiler' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/HaltCompiler.php', + 'PHPParser_Node_Stmt_If' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/If.php', + 'PHPParser_Node_Stmt_InlineHTML' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/InlineHTML.php', + 'PHPParser_Node_Stmt_Interface' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Interface.php', + 'PHPParser_Node_Stmt_Label' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Label.php', + 'PHPParser_Node_Stmt_Namespace' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Namespace.php', + 'PHPParser_Node_Stmt_Property' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Property.php', + 'PHPParser_Node_Stmt_PropertyProperty' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/PropertyProperty.php', + 'PHPParser_Node_Stmt_Return' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Return.php', + 'PHPParser_Node_Stmt_Static' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Static.php', + 'PHPParser_Node_Stmt_StaticVar' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/StaticVar.php', + 'PHPParser_Node_Stmt_Switch' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Switch.php', + 'PHPParser_Node_Stmt_Throw' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Throw.php', + 'PHPParser_Node_Stmt_Trait' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Trait.php', + 'PHPParser_Node_Stmt_TraitUse' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/TraitUse.php', + 'PHPParser_Node_Stmt_TraitUseAdaptation' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/TraitUseAdaptation.php', + 'PHPParser_Node_Stmt_TraitUseAdaptation_Alias' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/TraitUseAdaptation/Alias.php', + 'PHPParser_Node_Stmt_TraitUseAdaptation_Precedence' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/TraitUseAdaptation/Precedence.php', + 'PHPParser_Node_Stmt_TryCatch' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/TryCatch.php', + 'PHPParser_Node_Stmt_Unset' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Unset.php', + 'PHPParser_Node_Stmt_Use' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/Use.php', + 'PHPParser_Node_Stmt_UseUse' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/UseUse.php', + 'PHPParser_Node_Stmt_While' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Node/Stmt/While.php', + 'PHPParser_Parser' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Parser.php', + 'PHPParser_PrettyPrinterAbstract' => $vendorDir . '/nikic/php-parser/lib/PHPParser/PrettyPrinterAbstract.php', + 'PHPParser_PrettyPrinter_Default' => $vendorDir . '/nikic/php-parser/lib/PHPParser/PrettyPrinter/Default.php', + 'PHPParser_PrettyPrinter_Zend' => $vendorDir . '/nikic/php-parser/lib/PHPParser/PrettyPrinter/Zend.php', + 'PHPParser_Serializer' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Serializer.php', + 'PHPParser_Serializer_XML' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Serializer/XML.php', + 'PHPParser_Template' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Template.php', + 'PHPParser_TemplateLoader' => $vendorDir . '/nikic/php-parser/lib/PHPParser/TemplateLoader.php', + 'PHPParser_Unserializer' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Unserializer.php', + 'PHPParser_Unserializer_XML' => $vendorDir . '/nikic/php-parser/lib/PHPParser/Unserializer/XML.php', + 'Patchwork\\PHP\\Shim\\Iconv' => $vendorDir . '/patchwork/utf8/class/Patchwork/PHP/Shim/Iconv.php', + 'Patchwork\\PHP\\Shim\\Intl' => $vendorDir . '/patchwork/utf8/class/Patchwork/PHP/Shim/Intl.php', + 'Patchwork\\PHP\\Shim\\Mbstring' => $vendorDir . '/patchwork/utf8/class/Patchwork/PHP/Shim/Mbstring.php', + 'Patchwork\\PHP\\Shim\\Normalizer' => $vendorDir . '/patchwork/utf8/class/Patchwork/PHP/Shim/Normalizer.php', + 'Patchwork\\PHP\\Shim\\Xml' => $vendorDir . '/patchwork/utf8/class/Patchwork/PHP/Shim/Xml.php', + 'Patchwork\\Utf8' => $vendorDir . '/patchwork/utf8/class/Patchwork/Utf8.php', + 'Patchwork\\Utf8\\Bootup' => $vendorDir . '/patchwork/utf8/class/Patchwork/Utf8/Bootup.php', + 'Predis\\Autoloader' => $vendorDir . '/predis/predis/lib/Predis/Autoloader.php', + 'Predis\\BasicClientInterface' => $vendorDir . '/predis/predis/lib/Predis/BasicClientInterface.php', + 'Predis\\Client' => $vendorDir . '/predis/predis/lib/Predis/Client.php', + 'Predis\\ClientException' => $vendorDir . '/predis/predis/lib/Predis/ClientException.php', + 'Predis\\ClientInterface' => $vendorDir . '/predis/predis/lib/Predis/ClientInterface.php', + 'Predis\\Cluster\\CommandHashStrategyInterface' => $vendorDir . '/predis/predis/lib/Predis/Cluster/CommandHashStrategyInterface.php', + 'Predis\\Cluster\\Distribution\\DistributionStrategyInterface' => $vendorDir . '/predis/predis/lib/Predis/Cluster/Distribution/DistributionStrategyInterface.php', + 'Predis\\Cluster\\Distribution\\EmptyRingException' => $vendorDir . '/predis/predis/lib/Predis/Cluster/Distribution/EmptyRingException.php', + 'Predis\\Cluster\\Distribution\\HashRing' => $vendorDir . '/predis/predis/lib/Predis/Cluster/Distribution/HashRing.php', + 'Predis\\Cluster\\Distribution\\KetamaPureRing' => $vendorDir . '/predis/predis/lib/Predis/Cluster/Distribution/KetamaPureRing.php', + 'Predis\\Cluster\\Hash\\CRC16HashGenerator' => $vendorDir . '/predis/predis/lib/Predis/Cluster/Hash/CRC16HashGenerator.php', + 'Predis\\Cluster\\Hash\\HashGeneratorInterface' => $vendorDir . '/predis/predis/lib/Predis/Cluster/Hash/HashGeneratorInterface.php', + 'Predis\\Cluster\\PredisClusterHashStrategy' => $vendorDir . '/predis/predis/lib/Predis/Cluster/PredisClusterHashStrategy.php', + 'Predis\\Cluster\\RedisClusterHashStrategy' => $vendorDir . '/predis/predis/lib/Predis/Cluster/RedisClusterHashStrategy.php', + 'Predis\\Command\\AbstractCommand' => $vendorDir . '/predis/predis/lib/Predis/Command/AbstractCommand.php', + 'Predis\\Command\\CommandInterface' => $vendorDir . '/predis/predis/lib/Predis/Command/CommandInterface.php', + 'Predis\\Command\\ConnectionAuth' => $vendorDir . '/predis/predis/lib/Predis/Command/ConnectionAuth.php', + 'Predis\\Command\\ConnectionEcho' => $vendorDir . '/predis/predis/lib/Predis/Command/ConnectionEcho.php', + 'Predis\\Command\\ConnectionPing' => $vendorDir . '/predis/predis/lib/Predis/Command/ConnectionPing.php', + 'Predis\\Command\\ConnectionQuit' => $vendorDir . '/predis/predis/lib/Predis/Command/ConnectionQuit.php', + 'Predis\\Command\\ConnectionSelect' => $vendorDir . '/predis/predis/lib/Predis/Command/ConnectionSelect.php', + 'Predis\\Command\\HashDelete' => $vendorDir . '/predis/predis/lib/Predis/Command/HashDelete.php', + 'Predis\\Command\\HashExists' => $vendorDir . '/predis/predis/lib/Predis/Command/HashExists.php', + 'Predis\\Command\\HashGet' => $vendorDir . '/predis/predis/lib/Predis/Command/HashGet.php', + 'Predis\\Command\\HashGetAll' => $vendorDir . '/predis/predis/lib/Predis/Command/HashGetAll.php', + 'Predis\\Command\\HashGetMultiple' => $vendorDir . '/predis/predis/lib/Predis/Command/HashGetMultiple.php', + 'Predis\\Command\\HashIncrementBy' => $vendorDir . '/predis/predis/lib/Predis/Command/HashIncrementBy.php', + 'Predis\\Command\\HashIncrementByFloat' => $vendorDir . '/predis/predis/lib/Predis/Command/HashIncrementByFloat.php', + 'Predis\\Command\\HashKeys' => $vendorDir . '/predis/predis/lib/Predis/Command/HashKeys.php', + 'Predis\\Command\\HashLength' => $vendorDir . '/predis/predis/lib/Predis/Command/HashLength.php', + 'Predis\\Command\\HashSet' => $vendorDir . '/predis/predis/lib/Predis/Command/HashSet.php', + 'Predis\\Command\\HashSetMultiple' => $vendorDir . '/predis/predis/lib/Predis/Command/HashSetMultiple.php', + 'Predis\\Command\\HashSetPreserve' => $vendorDir . '/predis/predis/lib/Predis/Command/HashSetPreserve.php', + 'Predis\\Command\\HashValues' => $vendorDir . '/predis/predis/lib/Predis/Command/HashValues.php', + 'Predis\\Command\\KeyDelete' => $vendorDir . '/predis/predis/lib/Predis/Command/KeyDelete.php', + 'Predis\\Command\\KeyDump' => $vendorDir . '/predis/predis/lib/Predis/Command/KeyDump.php', + 'Predis\\Command\\KeyExists' => $vendorDir . '/predis/predis/lib/Predis/Command/KeyExists.php', + 'Predis\\Command\\KeyExpire' => $vendorDir . '/predis/predis/lib/Predis/Command/KeyExpire.php', + 'Predis\\Command\\KeyExpireAt' => $vendorDir . '/predis/predis/lib/Predis/Command/KeyExpireAt.php', + 'Predis\\Command\\KeyKeys' => $vendorDir . '/predis/predis/lib/Predis/Command/KeyKeys.php', + 'Predis\\Command\\KeyKeysV12x' => $vendorDir . '/predis/predis/lib/Predis/Command/KeyKeysV12x.php', + 'Predis\\Command\\KeyMove' => $vendorDir . '/predis/predis/lib/Predis/Command/KeyMove.php', + 'Predis\\Command\\KeyPersist' => $vendorDir . '/predis/predis/lib/Predis/Command/KeyPersist.php', + 'Predis\\Command\\KeyPreciseExpire' => $vendorDir . '/predis/predis/lib/Predis/Command/KeyPreciseExpire.php', + 'Predis\\Command\\KeyPreciseExpireAt' => $vendorDir . '/predis/predis/lib/Predis/Command/KeyPreciseExpireAt.php', + 'Predis\\Command\\KeyPreciseTimeToLive' => $vendorDir . '/predis/predis/lib/Predis/Command/KeyPreciseTimeToLive.php', + 'Predis\\Command\\KeyRandom' => $vendorDir . '/predis/predis/lib/Predis/Command/KeyRandom.php', + 'Predis\\Command\\KeyRename' => $vendorDir . '/predis/predis/lib/Predis/Command/KeyRename.php', + 'Predis\\Command\\KeyRenamePreserve' => $vendorDir . '/predis/predis/lib/Predis/Command/KeyRenamePreserve.php', + 'Predis\\Command\\KeyRestore' => $vendorDir . '/predis/predis/lib/Predis/Command/KeyRestore.php', + 'Predis\\Command\\KeySort' => $vendorDir . '/predis/predis/lib/Predis/Command/KeySort.php', + 'Predis\\Command\\KeyTimeToLive' => $vendorDir . '/predis/predis/lib/Predis/Command/KeyTimeToLive.php', + 'Predis\\Command\\KeyType' => $vendorDir . '/predis/predis/lib/Predis/Command/KeyType.php', + 'Predis\\Command\\ListIndex' => $vendorDir . '/predis/predis/lib/Predis/Command/ListIndex.php', + 'Predis\\Command\\ListInsert' => $vendorDir . '/predis/predis/lib/Predis/Command/ListInsert.php', + 'Predis\\Command\\ListLength' => $vendorDir . '/predis/predis/lib/Predis/Command/ListLength.php', + 'Predis\\Command\\ListPopFirst' => $vendorDir . '/predis/predis/lib/Predis/Command/ListPopFirst.php', + 'Predis\\Command\\ListPopFirstBlocking' => $vendorDir . '/predis/predis/lib/Predis/Command/ListPopFirstBlocking.php', + 'Predis\\Command\\ListPopLast' => $vendorDir . '/predis/predis/lib/Predis/Command/ListPopLast.php', + 'Predis\\Command\\ListPopLastBlocking' => $vendorDir . '/predis/predis/lib/Predis/Command/ListPopLastBlocking.php', + 'Predis\\Command\\ListPopLastPushHead' => $vendorDir . '/predis/predis/lib/Predis/Command/ListPopLastPushHead.php', + 'Predis\\Command\\ListPopLastPushHeadBlocking' => $vendorDir . '/predis/predis/lib/Predis/Command/ListPopLastPushHeadBlocking.php', + 'Predis\\Command\\ListPushHead' => $vendorDir . '/predis/predis/lib/Predis/Command/ListPushHead.php', + 'Predis\\Command\\ListPushHeadX' => $vendorDir . '/predis/predis/lib/Predis/Command/ListPushHeadX.php', + 'Predis\\Command\\ListPushTail' => $vendorDir . '/predis/predis/lib/Predis/Command/ListPushTail.php', + 'Predis\\Command\\ListPushTailX' => $vendorDir . '/predis/predis/lib/Predis/Command/ListPushTailX.php', + 'Predis\\Command\\ListRange' => $vendorDir . '/predis/predis/lib/Predis/Command/ListRange.php', + 'Predis\\Command\\ListRemove' => $vendorDir . '/predis/predis/lib/Predis/Command/ListRemove.php', + 'Predis\\Command\\ListSet' => $vendorDir . '/predis/predis/lib/Predis/Command/ListSet.php', + 'Predis\\Command\\ListTrim' => $vendorDir . '/predis/predis/lib/Predis/Command/ListTrim.php', + 'Predis\\Command\\PrefixHelpers' => $vendorDir . '/predis/predis/lib/Predis/Command/PrefixHelpers.php', + 'Predis\\Command\\PrefixableCommand' => $vendorDir . '/predis/predis/lib/Predis/Command/PrefixableCommand.php', + 'Predis\\Command\\PrefixableCommandInterface' => $vendorDir . '/predis/predis/lib/Predis/Command/PrefixableCommandInterface.php', + 'Predis\\Command\\Processor\\CommandProcessingInterface' => $vendorDir . '/predis/predis/lib/Predis/Command/Processor/CommandProcessingInterface.php', + 'Predis\\Command\\Processor\\CommandProcessorChainInterface' => $vendorDir . '/predis/predis/lib/Predis/Command/Processor/CommandProcessorChainInterface.php', + 'Predis\\Command\\Processor\\CommandProcessorInterface' => $vendorDir . '/predis/predis/lib/Predis/Command/Processor/CommandProcessorInterface.php', + 'Predis\\Command\\Processor\\KeyPrefixProcessor' => $vendorDir . '/predis/predis/lib/Predis/Command/Processor/KeyPrefixProcessor.php', + 'Predis\\Command\\Processor\\ProcessorChain' => $vendorDir . '/predis/predis/lib/Predis/Command/Processor/ProcessorChain.php', + 'Predis\\Command\\PubSubPublish' => $vendorDir . '/predis/predis/lib/Predis/Command/PubSubPublish.php', + 'Predis\\Command\\PubSubSubscribe' => $vendorDir . '/predis/predis/lib/Predis/Command/PubSubSubscribe.php', + 'Predis\\Command\\PubSubSubscribeByPattern' => $vendorDir . '/predis/predis/lib/Predis/Command/PubSubSubscribeByPattern.php', + 'Predis\\Command\\PubSubUnsubscribe' => $vendorDir . '/predis/predis/lib/Predis/Command/PubSubUnsubscribe.php', + 'Predis\\Command\\PubSubUnsubscribeByPattern' => $vendorDir . '/predis/predis/lib/Predis/Command/PubSubUnsubscribeByPattern.php', + 'Predis\\Command\\ScriptedCommand' => $vendorDir . '/predis/predis/lib/Predis/Command/ScriptedCommand.php', + 'Predis\\Command\\ServerBackgroundRewriteAOF' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerBackgroundRewriteAOF.php', + 'Predis\\Command\\ServerBackgroundSave' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerBackgroundSave.php', + 'Predis\\Command\\ServerClient' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerClient.php', + 'Predis\\Command\\ServerConfig' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerConfig.php', + 'Predis\\Command\\ServerDatabaseSize' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerDatabaseSize.php', + 'Predis\\Command\\ServerEval' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerEval.php', + 'Predis\\Command\\ServerEvalSHA' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerEvalSHA.php', + 'Predis\\Command\\ServerFlushAll' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerFlushAll.php', + 'Predis\\Command\\ServerFlushDatabase' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerFlushDatabase.php', + 'Predis\\Command\\ServerInfo' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerInfo.php', + 'Predis\\Command\\ServerInfoV26x' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerInfoV26x.php', + 'Predis\\Command\\ServerLastSave' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerLastSave.php', + 'Predis\\Command\\ServerMonitor' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerMonitor.php', + 'Predis\\Command\\ServerObject' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerObject.php', + 'Predis\\Command\\ServerSave' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerSave.php', + 'Predis\\Command\\ServerScript' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerScript.php', + 'Predis\\Command\\ServerShutdown' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerShutdown.php', + 'Predis\\Command\\ServerSlaveOf' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerSlaveOf.php', + 'Predis\\Command\\ServerSlowlog' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerSlowlog.php', + 'Predis\\Command\\ServerTime' => $vendorDir . '/predis/predis/lib/Predis/Command/ServerTime.php', + 'Predis\\Command\\SetAdd' => $vendorDir . '/predis/predis/lib/Predis/Command/SetAdd.php', + 'Predis\\Command\\SetCardinality' => $vendorDir . '/predis/predis/lib/Predis/Command/SetCardinality.php', + 'Predis\\Command\\SetDifference' => $vendorDir . '/predis/predis/lib/Predis/Command/SetDifference.php', + 'Predis\\Command\\SetDifferenceStore' => $vendorDir . '/predis/predis/lib/Predis/Command/SetDifferenceStore.php', + 'Predis\\Command\\SetIntersection' => $vendorDir . '/predis/predis/lib/Predis/Command/SetIntersection.php', + 'Predis\\Command\\SetIntersectionStore' => $vendorDir . '/predis/predis/lib/Predis/Command/SetIntersectionStore.php', + 'Predis\\Command\\SetIsMember' => $vendorDir . '/predis/predis/lib/Predis/Command/SetIsMember.php', + 'Predis\\Command\\SetMembers' => $vendorDir . '/predis/predis/lib/Predis/Command/SetMembers.php', + 'Predis\\Command\\SetMove' => $vendorDir . '/predis/predis/lib/Predis/Command/SetMove.php', + 'Predis\\Command\\SetPop' => $vendorDir . '/predis/predis/lib/Predis/Command/SetPop.php', + 'Predis\\Command\\SetRandomMember' => $vendorDir . '/predis/predis/lib/Predis/Command/SetRandomMember.php', + 'Predis\\Command\\SetRemove' => $vendorDir . '/predis/predis/lib/Predis/Command/SetRemove.php', + 'Predis\\Command\\SetUnion' => $vendorDir . '/predis/predis/lib/Predis/Command/SetUnion.php', + 'Predis\\Command\\SetUnionStore' => $vendorDir . '/predis/predis/lib/Predis/Command/SetUnionStore.php', + 'Predis\\Command\\StringAppend' => $vendorDir . '/predis/predis/lib/Predis/Command/StringAppend.php', + 'Predis\\Command\\StringBitCount' => $vendorDir . '/predis/predis/lib/Predis/Command/StringBitCount.php', + 'Predis\\Command\\StringBitOp' => $vendorDir . '/predis/predis/lib/Predis/Command/StringBitOp.php', + 'Predis\\Command\\StringDecrement' => $vendorDir . '/predis/predis/lib/Predis/Command/StringDecrement.php', + 'Predis\\Command\\StringDecrementBy' => $vendorDir . '/predis/predis/lib/Predis/Command/StringDecrementBy.php', + 'Predis\\Command\\StringGet' => $vendorDir . '/predis/predis/lib/Predis/Command/StringGet.php', + 'Predis\\Command\\StringGetBit' => $vendorDir . '/predis/predis/lib/Predis/Command/StringGetBit.php', + 'Predis\\Command\\StringGetMultiple' => $vendorDir . '/predis/predis/lib/Predis/Command/StringGetMultiple.php', + 'Predis\\Command\\StringGetRange' => $vendorDir . '/predis/predis/lib/Predis/Command/StringGetRange.php', + 'Predis\\Command\\StringGetSet' => $vendorDir . '/predis/predis/lib/Predis/Command/StringGetSet.php', + 'Predis\\Command\\StringIncrement' => $vendorDir . '/predis/predis/lib/Predis/Command/StringIncrement.php', + 'Predis\\Command\\StringIncrementBy' => $vendorDir . '/predis/predis/lib/Predis/Command/StringIncrementBy.php', + 'Predis\\Command\\StringIncrementByFloat' => $vendorDir . '/predis/predis/lib/Predis/Command/StringIncrementByFloat.php', + 'Predis\\Command\\StringPreciseSetExpire' => $vendorDir . '/predis/predis/lib/Predis/Command/StringPreciseSetExpire.php', + 'Predis\\Command\\StringSet' => $vendorDir . '/predis/predis/lib/Predis/Command/StringSet.php', + 'Predis\\Command\\StringSetBit' => $vendorDir . '/predis/predis/lib/Predis/Command/StringSetBit.php', + 'Predis\\Command\\StringSetExpire' => $vendorDir . '/predis/predis/lib/Predis/Command/StringSetExpire.php', + 'Predis\\Command\\StringSetMultiple' => $vendorDir . '/predis/predis/lib/Predis/Command/StringSetMultiple.php', + 'Predis\\Command\\StringSetMultiplePreserve' => $vendorDir . '/predis/predis/lib/Predis/Command/StringSetMultiplePreserve.php', + 'Predis\\Command\\StringSetPreserve' => $vendorDir . '/predis/predis/lib/Predis/Command/StringSetPreserve.php', + 'Predis\\Command\\StringSetRange' => $vendorDir . '/predis/predis/lib/Predis/Command/StringSetRange.php', + 'Predis\\Command\\StringStrlen' => $vendorDir . '/predis/predis/lib/Predis/Command/StringStrlen.php', + 'Predis\\Command\\StringSubstr' => $vendorDir . '/predis/predis/lib/Predis/Command/StringSubstr.php', + 'Predis\\Command\\TransactionDiscard' => $vendorDir . '/predis/predis/lib/Predis/Command/TransactionDiscard.php', + 'Predis\\Command\\TransactionExec' => $vendorDir . '/predis/predis/lib/Predis/Command/TransactionExec.php', + 'Predis\\Command\\TransactionMulti' => $vendorDir . '/predis/predis/lib/Predis/Command/TransactionMulti.php', + 'Predis\\Command\\TransactionUnwatch' => $vendorDir . '/predis/predis/lib/Predis/Command/TransactionUnwatch.php', + 'Predis\\Command\\TransactionWatch' => $vendorDir . '/predis/predis/lib/Predis/Command/TransactionWatch.php', + 'Predis\\Command\\ZSetAdd' => $vendorDir . '/predis/predis/lib/Predis/Command/ZSetAdd.php', + 'Predis\\Command\\ZSetCardinality' => $vendorDir . '/predis/predis/lib/Predis/Command/ZSetCardinality.php', + 'Predis\\Command\\ZSetCount' => $vendorDir . '/predis/predis/lib/Predis/Command/ZSetCount.php', + 'Predis\\Command\\ZSetIncrementBy' => $vendorDir . '/predis/predis/lib/Predis/Command/ZSetIncrementBy.php', + 'Predis\\Command\\ZSetIntersectionStore' => $vendorDir . '/predis/predis/lib/Predis/Command/ZSetIntersectionStore.php', + 'Predis\\Command\\ZSetRange' => $vendorDir . '/predis/predis/lib/Predis/Command/ZSetRange.php', + 'Predis\\Command\\ZSetRangeByScore' => $vendorDir . '/predis/predis/lib/Predis/Command/ZSetRangeByScore.php', + 'Predis\\Command\\ZSetRank' => $vendorDir . '/predis/predis/lib/Predis/Command/ZSetRank.php', + 'Predis\\Command\\ZSetRemove' => $vendorDir . '/predis/predis/lib/Predis/Command/ZSetRemove.php', + 'Predis\\Command\\ZSetRemoveRangeByRank' => $vendorDir . '/predis/predis/lib/Predis/Command/ZSetRemoveRangeByRank.php', + 'Predis\\Command\\ZSetRemoveRangeByScore' => $vendorDir . '/predis/predis/lib/Predis/Command/ZSetRemoveRangeByScore.php', + 'Predis\\Command\\ZSetReverseRange' => $vendorDir . '/predis/predis/lib/Predis/Command/ZSetReverseRange.php', + 'Predis\\Command\\ZSetReverseRangeByScore' => $vendorDir . '/predis/predis/lib/Predis/Command/ZSetReverseRangeByScore.php', + 'Predis\\Command\\ZSetReverseRank' => $vendorDir . '/predis/predis/lib/Predis/Command/ZSetReverseRank.php', + 'Predis\\Command\\ZSetScore' => $vendorDir . '/predis/predis/lib/Predis/Command/ZSetScore.php', + 'Predis\\Command\\ZSetUnionStore' => $vendorDir . '/predis/predis/lib/Predis/Command/ZSetUnionStore.php', + 'Predis\\CommunicationException' => $vendorDir . '/predis/predis/lib/Predis/CommunicationException.php', + 'Predis\\Connection\\AbstractConnection' => $vendorDir . '/predis/predis/lib/Predis/Connection/AbstractConnection.php', + 'Predis\\Connection\\AggregatedConnectionInterface' => $vendorDir . '/predis/predis/lib/Predis/Connection/AggregatedConnectionInterface.php', + 'Predis\\Connection\\ClusterConnectionInterface' => $vendorDir . '/predis/predis/lib/Predis/Connection/ClusterConnectionInterface.php', + 'Predis\\Connection\\ComposableConnectionInterface' => $vendorDir . '/predis/predis/lib/Predis/Connection/ComposableConnectionInterface.php', + 'Predis\\Connection\\ComposableStreamConnection' => $vendorDir . '/predis/predis/lib/Predis/Connection/ComposableStreamConnection.php', + 'Predis\\Connection\\ConnectionException' => $vendorDir . '/predis/predis/lib/Predis/Connection/ConnectionException.php', + 'Predis\\Connection\\ConnectionFactory' => $vendorDir . '/predis/predis/lib/Predis/Connection/ConnectionFactory.php', + 'Predis\\Connection\\ConnectionFactoryInterface' => $vendorDir . '/predis/predis/lib/Predis/Connection/ConnectionFactoryInterface.php', + 'Predis\\Connection\\ConnectionInterface' => $vendorDir . '/predis/predis/lib/Predis/Connection/ConnectionInterface.php', + 'Predis\\Connection\\ConnectionParameters' => $vendorDir . '/predis/predis/lib/Predis/Connection/ConnectionParameters.php', + 'Predis\\Connection\\ConnectionParametersInterface' => $vendorDir . '/predis/predis/lib/Predis/Connection/ConnectionParametersInterface.php', + 'Predis\\Connection\\MasterSlaveReplication' => $vendorDir . '/predis/predis/lib/Predis/Connection/MasterSlaveReplication.php', + 'Predis\\Connection\\PhpiredisConnection' => $vendorDir . '/predis/predis/lib/Predis/Connection/PhpiredisConnection.php', + 'Predis\\Connection\\PhpiredisStreamConnection' => $vendorDir . '/predis/predis/lib/Predis/Connection/PhpiredisStreamConnection.php', + 'Predis\\Connection\\PredisCluster' => $vendorDir . '/predis/predis/lib/Predis/Connection/PredisCluster.php', + 'Predis\\Connection\\RedisCluster' => $vendorDir . '/predis/predis/lib/Predis/Connection/RedisCluster.php', + 'Predis\\Connection\\ReplicationConnectionInterface' => $vendorDir . '/predis/predis/lib/Predis/Connection/ReplicationConnectionInterface.php', + 'Predis\\Connection\\SingleConnectionInterface' => $vendorDir . '/predis/predis/lib/Predis/Connection/SingleConnectionInterface.php', + 'Predis\\Connection\\StreamConnection' => $vendorDir . '/predis/predis/lib/Predis/Connection/StreamConnection.php', + 'Predis\\Connection\\WebdisConnection' => $vendorDir . '/predis/predis/lib/Predis/Connection/WebdisConnection.php', + 'Predis\\ExecutableContextInterface' => $vendorDir . '/predis/predis/lib/Predis/ExecutableContextInterface.php', + 'Predis\\Helpers' => $vendorDir . '/predis/predis/lib/Predis/Helpers.php', + 'Predis\\Iterator\\MultiBulkResponse' => $vendorDir . '/predis/predis/lib/Predis/Iterator/MultiBulkResponse.php', + 'Predis\\Iterator\\MultiBulkResponseSimple' => $vendorDir . '/predis/predis/lib/Predis/Iterator/MultiBulkResponseSimple.php', + 'Predis\\Iterator\\MultiBulkResponseTuple' => $vendorDir . '/predis/predis/lib/Predis/Iterator/MultiBulkResponseTuple.php', + 'Predis\\Monitor\\MonitorContext' => $vendorDir . '/predis/predis/lib/Predis/Monitor/MonitorContext.php', + 'Predis\\NotSupportedException' => $vendorDir . '/predis/predis/lib/Predis/NotSupportedException.php', + 'Predis\\Option\\AbstractOption' => $vendorDir . '/predis/predis/lib/Predis/Option/AbstractOption.php', + 'Predis\\Option\\ClientCluster' => $vendorDir . '/predis/predis/lib/Predis/Option/ClientCluster.php', + 'Predis\\Option\\ClientConnectionFactory' => $vendorDir . '/predis/predis/lib/Predis/Option/ClientConnectionFactory.php', + 'Predis\\Option\\ClientExceptions' => $vendorDir . '/predis/predis/lib/Predis/Option/ClientExceptions.php', + 'Predis\\Option\\ClientOptions' => $vendorDir . '/predis/predis/lib/Predis/Option/ClientOptions.php', + 'Predis\\Option\\ClientOptionsInterface' => $vendorDir . '/predis/predis/lib/Predis/Option/ClientOptionsInterface.php', + 'Predis\\Option\\ClientPrefix' => $vendorDir . '/predis/predis/lib/Predis/Option/ClientPrefix.php', + 'Predis\\Option\\ClientProfile' => $vendorDir . '/predis/predis/lib/Predis/Option/ClientProfile.php', + 'Predis\\Option\\ClientReplication' => $vendorDir . '/predis/predis/lib/Predis/Option/ClientReplication.php', + 'Predis\\Option\\CustomOption' => $vendorDir . '/predis/predis/lib/Predis/Option/CustomOption.php', + 'Predis\\Option\\OptionInterface' => $vendorDir . '/predis/predis/lib/Predis/Option/OptionInterface.php', + 'Predis\\Pipeline\\FireAndForgetExecutor' => $vendorDir . '/predis/predis/lib/Predis/Pipeline/FireAndForgetExecutor.php', + 'Predis\\Pipeline\\MultiExecExecutor' => $vendorDir . '/predis/predis/lib/Predis/Pipeline/MultiExecExecutor.php', + 'Predis\\Pipeline\\PipelineContext' => $vendorDir . '/predis/predis/lib/Predis/Pipeline/PipelineContext.php', + 'Predis\\Pipeline\\PipelineExecutorInterface' => $vendorDir . '/predis/predis/lib/Predis/Pipeline/PipelineExecutorInterface.php', + 'Predis\\Pipeline\\SafeClusterExecutor' => $vendorDir . '/predis/predis/lib/Predis/Pipeline/SafeClusterExecutor.php', + 'Predis\\Pipeline\\SafeExecutor' => $vendorDir . '/predis/predis/lib/Predis/Pipeline/SafeExecutor.php', + 'Predis\\Pipeline\\StandardExecutor' => $vendorDir . '/predis/predis/lib/Predis/Pipeline/StandardExecutor.php', + 'Predis\\PredisException' => $vendorDir . '/predis/predis/lib/Predis/PredisException.php', + 'Predis\\Profile\\ServerProfile' => $vendorDir . '/predis/predis/lib/Predis/Profile/ServerProfile.php', + 'Predis\\Profile\\ServerProfileInterface' => $vendorDir . '/predis/predis/lib/Predis/Profile/ServerProfileInterface.php', + 'Predis\\Profile\\ServerVersion12' => $vendorDir . '/predis/predis/lib/Predis/Profile/ServerVersion12.php', + 'Predis\\Profile\\ServerVersion20' => $vendorDir . '/predis/predis/lib/Predis/Profile/ServerVersion20.php', + 'Predis\\Profile\\ServerVersion22' => $vendorDir . '/predis/predis/lib/Predis/Profile/ServerVersion22.php', + 'Predis\\Profile\\ServerVersion24' => $vendorDir . '/predis/predis/lib/Predis/Profile/ServerVersion24.php', + 'Predis\\Profile\\ServerVersion26' => $vendorDir . '/predis/predis/lib/Predis/Profile/ServerVersion26.php', + 'Predis\\Profile\\ServerVersionNext' => $vendorDir . '/predis/predis/lib/Predis/Profile/ServerVersionNext.php', + 'Predis\\Protocol\\CommandSerializerInterface' => $vendorDir . '/predis/predis/lib/Predis/Protocol/CommandSerializerInterface.php', + 'Predis\\Protocol\\ComposableProtocolInterface' => $vendorDir . '/predis/predis/lib/Predis/Protocol/ComposableProtocolInterface.php', + 'Predis\\Protocol\\ProtocolException' => $vendorDir . '/predis/predis/lib/Predis/Protocol/ProtocolException.php', + 'Predis\\Protocol\\ProtocolInterface' => $vendorDir . '/predis/predis/lib/Predis/Protocol/ProtocolInterface.php', + 'Predis\\Protocol\\ResponseHandlerInterface' => $vendorDir . '/predis/predis/lib/Predis/Protocol/ResponseHandlerInterface.php', + 'Predis\\Protocol\\ResponseReaderInterface' => $vendorDir . '/predis/predis/lib/Predis/Protocol/ResponseReaderInterface.php', + 'Predis\\Protocol\\Text\\ComposableTextProtocol' => $vendorDir . '/predis/predis/lib/Predis/Protocol/Text/ComposableTextProtocol.php', + 'Predis\\Protocol\\Text\\ResponseBulkHandler' => $vendorDir . '/predis/predis/lib/Predis/Protocol/Text/ResponseBulkHandler.php', + 'Predis\\Protocol\\Text\\ResponseErrorHandler' => $vendorDir . '/predis/predis/lib/Predis/Protocol/Text/ResponseErrorHandler.php', + 'Predis\\Protocol\\Text\\ResponseIntegerHandler' => $vendorDir . '/predis/predis/lib/Predis/Protocol/Text/ResponseIntegerHandler.php', + 'Predis\\Protocol\\Text\\ResponseMultiBulkHandler' => $vendorDir . '/predis/predis/lib/Predis/Protocol/Text/ResponseMultiBulkHandler.php', + 'Predis\\Protocol\\Text\\ResponseMultiBulkStreamHandler' => $vendorDir . '/predis/predis/lib/Predis/Protocol/Text/ResponseMultiBulkStreamHandler.php', + 'Predis\\Protocol\\Text\\ResponseStatusHandler' => $vendorDir . '/predis/predis/lib/Predis/Protocol/Text/ResponseStatusHandler.php', + 'Predis\\Protocol\\Text\\TextCommandSerializer' => $vendorDir . '/predis/predis/lib/Predis/Protocol/Text/TextCommandSerializer.php', + 'Predis\\Protocol\\Text\\TextProtocol' => $vendorDir . '/predis/predis/lib/Predis/Protocol/Text/TextProtocol.php', + 'Predis\\Protocol\\Text\\TextResponseReader' => $vendorDir . '/predis/predis/lib/Predis/Protocol/Text/TextResponseReader.php', + 'Predis\\PubSub\\AbstractPubSubContext' => $vendorDir . '/predis/predis/lib/Predis/PubSub/AbstractPubSubContext.php', + 'Predis\\PubSub\\DispatcherLoop' => $vendorDir . '/predis/predis/lib/Predis/PubSub/DispatcherLoop.php', + 'Predis\\PubSub\\PubSubContext' => $vendorDir . '/predis/predis/lib/Predis/PubSub/PubSubContext.php', + 'Predis\\Replication\\ReplicationStrategy' => $vendorDir . '/predis/predis/lib/Predis/Replication/ReplicationStrategy.php', + 'Predis\\ResponseError' => $vendorDir . '/predis/predis/lib/Predis/ResponseError.php', + 'Predis\\ResponseErrorInterface' => $vendorDir . '/predis/predis/lib/Predis/ResponseErrorInterface.php', + 'Predis\\ResponseObjectInterface' => $vendorDir . '/predis/predis/lib/Predis/ResponseObjectInterface.php', + 'Predis\\ResponseQueued' => $vendorDir . '/predis/predis/lib/Predis/ResponseQueued.php', + 'Predis\\ServerException' => $vendorDir . '/predis/predis/lib/Predis/ServerException.php', + 'Predis\\Session\\SessionHandler' => $vendorDir . '/predis/predis/lib/Predis/Session/SessionHandler.php', + 'Predis\\Transaction\\AbortedMultiExecException' => $vendorDir . '/predis/predis/lib/Predis/Transaction/AbortedMultiExecException.php', + 'Predis\\Transaction\\MultiExecContext' => $vendorDir . '/predis/predis/lib/Predis/Transaction/MultiExecContext.php', + 'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php', + 'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php', + 'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php', + 'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareInterface.php', + 'Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareTrait.php', + 'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php', + 'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php', + 'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php', + 'SessionHandlerInterface' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Resources/stubs/SessionHandlerInterface.php', + 'Symfony\\Component\\BrowserKit\\Client' => $vendorDir . '/symfony/browser-kit/Symfony/Component/BrowserKit/Client.php', + 'Symfony\\Component\\BrowserKit\\Cookie' => $vendorDir . '/symfony/browser-kit/Symfony/Component/BrowserKit/Cookie.php', + 'Symfony\\Component\\BrowserKit\\CookieJar' => $vendorDir . '/symfony/browser-kit/Symfony/Component/BrowserKit/CookieJar.php', + 'Symfony\\Component\\BrowserKit\\History' => $vendorDir . '/symfony/browser-kit/Symfony/Component/BrowserKit/History.php', + 'Symfony\\Component\\BrowserKit\\Request' => $vendorDir . '/symfony/browser-kit/Symfony/Component/BrowserKit/Request.php', + 'Symfony\\Component\\BrowserKit\\Response' => $vendorDir . '/symfony/browser-kit/Symfony/Component/BrowserKit/Response.php', + 'Symfony\\Component\\Console\\Application' => $vendorDir . '/symfony/console/Symfony/Component/Console/Application.php', + 'Symfony\\Component\\Console\\Command\\Command' => $vendorDir . '/symfony/console/Symfony/Component/Console/Command/Command.php', + 'Symfony\\Component\\Console\\Command\\HelpCommand' => $vendorDir . '/symfony/console/Symfony/Component/Console/Command/HelpCommand.php', + 'Symfony\\Component\\Console\\Command\\ListCommand' => $vendorDir . '/symfony/console/Symfony/Component/Console/Command/ListCommand.php', + 'Symfony\\Component\\Console\\ConsoleEvents' => $vendorDir . '/symfony/console/Symfony/Component/Console/ConsoleEvents.php', + 'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => $vendorDir . '/symfony/console/Symfony/Component/Console/Descriptor/ApplicationDescription.php', + 'Symfony\\Component\\Console\\Descriptor\\Descriptor' => $vendorDir . '/symfony/console/Symfony/Component/Console/Descriptor/Descriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => $vendorDir . '/symfony/console/Symfony/Component/Console/Descriptor/DescriptorInterface.php', + 'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => $vendorDir . '/symfony/console/Symfony/Component/Console/Descriptor/JsonDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => $vendorDir . '/symfony/console/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => $vendorDir . '/symfony/console/Symfony/Component/Console/Descriptor/TextDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => $vendorDir . '/symfony/console/Symfony/Component/Console/Descriptor/XmlDescriptor.php', + 'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => $vendorDir . '/symfony/console/Symfony/Component/Console/Event/ConsoleCommandEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleEvent' => $vendorDir . '/symfony/console/Symfony/Component/Console/Event/ConsoleEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleExceptionEvent' => $vendorDir . '/symfony/console/Symfony/Component/Console/Event/ConsoleExceptionEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => $vendorDir . '/symfony/console/Symfony/Component/Console/Event/ConsoleTerminateEvent.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => $vendorDir . '/symfony/console/Symfony/Component/Console/Formatter/OutputFormatter.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => $vendorDir . '/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterInterface.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => $vendorDir . '/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyle.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => $vendorDir . '/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => $vendorDir . '/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php', + 'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => $vendorDir . '/symfony/console/Symfony/Component/Console/Helper/DescriptorHelper.php', + 'Symfony\\Component\\Console\\Helper\\DialogHelper' => $vendorDir . '/symfony/console/Symfony/Component/Console/Helper/DialogHelper.php', + 'Symfony\\Component\\Console\\Helper\\FormatterHelper' => $vendorDir . '/symfony/console/Symfony/Component/Console/Helper/FormatterHelper.php', + 'Symfony\\Component\\Console\\Helper\\Helper' => $vendorDir . '/symfony/console/Symfony/Component/Console/Helper/Helper.php', + 'Symfony\\Component\\Console\\Helper\\HelperInterface' => $vendorDir . '/symfony/console/Symfony/Component/Console/Helper/HelperInterface.php', + 'Symfony\\Component\\Console\\Helper\\HelperSet' => $vendorDir . '/symfony/console/Symfony/Component/Console/Helper/HelperSet.php', + 'Symfony\\Component\\Console\\Helper\\ProgressHelper' => $vendorDir . '/symfony/console/Symfony/Component/Console/Helper/ProgressHelper.php', + 'Symfony\\Component\\Console\\Helper\\TableHelper' => $vendorDir . '/symfony/console/Symfony/Component/Console/Helper/TableHelper.php', + 'Symfony\\Component\\Console\\Input\\ArgvInput' => $vendorDir . '/symfony/console/Symfony/Component/Console/Input/ArgvInput.php', + 'Symfony\\Component\\Console\\Input\\ArrayInput' => $vendorDir . '/symfony/console/Symfony/Component/Console/Input/ArrayInput.php', + 'Symfony\\Component\\Console\\Input\\Input' => $vendorDir . '/symfony/console/Symfony/Component/Console/Input/Input.php', + 'Symfony\\Component\\Console\\Input\\InputArgument' => $vendorDir . '/symfony/console/Symfony/Component/Console/Input/InputArgument.php', + 'Symfony\\Component\\Console\\Input\\InputDefinition' => $vendorDir . '/symfony/console/Symfony/Component/Console/Input/InputDefinition.php', + 'Symfony\\Component\\Console\\Input\\InputInterface' => $vendorDir . '/symfony/console/Symfony/Component/Console/Input/InputInterface.php', + 'Symfony\\Component\\Console\\Input\\InputOption' => $vendorDir . '/symfony/console/Symfony/Component/Console/Input/InputOption.php', + 'Symfony\\Component\\Console\\Input\\StringInput' => $vendorDir . '/symfony/console/Symfony/Component/Console/Input/StringInput.php', + 'Symfony\\Component\\Console\\Output\\ConsoleOutput' => $vendorDir . '/symfony/console/Symfony/Component/Console/Output/ConsoleOutput.php', + 'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => $vendorDir . '/symfony/console/Symfony/Component/Console/Output/ConsoleOutputInterface.php', + 'Symfony\\Component\\Console\\Output\\NullOutput' => $vendorDir . '/symfony/console/Symfony/Component/Console/Output/NullOutput.php', + 'Symfony\\Component\\Console\\Output\\Output' => $vendorDir . '/symfony/console/Symfony/Component/Console/Output/Output.php', + 'Symfony\\Component\\Console\\Output\\OutputInterface' => $vendorDir . '/symfony/console/Symfony/Component/Console/Output/OutputInterface.php', + 'Symfony\\Component\\Console\\Output\\StreamOutput' => $vendorDir . '/symfony/console/Symfony/Component/Console/Output/StreamOutput.php', + 'Symfony\\Component\\Console\\Shell' => $vendorDir . '/symfony/console/Symfony/Component/Console/Shell.php', + 'Symfony\\Component\\Console\\Tester\\ApplicationTester' => $vendorDir . '/symfony/console/Symfony/Component/Console/Tester/ApplicationTester.php', + 'Symfony\\Component\\Console\\Tester\\CommandTester' => $vendorDir . '/symfony/console/Symfony/Component/Console/Tester/CommandTester.php', + 'Symfony\\Component\\Console\\Tests\\Descriptor\\ObjectsProvider' => $vendorDir . '/symfony/console/Symfony/Component/Console/Tests/Descriptor/ObjectsProvider.php', + 'Symfony\\Component\\Console\\Tests\\Fixtures\\DescriptorApplication1' => $vendorDir . '/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorApplication1.php', + 'Symfony\\Component\\Console\\Tests\\Fixtures\\DescriptorApplication2' => $vendorDir . '/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorApplication2.php', + 'Symfony\\Component\\Console\\Tests\\Fixtures\\DescriptorCommand1' => $vendorDir . '/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorCommand1.php', + 'Symfony\\Component\\Console\\Tests\\Fixtures\\DescriptorCommand2' => $vendorDir . '/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorCommand2.php', + 'Symfony\\Component\\CssSelector\\CssSelector' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/CssSelector.php', + 'Symfony\\Component\\CssSelector\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Exception/ExceptionInterface.php', + 'Symfony\\Component\\CssSelector\\Exception\\ExpressionErrorException' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Exception/ExpressionErrorException.php', + 'Symfony\\Component\\CssSelector\\Exception\\InternalErrorException' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Exception/InternalErrorException.php', + 'Symfony\\Component\\CssSelector\\Exception\\ParseException' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Exception/ParseException.php', + 'Symfony\\Component\\CssSelector\\Exception\\SyntaxErrorException' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Exception/SyntaxErrorException.php', + 'Symfony\\Component\\CssSelector\\Node\\AbstractNode' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Node/AbstractNode.php', + 'Symfony\\Component\\CssSelector\\Node\\AttributeNode' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Node/AttributeNode.php', + 'Symfony\\Component\\CssSelector\\Node\\ClassNode' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Node/ClassNode.php', + 'Symfony\\Component\\CssSelector\\Node\\CombinedSelectorNode' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Node/CombinedSelectorNode.php', + 'Symfony\\Component\\CssSelector\\Node\\ElementNode' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Node/ElementNode.php', + 'Symfony\\Component\\CssSelector\\Node\\FunctionNode' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Node/FunctionNode.php', + 'Symfony\\Component\\CssSelector\\Node\\HashNode' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Node/HashNode.php', + 'Symfony\\Component\\CssSelector\\Node\\NegationNode' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Node/NegationNode.php', + 'Symfony\\Component\\CssSelector\\Node\\NodeInterface' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Node/NodeInterface.php', + 'Symfony\\Component\\CssSelector\\Node\\PseudoNode' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Node/PseudoNode.php', + 'Symfony\\Component\\CssSelector\\Node\\SelectorNode' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Node/SelectorNode.php', + 'Symfony\\Component\\CssSelector\\Node\\Specificity' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Node/Specificity.php', + 'Symfony\\Component\\CssSelector\\Parser\\Handler\\CommentHandler' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/CommentHandler.php', + 'Symfony\\Component\\CssSelector\\Parser\\Handler\\HandlerInterface' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/HandlerInterface.php', + 'Symfony\\Component\\CssSelector\\Parser\\Handler\\HashHandler' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/HashHandler.php', + 'Symfony\\Component\\CssSelector\\Parser\\Handler\\IdentifierHandler' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/IdentifierHandler.php', + 'Symfony\\Component\\CssSelector\\Parser\\Handler\\NumberHandler' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/NumberHandler.php', + 'Symfony\\Component\\CssSelector\\Parser\\Handler\\StringHandler' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/StringHandler.php', + 'Symfony\\Component\\CssSelector\\Parser\\Handler\\WhitespaceHandler' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/WhitespaceHandler.php', + 'Symfony\\Component\\CssSelector\\Parser\\Parser' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/Parser.php', + 'Symfony\\Component\\CssSelector\\Parser\\ParserInterface' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/ParserInterface.php', + 'Symfony\\Component\\CssSelector\\Parser\\Reader' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/Reader.php', + 'Symfony\\Component\\CssSelector\\Parser\\Shortcut\\ClassParser' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/ClassParser.php', + 'Symfony\\Component\\CssSelector\\Parser\\Shortcut\\ElementParser' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/ElementParser.php', + 'Symfony\\Component\\CssSelector\\Parser\\Shortcut\\EmptyStringParser' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/EmptyStringParser.php', + 'Symfony\\Component\\CssSelector\\Parser\\Shortcut\\HashParser' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/HashParser.php', + 'Symfony\\Component\\CssSelector\\Parser\\Token' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/Token.php', + 'Symfony\\Component\\CssSelector\\Parser\\TokenStream' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/TokenStream.php', + 'Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\Tokenizer' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/Tokenizer.php', + 'Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerEscaping' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerEscaping.php', + 'Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerPatterns' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerPatterns.php', + 'Symfony\\Component\\CssSelector\\XPath\\Extension\\AbstractExtension' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/AbstractExtension.php', + 'Symfony\\Component\\CssSelector\\XPath\\Extension\\AttributeMatchingExtension' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/AttributeMatchingExtension.php', + 'Symfony\\Component\\CssSelector\\XPath\\Extension\\CombinationExtension' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/CombinationExtension.php', + 'Symfony\\Component\\CssSelector\\XPath\\Extension\\ExtensionInterface' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/ExtensionInterface.php', + 'Symfony\\Component\\CssSelector\\XPath\\Extension\\FunctionExtension' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/FunctionExtension.php', + 'Symfony\\Component\\CssSelector\\XPath\\Extension\\HtmlExtension' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/HtmlExtension.php', + 'Symfony\\Component\\CssSelector\\XPath\\Extension\\NodeExtension' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/NodeExtension.php', + 'Symfony\\Component\\CssSelector\\XPath\\Extension\\PseudoClassExtension' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/PseudoClassExtension.php', + 'Symfony\\Component\\CssSelector\\XPath\\Translator' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/XPath/Translator.php', + 'Symfony\\Component\\CssSelector\\XPath\\TranslatorInterface' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/XPath/TranslatorInterface.php', + 'Symfony\\Component\\CssSelector\\XPath\\XPathExpr' => $vendorDir . '/symfony/css-selector/Symfony/Component/CssSelector/XPath/XPathExpr.php', + 'Symfony\\Component\\Debug\\Debug' => $vendorDir . '/symfony/debug/Symfony/Component/Debug/Debug.php', + 'Symfony\\Component\\Debug\\ErrorHandler' => $vendorDir . '/symfony/debug/Symfony/Component/Debug/ErrorHandler.php', + 'Symfony\\Component\\Debug\\ExceptionHandler' => $vendorDir . '/symfony/debug/Symfony/Component/Debug/ExceptionHandler.php', + 'Symfony\\Component\\Debug\\Exception\\ContextErrorException' => $vendorDir . '/symfony/debug/Symfony/Component/Debug/Exception/ContextErrorException.php', + 'Symfony\\Component\\Debug\\Exception\\FatalErrorException' => $vendorDir . '/symfony/debug/Symfony/Component/Debug/Exception/FatalErrorException.php', + 'Symfony\\Component\\Debug\\Exception\\FlattenException' => $vendorDir . '/symfony/debug/Symfony/Component/Debug/Exception/FlattenException.php', + 'Symfony\\Component\\DomCrawler\\Crawler' => $vendorDir . '/symfony/dom-crawler/Symfony/Component/DomCrawler/Crawler.php', + 'Symfony\\Component\\DomCrawler\\Field\\ChoiceFormField' => $vendorDir . '/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/ChoiceFormField.php', + 'Symfony\\Component\\DomCrawler\\Field\\FileFormField' => $vendorDir . '/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/FileFormField.php', + 'Symfony\\Component\\DomCrawler\\Field\\FormField' => $vendorDir . '/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/FormField.php', + 'Symfony\\Component\\DomCrawler\\Field\\InputFormField' => $vendorDir . '/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/InputFormField.php', + 'Symfony\\Component\\DomCrawler\\Field\\TextareaFormField' => $vendorDir . '/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/TextareaFormField.php', + 'Symfony\\Component\\DomCrawler\\Form' => $vendorDir . '/symfony/dom-crawler/Symfony/Component/DomCrawler/Form.php', + 'Symfony\\Component\\DomCrawler\\FormFieldRegistry' => $vendorDir . '/symfony/dom-crawler/Symfony/Component/DomCrawler/FormFieldRegistry.php', + 'Symfony\\Component\\DomCrawler\\Link' => $vendorDir . '/symfony/dom-crawler/Symfony/Component/DomCrawler/Link.php', + 'Symfony\\Component\\DomCrawler\\Tests\\Field\\FormFieldTestCase' => $vendorDir . '/symfony/dom-crawler/Symfony/Component/DomCrawler/Tests/Field/FormFieldTestCase.php', + 'Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.php', + 'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcherInterface' => $vendorDir . '/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcherInterface.php', + 'Symfony\\Component\\EventDispatcher\\Event' => $vendorDir . '/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Event.php', + 'Symfony\\Component\\EventDispatcher\\EventDispatcher' => $vendorDir . '/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventDispatcher.php', + 'Symfony\\Component\\EventDispatcher\\EventDispatcherInterface' => $vendorDir . '/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventDispatcherInterface.php', + 'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface' => $vendorDir . '/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventSubscriberInterface.php', + 'Symfony\\Component\\EventDispatcher\\GenericEvent' => $vendorDir . '/symfony/event-dispatcher/Symfony/Component/EventDispatcher/GenericEvent.php', + 'Symfony\\Component\\EventDispatcher\\ImmutableEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php', + 'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/filesystem/Symfony/Component/Filesystem/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Exception\\IOException' => $vendorDir . '/symfony/filesystem/Symfony/Component/Filesystem/Exception/IOException.php', + 'Symfony\\Component\\Filesystem\\Filesystem' => $vendorDir . '/symfony/filesystem/Symfony/Component/Filesystem/Filesystem.php', + 'Symfony\\Component\\Filesystem\\Tests\\FilesystemTestCase' => $vendorDir . '/symfony/filesystem/Symfony/Component/Filesystem/Tests/FilesystemTestCase.php', + 'Symfony\\Component\\Finder\\Adapter\\AbstractAdapter' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Adapter/AbstractAdapter.php', + 'Symfony\\Component\\Finder\\Adapter\\AbstractFindAdapter' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php', + 'Symfony\\Component\\Finder\\Adapter\\AdapterInterface' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Adapter/AdapterInterface.php', + 'Symfony\\Component\\Finder\\Adapter\\BsdFindAdapter' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Adapter/BsdFindAdapter.php', + 'Symfony\\Component\\Finder\\Adapter\\GnuFindAdapter' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Adapter/GnuFindAdapter.php', + 'Symfony\\Component\\Finder\\Adapter\\PhpAdapter' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Adapter/PhpAdapter.php', + 'Symfony\\Component\\Finder\\Comparator\\Comparator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Comparator/Comparator.php', + 'Symfony\\Component\\Finder\\Comparator\\DateComparator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Comparator/DateComparator.php', + 'Symfony\\Component\\Finder\\Comparator\\NumberComparator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Comparator/NumberComparator.php', + 'Symfony\\Component\\Finder\\Exception\\AccessDeniedException' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Exception/AccessDeniedException.php', + 'Symfony\\Component\\Finder\\Exception\\AdapterFailureException' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Exception/AdapterFailureException.php', + 'Symfony\\Component\\Finder\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Finder\\Exception\\OperationNotPermitedException' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Exception/OperationNotPermitedException.php', + 'Symfony\\Component\\Finder\\Exception\\ShellCommandFailureException' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Exception/ShellCommandFailureException.php', + 'Symfony\\Component\\Finder\\Expression\\Expression' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Expression/Expression.php', + 'Symfony\\Component\\Finder\\Expression\\Glob' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Expression/Glob.php', + 'Symfony\\Component\\Finder\\Expression\\Regex' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Expression/Regex.php', + 'Symfony\\Component\\Finder\\Expression\\ValueInterface' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Expression/ValueInterface.php', + 'Symfony\\Component\\Finder\\Finder' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Finder.php', + 'Symfony\\Component\\Finder\\Glob' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Glob.php', + 'Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Iterator/CustomFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\DateRangeFilterIterator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Iterator/DateRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\DepthRangeFilterIterator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Iterator/DepthRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\ExcludeDirectoryFilterIterator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Iterator/ExcludeDirectoryFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilePathsIterator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Iterator/FilePathsIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FileTypeFilterIterator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Iterator/FileTypeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilecontentFilterIterator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Iterator/FilecontentFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilenameFilterIterator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilterIterator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Iterator/FilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\MultiplePcreFilterIterator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Iterator/MultiplePcreFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\PathFilterIterator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Iterator/PathFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\RecursiveDirectoryIterator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\SizeRangeFilterIterator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Iterator/SizeRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\SortableIterator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Iterator/SortableIterator.php', + 'Symfony\\Component\\Finder\\Shell\\Command' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Shell/Command.php', + 'Symfony\\Component\\Finder\\Shell\\Shell' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Shell/Shell.php', + 'Symfony\\Component\\Finder\\SplFileInfo' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/SplFileInfo.php', + 'Symfony\\Component\\Finder\\Tests\\FakeAdapter\\DummyAdapter' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php', + 'Symfony\\Component\\Finder\\Tests\\FakeAdapter\\FailingAdapter' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/FailingAdapter.php', + 'Symfony\\Component\\Finder\\Tests\\FakeAdapter\\NamedAdapter' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/NamedAdapter.php', + 'Symfony\\Component\\Finder\\Tests\\FakeAdapter\\UnsupportedAdapter' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/UnsupportedAdapter.php', + 'Symfony\\Component\\Finder\\Tests\\Iterator\\Iterator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Tests/Iterator/Iterator.php', + 'Symfony\\Component\\Finder\\Tests\\Iterator\\IteratorTestCase' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php', + 'Symfony\\Component\\Finder\\Tests\\Iterator\\MockFileListIterator' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php', + 'Symfony\\Component\\Finder\\Tests\\Iterator\\MockSplFileInfo' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php', + 'Symfony\\Component\\Finder\\Tests\\Iterator\\RealIteratorTestCase' => $vendorDir . '/symfony/finder/Symfony/Component/Finder/Tests/Iterator/RealIteratorTestCase.php', + 'Symfony\\Component\\HttpFoundation\\AcceptHeader' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/AcceptHeader.php', + 'Symfony\\Component\\HttpFoundation\\AcceptHeaderItem' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/AcceptHeaderItem.php', + 'Symfony\\Component\\HttpFoundation\\ApacheRequest' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/ApacheRequest.php', + 'Symfony\\Component\\HttpFoundation\\BinaryFileResponse' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/BinaryFileResponse.php', + 'Symfony\\Component\\HttpFoundation\\Cookie' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Cookie.php', + 'Symfony\\Component\\HttpFoundation\\FileBag' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/FileBag.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\AccessDeniedException' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/AccessDeniedException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\FileException' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/FileException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\FileNotFoundException' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/FileNotFoundException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\UnexpectedTypeException' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/UnexpectedTypeException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\UploadException' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/UploadException.php', + 'Symfony\\Component\\HttpFoundation\\File\\File' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/File/File.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\ExtensionGuesser' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/ExtensionGuesser.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\ExtensionGuesserInterface' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/ExtensionGuesserInterface.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\FileBinaryMimeTypeGuesser' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/FileBinaryMimeTypeGuesser.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\FileinfoMimeTypeGuesser' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/FileinfoMimeTypeGuesser.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\MimeTypeExtensionGuesser' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeExtensionGuesser.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\MimeTypeGuesser' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesser.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\MimeTypeGuesserInterface' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesserInterface.php', + 'Symfony\\Component\\HttpFoundation\\File\\UploadedFile' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/File/UploadedFile.php', + 'Symfony\\Component\\HttpFoundation\\HeaderBag' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/HeaderBag.php', + 'Symfony\\Component\\HttpFoundation\\IpUtils' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/IpUtils.php', + 'Symfony\\Component\\HttpFoundation\\JsonResponse' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/JsonResponse.php', + 'Symfony\\Component\\HttpFoundation\\ParameterBag' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/ParameterBag.php', + 'Symfony\\Component\\HttpFoundation\\RedirectResponse' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/RedirectResponse.php', + 'Symfony\\Component\\HttpFoundation\\Request' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Request.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/RequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcherInterface' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/RequestMatcherInterface.php', + 'Symfony\\Component\\HttpFoundation\\Response' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Response.php', + 'Symfony\\Component\\HttpFoundation\\ResponseHeaderBag' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/ResponseHeaderBag.php', + 'Symfony\\Component\\HttpFoundation\\ServerBag' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/ServerBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBag' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Attribute\\NamespacedAttributeBag' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Attribute/NamespacedAttributeBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Flash\\AutoExpireFlashBag' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBag' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Flash/FlashBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Session' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Session.php', + 'Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/SessionBagInterface.php', + 'Symfony\\Component\\HttpFoundation\\Session\\SessionInterface' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/SessionInterface.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcacheSessionHandler' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcacheSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcachedSessionHandler' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MongoDbSessionHandler' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeFileSessionHandler' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeFileSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeSessionHandler' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NullSessionHandler' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\PdoSessionHandler' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MetadataBag' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockArraySessionStorage' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockFileSessionStorage' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\PhpBridgeSessionStorage' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\AbstractProxy' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\NativeProxy' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Proxy/NativeProxy.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\SessionHandlerProxy' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php', + 'Symfony\\Component\\HttpFoundation\\StreamedResponse' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/StreamedResponse.php', + 'Symfony\\Component\\HttpFoundation\\Tests\\ResponseTestCase' => $vendorDir . '/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ResponseTestCase.php', + 'Symfony\\Component\\HttpKernel\\Bundle\\Bundle' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Bundle/Bundle.php', + 'Symfony\\Component\\HttpKernel\\Bundle\\BundleInterface' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Bundle/BundleInterface.php', + 'Symfony\\Component\\HttpKernel\\CacheClearer\\CacheClearerInterface' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/CacheClearer/CacheClearerInterface.php', + 'Symfony\\Component\\HttpKernel\\CacheClearer\\ChainCacheClearer' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/CacheClearer/ChainCacheClearer.php', + 'Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmer' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmer.php', + 'Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerAggregate' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php', + 'Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerInterface' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerInterface.php', + 'Symfony\\Component\\HttpKernel\\CacheWarmer\\WarmableInterface' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/CacheWarmer/WarmableInterface.php', + 'Symfony\\Component\\HttpKernel\\Client' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Client.php', + 'Symfony\\Component\\HttpKernel\\Config\\FileLocator' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Config/FileLocator.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ControllerReference' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Controller/ControllerReference.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Controller/ControllerResolver.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php', + 'Symfony\\Component\\HttpKernel\\Controller\\TraceableControllerResolver' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Controller/TraceableControllerResolver.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\ConfigDataCollector' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\DataCollector' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/DataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\DataCollectorInterface' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\EventDataCollector' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\ExceptionDataCollector' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/ExceptionDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\LoggerDataCollector' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\MemoryDataCollector' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/MemoryDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\RequestDataCollector' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\RouterDataCollector' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/RouterDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\TimeDataCollector' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php', + 'Symfony\\Component\\HttpKernel\\Debug\\ErrorHandler' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Debug/ErrorHandler.php', + 'Symfony\\Component\\HttpKernel\\Debug\\ExceptionHandler' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Debug/ExceptionHandler.php', + 'Symfony\\Component\\HttpKernel\\Debug\\TraceableEventDispatcher' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\AddClassesToCachePass' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/AddClassesToCachePass.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ConfigurableExtension' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/ConfigurableExtension.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\Extension' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/Extension.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\MergeExtensionConfigurationPass' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/MergeExtensionConfigurationPass.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\RegisterListenersPass' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/RegisterListenersPass.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\ErrorsLoggerListener' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/ErrorsLoggerListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\EsiListener' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/EsiListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\FragmentListener' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/FragmentListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\LocaleListener' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/LocaleListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\ProfilerListener' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/ResponseListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\RouterListener' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/RouterListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\StreamedResponseListener' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/StreamedResponseListener.php', + 'Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Event/FilterControllerEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Event/FilterResponseEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Event/GetResponseEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Event/GetResponseForControllerResultEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Event/GetResponseForExceptionEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\KernelEvent' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Event/KernelEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\PostResponseEvent' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Event/PostResponseEvent.php', + 'Symfony\\Component\\HttpKernel\\Exception\\AccessDeniedHttpException' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/AccessDeniedHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\BadRequestHttpException' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/BadRequestHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\ConflictHttpException' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/ConflictHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\FatalErrorException' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/FatalErrorException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\FlattenException' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/FlattenException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\GoneHttpException' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/GoneHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\HttpException' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/HttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\HttpExceptionInterface' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/HttpExceptionInterface.php', + 'Symfony\\Component\\HttpKernel\\Exception\\LengthRequiredHttpException' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/LengthRequiredHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\MethodNotAllowedHttpException' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/MethodNotAllowedHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\NotAcceptableHttpException' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/NotAcceptableHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/NotFoundHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\PreconditionFailedHttpException' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/PreconditionFailedHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\PreconditionRequiredHttpException' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/PreconditionRequiredHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\ServiceUnavailableHttpException' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/ServiceUnavailableHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\TooManyRequestsHttpException' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/TooManyRequestsHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\UnauthorizedHttpException' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/UnauthorizedHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\UnsupportedMediaTypeHttpException' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/UnsupportedMediaTypeHttpException.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\EsiFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/EsiFragmentRenderer.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\FragmentHandler' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\FragmentRendererInterface' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/FragmentRendererInterface.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\HIncludeFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/HIncludeFragmentRenderer.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\InlineFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\RoutableFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/RoutableFragmentRenderer.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\Esi' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/Esi.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\EsiResponseCacheStrategy' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategy.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\EsiResponseCacheStrategyInterface' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategyInterface.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\HttpCache' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/HttpCache.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\Store' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/Store.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\StoreInterface' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/StoreInterface.php', + 'Symfony\\Component\\HttpKernel\\HttpKernel' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/HttpKernel.php', + 'Symfony\\Component\\HttpKernel\\HttpKernelInterface' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/HttpKernelInterface.php', + 'Symfony\\Component\\HttpKernel\\Kernel' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Kernel.php', + 'Symfony\\Component\\HttpKernel\\KernelEvents' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/KernelEvents.php', + 'Symfony\\Component\\HttpKernel\\KernelInterface' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/KernelInterface.php', + 'Symfony\\Component\\HttpKernel\\Log\\DebugLoggerInterface' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Log/DebugLoggerInterface.php', + 'Symfony\\Component\\HttpKernel\\Log\\LoggerInterface' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Log/LoggerInterface.php', + 'Symfony\\Component\\HttpKernel\\Log\\NullLogger' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Log/NullLogger.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\BaseMemcacheProfilerStorage' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/BaseMemcacheProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\FileProfilerStorage' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\MemcacheProfilerStorage' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/MemcacheProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\MemcachedProfilerStorage' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/MemcachedProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\MongoDbProfilerStorage' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/MongoDbProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\MysqlProfilerStorage' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/MysqlProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\PdoProfilerStorage' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/PdoProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\Profile' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/Profile.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\Profiler' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/Profiler.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\ProfilerStorageInterface' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/ProfilerStorageInterface.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\RedisProfilerStorage' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/RedisProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\SqliteProfilerStorage' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/SqliteProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\TerminableInterface' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/TerminableInterface.php', + 'Symfony\\Component\\HttpKernel\\Tests\\Fixtures\\ExtensionAbsentBundle\\ExtensionAbsentBundle' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php', + 'Symfony\\Component\\HttpKernel\\Tests\\Fixtures\\ExtensionLoadedBundle\\DependencyInjection\\ExtensionLoadedExtension' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.php', + 'Symfony\\Component\\HttpKernel\\Tests\\Fixtures\\ExtensionLoadedBundle\\ExtensionLoadedBundle' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php', + 'Symfony\\Component\\HttpKernel\\Tests\\Fixtures\\ExtensionPresentBundle\\Command\\BarCommand' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php', + 'Symfony\\Component\\HttpKernel\\Tests\\Fixtures\\ExtensionPresentBundle\\Command\\FooCommand' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionPresentBundle/Command/FooCommand.php', + 'Symfony\\Component\\HttpKernel\\Tests\\Fixtures\\ExtensionPresentBundle\\DependencyInjection\\ExtensionPresentExtension' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionPresentBundle/DependencyInjection/ExtensionPresentExtension.php', + 'Symfony\\Component\\HttpKernel\\Tests\\Fixtures\\ExtensionPresentBundle\\ExtensionPresentBundle' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionPresentBundle/ExtensionPresentBundle.php', + 'Symfony\\Component\\HttpKernel\\Tests\\Fixtures\\FooBarBundle' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/FooBarBundle.php', + 'Symfony\\Component\\HttpKernel\\Tests\\Fixtures\\KernelForOverrideName' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/KernelForOverrideName.php', + 'Symfony\\Component\\HttpKernel\\Tests\\Fixtures\\TestClient' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/TestClient.php', + 'Symfony\\Component\\HttpKernel\\Tests\\Fixtures\\TestEventDispatcher' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/TestEventDispatcher.php', + 'Symfony\\Component\\HttpKernel\\Tests\\HttpCache\\HttpCacheTestCase' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php', + 'Symfony\\Component\\HttpKernel\\Tests\\HttpCache\\TestHttpKernel' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/TestHttpKernel.php', + 'Symfony\\Component\\HttpKernel\\Tests\\HttpCache\\TestMultipleHttpKernel' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/TestMultipleHttpKernel.php', + 'Symfony\\Component\\HttpKernel\\Tests\\Logger' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Logger.php', + 'Symfony\\Component\\HttpKernel\\Tests\\Profiler\\Mock\\MemcacheMock' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/Mock/MemcacheMock.php', + 'Symfony\\Component\\HttpKernel\\Tests\\Profiler\\Mock\\MemcachedMock' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/Mock/MemcachedMock.php', + 'Symfony\\Component\\HttpKernel\\Tests\\Profiler\\Mock\\RedisMock' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/Mock/RedisMock.php', + 'Symfony\\Component\\HttpKernel\\Tests\\TestHttpKernel' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/TestHttpKernel.php', + 'Symfony\\Component\\HttpKernel\\UriSigner' => $vendorDir . '/symfony/http-kernel/Symfony/Component/HttpKernel/UriSigner.php', + 'Symfony\\Component\\Process\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/process/Symfony/Component/Process/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Process\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/process/Symfony/Component/Process/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Process\\Exception\\LogicException' => $vendorDir . '/symfony/process/Symfony/Component/Process/Exception/LogicException.php', + 'Symfony\\Component\\Process\\Exception\\ProcessFailedException' => $vendorDir . '/symfony/process/Symfony/Component/Process/Exception/ProcessFailedException.php', + 'Symfony\\Component\\Process\\Exception\\RuntimeException' => $vendorDir . '/symfony/process/Symfony/Component/Process/Exception/RuntimeException.php', + 'Symfony\\Component\\Process\\ExecutableFinder' => $vendorDir . '/symfony/process/Symfony/Component/Process/ExecutableFinder.php', + 'Symfony\\Component\\Process\\PhpExecutableFinder' => $vendorDir . '/symfony/process/Symfony/Component/Process/PhpExecutableFinder.php', + 'Symfony\\Component\\Process\\PhpProcess' => $vendorDir . '/symfony/process/Symfony/Component/Process/PhpProcess.php', + 'Symfony\\Component\\Process\\Process' => $vendorDir . '/symfony/process/Symfony/Component/Process/Process.php', + 'Symfony\\Component\\Process\\ProcessBuilder' => $vendorDir . '/symfony/process/Symfony/Component/Process/ProcessBuilder.php', + 'Symfony\\Component\\Process\\ProcessUtils' => $vendorDir . '/symfony/process/Symfony/Component/Process/ProcessUtils.php', + 'Symfony\\Component\\Process\\Tests\\ProcessInSigchildEnvironment' => $vendorDir . '/symfony/process/Symfony/Component/Process/Tests/ProcessInSigchildEnvironment.php', + 'Symfony\\Component\\Routing\\Annotation\\Route' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Annotation/Route.php', + 'Symfony\\Component\\Routing\\CompiledRoute' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/CompiledRoute.php', + 'Symfony\\Component\\Routing\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Routing\\Exception\\InvalidParameterException' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Exception/InvalidParameterException.php', + 'Symfony\\Component\\Routing\\Exception\\MethodNotAllowedException' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Exception/MethodNotAllowedException.php', + 'Symfony\\Component\\Routing\\Exception\\MissingMandatoryParametersException' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Exception/MissingMandatoryParametersException.php', + 'Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Exception/ResourceNotFoundException.php', + 'Symfony\\Component\\Routing\\Exception\\RouteNotFoundException' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Exception/RouteNotFoundException.php', + 'Symfony\\Component\\Routing\\Generator\\ConfigurableRequirementsInterface' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Generator/ConfigurableRequirementsInterface.php', + 'Symfony\\Component\\Routing\\Generator\\Dumper\\GeneratorDumper' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Generator/Dumper/GeneratorDumper.php', + 'Symfony\\Component\\Routing\\Generator\\Dumper\\GeneratorDumperInterface' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Generator/Dumper/GeneratorDumperInterface.php', + 'Symfony\\Component\\Routing\\Generator\\Dumper\\PhpGeneratorDumper' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php', + 'Symfony\\Component\\Routing\\Generator\\UrlGenerator' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Generator/UrlGenerator.php', + 'Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Generator/UrlGeneratorInterface.php', + 'Symfony\\Component\\Routing\\Loader\\AnnotationClassLoader' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Loader/AnnotationClassLoader.php', + 'Symfony\\Component\\Routing\\Loader\\AnnotationDirectoryLoader' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Loader/AnnotationDirectoryLoader.php', + 'Symfony\\Component\\Routing\\Loader\\AnnotationFileLoader' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Loader/AnnotationFileLoader.php', + 'Symfony\\Component\\Routing\\Loader\\ClosureLoader' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Loader/ClosureLoader.php', + 'Symfony\\Component\\Routing\\Loader\\PhpFileLoader' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Loader/PhpFileLoader.php', + 'Symfony\\Component\\Routing\\Loader\\XmlFileLoader' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Loader/XmlFileLoader.php', + 'Symfony\\Component\\Routing\\Loader\\YamlFileLoader' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Loader/YamlFileLoader.php', + 'Symfony\\Component\\Routing\\Matcher\\ApacheUrlMatcher' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Matcher/ApacheUrlMatcher.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\ApacheMatcherDumper' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\DumperCollection' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\DumperPrefixCollection' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperPrefixCollection.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\DumperRoute' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperRoute.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\MatcherDumper' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumper.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\MatcherDumperInterface' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumperInterface.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\PhpMatcherDumper' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php', + 'Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcher' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php', + 'Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Matcher/RedirectableUrlMatcherInterface.php', + 'Symfony\\Component\\Routing\\Matcher\\RequestMatcherInterface' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Matcher/RequestMatcherInterface.php', + 'Symfony\\Component\\Routing\\Matcher\\TraceableUrlMatcher' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php', + 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Matcher/UrlMatcher.php', + 'Symfony\\Component\\Routing\\Matcher\\UrlMatcherInterface' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Matcher/UrlMatcherInterface.php', + 'Symfony\\Component\\Routing\\RequestContext' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/RequestContext.php', + 'Symfony\\Component\\Routing\\RequestContextAwareInterface' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/RequestContextAwareInterface.php', + 'Symfony\\Component\\Routing\\Route' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Route.php', + 'Symfony\\Component\\Routing\\RouteCollection' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/RouteCollection.php', + 'Symfony\\Component\\Routing\\RouteCompiler' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/RouteCompiler.php', + 'Symfony\\Component\\Routing\\RouteCompilerInterface' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/RouteCompilerInterface.php', + 'Symfony\\Component\\Routing\\Router' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Router.php', + 'Symfony\\Component\\Routing\\RouterInterface' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/RouterInterface.php', + 'Symfony\\Component\\Routing\\Tests\\Fixtures\\AnnotatedClasses\\AbstractClass' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php', + 'Symfony\\Component\\Routing\\Tests\\Fixtures\\AnnotatedClasses\\BarClass' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/AnnotatedClasses/BarClass.php', + 'Symfony\\Component\\Routing\\Tests\\Fixtures\\AnnotatedClasses\\FooClass' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/AnnotatedClasses/FooClass.php', + 'Symfony\\Component\\Routing\\Tests\\Fixtures\\CustomXmlFileLoader' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/CustomXmlFileLoader.php', + 'Symfony\\Component\\Routing\\Tests\\Fixtures\\RedirectableUrlMatcher' => $vendorDir . '/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/RedirectableUrlMatcher.php', + 'Symfony\\Component\\Translation\\Catalogue\\AbstractOperation' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Catalogue/AbstractOperation.php', + 'Symfony\\Component\\Translation\\Catalogue\\DiffOperation' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Catalogue/DiffOperation.php', + 'Symfony\\Component\\Translation\\Catalogue\\MergeOperation' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Catalogue/MergeOperation.php', + 'Symfony\\Component\\Translation\\Catalogue\\OperationInterface' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Catalogue/OperationInterface.php', + 'Symfony\\Component\\Translation\\Dumper\\CsvFileDumper' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Dumper/CsvFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\DumperInterface' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Dumper/DumperInterface.php', + 'Symfony\\Component\\Translation\\Dumper\\FileDumper' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Dumper/FileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\IcuResFileDumper' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Dumper/IcuResFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\IniFileDumper' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Dumper/IniFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\MoFileDumper' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Dumper/MoFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\PhpFileDumper' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Dumper/PhpFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\PoFileDumper' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Dumper/PoFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\QtFileDumper' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Dumper/QtFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\XliffFileDumper' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Dumper/XliffFileDumper.php', + 'Symfony\\Component\\Translation\\Dumper\\YamlFileDumper' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Dumper/YamlFileDumper.php', + 'Symfony\\Component\\Translation\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Translation\\Exception\\InvalidResourceException' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Exception/InvalidResourceException.php', + 'Symfony\\Component\\Translation\\Exception\\NotFoundResourceException' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Exception/NotFoundResourceException.php', + 'Symfony\\Component\\Translation\\Extractor\\ChainExtractor' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Extractor/ChainExtractor.php', + 'Symfony\\Component\\Translation\\Extractor\\ExtractorInterface' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Extractor/ExtractorInterface.php', + 'Symfony\\Component\\Translation\\IdentityTranslator' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/IdentityTranslator.php', + 'Symfony\\Component\\Translation\\Interval' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Interval.php', + 'Symfony\\Component\\Translation\\Loader\\ArrayLoader' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Loader/ArrayLoader.php', + 'Symfony\\Component\\Translation\\Loader\\CsvFileLoader' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Loader/CsvFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\IcuDatFileLoader' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Loader/IcuDatFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\IcuResFileLoader' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Loader/IcuResFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\IniFileLoader' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Loader/IniFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\LoaderInterface' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Loader/LoaderInterface.php', + 'Symfony\\Component\\Translation\\Loader\\MoFileLoader' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Loader/MoFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\PhpFileLoader' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Loader/PhpFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\PoFileLoader' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Loader/PoFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\QtFileLoader' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Loader/QtFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\XliffFileLoader' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Loader/XliffFileLoader.php', + 'Symfony\\Component\\Translation\\Loader\\YamlFileLoader' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Loader/YamlFileLoader.php', + 'Symfony\\Component\\Translation\\MessageCatalogue' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/MessageCatalogue.php', + 'Symfony\\Component\\Translation\\MessageCatalogueInterface' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/MessageCatalogueInterface.php', + 'Symfony\\Component\\Translation\\MessageSelector' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/MessageSelector.php', + 'Symfony\\Component\\Translation\\MetadataAwareInterface' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/MetadataAwareInterface.php', + 'Symfony\\Component\\Translation\\PluralizationRules' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/PluralizationRules.php', + 'Symfony\\Component\\Translation\\Tests\\Loader\\LocalizedTestCase' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Tests/Loader/LocalizedTestCase.php', + 'Symfony\\Component\\Translation\\Translator' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Translator.php', + 'Symfony\\Component\\Translation\\TranslatorInterface' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/TranslatorInterface.php', + 'Symfony\\Component\\Translation\\Writer\\TranslationWriter' => $vendorDir . '/symfony/translation/Symfony/Component/Translation/Writer/TranslationWriter.php', + 'System' => $baseDir . '/app/database/migrations/2013_11_09_184814_system.php', + 'TMDb' => $baseDir . '/app/libraries/tmdb.php', + 'TMDbException' => $baseDir . '/app/libraries/tmdb.php', + 'TestCase' => $baseDir . '/app/tests/TestCase.php', + 'User' => $baseDir . '/app/models/User.php', + 'Vote' => $baseDir . '/app/models/Vote.php', + 'Whoops\\Exception\\ErrorException' => $vendorDir . '/filp/whoops/src/Whoops/Exception/ErrorException.php', + 'Whoops\\Exception\\Frame' => $vendorDir . '/filp/whoops/src/Whoops/Exception/Frame.php', + 'Whoops\\Exception\\FrameCollection' => $vendorDir . '/filp/whoops/src/Whoops/Exception/FrameCollection.php', + 'Whoops\\Exception\\Inspector' => $vendorDir . '/filp/whoops/src/Whoops/Exception/Inspector.php', + 'Whoops\\Handler\\CallbackHandler' => $vendorDir . '/filp/whoops/src/Whoops/Handler/CallbackHandler.php', + 'Whoops\\Handler\\Handler' => $vendorDir . '/filp/whoops/src/Whoops/Handler/Handler.php', + 'Whoops\\Handler\\HandlerInterface' => $vendorDir . '/filp/whoops/src/Whoops/Handler/HandlerInterface.php', + 'Whoops\\Handler\\JsonResponseHandler' => $vendorDir . '/filp/whoops/src/Whoops/Handler/JsonResponseHandler.php', + 'Whoops\\Handler\\PrettyPageHandler' => $vendorDir . '/filp/whoops/src/Whoops/Handler/PrettyPageHandler.php', + 'Whoops\\Module' => $vendorDir . '/filp/whoops/src/Whoops/Provider/Zend/Module.php', + 'Whoops\\Provider\\Phalcon\\WhoopsServiceProvider' => $vendorDir . '/filp/whoops/src/Whoops/Provider/Phalcon/WhoopsServiceProvider.php', + 'Whoops\\Provider\\Silex\\WhoopsServiceProvider' => $vendorDir . '/filp/whoops/src/Whoops/Provider/Silex/WhoopsServiceProvider.php', + 'Whoops\\Provider\\Zend\\ExceptionStrategy' => $vendorDir . '/filp/whoops/src/Whoops/Provider/Zend/ExceptionStrategy.php', + 'Whoops\\Provider\\Zend\\RouteNotFoundStrategy' => $vendorDir . '/filp/whoops/src/Whoops/Provider/Zend/RouteNotFoundStrategy.php', + 'Whoops\\Run' => $vendorDir . '/filp/whoops/src/Whoops/Run.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 0000000..a8ef604 --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,12 @@ + array($vendorDir . '/filp/whoops/src'), + 'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'), + 'Symfony\\Component\\Routing\\' => array($vendorDir . '/symfony/routing'), + 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'), + 'Symfony\\Component\\HttpKernel\\' => array($vendorDir . '/symfony/http-kernel'), + 'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'), + 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), + 'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'), + 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), + 'Symfony\\Component\\DomCrawler\\' => array($vendorDir . '/symfony/dom-crawler'), + 'Symfony\\Component\\Debug\\' => array($vendorDir . '/symfony/debug'), + 'Symfony\\Component\\CssSelector\\' => array($vendorDir . '/symfony/css-selector'), + 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), + 'Symfony\\Component\\BrowserKit\\' => array($vendorDir . '/symfony/browser-kit'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log'), + 'Predis' => array($vendorDir . '/predis/predis/lib'), + 'Patchwork' => array($vendorDir . '/patchwork/utf8/class'), + 'PHPParser' => array($vendorDir . '/nikic/php-parser/lib'), + 'Normalizer' => array($vendorDir . '/patchwork/utf8/class'), + 'Monolog' => array($vendorDir . '/monolog/monolog/src'), + 'Illuminate' => array($vendorDir . '/laravel/framework/src'), + 'Doctrine\\DBAL\\' => array($vendorDir . '/doctrine/dbal/lib'), + 'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib'), + 'Doctrine\\Common\\Inflector\\' => array($vendorDir . '/doctrine/inflector/lib'), + 'Doctrine\\Common\\Collections\\' => array($vendorDir . '/doctrine/collections/lib'), + 'Doctrine\\Common\\Cache\\' => array($vendorDir . '/doctrine/cache/lib'), + 'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib'), + 'Doctrine\\Common\\' => array($vendorDir . '/doctrine/common/lib'), + 'ClassPreloader' => array($vendorDir . '/classpreloader/classpreloader/src'), + 'Carbon' => array($vendorDir . '/nesbot/carbon/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..bb76896 --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,48 @@ + $path) { + $loader->set($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + + $loader->register(true); + + $includeFiles = require __DIR__ . '/autoload_files.php'; + foreach ($includeFiles as $file) { + require $file; + } + + return $loader; + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..6a44eca --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,1814 @@ +[ + { + "name": "psr/log", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", + "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", + "shasum": "" + }, + "time": "2012-12-21 11:40:51", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Psr\\Log\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "keywords": [ + "log", + "psr", + "psr-3" + ] + }, + { + "name": "monolog/monolog", + "version": "dev-master", + "version_normalized": "9999999-dev", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "1518320bec1f1583a0c514491528d171501dc781" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1518320bec1f1583a0c514491528d171501dc781", + "reference": "1518320bec1f1583a0c514491528d171501dc781", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "require-dev": { + "doctrine/couchdb": "dev-master", + "mlehner/gelf-php": "1.0.*", + "raven/raven": "0.5.*" + }, + "suggest": { + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "mlehner/gelf-php": "Allow sending log messages to a GrayLog2 server", + "raven/raven": "Allow sending log messages to a Sentry server" + }, + "time": "2013-09-02 10:24:25", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Monolog": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be", + "role": "Developer" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ] + }, + { + "name": "filp/whoops", + "version": "1.0.7", + "version_normalized": "1.0.7.0", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "12e144c788b275e5869ac15eda7bd0ae5da775bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/12e144c788b275e5869ac15eda7bd0ae5da775bd", + "reference": "12e144c788b275e5869ac15eda7bd0ae5da775bd", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "mockery/mockery": "dev-master", + "silex/silex": "1.0.*@dev" + }, + "time": "2013-06-10 12:22:13", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Whoops": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://github.com/filp/whoops", + "keywords": [ + "error", + "exception", + "handling", + "library", + "silex-provider", + "whoops", + "zf2" + ] + }, + { + "name": "doctrine/lexer", + "version": "dev-master", + "version_normalized": "9999999-dev", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "bc0e1f0cc285127a38c6c8ea88bc5dba2fd53e94" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/bc0e1f0cc285127a38c6c8ea88bc5dba2fd53e94", + "reference": "bc0e1f0cc285127a38c6c8ea88bc5dba2fd53e94", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "time": "2013-03-07 12:15:25", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com", + "homepage": "http://www.instaclick.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "http://jmsyst.com", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "lexer", + "parser" + ] + }, + { + "name": "doctrine/annotations", + "version": "v1.1.2", + "version_normalized": "1.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "40db0c96985aab2822edbc4848b3bd2429e02670" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/40db0c96985aab2822edbc4848b3bd2429e02670", + "reference": "40db0c96985aab2822edbc4848b3bd2429e02670", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": ">=5.3.2" + }, + "require-dev": { + "doctrine/cache": "1.*" + }, + "time": "2013-06-16 21:33:03", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Doctrine\\Common\\Annotations\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com", + "homepage": "http://www.jwage.com/" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com", + "homepage": "http://www.instaclick.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "http://jmsyst.com", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ] + }, + { + "name": "doctrine/collections", + "version": "dev-master", + "version_normalized": "9999999-dev", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "bcb53776a096a0c64579cc8d8ec0db62f1109fbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/bcb53776a096a0c64579cc8d8ec0db62f1109fbc", + "reference": "bcb53776a096a0c64579cc8d8ec0db62f1109fbc", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "time": "2013-08-29 16:56:45", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Doctrine\\Common\\Collections\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com", + "homepage": "http://www.jwage.com/" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com", + "homepage": "http://www.instaclick.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "http://jmsyst.com", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "Collections Abstraction library", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "array", + "collections", + "iterator" + ] + }, + { + "name": "doctrine/cache", + "version": "v1.1", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "2c9761ff1d13e188d5f7378066c1ce2882d7a336" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/2c9761ff1d13e188d5f7378066c1ce2882d7a336", + "reference": "2c9761ff1d13e188d5f7378066c1ce2882d7a336", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "time": "2013-08-07 16:04:25", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Doctrine\\Common\\Cache\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com", + "homepage": "http://www.jwage.com/" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com", + "homepage": "http://www.instaclick.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "http://jmsyst.com", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "Caching library offering an object-oriented API for many cache backends", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ] + }, + { + "name": "doctrine/inflector", + "version": "dev-master", + "version_normalized": "9999999-dev", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "8b4b3ccec7aafc596e2fc1e593c9f2e78f939c8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/8b4b3ccec7aafc596e2fc1e593c9f2e78f939c8c", + "reference": "8b4b3ccec7aafc596e2fc1e593c9f2e78f939c8c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "time": "2013-04-10 16:14:30", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Doctrine\\Common\\Inflector\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com", + "homepage": "http://www.jwage.com/" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com", + "homepage": "http://www.instaclick.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "http://jmsyst.com", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "Common String Manipulations with regard to casing and singular/plural rules.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "inflection", + "pluralize", + "singularize", + "string" + ] + }, + { + "name": "doctrine/common", + "version": "dev-master", + "version_normalized": "9999999-dev", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "b4c7860e0c5a8d13598ca34d156533300b0dacf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/b4c7860e0c5a8d13598ca34d156533300b0dacf0", + "reference": "b4c7860e0c5a8d13598ca34d156533300b0dacf0", + "shasum": "" + }, + "require": { + "doctrine/annotations": "1.*", + "doctrine/cache": "1.*", + "doctrine/collections": "1.*", + "doctrine/inflector": "1.*", + "doctrine/lexer": "1.*", + "php": ">=5.3.2" + }, + "time": "2013-08-20 18:10:31", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Doctrine\\Common\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com", + "homepage": "http://www.jwage.com/" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com", + "homepage": "http://www.instaclick.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "http://jmsyst.com", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "Common Library for Doctrine projects", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "collections", + "eventmanager", + "persistence", + "spl" + ] + }, + { + "name": "doctrine/dbal", + "version": "dev-master", + "version_normalized": "9999999-dev", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "60cc6c2da04c4025bdd72795d7423ae75ebf1455" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/60cc6c2da04c4025bdd72795d7423ae75ebf1455", + "reference": "60cc6c2da04c4025bdd72795d7423ae75ebf1455", + "shasum": "" + }, + "require": { + "doctrine/common": "2.4.*@beta", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*", + "symfony/console": "2.*" + }, + "suggest": { + "symfony/console": "Allows use of the command line interface" + }, + "time": "2013-09-05 17:30:39", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Doctrine\\DBAL\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com", + "homepage": "http://www.jwage.com/" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com", + "homepage": "http://www.instaclick.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + } + ], + "description": "Database Abstraction Layer", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "database", + "dbal", + "persistence", + "queryobject" + ] + }, + { + "name": "symfony/translation", + "version": "2.3.x-dev", + "version_normalized": "2.3.9999999.9999999-dev", + "target-dir": "Symfony/Component/Translation", + "source": { + "type": "git", + "url": "https://github.com/symfony/Translation.git", + "reference": "65f888291f0896ad492f9abc6dc05c998373aded" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Translation/zipball/65f888291f0896ad492f9abc6dc05c998373aded", + "reference": "65f888291f0896ad492f9abc6dc05c998373aded", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/config": "~2.0", + "symfony/yaml": "~2.2" + }, + "suggest": { + "symfony/config": "", + "symfony/yaml": "" + }, + "time": "2013-08-26 05:49:51", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Translation Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/routing", + "version": "2.3.x-dev", + "version_normalized": "2.3.9999999.9999999-dev", + "target-dir": "Symfony/Component/Routing", + "source": { + "type": "git", + "url": "https://github.com/symfony/Routing.git", + "reference": "3085975262bc36dc89929a936056dc6e5cb8a100" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Routing/zipball/3085975262bc36dc89929a936056dc6e5cb8a100", + "reference": "3085975262bc36dc89929a936056dc6e5cb8a100", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "doctrine/common": "~2.2", + "psr/log": "~1.0", + "symfony/config": "~2.2", + "symfony/yaml": "~2.0" + }, + "suggest": { + "doctrine/common": "", + "symfony/config": "", + "symfony/yaml": "" + }, + "time": "2013-09-06 18:20:34", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\Routing\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/process", + "version": "2.3.x-dev", + "version_normalized": "2.3.9999999.9999999-dev", + "target-dir": "Symfony/Component/Process", + "source": { + "type": "git", + "url": "https://github.com/symfony/Process.git", + "reference": "6e3b487a74ce7dcab6af34e14afcb9d438d69327" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Process/zipball/6e3b487a74ce7dcab6af34e14afcb9d438d69327", + "reference": "6e3b487a74ce7dcab6af34e14afcb9d438d69327", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2013-08-30 13:10:46", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\Process\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/debug", + "version": "2.3.x-dev", + "version_normalized": "2.3.9999999.9999999-dev", + "target-dir": "Symfony/Component/Debug", + "source": { + "type": "git", + "url": "https://github.com/symfony/Debug.git", + "reference": "729f6d19cfc401c4942e43fcc1059103bd6df130" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Debug/zipball/729f6d19cfc401c4942e43fcc1059103bd6df130", + "reference": "729f6d19cfc401c4942e43fcc1059103bd6df130", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/http-foundation": "~2.1", + "symfony/http-kernel": "~2.1" + }, + "suggest": { + "symfony/class-loader": "", + "symfony/http-foundation": "", + "symfony/http-kernel": "" + }, + "time": "2013-08-08 14:16:10", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\Debug\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/http-foundation", + "version": "2.3.x-dev", + "version_normalized": "2.3.9999999.9999999-dev", + "target-dir": "Symfony/Component/HttpFoundation", + "source": { + "type": "git", + "url": "https://github.com/symfony/HttpFoundation.git", + "reference": "cd7b96d5777a742e178fb58059ab70d794b5b351" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/cd7b96d5777a742e178fb58059ab70d794b5b351", + "reference": "cd7b96d5777a742e178fb58059ab70d794b5b351", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2013-08-29 19:32:30", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "classmap": [ + "Symfony/Component/HttpFoundation/Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/event-dispatcher", + "version": "2.3.x-dev", + "version_normalized": "2.3.9999999.9999999-dev", + "target-dir": "Symfony/Component/EventDispatcher", + "source": { + "type": "git", + "url": "https://github.com/symfony/EventDispatcher.git", + "reference": "41c9826457c65fa3cf746f214985b7ca9cba42f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/41c9826457c65fa3cf746f214985b7ca9cba42f8", + "reference": "41c9826457c65fa3cf746f214985b7ca9cba42f8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/dependency-injection": "~2.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "time": "2013-07-21 12:12:18", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/http-kernel", + "version": "2.3.x-dev", + "version_normalized": "2.3.9999999.9999999-dev", + "target-dir": "Symfony/Component/HttpKernel", + "source": { + "type": "git", + "url": "https://github.com/symfony/HttpKernel.git", + "reference": "0bd6f23256c47d209ed0f30ce54aef0405a750ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/HttpKernel/zipball/0bd6f23256c47d209ed0f30ce54aef0405a750ec", + "reference": "0bd6f23256c47d209ed0f30ce54aef0405a750ec", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "psr/log": "~1.0", + "symfony/debug": "~2.3", + "symfony/event-dispatcher": "~2.1", + "symfony/http-foundation": "~2.2" + }, + "require-dev": { + "symfony/browser-kit": "~2.2", + "symfony/class-loader": "~2.1", + "symfony/config": "~2.0", + "symfony/console": "~2.2", + "symfony/dependency-injection": "~2.0", + "symfony/finder": "~2.0", + "symfony/process": "~2.0", + "symfony/routing": "~2.2", + "symfony/stopwatch": "~2.2", + "symfony/templating": "~2.2" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "" + }, + "time": "2013-09-06 18:20:34", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\HttpKernel\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony HttpKernel Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/finder", + "version": "2.3.x-dev", + "version_normalized": "2.3.9999999.9999999-dev", + "target-dir": "Symfony/Component/Finder", + "source": { + "type": "git", + "url": "https://github.com/symfony/Finder.git", + "reference": "4a0fee5b86f5bbd9dfdc11ec124eba2915737ce1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Finder/zipball/4a0fee5b86f5bbd9dfdc11ec124eba2915737ce1", + "reference": "4a0fee5b86f5bbd9dfdc11ec124eba2915737ce1", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2013-08-13 20:18:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\Finder\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/dom-crawler", + "version": "2.3.x-dev", + "version_normalized": "2.3.9999999.9999999-dev", + "target-dir": "Symfony/Component/DomCrawler", + "source": { + "type": "git", + "url": "https://github.com/symfony/DomCrawler.git", + "reference": "e05e07fe8958a304b5e135f8e65d4ae6148cf59b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/DomCrawler/zipball/e05e07fe8958a304b5e135f8e65d4ae6148cf59b", + "reference": "e05e07fe8958a304b5e135f8e65d4ae6148cf59b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/css-selector": "~2.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "time": "2013-07-21 12:12:18", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\DomCrawler\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony DomCrawler Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/css-selector", + "version": "2.3.x-dev", + "version_normalized": "2.3.9999999.9999999-dev", + "target-dir": "Symfony/Component/CssSelector", + "source": { + "type": "git", + "url": "https://github.com/symfony/CssSelector.git", + "reference": "885544201cb24e79754da1dbd61bd779c2e4353e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/CssSelector/zipball/885544201cb24e79754da1dbd61bd779c2e4353e", + "reference": "885544201cb24e79754da1dbd61bd779c2e4353e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2013-07-21 12:12:18", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\CssSelector\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/console", + "version": "2.3.x-dev", + "version_normalized": "2.3.9999999.9999999-dev", + "target-dir": "Symfony/Component/Console", + "source": { + "type": "git", + "url": "https://github.com/symfony/Console.git", + "reference": "6db369933dae13262c7c8c9a265d51723be1d2e3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Console/zipball/6db369933dae13262c7c8c9a265d51723be1d2e3", + "reference": "6db369933dae13262c7c8c9a265d51723be1d2e3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/event-dispatcher": "~2.1" + }, + "suggest": { + "symfony/event-dispatcher": "" + }, + "time": "2013-09-06 18:20:34", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\Console\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/browser-kit", + "version": "2.3.x-dev", + "version_normalized": "2.3.9999999.9999999-dev", + "target-dir": "Symfony/Component/BrowserKit", + "source": { + "type": "git", + "url": "https://github.com/symfony/BrowserKit.git", + "reference": "11dcdf2a3f485e0b362cfc364b7e9e5ef80562ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/BrowserKit/zipball/11dcdf2a3f485e0b362cfc364b7e9e5ef80562ab", + "reference": "11dcdf2a3f485e0b362cfc364b7e9e5ef80562ab", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/dom-crawler": "~2.0" + }, + "require-dev": { + "symfony/css-selector": "~2.0", + "symfony/process": "~2.0" + }, + "suggest": { + "symfony/process": "" + }, + "time": "2013-08-31 06:12:22", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\BrowserKit\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony BrowserKit Component", + "homepage": "http://symfony.com" + }, + { + "name": "swiftmailer/swiftmailer", + "version": "v5.0.2", + "version_normalized": "5.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "f3917ecef35a4e4d98b303eb9fee463bc983f379" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/f3917ecef35a4e4d98b303eb9fee463bc983f379", + "reference": "f3917ecef35a4e4d98b303eb9fee463bc983f379", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "time": "2013-08-30 12:35:21", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "lib/swift_required.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Chris Corbyn" + } + ], + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "http://swiftmailer.org", + "keywords": [ + "mail", + "mailer" + ] + }, + { + "name": "predis/predis", + "version": "0.8.x-dev", + "version_normalized": "0.8.9999999.9999999-dev", + "source": { + "type": "git", + "url": "https://github.com/nrk/predis.git", + "reference": "4824af15631f6a80a939cdc58104d03e0aadd17a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nrk/predis/zipball/4824af15631f6a80a939cdc58104d03e0aadd17a", + "reference": "4824af15631f6a80a939cdc58104d03e0aadd17a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "suggest": { + "ext-curl": "Allows access to Webdis when paired with phpiredis", + "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" + }, + "time": "2013-08-31 08:51:10", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Predis": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniele Alessandri", + "email": "suppakilla@gmail.com", + "homepage": "http://clorophilla.net" + } + ], + "description": "Flexible and feature-complete PHP client library for Redis", + "homepage": "http://github.com/nrk/predis", + "keywords": [ + "nosql", + "predis", + "redis" + ] + }, + { + "name": "patchwork/utf8", + "version": "v1.1.11", + "version_normalized": "1.1.11.0", + "source": { + "type": "git", + "url": "https://github.com/nicolas-grekas/Patchwork-UTF8.git", + "reference": "153c3b3e5dded58a1eb749c80049f44ce086399e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nicolas-grekas/Patchwork-UTF8/zipball/153c3b3e5dded58a1eb749c80049f44ce086399e", + "reference": "153c3b3e5dded58a1eb749c80049f44ce086399e", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2013-08-19 09:37:02", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Patchwork": "class/", + "Normalizer": "class/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "(Apache-2.0 or GPL-2.0)" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com", + "role": "Developer" + } + ], + "description": "UTF-8 strings handling for PHP 5.3: portable, performant and extended", + "homepage": "https://github.com/nicolas-grekas/Patchwork-UTF8", + "keywords": [ + "i18n", + "unicode", + "utf-8", + "utf8" + ] + }, + { + "name": "nesbot/carbon", + "version": "1.3.0", + "version_normalized": "1.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "24c4262e994f7f2a258db5ccb5b2f34073f162ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/24c4262e994f7f2a258db5ccb5b2f34073f162ca", + "reference": "24c4262e994f7f2a258db5ccb5b2f34073f162ca", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2013-08-21 04:36:40", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Carbon": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + } + ], + "description": "A simple API extension for DateTime.", + "homepage": "https://github.com/briannesbitt/Carbon", + "keywords": [ + "date", + "datetime", + "time" + ] + }, + { + "name": "ircmaxell/password-compat", + "version": "1.0.x-dev", + "version_normalized": "1.0.9999999.9999999-dev", + "source": { + "type": "git", + "url": "https://github.com/ircmaxell/password_compat.git", + "reference": "1fc1521b5e9794ea77e4eca30717be9635f1d4f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/1fc1521b5e9794ea77e4eca30717be9635f1d4f4", + "reference": "1fc1521b5e9794ea77e4eca30717be9635f1d4f4", + "shasum": "" + }, + "time": "2013-04-30 19:58:08", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "lib/password.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Anthony Ferrara", + "email": "ircmaxell@php.net", + "homepage": "http://blog.ircmaxell.com" + } + ], + "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash", + "homepage": "https://github.com/ircmaxell/password_compat", + "keywords": [ + "hashing", + "password" + ] + }, + { + "name": "nikic/php-parser", + "version": "dev-master", + "version_normalized": "9999999-dev", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "1e5e280ae88a27effa2ae4aa2bd088494ed8594f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1e5e280ae88a27effa2ae4aa2bd088494ed8594f", + "reference": "1e5e280ae88a27effa2ae4aa2bd088494ed8594f", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "time": "2013-08-25 17:11:40", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.9-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "PHPParser": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ] + }, + { + "name": "symfony/filesystem", + "version": "dev-master", + "version_normalized": "9999999-dev", + "target-dir": "Symfony/Component/Filesystem", + "source": { + "type": "git", + "url": "https://github.com/symfony/Filesystem.git", + "reference": "da0dcf60b7307f68667fecef8ccbb2b070bda7cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Filesystem/zipball/da0dcf60b7307f68667fecef8ccbb2b070bda7cf", + "reference": "da0dcf60b7307f68667fecef8ccbb2b070bda7cf", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2013-08-09 07:26:54", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Symfony\\Component\\Filesystem\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "http://symfony.com" + }, + { + "name": "classpreloader/classpreloader", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/mtdowling/ClassPreloader.git", + "reference": "1a50f7945b725ff2c60f234e51407d1d6e7c77c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mtdowling/ClassPreloader/zipball/1a50f7945b725ff2c60f234e51407d1d6e7c77c5", + "reference": "1a50f7945b725ff2c60f234e51407d1d6e7c77c5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "*", + "php": ">=5.3.3", + "symfony/console": ">2.0", + "symfony/filesystem": ">2.0", + "symfony/finder": ">2.0" + }, + "time": "2013-06-24 22:58:34", + "bin": [ + "classpreloader.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "ClassPreloader": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Helps class loading performance by generating a single PHP file containing all of the autoloaded files for a specific use case", + "keywords": [ + "autoload", + "class", + "preload" + ] + }, + { + "name": "laravel/framework", + "version": "4.0.x-dev", + "version_normalized": "4.0.9999999.9999999-dev", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "307dcc142d65c71caf54d0192790dc1cfc78749f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/307dcc142d65c71caf54d0192790dc1cfc78749f", + "reference": "307dcc142d65c71caf54d0192790dc1cfc78749f", + "shasum": "" + }, + "require": { + "classpreloader/classpreloader": "1.0.*", + "doctrine/dbal": "2.4.x", + "filp/whoops": "1.0.7", + "ircmaxell/password-compat": "1.0.*", + "monolog/monolog": "1.6.*", + "nesbot/carbon": "1.*", + "patchwork/utf8": "1.1.*", + "php": ">=5.3.0", + "predis/predis": "0.8.*", + "swiftmailer/swiftmailer": "5.0.*", + "symfony/browser-kit": "2.3.*", + "symfony/console": "2.3.*", + "symfony/css-selector": "2.3.*", + "symfony/debug": "2.3.*", + "symfony/dom-crawler": "2.3.*", + "symfony/event-dispatcher": "2.3.*", + "symfony/finder": "2.3.*", + "symfony/http-foundation": "2.3.*", + "symfony/http-kernel": "2.3.*", + "symfony/process": "2.3.*", + "symfony/routing": "2.3.*", + "symfony/translation": "2.3.*" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/cache": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/exception": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/foundation": "self.version", + "illuminate/hashing": "self.version", + "illuminate/html": "self.version", + "illuminate/http": "self.version", + "illuminate/log": "self.version", + "illuminate/mail": "self.version", + "illuminate/pagination": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version", + "illuminate/workbench": "self.version" + }, + "require-dev": { + "aws/aws-sdk-php": "2.2.*", + "iron-io/iron_mq": "1.4.4", + "mockery/mockery": "0.7.2", + "pda/pheanstalk": "2.0.*", + "phpunit/phpunit": "3.7.*" + }, + "time": "2013-08-31 21:50:30", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + [ + "src/Illuminate/Queue/IlluminateQueueClosure.php" + ] + ], + "files": [ + "src/Illuminate/Support/helpers.php" + ], + "psr-0": { + "Illuminate": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com", + "homepage": "https://github.com/taylorotwell", + "role": "Developer" + } + ], + "description": "The Laravel Framework.", + "keywords": [ + "framework", + "laravel" + ] + } +] diff --git a/vendor/doctrine/annotations/.gitignore b/vendor/doctrine/annotations/.gitignore new file mode 100644 index 0000000..164c346 --- /dev/null +++ b/vendor/doctrine/annotations/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +composer.phar diff --git a/vendor/doctrine/annotations/.travis.yml b/vendor/doctrine/annotations/.travis.yml new file mode 100644 index 0000000..f3dcb23 --- /dev/null +++ b/vendor/doctrine/annotations/.travis.yml @@ -0,0 +1,9 @@ +language: php + +php: + - 5.3 + - 5.4 + +before_script: + - composer --prefer-source --dev install + - phpunit diff --git a/vendor/doctrine/annotations/README.md b/vendor/doctrine/annotations/README.md new file mode 100644 index 0000000..faad51f --- /dev/null +++ b/vendor/doctrine/annotations/README.md @@ -0,0 +1,11 @@ +# Doctrine Annotations + +[![Build Status](https://travis-ci.org/doctrine/annotations.png?branch=master)](https://travis-ci.org/doctrine/annotations) + +Docblock Annotations Parser library (extracted from Doctrine Common). + +## Changelog + +### v1.1 + +* Add Exception when ZendOptimizer+ or Opcache is configured to drop comments diff --git a/vendor/doctrine/annotations/composer.json b/vendor/doctrine/annotations/composer.json new file mode 100644 index 0000000..3569cf4 --- /dev/null +++ b/vendor/doctrine/annotations/composer.json @@ -0,0 +1,30 @@ +{ + "name": "doctrine/annotations", + "type": "library", + "description": "Docblock Annotations Parser", + "keywords": ["annotations", "docblock", "parser"], + "homepage": "http://www.doctrine-project.org", + "license": "MIT", + "authors": [ + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, + {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, + {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} + ], + "require": { + "php": ">=5.3.2", + "doctrine/lexer": "1.*" + }, + "require-dev": { + "doctrine/cache": "1.*" + }, + "autoload": { + "psr-0": { "Doctrine\\Common\\Annotations\\": "lib/" } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php new file mode 100644 index 0000000..6a1390a --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation.php @@ -0,0 +1,79 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * Annotations class + * + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class Annotation +{ + /** + * Value property. Common among all derived classes. + * + * @var string + */ + public $value; + + /** + * Constructor + * + * @param array $data Key-value for properties to be defined in this class + */ + public final function __construct(array $data) + { + foreach ($data as $key => $value) { + $this->$key = $value; + } + } + + /** + * Error handler for unknown property accessor in Annotation class. + * + * @param string $name Unknown property name + * + * @throws \BadMethodCallException + */ + public function __get($name) + { + throw new \BadMethodCallException( + sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this)) + ); + } + + /** + * Error handler for unknown property mutator in Annotation class. + * + * @param string $name Unkown property name + * @param mixed $value Property value + * + * @throws \BadMethodCallException + */ + public function __set($name, $value) + { + throw new \BadMethodCallException( + sprintf("Unknown property '%s' on annotation '%s'.", $name, get_class($this)) + ); + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php new file mode 100644 index 0000000..dbef6df --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attribute.php @@ -0,0 +1,47 @@ +. + */ + +namespace Doctrine\Common\Annotations\Annotation; + +/** + * Annotation that can be used to signal to the parser + * to check the attribute type during the parsing process. + * + * @author Fabio B. Silva + * + * @Annotation + */ +final class Attribute +{ + /** + * @var string + */ + public $name; + + /** + * @var string + */ + public $type; + + /** + * @var boolean + */ + public $required = false; +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php new file mode 100644 index 0000000..53134e3 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Attributes.php @@ -0,0 +1,37 @@ +. + */ + +namespace Doctrine\Common\Annotations\Annotation; + +/** + * Annotation that can be used to signal to the parser + * to check the types of all declared attributes during the parsing process. + * + * @author Fabio B. Silva + * + * @Annotation + */ +final class Attributes +{ + /** + * @var array + */ + public $value; +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php new file mode 100644 index 0000000..315812f --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Enum.php @@ -0,0 +1,85 @@ +. + */ + +namespace Doctrine\Common\Annotations\Annotation; + +/** + * Annotation that can be used to signal to the parser + * to check the available values during the parsing process. + * + * @since 2.4 + * @author Fabio B. Silva + * + * @Annotation + * @Attributes({ + * @Attribute("value", required = true, type = "array"), + * @Attribute("literal", required = false, type = "array") + * }) + */ +final class Enum +{ + /** + * @var array + */ + public $value; + + /** + * Literal target declaration. + * + * @var array + */ + public $literal; + + /** + * Annotation construct + * + * @param array $values + * + * @throws \InvalidArgumentException + */ + public function __construct(array $values) + { + if ( ! isset($values['literal'])) { + $values['literal'] = array(); + } + + foreach ($values['value'] as $var) { + if( ! is_scalar($var)) { + throw new \InvalidArgumentException(sprintf( + '@Enum supports only scalar values "%s" given.', + is_object($var) ? get_class($var) : gettype($var) + )); + } + } + + foreach ($values['literal'] as $key => $var) { + if( ! in_array($key, $values['value'])) { + throw new \InvalidArgumentException(sprintf( + 'Undefined enumerator value "%s" for literal "%s".', + $key , $var + )); + } + } + + $this->value = $values['value']; + $this->literal = $values['literal']; + } + +} \ No newline at end of file diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php new file mode 100644 index 0000000..a84a4f5 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/IgnoreAnnotation.php @@ -0,0 +1,54 @@ +. + */ + +namespace Doctrine\Common\Annotations\Annotation; + +/** + * Annotation that can be used to signal to the parser to ignore specific + * annotations during the parsing process. + * + * @Annotation + * @author Johannes M. Schmitt + */ +final class IgnoreAnnotation +{ + /** + * @var array + */ + public $names; + + /** + * Constructor + * + * @param array $values + * + * @throws \RuntimeException + */ + public function __construct(array $values) + { + if (is_string($values['value'])) { + $values['value'] = array($values['value']); + } + if (!is_array($values['value'])) { + throw new \RuntimeException(sprintf('@IgnoreAnnotation expects either a string name, or an array of strings, but got %s.', json_encode($values['value']))); + } + + $this->names = $values['value']; + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php new file mode 100644 index 0000000..d67f960 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Required.php @@ -0,0 +1,33 @@ +. + */ + +namespace Doctrine\Common\Annotations\Annotation; + +/** + * Annotation that can be used to signal to the parser + * to check if that attribute is required during the parsing process. + * + * @author Fabio B. Silva + * + * @Annotation + */ +final class Required +{ +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php new file mode 100644 index 0000000..64655ef --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Annotation/Target.php @@ -0,0 +1,107 @@ +. + */ + +namespace Doctrine\Common\Annotations\Annotation; + +/** + * Annotation that can be used to signal to the parser + * to check the annotation target during the parsing process. + * + * @author Fabio B. Silva + * + * @Annotation + */ +final class Target +{ + const TARGET_CLASS = 1; + const TARGET_METHOD = 2; + const TARGET_PROPERTY = 4; + const TARGET_ANNOTATION = 8; + const TARGET_ALL = 15; + + /** + * @var array + */ + private static $map = array( + 'ALL' => self::TARGET_ALL, + 'CLASS' => self::TARGET_CLASS, + 'METHOD' => self::TARGET_METHOD, + 'PROPERTY' => self::TARGET_PROPERTY, + 'ANNOTATION' => self::TARGET_ANNOTATION, + ); + + /** + * @var array + */ + public $value; + + /** + * Targets as bitmask. + * + * @var integer + */ + public $targets; + + /** + * Literal target declaration. + * + * @var integer + */ + public $literal; + + /** + * Annotation construct + * + * @param array $values + * + * @throws \InvalidArgumentException + */ + public function __construct(array $values) + { + if (!isset($values['value'])){ + $values['value'] = null; + } + if (is_string($values['value'])){ + $values['value'] = array($values['value']); + } + if (!is_array($values['value'])){ + throw new \InvalidArgumentException( + sprintf('@Target expects either a string value, or an array of strings, "%s" given.', + is_object($values['value']) ? get_class($values['value']) : gettype($values['value']) + ) + ); + } + + $bitmask = 0; + foreach ($values['value'] as $literal) { + if(!isset(self::$map[$literal])){ + throw new \InvalidArgumentException( + sprintf('Invalid Target "%s". Available targets: [%s]', + $literal, implode(', ', array_keys(self::$map))) + ); + } + $bitmask += self::$map[$literal]; + } + + $this->targets = $bitmask; + $this->value = $values['value']; + $this->literal = implode(', ', $this->value); + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php new file mode 100644 index 0000000..6cdb661 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php @@ -0,0 +1,158 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * Description of AnnotationException + * + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class AnnotationException extends \Exception +{ + /** + * Creates a new AnnotationException describing a Syntax error. + * + * @param string $message Exception message + * @return AnnotationException + */ + public static function syntaxError($message) + { + return new self('[Syntax Error] ' . $message); + } + + /** + * Creates a new AnnotationException describing a Semantical error. + * + * @param string $message Exception message + * @return AnnotationException + */ + public static function semanticalError($message) + { + return new self('[Semantical Error] ' . $message); + } + + /** + * Creates a new AnnotationException describing a constant semantical error. + * + * @since 2.3 + * @param string $identifier + * @param string $context + * @return AnnotationException + */ + public static function semanticalErrorConstants($identifier, $context = null) + { + return self::semanticalError(sprintf( + "Couldn't find constant %s%s", $identifier, + $context ? ", $context." : "." + )); + } + + /** + * Creates a new AnnotationException describing an error which occurred during + * the creation of the annotation. + * + * @since 2.2 + * @param string $message + * @return AnnotationException + */ + public static function creationError($message) + { + return new self('[Creation Error] ' . $message); + } + + /** + * Creates a new AnnotationException describing an type error of an attribute. + * + * @since 2.2 + * @param string $attributeName + * @param string $annotationName + * @param string $context + * @param string $expected + * @param mixed $actual + * @return AnnotationException + */ + public static function typeError($attributeName, $annotationName, $context, $expected, $actual) + { + return new self(sprintf( + '[Type Error] Attribute "%s" of @%s declared on %s expects %s, but got %s.', + $attributeName, + $annotationName, + $context, + $expected, + is_object($actual) ? 'an instance of '.get_class($actual) : gettype($actual) + )); + } + + /** + * Creates a new AnnotationException describing an required error of an attribute. + * + * @since 2.2 + * @param string $attributeName + * @param string $annotationName + * @param string $context + * @param string $expected + * @return AnnotationException + */ + public static function requiredError($attributeName, $annotationName, $context, $expected) + { + return new self(sprintf( + '[Type Error] Attribute "%s" of @%s declared on %s expects %s. This value should not be null.', + $attributeName, + $annotationName, + $context, + $expected + )); + } + + /** + * Creates a new AnnotationException describing a invalid enummerator. + * + * @since 2.4 + * @param string $attributeName + * @param string $annotationName + * @param string $context + * @param array $available + * @param mixed $given + * @return AnnotationException + */ + public static function enumeratorError($attributeName, $annotationName, $context, $available, $given) + { + throw new self(sprintf( + '[Enum Error] Attribute "%s" of @%s declared on %s accept only [%s], but got %s.', + $attributeName, + $annotationName, + $context, + implode(', ', $available), + is_object($given) ? get_class($given) : $given + )); + } + + /** + * @return AnnotationException + */ + public static function optimizerPlusSaveComments() + { + throw new self("You have to enable opcache.save_comments=1 or zend_optimizerplus.save_comments=1."); + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php new file mode 100644 index 0000000..ad4a264 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php @@ -0,0 +1,318 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +use Doctrine\Common\Annotations\Annotation\IgnoreAnnotation; +use Doctrine\Common\Annotations\Annotation\Target; +use Closure; +use ReflectionClass; +use ReflectionMethod; +use ReflectionProperty; + +/** + * A reader for docblock annotations. + * + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Johannes M. Schmitt + */ +class AnnotationReader implements Reader +{ + /** + * Global map for imports. + * + * @var array + */ + private static $globalImports = array( + 'ignoreannotation' => 'Doctrine\Common\Annotations\Annotation\IgnoreAnnotation', + ); + + /** + * A list with annotations that are not causing exceptions when not resolved to an annotation class. + * + * The names are case sensitive. + * + * @var array + */ + private static $globalIgnoredNames = array( + 'access'=> true, 'author'=> true, 'copyright'=> true, 'deprecated'=> true, + 'example'=> true, 'ignore'=> true, 'internal'=> true, 'link'=> true, 'see'=> true, + 'since'=> true, 'tutorial'=> true, 'version'=> true, 'package'=> true, + 'subpackage'=> true, 'name'=> true, 'global'=> true, 'param'=> true, + 'return'=> true, 'staticvar'=> true, 'category'=> true, 'staticVar'=> true, + 'static'=> true, 'var'=> true, 'throws'=> true, 'inheritdoc'=> true, + 'inheritDoc'=> true, 'license'=> true, 'todo'=> true, 'TODO'=> true, + 'deprec'=> true, 'property' => true, 'method' => true, + 'abstract'=> true, 'exception'=> true, 'magic' => true, 'api' => true, + 'final'=> true, 'filesource'=> true, 'throw' => true, 'uses' => true, + 'usedby'=> true, 'private' => true, 'Annotation' => true, 'override' => true, + 'codeCoverageIgnore' => true, 'codeCoverageIgnoreStart' => true, 'codeCoverageIgnoreEnd' => true, + 'Required' => true, 'Attribute' => true, 'Attributes' => true, + 'Target' => true, 'SuppressWarnings' => true, + 'ingroup' => true, 'code' => true, 'endcode' => true, + 'package_version' => true, 'fixme' => true + ); + + /** + * Add a new annotation to the globally ignored annotation names with regard to exception handling. + * + * @param string $name + */ + static public function addGlobalIgnoredName($name) + { + self::$globalIgnoredNames[$name] = true; + } + + /** + * Annotations Parser + * + * @var \Doctrine\Common\Annotations\DocParser + */ + private $parser; + + /** + * Annotations Parser used to collect parsing metadata + * + * @var \Doctrine\Common\Annotations\DocParser + */ + private $preParser; + + /** + * PHP Parser used to collect imports. + * + * @var \Doctrine\Common\Annotations\PhpParser + */ + private $phpParser; + + /** + * In-memory cache mechanism to store imported annotations per class. + * + * @var array + */ + private $imports = array(); + + /** + * In-memory cache mechanism to store ignored annotations per class. + * + * @var array + */ + private $ignoredAnnotationNames = array(); + + /** + * Constructor. + * + * Initializes a new AnnotationReader. + */ + public function __construct() + { + if (extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === "0" || ini_get('opcache.save_comments') === "0")) { + throw AnnotationException::optimizerPlusSaveComments(); + } + + if (extension_loaded('opcache') && ini_get('opcache.save_comments') == 0) { + throw AnnotationException::optimizerPlusSaveComments(); + } + + AnnotationRegistry::registerFile(__DIR__ . '/Annotation/IgnoreAnnotation.php'); + + $this->parser = new DocParser; + + $this->preParser = new DocParser; + $this->preParser->setImports(self::$globalImports); + $this->preParser->setIgnoreNotImportedAnnotations(true); + + $this->phpParser = new PhpParser; + } + + /** + * Gets the annotations applied to a class. + * + * @param ReflectionClass $class The ReflectionClass of the class from which + * the class annotations should be read. + * @return array An array of Annotations. + */ + public function getClassAnnotations(ReflectionClass $class) + { + $this->parser->setTarget(Target::TARGET_CLASS); + $this->parser->setImports($this->getImports($class)); + $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); + + return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName()); + } + + /** + * Gets a class annotation. + * + * @param ReflectionClass $class The ReflectionClass of the class from which + * the class annotations should be read. + * @param string $annotationName The name of the annotation. + * @return mixed The Annotation or NULL, if the requested annotation does not exist. + */ + public function getClassAnnotation(ReflectionClass $class, $annotationName) + { + $annotations = $this->getClassAnnotations($class); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * Gets the annotations applied to a property. + * + * @param ReflectionProperty $property The ReflectionProperty of the property + * from which the annotations should be read. + * @return array An array of Annotations. + */ + public function getPropertyAnnotations(ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + $context = 'property ' . $class->getName() . "::\$" . $property->getName(); + $this->parser->setTarget(Target::TARGET_PROPERTY); + $this->parser->setImports($this->getImports($class)); + $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); + + return $this->parser->parse($property->getDocComment(), $context); + } + + /** + * Gets a property annotation. + * + * @param ReflectionProperty $property + * @param string $annotationName The name of the annotation. + * @return mixed The Annotation or NULL, if the requested annotation does not exist. + */ + public function getPropertyAnnotation(ReflectionProperty $property, $annotationName) + { + $annotations = $this->getPropertyAnnotations($property); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * Gets the annotations applied to a method. + * + * @param \ReflectionMethod $method The ReflectionMethod of the method from which + * the annotations should be read. + * + * @return array An array of Annotations. + */ + public function getMethodAnnotations(ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + $context = 'method ' . $class->getName() . '::' . $method->getName() . '()'; + $this->parser->setTarget(Target::TARGET_METHOD); + $this->parser->setImports($this->getImports($class)); + $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class)); + + return $this->parser->parse($method->getDocComment(), $context); + } + + /** + * Gets a method annotation. + * + * @param ReflectionMethod $method + * @param string $annotationName The name of the annotation. + * @return mixed The Annotation or NULL, if the requested annotation does not exist. + */ + public function getMethodAnnotation(ReflectionMethod $method, $annotationName) + { + $annotations = $this->getMethodAnnotations($method); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * Returns the ignored annotations for the given class. + * + * @param ReflectionClass $class + * @return array + */ + private function getIgnoredAnnotationNames(ReflectionClass $class) + { + if (isset($this->ignoredAnnotationNames[$name = $class->getName()])) { + return $this->ignoredAnnotationNames[$name]; + } + $this->collectParsingMetadata($class); + + return $this->ignoredAnnotationNames[$name]; + } + + /** + * Retrieve imports + * + * @param \ReflectionClass $class + * @return array + */ + private function getImports(ReflectionClass $class) + { + if (isset($this->imports[$name = $class->getName()])) { + return $this->imports[$name]; + } + $this->collectParsingMetadata($class); + + return $this->imports[$name]; + } + + /** + * Collects parsing metadata for a given class + * + * @param ReflectionClass $class + */ + private function collectParsingMetadata(ReflectionClass $class) + { + $ignoredAnnotationNames = self::$globalIgnoredNames; + + $annotations = $this->preParser->parse($class->getDocComment(), 'class '.$class->name); + foreach ($annotations as $annotation) { + if ($annotation instanceof IgnoreAnnotation) { + foreach ($annotation->names AS $annot) { + $ignoredAnnotationNames[$annot] = true; + } + } + } + + $name = $class->getName(); + $this->imports[$name] = array_merge( + self::$globalImports, + $this->phpParser->parseClass($class), + array('__NAMESPACE__' => $class->getNamespaceName()) + ); + $this->ignoredAnnotationNames[$name] = $ignoredAnnotationNames; + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php new file mode 100644 index 0000000..6135f53 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationRegistry.php @@ -0,0 +1,139 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * AnnotationRegistry + */ +final class AnnotationRegistry +{ + /** + * A map of namespaces to use for autoloading purposes based on a PSR-0 convention. + * + * Contains the namespace as key and an array of directories as value. If the value is NULL + * the include path is used for checking for the corresponding file. + * + * This autoloading mechanism does not utilize the PHP autoloading but implements autoloading on its own. + * + * @var array + */ + static private $autoloadNamespaces = array(); + + /** + * A map of autoloader callables. + * + * @var array + */ + static private $loaders = array(); + + static public function reset() + { + self::$autoloadNamespaces = array(); + self::$loaders = array(); + } + + /** + * Register file + * + * @param string $file + */ + static public function registerFile($file) + { + require_once $file; + } + + /** + * Add a namespace with one or many directories to look for files or null for the include path. + * + * Loading of this namespaces will be done with a PSR-0 namespace loading algorithm. + * + * @param string $namespace + * @param string|array|null $dirs + */ + static public function registerAutoloadNamespace($namespace, $dirs = null) + { + self::$autoloadNamespaces[$namespace] = $dirs; + } + + /** + * Register multiple namespaces + * + * Loading of this namespaces will be done with a PSR-0 namespace loading algorithm. + * + * @param array $namespaces + */ + static public function registerAutoloadNamespaces(array $namespaces) + { + self::$autoloadNamespaces = array_merge(self::$autoloadNamespaces, $namespaces); + } + + /** + * Register an autoloading callable for annotations, much like spl_autoload_register(). + * + * NOTE: These class loaders HAVE to be silent when a class was not found! + * IMPORTANT: Loaders have to return true if they loaded a class that could contain the searched annotation class. + * + * @param callable $callable + * + * @throws \InvalidArgumentException + */ + static public function registerLoader($callable) + { + if (!is_callable($callable)) { + throw new \InvalidArgumentException("A callable is expected in AnnotationRegistry::registerLoader()."); + } + self::$loaders[] = $callable; + } + + /** + * Autoload an annotation class silently. + * + * @param string $class + * @return boolean + */ + static public function loadAnnotationClass($class) + { + foreach (self::$autoloadNamespaces AS $namespace => $dirs) { + if (strpos($class, $namespace) === 0) { + $file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php"; + if ($dirs === null) { + if ($path = stream_resolve_include_path($file)) { + require $path; + return true; + } + } else { + foreach((array)$dirs AS $dir) { + if (is_file($dir . DIRECTORY_SEPARATOR . $file)) { + require $dir . DIRECTORY_SEPARATOR . $file; + return true; + } + } + } + } + } + + foreach (self::$loaders AS $loader) { + if (call_user_func($loader, $class) === true) { + return true; + } + } + return false; + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php new file mode 100644 index 0000000..e377e3b --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/CachedReader.php @@ -0,0 +1,250 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +use Doctrine\Common\Cache\Cache; + +/** + * A cache aware annotation reader. + * + * @author Johannes M. Schmitt + * @author Benjamin Eberlei + */ +final class CachedReader implements Reader +{ + /** + * @var string + */ + private static $CACHE_SALT = '@[Annot]'; + + /** + * @var Reader + */ + private $delegate; + + /** + * @var Cache + */ + private $cache; + + /** + * @var boolean + */ + private $debug; + + /** + * @var array + */ + private $loadedAnnotations; + + /** + * Constructor + * + * @param Reader $reader + * @param Cache $cache + * @param bool $debug + */ + public function __construct(Reader $reader, Cache $cache, $debug = false) + { + $this->delegate = $reader; + $this->cache = $cache; + $this->debug = (Boolean) $debug; + } + + /** + * Get annotations for class + * + * @param \ReflectionClass $class + * @return array + */ + public function getClassAnnotations(\ReflectionClass $class) + { + $cacheKey = $class->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) { + $annots = $this->delegate->getClassAnnotations($class); + $this->saveToCache($cacheKey, $annots); + } + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * Get selected annotation for class + * + * @param \ReflectionClass $class + * @param string $annotationName + * @return null + */ + public function getClassAnnotation(\ReflectionClass $class, $annotationName) + { + foreach ($this->getClassAnnotations($class) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * Get annotations for property + * + * @param \ReflectionProperty $property + * @return array + */ + public function getPropertyAnnotations(\ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + $cacheKey = $class->getName().'$'.$property->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) { + $annots = $this->delegate->getPropertyAnnotations($property); + $this->saveToCache($cacheKey, $annots); + } + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * Get selected annotation for property + * + * @param \ReflectionProperty $property + * @param string $annotationName + * @return null + */ + public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName) + { + foreach ($this->getPropertyAnnotations($property) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * Get method annotations + * + * @param \ReflectionMethod $method + * @return array + */ + public function getMethodAnnotations(\ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + $cacheKey = $class->getName().'#'.$method->getName(); + + if (isset($this->loadedAnnotations[$cacheKey])) { + return $this->loadedAnnotations[$cacheKey]; + } + + if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) { + $annots = $this->delegate->getMethodAnnotations($method); + $this->saveToCache($cacheKey, $annots); + } + + return $this->loadedAnnotations[$cacheKey] = $annots; + } + + /** + * Get selected method annotation + * + * @param \ReflectionMethod $method + * @param string $annotationName + * @return null + */ + public function getMethodAnnotation(\ReflectionMethod $method, $annotationName) + { + foreach ($this->getMethodAnnotations($method) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * Clear loaded annotations + */ + public function clearLoadedAnnotations() + { + $this->loadedAnnotations = array(); + } + + /** + * Fetches a value from the cache. + * + * @param string $rawCacheKey The cache key. + * @param \ReflectionClass $class The related class. + * @return mixed|boolean The cached value or false when the value is not in cache. + */ + private function fetchFromCache($rawCacheKey, \ReflectionClass $class) + { + $cacheKey = $rawCacheKey . self::$CACHE_SALT; + if (($data = $this->cache->fetch($cacheKey)) !== false) { + if (!$this->debug || $this->isCacheFresh($cacheKey, $class)) { + return $data; + } + } + + return false; + } + + /** + * Saves a value to the cache + * + * @param string $rawCacheKey The cache key. + * @param mixed $value The value. + */ + private function saveToCache($rawCacheKey, $value) + { + $cacheKey = $rawCacheKey . self::$CACHE_SALT; + $this->cache->save($cacheKey, $value); + if ($this->debug) { + $this->cache->save('[C]'.$cacheKey, time()); + } + } + + /** + * Check if cache is fresh + * + * @param string $cacheKey + * @param \ReflectionClass $class + * @return bool + */ + private function isCacheFresh($cacheKey, \ReflectionClass $class) + { + if (false === $filename = $class->getFilename()) { + return true; + } + + return $this->cache->fetch('[C]'.$cacheKey) >= filemtime($filename); + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php new file mode 100644 index 0000000..ddc84d6 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocLexer.php @@ -0,0 +1,132 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +use Doctrine\Common\Lexer\AbstractLexer; + +/** + * Simple lexer for docblock annotations. + * + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Johannes M. Schmitt + */ +final class DocLexer extends AbstractLexer +{ + const T_NONE = 1; + const T_INTEGER = 2; + const T_STRING = 3; + const T_FLOAT = 4; + + // All tokens that are also identifiers should be >= 100 + const T_IDENTIFIER = 100; + const T_AT = 101; + const T_CLOSE_CURLY_BRACES = 102; + const T_CLOSE_PARENTHESIS = 103; + const T_COMMA = 104; + const T_EQUALS = 105; + const T_FALSE = 106; + const T_NAMESPACE_SEPARATOR = 107; + const T_OPEN_CURLY_BRACES = 108; + const T_OPEN_PARENTHESIS = 109; + const T_TRUE = 110; + const T_NULL = 111; + const T_COLON = 112; + + protected $noCase = array( + '@' => self::T_AT, + ',' => self::T_COMMA, + '(' => self::T_OPEN_PARENTHESIS, + ')' => self::T_CLOSE_PARENTHESIS, + '{' => self::T_OPEN_CURLY_BRACES, + '}' => self::T_CLOSE_CURLY_BRACES, + '=' => self::T_EQUALS, + ':' => self::T_COLON, + '\\' => self::T_NAMESPACE_SEPARATOR + ); + + protected $withCase = array( + 'true' => self::T_TRUE, + 'false' => self::T_FALSE, + 'null' => self::T_NULL + ); + + /** + * {@inheritdoc} + */ + protected function getCatchablePatterns() + { + return array( + '[a-z_\\\][a-z0-9_\:\\\]*[a-z]{1}', + '(?:[+-]?[0-9]+(?:[\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?', + '"(?:[^"]|"")*"', + ); + } + + /** + * {@inheritdoc} + */ + protected function getNonCatchablePatterns() + { + return array('\s+', '\*+', '(.)'); + } + + /** + * {@inheritdoc} + * + * @param string $value + * + * @return int + */ + protected function getType(&$value) + { + $type = self::T_NONE; + + if ($value[0] === '"') { + $value = str_replace('""', '"', substr($value, 1, strlen($value) - 2)); + + return self::T_STRING; + } + + if (isset($this->noCase[$value])) { + return $this->noCase[$value]; + } + + if ($value[0] === '_' || $value[0] === '\\' || ctype_alpha($value[0])) { + return self::T_IDENTIFIER; + } + + $lowerValue = strtolower($value); + + if (isset($this->withCase[$lowerValue])) { + return $this->withCase[$lowerValue]; + } + + // Checking numeric value + if (is_numeric($value)) { + return (strpos($value, '.') !== false || stripos($value, 'e') !== false) + ? self::T_FLOAT : self::T_INTEGER; + } + + return $type; + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php new file mode 100644 index 0000000..9b5daec --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/DocParser.php @@ -0,0 +1,1049 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +use Closure; +use ReflectionClass; +use Doctrine\Common\Annotations\Annotation\Enum; +use Doctrine\Common\Annotations\Annotation\Target; +use Doctrine\Common\Annotations\Annotation\Attribute; +use Doctrine\Common\Annotations\Annotation\Attributes; + +/** + * A parser for docblock annotations. + * + * It is strongly discouraged to change the default annotation parsing process. + * + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Johannes M. Schmitt + * @author Fabio B. Silva + */ +final class DocParser +{ + /** + * An array of all valid tokens for a class name. + * + * @var array + */ + private static $classIdentifiers = array(DocLexer::T_IDENTIFIER, DocLexer::T_TRUE, DocLexer::T_FALSE, DocLexer::T_NULL); + + /** + * The lexer. + * + * @var \Doctrine\Common\Annotations\DocLexer + */ + private $lexer; + + /** + * Current target context + * + * @var string + */ + private $target; + + /** + * Doc Parser used to collect annotation target + * + * @var \Doctrine\Common\Annotations\DocParser + */ + private static $metadataParser; + + /** + * Flag to control if the current annotation is nested or not. + * + * @var boolean + */ + private $isNestedAnnotation = false; + + /** + * Hashmap containing all use-statements that are to be used when parsing + * the given doc block. + * + * @var array + */ + private $imports = array(); + + /** + * This hashmap is used internally to cache results of class_exists() + * look-ups. + * + * @var array + */ + private $classExists = array(); + + /** + * Whether annotations that have not been imported should be ignored. + * + * @var boolean + */ + private $ignoreNotImportedAnnotations = false; + + /** + * An array of default namespaces if operating in simple mode. + * + * @var array + */ + private $namespaces = array(); + + /** + * A list with annotations that are not causing exceptions when not resolved to an annotation class. + * + * The names must be the raw names as used in the class, not the fully qualified + * class names. + * + * @var array + */ + private $ignoredAnnotationNames = array(); + + /** + * @var string + */ + private $context = ''; + + /** + * Hash-map for caching annotation metadata + * @var array + */ + private static $annotationMetadata = array( + 'Doctrine\Common\Annotations\Annotation\Target' => array( + 'is_annotation' => true, + 'has_constructor' => true, + 'properties' => array(), + 'targets_literal' => 'ANNOTATION_CLASS', + 'targets' => Target::TARGET_CLASS, + 'default_property' => 'value', + 'attribute_types' => array( + 'value' => array( + 'required' => false, + 'type' =>'array', + 'array_type'=>'string', + 'value' =>'array' + ) + ), + ), + 'Doctrine\Common\Annotations\Annotation\Attribute' => array( + 'is_annotation' => true, + 'has_constructor' => false, + 'targets_literal' => 'ANNOTATION_ANNOTATION', + 'targets' => Target::TARGET_ANNOTATION, + 'default_property' => 'name', + 'properties' => array( + 'name' => 'name', + 'type' => 'type', + 'required' => 'required' + ), + 'attribute_types' => array( + 'value' => array( + 'required' => true, + 'type' =>'string', + 'value' =>'string' + ), + 'type' => array( + 'required' =>true, + 'type' =>'string', + 'value' =>'string' + ), + 'required' => array( + 'required' =>false, + 'type' =>'boolean', + 'value' =>'boolean' + ) + ), + ), + 'Doctrine\Common\Annotations\Annotation\Attributes' => array( + 'is_annotation' => true, + 'has_constructor' => false, + 'targets_literal' => 'ANNOTATION_CLASS', + 'targets' => Target::TARGET_CLASS, + 'default_property' => 'value', + 'properties' => array( + 'value' => 'value' + ), + 'attribute_types' => array( + 'value' => array( + 'type' =>'array', + 'required' =>true, + 'array_type'=>'Doctrine\Common\Annotations\Annotation\Attribute', + 'value' =>'array' + ) + ), + ), + 'Doctrine\Common\Annotations\Annotation\Enum' => array( + 'is_annotation' => true, + 'has_constructor' => true, + 'targets_literal' => 'ANNOTATION_PROPERTY', + 'targets' => Target::TARGET_PROPERTY, + 'default_property' => 'value', + 'properties' => array( + 'value' => 'value' + ), + 'attribute_types' => array( + 'value' => array( + 'type' => 'array', + 'required' => true, + ), + 'literal' => array( + 'type' => 'array', + 'required' => false, + ), + ), + ), + ); + + /** + * Hash-map for handle types declaration + * + * @var array + */ + private static $typeMap = array( + 'float' => 'double', + 'bool' => 'boolean', + // allow uppercase Boolean in honor of George Boole + 'Boolean' => 'boolean', + 'int' => 'integer', + ); + + /** + * Constructs a new DocParser. + */ + public function __construct() + { + $this->lexer = new DocLexer; + } + + /** + * Sets the annotation names that are ignored during the parsing process. + * + * The names are supposed to be the raw names as used in the class, not the + * fully qualified class names. + * + * @param array $names + */ + public function setIgnoredAnnotationNames(array $names) + { + $this->ignoredAnnotationNames = $names; + } + + /** + * Sets ignore on not-imported annotations + * + * @param $bool + */ + public function setIgnoreNotImportedAnnotations($bool) + { + $this->ignoreNotImportedAnnotations = (Boolean) $bool; + } + + /** + * Sets the default namespaces. + * + * @param array $namespace + * + * @throws \RuntimeException + */ + public function addNamespace($namespace) + { + if ($this->imports) { + throw new \RuntimeException('You must either use addNamespace(), or setImports(), but not both.'); + } + $this->namespaces[] = $namespace; + } + + /** + * Sets the imports + * + * @param array $imports + * @throws \RuntimeException + */ + public function setImports(array $imports) + { + if ($this->namespaces) { + throw new \RuntimeException('You must either use addNamespace(), or setImports(), but not both.'); + } + $this->imports = $imports; + } + + /** + * Sets current target context as bitmask. + * + * @param integer $target + */ + public function setTarget($target) + { + $this->target = $target; + } + + /** + * Parses the given docblock string for annotations. + * + * @param string $input The docblock string to parse. + * @param string $context The parsing context. + * @return array Array of annotations. If no annotations are found, an empty array is returned. + */ + public function parse($input, $context = '') + { + if (false === $pos = strpos($input, '@')) { + return array(); + } + + // also parse whatever character is before the @ + if ($pos > 0) { + $pos -= 1; + } + + $this->context = $context; + $this->lexer->setInput(trim(substr($input, $pos), '* /')); + $this->lexer->moveNext(); + + return $this->Annotations(); + } + + /** + * Attempts to match the given token with the current lookahead token. + * If they match, updates the lookahead token; otherwise raises a syntax error. + * + * @param int $token type of Token. + * @return bool True if tokens match; false otherwise. + */ + private function match($token) + { + if ( ! $this->lexer->isNextToken($token) ) { + $this->syntaxError($this->lexer->getLiteral($token)); + } + + return $this->lexer->moveNext(); + } + + /** + * Attempts to match the current lookahead token with any of the given tokens. + * + * If any of them matches, this method updates the lookahead token; otherwise + * a syntax error is raised. + * + * @param array $tokens + * @return bool + */ + private function matchAny(array $tokens) + { + if ( ! $this->lexer->isNextTokenAny($tokens)) { + $this->syntaxError(implode(' or ', array_map(array($this->lexer, 'getLiteral'), $tokens))); + } + + return $this->lexer->moveNext(); + } + + /** + * Generates a new syntax error. + * + * @param string $expected Expected string. + * @param array $token Optional token. + * + * @throws AnnotationException + */ + private function syntaxError($expected, $token = null) + { + if ($token === null) { + $token = $this->lexer->lookahead; + } + + $message = "Expected {$expected}, got "; + + if ($this->lexer->lookahead === null) { + $message .= 'end of string'; + } else { + $message .= "'{$token['value']}' at position {$token['position']}"; + } + + if (strlen($this->context)) { + $message .= ' in ' . $this->context; + } + + $message .= '.'; + + throw AnnotationException::syntaxError($message); + } + + /** + * Attempt to check if a class exists or not. This never goes through the PHP autoloading mechanism + * but uses the {@link AnnotationRegistry} to load classes. + * + * @param string $fqcn + * @return boolean + */ + private function classExists($fqcn) + { + if (isset($this->classExists[$fqcn])) { + return $this->classExists[$fqcn]; + } + + // first check if the class already exists, maybe loaded through another AnnotationReader + if (class_exists($fqcn, false)) { + return $this->classExists[$fqcn] = true; + } + + // final check, does this class exist? + return $this->classExists[$fqcn] = AnnotationRegistry::loadAnnotationClass($fqcn); + } + + /** + * Collects parsing metadata for a given annotation class + * + * @param string $name The annotation name + */ + private function collectAnnotationMetadata($name) + { + if (self::$metadataParser == null){ + self::$metadataParser = new self(); + self::$metadataParser->setIgnoreNotImportedAnnotations(true); + self::$metadataParser->setIgnoredAnnotationNames($this->ignoredAnnotationNames); + self::$metadataParser->setImports(array( + 'enum' => 'Doctrine\Common\Annotations\Annotation\Enum', + 'target' => 'Doctrine\Common\Annotations\Annotation\Target', + 'attribute' => 'Doctrine\Common\Annotations\Annotation\Attribute', + 'attributes' => 'Doctrine\Common\Annotations\Annotation\Attributes' + )); + AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Enum.php'); + AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Target.php'); + AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Attribute.php'); + AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Attributes.php'); + } + + $class = new \ReflectionClass($name); + $docComment = $class->getDocComment(); + + // Sets default values for annotation metadata + $metadata = array( + 'default_property' => null, + 'has_constructor' => (null !== $constructor = $class->getConstructor()) && $constructor->getNumberOfParameters() > 0, + 'properties' => array(), + 'property_types' => array(), + 'attribute_types' => array(), + 'targets_literal' => null, + 'targets' => Target::TARGET_ALL, + 'is_annotation' => false !== strpos($docComment, '@Annotation'), + ); + + // verify that the class is really meant to be an annotation + if ($metadata['is_annotation']) { + + self::$metadataParser->setTarget(Target::TARGET_CLASS); + + foreach (self::$metadataParser->parse($docComment, 'class @' . $name) as $annotation) { + if ($annotation instanceof Target) { + $metadata['targets'] = $annotation->targets; + $metadata['targets_literal'] = $annotation->literal; + + } elseif ($annotation instanceof Attributes) { + foreach ($annotation->value as $attrib) { + // handle internal type declaration + $type = isset(self::$typeMap[$attrib->type]) ? self::$typeMap[$attrib->type] : $attrib->type; + + // handle the case if the property type is mixed + if ('mixed' !== $type) { + // Checks if the property has array + if (false !== $pos = strpos($type, '<')) { + $arrayType = substr($type, $pos+1, -1); + $type = 'array'; + + if (isset(self::$typeMap[$arrayType])) { + $arrayType = self::$typeMap[$arrayType]; + } + + $metadata['attribute_types'][$attrib->name]['array_type'] = $arrayType; + } + + $metadata['attribute_types'][$attrib->name]['type'] = $type; + $metadata['attribute_types'][$attrib->name]['value'] = $attrib->type; + $metadata['attribute_types'][$attrib->name]['required'] = $attrib->required; + } + } + } + } + + // if not has a constructor will inject values into public properties + if (false === $metadata['has_constructor']) { + // collect all public properties + foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { + $metadata['properties'][$property->name] = $property->name; + + if(false === ($propertyComment = $property->getDocComment())) { + continue; + } + + // checks if the property has @var annotation + if (false !== strpos($propertyComment, '@var') + && preg_match('/@var\s+([^\s]+)/',$propertyComment, $matches)) { + // literal type declaration + $value = $matches[1]; + + // handle internal type declaration + $type = isset(self::$typeMap[$value]) ? self::$typeMap[$value] : $value; + + // handle the case if the property type is mixed + if ('mixed' !== $type) { + // Checks if the property has @var array annotation + if (false !== $pos = strpos($type, '<')) { + $arrayType = substr($type, $pos+1, -1); + $type = 'array'; + + if (isset(self::$typeMap[$arrayType])) { + $arrayType = self::$typeMap[$arrayType]; + } + + $metadata['attribute_types'][$property->name]['array_type'] = $arrayType; + } + + $metadata['attribute_types'][$property->name]['type'] = $type; + $metadata['attribute_types'][$property->name]['value'] = $value; + $metadata['attribute_types'][$property->name]['required'] = false !== strpos($propertyComment, '@Required'); + } + } + + // checks if the property has @Enum + if (false !== strpos($propertyComment, '@Enum')){ + + $context = 'property ' . $class->name . "::\$" . $property->name; + self::$metadataParser->setTarget(Target::TARGET_PROPERTY); + + foreach (self::$metadataParser->parse($propertyComment, $context) as $annotation) { + if($annotation instanceof Enum) { + $metadata['enum'][$property->name]['value'] = $annotation->value; + $metadata['enum'][$property->name]['literal'] = ! empty($annotation->literal) ? $annotation->literal : $annotation->value; + } + } + } + } + + // choose the first property as default property + $metadata['default_property'] = reset($metadata['properties']); + } + } + + self::$annotationMetadata[$name] = $metadata; + } + + /** + * Annotations ::= Annotation {[ "*" ]* [Annotation]}* + * + * @return array + */ + private function Annotations() + { + $annotations = array(); + + while (null !== $this->lexer->lookahead) { + if (DocLexer::T_AT !== $this->lexer->lookahead['type']) { + $this->lexer->moveNext(); + continue; + } + + // make sure the @ is preceded by non-catchable pattern + if (null !== $this->lexer->token && $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value'])) { + $this->lexer->moveNext(); + continue; + } + + // make sure the @ is followed by either a namespace separator, or + // an identifier token + if ((null === $peek = $this->lexer->glimpse()) + || (DocLexer::T_NAMESPACE_SEPARATOR !== $peek['type'] && !in_array($peek['type'], self::$classIdentifiers, true)) + || $peek['position'] !== $this->lexer->lookahead['position'] + 1) { + $this->lexer->moveNext(); + continue; + } + + $this->isNestedAnnotation = false; + if (false !== $annot = $this->Annotation()) { + $annotations[] = $annot; + } + } + + return $annotations; + } + + /** + * Annotation ::= "@" AnnotationName ["(" [Values] ")"] + * AnnotationName ::= QualifiedName | SimpleName + * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName + * NameSpacePart ::= identifier | null | false | true + * SimpleName ::= identifier | null | false | true + * + * @throws AnnotationException + * @return mixed False if it is not a valid annotation. + */ + private function Annotation() + { + $this->match(DocLexer::T_AT); + + // check if we have an annotation + $name = $this->Identifier(); + + // only process names which are not fully qualified, yet + // fully qualified names must start with a \ + $originalName = $name; + if ('\\' !== $name[0]) { + $alias = (false === $pos = strpos($name, '\\'))? $name : substr($name, 0, $pos); + + $found = false; + if ($this->namespaces) { + foreach ($this->namespaces as $namespace) { + if ($this->classExists($namespace.'\\'.$name)) { + $name = $namespace.'\\'.$name; + $found = true; + break; + } + } + } elseif (isset($this->imports[$loweredAlias = strtolower($alias)])) { + if (false !== $pos) { + $name = $this->imports[$loweredAlias].substr($name, $pos); + } else { + $name = $this->imports[$loweredAlias]; + } + $found = true; + } elseif (isset($this->imports['__NAMESPACE__']) && $this->classExists($this->imports['__NAMESPACE__'].'\\'.$name)) { + $name = $this->imports['__NAMESPACE__'].'\\'.$name; + $found = true; + } elseif ($this->classExists($name)) { + $found = true; + } + + if (!$found) { + if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$name])) { + return false; + } + + throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s was never imported. Did you maybe forget to add a "use" statement for this annotation?', $name, $this->context)); + } + } + + if (!$this->classExists($name)) { + throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s does not exist, or could not be auto-loaded.', $name, $this->context)); + } + + // at this point, $name contains the fully qualified class name of the + // annotation, and it is also guaranteed that this class exists, and + // that it is loaded + + + // collects the metadata annotation only if there is not yet + if (!isset(self::$annotationMetadata[$name])) { + $this->collectAnnotationMetadata($name); + } + + // verify that the class is really meant to be an annotation and not just any ordinary class + if (self::$annotationMetadata[$name]['is_annotation'] === false) { + if (isset($this->ignoredAnnotationNames[$originalName])) { + return false; + } + + throw AnnotationException::semanticalError(sprintf('The class "%s" is not annotated with @Annotation. Are you sure this class can be used as annotation? If so, then you need to add @Annotation to the _class_ doc comment of "%s". If it is indeed no annotation, then you need to add @IgnoreAnnotation("%s") to the _class_ doc comment of %s.', $name, $name, $originalName, $this->context)); + } + + //if target is nested annotation + $target = $this->isNestedAnnotation ? Target::TARGET_ANNOTATION : $this->target; + + // Next will be nested + $this->isNestedAnnotation = true; + + //if annotation does not support current target + if (0 === (self::$annotationMetadata[$name]['targets'] & $target) && $target) { + throw AnnotationException::semanticalError( + sprintf('Annotation @%s is not allowed to be declared on %s. You may only use this annotation on these code elements: %s.', + $originalName, $this->context, self::$annotationMetadata[$name]['targets_literal']) + ); + } + + $values = array(); + if ($this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) { + $this->match(DocLexer::T_OPEN_PARENTHESIS); + + if ( ! $this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) { + $values = $this->Values(); + } + + $this->match(DocLexer::T_CLOSE_PARENTHESIS); + } + + if (isset(self::$annotationMetadata[$name]['enum'])) { + // checks all declared attributes + foreach (self::$annotationMetadata[$name]['enum'] as $property => $enum) { + // checks if the attribute is a valid enumerator + if (isset($values[$property]) && ! in_array($values[$property], $enum['value'])) { + throw AnnotationException::enumeratorError($property, $name, $this->context, $enum['literal'], $values[$property]); + } + } + } + + // checks all declared attributes + foreach (self::$annotationMetadata[$name]['attribute_types'] as $property => $type) { + if ($property === self::$annotationMetadata[$name]['default_property'] + && !isset($values[$property]) && isset($values['value'])) { + $property = 'value'; + } + + // handle a not given attribute or null value + if (!isset($values[$property])) { + if ($type['required']) { + throw AnnotationException::requiredError($property, $originalName, $this->context, 'a(n) '.$type['value']); + } + + continue; + } + + if ($type['type'] === 'array') { + // handle the case of a single value + if ( ! is_array($values[$property])) { + $values[$property] = array($values[$property]); + } + + // checks if the attribute has array type declaration, such as "array" + if (isset($type['array_type'])) { + foreach ($values[$property] as $item) { + if (gettype($item) !== $type['array_type'] && !$item instanceof $type['array_type']) { + throw AnnotationException::typeError($property, $originalName, $this->context, 'either a(n) '.$type['array_type'].', or an array of '.$type['array_type'].'s', $item); + } + } + } + } elseif (gettype($values[$property]) !== $type['type'] && !$values[$property] instanceof $type['type']) { + throw AnnotationException::typeError($property, $originalName, $this->context, 'a(n) '.$type['value'], $values[$property]); + } + } + + // check if the annotation expects values via the constructor, + // or directly injected into public properties + if (self::$annotationMetadata[$name]['has_constructor'] === true) { + return new $name($values); + } + + $instance = new $name(); + foreach ($values as $property => $value) { + if (!isset(self::$annotationMetadata[$name]['properties'][$property])) { + if ('value' !== $property) { + throw AnnotationException::creationError(sprintf('The annotation @%s declared on %s does not have a property named "%s". Available properties: %s', $originalName, $this->context, $property, implode(', ', self::$annotationMetadata[$name]['properties']))); + } + + // handle the case if the property has no annotations + if (!$property = self::$annotationMetadata[$name]['default_property']) { + throw AnnotationException::creationError(sprintf('The annotation @%s declared on %s does not accept any values, but got %s.', $originalName, $this->context, json_encode($values))); + } + } + + $instance->{$property} = $value; + } + + return $instance; + } + + /** + * Values ::= Array | Value {"," Value}* + * + * @return array + */ + private function Values() + { + $values = array(); + + // Handle the case of a single array as value, i.e. @Foo({....}) + if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) { + $values['value'] = $this->Value(); + return $values; + } + + $values[] = $this->Value(); + + while ($this->lexer->isNextToken(DocLexer::T_COMMA)) { + $this->match(DocLexer::T_COMMA); + $token = $this->lexer->lookahead; + $value = $this->Value(); + + if ( ! is_object($value) && ! is_array($value)) { + $this->syntaxError('Value', $token); + } + + $values[] = $value; + } + + foreach ($values as $k => $value) { + if (is_object($value) && $value instanceof \stdClass) { + $values[$value->name] = $value->value; + } else if ( ! isset($values['value'])){ + $values['value'] = $value; + } else { + if ( ! is_array($values['value'])) { + $values['value'] = array($values['value']); + } + + $values['value'][] = $value; + } + + unset($values[$k]); + } + + return $values; + } + + /** + * Constant ::= integer | string | float | boolean + * + * @throws AnnotationException + * @return mixed + */ + private function Constant() + { + $identifier = $this->Identifier(); + + if (!defined($identifier) && false !== strpos($identifier, '::') && '\\' !== $identifier[0]) { + + list($className, $const) = explode('::', $identifier); + $alias = (false === $pos = strpos($className, '\\'))? $className : substr($className, 0, $pos); + + $found = false; + switch (true) { + case !empty ($this->namespaces): + foreach ($this->namespaces as $ns) { + if (class_exists($ns.'\\'.$className) || interface_exists($ns.'\\'.$className)) { + $className = $ns.'\\'.$className; + $found = true; + break; + } + } + break; + + case isset($this->imports[$loweredAlias = strtolower($alias)]): + $found = true; + if (false !== $pos) { + $className = $this->imports[$loweredAlias].substr($className, $pos); + } else { + $className = $this->imports[$loweredAlias]; + } + break; + + default: + if(isset($this->imports['__NAMESPACE__'])) { + $ns = $this->imports['__NAMESPACE__']; + if (class_exists($ns.'\\'.$className) || interface_exists($ns.'\\'.$className)) { + $className = $ns.'\\'.$className; + $found = true; + } + } + break; + } + + if ($found) { + $identifier = $className . '::' . $const; + } + } + + if (!defined($identifier)) { + throw AnnotationException::semanticalErrorConstants($identifier, $this->context); + } + + return constant($identifier); + } + + /** + * Identifier ::= string + * + * @return string + */ + private function Identifier() + { + // check if we have an annotation + if ($this->lexer->isNextTokenAny(self::$classIdentifiers)) { + $this->lexer->moveNext(); + $className = $this->lexer->token['value']; + } else { + $this->syntaxError('namespace separator or identifier'); + } + + while ($this->lexer->lookahead['position'] === ($this->lexer->token['position'] + strlen($this->lexer->token['value'])) + && $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) { + + $this->match(DocLexer::T_NAMESPACE_SEPARATOR); + $this->matchAny(self::$classIdentifiers); + $className .= '\\' . $this->lexer->token['value']; + } + + return $className; + } + + /** + * Value ::= PlainValue | FieldAssignment + * + * @return mixed + */ + private function Value() + { + $peek = $this->lexer->glimpse(); + + if (DocLexer::T_EQUALS === $peek['type']) { + return $this->FieldAssignment(); + } + + return $this->PlainValue(); + } + + /** + * PlainValue ::= integer | string | float | boolean | Array | Annotation + * + * @return mixed + */ + private function PlainValue() + { + if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) { + return $this->Arrayx(); + } + + if ($this->lexer->isNextToken(DocLexer::T_AT)) { + return $this->Annotation(); + } + + if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { + return $this->Constant(); + } + + switch ($this->lexer->lookahead['type']) { + case DocLexer::T_STRING: + $this->match(DocLexer::T_STRING); + return $this->lexer->token['value']; + + case DocLexer::T_INTEGER: + $this->match(DocLexer::T_INTEGER); + return (int)$this->lexer->token['value']; + + case DocLexer::T_FLOAT: + $this->match(DocLexer::T_FLOAT); + return (float)$this->lexer->token['value']; + + case DocLexer::T_TRUE: + $this->match(DocLexer::T_TRUE); + return true; + + case DocLexer::T_FALSE: + $this->match(DocLexer::T_FALSE); + return false; + + case DocLexer::T_NULL: + $this->match(DocLexer::T_NULL); + return null; + + default: + $this->syntaxError('PlainValue'); + } + } + + /** + * FieldAssignment ::= FieldName "=" PlainValue + * FieldName ::= identifier + * + * @return array + */ + private function FieldAssignment() + { + $this->match(DocLexer::T_IDENTIFIER); + $fieldName = $this->lexer->token['value']; + + $this->match(DocLexer::T_EQUALS); + + $item = new \stdClass(); + $item->name = $fieldName; + $item->value = $this->PlainValue(); + + return $item; + } + + /** + * Array ::= "{" ArrayEntry {"," ArrayEntry}* [","] "}" + * + * @return array + */ + private function Arrayx() + { + $array = $values = array(); + + $this->match(DocLexer::T_OPEN_CURLY_BRACES); + + // If the array is empty, stop parsing and return. + if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) { + $this->match(DocLexer::T_CLOSE_CURLY_BRACES); + + return $array; + } + + $values[] = $this->ArrayEntry(); + + while ($this->lexer->isNextToken(DocLexer::T_COMMA)) { + $this->match(DocLexer::T_COMMA); + + // optional trailing comma + if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) { + break; + } + + $values[] = $this->ArrayEntry(); + } + + $this->match(DocLexer::T_CLOSE_CURLY_BRACES); + + foreach ($values as $value) { + list ($key, $val) = $value; + + if ($key !== null) { + $array[$key] = $val; + } else { + $array[] = $val; + } + } + + return $array; + } + + /** + * ArrayEntry ::= Value | KeyValuePair + * KeyValuePair ::= Key ("=" | ":") PlainValue | Constant + * Key ::= string | integer | Constant + * + * @return array + */ + private function ArrayEntry() + { + $peek = $this->lexer->glimpse(); + + if (DocLexer::T_EQUALS === $peek['type'] + || DocLexer::T_COLON === $peek['type']) { + + if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) { + $key = $this->Constant(); + } else { + $this->matchAny(array(DocLexer::T_INTEGER, DocLexer::T_STRING)); + $key = $this->lexer->token['value']; + } + + $this->matchAny(array(DocLexer::T_EQUALS, DocLexer::T_COLON)); + + return array($key, $this->PlainValue()); + } + + return array(null, $this->Value()); + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php new file mode 100644 index 0000000..5e937a8 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/FileCacheReader.php @@ -0,0 +1,269 @@ +. + */ + +namespace Doctrine\Common\Annotations; + + +/** + * File cache reader for annotations. + * + * @author Johannes M. Schmitt + * @author Benjamin Eberlei + */ +class FileCacheReader implements Reader +{ + /** + * @var Reader + */ + private $reader; + + /** + * @var string + */ + private $dir; + + /** + * @var bool + */ + private $debug; + + /** + * @var array + */ + private $loadedAnnotations = array(); + + private $classNameHashes = array(); + + /** + * Constructor + * + * @param Reader $reader + * @param string $cacheDir + * @param bool $debug + * + * @throws \InvalidArgumentException + */ + public function __construct(Reader $reader, $cacheDir, $debug = false) + { + $this->reader = $reader; + if (!is_dir($cacheDir) && !@mkdir($cacheDir, 0777, true)) { + throw new \InvalidArgumentException(sprintf('The directory "%s" does not exist and could not be created.', $cacheDir)); + } + if (!is_writable($cacheDir)) { + throw new \InvalidArgumentException(sprintf('The directory "%s" is not writable. Both, the webserver and the console user need access. You can manage access rights for multiple users with "chmod +a". If your system does not support this, check out the acl package.', $cacheDir)); + } + + $this->dir = rtrim($cacheDir, '\\/'); + $this->debug = $debug; + } + + /** + * Retrieve annotations for class + * + * @param \ReflectionClass $class + * @return array + */ + public function getClassAnnotations(\ReflectionClass $class) + { + if ( ! isset($this->classNameHashes[$class->name])) { + $this->classNameHashes[$class->name] = sha1($class->name); + } + $key = $this->classNameHashes[$class->name]; + + if (isset($this->loadedAnnotations[$key])) { + return $this->loadedAnnotations[$key]; + } + + $path = $this->dir.'/'.strtr($key, '\\', '-').'.cache.php'; + if (!is_file($path)) { + $annot = $this->reader->getClassAnnotations($class); + $this->saveCacheFile($path, $annot); + return $this->loadedAnnotations[$key] = $annot; + } + + if ($this->debug + && (false !== $filename = $class->getFilename()) + && filemtime($path) < filemtime($filename)) { + @unlink($path); + + $annot = $this->reader->getClassAnnotations($class); + $this->saveCacheFile($path, $annot); + return $this->loadedAnnotations[$key] = $annot; + } + + return $this->loadedAnnotations[$key] = include $path; + } + + /** + * Get annotations for property + * + * @param \ReflectionProperty $property + * @return array + */ + public function getPropertyAnnotations(\ReflectionProperty $property) + { + $class = $property->getDeclaringClass(); + if ( ! isset($this->classNameHashes[$class->name])) { + $this->classNameHashes[$class->name] = sha1($class->name); + } + $key = $this->classNameHashes[$class->name].'$'.$property->getName(); + + if (isset($this->loadedAnnotations[$key])) { + return $this->loadedAnnotations[$key]; + } + + $path = $this->dir.'/'.strtr($key, '\\', '-').'.cache.php'; + if (!is_file($path)) { + $annot = $this->reader->getPropertyAnnotations($property); + $this->saveCacheFile($path, $annot); + return $this->loadedAnnotations[$key] = $annot; + } + + if ($this->debug + && (false !== $filename = $class->getFilename()) + && filemtime($path) < filemtime($filename)) { + @unlink($path); + + $annot = $this->reader->getPropertyAnnotations($property); + $this->saveCacheFile($path, $annot); + return $this->loadedAnnotations[$key] = $annot; + } + + return $this->loadedAnnotations[$key] = include $path; + } + + /** + * Retrieve annotations for method + * + * @param \ReflectionMethod $method + * @return array + */ + public function getMethodAnnotations(\ReflectionMethod $method) + { + $class = $method->getDeclaringClass(); + if ( ! isset($this->classNameHashes[$class->name])) { + $this->classNameHashes[$class->name] = sha1($class->name); + } + $key = $this->classNameHashes[$class->name].'#'.$method->getName(); + + if (isset($this->loadedAnnotations[$key])) { + return $this->loadedAnnotations[$key]; + } + + $path = $this->dir.'/'.strtr($key, '\\', '-').'.cache.php'; + if (!is_file($path)) { + $annot = $this->reader->getMethodAnnotations($method); + $this->saveCacheFile($path, $annot); + return $this->loadedAnnotations[$key] = $annot; + } + + if ($this->debug + && (false !== $filename = $class->getFilename()) + && filemtime($path) < filemtime($filename)) { + @unlink($path); + + $annot = $this->reader->getMethodAnnotations($method); + $this->saveCacheFile($path, $annot); + return $this->loadedAnnotations[$key] = $annot; + } + + return $this->loadedAnnotations[$key] = include $path; + } + + /** + * Save cache file + * + * @param string $path + * @param mixed $data + */ + private function saveCacheFile($path, $data) + { + file_put_contents($path, 'getClassAnnotations($class); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * Gets a method annotation. + * + * @param \ReflectionMethod $method + * @param string $annotationName The name of the annotation. + * @return mixed The Annotation or NULL, if the requested annotation does not exist. + */ + public function getMethodAnnotation(\ReflectionMethod $method, $annotationName) + { + $annotations = $this->getMethodAnnotations($method); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * Gets a property annotation. + * + * @param \ReflectionProperty $property + * @param string $annotationName The name of the annotation. + * @return mixed The Annotation or NULL, if the requested annotation does not exist. + */ + public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName) + { + $annotations = $this->getPropertyAnnotations($property); + + foreach ($annotations as $annotation) { + if ($annotation instanceof $annotationName) { + return $annotation; + } + } + + return null; + } + + /** + * Clear stores annotations + */ + public function clearLoadedAnnotations() + { + $this->loadedAnnotations = array(); + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php new file mode 100644 index 0000000..2dfdd4d --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/IndexedReader.php @@ -0,0 +1,141 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +use Doctrine\Common\Annotations\Reader; + +/** + * Allows the reader to be used in-place of Doctrine's reader. + * + * @author Johannes M. Schmitt + */ +class IndexedReader implements Reader +{ + /** + * @var Reader + */ + private $delegate; + + /** + * Constructor + * + * @param Reader $reader + */ + public function __construct(Reader $reader) + { + $this->delegate = $reader; + } + + /** + * Get Annotations for class + * + * @param \ReflectionClass $class + * @return array + */ + public function getClassAnnotations(\ReflectionClass $class) + { + $annotations = array(); + foreach ($this->delegate->getClassAnnotations($class) as $annot) { + $annotations[get_class($annot)] = $annot; + } + + return $annotations; + } + + /** + * Get selected annotation for class + * + * @param \ReflectionClass $class + * @param string $annotation + * @return mixed + */ + public function getClassAnnotation(\ReflectionClass $class, $annotation) + { + return $this->delegate->getClassAnnotation($class, $annotation); + } + + /** + * Get Annotations for method + * + * @param \ReflectionMethod $method + * @return array + */ + public function getMethodAnnotations(\ReflectionMethod $method) + { + $annotations = array(); + foreach ($this->delegate->getMethodAnnotations($method) as $annot) { + $annotations[get_class($annot)] = $annot; + } + + return $annotations; + } + + /** + * Get selected annotation for method + * + * @param \ReflectionMethod $method + * @param string $annotation + * @return mixed + */ + public function getMethodAnnotation(\ReflectionMethod $method, $annotation) + { + return $this->delegate->getMethodAnnotation($method, $annotation); + } + + /** + * Get annotations for property + * + * @param \ReflectionProperty $property + * @return array + */ + public function getPropertyAnnotations(\ReflectionProperty $property) + { + $annotations = array(); + foreach ($this->delegate->getPropertyAnnotations($property) as $annot) { + $annotations[get_class($annot)] = $annot; + } + + return $annotations; + } + + /** + * Get selected annotation for property + * + * @param \ReflectionProperty $property + * @param string $annotation + * @return mixed + */ + public function getPropertyAnnotation(\ReflectionProperty $property, $annotation) + { + return $this->delegate->getPropertyAnnotation($property, $annotation); + } + + /** + * Proxy all methods to the delegate. + * + * @param string $method + * @param array $args + * @return mixed + */ + public function __call($method, $args) + { + return call_user_func_array(array($this->delegate, $method), $args); + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php new file mode 100644 index 0000000..9d61020 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/PhpParser.php @@ -0,0 +1,89 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +use SplFileObject; + +/** + * Parses a file for namespaces/use/class declarations. + * + * @author Fabien Potencier + * @author Christian Kaps + */ +final class PhpParser +{ + /** + * Parses a class. + * + * @param \ReflectionClass $class A ReflectionClass object. + * @return array A list with use statements in the form (Alias => FQN). + */ + public function parseClass(\ReflectionClass $class) + { + if (method_exists($class, 'getUseStatements')) { + return $class->getUseStatements(); + } + + if (false === $filename = $class->getFilename()) { + return array(); + } + + $content = $this->getFileContent($filename, $class->getStartLine()); + + if (null === $content) { + return array(); + } + + $namespace = preg_quote($class->getNamespaceName()); + $content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content); + $tokenizer = new TokenParser('parseUseStatements($class->getNamespaceName()); + + return $statements; + } + + /** + * Get the content of the file right up to the given line number. + * + * @param string $filename The name of the file to load. + * @param int $lineNumber The number of lines to read from file. + * @return string The content of the file. + */ + private function getFileContent($filename, $lineNumber) + { + if ( ! is_file($filename)) { + return null; + } + + $content = ''; + $lineCnt = 0; + $file = new SplFileObject($filename); + while (!$file->eof()) { + if ($lineCnt++ == $lineNumber) { + break; + } + + $content .= $file->fgets(); + } + + return $content; + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php new file mode 100644 index 0000000..6a01cb4 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/Reader.php @@ -0,0 +1,67 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * Interface for annotation readers. + * + * @author Johannes M. Schmitt + */ +interface Reader +{ + /** + * @param \ReflectionClass $class + * @return mixed + */ + function getClassAnnotations(\ReflectionClass $class); + + /** + * @param \ReflectionClass $class + * @param string $annotationName + * @return mixed + */ + function getClassAnnotation(\ReflectionClass $class, $annotationName); + + /** + * @param \ReflectionMethod $method + * @return mixed + */ + function getMethodAnnotations(\ReflectionMethod $method); + + /** + * @param \ReflectionMethod $method + * @param string $annotationName + * @return mixed + */ + function getMethodAnnotation(\ReflectionMethod $method, $annotationName); + + /** + * @param \ReflectionProperty $property + * @return mixed + */ + function getPropertyAnnotations(\ReflectionProperty $property); + + /** + * @param \ReflectionProperty $property + * @param string $annotationName + * @return mixed + */ + function getPropertyAnnotation(\ReflectionProperty $property, $annotationName); +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php new file mode 100644 index 0000000..4210d90 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/SimpleAnnotationReader.php @@ -0,0 +1,157 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +use Doctrine\Common\Annotations\Annotation\Target; + +/** + * Simple Annotation Reader. + * + * This annotation reader is intended to be used in projects where you have + * full-control over all annotations that are available. + * + * @since 2.2 + * @author Johannes M. Schmitt + * @author Fabio B. Silva + */ +class SimpleAnnotationReader implements Reader +{ + /** + * @var DocParser + */ + private $parser; + + /** + * Constructor. + * + * Initializes a new SimpleAnnotationReader. + */ + public function __construct() + { + $this->parser = new DocParser(); + $this->parser->setIgnoreNotImportedAnnotations(true); + } + + /** + * Adds a namespace in which we will look for annotations. + * + * @param string $namespace + */ + public function addNamespace($namespace) + { + $this->parser->addNamespace($namespace); + } + + /** + * Gets the annotations applied to a class. + * + * @param \ReflectionClass $class The ReflectionClass of the class from which + * the class annotations should be read. + * + * @return array An array of Annotations. + */ + public function getClassAnnotations(\ReflectionClass $class) + { + return $this->parser->parse($class->getDocComment(), 'class '.$class->getName()); + } + + /** + * Gets the annotations applied to a method. + * + * @param \ReflectionMethod $method The ReflectionMethod of the method from which + * the annotations should be read. + * + * @return array An array of Annotations. + */ + public function getMethodAnnotations(\ReflectionMethod $method) + { + return $this->parser->parse($method->getDocComment(), 'method '.$method->getDeclaringClass()->name.'::'.$method->getName().'()'); + } + + /** + * Gets the annotations applied to a property. + * + * @param \ReflectionProperty $property The ReflectionProperty of the property + * from which the annotations should be read. + * + * @return array An array of Annotations. + */ + public function getPropertyAnnotations(\ReflectionProperty $property) + { + return $this->parser->parse($property->getDocComment(), 'property '.$property->getDeclaringClass()->name.'::$'.$property->getName()); + } + + /** + * Gets a class annotation. + * + * @param \ReflectionClass $class The ReflectionClass of the class from which + * the class annotations should be read. + * @param string $annotationName The name of the annotation. + * + * @return mixed The Annotation or NULL, if the requested annotation does not exist. + */ + public function getClassAnnotation(\ReflectionClass $class, $annotationName) + { + foreach ($this->getClassAnnotations($class) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * Gets a method annotation. + * + * @param \ReflectionMethod $method + * @param string $annotationName The name of the annotation. + * + * @return mixed The Annotation or NULL, if the requested annotation does not exist. + */ + public function getMethodAnnotation(\ReflectionMethod $method, $annotationName) + { + foreach ($this->getMethodAnnotations($method) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } + + /** + * Gets a property annotation. + * + * @param \ReflectionProperty $property + * @param string $annotationName The name of the annotation. + * @return mixed The Annotation or NULL, if the requested annotation does not exist. + */ + public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName) + { + foreach ($this->getPropertyAnnotations($property) as $annot) { + if ($annot instanceof $annotationName) { + return $annot; + } + } + + return null; + } +} diff --git a/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php new file mode 100644 index 0000000..a1ef115 --- /dev/null +++ b/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/TokenParser.php @@ -0,0 +1,175 @@ +. + */ + +namespace Doctrine\Common\Annotations; + +/** + * Parses a file for namespaces/use/class declarations. + * + * @author Fabien Potencier + * @author Christian Kaps + */ +class TokenParser +{ + /** + * The token list. + * + * @var array + */ + private $tokens; + + /** + * The number of tokens. + * + * @var int + */ + private $numTokens = 0; + + /** + * The current array pointer. + * + * @var int + */ + private $pointer = 0; + + public function __construct($contents) + { + $this->tokens = token_get_all($contents); + $this->numTokens = count($this->tokens); + $this->pointer = 0; + } + + /** + * Gets the next non whitespace and non comment token. + * + * @param $docCommentIsComment + * If TRUE then a doc comment is considered a comment and skipped. + * If FALSE then only whitespace and normal comments are skipped. + * + * @return array The token if exists, null otherwise. + */ + public function next($docCommentIsComment = TRUE) + { + for ($i = $this->pointer; $i < $this->numTokens; $i++) { + $this->pointer++; + if ($this->tokens[$i][0] === T_WHITESPACE || + $this->tokens[$i][0] === T_COMMENT || + ($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT)) { + + continue; + } + + return $this->tokens[$i]; + } + + return null; + } + + /** + * Parse a single use statement. + * + * @return array A list with all found class names for a use statement. + */ + public function parseUseStatement() + { + $class = ''; + $alias = ''; + $statements = array(); + $explicitAlias = false; + while (($token = $this->next())) { + $isNameToken = $token[0] === T_STRING || $token[0] === T_NS_SEPARATOR; + if (!$explicitAlias && $isNameToken) { + $class .= $token[1]; + $alias = $token[1]; + } else if ($explicitAlias && $isNameToken) { + $alias .= $token[1]; + } else if ($token[0] === T_AS) { + $explicitAlias = true; + $alias = ''; + } else if ($token === ',') { + $statements[strtolower($alias)] = $class; + $class = ''; + $alias = ''; + $explicitAlias = false; + } else if ($token === ';') { + $statements[strtolower($alias)] = $class; + break; + } else { + break; + } + } + + return $statements; + } + + /** + * Get all use statements. + * + * @param string $namespaceName The namespace name of the reflected class. + * @return array A list with all found use statements. + */ + public function parseUseStatements($namespaceName) + { + $statements = array(); + while (($token = $this->next())) { + if ($token[0] === T_USE) { + $statements = array_merge($statements, $this->parseUseStatement()); + continue; + } + if ($token[0] !== T_NAMESPACE || $this->parseNamespace() != $namespaceName) { + continue; + } + + // Get fresh array for new namespace. This is to prevent the parser to collect the use statements + // for a previous namespace with the same name. This is the case if a namespace is defined twice + // or if a namespace with the same name is commented out. + $statements = array(); + } + + return $statements; + } + + /** + * Get the namespace. + * + * @return string The found namespace. + */ + public function parseNamespace() + { + $name = ''; + while (($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR)) { + $name .= $token[1]; + } + + return $name; + } + + /** + * Get the class name. + * + * @return string The foundclass name. + */ + public function parseClass() + { + // Namespaces and class names are tokenized the same: T_STRINGs + // separated by T_NS_SEPARATOR so we can use one function to provide + // both. + return $this->parseNamespace(); + } +} diff --git a/vendor/doctrine/annotations/phpunit.xml.dist b/vendor/doctrine/annotations/phpunit.xml.dist new file mode 100644 index 0000000..6ab0c8c --- /dev/null +++ b/vendor/doctrine/annotations/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + ./tests/Doctrine/ + + + + + + ./lib/Doctrine/ + + + + + + performance + + + diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/AbstractReaderTest.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/AbstractReaderTest.php new file mode 100644 index 0000000..ea52b02 --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/AbstractReaderTest.php @@ -0,0 +1,571 @@ +getReflectionClass(); + $reader = $this->getReader(); + + $this->assertEquals(1, count($reader->getClassAnnotations($class))); + $this->assertInstanceOf($annotName = 'Doctrine\Tests\Common\Annotations\DummyAnnotation', $annot = $reader->getClassAnnotation($class, $annotName)); + $this->assertEquals("hello", $annot->dummyValue); + + $field1Prop = $class->getProperty('field1'); + $propAnnots = $reader->getPropertyAnnotations($field1Prop); + $this->assertEquals(1, count($propAnnots)); + $this->assertInstanceOf($annotName, $annot = $reader->getPropertyAnnotation($field1Prop, $annotName)); + $this->assertEquals("fieldHello", $annot->dummyValue); + + $getField1Method = $class->getMethod('getField1'); + $methodAnnots = $reader->getMethodAnnotations($getField1Method); + $this->assertEquals(1, count($methodAnnots)); + $this->assertInstanceOf($annotName, $annot = $reader->getMethodAnnotation($getField1Method, $annotName)); + $this->assertEquals(array(1, 2, "three"), $annot->value); + + $field2Prop = $class->getProperty('field2'); + $propAnnots = $reader->getPropertyAnnotations($field2Prop); + $this->assertEquals(1, count($propAnnots)); + $this->assertInstanceOf($annotName = 'Doctrine\Tests\Common\Annotations\DummyJoinTable', $joinTableAnnot = $reader->getPropertyAnnotation($field2Prop, $annotName)); + $this->assertEquals(1, count($joinTableAnnot->joinColumns)); + $this->assertEquals(1, count($joinTableAnnot->inverseJoinColumns)); + $this->assertTrue($joinTableAnnot->joinColumns[0] instanceof DummyJoinColumn); + $this->assertTrue($joinTableAnnot->inverseJoinColumns[0] instanceof DummyJoinColumn); + $this->assertEquals('col1', $joinTableAnnot->joinColumns[0]->name); + $this->assertEquals('col2', $joinTableAnnot->joinColumns[0]->referencedColumnName); + $this->assertEquals('col3', $joinTableAnnot->inverseJoinColumns[0]->name); + $this->assertEquals('col4', $joinTableAnnot->inverseJoinColumns[0]->referencedColumnName); + + $dummyAnnot = $reader->getMethodAnnotation($class->getMethod('getField1'), 'Doctrine\Tests\Common\Annotations\DummyAnnotation'); + $this->assertEquals('', $dummyAnnot->dummyValue); + $this->assertEquals(array(1, 2, 'three'), $dummyAnnot->value); + + $dummyAnnot = $reader->getPropertyAnnotation($class->getProperty('field1'), 'Doctrine\Tests\Common\Annotations\DummyAnnotation'); + $this->assertEquals('fieldHello', $dummyAnnot->dummyValue); + + $classAnnot = $reader->getClassAnnotation($class, 'Doctrine\Tests\Common\Annotations\DummyAnnotation'); + $this->assertEquals('hello', $classAnnot->dummyValue); + } + + public function testAnnotationsWithValidTargets() + { + $reader = $this->getReader(); + $class = new ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithValidAnnotationTarget'); + + $this->assertEquals(1,count($reader->getClassAnnotations($class))); + $this->assertEquals(1,count($reader->getPropertyAnnotations($class->getProperty('foo')))); + $this->assertEquals(1,count($reader->getMethodAnnotations($class->getMethod('someFunction')))); + $this->assertEquals(1,count($reader->getPropertyAnnotations($class->getProperty('nested')))); + } + + public function testAnnotationsWithVarType() + { + $reader = $this->getReader(); + $class = new ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithAnnotationWithVarType'); + + $this->assertEquals(1,count($fooAnnot = $reader->getPropertyAnnotations($class->getProperty('foo')))); + $this->assertEquals(1,count($barAnnot = $reader->getMethodAnnotations($class->getMethod('bar')))); + + $this->assertInternalType('string', $fooAnnot[0]->string); + $this->assertInstanceOf('Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll', $barAnnot[0]->annotation); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage [Semantical Error] Annotation @AnnotationTargetPropertyMethod is not allowed to be declared on class Doctrine\Tests\Common\Annotations\Fixtures\ClassWithInvalidAnnotationTargetAtClass. You may only use this annotation on these code elements: METHOD, PROPERTY + */ + public function testClassWithInvalidAnnotationTargetAtClassDocBlock() + { + $reader = $this->getReader(); + $reader->getClassAnnotations(new \ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithInvalidAnnotationTargetAtClass')); + } + + public function testClassWithWithInclude() + { + $reader = $this->getReader(); + $annots = $reader->getClassAnnotations(new \ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithRequire')); + $this->assertCount(1, $annots); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage [Semantical Error] Annotation @AnnotationTargetClass is not allowed to be declared on property Doctrine\Tests\Common\Annotations\Fixtures\ClassWithInvalidAnnotationTargetAtProperty::$foo. You may only use this annotation on these code elements: CLASS + */ + public function testClassWithInvalidAnnotationTargetAtPropertyDocBlock() + { + $reader = $this->getReader(); + $reader->getPropertyAnnotations(new \ReflectionProperty('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithInvalidAnnotationTargetAtProperty', 'foo')); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage [Semantical Error] Annotation @AnnotationTargetAnnotation is not allowed to be declared on property Doctrine\Tests\Common\Annotations\Fixtures\ClassWithInvalidAnnotationTargetAtProperty::$bar. You may only use this annotation on these code elements: ANNOTATION + */ + public function testClassWithInvalidNestedAnnotationTargetAtPropertyDocBlock() + { + $reader = $this->getReader(); + $reader->getPropertyAnnotations(new \ReflectionProperty('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithInvalidAnnotationTargetAtProperty', 'bar')); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage [Semantical Error] Annotation @AnnotationTargetClass is not allowed to be declared on method Doctrine\Tests\Common\Annotations\Fixtures\ClassWithInvalidAnnotationTargetAtMethod::functionName(). You may only use this annotation on these code elements: CLASS + */ + public function testClassWithInvalidAnnotationTargetAtMethodDocBlock() + { + $reader = $this->getReader(); + $reader->getMethodAnnotations(new \ReflectionMethod('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithInvalidAnnotationTargetAtMethod', 'functionName')); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage Expected namespace separator or identifier, got ')' at position 24 in class @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithTargetSyntaxError. + */ + public function testClassWithAnnotationWithTargetSyntaxErrorAtClassDocBlock() + { + $reader = $this->getReader(); + $reader->getClassAnnotations(new \ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithAnnotationWithTargetSyntaxError')); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage Expected namespace separator or identifier, got ')' at position 24 in class @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithTargetSyntaxError. + */ + public function testClassWithAnnotationWithTargetSyntaxErrorAtPropertyDocBlock() + { + $reader = $this->getReader(); + $reader->getPropertyAnnotations(new \ReflectionProperty('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithAnnotationWithTargetSyntaxError','foo')); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage Expected namespace separator or identifier, got ')' at position 24 in class @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithTargetSyntaxError. + */ + public function testClassWithAnnotationWithTargetSyntaxErrorAtMethodDocBlock() + { + $reader = $this->getReader(); + $reader->getMethodAnnotations(new \ReflectionMethod('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithAnnotationWithTargetSyntaxError','bar')); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage [Type Error] Attribute "string" of @AnnotationWithVarType declared on property Doctrine\Tests\Common\Annotations\Fixtures\ClassWithAnnotationWithVarType::$invalidProperty expects a(n) string, but got integer. + */ + public function testClassWithPropertyInvalidVarTypeError() + { + $reader = $this->getReader(); + $class = new ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithAnnotationWithVarType'); + + $reader->getPropertyAnnotations($class->getProperty('invalidProperty')); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage [Type Error] Attribute "annotation" of @AnnotationWithVarType declared on method Doctrine\Tests\Common\Annotations\Fixtures\ClassWithAnnotationWithVarType::invalidMethod() expects a(n) Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll, but got an instance of Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAnnotation. + */ + public function testClassWithMethodInvalidVarTypeError() + { + $reader = $this->getReader(); + $class = new ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithAnnotationWithVarType'); + + $reader->getMethodAnnotations($class->getMethod('invalidMethod')); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage Expected namespace separator or identifier, got ')' at position 18 in class Doctrine\Tests\Common\Annotations\DummyClassSyntaxError. + */ + public function testClassSyntaxErrorContext() + { + $reader = $this->getReader(); + $reader->getClassAnnotations(new \ReflectionClass('Doctrine\Tests\Common\Annotations\DummyClassSyntaxError')); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage Expected namespace separator or identifier, got ')' at position 18 in method Doctrine\Tests\Common\Annotations\DummyClassMethodSyntaxError::foo(). + */ + public function testMethodSyntaxErrorContext() + { + $reader = $this->getReader(); + $reader->getMethodAnnotations(new \ReflectionMethod('Doctrine\Tests\Common\Annotations\DummyClassMethodSyntaxError', 'foo')); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage Expected namespace separator or identifier, got ')' at position 18 in property Doctrine\Tests\Common\Annotations\DummyClassPropertySyntaxError::$foo. + */ + public function testPropertySyntaxErrorContext() + { + $reader = $this->getReader(); + $reader->getPropertyAnnotations(new \ReflectionProperty('Doctrine\Tests\Common\Annotations\DummyClassPropertySyntaxError', 'foo')); + } + + /** + * @group regression + */ + public function testMultipleAnnotationsOnSameLine() + { + $reader = $this->getReader(); + $annots = $reader->getPropertyAnnotations(new \ReflectionProperty('Doctrine\Tests\Common\Annotations\DummyClass2', 'id')); + $this->assertEquals(3, count($annots)); + } + + public function testNonAnnotationProblem() + { + $reader = $this->getReader(); + + $this->assertNotNull($annot = $reader->getPropertyAnnotation(new \ReflectionProperty('Doctrine\Tests\Common\Annotations\DummyClassNonAnnotationProblem', 'foo'), $name = 'Doctrine\Tests\Common\Annotations\DummyAnnotation')); + $this->assertInstanceOf($name, $annot); + } + + public function testImportWithConcreteAnnotation() + { + $reader = $this->getReader(); + $property = new \ReflectionProperty('Doctrine\Tests\Common\Annotations\TestImportWithConcreteAnnotation', 'field'); + $annotations = $reader->getPropertyAnnotations($property); + $this->assertEquals(1, count($annotations)); + $this->assertNotNull($reader->getPropertyAnnotation($property, 'Doctrine\Tests\Common\Annotations\DummyAnnotation')); + } + + public function testImportWithInheritance() + { + $reader = $this->getReader(); + + $class = new TestParentClass(); + $ref = new \ReflectionClass($class); + + $childAnnotations = $reader->getPropertyAnnotations($ref->getProperty('child')); + $this->assertEquals(1, count($childAnnotations)); + $this->assertInstanceOf('Doctrine\Tests\Common\Annotations\Foo\Name', reset($childAnnotations)); + + $parentAnnotations = $reader->getPropertyAnnotations($ref->getProperty('parent')); + $this->assertEquals(1, count($parentAnnotations)); + $this->assertInstanceOf('Doctrine\Tests\Common\Annotations\Bar\Name', reset($parentAnnotations)); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage The annotation "@NameFoo" in property Doctrine\Tests\Common\Annotations\TestAnnotationNotImportedClass::$field was never imported. + */ + public function testImportDetectsNotImportedAnnotation() + { + $reader = $this->getReader(); + $reader->getPropertyAnnotations(new \ReflectionProperty('Doctrine\Tests\Common\Annotations\TestAnnotationNotImportedClass', 'field')); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage The annotation "@Foo\Bar\Name" in property Doctrine\Tests\Common\Annotations\TestNonExistentAnnotationClass::$field was never imported. + */ + public function testImportDetectsNonExistentAnnotation() + { + $reader = $this->getReader(); + $reader->getPropertyAnnotations(new \ReflectionProperty('Doctrine\Tests\Common\Annotations\TestNonExistentAnnotationClass', 'field')); + } + + public function testTopLevelAnnotation() + { + $reader = $this->getReader(); + $annotations = $reader->getPropertyAnnotations(new \ReflectionProperty('Doctrine\Tests\Common\Annotations\TestTopLevelAnnotationClass', 'field')); + + $this->assertEquals(1, count($annotations)); + $this->assertInstanceOf('\TopLevelAnnotation', reset($annotations)); + } + + public function testIgnoresAnnotationsNotPrefixedWithWhitespace() + { + $reader = $this->getReader(); + + $annotation = $reader->getClassAnnotation(new \ReflectionClass(new TestIgnoresNonAnnotationsClass()), 'Doctrine\Tests\Common\Annotations\Name'); + $this->assertInstanceOf('Doctrine\Tests\Common\Annotations\Name', $annotation); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage The class "Doctrine\Tests\Common\Annotations\Fixtures\NoAnnotation" is not annotated with @Annotation. Are you sure this class can be used as annotation? If so, then you need to add @Annotation to the _class_ doc comment of "Doctrine\Tests\Common\Annotations\Fixtures\NoAnnotation". If it is indeed no annotation, then you need to add @IgnoreAnnotation("NoAnnotation") to the _class_ doc comment of class Doctrine\Tests\Common\Annotations\Fixtures\InvalidAnnotationUsageClass. + */ + public function testErrorWhenInvalidAnnotationIsUsed() + { + $reader = $this->getReader(); + $ref = new \ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\InvalidAnnotationUsageClass'); + $reader->getClassAnnotations($ref); + } + + public function testInvalidAnnotationUsageButIgnoredClass() + { + $reader = $this->getReader(); + $ref = new \ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\InvalidAnnotationUsageButIgnoredClass'); + $annots = $reader->getClassAnnotations($ref); + + $this->assertEquals(2, count($annots)); + } + + /** + * @group DDC-1660 + * @group regression + */ + public function testInvalidAnnotationButIgnored() + { + $reader = $this->getReader(); + $class = new \ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\ClassDDC1660'); + + $this->assertTrue(class_exists('Doctrine\Tests\Common\Annotations\Fixtures\Annotation\Version')); + $this->assertCount(0, $reader->getClassAnnotations($class)); + $this->assertCount(0, $reader->getMethodAnnotations($class->getMethod('bar'))); + $this->assertCount(0, $reader->getPropertyAnnotations($class->getProperty('foo'))); + } + + public function testAnnotationEnumeratorException() + { + $reader = $this->getReader(); + $class = new \ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithAnnotationEnum'); + + $this->assertCount(1, $bar = $reader->getMethodAnnotations($class->getMethod('bar'))); + $this->assertCount(1, $foo = $reader->getPropertyAnnotations($class->getProperty('foo'))); + + $this->assertInstanceOf('Doctrine\Tests\Common\Annotations\Fixtures\AnnotationEnum', $bar[0]); + $this->assertInstanceOf('Doctrine\Tests\Common\Annotations\Fixtures\AnnotationEnum', $foo[0]); + + try { + $reader->getPropertyAnnotations($class->getProperty('invalidProperty')); + $this->fail(); + } catch (\Doctrine\Common\Annotations\AnnotationException $exc) { + $this->assertEquals('[Enum Error] Attribute "value" of @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationEnum declared on property Doctrine\Tests\Common\Annotations\Fixtures\ClassWithAnnotationEnum::$invalidProperty accept only [ONE, TWO, THREE], but got FOUR.', $exc->getMessage()); + } + + try { + $reader->getMethodAnnotations($class->getMethod('invalidMethod')); + $this->fail(); + } catch (\Doctrine\Common\Annotations\AnnotationException $exc) { + $this->assertEquals('[Enum Error] Attribute "value" of @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationEnum declared on method Doctrine\Tests\Common\Annotations\Fixtures\ClassWithAnnotationEnum::invalidMethod() accept only [ONE, TWO, THREE], but got 5.', $exc->getMessage()); + } + } + + /** + * @group DCOM-106 + */ + public function testIgnoreFixMeAndUpperCaseToDo() + { + $reader = $this->getReader(); + $ref = new \ReflectionClass('Doctrine\Tests\Common\Annotations\DCOM106'); + $reader->getClassAnnotations($ref); + } + + /** + * @return AnnotationReader + */ + abstract protected function getReader(); +} + +/** + * @parseAnnotation("var") + * @author Johannes M. Schmitt + * + */ +class TestParseAnnotationClass +{ + /** + * @var + */ + private $field; +} + +/** + * @Name + * @author Johannes M. Schmitt + */ +class TestIgnoresNonAnnotationsClass +{ +} + +class TestTopLevelAnnotationClass +{ + /** + * @\TopLevelAnnotation + */ + private $field; +} + +class TestNonExistentAnnotationClass +{ + /** + * @Foo\Bar\Name + */ + private $field; +} + +class TestAnnotationNotImportedClass +{ + /** + * @NameFoo + */ + private $field; +} + +class TestChildClass +{ + /** + * @\Doctrine\Tests\Common\Annotations\Foo\Name(name = "foo") + */ + protected $child; +} + +class TestParentClass extends TestChildClass +{ + /** + * @\Doctrine\Tests\Common\Annotations\Bar\Name(name = "bar") + */ + private $parent; +} + +class TestImportWithConcreteAnnotation +{ + /** + * @DummyAnnotation(dummyValue = "bar") + */ + private $field; +} + +/** + * @ignoreAnnotation("var") + */ +class DummyClass2 { + /** + * @DummyId @DummyColumn(type="integer") @DummyGeneratedValue + * @var integer + */ + private $id; +} + +/** @Annotation */ +class DummyId extends \Doctrine\Common\Annotations\Annotation {} +/** @Annotation */ +class DummyColumn extends \Doctrine\Common\Annotations\Annotation { + public $type; +} +/** @Annotation */ +class DummyGeneratedValue extends \Doctrine\Common\Annotations\Annotation {} +/** @Annotation */ +class DummyAnnotation extends \Doctrine\Common\Annotations\Annotation { + public $dummyValue; +} + +/** + * @api + * @Annotation + */ +class DummyAnnotationWithIgnoredAnnotation extends \Doctrine\Common\Annotations\Annotation { + public $dummyValue; +} + +/** @Annotation */ +class DummyJoinColumn extends \Doctrine\Common\Annotations\Annotation { + public $name; + public $referencedColumnName; +} +/** @Annotation */ +class DummyJoinTable extends \Doctrine\Common\Annotations\Annotation { + public $name; + public $joinColumns; + public $inverseJoinColumns; +} + +/** + * @DummyAnnotation(@) + */ +class DummyClassSyntaxError +{ + +} + +class DummyClassMethodSyntaxError +{ + /** + * @DummyAnnotation(@) + */ + public function foo() + { + + } +} + +class DummyClassPropertySyntaxError +{ + /** + * @DummyAnnotation(@) + */ + public $foo; +} + +/** + * @ignoreAnnotation({"since", "var"}) + */ +class DummyClassNonAnnotationProblem +{ + /** + * @DummyAnnotation + * + * @var \Test + * @since 0.1 + */ + public $foo; +} + + +/** +* @DummyAnnotation Foo bar +*/ +class DummyClassWithEmail +{ + +} + + +/** + * @fixme public + * @TODO + */ +class DCOM106 +{ + +} + +namespace Doctrine\Tests\Common\Annotations\Foo; + +/** @Annotation */ +class Name extends \Doctrine\Common\Annotations\Annotation +{ + public $name; +} + +namespace Doctrine\Tests\Common\Annotations\Bar; + +/** @Annotation */ +class Name extends \Doctrine\Common\Annotations\Annotation +{ + public $name; +} diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/AnnotationReaderTest.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/AnnotationReaderTest.php new file mode 100644 index 0000000..d2cc667 --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/AnnotationReaderTest.php @@ -0,0 +1,13 @@ +getMock('Doctrine\Common\Cache\Cache'); + $cache + ->expects($this->at(0)) + ->method('fetch') + ->with($this->equalTo($cacheKey)) + ->will($this->returnValue(array())) + ; + $cache + ->expects($this->at(1)) + ->method('fetch') + ->with($this->equalTo('[C]'.$cacheKey)) + ->will($this->returnValue(time() - 10)) + ; + $cache + ->expects($this->at(2)) + ->method('save') + ->with($this->equalTo($cacheKey)) + ; + $cache + ->expects($this->at(3)) + ->method('save') + ->with($this->equalTo('[C]'.$cacheKey)) + ; + + $reader = new CachedReader(new AnnotationReader(), $cache, true); + $route = new Route(); + $route->pattern = '/someprefix'; + $this->assertEquals(array($route), $reader->getClassAnnotations(new \ReflectionClass($name))); + } + + protected function getReader() + { + $this->cache = new ArrayCache(); + return new CachedReader(new AnnotationReader(), $this->cache); + } +} \ No newline at end of file diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/DocLexerTest.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/DocLexerTest.php new file mode 100644 index 0000000..03a55c8 --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/DocLexerTest.php @@ -0,0 +1,137 @@ +setInput("@Name"); + $this->assertNull($lexer->token); + $this->assertNull($lexer->lookahead); + + $this->assertTrue($lexer->moveNext()); + $this->assertNull($lexer->token); + $this->assertEquals('@', $lexer->lookahead['value']); + + $this->assertTrue($lexer->moveNext()); + $this->assertEquals('@', $lexer->token['value']); + $this->assertEquals('Name', $lexer->lookahead['value']); + + $this->assertFalse($lexer->moveNext()); + } + + public function testScannerTokenizesDocBlockWhitConstants() + { + $lexer = new DocLexer(); + $docblock = '@AnnotationWithConstants(PHP_EOL, ClassWithConstants::SOME_VALUE, \Doctrine\Tests\Common\Annotations\Fixtures\IntefaceWithConstants::SOME_VALUE)'; + + $tokens = array ( + array( + 'value' => '@', + 'position' => 0, + 'type' => DocLexer::T_AT, + ), + array( + 'value' => 'AnnotationWithConstants', + 'position' => 1, + 'type' => DocLexer::T_IDENTIFIER, + ), + array( + 'value' => '(', + 'position' => 24, + 'type' => DocLexer::T_OPEN_PARENTHESIS, + ), + array( + 'value' => 'PHP_EOL', + 'position' => 25, + 'type' => DocLexer::T_IDENTIFIER, + ), + array( + 'value' => ',', + 'position' => 32, + 'type' => DocLexer::T_COMMA, + ), + array( + 'value' => 'ClassWithConstants::SOME_VALUE', + 'position' => 34, + 'type' => DocLexer::T_IDENTIFIER, + ), + array( + 'value' => ',', + 'position' => 64, + 'type' => DocLexer::T_COMMA, + ), + array( + 'value' => '\\Doctrine\\Tests\\Common\\Annotations\\Fixtures\\IntefaceWithConstants::SOME_VALUE', + 'position' => 66, + 'type' => DocLexer::T_IDENTIFIER, + ), + array( + 'value' => ')', + 'position' => 143, + 'type' => DocLexer::T_CLOSE_PARENTHESIS, + ) + + ); + + $lexer->setInput($docblock); + + foreach ($tokens as $expected) { + $lexer->moveNext(); + $lookahead = $lexer->lookahead; + $this->assertEquals($expected['value'], $lookahead['value']); + $this->assertEquals($expected['type'], $lookahead['type']); + $this->assertEquals($expected['position'], $lookahead['position']); + } + + $this->assertFalse($lexer->moveNext()); + } + + + public function testScannerTokenizesDocBlockWhitInvalidIdentifier() + { + $lexer = new DocLexer(); + $docblock = '@Foo\3.42'; + + $tokens = array ( + array( + 'value' => '@', + 'position' => 0, + 'type' => DocLexer::T_AT, + ), + array( + 'value' => 'Foo', + 'position' => 1, + 'type' => DocLexer::T_IDENTIFIER, + ), + array( + 'value' => '\\', + 'position' => 4, + 'type' => DocLexer::T_NAMESPACE_SEPARATOR, + ), + array( + 'value' => 3.42, + 'position' => 5, + 'type' => DocLexer::T_FLOAT, + ) + ); + + $lexer->setInput($docblock); + + foreach ($tokens as $expected) { + $lexer->moveNext(); + $lookahead = $lexer->lookahead; + $this->assertEquals($expected['value'], $lookahead['value']); + $this->assertEquals($expected['type'], $lookahead['type']); + $this->assertEquals($expected['position'], $lookahead['position']); + } + + $this->assertFalse($lexer->moveNext()); + } + +} \ No newline at end of file diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/DocParserTest.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/DocParserTest.php new file mode 100644 index 0000000..6c8016f --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/DocParserTest.php @@ -0,0 +1,1276 @@ +createTestParser(); + + // Nested arrays with nested annotations + $result = $parser->parse('@Name(foo={1,2, {"key"=@Name}})'); + $annot = $result[0]; + + $this->assertTrue($annot instanceof Name); + $this->assertNull($annot->value); + $this->assertEquals(3, count($annot->foo)); + $this->assertEquals(1, $annot->foo[0]); + $this->assertEquals(2, $annot->foo[1]); + $this->assertTrue(is_array($annot->foo[2])); + + $nestedArray = $annot->foo[2]; + $this->assertTrue(isset($nestedArray['key'])); + $this->assertTrue($nestedArray['key'] instanceof Name); + } + + public function testBasicAnnotations() + { + $parser = $this->createTestParser(); + + // Marker annotation + $result = $parser->parse("@Name"); + $annot = $result[0]; + $this->assertTrue($annot instanceof Name); + $this->assertNull($annot->value); + $this->assertNull($annot->foo); + + // Associative arrays + $result = $parser->parse('@Name(foo={"key1" = "value1"})'); + $annot = $result[0]; + $this->assertNull($annot->value); + $this->assertTrue(is_array($annot->foo)); + $this->assertTrue(isset($annot->foo['key1'])); + + // Numerical arrays + $result = $parser->parse('@Name({2="foo", 4="bar"})'); + $annot = $result[0]; + $this->assertTrue(is_array($annot->value)); + $this->assertEquals('foo', $annot->value[2]); + $this->assertEquals('bar', $annot->value[4]); + $this->assertFalse(isset($annot->value[0])); + $this->assertFalse(isset($annot->value[1])); + $this->assertFalse(isset($annot->value[3])); + + // Multiple values + $result = $parser->parse('@Name(@Name, @Name)'); + $annot = $result[0]; + + $this->assertTrue($annot instanceof Name); + $this->assertTrue(is_array($annot->value)); + $this->assertTrue($annot->value[0] instanceof Name); + $this->assertTrue($annot->value[1] instanceof Name); + + // Multiple types as values + $result = $parser->parse('@Name(foo="Bar", @Name, {"key1"="value1", "key2"="value2"})'); + $annot = $result[0]; + + $this->assertTrue($annot instanceof Name); + $this->assertTrue(is_array($annot->value)); + $this->assertTrue($annot->value[0] instanceof Name); + $this->assertTrue(is_array($annot->value[1])); + $this->assertEquals('value1', $annot->value[1]['key1']); + $this->assertEquals('value2', $annot->value[1]['key2']); + + // Complete docblock + $docblock = <<parse($docblock); + $this->assertEquals(1, count($result)); + $annot = $result[0]; + $this->assertTrue($annot instanceof Name); + $this->assertEquals("bar", $annot->foo); + $this->assertNull($annot->value); + } + + public function testNamespacedAnnotations() + { + $parser = new DocParser; + $parser->setIgnoreNotImportedAnnotations(true); + + $docblock = << + * @Doctrine\Tests\Common\Annotations\Name(foo="bar") + * @ignore + */ +DOCBLOCK; + + $result = $parser->parse($docblock); + $this->assertEquals(1, count($result)); + $annot = $result[0]; + $this->assertTrue($annot instanceof Name); + $this->assertEquals("bar", $annot->foo); + } + + /** + * @group debug + */ + public function testTypicalMethodDocBlock() + { + $parser = $this->createTestParser(); + + $docblock = <<parse($docblock); + $this->assertEquals(2, count($result)); + $this->assertTrue(isset($result[0])); + $this->assertTrue(isset($result[1])); + $annot = $result[0]; + $this->assertTrue($annot instanceof Name); + $this->assertEquals("bar", $annot->foo); + $marker = $result[1]; + $this->assertTrue($marker instanceof Marker); + } + + + public function testAnnotationWithoutConstructor() + { + $parser = $this->createTestParser(); + + + $docblock = <<parse($docblock); + $this->assertEquals(count($result), 1); + $annot = $result[0]; + + $this->assertNotNull($annot); + $this->assertTrue($annot instanceof SomeAnnotationClassNameWithoutConstructor); + + $this->assertNull($annot->name); + $this->assertNotNull($annot->data); + $this->assertEquals($annot->data, "Some data"); + + + + +$docblock = <<parse($docblock); + $this->assertEquals(count($result), 1); + $annot = $result[0]; + + $this->assertNotNull($annot); + $this->assertTrue($annot instanceof SomeAnnotationClassNameWithoutConstructor); + + $this->assertEquals($annot->name, "Some Name"); + $this->assertEquals($annot->data, "Some data"); + + + + +$docblock = <<parse($docblock); + $this->assertEquals(count($result), 1); + $annot = $result[0]; + + $this->assertEquals($annot->data, "Some data"); + $this->assertNull($annot->name); + + + $docblock = <<parse($docblock); + $this->assertEquals(count($result), 1); + $annot = $result[0]; + + $this->assertEquals($annot->name, "Some name"); + $this->assertNull($annot->data); + + $docblock = <<parse($docblock); + $this->assertEquals(count($result), 1); + $annot = $result[0]; + + $this->assertEquals($annot->data, "Some data"); + $this->assertNull($annot->name); + + + + $docblock = <<parse($docblock); + $this->assertEquals(count($result), 1); + $annot = $result[0]; + + $this->assertEquals($annot->name, "Some name"); + $this->assertEquals($annot->data, "Some data"); + + + $docblock = <<parse($docblock); + $this->assertEquals(count($result), 1); + $annot = $result[0]; + + $this->assertEquals($annot->name, "Some name"); + $this->assertEquals($annot->data, "Some data"); + + $docblock = <<parse($docblock); + $this->assertEquals(count($result), 1); + $this->assertTrue($result[0] instanceof SomeAnnotationClassNameWithoutConstructorAndProperties); + } + + public function testAnnotationTarget() + { + + $parser = new DocParser; + $parser->setImports(array( + '__NAMESPACE__' => 'Doctrine\Tests\Common\Annotations\Fixtures', + )); + $class = new \ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithValidAnnotationTarget'); + + + $context = 'class ' . $class->getName(); + $docComment = $class->getDocComment(); + + $parser->setTarget(Target::TARGET_CLASS); + $this->assertNotNull($parser->parse($docComment,$context)); + + + $property = $class->getProperty('foo'); + $docComment = $property->getDocComment(); + $context = 'property ' . $class->getName() . "::\$" . $property->getName(); + + $parser->setTarget(Target::TARGET_PROPERTY); + $this->assertNotNull($parser->parse($docComment,$context)); + + + + $method = $class->getMethod('someFunction'); + $docComment = $property->getDocComment(); + $context = 'method ' . $class->getName() . '::' . $method->getName() . '()'; + + $parser->setTarget(Target::TARGET_METHOD); + $this->assertNotNull($parser->parse($docComment,$context)); + + + try { + $class = new \ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithInvalidAnnotationTargetAtClass'); + $context = 'class ' . $class->getName(); + $docComment = $class->getDocComment(); + + $parser->setTarget(Target::TARGET_CLASS); + $parser->parse($class->getDocComment(),$context); + + $this->fail(); + } catch (\Doctrine\Common\Annotations\AnnotationException $exc) { + $this->assertNotNull($exc->getMessage()); + } + + + try { + + $class = new \ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithInvalidAnnotationTargetAtMethod'); + $method = $class->getMethod('functionName'); + $docComment = $method->getDocComment(); + $context = 'method ' . $class->getName() . '::' . $method->getName() . '()'; + + $parser->setTarget(Target::TARGET_METHOD); + $parser->parse($docComment,$context); + + $this->fail(); + } catch (\Doctrine\Common\Annotations\AnnotationException $exc) { + $this->assertNotNull($exc->getMessage()); + } + + + try { + $class = new \ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\ClassWithInvalidAnnotationTargetAtProperty'); + $property = $class->getProperty('foo'); + $docComment = $property->getDocComment(); + $context = 'property ' . $class->getName() . "::\$" . $property->getName(); + + $parser->setTarget(Target::TARGET_PROPERTY); + $parser->parse($docComment,$context); + + $this->fail(); + } catch (\Doctrine\Common\Annotations\AnnotationException $exc) { + $this->assertNotNull($exc->getMessage()); + } + + } + + public function getAnnotationVarTypeProviderValid() + { + //({attribute name}, {attribute value}) + return array( + // mixed type + array('mixed', '"String Value"'), + array('mixed', 'true'), + array('mixed', 'false'), + array('mixed', '1'), + array('mixed', '1.2'), + array('mixed', '@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll'), + + // boolean type + array('boolean', 'true'), + array('boolean', 'false'), + + // alias for internal type boolean + array('bool', 'true'), + array('bool', 'false'), + + // integer type + array('integer', '0'), + array('integer', '1'), + array('integer', '123456789'), + array('integer', '9223372036854775807'), + + // alias for internal type double + array('float', '0.1'), + array('float', '1.2'), + array('float', '123.456'), + + // string type + array('string', '"String Value"'), + array('string', '"true"'), + array('string', '"123"'), + + // array type + array('array', '{@AnnotationExtendsAnnotationTargetAll}'), + array('array', '{@AnnotationExtendsAnnotationTargetAll,@AnnotationExtendsAnnotationTargetAll}'), + + array('arrayOfIntegers', '1'), + array('arrayOfIntegers', '{1}'), + array('arrayOfIntegers', '{1,2,3,4}'), + array('arrayOfAnnotations', '@AnnotationExtendsAnnotationTargetAll'), + array('arrayOfAnnotations', '{@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll}'), + array('arrayOfAnnotations', '{@AnnotationExtendsAnnotationTargetAll, @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll}'), + + // annotation instance + array('annotation', '@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll'), + array('annotation', '@AnnotationExtendsAnnotationTargetAll'), + ); + } + + public function getAnnotationVarTypeProviderInvalid() + { + //({attribute name}, {type declared type}, {attribute value} , {given type or class}) + return array( + // boolean type + array('boolean','boolean','1','integer'), + array('boolean','boolean','1.2','double'), + array('boolean','boolean','"str"','string'), + array('boolean','boolean','{1,2,3}','array'), + array('boolean','boolean','@Name', 'an instance of Doctrine\Tests\Common\Annotations\Name'), + + // alias for internal type boolean + array('bool','bool', '1','integer'), + array('bool','bool', '1.2','double'), + array('bool','bool', '"str"','string'), + array('bool','bool', '{"str"}','array'), + + // integer type + array('integer','integer', 'true','boolean'), + array('integer','integer', 'false','boolean'), + array('integer','integer', '1.2','double'), + array('integer','integer', '"str"','string'), + array('integer','integer', '{"str"}','array'), + array('integer','integer', '{1,2,3,4}','array'), + + // alias for internal type double + array('float','float', 'true','boolean'), + array('float','float', 'false','boolean'), + array('float','float', '123','integer'), + array('float','float', '"str"','string'), + array('float','float', '{"str"}','array'), + array('float','float', '{12.34}','array'), + array('float','float', '{1,2,3}','array'), + + // string type + array('string','string', 'true','boolean'), + array('string','string', 'false','boolean'), + array('string','string', '12','integer'), + array('string','string', '1.2','double'), + array('string','string', '{"str"}','array'), + array('string','string', '{1,2,3,4}','array'), + + // annotation instance + array('annotation','Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll', 'true','boolean'), + array('annotation','Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll', 'false','boolean'), + array('annotation','Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll', '12','integer'), + array('annotation','Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll', '1.2','double'), + array('annotation','Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll', '{"str"}','array'), + array('annotation','Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll', '{1,2,3,4}','array'), + array('annotation','Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll', '@Name','an instance of Doctrine\Tests\Common\Annotations\Name'), + ); + } + + public function getAnnotationVarTypeArrayProviderInvalid() + { + //({attribute name}, {type declared type}, {attribute value} , {given type or class}) + return array( + array('arrayOfIntegers','integer', 'true','boolean'), + array('arrayOfIntegers','integer', 'false','boolean'), + array('arrayOfIntegers','integer', '{true,true}','boolean'), + array('arrayOfIntegers','integer', '{1,true}','boolean'), + array('arrayOfIntegers','integer', '{1,2,1.2}','double'), + array('arrayOfIntegers','integer', '{1,2,"str"}','string'), + + + array('arrayOfAnnotations','Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll', 'true','boolean'), + array('arrayOfAnnotations','Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll', 'false','boolean'), + array('arrayOfAnnotations','Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll', '{@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll,true}','boolean'), + array('arrayOfAnnotations','Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll', '{@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll,true}','boolean'), + array('arrayOfAnnotations','Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll', '{@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll,1.2}','double'), + array('arrayOfAnnotations','Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll', '{@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll,@AnnotationExtendsAnnotationTargetAll,"str"}','string'), + ); + } + + /** + * @dataProvider getAnnotationVarTypeProviderValid + */ + public function testAnnotationWithVarType($attribute, $value) + { + $parser = $this->createTestParser(); + $context = 'property SomeClassName::$invalidProperty.'; + $docblock = sprintf('@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithVarType(%s = %s)',$attribute, $value); + $parser->setTarget(Target::TARGET_PROPERTY); + + $result = $parser->parse($docblock, $context); + + $this->assertTrue(sizeof($result) === 1); + $this->assertInstanceOf('Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithVarType', $result[0]); + $this->assertNotNull($result[0]->$attribute); + } + + /** + * @dataProvider getAnnotationVarTypeProviderInvalid + */ + public function testAnnotationWithVarTypeError($attribute,$type,$value,$given) + { + $parser = $this->createTestParser(); + $context = 'property SomeClassName::invalidProperty.'; + $docblock = sprintf('@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithVarType(%s = %s)',$attribute, $value); + $parser->setTarget(Target::TARGET_PROPERTY); + + try { + $parser->parse($docblock, $context); + $this->fail(); + } catch (\Doctrine\Common\Annotations\AnnotationException $exc) { + $this->assertContains("[Type Error] Attribute \"$attribute\" of @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithVarType declared on property SomeClassName::invalidProperty. expects a(n) $type, but got $given.", $exc->getMessage()); + } + } + + + /** + * @dataProvider getAnnotationVarTypeArrayProviderInvalid + */ + public function testAnnotationWithVarTypeArrayError($attribute,$type,$value,$given) + { + $parser = $this->createTestParser(); + $context = 'property SomeClassName::invalidProperty.'; + $docblock = sprintf('@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithVarType(%s = %s)',$attribute, $value); + $parser->setTarget(Target::TARGET_PROPERTY); + + try { + $parser->parse($docblock, $context); + $this->fail(); + } catch (\Doctrine\Common\Annotations\AnnotationException $exc) { + $this->assertContains("[Type Error] Attribute \"$attribute\" of @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithVarType declared on property SomeClassName::invalidProperty. expects either a(n) $type, or an array of {$type}s, but got $given.", $exc->getMessage()); + } + } + + /** + * @dataProvider getAnnotationVarTypeProviderValid + */ + public function testAnnotationWithAttributes($attribute, $value) + { + $parser = $this->createTestParser(); + $context = 'property SomeClassName::$invalidProperty.'; + $docblock = sprintf('@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithAttributes(%s = %s)',$attribute, $value); + $parser->setTarget(Target::TARGET_PROPERTY); + + $result = $parser->parse($docblock, $context); + + $this->assertTrue(sizeof($result) === 1); + $this->assertInstanceOf('Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithAttributes', $result[0]); + $getter = "get".ucfirst($attribute); + $this->assertNotNull($result[0]->$getter()); + } + + /** + * @dataProvider getAnnotationVarTypeProviderInvalid + */ + public function testAnnotationWithAttributesError($attribute,$type,$value,$given) + { + $parser = $this->createTestParser(); + $context = 'property SomeClassName::invalidProperty.'; + $docblock = sprintf('@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithAttributes(%s = %s)',$attribute, $value); + $parser->setTarget(Target::TARGET_PROPERTY); + + try { + $parser->parse($docblock, $context); + $this->fail(); + } catch (\Doctrine\Common\Annotations\AnnotationException $exc) { + $this->assertContains("[Type Error] Attribute \"$attribute\" of @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithAttributes declared on property SomeClassName::invalidProperty. expects a(n) $type, but got $given.", $exc->getMessage()); + } + } + + + /** + * @dataProvider getAnnotationVarTypeArrayProviderInvalid + */ + public function testAnnotationWithAttributesWithVarTypeArrayError($attribute,$type,$value,$given) + { + $parser = $this->createTestParser(); + $context = 'property SomeClassName::invalidProperty.'; + $docblock = sprintf('@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithAttributes(%s = %s)',$attribute, $value); + $parser->setTarget(Target::TARGET_PROPERTY); + + try { + $parser->parse($docblock, $context); + $this->fail(); + } catch (\Doctrine\Common\Annotations\AnnotationException $exc) { + $this->assertContains("[Type Error] Attribute \"$attribute\" of @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithAttributes declared on property SomeClassName::invalidProperty. expects either a(n) $type, or an array of {$type}s, but got $given.", $exc->getMessage()); + } + } + + public function testAnnotationWithRequiredAttributes() + { + $parser = $this->createTestParser(); + $context = 'property SomeClassName::invalidProperty.'; + $parser->setTarget(Target::TARGET_PROPERTY); + + + $docblock = '@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithRequiredAttributes("Some Value", annot = @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAnnotation)'; + $result = $parser->parse($docblock); + + $this->assertTrue(sizeof($result) === 1); + $this->assertInstanceOf('Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithRequiredAttributes', $result[0]); + $this->assertEquals("Some Value",$result[0]->getValue()); + $this->assertInstanceOf('Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAnnotation', $result[0]->getAnnot()); + + + $docblock = '@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithRequiredAttributes("Some Value")'; + try { + $result = $parser->parse($docblock,$context); + $this->fail(); + } catch (\Doctrine\Common\Annotations\AnnotationException $exc) { + $this->assertContains('Attribute "annot" of @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithRequiredAttributes declared on property SomeClassName::invalidProperty. expects a(n) Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAnnotation. This value should not be null.', $exc->getMessage()); + } + + $docblock = '@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithRequiredAttributes(annot = @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAnnotation)'; + try { + $result = $parser->parse($docblock,$context); + $this->fail(); + } catch (\Doctrine\Common\Annotations\AnnotationException $exc) { + $this->assertContains('Attribute "value" of @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithRequiredAttributes declared on property SomeClassName::invalidProperty. expects a(n) string. This value should not be null.', $exc->getMessage()); + } + + } + + public function testAnnotationWithRequiredAttributesWithoutContructor() + { + $parser = $this->createTestParser(); + $context = 'property SomeClassName::invalidProperty.'; + $parser->setTarget(Target::TARGET_PROPERTY); + + + $docblock = '@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithRequiredAttributesWithoutContructor("Some Value", annot = @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAnnotation)'; + $result = $parser->parse($docblock); + + $this->assertTrue(sizeof($result) === 1); + $this->assertInstanceOf('Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithRequiredAttributesWithoutContructor', $result[0]); + $this->assertEquals("Some Value", $result[0]->value); + $this->assertInstanceOf('Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAnnotation', $result[0]->annot); + + + $docblock = '@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithRequiredAttributesWithoutContructor("Some Value")'; + try { + $result = $parser->parse($docblock,$context); + $this->fail(); + } catch (\Doctrine\Common\Annotations\AnnotationException $exc) { + $this->assertContains('Attribute "annot" of @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithRequiredAttributesWithoutContructor declared on property SomeClassName::invalidProperty. expects a(n) Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAnnotation. This value should not be null.', $exc->getMessage()); + } + + $docblock = '@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithRequiredAttributesWithoutContructor(annot = @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAnnotation)'; + try { + $result = $parser->parse($docblock,$context); + $this->fail(); + } catch (\Doctrine\Common\Annotations\AnnotationException $exc) { + $this->assertContains('Attribute "value" of @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithRequiredAttributesWithoutContructor declared on property SomeClassName::invalidProperty. expects a(n) string. This value should not be null.', $exc->getMessage()); + } + + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage Attribute "value" of @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationEnum declared on property SomeClassName::invalidProperty. accept only [ONE, TWO, THREE], but got FOUR. + */ + public function testAnnotationEnumeratorException() + { + $parser = $this->createTestParser(); + $context = 'property SomeClassName::invalidProperty.'; + $docblock = '@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationEnum("FOUR")'; + + $parser->setIgnoreNotImportedAnnotations(false); + $parser->setTarget(Target::TARGET_PROPERTY); + $parser->parse($docblock, $context); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage Attribute "value" of @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationEnumLiteral declared on property SomeClassName::invalidProperty. accept only [AnnotationEnumLiteral::ONE, AnnotationEnumLiteral::TWO, AnnotationEnumLiteral::THREE], but got 4. + */ + public function testAnnotationEnumeratorLiteralException() + { + $parser = $this->createTestParser(); + $context = 'property SomeClassName::invalidProperty.'; + $docblock = '@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationEnumLiteral(4)'; + + $parser->setIgnoreNotImportedAnnotations(false); + $parser->setTarget(Target::TARGET_PROPERTY); + $parser->parse($docblock, $context); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage @Enum supports only scalar values "array" given. + */ + public function testAnnotationEnumInvalidTypeDeclarationException() + { + $parser = $this->createTestParser(); + $docblock = '@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationEnumInvalid("foo")'; + + $parser->setIgnoreNotImportedAnnotations(false); + $parser->parse($docblock); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Undefined enumerator value "3" for literal "AnnotationEnumLiteral::THREE". + */ + public function testAnnotationEnumInvalidLiteralDeclarationException() + { + $parser = $this->createTestParser(); + $docblock = '@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationEnumLiteralInvalid("foo")'; + + $parser->setIgnoreNotImportedAnnotations(false); + $parser->parse($docblock); + } + + public function getConstantsProvider() + { + $provider[] = array( + '@AnnotationWithConstants(PHP_EOL)', + PHP_EOL + ); + $provider[] = array( + '@AnnotationWithConstants(AnnotationWithConstants::INTEGER)', + AnnotationWithConstants::INTEGER + ); + $provider[] = array( + '@Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithConstants(AnnotationWithConstants::STRING)', + AnnotationWithConstants::STRING + ); + $provider[] = array( + '@AnnotationWithConstants(Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithConstants::FLOAT)', + AnnotationWithConstants::FLOAT + ); + $provider[] = array( + '@AnnotationWithConstants(ClassWithConstants::SOME_VALUE)', + ClassWithConstants::SOME_VALUE + ); + $provider[] = array( + '@AnnotationWithConstants(Doctrine\Tests\Common\Annotations\Fixtures\ClassWithConstants::SOME_VALUE)', + ClassWithConstants::SOME_VALUE + ); + $provider[] = array( + '@AnnotationWithConstants(IntefaceWithConstants::SOME_VALUE)', + IntefaceWithConstants::SOME_VALUE + ); + $provider[] = array( + '@AnnotationWithConstants(\Doctrine\Tests\Common\Annotations\Fixtures\IntefaceWithConstants::SOME_VALUE)', + IntefaceWithConstants::SOME_VALUE + ); + $provider[] = array( + '@AnnotationWithConstants({AnnotationWithConstants::STRING, AnnotationWithConstants::INTEGER, AnnotationWithConstants::FLOAT})', + array(AnnotationWithConstants::STRING, AnnotationWithConstants::INTEGER, AnnotationWithConstants::FLOAT) + ); + $provider[] = array( + '@AnnotationWithConstants({ + AnnotationWithConstants::STRING = AnnotationWithConstants::INTEGER + })', + array(AnnotationWithConstants::STRING => AnnotationWithConstants::INTEGER) + ); + $provider[] = array( + '@AnnotationWithConstants({ + Doctrine\Tests\Common\Annotations\Fixtures\IntefaceWithConstants::SOME_KEY = AnnotationWithConstants::INTEGER + })', + array(IntefaceWithConstants::SOME_KEY => AnnotationWithConstants::INTEGER) + ); + $provider[] = array( + '@AnnotationWithConstants({ + \Doctrine\Tests\Common\Annotations\Fixtures\IntefaceWithConstants::SOME_KEY = AnnotationWithConstants::INTEGER + })', + array(IntefaceWithConstants::SOME_KEY => AnnotationWithConstants::INTEGER) + ); + $provider[] = array( + '@AnnotationWithConstants({ + AnnotationWithConstants::STRING = AnnotationWithConstants::INTEGER, + ClassWithConstants::SOME_KEY = ClassWithConstants::SOME_VALUE, + Doctrine\Tests\Common\Annotations\Fixtures\ClassWithConstants::SOME_KEY = IntefaceWithConstants::SOME_VALUE + })', + array( + AnnotationWithConstants::STRING => AnnotationWithConstants::INTEGER, + ClassWithConstants::SOME_KEY => ClassWithConstants::SOME_VALUE, + ClassWithConstants::SOME_KEY => IntefaceWithConstants::SOME_VALUE + ) + ); + return $provider; + } + + /** + * @dataProvider getConstantsProvider + */ + public function testSupportClassConstants($docblock, $expected) + { + $parser = $this->createTestParser(); + $parser->setImports(array( + 'classwithconstants' => 'Doctrine\Tests\Common\Annotations\Fixtures\ClassWithConstants', + 'intefacewithconstants' => 'Doctrine\Tests\Common\Annotations\Fixtures\IntefaceWithConstants', + 'annotationwithconstants' => 'Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithConstants' + )); + + $result = $parser->parse($docblock); + $this->assertInstanceOf('\Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithConstants', $annotation = $result[0]); + $this->assertEquals($expected, $annotation->value); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage The annotation @SomeAnnotationClassNameWithoutConstructorAndProperties declared on does not accept any values, but got {"value":"Foo"}. + */ + public function testWithoutConstructorWhenIsNotDefaultValue() + { + $parser = $this->createTestParser(); + $docblock = <<setTarget(Target::TARGET_CLASS); + $parser->parse($docblock); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage The annotation @SomeAnnotationClassNameWithoutConstructorAndProperties declared on does not accept any values, but got {"value":"Foo"}. + */ + public function testWithoutConstructorWhenHasNoProperties() + { + $parser = $this->createTestParser(); + $docblock = <<setTarget(Target::TARGET_CLASS); + $parser->parse($docblock); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage Expected namespace separator or identifier, got ')' at position 24 in class @Doctrine\Tests\Common\Annotations\Fixtures\AnnotationWithTargetSyntaxError. + */ + public function testAnnotationTargetSyntaxError() + { + $parser = $this->createTestParser(); + $context = 'class ' . 'SomeClassName'; + $docblock = <<setTarget(Target::TARGET_CLASS); + $parser->parse($docblock,$context); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Invalid Target "Foo". Available targets: [ALL, CLASS, METHOD, PROPERTY, ANNOTATION] + */ + public function testAnnotationWithInvalidTargetDeclarationError() + { + $parser = $this->createTestParser(); + $context = 'class ' . 'SomeClassName'; + $docblock = <<setTarget(Target::TARGET_CLASS); + $parser->parse($docblock,$context); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage @Target expects either a string value, or an array of strings, "NULL" given. + */ + public function testAnnotationWithTargetEmptyError() + { + $parser = $this->createTestParser(); + $context = 'class ' . 'SomeClassName'; + $docblock = <<setTarget(Target::TARGET_CLASS); + $parser->parse($docblock,$context); + } + + /** + * @group DDC-575 + */ + public function testRegressionDDC575() + { + $parser = $this->createTestParser(); + + $docblock = <<parse($docblock); + + $this->assertInstanceOf("Doctrine\Tests\Common\Annotations\Name", $result[0]); + + $docblock = <<parse($docblock); + + $this->assertInstanceOf("Doctrine\Tests\Common\Annotations\Name", $result[0]); + } + + /** + * @group DDC-77 + */ + public function testAnnotationWithoutClassIsIgnoredWithoutWarning() + { + $parser = new DocParser(); + $parser->setIgnoreNotImportedAnnotations(true); + $result = $parser->parse("@param"); + + $this->assertEquals(0, count($result)); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage Expected PlainValue, got ''' at position 10. + */ + public function testAnnotationDontAcceptSingleQuotes() + { + $parser = $this->createTestParser(); + $parser->parse("@Name(foo='bar')"); + } + + /** + * @group DCOM-41 + */ + public function testAnnotationDoesntThrowExceptionWhenAtSignIsNotFollowedByIdentifier() + { + $parser = new DocParser(); + $result = $parser->parse("'@'"); + + $this->assertEquals(0, count($result)); + } + + /** + * @group DCOM-41 + * @expectedException Doctrine\Common\Annotations\AnnotationException + */ + public function testAnnotationThrowsExceptionWhenAtSignIsNotFollowedByIdentifierInNestedAnnotation() + { + $parser = new DocParser(); + $result = $parser->parse("@Doctrine\Tests\Common\Annotations\Name(@')"); + } + + /** + * @group DCOM-56 + */ + public function testAutoloadAnnotation() + { + $this->assertFalse(class_exists('Doctrine\Tests\Common\Annotations\Fixture\Annotation\Autoload', false), 'Pre-condition: Doctrine\Tests\Common\Annotations\Fixture\Annotation\Autoload not allowed to be loaded.'); + + $parser = new DocParser(); + + AnnotationRegistry::registerAutoloadNamespace('Doctrine\Tests\Common\Annotations\Fixtures\Annotation', __DIR__ . '/../../../../'); + + $parser->setImports(array( + 'autoload' => 'Doctrine\Tests\Common\Annotations\Fixtures\Annotation\Autoload', + )); + $annotations = $parser->parse('@Autoload'); + + $this->assertEquals(1, count($annotations)); + $this->assertInstanceOf('Doctrine\Tests\Common\Annotations\Fixtures\Annotation\Autoload', $annotations[0]); + } + + public function createTestParser() + { + $parser = new DocParser(); + $parser->setIgnoreNotImportedAnnotations(true); + $parser->setImports(array( + 'name' => 'Doctrine\Tests\Common\Annotations\Name', + '__NAMESPACE__' => 'Doctrine\Tests\Common\Annotations', + )); + + return $parser; + } + + /** + * @group DDC-78 + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage Expected PlainValue, got ''' at position 10 in class \Doctrine\Tests\Common\Annotations\Name + */ + public function testSyntaxErrorWithContextDescription() + { + $parser = $this->createTestParser(); + $parser->parse("@Name(foo='bar')", "class \Doctrine\Tests\Common\Annotations\Name"); + } + + /** + * @group DDC-183 + */ + public function testSyntaxErrorWithUnknownCharacters() + { + $docblock = <<setInput(trim($docblock, '/ *')); + //var_dump($lexer); + + try { + $parser = $this->createTestParser(); + $result = $parser->parse($docblock); + } catch (Exception $e) { + $this->fail($e->getMessage()); + } + } + + /** + * @group DCOM-14 + */ + public function testIgnorePHPDocThrowTag() + { + $docblock = <<createTestParser(); + $result = $parser->parse($docblock); + } catch (Exception $e) { + $this->fail($e->getMessage()); + } + } + + /** + * @group DCOM-38 + */ + public function testCastInt() + { + $parser = $this->createTestParser(); + + $result = $parser->parse("@Name(foo=1234)"); + $annot = $result[0]; + $this->assertInternalType('int', $annot->foo); + } + + /** + * @group DCOM-38 + */ + public function testCastNegativeInt() + { + $parser = $this->createTestParser(); + + $result = $parser->parse("@Name(foo=-1234)"); + $annot = $result[0]; + $this->assertInternalType('int', $annot->foo); + } + + /** + * @group DCOM-38 + */ + public function testCastFloat() + { + $parser = $this->createTestParser(); + + $result = $parser->parse("@Name(foo=1234.345)"); + $annot = $result[0]; + $this->assertInternalType('float', $annot->foo); + } + + /** + * @group DCOM-38 + */ + public function testCastNegativeFloat() + { + $parser = $this->createTestParser(); + + $result = $parser->parse("@Name(foo=-1234.345)"); + $annot = $result[0]; + $this->assertInternalType('float', $annot->foo); + + $result = $parser->parse("@Marker(-1234.345)"); + $annot = $result[0]; + $this->assertInternalType('float', $annot->value); + } + + public function testReservedKeywordsInAnnotations() + { + $parser = $this->createTestParser(); + + $result = $parser->parse('@Doctrine\Tests\Common\Annotations\True'); + $this->assertTrue($result[0] instanceof True); + $result = $parser->parse('@Doctrine\Tests\Common\Annotations\False'); + $this->assertTrue($result[0] instanceof False); + $result = $parser->parse('@Doctrine\Tests\Common\Annotations\Null'); + $this->assertTrue($result[0] instanceof Null); + + $result = $parser->parse('@True'); + $this->assertTrue($result[0] instanceof True); + $result = $parser->parse('@False'); + $this->assertTrue($result[0] instanceof False); + $result = $parser->parse('@Null'); + $this->assertTrue($result[0] instanceof Null); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage [Creation Error] The annotation @SomeAnnotationClassNameWithoutConstructor declared on some class does not have a property named "invalidaProperty". Available properties: data, name + */ + public function testSetValuesExeption() + { + $docblock = <<createTestParser()->parse($docblock, 'some class'); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage [Syntax Error] Expected Doctrine\Common\Annotations\DocLexer::T_IDENTIFIER or Doctrine\Common\Annotations\DocLexer::T_TRUE or Doctrine\Common\Annotations\DocLexer::T_FALSE or Doctrine\Common\Annotations\DocLexer::T_NULL, got '3.42' at position 5. + */ + public function testInvalidIdentifierInAnnotation() + { + $parser = $this->createTestParser(); + $parser->parse('@Foo\3.42'); + } + + public function testTrailingCommaIsAllowed() + { + $parser = $this->createTestParser(); + + $annots = $parser->parse('@Name({ + "Foo", + "Bar", + })'); + $this->assertEquals(1, count($annots)); + $this->assertEquals(array('Foo', 'Bar'), $annots[0]->value); + } + + public function testDefaultAnnotationValueIsNotOverwritten() + { + $parser = $this->createTestParser(); + + $annots = $parser->parse('@Doctrine\Tests\Common\Annotations\Fixtures\Annotation\AnnotWithDefaultValue'); + $this->assertEquals(1, count($annots)); + $this->assertEquals('bar', $annots[0]->foo); + } + + public function testArrayWithColon() + { + $parser = $this->createTestParser(); + + $annots = $parser->parse('@Name({"foo": "bar"})'); + $this->assertEquals(1, count($annots)); + $this->assertEquals(array('foo' => 'bar'), $annots[0]->value); + } + + /** + * @expectedException Doctrine\Common\Annotations\AnnotationException + * @expectedExceptionMessage [Semantical Error] Couldn't find constant foo. + */ + public function testInvalidContantName() + { + $parser = $this->createTestParser(); + $parser->parse('@Name(foo: "bar")'); + } + + /** + * Tests parsing empty arrays. + */ + public function testEmptyArray() + { + $parser = $this->createTestParser(); + + $annots = $parser->parse('@Name({"foo": {}})'); + $this->assertEquals(1, count($annots)); + $this->assertEquals(array('foo' => array()), $annots[0]->value); + } +} + +/** @Annotation */ +class SomeAnnotationClassNameWithoutConstructor +{ + public $data; + public $name; +} + +/** @Annotation */ +class SomeAnnotationWithConstructorWithoutParams +{ + function __construct() + { + $this->data = "Some data"; + } + public $data; + public $name; +} + +/** @Annotation */ +class SomeAnnotationClassNameWithoutConstructorAndProperties{} + +/** + * @Annotation + * @Target("Foo") + */ +class AnnotationWithInvalidTargetDeclaration{} + +/** + * @Annotation + * @Target + */ +class AnnotationWithTargetEmpty{} + +/** @Annotation */ +class AnnotationExtendsAnnotationTargetAll extends \Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll +{ +} + +/** @Annotation */ +class Name extends \Doctrine\Common\Annotations\Annotation { + public $foo; +} + +/** @Annotation */ +class Marker { + public $value; +} + +/** @Annotation */ +class True {} + +/** @Annotation */ +class False {} + +/** @Annotation */ +class Null {} + +namespace Doctrine\Tests\Common\Annotations\FooBar; + +/** @Annotation */ +class Name extends \Doctrine\Common\Annotations\Annotation { +} diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/DummyClass.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/DummyClass.php new file mode 100644 index 0000000..17223f6 --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/DummyClass.php @@ -0,0 +1,48 @@ +cacheDir = sys_get_temp_dir() . "/annotations_". uniqid(); + @mkdir($this->cacheDir); + return new FileCacheReader(new AnnotationReader(), $this->cacheDir); + } + + public function tearDown() + { + foreach (glob($this->cacheDir.'/*.php') AS $file) { + unlink($file); + } + rmdir($this->cacheDir); + } + + /** + * @group DCOM-81 + */ + public function testAttemptToCreateAnnotationCacheDir() + { + $this->cacheDir = sys_get_temp_dir() . "/not_existed_dir_". uniqid(); + + $this->assertFalse(is_dir($this->cacheDir)); + + $cache = new FileCacheReader(new AnnotationReader(), $this->cacheDir); + + $this->assertTrue(is_dir($this->cacheDir)); + } +} \ No newline at end of file diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/Annotation/AnnotWithDefaultValue.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/Annotation/AnnotWithDefaultValue.php new file mode 100644 index 0000000..44108e1 --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/Annotation/AnnotWithDefaultValue.php @@ -0,0 +1,10 @@ +roles = $values['value']; + } +} \ No newline at end of file diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/Annotation/Template.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/Annotation/Template.php new file mode 100644 index 0000000..b507e60 --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/Annotation/Template.php @@ -0,0 +1,14 @@ +name = isset($values['value']) ? $values['value'] : null; + } +} \ No newline at end of file diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/Annotation/Version.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/Annotation/Version.php new file mode 100644 index 0000000..09ef031 --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/Annotation/Version.php @@ -0,0 +1,11 @@ +"), + @Attribute("annotation", type = "Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll"), + @Attribute("arrayOfAnnotations", type = "array"), + }) + */ +final class AnnotationWithAttributes +{ + + public final function __construct(array $data) + { + foreach ($data as $key => $value) { + $this->$key = $value; + } + } + + private $mixed; + private $boolean; + private $bool; + private $float; + private $string; + private $integer; + private $array; + private $annotation; + private $arrayOfIntegers; + private $arrayOfAnnotations; + + /** + * @return mixed + */ + public function getMixed() + { + return $this->mixed; + } + + /** + * @return boolean + */ + public function getBoolean() + { + return $this->boolean; + } + + /** + * @return bool + */ + public function getBool() + { + return $this->bool; + } + + /** + * @return float + */ + public function getFloat() + { + return $this->float; + } + + /** + * @return string + */ + public function getString() + { + return $this->string; + } + + public function getInteger() + { + return $this->integer; + } + + /** + * @return array + */ + public function getArray() + { + return $this->array; + } + + /** + * @return Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAll + */ + public function getAnnotation() + { + return $this->annotation; + } + + /** + * @return array + */ + public function getArrayOfIntegers() + { + return $this->arrayOfIntegers; + } + + /** + * @return array + */ + public function getArrayOfAnnotations() + { + return $this->arrayOfAnnotations; + } + +} \ No newline at end of file diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/AnnotationWithConstants.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/AnnotationWithConstants.php new file mode 100644 index 0000000..9c94558 --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/AnnotationWithConstants.php @@ -0,0 +1,20 @@ + $value) { + $this->$key = $value; + } + } + + /** + * @var string + */ + private $value; + + /** + * + * @var Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAnnotation + */ + private $annot; + + /** + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * @return Doctrine\Tests\Common\Annotations\Fixtures\AnnotationTargetAnnotation + */ + public function getAnnot() + { + return $this->annot; + } + +} \ No newline at end of file diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/AnnotationWithRequiredAttributesWithoutContructor.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/AnnotationWithRequiredAttributesWithoutContructor.php new file mode 100644 index 0000000..bf458ee --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/AnnotationWithRequiredAttributesWithoutContructor.php @@ -0,0 +1,24 @@ + + */ + public $arrayOfIntegers; + + /** + * @var array + */ + public $arrayOfAnnotations; + +} \ No newline at end of file diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/Api.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/Api.php new file mode 100644 index 0000000..534ad14 --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/Api.php @@ -0,0 +1,10 @@ +events->filter(function ($item) use ($year, $month, $day) { + $leftDate = new \DateTime($year.'-'.$month.'-'.$day.' 00:00'); + $rigthDate = new \DateTime($year.'-'.$month.'-'.$day.' +1 day 00:00'); + return ( ( $leftDate <= $item->getDateStart() ) && ( $item->getDateStart() < $rigthDate ) ); + + } + ); + return $extractEvents; + } + +} \ No newline at end of file diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/ClassWithConstants.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/ClassWithConstants.php new file mode 100644 index 0000000..055e245 --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/ClassWithConstants.php @@ -0,0 +1,10 @@ + + */ +class Controller +{ + /** + * @Route("/", name="_demo") + * @Template() + */ + public function indexAction() + { + return array(); + } + + /** + * @Route("/hello/{name}", name="_demo_hello") + * @Template() + */ + public function helloAction($name) + { + return array('name' => $name); + } + + /** + * @Route("/contact", name="_demo_contact") + * @Template() + */ + public function contactAction() + { + $form = ContactForm::create($this->get('form.context'), 'contact'); + + $form->bind($this->container->get('request'), $form); + if ($form->isValid()) { + $form->send($this->get('mailer')); + + $this->get('session')->setFlash('notice', 'Message sent!'); + + return new RedirectResponse($this->generateUrl('_demo')); + } + + return array('form' => $form); + } + + /** + * Creates the ACL for the passed object identity + * + * @param ObjectIdentityInterface $oid + * @return void + */ + private function createObjectIdentity(ObjectIdentityInterface $oid) + { + $classId = $this->createOrRetrieveClassId($oid->getType()); + + $this->connection->executeQuery($this->getInsertObjectIdentitySql($oid->getIdentifier(), $classId, true)); + } + + /** + * Returns the primary key for the passed class type. + * + * If the type does not yet exist in the database, it will be created. + * + * @param string $classType + * @return integer + */ + private function createOrRetrieveClassId($classType) + { + if (false !== $id = $this->connection->executeQuery($this->getSelectClassIdSql($classType))->fetchColumn()) { + return $id; + } + + $this->connection->executeQuery($this->getInsertClassSql($classType)); + + return $this->connection->executeQuery($this->getSelectClassIdSql($classType))->fetchColumn(); + } + + /** + * Returns the primary key for the passed security identity. + * + * If the security identity does not yet exist in the database, it will be + * created. + * + * @param SecurityIdentityInterface $sid + * @return integer + */ + private function createOrRetrieveSecurityIdentityId(SecurityIdentityInterface $sid) + { + if (false !== $id = $this->connection->executeQuery($this->getSelectSecurityIdentityIdSql($sid))->fetchColumn()) { + return $id; + } + + $this->connection->executeQuery($this->getInsertSecurityIdentitySql($sid)); + + return $this->connection->executeQuery($this->getSelectSecurityIdentityIdSql($sid))->fetchColumn(); + } + + /** + * Deletes all ACEs for the given object identity primary key. + * + * @param integer $oidPK + * @return void + */ + private function deleteAccessControlEntries($oidPK) + { + $this->connection->executeQuery($this->getDeleteAccessControlEntriesSql($oidPK)); + } + + /** + * Deletes the object identity from the database. + * + * @param integer $pk + * @return void + */ + private function deleteObjectIdentity($pk) + { + $this->connection->executeQuery($this->getDeleteObjectIdentitySql($pk)); + } + + /** + * Deletes all entries from the relations table from the database. + * + * @param integer $pk + * @return void + */ + private function deleteObjectIdentityRelations($pk) + { + $this->connection->executeQuery($this->getDeleteObjectIdentityRelationsSql($pk)); + } + + /** + * This regenerates the ancestor table which is used for fast read access. + * + * @param AclInterface $acl + * @return void + */ + private function regenerateAncestorRelations(AclInterface $acl) + { + $pk = $acl->getId(); + $this->connection->executeQuery($this->getDeleteObjectIdentityRelationsSql($pk)); + $this->connection->executeQuery($this->getInsertObjectIdentityRelationSql($pk, $pk)); + + $parentAcl = $acl->getParentAcl(); + while (null !== $parentAcl) { + $this->connection->executeQuery($this->getInsertObjectIdentityRelationSql($pk, $parentAcl->getId())); + + $parentAcl = $parentAcl->getParentAcl(); + } + } + + /** + * This processes changes on an ACE related property (classFieldAces, or objectFieldAces). + * + * @param string $name + * @param array $changes + * @return void + */ + private function updateFieldAceProperty($name, array $changes) + { + $sids = new \SplObjectStorage(); + $classIds = new \SplObjectStorage(); + $currentIds = array(); + foreach ($changes[1] as $field => $new) { + for ($i=0,$c=count($new); $i<$c; $i++) { + $ace = $new[$i]; + + if (null === $ace->getId()) { + if ($sids->contains($ace->getSecurityIdentity())) { + $sid = $sids->offsetGet($ace->getSecurityIdentity()); + } else { + $sid = $this->createOrRetrieveSecurityIdentityId($ace->getSecurityIdentity()); + } + + $oid = $ace->getAcl()->getObjectIdentity(); + if ($classIds->contains($oid)) { + $classId = $classIds->offsetGet($oid); + } else { + $classId = $this->createOrRetrieveClassId($oid->getType()); + } + + $objectIdentityId = $name === 'classFieldAces' ? null : $ace->getAcl()->getId(); + + $this->connection->executeQuery($this->getInsertAccessControlEntrySql($classId, $objectIdentityId, $field, $i, $sid, $ace->getStrategy(), $ace->getMask(), $ace->isGranting(), $ace->isAuditSuccess(), $ace->isAuditFailure())); + $aceId = $this->connection->executeQuery($this->getSelectAccessControlEntryIdSql($classId, $objectIdentityId, $field, $i))->fetchColumn(); + $this->loadedAces[$aceId] = $ace; + + $aceIdProperty = new \ReflectionProperty('Symfony\Component\Security\Acl\Domain\Entry', 'id'); + $aceIdProperty->setAccessible(true); + $aceIdProperty->setValue($ace, intval($aceId)); + } else { + $currentIds[$ace->getId()] = true; + } + } + } + + foreach ($changes[0] as $old) { + for ($i=0,$c=count($old); $i<$c; $i++) { + $ace = $old[$i]; + + if (!isset($currentIds[$ace->getId()])) { + $this->connection->executeQuery($this->getDeleteAccessControlEntrySql($ace->getId())); + unset($this->loadedAces[$ace->getId()]); + } + } + } + } + + /** + * This processes changes on an ACE related property (classAces, or objectAces). + * + * @param string $name + * @param array $changes + * @return void + */ + private function updateAceProperty($name, array $changes) + { + list($old, $new) = $changes; + + $sids = new \SplObjectStorage(); + $classIds = new \SplObjectStorage(); + $currentIds = array(); + for ($i=0,$c=count($new); $i<$c; $i++) { + $ace = $new[$i]; + + if (null === $ace->getId()) { + if ($sids->contains($ace->getSecurityIdentity())) { + $sid = $sids->offsetGet($ace->getSecurityIdentity()); + } else { + $sid = $this->createOrRetrieveSecurityIdentityId($ace->getSecurityIdentity()); + } + + $oid = $ace->getAcl()->getObjectIdentity(); + if ($classIds->contains($oid)) { + $classId = $classIds->offsetGet($oid); + } else { + $classId = $this->createOrRetrieveClassId($oid->getType()); + } + + $objectIdentityId = $name === 'classAces' ? null : $ace->getAcl()->getId(); + + $this->connection->executeQuery($this->getInsertAccessControlEntrySql($classId, $objectIdentityId, null, $i, $sid, $ace->getStrategy(), $ace->getMask(), $ace->isGranting(), $ace->isAuditSuccess(), $ace->isAuditFailure())); + $aceId = $this->connection->executeQuery($this->getSelectAccessControlEntryIdSql($classId, $objectIdentityId, null, $i))->fetchColumn(); + $this->loadedAces[$aceId] = $ace; + + $aceIdProperty = new \ReflectionProperty($ace, 'id'); + $aceIdProperty->setAccessible(true); + $aceIdProperty->setValue($ace, intval($aceId)); + } else { + $currentIds[$ace->getId()] = true; + } + } + + for ($i=0,$c=count($old); $i<$c; $i++) { + $ace = $old[$i]; + + if (!isset($currentIds[$ace->getId()])) { + $this->connection->executeQuery($this->getDeleteAccessControlEntrySql($ace->getId())); + unset($this->loadedAces[$ace->getId()]); + } + } + } + + /** + * Persists the changes which were made to ACEs to the database. + * + * @param \SplObjectStorage $aces + * @return void + */ + private function updateAces(\SplObjectStorage $aces) + { + foreach ($aces as $ace) { + $propertyChanges = $aces->offsetGet($ace); + $sets = array(); + + if (isset($propertyChanges['mask'])) { + $sets[] = sprintf('mask = %d', $propertyChanges['mask'][1]); + } + if (isset($propertyChanges['strategy'])) { + $sets[] = sprintf('granting_strategy = %s', $this->connection->quote($propertyChanges['strategy'])); + } + if (isset($propertyChanges['aceOrder'])) { + $sets[] = sprintf('ace_order = %d', $propertyChanges['aceOrder'][1]); + } + if (isset($propertyChanges['auditSuccess'])) { + $sets[] = sprintf('audit_success = %s', $this->connection->getDatabasePlatform()->convertBooleans($propertyChanges['auditSuccess'][1])); + } + if (isset($propertyChanges['auditFailure'])) { + $sets[] = sprintf('audit_failure = %s', $this->connection->getDatabasePlatform()->convertBooleans($propertyChanges['auditFailure'][1])); + } + + $this->connection->executeQuery($this->getUpdateAccessControlEntrySql($ace->getId(), $sets)); + } + } +} \ No newline at end of file diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/DifferentNamespacesPerFileWithClassAsFirst.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/DifferentNamespacesPerFileWithClassAsFirst.php new file mode 100644 index 0000000..bda2cc2 --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/DifferentNamespacesPerFileWithClassAsFirst.php @@ -0,0 +1,15 @@ +test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test2() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test3() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test4() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test5() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test6() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test7() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test8() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + + } + + public function test9() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test10() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test11() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test12() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test13() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test14() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test15() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test16() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test17() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + + } + + public function test18() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test19() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test20() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test21() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test22() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test23() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test24() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test25() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test26() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test27() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + + } + + public function test28() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test29() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test30() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test31() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test32() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test33() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test34() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test35() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test36() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test37() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + + } + + public function test38() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test39() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } +} \ No newline at end of file diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/NoAnnotation.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/NoAnnotation.php new file mode 100644 index 0000000..1dae104 --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/NoAnnotation.php @@ -0,0 +1,5 @@ +test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test2() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test3() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test4() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test5() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test6() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test7() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test8() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + + } + + public function test9() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test10() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test11() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test12() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test13() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test14() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test15() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test16() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test17() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + + } + + public function test18() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test19() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test20() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test21() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test22() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test23() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test24() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test25() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test26() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test27() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + + } + + public function test28() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test29() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test30() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test31() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test32() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test33() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test34() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test35() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test36() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test37() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + + } + + public function test38() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } + + public function test39() + { + echo $this->test1; + echo $this->test2; + echo $this->test3; + $array = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + foreach ($array as $key => $value) { + echo $key . ' => ' . $value; + } + + $val = (string)self::TEST1; + $val .= (string)self::TEST2; + $val .= (string)self::TEST3; + $val .= (string)self::TEST4; + $val .= (string)self::TEST5; + $val .= (string)self::TEST6; + $val .= (string)self::TEST7; + $val .= (string)self::TEST8; + $val .= (string)self::TEST9; + + strtolower($val); + + return $val; + } +} \ No newline at end of file diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/TestInterface.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/TestInterface.php new file mode 100644 index 0000000..58c5e6a --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Fixtures/TestInterface.php @@ -0,0 +1,13 @@ +getMethod(); + + $time = microtime(true); + for ($i=0,$c=500; $i<$c; $i++) { + $reader->getMethodAnnotations($method); + } + $time = microtime(true) - $time; + + $this->printResults('cached reader (in-memory)', $time, $c); + } + + /** + * @group performance + */ + public function testCachedReadPerformanceWithFileCache() + { + $method = $this->getMethod(); + + // prime cache + $reader = new FileCacheReader(new AnnotationReader(), sys_get_temp_dir()); + $reader->getMethodAnnotations($method); + + $time = microtime(true); + for ($i=0,$c=500; $i<$c; $i++) { + $reader = new FileCacheReader(new AnnotationReader(), sys_get_temp_dir()); + $reader->getMethodAnnotations($method); + clearstatcache(); + } + $time = microtime(true) - $time; + + $this->printResults('cached reader (file)', $time, $c); + } + + /** + * @group performance + */ + public function testReadPerformance() + { + $method = $this->getMethod(); + + $time = microtime(true); + for ($i=0,$c=150; $i<$c; $i++) { + $reader = new AnnotationReader(); + $reader->getMethodAnnotations($method); + } + $time = microtime(true) - $time; + + $this->printResults('reader', $time, $c); + } + + /** + * @group performance + */ + public function testDocParsePerformance() + { + $imports = array( + 'ignorephpdoc' => 'Annotations\Annotation\IgnorePhpDoc', + 'ignoreannotation' => 'Annotations\Annotation\IgnoreAnnotation', + 'route' => 'Doctrine\Tests\Common\Annotations\Fixtures\Annotation\Route', + 'template' => 'Doctrine\Tests\Common\Annotations\Fixtures\Annotation\Template', + '__NAMESPACE__' => 'Doctrine\Tests\Common\Annotations\Fixtures', + ); + $ignored = array( + 'access', 'author', 'copyright', 'deprecated', 'example', 'ignore', + 'internal', 'link', 'see', 'since', 'tutorial', 'version', 'package', + 'subpackage', 'name', 'global', 'param', 'return', 'staticvar', + 'static', 'var', 'throws', 'inheritdoc', + ); + + $method = $this->getMethod(); + $methodComment = $method->getDocComment(); + $classComment = $method->getDeclaringClass()->getDocComment(); + + $time = microtime(true); + for ($i=0,$c=200; $i<$c; $i++) { + $parser = new DocParser(); + $parser->setImports($imports); + $parser->setIgnoredAnnotationNames($ignored); + $parser->setIgnoreNotImportedAnnotations(true); + + $parser->parse($methodComment); + $parser->parse($classComment); + } + $time = microtime(true) - $time; + + $this->printResults('doc-parser', $time, $c); + } + + /** + * @group performance + */ + public function testDocLexerPerformance() + { + $method = $this->getMethod(); + $methodComment = $method->getDocComment(); + $classComment = $method->getDeclaringClass()->getDocComment(); + + $time = microtime(true); + for ($i=0,$c=500; $i<$c; $i++) { + $lexer = new DocLexer(); + $lexer->setInput($methodComment); + $lexer->setInput($classComment); + } + $time = microtime(true) - $time; + + $this->printResults('doc-lexer', $time, $c); + } + + /** + * @group performance + */ + public function testPhpParserPerformanceWithShortCut() + { + $class = new \ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\NamespacedSingleClassLOC1000'); + + $time = microtime(true); + for ($i=0,$c=500; $i<$c; $i++) { + $parser = new PhpParser(); + $parser->parseClass($class); + } + $time = microtime(true) - $time; + + $this->printResults('doc-parser-with-short-cut', $time, $c); + } + + /** + * @group performance + */ + public function testPhpParserPerformanceWithoutShortCut() + { + $class = new \ReflectionClass('SingleClassLOC1000'); + + $time = microtime(true); + for ($i=0,$c=500; $i<$c; $i++) { + $parser = new PhpParser(); + $parser->parseClass($class); + } + $time = microtime(true) - $time; + + $this->printResults('doc-parser-without-short-cut', $time, $c); + } + + private function getMethod() + { + return new \ReflectionMethod('Doctrine\Tests\Common\Annotations\Fixtures\Controller', 'helloAction'); + } + + private function printResults($test, $time, $iterations) + { + if (0 == $iterations) { + throw new \InvalidArgumentException('$iterations cannot be zero.'); + } + + $title = $test." results:\n"; + $iterationsText = sprintf("Iterations: %d\n", $iterations); + $totalTime = sprintf("Total Time: %.3f s\n", $time); + $iterationTime = sprintf("Time per iteration: %.3f ms\n", $time/$iterations * 1000); + + $max = max(strlen($title), strlen($iterationTime)) - 1; + + echo "\n".str_repeat('-', $max)."\n"; + echo $title; + echo str_repeat('=', $max)."\n"; + echo $iterationsText; + echo $totalTime; + echo $iterationTime; + echo str_repeat('-', $max)."\n"; + } +} \ No newline at end of file diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/PhpParserTest.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/PhpParserTest.php new file mode 100644 index 0000000..dc01f8b --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/PhpParserTest.php @@ -0,0 +1,207 @@ +assertEquals(array( + 'route' => __NAMESPACE__ . '\Fixtures\Annotation\Route', + 'secure' => __NAMESPACE__ . '\Fixtures\Annotation\Secure', + ), $parser->parseClass($class)); + } + + public function testParseClassWithMultipleImportsInUseStatement() + { + $class = new ReflectionClass(__NAMESPACE__ . '\Fixtures\MultipleImportsInUseStatement'); + $parser = new PhpParser(); + + $this->assertEquals(array( + 'route' => __NAMESPACE__ . '\Fixtures\Annotation\Route', + 'secure' => __NAMESPACE__ . '\Fixtures\Annotation\Secure', + ), $parser->parseClass($class)); + } + + public function testParseClassWhenNotUserDefined() + { + $parser = new PhpParser(); + $this->assertEquals(array(), $parser->parseClass(new \ReflectionClass('\stdClass'))); + } + + public function testClassFileDoesNotExist() + { + $class = $this->getMockBuilder('\ReflectionClass') + ->disableOriginalConstructor() + ->getMock(); + $class->expects($this->once()) + ->method('getFilename') + ->will($this->returnValue('/valid/class/Fake.php(35) : eval()d code')); + + $parser = new PhpParser(); + $this->assertEquals(array(), $parser->parseClass($class)); + } + + public function testParseClassWhenClassIsNotNamespaced() + { + $parser = new PhpParser(); + $class = new ReflectionClass('\AnnotationsTestsFixturesNonNamespacedClass'); + + $this->assertEquals(array( + 'route' => __NAMESPACE__ . '\Fixtures\Annotation\Route', + 'template' => __NAMESPACE__ . '\Fixtures\Annotation\Template', + ), $parser->parseClass($class)); + } + + public function testParseClassWhenClassIsInterface() + { + $parser = new PhpParser(); + $class = new ReflectionClass(__NAMESPACE__ . '\Fixtures\TestInterface'); + + $this->assertEquals(array( + 'secure' => __NAMESPACE__ . '\Fixtures\Annotation\Secure', + ), $parser->parseClass($class)); + } + + public function testClassWithFullyQualifiedUseStatements() + { + $parser = new PhpParser(); + $class = new ReflectionClass(__NAMESPACE__ . '\Fixtures\ClassWithFullyQualifiedUseStatements'); + + $this->assertEquals(array( + 'secure' => '\\' . __NAMESPACE__ . '\Fixtures\Annotation\Secure', + 'route' => '\\' . __NAMESPACE__ . '\Fixtures\Annotation\Route', + 'template' => '\\' . __NAMESPACE__ . '\Fixtures\Annotation\Template', + ), $parser->parseClass($class)); + } + + public function testNamespaceAndClassCommentedOut() + { + $parser = new PhpParser(); + $class = new ReflectionClass(__NAMESPACE__ . '\Fixtures\NamespaceAndClassCommentedOut'); + + $this->assertEquals(array( + 'route' => __NAMESPACE__ . '\Fixtures\Annotation\Route', + 'template' => __NAMESPACE__ . '\Fixtures\Annotation\Template', + ), $parser->parseClass($class)); + } + + public function testEqualNamespacesPerFileWithClassAsFirst() + { + $parser = new PhpParser(); + $class = new ReflectionClass(__NAMESPACE__ . '\Fixtures\EqualNamespacesPerFileWithClassAsFirst'); + + $this->assertEquals(array( + 'secure' => __NAMESPACE__ . '\Fixtures\Annotation\Secure', + 'route' => __NAMESPACE__ . '\Fixtures\Annotation\Route', + ), $parser->parseClass($class)); + } + + public function testEqualNamespacesPerFileWithClassAsLast() + { + $parser = new PhpParser(); + $class = new ReflectionClass(__NAMESPACE__ . '\Fixtures\EqualNamespacesPerFileWithClassAsLast'); + + $this->assertEquals(array( + 'route' => __NAMESPACE__ . '\Fixtures\Annotation\Route', + 'template' => __NAMESPACE__ . '\Fixtures\Annotation\Template', + ), $parser->parseClass($class)); + } + + public function testDifferentNamespacesPerFileWithClassAsFirst() + { + $parser = new PhpParser(); + $class = new ReflectionClass(__NAMESPACE__ . '\Fixtures\DifferentNamespacesPerFileWithClassAsFirst'); + + $this->assertEquals(array( + 'secure' => __NAMESPACE__ . '\Fixtures\Annotation\Secure', + ), $parser->parseClass($class)); + } + + public function testDifferentNamespacesPerFileWithClassAsLast() + { + $parser = new PhpParser(); + $class = new ReflectionClass(__NAMESPACE__ . '\Fixtures\DifferentNamespacesPerFileWithClassAsLast'); + + $this->assertEquals(array( + 'template' => __NAMESPACE__ . '\Fixtures\Annotation\Template', + ), $parser->parseClass($class)); + } + + public function testGlobalNamespacesPerFileWithClassAsFirst() + { + $parser = new PhpParser(); + $class = new \ReflectionClass('\GlobalNamespacesPerFileWithClassAsFirst'); + + $this->assertEquals(array( + 'secure' => __NAMESPACE__ . '\Fixtures\Annotation\Secure', + 'route' => __NAMESPACE__ . '\Fixtures\Annotation\Route', + ), $parser->parseClass($class)); + } + + public function testGlobalNamespacesPerFileWithClassAsLast() + { + $parser = new PhpParser(); + $class = new ReflectionClass('\GlobalNamespacesPerFileWithClassAsLast'); + + $this->assertEquals(array( + 'route' => __NAMESPACE__ . '\Fixtures\Annotation\Route', + 'template' => __NAMESPACE__ . '\Fixtures\Annotation\Template', + ), $parser->parseClass($class)); + } + + public function testNamespaceWithClosureDeclaration() + { + $parser = new PhpParser(); + $class = new ReflectionClass(__NAMESPACE__ . '\Fixtures\NamespaceWithClosureDeclaration'); + + $this->assertEquals(array( + 'secure' => __NAMESPACE__ . '\Fixtures\Annotation\Secure', + 'route' => __NAMESPACE__ . '\Fixtures\Annotation\Route', + 'template' => __NAMESPACE__ . '\Fixtures\Annotation\Template', + ), $parser->parseClass($class)); + } + + public function testIfPointerResetsOnMultipleParsingTries() + { + $parser = new PhpParser(); + $class = new ReflectionClass(__NAMESPACE__ . '\Fixtures\NamespaceWithClosureDeclaration'); + + $this->assertEquals(array( + 'secure' => __NAMESPACE__ . '\Fixtures\Annotation\Secure', + 'route' => __NAMESPACE__ . '\Fixtures\Annotation\Route', + 'template' => __NAMESPACE__ . '\Fixtures\Annotation\Template', + ), $parser->parseClass($class)); + + $this->assertEquals(array( + 'secure' => __NAMESPACE__ . '\Fixtures\Annotation\Secure', + 'route' => __NAMESPACE__ . '\Fixtures\Annotation\Route', + 'template' => __NAMESPACE__ . '\Fixtures\Annotation\Template', + ), $parser->parseClass($class)); + } + + /** + * @group DCOM-97 + * @group regression + */ + public function testClassWithClosure() + { + $parser = new PhpParser(); + $class = new ReflectionClass(__NAMESPACE__ . '\Fixtures\ClassWithClosure'); + + $this->assertEquals(array( + 'annotationtargetall' => __NAMESPACE__ . '\Fixtures\AnnotationTargetAll', + 'annotationtargetannotation' => __NAMESPACE__ . '\Fixtures\AnnotationTargetAnnotation', + ), $parser->parseClass($class)); + } +} \ No newline at end of file diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/SimpleAnnotationReaderTest.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/SimpleAnnotationReaderTest.php new file mode 100644 index 0000000..376539f --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/SimpleAnnotationReaderTest.php @@ -0,0 +1,97 @@ +getReader(); + $class = new \ReflectionClass('Doctrine\Tests\Common\Annotations\Fixtures\ClassDDC1660'); + + $this->assertTrue(class_exists('Doctrine\Tests\Common\Annotations\Fixtures\Annotation\Version')); + $this->assertCount(1, $reader->getClassAnnotations($class)); + $this->assertCount(1, $reader->getMethodAnnotations($class->getMethod('bar'))); + $this->assertCount(1, $reader->getPropertyAnnotations($class->getProperty('foo'))); + } + + protected function getReader() + { + $reader = new SimpleAnnotationReader(); + $reader->addNamespace(__NAMESPACE__); + $reader->addNamespace(__NAMESPACE__ . '\Fixtures'); + $reader->addNamespace(__NAMESPACE__ . '\Fixtures\Annotation'); + + return $reader; + } +} \ No newline at end of file diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Ticket/DCOM55Test.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Ticket/DCOM55Test.php new file mode 100644 index 0000000..a7b9e2f --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Ticket/DCOM55Test.php @@ -0,0 +1,65 @@ +getClassAnnotations($class); + } + + public function testAnnotation() + { + $class = new \ReflectionClass(__NAMESPACE__ . '\\DCOM55Consumer'); + $reader = new \Doctrine\Common\Annotations\AnnotationReader(); + $annots = $reader->getClassAnnotations($class); + + $this->assertEquals(1, count($annots)); + $this->assertInstanceOf(__NAMESPACE__.'\\DCOM55Annotation', $annots[0]); + } + + public function testParseAnnotationDocblocks() + { + $class = new \ReflectionClass(__NAMESPACE__ . '\\DCOM55Annotation'); + $reader = new \Doctrine\Common\Annotations\AnnotationReader(); + $annots = $reader->getClassAnnotations($class); + + $this->assertEquals(0, count($annots)); + } +} + +/** + * @Controller + */ +class Dummy +{ + +} + +/** + * @Annotation + */ +class DCOM55Annotation +{ + +} + +/** + * @DCOM55Annotation + */ +class DCOM55Consumer +{ + +} \ No newline at end of file diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Ticket/DCOM58Entity.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Ticket/DCOM58Entity.php new file mode 100644 index 0000000..708bcc9 --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/Ticket/DCOM58Entity.php @@ -0,0 +1,8 @@ +getClassAnnotations(new \ReflectionClass(__NAMESPACE__."\MappedClass")); + + foreach ($result as $annot) { + $classAnnotations[get_class($annot)] = $annot; + } + + $this->assertTrue(!isset($classAnnotations['']), 'Class "xxx" is not a valid entity or mapped super class.'); + } + + public function testIssueGlobalNamespace() + { + $docblock = "@Entity"; + $parser = new \Doctrine\Common\Annotations\DocParser(); + $parser->setImports(array( + "__NAMESPACE__" =>"Doctrine\Tests\Common\Annotations\Ticket\Doctrine\ORM\Mapping" + )); + + $annots = $parser->parse($docblock); + + $this->assertEquals(1, count($annots)); + $this->assertInstanceOf("Doctrine\Tests\Common\Annotations\Ticket\Doctrine\ORM\Mapping\Entity", $annots[0]); + } + + public function testIssueNamespaces() + { + $docblock = "@Entity"; + $parser = new \Doctrine\Common\Annotations\DocParser(); + $parser->addNamespace("Doctrine\Tests\Common\Annotations\Ticket\Doctrine\ORM"); + + $annots = $parser->parse($docblock); + + $this->assertEquals(1, count($annots)); + $this->assertInstanceOf("Doctrine\Tests\Common\Annotations\Ticket\Doctrine\ORM\Entity", $annots[0]); + } + + public function testIssueMultipleNamespaces() + { + $docblock = "@Entity"; + $parser = new \Doctrine\Common\Annotations\DocParser(); + $parser->addNamespace("Doctrine\Tests\Common\Annotations\Ticket\Doctrine\ORM\Mapping"); + $parser->addNamespace("Doctrine\Tests\Common\Annotations\Ticket\Doctrine\ORM"); + + $annots = $parser->parse($docblock); + + $this->assertEquals(1, count($annots)); + $this->assertInstanceOf("Doctrine\Tests\Common\Annotations\Ticket\Doctrine\ORM\Mapping\Entity", $annots[0]); + } + + public function testIssueWithNamespacesOrImports() + { + $docblock = "@Entity"; + $parser = new \Doctrine\Common\Annotations\DocParser(); + $annots = $parser->parse($docblock); + + $this->assertEquals(1, count($annots)); + $this->assertInstanceOf("Entity", $annots[0]); + $this->assertEquals(1, count($annots)); + } + + + public function testIssueSimpleAnnotationReader() + { + $reader = new \Doctrine\Common\Annotations\SimpleAnnotationReader(); + $reader->addNamespace('Doctrine\Tests\Common\Annotations\Ticket\Doctrine\ORM\Mapping'); + $annots = $reader->getClassAnnotations(new \ReflectionClass(__NAMESPACE__."\MappedClass")); + + $this->assertEquals(1, count($annots)); + $this->assertInstanceOf("Doctrine\Tests\Common\Annotations\Ticket\Doctrine\ORM\Mapping\Entity", $annots[0]); + } + +} + +/** + * @Entity + */ +class MappedClass +{ + +} + + +namespace Doctrine\Tests\Common\Annotations\Ticket\Doctrine\ORM\Mapping; +/** +* @Annotation +*/ +class Entity +{ + +} + +namespace Doctrine\Tests\Common\Annotations\Ticket\Doctrine\ORM; +/** +* @Annotation +*/ +class Entity +{ + +} diff --git a/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/TopLevelAnnotation.php b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/TopLevelAnnotation.php new file mode 100644 index 0000000..ff3ca37 --- /dev/null +++ b/vendor/doctrine/annotations/tests/Doctrine/Tests/Common/Annotations/TopLevelAnnotation.php @@ -0,0 +1,8 @@ +=5.3.2" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "autoload": { + "psr-0": { "Doctrine\\Common\\Cache\\": "lib/" } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php new file mode 100644 index 0000000..680440b --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ApcCache.php @@ -0,0 +1,91 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * APC cache provider. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class ApcCache extends CacheProvider +{ + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return apc_fetch($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return apc_exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return (bool) apc_store($id, $data, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return apc_delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return apc_clear_cache() && apc_clear_cache('user'); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $info = apc_cache_info(); + $sma = apc_sma_info(); + + return array( + Cache::STATS_HITS => $info['num_hits'], + Cache::STATS_MISSES => $info['num_misses'], + Cache::STATS_UPTIME => $info['start_time'], + Cache::STATS_MEMORY_USAGE => $info['mem_size'], + Cache::STATS_MEMORY_AVAILABLE => $sma['avail_mem'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php new file mode 100644 index 0000000..e9f08a2 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ArrayCache.php @@ -0,0 +1,93 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Array cache driver. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class ArrayCache extends CacheProvider +{ + /** + * @var array $data + */ + private $data = array(); + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return (isset($this->data[$id])) ? $this->data[$id] : false; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return isset($this->data[$id]); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + $this->data[$id] = $data; + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + unset($this->data[$id]); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + $this->data = array(); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + return null; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php new file mode 100644 index 0000000..0785f26 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/Cache.php @@ -0,0 +1,111 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Interface for cache drivers. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Fabio B. Silva + */ +interface Cache +{ + const STATS_HITS = 'hits'; + const STATS_MISSES = 'misses'; + const STATS_UPTIME = 'uptime'; + const STATS_MEMORY_USAGE = 'memory_usage'; + const STATS_MEMORY_AVAILABLE = 'memory_available'; + /** + * Only for backward compatibility (may be removed in next major release) + * + * @deprecated + */ + const STATS_MEMORY_AVAILIABLE = 'memory_available'; + + /** + * Fetches an entry from the cache. + * + * @param string $id The id of the cache entry to fetch. + * + * @return mixed The cached data or FALSE, if no cache entry exists for the given id. + */ + function fetch($id); + + /** + * Tests if an entry exists in the cache. + * + * @param string $id The cache id of the entry to check for. + * + * @return boolean TRUE if a cache entry exists for the given cache id, FALSE otherwise. + */ + function contains($id); + + /** + * Puts data into the cache. + * + * @param string $id The cache id. + * @param mixed $data The cache entry/data. + * @param int $lifeTime The cache lifetime. + * If != 0, sets a specific lifetime for this cache entry (0 => infinite lifeTime). + * + * @return boolean TRUE if the entry was successfully stored in the cache, FALSE otherwise. + */ + function save($id, $data, $lifeTime = 0); + + /** + * Deletes a cache entry. + * + * @param string $id The cache id. + * + * @return boolean TRUE if the cache entry was successfully deleted, FALSE otherwise. + */ + function delete($id); + + /** + * Retrieves cached information from the data store. + * + * The server's statistics array has the following values: + * + * - hits + * Number of keys that have been requested and found present. + * + * - misses + * Number of items that have been requested and not found. + * + * - uptime + * Time that the server is running. + * + * - memory_usage + * Memory used by this server to store items. + * + * - memory_available + * Memory allowed to use for storage. + * + * @since 2.2 + * + * @return array|null An associative array with server's statistics if available, NULL otherwise. + */ + function getStats(); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php new file mode 100644 index 0000000..45d9acb --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CacheProvider.php @@ -0,0 +1,240 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Base class for cache provider implementations. + * + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Fabio B. Silva + */ +abstract class CacheProvider implements Cache +{ + const DOCTRINE_NAMESPACE_CACHEKEY = 'DoctrineNamespaceCacheKey[%s]'; + + /** + * The namespace to prefix all cache ids with. + * + * @var string + */ + private $namespace = ''; + + /** + * The namespace version. + * + * @var string + */ + private $namespaceVersion; + + /** + * Sets the namespace to prefix all cache ids with. + * + * @param string $namespace + * + * @return void + */ + public function setNamespace($namespace) + { + $this->namespace = (string) $namespace; + } + + /** + * Retrieves the namespace that prefixes all cache ids. + * + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * {@inheritdoc} + */ + public function fetch($id) + { + return $this->doFetch($this->getNamespacedId($id)); + } + + /** + * {@inheritdoc} + */ + public function contains($id) + { + return $this->doContains($this->getNamespacedId($id)); + } + + /** + * {@inheritdoc} + */ + public function save($id, $data, $lifeTime = 0) + { + return $this->doSave($this->getNamespacedId($id), $data, $lifeTime); + } + + /** + * {@inheritdoc} + */ + public function delete($id) + { + return $this->doDelete($this->getNamespacedId($id)); + } + + /** + * {@inheritdoc} + */ + public function getStats() + { + return $this->doGetStats(); + } + + /** + * Flushes all cache entries. + * + * @return boolean TRUE if the cache entries were successfully flushed, FALSE otherwise. + */ + public function flushAll() + { + return $this->doFlush(); + } + + /** + * Deletes all cache entries. + * + * @return boolean TRUE if the cache entries were successfully deleted, FALSE otherwise. + */ + public function deleteAll() + { + $namespaceCacheKey = $this->getNamespaceCacheKey(); + $namespaceVersion = $this->getNamespaceVersion() + 1; + + $this->namespaceVersion = $namespaceVersion; + + return $this->doSave($namespaceCacheKey, $namespaceVersion); + } + + /** + * Prefixes the passed id with the configured namespace value. + * + * @param string $id The id to namespace. + * + * @return string The namespaced id. + */ + private function getNamespacedId($id) + { + $namespaceVersion = $this->getNamespaceVersion(); + + return sprintf('%s[%s][%s]', $this->namespace, $id, $namespaceVersion); + } + + /** + * Returns the namespace cache key. + * + * @return string + */ + private function getNamespaceCacheKey() + { + return sprintf(self::DOCTRINE_NAMESPACE_CACHEKEY, $this->namespace); + } + + /** + * Returns the namespace version. + * + * @return string + */ + private function getNamespaceVersion() + { + if (null !== $this->namespaceVersion) { + return $this->namespaceVersion; + } + + $namespaceCacheKey = $this->getNamespaceCacheKey(); + $namespaceVersion = $this->doFetch($namespaceCacheKey); + + if (false === $namespaceVersion) { + $namespaceVersion = 1; + + $this->doSave($namespaceCacheKey, $namespaceVersion); + } + + $this->namespaceVersion = $namespaceVersion; + + return $this->namespaceVersion; + } + + /** + * Fetches an entry from the cache. + * + * @param string $id The id of the cache entry to fetch. + * + * @return string|bool The cached data or FALSE, if no cache entry exists for the given id. + */ + abstract protected function doFetch($id); + + /** + * Tests if an entry exists in the cache. + * + * @param string $id The cache id of the entry to check for. + * + * @return boolean TRUE if a cache entry exists for the given cache id, FALSE otherwise. + */ + abstract protected function doContains($id); + + /** + * Puts data into the cache. + * + * @param string $id The cache id. + * @param string $data The cache entry/data. + * @param int $lifeTime The lifetime. If != 0, sets a specific lifetime for this + * cache entry (0 => infinite lifeTime). + * + * @return boolean TRUE if the entry was successfully stored in the cache, FALSE otherwise. + */ + abstract protected function doSave($id, $data, $lifeTime = 0); + + /** + * Deletes a cache entry. + * + * @param string $id The cache id. + * + * @return boolean TRUE if the cache entry was successfully deleted, FALSE otherwise. + */ + abstract protected function doDelete($id); + + /** + * Flushes all cache entries. + * + * @return boolean TRUE if the cache entry was successfully deleted, FALSE otherwise. + */ + abstract protected function doFlush(); + + /** + * Retrieves cached information from the data store. + * + * @since 2.2 + * + * @return array|null An associative array with server's statistics if available, NULL otherwise. + */ + abstract protected function doGetStats(); +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php new file mode 100644 index 0000000..c21691d --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/CouchbaseCache.php @@ -0,0 +1,121 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use \Couchbase; + +/** + * Couchbase cache provider. + * + * @link www.doctrine-project.org + * @since 2.4 + * @author Michael Nitschinger + */ +class CouchbaseCache extends CacheProvider +{ + /** + * @var Couchbase|null + */ + private $couchbase; + + /** + * Sets the Couchbase instance to use. + * + * @param Couchbase $couchbase + * + * @return void + */ + public function setCouchbase(Couchbase $couchbase) + { + $this->couchbase = $couchbase; + } + + /** + * Gets the Couchbase instance used by the cache. + * + * @return Couchbase|null + */ + public function getCouchbase() + { + return $this->couchbase; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->couchbase->get($id) ?: false; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return (null !== $this->couchbase->get($id)); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 30 * 24 * 3600) { + $lifeTime = time() + $lifeTime; + } + return $this->couchbase->set($id, $data, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->couchbase->delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->couchbase->flush(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $stats = $this->couchbase->getStats(); + $servers = $this->couchbase->getServers(); + $server = explode(":", $servers[0]); + $key = $server[0] . ":" . "11210"; + $stats = $stats[$key]; + return array( + Cache::STATS_HITS => $stats['get_hits'], + Cache::STATS_MISSES => $stats['get_misses'], + Cache::STATS_UPTIME => $stats['uptime'], + Cache::STATS_MEMORY_USAGE => $stats['bytes'], + Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php new file mode 100644 index 0000000..1aa4d79 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FileCache.php @@ -0,0 +1,158 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Base file cache driver. + * + * @since 2.3 + * @author Fabio B. Silva + */ +abstract class FileCache extends CacheProvider +{ + /** + * The cache directory. + * + * @var string + */ + protected $directory; + + /** + * The cache file extension. + * + * @var string|null + */ + protected $extension; + + /** + * Constructor. + * + * @param string $directory The cache directory. + * @param string|null $extension The cache file extension. + * + * @throws \InvalidArgumentException + */ + public function __construct($directory, $extension = null) + { + if ( ! is_dir($directory) && ! @mkdir($directory, 0777, true)) { + throw new \InvalidArgumentException(sprintf( + 'The directory "%s" does not exist and could not be created.', + $directory + )); + } + + if ( ! is_writable($directory)) { + throw new \InvalidArgumentException(sprintf( + 'The directory "%s" is not writable.', + $directory + )); + } + + $this->directory = realpath($directory); + $this->extension = $extension ?: $this->extension; + } + + /** + * Gets the cache directory. + * + * @return string + */ + public function getDirectory() + { + return $this->directory; + } + + /** + * Gets the cache file extension. + * + * @return string|null + */ + public function getExtension() + { + return $this->extension; + } + + /** + * @param string $id + * + * @return string + */ + protected function getFilename($id) + { + $hash = hash('sha256', $id); + $path = implode(str_split($hash, 16), DIRECTORY_SEPARATOR); + $path = $this->directory . DIRECTORY_SEPARATOR . $path; + $id = preg_replace('@[\\\/:"*?<>|]+@', '', $id); + + return $path . DIRECTORY_SEPARATOR . $id . $this->extension; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return @unlink($this->getFilename($id)); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + foreach ($this->getIterator() as $name => $file) { + @unlink($name); + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $usage = 0; + foreach ($this->getIterator() as $name => $file) { + $usage += $file->getSize(); + } + + $free = disk_free_space($this->directory); + + return array( + Cache::STATS_HITS => null, + Cache::STATS_MISSES => null, + Cache::STATS_UPTIME => null, + Cache::STATS_MEMORY_USAGE => $usage, + Cache::STATS_MEMORY_AVAILABLE => $free, + ); + } + + /** + * @return \Iterator + */ + private function getIterator() + { + $pattern = '/^.+\\' . $this->extension . '$/i'; + $iterator = new \RecursiveDirectoryIterator($this->directory); + $iterator = new \RecursiveIteratorIterator($iterator); + return new \RegexIterator($iterator, $pattern); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php new file mode 100644 index 0000000..5c5a46e --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/FilesystemCache.php @@ -0,0 +1,113 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Filesystem cache driver. + * + * @since 2.3 + * @author Fabio B. Silva + */ +class FilesystemCache extends FileCache +{ + const EXTENSION = '.doctrinecache.data'; + + /** + * {@inheritdoc} + */ + protected $extension = self::EXTENSION; + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $data = ''; + $lifetime = -1; + $filename = $this->getFilename($id); + + if ( ! is_file($filename)) { + return false; + } + + $resource = fopen($filename, "r"); + + if (false !== ($line = fgets($resource))) { + $lifetime = (integer) $line; + } + + if ($lifetime !== 0 && $lifetime < time()) { + fclose($resource); + + return false; + } + + while (false !== ($line = fgets($resource))) { + $data .= $line; + } + + fclose($resource); + + return unserialize($data); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $lifetime = -1; + $filename = $this->getFilename($id); + + if ( ! is_file($filename)) { + return false; + } + + $resource = fopen($filename, "r"); + + if (false !== ($line = fgets($resource))) { + $lifetime = (integer) $line; + } + + fclose($resource); + + return $lifetime === 0 || $lifetime > time(); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 0) { + $lifeTime = time() + $lifeTime; + } + + $data = serialize($data); + $filename = $this->getFilename($id); + $filepath = pathinfo($filename, PATHINFO_DIRNAME); + + if ( ! is_dir($filepath)) { + mkdir($filepath, 0777, true); + } + + return file_put_contents($filename, $lifeTime . PHP_EOL . $data); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php new file mode 100644 index 0000000..f839a65 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcacheCache.php @@ -0,0 +1,121 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use \Memcache; + +/** + * Memcache cache provider. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class MemcacheCache extends CacheProvider +{ + /** + * @var Memcache|null + */ + private $memcache; + + /** + * Sets the memcache instance to use. + * + * @param Memcache $memcache + * + * @return void + */ + public function setMemcache(Memcache $memcache) + { + $this->memcache = $memcache; + } + + /** + * Gets the memcache instance used by the cache. + * + * @return Memcache|null + */ + public function getMemcache() + { + return $this->memcache; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->memcache->get($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return (bool) $this->memcache->get($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 30 * 24 * 3600) { + $lifeTime = time() + $lifeTime; + } + return $this->memcache->set($id, $data, 0, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->memcache->delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->memcache->flush(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $stats = $this->memcache->getStats(); + return array( + Cache::STATS_HITS => $stats['get_hits'], + Cache::STATS_MISSES => $stats['get_misses'], + Cache::STATS_UPTIME => $stats['uptime'], + Cache::STATS_MEMORY_USAGE => $stats['bytes'], + Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php new file mode 100644 index 0000000..f7e5500 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/MemcachedCache.php @@ -0,0 +1,124 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use \Memcached; + +/** + * Memcached cache provider. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class MemcachedCache extends CacheProvider +{ + /** + * @var Memcached|null + */ + private $memcached; + + /** + * Sets the memcache instance to use. + * + * @param Memcached $memcached + * + * @return void + */ + public function setMemcached(Memcached $memcached) + { + $this->memcached = $memcached; + } + + /** + * Gets the memcached instance used by the cache. + * + * @return Memcached|null + */ + public function getMemcached() + { + return $this->memcached; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->memcached->get($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return (false !== $this->memcached->get($id)); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 30 * 24 * 3600) { + $lifeTime = time() + $lifeTime; + } + return $this->memcached->set($id, $data, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->memcached->delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->memcached->flush(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $stats = $this->memcached->getStats(); + $servers = $this->memcached->getServerList(); + $key = $servers[0]['host'] . ':' . $servers[0]['port']; + $stats = $stats[$key]; + return array( + Cache::STATS_HITS => $stats['get_hits'], + Cache::STATS_MISSES => $stats['get_misses'], + Cache::STATS_UPTIME => $stats['uptime'], + Cache::STATS_MEMORY_USAGE => $stats['bytes'], + Cache::STATS_MEMORY_AVAILABLE => $stats['limit_maxbytes'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php new file mode 100644 index 0000000..83c6b52 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/PhpFileCache.php @@ -0,0 +1,107 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Php file cache driver. + * + * @since 2.3 + * @author Fabio B. Silva + */ +class PhpFileCache extends FileCache +{ + const EXTENSION = '.doctrinecache.php'; + + /** + * {@inheritdoc} + */ + protected $extension = self::EXTENSION; + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + $filename = $this->getFilename($id); + + if ( ! is_file($filename)) { + return false; + } + + $value = include $filename; + + if ($value['lifetime'] !== 0 && $value['lifetime'] < time()) { + return false; + } + + return $value['data']; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + $filename = $this->getFilename($id); + + if ( ! is_file($filename)) { + return false; + } + + $value = include $filename; + + return $value['lifetime'] === 0 || $value['lifetime'] > time(); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 0) { + $lifeTime = time() + $lifeTime; + } + + if (is_object($data) && ! method_exists($data, '__set_state')) { + throw new \InvalidArgumentException( + "Invalid argument given, PhpFileCache only allows objects that implement __set_state() " . + "and fully support var_export(). You can use the FilesystemCache to save arbitrary object " . + "graphs using serialize()/deserialize()." + ); + } + + $filename = $this->getFilename($id); + $filepath = pathinfo($filename, PATHINFO_DIRNAME); + + if ( ! is_dir($filepath)) { + mkdir($filepath, 0777, true); + } + + $value = array( + 'lifetime' => $lifeTime, + 'data' => $data + ); + + $value = var_export($value, true); + $code = sprintf('. + */ + +namespace Doctrine\Common\Cache; + +use Redis; + +/** + * Redis cache provider. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Osman Ungur + */ +class RedisCache extends CacheProvider +{ + /** + * @var Redis|null + */ + private $redis; + + /** + * Sets the redis instance to use. + * + * @param Redis $redis + * + * @return void + */ + public function setRedis(Redis $redis) + { + $redis->setOption(Redis::OPT_SERIALIZER, $this->getSerializerValue()); + $this->redis = $redis; + } + + /** + * Gets the redis instance used by the cache. + * + * @return Redis|null + */ + public function getRedis() + { + return $this->redis; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->redis->get($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return $this->redis->exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + if ($lifeTime > 0) { + return $this->redis->setex($id, $lifeTime, $data); + } + return $this->redis->set($id, $data); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return $this->redis->delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return $this->redis->flushDB(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $info = $this->redis->info(); + return array( + Cache::STATS_HITS => false, + Cache::STATS_MISSES => false, + Cache::STATS_UPTIME => $info['uptime_in_seconds'], + Cache::STATS_MEMORY_USAGE => $info['used_memory'], + Cache::STATS_MEMORY_AVAILABLE => false + ); + } + + /** + * Returns the serializer constant to use. If Redis is compiled with + * igbinary support, that is used. Otherwise the default PHP serializer is + * used. + * + * @return integer One of the Redis::SERIALIZER_* constants + */ + protected function getSerializerValue() + { + return defined('Redis::SERIALIZER_IGBINARY') ? Redis::SERIALIZER_IGBINARY : Redis::SERIALIZER_PHP; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php new file mode 100644 index 0000000..b8dbfd5 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/RiakCache.php @@ -0,0 +1,250 @@ +. + */ + +namespace Doctrine\Common\Cache; + +use Riak\Bucket; +use Riak\Connection; +use Riak\Input; +use Riak\Exception; +use Riak\Object; + +/** + * Riak cache provider. + * + * @link www.doctrine-project.org + * @since 1.1 + * @author Guilherme Blanco + */ +class RiakCache extends CacheProvider +{ + const EXPIRES_HEADER = 'X-Riak-Meta-Expires'; + + /** + * @var \Riak\Bucket + */ + private $bucket; + + /** + * Sets the riak bucket instance to use. + * + * @param \Riak\Bucket $bucket + */ + public function __construct(Bucket $bucket) + { + $this->bucket = $bucket; + } + + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + try { + $response = $this->bucket->get(urlencode($id)); + + // No objects found + if ( ! $response->hasObject()) { + return false; + } + + // Check for attempted siblings + $object = ($response->hasSiblings()) + ? $this->resolveConflict($id, $response->getVClock(), $response->getObjectList()) + : $response->getFirstObject(); + + // Check for expired object + if ($this->isExpired($object)) { + $this->bucket->delete($object); + + return false; + } + + return unserialize($object->getContent()); + } catch (Exception\RiakException $e) { + // Covers: + // - Riak\ConnectionException + // - Riak\CommunicationException + // - Riak\UnexpectedResponseException + // - Riak\NotFoundException + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + try { + // We only need the HEAD, not the entire object + $input = new Input\GetInput(); + + $input->setReturnHead(true); + + $response = $this->bucket->get(urlencode($id), $input); + + // No objects found + if ( ! $response->hasObject()) { + return false; + } + + $object = $response->getFirstObject(); + + // Check for expired object + if ($this->isExpired($object)) { + $this->bucket->delete($object); + + return false; + } + + return true; + } catch (Exception\RiakException $e) { + // Do nothing + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + try { + $object = new Object(urlencode($id)); + + $object->setContent(serialize($data)); + + if ($lifeTime > 0) { + $object->addMetadata(self::EXPIRES_HEADER, (string) (time() + $lifeTime)); + } + + $this->bucket->put($object); + + return true; + } catch (Exception\RiakException $e) { + // Do nothing + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + try { + $this->bucket->delete(urlencode($id)); + + return true; + } catch (Exception\BadArgumentsException $e) { + // Key did not exist on cluster already + } catch (Exception\RiakException $e) { + // Covers: + // - Riak\Exception\ConnectionException + // - Riak\Exception\CommunicationException + // - Riak\Exception\UnexpectedResponseException + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + try { + $keyList = $this->bucket->getKeyList(); + + foreach ($keyList as $key) { + $this->bucket->delete($key); + } + + return true; + } catch (Exception\RiakException $e) { + // Do nothing + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + // Only exposed through HTTP stats API, not Protocol Buffers API + return null; + } + + /** + * Check if a given Riak Object have expired. + * + * @param \Riak\Object $object + * + * @return boolean + */ + private function isExpired(Object $object) + { + $metadataMap = $object->getMetadataMap(); + + return isset($metadataMap[self::EXPIRES_HEADER]) + && $metadataMap[self::EXPIRES_HEADER] < time(); + } + + /** + * On-read conflict resolution. Applied approach here is last write wins. + * Specific needs may override this method to apply alternate conflict resolutions. + * + * {@internal Riak does not attempt to resolve a write conflict, and store + * it as sibling of conflicted one. By following this approach, it is up to + * the next read to resolve the conflict. When this happens, your fetched + * object will have a list of siblings (read as a list of objects). + * In our specific case, we do not care about the intermediate ones since + * they are all the same read from storage, and we do apply a last sibling + * (last write) wins logic. + * If by any means our resolution generates another conflict, it'll up to + * next read to properly solve it.} + * + * @param string $id + * @param string $vClock + * @param array $objectList + * + * @return \Riak\Object + */ + protected function resolveConflict($id, $vClock, array $objectList) + { + // Our approach here is last-write wins + $winner = $objectList[count($objectList)]; + + $putInput = new Input\PutInput(); + $putInput->setVClock($vClock); + + $mergedObject = new Object(urlencode($id)); + $mergedObject->setContent($winner->getContent()); + + $this->bucket->put($mergedObject, $putInput); + + return $mergedObject; + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php new file mode 100644 index 0000000..ae32772 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/WinCacheCache.php @@ -0,0 +1,91 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * WinCache cache provider. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class WinCacheCache extends CacheProvider +{ + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return wincache_ucache_get($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return wincache_ucache_exists($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return (bool) wincache_ucache_set($id, $data, (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return wincache_ucache_delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + return wincache_ucache_clear(); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $info = wincache_ucache_info(); + $meminfo = wincache_ucache_meminfo(); + + return array( + Cache::STATS_HITS => $info['total_hit_count'], + Cache::STATS_MISSES => $info['total_miss_count'], + Cache::STATS_UPTIME => $info['total_cache_uptime'], + Cache::STATS_MEMORY_USAGE => $meminfo['memory_total'], + Cache::STATS_MEMORY_AVAILABLE => $meminfo['memory_free'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php new file mode 100644 index 0000000..833b02a --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/XcacheCache.php @@ -0,0 +1,109 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Xcache cache driver. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author David Abdemoulaie + */ +class XcacheCache extends CacheProvider +{ + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return $this->doContains($id) ? unserialize(xcache_get($id)) : false; + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return xcache_isset($id); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return xcache_set($id, serialize($data), (int) $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return xcache_unset($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + $this->checkAuthorization(); + + xcache_clear_cache(XC_TYPE_VAR, 0); + + return true; + } + + /** + * Checks that xcache.admin.enable_auth is Off. + * + * @return void + * + * @throws \BadMethodCallException When xcache.admin.enable_auth is On. + */ + protected function checkAuthorization() + { + if (ini_get('xcache.admin.enable_auth')) { + throw new \BadMethodCallException('To use all features of \Doctrine\Common\Cache\XcacheCache, you must set "xcache.admin.enable_auth" to "Off" in your php.ini.'); + } + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + $this->checkAuthorization(); + + $info = xcache_info(XC_TYPE_VAR, 0); + return array( + Cache::STATS_HITS => $info['hits'], + Cache::STATS_MISSES => $info['misses'], + Cache::STATS_UPTIME => null, + Cache::STATS_MEMORY_USAGE => $info['size'], + Cache::STATS_MEMORY_AVAILABLE => $info['avail'], + ); + } +} diff --git a/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php new file mode 100644 index 0000000..6e35ac8 --- /dev/null +++ b/vendor/doctrine/cache/lib/Doctrine/Common/Cache/ZendDataCache.php @@ -0,0 +1,83 @@ +. + */ + +namespace Doctrine\Common\Cache; + +/** + * Zend Data Cache cache driver. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Ralph Schindler + * @author Guilherme Blanco + */ +class ZendDataCache extends CacheProvider +{ + /** + * {@inheritdoc} + */ + protected function doFetch($id) + { + return zend_shm_cache_fetch($id); + } + + /** + * {@inheritdoc} + */ + protected function doContains($id) + { + return (false !== zend_shm_cache_fetch($id)); + } + + /** + * {@inheritdoc} + */ + protected function doSave($id, $data, $lifeTime = 0) + { + return zend_shm_cache_store($id, $data, $lifeTime); + } + + /** + * {@inheritdoc} + */ + protected function doDelete($id) + { + return zend_shm_cache_delete($id); + } + + /** + * {@inheritdoc} + */ + protected function doFlush() + { + $namespace = $this->getNamespace(); + if (empty($namespace)) { + return zend_shm_cache_clear(); + } + return zend_shm_cache_clear($namespace); + } + + /** + * {@inheritdoc} + */ + protected function doGetStats() + { + return null; + } +} diff --git a/vendor/doctrine/cache/phpunit.xml.dist b/vendor/doctrine/cache/phpunit.xml.dist new file mode 100644 index 0000000..900378b --- /dev/null +++ b/vendor/doctrine/cache/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + ./tests/Doctrine/ + + + + + + ./lib/Doctrine/ + + + + + + performance + + + diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ApcCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ApcCacheTest.php new file mode 100644 index 0000000..df81262 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ApcCacheTest.php @@ -0,0 +1,20 @@ +markTestSkipped('The ' . __CLASS__ .' requires the use of APC'); + } + } + + protected function _getCacheDriver() + { + return new ApcCache(); + } +} \ No newline at end of file diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ArrayCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ArrayCacheTest.php new file mode 100644 index 0000000..6cad891 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ArrayCacheTest.php @@ -0,0 +1,21 @@ +_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertNull($stats); + } +} \ No newline at end of file diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CacheTest.php new file mode 100644 index 0000000..8d0e16e --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CacheTest.php @@ -0,0 +1,103 @@ +_getCacheDriver(); + + // Test save + $cache->save('test_key', 'testing this out'); + + // Test contains to test that save() worked + $this->assertTrue($cache->contains('test_key')); + + // Test fetch + $this->assertEquals('testing this out', $cache->fetch('test_key')); + + // Test delete + $cache->save('test_key2', 'test2'); + $cache->delete('test_key2'); + $this->assertFalse($cache->contains('test_key2')); + } + + public function testObjects() + { + $cache = $this->_getCacheDriver(); + + // Fetch/save test with objects (Is cache driver serializes/unserializes objects correctly ?) + $cache->save('test_object_key', new \ArrayObject()); + $this->assertTrue($cache->fetch('test_object_key') instanceof \ArrayObject); + } + + public function testDeleteAll() + { + $cache = $this->_getCacheDriver(); + $cache->save('test_key1', '1'); + $cache->save('test_key2', '2'); + $cache->deleteAll(); + + $this->assertFalse($cache->contains('test_key1')); + $this->assertFalse($cache->contains('test_key2')); + } + + public function testFlushAll() + { + $cache = $this->_getCacheDriver(); + $cache->save('test_key1', '1'); + $cache->save('test_key2', '2'); + $cache->flushAll(); + + $this->assertFalse($cache->contains('test_key1')); + $this->assertFalse($cache->contains('test_key2')); + } + + public function testNamespace() + { + $cache = $this->_getCacheDriver(); + $cache->setNamespace('test_'); + $cache->save('key1', 'test'); + + $this->assertTrue($cache->contains('key1')); + + $cache->setNamespace('test2_'); + + $this->assertFalse($cache->contains('key1')); + } + + /** + * @group DCOM-43 + */ + public function testGetStats() + { + $cache = $this->_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertArrayHasKey(Cache::STATS_HITS, $stats); + $this->assertArrayHasKey(Cache::STATS_MISSES, $stats); + $this->assertArrayHasKey(Cache::STATS_UPTIME, $stats); + $this->assertArrayHasKey(Cache::STATS_MEMORY_USAGE, $stats); + $this->assertArrayHasKey(Cache::STATS_MEMORY_AVAILABLE, $stats); + } + + /** + * Make sure that all supported caches return "false" instead of "null" to be compatible + * with ORM integration. + */ + public function testFalseOnFailedFetch() + { + $cache = $this->_getCacheDriver(); + $result = $cache->fetch('nonexistent_key'); + $this->assertFalse($result); + $this->assertNotNull($result); + } + + /** + * @return \Doctrine\Common\Cache\CacheProvider + */ + abstract protected function _getCacheDriver(); +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CouchbaseCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CouchbaseCacheTest.php new file mode 100644 index 0000000..40d5a69 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/CouchbaseCacheTest.php @@ -0,0 +1,47 @@ +couchbase = new Couchbase('127.0.0.1', 'Administrator', 'password', 'default'); + } catch(Exception $ex) { + $this->markTestSkipped('Could not instantiate the Couchbase cache because of: ' . $ex); + } + } else { + $this->markTestSkipped('The ' . __CLASS__ .' requires the use of the couchbase extension'); + } + } + + public function testNoExpire() + { + $cache = $this->_getCacheDriver(); + $cache->save('noexpire', 'value', 0); + sleep(1); + $this->assertTrue($cache->contains('noexpire'), 'Couchbase provider should support no-expire'); + } + + public function testLongLifetime() + { + $cache = $this->_getCacheDriver(); + $cache->save('key', 'value', 30 * 24 * 3600 + 1); + + $this->assertTrue($cache->contains('key'), 'Couchbase provider should support TTL > 30 days'); + } + + protected function _getCacheDriver() + { + $driver = new CouchbaseCache(); + $driver->setCouchbase($this->couchbase); + return $driver; + } +} \ No newline at end of file diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FileCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FileCacheTest.php new file mode 100644 index 0000000..6f9df81 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FileCacheTest.php @@ -0,0 +1,107 @@ +driver = $this->getMock( + 'Doctrine\Common\Cache\FileCache', + array('doFetch', 'doContains', 'doSave'), + array(), '', false + ); + } + + public function getProviderFileName() + { + return array( + //The characters :\/<>"*?| are not valid in Windows filenames. + array('key:1', 'key1'), + array('key\2', 'key2'), + array('key/3', 'key3'), + array('key<4', 'key4'), + array('key>5', 'key5'), + array('key"6', 'key6'), + array('key*7', 'key7'), + array('key?8', 'key8'), + array('key|9', 'key9'), + array('key[0]','key[0]'), + ); + } + + /** + * @dataProvider getProviderFileName + */ + public function testInvalidFilename($key, $expected) + { + $cache = $this->driver; + $method = new \ReflectionMethod($cache, 'getFilename'); + + $method->setAccessible(true); + + $value = $method->invoke($cache, $key); + $actual = pathinfo($value, PATHINFO_FILENAME); + + $this->assertEquals($expected, $actual); + } + + public function testFilenameCollision() + { + $data['key:0'] = 'key0'; + $data['key\0'] = 'key0'; + $data['key/0'] = 'key0'; + $data['key<0'] = 'key0'; + $data['key>0'] = 'key0'; + $data['key"0'] = 'key0'; + $data['key*0'] = 'key0'; + $data['key?0'] = 'key0'; + $data['key|0'] = 'key0'; + + $paths = array(); + $cache = $this->driver; + $method = new \ReflectionMethod($cache, 'getFilename'); + + $method->setAccessible(true); + + foreach ($data as $key => $expected) { + $path = $method->invoke($cache, $key); + $actual = pathinfo($path, PATHINFO_FILENAME); + + $this->assertNotContains($path, $paths); + $this->assertEquals($expected, $actual); + + $paths[] = $path; + } + } + + public function testFilenameShouldCreateThePathWithFourSubDirectories() + { + $cache = $this->driver; + $method = new \ReflectionMethod($cache, 'getFilename'); + $key = 'item-key'; + $expectedDir[] = '84e0e2e893febb73'; + $expectedDir[] = '7a0fee0c89d53f4b'; + $expectedDir[] = 'b7fcb44c57cdf3d3'; + $expectedDir[] = '2ce7363f5d597760'; + $expectedDir = implode(DIRECTORY_SEPARATOR, $expectedDir); + + $method->setAccessible(true); + + $path = $method->invoke($cache, $key); + $filename = pathinfo($path, PATHINFO_FILENAME); + $dirname = pathinfo($path, PATHINFO_DIRNAME); + + $this->assertEquals('item-key', $filename); + $this->assertEquals(DIRECTORY_SEPARATOR . $expectedDir, $dirname); + $this->assertEquals(DIRECTORY_SEPARATOR . $expectedDir . DIRECTORY_SEPARATOR . $key, $path); + } +} \ No newline at end of file diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FilesystemCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FilesystemCacheTest.php new file mode 100644 index 0000000..ff243ce --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/FilesystemCacheTest.php @@ -0,0 +1,102 @@ +assertFalse(is_dir($dir)); + + $this->driver = new FilesystemCache($dir); + $this->assertTrue(is_dir($dir)); + + return $this->driver; + } + + public function testLifetime() + { + $cache = $this->_getCacheDriver(); + + // Test save + $cache->save('test_key', 'testing this out', 10); + + // Test contains to test that save() worked + $this->assertTrue($cache->contains('test_key')); + + // Test fetch + $this->assertEquals('testing this out', $cache->fetch('test_key')); + + // access private methods + $getFilename = new \ReflectionMethod($cache, 'getFilename'); + $getNamespacedId = new \ReflectionMethod($cache, 'getNamespacedId'); + + $getFilename->setAccessible(true); + $getNamespacedId->setAccessible(true); + + $id = $getNamespacedId->invoke($cache, 'test_key'); + $filename = $getFilename->invoke($cache, $id); + + $data = ''; + $lifetime = 0; + $resource = fopen($filename, "r"); + + if (false !== ($line = fgets($resource))) { + $lifetime = (integer) $line; + } + + while (false !== ($line = fgets($resource))) { + $data .= $line; + } + + $this->assertNotEquals(0, $lifetime, "previous lifetime could not be loaded"); + + // update lifetime + $lifetime = $lifetime - 20; + file_put_contents($filename, $lifetime . PHP_EOL . $data); + + // test expired data + $this->assertFalse($cache->contains('test_key')); + $this->assertFalse($cache->fetch('test_key')); + } + + public function testGetStats() + { + $cache = $this->_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertNull($stats[Cache::STATS_HITS]); + $this->assertNull($stats[Cache::STATS_MISSES]); + $this->assertNull($stats[Cache::STATS_UPTIME]); + $this->assertEquals(0, $stats[Cache::STATS_MEMORY_USAGE]); + $this->assertGreaterThan(0, $stats[Cache::STATS_MEMORY_AVAILABLE]); + } + + public function tearDown() + { + $dir = $this->driver->getDirectory(); + $ext = $this->driver->getExtension(); + $iterator = new \RecursiveDirectoryIterator($dir); + + foreach (new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST) as $file) { + if ($file->isFile()) { + @unlink($file->getRealPath()); + } else { + @rmdir($file->getRealPath()); + } + } + } + +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcacheCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcacheCacheTest.php new file mode 100644 index 0000000..36c180c --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcacheCacheTest.php @@ -0,0 +1,45 @@ +_memcache = new \Memcache; + $ok = @$this->_memcache->connect('localhost', 11211); + if (!$ok) { + $this->markTestSkipped('The ' . __CLASS__ .' requires the use of memcache'); + } + } else { + $this->markTestSkipped('The ' . __CLASS__ .' requires the use of memcache'); + } + } + + public function testNoExpire() { + $cache = $this->_getCacheDriver(); + $cache->save('noexpire', 'value', 0); + sleep(1); + $this->assertTrue($cache->contains('noexpire'), 'Memcache provider should support no-expire'); + } + + public function testLongLifetime() + { + $cache = $this->_getCacheDriver(); + $cache->save('key', 'value', 30 * 24 * 3600 + 1); + $this->assertTrue($cache->contains('key'), 'Memcache provider should support TTL > 30 days'); + } + + protected function _getCacheDriver() + { + $driver = new MemcacheCache(); + $driver->setMemcache($this->_memcache); + return $driver; + } + +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcachedCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcachedCacheTest.php new file mode 100644 index 0000000..ecbe5a6 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/MemcachedCacheTest.php @@ -0,0 +1,48 @@ +memcached = new \Memcached(); + $this->memcached->setOption(\Memcached::OPT_COMPRESSION, false); + $this->memcached->addServer('127.0.0.1', 11211); + + $fh = @fsockopen('127.0.0.1', 11211); + if (!$fh) { + $this->markTestSkipped('The ' . __CLASS__ .' requires the use of memcache'); + } + } else { + $this->markTestSkipped('The ' . __CLASS__ .' requires the use of memcache'); + } + } + + public function testNoExpire() { + $cache = $this->_getCacheDriver(); + $cache->save('noexpire', 'value', 0); + sleep(1); + $this->assertTrue($cache->contains('noexpire'), 'Memcache provider should support no-expire'); + } + + public function testLongLifetime() + { + $cache = $this->_getCacheDriver(); + $cache->save('key', 'value', 30 * 24 * 3600 + 1); + + $this->assertTrue($cache->contains('key'), 'Memcached provider should support TTL > 30 days'); + } + + protected function _getCacheDriver() + { + $driver = new MemcachedCache(); + $driver->setMemcached($this->memcached); + return $driver; + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/PhpFileCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/PhpFileCacheTest.php new file mode 100644 index 0000000..1f13b27 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/PhpFileCacheTest.php @@ -0,0 +1,154 @@ +assertFalse(is_dir($dir)); + + $this->driver = new PhpFileCache($dir); + $this->assertTrue(is_dir($dir)); + + return $this->driver; + } + + public function testObjects() + { + $this->markTestSkipped('PhpFileCache does not support saving objects that dont implement __set_state()'); + } + + public function testLifetime() + { + $cache = $this->_getCacheDriver(); + + // Test save + $cache->save('test_key', 'testing this out', 10); + + // Test contains to test that save() worked + $this->assertTrue($cache->contains('test_key')); + + // Test fetch + $this->assertEquals('testing this out', $cache->fetch('test_key')); + + // access private methods + $getFilename = new \ReflectionMethod($cache, 'getFilename'); + $getNamespacedId = new \ReflectionMethod($cache, 'getNamespacedId'); + + $getFilename->setAccessible(true); + $getNamespacedId->setAccessible(true); + + $id = $getNamespacedId->invoke($cache, 'test_key'); + $path = $getFilename->invoke($cache, $id); + $value = include $path; + + // update lifetime + $value['lifetime'] = $value['lifetime'] - 20; + file_put_contents($path, 'assertFalse($cache->contains('test_key')); + $this->assertFalse($cache->fetch('test_key')); + } + + public function testImplementsSetState() + { + $cache = $this->_getCacheDriver(); + + // Test save + $cache->save('test_set_state', new SetStateClass(array(1,2,3))); + + //Test __set_state call + $this->assertCount(0, SetStateClass::$values); + + // Test fetch + $value = $cache->fetch('test_set_state'); + $this->assertInstanceOf('Doctrine\Tests\Common\Cache\SetStateClass', $value); + $this->assertEquals(array(1,2,3), $value->getValue()); + + //Test __set_state call + $this->assertCount(1, SetStateClass::$values); + + // Test contains + $this->assertTrue($cache->contains('test_set_state')); + } + + public function testNotImplementsSetState() + { + $cache = $this->_getCacheDriver(); + + $this->setExpectedException('InvalidArgumentException'); + $cache->save('test_not_set_state', new NotSetStateClass(array(1,2,3))); + } + + public function testGetStats() + { + $cache = $this->_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertNull($stats[Cache::STATS_HITS]); + $this->assertNull($stats[Cache::STATS_MISSES]); + $this->assertNull($stats[Cache::STATS_UPTIME]); + $this->assertEquals(0, $stats[Cache::STATS_MEMORY_USAGE]); + $this->assertGreaterThan(0, $stats[Cache::STATS_MEMORY_AVAILABLE]); + } + + public function tearDown() + { + if (!$this->driver) { + return; + } + + $dir = $this->driver->getDirectory(); + $ext = $this->driver->getExtension(); + $iterator = new \RecursiveDirectoryIterator($dir); + + foreach (new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST) as $file) { + if ($file->isFile()) { + @unlink($file->getRealPath()); + } else { + @rmdir($file->getRealPath()); + } + } + } + +} + +class NotSetStateClass +{ + private $value; + + public function __construct($value) + { + $this->value = $value; + } + + public function getValue() + { + return $this->value; + } +} + +class SetStateClass extends NotSetStateClass +{ + public static $values = array(); + + public static function __set_state($data) + { + self::$values = $data; + return new self($data['value']); + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RedisCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RedisCacheTest.php new file mode 100644 index 0000000..45bbc75 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RedisCacheTest.php @@ -0,0 +1,30 @@ +_redis = new \Redis(); + $ok = @$this->_redis->connect('127.0.0.1'); + if (!$ok) { + $this->markTestSkipped('The ' . __CLASS__ .' requires the use of redis'); + } + } else { + $this->markTestSkipped('The ' . __CLASS__ .' requires the use of redis'); + } + } + + protected function _getCacheDriver() + { + $driver = new RedisCache(); + $driver->setRedis($this->_redis); + return $driver; + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RiakCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RiakCacheTest.php new file mode 100644 index 0000000..dce8cc0 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/RiakCacheTest.php @@ -0,0 +1,64 @@ +markTestSkipped('The ' . __CLASS__ .' requires the use of Riak'); + } + + try { + $this->connection = new Connection('127.0.0.1', 8087); + $this->bucket = new Bucket($this->connection, 'test'); + } catch (Exception\RiakException $e) { + $this->markTestSkipped('The ' . __CLASS__ .' requires the use of Riak'); + } + } + + /** + * {@inheritdoc} + */ + public function testGetStats() + { + $cache = $this->_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertNull($stats); + } + + /** + * Retrieve RiakCache instance. + * + * @return \Doctrine\Common\Cache\RiakCache + */ + protected function _getCacheDriver() + { + return new RiakCache($this->bucket); + } +} diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/WinCacheCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/WinCacheCacheTest.php new file mode 100644 index 0000000..cb363df --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/WinCacheCacheTest.php @@ -0,0 +1,20 @@ +markTestSkipped('The ' . __CLASS__ .' requires the use of Wincache'); + } + } + + protected function _getCacheDriver() + { + return new WincacheCache(); + } +} \ No newline at end of file diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/XcacheCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/XcacheCacheTest.php new file mode 100644 index 0000000..6259848 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/XcacheCacheTest.php @@ -0,0 +1,20 @@ +markTestSkipped('The ' . __CLASS__ .' requires the use of xcache'); + } + } + + protected function _getCacheDriver() + { + return new XcacheCache(); + } +} \ No newline at end of file diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ZendDataCacheTest.php b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ZendDataCacheTest.php new file mode 100644 index 0000000..cd66e15 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/Common/Cache/ZendDataCacheTest.php @@ -0,0 +1,28 @@ +markTestSkipped('The ' . __CLASS__ .' requires the use of Zend Data Cache which only works in apache2handler SAPI'); + } + } + + public function testGetStats() + { + $cache = $this->_getCacheDriver(); + $stats = $cache->getStats(); + + $this->assertNull($stats); + } + + protected function _getCacheDriver() + { + return new ZendDataCache(); + } +} \ No newline at end of file diff --git a/vendor/doctrine/cache/tests/Doctrine/Tests/DoctrineTestCase.php b/vendor/doctrine/cache/tests/Doctrine/Tests/DoctrineTestCase.php new file mode 100644 index 0000000..e8323d2 --- /dev/null +++ b/vendor/doctrine/cache/tests/Doctrine/Tests/DoctrineTestCase.php @@ -0,0 +1,10 @@ +andWhere($criteria->expr()->contains('property', 'Foo')); diff --git a/vendor/doctrine/collections/composer.json b/vendor/doctrine/collections/composer.json new file mode 100644 index 0000000..dd30961 --- /dev/null +++ b/vendor/doctrine/collections/composer.json @@ -0,0 +1,26 @@ +{ + "name": "doctrine/collections", + "type": "library", + "description": "Collections Abstraction library", + "keywords": ["collections", "array", "iterator"], + "homepage": "http://www.doctrine-project.org", + "license": "MIT", + "authors": [ + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, + {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, + {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} + ], + "require": { + "php": ">=5.3.2" + }, + "autoload": { + "psr-0": { "Doctrine\\Common\\Collections\\": "lib/" } + }, + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php new file mode 100644 index 0000000..9c2c8e1 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.php @@ -0,0 +1,385 @@ +. + */ + +namespace Doctrine\Common\Collections; + +use Closure, ArrayIterator; +use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor; + +/** + * An ArrayCollection is a Collection implementation that wraps a regular PHP array. + * + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class ArrayCollection implements Collection, Selectable +{ + /** + * An array containing the entries of this collection. + * + * @var array + */ + private $_elements; + + /** + * Initializes a new ArrayCollection. + * + * @param array $elements + */ + public function __construct(array $elements = array()) + { + $this->_elements = $elements; + } + + /** + * {@inheritDoc} + */ + public function toArray() + { + return $this->_elements; + } + + /** + * {@inheritDoc} + */ + public function first() + { + return reset($this->_elements); + } + + /** + * {@inheritDoc} + */ + public function last() + { + return end($this->_elements); + } + + /** + * {@inheritDoc} + */ + public function key() + { + return key($this->_elements); + } + + /** + * {@inheritDoc} + */ + public function next() + { + return next($this->_elements); + } + + /** + * {@inheritDoc} + */ + public function current() + { + return current($this->_elements); + } + + /** + * {@inheritDoc} + */ + public function remove($key) + { + if (isset($this->_elements[$key]) || array_key_exists($key, $this->_elements)) { + $removed = $this->_elements[$key]; + unset($this->_elements[$key]); + + return $removed; + } + + return null; + } + + /** + * {@inheritDoc} + */ + public function removeElement($element) + { + $key = array_search($element, $this->_elements, true); + + if ($key !== false) { + unset($this->_elements[$key]); + + return true; + } + + return false; + } + + /** + * Required by interface ArrayAccess. + * + * {@inheritDoc} + */ + public function offsetExists($offset) + { + return $this->containsKey($offset); + } + + /** + * Required by interface ArrayAccess. + * + * {@inheritDoc} + */ + public function offsetGet($offset) + { + return $this->get($offset); + } + + /** + * Required by interface ArrayAccess. + * + * {@inheritDoc} + */ + public function offsetSet($offset, $value) + { + if ( ! isset($offset)) { + return $this->add($value); + } + return $this->set($offset, $value); + } + + /** + * Required by interface ArrayAccess. + * + * {@inheritDoc} + */ + public function offsetUnset($offset) + { + return $this->remove($offset); + } + + /** + * {@inheritDoc} + */ + public function containsKey($key) + { + return isset($this->_elements[$key]) || array_key_exists($key, $this->_elements); + } + + /** + * {@inheritDoc} + */ + public function contains($element) + { + return in_array($element, $this->_elements, true); + } + + /** + * {@inheritDoc} + */ + public function exists(Closure $p) + { + foreach ($this->_elements as $key => $element) { + if ($p($key, $element)) { + return true; + } + } + return false; + } + + /** + * {@inheritDoc} + */ + public function indexOf($element) + { + return array_search($element, $this->_elements, true); + } + + /** + * {@inheritDoc} + */ + public function get($key) + { + if (isset($this->_elements[$key])) { + return $this->_elements[$key]; + } + return null; + } + + /** + * {@inheritDoc} + */ + public function getKeys() + { + return array_keys($this->_elements); + } + + /** + * {@inheritDoc} + */ + public function getValues() + { + return array_values($this->_elements); + } + + /** + * {@inheritDoc} + */ + public function count() + { + return count($this->_elements); + } + + /** + * {@inheritDoc} + */ + public function set($key, $value) + { + $this->_elements[$key] = $value; + } + + /** + * {@inheritDoc} + */ + public function add($value) + { + $this->_elements[] = $value; + return true; + } + + /** + * {@inheritDoc} + */ + public function isEmpty() + { + return ! $this->_elements; + } + + /** + * Required by interface IteratorAggregate. + * + * {@inheritDoc} + */ + public function getIterator() + { + return new ArrayIterator($this->_elements); + } + + /** + * {@inheritDoc} + */ + public function map(Closure $func) + { + return new static(array_map($func, $this->_elements)); + } + + /** + * {@inheritDoc} + */ + public function filter(Closure $p) + { + return new static(array_filter($this->_elements, $p)); + } + + /** + * {@inheritDoc} + */ + public function forAll(Closure $p) + { + foreach ($this->_elements as $key => $element) { + if ( ! $p($key, $element)) { + return false; + } + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function partition(Closure $p) + { + $coll1 = $coll2 = array(); + foreach ($this->_elements as $key => $element) { + if ($p($key, $element)) { + $coll1[$key] = $element; + } else { + $coll2[$key] = $element; + } + } + return array(new static($coll1), new static($coll2)); + } + + /** + * Returns a string representation of this object. + * + * @return string + */ + public function __toString() + { + return __CLASS__ . '@' . spl_object_hash($this); + } + + /** + * {@inheritDoc} + */ + public function clear() + { + $this->_elements = array(); + } + + /** + * {@inheritDoc} + */ + public function slice($offset, $length = null) + { + return array_slice($this->_elements, $offset, $length, true); + } + + /** + * {@inheritDoc} + */ + public function matching(Criteria $criteria) + { + $expr = $criteria->getWhereExpression(); + $filtered = $this->_elements; + + if ($expr) { + $visitor = new ClosureExpressionVisitor(); + $filter = $visitor->dispatch($expr); + $filtered = array_filter($filtered, $filter); + } + + if ($orderings = $criteria->getOrderings()) { + $next = null; + foreach (array_reverse($orderings) as $field => $ordering) { + $next = ClosureExpressionVisitor::sortByField($field, $ordering == 'DESC' ? -1 : 1, $next); + } + + usort($filtered, $next); + } + + $offset = $criteria->getFirstResult(); + $length = $criteria->getMaxResults(); + + if ($offset || $length) { + $filtered = array_slice($filtered, (int)$offset, $length); + } + + return new static($filtered); + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Collection.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Collection.php new file mode 100644 index 0000000..a0808b3 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Collection.php @@ -0,0 +1,260 @@ +. + */ + +namespace Doctrine\Common\Collections; + +use Closure, Countable, IteratorAggregate, ArrayAccess; + +/** + * The missing (SPL) Collection/Array/OrderedMap interface. + * + * A Collection resembles the nature of a regular PHP array. That is, + * it is essentially an ordered map that can also be used + * like a list. + * + * A Collection has an internal iterator just like a PHP array. In addition, + * a Collection can be iterated with external iterators, which is preferrable. + * To use an external iterator simply use the foreach language construct to + * iterate over the collection (which calls {@link getIterator()} internally) or + * explicitly retrieve an iterator though {@link getIterator()} which can then be + * used to iterate over the collection. + * You can not rely on the internal iterator of the collection being at a certain + * position unless you explicitly positioned it before. Prefer iteration with + * external iterators. + * + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +interface Collection extends Countable, IteratorAggregate, ArrayAccess +{ + /** + * Adds an element at the end of the collection. + * + * @param mixed $element The element to add. + * + * @return boolean Always TRUE. + */ + function add($element); + + /** + * Clears the collection, removing all elements. + * + * @return void + */ + function clear(); + + /** + * Checks whether an element is contained in the collection. + * This is an O(n) operation, where n is the size of the collection. + * + * @param mixed $element The element to search for. + * + * @return boolean TRUE if the collection contains the element, FALSE otherwise. + */ + function contains($element); + + /** + * Checks whether the collection is empty (contains no elements). + * + * @return boolean TRUE if the collection is empty, FALSE otherwise. + */ + function isEmpty(); + + /** + * Removes the element at the specified index from the collection. + * + * @param string|integer $key The kex/index of the element to remove. + * + * @return mixed The removed element or NULL, if the collection did not contain the element. + */ + function remove($key); + + /** + * Removes the specified element from the collection, if it is found. + * + * @param mixed $element The element to remove. + * + * @return boolean TRUE if this collection contained the specified element, FALSE otherwise. + */ + function removeElement($element); + + /** + * Checks whether the collection contains an element with the specified key/index. + * + * @param string|integer $key The key/index to check for. + * + * @return boolean TRUE if the collection contains an element with the specified key/index, + * FALSE otherwise. + */ + function containsKey($key); + + /** + * Gets the element at the specified key/index. + * + * @param string|integer $key The key/index of the element to retrieve. + * + * @return mixed + */ + function get($key); + + /** + * Gets all keys/indices of the collection. + * + * @return array The keys/indices of the collection, in the order of the corresponding + * elements in the collection. + */ + function getKeys(); + + /** + * Gets all values of the collection. + * + * @return array The values of all elements in the collection, in the order they + * appear in the collection. + */ + function getValues(); + + /** + * Sets an element in the collection at the specified key/index. + * + * @param string|integer $key The key/index of the element to set. + * @param mixed $value The element to set. + * + * @return void + */ + function set($key, $value); + + /** + * Gets a native PHP array representation of the collection. + * + * @return array + */ + function toArray(); + + /** + * Sets the internal iterator to the first element in the collection and returns this element. + * + * @return mixed + */ + function first(); + + /** + * Sets the internal iterator to the last element in the collection and returns this element. + * + * @return mixed + */ + function last(); + + /** + * Gets the key/index of the element at the current iterator position. + * + * @return int|string + */ + function key(); + + /** + * Gets the element of the collection at the current iterator position. + * + * @return mixed + */ + function current(); + + /** + * Moves the internal iterator position to the next element and returns this element. + * + * @return mixed + */ + function next(); + + /** + * Tests for the existence of an element that satisfies the given predicate. + * + * @param Closure $p The predicate. + * + * @return boolean TRUE if the predicate is TRUE for at least one element, FALSE otherwise. + */ + function exists(Closure $p); + + /** + * Returns all the elements of this collection that satisfy the predicate p. + * The order of the elements is preserved. + * + * @param Closure $p The predicate used for filtering. + * + * @return Collection A collection with the results of the filter operation. + */ + function filter(Closure $p); + + /** + * Tests whether the given predicate p holds for all elements of this collection. + * + * @param Closure $p The predicate. + * + * @return boolean TRUE, if the predicate yields TRUE for all elements, FALSE otherwise. + */ + function forAll(Closure $p); + + /** + * Applies the given function to each element in the collection and returns + * a new collection with the elements returned by the function. + * + * @param Closure $func + * + * @return Collection + */ + function map(Closure $func); + + /** + * Partitions this collection in two collections according to a predicate. + * Keys are preserved in the resulting collections. + * + * @param Closure $p The predicate on which to partition. + * + * @return array An array with two elements. The first element contains the collection + * of elements where the predicate returned TRUE, the second element + * contains the collection of elements where the predicate returned FALSE. + */ + function partition(Closure $p); + + /** + * Gets the index/key of a given element. The comparison of two elements is strict, + * that means not only the value but also the type must match. + * For objects this means reference equality. + * + * @param mixed $element The element to search for. + * + * @return int|string|bool The key/index of the element or FALSE if the element was not found. + */ + function indexOf($element); + + /** + * Extracts a slice of $length elements starting at position $offset from the Collection. + * + * If $length is null it returns all elements from $offset to the end of the Collection. + * Keys have to be preserved by this method. Calling this method will only return the + * selected slice and NOT change the elements contained in the collection slice is called on. + * + * @param int $offset The offset to start from. + * @param int|null $length The maximum number of elements to return, or null for no limit. + * + * @return array + */ + function slice($offset, $length = null); +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Criteria.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Criteria.php new file mode 100644 index 0000000..42929bd --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Criteria.php @@ -0,0 +1,245 @@ +. + */ + +namespace Doctrine\Common\Collections; + +use Doctrine\Common\Collections\Expr\Expression; +use Doctrine\Common\Collections\Expr\CompositeExpression; + +/** + * Criteria for filtering Selectable collections. + * + * @author Benjamin Eberlei + * @since 2.3 + */ +class Criteria +{ + /** + * @var string + */ + const ASC = 'ASC'; + + /** + * @var string + */ + const DESC = 'DESC'; + + /** + * @var \Doctrine\Common\Collections\ExpressionBuilder|null + */ + private static $expressionBuilder; + + /** + * @var \Doctrine\Common\Collections\Expr\Expression|null + */ + private $expression; + + /** + * @var array|null + */ + private $orderings; + + /** + * @var int|null + */ + private $firstResult; + + /** + * @var int|null + */ + private $maxResults; + + /** + * Creates an instance of the class. + * + * @return Criteria + */ + public static function create() + { + return new static(); + } + + /** + * Returns the expression builder. + * + * @return \Doctrine\Common\Collections\ExpressionBuilder + */ + public static function expr() + { + if (self::$expressionBuilder === null) { + self::$expressionBuilder = new ExpressionBuilder(); + } + return self::$expressionBuilder; + } + + /** + * Construct a new Criteria. + * + * @param Expression $expression + * @param array|null $orderings + * @param int|null $firstResult + * @param int|null $maxResults + */ + public function __construct(Expression $expression = null, array $orderings = null, $firstResult = null, $maxResults = null) + { + $this->expression = $expression; + $this->orderings = $orderings; + $this->firstResult = $firstResult; + $this->maxResults = $maxResults; + } + + /** + * Sets the where expression to evaluate when this Criteria is searched for. + * + * @param Expression $expression + * + * @return Criteria + */ + public function where(Expression $expression) + { + $this->expression = $expression; + return $this; + } + + /** + * Appends the where expression to evaluate when this Criteria is searched for + * using an AND with previous expression. + * + * @param Expression $expression + * + * @return Criteria + */ + public function andWhere(Expression $expression) + { + if ($this->expression === null) { + return $this->where($expression); + } + + $this->expression = new CompositeExpression(CompositeExpression::TYPE_AND, array( + $this->expression, $expression + )); + + return $this; + } + + /** + * Appends the where expression to evaluate when this Criteria is searched for + * using an OR with previous expression. + * + * @param Expression $expression + * + * @return Criteria + */ + public function orWhere(Expression $expression) + { + if ($this->expression === null) { + return $this->where($expression); + } + + $this->expression = new CompositeExpression(CompositeExpression::TYPE_OR, array( + $this->expression, $expression + )); + + return $this; + } + + /** + * Gets the expression attached to this Criteria. + * + * @return Expression|null + */ + public function getWhereExpression() + { + return $this->expression; + } + + /** + * Gets the current orderings of this Criteria. + * + * @return array + */ + public function getOrderings() + { + return $this->orderings; + } + + /** + * Sets the ordering of the result of this Criteria. + * + * Keys are field and values are the order, being either ASC or DESC. + * + * @see Criteria::ASC + * @see Criteria::DESC + * + * @param array $orderings + * + * @return Criteria + */ + public function orderBy(array $orderings) + { + $this->orderings = $orderings; + return $this; + } + + /** + * Gets the current first result option of this Criteria. + * + * @return int|null + */ + public function getFirstResult() + { + return $this->firstResult; + } + + /** + * Set the number of first result that this Criteria should return. + * + * @param int|null $firstResult The value to set. + * + * @return Criteria + */ + public function setFirstResult($firstResult) + { + $this->firstResult = $firstResult; + return $this; + } + + /** + * Gets maxResults. + * + * @return int|null + */ + public function getMaxResults() + { + return $this->maxResults; + } + + /** + * Sets maxResults. + * + * @param int|null $maxResults The value to set. + * + * @return Criteria + */ + public function setMaxResults($maxResults) + { + $this->maxResults = $maxResults; + return $this; + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php new file mode 100644 index 0000000..03da2e1 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ClosureExpressionVisitor.php @@ -0,0 +1,223 @@ +. + */ + +namespace Doctrine\Common\Collections\Expr; + +/** + * Walks an expression graph and turns it into a PHP closure. + * + * This closure can be used with {@Collection#filter()} and is used internally + * by {@ArrayCollection#select()}. + * + * @author Benjamin Eberlei + * @since 2.3 + */ +class ClosureExpressionVisitor extends ExpressionVisitor +{ + /** + * Accesses the field of a given object. This field has to be public + * directly or indirectly (through an accessor get*, is*, or a magic + * method, __get, __call). + * + * @param object $object + * @param string $field + * + * @return mixed + */ + public static function getObjectFieldValue($object, $field) + { + $accessors = array('get', 'is'); + + foreach ($accessors as $accessor) { + $accessor .= $field; + + if ( ! method_exists($object, $accessor)) { + continue; + } + + return $object->$accessor(); + } + + // __call should be triggered for get. + $accessor = $accessors[0] . $field; + + if (method_exists($object, '__call')) { + return $object->$accessor(); + } + + if ($object instanceof \ArrayAccess || is_array($object)) { + return $object[$field]; + } + + return $object->$field; + } + + /** + * Helper for sorting arrays of objects based on multiple fields + orientations. + * + * @param string $name + * @param int $orientation + * @param \Closure $next + * + * @return \Closure + */ + public static function sortByField($name, $orientation = 1, \Closure $next = null) + { + if (!$next) { + $next = function() { + return 0; + }; + } + + return function ($a, $b) use ($name, $next, $orientation) { + $aValue = ClosureExpressionVisitor::getObjectFieldValue($a, $name); + $bValue = ClosureExpressionVisitor::getObjectFieldValue($b, $name); + + if ($aValue === $bValue) { + return $next($a, $b); + } + + return (($aValue > $bValue) ? 1 : -1) * $orientation; + }; + } + + /** + * {@inheritDoc} + */ + public function walkComparison(Comparison $comparison) + { + $field = $comparison->getField(); + $value = $comparison->getValue()->getValue(); // shortcut for walkValue() + + switch ($comparison->getOperator()) { + case Comparison::EQ: + return function ($object) use ($field, $value) { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) === $value; + }; + + case Comparison::NEQ: + return function ($object) use ($field, $value) { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) !== $value; + }; + + case Comparison::LT: + return function ($object) use ($field, $value) { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) < $value; + }; + + case Comparison::LTE: + return function ($object) use ($field, $value) { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) <= $value; + }; + + case Comparison::GT: + return function ($object) use ($field, $value) { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) > $value; + }; + + case Comparison::GTE: + return function ($object) use ($field, $value) { + return ClosureExpressionVisitor::getObjectFieldValue($object, $field) >= $value; + }; + + case Comparison::IN: + return function ($object) use ($field, $value) { + return in_array(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value); + }; + + case Comparison::NIN: + return function ($object) use ($field, $value) { + return ! in_array(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value); + }; + + case Comparison::CONTAINS: + return function ($object) use ($field, $value) { + return false !== strpos(ClosureExpressionVisitor::getObjectFieldValue($object, $field), $value); + }; + + default: + throw new \RuntimeException("Unknown comparison operator: " . $comparison->getOperator()); + } + } + + /** + * {@inheritDoc} + */ + public function walkValue(Value $value) + { + return $value->getValue(); + } + + /** + * {@inheritDoc} + */ + public function walkCompositeExpression(CompositeExpression $expr) + { + $expressionList = array(); + + foreach ($expr->getExpressionList() as $child) { + $expressionList[] = $this->dispatch($child); + } + + switch($expr->getType()) { + case CompositeExpression::TYPE_AND: + return $this->andExpressions($expressionList); + + case CompositeExpression::TYPE_OR: + return $this->orExpressions($expressionList); + + default: + throw new \RuntimeException("Unknown composite " . $expr->getType()); + } + } + + /** + * @param array $expressions + * + * @return callable + */ + private function andExpressions($expressions) + { + return function ($object) use ($expressions) { + foreach ($expressions as $expression) { + if ( ! $expression($object)) { + return false; + } + } + return true; + }; + } + + /** + * @param array $expressions + * + * @return callable + */ + private function orExpressions($expressions) + { + return function ($object) use ($expressions) { + foreach ($expressions as $expression) { + if ($expression($object)) { + return true; + } + } + return false; + }; + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Comparison.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Comparison.php new file mode 100644 index 0000000..d54ecf2 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Comparison.php @@ -0,0 +1,103 @@ +. + */ + +namespace Doctrine\Common\Collections\Expr; + +/** + * Comparison of a field with a value by the given operator. + * + * @author Benjamin Eberlei + * @since 2.3 + */ +class Comparison implements Expression +{ + const EQ = '='; + const NEQ = '<>'; + const LT = '<'; + const LTE = '<='; + const GT = '>'; + const GTE = '>='; + const IS = '='; // no difference with EQ + const IN = 'IN'; + const NIN = 'NIN'; + const CONTAINS = 'CONTAINS'; + + /** + * @var string + */ + private $field; + + /** + * @var string + */ + private $op; + + /** + * @var Value + */ + private $value; + + /** + * @param string $field + * @param string $operator + * @param mixed $value + */ + public function __construct($field, $operator, $value) + { + if ( ! ($value instanceof Value)) { + $value = new Value($value); + } + + $this->field = $field; + $this->op = $operator; + $this->value = $value; + } + + /** + * @return string + */ + public function getField() + { + return $this->field; + } + + /** + * @return Value + */ + public function getValue() + { + return $this->value; + } + + /** + * @return string + */ + public function getOperator() + { + return $this->op; + } + + /** + * {@inheritDoc} + */ + public function visit(ExpressionVisitor $visitor) + { + return $visitor->walkComparison($this); + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/CompositeExpression.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/CompositeExpression.php new file mode 100644 index 0000000..3613c02 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/CompositeExpression.php @@ -0,0 +1,90 @@ +. + */ + +namespace Doctrine\Common\Collections\Expr; + +/** + * Expression of Expressions combined by AND or OR operation. + * + * @author Benjamin Eberlei + * @since 2.3 + */ +class CompositeExpression implements Expression +{ + const TYPE_AND = 'AND'; + const TYPE_OR = 'OR'; + + /** + * @var string + */ + private $type; + + /** + * @var Expression[] + */ + private $expressions = array(); + + /** + * @param string $type + * @param array $expressions + * + * @throws \RuntimeException + */ + public function __construct($type, array $expressions) + { + $this->type = $type; + + foreach ($expressions as $expr) { + if ($expr instanceof Value) { + throw new \RuntimeException("Values are not supported expressions as children of and/or expressions."); + } + if ( ! ($expr instanceof Expression)) { + throw new \RuntimeException("No expression given to CompositeExpression."); + } + + $this->expressions[] = $expr; + } + } + + /** + * Returns the list of expressions nested in this composite. + * + * @return Expression[] + */ + public function getExpressionList() + { + return $this->expressions; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * {@inheritDoc} + */ + public function visit(ExpressionVisitor $visitor) + { + return $visitor->walkCompositeExpression($this); + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Expression.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Expression.php new file mode 100644 index 0000000..68db767 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Expression.php @@ -0,0 +1,35 @@ +. + */ + +namespace Doctrine\Common\Collections\Expr; + +/** + * Expression for the {@link Selectable} interface. + * + * @author Benjamin Eberlei + */ +interface Expression +{ + /** + * @param ExpressionVisitor $visitor + * + * @return mixed + */ + public function visit(ExpressionVisitor $visitor); +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ExpressionVisitor.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ExpressionVisitor.php new file mode 100644 index 0000000..080afdc --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/ExpressionVisitor.php @@ -0,0 +1,82 @@ +. + */ + +namespace Doctrine\Common\Collections\Expr; + +/** + * An Expression visitor walks a graph of expressions and turns them into a + * query for the underlying implementation. + * + * @author Benjamin Eberlei + */ +abstract class ExpressionVisitor +{ + /** + * Converts a comparison expression into the target query language output. + * + * @param Comparison $comparison + * + * @return mixed + */ + abstract public function walkComparison(Comparison $comparison); + + /** + * Converts a value expression into the target query language part. + * + * @param Value $value + * + * @return mixed + */ + abstract public function walkValue(Value $value); + + /** + * Converts a composite expression into the target query language output. + * + * @param CompositeExpression $expr + * + * @return mixed + */ + abstract public function walkCompositeExpression(CompositeExpression $expr); + + /** + * Dispatches walking an expression to the appropriate handler. + * + * @param Expression $expr + * + * @return mixed + * + * @throws \RuntimeException + */ + public function dispatch(Expression $expr) + { + switch (true) { + case ($expr instanceof Comparison): + return $this->walkComparison($expr); + + case ($expr instanceof Value): + return $this->walkValue($expr); + + case ($expr instanceof CompositeExpression): + return $this->walkCompositeExpression($expr); + + default: + throw new \RuntimeException("Unknown Expression " . get_class($expr)); + } + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Value.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Value.php new file mode 100644 index 0000000..7f6e831 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Expr/Value.php @@ -0,0 +1,52 @@ +. + */ + +namespace Doctrine\Common\Collections\Expr; + +class Value implements Expression +{ + /** + * @var mixed + */ + private $value; + + /** + * @param mixed $value + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * {@inheritDoc} + */ + public function visit(ExpressionVisitor $visitor) + { + return $visitor->walkValue($this); + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ExpressionBuilder.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ExpressionBuilder.php new file mode 100644 index 0000000..f9964d4 --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/ExpressionBuilder.php @@ -0,0 +1,162 @@ +. + */ + +namespace Doctrine\Common\Collections; + +use Doctrine\Common\Collections\Expr\Comparison; +use Doctrine\Common\Collections\Expr\CompositeExpression; +use Doctrine\Common\Collections\Expr\Value; + +/** + * Builder for Expressions in the {@link Selectable} interface. + * + * @author Benjamin Eberlei + * @since 2.3 + */ +class ExpressionBuilder +{ + /** + * @param mixed $x + * + * @return CompositeExpression + */ + public function andX($x = null) + { + return new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args()); + } + + /** + * @param mixed $x + * + * @return CompositeExpression + */ + public function orX($x = null) + { + return new CompositeExpression(CompositeExpression::TYPE_OR, func_get_args()); + } + + /** + * @param string $field + * @param mixed $value + * + * @return Comparison + */ + public function eq($field, $value) + { + return new Comparison($field, Comparison::EQ, new Value($value)); + } + + /** + * @param string $field + * @param mixed $value + * + * @return Comparison + */ + public function gt($field, $value) + { + return new Comparison($field, Comparison::GT, new Value($value)); + } + + /** + * @param string $field + * @param mixed $value + * + * @return Comparison + */ + public function lt($field, $value) + { + return new Comparison($field, Comparison::LT, new Value($value)); + } + + /** + * @param string $field + * @param mixed $value + * + * @return Comparison + */ + public function gte($field, $value) + { + return new Comparison($field, Comparison::GTE, new Value($value)); + } + + /** + * @param string $field + * @param mixed $value + * + * @return Comparison + */ + public function lte($field, $value) + { + return new Comparison($field, Comparison::LTE, new Value($value)); + } + + /** + * @param string $field + * @param mixed $value + * + * @return Comparison + */ + public function neq($field, $value) + { + return new Comparison($field, Comparison::NEQ, new Value($value)); + } + + /** + * @param string $field + * + * @return Comparison + */ + public function isNull($field) + { + return new Comparison($field, Comparison::EQ, new Value(null)); + } + + /** + * @param string $field + * @param mixed $values + * + * @return Comparison + */ + public function in($field, array $values) + { + return new Comparison($field, Comparison::IN, new Value($values)); + } + + /** + * @param string $field + * @param mixed $values + * + * @return Comparison + */ + public function notIn($field, array $values) + { + return new Comparison($field, Comparison::NIN, new Value($values)); + } + + /** + * @param string $field + * @param mixed $value + * + * @return Comparison + */ + public function contains($field, $value) + { + return new Comparison($field, Comparison::CONTAINS, new Value($value)); + } +} diff --git a/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Selectable.php b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Selectable.php new file mode 100644 index 0000000..401d46e --- /dev/null +++ b/vendor/doctrine/collections/lib/Doctrine/Common/Collections/Selectable.php @@ -0,0 +1,48 @@ +. + */ + +namespace Doctrine\Common\Collections; + +/** + * Interface for collections that allow efficient filtering with an expression API. + * + * Goal of this interface is a backend independent method to fetch elements + * from a collections. {@link Expression} is crafted in a way that you can + * implement queries from both in-memory and database-backed collections. + * + * For database backed collections this allows very efficient access by + * utilizing the query APIs, for example SQL in the ORM. Applications using + * this API can implement efficient database access without having to ask the + * EntityManager or Repositories. + * + * @author Benjamin Eberlei + * @since 2.3 + */ +interface Selectable +{ + /** + * Selects all elements from a selectable that match the expression and + * returns a new collection containing these elements. + * + * @param Criteria $criteria + * + * @return Collection + */ + function matching(Criteria $criteria); +} diff --git a/vendor/doctrine/collections/phpunit.xml.dist b/vendor/doctrine/collections/phpunit.xml.dist new file mode 100644 index 0000000..36968e9 --- /dev/null +++ b/vendor/doctrine/collections/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + ./tests/Doctrine/ + + + + + + ./lib/Doctrine/ + + + + + + performance + + + diff --git a/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/ClosureExpressionVisitorTest.php b/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/ClosureExpressionVisitorTest.php new file mode 100644 index 0000000..b640043 --- /dev/null +++ b/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/ClosureExpressionVisitorTest.php @@ -0,0 +1,243 @@ +. + */ + +namespace Doctrine\Tests\Common\Collections; + +use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor; +use Doctrine\Common\Collections\ExpressionBuilder; + +/** + * @group DDC-1637 + */ +class ClosureExpressionVisitorTest extends \PHPUnit_Framework_TestCase +{ + private $visitor; + private $builder; + + public function setUp() + { + $this->visitor = new ClosureExpressionVisitor(); + $this->builder = new ExpressionBuilder(); + } + + public function testGetObjectFieldValueIsAccessor() + { + $object = new TestObject(1, 2, true); + + $this->assertTrue($this->visitor->getObjectFieldValue($object, 'baz')); + } + + public function testGetObjectFieldValueMagicCallMethod() + { + $object = new TestObject(1, 2, true, 3); + + $this->assertEquals(3, $this->visitor->getObjectFieldValue($object, 'qux')); + } + + public function testWalkEqualsComparison() + { + $closure = $this->visitor->walkComparison($this->builder->eq("foo", 1)); + + $this->assertTrue($closure(new TestObject(1))); + $this->assertFalse($closure(new TestObject(2))); + } + + public function testWalkNotEqualsComparison() + { + $closure = $this->visitor->walkComparison($this->builder->neq("foo", 1)); + + $this->assertFalse($closure(new TestObject(1))); + $this->assertTrue($closure(new TestObject(2))); + } + + public function testWalkLessThanComparison() + { + $closure = $this->visitor->walkComparison($this->builder->lt("foo", 1)); + + $this->assertFalse($closure(new TestObject(1))); + $this->assertTrue($closure(new TestObject(0))); + } + + public function testWalkLessThanEqualsComparison() + { + $closure = $this->visitor->walkComparison($this->builder->lte("foo", 1)); + + $this->assertFalse($closure(new TestObject(2))); + $this->assertTrue($closure(new TestObject(1))); + $this->assertTrue($closure(new TestObject(0))); + } + + public function testWalkGreaterThanEqualsComparison() + { + $closure = $this->visitor->walkComparison($this->builder->gte("foo", 1)); + + $this->assertTrue($closure(new TestObject(2))); + $this->assertTrue($closure(new TestObject(1))); + $this->assertFalse($closure(new TestObject(0))); + } + + public function testWalkGreaterThanComparison() + { + $closure = $this->visitor->walkComparison($this->builder->gt("foo", 1)); + + $this->assertTrue($closure(new TestObject(2))); + $this->assertFalse($closure(new TestObject(1))); + $this->assertFalse($closure(new TestObject(0))); + } + + public function testWalkInComparison() + { + $closure = $this->visitor->walkComparison($this->builder->in("foo", array(1, 2, 3))); + + $this->assertTrue($closure(new TestObject(2))); + $this->assertTrue($closure(new TestObject(1))); + $this->assertFalse($closure(new TestObject(0))); + } + + public function testWalkNotInComparison() + { + $closure = $this->visitor->walkComparison($this->builder->notIn("foo", array(1, 2, 3))); + + $this->assertFalse($closure(new TestObject(1))); + $this->assertFalse($closure(new TestObject(2))); + $this->assertTrue($closure(new TestObject(0))); + $this->assertTrue($closure(new TestObject(4))); + } + + public function testWalkContainsComparison() + { + $closure = $this->visitor->walkComparison($this->builder->contains('foo', 'hello')); + + $this->assertTrue($closure(new TestObject('hello world'))); + $this->assertFalse($closure(new TestObject('world'))); + } + + public function testWalkAndCompositeExpression() + { + $closure = $this->visitor->walkCompositeExpression( + $this->builder->andX( + $this->builder->eq("foo", 1), + $this->builder->eq("bar", 1) + ) + ); + + $this->assertTrue($closure(new TestObject(1, 1))); + $this->assertFalse($closure(new TestObject(1, 0))); + $this->assertFalse($closure(new TestObject(0, 1))); + $this->assertFalse($closure(new TestObject(0, 0))); + } + + public function testWalkOrCompositeExpression() + { + $closure = $this->visitor->walkCompositeExpression( + $this->builder->orX( + $this->builder->eq("foo", 1), + $this->builder->eq("bar", 1) + ) + ); + + $this->assertTrue($closure(new TestObject(1, 1))); + $this->assertTrue($closure(new TestObject(1, 0))); + $this->assertTrue($closure(new TestObject(0, 1))); + $this->assertFalse($closure(new TestObject(0, 0))); + } + + public function testSortByFieldAscending() + { + $objects = array(new TestObject("b"), new TestObject("a"), new TestObject("c")); + $sort = ClosureExpressionVisitor::sortByField("foo"); + + usort($objects, $sort); + + $this->assertEquals("a", $objects[0]->getFoo()); + $this->assertEquals("b", $objects[1]->getFoo()); + $this->assertEquals("c", $objects[2]->getFoo()); + } + + public function testSortByFieldDescending() + { + $objects = array(new TestObject("b"), new TestObject("a"), new TestObject("c")); + $sort = ClosureExpressionVisitor::sortByField("foo", -1); + + usort($objects, $sort); + + $this->assertEquals("c", $objects[0]->getFoo()); + $this->assertEquals("b", $objects[1]->getFoo()); + $this->assertEquals("a", $objects[2]->getFoo()); + } + + public function testSortDelegate() + { + $objects = array(new TestObject("a", "c"), new TestObject("a", "b"), new TestObject("a", "a")); + $sort = ClosureExpressionVisitor::sortByField("bar", 1); + $sort = ClosureExpressionVisitor::sortByField("foo", 1, $sort); + + usort($objects, $sort); + + $this->assertEquals("a", $objects[0]->getBar()); + $this->assertEquals("b", $objects[1]->getBar()); + $this->assertEquals("c", $objects[2]->getBar()); + } + + public function testArrayComparison() + { + $closure = $this->visitor->walkComparison($this->builder->eq("foo", 42)); + + $this->assertTrue($closure(array('foo' => 42))); + } +} + +class TestObject +{ + private $foo; + private $bar; + private $baz; + private $qux; + + public function __construct($foo = null, $bar = null, $baz = null, $qux = null) + { + $this->foo = $foo; + $this->bar = $bar; + $this->baz = $baz; + $this->qux = $qux; + } + + public function __call($name, $arguments) + { + if ('getqux' === $name) { + return $this->qux; + } + } + + public function getFoo() + { + return $this->foo; + } + + public function getBar() + { + return $this->bar; + } + + public function isBaz() + { + return $this->baz; + } +} + diff --git a/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/CollectionTest.php b/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/CollectionTest.php new file mode 100644 index 0000000..d98246c --- /dev/null +++ b/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/CollectionTest.php @@ -0,0 +1,264 @@ +_coll = new \Doctrine\Common\Collections\ArrayCollection; + } + + public function testIssetAndUnset() + { + $this->assertFalse(isset($this->_coll[0])); + $this->_coll->add('testing'); + $this->assertTrue(isset($this->_coll[0])); + unset($this->_coll[0]); + $this->assertFalse(isset($this->_coll[0])); + } + + public function testToString() + { + $this->_coll->add('testing'); + $this->assertTrue(is_string((string) $this->_coll)); + } + + public function testRemovingNonExistentEntryReturnsNull() + { + $this->assertEquals(null, $this->_coll->remove('testing_does_not_exist')); + } + + public function testExists() + { + $this->_coll->add("one"); + $this->_coll->add("two"); + $exists = $this->_coll->exists(function($k, $e) { return $e == "one"; }); + $this->assertTrue($exists); + $exists = $this->_coll->exists(function($k, $e) { return $e == "other"; }); + $this->assertFalse($exists); + } + + public function testMap() + { + $this->_coll->add(1); + $this->_coll->add(2); + $res = $this->_coll->map(function($e) { return $e * 2; }); + $this->assertEquals(array(2, 4), $res->toArray()); + } + + public function testFilter() + { + $this->_coll->add(1); + $this->_coll->add("foo"); + $this->_coll->add(3); + $res = $this->_coll->filter(function($e) { return is_numeric($e); }); + $this->assertEquals(array(0 => 1, 2 => 3), $res->toArray()); + } + + public function testFirstAndLast() + { + $this->_coll->add('one'); + $this->_coll->add('two'); + + $this->assertEquals($this->_coll->first(), 'one'); + $this->assertEquals($this->_coll->last(), 'two'); + } + + public function testArrayAccess() + { + $this->_coll[] = 'one'; + $this->_coll[] = 'two'; + + $this->assertEquals($this->_coll[0], 'one'); + $this->assertEquals($this->_coll[1], 'two'); + + unset($this->_coll[0]); + $this->assertEquals($this->_coll->count(), 1); + } + + public function testContainsKey() + { + $this->_coll[5] = 'five'; + $this->assertTrue($this->_coll->containsKey(5)); + } + + public function testContains() + { + $this->_coll[0] = 'test'; + $this->assertTrue($this->_coll->contains('test')); + } + + public function testSearch() + { + $this->_coll[0] = 'test'; + $this->assertEquals(0, $this->_coll->indexOf('test')); + } + + public function testGet() + { + $this->_coll[0] = 'test'; + $this->assertEquals('test', $this->_coll->get(0)); + } + + public function testGetKeys() + { + $this->_coll[] = 'one'; + $this->_coll[] = 'two'; + $this->assertEquals(array(0, 1), $this->_coll->getKeys()); + } + + public function testGetValues() + { + $this->_coll[] = 'one'; + $this->_coll[] = 'two'; + $this->assertEquals(array('one', 'two'), $this->_coll->getValues()); + } + + public function testCount() + { + $this->_coll[] = 'one'; + $this->_coll[] = 'two'; + $this->assertEquals($this->_coll->count(), 2); + $this->assertEquals(count($this->_coll), 2); + } + + public function testForAll() + { + $this->_coll[] = 'one'; + $this->_coll[] = 'two'; + $this->assertEquals($this->_coll->forAll(function($k, $e) { return is_string($e); }), true); + $this->assertEquals($this->_coll->forAll(function($k, $e) { return is_array($e); }), false); + } + + public function testPartition() + { + $this->_coll[] = true; + $this->_coll[] = false; + $partition = $this->_coll->partition(function($k, $e) { return $e == true; }); + $this->assertEquals($partition[0][0], true); + $this->assertEquals($partition[1][0], false); + } + + public function testClear() + { + $this->_coll[] = 'one'; + $this->_coll[] = 'two'; + $this->_coll->clear(); + $this->assertEquals($this->_coll->isEmpty(), true); + } + + public function testRemove() + { + $this->_coll[] = 'one'; + $this->_coll[] = 'two'; + $el = $this->_coll->remove(0); + + $this->assertEquals('one', $el); + $this->assertEquals($this->_coll->contains('one'), false); + $this->assertNull($this->_coll->remove(0)); + } + + public function testRemoveElement() + { + $this->_coll[] = 'one'; + $this->_coll[] = 'two'; + + $this->assertTrue($this->_coll->removeElement('two')); + $this->assertFalse($this->_coll->contains('two')); + $this->assertFalse($this->_coll->removeElement('two')); + } + + public function testSlice() + { + $this->_coll[] = 'one'; + $this->_coll[] = 'two'; + $this->_coll[] = 'three'; + + $slice = $this->_coll->slice(0, 1); + $this->assertInternalType('array', $slice); + $this->assertEquals(array('one'), $slice); + + $slice = $this->_coll->slice(1); + $this->assertEquals(array(1 => 'two', 2 => 'three'), $slice); + + $slice = $this->_coll->slice(1, 1); + $this->assertEquals(array(1 => 'two'), $slice); + } + + public function fillMatchingFixture() + { + $std1 = new \stdClass(); + $std1->foo = "bar"; + $this->_coll[] = $std1; + + $std2 = new \stdClass(); + $std2->foo = "baz"; + $this->_coll[] = $std2; + } + + /** + * @group DDC-1637 + */ + public function testMatching() + { + $this->fillMatchingFixture(); + + $col = $this->_coll->matching(new Criteria(Criteria::expr()->eq("foo", "bar"))); + $this->assertInstanceOf('Doctrine\Common\Collections\Collection', $col); + $this->assertNotSame($col, $this->_coll); + $this->assertEquals(1, count($col)); + } + + /** + * @group DDC-1637 + */ + public function testMatchingOrdering() + { + $this->fillMatchingFixture(); + + $col = $this->_coll->matching(new Criteria(null, array('foo' => 'DESC'))); + + $this->assertInstanceOf('Doctrine\Common\Collections\Collection', $col); + $this->assertNotSame($col, $this->_coll); + $this->assertEquals(2, count($col)); + $this->assertEquals('baz', $col[0]->foo); + $this->assertEquals('bar', $col[1]->foo); + } + + /** + * @group DDC-1637 + */ + public function testMatchingSlice() + { + $this->fillMatchingFixture(); + + $col = $this->_coll->matching(new Criteria(null, null, 1, 1)); + + $this->assertInstanceOf('Doctrine\Common\Collections\Collection', $col); + $this->assertNotSame($col, $this->_coll); + $this->assertEquals(1, count($col)); + $this->assertEquals('baz', $col[0]->foo); + } + + public function testCanRemoveNullValuesByKey() + { + $this->_coll->add(null); + $this->_coll->remove(0); + $this->assertTrue($this->_coll->isEmpty()); + } + + public function testCanVerifyExistingKeysWithNullValues() + { + $this->_coll->set('key', null); + $this->assertTrue($this->_coll->containsKey('key')); + } +} diff --git a/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/CriteriaTest.php b/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/CriteriaTest.php new file mode 100644 index 0000000..03fb6ca --- /dev/null +++ b/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/CriteriaTest.php @@ -0,0 +1,82 @@ +assertInstanceOf("Doctrine\Common\Collections\Criteria", $criteria); + } + + public function testConstructor() + { + $expr = new Comparison("field", "=", "value"); + $criteria = new Criteria($expr, array("foo" => "ASC"), 10, 20); + + $this->assertSame($expr, $criteria->getWhereExpression()); + $this->assertEquals(array("foo" => "ASC"), $criteria->getOrderings()); + $this->assertEquals(10, $criteria->getFirstResult()); + $this->assertEquals(20, $criteria->getMaxResults()); + } + + public function testWhere() + { + $expr = new Comparison("field", "=", "value"); + $criteria = new Criteria(); + + $criteria->where($expr); + + $this->assertSame($expr, $criteria->getWhereExpression()); + } + + public function testAndWhere() + { + $expr = new Comparison("field", "=", "value"); + $criteria = new Criteria(); + + $criteria->where($expr); + $expr = $criteria->getWhereExpression(); + $criteria->andWhere($expr); + + $where = $criteria->getWhereExpression(); + $this->assertInstanceOf('Doctrine\Common\Collections\Expr\CompositeExpression', $where); + + $this->assertEquals(CompositeExpression::TYPE_AND, $where->getType()); + $this->assertSame(array($expr, $expr), $where->getExpressionList()); + } + + public function testOrWhere() + { + $expr = new Comparison("field", "=", "value"); + $criteria = new Criteria(); + + $criteria->where($expr); + $expr = $criteria->getWhereExpression(); + $criteria->orWhere($expr); + + $where = $criteria->getWhereExpression(); + $this->assertInstanceOf('Doctrine\Common\Collections\Expr\CompositeExpression', $where); + + $this->assertEquals(CompositeExpression::TYPE_OR, $where->getType()); + $this->assertSame(array($expr, $expr), $where->getExpressionList()); + } + + public function testOrderings() + { + $criteria = Criteria::create() + ->orderBy(array("foo" => "ASC")); + + $this->assertEquals(array("foo" => "ASC"), $criteria->getOrderings()); + } + + public function testExpr() + { + $this->assertInstanceOf('Doctrine\Common\Collections\ExpressionBuilder', Criteria::expr()); + } +} diff --git a/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/ExpressionBuilderTest.php b/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/ExpressionBuilderTest.php new file mode 100644 index 0000000..06d4155 --- /dev/null +++ b/vendor/doctrine/collections/tests/Doctrine/Tests/Common/Collections/ExpressionBuilderTest.php @@ -0,0 +1,122 @@ +builder = new ExpressionBuilder(); + } + + public function testAndX() + { + $expr = $this->builder->andX($this->builder->eq("a", "b")); + + $this->assertInstanceOf("Doctrine\Common\Collections\Expr\CompositeExpression", $expr); + $this->assertEquals(CompositeExpression::TYPE_AND, $expr->getType()); + } + + public function testOrX() + { + $expr = $this->builder->orX($this->builder->eq("a", "b")); + + $this->assertInstanceOf("Doctrine\Common\Collections\Expr\CompositeExpression", $expr); + $this->assertEquals(CompositeExpression::TYPE_OR, $expr->getType()); + } + + public function testInvalidAndXArgument() + { + $this->setExpectedException("RuntimeException"); + $this->builder->andX("foo"); + } + + public function testEq() + { + $expr = $this->builder->eq("a", "b"); + + $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr); + $this->assertEquals(Comparison::EQ, $expr->getOperator()); + } + + public function testNeq() + { + $expr = $this->builder->neq("a", "b"); + + $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr); + $this->assertEquals(Comparison::NEQ, $expr->getOperator()); + } + + public function testLt() + { + $expr = $this->builder->lt("a", "b"); + + $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr); + $this->assertEquals(Comparison::LT, $expr->getOperator()); + } + + public function testGt() + { + $expr = $this->builder->gt("a", "b"); + + $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr); + $this->assertEquals(Comparison::GT, $expr->getOperator()); + } + + public function testGte() + { + $expr = $this->builder->gte("a", "b"); + + $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr); + $this->assertEquals(Comparison::GTE, $expr->getOperator()); + } + + public function testLte() + { + $expr = $this->builder->lte("a", "b"); + + $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr); + $this->assertEquals(Comparison::LTE, $expr->getOperator()); + } + + public function testIn() + { + $expr = $this->builder->in("a", array("b")); + + $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr); + $this->assertEquals(Comparison::IN, $expr->getOperator()); + } + + public function testNotIn() + { + $expr = $this->builder->notIn("a", array("b")); + + $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr); + $this->assertEquals(Comparison::NIN, $expr->getOperator()); + } + + public function testIsNull() + { + $expr = $this->builder->isNull("a"); + + $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr); + $this->assertEquals(Comparison::EQ, $expr->getOperator()); + } + + public function testContains() + { + $expr = $this->builder->contains("a", "b"); + + $this->assertInstanceOf("Doctrine\Common\Collections\Expr\Comparison", $expr); + $this->assertEquals(Comparison::CONTAINS, $expr->getOperator()); + } +} + diff --git a/vendor/doctrine/collections/tests/Doctrine/Tests/DoctrineTestCase.php b/vendor/doctrine/collections/tests/Doctrine/Tests/DoctrineTestCase.php new file mode 100644 index 0000000..e8323d2 --- /dev/null +++ b/vendor/doctrine/collections/tests/Doctrine/Tests/DoctrineTestCase.php @@ -0,0 +1,10 @@ +setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\'); + // new code necessary starting here + $reader->setIgnoreNotImportedAnnotations(true); + $reader->setEnableParsePhpImports(false); + $reader = new \Doctrine\Common\Annotations\CachedReader( + new \Doctrine\Common\Annotations\IndexedReader($reader), new ArrayCache() + ); + +## Annotation Base class or @Annotation + +Beginning after 2.1-RC2 you have to either extend ``Doctrine\Common\Annotations\Annotation`` or add @Annotation to your annotations class-level docblock, otherwise the class will simply be ignored. + +## Removed methods on AnnotationReader + +* AnnotationReader::setAutoloadAnnotations() +* AnnotationReader::getAutoloadAnnotations() +* AnnotationReader::isAutoloadAnnotations() + +## AnnotationRegistry + +Autoloading through the PHP autoloader is removed from the 2.1 AnnotationReader. Instead you have to use the global AnnotationRegistry for loading purposes: + + \Doctrine\Common\Annotations\AnnotationRegistry::registerFile($fileWithAnnotations); + \Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespace($namespace, $dirs = null); + \Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespaces($namespaces); + \Doctrine\Common\Annotations\AnnotationRegistry::registerLoader($callable); + +The $callable for registering a loader accepts a class as first and only parameter and must try to silently autoload it. On success true has to be returned. +The registerAutoloadNamespace function registers a PSR-0 compatible silent autoloader for all classes with the given namespace in the given directories. +If null is passed as directory the include path will be used. + diff --git a/vendor/doctrine/common/UPGRADE_TO_2_2 b/vendor/doctrine/common/UPGRADE_TO_2_2 new file mode 100644 index 0000000..1d93a13 --- /dev/null +++ b/vendor/doctrine/common/UPGRADE_TO_2_2 @@ -0,0 +1,61 @@ +This document details all the possible changes that you should investigate when +updating your project from Doctrine Common 2.1 to 2.2: + +## Annotation Changes + +- AnnotationReader::setIgnoreNotImportedAnnotations has been removed, you need to + add ignore annotation names which are supposed to be ignored via + AnnotationReader::addGlobalIgnoredName + +- AnnotationReader::setAutoloadAnnotations was deprecated by the AnnotationRegistry + in 2.1 and has been removed in 2.2 + +- AnnotationReader::setEnableParsePhpImports was added to ease transition to the new + annotation mechanism in 2.1 and is removed in 2.2 + +- AnnotationReader::isParsePhpImportsEnabled is removed (see above) + +- AnnotationReader::setDefaultAnnotationNamespace was deprecated in favor of explicit + configuration in 2.1 and will be removed in 2.2 (for isolated projects where you + have full-control over _all_ available annotations, we offer a dedicated reader + class ``SimpleAnnotationReader``) + +- AnnotationReader::setAnnotationCreationFunction was deprecated in 2.1 and will be + removed in 2.2. We only offer two creation mechanisms which cannot be changed + anymore to allow the same reader instance to work with all annotations regardless + of which library they are coming from. + +- AnnotationReader::setAnnotationNamespaceAlias was deprecated in 2.1 and will be + removed in 2.2 (see setDefaultAnnotationNamespace) + +- If you use a class as annotation which has not the @Annotation marker in it's + class block, we will now throw an exception instead of silently ignoring it. You + can however still achieve the previous behavior using the @IgnoreAnnotation, or + AnnotationReader::addGlobalIgnoredName (the exception message will contain detailed + instructions when you run into this problem). + +## Cache Changes + +- Renamed old AbstractCache to CacheProvider + +- Dropped the support to the following functions of all cache providers: + + - CacheProvider::deleteByWildcard + + - CacheProvider::deleteByRegEx + + - CacheProvider::deleteByPrefix + + - CacheProvider::deleteBySuffix + +- CacheProvider::deleteAll will not remove ALL entries, it will only mark them as invalid + +- CacheProvider::flushAll will remove ALL entries, namespaced or not + +- Added support to MemcachedCache + +- Added support to WincacheCache + +## ClassLoader Changes + +- ClassLoader::fileExistsInIncludePath() no longer exists. Use the native stream_resolve_include_path() PHP function \ No newline at end of file diff --git a/vendor/doctrine/common/build.properties b/vendor/doctrine/common/build.properties new file mode 100644 index 0000000..ff311a4 --- /dev/null +++ b/vendor/doctrine/common/build.properties @@ -0,0 +1,6 @@ +# Project Name +project.name=DoctrineCommon + +# Version class and file +project.version_class = Doctrine\Common\Version +project.version_file = lib/Doctrine/Common/Version.php diff --git a/vendor/doctrine/common/build.xml b/vendor/doctrine/common/build.xml new file mode 100644 index 0000000..9d9100d --- /dev/null +++ b/vendor/doctrine/common/build.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DoctrineCommon + Doctrine Common PHP Extensions + pear.doctrine-project.org + The Doctrine Common package contains shared code between the other packages. + + + + + MIT + + + - + + + + + + + diff --git a/vendor/doctrine/common/composer.json b/vendor/doctrine/common/composer.json new file mode 100644 index 0000000..24f9a2c --- /dev/null +++ b/vendor/doctrine/common/composer.json @@ -0,0 +1,31 @@ +{ + "name": "doctrine/common", + "type": "library", + "description": "Common Library for Doctrine projects", + "keywords": ["collections", "spl", "eventmanager", "annotations", "persistence"], + "homepage": "http://www.doctrine-project.org", + "license": "MIT", + "authors": [ + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, + {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, + {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} + ], + "require": { + "php": ">=5.3.2", + "doctrine/inflector": "1.*", + "doctrine/cache": "1.*", + "doctrine/collections": "1.*", + "doctrine/lexer": "1.*", + "doctrine/annotations": "1.*" + }, + "autoload": { + "psr-0": { "Doctrine\\Common\\": "lib/" } + }, + "extra": { + "branch-alias": { + "dev-master": "2.4.x-dev" + } + } +} diff --git a/vendor/doctrine/common/composer.lock b/vendor/doctrine/common/composer.lock new file mode 100644 index 0000000..1ed8a6b --- /dev/null +++ b/vendor/doctrine/common/composer.lock @@ -0,0 +1,322 @@ +{ + "hash": "f763db6a8f5bcaff6e51ae516283a4c9", + "packages": [ + { + "name": "doctrine/annotations", + "version": "v1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "v1.0" + }, + "dist": { + "type": "zip", + "url": "https://github.com/doctrine/annotations/archive/v1.0.zip", + "reference": "v1.0", + "shasum": "" + }, + "require": { + "php": ">=5.3.2", + "doctrine/lexer": "1.*" + }, + "require-dev": { + "doctrine/cache": "1.*" + }, + "time": "2013-01-12 19:23:32", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Doctrine\\Common\\Annotations\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com", + "homepage": "http://www.jwage.com/" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com", + "homepage": "http://www.instaclick.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "parser", + "docblock" + ] + }, + { + "name": "doctrine/cache", + "version": "v1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "v1.0" + }, + "dist": { + "type": "zip", + "url": "https://github.com/doctrine/cache/archive/v1.0.zip", + "reference": "v1.0", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "time": "2013-01-10 22:43:46", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Doctrine\\Common\\Cache\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com", + "homepage": "http://www.jwage.com/" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com", + "homepage": "http://www.instaclick.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "Caching library offering an object-oriented API for many cache backends", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ] + }, + { + "name": "doctrine/collections", + "version": "v1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "v1.0" + }, + "dist": { + "type": "zip", + "url": "https://github.com/doctrine/collections/archive/v1.0.zip", + "reference": "v1.0", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "time": "2013-01-12 16:36:50", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Doctrine\\Common\\Collections\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com", + "homepage": "http://www.jwage.com/" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com", + "homepage": "http://www.instaclick.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "Collections Abstraction library", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "collections", + "iterator", + "array" + ] + }, + { + "name": "doctrine/inflector", + "version": "v1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "v1.0" + }, + "dist": { + "type": "zip", + "url": "https://github.com/doctrine/inflector/archive/v1.0.zip", + "reference": "v1.0", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "time": "2013-01-10 21:49:15", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Doctrine\\Common\\Inflector\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com", + "homepage": "http://www.jwage.com/" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com", + "homepage": "http://www.instaclick.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "Common String Manipulations with regard to casing and singular/plural rules.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "string", + "inflection", + "singuarlize", + "pluarlize" + ] + }, + { + "name": "doctrine/lexer", + "version": "v1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "v1.0" + }, + "dist": { + "type": "zip", + "url": "https://github.com/doctrine/lexer/archive/v1.0.zip", + "reference": "v1.0", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "time": "2013-01-12 18:59:04", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com", + "homepage": "http://www.instaclick.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh", + "role": "Developer of wrapped JMSSerializerBundle" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "parser", + "lexer" + ] + } + ], + "packages-dev": null, + "aliases": [ + + ], + "minimum-stability": "stable", + "stability-flags": [ + + ] +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/ClassLoader.php b/vendor/doctrine/common/lib/Doctrine/Common/ClassLoader.php new file mode 100644 index 0000000..632805e --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/ClassLoader.php @@ -0,0 +1,288 @@ +. + */ + +namespace Doctrine\Common; + +/** + * A ClassLoader is an autoloader for class files that can be + * installed on the SPL autoload stack. It is a class loader that either loads only classes + * of a specific namespace or all namespaces and it is suitable for working together + * with other autoloaders in the SPL autoload stack. + * + * If no include path is configured through the constructor or {@link setIncludePath}, a ClassLoader + * relies on the PHP include_path. + * + * @author Roman Borschel + * @since 2.0 + */ +class ClassLoader +{ + /** + * PHP file extension. + * + * @var string + */ + protected $fileExtension = '.php'; + + /** + * Current namespace. + * + * @var string|null + */ + protected $namespace; + + /** + * Current include path. + * + * @var string|null + */ + protected $includePath; + + /** + * PHP namespace separator. + * + * @var string + */ + protected $namespaceSeparator = '\\'; + + /** + * Creates a new ClassLoader that loads classes of the + * specified namespace from the specified include path. + * + * If no include path is given, the ClassLoader relies on the PHP include_path. + * If neither a namespace nor an include path is given, the ClassLoader will + * be responsible for loading all classes, thereby relying on the PHP include_path. + * + * @param string|null $ns The namespace of the classes to load. + * @param string|null $includePath The base include path to use. + */ + public function __construct($ns = null, $includePath = null) + { + $this->namespace = $ns; + $this->includePath = $includePath; + } + + /** + * Sets the namespace separator used by classes in the namespace of this ClassLoader. + * + * @param string $sep The separator to use. + * + * @return void + */ + public function setNamespaceSeparator($sep) + { + $this->namespaceSeparator = $sep; + } + + /** + * Gets the namespace separator used by classes in the namespace of this ClassLoader. + * + * @return string + */ + public function getNamespaceSeparator() + { + return $this->namespaceSeparator; + } + + /** + * Sets the base include path for all class files in the namespace of this ClassLoader. + * + * @param string|null $includePath + * + * @return void + */ + public function setIncludePath($includePath) + { + $this->includePath = $includePath; + } + + /** + * Gets the base include path for all class files in the namespace of this ClassLoader. + * + * @return string|null + */ + public function getIncludePath() + { + return $this->includePath; + } + + /** + * Sets the file extension of class files in the namespace of this ClassLoader. + * + * @param string $fileExtension + * + * @return void + */ + public function setFileExtension($fileExtension) + { + $this->fileExtension = $fileExtension; + } + + /** + * Gets the file extension of class files in the namespace of this ClassLoader. + * + * @return string + */ + public function getFileExtension() + { + return $this->fileExtension; + } + + /** + * Registers this ClassLoader on the SPL autoload stack. + * + * @return void + */ + public function register() + { + spl_autoload_register(array($this, 'loadClass')); + } + + /** + * Removes this ClassLoader from the SPL autoload stack. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $className The name of the class to load. + * + * @return boolean TRUE if the class has been successfully loaded, FALSE otherwise. + */ + public function loadClass($className) + { + if ($this->namespace !== null && strpos($className, $this->namespace.$this->namespaceSeparator) !== 0) { + return false; + } + + require ($this->includePath !== null ? $this->includePath . DIRECTORY_SEPARATOR : '') + . str_replace($this->namespaceSeparator, DIRECTORY_SEPARATOR, $className) + . $this->fileExtension; + + return true; + } + + /** + * Asks this ClassLoader whether it can potentially load the class (file) with + * the given name. + * + * @param string $className The fully-qualified name of the class. + * + * @return boolean TRUE if this ClassLoader can load the class, FALSE otherwise. + */ + public function canLoadClass($className) + { + if ($this->namespace !== null && strpos($className, $this->namespace.$this->namespaceSeparator) !== 0) { + return false; + } + + $file = str_replace($this->namespaceSeparator, DIRECTORY_SEPARATOR, $className) . $this->fileExtension; + + if ($this->includePath !== null) { + return is_file($this->includePath . DIRECTORY_SEPARATOR . $file); + } + + return (false !== stream_resolve_include_path($file)); + } + + /** + * Checks whether a class with a given name exists. A class "exists" if it is either + * already defined in the current request or if there is an autoloader on the SPL + * autoload stack that is a) responsible for the class in question and b) is able to + * load a class file in which the class definition resides. + * + * If the class is not already defined, each autoloader in the SPL autoload stack + * is asked whether it is able to tell if the class exists. If the autoloader is + * a ClassLoader, {@link canLoadClass} is used, otherwise the autoload + * function of the autoloader is invoked and expected to return a value that + * evaluates to TRUE if the class (file) exists. As soon as one autoloader reports + * that the class exists, TRUE is returned. + * + * Note that, depending on what kinds of autoloaders are installed on the SPL + * autoload stack, the class (file) might already be loaded as a result of checking + * for its existence. This is not the case with a ClassLoader, who separates + * these responsibilities. + * + * @param string $className The fully-qualified name of the class. + * + * @return boolean TRUE if the class exists as per the definition given above, FALSE otherwise. + */ + public static function classExists($className) + { + if (class_exists($className, false) || interface_exists($className, false)) { + return true; + } + + foreach (spl_autoload_functions() as $loader) { + if (is_array($loader)) { // array(???, ???) + if (is_object($loader[0])) { + if ($loader[0] instanceof ClassLoader) { // array($obj, 'methodName') + if ($loader[0]->canLoadClass($className)) { + return true; + } + } else if ($loader[0]->{$loader[1]}($className)) { + return true; + } + } else if ($loader[0]::$loader[1]($className)) { // array('ClassName', 'methodName') + return true; + } + } else if ($loader instanceof \Closure) { // function($className) {..} + if ($loader($className)) { + return true; + } + } else if (is_string($loader) && $loader($className)) { // "MyClass::loadClass" + return true; + } + + if (class_exists($className, false) || interface_exists($className, false)) { + return true; + } + } + + return false; + } + + /** + * Gets the ClassLoader from the SPL autoload stack that is responsible + * for (and is able to load) the class with the given name. + * + * @param string $className The name of the class. + * + * @return ClassLoader The ClassLoader for the class or NULL if no such ClassLoader exists. + */ + public static function getClassLoader($className) + { + foreach (spl_autoload_functions() as $loader) { + if (is_array($loader) + && $loader[0] instanceof ClassLoader + && $loader[0]->canLoadClass($className) + ) { + return $loader[0]; + } + } + + return null; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/CommonException.php b/vendor/doctrine/common/lib/Doctrine/Common/CommonException.php new file mode 100644 index 0000000..2a1a08e --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/CommonException.php @@ -0,0 +1,29 @@ +. + */ + +namespace Doctrine\Common; + +/** + * Base exception class for package Doctrine\Common. + * + * @author heinrich + */ +class CommonException extends \Exception +{ +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Comparable.php b/vendor/doctrine/common/lib/Doctrine/Common/Comparable.php new file mode 100644 index 0000000..8cd02c9 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Comparable.php @@ -0,0 +1,46 @@ +. + */ + +namespace Doctrine\Common; + +/** + * Comparable interface that allows to compare two value objects to each other for similarity. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + */ +interface Comparable +{ + /** + * Compares the current object to the passed $other. + * + * Returns 0 if they are semantically equal, 1 if the other object + * is less than the current one, or -1 if its more than the current one. + * + * This method should not check for identity using ===, only for semantical equality for example + * when two different DateTime instances point to the exact same Date + TZ. + * + * @param mixed $other + * + * @return int + */ + public function compareTo($other); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/EventArgs.php b/vendor/doctrine/common/lib/Doctrine/Common/EventArgs.php new file mode 100644 index 0000000..75506e6 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/EventArgs.php @@ -0,0 +1,67 @@ +. + */ + +namespace Doctrine\Common; + +/** + * EventArgs is the base class for classes containing event data. + * + * This class contains no event data. It is used by events that do not pass state + * information to an event handler when an event is raised. The single empty EventArgs + * instance can be obtained through {@link getEmptyInstance}. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class EventArgs +{ + /** + * Single instance of EventArgs. + * + * @var EventArgs + */ + private static $_emptyEventArgsInstance; + + /** + * Gets the single, empty and immutable EventArgs instance. + * + * This instance will be used when events are dispatched without any parameter, + * like this: EventManager::dispatchEvent('eventname'); + * + * The benefit from this is that only one empty instance is instantiated and shared + * (otherwise there would be instances for every dispatched in the abovementioned form). + * + * @see EventManager::dispatchEvent + * + * @link http://msdn.microsoft.com/en-us/library/system.eventargs.aspx + * + * @return EventArgs + */ + public static function getEmptyInstance() + { + if ( ! self::$_emptyEventArgsInstance) { + self::$_emptyEventArgsInstance = new EventArgs; + } + + return self::$_emptyEventArgsInstance; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/EventManager.php b/vendor/doctrine/common/lib/Doctrine/Common/EventManager.php new file mode 100644 index 0000000..69eb17e --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/EventManager.php @@ -0,0 +1,154 @@ +. + */ + +namespace Doctrine\Common; + +/** + * The EventManager is the central point of Doctrine's event listener system. + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class EventManager +{ + /** + * Map of registered listeners. + * => + * + * @var array + */ + private $_listeners = array(); + + /** + * Dispatches an event to all registered listeners. + * + * @param string $eventName The name of the event to dispatch. The name of the event is + * the name of the method that is invoked on listeners. + * @param EventArgs|null $eventArgs The event arguments to pass to the event handlers/listeners. + * If not supplied, the single empty EventArgs instance is used. + * + * @return boolean + */ + public function dispatchEvent($eventName, EventArgs $eventArgs = null) + { + if (isset($this->_listeners[$eventName])) { + $eventArgs = $eventArgs === null ? EventArgs::getEmptyInstance() : $eventArgs; + + foreach ($this->_listeners[$eventName] as $listener) { + $listener->$eventName($eventArgs); + } + } + } + + /** + * Gets the listeners of a specific event or all listeners. + * + * @param string|null $event The name of the event. + * + * @return array The event listeners for the specified event, or all event listeners. + */ + public function getListeners($event = null) + { + return $event ? $this->_listeners[$event] : $this->_listeners; + } + + /** + * Checks whether an event has any registered listeners. + * + * @param string $event + * + * @return boolean TRUE if the specified event has any listeners, FALSE otherwise. + */ + public function hasListeners($event) + { + return isset($this->_listeners[$event]) && $this->_listeners[$event]; + } + + /** + * Adds an event listener that listens on the specified events. + * + * @param string|array $events The event(s) to listen on. + * @param object $listener The listener object. + * + * @return void + */ + public function addEventListener($events, $listener) + { + // Picks the hash code related to that listener + $hash = spl_object_hash($listener); + + foreach ((array) $events as $event) { + // Overrides listener if a previous one was associated already + // Prevents duplicate listeners on same event (same instance only) + $this->_listeners[$event][$hash] = $listener; + } + } + + /** + * Removes an event listener from the specified events. + * + * @param string|array $events + * @param object $listener + * + * @return void + */ + public function removeEventListener($events, $listener) + { + // Picks the hash code related to that listener + $hash = spl_object_hash($listener); + + foreach ((array) $events as $event) { + // Check if actually have this listener associated + if (isset($this->_listeners[$event][$hash])) { + unset($this->_listeners[$event][$hash]); + } + } + } + + /** + * Adds an EventSubscriber. The subscriber is asked for all the events it is + * interested in and added as a listener for these events. + * + * @param \Doctrine\Common\EventSubscriber $subscriber The subscriber. + * + * @return void + */ + public function addEventSubscriber(EventSubscriber $subscriber) + { + $this->addEventListener($subscriber->getSubscribedEvents(), $subscriber); + } + + /** + * Removes an EventSubscriber. The subscriber is asked for all the events it is + * interested in and removed as a listener for these events. + * + * @param \Doctrine\Common\EventSubscriber $subscriber The subscriber. + * + * @return void + */ + public function removeEventSubscriber(EventSubscriber $subscriber) + { + $this->removeEventListener($subscriber->getSubscribedEvents(), $subscriber); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/EventSubscriber.php b/vendor/doctrine/common/lib/Doctrine/Common/EventSubscriber.php new file mode 100644 index 0000000..55d0f7d --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/EventSubscriber.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\Common; + +/** + * An EventSubscriber knows himself what events he is interested in. + * If an EventSubscriber is added to an EventManager, the manager invokes + * {@link getSubscribedEvents} and registers the subscriber as a listener for all + * returned events. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +interface EventSubscriber +{ + /** + * Returns an array of events this subscriber wants to listen to. + * + * @return array + */ + public function getSubscribedEvents(); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Lexer.php b/vendor/doctrine/common/lib/Doctrine/Common/Lexer.php new file mode 100644 index 0000000..0aa07f8 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Lexer.php @@ -0,0 +1,37 @@ +. + */ + +namespace Doctrine\Common; + +use Doctrine\Common\Lexer\AbstractLexer; + +/** + * Base class for writing simple lexers, i.e. for creating small DSLs. + * + * Lexer moved into its own Component Doctrine\Common\Lexer. This class + * only stays for being BC. + * + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +abstract class Lexer extends AbstractLexer +{ +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/NotifyPropertyChanged.php b/vendor/doctrine/common/lib/Doctrine/Common/NotifyPropertyChanged.php new file mode 100644 index 0000000..e25e999 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/NotifyPropertyChanged.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\Common; + +/** + * Contract for classes that provide the service of notifying listeners of + * changes to their properties. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +interface NotifyPropertyChanged +{ + /** + * Adds a listener that wants to be notified about property changes. + * + * @param PropertyChangedListener $listener + * + * @return void + */ + public function addPropertyChangedListener(PropertyChangedListener $listener); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/AbstractManagerRegistry.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/AbstractManagerRegistry.php new file mode 100644 index 0000000..15b5aa3 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/AbstractManagerRegistry.php @@ -0,0 +1,259 @@ +. + */ + +namespace Doctrine\Common\Persistence; + +use Doctrine\Common\Persistence\ManagerRegistry; + +/** + * Abstract implementation of the ManagerRegistry contract. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Fabien Potencier + * @author Benjamin Eberlei + * @author Lukas Kahwe Smith + */ +abstract class AbstractManagerRegistry implements ManagerRegistry +{ + /** + * @var string + */ + private $name; + + /** + * @var array + */ + private $connections; + + /** + * @var array + */ + private $managers; + + /** + * @var string + */ + private $defaultConnection; + + /** + * @var string + */ + private $defaultManager; + + /** + * @var string + */ + private $proxyInterfaceName; + + /** + * Constructor. + * + * @param string $name + * @param array $connections + * @param array $managers + * @param string $defaultConnection + * @param string $defaultManager + * @param string $proxyInterfaceName + */ + public function __construct($name, array $connections, array $managers, $defaultConnection, $defaultManager, $proxyInterfaceName) + { + $this->name = $name; + $this->connections = $connections; + $this->managers = $managers; + $this->defaultConnection = $defaultConnection; + $this->defaultManager = $defaultManager; + $this->proxyInterfaceName = $proxyInterfaceName; + } + + /** + * Fetches/creates the given services. + * + * A service in this context is connection or a manager instance. + * + * @param string $name The name of the service. + * + * @return object The instance of the given service. + */ + abstract protected function getService($name); + + /** + * Resets the given services. + * + * A service in this context is connection or a manager instance. + * + * @param string $name The name of the service. + * + * @return void + */ + abstract protected function resetService($name); + + /** + * Gets the name of the registry. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getConnection($name = null) + { + if (null === $name) { + $name = $this->defaultConnection; + } + + if (!isset($this->connections[$name])) { + throw new \InvalidArgumentException(sprintf('Doctrine %s Connection named "%s" does not exist.', $this->name, $name)); + } + + return $this->getService($this->connections[$name]); + } + + /** + * {@inheritdoc} + */ + public function getConnectionNames() + { + return $this->connections; + } + + /** + * {@inheritdoc} + */ + public function getConnections() + { + $connections = array(); + foreach ($this->connections as $name => $id) { + $connections[$name] = $this->getService($id); + } + + return $connections; + } + + /** + * {@inheritdoc} + */ + public function getDefaultConnectionName() + { + return $this->defaultConnection; + } + + /** + * {@inheritdoc} + */ + public function getDefaultManagerName() + { + return $this->defaultManager; + } + + /** + * {@inheritdoc} + * + * @throws \InvalidArgumentException + */ + public function getManager($name = null) + { + if (null === $name) { + $name = $this->defaultManager; + } + + if (!isset($this->managers[$name])) { + throw new \InvalidArgumentException(sprintf('Doctrine %s Manager named "%s" does not exist.', $this->name, $name)); + } + + return $this->getService($this->managers[$name]); + } + + /** + * {@inheritdoc} + */ + public function getManagerForClass($class) + { + // Check for namespace alias + if (strpos($class, ':') !== false) { + list($namespaceAlias, $simpleClassName) = explode(':', $class); + $class = $this->getAliasNamespace($namespaceAlias) . '\\' . $simpleClassName; + } + + $proxyClass = new \ReflectionClass($class); + if ($proxyClass->implementsInterface($this->proxyInterfaceName)) { + $class = $proxyClass->getParentClass()->getName(); + } + + foreach ($this->managers as $id) { + $manager = $this->getService($id); + + if (!$manager->getMetadataFactory()->isTransient($class)) { + return $manager; + } + } + } + + /** + * {@inheritdoc} + */ + public function getManagerNames() + { + return $this->managers; + } + + /** + * {@inheritdoc} + */ + public function getManagers() + { + $dms = array(); + foreach ($this->managers as $name => $id) { + $dms[$name] = $this->getService($id); + } + + return $dms; + } + + /** + * {@inheritdoc} + */ + public function getRepository($persistentObjectName, $persistentManagerName = null) + { + return $this->getManager($persistentManagerName)->getRepository($persistentObjectName); + } + + /** + * {@inheritdoc} + */ + public function resetManager($name = null) + { + if (null === $name) { + $name = $this->defaultManager; + } + + if (!isset($this->managers[$name])) { + throw new \InvalidArgumentException(sprintf('Doctrine %s Manager named "%s" does not exist.', $this->name, $name)); + } + + // force the creation of a new document manager + // if the current one is closed + $this->resetService($this->managers[$name]); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ConnectionRegistry.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ConnectionRegistry.php new file mode 100644 index 0000000..7c25e98 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ConnectionRegistry.php @@ -0,0 +1,62 @@ +. + */ + +namespace Doctrine\Common\Persistence; + +/** + * Contract covering connection for a Doctrine persistence layer ManagerRegistry class to implement. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Fabien Potencier + * @author Benjamin Eberlei + * @author Lukas Kahwe Smith + */ +interface ConnectionRegistry +{ + /** + * Gets the default connection name. + * + * @return string The default connection name. + */ + public function getDefaultConnectionName(); + + /** + * Gets the named connection. + * + * @param string $name The connection name (null for the default one). + * + * @return object + */ + public function getConnection($name = null); + + /** + * Gets an array of all registered connections. + * + * @return array An array of Connection instances. + */ + public function getConnections(); + + /** + * Gets all connection names. + * + * @return array An array of connection names. + */ + public function getConnectionNames(); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/LifecycleEventArgs.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/LifecycleEventArgs.php new file mode 100644 index 0000000..52f41c0 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/LifecycleEventArgs.php @@ -0,0 +1,89 @@ +. + */ + +namespace Doctrine\Common\Persistence\Event; + +use Doctrine\Common\EventArgs; +use Doctrine\Common\Persistence\ObjectManager; + +/** + * Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions + * of entities. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Roman Borschel + * @author Benjamin Eberlei + */ +class LifecycleEventArgs extends EventArgs +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var object + */ + private $object; + + /** + * Constructor. + * + * @param object $object + * @param ObjectManager $objectManager + */ + public function __construct($object, ObjectManager $objectManager) + { + $this->object = $object; + $this->objectManager = $objectManager; + } + + /** + * Retrieves the associated entity. + * + * @deprecated + * + * @return object + */ + public function getEntity() + { + return $this->object; + } + + /** + * Retrieves the associated object. + * + * @return object + */ + public function getObject() + { + return $this->object; + } + + /** + * Retrieves the associated ObjectManager. + * + * @return ObjectManager + */ + public function getObjectManager() + { + return $this->objectManager; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/LoadClassMetadataEventArgs.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/LoadClassMetadataEventArgs.php new file mode 100644 index 0000000..07770bb --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/LoadClassMetadataEventArgs.php @@ -0,0 +1,75 @@ +. + */ + +namespace Doctrine\Common\Persistence\Event; + +use Doctrine\Common\EventArgs; +use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\Common\Persistence\Mapping\ClassMetadata; + +/** + * Class that holds event arguments for a loadMetadata event. + * + * @author Jonathan H. Wage + * @since 2.2 + */ +class LoadClassMetadataEventArgs extends EventArgs +{ + /** + * @var ClassMetadata + */ + private $classMetadata; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * Constructor. + * + * @param ClassMetadata $classMetadata + * @param ObjectManager $objectManager + */ + public function __construct(ClassMetadata $classMetadata, ObjectManager $objectManager) + { + $this->classMetadata = $classMetadata; + $this->objectManager = $objectManager; + } + + /** + * Retrieves the associated ClassMetadata. + * + * @return ClassMetadata + */ + public function getClassMetadata() + { + return $this->classMetadata; + } + + /** + * Retrieves the associated ObjectManager. + * + * @return ObjectManager + */ + public function getObjectManager() + { + return $this->objectManager; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/ManagerEventArgs.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/ManagerEventArgs.php new file mode 100644 index 0000000..5527d4d --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/ManagerEventArgs.php @@ -0,0 +1,59 @@ +. + */ + +namespace Doctrine\Common\Persistence\Event; + +use Doctrine\Common\EventArgs; +use Doctrine\Common\Persistence\ObjectManager; + +/** + * Provides event arguments for the preFlush event. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Roman Borschel + * @author Benjamin Eberlei + */ +class ManagerEventArgs extends EventArgs +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * Constructor. + * + * @param ObjectManager $objectManager + */ + public function __construct(ObjectManager $objectManager) + { + $this->objectManager = $objectManager; + } + + /** + * Retrieves the associated ObjectManager. + * + * @return ObjectManager + */ + public function getObjectManager() + { + return $this->objectManager; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/OnClearEventArgs.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/OnClearEventArgs.php new file mode 100644 index 0000000..b78bad9 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/OnClearEventArgs.php @@ -0,0 +1,86 @@ +. + */ + +namespace Doctrine\Common\Persistence\Event; + +use Doctrine\Common\EventArgs; +use Doctrine\Common\Persistence\ObjectManager; + +/** + * Provides event arguments for the onClear event. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Roman Borschel + * @author Benjamin Eberlei + */ +class OnClearEventArgs extends EventArgs +{ + /** + * @var \Doctrine\Common\Persistence\ObjectManager + */ + private $objectManager; + + /** + * @var string|null + */ + private $entityClass; + + /** + * Constructor. + * + * @param ObjectManager $objectManager The object manager. + * @param string|null $entityClass The optional entity class. + */ + public function __construct($objectManager, $entityClass = null) + { + $this->objectManager = $objectManager; + $this->entityClass = $entityClass; + } + + /** + * Retrieves the associated ObjectManager. + * + * @return \Doctrine\Common\Persistence\ObjectManager + */ + public function getObjectManager() + { + return $this->objectManager; + } + + /** + * Returns the name of the entity class that is cleared, or null if all are cleared. + * + * @return string|null + */ + public function getEntityClass() + { + return $this->entityClass; + } + + /** + * Returns whether this event clears all entities. + * + * @return bool + */ + public function clearsAllEntities() + { + return ($this->entityClass === null); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/PreUpdateEventArgs.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/PreUpdateEventArgs.php new file mode 100644 index 0000000..d34de84 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Event/PreUpdateEventArgs.php @@ -0,0 +1,138 @@ +. + */ + +namespace Doctrine\Common\Persistence\Event; + +use Doctrine\Common\EventArgs; +use Doctrine\Common\Persistence\ObjectManager; + +/** + * Class that holds event arguments for a preUpdate event. + * + * @author Guilherme Blanco + * @author Roman Borschel + * @author Benjamin Eberlei + * @since 2.2 + */ +class PreUpdateEventArgs extends LifecycleEventArgs +{ + /** + * @var array + */ + private $entityChangeSet; + + /** + * Constructor. + * + * @param object $entity + * @param ObjectManager $objectManager + * @param array $changeSet + */ + public function __construct($entity, ObjectManager $objectManager, array &$changeSet) + { + parent::__construct($entity, $objectManager); + + $this->entityChangeSet = &$changeSet; + } + + /** + * Retrieves the entity changeset. + * + * @return array + */ + public function getEntityChangeSet() + { + return $this->entityChangeSet; + } + + /** + * Checks if field has a changeset. + * + * @param string $field + * + * @return boolean + */ + public function hasChangedField($field) + { + return isset($this->entityChangeSet[$field]); + } + + /** + * Gets the old value of the changeset of the changed field. + * + * @param string $field + * + * @return mixed + */ + public function getOldValue($field) + { + $this->assertValidField($field); + + return $this->entityChangeSet[$field][0]; + } + + /** + * Gets the new value of the changeset of the changed field. + * + * @param string $field + * + * @return mixed + */ + public function getNewValue($field) + { + $this->assertValidField($field); + + return $this->entityChangeSet[$field][1]; + } + + /** + * Sets the new value of this field. + * + * @param string $field + * @param mixed $value + * + * @return void + */ + public function setNewValue($field, $value) + { + $this->assertValidField($field); + + $this->entityChangeSet[$field][1] = $value; + } + + /** + * Asserts the field exists in changeset. + * + * @param string $field + * + * @return void + * + * @throws \InvalidArgumentException + */ + private function assertValidField($field) + { + if ( ! isset($this->entityChangeSet[$field])) { + throw new \InvalidArgumentException(sprintf( + 'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.', + $field, + get_class($this->getEntity()) + )); + } + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ManagerRegistry.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ManagerRegistry.php new file mode 100644 index 0000000..fce854b --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ManagerRegistry.php @@ -0,0 +1,111 @@ +. + */ + +namespace Doctrine\Common\Persistence; + +/** + * Contract covering object managers for a Doctrine persistence layer ManagerRegistry class to implement. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Fabien Potencier + * @author Benjamin Eberlei + * @author Lukas Kahwe Smith + */ +interface ManagerRegistry extends ConnectionRegistry +{ + /** + * Gets the default object manager name. + * + * @return string The default object manager name. + */ + public function getDefaultManagerName(); + + /** + * Gets a named object manager. + * + * @param string $name The object manager name (null for the default one). + * + * @return \Doctrine\Common\Persistence\ObjectManager + */ + public function getManager($name = null); + + /** + * Gets an array of all registered object managers. + * + * @return \Doctrine\Common\Persistence\ObjectManager[] An array of ObjectManager instances + */ + public function getManagers(); + + /** + * Resets a named object manager. + * + * This method is useful when an object manager has been closed + * because of a rollbacked transaction AND when you think that + * it makes sense to get a new one to replace the closed one. + * + * Be warned that you will get a brand new object manager as + * the existing one is not useable anymore. This means that any + * other object with a dependency on this object manager will + * hold an obsolete reference. You can inject the registry instead + * to avoid this problem. + * + * @param string|null $name The object manager name (null for the default one). + * + * @return \Doctrine\Common\Persistence\ObjectManager + */ + public function resetManager($name = null); + + /** + * Resolves a registered namespace alias to the full namespace. + * + * This method looks for the alias in all registered object managers. + * + * @param string $alias The alias. + * + * @return string The full namespace. + */ + public function getAliasNamespace($alias); + + /** + * Gets all connection names. + * + * @return array An array of connection names. + */ + public function getManagerNames(); + + /** + * Gets the ObjectRepository for an persistent object. + * + * @param string $persistentObject The name of the persistent object. + * @param string $persistentManagerName The object manager name (null for the default one). + * + * @return \Doctrine\Common\Persistence\ObjectRepository + */ + public function getRepository($persistentObject, $persistentManagerName = null); + + /** + * Gets the object manager associated with a given class. + * + * @param string $class A persistent object class name. + * + * @return \Doctrine\Common\Persistence\ObjectManager|null + */ + public function getManagerForClass($class); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php new file mode 100644 index 0000000..01ad58f --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/AbstractClassMetadataFactory.php @@ -0,0 +1,401 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping; + +use Doctrine\Common\Cache\Cache; +use Doctrine\Common\Util\ClassUtils; + +/** + * The ClassMetadataFactory is used to create ClassMetadata objects that contain all the + * metadata mapping informations of a class which describes how a class should be mapped + * to a relational database. + * + * This class was abstracted from the ORM ClassMetadataFactory. + * + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +abstract class AbstractClassMetadataFactory implements ClassMetadataFactory +{ + /** + * Salt used by specific Object Manager implementation. + * + * @var string + */ + protected $cacheSalt = '$CLASSMETADATA'; + + /** + * @var \Doctrine\Common\Cache\Cache|null + */ + private $cacheDriver; + + /** + * @var array + */ + private $loadedMetadata = array(); + + /** + * @var bool + */ + protected $initialized = false; + + /** + * @var ReflectionService|null + */ + private $reflectionService = null; + + /** + * Sets the cache driver used by the factory to cache ClassMetadata instances. + * + * @param \Doctrine\Common\Cache\Cache $cacheDriver + * + * @return void + */ + public function setCacheDriver(Cache $cacheDriver = null) + { + $this->cacheDriver = $cacheDriver; + } + + /** + * Gets the cache driver used by the factory to cache ClassMetadata instances. + * + * @return \Doctrine\Common\Cache\Cache|null + */ + public function getCacheDriver() + { + return $this->cacheDriver; + } + + /** + * Returns an array of all the loaded metadata currently in memory. + * + * @return array + */ + public function getLoadedMetadata() + { + return $this->loadedMetadata; + } + + /** + * Forces the factory to load the metadata of all classes known to the underlying + * mapping driver. + * + * @return array The ClassMetadata instances of all mapped classes. + */ + public function getAllMetadata() + { + if ( ! $this->initialized) { + $this->initialize(); + } + + $driver = $this->getDriver(); + $metadata = array(); + foreach ($driver->getAllClassNames() as $className) { + $metadata[] = $this->getMetadataFor($className); + } + + return $metadata; + } + + /** + * Lazy initialization of this stuff, especially the metadata driver, + * since these are not needed at all when a metadata cache is active. + * + * @return void + */ + abstract protected function initialize(); + + /** + * Gets the fully qualified class-name from the namespace alias. + * + * @param string $namespaceAlias + * @param string $simpleClassName + * + * @return string + */ + abstract protected function getFqcnFromAlias($namespaceAlias, $simpleClassName); + + /** + * Returns the mapping driver implementation. + * + * @return \Doctrine\Common\Persistence\Mapping\Driver\MappingDriver + */ + abstract protected function getDriver(); + + /** + * Wakes up reflection after ClassMetadata gets unserialized from cache. + * + * @param ClassMetadata $class + * @param ReflectionService $reflService + * + * @return void + */ + abstract protected function wakeupReflection(ClassMetadata $class, ReflectionService $reflService); + + /** + * Initializes Reflection after ClassMetadata was constructed. + * + * @param ClassMetadata $class + * @param ReflectionService $reflService + * + * @return void + */ + abstract protected function initializeReflection(ClassMetadata $class, ReflectionService $reflService); + + /** + * Checks whether the class metadata is an entity. + * + * This method should return false for mapped superclasses or embedded classes. + * + * @param ClassMetadata $class + * + * @return boolean + */ + abstract protected function isEntity(ClassMetadata $class); + + /** + * Gets the class metadata descriptor for a class. + * + * @param string $className The name of the class. + * + * @return \Doctrine\Common\Persistence\Mapping\ClassMetadata + */ + public function getMetadataFor($className) + { + if (isset($this->loadedMetadata[$className])) { + return $this->loadedMetadata[$className]; + } + + $realClassName = $className; + + // Check for namespace alias + if (strpos($className, ':') !== false) { + list($namespaceAlias, $simpleClassName) = explode(':', $className); + $realClassName = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName); + } else { + $realClassName = ClassUtils::getRealClass($realClassName); + } + + if (isset($this->loadedMetadata[$realClassName])) { + // We do not have the alias name in the map, include it + $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName]; + + return $this->loadedMetadata[$realClassName]; + } + + if ($this->cacheDriver) { + if (($cached = $this->cacheDriver->fetch($realClassName . $this->cacheSalt)) !== false) { + $this->loadedMetadata[$realClassName] = $cached; + $this->wakeupReflection($cached, $this->getReflectionService()); + } else { + foreach ($this->loadMetadata($realClassName) as $loadedClassName) { + $this->cacheDriver->save( + $loadedClassName . $this->cacheSalt, $this->loadedMetadata[$loadedClassName], null + ); + } + } + } else { + $this->loadMetadata($realClassName); + } + + if ($className != $realClassName) { + // We do not have the alias name in the map, include it + $this->loadedMetadata[$className] = $this->loadedMetadata[$realClassName]; + } + + return $this->loadedMetadata[$className]; + } + + /** + * Checks whether the factory has the metadata for a class loaded already. + * + * @param string $className + * + * @return boolean TRUE if the metadata of the class in question is already loaded, FALSE otherwise. + */ + public function hasMetadataFor($className) + { + return isset($this->loadedMetadata[$className]); + } + + /** + * Sets the metadata descriptor for a specific class. + * + * NOTE: This is only useful in very special cases, like when generating proxy classes. + * + * @param string $className + * @param ClassMetadata $class + * + * @return void + */ + public function setMetadataFor($className, $class) + { + $this->loadedMetadata[$className] = $class; + } + + /** + * Gets an array of parent classes for the given entity class. + * + * @param string $name + * + * @return array + */ + protected function getParentClasses($name) + { + // Collect parent classes, ignoring transient (not-mapped) classes. + $parentClasses = array(); + foreach (array_reverse($this->getReflectionService()->getParentClasses($name)) as $parentClass) { + if ( ! $this->getDriver()->isTransient($parentClass)) { + $parentClasses[] = $parentClass; + } + } + return $parentClasses; + } + + /** + * Loads the metadata of the class in question and all it's ancestors whose metadata + * is still not loaded. + * + * Important: The class $name does not necesarily exist at this point here. + * Scenarios in a code-generation setup might have access to XML/YAML + * Mapping files without the actual PHP code existing here. That is why the + * {@see Doctrine\Common\Persistence\Mapping\ReflectionService} interface + * should be used for reflection. + * + * @param string $name The name of the class for which the metadata should get loaded. + * + * @return array + */ + protected function loadMetadata($name) + { + if ( ! $this->initialized) { + $this->initialize(); + } + + $loaded = array(); + + $parentClasses = $this->getParentClasses($name); + $parentClasses[] = $name; + + // Move down the hierarchy of parent classes, starting from the topmost class + $parent = null; + $rootEntityFound = false; + $visited = array(); + $reflService = $this->getReflectionService(); + foreach ($parentClasses as $className) { + if (isset($this->loadedMetadata[$className])) { + $parent = $this->loadedMetadata[$className]; + if ($this->isEntity($parent)) { + $rootEntityFound = true; + array_unshift($visited, $className); + } + continue; + } + + $class = $this->newClassMetadataInstance($className); + $this->initializeReflection($class, $reflService); + + $this->doLoadMetadata($class, $parent, $rootEntityFound, $visited); + + $this->loadedMetadata[$className] = $class; + + $parent = $class; + + if ($this->isEntity($class)) { + $rootEntityFound = true; + array_unshift($visited, $className); + } + + $this->wakeupReflection($class, $reflService); + + $loaded[] = $className; + } + + return $loaded; + } + + /** + * Actually loads the metadata from the underlying metadata. + * + * @param ClassMetadata $class + * @param ClassMetadata|null $parent + * @param bool $rootEntityFound + * @param array $nonSuperclassParents All parent class names + * that are not marked as mapped superclasses. + * + * @return void + */ + abstract protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents); + + /** + * Creates a new ClassMetadata instance for the given class name. + * + * @param string $className + * + * @return ClassMetadata + */ + abstract protected function newClassMetadataInstance($className); + + /** + * {@inheritDoc} + */ + public function isTransient($class) + { + if ( ! $this->initialized) { + $this->initialize(); + } + + // Check for namespace alias + if (strpos($class, ':') !== false) { + list($namespaceAlias, $simpleClassName) = explode(':', $class); + $class = $this->getFqcnFromAlias($namespaceAlias, $simpleClassName); + } + + return $this->getDriver()->isTransient($class); + } + + /** + * Sets the reflectionService. + * + * @param ReflectionService $reflectionService + * + * @return void + */ + public function setReflectionService(ReflectionService $reflectionService) + { + $this->reflectionService = $reflectionService; + } + + /** + * Gets the reflection service associated with this metadata factory. + * + * @return ReflectionService + */ + public function getReflectionService() + { + if ($this->reflectionService === null) { + $this->reflectionService = new RuntimeReflectionService(); + } + return $this->reflectionService; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ClassMetadata.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ClassMetadata.php new file mode 100644 index 0000000..b8445f1 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ClassMetadata.php @@ -0,0 +1,174 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping; + +/** + * Contract for a Doctrine persistence layer ClassMetadata class to implement. + * + * @link www.doctrine-project.org + * @since 2.1 + * @author Benjamin Eberlei + * @author Jonathan Wage + */ +interface ClassMetadata +{ + /** + * Gets the fully-qualified class name of this persistent class. + * + * @return string + */ + public function getName(); + + /** + * Gets the mapped identifier field name. + * + * The returned structure is an array of the identifier field names. + * + * @return array + */ + public function getIdentifier(); + + /** + * Gets the ReflectionClass instance for this mapped class. + * + * @return \ReflectionClass + */ + public function getReflectionClass(); + + /** + * Checks if the given field name is a mapped identifier for this class. + * + * @param string $fieldName + * + * @return boolean + */ + public function isIdentifier($fieldName); + + /** + * Checks if the given field is a mapped property for this class. + * + * @param string $fieldName + * + * @return boolean + */ + public function hasField($fieldName); + + /** + * Checks if the given field is a mapped association for this class. + * + * @param string $fieldName + * + * @return boolean + */ + public function hasAssociation($fieldName); + + /** + * Checks if the given field is a mapped single valued association for this class. + * + * @param string $fieldName + * + * @return boolean + */ + public function isSingleValuedAssociation($fieldName); + + /** + * Checks if the given field is a mapped collection valued association for this class. + * + * @param string $fieldName + * + * @return boolean + */ + public function isCollectionValuedAssociation($fieldName); + + /** + * A numerically indexed list of field names of this persistent class. + * + * This array includes identifier fields if present on this class. + * + * @return array + */ + public function getFieldNames(); + + /** + * Returns an array of identifier field names numerically indexed. + * + * @return array + */ + public function getIdentifierFieldNames(); + + /** + * Returns a numerically indexed list of association names of this persistent class. + * + * This array includes identifier associations if present on this class. + * + * @return array + */ + public function getAssociationNames(); + + /** + * Returns a type name of this field. + * + * This type names can be implementation specific but should at least include the php types: + * integer, string, boolean, float/double, datetime. + * + * @param string $fieldName + * + * @return string + */ + public function getTypeOfField($fieldName); + + /** + * Returns the target class name of the given association. + * + * @param string $assocName + * + * @return string + */ + public function getAssociationTargetClass($assocName); + + /** + * Checks if the association is the inverse side of a bidirectional association. + * + * @param string $assocName + * + * @return boolean + */ + public function isAssociationInverseSide($assocName); + + /** + * Returns the target field of the owning side of the association. + * + * @param string $assocName + * + * @return string + */ + public function getAssociationMappedByTargetField($assocName); + + /** + * Returns the identifier of this object as an array with field name as key. + * + * Has to return an empty array if no identifier isset. + * + * @param object $object + * + * @return array + */ + public function getIdentifierValues($object); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ClassMetadataFactory.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ClassMetadataFactory.php new file mode 100644 index 0000000..3d82881 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ClassMetadataFactory.php @@ -0,0 +1,76 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping; + +/** + * Contract for a Doctrine persistence layer ClassMetadata class to implement. + * + * @link www.doctrine-project.org + * @since 2.1 + * @author Benjamin Eberlei + * @author Jonathan Wage + */ +interface ClassMetadataFactory +{ + /** + * Forces the factory to load the metadata of all classes known to the underlying + * mapping driver. + * + * @return array The ClassMetadata instances of all mapped classes. + */ + public function getAllMetadata(); + + /** + * Gets the class metadata descriptor for a class. + * + * @param string $className The name of the class. + * + * @return ClassMetadata + */ + public function getMetadataFor($className); + + /** + * Checks whether the factory has the metadata for a class loaded already. + * + * @param string $className + * + * @return boolean TRUE if the metadata of the class in question is already loaded, FALSE otherwise. + */ + public function hasMetadataFor($className); + + /** + * Sets the metadata descriptor for a specific class. + * + * @param string $className + * + * @param ClassMetadata $class + */ + public function setMetadataFor($className, $class); + + /** + * Returns whether the class with the specified name should have its metadata loaded. + * This is only the case if it is either mapped directly or as a MappedSuperclass. + * + * @param string $className + * + * @return boolean + */ + public function isTransient($className); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/AnnotationDriver.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/AnnotationDriver.php new file mode 100644 index 0000000..1737243 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/AnnotationDriver.php @@ -0,0 +1,217 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping\Driver; + +use Doctrine\Common\Annotations\AnnotationReader; +use Doctrine\Common\Annotations\AnnotationRegistry; +use Doctrine\Common\Persistence\Mapping\MappingException; + +/** + * The AnnotationDriver reads the mapping metadata from docblock annotations. + * + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan H. Wage + * @author Roman Borschel + */ +abstract class AnnotationDriver implements MappingDriver +{ + /** + * The AnnotationReader. + * + * @var AnnotationReader + */ + protected $reader; + + /** + * The paths where to look for mapping files. + * + * @var array + */ + protected $paths = array(); + + /** + * The file extension of mapping documents. + * + * @var string + */ + protected $fileExtension = '.php'; + + /** + * Cache for AnnotationDriver#getAllClassNames(). + * + * @var array|null + */ + protected $classNames; + + /** + * Name of the entity annotations as keys. + * + * @var array + */ + protected $entityAnnotationClasses = array(); + + /** + * Initializes a new AnnotationDriver that uses the given AnnotationReader for reading + * docblock annotations. + * + * @param AnnotationReader $reader The AnnotationReader to use, duck-typed. + * @param string|array|null $paths One or multiple paths where mapping classes can be found. + */ + public function __construct($reader, $paths = null) + { + $this->reader = $reader; + if ($paths) { + $this->addPaths((array) $paths); + } + } + + /** + * Appends lookup paths to metadata driver. + * + * @param array $paths + * + * @return void + */ + public function addPaths(array $paths) + { + $this->paths = array_unique(array_merge($this->paths, $paths)); + } + + /** + * Retrieves the defined metadata lookup paths. + * + * @return array + */ + public function getPaths() + { + return $this->paths; + } + + /** + * Retrieves the current annotation reader. + * + * @return AnnotationReader + */ + public function getReader() + { + return $this->reader; + } + + /** + * Gets the file extension used to look for mapping files under. + * + * @return string + */ + public function getFileExtension() + { + return $this->fileExtension; + } + + /** + * Sets the file extension used to look for mapping files under. + * + * @param string $fileExtension The file extension to set. + * + * @return void + */ + public function setFileExtension($fileExtension) + { + $this->fileExtension = $fileExtension; + } + + /** + * Returns whether the class with the specified name is transient. Only non-transient + * classes, that is entities and mapped superclasses, should have their metadata loaded. + * + * A class is non-transient if it is annotated with an annotation + * from the {@see AnnotationDriver::entityAnnotationClasses}. + * + * @param string $className + * + * @return boolean + */ + public function isTransient($className) + { + $classAnnotations = $this->reader->getClassAnnotations(new \ReflectionClass($className)); + + foreach ($classAnnotations as $annot) { + if (isset($this->entityAnnotationClasses[get_class($annot)])) { + return false; + } + } + return true; + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames() + { + if ($this->classNames !== null) { + return $this->classNames; + } + + if (!$this->paths) { + throw MappingException::pathRequired(); + } + + $classes = array(); + $includedFiles = array(); + + foreach ($this->paths as $path) { + if ( ! is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new \RegexIterator( + new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS), + \RecursiveIteratorIterator::LEAVES_ONLY + ), + '/^.+' . preg_quote($this->fileExtension) . '$/i', + \RecursiveRegexIterator::GET_MATCH + ); + + foreach ($iterator as $file) { + $sourceFile = realpath($file[0]); + + require_once $sourceFile; + + $includedFiles[] = $sourceFile; + } + } + + $declared = get_declared_classes(); + + foreach ($declared as $className) { + $rc = new \ReflectionClass($className); + $sourceFile = $rc->getFileName(); + if (in_array($sourceFile, $includedFiles) && ! $this->isTransient($className)) { + $classes[] = $className; + } + } + + $this->classNames = $classes; + + return $classes; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/DefaultFileLocator.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/DefaultFileLocator.php new file mode 100644 index 0000000..6a9e276 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/DefaultFileLocator.php @@ -0,0 +1,173 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping\Driver; + +use Doctrine\Common\Persistence\Mapping\MappingException; + +/** + * Locates the file that contains the metadata information for a given class name. + * + * This behavior is independent of the actual content of the file. It just detects + * the file which is responsible for the given class name. + * + * @author Benjamin Eberlei + * @author Johannes M. Schmitt + */ +class DefaultFileLocator implements FileLocator +{ + /** + * The paths where to look for mapping files. + * + * @var array + */ + protected $paths = array(); + + /** + * The file extension of mapping documents. + * + * @var string|null + */ + protected $fileExtension; + + /** + * Initializes a new FileDriver that looks in the given path(s) for mapping + * documents and operates in the specified operating mode. + * + * @param string|array $paths One or multiple paths where mapping documents can be found. + * @param string|null $fileExtension The file extension of mapping documents. + */ + public function __construct($paths, $fileExtension = null) + { + $this->addPaths((array) $paths); + $this->fileExtension = $fileExtension; + } + + /** + * Appends lookup paths to metadata driver. + * + * @param array $paths + * + * @return void + */ + public function addPaths(array $paths) + { + $this->paths = array_unique(array_merge($this->paths, $paths)); + } + + /** + * Retrieves the defined metadata lookup paths. + * + * @return array + */ + public function getPaths() + { + return $this->paths; + } + + /** + * Gets the file extension used to look for mapping files under. + * + * @return string|null + */ + public function getFileExtension() + { + return $this->fileExtension; + } + + /** + * Sets the file extension used to look for mapping files under. + * + * @param string|null $fileExtension The file extension to set. + * + * @return void + */ + public function setFileExtension($fileExtension) + { + $this->fileExtension = $fileExtension; + } + + /** + * {@inheritDoc} + */ + public function findMappingFile($className) + { + $fileName = str_replace('\\', '.', $className) . $this->fileExtension; + + // Check whether file exists + foreach ($this->paths as $path) { + if (is_file($path . DIRECTORY_SEPARATOR . $fileName)) { + return $path . DIRECTORY_SEPARATOR . $fileName; + } + } + + throw MappingException::mappingFileNotFound($className, $fileName); + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames($globalBasename) + { + $classes = array(); + + if ($this->paths) { + foreach ($this->paths as $path) { + if ( ! is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($path), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($iterator as $file) { + $fileName = $file->getBasename($this->fileExtension); + + if ($fileName == $file->getBasename() || $fileName == $globalBasename) { + continue; + } + + // NOTE: All files found here means classes are not transient! + $classes[] = str_replace('.', '\\', $fileName); + } + } + } + + return $classes; + } + + /** + * {@inheritDoc} + */ + public function fileExists($className) + { + $fileName = str_replace('\\', '.', $className) . $this->fileExtension; + + // Check whether file exists + foreach ((array) $this->paths as $path) { + if (is_file($path . DIRECTORY_SEPARATOR . $fileName)) { + return true; + } + } + + return false; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileDriver.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileDriver.php new file mode 100644 index 0000000..ccc64fa --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileDriver.php @@ -0,0 +1,211 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping\Driver; + +use Doctrine\Common\Persistence\Mapping\MappingException; + +/** + * Base driver for file-based metadata drivers. + * + * A file driver operates in a mode where it loads the mapping files of individual + * classes on demand. This requires the user to adhere to the convention of 1 mapping + * file per class and the file names of the mapping files must correspond to the full + * class name, including namespace, with the namespace delimiters '\', replaced by dots '.'. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan H. Wage + * @author Roman Borschel + */ +abstract class FileDriver implements MappingDriver +{ + /** + * @var FileLocator + */ + protected $locator; + + /** + * @var array|null + */ + protected $classCache; + + /** + * @var string|null + */ + protected $globalBasename; + + /** + * Initializes a new FileDriver that looks in the given path(s) for mapping + * documents and operates in the specified operating mode. + * + * @param string|array|FileLocator $locator A FileLocator or one/multiple paths + * where mapping documents can be found. + * @param string|null $fileExtension + */ + public function __construct($locator, $fileExtension = null) + { + if ($locator instanceof FileLocator) { + $this->locator = $locator; + } else { + $this->locator = new DefaultFileLocator((array)$locator, $fileExtension); + } + } + + /** + * Sets the global basename. + * + * @param string $file + * + * @return void + */ + public function setGlobalBasename($file) + { + $this->globalBasename = $file; + } + + /** + * Retrieves the global basename. + * + * @return string|null + */ + public function getGlobalBasename() + { + return $this->globalBasename; + } + + /** + * Gets the element of schema meta data for the class from the mapping file. + * This will lazily load the mapping file if it is not loaded yet. + * + * @param string $className + * + * @return array The element of schema meta data. + * + * @throws MappingException + */ + public function getElement($className) + { + if ($this->classCache === null) { + $this->initialize(); + } + + if (isset($this->classCache[$className])) { + return $this->classCache[$className]; + } + + $result = $this->loadMappingFile($this->locator->findMappingFile($className)); + if (!isset($result[$className])) { + throw MappingException::invalidMappingFile($className, str_replace('\\', '.', $className) . $this->locator->getFileExtension()); + } + + return $result[$className]; + } + + /** + * {@inheritDoc} + */ + public function isTransient($className) + { + if ($this->classCache === null) { + $this->initialize(); + } + + if (isset($this->classCache[$className])) { + return false; + } + + return !$this->locator->fileExists($className); + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames() + { + if ($this->classCache === null) { + $this->initialize(); + } + + $classNames = (array)$this->locator->getAllClassNames($this->globalBasename); + if ($this->classCache) { + $classNames = array_merge(array_keys($this->classCache), $classNames); + } + return $classNames; + } + + /** + * Loads a mapping file with the given name and returns a map + * from class/entity names to their corresponding file driver elements. + * + * @param string $file The mapping file to load. + * + * @return array + */ + abstract protected function loadMappingFile($file); + + /** + * Initializes the class cache from all the global files. + * + * Using this feature adds a substantial performance hit to file drivers as + * more metadata has to be loaded into memory than might actually be + * necessary. This may not be relevant to scenarios where caching of + * metadata is in place, however hits very hard in scenarios where no + * caching is used. + * + * @return void + */ + protected function initialize() + { + $this->classCache = array(); + if (null !== $this->globalBasename) { + foreach ($this->locator->getPaths() as $path) { + $file = $path.'/'.$this->globalBasename.$this->locator->getFileExtension(); + if (is_file($file)) { + $this->classCache = array_merge( + $this->classCache, + $this->loadMappingFile($file) + ); + } + } + } + } + + /** + * Retrieves the locator used to discover mapping files by className. + * + * @return FileLocator + */ + public function getLocator() + { + return $this->locator; + } + + /** + * Sets the locator used to discover mapping files by className. + * + * @param FileLocator $locator + */ + public function setLocator(FileLocator $locator) + { + $this->locator = $locator; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileLocator.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileLocator.php new file mode 100644 index 0000000..f622856 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/FileLocator.php @@ -0,0 +1,73 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping\Driver; + +/** + * Locates the file that contains the metadata information for a given class name. + * + * This behavior is independent of the actual content of the file. It just detects + * the file which is responsible for the given class name. + * + * @author Benjamin Eberlei + * @author Johannes M. Schmitt + */ +interface FileLocator +{ + /** + * Locates mapping file for the given class name. + * + * @param string $className + * + * @return string + */ + public function findMappingFile($className); + + /** + * Gets all class names that are found with this file locator. + * + * @param string $globalBasename Passed to allow excluding the basename. + * + * @return array + */ + public function getAllClassNames($globalBasename); + + /** + * Checks if a file can be found for this class name. + * + * @param string $className + * + * @return bool + */ + public function fileExists($className); + + /** + * Gets all the paths that this file locator looks for mapping files. + * + * @return array + */ + public function getPaths(); + + /** + * Gets the file extension that mapping files are suffixed with. + * + * @return string + */ + public function getFileExtension(); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriver.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriver.php new file mode 100644 index 0000000..b5d7ec0 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriver.php @@ -0,0 +1,58 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping\Driver; + +use Doctrine\Common\Persistence\Mapping\ClassMetadata; + +/** + * Contract for metadata drivers. + * + * @since 2.2 + * @author Jonathan H. Wage + */ +interface MappingDriver +{ + /** + * Loads the metadata for the specified class into the provided container. + * + * @param string $className + * @param ClassMetadata $metadata + * + * @return void + */ + public function loadMetadataForClass($className, ClassMetadata $metadata); + + /** + * Gets the names of all mapped classes known to this driver. + * + * @return array The names of all mapped classes known to this driver. + */ + public function getAllClassNames(); + + /** + * Returns whether the class with the specified name should have its metadata loaded. + * This is only the case if it is either mapped as an Entity or a MappedSuperclass. + * + * @param string $className + * + * @return boolean + */ + public function isTransient($className); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriverChain.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriverChain.php new file mode 100644 index 0000000..5fa9a7a --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/MappingDriverChain.php @@ -0,0 +1,166 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping\Driver; + +use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver; +use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use Doctrine\Common\Persistence\Mapping\MappingException; + +/** + * The DriverChain allows you to add multiple other mapping drivers for + * certain namespaces. + * + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class MappingDriverChain implements MappingDriver +{ + /** + * The default driver. + * + * @var MappingDriver|null + */ + private $defaultDriver = null; + + /** + * @var array + */ + private $drivers = array(); + + /** + * Gets the default driver. + * + * @return MappingDriver|null + */ + public function getDefaultDriver() + { + return $this->defaultDriver; + } + + /** + * Set the default driver. + * + * @param MappingDriver $driver + * + * @return void + */ + public function setDefaultDriver(MappingDriver $driver) + { + $this->defaultDriver = $driver; + } + + /** + * Adds a nested driver. + * + * @param MappingDriver $nestedDriver + * @param string $namespace + * + * @return void + */ + public function addDriver(MappingDriver $nestedDriver, $namespace) + { + $this->drivers[$namespace] = $nestedDriver; + } + + /** + * Gets the array of nested drivers. + * + * @return array $drivers + */ + public function getDrivers() + { + return $this->drivers; + } + + /** + * {@inheritDoc} + */ + public function loadMetadataForClass($className, ClassMetadata $metadata) + { + /* @var $driver MappingDriver */ + foreach ($this->drivers as $namespace => $driver) { + if (strpos($className, $namespace) === 0) { + $driver->loadMetadataForClass($className, $metadata); + return; + } + } + + if (null !== $this->defaultDriver) { + $this->defaultDriver->loadMetadataForClass($className, $metadata); + return; + } + + throw MappingException::classNotFoundInNamespaces($className, array_keys($this->drivers)); + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames() + { + $classNames = array(); + $driverClasses = array(); + + /* @var $driver MappingDriver */ + foreach ($this->drivers AS $namespace => $driver) { + $oid = spl_object_hash($driver); + + if (!isset($driverClasses[$oid])) { + $driverClasses[$oid] = $driver->getAllClassNames(); + } + + foreach ($driverClasses[$oid] AS $className) { + if (strpos($className, $namespace) === 0) { + $classNames[$className] = true; + } + } + } + + if (null !== $this->defaultDriver) { + foreach ($this->defaultDriver->getAllClassNames() as $className) { + $classNames[$className] = true; + } + } + + return array_keys($classNames); + } + + /** + * {@inheritDoc} + */ + public function isTransient($className) + { + /* @var $driver MappingDriver */ + foreach ($this->drivers AS $namespace => $driver) { + if (strpos($className, $namespace) === 0) { + return $driver->isTransient($className); + } + } + + if ($this->defaultDriver !== null) { + return $this->defaultDriver->isTransient($className); + } + + return true; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/PHPDriver.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/PHPDriver.php new file mode 100644 index 0000000..31651f3 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/PHPDriver.php @@ -0,0 +1,70 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping\Driver; + +use Doctrine\Common\Persistence\Mapping\ClassMetadata; + +/** + * The PHPDriver includes php files which just populate ClassMetadataInfo + * instances with plain PHP code. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class PHPDriver extends FileDriver +{ + /** + * {@inheritDoc} + */ + protected $metadata; + + /** + * {@inheritDoc} + */ + public function __construct($locator, $fileExtension = null) + { + $fileExtension = ".php"; + parent::__construct($locator, $fileExtension); + } + + /** + * {@inheritDoc} + */ + public function loadMetadataForClass($className, ClassMetadata $metadata) + { + $this->metadata = $metadata; + $this->loadMappingFile($this->locator->findMappingFile($className)); + } + + /** + * {@inheritDoc} + */ + protected function loadMappingFile($file) + { + $metadata = $this->metadata; + include $file; + + return array($metadata->getName() => $metadata); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/StaticPHPDriver.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/StaticPHPDriver.php new file mode 100644 index 0000000..df8f477 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/StaticPHPDriver.php @@ -0,0 +1,142 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping\Driver; + +use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use Doctrine\Common\Persistence\Mapping\MappingException; + +/** + * The StaticPHPDriver calls a static loadMetadata() method on your entity + * classes where you can manually populate the ClassMetadata instance. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan H. Wage + * @author Roman Borschel + */ +class StaticPHPDriver implements MappingDriver +{ + /** + * Paths of entity directories. + * + * @var array + */ + private $paths = array(); + + /** + * Map of all class names. + * + * @var array + */ + private $classNames; + + /** + * Constructor. + * + * @param array|string $paths + */ + public function __construct($paths) + { + $this->addPaths((array) $paths); + } + + /** + * Adds paths. + * + * @param array $paths + * + * @return void + */ + public function addPaths(array $paths) + { + $this->paths = array_unique(array_merge($this->paths, $paths)); + } + + /** + * {@inheritdoc} + */ + public function loadMetadataForClass($className, ClassMetadata $metadata) + { + $className::loadMetadata($metadata); + } + + /** + * {@inheritDoc} + * @todo Same code exists in AnnotationDriver, should we re-use it somehow or not worry about it? + */ + public function getAllClassNames() + { + if ($this->classNames !== null) { + return $this->classNames; + } + + if (!$this->paths) { + throw MappingException::pathRequired(); + } + + $classes = array(); + $includedFiles = array(); + + foreach ($this->paths as $path) { + if (!is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($path), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($iterator as $file) { + if ($file->getBasename('.php') == $file->getBasename()) { + continue; + } + + $sourceFile = realpath($file->getPathName()); + require_once $sourceFile; + $includedFiles[] = $sourceFile; + } + } + + $declared = get_declared_classes(); + + foreach ($declared as $className) { + $rc = new \ReflectionClass($className); + $sourceFile = $rc->getFileName(); + if (in_array($sourceFile, $includedFiles) && !$this->isTransient($className)) { + $classes[] = $className; + } + } + + $this->classNames = $classes; + + return $classes; + } + + /** + * {@inheritdoc} + */ + public function isTransient($className) + { + return ! method_exists($className, 'loadMetadata'); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/SymfonyFileLocator.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/SymfonyFileLocator.php new file mode 100644 index 0000000..dd4d741 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/Driver/SymfonyFileLocator.php @@ -0,0 +1,217 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping\Driver; + +use Doctrine\Common\Persistence\Mapping\MappingException; + +/** + * The Symfony File Locator makes a simplifying assumptions compared + * to the DefaultFileLocator. By assuming paths only contain entities of a certain + * namespace the mapping files consists of the short classname only. + * + * @author Fabien Potencier + * @author Benjamin Eberlei + * @license MIT + */ +class SymfonyFileLocator implements FileLocator +{ + /** + * The paths where to look for mapping files. + * + * @var array + */ + protected $paths = array(); + + /** + * A map of mapping directory path to namespace prefix used to expand class shortnames. + * + * @var array + */ + protected $prefixes = array(); + + /** + * File extension that is searched for. + * + * @var string|null + */ + protected $fileExtension; + + /** + * Constructor. + * + * @param array $prefixes + * @param string|null $fileExtension + */ + public function __construct(array $prefixes, $fileExtension = null) + { + $this->addNamespacePrefixes($prefixes); + $this->fileExtension = $fileExtension; + } + + /** + * Adds Namespace Prefixes. + * + * @param array $prefixes + * + * @return void + */ + public function addNamespacePrefixes(array $prefixes) + { + $this->prefixes = array_merge($this->prefixes, $prefixes); + $this->paths = array_merge($this->paths, array_keys($prefixes)); + } + + /** + * Gets Namespace Prefixes. + * + * @return array + */ + public function getNamespacePrefixes() + { + return $this->prefixes; + } + + /** + * {@inheritDoc} + */ + public function getPaths() + { + return $this->paths; + } + + /** + * {@inheritDoc} + */ + public function getFileExtension() + { + return $this->fileExtension; + } + + /** + * Sets the file extension used to look for mapping files under. + * + * @param string $fileExtension The file extension to set. + * + * @return void + */ + public function setFileExtension($fileExtension) + { + $this->fileExtension = $fileExtension; + } + + /** + * {@inheritDoc} + */ + public function fileExists($className) + { + $defaultFileName = str_replace('\\', '.', $className).$this->fileExtension; + foreach ($this->paths as $path) { + if (!isset($this->prefixes[$path])) { + // global namespace class + if (is_file($path.DIRECTORY_SEPARATOR.$defaultFileName)) { + return true; + } + + continue; + } + + $prefix = $this->prefixes[$path]; + + if (0 !== strpos($className, $prefix.'\\')) { + continue; + } + + $filename = $path.'/'.strtr(substr($className, strlen($prefix)+1), '\\', '.').$this->fileExtension; + return is_file($filename); + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function getAllClassNames($globalBasename = null) + { + $classes = array(); + + if ($this->paths) { + foreach ((array) $this->paths as $path) { + if (!is_dir($path)) { + throw MappingException::fileMappingDriversRequireConfiguredDirectoryPath($path); + } + + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($path), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($iterator as $file) { + $fileName = $file->getBasename($this->fileExtension); + + if ($fileName == $file->getBasename() || $fileName == $globalBasename) { + continue; + } + + // NOTE: All files found here means classes are not transient! + if (isset($this->prefixes[$path])) { + $classes[] = $this->prefixes[$path].'\\'.str_replace('.', '\\', $fileName); + } else { + $classes[] = str_replace('.', '\\', $fileName); + } + } + } + } + + return $classes; + } + + /** + * {@inheritDoc} + */ + public function findMappingFile($className) + { + $defaultFileName = str_replace('\\', '.', $className).$this->fileExtension; + foreach ($this->paths as $path) { + if (!isset($this->prefixes[$path])) { + if (is_file($path.DIRECTORY_SEPARATOR.$defaultFileName)) { + return $path.DIRECTORY_SEPARATOR.$defaultFileName; + } + + continue; + } + + $prefix = $this->prefixes[$path]; + + if (0 !== strpos($className, $prefix.'\\')) { + continue; + } + + $filename = $path.'/'.strtr(substr($className, strlen($prefix)+1), '\\', '.').$this->fileExtension; + if (is_file($filename)) { + return $filename; + } + + throw MappingException::mappingFileNotFound($className, $filename); + } + + throw MappingException::mappingFileNotFound($className, substr($className, strrpos($className, '\\') + 1).$this->fileExtension); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/MappingException.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/MappingException.php new file mode 100644 index 0000000..c8c32b2 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/MappingException.php @@ -0,0 +1,98 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping; + +/** + * A MappingException indicates that something is wrong with the mapping setup. + * + * @since 2.2 + */ +class MappingException extends \Exception +{ + /** + * @param string $className + * @param array $namespaces + * + * @return MappingException + */ + public static function classNotFoundInNamespaces($className, $namespaces) + { + return new self("The class '" . $className . "' was not found in the ". + "chain configured namespaces " . implode(", ", $namespaces)); + } + + /** + * @return MappingException + */ + public static function pathRequired() + { + return new self("Specifying the paths to your entities is required ". + "in the AnnotationDriver to retrieve all class names."); + } + + /** + * @param string|null $path + * + * @return MappingException + */ + public static function fileMappingDriversRequireConfiguredDirectoryPath($path = null) + { + if ( ! empty($path)) { + $path = '[' . $path . ']'; + } + + return new self( + 'File mapping drivers must have a valid directory path, ' . + 'however the given path ' . $path . ' seems to be incorrect!' + ); + } + + /** + * @param string $entityName + * @param string $fileName + * + * @return MappingException + */ + public static function mappingFileNotFound($entityName, $fileName) + { + return new self("No mapping file found named '$fileName' for class '$entityName'."); + } + + /** + * @param string $entityName + * @param string $fileName + * + * @return MappingException + */ + public static function invalidMappingFile($entityName, $fileName) + { + return new self("Invalid mapping file '$fileName' for class '$entityName'."); + } + + /** + * @param string $className + * + * @return \Doctrine\Common\Persistence\Mapping\MappingException + */ + public static function nonExistingClass($className) + { + return new self("Class '$className' does not exist"); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ReflectionService.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ReflectionService.php new file mode 100644 index 0000000..0088ed5 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/ReflectionService.php @@ -0,0 +1,87 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping; + +/** + * Very simple reflection service abstraction. + * + * This is required inside metadata layers that may require either + * static or runtime reflection. + * + * @author Benjamin Eberlei + */ +interface ReflectionService +{ + /** + * Returns an array of the parent classes (not interfaces) for the given class. + * + * @param string $class + * + * @throws \Doctrine\Common\Persistence\Mapping\MappingException + * + * @return array + */ + public function getParentClasses($class); + + /** + * Returns the shortname of a class. + * + * @param string $class + * + * @return string + */ + public function getClassShortName($class); + + /** + * @param string $class + * + * @return string + */ + public function getClassNamespace($class); + + /** + * Returns a reflection class instance or null. + * + * @param string $class + * + * @return \ReflectionClass|null + */ + public function getClass($class); + + /** + * Returns an accessible property (setAccessible(true)) or null. + * + * @param string $class + * @param string $property + * + * @return \ReflectionProperty|null + */ + public function getAccessibleProperty($class, $property); + + /** + * Checks if the class have a public method with the given name. + * + * @param mixed $class + * @param mixed $method + * + * @return bool + */ + public function hasPublicMethod($class, $method); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/RuntimeReflectionService.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/RuntimeReflectionService.php new file mode 100644 index 0000000..c5a37a8 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/RuntimeReflectionService.php @@ -0,0 +1,97 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping; + +use ReflectionClass; +use ReflectionProperty; +use Doctrine\Common\Reflection\RuntimePublicReflectionProperty; +use Doctrine\Common\Persistence\Mapping\MappingException; + +/** + * PHP Runtime Reflection Service. + * + * @author Benjamin Eberlei + */ +class RuntimeReflectionService implements ReflectionService +{ + /** + * {@inheritDoc} + */ + public function getParentClasses($class) + { + if ( ! class_exists($class)) { + throw MappingException::nonExistingClass($class); + } + + return class_parents($class); + } + + /** + * {@inheritDoc} + */ + public function getClassShortName($class) + { + $reflectionClass = new ReflectionClass($class); + + return $reflectionClass->getShortName(); + } + + /** + * {@inheritDoc} + */ + public function getClassNamespace($class) + { + $reflectionClass = new ReflectionClass($class); + + return $reflectionClass->getNamespaceName(); + } + + /** + * {@inheritDoc} + */ + public function getClass($class) + { + return new ReflectionClass($class); + } + + /** + * {@inheritDoc} + */ + public function getAccessibleProperty($class, $property) + { + $reflectionProperty = new ReflectionProperty($class, $property); + + if ($reflectionProperty->isPublic()) { + $reflectionProperty = new RuntimePublicReflectionProperty($class, $property); + } + + $reflectionProperty->setAccessible(true); + + return $reflectionProperty; + } + + /** + * {@inheritDoc} + */ + public function hasPublicMethod($class, $method) + { + return method_exists($class, $method) && is_callable(array($class, $method)); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/StaticReflectionService.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/StaticReflectionService.php new file mode 100644 index 0000000..f903097 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Mapping/StaticReflectionService.php @@ -0,0 +1,83 @@ +. + */ + +namespace Doctrine\Common\Persistence\Mapping; + +/** + * PHP Runtime Reflection Service. + * + * @author Benjamin Eberlei + */ +class StaticReflectionService implements ReflectionService +{ + /** + * {@inheritDoc} + */ + public function getParentClasses($class) + { + return array(); + } + + /** + * {@inheritDoc} + */ + public function getClassShortName($className) + { + if (strpos($className, '\\') !== false) { + $className = substr($className, strrpos($className, "\\")+1); + } + return $className; + } + + /** + * {@inheritDoc} + */ + public function getClassNamespace($className) + { + $namespace = ''; + if (strpos($className, '\\') !== false) { + $namespace = strrev(substr( strrev($className), strpos(strrev($className), '\\')+1 )); + } + return $namespace; + } + + /** + * {@inheritDoc} + */ + public function getClass($class) + { + return null; + } + + /** + * {@inheritDoc} + */ + public function getAccessibleProperty($class, $property) + { + return null; + } + + /** + * {@inheritDoc} + */ + public function hasPublicMethod($class, $method) + { + return method_exists($class, $method) && is_callable(array($class, $method)); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManager.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManager.php new file mode 100644 index 0000000..bb29e43 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManager.php @@ -0,0 +1,169 @@ +. + */ + +namespace Doctrine\Common\Persistence; + +/** + * Contract for a Doctrine persistence layer ObjectManager class to implement. + * + * @link www.doctrine-project.org + * @since 2.1 + * @author Benjamin Eberlei + * @author Jonathan Wage + */ +interface ObjectManager +{ + /** + * Finds a object by its identifier. + * + * This is just a convenient shortcut for getRepository($className)->find($id). + * + * @param string $className The class name of the object to find. + * @param mixed $id The identity of the object to find. + * + * @return object The found object. + */ + public function find($className, $id); + + /** + * Tells the ObjectManager to make an instance managed and persistent. + * + * The object will be entered into the database as a result of the flush operation. + * + * NOTE: The persist operation always considers objects that are not yet known to + * this ObjectManager as NEW. Do not pass detached objects to the persist operation. + * + * @param object $object The instance to make managed and persistent. + * + * @return void + */ + public function persist($object); + + /** + * Removes an object instance. + * + * A removed object will be removed from the database as a result of the flush operation. + * + * @param object $object The object instance to remove. + * + * @return void + */ + public function remove($object); + + /** + * Merges the state of a detached object into the persistence context + * of this ObjectManager and returns the managed copy of the object. + * The object passed to merge will not become associated/managed with this ObjectManager. + * + * @param object $object + * + * @return object + */ + public function merge($object); + + /** + * Clears the ObjectManager. All objects that are currently managed + * by this ObjectManager become detached. + * + * @param string|null $objectName if given, only objects of this type will get detached. + * + * @return void + */ + public function clear($objectName = null); + + /** + * Detaches an object from the ObjectManager, causing a managed object to + * become detached. Unflushed changes made to the object if any + * (including removal of the object), will not be synchronized to the database. + * Objects which previously referenced the detached object will continue to + * reference it. + * + * @param object $object The object to detach. + * + * @return void + */ + public function detach($object); + + /** + * Refreshes the persistent state of an object from the database, + * overriding any local changes that have not yet been persisted. + * + * @param object $object The object to refresh. + * + * @return void + */ + public function refresh($object); + + /** + * Flushes all changes to objects that have been queued up to now to the database. + * This effectively synchronizes the in-memory state of managed objects with the + * database. + * + * @return void + */ + public function flush(); + + /** + * Gets the repository for a class. + * + * @param string $className + * + * @return \Doctrine\Common\Persistence\ObjectRepository + */ + public function getRepository($className); + + /** + * Returns the ClassMetadata descriptor for a class. + * + * The class name must be the fully-qualified class name without a leading backslash + * (as it is returned by get_class($obj)). + * + * @param string $className + * + * @return \Doctrine\Common\Persistence\Mapping\ClassMetadata + */ + public function getClassMetadata($className); + + /** + * Gets the metadata factory used to gather the metadata of classes. + * + * @return \Doctrine\Common\Persistence\Mapping\ClassMetadataFactory + */ + public function getMetadataFactory(); + + /** + * Helper method to initialize a lazy loading proxy or persistent collection. + * + * This method is a no-op for other objects. + * + * @param object $obj + * + * @return void + */ + public function initializeObject($obj); + + /** + * Checks if the object is part of the current UnitOfWork and therefore managed. + * + * @param object $object + * + * @return bool + */ + public function contains($object); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManagerAware.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManagerAware.php new file mode 100644 index 0000000..9bc248a --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManagerAware.php @@ -0,0 +1,51 @@ +. + */ + +namespace Doctrine\Common\Persistence; + +use Doctrine\Common\Persistence\Mapping\ClassMetadata; + +/** + * Makes a Persistent Objects aware of its own object-manager. + * + * Using this interface the managing object manager and class metadata instances + * are injected into the persistent object after construction. This allows + * you to implement ActiveRecord functionality on top of the persistence-ignorance + * that Doctrine propagates. + * + * Word of Warning: This is a very powerful hook to change how you can work with your domain models. + * Using this hook will break the Single Responsibility Principle inside your Domain Objects + * and increase the coupling of database and objects. + * + * Every ObjectManager has to implement this functionality itself. + * + * @author Benjamin Eberlei + */ +interface ObjectManagerAware +{ + /** + * Injects responsible ObjectManager and the ClassMetadata into this persistent object. + * + * @param ObjectManager $objectManager + * @param ClassMetadata $classMetadata + * + * @return void + */ + public function injectObjectManager(ObjectManager $objectManager, ClassMetadata $classMetadata); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManagerDecorator.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManagerDecorator.php new file mode 100644 index 0000000..8946475 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectManagerDecorator.php @@ -0,0 +1,140 @@ +. + */ + +namespace Doctrine\Common\Persistence; + +/** + * Base class to simplify ObjectManager decorators + * + * @license http://opensource.org/licenses/MIT MIT + * @link www.doctrine-project.org + * @since 2.4 + * @author Lars Strojny + */ +abstract class ObjectManagerDecorator implements ObjectManager +{ + /** + * @var ObjectManager + */ + protected $wrapped; + + /** + * {@inheritdoc} + */ + public function find($className, $id) + { + return $this->wrapped->find($className, $id); + } + + /** + * {@inheritdoc} + */ + public function persist($object) + { + return $this->wrapped->persist($object); + } + + /** + * {@inheritdoc} + */ + public function remove($object) + { + return $this->wrapped->remove($object); + } + + /** + * {@inheritdoc} + */ + public function merge($object) + { + return $this->wrapped->merge($object); + } + + /** + * {@inheritdoc} + */ + public function clear($objectName = null) + { + return $this->wrapped->clear($objectName); + } + + /** + * {@inheritdoc} + */ + public function detach($object) + { + return $this->wrapped->detach($object); + } + + /** + * {@inheritdoc} + */ + public function refresh($object) + { + return $this->wrapped->refresh($object); + } + + /** + * {@inheritdoc} + */ + public function flush() + { + return $this->wrapped->flush(); + } + + /** + * {@inheritdoc} + */ + public function getRepository($className) + { + return $this->wrapped->getRepository($className); + } + + /** + * {@inheritdoc} + */ + public function getClassMetadata($className) + { + return $this->wrapped->getClassMetadata($className); + } + + /** + * {@inheritdoc} + */ + public function getMetadataFactory() + { + return $this->wrapped->getMetadataFactory(); + } + + /** + * {@inheritdoc} + */ + public function initializeObject($obj) + { + return $this->wrapped->initializeObject($obj); + } + + /** + * {@inheritdoc} + */ + public function contains($object) + { + return $this->wrapped->contains($object); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectRepository.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectRepository.php new file mode 100644 index 0000000..f607219 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/ObjectRepository.php @@ -0,0 +1,81 @@ +. + */ + +namespace Doctrine\Common\Persistence; + +/** + * Contract for a Doctrine persistence layer ObjectRepository class to implement. + * + * @link www.doctrine-project.org + * @since 2.1 + * @author Benjamin Eberlei + * @author Jonathan Wage + */ +interface ObjectRepository +{ + /** + * Finds an object by its primary key / identifier. + * + * @param mixed $id The identifier. + * + * @return object The object. + */ + public function find($id); + + /** + * Finds all objects in the repository. + * + * @return array The objects. + */ + public function findAll(); + + /** + * Finds objects by a set of criteria. + * + * Optionally sorting and limiting details can be passed. An implementation may throw + * an UnexpectedValueException if certain values of the sorting or limiting details are + * not supported. + * + * @param array $criteria + * @param array|null $orderBy + * @param int|null $limit + * @param int|null $offset + * + * @return array The objects. + * + * @throws \UnexpectedValueException + */ + public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null); + + /** + * Finds a single object by a set of criteria. + * + * @param array $criteria The criteria. + * + * @return object The object. + */ + public function findOneBy(array $criteria); + + /** + * Returns the class name of the object managed by the repository. + * + * @return string + */ + public function getClassName(); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/PersistentObject.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/PersistentObject.php new file mode 100644 index 0000000..08c6942 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/PersistentObject.php @@ -0,0 +1,254 @@ +. + */ + +namespace Doctrine\Common\Persistence; + +use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; + +/** + * PersistentObject base class that implements getter/setter methods for all mapped fields and associations + * by overriding __call. + * + * This class is a forward compatible implementation of the PersistentObject trait. + * + * Limitations: + * + * 1. All persistent objects have to be associated with a single ObjectManager, multiple + * ObjectManagers are not supported. You can set the ObjectManager with `PersistentObject#setObjectManager()`. + * 2. Setters and getters only work if a ClassMetadata instance was injected into the PersistentObject. + * This is either done on `postLoad` of an object or by accessing the global object manager. + * 3. There are no hooks for setters/getters. Just implement the method yourself instead of relying on __call(). + * 4. Slower than handcoded implementations: An average of 7 method calls per access to a field and 11 for an association. + * 5. Only the inverse side associations get autoset on the owning side as well. Setting objects on the owning side + * will not set the inverse side associations. + * + * @example + * + * PersistentObject::setObjectManager($em); + * + * class Foo extends PersistentObject + * { + * private $id; + * } + * + * $foo = new Foo(); + * $foo->getId(); // method exists through __call + * + * @author Benjamin Eberlei + */ +abstract class PersistentObject implements ObjectManagerAware +{ + /** + * @var ObjectManager|null + */ + private static $objectManager = null; + + /** + * @var ClassMetadata|null + */ + private $cm = null; + + /** + * Sets the object manager responsible for all persistent object base classes. + * + * @param ObjectManager|null $objectManager + * + * @return void + */ + static public function setObjectManager(ObjectManager $objectManager = null) + { + self::$objectManager = $objectManager; + } + + /** + * @return ObjectManager|null + */ + static public function getObjectManager() + { + return self::$objectManager; + } + + /** + * Injects the Doctrine Object Manager. + * + * @param ObjectManager $objectManager + * @param ClassMetadata $classMetadata + * + * @return void + * + * @throws \RuntimeException + */ + public function injectObjectManager(ObjectManager $objectManager, ClassMetadata $classMetadata) + { + if ($objectManager !== self::$objectManager) { + throw new \RuntimeException("Trying to use PersistentObject with different ObjectManager instances. " . + "Was PersistentObject::setObjectManager() called?"); + } + + $this->cm = $classMetadata; + } + + /** + * Sets a persistent fields value. + * + * @param string $field + * @param array $args + * + * @return void + * + * @throws \BadMethodCallException When no persistent field exists by that name. + * @throws \InvalidArgumentException When the wrong target object type is passed to an association. + */ + private function set($field, $args) + { + $this->initializeDoctrine(); + + if ($this->cm->hasField($field) && !$this->cm->isIdentifier($field)) { + $this->$field = $args[0]; + } else if ($this->cm->hasAssociation($field) && $this->cm->isSingleValuedAssociation($field)) { + $targetClass = $this->cm->getAssociationTargetClass($field); + if (!($args[0] instanceof $targetClass) && $args[0] !== null) { + throw new \InvalidArgumentException("Expected persistent object of type '".$targetClass."'"); + } + $this->$field = $args[0]; + $this->completeOwningSide($field, $targetClass, $args[0]); + } else { + throw new \BadMethodCallException("no field with name '".$field."' exists on '".$this->cm->getName()."'"); + } + } + + /** + * Gets a persistent field value. + * + * @param string $field + * + * @return mixed + * + * @throws \BadMethodCallException When no persistent field exists by that name. + */ + private function get($field) + { + $this->initializeDoctrine(); + + if ( $this->cm->hasField($field) || $this->cm->hasAssociation($field) ) { + return $this->$field; + } else { + throw new \BadMethodCallException("no field with name '".$field."' exists on '".$this->cm->getName()."'"); + } + } + + /** + * If this is an inverse side association, completes the owning side. + * + * @param string $field + * @param ClassMetadata $targetClass + * @param object $targetObject + * + * @return void + */ + private function completeOwningSide($field, $targetClass, $targetObject) + { + // add this object on the owning side as well, for obvious infinite recursion + // reasons this is only done when called on the inverse side. + if ($this->cm->isAssociationInverseSide($field)) { + $mappedByField = $this->cm->getAssociationMappedByTargetField($field); + $targetMetadata = self::$objectManager->getClassMetadata($targetClass); + + $setter = ($targetMetadata->isCollectionValuedAssociation($mappedByField) ? "add" : "set").$mappedByField; + $targetObject->$setter($this); + } + } + + /** + * Adds an object to a collection. + * + * @param string $field + * @param array $args + * + * @return void + * + * @throws \BadMethodCallException + * @throws \InvalidArgumentException + */ + private function add($field, $args) + { + $this->initializeDoctrine(); + + if ($this->cm->hasAssociation($field) && $this->cm->isCollectionValuedAssociation($field)) { + $targetClass = $this->cm->getAssociationTargetClass($field); + if (!($args[0] instanceof $targetClass)) { + throw new \InvalidArgumentException("Expected persistent object of type '".$targetClass."'"); + } + if (!($this->$field instanceof Collection)) { + $this->$field = new ArrayCollection($this->$field ?: array()); + } + $this->$field->add($args[0]); + $this->completeOwningSide($field, $targetClass, $args[0]); + } else { + throw new \BadMethodCallException("There is no method add".$field."() on ".$this->cm->getName()); + } + } + + /** + * Initializes Doctrine Metadata for this class. + * + * @return void + * + * @throws \RuntimeException + */ + private function initializeDoctrine() + { + if ($this->cm !== null) { + return; + } + + if (!self::$objectManager) { + throw new \RuntimeException("No runtime object manager set. Call PersistentObject#setObjectManager()."); + } + + $this->cm = self::$objectManager->getClassMetadata(get_class($this)); + } + + /** + * Magic methods. + * + * @param string $method + * @param array $args + * + * @return mixed + * + * @throws \BadMethodCallException + */ + public function __call($method, $args) + { + $command = substr($method, 0, 3); + $field = lcfirst(substr($method, 3)); + if ($command == "set") { + $this->set($field, $args); + } else if ($command == "get") { + return $this->get($field); + } else if ($command == "add") { + $this->add($field, $args); + } else { + throw new \BadMethodCallException("There is no method ".$method." on ".$this->cm->getName()); + } + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Proxy.php b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Proxy.php new file mode 100644 index 0000000..3369eb9 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Persistence/Proxy.php @@ -0,0 +1,59 @@ +. + */ + +namespace Doctrine\Common\Persistence; + +/** + * Interface for proxy classes. + * + * @author Roman Borschel + * @since 2.2 + */ +interface Proxy +{ + /** + * Marker for Proxy class names. + * + * @var string + */ + const MARKER = '__CG__'; + + /** + * Length of the proxy marker. + * + * @var integer + */ + const MARKER_LENGTH = 6; + + /** + * Initializes this proxy if its not yet initialized. + * + * Acts as a no-op if already initialized. + * + * @return void + */ + public function __load(); + + /** + * Returns whether this proxy is initialized or not. + * + * @return bool + */ + public function __isInitialized(); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/PropertyChangedListener.php b/vendor/doctrine/common/lib/Doctrine/Common/PropertyChangedListener.php new file mode 100644 index 0000000..1a59cd4 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/PropertyChangedListener.php @@ -0,0 +1,45 @@ +. + */ + +namespace Doctrine\Common; + +/** + * Contract for classes that are potential listeners of a NotifyPropertyChanged + * implementor. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +interface PropertyChangedListener +{ + /** + * Notifies the listener of a property change. + * + * @param object $sender The object on which the property changed. + * @param string $propertyName The name of the property that changed. + * @param mixed $oldValue The old value of the property that changed. + * @param mixed $newValue The new value of the property that changed. + * + * @return void + */ + function propertyChanged($sender, $propertyName, $oldValue, $newValue); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php new file mode 100644 index 0000000..adb6c7b --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/AbstractProxyFactory.php @@ -0,0 +1,237 @@ +. + */ + +namespace Doctrine\Common\Proxy; + +use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory; +use Doctrine\Common\Proxy\Exception\InvalidArgumentException; +use Doctrine\Common\Util\ClassUtils; +use Doctrine\Common\Persistence\Mapping\ClassMetadata; + +/** + * Abstract factory for proxy objects. + * + * @author Benjamin Eberlei + */ +abstract class AbstractProxyFactory +{ + /** + * Never autogenerate a proxy and rely that it was generated by some + * process before deployment. + * + * @var integer + */ + const AUTOGENERATE_NEVER = 0; + + /** + * Always generates a new proxy in every request. + * + * This is only sane during development. + * + * @var integer + */ + const AUTOGENERATE_ALWAYS = 1; + + /** + * Autogenerate the proxy class when the proxy file does not exist. + * + * This strategy causes a file exists call whenever any proxy is used the + * first time in a request. + * + * @var integer + */ + const AUTOGENERATE_FILE_NOT_EXISTS = 2; + + /** + * Generate the proxy classes using eval(). + * + * This strategy is only sane for development, and even then it gives me + * the creeps a little. + * + * @var integer + */ + const AUTOGENERATE_EVAL = 3; + + /** + * @var \Doctrine\Common\Persistence\Mapping\ClassMetadataFactory + */ + private $metadataFactory; + + /** + * @var \Doctrine\Common\Proxy\ProxyGenerator the proxy generator responsible for creating the proxy classes/files. + */ + private $proxyGenerator; + + /** + * @var bool Whether to automatically (re)generate proxy classes. + */ + private $autoGenerate; + + /** + * @var \Doctrine\Common\Proxy\ProxyDefinition[] + */ + private $definitions = array(); + + /** + * @param \Doctrine\Common\Proxy\ProxyGenerator $proxyGenerator + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadataFactory $metadataFactory + * @param bool|int $autoGenerate + */ + public function __construct(ProxyGenerator $proxyGenerator, ClassMetadataFactory $metadataFactory, $autoGenerate) + { + $this->proxyGenerator = $proxyGenerator; + $this->metadataFactory = $metadataFactory; + $this->autoGenerate = (int)$autoGenerate; + } + + /** + * Gets a reference proxy instance for the entity of the given type and identified by + * the given identifier. + * + * @param string $className + * @param array $identifier + * + * @return \Doctrine\Common\Proxy\Proxy + */ + public function getProxy($className, array $identifier) + { + $definition = isset($this->definitions[$className]) + ? $this->definitions[$className] + : $this->getProxyDefinition($className); + $fqcn = $definition->proxyClassName; + $proxy = new $fqcn($definition->initializer, $definition->cloner); + + foreach ($definition->identifierFields as $idField) { + $definition->reflectionFields[$idField]->setValue($proxy, $identifier[$idField]); + } + + return $proxy; + } + + /** + * Generates proxy classes for all given classes. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata[] $classes The classes (ClassMetadata instances) + * for which to generate proxies. + * @param string $proxyDir The target directory of the proxy classes. If not specified, the + * directory configured on the Configuration of the EntityManager used + * by this factory is used. + * @return int Number of generated proxies. + */ + public function generateProxyClasses(array $classes, $proxyDir = null) + { + $generated = 0; + + foreach ($classes as $class) { + if ($this->skipClass($class)) { + continue; + } + + $proxyFileName = $this->proxyGenerator->getProxyFileName($class->getName(), $proxyDir); + + $this->proxyGenerator->generateProxyClass($class, $proxyFileName); + + $generated += 1; + } + + return $generated; + } + + /** + * Reset initialization/cloning logic for an un-initialized proxy + * + * @param \Doctrine\Common\Proxy\Proxy $proxy + * + * @return \Doctrine\Common\Proxy\Proxy + * + * @throws \Doctrine\Common\Proxy\Exception\InvalidArgumentException + */ + public function resetUninitializedProxy(Proxy $proxy) + { + if ($proxy->__isInitialized()) { + throw InvalidArgumentException::unitializedProxyExpected($proxy); + } + + $className = ClassUtils::getClass($proxy); + $definition = isset($this->definitions[$className]) + ? $this->definitions[$className] + : $this->getProxyDefinition($className); + + $proxy->__setInitializer($definition->initializer); + $proxy->__setCloner($definition->cloner); + + return $proxy; + } + + /** + * Get a proxy definition for the given class name. + * + * @return ProxyDefinition + */ + private function getProxyDefinition($className) + { + $classMetadata = $this->metadataFactory->getMetadataFor($className); + $className = $classMetadata->getName(); // aliases and case sensitivity + + $this->definitions[$className] = $this->createProxyDefinition($className); + $proxyClassName = $this->definitions[$className]->proxyClassName; + + if ( ! class_exists($proxyClassName, false)) { + $fileName = $this->proxyGenerator->getProxyFileName($className); + + switch ($this->autoGenerate) { + case self::AUTOGENERATE_NEVER: + require $fileName; + break; + + case self::AUTOGENERATE_FILE_NOT_EXISTS: + if ( ! file_exists($fileName)) { + $this->proxyGenerator->generateProxyClass($classMetadata, $fileName); + } + require $fileName; + break; + + case self::AUTOGENERATE_ALWAYS: + $this->proxyGenerator->generateProxyClass($classMetadata, $fileName); + require $fileName; + break; + + case self::AUTOGENERATE_EVAL: + $this->proxyGenerator->generateProxyClass($classMetadata, false); + break; + } + } + + return $this->definitions[$className]; + } + + /** + * Determine if this class should be skipped during proxy generation. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $metadata + * @return bool + */ + abstract protected function skipClass(ClassMetadata $metadata); + + /** + * @return ProxyDefinition + */ + abstract protected function createProxyDefinition($className); +} + diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Autoloader.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Autoloader.php new file mode 100644 index 0000000..0aa930b --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Autoloader.php @@ -0,0 +1,92 @@ +. + */ + +namespace Doctrine\Common\Proxy; + +use Doctrine\Common\Proxy\Exception\InvalidArgumentException; + +/** + * Special Autoloader for Proxy classes, which are not PSR-0 compliant. + * + * @author Benjamin Eberlei + */ +class Autoloader +{ + /** + * Resolves proxy class name to a filename based on the following pattern. + * + * 1. Remove Proxy namespace from class name. + * 2. Remove namespace separators from remaining class name. + * 3. Return PHP filename from proxy-dir with the result from 2. + * + * @param string $proxyDir + * @param string $proxyNamespace + * @param string $className + * + * @return string + * + * @throws InvalidArgumentException + */ + public static function resolveFile($proxyDir, $proxyNamespace, $className) + { + if (0 !== strpos($className, $proxyNamespace)) { + throw InvalidArgumentException::notProxyClass($className, $proxyNamespace); + } + + $className = str_replace('\\', '', substr($className, strlen($proxyNamespace) + 1)); + + return $proxyDir . DIRECTORY_SEPARATOR . $className . '.php'; + } + + /** + * Registers and returns autoloader callback for the given proxy dir and namespace. + * + * @param string $proxyDir + * @param string $proxyNamespace + * @param callable|null $notFoundCallback Invoked when the proxy file is not found. + * + * @return \Closure + * + * @throws InvalidArgumentException + */ + public static function register($proxyDir, $proxyNamespace, $notFoundCallback = null) + { + $proxyNamespace = ltrim($proxyNamespace, '\\'); + + if ( ! (null === $notFoundCallback || is_callable($notFoundCallback))) { + throw InvalidArgumentException::invalidClassNotFoundCallback($notFoundCallback); + } + + $autoloader = function ($className) use ($proxyDir, $proxyNamespace, $notFoundCallback) { + if (0 === strpos($className, $proxyNamespace)) { + $file = Autoloader::resolveFile($proxyDir, $proxyNamespace, $className); + + if ($notFoundCallback && ! file_exists($file)) { + call_user_func($notFoundCallback, $proxyDir, $proxyNamespace, $className); + } + + require $file; + } + }; + + spl_autoload_register($autoloader); + + return $autoloader; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/InvalidArgumentException.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..24a2194 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/InvalidArgumentException.php @@ -0,0 +1,90 @@ +. + */ + +namespace Doctrine\Common\Proxy\Exception; + +use Doctrine\Common\Persistence\Proxy; +use InvalidArgumentException as BaseInvalidArgumentException; + +/** + * Proxy Invalid Argument Exception. + * + * @link www.doctrine-project.org + * @since 2.4 + * @author Marco Pivetta + */ +class InvalidArgumentException extends BaseInvalidArgumentException implements ProxyException +{ + /** + * @return self + */ + public static function proxyDirectoryRequired() + { + return new self('You must configure a proxy directory. See docs for details'); + } + + /** + * @param string $className + * @param string $proxyNamespace + * + * @return self + */ + public static function notProxyClass($className, $proxyNamespace) + { + return new self(sprintf('The class "%s" is not part of the proxy namespace "%s"', $className, $proxyNamespace)); + } + + /** + * @param string $name + * + * @return self + */ + public static function invalidPlaceholder($name) + { + return new self(sprintf('Provided placeholder for "%s" must be either a string or a valid callable', $name)); + } + + /** + * @return self + */ + public static function proxyNamespaceRequired() + { + return new self('You must configure a proxy namespace'); + } + + /** + * @return self + */ + public static function unitializedProxyExpected(Proxy $proxy) + { + return new self(sprintf('Provided proxy of type "%s" must not be initialized.', get_class($proxy))); + } + + /** + * @param mixed $callback + * + * @return self + */ + public static function invalidClassNotFoundCallback($callback) + { + $type = is_object($callback) ? get_class($callback) : gettype($callback); + + return new self(sprintf('Invalid \$notFoundCallback given: must be a callable, "%s" given', $type)); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/ProxyException.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/ProxyException.php new file mode 100644 index 0000000..0d1ff14 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/ProxyException.php @@ -0,0 +1,31 @@ +. + */ + +namespace Doctrine\Common\Proxy\Exception; + +/** + * Base exception interface for proxy exceptions. + * + * @link www.doctrine-project.org + * @since 2.4 + * @author Marco Pivetta + */ +interface ProxyException +{ +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/UnexpectedValueException.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/UnexpectedValueException.php new file mode 100644 index 0000000..73ec64d --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Exception/UnexpectedValueException.php @@ -0,0 +1,62 @@ +. + */ + +namespace Doctrine\Common\Proxy\Exception; + +use UnexpectedValueException as BaseUnexpectedValueException; + +/** + * Proxy Unexpected Value Exception. + * + * @link www.doctrine-project.org + * @since 2.4 + * @author Marco Pivetta + */ +class UnexpectedValueException extends BaseUnexpectedValueException implements ProxyException +{ + /** + * @return self + */ + public static function proxyDirectoryNotWritable() + { + return new self('Your proxy directory must be writable'); + } + + /** + * @param string $className + * @param string $methodName + * @param string $parameterName + * @param \Exception $previous + * + * @return self + */ + public static function invalidParameterTypeHint($className, $methodName, $parameterName, \Exception $previous) + { + return new self( + sprintf( + 'The type hint of parameter "%s" in method "%s" in class "%s" is invalid.', + $parameterName, + $methodName, + $className + ), + 0, + $previous + ); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Proxy.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Proxy.php new file mode 100644 index 0000000..8368430 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/Proxy.php @@ -0,0 +1,90 @@ +. + */ + +namespace Doctrine\Common\Proxy; + +use Doctrine\Common\Persistence\Proxy as BaseProxy; +use Closure; + +/** + * Interface for proxy classes. + * + * @author Roman Borschel + * @author Marco Pivetta + * @since 2.4 + */ +interface Proxy extends BaseProxy +{ + /** + * Marks the proxy as initialized or not. + * + * @param boolean $initialized + * + * @return void + */ + public function __setInitialized($initialized); + + /** + * Sets the initializer callback to be used when initializing the proxy. That + * initializer should accept 3 parameters: $proxy, $method and $params. Those + * are respectively the proxy object that is being initialized, the method name + * that triggered initialization and the parameters passed to that method. + * + * @param Closure|null $initializer + * + * @return void + */ + public function __setInitializer(Closure $initializer = null); + + /** + * Retrieves the initializer callback used to initialize the proxy. + * + * @see __setInitializer + * + * @return Closure|null + */ + public function __getInitializer(); + + /** + * Sets the callback to be used when cloning the proxy. That initializer should accept + * a single parameter, which is the cloned proxy instance itself. + * + * @param Closure|null $cloner + * + * @return void + */ + public function __setCloner(Closure $cloner = null); + + /** + * Retrieves the callback to be used when cloning the proxy. + * + * @see __setCloner + * + * @return Closure|null + */ + public function __getCloner(); + + /** + * Retrieves the list of lazy loaded properties for a given proxy + * + * @return array Keys are the property names, and values are the default values + * for those properties. + */ + public function __getLazyProperties(); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyDefinition.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyDefinition.php new file mode 100644 index 0000000..48b149a --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyDefinition.php @@ -0,0 +1,70 @@ +. + */ + +namespace Doctrine\Common\Proxy; + +/** + * Definition structure how to create a proxy. + * + * @author Benjamin Eberlei + */ +class ProxyDefinition +{ + /** + * @var string + */ + public $proxyClassName; + + /** + * @var array + */ + public $identifierFields; + + /** + * @var \ReflectionProperty[] + */ + public $reflectionFields; + + /** + * @var callable + */ + public $initializer; + + /** + * @var callable + */ + public $cloner; + + /** + * @param string $proxyClassName + * @param array $identifierFields + * @param array $reflectionFields + * @param callable $initializer + * @param callable $cloner + */ + public function __construct($proxyClassName, array $identifierFields, array $reflectionFields, $initializer, $cloner) + { + $this->proxyClassName = $proxyClassName; + $this->identifierFields = $identifierFields; + $this->reflectionFields = $reflectionFields; + $this->initializer = $initializer; + $this->cloner = $cloner; + } +} + diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyGenerator.php b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyGenerator.php new file mode 100644 index 0000000..7fd9391 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Proxy/ProxyGenerator.php @@ -0,0 +1,928 @@ +. + */ + +namespace Doctrine\Common\Proxy; + +use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use Doctrine\Common\Util\ClassUtils; +use Doctrine\Common\Proxy\Exception\InvalidArgumentException; +use Doctrine\Common\Proxy\Exception\UnexpectedValueException; + +/** + * This factory is used to generate proxy classes. + * It builds proxies from given parameters, a template and class metadata. + * + * @author Marco Pivetta + * @since 2.4 + */ +class ProxyGenerator +{ + /** + * Used to match very simple id methods that don't need + * to be decorated since the identifier is known. + */ + const PATTERN_MATCH_ID_METHOD = '((public\s)?(function\s{1,}%s\s?\(\)\s{1,})\s{0,}{\s{0,}return\s{0,}\$this->%s;\s{0,}})i'; + + /** + * The namespace that contains all proxy classes. + * + * @var string + */ + private $proxyNamespace; + + /** + * The directory that contains all proxy classes. + * + * @var string + */ + private $proxyDirectory; + + /** + * Map of callables used to fill in placeholders set in the template. + * + * @var string[]|callable[] + */ + protected $placeholders = array( + 'baseProxyInterface' => 'Doctrine\Common\Proxy\Proxy', + 'additionalProperties' => '', + ); + + /** + * Template used as a blueprint to generate proxies. + * + * @var string + */ + protected $proxyClassTemplate = '; + +/** + * DO NOT EDIT THIS FILE - IT WAS CREATED BY DOCTRINE\'S PROXY GENERATOR + */ +class extends \ implements \ +{ + /** + * @var \Closure the callback responsible for loading properties in the proxy object. This callback is called with + * three parameters, being respectively the proxy object to be initialized, the method that triggered the + * initialization process and an array of ordered parameters that were passed to that method. + * + * @see \Doctrine\Common\Persistence\Proxy::__setInitializer + */ + public $__initializer__; + + /** + * @var \Closure the callback responsible of loading properties that need to be copied in the cloned object + * + * @see \Doctrine\Common\Persistence\Proxy::__setCloner + */ + public $__cloner__; + + /** + * @var boolean flag indicating if this object was already initialized + * + * @see \Doctrine\Common\Persistence\Proxy::__isInitialized + */ + public $__isInitialized__ = false; + + /** + * @var array properties to be lazy loaded, with keys being the property + * names and values being their default values + * + * @see \Doctrine\Common\Persistence\Proxy::__getLazyProperties + */ + public static $lazyPropertiesDefaults = array(); + + + + + + + + + + + + + + + + + + /** + * Forces initialization of the proxy + */ + public function __load() + { + $this->__initializer__ && $this->__initializer__->__invoke($this, \'__load\', array()); + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __isInitialized() + { + return $this->__isInitialized__; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __setInitialized($initialized) + { + $this->__isInitialized__ = $initialized; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __setInitializer(\Closure $initializer = null) + { + $this->__initializer__ = $initializer; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __getInitializer() + { + return $this->__initializer__; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + */ + public function __setCloner(\Closure $cloner = null) + { + $this->__cloner__ = $cloner; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific cloning logic + */ + public function __getCloner() + { + return $this->__cloner__; + } + + /** + * {@inheritDoc} + * @internal generated method: use only when explicitly handling proxy specific loading logic + * @static + */ + public function __getLazyProperties() + { + return self::$lazyPropertiesDefaults; + } + + +} +'; + + /** + * Initializes a new instance of the ProxyFactory class that is + * connected to the given EntityManager. + * + * @param string $proxyDirectory The directory to use for the proxy classes. It must exist. + * @param string $proxyNamespace The namespace to use for the proxy classes. + * + * @throws InvalidArgumentException + */ + public function __construct($proxyDirectory, $proxyNamespace) + { + if ( ! $proxyDirectory) { + throw InvalidArgumentException::proxyDirectoryRequired(); + } + + if ( ! $proxyNamespace) { + throw InvalidArgumentException::proxyNamespaceRequired(); + } + + $this->proxyDirectory = $proxyDirectory; + $this->proxyNamespace = $proxyNamespace; + } + + /** + * Sets a placeholder to be replaced in the template. + * + * @param string $name + * @param string|callable $placeholder + * + * @throws InvalidArgumentException + */ + public function setPlaceholder($name, $placeholder) + { + if ( ! is_string($placeholder) && ! is_callable($placeholder)) { + throw InvalidArgumentException::invalidPlaceholder($name); + } + + $this->placeholders[$name] = $placeholder; + } + + /** + * Sets the base template used to create proxy classes. + * + * @param string $proxyClassTemplate + */ + public function setProxyClassTemplate($proxyClassTemplate) + { + $this->proxyClassTemplate = (string) $proxyClassTemplate; + } + + /** + * Generates a proxy class file. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class Metadata for the original class. + * @param string|bool $fileName Filename (full path) for the generated class. If none is given, eval() is used. + * + * @throws UnexpectedValueException + */ + public function generateProxyClass(ClassMetadata $class, $fileName = false) + { + preg_match_all('(<([a-zA-Z]+)>)', $this->proxyClassTemplate, $placeholderMatches); + + $placeholderMatches = array_combine($placeholderMatches[0], $placeholderMatches[1]); + $placeholders = array(); + + foreach ($placeholderMatches as $placeholder => $name) { + $placeholders[$placeholder] = isset($this->placeholders[$name]) + ? $this->placeholders[$name] + : array($this, 'generate' . $name); + } + + foreach ($placeholders as & $placeholder) { + if (is_callable($placeholder)) { + $placeholder = call_user_func($placeholder, $class); + } + } + + $proxyCode = strtr($this->proxyClassTemplate, $placeholders); + + if ( ! $fileName) { + $proxyClassName = $this->generateNamespace($class) . '\\' . $this->generateProxyShortClassName($class); + + if ( ! class_exists($proxyClassName)) { + eval(substr($proxyCode, 5)); + } + + return; + } + + $parentDirectory = dirname($fileName); + + if ( ! is_dir($parentDirectory) && (false === @mkdir($parentDirectory, 0775, true))) { + throw UnexpectedValueException::proxyDirectoryNotWritable(); + } + + if ( ! is_writable($parentDirectory)) { + throw UnexpectedValueException::proxyDirectoryNotWritable(); + } + + $tmpFileName = $fileName . '.' . uniqid('', true); + + file_put_contents($tmpFileName, $proxyCode); + rename($tmpFileName, $fileName); + } + + /** + * Generates the proxy short class name to be used in the template. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateProxyShortClassName(ClassMetadata $class) + { + $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace); + $parts = explode('\\', strrev($proxyClassName), 2); + + return strrev($parts[0]); + } + + /** + * Generates the proxy namespace. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateNamespace(ClassMetadata $class) + { + $proxyClassName = ClassUtils::generateProxyClassName($class->getName(), $this->proxyNamespace); + $parts = explode('\\', strrev($proxyClassName), 2); + + return strrev($parts[1]); + } + + /** + * Generates the original class name. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateClassName(ClassMetadata $class) + { + return ltrim($class->getName(), '\\'); + } + + /** + * Generates the array representation of lazy loaded public properties and their default values. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateLazyPropertiesDefaults(ClassMetadata $class) + { + $lazyPublicProperties = $this->getLazyLoadedPublicProperties($class); + $values = array(); + + foreach ($lazyPublicProperties as $key => $value) { + $values[] = var_export($key, true) . ' => ' . var_export($value, true); + } + + return implode(', ', $values); + } + + /** + * Generates the constructor code (un-setting public lazy loaded properties, setting identifier field values). + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateConstructorImpl(ClassMetadata $class) + { + $constructorImpl = <<<'EOT' + /** + * @param \Closure $initializer + * @param \Closure $cloner + */ + public function __construct($initializer = null, $cloner = null) + { + +EOT; + $toUnset = array(); + + foreach ($this->getLazyLoadedPublicProperties($class) as $lazyPublicProperty => $unused) { + $toUnset[] = '$this->' . $lazyPublicProperty; + } + + $constructorImpl .= (empty($toUnset) ? '' : ' unset(' . implode(', ', $toUnset) . ");\n") + . <<<'EOT' + + $this->__initializer__ = $initializer; + $this->__cloner__ = $cloner; + } +EOT; + + return $constructorImpl; + } + + /** + * Generates the magic getter invoked when lazy loaded public properties are requested. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateMagicGet(ClassMetadata $class) + { + $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class)); + $reflectionClass = $class->getReflectionClass(); + $hasParentGet = false; + $returnReference = ''; + $inheritDoc = ''; + + if ($reflectionClass->hasMethod('__get')) { + $hasParentGet = true; + $inheritDoc = '{@inheritDoc}'; + + if ($reflectionClass->getMethod('__get')->returnsReference()) { + $returnReference = '& '; + } + } + + if (empty($lazyPublicProperties) && ! $hasParentGet) { + return ''; + } + + $magicGet = <<__getLazyProperties())) { + $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', array($name)); + + return $this->$name; + } + + +EOT; + } + + if ($hasParentGet) { + $magicGet .= <<<'EOT' + $this->__initializer__ && $this->__initializer__->__invoke($this, '__get', array($name)); + + return parent::__get($name); + +EOT; + } else { + $magicGet .= <<<'EOT' + trigger_error(sprintf('Undefined property: %s::$%s', __CLASS__, $name), E_USER_NOTICE); + +EOT; + } + + $magicGet .= " }"; + + return $magicGet; + } + + /** + * Generates the magic setter (currently unused). + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateMagicSet(ClassMetadata $class) + { + $lazyPublicProperties = $this->getLazyLoadedPublicProperties($class); + $hasParentSet = $class->getReflectionClass()->hasMethod('__set'); + + if (empty($lazyPublicProperties) && ! $hasParentSet) { + return ''; + } + + $inheritDoc = $hasParentSet ? '{@inheritDoc}' : ''; + $magicSet = <<__getLazyProperties())) { + $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', array($name, $value)); + + $this->$name = $value; + + return; + } + + +EOT; + } + + if ($hasParentSet) { + $magicSet .= <<<'EOT' + $this->__initializer__ && $this->__initializer__->__invoke($this, '__set', array($name, $value)); + + return parent::__set($name, $value); +EOT; + } else { + $magicSet .= " \$this->\$name = \$value;"; + } + + $magicSet .= "\n }"; + + return $magicSet; + } + + /** + * Generates the magic issetter invoked when lazy loaded public properties are checked against isset(). + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateMagicIsset(ClassMetadata $class) + { + $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class)); + $hasParentIsset = $class->getReflectionClass()->hasMethod('__isset'); + + if (empty($lazyPublicProperties) && ! $hasParentIsset) { + return ''; + } + + $inheritDoc = $hasParentIsset ? '{@inheritDoc}' : ''; + $magicIsset = <<__getLazyProperties())) { + $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', array($name)); + + return isset($this->$name); + } + + +EOT; + } + + if ($hasParentIsset) { + $magicIsset .= <<<'EOT' + $this->__initializer__ && $this->__initializer__->__invoke($this, '__isset', array($name)); + + return parent::__isset($name); + +EOT; + } else { + $magicIsset .= " return false;"; + } + + return $magicIsset . "\n }"; + } + + /** + * Generates implementation for the `__sleep` method of proxies. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateSleepImpl(ClassMetadata $class) + { + $hasParentSleep = $class->getReflectionClass()->hasMethod('__sleep'); + $inheritDoc = $hasParentSleep ? '{@inheritDoc}' : ''; + $sleepImpl = <<__isInitialized__) { + $properties = array_diff($properties, array_keys($this->__getLazyProperties())); + } + + return $properties; + } +EOT; + } + + $allProperties = array('__isInitialized__'); + + /* @var $prop \ReflectionProperty */ + foreach ($class->getReflectionClass()->getProperties() as $prop) { + $allProperties[] = $prop->isPrivate() + ? "\0" . $prop->getDeclaringClass()->getName() . "\0" . $prop->getName() + : $prop->getName(); + } + + $lazyPublicProperties = array_keys($this->getLazyLoadedPublicProperties($class)); + $protectedProperties = array_diff($allProperties, $lazyPublicProperties); + + foreach ($allProperties as &$property) { + $property = var_export($property, true); + } + + foreach ($protectedProperties as &$property) { + $property = var_export($property, true); + } + + $allProperties = implode(', ', $allProperties); + $protectedProperties = implode(', ', $protectedProperties); + + return $sleepImpl . <<__isInitialized__) { + return array($allProperties); + } + + return array($protectedProperties); + } +EOT; + } + + /** + * Generates implementation for the `__wakeup` method of proxies. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateWakeupImpl(ClassMetadata $class) + { + $unsetPublicProperties = array(); + $hasWakeup = $class->getReflectionClass()->hasMethod('__wakeup'); + + foreach (array_keys($this->getLazyLoadedPublicProperties($class)) as $lazyPublicProperty) { + $unsetPublicProperties[] = '$this->' . $lazyPublicProperty; + } + + $shortName = $this->generateProxyShortClassName($class); + $inheritDoc = $hasWakeup ? '{@inheritDoc}' : ''; + $wakeupImpl = <<__isInitialized__) { + \$this->__initializer__ = function ($shortName \$proxy) { + \$proxy->__setInitializer(null); + \$proxy->__setCloner(null); + + \$existingProperties = get_object_vars(\$proxy); + + foreach (\$proxy->__getLazyProperties() as \$property => \$defaultValue) { + if ( ! array_key_exists(\$property, \$existingProperties)) { + \$proxy->\$property = \$defaultValue; + } + } + }; + +EOT; + + if ( ! empty($unsetPublicProperties)) { + $wakeupImpl .= "\n unset(" . implode(', ', $unsetPublicProperties) . ");"; + } + + $wakeupImpl .= "\n }"; + + if ($hasWakeup) { + $wakeupImpl .= "\n parent::__wakeup();"; + } + + $wakeupImpl .= "\n }"; + + return $wakeupImpl; + } + + /** + * Generates implementation for the `__clone` method of proxies. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateCloneImpl(ClassMetadata $class) + { + $hasParentClone = $class->getReflectionClass()->hasMethod('__clone'); + $inheritDoc = $hasParentClone ? '{@inheritDoc}' : ''; + $callParentClone = $hasParentClone ? "\n parent::__clone();\n" : ''; + + return <<__cloner__ && \$this->__cloner__->__invoke(\$this, '__clone', array()); +$callParentClone } +EOT; + } + + /** + * Generates decorated methods by picking those available in the parent class. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return string + */ + private function generateMethods(ClassMetadata $class) + { + $methods = ''; + $methodNames = array(); + $reflectionMethods = $class->getReflectionClass()->getMethods(\ReflectionMethod::IS_PUBLIC); + $skippedMethods = array( + '__sleep' => true, + '__clone' => true, + '__wakeup' => true, + '__get' => true, + '__set' => true, + '__isset' => true, + ); + + foreach ($reflectionMethods as $method) { + $name = $method->getName(); + + if ( + $method->isConstructor() || + isset($skippedMethods[strtolower($name)]) || + isset($methodNames[$name]) || + $method->isFinal() || + $method->isStatic() || + ( ! $method->isPublic()) + ) { + continue; + } + + $methodNames[$name] = true; + $methods .= "\n /**\n" + . " * {@inheritDoc}\n" + . " */\n" + . ' public function '; + + if ($method->returnsReference()) { + $methods .= '&'; + } + + $methods .= $name . '('; + + $firstParam = true; + $parameterString = ''; + $argumentString = ''; + $parameters = array(); + + foreach ($method->getParameters() as $param) { + if ($firstParam) { + $firstParam = false; + } else { + $parameterString .= ', '; + $argumentString .= ', '; + } + + try { + $paramClass = $param->getClass(); + } catch (\ReflectionException $previous) { + throw UnexpectedValueException::invalidParameterTypeHint( + $class->getName(), + $method->getName(), + $param->getName(), + $previous + ); + } + + // We need to pick the type hint class too + if (null !== $paramClass) { + $parameterString .= '\\' . $paramClass->getName() . ' '; + } elseif ($param->isArray()) { + $parameterString .= 'array '; + } elseif (method_exists($param, 'isCallable') && $param->isCallable()) { + $parameterString .= 'callable '; + } + + if ($param->isPassedByReference()) { + $parameterString .= '&'; + } + + $parameters[] = '$' . $param->getName(); + $parameterString .= '$' . $param->getName(); + $argumentString .= '$' . $param->getName(); + + if ($param->isDefaultValueAvailable()) { + $parameterString .= ' = ' . var_export($param->getDefaultValue(), true); + } + } + + $methods .= $parameterString . ')'; + $methods .= "\n" . ' {' . "\n"; + + if ($this->isShortIdentifierGetter($method, $class)) { + $identifier = lcfirst(substr($name, 3)); + $fieldType = $class->getTypeOfField($identifier); + $cast = in_array($fieldType, array('integer', 'smallint')) ? '(int) ' : ''; + + $methods .= ' if ($this->__isInitialized__ === false) {' . "\n"; + $methods .= ' return ' . $cast . ' parent::' . $method->getName() . "();\n"; + $methods .= ' }' . "\n\n"; + } + + $methods .= "\n \$this->__initializer__ " + . "&& \$this->__initializer__->__invoke(\$this, " . var_export($name, true) + . ", array(" . implode(', ', $parameters) . "));" + . "\n\n return parent::" . $name . '(' . $argumentString . ');' + . "\n" . ' }' . "\n"; + } + + return $methods; + } + + /** + * Generates the Proxy file name. + * + * @param string $className + * @param string $baseDirectory Optional base directory for proxy file name generation. + * If not specified, the directory configured on the Configuration of the + * EntityManager will be used by this factory. + * + * @return string + */ + public function getProxyFileName($className, $baseDirectory = null) + { + $baseDirectory = $baseDirectory ?: $this->proxyDirectory; + + return rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . Proxy::MARKER + . str_replace('\\', '', $className) . '.php'; + } + + /** + * Checks if the method is a short identifier getter. + * + * What does this mean? For proxy objects the identifier is already known, + * however accessing the getter for this identifier usually triggers the + * lazy loading, leading to a query that may not be necessary if only the + * ID is interesting for the userland code (for example in views that + * generate links to the entity, but do not display anything else). + * + * @param \ReflectionMethod $method + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return boolean + */ + private function isShortIdentifierGetter($method, ClassMetadata $class) + { + $identifier = lcfirst(substr($method->getName(), 3)); + $startLine = $method->getStartLine(); + $endLine = $method->getEndLine(); + $cheapCheck = ( + $method->getNumberOfParameters() == 0 + && substr($method->getName(), 0, 3) == 'get' + && in_array($identifier, $class->getIdentifier(), true) + && $class->hasField($identifier) + && (($endLine - $startLine) <= 4) + ); + + if ($cheapCheck) { + $code = file($method->getDeclaringClass()->getFileName()); + $code = trim(implode(' ', array_slice($code, $startLine - 1, $endLine - $startLine + 1))); + + $pattern = sprintf(self::PATTERN_MATCH_ID_METHOD, $method->getName(), $identifier); + + if (preg_match($pattern, $code)) { + return true; + } + } + + return false; + } + + /** + * Generates the list of public properties to be lazy loaded, with their default values. + * + * @param \Doctrine\Common\Persistence\Mapping\ClassMetadata $class + * + * @return mixed[] + */ + private function getLazyLoadedPublicProperties(ClassMetadata $class) + { + $defaultProperties = $class->getReflectionClass()->getDefaultProperties(); + $properties = array(); + + foreach ($class->getReflectionClass()->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { + $name = $property->getName(); + + if (($class->hasField($name) || $class->hasAssociation($name)) && ! $class->isIdentifier($name)) { + $properties[$name] = $defaultProperties[$name]; + } + } + + return $properties; + } +} + diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Reflection/ClassFinderInterface.php b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/ClassFinderInterface.php new file mode 100644 index 0000000..639fd69 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/ClassFinderInterface.php @@ -0,0 +1,37 @@ +. + */ + +namespace Doctrine\Common\Reflection; + +/** + * Finds a class in a PSR-0 structure. + * + * @author Karoly Negyesi + */ +interface ClassFinderInterface +{ + /** + * Finds a class. + * + * @param string $class The name of the class. + * + * @return string|null The name of the class or NULL if not found. + */ + public function findFile($class); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Reflection/Psr0FindFile.php b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/Psr0FindFile.php new file mode 100644 index 0000000..418bb0f --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/Psr0FindFile.php @@ -0,0 +1,79 @@ +. + */ + +namespace Doctrine\Common\Reflection; + +/** + * Finds a class in a PSR-0 structure. + * + * @author Karoly Negyesi + */ +class Psr0FindFile implements ClassFinderInterface +{ + /** + * The PSR-0 prefixes. + * + * @var array + */ + protected $prefixes; + + /** + * @param array $prefixes An array of prefixes. Each key is a PHP namespace and each value is + * a list of directories. + */ + public function __construct($prefixes) + { + $this->prefixes = $prefixes; + } + + /** + * {@inheritDoc} + */ + public function findFile($class) + { + $lastNsPos = strrpos($class, '\\'); + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + if (false !== $lastNsPos) { + // namespaced class name + $classPath = str_replace('\\', DIRECTORY_SEPARATOR, substr($class, 0, $lastNsPos)) . DIRECTORY_SEPARATOR; + $className = substr($class, $lastNsPos + 1); + } else { + // PEAR-like class name + $classPath = null; + $className = $class; + } + + $classPath .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php'; + + foreach ($this->prefixes as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (is_file($dir . DIRECTORY_SEPARATOR . $classPath)) { + return $dir . DIRECTORY_SEPARATOR . $classPath; + } + } + } + } + + return null; + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Reflection/ReflectionProviderInterface.php b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/ReflectionProviderInterface.php new file mode 100644 index 0000000..3d970ee --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/ReflectionProviderInterface.php @@ -0,0 +1,48 @@ +. + */ + +namespace Doctrine\Common\Reflection; + +interface ReflectionProviderInterface +{ + /** + * Gets the ReflectionClass equivalent for this class. + * + * @return \ReflectionClass + */ + public function getReflectionClass(); + + /** + * Gets the ReflectionMethod equivalent for this class. + * + * @param string $name + * + * @return \ReflectionMethod + */ + public function getReflectionMethod($name); + + /** + * Gets the ReflectionProperty equivalent for this class. + * + * @param string $name + * + * @return \ReflectionProperty + */ + public function getReflectionProperty($name); +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Reflection/RuntimePublicReflectionProperty.php b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/RuntimePublicReflectionProperty.php new file mode 100644 index 0000000..f155c45 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/RuntimePublicReflectionProperty.php @@ -0,0 +1,76 @@ +. + */ + +namespace Doctrine\Common\Reflection; + +use ReflectionProperty; +use Doctrine\Common\Proxy\Proxy; + +/** + * PHP Runtime Reflection Public Property - special overrides for public properties. + * + * @author Marco Pivetta + * @since 2.4 + */ +class RuntimePublicReflectionProperty extends ReflectionProperty +{ + /** + * {@inheritDoc} + * + * Checks is the value actually exist before fetching it. + * This is to avoid calling `__get` on the provided $object if it + * is a {@see \Doctrine\Common\Proxy\Proxy}. + */ + public function getValue($object = null) + { + $name = $this->getName(); + + if ($object instanceof Proxy && ! $object->__isInitialized()) { + $originalInitializer = $object->__getInitializer(); + $object->__setInitializer(null); + $val = isset($object->$name) ? $object->$name : null; + $object->__setInitializer($originalInitializer); + + return $val; + } + + return isset($object->$name) ? parent::getValue($object) : null; + } + + /** + * {@inheritDoc} + * + * Avoids triggering lazy loading via `__set` if the provided object + * is a {@see \Doctrine\Common\Proxy\Proxy}. + * @link https://bugs.php.net/bug.php?id=63463 + */ + public function setValue($object, $value = null) + { + if ( ! ($object instanceof Proxy && ! $object->__isInitialized())) { + parent::setValue($object, $value); + + return; + } + + $originalInitializer = $object->__getInitializer(); + $object->__setInitializer(null); + parent::setValue($object, $value); + $object->__setInitializer($originalInitializer); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionClass.php b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionClass.php new file mode 100644 index 0000000..b65979a --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionClass.php @@ -0,0 +1,433 @@ +. + */ + +namespace Doctrine\Common\Reflection; + +use ReflectionClass; +use ReflectionException; + +class StaticReflectionClass extends ReflectionClass +{ + /** + * The static reflection parser object. + * + * @var StaticReflectionParser + */ + private $staticReflectionParser; + + /** + * @param StaticReflectionParser $staticReflectionParser + */ + public function __construct(StaticReflectionParser $staticReflectionParser) + { + $this->staticReflectionParser = $staticReflectionParser; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return $this->staticReflectionParser->getClassName(); + } + + /** + * {@inheritDoc} + */ + public function getDocComment() + { + return $this->staticReflectionParser->getDocComment(); + } + + /** + * {@inheritDoc} + */ + public function getNamespaceName() + { + return $this->staticReflectionParser->getNamespaceName(); + } + + /** + * @return array + */ + public function getUseStatements() + { + return $this->staticReflectionParser->getUseStatements(); + } + + /** + * {@inheritDoc} + */ + public function getMethod($name) + { + return $this->staticReflectionParser->getReflectionMethod($name); + } + + /** + * {@inheritDoc} + */ + public function getProperty($name) + { + return $this->staticReflectionParser->getReflectionProperty($name); + } + + /** + * {@inheritDoc} + */ + public static function export($argument, $return = false) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getConstant($name) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getConstants() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getConstructor() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getDefaultProperties() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getEndLine() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getExtension() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getExtensionName() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getFileName() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getInterfaceNames() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getInterfaces() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getMethods($filter = null) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getModifiers() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getParentClass() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getProperties($filter = null) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getShortName() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getStartLine() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getStaticProperties() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getStaticPropertyValue($name, $default = '') + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getTraitAliases() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getTraitNames() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getTraits() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function hasConstant($name) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function hasMethod($name) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function hasProperty($name) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function implementsInterface($interface) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function inNamespace() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isAbstract() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isCloneable() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isFinal() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isInstance($object) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isInstantiable() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isInterface() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isInternal() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isIterateable() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isSubclassOf($class) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isTrait() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isUserDefined() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function newInstance($args) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function newInstanceArgs(array $args = array()) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function newInstanceWithoutConstructor() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function setStaticPropertyValue($name, $value) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function __toString() + { + throw new ReflectionException('Method not implemented'); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionMethod.php b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionMethod.php new file mode 100644 index 0000000..311e130 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionMethod.php @@ -0,0 +1,362 @@ +. + */ + +namespace Doctrine\Common\Reflection; + +use ReflectionMethod; +use ReflectionException; + +class StaticReflectionMethod extends ReflectionMethod +{ + /** + * The PSR-0 parser object. + * + * @var StaticReflectionParser + */ + protected $staticReflectionParser; + + /** + * The name of the method. + * + * @var string + */ + protected $methodName; + + /** + * @param StaticReflectionParser $staticReflectionParser + * @param string $methodName + */ + public function __construct(StaticReflectionParser $staticReflectionParser, $methodName) + { + $this->staticReflectionParser = $staticReflectionParser; + $this->methodName = $methodName; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return $this->methodName; + } + + /** + * @return StaticReflectionParser + */ + protected function getStaticReflectionParser() + { + return $this->staticReflectionParser->getStaticReflectionParserForDeclaringClass('method', $this->methodName); + } + + /** + * {@inheritDoc} + */ + public function getDeclaringClass() + { + return $this->getStaticReflectionParser()->getReflectionClass(); + } + + /** + * {@inheritDoc} + */ + public function getNamespaceName() + { + return $this->getStaticReflectionParser()->getNamespaceName(); + } + + /** + * {@inheritDoc} + */ + public function getDocComment() + { + return $this->getStaticReflectionParser()->getDocComment('method', $this->methodName); + } + + /** + * @return array + */ + public function getUseStatements() + { + return $this->getStaticReflectionParser()->getUseStatements(); + } + + /** + * {@inheritDoc} + */ + public static function export($class, $name, $return = false) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getClosure($object) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getModifiers() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getPrototype() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function invoke($object, $parameter = null) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function invokeArgs($object, array $args) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isAbstract() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isConstructor() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isDestructor() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isFinal() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isPrivate() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isProtected() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isPublic() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isStatic() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function setAccessible($accessible) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function __toString() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getClosureThis() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getEndLine() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getExtension() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getExtensionName() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getFileName() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getNumberOfParameters() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getNumberOfRequiredParameters() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getParameters() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getShortName() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getStartLine() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getStaticVariables() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function inNamespace() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isClosure() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isDeprecated() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isInternal() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isUserDefined() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function returnsReference() + { + throw new ReflectionException('Method not implemented'); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionParser.php b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionParser.php new file mode 100644 index 0000000..22a2c96 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionParser.php @@ -0,0 +1,307 @@ +. + */ + +namespace Doctrine\Common\Reflection; + +use ReflectionException; +use Doctrine\Common\Annotations\TokenParser; + +/** + * Parses a file for namespaces/use/class declarations. + * + * @author Karoly Negyesi + */ +class StaticReflectionParser implements ReflectionProviderInterface +{ + /** + * The fully qualified class name. + * + * @var string + */ + protected $className; + + /** + * The short class name. + * + * @var string + */ + protected $shortClassName; + + /** + * Whether the caller only wants class annotations. + * + * @var boolean. + */ + protected $classAnnotationOptimize; + + /** + * Whether the parser has run. + * + * @var boolean + */ + protected $parsed = false; + + /** + * The namespace of the class. + * + * @var string + */ + protected $namespace = ''; + + /** + * The use statements of the class. + * + * @var array + */ + protected $useStatements = array(); + + /** + * The docComment of the class. + * + * @var string + */ + protected $docComment = array( + 'class' => '', + 'property' => array(), + 'method' => array() + ); + + /** + * The name of the class this class extends, if any. + * + * @var string + */ + protected $parentClassName = ''; + + /** + * The parent PSR-0 Parser. + * + * @var \Doctrine\Common\Reflection\StaticReflectionParser + */ + protected $parentStaticReflectionParser; + + /** + * Parses a class residing in a PSR-0 hierarchy. + * + * @param string $className The full, namespaced class name. + * @param ClassFinderInterface $finder A ClassFinder object which finds the class. + * @param boolean $classAnnotationOptimize Only retrieve the class docComment. + * Presumes there is only one statement per line. + */ + public function __construct($className, $finder, $classAnnotationOptimize = false) + { + $this->className = ltrim($className, '\\'); + $lastNsPos = strrpos($this->className, '\\'); + + if ($lastNsPos !== false) { + $this->namespace = substr($this->className, 0, $lastNsPos); + $this->shortClassName = substr($this->className, $lastNsPos + 1); + } else { + $this->shortClassName = $this->className; + } + + $this->finder = $finder; + $this->classAnnotationOptimize = $classAnnotationOptimize; + } + + /** + * @return void + */ + protected function parse() + { + if ($this->parsed || !$fileName = $this->finder->findFile($this->className)) { + return; + } + $this->parsed = true; + $contents = file_get_contents($fileName); + if ($this->classAnnotationOptimize) { + if (preg_match("/(\A.*)^\s+(abstract|final)?\s+class\s+{$this->shortClassName}\s+{/sm", $contents, $matches)) { + $contents = $matches[1]; + } + } + $tokenParser = new TokenParser($contents); + $docComment = ''; + while ($token = $tokenParser->next(false)) { + if (is_array($token)) { + switch ($token[0]) { + case T_USE: + $this->useStatements = array_merge($this->useStatements, $tokenParser->parseUseStatement()); + break; + case T_DOC_COMMENT: + $docComment = $token[1]; + break; + case T_CLASS: + $this->docComment['class'] = $docComment; + $docComment = ''; + break; + case T_VAR: + case T_PRIVATE: + case T_PROTECTED: + case T_PUBLIC: + $token = $tokenParser->next(); + if ($token[0] === T_VARIABLE) { + $propertyName = substr($token[1], 1); + $this->docComment['property'][$propertyName] = $docComment; + continue 2; + } + if ($token[0] !== T_FUNCTION) { + // For example, it can be T_FINAL. + continue 2; + } + // No break. + case T_FUNCTION: + // The next string after function is the name, but + // there can be & before the function name so find the + // string. + while (($token = $tokenParser->next()) && $token[0] !== T_STRING); + $methodName = $token[1]; + $this->docComment['method'][$methodName] = $docComment; + $docComment = ''; + break; + case T_EXTENDS: + $this->parentClassName = $tokenParser->parseClass(); + $nsPos = strpos($this->parentClassName, '\\'); + $fullySpecified = false; + if ($nsPos === 0) { + $fullySpecified = true; + } else { + if ($nsPos) { + $prefix = strtolower(substr($this->parentClassName, 0, $nsPos)); + $postfix = substr($this->parentClassName, $nsPos); + } else { + $prefix = strtolower($this->parentClassName); + $postfix = ''; + } + foreach ($this->useStatements as $alias => $use) { + if ($alias == $prefix) { + $this->parentClassName = '\\' . $use . $postfix; + $fullySpecified = true; + } + } + } + if (!$fullySpecified) { + $this->parentClassName = '\\' . $this->namespace . '\\' . $this->parentClassName; + } + break; + } + } + } + } + + /** + * @return StaticReflectionParser + */ + protected function getParentStaticReflectionParser() + { + if (empty($this->parentStaticReflectionParser)) { + $this->parentStaticReflectionParser = new static($this->parentClassName, $this->finder); + } + + return $this->parentStaticReflectionParser; + } + + /** + * @return string + */ + public function getClassName() + { + return $this->className; + } + + /** + * @return string + */ + public function getNamespaceName() + { + return $this->namespace; + } + + /** + * {@inheritDoc} + */ + public function getReflectionClass() + { + return new StaticReflectionClass($this); + } + + /** + * {@inheritDoc} + */ + public function getReflectionMethod($methodName) + { + return new StaticReflectionMethod($this, $methodName); + } + + /** + * {@inheritDoc} + */ + public function getReflectionProperty($propertyName) + { + return new StaticReflectionProperty($this, $propertyName); + } + + /** + * Gets the use statements from this file. + * + * @return array + */ + public function getUseStatements() + { + $this->parse(); + + return $this->useStatements; + } + + /** + * Gets the doc comment. + * + * @param string $type The type: 'class', 'property' or 'method'. + * @param string $name The name of the property or method, not needed for 'class'. + * + * @return string The doc comment, empty string if none. + */ + public function getDocComment($type = 'class', $name = '') + { + $this->parse(); + + return $name ? $this->docComment[$type][$name] : $this->docComment[$type]; + } + + /** + * Gets the PSR-0 parser for the declaring class. + * + * @param string $type The type: 'property' or 'method'. + * @param string $name The name of the property or method. + * + * @return StaticReflectionParser A static reflection parser for the declaring class. + * + * @throws ReflectionException + */ + public function getStaticReflectionParserForDeclaringClass($type, $name) + { + $this->parse(); + if (isset($this->docComment[$type][$name])) { + return $this; + } + if (!empty($this->parentClassName)) { + return $this->getParentStaticReflectionParser()->getStaticReflectionParserForDeclaringClass($type, $name); + } + throw new ReflectionException('Invalid ' . $type . ' "' . $name . '"'); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionProperty.php b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionProperty.php new file mode 100644 index 0000000..1664822 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Reflection/StaticReflectionProperty.php @@ -0,0 +1,178 @@ +. + */ + +namespace Doctrine\Common\Reflection; + +use ReflectionProperty; +use ReflectionException; + +class StaticReflectionProperty extends ReflectionProperty +{ + /** + * The PSR-0 parser object. + * + * @var StaticReflectionParser + */ + protected $staticReflectionParser; + + /** + * The name of the property. + * + * @var string|null + */ + protected $propertyName; + + /** + * @param StaticReflectionParser $staticReflectionParser + * @param string|null $propertyName + */ + public function __construct(StaticReflectionParser $staticReflectionParser, $propertyName) + { + $this->staticReflectionParser = $staticReflectionParser; + $this->propertyName = $propertyName; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return $this->propertyName; + } + + /** + * @return StaticReflectionParser + */ + protected function getStaticReflectionParser() + { + return $this->staticReflectionParser->getStaticReflectionParserForDeclaringClass('property', $this->propertyName); + } + + /** + * {@inheritDoc} + */ + public function getDeclaringClass() + { + return $this->getStaticReflectionParser()->getReflectionClass(); + } + + /** + * {@inheritDoc} + */ + public function getDocComment() + { + return $this->getStaticReflectionParser()->getDocComment('property', $this->propertyName); + } + + /** + * @return array + */ + public function getUseStatements() + { + return $this->getStaticReflectionParser()->getUseStatements(); + } + + /** + * {@inheritDoc} + */ + public static function export ($class, $name, $return = false) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getModifiers() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getValue($object = null) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isDefault() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isPrivate() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isProtected() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isPublic() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isStatic() + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function setAccessible ($accessible) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function setValue ($object, $value = null) + { + throw new ReflectionException('Method not implemented'); + } + + /** + * {@inheritDoc} + */ + public function __toString() + { + throw new ReflectionException('Method not implemented'); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Util/ClassUtils.php b/vendor/doctrine/common/lib/Doctrine/Common/Util/ClassUtils.php new file mode 100644 index 0000000..49dc7bb --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Util/ClassUtils.php @@ -0,0 +1,109 @@ +. + */ + +namespace Doctrine\Common\Util; + +use Doctrine\Common\Persistence\Proxy; + +/** + * Class and reflection related functionality for objects that + * might or not be proxy objects at the moment. + * + * @author Benjamin Eberlei + * @author Johannes Schmitt + */ +class ClassUtils +{ + /** + * Gets the real class name of a class name that could be a proxy. + * + * @param string $class + * + * @return string + */ + public static function getRealClass($class) + { + if (false === $pos = strrpos($class, '\\'.Proxy::MARKER.'\\')) { + return $class; + } + + return substr($class, $pos + Proxy::MARKER_LENGTH + 2); + } + + /** + * Gets the real class name of an object (even if its a proxy). + * + * @param object $object + * + * @return string + */ + public static function getClass($object) + { + return self::getRealClass(get_class($object)); + } + + /** + * Gets the real parent class name of a class or object. + * + * @param string $className + * + * @return string + */ + public static function getParentClass($className) + { + return get_parent_class( self::getRealClass( $className ) ); + } + + /** + * Creates a new reflection class. + * + * @param string $class + * + * @return \ReflectionClass + */ + public static function newReflectionClass($class) + { + return new \ReflectionClass( self::getRealClass( $class ) ); + } + + /** + * Creates a new reflection object. + * + * @param object $object + * + * @return \ReflectionObject + */ + public static function newReflectionObject($object) + { + return self::newReflectionClass( self::getClass( $object ) ); + } + + /** + * Given a class name and a proxy namespace returns the proxy name. + * + * @param string $className + * @param string $proxyNamespace + * + * @return string + */ + public static function generateProxyClassName($className, $proxyNamespace) + { + return rtrim($proxyNamespace, '\\') . '\\'.Proxy::MARKER.'\\' . ltrim($className, '\\'); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Util/Debug.php b/vendor/doctrine/common/lib/Doctrine/Common/Util/Debug.php new file mode 100644 index 0000000..60a5ace --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Util/Debug.php @@ -0,0 +1,146 @@ +. + */ + +namespace Doctrine\Common\Util; + +use Doctrine\Common\Persistence\Proxy; + +/** + * Static class containing most used debug methods. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Giorgio Sironi + */ +final class Debug +{ + /** + * Private constructor (prevents instantiation). + */ + private function __construct() + { + } + + /** + * Prints a dump of the public, protected and private properties of $var. + * + * @link http://xdebug.org/ + * + * @param mixed $var The variable to dump. + * @param integer $maxDepth The maximum nesting level for object properties. + * @param boolean $stripTags Whether output should strip HTML tags. + */ + public static function dump($var, $maxDepth = 2, $stripTags = true) + { + $html = ini_get('html_errors'); + + if ($html !== true) { + ini_set('html_errors', true); + } + + if (extension_loaded('xdebug')) { + ini_set('xdebug.var_display_max_depth', $maxDepth); + } + + $var = self::export($var, $maxDepth++); + + ob_start(); + var_dump($var); + $dump = ob_get_contents(); + ob_end_clean(); + + echo ($stripTags ? strip_tags(html_entity_decode($dump)) : $dump); + + ini_set('html_errors', $html); + } + + /** + * @param mixed $var + * @param int $maxDepth + * + * @return mixed + */ + public static function export($var, $maxDepth) + { + $return = null; + $isObj = is_object($var); + + if ($isObj && in_array('Doctrine\Common\Collections\Collection', class_implements($var))) { + $var = $var->toArray(); + } + + if ($maxDepth) { + if (is_array($var)) { + $return = array(); + + foreach ($var as $k => $v) { + $return[$k] = self::export($v, $maxDepth - 1); + } + } else if ($isObj) { + $return = new \stdclass(); + if ($var instanceof \DateTime) { + $return->__CLASS__ = "DateTime"; + $return->date = $var->format('c'); + $return->timezone = $var->getTimeZone()->getName(); + } else { + $reflClass = ClassUtils::newReflectionObject($var); + $return->__CLASS__ = ClassUtils::getClass($var); + + if ($var instanceof Proxy) { + $return->__IS_PROXY__ = true; + $return->__PROXY_INITIALIZED__ = $var->__isInitialized(); + } + + if ($var instanceof \ArrayObject || $var instanceof \ArrayIterator) { + $return->__STORAGE__ = self::export($var->getArrayCopy(), $maxDepth - 1); + } + + foreach ($reflClass->getProperties() as $reflProperty) { + $name = $reflProperty->getName(); + + $reflProperty->setAccessible(true); + $return->$name = self::export($reflProperty->getValue($var), $maxDepth - 1); + } + } + } else { + $return = $var; + } + } else { + $return = is_object($var) ? get_class($var) + : (is_array($var) ? 'Array(' . count($var) . ')' : $var); + } + + return $return; + } + + /** + * Returns a string representation of an object. + * + * @param object $obj + * + * @return string + */ + public static function toString($obj) + { + return method_exists($obj, '__toString') ? (string) $obj : get_class($obj) . '@' . spl_object_hash($obj); + } +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Util/Inflector.php b/vendor/doctrine/common/lib/Doctrine/Common/Util/Inflector.php new file mode 100644 index 0000000..082dc78 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Util/Inflector.php @@ -0,0 +1,31 @@ +. + */ + +namespace Doctrine\Common\Util; + +use Doctrine\Common\Inflector\Inflector as BaseInflector; + +/** + * Doctrine inflector has static methods for inflecting text. + * + * Kept for backwards compatibility reasons, was moved to its own component. + */ +class Inflector extends BaseInflector +{ +} diff --git a/vendor/doctrine/common/lib/Doctrine/Common/Version.php b/vendor/doctrine/common/lib/Doctrine/Common/Version.php new file mode 100644 index 0000000..f23a461 --- /dev/null +++ b/vendor/doctrine/common/lib/Doctrine/Common/Version.php @@ -0,0 +1,53 @@ +. + */ + +namespace Doctrine\Common; + +/** + * Class to store and retrieve the version of Doctrine. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class Version +{ + /** + * Current Doctrine Version. + */ + const VERSION = '2.4.1'; + + /** + * Compares a Doctrine version with the current one. + * + * @param string $version Doctrine version to compare. + * + * @return int -1 if older, 0 if it is the same, 1 if version passed as argument is newer. + */ + public static function compare($version) + { + $currentVersion = str_replace(' ', '', strtolower(self::VERSION)); + $version = str_replace(' ', '', $version); + + return version_compare($version, $currentVersion); + } +} diff --git a/vendor/doctrine/common/phpunit.xml.dist b/vendor/doctrine/common/phpunit.xml.dist new file mode 100644 index 0000000..b9d3b34 --- /dev/null +++ b/vendor/doctrine/common/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + ./tests/Doctrine/ + + + + + + ./lib/Doctrine/ + + + + + + performance + + + diff --git a/vendor/doctrine/common/tests/.gitignore b/vendor/doctrine/common/tests/.gitignore new file mode 100644 index 0000000..7210405 --- /dev/null +++ b/vendor/doctrine/common/tests/.gitignore @@ -0,0 +1,3 @@ +Doctrine/Tests/Proxies/ +Doctrine/Tests/ORM/Proxy/generated/ +Doctrine/Tests/ORM/Tools/Export/export diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/ClassLoaderTest.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/ClassLoaderTest.php new file mode 100644 index 0000000..1eb2216 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/ClassLoaderTest.php @@ -0,0 +1,64 @@ +setIncludePath(__DIR__); + $classLoader->setFileExtension('.class.php'); + $classLoader->setNamespaceSeparator('_'); + + $this->assertTrue($classLoader->canLoadClass('ClassLoaderTest_ClassA')); + $this->assertTrue($classLoader->canLoadClass('ClassLoaderTest_ClassB')); + $this->assertTrue($classLoader->canLoadClass('ClassLoaderTest_ClassC')); + $this->assertFalse($classLoader->canLoadClass('OtherClass')); + $this->assertEquals($classLoader->loadClass('ClassLoaderTest_ClassA'), true); + $this->assertEquals($classLoader->loadClass('ClassLoaderTest_ClassB'), true); + $this->assertEquals($classLoader->loadClass('ClassLoaderTest_ClassC'), true); + } + + public function testClassExists() + { + $this->assertFalse(ClassLoader::classExists('ClassLoaderTest\ClassD')); + $badLoader = function($className) { + require __DIR__ . '/ClassLoaderTest/ClassD.php'; + return true; + }; + spl_autoload_register($badLoader); + $this->assertTrue(ClassLoader::classExists('ClassLoaderTest\ClassD')); + spl_autoload_unregister($badLoader); + } + + public function testGetClassLoader() + { + $cl = new ClassLoader('ClassLoaderTest', __DIR__); + $cl->register(); + $this->assertTrue(ClassLoader::getClassLoader('ClassLoaderTest\ClassD') instanceof \Doctrine\Common\ClassLoader); + $this->assertNull(ClassLoader::getClassLoader('This\Class\Does\Not\Exist')); + $cl->unregister(); + } + + public function testClassExistsWithSilentAutoloader() + { + $test = $this; + $silentLoader = function ($className) use ($test) { + $test->assertSame('ClassLoaderTest\ClassE', $className); + require __DIR__ . '/ClassLoaderTest/ClassE.php'; + }; + $additionalLoader = function () use ($test) { + $test->fail('Should not call this loader, class was already loaded'); + }; + + $this->assertFalse(ClassLoader::classExists('ClassLoaderTest\ClassE')); + spl_autoload_register($silentLoader); + spl_autoload_register($additionalLoader); + $this->assertTrue(ClassLoader::classExists('ClassLoaderTest\ClassE')); + spl_autoload_unregister($additionalLoader); + spl_autoload_unregister($silentLoader); + } +} diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/ClassLoaderTest/ClassA.class.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/ClassLoaderTest/ClassA.class.php new file mode 100644 index 0000000..8554654 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/ClassLoaderTest/ClassA.class.php @@ -0,0 +1,6 @@ +_eventManager = new EventManager; + $this->_preFooInvoked = false; + $this->_postFooInvoked = false; + } + + public function testInitialState() + { + $this->assertEquals(array(), $this->_eventManager->getListeners()); + $this->assertFalse($this->_eventManager->hasListeners(self::preFoo)); + $this->assertFalse($this->_eventManager->hasListeners(self::postFoo)); + } + + public function testAddEventListener() + { + $this->_eventManager->addEventListener(array('preFoo', 'postFoo'), $this); + $this->assertTrue($this->_eventManager->hasListeners(self::preFoo)); + $this->assertTrue($this->_eventManager->hasListeners(self::postFoo)); + $this->assertEquals(1, count($this->_eventManager->getListeners(self::preFoo))); + $this->assertEquals(1, count($this->_eventManager->getListeners(self::postFoo))); + $this->assertEquals(2, count($this->_eventManager->getListeners())); + } + + public function testDispatchEvent() + { + $this->_eventManager->addEventListener(array('preFoo', 'postFoo'), $this); + $this->_eventManager->dispatchEvent(self::preFoo); + $this->assertTrue($this->_preFooInvoked); + $this->assertFalse($this->_postFooInvoked); + } + + public function testRemoveEventListener() + { + $this->_eventManager->addEventListener(array('preBar'), $this); + $this->assertTrue($this->_eventManager->hasListeners(self::preBar)); + $this->_eventManager->removeEventListener(array('preBar'), $this); + $this->assertFalse($this->_eventManager->hasListeners(self::preBar)); + } + + public function testAddEventSubscriber() + { + $eventSubscriber = new TestEventSubscriber(); + $this->_eventManager->addEventSubscriber($eventSubscriber); + $this->assertTrue($this->_eventManager->hasListeners(self::preFoo)); + $this->assertTrue($this->_eventManager->hasListeners(self::postFoo)); + } + + /* Listener methods */ + + public function preFoo(EventArgs $e) + { + $this->_preFooInvoked = true; + } + + public function postFoo(EventArgs $e) + { + $this->_postFooInvoked = true; + } +} + +class TestEventSubscriber implements \Doctrine\Common\EventSubscriber +{ + public function getSubscribedEvents() + { + return array('preFoo', 'postFoo'); + } +} \ No newline at end of file diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/ChainDriverTest.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/ChainDriverTest.php new file mode 100644 index 0000000..f9edd10 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/ChainDriverTest.php @@ -0,0 +1,152 @@ +getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata'); + + $chain = new MappingDriverChain(); + + $driver1 = $this->getMock('Doctrine\Common\Persistence\Mapping\Driver\MappingDriver'); + $driver1->expects($this->never()) + ->method('loadMetadataForClass'); + $driver1->expectS($this->never()) + ->method('isTransient'); + + $driver2 = $this->getMock('Doctrine\Common\Persistence\Mapping\Driver\MappingDriver'); + $driver2->expects($this->at(0)) + ->method('loadMetadataForClass') + ->with($this->equalTo($className), $this->equalTo($classMetadata)); + $driver2->expects($this->at(1)) + ->method('isTransient') + ->with($this->equalTo($className)) + ->will($this->returnValue( true )); + + $chain->addDriver($driver1, 'Doctrine\Tests\Models\Company'); + $chain->addDriver($driver2, 'Doctrine\Tests\Common\Persistence\Mapping'); + + $chain->loadMetadataForClass($className, $classMetadata); + + $this->assertTrue( $chain->isTransient($className) ); + } + + public function testLoadMetadata_NoDelegatorFound_ThrowsMappingException() + { + $className = 'Doctrine\Tests\Common\Persistence\Mapping\DriverChainEntity'; + $classMetadata = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata'); + + $chain = new MappingDriverChain(); + + $this->setExpectedException('Doctrine\Common\Persistence\Mapping\MappingException'); + $chain->loadMetadataForClass($className, $classMetadata); + } + + public function testGatherAllClassNames() + { + $className = 'Doctrine\Tests\Common\Persistence\Mapping\DriverChainEntity'; + $classMetadata = $this->getMock('Doctrine\Common\Persistence\ClassMetadata'); + + $chain = new MappingDriverChain(); + + $driver1 = $this->getMock('Doctrine\Common\Persistence\Mapping\Driver\MappingDriver'); + $driver1->expects($this->once()) + ->method('getAllClassNames') + ->will($this->returnValue(array('Doctrine\Tests\Models\Company\Foo'))); + + $driver2 = $this->getMock('Doctrine\Common\Persistence\Mapping\Driver\MappingDriver'); + $driver2->expects($this->once()) + ->method('getAllClassNames') + ->will($this->returnValue(array('Doctrine\Tests\ORM\Mapping\Bar', 'Doctrine\Tests\ORM\Mapping\Baz', 'FooBarBaz'))); + + $chain->addDriver($driver1, 'Doctrine\Tests\Models\Company'); + $chain->addDriver($driver2, 'Doctrine\Tests\ORM\Mapping'); + + $this->assertEquals(array( + 'Doctrine\Tests\Models\Company\Foo', + 'Doctrine\Tests\ORM\Mapping\Bar', + 'Doctrine\Tests\ORM\Mapping\Baz' + ), $chain->getAllClassNames()); + } + + /** + * @group DDC-706 + */ + public function testIsTransient() + { + $driver1 = $this->getMock('Doctrine\Common\Persistence\Mapping\Driver\MappingDriver'); + $chain = new MappingDriverChain(); + $chain->addDriver($driver1, 'Doctrine\Tests\Models\CMS'); + + $this->assertTrue($chain->isTransient('stdClass'), "stdClass isTransient"); + } + + /** + * @group DDC-1412 + */ + public function testDefaultDriver() + { + $companyDriver = $this->getMock('Doctrine\Common\Persistence\Mapping\Driver\MappingDriver'); + $defaultDriver = $this->getMock('Doctrine\Common\Persistence\Mapping\Driver\MappingDriver'); + $entityClassName = 'Doctrine\Tests\ORM\Mapping\DriverChainEntity'; + $managerClassName = 'Doctrine\Tests\Models\Company\CompanyManager'; + $chain = new MappingDriverChain(); + + $companyDriver->expects($this->never()) + ->method('loadMetadataForClass'); + $companyDriver->expects($this->once()) + ->method('isTransient') + ->with($this->equalTo($managerClassName)) + ->will($this->returnValue(false)); + + $defaultDriver->expects($this->never()) + ->method('loadMetadataForClass'); + $defaultDriver->expects($this->once()) + ->method('isTransient') + ->with($this->equalTo($entityClassName)) + ->will($this->returnValue(true)); + + $this->assertNull($chain->getDefaultDriver()); + + $chain->setDefaultDriver($defaultDriver); + $chain->addDriver($companyDriver, 'Doctrine\Tests\Models\Company'); + + $this->assertSame($defaultDriver, $chain->getDefaultDriver()); + + $this->assertTrue($chain->isTransient($entityClassName)); + $this->assertFalse($chain->isTransient($managerClassName)); + } + + public function testDefaultDriverGetAllClassNames() + { + $companyDriver = $this->getMock('Doctrine\Common\Persistence\Mapping\Driver\MappingDriver'); + $defaultDriver = $this->getMock('Doctrine\Common\Persistence\Mapping\Driver\MappingDriver'); + $chain = new MappingDriverChain(); + + $companyDriver->expects($this->once()) + ->method('getAllClassNames') + ->will($this->returnValue(array('Doctrine\Tests\Models\Company\Foo'))); + + $defaultDriver->expects($this->once()) + ->method('getAllClassNames') + ->will($this->returnValue(array('Other\Class'))); + + $chain->setDefaultDriver($defaultDriver); + $chain->addDriver($companyDriver, 'Doctrine\Tests\Models\Company'); + + $classNames = $chain->getAllClassNames(); + + $this->assertEquals(array('Doctrine\Tests\Models\Company\Foo', 'Other\Class'), $classNames); + } +} + +class DriverChainEntity +{ + +} diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/ClassMetadataFactoryTest.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/ClassMetadataFactoryTest.php new file mode 100644 index 0000000..c5a457f --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/ClassMetadataFactoryTest.php @@ -0,0 +1,145 @@ +getMock('Doctrine\Common\Persistence\Mapping\Driver\MappingDriver'); + $metadata = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata'); + $this->cmf = new TestClassMetadataFactory($driver, $metadata); + } + + public function testGetCacheDriver() + { + $this->assertNull($this->cmf->getCacheDriver()); + $cache = new ArrayCache(); + $this->cmf->setCacheDriver($cache); + $this->assertSame($cache, $this->cmf->getCacheDriver()); + } + + public function testGetMetadataFor() + { + $metadata = $this->cmf->getMetadataFor('stdClass'); + + $this->assertInstanceOf('Doctrine\Common\Persistence\Mapping\ClassMetadata', $metadata); + $this->assertTrue($this->cmf->hasMetadataFor('stdClass')); + } + + public function testGetMetadataForAbsentClass() + { + $this->setExpectedException('Doctrine\Common\Persistence\Mapping\MappingException'); + $this->cmf->getMetadataFor(__NAMESPACE__ . '\AbsentClass'); + } + + public function testGetParentMetadata() + { + $metadata = $this->cmf->getMetadataFor(__NAMESPACE__ . '\ChildEntity'); + + $this->assertInstanceOf('Doctrine\Common\Persistence\Mapping\ClassMetadata', $metadata); + $this->assertTrue($this->cmf->hasMetadataFor(__NAMESPACE__ . '\ChildEntity')); + $this->assertTrue($this->cmf->hasMetadataFor(__NAMESPACE__ . '\RootEntity')); + } + + public function testGetCachedMetadata() + { + $metadata = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata'); + $cache = new ArrayCache(); + $cache->save(__NAMESPACE__. '\ChildEntity$CLASSMETADATA', $metadata); + + $this->cmf->setCacheDriver($cache); + + $loadedMetadata = $this->cmf->getMetadataFor(__NAMESPACE__ . '\ChildEntity'); + $this->assertSame($loadedMetadata, $metadata); + } + + public function testCacheGetMetadataFor() + { + $cache = new ArrayCache(); + $this->cmf->setCacheDriver($cache); + + $loadedMetadata = $this->cmf->getMetadataFor(__NAMESPACE__ . '\ChildEntity'); + + $this->assertSame($loadedMetadata, $cache->fetch(__NAMESPACE__. '\ChildEntity$CLASSMETADATA')); + } + + public function testGetAliasedMetadata() + { + $loadedMetadata = $this->cmf->getMetadataFor('prefix:ChildEntity'); + + $this->assertTrue($this->cmf->hasMetadataFor(__NAMESPACE__ . '\ChildEntity')); + $this->assertTrue($this->cmf->hasMetadataFor('prefix:ChildEntity')); + } +} + +class TestClassMetadataFactory extends AbstractClassMetadataFactory +{ + public $driver; + public $metadata; + + public function __construct($driver, $metadata) + { + $this->driver = $driver; + $this->metadata = $metadata; + } + + protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonSuperclassParents) + { + + } + + protected function getFqcnFromAlias($namespaceAlias, $simpleClassName) + { + return __NAMESPACE__ . '\\' . $simpleClassName; + } + + protected function initialize() + { + + } + + protected function newClassMetadataInstance($className) + { + return $this->metadata; + } + + protected function getDriver() + { + return $this->driver; + } + protected function wakeupReflection(ClassMetadata $class, ReflectionService $reflService) + { + } + + protected function initializeReflection(ClassMetadata $class, ReflectionService $reflService) + { + } + + protected function isEntity(ClassMetadata $class) + { + return true; + } +} + +class RootEntity +{ + +} + +class ChildEntity extends RootEntity +{ + +} diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/DefaultFileLocatorTest.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/DefaultFileLocatorTest.php new file mode 100644 index 0000000..37072de --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/DefaultFileLocatorTest.php @@ -0,0 +1,90 @@ +assertEquals(array($path), $locator->getPaths()); + + $locator = new DefaultFileLocator($path); + $this->assertEquals(array($path), $locator->getPaths()); + } + + public function testGetFileExtension() + { + $locator = new DefaultFileLocator(array(), ".yml"); + $this->assertEquals(".yml", $locator->getFileExtension()); + $locator->setFileExtension(".xml"); + $this->assertEquals(".xml", $locator->getFileExtension()); + } + + public function testUniquePaths() + { + $path = __DIR__ . "/_files"; + + $locator = new DefaultFileLocator(array($path, $path)); + $this->assertEquals(array($path), $locator->getPaths()); + } + + public function testFindMappingFile() + { + $path = __DIR__ . "/_files"; + + $locator = new DefaultFileLocator(array($path), ".yml"); + + $this->assertEquals(__DIR__ . '/_files' . DIRECTORY_SEPARATOR . 'stdClass.yml', $locator->findMappingFile('stdClass')); + } + + public function testFindMappingFileNotFound() + { + $path = __DIR__ . "/_files"; + + $locator = new DefaultFileLocator(array($path), ".yml"); + + $this->setExpectedException( + 'Doctrine\Common\Persistence\Mapping\MappingException', + "No mapping file found named 'stdClass2.yml' for class 'stdClass2'" + ); + $locator->findMappingFile('stdClass2'); + } + + public function testGetAllClassNames() + { + $path = __DIR__ . "/_files"; + + $locator = new DefaultFileLocator(array($path), ".yml"); + $classes = $locator->getAllClassNames(null); + sort($classes); + + $this->assertEquals(array('global', 'stdClass'), $classes); + $this->assertEquals(array('stdClass'), $locator->getAllClassNames("global")); + } + + public function testGetAllClassNamesNonMatchingFileExtension() + { + $path = __DIR__ . "/_files"; + + $locator = new DefaultFileLocator(array($path), ".xml"); + $this->assertEquals(array(), $locator->getAllClassNames("global")); + } + + public function testFileExists() + { + $path = __DIR__ . "/_files"; + + $locator = new DefaultFileLocator(array($path), ".yml"); + + $this->assertTrue($locator->fileExists("stdClass")); + $this->assertFalse($locator->fileExists("stdClass2")); + $this->assertTrue($locator->fileExists("global")); + $this->assertFalse($locator->fileExists("global2")); + } +} diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/FileDriverTest.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/FileDriverTest.php new file mode 100644 index 0000000..020c242 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/FileDriverTest.php @@ -0,0 +1,142 @@ +assertNull($driver->getGlobalBasename()); + + $driver->setGlobalBasename("global"); + $this->assertEquals("global", $driver->getGlobalBasename()); + } + + public function testGetElementFromGlobalFile() + { + $driver = new TestFileDriver($this->newLocator()); + $driver->setGlobalBasename("global"); + + $element = $driver->getElement('stdGlobal'); + + $this->assertEquals('stdGlobal', $element); + } + + public function testGetElementFromFile() + { + $locator = $this->newLocator(); + $locator->expects($this->once()) + ->method('findMappingFile') + ->with($this->equalTo('stdClass')) + ->will($this->returnValue(__DIR__ . '/_files/stdClass.yml')); + + $driver = new TestFileDriver($locator); + + $this->assertEquals('stdClass', $driver->getElement('stdClass')); + } + + public function testGetAllClassNamesGlobalBasename() + { + $driver = new TestFileDriver($this->newLocator()); + $driver->setGlobalBasename("global"); + + $classNames = $driver->getAllClassNames(); + + $this->assertEquals(array('stdGlobal', 'stdGlobal2'), $classNames); + } + + public function testGetAllClassNamesFromMappingFile() + { + $locator = $this->newLocator(); + $locator->expects($this->any()) + ->method('getAllClassNames') + ->with($this->equalTo(null)) + ->will($this->returnValue(array('stdClass'))); + $driver = new TestFileDriver($locator); + + $classNames = $driver->getAllClassNames(); + + $this->assertEquals(array('stdClass'), $classNames); + } + + public function testGetAllClassNamesBothSources() + { + $locator = $this->newLocator(); + $locator->expects($this->any()) + ->method('getAllClassNames') + ->with($this->equalTo('global')) + ->will($this->returnValue(array('stdClass'))); + $driver = new TestFileDriver($locator); + $driver->setGlobalBasename("global"); + + $classNames = $driver->getAllClassNames(); + + $this->assertEquals(array('stdGlobal', 'stdGlobal2', 'stdClass'), $classNames); + } + + public function testIsNotTransient() + { + $locator = $this->newLocator(); + $locator->expects($this->once()) + ->method('fileExists') + ->with($this->equalTo('stdClass')) + ->will($this->returnValue( true )); + + $driver = new TestFileDriver($locator); + $driver->setGlobalBasename("global"); + + $this->assertFalse($driver->isTransient('stdClass')); + $this->assertFalse($driver->isTransient('stdGlobal')); + $this->assertFalse($driver->isTransient('stdGlobal2')); + } + + public function testIsTransient() + { + $locator = $this->newLocator(); + $locator->expects($this->once()) + ->method('fileExists') + ->with($this->equalTo('stdClass2')) + ->will($this->returnValue( false )); + + $driver = new TestFileDriver($locator); + + $this->assertTrue($driver->isTransient('stdClass2')); + } + + public function testNonLocatorFallback() + { + $driver = new TestFileDriver(__DIR__ . '/_files', '.yml'); + $this->assertTrue($driver->isTransient('stdClass2')); + $this->assertFalse($driver->isTransient('stdClass')); + } + + private function newLocator() + { + $locator = $this->getMock('Doctrine\Common\Persistence\Mapping\Driver\FileLocator'); + $locator->expects($this->any())->method('getFileExtension')->will($this->returnValue('.yml')); + $locator->expects($this->any())->method('getPaths')->will($this->returnValue(array(__DIR__ . "/_files"))); + return $locator; + } +} + +class TestFileDriver extends FileDriver +{ + protected function loadMappingFile($file) + { + if (strpos($file, "global.yml") !== false) { + return array('stdGlobal' => 'stdGlobal', 'stdGlobal2' => 'stdGlobal2'); + } + return array('stdClass' => 'stdClass'); + } + + public function loadMetadataForClass($className, ClassMetadata $metadata) + { + + } +} \ No newline at end of file diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/PHPDriverTest.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/PHPDriverTest.php new file mode 100644 index 0000000..8fc4d80 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/PHPDriverTest.php @@ -0,0 +1,18 @@ +getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata'); + $metadata->expects($this->once())->method('getFieldNames'); + + $driver = new PHPDriver(array(__DIR__ . "/_files")); + $driver->loadMetadataForClass('TestEntity', $metadata); + } +} \ No newline at end of file diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/RuntimeReflectionServiceTest.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/RuntimeReflectionServiceTest.php new file mode 100644 index 0000000..767ccd6 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/RuntimeReflectionServiceTest.php @@ -0,0 +1,84 @@ +. + */ + +namespace Doctrine\Tests\Common\Persistence\Mapping; + +use Doctrine\Common\Persistence\Mapping\RuntimeReflectionService; + +/** + * @group DCOM-93 + */ +class RuntimeReflectionServiceTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var RuntimeReflectionService + */ + private $reflectionService; + + public $unusedPublicProperty; + + public function setUp() + { + $this->reflectionService = new RuntimeReflectionService(); + } + + public function testShortname() + { + $this->assertEquals("RuntimeReflectionServiceTest", $this->reflectionService->getClassShortName(__CLASS__)); + } + + public function testClassNamespaceName() + { + $this->assertEquals("Doctrine\Tests\Common\Persistence\Mapping", $this->reflectionService->getClassNamespace(__CLASS__)); + } + + public function testGetParentClasses() + { + $classes = $this->reflectionService->getParentClasses(__CLASS__); + $this->assertTrue(count($classes) >= 1, "The test class ".__CLASS__." should have at least one parent."); + } + + public function testGetParentClassesForAbsentClass() + { + $this->setExpectedException('Doctrine\Common\Persistence\Mapping\MappingException'); + $this->reflectionService->getParentClasses(__NAMESPACE__ . '\AbsentClass'); + } + + public function testGetReflectionClass() + { + $class = $this->reflectionService->getClass(__CLASS__); + $this->assertInstanceOf("ReflectionClass", $class); + } + + public function testGetMethods() + { + $this->assertTrue($this->reflectionService->hasPublicMethod(__CLASS__, "testGetMethods")); + $this->assertFalse($this->reflectionService->hasPublicMethod(__CLASS__, "testGetMethods2")); + } + + public function testGetAccessibleProperty() + { + $reflProp = $this->reflectionService->getAccessibleProperty(__CLASS__, "reflectionService"); + $this->assertInstanceOf("ReflectionProperty", $reflProp); + + $reflProp = $this->reflectionService->getAccessibleProperty(__CLASS__, "unusedPublicProperty"); + $this->assertInstanceOf("Doctrine\Common\Reflection\RuntimePublicReflectionProperty", $reflProp); + } +} + diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/StaticPHPDriverTest.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/StaticPHPDriverTest.php new file mode 100644 index 0000000..9f1c568 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/StaticPHPDriverTest.php @@ -0,0 +1,35 @@ +getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata'); + $metadata->expects($this->once())->method('getFieldNames'); + + $driver = new StaticPHPDriver(array(__DIR__)); + $driver->loadMetadataForClass(__NAMESPACE__ . '\\TestEntity', $metadata); + } + + public function testGetAllClassNames() + { + $driver = new StaticPHPDriver(array(__DIR__)); + $classNames = $driver->getAllClassNames(); + + $this->assertContains( + 'Doctrine\Tests\Common\Persistence\Mapping\TestEntity', $classNames); + } +} + +class TestEntity +{ + static public function loadMetadata($metadata) + { + $metadata->getFieldNames(); + } +} \ No newline at end of file diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/StaticReflectionServiceTest.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/StaticReflectionServiceTest.php new file mode 100644 index 0000000..1dd7a7a --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/StaticReflectionServiceTest.php @@ -0,0 +1,70 @@ +. + */ + +namespace Doctrine\Tests\Common\Persistence\Mapping; + +use Doctrine\Common\Persistence\Mapping\StaticReflectionService; + +/** + * @group DCOM-93 + */ +class StaticReflectionServiceTest extends \PHPUnit_Framework_TestCase +{ + private $reflectionService; + + public function setUp() + { + $this->reflectionService = new StaticReflectionService(); + } + + public function testShortname() + { + $this->assertEquals("StaticReflectionServiceTest", $this->reflectionService->getClassShortName(__CLASS__)); + } + + public function testClassNamespaceName() + { + $this->assertEquals("Doctrine\Tests\Common\Persistence\Mapping", $this->reflectionService->getClassNamespace(__CLASS__)); + } + + public function testGetParentClasses() + { + $classes = $this->reflectionService->getParentClasses(__CLASS__); + $this->assertTrue(count($classes) == 0, "The test class ".__CLASS__." should have no parents according to static reflection."); + } + + public function testGetReflectionClass() + { + $class = $this->reflectionService->getClass(__CLASS__); + $this->assertNull($class); + } + + public function testGetMethods() + { + $this->assertTrue($this->reflectionService->hasPublicMethod(__CLASS__, "testGetMethods")); + $this->assertFalse($this->reflectionService->hasPublicMethod(__CLASS__, "testGetMethods2")); + } + + public function testGetAccessibleProperty() + { + $reflProp = $this->reflectionService->getAccessibleProperty(__CLASS__, "reflectionService"); + $this->assertNull($reflProp); + } +} + diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/SymfonyFileLocatorTest.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/SymfonyFileLocatorTest.php new file mode 100644 index 0000000..b51162e --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/SymfonyFileLocatorTest.php @@ -0,0 +1,88 @@ + $prefix)); + $this->assertEquals(array($path), $locator->getPaths()); + + $locator = new SymfonyFileLocator(array($path => $prefix)); + $this->assertEquals(array($path), $locator->getPaths()); + } + + public function testGetPrefixes() + { + $path = __DIR__ . "/_files"; + $prefix = "Foo"; + + $locator = new SymfonyFileLocator(array($path => $prefix)); + $this->assertEquals(array($path => $prefix), $locator->getNamespacePrefixes()); + } + + public function testGetFileExtension() + { + $locator = new SymfonyFileLocator(array(), ".yml"); + $this->assertEquals(".yml", $locator->getFileExtension()); + $locator->setFileExtension(".xml"); + $this->assertEquals(".xml", $locator->getFileExtension()); + } + + public function testFileExists() + { + $path = __DIR__ . "/_files"; + $prefix = "Foo"; + + $locator = new SymfonyFileLocator(array($path => $prefix), ".yml"); + + $this->assertTrue($locator->fileExists("Foo\stdClass")); + $this->assertTrue($locator->fileExists("Foo\global")); + $this->assertFalse($locator->fileExists("Foo\stdClass2")); + $this->assertFalse($locator->fileExists("Foo\global2")); + } + + public function testGetAllClassNames() + { + $path = __DIR__ . "/_files"; + $prefix = "Foo"; + + $locator = new SymfonyFileLocator(array($path => $prefix), ".yml"); + $classes = $locator->getAllClassNames(null); + sort($classes); + + $this->assertEquals(array("Foo\\global", "Foo\\stdClass"), $classes); + $this->assertEquals(array("Foo\\stdClass"), $locator->getAllClassNames("global")); + } + + public function testFindMappingFile() + { + $path = __DIR__ . "/_files"; + $prefix = "Foo"; + + $locator = new SymfonyFileLocator(array($path => $prefix), ".yml"); + + $this->assertEquals(__DIR__ . "/_files/stdClass.yml", $locator->findMappingFile("Foo\\stdClass")); + } + + public function testFindMappingFileNotFound() + { + $path = __DIR__ . "/_files"; + $prefix = "Foo"; + + $locator = new SymfonyFileLocator(array($path => $prefix), ".yml"); + + $this->setExpectedException( + "Doctrine\Common\Persistence\Mapping\MappingException", + "No mapping file found named '".__DIR__."/_files/stdClass2.yml' for class 'Foo\stdClass2'." + ); + $locator->findMappingFile("Foo\\stdClass2"); + } +} diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/_files/TestEntity.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/_files/TestEntity.php new file mode 100644 index 0000000..d0e9976 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/_files/TestEntity.php @@ -0,0 +1,3 @@ +getFieldNames(); \ No newline at end of file diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/_files/global.yml b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/_files/global.yml new file mode 100644 index 0000000..30d74d2 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/_files/global.yml @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/_files/stdClass.yml b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/_files/stdClass.yml new file mode 100644 index 0000000..30d74d2 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/Mapping/_files/stdClass.yml @@ -0,0 +1 @@ +test \ No newline at end of file diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/ObjectManagerDecoratorTest.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/ObjectManagerDecoratorTest.php new file mode 100644 index 0000000..768b4d3 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/ObjectManagerDecoratorTest.php @@ -0,0 +1,60 @@ +wrapped = $wrapped; + } +} + +class ObjectManagerDecoratorTest extends \PHPUnit_Framework_TestCase +{ + private $wrapped; + private $decorated; + + public function setUp() + { + $this->wrapped = $this->getMock('Doctrine\Common\Persistence\ObjectManager'); + $this->decorated = new NullObjectManagerDecorator($this->wrapped); + } + + public function getMethodParameters() + { + $class = new \ReflectionClass('Doctrine\Common\Persistence\ObjectManager'); + + $methods = array(); + foreach ($class->getMethods() as $method) { + if ($method->getNumberOfRequiredParameters() === 0) { + $methods[] = array($method->getName(), array()); + } elseif ($method->getNumberOfRequiredParameters() > 0) { + $methods[] = array($method->getName(), array_fill(0, $method->getNumberOfRequiredParameters(), 'req') ?: array()); + } + if ($method->getNumberOfParameters() != $method->getNumberOfRequiredParameters()) { + $methods[] = array($method->getName(), array_fill(0, $method->getNumberOfParameters(), 'all') ?: array()); + } + } + + return $methods; + } + + /** + * @dataProvider getMethodParameters + */ + public function testAllMethodCallsAreDelegatedToTheWrappedInstance($method, array $parameters) + { + $stub = $this->wrapped + ->expects($this->once()) + ->method($method) + ->will($this->returnValue('INNER VALUE FROM ' . $method)); + + call_user_func_array(array($stub, 'with'), $parameters); + + $this->assertSame('INNER VALUE FROM ' . $method, call_user_func_array(array($this->decorated, $method), $parameters)); + } +} \ No newline at end of file diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/PersistentObjectTest.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/PersistentObjectTest.php new file mode 100644 index 0000000..180d0f0 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Persistence/PersistentObjectTest.php @@ -0,0 +1,247 @@ +cm = new TestObjectMetadata; + $this->om = $this->getMock('Doctrine\Common\Persistence\ObjectManager'); + $this->om->expects($this->any())->method('getClassMetadata') + ->will($this->returnValue($this->cm)); + $this->object = new TestObject; + PersistentObject::setObjectManager($this->om); + $this->object->injectObjectManager($this->om, $this->cm); + } + + public function testGetObjectManager() + { + $this->assertSame($this->om, PersistentObject::getObjectManager()); + } + + public function testNonMatchingObjectManager() + { + $this->setExpectedException('RuntimeException'); + $om = $this->getMock('Doctrine\Common\Persistence\ObjectManager'); + $this->object->injectObjectManager($om, $this->cm); + } + + public function testGetField() + { + $this->assertEquals('beberlei', $this->object->getName()); + } + + public function testSetField() + { + $this->object->setName("test"); + $this->assertEquals("test", $this->object->getName()); + } + + public function testGetIdentifier() + { + $this->assertEquals(1, $this->object->getId()); + } + + public function testSetIdentifier() + { + $this->setExpectedException('BadMethodCallException'); + $this->object->setId(2); + } + + public function testSetUnknownField() + { + $this->setExpectedException('BadMethodCallException'); + $this->object->setUnknown("test"); + } + + public function testGetUnknownField() + { + $this->setExpectedException('BadMethodCallException'); + $this->object->getUnknown(); + } + + public function testGetToOneAssociation() + { + $this->assertNull($this->object->getParent()); + } + + public function testSetToOneAssociation() + { + $parent = new TestObject(); + $this->object->setParent($parent); + $this->assertSame($parent, $this->object->getParent($parent)); + } + + public function testSetInvalidToOneAssociation() + { + $parent = new \stdClass(); + + $this->setExpectedException('InvalidArgumentException'); + $this->object->setParent($parent); + } + + public function testSetToOneAssociationNull() + { + $parent = new TestObject(); + $this->object->setParent($parent); + $this->object->setParent(null); + $this->assertNull($this->object->getParent()); + } + + public function testAddToManyAssociation() + { + $child = new TestObject(); + $this->object->addChildren($child); + + $this->assertSame($this->object, $child->getParent()); + $this->assertEquals(1, count($this->object->getChildren())); + + $child = new TestObject(); + $this->object->addChildren($child); + + $this->assertEquals(2, count($this->object->getChildren())); + } + + public function testAddInvalidToManyAssociation() + { + $this->setExpectedException('InvalidArgumentException'); + $this->object->addChildren(new \stdClass()); + } + + public function testNoObjectManagerSet() + { + PersistentObject::setObjectManager(null); + $child = new TestObject(); + + $this->setExpectedException('RuntimeException'); + $child->setName("test"); + } + + public function testInvalidMethod() + { + $this->setExpectedException('BadMethodCallException'); + $this->object->asdf(); + } + + public function testAddInvalidCollection() + { + $this->setExpectedException('BadMethodCallException'); + $this->object->addAsdf(new \stdClass()); + } +} + +class TestObject extends PersistentObject +{ + protected $id = 1; + protected $name = 'beberlei'; + protected $parent; + protected $children; +} + +class TestObjectMetadata implements ClassMetadata +{ + + public function getAssociationMappedByTargetField($assocName) + { + $assoc = array('children' => 'parent'); + return $assoc[$assocName]; + } + + public function getAssociationNames() + { + return array('parent', 'children'); + } + + public function getAssociationTargetClass($assocName) + { + return __NAMESPACE__ . '\TestObject'; + } + + public function getFieldNames() + { + return array('id', 'name'); + } + + public function getIdentifier() + { + return array('id'); + } + + public function getName() + { + return __NAMESPACE__ . '\TestObject'; + } + + public function getReflectionClass() + { + return new \ReflectionClass($this->getName()); + } + + public function getTypeOfField($fieldName) + { + $types = array('id' => 'integer', 'name' => 'string'); + return $types[$fieldName]; + } + + public function hasAssociation($fieldName) + { + return in_array($fieldName, array('parent', 'children')); + } + + public function hasField($fieldName) + { + return in_array($fieldName, array('id', 'name')); + } + + public function isAssociationInverseSide($assocName) + { + return ($assocName === 'children'); + } + + public function isCollectionValuedAssociation($fieldName) + { + return ($fieldName === 'children'); + } + + public function isIdentifier($fieldName) + { + return $fieldName === 'id'; + } + + public function isSingleValuedAssociation($fieldName) + { + return $fieldName === 'parent'; + } + + public function getIdentifierValues($entity) + { + + } + + public function getIdentifierFieldNames() + { + + } + + public function initializeReflection(ReflectionService $reflService) + { + + } + + public function wakeupReflection(ReflectionService $reflService) + { + + } +} diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/AbstractProxyFactoryTest.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/AbstractProxyFactoryTest.php new file mode 100644 index 0000000..8aa966d --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/AbstractProxyFactoryTest.php @@ -0,0 +1,118 @@ +getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata'); + $proxyGenerator = $this->getMock('Doctrine\Common\Proxy\ProxyGenerator', array(), array(), '', false); + + $proxyGenerator + ->expects($this->once()) + ->method('getProxyFileName'); + $proxyGenerator + ->expects($this->once()) + ->method('generateProxyClass'); + + $metadataFactory = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadataFactory'); + $proxyFactory = $this->getMockForAbstractClass( + 'Doctrine\Common\Proxy\AbstractProxyFactory', + array($proxyGenerator, $metadataFactory, true) + ); + + $proxyFactory + ->expects($this->any()) + ->method('skipClass') + ->will($this->returnValue(false)); + + $generated = $proxyFactory->generateProxyClasses(array($metadata), sys_get_temp_dir()); + + $this->assertEquals(1, $generated, 'One proxy was generated'); + } + + public function testGetProxy() + { + $metadata = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata'); + $proxy = $this->getMock('Doctrine\Common\Proxy\Proxy'); + $definition = new ProxyDefinition(get_class($proxy), array(), array(), null, null); + $proxyGenerator = $this->getMock('Doctrine\Common\Proxy\ProxyGenerator', array(), array(), '', false); + $metadataFactory = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadataFactory'); + + $metadataFactory + ->expects($this->once()) + ->method('getMetadataFor') + ->will($this->returnValue($metadata)); + + $proxyFactory = $this->getMockForAbstractClass( + 'Doctrine\Common\Proxy\AbstractProxyFactory', + array($proxyGenerator, $metadataFactory, true) + ); + + $proxyFactory + ->expects($this->any()) + ->method('createProxyDefinition') + ->will($this->returnValue($definition)); + + $generatedProxy = $proxyFactory->getProxy('Class', array('id' => 1)); + + $this->assertInstanceOf(get_class($proxy), $generatedProxy); + } + + public function testResetUnitializedProxy() + { + $metadata = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata'); + $proxy = $this->getMock('Doctrine\Common\Proxy\Proxy'); + $definition = new ProxyDefinition(get_class($proxy), array(), array(), null, null); + $proxyGenerator = $this->getMock('Doctrine\Common\Proxy\ProxyGenerator', array(), array(), '', false); + $metadataFactory = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadataFactory'); + + $metadataFactory + ->expects($this->once()) + ->method('getMetadataFor') + ->will($this->returnValue($metadata)); + + $proxyFactory = $this->getMockForAbstractClass( + 'Doctrine\Common\Proxy\AbstractProxyFactory', + array($proxyGenerator, $metadataFactory, true) + ); + + $proxyFactory + ->expects($this->any()) + ->method('createProxyDefinition') + ->will($this->returnValue($definition)); + + $proxy + ->expects($this->once()) + ->method('__isInitialized') + ->will($this->returnValue(false)); + $proxy + ->expects($this->once()) + ->method('__setInitializer'); + $proxy + ->expects($this->once()) + ->method('__setCloner'); + + $proxyFactory->resetUninitializedProxy($proxy); + } + + public function testDisallowsResettingInitializedProxy() + { + $proxyFactory = $this->getMockForAbstractClass('Doctrine\Common\Proxy\AbstractProxyFactory', array(), '', false); + $proxy = $this->getMock('Doctrine\Common\Proxy\Proxy'); + + $proxy + ->expects($this->any()) + ->method('__isInitialized') + ->will($this->returnValue(true)); + + $this->setExpectedException('Doctrine\Common\Proxy\Exception\InvalidArgumentException'); + + $proxyFactory->resetUninitializedProxy($proxy); + } +} + diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/AutoloaderTest.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/AutoloaderTest.php new file mode 100644 index 0000000..be713fc --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/AutoloaderTest.php @@ -0,0 +1,72 @@ +. + */ + +namespace Doctrine\Tests\Common\Proxy; + +use PHPUnit_Framework_TestCase; +use Doctrine\Common\Proxy\Autoloader; + +/** + * @group DDC-1698 + */ +class AutoloaderTest extends PHPUnit_Framework_TestCase +{ + public static function dataResolveFile() + { + return array( + array('/tmp', 'MyProxy', 'MyProxy\__CG__\RealClass', '/tmp' . DIRECTORY_SEPARATOR . '__CG__RealClass.php'), + array('/tmp', 'MyProxy\Subdir', 'MyProxy\Subdir\__CG__\RealClass', '/tmp' . DIRECTORY_SEPARATOR . '__CG__RealClass.php'), + array('/tmp', 'MyProxy', 'MyProxy\__CG__\Other\RealClass', '/tmp' . DIRECTORY_SEPARATOR . '__CG__OtherRealClass.php'), + ); + } + + /** + * @dataProvider dataResolveFile + */ + public function testResolveFile($proxyDir, $proxyNamespace, $className, $expectedProxyFile) + { + $actualProxyFile = Autoloader::resolveFile($proxyDir, $proxyNamespace, $className); + $this->assertEquals($expectedProxyFile, $actualProxyFile); + } + + public function testAutoload() + { + if (file_exists(sys_get_temp_dir() ."/AutoloaderTestClass.php")) { + unlink(sys_get_temp_dir() ."/AutoloaderTestClass.php"); + } + + $autoloader = Autoloader::register(sys_get_temp_dir(), 'ProxyAutoloaderTest', function($proxyDir, $proxyNamespace, $className) { + file_put_contents(sys_get_temp_dir() . "/AutoloaderTestClass.php", "assertTrue(class_exists('ProxyAutoloaderTest\AutoloaderTestClass', true)); + unlink(sys_get_temp_dir() ."/AutoloaderTestClass.php"); + } + + public function testRegisterWithInvalidCallback() + { + $this->setExpectedException( + 'Doctrine\Common\Proxy\Exception\InvalidArgumentException', + 'Invalid \$notFoundCallback given: must be a callable, "stdClass" given' + ); + + Autoloader::register('', '', new \stdClass()); + } +} + diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/CallableTypeHintClass.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/CallableTypeHintClass.php new file mode 100644 index 0000000..f5fccb0 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/CallableTypeHintClass.php @@ -0,0 +1,16 @@ +. + */ + +namespace Doctrine\Tests\Common\Proxy; + +/** + * Test asset representing a lazy loadable object + * + * @author Marco Pivetta + * @since 2.4 + */ +class LazyLoadableObject +{ + /** + * @var string + */ + public $publicIdentifierField; + + /** + * @var string + */ + protected $protectedIdentifierField; + + /** + * @var string + */ + public $publicTransientField = 'publicTransientFieldValue'; + + /** + * @var string + */ + protected $protectedTransientField = 'protectedTransientFieldValue'; + + /** + * @var string + */ + public $publicPersistentField = 'publicPersistentFieldValue'; + + /** + * @var string + */ + protected $protectedPersistentField = 'protectedPersistentFieldValue'; + + /** + * @var string + */ + public $publicAssociation = 'publicAssociationValue'; + + /** + * @var string + */ + protected $protectedAssociation = 'protectedAssociationValue'; + + /** + * @return string + */ + public function getProtectedIdentifierField() + { + return $this->protectedIdentifierField; + } + + /** + * @return string + */ + public function testInitializationTriggeringMethod() + { + return 'testInitializationTriggeringMethod'; + } + + /** + * @return string + */ + public function getProtectedAssociation() + { + return $this->protectedAssociation; + } + + /** + * @param \stdClass $param + */ + public function publicTypeHintedMethod(\stdClass $param) + { + } + + /** + * + */ + public function &byRefMethod() + { + } + + /** + * @param mixed $thisIsNotByRef + * @param &mixed $thisIsByRef + */ + public function byRefParamMethod($thisIsNotByRef, &$thisIsByRef) + { + } +} diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/LazyLoadableObjectClassMetadata.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/LazyLoadableObjectClassMetadata.php new file mode 100644 index 0000000..167386e --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/LazyLoadableObjectClassMetadata.php @@ -0,0 +1,195 @@ +. + */ + +namespace Doctrine\Tests\Common\Proxy; + +use ReflectionClass; +use Doctrine\Common\Persistence\Mapping\ClassMetadata; + +/** + * Class metadata test asset for @see LazyLoadableObject + * + * @author Marco Pivetta + * @since 2.4 + */ +class LazyLoadableObjectClassMetadata implements ClassMetadata +{ + /** + * @var ReflectionClass + */ + protected $reflectionClass; + + /** + * @var array + */ + protected $identifier = array( + 'publicIdentifierField' => true, + 'protectedIdentifierField' => true, + ); + + /** + * @var array + */ + protected $fields = array( + 'publicIdentifierField' => true, + 'protectedIdentifierField' => true, + 'publicPersistentField' => true, + 'protectedPersistentField' => true, + ); + + /** + * @var array + */ + protected $associations = array( + 'publicAssociation' => true, + 'protectedAssociation' => true, + ); + + /** + * {@inheritDoc} + */ + public function getName() + { + return $this->getReflectionClass()->getName(); + } + + /** + * {@inheritDoc} + */ + public function getIdentifier() + { + return array_keys($this->identifier); + } + + /** + * {@inheritDoc} + */ + public function getReflectionClass() + { + if (null === $this->reflectionClass) { + $this->reflectionClass = new \ReflectionClass(__NAMESPACE__ . '\LazyLoadableObject'); + } + + return $this->reflectionClass; + } + + /** + * {@inheritDoc} + */ + public function isIdentifier($fieldName) + { + return isset($this->identifier[$fieldName]); + } + + /** + * {@inheritDoc} + */ + public function hasField($fieldName) + { + return isset($this->fields[$fieldName]); + } + + /** + * {@inheritDoc} + */ + public function hasAssociation($fieldName) + { + return isset($this->associations[$fieldName]); + } + + /** + * {@inheritDoc} + */ + public function isSingleValuedAssociation($fieldName) + { + throw new \BadMethodCallException('not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isCollectionValuedAssociation($fieldName) + { + throw new \BadMethodCallException('not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getFieldNames() + { + return array_keys($this->fields); + } + + /** + * {@inheritDoc} + */ + public function getIdentifierFieldNames() + { + return $this->getIdentifier(); + } + + /** + * {@inheritDoc} + */ + public function getAssociationNames() + { + return array_keys($this->associations); + } + + /** + * {@inheritDoc} + */ + public function getTypeOfField($fieldName) + { + return 'string'; + } + + /** + * {@inheritDoc} + */ + public function getAssociationTargetClass($assocName) + { + throw new \BadMethodCallException('not implemented'); + } + + /** + * {@inheritDoc} + */ + public function isAssociationInverseSide($assocName) + { + throw new \BadMethodCallException('not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getAssociationMappedByTargetField($assocName) + { + throw new \BadMethodCallException('not implemented'); + } + + /** + * {@inheritDoc} + */ + public function getIdentifierValues($object) + { + throw new \BadMethodCallException('not implemented'); + } +} diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/MagicCloneClass.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/MagicCloneClass.php new file mode 100644 index 0000000..33b983d --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/MagicCloneClass.php @@ -0,0 +1,37 @@ +clonedValue = 'newClonedValue'; + } +} diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/MagicGetByRefClass.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/MagicGetByRefClass.php new file mode 100644 index 0000000..6adf1f5 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/MagicGetByRefClass.php @@ -0,0 +1,51 @@ +. + */ + +namespace Doctrine\Tests\Common\Proxy; + +use InvalidArgumentException; + +/** + * Test asset class + * + * @since 2.4 + */ +class MagicGetByRefClass +{ + /** + * @var mixed + */ + public $valueField; + + /** + * @param string $name + * + * @return mixed + * + * @throws \InvalidArgumentException + */ + public function & __get($name) + { + if ($name === 'value') { + return $this->valueField; + } + + throw new InvalidArgumentException(); + } +} diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/MagicGetClass.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/MagicGetClass.php new file mode 100644 index 0000000..0cab36a --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/MagicGetClass.php @@ -0,0 +1,38 @@ +testAttribute = $value; + } + + if ($name === 'publicField' || $name === 'id') { + throw new \BadMethodCallException('Should never be called for "publicField" or "id"'); + } + + $this->testAttribute = $value; + } +} diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/MagicSleepClass.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/MagicSleepClass.php new file mode 100644 index 0000000..3934a05 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/MagicSleepClass.php @@ -0,0 +1,37 @@ +wakeupValue = 'newWakeupValue'; + } +} diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/ProxyClassGeneratorTest.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/ProxyClassGeneratorTest.php new file mode 100644 index 0000000..4d5a613 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/ProxyClassGeneratorTest.php @@ -0,0 +1,209 @@ +. + */ + + +namespace Doctrine\Tests\Common\Proxy; + +use Doctrine\Common\Proxy\ProxyGenerator; +use ReflectionClass; +use ReflectionMethod; +use PHPUnit_Framework_TestCase; + +/** + * Test the proxy generator. Its work is generating on-the-fly subclasses of a given model, which implement the Proxy + * pattern. + * + * @author Giorgio Sironi + * @author Marco Pivetta + */ +class ProxyClassGeneratorTest extends PHPUnit_Framework_TestCase +{ + /** + * @var string + */ + protected $proxyClass = 'Doctrine\Tests\Common\ProxyProxy\__CG__\Doctrine\Tests\Common\Proxy\LazyLoadableObject'; + + /** + * @var LazyLoadableObjectClassMetadata + */ + protected $metadata; + + /** + * @var ProxyGenerator + */ + protected $proxyGenerator; + + /** + * {@inheritDoc} + */ + protected function setUp() + { + $this->metadata = new LazyLoadableObjectClassMetadata(); + $this->proxyGenerator = new ProxyGenerator(__DIR__ . '/generated', __NAMESPACE__ . 'Proxy', true); + + if (class_exists($this->proxyClass, false)) { + return; + } + + $this->generateAndRequire($this->proxyGenerator, $this->metadata); + } + + public function testReferenceProxyRespectsMethodsParametersTypeHinting() + { + $method = new ReflectionMethod($this->proxyClass, 'publicTypeHintedMethod'); + $params = $method->getParameters(); + + $this->assertEquals(1, count($params)); + $this->assertEquals('stdClass', $params[0]->getClass()->getName()); + } + + public function testProxyRespectsMethodsWhichReturnValuesByReference() + { + $method = new ReflectionMethod($this->proxyClass, 'byRefMethod'); + + $this->assertTrue($method->returnsReference()); + } + + public function testProxyRespectsByRefMethodParameters() + { + $method = new ReflectionMethod($this->proxyClass, 'byRefParamMethod'); + $parameters = $method->getParameters(); + $this->assertSame('thisIsNotByRef', $parameters[0]->getName()); + $this->assertFalse($parameters[0]->isPassedByReference()); + $this->assertSame('thisIsByRef', $parameters[1]->getName()); + $this->assertTrue($parameters[1]->isPassedByReference()); + } + + public function testCreatesAssociationProxyAsSubclassOfTheOriginalOne() + { + $this->assertTrue(is_subclass_of($this->proxyClass, $this->metadata->getName())); + } + + public function testNonNamespacedProxyGeneration() + { + $classCode = file_get_contents($this->proxyGenerator->getProxyFileName($this->metadata->getName())); + + $this->assertNotContains("class LazyLoadableObject extends \\\\" . $this->metadata->getName(), $classCode); + $this->assertContains("class LazyLoadableObject extends \\" . $this->metadata->getName(), $classCode); + } + + public function testClassWithSleepProxyGeneration() + { + if (!class_exists('Doctrine\Tests\Common\ProxyProxy\__CG__\SleepClass', false)) { + $className = 'Doctrine\Tests\Common\Proxy\SleepClass'; + $metadata = $this->createClassMetadata($className, array('id')); + $proxyGenerator = new ProxyGenerator(__DIR__ . '/generated', __NAMESPACE__ . 'Proxy', true); + + $this->generateAndRequire($proxyGenerator, $metadata); + } + + $classCode = file_get_contents(__DIR__ . '/generated/__CG__DoctrineTestsCommonProxySleepClass.php'); + $this->assertEquals(1, substr_count($classCode, 'function __sleep')); + $this->assertEquals(1, substr_count($classCode, 'parent::__sleep()')); + } + + private function generateAndRequire($proxyGenerator, $metadata) + { + $proxyGenerator->generateProxyClass($metadata, $proxyGenerator->getProxyFileName($metadata->getName())); + + require_once $proxyGenerator->getProxyFileName($metadata->getName()); + } + + public function testClassWithCallableTypeHintOnProxiedMethod() + { + if (PHP_VERSION_ID < 50400) { + $this->markTestSkipped('`callable` is only supported in PHP >=5.4.0'); + } + + if (!class_exists('Doctrine\Tests\Common\ProxyProxy\__CG__\CallableTypeHintClass', false)) { + $className = 'Doctrine\Tests\Common\Proxy\CallableTypeHintClass'; + $metadata = $this->createClassMetadata($className, array('id')); + + $proxyGenerator = new ProxyGenerator(__DIR__ . '/generated', __NAMESPACE__ . 'Proxy', true); + $this->generateAndRequire($proxyGenerator, $metadata); + } + + $classCode = file_get_contents(__DIR__ . '/generated/__CG__DoctrineTestsCommonProxyCallableTypeHintClass.php'); + + $this->assertEquals(1, substr_count($classCode, 'call(callable $foo)')); + } + + public function testClassWithInvalidTypeHintOnProxiedMethod() + { + $className = 'Doctrine\Tests\Common\Proxy\InvalidTypeHintClass'; + $metadata = $this->createClassMetadata($className, array('id')); + $proxyGenerator = new ProxyGenerator(__DIR__ . '/generated', __NAMESPACE__ . 'Proxy', true); + + $this->setExpectedException( + 'Doctrine\Common\Proxy\Exception\UnexpectedValueException', + 'The type hint of parameter "foo" in method "invalidTypeHintMethod"' + .' in class "' . $className . '" is invalid.' + ); + $proxyGenerator->generateProxyClass($metadata); + } + + public function testNoConfigDirThrowsException() + { + $this->setExpectedException('Doctrine\Common\Proxy\Exception\InvalidArgumentException'); + new ProxyGenerator(null, null); + } + + public function testNoNamespaceThrowsException() + { + $this->setExpectedException('Doctrine\Common\Proxy\Exception\InvalidArgumentException'); + new ProxyGenerator(__DIR__ . '/generated', null); + } + + public function testInvalidPlaceholderThrowsException() + { + $this->setExpectedException('Doctrine\Common\Proxy\Exception\InvalidArgumentException'); + $generator = new ProxyGenerator(__DIR__ . '/generated', 'SomeNamespace'); + $generator->setPlaceholder('', array()); + } + + public function testUseEvalIfNoFilenameIsGiven() + { + $proxyGenerator = new ProxyGenerator(__DIR__ . '/generated', __NAMESPACE__ . 'Proxy', true); + + $className = __NAMESPACE__ . '\\EvalBase'; + + $metadata = $this->createClassMetadata($className, array('id')); + + $proxyGenerator->generateProxyClass($metadata); + + $reflClass = new ReflectionClass('Doctrine\Tests\Common\ProxyProxy\__CG__\Doctrine\Tests\Common\Proxy\EvalBase'); + + $this->assertContains("eval()'d code", $reflClass->getFileName()); + } + + private function createClassMetadata($className, array $ids) + { + $metadata = $this->getMock('Doctrine\Common\Persistence\Mapping\ClassMetadata'); + $reflClass = new ReflectionClass($className); + $metadata->expects($this->any())->method('getReflectionClass')->will($this->returnValue($reflClass)); + $metadata->expects($this->any())->method('getIdentifierFieldNames')->will($this->returnValue($ids)); + $metadata->expects($this->any())->method('getName')->will($this->returnValue($className)); + + return $metadata; + } +} + +class EvalBase +{ +} diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/ProxyLogicTest.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/ProxyLogicTest.php new file mode 100644 index 0000000..9a5173c --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/ProxyLogicTest.php @@ -0,0 +1,696 @@ +. + */ + +namespace Doctrine\Tests\Common\Proxy; + +use Doctrine\Common\Proxy\ProxyGenerator; +use Doctrine\Common\Proxy\Proxy; +use Doctrine\Common\Proxy\Exception\UnexpectedValueException; +use Doctrine\Common\Persistence\Mapping\ClassMetadata; +use PHPUnit_Framework_TestCase; + +/** + * Test the generated proxies behavior. These tests make assumptions about the structure of LazyLoadableObject + * + * @author Marco Pivetta + */ +class ProxyLogicTest extends PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $proxyLoader; + + /** + * @var ClassMetadata + */ + protected $lazyLoadableObjectMetadata; + + /** + * @var LazyLoadableObject|Proxy + */ + protected $lazyObject; + + protected $identifier = array( + 'publicIdentifierField' => 'publicIdentifierFieldValue', + 'protectedIdentifierField' => 'protectedIdentifierFieldValue', + ); + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|Callable + */ + protected $initializerCallbackMock; + + /** + * {@inheritDoc} + */ + public function setUp() + { + $this->proxyLoader = $loader = $this->getMock('stdClass', array('load'), array(), '', false); + $this->initializerCallbackMock = $this->getMock('stdClass', array('__invoke')); + $identifier = $this->identifier; + $this->lazyLoadableObjectMetadata = $metadata = new LazyLoadableObjectClassMetadata(); + + // emulating what should happen in a proxy factory + $cloner = function (LazyLoadableObject $proxy) use ($loader, $identifier, $metadata) { + /* @var $proxy LazyLoadableObject|Proxy */ + if ($proxy->__isInitialized()) { + return; + } + + $proxy->__setInitialized(true); + $proxy->__setInitializer(null); + $original = $loader->load($identifier); + + if (null === $original) { + throw new UnexpectedValueException(); + } + + foreach ($metadata->getReflectionClass()->getProperties() as $reflProperty) { + $propertyName = $reflProperty->getName(); + + if ($metadata->hasField($propertyName) || $metadata->hasAssociation($propertyName)) { + $reflProperty->setAccessible(true); + $reflProperty->setValue($proxy, $reflProperty->getValue($original)); + } + } + }; + + $proxyClassName = 'Doctrine\Tests\Common\ProxyProxy\__CG__\Doctrine\Tests\Common\Proxy\LazyLoadableObject'; + + // creating the proxy class + if (!class_exists($proxyClassName, false)) { + $proxyGenerator = new ProxyGenerator(__DIR__ . '/generated', __NAMESPACE__ . 'Proxy', true); + $proxyGenerator->generateProxyClass($metadata); + require_once $proxyGenerator->getProxyFileName($metadata->getName()); + } + + $this->lazyObject = new $proxyClassName($this->getClosure($this->initializerCallbackMock), $cloner); + + // setting identifiers in the proxy via reflection + foreach ($metadata->getIdentifierFieldNames() as $idField) { + $prop = $metadata->getReflectionClass()->getProperty($idField); + $prop->setAccessible(true); + $prop->setValue($this->lazyObject, $identifier[$idField]); + } + + $this->assertFalse($this->lazyObject->__isInitialized()); + } + + public function testFetchingPublicIdentifierDoesNotCauseLazyLoading() + { + $this->configureInitializerMock(0); + + $this->assertSame('publicIdentifierFieldValue', $this->lazyObject->publicIdentifierField); + } + + public function testFetchingIdentifiersViaPublicGetterDoesNotCauseLazyLoading() + { + $this->configureInitializerMock(0); + + $this->assertSame('protectedIdentifierFieldValue', $this->lazyObject->getProtectedIdentifierField()); + } + + public function testCallingMethodCausesLazyLoading() + { + $this->configureInitializerMock( + 1, + array($this->lazyObject, 'testInitializationTriggeringMethod', array()), + function (Proxy $proxy) { + $proxy->__setInitializer(null); + } + ); + + $this->lazyObject->testInitializationTriggeringMethod(); + $this->lazyObject->testInitializationTriggeringMethod(); + } + + public function testFetchingPublicFieldsCausesLazyLoading() + { + $test = $this; + $this->configureInitializerMock( + 1, + array($this->lazyObject, '__get', array('publicPersistentField')), + function () use ($test) { + $test->setProxyValue('publicPersistentField', 'loadedValue'); + } + ); + + $this->assertSame('loadedValue', $this->lazyObject->publicPersistentField); + $this->assertSame('loadedValue', $this->lazyObject->publicPersistentField); + } + + public function testFetchingPublicAssociationCausesLazyLoading() + { + $test = $this; + $this->configureInitializerMock( + 1, + array($this->lazyObject, '__get', array('publicAssociation')), + function () use ($test) { + $test->setProxyValue('publicAssociation', 'loadedAssociation'); + } + ); + + $this->assertSame('loadedAssociation', $this->lazyObject->publicAssociation); + $this->assertSame('loadedAssociation', $this->lazyObject->publicAssociation); + } + + public function testFetchingProtectedAssociationViaPublicGetterCausesLazyLoading() + { + $this->configureInitializerMock( + 1, + array($this->lazyObject, 'getProtectedAssociation', array()), + function (Proxy $proxy) { + $proxy->__setInitializer(null); + } + ); + + $this->assertSame('protectedAssociationValue', $this->lazyObject->getProtectedAssociation()); + $this->assertSame('protectedAssociationValue', $this->lazyObject->getProtectedAssociation()); + } + + public function testLazyLoadingTriggeredOnlyAtFirstPublicPropertyRead() + { + $test = $this; + $this->configureInitializerMock( + 1, + array($this->lazyObject, '__get', array('publicPersistentField')), + function () use ($test) { + $test->setProxyValue('publicPersistentField', 'loadedValue'); + $test->setProxyValue('publicAssociation', 'publicAssociationValue'); + } + ); + + $this->assertSame('loadedValue', $this->lazyObject->publicPersistentField); + $this->assertSame('publicAssociationValue', $this->lazyObject->publicAssociation); + } + + public function testNoticeWhenReadingNonExistentPublicProperties() + { + $this->configureInitializerMock(0); + + $class = get_class($this->lazyObject); + $this->setExpectedException( + 'PHPUnit_Framework_Error_Notice', + 'Undefined property: ' . $class . '::$non_existing_property' + ); + + $this->lazyObject->non_existing_property; + } + + public function testFalseWhenCheckingNonExistentProperty() + { + $this->configureInitializerMock(0); + + $this->assertFalse(isset($this->lazyObject->non_existing_property)); + } + + public function testNoErrorWhenSettingNonExistentProperty() + { + $this->configureInitializerMock(0); + + $this->lazyObject->non_existing_property = 'now has a value'; + $this->assertSame('now has a value', $this->lazyObject->non_existing_property); + } + + public function testCloningCallsClonerWithClonedObject() + { + $lazyObject = $this->lazyObject; + $test = $this; + $cb = $this->getMock('stdClass', array('cb')); + $cb + ->expects($this->once()) + ->method('cb') + ->will($this->returnCallback(function (LazyLoadableObject $proxy) use ($lazyObject, $test) { + /* @var $proxy LazyLoadableObject|Proxy */ + $test->assertNotSame($proxy, $lazyObject); + $proxy->__setInitializer(null); + $proxy->publicAssociation = 'clonedAssociation'; + })); + + $this->lazyObject->__setCloner($this->getClosure(array($cb, 'cb'))); + + $cloned = clone $this->lazyObject; + $this->assertSame('clonedAssociation', $cloned->publicAssociation); + $this->assertNotSame($cloned, $lazyObject, 'a clone of the lazy object is retrieved'); + } + + public function testFetchingTransientPropertiesWillNotTriggerLazyLoading() + { + $this->configureInitializerMock(0); + + $this->assertSame( + 'publicTransientFieldValue', + $this->lazyObject->publicTransientField, + 'fetching public transient field won\'t trigger lazy loading' + ); + $property = $this + ->lazyLoadableObjectMetadata + ->getReflectionClass() + ->getProperty('protectedTransientField'); + $property->setAccessible(true); + $this->assertSame( + 'protectedTransientFieldValue', + $property->getValue($this->lazyObject), + 'fetching protected transient field via reflection won\'t trigger lazy loading' + ); + } + + /** + * Provided to guarantee backwards compatibility + */ + public function testLoadProxyMethod() + { + $this->configureInitializerMock(2, array($this->lazyObject, '__load', array())); + + $this->lazyObject->__load(); + $this->lazyObject->__load(); + } + + public function testLoadingWithPersisterWillBeTriggeredOnlyOnce() + { + $this + ->proxyLoader + ->expects($this->once()) + ->method('load') + ->with( + array( + 'publicIdentifierField' => 'publicIdentifierFieldValue', + 'protectedIdentifierField' => 'protectedIdentifierFieldValue', + ), + $this->lazyObject + ) + ->will($this->returnCallback(function ($id, LazyLoadableObject $lazyObject) { + // setting a value to verify that the persister can actually set something in the object + $lazyObject->publicAssociation = $id['publicIdentifierField'] . '-test'; + return true; + })); + $this->lazyObject->__setInitializer($this->getSuggestedInitializerImplementation()); + + $this->lazyObject->__load(); + $this->lazyObject->__load(); + $this->assertSame('publicIdentifierFieldValue-test', $this->lazyObject->publicAssociation); + } + + public function testFailedLoadingWillThrowException() + { + $this->proxyLoader->expects($this->any())->method('load')->will($this->returnValue(null)); + $this->setExpectedException('UnexpectedValueException'); + $this->lazyObject->__setInitializer($this->getSuggestedInitializerImplementation()); + + $this->lazyObject->__load(); + } + + public function testCloningWithPersister() + { + $this->lazyObject->publicTransientField = 'should-not-change'; + $this + ->proxyLoader + ->expects($this->exactly(2)) + ->method('load') + ->with(array( + 'publicIdentifierField' => 'publicIdentifierFieldValue', + 'protectedIdentifierField' => 'protectedIdentifierFieldValue', + )) + ->will($this->returnCallback(function () { + $blueprint = new LazyLoadableObject(); + $blueprint->publicPersistentField = 'checked-persistent-field'; + $blueprint->publicAssociation = 'checked-association-field'; + $blueprint->publicTransientField = 'checked-transient-field'; + + return $blueprint; + })); + + $firstClone = clone $this->lazyObject; + $this->assertSame( + 'checked-persistent-field', + $firstClone->publicPersistentField, + 'Persistent fields are cloned correctly' + ); + $this->assertSame( + 'checked-association-field', + $firstClone->publicAssociation, + 'Associations are cloned correctly' + ); + $this->assertSame( + 'should-not-change', + $firstClone->publicTransientField, + 'Transient fields are not overwritten' + ); + + $secondClone = clone $this->lazyObject; + $this->assertSame( + 'checked-persistent-field', + $secondClone->publicPersistentField, + 'Persistent fields are cloned correctly' + ); + $this->assertSame( + 'checked-association-field', + $secondClone->publicAssociation, + 'Associations are cloned correctly' + ); + $this->assertSame( + 'should-not-change', + $secondClone->publicTransientField, + 'Transient fields are not overwritten' + ); + + // those should not trigger lazy loading + $firstClone->__load(); + $secondClone->__load(); + } + + public function testNotInitializedProxyUnserialization() + { + $this->configureInitializerMock(); + + $serialized = serialize($this->lazyObject); + /* @var $unserialized LazyLoadableObject|Proxy */ + $unserialized = unserialize($serialized); + $reflClass = $this->lazyLoadableObjectMetadata->getReflectionClass(); + + $this->assertFalse($unserialized->__isInitialized(), 'serialization didn\'t cause intialization'); + + // Checking identifiers + $this->assertSame('publicIdentifierFieldValue', $unserialized->publicIdentifierField, 'identifiers are kept'); + $protectedIdentifierField = $reflClass->getProperty('protectedIdentifierField'); + $protectedIdentifierField->setAccessible(true); + $this->assertSame( + 'protectedIdentifierFieldValue', + $protectedIdentifierField->getValue($unserialized), + 'identifiers are kept' + ); + + // Checking transient fields + $this->assertSame( + 'publicTransientFieldValue', + $unserialized->publicTransientField, + 'transient fields are kept' + ); + $protectedTransientField = $reflClass->getProperty('protectedTransientField'); + $protectedTransientField->setAccessible(true); + $this->assertSame( + 'protectedTransientFieldValue', + $protectedTransientField->getValue($unserialized), + 'transient fields are kept' + ); + + // Checking persistent fields + $this->assertSame( + 'publicPersistentFieldValue', + $unserialized->publicPersistentField, + 'persistent fields are kept' + ); + $protectedPersistentField = $reflClass->getProperty('protectedPersistentField'); + $protectedPersistentField->setAccessible(true); + $this->assertSame( + 'protectedPersistentFieldValue', + $protectedPersistentField->getValue($unserialized), + 'persistent fields are kept' + ); + + // Checking associations + $this->assertSame('publicAssociationValue', $unserialized->publicAssociation, 'associations are kept'); + $protectedAssociationField = $reflClass->getProperty('protectedAssociation'); + $protectedAssociationField->setAccessible(true); + $this->assertSame( + 'protectedAssociationValue', + $protectedAssociationField->getValue($unserialized), + 'associations are kept' + ); + } + + public function testInitializedProxyUnserialization() + { + // persister will retrieve the lazy object itself, so that we don't have to re-define all field values + $this->proxyLoader->expects($this->once())->method('load')->will($this->returnValue($this->lazyObject)); + $this->lazyObject->__setInitializer($this->getSuggestedInitializerImplementation()); + $this->lazyObject->__load(); + + $serialized = serialize($this->lazyObject); + $reflClass = $this->lazyLoadableObjectMetadata->getReflectionClass(); + /* @var $unserialized LazyLoadableObject|Proxy */ + $unserialized = unserialize($serialized); + + $this->assertTrue($unserialized->__isInitialized(), 'serialization didn\'t cause intialization'); + + // Checking transient fields + $this->assertSame( + 'publicTransientFieldValue', + $unserialized->publicTransientField, + 'transient fields are kept' + ); + $protectedTransientField = $reflClass->getProperty('protectedTransientField'); + $protectedTransientField->setAccessible(true); + $this->assertSame( + 'protectedTransientFieldValue', + $protectedTransientField->getValue($unserialized), + 'transient fields are kept' + ); + + // Checking persistent fields + $this->assertSame( + 'publicPersistentFieldValue', + $unserialized->publicPersistentField, + 'persistent fields are kept' + ); + $protectedPersistentField = $reflClass->getProperty('protectedPersistentField'); + $protectedPersistentField->setAccessible(true); + $this->assertSame( + 'protectedPersistentFieldValue', + $protectedPersistentField->getValue($unserialized), + 'persistent fields are kept' + ); + + // Checking identifiers + $this->assertSame( + 'publicIdentifierFieldValue', + $unserialized->publicIdentifierField, + 'identifiers are kept' + ); + $protectedIdentifierField = $reflClass->getProperty('protectedIdentifierField'); + $protectedIdentifierField->setAccessible(true); + $this->assertSame( + 'protectedIdentifierFieldValue', + $protectedIdentifierField->getValue($unserialized), + 'identifiers are kept' + ); + + // Checking associations + $this->assertSame('publicAssociationValue', $unserialized->publicAssociation, 'associations are kept'); + $protectedAssociationField = $reflClass->getProperty('protectedAssociation'); + $protectedAssociationField->setAccessible(true); + $this->assertSame( + 'protectedAssociationValue', + $protectedAssociationField->getValue($unserialized), + 'associations are kept' + ); + } + + public function testInitializationRestoresDefaultPublicLazyLoadedFieldValues() + { + // setting noop persister + $this->proxyLoader->expects($this->once())->method('load')->will($this->returnValue($this->lazyObject)); + $this->lazyObject->__setInitializer($this->getSuggestedInitializerImplementation()); + + $this->assertSame( + 'publicPersistentFieldValue', + $this->lazyObject->publicPersistentField, + 'Persistent field is restored to default value' + ); + $this->assertSame( + 'publicAssociationValue', + $this->lazyObject->publicAssociation, + 'Association is restored to default value' + ); + } + + public function testSettingPublicFieldsCausesLazyLoading() + { + $test = $this; + $this->configureInitializerMock( + 1, + array($this->lazyObject, '__set', array('publicPersistentField', 'newPublicPersistentFieldValue')), + function () use ($test) { + $test->setProxyValue('publicPersistentField', 'overrideValue'); + $test->setProxyValue('publicAssociation', 'newAssociationValue'); + } + ); + + $this->lazyObject->publicPersistentField = 'newPublicPersistentFieldValue'; + $this->assertSame('newPublicPersistentFieldValue', $this->lazyObject->publicPersistentField); + $this->assertSame('newAssociationValue', $this->lazyObject->publicAssociation); + } + + public function testSettingPublicAssociationCausesLazyLoading() + { + $test = $this; + $this->configureInitializerMock( + 1, + array($this->lazyObject, '__set', array('publicAssociation', 'newPublicAssociationValue')), + function () use ($test) { + $test->setProxyValue('publicPersistentField', 'newPublicPersistentFieldValue'); + $test->setProxyValue('publicAssociation', 'overrideValue'); + } + ); + + $this->lazyObject->publicAssociation = 'newPublicAssociationValue'; + $this->assertSame('newPublicAssociationValue', $this->lazyObject->publicAssociation); + $this->assertSame('newPublicPersistentFieldValue', $this->lazyObject->publicPersistentField); + } + + public function testCheckingPublicFieldsCausesLazyLoading() + { + $test = $this; + $this->configureInitializerMock( + 1, + array($this->lazyObject, '__isset', array('publicPersistentField')), + function () use ($test) { + $test->setProxyValue('publicPersistentField', null); + $test->setProxyValue('publicAssociation', 'setPublicAssociation'); + } + ); + + $this->assertFalse(isset($this->lazyObject->publicPersistentField)); + $this->assertNull($this->lazyObject->publicPersistentField); + $this->assertTrue(isset($this->lazyObject->publicAssociation)); + $this->assertSame('setPublicAssociation', $this->lazyObject->publicAssociation); + } + + public function testCheckingPublicAssociationCausesLazyLoading() + { + $test = $this; + $this->configureInitializerMock( + 1, + array($this->lazyObject, '__isset', array('publicAssociation')), + function () use ($test) { + $test->setProxyValue('publicPersistentField', 'newPersistentFieldValue'); + $test->setProxyValue('publicAssociation', 'setPublicAssociation'); + } + ); + + $this->assertTrue(isset($this->lazyObject->publicAssociation)); + $this->assertSame('setPublicAssociation', $this->lazyObject->publicAssociation); + $this->assertTrue(isset($this->lazyObject->publicPersistentField)); + $this->assertSame('newPersistentFieldValue', $this->lazyObject->publicPersistentField); + } + + /** + * Converts a given callable into a closure + * + * @param callable $callable + * @return \Closure + */ + public function getClosure($callable) { + return function () use ($callable) { + call_user_func_array($callable, func_get_args()); + }; + } + + /** + * Configures the current initializer callback mock with provided matcher params + * + * @param int $expectedCallCount the number of invocations to be expected. If a value< 0 is provided, `any` is used + * @param array $callParamsMatch an ordered array of parameters to be expected + * @param callable $callbackClosure a return callback closure + * + * @return \PHPUnit_Framework_MockObject_MockObject| + */ + protected function configureInitializerMock( + $expectedCallCount = 0, + array $callParamsMatch = null, + \Closure $callbackClosure = null + ) { + if (!$expectedCallCount) { + $invocationCountMatcher = $this->exactly((int) $expectedCallCount); + } else { + $invocationCountMatcher = $expectedCallCount < 0 ? $this->any() : $this->exactly($expectedCallCount); + } + + $invocationMocker = $this->initializerCallbackMock->expects($invocationCountMatcher)->method('__invoke'); + + if (null !== $callParamsMatch) { + call_user_func_array(array($invocationMocker, 'with'), $callParamsMatch); + } + + if ($callbackClosure) { + $invocationMocker->will($this->returnCallback($callbackClosure)); + } + } + + /** + * Sets a value in the current proxy object without triggering lazy loading through `__set` + * + * @link https://bugs.php.net/bug.php?id=63463 + * + * @param string $property + * @param mixed $value + */ + public function setProxyValue($property, $value) + { + $reflectionProperty = new \ReflectionProperty($this->lazyObject, $property); + $initializer = $this->lazyObject->__getInitializer(); + + // disabling initializer since setting `publicPersistentField` triggers `__set`/`__get` + $this->lazyObject->__setInitializer(null); + $reflectionProperty->setValue($this->lazyObject, $value); + $this->lazyObject->__setInitializer($initializer); + } + + /** + * Retrieves the suggested implementation of an initializer that proxy factories in O*M + * are currently following, and that should be used to initialize the current proxy object + * + * @return \Closure + */ + protected function getSuggestedInitializerImplementation() + { + $loader = $this->proxyLoader; + $identifier = $this->identifier; + + return function (LazyLoadableObject $proxy) use ($loader, $identifier) { + /* @var $proxy LazyLoadableObject|Proxy */ + $proxy->__setInitializer(null); + $proxy->__setCloner(null); + + + if ($proxy->__isInitialized()) { + return; + } + + $properties = $proxy->__getLazyProperties(); + + foreach ($properties as $propertyName => $property) { + if (!isset($proxy->$propertyName)) { + $proxy->$propertyName = $properties[$propertyName]; + } + } + + $proxy->__setInitialized(true); + + if (method_exists($proxy, '__wakeup')) { + $proxy->__wakeup(); + } + + if (null === $loader->load($identifier, $proxy)) { + throw new \UnexpectedValueException('Couldn\'t load'); + } + }; + } +} \ No newline at end of file diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/ProxyMagicMethodsTest.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/ProxyMagicMethodsTest.php new file mode 100644 index 0000000..1ed9d92 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/ProxyMagicMethodsTest.php @@ -0,0 +1,327 @@ +. + */ + +namespace Doctrine\Tests\Common\Proxy; + +use Doctrine\Common\Proxy\ProxyGenerator; +use Doctrine\Common\Proxy\Proxy; +use Doctrine\Common\Proxy\Exception\UnexpectedValueException; +use PHPUnit_Framework_TestCase; +use ReflectionClass; + +/** + * Test for behavior of proxies with inherited magic methods + * + * @author Marco Pivetta + */ +class ProxyMagicMethodsTest extends PHPUnit_Framework_TestCase +{ + /** + * @var \Doctrine\Common\Proxy\ProxyGenerator + */ + protected $proxyGenerator; + + /** + * @var LazyLoadableObject|Proxy + */ + protected $lazyObject; + + protected $identifier = array( + 'publicIdentifierField' => 'publicIdentifierFieldValue', + 'protectedIdentifierField' => 'protectedIdentifierFieldValue', + ); + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|Callable + */ + protected $initializerCallbackMock; + + /** + * {@inheritDoc} + */ + public function setUp() + { + $this->proxyGenerator = new ProxyGenerator(__DIR__ . '/generated', __NAMESPACE__ . '\\MagicMethodProxy'); + } + + public static function tearDownAfterClass() + { + + } + + public function testInheritedMagicGet() + { + $proxyClassName = $this->generateProxyClass(__NAMESPACE__ . '\\MagicGetClass'); + $proxy = new $proxyClassName( + function (Proxy $proxy, $method, $params) use (&$counter) { + if ( ! in_array($params[0], array('publicField', 'test', 'notDefined'))) { + throw new \InvalidArgumentException('Unexpected access to field "' . $params[0] . '"'); + } + + $initializer = $proxy->__getInitializer(); + + $proxy->__setInitializer(null); + + $proxy->publicField = 'modifiedPublicField'; + $counter += 1; + + $proxy->__setInitializer($initializer); + + } + ); + + $this->assertSame('id', $proxy->id); + $this->assertSame('modifiedPublicField', $proxy->publicField); + $this->assertSame('test', $proxy->test); + $this->assertSame('not defined', $proxy->notDefined); + + $this->assertSame(3, $counter); + } + + /** + * @group DCOM-194 + */ + public function testInheritedMagicGetByRef() + { + $proxyClassName = $this->generateProxyClass(__NAMESPACE__ . '\\MagicGetByRefClass'); + /* @var $proxy \Doctrine\Tests\Common\Proxy\MagicGetByRefClass */ + $proxy = new $proxyClassName(); + $proxy->valueField = 123; + $value = & $proxy->__get('value'); + + $this->assertSame(123, $value); + + $value = 456; + + $this->assertSame(456, $proxy->__get('value'), 'Value was fetched by reference'); + + $this->setExpectedException('InvalidArgumentException'); + + $undefined = $proxy->nonExisting; + } + + public function testInheritedMagicSet() + { + $proxyClassName = $this->generateProxyClass(__NAMESPACE__ . '\\MagicSetClass'); + $proxy = new $proxyClassName( + function (Proxy $proxy, $method, $params) use (&$counter) { + if ( ! in_array($params[0], array('publicField', 'test', 'notDefined'))) { + throw new \InvalidArgumentException('Unexpected access to field "' . $params[0] . '"'); + } + + $counter += 1; + } + ); + + $this->assertSame('id', $proxy->id); + + $proxy->publicField = 'publicFieldValue'; + + $this->assertSame('publicFieldValue', $proxy->publicField); + + $proxy->test = 'testValue'; + + $this->assertSame('testValue', $proxy->testAttribute); + + $proxy->notDefined = 'not defined'; + + $this->assertSame('not defined', $proxy->testAttribute); + $this->assertSame(3, $counter); + } + + public function testInheritedMagicSleep() + { + $proxyClassName = $this->generateProxyClass(__NAMESPACE__ . '\\MagicSleepClass'); + $proxy = new $proxyClassName(); + + $this->assertSame('defaultValue', $proxy->serializedField); + $this->assertSame('defaultValue', $proxy->nonSerializedField); + + $proxy->serializedField = 'changedValue'; + $proxy->nonSerializedField = 'changedValue'; + + $unserialized = unserialize(serialize($proxy)); + + $this->assertSame('changedValue', $unserialized->serializedField); + $this->assertSame('defaultValue', $unserialized->nonSerializedField, 'Field was not returned by "__sleep"'); + } + + public function testInheritedMagicWakeup() + { + $proxyClassName = $this->generateProxyClass(__NAMESPACE__ . '\\MagicWakeupClass'); + $proxy = new $proxyClassName(); + + $this->assertSame('defaultValue', $proxy->wakeupValue); + + $proxy->wakeupValue = 'changedValue'; + $unserialized = unserialize(serialize($proxy)); + + $this->assertSame('newWakeupValue', $unserialized->wakeupValue, '"__wakeup" was called'); + + $unserialized->__setInitializer(function (Proxy $proxy) { + $proxy->__setInitializer(null); + + $proxy->publicField = 'newPublicFieldValue'; + }); + + $this->assertSame('newPublicFieldValue', $unserialized->publicField, 'Proxy can still be initialized'); + } + + public function testInheritedMagicIsset() + { + $proxyClassName = $this->generateProxyClass(__NAMESPACE__ . '\\MagicIssetClass'); + $proxy = new $proxyClassName(function (Proxy $proxy, $method, $params) use (&$counter) { + if (in_array($params[0], array('publicField', 'test', 'nonExisting'))) { + $initializer = $proxy->__getInitializer(); + + $proxy->__setInitializer(null); + + $proxy->publicField = 'modifiedPublicField'; + $counter += 1; + + $proxy->__setInitializer($initializer); + + return; + } + + throw new \InvalidArgumentException( + sprintf('Should not be initialized when checking isset("%s")', $params[0]) + ); + }); + + $this->assertTrue(isset($proxy->id)); + $this->assertTrue(isset($proxy->publicField)); + $this->assertTrue(isset($proxy->test)); + $this->assertFalse(isset($proxy->nonExisting)); + + $this->assertSame(3, $counter); + } + + public function testInheritedMagicClone() + { + $proxyClassName = $this->generateProxyClass(__NAMESPACE__ . '\\MagicCloneClass'); + $proxy = new $proxyClassName( + null, + function ($proxy) { + $proxy->cloned = true; + } + ); + + $cloned = clone $proxy; + + $this->assertSame('newClonedValue', $cloned->clonedValue); + $this->assertFalse($proxy->cloned); + $this->assertTrue($cloned->cloned); + } + + /** + * @group DCOM-175 + */ + public function testClonesPrivateProperties() + { + $proxyClassName = $this->generateProxyClass(__NAMESPACE__ . '\\SerializedClass'); + /* @var $proxy SerializedClass */ + $proxy = new $proxyClassName(); + + $proxy->setFoo(1); + $proxy->setBar(2); + $proxy->setBaz(3); + + $unserialized = unserialize(serialize($proxy)); + + $this->assertSame(1, $unserialized->getFoo()); + $this->assertSame(2, $unserialized->getBar()); + $this->assertSame(3, $unserialized->getBaz()); + } + + /** + * @param $className + * + * @return string + */ + private function generateProxyClass($className) + { + $proxyClassName = 'Doctrine\\Tests\\Common\\Proxy\\MagicMethodProxy\\__CG__\\' . $className; + + if (class_exists($proxyClassName, false)) { + return $proxyClassName; + } + + $metadata = $this->getMock('Doctrine\\Common\\Persistence\\Mapping\\ClassMetadata'); + + $metadata + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue($className)); + + $metadata + ->expects($this->any()) + ->method('getIdentifier') + ->will($this->returnValue(array('id'))); + + $metadata + ->expects($this->any()) + ->method('getReflectionClass') + ->will($this->returnValue(new ReflectionClass($className))); + + $metadata + ->expects($this->any()) + ->method('isIdentifier') + ->will($this->returnCallback(function ($fieldName) { + return 'id' === $fieldName; + })); + + $metadata + ->expects($this->any()) + ->method('hasField') + ->will($this->returnCallback(function ($fieldName) { + return in_array($fieldName, array('id', 'publicField')); + })); + + $metadata + ->expects($this->any()) + ->method('hasAssociation') + ->will($this->returnValue(false)); + + $metadata + ->expects($this->any()) + ->method('getFieldNames') + ->will($this->returnValue(array('id', 'publicField'))); + + $metadata + ->expects($this->any()) + ->method('getIdentifierFieldNames') + ->will($this->returnValue(array('id'))); + + $metadata + ->expects($this->any()) + ->method('getAssociationNames') + ->will($this->returnValue(array())); + + $metadata + ->expects($this->any()) + ->method('getTypeOfField') + ->will($this->returnValue('string')); + + $this->proxyGenerator->generateProxyClass($metadata, $this->proxyGenerator->getProxyFileName($className)); + require_once $this->proxyGenerator->getProxyFileName($className); + + return $proxyClassName; + } +} diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/SerializedClass.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/SerializedClass.php new file mode 100644 index 0000000..a36c3bb --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/SerializedClass.php @@ -0,0 +1,72 @@ +foo = $foo; + } + + /** + * @return mixed|string + */ + public function getFoo() + { + return $this->foo; + } + + /** + * @param $bar + */ + public function setBar($bar) + { + $this->bar = $bar; + } + + /** + * @return mixed|string + */ + public function getBar() + { + return $this->bar; + } + + /** + * @param $baz + */ + public function setBaz($baz) + { + $this->baz = $baz; + } + + /** + * @return mixed|string + */ + public function getBaz() + { + return $this->baz; + } +} diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/SleepClass.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/SleepClass.php new file mode 100644 index 0000000..3c6ffcd --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Proxy/SleepClass.php @@ -0,0 +1,19 @@ +getMock('stdClass', array('callGet')); + $getCheckMock->expects($this->never())->method('callGet'); + $initializer = function () use ($getCheckMock) { + call_user_func($getCheckMock); + }; + + $mockProxy = new RuntimePublicReflectionPropertyTestProxyMock(); + $mockProxy->__setInitializer($initializer); + + $reflProperty = new RuntimePublicReflectionProperty( + __NAMESPACE__ . '\RuntimePublicReflectionPropertyTestProxyMock', + 'checkedProperty' + ); + + $this->assertSame('testValue', $reflProperty->getValue($mockProxy)); + unset($mockProxy->checkedProperty); + $this->assertNull($reflProperty->getValue($mockProxy)); + } + + public function testSetValueOnProxyPublicProperty() + { + $setCheckMock = $this->getMock('stdClass', array('neverCallSet')); + $setCheckMock->expects($this->never())->method('neverCallSet'); + $initializer = function () use ($setCheckMock) { + call_user_func(array($setCheckMock, 'neverCallSet')); + }; + + $mockProxy = new RuntimePublicReflectionPropertyTestProxyMock(); + $mockProxy->__setInitializer($initializer); + + $reflProperty = new RuntimePublicReflectionProperty( + __NAMESPACE__ . '\RuntimePublicReflectionPropertyTestProxyMock', + 'checkedProperty' + ); + + $reflProperty->setValue($mockProxy, 'newValue'); + $this->assertSame('newValue', $mockProxy->checkedProperty); + + unset($mockProxy->checkedProperty); + $reflProperty->setValue($mockProxy, 'otherNewValue'); + $this->assertSame('otherNewValue', $mockProxy->checkedProperty); + + $setCheckMock = $this->getMock('stdClass', array('callSet')); + $setCheckMock->expects($this->once())->method('callSet'); + $initializer = function () use ($setCheckMock) { + call_user_func(array($setCheckMock, 'callSet')); + }; + + $mockProxy->__setInitializer($initializer); + $mockProxy->__setInitialized(true); + + unset($mockProxy->checkedProperty); + $reflProperty->setValue($mockProxy, 'againNewValue'); + $this->assertSame('againNewValue', $mockProxy->checkedProperty); + } +} + +/** + * Mock that simulates proxy public property lazy loading + */ +class RuntimePublicReflectionPropertyTestProxyMock implements Proxy +{ + /** + * @var \Closure|null + */ + private $initializer = null; + + /** + * @var \Closure|null + */ + private $initialized = false; + + /** + * @var string + */ + public $checkedProperty = 'testValue'; + + /** + * {@inheritDoc} + */ + public function __getInitializer() + { + return $this->initializer; + } + + /** + * {@inheritDoc} + */ + public function __setInitializer(\Closure $initializer = null) + { + $this->initializer = $initializer; + } + + /** + * {@inheritDoc} + */ + public function __getLazyProperties() + { + } + + /** + * {@inheritDoc} + */ + public function __load() + { + } + + /** + * {@inheritDoc} + */ + public function __isInitialized() + { + return $this->initialized; + } + + /** + * {@inheritDoc} + */ + public function __setInitialized($initialized) + { + $this->initialized = (bool) $initialized; + } + + /** + * @param string $name + */ + public function __get($name) + { + if ($this->initializer) { + $cb = $this->initializer; + $cb(); + } + + return $this->checkedProperty; + } + + /** + * @param string $name + * @param mixed $value + */ + public function __set($name, $value) + { + if ($this->initializer) { + $cb = $this->initializer; + $cb(); + } + + // triggers notices if `$name` is used: see https://bugs.php.net/bug.php?id=63463 + $this->checkedProperty = $value; + } + + /** + * @param string $name + * + * @return integer + */ + public function __isset($name) + { + if ($this->initializer) { + $cb = $this->initializer; + $cb(); + } + + return isset($this->checkedProperty); + } + + /** + * {@inheritDoc} + */ + public function __setCloner(\Closure $cloner = null) + { + } + + /** + * {@inheritDoc} + */ + public function __getCloner() + { + } +} \ No newline at end of file diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Reflection/SameNamespaceParent.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Reflection/SameNamespaceParent.php new file mode 100644 index 0000000..844d4a5 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Reflection/SameNamespaceParent.php @@ -0,0 +1,7 @@ + array($testsRoot), + ); + $noParentClassName = 'Doctrine\\Tests\\Common\\Reflection\\NoParent'; + $staticReflectionParser = new StaticReflectionParser($noParentClassName, new Psr0FindFile($paths), $classAnnotationOptimize); + $declaringClassName = $staticReflectionParser->getStaticReflectionParserForDeclaringClass('property', 'test')->getClassName(); + $this->assertEquals($noParentClassName, $declaringClassName); + + $className = 'Doctrine\\Tests\\Common\\Reflection\\FullyClassifiedParent'; + $staticReflectionParser = new StaticReflectionParser($className, new Psr0FindFile($paths), $classAnnotationOptimize); + $declaringClassName = $staticReflectionParser->getStaticReflectionParserForDeclaringClass('property', 'test')->getClassName(); + $this->assertEquals($noParentClassName, $declaringClassName); + + $className = 'Doctrine\\Tests\\Common\\Reflection\\SameNamespaceParent'; + $staticReflectionParser = new StaticReflectionParser($className, new Psr0FindFile($paths), $classAnnotationOptimize); + $declaringClassName = $staticReflectionParser->getStaticReflectionParserForDeclaringClass('property', 'test')->getClassName(); + $this->assertEquals($noParentClassName, $declaringClassName); + + $dummyParentClassName = 'Doctrine\\Tests\\Common\\Reflection\\Dummies\\NoParent'; + + $className = 'Doctrine\\Tests\\Common\\Reflection\\DeeperNamespaceParent'; + $staticReflectionParser = new StaticReflectionParser($className, new Psr0FindFile($paths), $classAnnotationOptimize); + $declaringClassName = $staticReflectionParser->getStaticReflectionParserForDeclaringClass('property', 'test')->getClassName(); + $this->assertEquals($dummyParentClassName, $declaringClassName); + + $className = 'Doctrine\\Tests\\Common\\Reflection\\UseParent'; + $staticReflectionParser = new StaticReflectionParser($className, new Psr0FindFile($paths), $classAnnotationOptimize); + $declaringClassName = $staticReflectionParser->getStaticReflectionParserForDeclaringClass('property', 'test')->getClassName(); + $this->assertEquals($dummyParentClassName, $declaringClassName); + + } + + /** + * @return array + */ + public function classAnnotationOptimize() + { + return array( + array(false), + array(true) + ); + } +} diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Reflection/UseParent.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Reflection/UseParent.php new file mode 100644 index 0000000..dd512d4 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Reflection/UseParent.php @@ -0,0 +1,9 @@ +assertEquals($expectedClassName, ClassUtils::getRealClass($className)); + } + + /** + * @dataProvider dataGetClass + */ + public function testGetClass( $className, $expectedClassName ) + { + $object = new $className(); + $this->assertEquals($expectedClassName, ClassUtils::getClass($object)); + } + + public function testGetParentClass() + { + $parentClass = ClassUtils::getParentClass( 'MyProject\Proxies\__CG__\OtherProject\Proxies\__CG__\Doctrine\Tests\Common\Util\ChildObject' ); + $this->assertEquals('stdClass', $parentClass); + } + + public function testGenerateProxyClassName() + { + $this->assertEquals( 'Proxies\__CG__\stdClass', ClassUtils::generateProxyClassName( 'stdClass', 'Proxies' ) ); + } + + /** + * @dataProvider dataGetClass + */ + public function testNewReflectionClass( $className, $expectedClassName ) + { + $reflClass = ClassUtils::newReflectionClass( $className ); + $this->assertEquals( $expectedClassName, $reflClass->getName() ); + } + + /** + * @dataProvider dataGetClass + */ + public function testNewReflectionObject( $className, $expectedClassName ) + { + $object = new $className; + $reflClass = ClassUtils::newReflectionObject( $object ); + $this->assertEquals( $expectedClassName, $reflClass->getName() ); + } + } + + class ChildObject extends \stdClass + { + } +} + +namespace MyProject\Proxies\__CG__ +{ + class stdClass extends \stdClass + { + } +} + +namespace MyProject\Proxies\__CG__\Doctrine\Tests\Common\Util +{ + class ChildObject extends \Doctrine\Tests\Common\Util\ChildObject + { + } +} + +namespace MyProject\Proxies\__CG__\OtherProject\Proxies\__CG__ +{ + class stdClass extends \MyProject\Proxies\__CG__\stdClass + { + } +} + +namespace MyProject\Proxies\__CG__\OtherProject\Proxies\__CG__\Doctrine\Tests\Common\Util +{ + class ChildObject extends \MyProject\Proxies\__CG__\Doctrine\Tests\Common\Util\ChildObject + { + } +} diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/Common/Util/DebugTest.php b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Util/DebugTest.php new file mode 100644 index 0000000..f6fc069 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/Common/Util/DebugTest.php @@ -0,0 +1,40 @@ +foo = "bar"; + $obj->bar = 1234; + + $var = Debug::export($obj, 2); + $this->assertEquals( "stdClass", $var->__CLASS__ ); + } + + public function testExportDateTime() + { + $obj = new \DateTime( "2010-10-10 10:10:10" ); + + $var = Debug::export( $obj, 2 ); + $this->assertEquals( "DateTime", $var->__CLASS__ ); + } + + public function testExportArrayTraversable() + { + $obj = new \ArrayObject(array('foobar')); + + $var = Debug::export($obj, 2); + $this->assertContains('foobar', $var->__STORAGE__); + + $it = new \ArrayIterator(array('foobar')); + + $var = Debug::export($it, 5); + $this->assertContains('foobar', $var->__STORAGE__); + } +} diff --git a/vendor/doctrine/common/tests/Doctrine/Tests/DoctrineTestCase.php b/vendor/doctrine/common/tests/Doctrine/Tests/DoctrineTestCase.php new file mode 100644 index 0000000..e8323d2 --- /dev/null +++ b/vendor/doctrine/common/tests/Doctrine/Tests/DoctrineTestCase.php @@ -0,0 +1,10 @@ + + */ +class NativePhpunitTask extends Task +{ + private $test; + private $testfile; + private $testdirectory; + private $configuration = null; + private $coverageClover = null; + private $junitlogfile = null; + private $haltonfailure = true; + private $haltonerror = true; + + public function setTestdirectory($directory) { + $this->testdirectory = $directory; + } + + public function setTest($test) { + $this->test = $test; + } + + public function setTestfile($testfile) { + $this->testfile = $testfile; + } + + public function setJunitlogfile($junitlogfile) { + if (strlen($junitlogfile) == 0) { + $junitlogfile = NULL; + } + + $this->junitlogfile = $junitlogfile; + } + + public function setConfiguration($configuration) { + if (strlen($configuration) == 0) { + $configuration = NULL; + } + + $this->configuration = $configuration; + } + + public function setCoverageClover($coverageClover) { + if (strlen($coverageClover) == 0) { + $coverageClover = NULL; + } + + $this->coverageClover = $coverageClover; + } + + public function setHaltonfailure($haltonfailures) { + $this->haltonfailure = $haltonfailures; + } + + public function setHaltonerror($haltonerrors) { + $this->haltonerror = $haltonerrors; + } + + public function init() + { + require_once "PHPUnit/Runner/Version.php"; + $version = PHPUnit_Runner_Version::id(); + + if (version_compare($version, '3.4.0') < 0) { + throw new BuildException("NativePHPUnitTask requires PHPUnit version >= 3.2.0", $this->getLocation()); + } + + require_once 'PHPUnit/Util/Filter.php'; + + // point PHPUnit_MAIN_METHOD define to non-existing method + if (!defined('PHPUnit_MAIN_METHOD')) { + define('PHPUnit_MAIN_METHOD', 'PHPUnitTask::undefined'); + } + } + + public function main() + { + if (!is_dir(realpath($this->testdirectory))) { + throw new BuildException("NativePHPUnitTask requires a Test Directory path given, '".$this->testdirectory."' given."); + } + set_include_path(realpath($this->testdirectory) . PATH_SEPARATOR . get_include_path()); + + $printer = new NativePhpunitPrinter(); + + $arguments = array( + 'configuration' => $this->configuration, + 'coverageClover' => $this->coverageClover, + 'junitLogfile' => $this->junitlogfile, + 'printer' => $printer, + ); + + $runner = new PHPUnit_TextUI_TestRunner(); + $suite = $runner->getTest($this->test, $this->testfile, true); + + try { + $result = $runner->doRun($suite, $arguments); + /* @var $result PHPUnit_Framework_TestResult */ + + if ( ($this->haltonfailure && $result->failureCount() > 0) || ($this->haltonerror && $result->errorCount() > 0) ) { + throw new BuildException("PHPUnit: ".$result->failureCount()." Failures and ".$result->errorCount()." Errors, ". + "last failure message: ".$printer->getMessages()); + } + + $this->log("PHPUnit Success: ".count($result->passed())." tests passed, no ". + "failures (".$result->skippedCount()." skipped, ".$result->notImplementedCount()." not implemented)"); + + // Hudson for example doesn't like the backslash in class names + if (is_file($this->coverageClover)) { + $this->log("Generated Clover Coverage XML to: ".$this->coverageClover); + $content = file_get_contents($this->coverageClover); + $content = str_replace("\\", ".", $content); + file_put_contents($this->coverageClover, $content); + unset($content); + } + + } catch(\Exception $e) { + throw new BuildException("NativePhpunitTask failed: ".$e->getMessage()); + } + } +} + +class NativePhpunitPrinter extends PHPUnit_Util_Printer implements PHPUnit_Framework_TestListener +{ + private $_messages = array(); + + public function write($buffer) + { + // do nothing + } + + public function getMessages() + { + return $this->_messages; + } + + /** + * An error occurred. + * + * @param PHPUnit_Framework_Test $test + * @param Exception $e + * @param float $time + */ + public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) + { + $this->_messages[] = "Test ERROR: ".$test->getName().": ".$e->getMessage(); + } + + /** + * A failure occurred. + * + * @param PHPUnit_Framework_Test $test + * @param PHPUnit_Framework_AssertionFailedError $e + * @param float $time + */ + public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) + { + $this->_messages[] = "Test FAILED: ".$test->getName().": ".$e->getMessage(); + } + + /** + * Incomplete test. + * + * @param PHPUnit_Framework_Test $test + * @param Exception $e + * @param float $time + */ + public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) + { + + } + + /** + * Skipped test. + * + * @param PHPUnit_Framework_Test $test + * @param Exception $e + * @param float $time + * @since Method available since Release 3.0.0 + */ + public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) + { + + } + + /** + * A test suite started. + * + * @param PHPUnit_Framework_TestSuite $suite + * @since Method available since Release 2.2.0 + */ + public function startTestSuite(PHPUnit_Framework_TestSuite $suite) + { + + } + + /** + * A test suite ended. + * + * @param PHPUnit_Framework_TestSuite $suite + * @since Method available since Release 2.2.0 + */ + public function endTestSuite(PHPUnit_Framework_TestSuite $suite) + { + + } + + /** + * A test started. + * + * @param PHPUnit_Framework_Test $test + */ + public function startTest(PHPUnit_Framework_Test $test) + { + + } + + /** + * A test ended. + * + * @param PHPUnit_Framework_Test $test + * @param float $time + */ + public function endTest(PHPUnit_Framework_Test $test, $time) + { + + } +} diff --git a/vendor/doctrine/common/tests/README.markdown b/vendor/doctrine/common/tests/README.markdown new file mode 100644 index 0000000..e6f1703 --- /dev/null +++ b/vendor/doctrine/common/tests/README.markdown @@ -0,0 +1,27 @@ +# Running the Doctrine 2 Testsuite + +## Running tests + +Execute PHPUnit in the root folder of your doctrine-common clone. + + phpunit + +## Testing Lock-Support + +The Lock support in Doctrine 2 is tested using Gearman, which allows to run concurrent tasks in parallel. +Install Gearman with PHP as follows: + +1. Go to http://www.gearman.org and download the latest Gearman Server +2. Compile it and then call ldconfig +3. Start it up "gearmand -vvvv" +4. Install pecl/gearman by calling "gearman-beta" + +You can then go into tests/ and start up two workers: + + php Doctrine/Tests/ORM/Functional/Locking/LockAgentWorker.php + +Then run the locking test-suite: + + phpunit --configuration Doctrine/Tests/ORM/Functional/Locking/GearmanLockTest.php + +This can run considerable time, because it is using sleep() to test for the timing ranges of locks. \ No newline at end of file diff --git a/vendor/doctrine/dbal/LICENSE b/vendor/doctrine/dbal/LICENSE new file mode 100644 index 0000000..4a91f0b --- /dev/null +++ b/vendor/doctrine/dbal/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2006-2012 Doctrine Project + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/doctrine/dbal/README.md b/vendor/doctrine/dbal/README.md new file mode 100644 index 0000000..970db7b --- /dev/null +++ b/vendor/doctrine/dbal/README.md @@ -0,0 +1,14 @@ +# Doctrine DBAL + +Powerful database abstraction layer with many features for database schema introspection, schema management and PDO abstraction. + +* Master: [![Build Status](https://secure.travis-ci.org/doctrine/dbal.png?branch=master)](http://travis-ci.org/doctrine/dbal) +* 2.3: [![Build Status](https://secure.travis-ci.org/doctrine/dbal.png?branch=2.3)](http://travis-ci.org/doctrine/dbal) +* 2.2: [![Build Status](https://secure.travis-ci.org/doctrine/dbal.png?branch=2.2)](http://travis-ci.org/doctrine/dbal) + +## More resources: + +* [Website](http://www.doctrine-project.org/projects/dbal.html) +* [Documentation](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/) +* [Issue Tracker](http://www.doctrine-project.org/jira/browse/DBAL) +* [Downloads](http://github.com/doctrine/dbal/downloads) diff --git a/vendor/doctrine/dbal/UPGRADE b/vendor/doctrine/dbal/UPGRADE new file mode 100644 index 0000000..04d488c --- /dev/null +++ b/vendor/doctrine/dbal/UPGRADE @@ -0,0 +1,158 @@ +# Upgrade to 2.4 + +## Doctrine\DBAL\Schema\Constraint + +If you have custom classes that implement the constraint interface, you have to implement +an additional method ``getQuotedColumns`` now. This method is used to build proper constraint +SQL for columns that need to be quoted, like keywords reserved by the specific platform used. +The method has to return the same values as ``getColumns`` only that those column names that +need quotation have to be returned quoted for the given platform. + +# Upgrade to 2.3 + +## Oracle Session Init now sets Numeric Character + +Before 2.3 the Oracle Session Init did not care about the numeric character of the Session. +This could lead to problems on non english locale systems that required a comma as a floating +point seperator in Oracle. Since 2.3, using the Oracle Session Init on connection start the +client session will be altered to set the numeric character to ".,": + + ALTER SESSION SET NLS_NUMERIC_CHARACTERS = '.,' + +See [DBAL-345](http://www.doctrine-project.org/jira/browse/DBAL-345) for more details. + +## Doctrine\DBAL\Connection and Doctrine\DBAL\Statement + +The query related methods including but not limited to executeQuery, exec, query, and executeUpdate +now wrap the driver exceptions such as PDOException with DBALException to add more debugging +information such as the executed SQL statement, and any bound parameters. + +If you want to retrieve the driver specific exception, you can retrieve it by calling the +``getPrevious()`` method on DBALException. + +Before: + + catch(\PDOException $ex) { + // ... + } + +After: + + catch(\Doctrine\DBAL\DBALException $ex) { + $pdoException = $ex->getPrevious(); + // ... + } + +## Doctrine\DBAL\Connection#setCharsetSQL() removed + +This method only worked on MySQL and it is considered unsafe on MySQL to use SET NAMES UTF-8 instead +of setting the charset directly on connection already. Replace this behavior with the +connection charset option: + +Before: + + $conn = DriverManager::getConnection(array(..)); + $conn->setCharset('UTF8'); + +After: + + $conn = DriverManager::getConnection(array('charset' => 'UTF8', ..)); + +## Doctrine\DBAL\Schema\Table#renameColumn() removed + +Doctrine\DBAL\Schema\Table#renameColumn() was removed, because it drops and recreates +the column instead. There is no fix available, because a schema diff +cannot reliably detect if a column was renamed or one column was created +and another one dropped. + +You should use explicit SQL ALTER TABLE statements to change columns names. + +## Schema Filter paths + +The Filter Schema assets expression is not wrapped in () anymore for the regexp automatically. + +Before: + + $config->setFilterSchemaAssetsExpression('foo'); + +After: + + $config->setFilterSchemaAssetsExpression('(foo)'); + +## Creating MySQL Tables now defaults to UTF-8 + +If you are creating a new MySQL Table through the Doctrine API, charset/collate are +now set to 'utf8'/'utf8_unicode_ci' by default. Previously the MySQL server defaults were used. + +# Upgrade to 2.2 + +## Doctrine\DBAL\Connection#insert and Doctrine\DBAL\Connection#update + +Both methods now accept an optional last parameter $types with binding types of the values passed. +This can potentially break child classes that have overwritten one of these methods. + +## Doctrine\DBAL\Connection#executeQuery + +Doctrine\DBAL\Connection#executeQuery() got a new last parameter "QueryCacheProfile $qcp" + +## Doctrine\DBAL\Driver\Statement split + +The Driver statement was split into a ResultStatement and the normal statement extending from it. +This separates the configuration and the retrieval API from a statement. + +## MsSql Platform/SchemaManager renamed + +The MsSqlPlatform was renamed to SQLServerPlatform, the MsSqlSchemaManager was renamed +to SQLServerSchemaManager. + +## Cleanup SQLServer Platform version mess + +DBAL 2.1 and before were actually only compatible to SQL Server 2008, not earlier versions. +Still other parts of the platform did use old features instead of newly introduced datatypes +in SQL Server 2005. Starting with DBAL 2.2 you can pick the Doctrine abstraction exactly +matching your SQL Server version. + +The PDO SqlSrv driver now uses the new `SQLServer2008Platform` as default platform. +This platform uses new features of SQL Server as of version 2008. This also includes a switch +in the used fields for "text" and "blob" field types to: + + "text" => "VARCHAR(MAX)" + "blob" => "VARBINARY(MAX)" + +Additionally `SQLServerPlatform` in DBAL 2.1 and before used "DATE", "TIME" and "DATETIME2" for dates. +This types are only available since version 2008 and the introduction of an explicit +SQLServer 2008 platform makes this dependency explicit. + +An `SQLServer2005Platform` was also introduced to differentiate the features between +versions 2003, earlier and 2005. + +With this change the `SQLServerPlatform` now throws an exception for using limit queries +with an offset, since SQLServer 2003 and lower do not support this feature. + +To use the old SQL Server Platform, because you are using SQL Server 2003 and below use +the following configuration code: + + use Doctrine\DBAL\DriverManager; + use Doctrine\DBAL\Platforms\SQLServerPlatform; + use Doctrine\DBAL\Platforms\SQLServer2005Platform; + + // You are using SQL Server 2003 or earlier + $conn = DriverManager::getConnection(array( + 'driver' => 'pdo_sqlsrv', + 'platform' => new SQLServerPlatform() + // .. additional parameters + )); + + // You are using SQL Server 2005 + $conn = DriverManager::getConnection(array( + 'driver' => 'pdo_sqlsrv', + 'platform' => new SQLServer2005Platform() + // .. additional parameters + )); + + // You are using SQL Server 2008 + $conn = DriverManager::getConnection(array( + 'driver' => 'pdo_sqlsrv', + // 2008 is default platform + // .. additional parameters + )); diff --git a/vendor/doctrine/dbal/bin/doctrine-dbal b/vendor/doctrine/dbal/bin/doctrine-dbal new file mode 100644 index 0000000..7ca0142 --- /dev/null +++ b/vendor/doctrine/dbal/bin/doctrine-dbal @@ -0,0 +1,4 @@ +#!/usr/bin/env php +register(); + +$classLoader = new \Doctrine\Common\ClassLoader('Symfony'); +$classLoader->register(); + +$configFile = getcwd() . DIRECTORY_SEPARATOR . 'cli-config.php'; + +$helperSet = null; +if (file_exists($configFile)) { + if ( ! is_readable($configFile)) { + trigger_error( + 'Configuration file [' . $configFile . '] does not have read permission.', E_ERROR + ); + } + + require $configFile; + + foreach ($GLOBALS as $helperSetCandidate) { + if ($helperSetCandidate instanceof \Symfony\Component\Console\Helper\HelperSet) { + $helperSet = $helperSetCandidate; + break; + } + } +} + +$helperSet = ($helperSet) ?: new \Symfony\Component\Console\Helper\HelperSet(); + +$cli = new \Symfony\Component\Console\Application('Doctrine Command Line Interface', Doctrine\DBAL\Version::VERSION); +$cli->setCatchExceptions(true); +$cli->setHelperSet($helperSet); +$cli->addCommands(array( + // DBAL Commands + new \Doctrine\DBAL\Tools\Console\Command\RunSqlCommand(), + new \Doctrine\DBAL\Tools\Console\Command\ImportCommand(), + new \Doctrine\DBAL\Tools\Console\Command\ReservedWordsCommand(), + +)); +$cli->run(); diff --git a/vendor/doctrine/dbal/bin/doctrine.php b/vendor/doctrine/dbal/bin/doctrine.php new file mode 100644 index 0000000..cd3184d --- /dev/null +++ b/vendor/doctrine/dbal/bin/doctrine.php @@ -0,0 +1,42 @@ +register(); + +$classLoader = new \Doctrine\Common\ClassLoader('Symfony'); +$classLoader->register(); + +$configFile = getcwd() . DIRECTORY_SEPARATOR . 'cli-config.php'; + +$helperSet = null; +if (file_exists($configFile)) { + if ( ! is_readable($configFile)) { + trigger_error( + 'Configuration file [' . $configFile . '] does not have read permission.', E_ERROR + ); + } + + require $configFile; + + foreach ($GLOBALS as $helperSetCandidate) { + if ($helperSetCandidate instanceof \Symfony\Component\Console\Helper\HelperSet) { + $helperSet = $helperSetCandidate; + break; + } + } +} + +$helperSet = ($helperSet) ?: new \Symfony\Component\Console\Helper\HelperSet(); + +$cli = new \Symfony\Component\Console\Application('Doctrine Command Line Interface', Doctrine\DBAL\Version::VERSION); +$cli->setCatchExceptions(true); +$cli->setHelperSet($helperSet); +$cli->addCommands(array( + // DBAL Commands + new \Doctrine\DBAL\Tools\Console\Command\RunSqlCommand(), + new \Doctrine\DBAL\Tools\Console\Command\ImportCommand(), + +)); +$cli->run(); diff --git a/vendor/doctrine/dbal/composer.json b/vendor/doctrine/dbal/composer.json new file mode 100644 index 0000000..c959df9 --- /dev/null +++ b/vendor/doctrine/dbal/composer.json @@ -0,0 +1,36 @@ +{ + "name": "doctrine/dbal", + "type": "library", + "description": "Database Abstraction Layer", + "keywords": ["dbal", "database", "persistence", "queryobject"], + "homepage": "http://www.doctrine-project.org", + "license": "MIT", + "authors": [ + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, + {"name": "Jonathan Wage", "email": "jonwage@gmail.com"} + ], + "require": { + "php": ">=5.3.2", + "doctrine/common": "2.4.*@beta" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*", + "symfony/console": "2.*" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "suggest": { + "symfony/console": "Allows use of the command line interface" + }, + "autoload": { + "psr-0": { "Doctrine\\DBAL\\": "lib/" } + }, + "extra": { + "branch-alias": { + "dev-master": "2.4.x-dev" + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/ArrayStatement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/ArrayStatement.php new file mode 100644 index 0000000..23f9117 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/ArrayStatement.php @@ -0,0 +1,148 @@ +. + */ + +namespace Doctrine\DBAL\Cache; + +use Doctrine\DBAL\Driver\ResultStatement; +use PDO; + +class ArrayStatement implements \IteratorAggregate, ResultStatement +{ + /** + * @var array + */ + private $data; + + /** + * @var integer + */ + private $columnCount = 0; + + /** + * @var integer + */ + private $num = 0; + + /** + * @var integer + */ + private $defaultFetchMode = PDO::FETCH_BOTH; + + /** + * @param array $data + */ + public function __construct(array $data) + { + $this->data = $data; + if (count($data)) { + $this->columnCount = count($data[0]); + } + } + + /** + * {@inheritdoc} + */ + public function closeCursor() + { + unset ($this->data); + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + return $this->columnCount; + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + if ($arg2 !== null || $arg3 !== null) { + throw new \InvalidArgumentException("Caching layer does not support 2nd/3rd argument to setFetchMode()"); + } + + $this->defaultFetchMode = $fetchMode; + + return true; + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + $data = $this->fetchAll(); + + return new \ArrayIterator($data); + } + + /** + * {@inheritdoc} + */ + public function fetch($fetchMode = null) + { + if (isset($this->data[$this->num])) { + $row = $this->data[$this->num++]; + $fetchMode = $fetchMode ?: $this->defaultFetchMode; + if ($fetchMode === PDO::FETCH_ASSOC) { + return $row; + } else if ($fetchMode === PDO::FETCH_NUM) { + return array_values($row); + } else if ($fetchMode === PDO::FETCH_BOTH) { + return array_merge($row, array_values($row)); + } else if ($fetchMode === PDO::FETCH_COLUMN) { + return reset($row); + } else { + throw new \InvalidArgumentException("Invalid fetch-style given for fetching result."); + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function fetchAll($fetchMode = null) + { + $rows = array(); + while ($row = $this->fetch($fetchMode)) { + $rows[] = $row; + } + + return $rows; + } + + /** + * {@inheritdoc} + */ + public function fetchColumn($columnIndex = 0) + { + $row = $this->fetch(PDO::FETCH_NUM); + if (!isset($row[$columnIndex])) { + // TODO: verify this is correct behavior + return false; + } + + return $row[$columnIndex]; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/CacheException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/CacheException.php new file mode 100644 index 0000000..6b3b539 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/CacheException.php @@ -0,0 +1,43 @@ +. + */ + +namespace Doctrine\DBAL\Cache; + +/** + * @author Benjamin Eberlei + * @since 2.2 + */ +class CacheException extends \Doctrine\DBAL\DBALException +{ + /** + * @return \Doctrine\DBAL\Cache\CacheException + */ + static public function noCacheKey() + { + return new self("No cache key was set."); + } + + /** + * @return \Doctrine\DBAL\Cache\CacheException + */ + static public function noResultDriverConfigured() + { + return new self("Trying to cache a query but no result driver is configured."); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/QueryCacheProfile.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/QueryCacheProfile.php new file mode 100644 index 0000000..330eec9 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/QueryCacheProfile.php @@ -0,0 +1,141 @@ +. + */ + +namespace Doctrine\DBAL\Cache; + +use Doctrine\Common\Cache\Cache; + +/** + * Query Cache Profile handles the data relevant for query caching. + * + * It is a value object, setter methods return NEW instances. + * + * @author Benjamin Eberlei + */ +class QueryCacheProfile +{ + /** + * @var \Doctrine\Common\Cache\Cache|null + */ + private $resultCacheDriver; + + /** + * @var integer + */ + private $lifetime = 0; + + /** + * @var string|null + */ + private $cacheKey; + + /** + * @param integer $lifetime + * @param string|null $cacheKey + * @param \Doctrine\Common\Cache\Cache|null $resultCache + */ + public function __construct($lifetime = 0, $cacheKey = null, Cache $resultCache = null) + { + $this->lifetime = $lifetime; + $this->cacheKey = $cacheKey; + $this->resultCacheDriver = $resultCache; + } + + /** + * @return \Doctrine\Common\Cache\Cache|null + */ + public function getResultCacheDriver() + { + return $this->resultCacheDriver; + } + + /** + * @return integer + */ + public function getLifetime() + { + return $this->lifetime; + } + + /** + * @return string + * + * @throws \Doctrine\DBAL\Cache\CacheException + */ + public function getCacheKey() + { + if ($this->cacheKey === null) { + throw CacheException::noCacheKey(); + } + + return $this->cacheKey; + } + + /** + * Generates the real cache key from query, params and types. + * + * @param string $query + * @param array $params + * @param array $types + * + * @return array + */ + public function generateCacheKeys($query, $params, $types) + { + $realCacheKey = $query . "-" . serialize($params) . "-" . serialize($types); + // should the key be automatically generated using the inputs or is the cache key set? + if ($this->cacheKey === null) { + $cacheKey = sha1($realCacheKey); + } else { + $cacheKey = $this->cacheKey; + } + + return array($cacheKey, $realCacheKey); + } + + /** + * @param \Doctrine\Common\Cache\Cache $cache + * + * @return \Doctrine\DBAL\Cache\QueryCacheProfile + */ + public function setResultCacheDriver(Cache $cache) + { + return new QueryCacheProfile($this->lifetime, $this->cacheKey, $cache); + } + + /** + * @param string|null $cacheKey + * + * @return \Doctrine\DBAL\Cache\QueryCacheProfile + */ + public function setCacheKey($cacheKey) + { + return new QueryCacheProfile($this->lifetime, $cacheKey, $this->resultCacheDriver); + } + + /** + * @param integer $lifetime + * + * @return \Doctrine\DBAL\Cache\QueryCacheProfile + */ + public function setLifetime($lifetime) + { + return new QueryCacheProfile($lifetime, $this->cacheKey, $this->resultCacheDriver); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/ResultCacheStatement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/ResultCacheStatement.php new file mode 100644 index 0000000..a3afe50 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Cache/ResultCacheStatement.php @@ -0,0 +1,221 @@ +. + */ + +namespace Doctrine\DBAL\Cache; + +use Doctrine\DBAL\Driver\Statement; +use Doctrine\DBAL\Driver\ResultStatement; +use Doctrine\Common\Cache\Cache; +use PDO; + +/** + * Cache statement for SQL results. + * + * A result is saved in multiple cache keys, there is the originally specified + * cache key which is just pointing to result rows by key. The following things + * have to be ensured: + * + * 1. lifetime of the original key has to be longer than that of all the individual rows keys + * 2. if any one row key is missing the query has to be re-executed. + * + * Also you have to realize that the cache will load the whole result into memory at once to ensure 2. + * This means that the memory usage for cached results might increase by using this feature. + */ +class ResultCacheStatement implements \IteratorAggregate, ResultStatement +{ + /** + * @var \Doctrine\Common\Cache\Cache + */ + private $resultCache; + + /** + * + * @var string + */ + private $cacheKey; + + /** + * @var string + */ + private $realKey; + + /** + * @var integer + */ + private $lifetime; + + /** + * @var \Doctrine\DBAL\Driver\Statement + */ + private $statement; + + /** + * Did we reach the end of the statement? + * + * @var boolean + */ + private $emptied = false; + + /** + * @var array + */ + private $data; + + /** + * @var integer + */ + private $defaultFetchMode = PDO::FETCH_BOTH; + + /** + * @param \Doctrine\DBAL\Driver\Statement $stmt + * @param \Doctrine\Common\Cache\Cache $resultCache + * @param string $cacheKey + * @param string $realKey + * @param integer $lifetime + */ + public function __construct(Statement $stmt, Cache $resultCache, $cacheKey, $realKey, $lifetime) + { + $this->statement = $stmt; + $this->resultCache = $resultCache; + $this->cacheKey = $cacheKey; + $this->realKey = $realKey; + $this->lifetime = $lifetime; + } + + /** + * {@inheritdoc} + */ + public function closeCursor() + { + $this->statement->closeCursor(); + if ($this->emptied && $this->data !== null) { + $data = $this->resultCache->fetch($this->cacheKey); + if ( ! $data) { + $data = array(); + } + $data[$this->realKey] = $this->data; + + $this->resultCache->save($this->cacheKey, $data, $this->lifetime); + unset($this->data); + } + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + return $this->statement->columnCount(); + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + $this->defaultFetchMode = $fetchMode; + + return true; + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + $data = $this->fetchAll(); + + return new \ArrayIterator($data); + } + + /** + * {@inheritdoc} + */ + public function fetch($fetchMode = null) + { + if ($this->data === null) { + $this->data = array(); + } + + $row = $this->statement->fetch(PDO::FETCH_ASSOC); + if ($row) { + $this->data[] = $row; + + $fetchMode = $fetchMode ?: $this->defaultFetchMode; + + if ($fetchMode == PDO::FETCH_ASSOC) { + return $row; + } else if ($fetchMode == PDO::FETCH_NUM) { + return array_values($row); + } else if ($fetchMode == PDO::FETCH_BOTH) { + return array_merge($row, array_values($row)); + } else if ($fetchMode == PDO::FETCH_COLUMN) { + return reset($row); + } else { + throw new \InvalidArgumentException("Invalid fetch-style given for caching result."); + } + } + $this->emptied = true; + + return false; + } + + /** + * {@inheritdoc} + */ + public function fetchAll($fetchMode = null) + { + $rows = array(); + while ($row = $this->fetch($fetchMode)) { + $rows[] = $row; + } + + return $rows; + } + + /** + * {@inheritdoc} + */ + public function fetchColumn($columnIndex = 0) + { + $row = $this->fetch(PDO::FETCH_NUM); + if (!isset($row[$columnIndex])) { + // TODO: verify this is correct behavior + return false; + } + + return $row[$columnIndex]; + } + + /** + * Returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement + * executed by the corresponding object. + * + * If the last SQL statement executed by the associated Statement object was a SELECT statement, + * some databases may return the number of rows returned by that statement. However, + * this behaviour is not guaranteed for all databases and should not be + * relied on for portable applications. + * + * @return integer The number of rows. + */ + public function rowCount() + { + return $this->statement->rowCount(); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Configuration.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Configuration.php new file mode 100644 index 0000000..0225d1f --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Configuration.php @@ -0,0 +1,120 @@ +. + */ + +namespace Doctrine\DBAL; + +use Doctrine\DBAL\Logging\SQLLogger; +use Doctrine\Common\Cache\Cache; + +/** + * Configuration container for the Doctrine DBAL. + * + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @internal When adding a new configuration option just write a getter/setter + * pair and add the option to the _attributes array with a proper default value. + */ +class Configuration +{ + /** + * The attributes that are contained in the configuration. + * Values are default values. + * + * @var array + */ + protected $_attributes = array(); + + /** + * Sets the SQL logger to use. Defaults to NULL which means SQL logging is disabled. + * + * @param \Doctrine\DBAL\Logging\SQLLogger|null $logger + * + * @return void + */ + public function setSQLLogger(SQLLogger $logger = null) + { + $this->_attributes['sqlLogger'] = $logger; + } + + /** + * Gets the SQL logger that is used. + * + * @return \Doctrine\DBAL\Logging\SQLLogger + */ + public function getSQLLogger() + { + return isset($this->_attributes['sqlLogger']) ? + $this->_attributes['sqlLogger'] : null; + } + + /** + * Gets the cache driver implementation that is used for query result caching. + * + * @return \Doctrine\Common\Cache\Cache|null + */ + public function getResultCacheImpl() + { + return isset($this->_attributes['resultCacheImpl']) ? + $this->_attributes['resultCacheImpl'] : null; + } + + /** + * Sets the cache driver implementation that is used for query result caching. + * + * @param \Doctrine\Common\Cache\Cache $cacheImpl + * + * @return void + */ + public function setResultCacheImpl(Cache $cacheImpl) + { + $this->_attributes['resultCacheImpl'] = $cacheImpl; + } + + /** + * Sets the filter schema assets expression. + * + * Only include tables/sequences matching the filter expression regexp in + * schema instances generated for the active connection when calling + * {AbstractSchemaManager#createSchema()}. + * + * @param string $filterExpression + * + * @return void + */ + public function setFilterSchemaAssetsExpression($filterExpression) + { + $this->_attributes['filterSchemaAssetsExpression'] = $filterExpression; + } + + /** + * Returns filter schema assets expression. + * + * @return string|null + */ + public function getFilterSchemaAssetsExpression() + { + if (isset($this->_attributes['filterSchemaAssetsExpression'])) { + return $this->_attributes['filterSchemaAssetsExpression']; + } + + return null; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php new file mode 100644 index 0000000..7964cc8 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php @@ -0,0 +1,1410 @@ +. + */ + +namespace Doctrine\DBAL; + +use PDO; +use Closure; +use Exception; +use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Driver\Connection as DriverConnection; +use Doctrine\Common\EventManager; +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Cache\ResultCacheStatement; +use Doctrine\DBAL\Cache\QueryCacheProfile; +use Doctrine\DBAL\Cache\ArrayStatement; +use Doctrine\DBAL\Cache\CacheException; + +/** + * A wrapper around a Doctrine\DBAL\Driver\Connection that adds features like + * events, transaction isolation levels, configuration, emulated transaction nesting, + * lazy connecting and more. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Konsta Vesterinen + * @author Lukas Smith (MDB2 library) + * @author Benjamin Eberlei + */ +class Connection implements DriverConnection +{ + /** + * Constant for transaction isolation level READ UNCOMMITTED. + */ + const TRANSACTION_READ_UNCOMMITTED = 1; + + /** + * Constant for transaction isolation level READ COMMITTED. + */ + const TRANSACTION_READ_COMMITTED = 2; + + /** + * Constant for transaction isolation level REPEATABLE READ. + */ + const TRANSACTION_REPEATABLE_READ = 3; + + /** + * Constant for transaction isolation level SERIALIZABLE. + */ + const TRANSACTION_SERIALIZABLE = 4; + + /** + * Represents an array of ints to be expanded by Doctrine SQL parsing. + * + * @var integer + */ + const PARAM_INT_ARRAY = 101; + + /** + * Represents an array of strings to be expanded by Doctrine SQL parsing. + * + * @var integer + */ + const PARAM_STR_ARRAY = 102; + + /** + * Offset by which PARAM_* constants are detected as arrays of the param type. + * + * @var integer + */ + const ARRAY_PARAM_OFFSET = 100; + + /** + * The wrapped driver connection. + * + * @var \Doctrine\DBAL\Driver\Connection + */ + protected $_conn; + + /** + * @var \Doctrine\DBAL\Configuration + */ + protected $_config; + + /** + * @var \Doctrine\Common\EventManager + */ + protected $_eventManager; + + /** + * @var \Doctrine\DBAL\Query\Expression\ExpressionBuilder + */ + protected $_expr; + + /** + * Whether or not a connection has been established. + * + * @var boolean + */ + private $_isConnected = false; + + /** + * The transaction nesting level. + * + * @var integer + */ + private $_transactionNestingLevel = 0; + + /** + * The currently active transaction isolation level. + * + * @var integer + */ + private $_transactionIsolationLevel; + + /** + * If nested transactions should use savepoints. + * + * @var integer + */ + private $_nestTransactionsWithSavepoints; + + /** + * The parameters used during creation of the Connection instance. + * + * @var array + */ + private $_params = array(); + + /** + * The DatabasePlatform object that provides information about the + * database platform used by the connection. + * + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + protected $_platform; + + /** + * The schema manager. + * + * @var \Doctrine\DBAL\Schema\AbstractSchemaManager + */ + protected $_schemaManager; + + /** + * The used DBAL driver. + * + * @var \Doctrine\DBAL\Driver + */ + protected $_driver; + + /** + * Flag that indicates whether the current transaction is marked for rollback only. + * + * @var boolean + */ + private $_isRollbackOnly = false; + + /** + * @var integer + */ + protected $defaultFetchMode = PDO::FETCH_ASSOC; + + /** + * Initializes a new instance of the Connection class. + * + * @param array $params The connection parameters. + * @param \Doctrine\DBAL\Driver $driver The driver to use. + * @param \Doctrine\DBAL\Configuration|null $config The configuration, optional. + * @param \Doctrine\Common\EventManager|null $eventManager The event manager, optional. + * + * @throws \Doctrine\DBAL\DBALException + */ + public function __construct(array $params, Driver $driver, Configuration $config = null, + EventManager $eventManager = null) + { + $this->_driver = $driver; + $this->_params = $params; + + if (isset($params['pdo'])) { + $this->_conn = $params['pdo']; + $this->_isConnected = true; + } + + // Create default config and event manager if none given + if ( ! $config) { + $config = new Configuration(); + } + + if ( ! $eventManager) { + $eventManager = new EventManager(); + } + + $this->_config = $config; + $this->_eventManager = $eventManager; + + $this->_expr = new Query\Expression\ExpressionBuilder($this); + + if ( ! isset($params['platform'])) { + $this->_platform = $driver->getDatabasePlatform(); + } else if ($params['platform'] instanceof Platforms\AbstractPlatform) { + $this->_platform = $params['platform']; + } else { + throw DBALException::invalidPlatformSpecified(); + } + + $this->_platform->setEventManager($eventManager); + + $this->_transactionIsolationLevel = $this->_platform->getDefaultTransactionIsolationLevel(); + } + + /** + * Gets the parameters used during instantiation. + * + * @return array + */ + public function getParams() + { + return $this->_params; + } + + /** + * Gets the name of the database this Connection is connected to. + * + * @return string + */ + public function getDatabase() + { + return $this->_driver->getDatabase($this); + } + + /** + * Gets the hostname of the currently connected database. + * + * @return string|null + */ + public function getHost() + { + return isset($this->_params['host']) ? $this->_params['host'] : null; + } + + /** + * Gets the port of the currently connected database. + * + * @return mixed + */ + public function getPort() + { + return isset($this->_params['port']) ? $this->_params['port'] : null; + } + + /** + * Gets the username used by this connection. + * + * @return string|null + */ + public function getUsername() + { + return isset($this->_params['user']) ? $this->_params['user'] : null; + } + + /** + * Gets the password used by this connection. + * + * @return string|null + */ + public function getPassword() + { + return isset($this->_params['password']) ? $this->_params['password'] : null; + } + + /** + * Gets the DBAL driver instance. + * + * @return \Doctrine\DBAL\Driver + */ + public function getDriver() + { + return $this->_driver; + } + + /** + * Gets the Configuration used by the Connection. + * + * @return \Doctrine\DBAL\Configuration + */ + public function getConfiguration() + { + return $this->_config; + } + + /** + * Gets the EventManager used by the Connection. + * + * @return \Doctrine\Common\EventManager + */ + public function getEventManager() + { + return $this->_eventManager; + } + + /** + * Gets the DatabasePlatform for the connection. + * + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getDatabasePlatform() + { + return $this->_platform; + } + + /** + * Gets the ExpressionBuilder for the connection. + * + * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder + */ + public function getExpressionBuilder() + { + return $this->_expr; + } + + /** + * Establishes the connection with the database. + * + * @return boolean TRUE if the connection was successfully established, FALSE if + * the connection is already open. + */ + public function connect() + { + if ($this->_isConnected) return false; + + $driverOptions = isset($this->_params['driverOptions']) ? + $this->_params['driverOptions'] : array(); + $user = isset($this->_params['user']) ? $this->_params['user'] : null; + $password = isset($this->_params['password']) ? + $this->_params['password'] : null; + + $this->_conn = $this->_driver->connect($this->_params, $user, $password, $driverOptions); + $this->_isConnected = true; + + if ($this->_eventManager->hasListeners(Events::postConnect)) { + $eventArgs = new Event\ConnectionEventArgs($this); + $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs); + } + + return true; + } + + /** + * Sets the fetch mode. + * + * @param integer $fetchMode + * + * @return void + */ + public function setFetchMode($fetchMode) + { + $this->defaultFetchMode = $fetchMode; + } + + /** + * Prepares and executes an SQL query and returns the first row of the result + * as an associative array. + * + * @param string $statement The SQL query. + * @param array $params The query parameters. + * + * @return array + */ + public function fetchAssoc($statement, array $params = array()) + { + return $this->executeQuery($statement, $params)->fetch(PDO::FETCH_ASSOC); + } + + /** + * Prepares and executes an SQL query and returns the first row of the result + * as a numerically indexed array. + * + * @param string $statement The SQL query to be executed. + * @param array $params The prepared statement params. + * + * @return array + */ + public function fetchArray($statement, array $params = array()) + { + return $this->executeQuery($statement, $params)->fetch(PDO::FETCH_NUM); + } + + /** + * Prepares and executes an SQL query and returns the value of a single column + * of the first row of the result. + * + * @param string $statement The SQL query to be executed. + * @param array $params The prepared statement params. + * @param integer $column The 0-indexed column number to retrieve. + * + * @return mixed + */ + public function fetchColumn($statement, array $params = array(), $column = 0) + { + return $this->executeQuery($statement, $params)->fetchColumn($column); + } + + /** + * Whether an actual connection to the database is established. + * + * @return boolean + */ + public function isConnected() + { + return $this->_isConnected; + } + + /** + * Checks whether a transaction is currently active. + * + * @return boolean TRUE if a transaction is currently active, FALSE otherwise. + */ + public function isTransactionActive() + { + return $this->_transactionNestingLevel > 0; + } + + /** + * Executes an SQL DELETE statement on a table. + * + * @param string $tableName The name of the table on which to delete. + * @param array $identifier The deletion criteria. An associative array containing column-value pairs. + * @param array $types The types of identifiers. + * + * @return integer The number of affected rows. + */ + public function delete($tableName, array $identifier, array $types = array()) + { + $this->connect(); + + $criteria = array(); + + foreach (array_keys($identifier) as $columnName) { + $criteria[] = $columnName . ' = ?'; + } + + if ( ! is_int(key($types))) { + $types = $this->extractTypeValues($identifier, $types); + } + + $query = 'DELETE FROM ' . $tableName . ' WHERE ' . implode(' AND ', $criteria); + + return $this->executeUpdate($query, array_values($identifier), $types); + } + + /** + * Closes the connection. + * + * @return void + */ + public function close() + { + unset($this->_conn); + + $this->_isConnected = false; + } + + /** + * Sets the transaction isolation level. + * + * @param integer $level The level to set. + * + * @return integer + */ + public function setTransactionIsolation($level) + { + $this->_transactionIsolationLevel = $level; + + return $this->executeUpdate($this->_platform->getSetTransactionIsolationSQL($level)); + } + + /** + * Gets the currently active transaction isolation level. + * + * @return integer The current transaction isolation level. + */ + public function getTransactionIsolation() + { + return $this->_transactionIsolationLevel; + } + + /** + * Executes an SQL UPDATE statement on a table. + * + * @param string $tableName The name of the table to update. + * @param array $data An associative array containing column-value pairs. + * @param array $identifier The update criteria. An associative array containing column-value pairs. + * @param array $types Types of the merged $data and $identifier arrays in that order. + * + * @return integer The number of affected rows. + */ + public function update($tableName, array $data, array $identifier, array $types = array()) + { + $this->connect(); + $set = array(); + + foreach ($data as $columnName => $value) { + $set[] = $columnName . ' = ?'; + } + + if ( ! is_int(key($types))) { + $types = $this->extractTypeValues(array_merge($data, $identifier), $types); + } + + $params = array_merge(array_values($data), array_values($identifier)); + + $sql = 'UPDATE ' . $tableName . ' SET ' . implode(', ', $set) + . ' WHERE ' . implode(' = ? AND ', array_keys($identifier)) + . ' = ?'; + + return $this->executeUpdate($sql, $params, $types); + } + + /** + * Inserts a table row with specified data. + * + * @param string $tableName The name of the table to insert data into. + * @param array $data An associative array containing column-value pairs. + * @param array $types Types of the inserted data. + * + * @return integer The number of affected rows. + */ + public function insert($tableName, array $data, array $types = array()) + { + $this->connect(); + + if ( ! is_int(key($types))) { + $types = $this->extractTypeValues($data, $types); + } + + $query = 'INSERT INTO ' . $tableName + . ' (' . implode(', ', array_keys($data)) . ')' + . ' VALUES (' . implode(', ', array_fill(0, count($data), '?')) . ')'; + + return $this->executeUpdate($query, array_values($data), $types); + } + + /** + * Extract ordered type list from two associate key lists of data and types. + * + * @param array $data + * @param array $types + * + * @return array + */ + private function extractTypeValues(array $data, array $types) + { + $typeValues = array(); + + foreach ($data as $k => $_) { + $typeValues[] = isset($types[$k]) + ? $types[$k] + : \PDO::PARAM_STR; + } + + return $typeValues; + } + + /** + * Quotes a string so it can be safely used as a table or column name, even if + * it is a reserved name. + * + * Delimiting style depends on the underlying database platform that is being used. + * + * NOTE: Just because you CAN use quoted identifiers does not mean + * you SHOULD use them. In general, they end up causing way more + * problems than they solve. + * + * @param string $str The name to be quoted. + * + * @return string The quoted name. + */ + public function quoteIdentifier($str) + { + return $this->_platform->quoteIdentifier($str); + } + + /** + * Quotes a given input parameter. + * + * @param mixed $input The parameter to be quoted. + * @param string|null $type The type of the parameter. + * + * @return string The quoted parameter. + */ + public function quote($input, $type = null) + { + $this->connect(); + + list($value, $bindingType) = $this->getBindingInfo($input, $type); + return $this->_conn->quote($value, $bindingType); + } + + /** + * Prepares and executes an SQL query and returns the result as an associative array. + * + * @param string $sql The SQL query. + * @param array $params The query parameters. + * @param array $types The query parameter types. + * + * @return array + */ + public function fetchAll($sql, array $params = array(), $types = array()) + { + return $this->executeQuery($sql, $params, $types)->fetchAll(); + } + + /** + * Prepares an SQL statement. + * + * @param string $statement The SQL statement to prepare. + * + * @return \Doctrine\DBAL\Driver\Statement The prepared statement. + * + * @throws \Doctrine\DBAL\DBALException + */ + public function prepare($statement) + { + $this->connect(); + + try { + $stmt = new Statement($statement, $this); + } catch (\Exception $ex) { + throw DBALException::driverExceptionDuringQuery($ex, $statement); + } + + $stmt->setFetchMode($this->defaultFetchMode); + + return $stmt; + } + + /** + * Executes an, optionally parametrized, SQL query. + * + * If the query is parametrized, a prepared statement is used. + * If an SQLLogger is configured, the execution is logged. + * + * @param string $query The SQL query to execute. + * @param array $params The parameters to bind to the query, if any. + * @param array $types The types the previous parameters are in. + * @param \Doctrine\DBAL\Cache\QueryCacheProfile|null $qcp The query cache profile, optional. + * + * @return \Doctrine\DBAL\Driver\Statement The executed statement. + * + * @throws \Doctrine\DBAL\DBALException + * + * @internal PERF: Directly prepares a driver statement, not a wrapper. + */ + public function executeQuery($query, array $params = array(), $types = array(), QueryCacheProfile $qcp = null) + { + if ($qcp !== null) { + return $this->executeCacheQuery($query, $params, $types, $qcp); + } + + $this->connect(); + + $logger = $this->_config->getSQLLogger(); + if ($logger) { + $logger->startQuery($query, $params, $types); + } + + try { + if ($params) { + list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types); + + $stmt = $this->_conn->prepare($query); + if ($types) { + $this->_bindTypedValues($stmt, $params, $types); + $stmt->execute(); + } else { + $stmt->execute($params); + } + } else { + $stmt = $this->_conn->query($query); + } + } catch (\Exception $ex) { + throw DBALException::driverExceptionDuringQuery($ex, $query, $this->resolveParams($params, $types)); + } + + $stmt->setFetchMode($this->defaultFetchMode); + + if ($logger) { + $logger->stopQuery(); + } + + return $stmt; + } + + /** + * Executes a caching query. + * + * @param string $query The SQL query to execute. + * @param array $params The parameters to bind to the query, if any. + * @param array $types The types the previous parameters are in. + * @param \Doctrine\DBAL\Cache\QueryCacheProfile $qcp The query cache profile. + * + * @return \Doctrine\DBAL\Driver\ResultStatement + * + * @throws \Doctrine\DBAL\Cache\CacheException + */ + public function executeCacheQuery($query, $params, $types, QueryCacheProfile $qcp) + { + $resultCache = $qcp->getResultCacheDriver() ?: $this->_config->getResultCacheImpl(); + if ( ! $resultCache) { + throw CacheException::noResultDriverConfigured(); + } + + list($cacheKey, $realKey) = $qcp->generateCacheKeys($query, $params, $types); + + // fetch the row pointers entry + if ($data = $resultCache->fetch($cacheKey)) { + // is the real key part of this row pointers map or is the cache only pointing to other cache keys? + if (isset($data[$realKey])) { + $stmt = new ArrayStatement($data[$realKey]); + } else if (array_key_exists($realKey, $data)) { + $stmt = new ArrayStatement(array()); + } + } + + if (!isset($stmt)) { + $stmt = new ResultCacheStatement($this->executeQuery($query, $params, $types), $resultCache, $cacheKey, $realKey, $qcp->getLifetime()); + } + + $stmt->setFetchMode($this->defaultFetchMode); + + return $stmt; + } + + /** + * Executes an, optionally parametrized, SQL query and returns the result, + * applying a given projection/transformation function on each row of the result. + * + * @param string $query The SQL query to execute. + * @param array $params The parameters, if any. + * @param \Closure $function The transformation function that is applied on each row. + * The function receives a single parameter, an array, that + * represents a row of the result set. + * + * @return mixed The projected result of the query. + */ + public function project($query, array $params, Closure $function) + { + $result = array(); + $stmt = $this->executeQuery($query, $params ?: array()); + + while ($row = $stmt->fetch()) { + $result[] = $function($row); + } + + $stmt->closeCursor(); + + return $result; + } + + /** + * Executes an SQL statement, returning a result set as a Statement object. + * + * @return \Doctrine\DBAL\Driver\Statement + * + * @throws \Doctrine\DBAL\DBALException + */ + public function query() + { + $this->connect(); + + $args = func_get_args(); + + $logger = $this->_config->getSQLLogger(); + if ($logger) { + $logger->startQuery($args[0]); + } + + try { + switch (func_num_args()) { + case 1: + $statement = $this->_conn->query($args[0]); + break; + case 2: + $statement = $this->_conn->query($args[0], $args[1]); + break; + default: + $statement = call_user_func_array(array($this->_conn, 'query'), $args); + break; + } + } catch (\Exception $ex) { + throw DBALException::driverExceptionDuringQuery($ex, $args[0]); + } + + $statement->setFetchMode($this->defaultFetchMode); + + if ($logger) { + $logger->stopQuery(); + } + + return $statement; + } + + /** + * Executes an SQL INSERT/UPDATE/DELETE query with the given parameters + * and returns the number of affected rows. + * + * This method supports PDO binding types as well as DBAL mapping types. + * + * @param string $query The SQL query. + * @param array $params The query parameters. + * @param array $types The parameter types. + * + * @return integer The number of affected rows. + * + * @throws \Doctrine\DBAL\DBALException + * + * @internal PERF: Directly prepares a driver statement, not a wrapper. + */ + public function executeUpdate($query, array $params = array(), array $types = array()) + { + $this->connect(); + + $logger = $this->_config->getSQLLogger(); + if ($logger) { + $logger->startQuery($query, $params, $types); + } + + try { + if ($params) { + list($query, $params, $types) = SQLParserUtils::expandListParameters($query, $params, $types); + + $stmt = $this->_conn->prepare($query); + if ($types) { + $this->_bindTypedValues($stmt, $params, $types); + $stmt->execute(); + } else { + $stmt->execute($params); + } + $result = $stmt->rowCount(); + } else { + $result = $this->_conn->exec($query); + } + } catch (\Exception $ex) { + throw DBALException::driverExceptionDuringQuery($ex, $query, $this->resolveParams($params, $types)); + } + + if ($logger) { + $logger->stopQuery(); + } + + return $result; + } + + /** + * Executes an SQL statement and return the number of affected rows. + * + * @param string $statement + * + * @return integer The number of affected rows. + * + * @throws \Doctrine\DBAL\DBALException + */ + public function exec($statement) + { + $this->connect(); + + $logger = $this->_config->getSQLLogger(); + if ($logger) { + $logger->startQuery($statement); + } + + try { + $result = $this->_conn->exec($statement); + } catch (\Exception $ex) { + throw DBALException::driverExceptionDuringQuery($ex, $statement); + } + + if ($logger) { + $logger->stopQuery(); + } + + return $result; + } + + /** + * Returns the current transaction nesting level. + * + * @return integer The nesting level. A value of 0 means there's no active transaction. + */ + public function getTransactionNestingLevel() + { + return $this->_transactionNestingLevel; + } + + /** + * Fetches the SQLSTATE associated with the last database operation. + * + * @return integer The last error code. + */ + public function errorCode() + { + $this->connect(); + + return $this->_conn->errorCode(); + } + + /** + * Fetches extended error information associated with the last database operation. + * + * @return array The last error information. + */ + public function errorInfo() + { + $this->connect(); + + return $this->_conn->errorInfo(); + } + + /** + * Returns the ID of the last inserted row, or the last value from a sequence object, + * depending on the underlying driver. + * + * Note: This method may not return a meaningful or consistent result across different drivers, + * because the underlying database may not even support the notion of AUTO_INCREMENT/IDENTITY + * columns or sequences. + * + * @param string|null $seqName Name of the sequence object from which the ID should be returned. + * + * @return string A string representation of the last inserted ID. + */ + public function lastInsertId($seqName = null) + { + $this->connect(); + + return $this->_conn->lastInsertId($seqName); + } + + /** + * Executes a function in a transaction. + * + * The function gets passed this Connection instance as an (optional) parameter. + * + * If an exception occurs during execution of the function or transaction commit, + * the transaction is rolled back and the exception re-thrown. + * + * @param \Closure $func The function to execute transactionally. + * + * @return void + * + * @throws \Exception + */ + public function transactional(Closure $func) + { + $this->beginTransaction(); + try { + $func($this); + $this->commit(); + } catch (Exception $e) { + $this->rollback(); + throw $e; + } + } + + /** + * Sets if nested transactions should use savepoints. + * + * @param boolean $nestTransactionsWithSavepoints + * + * @return void + * + * @throws \Doctrine\DBAL\ConnectionException + */ + public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints) + { + if ($this->_transactionNestingLevel > 0) { + throw ConnectionException::mayNotAlterNestedTransactionWithSavepointsInTransaction(); + } + + if ( ! $this->_platform->supportsSavepoints()) { + throw ConnectionException::savepointsNotSupported(); + } + + $this->_nestTransactionsWithSavepoints = $nestTransactionsWithSavepoints; + } + + /** + * Gets if nested transactions should use savepoints. + * + * @return boolean + */ + public function getNestTransactionsWithSavepoints() + { + return $this->_nestTransactionsWithSavepoints; + } + + /** + * Returns the savepoint name to use for nested transactions are false if they are not supported + * "savepointFormat" parameter is not set + * + * @return mixed A string with the savepoint name or false. + */ + protected function _getNestedTransactionSavePointName() + { + return 'DOCTRINE2_SAVEPOINT_'.$this->_transactionNestingLevel; + } + + /** + * Starts a transaction by suspending auto-commit mode. + * + * @return void + */ + public function beginTransaction() + { + $this->connect(); + + ++$this->_transactionNestingLevel; + + $logger = $this->_config->getSQLLogger(); + + if ($this->_transactionNestingLevel == 1) { + if ($logger) { + $logger->startQuery('"START TRANSACTION"'); + } + $this->_conn->beginTransaction(); + if ($logger) { + $logger->stopQuery(); + } + } else if ($this->_nestTransactionsWithSavepoints) { + if ($logger) { + $logger->startQuery('"SAVEPOINT"'); + } + $this->createSavepoint($this->_getNestedTransactionSavePointName()); + if ($logger) { + $logger->stopQuery(); + } + } + } + + /** + * Commits the current transaction. + * + * @return void + * + * @throws \Doctrine\DBAL\ConnectionException If the commit failed due to no active transaction or + * because the transaction was marked for rollback only. + */ + public function commit() + { + if ($this->_transactionNestingLevel == 0) { + throw ConnectionException::noActiveTransaction(); + } + if ($this->_isRollbackOnly) { + throw ConnectionException::commitFailedRollbackOnly(); + } + + $this->connect(); + + $logger = $this->_config->getSQLLogger(); + + if ($this->_transactionNestingLevel == 1) { + if ($logger) { + $logger->startQuery('"COMMIT"'); + } + $this->_conn->commit(); + if ($logger) { + $logger->stopQuery(); + } + } else if ($this->_nestTransactionsWithSavepoints) { + if ($logger) { + $logger->startQuery('"RELEASE SAVEPOINT"'); + } + $this->releaseSavepoint($this->_getNestedTransactionSavePointName()); + if ($logger) { + $logger->stopQuery(); + } + } + + --$this->_transactionNestingLevel; + } + + /** + * Cancels any database changes done during the current transaction. + * + * This method can be listened with onPreTransactionRollback and onTransactionRollback + * eventlistener methods. + * + * @throws \Doctrine\DBAL\ConnectionException If the rollback operation failed. + */ + public function rollBack() + { + if ($this->_transactionNestingLevel == 0) { + throw ConnectionException::noActiveTransaction(); + } + + $this->connect(); + + $logger = $this->_config->getSQLLogger(); + + if ($this->_transactionNestingLevel == 1) { + if ($logger) { + $logger->startQuery('"ROLLBACK"'); + } + $this->_transactionNestingLevel = 0; + $this->_conn->rollback(); + $this->_isRollbackOnly = false; + if ($logger) { + $logger->stopQuery(); + } + } else if ($this->_nestTransactionsWithSavepoints) { + if ($logger) { + $logger->startQuery('"ROLLBACK TO SAVEPOINT"'); + } + $this->rollbackSavepoint($this->_getNestedTransactionSavePointName()); + --$this->_transactionNestingLevel; + if ($logger) { + $logger->stopQuery(); + } + } else { + $this->_isRollbackOnly = true; + --$this->_transactionNestingLevel; + } + } + + /** + * Creates a new savepoint. + * + * @param string $savepoint The name of the savepoint to create. + * + * @return void + * + * @throws \Doctrine\DBAL\ConnectionException + */ + public function createSavepoint($savepoint) + { + if ( ! $this->_platform->supportsSavepoints()) { + throw ConnectionException::savepointsNotSupported(); + } + + $this->_conn->exec($this->_platform->createSavePoint($savepoint)); + } + + /** + * Releases the given savepoint. + * + * @param string $savepoint The name of the savepoint to release. + * + * @return void + * + * @throws \Doctrine\DBAL\ConnectionException + */ + public function releaseSavepoint($savepoint) + { + if ( ! $this->_platform->supportsSavepoints()) { + throw ConnectionException::savepointsNotSupported(); + } + + if ($this->_platform->supportsReleaseSavepoints()) { + $this->_conn->exec($this->_platform->releaseSavePoint($savepoint)); + } + } + + /** + * Rolls back to the given savepoint. + * + * @param string $savepoint The name of the savepoint to rollback to. + * + * @return void + * + * @throws \Doctrine\DBAL\ConnectionException + */ + public function rollbackSavepoint($savepoint) + { + if ( ! $this->_platform->supportsSavepoints()) { + throw ConnectionException::savepointsNotSupported(); + } + + $this->_conn->exec($this->_platform->rollbackSavePoint($savepoint)); + } + + /** + * Gets the wrapped driver connection. + * + * @return \Doctrine\DBAL\Driver\Connection + */ + public function getWrappedConnection() + { + $this->connect(); + + return $this->_conn; + } + + /** + * Gets the SchemaManager that can be used to inspect or change the + * database schema through the connection. + * + * @return \Doctrine\DBAL\Schema\AbstractSchemaManager + */ + public function getSchemaManager() + { + if ( ! $this->_schemaManager) { + $this->_schemaManager = $this->_driver->getSchemaManager($this); + } + + return $this->_schemaManager; + } + + /** + * Marks the current transaction so that the only possible + * outcome for the transaction to be rolled back. + * + * @return void + * + * @throws \Doctrine\DBAL\ConnectionException If no transaction is active. + */ + public function setRollbackOnly() + { + if ($this->_transactionNestingLevel == 0) { + throw ConnectionException::noActiveTransaction(); + } + $this->_isRollbackOnly = true; + } + + /** + * Checks whether the current transaction is marked for rollback only. + * + * @return boolean + * + * @throws \Doctrine\DBAL\ConnectionException If no transaction is active. + */ + public function isRollbackOnly() + { + if ($this->_transactionNestingLevel == 0) { + throw ConnectionException::noActiveTransaction(); + } + + return $this->_isRollbackOnly; + } + + /** + * Converts a given value to its database representation according to the conversion + * rules of a specific DBAL mapping type. + * + * @param mixed $value The value to convert. + * @param string $type The name of the DBAL mapping type. + * + * @return mixed The converted value. + */ + public function convertToDatabaseValue($value, $type) + { + return Type::getType($type)->convertToDatabaseValue($value, $this->_platform); + } + + /** + * Converts a given value to its PHP representation according to the conversion + * rules of a specific DBAL mapping type. + * + * @param mixed $value The value to convert. + * @param string $type The name of the DBAL mapping type. + * + * @return mixed The converted type. + */ + public function convertToPHPValue($value, $type) + { + return Type::getType($type)->convertToPHPValue($value, $this->_platform); + } + + /** + * Binds a set of parameters, some or all of which are typed with a PDO binding type + * or DBAL mapping type, to a given statement. + * + * @param \Doctrine\DBAL\Driver\Statement $stmt The statement to bind the values to. + * @param array $params The map/list of named/positional parameters. + * @param array $types The parameter types (PDO binding types or DBAL mapping types). + * + * @return void + * + * @internal Duck-typing used on the $stmt parameter to support driver statements as well as + * raw PDOStatement instances. + */ + private function _bindTypedValues($stmt, array $params, array $types) + { + // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO. + if (is_int(key($params))) { + // Positional parameters + $typeOffset = array_key_exists(0, $types) ? -1 : 0; + $bindIndex = 1; + foreach ($params as $value) { + $typeIndex = $bindIndex + $typeOffset; + if (isset($types[$typeIndex])) { + $type = $types[$typeIndex]; + list($value, $bindingType) = $this->getBindingInfo($value, $type); + $stmt->bindValue($bindIndex, $value, $bindingType); + } else { + $stmt->bindValue($bindIndex, $value); + } + ++$bindIndex; + } + } else { + // Named parameters + foreach ($params as $name => $value) { + if (isset($types[$name])) { + $type = $types[$name]; + list($value, $bindingType) = $this->getBindingInfo($value, $type); + $stmt->bindValue($name, $value, $bindingType); + } else { + $stmt->bindValue($name, $value); + } + } + } + } + + /** + * Gets the binding type of a given type. The given type can be a PDO or DBAL mapping type. + * + * @param mixed $value The value to bind. + * @param mixed $type The type to bind (PDO or DBAL). + * + * @return array [0] => the (escaped) value, [1] => the binding type. + */ + private function getBindingInfo($value, $type) + { + if (is_string($type)) { + $type = Type::getType($type); + } + if ($type instanceof Type) { + $value = $type->convertToDatabaseValue($value, $this->_platform); + $bindingType = $type->getBindingType(); + } else { + $bindingType = $type; // PDO::PARAM_* constants + } + + return array($value, $bindingType); + } + + /** + * Resolves the parameters to a format which can be displayed. + * + * @internal This is a purely internal method. If you rely on this method, you are advised to + * copy/paste the code as this method may change, or be removed without prior notice. + * + * @param array $params + * @param array $types + * + * @return array + */ + public function resolveParams(array $params, array $types) + { + $resolvedParams = array(); + + // Check whether parameters are positional or named. Mixing is not allowed, just like in PDO. + if (is_int(key($params))) { + // Positional parameters + $typeOffset = array_key_exists(0, $types) ? -1 : 0; + $bindIndex = 1; + foreach ($params as $value) { + $typeIndex = $bindIndex + $typeOffset; + if (isset($types[$typeIndex])) { + $type = $types[$typeIndex]; + list($value,) = $this->getBindingInfo($value, $type); + $resolvedParams[$bindIndex] = $value; + } else { + $resolvedParams[$bindIndex] = $value; + } + ++$bindIndex; + } + } else { + // Named parameters + foreach ($params as $name => $value) { + if (isset($types[$name])) { + $type = $types[$name]; + list($value,) = $this->getBindingInfo($value, $type); + $resolvedParams[$name] = $value; + } else { + $resolvedParams[$name] = $value; + } + } + } + + return $resolvedParams; + } + + /** + * Creates a new instance of a SQL query builder. + * + * @return \Doctrine\DBAL\Query\QueryBuilder + */ + public function createQueryBuilder() + { + return new Query\QueryBuilder($this); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/ConnectionException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/ConnectionException.php new file mode 100644 index 0000000..86494b0 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/ConnectionException.php @@ -0,0 +1,60 @@ +. + */ + +namespace Doctrine\DBAL; + +/** + * @link www.doctrine-project.org + * @since 2.0 + * @author Jonathan H. Wage . + */ + +namespace Doctrine\DBAL\Connections; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Configuration; +use Doctrine\Common\EventManager; +use Doctrine\DBAL\Event\ConnectionEventArgs; +use Doctrine\DBAL\Events; + +/** + * Master-Slave Connection + * + * Connection can be used with master-slave setups. + * + * Important for the understanding of this connection should be how and when + * it picks the slave or master. + * + * 1. Slave if master was never picked before and ONLY if 'getWrappedConnection' + * or 'executeQuery' is used. + * 2. Master picked when 'exec', 'executeUpdate', 'insert', 'delete', 'update', 'createSavepoint', + * 'releaseSavepoint', 'beginTransaction', 'rollback', 'commit', 'query' or + * 'prepare' is called. + * 3. If master was picked once during the lifetime of the connection it will always get picked afterwards. + * 4. One slave connection is randomly picked ONCE during a request. + * + * ATTENTION: You can write to the slave with this connection if you execute a write query without + * opening up a transaction. For example: + * + * $conn = DriverManager::getConnection(...); + * $conn->executeQuery("DELETE FROM table"); + * + * Be aware that Connection#executeQuery is a method specifically for READ + * operations only. + * + * This connection is limited to slave operations using the + * Connection#executeQuery operation only, because it wouldn't be compatible + * with the ORM or SchemaManager code otherwise. Both use all the other + * operations in a context where writes could happen to a slave, which makes + * this restricted approach necessary. + * + * You can manually connect to the master at any time by calling: + * + * $conn->connect('master'); + * + * Instantiation through the DriverManager looks like: + * + * @example + * + * $conn = DriverManager::getConnection(array( + * 'wrapperClass' => 'Doctrine\DBAL\Connections\MasterSlaveConnection', + * 'driver' => 'pdo_mysql', + * 'master' => array('user' => '', 'password' => '', 'host' => '', 'dbname' => ''), + * 'slaves' => array( + * array('user' => 'slave1', 'password', 'host' => '', 'dbname' => ''), + * array('user' => 'slave2', 'password', 'host' => '', 'dbname' => ''), + * ) + * )); + * + * You can also pass 'driverOptions' and any other documented option to each of this drivers to pass additional information. + * + * @author Lars Strojny + * @author Benjamin Eberlei + */ +class MasterSlaveConnection extends Connection +{ + /** + * Master and slave connection (one of the randomly picked slaves). + * + * @var \Doctrine\DBAL\Driver\Connection[] + */ + protected $connections = array('master' => null, 'slave' => null); + + /** + * You can keep the slave connection and then switch back to it + * during the request if you know what you are doing. + * + * @var boolean + */ + protected $keepSlave = false; + + /** + * Creates Master Slave Connection. + * + * @param array $params + * @param \Doctrine\DBAL\Driver $driver + * @param \Doctrine\DBAL\Configuration|null $config + * @param \Doctrine\Common\EventManager|null $eventManager + * + * @throws \InvalidArgumentException + */ + public function __construct(array $params, Driver $driver, Configuration $config = null, EventManager $eventManager = null) + { + if ( !isset($params['slaves']) || !isset($params['master']) ) { + throw new \InvalidArgumentException('master or slaves configuration missing'); + } + if ( count($params['slaves']) == 0 ) { + throw new \InvalidArgumentException('You have to configure at least one slaves.'); + } + + $params['master']['driver'] = $params['driver']; + foreach ($params['slaves'] as $slaveKey => $slave) { + $params['slaves'][$slaveKey]['driver'] = $params['driver']; + } + + $this->keepSlave = isset($params['keepSlave']) ? (bool)$params['keepSlave'] : false; + + parent::__construct($params, $driver, $config, $eventManager); + } + + /** + * Checks if the connection is currently towards the master or not. + * + * @return boolean + */ + public function isConnectedToMaster() + { + return $this->_conn !== null && $this->_conn === $this->connections['master']; + } + + /** + * {@inheritDoc} + */ + public function connect($connectionName = null) + { + $requestedConnectionChange = ($connectionName !== null); + $connectionName = $connectionName ?: 'slave'; + + if ( $connectionName !== 'slave' && $connectionName !== 'master' ) { + throw new \InvalidArgumentException("Invalid option to connect(), only master or slave allowed."); + } + + // If we have a connection open, and this is not an explicit connection + // change request, then abort right here, because we are already done. + // This prevents writes to the slave in case of "keepSlave" option enabled. + if ($this->_conn && !$requestedConnectionChange) { + return false; + } + + $forceMasterAsSlave = false; + + if ($this->getTransactionNestingLevel() > 0) { + $connectionName = 'master'; + $forceMasterAsSlave = true; + } + + if ($this->connections[$connectionName]) { + if ($forceMasterAsSlave) { + $this->connections['slave'] = $this->_conn = $this->connections['master']; + } else { + $this->_conn = $this->connections[$connectionName]; + } + return false; + } + + if ($connectionName === 'master') { + // Set slave connection to master to avoid invalid reads + if ($this->connections['slave'] && ! $this->keepSlave) { + unset($this->connections['slave']); + } + + $this->connections['master'] = $this->_conn = $this->connectTo($connectionName); + + if ( ! $this->keepSlave) { + $this->connections['slave'] = $this->connections['master']; + } + } else { + $this->connections['slave'] = $this->_conn = $this->connectTo($connectionName); + } + + if ($this->_eventManager->hasListeners(Events::postConnect)) { + $eventArgs = new ConnectionEventArgs($this); + $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs); + } + + return true; + } + + /** + * Connects to a specific connection. + * + * @param string $connectionName + * + * @return \Doctrine\DBAL\Driver + */ + protected function connectTo($connectionName) + { + $params = $this->getParams(); + + $driverOptions = isset($params['driverOptions']) ? $params['driverOptions'] : array(); + + $connectionParams = $this->chooseConnectionConfiguration($connectionName, $params); + + $user = isset($connectionParams['user']) ? $connectionParams['user'] : null; + $password = isset($connectionParams['password']) ? $connectionParams['password'] : null; + + return $this->_driver->connect($connectionParams, $user, $password, $driverOptions); + } + + /** + * @param string $connectionName + * @param array $params + * + * @return mixed + */ + protected function chooseConnectionConfiguration($connectionName, $params) + { + if ($connectionName === 'master') { + return $params['master']; + } + + return $params['slaves'][array_rand($params['slaves'])]; + } + + /** + * {@inheritDoc} + */ + public function executeUpdate($query, array $params = array(), array $types = array()) + { + $this->connect('master'); + + return parent::executeUpdate($query, $params, $types); + } + + /** + * {@inheritDoc} + */ + public function beginTransaction() + { + $this->connect('master'); + + return parent::beginTransaction(); + } + + /** + * {@inheritDoc} + */ + public function commit() + { + $this->connect('master'); + + return parent::commit(); + } + + /** + * {@inheritDoc} + */ + public function rollBack() + { + $this->connect('master'); + + return parent::rollBack(); + } + + /** + * {@inheritDoc} + */ + public function delete($tableName, array $identifier, array $types = array()) + { + $this->connect('master'); + + return parent::delete($tableName, $identifier, $types); + } + + /** + * {@inheritDoc} + */ + public function update($tableName, array $data, array $identifier, array $types = array()) + { + $this->connect('master'); + + return parent::update($tableName, $data, $identifier, $types); + } + + /** + * {@inheritDoc} + */ + public function insert($tableName, array $data, array $types = array()) + { + $this->connect('master'); + + return parent::insert($tableName, $data, $types); + } + + /** + * {@inheritDoc} + */ + public function exec($statement) + { + $this->connect('master'); + + return parent::exec($statement); + } + + /** + * {@inheritDoc} + */ + public function createSavepoint($savepoint) + { + $this->connect('master'); + + return parent::createSavepoint($savepoint); + } + + /** + * {@inheritDoc} + */ + public function releaseSavepoint($savepoint) + { + $this->connect('master'); + + return parent::releaseSavepoint($savepoint); + } + + /** + * {@inheritDoc} + */ + public function rollbackSavepoint($savepoint) + { + $this->connect('master'); + + return parent::rollbackSavepoint($savepoint); + } + + /** + * {@inheritDoc} + */ + public function query() + { + $this->connect('master'); + + $args = func_get_args(); + + $logger = $this->getConfiguration()->getSQLLogger(); + if ($logger) { + $logger->startQuery($args[0]); + } + + $statement = call_user_func_array(array($this->_conn, 'query'), $args); + + if ($logger) { + $logger->stopQuery(); + } + + return $statement; + } + + /** + * {@inheritDoc} + */ + public function prepare($statement) + { + $this->connect('master'); + + return parent::prepare($statement); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/DBALException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/DBALException.php new file mode 100644 index 0000000..b101f48 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/DBALException.php @@ -0,0 +1,202 @@ +. + */ + +namespace Doctrine\DBAL; + +class DBALException extends \Exception +{ + /** + * @param string $method + * + * @return \Doctrine\DBAL\DBALException + */ + public static function notSupported($method) + { + return new self("Operation '$method' is not supported by platform."); + } + + /** + * @return \Doctrine\DBAL\DBALException + */ + public static function invalidPlatformSpecified() + { + return new self( + "Invalid 'platform' option specified, need to give an instance of ". + "\Doctrine\DBAL\Platforms\AbstractPlatform."); + } + + /** + * @return \Doctrine\DBAL\DBALException + */ + public static function invalidPdoInstance() + { + return new self( + "The 'pdo' option was used in DriverManager::getConnection() but no ". + "instance of PDO was given." + ); + } + + /** + * @return \Doctrine\DBAL\DBALException + */ + public static function driverRequired() + { + return new self("The options 'driver' or 'driverClass' are mandatory if no PDO ". + "instance is given to DriverManager::getConnection()."); + } + + /** + * @param string $unknownDriverName + * @param array $knownDrivers + * + * @return \Doctrine\DBAL\DBALException + */ + public static function unknownDriver($unknownDriverName, array $knownDrivers) + { + return new self("The given 'driver' ".$unknownDriverName." is unknown, ". + "Doctrine currently supports only the following drivers: ".implode(", ", $knownDrivers)); + } + + /** + * @param \Exception $driverEx + * @param string $sql + * @param array $params + * + * @return \Doctrine\DBAL\DBALException + */ + public static function driverExceptionDuringQuery(\Exception $driverEx, $sql, array $params = array()) + { + $msg = "An exception occurred while executing '".$sql."'"; + if ($params) { + $msg .= " with params " . self::formatParameters($params); + } + $msg .= ":\n\n".$driverEx->getMessage(); + + return new self($msg, 0, $driverEx); + } + + /** + * Returns a human-readable representation of an array of parameters. + * This properly handles binary data by returning a hex representation. + * + * @param array $params + * + * @return string + */ + private static function formatParameters(array $params) + { + return '[' . implode(', ', array_map(function($param) { + $json = @json_encode($param); + + if (! is_string($json) || $json == 'null' && is_string($param)) { + // JSON encoding failed, this is not a UTF-8 string. + return '"\x' . implode('\x', str_split(bin2hex($param), 2)) . '"'; + } + + return $json; + }, $params)) . ']'; + } + + /** + * @param string $wrapperClass + * + * @return \Doctrine\DBAL\DBALException + */ + public static function invalidWrapperClass($wrapperClass) + { + return new self("The given 'wrapperClass' ".$wrapperClass." has to be a ". + "subtype of \Doctrine\DBAL\Connection."); + } + + /** + * @param string $driverClass + * + * @return \Doctrine\DBAL\DBALException + */ + public static function invalidDriverClass($driverClass) + { + return new self("The given 'driverClass' ".$driverClass." has to implement the ". + "\Doctrine\DBAL\Driver interface."); + } + + /** + * @param string $tableName + * + * @return \Doctrine\DBAL\DBALException + */ + public static function invalidTableName($tableName) + { + return new self("Invalid table name specified: ".$tableName); + } + + /** + * @param string $tableName + * + * @return \Doctrine\DBAL\DBALException + */ + public static function noColumnsSpecifiedForTable($tableName) + { + return new self("No columns specified for table ".$tableName); + } + + /** + * @return \Doctrine\DBAL\DBALException + */ + public static function limitOffsetInvalid() + { + return new self("Invalid Offset in Limit Query, it has to be larger or equal to 0."); + } + + /** + * @param string $name + * + * @return \Doctrine\DBAL\DBALException + */ + public static function typeExists($name) + { + return new self('Type '.$name.' already exists.'); + } + + /** + * @param string $name + * + * @return \Doctrine\DBAL\DBALException + */ + public static function unknownColumnType($name) + { + return new self('Unknown column type "'.$name.'" requested. Any Doctrine type that you use has ' . + 'to be registered with \Doctrine\DBAL\Types\Type::addType(). You can get a list of all the ' . + 'known types with \Doctrine\DBAL\Types\Type::getTypesMap(). If this error occurs during database ' . + 'introspection then you might have forgot to register all database types for a Doctrine Type. Use ' . + 'AbstractPlatform#registerDoctrineTypeMapping() or have your custom types implement ' . + 'Type#getMappedDatabaseTypes(). If the type name is empty you might ' . + 'have a problem with the cache or forgot some mapping information.' + ); + } + + /** + * @param string $name + * + * @return \Doctrine\DBAL\DBALException + */ + public static function typeNotFound($name) + { + return new self('Type to be overwritten '.$name.' does not exist.'); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver.php new file mode 100644 index 0000000..21d6064 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver.php @@ -0,0 +1,75 @@ +. + */ + +namespace Doctrine\DBAL; + +/** + * Driver interface. + * Interface that all DBAL drivers must implement. + * + * @since 2.0 + */ +interface Driver +{ + /** + * Attempts to create a connection with the database. + * + * @param array $params All connection parameters passed by the user. + * @param string|null $username The username to use when connecting. + * @param string|null $password The password to use when connecting. + * @param array $driverOptions The driver options to use when connecting. + * + * @return \Doctrine\DBAL\Driver\Connection The database connection. + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()); + + /** + * Gets the DatabasePlatform instance that provides all the metadata about + * the platform this driver connects to. + * + * @return \Doctrine\DBAL\Platforms\AbstractPlatform The database platform. + */ + public function getDatabasePlatform(); + + /** + * Gets the SchemaManager that can be used to inspect and change the underlying + * database schema of the platform this driver connects to. + * + * @param \Doctrine\DBAL\Connection $conn + * + * @return \Doctrine\DBAL\Schema\AbstractSchemaManager + */ + public function getSchemaManager(Connection $conn); + + /** + * Gets the name of the driver. + * + * @return string The name of the driver. + */ + public function getName(); + + /** + * Gets the name of the database connected to for this driver. + * + * @param \Doctrine\DBAL\Connection $conn + * + * @return string The name of the database. + */ + public function getDatabase(Connection $conn); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Connection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Connection.php new file mode 100644 index 0000000..401f217 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Connection.php @@ -0,0 +1,110 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +/** + * Connection interface. + * Driver connections must implement this interface. + * + * This resembles (a subset of) the PDO interface. + * + * @since 2.0 + */ +interface Connection +{ + /** + * Prepares a statement for execution and returns a Statement object. + * + * @param string $prepareString + * + * @return \Doctrine\DBAL\Driver\Statement + */ + function prepare($prepareString); + + /** + * Executes an SQL statement, returning a result set as a Statement object. + * + * @return \Doctrine\DBAL\Driver\Statement + */ + function query(); + + /** + * Quotes a string for use in a query. + * + * @param string $input + * @param integer $type + * + * @return string + */ + function quote($input, $type=\PDO::PARAM_STR); + + /** + * Executes an SQL statement and return the number of affected rows. + * + * @param string $statement + * + * @return integer + */ + function exec($statement); + + /** + * Returns the ID of the last inserted row or sequence value. + * + * @param string|null $name + * + * @return string + */ + function lastInsertId($name = null); + + /** + * Initiates a transaction. + * + * @return boolean TRUE on success or FALSE on failure. + */ + function beginTransaction(); + + /** + * Commits a transaction. + * + * @return boolean TRUE on success or FALSE on failure. + */ + function commit(); + + /** + * Rolls back the current transaction, as initiated by beginTransaction(). + * + * @return boolean TRUE on success or FALSE on failure. + */ + function rollBack(); + + /** + * Returns the error code associated with the last operation on the database handle. + * + * @return string|null The error code, or null if no operation has been run on the database handle. + */ + function errorCode(); + + /** + * Returns extended error information associated with the last operation on the database handle. + * + * @return array + */ + function errorInfo(); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Connection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Connection.php new file mode 100644 index 0000000..2b46c99 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Connection.php @@ -0,0 +1,41 @@ +. + */ + +namespace Doctrine\DBAL\Driver\DrizzlePDOMySql; + +/** + * @author Kim Hemsø Rasmussen + */ +class Connection extends \Doctrine\DBAL\Driver\PDOConnection +{ + /** + * {@inheritdoc} + */ + public function quote($value, $type=\PDO::PARAM_STR) + { + if (\PDO::PARAM_BOOL === $type) { + if ($value) { + return 'true'; + } else { + return 'false'; + } + } + return parent::quote($value, $type); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Driver.php new file mode 100644 index 0000000..376718b --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/DrizzlePDOMySql/Driver.php @@ -0,0 +1,102 @@ +. + */ + +namespace Doctrine\DBAL\Driver\DrizzlePDOMySql; + +/** + * Drizzle driver using PDO MySql. + * + * @author Kim Hemsø Rasmussen + */ +class Driver implements \Doctrine\DBAL\Driver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + $conn = new Connection( + $this->_constructPdoDsn($params), + $username, + $password, + $driverOptions + ); + return $conn; + } + + /** + * Constructs the Drizzle MySql PDO DSN. + * + * @param array $params + * + * @return string The DSN. + */ + private function _constructPdoDsn(array $params) + { + $dsn = 'mysql:'; + if (isset($params['host']) && $params['host'] != '') { + $dsn .= 'host=' . $params['host'] . ';'; + } + if (isset($params['port'])) { + $dsn .= 'port=' . $params['port'] . ';'; + } + if (isset($params['dbname'])) { + $dsn .= 'dbname=' . $params['dbname'] . ';'; + } + if (isset($params['unix_socket'])) { + $dsn .= 'unix_socket=' . $params['unix_socket'] . ';'; + } + + return $dsn; + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new \Doctrine\DBAL\Platforms\DrizzlePlatform(); + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(\Doctrine\DBAL\Connection $conn) + { + return new \Doctrine\DBAL\Schema\DrizzleSchemaManager($conn); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'drizzle_pdo_mysql'; + } + + /** + * {@inheritdoc} + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + + return $params['dbname']; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php new file mode 100644 index 0000000..4571c45 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Connection.php @@ -0,0 +1,154 @@ +. + */ + +namespace Doctrine\DBAL\Driver\IBMDB2; + +class DB2Connection implements \Doctrine\DBAL\Driver\Connection +{ + /** + * @var resource + */ + private $_conn = null; + + /** + * @param array $params + * @param string $username + * @param string $password + * @param array $driverOptions + * + * @throws \Doctrine\DBAL\Driver\IBMDB2\DB2Exception + */ + public function __construct(array $params, $username, $password, $driverOptions = array()) + { + $isPersistant = (isset($params['persistent']) && $params['persistent'] == true); + + if ($isPersistant) { + $this->_conn = db2_pconnect($params['dbname'], $username, $password, $driverOptions); + } else { + $this->_conn = db2_connect($params['dbname'], $username, $password, $driverOptions); + } + if ( ! $this->_conn) { + throw new DB2Exception(db2_conn_errormsg()); + } + } + + /** + * {@inheritdoc} + */ + public function prepare($sql) + { + $stmt = @db2_prepare($this->_conn, $sql); + if ( ! $stmt) { + throw new DB2Exception(db2_stmt_errormsg()); + } + return new DB2Statement($stmt); + } + + /** + * {@inheritdoc} + */ + public function query() + { + $args = func_get_args(); + $sql = $args[0]; + $stmt = $this->prepare($sql); + $stmt->execute(); + return $stmt; + } + + /** + * {@inheritdoc} + */ + public function quote($input, $type=\PDO::PARAM_STR) + { + $input = db2_escape_string($input); + if ($type == \PDO::PARAM_INT ) { + return $input; + } else { + return "'".$input."'"; + } + } + + /** + * {@inheritdoc} + */ + public function exec($statement) + { + $stmt = $this->prepare($statement); + $stmt->execute(); + return $stmt->rowCount(); + } + + /** + * {@inheritdoc} + */ + public function lastInsertId($name = null) + { + return db2_last_insert_id($this->_conn); + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + db2_autocommit($this->_conn, DB2_AUTOCOMMIT_OFF); + } + + /** + * {@inheritdoc} + */ + public function commit() + { + if (!db2_commit($this->_conn)) { + throw new DB2Exception(db2_conn_errormsg($this->_conn)); + } + db2_autocommit($this->_conn, DB2_AUTOCOMMIT_ON); + } + + /** + * {@inheritdoc} + */ + public function rollBack() + { + if (!db2_rollback($this->_conn)) { + throw new DB2Exception(db2_conn_errormsg($this->_conn)); + } + db2_autocommit($this->_conn, DB2_AUTOCOMMIT_ON); + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + return db2_conn_error($this->_conn); + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return array( + 0 => db2_conn_errormsg($this->_conn), + 1 => $this->errorCode(), + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Driver.php new file mode 100644 index 0000000..447b2c5 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Driver.php @@ -0,0 +1,94 @@ +. + */ + +namespace Doctrine\DBAL\Driver\IBMDB2; + +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Connection; + +/** + * IBM DB2 Driver. + * + * @since 2.0 + * @author Benjamin Eberlei + */ +class DB2Driver implements Driver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + if ( ! isset($params['protocol'])) { + $params['protocol'] = 'TCPIP'; + } + + if ($params['host'] !== 'localhost' && $params['host'] != '127.0.0.1') { + // if the host isn't localhost, use extended connection params + $params['dbname'] = 'DRIVER={IBM DB2 ODBC DRIVER}' . + ';DATABASE=' . $params['dbname'] . + ';HOSTNAME=' . $params['host'] . + ';PROTOCOL=' . $params['protocol'] . + ';UID=' . $username . + ';PWD=' . $password .';'; + if (isset($params['port'])) { + $params['dbname'] .= 'PORT=' . $params['port']; + } + + $username = null; + $password = null; + } + + return new DB2Connection($params, $username, $password, $driverOptions); + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new \Doctrine\DBAL\Platforms\DB2Platform; + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(Connection $conn) + { + return new \Doctrine\DBAL\Schema\DB2SchemaManager($conn); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ibm_db2'; + } + + /** + * {@inheritdoc} + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + + return $params['dbname']; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php new file mode 100644 index 0000000..72775cb --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Exception.php @@ -0,0 +1,24 @@ +. + */ + +namespace Doctrine\DBAL\Driver\IBMDB2; + +class DB2Exception extends \Exception +{ +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php new file mode 100644 index 0000000..bf63eac --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php @@ -0,0 +1,234 @@ +. + */ + +namespace Doctrine\DBAL\Driver\IBMDB2; + +use \Doctrine\DBAL\Driver\Statement; + +class DB2Statement implements \IteratorAggregate, Statement +{ + /** + * @var resource + */ + private $_stmt = null; + + /** + * @var array + */ + private $_bindParam = array(); + + /** + * @var integer + */ + private $_defaultFetchMode = \PDO::FETCH_BOTH; + + /** + * DB2_BINARY, DB2_CHAR, DB2_DOUBLE, or DB2_LONG + * + * @var array + */ + static private $_typeMap = array( + \PDO::PARAM_INT => DB2_LONG, + \PDO::PARAM_STR => DB2_CHAR, + ); + + /** + * @param resource $stmt + */ + public function __construct($stmt) + { + $this->_stmt = $stmt; + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = null) + { + return $this->bindParam($param, $value, $type); + } + + /** + * {@inheritdoc} + */ + public function bindParam($column, &$variable, $type = null, $length = null) + { + $this->_bindParam[$column] =& $variable; + + if ($type && isset(self::$_typeMap[$type])) { + $type = self::$_typeMap[$type]; + } else { + $type = DB2_CHAR; + } + + if (!db2_bind_param($this->_stmt, $column, "variable", DB2_PARAM_IN, $type)) { + throw new DB2Exception(db2_stmt_errormsg()); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function closeCursor() + { + if ( ! $this->_stmt) { + return false; + } + + $this->_bindParam = array(); + db2_free_result($this->_stmt); + $ret = db2_free_stmt($this->_stmt); + $this->_stmt = false; + + return $ret; + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + if ( ! $this->_stmt) { + return false; + } + + return db2_num_fields($this->_stmt); + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + return db2_stmt_error(); + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return array( + 0 => db2_stmt_errormsg(), + 1 => db2_stmt_error(), + ); + } + + /** + * {@inheritdoc} + */ + public function execute($params = null) + { + if ( ! $this->_stmt) { + return false; + } + + /*$retval = true; + if ($params !== null) { + $retval = @db2_execute($this->_stmt, $params); + } else { + $retval = @db2_execute($this->_stmt); + }*/ + if ($params === null) { + ksort($this->_bindParam); + $params = array_values($this->_bindParam); + } + $retval = @db2_execute($this->_stmt, $params); + + if ($retval === false) { + throw new DB2Exception(db2_stmt_errormsg()); + } + + return $retval; + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + $this->_defaultFetchMode = $fetchMode; + + return true; + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + $data = $this->fetchAll(); + + return new \ArrayIterator($data); + } + + /** + * {@inheritdoc} + */ + public function fetch($fetchMode = null) + { + $fetchMode = $fetchMode ?: $this->_defaultFetchMode; + switch ($fetchMode) { + case \PDO::FETCH_BOTH: + return db2_fetch_both($this->_stmt); + case \PDO::FETCH_ASSOC: + return db2_fetch_assoc($this->_stmt); + case \PDO::FETCH_NUM: + return db2_fetch_array($this->_stmt); + default: + throw new DB2Exception("Given Fetch-Style " . $fetchMode . " is not supported."); + } + } + + /** + * {@inheritdoc} + */ + public function fetchAll($fetchMode = null) + { + $rows = array(); + while ($row = $this->fetch($fetchMode)) { + $rows[] = $row; + } + + return $rows; + } + + /** + * {@inheritdoc} + */ + public function fetchColumn($columnIndex = 0) + { + $row = $this->fetch(\PDO::FETCH_NUM); + if ($row && isset($row[$columnIndex])) { + return $row[$columnIndex]; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function rowCount() + { + return (@db2_num_rows($this->_stmt))?:0; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/Driver.php new file mode 100644 index 0000000..74d5a1e --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/Driver.php @@ -0,0 +1,70 @@ +. + */ + +namespace Doctrine\DBAL\Driver\Mysqli; + +use Doctrine\DBAL\Driver as DriverInterface; + +/** + * @author Kim Hemsø Rasmussen + */ +class Driver implements DriverInterface +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + return new MysqliConnection($params, $username, $password, $driverOptions); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'mysqli'; + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(\Doctrine\DBAL\Connection $conn) + { + return new \Doctrine\DBAL\Schema\MySqlSchemaManager($conn); + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new \Doctrine\DBAL\Platforms\MySqlPlatform(); + } + + /** + * {@inheritdoc} + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + + return $params['dbname']; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliConnection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliConnection.php new file mode 100644 index 0000000..3ea1c98 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliConnection.php @@ -0,0 +1,154 @@ +. + */ + +namespace Doctrine\DBAL\Driver\Mysqli; + +use Doctrine\DBAL\Driver\Connection as Connection; + +/** + * @author Kim Hemsø Rasmussen + */ +class MysqliConnection implements Connection +{ + /** + * @var \mysqli + */ + private $_conn; + + /** + * @param array $params + * @param string $username + * @param string $password + * @param array $driverOptions + * + * @throws \Doctrine\DBAL\Driver\Mysqli\MysqliException + */ + public function __construct(array $params, $username, $password, array $driverOptions = array()) + { + $port = isset($params['port']) ? $params['port'] : ini_get('mysqli.default_port'); + $socket = isset($params['unix_socket']) ? $params['unix_socket'] : ini_get('mysqli.default_socket'); + + $this->_conn = mysqli_init(); + if ( ! $this->_conn->real_connect($params['host'], $username, $password, $params['dbname'], $port, $socket)) { + throw new MysqliException($this->_conn->connect_error, $this->_conn->connect_errno); + } + + if (isset($params['charset'])) { + $this->_conn->set_charset($params['charset']); + } + } + + /** + * Retrieves mysqli native resource handle. + * + * Could be used if part of your application is not using DBAL. + * + * @return \mysqli + */ + public function getWrappedResourceHandle() + { + return $this->_conn; + } + + /** + * {@inheritdoc} + */ + public function prepare($prepareString) + { + return new MysqliStatement($this->_conn, $prepareString); + } + + /** + * {@inheritdoc} + */ + public function query() + { + $args = func_get_args(); + $sql = $args[0]; + $stmt = $this->prepare($sql); + $stmt->execute(); + return $stmt; + } + + /** + * {@inheritdoc} + */ + public function quote($input, $type=\PDO::PARAM_STR) + { + return "'". $this->_conn->escape_string($input) ."'"; + } + + /** + * {@inheritdoc} + */ + public function exec($statement) + { + $this->_conn->query($statement); + return $this->_conn->affected_rows; + } + + /** + * {@inheritdoc} + */ + public function lastInsertId($name = null) + { + return $this->_conn->insert_id; + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $this->_conn->query('START TRANSACTION'); + return true; + } + + /** + * {@inheritdoc} + */ + public function commit() + { + return $this->_conn->commit(); + } + + /** + * {@inheritdoc}non-PHPdoc) + */ + public function rollBack() + { + return $this->_conn->rollback(); + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + return $this->_conn->errno; + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return $this->_conn->error; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliException.php new file mode 100644 index 0000000..b5583d8 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliException.php @@ -0,0 +1,27 @@ +. + */ + +namespace Doctrine\DBAL\Driver\Mysqli; + +/** + * @author Kim Hemsø Rasmussen + */ +class MysqliException extends \Exception +{ +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php new file mode 100644 index 0000000..1e27cfe --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Mysqli/MysqliStatement.php @@ -0,0 +1,372 @@ +. + */ + +namespace Doctrine\DBAL\Driver\Mysqli; + +use Doctrine\DBAL\Driver\Statement; +use PDO; + +/** + * @author Kim Hemsø Rasmussen + */ +class MysqliStatement implements \IteratorAggregate, Statement +{ + /** + * @var array + */ + protected static $_paramTypeMap = array( + PDO::PARAM_STR => 's', + PDO::PARAM_BOOL => 'i', + PDO::PARAM_NULL => 's', + PDO::PARAM_INT => 'i', + PDO::PARAM_LOB => 's' // TODO Support LOB bigger then max package size. + ); + + /** + * @var \mysqli + */ + protected $_conn; + + /** + * @var \mysqli_stmt + */ + protected $_stmt; + + /** + * @var null|boolean|array + */ + protected $_columnNames; + + /** + * @var null|array + */ + protected $_rowBindedValues; + + /** + * @var array + */ + protected $_bindedValues; + + /** + * Contains ref values for bindValue(). + * + * @var array + */ + protected $_values = array(); + + /** + * @var integer + */ + protected $_defaultFetchMode = PDO::FETCH_BOTH; + + /** + * @param \mysqli $conn + * @param string $prepareString + * + * @throws \Doctrine\DBAL\Driver\Mysqli\MysqliException + */ + public function __construct(\mysqli $conn, $prepareString) + { + $this->_conn = $conn; + $this->_stmt = $conn->prepare($prepareString); + if (false === $this->_stmt) { + throw new MysqliException($this->_conn->error, $this->_conn->errno); + } + + $paramCount = $this->_stmt->param_count; + if (0 < $paramCount) { + // Index 0 is types + // Need to init the string else php think we are trying to access it as a array. + $bindedValues = array(0 => str_repeat('s', $paramCount)); + $null = null; + for ($i = 1; $i < $paramCount; $i++) { + $bindedValues[] =& $null; + } + $this->_bindedValues = $bindedValues; + } + } + + /** + * {@inheritdoc} + */ + public function bindParam($column, &$variable, $type = null, $length = null) + { + if (null === $type) { + $type = 's'; + } else { + if (isset(self::$_paramTypeMap[$type])) { + $type = self::$_paramTypeMap[$type]; + } else { + throw new MysqliException("Unknown type: '{$type}'"); + } + } + + $this->_bindedValues[$column] =& $variable; + $this->_bindedValues[0][$column - 1] = $type; + + return true; + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = null) + { + if (null === $type) { + $type = 's'; + } else { + if (isset(self::$_paramTypeMap[$type])) { + $type = self::$_paramTypeMap[$type]; + } else { + throw new MysqliException("Unknown type: '{$type}'"); + } + } + + $this->_values[$param] = $value; + $this->_bindedValues[$param] =& $this->_values[$param]; + $this->_bindedValues[0][$param - 1] = $type; + + return true; + } + + /** + * {@inheritdoc} + */ + public function execute($params = null) + { + if (null !== $this->_bindedValues) { + if (null !== $params) { + if ( ! $this->_bindValues($params)) { + throw new MysqliException($this->_stmt->error, $this->_stmt->errno); + } + } else { + if (!call_user_func_array(array($this->_stmt, 'bind_param'), $this->_bindedValues)) { + throw new MysqliException($this->_stmt->error, $this->_stmt->errno); + } + } + } + + if ( ! $this->_stmt->execute()) { + throw new MysqliException($this->_stmt->error, $this->_stmt->errno); + } + + if (null === $this->_columnNames) { + $meta = $this->_stmt->result_metadata(); + if (false !== $meta) { + $columnNames = array(); + foreach ($meta->fetch_fields() as $col) { + $columnNames[] = $col->name; + } + $meta->free(); + + $this->_columnNames = $columnNames; + $this->_rowBindedValues = array_fill(0, count($columnNames), NULL); + + $refs = array(); + foreach ($this->_rowBindedValues as $key => &$value) { + $refs[$key] =& $value; + } + + if (!call_user_func_array(array($this->_stmt, 'bind_result'), $refs)) { + throw new MysqliException($this->_stmt->error, $this->_stmt->errno); + } + } else { + $this->_columnNames = false; + } + } + + // We have a result. + if (false !== $this->_columnNames) { + $this->_stmt->store_result(); + } + + return true; + } + + /** + * Binds a array of values to bound parameters. + * + * @param array $values + * + * @return boolean + */ + private function _bindValues($values) + { + $params = array(); + $types = str_repeat('s', count($values)); + $params[0] = $types; + + foreach ($values as &$v) { + $params[] =& $v; + } + + return call_user_func_array(array($this->_stmt, 'bind_param'), $params); + } + + /** + * @return boolean|array + */ + private function _fetch() + { + $ret = $this->_stmt->fetch(); + + if (true === $ret) { + $values = array(); + foreach ($this->_rowBindedValues as $v) { + // Mysqli converts them to a scalar type it can fit in. + $values[] = null === $v ? null : (string)$v; + } + return $values; + } + + return $ret; + } + + /** + * {@inheritdoc} + */ + public function fetch($fetchMode = null) + { + $values = $this->_fetch(); + if (null === $values) { + return null; + } + + if (false === $values) { + throw new MysqliException($this->_stmt->error, $this->_stmt->errno); + } + + $fetchMode = $fetchMode ?: $this->_defaultFetchMode; + + switch ($fetchMode) { + case PDO::FETCH_NUM: + return $values; + + case PDO::FETCH_ASSOC: + return array_combine($this->_columnNames, $values); + + case PDO::FETCH_BOTH: + $ret = array_combine($this->_columnNames, $values); + $ret += $values; + return $ret; + + default: + throw new MysqliException("Unknown fetch type '{$fetchMode}'"); + } + } + + /** + * {@inheritdoc} + */ + public function fetchAll($fetchMode = null) + { + $fetchMode = $fetchMode ?: $this->_defaultFetchMode; + + $rows = array(); + if (PDO::FETCH_COLUMN == $fetchMode) { + while (($row = $this->fetchColumn()) !== false) { + $rows[] = $row; + } + } else { + while (($row = $this->fetch($fetchMode)) !== null) { + $rows[] = $row; + } + } + + return $rows; + } + + /** + * {@inheritdoc} + */ + public function fetchColumn($columnIndex = 0) + { + $row = $this->fetch(PDO::FETCH_NUM); + if (null === $row) { + return false; + } + + return $row[$columnIndex]; + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + return $this->_stmt->errno; + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return $this->_stmt->error; + } + + /** + * {@inheritdoc} + */ + public function closeCursor() + { + $this->_stmt->free_result(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function rowCount() + { + if (false === $this->_columnNames) { + return $this->_stmt->affected_rows; + } + return $this->_stmt->num_rows; + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + return $this->_stmt->field_count; + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + $this->_defaultFetchMode = $fetchMode; + + return true; + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + $data = $this->fetchAll(); + + return new \ArrayIterator($data); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/Driver.php new file mode 100644 index 0000000..5fcc299 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/Driver.php @@ -0,0 +1,116 @@ +. + */ + +namespace Doctrine\DBAL\Driver\OCI8; + +use Doctrine\DBAL\Platforms; + +/** + * A Doctrine DBAL driver for the Oracle OCI8 PHP extensions. + * + * @author Roman Borschel + * @since 2.0 + */ +class Driver implements \Doctrine\DBAL\Driver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + return new OCI8Connection( + $username, + $password, + $this->_constructDsn($params), + isset($params['charset']) ? $params['charset'] : null, + isset($params['sessionMode']) ? $params['sessionMode'] : OCI_DEFAULT, + isset($params['persistent']) ? $params['persistent'] : false + ); + } + + /** + * Constructs the Oracle DSN. + * + * @param array $params + * + * @return string The DSN. + */ + protected function _constructDsn(array $params) + { + $dsn = ''; + if (isset($params['host']) && $params['host'] != '') { + $dsn .= '(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)' . + '(HOST=' . $params['host'] . ')'; + + if (isset($params['port'])) { + $dsn .= '(PORT=' . $params['port'] . ')'; + } else { + $dsn .= '(PORT=1521)'; + } + + if (isset($params['service']) && $params['service'] == true) { + $dsn .= '))(CONNECT_DATA=(SERVICE_NAME=' . $params['dbname'] . '))'; + } else { + $dsn .= '))(CONNECT_DATA=(SID=' . $params['dbname'] . '))'; + } + if (isset($params['pooled']) && $params['pooled'] == true) { + $dsn .= '(SERVER=POOLED)'; + } + $dsn .= ')'; + } else { + $dsn .= $params['dbname']; + } + + return $dsn; + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new \Doctrine\DBAL\Platforms\OraclePlatform(); + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(\Doctrine\DBAL\Connection $conn) + { + return new \Doctrine\DBAL\Schema\OracleSchemaManager($conn); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'oci8'; + } + + /** + * {@inheritdoc} + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + + return $params['user']; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Connection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Connection.php new file mode 100644 index 0000000..6776635 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Connection.php @@ -0,0 +1,202 @@ +. + */ + +namespace Doctrine\DBAL\Driver\OCI8; + +use Doctrine\DBAL\Platforms\OraclePlatform; + +/** + * OCI8 implementation of the Connection interface. + * + * @since 2.0 + */ +class OCI8Connection implements \Doctrine\DBAL\Driver\Connection +{ + /** + * @var resource + */ + protected $dbh; + + /** + * @var integer + */ + protected $executeMode = OCI_COMMIT_ON_SUCCESS; + + /** + * Creates a Connection to an Oracle Database using oci8 extension. + * + * @param string $username + * @param string $password + * @param string $db + * @param string|null $charset + * @param integer $sessionMode + * @param boolean $persistent + * + * @throws OCI8Exception + */ + public function __construct($username, $password, $db, $charset = null, $sessionMode = OCI_DEFAULT, $persistent = false) + { + if (!defined('OCI_NO_AUTO_COMMIT')) { + define('OCI_NO_AUTO_COMMIT', 0); + } + + $this->dbh = $persistent + ? @oci_pconnect($username, $password, $db, $charset, $sessionMode) + : @oci_connect($username, $password, $db, $charset, $sessionMode); + + if ( ! $this->dbh) { + throw OCI8Exception::fromErrorInfo(oci_error()); + } + } + + /** + * {@inheritdoc} + */ + public function prepare($prepareString) + { + return new OCI8Statement($this->dbh, $prepareString, $this); + } + + /** + * {@inheritdoc} + */ + public function query() + { + $args = func_get_args(); + $sql = $args[0]; + //$fetchMode = $args[1]; + $stmt = $this->prepare($sql); + $stmt->execute(); + + return $stmt; + } + + /** + * {@inheritdoc} + */ + public function quote($value, $type=\PDO::PARAM_STR) + { + if (is_int($value) || is_float($value)) { + return $value; + } + $value = str_replace("'", "''", $value); + + return "'" . addcslashes($value, "\000\n\r\\\032") . "'"; + } + + /** + * {@inheritdoc} + */ + public function exec($statement) + { + $stmt = $this->prepare($statement); + $stmt->execute(); + + return $stmt->rowCount(); + } + + /** + * {@inheritdoc} + */ + public function lastInsertId($name = null) + { + if ($name === null) { + return false; + } + + OraclePlatform::assertValidIdentifier($name); + + $sql = 'SELECT ' . $name . '.CURRVAL FROM DUAL'; + $stmt = $this->query($sql); + $result = $stmt->fetch(\PDO::FETCH_ASSOC); + + if ($result === false || !isset($result['CURRVAL'])) { + throw new OCI8Exception("lastInsertId failed: Query was executed but no result was returned."); + } + + return (int) $result['CURRVAL']; + } + + /** + * Returns the current execution mode. + * + * @return integer + */ + public function getExecuteMode() + { + return $this->executeMode; + } + + /** + * {@inheritdoc} + */ + public function beginTransaction() + { + $this->executeMode = OCI_NO_AUTO_COMMIT; + + return true; + } + + /** + * {@inheritdoc} + */ + public function commit() + { + if (!oci_commit($this->dbh)) { + throw OCI8Exception::fromErrorInfo($this->errorInfo()); + } + $this->executeMode = OCI_COMMIT_ON_SUCCESS; + + return true; + } + + /** + * {@inheritdoc} + */ + public function rollBack() + { + if (!oci_rollback($this->dbh)) { + throw OCI8Exception::fromErrorInfo($this->errorInfo()); + } + $this->executeMode = OCI_COMMIT_ON_SUCCESS; + + return true; + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + $error = oci_error($this->dbh); + if ($error !== false) { + $error = $error['code']; + } + + return $error; + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return oci_error($this->dbh); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Exception.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Exception.php new file mode 100644 index 0000000..3399592 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Exception.php @@ -0,0 +1,33 @@ +. + */ + +namespace Doctrine\DBAL\Driver\OCI8; + +class OCI8Exception extends \Exception +{ + /** + * @param array $error + * + * @return \Doctrine\DBAL\Driver\OCI8\OCI8Exception + */ + static public function fromErrorInfo($error) + { + return new self($error['message'], $error['code']); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php new file mode 100644 index 0000000..d13769f --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/OCI8/OCI8Statement.php @@ -0,0 +1,303 @@ +. + */ + +namespace Doctrine\DBAL\Driver\OCI8; + +use PDO; +use IteratorAggregate; +use Doctrine\DBAL\Driver\Statement; + +/** + * The OCI8 implementation of the Statement interface. + * + * @since 2.0 + * @author Roman Borschel + */ +class OCI8Statement implements \IteratorAggregate, Statement +{ + /** + * @var resource + */ + protected $_dbh; + + /** + * @var resource + */ + protected $_sth; + + /** + * @var \Doctrine\DBAL\Driver\OCI8\OCI8Connection + */ + protected $_conn; + + /** + * @var string + */ + protected static $_PARAM = ':param'; + + /** + * @var array + */ + protected static $fetchModeMap = array( + PDO::FETCH_BOTH => OCI_BOTH, + PDO::FETCH_ASSOC => OCI_ASSOC, + PDO::FETCH_NUM => OCI_NUM, + PDO::PARAM_LOB => OCI_B_BLOB, + PDO::FETCH_COLUMN => OCI_NUM, + ); + + /** + * @var integer + */ + protected $_defaultFetchMode = PDO::FETCH_BOTH; + + /** + * @var array + */ + protected $_paramMap = array(); + + /** + * Creates a new OCI8Statement that uses the given connection handle and SQL statement. + * + * @param resource $dbh The connection handle. + * @param string $statement The SQL statement. + * @param \Doctrine\DBAL\Driver\OCI8\OCI8Connection $conn + */ + public function __construct($dbh, $statement, OCI8Connection $conn) + { + list($statement, $paramMap) = self::convertPositionalToNamedPlaceholders($statement); + $this->_sth = oci_parse($dbh, $statement); + $this->_dbh = $dbh; + $this->_paramMap = $paramMap; + $this->_conn = $conn; + } + + /** + * Converts positional (?) into named placeholders (:param). + * + * Oracle does not support positional parameters, hence this method converts all + * positional parameters into artificially named parameters. Note that this conversion + * is not perfect. All question marks (?) in the original statement are treated as + * placeholders and converted to a named parameter. + * + * The algorithm uses a state machine with two possible states: InLiteral and NotInLiteral. + * Question marks inside literal strings are therefore handled correctly by this method. + * This comes at a cost, the whole sql statement has to be looped over. + * + * @todo extract into utility class in Doctrine\DBAL\Util namespace + * @todo review and test for lost spaces. we experienced missing spaces with oci8 in some sql statements. + * + * @param string $statement The SQL statement to convert. + * + * @return string + */ + static public function convertPositionalToNamedPlaceholders($statement) + { + $count = 1; + $inLiteral = false; // a valid query never starts with quotes + $stmtLen = strlen($statement); + $paramMap = array(); + for ($i = 0; $i < $stmtLen; $i++) { + if ($statement[$i] == '?' && !$inLiteral) { + // real positional parameter detected + $paramMap[$count] = ":param$count"; + $len = strlen($paramMap[$count]); + $statement = substr_replace($statement, ":param$count", $i, 1); + $i += $len-1; // jump ahead + $stmtLen = strlen($statement); // adjust statement length + ++$count; + } else if ($statement[$i] == "'" || $statement[$i] == '"') { + $inLiteral = ! $inLiteral; // switch state! + } + } + + return array($statement, $paramMap); + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = null) + { + return $this->bindParam($param, $value, $type, null); + } + + /** + * {@inheritdoc} + */ + public function bindParam($column, &$variable, $type = null, $length = null) + { + $column = isset($this->_paramMap[$column]) ? $this->_paramMap[$column] : $column; + + if ($type == \PDO::PARAM_LOB) { + $lob = oci_new_descriptor($this->_dbh, OCI_D_LOB); + $lob->writeTemporary($variable, OCI_TEMP_BLOB); + + return oci_bind_by_name($this->_sth, $column, $lob, -1, OCI_B_BLOB); + } else if ($length !== null) { + return oci_bind_by_name($this->_sth, $column, $variable, $length); + } + + return oci_bind_by_name($this->_sth, $column, $variable); + } + + /** + * {@inheritdoc} + */ + public function closeCursor() + { + return oci_free_statement($this->_sth); + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + return oci_num_fields($this->_sth); + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + $error = oci_error($this->_sth); + if ($error !== false) { + $error = $error['code']; + } + + return $error; + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return oci_error($this->_sth); + } + + /** + * {@inheritdoc} + */ + public function execute($params = null) + { + if ($params) { + $hasZeroIndex = array_key_exists(0, $params); + foreach ($params as $key => $val) { + if ($hasZeroIndex && is_numeric($key)) { + $this->bindValue($key + 1, $val); + } else { + $this->bindValue($key, $val); + } + } + } + + $ret = @oci_execute($this->_sth, $this->_conn->getExecuteMode()); + if ( ! $ret) { + throw OCI8Exception::fromErrorInfo($this->errorInfo()); + } + + return $ret; + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + $this->_defaultFetchMode = $fetchMode; + + return true; + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + $data = $this->fetchAll(); + + return new \ArrayIterator($data); + } + + /** + * {@inheritdoc} + */ + public function fetch($fetchMode = null) + { + $fetchMode = $fetchMode ?: $this->_defaultFetchMode; + if ( ! isset(self::$fetchModeMap[$fetchMode])) { + throw new \InvalidArgumentException("Invalid fetch style: " . $fetchMode); + } + + return oci_fetch_array($this->_sth, self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | OCI_RETURN_LOBS); + } + + /** + * {@inheritdoc} + */ + public function fetchAll($fetchMode = null) + { + $fetchMode = $fetchMode ?: $this->_defaultFetchMode; + if ( ! isset(self::$fetchModeMap[$fetchMode])) { + throw new \InvalidArgumentException("Invalid fetch style: " . $fetchMode); + } + + $result = array(); + if (self::$fetchModeMap[$fetchMode] === OCI_BOTH) { + while ($row = $this->fetch($fetchMode)) { + $result[] = $row; + } + } else { + $fetchStructure = OCI_FETCHSTATEMENT_BY_ROW; + if ($fetchMode == PDO::FETCH_COLUMN) { + $fetchStructure = OCI_FETCHSTATEMENT_BY_COLUMN; + } + + oci_fetch_all($this->_sth, $result, 0, -1, + self::$fetchModeMap[$fetchMode] | OCI_RETURN_NULLS | $fetchStructure | OCI_RETURN_LOBS); + + if ($fetchMode == PDO::FETCH_COLUMN) { + $result = $result[0]; + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function fetchColumn($columnIndex = 0) + { + $row = oci_fetch_array($this->_sth, OCI_NUM | OCI_RETURN_NULLS | OCI_RETURN_LOBS); + + return isset($row[$columnIndex]) ? $row[$columnIndex] : false; + } + + /** + * {@inheritdoc} + */ + public function rowCount() + { + return oci_num_rows($this->_sth); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php new file mode 100644 index 0000000..b65290e --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php @@ -0,0 +1,44 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +use PDO; + +/** + * PDO implementation of the Connection interface. + * Used by all PDO-based drivers. + * + * @since 2.0 + */ +class PDOConnection extends PDO implements Connection +{ + /** + * @param string $dsn + * @param string|null $user + * @param string|null $password + * @param array|null $options + */ + public function __construct($dsn, $user = null, $password = null, array $options = null) + { + parent::__construct($dsn, $user, $password, $options); + $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('Doctrine\DBAL\Driver\PDOStatement', array())); + $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php new file mode 100644 index 0000000..51e637c --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOIbm/Driver.php @@ -0,0 +1,108 @@ +. + */ + +namespace Doctrine\DBAL\Driver\PDOIbm; + +use Doctrine\DBAL\Connection; + +/** + * Driver for the PDO IBM extension. + * + * @link www.doctrine-project.org + * @since 1.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class Driver implements \Doctrine\DBAL\Driver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + $conn = new \Doctrine\DBAL\Driver\PDOConnection( + $this->_constructPdoDsn($params), + $username, + $password, + $driverOptions + ); + + return $conn; + } + + /** + * Constructs the IBM PDO DSN. + * + * @param array $params + * + * @return string The DSN. + */ + private function _constructPdoDsn(array $params) + { + $dsn = 'ibm:'; + if (isset($params['host'])) { + $dsn .= 'HOSTNAME=' . $params['host'] . ';'; + } + if (isset($params['port'])) { + $dsn .= 'PORT=' . $params['port'] . ';'; + } + $dsn .= 'PROTOCOL=TCPIP;'; + if (isset($params['dbname'])) { + $dsn .= 'DATABASE=' . $params['dbname'] . ';'; + } + + return $dsn; + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new \Doctrine\DBAL\Platforms\DB2Platform; + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(Connection $conn) + { + return new \Doctrine\DBAL\Schema\DB2SchemaManager($conn); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'pdo_ibm'; + } + + /** + * {@inheritdoc} + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + + return $params['dbname']; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php new file mode 100644 index 0000000..1c54e72 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOMySql/Driver.php @@ -0,0 +1,111 @@ +. + */ + +namespace Doctrine\DBAL\Driver\PDOMySql; + +use Doctrine\DBAL\Connection; + +/** + * PDO MySql driver. + * + * @since 2.0 + */ +class Driver implements \Doctrine\DBAL\Driver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + $conn = new \Doctrine\DBAL\Driver\PDOConnection( + $this->_constructPdoDsn($params), + $username, + $password, + $driverOptions + ); + + return $conn; + } + + /** + * Constructs the MySql PDO DSN. + * + * @param array $params + * + * @return string The DSN. + */ + private function _constructPdoDsn(array $params) + { + $dsn = 'mysql:'; + if (isset($params['host']) && $params['host'] != '') { + $dsn .= 'host=' . $params['host'] . ';'; + } + if (isset($params['port'])) { + $dsn .= 'port=' . $params['port'] . ';'; + } + if (isset($params['dbname'])) { + $dsn .= 'dbname=' . $params['dbname'] . ';'; + } + if (isset($params['unix_socket'])) { + $dsn .= 'unix_socket=' . $params['unix_socket'] . ';'; + } + if (isset($params['charset'])) { + $dsn .= 'charset=' . $params['charset'] . ';'; + } + + return $dsn; + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new \Doctrine\DBAL\Platforms\MySqlPlatform(); + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(\Doctrine\DBAL\Connection $conn) + { + return new \Doctrine\DBAL\Schema\MySqlSchemaManager($conn); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'pdo_mysql'; + } + + /** + * {@inheritdoc} + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + + if (isset($params['dbname'])) { + return $params['dbname']; + } + return $conn->query('SELECT DATABASE()')->fetchColumn(); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOOracle/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOOracle/Driver.php new file mode 100644 index 0000000..61c8ba3 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOOracle/Driver.php @@ -0,0 +1,116 @@ +. + */ + +namespace Doctrine\DBAL\Driver\PDOOracle; + +use Doctrine\DBAL\Platforms; + +/** + * PDO Oracle driver. + * + * WARNING: This driver gives us segfaults in our testsuites on CLOB and other + * stuff. PDO Oracle is not maintained by Oracle or anyone in the PHP community, + * which leads us to the recommendation to use the "oci8" driver to connect + * to Oracle instead. + */ +class Driver implements \Doctrine\DBAL\Driver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + return new \Doctrine\DBAL\Driver\PDOConnection( + $this->_constructPdoDsn($params), + $username, + $password, + $driverOptions + ); + } + + /** + * Constructs the Oracle PDO DSN. + * + * @param array $params + * + * @return string The DSN. + */ + private function _constructPdoDsn(array $params) + { + $dsn = 'oci:'; + if (isset($params['host']) && $params['host'] != '') { + $dsn .= 'dbname=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)' . + '(HOST=' . $params['host'] . ')'; + + if (isset($params['port'])) { + $dsn .= '(PORT=' . $params['port'] . ')'; + } else { + $dsn .= '(PORT=1521)'; + } + + if (isset($params['service']) && $params['service'] == true) { + $dsn .= '))(CONNECT_DATA=(SERVICE_NAME=' . $params['dbname'] . ')))'; + } else { + $dsn .= '))(CONNECT_DATA=(SID=' . $params['dbname'] . ')))'; + } + } else { + $dsn .= 'dbname=' . $params['dbname']; + } + + if (isset($params['charset'])) { + $dsn .= ';charset=' . $params['charset']; + } + + return $dsn; + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new \Doctrine\DBAL\Platforms\OraclePlatform(); + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(\Doctrine\DBAL\Connection $conn) + { + return new \Doctrine\DBAL\Schema\OracleSchemaManager($conn); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'pdo_oracle'; + } + + /** + * {@inheritdoc} + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + + return $params['user']; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOPgSql/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOPgSql/Driver.php new file mode 100644 index 0000000..5da59d5 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOPgSql/Driver.php @@ -0,0 +1,103 @@ +. + */ + +namespace Doctrine\DBAL\Driver\PDOPgSql; + +use Doctrine\DBAL\Platforms; + +/** + * Driver that connects through pdo_pgsql. + * + * @since 2.0 + */ +class Driver implements \Doctrine\DBAL\Driver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + return new \Doctrine\DBAL\Driver\PDOConnection( + $this->_constructPdoDsn($params), + $username, + $password, + $driverOptions + ); + } + + /** + * Constructs the Postgres PDO DSN. + * + * @param array $params + * + * @return string The DSN. + */ + private function _constructPdoDsn(array $params) + { + $dsn = 'pgsql:'; + if (isset($params['host']) && $params['host'] != '') { + $dsn .= 'host=' . $params['host'] . ' '; + } + if (isset($params['port']) && $params['port'] != '') { + $dsn .= 'port=' . $params['port'] . ' '; + } + if (isset($params['dbname'])) { + $dsn .= 'dbname=' . $params['dbname'] . ' '; + } + + return $dsn; + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new \Doctrine\DBAL\Platforms\PostgreSqlPlatform(); + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(\Doctrine\DBAL\Connection $conn) + { + return new \Doctrine\DBAL\Schema\PostgreSqlSchemaManager($conn); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'pdo_pgsql'; + } + + /** + * {@inheritdoc} + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + + return (isset($params['dbname'])) + ? $params['dbname'] + : $conn->query('SELECT CURRENT_DATABASE()')->fetchColumn(); + } +} + diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlite/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlite/Driver.php new file mode 100644 index 0000000..56e0346 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlite/Driver.php @@ -0,0 +1,115 @@ +. + */ + +namespace Doctrine\DBAL\Driver\PDOSqlite; + +/** + * The PDO Sqlite driver. + * + * @since 2.0 + */ +class Driver implements \Doctrine\DBAL\Driver +{ + /** + * @var array + */ + protected $_userDefinedFunctions = array( + 'sqrt' => array('callback' => array('Doctrine\DBAL\Platforms\SqlitePlatform', 'udfSqrt'), 'numArgs' => 1), + 'mod' => array('callback' => array('Doctrine\DBAL\Platforms\SqlitePlatform', 'udfMod'), 'numArgs' => 2), + 'locate' => array('callback' => array('Doctrine\DBAL\Platforms\SqlitePlatform', 'udfLocate'), 'numArgs' => -1), + ); + + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + if (isset($driverOptions['userDefinedFunctions'])) { + $this->_userDefinedFunctions = array_merge( + $this->_userDefinedFunctions, $driverOptions['userDefinedFunctions']); + unset($driverOptions['userDefinedFunctions']); + } + + $pdo = new \Doctrine\DBAL\Driver\PDOConnection( + $this->_constructPdoDsn($params), + $username, + $password, + $driverOptions + ); + + foreach ($this->_userDefinedFunctions as $fn => $data) { + $pdo->sqliteCreateFunction($fn, $data['callback'], $data['numArgs']); + } + + return $pdo; + } + + /** + * Constructs the Sqlite PDO DSN. + * + * @param array $params + * + * @return string The DSN. + */ + protected function _constructPdoDsn(array $params) + { + $dsn = 'sqlite:'; + if (isset($params['path'])) { + $dsn .= $params['path']; + } else if (isset($params['memory'])) { + $dsn .= ':memory:'; + } + + return $dsn; + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new \Doctrine\DBAL\Platforms\SqlitePlatform(); + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(\Doctrine\DBAL\Connection $conn) + { + return new \Doctrine\DBAL\Schema\SqliteSchemaManager($conn); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'pdo_sqlite'; + } + + /** + * {@inheritdoc} + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + + return isset($params['path']) ? $params['path'] : null; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Connection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Connection.php new file mode 100644 index 0000000..93314af --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Connection.php @@ -0,0 +1,43 @@ +. + */ + +namespace Doctrine\DBAL\Driver\PDOSqlsrv; + +/** + * Sqlsrv Connection implementation. + * + * @since 2.0 + */ +class Connection extends \Doctrine\DBAL\Driver\PDOConnection implements \Doctrine\DBAL\Driver\Connection +{ + /** + * @override + */ + public function quote($value, $type=\PDO::PARAM_STR) + { + $val = parent::quote($value, $type); + + // Fix for a driver version terminating all values with null byte + if (strpos($val, "\0") !== false) { + $val = substr($val, 0, -1); + } + + return $val; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Driver.php new file mode 100644 index 0000000..4429b44 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOSqlsrv/Driver.php @@ -0,0 +1,105 @@ +. + */ + +namespace Doctrine\DBAL\Driver\PDOSqlsrv; + +/** + * The PDO-based Sqlsrv driver. + * + * @since 2.0 + */ +class Driver implements \Doctrine\DBAL\Driver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + return new Connection( + $this->_constructPdoDsn($params), + $username, + $password, + $driverOptions + ); + } + + /** + * Constructs the Sqlsrv PDO DSN. + * + * @param array $params + * + * @return string The DSN. + */ + private function _constructPdoDsn(array $params) + { + $dsn = 'sqlsrv:server='; + + if (isset($params['host'])) { + $dsn .= $params['host']; + } + + if (isset($params['port']) && !empty($params['port'])) { + $dsn .= ',' . $params['port']; + } + + if (isset($params['dbname'])) {; + $dsn .= ';Database=' . $params['dbname']; + } + + if (isset($params['MultipleActiveResultSets'])) { + $dsn .= '; MultipleActiveResultSets=' . ($params['MultipleActiveResultSets'] ? 'true' : 'false'); + } + + return $dsn; + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new \Doctrine\DBAL\Platforms\SQLServer2008Platform(); + } + /** + * {@inheritdoc} + */ + + public function getSchemaManager(\Doctrine\DBAL\Connection $conn) + { + return new \Doctrine\DBAL\Schema\SQLServerSchemaManager($conn); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'pdo_sqlsrv'; + } + + /** + * {@inheritdoc} + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + + return $params['dbname']; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php new file mode 100644 index 0000000..0aee56d --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php @@ -0,0 +1,56 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +/** + * The PDO implementation of the Statement interface. + * Used by all PDO-based drivers. + * + * @since 2.0 + */ +class PDOStatement extends \PDOStatement implements Statement +{ + /** + * Private constructor. + */ + private function __construct() + { + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + // This thin wrapper is necessary to shield against the weird signature + // of PDOStatement::setFetchMode(): even if the second and third + // parameters are optional, PHP will not let us remove it from this + // declaration. + if ($arg2 === null && $arg3 === null) { + return parent::setFetchMode($fetchMode); + } + + if ($arg3 === null) { + return parent::setFetchMode($fetchMode, $arg2); + } + + return parent::setFetchMode($fetchMode, $arg2, $arg3); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/ResultStatement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/ResultStatement.php new file mode 100644 index 0000000..551a275 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/ResultStatement.php @@ -0,0 +1,88 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +/** + * Interface for the reading part of a prepare statement only. + * + * @author Benjamin Eberlei + */ +interface ResultStatement extends \Traversable +{ + /** + * Closes the cursor, enabling the statement to be executed again. + * + * @return boolean TRUE on success or FALSE on failure. + */ + public function closeCursor(); + + /** + * Returns the number of columns in the result set + * + * @return integer The number of columns in the result set represented + * by the PDOStatement object. If there is no result set, + * this method should return 0. + */ + public function columnCount(); + + /** + * Sets the fetch mode to use while iterating this statement. + * + * @param integer $fetchMode + * @param mixed $arg2 + * @param mixed $arg3 + * + * @return boolean + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null); + + /** + * @see Query::HYDRATE_* constants + * + * @param integer|null $fetchMode Controls how the next row will be returned to the caller. + * This value must be one of the Query::HYDRATE_* constants, + * defaulting to Query::HYDRATE_BOTH + * + * @return mixed + */ + public function fetch($fetchMode = null); + + /** + * Returns an array containing all of the result set rows. + * + * @param integer|null $fetchMode Controls how the next row will be returned to the caller. + * This value must be one of the Query::HYDRATE_* constants, + * defaulting to Query::HYDRATE_BOTH + * + * @return array + */ + public function fetchAll($fetchMode = null); + + /** + * Returns a single column from the next row of a result set or FALSE if there are no more rows. + * + * @param integer $columnIndex 0-indexed number of the column you wish to retrieve from the row. + * If no value is supplied, PDOStatement->fetchColumn() + * fetches the first column. + * + * @return string|boolean A single column in the next row of a result set, or FALSE if there are no more rows. + */ + public function fetchColumn($columnIndex = 0); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/Driver.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/Driver.php new file mode 100644 index 0000000..cb6297c --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/Driver.php @@ -0,0 +1,86 @@ +. + */ + +namespace Doctrine\DBAL\Driver\SQLSrv; + +/** + * Driver for ext/sqlsrv. + */ +class Driver implements \Doctrine\DBAL\Driver +{ + /** + * {@inheritdoc} + */ + public function connect(array $params, $username = null, $password = null, array $driverOptions = array()) + { + if (!isset($params['host'])) { + throw new SQLSrvException("Missing 'host' in configuration for sqlsrv driver."); + } + if (!isset($params['dbname'])) { + throw new SQLSrvException("Missing 'dbname' in configuration for sqlsrv driver."); + } + + $serverName = $params['host']; + if (isset($params['port'])) { + $serverName .= ', ' . $params['port']; + } + $driverOptions['Database'] = $params['dbname']; + $driverOptions['UID'] = $username; + $driverOptions['PWD'] = $password; + + if (!isset($driverOptions['ReturnDatesAsStrings'])) { + $driverOptions['ReturnDatesAsStrings'] = 1; + } + + return new SQLSrvConnection($serverName, $driverOptions); + } + + /** + * {@inheritdoc} + */ + public function getDatabasePlatform() + { + return new \Doctrine\DBAL\Platforms\SQLServer2008Platform(); + } + + /** + * {@inheritdoc} + */ + public function getSchemaManager(\Doctrine\DBAL\Connection $conn) + { + return new \Doctrine\DBAL\Schema\SQLServerSchemaManager($conn); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'sqlsrv'; + } + + /** + * {@inheritdoc} + */ + public function getDatabase(\Doctrine\DBAL\Connection $conn) + { + $params = $conn->getParams(); + return $params['dbname']; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/LastInsertId.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/LastInsertId.php new file mode 100644 index 0000000..67c7bfa --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/LastInsertId.php @@ -0,0 +1,50 @@ +. + */ + +namespace Doctrine\DBAL\Driver\SQLSrv; + +/** + * Last Id Data Container. + * + * @since 2.3 + * @author Benjamin Eberlei + */ +class LastInsertId +{ + /** + * @var integer + */ + private $id; + + /** + * @param integer $id + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * @return integer + */ + public function getId() + { + return $this->id; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvConnection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvConnection.php new file mode 100644 index 0000000..948c27f --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvConnection.php @@ -0,0 +1,168 @@ +. + */ + +namespace Doctrine\DBAL\Driver\SQLSrv; + +/** + * SQL Server implementation for the Connection interface. + * + * @since 2.3 + * @author Benjamin Eberlei + */ +class SQLSrvConnection implements \Doctrine\DBAL\Driver\Connection +{ + /** + * @var resource + */ + protected $conn; + + /** + * @var \Doctrine\DBAL\Driver\SQLSrv\LastInsertId + */ + protected $lastInsertId; + + /** + * @param string $serverName + * @param array $connectionOptions + * + * @throws \Doctrine\DBAL\Driver\SQLSrv\SQLSrvException + */ + public function __construct($serverName, $connectionOptions) + { + $this->conn = sqlsrv_connect($serverName, $connectionOptions); + if ( ! $this->conn) { + throw SQLSrvException::fromSqlSrvErrors(); + } + $this->lastInsertId = new LastInsertId(); + } + + /** + * {@inheritDoc} + */ + public function prepare($sql) + { + return new SQLSrvStatement($this->conn, $sql, $this->lastInsertId); + } + + /** + * {@inheritDoc} + */ + public function query() + { + $args = func_get_args(); + $sql = $args[0]; + $stmt = $this->prepare($sql); + $stmt->execute(); + + return $stmt; + } + + /** + * {@inheritDoc} + * @license New BSD, code from Zend Framework + */ + public function quote($value, $type=\PDO::PARAM_STR) + { + if (is_int($value)) { + return $value; + } else if (is_float($value)) { + return sprintf('%F', $value); + } + + return "'" . str_replace("'", "''", $value) . "'"; + } + + /** + * {@inheritDoc} + */ + public function exec($statement) + { + $stmt = $this->prepare($statement); + $stmt->execute(); + + return $stmt->rowCount(); + } + + /** + * {@inheritDoc} + */ + public function lastInsertId($name = null) + { + if ($name !== null) { + $sql = "SELECT IDENT_CURRENT(".$this->quote($name).") AS LastInsertId"; + $stmt = $this->prepare($sql); + $stmt->execute(); + + return $stmt->fetchColumn(); + } + + return $this->lastInsertId->getId(); + } + + /** + * {@inheritDoc} + */ + public function beginTransaction() + { + if ( ! sqlsrv_begin_transaction($this->conn)) { + throw SQLSrvException::fromSqlSrvErrors(); + } + } + + /** + * {@inheritDoc} + */ + public function commit() + { + if ( ! sqlsrv_commit($this->conn)) { + throw SQLSrvException::fromSqlSrvErrors(); + } + } + + /** + * {@inheritDoc} + */ + public function rollBack() + { + if ( ! sqlsrv_rollback($this->conn)) { + throw SQLSrvException::fromSqlSrvErrors(); + } + } + + /** + * {@inheritDoc} + */ + public function errorCode() + { + $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS); + if ($errors) { + return $errors[0]['code']; + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function errorInfo() + { + return sqlsrv_errors(SQLSRV_ERR_ERRORS); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvException.php new file mode 100644 index 0000000..3631e65 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvException.php @@ -0,0 +1,42 @@ +. + */ + +namespace Doctrine\DBAL\Driver\SQLSrv; + +class SQLSrvException extends \Doctrine\DBAL\DBALException +{ + /** + * Helper method to turn sql server errors into exception. + * + * @return \Doctrine\DBAL\Driver\SQLSrv\SQLSrvException + */ + static public function fromSqlSrvErrors() + { + $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS); + $message = ""; + foreach ($errors as $error) { + $message .= "SQLSTATE [".$error['SQLSTATE'].", ".$error['code']."]: ". $error['message']."\n"; + } + if ( ! $message) { + $message = "SQL Server error occurred but no error message was retrieved from driver."; + } + + return new self(rtrim($message)); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php new file mode 100644 index 0000000..fc11453 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php @@ -0,0 +1,278 @@ +. + */ + +namespace Doctrine\DBAL\Driver\SQLSrv; + +use PDO; +use IteratorAggregate; +use Doctrine\DBAL\Driver\Statement; + +/** + * SQL Server Statement. + * + * @since 2.3 + * @author Benjamin Eberlei + */ +class SQLSrvStatement implements IteratorAggregate, Statement +{ + /** + * The SQLSRV Resource. + * + * @var resource + */ + private $conn; + + /** + * The SQL statement to execute. + * + * @var string + */ + private $sql; + + /** + * The SQLSRV statement resource. + * + * @var resource + */ + private $stmt; + + /** + * Parameters to bind. + * + * @var array + */ + private $params = array(); + + /** + * Translations. + * + * @var array + */ + private static $fetchMap = array( + PDO::FETCH_BOTH => SQLSRV_FETCH_BOTH, + PDO::FETCH_ASSOC => SQLSRV_FETCH_ASSOC, + PDO::FETCH_NUM => SQLSRV_FETCH_NUMERIC, + ); + + /** + * The fetch style. + * + * @param integer + */ + private $defaultFetchMode = PDO::FETCH_BOTH; + + /** + * The last insert ID. + * + * @var \Doctrine\DBAL\Driver\SQLSrv\LastInsertId|null + */ + private $lastInsertId; + + /** + * Append to any INSERT query to retrieve the last insert id. + * + * @var string + */ + const LAST_INSERT_ID_SQL = ';SELECT SCOPE_IDENTITY() AS LastInsertId;'; + + /** + * @param resource $conn + * @param string $sql + * @param integer|null $lastInsertId + */ + public function __construct($conn, $sql, $lastInsertId = null) + { + $this->conn = $conn; + $this->sql = $sql; + + if (stripos($sql, 'INSERT INTO ') === 0) { + $this->sql .= self::LAST_INSERT_ID_SQL; + $this->lastInsertId = $lastInsertId; + } + } + + /** + * {@inheritdoc} + */ + public function bindValue($param, $value, $type = null) + { + return $this->bindParam($param, $value, $type,null); + } + + /** + * {@inheritdoc} + */ + public function bindParam($column, &$variable, $type = null, $length = null) + { + if (!is_numeric($column)) { + throw new SQLSrvException("sqlsrv does not support named parameters to queries, use question mark (?) placeholders instead."); + } + + if ($type === \PDO::PARAM_LOB) { + $this->params[$column-1] = array($variable, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY), SQLSRV_SQLTYPE_VARBINARY('max')); + } else { + $this->params[$column-1] = $variable; + } + } + + /** + * {@inheritdoc} + */ + public function closeCursor() + { + if ($this->stmt) { + sqlsrv_free_stmt($this->stmt); + } + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + return sqlsrv_num_fields($this->stmt); + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + $errors = sqlsrv_errors(SQLSRV_ERR_ERRORS); + if ($errors) { + return $errors[0]['code']; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return sqlsrv_errors(SQLSRV_ERR_ERRORS); + } + + /** + * {@inheritdoc} + */ + public function execute($params = null) + { + if ($params) { + $hasZeroIndex = array_key_exists(0, $params); + foreach ($params as $key => $val) { + $key = ($hasZeroIndex && is_numeric($key)) ? $key + 1 : $key; + $this->bindValue($key, $val); + } + } + + $this->stmt = sqlsrv_query($this->conn, $this->sql, $this->params); + if ( ! $this->stmt) { + throw SQLSrvException::fromSqlSrvErrors(); + } + + if ($this->lastInsertId) { + sqlsrv_next_result($this->stmt); + sqlsrv_fetch($this->stmt); + $this->lastInsertId->setId( sqlsrv_get_field($this->stmt, 0) ); + } + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + $this->defaultFetchMode = $fetchMode; + + return true; + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + $data = $this->fetchAll(); + + return new \ArrayIterator($data); + } + + /** + * {@inheritdoc} + */ + public function fetch($fetchMode = null) + { + $fetchMode = $fetchMode ?: $this->defaultFetchMode; + if (isset(self::$fetchMap[$fetchMode])) { + return sqlsrv_fetch_array($this->stmt, self::$fetchMap[$fetchMode]); + } else if ($fetchMode == PDO::FETCH_OBJ || $fetchMode == PDO::FETCH_CLASS) { + $className = null; + $ctorArgs = null; + if (func_num_args() >= 2) { + $args = func_get_args(); + $className = $args[1]; + $ctorArgs = (isset($args[2])) ? $args[2] : array(); + } + return sqlsrv_fetch_object($this->stmt, $className, $ctorArgs); + } + + throw new SQLSrvException("Fetch mode is not supported!"); + } + + /** + * {@inheritdoc} + */ + public function fetchAll($fetchMode = null) + { + $className = null; + $ctorArgs = null; + if (func_num_args() >= 2) { + $args = func_get_args(); + $className = $args[1]; + $ctorArgs = (isset($args[2])) ? $args[2] : array(); + } + + $rows = array(); + while ($row = $this->fetch($fetchMode, $className, $ctorArgs)) { + $rows[] = $row; + } + + return $rows; + } + + /** + * {@inheritdoc} + */ + public function fetchColumn($columnIndex = 0) + { + $row = $this->fetch(PDO::FETCH_NUM); + + return $row[$columnIndex]; + } + + /** + * {@inheritdoc} + */ + public function rowCount() + { + return sqlsrv_rows_affected($this->stmt); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Statement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Statement.php new file mode 100644 index 0000000..f545cd1 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/Statement.php @@ -0,0 +1,121 @@ +. + */ + +namespace Doctrine\DBAL\Driver; + +/** + * Statement interface. + * Drivers must implement this interface. + * + * This resembles (a subset of) the PDOStatement interface. + * + * @author Konsta Vesterinen + * @author Roman Borschel + * @link www.doctrine-project.org + * @since 2.0 + */ +interface Statement extends ResultStatement +{ + /** + * Binds a value to a corresponding named or positional + * placeholder in the SQL statement that was used to prepare the statement. + * + * @param mixed $param Parameter identifier. For a prepared statement using named placeholders, + * this will be a parameter name of the form :name. For a prepared statement + * using question mark placeholders, this will be the 1-indexed position of the parameter. + * @param mixed $value The value to bind to the parameter. + * @param integer $type Explicit data type for the parameter using the PDO::PARAM_* constants. + * + * @return boolean TRUE on success or FALSE on failure. + */ + function bindValue($param, $value, $type = null); + + /** + * Binds a PHP variable to a corresponding named or question mark placeholder in the + * SQL statement that was use to prepare the statement. Unlike PDOStatement->bindValue(), + * the variable is bound as a reference and will only be evaluated at the time + * that PDOStatement->execute() is called. + * + * Most parameters are input parameters, that is, parameters that are + * used in a read-only fashion to build up the query. Some drivers support the invocation + * of stored procedures that return data as output parameters, and some also as input/output + * parameters that both send in data and are updated to receive it. + * + * @param mixed $column Parameter identifier. For a prepared statement using named placeholders, + * this will be a parameter name of the form :name. For a prepared statement using + * question mark placeholders, this will be the 1-indexed position of the parameter. + * @param mixed $variable Name of the PHP variable to bind to the SQL statement parameter. + * @param integer|null $type Explicit data type for the parameter using the PDO::PARAM_* constants. To return + * an INOUT parameter from a stored procedure, use the bitwise OR operator to set the + * PDO::PARAM_INPUT_OUTPUT bits for the data_type parameter. + * @param integer|null $length You must specify maxlength when using an OUT bind + * so that PHP allocates enough memory to hold the returned value. + * + * @return boolean TRUE on success or FALSE on failure. + */ + function bindParam($column, &$variable, $type = null, $length = null); + + /** + * Fetches the SQLSTATE associated with the last operation on the statement handle. + * + * @see Doctrine_Adapter_Interface::errorCode() + * + * @return string The error code string. + */ + function errorCode(); + + /** + * Fetches extended error information associated with the last operation on the statement handle. + * + * @see Doctrine_Adapter_Interface::errorInfo() + * + * @return array The error info array. + */ + function errorInfo(); + + /** + * Executes a prepared statement + * + * If the prepared statement included parameter markers, you must either: + * call PDOStatement->bindParam() to bind PHP variables to the parameter markers: + * bound variables pass their value as input and receive the output value, + * if any, of their associated parameter markers or pass an array of input-only + * parameter values. + * + * + * @param array|null $params An array of values with as many elements as there are + * bound parameters in the SQL statement being executed. + * + * @return boolean TRUE on success or FALSE on failure. + */ + function execute($params = null); + + /** + * Returns the number of rows affected by the last DELETE, INSERT, or UPDATE statement + * executed by the corresponding object. + * + * If the last SQL statement executed by the associated Statement object was a SELECT statement, + * some databases may return the number of rows returned by that statement. However, + * this behaviour is not guaranteed for all databases and should not be + * relied on for portable applications. + * + * @return integer The number of rows. + */ + function rowCount(); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/DriverManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/DriverManager.php new file mode 100644 index 0000000..ebad358 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/DriverManager.php @@ -0,0 +1,197 @@ +. + */ + +namespace Doctrine\DBAL; + +use Doctrine\Common\EventManager; + +/** + * Factory for creating Doctrine\DBAL\Connection instances. + * + * @author Roman Borschel + * @since 2.0 + */ +final class DriverManager +{ + /** + * List of supported drivers and their mappings to the driver classes. + * + * To add your own driver use the 'driverClass' parameter to + * {@link DriverManager::getConnection()}. + * + * @var array + */ + private static $_driverMap = array( + 'pdo_mysql' => 'Doctrine\DBAL\Driver\PDOMySql\Driver', + 'pdo_sqlite' => 'Doctrine\DBAL\Driver\PDOSqlite\Driver', + 'pdo_pgsql' => 'Doctrine\DBAL\Driver\PDOPgSql\Driver', + 'pdo_oci' => 'Doctrine\DBAL\Driver\PDOOracle\Driver', + 'oci8' => 'Doctrine\DBAL\Driver\OCI8\Driver', + 'ibm_db2' => 'Doctrine\DBAL\Driver\IBMDB2\DB2Driver', + 'pdo_ibm' => 'Doctrine\DBAL\Driver\PDOIbm\Driver', + 'pdo_sqlsrv' => 'Doctrine\DBAL\Driver\PDOSqlsrv\Driver', + 'mysqli' => 'Doctrine\DBAL\Driver\Mysqli\Driver', + 'drizzle_pdo_mysql' => 'Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver', + 'sqlsrv' => 'Doctrine\DBAL\Driver\SQLSrv\Driver', + ); + + /** + * Private constructor. This class cannot be instantiated. + */ + private function __construct() + { + } + + /** + * Creates a connection object based on the specified parameters. + * This method returns a Doctrine\DBAL\Connection which wraps the underlying + * driver connection. + * + * $params must contain at least one of the following. + * + * Either 'driver' with one of the following values: + * + * pdo_mysql + * pdo_sqlite + * pdo_pgsql + * pdo_oci (unstable) + * pdo_sqlsrv + * pdo_ibm (unstable) + * pdo_sqlsrv + * mysqli + * sqlsrv + * ibm_db2 (unstable) + * drizzle_pdo_mysql + * + * OR 'driverClass' that contains the full class name (with namespace) of the + * driver class to instantiate. + * + * Other (optional) parameters: + * + * user (string): + * The username to use when connecting. + * + * password (string): + * The password to use when connecting. + * + * driverOptions (array): + * Any additional driver-specific options for the driver. These are just passed + * through to the driver. + * + * pdo: + * You can pass an existing PDO instance through this parameter. The PDO + * instance will be wrapped in a Doctrine\DBAL\Connection. + * + * wrapperClass: + * You may specify a custom wrapper class through the 'wrapperClass' + * parameter but this class MUST inherit from Doctrine\DBAL\Connection. + * + * driverClass: + * The driver class to use. + * + * @param array $params The parameters. + * @param \Doctrine\DBAL\Configuration|null $config The configuration to use. + * @param \Doctrine\Common\EventManager|null $eventManager The event manager to use. + * + * @return \Doctrine\DBAL\Connection + * + * @throws \Doctrine\DBAL\DBALException + */ + public static function getConnection( + array $params, + Configuration $config = null, + EventManager $eventManager = null) + { + // create default config and event manager, if not set + if ( ! $config) { + $config = new Configuration(); + } + if ( ! $eventManager) { + $eventManager = new EventManager(); + } + + // check for existing pdo object + if (isset($params['pdo']) && ! $params['pdo'] instanceof \PDO) { + throw DBALException::invalidPdoInstance(); + } else if (isset($params['pdo'])) { + $params['pdo']->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $params['driver'] = 'pdo_' . $params['pdo']->getAttribute(\PDO::ATTR_DRIVER_NAME); + } else { + self::_checkParams($params); + } + if (isset($params['driverClass'])) { + $className = $params['driverClass']; + } else { + $className = self::$_driverMap[$params['driver']]; + } + + $driver = new $className(); + + $wrapperClass = 'Doctrine\DBAL\Connection'; + if (isset($params['wrapperClass'])) { + if (is_subclass_of($params['wrapperClass'], $wrapperClass)) { + $wrapperClass = $params['wrapperClass']; + } else { + throw DBALException::invalidWrapperClass($params['wrapperClass']); + } + } + + return new $wrapperClass($params, $driver, $config, $eventManager); + } + + /** + * Returns the list of supported drivers. + * + * @return array + */ + public static function getAvailableDrivers() + { + return array_keys(self::$_driverMap); + } + + /** + * Checks the list of parameters. + * + * @param array $params The list of parameters. + * + * @return void + * + * @throws \Doctrine\DBAL\DBALException + */ + private static function _checkParams(array $params) + { + // check existence of mandatory parameters + + // driver + if ( ! isset($params['driver']) && ! isset($params['driverClass'])) { + throw DBALException::driverRequired(); + } + + // check validity of parameters + + // driver + if ( isset($params['driver']) && ! isset(self::$_driverMap[$params['driver']])) { + throw DBALException::unknownDriver($params['driver'], array_keys(self::$_driverMap)); + } + + if (isset($params['driverClass']) && ! in_array('Doctrine\DBAL\Driver', class_implements($params['driverClass'], true))) { + throw DBALException::invalidDriverClass($params['driverClass']); + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/ConnectionEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/ConnectionEventArgs.php new file mode 100644 index 0000000..fd82638 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/ConnectionEventArgs.php @@ -0,0 +1,78 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\Common\EventArgs; +use Doctrine\DBAL\Connection; + +/** + * Event Arguments used when a Driver connection is established inside Doctrine\DBAL\Connection. + * + * @link www.doctrine-project.org + * @since 1.0 + * @author Benjamin Eberlei + */ +class ConnectionEventArgs extends EventArgs +{ + /** + * @var \Doctrine\DBAL\Connection + */ + private $_connection; + + /** + * @param \Doctrine\DBAL\Connection $connection + */ + public function __construct(Connection $connection) + { + $this->_connection = $connection; + } + + /** + * @return \Doctrine\DBAL\Connection + */ + public function getConnection() + { + return $this->_connection; + } + + /** + * @return \Doctrine\DBAL\Driver + */ + public function getDriver() + { + return $this->_connection->getDriver(); + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getDatabasePlatform() + { + return $this->_connection->getDatabasePlatform(); + } + + /** + * @return \Doctrine\DBAL\Schema\AbstractSchemaManager + */ + public function getSchemaManager() + { + return $this->_connection->getSchemaManager(); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/MysqlSessionInit.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/MysqlSessionInit.php new file mode 100644 index 0000000..bd362c4 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/MysqlSessionInit.php @@ -0,0 +1,80 @@ +. + */ + +namespace Doctrine\DBAL\Event\Listeners; + +use Doctrine\DBAL\Event\ConnectionEventArgs; +use Doctrine\DBAL\Events; +use Doctrine\Common\EventSubscriber; + +/** + * MySQL Session Init Event Subscriber which allows to set the Client Encoding of the Connection. + * + * @link www.doctrine-project.org + * @since 1.0 + * @author Benjamin Eberlei + * @deprecated Use "charset" option to PDO MySQL Connection instead. + */ +class MysqlSessionInit implements EventSubscriber +{ + /** + * The charset. + * + * @var string + */ + private $_charset; + + /** + * The collation, or FALSE if no collation. + * + * @var string|boolean + */ + private $_collation; + + /** + * Configure Charset and Collation options of MySQL Client for each Connection. + * + * @param string $charset The charset. + * @param string|boolean $collation The collation, or FALSE if no collation. + */ + public function __construct($charset = 'utf8', $collation = false) + { + $this->_charset = $charset; + $this->_collation = $collation; + } + + /** + * @param \Doctrine\DBAL\Event\ConnectionEventArgs $args + * + * @return void + */ + public function postConnect(ConnectionEventArgs $args) + { + $collation = ($this->_collation) ? " COLLATE ".$this->_collation : ""; + $args->getConnection()->executeUpdate("SET NAMES ".$this->_charset . $collation); + } + + /** + * {@inheritdoc} + */ + public function getSubscribedEvents() + { + return array(Events::postConnect); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/OracleSessionInit.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/OracleSessionInit.php new file mode 100644 index 0000000..e19540e --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/OracleSessionInit.php @@ -0,0 +1,86 @@ +. + */ + +namespace Doctrine\DBAL\Event\Listeners; + +use Doctrine\DBAL\Event\ConnectionEventArgs; +use Doctrine\DBAL\Events; +use Doctrine\Common\EventSubscriber; + +/** + * Should be used when Oracle Server default environment does not match the Doctrine requirements. + * + * The following environment variables are required for the Doctrine default date format: + * + * NLS_TIME_FORMAT="HH24:MI:SS" + * NLS_DATE_FORMAT="YYYY-MM-DD HH24:MI:SS" + * NLS_TIMESTAMP_FORMAT="YYYY-MM-DD HH24:MI:SS" + * NLS_TIMESTAMP_TZ_FORMAT="YYYY-MM-DD HH24:MI:SS TZH:TZM" + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class OracleSessionInit implements EventSubscriber +{ + /** + * @var array + */ + protected $_defaultSessionVars = array( + 'NLS_TIME_FORMAT' => "HH24:MI:SS", + 'NLS_DATE_FORMAT' => "YYYY-MM-DD HH24:MI:SS", + 'NLS_TIMESTAMP_FORMAT' => "YYYY-MM-DD HH24:MI:SS", + 'NLS_TIMESTAMP_TZ_FORMAT' => "YYYY-MM-DD HH24:MI:SS TZH:TZM", + 'NLS_NUMERIC_CHARACTERS' => ".,", + ); + + /** + * @param array $oracleSessionVars + */ + public function __construct(array $oracleSessionVars = array()) + { + $this->_defaultSessionVars = array_merge($this->_defaultSessionVars, $oracleSessionVars); + } + + /** + * @param \Doctrine\DBAL\Event\ConnectionEventArgs $args + * + * @return void + */ + public function postConnect(ConnectionEventArgs $args) + { + if (count($this->_defaultSessionVars)) { + array_change_key_case($this->_defaultSessionVars, \CASE_UPPER); + $vars = array(); + foreach ($this->_defaultSessionVars as $option => $value) { + $vars[] = $option." = '".$value."'"; + } + $sql = "ALTER SESSION SET ".implode(" ", $vars); + $args->getConnection()->executeUpdate($sql); + } + } + + /** + * {@inheritdoc} + */ + public function getSubscribedEvents() + { + return array(Events::postConnect); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/SQLSessionInit.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/SQLSessionInit.php new file mode 100644 index 0000000..cfa4299 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/Listeners/SQLSessionInit.php @@ -0,0 +1,66 @@ +. + */ + +namespace Doctrine\DBAL\Event\Listeners; + +use Doctrine\DBAL\Event\ConnectionEventArgs; +use Doctrine\DBAL\Events; +use Doctrine\Common\EventSubscriber; + +/** + * Session init listener for executing a single SQL statement right after a connection is opened. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Benjamin Eberlei + */ +class SQLSessionInit implements EventSubscriber +{ + /** + * @var string + */ + protected $sql; + + /** + * @param string $sql + */ + public function __construct($sql) + { + $this->sql = $sql; + } + + /** + * @param \Doctrine\DBAL\Event\ConnectionEventArgs $args + * + * @return void + */ + public function postConnect(ConnectionEventArgs $args) + { + $conn = $args->getConnection(); + $conn->exec($this->sql); + } + + /** + * {@inheritdoc} + */ + public function getSubscribedEvents() + { + return array(Events::postConnect); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableAddColumnEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableAddColumnEventArgs.php new file mode 100644 index 0000000..c4477ad --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableAddColumnEventArgs.php @@ -0,0 +1,114 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\TableDiff; + +/** + * Event Arguments used when SQL queries for adding table columns are generated inside Doctrine\DBAL\Platform\*Platform. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaAlterTableAddColumnEventArgs extends SchemaEventArgs +{ + /** + * @var \Doctrine\DBAL\Schema\Column + */ + private $_column; + + /** + * @var \Doctrine\DBAL\Schema\TableDiff + */ + private $_tableDiff; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $_platform; + + /** + * @var array + */ + private $_sql = array(); + + /** + * @param \Doctrine\DBAL\Schema\Column $column + * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + public function __construct(Column $column, TableDiff $tableDiff, AbstractPlatform $platform) + { + $this->_column = $column; + $this->_tableDiff = $tableDiff; + $this->_platform = $platform; + } + + /** + * @return \Doctrine\DBAL\Schema\Column + */ + public function getColumn() + { + return $this->_column; + } + + /** + * @return \Doctrine\DBAL\Schema\TableDiff + */ + public function getTableDiff() + { + return $this->_tableDiff; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getPlatform() + { + return $this->_platform; + } + + /** + * @param string|array $sql + * + * @return \Doctrine\DBAL\Event\SchemaAlterTableAddColumnEventArgs + */ + public function addSql($sql) + { + if (is_array($sql)) { + $this->_sql = array_merge($this->_sql, $sql); + } else { + $this->_sql[] = $sql; + } + + return $this; + } + + /** + * @return array + */ + public function getSql() + { + return $this->_sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableChangeColumnEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableChangeColumnEventArgs.php new file mode 100644 index 0000000..7249b63 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableChangeColumnEventArgs.php @@ -0,0 +1,114 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\ColumnDiff; +use Doctrine\DBAL\Schema\TableDiff; + +/** + * Event Arguments used when SQL queries for changing table columns are generated inside Doctrine\DBAL\Platform\*Platform. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaAlterTableChangeColumnEventArgs extends SchemaEventArgs +{ + /** + * @var \Doctrine\DBAL\Schema\ColumnDiff + */ + private $_columnDiff; + + /** + * @var \Doctrine\DBAL\Schema\TableDiff + */ + private $_tableDiff; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $_platform; + + /** + * @var array + */ + private $_sql = array(); + + /** + * @param \Doctrine\DBAL\Schema\ColumnDiff $columnDiff + * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + public function __construct(ColumnDiff $columnDiff, TableDiff $tableDiff, AbstractPlatform $platform) + { + $this->_columnDiff = $columnDiff; + $this->_tableDiff = $tableDiff; + $this->_platform = $platform; + } + + /** + * @return \Doctrine\DBAL\Schema\ColumnDiff + */ + public function getColumnDiff() + { + return $this->_columnDiff; + } + + /** + * @return \Doctrine\DBAL\Schema\TableDiff + */ + public function getTableDiff() + { + return $this->_tableDiff; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getPlatform() + { + return $this->_platform; + } + + /** + * @param string|array $sql + * + * @return \Doctrine\DBAL\Event\SchemaAlterTableChangeColumnEventArgs + */ + public function addSql($sql) + { + if (is_array($sql)) { + $this->_sql = array_merge($this->_sql, $sql); + } else { + $this->_sql[] = $sql; + } + + return $this; + } + + /** + * @return array + */ + public function getSql() + { + return $this->_sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableEventArgs.php new file mode 100644 index 0000000..384315d --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableEventArgs.php @@ -0,0 +1,98 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\TableDiff; + +/** + * Event Arguments used when SQL queries for creating tables are generated inside Doctrine\DBAL\Platform\*Platform. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaAlterTableEventArgs extends SchemaEventArgs +{ + /** + * @var \Doctrine\DBAL\Schema\TableDiff + */ + private $_tableDiff; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $_platform; + + /** + * @var array + */ + private $_sql = array(); + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + public function __construct(TableDiff $tableDiff, AbstractPlatform $platform) + { + $this->_tableDiff = $tableDiff; + $this->_platform = $platform; + } + + /** + * @return \Doctrine\DBAL\Schema\TableDiff + */ + public function getTableDiff() + { + return $this->_tableDiff; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getPlatform() + { + return $this->_platform; + } + + /** + * @param string|array $sql + * + * @return \Doctrine\DBAL\Event\SchemaAlterTableEventArgs + */ + public function addSql($sql) + { + if (is_array($sql)) { + $this->_sql = array_merge($this->_sql, $sql); + } else { + $this->_sql[] = $sql; + } + + return $this; + } + + /** + * @return array + */ + public function getSql() + { + return $this->_sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableRemoveColumnEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableRemoveColumnEventArgs.php new file mode 100644 index 0000000..06f3788 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableRemoveColumnEventArgs.php @@ -0,0 +1,114 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\TableDiff; + +/** + * Event Arguments used when SQL queries for removing table columns are generated inside Doctrine\DBAL\Platform\*Platform. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaAlterTableRemoveColumnEventArgs extends SchemaEventArgs +{ + /** + * @var \Doctrine\DBAL\Schema\Column + */ + private $_column; + + /** + * @var \Doctrine\DBAL\Schema\TableDiff + */ + private $_tableDiff; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $_platform; + + /** + * @var array + */ + private $_sql = array(); + + /** + * @param \Doctrine\DBAL\Schema\Column $column + * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + public function __construct(Column $column, TableDiff $tableDiff, AbstractPlatform $platform) + { + $this->_column = $column; + $this->_tableDiff = $tableDiff; + $this->_platform = $platform; + } + + /** + * @return \Doctrine\DBAL\Schema\Column + */ + public function getColumn() + { + return $this->_column; + } + + /** + * @return \Doctrine\DBAL\Schema\TableDiff + */ + public function getTableDiff() + { + return $this->_tableDiff; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getPlatform() + { + return $this->_platform; + } + + /** + * @param string|array $sql + * + * @return \Doctrine\DBAL\Event\SchemaAlterTableRemoveColumnEventArgs + */ + public function addSql($sql) + { + if (is_array($sql)) { + $this->_sql = array_merge($this->_sql, $sql); + } else { + $this->_sql[] = $sql; + } + + return $this; + } + + /** + * @return array + */ + public function getSql() + { + return $this->_sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableRenameColumnEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableRenameColumnEventArgs.php new file mode 100644 index 0000000..94a274c --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaAlterTableRenameColumnEventArgs.php @@ -0,0 +1,129 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\TableDiff; + +/** + * Event Arguments used when SQL queries for renaming table columns are generated inside Doctrine\DBAL\Platform\*Platform. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaAlterTableRenameColumnEventArgs extends SchemaEventArgs +{ + /** + * @var string + */ + private $_oldColumnName; + + /** + * @var \Doctrine\DBAL\Schema\Column + */ + private $_column; + + /** + * @var \Doctrine\DBAL\Schema\TableDiff + */ + private $_tableDiff; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $_platform; + + /** + * @var array + */ + private $_sql = array(); + + /** + * @param string $oldColumnName + * @param \Doctrine\DBAL\Schema\Column $column + * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + public function __construct($oldColumnName, Column $column, TableDiff $tableDiff, AbstractPlatform $platform) + { + $this->_oldColumnName = $oldColumnName; + $this->_column = $column; + $this->_tableDiff = $tableDiff; + $this->_platform = $platform; + } + + /** + * @return string + */ + public function getOldColumnName() + { + return $this->_oldColumnName; + } + + /** + * @return \Doctrine\DBAL\Schema\Column + */ + public function getColumn() + { + return $this->_column; + } + + /** + * @return \Doctrine\DBAL\Schema\TableDiff + */ + public function getTableDiff() + { + return $this->_tableDiff; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getPlatform() + { + return $this->_platform; + } + + /** + * @param string|array $sql + * + * @return \Doctrine\DBAL\Event\SchemaAlterTableRenameColumnEventArgs + */ + public function addSql($sql) + { + if (is_array($sql)) { + $this->_sql = array_merge($this->_sql, $sql); + } else { + $this->_sql[] = $sql; + } + + return $this; + } + + /** + * @return array + */ + public function getSql() + { + return $this->_sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaColumnDefinitionEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaColumnDefinitionEventArgs.php new file mode 100644 index 0000000..14bb8df --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaColumnDefinitionEventArgs.php @@ -0,0 +1,137 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Schema\Column; + +/** + * Event Arguments used when the portable column definition is generated inside Doctrine\DBAL\Schema\AbstractSchemaManager. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaColumnDefinitionEventArgs extends SchemaEventArgs +{ + /** + * @var \Doctrine\DBAL\Schema\Column|null + */ + private $_column = null; + + /** + * Raw column data as fetched from the database. + * + * @var array + */ + private $_tableColumn; + + /** + * @var string + */ + private $_table; + + /** + * @var string + */ + private $_database; + + /** + * @var \Doctrine\DBAL\Connection + */ + private $_connection; + + /** + * @param array $tableColumn + * @param string $table + * @param string $database + * @param \Doctrine\DBAL\Connection $connection + */ + public function __construct(array $tableColumn, $table, $database, Connection $connection) + { + $this->_tableColumn = $tableColumn; + $this->_table = $table; + $this->_database = $database; + $this->_connection = $connection; + } + + /** + * Allows to clear the column which means the column will be excluded from + * tables column list. + * + * @param null|\Doctrine\DBAL\Schema\Column $column + * + * @return \Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs + */ + public function setColumn(Column $column = null) + { + $this->_column = $column; + + return $this; + } + + /** + * @return \Doctrine\DBAL\Schema\Column|null + */ + public function getColumn() + { + return $this->_column; + } + + /** + * @return array + */ + public function getTableColumn() + { + return $this->_tableColumn; + } + + /** + * @return string + */ + public function getTable() + { + return $this->_table; + } + + /** + * @return string + */ + public function getDatabase() + { + return $this->_database; + } + + /** + * @return \Doctrine\DBAL\Connection + */ + public function getConnection() + { + return $this->_connection; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getDatabasePlatform() + { + return $this->_connection->getDatabasePlatform(); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaCreateTableColumnEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaCreateTableColumnEventArgs.php new file mode 100644 index 0000000..38b2182 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaCreateTableColumnEventArgs.php @@ -0,0 +1,114 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\Table; + +/** + * Event Arguments used when SQL queries for creating table columns are generated inside Doctrine\DBAL\Platform\AbstractPlatform. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaCreateTableColumnEventArgs extends SchemaEventArgs +{ + /** + * @var \Doctrine\DBAL\Schema\Column + */ + private $_column; + + /** + * @var \Doctrine\DBAL\Schema\Table + */ + private $_table; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $_platform; + + /** + * @var array + */ + private $_sql = array(); + + /** + * @param \Doctrine\DBAL\Schema\Column $column + * @param \Doctrine\DBAL\Schema\Table $table + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + public function __construct(Column $column, Table $table, AbstractPlatform $platform) + { + $this->_column = $column; + $this->_table = $table; + $this->_platform = $platform; + } + + /** + * @return \Doctrine\DBAL\Schema\Column + */ + public function getColumn() + { + return $this->_column; + } + + /** + * @return \Doctrine\DBAL\Schema\Table + */ + public function getTable() + { + return $this->_table; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getPlatform() + { + return $this->_platform; + } + + /** + * @param string|array $sql + * + * @return \Doctrine\DBAL\Event\SchemaCreateTableColumnEventArgs + */ + public function addSql($sql) + { + if (is_array($sql)) { + $this->_sql = array_merge($this->_sql, $sql); + } else { + $this->_sql[] = $sql; + } + + return $this; + } + + /** + * @return array + */ + public function getSql() + { + return $this->_sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaCreateTableEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaCreateTableEventArgs.php new file mode 100644 index 0000000..175b0e9 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaCreateTableEventArgs.php @@ -0,0 +1,128 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Table; + +/** + * Event Arguments used when SQL queries for creating tables are generated inside Doctrine\DBAL\Platform\AbstractPlatform. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaCreateTableEventArgs extends SchemaEventArgs +{ + /** + * @var \Doctrine\DBAL\Schema\Table + */ + private $_table; + + /** + * @var array + */ + private $_columns; + + /** + * @var array + */ + private $_options; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $_platform; + + /** + * @var array + */ + private $_sql = array(); + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param array $columns + * @param array $options + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + public function __construct(Table $table, array $columns, array $options, AbstractPlatform $platform) + { + $this->_table = $table; + $this->_columns = $columns; + $this->_options = $options; + $this->_platform = $platform; + } + + /** + * @return \Doctrine\DBAL\Schema\Table + */ + public function getTable() + { + return $this->_table; + } + + /** + * @return array + */ + public function getColumns() + { + return $this->_columns; + } + + /** + * @return array + */ + public function getOptions() + { + return $this->_options; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getPlatform() + { + return $this->_platform; + } + + /** + * @param string|array $sql + * + * @return \Doctrine\DBAL\Event\SchemaCreateTableEventArgs + */ + public function addSql($sql) + { + if (is_array($sql)) { + $this->_sql = array_merge($this->_sql, $sql); + } else { + $this->_sql[] = $sql; + } + + return $this; + } + + /** + * @return array + */ + public function getSql() + { + return $this->_sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaDropTableEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaDropTableEventArgs.php new file mode 100644 index 0000000..d21488e --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaDropTableEventArgs.php @@ -0,0 +1,100 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Table; + +/** + * Event Arguments used when the SQL query for dropping tables are generated inside Doctrine\DBAL\Platform\AbstractPlatform. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaDropTableEventArgs extends SchemaEventArgs +{ + /** + * @var string|\Doctrine\DBAL\Schema\Table + */ + private $_table; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $_platform; + + /** + * @var string|null + */ + private $_sql = null; + + /** + * @param string|\Doctrine\DBAL\Schema\Table $table + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @throws \InvalidArgumentException + */ + public function __construct($table, AbstractPlatform $platform) + { + if ( ! $table instanceof Table && !is_string($table)) { + throw new \InvalidArgumentException('SchemaCreateTableEventArgs expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.'); + } + + $this->_table = $table; + $this->_platform = $platform; + } + + /** + * @return string|\Doctrine\DBAL\Schema\Table + */ + public function getTable() + { + return $this->_table; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getPlatform() + { + return $this->_platform; + } + + /** + * @param string $sql + * + * @return \Doctrine\DBAL\Event\SchemaDropTableEventArgs + */ + public function setSql($sql) + { + $this->_sql = $sql; + + return $this; + } + + /** + * @return string|null + */ + public function getSql() + { + return $this->_sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaEventArgs.php new file mode 100644 index 0000000..d9648b6 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaEventArgs.php @@ -0,0 +1,55 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\Common\EventArgs; + +/** + * Base class for schema related events. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaEventArgs extends EventArgs +{ + /** + * @var boolean + */ + private $_preventDefault = false; + + /** + * @return \Doctrine\DBAL\Event\SchemaEventArgs + */ + public function preventDefault() + { + $this->_preventDefault = true; + + return $this; + } + + /** + * @return boolean + */ + public function isDefaultPrevented() + { + return $this->_preventDefault; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaIndexDefinitionEventArgs.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaIndexDefinitionEventArgs.php new file mode 100644 index 0000000..3fd1d4b --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Event/SchemaIndexDefinitionEventArgs.php @@ -0,0 +1,121 @@ +. + */ + +namespace Doctrine\DBAL\Event; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Schema\Index; + +/** + * Event Arguments used when the portable index definition is generated inside Doctrine\DBAL\Schema\AbstractSchemaManager. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Jan Sorgalla + */ +class SchemaIndexDefinitionEventArgs extends SchemaEventArgs +{ + /** + * @var \Doctrine\DBAL\Schema\Index|null + */ + private $_index = null; + + /** + * Raw index data as fetched from the database. + * + * @var array + */ + private $_tableIndex; + + /** + * @var string + */ + private $_table; + + /** + * @var \Doctrine\DBAL\Connection + */ + private $_connection; + + /** + * @param array $tableIndex + * @param string $table + * @param \Doctrine\DBAL\Connection $connection + */ + public function __construct(array $tableIndex, $table, Connection $connection) + { + $this->_tableIndex = $tableIndex; + $this->_table = $table; + $this->_connection = $connection; + } + + /** + * Allows to clear the index which means the index will be excluded from tables index list. + * + * @param null|\Doctrine\DBAL\Schema\Index $index + * + * @return SchemaIndexDefinitionEventArgs + */ + public function setIndex(Index $index = null) + { + $this->_index = $index; + + return $this; + } + + /** + * @return \Doctrine\DBAL\Schema\Index|null + */ + public function getIndex() + { + return $this->_index; + } + + /** + * @return array + */ + public function getTableIndex() + { + return $this->_tableIndex; + } + + /** + * @return string + */ + public function getTable() + { + return $this->_table; + } + + /** + * @return \Doctrine\DBAL\Connection + */ + public function getConnection() + { + return $this->_connection; + } + + /** + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getDatabasePlatform() + { + return $this->_connection->getDatabasePlatform(); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Events.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Events.php new file mode 100644 index 0000000..0d31504 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Events.php @@ -0,0 +1,51 @@ +. + */ + +namespace Doctrine\DBAL; + +/** + * Container for all DBAL events. + * + * This class cannot be instantiated. + * + * @author Roman Borschel + * @since 2.0 + */ +final class Events +{ + /** + * Private constructor. This class cannot be instantiated. + */ + private function __construct() + { + } + + const postConnect = 'postConnect'; + + const onSchemaCreateTable = 'onSchemaCreateTable'; + const onSchemaCreateTableColumn = 'onSchemaCreateTableColumn'; + const onSchemaDropTable = 'onSchemaDropTable'; + const onSchemaAlterTable = 'onSchemaAlterTable'; + const onSchemaAlterTableAddColumn = 'onSchemaAlterTableAddColumn'; + const onSchemaAlterTableRemoveColumn = 'onSchemaAlterTableRemoveColumn'; + const onSchemaAlterTableChangeColumn = 'onSchemaAlterTableChangeColumn'; + const onSchemaAlterTableRenameColumn = 'onSchemaAlterTableRenameColumn'; + const onSchemaColumnDefinition = 'onSchemaColumnDefinition'; + const onSchemaIndexDefinition = 'onSchemaIndexDefinition'; +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Id/TableGenerator.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Id/TableGenerator.php new file mode 100644 index 0000000..090227a --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Id/TableGenerator.php @@ -0,0 +1,165 @@ +. + */ + +namespace Doctrine\DBAL\Id; + +use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Connection; + +/** + * Table ID Generator for those poor languages that are missing sequences. + * + * WARNING: The Table Id Generator clones a second independent database + * connection to work correctly. This means using the generator requests that + * generate IDs will have two open database connections. This is necessary to + * be safe from transaction failures in the main connection. Make sure to only + * ever use one TableGenerator otherwise you end up with many connections. + * + * TableID Generator does not work with SQLite. + * + * The TableGenerator does not take care of creating the SQL Table itself. You + * should look at the `TableGeneratorSchemaVisitor` to do this for you. + * Otherwise the schema for a table looks like: + * + * CREATE sequences ( + * sequence_name VARCHAR(255) NOT NULL, + * sequence_value INT NOT NULL DEFAULT '1', + * sequence_increment_by INT NOT NULL DEFAULT '1', + * PRIMARY KEY (table_name) + * ); + * + * Technically this generator works as follows: + * + * 1. Use a robust transaction serialization level. + * 2. Open transaction + * 3. Acquire a read lock on the table row (SELECT .. FOR UPDATE) + * 4. Increment current value by one and write back to database + * 5. Commit transaction + * + * If you are using a sequence_increment_by value that is larger than one the + * ID Generator will keep incrementing values until it hits the incrementation + * gap before issuing another query. + * + * If no row is present for a given sequence a new one will be created with the + * default values 'value' = 1 and 'increment_by' = 1 + * + * @author Benjamin Eberlei + */ +class TableGenerator +{ + /** + * @var \Doctrine\DBAL\Connection + */ + private $conn; + + /** + * @var string + */ + private $generatorTableName; + + /** + * @var array + */ + private $sequences = array(); + + /** + * @param \Doctrine\DBAL\Connection $conn + * @param string $generatorTableName + * + * @throws \Doctrine\DBAL\DBALException + */ + public function __construct(Connection $conn, $generatorTableName = 'sequences') + { + $params = $conn->getParams(); + if ($params['driver'] == 'pdo_sqlite') { + throw new \Doctrine\DBAL\DBALException("Cannot use TableGenerator with SQLite."); + } + $this->conn = DriverManager::getConnection($params, $conn->getConfiguration(), $conn->getEventManager()); + $this->generatorTableName = $generatorTableName; + } + + /** + * Generates the next unused value for the given sequence name. + * + * @param string $sequenceName + * + * @return integer + * + * @throws \Doctrine\DBAL\DBALException + */ + public function nextValue($sequenceName) + { + if (isset($this->sequences[$sequenceName])) { + $value = $this->sequences[$sequenceName]['value']; + $this->sequences[$sequenceName]['value']++; + if ($this->sequences[$sequenceName]['value'] >= $this->sequences[$sequenceName]['max']) { + unset ($this->sequences[$sequenceName]); + } + + return $value; + } + + $this->conn->beginTransaction(); + + try { + $platform = $this->conn->getDatabasePlatform(); + $sql = "SELECT sequence_value, sequence_increment_by " . + "FROM " . $platform->appendLockHint($this->generatorTableName, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE) . " " . + "WHERE sequence_name = ? " . $platform->getWriteLockSQL(); + $stmt = $this->conn->executeQuery($sql, array($sequenceName)); + + if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { + $row = array_change_key_case($row, CASE_LOWER); + + $value = $row['sequence_value']; + $value++; + + if ($row['sequence_increment_by'] > 1) { + $this->sequences[$sequenceName] = array( + 'value' => $value, + 'max' => $row['sequence_value'] + $row['sequence_increment_by'] + ); + } + + $sql = "UPDATE " . $this->generatorTableName . " ". + "SET sequence_value = sequence_value + sequence_increment_by " . + "WHERE sequence_name = ? AND sequence_value = ?"; + $rows = $this->conn->executeUpdate($sql, array($sequenceName, $row['sequence_value'])); + + if ($rows != 1) { + throw new \Doctrine\DBAL\DBALException("Race-condition detected while updating sequence. Aborting generation"); + } + } else { + $this->conn->insert( + $this->generatorTableName, + array('sequence_name' => $sequenceName, 'sequence_value' => 1, 'sequence_increment_by' => 1) + ); + $value = 1; + } + + $this->conn->commit(); + + } catch(\Exception $e) { + $this->conn->rollback(); + throw new \Doctrine\DBAL\DBALException("Error occurred while generating ID with TableGenerator, aborted generation: " . $e->getMessage(), 0, $e); + } + + return $value; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Id/TableGeneratorSchemaVisitor.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Id/TableGeneratorSchemaVisitor.php new file mode 100644 index 0000000..25b06ac --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Id/TableGeneratorSchemaVisitor.php @@ -0,0 +1,89 @@ +. + */ + +namespace Doctrine\DBAL\Id; + +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\Index; + +class TableGeneratorSchemaVisitor implements \Doctrine\DBAL\Schema\Visitor\Visitor +{ + /** + * @var string + */ + private $generatorTableName; + + /** + * @param string $generatorTableName + */ + public function __construct($generatorTableName = 'sequences') + { + $this->generatorTableName = $generatorTableName; + } + + /** + * {@inheritdoc} + */ + public function acceptSchema(Schema $schema) + { + $table = $schema->createTable($this->generatorTableName); + $table->addColumn('sequence_name', 'string'); + $table->addColumn('sequence_value', 'integer', array('default' => 1)); + $table->addColumn('sequence_increment_by', 'integer', array('default' => 1)); + } + + /** + * {@inheritdoc} + */ + public function acceptTable(Table $table) + { + } + + /** + * {@inheritdoc} + */ + public function acceptColumn(Table $table, Column $column) + { + } + + /** + * {@inheritdoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + } + + /** + * {@inheritdoc} + */ + public function acceptIndex(Table $table, Index $index) + { + } + + /** + * {@inheritdoc} + */ + public function acceptSequence(Sequence $sequence) + { + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/LockMode.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/LockMode.php new file mode 100644 index 0000000..8d78180 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/LockMode.php @@ -0,0 +1,43 @@ +. + */ + +namespace Doctrine\DBAL; + +/** + * Contains all DBAL LockModes. + * + * @link www.doctrine-project.org + * @since 1.0 + * @author Benjamin Eberlei + * @author Roman Borschel + */ +class LockMode +{ + const NONE = 0; + const OPTIMISTIC = 1; + const PESSIMISTIC_READ = 2; + const PESSIMISTIC_WRITE = 4; + + /** + * Private constructor. This class cannot be instantiated. + */ + final private function __construct() + { + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/DebugStack.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/DebugStack.php new file mode 100644 index 0000000..b143a3e --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/DebugStack.php @@ -0,0 +1,78 @@ +. + */ + +namespace Doctrine\DBAL\Logging; + +/** + * Includes executed SQLs in a Debug Stack. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class DebugStack implements SQLLogger +{ + /** + * Executed SQL queries. + * + * @var array + */ + public $queries = array(); + + /** + * If Debug Stack is enabled (log queries) or not. + * + * @var boolean + */ + public $enabled = true; + + /** + * @var float|null + */ + public $start = null; + + /** + * @var integer + */ + public $currentQuery = 0; + + /** + * {@inheritdoc} + */ + public function startQuery($sql, array $params = null, array $types = null) + { + if ($this->enabled) { + $this->start = microtime(true); + $this->queries[++$this->currentQuery] = array('sql' => $sql, 'params' => $params, 'types' => $types, 'executionMS' => 0); + } + } + + /** + * {@inheritdoc} + */ + public function stopQuery() + { + if ($this->enabled) { + $this->queries[$this->currentQuery]['executionMS'] = microtime(true) - $this->start; + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/EchoSQLLogger.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/EchoSQLLogger.php new file mode 100644 index 0000000..b1fedf1 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/EchoSQLLogger.php @@ -0,0 +1,56 @@ +. + */ + +namespace Doctrine\DBAL\Logging; + +/** + * A SQL logger that logs to the standard output using echo/var_dump. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class EchoSQLLogger implements SQLLogger +{ + /** + * {@inheritdoc} + */ + public function startQuery($sql, array $params = null, array $types = null) + { + echo $sql . PHP_EOL; + + if ($params) { + var_dump($params); + } + + if ($types) { + var_dump($types); + } + } + + /** + * {@inheritdoc} + */ + public function stopQuery() + { + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/LoggerChain.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/LoggerChain.php new file mode 100644 index 0000000..81845b1 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/LoggerChain.php @@ -0,0 +1,67 @@ +. + */ + +namespace Doctrine\DBAL\Logging; + +/** + * Chains multiple SQLLogger. + * + * @link www.doctrine-project.org + * @since 2.2 + * @author Christophe Coevoet + */ +class LoggerChain implements SQLLogger +{ + /** + * @var \Doctrine\DBAL\Logging\SQLLogger[] + */ + private $loggers = array(); + + /** + * Adds a logger in the chain. + * + * @param \Doctrine\DBAL\Logging\SQLLogger $logger + * + * @return void + */ + public function addLogger(SQLLogger $logger) + { + $this->loggers[] = $logger; + } + + /** + * {@inheritdoc} + */ + public function startQuery($sql, array $params = null, array $types = null) + { + foreach ($this->loggers as $logger) { + $logger->startQuery($sql, $params, $types); + } + } + + /** + * {@inheritdoc} + */ + public function stopQuery() + { + foreach ($this->loggers as $logger) { + $logger->stopQuery(); + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/SQLLogger.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/SQLLogger.php new file mode 100644 index 0000000..52491e5 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Logging/SQLLogger.php @@ -0,0 +1,51 @@ +. + */ + +namespace Doctrine\DBAL\Logging; + +/** + * Interface for SQL loggers. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +interface SQLLogger +{ + /** + * Logs a SQL statement somewhere. + * + * @param string $sql The SQL to be executed. + * @param array|null $params The SQL parameters. + * @param array|null $types The SQL parameter types. + * + * @return void + */ + public function startQuery($sql, array $params = null, array $types = null); + + /** + * Marks the last started query as stopped. This can be used for timing of queries. + * + * @return void + */ + public function stopQuery(); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php new file mode 100644 index 0000000..4ce5a3d --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/AbstractPlatform.php @@ -0,0 +1,3079 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Types; +use Doctrine\DBAL\Schema\Constraint; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\TableDiff; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ColumnDiff; +use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Events; +use Doctrine\Common\EventManager; +use Doctrine\DBAL\Event\SchemaCreateTableEventArgs; +use Doctrine\DBAL\Event\SchemaCreateTableColumnEventArgs; +use Doctrine\DBAL\Event\SchemaDropTableEventArgs; +use Doctrine\DBAL\Event\SchemaAlterTableEventArgs; +use Doctrine\DBAL\Event\SchemaAlterTableAddColumnEventArgs; +use Doctrine\DBAL\Event\SchemaAlterTableRemoveColumnEventArgs; +use Doctrine\DBAL\Event\SchemaAlterTableChangeColumnEventArgs; +use Doctrine\DBAL\Event\SchemaAlterTableRenameColumnEventArgs; + +/** + * Base class for all DatabasePlatforms. The DatabasePlatforms are the central + * point of abstraction of platform-specific behaviors, features and SQL dialects. + * They are a passive source of information. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Lukas Smith (PEAR MDB2 library) + * @author Benjamin Eberlei + * @todo Remove any unnecessary methods. + */ +abstract class AbstractPlatform +{ + /** + * @var integer + */ + const CREATE_INDEXES = 1; + + /** + * @var integer + */ + const CREATE_FOREIGNKEYS = 2; + + /** + * @var integer + */ + const TRIM_UNSPECIFIED = 0; + + /** + * @var integer + */ + const TRIM_LEADING = 1; + + /** + * @var integer + */ + const TRIM_TRAILING = 2; + + /** + * @var integer + */ + const TRIM_BOTH = 3; + + /** + * @var array|null + */ + protected $doctrineTypeMapping = null; + + /** + * Contains a list of all columns that should generate parseable column comments for type-detection + * in reverse engineering scenarios. + * + * @var array|null + */ + protected $doctrineTypeComments = null; + + /** + * @var \Doctrine\Common\EventManager + */ + protected $_eventManager; + + /** + * Holds the KeywordList instance for the current platform. + * + * @var \Doctrine\DBAL\Platforms\Keywords\KeywordList + */ + protected $_keywords; + + /** + * Constructor. + */ + public function __construct() + { + } + + /** + * Sets the EventManager used by the Platform. + * + * @param \Doctrine\Common\EventManager + */ + public function setEventManager(EventManager $eventManager) + { + $this->_eventManager = $eventManager; + } + + /** + * Gets the EventManager used by the Platform. + * + * @return \Doctrine\Common\EventManager + */ + public function getEventManager() + { + return $this->_eventManager; + } + + /** + * Returns the SQL snippet that declares a boolean column. + * + * @param array $columnDef + * + * @return string + */ + abstract public function getBooleanTypeDeclarationSQL(array $columnDef); + + /** + * Returns the SQL snippet that declares a 4 byte integer column. + * + * @param array $columnDef + * + * @return string + */ + abstract public function getIntegerTypeDeclarationSQL(array $columnDef); + + /** + * Returns the SQL snippet that declares an 8 byte integer column. + * + * @param array $columnDef + * + * @return string + */ + abstract public function getBigIntTypeDeclarationSQL(array $columnDef); + + /** + * Returns the SQL snippet that declares a 2 byte integer column. + * + * @param array $columnDef + * + * @return string + */ + abstract public function getSmallIntTypeDeclarationSQL(array $columnDef); + + /** + * Returns the SQL snippet that declares common properties of an integer column. + * + * @param array $columnDef + * + * @return string + */ + abstract protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef); + + /** + * Lazy load Doctrine Type Mappings. + * + * @return void + */ + abstract protected function initializeDoctrineTypeMappings(); + + /** + * Initializes Doctrine Type Mappings with the platform defaults + * and with all additional type mappings. + * + * @return void + */ + private function initializeAllDoctrineTypeMappings() + { + $this->initializeDoctrineTypeMappings(); + + foreach (Type::getTypesMap() as $typeName => $className) { + foreach (Type::getType($typeName)->getMappedDatabaseTypes($this) as $dbType) { + $this->doctrineTypeMapping[$dbType] = $typeName; + } + } + } + + /** + * Returns the SQL snippet used to declare a VARCHAR column type. + * + * @param array $field + * + * @return string + */ + public function getVarcharTypeDeclarationSQL(array $field) + { + if ( !isset($field['length'])) { + $field['length'] = $this->getVarcharDefaultLength(); + } + + $fixed = (isset($field['fixed'])) ? $field['fixed'] : false; + + if ($field['length'] > $this->getVarcharMaxLength()) { + return $this->getClobTypeDeclarationSQL($field); + } + + return $this->getVarcharTypeDeclarationSQLSnippet($field['length'], $fixed); + } + + /** + * Returns the SQL snippet to declare a GUID/UUID field. + * + * By default this maps directly to a VARCHAR and only maps to more + * special datatypes when the underlying databases support this datatype. + * + * @param array $field + * + * @return string + */ + public function getGuidTypeDeclarationSQL(array $field) + { + return $this->getVarcharTypeDeclarationSQL($field); + } + + /** + * @param integer $length + * @param boolean $fixed + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + throw DBALException::notSupported('VARCHARs not supported by Platform.'); + } + + /** + * Returns the SQL snippet used to declare a CLOB column type. + * + * @param array $field + * + * @return string + */ + abstract public function getClobTypeDeclarationSQL(array $field); + + /** + * Returns the SQL Snippet used to declare a BLOB column type. + * + * @param array $field + * + * @return string + */ + abstract public function getBlobTypeDeclarationSQL(array $field); + + /** + * Gets the name of the platform. + * + * @return string + */ + abstract public function getName(); + + /** + * Registers a doctrine type to be used in conjunction with a column type of this platform. + * + * @param string $dbType + * @param string $doctrineType + * + * @throws \Doctrine\DBAL\DBALException If the type is not found. + */ + public function registerDoctrineTypeMapping($dbType, $doctrineType) + { + if ($this->doctrineTypeMapping === null) { + $this->initializeAllDoctrineTypeMappings(); + } + + if (!Types\Type::hasType($doctrineType)) { + throw DBALException::typeNotFound($doctrineType); + } + + $dbType = strtolower($dbType); + $this->doctrineTypeMapping[$dbType] = $doctrineType; + } + + /** + * Gets the Doctrine type that is mapped for the given database column type. + * + * @param string $dbType + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException + */ + public function getDoctrineTypeMapping($dbType) + { + if ($this->doctrineTypeMapping === null) { + $this->initializeAllDoctrineTypeMappings(); + } + + $dbType = strtolower($dbType); + + if (!isset($this->doctrineTypeMapping[$dbType])) { + throw new \Doctrine\DBAL\DBALException("Unknown database type ".$dbType." requested, " . get_class($this) . " may not support it."); + } + + return $this->doctrineTypeMapping[$dbType]; + } + + /** + * Checks if a database type is currently supported by this platform. + * + * @param string $dbType + * + * @return boolean + */ + public function hasDoctrineTypeMappingFor($dbType) + { + if ($this->doctrineTypeMapping === null) { + $this->initializeAllDoctrineTypeMappings(); + } + + $dbType = strtolower($dbType); + + return isset($this->doctrineTypeMapping[$dbType]); + } + + /** + * Initializes the Doctrine Type comments instance variable for in_array() checks. + * + * @return void + */ + protected function initializeCommentedDoctrineTypes() + { + $this->doctrineTypeComments = array(); + + foreach (Type::getTypesMap() as $typeName => $className) { + $type = Type::getType($typeName); + + if ($type->requiresSQLCommentHint($this)) { + $this->doctrineTypeComments[] = $typeName; + } + } + } + + /** + * Is it necessary for the platform to add a parsable type comment to allow reverse engineering the given type? + * + * @param \Doctrine\DBAL\Types\Type $doctrineType + * + * @return boolean + */ + public function isCommentedDoctrineType(Type $doctrineType) + { + if ($this->doctrineTypeComments === null) { + $this->initializeCommentedDoctrineTypes(); + } + + return in_array($doctrineType->getName(), $this->doctrineTypeComments); + } + + /** + * Marks this type as to be commented in ALTER TABLE and CREATE TABLE statements. + * + * @param string|\Doctrine\DBAL\Types\Type $doctrineType + * + * @return void + */ + public function markDoctrineTypeCommented($doctrineType) + { + if ($this->doctrineTypeComments === null) { + $this->initializeCommentedDoctrineTypes(); + } + + $this->doctrineTypeComments[] = $doctrineType instanceof Type ? $doctrineType->getName() : $doctrineType; + } + + /** + * Gets the comment to append to a column comment that helps parsing this type in reverse engineering. + * + * @param \Doctrine\DBAL\Types\Type $doctrineType + * + * @return string + */ + public function getDoctrineTypeComment(Type $doctrineType) + { + return '(DC2Type:' . $doctrineType->getName() . ')'; + } + + /** + * Gets the comment of a passed column modified by potential doctrine type comment hints. + * + * @param \Doctrine\DBAL\Schema\Column $column + * + * @return string + */ + protected function getColumnComment(Column $column) + { + $comment = $column->getComment(); + + if ($this->isCommentedDoctrineType($column->getType())) { + $comment .= $this->getDoctrineTypeComment($column->getType()); + } + + return $comment; + } + + /** + * Gets the character used for identifier quoting. + * + * @return string + */ + public function getIdentifierQuoteCharacter() + { + return '"'; + } + + /** + * Gets the string portion that starts an SQL comment. + * + * @return string + */ + public function getSqlCommentStartString() + { + return "--"; + } + + /** + * Gets the string portion that ends an SQL comment. + * + * @return string + */ + public function getSqlCommentEndString() + { + return "\n"; + } + + /** + * Gets the maximum length of a varchar field. + * + * @return integer + */ + public function getVarcharMaxLength() + { + return 4000; + } + + /** + * Gets the default length of a varchar field. + * + * @return integer + */ + public function getVarcharDefaultLength() + { + return 255; + } + + /** + * Gets all SQL wildcard characters of the platform. + * + * @return array + */ + public function getWildcards() + { + return array('%', '_'); + } + + /** + * Returns the regular expression operator. + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getRegexpExpression() + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the global unique identifier expression. + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getGuidExpression() + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL snippet to get the average value of a column. + * + * @param string $column The column to use. + * + * @return string Generated SQL including an AVG aggregate function. + */ + public function getAvgExpression($column) + { + return 'AVG(' . $column . ')'; + } + + /** + * Returns the SQL snippet to get the number of rows (without a NULL value) of a column. + * + * If a '*' is used instead of a column the number of selected rows is returned. + * + * @param string|integer $column The column to use. + * + * @return string Generated SQL including a COUNT aggregate function. + */ + public function getCountExpression($column) + { + return 'COUNT(' . $column . ')'; + } + + /** + * Returns the SQL snippet to get the highest value of a column. + * + * @param string $column The column to use. + * + * @return string Generated SQL including a MAX aggregate function. + */ + public function getMaxExpression($column) + { + return 'MAX(' . $column . ')'; + } + + /** + * Returns the SQL snippet to get the lowest value of a column. + * + * @param string $column The column to use. + * + * @return string Generated SQL including a MIN aggregate function. + */ + public function getMinExpression($column) + { + return 'MIN(' . $column . ')'; + } + + /** + * Returns the SQL snippet to get the total sum of a column. + * + * @param string $column The column to use. + * + * @return string Generated SQL including a SUM aggregate function. + */ + public function getSumExpression($column) + { + return 'SUM(' . $column . ')'; + } + + // scalar functions + + /** + * Returns the SQL snippet to get the md5 sum of a field. + * + * Note: Not SQL92, but common functionality. + * + * @param string $column + * + * @return string + */ + public function getMd5Expression($column) + { + return 'MD5(' . $column . ')'; + } + + /** + * Returns the SQL snippet to get the length of a text field. + * + * @param string $column + * + * @return string + */ + public function getLengthExpression($column) + { + return 'LENGTH(' . $column . ')'; + } + + /** + * Returns the SQL snippet to get the squared value of a column. + * + * @param string $column The column to use. + * + * @return string Generated SQL including an SQRT aggregate function. + */ + public function getSqrtExpression($column) + { + return 'SQRT(' . $column . ')'; + } + + /** + * Returns the SQL snippet to round a numeric field to the number of decimals specified. + * + * @param string $column + * @param integer $decimals + * + * @return string + */ + public function getRoundExpression($column, $decimals = 0) + { + return 'ROUND(' . $column . ', ' . $decimals . ')'; + } + + /** + * Returns the SQL snippet to get the remainder of the division operation $expression1 / $expression2. + * + * @param string $expression1 + * @param string $expression2 + * + * @return string + */ + public function getModExpression($expression1, $expression2) + { + return 'MOD(' . $expression1 . ', ' . $expression2 . ')'; + } + + /** + * Returns the SQL snippet to trim a string. + * + * @param string $str The expression to apply the trim to. + * @param integer $pos The position of the trim (leading/trailing/both). + * @param string|boolean $char The char to trim, has to be quoted already. Defaults to space. + * + * @return string + */ + public function getTrimExpression($str, $pos = self::TRIM_UNSPECIFIED, $char = false) + { + $posStr = ''; + $trimChar = ($char != false) ? $char . ' FROM ' : ''; + + switch ($pos) { + case self::TRIM_LEADING: + $posStr = 'LEADING '.$trimChar; + break; + + case self::TRIM_TRAILING: + $posStr = 'TRAILING '.$trimChar; + break; + + case self::TRIM_BOTH: + $posStr = 'BOTH '.$trimChar; + break; + } + + return 'TRIM(' . $posStr . $str . ')'; + } + + /** + * Returns the SQL snippet to trim trailing space characters from the expression. + * + * @param string $str Literal string or column name. + * + * @return string + */ + public function getRtrimExpression($str) + { + return 'RTRIM(' . $str . ')'; + } + + /** + * Returns the SQL snippet to trim leading space characters from the expression. + * + * @param string $str Literal string or column name. + * + * @return string + */ + public function getLtrimExpression($str) + { + return 'LTRIM(' . $str . ')'; + } + + /** + * Returns the SQL snippet to change all characters from the expression to uppercase, + * according to the current character set mapping. + * + * @param string $str Literal string or column name. + * + * @return string + */ + public function getUpperExpression($str) + { + return 'UPPER(' . $str . ')'; + } + + /** + * Returns the SQL snippet to change all characters from the expression to lowercase, + * according to the current character set mapping. + * + * @param string $str Literal string or column name. + * + * @return string + */ + public function getLowerExpression($str) + { + return 'LOWER(' . $str . ')'; + } + + /** + * Returns the SQL snippet to get the position of the first occurrence of substring $substr in string $str. + * + * @param string $str Literal string. + * @param string $substr Literal string to find. + * @param integer|boolean $startPos Position to start at, beginning of string by default. + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL snippet to get the current system date. + * + * @return string + */ + public function getNowExpression() + { + return 'NOW()'; + } + + /** + * Returns a SQL snippet to get a substring inside an SQL statement. + * + * Note: Not SQL92, but common functionality. + * + * SQLite only supports the 2 parameter variant of this function. + * + * @param string $value An sql string literal or column name/alias. + * @param integer $from Where to start the substring portion. + * @param integer|null $length The substring portion length. + * + * @return string + */ + public function getSubstringExpression($value, $from, $length = null) + { + if ($length === null) { + return 'SUBSTRING(' . $value . ' FROM ' . $from . ')'; + } + + return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $length . ')'; + } + + /** + * Returns a SQL snippet to concatenate the given expressions. + * + * Accepts an arbitrary number of string parameters. Each parameter must contain an expression. + * + * @return string + */ + public function getConcatExpression() + { + return join(' || ' , func_get_args()); + } + + /** + * Returns the SQL for a logical not. + * + * Example: + * + * $q = new Doctrine_Query(); + * $e = $q->expr; + * $q->select('*')->from('table') + * ->where($e->eq('id', $e->not('null')); + * + * + * @param string $expression + * + * @return string The logical expression. + */ + public function getNotExpression($expression) + { + return 'NOT(' . $expression . ')'; + } + + /** + * Returns the SQL to check if a value is one in a set of given values. + * + * Accepts an arbitrary number of parameters. The first parameter + * must always specify the value that should be matched against. Successive + * must contain a logical expression or an array with logical expressions. + * These expressions will be matched against the first parameter. + * + * @param string $column The value that should be matched against. + * @param string|string[] $values The values that will be matched against $column. + * + * @return string The logical expression. + * + * @throws \InvalidArgumentException + */ + public function getInExpression($column, $values) + { + if ( ! is_array($values)) { + $values = array($values); + } + + // TODO: fix this code: the method does not exist + $values = $this->getIdentifiers($values); + + if (count($values) == 0) { + throw new \InvalidArgumentException('Values must not be empty.'); + } + + return $column . ' IN (' . implode(', ', $values) . ')'; + } + + /** + * Returns the SQL that checks if an expression is null. + * + * @param string $expression The expression that should be compared to null. + * + * @return string The logical expression. + */ + public function getIsNullExpression($expression) + { + return $expression . ' IS NULL'; + } + + /** + * Returns the SQL that checks if an expression is not null. + * + * @param string $expression The expression that should be compared to null. + * + * @return string The logical expression. + */ + public function getIsNotNullExpression($expression) + { + return $expression . ' IS NOT NULL'; + } + + /** + * Returns the SQL that checks if an expression evaluates to a value between two values. + * + * The parameter $expression is checked if it is between $value1 and $value2. + * + * Note: There is a slight difference in the way BETWEEN works on some databases. + * http://www.w3schools.com/sql/sql_between.asp. If you want complete database + * independence you should avoid using between(). + * + * @param string $expression The value to compare to. + * @param string $value1 The lower value to compare with. + * @param string $value2 The higher value to compare with. + * + * @return string The logical expression. + */ + public function getBetweenExpression($expression, $value1, $value2) + { + return $expression . ' BETWEEN ' .$value1 . ' AND ' . $value2; + } + + /** + * Returns the SQL to get the arccosine of a value. + * + * @param string $value + * + * @return string + */ + public function getAcosExpression($value) + { + return 'ACOS(' . $value . ')'; + } + + /** + * Returns the SQL to get the sine of a value. + * + * @param string $value + * + * @return string + */ + public function getSinExpression($value) + { + return 'SIN(' . $value . ')'; + } + + /** + * Returns the SQL to get the PI value. + * + * @return string + */ + public function getPiExpression() + { + return 'PI()'; + } + + /** + * Returns the SQL to get the cosine of a value. + * + * @param string $value + * + * @return string + */ + public function getCosExpression($value) + { + return 'COS(' . $value . ')'; + } + + /** + * Returns the SQL to calculate the difference in days between the two passed dates. + * + * Computes diff = date1 - date2. + * + * @param string $date1 + * @param string $date2 + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateDiffExpression($date1, $date2) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL to add the number of given hours to a date. + * + * @param string $date + * @param integer $hours + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateAddHourExpression($date, $hours) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL to subtract the number of given hours to a date. + * + * @param string $date + * @param integer $hours + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateSubHourExpression($date, $hours) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL to add the number of given days to a date. + * + * @param string $date + * @param integer $days + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateAddDaysExpression($date, $days) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL to subtract the number of given days to a date. + * + * @param string $date + * @param integer $days + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateSubDaysExpression($date, $days) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL to add the number of given months to a date. + * + * @param string $date + * @param integer $months + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateAddMonthExpression($date, $months) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL to subtract the number of given months to a date. + * + * @param string $date + * @param integer $months + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateSubMonthExpression($date, $months) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL bit AND comparison expression. + * + * @param string $value1 + * @param string $value2 + * + * @return string + */ + public function getBitAndComparisonExpression($value1, $value2) + { + return '(' . $value1 . ' & ' . $value2 . ')'; + } + + /** + * Returns the SQL bit OR comparison expression. + * + * @param string $value1 + * @param string $value2 + * + * @return string + */ + public function getBitOrComparisonExpression($value1, $value2) + { + return '(' . $value1 . ' | ' . $value2 . ')'; + } + + /** + * Returns the FOR UPDATE expression. + * + * @return string + */ + public function getForUpdateSQL() + { + return 'FOR UPDATE'; + } + + /** + * Honors that some SQL vendors such as MsSql use table hints for locking instead of the ANSI SQL FOR UPDATE specification. + * + * @param string $fromClause + * @param integer $lockMode + * + * @return string + */ + public function appendLockHint($fromClause, $lockMode) + { + return $fromClause; + } + + /** + * Returns the SQL snippet to append to any SELECT statement which locks rows in shared read lock. + * + * This defaults to the ANSI SQL "FOR UPDATE", which is an exclusive lock (Write). Some database + * vendors allow to lighten this constraint up to be a real read lock. + * + * @return string + */ + public function getReadLockSQL() + { + return $this->getForUpdateSQL(); + } + + /** + * Returns the SQL snippet to append to any SELECT statement which obtains an exclusive lock on the rows. + * + * The semantics of this lock mode should equal the SELECT .. FOR UPDATE of the ANSI SQL standard. + * + * @return string + */ + public function getWriteLockSQL() + { + return $this->getForUpdateSQL(); + } + + /** + * Returns the SQL snippet to drop an existing database. + * + * @param string $database The name of the database that should be dropped. + * + * @return string + */ + public function getDropDatabaseSQL($database) + { + return 'DROP DATABASE ' . $database; + } + + /** + * Returns the SQL snippet to drop an existing table. + * + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return string + * + * @throws \InvalidArgumentException + */ + public function getDropTableSQL($table) + { + $tableArg = $table; + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } else if(!is_string($table)) { + throw new \InvalidArgumentException('getDropTableSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.'); + } + + if (null !== $this->_eventManager && $this->_eventManager->hasListeners(Events::onSchemaDropTable)) { + $eventArgs = new SchemaDropTableEventArgs($tableArg, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaDropTable, $eventArgs); + + if ($eventArgs->isDefaultPrevented()) { + return $eventArgs->getSql(); + } + } + + return 'DROP TABLE ' . $table; + } + + /** + * Returns the SQL to safely drop a temporary table WITHOUT implicitly committing an open transaction. + * + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return string + */ + public function getDropTemporaryTableSQL($table) + { + return $this->getDropTableSQL($table); + } + + /** + * Returns the SQL to drop an index from a table. + * + * @param \Doctrine\DBAL\Schema\Index|string $index + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return string + * + * @throws \InvalidArgumentException + */ + public function getDropIndexSQL($index, $table = null) + { + if ($index instanceof Index) { + $index = $index->getQuotedName($this); + } else if(!is_string($index)) { + throw new \InvalidArgumentException('AbstractPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.'); + } + + return 'DROP INDEX ' . $index; + } + + /** + * Returns the SQL to drop a constraint. + * + * @param \Doctrine\DBAL\Schema\Constraint|string $constraint + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return string + */ + public function getDropConstraintSQL($constraint, $table) + { + if ($constraint instanceof Constraint) { + $constraint = $constraint->getQuotedName($this); + } + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $constraint; + } + + /** + * Returns the SQL to drop a foreign key. + * + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint|string $foreignKey + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return string + */ + public function getDropForeignKeySQL($foreignKey, $table) + { + if ($foreignKey instanceof ForeignKeyConstraint) { + $foreignKey = $foreignKey->getQuotedName($this); + } + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + return 'ALTER TABLE ' . $table . ' DROP FOREIGN KEY ' . $foreignKey; + } + + /** + * Returns the SQL statement(s) to create a table with the specified name, columns and constraints + * on this platform. + * + * @param \Doctrine\DBAL\Schema\Table $table + * @param integer $createFlags + * + * @return array The sequence of SQL statements. + * + * @throws \Doctrine\DBAL\DBALException + * @throws \InvalidArgumentException + */ + public function getCreateTableSQL(Table $table, $createFlags = self::CREATE_INDEXES) + { + if ( ! is_int($createFlags)) { + throw new \InvalidArgumentException("Second argument of AbstractPlatform::getCreateTableSQL() has to be integer."); + } + + if (count($table->getColumns()) === 0) { + throw DBALException::noColumnsSpecifiedForTable($table->getName()); + } + + $tableName = $table->getQuotedName($this); + $options = $table->getOptions(); + $options['uniqueConstraints'] = array(); + $options['indexes'] = array(); + $options['primary'] = array(); + + if (($createFlags&self::CREATE_INDEXES) > 0) { + foreach ($table->getIndexes() as $index) { + /* @var $index Index */ + if ($index->isPrimary()) { + $options['primary'] = $index->getQuotedColumns($this); + $options['primary_index'] = $index; + } else { + $options['indexes'][$index->getName()] = $index; + } + } + } + + $columnSql = array(); + $columns = array(); + + foreach ($table->getColumns() as $column) { + /* @var \Doctrine\DBAL\Schema\Column $column */ + + if (null !== $this->_eventManager && $this->_eventManager->hasListeners(Events::onSchemaCreateTableColumn)) { + $eventArgs = new SchemaCreateTableColumnEventArgs($column, $table, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaCreateTableColumn, $eventArgs); + + $columnSql = array_merge($columnSql, $eventArgs->getSql()); + + if ($eventArgs->isDefaultPrevented()) { + continue; + } + } + + $columnData = $column->toArray(); + $columnData['name'] = $column->getQuotedName($this); + $columnData['version'] = $column->hasPlatformOption("version") ? $column->getPlatformOption('version') : false; + $columnData['comment'] = $this->getColumnComment($column); + + if (strtolower($columnData['type']) == "string" && $columnData['length'] === null) { + $columnData['length'] = 255; + } + + if (in_array($column->getName(), $options['primary'])) { + $columnData['primary'] = true; + } + + $columns[$columnData['name']] = $columnData; + } + + if (($createFlags&self::CREATE_FOREIGNKEYS) > 0) { + $options['foreignKeys'] = array(); + foreach ($table->getForeignKeys() as $fkConstraint) { + $options['foreignKeys'][] = $fkConstraint; + } + } + + if (null !== $this->_eventManager && $this->_eventManager->hasListeners(Events::onSchemaCreateTable)) { + $eventArgs = new SchemaCreateTableEventArgs($table, $columns, $options, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaCreateTable, $eventArgs); + + if ($eventArgs->isDefaultPrevented()) { + return array_merge($eventArgs->getSql(), $columnSql); + } + } + + $sql = $this->_getCreateTableSQL($tableName, $columns, $options); + if ($this->supportsCommentOnStatement()) { + foreach ($table->getColumns() as $column) { + if ($this->getColumnComment($column)) { + $sql[] = $this->getCommentOnColumnSQL($tableName, $column->getName(), $this->getColumnComment($column)); + } + } + } + + return array_merge($sql, $columnSql); + } + + /** + * @param string $tableName + * @param string $columnName + * @param string $comment + * + * @return string + */ + public function getCommentOnColumnSQL($tableName, $columnName, $comment) + { + return "COMMENT ON COLUMN " . $tableName . "." . $columnName . " IS '" . $comment . "'"; + } + + /** + * Returns the SQL used to create a table. + * + * @param string $tableName + * @param array $columns + * @param array $options + * + * @return array + */ + protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) + { + $columnListSql = $this->getColumnDeclarationListSQL($columns); + + if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { + foreach ($options['uniqueConstraints'] as $name => $definition) { + $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition); + } + } + + if (isset($options['primary']) && ! empty($options['primary'])) { + $columnListSql .= ', PRIMARY KEY(' . implode(', ', array_unique(array_values($options['primary']))) . ')'; + } + + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach($options['indexes'] as $index => $definition) { + $columnListSql .= ', ' . $this->getIndexDeclarationSQL($index, $definition); + } + } + + $query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql; + + $check = $this->getCheckDeclarationSQL($columns); + if ( ! empty($check)) { + $query .= ', ' . $check; + } + $query .= ')'; + + $sql[] = $query; + + if (isset($options['foreignKeys'])) { + foreach ((array) $options['foreignKeys'] as $definition) { + $sql[] = $this->getCreateForeignKeySQL($definition, $tableName); + } + } + + return $sql; + } + + /** + * @return string + */ + public function getCreateTemporaryTableSnippetSQL() + { + return "CREATE TEMPORARY TABLE"; + } + + /** + * Returns the SQL to create a sequence on this platform. + * + * @param \Doctrine\DBAL\Schema\Sequence $sequence + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getCreateSequenceSQL(Sequence $sequence) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL to change a sequence on this platform. + * + * @param \Doctrine\DBAL\Schema\Sequence $sequence + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getAlterSequenceSQL(Sequence $sequence) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL to create a constraint on a table on this platform. + * + * @param \Doctrine\DBAL\Schema\Constraint $constraint + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return string + * + * @throws \InvalidArgumentException + */ + public function getCreateConstraintSQL(Constraint $constraint, $table) + { + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + $query = 'ALTER TABLE ' . $table . ' ADD CONSTRAINT ' . $constraint->getQuotedName($this); + + $columnList = '('. implode(', ', $constraint->getQuotedColumns($this)) . ')'; + + $referencesClause = ''; + if ($constraint instanceof Index) { + if($constraint->isPrimary()) { + $query .= ' PRIMARY KEY'; + } elseif ($constraint->isUnique()) { + $query .= ' UNIQUE'; + } else { + throw new \InvalidArgumentException( + 'Can only create primary or unique constraints, no common indexes with getCreateConstraintSQL().' + ); + } + } else if ($constraint instanceof ForeignKeyConstraint) { + $query .= ' FOREIGN KEY'; + + $referencesClause = ' REFERENCES ' . $constraint->getQuotedForeignTableName($this) . + ' (' . implode(', ', $constraint->getQuotedForeignColumns($this)) . ')'; + } + $query .= ' '.$columnList.$referencesClause; + + return $query; + } + + /** + * Returns the SQL to create an index on a table on this platform. + * + * @param \Doctrine\DBAL\Schema\Index $index + * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table on which the index is to be created. + * + * @return string + * + * @throws \InvalidArgumentException + */ + public function getCreateIndexSQL(Index $index, $table) + { + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + $name = $index->getQuotedName($this); + $columns = $index->getQuotedColumns($this); + + if (count($columns) == 0) { + throw new \InvalidArgumentException("Incomplete definition. 'columns' required."); + } + + if ($index->isPrimary()) { + return $this->getCreatePrimaryKeySQL($index, $table); + } + + $query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $table; + $query .= ' (' . $this->getIndexFieldDeclarationListSQL($columns) . ')'; + + return $query; + } + + /** + * Adds additional flags for index generation. + * + * @param \Doctrine\DBAL\Schema\Index $index + * + * @return string + */ + protected function getCreateIndexSQLFlags(Index $index) + { + return $index->isUnique() ? 'UNIQUE ' : ''; + } + + /** + * Returns the SQL to create an unnamed primary key constraint. + * + * @param \Doctrine\DBAL\Schema\Index $index + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return string + */ + public function getCreatePrimaryKeySQL(Index $index, $table) + { + return 'ALTER TABLE ' . $table . ' ADD PRIMARY KEY (' . $this->getIndexFieldDeclarationListSQL($index->getQuotedColumns($this)) . ')'; + } + + /** + * Returns the SQL to create a named schema. + * + * @param string $schemaName + * + * @return string + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getCreateSchemaSQL($schemaName) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Checks whether the schema $schemaName needs creating. + * + * @param string $schemaName + * + * @return boolean + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function schemaNeedsCreation($schemaName) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Quotes a string so that it can be safely used as a table or column name, + * even if it is a reserved word of the platform. This also detects identifier + * chains separated by dot and quotes them independently. + * + * NOTE: Just because you CAN use quoted identifiers doesn't mean + * you SHOULD use them. In general, they end up causing way more + * problems than they solve. + * + * @param string $str The identifier name to be quoted. + * + * @return string The quoted identifier string. + */ + public function quoteIdentifier($str) + { + if (strpos($str, ".") !== false) { + $parts = array_map(array($this, "quoteIdentifier"), explode(".", $str)); + + return implode(".", $parts); + } + + return $this->quoteSingleIdentifier($str); + } + + /** + * Quotes a single identifier (no dot chain separation). + * + * @param string $str The identifier name to be quoted. + * + * @return string The quoted identifier string. + */ + public function quoteSingleIdentifier($str) + { + $c = $this->getIdentifierQuoteCharacter(); + + return $c . str_replace($c, $c.$c, $str) . $c; + } + + /** + * Returns the SQL to create a new foreign key. + * + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey The foreign key constraint. + * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table on which the foreign key is to be created. + * + * @return string + */ + public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) + { + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + $query = 'ALTER TABLE ' . $table . ' ADD ' . $this->getForeignKeyDeclarationSQL($foreignKey); + + return $query; + } + + /** + * Gets the SQL statements for altering an existing table. + * + * This method returns an array of SQL statements, since some platforms need several statements. + * + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * + * @return array + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getAlterTableSQL(TableDiff $diff) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @param \Doctrine\DBAL\Schema\Column $column + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * @param array $columnSql + * + * @return boolean + */ + protected function onSchemaAlterTableAddColumn(Column $column, TableDiff $diff, &$columnSql) + { + if (null === $this->_eventManager) { + return false; + } + + if ( ! $this->_eventManager->hasListeners(Events::onSchemaAlterTableAddColumn)) { + return false; + } + + $eventArgs = new SchemaAlterTableAddColumnEventArgs($column, $diff, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableAddColumn, $eventArgs); + + $columnSql = array_merge($columnSql, $eventArgs->getSql()); + + return $eventArgs->isDefaultPrevented(); + } + + /** + * @param \Doctrine\DBAL\Schema\Column $column + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * @param array $columnSql + * + * @return boolean + */ + protected function onSchemaAlterTableRemoveColumn(Column $column, TableDiff $diff, &$columnSql) + { + if (null === $this->_eventManager) { + return false; + } + + if ( ! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRemoveColumn)) { + return false; + } + + $eventArgs = new SchemaAlterTableRemoveColumnEventArgs($column, $diff, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRemoveColumn, $eventArgs); + + $columnSql = array_merge($columnSql, $eventArgs->getSql()); + + return $eventArgs->isDefaultPrevented(); + } + + /** + * @param \Doctrine\DBAL\Schema\ColumnDiff $columnDiff + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * @param array $columnSql + * + * @return boolean + */ + protected function onSchemaAlterTableChangeColumn(ColumnDiff $columnDiff, TableDiff $diff, &$columnSql) + { + if (null === $this->_eventManager) { + return false; + } + + if ( ! $this->_eventManager->hasListeners(Events::onSchemaAlterTableChangeColumn)) { + return false; + } + + $eventArgs = new SchemaAlterTableChangeColumnEventArgs($columnDiff, $diff, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableChangeColumn, $eventArgs); + + $columnSql = array_merge($columnSql, $eventArgs->getSql()); + + return $eventArgs->isDefaultPrevented(); + } + + /** + * @param string $oldColumnName + * @param \Doctrine\DBAL\Schema\Column $column + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * @param array $columnSql + * + * @return boolean + */ + protected function onSchemaAlterTableRenameColumn($oldColumnName, Column $column, TableDiff $diff, &$columnSql) + { + if (null === $this->_eventManager) { + return false; + } + + if ( ! $this->_eventManager->hasListeners(Events::onSchemaAlterTableRenameColumn)) { + return false; + } + + $eventArgs = new SchemaAlterTableRenameColumnEventArgs($oldColumnName, $column, $diff, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaAlterTableRenameColumn, $eventArgs); + + $columnSql = array_merge($columnSql, $eventArgs->getSql()); + + return $eventArgs->isDefaultPrevented(); + } + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * @param array $sql + * + * @return boolean + */ + protected function onSchemaAlterTable(TableDiff $diff, &$sql) + { + if (null === $this->_eventManager) { + return false; + } + + if ( ! $this->_eventManager->hasListeners(Events::onSchemaAlterTable)) { + return false; + } + + $eventArgs = new SchemaAlterTableEventArgs($diff, $this); + $this->_eventManager->dispatchEvent(Events::onSchemaAlterTable, $eventArgs); + + $sql = array_merge($sql, $eventArgs->getSql()); + + return $eventArgs->isDefaultPrevented(); + } + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * + * @return array + */ + protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) + { + $tableName = $diff->name; + + $sql = array(); + if ($this->supportsForeignKeyConstraints()) { + foreach ($diff->removedForeignKeys as $foreignKey) { + $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName); + } + foreach ($diff->changedForeignKeys as $foreignKey) { + $sql[] = $this->getDropForeignKeySQL($foreignKey, $tableName); + } + } + + foreach ($diff->removedIndexes as $index) { + $sql[] = $this->getDropIndexSQL($index, $tableName); + } + foreach ($diff->changedIndexes as $index) { + $sql[] = $this->getDropIndexSQL($index, $tableName); + } + + return $sql; + } + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * + * @return array + */ + protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) + { + $tableName = false !== $diff->newName ? $diff->newName : $diff->name; + + $sql = array(); + if ($this->supportsForeignKeyConstraints()) { + foreach ($diff->addedForeignKeys as $foreignKey) { + $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName); + } + foreach ($diff->changedForeignKeys as $foreignKey) { + $sql[] = $this->getCreateForeignKeySQL($foreignKey, $tableName); + } + } + + foreach ($diff->addedIndexes as $index) { + $sql[] = $this->getCreateIndexSQL($index, $tableName); + } + foreach ($diff->changedIndexes as $index) { + $sql[] = $this->getCreateIndexSQL($index, $tableName); + } + + return $sql; + } + + /** + * Common code for alter table statement generation that updates the changed Index and Foreign Key definitions. + * + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * + * @return array + */ + protected function _getAlterTableIndexForeignKeySQL(TableDiff $diff) + { + return array_merge($this->getPreAlterTableIndexForeignKeySQL($diff), $this->getPostAlterTableIndexForeignKeySQL($diff)); + } + + /** + * Gets declaration of a number of fields in bulk. + * + * @param array $fields A multidimensional associative array. + * The first dimension determines the field name, while the second + * dimension is keyed with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * charset + * Text value with the default CHARACTER SET for this field. + * collation + * Text value with the default COLLATION for this field. + * unique + * unique constraint + * + * @return string + */ + public function getColumnDeclarationListSQL(array $fields) + { + $queryFields = array(); + + foreach ($fields as $fieldName => $field) { + $queryFields[] = $this->getColumnDeclarationSQL($fieldName, $field); + } + + return implode(', ', $queryFields); + } + + /** + * Obtains DBMS specific SQL code portion needed to declare a generic type + * field to be used in statements like CREATE TABLE. + * + * @param string $name The name the field to be declared. + * @param array $field An associative array with the name of the properties + * of the field being declared as array indexes. Currently, the types + * of supported field properties are as follows: + * + * length + * Integer value that determines the maximum length of the text + * field. If this argument is missing the field should be + * declared to have the longest length allowed by the DBMS. + * + * default + * Text value to be used as default for this field. + * + * notnull + * Boolean flag that indicates whether this field is constrained + * to not be set to null. + * charset + * Text value with the default CHARACTER SET for this field. + * collation + * Text value with the default COLLATION for this field. + * unique + * unique constraint + * check + * column check constraint + * columnDefinition + * a string that defines the complete column + * + * @return string DBMS specific SQL code portion that should be used to declare the column. + */ + public function getColumnDeclarationSQL($name, array $field) + { + if (isset($field['columnDefinition'])) { + $columnDef = $this->getCustomTypeDeclarationSQL($field); + } else { + $default = $this->getDefaultValueDeclarationSQL($field); + + $charset = (isset($field['charset']) && $field['charset']) ? + ' ' . $this->getColumnCharsetDeclarationSQL($field['charset']) : ''; + + $collation = (isset($field['collation']) && $field['collation']) ? + ' ' . $this->getColumnCollationDeclarationSQL($field['collation']) : ''; + + $notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : ''; + + $unique = (isset($field['unique']) && $field['unique']) ? + ' ' . $this->getUniqueFieldDeclarationSQL() : ''; + + $check = (isset($field['check']) && $field['check']) ? + ' ' . $field['check'] : ''; + + $typeDecl = $field['type']->getSqlDeclaration($field, $this); + $columnDef = $typeDecl . $charset . $default . $notnull . $unique . $check . $collation; + } + + if ($this->supportsInlineColumnComments() && isset($field['comment']) && $field['comment']) { + $columnDef .= " COMMENT '" . $field['comment'] . "'"; + } + + return $name . ' ' . $columnDef; + } + + /** + * Returns the SQL snippet that declares a floating point column of arbitrary precision. + * + * @param array $columnDef + * + * @return string + */ + public function getDecimalTypeDeclarationSQL(array $columnDef) + { + $columnDef['precision'] = ( ! isset($columnDef['precision']) || empty($columnDef['precision'])) + ? 10 : $columnDef['precision']; + $columnDef['scale'] = ( ! isset($columnDef['scale']) || empty($columnDef['scale'])) + ? 0 : $columnDef['scale']; + + return 'NUMERIC(' . $columnDef['precision'] . ', ' . $columnDef['scale'] . ')'; + } + + /** + * Obtains DBMS specific SQL code portion needed to set a default value + * declaration to be used in statements like CREATE TABLE. + * + * @param array $field The field definition array. + * + * @return string DBMS specific SQL code portion needed to set a default value. + */ + public function getDefaultValueDeclarationSQL($field) + { + $default = empty($field['notnull']) ? ' DEFAULT NULL' : ''; + + if (isset($field['default'])) { + $default = " DEFAULT '".$field['default']."'"; + if (isset($field['type'])) { + if (in_array((string)$field['type'], array("Integer", "BigInteger", "SmallInteger"))) { + $default = " DEFAULT ".$field['default']; + } else if ((string)$field['type'] == 'DateTime' && $field['default'] == $this->getCurrentTimestampSQL()) { + $default = " DEFAULT ".$this->getCurrentTimestampSQL(); + } else if ((string)$field['type'] == 'Time' && $field['default'] == $this->getCurrentTimeSQL()) { + $default = " DEFAULT ".$this->getCurrentTimeSQL(); + } else if ((string)$field['type'] == 'Date' && $field['default'] == $this->getCurrentDateSQL()) { + $default = " DEFAULT ".$this->getCurrentDateSQL(); + } else if ((string) $field['type'] == 'Boolean') { + $default = " DEFAULT '" . $this->convertBooleans($field['default']) . "'"; + } + } + } + + return $default; + } + + /** + * Obtains DBMS specific SQL code portion needed to set a CHECK constraint + * declaration to be used in statements like CREATE TABLE. + * + * @param array $definition The check definition. + * + * @return string DBMS specific SQL code portion needed to set a CHECK constraint. + */ + public function getCheckDeclarationSQL(array $definition) + { + $constraints = array(); + foreach ($definition as $field => $def) { + if (is_string($def)) { + $constraints[] = 'CHECK (' . $def . ')'; + } else { + if (isset($def['min'])) { + $constraints[] = 'CHECK (' . $field . ' >= ' . $def['min'] . ')'; + } + + if (isset($def['max'])) { + $constraints[] = 'CHECK (' . $field . ' <= ' . $def['max'] . ')'; + } + } + } + + return implode(', ', $constraints); + } + + /** + * Obtains DBMS specific SQL code portion needed to set a unique + * constraint declaration to be used in statements like CREATE TABLE. + * + * @param string $name The name of the unique constraint. + * @param \Doctrine\DBAL\Schema\Index $index The index definition. + * + * @return string DBMS specific SQL code portion needed to set a constraint. + * + * @throws \InvalidArgumentException + */ + public function getUniqueConstraintDeclarationSQL($name, Index $index) + { + $columns = $index->getQuotedColumns($this); + + if (count($columns) === 0) { + throw new \InvalidArgumentException("Incomplete definition. 'columns' required."); + } + + return 'CONSTRAINT ' . $name . ' UNIQUE (' + . $this->getIndexFieldDeclarationListSQL($columns) + . ')'; + } + + /** + * Obtains DBMS specific SQL code portion needed to set an index + * declaration to be used in statements like CREATE TABLE. + * + * @param string $name The name of the index. + * @param \Doctrine\DBAL\Schema\Index $index The index definition. + * + * @return string DBMS specific SQL code portion needed to set an index. + * + * @throws \InvalidArgumentException + */ + public function getIndexDeclarationSQL($name, Index $index) + { + $columns = $index->getQuotedColumns($this); + + if (count($columns) === 0) { + throw new \InvalidArgumentException("Incomplete definition. 'columns' required."); + } + + return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' (' + . $this->getIndexFieldDeclarationListSQL($columns) + . ')'; + } + + /** + * Obtains SQL code portion needed to create a custom column, + * e.g. when a field has the "columnDefinition" keyword. + * Only "AUTOINCREMENT" and "PRIMARY KEY" are added if appropriate. + * + * @param array $columnDef + * + * @return string + */ + public function getCustomTypeDeclarationSQL(array $columnDef) + { + return $columnDef['columnDefinition']; + } + + /** + * Obtains DBMS specific SQL code portion needed to set an index + * declaration to be used in statements like CREATE TABLE. + * + * @param array $fields + * + * @return string + */ + public function getIndexFieldDeclarationListSQL(array $fields) + { + $ret = array(); + + foreach ($fields as $field => $definition) { + if (is_array($definition)) { + $ret[] = $field; + } else { + $ret[] = $definition; + } + } + + return implode(', ', $ret); + } + + /** + * Returns the required SQL string that fits between CREATE ... TABLE + * to create the table as a temporary table. + * + * Should be overridden in driver classes to return the correct string for the + * specific database type. + * + * The default is to return the string "TEMPORARY" - this will result in a + * SQL error for any database that does not support temporary tables, or that + * requires a different SQL command from "CREATE TEMPORARY TABLE". + * + * @return string The string required to be placed between "CREATE" and "TABLE" + * to generate a temporary table, if possible. + */ + public function getTemporaryTableSQL() + { + return 'TEMPORARY'; + } + + /** + * Some vendors require temporary table names to be qualified specially. + * + * @param string $tableName + * + * @return string + */ + public function getTemporaryTableName($tableName) + { + return $tableName; + } + + /** + * Obtain DBMS specific SQL code portion needed to set the FOREIGN KEY constraint + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey + * + * @return string DBMS specific SQL code portion needed to set the FOREIGN KEY constraint + * of a field declaration. + */ + public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) + { + $sql = $this->getForeignKeyBaseDeclarationSQL($foreignKey); + $sql .= $this->getAdvancedForeignKeyOptionsSQL($foreignKey); + + return $sql; + } + + /** + * Returns the FOREIGN KEY query section dealing with non-standard options + * as MATCH, INITIALLY DEFERRED, ON UPDATE, ... + * + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey The foreign key definition. + * + * @return string + */ + public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) + { + $query = ''; + if ($this->supportsForeignKeyOnUpdate() && $foreignKey->hasOption('onUpdate')) { + $query .= ' ON UPDATE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onUpdate')); + } + if ($foreignKey->hasOption('onDelete')) { + $query .= ' ON DELETE ' . $this->getForeignKeyReferentialActionSQL($foreignKey->getOption('onDelete')); + } + + return $query; + } + + /** + * Returns the given referential action in uppercase if valid, otherwise throws an exception. + * + * @param string $action The foreign key referential action. + * + * @return string + * + * @throws \InvalidArgumentException if unknown referential action given + */ + public function getForeignKeyReferentialActionSQL($action) + { + $upper = strtoupper($action); + switch ($upper) { + case 'CASCADE': + case 'SET NULL': + case 'NO ACTION': + case 'RESTRICT': + case 'SET DEFAULT': + return $upper; + default: + throw new \InvalidArgumentException('Invalid foreign key action: ' . $upper); + } + } + + /** + * Obtains DBMS specific SQL code portion needed to set the FOREIGN KEY constraint + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey + * + * @return string + * + * @throws \InvalidArgumentException + */ + public function getForeignKeyBaseDeclarationSQL(ForeignKeyConstraint $foreignKey) + { + $sql = ''; + if (strlen($foreignKey->getName())) { + $sql .= 'CONSTRAINT ' . $foreignKey->getQuotedName($this) . ' '; + } + $sql .= 'FOREIGN KEY ('; + + if (count($foreignKey->getLocalColumns()) === 0) { + throw new \InvalidArgumentException("Incomplete definition. 'local' required."); + } + if (count($foreignKey->getForeignColumns()) === 0) { + throw new \InvalidArgumentException("Incomplete definition. 'foreign' required."); + } + if (strlen($foreignKey->getForeignTableName()) === 0) { + throw new \InvalidArgumentException("Incomplete definition. 'foreignTable' required."); + } + + $sql .= implode(', ', $foreignKey->getQuotedLocalColumns($this)) + . ') REFERENCES ' + . $foreignKey->getQuotedForeignTableName($this) . ' (' + . implode(', ', $foreignKey->getQuotedForeignColumns($this)) . ')'; + + return $sql; + } + + /** + * Obtains DBMS specific SQL code portion needed to set the UNIQUE constraint + * of a field declaration to be used in statements like CREATE TABLE. + * + * @return string DBMS specific SQL code portion needed to set the UNIQUE constraint + * of a field declaration. + */ + public function getUniqueFieldDeclarationSQL() + { + return 'UNIQUE'; + } + + /** + * Obtains DBMS specific SQL code portion needed to set the CHARACTER SET + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param string $charset The name of the charset. + * + * @return string DBMS specific SQL code portion needed to set the CHARACTER SET + * of a field declaration. + */ + public function getColumnCharsetDeclarationSQL($charset) + { + return ''; + } + + /** + * Obtains DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param string $collation The name of the collation. + * + * @return string DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration. + */ + public function getColumnCollationDeclarationSQL($collation) + { + return ''; + } + + /** + * Whether the platform prefers sequences for ID generation. + * Subclasses should override this method to return TRUE if they prefer sequences. + * + * @return boolean + */ + public function prefersSequences() + { + return false; + } + + /** + * Whether the platform prefers identity columns (eg. autoincrement) for ID generation. + * Subclasses should override this method to return TRUE if they prefer identity columns. + * + * @return boolean + */ + public function prefersIdentityColumns() + { + return false; + } + + /** + * Some platforms need the boolean values to be converted. + * + * The default conversion in this implementation converts to integers (false => 0, true => 1). + * + * @param mixed $item + * + * @return mixed + */ + public function convertBooleans($item) + { + if (is_array($item)) { + foreach ($item as $k => $value) { + if (is_bool($value)) { + $item[$k] = (int) $value; + } + } + } else if (is_bool($item)) { + $item = (int) $item; + } + + return $item; + } + + /** + * Returns the SQL specific for the platform to get the current date. + * + * @return string + */ + public function getCurrentDateSQL() + { + return 'CURRENT_DATE'; + } + + /** + * Returns the SQL specific for the platform to get the current time. + * + * @return string + */ + public function getCurrentTimeSQL() + { + return 'CURRENT_TIME'; + } + + /** + * Returns the SQL specific for the platform to get the current timestamp + * + * @return string + */ + public function getCurrentTimestampSQL() + { + return 'CURRENT_TIMESTAMP'; + } + + /** + * Returns the SQL for a given transaction isolation level Connection constant. + * + * @param integer $level + * + * @return string + * + * @throws \InvalidArgumentException + */ + protected function _getTransactionIsolationLevelSQL($level) + { + switch ($level) { + case Connection::TRANSACTION_READ_UNCOMMITTED: + return 'READ UNCOMMITTED'; + case Connection::TRANSACTION_READ_COMMITTED: + return 'READ COMMITTED'; + case Connection::TRANSACTION_REPEATABLE_READ: + return 'REPEATABLE READ'; + case Connection::TRANSACTION_SERIALIZABLE: + return 'SERIALIZABLE'; + default: + throw new \InvalidArgumentException('Invalid isolation level:' . $level); + } + } + + /** + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListDatabasesSQL() + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @param string $database + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListSequencesSQL($database) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @param string $table + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListTableConstraintsSQL($table) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @param string $table + * @param string|null $database + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListTableColumnsSQL($table, $database = null) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListTablesSQL() + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListUsersSQL() + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL to list all views of a database or user. + * + * @param string $database + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListViewsSQL($database) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the list of indexes for the current database. + * + * The current database parameter is optional but will always be passed + * when using the SchemaManager API and is the database the given table is in. + * + * Attention: Some platforms only support currentDatabase when they + * are connected with that database. Cross-database information schema + * requests may be impossible. + * + * @param string $table + * @param string $currentDatabase + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListTableIndexesSQL($table, $currentDatabase = null) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @param string $table + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getListTableForeignKeysSQL($table) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @param string $name + * @param string $sql + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getCreateViewSQL($name, $sql) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @param string $name + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDropViewSQL($name) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL snippet to drop an existing sequence. + * + * @param \Doctrine\DBAL\Schema\Sequence $sequence + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDropSequenceSQL($sequence) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @param string $sequenceName + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getSequenceNextValSQL($sequenceName) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL to create a new database. + * + * @param string $database The name of the database that should be created. + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getCreateDatabaseSQL($database) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Returns the SQL to set the transaction isolation level. + * + * @param integer $level + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getSetTransactionIsolationSQL($level) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Obtains DBMS specific SQL to be used to create datetime fields in + * statements like CREATE TABLE. + * + * @param array $fieldDeclaration + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Obtains DBMS specific SQL to be used to create datetime with timezone offset fields. + * + * @param array $fieldDeclaration + * + * @return string + */ + public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration) + { + return $this->getDateTimeTypeDeclarationSQL($fieldDeclaration); + } + + + /** + * Obtains DBMS specific SQL to be used to create date fields in statements + * like CREATE TABLE. + * + * @param array $fieldDeclaration + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * Obtains DBMS specific SQL to be used to create time fields in statements + * like CREATE TABLE. + * + * @param array $fieldDeclaration + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * @param array $fieldDeclaration + * + * @return string + */ + public function getFloatDeclarationSQL(array $fieldDeclaration) + { + return 'DOUBLE PRECISION'; + } + + /** + * Gets the default transaction isolation level of the platform. + * + * @return integer The default isolation level. + * + * @see Doctrine\DBAL\Connection\TRANSACTION_* constants. + */ + public function getDefaultTransactionIsolationLevel() + { + return Connection::TRANSACTION_READ_COMMITTED; + } + + /* supports*() methods */ + + /** + * Whether the platform supports sequences. + * + * @return boolean + */ + public function supportsSequences() + { + return false; + } + + /** + * Whether the platform supports identity columns. + * + * Identity columns are columns that receive an auto-generated value from the + * database on insert of a row. + * + * @return boolean + */ + public function supportsIdentityColumns() + { + return false; + } + + /** + * Whether the platform supports indexes. + * + * @return boolean + */ + public function supportsIndexes() + { + return true; + } + + /** + * Whether the platform supports altering tables. + * + * @return boolean + */ + public function supportsAlterTable() + { + return true; + } + + /** + * Whether the platform supports transactions. + * + * @return boolean + */ + public function supportsTransactions() + { + return true; + } + + /** + * Whether the platform supports savepoints. + * + * @return boolean + */ + public function supportsSavepoints() + { + return true; + } + + /** + * Whether the platform supports releasing savepoints. + * + * @return boolean + */ + public function supportsReleaseSavepoints() + { + return $this->supportsSavepoints(); + } + + /** + * Whether the platform supports primary key constraints. + * + * @return boolean + */ + public function supportsPrimaryConstraints() + { + return true; + } + + /** + * Whether the platform supports foreign key constraints. + * + * @return boolean + */ + public function supportsForeignKeyConstraints() + { + return true; + } + + /** + * Whether this platform supports onUpdate in foreign key constraints. + * + * @return boolean + */ + public function supportsForeignKeyOnUpdate() + { + return ($this->supportsForeignKeyConstraints() && true); + } + + /** + * Whether the platform supports database schemas. + * + * @return boolean + */ + public function supportsSchemas() + { + return false; + } + + /** + * Whether this platform can emulate schemas. + * + * Platforms that either support or emulate schemas don't automatically + * filter a schema for the namespaced elements in {@link + * AbstractManager#createSchema}. + * + * @return boolean + */ + public function canEmulateSchemas() + { + return false; + } + + /** + * Whether this platform supports create database. + * + * Some databases don't allow to create and drop databases at all or only with certain tools. + * + * @return boolean + */ + public function supportsCreateDropDatabase() + { + return true; + } + + /** + * Whether the platform supports getting the affected rows of a recent update/delete type query. + * + * @return boolean + */ + public function supportsGettingAffectedRows() + { + return true; + } + + /** + * Whether this platform support to add inline column comments as postfix. + * + * @return boolean + */ + public function supportsInlineColumnComments() + { + return false; + } + + /** + * Whether this platform support the proprietary syntax "COMMENT ON asset". + * + * @return boolean + */ + public function supportsCommentOnStatement() + { + return false; + } + + /** + * Does this platform have native guid type. + * + * @return boolean + */ + public function hasNativeGuidType() + { + return false; + } + + /** + * @deprecated + * @todo Remove in 3.0 + */ + public function getIdentityColumnNullInsertSQL() + { + return ""; + } + + /** + * Whether this platform supports views. + * + * @return boolean + */ + public function supportsViews() + { + return true; + } + + /** + * Gets the format string, as accepted by the date() function, that describes + * the format of a stored datetime value of this platform. + * + * @return string The format string. + */ + public function getDateTimeFormatString() + { + return 'Y-m-d H:i:s'; + } + + /** + * Gets the format string, as accepted by the date() function, that describes + * the format of a stored datetime with timezone value of this platform. + * + * @return string The format string. + */ + public function getDateTimeTzFormatString() + { + return 'Y-m-d H:i:s'; + } + + /** + * Gets the format string, as accepted by the date() function, that describes + * the format of a stored date value of this platform. + * + * @return string The format string. + */ + public function getDateFormatString() + { + return 'Y-m-d'; + } + + /** + * Gets the format string, as accepted by the date() function, that describes + * the format of a stored time value of this platform. + * + * @return string The format string. + */ + public function getTimeFormatString() + { + return 'H:i:s'; + } + + /** + * Adds an driver-specific LIMIT clause to the query. + * + * @param string $query + * @param integer|null $limit + * @param integer|null $offset + * + * @return string + * + * @throws DBALException + */ + final public function modifyLimitQuery($query, $limit, $offset = null) + { + if ($limit !== null) { + $limit = (int)$limit; + } + + if ($offset !== null) { + $offset = (int)$offset; + + if ($offset < 0) { + throw new DBALException("LIMIT argument offset=$offset is not valid"); + } + if ($offset > 0 && ! $this->supportsLimitOffset()) { + throw new DBALException(sprintf("Platform %s does not support offset values in limit queries.", $this->getName())); + } + } + + return $this->doModifyLimitQuery($query, $limit, $offset); + } + + /** + * Adds an driver-specific LIMIT clause to the query. + * + * @param string $query + * @param integer|null $limit + * @param integer|null $offset + * + * @return string + */ + protected function doModifyLimitQuery($query, $limit, $offset) + { + if ($limit !== null) { + $query .= ' LIMIT ' . $limit; + } + + if ($offset !== null) { + $query .= ' OFFSET ' . $offset; + } + + return $query; + } + + /** + * Whether the database platform support offsets in modify limit clauses. + * + * @return boolean + */ + public function supportsLimitOffset() + { + return true; + } + + /** + * Gets the character casing of a column in an SQL result set of this platform. + * + * @param string $column The column name for which to get the correct character casing. + * + * @return string The column name in the character casing used in SQL result sets. + */ + public function getSQLResultCasing($column) + { + return $column; + } + + /** + * Makes any fixes to a name of a schema element (table, sequence, ...) that are required + * by restrictions of the platform, like a maximum length. + * + * @param string $schemaElementName + * + * @return string + */ + public function fixSchemaElementName($schemaElementName) + { + return $schemaElementName; + } + + /** + * Maximum length of any given database identifier, like tables or column names. + * + * @return integer + */ + public function getMaxIdentifierLength() + { + return 63; + } + + /** + * Returns the insert SQL for an empty insert statement. + * + * @param string $tableName + * @param string $identifierColumnName + * + * @return string + */ + public function getEmptyIdentityInsertSQL($tableName, $identifierColumnName) + { + return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (null)'; + } + + /** + * Generates a Truncate Table SQL statement for a given table. + * + * Cascade is not supported on many platforms but would optionally cascade the truncate by + * following the foreign keys. + * + * @param string $tableName + * @param boolean $cascade + * + * @return string + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + return 'TRUNCATE '.$tableName; + } + + /** + * This is for test reasons, many vendors have special requirements for dummy statements. + * + * @return string + */ + public function getDummySelectSQL() + { + return 'SELECT 1'; + } + + /** + * Returns the SQL to create a new savepoint. + * + * @param string $savepoint + * + * @return string + */ + public function createSavePoint($savepoint) + { + return 'SAVEPOINT ' . $savepoint; + } + + /** + * Returns the SQL to release a savepoint. + * + * @param string $savepoint + * + * @return string + */ + public function releaseSavePoint($savepoint) + { + return 'RELEASE SAVEPOINT ' . $savepoint; + } + + /** + * Returns the SQL to rollback a savepoint. + * + * @param string $savepoint + * + * @return string + */ + public function rollbackSavePoint($savepoint) + { + return 'ROLLBACK TO SAVEPOINT ' . $savepoint; + } + + /** + * Returns the keyword list instance of this platform. + * + * @return \Doctrine\DBAL\Platforms\Keywords\KeywordList + * + * @throws \Doctrine\DBAL\DBALException If no keyword list is specified. + */ + final public function getReservedKeywordsList() + { + // Check for an existing instantiation of the keywords class. + if ($this->_keywords) { + return $this->_keywords; + } + + $class = $this->getReservedKeywordsClass(); + $keywords = new $class; + if ( ! $keywords instanceof \Doctrine\DBAL\Platforms\Keywords\KeywordList) { + throw DBALException::notSupported(__METHOD__); + } + + // Store the instance so it doesn't need to be generated on every request. + $this->_keywords = $keywords; + + return $keywords; + } + + /** + * Returns the class name of the reserved keywords list. + * + * @return string + * + * @throws \Doctrine\DBAL\DBALException If not supported on this platform. + */ + protected function getReservedKeywordsClass() + { + throw DBALException::notSupported(__METHOD__); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/DB2Platform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/DB2Platform.php new file mode 100644 index 0000000..b3e6adf --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/DB2Platform.php @@ -0,0 +1,580 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\TableDiff; + +class DB2Platform extends AbstractPlatform +{ + /** + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $field) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * {@inheritDoc} + */ + public function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = array( + 'smallint' => 'smallint', + 'bigint' => 'bigint', + 'integer' => 'integer', + 'time' => 'time', + 'date' => 'date', + 'varchar' => 'string', + 'character' => 'string', + 'clob' => 'text', + 'decimal' => 'decimal', + 'double' => 'float', + 'real' => 'float', + 'timestamp' => 'datetime', + ); + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') + : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $field) + { + // todo clob(n) with $field['length']; + return 'CLOB(1M)'; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'db2'; + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $columnDef) + { + return 'SMALLINT'; + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $columnDef) + { + return 'INTEGER' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef); + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $columnDef) + { + return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef); + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $columnDef) + { + return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($columnDef); + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) + { + $autoinc = ''; + if ( ! empty($columnDef['autoincrement'])) { + $autoinc = ' GENERATED BY DEFAULT AS IDENTITY'; + } + + return $autoinc; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] == true) { + return "TIMESTAMP(0) WITH DEFAULT"; + } + + return 'TIMESTAMP(0)'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIME'; + } + + /** + * {@inheritDoc} + */ + public function getListDatabasesSQL() + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * {@inheritDoc} + */ + public function getListSequencesSQL($database) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * {@inheritDoc} + */ + public function getListTableConstraintsSQL($table) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * This code fragment is originally from the Zend_Db_Adapter_Db2 class. + * + * @license New BSD License + * + * @param string $table + * @param string $database + * + * @return string + */ + public function getListTableColumnsSQL($table, $database = null) + { + return "SELECT DISTINCT c.tabschema, c.tabname, c.colname, c.colno, + c.typename, c.default, c.nulls, c.length, c.scale, + c.identity, tc.type AS tabconsttype, k.colseq + FROM syscat.columns c + LEFT JOIN (syscat.keycoluse k JOIN syscat.tabconst tc + ON (k.tabschema = tc.tabschema + AND k.tabname = tc.tabname + AND tc.type = 'P')) + ON (c.tabschema = k.tabschema + AND c.tabname = k.tabname + AND c.colname = k.colname) + WHERE UPPER(c.tabname) = UPPER('" . $table . "') ORDER BY c.colno"; + } + + /** + * {@inheritDoc} + */ + public function getListTablesSQL() + { + return "SELECT NAME FROM SYSIBM.SYSTABLES WHERE TYPE = 'T'"; + } + + /** + * {@inheritDoc} + */ + public function getListUsersSQL() + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * {@inheritDoc} + */ + public function getListViewsSQL($database) + { + return "SELECT NAME, TEXT FROM SYSIBM.SYSVIEWS"; + } + + /** + * {@inheritDoc} + */ + public function getListTableIndexesSQL($table, $currentDatabase = null) + { + return "SELECT NAME, COLNAMES, UNIQUERULE FROM SYSIBM.SYSINDEXES WHERE TBNAME = UPPER('" . $table . "')"; + } + + /** + * {@inheritDoc} + */ + public function getListTableForeignKeysSQL($table) + { + return "SELECT TBNAME, RELNAME, REFTBNAME, DELETERULE, UPDATERULE, FKCOLNAMES, PKCOLNAMES ". + "FROM SYSIBM.SYSRELS WHERE TBNAME = UPPER('".$table."')"; + } + + /** + * {@inheritDoc} + */ + public function getCreateViewSQL($name, $sql) + { + return "CREATE VIEW ".$name." AS ".$sql; + } + + /** + * {@inheritDoc} + */ + public function getDropViewSQL($name) + { + return "DROP VIEW ".$name; + } + + /** + * {@inheritDoc} + */ + public function getDropSequenceSQL($sequence) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * {@inheritDoc} + */ + public function getSequenceNextValSQL($sequenceName) + { + throw DBALException::notSupported(__METHOD__); + } + + /** + * {@inheritDoc} + */ + public function getCreateDatabaseSQL($database) + { + return "CREATE DATABASE ".$database; + } + + /** + * {@inheritDoc} + */ + public function getDropDatabaseSQL($database) + { + return "DROP DATABASE ".$database.";"; + } + + /** + * {@inheritDoc} + */ + public function supportsCreateDropDatabase() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function supportsReleaseSavepoints() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function getCurrentDateSQL() + { + return 'VALUES CURRENT DATE'; + } + + /** + * {@inheritDoc} + */ + public function getCurrentTimeSQL() + { + return 'VALUES CURRENT TIME'; + } + + /** + * {@inheritDoc} + */ + public function getCurrentTimestampSQL() + { + return "VALUES CURRENT TIMESTAMP"; + } + + /** + * {@inheritDoc} + */ + public function getIndexDeclarationSQL($name, Index $index) + { + return $this->getUniqueConstraintDeclarationSQL($name, $index); + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) + { + $indexes = array(); + if (isset($options['indexes'])) { + $indexes = $options['indexes']; + } + $options['indexes'] = array(); + + $sqls = parent::_getCreateTableSQL($tableName, $columns, $options); + + foreach ($indexes as $definition) { + $sqls[] = $this->getCreateIndexSQL($definition, $tableName); + } + return $sqls; + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $sql = array(); + $columnSql = array(); + + $queryParts = array(); + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $queryParts[] = 'ADD COLUMN ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); + } + + foreach ($diff->removedColumns as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this); + } + + foreach ($diff->changedColumns as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + /* @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */ + $column = $columnDiff->column; + $queryParts[] = 'ALTER ' . ($columnDiff->oldColumnName) . ' ' + . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $queryParts[] = 'RENAME ' . $oldColumnName . ' TO ' . $column->getQuotedName($this); + } + + $tableSql = array(); + + if ( ! $this->onSchemaAlterTable($diff, $tableSql)) { + if (count($queryParts) > 0) { + $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . implode(" ", $queryParts); + } + + $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff)); + + if ($diff->newName !== false) { + $sql[] = 'RENAME TABLE TO ' . $diff->newName; + } + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * {@inheritDoc} + */ + public function getDefaultValueDeclarationSQL($field) + { + if (isset($field['notnull']) && $field['notnull'] && !isset($field['default'])) { + if (in_array((string)$field['type'], array("Integer", "BigInteger", "SmallInteger"))) { + $field['default'] = 0; + } else if((string)$field['type'] == "DateTime") { + $field['default'] = "00-00-00 00:00:00"; + } else if ((string)$field['type'] == "Date") { + $field['default'] = "00-00-00"; + } else if((string)$field['type'] == "Time") { + $field['default'] = "00:00:00"; + } else { + $field['default'] = ''; + } + } + + unset($field['default']); // @todo this needs fixing + if (isset($field['version']) && $field['version']) { + if ((string)$field['type'] != "DateTime") { + $field['default'] = "1"; + } + } + + return parent::getDefaultValueDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getEmptyIdentityInsertSQL($tableName, $identifierColumnName) + { + return 'INSERT INTO ' . $tableName . ' (' . $identifierColumnName . ') VALUES (DEFAULT)'; + } + + /** + * {@inheritDoc} + */ + public function getCreateTemporaryTableSnippetSQL() + { + return "DECLARE GLOBAL TEMPORARY TABLE"; + } + + /** + * {@inheritDoc} + */ + public function getTemporaryTableName($tableName) + { + return "SESSION." . $tableName; + } + + /** + * {@inheritDoc} + */ + protected function doModifyLimitQuery($query, $limit, $offset = null) + { + if ($limit === null && $offset === null) { + return $query; + } + + $limit = (int)$limit; + $offset = (int)(($offset)?:0); + + // Todo OVER() needs ORDER BY data! + $sql = 'SELECT db22.* FROM (SELECT ROW_NUMBER() OVER() AS DC_ROWNUM, db21.* '. + 'FROM (' . $query . ') db21) db22 WHERE db22.DC_ROWNUM BETWEEN ' . ($offset+1) .' AND ' . ($offset+$limit); + + return $sql; + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos == false) { + return 'LOCATE(' . $substr . ', ' . $str . ')'; + } + + return 'LOCATE(' . $substr . ', ' . $str . ', '.$startPos.')'; + } + + /** + * {@inheritDoc} + */ + public function getSubstringExpression($value, $from, $length = null) + { + if ($length === null) { + return 'SUBSTR(' . $value . ', ' . $from . ')'; + } + + return 'SUBSTR(' . $value . ', ' . $from . ', ' . $length . ')'; + } + + /** + * {@inheritDoc} + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function prefersIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + * + * DB2 returns all column names in SQL result sets in uppercase. + */ + public function getSQLResultCasing($column) + { + return strtoupper($column); + } + + /** + * {@inheritDoc} + */ + public function getForUpdateSQL() + { + return ' WITH RR USE AND KEEP UPDATE LOCKS'; + } + + /** + * {@inheritDoc} + */ + public function getDummySelectSQL() + { + return 'SELECT 1 FROM sysibm.sysdummy1'; + } + + /** + * {@inheritDoc} + * + * DB2 supports savepoints, but they work semantically different than on other vendor platforms. + * + * TODO: We have to investigate how to get DB2 up and running with savepoints. + */ + public function supportsSavepoints() + { + return false; + } + + /** + * {@inheritDoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\DB2Keywords'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/DrizzlePlatform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/DrizzlePlatform.php new file mode 100644 index 0000000..3f4219a --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/DrizzlePlatform.php @@ -0,0 +1,520 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Schema\TableDiff; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Table; + +/** + * Drizzle platform + * + * @author Kim Hemsø Rasmussen + */ +class DrizzlePlatform extends AbstractPlatform +{ + /** + * {@inheritDoc} + */ + public function getName() + { + return 'drizzle'; + } + + /** + * {@inheritDoc} + */ + public function getIdentifierQuoteCharacter() + { + return '`'; + } + + /** + * {@inheritDoc} + */ + public function getConcatExpression() + { + $args = func_get_args(); + + return 'CONCAT(' . join(', ', (array) $args) . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return 'DATEDIFF(' . $date1 . ', ' . $date2 . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateAddHourExpression($date, $hours) + { + return 'DATE_ADD(' . $date . ', INTERVAL ' . $hours . ' HOUR)'; + } + + /** + * {@inheritDoc} + */ + public function getDateSubHourExpression($date, $hours) + { + return 'DATE_SUB(' . $date . ', INTERVAL ' . $hours . ' HOUR)'; + } + + /** + * {@inheritDoc} + */ + public function getDateAddDaysExpression($date, $days) + { + return 'DATE_ADD(' . $date . ', INTERVAL ' . $days . ' DAY)'; + } + + /** + * {@inheritDoc} + */ + public function getDateSubDaysExpression($date, $days) + { + return 'DATE_SUB(' . $date . ', INTERVAL ' . $days . ' DAY)'; + } + + /** + * {@inheritDoc} + */ + public function getDateAddMonthExpression($date, $months) + { + return 'DATE_ADD(' . $date . ', INTERVAL ' . $months . ' MONTH)'; + } + + /** + * {@inheritDoc} + */ + public function getDateSubMonthExpression($date, $months) + { + return 'DATE_SUB(' . $date . ', INTERVAL ' . $months . ' MONTH)'; + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $field) + { + return 'BOOLEAN'; + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $field) + { + return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) + { + $autoinc = ''; + if ( ! empty($columnDef['autoincrement'])) { + $autoinc = ' AUTO_INCREMENT'; + } + return $autoinc; + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $field) + { + return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $field) + { + return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = array( + 'boolean' => 'boolean', + 'varchar' => 'string', + 'integer' => 'integer', + 'blob' => 'text', + 'decimal' => 'decimal', + 'datetime' => 'datetime', + 'date' => 'date', + 'time' => 'time', + 'text' => 'text', + 'timestamp' => 'datetime', + 'double' => 'float', + 'bigint' => 'bigint', + ); + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $field) + { + return 'TEXT'; + } + + /** + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $field) + { + return 'BLOB'; + } + + /** + * {@inheritDoc} + */ + public function getCreateDatabaseSQL($name) + { + return 'CREATE DATABASE ' . $name; + } + + /** + * {@inheritDoc} + */ + public function getDropDatabaseSQL($name) + { + return 'DROP DATABASE ' . $name; + } + + /** + * {@inheritDoc} + */ + public function getListDatabasesSQL() + { + return "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE CATALOG_NAME='LOCAL'"; + } + + /** + * {@inheritDoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\DrizzleKeywords'; + } + + /** + * {@inheritDoc} + */ + public function getListTablesSQL() + { + return "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE' AND TABLE_SCHEMA=DATABASE()"; + } + + /** + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + if ($database) { + $database = "'" . $database . "'"; + } else { + $database = 'DATABASE()'; + } + + return "SELECT COLUMN_NAME, DATA_TYPE, COLUMN_COMMENT, IS_NULLABLE, IS_AUTO_INCREMENT, CHARACTER_MAXIMUM_LENGTH, COLUMN_DEFAULT," . + " NUMERIC_PRECISION, NUMERIC_SCALE" . + " FROM DATA_DICTIONARY.COLUMNS" . + " WHERE TABLE_SCHEMA=" . $database . " AND TABLE_NAME = '" . $table . "'"; + } + + /** + * {@inheritDoc} + */ + public function getListTableForeignKeysSQL($table, $database = null) + { + if ($database) { + $database = "'" . $database . "'"; + } else { + $database = 'DATABASE()'; + } + + return "SELECT CONSTRAINT_NAME, CONSTRAINT_COLUMNS, REFERENCED_TABLE_NAME, REFERENCED_TABLE_COLUMNS, UPDATE_RULE, DELETE_RULE" . + " FROM DATA_DICTIONARY.FOREIGN_KEYS" . + " WHERE CONSTRAINT_SCHEMA=" . $database . " AND CONSTRAINT_TABLE='" . $table . "'"; + } + + /** + * {@inheritDoc} + */ + public function getListTableIndexesSQL($table, $database = null) + { + if ($database) { + $database = "'" . $database . "'"; + } else { + $database = 'DATABASE()'; + } + + return "SELECT INDEX_NAME AS 'key_name', COLUMN_NAME AS 'column_name', IS_USED_IN_PRIMARY AS 'primary', IS_UNIQUE=0 AS 'non_unique'" . + " FROM DATA_DICTIONARY.INDEX_PARTS" . + " WHERE TABLE_SCHEMA=" . $database . " AND TABLE_NAME='" . $table . "'"; + } + + /** + * {@inheritDoc} + */ + public function prefersIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsInlineColumnComments() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsViews() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function getDropIndexSQL($index, $table=null) + { + if ($index instanceof Index) { + $indexName = $index->getQuotedName($this); + } else if (is_string($index)) { + $indexName = $index; + } else { + throw new \InvalidArgumentException('DrizzlePlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.'); + } + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } else if(!is_string($table)) { + throw new \InvalidArgumentException('DrizzlePlatform::getDropIndexSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.'); + } + + if ($index instanceof Index && $index->isPrimary()) { + // drizzle primary keys are always named "PRIMARY", + // so we cannot use them in statements because of them being keyword. + return $this->getDropPrimaryKeySQL($table); + } + + return 'DROP INDEX ' . $indexName . ' ON ' . $table; + } + + /** + * {@inheritDoc} + */ + protected function getDropPrimaryKeySQL($table) + { + return 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] == true) { + return 'TIMESTAMP'; + } + + return 'DATETIME'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIME'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $columnSql = array(); + $queryParts = array(); + + if ($diff->newName !== false) { + $queryParts[] = 'RENAME TO ' . $diff->newName; + } + + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $columnArray = $column->toArray(); + $columnArray['comment'] = $this->getColumnComment($column); + $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); + } + + foreach ($diff->removedColumns as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $queryParts[] = 'DROP ' . $column->getQuotedName($this); + } + + foreach ($diff->changedColumns as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + /* @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */ + $column = $columnDiff->column; + $columnArray = $column->toArray(); + $columnArray['comment'] = $this->getColumnComment($column); + $queryParts[] = 'CHANGE ' . ($columnDiff->oldColumnName) . ' ' + . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $columnArray = $column->toArray(); + $columnArray['comment'] = $this->getColumnComment($column); + $queryParts[] = 'CHANGE ' . $oldColumnName . ' ' + . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); + } + + $sql = array(); + $tableSql = array(); + + if ( ! $this->onSchemaAlterTable($diff, $tableSql)) { + if (count($queryParts) > 0) { + $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . implode(", ", $queryParts); + } + $sql = array_merge( + $this->getPreAlterTableIndexForeignKeySQL($diff), + $sql, + $this->getPostAlterTableIndexForeignKeySQL($diff) + ); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * {@inheritDoc} + */ + public function getDropTemporaryTableSQL($table) + { + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } else if(!is_string($table)) { + throw new \InvalidArgumentException('getDropTableSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.'); + } + + return 'DROP TEMPORARY TABLE ' . $table; + } + + /** + * {@inheritDoc} + */ + public function convertBooleans($item) + { + if (is_array($item)) { + foreach ($item as $key => $value) { + if (is_bool($value) || is_numeric($item)) { + $item[$key] = ($value) ? 'true' : 'false'; + } + } + } else if (is_bool($item) || is_numeric($item)) { + $item = ($item) ? 'true' : 'false'; + } + + return $item; + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos == false) { + return 'LOCATE(' . $substr . ', ' . $str . ')'; + } + + return 'LOCATE(' . $substr . ', ' . $str . ', '.$startPos.')'; + } + + /** + * {@inheritDoc} + */ + public function getGuidExpression() + { + return 'UUID()'; + } + + /** + * {@inheritDoc} + */ + public function getRegexpExpression() + { + return 'RLIKE'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/DB2Keywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/DB2Keywords.php new file mode 100644 index 0000000..ff8aac8 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/DB2Keywords.php @@ -0,0 +1,441 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * DB2 Keywords. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class DB2Keywords extends KeywordList +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'DB2'; + } + + /** + * {@inheritdoc} + */ + protected function getKeywords() + { + return array( + 'ACTIVATE', + 'ADD', + 'AFTER', + 'ALIAS', + 'ALL', + 'ALLOCATE', + 'DOCUMENT', + 'DOUBLE', + 'DROP', + 'DSSIZE', + 'DYNAMIC', + 'EACH', + 'LOCK', + 'LOCKMAX', + 'LOCKSIZE', + 'LONG', + 'LOOP', + 'MAINTAINED', + 'ROUND_CEILING', + 'ROUND_DOWN', + 'ROUND_FLOOR', + 'ROUND_HALF_DOWN', + 'ROUND_HALF_EVEN', + 'ROUND_HALF_UP', + 'ALLOW', + 'ALTER', + 'AND', + 'ANY', + 'AS', + 'ASENSITIVE', + 'ASSOCIATE', + 'ASUTIME', + 'AT', + 'ATTRIBUTES', + 'AUDIT', + 'AUTHORIZATION', + 'AUX', + 'AUXILIARY', + 'BEFORE', + 'BEGIN', + 'BETWEEN', + 'BINARY', + 'BUFFERPOOL', + 'BY', + 'CACHE', + 'CALL', + 'CALLED', + 'CAPTURE', + 'CARDINALITY', + 'CASCADED', + 'CASE', + 'CAST', + 'CCSID', + 'CHAR', + 'CHARACTER', + 'CHECK', + 'CLONE', + 'CLOSE', + 'CLUSTER', + 'COLLECTION', + 'COLLID', + 'COLUMN', + 'COMMENT', + 'COMMIT', + 'CONCAT', + 'CONDITION', + 'CONNECT', + 'CONNECTION', + 'CONSTRAINT', + 'CONTAINS', + 'CONTINUE', + 'COUNT', + 'COUNT_BIG', + 'CREATE', + 'CROSS', + 'CURRENT', + 'CURRENT_DATE', + 'CURRENT_LC_CTYPE', + 'CURRENT_PATH', + 'CURRENT_SCHEMA', + 'CURRENT_SERVER', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_TIMEZONE', + 'CURRENT_USER', + 'CURSOR', + 'CYCLE', + 'DATA', + 'DATABASE', + 'DATAPARTITIONNAME', + 'DATAPARTITIONNUM', + 'EDITPROC', + 'ELSE', + 'ELSEIF', + 'ENABLE', + 'ENCODING', + 'ENCRYPTION', + 'END', + 'END-EXEC', + 'ENDING', + 'ERASE', + 'ESCAPE', + 'EVERY', + 'EXCEPT', + 'EXCEPTION', + 'EXCLUDING', + 'EXCLUSIVE', + 'EXECUTE', + 'EXISTS', + 'EXIT', + 'EXPLAIN', + 'EXTERNAL', + 'EXTRACT', + 'FENCED', + 'FETCH', + 'FIELDPROC', + 'FILE', + 'FINAL', + 'FOR', + 'FOREIGN', + 'FREE', + 'FROM', + 'FULL', + 'FUNCTION', + 'GENERAL', + 'GENERATED', + 'GET', + 'GLOBAL', + 'GO', + 'GOTO', + 'GRANT', + 'GRAPHIC', + 'GROUP', + 'HANDLER', + 'HASH', + 'HASHED_VALUE', + 'HAVING', + 'HINT', + 'HOLD', + 'HOUR', + 'HOURS', + 'IDENTITY', + 'IF', + 'IMMEDIATE', + 'IN', + 'INCLUDING', + 'INCLUSIVE', + 'INCREMENT', + 'INDEX', + 'INDICATOR', + 'INF', + 'INFINITY', + 'INHERIT', + 'INNER', + 'INOUT', + 'INSENSITIVE', + 'INSERT', + 'INTEGRITY', + 'MATERIALIZED', + 'MAXVALUE', + 'MICROSECOND', + 'MICROSECONDS', + 'MINUTE', + 'MINUTES', + 'MINVALUE', + 'MODE', + 'MODIFIES', + 'MONTH', + 'MONTHS', + 'NAN', + 'NEW', + 'NEW_TABLE', + 'NEXTVAL', + 'NO', + 'NOCACHE', + 'NOCYCLE', + 'NODENAME', + 'NODENUMBER', + 'NOMAXVALUE', + 'NOMINVALUE', + 'NONE', + 'NOORDER', + 'NORMALIZED', + 'NOT', + 'NULL', + 'NULLS', + 'NUMPARTS', + 'OBID', + 'OF', + 'OLD', + 'OLD_TABLE', + 'ON', + 'OPEN', + 'OPTIMIZATION', + 'OPTIMIZE', + 'OPTION', + 'OR', + 'ORDER', + 'OUT', + 'OUTER', + 'OVER', + 'OVERRIDING', + 'PACKAGE', + 'PADDED', + 'PAGESIZE', + 'PARAMETER', + 'PART', + 'PARTITION', + 'PARTITIONED', + 'PARTITIONING', + 'PARTITIONS', + 'PASSWORD', + 'PATH', + 'PIECESIZE', + 'PLAN', + 'POSITION', + 'PRECISION', + 'PREPARE', + 'PREVVAL', + 'PRIMARY', + 'PRIQTY', + 'PRIVILEGES', + 'PROCEDURE', + 'PROGRAM', + 'PSID', + 'ROUND_UP', + 'ROUTINE', + 'ROW', + 'ROW_NUMBER', + 'ROWNUMBER', + 'ROWS', + 'ROWSET', + 'RRN', + 'RUN', + 'SAVEPOINT', + 'SCHEMA', + 'SCRATCHPAD', + 'SCROLL', + 'SEARCH', + 'SECOND', + 'SECONDS', + 'SECQTY', + 'SECURITY', + 'SELECT', + 'SENSITIVE', + 'SEQUENCE', + 'SESSION', + 'SESSION_USER', + 'SET', + 'SIGNAL', + 'SIMPLE', + 'SNAN', + 'SOME', + 'SOURCE', + 'SPECIFIC', + 'SQL', + 'SQLID', + 'STACKED', + 'STANDARD', + 'START', + 'STARTING', + 'STATEMENT', + 'STATIC', + 'STATMENT', + 'STAY', + 'STOGROUP', + 'STORES', + 'STYLE', + 'SUBSTRING', + 'SUMMARY', + 'SYNONYM', + 'SYSFUN', + 'SYSIBM', + 'SYSPROC', + 'SYSTEM', + 'SYSTEM_USER', + 'TABLE', + 'TABLESPACE', + 'THEN', + 'TIME', + 'TIMESTAMP', + 'TO', + 'TRANSACTION', + 'TRIGGER', + 'TRIM', + 'TRUNCATE', + 'TYPE', + 'UNDO', + 'UNION', + 'UNIQUE', + 'UNTIL', + 'UPDATE', + 'DATE', + 'DAY', + 'DAYS', + 'DB2GENERAL', + 'DB2GENRL', + 'DB2SQL', + 'DBINFO', + 'DBPARTITIONNAME', + 'DBPARTITIONNUM', + 'DEALLOCATE', + 'DECLARE', + 'DEFAULT', + 'DEFAULTS', + 'DEFINITION', + 'DELETE', + 'DENSE_RANK', + 'DENSERANK', + 'DESCRIBE', + 'DESCRIPTOR', + 'DETERMINISTIC', + 'DIAGNOSTICS', + 'DISABLE', + 'DISALLOW', + 'DISCONNECT', + 'DISTINCT', + 'DO', + 'INTERSECT', + 'PUBLIC', + 'USAGE', + 'INTO', + 'QUERY', + 'USER', + 'IS', + 'QUERYNO', + 'USING', + 'ISOBID', + 'RANGE', + 'VALIDPROC', + 'ISOLATION', + 'RANK', + 'VALUE', + 'ITERATE', + 'READ', + 'VALUES', + 'JAR', + 'READS', + 'VARIABLE', + 'JAVA', + 'RECOVERY', + 'VARIANT', + 'JOIN', + 'REFERENCES', + 'VCAT', + 'KEEP', + 'REFERENCING', + 'VERSION', + 'KEY', + 'REFRESH', + 'VIEW', + 'LABEL', + 'RELEASE', + 'VOLATILE', + 'LANGUAGE', + 'RENAME', + 'VOLUMES', + 'LATERAL', + 'REPEAT', + 'WHEN', + 'LC_CTYPE', + 'RESET', + 'WHENEVER', + 'LEAVE', + 'RESIGNAL', + 'WHERE', + 'LEFT', + 'RESTART', + 'WHILE', + 'LIKE', + 'RESTRICT', + 'WITH', + 'LINKTYPE', + 'RESULT', + 'WITHOUT', + 'LOCAL', + 'RESULT_SET_LOCATOR WLM', + 'LOCALDATE', + 'RETURN', + 'WRITE', + 'LOCALE', + 'RETURNS', + 'XMLELEMENT', + 'LOCALTIME', + 'REVOKE', + 'XMLEXISTS', + 'LOCALTIMESTAMP RIGHT', + 'XMLNAMESPACES', + 'LOCATOR', + 'ROLE', + 'YEAR', + 'LOCATORS', + 'ROLLBACK', + 'YEARS', + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/DrizzleKeywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/DrizzleKeywords.php new file mode 100644 index 0000000..b7db0fa --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/DrizzleKeywords.php @@ -0,0 +1,345 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * Drizzle Keywordlist. + * + * @author Kim Hemsø Rasmussen + */ +class DrizzleKeywords extends KeywordList +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'drizzle'; + } + + /** + * {@inheritdoc} + */ + protected function getKeywords() + { + return array( + 'ABS', + 'ALL', + 'ALLOCATE', + 'ALTER', + 'AND', + 'ANY', + 'ARE', + 'ARRAY', + 'AS', + 'ASENSITIVE', + 'ASYMMETRIC', + 'AT', + 'ATOMIC', + 'AUTHORIZATION', + 'AVG', + 'BEGIN', + 'BETWEEN', + 'BIGINT', + 'BINARY', + 'BLOB', + 'BOOLEAN', + 'BOTH', + 'BY', + 'CALL', + 'CALLED', + 'CARDINALITY', + 'CASCADED', + 'CASE', + 'CAST', + 'CEIL', + 'CEILING', + 'CHAR', + 'CHARACTER', + 'CHARACTER_LENGTH', + 'CHAR_LENGTH', + 'CHECK', + 'CLOB', + 'CLOSE', + 'COALESCE', + 'COLLATE', + 'COLLECT', + 'COLUMN', + 'COMMIT', + 'CONDITION', + 'CONNECT', + 'CONSTRAINT', + 'CONVERT', + 'CORR', + 'CORRESPONDING', + 'COUNT', + 'COVAR_POP', + 'COVAR_SAMP', + 'CREATE', + 'CROSS', + 'CUBE', + 'CUME_DIST', + 'CURRENT', + 'CURRENT_DATE', + 'CURRENT_DEFAULT_TRANSFORM_GROUP', + 'CURRENT_PATH', + 'CURRENT_ROLE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_TRANSFORM_GROUP_FOR_TYPE', + 'CURRENT_USER', + 'CURSOR', + 'CYCLE', + 'DATE', + 'DAY', + 'DEALLOCATE', + 'DEC', + 'DECIMAL', + 'DECLARE', + 'DEFAULT', + 'DELETE', + 'DENSE_RANK', + 'DEREF', + 'DESCRIBE', + 'DETERMINISTIC', + 'DISCONNECT', + 'DISTINCT', + 'DOUBLE', + 'DROP', + 'DYNAMIC', + 'EACH', + 'ELEMENT', + 'ELSE', + 'END', + 'ESCAPE', + 'EVERY', + 'EXCEPT', + 'EXEC', + 'EXECUTE', + 'EXISTS', + 'EXP', + 'EXTERNAL', + 'EXTRACT', + 'FALSE', + 'FETCH', + 'FILTER', + 'FLOAT', + 'FLOOR', + 'FOR', + 'FOREIGN', + 'FREE', + 'FROM', + 'FULL', + 'FUNCTION', + 'FUSION', + 'GET', + 'GLOBAL', + 'GRANT', + 'GROUP', + 'GROUPING', + 'HAVING', + 'HOLD', + 'HOUR', + 'IDENTITY', + 'IN', + 'INDICATOR', + 'INNER', + 'INOUT', + 'INSENSITIVE', + 'INSERT', + 'INT', + 'INTEGER', + 'INTERSECT', + 'INTERSECTION', + 'INTERVAL', + 'INTO', + 'IS', + 'JOIN', + 'LANGUAGE', + 'LARGE', + 'LATERAL', + 'LEADING', + 'LEFT', + 'LIKE', + 'LN', + 'LOCAL', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'LOWER', + 'MATCH', + 'MAX', + 'MEMBER', + 'MERGE', + 'METHOD', + 'MIN', + 'MINUTE', + 'MOD', + 'MODIFIES', + 'MODULE', + 'MONTH', + 'MULTISET', + 'NATIONAL', + 'NATURAL', + 'NCHAR', + 'NCLOB', + 'NEW', + 'NO', + 'NONE', + 'NORMALIZE', + 'NOT', + 'NULL_SYM', + 'NULLIF', + 'NUMERIC', + 'OCTET_LENGTH', + 'OF', + 'OLD', + 'ON', + 'ONLY', + 'OPEN', + 'OR', + 'ORDER', + 'OUT', + 'OUTER', + 'OVER', + 'OVERLAPS', + 'OVERLAY', + 'PARAMETER', + 'PARTITION', + 'PERCENTILE_CONT', + 'PERCENTILE_DISC', + 'PERCENT_RANK', + 'POSITION', + 'POWER', + 'PRECISION', + 'PREPARE', + 'PRIMARY', + 'PROCEDURE', + 'RANGE', + 'RANK', + 'READS', + 'REAL', + 'RECURSIVE', + 'REF', + 'REFERENCES', + 'REFERENCING', + 'REGR_AVGX', + 'REGR_AVGY', + 'REGR_COUNT', + 'REGR_INTERCEPT', + 'REGR_R2', + 'REGR_SLOPE', + 'REGR_SXX', + 'REGR_SXY', + 'REGR_SYY', + 'RELEASE', + 'RESULT', + 'RETURN', + 'RETURNS', + 'REVOKE', + 'RIGHT', + 'ROLLBACK', + 'ROLLUP', + 'ROW', + 'ROWS', + 'ROW_NUMBER', + 'SAVEPOINT', + 'SCOPE', + 'SCROLL', + 'SEARCH', + 'SECOND', + 'SELECT', + 'SENSITIVE', + 'SESSION_USER', + 'SET', + 'SIMILAR', + 'SMALLINT', + 'SOME', + 'SPECIFIC', + 'SPECIFICTYPE', + 'SQL', + 'SQLEXCEPTION', + 'SQLSTATE', + 'SQLWARNING', + 'SQRT', + 'START', + 'STATIC', + 'STDDEV_POP', + 'STDDEV_SAMP', + 'SUBMULTISET', + 'SUBSTRING', + 'SUM', + 'SYMMETRIC', + 'SYSTEM', + 'SYSTEM_USER', + 'TABLE', + 'TABLESAMPLE', + 'THEN', + 'TIME', + 'TIMESTAMP', + 'TIMEZONE_HOUR', + 'TIMEZONE_MINUTE', + 'TO', + 'TRAILING', + 'TRANSLATE', + 'TRANSLATION', + 'TREAT', + 'TRIGGER', + 'TRIM', + 'TRUE', + 'UESCAPE', + 'UNION', + 'UNIQUE', + 'UNKNOWN', + 'UNNEST', + 'UPDATE', + 'UPPER', + 'USER', + 'USING', + 'VALUE', + 'VALUES', + 'VARCHAR', + 'VARYING', + 'VAR_POP', + 'VAR_SAMP', + 'WHEN', + 'WHENEVER', + 'WHERE', + 'WIDTH_BUCKET', + 'WINDOW', + 'WITH', + 'WITHIN', + 'WITHOUT', + 'XML', + 'XMLAGG', + 'XMLATTRIBUTES', + 'XMLBINARY', + 'XMLCOMMENT', + 'XMLCONCAT', + 'XMLELEMENT', + 'XMLFOREST', + 'XMLNAMESPACES', + 'XMLPARSE', + 'XMLPI', + 'XMLROOT', + 'XMLSERIALIZE', + 'YEAR', + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/KeywordList.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/KeywordList.php new file mode 100644 index 0000000..5b8d74d --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/KeywordList.php @@ -0,0 +1,73 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * Abstract interface for a SQL reserved keyword dictionary. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +abstract class KeywordList +{ + /** + * @var array|null + */ + private $keywords = null; + + /** + * Checks if the given word is a keyword of this dialect/vendor platform. + * + * @param string $word + * + * @return boolean + */ + public function isKeyword($word) + { + if ($this->keywords === null) { + $this->initializeKeywords(); + } + + return isset($this->keywords[strtoupper($word)]); + } + + /** + * @return void + */ + protected function initializeKeywords() + { + $this->keywords = array_flip(array_map('strtoupper', $this->getKeywords())); + } + + /** + * Returns the list of keywords. + * + * @return array + */ + abstract protected function getKeywords(); + + /** + * Returns the name of this keyword list. + * + * @return string + */ + abstract public function getName(); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MsSQLKeywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MsSQLKeywords.php new file mode 100644 index 0000000..9c8f614 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MsSQLKeywords.php @@ -0,0 +1,43 @@ +. + */ + + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * MsSQL Keywordlist + * + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @link www.doctrine-project.com + * @since 2.0 + * @author Benjamin Eberlei + * @author David Coallier + * @author Steve Müller + * @deprecated Use SQLServerKeywords class instead. + */ +class MsSQLKeywords extends SQLServerKeywords +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'MsSQL'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQLKeywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQLKeywords.php new file mode 100644 index 0000000..ba25774 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/MySQLKeywords.php @@ -0,0 +1,273 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * MySQL Keywordlist. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author David Coallier + */ +class MySQLKeywords extends KeywordList +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'MySQL'; + } + + /** + * {@inheritdoc} + */ + protected function getKeywords() + { + return array( + 'ADD', + 'ALL', + 'ALTER', + 'ANALYZE', + 'AND', + 'AS', + 'ASC', + 'ASENSITIVE', + 'BEFORE', + 'BETWEEN', + 'BIGINT', + 'BINARY', + 'BLOB', + 'BOTH', + 'BY', + 'CALL', + 'CASCADE', + 'CASE', + 'CHANGE', + 'CHAR', + 'CHARACTER', + 'CHECK', + 'COLLATE', + 'COLUMN', + 'CONDITION', + 'CONNECTION', + 'CONSTRAINT', + 'CONTINUE', + 'CONVERT', + 'CREATE', + 'CROSS', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'CURSOR', + 'DATABASE', + 'DATABASES', + 'DAY_HOUR', + 'DAY_MICROSECOND', + 'DAY_MINUTE', + 'DAY_SECOND', + 'DEC', + 'DECIMAL', + 'DECLARE', + 'DEFAULT', + 'DELAYED', + 'DELETE', + 'DESC', + 'DESCRIBE', + 'DETERMINISTIC', + 'DISTINCT', + 'DISTINCTROW', + 'DIV', + 'DOUBLE', + 'DROP', + 'DUAL', + 'EACH', + 'ELSE', + 'ELSEIF', + 'ENCLOSED', + 'ESCAPED', + 'EXISTS', + 'EXIT', + 'EXPLAIN', + 'FALSE', + 'FETCH', + 'FLOAT', + 'FLOAT4', + 'FLOAT8', + 'FOR', + 'FORCE', + 'FOREIGN', + 'FROM', + 'FULLTEXT', + 'GOTO', + 'GRANT', + 'GROUP', + 'HAVING', + 'HIGH_PRIORITY', + 'HOUR_MICROSECOND', + 'HOUR_MINUTE', + 'HOUR_SECOND', + 'IF', + 'IGNORE', + 'IN', + 'INDEX', + 'INFILE', + 'INNER', + 'INOUT', + 'INSENSITIVE', + 'INSERT', + 'INT', + 'INT1', + 'INT2', + 'INT3', + 'INT4', + 'INT8', + 'INTEGER', + 'INTERVAL', + 'INTO', + 'IS', + 'ITERATE', + 'JOIN', + 'KEY', + 'KEYS', + 'KILL', + 'LABEL', + 'LEADING', + 'LEAVE', + 'LEFT', + 'LIKE', + 'LIMIT', + 'LINES', + 'LOAD', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'LOCK', + 'LONG', + 'LONGBLOB', + 'LONGTEXT', + 'LOOP', + 'LOW_PRIORITY', + 'MATCH', + 'MEDIUMBLOB', + 'MEDIUMINT', + 'MEDIUMTEXT', + 'MIDDLEINT', + 'MINUTE_MICROSECOND', + 'MINUTE_SECOND', + 'MOD', + 'MODIFIES', + 'NATURAL', + 'NOT', + 'NO_WRITE_TO_BINLOG', + 'NULL', + 'NUMERIC', + 'ON', + 'OPTIMIZE', + 'OPTION', + 'OPTIONALLY', + 'OR', + 'ORDER', + 'OUT', + 'OUTER', + 'OUTFILE', + 'PRECISION', + 'PRIMARY', + 'PROCEDURE', + 'PURGE', + 'RAID0', + 'RANGE', + 'READ', + 'READS', + 'REAL', + 'REFERENCES', + 'REGEXP', + 'RELEASE', + 'RENAME', + 'REPEAT', + 'REPLACE', + 'REQUIRE', + 'RESTRICT', + 'RETURN', + 'REVOKE', + 'RIGHT', + 'RLIKE', + 'SCHEMA', + 'SCHEMAS', + 'SECOND_MICROSECOND', + 'SELECT', + 'SENSITIVE', + 'SEPARATOR', + 'SET', + 'SHOW', + 'SMALLINT', + 'SONAME', + 'SPATIAL', + 'SPECIFIC', + 'SQL', + 'SQLEXCEPTION', + 'SQLSTATE', + 'SQLWARNING', + 'SQL_BIG_RESULT', + 'SQL_CALC_FOUND_ROWS', + 'SQL_SMALL_RESULT', + 'SSL', + 'STARTING', + 'STRAIGHT_JOIN', + 'TABLE', + 'TERMINATED', + 'THEN', + 'TINYBLOB', + 'TINYINT', + 'TINYTEXT', + 'TO', + 'TRAILING', + 'TRIGGER', + 'TRUE', + 'UNDO', + 'UNION', + 'UNIQUE', + 'UNLOCK', + 'UNSIGNED', + 'UPDATE', + 'USAGE', + 'USE', + 'USING', + 'UTC_DATE', + 'UTC_TIME', + 'UTC_TIMESTAMP', + 'VALUES', + 'VARBINARY', + 'VARCHAR', + 'VARCHARACTER', + 'VARYING', + 'WHEN', + 'WHERE', + 'WHILE', + 'WITH', + 'WRITE', + 'X509', + 'XOR', + 'YEAR_MONTH', + 'ZEROFILL', + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/OracleKeywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/OracleKeywords.php new file mode 100644 index 0000000..b37fdad --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/OracleKeywords.php @@ -0,0 +1,161 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * Oracle Keywordlist. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author David Coallier + */ +class OracleKeywords extends KeywordList +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'Oracle'; + } + + /** + * {@inheritdoc} + */ + protected function getKeywords() + { + return array( + 'ACCESS', + 'ELSE', + 'MODIFY', + 'START', + 'ADD', + 'EXCLUSIVE', + 'NOAUDIT', + 'SELECT', + 'ALL', + 'EXISTS', + 'NOCOMPRESS', + 'SESSION', + 'ALTER', + 'FILE', + 'NOT', + 'SET', + 'AND', + 'FLOAT', + 'NOTFOUND ', + 'SHARE', + 'ANY', + 'FOR', + 'NOWAIT', + 'SIZE', + 'ARRAYLEN', + 'FROM', + 'NULL', + 'SMALLINT', + 'AS', + 'GRANT', + 'NUMBER', + 'SQLBUF', + 'ASC', + 'GROUP', + 'OF', + 'SUCCESSFUL', + 'AUDIT', + 'HAVING', + 'OFFLINE ', + 'SYNONYM', + 'BETWEEN', + 'IDENTIFIED', + 'ON', + 'SYSDATE', + 'BY', + 'IMMEDIATE', + 'ONLINE', + 'TABLE', + 'CHAR', + 'IN', + 'OPTION', + 'THEN', + 'CHECK', + 'INCREMENT', + 'OR', + 'TO', + 'CLUSTER', + 'INDEX', + 'ORDER', + 'TRIGGER', + 'COLUMN', + 'INITIAL', + 'PCTFREE', + 'UID', + 'COMMENT', + 'INSERT', + 'PRIOR', + 'UNION', + 'COMPRESS', + 'INTEGER', + 'PRIVILEGES', + 'UNIQUE', + 'CONNECT', + 'INTERSECT', + 'PUBLIC', + 'UPDATE', + 'CREATE', + 'INTO', + 'RAW', + 'USER', + 'CURRENT', + 'IS', + 'RENAME', + 'VALIDATE', + 'DATE', + 'LEVEL', + 'RESOURCE', + 'VALUES', + 'DECIMAL', + 'LIKE', + 'REVOKE', + 'VARCHAR', + 'DEFAULT', + 'LOCK', + 'ROW', + 'VARCHAR2', + 'DELETE', + 'LONG', + 'ROWID', + 'VIEW', + 'DESC', + 'MAXEXTENTS', + 'ROWLABEL', + 'WHENEVER', + 'DISTINCT', + 'MINUS', + 'ROWNUM', + 'WHERE', + 'DROP', + 'MODE', + 'ROWS', + 'WITH', + 'RANGE', + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQLKeywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQLKeywords.php new file mode 100644 index 0000000..9e49d7f --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/PostgreSQLKeywords.php @@ -0,0 +1,135 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * PostgreSQL Keywordlist. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Marcelo Santos Araujo + */ +class PostgreSQLKeywords extends KeywordList +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'PostgreSQL'; + } + + /** + * {@inheritdoc} + */ + protected function getKeywords() + { + return array( + 'ALL', + 'ANALYSE', + 'ANALYZE', + 'AND', + 'ANY', + 'AS', + 'ASC', + 'AUTHORIZATION', + 'BETWEEN', + 'BINARY', + 'BOTH', + 'CASE', + 'CAST', + 'CHECK', + 'COLLATE', + 'COLUMN', + 'CONSTRAINT', + 'CREATE', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'DEFAULT', + 'DEFERRABLE', + 'DESC', + 'DISTINCT', + 'DO', + 'ELSE', + 'END', + 'EXCEPT', + 'FALSE', + 'FOR', + 'FOREIGN', + 'FREEZE', + 'FROM', + 'FULL', + 'GRANT', + 'GROUP', + 'HAVING', + 'ILIKE', + 'IN', + 'INITIALLY', + 'INNER', + 'INTERSECT', + 'INTO', + 'IS', + 'ISNULL', + 'JOIN', + 'LEADING', + 'LEFT', + 'LIKE', + 'LIMIT', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'NATURAL', + 'NEW', + 'NOT', + 'NOTNULL', + 'NULL', + 'OFF', + 'OFFSET', + 'OLD', + 'ON', + 'ONLY', + 'OR', + 'ORDER', + 'OUTER', + 'OVERLAPS', + 'PLACING', + 'PRIMARY', + 'REFERENCES', + 'SELECT', + 'SESSION_USER', + 'SIMILAR', + 'SOME', + 'TABLE', + 'THEN', + 'TO', + 'TRAILING', + 'TRUE', + 'UNION', + 'UNIQUE', + 'USER', + 'USING', + 'VERBOSE', + 'WHEN', + 'WHERE' + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/ReservedKeywordsValidator.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/ReservedKeywordsValidator.php new file mode 100644 index 0000000..248f26b --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/ReservedKeywordsValidator.php @@ -0,0 +1,142 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +use Doctrine\DBAL\Schema\Visitor\Visitor; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\Index; + +class ReservedKeywordsValidator implements Visitor +{ + /** + * @var KeywordList[] + */ + private $keywordLists = array(); + + /** + * @var array + */ + private $violations = array(); + + /** + * @param \Doctrine\DBAL\Platforms\Keywords\KeywordList[] $keywordLists + */ + public function __construct(array $keywordLists) + { + $this->keywordLists = $keywordLists; + } + + /** + * @return array + */ + public function getViolations() + { + return $this->violations; + } + + /** + * @param string $word + * + * @return array + */ + private function isReservedWord($word) + { + if ($word[0] == "`") { + $word = str_replace('`', '', $word); + } + + $keywordLists = array(); + foreach ($this->keywordLists as $keywordList) { + if ($keywordList->isKeyword($word)) { + $keywordLists[] = $keywordList->getName(); + } + } + return $keywordLists; + } + + /** + * @param string $asset + * @param array $violatedPlatforms + * + * @return void + */ + private function addViolation($asset, $violatedPlatforms) + { + if ( ! $violatedPlatforms) { + return; + } + + $this->violations[] = $asset . ' keyword violations: ' . implode(', ', $violatedPlatforms); + } + + /** + * {@inheritdoc} + */ + public function acceptColumn(Table $table, Column $column) + { + $this->addViolation( + 'Table ' . $table->getName() . ' column ' . $column->getName(), + $this->isReservedWord($column->getName()) + ); + } + + /** + * {@inheritdoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + } + + /** + * {@inheritdoc} + */ + public function acceptIndex(Table $table, Index $index) + { + } + + /** + * {@inheritdoc} + */ + public function acceptSchema(Schema $schema) + { + } + + /** + * {@inheritdoc} + */ + public function acceptSequence(Sequence $sequence) + { + } + + /** + * {@inheritdoc} + */ + public function acceptTable(Table $table) + { + $this->addViolation( + 'Table ' . $table->getName(), + $this->isReservedWord($table->getName()) + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2005Keywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2005Keywords.php new file mode 100644 index 0000000..3a74349 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2005Keywords.php @@ -0,0 +1,56 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * Microsoft SQL Server 2005 reserved keyword dictionary. + * + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @link www.doctrine-project.com + * @since 2.3 + * @author Steve Müller + */ +class SQLServer2005Keywords extends SQLServerKeywords +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'SQLServer2005'; + } + + /** + * {@inheritdoc} + * + * @link http://msdn.microsoft.com/en-US/library/ms189822%28v=sql.90%29.aspx + */ + protected function getKeywords() + { + return array_merge(array_diff(parent::getKeywords(), array('DUMMY')), array( + 'EXTERNAL', + 'PIVOT', + 'REVERT', + 'SECURITYAUDIT', + 'TABLESAMPLE', + 'UNPIVOT' + )); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2008Keywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2008Keywords.php new file mode 100644 index 0000000..38556b5 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2008Keywords.php @@ -0,0 +1,51 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * Microsoft SQL Server 2008 reserved keyword dictionary. + * + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @link www.doctrine-project.com + * @since 2.3 + * @author Steve Müller + */ +class SQLServer2008Keywords extends SQLServer2005Keywords +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'SQLServer2008'; + } + + /** + * {@inheritdoc} + * + * @link http://msdn.microsoft.com/en-us/library/ms189822%28v=sql.100%29.aspx + */ + protected function getKeywords() + { + return array_merge(parent::getKeywords(), array( + 'MERGE' + )); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2012Keywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2012Keywords.php new file mode 100644 index 0000000..ada1c02 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServer2012Keywords.php @@ -0,0 +1,55 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * Microsoft SQL Server 2012 reserved keyword dictionary. + * + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @link www.doctrine-project.com + * @since 2.3 + * @author Steve Müller + */ +class SQLServer2012Keywords extends SQLServer2008Keywords +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'SQLServer2012'; + } + + /** + * {@inheritdoc} + * + * @link http://msdn.microsoft.com/en-us/library/ms189822.aspx + */ + protected function getKeywords() + { + return array_merge(parent::getKeywords(), array( + 'SEMANTICKEYPHRASETABLE', + 'SEMANTICSIMILARITYDETAILSTABLE', + 'SEMANTICSIMILARITYTABLE', + 'TRY_CONVERT', + 'WITHIN GROUP' + )); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServerKeywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServerKeywords.php new file mode 100644 index 0000000..fb43d2d --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLServerKeywords.php @@ -0,0 +1,231 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * Microsoft SQL Server 2000 reserved keyword dictionary. + * + * @license BSD http://www.opensource.org/licenses/bsd-license.php + * @link www.doctrine-project.com + * @since 2.0 + * @author Benjamin Eberlei + * @author David Coallier + * @author Steve Müller + */ +class SQLServerKeywords extends KeywordList +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'SQLServer'; + } + + /** + * {@inheritdoc} + * + * @link http://msdn.microsoft.com/en-us/library/aa238507%28v=sql.80%29.aspx + */ + protected function getKeywords() + { + return array( + 'ADD', + 'ALL', + 'ALTER', + 'AND', + 'ANY', + 'AS', + 'ASC', + 'AUTHORIZATION', + 'BACKUP', + 'BEGIN', + 'BETWEEN', + 'BREAK', + 'BROWSE', + 'BULK', + 'BY', + 'CASCADE', + 'CASE', + 'CHECK', + 'CHECKPOINT', + 'CLOSE', + 'CLUSTERED', + 'COALESCE', + 'COLLATE', + 'COLUMN', + 'COMMIT', + 'COMPUTE', + 'CONSTRAINT', + 'CONTAINS', + 'CONTAINSTABLE', + 'CONTINUE', + 'CONVERT', + 'CREATE', + 'CROSS', + 'CURRENT', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'CURSOR', + 'DATABASE', + 'DBCC', + 'DEALLOCATE', + 'DECLARE', + 'DEFAULT', + 'DELETE', + 'DENY', + 'DESC', + 'DISK', + 'DISTINCT', + 'DISTRIBUTED', + 'DOUBLE', + 'DROP', + 'DUMP', + 'ELSE', + 'END', + 'ERRLVL', + 'ESCAPE', + 'EXCEPT', + 'EXEC', + 'EXECUTE', + 'EXISTS', + 'EXIT', + 'EXTERNAL', + 'FETCH', + 'FILE', + 'FILLFACTOR', + 'FOR', + 'FOREIGN', + 'FREETEXT', + 'FREETEXTTABLE', + 'FROM', + 'FULL', + 'FUNCTION', + 'GOTO', + 'GRANT', + 'GROUP', + 'HAVING', + 'HOLDLOCK', + 'IDENTITY', + 'IDENTITY_INSERT', + 'IDENTITYCOL', + 'IF', + 'IN', + 'INDEX', + 'INNER', + 'INSERT', + 'INTERSECT', + 'INTO', + 'IS', + 'JOIN', + 'KEY', + 'KILL', + 'LEFT', + 'LIKE', + 'LINENO', + 'LOAD', + 'NATIONAL', + 'NOCHECK ', + 'NONCLUSTERED', + 'NOT', + 'NULL', + 'NULLIF', + 'OF', + 'OFF', + 'OFFSETS', + 'ON', + 'OPEN', + 'OPENDATASOURCE', + 'OPENQUERY', + 'OPENROWSET', + 'OPENXML', + 'OPTION', + 'OR', + 'ORDER', + 'OUTER', + 'OVER', + 'PERCENT', + 'PIVOT', + 'PLAN', + 'PRECISION', + 'PRIMARY', + 'PRINT', + 'PROC', + 'PROCEDURE', + 'PUBLIC', + 'RAISERROR', + 'READ', + 'READTEXT', + 'RECONFIGURE', + 'REFERENCES', + 'REPLICATION', + 'RESTORE', + 'RESTRICT', + 'RETURN', + 'REVERT', + 'REVOKE', + 'RIGHT', + 'ROLLBACK', + 'ROWCOUNT', + 'ROWGUIDCOL', + 'RULE', + 'SAVE', + 'SCHEMA', + 'SECURITYAUDIT', + 'SELECT', + 'SESSION_USER', + 'SET', + 'SETUSER', + 'SHUTDOWN', + 'SOME', + 'STATISTICS', + 'SYSTEM_USER', + 'TABLE', + 'TABLESAMPLE', + 'TEXTSIZE', + 'THEN', + 'TO', + 'TOP', + 'TRAN', + 'TRANSACTION', + 'TRIGGER', + 'TRUNCATE', + 'TSEQUAL', + 'UNION', + 'UNIQUE', + 'UNPIVOT', + 'UPDATE', + 'UPDATETEXT', + 'USE', + 'USER', + 'VALUES', + 'VARYING', + 'VIEW', + 'WAITFOR', + 'WHEN', + 'WHERE', + 'WHILE', + 'WITH', + 'WRITETEXT' + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLiteKeywords.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLiteKeywords.php new file mode 100644 index 0000000..a162d3d --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/Keywords/SQLiteKeywords.php @@ -0,0 +1,168 @@ +. + */ + +namespace Doctrine\DBAL\Platforms\Keywords; + +/** + * SQLite Keywordlist. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class SQLiteKeywords extends KeywordList +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'SQLite'; + } + + /** + * {@inheritdoc} + */ + protected function getKeywords() + { + return array( + 'ABORT', + 'ACTION', + 'ADD', + 'AFTER', + 'ALL', + 'ALTER', + 'ANALYZE', + 'AND', + 'AS', + 'ASC', + 'ATTACH', + 'AUTOINCREMENT', + 'BEFORE', + 'BEGIN', + 'BETWEEN', + 'BY', + 'CASCADE', + 'CASE', + 'CAST', + 'CHECK', + 'COLLATE', + 'COLUMN', + 'COMMIT', + 'CONFLICT', + 'CONSTRAINT', + 'CREATE', + 'CROSS', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'DATABASE', + 'DEFAULT', + 'DEFERRABLE', + 'DEFERRED', + 'DELETE', + 'DESC', + 'DETACH', + 'DISTINCT', + 'DROP', + 'EACH', + 'ELSE', + 'END', + 'ESCAPE', + 'EXCEPT', + 'EXCLUSIVE', + 'EXISTS', + 'EXPLAIN', + 'FAIL', + 'FOR', + 'FOREIGN', + 'FROM', + 'FULL', + 'GLOB', + 'GROUP', + 'HAVING', + 'IF', + 'IGNORE', + 'IMMEDIATE', + 'IN', + 'INDEX', + 'INDEXED', + 'INITIALLY', + 'INNER', + 'INSERT', + 'INSTEAD', + 'INTERSECT', + 'INTO', + 'IS', + 'ISNULL', + 'JOIN', + 'KEY', + 'LEFT', + 'LIKE', + 'LIMIT', + 'MATCH', + 'NATURAL', + 'NO', + 'NOT', + 'NOTNULL', + 'NULL', + 'OF', + 'OFFSET', + 'ON', + 'OR', + 'ORDER', + 'OUTER', + 'PLAN', + 'PRAGMA', + 'PRIMARY', + 'QUERY', + 'RAISE', + 'REFERENCES', + 'REGEXP', + 'REINDEX', + 'RELEASE', + 'RENAME', + 'REPLACE', + 'RESTRICT', + 'RIGHT', + 'ROLLBACK', + 'ROW', + 'SAVEPOINT', + 'SELECT', + 'SET', + 'TABLE', + 'TEMP', + 'TEMPORARY', + 'THEN', + 'TO', + 'TRANSACTION', + 'TRIGGER', + 'UNION', + 'UNIQUE', + 'UPDATE', + 'USING', + 'VACUUM', + 'VALUES', + 'VIEW', + 'VIRTUAL', + 'WHEN', + 'WHERE' + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php new file mode 100644 index 0000000..8db9970 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/MySqlPlatform.php @@ -0,0 +1,860 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Schema\TableDiff; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Table; + +/** + * The MySqlPlatform provides the behavior, features and SQL dialect of the + * MySQL database platform. This platform represents a MySQL 5.0 or greater platform that + * uses the InnoDB storage engine. + * + * @since 2.0 + * @author Roman Borschel + * @author Benjamin Eberlei + * @todo Rename: MySQLPlatform + */ +class MySqlPlatform extends AbstractPlatform +{ + const LENGTH_LIMIT_TINYTEXT = 255; + const LENGTH_LIMIT_TEXT = 65535; + const LENGTH_LIMIT_MEDIUMTEXT = 16777215; + + const LENGTH_LIMIT_TINYBLOB = 255; + const LENGTH_LIMIT_BLOB = 65535; + const LENGTH_LIMIT_MEDIUMBLOB = 16777215; + + /** + * Adds MySQL-specific LIMIT clause to the query + * 18446744073709551615 is 2^64-1 maximum of unsigned BIGINT the biggest limit possible + */ + protected function doModifyLimitQuery($query, $limit, $offset) + { + if ($limit !== null) { + $query .= ' LIMIT ' . $limit; + if ($offset !== null) { + $query .= ' OFFSET ' . $offset; + } + } elseif ($offset !== null) { + $query .= ' LIMIT 18446744073709551615 OFFSET ' . $offset; + } + + return $query; + } + + /** + * {@inheritDoc} + */ + public function getIdentifierQuoteCharacter() + { + return '`'; + } + + /** + * {@inheritDoc} + */ + public function getRegexpExpression() + { + return 'RLIKE'; + } + + /** + * {@inheritDoc} + */ + public function getGuidExpression() + { + return 'UUID()'; + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos == false) { + return 'LOCATE(' . $substr . ', ' . $str . ')'; + } + + return 'LOCATE(' . $substr . ', ' . $str . ', '.$startPos.')'; + } + + /** + * {@inheritDoc} + */ + public function getConcatExpression() + { + $args = func_get_args(); + return 'CONCAT(' . join(', ', (array) $args) . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return 'DATEDIFF(' . $date1 . ', ' . $date2 . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateAddHourExpression($date, $hours) + { + return 'DATE_ADD(' . $date . ', INTERVAL ' . $hours . ' HOUR)'; + } + + /** + * {@inheritDoc} + */ + public function getDateSubHourExpression($date, $hours) + { + return 'DATE_SUB(' . $date . ', INTERVAL ' . $hours . ' HOUR)'; + } + + /** + * {@inheritDoc} + */ + public function getDateAddDaysExpression($date, $days) + { + return 'DATE_ADD(' . $date . ', INTERVAL ' . $days . ' DAY)'; + } + + /** + * {@inheritDoc} + */ + public function getDateSubDaysExpression($date, $days) + { + return 'DATE_SUB(' . $date . ', INTERVAL ' . $days . ' DAY)'; + } + + /** + * {@inheritDoc} + */ + public function getDateAddMonthExpression($date, $months) + { + return 'DATE_ADD(' . $date . ', INTERVAL ' . $months . ' MONTH)'; + } + + /** + * {@inheritDoc} + */ + public function getDateSubMonthExpression($date, $months) + { + return 'DATE_SUB(' . $date . ', INTERVAL ' . $months . ' MONTH)'; + } + + /** + * {@inheritDoc} + */ + public function getListDatabasesSQL() + { + return 'SHOW DATABASES'; + } + + /** + * {@inheritDoc} + */ + public function getListTableConstraintsSQL($table) + { + return 'SHOW INDEX FROM ' . $table; + } + + /** + * {@inheritDoc} + * + * Two approaches to listing the table indexes. The information_schema is + * preferred, because it doesn't cause problems with SQL keywords such as "order" or "table". + */ + public function getListTableIndexesSQL($table, $currentDatabase = null) + { + if ($currentDatabase) { + return "SELECT TABLE_NAME AS `Table`, NON_UNIQUE AS Non_Unique, INDEX_NAME AS Key_name, ". + "SEQ_IN_INDEX AS Seq_in_index, COLUMN_NAME AS Column_Name, COLLATION AS Collation, ". + "CARDINALITY AS Cardinality, SUB_PART AS Sub_Part, PACKED AS Packed, " . + "NULLABLE AS `Null`, INDEX_TYPE AS Index_Type, COMMENT AS Comment " . + "FROM information_schema.STATISTICS WHERE TABLE_NAME = '" . $table . "' AND TABLE_SCHEMA = '" . $currentDatabase . "'"; + } + + return 'SHOW INDEX FROM ' . $table; + } + + /** + * {@inheritDoc} + */ + public function getListViewsSQL($database) + { + return "SELECT * FROM information_schema.VIEWS WHERE TABLE_SCHEMA = '".$database."'"; + } + + /** + * {@inheritDoc} + */ + public function getListTableForeignKeysSQL($table, $database = null) + { + $sql = "SELECT DISTINCT k.`CONSTRAINT_NAME`, k.`COLUMN_NAME`, k.`REFERENCED_TABLE_NAME`, ". + "k.`REFERENCED_COLUMN_NAME` /*!50116 , c.update_rule, c.delete_rule */ ". + "FROM information_schema.key_column_usage k /*!50116 ". + "INNER JOIN information_schema.referential_constraints c ON ". + " c.constraint_name = k.constraint_name AND ". + " c.table_name = '$table' */ WHERE k.table_name = '$table'"; + + if ($database) { + $sql .= " AND k.table_schema = '$database' /*!50116 AND c.constraint_schema = '$database' */"; + } + + $sql .= " AND k.`REFERENCED_COLUMN_NAME` is not NULL"; + + return $sql; + } + + /** + * {@inheritDoc} + */ + public function getCreateViewSQL($name, $sql) + { + return 'CREATE VIEW ' . $name . ' AS ' . $sql; + } + + /** + * {@inheritDoc} + */ + public function getDropViewSQL($name) + { + return 'DROP VIEW '. $name; + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') + : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); + } + + /** + * Gets the SQL snippet used to declare a CLOB column type. + * TINYTEXT : 2 ^ 8 - 1 = 255 + * TEXT : 2 ^ 16 - 1 = 65535 + * MEDIUMTEXT : 2 ^ 24 - 1 = 16777215 + * LONGTEXT : 2 ^ 32 - 1 = 4294967295 + * + * @param array $field + * + * @return string + */ + public function getClobTypeDeclarationSQL(array $field) + { + if ( ! empty($field['length']) && is_numeric($field['length'])) { + $length = $field['length']; + + if ($length <= static::LENGTH_LIMIT_TINYTEXT) { + return 'TINYTEXT'; + } + + if ($length <= static::LENGTH_LIMIT_TEXT) { + return 'TEXT'; + } + + if ($length <= static::LENGTH_LIMIT_MEDIUMTEXT) { + return 'MEDIUMTEXT'; + } + } + + return 'LONGTEXT'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + if (isset($fieldDeclaration['version']) && $fieldDeclaration['version'] == true) { + return 'TIMESTAMP'; + } + + return 'DATETIME'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIME'; + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $field) + { + return 'TINYINT(1)'; + } + + /** + * Obtain DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration to be used in statements like CREATE TABLE. + * + * @param string $collation name of the collation + * + * @return string DBMS specific SQL code portion needed to set the COLLATION + * of a field declaration. + */ + public function getCollationFieldDeclaration($collation) + { + return 'COLLATE ' . $collation; + } + + /** + * {@inheritDoc} + * + * MySql prefers "autoincrement" identity columns since sequences can only + * be emulated with a table. + */ + public function prefersIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + * + * MySql supports this through AUTO_INCREMENT columns. + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsInlineColumnComments() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getListTablesSQL() + { + return "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"; + } + + /** + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + if ($database) { + return "SELECT COLUMN_NAME AS Field, COLUMN_TYPE AS Type, IS_NULLABLE AS `Null`, ". + "COLUMN_KEY AS `Key`, COLUMN_DEFAULT AS `Default`, EXTRA AS Extra, COLUMN_COMMENT AS Comment, " . + "CHARACTER_SET_NAME AS CharacterSet, COLLATION_NAME AS CollactionName ". + "FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '" . $database . "' AND TABLE_NAME = '" . $table . "'"; + } + + return 'DESCRIBE ' . $table; + } + + /** + * {@inheritDoc} + */ + public function getCreateDatabaseSQL($name) + { + return 'CREATE DATABASE ' . $name; + } + + /** + * {@inheritDoc} + */ + public function getDropDatabaseSQL($name) + { + return 'DROP DATABASE ' . $name; + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) + { + $queryFields = $this->getColumnDeclarationListSQL($columns); + + if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { + foreach ($options['uniqueConstraints'] as $index => $definition) { + $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($index, $definition); + } + } + + // add all indexes + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach($options['indexes'] as $index => $definition) { + $queryFields .= ', ' . $this->getIndexDeclarationSQL($index, $definition); + } + } + + // attach all primary keys + if (isset($options['primary']) && ! empty($options['primary'])) { + $keyColumns = array_unique(array_values($options['primary'])); + $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; + } + + $query = 'CREATE '; + + if (!empty($options['temporary'])) { + $query .= 'TEMPORARY '; + } + + $query .= 'TABLE ' . $tableName . ' (' . $queryFields . ') '; + $query .= $this->buildTableOptions($options); + $query .= $this->buildPartitionOptions($options); + + $sql[] = $query; + + if (isset($options['foreignKeys'])) { + foreach ((array) $options['foreignKeys'] as $definition) { + $sql[] = $this->getCreateForeignKeySQL($definition, $tableName); + } + } + + return $sql; + } + + /** + * Build SQL for table options + * + * @param array $options + * + * @return string + */ + private function buildTableOptions(array $options) + { + if (isset($options['table_options'])) { + return $options['table_options']; + } + + $tableOptions = array(); + + // Charset + if ( ! isset($options['charset'])) { + $options['charset'] = 'utf8'; + } + + $tableOptions[] = sprintf('DEFAULT CHARACTER SET %s', $options['charset']); + + // Collate + if ( ! isset($options['collate'])) { + $options['collate'] = 'utf8_unicode_ci'; + } + + $tableOptions[] = sprintf('COLLATE %s', $options['collate']); + + // Engine + if ( ! isset($options['engine'])) { + $options['engine'] = 'InnoDB'; + } + + $tableOptions[] = sprintf('ENGINE = %s', $options['engine']); + + // Auto increment + if (isset($options['auto_increment'])) { + $tableOptions[] = sprintf('AUTO_INCREMENT = %s', $options['auto_increment']); + } + + // Comment + if (isset($options['comment'])) { + $comment = trim($options['comment'], " '"); + + $tableOptions[] = sprintf("COMMENT = '%s' ", str_replace("'", "''", $comment)); + } + + // Row format + if (isset($options['row_format'])) { + $tableOptions[] = sprintf('ROW_FORMAT = %s', $options['row_format']); + } + + return implode(' ', $tableOptions); + } + + /** + * Build SQL for partition options. + * + * @param array $options + * + * @return string + */ + private function buildPartitionOptions(array $options) + { + return (isset($options['partition_options'])) + ? ' ' . $options['partition_options'] + : ''; + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $columnSql = array(); + $queryParts = array(); + if ($diff->newName !== false) { + $queryParts[] = 'RENAME TO ' . $diff->newName; + } + + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $columnArray = $column->toArray(); + $columnArray['comment'] = $this->getColumnComment($column); + $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); + } + + foreach ($diff->removedColumns as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $queryParts[] = 'DROP ' . $column->getQuotedName($this); + } + + foreach ($diff->changedColumns as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + /* @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */ + $column = $columnDiff->column; + $columnArray = $column->toArray(); + $columnArray['comment'] = $this->getColumnComment($column); + $queryParts[] = 'CHANGE ' . ($columnDiff->oldColumnName) . ' ' + . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $columnArray = $column->toArray(); + $columnArray['comment'] = $this->getColumnComment($column); + $queryParts[] = 'CHANGE ' . $oldColumnName . ' ' + . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); + } + + $sql = array(); + $tableSql = array(); + + if ( ! $this->onSchemaAlterTable($diff, $tableSql)) { + if (count($queryParts) > 0) { + $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . implode(", ", $queryParts); + } + $sql = array_merge( + $this->getPreAlterTableIndexForeignKeySQL($diff), + $sql, + $this->getPostAlterTableIndexForeignKeySQL($diff) + ); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * {@inheritDoc} + */ + protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) + { + $sql = array(); + $table = $diff->name; + + foreach ($diff->removedIndexes as $remKey => $remIndex) { + + foreach ($diff->addedIndexes as $addKey => $addIndex) { + if ($remIndex->getColumns() == $addIndex->getColumns()) { + + $type = ''; + if ($addIndex->isUnique()) { + $type = 'UNIQUE '; + } + + $query = 'ALTER TABLE ' . $table . ' DROP INDEX ' . $remIndex->getName() . ', '; + $query .= 'ADD ' . $type . 'INDEX ' . $addIndex->getName(); + $query .= ' (' . $this->getIndexFieldDeclarationListSQL($addIndex->getQuotedColumns($this)) . ')'; + + $sql[] = $query; + + unset($diff->removedIndexes[$remKey]); + unset($diff->addedIndexes[$addKey]); + + break; + } + } + } + + $sql = array_merge($sql, parent::getPreAlterTableIndexForeignKeySQL($diff)); + + return $sql; + } + + /** + * {@inheritDoc} + */ + protected function getCreateIndexSQLFlags(Index $index) + { + $type = ''; + if ($index->isUnique()) { + $type .= 'UNIQUE '; + } else if ($index->hasFlag('fulltext')) { + $type .= 'FULLTEXT '; + } + + return $type; + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $field) + { + return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $field) + { + return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $field) + { + return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) + { + $autoinc = ''; + if ( ! empty($columnDef['autoincrement'])) { + $autoinc = ' AUTO_INCREMENT'; + } + $unsigned = (isset($columnDef['unsigned']) && $columnDef['unsigned']) ? ' UNSIGNED' : ''; + + return $unsigned . $autoinc; + } + + /** + * {@inheritDoc} + */ + public function getAdvancedForeignKeyOptionsSQL(\Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey) + { + $query = ''; + if ($foreignKey->hasOption('match')) { + $query .= ' MATCH ' . $foreignKey->getOption('match'); + } + $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); + return $query; + } + + /** + * {@inheritDoc} + */ + public function getDropIndexSQL($index, $table=null) + { + if ($index instanceof Index) { + $indexName = $index->getQuotedName($this); + } else if(is_string($index)) { + $indexName = $index; + } else { + throw new \InvalidArgumentException('MysqlPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.'); + } + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } else if(!is_string($table)) { + throw new \InvalidArgumentException('MysqlPlatform::getDropIndexSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.'); + } + + if ($index instanceof Index && $index->isPrimary()) { + // mysql primary keys are always named "PRIMARY", + // so we cannot use them in statements because of them being keyword. + return $this->getDropPrimaryKeySQL($table); + } + + return 'DROP INDEX ' . $indexName . ' ON ' . $table; + } + + /** + * @param string $table + * + * @return string + */ + protected function getDropPrimaryKeySQL($table) + { + return 'ALTER TABLE ' . $table . ' DROP PRIMARY KEY'; + } + + /** + * {@inheritDoc} + */ + public function getSetTransactionIsolationSQL($level) + { + return 'SET SESSION TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'mysql'; + } + + /** + * {@inheritDoc} + */ + public function getReadLockSQL() + { + return 'LOCK IN SHARE MODE'; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = array( + 'tinyint' => 'boolean', + 'smallint' => 'smallint', + 'mediumint' => 'integer', + 'int' => 'integer', + 'integer' => 'integer', + 'bigint' => 'bigint', + 'tinytext' => 'text', + 'mediumtext' => 'text', + 'longtext' => 'text', + 'text' => 'text', + 'varchar' => 'string', + 'string' => 'string', + 'char' => 'string', + 'date' => 'date', + 'datetime' => 'datetime', + 'timestamp' => 'datetime', + 'time' => 'time', + 'float' => 'float', + 'double' => 'float', + 'real' => 'float', + 'decimal' => 'decimal', + 'numeric' => 'decimal', + 'year' => 'date', + 'longblob' => 'blob', + 'blob' => 'blob', + 'mediumblob' => 'blob', + 'tinyblob' => 'blob', + 'binary' => 'blob', + 'varbinary' => 'blob', + 'set' => 'simple_array', + ); + } + + /** + * {@inheritDoc} + */ + public function getVarcharMaxLength() + { + return 65535; + } + + /** + * {@inheritDoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\MySQLKeywords'; + } + + /** + * {@inheritDoc} + * + * MySQL commits a transaction implicitly when DROP TABLE is executed, however not + * if DROP TEMPORARY TABLE is executed. + */ + public function getDropTemporaryTableSQL($table) + { + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } else if(!is_string($table)) { + throw new \InvalidArgumentException('getDropTableSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.'); + } + + return 'DROP TEMPORARY TABLE ' . $table; + } + + /** + * Gets the SQL Snippet used to declare a BLOB column type. + * TINYBLOB : 2 ^ 8 - 1 = 255 + * BLOB : 2 ^ 16 - 1 = 65535 + * MEDIUMBLOB : 2 ^ 24 - 1 = 16777215 + * LONGBLOB : 2 ^ 32 - 1 = 4294967295 + * + * @param array $field + * + * @return string + */ + public function getBlobTypeDeclarationSQL(array $field) + { + if ( ! empty($field['length']) && is_numeric($field['length'])) { + $length = $field['length']; + + if ($length <= static::LENGTH_LIMIT_TINYBLOB) { + return 'TINYBLOB'; + } + + if ($length <= static::LENGTH_LIMIT_BLOB) { + return 'BLOB'; + } + + if ($length <= static::LENGTH_LIMIT_MEDIUMBLOB) { + return 'MEDIUMBLOB'; + } + } + + return 'LONGBLOB'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/OraclePlatform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/OraclePlatform.php new file mode 100644 index 0000000..6dbfbf1 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/OraclePlatform.php @@ -0,0 +1,897 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\TableDiff; +use Doctrine\DBAL\DBALException; + +/** + * OraclePlatform. + * + * @since 2.0 + * @author Roman Borschel + * @author Lukas Smith (PEAR MDB2 library) + * @author Benjamin Eberlei + */ +class OraclePlatform extends AbstractPlatform +{ + /** + * Assertion for Oracle identifiers. + * + * @link http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements008.htm + * + * @param string $identifier + * + * @throws DBALException + */ + static public function assertValidIdentifier($identifier) + { + if ( ! preg_match('(^(([a-zA-Z]{1}[a-zA-Z0-9_$#]{0,})|("[^"]+"))$)', $identifier)) { + throw new DBALException("Invalid Oracle identifier"); + } + } + + /** + * {@inheritDoc} + */ + public function getSubstringExpression($value, $position, $length = null) + { + if ($length !== null) { + return "SUBSTR($value, $position, $length)"; + } + + return "SUBSTR($value, $position)"; + } + + /** + * {@inheritDoc} + */ + public function getNowExpression($type = 'timestamp') + { + switch ($type) { + case 'date': + case 'time': + case 'timestamp': + default: + return 'TO_CHAR(CURRENT_TIMESTAMP, \'YYYY-MM-DD HH24:MI:SS\')'; + } + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos == false) { + return 'INSTR('.$str.', '.$substr.')'; + } + + return 'INSTR('.$str.', '.$substr.', '.$startPos.')'; + } + + /** + * {@inheritDoc} + */ + public function getGuidExpression() + { + return 'SYS_GUID()'; + } + + /** + * {@inheritDoc} + * + * Note: Since Oracle timestamp differences are calculated down to the microsecond we have to truncate + * them to the difference in days. This is obviously a restriction of the original functionality, but we + * need to make this a portable function. + */ + public function getDateDiffExpression($date1, $date2) + { + return "TRUNC(TO_NUMBER(SUBSTR((" . $date1 . "-" . $date2 . "), 1, INSTR(" . $date1 . "-" . $date2 .", ' '))))"; + } + + /** + * {@inheritDoc} + */ + public function getDateAddHourExpression($date, $hours) + { + return '(' . $date . '+' . $hours . '/24)'; + } + + /** + * {@inheritDoc} + */ + public function getDateSubHourExpression($date, $hours) + { + return '(' . $date . '-' . $hours . '/24)'; + } + + /** + * {@inheritDoc} + */ + public function getDateAddDaysExpression($date, $days) + { + return '(' . $date . '+' . $days . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateSubDaysExpression($date, $days) + { + return '(' . $date . '-' . $days . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateAddMonthExpression($date, $months) + { + return "ADD_MONTHS(" . $date . ", " . $months . ")"; + } + + /** + * {@inheritDoc} + */ + public function getDateSubMonthExpression($date, $months) + { + return "ADD_MONTHS(" . $date . ", -" . $months . ")"; + } + + /** + * {@inheritDoc} + */ + public function getBitAndComparisonExpression($value1, $value2) + { + return 'BITAND('.$value1 . ', ' . $value2 . ')'; + } + + /** + * {@inheritDoc} + */ + public function getBitOrComparisonExpression($value1, $value2) + { + return '(' . $value1 . '-' . + $this->getBitAndComparisonExpression($value1, $value2) + . '+' . $value2 . ')'; + } + + /** + * {@inheritDoc} + * + * Need to specifiy minvalue, since start with is hidden in the system and MINVALUE <= START WITH. + * Therefore we can use MINVALUE to be able to get a hint what START WITH was for later introspection + * in {@see listSequences()} + */ + public function getCreateSequenceSQL(Sequence $sequence) + { + return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . + ' START WITH ' . $sequence->getInitialValue() . + ' MINVALUE ' . $sequence->getInitialValue() . + ' INCREMENT BY ' . $sequence->getAllocationSize(); + } + + /** + * {@inheritDoc} + */ + public function getAlterSequenceSQL(\Doctrine\DBAL\Schema\Sequence $sequence) + { + return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . + ' INCREMENT BY ' . $sequence->getAllocationSize(); + } + + /** + * {@inheritDoc} + */ + public function getSequenceNextValSQL($sequenceName) + { + return 'SELECT ' . $sequenceName . '.nextval FROM DUAL'; + } + + /** + * {@inheritDoc} + */ + public function getSetTransactionIsolationSQL($level) + { + return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); + } + + /** + * {@inheritDoc} + */ + protected function _getTransactionIsolationLevelSQL($level) + { + switch ($level) { + case \Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED: + return 'READ UNCOMMITTED'; + case \Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED: + return 'READ COMMITTED'; + case \Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ: + case \Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE: + return 'SERIALIZABLE'; + default: + return parent::_getTransactionIsolationLevelSQL($level); + } + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $field) + { + return 'NUMBER(1)'; + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $field) + { + return 'NUMBER(10)'; + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $field) + { + return 'NUMBER(20)'; + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $field) + { + return 'NUMBER(5)'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIMESTAMP(0)'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIMESTAMP(0) WITH TIME ZONE'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) + { + return ''; + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(2000)') + : ($length ? 'VARCHAR2(' . $length . ')' : 'VARCHAR2(4000)'); + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $field) + { + return 'CLOB'; + } + + /** + * {@inheritDoc} + */ + public function getListDatabasesSQL() + { + return 'SELECT username FROM all_users'; + } + + /** + * {@inheritDoc} + */ + public function getListSequencesSQL($database) + { + return "SELECT sequence_name, min_value, increment_by FROM sys.all_sequences ". + "WHERE SEQUENCE_OWNER = '".strtoupper($database)."'"; + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($table, array $columns, array $options = array()) + { + $indexes = isset($options['indexes']) ? $options['indexes'] : array(); + $options['indexes'] = array(); + $sql = parent::_getCreateTableSQL($table, $columns, $options); + + foreach ($columns as $name => $column) { + if (isset($column['sequence'])) { + $sql[] = $this->getCreateSequenceSQL($column['sequence'], 1); + } + + if (isset($column['autoincrement']) && $column['autoincrement'] || + (isset($column['autoinc']) && $column['autoinc'])) { + $sql = array_merge($sql, $this->getCreateAutoincrementSql($name, $table)); + } + } + + if (isset($indexes) && ! empty($indexes)) { + foreach ($indexes as $index) { + $sql[] = $this->getCreateIndexSQL($index, $table); + } + } + + return $sql; + } + + /** + * {@inheritDoc} + * + * @license New BSD License + * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaOracleReader.html + */ + public function getListTableIndexesSQL($table, $currentDatabase = null) + { + $table = strtoupper($table); + + return "SELECT uind.index_name AS name, " . + " uind.index_type AS type, " . + " decode( uind.uniqueness, 'NONUNIQUE', 0, 'UNIQUE', 1 ) AS is_unique, " . + " uind_col.column_name AS column_name, " . + " uind_col.column_position AS column_pos, " . + " (SELECT ucon.constraint_type FROM user_constraints ucon WHERE ucon.constraint_name = uind.index_name) AS is_primary ". + "FROM user_indexes uind, user_ind_columns uind_col " . + "WHERE uind.index_name = uind_col.index_name AND uind_col.table_name = '$table' ORDER BY uind_col.column_position ASC"; + } + + /** + * {@inheritDoc} + */ + public function getListTablesSQL() + { + return 'SELECT * FROM sys.user_tables'; + } + + /** + * {@inheritDoc} + */ + public function getListViewsSQL($database) + { + return 'SELECT view_name, text FROM sys.user_views'; + } + + /** + * {@inheritDoc} + */ + public function getCreateViewSQL($name, $sql) + { + return 'CREATE VIEW ' . $name . ' AS ' . $sql; + } + + /** + * {@inheritDoc} + */ + public function getDropViewSQL($name) + { + return 'DROP VIEW '. $name; + } + + /** + * @param string $name + * @param string $table + * @param integer $start + * + * @return array + */ + public function getCreateAutoincrementSql($name, $table, $start = 1) + { + $table = strtoupper($table); + $name = strtoupper($name); + + $sql = array(); + + $indexName = $table . '_AI_PK'; + + $idx = new Index($indexName, array($name), true, true); + + $sql[] = 'DECLARE + constraints_Count NUMBER; +BEGIN + SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = \''.$table.'\' AND CONSTRAINT_TYPE = \'P\'; + IF constraints_Count = 0 OR constraints_Count = \'\' THEN + EXECUTE IMMEDIATE \''.$this->getCreateConstraintSQL($idx, $table).'\'; + END IF; +END;'; + + $sequenceName = $table . '_' . $name . '_SEQ'; + $sequence = new Sequence($sequenceName, $start); + $sql[] = $this->getCreateSequenceSQL($sequence); + + $triggerName = $table . '_AI_PK'; + $sql[] = 'CREATE TRIGGER ' . $triggerName . ' + BEFORE INSERT + ON ' . $table . ' + FOR EACH ROW +DECLARE + last_Sequence NUMBER; + last_InsertID NUMBER; +BEGIN + SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $name . ' FROM DUAL; + IF (:NEW.' . $name . ' IS NULL OR :NEW.'.$name.' = 0) THEN + SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $name . ' FROM DUAL; + ELSE + SELECT NVL(Last_Number, 0) INTO last_Sequence + FROM User_Sequences + WHERE Sequence_Name = \'' . $sequenceName . '\'; + SELECT :NEW.' . $name . ' INTO last_InsertID FROM DUAL; + WHILE (last_InsertID > last_Sequence) LOOP + SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL; + END LOOP; + END IF; +END;'; + + return $sql; + } + + /** + * @param string $table + * + * @return array + */ + public function getDropAutoincrementSql($table) + { + $table = strtoupper($table); + $trigger = $table . '_AI_PK'; + + $sql[] = 'DROP TRIGGER ' . $trigger; + $sql[] = $this->getDropSequenceSQL($table.'_SEQ'); + + $indexName = $table . '_AI_PK'; + $sql[] = $this->getDropConstraintSQL($indexName, $table); + + return $sql; + } + + /** + * {@inheritDoc} + */ + public function getListTableForeignKeysSQL($table) + { + $table = strtoupper($table); + + return "SELECT alc.constraint_name, + alc.DELETE_RULE, + alc.search_condition, + cols.column_name \"local_column\", + cols.position, + r_alc.table_name \"references_table\", + r_cols.column_name \"foreign_column\" + FROM user_cons_columns cols +LEFT JOIN user_constraints alc + ON alc.constraint_name = cols.constraint_name +LEFT JOIN user_constraints r_alc + ON alc.r_constraint_name = r_alc.constraint_name +LEFT JOIN user_cons_columns r_cols + ON r_alc.constraint_name = r_cols.constraint_name + AND cols.position = r_cols.position + WHERE alc.constraint_name = cols.constraint_name + AND alc.constraint_type = 'R' + AND alc.table_name = '".$table."'"; + } + + /** + * {@inheritDoc} + */ + public function getListTableConstraintsSQL($table) + { + $table = strtoupper($table); + return 'SELECT * FROM user_constraints WHERE table_name = \'' . $table . '\''; + } + + /** + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + $table = strtoupper($table); + + $tabColumnsTableName = "user_tab_columns"; + $ownerCondition = ''; + + if (null !== $database){ + $database = strtoupper($database); + $tabColumnsTableName = "all_tab_columns"; + $ownerCondition = "AND c.owner = '".$database."'"; + } + + return "SELECT c.*, d.comments FROM $tabColumnsTableName c ". + "INNER JOIN user_col_comments d ON d.TABLE_NAME = c.TABLE_NAME AND d.COLUMN_NAME = c.COLUMN_NAME ". + "WHERE c.table_name = '" . $table . "' ".$ownerCondition." ORDER BY c.column_name"; + } + + /** + * {@inheritDoc} + */ + public function getDropSequenceSQL($sequence) + { + if ($sequence instanceof Sequence) { + $sequence = $sequence->getQuotedName($this); + } + + return 'DROP SEQUENCE ' . $sequence; + } + + /** + * {@inheritDoc} + */ + public function getDropForeignKeySQL($foreignKey, $table) + { + if ($foreignKey instanceof ForeignKeyConstraint) { + $foreignKey = $foreignKey->getQuotedName($this); + } + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey; + } + + /** + * {@inheritDoc} + */ + public function getDropDatabaseSQL($database) + { + return 'DROP USER ' . $database . ' CASCADE'; + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $sql = array(); + $commentsSQL = array(); + $columnSql = array(); + + $fields = array(); + + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $fields[] = $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); + if ($comment = $this->getColumnComment($column)) { + $commentsSQL[] = $this->getCommentOnColumnSQL($diff->name, $column->getName(), $comment); + } + } + + if (count($fields)) { + $sql[] = 'ALTER TABLE ' . $diff->name . ' ADD (' . implode(', ', $fields) . ')'; + } + + $fields = array(); + foreach ($diff->changedColumns as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + $column = $columnDiff->column; + $columnHasChangedComment = $columnDiff->hasChanged('comment'); + + /** + * Do not add query part if only comment has changed + */ + if ( ! ($columnHasChangedComment && count($columnDiff->changedProperties) === 1)) { + $columnInfo = $column->toArray(); + + if ( ! $columnDiff->hasChanged('notnull')) { + $columnInfo['notnull'] = false; + } + + $fields[] = $column->getQuotedName($this) . ' ' . $this->getColumnDeclarationSQL('', $columnInfo); + } + + if ($columnHasChangedComment) { + $commentsSQL[] = $this->getCommentOnColumnSQL( + $diff->name, + $column->getName(), + $this->getColumnComment($column) + ); + } + } + + if (count($fields)) { + $sql[] = 'ALTER TABLE ' . $diff->name . ' MODIFY (' . implode(', ', $fields) . ')'; + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $sql[] = 'ALTER TABLE ' . $diff->name . ' RENAME COLUMN ' . $oldColumnName .' TO ' . $column->getQuotedName($this); + } + + $fields = array(); + foreach ($diff->removedColumns as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $fields[] = $column->getQuotedName($this); + } + + if (count($fields)) { + $sql[] = 'ALTER TABLE ' . $diff->name . ' DROP (' . implode(', ', $fields).')'; + } + + $tableSql = array(); + + if ( ! $this->onSchemaAlterTable($diff, $tableSql)) { + if ($diff->newName !== false) { + $sql[] = 'ALTER TABLE ' . $diff->name . ' RENAME TO ' . $diff->newName; + } + + $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff), $commentsSQL); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * {@inheritDoc} + */ + public function prefersSequences() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsCommentOnStatement() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'oracle'; + } + + /** + * {@inheritDoc} + */ + protected function doModifyLimitQuery($query, $limit, $offset = null) + { + $limit = (int) $limit; + $offset = (int) $offset; + + if (preg_match('/^\s*SELECT/i', $query)) { + if (!preg_match('/\sFROM\s/i', $query)) { + $query .= " FROM dual"; + } + if ($limit > 0) { + $max = $offset + $limit; + $column = '*'; + if ($offset > 0) { + $min = $offset + 1; + $query = 'SELECT * FROM (SELECT a.' . $column . ', rownum AS doctrine_rownum FROM (' . + $query . + ') a WHERE rownum <= ' . $max . ') WHERE doctrine_rownum >= ' . $min; + } else { + $query = 'SELECT a.' . $column . ' FROM (' . $query . ') a WHERE ROWNUM <= ' . $max; + } + } + } + + return $query; + } + + /** + * {@inheritDoc} + * + * Oracle returns all column names in SQL result sets in uppercase. + */ + public function getSQLResultCasing($column) + { + return strtoupper($column); + } + + /** + * {@inheritDoc} + */ + public function getCreateTemporaryTableSnippetSQL() + { + return "CREATE GLOBAL TEMPORARY TABLE"; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzFormatString() + { + return 'Y-m-d H:i:sP'; + } + + /** + * {@inheritDoc} + */ + public function getDateFormatString() + { + return 'Y-m-d 00:00:00'; + } + + /** + * {@inheritDoc} + */ + public function getTimeFormatString() + { + return '1900-01-01 H:i:s'; + } + + /** + * {@inheritDoc} + */ + public function fixSchemaElementName($schemaElementName) + { + if (strlen($schemaElementName) > 30) { + // Trim it + return substr($schemaElementName, 0, 30); + } + + return $schemaElementName; + } + + /** + * {@inheritDoc} + */ + public function getMaxIdentifierLength() + { + return 30; + } + + /** + * {@inheritDoc} + */ + public function supportsSequences() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsForeignKeyOnUpdate() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function supportsReleaseSavepoints() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + return 'TRUNCATE TABLE '.$tableName; + } + + /** + * {@inheritDoc} + */ + public function getDummySelectSQL() + { + return 'SELECT 1 FROM DUAL'; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = array( + 'integer' => 'integer', + 'number' => 'integer', + 'pls_integer' => 'boolean', + 'binary_integer' => 'boolean', + 'varchar' => 'string', + 'varchar2' => 'string', + 'nvarchar2' => 'string', + 'char' => 'string', + 'nchar' => 'string', + 'date' => 'datetime', + 'timestamp' => 'datetime', + 'timestamptz' => 'datetimetz', + 'float' => 'float', + 'long' => 'string', + 'clob' => 'text', + 'nclob' => 'text', + 'raw' => 'text', + 'long raw' => 'text', + 'rowid' => 'string', + 'urowid' => 'string', + 'blob' => 'blob', + ); + } + + /** + * {@inheritDoc} + */ + public function releaseSavePoint($savepoint) + { + return ''; + } + + /** + * {@inheritDoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\OracleKeywords'; + } + + /** + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $field) + { + return 'BLOB'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php new file mode 100644 index 0000000..3566c61 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/PostgreSqlPlatform.php @@ -0,0 +1,880 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\Schema\TableDiff; + +/** + * PostgreSqlPlatform. + * + * @since 2.0 + * @author Roman Borschel + * @author Lukas Smith (PEAR MDB2 library) + * @author Benjamin Eberlei + * @todo Rename: PostgreSQLPlatform + */ +class PostgreSqlPlatform extends AbstractPlatform +{ + /** + * @var bool + */ + private $useBooleanTrueFalseStrings = true; + + /** + * PostgreSQL has different behavior with some drivers + * with regard to how booleans have to be handled. + * + * Enables use of 'true'/'false' or otherwise 1 and 0 instead. + * + * @param bool $flag + */ + public function setUseBooleanTrueFalseStrings($flag) + { + $this->useBooleanTrueFalseStrings = (bool)$flag; + } + + /** + * {@inheritDoc} + */ + public function getSubstringExpression($value, $from, $length = null) + { + if ($length === null) { + return 'SUBSTRING(' . $value . ' FROM ' . $from . ')'; + } + + return 'SUBSTRING(' . $value . ' FROM ' . $from . ' FOR ' . $length . ')'; + } + + /** + * {@inheritDoc} + */ + public function getNowExpression() + { + return 'LOCALTIMESTAMP(0)'; + } + + /** + * {@inheritDoc} + */ + public function getRegexpExpression() + { + return 'SIMILAR TO'; + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos !== false) { + $str = $this->getSubstringExpression($str, $startPos); + + return 'CASE WHEN (POSITION('.$substr.' IN '.$str.') = 0) THEN 0 ELSE (POSITION('.$substr.' IN '.$str.') + '.($startPos-1).') END'; + } + + return 'POSITION('.$substr.' IN '.$str.')'; + } + + /** + * {@inheritDoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return '(DATE(' . $date1 . ')-DATE(' . $date2 . '))'; + } + + /** + * {@inheritDoc} + */ + public function getDateAddHourExpression($date, $hours) + { + return "(" . $date ." + (" . $hours . " || ' hour')::interval)"; + } + + /** + * {@inheritDoc} + */ + public function getDateSubHourExpression($date, $hours) + { + return "(" . $date ." - (" . $hours . " || ' hour')::interval)"; + } + + /** + * {@inheritDoc} + */ + public function getDateAddDaysExpression($date, $days) + { + return "(" . $date ." + (" . $days . " || ' day')::interval)"; + } + + /** + * {@inheritDoc} + */ + public function getDateSubDaysExpression($date, $days) + { + return "(" . $date ." - (" . $days . " || ' day')::interval)"; + } + + /** + * {@inheritDoc} + */ + public function getDateAddMonthExpression($date, $months) + { + return "(" . $date ." + (" . $months . " || ' month')::interval)"; + } + + /** + * {@inheritDoc} + */ + public function getDateSubMonthExpression($date, $months) + { + return "(" . $date ." - (" . $months . " || ' month')::interval)"; + } + + /** + * {@inheritDoc} + */ + public function supportsSequences() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsSchemas() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsCommentOnStatement() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function prefersSequences() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function hasNativeGuidType() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getListDatabasesSQL() + { + return 'SELECT datname FROM pg_database'; + } + + /** + * {@inheritDoc} + */ + public function getListSequencesSQL($database) + { + return "SELECT + c.relname, n.nspname AS schemaname + FROM + pg_class c, pg_namespace n + WHERE relkind = 'S' AND n.oid = c.relnamespace AND + (n.nspname NOT LIKE 'pg_%' AND n.nspname != 'information_schema')"; + } + + /** + * {@inheritDoc} + */ + public function getListTablesSQL() + { + return "SELECT tablename AS table_name, schemaname AS schema_name + FROM pg_tables WHERE schemaname NOT LIKE 'pg_%' AND schemaname != 'information_schema' AND tablename != 'geometry_columns' AND tablename != 'spatial_ref_sys'"; + } + + /** + * {@inheritDoc} + */ + public function getListViewsSQL($database) + { + return 'SELECT viewname, definition FROM pg_views'; + } + + /** + * {@inheritDoc} + */ + public function getListTableForeignKeysSQL($table, $database = null) + { + return "SELECT r.conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef + FROM pg_catalog.pg_constraint r + WHERE r.conrelid = + ( + SELECT c.oid + FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n + WHERE " .$this->getTableWhereClause($table) ." AND n.oid = c.relnamespace + ) + AND r.contype = 'f'"; + } + + /** + * {@inheritDoc} + */ + public function getCreateViewSQL($name, $sql) + { + return 'CREATE VIEW ' . $name . ' AS ' . $sql; + } + + /** + * {@inheritDoc} + */ + public function getDropViewSQL($name) + { + return 'DROP VIEW '. $name; + } + + /** + * {@inheritDoc} + */ + public function getListTableConstraintsSQL($table) + { + return "SELECT + relname + FROM + pg_class + WHERE oid IN ( + SELECT indexrelid + FROM pg_index, pg_class + WHERE pg_class.relname = '$table' + AND pg_class.oid = pg_index.indrelid + AND (indisunique = 't' OR indisprimary = 't') + )"; + } + + /** + * {@inheritDoc} + * + * @license New BSD License + * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html + */ + public function getListTableIndexesSQL($table, $currentDatabase = null) + { + return "SELECT relname, pg_index.indisunique, pg_index.indisprimary, + pg_index.indkey, pg_index.indrelid + FROM pg_class, pg_index + WHERE oid IN ( + SELECT indexrelid + FROM pg_index si, pg_class sc, pg_namespace sn + WHERE " . $this->getTableWhereClause($table, 'sc', 'sn')." AND sc.oid=si.indrelid AND sc.relnamespace = sn.oid + ) AND pg_index.indexrelid = oid"; + } + + /** + * @param string $table + * @param string $classAlias + * @param string $namespaceAlias + * + * @return string + */ + private function getTableWhereClause($table, $classAlias = 'c', $namespaceAlias = 'n') + { + $whereClause = $namespaceAlias.".nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') AND "; + if (strpos($table, ".") !== false) { + list($schema, $table) = explode(".", $table); + $schema = "'" . $schema . "'"; + } else { + $schema = "ANY(string_to_array((select replace(setting,'\"\$user\"',user) from pg_catalog.pg_settings where name = 'search_path'),','))"; + } + $whereClause .= "$classAlias.relname = '" . $table . "' AND $namespaceAlias.nspname = $schema"; + + return $whereClause; + } + + /** + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + return "SELECT + a.attnum, + a.attname AS field, + t.typname AS type, + format_type(a.atttypid, a.atttypmod) AS complete_type, + (SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type, + (SELECT format_type(t2.typbasetype, t2.typtypmod) FROM + pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type, + a.attnotnull AS isnotnull, + (SELECT 't' + FROM pg_index + WHERE c.oid = pg_index.indrelid + AND pg_index.indkey[0] = a.attnum + AND pg_index.indisprimary = 't' + ) AS pri, + (SELECT pg_attrdef.adsrc + FROM pg_attrdef + WHERE c.oid = pg_attrdef.adrelid + AND pg_attrdef.adnum=a.attnum + ) AS default, + (SELECT pg_description.description + FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid + ) AS comment + FROM pg_attribute a, pg_class c, pg_type t, pg_namespace n + WHERE ".$this->getTableWhereClause($table, 'c', 'n') ." + AND a.attnum > 0 + AND a.attrelid = c.oid + AND a.atttypid = t.oid + AND n.oid = c.relnamespace + ORDER BY a.attnum"; + } + + /** + * {@inheritDoc} + */ + public function getCreateDatabaseSQL($name) + { + return 'CREATE DATABASE ' . $name; + } + + /** + * {@inheritDoc} + */ + public function getAdvancedForeignKeyOptionsSQL(\Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey) + { + $query = ''; + + if ($foreignKey->hasOption('match')) { + $query .= ' MATCH ' . $foreignKey->getOption('match'); + } + + $query .= parent::getAdvancedForeignKeyOptionsSQL($foreignKey); + + if ($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) { + $query .= ' DEFERRABLE'; + } else { + $query .= ' NOT DEFERRABLE'; + } + + if (($foreignKey->hasOption('feferred') && $foreignKey->getOption('feferred') !== false) + || ($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) + ) { + $query .= ' INITIALLY DEFERRED'; + } else { + $query .= ' INITIALLY IMMEDIATE'; + } + + return $query; + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $sql = array(); + $commentsSQL = array(); + $columnSql = array(); + + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $query = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $column->toArray()); + $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query; + if ($comment = $this->getColumnComment($column)) { + $commentsSQL[] = $this->getCommentOnColumnSQL($diff->name, $column->getName(), $comment); + } + } + + foreach ($diff->removedColumns as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $query = 'DROP ' . $column->getQuotedName($this); + $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query; + } + + foreach ($diff->changedColumns as $columnDiff) { + /** @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */ + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + $oldColumnName = $columnDiff->oldColumnName; + $column = $columnDiff->column; + + if ($columnDiff->hasChanged('type')) { + $type = $column->getType(); + + // here was a server version check before, but DBAL API does not support this anymore. + $query = 'ALTER ' . $oldColumnName . ' TYPE ' . $type->getSqlDeclaration($column->toArray(), $this); + $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query; + } + + if ($columnDiff->hasChanged('default')) { + $query = 'ALTER ' . $oldColumnName . ' SET ' . $this->getDefaultValueDeclarationSQL($column->toArray()); + $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query; + } + + if ($columnDiff->hasChanged('notnull')) { + $query = 'ALTER ' . $oldColumnName . ' ' . ($column->getNotNull() ? 'SET' : 'DROP') . ' NOT NULL'; + $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query; + } + + if ($columnDiff->hasChanged('autoincrement')) { + if ($column->getAutoincrement()) { + // add autoincrement + $seqName = $diff->name . '_' . $oldColumnName . '_seq'; + + $sql[] = "CREATE SEQUENCE " . $seqName; + $sql[] = "SELECT setval('" . $seqName . "', (SELECT MAX(" . $oldColumnName . ") FROM " . $diff->name . "))"; + $query = "ALTER " . $oldColumnName . " SET DEFAULT nextval('" . $seqName . "')"; + $sql[] = "ALTER TABLE " . $diff->name . " " . $query; + } else { + // Drop autoincrement, but do NOT drop the sequence. It might be re-used by other tables or have + $query = "ALTER " . $oldColumnName . " " . "DROP DEFAULT"; + $sql[] = "ALTER TABLE " . $diff->name . " " . $query; + } + } + + if ($columnDiff->hasChanged('comment')) { + $commentsSQL[] = $this->getCommentOnColumnSQL( + $diff->name, + $column->getName(), + $this->getColumnComment($column) + ); + } + + if ($columnDiff->hasChanged('length')) { + $query = 'ALTER ' . $column->getName() . ' TYPE ' . $column->getType()->getSqlDeclaration($column->toArray(), $this); + $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query; + } + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $sql[] = 'ALTER TABLE ' . $diff->name . ' RENAME COLUMN ' . $oldColumnName . ' TO ' . $column->getQuotedName($this); + } + + $tableSql = array(); + + if ( ! $this->onSchemaAlterTable($diff, $tableSql)) { + if ($diff->newName !== false) { + $sql[] = 'ALTER TABLE ' . $diff->name . ' RENAME TO ' . $diff->newName; + } + + $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff), $commentsSQL); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * {@inheritdoc} + */ + public function getCommentOnColumnSQL($tableName, $columnName, $comment) + { + $comment = $comment === null ? 'NULL' : "'$comment'"; + + return "COMMENT ON COLUMN $tableName.$columnName IS $comment"; + } + + /** + * {@inheritDoc} + */ + public function getCreateSequenceSQL(\Doctrine\DBAL\Schema\Sequence $sequence) + { + return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . + ' INCREMENT BY ' . $sequence->getAllocationSize() . + ' MINVALUE ' . $sequence->getInitialValue() . + ' START ' . $sequence->getInitialValue(); + } + + /** + * {@inheritDoc} + */ + public function getAlterSequenceSQL(\Doctrine\DBAL\Schema\Sequence $sequence) + { + return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . + ' INCREMENT BY ' . $sequence->getAllocationSize(); + } + + /** + * {@inheritDoc} + */ + public function getDropSequenceSQL($sequence) + { + if ($sequence instanceof \Doctrine\DBAL\Schema\Sequence) { + $sequence = $sequence->getQuotedName($this); + } + return 'DROP SEQUENCE ' . $sequence . ' CASCADE'; + } + + /** + * {@inheritDoc} + */ + public function getCreateSchemaSQL($schemaName) + { + return 'CREATE SCHEMA ' . $schemaName; + } + + /** + * {@inheritDoc} + */ + public function schemaNeedsCreation($schemaName) + { + return !in_array($schemaName, array('default', 'public')); + } + + /** + * {@inheritDoc} + */ + public function getDropForeignKeySQL($foreignKey, $table) + { + return $this->getDropConstraintSQL($foreignKey, $table); + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) + { + $queryFields = $this->getColumnDeclarationListSQL($columns); + + if (isset($options['primary']) && ! empty($options['primary'])) { + $keyColumns = array_unique(array_values($options['primary'])); + $queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')'; + } + + $query = 'CREATE TABLE ' . $tableName . ' (' . $queryFields . ')'; + + $sql[] = $query; + + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach ($options['indexes'] as $index) { + $sql[] = $this->getCreateIndexSQL($index, $tableName); + } + } + + if (isset($options['foreignKeys'])) { + foreach ((array) $options['foreignKeys'] as $definition) { + $sql[] = $this->getCreateForeignKeySQL($definition, $tableName); + } + } + + return $sql; + } + + /** + * {@inheritDoc} + * + * Postgres wants boolean values converted to the strings 'true'/'false'. + */ + public function convertBooleans($item) + { + if ( ! $this->useBooleanTrueFalseStrings) { + return parent::convertBooleans($item); + } + + if (is_array($item)) { + foreach ($item as $key => $value) { + if (is_bool($value) || is_numeric($item)) { + $item[$key] = ($value) ? 'true' : 'false'; + } + } + } else { + if (is_bool($item) || is_numeric($item)) { + $item = ($item) ? 'true' : 'false'; + } + } + + return $item; + } + + /** + * {@inheritDoc} + */ + public function getSequenceNextValSQL($sequenceName) + { + return "SELECT NEXTVAL('" . $sequenceName . "')"; + } + + /** + * {@inheritDoc} + */ + public function getSetTransactionIsolationSQL($level) + { + return 'SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL ' + . $this->_getTransactionIsolationLevelSQL($level); + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $field) + { + return 'BOOLEAN'; + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $field) + { + if ( ! empty($field['autoincrement'])) { + return 'SERIAL'; + } + + return 'INT'; + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $field) + { + if ( ! empty($field['autoincrement'])) { + return 'BIGSERIAL'; + } + return 'BIGINT'; + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $field) + { + return 'SMALLINT'; + } + + /** + * {@inheritDoc} + */ + public function getGuidTypeDeclarationSQL(array $field) + { + return 'UUID'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIMESTAMP(0) WITHOUT TIME ZONE'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIMESTAMP(0) WITH TIME ZONE'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIME(0) WITHOUT TIME ZONE'; + } + + /** + * {@inheritDoc} + */ + public function getGuidExpression() + { + return 'UUID_GENERATE_V4()'; + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) + { + return ''; + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') + : ($length ? 'VARCHAR(' . $length . ')' : 'VARCHAR(255)'); + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $field) + { + return 'TEXT'; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'postgresql'; + } + + /** + * {@inheritDoc} + * + * PostgreSQL returns all column names in SQL result sets in lowercase. + */ + public function getSQLResultCasing($column) + { + return strtolower($column); + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzFormatString() + { + return 'Y-m-d H:i:sO'; + } + + /** + * {@inheritDoc} + */ + public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) + { + return 'INSERT INTO ' . $quotedTableName . ' (' . $quotedIdentifierColumnName . ') VALUES (DEFAULT)'; + } + + /** + * {@inheritDoc} + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + return 'TRUNCATE '.$tableName.' '.(($cascade)?'CASCADE':''); + } + + /** + * {@inheritDoc} + */ + public function getReadLockSQL() + { + return 'FOR SHARE'; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = array( + 'smallint' => 'smallint', + 'int2' => 'smallint', + 'serial' => 'integer', + 'serial4' => 'integer', + 'int' => 'integer', + 'int4' => 'integer', + 'integer' => 'integer', + 'bigserial' => 'bigint', + 'serial8' => 'bigint', + 'bigint' => 'bigint', + 'int8' => 'bigint', + 'bool' => 'boolean', + 'boolean' => 'boolean', + 'text' => 'text', + 'varchar' => 'string', + 'interval' => 'string', + '_varchar' => 'string', + 'char' => 'string', + 'bpchar' => 'string', + 'inet' => 'string', + 'date' => 'date', + 'datetime' => 'datetime', + 'timestamp' => 'datetime', + 'timestamptz' => 'datetimetz', + 'time' => 'time', + 'timetz' => 'time', + 'float' => 'float', + 'float4' => 'float', + 'float8' => 'float', + 'double' => 'float', + 'double precision' => 'float', + 'real' => 'float', + 'decimal' => 'decimal', + 'money' => 'decimal', + 'numeric' => 'decimal', + 'year' => 'date', + 'uuid' => 'guid', + 'bytea' => 'blob', + ); + } + + /** + * {@inheritDoc} + */ + public function getVarcharMaxLength() + { + return 65535; + } + + /** + * {@inheritDoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\PostgreSQLKeywords'; + } + + /** + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $field) + { + return 'BYTEA'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAzurePlatform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAzurePlatform.php new file mode 100644 index 0000000..71829cd --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLAzurePlatform.php @@ -0,0 +1,50 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\Schema\Table; + +/** + * Platform to ensure compatibility of Doctrine with SQL Azure + * + * On top of SQL Server 2008 the following functionality is added: + * + * - Create tables with the FEDERATED ON syntax. + */ +class SQLAzurePlatform extends SQLServer2008Platform +{ + /** + * {@inheritDoc} + */ + public function getCreateTableSQL(Table $table, $createFlags=self::CREATE_INDEXES) + { + $sql = parent::getCreateTableSQL($table, $createFlags); + + if ($table->hasOption('azure.federatedOnColumnName')) { + $distributionName = $table->getOption('azure.federatedOnDistributionName'); + $columnName = $table->getOption('azure.federatedOnColumnName'); + $stmt = ' FEDERATED ON (' . $distributionName . ' = ' . $columnName . ')'; + + $sql[0] = $sql[0] . $stmt; + } + + return $sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2005Platform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2005Platform.php new file mode 100644 index 0000000..6dc1188 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2005Platform.php @@ -0,0 +1,63 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +/** + * Platform to ensure compatibility of Doctrine with Microsoft SQL Server 2005 version and + * higher. + * + * Differences to SQL Server 2008 are: + * + * - DATETIME2 datatype does not exist, only DATETIME which has a precision of + * 3. This is not supported by PHP DateTime, so we are emulating it by + * setting .000 manually. + * - Starting with SQLServer2005 VARCHAR(MAX), VARBINARY(MAX) and + * NVARCHAR(max) replace the old TEXT, NTEXT and IMAGE types. See + * {@link http://www.sql-server-helper.com/faq/sql-server-2005-varchar-max-p01.aspx} + * for more information. + */ +class SQLServer2005Platform extends SQLServerPlatform +{ + /** + * {@inheritDoc} + */ + public function supportsLimitOffset() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $field) + { + return 'VARCHAR(MAX)'; + } + + /** + * {@inheritdoc} + * + * Returns Microsoft SQL Server 2005 specific keywords class + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\SQLServer2005Keywords'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2008Platform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2008Platform.php new file mode 100644 index 0000000..b2cde37 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2008Platform.php @@ -0,0 +1,119 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +/** + * Platform to ensure compatibility of Doctrine with Microsoft SQL Server 2008 version. + * + * Differences to SQL Server 2005 and before are that a new DATETIME2 type was + * introduced that has a higher precision. + */ +class SQLServer2008Platform extends SQLServer2005Platform +{ + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + // 3 - microseconds precision length + // http://msdn.microsoft.com/en-us/library/ms187819.aspx + return 'DATETIME2(6)'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIME(0)'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATETIMEOFFSET(6)'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeFormatString() + { + return 'Y-m-d H:i:s.u'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzFormatString() + { + return 'Y-m-d H:i:s.u P'; + } + + /** + * {@inheritDoc} + */ + public function getDateFormatString() + { + return 'Y-m-d'; + } + + /** + * {@inheritDoc} + */ + public function getTimeFormatString() + { + return 'H:i:s'; + } + + /** + * {@inheritDoc} + * + * Adding Datetime2 Type + */ + protected function initializeDoctrineTypeMappings() + { + parent::initializeDoctrineTypeMappings(); + $this->doctrineTypeMapping['datetime2'] = 'datetime'; + $this->doctrineTypeMapping['date'] = 'date'; + $this->doctrineTypeMapping['time'] = 'time'; + $this->doctrineTypeMapping['datetimeoffset'] = 'datetimetz'; + } + + /** + * {@inheritdoc} + * + * Returns Microsoft SQL Server 2008 specific keywords class + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\SQLServer2008Keywords'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2012Platform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2012Platform.php new file mode 100644 index 0000000..d5c9570 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServer2012Platform.php @@ -0,0 +1,98 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\Schema\Sequence; + +/** + * Platform to ensure compatibility of Doctrine with Microsoft SQL Server 2012 version. + * + * Differences to SQL Server 2008 and before are that sequences are introduced. + * + * @author Steve Müller + */ +class SQLServer2012Platform extends SQLServer2008Platform +{ + /** + * {@inheritdoc} + */ + public function getAlterSequenceSQL(Sequence $sequence) + { + return 'ALTER SEQUENCE ' . $sequence->getQuotedName($this) . + ' INCREMENT BY ' . $sequence->getAllocationSize(); + } + + /** + * {@inheritdoc} + */ + public function getCreateSequenceSQL(Sequence $sequence) + { + return 'CREATE SEQUENCE ' . $sequence->getQuotedName($this) . + ' START WITH ' . $sequence->getInitialValue() . + ' INCREMENT BY ' . $sequence->getAllocationSize() . + ' MINVALUE ' . $sequence->getInitialValue(); + } + + /** + * {@inheritdoc} + */ + public function getDropSequenceSQL($sequence) + { + if ($sequence instanceof Sequence) { + $sequence = $sequence->getQuotedName($this); + } + + return 'DROP SEQUENCE ' . $sequence; + } + + /** + * {@inheritdoc} + */ + public function getListSequencesSQL($database) + { + return 'SELECT seq.name, seq.increment, seq.start_value FROM sys.sequences AS seq'; + } + + /** + * {@inheritdoc} + */ + public function getSequenceNextValSQL($sequenceName) + { + return 'SELECT NEXT VALUE FOR ' . $sequenceName; + } + + /** + * {@inheritdoc} + */ + public function supportsSequences() + { + return true; + } + + /** + * {@inheritdoc} + * + * Returns Microsoft SQL Server 2012 specific keywords class + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\SQLServer2012Keywords'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php new file mode 100644 index 0000000..6dcd3ca --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SQLServerPlatform.php @@ -0,0 +1,1169 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\LockMode; +use Doctrine\DBAL\Schema\TableDiff; +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Table; + +/** + * The SQLServerPlatform provides the behavior, features and SQL dialect of the + * Microsoft SQL Server database platform. + * + * @since 2.0 + * @author Roman Borschel + * @author Jonathan H. Wage + * @author Benjamin Eberlei + * @author Steve Müller + */ +class SQLServerPlatform extends AbstractPlatform +{ + /** + * {@inheritDoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return 'DATEDIFF(day, ' . $date2 . ',' . $date1 . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateAddHourExpression($date, $hours) + { + return 'DATEADD(hour, ' . $hours . ', ' . $date . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateSubHourExpression($date, $hours) + { + return 'DATEADD(hour, -1 * ' . $hours . ', ' . $date . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateAddDaysExpression($date, $days) + { + return 'DATEADD(day, ' . $days . ', ' . $date . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateSubDaysExpression($date, $days) + { + return 'DATEADD(day, -1 * ' . $days . ', ' . $date . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateAddMonthExpression($date, $months) + { + return 'DATEADD(month, ' . $months . ', ' . $date . ')'; + } + + /** + * {@inheritDoc} + */ + public function getDateSubMonthExpression($date, $months) + { + return 'DATEADD(month, -1 * ' . $months . ', ' . $date . ')'; + } + + /** + * {@inheritDoc} + * + * Microsoft SQL Server prefers "autoincrement" identity columns + * since sequences can only be emulated with a table. + */ + public function prefersIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + * + * Microsoft SQL Server supports this through AUTO_INCREMENT columns. + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsReleaseSavepoints() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function supportsSchemas() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function hasNativeGuidType() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getCreateDatabaseSQL($name) + { + return 'CREATE DATABASE ' . $name; + } + + /** + * {@inheritDoc} + */ + public function getDropDatabaseSQL($name) + { + return 'DROP DATABASE ' . $name; + } + + /** + * {@inheritDoc} + */ + public function supportsCreateDropDatabase() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function getDropForeignKeySQL($foreignKey, $table) + { + if ($foreignKey instanceof ForeignKeyConstraint) { + $foreignKey = $foreignKey->getQuotedName($this); + } + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + return 'ALTER TABLE ' . $table . ' DROP CONSTRAINT ' . $foreignKey; + } + + /** + * {@inheritDoc} + */ + public function getDropIndexSQL($index, $table = null) + { + if ($index instanceof Index) { + $index = $index->getQuotedName($this); + } else if (!is_string($index)) { + throw new \InvalidArgumentException('AbstractPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.'); + } + + if (!isset($table)) { + return 'DROP INDEX ' . $index; + } + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + return "IF EXISTS (SELECT * FROM sysobjects WHERE name = '$index') + ALTER TABLE " . $table . " DROP CONSTRAINT " . $index . " + ELSE + DROP INDEX " . $index . " ON " . $table; + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($tableName, array $columns, array $options = array()) + { + $defaultConstraintsSql = array(); + + // @todo does other code breaks because of this? + // force primary keys to be not null + foreach ($columns as &$column) { + if (isset($column['primary']) && $column['primary']) { + $column['notnull'] = true; + } + + /** + * Build default constraints SQL statements + */ + if ( ! empty($column['default']) || is_numeric($column['default'])) { + $defaultConstraintsSql[] = 'ALTER TABLE ' . $tableName . + ' ADD' . $this->getDefaultConstraintDeclarationSQL($tableName, $column); + } + } + + $columnListSql = $this->getColumnDeclarationListSQL($columns); + + if (isset($options['uniqueConstraints']) && !empty($options['uniqueConstraints'])) { + foreach ($options['uniqueConstraints'] as $name => $definition) { + $columnListSql .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition); + } + } + + if (isset($options['primary']) && !empty($options['primary'])) { + $flags = ''; + if (isset($options['primary_index']) && $options['primary_index']->hasFlag('nonclustered')) { + $flags = ' NONCLUSTERED'; + } + $columnListSql .= ', PRIMARY KEY' . $flags . ' (' . implode(', ', array_unique(array_values($options['primary']))) . ')'; + } + + $query = 'CREATE TABLE ' . $tableName . ' (' . $columnListSql; + + $check = $this->getCheckDeclarationSQL($columns); + if (!empty($check)) { + $query .= ', ' . $check; + } + $query .= ')'; + + $sql[] = $query; + + if (isset($options['indexes']) && !empty($options['indexes'])) { + foreach ($options['indexes'] as $index) { + $sql[] = $this->getCreateIndexSQL($index, $tableName); + } + } + + if (isset($options['foreignKeys'])) { + foreach ((array) $options['foreignKeys'] as $definition) { + $sql[] = $this->getCreateForeignKeySQL($definition, $tableName); + } + } + + return array_merge($sql, $defaultConstraintsSql); + } + + /** + * {@inheritDoc} + */ + public function getCreatePrimaryKeySQL(Index $index, $table) + { + $flags = ''; + if ($index->hasFlag('nonclustered')) { + $flags = ' NONCLUSTERED'; + } + return 'ALTER TABLE ' . $table . ' ADD PRIMARY KEY' . $flags . ' (' . $this->getIndexFieldDeclarationListSQL($index->getQuotedColumns($this)) . ')'; + } + + /** + * Returns the SQL snippet for declaring a default constraint. + * + * @param string $table Name of the table to return the default constraint declaration for. + * @param array $column Column definition. + * + * @return string + * + * @throws \InvalidArgumentException + */ + public function getDefaultConstraintDeclarationSQL($table, array $column) + { + if (empty($column['default']) && ! is_numeric($column['default'])) { + throw new \InvalidArgumentException("Incomplete column definition. 'default' required."); + } + + return + ' CONSTRAINT ' . + $this->generateDefaultConstraintName($table, $column['name']) . + $this->getDefaultValueDeclarationSQL($column) . + ' FOR ' . $column['name']; + } + + /** + * {@inheritDoc} + */ + public function getUniqueConstraintDeclarationSQL($name, Index $index) + { + $constraint = parent::getUniqueConstraintDeclarationSQL($name, $index); + + $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index); + + return $constraint; + } + + /** + * {@inheritDoc} + */ + public function getCreateIndexSQL(Index $index, $table) + { + $constraint = parent::getCreateIndexSQL($index, $table); + + if ($index->isUnique() && !$index->isPrimary()) { + $constraint = $this->_appendUniqueConstraintDefinition($constraint, $index); + } + + return $constraint; + } + + /** + * {@inheritDoc} + */ + protected function getCreateIndexSQLFlags(Index $index) + { + $type = ''; + if ($index->isUnique()) { + $type .= 'UNIQUE '; + } + + if ($index->hasFlag('clustered')) { + $type .= 'CLUSTERED '; + } else if ($index->hasFlag('nonclustered')) { + $type .= 'NONCLUSTERED '; + } + + return $type; + } + + /** + * Extend unique key constraint with required filters + * + * @param string $sql + * @param \Doctrine\DBAL\Schema\Index $index + * + * @return string + */ + private function _appendUniqueConstraintDefinition($sql, Index $index) + { + $fields = array(); + + foreach ($index->getQuotedColumns($this) as $field) { + $fields[] = $field . ' IS NOT NULL'; + } + + return $sql . ' WHERE ' . implode(' AND ', $fields); + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $queryParts = array(); + $sql = array(); + $columnSql = array(); + + /** @var \Doctrine\DBAL\Schema\Column $column */ + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $columnDef = $column->toArray(); + $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef); + + if ( ! empty($columnDef['default']) || is_numeric($columnDef['default'])) { + $columnDef['name'] = $column->getQuotedName($this); + $queryParts[] = 'ADD' . $this->getDefaultConstraintDeclarationSQL($diff->name, $columnDef); + } + } + + foreach ($diff->removedColumns as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $queryParts[] = 'DROP COLUMN ' . $column->getQuotedName($this); + } + + /* @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */ + foreach ($diff->changedColumns as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + $fromColumn = $columnDiff->fromColumn; + $fromColumnDefault = isset($fromColumn) ? $fromColumn->getDefault() : null; + $column = $columnDiff->column; + $columnDef = $column->toArray(); + $columnDefaultHasChanged = $columnDiff->hasChanged('default'); + + /** + * Drop existing column default constraint + * if default value has changed and another + * default constraint already exists for the column. + */ + if ($columnDefaultHasChanged && ( ! empty($fromColumnDefault) || is_numeric($fromColumnDefault))) { + $queryParts[] = 'DROP CONSTRAINT ' . + $this->generateDefaultConstraintName($diff->name, $columnDiff->oldColumnName); + } + + $queryParts[] = 'ALTER COLUMN ' . + $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef); + + if ($columnDefaultHasChanged && (! empty($columnDef['default']) || is_numeric($columnDef['default']))) { + $columnDef['name'] = $column->getQuotedName($this); + $queryParts[] = 'ADD' . $this->getDefaultConstraintDeclarationSQL($diff->name, $columnDef); + } + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $sql[] = "sp_RENAME '". $diff->name. ".". $oldColumnName . "' , '".$column->getQuotedName($this)."', 'COLUMN'"; + + $columnDef = $column->toArray(); + + /** + * Drop existing default constraint for the old column name + * if column has default value. + */ + if ( ! empty($columnDef['default']) || is_numeric($columnDef['default'])) { + $queryParts[] = 'DROP CONSTRAINT ' . + $this->generateDefaultConstraintName($diff->name, $oldColumnName); + } + + $queryParts[] = 'ALTER COLUMN ' . + $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnDef); + + /** + * Readd default constraint for the new column name. + */ + if ( ! empty($columnDef['default']) || is_numeric($columnDef['default'])) { + $columnDef['name'] = $column->getQuotedName($this); + $queryParts[] = 'ADD' . $this->getDefaultConstraintDeclarationSQL($diff->name, $columnDef); + } + } + + $tableSql = array(); + + if ($this->onSchemaAlterTable($diff, $tableSql)) { + return array_merge($tableSql, $columnSql); + } + + foreach ($queryParts as $query) { + $sql[] = 'ALTER TABLE ' . $diff->name . ' ' . $query; + } + + $sql = array_merge($sql, $this->_getAlterTableIndexForeignKeySQL($diff)); + + if ($diff->newName !== false) { + $sql[] = "sp_RENAME '" . $diff->name . "', '" . $diff->newName . "'"; + + /** + * Rename table's default constraints names + * to match the new table name. + * This is necessary to ensure that the default + * constraints can be referenced in future table + * alterations as the table name is encoded in + * default constraints' names. + */ + $sql[] = "DECLARE @sql NVARCHAR(MAX) = N''; " . + "SELECT @sql += N'EXEC sp_rename N''' + dc.name + ''', N''' " . + "+ REPLACE(dc.name, '" . $this->generateIdentifierName($diff->name) . "', " . + "'" . $this->generateIdentifierName($diff->newName) . "') + ''', ''OBJECT'';' " . + "FROM sys.default_constraints dc " . + "JOIN sys.tables tbl ON dc.parent_object_id = tbl.object_id " . + "WHERE tbl.name = '" . $diff->newName . "';" . + "EXEC sp_executesql @sql"; + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * {@inheritDoc} + */ + public function getEmptyIdentityInsertSQL($quotedTableName, $quotedIdentifierColumnName) + { + return 'INSERT INTO ' . $quotedTableName . ' DEFAULT VALUES'; + } + + /** + * {@inheritDoc} + */ + public function getListTablesSQL() + { + // "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams + return "SELECT name FROM sysobjects WHERE type = 'U' AND name != 'sysdiagrams' ORDER BY name"; + } + + /** + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $database = null) + { + return "SELECT col.name, + type.name AS type, + col.max_length AS length, + ~col.is_nullable AS notnull, + def.definition AS [default], + col.scale, + col.precision, + col.is_identity AS autoincrement, + col.collation_name AS collation + FROM sys.columns AS col + JOIN sys.types AS type + ON col.user_type_id = type.user_type_id + JOIN sys.objects AS obj + ON col.object_id = obj.object_id + LEFT JOIN sys.default_constraints def + ON col.default_object_id = def.object_id + AND col.object_id = def.parent_object_id + WHERE obj.type = 'U' + AND obj.name = '$table'"; + } + + /** + * {@inheritDoc} + */ + public function getListTableForeignKeysSQL($table, $database = null) + { + return "SELECT f.name AS ForeignKey, + SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName, + OBJECT_NAME (f.parent_object_id) AS TableName, + COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName, + SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName, + OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName, + COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName, + f.delete_referential_action_desc, + f.update_referential_action_desc + FROM sys.foreign_keys AS f + INNER JOIN sys.foreign_key_columns AS fc + INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id + ON f.OBJECT_ID = fc.constraint_object_id + WHERE OBJECT_NAME (f.parent_object_id) = '" . $table . "'"; + } + + /** + * {@inheritDoc} + */ + public function getListTableIndexesSQL($table, $currentDatabase = null) + { + return "SELECT idx.name AS key_name, + col.name AS column_name, + ~idx.is_unique AS non_unique, + idx.is_primary_key AS [primary], + CASE idx.type + WHEN '1' THEN 'clustered' + WHEN '2' THEN 'nonclustered' + ELSE NULL + END AS flags + FROM sys.tables AS tbl + JOIN sys.indexes AS idx ON tbl.object_id = idx.object_id + JOIN sys.index_columns AS idxcol ON idx.object_id = idxcol.object_id AND idx.index_id = idxcol.index_id + JOIN sys.columns AS col ON idxcol.object_id = col.object_id AND idxcol.column_id = col.column_id + WHERE tbl.name = '$table' + ORDER BY idx.index_id ASC, idxcol.index_column_id ASC"; + } + + /** + * {@inheritDoc} + */ + public function getCreateViewSQL($name, $sql) + { + return 'CREATE VIEW ' . $name . ' AS ' . $sql; + } + + /** + * {@inheritDoc} + */ + public function getListViewsSQL($database) + { + return "SELECT name FROM sysobjects WHERE type = 'V' ORDER BY name"; + } + + /** + * {@inheritDoc} + */ + public function getDropViewSQL($name) + { + return 'DROP VIEW ' . $name; + } + + /** + * {@inheritDoc} + */ + public function getGuidExpression() + { + return 'NEWID()'; + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos == false) { + return 'CHARINDEX(' . $substr . ', ' . $str . ')'; + } + + return 'CHARINDEX(' . $substr . ', ' . $str . ', ' . $startPos . ')'; + } + + /** + * {@inheritDoc} + */ + public function getModExpression($expression1, $expression2) + { + return $expression1 . ' % ' . $expression2; + } + + /** + * {@inheritDoc} + */ + public function getTrimExpression($str, $pos = self::TRIM_UNSPECIFIED, $char = false) + { + if ( ! $char) { + switch ($pos) { + case self::TRIM_LEADING: + $trimFn = 'LTRIM'; + break; + + case self::TRIM_TRAILING: + $trimFn = 'RTRIM'; + break; + + default: + return 'LTRIM(RTRIM(' . $str . '))'; + } + + return $trimFn . '(' . $str . ')'; + } + + /** Original query used to get those expressions + declare @c varchar(100) = 'xxxBarxxx', @trim_char char(1) = 'x'; + declare @pat varchar(10) = '%[^' + @trim_char + ']%'; + select @c as string + , @trim_char as trim_char + , stuff(@c, 1, patindex(@pat, @c) - 1, null) as trim_leading + , reverse(stuff(reverse(@c), 1, patindex(@pat, reverse(@c)) - 1, null)) as trim_trailing + , reverse(stuff(reverse(stuff(@c, 1, patindex(@pat, @c) - 1, null)), 1, patindex(@pat, reverse(stuff(@c, 1, patindex(@pat, @c) - 1, null))) - 1, null)) as trim_both; + */ + $pattern = "'%[^' + $char + ']%'"; + + if ($pos == self::TRIM_LEADING) { + return 'stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)'; + } + + if ($pos == self::TRIM_TRAILING) { + return 'reverse(stuff(reverse(' . $str . '), 1, patindex(' . $pattern . ', reverse(' . $str . ')) - 1, null))'; + } + + return 'reverse(stuff(reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null)), 1, patindex(' . $pattern . ', reverse(stuff(' . $str . ', 1, patindex(' . $pattern . ', ' . $str . ') - 1, null))) - 1, null))'; + } + + /** + * {@inheritDoc} + */ + public function getConcatExpression() + { + $args = func_get_args(); + + return '(' . implode(' + ', $args) . ')'; + } + + /** + * {@inheritDoc} + */ + public function getListDatabasesSQL() + { + return 'SELECT * FROM SYS.DATABASES'; + } + + /** + * {@inheritDoc} + */ + public function getSubstringExpression($value, $from, $length = null) + { + if (!is_null($length)) { + return 'SUBSTRING(' . $value . ', ' . $from . ', ' . $length . ')'; + } + + return 'SUBSTRING(' . $value . ', ' . $from . ', LEN(' . $value . ') - ' . $from . ' + 1)'; + } + + /** + * {@inheritDoc} + */ + public function getLengthExpression($column) + { + return 'LEN(' . $column . ')'; + } + + /** + * {@inheritDoc} + */ + public function getSetTransactionIsolationSQL($level) + { + return 'SET TRANSACTION ISOLATION LEVEL ' . $this->_getTransactionIsolationLevelSQL($level); + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $field) + { + return 'INT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $field) + { + return 'BIGINT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $field) + { + return 'SMALLINT' . $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getGuidTypeDeclarationSQL(array $field) + { + return 'UNIQUEIDENTIFIER'; + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed ? ($length ? 'NCHAR(' . $length . ')' : 'CHAR(255)') : ($length ? 'NVARCHAR(' . $length . ')' : 'NVARCHAR(255)'); + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $field) + { + return 'TEXT'; + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) + { + return (!empty($columnDef['autoincrement'])) ? ' IDENTITY' : ''; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATETIME'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATETIME'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATETIME'; + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $field) + { + return 'BIT'; + } + + /** + * {@inheritDoc} + */ + protected function doModifyLimitQuery($query, $limit, $offset = null) + { + if ($limit === null) { + return $query; + } + + $start = $offset + 1; + $end = $offset + $limit; + $orderBy = stristr($query, 'ORDER BY'); + $query = preg_replace('/\s+ORDER\s+BY\s+([^\)]*)/', '', $query); //Remove ORDER BY from $query + $format = 'SELECT * FROM (%s) AS doctrine_tbl WHERE doctrine_rownum BETWEEN %d AND %d'; + + if ( ! $orderBy) { + //Replace only first occurrence of FROM with OVER to prevent changing FROM also in subqueries. + $query = preg_replace('/\sFROM\s/i', ', ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS doctrine_rownum FROM ', $query, 1); + + return sprintf($format, $query, $start, $end); + } + + //Clear ORDER BY + $orderBy = preg_replace('/ORDER\s+BY\s+([^\)]*)(.*)/', '$1', $orderBy); + $orderByParts = explode(',', $orderBy); + $orderbyColumns = array(); + + //Split ORDER BY into parts + foreach ($orderByParts as &$part) { + + if (preg_match('/(([^\s]*)\.)?([^\.\s]*)\s*(ASC|DESC)?/i', trim($part), $matches)) { + $orderbyColumns[] = array( + 'column' => $matches[3], + 'hasTable' => ( ! empty($matches[2])), + 'sort' => isset($matches[4]) ? $matches[4] : null, + 'table' => empty($matches[2]) ? '[^\.\s]*' : $matches[2] + ); + } + } + + //Find alias for each colum used in ORDER BY + if ( ! empty($orderbyColumns)) { + foreach ($orderbyColumns as $column) { + + $pattern = sprintf('/%s\.(%s)\s*(AS)?\s*([^,\s\)]*)/i', $column['table'], $column['column']); + $overColumn = preg_match($pattern, $query, $matches) + ? ($column['hasTable'] ? $column['table'] . '.' : '') . $column['column'] + : $column['column']; + + if (isset($column['sort'])) { + $overColumn .= ' ' . $column['sort']; + } + + $overColumns[] = $overColumn; + } + } + + //Replace only first occurrence of FROM with $over to prevent changing FROM also in subqueries. + $over = 'ORDER BY ' . implode(', ', $overColumns); + $query = preg_replace('/\sFROM\s/i', ", ROW_NUMBER() OVER ($over) AS doctrine_rownum FROM ", $query, 1); + + return sprintf($format, $query, $start, $end); + } + + /** + * {@inheritDoc} + */ + public function supportsLimitOffset() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function convertBooleans($item) + { + if (is_array($item)) { + foreach ($item as $key => $value) { + if (is_bool($value) || is_numeric($item)) { + $item[$key] = ($value) ? 1 : 0; + } + } + } else if (is_bool($item) || is_numeric($item)) { + $item = ($item) ? 1 : 0; + } + + return $item; + } + + /** + * {@inheritDoc} + */ + public function getCreateTemporaryTableSnippetSQL() + { + return "CREATE TABLE"; + } + + /** + * {@inheritDoc} + */ + public function getTemporaryTableName($tableName) + { + return '#' . $tableName; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeFormatString() + { + return 'Y-m-d H:i:s.000'; + } + + /** + * {@inheritDoc} + */ + public function getDateFormatString() + { + return 'Y-m-d H:i:s.000'; + } + + /** + * {@inheritDoc} + */ + public function getTimeFormatString() + { + return 'Y-m-d H:i:s.000'; + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTzFormatString() + { + return $this->getDateTimeFormatString(); + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'mssql'; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = array( + 'bigint' => 'bigint', + 'numeric' => 'decimal', + 'bit' => 'boolean', + 'smallint' => 'smallint', + 'decimal' => 'decimal', + 'smallmoney' => 'integer', + 'int' => 'integer', + 'tinyint' => 'smallint', + 'money' => 'integer', + 'float' => 'float', + 'real' => 'float', + 'double' => 'float', + 'double precision' => 'float', + 'smalldatetime' => 'datetime', + 'datetime' => 'datetime', + 'char' => 'string', + 'varchar' => 'string', + 'text' => 'text', + 'nchar' => 'string', + 'nvarchar' => 'string', + 'ntext' => 'text', + 'binary' => 'text', + 'varbinary' => 'blob', + 'image' => 'text', + 'uniqueidentifier' => 'guid', + ); + } + + /** + * {@inheritDoc} + */ + public function createSavePoint($savepoint) + { + return 'SAVE TRANSACTION ' . $savepoint; + } + + /** + * {@inheritDoc} + */ + public function releaseSavePoint($savepoint) + { + return ''; + } + + /** + * {@inheritDoc} + */ + public function rollbackSavePoint($savepoint) + { + return 'ROLLBACK TRANSACTION ' . $savepoint; + } + + /** + * {@inheritDoc} + */ + public function appendLockHint($fromClause, $lockMode) + { + switch ($lockMode) { + case LockMode::NONE: + $lockClause = ' WITH (NOLOCK)'; + break; + case LockMode::PESSIMISTIC_READ: + $lockClause = ' WITH (HOLDLOCK, ROWLOCK)'; + break; + case LockMode::PESSIMISTIC_WRITE: + $lockClause = ' WITH (UPDLOCK, ROWLOCK)'; + break; + default: + $lockClause = ''; + } + + return $fromClause . $lockClause; + } + + /** + * {@inheritDoc} + */ + public function getForUpdateSQL() + { + return ' '; + } + + /** + * {@inheritDoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\SQLServerKeywords'; + } + + /** + * {@inheritDoc} + */ + public function quoteSingleIdentifier($str) + { + return "[" . str_replace("]", "][", $str) . "]"; + } + + /** + * {@inheritDoc} + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + return 'TRUNCATE TABLE '.$tableName; + } + + /** + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $field) + { + return 'VARBINARY(MAX)'; + } + + /** + * {@inheritDoc} + */ + public function getDefaultValueDeclarationSQL($field) + { + if ( ! isset($field['default'])) { + return empty($field['notnull']) ? ' NULL' : ''; + } + + if ( ! isset($field['type'])) { + return " DEFAULT '" . $field['default'] . "'"; + } + + if (in_array((string) $field['type'], array('Integer', 'BigInteger', 'SmallInteger'))) { + return " DEFAULT " . $field['default']; + } + + if ((string) $field['type'] == 'DateTime' && $field['default'] == $this->getCurrentTimestampSQL()) { + return " DEFAULT " . $this->getCurrentTimestampSQL(); + } + + if ((string) $field['type'] == 'Boolean') { + return " DEFAULT '" . $this->convertBooleans($field['default']) . "'"; + } + + return " DEFAULT '" . $field['default'] . "'"; + } + + /** + * {@inheritdoc} + */ + public function getColumnCollationDeclarationSQL($collation) + { + return 'COLLATE ' . $collation; + } + + /** + * {@inheritdoc} + * + * Modifies column declaration order as it differs in Microsoft SQL Server. + */ + public function getColumnDeclarationSQL($name, array $field) + { + if (isset($field['columnDefinition'])) { + $columnDef = $this->getCustomTypeDeclarationSQL($field); + } else { + $collation = (isset($field['collate']) && $field['collate']) ? + ' ' . $this->getColumnCollationDeclarationSQL($field['collate']) : ''; + + $notnull = (isset($field['notnull']) && $field['notnull']) ? ' NOT NULL' : ''; + + $unique = (isset($field['unique']) && $field['unique']) ? + ' ' . $this->getUniqueFieldDeclarationSQL() : ''; + + $check = (isset($field['check']) && $field['check']) ? + ' ' . $field['check'] : ''; + + $typeDecl = $field['type']->getSqlDeclaration($field, $this); + $columnDef = $typeDecl . $collation . $notnull . $unique . $check; + } + + return $name . ' ' . $columnDef; + } + + /** + * Returns a unique default constraint name for a table and column. + * + * @param string $table Name of the table to generate the unique default constraint name for. + * @param string $column Name of the column in the table to generate the unique default constraint name for. + * + * @return string + */ + private function generateDefaultConstraintName($table, $column) + { + return 'DF_' . $this->generateIdentifierName($table) . '_' . $this->generateIdentifierName($column); + } + + /** + * Returns a hash value for a given identifier. + * + * @param string $identifier Identifier to generate a hash value for. + * + * @return string + */ + private function generateIdentifierName($identifier) + { + return strtoupper(dechex(crc32($identifier))); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php new file mode 100644 index 0000000..a74528f --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Platforms/SqlitePlatform.php @@ -0,0 +1,1027 @@ +. + */ + +namespace Doctrine\DBAL\Platforms; + +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Schema\TableDiff; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Index; +use Doctrine\DBAL\Schema\Constraint; + +/** + * The SqlitePlatform class describes the specifics and dialects of the SQLite + * database platform. + * + * @since 2.0 + * @author Roman Borschel + * @author Benjamin Eberlei + * @author Martin Hasoň + * @todo Rename: SQLitePlatform + */ +class SqlitePlatform extends AbstractPlatform +{ + /** + * {@inheritDoc} + */ + public function getRegexpExpression() + { + return 'RLIKE'; + } + + /** + * {@inheritDoc} + */ + public function getGuidExpression() + { + return "HEX(RANDOMBLOB(4)) || '-' || HEX(RANDOMBLOB(2)) || '-4' || " + . "SUBSTR(HEX(RANDOMBLOB(2)), 2) || '-' || " + . "SUBSTR('89AB', 1 + (ABS(RANDOM()) % 4), 1) || " + . "SUBSTR(HEX(RANDOMBLOB(2)), 2) || '-' || HEX(RANDOMBLOB(6))"; + } + + /** + * {@inheritDoc} + */ + public function getNowExpression($type = 'timestamp') + { + switch ($type) { + case 'time': + return 'time(\'now\')'; + case 'date': + return 'date(\'now\')'; + case 'timestamp': + default: + return 'datetime(\'now\')'; + } + } + + /** + * {@inheritDoc} + */ + public function getTrimExpression($str, $pos = self::TRIM_UNSPECIFIED, $char = false) + { + $trimChar = ($char != false) ? (', ' . $char) : ''; + + switch ($pos) { + case self::TRIM_LEADING: + $trimFn = 'LTRIM'; + break; + + case self::TRIM_TRAILING: + $trimFn = 'RTRIM'; + break; + + default: + $trimFn = 'TRIM'; + } + + return $trimFn . '(' . $str . $trimChar . ')'; + } + + /** + * {@inheritDoc} + * + * SQLite only supports the 2 parameter variant of this function + */ + public function getSubstringExpression($value, $position, $length = null) + { + if ($length !== null) { + return 'SUBSTR(' . $value . ', ' . $position . ', ' . $length . ')'; + } + + return 'SUBSTR(' . $value . ', ' . $position . ', LENGTH(' . $value . '))'; + } + + /** + * {@inheritDoc} + */ + public function getLocateExpression($str, $substr, $startPos = false) + { + if ($startPos == false) { + return 'LOCATE('.$str.', '.$substr.')'; + } + + return 'LOCATE('.$str.', '.$substr.', '.$startPos.')'; + } + + /** + * {@inheritDoc} + */ + public function getDateDiffExpression($date1, $date2) + { + return 'ROUND(JULIANDAY('.$date1 . ')-JULIANDAY('.$date2.'))'; + } + + /** + * {@inheritDoc} + */ + public function getDateAddHourExpression($date, $hours) + { + return "DATETIME(" . $date . ",'+". $hours . " hour')"; + } + + /** + * {@inheritDoc} + */ + public function getDateSubHourExpression($date, $hours) + { + return "DATETIME(" . $date . ",'-". $hours . " hour')"; + } + + /** + * {@inheritDoc} + */ + public function getDateAddDaysExpression($date, $days) + { + return "DATE(" . $date . ",'+". $days . " day')"; + } + + /** + * {@inheritDoc} + */ + public function getDateSubDaysExpression($date, $days) + { + return "DATE(" . $date . ",'-". $days . " day')"; + } + + /** + * {@inheritDoc} + */ + public function getDateAddMonthExpression($date, $months) + { + return "DATE(" . $date . ",'+". $months . " month')"; + } + + /** + * {@inheritDoc} + */ + public function getDateSubMonthExpression($date, $months) + { + return "DATE(" . $date . ",'-". $months . " month')"; + } + + /** + * {@inheritDoc} + */ + protected function _getTransactionIsolationLevelSQL($level) + { + switch ($level) { + case \Doctrine\DBAL\Connection::TRANSACTION_READ_UNCOMMITTED: + return 0; + case \Doctrine\DBAL\Connection::TRANSACTION_READ_COMMITTED: + case \Doctrine\DBAL\Connection::TRANSACTION_REPEATABLE_READ: + case \Doctrine\DBAL\Connection::TRANSACTION_SERIALIZABLE: + return 1; + default: + return parent::_getTransactionIsolationLevelSQL($level); + } + } + + /** + * {@inheritDoc} + */ + public function getSetTransactionIsolationSQL($level) + { + return 'PRAGMA read_uncommitted = ' . $this->_getTransactionIsolationLevelSQL($level); + } + + /** + * {@inheritDoc} + */ + public function prefersIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getBooleanTypeDeclarationSQL(array $field) + { + return 'BOOLEAN'; + } + + /** + * {@inheritDoc} + */ + public function getIntegerTypeDeclarationSQL(array $field) + { + return $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getBigIntTypeDeclarationSQL(array $field) + { + return $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getTinyIntTypeDeclarationSql(array $field) + { + return $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getSmallIntTypeDeclarationSQL(array $field) + { + return $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getMediumIntTypeDeclarationSql(array $field) + { + return $this->_getCommonIntegerTypeDeclarationSQL($field); + } + + /** + * {@inheritDoc} + */ + public function getDateTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATETIME'; + } + + /** + * {@inheritDoc} + */ + public function getDateTypeDeclarationSQL(array $fieldDeclaration) + { + return 'DATE'; + } + + /** + * {@inheritDoc} + */ + public function getTimeTypeDeclarationSQL(array $fieldDeclaration) + { + return 'TIME'; + } + + /** + * {@inheritDoc} + */ + protected function _getCommonIntegerTypeDeclarationSQL(array $columnDef) + { + return 'INTEGER'; + } + + /** + * {@inheritDoc} + */ + public function getForeignKeyDeclarationSQL(ForeignKeyConstraint $foreignKey) + { + return parent::getForeignKeyDeclarationSQL(new ForeignKeyConstraint( + $foreignKey->getQuotedLocalColumns($this), + str_replace('.', '__', $foreignKey->getQuotedForeignTableName($this)), + $foreignKey->getQuotedForeignColumns($this), + $foreignKey->getName(), + $foreignKey->getOptions() + )); + } + + /** + * {@inheritDoc} + */ + protected function _getCreateTableSQL($name, array $columns, array $options = array()) + { + $name = str_replace('.', '__', $name); + $queryFields = $this->getColumnDeclarationListSQL($columns); + + if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) { + foreach ($options['uniqueConstraints'] as $name => $definition) { + $queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($name, $definition); + } + } + + if (isset($options['primary']) && ! empty($options['primary'])) { + $keyColumns = array_unique(array_values($options['primary'])); + $queryFields.= ', PRIMARY KEY('.implode(', ', $keyColumns).')'; + } + + if (isset($options['foreignKeys'])) { + foreach ($options['foreignKeys'] as $foreignKey) { + $queryFields.= ', '.$this->getForeignKeyDeclarationSQL($foreignKey); + } + } + + $query[] = 'CREATE TABLE ' . $name . ' (' . $queryFields . ')'; + + if (isset($options['alter']) && true === $options['alter']) { + return $query; + } + + if (isset($options['indexes']) && ! empty($options['indexes'])) { + foreach ($options['indexes'] as $indexDef) { + $query[] = $this->getCreateIndexSQL($indexDef, $name); + } + } + + if (isset($options['unique']) && ! empty($options['unique'])) { + foreach ($options['unique'] as $indexDef) { + $query[] = $this->getCreateIndexSQL($indexDef, $name); + } + } + + return $query; + } + + /** + * {@inheritDoc} + */ + protected function getVarcharTypeDeclarationSQLSnippet($length, $fixed) + { + return $fixed ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(255)') + : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT'); + } + + /** + * {@inheritDoc} + */ + public function getClobTypeDeclarationSQL(array $field) + { + return 'CLOB'; + } + + /** + * {@inheritDoc} + */ + public function getListTableConstraintsSQL($table) + { + $table = str_replace('.', '__', $table); + + return "SELECT sql FROM sqlite_master WHERE type='index' AND tbl_name = '$table' AND sql NOT NULL ORDER BY name"; + } + + /** + * {@inheritDoc} + */ + public function getListTableColumnsSQL($table, $currentDatabase = null) + { + $table = str_replace('.', '__', $table); + + return "PRAGMA table_info('$table')"; + } + + /** + * {@inheritDoc} + */ + public function getListTableIndexesSQL($table, $currentDatabase = null) + { + $table = str_replace('.', '__', $table); + + return "PRAGMA index_list('$table')"; + } + + /** + * {@inheritDoc} + */ + public function getListTablesSQL() + { + return "SELECT name FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence' AND name != 'geometry_columns' AND name != 'spatial_ref_sys' " + . "UNION ALL SELECT name FROM sqlite_temp_master " + . "WHERE type = 'table' ORDER BY name"; + } + + /** + * {@inheritDoc} + */ + public function getListViewsSQL($database) + { + return "SELECT name, sql FROM sqlite_master WHERE type='view' AND sql NOT NULL"; + } + + /** + * {@inheritDoc} + */ + public function getCreateViewSQL($name, $sql) + { + return 'CREATE VIEW ' . $name . ' AS ' . $sql; + } + + /** + * {@inheritDoc} + */ + public function getDropViewSQL($name) + { + return 'DROP VIEW '. $name; + } + + /** + * {@inheritDoc} + */ + public function getAdvancedForeignKeyOptionsSQL(ForeignKeyConstraint $foreignKey) + { + $query = parent::getAdvancedForeignKeyOptionsSQL($foreignKey); + + $query .= (($foreignKey->hasOption('deferrable') && $foreignKey->getOption('deferrable') !== false) ? ' ' : ' NOT ') . 'DEFERRABLE'; + $query .= ' INITIALLY ' . (($foreignKey->hasOption('deferred') && $foreignKey->getOption('deferred') !== false) ? 'DEFERRED' : 'IMMEDIATE'); + + return $query; + } + + /** + * {@inheritDoc} + */ + public function supportsIdentityColumns() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'sqlite'; + } + + /** + * {@inheritDoc} + */ + public function getTruncateTableSQL($tableName, $cascade = false) + { + $tableName = str_replace('.', '__', $tableName); + + return 'DELETE FROM ' . $tableName; + } + + /** + * User-defined function for Sqlite that is used with PDO::sqliteCreateFunction(). + * + * @param integer|float $value + * + * @return float + */ + static public function udfSqrt($value) + { + return sqrt($value); + } + + /** + * User-defined function for Sqlite that implements MOD(a, b). + * + * @param integer $a + * @param integer $b + * + * @return integer + */ + static public function udfMod($a, $b) + { + return ($a % $b); + } + + /** + * @param string $str + * @param string $substr + * @param integer $offset + * + * @return integer + */ + static public function udfLocate($str, $substr, $offset = 0) + { + $pos = strpos($str, $substr, $offset); + if ($pos !== false) { + return $pos+1; + } + + return 0; + } + + /** + * {@inheritDoc} + */ + public function getForUpdateSql() + { + return ''; + } + + /** + * {@inheritDoc} + */ + protected function initializeDoctrineTypeMappings() + { + $this->doctrineTypeMapping = array( + 'boolean' => 'boolean', + 'tinyint' => 'boolean', + 'smallint' => 'smallint', + 'mediumint' => 'integer', + 'int' => 'integer', + 'integer' => 'integer', + 'serial' => 'integer', + 'bigint' => 'bigint', + 'bigserial' => 'bigint', + 'clob' => 'text', + 'tinytext' => 'text', + 'mediumtext' => 'text', + 'longtext' => 'text', + 'text' => 'text', + 'varchar' => 'string', + 'longvarchar' => 'string', + 'varchar2' => 'string', + 'nvarchar' => 'string', + 'image' => 'string', + 'ntext' => 'string', + 'char' => 'string', + 'date' => 'date', + 'datetime' => 'datetime', + 'timestamp' => 'datetime', + 'time' => 'time', + 'float' => 'float', + 'double' => 'float', + 'double precision' => 'float', + 'real' => 'float', + 'decimal' => 'decimal', + 'numeric' => 'decimal', + 'blob' => 'blob', + 'integer unsigned' => 'integer', + ); + } + + /** + * {@inheritDoc} + */ + protected function getReservedKeywordsClass() + { + return 'Doctrine\DBAL\Platforms\Keywords\SQLiteKeywords'; + } + + /** + * {@inheritDoc} + */ + protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) + { + if ( ! $diff->fromTable instanceof Table) { + throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema'); + } + + $sql = array(); + foreach ($diff->fromTable->getIndexes() as $index) { + if ( ! $index->isPrimary()) { + $sql[] = $this->getDropIndexSQL($index, $diff->name); + } + } + + return $sql; + } + + /** + * {@inheritDoc} + */ + protected function getPostAlterTableIndexForeignKeySQL(TableDiff $diff) + { + if ( ! $diff->fromTable instanceof Table) { + throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema'); + } + + $sql = array(); + $tableName = $diff->newName ?: $diff->name; + foreach ($this->getIndexesInAlteredTable($diff) as $index) { + if ($index->isPrimary()) { + continue; + } + + $sql[] = $this->getCreateIndexSQL($index, $tableName); + } + + return $sql; + } + + /** + * {@inheritDoc} + */ + public function getBlobTypeDeclarationSQL(array $field) + { + return 'BLOB'; + } + + /** + * {@inheritDoc} + */ + public function getTemporaryTableName($tableName) + { + $tableName = str_replace('.', '__', $tableName); + + return $tableName; + } + + /** + * {@inheritDoc} + * + * Sqlite Platform emulates schema by underscoring each dot and generating tables + * into the default database. + * + * This hack is implemented to be able to use SQLite as testdriver when + * using schema supporting databases. + */ + public function canEmulateSchemas() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function supportsForeignKeyConstraints() + { + return false; + } + + /** + * {@inheritDoc} + */ + public function getCreatePrimaryKeySQL(Index $index, $table) + { + throw new DBALException('Sqlite platform does not support alter primary key.'); + } + + /** + * {@inheritdoc} + */ + public function getCreateForeignKeySQL(ForeignKeyConstraint $foreignKey, $table) + { + throw new DBALException('Sqlite platform does not support alter foreign key.'); + } + + /** + * {@inheritdoc} + */ + public function getDropForeignKeySQL($foreignKey, $table) + { + throw new DBALException('Sqlite platform does not support alter foreign key.'); + } + + /** + * {@inheritDoc} + */ + public function getCreateConstraintSQL(Constraint $constraint, $table) + { + throw new DBALException('Sqlite platform does not support alter constraint.'); + } + + /** + * {@inheritDoc} + */ + public function getCreateTableSQL(Table $table, $createFlags = null) + { + $createFlags = null === $createFlags ? self::CREATE_INDEXES | self::CREATE_FOREIGNKEYS : $createFlags; + + return parent::getCreateTableSQL($table, $createFlags); + } + + /** + * {@inheritDoc} + */ + public function getListTableForeignKeysSQL($table, $database = null) + { + $table = str_replace('.', '__', $table); + + return "PRAGMA foreign_key_list('$table')"; + } + + /** + * {@inheritDoc} + */ + public function getAlterTableSQL(TableDiff $diff) + { + $sql = $this->getSimpleAlterTableSQL($diff); + if (false !== $sql) { + return $sql; + } + + $fromTable = $diff->fromTable; + if ( ! $fromTable instanceof Table) { + throw new DBALException('Sqlite platform requires for alter table the table diff with reference to original table schema'); + } + + $table = clone $fromTable; + + $columns = array(); + $oldColumnNames = array(); + $newColumnNames = array(); + $columnSql = array(); + + foreach ($table->getColumns() as $columnName => $column) { + $columnName = strtolower($columnName); + $columns[$columnName] = $column; + $oldColumnNames[$columnName] = $newColumnNames[$columnName] = $column->getQuotedName($this); + } + + foreach ($diff->removedColumns as $columnName => $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + $columnName = strtolower($columnName); + if (isset($columns[$columnName])) { + unset($columns[$columnName]); + unset($oldColumnNames[$columnName]); + unset($newColumnNames[$columnName]); + } + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $oldColumnName = strtolower($oldColumnName); + if (isset($columns[$oldColumnName])) { + unset($columns[$oldColumnName]); + } + + $columns[strtolower($column->getName())] = $column; + + if (isset($newColumnNames[$oldColumnName])) { + $newColumnNames[$oldColumnName] = $column->getQuotedName($this); + } + } + + foreach ($diff->changedColumns as $oldColumnName => $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + if (isset($columns[$oldColumnName])) { + unset($columns[$oldColumnName]); + } + + $columns[strtolower($columnDiff->column->getName())] = $columnDiff->column; + + if (isset($newColumnNames[$oldColumnName])) { + $newColumnNames[$oldColumnName] = $columnDiff->column->getQuotedName($this); + } + } + + foreach ($diff->addedColumns as $columnName => $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $columns[strtolower($columnName)] = $column; + } + + $sql = array(); + $tableSql = array(); + if ( ! $this->onSchemaAlterTable($diff, $tableSql)) { + $dataTable = new Table('__temp__'.$table->getName()); + + $newTable = new Table($table->getName(), $columns, $this->getPrimaryIndexInAlteredTable($diff), $this->getForeignKeysInAlteredTable($diff), 0, $table->getOptions()); + $newTable->addOption('alter', true); + + $sql = $this->getPreAlterTableIndexForeignKeySQL($diff); + //$sql = array_merge($sql, $this->getCreateTableSQL($dataTable, 0)); + $sql[] = sprintf('CREATE TEMPORARY TABLE %s AS SELECT %s FROM %s', $dataTable->getQuotedName($this), implode(', ', $oldColumnNames), $table->getQuotedName($this)); + $sql[] = $this->getDropTableSQL($fromTable); + + $sql = array_merge($sql, $this->getCreateTableSQL($newTable)); + $sql[] = sprintf('INSERT INTO %s (%s) SELECT %s FROM %s', $newTable->getQuotedName($this), implode(', ', $newColumnNames), implode(', ', $oldColumnNames), $dataTable->getQuotedName($this)); + $sql[] = $this->getDropTableSQL($dataTable); + + if ($diff->newName && $diff->newName != $diff->name) { + $renamedTable = new Table($diff->newName); + $sql[] = 'ALTER TABLE '.$newTable->getQuotedName($this).' RENAME TO '.$renamedTable->getQuotedName($this); + } + + $sql = array_merge($sql, $this->getPostAlterTableIndexForeignKeySQL($diff)); + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * + * @return array|bool + */ + private function getSimpleAlterTableSQL(TableDiff $diff) + { + if ( ! empty($diff->renamedColumns) || ! empty($diff->addedForeignKeys) || ! empty($diff->addedIndexes) + || ! empty($diff->changedColumns) || ! empty($diff->changedForeignKeys) || ! empty($diff->changedIndexes) + || ! empty($diff->removedColumns) || ! empty($diff->removedForeignKeys) || ! empty($diff->removedIndexes) + ) { + return false; + } + + $table = new Table($diff->name); + + $sql = array(); + $tableSql = array(); + $columnSql = array(); + + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $field = array_merge(array('unique' => null, 'autoincrement' => null, 'default' => null), $column->toArray()); + $type = (string) $field['type']; + switch (true) { + case isset($field['columnDefinition']) || $field['autoincrement'] || $field['unique']: + case $type == 'DateTime' && $field['default'] == $this->getCurrentTimestampSQL(): + case $type == 'Date' && $field['default'] == $this->getCurrentDateSQL(): + case $type == 'Time' && $field['default'] == $this->getCurrentTimeSQL(): + return false; + } + + $field['name'] = $column->getQuotedName($this); + if (strtolower($field['type']) == 'string' && $field['length'] === null) { + $field['length'] = 255; + } + + $sql[] = 'ALTER TABLE '.$table->getQuotedName($this).' ADD COLUMN '.$this->getColumnDeclarationSQL($field['name'], $field); + } + + if ( ! $this->onSchemaAlterTable($diff, $tableSql)) { + if ($diff->newName !== false) { + $newTable = new Table($diff->newName); + $sql[] = 'ALTER TABLE '.$table->getQuotedName($this).' RENAME TO '.$newTable->getQuotedName($this); + } + } + + return array_merge($sql, $tableSql, $columnSql); + } + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * + * @return array + */ + private function getColumnNamesInAlteredTable(TableDiff $diff) + { + $columns = array(); + + foreach ($diff->fromTable->getColumns() as $columnName => $column) { + $columns[strtolower($columnName)] = $column->getName(); + } + + foreach ($diff->removedColumns as $columnName => $column) { + $columnName = strtolower($columnName); + if (isset($columns[$columnName])) { + unset($columns[$columnName]); + } + } + + foreach ($diff->renamedColumns as $oldColumnName => $column) { + $columnName = $column->getName(); + $columns[strtolower($oldColumnName)] = $columnName; + $columns[strtolower($columnName)] = $columnName; + } + + foreach ($diff->changedColumns as $oldColumnName => $columnDiff) { + $columnName = $columnDiff->column->getName(); + $columns[strtolower($oldColumnName)] = $columnName; + $columns[strtolower($columnName)] = $columnName; + } + + foreach ($diff->addedColumns as $columnName => $column) { + $columns[strtolower($columnName)] = $columnName; + } + + return $columns; + } + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * + * @return \Doctrine\DBAL\Schema\Index[] + */ + private function getIndexesInAlteredTable(TableDiff $diff) + { + $indexes = $diff->fromTable->getIndexes(); + $columnNames = $this->getColumnNamesInAlteredTable($diff); + + foreach ($indexes as $key => $index) { + $changed = false; + $indexColumns = array(); + foreach ($index->getColumns() as $columnName) { + $normalizedColumnName = strtolower($columnName); + if ( ! isset($columnNames[$normalizedColumnName])) { + unset($indexes[$key]); + continue 2; + } else { + $indexColumns[] = $columnNames[$normalizedColumnName]; + if ($columnName !== $columnNames[$normalizedColumnName]) { + $changed = true; + } + } + } + + if ($changed) { + $indexes[$key] = new Index($index->getName(), $indexColumns, $index->isUnique(), $index->isPrimary(), $index->getFlags()); + } + } + + foreach ($diff->removedIndexes as $index) { + $indexName = strtolower($index->getName()); + if (strlen($indexName) && isset($indexes[$indexName])) { + unset($indexes[$indexName]); + } + } + + foreach (array_merge($diff->changedIndexes, $diff->addedIndexes) as $index) { + $indexName = strtolower($index->getName()); + if (strlen($indexName)) { + $indexes[$indexName] = $index; + } else { + $indexes[] = $index; + } + } + + return $indexes; + } + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * + * @return array + */ + private function getForeignKeysInAlteredTable(TableDiff $diff) + { + $foreignKeys = $diff->fromTable->getForeignKeys(); + $columnNames = $this->getColumnNamesInAlteredTable($diff); + + foreach ($foreignKeys as $key => $constraint) { + $changed = false; + $localColumns = array(); + foreach ($constraint->getLocalColumns() as $columnName) { + $normalizedColumnName = strtolower($columnName); + if ( ! isset($columnNames[$normalizedColumnName])) { + unset($foreignKeys[$key]); + continue 2; + } else { + $localColumns[] = $columnNames[$normalizedColumnName]; + if ($columnName !== $columnNames[$normalizedColumnName]) { + $changed = true; + } + } + } + + if ($changed) { + $foreignKeys[$key] = new ForeignKeyConstraint($localColumns, $constraint->getForeignTableName(), $constraint->getForeignColumns(), $constraint->getName(), $constraint->getOptions()); + } + } + + foreach ($diff->removedForeignKeys as $constraint) { + $constraintName = strtolower($constraint->getName()); + if (strlen($constraintName) && isset($foreignKeys[$constraintName])) { + unset($foreignKeys[$constraintName]); + } + } + + foreach (array_merge($diff->changedForeignKeys, $diff->addedForeignKeys) as $constraint) { + $constraintName = strtolower($constraint->getName()); + if (strlen($constraintName)) { + $foreignKeys[$constraintName] = $constraint; + } else { + $foreignKeys[] = $constraint; + } + } + + return $foreignKeys; + } + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $diff + * + * @return array + */ + private function getPrimaryIndexInAlteredTable(TableDiff $diff) + { + $primaryIndex = array(); + + foreach ($this->getIndexesInAlteredTable($diff) as $index) { + if ($index->isPrimary()) { + $primaryIndex = array($index->getName() => $index); + } + } + + return $primaryIndex; + } +} + diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Portability/Connection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Portability/Connection.php new file mode 100644 index 0000000..f6a3896 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Portability/Connection.php @@ -0,0 +1,145 @@ +. + */ + +namespace Doctrine\DBAL\Portability; + +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Cache\QueryCacheProfile; + +/** + * Portability wrapper for a Connection. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class Connection extends \Doctrine\DBAL\Connection +{ + const PORTABILITY_ALL = 255; + const PORTABILITY_NONE = 0; + const PORTABILITY_RTRIM = 1; + const PORTABILITY_EMPTY_TO_NULL = 4; + const PORTABILITY_FIX_CASE = 8; + + const PORTABILITY_ORACLE = 9; + const PORTABILITY_POSTGRESQL = 13; + const PORTABILITY_SQLITE = 13; + const PORTABILITY_OTHERVENDORS = 12; + const PORTABILITY_DRIZZLE = 13; + const PORTABILITY_SQLSRV = 13; + + /** + * @var integer + */ + private $portability = self::PORTABILITY_NONE; + + /** + * @var integer + */ + private $case; + + /** + * {@inheritdoc} + */ + public function connect() + { + $ret = parent::connect(); + if ($ret) { + $params = $this->getParams(); + if (isset($params['portability'])) { + if ($this->_platform->getName() === "oracle") { + $params['portability'] = $params['portability'] & self::PORTABILITY_ORACLE; + } else if ($this->_platform->getName() === "postgresql") { + $params['portability'] = $params['portability'] & self::PORTABILITY_POSTGRESQL; + } else if ($this->_platform->getName() === "sqlite") { + $params['portability'] = $params['portability'] & self::PORTABILITY_SQLITE; + } else if ($this->_platform->getName() === "drizzle") { + $params['portability'] = self::PORTABILITY_DRIZZLE; + } else if ($this->_platform->getName() === 'sqlsrv') { + $params['portability'] = $params['portabililty'] & self::PORTABILITY_SQLSRV; + } else { + $params['portability'] = $params['portability'] & self::PORTABILITY_OTHERVENDORS; + } + $this->portability = $params['portability']; + } + if (isset($params['fetch_case']) && $this->portability & self::PORTABILITY_FIX_CASE) { + if ($this->_conn instanceof \Doctrine\DBAL\Driver\PDOConnection) { + // make use of c-level support for case handling + $this->_conn->setAttribute(\PDO::ATTR_CASE, $params['fetch_case']); + } else { + $this->case = ($params['fetch_case'] == \PDO::CASE_LOWER) ? CASE_LOWER : CASE_UPPER; + } + } + } + + return $ret; + } + + /** + * @return integer + */ + public function getPortability() + { + return $this->portability; + } + + /** + * @return integer + */ + public function getFetchCase() + { + return $this->case; + } + + /** + * {@inheritdoc} + */ + public function executeQuery($query, array $params = array(), $types = array(), QueryCacheProfile $qcp = null) + { + $stmt = new Statement(parent::executeQuery($query, $params, $types, $qcp), $this); + $stmt->setFetchMode($this->defaultFetchMode); + + return $stmt; + } + + /** + * {@inheritdoc} + */ + public function prepare($statement) + { + $stmt = new Statement(parent::prepare($statement), $this); + $stmt->setFetchMode($this->defaultFetchMode); + + return $stmt; + } + + /** + * {@inheritdoc} + */ + public function query() + { + $this->connect(); + + $stmt = call_user_func_array(array($this->_conn, 'query'), func_get_args()); + $stmt = new Statement($stmt, $this); + $stmt->setFetchMode($this->defaultFetchMode); + + return $stmt; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Portability/Statement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Portability/Statement.php new file mode 100644 index 0000000..29bc8b6 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Portability/Statement.php @@ -0,0 +1,240 @@ +. + */ + +namespace Doctrine\DBAL\Portability; + +use PDO; + +/** + * Portability wrapper for a Statement. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class Statement implements \IteratorAggregate, \Doctrine\DBAL\Driver\Statement +{ + /** + * @var integer + */ + private $portability; + + /** + * @var \Doctrine\DBAL\Driver\Statement + */ + private $stmt; + + /** + * @var integer + */ + private $case; + + /** + * @var integer + */ + private $defaultFetchMode = PDO::FETCH_BOTH; + + /** + * Wraps Statement and applies portability measures. + * + * @param \Doctrine\DBAL\Driver\Statement $stmt + * @param \Doctrine\DBAL\Portability\Connection $conn + */ + public function __construct($stmt, Connection $conn) + { + $this->stmt = $stmt; + $this->portability = $conn->getPortability(); + $this->case = $conn->getFetchCase(); + } + + /** + * {@inheritdoc} + */ + public function bindParam($column, &$variable, $type = null,$length = null) + { + return $this->stmt->bindParam($column, $variable, $type); + } + /** + * {@inheritdoc} + */ + + public function bindValue($param, $value, $type = null) + { + return $this->stmt->bindValue($param, $value, $type); + } + + /** + * {@inheritdoc} + */ + public function closeCursor() + { + return $this->stmt->closeCursor(); + } + + /** + * {@inheritdoc} + */ + public function columnCount() + { + return $this->stmt->columnCount(); + } + + /** + * {@inheritdoc} + */ + public function errorCode() + { + return $this->stmt->errorCode(); + } + + /** + * {@inheritdoc} + */ + public function errorInfo() + { + return $this->stmt->errorInfo(); + } + + /** + * {@inheritdoc} + */ + public function execute($params = null) + { + return $this->stmt->execute($params); + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg1 = null, $arg2 = null) + { + $this->defaultFetchMode = $fetchMode; + + return $this->stmt->setFetchMode($fetchMode, $arg1, $arg2); + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + $data = $this->fetchAll(); + + return new \ArrayIterator($data); + } + + /** + * {@inheritdoc} + */ + public function fetch($fetchMode = null) + { + $fetchMode = $fetchMode ?: $this->defaultFetchMode; + + $row = $this->stmt->fetch($fetchMode); + + $row = $this->fixRow($row, + $this->portability & (Connection::PORTABILITY_EMPTY_TO_NULL|Connection::PORTABILITY_RTRIM), + !is_null($this->case) && ($fetchMode == PDO::FETCH_ASSOC || $fetchMode == PDO::FETCH_BOTH) && ($this->portability & Connection::PORTABILITY_FIX_CASE) + ); + + return $row; + } + + /** + * {@inheritdoc} + */ + public function fetchAll($fetchMode = null, $columnIndex = 0) + { + $fetchMode = $fetchMode ?: $this->defaultFetchMode; + + if ($columnIndex != 0) { + $rows = $this->stmt->fetchAll($fetchMode, $columnIndex); + } else { + $rows = $this->stmt->fetchAll($fetchMode); + } + + $iterateRow = $this->portability & (Connection::PORTABILITY_EMPTY_TO_NULL|Connection::PORTABILITY_RTRIM); + $fixCase = !is_null($this->case) && ($fetchMode == PDO::FETCH_ASSOC || $fetchMode == PDO::FETCH_BOTH) && ($this->portability & Connection::PORTABILITY_FIX_CASE); + if ( ! $iterateRow && !$fixCase) { + return $rows; + } + + foreach ($rows as $num => $row) { + $rows[$num] = $this->fixRow($row, $iterateRow, $fixCase); + } + + return $rows; + } + + /** + * @param mixed $row + * @param integer $iterateRow + * @param boolean $fixCase + * + * @return array + */ + protected function fixRow($row, $iterateRow, $fixCase) + { + if ( ! $row) { + return $row; + } + + if ($fixCase) { + $row = array_change_key_case($row, $this->case); + } + + if ($iterateRow) { + foreach ($row as $k => $v) { + if (($this->portability & Connection::PORTABILITY_EMPTY_TO_NULL) && $v === '') { + $row[$k] = null; + } else if (($this->portability & Connection::PORTABILITY_RTRIM) && is_string($v)) { + $row[$k] = rtrim($v); + } + } + } + + return $row; + } + + /** + * {@inheritdoc} + */ + public function fetchColumn($columnIndex = 0) + { + $value = $this->stmt->fetchColumn($columnIndex); + + if ($this->portability & (Connection::PORTABILITY_EMPTY_TO_NULL|Connection::PORTABILITY_RTRIM)) { + if (($this->portability & Connection::PORTABILITY_EMPTY_TO_NULL) && $value === '') { + $value = null; + } else if (($this->portability & Connection::PORTABILITY_RTRIM) && is_string($value)) { + $value = rtrim($value); + } + } + + return $value; + } + + /** + * {@inheritdoc} + */ + public function rowCount() + { + return $this->stmt->rowCount(); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/Expression/CompositeExpression.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/Expression/CompositeExpression.php new file mode 100644 index 0000000..937726f --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/Expression/CompositeExpression.php @@ -0,0 +1,134 @@ +. + */ + +namespace Doctrine\DBAL\Query\Expression; + +/** + * Composite expression is responsible to build a group of similar expression. + * + * @link www.doctrine-project.org + * @since 2.1 + * @author Guilherme Blanco + * @author Benjamin Eberlei + */ +class CompositeExpression implements \Countable +{ + /** + * Constant that represents an AND composite expression. + */ + const TYPE_AND = 'AND'; + + /** + * Constant that represents an OR composite expression. + */ + const TYPE_OR = 'OR'; + + /** + * The instance type of composite expression. + * + * @var string + */ + private $type; + + /** + * Each expression part of the composite expression. + * + * @var array + */ + private $parts = array(); + + /** + * Constructor. + * + * @param string $type Instance type of composite expression. + * @param array $parts Composition of expressions to be joined on composite expression. + */ + public function __construct($type, array $parts = array()) + { + $this->type = $type; + + $this->addMultiple($parts); + } + + /** + * Adds multiple parts to composite expression. + * + * @param array $parts + * + * @return \Doctrine\DBAL\Query\Expression\CompositeExpression + */ + public function addMultiple(array $parts = array()) + { + foreach ((array) $parts as $part) { + $this->add($part); + } + + return $this; + } + + /** + * Adds an expression to composite expression. + * + * @param mixed $part + * + * @return \Doctrine\DBAL\Query\Expression\CompositeExpression + */ + public function add($part) + { + if ( ! empty($part) || ($part instanceof self && $part->count() > 0)) { + $this->parts[] = $part; + } + + return $this; + } + + /** + * Retrieves the amount of expressions on composite expression. + * + * @return integer + */ + public function count() + { + return count($this->parts); + } + + /** + * Retrieves the string representation of this composite expression. + * + * @return string + */ + public function __toString() + { + if (count($this->parts) === 1) { + return (string) $this->parts[0]; + } + + return '(' . implode(') ' . $this->type . ' (', $this->parts) . ')'; + } + + /** + * Returns the type of this composite expression (AND/OR). + * + * @return string + */ + public function getType() + { + return $this->type; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/Expression/ExpressionBuilder.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/Expression/ExpressionBuilder.php new file mode 100644 index 0000000..5f2b3cf --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/Expression/ExpressionBuilder.php @@ -0,0 +1,313 @@ +. + */ + +namespace Doctrine\DBAL\Query\Expression; + +use Doctrine\DBAL\Connection; + +/** + * ExpressionBuilder class is responsible to dynamically create SQL query parts. + * + * @link www.doctrine-project.org + * @since 2.1 + * @author Guilherme Blanco + * @author Benjamin Eberlei + */ +class ExpressionBuilder +{ + const EQ = '='; + const NEQ = '<>'; + const LT = '<'; + const LTE = '<='; + const GT = '>'; + const GTE = '>='; + + /** + * The DBAL Connection. + * + * @var \Doctrine\DBAL\Connection + */ + private $connection; + + /** + * Initializes a new ExpressionBuilder. + * + * @param \Doctrine\DBAL\Connection $connection The DBAL Connection. + */ + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + /** + * Creates a conjunction of the given boolean expressions. + * + * Example: + * + * [php] + * // (u.type = ?) AND (u.role = ?) + * $expr->andX('u.type = ?', 'u.role = ?')); + * + * @param mixed $x Optional clause. Defaults = null, but requires + * at least one defined when converting to string. + * + * @return \Doctrine\DBAL\Query\Expression\CompositeExpression + */ + public function andX($x = null) + { + return new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args()); + } + + /** + * Creates a disjunction of the given boolean expressions. + * + * Example: + * + * [php] + * // (u.type = ?) OR (u.role = ?) + * $qb->where($qb->expr()->orX('u.type = ?', 'u.role = ?')); + * + * @param mixed $x Optional clause. Defaults = null, but requires + * at least one defined when converting to string. + * + * @return \Doctrine\DBAL\Query\Expression\CompositeExpression + */ + public function orX($x = null) + { + return new CompositeExpression(CompositeExpression::TYPE_OR, func_get_args()); + } + + /** + * Creates a comparison expression. + * + * @param mixed $x The left expression. + * @param string $operator One of the ExpressionBuilder::* constants. + * @param mixed $y The right expression. + * + * @return string + */ + public function comparison($x, $operator, $y) + { + return $x . ' ' . $operator . ' ' . $y; + } + + /** + * Creates an equality comparison expression with the given arguments. + * + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a = . Example: + * + * [php] + * // u.id = ? + * $expr->eq('u.id', '?'); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * + * @return string + */ + public function eq($x, $y) + { + return $this->comparison($x, self::EQ, $y); + } + + /** + * Creates a non equality comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a <> . Example: + * + * [php] + * // u.id <> 1 + * $q->where($q->expr()->neq('u.id', '1')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * + * @return string + */ + public function neq($x, $y) + { + return $this->comparison($x, self::NEQ, $y); + } + + /** + * Creates a lower-than comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a < . Example: + * + * [php] + * // u.id < ? + * $q->where($q->expr()->lt('u.id', '?')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * + * @return string + */ + public function lt($x, $y) + { + return $this->comparison($x, self::LT, $y); + } + + /** + * Creates a lower-than-equal comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a <= . Example: + * + * [php] + * // u.id <= ? + * $q->where($q->expr()->lte('u.id', '?')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * + * @return string + */ + public function lte($x, $y) + { + return $this->comparison($x, self::LTE, $y); + } + + /** + * Creates a greater-than comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a > . Example: + * + * [php] + * // u.id > ? + * $q->where($q->expr()->gt('u.id', '?')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * + * @return string + */ + public function gt($x, $y) + { + return $this->comparison($x, self::GT, $y); + } + + /** + * Creates a greater-than-equal comparison expression with the given arguments. + * First argument is considered the left expression and the second is the right expression. + * When converted to string, it will generated a >= . Example: + * + * [php] + * // u.id >= ? + * $q->where($q->expr()->gte('u.id', '?')); + * + * @param mixed $x The left expression. + * @param mixed $y The right expression. + * + * @return string + */ + public function gte($x, $y) + { + return $this->comparison($x, self::GTE, $y); + } + + /** + * Creates an IS NULL expression with the given arguments. + * + * @param string $x The field in string format to be restricted by IS NULL. + * + * @return string + */ + public function isNull($x) + { + return $x . ' IS NULL'; + } + + /** + * Creates an IS NOT NULL expression with the given arguments. + * + * @param string $x The field in string format to be restricted by IS NOT NULL. + * + * @return string + */ + public function isNotNull($x) + { + return $x . ' IS NOT NULL'; + } + + /** + * Creates a LIKE() comparison expression with the given arguments. + * + * @param string $x Field in string format to be inspected by LIKE() comparison. + * @param mixed $y Argument to be used in LIKE() comparison. + * + * @return string + */ + public function like($x, $y) + { + return $this->comparison($x, 'LIKE', $y); + } + + /** + * Creates a NOT LIKE() comparison expression with the given arguments. + * + * @param string $x Field in string format to be inspected by NOT LIKE() comparison. + * @param mixed $y Argument to be used in NOT LIKE() comparison. + * + * @return string + */ + public function notLike($x, $y) + { + return $this->comparison($x, 'NOT LIKE', $y); + } + + /** + * Creates a IN () comparison expression with the given arguments. + * + * @param string $x The field in string format to be inspected by IN() comparison. + * @param array $y The array of values to be used by IN() comparison. + * + * @return string + */ + public function in($x, array $y) + { + return $this->comparison($x, 'IN', '('.implode(', ', $y).')'); + } + + /** + * Creates a NOT IN () comparison expression with the given arguments. + * + * @param string $x The field in string format to be inspected by NOT IN() comparison. + * @param array $y The array of values to be used by NOT IN() comparison. + * + * @return string + */ + public function notIn($x, array $y) + { + return $this->comparison($x, 'NOT IN', '('.implode(', ', $y).')'); + } + + /** + * Quotes a given input parameter. + * + * @param mixed $input The parameter to be quoted. + * @param string|null $type The type of the parameter. + * + * @return string + */ + public function literal($input, $type = null) + { + return $this->connection->quote($input, $type); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryBuilder.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryBuilder.php new file mode 100644 index 0000000..ff56823 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryBuilder.php @@ -0,0 +1,1189 @@ +. + */ + +namespace Doctrine\DBAL\Query; + +use Doctrine\DBAL\Query\Expression\CompositeExpression; +use Doctrine\DBAL\Connection; + +/** + * QueryBuilder class is responsible to dynamically create SQL queries. + * + * Important: Verify that every feature you use will work with your database vendor. + * SQL Query Builder does not attempt to validate the generated SQL at all. + * + * The query builder does no validation whatsoever if certain features even work with the + * underlying database vendor. Limit queries and joins are NOT applied to UPDATE and DELETE statements + * even if some vendors such as MySQL support it. + * + * @link www.doctrine-project.org + * @since 2.1 + * @author Guilherme Blanco + * @author Benjamin Eberlei + */ +class QueryBuilder +{ + /* + * The query types. + */ + const SELECT = 0; + const DELETE = 1; + const UPDATE = 2; + + /* + * The builder states. + */ + const STATE_DIRTY = 0; + const STATE_CLEAN = 1; + + /** + * The DBAL Connection. + * + * @var \Doctrine\DBAL\Connection + */ + private $connection; + + /** + * @var array The array of SQL parts collected. + */ + private $sqlParts = array( + 'select' => array(), + 'from' => array(), + 'join' => array(), + 'set' => array(), + 'where' => null, + 'groupBy' => array(), + 'having' => null, + 'orderBy' => array() + ); + + /** + * The complete SQL string for this query. + * + * @var string + */ + private $sql; + + /** + * The query parameters. + * + * @var array + */ + private $params = array(); + + /** + * The parameter type map of this query. + * + * @var array + */ + private $paramTypes = array(); + + /** + * The type of query this is. Can be select, update or delete. + * + * @var integer + */ + private $type = self::SELECT; + + /** + * The state of the query object. Can be dirty or clean. + * + * @var integer + */ + private $state = self::STATE_CLEAN; + + /** + * The index of the first result to retrieve. + * + * @var integer + */ + private $firstResult = null; + + /** + * The maximum number of results to retrieve. + * + * @var integer + */ + private $maxResults = null; + + /** + * The counter of bound parameters used with {@see bindValue). + * + * @var integer + */ + private $boundCounter = 0; + + /** + * Initializes a new QueryBuilder. + * + * @param \Doctrine\DBAL\Connection $connection The DBAL Connection. + */ + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + /** + * Gets an ExpressionBuilder used for object-oriented construction of query expressions. + * This producer method is intended for convenient inline usage. Example: + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u') + * ->from('users', 'u') + * ->where($qb->expr()->eq('u.id', 1)); + * + * + * For more complex expression construction, consider storing the expression + * builder object in a local variable. + * + * @return \Doctrine\DBAL\Query\Expression\ExpressionBuilder + */ + public function expr() + { + return $this->connection->getExpressionBuilder(); + } + + /** + * Gets the type of the currently built query. + * + * @return integer + */ + public function getType() + { + return $this->type; + } + + /** + * Gets the associated DBAL Connection for this query builder. + * + * @return \Doctrine\DBAL\Connection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Gets the state of this query builder instance. + * + * @return integer Either QueryBuilder::STATE_DIRTY or QueryBuilder::STATE_CLEAN. + */ + public function getState() + { + return $this->state; + } + + /** + * Executes this query using the bound parameters and their types. + * + * Uses {@see Connection::executeQuery} for select statements and {@see Connection::executeUpdate} + * for insert, update and delete statements. + * + * @return mixed + */ + public function execute() + { + if ($this->type == self::SELECT) { + return $this->connection->executeQuery($this->getSQL(), $this->params, $this->paramTypes); + } else { + return $this->connection->executeUpdate($this->getSQL(), $this->params, $this->paramTypes); + } + } + + /** + * Gets the complete SQL string formed by the current specifications of this QueryBuilder. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u') + * ->from('User', 'u') + * echo $qb->getSQL(); // SELECT u FROM User u + * + * + * @return string The SQL query string. + */ + public function getSQL() + { + if ($this->sql !== null && $this->state === self::STATE_CLEAN) { + return $this->sql; + } + + switch ($this->type) { + case self::DELETE: + $sql = $this->getSQLForDelete(); + break; + + case self::UPDATE: + $sql = $this->getSQLForUpdate(); + break; + + case self::SELECT: + default: + $sql = $this->getSQLForSelect(); + break; + } + + $this->state = self::STATE_CLEAN; + $this->sql = $sql; + + return $sql; + } + + /** + * Sets a query parameter for the query being constructed. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u') + * ->from('users', 'u') + * ->where('u.id = :user_id') + * ->setParameter(':user_id', 1); + * + * + * @param string|integer $key The parameter position or name. + * @param mixed $value The parameter value. + * @param string|null $type One of the PDO::PARAM_* constants. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function setParameter($key, $value, $type = null) + { + if ($type !== null) { + $this->paramTypes[$key] = $type; + } + + $this->params[$key] = $value; + + return $this; + } + + /** + * Sets a collection of query parameters for the query being constructed. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u') + * ->from('users', 'u') + * ->where('u.id = :user_id1 OR u.id = :user_id2') + * ->setParameters(array( + * ':user_id1' => 1, + * ':user_id2' => 2 + * )); + * + * + * @param array $params The query parameters to set. + * @param array $types The query parameters types to set. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function setParameters(array $params, array $types = array()) + { + $this->paramTypes = $types; + $this->params = $params; + + return $this; + } + + /** + * Gets all defined query parameters for the query being constructed. + * + * @return array The currently defined query parameters. + */ + public function getParameters() + { + return $this->params; + } + + /** + * Gets a (previously set) query parameter of the query being constructed. + * + * @param mixed $key The key (index or name) of the bound parameter. + * + * @return mixed The value of the bound parameter. + */ + public function getParameter($key) + { + return isset($this->params[$key]) ? $this->params[$key] : null; + } + + /** + * Sets the position of the first result to retrieve (the "offset"). + * + * @param integer $firstResult The first result to return. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function setFirstResult($firstResult) + { + $this->state = self::STATE_DIRTY; + $this->firstResult = $firstResult; + + return $this; + } + + /** + * Gets the position of the first result the query object was set to retrieve (the "offset"). + * Returns NULL if {@link setFirstResult} was not applied to this QueryBuilder. + * + * @return integer The position of the first result. + */ + public function getFirstResult() + { + return $this->firstResult; + } + + /** + * Sets the maximum number of results to retrieve (the "limit"). + * + * @param integer $maxResults The maximum number of results to retrieve. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function setMaxResults($maxResults) + { + $this->state = self::STATE_DIRTY; + $this->maxResults = $maxResults; + + return $this; + } + + /** + * Gets the maximum number of results the query object was set to retrieve (the "limit"). + * Returns NULL if {@link setMaxResults} was not applied to this query builder. + * + * @return integer The maximum number of results. + */ + public function getMaxResults() + { + return $this->maxResults; + } + + /** + * Either appends to or replaces a single, generic query part. + * + * The available parts are: 'select', 'from', 'set', 'where', + * 'groupBy', 'having' and 'orderBy'. + * + * @param string $sqlPartName + * @param string $sqlPart + * @param boolean $append + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function add($sqlPartName, $sqlPart, $append = false) + { + $isArray = is_array($sqlPart); + $isMultiple = is_array($this->sqlParts[$sqlPartName]); + + if ($isMultiple && !$isArray) { + $sqlPart = array($sqlPart); + } + + $this->state = self::STATE_DIRTY; + + if ($append) { + if ($sqlPartName == "orderBy" || $sqlPartName == "groupBy" || $sqlPartName == "select" || $sqlPartName == "set") { + foreach ($sqlPart as $part) { + $this->sqlParts[$sqlPartName][] = $part; + } + } else if ($isArray && is_array($sqlPart[key($sqlPart)])) { + $key = key($sqlPart); + $this->sqlParts[$sqlPartName][$key][] = $sqlPart[$key]; + } else if ($isMultiple) { + $this->sqlParts[$sqlPartName][] = $sqlPart; + } else { + $this->sqlParts[$sqlPartName] = $sqlPart; + } + + return $this; + } + + $this->sqlParts[$sqlPartName] = $sqlPart; + + return $this; + } + + /** + * Specifies an item that is to be returned in the query result. + * Replaces any previously specified selections, if any. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.id', 'p.id') + * ->from('users', 'u') + * ->leftJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id'); + * + * + * @param mixed $select The selection expressions. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function select($select = null) + { + $this->type = self::SELECT; + + if (empty($select)) { + return $this; + } + + $selects = is_array($select) ? $select : func_get_args(); + + return $this->add('select', $selects, false); + } + + /** + * Adds an item that is to be returned in the query result. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.id') + * ->addSelect('p.id') + * ->from('users', 'u') + * ->leftJoin('u', 'phonenumbers', 'u.id = p.user_id'); + * + * + * @param mixed $select The selection expression. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function addSelect($select = null) + { + $this->type = self::SELECT; + + if (empty($select)) { + return $this; + } + + $selects = is_array($select) ? $select : func_get_args(); + + return $this->add('select', $selects, true); + } + + /** + * Turns the query being built into a bulk delete query that ranges over + * a certain table. + * + * + * $qb = $conn->createQueryBuilder() + * ->delete('users', 'u') + * ->where('u.id = :user_id'); + * ->setParameter(':user_id', 1); + * + * + * @param string $delete The table whose rows are subject to the deletion. + * @param string $alias The table alias used in the constructed query. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function delete($delete = null, $alias = null) + { + $this->type = self::DELETE; + + if ( ! $delete) { + return $this; + } + + return $this->add('from', array( + 'table' => $delete, + 'alias' => $alias + )); + } + + /** + * Turns the query being built into a bulk update query that ranges over + * a certain table + * + * + * $qb = $conn->createQueryBuilder() + * ->update('users', 'u') + * ->set('u.password', md5('password')) + * ->where('u.id = ?'); + * + * + * @param string $update The table whose rows are subject to the update. + * @param string $alias The table alias used in the constructed query. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function update($update = null, $alias = null) + { + $this->type = self::UPDATE; + + if ( ! $update) { + return $this; + } + + return $this->add('from', array( + 'table' => $update, + 'alias' => $alias + )); + } + + /** + * Creates and adds a query root corresponding to the table identified by the + * given alias, forming a cartesian product with any existing query roots. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.id') + * ->from('users', 'u') + * + * + * @param string $from The table. + * @param string $alias The alias of the table. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function from($from, $alias) + { + return $this->add('from', array( + 'table' => $from, + 'alias' => $alias + ), true); + } + + /** + * Creates and adds a join to the query. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->join('u', 'phonenumbers', 'p', 'p.is_primary = 1'); + * + * + * @param string $fromAlias The alias that points to a from clause. + * @param string $join The table name to join. + * @param string $alias The alias of the join table. + * @param string $condition The condition for the join. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function join($fromAlias, $join, $alias, $condition = null) + { + return $this->innerJoin($fromAlias, $join, $alias, $condition); + } + + /** + * Creates and adds a join to the query. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->innerJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); + * + * + * @param string $fromAlias The alias that points to a from clause. + * @param string $join The table name to join. + * @param string $alias The alias of the join table. + * @param string $condition The condition for the join. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function innerJoin($fromAlias, $join, $alias, $condition = null) + { + return $this->add('join', array( + $fromAlias => array( + 'joinType' => 'inner', + 'joinTable' => $join, + 'joinAlias' => $alias, + 'joinCondition' => $condition + ) + ), true); + } + + /** + * Creates and adds a left join to the query. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->leftJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); + * + * + * @param string $fromAlias The alias that points to a from clause. + * @param string $join The table name to join. + * @param string $alias The alias of the join table. + * @param string $condition The condition for the join. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function leftJoin($fromAlias, $join, $alias, $condition = null) + { + return $this->add('join', array( + $fromAlias => array( + 'joinType' => 'left', + 'joinTable' => $join, + 'joinAlias' => $alias, + 'joinCondition' => $condition + ) + ), true); + } + + /** + * Creates and adds a right join to the query. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->rightJoin('u', 'phonenumbers', 'p', 'p.is_primary = 1'); + * + * + * @param string $fromAlias The alias that points to a from clause. + * @param string $join The table name to join. + * @param string $alias The alias of the join table. + * @param string $condition The condition for the join. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function rightJoin($fromAlias, $join, $alias, $condition = null) + { + return $this->add('join', array( + $fromAlias => array( + 'joinType' => 'right', + 'joinTable' => $join, + 'joinAlias' => $alias, + 'joinCondition' => $condition + ) + ), true); + } + + /** + * Sets a new value for a column in a bulk update query. + * + * + * $qb = $conn->createQueryBuilder() + * ->update('users', 'u') + * ->set('u.password', md5('password')) + * ->where('u.id = ?'); + * + * + * @param string $key The column to set. + * @param string $value The value, expression, placeholder, etc. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function set($key, $value) + { + return $this->add('set', $key .' = ' . $value, true); + } + + /** + * Specifies one or more restrictions to the query result. + * Replaces any previously specified restrictions, if any. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->where('u.id = ?'); + * + * // You can optionally programatically build and/or expressions + * $qb = $conn->createQueryBuilder(); + * + * $or = $qb->expr()->orx(); + * $or->add($qb->expr()->eq('u.id', 1)); + * $or->add($qb->expr()->eq('u.id', 2)); + * + * $qb->update('users', 'u') + * ->set('u.password', md5('password')) + * ->where($or); + * + * + * @param mixed $predicates The restriction predicates. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function where($predicates) + { + if ( ! (func_num_args() == 1 && $predicates instanceof CompositeExpression) ) { + $predicates = new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args()); + } + + return $this->add('where', $predicates); + } + + /** + * Adds one or more restrictions to the query results, forming a logical + * conjunction with any previously specified restrictions. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u') + * ->from('users', 'u') + * ->where('u.username LIKE ?') + * ->andWhere('u.is_active = 1'); + * + * + * @param mixed $where The query restrictions. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + * + * @see where() + */ + public function andWhere($where) + { + $where = $this->getQueryPart('where'); + $args = func_get_args(); + + if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_AND) { + $where->addMultiple($args); + } else { + array_unshift($args, $where); + $where = new CompositeExpression(CompositeExpression::TYPE_AND, $args); + } + + return $this->add('where', $where, true); + } + + /** + * Adds one or more restrictions to the query results, forming a logical + * disjunction with any previously specified restrictions. + * + * + * $qb = $em->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->where('u.id = 1') + * ->orWhere('u.id = 2'); + * + * + * @param mixed $where The WHERE statement. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + * + * @see where() + */ + public function orWhere($where) + { + $where = $this->getQueryPart('where'); + $args = func_get_args(); + + if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_OR) { + $where->addMultiple($args); + } else { + array_unshift($args, $where); + $where = new CompositeExpression(CompositeExpression::TYPE_OR, $args); + } + + return $this->add('where', $where, true); + } + + /** + * Specifies a grouping over the results of the query. + * Replaces any previously specified groupings, if any. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->groupBy('u.id'); + * + * + * @param mixed $groupBy The grouping expression. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function groupBy($groupBy) + { + if (empty($groupBy)) { + return $this; + } + + $groupBy = is_array($groupBy) ? $groupBy : func_get_args(); + + return $this->add('groupBy', $groupBy, false); + } + + + /** + * Adds a grouping expression to the query. + * + * + * $qb = $conn->createQueryBuilder() + * ->select('u.name') + * ->from('users', 'u') + * ->groupBy('u.lastLogin'); + * ->addGroupBy('u.createdAt') + * + * + * @param mixed $groupBy The grouping expression. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function addGroupBy($groupBy) + { + if (empty($groupBy)) { + return $this; + } + + $groupBy = is_array($groupBy) ? $groupBy : func_get_args(); + + return $this->add('groupBy', $groupBy, true); + } + + /** + * Specifies a restriction over the groups of the query. + * Replaces any previous having restrictions, if any. + * + * @param mixed $having The restriction over the groups. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function having($having) + { + if ( ! (func_num_args() == 1 && $having instanceof CompositeExpression)) { + $having = new CompositeExpression(CompositeExpression::TYPE_AND, func_get_args()); + } + + return $this->add('having', $having); + } + + /** + * Adds a restriction over the groups of the query, forming a logical + * conjunction with any existing having restrictions. + * + * @param mixed $having The restriction to append. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function andHaving($having) + { + $having = $this->getQueryPart('having'); + $args = func_get_args(); + + if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_AND) { + $having->addMultiple($args); + } else { + array_unshift($args, $having); + $having = new CompositeExpression(CompositeExpression::TYPE_AND, $args); + } + + return $this->add('having', $having); + } + + /** + * Adds a restriction over the groups of the query, forming a logical + * disjunction with any existing having restrictions. + * + * @param mixed $having The restriction to add. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function orHaving($having) + { + $having = $this->getQueryPart('having'); + $args = func_get_args(); + + if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_OR) { + $having->addMultiple($args); + } else { + array_unshift($args, $having); + $having = new CompositeExpression(CompositeExpression::TYPE_OR, $args); + } + + return $this->add('having', $having); + } + + /** + * Specifies an ordering for the query results. + * Replaces any previously specified orderings, if any. + * + * @param string $sort The ordering expression. + * @param string $order The ordering direction. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function orderBy($sort, $order = null) + { + return $this->add('orderBy', $sort . ' ' . (! $order ? 'ASC' : $order), false); + } + + /** + * Adds an ordering to the query results. + * + * @param string $sort The ordering expression. + * @param string $order The ordering direction. + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function addOrderBy($sort, $order = null) + { + return $this->add('orderBy', $sort . ' ' . (! $order ? 'ASC' : $order), true); + } + + /** + * Gets a query part by its name. + * + * @param string $queryPartName + * + * @return mixed + */ + public function getQueryPart($queryPartName) + { + return $this->sqlParts[$queryPartName]; + } + + /** + * Gets all query parts. + * + * @return array + */ + public function getQueryParts() + { + return $this->sqlParts; + } + + /** + * Resets SQL parts. + * + * @param array|null $queryPartNames + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function resetQueryParts($queryPartNames = null) + { + if (is_null($queryPartNames)) { + $queryPartNames = array_keys($this->sqlParts); + } + + foreach ($queryPartNames as $queryPartName) { + $this->resetQueryPart($queryPartName); + } + + return $this; + } + + /** + * Resets a single SQL part. + * + * @param string $queryPartName + * + * @return \Doctrine\DBAL\Query\QueryBuilder This QueryBuilder instance. + */ + public function resetQueryPart($queryPartName) + { + $this->sqlParts[$queryPartName] = is_array($this->sqlParts[$queryPartName]) + ? array() : null; + + $this->state = self::STATE_DIRTY; + + return $this; + } + + /** + * @return string + * + * @throws \Doctrine\DBAL\Query\QueryException + */ + private function getSQLForSelect() + { + $query = 'SELECT ' . implode(', ', $this->sqlParts['select']) . ' FROM '; + + $fromClauses = array(); + $knownAliases = array(); + + // Loop through all FROM clauses + foreach ($this->sqlParts['from'] as $from) { + $knownAliases[$from['alias']] = true; + $fromClause = $from['table'] . ' ' . $from['alias'] + . $this->getSQLForJoins($from['alias'], $knownAliases); + + $fromClauses[$from['alias']] = $fromClause; + } + + foreach ($this->sqlParts['join'] as $fromAlias => $joins) { + if ( ! isset($knownAliases[$fromAlias]) ) { + throw QueryException::unknownAlias($fromAlias, array_keys($knownAliases)); + } + } + + $query .= implode(', ', $fromClauses) + . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : '') + . ($this->sqlParts['groupBy'] ? ' GROUP BY ' . implode(', ', $this->sqlParts['groupBy']) : '') + . ($this->sqlParts['having'] !== null ? ' HAVING ' . ((string) $this->sqlParts['having']) : '') + . ($this->sqlParts['orderBy'] ? ' ORDER BY ' . implode(', ', $this->sqlParts['orderBy']) : ''); + + return ($this->maxResults === null && $this->firstResult == null) + ? $query + : $this->connection->getDatabasePlatform()->modifyLimitQuery($query, $this->maxResults, $this->firstResult); + } + + /** + * Converts this instance into an UPDATE string in SQL. + * + * @return string + */ + private function getSQLForUpdate() + { + $table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : ''); + $query = 'UPDATE ' . $table + . ' SET ' . implode(", ", $this->sqlParts['set']) + . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : ''); + + return $query; + } + + /** + * Converts this instance into a DELETE string in SQL. + * + * @return string + */ + private function getSQLForDelete() + { + $table = $this->sqlParts['from']['table'] . ($this->sqlParts['from']['alias'] ? ' ' . $this->sqlParts['from']['alias'] : ''); + $query = 'DELETE FROM ' . $table . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string) $this->sqlParts['where']) : ''); + + return $query; + } + + /** + * Gets a string representation of this QueryBuilder which corresponds to + * the final SQL query being constructed. + * + * @return string The string representation of this QueryBuilder. + */ + public function __toString() + { + return $this->getSQL(); + } + + /** + * Creates a new named parameter and bind the value $value to it. + * + * This method provides a shortcut for PDOStatement::bindValue + * when using prepared statements. + * + * The parameter $value specifies the value that you want to bind. If + * $placeholder is not provided bindValue() will automatically create a + * placeholder for you. An automatic placeholder will be of the name + * ':dcValue1', ':dcValue2' etc. + * + * For more information see {@link http://php.net/pdostatement-bindparam} + * + * Example: + * + * $value = 2; + * $q->eq( 'id', $q->bindValue( $value ) ); + * $stmt = $q->executeQuery(); // executed with 'id = 2' + * + * + * @license New BSD License + * @link http://www.zetacomponents.org + * + * @param mixed $value + * @param mixed $type + * @param string $placeHolder The name to bind with. The string must start with a colon ':'. + * + * @return string the placeholder name used. + */ + public function createNamedParameter( $value, $type = \PDO::PARAM_STR, $placeHolder = null ) + { + if ( $placeHolder === null ) { + $this->boundCounter++; + $placeHolder = ":dcValue" . $this->boundCounter; + } + $this->setParameter(substr($placeHolder, 1), $value, $type); + + return $placeHolder; + } + + /** + * Creates a new positional parameter and bind the given value to it. + * + * Attention: If you are using positional parameters with the query builder you have + * to be very careful to bind all parameters in the order they appear in the SQL + * statement , otherwise they get bound in the wrong order which can lead to serious + * bugs in your code. + * + * Example: + * + * $qb = $conn->createQueryBuilder(); + * $qb->select('u.*') + * ->from('users', 'u') + * ->where('u.username = ' . $qb->createPositionalParameter('Foo', PDO::PARAM_STR)) + * ->orWhere('u.username = ' . $qb->createPositionalParameter('Bar', PDO::PARAM_STR)) + * + * + * @param mixed $value + * @param integer $type + * + * @return string + */ + public function createPositionalParameter($value, $type = \PDO::PARAM_STR) + { + $this->boundCounter++; + $this->setParameter($this->boundCounter, $value, $type); + return "?"; + } + + /** + * @param string $fromAlias + * @param array $knownAliases + * + * @return string + */ + private function getSQLForJoins($fromAlias, array &$knownAliases) + { + $sql = ''; + + if (isset($this->sqlParts['join'][$fromAlias])) { + foreach ($this->sqlParts['join'][$fromAlias] as $join) { + $sql .= ' ' . strtoupper($join['joinType']) + . ' JOIN ' . $join['joinTable'] . ' ' . $join['joinAlias'] + . ' ON ' . ((string) $join['joinCondition']); + $knownAliases[$join['joinAlias']] = true; + + $sql .= $this->getSQLForJoins($join['joinAlias'], $knownAliases); + } + } + + return $sql; + } + + /** + * Deep clone of all expression objects in the SQL parts. + * + * @return void + */ + public function __clone() + { + foreach ($this->sqlParts as $part => $elements) { + if (is_array($this->sqlParts[$part])) { + foreach ($this->sqlParts[$part] as $idx => $element) { + if (is_object($element)) { + $this->sqlParts[$part][$idx] = clone $element; + } + } + } else if (is_object($elements)) { + $this->sqlParts[$part] = clone $elements; + } + } + + foreach ($this->params as $name => $param) { + if(is_object($param)){ + $this->params[$name] = clone $param; + } + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryException.php new file mode 100644 index 0000000..aeeaab3 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryException.php @@ -0,0 +1,41 @@ +. + */ + +namespace Doctrine\DBAL\Query; + +use Doctrine\DBAL\DBALException; + +/** + * @since 2.1.4 + */ +class QueryException extends DBALException +{ + /** + * @param string $alias + * @param array $registeredAliases + * + * @return \Doctrine\DBAL\Query\QueryException + */ + static public function unknownAlias($alias, $registeredAliases) + { + return new self("The given alias '" . $alias . "' is not part of " . + "any FROM or JOIN clause table. The currently registered " . + "aliases are: " . implode(", ", $registeredAliases) . "."); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/README.markdown b/vendor/doctrine/dbal/lib/Doctrine/DBAL/README.markdown new file mode 100644 index 0000000..e69de29 diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/SQLParserUtils.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/SQLParserUtils.php new file mode 100644 index 0000000..512408d --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/SQLParserUtils.php @@ -0,0 +1,233 @@ +. + */ + +namespace Doctrine\DBAL; + +use Doctrine\DBAL\Connection; + +/** + * Utility class that parses sql statements with regard to types and parameters. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class SQLParserUtils +{ + const POSITIONAL_TOKEN = '\?'; + const NAMED_TOKEN = '(? integer pair (indexed from zero) for a positional statement + * and a string => int[] pair for a named statement. + * + * @param string $statement + * @param boolean $isPositional + * + * @return array + */ + static public function getPlaceholderPositions($statement, $isPositional = true) + { + $match = ($isPositional) ? '?' : ':'; + if (strpos($statement, $match) === false) { + return array(); + } + + $token = ($isPositional) ? self::POSITIONAL_TOKEN : self::NAMED_TOKEN; + $paramMap = array(); + + foreach (self::getUnquotedStatementFragments($statement) as $fragment) { + preg_match_all("/$token/", $fragment[0], $matches, PREG_OFFSET_CAPTURE); + foreach ($matches[0] as $placeholder) { + if ($isPositional) { + $paramMap[] = $placeholder[1] + $fragment[1]; + } else { + $pos = $placeholder[1] + $fragment[1]; + $paramMap[$pos] = substr($placeholder[0], 1, strlen($placeholder[0])); + } + } + } + + return $paramMap; + } + + /** + * For a positional query this method can rewrite the sql statement with regard to array parameters. + * + * @param string $query The SQL query to execute. + * @param array $params The parameters to bind to the query. + * @param array $types The types the previous parameters are in. + * + * @return array + * + * @throws SQLParserUtilsException + */ + static public function expandListParameters($query, $params, $types) + { + $isPositional = is_int(key($params)); + $arrayPositions = array(); + $bindIndex = -1; + + foreach ($types as $name => $type) { + ++$bindIndex; + + if ($type !== Connection::PARAM_INT_ARRAY && $type !== Connection::PARAM_STR_ARRAY) { + continue; + } + + if ($isPositional) { + $name = $bindIndex; + } + + $arrayPositions[$name] = false; + } + + if (( ! $arrayPositions && $isPositional)) { + return array($query, $params, $types); + } + + $paramPos = self::getPlaceholderPositions($query, $isPositional); + + if ($isPositional) { + $paramOffset = 0; + $queryOffset = 0; + + foreach ($paramPos as $needle => $needlePos) { + if ( ! isset($arrayPositions[$needle])) { + continue; + } + + $needle += $paramOffset; + $needlePos += $queryOffset; + $count = count($params[$needle]); + + $params = array_merge( + array_slice($params, 0, $needle), + $params[$needle], + array_slice($params, $needle + 1) + ); + + $types = array_merge( + array_slice($types, 0, $needle), + $count ? + array_fill(0, $count, $types[$needle] - Connection::ARRAY_PARAM_OFFSET) : // array needles are at PDO::PARAM_* + 100 + array(), + array_slice($types, $needle + 1) + ); + + $expandStr = $count ? implode(", ", array_fill(0, $count, "?")) : 'NULL'; + $query = substr($query, 0, $needlePos) . $expandStr . substr($query, $needlePos + 1); + + $paramOffset += ($count - 1); // Grows larger by number of parameters minus the replaced needle. + $queryOffset += (strlen($expandStr) - 1); + } + + return array($query, $params, $types); + } + + $queryOffset = 0; + $typesOrd = array(); + $paramsOrd = array(); + + foreach ($paramPos as $pos => $paramName) { + $paramLen = strlen($paramName) + 1; + $value = static::extractParam($paramName, $params, true); + + if ( ! isset($arrayPositions[$paramName]) && ! isset($arrayPositions[':' . $paramName])) { + $pos += $queryOffset; + $queryOffset -= ($paramLen - 1); + $paramsOrd[] = $value; + $typesOrd[] = static::extractParam($paramName, $types, false, \PDO::PARAM_STR); + $query = substr($query, 0, $pos) . '?' . substr($query, ($pos + $paramLen)); + + continue; + } + + $count = count($value); + $expandStr = $count > 0 ? implode(', ', array_fill(0, $count, '?')) : 'NULL'; + + foreach ($value as $val) { + $paramsOrd[] = $val; + $typesOrd[] = static::extractParam($paramName, $types, false) - Connection::ARRAY_PARAM_OFFSET; + } + + $pos += $queryOffset; + $queryOffset += (strlen($expandStr) - $paramLen); + $query = substr($query, 0, $pos) . $expandStr . substr($query, ($pos + $paramLen)); + } + + return array($query, $paramsOrd, $typesOrd); + } + + /** + * Slice the SQL statement around pairs of quotes and + * return string fragments of SQL outside of quoted literals. + * Each fragment is captured as a 2-element array: + * + * 0 => matched fragment string, + * 1 => offset of fragment in $statement + * + * @param string $statement + * @return array + */ + static private function getUnquotedStatementFragments($statement) + { + $literal = self::ESCAPED_SINGLE_QUOTED_TEXT . '|' . self::ESCAPED_DOUBLE_QUOTED_TEXT; + preg_match_all("/([^'\"]+)(?:$literal)?/s", $statement, $fragments, PREG_OFFSET_CAPTURE); + + return $fragments[1]; + } + + /** + * @param string $paramName The name of the parameter (without a colon in front) + * @param array $paramsOrTypes A hash of parameters or types + * @param bool $isParam + * @param mixed $defaultValue An optional default value. If omitted, an exception is thrown + * + * @throws SQLParserUtilsException + * @return mixed + */ + static private function extractParam($paramName, $paramsOrTypes, $isParam, $defaultValue = null) + { + if (array_key_exists($paramName, $paramsOrTypes)) { + return $paramsOrTypes[$paramName]; + } + + // Hash keys can be prefixed with a colon for compatibility + if (array_key_exists(':' . $paramName, $paramsOrTypes)) { + return $paramsOrTypes[':' . $paramName]; + } + + if (null !== $defaultValue) { + return $defaultValue; + } + + if ($isParam) { + throw SQLParserUtilsException::missingParam($paramName); + } + + throw SQLParserUtilsException::missingType($paramName); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/SQLParserUtilsException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/SQLParserUtilsException.php new file mode 100644 index 0000000..6ac6e90 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/SQLParserUtilsException.php @@ -0,0 +1,53 @@ +. + */ + +namespace Doctrine\DBAL; + +/** + * Doctrine\DBAL\ConnectionException + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @link www.doctrine-project.org + * @since 2.4 + * @author Lars Strojny + */ +class SQLParserUtilsException extends DBALException +{ + /** + * @param string $paramName + * + * @return \Doctrine\DBAL\SQLParserUtilsException + */ + public static function missingParam($paramName) + { + return new self(sprintf('Value for :%1$s not found in params array. Params array key should be "%1$s"', $paramName)); + } + + /** + * @param string $typeName + * + * @return \Doctrine\DBAL\SQLParserUtilsException + */ + public static function missingType($typeName) + { + return new self(sprintf('Value for :%1$s not found in types array. Types array key should be "%1$s"', $typeName)); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/AbstractAsset.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/AbstractAsset.php new file mode 100644 index 0000000..2159b02 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/AbstractAsset.php @@ -0,0 +1,226 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * The abstract asset allows to reset the name of all assets without publishing this to the public userland. + * + * This encapsulation hack is necessary to keep a consistent state of the database schema. Say we have a list of tables + * array($tableName => Table($tableName)); if you want to rename the table, you have to make sure + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +abstract class AbstractAsset +{ + /** + * @var string + */ + protected $_name; + + /** + * Namespace of the asset. If none isset the default namespace is assumed. + * + * @var string|null + */ + protected $_namespace = null; + + /** + * @var boolean + */ + protected $_quoted = false; + + /** + * Sets the name of this asset. + * + * @param string $name + * + * @return void + */ + protected function _setName($name) + { + if ($this->isIdentifierQuoted($name)) { + $this->_quoted = true; + $name = $this->trimQuotes($name); + } + if (strpos($name, ".") !== false) { + $parts = explode(".", $name); + $this->_namespace = $parts[0]; + $name = $parts[1]; + } + $this->_name = $name; + } + + /** + * Is this asset in the default namespace? + * + * @param string $defaultNamespaceName + * + * @return boolean + */ + public function isInDefaultNamespace($defaultNamespaceName) + { + return $this->_namespace == $defaultNamespaceName || $this->_namespace === null; + } + + /** + * Gets the namespace name of this asset. + * + * If NULL is returned this means the default namespace is used. + * + * @return string|null + */ + public function getNamespaceName() + { + return $this->_namespace; + } + + /** + * The shortest name is stripped of the default namespace. All other + * namespaced elements are returned as full-qualified names. + * + * @param string + * + * @return string + */ + public function getShortestName($defaultNamespaceName) + { + $shortestName = $this->getName(); + if ($this->_namespace == $defaultNamespaceName) { + $shortestName = $this->_name; + } + + return strtolower($shortestName); + } + + /** + * The normalized name is full-qualified and lowerspaced. Lowerspacing is + * actually wrong, but we have to do it to keep our sanity. If you are + * using database objects that only differentiate in the casing (FOO vs + * Foo) then you will NOT be able to use Doctrine Schema abstraction. + * + * Every non-namespaced element is prefixed with the default namespace + * name which is passed as argument to this method. + * + * @param string $defaultNamespaceName + * + * @return string + */ + public function getFullQualifiedName($defaultNamespaceName) + { + $name = $this->getName(); + if ( ! $this->_namespace) { + $name = $defaultNamespaceName . "." . $name; + } + + return strtolower($name); + } + + /** + * Checks if this asset's name is quoted. + * + * @return boolean + */ + public function isQuoted() + { + return $this->_quoted; + } + + /** + * Checks if this identifier is quoted. + * + * @param string $identifier + * + * @return boolean + */ + protected function isIdentifierQuoted($identifier) + { + return (isset($identifier[0]) && ($identifier[0] == '`' || $identifier[0] == '"')); + } + + /** + * Trim quotes from the identifier. + * + * @param string $identifier + * + * @return string + */ + protected function trimQuotes($identifier) + { + return str_replace(array('`', '"'), '', $identifier); + } + + /** + * Returns the name of this schema asset. + * + * @return string + */ + public function getName() + { + if ($this->_namespace) { + return $this->_namespace . "." . $this->_name; + } + return $this->_name; + } + + /** + * Gets the quoted representation of this asset but only if it was defined with one. Otherwise + * return the plain unquoted value as inserted. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return string + */ + public function getQuotedName(AbstractPlatform $platform) + { + $keywords = $platform->getReservedKeywordsList(); + $parts = explode(".", $this->getName()); + foreach ($parts as $k => $v) { + $parts[$k] = ($this->_quoted || $keywords->isKeyword($v)) ? $platform->quoteIdentifier($v) : $v; + } + + return implode(".", $parts); + } + + /** + * Generates an identifier from a list of column names obeying a certain string length. + * + * This is especially important for Oracle, since it does not allow identifiers larger than 30 chars, + * however building idents automatically for foreign keys, composite keys or such can easily create + * very long names. + * + * @param array $columnNames + * @param string $prefix + * @param integer $maxSize + * + * @return string + */ + protected function _generateIdentifierName($columnNames, $prefix='', $maxSize=30) + { + $hash = implode("", array_map(function($column) { + return dechex(crc32($column)); + }, $columnNames)); + + return substr(strtoupper($prefix . "_" . $hash), 0, $maxSize); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/AbstractSchemaManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/AbstractSchemaManager.php new file mode 100644 index 0000000..72fb530 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/AbstractSchemaManager.php @@ -0,0 +1,1059 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Events; +use Doctrine\DBAL\Event\SchemaColumnDefinitionEventArgs; +use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs; +use Doctrine\DBAL\Types; +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Base class for schema managers. Schema managers are used to inspect and/or + * modify the database schema/structure. + * + * @author Konsta Vesterinen + * @author Lukas Smith (PEAR MDB2 library) + * @author Roman Borschel + * @author Jonathan H. Wage + * @author Benjamin Eberlei + * @since 2.0 + */ +abstract class AbstractSchemaManager +{ + /** + * Holds instance of the Doctrine connection for this schema manager. + * + * @var \Doctrine\DBAL\Connection + */ + protected $_conn; + + /** + * Holds instance of the database platform used for this schema manager. + * + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + protected $_platform; + + /** + * Constructor. Accepts the Connection instance to manage the schema for. + * + * @param \Doctrine\DBAL\Connection $conn + * @param \Doctrine\DBAL\Platforms\AbstractPlatform|null $platform + */ + public function __construct(\Doctrine\DBAL\Connection $conn, AbstractPlatform $platform = null) + { + $this->_conn = $conn; + $this->_platform = $platform ?: $this->_conn->getDatabasePlatform(); + } + + /** + * Returns the associated platform. + * + * @return \Doctrine\DBAL\Platforms\AbstractPlatform + */ + public function getDatabasePlatform() + { + return $this->_platform; + } + + /** + * Tries any method on the schema manager. Normally a method throws an + * exception when your DBMS doesn't support it or if an error occurs. + * This method allows you to try and method on your SchemaManager + * instance and will return false if it does not work or is not supported. + * + * + * $result = $sm->tryMethod('dropView', 'view_name'); + * + * + * @return mixed + */ + public function tryMethod() + { + $args = func_get_args(); + $method = $args[0]; + unset($args[0]); + $args = array_values($args); + + try { + return call_user_func_array(array($this, $method), $args); + } catch (\Exception $e) { + return false; + } + } + + /** + * Lists the available databases for this connection. + * + * @return array + */ + public function listDatabases() + { + $sql = $this->_platform->getListDatabasesSQL(); + + $databases = $this->_conn->fetchAll($sql); + + return $this->_getPortableDatabasesList($databases); + } + + /** + * Lists the available sequences for this connection. + * + * @param string|null $database + * + * @return \Doctrine\DBAL\Schema\Sequence[] + */ + public function listSequences($database = null) + { + if (is_null($database)) { + $database = $this->_conn->getDatabase(); + } + $sql = $this->_platform->getListSequencesSQL($database); + + $sequences = $this->_conn->fetchAll($sql); + + return $this->filterAssetNames($this->_getPortableSequencesList($sequences)); + } + + /** + * Lists the columns for a given table. + * + * In contrast to other libraries and to the old version of Doctrine, + * this column definition does try to contain the 'primary' field for + * the reason that it is not portable accross different RDBMS. Use + * {@see listTableIndexes($tableName)} to retrieve the primary key + * of a table. We're a RDBMS specifies more details these are held + * in the platformDetails array. + * + * @param string $table The name of the table. + * @param string|null $database + * + * @return \Doctrine\DBAL\Schema\Column[] + */ + public function listTableColumns($table, $database = null) + { + if ( ! $database) { + $database = $this->_conn->getDatabase(); + } + + $sql = $this->_platform->getListTableColumnsSQL($table, $database); + + $tableColumns = $this->_conn->fetchAll($sql); + + return $this->_getPortableTableColumnList($table, $database, $tableColumns); + } + + /** + * Lists the indexes for a given table returning an array of Index instances. + * + * Keys of the portable indexes list are all lower-cased. + * + * @param string $table The name of the table. + * + * @return \Doctrine\DBAL\Schema\Index[] + */ + public function listTableIndexes($table) + { + $sql = $this->_platform->getListTableIndexesSQL($table, $this->_conn->getDatabase()); + + $tableIndexes = $this->_conn->fetchAll($sql); + + return $this->_getPortableTableIndexesList($tableIndexes, $table); + } + + /** + * Returns true if all the given tables exist. + * + * @param array $tableNames + * + * @return boolean + */ + public function tablesExist($tableNames) + { + $tableNames = array_map('strtolower', (array)$tableNames); + + return count($tableNames) == count(\array_intersect($tableNames, array_map('strtolower', $this->listTableNames()))); + } + + /** + * Returns a list of all tables in the current database. + * + * @return array + */ + public function listTableNames() + { + $sql = $this->_platform->getListTablesSQL(); + + $tables = $this->_conn->fetchAll($sql); + $tableNames = $this->_getPortableTablesList($tables); + + return $this->filterAssetNames($tableNames); + } + + /** + * Filters asset names if they are configured to return only a subset of all + * the found elements. + * + * @param array $assetNames + * + * @return array + */ + protected function filterAssetNames($assetNames) + { + $filterExpr = $this->getFilterSchemaAssetsExpression(); + if ( ! $filterExpr) { + return $assetNames; + } + + return array_values ( + array_filter($assetNames, function ($assetName) use ($filterExpr) { + $assetName = ($assetName instanceof AbstractAsset) ? $assetName->getName() : $assetName; + return preg_match($filterExpr, $assetName); + }) + ); + } + + /** + * @return string|null + */ + protected function getFilterSchemaAssetsExpression() + { + return $this->_conn->getConfiguration()->getFilterSchemaAssetsExpression(); + } + + /** + * Lists the tables for this connection. + * + * @return \Doctrine\DBAL\Schema\Table[] + */ + public function listTables() + { + $tableNames = $this->listTableNames(); + + $tables = array(); + foreach ($tableNames as $tableName) { + $tables[] = $this->listTableDetails($tableName); + } + + return $tables; + } + + /** + * @param string $tableName + * + * @return \Doctrine\DBAL\Schema\Table + */ + public function listTableDetails($tableName) + { + $columns = $this->listTableColumns($tableName); + $foreignKeys = array(); + if ($this->_platform->supportsForeignKeyConstraints()) { + $foreignKeys = $this->listTableForeignKeys($tableName); + } + $indexes = $this->listTableIndexes($tableName); + + return new Table($tableName, $columns, $indexes, $foreignKeys, false, array()); + } + + /** + * Lists the views this connection has. + * + * @return \Doctrine\DBAL\Schema\View[] + */ + public function listViews() + { + $database = $this->_conn->getDatabase(); + $sql = $this->_platform->getListViewsSQL($database); + $views = $this->_conn->fetchAll($sql); + + return $this->_getPortableViewsList($views); + } + + /** + * Lists the foreign keys for the given table. + * + * @param string $table The name of the table. + * @param string|null $database + * + * @return \Doctrine\DBAL\Schema\ForeignKeyConstraint[] + */ + public function listTableForeignKeys($table, $database = null) + { + if (is_null($database)) { + $database = $this->_conn->getDatabase(); + } + $sql = $this->_platform->getListTableForeignKeysSQL($table, $database); + $tableForeignKeys = $this->_conn->fetchAll($sql); + + return $this->_getPortableTableForeignKeysList($tableForeignKeys); + } + + /* drop*() Methods */ + + /** + * Drops a database. + * + * NOTE: You can not drop the database this SchemaManager is currently connected to. + * + * @param string $database The name of the database to drop. + * + * @return void + */ + public function dropDatabase($database) + { + $this->_execSql($this->_platform->getDropDatabaseSQL($database)); + } + + /** + * Drops the given table. + * + * @param string $table The name of the table to drop. + * + * @return void + */ + public function dropTable($table) + { + $this->_execSql($this->_platform->getDropTableSQL($table)); + } + + /** + * Drops the index from the given table. + * + * @param \Doctrine\DBAL\Schema\Index|string $index The name of the index. + * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table. + * + * @return void + */ + public function dropIndex($index, $table) + { + if($index instanceof Index) { + $index = $index->getQuotedName($this->_platform); + } + + $this->_execSql($this->_platform->getDropIndexSQL($index, $table)); + } + + /** + * Drops the constraint from the given table. + * + * @param \Doctrine\DBAL\Schema\Constraint $constraint + * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table. + * + * @return void + */ + public function dropConstraint(Constraint $constraint, $table) + { + $this->_execSql($this->_platform->getDropConstraintSQL($constraint, $table)); + } + + /** + * Drops a foreign key from a table. + * + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint|string $foreignKey The name of the foreign key. + * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table with the foreign key. + * + * @return void + */ + public function dropForeignKey($foreignKey, $table) + { + $this->_execSql($this->_platform->getDropForeignKeySQL($foreignKey, $table)); + } + + /** + * Drops a sequence with a given name. + * + * @param string $name The name of the sequence to drop. + * + * @return void + */ + public function dropSequence($name) + { + $this->_execSql($this->_platform->getDropSequenceSQL($name)); + } + + /** + * Drops a view. + * + * @param string $name The name of the view. + * + * @return void + */ + public function dropView($name) + { + $this->_execSql($this->_platform->getDropViewSQL($name)); + } + + /* create*() Methods */ + + /** + * Creates a new database. + * + * @param string $database The name of the database to create. + * + * @return void + */ + public function createDatabase($database) + { + $this->_execSql($this->_platform->getCreateDatabaseSQL($database)); + } + + /** + * Creates a new table. + * + * @param \Doctrine\DBAL\Schema\Table $table + * + * @return void + */ + public function createTable(Table $table) + { + $createFlags = AbstractPlatform::CREATE_INDEXES|AbstractPlatform::CREATE_FOREIGNKEYS; + $this->_execSql($this->_platform->getCreateTableSQL($table, $createFlags)); + } + + /** + * Creates a new sequence. + * + * @param \Doctrine\DBAL\Schema\Sequence $sequence + * + * @return void + * + * @throws \Doctrine\DBAL\ConnectionException If something fails at database level. + */ + public function createSequence($sequence) + { + $this->_execSql($this->_platform->getCreateSequenceSQL($sequence)); + } + + /** + * Creates a constraint on a table. + * + * @param \Doctrine\DBAL\Schema\Constraint $constraint + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return void + */ + public function createConstraint(Constraint $constraint, $table) + { + $this->_execSql($this->_platform->getCreateConstraintSQL($constraint, $table)); + } + + /** + * Creates a new index on a table. + * + * @param \Doctrine\DBAL\Schema\Index $index + * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table on which the index is to be created. + * + * @return void + */ + public function createIndex(Index $index, $table) + { + $this->_execSql($this->_platform->getCreateIndexSQL($index, $table)); + } + + /** + * Creates a new foreign key. + * + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey The ForeignKey instance. + * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table on which the foreign key is to be created. + * + * @return void + */ + public function createForeignKey(ForeignKeyConstraint $foreignKey, $table) + { + $this->_execSql($this->_platform->getCreateForeignKeySQL($foreignKey, $table)); + } + + /** + * Creates a new view. + * + * @param \Doctrine\DBAL\Schema\View $view + * + * @return void + */ + public function createView(View $view) + { + $this->_execSql($this->_platform->getCreateViewSQL($view->getQuotedName($this->_platform), $view->getSql())); + } + + /* dropAndCreate*() Methods */ + + /** + * Drops and creates a constraint. + * + * @see dropConstraint() + * @see createConstraint() + * + * @param \Doctrine\DBAL\Schema\Constraint $constraint + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return void + */ + public function dropAndCreateConstraint(Constraint $constraint, $table) + { + $this->tryMethod('dropConstraint', $constraint, $table); + $this->createConstraint($constraint, $table); + } + + /** + * Drops and creates a new index on a table. + * + * @param \Doctrine\DBAL\Schema\Index $index + * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table on which the index is to be created. + * + * @return void + */ + public function dropAndCreateIndex(Index $index, $table) + { + $this->tryMethod('dropIndex', $index->getQuotedName($this->_platform), $table); + $this->createIndex($index, $table); + } + + /** + * Drops and creates a new foreign key. + * + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey An associative array that defines properties of the foreign key to be created. + * @param \Doctrine\DBAL\Schema\Table|string $table The name of the table on which the foreign key is to be created. + * + * @return void + */ + public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table) + { + $this->tryMethod('dropForeignKey', $foreignKey, $table); + $this->createForeignKey($foreignKey, $table); + } + + /** + * Drops and create a new sequence. + * + * @param \Doctrine\DBAL\Schema\Sequence $sequence + * + * @return void + * + * @throws \Doctrine\DBAL\ConnectionException If something fails at database level. + */ + public function dropAndCreateSequence(Sequence $sequence) + { + $this->tryMethod('dropSequence', $sequence->getQuotedName($this->_platform)); + $this->createSequence($sequence); + } + + /** + * Drops and creates a new table. + * + * @param \Doctrine\DBAL\Schema\Table $table + * + * @return void + */ + public function dropAndCreateTable(Table $table) + { + $this->tryMethod('dropTable', $table->getQuotedName($this->_platform)); + $this->createTable($table); + } + + /** + * Drops and creates a new database. + * + * @param string $database The name of the database to create. + * + * @return void + */ + public function dropAndCreateDatabase($database) + { + $this->tryMethod('dropDatabase', $database); + $this->createDatabase($database); + } + + /** + * Drops and creates a new view. + * + * @param \Doctrine\DBAL\Schema\View $view + * + * @return void + */ + public function dropAndCreateView(View $view) + { + $this->tryMethod('dropView', $view->getQuotedName($this->_platform)); + $this->createView($view); + } + + /* alterTable() Methods */ + + /** + * Alters an existing tables schema. + * + * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff + * + * @return void + */ + public function alterTable(TableDiff $tableDiff) + { + $queries = $this->_platform->getAlterTableSQL($tableDiff); + if (is_array($queries) && count($queries)) { + foreach ($queries as $ddlQuery) { + $this->_execSql($ddlQuery); + } + } + } + + /** + * Renames a given table to another name. + * + * @param string $name The current name of the table. + * @param string $newName The new name of the table. + * + * @return void + */ + public function renameTable($name, $newName) + { + $tableDiff = new TableDiff($name); + $tableDiff->newName = $newName; + $this->alterTable($tableDiff); + } + + /** + * Methods for filtering return values of list*() methods to convert + * the native DBMS data definition to a portable Doctrine definition + */ + + /** + * @param array $databases + * + * @return array + */ + protected function _getPortableDatabasesList($databases) + { + $list = array(); + foreach ($databases as $value) { + if ($value = $this->_getPortableDatabaseDefinition($value)) { + $list[] = $value; + } + } + + return $list; + } + + /** + * @param array $database + * + * @return mixed + */ + protected function _getPortableDatabaseDefinition($database) + { + return $database; + } + + /** + * @param array $functions + * + * @return array + */ + protected function _getPortableFunctionsList($functions) + { + $list = array(); + foreach ($functions as $value) { + if ($value = $this->_getPortableFunctionDefinition($value)) { + $list[] = $value; + } + } + + return $list; + } + + /** + * @param array $function + * + * @return mixed + */ + protected function _getPortableFunctionDefinition($function) + { + return $function; + } + + /** + * @param array $triggers + * + * @return array + */ + protected function _getPortableTriggersList($triggers) + { + $list = array(); + foreach ($triggers as $value) { + if ($value = $this->_getPortableTriggerDefinition($value)) { + $list[] = $value; + } + } + + return $list; + } + + /** + * @param array $trigger + * + * @return mixed + */ + protected function _getPortableTriggerDefinition($trigger) + { + return $trigger; + } + + /** + * @param array $sequences + * + * @return array + */ + protected function _getPortableSequencesList($sequences) + { + $list = array(); + foreach ($sequences as $value) { + if ($value = $this->_getPortableSequenceDefinition($value)) { + $list[] = $value; + } + } + + return $list; + } + + /** + * @param array $sequence + * + * @return \Doctrine\DBAL\Schema\Sequence + * + * @throws \Doctrine\DBAL\DBALException + */ + protected function _getPortableSequenceDefinition($sequence) + { + throw DBALException::notSupported('Sequences'); + } + + /** + * Independent of the database the keys of the column list result are lowercased. + * + * The name of the created column instance however is kept in its case. + * + * @param string $table The name of the table. + * @param string $database + * @param array $tableColumns + * + * @return array + */ + protected function _getPortableTableColumnList($table, $database, $tableColumns) + { + $eventManager = $this->_platform->getEventManager(); + + $list = array(); + foreach ($tableColumns as $tableColumn) { + $column = null; + $defaultPrevented = false; + + if (null !== $eventManager && $eventManager->hasListeners(Events::onSchemaColumnDefinition)) { + $eventArgs = new SchemaColumnDefinitionEventArgs($tableColumn, $table, $database, $this->_conn); + $eventManager->dispatchEvent(Events::onSchemaColumnDefinition, $eventArgs); + + $defaultPrevented = $eventArgs->isDefaultPrevented(); + $column = $eventArgs->getColumn(); + } + + if ( ! $defaultPrevented) { + $column = $this->_getPortableTableColumnDefinition($tableColumn); + } + + if ($column) { + $name = strtolower($column->getQuotedName($this->_platform)); + $list[$name] = $column; + } + } + + return $list; + } + + /** + * Gets Table Column Definition. + * + * @param array $tableColumn + * + * @return \Doctrine\DBAL\Schema\Column + */ + abstract protected function _getPortableTableColumnDefinition($tableColumn); + + /** + * Aggregates and groups the index results according to the required data result. + * + * @param array $tableIndexRows + * @param string|null $tableName + * + * @return array + */ + protected function _getPortableTableIndexesList($tableIndexRows, $tableName=null) + { + $result = array(); + foreach($tableIndexRows as $tableIndex) { + $indexName = $keyName = $tableIndex['key_name']; + if($tableIndex['primary']) { + $keyName = 'primary'; + } + $keyName = strtolower($keyName); + + if(!isset($result[$keyName])) { + $result[$keyName] = array( + 'name' => $indexName, + 'columns' => array($tableIndex['column_name']), + 'unique' => $tableIndex['non_unique'] ? false : true, + 'primary' => $tableIndex['primary'], + 'flags' => isset($tableIndex['flags']) ? $tableIndex['flags'] : array(), + ); + } else { + $result[$keyName]['columns'][] = $tableIndex['column_name']; + } + } + + $eventManager = $this->_platform->getEventManager(); + + $indexes = array(); + foreach($result as $indexKey => $data) { + $index = null; + $defaultPrevented = false; + + if (null !== $eventManager && $eventManager->hasListeners(Events::onSchemaIndexDefinition)) { + $eventArgs = new SchemaIndexDefinitionEventArgs($data, $tableName, $this->_conn); + $eventManager->dispatchEvent(Events::onSchemaIndexDefinition, $eventArgs); + + $defaultPrevented = $eventArgs->isDefaultPrevented(); + $index = $eventArgs->getIndex(); + } + + if ( ! $defaultPrevented) { + $index = new Index($data['name'], $data['columns'], $data['unique'], $data['primary'], $data['flags']); + } + + if ($index) { + $indexes[$indexKey] = $index; + } + } + + return $indexes; + } + + /** + * @param array $tables + * + * @return array + */ + protected function _getPortableTablesList($tables) + { + $list = array(); + foreach ($tables as $value) { + if ($value = $this->_getPortableTableDefinition($value)) { + $list[] = $value; + } + } + + return $list; + } + + /** + * @param array $table + * + * @return array + */ + protected function _getPortableTableDefinition($table) + { + return $table; + } + + /** + * @param array $users + * + * @return array + */ + protected function _getPortableUsersList($users) + { + $list = array(); + foreach ($users as $value) { + if ($value = $this->_getPortableUserDefinition($value)) { + $list[] = $value; + } + } + + return $list; + } + + /** + * @param array $user + * + * @return mixed + */ + protected function _getPortableUserDefinition($user) + { + return $user; + } + + /** + * @param array $views + * @return array + */ + protected function _getPortableViewsList($views) + { + $list = array(); + foreach ($views as $value) { + if ($view = $this->_getPortableViewDefinition($value)) { + $viewName = strtolower($view->getQuotedName($this->_platform)); + $list[$viewName] = $view; + } + } + + return $list; + } + + /** + * @param array $view + * + * @return mixed + */ + protected function _getPortableViewDefinition($view) + { + return false; + } + + /** + * @param array $tableForeignKeys + * + * @return array + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $list = array(); + foreach ($tableForeignKeys as $value) { + if ($value = $this->_getPortableTableForeignKeyDefinition($value)) { + $list[] = $value; + } + } + + return $list; + } + + /** + * @param array $tableForeignKey + * + * @return mixed + */ + protected function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + return $tableForeignKey; + } + + /** + * @param array|string $sql + * + * @return void + */ + protected function _execSql($sql) + { + foreach ((array) $sql as $query) { + $this->_conn->executeUpdate($query); + } + } + + /** + * Creates a schema instance for the current database. + * + * @return \Doctrine\DBAL\Schema\Schema + */ + public function createSchema() + { + $sequences = array(); + if($this->_platform->supportsSequences()) { + $sequences = $this->listSequences(); + } + $tables = $this->listTables(); + + return new Schema($tables, $sequences, $this->createSchemaConfig()); + } + + /** + * Creates the configuration for this schema. + * + * @return \Doctrine\DBAL\Schema\SchemaConfig + */ + public function createSchemaConfig() + { + $schemaConfig = new SchemaConfig(); + $schemaConfig->setMaxIdentifierLength($this->_platform->getMaxIdentifierLength()); + + $searchPaths = $this->getSchemaSearchPaths(); + if (isset($searchPaths[0])) { + $schemaConfig->setName($searchPaths[0]); + } + + $params = $this->_conn->getParams(); + if (isset($params['defaultTableOptions'])) { + $schemaConfig->setDefaultTableOptions($params['defaultTableOptions']); + } + + return $schemaConfig; + } + + /** + * The search path for namespaces in the currently connected database. + * + * The first entry is usually the default namespace in the Schema. All + * further namespaces contain tables/sequences which can also be addressed + * with a short, not full-qualified name. + * + * For databases that don't support subschema/namespaces this method + * returns the name of the currently connected database. + * + * @return array + */ + public function getSchemaSearchPaths() + { + return array($this->_conn->getDatabase()); + } + + /** + * Given a table comment this method tries to extract a typehint for Doctrine Type, or returns + * the type given as default. + * + * @param string $comment + * @param string $currentType + * + * @return string + */ + public function extractDoctrineTypeFromComment($comment, $currentType) + { + if (preg_match("(\(DC2Type:([a-zA-Z0-9_]+)\))", $comment, $match)) { + $currentType = $match[1]; + } + + return $currentType; + } + + /** + * @param string $comment + * @param string $type + * + * @return string + */ + public function removeDoctrineTypeFromComment($comment, $type) + { + return str_replace('(DC2Type:'.$type.')', '', $comment); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Column.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Column.php new file mode 100644 index 0000000..c086c86 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Column.php @@ -0,0 +1,494 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Schema\Visitor\Visitor; + +/** + * Object representation of a database column. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class Column extends AbstractAsset +{ + /** + * @var \Doctrine\DBAL\Types\Type + */ + protected $_type; + + /** + * @var integer|null + */ + protected $_length = null; + + /** + * @var integer + */ + protected $_precision = 10; + + /** + * @var integer + */ + protected $_scale = 0; + + /** + * @var boolean + */ + protected $_unsigned = false; + + /** + * @var boolean + */ + protected $_fixed = false; + + /** + * @var boolean + */ + protected $_notnull = true; + + /** + * @var string|null + */ + protected $_default = null; + + /** + * @var boolean + */ + protected $_autoincrement = false; + + /** + * @var array + */ + protected $_platformOptions = array(); + + /** + * @var string|null + */ + protected $_columnDefinition = null; + + /** + * @var string|null + */ + protected $_comment = null; + + /** + * @var array + */ + protected $_customSchemaOptions = array(); + + /** + * Creates a new Column. + * + * @param string $columnName + * @param \Doctrine\DBAL\Types\Type $type + * @param array $options + */ + public function __construct($columnName, Type $type, array $options=array()) + { + $this->_setName($columnName); + $this->setType($type); + $this->setOptions($options); + } + + /** + * @param array $options + * + * @return \Doctrine\DBAL\Schema\Column + */ + public function setOptions(array $options) + { + foreach ($options as $name => $value) { + $method = "set".$name; + if (method_exists($this, $method)) { + $this->$method($value); + } + } + + return $this; + } + + /** + * @param \Doctrine\DBAL\Types\Type $type + * + * @return \Doctrine\DBAL\Schema\Column + */ + public function setType(Type $type) + { + $this->_type = $type; + return $this; + } + + /** + * @param integer|null $length + * + * @return \Doctrine\DBAL\Schema\Column + */ + public function setLength($length) + { + if($length !== null) { + $this->_length = (int)$length; + } else { + $this->_length = null; + } + + return $this; + } + + /** + * @param integer $precision + * + * @return \Doctrine\DBAL\Schema\Column + */ + public function setPrecision($precision) + { + if (!is_numeric($precision)) { + $precision = 10; // defaults to 10 when no valid precision is given. + } + + $this->_precision = (int)$precision; + + return $this; + } + + /** + * @param integer $scale + * + * @return \Doctrine\DBAL\Schema\Column + */ + public function setScale($scale) + { + if (!is_numeric($scale)) { + $scale = 0; + } + + $this->_scale = (int)$scale; + + return $this; + } + + /** + * @param boolean $unsigned + * + * @return \Doctrine\DBAL\Schema\Column + */ + public function setUnsigned($unsigned) + { + $this->_unsigned = (bool)$unsigned; + + return $this; + } + + /** + * @param boolean $fixed + * + * @return \Doctrine\DBAL\Schema\Column + */ + public function setFixed($fixed) + { + $this->_fixed = (bool)$fixed; + + return $this; + } + + /** + * @param boolean $notnull + * + * @return \Doctrine\DBAL\Schema\Column + */ + public function setNotnull($notnull) + { + $this->_notnull = (bool)$notnull; + + return $this; + } + + /** + * @param mixed $default + * + * @return \Doctrine\DBAL\Schema\Column + */ + public function setDefault($default) + { + $this->_default = $default; + + return $this; + } + + /** + * @param array $platformOptions + * + * @return \Doctrine\DBAL\Schema\Column + */ + public function setPlatformOptions(array $platformOptions) + { + $this->_platformOptions = $platformOptions; + + return $this; + } + + /** + * @param string $name + * @param mixed $value + * + * @return \Doctrine\DBAL\Schema\Column + */ + public function setPlatformOption($name, $value) + { + $this->_platformOptions[$name] = $value; + + return $this; + } + + /** + * @param string $value + * + * @return \Doctrine\DBAL\Schema\Column + */ + public function setColumnDefinition($value) + { + $this->_columnDefinition = $value; + + return $this; + } + + /** + * @return \Doctrine\DBAL\Types\Type + */ + public function getType() + { + return $this->_type; + } + + /** + * @return integer|null + */ + public function getLength() + { + return $this->_length; + } + + /** + * @return integer + */ + public function getPrecision() + { + return $this->_precision; + } + + /** + * @return integer + */ + public function getScale() + { + return $this->_scale; + } + + /** + * @return boolean + */ + public function getUnsigned() + { + return $this->_unsigned; + } + + /** + * @return boolean + */ + public function getFixed() + { + return $this->_fixed; + } + + /** + * @return boolean + */ + public function getNotnull() + { + return $this->_notnull; + } + + /** + * @return string|null + */ + public function getDefault() + { + return $this->_default; + } + + /** + * @return array + */ + public function getPlatformOptions() + { + return $this->_platformOptions; + } + + /** + * @param string $name + * + * @return boolean + */ + public function hasPlatformOption($name) + { + return isset($this->_platformOptions[$name]); + } + + /** + * @param string $name + * + * @return mixed + */ + public function getPlatformOption($name) + { + return $this->_platformOptions[$name]; + } + + /** + * @return string|null + */ + public function getColumnDefinition() + { + return $this->_columnDefinition; + } + + /** + * @return boolean + */ + public function getAutoincrement() + { + return $this->_autoincrement; + } + + /** + * @param boolean $flag + * + * @return \Doctrine\DBAL\Schema\Column + */ + public function setAutoincrement($flag) + { + $this->_autoincrement = $flag; + return $this; + } + + /** + * @param string $comment + * + * @return \Doctrine\DBAL\Schema\Column + */ + public function setComment($comment) + { + $this->_comment = $comment; + + return $this; + } + + /** + * @return string|null + */ + public function getComment() + { + return $this->_comment; + } + + /** + * @param string $name + * @param mixed $value + * + * @return \Doctrine\DBAL\Schema\Column + */ + public function setCustomSchemaOption($name, $value) + { + $this->_customSchemaOptions[$name] = $value; + + return $this; + } + + /** + * @param string $name + * + * @return boolean + */ + public function hasCustomSchemaOption($name) + { + return isset($this->_customSchemaOptions[$name]); + } + + /** + * @param string $name + * + * @return mixed + */ + public function getCustomSchemaOption($name) + { + return $this->_customSchemaOptions[$name]; + } + + /** + * @param array $customSchemaOptions + * + * @return \Doctrine\DBAL\Schema\Column + */ + public function setCustomSchemaOptions(array $customSchemaOptions) + { + $this->_customSchemaOptions = $customSchemaOptions; + + return $this; + } + + /** + * @return array + */ + public function getCustomSchemaOptions() + { + return $this->_customSchemaOptions; + } + + /** + * @param \Doctrine\DBAL\Schema\Visitor\Visitor $visitor + */ + public function visit(Visitor $visitor) + { + $visitor->accept($this); + } + + /** + * @return array + */ + public function toArray() + { + return array_merge(array( + 'name' => $this->_name, + 'type' => $this->_type, + 'default' => $this->_default, + 'notnull' => $this->_notnull, + 'length' => $this->_length, + 'precision' => $this->_precision, + 'scale' => $this->_scale, + 'fixed' => $this->_fixed, + 'unsigned' => $this->_unsigned, + 'autoincrement' => $this->_autoincrement, + 'columnDefinition' => $this->_columnDefinition, + 'comment' => $this->_comment, + ), $this->_platformOptions, $this->_customSchemaOptions); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/ColumnDiff.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/ColumnDiff.php new file mode 100644 index 0000000..18068b0 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/ColumnDiff.php @@ -0,0 +1,74 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +/** + * Represents the change of a column. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class ColumnDiff +{ + /** + * @var string + */ + public $oldColumnName; + + /** + * @var \Doctrine\DBAL\Schema\Column + */ + public $column; + + /** + * @var array + */ + public $changedProperties = array(); + + /** + * @var \Doctrine\DBAL\Schema\Column + */ + public $fromColumn; + + /** + * @param string $oldColumnName + * @param \Doctrine\DBAL\Schema\Column $column + * @param array $changedProperties + * @param \Doctrine\DBAL\Schema\Column $fromColumn + */ + public function __construct($oldColumnName, Column $column, array $changedProperties = array(), Column $fromColumn = null) + { + $this->oldColumnName = $oldColumnName; + $this->column = $column; + $this->changedProperties = $changedProperties; + $this->fromColumn = $fromColumn; + } + + /** + * @param string $propertyName + * + * @return boolean + */ + public function hasChanged($propertyName) + { + return in_array($propertyName, $this->changedProperties); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Comparator.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Comparator.php new file mode 100644 index 0000000..839108f --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Comparator.php @@ -0,0 +1,446 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +/** + * Compares two Schemas and return an instance of SchemaDiff. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class Comparator +{ + /** + * @param \Doctrine\DBAL\Schema\Schema $fromSchema + * @param \Doctrine\DBAL\Schema\Schema $toSchema + * + * @return \Doctrine\DBAL\Schema\SchemaDiff + */ + static public function compareSchemas(Schema $fromSchema, Schema $toSchema) + { + $c = new self(); + + return $c->compare($fromSchema, $toSchema); + } + + /** + * Returns a SchemaDiff object containing the differences between the schemas $fromSchema and $toSchema. + * + * The returned differences are returned in such a way that they contain the + * operations to change the schema stored in $fromSchema to the schema that is + * stored in $toSchema. + * + * @param \Doctrine\DBAL\Schema\Schema $fromSchema + * @param \Doctrine\DBAL\Schema\Schema $toSchema + * + * @return \Doctrine\DBAL\Schema\SchemaDiff + */ + public function compare(Schema $fromSchema, Schema $toSchema) + { + $diff = new SchemaDiff(); + $diff->fromSchema = $fromSchema; + + $foreignKeysToTable = array(); + + foreach ( $toSchema->getTables() as $table ) { + $tableName = $table->getShortestName($toSchema->getName()); + if ( ! $fromSchema->hasTable($tableName)) { + $diff->newTables[$tableName] = $toSchema->getTable($tableName); + } else { + $tableDifferences = $this->diffTable($fromSchema->getTable($tableName), $toSchema->getTable($tableName)); + if ($tableDifferences !== false) { + $diff->changedTables[$tableName] = $tableDifferences; + } + } + } + + /* Check if there are tables removed */ + foreach ($fromSchema->getTables() as $table) { + $tableName = $table->getShortestName($fromSchema->getName()); + + $table = $fromSchema->getTable($tableName); + if ( ! $toSchema->hasTable($tableName) ) { + $diff->removedTables[$tableName] = $table; + } + + // also remember all foreign keys that point to a specific table + foreach ($table->getForeignKeys() as $foreignKey) { + $foreignTable = strtolower($foreignKey->getForeignTableName()); + if (!isset($foreignKeysToTable[$foreignTable])) { + $foreignKeysToTable[$foreignTable] = array(); + } + $foreignKeysToTable[$foreignTable][] = $foreignKey; + } + } + + foreach ($diff->removedTables as $tableName => $table) { + if (isset($foreignKeysToTable[$tableName])) { + $diff->orphanedForeignKeys = array_merge($diff->orphanedForeignKeys, $foreignKeysToTable[$tableName]); + + // deleting duplicated foreign keys present on both on the orphanedForeignKey + // and the removedForeignKeys from changedTables + foreach ($foreignKeysToTable[$tableName] as $foreignKey) { + // strtolower the table name to make if compatible with getShortestName + $localTableName = strtolower($foreignKey->getLocalTableName()); + if (isset($diff->changedTables[$localTableName])) { + foreach ($diff->changedTables[$localTableName]->removedForeignKeys as $key => $removedForeignKey) { + unset($diff->changedTables[$localTableName]->removedForeignKeys[$key]); + } + } + } + } + } + + foreach ($toSchema->getSequences() as $sequence) { + $sequenceName = $sequence->getShortestName($toSchema->getName()); + if ( ! $fromSchema->hasSequence($sequenceName)) { + $diff->newSequences[] = $sequence; + } else { + if ($this->diffSequence($sequence, $fromSchema->getSequence($sequenceName))) { + $diff->changedSequences[] = $toSchema->getSequence($sequenceName); + } + } + } + + foreach ($fromSchema->getSequences() as $sequence) { + if ($this->isAutoIncrementSequenceInSchema($toSchema, $sequence)) { + continue; + } + + $sequenceName = $sequence->getShortestName($fromSchema->getName()); + + if ( ! $toSchema->hasSequence($sequenceName)) { + $diff->removedSequences[] = $sequence; + } + } + + return $diff; + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $schema + * @param \Doctrine\DBAL\Schema\Sequence $sequence + * + * @return boolean + */ + private function isAutoIncrementSequenceInSchema($schema, $sequence) + { + foreach ($schema->getTables() as $table) { + if ($sequence->isAutoIncrementsFor($table)) { + return true; + } + } + + return false; + } + + /** + * @param \Doctrine\DBAL\Schema\Sequence $sequence1 + * @param \Doctrine\DBAL\Schema\Sequence $sequence2 + * + * @return boolean + */ + public function diffSequence(Sequence $sequence1, Sequence $sequence2) + { + if($sequence1->getAllocationSize() != $sequence2->getAllocationSize()) { + return true; + } + + if($sequence1->getInitialValue() != $sequence2->getInitialValue()) { + return true; + } + + return false; + } + + /** + * Returns the difference between the tables $table1 and $table2. + * + * If there are no differences this method returns the boolean false. + * + * @param \Doctrine\DBAL\Schema\Table $table1 + * @param \Doctrine\DBAL\Schema\Table $table2 + * + * @return boolean|\Doctrine\DBAL\Schema\TableDiff + */ + public function diffTable(Table $table1, Table $table2) + { + $changes = 0; + $tableDifferences = new TableDiff($table1->getName()); + $tableDifferences->fromTable = $table1; + + $table1Columns = $table1->getColumns(); + $table2Columns = $table2->getColumns(); + + /* See if all the fields in table 1 exist in table 2 */ + foreach ( $table2Columns as $columnName => $column ) { + if ( !$table1->hasColumn($columnName) ) { + $tableDifferences->addedColumns[$columnName] = $column; + $changes++; + } + } + /* See if there are any removed fields in table 2 */ + foreach ( $table1Columns as $columnName => $column ) { + if ( !$table2->hasColumn($columnName) ) { + $tableDifferences->removedColumns[$columnName] = $column; + $changes++; + } + } + + foreach ( $table1Columns as $columnName => $column ) { + if ( $table2->hasColumn($columnName) ) { + $changedProperties = $this->diffColumn( $column, $table2->getColumn($columnName) ); + if (count($changedProperties) ) { + $columnDiff = new ColumnDiff($column->getName(), $table2->getColumn($columnName), $changedProperties); + $columnDiff->fromColumn = $column; + $tableDifferences->changedColumns[$column->getName()] = $columnDiff; + $changes++; + } + } + } + + $this->detectColumnRenamings($tableDifferences); + + $table1Indexes = $table1->getIndexes(); + $table2Indexes = $table2->getIndexes(); + + foreach ($table2Indexes as $index2Name => $index2Definition) { + foreach ($table1Indexes as $index1Name => $index1Definition) { + if ($this->diffIndex($index1Definition, $index2Definition) === false) { + unset($table1Indexes[$index1Name]); + unset($table2Indexes[$index2Name]); + } else { + if ($index1Name == $index2Name) { + $tableDifferences->changedIndexes[$index2Name] = $table2Indexes[$index2Name]; + unset($table1Indexes[$index1Name]); + unset($table2Indexes[$index2Name]); + $changes++; + } + } + } + } + + foreach ($table1Indexes as $index1Name => $index1Definition) { + $tableDifferences->removedIndexes[$index1Name] = $index1Definition; + $changes++; + } + + foreach ($table2Indexes as $index2Name => $index2Definition) { + $tableDifferences->addedIndexes[$index2Name] = $index2Definition; + $changes++; + } + + $fromFkeys = $table1->getForeignKeys(); + $toFkeys = $table2->getForeignKeys(); + + foreach ($fromFkeys as $key1 => $constraint1) { + foreach ($toFkeys as $key2 => $constraint2) { + if($this->diffForeignKey($constraint1, $constraint2) === false) { + unset($fromFkeys[$key1]); + unset($toFkeys[$key2]); + } else { + if (strtolower($constraint1->getName()) == strtolower($constraint2->getName())) { + $tableDifferences->changedForeignKeys[] = $constraint2; + $changes++; + unset($fromFkeys[$key1]); + unset($toFkeys[$key2]); + } + } + } + } + + foreach ($fromFkeys as $constraint1) { + $tableDifferences->removedForeignKeys[] = $constraint1; + $changes++; + } + + foreach ($toFkeys as $constraint2) { + $tableDifferences->addedForeignKeys[] = $constraint2; + $changes++; + } + + return $changes ? $tableDifferences : false; + } + + /** + * Try to find columns that only changed their name, rename operations maybe cheaper than add/drop + * however ambiguities between different possibilities should not lead to renaming at all. + * + * @param \Doctrine\DBAL\Schema\TableDiff $tableDifferences + * + * @return void + */ + private function detectColumnRenamings(TableDiff $tableDifferences) + { + $renameCandidates = array(); + foreach ($tableDifferences->addedColumns as $addedColumnName => $addedColumn) { + foreach ($tableDifferences->removedColumns as $removedColumn) { + if (count($this->diffColumn($addedColumn, $removedColumn)) == 0) { + $renameCandidates[$addedColumn->getName()][] = array($removedColumn, $addedColumn, $addedColumnName); + } + } + } + + foreach ($renameCandidates as $candidateColumns) { + if (count($candidateColumns) == 1) { + list($removedColumn, $addedColumn) = $candidateColumns[0]; + $removedColumnName = strtolower($removedColumn->getName()); + $addedColumnName = strtolower($addedColumn->getName()); + + if ( ! isset($tableDifferences->renamedColumns[$removedColumnName])) { + $tableDifferences->renamedColumns[$removedColumnName] = $addedColumn; + unset($tableDifferences->addedColumns[$addedColumnName]); + unset($tableDifferences->removedColumns[$removedColumnName]); + } + } + } + } + + /** + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $key1 + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $key2 + * + * @return boolean + */ + public function diffForeignKey(ForeignKeyConstraint $key1, ForeignKeyConstraint $key2) + { + if (array_map('strtolower', $key1->getLocalColumns()) != array_map('strtolower', $key2->getLocalColumns())) { + return true; + } + + if (array_map('strtolower', $key1->getForeignColumns()) != array_map('strtolower', $key2->getForeignColumns())) { + return true; + } + + if ($key1->getUnqualifiedForeignTableName() !== $key2->getUnqualifiedForeignTableName()) { + return true; + } + + if ($key1->onUpdate() != $key2->onUpdate()) { + return true; + } + + if ($key1->onDelete() != $key2->onDelete()) { + return true; + } + + return false; + } + + /** + * Returns the difference between the fields $field1 and $field2. + * + * If there are differences this method returns $field2, otherwise the + * boolean false. + * + * @param \Doctrine\DBAL\Schema\Column $column1 + * @param \Doctrine\DBAL\Schema\Column $column2 + * + * @return array + */ + public function diffColumn(Column $column1, Column $column2) + { + $changedProperties = array(); + if ( $column1->getType() != $column2->getType() ) { + $changedProperties[] = 'type'; + } + + if ($column1->getNotnull() != $column2->getNotnull()) { + $changedProperties[] = 'notnull'; + } + + if ($column1->getDefault() != $column2->getDefault()) { + $changedProperties[] = 'default'; + } + + if ($column1->getUnsigned() != $column2->getUnsigned()) { + $changedProperties[] = 'unsigned'; + } + + if ($column1->getType() instanceof \Doctrine\DBAL\Types\StringType) { + // check if value of length is set at all, default value assumed otherwise. + $length1 = $column1->getLength() ?: 255; + $length2 = $column2->getLength() ?: 255; + if ($length1 != $length2) { + $changedProperties[] = 'length'; + } + + if ($column1->getFixed() != $column2->getFixed()) { + $changedProperties[] = 'fixed'; + } + } + + if ($column1->getType() instanceof \Doctrine\DBAL\Types\DecimalType) { + if (($column1->getPrecision()?:10) != ($column2->getPrecision()?:10)) { + $changedProperties[] = 'precision'; + } + if ($column1->getScale() != $column2->getScale()) { + $changedProperties[] = 'scale'; + } + } + + if ($column1->getAutoincrement() != $column2->getAutoincrement()) { + $changedProperties[] = 'autoincrement'; + } + + // only allow to delete comment if its set to '' not to null. + if ($column1->getComment() !== null && $column1->getComment() != $column2->getComment()) { + $changedProperties[] = 'comment'; + } + + $options1 = $column1->getCustomSchemaOptions(); + $options2 = $column2->getCustomSchemaOptions(); + + $commonKeys = array_keys(array_intersect_key($options1, $options2)); + + foreach ($commonKeys as $key) { + if ($options1[$key] !== $options2[$key]) { + $changedProperties[] = $key; + } + } + + $diffKeys = array_keys(array_diff_key($options1, $options2) + array_diff_key($options2, $options1)); + + $changedProperties = array_merge($changedProperties, $diffKeys); + + return $changedProperties; + } + + /** + * Finds the difference between the indexes $index1 and $index2. + * + * Compares $index1 with $index2 and returns $index2 if there are any + * differences or false in case there are no differences. + * + * @param \Doctrine\DBAL\Schema\Index $index1 + * @param \Doctrine\DBAL\Schema\Index $index2 + * + * @return boolean + */ + public function diffIndex(Index $index1, Index $index2) + { + if ($index1->isFullfilledBy($index2) && $index2->isFullfilledBy($index1)) { + return false; + } + + return true; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Constraint.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Constraint.php new file mode 100644 index 0000000..c373472 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Constraint.php @@ -0,0 +1,66 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Marker interface for contraints. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +interface Constraint +{ + /** + * @return string + */ + public function getName(); + + /** + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return string + */ + public function getQuotedName(AbstractPlatform $platform); + + /** + * Returns the names of the referencing table columns + * the constraint is associated with. + * + * @return array + */ + public function getColumns(); + + /** + * Returns the quoted representation of the column names + * the constraint is associated with. + * + * But only if they were defined with one or a column name + * is a keyword reserved by the platform. + * Otherwise the plain unquoted value as inserted is returned. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The platform to use for quotation. + * + * @return array + */ + public function getQuotedColumns(AbstractPlatform $platform); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/DB2SchemaManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/DB2SchemaManager.php new file mode 100644 index 0000000..e262544 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/DB2SchemaManager.php @@ -0,0 +1,216 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs; +use Doctrine\DBAL\Events; + +/** + * IBM Db2 Schema Manager. + * + * @link www.doctrine-project.org + * @since 1.0 + * @author Benjamin Eberlei + */ +class DB2SchemaManager extends AbstractSchemaManager +{ + /** + * {@inheritdoc} + * + * Apparently creator is the schema not the user who created it: + * {@link http://publib.boulder.ibm.com/infocenter/dzichelp/v2r2/index.jsp?topic=/com.ibm.db29.doc.sqlref/db2z_sysibmsystablestable.htm} + */ + public function listTableNames() + { + $sql = $this->_platform->getListTablesSQL(); + $sql .= " AND CREATOR = UPPER('".$this->_conn->getUsername()."')"; + + $tables = $this->_conn->fetchAll($sql); + + return $this->_getPortableTablesList($tables); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $tableColumn = array_change_key_case($tableColumn, \CASE_LOWER); + + $length = null; + $fixed = null; + $unsigned = false; + $scale = false; + $precision = false; + + $type = $this->_platform->getDoctrineTypeMapping($tableColumn['typename']); + + switch (strtolower($tableColumn['typename'])) { + case 'varchar': + $length = $tableColumn['length']; + $fixed = false; + break; + case 'character': + $length = $tableColumn['length']; + $fixed = true; + break; + case 'clob': + $length = $tableColumn['length']; + break; + case 'decimal': + case 'double': + case 'real': + $scale = $tableColumn['scale']; + $precision = $tableColumn['length']; + break; + } + + $options = array( + 'length' => $length, + 'unsigned' => (bool)$unsigned, + 'fixed' => (bool)$fixed, + 'default' => ($tableColumn['default'] == "NULL") ? null : $tableColumn['default'], + 'notnull' => (bool) ($tableColumn['nulls'] == 'N'), + 'scale' => null, + 'precision' => null, + 'platformOptions' => array(), + ); + + if ($scale !== null && $precision !== null) { + $options['scale'] = $scale; + $options['precision'] = $precision; + } + + return new Column($tableColumn['colname'], \Doctrine\DBAL\Types\Type::getType($type), $options); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTablesList($tables) + { + $tableNames = array(); + foreach ($tables as $tableRow) { + $tableRow = array_change_key_case($tableRow, \CASE_LOWER); + $tableNames[] = $tableRow['name']; + } + + return $tableNames; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName=null) + { + $eventManager = $this->_platform->getEventManager(); + + $indexes = array(); + foreach($tableIndexes as $indexKey => $data) { + $data = array_change_key_case($data, \CASE_LOWER); + $unique = ($data['uniquerule'] == "D") ? false : true; + $primary = ($data['uniquerule'] == "P"); + + $indexName = strtolower($data['name']); + + $data = array( + 'name' => $indexName, + 'columns' => explode("+", ltrim($data['colnames'], '+')), + 'unique' => $unique, + 'primary' => $primary + ); + + $index = null; + $defaultPrevented = false; + + if (null !== $eventManager && $eventManager->hasListeners(Events::onSchemaIndexDefinition)) { + $eventArgs = new SchemaIndexDefinitionEventArgs($data, $tableName, $this->_conn); + $eventManager->dispatchEvent(Events::onSchemaIndexDefinition, $eventArgs); + + $defaultPrevented = $eventArgs->isDefaultPrevented(); + $index = $eventArgs->getIndex(); + } + + if ( ! $defaultPrevented) { + $index = new Index($data['name'], $data['columns'], $data['unique'], $data['primary']); + } + + if ($index) { + $indexes[$indexKey] = $index; + } + } + + return $indexes; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + $tableForeignKey = array_change_key_case($tableForeignKey, CASE_LOWER); + + $tableForeignKey['deleterule'] = $this->_getPortableForeignKeyRuleDef($tableForeignKey['deleterule']); + $tableForeignKey['updaterule'] = $this->_getPortableForeignKeyRuleDef($tableForeignKey['updaterule']); + + return new ForeignKeyConstraint( + array_map('trim', (array)$tableForeignKey['fkcolnames']), + $tableForeignKey['reftbname'], + array_map('trim', (array)$tableForeignKey['pkcolnames']), + $tableForeignKey['relname'], + array( + 'onUpdate' => $tableForeignKey['updaterule'], + 'onDelete' => $tableForeignKey['deleterule'], + ) + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableForeignKeyRuleDef($def) + { + if ($def == "C") { + return "CASCADE"; + } else if ($def == "N") { + return "SET NULL"; + } + + return null; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableViewDefinition($view) + { + $view = array_change_key_case($view, \CASE_LOWER); + // sadly this still segfaults on PDO_IBM, see http://pecl.php.net/bugs/bug.php?id=17199 + //$view['text'] = (is_resource($view['text']) ? stream_get_contents($view['text']) : $view['text']); + if (!is_resource($view['text'])) { + $pos = strpos($view['text'], ' AS '); + $sql = substr($view['text'], $pos+4); + } else { + $sql = ''; + } + + return new View($view['name'], $sql); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/DrizzleSchemaManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/DrizzleSchemaManager.php new file mode 100644 index 0000000..cffd40e --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/DrizzleSchemaManager.php @@ -0,0 +1,110 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +/** + * Schema manager for the Drizzle RDBMS. + * + * @author Kim Hemsø Rasmussen + */ +class DrizzleSchemaManager extends AbstractSchemaManager +{ + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $tableName = $tableColumn['COLUMN_NAME']; + $dbType = strtolower($tableColumn['DATA_TYPE']); + + $type = $this->_platform->getDoctrineTypeMapping($dbType); + $type = $this->extractDoctrineTypeFromComment($tableColumn['COLUMN_COMMENT'], $type); + $tableColumn['COLUMN_COMMENT'] = $this->removeDoctrineTypeFromComment($tableColumn['COLUMN_COMMENT'], $type); + + $options = array( + 'notnull' => !(bool)$tableColumn['IS_NULLABLE'], + 'length' => (int)$tableColumn['CHARACTER_MAXIMUM_LENGTH'], + 'default' => empty($tableColumn['COLUMN_DEFAULT']) ? null : $tableColumn['COLUMN_DEFAULT'], + 'autoincrement' => (bool)$tableColumn['IS_AUTO_INCREMENT'], + 'scale' => (int)$tableColumn['NUMERIC_SCALE'], + 'precision' => (int)$tableColumn['NUMERIC_PRECISION'], + 'comment' => (isset($tableColumn['COLUMN_COMMENT']) ? $tableColumn['COLUMN_COMMENT'] : null), + ); + + return new Column($tableName, \Doctrine\DBAL\Types\Type::getType($type), $options); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableDatabaseDefinition($database) + { + return $database['SCHEMA_NAME']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableDefinition($table) + { + return $table['TABLE_NAME']; + } + + /** + * {@inheritdoc} + */ + public function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + $columns = array(); + foreach (explode(',', $tableForeignKey['CONSTRAINT_COLUMNS']) as $value) { + $columns[] = trim($value, ' `'); + } + + $ref_columns = array(); + foreach (explode(',', $tableForeignKey['REFERENCED_TABLE_COLUMNS']) as $value) { + $ref_columns[] = trim($value, ' `'); + } + + return new ForeignKeyConstraint( + $columns, + $tableForeignKey['REFERENCED_TABLE_NAME'], + $ref_columns, + $tableForeignKey['CONSTRAINT_NAME'], + array( + 'onUpdate' => $tableForeignKey['UPDATE_RULE'], + 'onDelete' => $tableForeignKey['DELETE_RULE'], + ) + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName = null) + { + $indexes = array(); + foreach ($tableIndexes as $k) { + $k['primary'] = (boolean)$k['primary']; + $indexes[] = $k; + } + + return parent::_getPortableTableIndexesList($indexes, $tableName); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/ForeignKeyConstraint.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/ForeignKeyConstraint.php new file mode 100644 index 0000000..9eac562 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/ForeignKeyConstraint.php @@ -0,0 +1,346 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * An abstraction class for a foreign key constraint. + * + * @author Benjamin Eberlei + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.0 + */ +class ForeignKeyConstraint extends AbstractAsset implements Constraint +{ + /** + * Instance of the referencing table the foreign key constraint is associated with. + * + * @var \Doctrine\DBAL\Schema\Table + */ + protected $_localTable; + + /** + * Asset identifier instances of the referencing table column names the foreign key constraint is associated with. + * array($columnName => Identifier) + * + * @var Identifier[] + */ + protected $_localColumnNames; + + /** + * Table or asset identifier instance of the referenced table name the foreign key constraint is associated with. + * + * @var Table|Identifier + */ + protected $_foreignTableName; + + /** + * Asset identifier instances of the referenced table column names the foreign key constraint is associated with. + * array($columnName => Identifier) + * + * @var Identifier[] + */ + protected $_foreignColumnNames; + + /** + * @var array Options associated with the foreign key constraint. + */ + protected $_options; + + /** + * Initializes the foreign key constraint. + * + * @param array $localColumnNames Names of the referencing table columns. + * @param Table|string $foreignTableName Referenced table. + * @param array $foreignColumnNames Names of the referenced table columns. + * @param string|null $name Name of the foreign key constraint. + * @param array $options Options associated with the foreign key constraint. + */ + public function __construct(array $localColumnNames, $foreignTableName, array $foreignColumnNames, $name = null, array $options = array()) + { + $this->_setName($name); + $identifierConstructorCallback = function ($column) { + return new Identifier($column); + }; + $this->_localColumnNames = $localColumnNames + ? array_combine($localColumnNames, array_map($identifierConstructorCallback, $localColumnNames)) + : array(); + + if ($foreignTableName instanceof Table) { + $this->_foreignTableName = $foreignTableName; + } else { + $this->_foreignTableName = new Identifier($foreignTableName); + } + + $this->_foreignColumnNames = $foreignColumnNames + ? array_combine($foreignColumnNames, array_map($identifierConstructorCallback, $foreignColumnNames)) + : array(); + $this->_options = $options; + } + + /** + * Returns the name of the referencing table + * the foreign key constraint is associated with. + * + * @return string + */ + public function getLocalTableName() + { + return $this->_localTable->getName(); + } + + /** + * Sets the Table instance of the referencing table + * the foreign key constraint is associated with. + * + * @param \Doctrine\DBAL\Schema\Table $table Instance of the referencing table. + * + * @return void + */ + public function setLocalTable(Table $table) + { + $this->_localTable = $table; + } + + /** + * @return Table + */ + public function getLocalTable() + { + return $this->_localTable; + } + + /** + * Returns the names of the referencing table columns + * the foreign key constraint is associated with. + * + * @return array + */ + public function getLocalColumns() + { + return array_keys($this->_localColumnNames); + } + + /** + * Returns the quoted representation of the referencing table column names + * the foreign key constraint is associated with. + * + * But only if they were defined with one or the referencing table column name + * is a keyword reserved by the platform. + * Otherwise the plain unquoted value as inserted is returned. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The platform to use for quotation. + * + * @return array + */ + public function getQuotedLocalColumns(AbstractPlatform $platform) + { + $columns = array(); + + foreach ($this->_localColumnNames as $column) { + $columns[] = $column->getQuotedName($platform); + } + + return $columns; + } + + /** + * {@inheritdoc} + * + * @see getLocalColumns + */ + public function getColumns() + { + return $this->getLocalColumns(); + } + + /** + * Returns the quoted representation of the referencing table column names + * the foreign key constraint is associated with. + * + * But only if they were defined with one or the referencing table column name + * is a keyword reserved by the platform. + * Otherwise the plain unquoted value as inserted is returned. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The platform to use for quotation. + * + * @see getQuotedLocalColumns + * + * @return array + */ + public function getQuotedColumns(AbstractPlatform $platform) + { + return $this->getQuotedLocalColumns($platform); + } + + /** + * Returns the name of the referenced table + * the foreign key constraint is associated with. + * + * @return string + */ + public function getForeignTableName() + { + return $this->_foreignTableName->getName(); + } + + /** + * Returns the non-schema qualified foreign table name. + * + * @return string + */ + public function getUnqualifiedForeignTableName() + { + $parts = explode(".", $this->_foreignTableName->getName()); + return strtolower(end($parts)); + } + + /** + * Returns the quoted representation of the referenced table name + * the foreign key constraint is associated with. + * + * But only if it was defined with one or the referenced table name + * is a keyword reserved by the platform. + * Otherwise the plain unquoted value as inserted is returned. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The platform to use for quotation. + * + * @return string + */ + public function getQuotedForeignTableName(AbstractPlatform $platform) + { + return $this->_foreignTableName->getQuotedName($platform); + } + + /** + * Returns the names of the referenced table columns + * the foreign key constraint is associated with. + * + * @return array + */ + public function getForeignColumns() + { + return array_keys($this->_foreignColumnNames); + } + + /** + * Returns the quoted representation of the referenced table column names + * the foreign key constraint is associated with. + * + * But only if they were defined with one or the referenced table column name + * is a keyword reserved by the platform. + * Otherwise the plain unquoted value as inserted is returned. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The platform to use for quotation. + * + * @return array + */ + public function getQuotedForeignColumns(AbstractPlatform $platform) + { + $columns = array(); + + foreach ($this->_foreignColumnNames as $column) { + $columns[] = $column->getQuotedName($platform); + } + + return $columns; + } + + /** + * Returns whether or not a given option + * is associated with the foreign key constraint. + * + * @param string $name Name of the option to check. + * + * @return boolean + */ + public function hasOption($name) + { + return isset($this->_options[$name]); + } + + /** + * Returns an option associated with the foreign key constraint. + * + * @param string $name Name of the option the foreign key constraint is associated with. + * + * @return mixed + */ + public function getOption($name) + { + return $this->_options[$name]; + } + + /** + * Returns the options associated with the foreign key constraint. + * + * @return array + */ + public function getOptions() + { + return $this->_options; + } + + /** + * Returns the referential action for UPDATE operations + * on the referenced table the foreign key constraint is associated with. + * + * @return string|null + */ + public function onUpdate() + { + return $this->onEvent('onUpdate'); + } + + /** + * Returns the referential action for DELETE operations + * on the referenced table the foreign key constraint is associated with. + * + * @return string|null + */ + public function onDelete() + { + return $this->onEvent('onDelete'); + } + + /** + * Returns the referential action for a given database operation + * on the referenced table the foreign key constraint is associated with. + * + * @param string $event Name of the database operation/event to return the referential action for. + * + * @return string|null + */ + private function onEvent($event) + { + if (isset($this->_options[$event])) { + $onEvent = strtoupper($this->_options[$event]); + + if ( ! in_array($onEvent, array('NO ACTION', 'RESTRICT'))) { + return $onEvent; + } + } + + return false; + } +} + diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Identifier.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Identifier.php new file mode 100644 index 0000000..e50e767 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Identifier.php @@ -0,0 +1,45 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Schema\AbstractAsset; + +/** + * An abstraction class for an asset identifier. + * + * Wraps identifier names like column names in indexes / foreign keys + * in an abstract class for proper quotation capabilities. + * + * @author Steve Müller + * @link www.doctrine-project.org + * @since 2.4 + */ +class Identifier extends AbstractAsset +{ + /** + * Constructor. + * + * @param string $identifier Identifier name to wrap. + */ + public function __construct($identifier) + { + $this->_setName($identifier); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Index.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Index.php new file mode 100644 index 0000000..7efc201 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Index.php @@ -0,0 +1,290 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +class Index extends AbstractAsset implements Constraint +{ + /** + * Asset identifier instances of the column names the index is associated with. + * array($columnName => Identifier) + * + * @var Identifier[] + */ + protected $_columns; + + /** + * @var boolean + */ + protected $_isUnique = false; + + /** + * @var boolean + */ + protected $_isPrimary = false; + + /** + * Platform specific flags for indexes. + * + * @var array + */ + protected $_flags = array(); + + /** + * @param string $indexName + * @param array $columns + * @param boolean $isUnique + * @param boolean $isPrimary + * @param array $flags + */ + public function __construct($indexName, array $columns, $isUnique = false, $isPrimary = false, array $flags = array()) + { + $isUnique = ($isPrimary)?true:$isUnique; + + $this->_setName($indexName); + $this->_isUnique = $isUnique; + $this->_isPrimary = $isPrimary; + + foreach ($columns as $column) { + $this->_addColumn($column); + } + foreach ($flags as $flag) { + $this->addFlag($flag); + } + } + + /** + * @param string $column + * + * @return void + * + * @throws \InvalidArgumentException + */ + protected function _addColumn($column) + { + if(is_string($column)) { + $this->_columns[$column] = new Identifier($column); + } else { + throw new \InvalidArgumentException("Expecting a string as Index Column"); + } + } + + /** + * {@inheritdoc} + */ + public function getColumns() + { + return array_keys($this->_columns); + } + + /** + * {@inheritdoc} + */ + public function getQuotedColumns(AbstractPlatform $platform) + { + $columns = array(); + + foreach ($this->_columns as $column) { + $columns[] = $column->getQuotedName($platform); + } + + return $columns; + } + + /** + * @return array + */ + public function getUnquotedColumns() + { + return array_map(array($this, 'trimQuotes'), $this->getColumns()); + } + + /** + * Is the index neither unique nor primary key? + * + * @return boolean + */ + public function isSimpleIndex() + { + return !$this->_isPrimary && !$this->_isUnique; + } + + /** + * @return boolean + */ + public function isUnique() + { + return $this->_isUnique; + } + + /** + * @return boolean + */ + public function isPrimary() + { + return $this->_isPrimary; + } + + /** + * @param string $columnName + * @param integer $pos + * + * @return boolean + */ + public function hasColumnAtPosition($columnName, $pos = 0) + { + $columnName = $this->trimQuotes(strtolower($columnName)); + $indexColumns = array_map('strtolower', $this->getUnquotedColumns()); + + return array_search($columnName, $indexColumns) === $pos; + } + + /** + * Checks if this index exactly spans the given column names in the correct order. + * + * @param array $columnNames + * + * @return boolean + */ + public function spansColumns(array $columnNames) + { + $columns = $this->getColumns(); + $numberOfColumns = count($columns); + $sameColumns = true; + + for ($i = 0; $i < $numberOfColumns; $i++) { + if ( ! isset($columnNames[$i]) || $this->trimQuotes(strtolower($columns[$i])) !== $this->trimQuotes(strtolower($columnNames[$i]))) { + $sameColumns = false; + } + } + + return $sameColumns; + } + + /** + * Checks if the other index already fulfills all the indexing and constraint needs of the current one. + * + * @param \Doctrine\DBAL\Schema\Index $other + * + * @return boolean + */ + public function isFullfilledBy(Index $other) + { + // allow the other index to be equally large only. It being larger is an option + // but it creates a problem with scenarios of the kind PRIMARY KEY(foo,bar) UNIQUE(foo) + if (count($other->getColumns()) != count($this->getColumns())) { + return false; + } + + // Check if columns are the same, and even in the same order + $sameColumns = $this->spansColumns($other->getColumns()); + + if ($sameColumns) { + if ( ! $this->isUnique() && !$this->isPrimary()) { + // this is a special case: If the current key is neither primary or unique, any uniqe or + // primary key will always have the same effect for the index and there cannot be any constraint + // overlaps. This means a primary or unique index can always fulfill the requirements of just an + // index that has no constraints. + return true; + } else if ($other->isPrimary() != $this->isPrimary()) { + return false; + } else if ($other->isUnique() != $this->isUnique()) { + return false; + } + + return true; + } + + return false; + } + + /** + * Detects if the other index is a non-unique, non primary index that can be overwritten by this one. + * + * @param \Doctrine\DBAL\Schema\Index $other + * + * @return boolean + */ + public function overrules(Index $other) + { + if ($other->isPrimary()) { + return false; + } else if ($this->isSimpleIndex() && $other->isUnique()) { + return false; + } + + if ($this->spansColumns($other->getColumns()) && ($this->isPrimary() || $this->isUnique())) { + return true; + } + + return false; + } + + /** + * Returns platform specific flags for indexes. + * + * @return array + */ + public function getFlags() + { + return array_keys($this->_flags); + } + + /** + * Adds Flag for an index that translates to platform specific handling. + * + * @example $index->addFlag('CLUSTERED') + * + * @param string $flag + * + * @return \Doctrine\DBAL\Schema\Index + */ + public function addFlag($flag) + { + $this->flags[strtolower($flag)] = true; + + return $this; + } + + /** + * Does this index have a specific flag? + * + * @param string $flag + * + * @return boolean + */ + public function hasFlag($flag) + { + return isset($this->flags[strtolower($flag)]); + } + + /** + * Removes a flag. + * + * @param string $flag + * + * @return void + */ + public function removeFlag($flag) + { + unset($this->flags[strtolower($flag)]); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php new file mode 100644 index 0000000..675b8a0 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/MySqlSchemaManager.php @@ -0,0 +1,224 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +/** + * Schema manager for the MySql RDBMS. + * + * @author Konsta Vesterinen + * @author Lukas Smith (PEAR MDB2 library) + * @author Roman Borschel + * @author Benjamin Eberlei + * @since 2.0 + */ +class MySqlSchemaManager extends AbstractSchemaManager +{ + /** + * {@inheritdoc} + */ + protected function _getPortableViewDefinition($view) + { + return new View($view['TABLE_NAME'], $view['VIEW_DEFINITION']); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableDefinition($table) + { + return array_shift($table); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableUserDefinition($user) + { + return array( + 'user' => $user['User'], + 'password' => $user['Password'], + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName=null) + { + foreach($tableIndexes as $k => $v) { + $v = array_change_key_case($v, CASE_LOWER); + if($v['key_name'] == 'PRIMARY') { + $v['primary'] = true; + } else { + $v['primary'] = false; + } + if (strpos($v['index_type'], 'FULLTEXT') !== false) { + $v['flags'] = array('FULLTEXT'); + } + $tableIndexes[$k] = $v; + } + + return parent::_getPortableTableIndexesList($tableIndexes, $tableName); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableSequenceDefinition($sequence) + { + return end($sequence); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableDatabaseDefinition($database) + { + return $database['Database']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); + + $dbType = strtolower($tableColumn['type']); + $dbType = strtok($dbType, '(), '); + if (isset($tableColumn['length'])) { + $length = $tableColumn['length']; + } else { + $length = strtok('(), '); + } + + $fixed = null; + + if ( ! isset($tableColumn['name'])) { + $tableColumn['name'] = ''; + } + + $scale = null; + $precision = null; + + $type = $this->_platform->getDoctrineTypeMapping($dbType); + + // In cases where not connected to a database DESCRIBE $table does not return 'Comment' + if (isset($tableColumn['comment'])) { + $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); + $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); + } + + switch ($dbType) { + case 'char': + $fixed = true; + break; + case 'float': + case 'double': + case 'real': + case 'numeric': + case 'decimal': + if(preg_match('([A-Za-z]+\(([0-9]+)\,([0-9]+)\))', $tableColumn['type'], $match)) { + $precision = $match[1]; + $scale = $match[2]; + $length = null; + } + break; + case 'tinyint': + case 'smallint': + case 'mediumint': + case 'int': + case 'integer': + case 'bigint': + case 'tinyblob': + case 'mediumblob': + case 'longblob': + case 'blob': + case 'year': + $length = null; + break; + } + + $length = ((int) $length == 0) ? null : (int) $length; + + $options = array( + 'length' => $length, + 'unsigned' => (bool) (strpos($tableColumn['type'], 'unsigned') !== false), + 'fixed' => (bool) $fixed, + 'default' => isset($tableColumn['default']) ? $tableColumn['default'] : null, + 'notnull' => (bool) ($tableColumn['null'] != 'YES'), + 'scale' => null, + 'precision' => null, + 'autoincrement' => (bool) (strpos($tableColumn['extra'], 'auto_increment') !== false), + 'comment' => (isset($tableColumn['comment'])) ? $tableColumn['comment'] : null + ); + + if ($scale !== null && $precision !== null) { + $options['scale'] = $scale; + $options['precision'] = $precision; + } + + return new Column($tableColumn['field'], \Doctrine\DBAL\Types\Type::getType($type), $options); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $list = array(); + foreach ($tableForeignKeys as $value) { + $value = array_change_key_case($value, CASE_LOWER); + if (!isset($list[$value['constraint_name']])) { + if (!isset($value['delete_rule']) || $value['delete_rule'] == "RESTRICT") { + $value['delete_rule'] = null; + } + if (!isset($value['update_rule']) || $value['update_rule'] == "RESTRICT") { + $value['update_rule'] = null; + } + + $list[$value['constraint_name']] = array( + 'name' => $value['constraint_name'], + 'local' => array(), + 'foreign' => array(), + 'foreignTable' => $value['referenced_table_name'], + 'onDelete' => $value['delete_rule'], + 'onUpdate' => $value['update_rule'], + ); + } + $list[$value['constraint_name']]['local'][] = $value['column_name']; + $list[$value['constraint_name']]['foreign'][] = $value['referenced_column_name']; + } + + $result = array(); + foreach($list as $constraint) { + $result[] = new ForeignKeyConstraint( + array_values($constraint['local']), $constraint['foreignTable'], + array_values($constraint['foreign']), $constraint['name'], + array( + 'onDelete' => $constraint['onDelete'], + 'onUpdate' => $constraint['onUpdate'], + ) + ); + } + + return $result; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/OracleSchemaManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/OracleSchemaManager.php new file mode 100644 index 0000000..7fd3064 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/OracleSchemaManager.php @@ -0,0 +1,316 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +/** + * Oracle Schema Manager. + * + * @author Konsta Vesterinen + * @author Lukas Smith (PEAR MDB2 library) + * @author Benjamin Eberlei + * @since 2.0 + */ +class OracleSchemaManager extends AbstractSchemaManager +{ + /** + * {@inheritdoc} + */ + protected function _getPortableViewDefinition($view) + { + $view = \array_change_key_case($view, CASE_LOWER); + + return new View($view['view_name'], $view['text']); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableUserDefinition($user) + { + $user = \array_change_key_case($user, CASE_LOWER); + + return array( + 'user' => $user['username'], + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableDefinition($table) + { + $table = \array_change_key_case($table, CASE_LOWER); + + return $table['table_name']; + } + + /** + * {@inheritdoc} + * + * @license New BSD License + * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName=null) + { + $indexBuffer = array(); + foreach ( $tableIndexes as $tableIndex ) { + $tableIndex = \array_change_key_case($tableIndex, CASE_LOWER); + + $keyName = strtolower($tableIndex['name']); + + if ( strtolower($tableIndex['is_primary']) == "p" ) { + $keyName = 'primary'; + $buffer['primary'] = true; + $buffer['non_unique'] = false; + } else { + $buffer['primary'] = false; + $buffer['non_unique'] = ( $tableIndex['is_unique'] == 0 ) ? true : false; + } + $buffer['key_name'] = $keyName; + $buffer['column_name'] = $tableIndex['column_name']; + $indexBuffer[] = $buffer; + } + + return parent::_getPortableTableIndexesList($indexBuffer, $tableName); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $tableColumn = \array_change_key_case($tableColumn, CASE_LOWER); + + $dbType = strtolower($tableColumn['data_type']); + if(strpos($dbType, "timestamp(") === 0) { + if (strpos($dbType, "WITH TIME ZONE")) { + $dbType = "timestamptz"; + } else { + $dbType = "timestamp"; + } + } + + $unsigned = $fixed = null; + + if ( ! isset($tableColumn['column_name'])) { + $tableColumn['column_name'] = ''; + } + + if (stripos($tableColumn['data_default'], 'NULL') !== null) { + $tableColumn['data_default'] = null; + } + + $precision = null; + $scale = null; + + $type = $this->_platform->getDoctrineTypeMapping($dbType); + $type = $this->extractDoctrineTypeFromComment($tableColumn['comments'], $type); + $tableColumn['comments'] = $this->removeDoctrineTypeFromComment($tableColumn['comments'], $type); + + switch ($dbType) { + case 'number': + if ($tableColumn['data_precision'] == 20 && $tableColumn['data_scale'] == 0) { + $precision = 20; + $scale = 0; + $type = 'bigint'; + } elseif ($tableColumn['data_precision'] == 5 && $tableColumn['data_scale'] == 0) { + $type = 'smallint'; + $precision = 5; + $scale = 0; + } elseif ($tableColumn['data_precision'] == 1 && $tableColumn['data_scale'] == 0) { + $precision = 1; + $scale = 0; + $type = 'boolean'; + } elseif ($tableColumn['data_scale'] > 0) { + $precision = $tableColumn['data_precision']; + $scale = $tableColumn['data_scale']; + $type = 'decimal'; + } + $length = null; + break; + case 'pls_integer': + case 'binary_integer': + $length = null; + break; + case 'varchar': + case 'varchar2': + case 'nvarchar2': + $length = $tableColumn['char_length']; + $fixed = false; + break; + case 'char': + case 'nchar': + $length = $tableColumn['char_length']; + $fixed = true; + break; + case 'date': + case 'timestamp': + $length = null; + break; + case 'float': + $precision = $tableColumn['data_precision']; + $scale = $tableColumn['data_scale']; + $length = null; + break; + case 'clob': + case 'nclob': + $length = null; + break; + case 'blob': + case 'raw': + case 'long raw': + case 'bfile': + $length = null; + break; + case 'rowid': + case 'urowid': + default: + $length = null; + } + + $options = array( + 'notnull' => (bool) ($tableColumn['nullable'] === 'N'), + 'fixed' => (bool) $fixed, + 'unsigned' => (bool) $unsigned, + 'default' => $tableColumn['data_default'], + 'length' => $length, + 'precision' => $precision, + 'scale' => $scale, + 'comment' => (isset($tableColumn['comments'])) ? $tableColumn['comments'] : null, + 'platformDetails' => array(), + ); + + return new Column($tableColumn['column_name'], \Doctrine\DBAL\Types\Type::getType($type), $options); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $list = array(); + foreach ($tableForeignKeys as $value) { + $value = \array_change_key_case($value, CASE_LOWER); + if (!isset($list[$value['constraint_name']])) { + if ($value['delete_rule'] == "NO ACTION") { + $value['delete_rule'] = null; + } + + $list[$value['constraint_name']] = array( + 'name' => $value['constraint_name'], + 'local' => array(), + 'foreign' => array(), + 'foreignTable' => $value['references_table'], + 'onDelete' => $value['delete_rule'], + ); + } + $list[$value['constraint_name']]['local'][$value['position']] = $value['local_column']; + $list[$value['constraint_name']]['foreign'][$value['position']] = $value['foreign_column']; + } + + $result = array(); + foreach($list as $constraint) { + $result[] = new ForeignKeyConstraint( + array_values($constraint['local']), $constraint['foreignTable'], + array_values($constraint['foreign']), $constraint['name'], + array('onDelete' => $constraint['onDelete']) + ); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableSequenceDefinition($sequence) + { + $sequence = \array_change_key_case($sequence, CASE_LOWER); + + return new Sequence($sequence['sequence_name'], $sequence['increment_by'], $sequence['min_value']); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableFunctionDefinition($function) + { + $function = \array_change_key_case($function, CASE_LOWER); + + return $function['name']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableDatabaseDefinition($database) + { + $database = \array_change_key_case($database, CASE_LOWER); + + return $database['username']; + } + + /** + * {@inheritdoc} + */ + public function createDatabase($database = null) + { + if (is_null($database)) { + $database = $this->_conn->getDatabase(); + } + + $params = $this->_conn->getParams(); + $username = $database; + $password = $params['password']; + + $query = 'CREATE USER ' . $username . ' IDENTIFIED BY ' . $password; + $this->_conn->executeUpdate($query); + + $query = 'GRANT CREATE SESSION, CREATE TABLE, UNLIMITED TABLESPACE, CREATE SEQUENCE, CREATE TRIGGER TO ' . $username; + $this->_conn->executeUpdate($query); + + return true; + } + + /** + * @param string $table + * + * @return boolean + */ + public function dropAutoincrement($table) + { + $sql = $this->_platform->getDropAutoincrementSql($table); + foreach ($sql as $query) { + $this->_conn->executeUpdate($query); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function dropTable($name) + { + $this->dropAutoincrement($name); + + parent::dropTable($name); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php new file mode 100644 index 0000000..f695507 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/PostgreSqlSchemaManager.php @@ -0,0 +1,399 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +/** + * PostgreSQL Schema Manager. + * + * @author Konsta Vesterinen + * @author Lukas Smith (PEAR MDB2 library) + * @author Benjamin Eberlei + * @since 2.0 + */ +class PostgreSqlSchemaManager extends AbstractSchemaManager +{ + /** + * @var array + */ + private $existingSchemaPaths; + + /** + * Gets all the existing schema names. + * + * @return array + */ + public function getSchemaNames() + { + $rows = $this->_conn->fetchAll("SELECT nspname as schema_name FROM pg_namespace WHERE nspname !~ '^pg_.*' and nspname != 'information_schema'"); + + return array_map(function($v) { return $v['schema_name']; }, $rows); + } + + /** + * Returns an array of schema search paths. + * + * This is a PostgreSQL only function. + * + * @return array + */ + public function getSchemaSearchPaths() + { + $params = $this->_conn->getParams(); + $schema = explode(",", $this->_conn->fetchColumn('SHOW search_path')); + + if (isset($params['user'])) { + $schema = str_replace('"$user"', $params['user'], $schema); + } + + return array_map('trim', $schema); + } + + /** + * Gets names of all existing schemas in the current users search path. + * + * This is a PostgreSQL only function. + * + * @return array + */ + public function getExistingSchemaSearchPaths() + { + if ($this->existingSchemaPaths === null) { + $this->determineExistingSchemaSearchPaths(); + } + + return $this->existingSchemaPaths; + } + + /** + * Sets or resets the order of the existing schemas in the current search path of the user. + * + * This is a PostgreSQL only function. + * + * @return void + */ + public function determineExistingSchemaSearchPaths() + { + $names = $this->getSchemaNames(); + $paths = $this->getSchemaSearchPaths(); + + $this->existingSchemaPaths = array_filter($paths, function ($v) use ($names) { + return in_array($v, $names); + }); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + $onUpdate = null; + $onDelete = null; + + if (preg_match('(ON UPDATE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match)) { + $onUpdate = $match[1]; + } + if (preg_match('(ON DELETE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match)) { + $onDelete = $match[1]; + } + + if (preg_match('/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)/', $tableForeignKey['condef'], $values)) { + // PostgreSQL returns identifiers that are keywords with quotes, we need them later, don't get + // the idea to trim them here. + $localColumns = array_map('trim', explode(",", $values[1])); + $foreignColumns = array_map('trim', explode(",", $values[3])); + $foreignTable = $values[2]; + } + + return new ForeignKeyConstraint( + $localColumns, $foreignTable, $foreignColumns, $tableForeignKey['conname'], + array('onUpdate' => $onUpdate, 'onDelete' => $onDelete) + ); + } + + /** + * {@inheritdoc} + */ + public function dropDatabase($database) + { + $params = $this->_conn->getParams(); + $params["dbname"] = "postgres"; + $tmpPlatform = $this->_platform; + $tmpConn = $this->_conn; + + $this->_conn = \Doctrine\DBAL\DriverManager::getConnection($params); + $this->_platform = $this->_conn->getDatabasePlatform(); + + parent::dropDatabase($database); + + $this->_conn->close(); + + $this->_platform = $tmpPlatform; + $this->_conn = $tmpConn; + } + + /** + * {@inheritdoc} + */ + public function createDatabase($database) + { + $params = $this->_conn->getParams(); + $params["dbname"] = "postgres"; + $tmpPlatform = $this->_platform; + $tmpConn = $this->_conn; + + $this->_conn = \Doctrine\DBAL\DriverManager::getConnection($params); + $this->_platform = $this->_conn->getDatabasePlatform(); + + parent::createDatabase($database); + + $this->_conn->close(); + + $this->_platform = $tmpPlatform; + $this->_conn = $tmpConn; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTriggerDefinition($trigger) + { + return $trigger['trigger_name']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableViewDefinition($view) + { + return new View($view['viewname'], $view['definition']); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableUserDefinition($user) + { + return array( + 'user' => $user['usename'], + 'password' => $user['passwd'] + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableDefinition($table) + { + $schemas = $this->getExistingSchemaSearchPaths(); + $firstSchema = array_shift($schemas); + + if ($table['schema_name'] == $firstSchema) { + return $table['table_name']; + } else { + return $table['schema_name'] . "." . $table['table_name']; + } + } + + /** + * {@inheritdoc} + * + * @license New BSD License + * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName=null) + { + $buffer = array(); + foreach ($tableIndexes as $row) { + $colNumbers = explode(' ', $row['indkey']); + $colNumbersSql = 'IN (' . join(' ,', $colNumbers) . ' )'; + $columnNameSql = "SELECT attnum, attname FROM pg_attribute + WHERE attrelid={$row['indrelid']} AND attnum $colNumbersSql ORDER BY attnum ASC;"; + + $stmt = $this->_conn->executeQuery($columnNameSql); + $indexColumns = $stmt->fetchAll(); + + // required for getting the order of the columns right. + foreach ($colNumbers as $colNum) { + foreach ($indexColumns as $colRow) { + if ($colNum == $colRow['attnum']) { + $buffer[] = array( + 'key_name' => $row['relname'], + 'column_name' => trim($colRow['attname']), + 'non_unique' => !$row['indisunique'], + 'primary' => $row['indisprimary'] + ); + } + } + } + } + + return parent::_getPortableTableIndexesList($buffer, $tableName); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableDatabaseDefinition($database) + { + return $database['datname']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableSequenceDefinition($sequence) + { + if ($sequence['schemaname'] != 'public') { + $sequenceName = $sequence['schemaname'] . "." . $sequence['relname']; + } else { + $sequenceName = $sequence['relname']; + } + + $data = $this->_conn->fetchAll('SELECT min_value, increment_by FROM ' . $this->_platform->quoteIdentifier($sequenceName)); + + return new Sequence($sequenceName, $data[0]['increment_by'], $data[0]['min_value']); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $tableColumn = array_change_key_case($tableColumn, CASE_LOWER); + + if (strtolower($tableColumn['type']) === 'varchar') { + // get length from varchar definition + $length = preg_replace('~.*\(([0-9]*)\).*~', '$1', $tableColumn['complete_type']); + $tableColumn['length'] = $length; + } + + $matches = array(); + + $autoincrement = false; + if (preg_match("/^nextval\('(.*)'(::.*)?\)$/", $tableColumn['default'], $matches)) { + $tableColumn['sequence'] = $matches[1]; + $tableColumn['default'] = null; + $autoincrement = true; + } + + if (preg_match("/^'(.*)'::.*$/", $tableColumn['default'], $matches)) { + $tableColumn['default'] = $matches[1]; + } + + if (stripos($tableColumn['default'], 'NULL') === 0) { + $tableColumn['default'] = null; + } + + $length = (isset($tableColumn['length'])) ? $tableColumn['length'] : null; + if ($length == '-1' && isset($tableColumn['atttypmod'])) { + $length = $tableColumn['atttypmod'] - 4; + } + if ((int) $length <= 0) { + $length = null; + } + $fixed = null; + + if (!isset($tableColumn['name'])) { + $tableColumn['name'] = ''; + } + + $precision = null; + $scale = null; + + $dbType = strtolower($tableColumn['type']); + if (strlen($tableColumn['domain_type']) && !$this->_platform->hasDoctrineTypeMappingFor($tableColumn['type'])) { + $dbType = strtolower($tableColumn['domain_type']); + $tableColumn['complete_type'] = $tableColumn['domain_complete_type']; + } + + $type = $this->_platform->getDoctrineTypeMapping($dbType); + $type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type); + $tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type); + + switch ($dbType) { + case 'smallint': + case 'int2': + $length = null; + break; + case 'int': + case 'int4': + case 'integer': + $length = null; + break; + case 'bigint': + case 'int8': + $length = null; + break; + case 'bool': + case 'boolean': + $length = null; + break; + case 'text': + $fixed = false; + break; + case 'varchar': + case 'interval': + case '_varchar': + $fixed = false; + break; + case 'char': + case 'bpchar': + $fixed = true; + break; + case 'float': + case 'float4': + case 'float8': + case 'double': + case 'double precision': + case 'real': + case 'decimal': + case 'money': + case 'numeric': + if (preg_match('([A-Za-z]+\(([0-9]+)\,([0-9]+)\))', $tableColumn['complete_type'], $match)) { + $precision = $match[1]; + $scale = $match[2]; + $length = null; + } + break; + case 'year': + $length = null; + break; + } + + if ($tableColumn['default'] && preg_match("('([^']+)'::)", $tableColumn['default'], $match)) { + $tableColumn['default'] = $match[1]; + } + + $options = array( + 'length' => $length, + 'notnull' => (bool) $tableColumn['isnotnull'], + 'default' => $tableColumn['default'], + 'primary' => (bool) ($tableColumn['pri'] == 't'), + 'precision' => $precision, + 'scale' => $scale, + 'fixed' => $fixed, + 'unsigned' => false, + 'autoincrement' => $autoincrement, + 'comment' => $tableColumn['comment'], + ); + + return new Column($tableColumn['field'], \Doctrine\DBAL\Types\Type::getType($type), $options); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php new file mode 100644 index 0000000..45d4493 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SQLServerSchemaManager.php @@ -0,0 +1,256 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Driver\SQLSrv\SQLSrvException; +use Doctrine\DBAL\Types\Type; + +/** + * SQL Server Schema Manager. + * + * @license http://www.opensource.org/licenses/lgpl-license.php LGPL + * @author Konsta Vesterinen + * @author Lukas Smith (PEAR MDB2 library) + * @author Juozas Kaziukenas + * @author Steve Müller + * @since 2.0 + */ +class SQLServerSchemaManager extends AbstractSchemaManager +{ + /** + * {@inheritdoc} + */ + protected function _getPortableSequenceDefinition($sequence) + { + return new Sequence($sequence['name'], $sequence['increment'], $sequence['start_value']); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $dbType = strtok($tableColumn['type'], '(), '); + $fixed = null; + $length = (int) $tableColumn['length']; + $default = $tableColumn['default']; + + if (!isset($tableColumn['name'])) { + $tableColumn['name'] = ''; + } + + while ($default != ($default2 = preg_replace("/^\((.*)\)$/", '$1', $default))) { + $default = trim($default2, "'"); + + if ($default == 'getdate()') { + $default = $this->_platform->getCurrentTimestampSQL(); + } + } + + switch ($dbType) { + case 'nchar': + case 'nvarchar': + case 'ntext': + // Unicode data requires 2 bytes per character + $length = $length / 2; + break; + case 'varchar': + // TEXT type is returned as VARCHAR(MAX) with a length of -1 + if ($length == -1) { + $dbType = 'text'; + } + break; + } + + $type = $this->_platform->getDoctrineTypeMapping($dbType); + + switch ($type) { + case 'char': + $fixed = true; + break; + case 'text': + $fixed = false; + break; + } + + $options = array( + 'length' => ($length == 0 || !in_array($type, array('text', 'string'))) ? null : $length, + 'unsigned' => false, + 'fixed' => (bool) $fixed, + 'default' => $default !== 'NULL' ? $default : null, + 'notnull' => (bool) $tableColumn['notnull'], + 'scale' => $tableColumn['scale'], + 'precision' => $tableColumn['precision'], + 'autoincrement' => (bool) $tableColumn['autoincrement'], + ); + + $platformOptions = array( + 'collate' => $tableColumn['collation'] == 'NULL' ? null : $tableColumn['collation'] + ); + + $column = new Column($tableColumn['name'], Type::getType($type), $options); + $column->setPlatformOptions($platformOptions); + + return $column; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $foreignKeys = array(); + + foreach ($tableForeignKeys as $tableForeignKey) { + if ( ! isset($foreignKeys[$tableForeignKey['ForeignKey']])) { + $foreignKeys[$tableForeignKey['ForeignKey']] = array( + 'local_columns' => array($tableForeignKey['ColumnName']), + 'foreign_table' => $tableForeignKey['ReferenceTableName'], + 'foreign_columns' => array($tableForeignKey['ReferenceColumnName']), + 'name' => $tableForeignKey['ForeignKey'], + 'options' => array( + 'onUpdate' => str_replace('_', ' ', $tableForeignKey['update_referential_action_desc']), + 'onDelete' => str_replace('_', ' ', $tableForeignKey['delete_referential_action_desc']) + ) + ); + } else { + $foreignKeys[$tableForeignKey['ForeignKey']]['local_columns'][] = $tableForeignKey['ColumnName']; + $foreignKeys[$tableForeignKey['ForeignKey']]['foreign_columns'][] = $tableForeignKey['ReferenceColumnName']; + } + } + + return parent::_getPortableTableForeignKeysList($foreignKeys); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableIndexesList($tableIndexRows, $tableName=null) + { + foreach ($tableIndexRows as &$tableIndex) { + $tableIndex['non_unique'] = (boolean) $tableIndex['non_unique']; + $tableIndex['primary'] = (boolean) $tableIndex['primary']; + $tableIndex['flags'] = $tableIndex['flags'] ? array($tableIndex['flags']) : null; + } + + return parent::_getPortableTableIndexesList($tableIndexRows, $tableName); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeyDefinition($tableForeignKey) + { + return new ForeignKeyConstraint( + $tableForeignKey['local_columns'], + $tableForeignKey['foreign_table'], + $tableForeignKey['foreign_columns'], + $tableForeignKey['name'], + $tableForeignKey['options'] + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableDefinition($table) + { + return $table['name']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableDatabaseDefinition($database) + { + return $database['name']; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableViewDefinition($view) + { + // @todo + return new View($view['name'], null); + } + + /** + * {@inheritdoc} + */ + public function listTableIndexes($table) + { + $sql = $this->_platform->getListTableIndexesSQL($table, $this->_conn->getDatabase()); + + try { + $tableIndexes = $this->_conn->fetchAll($sql); + } catch(\PDOException $e) { + if ($e->getCode() == "IMSSP") { + return array(); + } else { + throw $e; + } + } catch(SQLSrvException $e) { + if (strpos($e->getMessage(), 'SQLSTATE [01000, 15472]') === 0) { + return array(); + } else { + throw $e; + } + } + + return $this->_getPortableTableIndexesList($tableIndexes, $table); + } + + /** + * {@inheritdoc} + */ + public function alterTable(TableDiff $tableDiff) + { + if(count($tableDiff->removedColumns) > 0) { + foreach($tableDiff->removedColumns as $col){ + $columnConstraintSql = $this->getColumnConstraintSQL($tableDiff->name, $col->getName()); + foreach ($this->_conn->fetchAll($columnConstraintSql) as $constraint) { + $this->_conn->exec("ALTER TABLE $tableDiff->name DROP CONSTRAINT " . $constraint['Name']); + } + } + } + + parent::alterTable($tableDiff); + } + + /** + * Returns the SQL to retrieve the constraints for a given column. + * + * @param string $table + * @param string $column + * + * @return string + */ + private function getColumnConstraintSQL($table, $column) + { + return "SELECT SysObjects.[Name] + FROM SysObjects INNER JOIN (SELECT [Name],[ID] FROM SysObjects WHERE XType = 'U') AS Tab + ON Tab.[ID] = Sysobjects.[Parent_Obj] + INNER JOIN sys.default_constraints DefCons ON DefCons.[object_id] = Sysobjects.[ID] + INNER JOIN SysColumns Col ON Col.[ColID] = DefCons.[parent_column_id] AND Col.[ID] = Tab.[ID] + WHERE Col.[Name] = " . $this->_conn->quote($column) ." AND Tab.[Name] = " . $this->_conn->quote($table) . " + ORDER BY Col.[Name]"; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Schema.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Schema.php new file mode 100644 index 0000000..fcb6d32 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Schema.php @@ -0,0 +1,414 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Schema\Visitor\CreateSchemaSqlCollector; +use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector; +use Doctrine\DBAL\Schema\Visitor\Visitor; +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Object representation of a database schema. + * + * Different vendors have very inconsistent naming with regard to the concept + * of a "schema". Doctrine understands a schema as the entity that conceptually + * wraps a set of database objects such as tables, sequences, indexes and + * foreign keys that belong to each other into a namespace. A Doctrine Schema + * has nothing to do with the "SCHEMA" defined as in PostgreSQL, it is more + * related to the concept of "DATABASE" that exists in MySQL and PostgreSQL. + * + * Every asset in the doctrine schema has a name. A name consists of either a + * namespace.local name pair or just a local unqualified name. + * + * The abstraction layer that covers a PostgreSQL schema is the namespace of an + * database object (asset). A schema can have a name, which will be used as + * default namespace for the unqualified database objects that are created in + * the schema. + * + * In the case of MySQL where cross-database queries are allowed this leads to + * databases being "misinterpreted" as namespaces. This is intentional, however + * the CREATE/DROP SQL visitors will just filter this queries and do not + * execute them. Only the queries for the currently connected database are + * executed. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class Schema extends AbstractAsset +{ + /** + * @var \Doctrine\DBAL\Schema\Table[] + */ + protected $_tables = array(); + + /** + * @var \Doctrine\DBAL\Schema\Sequence[] + */ + protected $_sequences = array(); + + /** + * @var \Doctrine\DBAL\Schema\SchemaConfig + */ + protected $_schemaConfig = false; + + /** + * @param \Doctrine\DBAL\Schema\Table[] $tables + * @param \Doctrine\DBAL\Schema\Sequence[] $sequences + * @param \Doctrine\DBAL\Schema\SchemaConfig $schemaConfig + */ + public function __construct(array $tables=array(), array $sequences=array(), SchemaConfig $schemaConfig=null) + { + if ($schemaConfig == null) { + $schemaConfig = new SchemaConfig(); + } + $this->_schemaConfig = $schemaConfig; + $this->_setName($schemaConfig->getName() ?: 'public'); + + foreach ($tables as $table) { + $this->_addTable($table); + } + + foreach ($sequences as $sequence) { + $this->_addSequence($sequence); + } + } + + /** + * @return boolean + */ + public function hasExplicitForeignKeyIndexes() + { + return $this->_schemaConfig->hasExplicitForeignKeyIndexes(); + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * + * @return void + * + * @throws \Doctrine\DBAL\Schema\SchemaException + */ + protected function _addTable(Table $table) + { + $tableName = $table->getFullQualifiedName($this->getName()); + if(isset($this->_tables[$tableName])) { + throw SchemaException::tableAlreadyExists($tableName); + } + + $this->_tables[$tableName] = $table; + $table->setSchemaConfig($this->_schemaConfig); + } + + /** + * @param \Doctrine\DBAL\Schema\Sequence $sequence + * + * @return void + * + * @throws \Doctrine\DBAL\Schema\SchemaException + */ + protected function _addSequence(Sequence $sequence) + { + $seqName = $sequence->getFullQualifiedName($this->getName()); + if (isset($this->_sequences[$seqName])) { + throw SchemaException::sequenceAlreadyExists($seqName); + } + $this->_sequences[$seqName] = $sequence; + } + + /** + * Gets all tables of this schema. + * + * @return \Doctrine\DBAL\Schema\Table[] + */ + public function getTables() + { + return $this->_tables; + } + + /** + * @param string $tableName + * + * @return \Doctrine\DBAL\Schema\Table + * + * @throws \Doctrine\DBAL\Schema\SchemaException + */ + public function getTable($tableName) + { + $tableName = $this->getFullQualifiedAssetName($tableName); + if (!isset($this->_tables[$tableName])) { + throw SchemaException::tableDoesNotExist($tableName); + } + + return $this->_tables[$tableName]; + } + + /** + * @param string $name + * + * @return string + */ + private function getFullQualifiedAssetName($name) + { + if ($this->isIdentifierQuoted($name)) { + $name = $this->trimQuotes($name); + } + if (strpos($name, ".") === false) { + $name = $this->getName() . "." . $name; + } + + return strtolower($name); + } + + /** + * Does this schema have a table with the given name? + * + * @param string $tableName + * + * @return boolean + */ + public function hasTable($tableName) + { + $tableName = $this->getFullQualifiedAssetName($tableName); + + return isset($this->_tables[$tableName]); + } + + /** + * Gets all table names, prefixed with a schema name, even the default one if present. + * + * @return array + */ + public function getTableNames() + { + return array_keys($this->_tables); + } + + /** + * @param string $sequenceName + * + * @return boolean + */ + public function hasSequence($sequenceName) + { + $sequenceName = $this->getFullQualifiedAssetName($sequenceName); + + return isset($this->_sequences[$sequenceName]); + } + + /** + * @param string $sequenceName + * + * @return \Doctrine\DBAL\Schema\Sequence + * + * @throws \Doctrine\DBAL\Schema\SchemaException + */ + public function getSequence($sequenceName) + { + $sequenceName = $this->getFullQualifiedAssetName($sequenceName); + if(!$this->hasSequence($sequenceName)) { + throw SchemaException::sequenceDoesNotExist($sequenceName); + } + + return $this->_sequences[$sequenceName]; + } + + /** + * @return \Doctrine\DBAL\Schema\Sequence[] + */ + public function getSequences() + { + return $this->_sequences; + } + + /** + * Creates a new table. + * + * @param string $tableName + * + * @return \Doctrine\DBAL\Schema\Table + */ + public function createTable($tableName) + { + $table = new Table($tableName); + $this->_addTable($table); + + foreach ($this->_schemaConfig->getDefaultTableOptions() as $name => $value) { + $table->addOption($name, $value); + } + + return $table; + } + + /** + * Renames a table. + * + * @param string $oldTableName + * @param string $newTableName + * + * @return \Doctrine\DBAL\Schema\Schema + */ + public function renameTable($oldTableName, $newTableName) + { + $table = $this->getTable($oldTableName); + $table->_setName($newTableName); + + $this->dropTable($oldTableName); + $this->_addTable($table); + + return $this; + } + + /** + * Drops a table from the schema. + * + * @param string $tableName + * + * @return \Doctrine\DBAL\Schema\Schema + */ + public function dropTable($tableName) + { + $tableName = $this->getFullQualifiedAssetName($tableName); + $this->getTable($tableName); + unset($this->_tables[$tableName]); + + return $this; + } + + /** + * Creates a new sequence. + * + * @param string $sequenceName + * @param integer $allocationSize + * @param integer $initialValue + * + * @return \Doctrine\DBAL\Schema\Sequence + */ + public function createSequence($sequenceName, $allocationSize=1, $initialValue=1) + { + $seq = new Sequence($sequenceName, $allocationSize, $initialValue); + $this->_addSequence($seq); + + return $seq; + } + + /** + * @param string $sequenceName + * + * @return \Doctrine\DBAL\Schema\Schema + */ + public function dropSequence($sequenceName) + { + $sequenceName = $this->getFullQualifiedAssetName($sequenceName); + unset($this->_sequences[$sequenceName]); + + return $this; + } + + /** + * Returns an array of necessary SQL queries to create the schema on the given platform. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return array + */ + public function toSql(AbstractPlatform $platform) + { + $sqlCollector = new CreateSchemaSqlCollector($platform); + $this->visit($sqlCollector); + + return $sqlCollector->getQueries(); + } + + /** + * Return an array of necessary SQL queries to drop the schema on the given platform. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return array + */ + public function toDropSql(AbstractPlatform $platform) + { + $dropSqlCollector = new DropSchemaSqlCollector($platform); + $this->visit($dropSqlCollector); + + return $dropSqlCollector->getQueries(); + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $toSchema + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return array + */ + public function getMigrateToSql(Schema $toSchema, AbstractPlatform $platform) + { + $comparator = new Comparator(); + $schemaDiff = $comparator->compare($this, $toSchema); + + return $schemaDiff->toSql($platform); + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $fromSchema + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return array + */ + public function getMigrateFromSql(Schema $fromSchema, AbstractPlatform $platform) + { + $comparator = new Comparator(); + $schemaDiff = $comparator->compare($fromSchema, $this); + + return $schemaDiff->toSql($platform); + } + + /** + * @param \Doctrine\DBAL\Schema\Visitor\Visitor $visitor + * + * @return void + */ + public function visit(Visitor $visitor) + { + $visitor->acceptSchema($this); + + foreach ($this->_tables as $table) { + $table->visit($visitor); + } + foreach ($this->_sequences as $sequence) { + $sequence->visit($visitor); + } + } + + /** + * Cloning a Schema triggers a deep clone of all related assets. + * + * @return void + */ + public function __clone() + { + foreach ($this->_tables as $k => $table) { + $this->_tables[$k] = clone $table; + } + foreach ($this->_sequences as $k => $sequence) { + $this->_sequences[$k] = clone $sequence; + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaConfig.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaConfig.php new file mode 100644 index 0000000..e8ba6fd --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaConfig.php @@ -0,0 +1,129 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +/** + * Configuration for a Schema. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class SchemaConfig +{ + /** + * @var boolean + */ + protected $hasExplicitForeignKeyIndexes = false; + + /** + * @var integer + */ + protected $maxIdentifierLength = 63; + + /** + * @var string + */ + protected $name; + + /** + * @var array + */ + protected $defaultTableOptions = array(); + + /** + * @return boolean + */ + public function hasExplicitForeignKeyIndexes() + { + return $this->hasExplicitForeignKeyIndexes; + } + + /** + * @param boolean $flag + * + * @return void + */ + public function setExplicitForeignKeyIndexes($flag) + { + $this->hasExplicitForeignKeyIndexes = (bool)$flag; + } + + /** + * @param integer $length + * + * @return void + */ + public function setMaxIdentifierLength($length) + { + $this->maxIdentifierLength = (int)$length; + } + + /** + * @return integer + */ + public function getMaxIdentifierLength() + { + return $this->maxIdentifierLength; + } + + /** + * Gets the default namespace of schema objects. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the default namespace name of schema objects. + * + * @param string $name The value to set. + * + * @return void + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Gets the default options that are passed to Table instances created with + * Schema#createTable(). + * + * @return array + */ + public function getDefaultTableOptions() + { + return $this->defaultTableOptions; + } + + /** + * @param array $defaultTableOptions + * + * @return void + */ + public function setDefaultTableOptions(array $defaultTableOptions) + { + $this->defaultTableOptions = $defaultTableOptions; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaDiff.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaDiff.php new file mode 100644 index 0000000..4fcdc30 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaDiff.php @@ -0,0 +1,184 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use \Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Schema Diff. + * + * @link www.doctrine-project.org + * @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved. + * @license http://ez.no/licenses/new_bsd New BSD License + * @since 2.0 + * @author Benjamin Eberlei + */ +class SchemaDiff +{ + /** + * @var \Doctrine\DBAL\Schema\Schema + */ + public $fromSchema; + + /** + * All added tables. + * + * @var \Doctrine\DBAL\Schema\Table[] + */ + public $newTables = array(); + + /** + * All changed tables. + * + * @var \Doctrine\DBAL\Schema\TableDiff[] + */ + public $changedTables = array(); + + /** + * All removed tables. + * + * @var \Doctrine\DBAL\Schema\Table[] + */ + public $removedTables = array(); + + /** + * @var \Doctrine\DBAL\Schema\Sequence[] + */ + public $newSequences = array(); + + /** + * @var \Doctrine\DBAL\Schema\Sequence[] + */ + public $changedSequences = array(); + + /** + * @var \Doctrine\DBAL\Schema\Sequence[] + */ + public $removedSequences = array(); + + /** + * @var \Doctrine\DBAL\Schema\ForeignKeyConstraint[] + */ + public $orphanedForeignKeys = array(); + + /** + * Constructs an SchemaDiff object. + * + * @param \Doctrine\DBAL\Schema\Table[] $newTables + * @param \Doctrine\DBAL\Schema\TableDiff[] $changedTables + * @param \Doctrine\DBAL\Schema\Table[] $removedTables + * @param \Doctrine\DBAL\Schema\Schema|null $fromSchema + */ + public function __construct($newTables = array(), $changedTables = array(), $removedTables = array(), Schema $fromSchema = null) + { + $this->newTables = $newTables; + $this->changedTables = $changedTables; + $this->removedTables = $removedTables; + $this->fromSchema = $fromSchema; + } + + /** + * The to save sql mode ensures that the following things don't happen: + * + * 1. Tables are deleted + * 2. Sequences are deleted + * 3. Foreign Keys which reference tables that would otherwise be deleted. + * + * This way it is ensured that assets are deleted which might not be relevant to the metadata schema at all. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return array + */ + public function toSaveSql(AbstractPlatform $platform) + { + return $this->_toSql($platform, true); + } + + /** + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return array + */ + public function toSql(AbstractPlatform $platform) + { + return $this->_toSql($platform, false); + } + + /** + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * @param boolean $saveMode + * + * @return array + */ + protected function _toSql(AbstractPlatform $platform, $saveMode = false) + { + $sql = array(); + + if ($platform->supportsForeignKeyConstraints() && $saveMode == false) { + foreach ($this->orphanedForeignKeys as $orphanedForeignKey) { + $sql[] = $platform->getDropForeignKeySQL($orphanedForeignKey, $orphanedForeignKey->getLocalTableName()); + } + } + + if ($platform->supportsSequences() == true) { + foreach ($this->changedSequences as $sequence) { + $sql[] = $platform->getAlterSequenceSQL($sequence); + } + + if ($saveMode === false) { + foreach ($this->removedSequences as $sequence) { + $sql[] = $platform->getDropSequenceSQL($sequence); + } + } + + foreach ($this->newSequences as $sequence) { + $sql[] = $platform->getCreateSequenceSQL($sequence); + } + } + + $foreignKeySql = array(); + foreach ($this->newTables as $table) { + $sql = array_merge( + $sql, + $platform->getCreateTableSQL($table, AbstractPlatform::CREATE_INDEXES) + ); + + if ($platform->supportsForeignKeyConstraints()) { + foreach ($table->getForeignKeys() as $foreignKey) { + $foreignKeySql[] = $platform->getCreateForeignKeySQL($foreignKey, $table); + } + } + } + $sql = array_merge($sql, $foreignKeySql); + + if ($saveMode === false) { + foreach ($this->removedTables as $table) { + $sql[] = $platform->getDropTableSQL($table); + } + } + + foreach ($this->changedTables as $tableDiff) { + $sql = array_merge($sql, $platform->getAlterTableSQL($tableDiff)); + } + + return $sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaException.php new file mode 100644 index 0000000..2b2d2ef --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SchemaException.php @@ -0,0 +1,167 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +class SchemaException extends \Doctrine\DBAL\DBALException +{ + const TABLE_DOESNT_EXIST = 10; + const TABLE_ALREADY_EXISTS = 20; + const COLUMN_DOESNT_EXIST = 30; + const COLUMN_ALREADY_EXISTS = 40; + const INDEX_DOESNT_EXIST = 50; + const INDEX_ALREADY_EXISTS = 60; + const SEQUENCE_DOENST_EXIST = 70; + const SEQUENCE_ALREADY_EXISTS = 80; + const INDEX_INVALID_NAME = 90; + const FOREIGNKEY_DOESNT_EXIST = 100; + + /** + * @param string $tableName + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function tableDoesNotExist($tableName) + { + return new self("There is no table with name '".$tableName."' in the schema.", self::TABLE_DOESNT_EXIST); + } + + /** + * @param string $indexName + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function indexNameInvalid($indexName) + { + return new self("Invalid index-name $indexName given, has to be [a-zA-Z0-9_]", self::INDEX_INVALID_NAME); + } + + /** + * @param string $indexName + * @param string $table + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function indexDoesNotExist($indexName, $table) + { + return new self("Index '$indexName' does not exist on table '$table'.", self::INDEX_DOESNT_EXIST); + } + + /** + * @param string $indexName + * @param string $table + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function indexAlreadyExists($indexName, $table) + { + return new self("An index with name '$indexName' was already defined on table '$table'.", self::INDEX_ALREADY_EXISTS); + } + + /** + * @param string $columnName + * @param string $table + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function columnDoesNotExist($columnName, $table) + { + return new self("There is no column with name '$columnName' on table '$table'.", self::COLUMN_DOESNT_EXIST); + } + + /** + * @param string $tableName + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function tableAlreadyExists($tableName) + { + return new self("The table with name '".$tableName."' already exists.", self::TABLE_ALREADY_EXISTS); + } + + /** + * @param string $tableName + * @param string $columnName + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function columnAlreadyExists($tableName, $columnName) + { + return new self( + "The column '".$columnName."' on table '".$tableName."' already exists.", self::COLUMN_ALREADY_EXISTS + ); + } + + /** + * @param string $sequenceName + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function sequenceAlreadyExists($sequenceName) + { + return new self("The sequence '".$sequenceName."' already exists.", self::SEQUENCE_ALREADY_EXISTS); + } + + /** + * @param string $sequenceName + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function sequenceDoesNotExist($sequenceName) + { + return new self("There exists no sequence with the name '".$sequenceName."'.", self::SEQUENCE_DOENST_EXIST); + } + + /** + * @param string $fkName + * @param string $table + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function foreignKeyDoesNotExist($fkName, $table) + { + return new self("There exists no foreign key with the name '$fkName' on table '$table'.", self::FOREIGNKEY_DOESNT_EXIST); + } + + /** + * @param \Doctrine\DBAL\Schema\Table $localTable + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function namedForeignKeyRequired(Table $localTable, ForeignKeyConstraint $foreignKey) + { + return new self( + "The performed schema operation on ".$localTable->getName()." requires a named foreign key, ". + "but the given foreign key from (".implode(", ", $foreignKey->getColumns()).") onto foreign table ". + "'".$foreignKey->getForeignTableName()."' (".implode(", ", $foreignKey->getForeignColumns()).") is currently ". + "unnamed." + ); + } + + /** + * @param string $changeName + * + * @return \Doctrine\DBAL\Schema\SchemaException + */ + static public function alterTableChangeNotSupported($changeName) + { + return new self ("Alter table change not supported, given '$changeName'"); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Sequence.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Sequence.php new file mode 100644 index 0000000..1077237 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Sequence.php @@ -0,0 +1,135 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Schema\Visitor\Visitor; + +/** + * Sequence structure. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class Sequence extends AbstractAsset +{ + /** + * @var integer + */ + protected $_allocationSize = 1; + + /** + * @var integer + */ + protected $_initialValue = 1; + + /** + * @param string $name + * @param integer $allocationSize + * @param integer $initialValue + */ + public function __construct($name, $allocationSize=1, $initialValue=1) + { + $this->_setName($name); + $this->_allocationSize = (is_numeric($allocationSize))?$allocationSize:1; + $this->_initialValue = (is_numeric($initialValue))?$initialValue:1; + } + + /** + * @return integer + */ + public function getAllocationSize() + { + return $this->_allocationSize; + } + + /** + * @return integer + */ + public function getInitialValue() + { + return $this->_initialValue; + } + + /** + * @param integer $allocationSize + * + * @return void + */ + public function setAllocationSize($allocationSize) + { + $this->_allocationSize = (is_numeric($allocationSize))?$allocationSize:1; + } + + /** + * @param integer $initialValue + * + * @return void + */ + public function setInitialValue($initialValue) + { + $this->_initialValue = (is_numeric($initialValue))?$initialValue:1; + } + + /** + * Checks if this sequence is an autoincrement sequence for a given table. + * + * This is used inside the comparator to not report sequences as missing, + * when the "from" schema implicitly creates the sequences. + * + * @param \Doctrine\DBAL\Schema\Table $table + * + * @return boolean + */ + public function isAutoIncrementsFor(Table $table) + { + if ( ! $table->hasPrimaryKey()) { + return false; + } + + $pkColumns = $table->getPrimaryKey()->getColumns(); + + if (count($pkColumns) != 1) { + return false; + } + + $column = $table->getColumn($pkColumns[0]); + + if ( ! $column->getAutoincrement()) { + return false; + } + + $sequenceName = $this->getShortestName($table->getNamespaceName()); + $tableName = $table->getShortestName($table->getNamespaceName()); + $tableSequenceName = sprintf('%s_%s_seq', $tableName, $pkColumns[0]); + + return $tableSequenceName === $sequenceName; + } + + /** + * @param \Doctrine\DBAL\Schema\Visitor\Visitor $visitor + * + * @return void + */ + public function visit(Visitor $visitor) + { + $visitor->acceptSequence($this); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php new file mode 100644 index 0000000..e67f41d --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/SqliteSchemaManager.php @@ -0,0 +1,390 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\DBALException; + +/** + * Sqlite SchemaManager. + * + * @author Konsta Vesterinen + * @author Lukas Smith (PEAR MDB2 library) + * @author Jonathan H. Wage + * @author Martin Hasoň + * @since 2.0 + */ +class SqliteSchemaManager extends AbstractSchemaManager +{ + /** + * {@inheritdoc} + */ + public function dropDatabase($database) + { + if (file_exists($database)) { + unlink($database); + } + } + + /** + * {@inheritdoc} + */ + public function createDatabase($database) + { + $params = $this->_conn->getParams(); + $driver = $params['driver']; + $options = array( + 'driver' => $driver, + 'path' => $database + ); + $conn = \Doctrine\DBAL\DriverManager::getConnection($options); + $conn->connect(); + $conn->close(); + } + + /** + * {@inheritdoc} + */ + public function renameTable($name, $newName) + { + $tableDiff = new TableDiff($name); + $tableDiff->fromTable = $this->listTableDetails($name); + $tableDiff->newName = $newName; + $this->alterTable($tableDiff); + } + + /** + * {@inheritdoc} + */ + public function createForeignKey(ForeignKeyConstraint $foreignKey, $table) + { + $tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table); + $tableDiff->addedForeignKeys[] = $foreignKey; + + $this->alterTable($tableDiff); + } + + /** + * {@inheritdoc} + */ + public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table) + { + $tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table); + $tableDiff->changedForeignKeys[] = $foreignKey; + + $this->alterTable($tableDiff); + } + + /** + * {@inheritdoc} + */ + public function dropForeignKey($foreignKey, $table) + { + $tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table); + $tableDiff->removedForeignKeys[] = $foreignKey; + + $this->alterTable($tableDiff); + } + + /** + * {@inheritdoc} + */ + public function listTableForeignKeys($table, $database = null) + { + if (null === $database) { + $database = $this->_conn->getDatabase(); + } + $sql = $this->_platform->getListTableForeignKeysSQL($table, $database); + $tableForeignKeys = $this->_conn->fetchAll($sql); + + if ( ! empty($tableForeignKeys)) { + $createSql = $this->_conn->fetchAll("SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = 'table' AND name = '$table'"); + $createSql = isset($createSql[0]['sql']) ? $createSql[0]['sql'] : ''; + if (preg_match_all('# + (?:CONSTRAINT\s+([^\s]+)\s+)? + (?:FOREIGN\s+KEY[^\)]+\)\s*)? + REFERENCES\s+[^\s]+\s+(?:\([^\)]+\))? + (?: + [^,]*? + (NOT\s+DEFERRABLE|DEFERRABLE) + (?:\s+INITIALLY\s+(DEFERRED|IMMEDIATE))? + )?#isx', + $createSql, $match)) { + + $names = array_reverse($match[1]); + $deferrable = array_reverse($match[2]); + $deferred = array_reverse($match[3]); + } else { + $names = $deferrable = $deferred = array(); + } + + foreach ($tableForeignKeys as $key => $value) { + $id = $value['id']; + $tableForeignKeys[$key]['constraint_name'] = isset($names[$id]) && '' != $names[$id] ? $names[$id] : $id; + $tableForeignKeys[$key]['deferrable'] = isset($deferrable[$id]) && 'deferrable' == strtolower($deferrable[$id]) ? true : false; + $tableForeignKeys[$key]['deferred'] = isset($deferred[$id]) && 'deferred' == strtolower($deferred[$id]) ? true : false; + } + } + + return $this->_getPortableTableForeignKeysList($tableForeignKeys); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableDefinition($table) + { + return $table['name']; + } + + /** + * {@inheritdoc} + * + * @license New BSD License + * @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html + */ + protected function _getPortableTableIndexesList($tableIndexes, $tableName=null) + { + $indexBuffer = array(); + + // fetch primary + $stmt = $this->_conn->executeQuery( "PRAGMA TABLE_INFO ('$tableName')" ); + $indexArray = $stmt->fetchAll(\PDO::FETCH_ASSOC); + foreach($indexArray as $indexColumnRow) { + if($indexColumnRow['pk'] == "1") { + $indexBuffer[] = array( + 'key_name' => 'primary', + 'primary' => true, + 'non_unique' => false, + 'column_name' => $indexColumnRow['name'] + ); + } + } + + // fetch regular indexes + foreach($tableIndexes as $tableIndex) { + // Ignore indexes with reserved names, e.g. autoindexes + if (strpos($tableIndex['name'], 'sqlite_') !== 0) { + $keyName = $tableIndex['name']; + $idx = array(); + $idx['key_name'] = $keyName; + $idx['primary'] = false; + $idx['non_unique'] = $tableIndex['unique']?false:true; + + $stmt = $this->_conn->executeQuery( "PRAGMA INDEX_INFO ( '{$keyName}' )" ); + $indexArray = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + foreach ( $indexArray as $indexColumnRow ) { + $idx['column_name'] = $indexColumnRow['name']; + $indexBuffer[] = $idx; + } + } + } + + return parent::_getPortableTableIndexesList($indexBuffer, $tableName); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableIndexDefinition($tableIndex) + { + return array( + 'name' => $tableIndex['name'], + 'unique' => (bool) $tableIndex['unique'] + ); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnList($table, $database, $tableColumns) + { + $list = parent::_getPortableTableColumnList($table, $database, $tableColumns); + $autoincrementColumn = null; + $autoincrementCount = 0; + foreach ($tableColumns as $tableColumn) { + if ('1' == $tableColumn['pk']) { + $autoincrementCount++; + if (null === $autoincrementColumn && 'integer' == strtolower($tableColumn['type'])) { + $autoincrementColumn = $tableColumn['name']; + } + } + } + + if (1 == $autoincrementCount && null !== $autoincrementColumn) { + foreach ($list as $column) { + if ($autoincrementColumn == $column->getName()) { + $column->setAutoincrement(true); + } + } + } + + return $list; + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableColumnDefinition($tableColumn) + { + $e = explode('(', $tableColumn['type']); + $tableColumn['type'] = $e[0]; + if (isset($e[1])) { + $length = trim($e[1], ')'); + $tableColumn['length'] = $length; + } + + $dbType = strtolower($tableColumn['type']); + $length = isset($tableColumn['length']) ? $tableColumn['length'] : null; + $unsigned = (boolean) isset($tableColumn['unsigned']) ? $tableColumn['unsigned'] : false; + $fixed = false; + $type = $this->_platform->getDoctrineTypeMapping($dbType); + $default = $tableColumn['dflt_value']; + if ($default == 'NULL') { + $default = null; + } + if ($default !== null) { + // SQLite returns strings wrapped in single quotes, so we need to strip them + $default = preg_replace("/^'(.*)'$/", '\1', $default); + } + $notnull = (bool) $tableColumn['notnull']; + + if ( ! isset($tableColumn['name'])) { + $tableColumn['name'] = ''; + } + + $precision = null; + $scale = null; + + switch ($dbType) { + case 'char': + $fixed = true; + break; + case 'float': + case 'double': + case 'real': + case 'decimal': + case 'numeric': + if (isset($tableColumn['length'])) { + if (strpos($tableColumn['length'], ',') === false) { + $tableColumn['length'] .= ",0"; + } + list($precision, $scale) = array_map('trim', explode(',', $tableColumn['length'])); + } + $length = null; + break; + } + + $options = array( + 'length' => $length, + 'unsigned' => (bool) $unsigned, + 'fixed' => $fixed, + 'notnull' => $notnull, + 'default' => $default, + 'precision' => $precision, + 'scale' => $scale, + 'autoincrement' => false, + ); + + return new Column($tableColumn['name'], \Doctrine\DBAL\Types\Type::getType($type), $options); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableViewDefinition($view) + { + return new View($view['name'], $view['sql']); + } + + /** + * {@inheritdoc} + */ + protected function _getPortableTableForeignKeysList($tableForeignKeys) + { + $list = array(); + foreach ($tableForeignKeys as $value) { + $value = array_change_key_case($value, CASE_LOWER); + $name = $value['constraint_name']; + if ( ! isset($list[$name])) { + if ( ! isset($value['on_delete']) || $value['on_delete'] == "RESTRICT") { + $value['on_delete'] = null; + } + if ( ! isset($value['on_update']) || $value['on_update'] == "RESTRICT") { + $value['on_update'] = null; + } + + $list[$name] = array( + 'name' => $name, + 'local' => array(), + 'foreign' => array(), + 'foreignTable' => $value['table'], + 'onDelete' => $value['on_delete'], + 'onUpdate' => $value['on_update'], + 'deferrable' => $value['deferrable'], + 'deferred'=> $value['deferred'], + ); + } + $list[$name]['local'][] = $value['from']; + $list[$name]['foreign'][] = $value['to']; + } + + $result = array(); + foreach($list as $constraint) { + $result[] = new ForeignKeyConstraint( + array_values($constraint['local']), $constraint['foreignTable'], + array_values($constraint['foreign']), $constraint['name'], + array( + 'onDelete' => $constraint['onDelete'], + 'onUpdate' => $constraint['onUpdate'], + 'deferrable' => $constraint['deferrable'], + 'deferred'=> $constraint['deferred'], + ) + ); + } + + return $result; + } + + /** + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey + * @param \Doctrine\DBAL\Schema\Table|string $table + * + * @return \Doctrine\DBAL\Schema\TableDiff + * + * @throws \Doctrine\DBAL\DBALException + */ + private function getTableDiffForAlterForeignKey(ForeignKeyConstraint $foreignKey, $table) + { + if ( ! $table instanceof Table) { + $tableDetails = $this->tryMethod('listTableDetails', $table); + if (false === $table) { + throw new DBALException(sprintf('Sqlite schema manager requires to modify foreign keys table definition "%s".', $table)); + } + + $table = $tableDetails; + } + + $tableDiff = new TableDiff($table->getName()); + $tableDiff->fromTable = $table; + + return $tableDiff; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/AbstractSchemaSynchronizer.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/AbstractSchemaSynchronizer.php new file mode 100644 index 0000000..fc1bec1 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/AbstractSchemaSynchronizer.php @@ -0,0 +1,65 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Synchronizer; + +use Doctrine\DBAL\Connection; + +/** + * Abstract schema synchronizer with methods for executing batches of SQL. + */ +abstract class AbstractSchemaSynchronizer implements SchemaSynchronizer +{ + /** + * @var \Doctrine\DBAL\Connection + */ + protected $conn; + + /** + * @param \Doctrine\DBAL\Connection $conn + */ + public function __construct(Connection $conn) + { + $this->conn = $conn; + } + + /** + * @param array $sql + */ + protected function processSqlSafely(array $sql) + { + foreach ($sql as $s) { + try { + $this->conn->exec($s); + } catch(\Exception $e) { + + } + } + } + + /** + * @param array $sql + */ + protected function processSql(array $sql) + { + foreach ($sql as $s) { + $this->conn->exec($s); + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/SchemaSynchronizer.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/SchemaSynchronizer.php new file mode 100644 index 0000000..1a5552e --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/SchemaSynchronizer.php @@ -0,0 +1,101 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Synchronizer; + +use Doctrine\DBAL\Schema\Schema; + +/** + * The synchronizer knows how to synchronize a schema with the configured + * database. + * + * @author Benjamin Eberlei + */ +interface SchemaSynchronizer +{ + /** + * Gets the SQL statements that can be executed to create the schema. + * + * @param \Doctrine\DBAL\Schema\Schema $createSchema + * + * @return array + */ + function getCreateSchema(Schema $createSchema); + + /** + * Gets the SQL Statements to update given schema with the underlying db. + * + * @param \Doctrine\DBAL\Schema\Schema $toSchema + * @param boolean $noDrops + * + * @return array + */ + function getUpdateSchema(Schema $toSchema, $noDrops = false); + + /** + * Gets the SQL Statements to drop the given schema from underlying db. + * + * @param \Doctrine\DBAL\Schema\Schema $dropSchema + * + * @return array + */ + function getDropSchema(Schema $dropSchema); + + /** + * Gets the SQL statements to drop all schema assets from underlying db. + * + * @return array + */ + function getDropAllSchema(); + + /** + * Creates the Schema. + * + * @param \Doctrine\DBAL\Schema\Schema $createSchema + * + * @return void + */ + function createSchema(Schema $createSchema); + + /** + * Updates the Schema to new schema version. + * + * @param \Doctrine\DBAL\Schema\Schema $toSchema + * @param boolean $noDrops + * + * @return void + */ + function updateSchema(Schema $toSchema, $noDrops = false); + + /** + * Drops the given database schema from the underlying db. + * + * @param \Doctrine\DBAL\Schema\Schema $dropSchema + * + * @return void + */ + function dropSchema(Schema $dropSchema); + + /** + * Drops all assets from the underlying db. + * + * @return void + */ + function dropAllSchema(); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/SingleDatabaseSynchronizer.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/SingleDatabaseSynchronizer.php new file mode 100644 index 0000000..9e28cb0 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Synchronizer/SingleDatabaseSynchronizer.php @@ -0,0 +1,176 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Synchronizer; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\Comparator; +use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector; + +/** + * Schema Synchronizer for Default DBAL Connection. + * + * @author Benjamin Eberlei + */ +class SingleDatabaseSynchronizer extends AbstractSchemaSynchronizer +{ + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $platform; + + /** + * @param \Doctrine\DBAL\Connection $conn + */ + public function __construct(Connection $conn) + { + parent::__construct($conn); + $this->platform = $conn->getDatabasePlatform(); + } + + /** + * {@inheritdoc} + */ + public function getCreateSchema(Schema $createSchema) + { + return $createSchema->toSql($this->platform); + } + + + /** + * {@inheritdoc} + */ + public function getUpdateSchema(Schema $toSchema, $noDrops = false) + { + $comparator = new Comparator(); + $sm = $this->conn->getSchemaManager(); + + $fromSchema = $sm->createSchema(); + $schemaDiff = $comparator->compare($fromSchema, $toSchema); + + if ($noDrops) { + return $schemaDiff->toSaveSql($this->platform); + } + + return $schemaDiff->toSql($this->platform); + } + + /** + * {@inheritdoc} + */ + public function getDropSchema(Schema $dropSchema) + { + $visitor = new DropSchemaSqlCollector($this->platform); + $sm = $this->conn->getSchemaManager(); + + $fullSchema = $sm->createSchema(); + + foreach ($fullSchema->getTables() as $table) { + if ( $dropSchema->hasTable($table->getName())) { + $visitor->acceptTable($table); + } + + foreach ($table->getForeignKeys() as $foreignKey) { + if ( ! $dropSchema->hasTable($table->getName())) { + continue; + } + + if ( ! $dropSchema->hasTable($foreignKey->getForeignTableName())) { + continue; + } + + $visitor->acceptForeignKey($table, $foreignKey); + } + } + + if ( ! $this->platform->supportsSequences()) { + return $visitor->getQueries(); + } + + foreach ($dropSchema->getSequences() as $sequence) { + $visitor->acceptSequence($sequence); + } + + foreach ($dropSchema->getTables() as $table) { + if ( ! $table->hasPrimaryKey()) { + continue; + } + + $columns = $table->getPrimaryKey()->getColumns(); + if (count($columns) > 1) { + continue; + } + + $checkSequence = $table->getName() . "_" . $columns[0] . "_seq"; + if ($fullSchema->hasSequence($checkSequence)) { + $visitor->acceptSequence($fullSchema->getSequence($checkSequence)); + } + } + + return $visitor->getQueries(); + } + + /** + * {@inheritdoc} + */ + public function getDropAllSchema() + { + $sm = $this->conn->getSchemaManager(); + $visitor = new DropSchemaSqlCollector($this->platform); + + /* @var $schema \Doctrine\DBAL\Schema\Schema */ + $schema = $sm->createSchema(); + $schema->visit($visitor); + + return $visitor->getQueries(); + } + + /** + * {@inheritdoc} + */ + public function createSchema(Schema $createSchema) + { + $this->processSql($this->getCreateSchema($createSchema)); + } + + /** + * {@inheritdoc} + */ + public function updateSchema(Schema $toSchema, $noDrops = false) + { + $this->processSql($this->getUpdateSchema($toSchema, $noDrops)); + } + + /** + * {@inheritdoc} + */ + public function dropSchema(Schema $dropSchema) + { + $this->processSqlSafely($this->getDropSchema($dropSchema)); + } + + /** + * {@inheritdoc} + */ + public function dropAllSchema() + { + $this->processSql($this->getDropAllSchema()); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Table.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Table.php new file mode 100644 index 0000000..37a9962 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Table.php @@ -0,0 +1,771 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Schema\Visitor\Visitor; +use Doctrine\DBAL\DBALException; + +/** + * Object Representation of a table. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class Table extends AbstractAsset +{ + /** + * @var string + */ + protected $_name = null; + + /** + * @var \Doctrine\DBAL\Schema\Column[] + */ + protected $_columns = array(); + + /** + * @var \Doctrine\DBAL\Schema\Index[] + */ + protected $_indexes = array(); + + /** + * @var string + */ + protected $_primaryKeyName = false; + + /** + * @var \Doctrine\DBAL\Schema\ForeignKeyConstraint[] + */ + protected $_fkConstraints = array(); + + /** + * @var array + */ + protected $_options = array(); + + /** + * @var \Doctrine\DBAL\Schema\SchemaConfig + */ + protected $_schemaConfig = null; + + /** + * @param string $tableName + * @param array $columns + * @param array $indexes + * @param array $fkConstraints + * @param integer $idGeneratorType + * @param array $options + * + * @throws \Doctrine\DBAL\DBALException + */ + public function __construct($tableName, array $columns=array(), array $indexes=array(), array $fkConstraints=array(), $idGeneratorType = 0, array $options=array()) + { + if (strlen($tableName) == 0) { + throw DBALException::invalidTableName($tableName); + } + + $this->_setName($tableName); + $this->_idGeneratorType = $idGeneratorType; + + foreach ($columns as $column) { + $this->_addColumn($column); + } + + foreach ($indexes as $idx) { + $this->_addIndex($idx); + } + + foreach ($fkConstraints as $constraint) { + $this->_addForeignKeyConstraint($constraint); + } + + $this->_options = $options; + } + + /** + * @param \Doctrine\DBAL\Schema\SchemaConfig $schemaConfig + * + * @return void + */ + public function setSchemaConfig(SchemaConfig $schemaConfig) + { + $this->_schemaConfig = $schemaConfig; + } + + /** + * @return integer + */ + protected function _getMaxIdentifierLength() + { + if ($this->_schemaConfig instanceof SchemaConfig) { + return $this->_schemaConfig->getMaxIdentifierLength(); + } else { + return 63; + } + } + + /** + * Sets the Primary Key. + * + * @param array $columns + * @param string|boolean $indexName + * + * @return \Doctrine\DBAL\Schema\Table + */ + public function setPrimaryKey(array $columns, $indexName = false) + { + $primaryKey = $this->_createIndex($columns, $indexName ?: "primary", true, true); + + foreach ($columns as $columnName) { + $column = $this->getColumn($columnName); + $column->setNotnull(true); + } + + return $primaryKey; + } + + /** + * @param array $columnNames + * @param string|null $indexName + * @param array $flags + * + * @return \Doctrine\DBAL\Schema\Table + */ + public function addIndex(array $columnNames, $indexName = null, array $flags = array()) + { + if($indexName == null) { + $indexName = $this->_generateIdentifierName( + array_merge(array($this->getName()), $columnNames), "idx", $this->_getMaxIdentifierLength() + ); + } + + return $this->_createIndex($columnNames, $indexName, false, false, $flags); + } + + /** + * Drops the primary key from this table. + * + * @return void + */ + public function dropPrimaryKey() + { + $this->dropIndex($this->_primaryKeyName); + $this->_primaryKeyName = false; + } + + /** + * Drops an index from this table. + * + * @param string $indexName The index name. + * + * @return void + * + * @throws \Doctrine\DBAL\Schema\SchemaException If the index does not exist. + */ + public function dropIndex($indexName) + { + $indexName = strtolower($indexName); + if ( ! $this->hasIndex($indexName)) { + throw SchemaException::indexDoesNotExist($indexName, $this->_name); + } + unset($this->_indexes[$indexName]); + } + + /** + * @param array $columnNames + * @param string|null $indexName + * + * @return \Doctrine\DBAL\Schema\Table + */ + public function addUniqueIndex(array $columnNames, $indexName = null) + { + if ($indexName === null) { + $indexName = $this->_generateIdentifierName( + array_merge(array($this->getName()), $columnNames), "uniq", $this->_getMaxIdentifierLength() + ); + } + + return $this->_createIndex($columnNames, $indexName, true, false); + } + + /** + * Checks if an index begins in the order of the given columns. + * + * @param array $columnsNames + * + * @return boolean + */ + public function columnsAreIndexed(array $columnsNames) + { + foreach ($this->getIndexes() as $index) { + /* @var $index Index */ + if ($index->spansColumns($columnsNames)) { + return true; + } + } + + return false; + } + + /** + * @param array $columnNames + * @param string $indexName + * @param boolean $isUnique + * @param boolean $isPrimary + * @param array $flags + * + * @return \Doctrine\DBAL\Schema\Table + * + * @throws \Doctrine\DBAL\Schema\SchemaException + */ + private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = array()) + { + if (preg_match('(([^a-zA-Z0-9_]+))', $indexName)) { + throw SchemaException::indexNameInvalid($indexName); + } + + foreach ($columnNames as $columnName => $indexColOptions) { + if (is_numeric($columnName) && is_string($indexColOptions)) { + $columnName = $indexColOptions; + } + + if ( ! $this->hasColumn($columnName)) { + throw SchemaException::columnDoesNotExist($columnName, $this->_name); + } + } + + $this->_addIndex(new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags)); + + return $this; + } + + /** + * @param string $columnName + * @param string $typeName + * @param array $options + * + * @return \Doctrine\DBAL\Schema\Column + */ + public function addColumn($columnName, $typeName, array $options=array()) + { + $column = new Column($columnName, Type::getType($typeName), $options); + + $this->_addColumn($column); + + return $column; + } + + /** + * Renames a Column. + * + * @param string $oldColumnName + * @param string $newColumnName + * + * @return \Doctrine\DBAL\Schema\Table + * + * @throws \Doctrine\DBAL\DBALException + */ + public function renameColumn($oldColumnName, $newColumnName) + { + throw new DBALException("Table#renameColumn() was removed, because it drops and recreates " . + "the column instead. There is no fix available, because a schema diff cannot reliably detect if a " . + "column was renamed or one column was created and another one dropped."); + } + + /** + * Change Column Details. + * + * @param string $columnName + * @param array $options + * + * @return \Doctrine\DBAL\Schema\Table + */ + public function changeColumn($columnName, array $options) + { + $column = $this->getColumn($columnName); + $column->setOptions($options); + + return $this; + } + + /** + * Drops a Column from the Table. + * + * @param string $columnName + * + * @return \Doctrine\DBAL\Schema\Table + */ + public function dropColumn($columnName) + { + $columnName = strtolower($columnName); + unset($this->_columns[$columnName]); + + return $this; + } + + /** + * Adds a foreign key constraint. + * + * Name is inferred from the local columns. + * + * @param \Doctrine\DBAL\Schema\Table $foreignTable + * @param array $localColumnNames + * @param array $foreignColumnNames + * @param array $options + * @param string|null $constraintName + * + * @return \Doctrine\DBAL\Schema\Table + */ + public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array(), $constraintName = null) + { + $constraintName = $constraintName ?: $this->_generateIdentifierName(array_merge((array)$this->getName(), $localColumnNames), "fk", $this->_getMaxIdentifierLength()); + + return $this->addNamedForeignKeyConstraint($constraintName, $foreignTable, $localColumnNames, $foreignColumnNames, $options); + } + + /** + * Adds a foreign key constraint. + * + * Name is to be generated by the database itself. + * + * @deprecated Use {@link addForeignKeyConstraint} + * + * @param \Doctrine\DBAL\Schema\Table $foreignTable + * @param array $localColumnNames + * @param array $foreignColumnNames + * @param array $options + * + * @return \Doctrine\DBAL\Schema\Table + */ + public function addUnnamedForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array()) + { + return $this->addForeignKeyConstraint($foreignTable, $localColumnNames, $foreignColumnNames, $options); + } + + /** + * Adds a foreign key constraint with a given name. + * + * @deprecated Use {@link addForeignKeyConstraint} + * + * @param string $name + * @param \Doctrine\DBAL\Schema\Table $foreignTable + * @param array $localColumnNames + * @param array $foreignColumnNames + * @param array $options + * + * @return \Doctrine\DBAL\Schema\Table + * + * @throws \Doctrine\DBAL\Schema\SchemaException + */ + public function addNamedForeignKeyConstraint($name, $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=array()) + { + if ($foreignTable instanceof Table) { + foreach ($foreignColumnNames as $columnName) { + if ( ! $foreignTable->hasColumn($columnName)) { + throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName()); + } + } + } + + foreach ($localColumnNames as $columnName) { + if ( ! $this->hasColumn($columnName)) { + throw SchemaException::columnDoesNotExist($columnName, $this->_name); + } + } + + $constraint = new ForeignKeyConstraint( + $localColumnNames, $foreignTable, $foreignColumnNames, $name, $options + ); + $this->_addForeignKeyConstraint($constraint); + + return $this; + } + + /** + * @param string $name + * @param string $value + * + * @return \Doctrine\DBAL\Schema\Table + */ + public function addOption($name, $value) + { + $this->_options[$name] = $value; + + return $this; + } + + /** + * @param \Doctrine\DBAL\Schema\Column $column + * + * @return void + * + * @throws \Doctrine\DBAL\Schema\SchemaException + */ + protected function _addColumn(Column $column) + { + $columnName = $column->getName(); + $columnName = strtolower($columnName); + + if (isset($this->_columns[$columnName])) { + throw SchemaException::columnAlreadyExists($this->getName(), $columnName); + } + + $this->_columns[$columnName] = $column; + } + + /** + * Adds an index to the table. + * + * @param \Doctrine\DBAL\Schema\Index $indexCandidate + * + * @return \Doctrine\DBAL\Schema\Table + * + * @throws \Doctrine\DBAL\Schema\SchemaException + */ + protected function _addIndex(Index $indexCandidate) + { + // check for duplicates + foreach ($this->_indexes as $existingIndex) { + if ($indexCandidate->isFullfilledBy($existingIndex)) { + return $this; + } + } + + $indexName = $indexCandidate->getName(); + $indexName = strtolower($indexName); + + if (isset($this->_indexes[$indexName]) || ($this->_primaryKeyName != false && $indexCandidate->isPrimary())) { + throw SchemaException::indexAlreadyExists($indexName, $this->_name); + } + + // remove overruled indexes + foreach ($this->_indexes as $idxKey => $existingIndex) { + if ($indexCandidate->overrules($existingIndex)) { + unset($this->_indexes[$idxKey]); + } + } + + if ($indexCandidate->isPrimary()) { + $this->_primaryKeyName = $indexName; + } + + $this->_indexes[$indexName] = $indexCandidate; + + return $this; + } + + /** + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $constraint + * + * @return void + */ + protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint) + { + $constraint->setLocalTable($this); + + if(strlen($constraint->getName())) { + $name = $constraint->getName(); + } else { + $name = $this->_generateIdentifierName( + array_merge((array)$this->getName(), $constraint->getLocalColumns()), "fk", $this->_getMaxIdentifierLength() + ); + } + $name = strtolower($name); + + $this->_fkConstraints[$name] = $constraint; + // add an explicit index on the foreign key columns. If there is already an index that fulfils this requirements drop the request. + // In the case of __construct calling this method during hydration from schema-details all the explicitly added indexes + // lead to duplicates. This creates computation overhead in this case, however no duplicate indexes are ever added (based on columns). + $this->addIndex($constraint->getColumns()); + } + + /** + * Returns whether this table has a foreign key constraint with the given name. + * + * @param string $constraintName + * + * @return boolean + */ + public function hasForeignKey($constraintName) + { + $constraintName = strtolower($constraintName); + + return isset($this->_fkConstraints[$constraintName]); + } + + /** + * Returns the foreign key constraint with the given name. + * + * @param string $constraintName The constraint name. + * + * @return \Doctrine\DBAL\Schema\ForeignKeyConstraint + * + * @throws \Doctrine\DBAL\Schema\SchemaException If the foreign key does not exist. + */ + public function getForeignKey($constraintName) + { + $constraintName = strtolower($constraintName); + if(!$this->hasForeignKey($constraintName)) { + throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name); + } + + return $this->_fkConstraints[$constraintName]; + } + + /** + * Removes the foreign key constraint with the given name. + * + * @param string $constraintName The constraint name. + * + * @return void + * + * @throws \Doctrine\DBAL\Schema\SchemaException + */ + public function removeForeignKey($constraintName) + { + $constraintName = strtolower($constraintName); + if(!$this->hasForeignKey($constraintName)) { + throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name); + } + + unset($this->_fkConstraints[$constraintName]); + } + + /** + * @return \Doctrine\DBAL\Schema\Column[] + */ + public function getColumns() + { + $columns = $this->_columns; + + $pkCols = array(); + $fkCols = array(); + + if ($this->hasPrimaryKey()) { + $pkCols = $this->getPrimaryKey()->getColumns(); + } + foreach ($this->getForeignKeys() as $fk) { + /* @var $fk ForeignKeyConstraint */ + $fkCols = array_merge($fkCols, $fk->getColumns()); + } + $colNames = array_unique(array_merge($pkCols, $fkCols, array_keys($columns))); + + uksort($columns, function($a, $b) use($colNames) { + return (array_search($a, $colNames) >= array_search($b, $colNames)); + }); + + return $columns; + } + + /** + * Returns whether this table has a Column with the given name. + * + * @param string $columnName The column name. + * + * @return boolean + */ + public function hasColumn($columnName) + { + $columnName = $this->trimQuotes(strtolower($columnName)); + + return isset($this->_columns[$columnName]); + } + + /** + * Returns the Column with the given name. + * + * @param string $columnName The column name. + * + * @return \Doctrine\DBAL\Schema\Column + * + * @throws \Doctrine\DBAL\Schema\SchemaException If the column does not exist. + */ + public function getColumn($columnName) + { + $columnName = strtolower($this->trimQuotes($columnName)); + if ( ! $this->hasColumn($columnName)) { + throw SchemaException::columnDoesNotExist($columnName, $this->_name); + } + + return $this->_columns[$columnName]; + } + + /** + * Returns the primary key. + * + * @return \Doctrine\DBAL\Schema\Index|null The primary key, or null if this Table has no primary key. + */ + public function getPrimaryKey() + { + if ( ! $this->hasPrimaryKey()) { + return null; + } + + return $this->getIndex($this->_primaryKeyName); + } + + /** + * Returns the primary key columns. + * + * @return array + * + * @throws \Doctrine\DBAL\DBALException + */ + public function getPrimaryKeyColumns() + { + if ( ! $this->hasPrimaryKey()) { + throw new DBALException("Table " . $this->getName() . " has no primary key."); + } + + return $this->getPrimaryKey()->getColumns(); + } + + /** + * Returns whether this table has a primary key. + * + * @return boolean + */ + public function hasPrimaryKey() + { + return ($this->_primaryKeyName && $this->hasIndex($this->_primaryKeyName)); + } + + /** + * Returns whether this table has an Index with the given name. + * + * @param string $indexName The index name. + * + * @return boolean + */ + public function hasIndex($indexName) + { + $indexName = strtolower($indexName); + + return (isset($this->_indexes[$indexName])); + } + + /** + * Returns the Index with the given name. + * + * @param string $indexName The index name. + * + * @return \Doctrine\DBAL\Schema\Index + * + * @throws \Doctrine\DBAL\Schema\SchemaException If the index does not exist. + */ + public function getIndex($indexName) + { + $indexName = strtolower($indexName); + if ( ! $this->hasIndex($indexName)) { + throw SchemaException::indexDoesNotExist($indexName, $this->_name); + } + + return $this->_indexes[$indexName]; + } + + /** + * @return \Doctrine\DBAL\Schema\Index[] + */ + public function getIndexes() + { + return $this->_indexes; + } + + /** + * Returns the foreign key constraints. + * + * @return \Doctrine\DBAL\Schema\ForeignKeyConstraint[] + */ + public function getForeignKeys() + { + return $this->_fkConstraints; + } + + /** + * @param string $name + * + * @return boolean + */ + public function hasOption($name) + { + return isset($this->_options[$name]); + } + + /** + * @param string $name + * + * @return mixed + */ + public function getOption($name) + { + return $this->_options[$name]; + } + + /** + * @return array + */ + public function getOptions() + { + return $this->_options; + } + + /** + * @param \Doctrine\DBAL\Schema\Visitor\Visitor $visitor + * + * @return void + */ + public function visit(Visitor $visitor) + { + $visitor->acceptTable($this); + + foreach ($this->getColumns() as $column) { + $visitor->acceptColumn($this, $column); + } + + foreach ($this->getIndexes() as $index) { + $visitor->acceptIndex($this, $index); + } + + foreach ($this->getForeignKeys() as $constraint) { + $visitor->acceptForeignKey($this, $constraint); + } + } + + /** + * Clone of a Table triggers a deep clone of all affected assets. + * + * @return void + */ + public function __clone() + { + foreach ($this->_columns as $k => $column) { + $this->_columns[$k] = clone $column; + } + foreach ($this->_indexes as $k => $index) { + $this->_indexes[$k] = clone $index; + } + foreach ($this->_fkConstraints as $k => $fk) { + $this->_fkConstraints[$k] = clone $fk; + $this->_fkConstraints[$k]->setLocalTable($this); + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/TableDiff.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/TableDiff.php new file mode 100644 index 0000000..a61f0ce --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/TableDiff.php @@ -0,0 +1,141 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +/** + * Table Diff. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class TableDiff +{ + /** + * @var string + */ + public $name = null; + + /** + * @var string|boolean + */ + public $newName = false; + + /** + * All added fields. + * + * @var \Doctrine\DBAL\Schema\Column[] + */ + public $addedColumns; + + /** + * All changed fields. + * + * @var \Doctrine\DBAL\Schema\Column[] + */ + public $changedColumns = array(); + + /** + * All removed fields. + * + * @var \Doctrine\DBAL\Schema\Column[] + */ + public $removedColumns = array(); + + /** + * Columns that are only renamed from key to column instance name. + * + * @var \Doctrine\DBAL\Schema\Column[] + */ + public $renamedColumns = array(); + + /** + * All added indexes. + * + * @var \Doctrine\DBAL\Schema\Index[] + */ + public $addedIndexes = array(); + + /** + * All changed indexes. + * + * @var \Doctrine\DBAL\Schema\Index[] + */ + public $changedIndexes = array(); + + /** + * All removed indexes + * + * @var \Doctrine\DBAL\Schema\Index[] + */ + public $removedIndexes = array(); + + /** + * All added foreign key definitions + * + * @var \Doctrine\DBAL\Schema\ForeignKeyConstraint[] + */ + public $addedForeignKeys = array(); + + /** + * All changed foreign keys + * + * @var \Doctrine\DBAL\Schema\ForeignKeyConstraint[] + */ + public $changedForeignKeys = array(); + + /** + * All removed foreign keys + * + * @var \Doctrine\DBAL\Schema\ForeignKeyConstraint[] + */ + public $removedForeignKeys = array(); + + /** + * @var \Doctrine\DBAL\Schema\Table + */ + public $fromTable; + + /** + * Constructs an TableDiff object. + * + * @param string $tableName + * @param \Doctrine\DBAL\Schema\Column[] $addedColumns + * @param \Doctrine\DBAL\Schema\Column[] $changedColumns + * @param \Doctrine\DBAL\Schema\Column[] $removedColumns + * @param \Doctrine\DBAL\Schema\Index[] $addedIndexes + * @param \Doctrine\DBAL\Schema\Index[] $changedIndexes + * @param \Doctrine\DBAL\Schema\Index[] $removedIndexes + * @param \Doctrine\DBAL\Schema\Table|null $fromTable + */ + public function __construct($tableName, $addedColumns = array(), + $changedColumns = array(), $removedColumns = array(), $addedIndexes = array(), + $changedIndexes = array(), $removedIndexes = array(), Table $fromTable = null) + { + $this->name = $tableName; + $this->addedColumns = $addedColumns; + $this->changedColumns = $changedColumns; + $this->removedColumns = $removedColumns; + $this->addedIndexes = $addedIndexes; + $this->changedIndexes = $changedIndexes; + $this->removedIndexes = $removedIndexes; + $this->fromTable = $fromTable; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/View.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/View.php new file mode 100644 index 0000000..0ef7d30 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/View.php @@ -0,0 +1,53 @@ +. + */ + +namespace Doctrine\DBAL\Schema; + +/** + * Representation of a Database View. + * + * @link www.doctrine-project.org + * @since 1.0 + * @author Benjamin Eberlei + */ +class View extends AbstractAsset +{ + /** + * @var string + */ + private $_sql; + + /** + * @param string $name + * @param string $sql + */ + public function __construct($name, $sql) + { + $this->_setName($name); + $this->_sql = $sql; + } + + /** + * @return string + */ + public function getSql() + { + return $this->_sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/AbstractVisitor.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/AbstractVisitor.php new file mode 100644 index 0000000..af1b63e --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/AbstractVisitor.php @@ -0,0 +1,79 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Visitor; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\Index; + +/** + * Abstract Visitor with empty methods for easy extension. + */ +class AbstractVisitor implements Visitor +{ + /** + * @param \Doctrine\DBAL\Schema\Schema $schema + */ + public function acceptSchema(Schema $schema) + { + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + */ + public function acceptTable(Table $table) + { + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param \Doctrine\DBAL\Schema\Column $column + */ + public function acceptColumn(Table $table, Column $column) + { + } + + /** + * @param \Doctrine\DBAL\Schema\Table $localTable + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $fkConstraint + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param \Doctrine\DBAL\Schema\Index $index + */ + public function acceptIndex(Table $table, Index $index) + { + } + + /** + * @param \Doctrine\DBAL\Schema\Sequence $sequence + */ + public function acceptSequence(Sequence $sequence) + { + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/CreateSchemaSqlCollector.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/CreateSchemaSqlCollector.php new file mode 100644 index 0000000..15cf108 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/CreateSchemaSqlCollector.php @@ -0,0 +1,159 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Visitor; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Sequence; + +class CreateSchemaSqlCollector extends AbstractVisitor +{ + /** + * @var array + */ + private $createTableQueries = array(); + + /** + * @var array + */ + private $createSequenceQueries = array(); + + /** + * @var array + */ + private $createFkConstraintQueries = array(); + + /** + * + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $platform = null; + + /** + * @param AbstractPlatform $platform + */ + public function __construct(AbstractPlatform $platform) + { + $this->platform = $platform; + } + + /** + * {@inheritdoc} + */ + public function acceptTable(Table $table) + { + $namespace = $this->getNamespace($table); + + $this->createTableQueries[$namespace] = array_merge( + $this->createTableQueries[$namespace], + $this->platform->getCreateTableSQL($table) + ); + } + + /** + * {@inheritdoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + $namespace = $this->getNamespace($localTable); + + if ($this->platform->supportsForeignKeyConstraints()) { + $this->createFkConstraintQueries[$namespace] = array_merge( + $this->createFkConstraintQueries[$namespace], + (array) $this->platform->getCreateForeignKeySQL( + $fkConstraint, $localTable + ) + ); + } + } + + /** + * {@inheritdoc} + */ + public function acceptSequence(Sequence $sequence) + { + $namespace = $this->getNamespace($sequence); + + $this->createSequenceQueries[$namespace] = array_merge( + $this->createSequenceQueries[$namespace], + (array)$this->platform->getCreateSequenceSQL($sequence) + ); + } + + /** + * @param \Doctrine\DBAL\Schema\AbstractAsset $asset + * + * @return string + */ + private function getNamespace($asset) + { + $namespace = $asset->getNamespaceName() ?: 'default'; + + if ( !isset($this->createTableQueries[$namespace])) { + $this->createTableQueries[$namespace] = array(); + $this->createSequenceQueries[$namespace] = array(); + $this->createFkConstraintQueries[$namespace] = array(); + } + + return $namespace; + } + + /** + * @return void + */ + public function resetQueries() + { + $this->createTableQueries = array(); + $this->createSequenceQueries = array(); + $this->createFkConstraintQueries = array(); + } + + /** + * Gets all queries collected so far. + * + * @return array + */ + public function getQueries() + { + $sql = array(); + + foreach (array_keys($this->createTableQueries) as $namespace) { + if ($this->platform->supportsSchemas() && $this->platform->schemaNeedsCreation($namespace)) { + $query = $this->platform->getCreateSchemaSQL($namespace); + array_push($sql, $query); + } + } + + foreach ($this->createTableQueries as $schemaSql) { + $sql = array_merge($sql, $schemaSql); + } + + foreach ($this->createSequenceQueries as $schemaSql) { + $sql = array_merge($sql, $schemaSql); + } + + foreach ($this->createFkConstraintQueries as $schemaSql) { + $sql = array_merge($sql, $schemaSql); + } + + return $sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/DropSchemaSqlCollector.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/DropSchemaSqlCollector.php new file mode 100644 index 0000000..047ac98 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/DropSchemaSqlCollector.php @@ -0,0 +1,127 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Visitor; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\SchemaException; + +/** + * Gathers SQL statements that allow to completely drop the current schema. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +class DropSchemaSqlCollector extends AbstractVisitor +{ + /** + * @var \SplObjectStorage + */ + private $constraints; + + /** + * @var \SplObjectStorage + */ + private $sequences; + + /** + * @var \SplObjectStorage + */ + private $tables; + + /** + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + private $platform; + + /** + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + */ + public function __construct(AbstractPlatform $platform) + { + $this->platform = $platform; + $this->clearQueries(); + } + + /** + * {@inheritdoc} + */ + public function acceptTable(Table $table) + { + $this->tables->attach($table); + } + + /** + * {@inheritdoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + if (strlen($fkConstraint->getName()) == 0) { + throw SchemaException::namedForeignKeyRequired($localTable, $fkConstraint); + } + + $this->constraints->attach($fkConstraint); + $this->constraints[$fkConstraint] = $localTable; + } + + /** + * {@inheritdoc} + */ + public function acceptSequence(Sequence $sequence) + { + $this->sequences->attach($sequence); + } + + /** + * @return void + */ + public function clearQueries() + { + $this->constraints = new \SplObjectStorage(); + $this->sequences = new \SplObjectStorage(); + $this->tables = new \SplObjectStorage(); + } + + /** + * @return array + */ + public function getQueries() + { + $sql = array(); + + foreach ($this->constraints as $fkConstraint) { + $localTable = $this->constraints[$fkConstraint]; + $sql[] = $this->platform->getDropForeignKeySQL($fkConstraint, $localTable); + } + + foreach ($this->sequences as $sequence) { + $sql[] = $this->platform->getDropSequenceSQL($sequence); + } + + foreach ($this->tables as $table) { + $sql[] = $this->platform->getDropTableSQL($table); + } + + return $sql; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/Graphviz.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/Graphviz.php new file mode 100644 index 0000000..e49afdd --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/Graphviz.php @@ -0,0 +1,177 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Visitor; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; + +/** + * Create a Graphviz output of a Schema. + */ +class Graphviz extends AbstractVisitor +{ + /** + * @var string + */ + private $output = ''; + + /** + * {@inheritdoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + $this->output .= $this->createNodeRelation( + $fkConstraint->getLocalTableName() . ":col" . current($fkConstraint->getLocalColumns()).":se", + $fkConstraint->getForeignTableName() . ":col" . current($fkConstraint->getForeignColumns()).":se", + array( + 'dir' => 'back', + 'arrowtail' => 'dot', + 'arrowhead' => 'normal', + ) + ); + } + + /** + * {@inheritdoc} + */ + public function acceptSchema(Schema $schema) + { + $this->output = 'digraph "' . sha1( mt_rand() ) . '" {' . "\n"; + $this->output .= 'splines = true;' . "\n"; + $this->output .= 'overlap = false;' . "\n"; + $this->output .= 'outputorder=edgesfirst;'."\n"; + $this->output .= 'mindist = 0.6;' . "\n"; + $this->output .= 'sep = .2;' . "\n"; + } + + /** + * {@inheritdoc} + */ + public function acceptTable(Table $table) + { + $this->output .= $this->createNode( + $table->getName(), + array( + 'label' => $this->createTableLabel( $table ), + 'shape' => 'plaintext', + ) + ); + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * + * @return string + */ + private function createTableLabel(Table $table) + { + // Start the table + $label = '<'; + + // The title + $label .= ''; + + // The attributes block + foreach( $table->getColumns() as $column ) { + $columnLabel = $column->getName(); + + $label .= ''; + $label .= ''; + $label .= ''; + } + + // End the table + $label .= '
' . $table->getName() . '
'; + $label .= '' . $columnLabel . ''; + $label .= '' . strtolower($column->getType()) . ''; + if ($table->hasPrimaryKey() && in_array($column->getName(), $table->getPrimaryKey()->getColumns())) { + $label .= "\xe2\x9c\xb7"; + } + $label .= '
>'; + + return $label; + } + + /** + * @param string $name + * @param array $options + * + * @return string + */ + private function createNode($name, $options) + { + $node = $name . " ["; + foreach( $options as $key => $value ) + { + $node .= $key . '=' . $value . ' '; + } + $node .= "]\n"; + + return $node; + } + + /** + * @param string $node1 + * @param string $node2 + * @param array $options + * + * @return string + */ + private function createNodeRelation($node1, $node2, $options) + { + $relation = $node1 . ' -> ' . $node2 . ' ['; + foreach( $options as $key => $value ) + { + $relation .= $key . '=' . $value . ' '; + } + $relation .= "]\n"; + + return $relation; + } + + /** + * Get Graphviz Output + * + * @return string + */ + public function getOutput() + { + return $this->output . "}"; + } + + /** + * Writes dot language output to a file. This should usually be a *.dot file. + * + * You have to convert the output into a viewable format. For example use "neato" on linux systems + * and execute: + * + * neato -Tpng -o er.png er.dot + * + * @param string $filename + * + * @return void + */ + public function write($filename) + { + file_put_contents($filename, $this->getOutput()); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/RemoveNamespacedAssets.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/RemoveNamespacedAssets.php new file mode 100644 index 0000000..05f52be --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/RemoveNamespacedAssets.php @@ -0,0 +1,95 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Visitor; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Sequence; + +/** + * Removes assets from a schema that are not in the default namespace. + * + * Some databases such as MySQL support cross databases joins, but don't + * allow to call DDLs to a database from another connected database. + * Before a schema is serialized into SQL this visitor can cleanup schemas with + * non default namespaces. + * + * This visitor filters all these non-default namespaced tables and sequences + * and removes them from the SChema instance. + * + * @author Benjamin Eberlei + * @since 2.2 + */ +class RemoveNamespacedAssets extends AbstractVisitor +{ + /** + * @var \Doctrine\DBAL\Schema\Schema + */ + private $schema; + + /** + * {@inheritdoc} + */ + public function acceptSchema(Schema $schema) + { + $this->schema = $schema; + } + + /** + * {@inheritdoc} + */ + public function acceptTable(Table $table) + { + if ( ! $table->isInDefaultNamespace($this->schema->getName()) ) { + $this->schema->dropTable($table->getName()); + } + } + + /** + * {@inheritdoc} + */ + public function acceptSequence(Sequence $sequence) + { + if ( ! $sequence->isInDefaultNamespace($this->schema->getName()) ) { + $this->schema->dropSequence($sequence->getName()); + } + } + + /** + * {@inheritdoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + // The table may already be deleted in a previous + // RemoveNamespacedAssets#acceptTable call. Removing Foreign keys that + // point to nowhere. + if ( ! $this->schema->hasTable($fkConstraint->getForeignTableName())) { + $localTable->removeForeignKey($fkConstraint->getName()); + return; + } + + $foreignTable = $this->schema->getTable($fkConstraint->getForeignTableName()); + if ( ! $foreignTable->isInDefaultNamespace($this->schema->getName()) ) { + $localTable->removeForeignKey($fkConstraint->getName()); + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/SchemaDiffVisitor.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/SchemaDiffVisitor.php new file mode 100644 index 0000000..b139aa2 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/SchemaDiffVisitor.php @@ -0,0 +1,84 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Visitor; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\TableDiff; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\SchemaException; + +/** + * Visit a SchemaDiff. + * + * @link www.doctrine-project.org + * @since 2.4 + * @author Benjamin Eberlei + */ +interface SchemaDiffVisitor +{ + /** + * Visit an orphaned foreign key whose table was deleted. + * + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey + */ + function visitOrphanedForeignKey(ForeignKeyConstraint $foreignKey); + + /** + * Visit a sequence that has changed. + * + * @param \Doctrine\DBAL\Schema\Sequence $sequence + */ + function visitChangedSequence(Sequence $sequence); + + /** + * Visit a sequence that has been removed. + * + * @param \Doctrine\DBAL\Schema\Sequence $sequence + */ + function visitRemovedSequence(Sequence $sequence); + + /** + * @param \Doctrine\DBAL\Schema\Sequence $sequence + */ + function visitNewSequence(Sequence $sequence); + + /** + * @param \Doctrine\DBAL\Schema\Table $table + */ + function visitNewTable(Table $table); + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey + */ + function visitNewTableForeignKey(Table $table, ForeignKeyConstraint $foreignKey); + + /** + * @param \Doctrine\DBAL\Schema\Table $table + */ + function visitRemovedTable(Table $table); + + /** + * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff + */ + function visitChangedTable(TableDiff $tableDiff); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/Visitor.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/Visitor.php new file mode 100644 index 0000000..9bd5528 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Schema/Visitor/Visitor.php @@ -0,0 +1,83 @@ +. + */ + +namespace Doctrine\DBAL\Schema\Visitor; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\Index; + +/** + * Schema Visitor used for Validation or Generation purposes. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + */ +interface Visitor +{ + /** + * @param \Doctrine\DBAL\Schema\Schema $schema + * + * @return void + */ + public function acceptSchema(Schema $schema); + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * + * @return void + */ + public function acceptTable(Table $table); + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param \Doctrine\DBAL\Schema\Column $column + * + * @return void + */ + public function acceptColumn(Table $table, Column $column); + + /** + * @param \Doctrine\DBAL\Schema\Table $localTable + * @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $fkConstraint + * + * @return void + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint); + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * @param \Doctrine\DBAL\Schema\Index $index + * + * @return void + */ + public function acceptIndex(Table $table, Index $index); + + /** + * @param \Doctrine\DBAL\Schema\Sequence $sequence + * + * @return void + */ + public function acceptSequence(Sequence $sequence); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/PoolingShardConnection.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/PoolingShardConnection.php new file mode 100644 index 0000000..6048e81 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/PoolingShardConnection.php @@ -0,0 +1,214 @@ +. + */ + +namespace Doctrine\DBAL\Sharding; + +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Event\ConnectionEventArgs; +use Doctrine\DBAL\Events; +use Doctrine\DBAL\Driver; +use Doctrine\DBAL\Configuration; + +use Doctrine\Common\EventManager; + +use Doctrine\DBAL\Sharding\ShardChoser\ShardChoser; + +/** + * Sharding implementation that pools many different connections + * internally and serves data from the currently active connection. + * + * The internals of this class are: + * + * - All sharding clients are specified and given a shard-id during + * configuration. + * - By default, the global shard is selected. If no global shard is configured + * an exception is thrown on access. + * - Selecting a shard by distribution value delegates the mapping + * "distributionValue" => "client" to the ShardChooser interface. + * - An exception is thrown if trying to switch shards during an open + * transaction. + * + * Instantiation through the DriverManager looks like: + * + * @example + * + * $conn = DriverManager::getConnection(array( + * 'wrapperClass' => 'Doctrine\DBAL\Sharding\PoolingShardConnection', + * 'driver' => 'pdo_mysql', + * 'global' => array('user' => '', 'password' => '', 'host' => '', 'dbname' => ''), + * 'shards' => array( + * array('id' => 1, 'user' => 'slave1', 'password', 'host' => '', 'dbname' => ''), + * array('id' => 2, 'user' => 'slave2', 'password', 'host' => '', 'dbname' => ''), + * ), + * 'shardChoser' => 'Doctrine\DBAL\Sharding\ShardChoser\MultiTenantShardChoser', + * )); + * $shardManager = $conn->getShardManager(); + * $shardManager->selectGlobal(); + * $shardManager->selectShard($value); + * + * @author Benjamin Eberlei + */ +class PoolingShardConnection extends Connection +{ + /** + * @var array + */ + private $activeConnections; + + /** + * @var integer + */ + private $activeShardId; + + /** + * @var array + */ + private $connections; + + /** + * @param array $params + * @param \Doctrine\DBAL\Driver $driver + * @param \Doctrine\DBAL\Configuration $config + * @param \Doctrine\Common\EventManager $eventManager + * + * @throws \InvalidArgumentException + */ + public function __construct(array $params, Driver $driver, Configuration $config = null, EventManager $eventManager = null) + { + if ( !isset($params['global']) || !isset($params['shards'])) { + throw new \InvalidArgumentException("Connection Parameters require 'global' and 'shards' configurations."); + } + + if ( !isset($params['shardChoser'])) { + throw new \InvalidArgumentException("Missing Shard Choser configuration 'shardChoser'"); + } + + if (is_string($params['shardChoser'])) { + $params['shardChoser'] = new $params['shardChoser']; + } + + if ( ! ($params['shardChoser'] instanceof ShardChoser)) { + throw new \InvalidArgumentException("The 'shardChoser' configuration is not a valid instance of Doctrine\DBAL\Sharding\ShardChoser\ShardChoser"); + } + + $this->connections[0] = array_merge($params, $params['global']); + + foreach ($params['shards'] as $shard) { + if ( ! isset($shard['id'])) { + throw new \InvalidArgumentException("Missing 'id' for one configured shard. Please specify a unique shard-id."); + } + + if ( !is_numeric($shard['id']) || $shard['id'] < 1) { + throw new \InvalidArgumentException("Shard Id has to be a non-negative number."); + } + + if (isset($this->connections[$shard['id']])) { + throw new \InvalidArgumentException("Shard " . $shard['id'] . " is duplicated in the configuration."); + } + + $this->connections[$shard['id']] = array_merge($params, $shard); + } + + parent::__construct($params, $driver, $config, $eventManager); + } + + /** + * Connects to a given shard. + * + * @param mixed $shardId + * + * @return boolean + * + * @throws \Doctrine\DBAL\Sharding\ShardingException + */ + public function connect($shardId = null) + { + if ($shardId === null && $this->_conn) { + return false; + } + + if ($shardId !== null && $shardId === $this->activeShardId) { + return false; + } + + if ($this->getTransactionNestingLevel() > 0) { + throw new ShardingException("Cannot switch shard when transaction is active."); + } + + $this->activeShardId = (int)$shardId; + + if (isset($this->activeConnections[$this->activeShardId])) { + $this->_conn = $this->activeConnections[$this->activeShardId]; + return false; + } + + $this->_conn = $this->activeConnections[$this->activeShardId] = $this->connectTo($this->activeShardId); + + if ($this->_eventManager->hasListeners(Events::postConnect)) { + $eventArgs = new \Doctrine\DBAL\Event\ConnectionEventArgs($this); + $this->_eventManager->dispatchEvent(Events::postConnect, $eventArgs); + } + + return true; + } + + /** + * Connects to a specific connection. + * + * @param string $shardId + * + * @return \Doctrine\DBAL\Driver\Connection + */ + protected function connectTo($shardId) + { + $params = $this->getParams(); + + $driverOptions = isset($params['driverOptions']) ? $params['driverOptions'] : array(); + + $connectionParams = $this->connections[$shardId]; + + $user = isset($connectionParams['user']) ? $connectionParams['user'] : null; + $password = isset($connectionParams['password']) ? $connectionParams['password'] : null; + + return $this->_driver->connect($connectionParams, $user, $password, $driverOptions); + } + + /** + * @param string|null $shardId + * + * @return boolean + */ + public function isConnected($shardId = null) + { + if ($shardId === null) { + return $this->_conn !== null; + } + + return isset($this->activeConnections[$shardId]); + } + + /** + * @return void + */ + public function close() + { + $this->_conn = null; + $this->activeConnections = null; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/PoolingShardManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/PoolingShardManager.php new file mode 100644 index 0000000..22274d5 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/PoolingShardManager.php @@ -0,0 +1,132 @@ +. + */ + +namespace Doctrine\DBAL\Sharding; + +/** + * Shard Manager for the Connection Pooling Shard Strategy + * + * @author Benjamin Eberlei + */ +class PoolingShardManager implements ShardManager +{ + /** + * @var \Doctrine\DBAL\Sharding\PoolingShardConnection + */ + private $conn; + + /** + * @var \Doctrine\DBAL\Sharding\ShardChoser\ShardChoser + */ + private $choser; + + /** + * @var string|null + */ + private $currentDistributionValue; + + /** + * @param \Doctrine\DBAL\Sharding\PoolingShardConnection $conn + */ + public function __construct(PoolingShardConnection $conn) + { + $params = $conn->getParams(); + $this->conn = $conn; + $this->choser = $params['shardChoser']; + } + + /** + * @return void + */ + public function selectGlobal() + { + $this->conn->connect(0); + $this->currentDistributionValue = null; + } + + /** + * @param string $distributionValue + * + * @return void + */ + public function selectShard($distributionValue) + { + $shardId = $this->choser->pickShard($distributionValue, $this->conn); + $this->conn->connect($shardId); + $this->currentDistributionValue = $distributionValue; + } + + /** + * @return string|null + */ + public function getCurrentDistributionValue() + { + return $this->currentDistributionValue; + } + + /** + * @return array + */ + public function getShards() + { + $params = $this->conn->getParams(); + $shards = array(); + + foreach ($params['shards'] as $shard) { + $shards[] = array('id' => $shard['id']); + } + + return $shards; + } + + /** + * @param string $sql + * @param array $params + * @param array $types + * + * @return array + * + * @throws \RuntimeException + */ + public function queryAll($sql, array $params, array $types) + { + $shards = $this->getShards(); + if (!$shards) { + throw new \RuntimeException("No shards found."); + } + + $result = array(); + $oldDistribution = $this->getCurrentDistributionValue(); + + foreach ($shards as $shard) { + $this->selectShard($shard['id']); + foreach ($this->conn->fetchAll($sql, $params, $types) as $row) { + $result[] = $row; + } + } + + if ($oldDistribution === null) { + $this->selectGlobal(); + } else { + $this->selectShard($oldDistribution); + } + + return $result; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureFederationsSynchronizer.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureFederationsSynchronizer.php new file mode 100644 index 0000000..564c54b --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureFederationsSynchronizer.php @@ -0,0 +1,297 @@ +. + */ + +namespace Doctrine\DBAL\Sharding\SQLAzure; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Types\Type; + +use Doctrine\DBAL\Schema\Synchronizer\AbstractSchemaSynchronizer; +use Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer; +use Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer; + +/** + * SQL Azure Schema Synchronizer. + * + * Will iterate over all shards when performing schema operations. This is done + * by partitioning the passed schema into subschemas for the federation and the + * global database and then applying the operations step by step using the + * {@see \Doctrine\DBAL\Schema\Synchronizer\SingleDatabaseSynchronizer}. + * + * @author Benjamin Eberlei + */ +class SQLAzureFederationsSynchronizer extends AbstractSchemaSynchronizer +{ + const FEDERATION_TABLE_FEDERATED = 'azure.federated'; + const FEDERATION_DISTRIBUTION_NAME = 'azure.federatedOnDistributionName'; + + /** + * @var \Doctrine\DBAL\Sharding\SQLAzure\SQLAzureShardManager + */ + private $shardManager; + + /** + * @var \Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer + */ + private $synchronizer; + + /** + * @param \Doctrine\DBAL\Connection $conn + * @param \Doctrine\DBAL\Sharding\SQLAzure\SQLAzureShardManager $shardManager + * @param \Doctrine\DBAL\Schema\Synchronizer\SchemaSynchronizer|null $sync + */ + public function __construct(Connection $conn, SQLAzureShardManager $shardManager, SchemaSynchronizer $sync = null) + { + parent::__construct($conn); + $this->shardManager = $shardManager; + $this->synchronizer = $sync ?: new SingleDatabaseSynchronizer($conn); + } + + /** + * {@inheritdoc} + */ + public function getCreateSchema(Schema $createSchema) + { + $sql = array(); + + list($global, $federation) = $this->partitionSchema($createSchema); + + $globalSql = $this->synchronizer->getCreateSchema($global); + if ($globalSql) { + $sql[] = "-- Create Root Federation\n" . + "USE FEDERATION ROOT WITH RESET;"; + $sql = array_merge($sql, $globalSql); + } + + $federationSql = $this->synchronizer->getCreateSchema($federation); + + if ($federationSql) { + $defaultValue = $this->getFederationTypeDefaultValue(); + + $sql[] = $this->getCreateFederationStatement(); + $sql[] = "USE FEDERATION " . $this->shardManager->getFederationName() . " (" . $this->shardManager->getDistributionKey() . " = " . $defaultValue . ") WITH RESET, FILTERING = OFF;"; + $sql = array_merge($sql, $federationSql); + } + + return $sql; + } + + /** + * {@inheritdoc} + */ + public function getUpdateSchema(Schema $toSchema, $noDrops = false) + { + return $this->work($toSchema, function($synchronizer, $schema) use ($noDrops) { + return $synchronizer->getUpdateSchema($schema, $noDrops); + }); + } + + /** + * {@inheritdoc} + */ + public function getDropSchema(Schema $dropSchema) + { + return $this->work($dropSchema, function($synchronizer, $schema) { + return $synchronizer->getDropSchema($schema); + }); + } + + /** + * {@inheritdoc} + */ + public function createSchema(Schema $createSchema) + { + $this->processSql($this->getCreateSchema($createSchema)); + } + + /** + * {@inheritdoc} + */ + public function updateSchema(Schema $toSchema, $noDrops = false) + { + $this->processSql($this->getUpdateSchema($toSchema, $noDrops)); + } + + /** + * {@inheritdoc} + */ + public function dropSchema(Schema $dropSchema) + { + $this->processSqlSafely($this->getDropSchema($dropSchema)); + } + + /** + * {@inheritdoc} + */ + public function getDropAllSchema() + { + $this->shardManager->selectGlobal(); + $globalSql = $this->synchronizer->getDropAllSchema(); + + if ($globalSql) { + $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;"; + $sql = array_merge($sql, $globalSql); + } + + $shards = $this->shardManager->getShards(); + foreach ($shards as $shard) { + $this->shardManager->selectShard($shard['rangeLow']); + + $federationSql = $this->synchronizer->getDropAllSchema(); + if ($federationSql) { + $sql[] = "-- Work on Federation ID " . $shard['id'] . "\n" . + "USE FEDERATION " . $this->shardManager->getFederationName() . " (" . $this->shardManager->getDistributionKey() . " = " . $shard['rangeLow'].") WITH RESET, FILTERING = OFF;"; + $sql = array_merge($sql, $federationSql); + } + } + + $sql[] = "USE FEDERATION ROOT WITH RESET;"; + $sql[] = "DROP FEDERATION " . $this->shardManager->getFederationName(); + + return $sql; + } + + /** + * {@inheritdoc} + */ + public function dropAllSchema() + { + $this->processSqlSafely($this->getDropAllSchema()); + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $schema + * + * @return array + */ + private function partitionSchema(Schema $schema) + { + return array( + $this->extractSchemaFederation($schema, false), + $this->extractSchemaFederation($schema, true), + ); + } + + /** + * @param \Doctrine\DBAL\Schema\Schema $schema + * @param boolean $isFederation + * + * @return \Doctrine\DBAL\Schema\Schema + * + * @throws \RuntimeException + */ + private function extractSchemaFederation(Schema $schema, $isFederation) + { + $partitionedSchema = clone $schema; + + foreach ($partitionedSchema->getTables() as $table) { + if ($isFederation) { + $table->addOption(self::FEDERATION_DISTRIBUTION_NAME, $this->shardManager->getDistributionKey()); + } + + if ( $table->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) { + $partitionedSchema->dropTable($table->getName()); + } else { + foreach ($table->getForeignKeys() as $fk) { + $foreignTable = $schema->getTable($fk->getForeignTableName()); + if ($foreignTable->hasOption(self::FEDERATION_TABLE_FEDERATED) !== $isFederation) { + throw new \RuntimeException("Cannot have foreign key between global/federation."); + } + } + } + } + + return $partitionedSchema; + } + + /** + * Work on the Global/Federation based on currently existing shards and + * perform the given operation on the underlying schema synchronizer given + * the different partitioned schema instances. + * + * @param \Doctrine\DBAL\Schema\Schema $schema + * @param \Closure $operation + * + * @return array + */ + private function work(Schema $schema, \Closure $operation) + { + list($global, $federation) = $this->partitionSchema($schema); + $sql = array(); + + $this->shardManager->selectGlobal(); + $globalSql = $operation($this->synchronizer, $global); + + if ($globalSql) { + $sql[] = "-- Work on Root Federation\nUSE FEDERATION ROOT WITH RESET;"; + $sql = array_merge($sql, $globalSql); + } + + $shards = $this->shardManager->getShards(); + + foreach ($shards as $shard) { + $this->shardManager->selectShard($shard['rangeLow']); + + $federationSql = $operation($this->synchronizer, $federation); + if ($federationSql) { + $sql[] = "-- Work on Federation ID " . $shard['id'] . "\n" . + "USE FEDERATION " . $this->shardManager->getFederationName() . " (" . $this->shardManager->getDistributionKey() . " = " . $shard['rangeLow'].") WITH RESET, FILTERING = OFF;"; + $sql = array_merge($sql, $federationSql); + } + } + + return $sql; + } + + /** + * @return string + */ + private function getFederationTypeDefaultValue() + { + $federationType = Type::getType($this->shardManager->getDistributionType()); + + switch ($federationType->getName()) { + case Type::GUID: + $defaultValue = '00000000-0000-0000-0000-000000000000'; + break; + case Type::INTEGER: + case Type::SMALLINT: + case Type::BIGINT: + $defaultValue = '0'; + break; + default: + $defaultValue = ''; + break; + } + return $defaultValue; + } + + /** + * @return string + */ + private function getCreateFederationStatement() + { + $federationType = Type::getType($this->shardManager->getDistributionType()); + $federationTypeSql = $federationType->getSqlDeclaration(array(), $this->conn->getDatabasePlatform()); + + return "--Create Federation\n" . + "CREATE FEDERATION " . $this->shardManager->getFederationName() . " (" . $this->shardManager->getDistributionKey() . " " . $federationTypeSql ." RANGE)"; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureShardManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureShardManager.php new file mode 100644 index 0000000..c343981 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/SQLAzureShardManager.php @@ -0,0 +1,242 @@ +. + */ + +namespace Doctrine\DBAL\Sharding\SQLAzure; + +use Doctrine\DBAL\Sharding\ShardManager; +use Doctrine\DBAL\Sharding\ShardingException; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Types\Type; + +/** + * Sharding using the SQL Azure Federations support. + * + * @author Benjamin Eberlei + */ +class SQLAzureShardManager implements ShardManager +{ + /** + * @var string + */ + private $federationName; + + /** + * @var boolean + */ + private $filteringEnabled; + + /** + * @var string + */ + private $distributionKey; + + /** + * @var string + */ + private $distributionType; + + /** + * @var \Doctrine\DBAL\Connection + */ + private $conn; + + /** + * @var string + */ + private $currentDistributionValue; + + /** + * @param \Doctrine\DBAL\Connection $conn + * + * @throws \Doctrine\DBAL\Sharding\ShardingException + */ + public function __construct(Connection $conn) + { + $this->conn = $conn; + $params = $conn->getParams(); + + if ( ! isset($params['sharding']['federationName'])) { + throw ShardingException::missingDefaultFederationName(); + } + + if ( ! isset($params['sharding']['distributionKey'])) { + throw ShardingException::missingDefaultDistributionKey(); + } + + if ( ! isset($params['sharding']['distributionType'])) { + throw ShardingException::missingDistributionType(); + } + + $this->federationName = $params['sharding']['federationName']; + $this->distributionKey = $params['sharding']['distributionKey']; + $this->distributionType = $params['sharding']['distributionType']; + $this->filteringEnabled = (isset($params['sharding']['filteringEnabled'])) ? (bool)$params['sharding']['filteringEnabled'] : false; + } + + /** + * Gets the name of the federation. + * + * @return string + */ + public function getFederationName() + { + return $this->federationName; + } + + /** + * Gets the distribution key. + * + * @return string + */ + public function getDistributionKey() + { + return $this->distributionKey; + } + + /** + * Gets the Doctrine Type name used for the distribution. + * + * @return string + */ + public function getDistributionType() + { + return $this->distributionType; + } + + /** + * Sets Enabled/Disable filtering on the fly. + * + * @param boolean $flag + * + * @return void + */ + public function setFilteringEnabled($flag) + { + $this->filteringEnabled = (bool)$flag; + } + + /** + * {@inheritDoc} + */ + public function selectGlobal() + { + if ($this->conn->isTransactionActive()) { + throw ShardingException::activeTransaction(); + } + + $sql = "USE FEDERATION ROOT WITH RESET"; + $this->conn->exec($sql); + $this->currentDistributionValue = null; + } + + /** + * {@inheritDoc} + */ + public function selectShard($distributionValue) + { + if ($this->conn->isTransactionActive()) { + throw ShardingException::activeTransaction(); + } + + if ($distributionValue === null || is_bool($distributionValue) || !is_scalar($distributionValue)) { + throw ShardingException::noShardDistributionValue(); + } + + $platform = $this->conn->getDatabasePlatform(); + $sql = sprintf( + "USE FEDERATION %s (%s = %s) WITH RESET, FILTERING = %s;", + $platform->quoteIdentifier($this->federationName), + $platform->quoteIdentifier($this->distributionKey), + $this->conn->quote($distributionValue), + ($this->filteringEnabled ? 'ON' : 'OFF') + ); + + $this->conn->exec($sql); + $this->currentDistributionValue = $distributionValue; + } + + /** + * {@inheritDoc} + */ + public function getCurrentDistributionValue() + { + return $this->currentDistributionValue; + } + + /** + * {@inheritDoc} + */ + public function getShards() + { + $sql = "SELECT member_id as id, + distribution_name as distribution_key, + CAST(range_low AS CHAR) AS rangeLow, + CAST(range_high AS CHAR) AS rangeHigh + FROM sys.federation_member_distributions d + INNER JOIN sys.federations f ON f.federation_id = d.federation_id + WHERE f.name = " . $this->conn->quote($this->federationName); + return $this->conn->fetchAll($sql); + } + + /** + * {@inheritDoc} + */ + public function queryAll($sql, array $params = array(), array $types = array()) + { + $shards = $this->getShards(); + if (!$shards) { + throw new \RuntimeException("No shards found for " . $this->federationName); + } + + $result = array(); + $oldDistribution = $this->getCurrentDistributionValue(); + + foreach ($shards as $shard) { + $this->selectShard($shard['rangeLow']); + foreach ($this->conn->fetchAll($sql, $params, $types) as $row) { + $result[] = $row; + } + } + + if ($oldDistribution === null) { + $this->selectGlobal(); + } else { + $this->selectShard($oldDistribution); + } + + return $result; + } + + /** + * Splits Federation at a given distribution value. + * + * @param mixed $splitDistributionValue + * + * @return void + */ + public function splitFederation($splitDistributionValue) + { + $type = Type::getType($this->distributionType); + + $sql = "ALTER FEDERATION " . $this->getFederationName() . " " . + "SPLIT AT (" . $this->getDistributionKey() . " = " . + $this->conn->quote($splitDistributionValue, $type->getBindingType()) . ")"; + $this->conn->exec($sql); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/Schema/MultiTenantVisitor.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/Schema/MultiTenantVisitor.php new file mode 100644 index 0000000..ae327a4 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/SQLAzure/Schema/MultiTenantVisitor.php @@ -0,0 +1,169 @@ +. + */ + +namespace Doctrine\DBAL\Sharding\SQLAzure\Schema; + +use Doctrine\DBAL\Schema\Visitor\Visitor; +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Schema\Schema; +use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\ForeignKeyConstraint; +use Doctrine\DBAL\Schema\Sequence; +use Doctrine\DBAL\Schema\Index; + +/** + * Converts a single tenant schema into a multi-tenant schema for SQL Azure + * Federations under the following assumptions: + * + * - Every table is part of the multi-tenant application, only explicitly + * excluded tables are non-federated. The behavior of the tables being in + * global or federated database is undefined. It depends on you selecting a + * federation before DDL statements or not. + * - Every Primary key of a federated table is extended by another column + * 'tenant_id' with a default value of the SQLAzure function + * `federation_filtering_value('tenant_id')`. + * - You always have to work with `filtering=On` when using federations with this + * multi-tenant approach. + * - Primary keys are either using globally unique ids (GUID, Table Generator) + * or you explicitly add the tenent_id in every UPDATE or DELETE statement + * (otherwise they will affect the same-id rows from other tenents as well). + * SQLAzure throws errors when you try to create IDENTIY columns on federated + * tables. + * + * @author Benjamin Eberlei + */ +class MultiTenantVisitor implements Visitor +{ + /** + * @var array + */ + private $excludedTables = array(); + + /** + * @var string + */ + private $tenantColumnName; + + /** + * @var string + */ + private $tenantColumnType = 'integer'; + + /** + * Name of the federation distribution, defaulting to the tenantColumnName + * if not specified. + * + * @var string + */ + private $distributionName; + + /** + * @param array $excludedTables + * @param string $tenantColumnName + * @param string|null $distributionName + */ + public function __construct(array $excludedTables = array(), $tenantColumnName = 'tenant_id', $distributionName = null) + { + $this->excludedTables = $excludedTables; + $this->tenantColumnName = $tenantColumnName; + $this->distributionName = $distributionName ?: $tenantColumnName; + } + + /** + * {@inheritdoc} + */ + public function acceptTable(Table $table) + { + if (in_array($table->getName(), $this->excludedTables)) { + return; + } + + $table->addColumn($this->tenantColumnName, $this->tenantColumnType, array( + 'default' => "federation_filtering_value('". $this->distributionName ."')", + )); + + $clusteredIndex = $this->getClusteredIndex($table); + + $indexColumns = $clusteredIndex->getColumns(); + $indexColumns[] = $this->tenantColumnName; + + if ($clusteredIndex->isPrimary()) { + $table->dropPrimaryKey(); + $table->setPrimaryKey($indexColumns); + } else { + $table->dropIndex($clusteredIndex->getName()); + $table->addIndex($indexColumns, $clusteredIndex->getName()); + $table->getIndex($clusteredIndex->getName())->addFlag('clustered'); + } + } + + /** + * @param \Doctrine\DBAL\Schema\Table $table + * + * @return \Doctrine\DBAL\Schema\Index + * + * @throws \RuntimeException + */ + private function getClusteredIndex($table) + { + foreach ($table->getIndexes() as $index) { + if ($index->isPrimary() && ! $index->hasFlag('nonclustered')) { + return $index; + } else if ($index->hasFlag('clustered')) { + return $index; + } + } + throw new \RuntimeException("No clustered index found on table " . $table->getName()); + } + + /** + * {@inheritdoc} + */ + public function acceptSchema(Schema $schema) + { + } + + /** + * {@inheritdoc} + */ + public function acceptColumn(Table $table, Column $column) + { + } + + /** + * {@inheritdoc} + */ + public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint) + { + } + + /** + * {@inheritdoc} + */ + public function acceptIndex(Table $table, Index $index) + { + } + + /** + * {@inheritdoc} + */ + public function acceptSequence(Sequence $sequence) + { + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardChoser/MultiTenantShardChoser.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardChoser/MultiTenantShardChoser.php new file mode 100644 index 0000000..f1d51b8 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardChoser/MultiTenantShardChoser.php @@ -0,0 +1,39 @@ +. + */ + +namespace Doctrine\DBAL\Sharding\ShardChoser; + +use Doctrine\DBAL\Sharding\PoolingShardConnection; + +/** + * The MultiTenant Shard choser assumes that the distribution value directly + * maps to the shard id. + * + * @author Benjamin Eberlei + */ +class MultiTenantShardChoser implements ShardChoser +{ + /** + * {@inheritdoc} + */ + public function pickShard($distributionValue, PoolingShardConnection $conn) + { + return $distributionValue; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardChoser/ShardChoser.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardChoser/ShardChoser.php new file mode 100644 index 0000000..a7b85a6 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardChoser/ShardChoser.php @@ -0,0 +1,41 @@ +. + */ + +namespace Doctrine\DBAL\Sharding\ShardChoser; + +use Doctrine\DBAL\Sharding\PoolingShardConnection; + +/** + * Given a distribution value this shard-choser strategy will pick the shard to + * connect to for retrieving rows with the distribution value. + * + * @author Benjamin Eberlei + */ +interface ShardChoser +{ + /** + * Picks a shard for the given distribution value. + * + * @param string $distributionValue + * @param \Doctrine\DBAL\Sharding\PoolingShardConnection $conn + * + * @return integer + */ + function pickShard($distributionValue, PoolingShardConnection $conn); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardManager.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardManager.php new file mode 100644 index 0000000..b742793 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardManager.php @@ -0,0 +1,93 @@ +. + */ + +namespace Doctrine\DBAL\Sharding; + +/** + * Sharding Manager gives access to APIs to implementing sharding on top of + * Doctrine\DBAL\Connection instances. + * + * For simplicity and developer ease-of-use (and understanding) the sharding + * API only covers single shard queries, no fan-out support. It is primarily + * suited for multi-tenant applications. + * + * The assumption about sharding here + * is that a distribution value can be found that gives access to all the + * necessary data for all use-cases. Switching between shards should be done with + * caution, especially if lazy loading is implemented. Any query is always + * executed against the last shard that was selected. If a query is created for + * a shard Y but then a shard X is selected when its actually executed you + * will hit the wrong shard. + * + * @author Benjamin Eberlei + */ +interface ShardManager +{ + /** + * Selects global database with global data. + * + * This is the default database that is connected when no shard is + * selected. + * + * @return void + */ + function selectGlobal(); + + /** + * Selects the shard against which the queries after this statement will be issued. + * + * @param string $distributionValue + * + * @return void + * + * @throws \Doctrine\DBAL\Sharding\ShardingException If no value is passed as shard identifier. + */ + function selectShard($distributionValue); + + /** + * Gets the distribution value currently used for sharding. + * + * @return string + */ + function getCurrentDistributionValue(); + + /** + * Gets information about the amount of shards and other details. + * + * Format is implementation specific, each shard is one element and has an + * 'id' attribute at least. + * + * @return array + */ + function getShards(); + + /** + * Queries all shards in undefined order and return the results appended to + * each other. Restore the previous distribution value after execution. + * + * Using {@link \Doctrine\DBAL\Connection::fetchAll} to retrieve rows internally. + * + * @param string $sql + * @param array $params + * @param array $types + * + * @return array + */ + function queryAll($sql, array $params, array $types); +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardingException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardingException.php new file mode 100644 index 0000000..407bf84 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Sharding/ShardingException.php @@ -0,0 +1,78 @@ +. + */ + +namespace Doctrine\DBAL\Sharding; + +use Doctrine\DBAL\DBALException; + +/** + * Sharding related Exceptions + * + * @since 2.3 + */ +class ShardingException extends DBALException +{ + /** + * @return \Doctrine\DBAL\Sharding\ShardingException + */ + static public function notImplemented() + { + return new self("This functionality is not implemented with this sharding provider.", 1331557937); + } + + /** + * @return \Doctrine\DBAL\Sharding\ShardingException + */ + static public function missingDefaultFederationName() + { + return new self("SQLAzure requires a federation name to be set during sharding configuration.", 1332141280); + } + + /** + * @return \Doctrine\DBAL\Sharding\ShardingException + */ + static public function missingDefaultDistributionKey() + { + return new self("SQLAzure requires a distribution key to be set during sharding configuration.", 1332141329); + } + + /** + * @return \Doctrine\DBAL\Sharding\ShardingException + */ + static public function activeTransaction() + { + return new self("Cannot switch shard during an active transaction.", 1332141766); + } + + /** + * @return \Doctrine\DBAL\Sharding\ShardingException + */ + static public function noShardDistributionValue() + { + return new self("You have to specify a string or integer as shard distribution value.", 1332142103); + } + + /** + * @return \Doctrine\DBAL\Sharding\ShardingException + */ + static public function missingDistributionType() + { + return new self("You have to specify a sharding distribution type such as 'integer', 'string', 'guid'."); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Statement.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Statement.php new file mode 100644 index 0000000..7943d6c --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Statement.php @@ -0,0 +1,304 @@ +. + */ + +namespace Doctrine\DBAL; + +use PDO; +use Doctrine\DBAL\Types\Type; +use Doctrine\DBAL\Driver\Statement as DriverStatement; + +/** + * A thin wrapper around a Doctrine\DBAL\Driver\Statement that adds support + * for logging, DBAL mapping types, etc. + * + * @author Roman Borschel + * @since 2.0 + */ +class Statement implements \IteratorAggregate, DriverStatement +{ + /** + * The SQL statement. + * + * @var string + */ + protected $sql; + + /** + * The bound parameters. + * + * @var array + */ + protected $params = array(); + + /** + * The parameter types. + * + * @var array + */ + protected $types = array(); + + /** + * The underlying driver statement. + * + * @var \Doctrine\DBAL\Driver\Statement + */ + protected $stmt; + + /** + * The underlying database platform. + * + * @var \Doctrine\DBAL\Platforms\AbstractPlatform + */ + protected $platform; + + /** + * The connection this statement is bound to and executed on. + * + * @var \Doctrine\DBAL\Connection + */ + protected $conn; + + /** + * Creates a new Statement for the given SQL and Connection. + * + * @param string $sql The SQL of the statement. + * @param \Doctrine\DBAL\Connection $conn The connection on which the statement should be executed. + */ + public function __construct($sql, Connection $conn) + { + $this->sql = $sql; + $this->stmt = $conn->getWrappedConnection()->prepare($sql); + $this->conn = $conn; + $this->platform = $conn->getDatabasePlatform(); + } + + /** + * Binds a parameter value to the statement. + * + * The value can optionally be bound with a PDO binding type or a DBAL mapping type. + * If bound with a DBAL mapping type, the binding type is derived from the mapping + * type and the value undergoes the conversion routines of the mapping type before + * being bound. + * + * @param string $name The name or position of the parameter. + * @param mixed $value The value of the parameter. + * @param mixed $type Either a PDO binding type or a DBAL mapping type name or instance. + * + * @return boolean TRUE on success, FALSE on failure. + */ + public function bindValue($name, $value, $type = null) + { + $this->params[$name] = $value; + $this->types[$name] = $type; + if ($type !== null) { + if (is_string($type)) { + $type = Type::getType($type); + } + if ($type instanceof Type) { + $value = $type->convertToDatabaseValue($value, $this->platform); + $bindingType = $type->getBindingType(); + } else { + $bindingType = $type; // PDO::PARAM_* constants + } + + return $this->stmt->bindValue($name, $value, $bindingType); + } else { + return $this->stmt->bindValue($name, $value); + } + } + + /** + * Binds a parameter to a value by reference. + * + * Binding a parameter by reference does not support DBAL mapping types. + * + * @param string $name The name or position of the parameter. + * @param mixed $var The reference to the variable to bind. + * @param integer $type The PDO binding type. + * @param integer|null $length Must be specified when using an OUT bind + * so that PHP allocates enough memory to hold the returned value. + * + * @return boolean TRUE on success, FALSE on failure. + */ + public function bindParam($name, &$var, $type = PDO::PARAM_STR, $length = null) + { + return $this->stmt->bindParam($name, $var, $type, $length); + } + + /** + * Executes the statement with the currently bound parameters. + * + * @param array|null $params + * + * @return boolean TRUE on success, FALSE on failure. + * + * @throws \Doctrine\DBAL\DBALException + */ + public function execute($params = null) + { + if (is_array($params)) { + $this->params = $params; + } + + $logger = $this->conn->getConfiguration()->getSQLLogger(); + if ($logger) { + $logger->startQuery($this->sql, $this->params, $this->types); + } + + try { + $stmt = $this->stmt->execute($params); + } catch (\Exception $ex) { + throw DBALException::driverExceptionDuringQuery($ex, $this->sql, $this->conn->resolveParams($this->params, $this->types)); + } + + if ($logger) { + $logger->stopQuery(); + } + $this->params = array(); + $this->types = array(); + + return $stmt; + } + + /** + * Closes the cursor, freeing the database resources used by this statement. + * + * @return boolean TRUE on success, FALSE on failure. + */ + public function closeCursor() + { + return $this->stmt->closeCursor(); + } + + /** + * Returns the number of columns in the result set. + * + * @return integer + */ + public function columnCount() + { + return $this->stmt->columnCount(); + } + + /** + * Fetches the SQLSTATE associated with the last operation on the statement. + * + * @return string + */ + public function errorCode() + { + return $this->stmt->errorCode(); + } + + /** + * Fetches extended error information associated with the last operation on the statement. + * + * @return array + */ + public function errorInfo() + { + return $this->stmt->errorInfo(); + } + + /** + * {@inheritdoc} + */ + public function setFetchMode($fetchMode, $arg2 = null, $arg3 = null) + { + if ($arg2 === null) { + return $this->stmt->setFetchMode($fetchMode); + } else if ($arg3 === null) { + return $this->stmt->setFetchMode($fetchMode, $arg2); + } + + return $this->stmt->setFetchMode($fetchMode, $arg2, $arg3); + } + + /** + * Required by interface IteratorAggregate. + * + * {@inheritdoc} + */ + public function getIterator() + { + return $this->stmt; + } + + /** + * Fetches the next row from a result set. + * + * @param integer|null $fetchMode + * + * @return mixed The return value of this function on success depends on the fetch type. + * In all cases, FALSE is returned on failure. + */ + public function fetch($fetchMode = null) + { + return $this->stmt->fetch($fetchMode); + } + + /** + * Returns an array containing all of the result set rows. + * + * @param integer|null $fetchMode + * @param mixed $fetchArgument + * + * @return array An array containing all of the remaining rows in the result set. + */ + public function fetchAll($fetchMode = null, $fetchArgument = 0) + { + if ($fetchArgument !== 0) { + return $this->stmt->fetchAll($fetchMode, $fetchArgument); + } + + return $this->stmt->fetchAll($fetchMode); + } + + /** + * Returns a single column from the next row of a result set. + * + * @param integer $columnIndex + * + * @return mixed A single column from the next row of a result set or FALSE if there are no more rows. + */ + public function fetchColumn($columnIndex = 0) + { + return $this->stmt->fetchColumn($columnIndex); + } + + /** + * Returns the number of rows affected by the last execution of this statement. + * + * @return integer The number of affected rows. + */ + public function rowCount() + { + return $this->stmt->rowCount(); + } + + /** + * Gets the wrapped driver statement. + * + * @return \Doctrine\DBAL\Driver\Statement + */ + public function getWrappedStatement() + { + return $this->stmt; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/ImportCommand.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/ImportCommand.php new file mode 100644 index 0000000..1874431 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/ImportCommand.php @@ -0,0 +1,125 @@ +. + */ + +namespace Doctrine\DBAL\Tools\Console\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Task for executing arbitrary SQL that can come from a file or directly from + * the command line. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class ImportCommand extends Command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('dbal:import') + ->setDescription('Import SQL file(s) directly to Database.') + ->setDefinition(array( + new InputArgument( + 'file', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'File path(s) of SQL to be executed.' + ) + )) + ->setHelp(<<getHelper('db')->getConnection(); + + if (($fileNames = $input->getArgument('file')) !== null) { + foreach ((array) $fileNames as $fileName) { + $fileName = realpath($fileName); + + if ( ! file_exists($fileName)) { + throw new \InvalidArgumentException( + sprintf("SQL file '%s' does not exist.", $fileName) + ); + } else if ( ! is_readable($fileName)) { + throw new \InvalidArgumentException( + sprintf("SQL file '%s' does not have read permissions.", $fileName) + ); + } + + $output->write(sprintf("Processing file '%s'... ", $fileName)); + $sql = file_get_contents($fileName); + + if ($conn instanceof \Doctrine\DBAL\Driver\PDOConnection) { + // PDO Drivers + try { + $lines = 0; + + $stmt = $conn->prepare($sql); + $stmt->execute(); + + do { + // Required due to "MySQL has gone away!" issue + $stmt->fetch(); + $stmt->closeCursor(); + + $lines++; + } while ($stmt->nextRowset()); + + $output->write(sprintf('%d statements executed!', $lines) . PHP_EOL); + } catch (\PDOException $e) { + $output->write('error!' . PHP_EOL); + + throw new \RuntimeException($e->getMessage(), $e->getCode(), $e); + } + } else { + // Non-PDO Drivers (ie. OCI8 driver) + $stmt = $conn->prepare($sql); + $rs = $stmt->execute(); + + if ($rs) { + $output->writeln('OK!' . PHP_EOL); + } else { + $error = $stmt->errorInfo(); + + $output->write('error!' . PHP_EOL); + + throw new \RuntimeException($error[2], $error[0]); + } + + $stmt->closeCursor(); + } + } + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php new file mode 100644 index 0000000..846acb8 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/ReservedWordsCommand.php @@ -0,0 +1,154 @@ +. + */ + +namespace Doctrine\DBAL\Tools\Console\Command; + +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Doctrine\DBAL\Platforms\Keywords\ReservedKeywordsValidator; + +class ReservedWordsCommand extends Command +{ + /** + * @var array + */ + private $keywordListClasses = array( + 'mysql' => 'Doctrine\DBAL\Platforms\Keywords\MySQLKeywords', + 'sqlserver' => 'Doctrine\DBAL\Platforms\Keywords\SQLServerKeywords', + 'sqlserver2005' => 'Doctrine\DBAL\Platforms\Keywords\SQLServer2005Keywords', + 'sqlserver2008' => 'Doctrine\DBAL\Platforms\Keywords\SQLServer2008Keywords', + 'sqlserver2012' => 'Doctrine\DBAL\Platforms\Keywords\SQLServer2012Keywords', + 'sqlite' => 'Doctrine\DBAL\Platforms\Keywords\SQLiteKeywords', + 'pgsql' => 'Doctrine\DBAL\Platforms\Keywords\PostgreSQLKeywords', + 'oracle' => 'Doctrine\DBAL\Platforms\Keywords\OracleKeywords', + 'db2' => 'Doctrine\DBAL\Platforms\Keywords\DB2Keywords', + ); + + /** + * If you want to add or replace a keywords list use this command. + * + * @param string $name + * @param string $class + * + * @return void + */ + public function setKeywordListClass($name, $class) + { + $this->keywordListClasses[$name] = $class; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('dbal:reserved-words') + ->setDescription('Checks if the current database contains identifiers that are reserved.') + ->setDefinition(array( + new InputOption( + 'list', 'l', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Keyword-List name.' + ) + )) + ->setHelp(<<%command.full_name% + +If you want to check against specific dialects you can +pass them to the command: + + %command.full_name% mysql pgsql + +The following keyword lists are currently shipped with Doctrine: + + * mysql + * pgsql + * sqlite + * oracle + * sqlserver + * sqlserver2005 + * sqlserver2008 + * sqlserver2012 + * db2 (Not checked by default) +EOT + ); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + /* @var $conn \Doctrine\DBAL\Connection */ + $conn = $this->getHelper('db')->getConnection(); + + $keywordLists = (array)$input->getOption('list'); + if ( ! $keywordLists) { + $keywordLists = array( + 'mysql', + 'pgsql', + 'sqlite', + 'oracle', + 'sqlserver', + 'sqlserver2005', + 'sqlserver2008', + 'sqlserver2012' + ); + } + + $keywords = array(); + foreach ($keywordLists as $keywordList) { + if (!isset($this->keywordListClasses[$keywordList])) { + throw new \InvalidArgumentException( + "There exists no keyword list with name '" . $keywordList . "'. ". + "Known lists: " . implode(", ", array_keys($this->keywordListClasses)) + ); + } + $class = $this->keywordListClasses[$keywordList]; + $keywords[] = new $class; + } + + $output->write('Checking keyword violations for ' . implode(", ", $keywordLists) . "...", true); + + /* @var $schema \Doctrine\DBAL\Schema\Schema */ + $schema = $conn->getSchemaManager()->createSchema(); + $visitor = new ReservedKeywordsValidator($keywords); + $schema->visit($visitor); + + $violations = $visitor->getViolations(); + if (count($violations) == 0) { + $output->write("No reserved keywords violations have been found!", true); + } else { + $output->write('There are ' . count($violations) . ' reserved keyword violations in your database schema:', true); + foreach ($violations as $violation) { + $output->write(' - ' . $violation, true); + } + + return 1; + } + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php new file mode 100644 index 0000000..b2b2d97 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Command/RunSqlCommand.php @@ -0,0 +1,88 @@ +. + */ + +namespace Doctrine\DBAL\Tools\Console\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputOption; + +/** + * Task for executing arbitrary SQL that can come from a file or directly from + * the command line. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class RunSqlCommand extends Command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('dbal:run-sql') + ->setDescription('Executes arbitrary SQL directly from the command line.') + ->setDefinition(array( + new InputArgument('sql', InputArgument::REQUIRED, 'The SQL statement to execute.'), + new InputOption('depth', null, InputOption::VALUE_REQUIRED, 'Dumping depth of result set.', 7) + )) + ->setHelp(<<getHelper('db')->getConnection(); + + if (($sql = $input->getArgument('sql')) === null) { + throw new \RuntimeException("Argument 'SQL' is required in order to execute this command correctly."); + } + + $depth = $input->getOption('depth'); + + if ( ! is_numeric($depth)) { + throw new \LogicException("Option 'depth' must contains an integer value"); + } + + if (stripos($sql, 'select') === 0) { + $resultSet = $conn->fetchAll($sql); + } else { + $resultSet = $conn->executeUpdate($sql); + } + + ob_start(); + \Doctrine\Common\Util\Debug::dump($resultSet, (int) $depth); + $message = ob_get_clean(); + + $output->write($message); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Helper/ConnectionHelper.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Helper/ConnectionHelper.php new file mode 100644 index 0000000..1331c2b --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Tools/Console/Helper/ConnectionHelper.php @@ -0,0 +1,71 @@ +. + */ + +namespace Doctrine\DBAL\Tools\Console\Helper; + +use Symfony\Component\Console\Helper\Helper; +use Doctrine\DBAL\Connection; + +/** + * Doctrine CLI Connection Helper. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class ConnectionHelper extends Helper +{ + /** + * The Doctrine database Connection. + * + * @var \Doctrine\DBAL\Connection + */ + protected $_connection; + + /** + * Constructor. + * + * @param \Doctrine\DBAL\Connection $connection The Doctrine database Connection. + */ + public function __construct(Connection $connection) + { + $this->_connection = $connection; + } + + /** + * Retrieves the Doctrine database Connection. + * + * @return \Doctrine\DBAL\Connection + */ + public function getConnection() + { + return $this->_connection; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'connection'; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ArrayType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ArrayType.php new file mode 100644 index 0000000..f8c62cb --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ArrayType.php @@ -0,0 +1,81 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps a PHP array to a clob SQL type. + * + * @since 2.0 + */ +class ArrayType extends Type +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getClobTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + // @todo 3.0 - $value === null check to save real NULL in database + return serialize($value); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return null; + } + + $value = (is_resource($value)) ? stream_get_contents($value) : $value; + $val = unserialize($value); + if ($val === false && $value != 'b:0;') { + throw ConversionException::conversionFailed($value, $this->getName()); + } + + return $val; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::TARRAY; + } + + /** + * {@inheritdoc} + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return true; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BigIntType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BigIntType.php new file mode 100644 index 0000000..ded07ee --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BigIntType.php @@ -0,0 +1,63 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps a database BIGINT to a PHP string. + * + * @author robo + * @since 2.0 + */ +class BigIntType extends Type +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::BIGINT; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getBigIntTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function getBindingType() + { + return \PDO::PARAM_STR; + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return (null === $value) ? null : (string) $value; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BlobType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BlobType.php new file mode 100644 index 0000000..7ce033f --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BlobType.php @@ -0,0 +1,74 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps an SQL BLOB to a PHP resource stream. + * + * @since 2.2 + */ +class BlobType extends Type +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getBlobTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if (null === $value) { + return null; + } + + if (is_string($value)) { + $value = fopen('data://text/plain;base64,' . base64_encode($value), 'r'); + } + + if ( ! is_resource($value)) { + throw ConversionException::conversionFailed($value, self::BLOB); + } + + return $value; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::BLOB; + } + + /** + * {@inheritdoc} + */ + public function getBindingType() + { + return \PDO::PARAM_LOB; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BooleanType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BooleanType.php new file mode 100644 index 0000000..6b101fb --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/BooleanType.php @@ -0,0 +1,70 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps an SQL boolean to a PHP boolean. + * + * @since 2.0 + */ +class BooleanType extends Type +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getBooleanTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + return $platform->convertBooleans($value); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return (null === $value) ? null : (bool) $value; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::BOOLEAN; + } + + /** + * {@inheritdoc} + */ + public function getBindingType() + { + return \PDO::PARAM_BOOL; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ConversionException.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ConversionException.php new file mode 100644 index 0000000..4675101 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ConversionException.php @@ -0,0 +1,68 @@ +. + */ + +/** + * Conversion Exception is thrown when the database to PHP conversion fails. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +namespace Doctrine\DBAL\Types; + +class ConversionException extends \Doctrine\DBAL\DBALException +{ + /** + * Thrown when a Database to Doctrine Type Conversion fails. + * + * @param string $value + * @param string $toType + * + * @return \Doctrine\DBAL\Types\ConversionException + */ + static public function conversionFailed($value, $toType) + { + $value = (strlen($value) > 32) ? substr($value, 0, 20) . "..." : $value; + + return new self('Could not convert database value "' . $value . '" to Doctrine Type ' . $toType); + } + + /** + * Thrown when a Database to Doctrine Type Conversion fails and we can make a statement + * about the expected format. + * + * @param string $value + * @param string $toType + * @param string $expectedFormat + * + * @return \Doctrine\DBAL\Types\ConversionException + */ + static public function conversionFailedFormat($value, $toType, $expectedFormat) + { + $value = (strlen($value) > 32) ? substr($value, 0, 20) . "..." : $value; + + return new self( + 'Could not convert database value "' . $value . '" to Doctrine Type ' . + $toType . '. Expected format: ' . $expectedFormat + ); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateTimeType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateTimeType.php new file mode 100644 index 0000000..bd6eda1 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateTimeType.php @@ -0,0 +1,72 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps an SQL DATETIME/TIMESTAMP to a PHP DateTime object. + * + * @since 2.0 + */ +class DateTimeType extends Type +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::DATETIME; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getDateTimeTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + return ($value !== null) + ? $value->format($platform->getDateTimeFormatString()) : null; + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value instanceof \DateTime) { + return $value; + } + + $val = \DateTime::createFromFormat($platform->getDateTimeFormatString(), $value); + if ( ! $val) { + throw ConversionException::conversionFailedFormat($value, $this->getName(), $platform->getDateTimeFormatString()); + } + + return $val; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateTimeTzType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateTimeTzType.php new file mode 100644 index 0000000..7303ff0 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateTimeTzType.php @@ -0,0 +1,90 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * DateTime type saving additional timezone information. + * + * Caution: Databases are not necessarily experts at storing timezone related + * data of dates. First, of all the supported vendors only PostgreSQL and Oracle + * support storing Timezone data. But those two don't save the actual timezone + * attached to a DateTime instance (for example "Europe/Berlin" or "America/Montreal") + * but the current offset of them related to UTC. That means depending on daylight saving times + * or not you may get different offsets. + * + * This datatype makes only sense to use, if your application works with an offset, not + * with an actual timezone that uses transitions. Otherwise your DateTime instance + * attached with a timezone such as Europe/Berlin gets saved into the database with + * the offset and re-created from persistence with only the offset, not the original timezone + * attached. + * + * @link www.doctrine-project.org + * @since 1.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class DateTimeTzType extends Type +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::DATETIMETZ; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getDateTimeTzTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + return ($value !== null) + ? $value->format($platform->getDateTimeTzFormatString()) : null; + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value instanceof \DateTime) { + return $value; + } + + $val = \DateTime::createFromFormat($platform->getDateTimeTzFormatString(), $value); + if ( ! $val) { + throw ConversionException::conversionFailedFormat($value, $this->getName(), $platform->getDateTimeTzFormatString()); + } + + return $val; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateType.php new file mode 100644 index 0000000..9d33755 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateType.php @@ -0,0 +1,72 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps an SQL DATE to a PHP Date object. + * + * @since 2.0 + */ +class DateType extends Type +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::DATE; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getDateTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + return ($value !== null) + ? $value->format($platform->getDateFormatString()) : null; + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value instanceof \DateTime) { + return $value; + } + + $val = \DateTime::createFromFormat('!'.$platform->getDateFormatString(), $value); + if ( ! $val) { + throw ConversionException::conversionFailedFormat($value, $this->getName(), $platform->getDateFormatString()); + } + + return $val; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DecimalType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DecimalType.php new file mode 100644 index 0000000..df251d7 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DecimalType.php @@ -0,0 +1,54 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps an SQL DECIMAL to a PHP string. + * + * @since 2.0 + */ +class DecimalType extends Type +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::DECIMAL; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getDecimalTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return (null === $value) ? null : $value; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/FloatType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/FloatType.php new file mode 100644 index 0000000..87f9c32 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/FloatType.php @@ -0,0 +1,49 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +class FloatType extends Type +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::FLOAT; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getFloatDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return (null === $value) ? null : (double) $value; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/GuidType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/GuidType.php new file mode 100644 index 0000000..761d58a --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/GuidType.php @@ -0,0 +1,55 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Represents a GUID/UUID datatype (both are actually synonyms) in the database. + * + * @author Benjamin Eberlei + * @since 2.3 + */ +class GuidType extends StringType +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getGuidTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::GUID; + } + + /** + * {@inheritdoc} + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return !$platform->hasNativeGuidType(); + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/IntegerType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/IntegerType.php new file mode 100644 index 0000000..6fe6375 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/IntegerType.php @@ -0,0 +1,63 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps an SQL INT to a PHP integer. + * + * @author Roman Borschel + * @since 2.0 + */ +class IntegerType extends Type +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::INTEGER; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getIntegerTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return (null === $value) ? null : (int) $value; + } + + /** + * {@inheritdoc} + */ + public function getBindingType() + { + return \PDO::PARAM_INT; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/JsonArrayType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/JsonArrayType.php new file mode 100644 index 0000000..173c826 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/JsonArrayType.php @@ -0,0 +1,81 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Array Type which can be used to generate json arrays. + * + * @since 2.3 + * @author Johannes M. Schmitt + */ +class JsonArrayType extends Type +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getClobTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + if (null === $value) { + return null; + } + + return json_encode($value); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return array(); + } + + $value = (is_resource($value)) ? stream_get_contents($value) : $value; + + return json_decode($value, true); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::JSON_ARRAY; + } + + /** + * {@inheritdoc} + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return true; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ObjectType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ObjectType.php new file mode 100644 index 0000000..0b1f87a --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/ObjectType.php @@ -0,0 +1,80 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps a PHP object to a clob SQL type. + * + * @since 2.0 + */ +class ObjectType extends Type +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getClobTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + return serialize($value); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return null; + } + + $value = (is_resource($value)) ? stream_get_contents($value) : $value; + $val = unserialize($value); + if ($val === false && $value !== 'b:0;') { + throw ConversionException::conversionFailed($value, $this->getName()); + } + + return $val; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::OBJECT; + } + + /** + * {@inheritdoc} + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return true; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/SimpleArrayType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/SimpleArrayType.php new file mode 100644 index 0000000..47c821b --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/SimpleArrayType.php @@ -0,0 +1,83 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Array Type which can be used for simple values. + * + * Only use this type if you are sure that your values cannot contain a ",". + * + * @since 2.3 + * @author Johannes M. Schmitt + */ +class SimpleArrayType extends Type +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getClobTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + if (!$value) { + return null; + } + + return implode(',', $value); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return array(); + } + + $value = (is_resource($value)) ? stream_get_contents($value) : $value; + + return explode(',', $value); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::SIMPLE_ARRAY; + } + + /** + * {@inheritdoc} + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return true; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/SmallIntType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/SmallIntType.php new file mode 100644 index 0000000..7ebae6e --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/SmallIntType.php @@ -0,0 +1,62 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps a database SMALLINT to a PHP integer. + * + * @author robo + */ +class SmallIntType extends Type +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::SMALLINT; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getSmallIntTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return (null === $value) ? null : (int) $value; + } + + /** + * {@inheritdoc} + */ + public function getBindingType() + { + return \PDO::PARAM_INT; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/StringType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/StringType.php new file mode 100644 index 0000000..39bd32d --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/StringType.php @@ -0,0 +1,54 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps an SQL VARCHAR to a PHP string. + * + * @since 2.0 + */ +class StringType extends Type +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getVarcharTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function getDefaultLength(AbstractPlatform $platform) + { + return $platform->getVarcharDefaultLength(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::STRING; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/TextType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/TextType.php new file mode 100644 index 0000000..4055ac8 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/TextType.php @@ -0,0 +1,54 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps an SQL CLOB to a PHP string. + * + * @since 2.0 + */ +class TextType extends Type +{ + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getClobTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return (is_resource($value)) ? stream_get_contents($value) : $value; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::TEXT; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/TimeType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/TimeType.php new file mode 100644 index 0000000..54e740b --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/TimeType.php @@ -0,0 +1,72 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Type that maps an SQL TIME to a PHP DateTime object. + * + * @since 2.0 + */ +class TimeType extends Type +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return Type::TIME; + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform) + { + return $platform->getTimeTypeDeclarationSQL($fieldDeclaration); + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + return ($value !== null) + ? $value->format($platform->getTimeFormatString()) : null; + } + + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value instanceof \DateTime) { + return $value; + } + + $val = \DateTime::createFromFormat($platform->getTimeFormatString(), $value); + if ( ! $val) { + throw ConversionException::conversionFailedFormat($value, $this->getName(), $platform->getTimeFormatString()); + } + + return $val; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/Type.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/Type.php new file mode 100644 index 0000000..73f853f --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/Type.php @@ -0,0 +1,339 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\DBALException; + +/** + * The base class for so-called Doctrine mapping types. + * + * A Type object is obtained by calling the static {@link getType()} method. + * + * @author Roman Borschel + * @author Benjamin Eberlei + * @since 2.0 + */ +abstract class Type +{ + const TARRAY = 'array'; + const SIMPLE_ARRAY = 'simple_array'; + const JSON_ARRAY = 'json_array'; + const BIGINT = 'bigint'; + const BOOLEAN = 'boolean'; + const DATETIME = 'datetime'; + const DATETIMETZ = 'datetimetz'; + const DATE = 'date'; + const TIME = 'time'; + const DECIMAL = 'decimal'; + const INTEGER = 'integer'; + const OBJECT = 'object'; + const SMALLINT = 'smallint'; + const STRING = 'string'; + const TEXT = 'text'; + const BLOB = 'blob'; + const FLOAT = 'float'; + const GUID = 'guid'; + + /** + * Map of already instantiated type objects. One instance per type (flyweight). + * + * @var array + */ + private static $_typeObjects = array(); + + /** + * The map of supported doctrine mapping types. + * + * @var array + */ + private static $_typesMap = array( + self::TARRAY => 'Doctrine\DBAL\Types\ArrayType', + self::SIMPLE_ARRAY => 'Doctrine\DBAL\Types\SimpleArrayType', + self::JSON_ARRAY => 'Doctrine\DBAL\Types\JsonArrayType', + self::OBJECT => 'Doctrine\DBAL\Types\ObjectType', + self::BOOLEAN => 'Doctrine\DBAL\Types\BooleanType', + self::INTEGER => 'Doctrine\DBAL\Types\IntegerType', + self::SMALLINT => 'Doctrine\DBAL\Types\SmallIntType', + self::BIGINT => 'Doctrine\DBAL\Types\BigIntType', + self::STRING => 'Doctrine\DBAL\Types\StringType', + self::TEXT => 'Doctrine\DBAL\Types\TextType', + self::DATETIME => 'Doctrine\DBAL\Types\DateTimeType', + self::DATETIMETZ => 'Doctrine\DBAL\Types\DateTimeTzType', + self::DATE => 'Doctrine\DBAL\Types\DateType', + self::TIME => 'Doctrine\DBAL\Types\TimeType', + self::DECIMAL => 'Doctrine\DBAL\Types\DecimalType', + self::FLOAT => 'Doctrine\DBAL\Types\FloatType', + self::BLOB => 'Doctrine\DBAL\Types\BlobType', + self::GUID => 'Doctrine\DBAL\Types\GuidType', + ); + + /** + * Prevents instantiation and forces use of the factory method. + */ + final private function __construct() + { + } + + /** + * Converts a value from its PHP representation to its database representation + * of this type. + * + * @param mixed $value The value to convert. + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The currently used database platform. + * + * @return mixed The database representation of the value. + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + return $value; + } + + /** + * Converts a value from its database representation to its PHP representation + * of this type. + * + * @param mixed $value The value to convert. + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The currently used database platform. + * + * @return mixed The PHP representation of the value. + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return $value; + } + + /** + * Gets the default length of this type. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return integer|null + * + * @todo Needed? + */ + public function getDefaultLength(AbstractPlatform $platform) + { + return null; + } + + /** + * Gets the SQL declaration snippet for a field of this type. + * + * @param array $fieldDeclaration The field declaration. + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The currently used database platform. + * + * @return string + */ + abstract public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform); + + /** + * Gets the name of this type. + * + * @return string + * + * @todo Needed? + */ + abstract public function getName(); + + /** + * Factory method to create type instances. + * Type instances are implemented as flyweights. + * + * @param string $name The name of the type (as returned by getName()). + * + * @return \Doctrine\DBAL\Types\Type + * + * @throws \Doctrine\DBAL\DBALException + */ + public static function getType($name) + { + if ( ! isset(self::$_typeObjects[$name])) { + if ( ! isset(self::$_typesMap[$name])) { + throw DBALException::unknownColumnType($name); + } + self::$_typeObjects[$name] = new self::$_typesMap[$name](); + } + + return self::$_typeObjects[$name]; + } + + /** + * Adds a custom type to the type map. + * + * @param string $name The name of the type. This should correspond to what getName() returns. + * @param string $className The class name of the custom type. + * + * @return void + * + * @throws \Doctrine\DBAL\DBALException + */ + public static function addType($name, $className) + { + if (isset(self::$_typesMap[$name])) { + throw DBALException::typeExists($name); + } + + self::$_typesMap[$name] = $className; + } + + /** + * Checks if exists support for a type. + * + * @param string $name The name of the type. + * + * @return boolean TRUE if type is supported; FALSE otherwise. + */ + public static function hasType($name) + { + return isset(self::$_typesMap[$name]); + } + + /** + * Overrides an already defined type to use a different implementation. + * + * @param string $name + * @param string $className + * + * @return void + * + * @throws \Doctrine\DBAL\DBALException + */ + public static function overrideType($name, $className) + { + if ( ! isset(self::$_typesMap[$name])) { + throw DBALException::typeNotFound($name); + } + + if (isset(self::$_typeObjects[$name])) { + unset(self::$_typeObjects[$name]); + } + + self::$_typesMap[$name] = $className; + } + + /** + * Gets the (preferred) binding type for values of this type that + * can be used when binding parameters to prepared statements. + * + * This method should return one of the PDO::PARAM_* constants, that is, one of: + * + * PDO::PARAM_BOOL + * PDO::PARAM_NULL + * PDO::PARAM_INT + * PDO::PARAM_STR + * PDO::PARAM_LOB + * + * @return integer + */ + public function getBindingType() + { + return \PDO::PARAM_STR; + } + + /** + * Gets the types array map which holds all registered types and the corresponding + * type class + * + * @return array + */ + public static function getTypesMap() + { + return self::$_typesMap; + } + + /** + * @return string + */ + public function __toString() + { + $e = explode('\\', get_class($this)); + + return str_replace('Type', '', end($e)); + } + + /** + * Does working with this column require SQL conversion functions? + * + * This is a metadata function that is required for example in the ORM. + * Usage of {@link convertToDatabaseValueSQL} and + * {@link convertToPHPValueSQL} works for any type and mostly + * does nothing. This method can additionally be used for optimization purposes. + * + * @return boolean + */ + public function canRequireSQLConversion() + { + return false; + } + + /** + * Modifies the SQL expression (identifier, parameter) to convert to a database value. + * + * @param string $sqlExpr + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return string + */ + public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform) + { + return $sqlExpr; + } + + /** + * Modifies the SQL expression (identifier, parameter) to convert to a PHP value. + * + * @param string $sqlExpr + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return string + */ + public function convertToPHPValueSQL($sqlExpr, $platform) + { + return $sqlExpr; + } + + /** + * Gets an array of database types that map to this Doctrine type. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return array + */ + public function getMappedDatabaseTypes(AbstractPlatform $platform) + { + return array(); + } + + /** + * If this Doctrine Type maps to an already mapped database type, + * reverse schema engineering can't take them apart. You need to mark + * one of those types as commented, which will have Doctrine use an SQL + * comment to typehint the actual Doctrine Type. + * + * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform + * + * @return boolean + */ + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return false; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/VarDateTimeType.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/VarDateTimeType.php new file mode 100644 index 0000000..52bd56b --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/VarDateTimeType.php @@ -0,0 +1,55 @@ +. + */ + +namespace Doctrine\DBAL\Types; + +use Doctrine\DBAL\Platforms\AbstractPlatform; + +/** + * Variable DateTime Type using date_create() instead of DateTime::createFromFormat(). + * + * This type has performance implications as it runs twice as long as the regular + * {@see DateTimeType}, however in certain PostgreSQL configurations with + * TIMESTAMP(n) columns where n > 0 it is necessary to use this type. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class VarDateTimeType extends DateTimeType +{ + /** + * {@inheritdoc} + */ + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value instanceof \DateTime) { + return $value; + } + + $val = date_create($value); + if ( ! $val) { + throw ConversionException::conversionFailed($value, $this->getName()); + } + return $val; + } +} diff --git a/vendor/doctrine/dbal/lib/Doctrine/DBAL/Version.php b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Version.php new file mode 100644 index 0000000..60ee424 --- /dev/null +++ b/vendor/doctrine/dbal/lib/Doctrine/DBAL/Version.php @@ -0,0 +1,53 @@ +. + */ + +namespace Doctrine\DBAL; + +/** + * Class to store and retrieve the version of Doctrine. + * + * @link www.doctrine-project.org + * @since 2.0 + * @author Benjamin Eberlei + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +class Version +{ + /** + * Current Doctrine Version. + */ + const VERSION = '2.4.0-DEV'; + + /** + * Compares a Doctrine version with the current one. + * + * @param string $version The Doctrine version to compare to. + * + * @return integer -1 if older, 0 if it is the same, 1 if version passed as argument is newer. + */ + public static function compare($version) + { + $currentVersion = str_replace(' ', '', strtolower(self::VERSION)); + $version = str_replace(' ', '', $version); + + return version_compare($version, $currentVersion); + } +} diff --git a/vendor/doctrine/inflector/.travis.yml b/vendor/doctrine/inflector/.travis.yml new file mode 100644 index 0000000..478e5d6 --- /dev/null +++ b/vendor/doctrine/inflector/.travis.yml @@ -0,0 +1,8 @@ +language: php + +php: + - 5.3 + - 5.4 + +before_script: + - composer --prefer-source --dev install diff --git a/vendor/doctrine/inflector/README.md b/vendor/doctrine/inflector/README.md new file mode 100644 index 0000000..f2d18d0 --- /dev/null +++ b/vendor/doctrine/inflector/README.md @@ -0,0 +1,4 @@ +# Doctrine Inflector + +Doctrine Inflector is a small library that can perform string manipulations +with regard to upper-/lowercase and singular/plural forms of words. diff --git a/vendor/doctrine/inflector/composer.json b/vendor/doctrine/inflector/composer.json new file mode 100644 index 0000000..ee00500 --- /dev/null +++ b/vendor/doctrine/inflector/composer.json @@ -0,0 +1,26 @@ +{ + "name": "doctrine/inflector", + "type": "library", + "description": "Common String Manipulations with regard to casing and singular/plural rules.", + "keywords": ["string", "inflection", "singularize", "pluralize"], + "homepage": "http://www.doctrine-project.org", + "license": "MIT", + "authors": [ + {"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"}, + {"name": "Roman Borschel", "email": "roman@code-factory.org"}, + {"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"}, + {"name": "Jonathan Wage", "email": "jonwage@gmail.com"}, + {"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"} + ], + "require": { + "php": ">=5.3.2" + }, + "autoload": { + "psr-0": { "Doctrine\\Common\\Inflector\\": "lib/" } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php b/vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php new file mode 100644 index 0000000..33e56a6 --- /dev/null +++ b/vendor/doctrine/inflector/lib/Doctrine/Common/Inflector/Inflector.php @@ -0,0 +1,391 @@ +. + */ + +namespace Doctrine\Common\Inflector; + +/** + * Doctrine inflector has static methods for inflecting text. + * + * The methods in these classes are from several different sources collected + * across several different php projects and several different authors. The + * original author names and emails are not known. + * + * Pluralize & Singularize implementation are borrowed from CakePHP with some modifications. + * + * @link www.doctrine-project.org + * @since 1.0 + * @author Konsta Vesterinen + * @author Jonathan H. Wage + */ +class Inflector +{ + /** + * Plural inflector rules. + * + * @var array + */ + private static $plural = array( + 'rules' => array( + '/(s)tatus$/i' => '\1\2tatuses', + '/(quiz)$/i' => '\1zes', + '/^(ox)$/i' => '\1\2en', + '/([m|l])ouse$/i' => '\1ice', + '/(matr|vert|ind)(ix|ex)$/i' => '\1ices', + '/(x|ch|ss|sh)$/i' => '\1es', + '/([^aeiouy]|qu)y$/i' => '\1ies', + '/(hive)$/i' => '\1s', + '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', + '/sis$/i' => 'ses', + '/([ti])um$/i' => '\1a', + '/(p)erson$/i' => '\1eople', + '/(m)an$/i' => '\1en', + '/(c)hild$/i' => '\1hildren', + '/(buffal|tomat)o$/i' => '\1\2oes', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i', + '/us$/i' => 'uses', + '/(alias)$/i' => '\1es', + '/(ax|cris|test)is$/i' => '\1es', + '/s$/' => 's', + '/^$/' => '', + '/$/' => 's', + ), + 'uninflected' => array( + '.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', 'people', 'cookie' + ), + 'irregular' => array( + 'atlas' => 'atlases', + 'beef' => 'beefs', + 'brother' => 'brothers', + 'cafe' => 'cafes', + 'child' => 'children', + 'cookie' => 'cookies', + 'corpus' => 'corpuses', + 'cow' => 'cows', + 'ganglion' => 'ganglions', + 'genie' => 'genies', + 'genus' => 'genera', + 'graffito' => 'graffiti', + 'hoof' => 'hoofs', + 'loaf' => 'loaves', + 'man' => 'men', + 'money' => 'monies', + 'mongoose' => 'mongooses', + 'move' => 'moves', + 'mythos' => 'mythoi', + 'niche' => 'niches', + 'numen' => 'numina', + 'occiput' => 'occiputs', + 'octopus' => 'octopuses', + 'opus' => 'opuses', + 'ox' => 'oxen', + 'penis' => 'penises', + 'person' => 'people', + 'sex' => 'sexes', + 'soliloquy' => 'soliloquies', + 'testis' => 'testes', + 'trilby' => 'trilbys', + 'turf' => 'turfs' + ) + ); + + /** + * Singular inflector rules. + * + * @var array + */ + private static $singular = array( + 'rules' => array( + '/(s)tatuses$/i' => '\1\2tatus', + '/^(.*)(menu)s$/i' => '\1\2', + '/(quiz)zes$/i' => '\\1', + '/(matr)ices$/i' => '\1ix', + '/(vert|ind)ices$/i' => '\1ex', + '/^(ox)en/i' => '\1', + '/(alias)(es)*$/i' => '\1', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', + '/([ftw]ax)es/i' => '\1', + '/(cris|ax|test)es$/i' => '\1is', + '/(shoe|slave)s$/i' => '\1', + '/(o)es$/i' => '\1', + '/ouses$/' => 'ouse', + '/([^a])uses$/' => '\1us', + '/([m|l])ice$/i' => '\1ouse', + '/(x|ch|ss|sh)es$/i' => '\1', + '/(m)ovies$/i' => '\1\2ovie', + '/(s)eries$/i' => '\1\2eries', + '/([^aeiouy]|qu)ies$/i' => '\1y', + '/([lr])ves$/i' => '\1f', + '/(tive)s$/i' => '\1', + '/(hive)s$/i' => '\1', + '/(drive)s$/i' => '\1', + '/([^fo])ves$/i' => '\1fe', + '/(^analy)ses$/i' => '\1sis', + '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', + '/([ti])a$/i' => '\1um', + '/(p)eople$/i' => '\1\2erson', + '/(m)en$/i' => '\1an', + '/(c)hildren$/i' => '\1\2hild', + '/(n)ews$/i' => '\1\2ews', + '/eaus$/' => 'eau', + '/^(.*us)$/' => '\\1', + '/s$/i' => '' + ), + 'uninflected' => array( + '.*[nrlm]ese', '.*deer', '.*fish', '.*measles', '.*ois', '.*pox', '.*sheep', '.*ss' + ), + 'irregular' => array( + 'foes' => 'foe', + 'waves' => 'wave', + 'curves' => 'curve' + ) + ); + + /** + * Words that should not be inflected. + * + * @var array + */ + private static $uninflected = array( + 'Amoyese', 'bison', 'Borghese', 'bream', 'breeches', 'britches', 'buffalo', 'cantus', + 'carp', 'chassis', 'clippers', 'cod', 'coitus', 'Congoese', 'contretemps', 'corps', + 'debris', 'diabetes', 'djinn', 'eland', 'elk', 'equipment', 'Faroese', 'flounder', + 'Foochowese', 'gallows', 'Genevese', 'Genoese', 'Gilbertese', 'graffiti', + 'headquarters', 'herpes', 'hijinks', 'Hottentotese', 'information', 'innings', + 'jackanapes', 'Kiplingese', 'Kongoese', 'Lucchese', 'mackerel', 'Maltese', '.*?media', + 'mews', 'moose', 'mumps', 'Nankingese', 'news', 'nexus', 'Niasese', + 'Pekingese', 'Piedmontese', 'pincers', 'Pistoiese', 'pliers', 'Portuguese', + 'proceedings', 'rabies', 'rice', 'rhinoceros', 'salmon', 'Sarawakese', 'scissors', + 'sea[- ]bass', 'series', 'Shavese', 'shears', 'siemens', 'species', 'staff', 'swine', + 'testes', 'trousers', 'trout', 'tuna', 'Vermontese', 'Wenchowese', 'whiting', + 'wildebeest', 'Yengeese' + ); + + /** + * Method cache array. + * + * @var array + */ + private static $cache = array(); + + /** + * The initial state of Inflector so reset() works. + * + * @var array + */ + private static $initialState = array(); + + /** + * Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'. + * + * @param string $word The word to tableize. + * + * @return string The tableized word. + */ + public static function tableize($word) + { + return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $word)); + } + + /** + * Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'. + * + * @param string $word The word to classify. + * + * @return string The classified word. + */ + public static function classify($word) + { + return str_replace(" ", "", ucwords(strtr($word, "_-", " "))); + } + + /** + * Camelizes a word. This uses the classify() method and turns the first character to lowercase. + * + * @param string $word The word to camelize. + * + * @return string The camelized word. + */ + public static function camelize($word) + { + return lcfirst(self::classify($word)); + } + + /** + * Clears Inflectors inflected value caches, and resets the inflection + * rules to the initial values. + * + * @return void + */ + public static function reset() + { + if (empty(self::$initialState)) { + self::$initialState = get_class_vars('Inflector'); + return; + } + foreach (self::$initialState as $key => $val) { + if ($key != 'initialState') { + self::${$key} = $val; + } + } + } + + /** + * Adds custom inflection $rules, of either 'plural' or 'singular' $type. + * + * ### Usage: + * + * {{{ + * Inflector::rules('plural', array('/^(inflect)or$/i' => '\1ables')); + * Inflector::rules('plural', array( + * 'rules' => array('/^(inflect)ors$/i' => '\1ables'), + * 'uninflected' => array('dontinflectme'), + * 'irregular' => array('red' => 'redlings') + * )); + * }}} + * + * @param string $type The type of inflection, either 'plural' or 'singular' + * @param array $rules An array of rules to be added. + * @param boolean $reset If true, will unset default inflections for all + * new rules that are being defined in $rules. + * + * @return void + */ + public static function rules($type, $rules, $reset = false) + { + foreach ($rules as $rule => $pattern) { + if (is_array($pattern)) { + if ($reset) { + self::${$type}[$rule] = $pattern; + } else { + if ($rule === 'uninflected') { + self::${$type}[$rule] = array_merge($pattern, self::${$type}[$rule]); + } else { + self::${$type}[$rule] = $pattern + self::${$type}[$rule]; + } + } + unset($rules[$rule], self::${$type}['cache' . ucfirst($rule)]); + if (isset(self::${$type}['merged'][$rule])) { + unset(self::${$type}['merged'][$rule]); + } + if ($type === 'plural') { + self::$cache['pluralize'] = self::$cache['tableize'] = array(); + } elseif ($type === 'singular') { + self::$cache['singularize'] = array(); + } + } + } + self::${$type}['rules'] = $rules + self::${$type}['rules']; + } + + /** + * Returns a word in plural form. + * + * @param string $word The word in singular form. + * + * @return string The word in plural form. + */ + public static function pluralize($word) + { + if (isset(self::$cache['pluralize'][$word])) { + return self::$cache['pluralize'][$word]; + } + + if (!isset(self::$plural['merged']['irregular'])) { + self::$plural['merged']['irregular'] = self::$plural['irregular']; + } + + if (!isset(self::$plural['merged']['uninflected'])) { + self::$plural['merged']['uninflected'] = array_merge(self::$plural['uninflected'], self::$uninflected); + } + + if (!isset(self::$plural['cacheUninflected']) || !isset(self::$plural['cacheIrregular'])) { + self::$plural['cacheUninflected'] = '(?:' . implode('|', self::$plural['merged']['uninflected']) . ')'; + self::$plural['cacheIrregular'] = '(?:' . implode('|', array_keys(self::$plural['merged']['irregular'])) . ')'; + } + + if (preg_match('/(.*)\\b(' . self::$plural['cacheIrregular'] . ')$/i', $word, $regs)) { + self::$cache['pluralize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$plural['merged']['irregular'][strtolower($regs[2])], 1); + return self::$cache['pluralize'][$word]; + } + + if (preg_match('/^(' . self::$plural['cacheUninflected'] . ')$/i', $word, $regs)) { + self::$cache['pluralize'][$word] = $word; + return $word; + } + + foreach (self::$plural['rules'] as $rule => $replacement) { + if (preg_match($rule, $word)) { + self::$cache['pluralize'][$word] = preg_replace($rule, $replacement, $word); + return self::$cache['pluralize'][$word]; + } + } + } + + /** + * Returns a word in singular form. + * + * @param string $word The word in plural form. + * + * @return string The word in singular form. + */ + public static function singularize($word) + { + if (isset(self::$cache['singularize'][$word])) { + return self::$cache['singularize'][$word]; + } + + if (!isset(self::$singular['merged']['uninflected'])) { + self::$singular['merged']['uninflected'] = array_merge( + self::$singular['uninflected'], + self::$uninflected + ); + } + + if (!isset(self::$singular['merged']['irregular'])) { + self::$singular['merged']['irregular'] = array_merge( + self::$singular['irregular'], + array_flip(self::$plural['irregular']) + ); + } + + if (!isset(self::$singular['cacheUninflected']) || !isset(self::$singular['cacheIrregular'])) { + self::$singular['cacheUninflected'] = '(?:' . join('|', self::$singular['merged']['uninflected']) . ')'; + self::$singular['cacheIrregular'] = '(?:' . join('|', array_keys(self::$singular['merged']['irregular'])) . ')'; + } + + if (preg_match('/(.*)\\b(' . self::$singular['cacheIrregular'] . ')$/i', $word, $regs)) { + self::$cache['singularize'][$word] = $regs[1] . substr($word, 0, 1) . substr(self::$singular['merged']['irregular'][strtolower($regs[2])], 1); + return self::$cache['singularize'][$word]; + } + + if (preg_match('/^(' . self::$singular['cacheUninflected'] . ')$/i', $word, $regs)) { + self::$cache['singularize'][$word] = $word; + return $word; + } + + foreach (self::$singular['rules'] as $rule => $replacement) { + if (preg_match($rule, $word)) { + self::$cache['singularize'][$word] = preg_replace($rule, $replacement, $word); + return self::$cache['singularize'][$word]; + } + } + self::$cache['singularize'][$word] = $word; + return $word; + } +} diff --git a/vendor/doctrine/inflector/phpunit.xml.dist b/vendor/doctrine/inflector/phpunit.xml.dist new file mode 100644 index 0000000..ef07faa --- /dev/null +++ b/vendor/doctrine/inflector/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + ./tests/Doctrine/ + + + + + + ./lib/Doctrine/ + + + + + + performance + + + diff --git a/vendor/doctrine/inflector/tests/Doctrine/Tests/Common/Inflector/InflectorTest.php b/vendor/doctrine/inflector/tests/Doctrine/Tests/Common/Inflector/InflectorTest.php new file mode 100644 index 0000000..487c2cd --- /dev/null +++ b/vendor/doctrine/inflector/tests/Doctrine/Tests/Common/Inflector/InflectorTest.php @@ -0,0 +1,185 @@ +assertEquals($singular, Inflector::singularize($plural), "'$plural' should be singularized to '$singular'"); + } + + /** + * testInflectingPlurals method + * + * @dataProvider dataSampleWords + * @return void + */ + public function testInflectingPlurals($singular, $plural) { + $this->assertEquals($plural, Inflector::pluralize($singular), "'$singular' should be pluralized to '$plural'"); + } + + /** + * testCustomPluralRule method + * + * @return void + */ + public function testCustomPluralRule() { + Inflector::reset(); + Inflector::rules('plural', array('/^(custom)$/i' => '\1izables')); + $this->assertEquals(Inflector::pluralize('custom'), 'customizables'); + + Inflector::rules('plural', array('uninflected' => array('uninflectable'))); + $this->assertEquals(Inflector::pluralize('uninflectable'), 'uninflectable'); + + Inflector::rules('plural', array( + 'rules' => array('/^(alert)$/i' => '\1ables'), + 'uninflected' => array('noflect', 'abtuse'), + 'irregular' => array('amaze' => 'amazable', 'phone' => 'phonezes') + )); + $this->assertEquals(Inflector::pluralize('noflect'), 'noflect'); + $this->assertEquals(Inflector::pluralize('abtuse'), 'abtuse'); + $this->assertEquals(Inflector::pluralize('alert'), 'alertables'); + $this->assertEquals(Inflector::pluralize('amaze'), 'amazable'); + $this->assertEquals(Inflector::pluralize('phone'), 'phonezes'); + } + + /** + * testCustomSingularRule method + * + * @return void + */ + public function testCustomSingularRule() { + Inflector::reset(); + Inflector::rules('singular', array('/(eple)r$/i' => '\1', '/(jente)r$/i' => '\1')); + + $this->assertEquals(Inflector::singularize('epler'), 'eple'); + $this->assertEquals(Inflector::singularize('jenter'), 'jente'); + + Inflector::rules('singular', array( + 'rules' => array('/^(bil)er$/i' => '\1', '/^(inflec|contribu)tors$/i' => '\1ta'), + 'uninflected' => array('singulars'), + 'irregular' => array('spins' => 'spinor') + )); + + $this->assertEquals(Inflector::singularize('inflectors'), 'inflecta'); + $this->assertEquals(Inflector::singularize('contributors'), 'contributa'); + $this->assertEquals(Inflector::singularize('spins'), 'spinor'); + $this->assertEquals(Inflector::singularize('singulars'), 'singulars'); + } + + /** + * test that setting new rules clears the inflector caches. + * + * @return void + */ + public function testRulesClearsCaches() { + Inflector::reset(); + $this->assertEquals(Inflector::singularize('Bananas'), 'Banana'); + $this->assertEquals(Inflector::pluralize('Banana'), 'Bananas'); + + Inflector::rules('singular', array( + 'rules' => array('/(.*)nas$/i' => '\1zzz') + )); + $this->assertEquals('Banazzz', Inflector::singularize('Bananas'), 'Was inflected with old rules.'); + + Inflector::rules('plural', array( + 'rules' => array('/(.*)na$/i' => '\1zzz'), + 'irregular' => array('corpus' => 'corpora') + )); + $this->assertEquals(Inflector::pluralize('Banana'), 'Banazzz', 'Was inflected with old rules.'); + $this->assertEquals(Inflector::pluralize('corpus'), 'corpora', 'Was inflected with old irregular form.'); + } + + /** + * Test resetting inflection rules. + * + * @return void + */ + public function testCustomRuleWithReset() { + Inflector::reset(); + $uninflected = array('atlas', 'lapis', 'onibus', 'pires', 'virus', '.*x'); + $pluralIrregular = array('as' => 'ases'); + + Inflector::rules('singular', array( + 'rules' => array('/^(.*)(a|e|o|u)is$/i' => '\1\2l'), + 'uninflected' => $uninflected, + ), true); + + Inflector::rules('plural', array( + 'rules' => array( + '/^(.*)(a|e|o|u)l$/i' => '\1\2is', + ), + 'uninflected' => $uninflected, + 'irregular' => $pluralIrregular + ), true); + + $this->assertEquals(Inflector::pluralize('Alcool'), 'Alcoois'); + $this->assertEquals(Inflector::pluralize('Atlas'), 'Atlas'); + $this->assertEquals(Inflector::singularize('Alcoois'), 'Alcool'); + $this->assertEquals(Inflector::singularize('Atlas'), 'Atlas'); + } +} + diff --git a/vendor/doctrine/inflector/tests/Doctrine/Tests/DoctrineTestCase.php b/vendor/doctrine/inflector/tests/Doctrine/Tests/DoctrineTestCase.php new file mode 100644 index 0000000..e8323d2 --- /dev/null +++ b/vendor/doctrine/inflector/tests/Doctrine/Tests/DoctrineTestCase.php @@ -0,0 +1,10 @@ +=5.3.2" + }, + "autoload": { + "psr-0": { "Doctrine\\Common\\Lexer\\": "lib/" } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php b/vendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php new file mode 100644 index 0000000..3758b43 --- /dev/null +++ b/vendor/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php @@ -0,0 +1,292 @@ +. + */ + +namespace Doctrine\Common\Lexer; + +/** + * Base class for writing simple lexers, i.e. for creating small DSLs. + * + * @since 2.0 + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + */ +abstract class AbstractLexer +{ + /** + * Array of scanned tokens. + * + * @var array + */ + private $tokens = array(); + + /** + * Current lexer position in input string. + * + * @var integer + */ + private $position = 0; + + /** + * Current peek of current lexer position. + * + * @var integer + */ + private $peek = 0; + + /** + * The next token in the input. + * + * @var array + */ + public $lookahead; + + /** + * The last matched/seen token. + * + * @var array + */ + public $token; + + /** + * Sets the input data to be tokenized. + * + * The Lexer is immediately reset and the new input tokenized. + * Any unprocessed tokens from any previous input are lost. + * + * @param string $input The input to be tokenized. + * + * @return void + */ + public function setInput($input) + { + $this->tokens = array(); + $this->reset(); + $this->scan($input); + } + + /** + * Resets the lexer. + * + * @return void + */ + public function reset() + { + $this->lookahead = null; + $this->token = null; + $this->peek = 0; + $this->position = 0; + } + + /** + * Resets the peek pointer to 0. + * + * @return void + */ + public function resetPeek() + { + $this->peek = 0; + } + + /** + * Resets the lexer position on the input to the given position. + * + * @param integer $position Position to place the lexical scanner. + * + * @return void + */ + public function resetPosition($position = 0) + { + $this->position = $position; + } + + /** + * Checks whether a given token matches the current lookahead. + * + * @param integer|string $token + * + * @return boolean + */ + public function isNextToken($token) + { + return null !== $this->lookahead && $this->lookahead['type'] === $token; + } + + /** + * Checks whether any of the given tokens matches the current lookahead. + * + * @param array $tokens + * + * @return boolean + */ + public function isNextTokenAny(array $tokens) + { + return null !== $this->lookahead && in_array($this->lookahead['type'], $tokens, true); + } + + /** + * Moves to the next token in the input string. + * + * A token is an associative array containing three items: + * - 'value' : the string value of the token in the input string + * - 'type' : the type of the token (identifier, numeric, string, input + * parameter, none) + * - 'position' : the position of the token in the input string + * + * @return array|null The next token; null if there is no more tokens left. + */ + public function moveNext() + { + $this->peek = 0; + $this->token = $this->lookahead; + $this->lookahead = (isset($this->tokens[$this->position])) + ? $this->tokens[$this->position++] : null; + + return $this->lookahead !== null; + } + + /** + * Tells the lexer to skip input tokens until it sees a token with the given value. + * + * @param string $type The token type to skip until. + * + * @return void + */ + public function skipUntil($type) + { + while ($this->lookahead !== null && $this->lookahead['type'] !== $type) { + $this->moveNext(); + } + } + + /** + * Checks if given value is identical to the given token. + * + * @param mixed $value + * @param integer $token + * + * @return boolean + */ + public function isA($value, $token) + { + return $this->getType($value) === $token; + } + + /** + * Moves the lookahead token forward. + * + * @return array|null The next token or NULL if there are no more tokens ahead. + */ + public function peek() + { + if (isset($this->tokens[$this->position + $this->peek])) { + return $this->tokens[$this->position + $this->peek++]; + } else { + return null; + } + } + + /** + * Peeks at the next token, returns it and immediately resets the peek. + * + * @return array|null The next token or NULL if there are no more tokens ahead. + */ + public function glimpse() + { + $peek = $this->peek(); + $this->peek = 0; + return $peek; + } + + /** + * Scans the input string for tokens. + * + * @param string $input A query string. + * + * @return void + */ + protected function scan($input) + { + static $regex; + + if ( ! isset($regex)) { + $regex = '/(' . implode(')|(', $this->getCatchablePatterns()) . ')|' + . implode('|', $this->getNonCatchablePatterns()) . '/i'; + } + + $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE; + $matches = preg_split($regex, $input, -1, $flags); + + foreach ($matches as $match) { + // Must remain before 'value' assignment since it can change content + $type = $this->getType($match[0]); + + $this->tokens[] = array( + 'value' => $match[0], + 'type' => $type, + 'position' => $match[1], + ); + } + } + + /** + * Gets the literal for a given token. + * + * @param integer $token + * + * @return string + */ + public function getLiteral($token) + { + $className = get_class($this); + $reflClass = new \ReflectionClass($className); + $constants = $reflClass->getConstants(); + + foreach ($constants as $name => $value) { + if ($value === $token) { + return $className . '::' . $name; + } + } + + return $token; + } + + /** + * Lexical catchable patterns. + * + * @return array + */ + abstract protected function getCatchablePatterns(); + + /** + * Lexical non-catchable patterns. + * + * @return array + */ + abstract protected function getNonCatchablePatterns(); + + /** + * Retrieve token type. Also processes the token value if necessary. + * + * @param string $value + * + * @return integer + */ + abstract protected function getType(&$value); +} diff --git a/vendor/filp/whoops/.gitignore b/vendor/filp/whoops/.gitignore new file mode 100644 index 0000000..b651323 --- /dev/null +++ b/vendor/filp/whoops/.gitignore @@ -0,0 +1,4 @@ +vendor/* +report/* +phpunit.xml +*.swp diff --git a/vendor/filp/whoops/.travis.yml b/vendor/filp/whoops/.travis.yml new file mode 100644 index 0000000..076a66b --- /dev/null +++ b/vendor/filp/whoops/.travis.yml @@ -0,0 +1,6 @@ +language: php +php: + - 5.4 + - 5.3 +before_script: + - composer install --dev diff --git a/vendor/filp/whoops/LICENSE.md b/vendor/filp/whoops/LICENSE.md new file mode 100644 index 0000000..17707b3 --- /dev/null +++ b/vendor/filp/whoops/LICENSE.md @@ -0,0 +1,19 @@ +#The MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/filp/whoops/README.md b/vendor/filp/whoops/README.md new file mode 100644 index 0000000..949b728 --- /dev/null +++ b/vendor/filp/whoops/README.md @@ -0,0 +1,225 @@ +# whoops +php errors for cool kids + +[![Build Status](https://travis-ci.org/filp/whoops.png?branch=master)](https://travis-ci.org/filp/whoops) + +----- + +![Whoops!](http://i.imgur.com/xiZ1tUU.png) + +**whoops** is an error handler base/framework for PHP. Out-of-the-box, it provides a pretty +error interface that helps you debug your web projects, but at heart it's a simple yet +powerful stacked error handling system. + +## (current) Features + +- Flexible, stack-based error handling +- Stand-alone library with (currently) no required dependencies +- Simple API for dealing with exceptions, trace frames & their data +- Includes a pretty rad error page for your webapp projects +- **NEW** Includes the ability to open referenced files directly in your editor and IDE +- Includes a Silex Service Provider for painless integration with [Silex](http://silex.sensiolabs.org/) +- Includes a Phalcon Service Provider for painless integration with [Phalcon](http://phalconphp.com/) +- Includes a Module for equally painless integration with [Zend Framework 2](http://framework.zend.com/) +- Easy to extend and integrate with existing libraries +- Clean, well-structured & tested code-base (well, except `pretty-template.php`, for now...) + +## Installing + +- Install [Composer](http://getcomposer.org) and place the executable somewhere in your `$PATH` (for the rest of this README, +I'll reference it as just `composer`) + +- Add `filp/whoops` to your project's `composer.json: + +```json +{ + "require": { + "filp/whoops": "1.*" + } +} +``` + +- Install/update your dependencies + +```bash +$ cd my_project +$ composer install +``` + +And you're good to go! Have a look at the **example files** in `examples/` to get a feel for how things work. +I promise it's really simple! + +## API Documentation + +Initial API documentation of the whoops library is available here: +https://github.com/filp/whoops/wiki/API-Documentation + +## Usage + +### Integrating with Silex + +**whoops** comes packaged with a Silex Service Provider: `Whoops\Provider\Silex\WhoopsServiceProvider`. Using it +in your existing Silex project is easy: + +```php + +require 'vendor/autoload.php'; + +use Silex\Application; + +// ... some awesome code here ... + +if($app['debug']) { + $app->register(new Whoops\Provider\Silex\WhoopsServiceProvider); +} + +// ... + +$app->run(); +``` + +And that's about it. By default, you'll get the pretty error pages if something goes awry in your development +environment, but you also have full access to the **whoops** library, obviously. For example, adding a new handler +into your app is as simple as extending `whoops`: + +```php +$app['whoops'] = $app->extend('whoops', function($whoops) { + $whoops->pushHandler(new DeleteWholeProjectHandler); + return $whoops; +}); +``` +### Integrating with Phalcon + +**whoops** comes packaged with a Phalcon Service Provider: `Whoops\Provider\Phalcon\WhoopsServiceProvider`. Using it +in your existing Phalcon project is easy. The provider uses the default Phalcon DI unless you pass a DI instance into the constructor. + +```php +new Whoops\Provider\Phalcon\WhoopsServiceProvider; + +// --- or --- + +$di = Phalcon\DI\FactoryDefault; +new Whoops\Provider\Phalcon\WhoopsServiceProvider($di); +``` + +### Integrating with Laravel 4/Illuminate + +If you're using Laravel 4, as of [this commit to laravel/framework](https://github.com/laravel/framework/commit/64f3a79aae254b71550a8097880f0b0e09062d24), you're already using Whoops! Yay! + +### Integrating with Laravel 3 + +User [@hdias](https://github.com/hdias) contributed a simple guide/example to help you integrate **whoops** with Laravel 3's IoC container, available at: + +https://gist.github.com/hdias/5169713#file-start-php + +### Integrating with Zend Framework 2 + +User [@zsilbi](https://github.com/zsilbi) contributed a provider for ZF2 integration, +available in the following location: + +https://github.com/filp/whoops/tree/master/src/Whoops/Provider/Zend + +**Instructions:** + +- Add Whoops as a module to you app (/vendor/Whoops) +- Whoops must be the first module: + +```php +'modules' => array( + 'Whoops', + 'Application' + ) +``` + +- Move Module.php from /Whoops/Provider/Zend/Module.php to /Whoops/Module.php +- Use optional configurations in your controller config: + +```php +return array( + 'view_manager' => array( + 'display_not_found_reason' => true, + 'display_exceptions' => true, + 'json_exceptions' => array( + 'display' => true, + 'ajax_only' => true, + 'show_trace' => true + ) + ), +); +``` + +- NOTE: ob_clean(); is used to remove previous output, so you may use ob_start(); at the beginning of your app (index.php) + +### Opening referenced files with your favorite editor or IDE + +When using the pretty error page feature, whoops comes with the ability to +open referenced files directly in your IDE or editor. + +```php +setEditor('sublime'); +``` + +The following editors are currently supported by default. + +- `sublime` - Sublime Text 2 +- `emacs` - Emacs +- `textmate` - Textmate +- `macvim` - MacVim +- `xdebug` - xdebug (uses [xdebug.file_link_format](http://xdebug.org/docs/all_settings#file_link_format)) + +Adding your own editor is simple: + +```php + +$handler->setEditor(function($file, $line) { + return "whatever://open?file=$file&line=$line"; +}); + +``` + +### Available Handlers + +**whoops** currently ships with the following built-in handlers, available in the `Whoops\Handler` namespace: + +- [`PrettyPageHandler`](https://github.com/filp/whoops/blob/master/src/Whoops/Handler/PrettyPageHandler.php) - Shows a pretty error page when something goes pants-up +- [`CallbackHandler`](https://github.com/filp/whoops/blob/master/src/Whoops/Handler/CallbackHandler.php) - Wraps a closure or other callable as a handler. You do not need to use this handler explicitly, **whoops** will automatically wrap any closure or callable you pass to `Whoops\Run::pushHandler` +- [`JsonResponseHandler`](https://github.com/filp/whoops/blob/master/src/Whoops/Handler/JsonResponseHandler.php) - Captures exceptions and returns information on them as a JSON string. Can be used to, for example, play nice with AJAX requests. + +## Contributing + +If you want to give me some feedback or make a suggestion, send me a message through +twitter: [@imfilp](https://twitter.com/imfilp) + +If you want to get your hands dirty, great! Here's a couple of steps/guidelines: + +- Fork/clone this repo, and update dev dependencies using Composer + +```bash +$ git clone git@github.com:filp/whoops.git +$ cd whoops +$ composer install --dev +``` + +- Create a new branch for your feature or fix + +```bash +$ git checkout -b feature/flames-on-the-side +``` + +- Add your changes & tests for those changes (in `tests/`). +- Remember to stick to the existing code style as best as possible. When in doubt, follow `PSR-2`. +- Send me a pull request! + +If you don't want to go through all this, but still found something wrong or missing, please +let me know, and/or **open a new issue report** so that I or others may take care of it. + +## Authors + +This library was primarily developed by [Filipe Dobreira](https://github.com/filp). + +A lot of awesome fixes and enhancements were also sent in by contributors, which you can find **[in this page right here](https://github.com/filp/whoops/contributors)**. diff --git a/vendor/filp/whoops/composer.json b/vendor/filp/whoops/composer.json new file mode 100644 index 0000000..3bed141 --- /dev/null +++ b/vendor/filp/whoops/composer.json @@ -0,0 +1,27 @@ +{ + "name": "filp/whoops", + "license": "MIT", + "description": "php error handling for cool kids", + "version": "1.0.7", + "keywords": ["library", "error", "handling", "exception", "silex-provider", "whoops", "zf2"], + "homepage": "https://github.com/filp/whoops", + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "mockery/mockery": "dev-master", + "silex/silex": "1.0.*@dev" + }, + "autoload": { + "psr-0": { + "Whoops": "src/" + } + } +} diff --git a/vendor/filp/whoops/composer.lock b/vendor/filp/whoops/composer.lock new file mode 100644 index 0000000..af208ce --- /dev/null +++ b/vendor/filp/whoops/composer.lock @@ -0,0 +1,452 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" + ], + "hash": "6e72d0b30a42469a5cef281fc82e77eb", + "packages": [ + + ], + "packages-dev": [ + { + "name": "mockery/mockery", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/padraic/mockery.git", + "reference": "70739803f85065cc2ec00fae662bdcaaa4df487e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/padraic/mockery/zipball/70739803f85065cc2ec00fae662bdcaaa4df487e", + "reference": "70739803f85065cc2ec00fae662bdcaaa4df487e", + "shasum": "" + }, + "require": { + "lib-pcre": ">=7.0", + "php": ">=5.3.2" + }, + "require-dev": { + "hamcrest/hamcrest": "1.1.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succint API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.", + "homepage": "http://github.com/padraic/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "time": "2013-05-29 09:13:17" + }, + { + "name": "pimple/pimple", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/fabpot/Pimple.git", + "reference": "v1.0.2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fabpot/Pimple/zipball/v1.0.2", + "reference": "v1.0.2", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple is a simple Dependency Injection Container for PHP 5.3", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ], + "time": "2013-03-08 08:21:40" + }, + { + "name": "psr/log", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log", + "reference": "1.0.0" + }, + "dist": { + "type": "zip", + "url": "https://github.com/php-fig/log/archive/1.0.0.zip", + "reference": "1.0.0", + "shasum": "" + }, + "type": "library", + "autoload": { + "psr-0": { + "Psr\\Log\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2012-12-21 11:40:51" + }, + { + "name": "symfony/debug", + "version": "v2.3.0", + "target-dir": "Symfony/Component/Debug", + "source": { + "type": "git", + "url": "https://github.com/symfony/Debug.git", + "reference": "v2.3.0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Debug/zipball/v2.3.0", + "reference": "v2.3.0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/http-foundation": ">=2.1,<3.0", + "symfony/http-kernel": ">=2.1,<3.0" + }, + "suggest": { + "symfony/class-loader": "", + "symfony/http-foundation": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Debug\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "http://symfony.com", + "time": "2013-06-02 11:58:44" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.3.0", + "target-dir": "Symfony/Component/EventDispatcher", + "source": { + "type": "git", + "url": "https://github.com/symfony/EventDispatcher.git", + "reference": "v2.3.0-RC1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/v2.3.0-RC1", + "reference": "v2.3.0-RC1", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/dependency-injection": ">=2.0,<3.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "http://symfony.com", + "time": "2013-05-13 14:36:40" + }, + { + "name": "symfony/http-foundation", + "version": "v2.3.0", + "target-dir": "Symfony/Component/HttpFoundation", + "source": { + "type": "git", + "url": "https://github.com/symfony/HttpFoundation.git", + "reference": "v2.3.0-RC1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/v2.3.0-RC1", + "reference": "v2.3.0-RC1", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "classmap": [ + "Symfony/Component/HttpFoundation/Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "http://symfony.com", + "time": "2013-05-10 06:00:03" + }, + { + "name": "symfony/http-kernel", + "version": "v2.3.0", + "target-dir": "Symfony/Component/HttpKernel", + "source": { + "type": "git", + "url": "https://github.com/symfony/HttpKernel.git", + "reference": "v2.3.0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/HttpKernel/zipball/v2.3.0", + "reference": "v2.3.0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "psr/log": ">=1.0,<2.0", + "symfony/debug": ">=2.3,<3.0", + "symfony/event-dispatcher": ">=2.1,<3.0", + "symfony/http-foundation": ">=2.2,<3.0" + }, + "require-dev": { + "symfony/browser-kit": "2.2.*", + "symfony/class-loader": ">=2.1,<3.0", + "symfony/config": ">=2.0,<3.0", + "symfony/console": "2.2.*", + "symfony/dependency-injection": ">=2.0,<3.0", + "symfony/finder": ">=2.0,<3.0", + "symfony/process": ">=2.0,<3.0", + "symfony/routing": ">=2.2,<3.0", + "symfony/stopwatch": ">=2.2,<3.0" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\HttpKernel\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony HttpKernel Component", + "homepage": "http://symfony.com", + "time": "2013-06-03 14:13:35" + }, + { + "name": "symfony/routing", + "version": "v2.3.0", + "target-dir": "Symfony/Component/Routing", + "source": { + "type": "git", + "url": "https://github.com/symfony/Routing.git", + "reference": "v2.3.0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Routing/zipball/v2.3.0", + "reference": "v2.3.0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "doctrine/common": ">=2.2,<3.0", + "psr/log": ">=1.0,<2.0", + "symfony/config": ">=2.2,<3.0", + "symfony/yaml": ">=2.0,<3.0" + }, + "suggest": { + "doctrine/common": "", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Routing\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "http://symfony.com", + "time": "2013-05-20 08:57:26" + } + ], + "aliases": [ + + ], + "minimum-stability": "stable", + "stability-flags": { + "mockery/mockery": 20, + "silex/silex": 20 + }, + "platform": { + "php": ">=5.3.0" + }, + "platform-dev": [ + + ] +} diff --git a/vendor/filp/whoops/examples/example-ajax-only.php b/vendor/filp/whoops/examples/example-ajax-only.php new file mode 100644 index 0000000..3efdb45 --- /dev/null +++ b/vendor/filp/whoops/examples/example-ajax-only.php @@ -0,0 +1,46 @@ + + * + * Run this example file with the PHP 5.4 web server with: + * + * $ cd project_dir + * $ php -S localhost:8080 + * + * and access localhost:8080/example/example-ajax-only.php through your browser + * + * Or just run it through apache/nginx/what-have-yous as usual. + */ + +namespace Whoops\Example; +use Whoops\Run; +use Whoops\Handler\PrettyPageHandler; +use Whoops\Handler\JsonResponseHandler; +use RuntimeException; + +require __DIR__ . '/../vendor/autoload.php'; + +$run = new Run; + +// We want the error page to be shown by default, if this is a +// regular request, so that's the first thing to go into the stack: +$run->pushHandler(new PrettyPageHandler); + +// Now, we want a second handler that will run before the error page, +// and immediately return an error message in JSON format, if something +// goes awry. +$jsonHandler = new JsonResponseHandler; + +// Make sure it only triggers for AJAX requests: +$jsonHandler->onlyForAjaxRequests(true); + +// You can also tell JsonResponseHandler to give you a full stack trace: +// $jsonHandler->addTraceToOutput(true); + +// And push it into the stack: +$run->pushHandler($jsonHandler); + +// That's it! Register Whoops and throw a dummy exception: +$run->register(); +throw new RuntimeException("Oh fudge napkins!"); diff --git a/vendor/filp/whoops/examples/example-silex.php b/vendor/filp/whoops/examples/example-silex.php new file mode 100644 index 0000000..86530a4 --- /dev/null +++ b/vendor/filp/whoops/examples/example-silex.php @@ -0,0 +1,36 @@ + + * + * NOTE: Requires silex/silex, can be installed with composer + * within this project using the --dev flag: + * + * $ composer install --dev + * + * Run this example file with the PHP 5.4 web server with: + * + * $ cd project_dir + * $ php -S localhost:8080 + * + * and access localhost:8080/examples/example-silex.php through your browser + * + * Or just run it through apache/nginx/what-have-yous as usual. + */ +require __DIR__ . '/../vendor/autoload.php'; + +use Whoops\Provider\Silex\WhoopsServiceProvider; +use Silex\Application; + +$app = new Application; +$app['debug'] = true; + +if($app['debug']) { + $app->register(new WhoopsServiceProvider); +} + +$app->get('/', function() use($app) { + throw new RuntimeException("Oh no!"); +}); + +$app->run(); diff --git a/vendor/filp/whoops/examples/example.php b/vendor/filp/whoops/examples/example.php new file mode 100644 index 0000000..a1a5710 --- /dev/null +++ b/vendor/filp/whoops/examples/example.php @@ -0,0 +1,63 @@ + + * + * Run this example file with the PHP 5.4 web server with: + * + * $ cd project_dir + * $ php -S localhost:8080 + * + * and access localhost:8080/example/example.php through your browser + * + * Or just run it through apache/nginx/what-have-yous as usual. + */ + +namespace Whoops\Example; +use Whoops\Run; +use Whoops\Handler\PrettyPageHandler; +use Exception as BaseException; + +require __DIR__ . '/../vendor/autoload.php'; + +class Exception extends BaseException {} + +$run = new Run; +$handler = new PrettyPageHandler; + +// Add a custom table to the layout: +$handler->addDataTable('Ice-cream I like', array( + 'Chocolate' => 'yes', + 'Coffee & chocolate' => 'a lot', + 'Strawberry & chocolate' => 'it\'s alright', + 'Vanilla' => 'ew' +)); + +$run->pushHandler($handler); + +// Example: tag all frames inside a function with their function name +$run->pushHandler(function($exception, $inspector, $run) { + + $inspector->getFrames()->map(function($frame) { + + if($function = $frame->getFunction()) { + $frame->addComment("This frame is within function '$function'", 'cpt-obvious'); + } + + return $frame; + }); + +}); + +$run->register(); + +function fooBar() { + throw new Exception("Something broke!"); +} + +function bar() +{ + fooBar(); +} + +bar(); diff --git a/vendor/filp/whoops/phpunit.xml.dist b/vendor/filp/whoops/phpunit.xml.dist new file mode 100644 index 0000000..a5abf9a --- /dev/null +++ b/vendor/filp/whoops/phpunit.xml.dist @@ -0,0 +1,15 @@ + + + + + + tests/Whoops/ + + + + + + src/Whoops/ + + + diff --git a/vendor/filp/whoops/src/Whoops/Exception/ErrorException.php b/vendor/filp/whoops/src/Whoops/Exception/ErrorException.php new file mode 100644 index 0000000..8a770af --- /dev/null +++ b/vendor/filp/whoops/src/Whoops/Exception/ErrorException.php @@ -0,0 +1,14 @@ + + */ + +namespace Whoops\Exception; +use ErrorException as BaseErrorException; + +/** + * Wraps ErrorException; mostly used for typing (at least now) + * to easily cleanup the stack trace of redundant info. + */ +class ErrorException extends BaseErrorException {} diff --git a/vendor/filp/whoops/src/Whoops/Exception/Frame.php b/vendor/filp/whoops/src/Whoops/Exception/Frame.php new file mode 100644 index 0000000..a97268e --- /dev/null +++ b/vendor/filp/whoops/src/Whoops/Exception/Frame.php @@ -0,0 +1,228 @@ + + */ + +namespace Whoops\Exception; +use InvalidArgumentException; +use Serializable; + +class Frame implements Serializable +{ + /** + * @var array + */ + protected $frame; + + /** + * @var string + */ + protected $fileContentsCache; + + /** + * @var array[] + */ + protected $comments = array(); + + /** + * @param array[] + */ + public function __construct(array $frame) + { + $this->frame = $frame; + } + + /** + * @param bool $shortened + * @return string|null + */ + public function getFile($shortened = false) + { + $file = !empty($this->frame['file']) ? $this->frame['file'] : null; + if ($shortened && is_string($file)) { + // Replace the part of the path that all frames have in common, and add 'soft hyphens' for smoother line-breaks. + $dirname = dirname(dirname(dirname(dirname(dirname(dirname(__DIR__)))))); + $file = str_replace($dirname, "…", $file); + $file = str_replace("/", "/­", $file); + } + return $file; + } + + /** + * @return int|null + */ + public function getLine() + { + return isset($this->frame['line']) ? $this->frame['line'] : null; + } + + /** + * @return string|null + */ + public function getClass() + { + return isset($this->frame['class']) ? $this->frame['class'] : null; + } + + /** + * @return string|null + */ + public function getFunction() + { + return isset($this->frame['function']) ? $this->frame['function'] : null; + } + + /** + * @return array + */ + public function getArgs() + { + return isset($this->frame['args']) ? (array) $this->frame['args'] : array(); + } + + /** + * Returns the full contents of the file for this frame, + * if it's known. + * @return string|null + */ + public function getFileContents() + { + if($this->fileContentsCache === null && $filePath = $this->getFile()) { + $this->fileContentsCache = file_get_contents($filePath); + } + + return $this->fileContentsCache; + } + + /** + * Adds a comment to this frame, that can be received and + * used by other handlers. For example, the PrettyPage handler + * can attach these comments under the code for each frame. + * + * An interesting use for this would be, for example, code analysis + * & annotations. + * + * @param string $comment + * @param string $context Optional string identifying the origin of the comment + */ + public function addComment($comment, $context = 'global') + { + $this->comments[] = array( + 'comment' => $comment, + 'context' => $context + ); + } + + /** + * Returns all comments for this frame. Optionally allows + * a filter to only retrieve comments from a specific + * context. + * + * @param string $filter + * @return array[] + */ + public function getComments($filter = null) + { + $comments = $this->comments; + + if($filter !== null) { + $comments = array_filter($comments, function($c) use($filter) { + return $c['context'] == $filter; + }); + } + + return $comments; + } + + /** + * Returns the array containing the raw frame data from which + * this Frame object was built + * + * @return array + */ + public function getRawFrame() + { + return $this->frame; + } + + /** + * Returns the contents of the file for this frame as an + * array of lines, and optionally as a clamped range of lines. + * + * NOTE: lines are 0-indexed + * + * @example + * Get all lines for this file + * $frame->getFileLines(); // => array( 0 => ' '...', ...) + * @example + * Get one line for this file, starting at line 10 (zero-indexed, remember!) + * $frame->getFileLines(9, 1); // array( 10 => '...', 11 => '...') + * + * @param int $start + * @param int $length + * @return string[]|null + */ + public function getFileLines($start = 0, $length = null) + { + if(null !== ($contents = $this->getFileContents())) { + $lines = explode("\n", $contents); + + // Get a subset of lines from $start to $end + if($length !== null) + { + $start = (int) $start; + $length = (int) $length; + if ($start < 0) { + $start = 0; + } + + if($length <= 0) { + throw new InvalidArgumentException( + "\$length($length) cannot be lower or equal to 0" + ); + } + + $lines = array_slice($lines, $start, $length, true); + } + + return $lines; + } + } + + /** + * Implements the Serializable interface, with special + * steps to also save the existing comments. + * + * @see Serializable::serialize + * @return string + */ + public function serialize() + { + $frame = $this->frame; + if(!empty($this->comments)) { + $frame['_comments'] = $this->comments; + } + + return serialize($frame); + } + + /** + * Unserializes the frame data, while also preserving + * any existing comment data. + * + * @see Serializable::unserialize + * @param string $serializedFrame + */ + public function unserialize($serializedFrame) + { + $frame = unserialize($serializedFrame); + + if(!empty($frame['_comments'])) { + $this->comments = $frame['_comments']; + unset($frame['_comments']); + } + + $this->frame = $frame; + } +} diff --git a/vendor/filp/whoops/src/Whoops/Exception/FrameCollection.php b/vendor/filp/whoops/src/Whoops/Exception/FrameCollection.php new file mode 100644 index 0000000..c5fb7bc --- /dev/null +++ b/vendor/filp/whoops/src/Whoops/Exception/FrameCollection.php @@ -0,0 +1,122 @@ + + */ + +namespace Whoops\Exception; +use Whoops\Exception\Frame; +use UnexpectedValueException; +use IteratorAggregate; +use ArrayIterator; +use Serializable; +use Countable; + +/** + * Exposes a fluent interface for dealing with an ordered list + * of stack-trace frames. + */ +class FrameCollection implements IteratorAggregate, Serializable, Countable +{ + /** + * @var array[] + */ + private $frames; + + /** + * @param array $frames + */ + public function __construct(array $frames) + { + $this->frames = array_map(function($frame) { + return new Frame($frame); + }, $frames); + } + + /** + * Filters frames using a callable, returns the same FrameCollection + * + * @param callable $callable + * @return Whoops\Exception\FrameCollection + */ + public function filter($callable) + { + $this->frames = array_filter($this->frames, $callable); + return $this; + } + + /** + * Map the collection of frames + * + * @param callable $callable + * @return Whoops\Exception\FrameCollection + */ + public function map($callable) + { + // Contain the map within a higher-order callable + // that enforces type-correctness for the $callable + $this->frames = array_map(function($frame) use($callable) { + $frame = call_user_func($callable, $frame); + + if(!$frame instanceof Frame) { + throw new UnexpectedValueException( + "Callable to " . __METHOD__ . " must return a Frame object" + ); + } + + return $frame; + }, $this->frames); + + return $this; + } + + /** + * Returns an array with all frames, does not affect + * the internal array. + * + * @todo If this gets any more complex than this, + * have getIterator use this method. + * @see Whoops\Exception\FrameCollection::getIterator + * @return array + */ + public function getArray() + { + return $this->frames; + } + + /** + * @see IteratorAggregate::getIterator + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->frames); + } + + /** + * @see Countable::count + * @return int + */ + public function count() + { + return count($this->frames); + } + + /** + * @see Serializable::serialize + * @return string + */ + public function serialize() + { + return serialize($this->frames); + } + + /** + * @see Serializable::unserialize + * @param string $serializedFrames + */ + public function unserialize($serializedFrames) + { + $this->frames = unserialize($serializedFrames); + } +} diff --git a/vendor/filp/whoops/src/Whoops/Exception/Inspector.php b/vendor/filp/whoops/src/Whoops/Exception/Inspector.php new file mode 100644 index 0000000..7125c3f --- /dev/null +++ b/vendor/filp/whoops/src/Whoops/Exception/Inspector.php @@ -0,0 +1,115 @@ + + */ + +namespace Whoops\Exception; +use Whoops\Exception\FrameCollection; +use Whoops\Exception\ErrorException; +use Exception; + +class Inspector +{ + /** + * @var Exception + */ + private $exception; + + /** + * @var Whoops\Exception\FrameCollection + */ + private $frames; + + /** + * @param Exception $exception The exception to inspect + */ + public function __construct(Exception $exception) + { + $this->exception = $exception; + } + + /** + * @return Exception + */ + public function getException() + { + return $this->exception; + } + + /** + * @return string + */ + public function getExceptionName() + { + return get_class($this->exception); + } + + /** + * @return string + */ + public function getExceptionMessage() + { + return $this->exception->getMessage(); + } + + /** + * Returns an iterator for the inspected exception's + * frames. + * @return Whoops\Exception\FrameCollection + */ + public function getFrames() + { + if($this->frames === null) { + $frames = $this->exception->getTrace(); + + // If we're handling an ErrorException thrown by Whoops, + // get rid of the last frame, which matches the handleError method, + // and do not add the current exception to trace. We ensure that + // the next frame does have a filename / linenumber, though. + if($this->exception instanceof ErrorException && empty($frames[1]['line'])) { + $frames = array($this->getFrameFromError($this->exception)); + } else { + $firstFrame = $this->getFrameFromException($this->exception); + array_unshift($frames, $firstFrame); + } + $this->frames = new FrameCollection($frames); + } + + return $this->frames; + } + + /** + * Given an exception, generates an array in the format + * generated by Exception::getTrace() + * @param Exception $exception + * @return array + */ + protected function getFrameFromException(Exception $exception) + { + return array( + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'class' => get_class($exception), + 'args' => array( + $exception->getMessage() + ) + ); + } + + /** + * Given an error, generates an array in the format + * generated by ErrorException + * @param ErrorException $exception + * @return array + */ + protected function getFrameFromError(ErrorException $exception) + { + return array( + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'class' => null, + 'args' => array() + ); + } +} diff --git a/vendor/filp/whoops/src/Whoops/Handler/CallbackHandler.php b/vendor/filp/whoops/src/Whoops/Handler/CallbackHandler.php new file mode 100644 index 0000000..9350d29 --- /dev/null +++ b/vendor/filp/whoops/src/Whoops/Handler/CallbackHandler.php @@ -0,0 +1,48 @@ + + */ + +namespace Whoops\Handler; +use Whoops\Handler\Handler; +use InvalidArgumentException; + +/** + * Wrapper for Closures passed as handlers. Can be used + * directly, or will be instantiated automagically by Whoops\Run + * if passed to Run::pushHandler + */ +class CallbackHandler extends Handler +{ + /** + * @var callable + */ + protected $callable; + + /** + * @param callable $callable + */ + public function __construct($callable) + { + if(!is_callable($callable)) { + throw new InvalidArgumentException( + 'Argument to ' . __METHOD__ . ' must be valid callable' + ); + } + + $this->callable = $callable; + } + + /** + * @return int|null + */ + public function handle() + { + $exception = $this->getException(); + $inspector = $this->getInspector(); + $run = $this->getRun(); + + return call_user_func($this->callable, $exception, $inspector, $run); + } +} diff --git a/vendor/filp/whoops/src/Whoops/Handler/Handler.php b/vendor/filp/whoops/src/Whoops/Handler/Handler.php new file mode 100644 index 0000000..be2e2c7 --- /dev/null +++ b/vendor/filp/whoops/src/Whoops/Handler/Handler.php @@ -0,0 +1,89 @@ + + */ + +namespace Whoops\Handler; +use Whoops\Handler\HandlerInterface; +use Whoops\Exception\Inspector; +use Whoops\Run; +use Exception; + +/** + * Abstract implementation of a Handler. + */ +abstract class Handler implements HandlerInterface +{ + /** + * Return constants that can be returned from Handler::handle + * to message the handler walker. + */ + const DONE = 0x10; // returning this is optional, only exists for + // semantic purposes + const LAST_HANDLER = 0x20; + const QUIT = 0x30; + + /** + * @var Whoops\Run + */ + private $run; + + /** + * @var Whoops\Exception\Inspector $inspector + */ + private $inspector; + + /** + * @var Exception $exception + */ + private $exception; + + /** + * @param Whoops\Run $run + */ + public function setRun(Run $run) + { + $this->run = $run; + } + + /** + * @return Whoops\Run + */ + protected function getRun() + { + return $this->run; + } + + /** + * @param Whoops\Exception\Inspector $inspector + */ + public function setInspector(Inspector $inspector) + { + $this->inspector = $inspector; + } + + /** + * @return Whoops\Run + */ + protected function getInspector() + { + return $this->inspector; + } + + /** + * @param Exception $exception + */ + public function setException(Exception $exception) + { + $this->exception = $exception; + } + + /** + * @return Exception + */ + protected function getException() + { + return $this->exception; + } +} diff --git a/vendor/filp/whoops/src/Whoops/Handler/HandlerInterface.php b/vendor/filp/whoops/src/Whoops/Handler/HandlerInterface.php new file mode 100644 index 0000000..80169f3 --- /dev/null +++ b/vendor/filp/whoops/src/Whoops/Handler/HandlerInterface.php @@ -0,0 +1,33 @@ + + */ + +namespace Whoops\Handler; +use Whoops\Exception\Inspector; +use Whoops\Run; +use Exception; + +interface HandlerInterface +{ + /** + * @return int|null A handler may return nothing, or a Handler::HANDLE_* constant + */ + public function handle(); + + /** + * @param Whoops\Run $run + */ + public function setRun(Run $run); + + /** + * @param Exception $exception + */ + public function setException(Exception $exception); + + /** + * @param Whoops\Exception\Inspector $run + */ + public function setInspector(Inspector $inspector); +} diff --git a/vendor/filp/whoops/src/Whoops/Handler/JsonResponseHandler.php b/vendor/filp/whoops/src/Whoops/Handler/JsonResponseHandler.php new file mode 100644 index 0000000..f34ea6b --- /dev/null +++ b/vendor/filp/whoops/src/Whoops/Handler/JsonResponseHandler.php @@ -0,0 +1,106 @@ + + */ + +namespace Whoops\Handler; +use Whoops\Handler\Handler; + +/** + * Catches an exception and converts it to a JSON + * response. Additionally can also return exception + * frames for consumption by an API. + */ +class JsonResponseHandler extends Handler +{ + /** + * @var bool + */ + private $returnFrames = false; + + /** + * @var bool + */ + private $onlyForAjaxRequests = false; + + /** + * @param bool|null $returnFrames + * @return null|bool + */ + public function addTraceToOutput($returnFrames = null) + { + if(func_num_args() == 0) { + return $this->returnFrames; + } + + $this->returnFrames = (bool) $returnFrames; + } + + /** + * @param bool|null $onlyForAjaxRequests + * @return null|bool + */ + public function onlyForAjaxRequests($onlyForAjaxRequests = null) + { + if(func_num_args() == 0) { + return $this->onlyForAjaxRequests; + } + + $this->onlyForAjaxRequests = (bool) $onlyForAjaxRequests; + } + + /** + * Check, if possible, that this execution was triggered by an AJAX request. + * @param bool + */ + private function isAjaxRequest() + { + return ( + !empty($_SERVER['HTTP_X_REQUESTED_WITH']) + && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') + ; + } + + /** + * @return int + */ + public function handle() + { + if($this->onlyForAjaxRequests() && !$this->isAjaxRequest()) { + return Handler::DONE; + } + + $exception = $this->getException(); + + $response = array( + 'error' => array( + 'type' => get_class($exception), + 'message' => $exception->getMessage(), + 'file' => $exception->getFile(), + 'line' => $exception->getLine() + ) + ); + + if($this->addTraceToOutput()) { + $inspector = $this->getInspector(); + $frames = $inspector->getFrames(); + $frameData = array(); + + foreach($frames as $frame) { + $frameData[] = array( + 'file' => $frame->getFile(), + 'line' => $frame->getLine(), + 'function' => $frame->getFunction(), + 'class' => $frame->getClass(), + 'args' => $frame->getArgs() + ); + } + + $response['error']['trace'] = $frameData; + } + + echo json_encode($response); + return Handler::QUIT; + } +} diff --git a/vendor/filp/whoops/src/Whoops/Handler/PrettyPageHandler.php b/vendor/filp/whoops/src/Whoops/Handler/PrettyPageHandler.php new file mode 100644 index 0000000..77dc587 --- /dev/null +++ b/vendor/filp/whoops/src/Whoops/Handler/PrettyPageHandler.php @@ -0,0 +1,328 @@ + + */ + +namespace Whoops\Handler; +use Whoops\Handler\Handler; +use InvalidArgumentException; + +class PrettyPageHandler extends Handler +{ + /** + * @var string + */ + private $resourcesPath; + + /** + * @var array[] + */ + private $extraTables = array(); + + /** + * @var string + */ + private $pageTitle = 'Whoops! There was an error.'; + + /** + * A string identifier for a known IDE/text editor, or a closure + * that resolves a string that can be used to open a given file + * in an editor. If the string contains the special substrings + * %file or %line, they will be replaced with the correct data. + * + * @example + * "txmt://open?url=%file&line=%line" + * @var mixed $editor + */ + protected $editor; + + /** + * A list of known editor strings + * @var array + */ + protected $editors = array( + 'sublime' => 'subl://open?url=file://%file&line=%line', + 'textmate' => 'txmt://open?url=file://%file&line=%line', + 'emacs' => 'emacs://open?url=file://%file&line=%line', + 'macvim' => 'mvim://open/?url=file://%file&line=%line' + ); + + /** + * Constructor. + */ + public function __construct() + { + if (extension_loaded('xdebug')) { + // Register editor using xdebug's file_link_format option. + $this->editors['xdebug'] = function($file, $line) { + return str_replace(array('%f', '%l'), array($file, $line), ini_get('xdebug.file_link_format')); + }; + } + } + + /** + * @return int|null + */ + public function handle() + { + // Check conditions for outputting HTML: + // @todo: make this more robust + if(php_sapi_name() === 'cli' && !isset($_ENV['whoops-test'])) { + return Handler::DONE; + } + + // Get the 'pretty-template.php' template file + // @todo: this can be made more dynamic &&|| cleaned-up + if(!($resources = $this->getResourcesPath())) { + $resources = __DIR__ . '/../Resources'; + } + + $templateFile = "$resources/pretty-template.php"; + + // @todo: Make this more reliable, + // possibly by adding methods to append CSS & JS to the page + $cssFile = "$resources/pretty-page.css"; + + // Prepare the $v global variable that will pass relevant + // information to the template + $inspector = $this->getInspector(); + $frames = $inspector->getFrames(); + + $v = (object) array( + 'title' => $this->getPageTitle(), + 'name' => explode('\\', $inspector->getExceptionName()), + 'message' => $inspector->getException()->getMessage(), + 'frames' => $frames, + 'hasFrames' => !!count($frames), + 'handler' => $this, + 'handlers' => $this->getRun()->getHandlers(), + 'pageStyle' => file_get_contents($cssFile), + + 'tables' => array( + 'Server/Request Data' => $_SERVER, + 'GET Data' => $_GET, + 'POST Data' => $_POST, + 'Files' => $_FILES, + 'Cookies' => $_COOKIE, + 'Session' => isset($_SESSION) ? $_SESSION: array(), + 'Environment Variables' => $_ENV + ) + ); + + $extraTables = array_map(function($table) { + return $table instanceof \Closure ? $table() : $table; + }, $this->getDataTables()); + + // Add extra entries list of data tables: + $v->tables = array_merge($extraTables, $v->tables); + + call_user_func(function() use($templateFile, $v) { + // $e -> cleanup output, optionally preserving URIs as anchors: + $e = function($_, $allowLinks = false) { + $escaped = htmlspecialchars($_, ENT_QUOTES, 'UTF-8'); + + // convert URIs to clickable anchor elements: + if($allowLinks) { + $escaped = preg_replace( + '@([A-z]+?://([-\w\.]+[-\w])+(:\d+)?(/([\w/_\.#-]*(\?\S+)?[^\.\s])?)?)@', + "$1", $escaped + ); + } + + return $escaped; + }; + + // $slug -> sluggify string (i.e: Hello world! -> hello-world) + $slug = function($_) { + $_ = str_replace(" ", "-", $_); + $_ = preg_replace('/[^\w\d\-\_]/i', '', $_); + return strtolower($_); + }; + + require $templateFile; + }); + + + return Handler::QUIT; + } + + /** + * Adds an entry to the list of tables displayed in the template. + * The expected data is a simple associative array. Any nested arrays + * will be flattened with print_r + * @param string $label + * @param array $data + */ + public function addDataTable($label, array $data) + { + $this->extraTables[$label] = $data; + } + + /** + * Lazily adds an entry to the list of tables displayed in the table. + * The supplied callback argument will be called when the error is rendered, + * it should produce a simple associative array. Any nested arrays will + * be flattened with print_r. + * @param string $label + * @param callable $callback Callable returning an associative array + */ + public function addDataTableCallback($label, /* callable */ $callback) + { + if (!is_callable($callback)) { + throw new InvalidArgumentException('Expecting callback argument to be callable'); + } + + $this->extraTables[$label] = function() use ($callback) { + try { + $result = call_user_func($callback); + + // Only return the result if it can be iterated over by foreach(). + return is_array($result) || $result instanceof \Traversable ? $result : array(); + } catch (\Exception $e) { + // Don't allow failiure to break the rendering of the original exception. + return array(); + } + }; + } + + /** + * Returns all the extra data tables registered with this handler. + * Optionally accepts a 'label' parameter, to only return the data + * table under that label. + * @param string|null $label + * @return array[] + */ + public function getDataTables($label = null) + { + if($label !== null) { + return isset($this->extraTables[$label]) ? + $this->extraTables[$label] : array(); + } + + return $this->extraTables; + } + + /** + * Adds an editor resolver, identified by a string + * name, and that may be a string path, or a callable + * resolver. If the callable returns a string, it will + * be set as the file reference's href attribute. + * + * @example + * $run->addEditor('macvim', "mvim://open?url=file://%file&line=%line") + * @example + * $run->addEditor('remove-it', function($file, $line) { + * unlink($file); + * return "http://stackoverflow.com"; + * }); + * @param string $identifier + * @param string $resolver + */ + public function addEditor($identifier, $resolver) + { + $this->editors[$identifier] = $resolver; + } + + /** + * Set the editor to use to open referenced files, by a string + * identifier, or a callable that will be executed for every + * file reference, with a $file and $line argument, and should + * return a string. + * + * @example + * $run->setEditor(function($file, $line) { return "file:///{$file}"; }); + * @example + * $run->setEditor('sublime'); + * + * @param string|callable $editor + */ + public function setEditor($editor) + { + if(!is_callable($editor) && !isset($this->editors[$editor])) { + throw new InvalidArgumentException( + "Unknown editor identifier: $editor. Known editors:" . + implode(",", array_keys($this->editors)) + ); + } + + $this->editor = $editor; + } + + /** + * Given a string file path, and an integer file line, + * executes the editor resolver and returns, if available, + * a string that may be used as the href property for that + * file reference. + * + * @param string $filePath + * @param int $line + * @return string|false + */ + public function getEditorHref($filePath, $line) + { + if($this->editor === null) { + return false; + } + + $editor = $this->editor; + if(is_string($editor)) { + $editor = $this->editors[$editor]; + } + + if(is_callable($editor)) { + $editor = call_user_func($editor, $filePath, $line); + } + + // Check that the editor is a string, and replace the + // %line and %file placeholders: + if(!is_string($editor)) { + throw new InvalidArgumentException( + __METHOD__ . " should always resolve to a string; got something else instead" + ); + } + + $editor = str_replace("%line", rawurlencode($line), $editor); + $editor = str_replace("%file", rawurlencode($filePath), $editor); + + return $editor; + } + + /** + * @var string + */ + public function setPageTitle($title) + { + $this->pageTitle = (string) $title; + } + + /** + * @return string + */ + public function getPageTitle() + { + return $this->pageTitle; + } + + /** + * @return string + */ + public function getResourcesPath() + { + return $this->resourcesPath; + } + + /** + * @param string $resourcesPath + */ + public function setResourcesPath($resourcesPath) + { + if(!is_dir($resourcesPath)) { + throw new InvalidArgumentException( + "$resourcesPath is not a valid directory" + ); + } + + $this->resourcesPath = $resourcesPath; + } +} diff --git a/vendor/filp/whoops/src/Whoops/Provider/Phalcon/WhoopsServiceProvider.php b/vendor/filp/whoops/src/Whoops/Provider/Phalcon/WhoopsServiceProvider.php new file mode 100644 index 0000000..40b9360 --- /dev/null +++ b/vendor/filp/whoops/src/Whoops/Provider/Phalcon/WhoopsServiceProvider.php @@ -0,0 +1,69 @@ + + */ + +namespace Whoops\Provider\Phalcon; + +use Whoops\Run; +use Whoops\Handler\PrettyPageHandler; +use Phalcon\DI; +use Phalcon\DI\Exception; + +class WhoopsServiceProvider +{ + /** + * @param Phalcon\DI $di + */ + public function __construct(DI $di = null) + { + if (!$di) { + $di = DI::getDefault(); + } + + // There's only ever going to be one error page...right? + $di->setShared('whoops.error_page_handler', function() { + return new PrettyPageHandler; + }); + + // Retrieves info on the Phalcon environment and ships it off + // to the PrettyPageHandler's data tables: + // This works by adding a new handler to the stack that runs + // before the error page, retrieving the shared page handler + // instance, and working with it to add new data tables + $phalcon_info_handler = function() use($di) { + try { + $request = $di['request']; + } catch (Exception $e) { + // This error occurred too early in the application's life + // and the request instance is not yet available. + return; + } + + // Request info: + $di['whoops.error_page_handler']->addDataTable('Phalcon Application (Request)', array( + 'URI' => $request->getScheme().'://'.$request->getServer('HTTP_HOST').$request->getServer('REQUEST_URI'), + 'Request URI' => $request->getServer('REQUEST_URI'), + 'Path Info' => $request->getServer('PATH_INFO'), + 'Query String'=> $request->getServer('QUERY_STRING') ?: '', + 'HTTP Method' => $request->getMethod(), + 'Script Name' => $request->getServer('SCRIPT_NAME'), + //'Base Path' => $request->getBasePath(), + //'Base URL' => $request->getBaseUrl(), + 'Scheme' => $request->getScheme(), + 'Port' => $request->getServer('SERVER_PORT'), + 'Host' => $request->getServerName(), + )); + }; + + $di->setShared('whoops', function() use($di, $phalcon_info_handler) { + $run = new Run; + $run->pushHandler($di['whoops.error_page_handler']); + $run->pushHandler($phalcon_info_handler); + return $run; + }); + + $di['whoops']->register(); + } +} diff --git a/vendor/filp/whoops/src/Whoops/Provider/Silex/WhoopsServiceProvider.php b/vendor/filp/whoops/src/Whoops/Provider/Silex/WhoopsServiceProvider.php new file mode 100644 index 0000000..238ab82 --- /dev/null +++ b/vendor/filp/whoops/src/Whoops/Provider/Silex/WhoopsServiceProvider.php @@ -0,0 +1,81 @@ + + */ + +namespace Whoops\Provider\Silex; +use Whoops\Run; +use Whoops\Handler\PrettyPageHandler; +use Silex\ServiceProviderInterface; +use Silex\Application; +use RuntimeException; + +class WhoopsServiceProvider implements ServiceProviderInterface +{ + /** + * @see Silex\ServiceProviderInterface::register + * @param Silex\Application $app + */ + public function register(Application $app) + { + // There's only ever going to be one error page...right? + $app['whoops.error_page_handler'] = $app->share(function() { + return new PrettyPageHandler; + }); + + // Retrieves info on the Silex environment and ships it off + // to the PrettyPageHandler's data tables: + // This works by adding a new handler to the stack that runs + // before the error page, retrieving the shared page handler + // instance, and working with it to add new data tables + $app['whoops.silex_info_handler'] = $app->protect(function() use($app) { + try { + $request = $app['request']; + } catch (RuntimeException $e) { + // This error occurred too early in the application's life + // and the request instance is not yet available. + return; + } + + // General application info: + $app['whoops.error_page_handler']->addDataTable('Silex Application', array( + 'Charset' => $app['charset'], + 'Locale' => $app['locale'], + 'Route Class' => $app['route_class'], + 'Dispatcher Class' => $app['dispatcher_class'], + 'Application Class'=> get_class($app) + )); + + // Request info: + $app['whoops.error_page_handler']->addDataTable('Silex Application (Request)', array( + 'URI' => $request->getUri(), + 'Request URI' => $request->getRequestUri(), + 'Path Info' => $request->getPathInfo(), + 'Query String'=> $request->getQueryString() ?: '', + 'HTTP Method' => $request->getMethod(), + 'Script Name' => $request->getScriptName(), + 'Base Path' => $request->getBasePath(), + 'Base URL' => $request->getBaseUrl(), + 'Scheme' => $request->getScheme(), + 'Port' => $request->getPort(), + 'Host' => $request->getHost(), + )); + }); + + $app['whoops'] = $app->share(function() use($app) { + $run = new Run; + $run->pushHandler($app['whoops.error_page_handler']); + $run->pushHandler($app['whoops.silex_info_handler']); + return $run; + }); + + $app->error(array($app['whoops'], Run::EXCEPTION_HANDLER)); + $app['whoops']->register(); + } + + /** + * @see Silex\ServiceProviderInterface::boot + */ + public function boot(Application $app) {} +} diff --git a/vendor/filp/whoops/src/Whoops/Provider/Zend/ExceptionStrategy.php b/vendor/filp/whoops/src/Whoops/Provider/Zend/ExceptionStrategy.php new file mode 100644 index 0000000..bf8830d --- /dev/null +++ b/vendor/filp/whoops/src/Whoops/Provider/Zend/ExceptionStrategy.php @@ -0,0 +1,56 @@ + + */ + +namespace Whoops\Provider\Zend; + +use Whoops\Run; + +use Zend\Mvc\View\Http\ExceptionStrategy as BaseExceptionStrategy; +use Zend\Mvc\MvcEvent; +use Zend\Mvc\Application; + +class ExceptionStrategy extends BaseExceptionStrategy { + + protected $run; + + public function __construct(Run $run) { + $this->run = $run; + return $this; + } + + public function prepareExceptionViewModel(MvcEvent $event) { + // Do nothing if no error in the event + $error = $event->getError(); + if (empty($error)) { + return; + } + + // Do nothing if the result is a response object + $result = $event->getResult(); + if ($result instanceof Response) { + return; + } + + switch ($error) { + case Application::ERROR_CONTROLLER_NOT_FOUND: + case Application::ERROR_CONTROLLER_INVALID: + case Application::ERROR_ROUTER_NO_MATCH: + // Specifically not handling these + return; + + case Application::ERROR_EXCEPTION: + default: + $response = $event->getResponse(); + if (!$response || $response->getStatusCode() === 200) { + header('HTTP/1.0 500 Internal Server Error', true, 500); + } + ob_clean(); + $this->run->handleException($event->getParam('exception')); + break; + } + } + +} diff --git a/vendor/filp/whoops/src/Whoops/Provider/Zend/Module.php b/vendor/filp/whoops/src/Whoops/Provider/Zend/Module.php new file mode 100644 index 0000000..44f14ca --- /dev/null +++ b/vendor/filp/whoops/src/Whoops/Provider/Zend/Module.php @@ -0,0 +1,106 @@ + + * + * The Whoops directory should be added as a module to ZF2 (/vendor/Whoops) + * + * Whoops must be added as the first module + * For example: + * 'modules' => array( + * 'Whoops', + * 'Application', + * ), + * + * This file should be moved next to Whoops/Run.php (/vendor/Whoops/Module.php) + * + */ + +namespace Whoops; + +use Whoops\Run; +use Whoops\Provider\Zend\ExceptionStrategy; +use Whoops\Provider\Zend\RouteNotFoundStrategy; +use Whoops\Handler\JsonResponseHandler; +use Whoops\Handler\PrettyPageHandler; +use Zend\EventManager\EventInterface; +use Zend\Console\Request as ConsoleRequest; + +class Module +{ + protected $run; + + public function onBootstrap(EventInterface $event) + { + $prettyPageHandler = new PrettyPageHandler(); + + // Set editor + $config = $event->getApplication()->getServiceManager()->get('Config'); + if (isset($config['view_manager']['editor'])) { + $prettyPageHandler->setEditor($config['view_manager']['editor']); + } + + + $this->run = new Run(); + $this->run->register(); + $this->run->pushHandler($prettyPageHandler); + + $this->attachListeners($event); + } + + public function getAutoloaderConfig() + { + return array( + 'Zend\Loader\StandardAutoloader' => array( + 'namespaces' => array( + __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__, + ), + ), + ); + } + + private function attachListeners(EventInterface $event) + { + $request = $event->getRequest(); + $application = $event->getApplication(); + $services = $application->getServiceManager(); + $events = $application->getEventManager(); + $config = $services->get('Config'); + + //Display exceptions based on configuration and console mode + if ($request instanceof ConsoleRequest || empty($config['view_manager']['display_exceptions'])) + return; + + $jsonHandler = new JsonResponseHandler(); + + if (!empty($config['view_manager']['json_exceptions']['show_trace'])) { + //Add trace to the JSON output + $jsonHandler->addTraceToOutput(true); + } + + if (!empty($config['view_manager']['json_exceptions']['ajax_only'])) { + //Only return JSON response for AJAX requests + $jsonHandler->onlyForAjaxRequests(true); + } + + if (!empty($config['view_manager']['json_exceptions']['display'])) { + //Turn on JSON handler + $this->run->pushHandler($jsonHandler); + } + + //Attach the Whoops ExceptionStrategy + $exceptionStrategy = new ExceptionStrategy($this->run); + $exceptionStrategy->attach($events); + + //Attach the Whoops RouteNotFoundStrategy + $routeNotFoundStrategy = new RouteNotFoundStrategy($this->run); + $routeNotFoundStrategy->attach($events); + + //Detach default ExceptionStrategy + $services->get('Zend\Mvc\View\Http\ExceptionStrategy')->detach($events); + + //Detach default RouteNotFoundStrategy + $services->get('Zend\Mvc\View\Http\RouteNotFoundStrategy')->detach($events); + } + +} diff --git a/vendor/filp/whoops/src/Whoops/Provider/Zend/RouteNotFoundStrategy.php b/vendor/filp/whoops/src/Whoops/Provider/Zend/RouteNotFoundStrategy.php new file mode 100644 index 0000000..6c0b3de --- /dev/null +++ b/vendor/filp/whoops/src/Whoops/Provider/Zend/RouteNotFoundStrategy.php @@ -0,0 +1,64 @@ + + */ + +namespace Whoops\Provider\Zend; + +use Whoops\Run; + +use Zend\Mvc\View\Http\RouteNotFoundStrategy as BaseRouteNotFoundStrategy; +use Zend\Mvc\MvcEvent; +use Zend\Stdlib\ResponseInterface as Response; +use Zend\View\Model\ViewModel; + +class RouteNotFoundStrategy extends BaseRouteNotFoundStrategy { + + protected $run; + + public function __construct(Run $run) { + $this->run = $run; + } + + public function prepareNotFoundViewModel(MvcEvent $e) { + $vars = $e->getResult(); + if ($vars instanceof Response) { + // Already have a response as the result + return; + } + + $response = $e->getResponse(); + if ($response->getStatusCode() != 404) { + // Only handle 404 responses + return; + } + + if (!$vars instanceof ViewModel) { + $model = new ViewModel(); + if (is_string($vars)) { + $model->setVariable('message', $vars); + } else { + $model->setVariable('message', 'Page not found.'); + } + } else { + $model = $vars; + if ($model->getVariable('message') === null) { + $model->setVariable('message', 'Page not found.'); + } + } + // If displaying reasons, inject the reason + $this->injectNotFoundReason($model, $e); + + // If displaying exceptions, inject + $this->injectException($model, $e); + + // Inject controller if we're displaying either the reason or the exception + $this->injectController($model, $e); + + ob_clean(); + + throw new \Exception($model->getVariable('message') . ' ' . $model->getVariable('reason')); + } + +} diff --git a/vendor/filp/whoops/src/Whoops/Provider/Zend/module.config.example.php b/vendor/filp/whoops/src/Whoops/Provider/Zend/module.config.example.php new file mode 100644 index 0000000..13198a8 --- /dev/null +++ b/vendor/filp/whoops/src/Whoops/Provider/Zend/module.config.example.php @@ -0,0 +1,20 @@ + + * + * Example controller configuration + */ + +return array( + 'view_manager' => array( + 'editor' => 'sublime', + 'display_not_found_reason' => true, + 'display_exceptions' => true, + 'json_exceptions' => array( + 'display' => true, + 'ajax_only' => true, + 'show_trace' => true + ) + ), +); diff --git a/vendor/filp/whoops/src/Whoops/Resources/pretty-page.css b/vendor/filp/whoops/src/Whoops/Resources/pretty-page.css new file mode 100644 index 0000000..56a073d --- /dev/null +++ b/vendor/filp/whoops/src/Whoops/Resources/pretty-page.css @@ -0,0 +1,319 @@ +.cf:before, .cf:after {content: " ";display: table;} .cf:after {clear: both;} .cf {*zoom: 1;} +body { + font: 14px helvetica, arial, sans-serif; + color: #2B2B2B; + background-color: #D4D4D4; + padding:0; + margin: 0; + max-height: 100%; +} + a { + text-decoration: none; + } + +.container{ + height: 100%; + width: 100%; + position: fixed; + margin: 0; + padding: 0; + left: 0; + top: 0; +} + +.branding { + position: absolute; + top: 10px; + right: 20px; + color: #777777; + font-size: 10px; + z-index: 100; +} + .branding a { + color: #CD3F3F; + } + +header { + padding: 30px 20px; + color: white; + background: #272727; + box-sizing: border-box; + border-left: 5px solid #CD3F3F; +} + .exc-title { + margin: 0; + color: #616161; + text-shadow: 0 1px 2px rgba(0, 0, 0, .1); + } + .exc-title-primary { color: #CD3F3F; } + .exc-message { + font-size: 32px; + margin: 5px 0; + word-wrap: break-word; + } + +.stack-container { + height: 100%; + position: relative; +} + +.details-container { + height: 100%; + overflow: auto; + float: right; + width: 70%; + background: #DADADA; +} + .details { + padding: 10px; + padding-left: 5px; + border-left: 5px solid rgba(0, 0, 0, .1); + } + +.frames-container { + height: 100%; + overflow: auto; + float: left; + width: 30%; + background: #FFF; +} + .frame { + padding: 14px; + background: #F3F3F3; + border-right: 1px solid rgba(0, 0, 0, .2); + cursor: pointer; + } + .frame.active { + background-color: #4288CE; + color: #F3F3F3; + box-shadow: inset -2px 0 0 rgba(255, 255, 255, .1); + text-shadow: 0 1px 0 rgba(0, 0, 0, .2); + } + + .frame:not(.active):hover { + background: #BEE9EA; + } + + .frame-class, .frame-function, .frame-index { + font-weight: bold; + } + + .frame-index { + font-size: 11px; + color: #BDBDBD; + } + + .frame-class { + color: #4288CE; + } + .active .frame-class { + color: #BEE9EA; + } + + .frame-file { + font-family: consolas, monospace; + word-wrap:break-word; + } + + .frame-file .editor-link { + color: #272727; + } + + .frame-line { + font-weight: bold; + color: #4288CE; + } + + .active .frame-line { color: #BEE9EA; } + .frame-line:before { + content: ":"; + } + + .frame-code { + padding: 10px; + padding-left: 5px; + background: #BDBDBD; + display: none; + border-left: 5px solid #4288CE; + } + + .frame-code.active { + display: block; + } + + .frame-code .frame-file { + background: #C6C6C6; + color: #525252; + text-shadow: 0 1px 0 #E7E7E7; + padding: 10px 10px 5px 10px; + + border-top-right-radius: 6px; + border-top-left-radius: 6px; + + border: 1px solid rgba(0, 0, 0, .1); + border-bottom: none; + box-shadow: inset 0 1px 0 #DADADA; + } + + .code-block { + padding: 10px; + margin: 0; + box-shadow: inset 0 0 6px rgba(0, 0, 0, .3); + } + + .linenums { + margin: 0; + margin-left: 10px; + } + + .frame-comments { + box-shadow: inset 0 0 6px rgba(0, 0, 0, .3); + border: 1px solid rgba(0, 0, 0, .2); + border-top: none; + + border-bottom-right-radius: 6px; + border-bottom-left-radius: 6px; + + padding: 5px; + font-size: 12px; + background: #404040; + } + + .frame-comments.empty { + padding: 8px 15px; + } + + .frame-comments.empty:before { + content: "No comments for this stack frame."; + font-style: italic; + color: #828282; + } + + .frame-comment { + padding: 10px; + color: #D2D2D2; + } + .frame-comment a { + color: #BEE9EA; + font-weight: bold; + text-decoration: none; + } + .frame-comment a:hover { + color: #4bb1b1; + } + + .frame-comment:not(:last-child) { + border-bottom: 1px dotted rgba(0, 0, 0, .3); + } + + .frame-comment-context { + font-size: 10px; + font-weight: bold; + color: #86D2B6; + } + +.data-table-container label { + font-size: 16px; + font-weight: bold; + color: #4288CE; + margin: 10px 0; + padding: 10px 0; + + display: block; + margin-bottom: 5px; + padding-bottom: 5px; + border-bottom: 1px dotted rgba(0, 0, 0, .2); +} + .data-table { + width: 100%; + margin: 10px 0; + } + + .data-table tbody { + font: 13px consolas, monospace; + } + + .data-table thead { + display: none; + } + + .data-table tr { + padding: 5px 0; + } + + .data-table td:first-child { + width: 20%; + min-width: 130px; + overflow: hidden; + font-weight: bold; + color: #463C54; + padding-right: 5px; + + } + + .data-table td:last-child { + width: 80%; + -ms-word-break: break-all; + word-break: break-all; + word-break: break-word; + -webkit-hyphens: auto; + -moz-hyphens: auto; + hyphens: auto; + } + + .data-table .empty { + color: rgba(0, 0, 0, .3); + font-style: italic; + } + +.handler { + padding: 10px; + font: 14px monospace; +} + +.handler.active { + color: #BBBBBB; + background: #989898; + font-weight: bold; +} + +/* prettify code style +Uses the Doxy theme as a base */ +pre .str, code .str { color: #BCD42A; } /* string */ +pre .kwd, code .kwd { color: #4bb1b1; font-weight: bold; } /* keyword*/ +pre .com, code .com { color: #888; font-weight: bold; } /* comment */ +pre .typ, code .typ { color: #ef7c61; } /* type */ +pre .lit, code .lit { color: #BCD42A; } /* literal */ +pre .pun, code .pun { color: #fff; font-weight: bold; } /* punctuation */ +pre .pln, code .pln { color: #e9e4e5; } /* plaintext */ +pre .tag, code .tag { color: #4bb1b1; } /* html/xml tag */ +pre .htm, code .htm { color: #dda0dd; } /* html tag */ +pre .xsl, code .xsl { color: #d0a0d0; } /* xslt tag */ +pre .atn, code .atn { color: #ef7c61; font-weight: normal;} /* html/xml attribute name */ +pre .atv, code .atv { color: #bcd42a; } /* html/xml attribute value */ +pre .dec, code .dec { color: #606; } /* decimal */ +pre.prettyprint, code.prettyprint { + font-family: 'Source Code Pro', Monaco, Consolas, "Lucida Console", monospace;; + background: #333; + color: #e9e4e5; +} + pre.prettyprint { + white-space: pre-wrap; + } + + pre.prettyprint a, code.prettyprint a { + text-decoration:none; + } + + .linenums li { + color: #A5A5A5; + } + + .linenums li.current{ + background: rgba(255, 100, 100, .07); + padding-top: 4px; + padding-left: 1px; + } + .linenums li.current.active { + background: rgba(255, 100, 100, .17); + } diff --git a/vendor/filp/whoops/src/Whoops/Resources/pretty-template.php b/vendor/filp/whoops/src/Whoops/Resources/pretty-template.php new file mode 100644 index 0000000..90cdb9f --- /dev/null +++ b/vendor/filp/whoops/src/Whoops/Resources/pretty-template.php @@ -0,0 +1,205 @@ + + + + + + <?php echo $e($v->title) ?> + + + + +
+ +
+ +
+ + + frames as $i => $frame): ?> +
+
+ frames) - $i - 1) ?>. + getClass() ?: '') ?> + getFunction() ?: '') ?> +
+ + + getFile(true) ?: '<#unknown>') ?>getLine() ?> + +
+ + +
+ +
+ +
+
+

+ name as $i => $nameSection): ?> + name) - 1): ?> + + + + + +

+

+ message) ?> +

+
+
+ + +
+ frames as $i => $frame): ?> + getLine(); ?> +
+
+ getFile(); ?> + handler->getEditorHref($filePath, (int) $line)): ?> + Open: + + ') ?> + + + ') ?> + +
+ getFileLines($line - 8, 10); + $range = array_map(function($line){ return empty($line) ? ' ' : $line;}, $range); + $start = key($range) + 1; + $code = join("\n", $range); + ?> +
+ + + getComments(); + ?> +
+ $comment): ?> + +
+ + +
+ +
+ +
+ +
+ + +
+
+ tables as $label => $data): ?> +
+ + + + + + + + + + $value): ?> + + + + + +
KeyValue
+ + empty + +
+ +
+ + +
+ + handlers as $i => $handler): ?> +
+ . +
+ +
+ +
+
+ +
+
+ + + + + + diff --git a/vendor/filp/whoops/src/Whoops/Run.php b/vendor/filp/whoops/src/Whoops/Run.php new file mode 100644 index 0000000..1f2ddde --- /dev/null +++ b/vendor/filp/whoops/src/Whoops/Run.php @@ -0,0 +1,253 @@ + + */ + +namespace Whoops; +use Whoops\Handler\HandlerInterface; +use Whoops\Handler\Handler; +use Whoops\Handler\CallbackHandler; +use Whoops\Exception\Inspector; +use Whoops\Exception\ErrorException; +use InvalidArgumentException; +use Exception; + +class Run +{ + const EXCEPTION_HANDLER = 'handleException'; + const ERROR_HANDLER = 'handleError'; + const SHUTDOWN_HANDLER = 'handleShutdown'; + + protected $isRegistered; + protected $allowQuit = true; + protected $sendOutput = true; + + /** + * @var Whoops\Handler\HandlerInterface[] + */ + protected $handlerStack = array(); + + /** + * Pushes a handler to the end of the stack. + * @param Whoops\HandlerInterface $handler + * @return Whoops\Run + */ + public function pushHandler($handler) + { + if(is_callable($handler)) { + $handler = new CallbackHandler($handler); + } + + if(!$handler instanceof HandlerInterface) { + throw new InvalidArgumentException( + 'Argument to ' . __METHOD__ . ' must be a callable, or instance of' + . 'Whoops\\Handler\\HandlerInterface' + ); + } + + $this->handlerStack[] = $handler; + return $this; + } + + /** + * Removes the last handler in the stack and returns it. + * Returns null if there's nothing else to pop. + * @return null|Whoops\Handler\HandlerInterface + */ + public function popHandler() + { + return array_pop($this->handlerStack); + } + + /** + * Returns an array with all handlers, in the + * order they were added to the stack. + * @return array + */ + public function getHandlers() + { + return $this->handlerStack; + } + + /** + * Clears all handlers in the handlerStack, including + * the default PrettyPage handler. + * @return Whoops\Run + */ + public function clearHandlers() + { + $this->handlerStack = array(); + return $this; + } + + /** + * @param Exception $exception + * @return Whoops\Exception\Inspector + */ + protected function getInspector(Exception $exception) + { + return new Inspector($exception); + } + + /** + * Registers this instance as an error handler. + * @return Whoops\Run + */ + public function register() + { + if(!$this->isRegistered) { + set_error_handler(array($this, self::ERROR_HANDLER)); + set_exception_handler(array($this, self::EXCEPTION_HANDLER)); + register_shutdown_function(array($this, self::SHUTDOWN_HANDLER)); + + $this->isRegistered = true; + } + + return $this; + } + + /** + * Unregisters all handlers registered by this Whoops\Run instance + * @return Whoops\Run + */ + public function unregister() + { + if($this->isRegistered) { + restore_exception_handler(); + restore_error_handler(); + + $this->isRegistered = false; + } + + return $this; + } + + /** + * Should Whoops allow Handlers to force the script to quit? + * @param bool|num $exit + * @return bool + */ + public function allowQuit($exit = null) + { + if(func_num_args() == 0) { + return $this->allowQuit; + } + + return $this->allowQuit = (bool) $exit; + } + + /** + * Should Whoops push output directly to the client? + * If this is false, output will be returned by handleException + * @param bool|num $send + * @return bool + */ + public function writeToOutput($send = null) + { + if(func_num_args() == 0) { + return $this->sendOutput; + } + + return $this->sendOutput = (bool) $send; + } + + /** + * Handles an exception, ultimately generating a Whoops error + * page. + * + * @param Exception $exception + * @return string Output generated by handlers + */ + public function handleException(Exception $exception) + { + // Walk the registered handlers in the reverse order + // they were registered, and pass off the exception + $inspector = $this->getInspector($exception); + + // Capture output produced while handling the exception, + // we might want to send it straight away to the client, + // or return it silently. + ob_start(); + + for($i = count($this->handlerStack) - 1; $i >= 0; $i--) { + $handler = $this->handlerStack[$i]; + + $handler->setRun($this); + $handler->setInspector($inspector); + $handler->setException($exception); + + $handlerResponse = $handler->handle($exception); + + if(in_array($handlerResponse, array(Handler::LAST_HANDLER, Handler::QUIT))) { + // The Handler has handled the exception in some way, and + // wishes to quit execution (Handler::QUIT), or skip any + // other handlers (Handler::LAST_HANDLER). If $this->allowQuit + // is false, Handler::QUIT behaves like Handler::LAST_HANDLER + break; + } + } + + $output = ob_get_clean(); + + // If we're allowed to, send output generated by handlers directly + // to the output, otherwise, and if the script doesn't quit, return + // it so that it may be used by the caller + if($this->writeToOutput()) { + // @todo Might be able to clean this up a bit better + // If we're going to quit execution, cleanup all other output + // buffers before sending our own output: + if($handlerResponse == Handler::QUIT && $this->allowQuit()) { + while (ob_get_level() > 0) ob_end_clean(); + } + + echo $output; + } + + // Handlers are done! Check if we got here because of Handler::QUIT + // ($handlerResponse will be the response from the last queried handler) + // and if so, try to quit execution. + if($handlerResponse == Handler::QUIT && $this->allowQuit()) { + exit; + } + + return $output; + } + + /** + * Converts generic PHP errors to \ErrorException + * instances, before passing them off to be handled. + * + * This method MUST be compatible with set_error_handler. + * + * @param int $level + * @param string $message + * @param string $file + * @param int $line + */ + public function handleError($level, $message, $file = null, $line = null) + { + if ($level & error_reporting()) { + $this->handleException( + new ErrorException( + $message, $level, 0, $file, $line + ) + ); + } + } + + /** + * Special case to deal with Fatal errors and the like. + */ + public function handleShutdown() + { + if($error = error_get_last()) { + $this->handleError( + $error['type'], + $error['message'], + $error['file'], + $error['line'] + ); + } + } +} diff --git a/vendor/filp/whoops/tests/Whoops/Exception/FrameCollectionTest.php b/vendor/filp/whoops/tests/Whoops/Exception/FrameCollectionTest.php new file mode 100644 index 0000000..136d115 --- /dev/null +++ b/vendor/filp/whoops/tests/Whoops/Exception/FrameCollectionTest.php @@ -0,0 +1,150 @@ + + */ + +namespace Whoops\Exception; +use Whoops\Exception\FrameCollection; +use Whoops\TestCase; +use Mockery as m; + +class FrameCollectionTest extends TestCase +{ + /** + * Stupid little counter for tagging frames + * with a unique but predictable id + * @var int + */ + private $frameIdCounter = 0; + + /** + * @return array + */ + public function getFrameData() + { + $id = ++$this->frameIdCounter; + return array( + 'file' => __DIR__ . '/../../fixtures/frame.lines-test.php', + 'line' => $id, + 'function' => 'test-' . $id, + 'class' => 'MyClass', + 'args' => array(true, 'hello') + ); + } + + /** + * @param int $total + * @return array + */ + public function getFrameDataList($total) + { + $total = max((int) $total, 1); + $self = $this; + $frames = array_map(function() use($self) { + return $self->getFrameData(); + }, range(1, $total)); + + return $frames; + } + + /** + * @param array $frames + * @return Whoops\Exception\FrameCollection + */ + private function getFrameCollectionInstance($frames = null) + { + if($frames === null) { + $frames = $this->getFrameDataList(10); + } + + return new FrameCollection($frames); + } + + /** + * @covers Whoops\Exception\FrameCollection::filter + * @covers Whoops\Exception\FrameCollection::count + */ + public function testFilterFrames() + { + $frames = $this->getFrameCollectionInstance(); + + // Filter out all frames with a line number under 6 + $frames->filter(function($frame) { + return $frame->getLine() <= 5; + }); + + $this->assertCount(5, $frames); + } + + /** + * @covers Whoops\Exception\FrameCollection::map + */ + public function testMapFrames() + { + $frames = $this->getFrameCollectionInstance(); + + // Filter out all frames with a line number under 6 + $frames->map(function($frame) { + $frame->addComment("This is cool", "test"); + return $frame; + }); + + $this->assertCount(10, $frames); + } + + + /** + * @covers Whoops\Exception\FrameCollection::map + * @expectedException UnexpectedValueException + */ + public function testMapFramesEnforceType() + { + $frames = $this->getFrameCollectionInstance(); + + // Filter out all frames with a line number under 6 + $frames->map(function($frame) { + return "bajango"; + }); + } + + /** + * @covers Whoops\Exception\FrameCollection::getArray + */ + public function testGetArray() + { + $frames = $this->getFrameCollectionInstance(); + $frames = $frames->getArray(); + + $this->assertCount(10, $frames); + foreach($frames as $frame) { + $this->assertInstanceOf('Whoops\\Exception\\Frame', $frame); + } + } + + /** + * @covers Whoops\Exception\FrameCollection::getIterator + */ + public function testCollectionIsIterable() + { + $frames = $this->getFrameCollectionInstance(); + foreach($frames as $frame) { + $this->assertInstanceOf('Whoops\\Exception\\Frame', $frame); + } + } + + /** + * @covers Whoops\Exception\FrameCollection::serialize + * @covers Whoops\Exception\FrameCollection::unserialize + */ + public function testCollectionIsSerializable() + { + $frames = $this->getFrameCollectionInstance(); + $serializedFrames = serialize($frames); + $newFrames = unserialize($serializedFrames); + + foreach($newFrames as $frame) { + $this->assertInstanceOf('Whoops\\Exception\\Frame', $frame); + } + } +} diff --git a/vendor/filp/whoops/tests/Whoops/Exception/FrameTest.php b/vendor/filp/whoops/tests/Whoops/Exception/FrameTest.php new file mode 100644 index 0000000..d615ae0 --- /dev/null +++ b/vendor/filp/whoops/tests/Whoops/Exception/FrameTest.php @@ -0,0 +1,209 @@ + + */ + +namespace Whoops\Exception; +use Whoops\Exception\Frame; +use Whoops\TestCase; +use Mockery as m; + +class FrameTest extends TestCase +{ + /** + * @return array + */ + private function getFrameData() + { + return array( + 'file' => __DIR__ . '/../../fixtures/frame.lines-test.php', + 'line' => 0, + 'function' => 'test', + 'class' => 'MyClass', + 'args' => array(true, 'hello') + ); + } + + /** + * @param array $data + * @return Whoops\Exception\Frame + */ + private function getFrameInstance($data = null) + { + if($data === null) { + $data = $this->getFrameData(); + } + + return new Frame($data); + } + + /** + * @covers Whoops\Exception\Frame::getFile + */ + public function testGetFile() + { + $data = $this->getFrameData(); + $frame = $this->getFrameInstance($data); + + $this->assertEquals($frame->getFile(), $data['file']); + } + + /** + * @covers Whoops\Exception\Frame::getLine + */ + public function testGetLine() + { + $data = $this->getFrameData(); + $frame = $this->getFrameInstance($data); + + $this->assertEquals($frame->getLine(), $data['line']); + } + + /** + * @covers Whoops\Exception\Frame::getClass + */ + public function testGetClass() + { + $data = $this->getFrameData(); + $frame = $this->getFrameInstance($data); + + $this->assertEquals($frame->getClass(), $data['class']); + } + + /** + * @covers Whoops\Exception\Frame::getFunction + */ + public function testGetFunction() + { + $data = $this->getFrameData(); + $frame = $this->getFrameInstance($data); + + $this->assertEquals($frame->getFunction(), $data['function']); + } + + /** + * @covers Whoops\Exception\Frame::getArgs + */ + public function testGetArgs() + { + $data = $this->getFrameData(); + $frame = $this->getFrameInstance($data); + + $this->assertEquals($frame->getArgs(), $data['args']); + } + + /** + * @covers Whoops\Exception\Frame::getFileContents + */ + public function testGetFileContents() + { + $data = $this->getFrameData(); + $frame = $this->getFrameInstance($data); + + $this->assertEquals($frame->getFileContents(), file_get_contents($data['file'])); + } + + /** + * @covers Whoops\Exception\Frame::getFileLines + */ + public function testGetFileLines() + { + $data = $this->getFrameData(); + $frame = $this->getFrameInstance($data); + + $lines = explode("\n", $frame->getFileContents()); + $this->assertEquals($frame->getFileLines(), $lines); + } + + /** + * @covers Whoops\Exception\Frame::getFileLines + */ + public function testGetFileLinesRange() + { + $data = $this->getFrameData(); + $frame = $this->getFrameInstance($data); + + $lines = $frame->getFileLines(0, 3); + + $this->assertEquals($lines[0], 'assertEquals($lines[1], '// Line 2'); + $this->assertEquals($lines[2], '// Line 3'); + } + + /** + * @covers Whoops\Exception\Frame::addComment + * @covers Whoops\Exception\Frame::getComments + */ + public function testGetComments() + { + $frame = $this->getFrameInstance(); + $testComments = array( + 'Dang, yo!', + 'Errthangs broken!', + 'Dayumm!' + ); + + $frame->addComment($testComments[0]); + $frame->addComment($testComments[1]); + $frame->addComment($testComments[2]); + + $comments = $frame->getComments(); + + $this->assertCount(3, $comments); + + $this->assertEquals($comments[0]['comment'], $testComments[0]); + $this->assertEquals($comments[1]['comment'], $testComments[1]); + $this->assertEquals($comments[2]['comment'], $testComments[2]); + } + + /** + * @covers Whoops\Exception\Frame::addComment + * @covers Whoops\Exception\Frame::getComments + */ + public function testGetFilteredComments() + { + $frame = $this->getFrameInstance(); + $testComments = array( + array('Dang, yo!', 'test'), + array('Errthangs broken!', 'test'), + 'Dayumm!' + ); + + $frame->addComment($testComments[0][0], $testComments[0][1]); + $frame->addComment($testComments[1][0], $testComments[1][1]); + $frame->addComment($testComments[2][0], $testComments[2][1]); + + $comments = $frame->getComments('test'); + + $this->assertCount(2, $comments); + $this->assertEquals($comments[0]['comment'], $testComments[0][0]); + $this->assertEquals($comments[1]['comment'], $testComments[1][0]); + } + + /** + * @covers Whoops\Exception\Frame::serialize + * @covers Whoops\Exception\Frame::unserialize + */ + public function testFrameIsSerializable() + { + $data = $this->getFrameData(); + $frame = $this->getFrameInstance(); + $commentText = "Gee I hope this works"; + $commentContext = "test"; + + $frame->addComment($commentText, $commentContext); + + $serializedFrame = serialize($frame); + $newFrame = unserialize($serializedFrame); + + $this->assertInstanceOf('Whoops\\Exception\\Frame', $newFrame); + $this->assertEquals($newFrame->getFile(), $data['file']); + $this->assertEquals($newFrame->getLine(), $data['line']); + + $comments = $newFrame->getComments(); + $this->assertCount(1, $comments); + $this->assertEquals($comments[0]["comment"], $commentText); + $this->assertEquals($comments[0]["context"], $commentContext); + } +} diff --git a/vendor/filp/whoops/tests/Whoops/Exception/InspectorTest.php b/vendor/filp/whoops/tests/Whoops/Exception/InspectorTest.php new file mode 100644 index 0000000..7c78740 --- /dev/null +++ b/vendor/filp/whoops/tests/Whoops/Exception/InspectorTest.php @@ -0,0 +1,67 @@ + + */ + +namespace Whoops\Exception; +use Whoops\Exception\Inspector; +use Whoops\TestCase; +use RuntimeException; +use Exception; +use Mockery as m; + +class InspectorTest extends TestCase +{ + /** + * @param string $message + * @return Exception + */ + protected function getException($message = null) + { + return m::mock('Exception', array($message)); + } + + /** + * @param Exception $exception|null + * @return Whoops\Exception\Inspector + */ + protected function getInspectorInstance(Exception $exception = null) + { + return new Inspector($exception); + } + + /** + * @covers Whoops\Exception\Inspector::getExceptionName + */ + public function testReturnsCorrectExceptionName() + { + $exception = $this->getException(); + $inspector = $this->getInspectorInstance($exception); + + $this->assertEquals(get_class($exception), $inspector->getExceptionName()); + } + + /** + * @covers Whoops\Exception\Inspector::__construct + * @covers Whoops\Exception\Inspector::getException + */ + public function testExceptionIsStoredAndReturned() + { + $exception = $this->getException(); + $inspector = $this->getInspectorInstance($exception); + + $this->assertSame($exception, $inspector->getException()); + } + + /** + * @covers Whoops\Exception\Inspector::getFrames + */ + public function testGetFramesReturnsCollection() + { + $exception = $this->getException(); + $inspector = $this->getInspectorInstance($exception); + + $this->assertInstanceOf('Whoops\\Exception\\FrameCollection', $inspector->getFrames()); + } +} diff --git a/vendor/filp/whoops/tests/Whoops/Handler/JsonResponseHandlerTest.php b/vendor/filp/whoops/tests/Whoops/Handler/JsonResponseHandlerTest.php new file mode 100644 index 0000000..548d4f7 --- /dev/null +++ b/vendor/filp/whoops/tests/Whoops/Handler/JsonResponseHandlerTest.php @@ -0,0 +1,96 @@ + + */ + +namespace Whoops\Handler; +use Whoops\TestCase; +use Whoops\Handler\JsonResponseHandler; +use RuntimeException; + +class JsonResponseHandlerTest extends TestCase +{ + /** + * @return Whoops\Handler\JsonResponseHandler + */ + private function getHandler() + { + return new JsonResponseHandler; + } + + /** + * @return RuntimeException + */ + public function getException($message = 'test message') + { + return new RuntimeException($message); + } + + /** + * @param bool $withTrace + * @return array + */ + private function getJsonResponseFromHandler($withTrace = false) + { + $handler = $this->getHandler(); + $handler->addTraceToOutput($withTrace); + + $run = $this->getRunInstance(); + $run->pushHandler($handler); + $run->register(); + + $exception = $this->getException(); + ob_start(); + $run->handleException($exception); + $json = json_decode(ob_get_clean(), true); + + // Check that the json response is parse-able: + $this->assertEquals(json_last_error(), JSON_ERROR_NONE); + + return $json; + } + + /** + * @covers Whoops\Handler\JsonResponseHandler::addTraceToOutput + * @covers Whoops\Handler\JsonResponseHandler::handle + */ + public function testReturnsWithoutFrames() + { + $json = $this->getJsonResponseFromHandler($withTrace = false); + + // Check that the response has the expected keys: + $this->assertArrayHasKey('error', $json); + $this->assertArrayHasKey('type', $json['error']); + $this->assertArrayHasKey('file', $json['error']); + $this->assertArrayHasKey('line', $json['error']); + + // Check the field values: + $this->assertEquals($json['error']['file'], __FILE__); + $this->assertEquals($json['error']['message'], 'test message'); + $this->assertEquals($json['error']['type'], get_class($this->getException())); + + // Check that the trace is NOT returned: + $this->assertArrayNotHasKey('trace', $json['error']); + } + + /** + * @covers Whoops\Handler\JsonResponseHandler::addTraceToOutput + * @covers Whoops\Handler\JsonResponseHandler::handle + */ + public function testReturnsWithFrames() + { + $json = $this->getJsonResponseFromHandler($withTrace = true); + + // Check that the trace is returned: + $this->assertArrayHasKey('trace', $json['error']); + + // Check that a random frame has the expected fields + $traceFrame = reset($json['error']['trace']); + $this->assertArrayHasKey('file', $traceFrame); + $this->assertArrayHasKey('line', $traceFrame); + $this->assertArrayHasKey('function', $traceFrame); + $this->assertArrayHasKey('class', $traceFrame); + $this->assertArrayHasKey('args', $traceFrame); + } +} diff --git a/vendor/filp/whoops/tests/Whoops/Handler/PrettyPageHandlerTest.php b/vendor/filp/whoops/tests/Whoops/Handler/PrettyPageHandlerTest.php new file mode 100644 index 0000000..3bc6c2a --- /dev/null +++ b/vendor/filp/whoops/tests/Whoops/Handler/PrettyPageHandlerTest.php @@ -0,0 +1,268 @@ + + */ + +namespace Whoops\Handler; +use Whoops\TestCase; +use Whoops\Handler\PrettyPageHandler; +use RuntimeException; +use InvalidArgumentException; + +class PrettyPageHandlerTest extends TestCase +{ + /** + * @return Whoops\Handler\JsonResponseHandler + */ + private function getHandler() + { + return new PrettyPageHandler; + } + + /** + * @return RuntimeException + */ + public function getException() + { + return new RuntimeException; + } + + /** + * Test that PrettyPageHandle handles the template without + * any errors. + * @covers Whoops\Handler\PrettyPageHandler::handle + */ + public function testHandleWithoutErrors() + { + $run = $this->getRunInstance(); + $handler = $this->getHandler(); + + $run->pushHandler($handler); + + ob_start(); + $run->handleException($this->getException()); + ob_get_clean(); + } + + /** + * @covers Whoops\Handler\PrettyPageHandler::setPageTitle + * @covers Whoops\Handler\PrettyPageHandler::getPageTitle + */ + public function testGetSetPageTitle() + { + $title = 'My Cool Error Handler'; + $handler = $this->getHandler(); + $handler->setPageTitle($title); + + $this->assertEquals($title, $handler->getPagetitle()); + } + + /** + * @covers Whoops\Handler\PrettyPageHandler::setResourcesPath + * @covers Whoops\Handler\PrettyPageHandler::getResourcesPath + */ + public function testGetSetResourcesPath() + { + $path = __DIR__; // guaranteed to be valid! + $handler = $this->getHandler(); + + $handler->setResourcesPath($path); + $this->assertEquals($path, $handler->getResourcesPath()); + } + + /** + * @covers Whoops\Handler\PrettyPageHandler::setResourcesPath + * @expectedException InvalidArgumentException + */ + public function testSetInvalidResourcesPath() + { + $path = __DIR__ . '/ZIMBABWE'; // guaranteed to be invalid! + $this->getHandler()->setResourcesPath($path); + } + + /** + * @covers Whoops\Handler\PrettyPageHandler::getDataTables + * @covers Whoops\Handler\PrettyPageHandler::addDataTable + */ + public function testGetSetDataTables() + { + $handler = $this->getHandler(); + + // should have no tables by default: + $this->assertEmpty($handler->getDataTables()); + + $tableOne = array( + 'ice' => 'cream', + 'ice-ice' => 'baby' + ); + + $tableTwo = array( + 'dolan' =>'pls', + 'time' => time() + ); + + $handler->addDataTable('table 1', $tableOne); + $handler->addDataTable('table 2', $tableTwo); + + // should contain both tables: + $tables = $handler->getDataTables(); + $this->assertCount(2, $tables); + + $this->assertEquals($tableOne, $tables['table 1']); + $this->assertEquals($tableTwo, $tables['table 2']); + + // should contain only table 1 + $this->assertEquals($tableOne, $handler->getDataTables('table 1')); + + // should return an empty table: + $this->assertEmpty($handler->getDataTables('ZIMBABWE!')); + } + + /** + * @covers Whoops\Handler\PrettyPageHandler::getDataTables + * @covers Whoops\Handler\PrettyPageHandler::addDataTableCallback + */ + public function testSetCallbackDataTables() + { + $handler = $this->getHandler(); + + $this->assertEmpty($handler->getDataTables()); + $table1 = function() { + return array( + 'hammer' => 'time', + 'foo' => 'bar', + ); + }; + $expected1 = array('hammer' => 'time', 'foo' => 'bar'); + + $table2 = function() use ($expected1) { + return array( + 'another' => 'table', + 'this' => $expected1, + ); + }; + $expected2 = array('another' => 'table', 'this' => $expected1); + + $table3 = create_function('', 'return array("oh my" => "how times have changed!");'); + $expected3 = array('oh my' => 'how times have changed!'); + + // Sanity check, make sure expected values really are correct. + $this->assertSame($expected1, $table1()); + $this->assertSame($expected2, $table2()); + $this->assertSame($expected3, $table3()); + + $handler->addDataTableCallback('table1', $table1); + $handler->addDataTableCallback('table2', $table2); + $handler->addDataTableCallback('table3', $table3); + + $tables = $handler->getDataTables(); + $this->assertCount(3, $tables); + + // Supplied callable is wrapped in a closure + $this->assertInstanceOf('Closure', $tables['table1']); + $this->assertInstanceOf('Closure', $tables['table2']); + $this->assertInstanceOf('Closure', $tables['table3']); + + // Run each wrapped callable and check results against expected output. + $this->assertEquals($expected1, $tables['table1']()); + $this->assertEquals($expected2, $tables['table2']()); + $this->assertEquals($expected3, $tables['table3']()); + + $this->assertSame($tables['table1'], $handler->getDataTables('table1')); + $this->assertSame($expected1, call_user_func($handler->getDataTables('table1'))); + } + + /** + * @covers Whoops\Handler\PrettyPageHandler::setEditor + * @covers Whoops\Handler\PrettyPageHandler::getEditorHref + */ + public function testSetEditorSimple() + { + $handler = $this->getHandler(); + $handler->setEditor('sublime'); + + $this->assertEquals( + $handler->getEditorHref('/foo/bar.php', 10), + 'subl://open?url=file://%2Ffoo%2Fbar.php&line=10' + ); + + $this->assertEquals( + $handler->getEditorHref('/foo/with space?.php', 2324), + 'subl://open?url=file://%2Ffoo%2Fwith%20space%3F.php&line=2324' + ); + + $this->assertEquals( + $handler->getEditorHref('/foo/bar/with-dash.php', 0), + 'subl://open?url=file://%2Ffoo%2Fbar%2Fwith-dash.php&line=0' + ); + } + + /** + * @covers Whoops\Handler\PrettyPageHandler::setEditor + * @covers Whoops\Handler\PrettyPageHandler::getEditorHref + */ + public function testSetEditorCallable() + { + $handler = $this->getHandler(); + $handler->setEditor(function($file, $line) { + $file = rawurlencode($file); + $line = rawurlencode($line); + return "http://google.com/search/?q=$file:$line"; + }); + + $this->assertEquals( + $handler->getEditorHref('/foo/bar.php', 10), + 'http://google.com/search/?q=%2Ffoo%2Fbar.php:10' + ); + } + + /** + * @covers Whoops\Handler\PrettyPageHandler::setEditor + * @covers Whoops\Handler\PrettyPageHandler::addEditor + * @covers Whoops\Handler\PrettyPageHandler::getEditorHref + */ + public function testAddEditor() + { + $handler = $this->getHandler(); + $handler->addEditor('test-editor', function($file, $line) { + return "cool beans $file:$line"; + }); + + $handler->setEditor('test-editor'); + + $this->assertEquals( + $handler->getEditorHref('hello', 20), + 'cool beans hello:20' + ); + } + + public function testEditorXdebug() + { + if (!extension_loaded('xdebug')) { + $this->markTestSkipped('xdebug is not available'); + } + + $originalValue = ini_get('xdebug.file_link_format'); + + $handler = $this->getHandler(); + $handler->setEditor('xdebug'); + + ini_set('xdebug.file_link_format', '%f:%l'); + + $this->assertEquals( + '/foo/bar.php:10', + $handler->getEditorHref('/foo/bar.php', 10) + ); + + ini_set('xdebug.file_link_format', 'subl://open?url=%f&line=%l'); + + // xdebug doesn't do any URL encoded, matching that behaviour. + $this->assertEquals( + 'subl://open?url=/foo/with space?.php&line=2324', + $handler->getEditorHref('/foo/with space?.php', 2324) + ); + + ini_set('xdebug.file_link_format', $originalValue); + } +} diff --git a/vendor/filp/whoops/tests/Whoops/RunTest.php b/vendor/filp/whoops/tests/Whoops/RunTest.php new file mode 100755 index 0000000..a3e1ce5 --- /dev/null +++ b/vendor/filp/whoops/tests/Whoops/RunTest.php @@ -0,0 +1,330 @@ + + */ + +namespace Whoops; +use Whoops\TestCase; +use Whoops\Run; +use Whoops\Handler\Handler; +use RuntimeException; +use ArrayObject; +use Mockery as m; + +class RunTest extends TestCase +{ + + /** + * @param string $message + * @return Exception + */ + protected function getException($message = null) + { + return m::mock('Exception', array($message)); + } + + /** + * @return Whoops\Handler\Handler + */ + protected function getHandler() + { + return m::mock('Whoops\\Handler\\Handler') + ->shouldReceive('setRun') + ->andReturn(null) + ->mock() + + ->shouldReceive('setInspector') + ->andReturn(null) + ->mock() + + ->shouldReceive('setException') + ->andReturn(null) + ->mock() + ; + } + + /** + * @covers Whoops\Run::clearHandlers + */ + public function testClearHandlers() + { + $run = $this->getRunInstance(); + $run->clearHandlers(); + + $handlers = $run->getHandlers(); + + $this->assertEmpty($handlers); + } + + /** + * @covers Whoops\Run::pushHandler + */ + public function testPushHandler() + { + $run = $this->getRunInstance(); + $run->clearHandlers(); + + $handlerOne = $this->getHandler(); + $handlerTwo = $this->getHandler(); + + $run->pushHandler($handlerOne); + $run->pushHandler($handlerTwo); + + $handlers = $run->getHandlers(); + + $this->assertCount(2, $handlers); + $this->assertContains($handlerOne, $handlers); + $this->assertContains($handlerTwo, $handlers); + } + + /** + * @expectedException InvalidArgumentException + * @covers Whoops\Run::pushHandler + */ + public function testPushInvalidHandler() + { + $run = $this->getRunInstance(); + $run->pushHandler($banana = 'actually turnip'); + } + + /** + * @covers Whoops\Run::pushHandler + */ + public function testPushClosureBecomesHandler() + { + $run = $this->getRunInstance(); + $run->pushHandler(function() {}); + $this->assertInstanceOf('Whoops\\Handler\\CallbackHandler', $run->popHandler()); + } + + /** + * @covers Whoops\Run::popHandler + * @covers Whoops\Run::getHandlers + */ + public function testPopHandler() + { + $run = $this->getRunInstance(); + + $handlerOne = $this->getHandler(); + $handlerTwo = $this->getHandler(); + $handlerThree = $this->getHandler(); + + $run->pushHandler($handlerOne); + $run->pushHandler($handlerTwo); + $run->pushHandler($handlerThree); + + $this->assertSame($handlerThree, $run->popHandler()); + $this->assertSame($handlerTwo, $run->popHandler()); + $this->assertSame($handlerOne, $run->popHandler()); + + // Should return null if there's nothing else in + // the stack + $this->assertNull($run->popHandler()); + + // Should be empty since we popped everything off + // the stack: + $this->assertEmpty($run->getHandlers()); + } + + /** + * @covers Whoops\Run::register + */ + public function testRegisterHandler() + { + $this->markTestSkipped("Need to test exception handler"); + + $run = $this->getRunInstance(); + $run->register(); + + $handler = $this->getHandler(); + $run->pushHandler($handler); + + throw $this->getException(); + + $this->assertCount(2, $handler->exceptions); + } + + /** + * @covers Whoops\Run::unregister + * @expectedException Exception + */ + public function testUnregisterHandler() + { + $run = $this->getRunInstance(); + $run->register(); + + $handler = $this->getHandler(); + $run->pushHandler($handler); + + $run->unregister(); + throw $this->getException("I'm not supposed to be caught!"); + } + + /** + * @covers Whoops\Run::pushHandler + * @covers Whoops\Run::getHandlers + */ + public function testHandlerHoldsOrder() + { + $run = $this->getRunInstance(); + + $handlerOne = $this->getHandler(); + $handlerTwo = $this->getHandler(); + $handlerThree = $this->getHandler(); + $handlerFour = $this->getHandler(); + + $run->pushHandler($handlerOne); + $run->pushHandler($handlerTwo); + $run->pushHandler($handlerThree); + $run->pushHandler($handlerFour); + + $handlers = $run->getHandlers(); + + $this->assertSame($handlers[0], $handlerOne); + $this->assertSame($handlers[1], $handlerTwo); + $this->assertSame($handlers[2], $handlerThree); + $this->assertSame($handlers[3], $handlerFour); + } + + /** + * @todo possibly split this up a bit and move + * some of this test to Handler unit tests? + * @covers Whoops\Run::handleException + */ + public function testHandlersGonnaHandle() + { + $run = $this->getRunInstance(); + $exception = $this->getException(); + $order = new ArrayObject; + + $handlerOne = $this->getHandler(); + $handlerTwo = $this->getHandler(); + $handlerThree = $this->getHandler(); + + $handlerOne->shouldReceive('handle') + ->andReturnUsing(function() use($order) { $order[] = 1; }); + $handlerTwo->shouldReceive('handle') + ->andReturnUsing(function() use($order) { $order[] = 2; }); + $handlerThree->shouldReceive('handle') + ->andReturnUsing(function() use($order) { $order[] = 3; }); + + $run->pushHandler($handlerOne); + $run->pushHandler($handlerTwo); + $run->pushHandler($handlerThree); + + // Get an exception to be handled, and verify that the handlers + // are given the handler, and in the inverse order they were + // registered. + $run->handleException($exception); + $this->assertEquals((array) $order, array(3, 2, 1)); + } + + /** + * @covers Whoops\Run::handleException + */ + public function testLastHandler() + { + $run = $this->getRunInstance(); + + $handlerOne = $this->getHandler(); + $handlerTwo = $this->getHandler(); + + $run->pushHandler($handlerOne); + $run->pushHandler($handlerTwo); + + $test = $this; + $handlerOne + ->shouldReceive('handle') + ->andReturnUsing(function () use($test) { + $test->fail('$handlerOne should not be called'); + }) + ; + + $handlerTwo + ->shouldReceive('handle') + ->andReturn(Handler::LAST_HANDLER) + ; + + $run->handleException($this->getException()); + } + + /** + * Test error suppression using @ operator. + */ + public function testErrorSuppression() + { + $run = $this->getRunInstance(); + $run->register(); + + $handler = $this->getHandler(); + $run->pushHandler($handler); + + $test = $this; + $handler + ->shouldReceive('handle') + ->andReturnUsing(function () use($test) { + $test->fail('$handler should not be called, error not suppressed'); + }) + ; + + @trigger_error("Test error suppression"); + } + + /** + * Test to make sure that error_reporting is respected. + */ + public function testErrorReporting() + { + $run = $this->getRunInstance(); + $run->register(); + + $handler = $this->getHandler(); + $run->pushHandler($handler); + + $test = $this; + $handler + ->shouldReceive('handle') + ->andReturnUsing(function () use($test) { + $test->fail('$handler should not be called, error_reporting not respected'); + }) + ; + + $oldLevel = error_reporting(E_ALL ^ E_USER_NOTICE); + trigger_error("Test error reporting", E_USER_NOTICE); + error_reporting($oldLevel); + } + + /** + * @covers Whoops\Run::handleException + * @covers Whoops\Run::writeToOutput + */ + public function testOutputIsSent() + { + $run = $this->getRunInstance(); + $run->pushHandler(function() { + echo "hello there"; + }); + + ob_start(); + $run->handleException(new RuntimeException); + $this->assertEquals("hello there", ob_get_clean()); + } + + /** + * @covers Whoops\Run::handleException + * @covers Whoops\Run::writeToOutput + */ + public function testOutputIsNotSent() + { + $run = $this->getRunInstance(); + $run->writeToOutput(false); + $run->pushHandler(function() { + echo "hello there"; + }); + + ob_start(); + $this->assertEquals("hello there", $run->handleException(new RuntimeException)); + $this->assertEquals("", ob_get_clean()); + } +} diff --git a/vendor/filp/whoops/tests/Whoops/TestCase.php b/vendor/filp/whoops/tests/Whoops/TestCase.php new file mode 100644 index 0000000..f47c1a8 --- /dev/null +++ b/vendor/filp/whoops/tests/Whoops/TestCase.php @@ -0,0 +1,22 @@ + + */ + +namespace Whoops; +use Whoops\Run; + +class TestCase extends \PHPUnit_Framework_TestCase +{ + /** + * @return Whoops\Run + */ + protected function getRunInstance() + { + $run = new Run; + $run->allowQuit(false); + + return $run; + } +} diff --git a/vendor/filp/whoops/tests/bootstrap.php b/vendor/filp/whoops/tests/bootstrap.php new file mode 100644 index 0000000..d88d772 --- /dev/null +++ b/vendor/filp/whoops/tests/bootstrap.php @@ -0,0 +1,11 @@ + + * + * Bootstraper for PHPUnit tests. + */ +error_reporting(E_ALL | E_STRICT); +$_ENV['whoops-test'] = true; +$loader = require_once __DIR__ . '/../vendor/autoload.php'; +$loader->add('Whoops\\', __DIR__); diff --git a/vendor/filp/whoops/tests/fixtures/frame.lines-test.php b/vendor/filp/whoops/tests/fixtures/frame.lines-test.php new file mode 100644 index 0000000..687a052 --- /dev/null +++ b/vendor/filp/whoops/tests/fixtures/frame.lines-test.php @@ -0,0 +1,10 @@ += 5.3.7` OR a version that has the `$2y` fix backported into it (such as Debian provides). + +The runtime checks have been removed due to this version issue. To see if password_compat is available for your system, run the included `version-test.php`. If it outputs "Pass", you can safely use the library. If not, you cannot. + +If you attempt to use password-compat on an unsupported version, attempts to create or verify hashes will return `false`. You have been warned! + +The reason for this is that PHP prior to 5.3.7 contains a security issue with its BCRYPT implementation. Therefore, it's highly recommended that you upgrade to a newer version of PHP prior to using this layer. + +Installation +============ + +To install, simply `require` the `password.php` file under `lib`. + +You can also install it via `Composer` by using the [Packagist archive](http://packagist.org/packages/ircmaxell/password-compat). + +Usage +===== + +**Creating Password Hashes** + +To create a password hash from a password, simply use the `password_hash` function. + + $hash = password_hash($password, PASSWORD_BCRYPT); + +Note that the algorithm that we chose is `PASSWORD_BCRYPT`. That's the current strongest algorithm supported. This is the `BCRYPT` crypt algorithm. It produces a 60 character hash as the result. + +`BCRYPT` also allows for you to define a `cost` parameter in the options array. This allows for you to change the CPU cost of the algorithm: + + $hash = password_hash($password, PASSWORD_BCRYPT, ["cost" => 10]); + +That's the same as the default. The cost can range from `4` to `31`. I would suggest that you use the highest cost that you can, while keeping response time reasonable (I target between 0.1 and 0.5 seconds for a hash, depending on use-case). + +Another algorithm name is supported: + + PASSWORD_DEFAULT + +This will use the strongest algorithm available to PHP at the current time. Presently, this is the same as specifying `PASSWORD_BCRYPT`. But in future versions of PHP, it may be updated to use a stronger algorithm if one is introduced. It can also be changed if a problem is identified with the BCRYPT algorithm. Note that if you use this option, you are **strongly** encouraged to store it in a `VARCHAR(255)` column to avoid truncation issues if a future algorithm increases the length of the generated hash. + +It is very important that you should check the return value of `password_hash` prior to storing it, because a `false` may be returned if it encountered an error. + +**Verifying Password Hashes** + +To verify a hash created by `password_hash`, simply call: + + if (password_verify($password, $hash)) { + /* Valid */ + } else { + /* Invalid */ + } + +That's all there is to it. + +**Rehashing Passwords** + +From time to time you may update your hashing parameters (algorithm, cost, etc). So a function to determine if rehashing is necessary is available: + + if (password_verify($password, $hash)) { + if (password_needs_rehash($hash, $algorithm, $options)) { + $hash = password_hash($password, $algorithm, $options); + /* Store new hash in db */ + } + } diff --git a/vendor/ircmaxell/password-compat/composer.json b/vendor/ircmaxell/password-compat/composer.json new file mode 100644 index 0000000..e0d4f14 --- /dev/null +++ b/vendor/ircmaxell/password-compat/composer.json @@ -0,0 +1,18 @@ +{ + "name": "ircmaxell/password-compat", + "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash", + "version": "1.0.3", + "keywords": ["password", "hashing"], + "homepage": "https://github.com/ircmaxell/password_compat", + "license": "MIT", + "authors": [ + { + "name": "Anthony Ferrara", + "email": "ircmaxell@php.net", + "homepage": "http://blog.ircmaxell.com" + } + ], + "autoload": { + "files": ["lib/password.php"] + } +} \ No newline at end of file diff --git a/vendor/ircmaxell/password-compat/lib/password.php b/vendor/ircmaxell/password-compat/lib/password.php new file mode 100644 index 0000000..4d0e8b7 --- /dev/null +++ b/vendor/ircmaxell/password-compat/lib/password.php @@ -0,0 +1,222 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @copyright 2012 The Authors + */ + +if (!defined('PASSWORD_BCRYPT')) { + + define('PASSWORD_BCRYPT', 1); + define('PASSWORD_DEFAULT', PASSWORD_BCRYPT); + + /** + * Hash the password using the specified algorithm + * + * @param string $password The password to hash + * @param int $algo The algorithm to use (Defined by PASSWORD_* constants) + * @param array $options The options for the algorithm to use + * + * @return string|false The hashed password, or false on error. + */ + function password_hash($password, $algo, array $options = array()) { + if (!function_exists('crypt')) { + trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING); + return null; + } + if (!is_string($password)) { + trigger_error("password_hash(): Password must be a string", E_USER_WARNING); + return null; + } + if (!is_int($algo)) { + trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING); + return null; + } + switch ($algo) { + case PASSWORD_BCRYPT: + // Note that this is a C constant, but not exposed to PHP, so we don't define it here. + $cost = 10; + if (isset($options['cost'])) { + $cost = $options['cost']; + if ($cost < 4 || $cost > 31) { + trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING); + return null; + } + } + // The length of salt to generate + $raw_salt_len = 16; + // The length required in the final serialization + $required_salt_len = 22; + $hash_format = sprintf("$2y$%02d$", $cost); + break; + default: + trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING); + return null; + } + if (isset($options['salt'])) { + switch (gettype($options['salt'])) { + case 'NULL': + case 'boolean': + case 'integer': + case 'double': + case 'string': + $salt = (string) $options['salt']; + break; + case 'object': + if (method_exists($options['salt'], '__tostring')) { + $salt = (string) $options['salt']; + break; + } + case 'array': + case 'resource': + default: + trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING); + return null; + } + if (strlen($salt) < $required_salt_len) { + trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING); + return null; + } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) { + $salt = str_replace('+', '.', base64_encode($salt)); + } + } else { + $buffer = ''; + $buffer_valid = false; + if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) { + $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM); + if ($buffer) { + $buffer_valid = true; + } + } + if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) { + $buffer = openssl_random_pseudo_bytes($raw_salt_len); + if ($buffer) { + $buffer_valid = true; + } + } + if (!$buffer_valid && is_readable('/dev/urandom')) { + $f = fopen('/dev/urandom', 'r'); + $read = strlen($buffer); + while ($read < $raw_salt_len) { + $buffer .= fread($f, $raw_salt_len - $read); + $read = strlen($buffer); + } + fclose($f); + if ($read >= $raw_salt_len) { + $buffer_valid = true; + } + } + if (!$buffer_valid || strlen($buffer) < $raw_salt_len) { + $bl = strlen($buffer); + for ($i = 0; $i < $raw_salt_len; $i++) { + if ($i < $bl) { + $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255)); + } else { + $buffer .= chr(mt_rand(0, 255)); + } + } + } + $salt = str_replace('+', '.', base64_encode($buffer)); + } + $salt = substr($salt, 0, $required_salt_len); + + $hash = $hash_format . $salt; + + $ret = crypt($password, $hash); + + if (!is_string($ret) || strlen($ret) <= 13) { + return false; + } + + return $ret; + } + + /** + * Get information about the password hash. Returns an array of the information + * that was used to generate the password hash. + * + * array( + * 'algo' => 1, + * 'algoName' => 'bcrypt', + * 'options' => array( + * 'cost' => 10, + * ), + * ) + * + * @param string $hash The password hash to extract info from + * + * @return array The array of information about the hash. + */ + function password_get_info($hash) { + $return = array( + 'algo' => 0, + 'algoName' => 'unknown', + 'options' => array(), + ); + if (substr($hash, 0, 4) == '$2y$' && strlen($hash) == 60) { + $return['algo'] = PASSWORD_BCRYPT; + $return['algoName'] = 'bcrypt'; + list($cost) = sscanf($hash, "$2y$%d$"); + $return['options']['cost'] = $cost; + } + return $return; + } + + /** + * Determine if the password hash needs to be rehashed according to the options provided + * + * If the answer is true, after validating the password using password_verify, rehash it. + * + * @param string $hash The hash to test + * @param int $algo The algorithm used for new password hashes + * @param array $options The options array passed to password_hash + * + * @return boolean True if the password needs to be rehashed. + */ + function password_needs_rehash($hash, $algo, array $options = array()) { + $info = password_get_info($hash); + if ($info['algo'] != $algo) { + return true; + } + switch ($algo) { + case PASSWORD_BCRYPT: + $cost = isset($options['cost']) ? $options['cost'] : 10; + if ($cost != $info['options']['cost']) { + return true; + } + break; + } + return false; + } + + /** + * Verify a password against a hash using a timing attack resistant approach + * + * @param string $password The password to verify + * @param string $hash The hash to verify against + * + * @return boolean If the password matches the hash + */ + function password_verify($password, $hash) { + if (!function_exists('crypt')) { + trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING); + return false; + } + $ret = crypt($password, $hash); + if (!is_string($ret) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) { + return false; + } + + $status = 0; + for ($i = 0; $i < strlen($ret); $i++) { + $status |= (ord($ret[$i]) ^ ord($hash[$i])); + } + + return $status === 0; + } +} + + + diff --git a/vendor/ircmaxell/password-compat/phpunit.xml.dist b/vendor/ircmaxell/password-compat/phpunit.xml.dist new file mode 100644 index 0000000..b2b3afb --- /dev/null +++ b/vendor/ircmaxell/password-compat/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + test/Unit + + + + + lib/ + + + diff --git a/vendor/ircmaxell/password-compat/test/Unit/PasswordGetInfoTest.php b/vendor/ircmaxell/password-compat/test/Unit/PasswordGetInfoTest.php new file mode 100644 index 0000000..6aab976 --- /dev/null +++ b/vendor/ircmaxell/password-compat/test/Unit/PasswordGetInfoTest.php @@ -0,0 +1,26 @@ + 0, 'algoName' => 'unknown', 'options' => array())), + array('$2y$', array('algo' => 0, 'algoName' => 'unknown', 'options' => array())), + array('$2y$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi', array('algo' => PASSWORD_BCRYPT, 'algoName' => 'bcrypt', 'options' => array('cost' => 7))), + array('$2y$10$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi', array('algo' => PASSWORD_BCRYPT, 'algoName' => 'bcrypt', 'options' => array('cost' => 10))), + + ); + } + + public function testFuncExists() { + $this->assertTrue(function_exists('password_get_info')); + } + + /** + * @dataProvider provideInfo + */ + public function testInfo($hash, $info) { + $this->assertEquals($info, password_get_info($hash)); + } + +} \ No newline at end of file diff --git a/vendor/ircmaxell/password-compat/test/Unit/PasswordHashTest.php b/vendor/ircmaxell/password-compat/test/Unit/PasswordHashTest.php new file mode 100644 index 0000000..9e5e9ec --- /dev/null +++ b/vendor/ircmaxell/password-compat/test/Unit/PasswordHashTest.php @@ -0,0 +1,84 @@ +assertTrue(function_exists('password_hash')); + } + + public function testStringLength() { + $this->assertEquals(60, strlen(password_hash('foo', PASSWORD_BCRYPT))); + } + + public function testHash() { + $hash = password_hash('foo', PASSWORD_BCRYPT); + $this->assertEquals($hash, crypt('foo', $hash)); + } + + public function testKnownSalt() { + $hash = password_hash("rasmuslerdorf", PASSWORD_BCRYPT, array("cost" => 7, "salt" => "usesomesillystringforsalt")); + $this->assertEquals('$2y$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi', $hash); + } + + public function testRawSalt() { + $hash = password_hash("test", PASSWORD_BCRYPT, array("salt" => "123456789012345678901" . chr(0))); + $this->assertEquals('$2y$10$MTIzNDU2Nzg5MDEyMzQ1Nej0NmcAWSLR.oP7XOR9HD/vjUuOj100y', $hash); + } + + /** + * @expectedException PHPUnit_Framework_Error + */ + public function testInvalidAlgo() { + password_hash('foo', array()); + } + + /** + * @expectedException PHPUnit_Framework_Error + */ + public function testInvalidAlgo2() { + password_hash('foo', 2); + } + + /** + * @expectedException PHPUnit_Framework_Error + */ + public function testInvalidPassword() { + password_hash(array(), 1); + } + + /** + * @expectedException PHPUnit_Framework_Error + */ + public function testInvalidSalt() { + password_hash('foo', PASSWORD_BCRYPT, array('salt' => array())); + } + + /** + * @expectedException PHPUnit_Framework_Error + */ + public function testInvalidBcryptCostLow() { + password_hash('foo', PASSWORD_BCRYPT, array('cost' => 3)); + } + + /** + * @expectedException PHPUnit_Framework_Error + */ + public function testInvalidBcryptCostHigh() { + password_hash('foo', PASSWORD_BCRYPT, array('cost' => 32)); + } + + /** + * @expectedException PHPUnit_Framework_Error + */ + public function testInvalidBcryptCostInvalid() { + password_hash('foo', PASSWORD_BCRYPT, array('cost' => 'foo')); + } + + /** + * @expectedException PHPUnit_Framework_Error + */ + public function testInvalidBcryptSaltShort() { + password_hash('foo', PASSWORD_BCRYPT, array('salt' => 'abc')); + } + +} \ No newline at end of file diff --git a/vendor/ircmaxell/password-compat/test/Unit/PasswordNeedsRehashTest.php b/vendor/ircmaxell/password-compat/test/Unit/PasswordNeedsRehashTest.php new file mode 100644 index 0000000..c2932dc --- /dev/null +++ b/vendor/ircmaxell/password-compat/test/Unit/PasswordNeedsRehashTest.php @@ -0,0 +1,26 @@ + 7), false), + array('$2y$07$usesomesillystringfore2udlvp1ii2e./u9c8sbjqp8i90dh6hi', PASSWORD_BCRYPT, array('cost' => 5), true), + ); + } + + public function testFuncExists() { + $this->assertTrue(function_exists('password_needs_rehash')); + } + + /** + * @dataProvider provideCases + */ + public function testCases($hash, $algo, $options, $valid) { + $this->assertEquals($valid, password_needs_rehash($hash, $algo, $options)); + } + +} \ No newline at end of file diff --git a/vendor/ircmaxell/password-compat/test/Unit/PasswordVerifyTest.php b/vendor/ircmaxell/password-compat/test/Unit/PasswordVerifyTest.php new file mode 100644 index 0000000..9f67bb9 --- /dev/null +++ b/vendor/ircmaxell/password-compat/test/Unit/PasswordVerifyTest.php @@ -0,0 +1,29 @@ +assertTrue(function_exists('password_verify')); + } + + public function testFailedType() { + $this->assertFalse(password_verify(123, 123)); + } + + public function testSaltOnly() { + $this->assertFalse(password_verify('foo', '$2a$07$usesomesillystringforsalt$')); + } + + public function testInvalidPassword() { + $this->assertFalse(password_verify('rasmusler', '$2a$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi')); + } + + public function testValidPassword() { + $this->assertTrue(password_verify('rasmuslerdorf', '$2a$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hi')); + } + + public function testInValidHash() { + $this->assertFalse(password_verify('rasmuslerdorf', '$2a$07$usesomesillystringfore2uDLvp1Ii2e./U9C8sBjqp8I90dH6hj')); + } + +} \ No newline at end of file diff --git a/vendor/ircmaxell/password-compat/version-test.php b/vendor/ircmaxell/password-compat/version-test.php new file mode 100644 index 0000000..f527e30 --- /dev/null +++ b/vendor/ircmaxell/password-compat/version-test.php @@ -0,0 +1,8 @@ +=5.3.0", + "classpreloader/classpreloader": "1.0.*", + "doctrine/dbal": "2.4.x", + "ircmaxell/password-compat": "1.0.*", + "filp/whoops": "1.0.7", + "monolog/monolog": "1.6.*", + "nesbot/carbon": "1.*", + "patchwork/utf8": "1.1.*", + "predis/predis": "0.8.*", + "swiftmailer/swiftmailer": "5.0.*", + "symfony/browser-kit": "2.3.*", + "symfony/console": "2.3.*", + "symfony/css-selector": "2.3.*", + "symfony/debug": "2.3.*", + "symfony/dom-crawler": "2.3.*", + "symfony/event-dispatcher": "2.3.*", + "symfony/finder": "2.3.*", + "symfony/http-foundation": "2.3.*", + "symfony/http-kernel": "2.3.*", + "symfony/process": "2.3.*", + "symfony/routing": "2.3.*", + "symfony/translation": "2.3.*" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/cache": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/exception": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/foundation": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/html": "self.version", + "illuminate/log": "self.version", + "illuminate/mail": "self.version", + "illuminate/pagination": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version", + "illuminate/workbench": "self.version" + }, + "require-dev": { + "aws/aws-sdk-php": "2.2.*", + "iron-io/iron_mq": "1.4.4", + "pda/pheanstalk": "2.0.*", + "mockery/mockery": "0.7.2", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "classmap": [ + ["src/Illuminate/Queue/IlluminateQueueClosure.php"] + ], + "files": [ + "src/Illuminate/Support/helpers.php" + ], + "psr-0": { + "Illuminate": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/laravel/framework/phpunit.php b/vendor/laravel/framework/phpunit.php new file mode 100755 index 0000000..f6e3fc6 --- /dev/null +++ b/vendor/laravel/framework/phpunit.php @@ -0,0 +1,28 @@ + + + + + ./tests/ + + + + + + src + + vendor + + + + \ No newline at end of file diff --git a/vendor/laravel/framework/readme.md b/vendor/laravel/framework/readme.md new file mode 100755 index 0000000..35227db --- /dev/null +++ b/vendor/laravel/framework/readme.md @@ -0,0 +1,5 @@ +## Laravel Framework (Core) + +[![Latest Stable Version](https://poser.pugx.org/laravel/framework/version.png)](https://packagist.org/packages/laravel/framework) [![Total Downloads](https://poser.pugx.org/laravel/framework/d/total.png)](https://packagist.org/packages/laravel/framework) [![Build Status](https://travis-ci.org/laravel/framework.png)](https://travis-ci.org/laravel/framework) + +This repository contains the core code of the Laravel framework. If you want to build an application using Laravel 4, visit the main [Laravel repository](https://github.com/laravel/laravel). \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Auth/AuthManager.php b/vendor/laravel/framework/src/Illuminate/Auth/AuthManager.php new file mode 100755 index 0000000..a77cafd --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Auth/AuthManager.php @@ -0,0 +1,105 @@ +setCookieJar($this->app['cookie']); + + $guard->setDispatcher($this->app['events']); + + return $guard->setRequest($this->app['request']); + } + + /** + * Call a custom driver creator. + * + * @param string $driver + * @return mixed + */ + protected function callCustomCreator($driver) + { + $custom = parent::callCustomCreator($driver); + + if ($custom instanceof Guard) return $custom; + + return new Guard($custom, $this->app['session']); + } + + /** + * Create an instance of the database driver. + * + * @return \Illuminate\Auth\Guard + */ + protected function createDatabaseDriver() + { + $provider = $this->createDatabaseProvider(); + + return new Guard($provider, $this->app['session']); + } + + /** + * Create an instance of the database user provider. + * + * @return \Illuminate\Auth\DatabaseUserProvider + */ + protected function createDatabaseProvider() + { + $connection = $this->app['db']->connection(); + + // When using the basic database user provider, we need to inject the table we + // want to use, since this is not an Eloquent model we will have no way to + // know without telling the provider, so we'll inject the config value. + $table = $this->app['config']['auth.table']; + + return new DatabaseUserProvider($connection, $this->app['hash'], $table); + } + + /** + * Create an instance of the Eloquent driver. + * + * @return \Illuminate\Auth\Guard + */ + public function createEloquentDriver() + { + $provider = $this->createEloquentProvider(); + + return new Guard($provider, $this->app['session']); + } + + /** + * Create an instance of the Eloquent user provider. + * + * @return \Illuminate\Auth\EloquentUserProvider + */ + protected function createEloquentProvider() + { + $model = $this->app['config']['auth.model']; + + return new EloquentUserProvider($this->app['hash'], $model); + } + + /** + * Get the default authentication driver name. + * + * @return string + */ + protected function getDefaultDriver() + { + return $this->app['config']['auth.driver']; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Auth/AuthServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Auth/AuthServiceProvider.php new file mode 100755 index 0000000..795d652 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Auth/AuthServiceProvider.php @@ -0,0 +1,79 @@ +registerAuthEvents(); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->app['auth'] = $this->app->share(function($app) + { + // Once the authentication service has actually been requested by the developer + // we will set a variable in the application indicating such. This helps us + // know that we need to set any queued cookies in the after event later. + $app['auth.loaded'] = true; + + return new AuthManager($app); + }); + } + + /** + * Register the events needed for authentication. + * + * @return void + */ + protected function registerAuthEvents() + { + $app = $this->app; + + $app->after(function($request, $response) use ($app) + { + // If the authentication service has been used, we'll check for any cookies + // that may be queued by the service. These cookies are all queued until + // they are attached onto Response objects at the end of the requests. + if (isset($app['auth.loaded'])) + { + foreach ($app['auth']->getDrivers() as $driver) + { + foreach ($driver->getQueuedCookies() as $cookie) + { + $response->headers->setCookie($cookie); + } + } + } + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('auth'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Auth/Console/MakeRemindersCommand.php b/vendor/laravel/framework/src/Illuminate/Auth/Console/MakeRemindersCommand.php new file mode 100755 index 0000000..8a883d8 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Auth/Console/MakeRemindersCommand.php @@ -0,0 +1,96 @@ +files = $files; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $fullPath = $this->createBaseMigration(); + + $this->files->put($fullPath, $this->getMigrationStub()); + + $this->info('Migration created successfully!'); + + $this->call('dump-autoload'); + } + + /** + * Create a base migration file for the reminders. + * + * @return string + */ + protected function createBaseMigration() + { + $name = 'create_password_reminders_table'; + + $path = $this->laravel['path'].'/database/migrations'; + + return $this->laravel['migration.creator']->create($name, $path); + } + + /** + * Get the contents of the reminder migration stub. + * + * @return string + */ + protected function getMigrationStub() + { + $stub = $this->files->get(__DIR__.'/stubs/reminders.stub'); + + return str_replace('password_reminders', $this->getTable(), $stub); + } + + /** + * Get the password reminder table name. + * + * @return string + */ + protected function getTable() + { + return $this->laravel['config']->get('auth.reminder.table'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/reminders.stub b/vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/reminders.stub new file mode 100644 index 0000000..ac68f8b --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/reminders.stub @@ -0,0 +1,32 @@ +string('email'); + $t->string('token'); + $t->timestamp('created_at'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('password_reminders'); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Auth/DatabaseUserProvider.php b/vendor/laravel/framework/src/Illuminate/Auth/DatabaseUserProvider.php new file mode 100755 index 0000000..a25f516 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Auth/DatabaseUserProvider.php @@ -0,0 +1,106 @@ +conn = $conn; + $this->table = $table; + $this->hasher = $hasher; + } + + /** + * Retrieve a user by their unique identifier. + * + * @param mixed $identifier + * @return \Illuminate\Auth\UserInterface|null + */ + public function retrieveById($identifier) + { + $user = $this->conn->table($this->table)->find($identifier); + + if ( ! is_null($user)) + { + return new GenericUser((array) $user); + } + } + + /** + * Retrieve a user by the given credentials. + * + * @param array $credentials + * @return \Illuminate\Auth\UserInterface|null + */ + public function retrieveByCredentials(array $credentials) + { + // First we will add each credential element to the query as a where clause. + // Then we can execute the query and, if we found a user, return it in a + // generic "user" object that will be utilized by the Guard instances. + $query = $this->conn->table($this->table); + + foreach ($credentials as $key => $value) + { + if ( ! str_contains($key, 'password')) + { + $query->where($key, $value); + } + } + + // Now we are ready to execute the query to see if we have an user matching + // the given credentials. If not, we will just return nulls and indicate + // that there are no matching users for these given credential arrays. + $user = $query->first(); + + if ( ! is_null($user)) + { + return new GenericUser((array) $user); + } + } + + /** + * Validate a user against the given credentials. + * + * @param \Illuminate\Auth\UserInterface $user + * @param array $credentials + * @return bool + */ + public function validateCredentials(UserInterface $user, array $credentials) + { + $plain = $credentials['password']; + + return $this->hasher->check($plain, $user->getAuthPassword()); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Auth/EloquentUserProvider.php b/vendor/laravel/framework/src/Illuminate/Auth/EloquentUserProvider.php new file mode 100755 index 0000000..de66132 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Auth/EloquentUserProvider.php @@ -0,0 +1,92 @@ +model = $model; + $this->hasher = $hasher; + } + + /** + * Retrieve a user by their unique identifier. + * + * @param mixed $identifier + * @return \Illuminate\Auth\UserInterface|null + */ + public function retrieveById($identifier) + { + return $this->createModel()->newQuery()->find($identifier); + } + + /** + * Retrieve a user by the given credentials. + * + * @param array $credentials + * @return \Illuminate\Auth\UserInterface|null + */ + public function retrieveByCredentials(array $credentials) + { + // First we will add each credential element to the query as a where clause. + // Then we can execute the query and, if we found a user, return it in a + // Eloquent User "model" that will be utilized by the Guard instances. + $query = $this->createModel()->newQuery(); + + foreach ($credentials as $key => $value) + { + if ( ! str_contains($key, 'password')) $query->where($key, $value); + } + + return $query->first(); + } + + /** + * Validate a user against the given credentials. + * + * @param \Illuminate\Auth\UserInterface $user + * @param array $credentials + * @return bool + */ + public function validateCredentials(UserInterface $user, array $credentials) + { + $plain = $credentials['password']; + + return $this->hasher->check($plain, $user->getAuthPassword()); + } + + /** + * Create a new instance of the model. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function createModel() + { + $class = '\\'.ltrim($this->model, '\\'); + + return new $class; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Auth/GenericUser.php b/vendor/laravel/framework/src/Illuminate/Auth/GenericUser.php new file mode 100755 index 0000000..ec9215b --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Auth/GenericUser.php @@ -0,0 +1,86 @@ +attributes = $attributes; + } + + /** + * Get the unique identifier for the user. + * + * @return mixed + */ + public function getAuthIdentifier() + { + return $this->attributes['id']; + } + + /** + * Get the password for the user. + * + * @return string + */ + public function getAuthPassword() + { + return $this->attributes['password']; + } + + /** + * Dynamically access the user's attributes. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->attributes[$key]; + } + + /** + * Dynamically set an attribute on the user. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this->attributes[$key] = $value; + } + + /** + * Dynamically check if a value is set on the user. + * + * @return bool + */ + public function __isset($key) + { + return isset($this->attributes[$key]); + } + + /** + * Dynamically unset a value on the user. + * + * @return bool + */ + public function __unset($key) + { + unset($this->attributes[$key]); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Auth/Guard.php b/vendor/laravel/framework/src/Illuminate/Auth/Guard.php new file mode 100755 index 0000000..e26d343 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Auth/Guard.php @@ -0,0 +1,589 @@ +session = $session; + $this->provider = $provider; + } + + /** + * Determine if the current user is authenticated. + * + * @return bool + */ + public function check() + { + return ! is_null($this->user()); + } + + /** + * Determine if the current user is a guest. + * + * @return bool + */ + public function guest() + { + return is_null($this->user()); + } + + /** + * Get the currently authenticated user. + * + * @return \Illuminate\Auth\UserInterface|null + */ + public function user() + { + if ($this->loggedOut) return; + + // If we have already retrieved the user for the current request we can just + // return it back immediately. We do not want to pull the user data every + // request into the method becaue that would tremendously slow the app. + if ( ! is_null($this->user)) + { + return $this->user; + } + + $id = $this->session->get($this->getName()); + + // First we will try to load the user using the identifier in the session if + // one exists. Otherwise we will check for a "remember me" cookie in this + // request, and if one exists, attempt to retrieve the user using that. + $user = null; + + if ( ! is_null($id)) + { + $user = $this->provider->retrieveByID($id); + } + + // If the user is null, but we decrypt a "recaller" cookie we can attempt to + // pull the user data on that cookie which serves as a remember cookie on + // the application. Once we have a user we can return it to the caller. + $recaller = $this->getRecaller(); + + if (is_null($user) and ! is_null($recaller)) + { + $user = $this->provider->retrieveByID($recaller); + } + + return $this->user = $user; + } + + /** + * Get the decrypted recaller cookie for the request. + * + * @return string|null + */ + protected function getRecaller() + { + if (isset($this->cookie)) + { + return $this->getCookieJar()->get($this->getRecallerName()); + } + } + + /** + * Log a user into the application without sessions or cookies. + * + * @param array $credentials + * @return bool + */ + public function once(array $credentials = array()) + { + if ($this->validate($credentials)) + { + $this->setUser($this->provider->retrieveByCredentials($credentials)); + + return true; + } + + return false; + } + + /** + * Validate a user's credentials. + * + * @param array $credentials + * @return bool + */ + public function validate(array $credentials = array()) + { + return $this->attempt($credentials, false, false); + } + + /** + * Attempt to authenticate using HTTP Basic Auth. + * + * @param string $field + * @param \Symfony\Component\HttpFoundation\Request $request + * @return \Symfony\Component\HttpFoundation\Response|null + */ + public function basic($field = 'email', Request $request = null) + { + if ($this->check()) return; + + $request = $request ?: $this->getRequest(); + + // If a username is set on the HTTP basic request, we will return out without + // interrupting the request lifecycle. Otherwise, we'll need to generate a + // request indicating that the given credentials were invalid for login. + if ($this->attemptBasic($request, $field)) return; + + + return $this->getBasicResponse(); + } + + /** + * Perform a stateless HTTP Basic login attempt. + * + * @param string $field + * @param \Symfony\Component\HttpFoundation\Request $request + * @return \Symfony\Component\HttpFoundation\Response|null + */ + public function onceBasic($field = 'email', Request $request = null) + { + $request = $request ?: $this->getRequest(); + + if ( ! $this->once($this->getBasicCredentials($request, $field))) + { + return $this->getBasicResponse(); + } + } + + /** + * Attempt to authenticate using basic authentication. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param string $field + * @return bool + */ + protected function attemptBasic(Request $request, $field) + { + if ( ! $request->getUser()) return false; + + return $this->attempt($this->getBasicCredentials($request, $field)); + } + + /** + * Get the credential array for a HTTP Basic request. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param string $field + * @return array + */ + protected function getBasicCredentials(Request $request, $field) + { + return array($field => $request->getUser(), 'password' => $request->getPassword()); + } + + /** + * Get the response for basic authentication. + * + * @return \Symfony\Component\HttpFoundation\Response + */ + protected function getBasicResponse() + { + $headers = array('WWW-Authenticate' => 'Basic'); + + return new Response('Invalid credentials.', 401, $headers); + } + + /** + * Attempt to authenticate a user using the given credentials. + * + * @param array $credentials + * @param bool $remember + * @param bool $login + * @return bool + */ + public function attempt(array $credentials = array(), $remember = false, $login = true) + { + $this->fireAttemptEvent($credentials, $remember, $login); + + $user = $this->provider->retrieveByCredentials($credentials); + + // If an implementation of UserInterface was returned, we'll ask the provider + // to validate the user against the given credentials, and if they are in + // fact valid we'll log the users into the application and return true. + if ($user instanceof UserInterface) + { + if ($this->provider->validateCredentials($user, $credentials)) + { + if ($login) $this->login($user, $remember); + + return true; + } + } + + return false; + } + + /** + * Fire the attempt event with the arguments. + * + * @param array $credentials + * @param bool $remember + * @param bool $login + * @return void + */ + protected function fireAttemptEvent(array $credentials, $remember, $login) + { + if ($this->events) + { + $payload = array($credentials, $remember, $login); + + $this->events->fire('auth.attempt', $payload); + } + } + + /** + * Register an authentication attempt event listener. + * + * @param mixed $callback + * @return void + */ + public function attempting($callback) + { + if ($this->events) + { + $this->events->listen('auth.attempt', $callback); + } + } + + /** + * Log a user into the application. + * + * @param \Illuminate\Auth\UserInterface $user + * @param bool $remember + * @return void + */ + public function login(UserInterface $user, $remember = false) + { + $id = $user->getAuthIdentifier(); + + $this->session->put($this->getName(), $id); + + // If the user should be permanently "remembered" by the application we will + // queue a permanent cookie that contains the encrypted copy of the user + // identifier. We will then decrypt this later to retrieve the users. + if ($remember) + { + $this->queuedCookies[] = $this->createRecaller($id); + } + + // If we have an event dispatcher instance set we will fire an event so that + // any listeners will hook into the authentication events and run actions + // based on the login and logout events fired from the guard instances. + if (isset($this->events)) + { + $this->events->fire('auth.login', array($user, $remember)); + } + + $this->setUser($user); + } + + /** + * Log the given user ID into the application. + * + * @param mixed $id + * @param bool $remember + * @return \Illuminate\Auth\UserInterface + */ + public function loginUsingId($id, $remember = false) + { + $this->session->put($this->getName(), $id); + + return $this->login($this->provider->retrieveById($id), $remember); + } + + /** + * Log the given user ID into the application without sessions or cookies. + * + * @param mixed $id + * @return bool + */ + public function onceUsingId($id) + { + $this->setUser($this->provider->retrieveById($id)); + + return $this->user instanceof UserInterface; + } + + /** + * Create a remember me cookie for a given ID. + * + * @param mixed $id + * @return \Symfony\Component\HttpFoundation\Cookie + */ + protected function createRecaller($id) + { + return $this->getCookieJar()->forever($this->getRecallerName(), $id); + } + + /** + * Log the user out of the application. + * + * @return void + */ + public function logout() + { + $user = $this->user(); + + // If we have an event dispatcher instance, we can fire off the logout event + // so any further processing can be done. This allows the developer to be + // listening for anytime a user signs out of this application manually. + $this->clearUserDataFromStorage(); + + if (isset($this->events)) + { + $this->events->fire('auth.logout', array($user)); + } + + // Once we have fired the logout event we will clear the users out of memory + // so they are no longer available as the user is no longer considered as + // being signed into this application and should not be available here. + $this->user = null; + + $this->loggedOut = true; + } + + /** + * Remove the user data from the session and cookies. + * + * @return void + */ + protected function clearUserDataFromStorage() + { + $this->session->forget($this->getName()); + + $recaller = $this->getRecallerName(); + + $this->queuedCookies[] = $this->getCookieJar()->forget($recaller); + } + + /** + * Get the cookies queued by the guard. + * + * @return array + */ + public function getQueuedCookies() + { + return $this->queuedCookies; + } + + /** + * Get the cookie creator instance used by the guard. + * + * @return \Illuminate\Cookie\CookieJar + */ + public function getCookieJar() + { + if ( ! isset($this->cookie)) + { + throw new \RuntimeException("Cookie jar has not been set."); + } + + return $this->cookie; + } + + /** + * Set the cookie creator instance used by the guard. + * + * @param \Illuminate\Cookie\CookieJar $cookie + * @return void + */ + public function setCookieJar(CookieJar $cookie) + { + $this->cookie = $cookie; + } + + /** + * Get the event dispatcher instance. + * + * @return \Illuminate\Events\Dispatcher + */ + public function getDispatcher() + { + return $this->events; + } + + /** + * Set the event dispatcher instance. + * + * @param \Illuminate\Events\Dispatcher + */ + public function setDispatcher(Dispatcher $events) + { + $this->events = $events; + } + + /** + * Get the session store used by the guard. + * + * @return \Illuminate\Session\Store + */ + public function getSession() + { + return $this->session; + } + + /** + * Get the user provider used by the guard. + * + * @return \Illuminate\Auth\UserProviderInterface + */ + public function getProvider() + { + return $this->provider; + } + + /** + * Set the user provider used by the guard. + * + * @param \Illuminate\Auth\UserProviderInterface $provider + * @return void + */ + public function setProvider(UserProviderInterface $provider) + { + $this->provider = $provider; + } + + /** + * Return the currently cached user of the application. + * + * @return \Illuminate\Auth\UserInterface|null + */ + public function getUser() + { + return $this->user; + } + + /** + * Set the current user of the application. + * + * @param \Illuminate\Auth\UserInterface $user + * @return void + */ + public function setUser(UserInterface $user) + { + $this->user = $user; + + $this->loggedOut = false; + } + + /** + * Get the current request instance. + * + * @return \Symfony\Component\HttpFoundation\Request + */ + public function getRequest() + { + return $this->request ?: Request::createFromGlobals(); + } + + /** + * Set the current request instance. + * + * @param \Symfony\Component\HttpFoundation\Request + * @return \Illuminate\Auth\Guard + */ + public function setRequest(Request $request) + { + $this->request = $request; + + return $this; + } + + /** + * Get a unique identifier for the auth session value. + * + * @return string + */ + public function getName() + { + return 'login_'.md5(get_class($this)); + } + + /** + * Get the name of the cookie used to store the "recaller". + * + * @return string + */ + public function getRecallerName() + { + return 'remember_'.md5(get_class($this)); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Auth/Reminders/DatabaseReminderRepository.php b/vendor/laravel/framework/src/Illuminate/Auth/Reminders/DatabaseReminderRepository.php new file mode 100755 index 0000000..c8f3488 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Auth/Reminders/DatabaseReminderRepository.php @@ -0,0 +1,170 @@ +table = $table; + $this->hashKey = $hashKey; + $this->expires = $expires * 60; + $this->connection = $connection; + } + + /** + * Create a new reminder record and token. + * + * @param \Illuminate\Auth\RemindableInterface $user + * @return string + */ + public function create(RemindableInterface $user) + { + $email = $user->getReminderEmail(); + + // We will create a new, random token for the user so that we can e-mail them + // a safe link to the password reset form. Then we will insert a record in + // the database so that we can verify the token within the actual reset. + $token = $this->createNewToken($user); + + $this->getTable()->insert($this->getPayload($email, $token)); + + return $token; + } + + /** + * Build the record payload for the table. + * + * @param string $email + * @param string $token + * @return array + */ + protected function getPayload($email, $token) + { + return array('email' => $email, 'token' => $token, 'created_at' => new DateTime); + } + + /** + * Determine if a reminder record exists and is valid. + * + * @param \Illuminate\Auth\RemindableInterface $user + * @param string $token + * @return bool + */ + public function exists(RemindableInterface $user, $token) + { + $email = $user->getReminderEmail(); + + $reminder = $this->getTable()->where('email', $email)->where('token', $token)->first(); + + return $reminder and ! $this->reminderExpired($reminder); + } + + /** + * Determine if the reminder has expired. + * + * @param StdClass $reminder + * @return bool + */ + protected function reminderExpired($reminder) + { + $createdPlusHour = strtotime($reminder->created_at) + $this->expires; + + return $createdPlusHour < $this->getCurrentTime(); + } + + /** + * Get the current UNIX timestamp. + * + * @return int + */ + protected function getCurrentTime() + { + return time(); + } + + /** + * Delete a reminder record by token. + * + * @param string $token + * @return void + */ + public function delete($token) + { + $this->getTable()->where('token', $token)->delete(); + } + + /** + * Create a new token for the user. + * + * @param \Illuminate\Auth\RemindableInterface $user + * @return string + */ + public function createNewToken(RemindableInterface $user) + { + $email = $user->getReminderEmail(); + + $value = str_shuffle(sha1($email.spl_object_hash($this).microtime(true))); + + return hash_hmac('sha1', $value, $this->hashKey); + } + + /** + * Begin a new database query against the table. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function getTable() + { + return $this->connection->table($this->table); + } + + /** + * Get the database connection instance. + * + * @return \Illuminate\Database\Connection + */ + public function getConnection() + { + return $this->connection; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Auth/Reminders/PasswordBroker.php b/vendor/laravel/framework/src/Illuminate/Auth/Reminders/PasswordBroker.php new file mode 100755 index 0000000..a873774 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Auth/Reminders/PasswordBroker.php @@ -0,0 +1,262 @@ +users = $users; + $this->mailer = $mailer; + $this->redirect = $redirect; + $this->reminders = $reminders; + $this->reminderView = $reminderView; + } + + /** + * Send a password reminder to a user. + * + * @param array $credentials + * @param Closure $callback + * @return \Illuminate\Http\RedirectResponse + */ + public function remind(array $credentials, Closure $callback = null) + { + // First we will check to see if we found a user at the given credentials and + // if we did not we will redirect back to this current URI with a piece of + // "flash" data in the session to indicate to the developers the errors. + $user = $this->getUser($credentials); + + if (is_null($user)) + { + return $this->makeErrorRedirect('user'); + } + + // Once we have the reminder token, we are ready to send a message out to the + // user with a link to reset their password. We will then redirect back to + // the current URI having nothing set in the session to indicate errors. + $token = $this->reminders->create($user); + + $this->sendReminder($user, $token, $callback); + + return $this->redirect->refresh()->with('success', true); + } + + /** + * Send the password reminder e-mail. + * + * @param \Illuminate\Auth\Reminders\RemindableInterface $user + * @param string $token + * @param Closure $callback + * @return void + */ + public function sendReminder(RemindableInterface $user, $token, Closure $callback = null) + { + // We will use the reminder view that was given to the broker to display the + // password reminder e-mail. We'll pass a "token" variable into the views + // so that it may be displayed for an user to click for password reset. + $view = $this->reminderView; + + return $this->mailer->send($view, compact('token', 'user'), function($m) use ($user, $callback) + { + $m->to($user->getReminderEmail()); + + if ( ! is_null($callback)) call_user_func($callback, $m, $user); + }); + } + + /** + * Reset the password for the given token. + * + * @param array $credentials + * @param Closure $callback + * @return mixed + */ + public function reset(array $credentials, Closure $callback) + { + // If the responses from the validate method is not a user instance, we will + // assume that it is a redirect and simply return it from this method and + // the user is properly redirected having an error message on the post. + $user = $this->validateReset($credentials); + + if ( ! $user instanceof RemindableInterface) + { + return $user; + } + + $pass = $this->getPassword(); + + // Once we have called this callback, we will remove this token row from the + // table and return the response from this callback so the user gets sent + // to the destination given by the developers from the callback return. + $response = call_user_func($callback, $user, $pass); + + $this->reminders->delete($this->getToken()); + + return $response; + } + + /** + * Validate a password reset for the given credentials. + * + * @param array $credenitals + * @return \Illuminate\Auth\RemindableInterface + */ + protected function validateReset(array $credentials) + { + if (is_null($user = $this->getUser($credentials))) + { + return $this->makeErrorRedirect('user'); + } + + if ( ! $this->validNewPasswords()) + { + return $this->makeErrorRedirect('password'); + } + + if ( ! $this->reminders->exists($user, $this->getToken())) + { + return $this->makeErrorRedirect('token'); + } + + return $user; + } + + /** + * Determine if the passwords match for the request. + * + * @return bool + */ + protected function validNewPasswords() + { + $password = $this->getPassword(); + + $confirm = $this->getConfirmedPassword(); + + return $password and strlen($password) >= 6 and $password == $confirm; + } + + /** + * Make an error redirect response. + * + * @param string $reason + * @return \Illuminate\Http\RedirectResponse + */ + protected function makeErrorRedirect($reason = '') + { + if ($reason != '') $reason = 'reminders.'.$reason; + + return $this->redirect->refresh()->with('error', true)->with('reason', $reason); + } + + /** + * Get the user for the given credentials. + * + * @param array $credentials + * @return \Illuminate\Auth\Reminders\RemindableInterface + */ + public function getUser(array $credentials) + { + $user = $this->users->retrieveByCredentials($credentials); + + if ($user and ! $user instanceof RemindableInterface) + { + throw new \UnexpectedValueException("User must implement Remindable interface."); + } + + return $user; + } + + /** + * Get the current request object. + * + * @return \Illuminate\Http\Request + */ + protected function getRequest() + { + return $this->redirect->getUrlGenerator()->getRequest(); + } + + /** + * Get the reset token for the current request. + * + * @return string + */ + protected function getToken() + { + return $this->getRequest()->input('token'); + } + + /** + * Get the password for the current request. + * + * @return string + */ + protected function getPassword() + { + return $this->getRequest()->input('password'); + } + + /** + * Get the confirmed password. + * + * @return string + */ + protected function getConfirmedPassword() + { + return $this->getRequest()->input('password_confirmation'); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Auth/Reminders/RemindableInterface.php b/vendor/laravel/framework/src/Illuminate/Auth/Reminders/RemindableInterface.php new file mode 100755 index 0000000..39b0a40 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Auth/Reminders/RemindableInterface.php @@ -0,0 +1,12 @@ +registerPasswordBroker(); + + $this->registerReminderRepository(); + + $this->registerCommands(); + } + + /** + * Register the password broker instance. + * + * @return void + */ + protected function registerPasswordBroker() + { + $this->app['auth.reminder'] = $this->app->share(function($app) + { + // The reminder repository is responsible for storing the user e-mail addresses + // and password reset tokens. It will be used to verify the tokens are valid + // for the given e-mail addresses. We will resolve an implementation here. + $reminders = $app['auth.reminder.repository']; + + $users = $app['auth']->driver()->getProvider(); + + $view = $app['config']['auth.reminder.email']; + + // The password broker uses the reminder repository to validate tokens and send + // reminder e-mails, as well as validating that password reset process as an + // aggregate service of sorts providing a convenient interface for resets. + return new PasswordBroker( + + $reminders, $users, $app['redirect'], $app['mailer'], $view + + ); + }); + } + + /** + * Register the reminder repository implementation. + * + * @return void + */ + protected function registerReminderRepository() + { + $app = $this->app; + + $app['auth.reminder.repository'] = $app->share(function($app) + { + $connection = $app['db']->connection(); + + // The database reminder repository is an implementation of the reminder repo + // interface, and is responsible for the actual storing of auth tokens and + // their e-mail addresses. We will inject this table and hash key to it. + $table = $app['config']['auth.reminder.table']; + + $key = $app['config']['app.key']; + + $expire = $app['config']->get('auth.reminder.expire', 60); + + return new DbRepository($connection, $table, $key, $expire); + }); + } + + /** + * Register the auth related console commands. + * + * @return void + */ + protected function registerCommands() + { + $app = $this->app; + + $app['command.auth.reminders'] = $app->share(function($app) + { + return new MakeRemindersCommand($app['files']); + }); + + $this->commands('command.auth.reminders'); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('auth.reminder', 'auth.reminder.repository', 'command.auth.reminders'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Auth/UserInterface.php b/vendor/laravel/framework/src/Illuminate/Auth/UserInterface.php new file mode 100755 index 0000000..3e1e86b --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Auth/UserInterface.php @@ -0,0 +1,19 @@ +=5.3.0", + "illuminate/cookie": "4.0.x", + "illuminate/encryption": "4.0.x", + "illuminate/events": "4.0.x", + "illuminate/hashing": "4.0.x", + "illuminate/http": "4.0.x", + "illuminate/session": "4.0.x", + "illuminate/support": "4.0.x" + }, + "require-dev": { + "illuminate/console": "4.0.x", + "illuminate/routing": "4.0.x", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": {"Illuminate\\Auth": ""} + }, + "target-dir": "Illuminate/Auth", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Cache/ApcStore.php b/vendor/laravel/framework/src/Illuminate/Cache/ApcStore.php new file mode 100755 index 0000000..5fc3aaa --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cache/ApcStore.php @@ -0,0 +1,139 @@ +apc = $apc; + $this->prefix = $prefix; + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + $value = $this->apc->get($this->prefix.$key); + + if ($value !== false) + { + return $value; + } + } + + /** + * Store an item in the cache for a given number of minutes. + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return array|bool + */ + public function put($key, $value, $minutes) + { + $this->apc->put($this->prefix.$key, $value, $minutes * 60); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return array|bool + */ + public function increment($key, $value = 1) + { + return $this->apc->increment($this->prefix.$key, $value); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return array|bool + */ + public function decrement($key, $value = 1) + { + return $this->apc->decrement($this->prefix.$key, $value); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return array|bool + */ + public function forever($key, $value) + { + return $this->put($key, $value, 0); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return array|bool + */ + public function forget($key) + { + $this->apc->delete($this->prefix.$key); + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + $this->apc->flush(); + } + + /** + * Begin executing a new section operation. + * + * @param string $name + * @return \Illuminate\Cache\Section + */ + public function section($name) + { + return new Section($this, $name); + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Cache/ApcWrapper.php b/vendor/laravel/framework/src/Illuminate/Cache/ApcWrapper.php new file mode 100755 index 0000000..688b46d --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cache/ApcWrapper.php @@ -0,0 +1,91 @@ +apcu = function_exists('apcu_fetch'); + } + + /** + * Get an item from the cache. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + return $this->apcu ? apcu_fetch($key) : apc_fetch($key); + } + + /** + * Store an item in the cache. + * + * @param string $key + * @param mixed $value + * @param int $seconds + * @return array|bool + */ + public function put($key, $value, $seconds) + { + return $this->apcu ? apcu_store($key, $value, $seconds) : apc_store($key, $value, $seconds); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return array|bool + */ + public function increment($key, $value) + { + return $this->apcu ? apcu_inc($key, $value) : apc_inc($key, $value); + } + + /** + * Decremenet the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return array|bool + */ + public function decrement($key, $value) + { + return $this->apcu ? apcu_dec($key, $value) : apc_dec($key, $value); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return array|bool + */ + public function delete($key) + { + return $this->apcu ? apcu_delete($key) : apc_delete($key); + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + $this->apcu ? apcu_clear_cache() : apc_clear_cache('user'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Cache/ArrayStore.php b/vendor/laravel/framework/src/Illuminate/Cache/ArrayStore.php new file mode 100755 index 0000000..eb6e927 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cache/ArrayStore.php @@ -0,0 +1,121 @@ +storage)) + { + return $this->storage[$key]; + } + } + + /** + * Store an item in the cache for a given number of minutes. + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return void + */ + public function put($key, $value, $minutes) + { + $this->storage[$key] = $value; + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function increment($key, $value = 1) + { + $this->storage[$key] = $this->storage[$key] + $value; + + return $this->storage[$key]; + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function decrement($key, $value = 1) + { + $this->storage[$key] = $this->storage[$key] - $value; + + return $this->storage[$key]; + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function forever($key, $value) + { + return $this->put($key, $value, 0); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return void + */ + public function forget($key) + { + unset($this->storage[$key]); + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + $this->storage = array(); + } + + /** + * Begin executing a new section operation. + * + * @param string $name + * @return \Illuminate\Cache\Section + */ + public function section($name) + { + return new Section($this, $name); + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return ''; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Cache/CacheManager.php b/vendor/laravel/framework/src/Illuminate/Cache/CacheManager.php new file mode 100755 index 0000000..48f40cc --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cache/CacheManager.php @@ -0,0 +1,139 @@ +repository(new ApcStore(new ApcWrapper, $this->getPrefix())); + } + + /** + * Create an instance of the array cache driver. + * + * @return \Illuminate\Cache\ArrayStore + */ + protected function createArrayDriver() + { + return $this->repository(new ArrayStore); + } + + /** + * Create an instance of the file cache driver. + * + * @return \Illuminate\Cache\FileStore + */ + protected function createFileDriver() + { + $path = $this->app['config']['cache.path']; + + return $this->repository(new FileStore($this->app['files'], $path)); + } + + /** + * Create an instance of the Memcached cache driver. + * + * @return \Illuminate\Cache\MemcachedStore + */ + protected function createMemcachedDriver() + { + $servers = $this->app['config']['cache.memcached']; + + $memcached = $this->app['memcached.connector']->connect($servers); + + return $this->repository(new MemcachedStore($memcached, $this->getPrefix())); + } + + /** + * Create an instance of the WinCache cache driver. + * + * @return \Illuminate\Cache\WinCacheStore + */ + protected function createWincacheDriver() + { + return $this->repository(new WinCacheStore($this->getPrefix())); + } + + /** + * Create an instance of the Redis cache driver. + * + * @return \Illuminate\Cache\RedisStore + */ + protected function createRedisDriver() + { + $redis = $this->app['redis']; + + return $this->repository(new RedisStore($redis, $this->getPrefix())); + } + + /** + * Create an instance of the database cache driver. + * + * @return \Illuminate\Cache\DatabaseStore + */ + protected function createDatabaseDriver() + { + $connection = $this->getDatabaseConnection(); + + $encrypter = $this->app['encrypter']; + + // We allow the developer to specify which connection and table should be used + // to store the cached items. We also need to grab a prefix in case a table + // is being used by multiple applications although this is very unlikely. + $table = $this->app['config']['cache.table']; + + $prefix = $this->getPrefix(); + + return $this->repository(new DatabaseStore($connection, $encrypter, $table, $prefix)); + } + + /** + * Get the database connection for the database driver. + * + * @return \Illuminate\Database\Connection + */ + protected function getDatabaseConnection() + { + $connection = $this->app['config']['cache.connection']; + + return $this->app['db']->connection($connection); + } + + /** + * Get the cache "prefix" value. + * + * @return string + */ + public function getPrefix() + { + return $this->app['config']['cache.prefix']; + } + + /** + * Create a new cache repository with the given implementation. + * + * @param \Illuminate\Cache\StoreInterface $store + * @return \Illuminate\Cache\Repository + */ + protected function repository(StoreInterface $store) + { + return new Repository($store); + } + + /** + * Get the default cache driver name. + * + * @return string + */ + protected function getDefaultDriver() + { + return $this->app['config']['cache.driver']; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Cache/CacheServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Cache/CacheServiceProvider.php new file mode 100755 index 0000000..a01f0be --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cache/CacheServiceProvider.php @@ -0,0 +1,59 @@ +app['cache'] = $this->app->share(function($app) + { + return new CacheManager($app); + }); + + $this->app['memcached.connector'] = $this->app->share(function() + { + return new MemcachedConnector; + }); + + $this->registerCommands(); + } + + /** + * Register the cache related console commands. + * + * @return void + */ + public function registerCommands() + { + $this->app['command.cache.clear'] = $this->app->share(function($app) + { + return new Console\ClearCommand($app['cache'], $app['files']); + }); + + $this->commands('command.cache.clear'); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('cache', 'memcached.connector', 'command.cache.clear'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Cache/Console/ClearCommand.php b/vendor/laravel/framework/src/Illuminate/Cache/Console/ClearCommand.php new file mode 100755 index 0000000..91fcee4 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cache/Console/ClearCommand.php @@ -0,0 +1,66 @@ +cache = $cache; + $this->files = $files; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->cache->flush(); + + $this->files->delete($this->laravel['config']['app.manifest'].'/services.json'); + + $this->info('Application cache cleared!'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Cache/DatabaseStore.php b/vendor/laravel/framework/src/Illuminate/Cache/DatabaseStore.php new file mode 100755 index 0000000..edd14dd --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cache/DatabaseStore.php @@ -0,0 +1,217 @@ +table = $table; + $this->prefix = $prefix; + $this->encrypter = $encrypter; + $this->connection = $connection; + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + $prefixed = $this->prefix.$key; + + $cache = $this->table()->where('key', $prefixed)->first(); + + // If we have a cache record we will check the expiration time against current + // time on the system and see if the record has expired. If it has, we will + // remove the records from the database table so it isn't returned again. + if ( ! is_null($cache)) + { + if (is_array($cache)) $cache = (object) $cache; + + if (time() >= $cache->expiration) + { + return $this->forget($key); + } + + return $this->encrypter->decrypt($cache->value); + } + } + + /** + * Store an item in the cache for a given number of minutes. + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return void + */ + public function put($key, $value, $minutes) + { + $key = $this->prefix.$key; + + // All of the cached values in the database are encrypted in case this is used + // as a session data store by the consumer. We'll also calculate the expire + // time and place that on the table so we will check it on our retrieval. + $value = $this->encrypter->encrypt($value); + + $expiration = $this->getTime() + ($minutes * 60); + + try + { + $this->table()->insert(compact('key', 'value', 'expiration')); + } + catch (\Exception $e) + { + $this->table()->where('key', $key)->update(compact('value', 'expiration')); + } + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function increment($key, $value = 1) + { + throw new \LogicException("Increment operations not supported by this driver."); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function decrement($key, $value = 1) + { + throw new \LogicException("Increment operations not supported by this driver."); + } + + /** + * Get the current system time. + * + * @return int + */ + protected function getTime() + { + return time(); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function forever($key, $value) + { + return $this->put($key, $value, 5256000); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return void + */ + public function forget($key) + { + $this->table()->where('key', $this->prefix.$key)->delete(); + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + $this->table()->delete(); + } + + /** + * Get a query builder for the cache table. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function table() + { + return $this->connection->table($this->table); + } + + /** + * Get the underlying database connection. + * + * @return \Illuminate\Database\Connection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Get the encrypter instance. + * + * @return \Illuminate\Encryption\Encrypter + */ + public function getEncrypter() + { + return $this->encrypter; + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Cache/FileStore.php b/vendor/laravel/framework/src/Illuminate/Cache/FileStore.php new file mode 100755 index 0000000..5ce3e22 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cache/FileStore.php @@ -0,0 +1,212 @@ +files = $files; + $this->directory = $directory; + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + $path = $this->path($key); + + // If the file doesn't exists, we obviously can't return the cache so we will + // just return null. Otherwise, we'll get the contents of the file and get + // the expiration UNIX timestamps from the start of the file's contents. + if ( ! $this->files->exists($path)) + { + return null; + } + + $expire = substr($contents = $this->files->get($path), 0, 10); + + // If the current time is greater than expiration timestamps we will delete + // the file and return null. This helps clean up the old files and keeps + // this directory much cleaner for us as old files aren't hanging out. + if (time() >= $expire) + { + return $this->forget($key); + } + + return unserialize(substr($contents, 10)); + } + + /** + * Store an item in the cache for a given number of minutes. + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return void + */ + public function put($key, $value, $minutes) + { + $value = $this->expiration($minutes).serialize($value); + + $this->createCacheDirectory($path = $this->path($key)); + + $this->files->put($path, $value); + } + + /** + * Create the file cache directory if necessary. + * + * @param string $path + * @return void + */ + protected function createCacheDirectory($path) + { + if ( ! $this->files->isDirectory($directory = dirname($path))) + { + $this->files->makeDirectory($directory, 0777, true); + } + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function increment($key, $value = 1) + { + throw new \LogicException("Increment operations not supported by this driver."); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function decrement($key, $value = 1) + { + throw new \LogicException("Increment operations not supported by this driver."); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function forever($key, $value) + { + return $this->put($key, $value, 0); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return void + */ + public function forget($key) + { + $this->files->delete($this->path($key)); + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + foreach ($this->files->directories($this->directory) as $directory) + { + $this->files->deleteDirectory($directory); + } + } + + /** + * Get the full path for the given cache key. + * + * @param string $key + * @return string + */ + protected function path($key) + { + $parts = array_slice(str_split($hash = md5($key), 2), 0, 2); + + return $this->directory.'/'.join('/', $parts).'/'.$hash; + } + + /** + * Get the expiration time based on the given minutes. + * + * @param int $minutes + * @return int + */ + protected function expiration($minutes) + { + if ($minutes === 0) return 9999999999; + + return time() + ($minutes * 60); + } + + /** + * Get the Filesystem instance. + * + * @return \Illuminate\Filesystem\Filesystem + */ + public function getFilesystem() + { + return $this->files; + } + + /** + * Get the working directory of the cache. + * + * @return string + */ + public function getDirectory() + { + return $this->directory; + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return ''; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Cache/MemcachedConnector.php b/vendor/laravel/framework/src/Illuminate/Cache/MemcachedConnector.php new file mode 100755 index 0000000..83a1280 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cache/MemcachedConnector.php @@ -0,0 +1,43 @@ +getMemcached(); + + // For each server in the array, we'll just extract the configuration and add + // the server to the Memcached connection. Once we have added all of these + // servers we'll verify the connection is successful and return it back. + foreach ($servers as $server) + { + $memcached->addServer($server['host'], $server['port'], $server['weight']); + } + + if ($memcached->getVersion() === false) + { + throw new \RuntimeException("Could not establish Memcached connection."); + } + + return $memcached; + } + + /** + * Get a new Memcached instance. + * + * @return \Memcached + */ + protected function getMemcached() + { + return new Memcached; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Cache/MemcachedStore.php b/vendor/laravel/framework/src/Illuminate/Cache/MemcachedStore.php new file mode 100755 index 0000000..a073e92 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cache/MemcachedStore.php @@ -0,0 +1,151 @@ +memcached = $memcached; + $this->prefix = strlen($prefix) > 0 ? $prefix.':' : ''; + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + $value = $this->memcached->get($this->prefix.$key); + + if ($this->memcached->getResultCode() == 0) + { + return $value; + } + } + + /** + * Store an item in the cache for a given number of minutes. + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return void + */ + public function put($key, $value, $minutes) + { + $this->memcached->set($this->prefix.$key, $value, $minutes * 60); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function increment($key, $value = 1) + { + return $this->memcached->increment($this->prefix.$key, $value); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function decrement($key, $value = 1) + { + return $this->memcached->decrement($this->prefix.$key, $value); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function forever($key, $value) + { + return $this->put($key, $value, 0); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return void + */ + public function forget($key) + { + $this->memcached->delete($this->prefix.$key); + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + $this->memcached->flush(); + } + + /** + * Begin executing a new section operation. + * + * @param string $name + * @return \Illuminate\Cache\Section + */ + public function section($name) + { + return new Section($this, $name); + } + + /** + * Get the underlying Memcached connection. + * + * @return \Memcached + */ + public function getMemcached() + { + return $this->memcached; + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Cache/RedisSection.php b/vendor/laravel/framework/src/Illuminate/Cache/RedisSection.php new file mode 100755 index 0000000..78cd1a5 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cache/RedisSection.php @@ -0,0 +1,84 @@ +store->connection()->lpush($this->foreverKey(), $key); + + $this->store->forever($this->sectionItemKey($key), $value); + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + $this->deleteForeverKeys(); + + $this->store->connection()->del($this->foreverKey()); + + $this->store->increment($this->sectionKey()); + } + + /** + * Delete all of the keys that have been stored forever. + * + * @return void + */ + protected function deleteForeverKeys() + { + $forever = $this->getForeverKeys(); + + if (count($forever) > 0) + { + call_user_func_array(array($this->store->connection(), 'del'), $forever); + } + } + + /** + * Get the keys that have been stored forever. + * + * @return array + */ + protected function getForeverKeys() + { + $me = $this; + + return array_map(function($x) use ($me) + { + return $me->getPrefix().$me->sectionItemKey($x); + + }, array_unique($this->store->connection()->lrange($this->foreverKey(), 0, -1))); + } + + /** + * Get the forever list identifier. + * + * @return string + */ + protected function foreverKey() + { + return $this->sectionKey().':forever'; + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return $this->store->getPrefix(); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Cache/RedisStore.php b/vendor/laravel/framework/src/Illuminate/Cache/RedisStore.php new file mode 100755 index 0000000..7505fa9 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cache/RedisStore.php @@ -0,0 +1,185 @@ +redis = $redis; + $this->prefix = $prefix.':'; + $this->connection = $connection; + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + if ( ! is_null($value = $this->connection()->get($this->prefix.$key))) + { + return is_numeric($value) ? $value : unserialize($value); + } + } + + /** + * Store an item in the cache for a given number of minutes. + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return void + */ + public function put($key, $value, $minutes) + { + $value = is_numeric($value) ? $value : serialize($value); + + $this->connection()->set($this->prefix.$key, $value); + + $this->connection()->expire($this->prefix.$key, $minutes * 60); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function increment($key, $value = 1) + { + return $this->connection()->incrby($this->prefix.$key, $value); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function decrement($key, $value = 1) + { + return $this->connection()->decrby($this->prefix.$key, $value); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function forever($key, $value) + { + $value = is_numeric($value) ? $value : serialize($value); + + $this->connection()->set($this->prefix.$key, $value); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return void + */ + public function forget($key) + { + $this->connection()->del($this->prefix.$key); + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + $this->connection()->flushdb(); + } + + /** + * Begin executing a new section operation. + * + * @param string $name + * @return \Illuminate\Cache\Section + */ + public function section($name) + { + return new RedisSection($this, $name); + } + + /** + * Get the Redis connection instance. + * + * @return \Predis\Connection\SingleConnectionInterface + */ + public function connection() + { + return $this->redis->connection($this->connection); + } + + /** + * Set the connection name to be used. + * + * @param string $connection + * @return void + */ + public function setConnection($connection) + { + $this->connection = $connection; + } + + /** + * Get the Redis database instance. + * + * @return \Illuminate\Redis\Database + */ + public function getRedis() + { + return $this->redis; + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Cache/Repository.php b/vendor/laravel/framework/src/Illuminate/Cache/Repository.php new file mode 100755 index 0000000..594c007 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cache/Repository.php @@ -0,0 +1,209 @@ +store = $store; + } + + /** + * Determine if an item exists in the cache. + * + * @param string $key + * @return bool + */ + public function has($key) + { + return ! is_null($this->get($key)); + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + $value = $this->store->get($key); + + return ! is_null($value) ? $value : value($default); + } + + /** + * Store an item in the cache if the key does not exist. + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return void + */ + public function add($key, $value, $minutes) + { + if (is_null($this->get($key))) $this->put($key, $value, $minutes); + } + + /** + * Get an item from the cache, or store the default value. + * + * @param string $key + * @param int $minutes + * @param Closure $callback + * @return mixed + */ + public function remember($key, $minutes, Closure $callback) + { + // If the item exists in the cache we will just return this immediately + // otherwise we will execute the given Closure and cache the result + // of that execution for the given number of minutes in storage. + if ($this->has($key)) return $this->get($key); + + $this->put($key, $value = $callback(), $minutes); + + return $value; + } + + /** + * Get an item from the cache, or store the default value forever. + * + * @param string $key + * @param Closure $callback + * @return mixed + */ + public function sear($key, Closure $callback) + { + return $this->rememberForever($key, $callback); + } + + /** + * Get an item from the cache, or store the default value forever. + * + * @param string $key + * @param Closure $callback + * @return mixed + */ + public function rememberForever($key, Closure $callback) + { + // If the item exists in the cache we will just return this immediately + // otherwise we will execute the given Closure and cache the result + // of that execution for the given number of minutes. It's easy. + if ( ! is_null($value = $this->get($key))) + { + return $value; + } + + $this->forever($key, $value = $callback()); + + return $value; + } + + /** + * Get the default cache time. + * + * @return int + */ + public function getDefaultCacheTime() + { + return $this->default; + } + + /** + * Set the default cache time in minutes. + * + * @param int $minutes + * @return void + */ + public function setDefaultCacheTime($minutes) + { + $this->default = $minutes; + } + + /** + * Get the cache store implementation. + * + * @return \Illuminate\Cache\StoreInterface + */ + public function getStore() + { + return $this->store; + } + + /** + * Determine if a cached value exists. + * + * @param string $key + * @return bool + */ + public function offsetExists($key) + { + return $this->has($key); + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + */ + public function offsetGet($key) + { + return $this->get($key); + } + + /** + * Store an item in the cache for the default time. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value) + { + $this->put($key, $value, $this->default); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return void + */ + public function offsetUnset($key) + { + return $this->forget($key); + } + + /** + * Dynamically pass missing methods to the store. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return call_user_func_array(array($this->store, $method), $parameters); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Cache/Section.php b/vendor/laravel/framework/src/Illuminate/Cache/Section.php new file mode 100755 index 0000000..2b5bf84 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cache/Section.php @@ -0,0 +1,230 @@ +name = $name; + $this->store = $store; + } + + /** + * Determine if an item exists in the cache. + * + * @param string $key + * @return bool + */ + public function has($key) + { + return ! is_null($this->get($key)); + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + $value = $this->store->get($this->sectionItemKey($key)); + + return ! is_null($value) ? $value : value($default); + } + + /** + * Store an item in the cache for a given number of minutes. + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return void + */ + public function put($key, $value, $minutes) + { + return $this->store->put($this->sectionItemKey($key), $value, $minutes); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function increment($key, $value = 1) + { + $this->store->increment($this->sectionItemKey($key), $value); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function decrement($key, $value = 1) + { + $this->store->decrement($this->sectionItemKey($key), $value); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function forever($key, $value) + { + $this->store->forever($this->sectionItemKey($key), $value); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return void + */ + public function forget($key) + { + $this->store->forget($this->sectionItemKey($key)); + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + $this->reset(); + } + + /** + * Get an item from the cache, or store the default value. + * + * @param string $key + * @param int $minutes + * @param Closure $callback + * @return mixed + */ + public function remember($key, $minutes, Closure $callback) + { + // If the item exists in the cache we will just return this immediately + // otherwise we will execute the given Closure and cache the result + // of that execution for the given number of minutes in storage. + if ($this->has($key)) return $this->get($key); + + $this->put($key, $value = $callback(), $minutes); + + return $value; + } + + /** + * Get an item from the cache, or store the default value forever. + * + * @param string $key + * @param Closure $callback + * @return mixed + */ + public function sear($key, Closure $callback) + { + return $this->rememberForever($key, $callback); + } + + /** + * Get an item from the cache, or store the default value forever. + * + * @param string $key + * @param Closure $callback + * @return mixed + */ + public function rememberForever($key, Closure $callback) + { + // If the item exists in the cache we will just return this immediately + // otherwise we will execute the given Closure and cache the result + // of that execution for the given number of minutes. It's easy. + if ($this->has($key)) return $this->get($key); + + $this->forever($key, $value = $callback()); + + return $value; + } + + /** + * Get a fully qualfied section item key. + * + * @param string $key + * @return string + */ + public function sectionItemKey($key) + { + return $this->name.':'.$this->sectionId().':'.$key; + } + + /** + * Reset the section, returning a new section identifier + * + * @return string + */ + protected function reset() + { + $this->store->forever($this->sectionKey(), $id = uniqid()); + + return $id; + } + + /** + * Get the unique section identifier. + * + * @return string + */ + protected function sectionId() + { + $id = $this->store->get($this->sectionKey()); + + if (is_null($id)) + { + $id = $this->reset(); + } + + return $id; + } + + /** + * Get the section identifier key. + * + * @return string + */ + protected function sectionKey() + { + return 'section:'.$this->name.':key'; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Cache/StoreInterface.php b/vendor/laravel/framework/src/Illuminate/Cache/StoreInterface.php new file mode 100755 index 0000000..54efca6 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cache/StoreInterface.php @@ -0,0 +1,72 @@ +prefix = $prefix; + } + + /** + * Retrieve an item from the cache by key. + * + * @param string $key + * @return mixed + */ + public function get($key) + { + $value = wincache_ucache_get($this->prefix.$key); + + if ($value !== false) + { + return $value; + } + } + + /** + * Store an item in the cache for a given number of minutes. + * + * @param string $key + * @param mixed $value + * @param int $minutes + * @return void + */ + public function put($key, $value, $minutes) + { + wincache_ucache_add($this->prefix.$key, $value, $minutes * 60); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function increment($key, $value = 1) + { + return wincache_ucache_inc($this->prefix.$key, $value); + } + + /** + * Increment the value of an item in the cache. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function decrement($key, $value = 1) + { + return wincache_ucache_dec($this->prefix.$key, $value); + } + + /** + * Store an item in the cache indefinitely. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function forever($key, $value) + { + return $this->put($key, $value, 0); + } + + /** + * Remove an item from the cache. + * + * @param string $key + * @return void + */ + public function forget($key) + { + wincache_ucache_delete($this->prefix.$key); + } + + /** + * Remove all items from the cache. + * + * @return void + */ + public function flush() + { + wincache_ucache_clear(); + } + + /** + * Begin executing a new section operation. + * + * @param string $name + * @return \Illuminate\Cache\Section + */ + public function section($name) + { + return new Section($this, $name); + } + + /** + * Get the cache key prefix. + * + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Cache/composer.json b/vendor/laravel/framework/src/Illuminate/Cache/composer.json new file mode 100755 index 0000000..3734879 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cache/composer.json @@ -0,0 +1,31 @@ +{ + "name": "illuminate/cache", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "illuminate/support": "4.0.x" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*", + "illuminate/database": "4.0.x", + "illuminate/encryption": "4.0.x", + "illuminate/filesystem": "4.0.x", + "illuminate/redis": "4.0.x" + }, + "autoload": { + "psr-0": {"Illuminate\\Cache": ""} + }, + "target-dir": "Illuminate/Cache", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Config/FileLoader.php b/vendor/laravel/framework/src/Illuminate/Config/FileLoader.php new file mode 100755 index 0000000..b89e4e8 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Config/FileLoader.php @@ -0,0 +1,255 @@ +files = $files; + $this->defaultPath = $defaultPath; + } + + /** + * Load the given configuration group. + * + * @param string $environment + * @param string $group + * @param string $namespace + * @return array + */ + public function load($environment, $group, $namespace = null) + { + $items = array(); + + // First we'll get the root configuration path for the environment which is + // where all of the configuration files live for that namespace, as well + // as any environment folders with their specific configuration items. + $path = $this->getPath($namespace); + + if (is_null($path)) + { + return $items; + } + + // First we'll get the main configuration file for the groups. Once we have + // that we can check for any environment specific files, which will get + // merged on top of the main arrays to make the environments cascade. + $file = "{$path}/{$group}.php"; + + if ($this->files->exists($file)) + { + $items = $this->files->getRequire($file); + } + + // Finally we're ready to check for the environment specific configuration + // file which will be merged on top of the main arrays so that they get + // precedence over them if we are currently in an environments setup. + $file = "{$path}/{$environment}/{$group}.php"; + + if ($this->files->exists($file)) + { + $items = $this->mergeEnvironment($items, $file); + } + + return $items; + } + + /** + * Merge the items in the given file into the items. + * + * @param array $items + * @param string $file + * @return array + */ + protected function mergeEnvironment(array $items, $file) + { + return array_replace_recursive($items, $this->files->getRequire($file)); + } + + /** + * Determine if the given group exists. + * + * @param string $group + * @param string $namespace + * @return bool + */ + public function exists($group, $namespace = null) + { + $key = $group.$namespace; + + // We'll first check to see if we have determined if this namespace and + // group combination have been checked before. If they have, we will + // just return the cached result so we don't have to hit the disk. + if (isset($this->exists[$key])) + { + return $this->exists[$key]; + } + + $path = $this->getPath($namespace); + + // To check if a group exists, we will simply get the path based on the + // namespace, and then check to see if this files exists within that + // namespace. False is returned if no path exists for a namespace. + if (is_null($path)) + { + return $this->exists[$key] = false; + } + + $file = "{$path}/{$group}.php"; + + // Finally, we can simply check if this file exists. We will also cache + // the value in an array so we don't have to go through this process + // again on subsequent checks for the existing of the config file. + $exists = $this->files->exists($file); + + return $this->exists[$key] = $exists; + } + + /** + * Apply any cascades to an array of package options. + * + * @param string $env + * @param string $package + * @param string $group + * @param array $items + * @return array + */ + public function cascadePackage($env, $package, $group, $items) + { + // First we will look for a configuration file in the packages configuration + // folder. If it exists, we will load it and merge it with these original + // options so that we will easily "cascade" a package's configurations. + $file = "packages/{$package}/{$group}.php"; + + if ($this->files->exists($path = $this->defaultPath.'/'.$file)) + { + $items = array_merge($items, $this->getRequire($path)); + } + + // Once we have merged the regular package configuration we need to look for + // an environment specific configuration file. If one exists, we will get + // the contents and merge them on top of this array of options we have. + $path = $this->getPackagePath($env, $package, $group); + + if ($this->files->exists($path)) + { + $items = array_merge($items, $this->getRequire($path)); + } + + return $items; + } + + /** + * Get the package path for an environment and group. + * + * @param string $env + * @param string $package + * @param string $group + * @return string + */ + protected function getPackagePath($env, $package, $group) + { + $file = "packages/{$package}/{$env}/{$group}.php"; + + return $this->defaultPath.'/'.$file; + } + + /** + * Get the configuration path for a namespace. + * + * @param string $namespace + * @return string + */ + protected function getPath($namespace) + { + if (is_null($namespace)) + { + return $this->defaultPath; + } + elseif (isset($this->hints[$namespace])) + { + return $this->hints[$namespace]; + } + } + + /** + * Add a new namespace to the loader. + * + * @param string $namespace + * @param string $hint + * @return void + */ + public function addNamespace($namespace, $hint) + { + $this->hints[$namespace] = $hint; + } + + /** + * Returns all registered namespaces with the config + * loader. + * + * @return array + */ + public function getNamespaces() + { + return $this->hints; + } + + /** + * Get a file's contents by requiring it. + * + * @param string $path + * @return mixed + */ + protected function getRequire($path) + { + return $this->files->getRequire($path); + } + + /** + * Get the Filesystem instance. + * + * @return \Illuminate\Filesystem\Filesystem + */ + public function getFilesystem() + { + return $this->files; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Config/LoaderInterface.php b/vendor/laravel/framework/src/Illuminate/Config/LoaderInterface.php new file mode 100755 index 0000000..0d6aabf --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Config/LoaderInterface.php @@ -0,0 +1,52 @@ +loader = $loader; + $this->environment = $environment; + } + + /** + * Determine if the given configuration value exists. + * + * @param string $key + * @return bool + */ + public function has($key) + { + $default = microtime(true); + + return $this->get($key, $default) !== $default; + } + + /** + * Determine if a configuration group exists. + * + * @param string $key + * @return bool + */ + public function hasGroup($key) + { + list($namespace, $group, $item) = $this->parseKey($key); + + return $this->loader->exists($group, $namespace); + } + + /** + * Get the specified configuration value. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + list($namespace, $group, $item) = $this->parseKey($key); + + // Configuration items are actually keyed by "collection", which is simply a + // combination of each namespace and groups, which allows a unique way to + // identify the arrays of configuration items for the particular files. + $collection = $this->getCollection($group, $namespace); + + $this->load($group, $namespace, $collection); + + return array_get($this->items[$collection], $item, $default); + } + + /** + * Set a given configuration value. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function set($key, $value) + { + list($namespace, $group, $item) = $this->parseKey($key); + + $collection = $this->getCollection($group, $namespace); + + // We'll need to go ahead and lazy load each configuration groups even when + // we're just setting a configuration item so that the set item does not + // get overwritten if a different item in the group is requested later. + $this->load($group, $namespace, $collection); + + if (is_null($item)) + { + $this->items[$collection] = $value; + } + else + { + array_set($this->items[$collection], $item, $value); + } + } + + /** + * Load the configuration group for the key. + * + * @param string $key + * @param string $namespace + * @param string $collection + * @return void + */ + protected function load($group, $namespace, $collection) + { + $env = $this->environment; + + // If we've already loaded this collection, we will just bail out since we do + // not want to load it again. Once items are loaded a first time they will + // stay kept in memory within this class and not loaded from disk again. + if (isset($this->items[$collection])) + { + return; + } + + $items = $this->loader->load($env, $group, $namespace); + + // If we've already loaded this collection, we will just bail out since we do + // not want to load it again. Once items are loaded a first time they will + // stay kept in memory within this class and not loaded from disk again. + if (isset($this->afterLoad[$namespace])) + { + $items = $this->callAfterLoad($namespace, $group, $items); + } + + $this->items[$collection] = $items; + } + + /** + * Call the after load callback for a namespace. + * + * @param string $namespace + * @param string $group + * @param array $items + * @return array + */ + protected function callAfterLoad($namespace, $group, $items) + { + $callback = $this->afterLoad[$namespace]; + + return call_user_func($callback, $this, $group, $items); + } + + /** + * Parse an array of namespaced segments. + * + * @param string $key + * @return array + */ + protected function parseNamespacedSegments($key) + { + list($namespace, $item) = explode('::', $key); + + // If the namespace is registered as a package, we will just assume the group + // is equal to the namespace since all packages cascade in this way having + // a single file per package, otherwise we'll just parse them as normal. + if (in_array($namespace, $this->packages)) + { + return $this->parsePackageSegments($key, $namespace, $item); + } + + return parent::parseNamespacedSegments($key); + } + + /** + * Parse the segments of a package namespace. + * + * @param string $namespace + * @param string $item + * @return array + */ + protected function parsePackageSegments($key, $namespace, $item) + { + $itemSegments = explode('.', $item); + + // If the configuration file doesn't exist for the given package group we can + // assume that we should implicitly use the config file matching the name + // of the namespace. Generally packages should use one type or another. + if ( ! $this->loader->exists($itemSegments[0], $namespace)) + { + return array($namespace, 'config', $item); + } + + return parent::parseNamespacedSegments($key); + } + + /** + * Register a package for cascading configuration. + * + * @param string $package + * @param string $hint + * @param string $namespace + * @return void + */ + public function package($package, $hint, $namespace = null) + { + $namespace = $this->getPackageNamespace($package, $namespace); + + $this->packages[] = $namespace; + + // First we will simply register the namespace with the repository so that it + // can be loaded. Once we have done that we'll register an after namespace + // callback so that we can cascade an application package configuration. + $this->addNamespace($namespace, $hint); + + $this->afterLoading($namespace, function($me, $group, $items) use ($package) + { + $env = $me->getEnvironment(); + + $loader = $me->getLoader(); + + return $loader->cascadePackage($env, $package, $group, $items); + }); + } + + /** + * Get the configuration namespace for a package. + * + * @param string $package + * @param string $namespace + * @return string + */ + protected function getPackageNamespace($package, $namespace) + { + if (is_null($namespace)) + { + list($vendor, $namespace) = explode('/', $package); + } + + return $namespace; + } + + /** + * Register an after load callback for a given namespace. + * + * @param string $namespace + * @param Closure $callback + * @return void + */ + public function afterLoading($namespace, Closure $callback) + { + $this->afterLoad[$namespace] = $callback; + } + + /** + * Get the collection identifier. + * + * @param string $group + * @param string $namespace + * @return string + */ + protected function getCollection($group, $namespace = null) + { + $namespace = $namespace ?: '*'; + + return $namespace.'::'.$group; + } + + /** + * Add a new namespace to the loader. + * + * @param string $namespace + * @param string $hint + * @return void + */ + public function addNamespace($namespace, $hint) + { + return $this->loader->addNamespace($namespace, $hint); + } + + /** + * Returns all registered namespaces with the config + * loader. + * + * @return array + */ + public function getNamespaces() + { + return $this->loader->getNamespaces(); + } + + /** + * Get the loader implementation. + * + * @return \Illuminate\Config\LoaderInterface + */ + public function getLoader() + { + return $this->loader; + } + + /** + * Set the loader implementation. + * + * @param \Illuminate\Config\LoaderInterface $loader + * @return void + */ + public function setLoader(LoaderInterface $loader) + { + $this->loader = $loader; + } + + /** + * Get the current configuration environment. + * + * @return string + */ + public function getEnvironment() + { + return $this->environment; + } + + /** + * Get the after load callback array. + * + * @return array + */ + public function getAfterLoadCallbacks() + { + return $this->afterLoad; + } + + /** + * Get all of the configuration items. + * + * @return array + */ + public function getItems() + { + return $this->items; + } + + /** + * Determine if the given configuration option exists. + * + * @param string $key + * @return bool + */ + public function offsetExists($key) + { + return $this->has($key); + } + + /** + * Get a configuration option. + * + * @param string $key + * @return bool + */ + public function offsetGet($key) + { + return $this->get($key); + } + + /** + * Set a configuration option. + * + * @param string $key + * @param string $value + * @return void + */ + public function offsetSet($key, $value) + { + $this->set($key, $value); + } + + /** + * Unset a configuration option. + * + * @param string $key + * @return void + */ + public function offsetUnset($key) + { + $this->set($key, null); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Config/composer.json b/vendor/laravel/framework/src/Illuminate/Config/composer.json new file mode 100755 index 0000000..29cd26d --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Config/composer.json @@ -0,0 +1,28 @@ +{ + "name": "illuminate/config", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "illuminate/filesystem": "4.0.x", + "illuminate/support": "4.0.x" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": {"Illuminate\\Config": ""} + }, + "target-dir": "Illuminate/Config", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/laravel/framework/src/Illuminate/Console/Application.php b/vendor/laravel/framework/src/Illuminate/Console/Application.php new file mode 100755 index 0000000..672c384 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Console/Application.php @@ -0,0 +1,168 @@ +setAutoExit(false); + + // If the event dispatcher is set on the application, we will fire an event + // with the Artisan instance to provide each listener the opportunity to + // register their commands on this application before it gets started. + if (isset($app['events'])) + { + $app['events']->fire('artisan.start', array($artisan)); + } + + return $artisan; + } + + /** + * Add a command to the console. + * + * @param \Symfony\Component\Console\Command\Command $command + * @return \Symfony\Component\Console\Command\Command + */ + public function add(SymfonyCommand $command) + { + if ($command instanceof Command) + { + $command->setLaravel($this->laravel); + } + + return $this->addToParent($command); + } + + /** + * Add the command to the parent instance. + * + * @param \Symfony\Component\Console\Command $command + * @return \Symfony\Component\Console\Command + */ + protected function addToParent($command) + { + return parent::add($command); + } + + /** + * Add a command, resolving through the application. + * + * @param string $command + * @return void + */ + public function resolve($command) + { + return $this->add($this->laravel[$command]); + } + + /** + * Resolve an array of commands through the application. + * + * @param array|dynamic $commands + * @return void + */ + public function resolveCommands($commands) + { + $commands = is_array($commands) ? $commands : func_get_args(); + + foreach ($commands as $command) + { + $this->resolve($command); + } + } + + /** + * Get the default input definitions for the applications. + * + * @return \Symfony\Component\Console\Input\InputDefinition + */ + protected function getDefaultInputDefinition() + { + $definition = parent::getDefaultInputDefinition(); + + $definition->addOption($this->getEnvironmentOption()); + + return $definition; + } + + /** + * Get the global environment option for the definition. + * + * @return \Symfony\Component\Console\Input\InputOption + */ + protected function getEnvironmentOption() + { + $message = 'The environment the command should run under.'; + + return new InputOption('--env', null, InputOption::VALUE_OPTIONAL, $message); + } + + /** + * Render the given exception. + * + * @param Exception $e + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return void + */ + public function renderException($e, $output) + { + // If we have an exception handler instance, we will call that first in case + // it has some handlers that need to be run first. We will pass "true" as + // the second parameter to indicate that it's handling a console error. + if (isset($this->exceptionHandler)) + { + $this->exceptionHandler->handleConsole($e); + } + + return parent::renderException($e, $output); + } + + /** + * Set the exception handler instance. + * + * @param \Illuminate\Exception\Handler $handler + * @return void + */ + public function setExceptionHandler($handler) + { + $this->exceptionHandler = $handler; + } + + /** + * Set the Laravel application instance. + * + * @param \Illuminate\Foundation\Application $laravel + * @return void + */ + public function setLaravel($laravel) + { + $this->laravel = $laravel; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Console/Command.php b/vendor/laravel/framework/src/Illuminate/Console/Command.php new file mode 100755 index 0000000..6ecd33e --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Console/Command.php @@ -0,0 +1,317 @@ +name); + + // We will go ahead and set the name, description, and parameters on console + // commands just to make things a little easier on the developer. This is + // so they don't have to all be manually specified in the constructors. + $this->setDescription($this->description); + + $this->specifyParameters(); + } + + /** + * Specify the arguments and options on the command. + * + * @return void + */ + protected function specifyParameters() + { + // We will loop through all of the arguments and options for the command and + // set them all on the base command instance. This specifies what can get + // passed into these commands as "parameters" to control the execution. + foreach ($this->getArguments() as $arguments) + { + call_user_func_array(array($this, 'addArgument'), $arguments); + } + + foreach ($this->getOptions() as $options) + { + call_user_func_array(array($this, 'addOption'), $options); + } + } + + /** + * Run the console command. + * + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return integer + */ + public function run(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + + $this->output = $output; + + return parent::run($input, $output); + } + + /** + * Execute the console command. + * + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return mixed + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + return $this->fire(); + } + + /** + * Call another console command. + * + * @param string $command + * @param array $arguments + * @return integer + */ + public function call($command, array $arguments = array()) + { + $instance = $this->getApplication()->find($command); + + $arguments['command'] = $command; + + return $instance->run(new ArrayInput($arguments), $this->output); + } + + /** + * Call another console command silently. + * + * @param string $command + * @param array $arguments + * @return integer + */ + public function callSilent($command, array $arguments = array()) + { + $instance = $this->getApplication()->find($command); + + $arguments['command'] = $command; + + return $instance->run(new ArrayInput($arguments), new NullOutput); + } + + /** + * Get the value of a command argument. + * + * @param string $key + * @return string|array + */ + public function argument($key = null) + { + if (is_null($key)) return $this->input->getArguments(); + + return $this->input->getArgument($key); + } + + /** + * Get the value of a command option. + * + * @param string $key + * @return string|array + */ + public function option($key = null) + { + if (is_null($key)) return $this->input->getOptions(); + + return $this->input->getOption($key); + } + + /** + * Confirm a question with the user. + * + * @param string $question + * @param bool $default + * @return bool + */ + public function confirm($question, $default = true) + { + $dialog = $this->getHelperSet()->get('dialog'); + + return $dialog->askConfirmation($this->output, "$question", $default); + } + + /** + * Prompt the user for input. + * + * @param string $question + * @param string $default + * @return string + */ + public function ask($question, $default = null) + { + $dialog = $this->getHelperSet()->get('dialog'); + + return $dialog->ask($this->output, "$question", $default); + } + + /** + * Prompt the user for input but hide the answer from the console. + * + * @param string $question + * @param bool $fallabck + * @return string + */ + public function secret($question, $fallback = true) + { + $dialog = $this->getHelperSet()->get('dialog'); + + return $dialog->askHiddenResponse($this->output, "$question", $fallback); + } + + /** + * Write a string as standard output. + * + * @param string $string + * @return void + */ + public function line($string) + { + $this->output->writeln($string); + } + + /** + * Write a string as information output. + * + * @param string $string + * @return void + */ + public function info($string) + { + $this->output->writeln("$string"); + } + + /** + * Write a string as comment output. + * + * @param string $string + * @return void + */ + public function comment($string) + { + $this->output->writeln("$string"); + } + + /** + * Write a string as question output. + * + * @param string $string + * @return void + */ + public function question($string) + { + $this->output->writeln("$string"); + } + + /** + * Write a string as error output. + * + * @param string $string + * @return void + */ + public function error($string) + { + $this->output->writeln("$string"); + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return array(); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array(); + } + + /** + * Get the output implementation. + * + * @return \Symfony\Component\Console\Output\OutputInterface + */ + public function getOutput() + { + return $this->output; + } + + /** + * Set the Laravel application instance. + * + * @return \Illuminate\Foundation\Application + */ + public function getLaravel() + { + return $this->laravel; + } + + /** + * Set the Laravel application instance. + * + * @param \Illuminate\Foundation\Application $laravel + * @return void + */ + public function setLaravel($laravel) + { + $this->laravel = $laravel; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Console/composer.json b/vendor/laravel/framework/src/Illuminate/Console/composer.json new file mode 100755 index 0000000..98c7bed --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Console/composer.json @@ -0,0 +1,28 @@ +{ + "name": "illuminate/console", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "symfony/console": "2.3.*" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Console": "" + } + }, + "target-dir": "Illuminate/Console", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Console/start.php b/vendor/laravel/framework/src/Illuminate/Console/start.php new file mode 100755 index 0000000..8f5a635 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Console/start.php @@ -0,0 +1,59 @@ +instance('artisan', $artisan); + +/* +|-------------------------------------------------------------------------- +| Set The Laravel Exception Handler +|-------------------------------------------------------------------------- +| +| We'll go ahead and set the Laravel exception handler so the console can +| call these handler when an exception is thrown from the CLI. This is +| important since there could be loggers, etc. setup for exceptions. +| +*/ + +$artisan->setExceptionHandler($app['exception']); + +/* +|-------------------------------------------------------------------------- +| Set The Laravel Application +|-------------------------------------------------------------------------- +| +| When creating the Artisan application, we will set the Laravel app on +| the console so that we can easily access it from our commands when +| necessary, which allows us to quickly access other app services. +| +*/ + +$artisan->setLaravel($app); + +/* +|-------------------------------------------------------------------------- +| Register The Artisan Commands +|-------------------------------------------------------------------------- +| +| Each available Artisan command must be registered with the console so +| that it is available to be called. We'll register every command so +| the console gets access to each of the command object instances. +| +*/ + +require $app['path'].'/start/artisan.php'; + +return $artisan; \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Container/Container.php b/vendor/laravel/framework/src/Illuminate/Container/Container.php new file mode 100755 index 0000000..660efa4 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Container/Container.php @@ -0,0 +1,534 @@ +instances[$abstract]); + } + + /** + * Register a binding with the container. + * + * @param string $abstract + * @param Closure|string|null $concrete + * @param bool $shared + * @return void + */ + public function bind($abstract, $concrete = null, $shared = false) + { + // If the given type is actually an array, we'll assume an alias is being + // defined and will grab the real abstract class name and register the + // alias with the container so it can be used as a short-cut for it. + if (is_array($abstract)) + { + list($abstract, $alias) = $this->extractAlias($abstract); + + $this->alias($abstract, $alias); + } + + // If no concrete type was given, we will simply set the concrete type to + // the abstract. This allows concrete types to be registered as shared + // without being made state their classes in both of the parameters. + unset($this->instances[$abstract]); + + if (is_null($concrete)) + { + $concrete = $abstract; + } + + // If the factory is not a Closure, it means it is just a class name that + // is bound into the container to an abstract type and we'll just wrap + // it up in a Closure to make things more convenient when extending. + if ( ! $concrete instanceof Closure) + { + $concrete = function($c) use ($abstract, $concrete) + { + $method = ($abstract == $concrete) ? 'build' : 'make'; + + return $c->$method($concrete); + }; + } + + $this->bindings[$abstract] = compact('concrete', 'shared'); + } + + /** + * Register a binding if it hasn't already been registered. + * + * @param string $abstract + * @param Closure|string|null $concrete + * @param bool $shared + * @return bool + */ + public function bindIf($abstract, $concrete = null, $shared = false) + { + if ( ! $this->bound($abstract)) + { + $this->bind($abstract, $concrete, $shared); + } + } + + /** + * Register a shared binding in the container. + * + * @param string $abstract + * @param Closure|string|null $concrete + * @return void + */ + public function singleton($abstract, $concrete = null) + { + return $this->bind($abstract, $concrete, true); + } + + /** + * Wrap a Closure such that it is shared. + * + * @param Closure $closure + * @return Closure + */ + public function share(Closure $closure) + { + return function($container) use ($closure) + { + // We'll simply declare a static variable within the Closures and if + // it has not been set we'll execute the given Closure to resolve + // the value and return it back to the consumers of the method. + static $object; + + if (is_null($object)) + { + $object = $closure($container); + } + + return $object; + }; + } + + /** + * "Extend" an abstract type in the container. + * + * @param string $abstract + * @param Closure $closure + * @return void + */ + public function extend($abstract, Closure $closure) + { + if ( ! isset($this->bindings[$abstract])) + { + throw new \InvalidArgumentException("Type {$abstract} is not bound."); + } + + // To "extend" a binding, we will grab the old "resolver" Closure and pass it + // into a new one. The old resolver will be called first and the result is + // handed off to the "new" resolver, along with this container instance. + $resolver = $this->bindings[$abstract]['concrete']; + + $this->bind($abstract, function($container) use ($resolver, $closure) + { + return $closure($resolver($container), $container); + + }, $this->isShared($abstract)); + } + + /** + * Register an existing instance as shared in the container. + * + * @param string $abstract + * @param mixed $instance + * @return void + */ + public function instance($abstract, $instance) + { + if (is_array($abstract)) + { + list($abstract, $alias) = $this->extractAlias($abstract); + + $this->alias($abstract, $alias); + } + + $this->instances[$abstract] = $instance; + } + + /** + * Alias a type to a shorter name. + * + * @param string $abstract + * @param string $alias + * @return void + */ + public function alias($abstract, $alias) + { + $this->aliases[$alias] = $abstract; + } + + /** + * Extract the type and alias from a given definition. + * + * @param array $definition + * @return array + */ + protected function extractAlias(array $definition) + { + return array(key($definition), current($definition)); + } + + /** + * Resolve the given type from the container. + * + * @param string $abstract + * @param array $parameters + * @return mixed + */ + public function make($abstract, $parameters = array()) + { + $abstract = $this->getAlias($abstract); + + // If an instance of the type is currently being managed as a singleton we'll + // just return an existing instance instead of instantiating new instances + // so the developer can keep using the same objects instance every time. + if (isset($this->instances[$abstract])) + { + return $this->instances[$abstract]; + } + + $concrete = $this->getConcrete($abstract); + + // We're ready to instantiate an instance of the concrete type registered for + // the binding. This will instantiate the types, as well as resolve any of + // its "nested" dependencies recursively until all have gotten resolved. + if ($this->isBuildable($concrete, $abstract)) + { + $object = $this->build($concrete, $parameters); + } + else + { + $object = $this->make($concrete, $parameters); + } + + // If the requested type is registered as a singleton we'll want to cache off + // the instances in "memory" so we can return it later without creating an + // entirely new instance of an object on each subsequent request for it. + if ($this->isShared($abstract)) + { + $this->instances[$abstract] = $object; + } + + $this->fireResolvingCallbacks($object); + + return $object; + } + + /** + * Get the concrete type for a given abstract. + * + * @param string $abstract + * @return mixed $concrete + */ + protected function getConcrete($abstract) + { + // If we don't have a registered resolver or concrete for the type, we'll just + // assume each type is a concrete name and will attempt to resolve it as is + // since the container should be able to resolve concretes automatically. + if ( ! isset($this->bindings[$abstract])) + { + return $abstract; + } + else + { + return $this->bindings[$abstract]['concrete']; + } + } + + /** + * Instantiate a concrete instance of the given type. + * + * @param string $concrete + * @param array $parameters + * @return mixed + */ + public function build($concrete, $parameters = array()) + { + // If the concrete type is actually a Closure, we will just execute it and + // hand back the results of the functions, which allows functions to be + // used as resolvers for more fine-tuned resolution of these objects. + if ($concrete instanceof Closure) + { + return $concrete($this, $parameters); + } + + $reflector = new \ReflectionClass($concrete); + + // If the type is not instantiable, the developer is attempting to resolve + // an abstract type such as an Interface of Abstract Class and there is + // no binding registered for the abstractions so we need to bail out. + if ( ! $reflector->isInstantiable()) + { + $message = "Target [$concrete] is not instantiable."; + + throw new BindingResolutionException($message); + } + + $constructor = $reflector->getConstructor(); + + // If there are no constructors, that means there are no dependencies then + // we can just resolve the instances of the objects right away, without + // resolving any other types or dependencies out of these containers. + if (is_null($constructor)) + { + return new $concrete; + } + + $parameters = $constructor->getParameters(); + + // Once we have all the constructor's parameters we can create each of the + // dependency instances and then use the reflection instances to make a + // new instance of this class, injecting the created dependencies in. + $dependencies = $this->getDependencies($parameters); + + return $reflector->newInstanceArgs($dependencies); + } + + /** + * Resolve all of the dependencies from the ReflectionParameters. + * + * @param array $parameters + * @return array + */ + protected function getDependencies($parameters) + { + $dependencies = array(); + + foreach ($parameters as $parameter) + { + $dependency = $parameter->getClass(); + + // If the class is null, it means the dependency is a string or some other + // primitive type which we can not resolve since it is not a class and + // we'll just bomb out with an error since we have no-where to go. + if (is_null($dependency)) + { + $dependencies[] = $this->resolveNonClass($parameter); + } + else + { + $dependencies[] = $this->resolveClass($parameter); + } + } + + return (array) $dependencies; + } + + /** + * Resolve a non-class hinted dependency. + * + * @param ReflectionParameter $parameter + * @return mixed + */ + protected function resolveNonClass(ReflectionParameter $parameter) + { + if ($parameter->isDefaultValueAvailable()) + { + return $parameter->getDefaultValue(); + } + else + { + $message = "Unresolvable dependency resolving [$parameter]."; + + throw new BindingResolutionException($message); + } + } + + /** + * Resolve a class based dependency from the container. + * + * @param \ReflectionParameter $parameter + * @return mixed + */ + protected function resolveClass(ReflectionParameter $parameter) + { + try + { + return $this->make($parameter->getClass()->name); + } + + // If we can not resolve the class instance, we will check to see if the value + // is optional, and if it is we will return the optional parameter value as + // the value of the dependency, similarly to how we do this with scalars. + catch (BindingResolutionException $e) + { + if ($parameter->isOptional()) + { + return $parameter->getDefaultValue(); + } + else + { + throw $e; + } + } + } + + /** + * Register a new resolving callback. + * + * @param Closure $callback + * @return void + */ + public function resolving(Closure $callback) + { + $this->resolvingCallbacks[] = $callback; + } + + /** + * Fire all of the resolving callbacks. + * + * @param mixed $object + * @return void + */ + protected function fireResolvingCallbacks($object) + { + foreach ($this->resolvingCallbacks as $callback) + { + call_user_func($callback, $object); + } + } + + /** + * Determine if a given type is shared. + * + * @param string $abstract + * @return bool + */ + protected function isShared($abstract) + { + $set = isset($this->bindings[$abstract]['shared']); + + return $set and $this->bindings[$abstract]['shared'] === true; + } + + /** + * Determine if the given concrete is buildable. + * + * @param mixed $concrete + * @param string $abstract + * @return bool + */ + protected function isBuildable($concrete, $abstract) + { + return $concrete === $abstract or $concrete instanceof Closure; + } + + /** + * Get the alias for an abstract if available. + * + * @param string $abstract + * @return string + */ + protected function getAlias($abstract) + { + return isset($this->aliases[$abstract]) ? $this->aliases[$abstract] : $abstract; + } + + /** + * Get the container's bindings. + * + * @return array + */ + public function getBindings() + { + return $this->bindings; + } + + /** + * Determine if a given offset exists. + * + * @param string $key + * @return bool + */ + public function offsetExists($key) + { + return isset($this->bindings[$key]); + } + + /** + * Get the value at a given offset. + * + * @param string $key + * @return mixed + */ + public function offsetGet($key) + { + return $this->make($key); + } + + /** + * Set the value at a given offset. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value) + { + // If the value is not a Closure, we will make it one. This simply gives + // more "drop-in" replacement functionality for the Pimple which this + // container's simplest functions are base modeled and built after. + if ( ! $value instanceof Closure) + { + $value = function() use ($value) + { + return $value; + }; + } + + $this->bind($key, $value); + } + + /** + * Unset the value at a given offset. + * + * @param string $key + * @return void + */ + public function offsetUnset($key) + { + unset($this->bindings[$key]); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Container/composer.json b/vendor/laravel/framework/src/Illuminate/Container/composer.json new file mode 100755 index 0000000..84b4fab --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Container/composer.json @@ -0,0 +1,28 @@ +{ + "name": "illuminate/container", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Container": "" + } + }, + "target-dir": "Illuminate/Container", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Cookie/CookieJar.php b/vendor/laravel/framework/src/Illuminate/Cookie/CookieJar.php new file mode 100755 index 0000000..89c3c2b --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cookie/CookieJar.php @@ -0,0 +1,199 @@ +request = $request; + $this->encrypter = $encrypter; + } + + /** + * Determine if a cookie exists and is not null. + * + * @param string $key + * @return bool + */ + public function has($key) + { + return ! is_null($this->get($key)); + } + + /** + * Get the value of the given cookie. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + $value = $this->request->cookies->get($key); + + if ( ! is_null($value)) + { + return $this->decrypt($value); + } + + return $default instanceof Closure ? $default() : $default; + } + + /** + * Decrypt the given cookie value. + * + * @param string $value + * @return mixed|null + */ + protected function decrypt($value) + { + try + { + return $this->encrypter->decrypt($value); + } + catch (\Exception $e) + { + return null; + } + } + + /** + * Create a new cookie instance. + * + * @param string $name + * @param string $value + * @param int $minutes + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $httpOnly + * @return \Symfony\Component\HttpFoundation\Cookie + */ + public function make($name, $value, $minutes = 0, $path = null, $domain = null, $secure = false, $httpOnly = true) + { + list($path, $domain) = $this->getPathAndDomain($path, $domain); + + // Once we calculate the time we can encrypt the message. All cookies will be + // encrypted using the Illuminate encryption component and will have a MAC + // assigned to them by the encrypter to make sure they remain authentic. + $time = ($minutes == 0) ? 0 : time() + ($minutes * 60); + + $value = $this->encrypter->encrypt($value); + + return new Cookie($name, $value, $time, $path, $domain, $secure, $httpOnly); + } + + /** + * Create a cookie that lasts "forever" (five years). + * + * @param string $name + * @param string $value + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $httpOnly + * @return \Symfony\Component\HttpFoundation\Cookie + */ + public function forever($name, $value, $path = null, $domain = null, $secure = false, $httpOnly = true) + { + return $this->make($name, $value, 2628000, $path, $domain, $secure, $httpOnly); + } + + /** + * Expire the given cookie. + * + * @param string $name + * @return \Symfony\Component\HttpFoundation\Cookie + */ + public function forget($name) + { + return $this->make($name, null, -2628000); + } + + /** + * Get the path and domain, or the default values. + * + * @param string $path + * @param string $domain + * @return array + */ + protected function getPathAndDomain($path, $domain) + { + return array($path ?: $this->path, $domain ?: $this->domain); + } + + /** + * Set the default path and domain for the jar. + * + * @param string $path + * @param string $domain + * @return void + */ + public function setDefaultPathAndDomain($path, $domain) + { + list($this->path, $this->domain) = array($path, $domain); + + return $this; + } + + /** + * Get the request instance. + * + * @return \Symfony\Component\HttpFoundation\Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * Get the encrypter instance. + * + * @return \Illuminate\Encryption\Encrypter + */ + public function getEncrypter() + { + return $this->encrypter; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Cookie/CookieServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Cookie/CookieServiceProvider.php new file mode 100755 index 0000000..6150fa1 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cookie/CookieServiceProvider.php @@ -0,0 +1,24 @@ +app['cookie'] = $this->app->share(function($app) + { + $cookies = new CookieJar($app['request'], $app['encrypter']); + + $config = $app['config']['session']; + + return $cookies->setDefaultPathAndDomain($config['path'], $config['domain']); + }); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Cookie/composer.json b/vendor/laravel/framework/src/Illuminate/Cookie/composer.json new file mode 100755 index 0000000..878c126 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Cookie/composer.json @@ -0,0 +1,32 @@ +{ + "name": "illuminate/cookie", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "illuminate/encryption": "4.0.x", + "illuminate/support": "4.0.x", + "symfony/http-foundation": "2.3.*" + }, + "require-dev": { + "mockery/mockery": "0.7.2", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Cookie": "" + } + }, + "target-dir": "Illuminate/Cookie", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Capsule/Manager.php b/vendor/laravel/framework/src/Illuminate/Database/Capsule/Manager.php new file mode 100644 index 0000000..dbeec31 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Capsule/Manager.php @@ -0,0 +1,259 @@ +setupContainer($container); + + // Once we have the container setup, we will setup the default configuration + // options in the container "config" binding. This will make the database + // manager behave correctly since all the correct binding are in place. + $this->setupDefaultConfiguration(); + + $this->setupManager(); + } + + /** + * Setup the IoC container instance. + * + * @param \Illuminate\Container\Container $container + * @return void + */ + protected function setupContainer($container) + { + $this->container = $container ?: new Container; + + $this->container->instance('config', new Fluent); + } + + /** + * Setup the default database configuration options. + * + * @return void + */ + protected function setupDefaultConfiguration() + { + $this->container['config']['database.fetch'] = PDO::FETCH_ASSOC; + + $this->container['config']['database.default'] = 'default'; + } + + /** + * Build the database manager instance. + * + * @return void + */ + protected function setupManager() + { + $factory = new ConnectionFactory($this->container); + + $this->manager = new DatabaseManager($this->container, $factory); + } + + /** + * Get a connection instance from the global manager. + * + * @param string $connection + * @return \Illuminate\Database\Connection + */ + public static function connection($connection = null) + { + return static::$instance->getConnection($connection); + } + + /** + * Get a fluent query builder instance. + * + * @param string $table + * @param string $connection + * @return \Illuminate\Database\Query\Builder + */ + public static function table($table, $connection = null) + { + return static::$instance->connection($connection)->table($table); + } + + /** + * Get a schema builder instance. + * + * @param string $connection + * @return \Illuminate\Database\Schema\Builder + */ + public static function schema($connection = null) + { + return static::$instance->connection($connection)->getSchemaBuilder(); + } + + /** + * Get a registered connection instance. + * + * @param string $name + * @return \Illuminate\Database\Connection + */ + public function getConnection($name = null) + { + return $this->manager->connection($name); + } + + /** + * Register a connection with the manager. + * + * @param array $config + * @param string $name + * @return void + */ + public function addConnection(array $config, $name = 'default') + { + $connections = $this->container['config']['database.connections']; + + $connections[$name] = $config; + + $this->container['config']['database.connections'] = $connections; + } + + /** + * Bootstrap Eloquent so it is ready for usage. + * + * @return void + */ + public function bootEloquent() + { + Eloquent::setConnectionResolver($this->manager); + + // If we have an event dispatcher instance, we will go ahead and register it + // with the Eloquent ORM, allowing for model callbacks while creating and + // updating "model" instances; however, if it not necessary to operate. + if ($dispatcher = $this->getEventDispatcher()) + { + Eloquent::setEventDispatcher($dispatcher); + } + } + + /** + * Set the fetch mode for the database connections. + * + * @param int $fetchMode + * @return \Illuminate\Database\Capsule\Manager + */ + public function setFetchMode($fetchMode) + { + $this->container['config']['database.fetch'] = $fetchMode; + + return $this; + } + + /** + * Make this capsule instance available globally. + * + * @return void + */ + public function setAsGlobal() + { + static::$instance = $this; + } + + /** + * Get the current event dispatcher instance. + * + * @return \Illuminate\Events\Dispatcher + */ + public function getEventDispatcher() + { + if ($this->container->bound('events')) + { + return $this->container['events']; + } + } + + /** + * Set the event dispatcher instance to be used by connections. + * + * @param \Illuminate\Events\Dispatcher $dispatcher + * @return void + */ + public function setEventDispatcher(Dispatcher $dispatcher) + { + $this->container->instance('events', $dispatcher); + } + + /** + * Get the current cache manager instance. + * + * @return \Illuminate\Cache\Manager + */ + public function getCacheManager() + { + if ($this->container->bound('cache')) + { + return $this->container['cache']; + } + } + + /** + * Set the cache manager to bse used by connections. + * + * @param \Illuminate\Cache\CacheManager $cache + * @return void + */ + public function setCacheManager(CacheManager $cache) + { + $this->container->instance('cache', $cache); + } + + /** + * Get the IoC container instance. + * + * @return \Illuminate\Container\Container + */ + public function getContainer() + { + return $this->container; + } + + /** + * Set the IoC container instance. + * + * @param \Illuminate\Container\Container $container + * @return void + */ + public function setContainer(Container $container) + { + $this->container = $container; + } + + /** + * Dynamically pass methods to the default connection. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + return call_user_func_array(array(static::connection(), $method), $parameters); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Connection.php b/vendor/laravel/framework/src/Illuminate/Database/Connection.php new file mode 100755 index 0000000..ff71bac --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Connection.php @@ -0,0 +1,946 @@ +pdo = $pdo; + + // First we will setup the default properties. We keep track of the DB + // name we are connected to since it is needed when some reflective + // type commands are run such as checking whether a table exists. + $this->database = $database; + + $this->tablePrefix = $tablePrefix; + + $this->config = $config; + + // We need to initialize a query grammar and the query post processors + // which are both very important parts of the database abstractions + // so we initialize these to their default values while starting. + $this->useDefaultQueryGrammar(); + + $this->useDefaultPostProcessor(); + } + + /** + * Set the query grammar to the default implementation. + * + * @return void + */ + public function useDefaultQueryGrammar() + { + $this->queryGrammar = $this->getDefaultQueryGrammar(); + } + + /** + * Get the default query grammar instance. + * + * @return \Illuminate\Database\Query\Grammars\Grammar + */ + protected function getDefaultQueryGrammar() + { + return new Query\Grammars\Grammar; + } + + /** + * Set the schema grammar to the default implementation. + * + * @return void + */ + public function useDefaultSchemaGrammar() + { + $this->schemaGrammar = $this->getDefaultSchemaGrammar(); + } + + /** + * Get the default schema grammar instance. + * + * @return \Illuminate\Database\Schema\Grammars\Grammar + */ + protected function getDefaultSchemaGrammar() {} + + /** + * Set the query post processor to the default implementation. + * + * @return void + */ + public function useDefaultPostProcessor() + { + $this->postProcessor = $this->getDefaultPostProcessor(); + } + + /** + * Get the default post processor instance. + * + * @return \Illuminate\Database\Query\Processors\Processor + */ + protected function getDefaultPostProcessor() + { + return new Query\Processors\Processor; + } + + /** + * Get a schema builder instance for the connection. + * + * @return \Illuminate\Database\Schema\Builder + */ + public function getSchemaBuilder() + { + if (is_null($this->schemaGrammar)) { $this->useDefaultSchemaGrammar(); } + + return new Schema\Builder($this); + } + + /** + * Begin a fluent query against a database table. + * + * @param string $table + * @return \Illuminate\Database\Query\Builder + */ + public function table($table) + { + $processor = $this->getPostProcessor(); + + $query = new Query\Builder($this, $this->getQueryGrammar(), $processor); + + return $query->from($table); + } + + /** + * Get a new raw query expression. + * + * @param mixed $value + * @return \Illuminate\Database\Query\Expression + */ + public function raw($value) + { + return new Query\Expression($value); + } + + /** + * Run a select statement and return a single result. + * + * @param string $query + * @param array $bindings + * @return mixed + */ + public function selectOne($query, $bindings = array()) + { + $records = $this->select($query, $bindings); + + return count($records) > 0 ? reset($records) : null; + } + + /** + * Run a select statement against the database. + * + * @param string $query + * @param array $bindings + * @return array + */ + public function select($query, $bindings = array()) + { + return $this->run($query, $bindings, function($me, $query, $bindings) + { + if ($me->pretending()) return array(); + + // For select statements, we'll simply execute the query and return an array + // of the database result set. Each element in the array will be a single + // row from the database table, and will either be an array or objects. + $statement = $me->getPdo()->prepare($query); + + $statement->execute($me->prepareBindings($bindings)); + + return $statement->fetchAll($me->getFetchMode()); + }); + } + + /** + * Run an insert statement against the database. + * + * @param string $query + * @param array $bindings + * @return bool + */ + public function insert($query, $bindings = array()) + { + return $this->statement($query, $bindings); + } + + /** + * Run an update statement against the database. + * + * @param string $query + * @param array $bindings + * @return int + */ + public function update($query, $bindings = array()) + { + return $this->affectingStatement($query, $bindings); + } + + /** + * Run a delete statement against the database. + * + * @param string $query + * @param array $bindings + * @return int + */ + public function delete($query, $bindings = array()) + { + return $this->affectingStatement($query, $bindings); + } + + /** + * Execute an SQL statement and return the boolean result. + * + * @param string $query + * @param array $bindings + * @return bool + */ + public function statement($query, $bindings = array()) + { + return $this->run($query, $bindings, function($me, $query, $bindings) + { + if ($me->pretending()) return true; + + $bindings = $me->prepareBindings($bindings); + + return $me->getPdo()->prepare($query)->execute($bindings); + }); + } + + /** + * Run an SQL statement and get the number of rows affected. + * + * @param string $query + * @param array $bindings + * @return int + */ + public function affectingStatement($query, $bindings = array()) + { + return $this->run($query, $bindings, function($me, $query, $bindings) + { + if ($me->pretending()) return 0; + + // For update or delete statements, we want to get the number of rows affected + // by the statement and return that back to the developer. We'll first need + // to execute the statement and then we'll use PDO to fetch the affected. + $statement = $me->getPdo()->prepare($query); + + $statement->execute($me->prepareBindings($bindings)); + + return $statement->rowCount(); + }); + } + + /** + * Run a raw, unprepared query against the PDO connection. + * + * @param string $query + * @return bool + */ + public function unprepared($query) + { + return $this->run($query, array(), function($me, $query, $bindings) + { + if ($me->pretending()) return true; + + return (bool) $me->getPdo()->exec($query); + }); + } + + /** + * Prepare the query bindings for execution. + * + * @param array $bindings + * @return array + */ + public function prepareBindings(array $bindings) + { + $grammar = $this->getQueryGrammar(); + + foreach ($bindings as $key => $value) + { + // We need to transform all instances of the DateTime class into an actual + // date string. Each query grammar maintains its own date string format + // so we'll just ask the grammar for the format to get from the date. + if ($value instanceof DateTime) + { + $bindings[$key] = $value->format($grammar->getDateFormat()); + } + elseif ($value === false) + { + $bindings[$key] = 0; + } + } + + return $bindings; + } + + /** + * Execute a Closure within a transaction. + * + * @param Closure $callback + * @return mixed + */ + public function transaction(Closure $callback) + { + $this->beginTransaction(); + + // We'll simply execute the given callback within a try / catch block + // and if we catch any exception we can rollback the transaction + // so that none of the changes are persisted to the database. + try + { + $result = $callback($this); + + $this->commit(); + } + + // If we catch an exception, we will roll back so nothing gets messed + // up in the database. Then we'll re-throw the exception so it can + // be handled how the developer sees fit for their applications. + catch (\Exception $e) + { + $this->rollBack(); + + throw $e; + } + + return $result; + } + + /** + * Start a new database transaction. + * + * @return void + */ + public function beginTransaction() + { + ++$this->transactions; + + if ($this->transactions == 1) + { + $this->pdo->beginTransaction(); + } + } + + /** + * Commit the active database transaction. + * + * @return void + */ + public function commit() + { + if ($this->transactions == 1) $this->pdo->commit(); + + --$this->transactions; + } + + /** + * Rollback the active database transaction. + * + * @return void + */ + public function rollBack() + { + if ($this->transactions == 1) + { + $this->transactions = 0; + + $this->pdo->rollBack(); + } + else + { + --$this->transactions; + } + } + + /** + * Execute the given callback in "dry run" mode. + * + * @param Closure $callback + * @return array + */ + public function pretend(Closure $callback) + { + $this->pretending = true; + + $this->queryLog = array(); + + // Basically to make the database connection "pretend", we will just return + // the default values for all the query methods, then we will return an + // array of queries that were "executed" within the Closure callback. + $callback($this); + + $this->pretending = false; + + return $this->queryLog; + } + + /** + * Run a SQL statement and log its execution context. + * + * @param string $query + * @param array $bindings + * @param Closure $callback + * @return mixed + */ + protected function run($query, $bindings, Closure $callback) + { + $start = microtime(true); + + // To execute the statement, we'll simply call the callback, which will actually + // run the SQL against the PDO connection. Then we can calculate the time it + // took to execute and log the query SQL, bindings and time in our memory. + try + { + $result = $callback($this, $query, $bindings); + } + + // If an exception occurs when attempting to run a query, we'll format the error + // message to include the bindings with SQL, which will make this exception a + // lot more helpful to the developer instead of just the database's errors. + catch (\Exception $e) + { + $this->handleQueryException($e, $query, $bindings); + } + + // Once we have run the query we will calculate the time that it took to run and + // then log the query, bindings, and execution time so we will report them on + // the event that the developer needs them. We'll log time in milliseconds. + $time = $this->getElapsedTime($start); + + $this->logQuery($query, $bindings, $time); + + return $result; + } + + /** + * Handle an exception that occurred during a query. + * + * @param Exception $e + * @param string $query + * @param array $bindings + * @return void + */ + protected function handleQueryException(\Exception $e, $query, $bindings) + { + $bindings = var_export($bindings, true); + + $message = $e->getMessage()." (SQL: {$query}) (Bindings: {$bindings})"; + + throw new \Exception($message, 0, $e); + } + + /** + * Log a query in the connection's query log. + * + * @param string $query + * @param array $bindings + * @param $time + * @return void + */ + public function logQuery($query, $bindings, $time = null) + { + if (isset($this->events)) + { + $this->events->fire('illuminate.query', array($query, $bindings, $time, $this->getName())); + } + + if ( ! $this->loggingQueries) return; + + $this->queryLog[] = compact('query', 'bindings', 'time'); + } + + /** + * Register a database query listener with the connection. + * + * @param Closure $callback + * @return void + */ + public function listen(Closure $callback) + { + if (isset($this->events)) + { + $this->events->listen('illuminate.query', $callback); + } + } + + /** + * Get the elapsed time since a given starting point. + * + * @param int $start + * @return float + */ + protected function getElapsedTime($start) + { + return round((microtime(true) - $start) * 1000, 2); + } + + /** + * Get a Doctrine Schema Column instance. + * + * @param string $table + * @param string $column + * @return \Doctrine\DBAL\Schema\Column + */ + public function getDoctrineColumn($table, $column) + { + $schema = $this->getDoctrineSchemaManager(); + + return $schema->listTableDetails($table)->getColumn($column); + } + + /** + * Get the Doctrine DBAL schema manager for the connection. + * + * @return \Doctrine\DBAL\Schema\AbstractSchemaManager + */ + public function getDoctrineSchemaManager() + { + return $this->getDoctrineDriver()->getSchemaManager($this->getDoctrineConnection()); + } + + /** + * Get the Doctrine DBAL database connection instance. + * + * @return \Doctrine\DBAL\Connection + */ + public function getDoctrineConnection() + { + $driver = $this->getDoctrineDriver(); + + $data = array('pdo' => $this->pdo, 'dbname' => $this->getConfig('database')); + + return new \Doctrine\DBAL\Connection($data, $driver); + } + + /** + * Get the currently used PDO connection. + * + * @return PDO + */ + public function getPdo() + { + return $this->pdo; + } + + /** + * Get the database connection name. + * + * @return string|null + */ + public function getName() + { + return $this->getConfig('name'); + } + + /** + * Get an option from the configuration options. + * + * @param string $option + * @return mixed + */ + public function getConfig($option) + { + return array_get($this->config, $option); + } + + /** + * Get the PDO driver name. + * + * @return string + */ + public function getDriverName() + { + return $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + } + + /** + * Get the query grammar used by the connection. + * + * @return \Illuminate\Database\Query\Grammars\Grammar + */ + public function getQueryGrammar() + { + return $this->queryGrammar; + } + + /** + * Set the query grammar used by the connection. + * + * @param \Illuminate\Database\Query\Grammars\Grammar + * @return void + */ + public function setQueryGrammar(Query\Grammars\Grammar $grammar) + { + $this->queryGrammar = $grammar; + } + + /** + * Get the schema grammar used by the connection. + * + * @return \Illuminate\Database\Query\Grammars\Grammar + */ + public function getSchemaGrammar() + { + return $this->schemaGrammar; + } + + /** + * Set the schema grammar used by the connection. + * + * @param \Illuminate\Database\Schema\Grammars\Grammar + * @return void + */ + public function setSchemaGrammar(Schema\Grammars\Grammar $grammar) + { + $this->schemaGrammar = $grammar; + } + + /** + * Get the query post processor used by the connection. + * + * @return \Illuminate\Database\Query\Processors\Processor + */ + public function getPostProcessor() + { + return $this->postProcessor; + } + + /** + * Set the query post processor used by the connection. + * + * @param \Illuminate\Database\Query\Processors\Processor + * @return void + */ + public function setPostProcessor(Processor $processor) + { + $this->postProcessor = $processor; + } + + /** + * Get the event dispatcher used by the connection. + * + * @return \Illuminate\Events\Dispatcher + */ + public function getEventDispatcher() + { + return $this->events; + } + + /** + * Set the event dispatcher instance on the connection. + * + * @param \Illuminate\Events\Dispatcher + * @return void + */ + public function setEventDispatcher(\Illuminate\Events\Dispatcher $events) + { + $this->events = $events; + } + + /** + * Get the paginator environment instance. + * + * @return \Illuminate\Pagination\Environment + */ + public function getPaginator() + { + if ($this->paginator instanceof Closure) + { + $this->paginator = call_user_func($this->paginator); + } + + return $this->paginator; + } + + /** + * Set the pagination environment instance. + * + * @param \Illuminate\Pagination\Environment|\Closure $paginator + * @return void + */ + public function setPaginator($paginator) + { + $this->paginator = $paginator; + } + + /** + * Get the cache manager instance. + * + * @return \Illuminate\Cache\CacheManager + */ + public function getCacheManager() + { + if ($this->cache instanceof Closure) + { + $this->cache = call_user_func($this->cache); + } + + return $this->cache; + } + + /** + * Set the cache manager instance on the connection. + * + * @param \Illuminate\Cache\CacheManager|\Closure $cache + * @return void + */ + public function setCacheManager($cache) + { + $this->cache = $cache; + } + + /** + * Determine if the connection in a "dry run". + * + * @return bool + */ + public function pretending() + { + return $this->pretending === true; + } + + /** + * Get the default fetch mode for the connection. + * + * @return int + */ + public function getFetchMode() + { + return $this->fetchMode; + } + + /** + * Set the default fetch mode for the connection. + * + * @param int $fetchMode + * @return int + */ + public function setFetchMode($fetchMode) + { + $this->fetchMode = $fetchMode; + } + + /** + * Get the connection query log. + * + * @return array + */ + public function getQueryLog() + { + return $this->queryLog; + } + + /** + * Clear the query log. + * + * @return void + */ + public function flushQueryLog() + { + $this->queryLog = array(); + } + + /** + * Enable the query log on the connection. + * + * @return void + */ + public function enableQueryLog() + { + $this->loggingQueries = true; + } + + /** + * Disable the query log on the connection. + * + * @return void + */ + public function disableQueryLog() + { + $this->loggingQueries = false; + } + + /** + * Get the name of the connected database. + * + * @return string + */ + public function getDatabaseName() + { + return $this->database; + } + + /** + * Set the name of the connected database. + * + * @param string $database + * @return string + */ + public function setDatabaseName($database) + { + $this->database = $database; + } + + /** + * Get the table prefix for the connection. + * + * @return string + */ + public function getTablePrefix() + { + return $this->tablePrefix; + } + + /** + * Set the table prefix in use by the connection. + * + * @param string $prefix + * @return void + */ + public function setTablePrefix($prefix) + { + $this->tablePrefix = $prefix; + + $this->getQueryGrammar()->setTablePrefix($prefix); + } + + /** + * Set the table prefix and return the grammar. + * + * @param \Illuminate\Database\Grammar $grammar + * @return \Illuminate\Database\Grammar + */ + public function withTablePrefix(Grammar $grammar) + { + $grammar->setTablePrefix($this->tablePrefix); + + return $grammar; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Database/ConnectionInterface.php b/vendor/laravel/framework/src/Illuminate/Database/ConnectionInterface.php new file mode 100755 index 0000000..86fbd7e --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/ConnectionInterface.php @@ -0,0 +1,67 @@ + $connection) + { + $this->addConnection($name, $connection); + } + } + + /** + * Get a database connection instance. + * + * @param string $name + * @return \Illuminate\Database\Connection + */ + public function connection($name = null) + { + if (is_null($name)) $name = $this->getDefaultConnection(); + + return $this->connections[$name]; + } + + /** + * Add a connection to the resolver. + * + * @param string $name + * @param \Illuminate\Database\Connection $connection + * @return void + */ + public function addConnection($name, Connection $connection) + { + $this->connections[$name] = $connection; + } + + /** + * Check if a connection has been registered. + * + * @param string $name + * @return bool + */ + public function hasConnection($name) + { + return isset($this->connections[$name]); + } + + /** + * Get the default connection name. + * + * @return string + */ + public function getDefaultConnection() + { + return $this->default; + } + + /** + * Set the default connection name. + * + * @param string $name + * @return void + */ + public function setDefaultConnection($name) + { + $this->default = $name; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/ConnectionResolverInterface.php b/vendor/laravel/framework/src/Illuminate/Database/ConnectionResolverInterface.php new file mode 100755 index 0000000..7e9cfd6 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/ConnectionResolverInterface.php @@ -0,0 +1,28 @@ +container = $container; + } + + /** + * Establish a PDO connection based on the configuration. + * + * @param array $config + * @param string $name + * @return \Illuminate\Database\Connection + */ + public function make(array $config, $name = null) + { + $config = $this->parseConfig($config, $name); + + $pdo = $this->createConnector($config)->connect($config); + + return $this->createConnection($config['driver'], $pdo, $config['database'], $config['prefix'], $config); + } + + /** + * Parse and prepare the database configuration. + * + * @param array $config + * @param string $name + * @return array + */ + protected function parseConfig(array $config, $name) + { + return array_add(array_add($config, 'prefix', ''), 'name', $name); + } + + /** + * Create a connector instance based on the configuration. + * + * @param array $config + * @return \Illuminate\Database\Connectors\ConnectorInterface + */ + public function createConnector(array $config) + { + if ( ! isset($config['driver'])) + { + throw new \InvalidArgumentException("A driver must be specified."); + } + + switch ($config['driver']) + { + case 'mysql': + return new MySqlConnector; + + case 'pgsql': + return new PostgresConnector; + + case 'sqlite': + return new SQLiteConnector; + + case 'sqlsrv': + return new SqlServerConnector; + } + + throw new \InvalidArgumentException("Unsupported driver [{$config['driver']}]"); + } + + /** + * Create a new connection instance. + * + * @param string $driver + * @param PDO $connection + * @param string $database + * @param string $prefix + * @param array $config + * @return \Illuminate\Database\Connection + */ + protected function createConnection($driver, PDO $connection, $database, $prefix = '', $config = null) + { + if ($this->container->bound($key = "db.connection.{$driver}")) + { + return $this->container->make($key, array($connection, $database, $prefix, $config)); + } + + switch ($driver) + { + case 'mysql': + return new MySqlConnection($connection, $database, $prefix, $config); + + case 'pgsql': + return new PostgresConnection($connection, $database, $prefix, $config); + + case 'sqlite': + return new SQLiteConnection($connection, $database, $prefix, $config); + + case 'sqlsrv': + return new SqlServerConnection($connection, $database, $prefix, $config); + } + + throw new \InvalidArgumentException("Unsupported driver [$driver]"); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php b/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php new file mode 100755 index 0000000..bb24034 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php @@ -0,0 +1,71 @@ + PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + PDO::ATTR_EMULATE_PREPARES => false, + ); + + /** + * Get the PDO options based on the configuration. + * + * @param array $config + * @return array + */ + public function getOptions(array $config) + { + $options = array_get($config, 'options', array()); + + return array_diff_key($this->options, $options) + $options; + } + + /** + * Create a new PDO connection. + * + * @param string $dsn + * @param array $config + * @param array $options + * @return PDO + */ + public function createConnection($dsn, array $config, array $options) + { + $username = array_get($config, 'username'); + + $password = array_get($config, 'password'); + + return new PDO($dsn, $username, $password, $options); + } + + /** + * Get the default PDO connection options. + * + * @return array + */ + public function getDefaultOptions() + { + return $this->options; + } + + /** + * Set the default PDO connection options. + * + * @param array $options + * @return void + */ + public function setDefaultOptions(array $options) + { + $this->options = $options; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Connectors/ConnectorInterface.php b/vendor/laravel/framework/src/Illuminate/Database/Connectors/ConnectorInterface.php new file mode 100755 index 0000000..c734f9d --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Connectors/ConnectorInterface.php @@ -0,0 +1,13 @@ +getDsn($config); + + // We need to grab the PDO options that should be used while making the brand + // new connection instance. The PDO options control various aspects of the + // connection's behavior, and some might be specified by the developers. + $options = $this->getOptions($config); + + $connection = $this->createConnection($dsn, $config, $options); + + $collation = $config['collation']; + + $charset = $config['charset']; + + // Next we will set the "names" and "collation" on the clients connections so + // a correct character set will be used by this client. The collation also + // is set on the server but needs to be set here on this client objects. + $names = "set names '$charset' collate '$collation'"; + + $connection->prepare($names)->execute(); + + return $connection; + } + + /** + * Create a DSN string from a configuration. + * + * @param array $config + * @return string + */ + protected function getDsn(array $config) + { + // First we will create the basic DSN setup as well as the port if it is in + // in the configuration options. This will give us the basic DSN we will + // need to establish the PDO connections and return them back for use. + extract($config); + + $dsn = "mysql:host={$host};dbname={$database}"; + + if (isset($config['port'])) + { + $dsn .= ";port={$port}"; + } + + // Sometimes the developer may specify the specific UNIX socket that should + // be used. If that is the case we will add that option to the string we + // have created so that it gets utilized while the connection is made. + if (isset($config['unix_socket'])) + { + $dsn .= ";unix_socket={$config['unix_socket']}"; + } + + return $dsn; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Connectors/PostgresConnector.php b/vendor/laravel/framework/src/Illuminate/Database/Connectors/PostgresConnector.php new file mode 100755 index 0000000..d2df102 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Connectors/PostgresConnector.php @@ -0,0 +1,82 @@ + PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ); + + + /** + * Establish a database connection. + * + * @param array $options + * @return PDO + */ + public function connect(array $config) + { + // First we'll create the basic DSN and connection instance connecting to the + // using the configuration option specified by the developer. We will also + // set the default character set on the connections to UTF-8 by default. + $dsn = $this->getDsn($config); + + $options = $this->getOptions($config); + + $connection = $this->createConnection($dsn, $config, $options); + + $charset = $config['charset']; + + $connection->prepare("set names '$charset'")->execute(); + + // Unlike MySQL, Postgres allows the concept of "schema" and a default schema + // may have been specified on the connections. If that is the case we will + // set the default schema search paths to the specified database schema. + if (isset($config['schema'])) + { + $schema = $config['schema']; + + $connection->prepare("set search_path to {$schema}")->execute(); + } + + return $connection; + } + + /** + * Create a DSN string from a configuration. + * + * @param array $config + * @return string + */ + protected function getDsn(array $config) + { + // First we will create the basic DSN setup as well as the port if it is in + // in the configuration options. This will give us the basic DSN we will + // need to establish the PDO connections and return them back for use. + extract($config); + + $host = isset($host) ? "host={$host};" : ''; + + $dsn = "pgsql:{$host}dbname={$database}"; + + // If a port was specified, we will add it to this Postgres DSN connections + // format. Once we have done that we are ready to return this connection + // string back out for usage, as this has been fully constructed here. + if (isset($config['port'])) + { + $dsn .= ";port={$port}"; + } + + return $dsn; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Connectors/SQLiteConnector.php b/vendor/laravel/framework/src/Illuminate/Database/Connectors/SQLiteConnector.php new file mode 100755 index 0000000..3f79531 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Connectors/SQLiteConnector.php @@ -0,0 +1,36 @@ +getOptions($config); + + // SQLite supports "in-memory" databases that only last as long as the owning + // connection does. These are useful for tests or for short lifetime store + // querying. In-memory databases may only have a single open connection. + if ($config['database'] == ':memory:') + { + return $this->createConnection('sqlite::memory:', $config, $options); + } + + $path = realpath($config['database']); + + // Here we'll verify that the SQLite database exists before we gooing further + // as the developer probably wants to know if the database exists and this + // SQLite driver will not throw any exception if it does not by default. + if ($path === false) + { + throw new \InvalidArgumentException("Database does not exist."); + } + + return $this->createConnection("sqlite:{$path}", $config, $options); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Connectors/SqlServerConnector.php b/vendor/laravel/framework/src/Illuminate/Database/Connectors/SqlServerConnector.php new file mode 100755 index 0000000..2378e4c --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Connectors/SqlServerConnector.php @@ -0,0 +1,67 @@ + PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ); + + /** + * Establish a database connection. + * + * @param array $options + * @return PDO + */ + public function connect(array $config) + { + $options = $this->getOptions($config); + + return $this->createConnection($this->getDsn($config), $config, $options); + } + + /** + * Create a DSN string from a configuration. + * + * @param array $config + * @return string + */ + protected function getDsn(array $config) + { + extract($config); + + // First we will create the basic DSN setup as well as the port if it is in + // in the configuration options. This will give us the basic DSN we will + // need to establish the PDO connections and return them back for use. + $port = isset($config['port']) ? ','.$port : ''; + + if (in_array('dblib', $this->getAvailableDrivers())) + { + return "dblib:host={$host}{$port};dbname={$database}"; + } + else + { + return "sqlsrv:Server={$host}{$port};Database={$database}"; + } + } + + /** + * Get the available PDO drivers. + * + * @return array + */ + protected function getAvailableDrivers() + { + return PDO::getAvailableDrivers(); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/BaseCommand.php b/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/BaseCommand.php new file mode 100755 index 0000000..d4a3d5d --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/BaseCommand.php @@ -0,0 +1,49 @@ +input->getOption('path'); + + // First, we will check to see if a path option has been defined. If it has + // we will use the path relative to the root of this installation folder + // so that migrations may be run for any path within the applications. + if ( ! is_null($path)) + { + return $this->laravel['path.base'].'/'.$path; + } + + $package = $this->input->getOption('package'); + + // If the package is in the list of migration paths we received we will put + // the migrations in that path. Otherwise, we will assume the package is + // is in the package directories and will place them in that location. + if ( ! is_null($package)) + { + return $this->packagePath.'/'.$package.'/src/migrations'; + } + + $bench = $this->input->getOption('bench'); + + // Finally we will check for the workbench option, which is a shortcut into + // specifying the full path for a "workbench" project. Workbenches allow + // developers to develop packages along side a "standard" app install. + if ( ! is_null($bench)) + { + $path = "/workbench/{$bench}/src/migrations"; + + return $this->laravel['path.base'].$path; + } + + return $this->laravel['path'].'/database/migrations'; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/InstallCommand.php b/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/InstallCommand.php new file mode 100755 index 0000000..9feb2ae --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/InstallCommand.php @@ -0,0 +1,69 @@ +repository = $repository; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->repository->setSource($this->input->getOption('database')); + + $this->repository->createRepository(); + + $this->info("Migration table created successfully."); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'), + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MakeCommand.php b/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MakeCommand.php new file mode 100755 index 0000000..c2cd56c --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MakeCommand.php @@ -0,0 +1,126 @@ +creator = $creator; + $this->packagePath = $packagePath; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + // It's possible for the developer to specify the tables to modify in this + // schema operation. The developer may also specify if this table needs + // to be freshly created so we can create the appropriate migrations. + $name = $this->input->getArgument('name'); + + $table = $this->input->getOption('table'); + + $create = $this->input->getOption('create'); + + // Now we are ready to write the migration out to disk. Once we've written + // the migration out, we will dump-autoload for the entire framework to + // make sure that the migrations are registered by the class loaders. + $this->writeMigration($name, $table, $create); + + $this->call('dump-autoload'); + } + + /** + * Write the migration file to disk. + * + * @param string $name + * @param string $table + * @param bool $create + * @return string + */ + protected function writeMigration($name, $table, $create) + { + $path = $this->getMigrationPath(); + + $file = pathinfo($this->creator->create($name, $path, $table, $create), PATHINFO_FILENAME); + + $this->line("Created Migration: $file"); + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return array( + array('name', InputArgument::REQUIRED, 'The name of the migration'), + ); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('bench', null, InputOption::VALUE_OPTIONAL, 'The workbench the migration belongs to.', null), + + array('create', null, InputOption::VALUE_NONE, 'The table needs to be created.'), + + array('package', null, InputOption::VALUE_OPTIONAL, 'The package the migration belongs to.', null), + + array('path', null, InputOption::VALUE_OPTIONAL, 'Where to store the migration.', null), + + array('table', null, InputOption::VALUE_OPTIONAL, 'The table to migrate.'), + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php b/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php new file mode 100755 index 0000000..8347455 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/MigrateCommand.php @@ -0,0 +1,125 @@ +migrator = $migrator; + $this->packagePath = $packagePath; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->prepareDatabase(); + + // The pretend option can be used for "simulating" the migration and grabbing + // the SQL queries that would fire if the migration were to be run against + // a database for real, which is helpful for double checking migrations. + $pretend = $this->input->getOption('pretend'); + + $path = $this->getMigrationPath(); + + $this->migrator->run($path, $pretend); + + // Once the migrator has run we will grab the note output and send it out to + // the console screen, since the migrator itself functions without having + // any instances of the OutputInterface contract passed into the class. + foreach ($this->migrator->getNotes() as $note) + { + $this->output->writeln($note); + } + + // Finally, if the "seed" option has been given, we will re-run the database + // seed task to re-populate the database, which is convenient when adding + // a migration and a seed at the same time, as it is only this command. + if ($this->input->getOption('seed')) + { + $this->call('db:seed'); + } + } + + /** + * Prepare the migration database for running. + * + * @return void + */ + protected function prepareDatabase() + { + $this->migrator->setConnection($this->input->getOption('database')); + + if ( ! $this->migrator->repositoryExists()) + { + $options = array('--database' => $this->input->getOption('database')); + + $this->call('migrate:install', $options); + } + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('bench', null, InputOption::VALUE_OPTIONAL, 'The name of the workbench to migrate.', null), + + array('database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'), + + array('path', null, InputOption::VALUE_OPTIONAL, 'The path to migration files.', null), + + array('package', null, InputOption::VALUE_OPTIONAL, 'The package to migrate.', null), + + array('pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run.'), + + array('seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run.'), + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/RefreshCommand.php b/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/RefreshCommand.php new file mode 100755 index 0000000..330dc73 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/RefreshCommand.php @@ -0,0 +1,58 @@ +input->getOption('database'); + + $this->call('migrate:reset', array('--database' => $database)); + + // The refresh command is essentially just a brief aggregate of a few other of + // the migration commands and just provides a convenient wrapper to execute + // them in succession. We'll also see if we need to res-eed the database. + $this->call('migrate', array('--database' => $database)); + + if ($this->input->getOption('seed')) + { + $this->call('db:seed', array('--database' => $database)); + } + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'), + + array('seed', null, InputOption::VALUE_NONE, 'Indicates if the seed task should be re-run.'), + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/ResetCommand.php b/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/ResetCommand.php new file mode 100755 index 0000000..386858d --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/ResetCommand.php @@ -0,0 +1,84 @@ +migrator = $migrator; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->migrator->setConnection($this->input->getOption('database')); + + $pretend = $this->input->getOption('pretend'); + + while (true) + { + $count = $this->migrator->rollback($pretend); + + // Once the migrator has run we will grab the note output and send it out to + // the console screen, since the migrator itself functions without having + // any instances of the OutputInterface contract passed into the class. + foreach ($this->migrator->getNotes() as $note) + { + $this->output->writeln($note); + } + + if ($count == 0) break; + } + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'), + + array('pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run.'), + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/RollbackCommand.php b/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/RollbackCommand.php new file mode 100755 index 0000000..5d2ab4b --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Console/Migrations/RollbackCommand.php @@ -0,0 +1,79 @@ +migrator = $migrator; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->migrator->setConnection($this->input->getOption('database')); + + $pretend = $this->input->getOption('pretend'); + + $this->migrator->rollback($pretend); + + // Once the migrator has run we will grab the note output and send it out to + // the console screen, since the migrator itself functions without having + // any instances of the OutputInterface contract passed into the class. + foreach ($this->migrator->getNotes() as $note) + { + $this->output->writeln($note); + } + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('database', null, InputOption::VALUE_OPTIONAL, 'The database connection to use.'), + + array('pretend', null, InputOption::VALUE_NONE, 'Dump the SQL queries that would be run.'), + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Console/SeedCommand.php b/vendor/laravel/framework/src/Illuminate/Database/Console/SeedCommand.php new file mode 100755 index 0000000..d753af3 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Console/SeedCommand.php @@ -0,0 +1,96 @@ +resolver = $resolver; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->resolver->setDefaultConnection($this->getDatabase()); + + $this->getSeeder()->run(); + + $this->info('Database seeded!'); + } + + /** + * Get a seeder instance from the container. + * + * @return \Illuminate\Database\Seeder + */ + protected function getSeeder() + { + $class = $this->laravel->make($this->input->getOption('class')); + + return $class->setContainer($this->laravel)->setCommand($this); + } + + /** + * Get the name of the database connection to use. + * + * @return string + */ + protected function getDatabase() + { + $database = $this->input->getOption('database'); + + return $database ?: $this->laravel['config']['database.default']; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('class', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder', 'DatabaseSeeder'), + + array('database', null, InputOption::VALUE_OPTIONAL, 'The database connection to seed'), + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php b/vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php new file mode 100755 index 0000000..c045b7c --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/DatabaseManager.php @@ -0,0 +1,222 @@ +app = $app; + $this->factory = $factory; + } + + /** + * Get a database connection instance. + * + * @param string $name + * @return \Illuminate\Database\Connection + */ + public function connection($name = null) + { + $name = $name ?: $this->getDefaultConnection(); + + // If we haven't created this connection, we'll create it based on the config + // provided in the application. Once we've created the connections we will + // set the "fetch mode" for PDO which determines the query return types. + if ( ! isset($this->connections[$name])) + { + $connection = $this->makeConnection($name); + + $this->connections[$name] = $this->prepare($connection); + } + + return $this->connections[$name]; + } + + /** + * Reconnect to the given database. + * + * @param string $name + * @return \Illuminate\Database\Connection + */ + public function reconnect($name = null) + { + $name = $name ?: $this->getDefaultConnection(); + + unset($this->connections[$name]); + + return $this->connection($name); + } + + /** + * Make the database connection instance. + * + * @param string $name + * @return \Illuminate\Database\Connection + */ + protected function makeConnection($name) + { + $config = $this->getConfig($name); + + // First we will check by the connection name to see if an extension has been + // registered specifically for that connection. If it has we will call the + // Closure and pass it the config allowing it to resolve the connection. + if (isset($this->extensions[$name])) + { + return call_user_func($this->extensions[$name], $config); + } + + $driver = $config['driver']; + + // Next we will check to see if an extension has been registered for a driver + // and will call the Closure if so, which allows us to have a more generic + // resolver for the drivers themselves which applies to all connections. + if (isset($this->extensions[$driver])) + { + return call_user_func($this->extensions[$driver], $config); + } + + return $this->factory->make($config, $name); + } + + /** + * Prepare the database connection instance. + * + * @param \Illuminate\Database\Connection $connection + * @return \Illuminate\Database\Connection + */ + protected function prepare(Connection $connection) + { + $connection->setFetchMode($this->app['config']['database.fetch']); + + if ($this->app->bound('events')) + { + $connection->setEventDispatcher($this->app['events']); + } + + // The database connection can also utilize a cache manager instance when cache + // functionality is used on queries, which provides an expressive interface + // to caching both fluent queries and Eloquent queries that are executed. + $app = $this->app; + + $connection->setCacheManager(function() use ($app) + { + return $app['cache']; + }); + + // We will setup a Closure to resolve the paginator instance on the connection + // since the Paginator isn't sued on every request and needs quite a few of + // our dependencies. It'll be more efficient to lazily resolve instances. + $connection->setPaginator(function() use ($app) + { + return $app['paginator']; + }); + + return $connection; + } + + /** + * Get the configuration for a connection. + * + * @param string $name + * @return array + */ + protected function getConfig($name) + { + $name = $name ?: $this->getDefaultConnection(); + + // To get the database connection configuration, we will just pull each of the + // connection configurations and get the configurations for the given name. + // If the configuration doesn't exist, we'll throw an exception and bail. + $connections = $this->app['config']['database.connections']; + + if (is_null($config = array_get($connections, $name))) + { + throw new \InvalidArgumentException("Database [$name] not configured."); + } + + return $config; + } + + /** + * Get the default connection name. + * + * @return string + */ + public function getDefaultConnection() + { + return $this->app['config']['database.default']; + } + + /** + * Set the default connection name. + * + * @param string $name + * @return void + */ + public function setDefaultConnection($name) + { + $this->app['config']['database.default'] = $name; + } + + /** + * Register an extension connection resolver. + * + * @param string $name + * @param callable $resolver + * @return void + */ + public function extend($name, $resolver) + { + $this->extensions[$name] = $resolver; + } + + /** + * Dynamically pass methods to the default connection. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return call_user_func_array(array($this->connection(), $method), $parameters); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/DatabaseServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Database/DatabaseServiceProvider.php new file mode 100755 index 0000000..5a030b6 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/DatabaseServiceProvider.php @@ -0,0 +1,45 @@ +app['db']); + + Model::setEventDispatcher($this->app['events']); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + // The connection factory is used to create the actual connection instances on + // the database. We will inject the factory into the manager so that it may + // make the connections while they are actually needed and not of before. + $this->app['db.factory'] = $this->app->share(function($app) + { + return new ConnectionFactory($app); + }); + + // The database manager is used to resolve various connections, since multiple + // connections might be managed. It also implements the connection resolver + // interface which may be used by other components requiring connections. + $this->app['db'] = $this->app->share(function($app) + { + return new DatabaseManager($app, $app['db.factory']); + }); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php new file mode 100755 index 0000000..144090c --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Builder.php @@ -0,0 +1,737 @@ +query = $query; + } + + /** + * Find a model by its primary key. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|static|null + */ + public function find($id, $columns = array('*')) + { + $this->query->where($this->model->getKeyName(), '=', $id); + + return $this->first($columns); + } + + /** + * Find a model by its primary key or throw an exception. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|static + */ + public function findOrFail($id, $columns = array('*')) + { + if ( ! is_null($model = $this->find($id, $columns))) return $model; + + throw new ModelNotFoundException; + } + + /** + * Execute the query and get the first result. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|static|null + */ + public function first($columns = array('*')) + { + return $this->take(1)->get($columns)->first(); + } + + /** + * Execute the query and get the first result or throw an exception. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|static + */ + public function firstOrFail($columns = array('*')) + { + if ( ! is_null($model = $this->first($columns))) return $model; + + throw new ModelNotFoundException; + } + + /** + * Execute the query as a "select" statement. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection|static[] + */ + public function get($columns = array('*')) + { + $models = $this->getModels($columns); + + // If we actually found models we will also eager load any relationships that + // have been specified as needing to be eager loaded, which will solve the + // n+1 query issue for the developers to avoid running a lot of queries. + if (count($models) > 0) + { + $models = $this->eagerLoadRelations($models); + } + + return $this->model->newCollection($models); + } + + /** + * Pluck a single column from the database. + * + * @param string $column + * @return mixed + */ + public function pluck($column) + { + $result = $this->first(array($column)); + + if ($result) return $result->{$column}; + } + + /** + * Get an array with the values of a given column. + * + * @param string $column + * @param string $key + * @return array + */ + public function lists($column, $key = null) + { + $results = $this->query->lists($column, $key); + + // If the model has a mutator for the requested column, we will spin through + // the results and mutate the values so that the mutated version of these + // columns are returned as you would expect from these Eloquent models. + if ($this->model->hasGetMutator($column)) + { + foreach ($results as $key => &$value) + { + $fill = array($column => $value); + + $value = $this->model->newFromBuilder($fill)->$column; + } + } + + return $results; + } + + /** + * Get a paginator for the "select" statement. + * + * @param int $perPage + * @param array $columns + * @return \Illuminate\Pagination\Paginator + */ + public function paginate($perPage = null, $columns = array('*')) + { + $perPage = $perPage ?: $this->model->getPerPage(); + + $paginator = $this->query->getConnection()->getPaginator(); + + if (isset($this->query->groups)) + { + return $this->groupedPaginate($paginator, $perPage, $columns); + } + else + { + return $this->ungroupedPaginate($paginator, $perPage, $columns); + } + } + + /** + * Get a paginator for a grouped statement. + * + * @param \Illuminate\Pagination\Environment $paginator + * @param int $perPage + * @param array $columns + * @return \Illuminate\Pagination\Paginator + */ + protected function groupedPaginate($paginator, $perPage, $columns) + { + $results = $this->get($columns)->all(); + + return $this->query->buildRawPaginator($paginator, $results, $perPage); + } + + /** + * Get a paginator for an ungrouped statement. + * + * @param \Illuminate\Pagination\Environment $paginator + * @param int $perPage + * @param array $columns + * @return \Illuminate\Pagination\Paginator + */ + protected function ungroupedPaginate($paginator, $perPage, $columns) + { + $total = $this->query->getPaginationCount(); + + // Once we have the paginator we need to set the limit and offset values for + // the query so we can get the properly paginated items. Once we have an + // array of items we can create the paginator instances for the items. + $page = $paginator->getCurrentPage(); + + $this->query->forPage($page, $perPage); + + return $paginator->make($this->get($columns)->all(), $total, $perPage); + } + + /** + * Update a record in the database. + * + * @param array $values + * @return int + */ + public function update(array $values) + { + return $this->query->update($this->addUpdatedAtColumn($values)); + } + + /** + * Increment a column's value by a given amount. + * + * @param string $column + * @param int $amount + * @param array $extra + * @return int + */ + public function increment($column, $amount = 1, array $extra = array()) + { + $extra = $this->addUpdatedAtColumn($extra); + + return $this->query->increment($column, $amount, $extra); + } + + /** + * Decrement a column's value by a given amount. + * + * @param string $column + * @param int $amount + * @param array $extra + * @return int + */ + public function decrement($column, $amount = 1, array $extra = array()) + { + $extra = $this->addUpdatedAtColumn($extra); + + return $this->query->decrement($column, $amount, $extra); + } + + /** + * Add the "updated at" column to an array of values. + * + * @param array $values + * @return array + */ + protected function addUpdatedAtColumn(array $values) + { + if ( ! $this->model->usesTimestamps()) return $values; + + $column = $this->model->getUpdatedAtColumn(); + + return array_add($values, $column, $this->model->freshTimestampString()); + } + + /** + * Delete a record from the database. + * + * @return int + */ + public function delete() + { + if ($this->model->isSoftDeleting()) + { + return $this->softDelete(); + } + else + { + return $this->query->delete(); + } + } + + /** + * Soft delete the record in the database. + * + * @return int + */ + protected function softDelete() + { + $column = $this->model->getDeletedAtColumn(); + + return $this->update(array($column => $this->model->freshTimestampString())); + } + + /** + * Force a delete on a set of soft deleted models. + * + * @return int + */ + public function forceDelete() + { + return $this->query->delete(); + } + + /** + * Restore the soft-deleted model instances. + * + * @return int + */ + public function restore() + { + if ($this->model->isSoftDeleting()) + { + $column = $this->model->getDeletedAtColumn(); + + return $this->update(array($column => null)); + } + } + + /** + * Include the soft deleted models in the results. + * + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function withTrashed() + { + $column = $this->model->getQualifiedDeletedAtColumn(); + + foreach ($this->query->wheres as $key => $where) + { + // If the where clause is a soft delete date constraint, we will remove it from + // the query and reset the keys on the wheres. This allows this developer to + // include deleted model in a relationship result set that is lazy loaded. + if ($this->isSoftDeleteConstraint($where, $column)) + { + unset($this->query->wheres[$key]); + + $this->query->wheres = array_values($this->query->wheres); + } + } + + return $this; + } + + /** + * Force the result set to only included soft deletes. + * + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function onlyTrashed() + { + $this->withTrashed(); + + $this->query->whereNotNull($this->model->getQualifiedDeletedAtColumn()); + + return $this; + } + + /** + * Determine if the given where clause is a soft delete constraint. + * + * @param array $where + * @param string $column + * @return bool + */ + protected function isSoftDeleteConstraint(array $where, $column) + { + return $where['column'] == $column and $where['type'] == 'Null'; + } + + /** + * Get the hydrated models without eager loading. + * + * @param array $columns + * @return array|static[] + */ + public function getModels($columns = array('*')) + { + // First, we will simply get the raw results from the query builders which we + // can use to populate an array with Eloquent models. We will pass columns + // that should be selected as well, which are typically just everything. + $results = $this->query->get($columns); + + $connection = $this->model->getConnectionName(); + + $models = array(); + + // Once we have the results, we can spin through them and instantiate a fresh + // model instance for each records we retrieved from the database. We will + // also set the proper connection name for the model after we create it. + foreach ($results as $result) + { + $models[] = $model = $this->model->newFromBuilder($result); + + $model->setConnection($connection); + } + + return $models; + } + + /** + * Eager load the relationships for the models. + * + * @param array $models + * @return array + */ + public function eagerLoadRelations(array $models) + { + foreach ($this->eagerLoad as $name => $constraints) + { + // For nested eager loads we'll skip loading them here and they will be set as an + // eager load on the query to retrieve the relation so that they will be eager + // loaded on that query, because that is where they get hydrated as models. + if (strpos($name, '.') === false) + { + $models = $this->loadRelation($models, $name, $constraints); + } + } + + return $models; + } + + /** + * Eagerly load the relationship on a set of models. + * + * @param array $models + * @param string $name + * @param \Closure $constraints + * @return array + */ + protected function loadRelation(array $models, $name, Closure $constraints) + { + // First we will "back up" the existing where conditions on the query so we can + // add our eager constraints. Then we will merge the wheres that were on the + // query back to it in order that any where conditions might be specified. + $relation = $this->getRelation($name); + + $relation->addEagerConstraints($models); + + call_user_func($constraints, $relation); + + $models = $relation->initRelation($models, $name); + + // Once we have the results, we just match those back up to their parent models + // using the relationship instance. Then we just return the finished arrays + // of models which have been eagerly hydrated and are readied for return. + $results = $relation->get(); + + return $relation->match($models, $results, $name); + } + + /** + * Get the relation instance for the given relation name. + * + * @param string $relation + * @return \Illuminate\Database\Eloquent\Relations\Relation + */ + public function getRelation($relation) + { + $me = $this; + + // We want to run a relationship query without any constrains so that we will + // not have to remove these where clauses manually which gets really hacky + // and is error prone while we remove the developer's own where clauses. + $query = Relation::noConstraints(function() use ($me, $relation) + { + return $me->getModel()->$relation(); + }); + + $nested = $this->nestedRelations($relation); + + // If there are nested relationships set on the query, we will put those onto + // the query instances so that they can be handled after this relationship + // is loaded. In this way they will all trickle down as they are loaded. + if (count($nested) > 0) + { + $query->getQuery()->with($nested); + } + + return $query; + } + + /** + * Get the deeply nested relations for a given top-level relation. + * + * @param string $relation + * @return array + */ + protected function nestedRelations($relation) + { + $nested = array(); + + // We are basically looking for any relationships that are nested deeper than + // the given top-level relationship. We will just check for any relations + // that start with the given top relations and adds them to our arrays. + foreach ($this->eagerLoad as $name => $constraints) + { + if ($this->isNested($name, $relation)) + { + $nested[substr($name, strlen($relation.'.'))] = $constraints; + } + } + + return $nested; + } + + /** + * Determine if the relationship is nested. + * + * @param string $name + * @param string $relation + * @return bool + */ + protected function isNested($name, $relation) + { + $dots = str_contains($name, '.'); + + return $dots and starts_with($name, $relation) and $name != $relation; + } + + /** + * Add a relationship count condition to the query. + * + * @param string $relation + * @param string $operator + * @param int $count + * @param string $boolean + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function has($relation, $operator = '>=', $count = 1, $boolean = 'and') + { + $instance = $this->model->$relation(); + + $query = $instance->getRelationCountQuery($instance->getRelated()->newQuery()); + + $this->query->mergeBindings($query->getQuery()); + + return $this->where(new Expression('('.$query->toSql().')'), $operator, $count, $boolean); + } + + /** + * Add a relationship count condition to the query with an "or". + * + * @param string $relation + * @param string $operator + * @param int $count + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function orHas($relation, $operator = '>=', $count = 1) + { + return $this->has($relation, $operator, $count, 'or'); + } + + /** + * Set the relationships that should be eager loaded. + * + * @param dynamic $relations + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function with($relations) + { + if (is_string($relations)) $relations = func_get_args(); + + $eagers = $this->parseRelations($relations); + + $this->eagerLoad = array_merge($this->eagerLoad, $eagers); + + return $this; + } + + /** + * Parse a list of relations into individuals. + * + * @param array $relations + * @return array + */ + protected function parseRelations(array $relations) + { + $results = array(); + + foreach ($relations as $name => $constraints) + { + // If the "relation" value is actually a numeric key, we can assume that no + // constraints have been specified for the eager load and we'll just put + // an empty Closure with the loader so that we can treat all the same. + if (is_numeric($name)) + { + $f = function() {}; + + list($name, $constraints) = array($constraints, $f); + } + + // We need to separate out any nested includes. Which allows the developers + // to load deep relationships using "dots" without stating each level of + // the relationship with its own key in the array of eager load names. + $results = $this->parseNested($name, $results); + + $results[$name] = $constraints; + } + + return $results; + } + + /** + * Parse the nested relationships in a relation. + * + * @param string $name + * @param array $results + * @return array + */ + protected function parseNested($name, $results) + { + $progress = array(); + + // If the relation has already been set on the result array, we will not set it + // again, since that would override any constraints that were already placed + // on the relationships. We will only set the ones that are not specified. + foreach (explode('.', $name) as $segment) + { + $progress[] = $segment; + + if ( ! isset($results[$last = implode('.', $progress)])) + { + $results[$last] = function() {}; + } + } + + return $results; + } + + /** + * Get the underlying query builder instance. + * + * @return \Illuminate\Database\Query\Builder|static + */ + public function getQuery() + { + return $this->query; + } + + /** + * Set the underlying query builder instance. + * + * @param \Illuminate\Database\Query\Builder $query + * @return void + */ + public function setQuery($query) + { + $this->query = $query; + } + + /** + * Get the relationships being eagerly loaded. + * + * @return array + */ + public function getEagerLoads() + { + return $this->eagerLoad; + } + + /** + * Set the relationships being eagerly loaded. + * + * @param array $eagerLoad + * @return void + */ + public function setEagerLoads(array $eagerLoad) + { + $this->eagerLoad = $eagerLoad; + } + + /** + * Get the model instance being queried. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function getModel() + { + return $this->model; + } + + /** + * Set a model instance for the model being queried. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return \Illuminate\Database\Eloquent\Builder + */ + public function setModel(Model $model) + { + $this->model = $model; + + $this->query->from($model->getTable()); + + return $this; + } + + /** + * Dynamically handle calls into the query instance. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (method_exists($this->model, $scope = 'scope'.ucfirst($method))) + { + array_unshift($parameters, $this); + + call_user_func_array(array($this->model, $scope), $parameters); + } + else + { + $result = call_user_func_array(array($this->query, $method), $parameters); + } + + return in_array($method, $this->passthru) ? $result : $this; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Collection.php b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Collection.php new file mode 100755 index 0000000..2358485 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Collection.php @@ -0,0 +1,84 @@ +items, function($key, $model) use ($key) + { + return $model->getKey() == $key; + + }, $default); + } + + /** + * Load a set of relationships onto the collection. + * + * @param dynamic string + * @return void + */ + public function load() + { + if (count($this->items) > 0) + { + $query = $this->first()->newQuery()->with(func_get_args()); + + $this->items = $query->eagerLoadRelations($this->items); + } + } + + /** + * Add an item to the collection. + * + * @param mixed $item + * @return \Illuminate\Database\Eloquent\Collection + */ + public function add($item) + { + $this->items[] = $item; + + return $this; + } + + /** + * Determine if a key exists in the collection. + * + * @param mixed $key + * @return bool + */ + public function contains($key) + { + return ! is_null($this->find($key)); + } + + /** + * Fetch a nested element of the collection. + * + * @param string $key + * @return \Illuminate\Support\Collection + */ + public function fetch($key) + { + return new static(array_fetch($this->toArray(), $key)); + } + + /** + * Get the array of primary keys + * + * @return array + */ + public function modelKeys() + { + return array_map(function($m) { return $m->getKey(); }, $this->items); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Database/Eloquent/MassAssignmentException.php b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/MassAssignmentException.php new file mode 100755 index 0000000..9352aed --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/MassAssignmentException.php @@ -0,0 +1,3 @@ +fill($attributes); + } + + /** + * The "booting" method of the model. + * + * @return void + */ + protected static function boot() + { + $class = get_called_class(); + + static::$mutatorCache[$class] = array(); + + // Here we will extract all of the mutated attributes so that we can quickly + // spin through them after we export models to their array form, which we + // need to be fast. This will let us always know the attributes mutate. + foreach (get_class_methods($class) as $method) + { + if (preg_match('/^get(.+)Attribute$/', $method, $matches)) + { + if (static::$snakeAttributes) $matches[1] = snake_case($matches[1]); + + static::$mutatorCache[$class][] = lcfirst($matches[1]); + } + } + } + + /** + * Register an observer with the Model. + * + * @param object $class + * @return void + */ + public static function observe($class) + { + $instance = new static; + + $className = get_class($class); + + // When registering a model observer, we will spin through the possible events + // and determine if this observer has that method. If it does, we will hook + // it into the model's event system, making it convenient to watch these. + foreach ($instance->getObservableEvents() as $event) + { + if (method_exists($class, $event)) + { + static::registerModelEvent($event, $className.'@'.$event); + } + } + } + + /** + * Fill the model with an array of attributes. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model|static + */ + public function fill(array $attributes) + { + foreach ($attributes as $key => $value) + { + $key = $this->removeTableFromKey($key); + + // The developers may choose to place some attributes in the "fillable" + // array, which means only those attributes may be set through mass + // assignment to the model, and all others will just be ignored. + if ($this->isFillable($key)) + { + $this->setAttribute($key, $value); + } + elseif ($this->totallyGuarded()) + { + throw new MassAssignmentException($key); + } + } + + return $this; + } + + /** + * Create a new instance of the given model. + * + * @param array $attributes + * @param bool $exists + * @return \Illuminate\Database\Eloquent\Model|static + */ + public function newInstance($attributes = array(), $exists = false) + { + // This method just provides a convenient way for us to generate fresh model + // instances of this current model. It is particularly useful during the + // hydration of new objects via the Eloquent query builder instances. + $model = new static((array) $attributes); + + $model->exists = $exists; + + return $model; + } + + /** + * Create a new model instance that is existing. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model|static + */ + public function newFromBuilder($attributes = array()) + { + $instance = $this->newInstance(array(), true); + + $instance->setRawAttributes((array) $attributes, true); + + return $instance; + } + + /** + * Save a new model and return the instance. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model|static + */ + public static function create(array $attributes) + { + $model = new static($attributes); + + $model->save(); + + return $model; + } + + /** + * Begin querying the model. + * + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public static function query() + { + return with(new static)->newQuery(); + } + + /** + * Begin querying the model on a given connection. + * + * @param string $connection + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public static function on($connection = null) + { + // First we will just create a fresh instance of this model, and then we can + // set the connection on the model so that it is be used for the queries + // we execute, as well as being set on each relationship we retrieve. + $instance = new static; + + $instance->setConnection($connection); + + return $instance->newQuery(); + } + + /** + * Get all of the models from the database. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection|static[] + */ + public static function all($columns = array('*')) + { + $instance = new static; + + return $instance->newQuery()->get($columns); + } + + /** + * Find a model by its primary key. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|Collection|static + */ + public static function find($id, $columns = array('*')) + { + $instance = new static; + + if (is_array($id)) + { + return $instance->newQuery()->whereIn($instance->getKeyName(), $id)->get($columns); + } + + return $instance->newQuery()->find($id, $columns); + } + + /** + * Find a model by its primary key or throw an exception. + * + * @param mixed $id + * @param array $columns + * @return \Illuminate\Database\Eloquent\Model|Collection|static + */ + public static function findOrFail($id, $columns = array('*')) + { + if ( ! is_null($model = static::find($id, $columns))) return $model; + + throw new ModelNotFoundException; + } + + /** + * Eager load relations on the model. + * + * @param array|string $relations + * @return void + */ + public function load($relations) + { + if (is_string($relations)) $relations = func_get_args(); + + $query = $this->newQuery()->with($relations); + + $query->eagerLoadRelations(array($this)); + } + + /** + * Being querying a model with eager loading. + * + * @param array|string $relations + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public static function with($relations) + { + if (is_string($relations)) $relations = func_get_args(); + + $instance = new static; + + return $instance->newQuery()->with($relations); + } + + /** + * Define a one-to-one relationship. + * + * @param string $related + * @param string $foreignKey + * @return \Illuminate\Database\Eloquent\Relations\HasOne + */ + public function hasOne($related, $foreignKey = null) + { + $foreignKey = $foreignKey ?: $this->getForeignKey(); + + $instance = new $related; + + return new HasOne($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey); + } + + /** + * Define a polymorphic one-to-one relationship. + * + * @param string $related + * @param string $name + * @param string $type + * @param string $id + * @return \Illuminate\Database\Eloquent\Relations\MorphOne + */ + public function morphOne($related, $name, $type = null, $id = null) + { + $instance = new $related; + + list($type, $id) = $this->getMorphs($name, $type, $id); + + $table = $instance->getTable(); + + return new MorphOne($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id); + } + + /** + * Define an inverse one-to-one or many relationship. + * + * @param string $related + * @param string $foreignKey + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function belongsTo($related, $foreignKey = null) + { + list(, $caller) = debug_backtrace(false); + + // If no foreign key was supplied, we can use a backtrace to guess the proper + // foreign key name by using the name of the relationship function, which + // when combined with an "_id" should conventionally match the columns. + $relation = $caller['function']; + + if (is_null($foreignKey)) + { + $foreignKey = snake_case($relation).'_id'; + } + + // Once we have the foreign key names, we'll just create a new Eloquent query + // for the related models and returns the relationship instance which will + // actually be responsible for retrieving and hydrating every relations. + $instance = new $related; + + $query = $instance->newQuery(); + + return new BelongsTo($query, $this, $foreignKey, $relation); + } + + /** + * Define an polymorphic, inverse one-to-one or many relationship. + * + * @param string $name + * @param string $type + * @param string $id + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function morphTo($name = null, $type = null, $id = null) + { + // If no name is provided, we will use the backtrace to get the function name + // since that is most likely the name of the polymorphic interface. We can + // use that to get both the class and foreign key that will be utilized. + if (is_null($name)) + { + list(, $caller) = debug_backtrace(false); + + $name = snake_case($caller['function']); + } + + // Next we will guess the type and ID if necessary. The type and IDs may also + // be passed into the function so that the developers may manually specify + // them on the relations. Otherwise, we will just make a great estimate. + list($type, $id) = $this->getMorphs($name, $type, $id); + + $class = $this->$type; + + return $this->belongsTo($class, $id); + } + + /** + * Define a one-to-many relationship. + * + * @param string $related + * @param string $foreignKey + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function hasMany($related, $foreignKey = null) + { + $foreignKey = $foreignKey ?: $this->getForeignKey(); + + $instance = new $related; + + return new HasMany($instance->newQuery(), $this, $instance->getTable().'.'.$foreignKey); + } + + /** + * Define a polymorphic one-to-many relationship. + * + * @param string $related + * @param string $name + * @param string $type + * @param string $id + * @return \Illuminate\Database\Eloquent\Relations\MorphMany + */ + public function morphMany($related, $name, $type = null, $id = null) + { + $instance = new $related; + + list($type, $id) = $this->getMorphs($name, $type, $id); + + $table = $instance->getTable(); + + return new MorphMany($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id); + } + + /** + * Define a many-to-many relationship. + * + * @param string $related + * @param string $table + * @param string $foreignKey + * @param string $otherKey + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function belongsToMany($related, $table = null, $foreignKey = null, $otherKey = null) + { + $caller = $this->getBelongsToManyCaller(); + + // First, we'll need to determine the foreign key and "other key" for the + // relationship. Once we have determined the keys we'll make the query + // instances as well as the relationship instances we need for this. + $foreignKey = $foreignKey ?: $this->getForeignKey(); + + $instance = new $related; + + $otherKey = $otherKey ?: $instance->getForeignKey(); + + // If no table name was provided, we can guess it by concatenating the two + // models using underscores in alphabetical order. The two model names + // are transformed to snake case from their default CamelCase also. + if (is_null($table)) + { + $table = $this->joiningTable($related); + } + + // Now we're ready to create a new query builder for the related model and + // the relationship instances for the relation. The relations will set + // appropriate query constraint and entirely manages the hydrations. + $query = $instance->newQuery(); + + return new BelongsToMany($query, $this, $table, $foreignKey, $otherKey, $caller['function']); + } + + /** + * Get the relationship name of the belongs to many. + * + * @return string + */ + protected function getBelongsToManyCaller() + { + $self = __FUNCTION__; + + return array_first(debug_backtrace(false), function($trace) use ($self) + { + $caller = $trace['function']; + + return $caller != 'belongsToMany' and $caller != $self; + }); + } + + /** + * Get the joining table name for a many-to-many relation. + * + * @param string $related + * @return string + */ + public function joiningTable($related) + { + // The joining table name, by convention, is simply the snake cased models + // sorted alphabetically and concatenated with an underscore, so we can + // just sort the models and join them together to get the table name. + $base = snake_case(class_basename($this)); + + $related = snake_case(class_basename($related)); + + $models = array($related, $base); + + // Now that we have the model names in an array we can just sort them and + // use the implode function to join them together with an underscores, + // which is typically used by convention within the database system. + sort($models); + + return strtolower(implode('_', $models)); + } + + /** + * Destroy the models for the given IDs. + * + * @param array|int $ids + * @return void + */ + public static function destroy($ids) + { + $ids = is_array($ids) ? $ids : func_get_args(); + + $instance = new static; + + // We will actually pull the models from the database table and call delete on + // each of them individually so that their events get fired properly with a + // correct set of attributes in case the developers wants to check these. + $key = $instance->getKeyName(); + + foreach ($instance->whereIn($key, $ids)->get() as $model) + { + $model->delete(); + } + } + + /** + * Delete the model from the database. + * + * @return bool|null + */ + public function delete() + { + if ($this->exists) + { + if ($this->fireModelEvent('deleting') === false) return false; + + // Here, we'll touch the owning models, verifying these timestamps get updated + // for the models. This will allow any caching to get broken on the parents + // by the timestamp. Then we will go ahead and delete the model instance. + $this->touchOwners(); + + $this->performDeleteOnModel(); + + $this->exists = false; + + // Once the model has been deleted, we will fire off the deleted event so that + // the developers may hook into post-delete operations. We will then return + // a boolean true as the delete is presumably successful on the database. + $this->fireModelEvent('deleted', false); + + return true; + } + } + + /** + * Force a hard delete on a soft deleted model. + * + * @return void + */ + public function forceDelete() + { + $softDelete = $this->softDelete; + + // We will temporarily disable false delete to allow us to perform the real + // delete operation against the model. We will then restore the deleting + // state to what this was prior to this given hard deleting operation. + $this->softDelete = false; + + $this->delete(); + + $this->softDelete = $softDelete; + } + + /** + * Perform the actual delete query on this model instance. + * + * @return void + */ + protected function performDeleteOnModel() + { + $query = $this->newQuery()->where($this->getKeyName(), $this->getKey()); + + if ($this->softDelete) + { + $this->{static::DELETED_AT} = $time = $this->freshTimestamp(); + + $query->update(array(static::DELETED_AT => $this->fromDateTime($time))); + } + else + { + $query->delete(); + } + } + + /** + * Restore a soft-deleted model instance. + * + * @return bool|null + */ + public function restore() + { + if ($this->softDelete) + { + // If the restoring event does not return false, we will proceed with this + // restore operation. Otherwise, we bail out so the developer will stop + // the restore totally. We will clear the deleted timestamp and save. + if ($this->fireModelEvent('restoring') === false) + { + return false; + } + + $this->{static::DELETED_AT} = null; + + // Once we have saved the model, we will fire the "restored" event so this + // developer will do anything they need to after a restore operation is + // totally finished. Then we will return the result of the save call. + $result = $this->save(); + + $this->fireModelEvent('restored', false); + + return $result; + } + } + + /** + * Register a saving model event with the dispatcher. + * + * @param \Closure|string $callback + * @return void + */ + public static function saving($callback) + { + static::registerModelEvent('saving', $callback); + } + + /** + * Register a saved model event with the dispatcher. + * + * @param \Closure|string $callback + * @return void + */ + public static function saved($callback) + { + static::registerModelEvent('saved', $callback); + } + + /** + * Register an updating model event with the dispatcher. + * + * @param \Closure|string $callback + * @return void + */ + public static function updating($callback) + { + static::registerModelEvent('updating', $callback); + } + + /** + * Register an updated model event with the dispatcher. + * + * @param \Closure|string $callback + * @return void + */ + public static function updated($callback) + { + static::registerModelEvent('updated', $callback); + } + + /** + * Register a creating model event with the dispatcher. + * + * @param \Closure|string $callback + * @return void + */ + public static function creating($callback) + { + static::registerModelEvent('creating', $callback); + } + + /** + * Register a created model event with the dispatcher. + * + * @param \Closure|string $callback + * @return void + */ + public static function created($callback) + { + static::registerModelEvent('created', $callback); + } + + /** + * Register a deleting model event with the dispatcher. + * + * @param \Closure|string $callback + * @return void + */ + public static function deleting($callback) + { + static::registerModelEvent('deleting', $callback); + } + + /** + * Register a deleted model event with the dispatcher. + * + * @param \Closure|string $callback + * @return void + */ + public static function deleted($callback) + { + static::registerModelEvent('deleted', $callback); + } + + /** + * Register a restoring model event with the dispatcher. + * + * @param \Closure|string $callback + * @return void + */ + public static function restoring($callback) + { + static::registerModelEvent('restoring', $callback); + } + + /** + * Register a restored model event with the dispatcher. + * + * @param \Closure|string $callback + * @return void + */ + public static function restored($callback) + { + static::registerModelEvent('restored', $callback); + } + + /** + * Remove all of the event listeners for the model. + * + * @return void + */ + public static function flushEventListeners() + { + if ( ! isset(static::$dispatcher)) return; + + $instance = new static; + + foreach ($instance->getObservableEvents() as $event) + { + static::$dispatcher->forget("eloquent.{$event}: ".get_called_class()); + } + } + + /** + * Register a model event with the dispatcher. + * + * @param string $event + * @param \Closure|string $callback + * @return void + */ + protected static function registerModelEvent($event, $callback) + { + if (isset(static::$dispatcher)) + { + $name = get_called_class(); + + static::$dispatcher->listen("eloquent.{$event}: {$name}", $callback); + } + } + + /** + * Get the observable event names. + * + * @return array + */ + public function getObservableEvents() + { + return array( + 'creating', 'created', 'updating', 'updated', + 'deleting', 'deleted', 'saving', 'saved', + 'restoring', 'restored', + ); + } + + /** + * Increment a column's value by a given amount. + * + * @param string $column + * @param int $amount + * @return int + */ + protected function increment($column, $amount = 1) + { + return $this->incrementOrDecrement($column, $amount, 'increment'); + } + + /** + * Decrement a column's value by a given amount. + * + * @param string $column + * @param int $amount + * @return int + */ + protected function decrement($column, $amount = 1) + { + return $this->incrementOrDecrement($column, $amount, 'decrement'); + } + + /** + * Run the increment or decrement method on the model. + * + * @param string $column + * @param int $amount + * @param string $method + * @return int + */ + protected function incrementOrDecrement($column, $amount, $method) + { + $query = $this->newQuery(); + + if ( ! $this->exists) + { + return $query->{$method}($column, $amount); + } + + return $query->where($this->getKeyName(), $this->getKey())->{$method}($column, $amount); + } + + /** + * Update the model in the database. + * + * @param array $attributes + * @return mixed + */ + public function update(array $attributes = array()) + { + if ( ! $this->exists) + { + return $this->newQuery()->update($attributes); + } + + return $this->fill($attributes)->save(); + } + + /** + * Save the model and all of its relationships. + * + * @return bool + */ + public function push() + { + if ( ! $this->save()) return false; + + // To sync all of the relationships to the database, we will simply spin through + // the relationships and save each model via this "push" method, which allows + // us to recurse into all of these nested relations for the model instance. + foreach ($this->relations as $models) + { + foreach (Collection::make($models) as $model) + { + if ( ! $model->push()) return false; + } + } + + return true; + } + + /** + * Save the model to the database. + * + * @param array $options + * @return bool + */ + public function save(array $options = array()) + { + $query = $this->newQueryWithDeleted(); + + // If the "saving" event returns false we'll bail out of the save and return + // false, indicating that the save failed. This gives an opportunities to + // listeners to cancel save operations if validations fail or whatever. + if ($this->fireModelEvent('saving') === false) + { + return false; + } + + // If the model already exists in the database we can just update our record + // that is already in this database using the current IDs in this "where" + // clause to only update this model. Otherwise, we'll just insert them. + if ($this->exists) + { + $saved = $this->performUpdate($query); + } + + // If the model is brand new, we'll insert it into our database and set the + // ID attribute on the model to the value of the newly inserted row's ID + // which is typically an auto-increment value managed by the database. + else + { + $saved = $this->performInsert($query); + } + + if ($saved) $this->finishSave($options); + + return $saved; + } + + /** + * Finish processing on a successful save operation. + * + * @return void + */ + protected function finishSave(array $options) + { + $this->syncOriginal(); + + $this->fireModelEvent('saved', false); + + if (array_get($options, 'touch', true)) $this->touchOwners(); + } + + /** + * Perform a model update operation. + * + * @param \Illuminate\Database\Eloquent\Builder + * @return bool + */ + protected function performUpdate($query) + { + $dirty = $this->getDirty(); + + if (count($dirty) > 0) + { + // If the updating event returns false, we will cancel the update operation so + // developers can hook Validation systems into their models and cancel this + // operation if the model does not pass validation. Otherwise, we update. + if ($this->fireModelEvent('updating') === false) + { + return false; + } + + // First we need to create a fresh query instance and touch the creation and + // update timestamp on the model which are maintained by us for developer + // convenience. Then we will just continue saving the model instances. + if ($this->timestamps) + { + $this->updateTimestamps(); + + $dirty = $this->getDirty(); + } + + // Once we have run the update operation, we will fire the "updated" event for + // this model instance. This will allow developers to hook into these after + // models are updated, giving them a chance to do any special processing. + $this->setKeysForSaveQuery($query)->update($dirty); + + $this->fireModelEvent('updated', false); + } + + return true; + } + + /** + * Perform a model insert operation. + * + * @param \Illuminate\Database\Eloquent\Builder + * @return bool + */ + protected function performInsert($query) + { + if ($this->fireModelEvent('creating') === false) return false; + + // First we'll need to create a fresh query instance and touch the creation and + // update timestamps on this model, which are maintained by us for developer + // convenience. After, we will just continue saving these model instances. + if ($this->timestamps) + { + $this->updateTimestamps(); + } + + // If the model has an incrementing key, we can use the "insertGetId" method on + // the query builder, which will give us back the final inserted ID for this + // table from the database. Not all tables have to be incrementing though. + $attributes = $this->attributes; + + if ($this->incrementing) + { + $this->insertAndSetId($query, $attributes); + } + + // If the table is not incrementing we'll simply insert this attributes as they + // are, as this attributes arrays must contain an "id" column already placed + // there by the developer as the manually determined key for these models. + else + { + $query->insert($attributes); + } + + // We will go ahead and set the exists property to true, so that it is set when + // the created event is fired, just in case the developer tries to update it + // during the event. This will allow them to do so and run an update here. + $this->exists = true; + + $this->fireModelEvent('created', false); + + return true; + } + + /** + * Insert the given attributes and set the ID on the model. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @param array $attributes + * @return void + */ + protected function insertAndSetId($query, $attributes) + { + $id = $query->insertGetId($attributes, $keyName = $this->getKeyName()); + + $this->setAttribute($keyName, $id); + } + + /** + * Touch the owning relations of the model. + * + * @return void + */ + public function touchOwners() + { + foreach ($this->touches as $relation) + { + $this->$relation()->touch(); + } + } + + /** + * Determine if the model touches a given relation. + * + * @param string $relation + * @return bool + */ + public function touches($relation) + { + return in_array($relation, $this->touches); + } + + /** + * Fire the given event for the model. + * + * @param string $event + * @param bool $halt + * @return mixed + */ + protected function fireModelEvent($event, $halt = true) + { + if ( ! isset(static::$dispatcher)) return true; + + // We will append the names of the class to the event to distinguish it from + // other model events that are fired, allowing us to listen on each model + // event set individually instead of catching event for all the models. + $event = "eloquent.{$event}: ".get_class($this); + + $method = $halt ? 'until' : 'fire'; + + return static::$dispatcher->$method($event, $this); + } + + /** + * Set the keys for a save update query. + * + * @param \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function setKeysForSaveQuery($query) + { + $query->where($this->getKeyName(), '=', $this->getKey()); + + return $query; + } + + /** + * Update the model's update timestamp. + * + * @return bool + */ + public function touch() + { + $this->updateTimestamps(); + + return $this->save(); + } + + /** + * Update the creation and update timestamps. + * + * @return void + */ + protected function updateTimestamps() + { + $time = $this->freshTimestamp(); + + if ( ! $this->isDirty(static::UPDATED_AT)) + { + $this->setUpdatedAt($time); + } + + if ( ! $this->exists and ! $this->isDirty(static::CREATED_AT)) + { + $this->setCreatedAt($time); + } + } + + /** + * Set the value of the "created at" attribute. + * + * @param mixed $value + * @return void + */ + public function setCreatedAt($value) + { + $this->{static::CREATED_AT} = $value; + } + + /** + * Set the value of the "updated at" attribute. + * + * @param mixed $value + * @return void + */ + public function setUpdatedAt($value) + { + $this->{static::UPDATED_AT} = $value; + } + + /** + * Get the name of the "created at" column. + * + * @return string + */ + public function getCreatedAtColumn() + { + return static::CREATED_AT; + } + + /** + * Get the name of the "updated at" column. + * + * @return string + */ + public function getUpdatedAtColumn() + { + return static::UPDATED_AT; + } + + /** + * Get the name of the "deleted at" column. + * + * @return string + */ + public function getDeletedAtColumn() + { + return static::DELETED_AT; + } + + /** + * Get the fully qualified "deleted at" column. + * + * @return string + */ + public function getQualifiedDeletedAtColumn() + { + return $this->getTable().'.'.$this->getDeletedAtColumn(); + } + + /** + * Get a fresh timestamp for the model. + * + * @return DateTime + */ + public function freshTimestamp() + { + return new DateTime; + } + + /** + * Get a fresh timestamp for the model. + * + * @return DateTime + */ + public function freshTimestampString() + { + return $this->fromDateTime($this->freshTimestamp()); + } + + /** + * Get a new query builder for the model's table. + * + * @param bool $excludeDeleted + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function newQuery($excludeDeleted = true) + { + $builder = new Builder($this->newBaseQueryBuilder()); + + // Once we have the query builders, we will set the model instances so the + // builder can easily access any information it may need from the model + // while it is constructing and executing various queries against it. + $builder->setModel($this)->with($this->with); + + if ($excludeDeleted and $this->softDelete) + { + $builder->whereNull($this->getQualifiedDeletedAtColumn()); + } + + return $builder; + } + + /** + * Get a new query builder that includes soft deletes. + * + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public function newQueryWithDeleted() + { + return $this->newQuery(false); + } + + /** + * Determine if the model instance has been soft-deleted. + * + * @return bool + */ + public function trashed() + { + return $this->softDelete and ! is_null($this->{static::DELETED_AT}); + } + + /** + * Get a new query builder that includes soft deletes. + * + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public static function withTrashed() + { + return with(new static)->newQueryWithDeleted(); + } + + /** + * Get a new query builder that only includes soft deletes. + * + * @return \Illuminate\Database\Eloquent\Builder|static + */ + public static function onlyTrashed() + { + $instance = new static; + + $column = $instance->getQualifiedDeletedAtColumn(); + + return $instance->newQueryWithDeleted()->whereNotNull($column); + } + + /** + * Get a new query builder instance for the connection. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function newBaseQueryBuilder() + { + $conn = $this->getConnection(); + + $grammar = $conn->getQueryGrammar(); + + return new QueryBuilder($conn, $grammar, $conn->getPostProcessor()); + } + + /** + * Create a new Eloquent Collection instance. + * + * @param array $models + * @return \Illuminate\Database\Eloquent\Collection + */ + public function newCollection(array $models = array()) + { + return new Collection($models); + } + + /** + * Get the table associated with the model. + * + * @return string + */ + public function getTable() + { + if (isset($this->table)) return $this->table; + + return str_replace('\\', '', snake_case(str_plural(class_basename($this)))); + } + + /** + * Set the table associated with the model. + * + * @param string $table + * @return void + */ + public function setTable($table) + { + $this->table = $table; + } + + /** + * Get the value of the model's primary key. + * + * @return mixed + */ + public function getKey() + { + return $this->getAttribute($this->getKeyName()); + } + + /** + * Get the primary key for the model. + * + * @return string + */ + public function getKeyName() + { + return $this->primaryKey; + } + + /** + * Get the table qualified key name. + * + * @return string + */ + public function getQualifiedKeyName() + { + return $this->getTable().'.'.$this->getKeyName(); + } + + /** + * Determine if the model uses timestamps. + * + * @return bool + */ + public function usesTimestamps() + { + return $this->timestamps; + } + + /** + * Determine if the model instance uses soft deletes. + * + * @return bool + */ + public function isSoftDeleting() + { + return $this->softDelete; + } + + /** + * Set the soft deleting property on the model. + * + * @param bool $enabled + * @return void + */ + public function setSoftDeleting($enabled) + { + $this->softDelete = $enabled; + } + + /** + * Get the polymorphic relationship columns. + * + * @param string $name + * @param string $type + * @param string $id + * @return array + */ + protected function getMorphs($name, $type, $id) + { + $type = $type ?: $name.'_type'; + + $id = $id ?: $name.'_id'; + + return array($type, $id); + } + + /** + * Get the number of models to return per page. + * + * @return int + */ + public function getPerPage() + { + return $this->perPage; + } + + /** + * Set the number of models ot return per page. + * + * @param int $perPage + * @return void + */ + public function setPerPage($perPage) + { + $this->perPage = $perPage; + } + + /** + * Get the default foreign key name for the model. + * + * @return string + */ + public function getForeignKey() + { + return snake_case(class_basename($this)).'_id'; + } + + /** + * Get the hidden attributes for the model. + * + * @return array + */ + public function getHidden() + { + return $this->hidden; + } + + /** + * Set the hidden attributes for the model. + * + * @param array $hidden + * @return void + */ + public function setHidden(array $hidden) + { + $this->hidden = $hidden; + } + + /** + * Set the visible attributes for the model. + * + * @param array $visible + * @return void + */ + public function setVisible(array $visible) + { + $this->visible = $visible; + } + + /** + * Set the accessors to append to model arrays. + * + * @param array $appends + * @return void + */ + public function setAppends(array $appends) + { + $this->appends = $appends; + } + + /** + * Get the fillable attributes for the model. + * + * @return array + */ + public function getFillable() + { + return $this->fillable; + } + + /** + * Set the fillable attributes for the model. + * + * @param array $fillable + * @return \Illuminate\Database\Eloquent\Model + */ + public function fillable(array $fillable) + { + $this->fillable = $fillable; + + return $this; + } + + /** + * Set the guarded attributes for the model. + * + * @param array $guarded + * @return \Illuminate\Database\Eloquent\Model + */ + public function guard(array $guarded) + { + $this->guarded = $guarded; + + return $this; + } + + /** + * Disable all mass assignable restrictions. + * + * @return void + */ + public static function unguard() + { + static::$unguarded = true; + } + + /** + * Enable the mass assignment restrictions. + * + * @return void + */ + public static function reguard() + { + static::$unguarded = false; + } + + /** + * Set "unguard" to a given state. + * + * @param bool $state + * @return void + */ + public static function setUnguardState($state) + { + static::$unguarded = $state; + } + + /** + * Determine if the given attribute may be mass assigned. + * + * @param string $key + * @return bool + */ + public function isFillable($key) + { + if (static::$unguarded) return true; + + // If the key is in the "fillable" array, we can of course assume tha it is + // a fillable attribute. Otherwise, we will check the guarded array when + // we need to determine if the attribute is black-listed on the model. + if (in_array($key, $this->fillable)) return true; + + if ($this->isGuarded($key)) return false; + + return empty($this->fillable) and ! starts_with($key, '_'); + } + + /** + * Determine if the given key is guarded. + * + * @param string $key + * @return bool + */ + public function isGuarded($key) + { + return in_array($key, $this->guarded) or $this->guarded == array('*'); + } + + /** + * Determine if the model is totally guarded. + * + * @return bool + */ + public function totallyGuarded() + { + return count($this->fillable) == 0 and $this->guarded == array('*'); + } + + /** + * Remove the table name from a given key. + * + * @param string $key + * @return string + */ + protected function removeTableFromKey($key) + { + if ( ! str_contains($key, '.')) return $key; + + return last(explode('.', $key)); + } + + /** + * Get the relationships that are touched on save. + * + * @return array + */ + public function getTouchedRelations() + { + return $this->touches; + } + + /** + * Set the relationships that are touched on save. + * + * @param array $touches + * @return void + */ + public function setTouchedRelations(array $touches) + { + $this->touches = $touches; + } + + /** + * Get the value indicating whether the IDs are incrementing. + * + * @return bool + */ + public function getIncrementing() + { + return $this->incrementing; + } + + /** + * Set whether IDs are incrementing. + * + * @param bool $value + * @return void + */ + public function setIncrementing($value) + { + $this->incrementing = $value; + } + + /** + * Convert the model instance to JSON. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->toArray(), $options); + } + + /** + * Convert the model instance to an array. + * + * @return array + */ + public function toArray() + { + $attributes = $this->attributesToArray(); + + return array_merge($attributes, $this->relationsToArray()); + } + + /** + * Convert the model's attributes to an array. + * + * @return array + */ + public function attributesToArray() + { + $attributes = $this->getArrayableAttributes(); + + // We want to spin through all the mutated attributes for this model and call + // the mutator for the attribute. We cache off every mutated attributes so + // we don't have to constantly check on attributes that actually change. + foreach ($this->getMutatedAttributes() as $key) + { + if ( ! array_key_exists($key, $attributes)) continue; + + $attributes[$key] = $this->mutateAttribute( + $key, $attributes[$key] + ); + } + + // Here we will grab all of the appended, calculated attributes to this model + // as these attributes are not really in the attributes array, but are run + // when we need to array or JSON the model for convenience to the coder. + foreach ($this->appends as $key) + { + $attributes[$key] = $this->mutateAttribute($key, null); + } + + return $attributes; + } + + /** + * Get an attribute array of all arrayable attributes. + * + * @return array + */ + protected function getArrayableAttributes() + { + return $this->getArrayableItems($this->attributes); + } + + /** + * Get the model's relationships in array form. + * + * @return array + */ + public function relationsToArray() + { + $attributes = array(); + + foreach ($this->getArrayableRelations() as $key => $value) + { + if (in_array($key, $this->hidden)) continue; + + // If the values implements the Arrayable interface we can just call this + // toArray method on the instances which will convert both models and + // collections to their proper array form and we'll set the values. + if ($value instanceof ArrayableInterface) + { + $relation = $value->toArray(); + } + + // If the value is null, we'll still go ahead and set it in this list of + // attributes since null is used to represent empty relationships if + // if it a has one or belongs to type relationships on the models. + elseif (is_null($value)) + { + $relation = $value; + } + + // If the relationships snake-casing is enabled, we will snake case this + // key so that the relation attribute is snake cased in this returned + // array to the developer, making this consisntent with attributes. + if (static::$snakeAttributes) + { + $key = snake_case($key); + } + + // If the relation value has been set, we will set it on this attributes + // list for returning. If it was not arrayable or null, we'll not set + // the value on the array because it is some type of invalid value. + if (isset($relation) or is_null($value)) + { + $attributes[$key] = $relation; + } + } + + return $attributes; + } + + /** + * Get an attribute array of all arrayable relations. + * + * @return array + */ + protected function getArrayableRelations() + { + return $this->getArrayableItems($this->relations); + } + + /** + * Get an attribute array of all arrayable values. + * + * @param array $values + * @return array + */ + protected function getArrayableItems(array $values) + { + if (count($this->visible) > 0) + { + return array_intersect_key($values, array_flip($this->visible)); + } + + return array_diff_key($values, array_flip($this->hidden)); + } + + /** + * Get an attribute from the model. + * + * @param string $key + * @return mixed + */ + public function getAttribute($key) + { + $inAttributes = array_key_exists($key, $this->attributes); + + // If the key references an attribute, we can just go ahead and return the + // plain attribute value from the model. This allows every attribute to + // be dynamically accessed through the _get method without accessors. + if ($inAttributes or $this->hasGetMutator($key)) + { + return $this->getAttributeValue($key); + } + + // If the key already exists in the relationships array, it just means the + // relationship has already been loaded, so we'll just return it out of + // here because there is no need to query within the relations twice. + if (array_key_exists($key, $this->relations)) + { + return $this->relations[$key]; + } + + // If the "attribute" exists as a method on the model, we will just assume + // it is a relationship and will load and return results from the query + // and hydrate the relationship's value on the "relationships" array. + $camelKey = camel_case($key); + + if (method_exists($this, $camelKey)) + { + $relations = $this->$camelKey()->getResults(); + + return $this->relations[$key] = $relations; + } + } + + /** + * Get a plain attribute (not a relationship). + * + * @param string $key + * @return mixed + */ + protected function getAttributeValue($key) + { + $value = $this->getAttributeFromArray($key); + + // If the attribute has a get mutator, we will call that then return what + // it returns as the value, which is useful for transforming values on + // retrieval from the model to a form that is more useful for usage. + if ($this->hasGetMutator($key)) + { + return $this->mutateAttribute($key, $value); + } + + // If the attribute is listed as a date, we will convert it to a DateTime + // instance on retrieval, which makes it quite convenient to work with + // date fields without having to create a mutator for each property. + elseif (in_array($key, $this->getDates())) + { + if ($value) return $this->asDateTime($value); + } + + return $value; + } + + /** + * Get an attribute from the $attributes array. + * + * @param string $key + * @return mixed + */ + protected function getAttributeFromArray($key) + { + if (array_key_exists($key, $this->attributes)) + { + return $this->attributes[$key]; + } + } + + /** + * Determine if a get mutator exists for an attribute. + * + * @param string $key + * @return bool + */ + public function hasGetMutator($key) + { + return method_exists($this, 'get'.studly_case($key).'Attribute'); + } + + /** + * Get the value of an attribute using its mutator. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function mutateAttribute($key, $value) + { + return $this->{'get'.studly_case($key).'Attribute'}($value); + } + + /** + * Set a given attribute on the model. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function setAttribute($key, $value) + { + // First we will check for the presence of a mutator for the set operation + // which simply lets the developers tweak the attribute as it is set on + // the model, such as "json_encoding" an listing of data for storage. + if ($this->hasSetMutator($key)) + { + $method = 'set'.studly_case($key).'Attribute'; + + return $this->{$method}($value); + } + + // If an attribute is listed as a "date", we'll convert it from a DateTime + // instance into a form proper for storage on the database tables using + // the connection grammar's date format. We will auto set the values. + elseif (in_array($key, $this->getDates())) + { + if ($value) + { + $value = $this->fromDateTime($value); + } + } + + $this->attributes[$key] = $value; + } + + /** + * Determine if a set mutator exists for an attribute. + * + * @param string $key + * @return bool + */ + public function hasSetMutator($key) + { + return method_exists($this, 'set'.studly_case($key).'Attribute'); + } + + /** + * Get the attributes that should be converted to dates. + * + * @return array + */ + public function getDates() + { + return array(static::CREATED_AT, static::UPDATED_AT, static::DELETED_AT); + } + + /** + * Convert a DateTime to a storable string. + * + * @param DateTime|int $value + * @return string + */ + public function fromDateTime($value) + { + $format = $this->getDateFormat(); + + // If the value is already a DateTime instance, we will just skip the rest of + // these checks since they will be a waste of time, and hinder performance + // when checking the field. We will just return the DateTime right away. + if ($value instanceof DateTime) + { + // + } + + // If the value is totally numeric, we will assume it is a UNIX timestamp and + // format the date as such. Once we have the date in DateTime form we will + // format it according to the proper format for the database connection. + elseif (is_numeric($value)) + { + $value = Carbon::createFromTimestamp($value); + } + + // If the value is in simple year, month, day format, we will format it using + // that setup. This is for simple "date" fields which do not have hours on + // the field. This conveniently picks up those dates and format correct. + elseif (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $value)) + { + $value = Carbon::createFromFormat('Y-m-d', $value); + } + + // If this value is some other type of string, we'll create the DateTime with + // the format used by the database connection. Once we get the instance we + // can return back the finally formatted DateTime instances to the devs. + elseif ( ! $value instanceof DateTime) + { + $value = Carbon::createFromFormat($format, $value); + } + + return $value->format($format); + } + + /** + * Return a timestamp as DateTime object. + * + * @param mixed $value + * @return DateTime + */ + protected function asDateTime($value) + { + // If this value is an integer, we will assume it is a UNIX timestamp's value + // and format a Carbon object from this timestamp. This allows flexibility + // when defining your date fields as they might be UNIX timestamps here. + if (is_numeric($value)) + { + return Carbon::createFromTimestamp($value); + } + + // If the value is in simply year, month, day format, we will instantiate the + // Carbon instances from that fomrat. Again, this provides for simple date + // fields on the database, while still supporting Carbonized conversion. + elseif (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $value)) + { + return Carbon::createFromFormat('Y-m-d', $value); + } + + // Finally, we will just assume this date is in the format used by default on + // the database connection and use that format to create the Carbon object + // that is returned back out to the developers after we convert it here. + elseif ( ! $value instanceof DateTime) + { + $format = $this->getDateFormat(); + + return Carbon::createFromFormat($format, $value); + } + + return Carbon::instance($value); + } + + /** + * Get the format for database stored dates. + * + * @return string + */ + protected function getDateFormat() + { + return $this->getConnection()->getQueryGrammar()->getDateFormat(); + } + + /** + * Clone the model into a new, non-existing instance. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function replicate() + { + $attributes = array_except($this->attributes, array($this->getKeyName())); + + with($instance = new static)->setRawAttributes($attributes); + + return $instance->setRelations($this->relations); + } + + /** + * Get all of the current attributes on the model. + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Set the array of model attributes. No checking is done. + * + * @param array $attributes + * @param bool $sync + * @return void + */ + public function setRawAttributes(array $attributes, $sync = false) + { + $this->attributes = $attributes; + + if ($sync) $this->syncOriginal(); + } + + /** + * Get the model's original attribute values. + * + * @param string $key + * @param mixed $default + * @return array + */ + public function getOriginal($key = null, $default = null) + { + return array_get($this->original, $key, $default); + } + + /** + * Sync the original attributes with the current. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function syncOriginal() + { + $this->original = $this->attributes; + + return $this; + } + + /** + * Determine if a given attribute is dirty. + * + * @param string $attribute + * @return bool + */ + public function isDirty($attribute) + { + return array_key_exists($attribute, $this->getDirty()); + } + + /** + * Get the attributes that have been changed since last sync. + * + * @return array + */ + public function getDirty() + { + $dirty = array(); + + foreach ($this->attributes as $key => $value) + { + if ( ! array_key_exists($key, $this->original) or $value !== $this->original[$key]) + { + $dirty[$key] = $value; + } + } + + return $dirty; + } + + /** + * Get all the loaded relations for the instance. + * + * @return array + */ + public function getRelations() + { + return $this->relations; + } + + /** + * Get a specified relationship. + * + * @param string $relation + * @return mixed + */ + public function getRelation($relation) + { + return $this->relations[$relation]; + } + + /** + * Set the specific relationship in the model. + * + * @param string $relation + * @param mixed $value + * @return \Illuminate\Database\Eloquent\Model + */ + public function setRelation($relation, $value) + { + $this->relations[$relation] = $value; + + return $this; + } + + /** + * Set the entire relations array on the model. + * + * @param array $relations + * @return \Illuminate\Database\Eloquent\Model + */ + public function setRelations(array $relations) + { + $this->relations = $relations; + + return $this; + } + + /** + * Get the database connection for the model. + * + * @return \Illuminate\Database\Connection + */ + public function getConnection() + { + return static::resolveConnection($this->connection); + } + + /** + * Get the current connection name for the model. + * + * @return string + */ + public function getConnectionName() + { + return $this->connection; + } + + /** + * Set the connection associated with the model. + * + * @param string $name + * @return \Illuminate\Database\Eloquent\Model + */ + public function setConnection($name) + { + $this->connection = $name; + + return $this; + } + + /** + * Resolve a connection instance. + * + * @param string $connection + * @return \Illuminate\Database\Connection + */ + public static function resolveConnection($connection = null) + { + return static::$resolver->connection($connection); + } + + /** + * Get the connection resolver instance. + * + * @return \Illuminate\Database\ConnectionResolverInterface + */ + public static function getConnectionResolver() + { + return static::$resolver; + } + + /** + * Set the connection resolver instance. + * + * @param \Illuminate\Database\ConnectionResolverInterface $resolver + * @return void + */ + public static function setConnectionResolver(Resolver $resolver) + { + static::$resolver = $resolver; + } + + /** + * Get the event dispatcher instance. + * + * @return \Illuminate\Events\Dispatcher + */ + public static function getEventDispatcher() + { + return static::$dispatcher; + } + + /** + * Set the event dispatcher instance. + * + * @param \Illuminate\Events\Dispatcher $dispatcher + * @return void + */ + public static function setEventDispatcher(Dispatcher $dispatcher) + { + static::$dispatcher = $dispatcher; + } + + /** + * Unset the event dispatcher for models. + * + * @return void + */ + public static function unsetEventDispatcher() + { + static::$dispatcher = null; + } + + /** + * Get the mutated attributes for a given instance. + * + * @return array + */ + public function getMutatedAttributes() + { + $class = get_class($this); + + if (isset(static::$mutatorCache[$class])) + { + return static::$mutatorCache[get_class($this)]; + } + + return array(); + } + + /** + * Dynamically retrieve attributes on the model. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->getAttribute($key); + } + + /** + * Dynamically set attributes on the model. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this->setAttribute($key, $value); + } + + /** + * Determine if the given attribute exists. + * + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + return isset($this->$offset); + } + + /** + * Get the value for a given offset. + * + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->$offset; + } + + /** + * Set the value for a given offset. + * + * @param mixed $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value) + { + $this->$offset = $value; + } + + /** + * Unset the value for a given offset. + * + * @param mixed $offset + * @return void + */ + public function offsetUnset($offset) + { + unset($this->$offset); + } + + /** + * Determine if an attribute exists on the model. + * + * @param string $key + * @return void + */ + public function __isset($key) + { + return isset($this->attributes[$key]) or isset($this->relations[$key]); + } + + /** + * Unset an attribute on the model. + * + * @param string $key + * @return void + */ + public function __unset($key) + { + unset($this->attributes[$key]); + + unset($this->relations[$key]); + } + + /** + * Handle dynamic method calls into the method. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (in_array($method, array('increment', 'decrement'))) + { + return call_user_func_array(array($this, $method), $parameters); + } + + $query = $this->newQuery(); + + return call_user_func_array(array($query, $method), $parameters); + } + + /** + * Handle dynamic static method calls into the method. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + $instance = new static; + + return call_user_func_array(array($instance, $method), $parameters); + } + + /** + * Convert the model to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->toJson(); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Database/Eloquent/ModelNotFoundException.php b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/ModelNotFoundException.php new file mode 100755 index 0000000..25750dc --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/ModelNotFoundException.php @@ -0,0 +1,3 @@ +relation = $relation; + $this->foreignKey = $foreignKey; + + parent::__construct($query, $parent); + } + + /** + * Get the results of the relationship. + * + * @return mixed + */ + public function getResults() + { + return $this->query->first(); + } + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + public function addConstraints() + { + if (static::$constraints) + { + // For belongs to relationships, which are essentially the inverse of has one + // or has many relationships, we need to actually query on the primary key + // of the related models matching on the foreign key that's on a parent. + $key = $this->related->getKeyName(); + + $table = $this->related->getTable(); + + $this->query->where($table.'.'.$key, '=', $this->parent->{$this->foreignKey}); + } + } + + /** + * Add the constraints for a relationship count query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationCountQuery(Builder $query) + { + throw new LogicException('Has method invalid on "belongsTo" relations.'); + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + // We'll grab the primary key name of the related models since it could be set to + // a non-standard name and not "id". We will then construct the constraint for + // our eagerly loading query so it returns the proper models from execution. + $key = $this->related->getKeyName(); + + $key = $this->related->getTable().'.'.$key; + + $this->query->whereIn($key, $this->getEagerModelKeys($models)); + } + + /** + * Gather the keys from an array of related models. + * + * @param array $models + * @return array + */ + protected function getEagerModelKeys(array $models) + { + $keys = array(); + + // First we need to gather all of the keys from the parent models so we know what + // to query for via the eager loading query. We will add them to an array then + // execute a "where in" statement to gather up all of those related records. + foreach ($models as $model) + { + if ( ! is_null($value = $model->{$this->foreignKey})) + { + $keys[] = $value; + } + } + + // If there are no keys that were not null we will just return an array with 0 in + // it so the query doesn't fail, but will not return any results, which should + // be what this developer is expecting in a case where this happens to them. + if (count($keys) == 0) + { + return array(0); + } + + return array_values(array_unique($keys)); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return void + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) + { + $model->setRelation($relation, null); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + $foreign = $this->foreignKey; + + // First we will get to build a dictionary of the child models by their primary + // key of the relationship, then we can easily match the children back onto + // the parents using that dictionary and the primary key of the children. + $dictionary = array(); + + foreach ($results as $result) + { + $dictionary[$result->getKey()] = $result; + } + + // Once we have the dictionary constructed, we can loop through all the parents + // and match back onto their children using these keys of the dictionary and + // the primary key of the children to map them onto the correct instances. + foreach ($models as $model) + { + if (isset($dictionary[$model->$foreign])) + { + $model->setRelation($relation, $dictionary[$model->$foreign]); + } + } + + return $models; + } + + /** + * Associate the model instance to the given parent. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return \Illuminate\Database\Eloquent\Model + */ + public function associate(Model $model) + { + $this->parent->setAttribute($this->foreignKey, $model->getKey()); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * Update the parent model on the relationship. + * + * @param array $attributes + * @return mixed + */ + public function update(array $attributes) + { + $instance = $this->getResults(); + + return $instance->fill($attributes)->save(); + } + + /** + * Get the foreign key of the relationship. + * + * @return string + */ + public function getForeignKey() + { + return $this->foreignKey; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php new file mode 100755 index 0000000..dba1111 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -0,0 +1,900 @@ +table = $table; + $this->otherKey = $otherKey; + $this->foreignKey = $foreignKey; + $this->relationName = $relationName; + + parent::__construct($query, $parent); + } + + /** + * Get the results of the relationship. + * + * @return mixed + */ + public function getResults() + { + return $this->get(); + } + + /** + * Execute the query and get the first result. + * + * @param array $columns + * @return mixed + */ + public function first($columns = array('*')) + { + $results = $this->take(1)->get($columns); + + return count($results) > 0 ? $results->first() : null; + } + + /** + * Execute the query as a "select" statement. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Collection + */ + public function get($columns = array('*')) + { + // First we'll add the proper select columns onto the query so it is run with + // the proper columns. Then, we will get the results and hydrate out pivot + // models with the result of those columns as a separate model relation. + $select = $this->getSelectColumns($columns); + + $models = $this->query->addSelect($select)->getModels(); + + $this->hydratePivotRelation($models); + + // If we actually found models we will also eager load any relationships that + // have been specified as needing to be eager loaded. This will solve the + // n + 1 query problem for the developer and also increase performance. + if (count($models) > 0) + { + $models = $this->query->eagerLoadRelations($models); + } + + return $this->related->newCollection($models); + } + + /** + * Get a paginator for the "select" statement. + * + * @param int $perPage + * @param array $columns + * @return \Illuminate\Pagination\Paginator + */ + public function paginate($perPage = null, $columns = array('*')) + { + $this->query->addSelect($this->getSelectColumns($columns)); + + // When paginating results, we need to add the pivot columns to the query and + // then hydrate into the pivot objects once the results have been gathered + // from the database since this isn't performed by the Eloquent builder. + $pager = $this->query->paginate($perPage, $columns); + + $this->hydratePivotRelation($pager->getItems()); + + return $pager; + } + + /** + * Hydrate the pivot table relationship on the models. + * + * @param array $models + * @return void + */ + protected function hydratePivotRelation(array $models) + { + // To hydrate the pivot relationship, we will just gather the pivot attributes + // and create a new Pivot model, which is basically a dynamic model that we + // will set the attributes, table, and connections on so it they be used. + foreach ($models as $model) + { + $pivot = $this->newExistingPivot($this->cleanPivotAttributes($model)); + + $model->setRelation('pivot', $pivot); + } + } + + /** + * Get the pivot attributes from a model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return array + */ + protected function cleanPivotAttributes(Model $model) + { + $values = array(); + + foreach ($model->getAttributes() as $key => $value) + { + // To get the pivots attributes we will just take any of the attributes which + // begin with "pivot_" and add those to this arrays, as well as unsetting + // them from the parent's models since they exist in a different table. + if (strpos($key, 'pivot_') === 0) + { + $values[substr($key, 6)] = $value; + + unset($model->$key); + } + } + + return $values; + } + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + public function addConstraints() + { + $this->setJoin(); + + if (static::$constraints) $this->setWhere(); + } + + /** + * Add the constraints for a relationship count query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationCountQuery(Builder $query) + { + $this->setJoin($query); + + return parent::getRelationCountQuery($query); + } + + /** + * Set the select clause for the relation query. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + protected function getSelectColumns(array $columns = array('*')) + { + if ($columns == array('*')) + { + $columns = array($this->related->getTable().'.*'); + } + + return array_merge($columns, $this->getAliasedPivotColumns()); + } + + /** + * Get the pivot columns for the relation. + * + * @return array + */ + protected function getAliasedPivotColumns() + { + $defaults = array($this->foreignKey, $this->otherKey); + + // We need to alias all of the pivot columns with the "pivot_" prefix so we + // can easily extract them out of the models and put them into the pivot + // relationships when they are retrieved and hydrated into the models. + $columns = array(); + + foreach (array_merge($defaults, $this->pivotColumns) as $column) + { + $columns[] = $this->table.'.'.$column.' as pivot_'.$column; + } + + return array_unique($columns); + } + + /** + * Set the join clause for the relation query. + * + * @param \Illuminate\Database\Eloquent\Builder|null + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + protected function setJoin($query = null) + { + $query = $query ?: $this->query; + + // We need to join to the intermediate table on the related model's primary + // key column with the intermediate table's foreign key for the related + // model instance. Then we can set the "where" for the parent models. + $baseTable = $this->related->getTable(); + + $key = $baseTable.'.'.$this->related->getKeyName(); + + $query->join($this->table, $key, '=', $this->getOtherKey()); + + return $this; + } + + /** + * Set the where clause for the relation query. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + protected function setWhere() + { + $foreign = $this->getForeignKey(); + + $this->query->where($foreign, '=', $this->parent->getKey()); + + return $this; + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + $this->query->whereIn($this->getForeignKey(), $this->getKeys($models)); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return void + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) + { + $model->setRelation($relation, $this->related->newCollection()); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + $dictionary = $this->buildDictionary($results); + + // Once we have an array dictionary of child objects we can easily match the + // children back to their parent using the dictionary and the keys on the + // the parent models. Then we will return the hydrated models back out. + foreach ($models as $model) + { + if (isset($dictionary[$key = $model->getKey()])) + { + $collection = $this->related->newCollection($dictionary[$key]); + + $model->setRelation($relation, $collection); + } + } + + return $models; + } + + /** + * Build model dictionary keyed by the relation's foreign key. + * + * @param \Illuminate\Database\Eloquent\Collection $results + * @return array + */ + protected function buildDictionary(Collection $results) + { + $foreign = $this->foreignKey; + + // First we will build a dictionary of child models keyed by the foreign key + // of the relation so that we will easily and quickly match them to their + // parents without having a possibly slow inner loops for every models. + $dictionary = array(); + + foreach ($results as $result) + { + $dictionary[$result->pivot->$foreign][] = $result; + } + + return $dictionary; + } + + /** + * Touch all of the related models for the relationship. + * + * E.g.: Touch all roles associated with this user. + * + * @return void + */ + public function touch() + { + $key = $this->getRelated()->getKeyName(); + + $columns = $this->getRelatedFreshUpdate(); + + // If we actually have IDs for the relation, we will run the query to update all + // the related model's timestamps, to make sure these all reflect the changes + // to the parent models. This will help us keep any caching synced up here. + $ids = $this->getRelatedIds(); + + if (count($ids) > 0) + { + $this->getRelated()->newQuery()->whereIn($key, $ids)->update($columns); + } + } + + /** + * Get all of the IDs for the related models. + * + * @return array + */ + public function getRelatedIds() + { + $related = $this->getRelated(); + + $fullKey = $related->getQualifiedKeyName(); + + return $this->getQuery()->select($fullKey)->lists($related->getKeyName()); + } + + /** + * Save a new model and attach it to the parent model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param array $joining + * @param bool $touch + * @return \Illuminate\Database\Eloquent\Model + */ + public function save(Model $model, array $joining = array(), $touch = true) + { + $model->save(array('touch' => false)); + + $this->attach($model->getKey(), $joining, $touch); + + return $model; + } + + /** + * Save an array of new models and attach them to the parent model. + * + * @param array $models + * @param array $joinings + * @return array + */ + public function saveMany(array $models, array $joinings = array()) + { + foreach ($models as $key => $model) + { + $this->save($model, (array) array_get($joinings, $key), false); + } + + $this->touchIfTouching(); + + return $models; + } + + /** + * Create a new instance of the related model. + * + * @param array $attributes + * @param array $joining + * @param bool $touch + * @return \Illuminate\Database\Eloquent\Model + */ + public function create(array $attributes, array $joining = array(), $touch = true) + { + $instance = $this->related->newInstance($attributes); + + // Once we save the related model, we need to attach it to the base model via + // through intermediate table so we'll use the existing "attach" method to + // accomplish this which will insert the record and any more attributes. + $instance->save(array('touch' => false)); + + $this->attach($instance->getKey(), $joining, $touch); + + return $instance; + } + + /** + * Create an array of new instances of the related models. + * + * @param array $records + * @param array $joinings + * @return \Illuminate\Database\Eloquent\Model + */ + public function createMany(array $records, array $joinings = array()) + { + $instances = array(); + + foreach ($records as $key => $record) + { + $instances[] = $this->create($record, (array) array_get($joinings, $key), false); + } + + $this->touchIfTouching(); + + return $instances; + } + + /** + * Sync the intermediate tables with a list of IDs. + * + * @param array $ids + * @param bool $detaching + * @return void + */ + public function sync(array $ids, $detaching = true) + { + // First we need to attach any of the associated models that are not currently + // in this joining table. We'll spin through the given IDs, checking to see + // if they exist in the array of current ones, and if not we will insert. + $current = $this->newPivotQuery()->lists($this->otherKey); + + $records = $this->formatSyncList($ids); + + $detach = array_diff($current, array_keys($records)); + + // Next, we will take the differences of the currents and given IDs and detach + // all of the entities that exist in the "current" array but are not in the + // the array of the IDs given to the method which will complete the sync. + if ($detaching and count($detach) > 0) + { + $this->detach($detach); + } + + // Now we are finally ready to attach the new records. Note that we'll disable + // touching until after the entire operation is complete so we don't fire a + // ton of touch operations until we are totally done syncing the records. + $this->attachNew($records, $current, false); + + $this->touchIfTouching(); + } + + /** + * Format the sync list so that it is keyed by ID. + * + * @param array $records + * @return array + */ + protected function formatSyncList(array $records) + { + $results = array(); + + foreach ($records as $id => $attributes) + { + if (is_numeric($attributes)) + { + list($id, $attributes) = array((int) $attributes, array()); + } + + $results[$id] = $attributes; + } + + return $results; + } + + /** + * Attach all of the IDs that aren't in the current array. + * + * @param array $records + * @param array $current + * @param bool $touch + * @return void + */ + protected function attachNew(array $records, array $current, $touch = true) + { + foreach ($records as $id => $attributes) + { + // If the ID is not in the list of existing pivot IDs, we will insert a new pivot + // record, otherwise, we will just update this existing record on this joining + // table, so that the developers will easily update these records pain free. + if ( ! in_array($id, $current)) + { + $this->attach($id, $attributes, $touch); + } + elseif (count($attributes) > 0) + { + $this->updateExistingPivot($id, $attributes, $touch); + } + } + } + + /** + * Update an existing pivot record on the table. + * + * @param mixed $id + * @param array $attributes + * @param bool $touch + * @return void + */ + protected function updateExistingPivot($id, array $attributes, $touch) + { + if (in_array($this->updatedAt(), $this->pivotColumns)) + { + $attributes = $this->setTimestampsOnAttach($attributes, true); + } + + $this->newPivotStatementForId($id)->update($attributes); + + if ($touch) $this->touchIfTouching(); + } + + /** + * Attach a model to the parent. + * + * @param mixed $id + * @param array $attributes + * @param bool $touch + * @return void + */ + public function attach($id, array $attributes = array(), $touch = true) + { + if ($id instanceof Model) $id = $id->getKey(); + + $query = $this->newPivotStatement(); + + $query->insert($this->createAttachRecords((array) $id, $attributes)); + + if ($touch) $this->touchIfTouching(); + } + + /** + * Create an array of records to insert into the pivot table. + * + * @param array $ids + * @return void + */ + protected function createAttachRecords($ids, array $attributes) + { + $records = array(); + + $timed = in_array($this->createdAt(), $this->pivotColumns); + + // To create the attachment records, we will simply spin through the IDs given + // and create a new record to insert for each ID. Each ID may actually be a + // key in the array, with extra attributes to be placed in other columns. + foreach ($ids as $key => $value) + { + $records[] = $this->attacher($key, $value, $attributes, $timed); + } + + return $records; + } + + /** + * Create a full attachment record payload. + * + * @param int $key + * @param mixed $value + * @param array $attributes + * @param bool $timed + * @return array + */ + protected function attacher($key, $value, $attributes, $timed) + { + list($id, $extra) = $this->getAttachId($key, $value, $attributes); + + // To create the attachment records, we will simply spin through the IDs given + // and create a new record to insert for each ID. Each ID may actually be a + // key in the array, with extra attributes to be placed in other columns. + $record = $this->createAttachRecord($id, $timed); + + return array_merge($record, $extra); + } + + /** + * Get the attach record ID and extra attributes. + * + * @param mixed $key + * @param mixed $value + * @param array $attributes + * @return array + */ + protected function getAttachId($key, $value, array $attributes) + { + if (is_array($value)) + { + return array($key, array_merge($value, $attributes)); + } + else + { + return array($value, $attributes); + } + } + + /** + * Create a new pivot attachment record. + * + * @param int $id + * @param bool $timed + * @return array + */ + protected function createAttachRecord($id, $timed) + { + $record[$this->foreignKey] = $this->parent->getKey(); + + $record[$this->otherKey] = $id; + + // If the record needs to have creation and update timestamps, we will make + // them by calling the parent model's "freshTimestamp" method which will + // provide us with a fresh timestamp in this model's preferred format. + if ($timed) + { + $record = $this->setTimestampsOnAttach($record); + } + + return $record; + } + + /** + * Set the creation and update timstamps on an attach record. + * + * @param array $record + * @param bool $exists + * @return array + */ + protected function setTimestampsOnAttach(array $record, $exists = false) + { + $fresh = $this->parent->freshTimestamp(); + + if ( ! $exists) $record[$this->createdAt()] = $fresh; + + $record[$this->updatedAt()] = $fresh; + + return $record; + } + + /** + * Detach models from the relationship. + * + * @param int|array $ids + * @param bool $touch + * @return int + */ + public function detach($ids = array(), $touch = true) + { + if ($ids instanceof Model) $ids = (array) $ids->getKey(); + + $query = $this->newPivotQuery(); + + // If associated IDs were passed to the method we will only delete those + // associations, otherwise all of the association ties will be broken. + // We'll return the numbers of affected rows when we do the deletes. + $ids = (array) $ids; + + if (count($ids) > 0) + { + $query->whereIn($this->otherKey, $ids); + } + + if ($touch) $this->touchIfTouching(); + + // Once we have all of the conditions set on the statement, we are ready + // to run the delete on the pivot table. Then, if the touch parameter + // is true, we will go ahead and touch all related models to sync. + $results = $query->delete(); + + return $results; + } + + /** + * If we're touching the parent model, touch. + * + * @return void + */ + public function touchIfTouching() + { + if ($this->touchingParent()) $this->getParent()->touch(); + + if ($this->getParent()->touches($this->relationName)) $this->touch(); + } + + /** + * Determine if we should touch the parent on sync. + * + * @return bool + */ + protected function touchingParent() + { + return $this->getRelated()->touches($this->guessInverseRelation()); + } + + /** + * Attempt to guess the name of the inverse of the relation. + * + * @return string + */ + protected function guessInverseRelation() + { + return strtolower(str_plural(class_basename($this->getParent()))); + } + + /** + * Create a new query builder for the pivot table. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function newPivotQuery() + { + $query = $this->newPivotStatement(); + + return $query->where($this->foreignKey, $this->parent->getKey()); + } + + /** + * Get a new plain query builder for the pivot table. + * + * @return \Illuminate\Database\Query\Builder + */ + public function newPivotStatement() + { + return $this->query->getQuery()->newQuery()->from($this->table); + } + + /** + * Get a new pivot statement for a given "other" ID. + * + * @param mixed $id + * @return \Illuminate\Database\Query\Builder + */ + protected function newPivotStatementForId($id) + { + $pivot = $this->newPivotStatement(); + + $key = $this->parent->getKey(); + + return $pivot->where($this->foreignKey, $key)->where($this->otherKey, $id); + } + + /** + * Create a new pivot model instance. + * + * @param array $attributes + * @param bool $exists + * @return \Illuminate\Database\Eloquent\Relation\Pivot + */ + public function newPivot(array $attributes = array(), $exists = false) + { + $pivot = new Pivot($this->parent, $attributes, $this->table, $exists); + + $pivot->setPivotKeys($this->foreignKey, $this->otherKey); + + return $pivot; + } + + /** + * Create a new existing pivot model instance. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Relations\Pivot + */ + public function newExistingPivot(array $attributes = array()) + { + return $this->newPivot($attributes, true); + } + + /** + * Set the columns on the pivot table to retrieve. + * + * @param array $columns + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function withPivot($columns) + { + $columns = is_array($columns) ? $columns : func_get_args(); + + $this->pivotColumns = array_merge($this->pivotColumns, $columns); + + return $this; + } + + /** + * Specify that the pivot table has creation and update timestamps. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ + public function withTimestamps() + { + return $this->withPivot($this->createdAt(), $this->updatedAt()); + } + + /** + * Get the related model's updated at column name. + * + * @return string + */ + public function getRelatedFreshUpdate() + { + return array($this->related->getUpdatedAtColumn() => $this->related->freshTimestamp()); + } + + /** + * Get the fully qualified foreign key for the relation. + * + * @return string + */ + public function getForeignKey() + { + return $this->table.'.'.$this->foreignKey; + } + + /** + * Get the fully qualified "other key" for the relation. + * + * @return string + */ + public function getOtherKey() + { + return $this->table.'.'.$this->otherKey; + } + + /** + * Get the intermediate table for the relationship. + * + * @return string + */ + public function getTable() + { + return $this->table; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasMany.php b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasMany.php new file mode 100755 index 0000000..1718582 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasMany.php @@ -0,0 +1,47 @@ +query->get(); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return void + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) + { + $model->setRelation($relation, $this->related->newCollection()); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + return $this->matchMany($models, $results, $relation); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOne.php b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOne.php new file mode 100755 index 0000000..69437c2 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOne.php @@ -0,0 +1,47 @@ +query->first(); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return void + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) + { + $model->setRelation($relation, null); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + return $this->matchOne($models, $results, $relation); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php new file mode 100755 index 0000000..54040cb --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php @@ -0,0 +1,259 @@ +foreignKey = $foreignKey; + + parent::__construct($query, $parent); + } + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + public function addConstraints() + { + if (static::$constraints) + { + $key = $this->parent->getKey(); + + $this->query->where($this->foreignKey, '=', $key); + } + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + $this->query->whereIn($this->foreignKey, $this->getKeys($models)); + } + + /** + * Match the eagerly loaded results to their single parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function matchOne(array $models, Collection $results, $relation) + { + return $this->matchOneOrMany($models, $results, $relation, 'one'); + } + + /** + * Match the eagerly loaded results to their many parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function matchMany(array $models, Collection $results, $relation) + { + return $this->matchOneOrMany($models, $results, $relation, 'many'); + } + + /** + * Match the eagerly loaded results to their many parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @param string $type + * @return array + */ + protected function matchOneOrMany(array $models, Collection $results, $relation, $type) + { + $dictionary = $this->buildDictionary($results); + + // Once we have the dictionary we can simply spin through the parent models to + // link them up with their children using the keyed dictionary to make the + // matching very convenient and easy work. Then we'll just return them. + foreach ($models as $model) + { + $key = $model->getKey(); + + if (isset($dictionary[$key])) + { + $value = $this->getRelationValue($dictionary, $key, $type); + + $model->setRelation($relation, $value); + } + } + + return $models; + } + + /** + * Get the value of a relationship by one or many type. + * + * @param array $dictionary + * @param string $key + * @param string $type + * @return mixed + */ + protected function getRelationValue(array $dictionary, $key, $type) + { + $value = $dictionary[$key]; + + return $type == 'one' ? reset($value) : $this->related->newCollection($value); + } + + /** + * Build model dictionary keyed by the relation's foreign key. + * + * @param \Illuminate\Database\Eloquent\Collection $results + * @return array + */ + protected function buildDictionary(Collection $results) + { + $dictionary = array(); + + $foreign = $this->getPlainForeignKey(); + + // First we will create a dictionary of models keyed by the foreign key of the + // relationship as this will allow us to quickly access all of the related + // models without having to do nested looping which will be quite slow. + foreach ($results as $result) + { + $dictionary[$result->{$foreign}][] = $result; + } + + return $dictionary; + } + + /** + * Attach a model instance to the parent model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return \Illuminate\Database\Eloquent\Model + */ + public function save(Model $model) + { + $model->setAttribute($this->getPlainForeignKey(), $this->parent->getKey()); + + return $model->save() ? $model : false; + } + + /** + * Attach an array of models to the parent instance. + * + * @param array $models + * @return array + */ + public function saveMany(array $models) + { + array_walk($models, array($this, 'save')); + + return $models; + } + + /** + * Create a new instance of the related model. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function create(array $attributes) + { + $foreign = array( + $this->getPlainForeignKey() => $this->parent->getKey() + ); + + // Here we will set the raw attributes to avoid hitting the "fill" method so + // that we do not have to worry about a mass accessor rules blocking sets + // on the models. Otherwise, some of these attributes will not get set. + $instance = $this->related->newInstance(); + + $instance->setRawAttributes(array_merge($attributes, $foreign)); + + $instance->save(); + + return $instance; + } + + /** + * Create an array of new instances of the related model. + * + * @param array $records + * @return array + */ + public function createMany(array $records) + { + $instances = array(); + + foreach ($records as $record) + { + $instances[] = $this->create($record); + } + + return $instances; + } + + /** + * Perform an update on all the related models. + * + * @param array $attributes + * @return int + */ + public function update(array $attributes) + { + if ($this->related->usesTimestamps()) + { + $attributes[$this->relatedUpdatedAt()] = $this->related->freshTimestamp(); + } + + return $this->query->update($attributes); + } + + /** + * Get the foreign key for the relationship. + * + * @return string + */ + public function getForeignKey() + { + return $this->foreignKey; + } + + /** + * Get the plain foreign key. + * + * @return string + */ + public function getPlainForeignKey() + { + $segments = explode('.', $this->getForeignKey()); + + return $segments[count($segments) - 1]; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphMany.php b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphMany.php new file mode 100755 index 0000000..710fab5 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphMany.php @@ -0,0 +1,47 @@ +query->get(); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return void + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) + { + $model->setRelation($relation, $this->related->newCollection()); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + return $this->matchMany($models, $results, $relation); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOne.php b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOne.php new file mode 100755 index 0000000..9d00c53 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOne.php @@ -0,0 +1,47 @@ +query->first(); + } + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return void + */ + public function initRelation(array $models, $relation) + { + foreach ($models as $model) + { + $model->setRelation($relation, null); + } + + return $models; + } + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + public function match(array $models, Collection $results, $relation) + { + return $this->matchOne($models, $results, $relation); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php new file mode 100755 index 0000000..f83ff4d --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php @@ -0,0 +1,160 @@ +morphType = $type; + + $this->morphClass = get_class($parent); + + parent::__construct($query, $parent, $id); + } + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + public function addConstraints() + { + if (static::$constraints) + { + parent::addConstraints(); + + $this->query->where($this->morphType, $this->morphClass); + } + } + + /** + * Add the constraints for a relationship count query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationCountQuery(Builder $query) + { + $query = parent::getRelationCountQuery($query); + + return $query->where($this->morphType, $this->morphClass); + } + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + public function addEagerConstraints(array $models) + { + parent::addEagerConstraints($models); + + $this->query->where($this->morphType, $this->morphClass); + } + + /** + * Attach a model instance to the parent model. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return \Illuminate\Database\Eloquent\Model + */ + public function save(Model $model) + { + $model->setAttribute($this->getPlainMorphType(), $this->morphClass); + + return parent::save($model); + } + + /** + * Create a new instance of the related model. + * + * @param array $attributes + * @return \Illuminate\Database\Eloquent\Model + */ + public function create(array $attributes) + { + $foreign = $this->getForeignAttributesForCreate(); + + // When saving a polymorphic relationship, we need to set not only the foreign + // key, but also the foreign key type, which is typically the class name of + // the parent model. This makes the polymorphic item unique in the table. + $attributes = array_merge($attributes, $foreign); + + $instance = $this->related->newInstance($attributes); + + $instance->save(); + + return $instance; + } + + /** + * Get the foreign ID and type for creating a related model. + * + * @return array + */ + protected function getForeignAttributesForCreate() + { + $foreign = array($this->getPlainForeignKey() => $this->parent->getKey()); + + $foreign[last(explode('.', $this->morphType))] = $this->morphClass; + + return $foreign; + } + + /** + * Get the foreign key "type" name. + * + * @return string + */ + public function getMorphType() + { + return $this->morphType; + } + + /** + * Get the plain morph type name without the table. + * + * @return string + */ + public function getPlainMorphType() + { + return last(explode('.', $this->morphType)); + } + + /** + * Get the class name of the parent model. + * + * @return string + */ + public function getMorphClass() + { + return $this->morphClass; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Pivot.php b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Pivot.php new file mode 100755 index 0000000..9c7ec55 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Pivot.php @@ -0,0 +1,158 @@ +setRawAttributes($attributes); + + $this->setTable($table); + + $this->setConnection($parent->getConnectionName()); + + // We store off the parent instance so we will access the timestamp column names + // for the model, since the pivot model timestamps aren't easily configurable + // from the developer's point of view. We can use the parents to get these. + $this->parent = $parent; + + $this->exists = $exists; + + $this->timestamps = $this->hasTimestampAttributes(); + } + + /** + * Set the keys for a save update query. + * + * @param \Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Database\Eloquent\Builder + */ + protected function setKeysForSaveQuery($query) + { + $query->where($this->foreignKey, $this->getAttribute($this->foreignKey)); + + return $query->where($this->otherKey, $this->getAttribute($this->otherKey)); + } + + /** + * Delete the pivot model record from the database. + * + * @return int + */ + public function delete() + { + $foreign = $this->getAttribute($this->foreignKey); + + $query = $this->newQuery()->where($this->foreignKey, $foreign); + + return $query->where($this->otherKey, $this->getAttribute($this->otherKey))->delete(); + } + + /** + * Get the foreign key column name. + * + * @return string + */ + public function getForeignKey() + { + return $this->foreignKey; + } + + /** + * Get the "other key" column name. + * + * @return string + */ + public function getOtherKey() + { + return $this->otherKey; + } + + /** + * Set the key names for the pivot model instance. + * + * @param string $foreignKey + * @param string $otherKey + * @return void + */ + public function setPivotKeys($foreignKey, $otherKey) + { + $this->foreignKey = $foreignKey; + + $this->otherKey = $otherKey; + } + + /** + * Determine if the pivot model has timestamp attributes. + * + * @return bool + */ + public function hasTimestampAttributes() + { + return array_key_exists($this->getCreatedAtColumn(), $this->attributes); + } + + /** + * Get the name of the "created at" column. + * + * @return string + */ + public function getCreatedAtColumn() + { + return $this->parent->getCreatedAtColumn(); + } + + /** + * Get the name of the "updated at" column. + * + * @return string + */ + public function getUpdatedAtColumn() + { + return $this->parent->getUpdatedAtColumn(); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Relation.php b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Relation.php new file mode 100755 index 0000000..2a6d0df --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/Relation.php @@ -0,0 +1,278 @@ +query = $query; + $this->parent = $parent; + $this->related = $query->getModel(); + + $this->addConstraints(); + } + + /** + * Set the base constraints on the relation query. + * + * @return void + */ + abstract public function addConstraints(); + + /** + * Set the constraints for an eager load of the relation. + * + * @param array $models + * @return void + */ + abstract public function addEagerConstraints(array $models); + + /** + * Initialize the relation on a set of models. + * + * @param array $models + * @param string $relation + * @return void + */ + abstract public function initRelation(array $models, $relation); + + /** + * Match the eagerly loaded results to their parents. + * + * @param array $models + * @param \Illuminate\Database\Eloquent\Collection $results + * @param string $relation + * @return array + */ + abstract public function match(array $models, Collection $results, $relation); + + /** + * Get the results of the relationship. + * + * @return mixed + */ + abstract public function getResults(); + + /** + * Touch all of the related models for the relationship. + * + * @return void + */ + public function touch() + { + $column = $this->getRelated()->getUpdatedAtColumn(); + + $this->rawUpdate(array($column => $this->getRelated()->freshTimestamp())); + } + + /** + * Restore all of the soft deleted related models. + * + * @return int + */ + public function restore() + { + return $this->query->withTrashed()->restore(); + } + + /** + * Run a raw update against the base query. + * + * @param array $attributes + * @return int + */ + public function rawUpdate(array $attributes = array()) + { + return $this->query->update($attributes); + } + + /** + * Add the constraints for a relationship count query. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getRelationCountQuery(Builder $query) + { + $query->select(new Expression('count(*)')); + + $key = $this->wrap($this->parent->getQualifiedKeyName()); + + return $query->where($this->getForeignKey(), '=', new Expression($key)); + } + + /** + * Run a callback with constrains disabled on the relation. + * + * @param \Closure $callback + * @return mixed + */ + public static function noConstraints(Closure $callback) + { + static::$constraints = false; + + // When resetting the relation where clause, we want to shift the first element + // off of the bindings, leaving only the constraints that the developers put + // as "extra" on the relationships, and not original relation constraints. + $results = call_user_func($callback); + + static::$constraints = true; + + return $results; + } + + /** + * Get all of the primary keys for an array of models. + * + * @param array $models + * @return array + */ + protected function getKeys(array $models) + { + return array_values(array_map(function($value) + { + return $value->getKey(); + + }, $models)); + } + + /** + * Get the underlying query for the relation. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function getQuery() + { + return $this->query; + } + + /** + * Get the base query builder driving the Eloquent builder. + * + * @return \Illuminate\Database\Query\Builder + */ + public function getBaseQuery() + { + return $this->query->getQuery(); + } + + /** + * Get the parent model of the relation. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function getParent() + { + return $this->parent; + } + + /** + * Get the related model of the relation. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function getRelated() + { + return $this->related; + } + + /** + * Get the name of the "created at" column. + * + * @return string + */ + public function createdAt() + { + return $this->parent->getCreatedAtColumn(); + } + + /** + * Get the name of the "updated at" column. + * + * @return string + */ + public function updatedAt() + { + return $this->parent->getUpdatedAtColumn(); + } + + /** + * Get the name of the related model's "updated at" column. + * + * @return string + */ + public function relatedUpdatedAt() + { + return $this->related->getUpdatedAtColumn(); + } + + /** + * Wrap the given value with the parent query's grammar. + * + * @param string $value + * @return string + */ + public function wrap($value) + { + return $this->parent->getQuery()->getGrammar()->wrap($value); + } + + /** + * Handle dynamic method calls to the relationship. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + $result = call_user_func_array(array($this->query, $method), $parameters); + + if ($result === $this->query) return $this; + + return $result; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Grammar.php b/vendor/laravel/framework/src/Illuminate/Database/Grammar.php new file mode 100755 index 0000000..fec01bb --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Grammar.php @@ -0,0 +1,177 @@ +isExpression($table)) return $this->getValue($table); + + return $this->wrap($this->tablePrefix.$table); + } + + /** + * Wrap a value in keyword identifiers. + * + * @param string $value + * @return string + */ + public function wrap($value) + { + if ($this->isExpression($value)) return $this->getValue($value); + + // If the value being wrapped has a column alias we will need to separate out + // the pieces so we can wrap each of the segments of the expression on it + // own, and then joins them both back together with the "as" connector. + if (strpos(strtolower($value), ' as ') !== false) + { + $segments = explode(' ', $value); + + return $this->wrap($segments[0]).' as '.$this->wrap($segments[2]); + } + + $wrapped = array(); + + $segments = explode('.', $value); + + // If the value is not an aliased table expression, we'll just wrap it like + // normal, so if there is more than one segment, we will wrap the first + // segments as if it was a table and the rest as just regular values. + foreach ($segments as $key => $segment) + { + if ($key == 0 and count($segments) > 1) + { + $wrapped[] = $this->wrapTable($segment); + } + else + { + $wrapped[] = $this->wrapValue($segment); + } + } + + return implode('.', $wrapped); + } + + /** + * Wrap a single string in keyword identifiers. + * + * @param string $value + * @return string + */ + protected function wrapValue($value) + { + return $value !== '*' ? sprintf($this->wrapper, $value) : $value; + } + + /** + * Convert an array of column names into a delimited string. + * + * @param array $columns + * @return string + */ + public function columnize(array $columns) + { + return implode(', ', array_map(array($this, 'wrap'), $columns)); + } + + /** + * Create query parameter place-holders for an array. + * + * @param array $values + * @return string + */ + public function parameterize(array $values) + { + return implode(', ', array_map(array($this, 'parameter'), $values)); + } + + /** + * Get the appropriate query parameter place-holder for a value. + * + * @param mixed $value + * @return string + */ + public function parameter($value) + { + return $this->isExpression($value) ? $this->getValue($value) : '?'; + } + + /** + * Get the value of a raw expression. + * + * @param \Illuminate\Database\Query\Expression $expression + * @return string + */ + public function getValue($expression) + { + return $expression->getValue(); + } + + /** + * Determine if the given value is a raw expression. + * + * @param mixed $value + * @return bool + */ + public function isExpression($value) + { + return $value instanceof Query\Expression; + } + + /** + * Get the format for database stored dates. + * + * @return string + */ + public function getDateFormat() + { + return 'Y-m-d H:i:s'; + } + + /** + * Get the grammar's table prefix. + * + * @return string + */ + public function getTablePrefix() + { + return $this->tablePrefix; + } + + /** + * Set the grammar's table prefix. + * + * @param string $prefix + * @return \Illuminate\Database\Grammar + */ + public function setTablePrefix($prefix) + { + $this->tablePrefix = $prefix; + + return $this; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/MigrationServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Database/MigrationServiceProvider.php new file mode 100755 index 0000000..48dbf97 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/MigrationServiceProvider.php @@ -0,0 +1,207 @@ +registerRepository(); + + // Once we have registered the migrator instance we will go ahead and register + // all of the migration related commands that are used by the "Artisan" CLI + // so that they may be easily accessed for registering with the consoles. + $this->registerMigrator(); + + $this->registerCommands(); + } + + /** + * Register the migration repository service. + * + * @return void + */ + protected function registerRepository() + { + $this->app['migration.repository'] = $this->app->share(function($app) + { + $table = $app['config']['database.migrations']; + + return new DatabaseMigrationRepository($app['db'], $table); + }); + } + + /** + * Register the migrator service. + * + * @return void + */ + protected function registerMigrator() + { + // The migrator is responsible for actually running and rollback the migration + // files in the application. We'll pass in our database connection resolver + // so the migrator can resolve any of these connections when it needs to. + $this->app['migrator'] = $this->app->share(function($app) + { + $repository = $app['migration.repository']; + + return new Migrator($repository, $app['db'], $app['files']); + }); + } + + /** + * Register all of the migration commands. + * + * @return void + */ + protected function registerCommands() + { + $commands = array('Migrate', 'Rollback', 'Reset', 'Refresh', 'Install', 'Make'); + + // We'll simply spin through the list of commands that are migration related + // and register each one of them with an application container. They will + // be resolved in the Artisan start file and registered on the console. + foreach ($commands as $command) + { + $this->{'register'.$command.'Command'}(); + } + + // Once the commands are registered in the application IoC container we will + // register them with the Artisan start event so that these are available + // when the Artisan application actually starts up and is getting used. + $this->commands( + 'command.migrate', 'command.migrate.make', + 'command.migrate.install', 'command.migrate.rollback', + 'command.migrate.reset', 'command.migrate.refresh' + ); + } + + /** + * Register the "migrate" migration command. + * + * @return void + */ + protected function registerMigrateCommand() + { + $this->app['command.migrate'] = $this->app->share(function($app) + { + $packagePath = $app['path.base'].'/vendor'; + + return new MigrateCommand($app['migrator'], $packagePath); + }); + } + + /** + * Register the "rollback" migration command. + * + * @return void + */ + protected function registerRollbackCommand() + { + $this->app['command.migrate.rollback'] = $this->app->share(function($app) + { + return new RollbackCommand($app['migrator']); + }); + } + + /** + * Register the "reset" migration command. + * + * @return void + */ + protected function registerResetCommand() + { + $this->app['command.migrate.reset'] = $this->app->share(function($app) + { + return new ResetCommand($app['migrator']); + }); + } + + /** + * Register the "refresh" migration command. + * + * @return void + */ + protected function registerRefreshCommand() + { + $this->app['command.migrate.refresh'] = $this->app->share(function($app) + { + return new RefreshCommand; + }); + } + + /** + * Register the "install" migration command. + * + * @return void + */ + protected function registerInstallCommand() + { + $this->app['command.migrate.install'] = $this->app->share(function($app) + { + return new InstallCommand($app['migration.repository']); + }); + } + + /** + * Register the "install" migration command. + * + * @return void + */ + protected function registerMakeCommand() + { + $this->app['migration.creator'] = $this->app->share(function($app) + { + return new MigrationCreator($app['files']); + }); + + $this->app['command.migrate.make'] = $this->app->share(function($app) + { + // Once we have the migration creator registered, we will create the command + // and inject the creator. The creator is responsible for the actual file + // creation of the migrations, and may be extended by these developers. + $creator = $app['migration.creator']; + + $packagePath = $app['path.base'].'/vendor'; + + return new MakeCommand($creator, $packagePath); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array( + 'migrator', 'migration.repository', 'command.migrate', + 'command.migrate.rollback', 'command.migrate.reset', + 'command.migrate.refresh', 'command.migrate.install', + 'migration.creator', 'command.migrate.make', + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Migrations/DatabaseMigrationRepository.php b/vendor/laravel/framework/src/Illuminate/Database/Migrations/DatabaseMigrationRepository.php new file mode 100755 index 0000000..88ac497 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Migrations/DatabaseMigrationRepository.php @@ -0,0 +1,182 @@ +table = $table; + $this->resolver = $resolver; + } + + /** + * Get the ran migrations. + * + * @return array + */ + public function getRan() + { + return $this->table()->lists('migration'); + } + + /** + * Get the last migration batch. + * + * @return array + */ + public function getLast() + { + $query = $this->table()->where('batch', $this->getLastBatchNumber()); + + return $query->orderBy('migration', 'desc')->get(); + } + + /** + * Log that a migration was run. + * + * @param string $file + * @param int $batch + * @return void + */ + public function log($file, $batch) + { + $record = array('migration' => $file, 'batch' => $batch); + + $this->table()->insert($record); + } + + /** + * Remove a migration from the log. + * + * @param \StdClass $migration + * @return void + */ + public function delete($migration) + { + $query = $this->table()->where('migration', $migration->migration)->delete(); + } + + /** + * Get the next migration batch number. + * + * @return int + */ + public function getNextBatchNumber() + { + return $this->getLastBatchNumber() + 1; + } + + /** + * Get the last migration batch number. + * + * @return int + */ + public function getLastBatchNumber() + { + return $this->table()->max('batch'); + } + + /** + * Create the migration repository data store. + * + * @return void + */ + public function createRepository() + { + $schema = $this->getConnection()->getSchemaBuilder(); + + $schema->create($this->table, function($table) + { + // The migrations table is responsible for keeping track of which of the + // migrations have actually run for the application. We'll create the + // table to hold the migration file's path as well as the batch ID. + $table->string('migration'); + + $table->integer('batch'); + }); + } + + /** + * Determine if the migration repository exists. + * + * @return bool + */ + public function repositoryExists() + { + $schema = $this->getConnection()->getSchemaBuilder(); + + return $schema->hasTable($this->table); + } + + /** + * Get a query builder for the migration table. + * + * @return \Illuminate\Database\Query\Builder + */ + protected function table() + { + return $this->getConnection()->table($this->table); + } + + /** + * Get the connection resolver instance. + * + * @return \Illuminate\Database\ConnectionResolverInterface + */ + public function getConnectionResolver() + { + return $this->resolver; + } + + /** + * Resolve the database connection instance. + * + * @return \Illuminate\Database\Connection + */ + public function getConnection() + { + return $this->resolver->connection($this->connection); + } + + /** + * Set the information source to gather data. + * + * @param string $name + * @return void + */ + public function setSource($name) + { + $this->connection = $name; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migration.php b/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migration.php new file mode 100755 index 0000000..05a6b94 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Migrations/Migration.php @@ -0,0 +1,22 @@ +connection; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Migrations/MigrationCreator.php b/vendor/laravel/framework/src/Illuminate/Database/Migrations/MigrationCreator.php new file mode 100755 index 0000000..58778c0 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Migrations/MigrationCreator.php @@ -0,0 +1,171 @@ +files = $files; + } + + /** + * Create a new migration at the given path. + * + * @param string $name + * @param string $path + * @param string $table + * @param bool $create + * @return string + */ + public function create($name, $path, $table = null, $create = false) + { + $path = $this->getPath($name, $path); + + // First we will get the stub file for the migration, which serves as a type + // of template for the migration. Once we have those we will populate the + // various place-holders, save the file, and run the post create event. + $stub = $this->getStub($table, $create); + + $this->files->put($path, $this->populateStub($name, $stub, $table)); + + $this->firePostCreateHooks(); + + return $path; + } + + /** + * Get the migration stub file. + * + * @param string $table + * @return void + */ + protected function getStub($table, $create) + { + if (is_null($table)) + { + return $this->files->get($this->getStubPath().'/blank.stub'); + } + + // We also have stubs for creating new tables and modifying existing tables + // to save the developer some typing when they are creating a new tables + // or modifying existing tables. We'll grab the appropriate stub here. + else + { + $stub = $create ? 'create.stub' : 'update.stub'; + + return $this->files->get($this->getStubPath()."/{$stub}"); + } + } + + /** + * Populate the place-holders in the migration stub. + * + * @param string $name + * @param string $stub + * @param string $table + * @return string + */ + protected function populateStub($name, $stub, $table) + { + $stub = str_replace('{{class}}', studly_case($name), $stub); + + // Here we will replace the table place-holders with the table specified by + // the developer, which is useful for quickly creating a tables creation + // or update migration from the console instead of typing it manually. + if ( ! is_null($table)) + { + $stub = str_replace('{{table}}', $table, $stub); + } + + return $stub; + } + + /** + * Fire the registered post create hooks. + * + * @return void + */ + protected function firePostCreateHooks() + { + foreach ($this->postCreate as $callback) + { + call_user_func($callback); + } + } + + /** + * Register a post migration create hook. + * + * @param Closure $callback + * @return void + */ + public function afterCreate(Closure $callback) + { + $this->postCreate[] = $callback; + } + + /** + * Get the full path name to the migration. + * + * @param string $name + * @param string $path + * @return string + */ + protected function getPath($name, $path) + { + return $path.'/'.$this->getDatePrefix().'_'.$name.'.php'; + } + + /** + * Get the date prefix for the migration. + * + * @return int + */ + protected function getDatePrefix() + { + return date('Y_m_d_His'); + } + + /** + * Get the path to the stubs. + * + * @return string + */ + public function getStubPath() + { + return __DIR__.'/stubs'; + } + + /** + * Get the filesystem instance. + * + * @return \Illuminate\Filesystem\Filesystem + */ + public function getFilesystem() + { + return $this->files; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Migrations/MigrationRepositoryInterface.php b/vendor/laravel/framework/src/Illuminate/Database/Migrations/MigrationRepositoryInterface.php new file mode 100755 index 0000000..dfd6e7f --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Migrations/MigrationRepositoryInterface.php @@ -0,0 +1,65 @@ +files = $files; + $this->resolver = $resolver; + $this->repository = $repository; + } + + /** + * Run the outstanding migrations at a given path. + * + * @param string $path + * @param bool $pretend + * @return void + */ + public function run($path, $pretend = false) + { + $this->notes = array(); + + $this->requireFiles($path, $files = $this->getMigrationFiles($path)); + + // Once we grab all of the migration files for the path, we will compare them + // against the migrations that have already been run for this package then + // run all of the oustanding migrations against the database connection. + $ran = $this->repository->getRan(); + + $migrations = array_diff($files, $ran); + + $this->runMigrationList($migrations, $pretend); + } + + /** + * Run an array of migrations. + * + * @param array $migrations + * @param bool $pretend + * @return void + */ + public function runMigrationList($migrations, $pretend = false) + { + // First we will just make sure that there are any migrations to run. If there + // aren't, we will just make a note of it to the developer so they're aware + // that all of the migrations have been run against this database system. + if (count($migrations) == 0) + { + $this->note('Nothing to migrate.'); + + return; + } + + $batch = $this->repository->getNextBatchNumber(); + + // Once we have the array of migrations, we will spin through them and run the + // migrations "up" so the changes are made to the databases. We'll then log + // that the migration was run so we don't repeat it next time we execute. + foreach ($migrations as $file) + { + $this->runUp($file, $batch, $pretend); + } + } + + /** + * Run "up" a migration instance. + * + * @param string $file + * @param int $batch + * @param bool $pretend + * @return void + */ + protected function runUp($file, $batch, $pretend) + { + // First we will resolve a "real" instance of the migration class from this + // migration file name. Once we have the instances we can run the actual + // command such as "up" or "down", or we can just simulate the action. + $migration = $this->resolve($file); + + if ($pretend) + { + return $this->pretendToRun($migration, 'up'); + } + + $migration->up(); + + // Once we have run a migrations class, we will log that it was run in this + // repository so that we don't try to run it next time we do a migration + // in the application. A migration repository keeps the migrate order. + $this->repository->log($file, $batch); + + $this->note("Migrated: $file"); + } + + /** + * Rollback the last migration operation. + * + * @param bool $pretend + * @return int + */ + public function rollback($pretend = false) + { + $this->notes = array(); + + // We want to pull in the last batch of migrations that ran on the previous + // migration operation. We'll then reverse those migrations and run each + // of them "down" to reverse the last migration "operation" which ran. + $migrations = $this->repository->getLast(); + + if (count($migrations) == 0) + { + $this->note('Nothing to rollback.'); + + return count($migrations); + } + + // We need to reverse these migrations so that they are "downed" in reverse + // to what they run on "up". It lets us backtrack through the migrations + // and properly reverse the entire database schema operation that ran. + foreach ($migrations as $migration) + { + $this->runDown((object) $migration, $pretend); + } + + return count($migrations); + } + + /** + * Run "down" a migration instance. + * + * @param \StdClass $migration + * @param bool $pretend + * @return void + */ + protected function runDown($migration, $pretend) + { + $file = $migration->migration; + + // First we will get the file name of the migration so we can resolve out an + // instance of the migration. Once we get an instance we can either run a + // pretend execution of the migration or we can run the real migration. + $instance = $this->resolve($file); + + if ($pretend) + { + return $this->pretendToRun($instance, 'down'); + } + + $instance->down(); + + // Once we have successfully run the migration "down" we will remove it from + // the migration repository so it will be considered to have not been run + // by the application then will be able to fire by any later operation. + $this->repository->delete($migration); + + $this->note("Rolled back: $file"); + } + + /** + * Get all of the migration files in a given path. + * + * @param string $path + * @return array + */ + public function getMigrationFiles($path) + { + $files = $this->files->glob($path.'/*_*.php'); + + // Once we have the array of files in the directory we will just remove the + // extension and take the basename of the file which is all we need when + // finding the migrations that haven't been run against the databases. + if ($files === false) return array(); + + $files = array_map(function($file) + { + return str_replace('.php', '', basename($file)); + + }, $files); + + // Once we have all of the formatted file names we will sort them and since + // they all start with a timestamp this should give us the migrations in + // the order they were actually created by the application developers. + sort($files); + + return $files; + } + + /** + * Require in all the migration files in a given path. + * + * @param array $files + * @return void + */ + public function requireFiles($path, array $files) + { + foreach ($files as $file) $this->files->requireOnce($path.'/'.$file.'.php'); + } + + /** + * Pretend to run the migrations. + * + * @param object $migration + * @return void + */ + protected function pretendToRun($migration, $method) + { + foreach ($this->getQueries($migration, $method) as $query) + { + $name = get_class($migration); + + $this->note("{$name}: {$query['query']}"); + } + } + + /** + * Get all of the queries that would be run for a migration. + * + * @param object $migration + * @param string $method + * @return array + */ + protected function getQueries($migration, $method) + { + $connection = $migration->getConnection(); + + // Now that we have the connections we can resolve it and pretend to run the + // queries against the database returning the array of raw SQL statements + // that would get fired against the database system for this migration. + $db = $this->resolveConnection($connection); + + return $db->pretend(function() use ($migration, $method) + { + $migration->$method(); + }); + } + + /** + * Resolve a migration instance from a file. + * + * @param string $file + * @return object + */ + public function resolve($file) + { + $file = implode('_', array_slice(explode('_', $file), 4)); + + $class = studly_case($file); + + return new $class; + } + + /** + * Raise a note event for the migrator. + * + * @param string $message + * @return void + */ + protected function note($message) + { + $this->notes[] = $message; + } + + /** + * Get the notes for the last operation. + * + * @return array + */ + public function getNotes() + { + return $this->notes; + } + + /** + * Resolve the database connection instance. + * + * @return \Illuminate\Database\Connection + */ + public function resolveConnection() + { + return $this->resolver->connection($this->connection); + } + + /** + * Set the default connection name. + * + * @param string $name + * @return void + */ + public function setConnection($name) + { + if ( ! is_null($name)) + { + $this->resolver->setDefaultConnection($name); + } + + $this->repository->setSource($name); + + $this->connection = $name; + } + + /** + * Get the migration repository instance. + * + * @return \Illuminate\Database\Migrations\MigrationRepositoryInterface + */ + public function getRepository() + { + return $this->repository; + } + + /** + * Determine if the migration repository exists. + * + * @return bool + */ + public function repositoryExists() + { + return $this->repository->repositoryExists(); + } + + /** + * Get the file system instance. + * + * @return \Illuminate\Filesystem\Filesystem + */ + public function getFilesystem() + { + return $this->files; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs/blank.stub b/vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs/blank.stub new file mode 100644 index 0000000..5ce4016 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs/blank.stub @@ -0,0 +1,27 @@ +increments('id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('{{table}}'); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs/update.stub b/vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs/update.stub new file mode 100644 index 0000000..07d3ea5 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Migrations/stubs/update.stub @@ -0,0 +1,34 @@ +schemaGrammar)) { $this->useDefaultSchemaGrammar(); } + + return new Schema\MySqlBuilder($this); + } + + /** + * Get the default query grammar instance. + * + * @return \Illuminate\Database\Query\Grammars\Grammars\Grammar + */ + protected function getDefaultQueryGrammar() + { + return $this->withTablePrefix(new Query\Grammars\MySqlGrammar); + } + + /** + * Get the default schema grammar instance. + * + * @return \Illuminate\Database\Schema\Grammars\Grammar + */ + protected function getDefaultSchemaGrammar() + { + return $this->withTablePrefix(new Schema\Grammars\MySqlGrammar); + } + + /** + * Get the Doctrine DBAL Driver. + * + * @return \Doctrine\DBAL\Driver + */ + protected function getDoctrineDriver() + { + return new \Doctrine\DBAL\Driver\PDOMySql\Driver; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/PostgresConnection.php b/vendor/laravel/framework/src/Illuminate/Database/PostgresConnection.php new file mode 100755 index 0000000..8a5acd6 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/PostgresConnection.php @@ -0,0 +1,45 @@ +withTablePrefix(new Query\Grammars\PostgresGrammar); + } + + /** + * Get the default schema grammar instance. + * + * @return \Illuminate\Database\Schema\Grammars\Grammar + */ + protected function getDefaultSchemaGrammar() + { + return $this->withTablePrefix(new Schema\Grammars\PostgresGrammar); + } + + /** + * Get the default post processor instance. + * + * @return \Illuminate\Database\Query\Processors\Processor + */ + protected function getDefaultPostProcessor() + { + return new Query\Processors\PostgresProcessor; + } + + /** + * Get the Doctrine DBAL Driver. + * + * @return \Doctrine\DBAL\Driver + */ + protected function getDoctrineDriver() + { + return new \Doctrine\DBAL\Driver\PDOPgSql\Driver; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php b/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php new file mode 100755 index 0000000..8cfe246 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php @@ -0,0 +1,1595 @@ +', '<=', '>=', '<>', '!=', + 'like', 'not like', 'between', 'ilike', + ); + + /** + * Create a new query builder instance. + * + * @param \Illuminate\Database\ConnectionInterface $connection + * @param \Illuminate\Database\Query\Grammars\Grammar $grammar + * @param \Illuminate\Database\Query\Processors\Processor $processor + * @return void + */ + public function __construct(ConnectionInterface $connection, + Grammar $grammar, + Processor $processor) + { + $this->grammar = $grammar; + $this->processor = $processor; + $this->connection = $connection; + } + + /** + * Set the columns to be selected. + * + * @param array $columns + * @return \Illuminate\Database\Query\Builder|static + */ + public function select($columns = array('*')) + { + $this->columns = is_array($columns) ? $columns : func_get_args(); + + return $this; + } + + /** + * Add a new select column to the query. + * + * @param mixed $column + * @return \Illuminate\Database\Query\Builder|static + */ + public function addSelect($column) + { + $column = is_array($column) ? $column : func_get_args(); + + $this->columns = array_merge((array) $this->columns, $column); + + return $this; + } + + /** + * Force the query to only return distinct results. + * + * @return \Illuminate\Database\Query\Builder|static + */ + public function distinct() + { + $this->distinct = true; + + return $this; + } + + /** + * Set the table which the query is targeting. + * + * @param string $table + * @return \Illuminate\Database\Query\Builder|static + */ + public function from($table) + { + $this->from = $table; + + return $this; + } + + /** + * Add a join clause to the query. + * + * @param string $table + * @param string $first + * @param string $operator + * @param string $second + * @param string $type + * @return \Illuminate\Database\Query\Builder|static + */ + public function join($table, $first, $operator = null, $second = null, $type = 'inner') + { + // If the first "column" of the join is really a Closure instance the developer + // is trying to build a join with a complex "on" clause containing more than + // one condition, so we'll add the join and call a Closure with the query. + if ($first instanceof Closure) + { + $this->joins[] = new JoinClause($type, $table); + + call_user_func($first, end($this->joins)); + } + + // If the column is simply a string, we can assume the join simply has a basic + // "on" clause with a single condition. So we will just build the join with + // this simple join clauses attached to it. There is not a join callback. + else + { + $join = new JoinClause($type, $table); + + $join->on($first, $operator, $second); + + $this->joins[] = $join; + } + + return $this; + } + + /** + * Add a left join to the query. + * + * @param string $table + * @param string $first + * @param string $operator + * @param string $second + * @return \Illuminate\Database\Query\Builder|static + */ + public function leftJoin($table, $first, $operator = null, $second = null) + { + return $this->join($table, $first, $operator, $second, 'left'); + } + + /** + * Add a basic where clause to the query. + * + * @param string $column + * @param string $operator + * @param mixed $value + * @param string $boolean + * @return \Illuminate\Database\Query\Builder|static + */ + public function where($column, $operator = null, $value = null, $boolean = 'and') + { + // If the columns is actually a Closure instance, we will assume the developer + // wants to begin a nested where statement which is wrapped in parenthesis. + // We'll add that Closure to the query then return back out immediately. + if ($column instanceof Closure) + { + return $this->whereNested($column, $boolean); + } + + // If the given operator is not found in the list of valid operators we will + // assume that the developer is just short-cutting the '=' operators and + // we will set the operators to '=' and set the values appropriately. + if ( ! in_array(strtolower($operator), $this->operators, true)) + { + list($value, $operator) = array($operator, '='); + } + + // If the value is a Closure, it means the developer is performing an entire + // sub-select within the query and we will need to compile the sub-select + // within the where clause to get the appropriate query record results. + if ($value instanceof Closure) + { + return $this->whereSub($column, $operator, $value, $boolean); + } + + // If the value is "null", we will just assume the developer wants to add a + // where null clause to the query. So, we will allow a short-cut here to + // that method for convenience so the developer doesn't have to check. + if (is_null($value)) + { + return $this->whereNull($column, $boolean, $operator != '='); + } + + // Now that we are working with just a simple query we can put the elements + // in our array and add the query binding to our array of bindings that + // will be bound to each SQL statements when it is finally executed. + $type = 'Basic'; + + $this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean'); + + if ( ! $value instanceof Expression) + { + $this->bindings[] = $value; + } + + return $this; + } + + /** + * Add an "or where" clause to the query. + * + * @param string $column + * @param string $operator + * @param mixed $value + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhere($column, $operator = null, $value = null) + { + return $this->where($column, $operator, $value, 'or'); + } + + /** + * Add a raw where clause to the query. + * + * @param string $sql + * @param array $bindings + * @param string $boolean + * @return \Illuminate\Database\Query\Builder|static + */ + public function whereRaw($sql, array $bindings = array(), $boolean = 'and') + { + $type = 'raw'; + + $this->wheres[] = compact('type', 'sql', 'boolean'); + + $this->bindings = array_merge($this->bindings, $bindings); + + return $this; + } + + /** + * Add a raw or where clause to the query. + * + * @param string $sql + * @param array $bindings + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhereRaw($sql, array $bindings = array()) + { + return $this->whereRaw($sql, $bindings, 'or'); + } + + /** + * Add a where between statement to the query. + * + * @param string $column + * @param array $values + * @param string $boolean + * @return \Illuminate\Database\Query\Builder|static + */ + public function whereBetween($column, array $values, $boolean = 'and') + { + $type = 'between'; + + $this->wheres[] = compact('column', 'type', 'boolean'); + + $this->bindings = array_merge($this->bindings, $values); + + return $this; + } + + /** + * Add an or where between statement to the query. + * + * @param string $column + * @param array $values + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhereBetween($column, array $values) + { + return $this->whereBetween($column, $values, 'or'); + } + + /** + * Add a nested where statement to the query. + * + * @param \Closure $callback + * @param string $boolean + * @return \Illuminate\Database\Query\Builder|static + */ + public function whereNested(Closure $callback, $boolean = 'and') + { + // To handle nested queries we'll actually create a brand new query instance + // and pass it off to the Closure that we have. The Closure can simply do + // do whatever it wants to a query then we will store it for compiling. + $type = 'Nested'; + + $query = $this->newQuery(); + + $query->from($this->from); + + call_user_func($callback, $query); + + // Once we have let the Closure do its things, we can gather the bindings on + // the nested query builder and merge them into these bindings since they + // need to get extracted out of the children and assigned to the array. + if (count($query->wheres)) + { + $this->wheres[] = compact('type', 'query', 'boolean'); + + $this->mergeBindings($query); + } + + return $this; + } + + /** + * Add a full sub-select to the query. + * + * @param string $column + * @param string $operator + * @param \Closure $callback + * @param string $boolean + * @return \Illuminate\Database\Query\Builder|static + */ + protected function whereSub($column, $operator, Closure $callback, $boolean) + { + $type = 'Sub'; + + $query = $this->newQuery(); + + // Once we have the query instance we can simply execute it so it can add all + // of the sub-select's conditions to itself, and then we can cache it off + // in the array of where clauses for the "main" parent query instance. + call_user_func($callback, $query); + + $this->wheres[] = compact('type', 'column', 'operator', 'query', 'boolean'); + + $this->mergeBindings($query); + + return $this; + } + + /** + * Add an exists clause to the query. + * + * @param \Closure $callback + * @param string $boolean + * @param bool $not + * @return \Illuminate\Database\Query\Builder|static + */ + public function whereExists(Closure $callback, $boolean = 'and', $not = false) + { + $type = $not ? 'NotExists' : 'Exists'; + + $query = $this->newQuery(); + + // Similar to the sub-select clause, we will create a new query instance so + // the developer may cleanly specify the entire exists query and we will + // compile the whole thing in the grammar and insert it into the SQL. + call_user_func($callback, $query); + + $this->wheres[] = compact('type', 'operator', 'query', 'boolean'); + + $this->mergeBindings($query); + + return $this; + } + + /** + * Add an or exists clause to the query. + * + * @param \Closure $callback + * @param bool $not + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhereExists(Closure $callback, $not = false) + { + return $this->whereExists($callback, 'or', $not); + } + + /** + * Add a where not exists clause to the query. + * + * @param \Closure $callback + * @param string $boolean + * @return \Illuminate\Database\Query\Builder|static + */ + public function whereNotExists(Closure $callback, $boolean = 'and') + { + return $this->whereExists($callback, $boolean, true); + } + + /** + * Add a where not exists clause to the query. + * + * @param \Closure $callback + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhereNotExists(Closure $callback) + { + return $this->orWhereExists($callback, true); + } + + /** + * Add a "where in" clause to the query. + * + * @param string $column + * @param mixed $values + * @param string $boolean + * @param bool $not + * @return \Illuminate\Database\Query\Builder|static + */ + public function whereIn($column, $values, $boolean = 'and', $not = false) + { + $type = $not ? 'NotIn' : 'In'; + + // If the value of the where in clause is actually a Closure, we will assume that + // the developer is using a full sub-select for this "in" statement, and will + // execute those Closures, then we can re-construct the entire sub-selects. + if ($values instanceof Closure) + { + return $this->whereInSub($column, $values, $boolean, $not); + } + + $this->wheres[] = compact('type', 'column', 'values', 'boolean'); + + $this->bindings = array_merge($this->bindings, $values); + + return $this; + } + + /** + * Add an "or where in" clause to the query. + * + * @param string $column + * @param mixed $values + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhereIn($column, $values) + { + return $this->whereIn($column, $values, 'or'); + } + + /** + * Add a "where not in" clause to the query. + * + * @param string $column + * @param mixed $values + * @param string $boolean + * @return \Illuminate\Database\Query\Builder|static + */ + public function whereNotIn($column, $values, $boolean = 'and') + { + return $this->whereIn($column, $values, $boolean, true); + } + + /** + * Add an "or where not in" clause to the query. + * + * @param string $column + * @param mixed $values + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhereNotIn($column, $values) + { + return $this->whereNotIn($column, $values, 'or'); + } + + /** + * Add a where in with a sub-select to the query. + * + * @param string $column + * @param \Closure $callback + * @param string $boolean + * @param bool $not + * @return \Illuminate\Database\Query\Builder|static + */ + protected function whereInSub($column, Closure $callback, $boolean, $not) + { + $type = $not ? 'NotInSub' : 'InSub'; + + // To create the exists sub-select, we will actually create a query and call the + // provided callback with the query so the developer may set any of the query + // conditions they want for the in clause, then we'll put it in this array. + call_user_func($callback, $query = $this->newQuery()); + + $this->wheres[] = compact('type', 'column', 'query', 'boolean'); + + $this->mergeBindings($query); + + return $this; + } + + /** + * Add a "where null" clause to the query. + * + * @param string $column + * @param string $boolean + * @param bool $not + * @return \Illuminate\Database\Query\Builder|static + */ + public function whereNull($column, $boolean = 'and', $not = false) + { + $type = $not ? 'NotNull' : 'Null'; + + $this->wheres[] = compact('type', 'column', 'boolean'); + + return $this; + } + + /** + * Add an "or where null" clause to the query. + * + * @param string $column + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhereNull($column) + { + return $this->whereNull($column, 'or'); + } + + /** + * Add a "where not null" clause to the query. + * + * @param string $column + * @param string $boolean + * @return \Illuminate\Database\Query\Builder|static + */ + public function whereNotNull($column, $boolean = 'and') + { + return $this->whereNull($column, $boolean, true); + } + + /** + * Add an "or where not null" clause to the query. + * + * @param string $column + * @return \Illuminate\Database\Query\Builder|static + */ + public function orWhereNotNull($column) + { + return $this->whereNotNull($column, 'or'); + } + + /** + * Handles dynamic "where" clauses to the query. + * + * @param string $method + * @param string $parameters + * @return \Illuminate\Database\Query\Builder|static + */ + public function dynamicWhere($method, $parameters) + { + $finder = substr($method, 5); + + $segments = preg_split('/(And|Or)(?=[A-Z])/', $finder, -1, PREG_SPLIT_DELIM_CAPTURE); + + // The connector variable will determine which connector will be used for the + // query condition. We will change it as we come across new boolean values + // in the dynamic method strings, which could contain a number of these. + $connector = 'and'; + + $index = 0; + + foreach ($segments as $segment) + { + // If the segment is not a boolean connector, we can assume it is a column's name + // and we will add it to the query as a new constraint as a where clause, then + // we can keep iterating through the dynamic method string's segments again. + if ($segment != 'And' and $segment != 'Or') + { + $this->addDynamic($segment, $connector, $parameters, $index); + + $index++; + } + + // Otherwise, we will store the connector so we know how the next where clause we + // find in the query should be connected to the previous ones, meaning we will + // have the proper boolean connector to connect the next where clause found. + else + { + $connector = $segment; + } + } + + return $this; + } + + /** + * Add a single dynamic where clause statement to the query. + * + * @param string $segment + * @param string $connector + * @param array $parameters + * @param int $index + * @return void + */ + protected function addDynamic($segment, $connector, $parameters, $index) + { + // Once we have parsed out the columns and formatted the boolean operators we + // are ready to add it to this query as a where clause just like any other + // clause on the query. Then we'll increment the parameter index values. + $bool = strtolower($connector); + + $this->where(snake_case($segment), '=', $parameters[$index], $bool); + } + + /** + * Add a "group by" clause to the query. + * + * @param dynamic $columns + * @return \Illuminate\Database\Query\Builder|static + */ + public function groupBy() + { + $this->groups = array_merge((array) $this->groups, func_get_args()); + + return $this; + } + + /** + * Add a "having" clause to the query. + * + * @param string $column + * @param string $operator + * @param string $value + * @return \Illuminate\Database\Query\Builder|static + */ + public function having($column, $operator = null, $value = null) + { + $type = 'basic'; + + $this->havings[] = compact('type', 'column', 'operator', 'value'); + + $this->bindings[] = $value; + + return $this; + } + + /** + * Add a raw having clause to the query. + * + * @param string $sql + * @param array $bindings + * @param string $boolean + * @return \Illuminate\Database\Query\Builder|static + */ + public function havingRaw($sql, array $bindings = array(), $boolean = 'and') + { + $type = 'raw'; + + $this->havings[] = compact('type', 'sql', 'boolean'); + + $this->bindings = array_merge($this->bindings, $bindings); + + return $this; + } + + /** + * Add a raw or having clause to the query. + * + * @param string $sql + * @param array $bindings + * @return \Illuminate\Database\Query\Builder|static + */ + public function orHavingRaw($sql, array $bindings = array()) + { + return $this->havingRaw($sql, $bindings, 'or'); + } + + /** + * Add an "order by" clause to the query. + * + * @param string $column + * @param string $direction + * @return \Illuminate\Database\Query\Builder|static + */ + public function orderBy($column, $direction = 'asc') + { + $this->orders[] = compact('column', 'direction'); + + return $this; + } + + /** + * Set the "offset" value of the query. + * + * @param int $value + * @return \Illuminate\Database\Query\Builder|static + */ + public function offset($value) + { + $this->offset = $value; + + return $this; + } + + /** + * Alias to set the "offset" value of the query. + * + * @param int $value + * @return \Illuminate\Database\Query\Builder|static + */ + public function skip($value) + { + return $this->offset($value); + } + + /** + * Set the "limit" value of the query. + * + * @param int $value + * @return \Illuminate\Database\Query\Builder|static + */ + public function limit($value) + { + if ($value > 0) $this->limit = $value; + + return $this; + } + + /** + * Alias to set the "limit" value of the query. + * + * @param int $value + * @return \Illuminate\Database\Query\Builder|static + */ + public function take($value) + { + return $this->limit($value); + } + + /** + * Set the limit and offset for a given page. + * + * @param int $page + * @param int $perPage + * @return \Illuminate\Database\Query\Builder|static + */ + public function forPage($page, $perPage = 15) + { + return $this->skip(($page - 1) * $perPage)->take($perPage); + } + + /** + * Add a union statement to the query. + * + * @param \Illuminate\Database\Query\Builder|\Closure $query + * @param bool $all + * @return \Illuminate\Database\Query\Builder|static + */ + public function union($query, $all = false) + { + if ($query instanceof Closure) + { + call_user_func($query, $query = $this->newQuery()); + } + + $this->unions[] = compact('query', 'all'); + + return $this->mergeBindings($query); + } + + /** + * Add a union all statement to the query. + * + * @param \Illuminate\Database\Query\Builder|\Closure $query + * @return \Illuminate\Database\Query\Builder|static + */ + public function unionAll($query) + { + return $this->union($query, true); + } + + /** + * Get the SQL representation of the query. + * + * @return string + */ + public function toSql() + { + return $this->grammar->compileSelect($this); + } + + /** + * Indicate that the query results should be cached. + * + * @param int $minutes + * @param string $key + * @return \Illuminate\Database\Query\Builder|static + */ + public function remember($minutes, $key = null) + { + list($this->cacheMinutes, $this->cacheKey) = array($minutes, $key); + + return $this; + } + + /** + * Execute a query for a single record by ID. + * + * @param int $id + * @param array $columns + * @return mixed|static + */ + public function find($id, $columns = array('*')) + { + return $this->where('id', '=', $id)->first($columns); + } + + /** + * Pluck a single column from the database. + * + * @param string $column + * @return mixed + */ + public function pluck($column) + { + $result = (array) $this->first(array($column)); + + return count($result) > 0 ? reset($result) : null; + } + + /** + * Execute the query and get the first result. + * + * @param array $columns + * @return mixed|static + */ + public function first($columns = array('*')) + { + $results = $this->take(1)->get($columns); + + return count($results) > 0 ? reset($results) : null; + } + + /** + * Execute the query as a "select" statement. + * + * @param array $columns + * @return array|static[] + */ + public function get($columns = array('*')) + { + if ( ! is_null($this->cacheMinutes)) return $this->getCached($columns); + + return $this->getFresh($columns); + } + + /** + * Execute the query as a fresh "select" statement. + * + * @param array $columns + * @return array|static[] + */ + public function getFresh($columns = array('*')) + { + if (is_null($this->columns)) $this->columns = $columns; + + return $this->processor->processSelect($this, $this->runSelect()); + } + + /** + * Run the query as a "select" statement against the connection. + * + * @return array + */ + protected function runSelect() + { + return $this->connection->select($this->toSql(), $this->bindings); + } + + /** + * Execute the query as a cached "select" statement. + * + * @param array $columns + * @return array + */ + public function getCached($columns = array('*')) + { + if (is_null($this->columns)) $this->columns = $columns; + + list($key, $minutes) = $this->getCacheInfo(); + + // If the query is requested ot be cached, we will cache it using a unique key + // for this database connection and query statement, including the bindings + // that are used on this query, providing great convenience when caching. + $cache = $this->connection->getCacheManager(); + + $callback = $this->getCacheCallback($columns); + + return $cache->remember($key, $minutes, $callback); + } + + /** + * Get the cache key and cache minutes as an array. + * + * @return array + */ + protected function getCacheInfo() + { + return array($this->getCacheKey(), $this->cacheMinutes); + } + + /** + * Get a unique cache key for the complete query. + * + * @return string + */ + public function getCacheKey() + { + return $this->cacheKey ?: $this->generateCacheKey(); + } + + /** + * Generate the unique cache key for the query. + * + * @return string + */ + public function generateCacheKey() + { + $name = $this->connection->getName(); + + return md5($name.$this->toSql().serialize($this->bindings)); + } + + /** + * Get the Closure callback used when caching queries. + * + * @param array $columns + * @return \Closure + */ + protected function getCacheCallback($columns) + { + $me = $this; + + return function() use ($me, $columns) { return $me->getFresh($columns); }; + } + + /** + * Get an array with the values of a given column. + * + * @param string $column + * @param string $key + * @return array + */ + public function lists($column, $key = null) + { + $columns = $this->getListSelect($column, $key); + + // First we will just get all of the column values for the record result set + // then we can associate those values with the column if it was specified + // otherwise we can just give these values back without a specific key. + $results = new Collection($this->get($columns)); + + $values = $results->fetch($columns[0])->all(); + + // If a key was specified and we have results, we will go ahead and combine + // the values with the keys of all of the records so that the values can + // be accessed by the key of the rows instead of simply being numeric. + if ( ! is_null($key) and count($results) > 0) + { + $keys = $results->fetch($key)->all(); + + return array_combine($keys, $values); + } + + return $values; + } + + /** + * Get the columns that should be used in a list array. + * + * @param string $column + * @param string $key + * @return array + */ + protected function getListSelect($column, $key) + { + $select = is_null($key) ? array($column) : array($column, $key); + + // If the selected column contains a "dot", we will remove it so that the list + // operation can run normally. Specifying the table is not needed, since we + // really want the names of the columns as it is in this resulting array. + if (($dot = strpos($select[0], '.')) !== false) + { + $select[0] = substr($select[0], $dot + 1); + } + + return $select; + } + + /** + * Concatenate values of a given column as a string. + * + * @param string $column + * @param string $glue + * @return string + */ + public function implode($column, $glue = null) + { + if (is_null($glue)) return implode($this->lists($column)); + + return implode($glue, $this->lists($column)); + } + + /** + * Get a paginator for the "select" statement. + * + * @param int $perPage + * @param array $columns + * @return \Illuminate\Pagination\Paginator + */ + public function paginate($perPage = 15, $columns = array('*')) + { + $paginator = $this->connection->getPaginator(); + + if (isset($this->groups)) + { + return $this->groupedPaginate($paginator, $perPage, $columns); + } + else + { + return $this->ungroupedPaginate($paginator, $perPage, $columns); + } + } + + /** + * Create a paginator for a grouped pagination statement. + * + * @param \Illuminate\Pagination\Environment $paginator + * @param int $perPage + * @param array $columns + * @return \Illuminate\Pagination\Paginator + */ + protected function groupedPaginate($paginator, $perPage, $columns) + { + $results = $this->get($columns); + + return $this->buildRawPaginator($paginator, $results, $perPage); + } + + /** + * Build a paginator instance from a raw result array. + * + * @param \Illuminate\Pagination\Environment $paginator + * @param array $results + * @param int $perPage + * @return \Illuminate\Pagination\Paginator + */ + public function buildRawPaginator($paginator, $results, $perPage) + { + // For queries which have a group by, we will actually retrieve the entire set + // of rows from the table and "slice" them via PHP. This is inefficient and + // the developer must be aware of this behavior; however, it's an option. + $start = ($paginator->getCurrentPage() - 1) * $perPage; + + $sliced = array_slice($results, $start, $perPage); + + return $paginator->make($sliced, count($results), $perPage); + } + + /** + * Create a paginator for an un-grouped pagination statement. + * + * @param \Illuminate\Pagination\Environment $paginator + * @param int $perPage + * @param array $columns + * @return \Illuminate\Pagination\Paginator + */ + protected function ungroupedPaginate($paginator, $perPage, $columns) + { + $total = $this->getPaginationCount(); + + // Once we have the total number of records to be paginated, we can grab the + // current page and the result array. Then we are ready to create a brand + // new Paginator instances for the results which will create the links. + $page = $paginator->getCurrentPage(); + + $results = $this->forPage($page, $perPage)->get($columns); + + return $paginator->make($results, $total, $perPage); + } + + /** + * Get the count of the total records for pagination. + * + * @return int + */ + public function getPaginationCount() + { + list($orders, $this->orders) = array($this->orders, null); + + $columns = $this->columns; + + // Because some database engines may throw errors if we leave the ordering + // statements on the query, we will "back them up" and remove them from + // the query. Once we have the count we will put them back onto this. + $total = $this->count(); + + $this->orders = $orders; + + // Once the query is run we need to put the old select columns back on the + // instance so that the select query will run properly. Otherwise, they + // will be cleared, then the query will fire with all of the columns. + $this->columns = $columns; + + return $total; + } + + /** + * Determine if any rows exist for the current query. + * + * @return bool + */ + public function exists() + { + return $this->count() > 0; + } + + /** + * Retrieve the "count" result of the query. + * + * @param string $column + * @return int + */ + public function count($column = '*') + { + return $this->aggregate(__FUNCTION__, array($column)); + } + + /** + * Retrieve the minimum value of a given column. + * + * @param string $column + * @return mixed + */ + public function min($column) + { + return $this->aggregate(__FUNCTION__, array($column)); + } + + /** + * Retrieve the maximum value of a given column. + * + * @param string $column + * @return mixed + */ + public function max($column) + { + return $this->aggregate(__FUNCTION__, array($column)); + } + + /** + * Retrieve the sum of the values of a given column. + * + * @param string $column + * @return mixed + */ + public function sum($column) + { + return $this->aggregate(__FUNCTION__, array($column)); + } + + /** + * Retrieve the average of the values of a given column. + * + * @param string $column + * @return mixed + */ + public function avg($column) + { + return $this->aggregate(__FUNCTION__, array($column)); + } + + /** + * Execute an aggregate function on the database. + * + * @param string $function + * @param array $columns + * @return mixed + */ + public function aggregate($function, $columns = array('*')) + { + $this->aggregate = compact('function', 'columns'); + + $results = $this->get($columns); + + // Once we have executed the query, we will reset the aggregate property so + // that more select queries can be executed against the database without + // the aggregate value getting in the way when the grammar builds it. + $this->columns = null; $this->aggregate = null; + + if (isset($results[0])) + { + $result = (array) $results[0]; + + return $result['aggregate']; + } + } + + /** + * Insert a new record into the database. + * + * @param array $values + * @return bool + */ + public function insert(array $values) + { + // Since every insert gets treated like a batch insert, we will make sure the + // bindings are structured in a way that is convenient for building these + // inserts statements by verifying the elements are actually an array. + if ( ! is_array(reset($values))) + { + $values = array($values); + } + + // Since every insert gets treated like a batch insert, we will make sure the + // bindings are structured in a way that is convenient for building these + // inserts statements by verifying the elements are actually an array. + else + { + foreach ($values as $key => $value) + { + ksort($value); $values[$key] = $value; + } + } + + // We'll treat every insert like a batch insert so we can easily insert each + // of the records into the database consistently. This will make it much + // easier on the grammars to just handle one type of record insertion. + $bindings = array(); + + foreach ($values as $record) + { + $bindings = array_merge($bindings, array_values($record)); + } + + $sql = $this->grammar->compileInsert($this, $values); + + // Once we have compiled the insert statement's SQL we can execute it on the + // connection and return a result as a boolean success indicator as that + // is the same type of result returned by the raw connection instance. + $bindings = $this->cleanBindings($bindings); + + return $this->connection->insert($sql, $bindings); + } + + /** + * Insert a new record and get the value of the primary key. + * + * @param array $values + * @param string $sequence + * @return int + */ + public function insertGetId(array $values, $sequence = null) + { + $sql = $this->grammar->compileInsertGetId($this, $values, $sequence); + + $values = $this->cleanBindings($values); + + return $this->processor->processInsertGetId($this, $sql, $values, $sequence); + } + + /** + * Update a record in the database. + * + * @param array $values + * @return int + */ + public function update(array $values) + { + $bindings = array_values(array_merge($values, $this->bindings)); + + $sql = $this->grammar->compileUpdate($this, $values); + + return $this->connection->update($sql, $this->cleanBindings($bindings)); + } + + /** + * Increment a column's value by a given amount. + * + * @param string $column + * @param int $amount + * @param array $extra + * @return int + */ + public function increment($column, $amount = 1, array $extra = array()) + { + $wrapped = $this->grammar->wrap($column); + + $columns = array_merge(array($column => $this->raw("$wrapped + $amount")), $extra); + + return $this->update($columns); + } + + /** + * Decrement a column's value by a given amount. + * + * @param string $column + * @param int $amount + * @param array $extra + * @return int + */ + public function decrement($column, $amount = 1, array $extra = array()) + { + $wrapped = $this->grammar->wrap($column); + + $columns = array_merge(array($column => $this->raw("$wrapped - $amount")), $extra); + + return $this->update($columns); + } + + /** + * Delete a record from the database. + * + * @param mixed $id + * @return int + */ + public function delete($id = null) + { + // If an ID is passed to the method, we will set the where clause to check + // the ID to allow developers to simply and quickly remove a single row + // from their database without manually specifying the where clauses. + if ( ! is_null($id)) $this->where('id', '=', $id); + + $sql = $this->grammar->compileDelete($this); + + return $this->connection->delete($sql, $this->bindings); + } + + /** + * Run a truncate statement on the table. + * + * @return void + */ + public function truncate() + { + foreach ($this->grammar->compileTruncate($this) as $sql => $bindings) + { + $this->connection->statement($sql, $bindings); + } + } + + /** + * Get a new instance of the query builder. + * + * @return \Illuminate\Database\Query\Builder + */ + public function newQuery() + { + return new Builder($this->connection, $this->grammar, $this->processor); + } + + /** + * Merge an array of where clauses and bindings. + * + * @param array $wheres + * @param array $bindings + * @return void + */ + public function mergeWheres($wheres, $bindings) + { + $this->wheres = array_merge($this->wheres, (array) $wheres); + + $this->bindings = array_values(array_merge($this->bindings, (array) $bindings)); + } + + /** + * Remove all of the expressions from a list of bindings. + * + * @param array $bindings + * @return array + */ + protected function cleanBindings(array $bindings) + { + return array_values(array_filter($bindings, function($binding) + { + return ! $binding instanceof Expression; + })); + } + + /** + * Create a raw database expression. + * + * @param mixed $value + * @return \Illuminate\Database\Query\Expression + */ + public function raw($value) + { + return $this->connection->raw($value); + } + + /** + * Get the current query value bindings. + * + * @return array + */ + public function getBindings() + { + return $this->bindings; + } + + /** + * Set the bindings on the query builder. + * + * @param array $bindings + * @return void + */ + public function setBindings(array $bindings) + { + $this->bindings = $bindings; + } + + /** + * Merge an array of bindings into our bindings. + * + * @param \Illuminate\Database\Query\Builder $query + * @return \Illuminate\Database\Query\Builder + */ + public function mergeBindings(Builder $query) + { + $this->bindings = array_values(array_merge($this->bindings, $query->bindings)); + + return $this; + } + + /** + * Get the database connection instance. + * + * @return \Illuminate\Database\ConnectionInterface + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Get the database query processor instance. + * + * @return \Illuminate\Database\Query\Processors\Processor + */ + public function getProcessor() + { + return $this->processor; + } + + /** + * Get the query grammar instance. + * + * @return \Illuminate\Database\Grammar + */ + public function getGrammar() + { + return $this->grammar; + } + + /** + * Handle dynamic method calls into the method. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (starts_with($method, 'where')) + { + return $this->dynamicWhere($method, $parameters); + } + + $className = get_class($this); + + throw new \BadMethodCallException("Call to undefined method {$className}::{$method}()"); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Database/Query/Expression.php b/vendor/laravel/framework/src/Illuminate/Database/Query/Expression.php new file mode 100755 index 0000000..82cdba6 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Query/Expression.php @@ -0,0 +1,43 @@ +value = $value; + } + + /** + * Get the value of the expression. + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * Get the value of the expression. + * + * @return string + */ + public function __toString() + { + return (string) $this->getValue(); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php b/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php new file mode 100755 index 0000000..4e09730 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/Grammar.php @@ -0,0 +1,654 @@ +columns)) $query->columns = array('*'); + + return trim($this->concatenate($this->compileComponents($query))); + } + + /** + * Compile the components necessary for a select clause. + * + * @param \Illuminate\Database\Query\Builder + * @return array + */ + protected function compileComponents(Builder $query) + { + $sql = array(); + + foreach ($this->selectComponents as $component) + { + // To compile the query, we'll spin through each component of the query and + // see if that component exists. If it does we'll just call the compiler + // function for the component which is responsible for making the SQL. + if ( ! is_null($query->$component)) + { + $method = 'compile'.ucfirst($component); + + $sql[$component] = $this->$method($query, $query->$component); + } + } + + return $sql; + } + + /** + * Compile an aggregated select clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $aggregate + * @return string + */ + protected function compileAggregate(Builder $query, $aggregate) + { + $column = $this->columnize($aggregate['columns']); + + // If the query has a "distinct" constraint and we're not asking for all columns + // we need to prepend "distinct" onto the column name so that the query takes + // it into account when it performs the aggregating operations on the data. + if ($query->distinct and $column !== '*') + { + $column = 'distinct '.$column; + } + + return 'select '.$aggregate['function'].'('.$column.') as aggregate'; + } + + /** + * Compile the "select *" portion of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $columns + * @return string + */ + protected function compileColumns(Builder $query, $columns) + { + // If the query is actually performing an aggregating select, we will let that + // compiler handle the building of the select clauses, as it will need some + // more syntax that is best handled by that function to keep things neat. + if ( ! is_null($query->aggregate)) return; + + $select = $query->distinct ? 'select distinct ' : 'select '; + + return $select.$this->columnize($columns); + } + + /** + * Compile the "from" portion of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $table + * @return string + */ + protected function compileFrom(Builder $query, $table) + { + return 'from '.$this->wrapTable($table); + } + + /** + * Compile the "join" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $joins + * @return string + */ + protected function compileJoins(Builder $query, $joins) + { + $sql = array(); + + foreach ($joins as $join) + { + $table = $this->wrapTable($join->table); + + // First we need to build all of the "on" clauses for the join. There may be many + // of these clauses so we will need to iterate through each one and build them + // separately, then we'll join them up into a single string when we're done. + $clauses = array(); + + foreach ($join->clauses as $clause) + { + $clauses[] = $this->compileJoinConstraint($clause); + } + + // Once we have constructed the clauses, we'll need to take the boolean connector + // off of the first clause as it obviously will not be required on that clause + // because it leads the rest of the clauses, thus not requiring any boolean. + $clauses[0] = $this->removeLeadingBoolean($clauses[0]); + + $clauses = implode(' ', $clauses); + + $type = $join->type; + + // Once we have everything ready to go, we will just concatenate all the parts to + // build the final join statement SQL for the query and we can then return the + // final clause back to the callers as a single, stringified join statement. + $sql[] = "$type join $table on $clauses"; + } + + return implode(' ', $sql); + } + + /** + * Create a join clause constraint segment. + * + * @param array $clause + * @return string + */ + protected function compileJoinConstraint(array $clause) + { + $first = $this->wrap($clause['first']); + + $second = $this->wrap($clause['second']); + + return "{$clause['boolean']} $first {$clause['operator']} $second"; + } + + /** + * Compile the "where" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileWheres(Builder $query) + { + $sql = array(); + + if (is_null($query->wheres)) return ''; + + // Each type of where clauses has its own compiler function which is responsible + // for actually creating the where clauses SQL. This helps keep the code nice + // and maintainable since each clause has a very small method that it uses. + foreach ($query->wheres as $where) + { + $method = "where{$where['type']}"; + + $sql[] = $where['boolean'].' '.$this->$method($query, $where); + } + + // If we actually have some where clauses, we will strip off the first boolean + // operator, which is added by the query builders for convenience so we can + // avoid checking for the first clauses in each of the compilers methods. + if (count($sql) > 0) + { + $sql = implode(' ', $sql); + + return 'where '.preg_replace('/and |or /', '', $sql, 1); + } + + return ''; + } + + /** + * Compile a nested where clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNested(Builder $query, $where) + { + $nested = $where['query']; + + return '('.substr($this->compileWheres($nested), 6).')'; + } + + /** + * Compile a where condition with a sub-select. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereSub(Builder $query, $where) + { + $select = $this->compileSelect($where['query']); + + return $this->wrap($where['column']).' '.$where['operator']." ($select)"; + } + + /** + * Compile a basic where clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereBasic(Builder $query, $where) + { + $value = $this->parameter($where['value']); + + return $this->wrap($where['column']).' '.$where['operator'].' '.$value; + } + + /** + * Compile a "between" where clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereBetween(Builder $query, $where) + { + return $this->wrap($where['column']).' between ? and ?'; + } + + /** + * Compile a where exists clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereExists(Builder $query, $where) + { + return 'exists ('.$this->compileSelect($where['query']).')'; + } + + /** + * Compile a where exists clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNotExists(Builder $query, $where) + { + return 'not exists ('.$this->compileSelect($where['query']).')'; + } + + /** + * Compile a "where in" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereIn(Builder $query, $where) + { + $values = $this->parameterize($where['values']); + + return $this->wrap($where['column']).' in ('.$values.')'; + } + + /** + * Compile a "where not in" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNotIn(Builder $query, $where) + { + $values = $this->parameterize($where['values']); + + return $this->wrap($where['column']).' not in ('.$values.')'; + } + + /** + * Compile a where in sub-select clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereInSub(Builder $query, $where) + { + $select = $this->compileSelect($where['query']); + + return $this->wrap($where['column']).' in ('.$select.')'; + } + + /** + * Compile a where not in sub-select clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNotInSub(Builder $query, $where) + { + $select = $this->compileSelect($where['query']); + + return $this->wrap($where['column']).' not in ('.$select.')'; + } + + /** + * Compile a "where null" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNull(Builder $query, $where) + { + return $this->wrap($where['column']).' is null'; + } + + /** + * Compile a "where not null" clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereNotNull(Builder $query, $where) + { + return $this->wrap($where['column']).' is not null'; + } + + /** + * Compile a raw where clause. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $where + * @return string + */ + protected function whereRaw(Builder $query, $where) + { + return $where['sql']; + } + + /** + * Compile the "group by" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $groups + * @return string + */ + protected function compileGroups(Builder $query, $groups) + { + return 'group by '.$this->columnize($groups); + } + + /** + * Compile the "having" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $havings + * @return string + */ + protected function compileHavings(Builder $query, $havings) + { + $me = $this; + + $sql = implode(' ', array_map(array($this, 'compileHaving'), $havings)); + + return 'having '.preg_replace('/and /', '', $sql, 1); + } + + /** + * Compile a single having clause. + * + * @param array $having + * @return string + */ + protected function compileHaving(array $having) + { + // If the having clause is "raw", we can just return the clause straight away + // without doing any more processing on it. Otherwise, we will compile the + // clause into SQL based on the components that make it up from builder. + if ($having['type'] === 'raw') + { + return $having['boolean'].' '.$having['sql']; + } + + return $this->compileBasicHaving($having); + } + + /** + * Compile a basic having clause. + * + * @param array $having + * @return string + */ + protected function compileBasicHaving($having) + { + $column = $this->wrap($having['column']); + + $parameter = $this->parameter($having['value']); + + return 'and '.$column.' '.$having['operator'].' '.$parameter; + } + + /** + * Compile the "order by" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $orders + * @return string + */ + protected function compileOrders(Builder $query, $orders) + { + $me = $this; + + return 'order by '.implode(', ', array_map(function($order) use ($me) + { + return $me->wrap($order['column']).' '.$order['direction']; + } + , $orders)); + } + + /** + * Compile the "limit" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param int $limit + * @return string + */ + protected function compileLimit(Builder $query, $limit) + { + return "limit $limit"; + } + + /** + * Compile the "offset" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param int $offset + * @return string + */ + protected function compileOffset(Builder $query, $offset) + { + return "offset $offset"; + } + + /** + * Compile the "union" queries attached to the main query. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileUnions(Builder $query) + { + $sql = ''; + + foreach ($query->unions as $union) + { + $joiner = $union['all'] ? 'union all ' : 'union '; + + $sql = $joiner.$union['query']->toSql(); + } + + return $sql; + } + + /** + * Compile an insert statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileInsert(Builder $query, array $values) + { + // Essentially we will force every insert to be treated as a batch insert which + // simply makes creating the SQL easier for us since we can utilize the same + // basic routine regardless of an amount of records given to us to insert. + $table = $this->wrapTable($query->from); + + if ( ! is_array(reset($values))) + { + $values = array($values); + } + + $columns = $this->columnize(array_keys(reset($values))); + + // We need to build a list of parameter place-holders of values that are bound + // to the query. Each insert should have the exact same amount of parameter + // bindings so we can just go off the first list of values in this array. + $parameters = $this->parameterize(reset($values)); + + $value = array_fill(0, count($values), "($parameters)"); + + $parameters = implode(', ', $value); + + return "insert into $table ($columns) values $parameters"; + } + + /** + * Compile an insert and get ID statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @param string $sequence + * @return string + */ + public function compileInsertGetId(Builder $query, $values, $sequence) + { + return $this->compileInsert($query, $values); + } + + /** + * Compile an update statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileUpdate(Builder $query, $values) + { + $table = $this->wrapTable($query->from); + + // Each one of the columns in the update statements needs to be wrapped in the + // keyword identifiers, also a place-holder needs to be created for each of + // the values in the list of bindings so we can make the sets statements. + $columns = array(); + + foreach ($values as $key => $value) + { + $columns[] = $this->wrap($key).' = '.$this->parameter($value); + } + + $columns = implode(', ', $columns); + + // If the query has any "join" clauses, we will setup the joins on the builder + // and compile them so we can attach them to this update, as update queries + // can get join statements to attach to other tables when they're needed. + if (isset($query->joins)) + { + $joins = ' '.$this->compileJoins($query, $query->joins); + } + else + { + $joins = ''; + } + + // Of course, update queries may also be constrained by where clauses so we'll + // need to compile the where clauses and attach it to the query so only the + // intended records are updated by the SQL statements we generate to run. + $where = $this->compileWheres($query); + + return trim("update {$table}{$joins} set $columns $where"); + } + + /** + * Compile a delete statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileDelete(Builder $query) + { + $table = $this->wrapTable($query->from); + + $where = is_array($query->wheres) ? $this->compileWheres($query) : ''; + + return trim("delete from $table ".$where); + } + + /** + * Compile a truncate table statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return array + */ + public function compileTruncate(Builder $query) + { + return array('truncate '.$this->wrapTable($query->from) => array()); + } + + /** + * Concatenate an array of segments, removing empties. + * + * @param array $segments + * @return string + */ + protected function concatenate($segments) + { + return implode(' ', array_filter($segments, function($value) + { + return (string) $value !== ''; + })); + } + + /** + * Remove the leading boolean from a statement. + * + * @param string $value + * @return string + */ + protected function removeLeadingBoolean($value) + { + return preg_replace('/and |or /', '', $value, 1); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php b/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php new file mode 100755 index 0000000..6a6b723 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php @@ -0,0 +1,12 @@ +wrapTable($query->from); + + // Each one of the columns in the update statements needs to be wrapped in the + // keyword identifiers, also a place-holder needs to be created for each of + // the values in the list of bindings so we can make the sets statements. + $columns = $this->compileUpdateColumns($values); + + $from = $this->compileUpdateFrom($query); + + $where = $this->compileUpdateWheres($query); + + return trim("update {$table} set {$columns}{$from} $where"); + } + + /** + * Compile the columns for the update statement. + * + * @param array $values + * @return string + */ + protected function compileUpdateColumns($values) + { + $columns = array(); + + // When gathering the columns for an update statement, we'll wrap each of the + // columns and convert it to a parameter value. Then we will concatenate a + // list of the columns that can be added into this update query clauses. + foreach ($values as $key => $value) + { + $columns[] = $this->wrap($key).' = '.$this->parameter($value); + } + + return implode(', ', $columns); + } + + /** + * Compile the "from" clause for an update with a join. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileUpdateFrom(Builder $query) + { + if ( ! isset($query->joins)) return ''; + + $froms = array(); + + // When using Postgres, updates with joins list the joined tables in the from + // clause, which is different than other systems like MySQL. Here, we will + // compile out the tables that are joined and add them to a from clause. + foreach ($query->joins as $join) + { + $froms[] = $this->wrapTable($join->table); + } + + if (count($froms) > 0) return ' from '.implode(', ', $froms); + } + + /** + * Compile the additional where clauses for updates with joins. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileUpdateWheres(Builder $query) + { + $baseWhere = $this->compileWheres($query); + + if ( ! isset($query->joins)) return $baseWhere; + + // Once we compile the join constraints, we will either use them as the where + // clause or append them to the existing base where clauses. If we need to + // strip the leading boolean we will do so when using as the only where. + $joinWhere = $this->compileUpdateJoinWheres($query); + + if (trim($baseWhere) == '') + { + return 'where '.$this->removeLeadingBoolean($joinWhere); + } + else + { + return $baseWhere.' '.$joinWhere; + } + } + + /** + * Compile the "join" clauses for an update. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileUpdateJoinWheres(Builder $query) + { + $joinWheres = array(); + + // Here we will just loop through all of the join constraints and compile them + // all out then implode them. This should give us "where" like syntax after + // everything has been built and then we will join it to the real wheres. + foreach ($query->joins as $join) + { + foreach ($join->clauses as $clause) + { + $joinWheres[] = $this->compileJoinConstraint($clause); + } + } + + return implode(' ', $joinWheres); + } + + /** + * Compile an insert and get ID statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @param string $sequence + * @return string + */ + public function compileInsertGetId(Builder $query, $values, $sequence) + { + if (is_null($sequence)) $sequence = 'id'; + + return $this->compileInsert($query, $values).' returning '.$this->wrap($sequence); + } + + /** + * Compile a truncate table statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return array + */ + public function compileTruncate(Builder $query) + { + return array('truncate '.$this->wrapTable($query->from).' restart identity' => array()); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php b/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php new file mode 100755 index 0000000..5e68c90 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php @@ -0,0 +1,84 @@ +wrap($order['column']).' collate nocase '.$order['direction']; + } + , $orders)); + } + + /** + * Compile an insert statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $values + * @return string + */ + public function compileInsert(Builder $query, array $values) + { + // Essentially we will force every insert to be treated as a batch insert which + // simply makes creating the SQL easier for us since we can utilize the same + // basic routine regardless of an amount of records given to us to insert. + $table = $this->wrapTable($query->from); + + if ( ! is_array(reset($values))) + { + $values = array($values); + } + + // If there is only one record being inserted, we will just use the usual query + // grammar insert builder because no special syntax is needed for the single + // row inserts in SQLite. However, if there are multiples, we'll continue. + if (count($values) == 1) + { + return parent::compileInsert($query, $values[0]); + } + + $names = $this->columnize(array_keys($values[0])); + + $columns = array(); + + // SQLite requires us to build the multi-row insert as a listing of select with + // unions joining them together. So we'll build out this list of columns and + // then join them all together with select unions to complete the queries. + foreach (array_keys($values[0]) as $column) + { + $columns[] = '? as '.$this->wrap($column); + } + + $columns = array_fill(0, count($values), implode(', ', $columns)); + + return "insert into $table ($names) select ".implode(' union select ', $columns); + } + + /** + * Compile a truncate table statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return array + */ + public function compileTruncate(Builder $query) + { + $sql = array('delete from sqlite_sequence where name = ?' => array($query->from)); + + $sql['delete from '.$this->wrapTable($query->from)] = array(); + + return $sql; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php b/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php new file mode 100755 index 0000000..1119a50 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php @@ -0,0 +1,188 @@ +compileComponents($query); + + // If an offset is present on the query, we will need to wrap the query in + // a big "ANSI" offset syntax block. This is very nasty compared to the + // other database systems but is necessary for implementing features. + if ($query->offset > 0) + { + return $this->compileAnsiOffset($query, $components); + } + + return $this->concatenate($components); + } + + /** + * Compile the "select *" portion of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $columns + * @return string + */ + protected function compileColumns(Builder $query, $columns) + { + if ( ! is_null($query->aggregate)) return; + + $select = $query->distinct ? 'select distinct ' : 'select '; + + // If there is a limit on the query, but not an offset, we will add the top + // clause to the query, which serves as a "limit" type clause within the + // SQL Server system similar to the limit keywords available in MySQL. + if ($query->limit > 0 and $query->offset <= 0) + { + $select .= 'top '.$query->limit.' '; + } + + return $select.$this->columnize($columns); + } + + /** + * Create a full ANSI offset clause for the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $components + * @return string + */ + protected function compileAnsiOffset(Builder $query, $components) + { + // An ORDER BY clause is required to make this offset query work, so if one does + // not exist we'll just create a dummy clause to trick the database and so it + // does not complain about the queries for not having an "order by" clause. + if ( ! isset($components['orders'])) + { + $components['orders'] = 'order by (select 0)'; + } + + // We need to add the row number to the query so we can compare it to the offset + // and limit values given for the statements. So we will add an expression to + // the "select" that will give back the row numbers on each of the records. + $orderings = $components['orders']; + + $components['columns'] .= $this->compileOver($orderings); + + unset($components['orders']); + + // Next we need to calculate the constraints that should be placed on the query + // to get the right offset and limit from our query but if there is no limit + // set we will just handle the offset only since that is all that matters. + $start = $query->offset + 1; + + $constraint = $this->compileRowConstraint($query); + + $sql = $this->concatenate($components); + + // We are now ready to build the final SQL query so we'll create a common table + // expression from the query and get the records with row numbers within our + // given limit and offset value that we just put on as a query constraint. + return $this->compileTableExpression($sql, $constraint); + } + + /** + * Compile the over statement for a table expression. + * + * @param string $orderings + * @return string + */ + protected function compileOver($orderings) + { + return ", row_number() over ({$orderings}) as row_num"; + } + + /** + * Compile the limit / offset row constraint for a query. + * + * @param \Illuminate\Database\Query\Builder $query + * @return string + */ + protected function compileRowConstraint($query) + { + $start = $query->offset + 1; + + if ($query->limit > 0) + { + $finish = $query->offset + $query->limit; + + return "between {$start} and {$finish}"; + } + + return ">= {$start}"; + } + + /** + * Compile a common table expression for a query. + * + * @param string $sql + * @param string $constraint + * @return string + */ + protected function compileTableExpression($sql, $constraint) + { + return "select * from ({$sql}) as temp_table where row_num {$constraint}"; + } + + /** + * Compile the "limit" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param int $limit + * @return string + */ + protected function compileLimit(Builder $query, $limit) + { + return ''; + } + + /** + * Compile the "offset" portions of the query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param int $offset + * @return string + */ + protected function compileOffset(Builder $query, $offset) + { + return ''; + } + + /** + * Compile a truncate table statement into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @return array + */ + public function compileTruncate(Builder $query) + { + return array('truncate table '.$this->wrapTable($query->from) => array()); + } + + /** + * Get the format for database stored dates. + * + * @return string + */ + public function getDateFormat() + { + return 'Y-m-d H:i:s.000'; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Query/JoinClause.php b/vendor/laravel/framework/src/Illuminate/Database/Query/JoinClause.php new file mode 100755 index 0000000..d3f610a --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Query/JoinClause.php @@ -0,0 +1,68 @@ +type = $type; + $this->table = $table; + } + + /** + * Add an "on" clause to the join. + * + * @param string $first + * @param string $operator + * @param string $second + * @param string $boolean + * @return \Illuminate\Database\Query\JoinClause + */ + public function on($first, $operator, $second, $boolean = 'and') + { + $this->clauses[] = compact('first', 'operator', 'second', 'boolean'); + + return $this; + } + + /** + * Add an "or on" clause to the join. + * + * @param string $first + * @param string $operator + * @param string $second + * @return \Illuminate\Database\Query\JoinClause + */ + public function orOn($first, $operator, $second) + { + return $this->on($first, $operator, $second, 'or'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/PostgresProcessor.php b/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/PostgresProcessor.php new file mode 100755 index 0000000..1ca5365 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/PostgresProcessor.php @@ -0,0 +1,27 @@ +getConnection()->select($sql, $values); + + $sequence = $sequence ?: 'id'; + + $result = (array) $results[0]; + + return (int) $result[$sequence]; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/Processor.php b/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/Processor.php new file mode 100755 index 0000000..c24091c --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/Processor.php @@ -0,0 +1,37 @@ +getConnection()->insert($sql, $values); + + $id = $query->getConnection()->getPdo()->lastInsertId($sequence); + + return is_numeric($id) ? (int) $id : $id; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php b/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php new file mode 100755 index 0000000..d7fab3f --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php @@ -0,0 +1,25 @@ +getConnection()->insert($sql, $values); + + $id = $query->getConnection()->getPdo()->lastInsertId(); + + return is_numeric($id) ? (int) $id : $id; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/README.md b/vendor/laravel/framework/src/Illuminate/Database/README.md new file mode 100755 index 0000000..bfe95c0 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/README.md @@ -0,0 +1,69 @@ +## Illuminate Database + +The Illuminate Database component is a full database toolkit for PHP, providing an expressive query builder, ActiveRecord style ORM, and schema builder. It currently supports MySQL, Postgres, SQL Server, and SQLite. It also serves as the database layer of the Laravel PHP framework. + +### Usage Instructions + +First, create a new "Capsule" manager instance. Capsule aims to make configuring the library for usage outside of the Laravel framework as easy as possible. + +``` +use Illuminate\Database\Capsule\Manager as Capsule; + +$capsule = new Capsule; + +$capsule->addConnection([ + 'driver' => 'mysql', + 'host' => 'localhost', + 'database' => 'database', + 'username' => 'root', + 'password' => 'password', + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '', +]); + +// Setup the Eloquent ORM... (optional) +$capsule->bootEloquent(); + +// Set the event dispatcher used by Eloquent models... (optional) +$capsule->setEventDispatcher(...); + +// Set the cache manager instance used by connections... (optional) +$capsule->setCacheManager(...); + +// Make this Capsule instance available globally via static methods... (optional) +$capsule->setAsGlobal(); +``` + +Once the Capsule instance has been registered. You may use it like so: + +**Using The Query Builder** + +``` +$users = Capsule::table('users')->where('votes', '>' 100)->get(); +``` +Other core methods may be accessed directly from the Capsule in the same manner as from the DB facade: +``` +$results = Capsule::select('select * from users where id = ?', array(1)); +``` + +**Using The Schema Builder** + +``` +Capsule::schema()->create('users', function($table) +{ + $table->increments('id'); + $table->string('email')->unique(); + $table->timestamps(); +}); +``` + +**Using The Eloquent ORM** + +``` +class User extends Illuminate\Database\Eloquent\Model {} + +$users = User::where('votes', '>', 1)->get(); +``` + +For further documentation on using the various database facilities this library provides, consult the [Laravel framework documentation](http://laravel.com/docs). \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/SQLiteConnection.php b/vendor/laravel/framework/src/Illuminate/Database/SQLiteConnection.php new file mode 100755 index 0000000..0751baa --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/SQLiteConnection.php @@ -0,0 +1,35 @@ +withTablePrefix(new Query\Grammars\SQLiteGrammar); + } + + /** + * Get the default schema grammar instance. + * + * @return \Illuminate\Database\Schema\Grammars\Grammar + */ + protected function getDefaultSchemaGrammar() + { + return $this->withTablePrefix(new Schema\Grammars\SQLiteGrammar); + } + + /** + * Get the Doctrine DBAL Driver. + * + * @return \Doctrine\DBAL\Driver + */ + protected function getDoctrineDriver() + { + return new \Doctrine\DBAL\Driver\PDOSqlite\Driver; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php b/vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php new file mode 100755 index 0000000..4c4aebf --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Schema/Blueprint.php @@ -0,0 +1,757 @@ +table = $table; + + if ( ! is_null($callback)) $callback($this); + } + + /** + * Execute the blueprint against the database. + * + * @param \Illuminate\Database\Connection $connection + * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar + * @return void + */ + public function build(Connection $connection, Grammar $grammar) + { + foreach ($this->toSql($connection, $grammar) as $statement) + { + $connection->statement($statement); + } + } + + /** + * Get the raw SQL statements for the blueprint. + * + * @param \Illuminate\Database\Connection $connection + * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar + * @return array + */ + public function toSql(Connection $connection, Grammar $grammar) + { + $this->addImpliedCommands(); + + $statements = array(); + + // Each type of command has a corresponding compiler function on the schema + // grammar which is used to build the necessary SQL statements to build + // the blueprint element, so we'll just call that compilers function. + foreach ($this->commands as $command) + { + $method = 'compile'.ucfirst($command->name); + + if (method_exists($grammar, $method)) + { + if ( ! is_null($sql = $grammar->$method($this, $command, $connection))) + { + $statements = array_merge($statements, (array) $sql); + } + } + } + + return $statements; + } + + /** + * Add the commands that are implied by the blueprint. + * + * @return void + */ + protected function addImpliedCommands() + { + if (count($this->columns) > 0 and ! $this->creating()) + { + array_unshift($this->commands, $this->createCommand('add')); + } + + $this->addFluentIndexes(); + } + + /** + * Add the index commands fluently specified on columns. + * + * @return void + */ + protected function addFluentIndexes() + { + foreach ($this->columns as $column) + { + foreach (array('primary', 'unique', 'index') as $index) + { + // If the index has been specified on the given column, but is simply + // equal to "true" (boolean), no name has been specified for this + // index, so we will simply call the index methods without one. + if ($column->$index === true) + { + $this->$index($column->name); + + continue 2; + } + + // If the index has been specified on the column and it is something + // other than boolean true, we will assume a name was provided on + // the index specification, and pass in the name to the method. + elseif (isset($column->$index)) + { + $this->$index($column->name, $column->$index); + + continue 2; + } + } + } + } + + /** + * Determine if the blueprint has a create command. + * + * @return bool + */ + protected function creating() + { + foreach ($this->commands as $command) + { + if ($command->name == 'create') return true; + } + + return false; + } + + /** + * Indicate that the table needs to be created. + * + * @return \Illuminate\Support\Fluent + */ + public function create() + { + return $this->addCommand('create'); + } + + /** + * Indicate that the table should be dropped. + * + * @return \Illuminate\Support\Fluent + */ + public function drop() + { + return $this->addCommand('drop'); + } + + /** + * Indicate that the table should be dropped if it exists. + * + * @return \Illuminate\Support\Fluent + */ + public function dropIfExists() + { + return $this->addCommand('dropIfExists'); + } + + /** + * Indicate that the given columns should be dropped. + * + * @param string|array $columns + * @return \Illuminate\Support\Fluent + */ + public function dropColumn($columns) + { + $columns = is_array($columns) ? $columns : (array) func_get_args(); + + return $this->addCommand('dropColumn', compact('columns')); + } + + /** + * Indicate that the given columns should be renamed. + * + * @param string $from + * @param string $to + * @return \Illuminate\Support\Fluent + */ + public function renameColumn($from, $to) + { + return $this->addCommand('renameColumn', compact('from', 'to')); + } + + /** + * Indicate that the given primary key should be dropped. + * + * @param string|array $index + * @return \Illuminate\Support\Fluent + */ + public function dropPrimary($index = null) + { + return $this->dropIndexCommand('dropPrimary', 'primary', $index); + } + + /** + * Indicate that the given unique key should be dropped. + * + * @param string|array $index + * @return \Illuminate\Support\Fluent + */ + public function dropUnique($index) + { + return $this->dropIndexCommand('dropUnique', 'unique', $index); + } + + /** + * Indicate that the given index should be dropped. + * + * @param string|array $index + * @return \Illuminate\Support\Fluent + */ + public function dropIndex($index) + { + return $this->dropIndexCommand('dropIndex', 'index', $index); + } + + /** + * Indicate that the given foreign key should be dropped. + * + * @param string $index + * @return \Illuminate\Support\Fluent + */ + public function dropForeign($index) + { + return $this->dropIndexCommand('dropForeign', 'foreign', $index); + } + + /** + * Indicate that the timestamp columns should be dropped. + * + * @return void + */ + public function dropTimestamps() + { + $this->dropColumn('created_at', 'updated_at'); + } + + /** + * Rename the table to a given name. + * + * @param string $to + * @return \Illuminate\Support\Fluent + */ + public function rename($to) + { + return $this->addCommand('rename', compact('to')); + } + + /** + * Specify the primary key(s) for the table. + * + * @param string|array $columns + * @param string $name + * @return \Illuminate\Support\Fluent + */ + public function primary($columns, $name = null) + { + return $this->indexCommand('primary', $columns, $name); + } + + /** + * Specify a unique index for the table. + * + * @param string|array $columns + * @param string $name + * @return \Illuminate\Support\Fluent + */ + public function unique($columns, $name = null) + { + return $this->indexCommand('unique', $columns, $name); + } + + /** + * Specify an index for the table. + * + * @param string|array $columns + * @param string $name + * @return \Illuminate\Support\Fluent + */ + public function index($columns, $name = null) + { + return $this->indexCommand('index', $columns, $name); + } + + /** + * Specify a foreign key for the table. + * + * @param string|array $columns + * @param string $name + * @return \Illuminate\Support\Fluent + */ + public function foreign($columns, $name = null) + { + return $this->indexCommand('foreign', $columns, $name); + } + + /** + * Create a new auto-incrementing integer column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function increments($column) + { + return $this->unsignedInteger($column, true); + } + + /** + * Create a new auto-incrementing big integer column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function bigIncrements($column) + { + return $this->unsignedBigInteger($column, true); + } + + /** + * Create a new string column on the table. + * + * @param string $column + * @param int $length + * @return \Illuminate\Support\Fluent + */ + public function string($column, $length = 255) + { + return $this->addColumn('string', $column, compact('length')); + } + + /** + * Create a new text column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function text($column) + { + return $this->addColumn('text', $column); + } + + /** + * Create a new medium text column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function mediumText($column) + { + return $this->addColumn('mediumText', $column); + } + + /** + * Create a new long text column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function longText($column) + { + return $this->addColumn('longText', $column); + } + + /** + * Create a new integer column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @param bool $unsigned + * @return \Illuminate\Support\Fluent + */ + public function integer($column, $autoIncrement = false, $unsigned = false) + { + return $this->addColumn('integer', $column, compact('autoIncrement', 'unsigned')); + } + + /** + * Create a new big integer column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @param bool $unsigned + * @return \Illuminate\Support\Fluent + */ + public function bigInteger($column, $autoIncrement = false, $unsigned = false) + { + return $this->addColumn('bigInteger', $column, compact('autoIncrement', 'unsigned')); + } + + /** + * Create a new medium integer column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function mediumInteger($column) + { + return $this->addColumn('mediumInteger', $column); + } + + /** + * Create a new tiny integer column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function tinyInteger($column) + { + return $this->addColumn('tinyInteger', $column); + } + + /** + * Create a new small integer column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function smallInteger($column) + { + return $this->addColumn('smallInteger', $column); + } + + /** + * Create a new unsigned integer column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @param bool $unsigned + * @return \Illuminate\Support\Fluent + */ + public function unsignedInteger($column, $autoIncrement = false) + { + return $this->integer($column, $autoIncrement, true); + } + + /** + * Create a new unsigned big integer column on the table. + * + * @param string $column + * @param bool $autoIncrement + * @param bool $unsigned + * @return \Illuminate\Support\Fluent + */ + public function unsignedBigInteger($column, $autoIncrement = false) + { + return $this->bigInteger($column, $autoIncrement, true); + } + + /** + * Create a new float column on the table. + * + * @param string $column + * @param int $total + * @param int $places + * @return \Illuminate\Support\Fluent + */ + public function float($column, $total = 8, $places = 2) + { + return $this->addColumn('float', $column, compact('total', 'places')); + } + + /** + * Create a new decimal column on the table. + * + * @param string $column + * @param int $total + * @param int $places + * @return \Illuminate\Support\Fluent + */ + public function decimal($column, $total = 8, $places = 2) + { + return $this->addColumn('decimal', $column, compact('total', 'places')); + } + + /** + * Create a new boolean column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function boolean($column) + { + return $this->addColumn('boolean', $column); + } + + /** + * Create a new enum column on the table. + * + * @param string $column + * @param array $allowed + * @return \Illuminate\Support\Fluent + */ + public function enum($column, array $allowed) + { + return $this->addColumn('enum', $column, compact('allowed')); + } + + /** + * Create a new date column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function date($column) + { + return $this->addColumn('date', $column); + } + + /** + * Create a new date-time column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function dateTime($column) + { + return $this->addColumn('dateTime', $column); + } + + /** + * Create a new time column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function time($column) + { + return $this->addColumn('time', $column); + } + + /** + * Create a new timestamp column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function timestamp($column) + { + return $this->addColumn('timestamp', $column); + } + + /** + * Add creation and update timestamps to the table. + * + * @return void + */ + public function timestamps() + { + $this->timestamp('created_at'); + + $this->timestamp('updated_at'); + } + + /** + * Add a "deleted at" timestamp for the table. + * + * @return void + */ + public function softDeletes() + { + $this->timestamp('deleted_at')->nullable(); + } + + /** + * Create a new binary column on the table. + * + * @param string $column + * @return \Illuminate\Support\Fluent + */ + public function binary($column) + { + return $this->addColumn('binary', $column); + } + + /** + * Add the proper columns for a polymorphic table. + * + * @param string $name + * @return void + */ + public function morphs($name) + { + $this->integer("{$name}_id"); + + $this->string("{$name}_type"); + } + + /** + * Create a new drop index command on the blueprint. + * + * @param string $command + * @param string $type + * @param string|array $index + * @return \Illuminate\Support\Fluent + */ + protected function dropIndexCommand($command, $type, $index) + { + $columns = array(); + + // If the given "index" is actually an array of columns, the developer means + // to drop an index merely by specifying the columns involved without the + // conventional name, so we will built the index name from the columns. + if (is_array($index)) + { + $columns = $index; + + $index = $this->createIndexName($type, $columns); + } + + return $this->indexCommand($command, $columns, $index); + } + + /** + * Add a new index command to the blueprint. + * + * @param string $type + * @param string|array $columns + * @param string $index + * @return \Illuminate\Support\Fluent + */ + protected function indexCommand($type, $columns, $index) + { + $columns = (array) $columns; + + // If no name was specified for this index, we will create one using a basic + // convention of the table name, followed by the columns, followed by an + // index type, such as primary or index, which makes the index unique. + if (is_null($index)) + { + $index = $this->createIndexName($type, $columns); + } + + return $this->addCommand($type, compact('index', 'columns')); + } + + /** + * Create a default index name for the table. + * + * @param string $type + * @param array $columns + * @return string + */ + protected function createIndexName($type, array $columns) + { + $table = str_replace(array('-', '.'), '_', $this->table); + + return strtolower($table.'_'.implode('_', $columns).'_'.$type); + } + + /** + * Add a new column to the blueprint. + * + * @param string $type + * @param string $name + * @param array $parameters + * @return \Illuminate\Support\Fluent + */ + protected function addColumn($type, $name, array $parameters = array()) + { + $attributes = array_merge(compact('type', 'name'), $parameters); + + $this->columns[] = $column = new Fluent($attributes); + + return $column; + } + + /** + * Add a new command to the blueprint. + * + * @param string $name + * @param array $parameters + * @return \Illuminate\Support\Fluent + */ + protected function addCommand($name, array $parameters = array()) + { + $this->commands[] = $command = $this->createCommand($name, $parameters); + + return $command; + } + + /** + * Create a new Fluent command. + * + * @param string $name + * @param array $parameters + * @return \Illuminate\Support\Fluent + */ + protected function createCommand($name, array $parameters = array()) + { + return new Fluent(array_merge(compact('name'), $parameters)); + } + + /** + * Get the table the blueprint describes. + * + * @return string + */ + public function getTable() + { + return $this->table; + } + + /** + * Get the columns that should be added. + * + * @return array + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Get the commands on the blueprint. + * + * @return array + */ + public function getCommands() + { + return $this->commands; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php b/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php new file mode 100755 index 0000000..edd73a1 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Schema/Builder.php @@ -0,0 +1,186 @@ +connection = $connection; + $this->grammar = $connection->getSchemaGrammar(); + } + + /** + * Determine if the given table exists. + * + * @param string $table + * @return bool + */ + public function hasTable($table) + { + $sql = $this->grammar->compileTableExists(); + + $table = $this->connection->getTablePrefix().$table; + + return count($this->connection->select($sql, array($table))) > 0; + } + + /** + * Determine if the given table has a given column. + * + * @param string $table + * @param string $column + * @return bool + */ + public function hasColumn($table, $column) + { + $schema = $this->connection->getDoctrineSchemaManager(); + + return in_array($column, array_keys($schema->listTableColumns($table))); + } + + /** + * Modify a table on the schema. + * + * @param string $table + * @param Closure $callback + * @return \Illuminate\Database\Schema\Blueprint + */ + public function table($table, Closure $callback) + { + $this->build($this->createBlueprint($table, $callback)); + } + + /** + * Create a new table on the schema. + * + * @param string $table + * @param Closure $callback + * @return \Illuminate\Database\Schema\Blueprint + */ + public function create($table, Closure $callback) + { + $blueprint = $this->createBlueprint($table); + + $blueprint->create(); + + $callback($blueprint); + + $this->build($blueprint); + } + + /** + * Drop a table from the schema. + * + * @param string $table + * @return \Illuminate\Database\Schema\Blueprint + */ + public function drop($table) + { + $blueprint = $this->createBlueprint($table); + + $blueprint->drop(); + + $this->build($blueprint); + } + + /** + * Drop a table from the schema if it exists. + * + * @param string $table + * @return \Illuminate\Database\Schema\Blueprint + */ + public function dropIfExists($table) + { + $blueprint = $this->createBlueprint($table); + + $blueprint->dropIfExists(); + + $this->build($blueprint); + } + + /** + * Rename a table on the schema. + * + * @param string $from + * @param string $to + * @return \Illuminate\Database\Schema\Blueprint + */ + public function rename($from, $to) + { + $blueprint = $this->createBlueprint($from); + + $blueprint->rename($to); + + $this->build($blueprint); + } + + /** + * Execute the blueprint to build / modify the table. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @return void + */ + protected function build(Blueprint $blueprint) + { + $blueprint->build($this->connection, $this->grammar); + } + + /** + * Create a new command set with a Closure. + * + * @param string $table + * @param Closure $callback + * @return \Illuminate\Database\Schema\Blueprint + */ + protected function createBlueprint($table, Closure $callback = null) + { + return new Blueprint($table, $callback); + } + + /** + * Get the database connection instance. + * + * @return \Illuminate\Database\Connection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * Set the database connection instance. + * + * @param \Illuminate\Database\Connection + * @return \Illuminate\Database\Schema\Builder + */ + public function setConnection(Connection $connection) + { + $this->connection = $connection; + + return $this; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/Grammar.php b/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/Grammar.php new file mode 100755 index 0000000..7d74246 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/Grammar.php @@ -0,0 +1,265 @@ +getDoctrineSchemaManager(); + + $column = $connection->getDoctrineColumn($blueprint->getTable(), $command->from); + + $tableDiff = $this->getRenamedDiff($blueprint, $command, $column, $schema); + + return (array) $schema->getDatabasePlatform()->getAlterTableSQL($tableDiff); + } + + /** + * Get a new column instance with the new column name. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @param \Doctrine\DBAL\Schema\Column $column + * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema + * @return \Doctrine\DBAL\Schema\TableDiff + */ + protected function getRenamedDiff(Blueprint $blueprint, Fluent $command, Column $column, SchemaManager $schema) + { + $tableDiff = $this->getDoctrineTableDiff($blueprint, $schema); + + return $this->setRenamedColumns($tableDiff, $command, $column); + } + + /** + * Set the renamed columns on the table diff. + * + * @param \Doctrine\DBAL\Schema\TableDiff $tableDiff + * @param \Illuminate\Support\Fluent $command + * @param \Doctrine\DBAL\Schema\Column $column + * @return \Doctrine\DBAL\Schema\TableDiff + */ + protected function setRenamedColumns(TableDiff $tableDiff, Fluent $command, Column $column) + { + $newColumn = new Column($command->to, $column->getType(), $column->toArray()); + + $tableDiff->renamedColumns = array($command->from => $newColumn); + + return $tableDiff; + } + + /** + * Compile a foreign key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileForeign(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + $on = $this->wrapTable($command->on); + + // We need to prepare several of the elements of the foreign key definition + // before we can create the SQL, such as wrapping the tables and convert + // an array of columns to comma-delimited strings for the SQL queries. + $columns = $this->columnize($command->columns); + + $onColumns = $this->columnize((array) $command->references); + + $sql = "alter table {$table} add constraint {$command->index} "; + + $sql .= "foreign key ({$columns}) references {$on} ({$onColumns})"; + + // Once we have the basic foreign key creation statement constructed we can + // build out the syntax for what should happen on an update or delete of + // the affected columns, which will get something like "cascade", etc. + if ( ! is_null($command->onDelete)) + { + $sql .= " on delete {$command->onDelete}"; + } + + if ( ! is_null($command->onUpdate)) + { + $sql .= " on update {$command->onUpdate}"; + } + + return $sql; + } + + /** + * Compile the blueprint's column definitions. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @return array + */ + protected function getColumns(Blueprint $blueprint) + { + $columns = array(); + + foreach ($blueprint->getColumns() as $column) + { + // Each of the column types have their own compiler functions which are tasked + // with turning the column definition into its SQL format for this platform + // used by the connection. The column's modifiers are compiled and added. + $sql = $this->wrap($column).' '.$this->getType($column); + + $columns[] = $this->addModifiers($sql, $blueprint, $column); + } + + return $columns; + } + + /** + * Add the column modifiers to the definition. + * + * @param string $sql + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function addModifiers($sql, Blueprint $blueprint, Fluent $column) + { + foreach ($this->modifiers as $modifier) + { + if (method_exists($this, $method = "modify{$modifier}")) + { + $sql .= $this->{$method}($blueprint, $column); + } + } + + return $sql; + } + + /** + * Get the primary key command if it exists on the blueprint. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @return \Illuminate\Support\Fluent|null + */ + protected function getCommandByName(Blueprint $blueprint, $name) + { + $commands = $this->getCommandsByName($blueprint, $name); + + if (count($commands) > 0) + { + return reset($commands); + } + } + + /** + * Get all of the commands with a given name. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param string $name + * @return array + */ + protected function getCommandsByName(Blueprint $blueprint, $name) + { + return array_filter($blueprint->getCommands(), function($value) use ($name) + { + return $value->name == $name; + }); + } + + /** + * Get the SQL for the column data type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function getType(Fluent $column) + { + return $this->{"type".ucfirst($column->type)}($column); + } + + /** + * Add a prefix to an array of values. + * + * @param string $prefix + * @param array $values + * @return array + */ + public function prefixArray($prefix, array $values) + { + return array_map(function($value) use ($prefix) + { + return $prefix.' '.$value; + + }, $values); + } + + /** + * Wrap a table in keyword identifiers. + * + * @param mixed $table + * @return string + */ + public function wrapTable($table) + { + if ($table instanceof Blueprint) $table = $table->getTable(); + + return parent::wrapTable($table); + } + + /** + * Wrap a value in keyword identifiers. + * + * @param string $value + * @return string + */ + public function wrap($value) + { + if ($value instanceof Fluent) $value = $value->name; + + return parent::wrap($value); + } + + /** + * Format a value so that it can be used in "default" clauses. + * + * @param mixed $value + * @return string + */ + protected function getDefaultValue($value) + { + if ($value instanceof Expression) return $value; + + if (is_bool($value)) return "'".intval($value)."'"; + + return "'".strval($value)."'"; + } + + /** + * Create an empty Doctrine DBAL TableDiff from the Blueprint. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Doctrine\DBAL\Schema\AbstractSchemaManager $schema + * @return \Doctrine\DBAL\Schema\TableDiff + */ + protected function getDoctrineTableDiff(Blueprint $blueprint, SchemaManager $schema) + { + $tableDiff = new TableDiff($blueprint->getTable()); + + $tableDiff->fromTable = $schema->listTableDetails($blueprint->getTable()); + + return $tableDiff; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php new file mode 100755 index 0000000..5c88cb1 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -0,0 +1,537 @@ +getColumns($blueprint)); + + $sql = 'create table '.$this->wrapTable($blueprint)." ($columns)"; + + // Once we have the primary SQL, we can add the encoding option to the SQL for + // the table. Then, we can check if a storage engine has been supplied for + // the table. If so, we will add the engine declaration to the SQL query. + $sql = $this->compileCreateEncoding($sql, $connection); + + if (isset($blueprint->engine)) + { + $sql .= ' engine = '.$blueprint->engine; + } + + return $sql; + } + + /** + * Append the character set specifications to a command. + * + * @param string $sql + * @param \Illuminate\Database\Connection $connection + * @return string + */ + protected function compileCreateEncoding($sql, Connection $connection) + { + if ( ! is_null($charset = $connection->getConfig('charset'))) + { + $sql .= ' default character set '.$charset; + } + + if ( ! is_null($collation = $connection->getConfig('collation'))) + { + $sql .= ' collate '.$collation; + } + + return $sql; + } + + /** + * Compile a create table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileAdd(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + $columns = $this->prefixArray('add', $this->getColumns($blueprint)); + + return 'alter table '.$table.' '.implode(', ', $columns); + } + + /** + * Compile a primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compilePrimary(Blueprint $blueprint, Fluent $command) + { + $command->name(null); + + return $this->compileKey($blueprint, $command, 'primary key'); + } + + /** + * Compile a unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileUnique(Blueprint $blueprint, Fluent $command) + { + return $this->compileKey($blueprint, $command, 'unique'); + } + + /** + * Compile a plain index key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileIndex(Blueprint $blueprint, Fluent $command) + { + return $this->compileKey($blueprint, $command, 'index'); + } + + /** + * Compile an index creation command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @param string $type + * @return string + */ + protected function compileKey(Blueprint $blueprint, Fluent $command, $type) + { + $columns = $this->columnize($command->columns); + + $table = $this->wrapTable($blueprint); + + return "alter table {$table} add {$type} {$command->index}($columns)"; + } + + /** + * Compile a drop table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDrop(Blueprint $blueprint, Fluent $command) + { + return 'drop table '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop table (if exists) command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIfExists(Blueprint $blueprint, Fluent $command) + { + return 'drop table if exists '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop column command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropColumn(Blueprint $blueprint, Fluent $command) + { + $columns = $this->prefixArray('drop', $this->wrapArray($command->columns)); + + $table = $this->wrapTable($blueprint); + + return 'alter table '.$table.' '.implode(', ', $columns); + } + + /** + * Compile a drop primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropPrimary(Blueprint $blueprint, Fluent $command) + { + return 'alter table '.$this->wrapTable($blueprint).' drop primary key'; + } + + /** + * Compile a drop unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropUnique(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + return "alter table {$table} drop index {$command->index}"; + } + + /** + * Compile a drop index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIndex(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + return "alter table {$table} drop index {$command->index}"; + } + + /** + * Compile a drop foreign key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropForeign(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + return "alter table {$table} drop foreign key {$command->index}"; + } + + /** + * Compile a rename table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileRename(Blueprint $blueprint, Fluent $command) + { + $from = $this->wrapTable($blueprint); + + return "rename table {$from} to ".$this->wrapTable($command->to); + } + + /** + * Create the column definition for a string type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeString(Fluent $column) + { + return "varchar({$column->length})"; + } + + /** + * Create the column definition for a text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a medium text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumText(Fluent $column) + { + return 'mediumtext'; + } + + /** + * Create the column definition for a long text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeLongText(Fluent $column) + { + return 'longtext'; + } + + /** + * Create the column definition for a big integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBigInteger(Fluent $column) + { + return 'bigint'; + } + + /** + * Create the column definition for a integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeInteger(Fluent $column) + { + return 'int'; + } + + /** + * Create the column definition for a medium integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumInteger(Fluent $column) + { + return 'mediumint'; + } + + /** + * Create the column definition for a tiny integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyInteger(Fluent $column) + { + return 'tinyint(1)'; + } + + /** + * Create the column definition for a small integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeSmallInteger(Fluent $column) + { + return 'smallint'; + } + + /** + * Create the column definition for a float type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeFloat(Fluent $column) + { + return "float({$column->total}, {$column->places})"; + } + + /** + * Create the column definition for a decimal type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDecimal(Fluent $column) + { + return "decimal({$column->total}, {$column->places})"; + } + + /** + * Create the column definition for a boolean type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBoolean(Fluent $column) + { + return 'tinyint(1)'; + } + + /** + * Create the column definition for a enum type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeEnum(Fluent $column) + { + return "enum('".implode("', '", $column->allowed)."')"; + } + + /** + * Create the column definition for a date type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDate(Fluent $column) + { + return 'date'; + } + + /** + * Create the column definition for a date-time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTime(Fluent $column) + { + return 'datetime'; + } + + /** + * Create the column definition for a time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTime(Fluent $column) + { + return 'time'; + } + + /** + * Create the column definition for a timestamp type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestamp(Fluent $column) + { + if ( ! $column->nullable) return 'timestamp default 0'; + + return 'timestamp'; + } + + /** + * Create the column definition for a binary type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBinary(Fluent $column) + { + return 'blob'; + } + + /** + * Get the SQL for an unsigned column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyUnsigned(Blueprint $blueprint, Fluent $column) + { + if ($column->unsigned) return ' unsigned'; + } + + /** + * Get the SQL for a nullable column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyNullable(Blueprint $blueprint, Fluent $column) + { + return $column->nullable ? ' null' : ' not null'; + } + + /** + * Get the SQL for a default column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyDefault(Blueprint $blueprint, Fluent $column) + { + if ( ! is_null($column->default)) + { + return " default ".$this->getDefaultValue($column->default); + } + } + + /** + * Get the SQL for an auto-increment column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyIncrement(Blueprint $blueprint, Fluent $column) + { + if (in_array($column->type, $this->serials) and $column->autoIncrement) + { + return ' auto_increment primary key'; + } + } + + /** + * Get the SQL for an "after" column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyAfter(Blueprint $blueprint, Fluent $column) + { + if ( ! is_null($column->after)) + { + return ' after '.$this->wrap($column->after); + } + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php new file mode 100755 index 0000000..d56b8af --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -0,0 +1,463 @@ +getColumns($blueprint)); + + return 'create table '.$this->wrapTable($blueprint)." ($columns)"; + } + + /** + * Compile a create table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileAdd(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + $columns = $this->prefixArray('add column', $this->getColumns($blueprint)); + + return 'alter table '.$table.' '.implode(', ', $columns); + } + + /** + * Compile a primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compilePrimary(Blueprint $blueprint, Fluent $command) + { + $columns = $this->columnize($command->columns); + + return 'alter table '.$this->wrapTable($blueprint)." add primary key ({$columns})"; + } + + /** + * Compile a unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileUnique(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + $columns = $this->columnize($command->columns); + + return "alter table $table add constraint {$command->index} unique ($columns)"; + } + + /** + * Compile a plain index key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileIndex(Blueprint $blueprint, Fluent $command) + { + $columns = $this->columnize($command->columns); + + return "create index {$command->index} on ".$this->wrapTable($blueprint)." ({$columns})"; + } + + /** + * Compile a drop table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDrop(Blueprint $blueprint, Fluent $command) + { + return 'drop table '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop table (if exists) command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIfExists(Blueprint $blueprint, Fluent $command) + { + return 'drop table if exists '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop column command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropColumn(Blueprint $blueprint, Fluent $command) + { + $columns = $this->prefixArray('drop column', $this->wrapArray($command->columns)); + + $table = $this->wrapTable($blueprint); + + return 'alter table '.$table.' '.implode(', ', $columns); + } + + /** + * Compile a drop primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropPrimary(Blueprint $blueprint, Fluent $command) + { + $table = $blueprint->getTable(); + + return 'alter table '.$this->wrapTable($blueprint)." drop constraint {$table}_pkey"; + } + + /** + * Compile a drop unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropUnique(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + return "alter table {$table} drop constraint {$command->index}"; + } + + /** + * Compile a drop index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIndex(Blueprint $blueprint, Fluent $command) + { + return "drop index {$command->index}"; + } + + /** + * Compile a drop foreign key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropForeign(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + return "alter table {$table} drop constraint {$command->index}"; + } + + /** + * Compile a rename table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileRename(Blueprint $blueprint, Fluent $command) + { + $from = $this->wrapTable($blueprint); + + return "alter table {$from} rename to ".$this->wrapTable($command->to); + } + + /** + * Create the column definition for a string type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeString(Fluent $column) + { + return "varchar({$column->length})"; + } + + /** + * Create the column definition for a text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a medium text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a long text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeLongText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeInteger(Fluent $column) + { + return $column->autoIncrement ? 'serial' : 'integer'; + } + + /** + * Create the column definition for a big integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBigInteger(Fluent $column) + { + return $column->autoIncrement ? 'bigserial' : 'bigint'; + } + + /** + * Create the column definition for a medium integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumInteger(Fluent $column) + { + return 'integer'; + } + + /** + * Create the column definition for a tiny integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyInteger(Fluent $column) + { + return 'smallint'; + } + + /** + * Create the column definition for a small integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeSmallInteger(Fluent $column) + { + return 'smallint'; + } + + /** + * Create the column definition for a float type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeFloat(Fluent $column) + { + return 'real'; + } + + /** + * Create the column definition for a decimal type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDecimal(Fluent $column) + { + return "decimal({$column->total}, {$column->places})"; + } + + /** + * Create the column definition for a boolean type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBoolean(Fluent $column) + { + return 'boolean'; + } + + /** + * Create the column definition for an enum type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeEnum(Fluent $column) + { + $allowed = array_map(function($a) { return "'".$a."'"; }, $column->allowed); + + return "varchar(255) check ({$column->name} in (".implode(', ', $allowed)."))"; + } + + /** + * Create the column definition for a date type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDate(Fluent $column) + { + return 'date'; + } + + /** + * Create the column definition for a date-time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTime(Fluent $column) + { + return 'timestamp'; + } + + /** + * Create the column definition for a time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTime(Fluent $column) + { + return 'time'; + } + + /** + * Create the column definition for a timestamp type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestamp(Fluent $column) + { + return 'timestamp'; + } + + /** + * Create the column definition for a binary type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBinary(Fluent $column) + { + return 'bytea'; + } + + /** + * Get the SQL for a nullable column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyNullable(Blueprint $blueprint, Fluent $column) + { + return $column->nullable ? ' null' : ' not null'; + } + + /** + * Get the SQL for a default column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyDefault(Blueprint $blueprint, Fluent $column) + { + if ( ! is_null($column->default)) + { + return " default ".$this->getDefaultValue($column->default); + } + } + + /** + * Get the SQL for an auto-increment column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyIncrement(Blueprint $blueprint, Fluent $column) + { + if (in_array($column->type, $this->serials) and $column->autoIncrement) + { + return ' primary key'; + } + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php new file mode 100755 index 0000000..28ee80b --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -0,0 +1,525 @@ +getColumns($blueprint)); + + $sql = 'create table '.$this->wrapTable($blueprint)." ($columns"; + + // SQLite forces primary keys to be added when the table is initially created + // so we will need to check for a primary key commands and add the columns + // to the table's declaration here so they can be created on the tables. + $sql .= (string) $this->addForeignKeys($blueprint); + + $sql .= (string) $this->addPrimaryKeys($blueprint); + + return $sql .= ')'; + } + + /** + * Get the foreign key syntax for a table creation statement. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @return string|null + */ + protected function addForeignKeys(Blueprint $blueprint) + { + $sql = ''; + + $foreigns = $this->getCommandsByName($blueprint, 'foreign'); + + // Once we have all the foreign key commands for the table creation statement + // we'll loop through each of them and add them to the create table SQL we + // are building, since SQLite needs foreign keys on the tables creation. + foreach ($foreigns as $foreign) + { + $sql .= $this->getForeignKey($foreign); + + if ( ! is_null($foreign->onDelete)) + { + $sql .= " on delete {$foreign->onDelete}"; + } + + if ( ! is_null($foreign->onUpdate)) + { + $sql .= " on update {$foreign->onUpdate}"; + } + } + + return $sql; + } + + /** + * Get the SQL for the foreign key. + * + * @param \Illuminate\Support\Fluent $foreign + * @return string + */ + protected function getForeignKey($foreign) + { + $on = $this->wrapTable($foreign->on); + + // We need to columnize the columns that the foreign key is being defined for + // so that it is a properly formatted list. Once we have done this, we can + // return the foreign key SQL declaration to the calling method for use. + $columns = $this->columnize($foreign->columns); + + $onColumns = $this->columnize((array) $foreign->references); + + return ", foreign key($columns) references $on($onColumns)"; + } + + /** + * Get the primary key syntax for a table creation statement. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @return string|null + */ + protected function addPrimaryKeys(Blueprint $blueprint) + { + $primary = $this->getCommandByName($blueprint, 'primary'); + + if ( ! is_null($primary)) + { + $columns = $this->columnize($primary->columns); + + return ", primary key ({$columns})"; + } + } + + /** + * Compile alter table commands for adding columns + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return array + */ + public function compileAdd(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + $columns = $this->prefixArray('add column', $this->getColumns($blueprint)); + + foreach ($columns as $column) + { + $statements[] = 'alter table '.$table.' '.$column; + } + + return $statements; + } + + /** + * Compile a unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileUnique(Blueprint $blueprint, Fluent $command) + { + $columns = $this->columnize($command->columns); + + $table = $this->wrapTable($blueprint); + + return "create unique index {$command->index} on {$table} ({$columns})"; + } + + /** + * Compile a plain index key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileIndex(Blueprint $blueprint, Fluent $command) + { + $columns = $this->columnize($command->columns); + + $table = $this->wrapTable($blueprint); + + return "create index {$command->index} on {$table} ({$columns})"; + } + + /** + * Compile a foreign key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileForeign(Blueprint $blueprint, Fluent $command) + { + // Handled on table creation... + } + + /** + * Compile a drop table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDrop(Blueprint $blueprint, Fluent $command) + { + return 'drop table '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop table (if exists) command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIfExists(Blueprint $blueprint, Fluent $command) + { + return 'drop table if exists '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop column command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @param \Illuminate\Database\Connection $connection + * @return array + */ + public function compileDropColumn(Blueprint $blueprint, Fluent $command, Connection $connection) + { + $schema = $connection->getDoctrineSchemaManager(); + + $tableDiff = $this->getDoctrineTableDiff($blueprint, $schema); + + foreach ($command->columns as $name) + { + $column = $connection->getDoctrineColumn($blueprint->getTable(), $name); + + $tableDiff->removedColumns[$name] = $column; + } + + return (array) $schema->getDatabasePlatform()->getAlterTableSQL($tableDiff); + } + + /** + * Compile a drop unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropUnique(Blueprint $blueprint, Fluent $command) + { + return "drop index {$command->index}"; + } + + /** + * Compile a drop index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIndex(Blueprint $blueprint, Fluent $command) + { + return "drop index {$command->index}"; + } + + /** + * Compile a rename table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileRename(Blueprint $blueprint, Fluent $command) + { + $from = $this->wrapTable($blueprint); + + return "alter table {$from} rename to ".$this->wrapTable($command->to); + } + + /** + * Create the column definition for a string type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeString(Fluent $column) + { + return 'varchar'; + } + + /** + * Create the column definition for a text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a medium text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a long text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeLongText(Fluent $column) + { + return 'text'; + } + + /** + * Create the column definition for a integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeInteger(Fluent $column) + { + return 'integer'; + } + + /** + * Create the column definition for a big integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBigInteger(Fluent $column) + { + return 'integer'; + } + + /** + * Create the column definition for a medium integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumInteger(Fluent $column) + { + return 'integer'; + } + + /** + * Create the column definition for a tiny integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyInteger(Fluent $column) + { + return 'integer'; + } + + /** + * Create the column definition for a small integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeSmallInteger(Fluent $column) + { + return 'integer'; + } + + /** + * Create the column definition for a float type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeFloat(Fluent $column) + { + return 'float'; + } + + /** + * Create the column definition for a decimal type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDecimal(Fluent $column) + { + return 'float'; + } + + /** + * Create the column definition for a boolean type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBoolean(Fluent $column) + { + return 'tinyint'; + } + + /** + * Create the column definition for a enum type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeEnum(Fluent $column) + { + return 'varchar'; + } + + /** + * Create the column definition for a date type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDate(Fluent $column) + { + return 'date'; + } + + /** + * Create the column definition for a date-time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTime(Fluent $column) + { + return 'datetime'; + } + + /** + * Create the column definition for a time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTime(Fluent $column) + { + return 'time'; + } + + /** + * Create the column definition for a timestamp type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestamp(Fluent $column) + { + return 'datetime'; + } + + /** + * Create the column definition for a binary type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBinary(Fluent $column) + { + return 'blob'; + } + + /** + * Get the SQL for a nullable column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyNullable(Blueprint $blueprint, Fluent $column) + { + return ' null'; + } + + /** + * Get the SQL for a default column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyDefault(Blueprint $blueprint, Fluent $column) + { + if ( ! is_null($column->default)) + { + return " default ".$this->getDefaultValue($column->default); + } + } + + /** + * Get the SQL for an auto-increment column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyIncrement(Blueprint $blueprint, Fluent $column) + { + if (in_array($column->type, $this->serials) and $column->autoIncrement) + { + return ' primary key autoincrement'; + } + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php new file mode 100755 index 0000000..89d6265 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -0,0 +1,457 @@ +getColumns($blueprint)); + + return 'create table '.$this->wrapTable($blueprint)." ($columns)"; + } + + /** + * Compile a create table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileAdd(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + $columns = $this->getColumns($blueprint); + + return 'alter table '.$table.' add '.implode(', ', $columns); + } + + /** + * Compile a primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compilePrimary(Blueprint $blueprint, Fluent $command) + { + $columns = $this->columnize($command->columns); + + $table = $this->wrapTable($blueprint); + + return "alter table {$table} add constraint {$command->index} primary key ({$columns})"; + } + + /** + * Compile a unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileUnique(Blueprint $blueprint, Fluent $command) + { + $columns = $this->columnize($command->columns); + + $table = $this->wrapTable($blueprint); + + return "create unique index {$command->index} on {$table} ({$columns})"; + } + + /** + * Compile a plain index key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileIndex(Blueprint $blueprint, Fluent $command) + { + $columns = $this->columnize($command->columns); + + $table = $this->wrapTable($blueprint); + + return "create index {$command->index} on {$table} ({$columns})"; + } + + /** + * Compile a drop table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDrop(Blueprint $blueprint, Fluent $command) + { + return 'drop table '.$this->wrapTable($blueprint); + } + + /** + * Compile a drop column command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropColumn(Blueprint $blueprint, Fluent $command) + { + $columns = $this->wrapArray($command->columns); + + $table = $this->wrapTable($blueprint); + + return 'alter table '.$table.' drop column '.implode(', ', $columns); + } + + /** + * Compile a drop primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropPrimary(Blueprint $blueprint, Fluent $command) + { + $table = $blueprint->getTable(); + + $table = $this->wrapTable($blueprint); + + return "alter table {$table} drop constraint {$command->index}"; + } + + /** + * Compile a drop unique key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropUnique(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + return "drop index {$command->index} on {$table}"; + } + + /** + * Compile a drop index command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropIndex(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + return "drop index {$command->index} on {$table}"; + } + + /** + * Compile a drop foreign key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropForeign(Blueprint $blueprint, Fluent $command) + { + $table = $this->wrapTable($blueprint); + + return "alter table {$table} drop constraint {$command->index}"; + } + + /** + * Compile a rename table command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileRename(Blueprint $blueprint, Fluent $command) + { + $from = $this->wrapTable($blueprint); + + return "sp_rename {$from}, ".$this->wrapTable($command->to); + } + + /** + * Create the column definition for a string type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeString(Fluent $column) + { + return "nvarchar({$column->length})"; + } + + /** + * Create the column definition for a text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeText(Fluent $column) + { + return 'nvarchar(max)'; + } + + /** + * Create the column definition for a medium text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumText(Fluent $column) + { + return 'nvarchar(max)'; + } + + /** + * Create the column definition for a long text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeLongText(Fluent $column) + { + return 'nvarchar(max)'; + } + + /** + * Create the column definition for a integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeInteger(Fluent $column) + { + return 'int'; + } + + /** + * Create the column definition for a big integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBigInteger(Fluent $column) + { + return 'bigint'; + } + + /** + * Create the column definition for a medium integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeMediumInteger(Fluent $column) + { + return 'int'; + } + + /** + * Create the column definition for a tiny integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyInteger(Fluent $column) + { + return 'tinyint'; + } + + /** + * Create the column definition for a small integer type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeSmallInteger(Fluent $column) + { + return 'smallint'; + } + + /** + * Create the column definition for a float type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeFloat(Fluent $column) + { + return 'float'; + } + + /** + * Create the column definition for a decimal type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDecimal(Fluent $column) + { + return "decimal({$column->total}, {$column->places})"; + } + + /** + * Create the column definition for a boolean type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBoolean(Fluent $column) + { + return 'tinyint'; + } + + /** + * Create the column definition for a enum type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeEnum(Fluent $column) + { + return 'nvarchar(255)'; + } + + /** + * Create the column definition for a date type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDate(Fluent $column) + { + return 'date'; + } + + /** + * Create the column definition for a date-time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeDateTime(Fluent $column) + { + return 'datetime'; + } + + /** + * Create the column definition for a time type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTime(Fluent $column) + { + return 'time'; + } + + /** + * Create the column definition for a timestamp type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTimestamp(Fluent $column) + { + return 'datetime'; + } + + /** + * Create the column definition for a binary type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeBinary(Fluent $column) + { + return 'varbinary(max)'; + } + + /** + * Get the SQL for a nullable column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyNullable(Blueprint $blueprint, Fluent $column) + { + return $column->nullable ? ' null' : ' not null'; + } + + /** + * Get the SQL for a default column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyDefault(Blueprint $blueprint, Fluent $column) + { + if ( ! is_null($column->default)) + { + return " default ".$this->getDefaultValue($column->default); + } + } + + /** + * Get the SQL for an auto-increment column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyIncrement(Blueprint $blueprint, Fluent $column) + { + if (in_array($column->type, $this->serials) and $column->autoIncrement) + { + return ' identity primary key'; + } + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Database/Schema/MySqlBuilder.php b/vendor/laravel/framework/src/Illuminate/Database/Schema/MySqlBuilder.php new file mode 100755 index 0000000..ca6e56f --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Schema/MySqlBuilder.php @@ -0,0 +1,22 @@ +grammar->compileTableExists(); + + $database = $this->connection->getDatabaseName(); + + $table = $this->connection->getTablePrefix().$table; + + return count($this->connection->select($sql, array($database, $table))) > 0; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/SeedServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Database/SeedServiceProvider.php new file mode 100755 index 0000000..a94706e --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/SeedServiceProvider.php @@ -0,0 +1,55 @@ +registerSeedCommand(); + + $this->app['seeder'] = $this->app->share(function($app) + { + return new Seeder; + }); + + $this->commands('command.seed'); + } + + /** + * Register the seed console command. + * + * @return void + */ + protected function registerSeedCommand() + { + $this->app['command.seed'] = $this->app->share(function($app) + { + return new SeedCommand($app['db']); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('seeder', 'command.seed'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/Seeder.php b/vendor/laravel/framework/src/Illuminate/Database/Seeder.php new file mode 100755 index 0000000..8a0dc7f --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/Seeder.php @@ -0,0 +1,87 @@ +resolve($class)->run(); + } + + /** + * Resolve an instance of the given seeder class. + * + * @param string $class + * @return \Illuminate\Database\Seeder + */ + protected function resolve($class) + { + if (isset($this->container)) + { + $instance = $this->container->make($class); + + return $instance->setContainer($this->container)->setCommand($this->command); + } + else + { + return new $class; + } + } + + /** + * Set the IoC container instance. + * + * @param \Illuminate\Container\Container $container + * @return void + */ + public function setContainer(Container $container) + { + $this->container = $container; + + return $this; + } + + /** + * Set the console command instance. + * + * @param \Illuminate\Console\Command $command + * @return void + */ + public function setCommand(Command $command) + { + $this->command = $command; + + return $this; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/SqlServerConnection.php b/vendor/laravel/framework/src/Illuminate/Database/SqlServerConnection.php new file mode 100755 index 0000000..fb4839d --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/SqlServerConnection.php @@ -0,0 +1,80 @@ +pdo->exec('BEGIN TRAN'); + + // We'll simply execute the given callback within a try / catch block + // and if we catch any exception we can rollback the transaction + // so that none of the changes are persisted to the database. + try + { + $result = $callback($this); + + $this->pdo->exec('COMMIT TRAN'); + } + + // If we catch an exception, we will roll back so nothing gets messed + // up in the database. Then we'll re-throw the exception so it can + // be handled how the developer sees fit for their applications. + catch (\Exception $e) + { + $this->pdo->exec('ROLLBACK TRAN'); + + throw $e; + } + + return $result; + } + + /** + * Get the default query grammar instance. + * + * @return \Illuminate\Database\Query\Grammars\Grammars\Grammar + */ + protected function getDefaultQueryGrammar() + { + return $this->withTablePrefix(new Query\Grammars\SqlServerGrammar); + } + + /** + * Get the default schema grammar instance. + * + * @return \Illuminate\Database\Schema\Grammars\Grammar + */ + protected function getDefaultSchemaGrammar() + { + return $this->withTablePrefix(new Schema\Grammars\SqlServerGrammar); + } + + /** + * Get the Doctrine DBAL Driver. + * + * @return \Doctrine\DBAL\Driver + */ + protected function getDoctrineDriver() + { + return new \Doctrine\DBAL\Driver\PDOSqlsrv\Driver; + } + + /** + * Get the default post processor instance. + * + * @return \Illuminate\Database\Query\Processors\Processor + */ + protected function getDefaultPostProcessor() + { + return new Query\Processors\SqlServerProcessor; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Database/composer.json b/vendor/laravel/framework/src/Illuminate/Database/composer.json new file mode 100755 index 0000000..11de299 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Database/composer.json @@ -0,0 +1,39 @@ +{ + "name": "illuminate/database", + "license": "MIT", + "keywords": ["laravel", "database", "sql", "orm"], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "illuminate/container": "4.0.x", + "illuminate/events": "4.0.x", + "illuminate/support": "4.0.x", + "nesbot/carbon": "1.*" + }, + "require-dev": { + "illuminate/cache": "4.0.x", + "illuminate/console": "4.0.x", + "illuminate/filesystem": "4.0.x", + "illuminate/pagination": "4.0.x", + "illuminate/support": "4.0.x", + "mockery/mockery": "0.7.2", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Database": "" + } + }, + "target-dir": "Illuminate/Database", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/laravel/framework/src/Illuminate/Encryption/Encrypter.php b/vendor/laravel/framework/src/Illuminate/Encryption/Encrypter.php new file mode 100755 index 0000000..a528375 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Encryption/Encrypter.php @@ -0,0 +1,271 @@ +key = $key; + } + + /** + * Encrypt the given value. + * + * @param string $value + * @return string + */ + public function encrypt($value) + { + $iv = mcrypt_create_iv($this->getIvSize(), $this->getRandomizer()); + + $value = base64_encode($this->padAndMcrypt($value, $iv)); + + // Once we have the encrypted value we will go ahead base64_encode the input + // vector and create the MAC for the encrypted value so we can verify its + // authenticity. Then, we'll JSON encode the data in a "payload" array. + $mac = $this->hash($iv = base64_encode($iv), $value); + + return base64_encode(json_encode(compact('iv', 'value', 'mac'))); + } + + /** + * Pad and use mcrypt on the given value and input vector. + * + * @param string $value + * @param string $iv + * @return string + */ + protected function padAndMcrypt($value, $iv) + { + $value = $this->addPadding(serialize($value)); + + return mcrypt_encrypt($this->cipher, $this->key, $value, $this->mode, $iv); + } + + /** + * Decrypt the given value. + * + * @param string $payload + * @return string + */ + public function decrypt($payload) + { + $payload = $this->getJsonPayload($payload); + + // We'll go ahead and remove the PKCS7 padding from the encrypted value before + // we decrypt it. Once we have the de-padded value, we will grab the vector + // and decrypt the data, passing back the unserialized from of the value. + $value = base64_decode($payload['value']); + + $iv = base64_decode($payload['iv']); + + return unserialize($this->stripPadding($this->mcryptDecrypt($value, $iv))); + } + + /** + * Run the mcrypt decryption routine for the value. + * + * @param string $value + * @param string $iv + * @return string + */ + protected function mcryptDecrypt($value, $iv) + { + return mcrypt_decrypt($this->cipher, $this->key, $value, $this->mode, $iv); + } + + /** + * Get the JSON array from the given payload. + * + * @param string $payload + * @return array + */ + protected function getJsonPayload($payload) + { + $payload = json_decode(base64_decode($payload), true); + + // If the payload is not valid JSON or does not have the proper keys set we will + // assume it is invalid and bail out of the routine since we will not be able + // to decrypt the given value. We'll also check the MAC for this encryption. + if ( ! $payload or $this->invalidPayload($payload)) + { + throw new DecryptException("Invalid data."); + } + + if ( ! $this->validMac($payload)) + { + throw new DecryptException("MAC is invalid."); + } + + return $payload; + } + + /** + * Determine if the MAC for the given payload is valid. + * + * @param array $payload + * @return bool + */ + protected function validMac(array $payload) + { + return ($payload['mac'] == $this->hash($payload['iv'], $payload['value'])); + } + + /** + * Create a MAC for the given value. + * + * @param string $iv + * @param string $value + * @return string + */ + protected function hash($iv, $value) + { + return hash_hmac('sha256', $iv.$value, $this->key); + } + + /** + * Add PKCS7 padding to a given value. + * + * @param string $value + * @return string + */ + protected function addPadding($value) + { + $pad = $this->block - (strlen($value) % $this->block); + + return $value.str_repeat(chr($pad), $pad); + } + + /** + * Remove the padding from the given value. + * + * @param string $value + * @return string + */ + protected function stripPadding($value) + { + $pad = ord($value[($len = strlen($value)) - 1]); + + return $this->paddingIsValid($pad, $value) ? substr($value, 0, strlen($value) - $pad) : $value; + } + + /** + * Determine if the given padding for a value is valid. + * + * @param string $pad + * @param string $value + * @return bool + */ + protected function paddingIsValid($pad, $value) + { + $beforePad = strlen($value) - $pad; + + return substr($value, $beforePad) == str_repeat(substr($value, -1), $pad); + } + + /** + * Verify that the encryption payload is valid. + * + * @param array $data + * @return bool + */ + protected function invalidPayload(array $data) + { + return ! isset($data['iv']) or ! isset($data['value']) or ! isset($data['mac']); + } + + /** + * Get the IV size for the cipher. + * + * @return int + */ + protected function getIvSize() + { + return mcrypt_get_iv_size($this->cipher, $this->mode); + } + + /** + * Get the random data source available for the OS. + * + * @return int + */ + protected function getRandomizer() + { + if (defined('MCRYPT_DEV_URANDOM')) return MCRYPT_DEV_URANDOM; + + if (defined('MCRYPT_DEV_RANDOM')) return MCRYPT_DEV_RANDOM; + + mt_srand(); + + return MCRYPT_RAND; + } + + /** + * Set the encryption key. + * + * @param string $key + * @return void + */ + public function setKey($key) + { + $this->key = $key; + } + + /** + * Set the encryption cipher. + * + * @param string $cipher + * @return void + */ + public function setCipher($cipher) + { + $this->cipher = $cipher; + } + + /** + * Set the encryption mode. + * + * @param string $mode + * @return void + */ + public function setMode($mode) + { + $this->mode = $mode; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Encryption/EncryptionServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Encryption/EncryptionServiceProvider.php new file mode 100755 index 0000000..5c3a918 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Encryption/EncryptionServiceProvider.php @@ -0,0 +1,20 @@ +app['encrypter'] = $this->app->share(function($app) + { + return new Encrypter($app['config']['app.key']); + }); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Encryption/composer.json b/vendor/laravel/framework/src/Illuminate/Encryption/composer.json new file mode 100755 index 0000000..20ddbee --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Encryption/composer.json @@ -0,0 +1,29 @@ +{ + "name": "illuminate/encryption", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "illuminate/support": "4.0.x" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Encryption": "" + } + }, + "target-dir": "Illuminate/Encryption", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php b/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php new file mode 100755 index 0000000..474c8df --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php @@ -0,0 +1,317 @@ +container = $container ?: new Container; + } + + /** + * Register an event listener with the dispatcher. + * + * @param string $event + * @param mixed $listener + * @param int $priority + * @return void + */ + public function listen($event, $listener, $priority = 0) + { + if (str_contains($event, '*')) + { + return $this->setupWildcardListen($event, $listener, $priority = 0); + } + + $this->listeners[$event][$priority][] = $this->makeListener($listener); + + unset($this->sorted[$event]); + } + + /** + * Setup a wildcard listener callback. + * + * @param string $event + * @param mixed $listener + * @param int $priority + * @return void + */ + protected function setupWildcardListen($event, $listener, $priority) + { + $this->wildcards[$event][] = $this->makeListener($listener); + } + + /** + * Determine if a given event has listeners. + * + * @param string $eventName + * @return bool + */ + public function hasListeners($eventName) + { + return isset($this->listeners[$eventName]); + } + + /** + * Register a queued event and payload. + * + * @param string $event + * @param array $payload + * @return void + */ + public function queue($event, $payload = array()) + { + $me = $this; + + $this->listen($event.'_queue', function() use ($me, $event, $payload) + { + $me->fire($event, $payload); + }); + } + + /** + * Register an event subscriber with the dispatcher. + * + * @param string $subscriber + * @return void + */ + public function subscribe($subscriber) + { + $subscriber = $this->resolveSubscriber($subscriber); + + $subscriber->subscribe($this); + } + + /** + * Resolve the subscriber instance. + * + * @param mixed $subscriber + * @return mixed + */ + protected function resolveSubscriber($subscriber) + { + if (is_string($subscriber)) + { + return $this->container->make($subscriber); + } + + return $subscriber; + } + + /** + * Fire an event until the first non-null response is returned. + * + * @param string $event + * @param array $payload + * @return mixed + */ + public function until($event, $payload = array()) + { + return $this->fire($event, $payload, true); + } + + /** + * Flush a set of queued events. + * + * @param string $event + * @return void + */ + public function flush($event) + { + $this->fire($event.'_queue'); + } + + /** + * Fire an event and call the listeners. + * + * @param string $event + * @param mixed $payload + * @param bool $halt + * @return array|null + */ + public function fire($event, $payload = array(), $halt = false) + { + $responses = array(); + + // If an array is not given to us as the payload, we will turn it into one so + // we can easily use call_user_func_array on the listeners, passing in the + // payload to each of them so that they receive each of these arguments. + if ( ! is_array($payload)) $payload = array($payload); + + $payload[] = $event; + + foreach ($this->getListeners($event) as $listener) + { + $response = call_user_func_array($listener, $payload); + + // If a response is returned from the listener and event halting is enabled + // we will just return this response, and not call the rest of the event + // listeners. Otherwise we will add the response on the response list. + if ( ! is_null($response) and $halt) + { + return $response; + } + + // If a boolean false is returned from a listener, we will stop propogating + // the event to any further listeners down in the chain, else we keep on + // looping through the listeners and firing every one in our sequence. + if ($response === false) break; + + $responses[] = $response; + } + + return $halt ? null : $responses; + } + + /** + * Get all of the listeners for a given event name. + * + * @param string $eventName + * @return array + */ + public function getListeners($eventName) + { + $wildcards = $this->getWildcardListeners($eventName); + + if ( ! isset($this->sorted[$eventName])) + { + $this->sortListeners($eventName); + } + + return array_merge($this->sorted[$eventName], $wildcards); + } + + /** + * Get the wildcard listeners for the event. + * + * @param string $eventName + * @return array + */ + protected function getWildcardListeners($eventName) + { + $wildcards = array(); + + foreach ($this->wildcards as $key => $listeners) + { + if (str_is($key, $eventName)) $wildcards = array_merge($wildcards, $listeners); + } + + return $wildcards; + } + + /** + * Sort the listeners for a given event by priority. + * + * @param string $eventName + * @return array + */ + protected function sortListeners($eventName) + { + $this->sorted[$eventName] = array(); + + // If listeners exist for the given event, we will sort them by the priority + // so that we can call them in the correct order. We will cache off these + // sorted event listeners so we do not have to re-sort on every events. + if (isset($this->listeners[$eventName])) + { + krsort($this->listeners[$eventName]); + + $this->sorted[$eventName] = call_user_func_array('array_merge', $this->listeners[$eventName]); + } + } + + /** + * Register an event listener with the dispatcher. + * + * @param mixed $listener + * @return mixed + */ + public function makeListener($listener) + { + if (is_string($listener)) + { + $listener = $this->createClassListener($listener); + } + + return $listener; + } + + /** + * Create a class based listener using the IoC container. + * + * @param mixed $listener + * @return Closure + */ + public function createClassListener($listener) + { + $container = $this->container; + + return function() use ($listener, $container) + { + // If the listener has an @ sign, we will assume it is being used to delimit + // the class name from the handle method name. This allows for handlers + // to run multiple handler methods in a single class for convenience. + $segments = explode('@', $listener); + + $method = count($segments) == 2 ? $segments[1] : 'handle'; + + $callable = array($container->make($segments[0]), $method); + + // We will make a callable of the listener instance and a method that should + // be called on that instance, then we will pass in the arguments that we + // received in this method into this listener class instance's methods. + $data = func_get_args(); + + return call_user_func_array($callable, $data); + }; + } + + /** + * Remove a set of listeners from the dispatcher. + * + * @param string $event + * @return void + */ + public function forget($event) + { + unset($this->listeners[$event]); + + unset($this->sorted[$event]); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Events/EventServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Events/EventServiceProvider.php new file mode 100755 index 0000000..24de6b9 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Events/EventServiceProvider.php @@ -0,0 +1,20 @@ +app['events'] = $this->app->share(function($app) + { + return new Dispatcher($app); + }); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Events/Subscriber.php b/vendor/laravel/framework/src/Illuminate/Events/Subscriber.php new file mode 100755 index 0000000..6f7b750 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Events/Subscriber.php @@ -0,0 +1,25 @@ +=5.3.0", + "illuminate/container": "4.0.x", + "illuminate/support": "4.0.x" + }, + "require-dev": { + "mockery/mockery": "0.7.2", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Events": "" + } + }, + "target-dir": "Illuminate/Events", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Exception/ExceptionDisplayerInterface.php b/vendor/laravel/framework/src/Illuminate/Exception/ExceptionDisplayerInterface.php new file mode 100755 index 0000000..4dfe743 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Exception/ExceptionDisplayerInterface.php @@ -0,0 +1,14 @@ +registerDisplayers(); + + $this->registerHandler(); + } + + /** + * Register the exception displayers. + * + * @return void + */ + protected function registerDisplayers() + { + $this->registerPlainDisplayer(); + + $this->registerDebugDisplayer(); + } + + /** + * Register the exception handler instance. + * + * @return void + */ + protected function registerHandler() + { + $this->app['exception'] = $this->app->share(function($app) + { + return new Handler($app, $app['exception.plain'], $app['exception.debug']); + }); + } + + /** + * Register the plain exception displayer. + * + * @return void + */ + protected function registerPlainDisplayer() + { + $this->app['exception.plain'] = $this->app->share(function($app) + { + $handler = new KernelHandler($app['config']['app.debug']); + + return new SymfonyDisplayer($handler); + }); + } + + /** + * Register the Whoops exception displayer. + * + * @return void + */ + protected function registerDebugDisplayer() + { + $this->registerWhoops(); + + $this->app['exception.debug'] = $this->app->share(function($app) + { + return new WhoopsDisplayer($app['whoops'], $app->runningInConsole()); + }); + } + + /** + * Register the Whoops error display service. + * + * @return void + */ + protected function registerWhoops() + { + $this->registerWhoopsHandler(); + + $this->app['whoops'] = $this->app->share(function($app) + { + // We will instruct Whoops to not exit after it displays the exception as it + // will otherwise run out before we can do anything else. We just want to + // let the framework go ahead and finish a request on this end instead. + with($whoops = new \Whoops\Run)->allowQuit(false); + + return $whoops->pushHandler($app['whoops.handler']); + }); + } + + /** + * Register the Whoops handler for the request. + * + * @return void + */ + protected function registerWhoopsHandler() + { + if ($this->shouldReturnJson()) + { + $this->app['whoops.handler'] = $this->app->share(function() + { + return new JsonResponseHandler; + }); + } + else + { + $this->registerPrettyWhoopsHandler(); + } + } + + /** + * Determine if the error provider should return JSON. + * + * @return bool + */ + protected function shouldReturnJson() + { + $definitely = ($this->app['request']->ajax() or $this->app->runningInConsole()); + + return $definitely or $this->app['request']->wantsJson(); + } + + /** + * Register the "pretty" Whoops handler. + * + * @return void + */ + protected function registerPrettyWhoopsHandler() + { + $me = $this; + + $this->app['whoops.handler'] = $this->app->share(function() use ($me) + { + with($handler = new PrettyPageHandler)->setEditor('sublime'); + + // If the resource path exists, we will register the resource path with Whoops + // so our custom Laravel branded exception pages will be used when they are + // displayed back to the developer. Otherwise, the default pages are run. + if ( ! is_null($path = $me->resourcePath())) + { + $handler->setResourcesPath($path); + } + + return $handler; + }); + } + + /** + * Get the resource path for Whoops. + * + * @return string + */ + public function resourcePath() + { + if (is_dir($path = $this->getResourcePath())) return $path; + } + + /** + * Get the Whoops custom resource path. + * + * @return string + */ + protected function getResourcePath() + { + $base = $this->app['path.base']; + + return $base.'/vendor/laravel/framework/src/Illuminate/Exception/resources'; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Exception/Handler.php b/vendor/laravel/framework/src/Illuminate/Exception/Handler.php new file mode 100755 index 0000000..b1a0888 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Exception/Handler.php @@ -0,0 +1,375 @@ +debug = $debug; + $this->plainDisplayer = $plainDisplayer; + $this->debugDisplayer = $debugDisplayer; + $this->responsePreparer = $responsePreparer; + } + + /** + * Register the exception / error handlers for the application. + * + * @param string $environment + * @return void + */ + public function register($environment) + { + $this->registerErrorHandler(); + + $this->registerExceptionHandler(); + + if ($environment != 'testing') $this->registerShutdownHandler(); + } + + /** + * Register the PHP error handler. + * + * @return void + */ + protected function registerErrorHandler() + { + set_error_handler(array($this, 'handleError')); + } + + /** + * Register the PHP exception handler. + * + * @return void + */ + protected function registerExceptionHandler() + { + set_exception_handler(array($this, 'handleException')); + } + + /** + * Register the PHP shutdown handler. + * + * @return void + */ + protected function registerShutdownHandler() + { + register_shutdown_function(array($this, 'handleShutdown')); + } + + /** + * Handle a PHP error for the application. + * + * @param int $level + * @param string $message + * @param string $file + * @param int $line + * @param array $context + */ + public function handleError($level, $message, $file, $line, $context) + { + if (error_reporting() & $level) + { + $e = new ErrorException($message, $level, 0, $file, $line); + + $this->handleException($e); + } + } + + /** + * Handle an exception for the application. + * + * @param \Exception $exception + * @return void + */ + public function handleException($exception) + { + $response = $this->callCustomHandlers($exception); + + // If one of the custom error handlers returned a response, we will send that + // response back to the client after preparing it. This allows a specific + // type of exceptions to handled by a Closure giving great flexibility. + if ( ! is_null($response)) + { + $response = $this->prepareResponse($response); + + $response->send(); + } + + // If no response was sent by this custom exception handler, we will call the + // default exception displayer for the current application context and let + // it show the exception to the user / developer based on the situation. + else + { + $this->displayException($exception); + } + + $this->bail(); + } + + /** + * Handle the PHP shutdown event. + * + * @return void + */ + public function handleShutdown() + { + $error = error_get_last(); + + // If an error has occurred that has not been displayed, we will create a fatal + // error exception instance and pass it into the regular exception handling + // code so it can be displayed back out to the developer for information. + if ( ! is_null($error)) + { + extract($error); + + if ( ! $this->isFatal($type)) return; + + $this->handleException(new FatalError($message, $type, 0, $file, $line)); + } + } + + /** + * Determine if the error type is fatal. + * + * @param int $type + * @return bool + */ + protected function isFatal($type) + { + return in_array($type, array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE)); + } + + /** + * Handle a console exception. + * + * @param Exception $exception + * @return void + */ + public function handleConsole($exception) + { + return $this->callCustomHandlers($exception, true); + } + + /** + * Handle the given exception. + * + * @param Exception $exception + * @param bool $fromConsole + * @return void + */ + protected function callCustomHandlers($exception, $fromConsole = false) + { + foreach ($this->handlers as $handler) + { + // If this exception handler does not handle the given exception, we will just + // go the next one. A handler may type-hint an exception that it handles so + // we can have more granularity on the error handling for the developer. + if ( ! $this->handlesException($handler, $exception)) + { + continue; + } + elseif ($exception instanceof HttpExceptionInterface) + { + $code = $exception->getStatusCode(); + } + + // If the exception doesn't implement the HttpExceptionInterface, we will just + // use the generic 500 error code for a server side error. If it implements + // the HttpException interfaces we'll grab the error code from the class. + else + { + $code = 500; + } + + // We will wrap this handler in a try / catch and avoid white screens of death + // if any exceptions are thrown from a handler itself. This way we will get + // at least some errors, and avoid errors with no data or not log writes. + try + { + $response = $handler($exception, $code, $fromConsole); + } + catch (\Exception $e) + { + $response = $this->formatException($e); + } + + // If this handler returns a "non-null" response, we will return it so it will + // get sent back to the browsers. Once the handler returns a valid response + // we will cease iterating through them and calling these other handlers. + if (isset($response) and ! is_null($response)) + { + return $response; + } + } + } + + /** + * Display the given exception to the user. + * + * @param \Exception $exception + * @return void + */ + protected function displayException($exception) + { + $displayer = $this->debug ? $this->debugDisplayer : $this->plainDisplayer; + + $displayer->display($exception); + } + + /** + * Determine if the given handler handles this exception. + * + * @param Closure $handler + * @param Exception $exception + * @return bool + */ + protected function handlesException(Closure $handler, $exception) + { + $reflection = new ReflectionFunction($handler); + + return $reflection->getNumberOfParameters() == 0 or $this->hints($reflection, $exception); + } + + /** + * Determine if the given handler type hints the exception. + * + * @param ReflectionFunction $reflection + * @param Exception $exception + * @return bool + */ + protected function hints(ReflectionFunction $reflection, $exception) + { + $parameters = $reflection->getParameters(); + + $expected = $parameters[0]; + + return ! $expected->getClass() or $expected->getClass()->isInstance($exception); + } + + /** + * Format an exception thrown by a handler. + * + * @param Exception $e + * @return string + */ + protected function formatException(\Exception $e) + { + $location = $e->getMessage().' in '.$e->getFile().':'.$e->getLine(); + + return 'Error in exception handler: '.$location; + } + + /** + * Register an application error handler. + * + * @param Closure $callback + * @return void + */ + public function error(Closure $callback) + { + array_unshift($this->handlers, $callback); + } + + /** + * Register an application error handler at the bottom of the stack. + * + * @param Closure $callback + * @return void + */ + public function pushError(Closure $callback) + { + $this->handlers[] = $callback; + } + + /** + * Prepare the given response. + * + * @param mixed $response + * @return \Illuminate\Http\Response + */ + protected function prepareResponse($response) + { + return $this->responsePreparer->prepareResponse($response); + } + + /** + * Exit the application. + * + * @return void + */ + protected function bail() + { + exit(1); + } + + /** + * Set the debug level for the handler. + * + * @param bool $debug + * @return void + */ + public function setDebug($debug) + { + $this->debug = $debug; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Exception/SymfonyDisplayer.php b/vendor/laravel/framework/src/Illuminate/Exception/SymfonyDisplayer.php new file mode 100755 index 0000000..71556a3 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Exception/SymfonyDisplayer.php @@ -0,0 +1,36 @@ +symfony = $symfony; + } + + /** + * Display the given exception to the user. + * + * @param \Exception $exception + */ + public function display(Exception $exception) + { + $this->symfony->handle($exception); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Exception/WhoopsDisplayer.php b/vendor/laravel/framework/src/Illuminate/Exception/WhoopsDisplayer.php new file mode 100755 index 0000000..e67b1d9 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Exception/WhoopsDisplayer.php @@ -0,0 +1,50 @@ +whoops = $whoops; + $this->runningInConsole = $runningInConsole; + } + + /** + * Display the given exception to the user. + * + * @param \Exception $exception + */ + public function display(Exception $exception) + { + if ( ! $this->runningInConsole and ! headers_sent()) + { + header('HTTP/1.1 500 Internal Server Error'); + } + + $this->whoops->handleException($exception); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Exception/composer.json b/vendor/laravel/framework/src/Illuminate/Exception/composer.json new file mode 100755 index 0000000..0401f05 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Exception/composer.json @@ -0,0 +1,33 @@ +{ + "name": "illuminate/exception", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "filp/whoops": "1.0.7", + "illuminate/support": "4.0.x", + "symfony/http-foundation": "2.3.x", + "symfony/http-kernel": "2.3.*" + }, + "require-dev": { + "mockery/mockery": "0.7.2", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Exception": "" + } + }, + "target-dir": "Illuminate/Exception", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/laravel/framework/src/Illuminate/Exception/resources/pretty-page.css b/vendor/laravel/framework/src/Illuminate/Exception/resources/pretty-page.css new file mode 100755 index 0000000..26ef1e5 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Exception/resources/pretty-page.css @@ -0,0 +1,309 @@ +.cf:before, .cf:after {content: " ";display: table;} .cf:after {clear: both;} .cf {*zoom: 1;} +body { + font: 14px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif; + color: #2B2B2B; + background-color: #e7e7e7; + padding:0; + margin: 0; + max-height: 100%; +} + a { + text-decoration: none; + color: #FE8A59; + } + +.container{ + height: 100%; + width: 100%; + position: fixed; + margin: 0; + padding: 0; + left: 0; + top: 0; +} + +.branding { + position: absolute; + top: 10px; + right: 20px; + color: #777777; + font-size: 10px; + z-index: 100; +} + .branding a { + color: #CD3F3F; + } + +header { + padding: 20px 20px; + color: #555; + background: #ddd; + box-sizing: border-box; + border-left: 5px solid #ED3D1A; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQMAAACWCAYAAADaDe2PAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpBNTk1RDY3OTlBRkQxMUUyOUM0M0EzNzM1OTdBRTY0NyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpBNTk1RDY3QTlBRkQxMUUyOUM0M0EzNzM1OTdBRTY0NyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkE1OTVENjc3OUFGRDExRTI5QzQzQTM3MzU5N0FFNjQ3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkE1OTVENjc4OUFGRDExRTI5QzQzQTM3MzU5N0FFNjQ3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+GLAjkgAAEaxJREFUeNrsXet22zYTXFHUXXZT93nyo0/eH3mfpI5lybpQrPAd7GeEEYGFRBEXzpyjY7eRbYrEDmYHi8WormuS4O+vX+nBGOmvhf5+ZHxvfi2M/+b3K5wvr+ry2l1eBwKAiPDPt2/RX2PZY3C3BXTz68j4WR8U+vPMLq/N5bXFEASAx5BB28x9LbjN7+nG4L4Ha60OTnjEAOBJBpc0YHz5Mr4S4M0gDxHct+D58vpxedV4zAAgIIMLCYz0TDpPJMh9iG55eb3jMQOALM9WRLDIjAgYK3qsLwIAWc2ek0ivrdavc+Nr1fhvRWRTy+950ukCAAAOMhgFDvDm6xoB2KDI4cXy7xOdAn3gcQOAnQxUMI0fHODnK+/rCicd6HPLe1QqtCeYiQBgJYODQ2YzjkZAXZvFQwaaMglnFpWjvBGYiQBgQeExYyrSUIU8O/0zRz0rnyn8jKvUzcbxnuWdCggA8iaDf759q0hWvhv70uNOE1QbeAkVAIAWZUAkK90dO/LyGOBSBzP9AgCghQyOjlnVlNoxqwP1GVyrBmvKs6YCAO4nA72jKhd1oEzC2vEZlnj0AHBdGSgoU1CysWcV+cxakXvVAJWJANBGBh7qQP3MIvLPpczEs+M9T3j8AHBdGbA6qDLwDlSa4DITVWUizEQAuEYGF3VQ61lV8nOxewfKSHSZorGnPAAQTBmwOpAUEc0T+HwudVAmkPIAQBgy0EVIkk09Kcjso0DpwEwEgBZlQMJUgb2D2KFWFmxmIioTAaCNDC7q4KTThRzUwZncS41TgpkIgAxaIe0unII6UErHVUOBykQAZNCiDqQlyhOSbYEODRe5oTIRABk48m0JUsi5lSnq2p25ImxzBkAGV3EQqoMykZx7I3gPzEQAZHAlVcjNOziRe6VklkjaAwC9KgNWB5INTKl4B66lRlYHMBMBkEFDHdQk7yycQlWiZKkRlYkAyKAFigykJcopVPO5WqQpoDIRABlcUQdnklclrhL57G+Of0dlIgAysMymEnUwS2RGlVRZojIRABlcQUWyEmWFVIp3JHUUKwwTAGTwO6TLjMo7SKF45yT4TDATAZDBjdKakUq+/U7u7k5rQmViyhhpUp/qV4Fbcn3W88VWmEezd3CK/B7UmhCeHYNJEcIrhkz0QT/Wr7Lx1awb4eXlHW7ZfWSgluSk5zMq7+BnAvfhQ6cCEwe5TUl2+hTw+KAvrgT8WDjrq/c86Z97w+28nQxYHUjIYC6U4TFADYoXQbrwg3Cac99BzwHvG/QuLIxnDzK48ed4A9PEgxBiB+9bWDju14LkRiogQ2G8moH/6Px+QbKqVJCBBTshGSxJdo5BDNiQ+4DZFcn3awD2nN6c6UPuBVlp9foBMrgNex3ghWAALBJh3lrP+ivH51H/DjNRPusrgp31NNPbnm1lKJAmnrTarUAGt93cHcmKclJSB1ty10nMtCo6ItatUPfwC/W/LHvWQV1pBcdBzpOXWjmaXiH5P2jAntC9ZcM7kp2ulJo62OiBYQObiYCdNB9NBGbQ86uyBLQiBLXC9XJFIZSaKF5BBrcxMC/LucDGWwqsuyf38ulEfyasVbejK+VUGzK/GfznG8YUE8KXFgJb0QANxS42FLGsdqmDQquIVG4yLzWOHOrgMOQ8U0AGW/Lbq1Ibs7s5098S9DYc9DO+dgDvSv+9HcjAX6ZJ1UFK3kFFMjMRlYl2vGsVNXEE5ocx0/c1PnbUvvfkyfAbBoGunF3puvuI0tr0sxUMTPRMdM/0b45Z/dQggz6xsQT8Mw2o/V1XZOCzvXmR0A2WHO3O6QJ6JtqDfeNQjNOAz/hnCwmNyb5nBWRwpzooKK2GIXuBJ4BtzjJJbpswnilcDUJF7SXJMxpIT4subz5vYJJAshwZm8x1YUXYGiuR5LVlLIachffUbm6vhkD2XQ9eaTlnKoeuMA6CNAg9E++bgUmnCiFn4XfLc36izL2hrslAIqlNdZDLrMZQS6wTxLxzwvhwKKyQ9/CnZQw/U8ZNbromg9rDO0hNHVTCzwZ14MabY9J4CphGqjH82kL8Rc7P9xE5rmJ96fJQasaMZKlxQjATJQH30zFRhAy6Ew3QUCwe9KB91ME8sUEsMRPRM9GNI9mXGxeBx8YHDcxQfJT77VNlmJp3sCeYiV0qrYMjXSgDX9/Rcm1TkIFsBpXWdafmHRDJ9legMlHuH9QWUg1ZBWgrSCLKzFB85Lq49HzGFL0Dact4HMDiRuUg19D+QUXt/kZBGZUsFw++iT51B6ktyUmWGieUlicSUo7vHf5BSJV1oHZ/Y5IL6Rc9PORc1YFrRjNzS1QmumFb32dJHvI+bqndGF9SBoZi0UPASEuUpwmqgx25G6PCTJTn52+OsfoUgRrM1lAsegoYKZYJDmDJrkZUJsrluE1tzSIYIzbDM2lDsejpAUvbiqdynHvz80nMxCfEugjvjvGyDkysJ8rUUOwrB/M5dGSV6AB2eSNlgson5OxLEfsHth2Ok1SJv68b+uGpDlKT1JKj3ZnoUJnohqs6cRxBwL1T+2rZPEXi75NdfdRBijPoTqAORlAHXuPl4Jg0Qi/bvlG7oajSmSnIoF1aSbc3p+gdSM/rWyT42UIGm62s/Smw0qrJ3b1pDDK4fuNyXllgdSBJh2AmyuBqhsLlyn1ipNNYNT6/kH3ZOClDsQgQLNINTDNKr1hHutSIykQ/Rblz3Mv1g2OEg1+dsvXX5fWnRxrwP0Px769fo7/RZYBg2QofHufXm8QGL58B4Ar2J/3eM+LdiY0OqtKiIrnd+r3gE6Inxt+8d2af6+cc9VgOMfP6bGBaUJqlvJKlRlQmdqu4bvUPCj3Dr/WM/6IVwFKTQVcSP/py5RCBdib5GQupHbpi5roSMxGViX6K670D/6AZ/H/p3L/r4L82JpAmXMHWI2dO6Ui2pj+yEMxWakb7jlgXjxvbHpZrh+EWhuyfdiT7fUngQH5L64MigxO5TzluqoPUTsWt9TU/C57BvKN8dwjpwpue0dsCeq3fNzby/j6D/2yM76P+vv7n2zcCGdjzamlRRqrqgA+kdaUCa5061Yh30UTyStePU/dJF7okKA58PpE7yecYkgyOA1AHChs9k7ny2DXJmq0OGSM9Zsc64EKs3/OR8Tx+QxwWmx0ZmDlgzurgSLKlRs51T4j534J/YrxCmN6VEfxHSsAMTJEMmFlL4cBIVR2oa54JZjJlJv5A8P/fJCwDBX9tBH/S0j8lMmB1IM3xFuTXSi0W8FKjq66AKxOHYiaO6VejL1TwnxuzfzbSPzUy+NApgORaCh0suwTvNS+nuj7nU8ZStFnZNw6c97PsPxLM22h2z/mogyX5VTHGBGUmfhFIZdX34GcGkn/cyPdD7zD8MIK/IiBKMthrWSaRiGOdf6copblFmuvQmLkmyFOiwT81Zv6Yrm9P8ga9IIOArL0neenxMuG8ekuyE6SUOniNPPhDO/2m3D+Qe8uw2nPwHaogbjIwc+qR8LpTNdqO9FmqbMNMv/aRXDebfWXg4G8afc3Athm1XJD0LzyCuMmAT2CSqoMVpVu1x0uNrmBiM/EcKPib+f4oQPD7Fvhs9fW2qS9uWPoT4R8vGfCDXHgM1lS9A26R5up4VGjSe+sx+DnfD7lvxTbzS/DmUC5zkp2mDTIIrA4kBlsO3sGOZFuYH1WZ2FzmCzUWzvTrEt+po9+pZv4vDtV1K9mADHpUBzOP648pr74lXfgieN+9lYkxLfNd3dX3gL/D/Q9WFtX1TMOu+IyeDHw2MPHMmSoZSFuk+VYmFvRp9oVe5mtW9/VZ4MM7Y239D5TZuAEVxNuy22d7Mz/sY6LPYEPyfQttPRMf0bfvVtSG5D88cOaXQqULL5b70WX/RJDBg9TBkeQtwZQU/DfRZ3DWqZHrWDlzo1YMTr8Z/E3ZH1NdP7dbfxb4ByeQQZxQAfLHQNQB1x24lhqXWkWEDH6iT8c/ldLeDz1G5haiHfqO0ajJgCWxtLBFBcpros+B1YFrV+Mo0DM7N3L+FGfQN7IbpxOtzt5BBvGBT2CSnsrMR7KlNlBHRs4fk7SuIsr7uxpPvNw4sqSb/JlBBhGmCj5nJ8Rez0/02a03ZFmva+bPtZnHUeAfDHb/QuxkkIM6KOh3p7+I4L42Zf9QavUl/sEg9y+kcBrwTvsBUsMstDpoBn/frbrbgr9p+g35WLeNJoTC4R9sQAZx4Ux+G5j6Vge8m8/s2xfDqbu8VJZ1E887xtQb2Verlvq+7UEG8XkHc48gW9BjNveMGsFfRhL8NX2W+ZqmH9COvR5XS8t71kNSUamQge/2Zu4UVHUQ/DG06nbNcj8w89+cLnDJdpvqY/8AZBCZd7DwCOLFDTnfqBH4JcV/CnShrxVkcBu4XLntOU9pIPUHKZHBify2N3Nb9bMjkMpIg5+lv2SD0Zra9y0AbmXl2u48iPqDMrHr3XmQwbVDV8w+/SFO5JUQnrnkR45ZyyS1JWH33a04CJSnShe+50y4qZEBr4lLr3uhZ9iC4nL6eebnSj/z5J4mJB2RiD4bvcA4vN0/mFjGVpG7f5AaGdTkd8YCH2gaC8zAl3bZkXZEIsJmm3vHlkoXbMe9Z+0flAle814H0TiBa+1qg4+0I9LQjmd7RJrmKldeNdI4kEFgBt9FNuOb1/aIxh7Sw1d4sKbaNToGuMqVTf+gBhmEB5cox1Dj79vK+1a8C8lgTOmeRxkLXNuduf7gFWQQjzpYBfi7FX0uc/bZXddnaZXVAZYa7/MPbNudZ3pC2oIMwsN3e3MXM39FYYt7eIONa0WEjVMcFHI7jmQ/nYn0v/H+D5BBYPZ2bTa5dQaOtaVXZaRILnCqcERc3zXhTMnenDeb+oMi8evf0/2FNrzv4U0/1O/6+w+Ks8R36zHw1ojnTvwDVxVrFve5zOAzbLVKWJOsoOhMvzr+qXX1kfZLVMBSYzdqzKVA5/R5BgbIIDB2+mHM6bNpxcgInop+Pb6rzuDzzoXPD/sWulGgrnLl5Nutlxk9MBXw7/o1angLuaHW6ZGkEKnPw1tzxjvZe2pwu7QfqY65ItMHVxuvXMGFSBKoQTxGPN8EFeQznSa40tCSZPtIoAyAh8xWM+GAVuoAS41yApjoezvznDST9Q9ABmnDpxCJjcQDblsrpvpeTu9UUrZzMUEGwEPVgaQQSWGdck77IAVQGiRQdvh7m700QAZAL+pAcnArP2/uADX0FMBceUJsgQyygU9pNu9bqAZGAKXhAfRhpu5ABkAI1CTviDQkM9EkgL7GOrdgP4IMgFDgohjJM81530IIAqg0CSTddg5kkBekhUicLvybEQGwCTjp6W9yWfueMmkmAzLIC7y+PRe8l4Nnn/DY7VsB1MY9PhA6HQGR411IBgrrxAZ1CAVADQWQrfEKMsgPlYc6UK76kuJeD+dzLec9EwAf+DuY9vMgg3zVgZo9JYVIfN5CFSEBsALo66yL2lAA2aUBIIPhqgNpIdJIpwuvERAA7weY9kwAJ02Ig+4bCTLIF1uSm2tsxO0DEACnAH2fdmUagej1ADLIGlyIJO0R2ZeZGCoFIPrc2LUnHEMHMhgYOPedCoP0UfsW+KzLec8pAOlZ3/QBAJDBYKEKkV6E72UzsQvZPKJftwT32UiH6wH2SANABsCv0tjVv8+cwe9pkVbQr8VAfROAmQZUePQgA+B3bMnev8/EwlNSswIIkQIoHEEAIANADp+lRgVXE5SQKQB/nuQ3BoEMgJDqQNrzoNSE0EwX2AScBSCAM30uBx4J3ZpABsBdObUyE5+F719oBbA3fIAywDWbaQCMQJAB0BE+dJBLa/znJN/01CUBwAgEGQA9QEn/P6l/o88FGIEgA6Bn8FLjMoJrgREIMgACg3sehDhRi43AQe4MBBkAscGngWpXf+9I2BgEMgCiBJuJ5QMJAEYgyABIRB2odukvHf9eGIEgAyBBqJlb1R6sO/g92BoMMgASx1aPA996AnNrMCoCQQZAJuDTleYCAsBKAMgAGAAhqBme9y+MdLBX+v8foADyxn8CDABulgqUemYwnwAAAABJRU5ErkJggg==); + background-repeat:no-repeat; + background-position:right; +} + .exc-title { + margin: 0; + color: #616161; + font-weight:normal; + } + .exc-title-primary { color: #ED591A; } + .exc-message { + font-size: 32px; + margin: 5px 0; + word-wrap: break-word; + } + +.stack-container { + height: 100%; + position: relative; +} + +.details-container { + height: 100%; + overflow: auto; + float: right; + width: 70%; + background: #fff; +} + .details { + padding: 10px 20px; + border-left: 5px solid rgba(0, 0, 0, .2); + } + +.frames-container { + height: 100%; + overflow: auto; + float: left; + width: 30%; +} + .frame { + padding: 14px; + background: #F3F3F3; + border-right: 1px solid rgba(0, 0, 0, .2); + cursor: pointer; + font-size:12px; + } + .frame.active { + background-color: #ED591A; + color: #F3F3F3; + + } + + .frame:not(.active):hover { + background: #F0E5DF; + } + + .frame-class, .frame-function, .frame-index { + font-weight: bold; + } + + .frame-index { + font-size: 11px; + color: #BDBDBD; + } + + .frame-class { + color: #ED591A; + } + .active .frame-class { + color: #5E2204; + } + + .frame-file { + font-family: 'Source Code Pro', Monaco, Consolas, "Lucida Console", monospace; + color:#999; + word-wrap:break-word; + } + .editor-link { + color: inherit; + } + .editor-link:hover strong { + color: #F0E5DF; + } + + .editor-link-callout { + padding: 2px 4px; + background: #872D00; + } + + .frame-line { + font-weight: bold; + color: #A33202; + } + + .active .frame-file{ color:#872D00; } + + .active .frame-line { color: #fff; } + .frame-line:before { + content: ":"; + } + + .frame-code { + padding: 20px; + background: #f0f0f0; + display: none; + border-left: 5px solid #EDA31A; + } + + .frame-code.active { + display: block; + } + + .frame-code .frame-file { + background: #ED591A; + color: #fff; + padding: 10px 10px 10px 10px; + font-size:11px; + font-weight:normal; + } + + .code-block { + padding: 10px; + margin: 0; + box-shadow: inset 0 0 6px rgba(0, 0, 0, .3); + } + + .linenums { + margin: 0; + margin-left: 10px; + } + + .frame-comments { + border-top: none; + padding: 5px; + font-size: 12px; + background: #404040; + } + + .frame-comments.empty { + padding: 8px 15px; + } + + .frame-comments.empty:before { + content: "No comments for this stack frame."; + color: #828282; + } + + .frame-comment { + padding: 10px 5px; + color: #D2D2D2; + } + + .frame-comment:not(:last-child) { + border-bottom: 1px dotted rgba(0, 0, 0, .3); + } + + .frame-comment-context { + font-size: 10px; + font-weight: bold; + color: #86D2B6; + } + +.data-table-container label { + font-size: 16px; + font-weight: bold; + color: #ED591A; + margin: 10px 0; + padding: 10px 0; + + display: block; + margin-bottom: 5px; + padding-bottom: 5px; + border-bottom: 1px solid rgba(0, 0, 0, .08); +} + .data-table { + width: 100%; + margin: 10px 0; + font: 12px 'Source Code Pro', Monaco, Consolas, "Lucida Console", monospace; + } + + .data-table thead { + display: none; + } + + .data-table tr { + padding: 5px 0; + } + + .data-table td:first-child { + width: 20%; + min-width: 130px; + overflow: hidden; + color: #463C54; + padding-right: 5px; + + } + + .data-table td:last-child { + width: 80%; + color:#999; + -ms-word-break: break-all; + word-break: break-all; + word-break: break-word; + -webkit-hyphens: auto; + -moz-hyphens: auto; + hyphens: auto; + } + + .data-table .empty { + color: rgba(0, 0, 0, .3); + font-style: italic; + } + +.handler { + padding: 10px; + font: 14px monospace; +} + +.handler.active { + color: #BBBBBB; + background: #989898; + font-weight: bold; +} + +/* prettify code style +Uses the Doxy theme as a base */ +pre .str, code .str { color: #E3B446; } /* string */ +pre .kwd, code .kwd { color: #DB613B; font-weight: bold; } /* keyword*/ +pre .com, code .com { color: #555; font-weight: bold; } /* comment */ +pre .typ, code .typ { color: #fff; } /* type */ +pre .lit, code .lit { color: #17CFB6; } /* literal */ +pre .pun, code .pun { color: #93a1a1; font-weight: bold; } /* punctuation */ +pre .pln, code .pln { color: #ccc; } /* plaintext */ +pre .tag, code .tag { color: #DB613B; } /* html/xml tag */ +pre .htm, code .htm { color: #dda0dd; } /* html tag */ +pre .xsl, code .xsl { color: #d0a0d0; } /* xslt tag */ +pre .atn, code .atn { color: #fff; font-weight: normal;} /* html/xml attribute name */ +pre .atv, code .atv { color: #E3B446; } /* html/xml attribute value */ +pre .dec, code .dec { color: #fff; } /* decimal */ +pre.prettyprint, code.prettyprint { + font-weight:normal; + font-family: 'Source Code Pro', Monaco, Consolas, "Lucida Console", monospace; + background: #272727; + color: #929292; + font-size:11px; + line-height:1.5em; +} + pre.prettyprint { + white-space: pre-wrap; + } + + pre.prettyprint a, code.prettyprint a { + text-decoration:none; + } + + .linenums li { + color: #A5A5A5; + } + + .linenums li.current{ + background: rgba(255, 255, 255, .05); + padding-top: 4px; + padding-left: 1px; + } + .linenums li.current.active { + background: rgba(255, 255, 255, .1); + } \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Exception/resources/pretty-template.php b/vendor/laravel/framework/src/Illuminate/Exception/resources/pretty-template.php new file mode 100755 index 0000000..4bd4dd6 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Exception/resources/pretty-template.php @@ -0,0 +1,204 @@ + + + + + + <?php echo $e($v->title) ?> + + + + +
+ +
+ +
+ + + frames as $i => $frame): ?> +
+
+ frames) - $i - 1) ?>. + getClass() ?: '') ?> + getFunction() ?: '') ?> +
+ + + getFile(true) ?: '<#unknown>') ?>getLine() ?> + +
+ + +
+ +
+ +
+
+

+ name as $i => $nameSection): ?> + name) - 1): ?> + + + + + +

+

+ message) ?> +

+
+
+ + +
+ frames as $i => $frame): ?> + getLine(); ?> +
+
+ getFile(); ?> + handler->getEditorHref($filePath, (int) $line)): ?> + + open: ') ?> + + + ') ?> + +
+ getFileLines($line - 8, 10); + $range = array_map(function($line){ return empty($line) ? ' ' : $line;}, $range); + $start = key($range) + 1; + $code = join("\n", $range); + ?> +
+ + + getComments(); + ?> +
+ $comment): ?> + +
+ + +
+ +
+ +
+ +
+ + +
+
+ tables as $label => $data): ?> +
+ + + + + + + + + + $value): ?> + + + + + +
KeyValue
+ + empty + +
+ +
+ + +
+ + handlers as $i => $handler): ?> +
+ . +
+ +
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php b/vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php new file mode 100755 index 0000000..e31553f --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Filesystem/Filesystem.php @@ -0,0 +1,378 @@ +isFile($path)) return file_get_contents($path); + + throw new FileNotFoundException("File does not exist at path {$path}"); + } + + /** + * Get the contents of a remote file. + * + * @param string $path + * @return string + */ + public function getRemote($path) + { + return file_get_contents($path); + } + + /** + * Get the returned value of a file. + * + * @param string $path + * @return mixed + */ + public function getRequire($path) + { + if ($this->isFile($path)) return require $path; + + throw new FileNotFoundException("File does not exist at path {$path}"); + } + + /** + * Require the given file once. + * + * @param string $file + * @return void + */ + public function requireOnce($file) + { + require_once $file; + } + + /** + * Write the contents of a file. + * + * @param string $path + * @param string $contents + * @return int + */ + public function put($path, $contents) + { + return file_put_contents($path, $contents); + } + + /** + * Append to a file. + * + * @param string $path + * @param string $data + * @return int + */ + public function append($path, $data) + { + return file_put_contents($path, $data, FILE_APPEND); + } + + /** + * Delete the file at a given path. + * + * @param string $path + * @return bool + */ + public function delete($path) + { + return @unlink($path); + } + + /** + * Move a file to a new location. + * + * @param string $path + * @param string $target + * @return void + */ + public function move($path, $target) + { + return rename($path, $target); + } + + /** + * Copy a file to a new location. + * + * @param string $path + * @param string $target + * @return void + */ + public function copy($path, $target) + { + return copy($path, $target); + } + + /** + * Extract the file extension from a file path. + * + * @param string $path + * @return string + */ + public function extension($path) + { + return pathinfo($path, PATHINFO_EXTENSION); + } + + /** + * Get the file type of a given file. + * + * @param string $path + * @return string + */ + public function type($path) + { + return filetype($path); + } + + /** + * Get the file size of a given file. + * + * @param string $path + * @return int + */ + public function size($path) + { + return filesize($path); + } + + /** + * Get the file's last modification time. + * + * @param string $path + * @return int + */ + public function lastModified($path) + { + return filemtime($path); + } + + /** + * Determine if the given path is a directory. + * + * @param string $directory + * @return bool + */ + public function isDirectory($directory) + { + return is_dir($directory); + } + + /** + * Determine if the given path is writable. + * + * @param string $path + * @return bool + */ + public function isWritable($path) + { + return is_writable($path); + } + + /** + * Determine if the given path is a file. + * + * @param string $file + * @return bool + */ + public function isFile($file) + { + return is_file($file); + } + + /** + * Find path names matching a given pattern. + * + * @param string $pattern + * @param int $flags + * @return array + */ + public function glob($pattern, $flags = 0) + { + return glob($pattern, $flags); + } + + /** + * Get an array of all files in a directory. + * + * @param string $directory + * @return array + */ + public function files($directory) + { + $glob = glob($directory.'/*'); + + if ($glob === false) return array(); + + // To get the appropriate files, we'll simply glob the directory and filter + // out any "files" that are not truly files so we do not end up with any + // directories in our list, but only true files within the directory. + return array_filter($glob, function($file) + { + return filetype($file) == 'file'; + }); + } + + /** + * Get all of the files from the given directory (recursive). + * + * @param string $directory + * @return array + */ + public function allFiles($directory) + { + return iterator_to_array(Finder::create()->files()->in($directory), false); + } + + /** + * Get all of the directories within a given directory. + * + * @param string $directory + * @return array + */ + public function directories($directory) + { + $directories = array(); + + foreach (Finder::create()->in($directory)->directories()->depth(0) as $dir) + { + $directories[] = $dir->getPathname(); + } + + return $directories; + } + + /** + * Create a directory. + * + * @param string $path + * @param int $mode + * @param bool $recursive + * @return bool + */ + public function makeDirectory($path, $mode = 0777, $recursive = false) + { + return mkdir($path, $mode, $recursive); + } + + /** + * Copy a directory from one location to another. + * + * @param string $directory + * @param string $destination + * @param int $options + * @return void + */ + public function copyDirectory($directory, $destination, $options = null) + { + if ( ! $this->isDirectory($directory)) return false; + + $options = $options ?: FilesystemIterator::SKIP_DOTS; + + // If the destination directory does not actually exist, we will go ahead and + // create it recursively, which just gets the destination prepared to copy + // the files over. Once we make the directory we'll proceed the copying. + if ( ! $this->isDirectory($destination)) + { + $this->makeDirectory($destination, 0777, true); + } + + $items = new FilesystemIterator($directory, $options); + + foreach ($items as $item) + { + // As we spin through items, we will check to see if the current file is actually + // a directory or a file. When it is actually a directory we will need to call + // back into this function recursively to keep copying these nested folders. + $target = $destination.'/'.$item->getBasename(); + + if ($item->isDir()) + { + $path = $item->getPathname(); + + if ( ! $this->copyDirectory($path, $target, $options)) return false; + } + + // If the current items is just a regular file, we will just copy this to the new + // location and keep looping. If for some reason the copy fails we'll bail out + // and return false, so the developer is aware that the copy process failed. + else + { + if ( ! $this->copy($item->getPathname(), $target)) return false; + } + } + + return true; + } + + /** + * Recursively delete a directory. + * + * The directory itself may be optionally preserved. + * + * @param string $directory + * @param bool $preserve + * @return void + */ + public function deleteDirectory($directory, $preserve = false) + { + if ( ! $this->isDirectory($directory)) return; + + $items = new FilesystemIterator($directory); + + foreach ($items as $item) + { + // If the item is a directory, we can just recurse into the function and + // delete that sub-director, otherwise we'll just delete the file and + // keep iterating through each file until the directory is cleaned. + if ($item->isDir()) + { + $this->deleteDirectory($item->getPathname()); + } + + // If the item is just a file, we can go ahead and delete it since we're + // just looping through and waxing all of the files in this directory + // and calling directories recursively, so we delete the real path. + else + { + $this->delete($item->getPathname()); + } + } + + if ( ! $preserve) @rmdir($directory); + } + + /** + * Empty the specified directory of all files and folders. + * + * @param string $directory + * @return void + */ + public function cleanDirectory($directory) + { + return $this->deleteDirectory($directory, true); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemServiceProvider.php new file mode 100755 index 0000000..a346ac0 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemServiceProvider.php @@ -0,0 +1,17 @@ +app['files'] = $this->app->share(function() { return new Filesystem; }); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Filesystem/composer.json b/vendor/laravel/framework/src/Illuminate/Filesystem/composer.json new file mode 100755 index 0000000..e934fb0 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Filesystem/composer.json @@ -0,0 +1,30 @@ +{ + "name": "illuminate/filesystem", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "illuminate/support": "4.0.x", + "symfony/finder": "2.3.x" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Filesystem": "" + } + }, + "target-dir": "Illuminate/Filesystem", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/AliasLoader.php b/vendor/laravel/framework/src/Illuminate/Foundation/AliasLoader.php new file mode 100755 index 0000000..aa55005 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/AliasLoader.php @@ -0,0 +1,158 @@ +aliases = $aliases; + } + + /** + * Get or create the singleton alias loader instance. + * + * @param array $aliases + * @return \Illuminate\Foundation\AliasLoader + */ + public static function getInstance(array $aliases = array()) + { + if (is_null(static::$instance)) static::$instance = new static($aliases); + + $aliases = array_merge(static::$instance->getAliases(), $aliases); + + static::$instance->setAliases($aliases); + + return static::$instance; + } + + /** + * Load a class alias if it is registered. + * + * @param string $alias + * @return void + */ + public function load($alias) + { + if (isset($this->aliases[$alias])) + { + return class_alias($this->aliases[$alias], $alias); + } + } + + /** + * Add an alias to the loader. + * + * @param string $class + * @param string $alias + * @return void + */ + public function alias($class, $alias) + { + $this->aliases[$class] = $alias; + } + + /** + * Register the loader on the auto-loader stack. + * + * @return void + */ + public function register() + { + if ( ! $this->registered) + { + $this->prependToLoaderStack(); + + $this->registered = true; + } + } + + /** + * Prepend the load method to the auto-loader stack. + * + * @return void + */ + protected function prependToLoaderStack() + { + spl_autoload_register(array($this, 'load'), true, true); + } + + /** + * Get the registered aliases. + * + * @return array + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * Set the registered aliases. + * + * @param array $aliases + * @return void + */ + public function setAliases(array $aliases) + { + $this->aliases = $aliases; + } + + /** + * Indicates if the loader has been registered. + * + * @return bool + */ + public function isRegistered() + { + return $this->registered; + } + + /** + * Set the "registered" state of the loader. + * + * @param bool $value + * @return void + */ + public function setRegistered($value) + { + $this->registered = $value; + } + + /** + * Set the value of the singleton alias loader. + * + * @param \Illuminate\Foundation\AliasLoader $loader + * @return void + */ + public static function setInstance($loader) + { + static::$instance = $loader; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Application.php b/vendor/laravel/framework/src/Illuminate/Foundation/Application.php new file mode 100755 index 0000000..f3364e3 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Application.php @@ -0,0 +1,842 @@ +createRequest($request); + + // The exception handler class takes care of determining which of the bound + // exception handler Closures should be called for a given exception and + // gets the response from them. We'll bind it here to allow overrides. + $this->register(new ExceptionServiceProvider($this)); + + $this->register(new RoutingServiceProvider($this)); + + $this->register(new EventServiceProvider($this)); + } + + /** + * Create the request for the application. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Request + */ + protected function createRequest(Request $request = null) + { + return $request ?: static::onRequest('createFromGlobals'); + } + + /** + * Set the application request for the console environment. + * + * @return void + */ + public function setRequestForConsoleEnvironment() + { + $url = $this['config']->get('app.url', 'http://localhost'); + + $parameters = array($url, 'GET', array(), array(), array(), $_SERVER); + + $this->instance('request', static::onRequest('create', $parameters)); + } + + /** + * Redirect the request if it has a trailing slash. + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse|null + */ + public function redirectIfTrailingSlash() + { + if ($this->runningInConsole()) return; + + // Here we will check if the request path ends in a single trailing slash and + // redirect it using a 301 response code if it does which avoids duplicate + // content in this application while still providing a solid experience. + $path = $this['request']->getPathInfo(); + + if ($path != '/' and ends_with($path, '/') and ! ends_with($path, '//')) + { + with(new SymfonyRedirect($this['request']->fullUrl(), 301))->send(); + + exit; + } + } + + /** + * Bind the installation paths to the application. + * + * @param array $paths + * @return void + */ + public function bindInstallPaths(array $paths) + { + $this->instance('path', realpath($paths['app'])); + + foreach (array_except($paths, array('app')) as $key => $value) + { + $this->instance("path.{$key}", realpath($value)); + } + } + + /** + * Get the application bootstrap file. + * + * @return string + */ + public static function getBootstrapFile() + { + return __DIR__.'/start.php'; + } + + /** + * Start the exception handling for the request. + * + * @return void + */ + public function startExceptionHandling() + { + $this['exception']->register($this->environment()); + + $this['exception']->setDebug($this['config']['app.debug']); + } + + /** + * Get the current application environment. + * + * @return string + */ + public function environment() + { + return $this['env']; + } + + /** + * Detect the application's current environment. + * + * @param array|string $environments + * @return string + */ + public function detectEnvironment($environments) + { + $base = $this['request']->getHost(); + + $arguments = $this['request']->server->get('argv'); + + if ($this->runningInConsole()) + { + return $this->detectConsoleEnvironment($base, $environments, $arguments); + } + + return $this->detectWebEnvironment($base, $environments); + } + + /** + * Set the application environment for a web request. + * + * @param string $base + * @param array|string $environments + * @return string + */ + protected function detectWebEnvironment($base, $environments) + { + // If the given environment is just a Closure, we will defer the environment + // detection to the Closure the developer has provided, which allows them + // to totally control the web environment detection if they require to. + if ($environments instanceof Closure) + { + return $this['env'] = call_user_func($environments); + } + + foreach ($environments as $environment => $hosts) + { + // To determine the current environment, we'll simply iterate through the + // possible environments and look for a host that matches this host in + // the request's context, then return back that environment's names. + foreach ((array) $hosts as $host) + { + if (str_is($host, $base) or $this->isMachine($host)) + { + return $this['env'] = $environment; + } + } + } + + return $this['env'] = 'production'; + } + + /** + * Set the application environment from command-line arguments. + * + * @param string $base + * @param mixed $environments + * @param array $arguments + * @return string + */ + protected function detectConsoleEnvironment($base, $environments, $arguments) + { + foreach ($arguments as $key => $value) + { + // For the console environment, we'll just look for an argument that starts + // with "--env" then assume that it is setting the environment for every + // operation being performed, and we'll use that environment's config. + if (starts_with($value, '--env=')) + { + $segments = array_slice(explode('=', $value), 1); + + return $this['env'] = head($segments); + } + } + + return $this->detectWebEnvironment($base, $environments); + } + + /** + * Determine if the name matches the machine name. + * + * @param string $name + * @return bool + */ + protected function isMachine($name) + { + return str_is($name, gethostname()); + } + + /** + * Determine if we are running in the console. + * + * @return bool + */ + public function runningInConsole() + { + return php_sapi_name() == 'cli'; + } + + /** + * Determine if we are running unit tests. + * + * @return bool + */ + public function runningUnitTests() + { + return $this['env'] == 'testing'; + } + + /** + * Register a service provider with the application. + * + * @param \Illuminate\Support\ServiceProvider|string $provider + * @param array $options + * @return void + */ + public function register($provider, $options = array()) + { + // If the given "provider" is a string, we will resolve it, passing in the + // application instance automatically for the developer. This is simply + // a more convenient way of specifying your service provider classes. + if (is_string($provider)) + { + $provider = $this->resolveProviderClass($provider); + } + + $provider->register(); + + // Once we have registered the service we will iterate through the options + // and set each of them on the application so they will be available on + // the actual loading of the service objects and for developer usage. + foreach ($options as $key => $value) + { + $this[$key] = $value; + } + + $this->serviceProviders[] = $provider; + + $this->loadedProviders[get_class($provider)] = true; + } + + /** + * Resolve a service provider instance from the class name. + * + * @param string $provider + * @return \Illuminate\Support\ServiceProvider + */ + protected function resolveProviderClass($provider) + { + return new $provider($this); + } + + /** + * Load and boot all of the remaining deferred providers. + * + * @return void + */ + public function loadDeferredProviders() + { + // We will simply spin through each of the deferred providers and register each + // one and boot them if the application has booted. This should make each of + // the remaining services available to this application for immediate use. + foreach (array_unique($this->deferredServices) as $provider) + { + $this->register($instance = new $provider($this)); + + if ($this->booted) $instance->boot(); + } + + $this->deferredServices = array(); + } + + /** + * Load the provider for a deferred service. + * + * @param string $service + * @return void + */ + protected function loadDeferredProvider($service) + { + $provider = $this->deferredServices[$service]; + + // If the service provider has not already been loaded and registered we can + // register it with the application and remove the service from this list + // of deferred services, since it will already be loaded on subsequent. + if ( ! isset($this->loadedProviders[$provider])) + { + $this->register($instance = new $provider($this)); + + unset($this->deferredServices[$service]); + + $this->setupDeferredBoot($instance); + } + } + + /** + * Handle the booting of a deferred service provider. + * + * @param \Illuminate\Support\ServiceProvider $instance + * @return void + */ + protected function setupDeferredBoot($instance) + { + if ($this->booted) return $instance->boot(); + + $this->booting(function() use ($instance) { $instance->boot(); }); + } + + /** + * Resolve the given type from the container. + * + * (Overriding Container::make) + * + * @param string $abstract + * @param array $parameters + * @return mixed + */ + public function make($abstract, $parameters = array()) + { + if (isset($this->deferredServices[$abstract])) + { + $this->loadDeferredProvider($abstract); + } + + return parent::make($abstract, $parameters); + } + + /** + * Register a "before" application filter. + * + * @param Closure|string $callback + * @return void + */ + public function before($callback) + { + return $this['router']->before($callback); + } + + /** + * Register an "after" application filter. + * + * @param Closure|string $callback + * @return void + */ + public function after($callback) + { + return $this['router']->after($callback); + } + + /** + * Register a "close" application filter. + * + * @param Closure|string $callback + * @return void + */ + public function close($callback) + { + return $this['router']->close($callback); + } + + /** + * Register a "finish" application filter. + * + * @param Closure|string $callback + * @return void + */ + public function finish($callback) + { + $this['router']->finish($callback); + } + + /** + * Register a "shutdown" callback. + * + * @param callable $callback + * @return void + */ + public function shutdown($callback = null) + { + if (is_null($callback)) + { + $this->fireAppCallbacks($this->shutdownCallbacks); + } + else + { + $this->shutdownCallbacks[] = $callback; + } + } + + /** + * Handles the given request and delivers the response. + * + * @return void + */ + public function run() + { + $response = $this->dispatch($this['request']); + + $this['router']->callCloseFilter($this['request'], $response); + + $response->send(); + + $this['router']->callFinishFilter($this['request'], $response); + } + + /** + * Handle the given request and get the response. + * + * @param \Illuminate\Http\Request $request + * @return \Symfony\Component\HttpFoundation\Response + */ + public function dispatch(Request $request) + { + if ($this->isDownForMaintenance()) + { + $response = $this['events']->until('illuminate.app.down'); + + if ( ! is_null($response)) return $this->prepareResponse($response, $request); + } + + return $this['router']->dispatch($this->prepareRequest($request)); + } + + /** + * Handle the given request and get the response. + * + * Provides compatibility with BrowserKit functional testing. + * + * @implements HttpKernelInterface::handle + * + * @param \Illuminate\Http\Request $request + * @param int $type + * @param bool $catch + * @return \Symfony\Component\HttpFoundation\Response + */ + public function handle(SymfonyRequest $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + $this->instance('request', $request); + + Facade::clearResolvedInstance('request'); + + return $this->dispatch($request); + } + + /** + * Boot the application's service providers. + * + * @return void + */ + public function boot() + { + if ($this->booted) return; + + // To boot the application we will simply spin through each service provider + // and call the boot method, which will give them a chance to override on + // something that was registered by another provider when it registers. + foreach ($this->serviceProviders as $provider) + { + $provider->boot(); + } + + $this->fireAppCallbacks($this->bootingCallbacks); + + // Once the application has booted we will also fire some "booted" callbacks + // for any listeners that need to do work after this initial booting gets + // finished. This is useful when ordering the boot-up processes we run. + $this->booted = true; + + $this->fireAppCallbacks($this->bootedCallbacks); + } + + /** + * Register a new boot listener. + * + * @param mixed $callback + * @return void + */ + public function booting($callback) + { + $this->bootingCallbacks[] = $callback; + } + + /** + * Register a new "booted" listener. + * + * @param mixed $callback + * @return void + */ + public function booted($callback) + { + $this->bootedCallbacks[] = $callback; + } + + /** + * Call the booting callbacks for the application. + * + * @return void + */ + protected function fireAppCallbacks(array $callbacks) + { + foreach ($callbacks as $callback) + { + call_user_func($callback, $this); + } + } + + /** + * Prepare the request by injecting any services. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Request + */ + public function prepareRequest(Request $request) + { + if (isset($this['session'])) + { + $request->setSessionStore($this['session']); + } + + return $request; + } + + /** + * Prepare the given value as a Response object. + * + * @param mixed $value + * @return \Symfony\Component\HttpFoundation\Response + */ + public function prepareResponse($value) + { + if ( ! $value instanceof SymfonyResponse) $value = new Response($value); + + return $value->prepare($this['request']); + } + + /** + * Determine if the application is currently down for maintenance. + * + * @return bool + */ + public function isDownForMaintenance() + { + return file_exists($this['path.storage'].'/meta/down'); + } + + /** + * Register a maintenance mode event listener. + * + * @param \Closure $callback + * @return void + */ + public function down(Closure $callback) + { + $this['events']->listen('illuminate.app.down', $callback); + } + + /** + * Throw an HttpException with the given data. + * + * @param int $code + * @param string $message + * @param array $headers + * @return void + */ + public function abort($code, $message = '', array $headers = array()) + { + if ($code == 404) + { + throw new NotFoundHttpException($message); + } + else + { + throw new HttpException($code, $message, null, $headers); + } + } + + /** + * Register a 404 error handler. + * + * @param Closure $callback + * @return void + */ + public function missing(Closure $callback) + { + $this->error(function(NotFoundHttpException $e) use ($callback) + { + return call_user_func($callback, $e); + }); + } + + /** + * Register an application error handler. + * + * @param \Closure $callback + * @return void + */ + public function error(Closure $callback) + { + $this['exception']->error($callback); + } + + /** + * Register an error handler at the bottom of the stack. + * + * @param \Closure $callback + * @return void + */ + public function pushError(Closure $callback) + { + $this['exception']->pushError($callback); + } + + /** + * Register an error handler for fatal errors. + * + * @param Closure $callback + * @return void + */ + public function fatal(Closure $callback) + { + $this->error(function(FatalErrorException $e) use ($callback) + { + return call_user_func($callback, $e); + }); + } + + /** + * Get the configuration loader instance. + * + * @return \Illuminate\Config\LoaderInterface + */ + public function getConfigLoader() + { + return new FileLoader(new Filesystem, $this['path'].'/config'); + } + + /** + * Get the service provider repository instance. + * + * @return \Illuminate\Foundation\ProviderRepository + */ + public function getProviderRepository() + { + $manifest = $this['config']['app.manifest']; + + return new ProviderRepository(new Filesystem, $manifest); + } + + /** + * Set the current application locale. + * + * @param string $locale + * @return void + */ + public function setLocale($locale) + { + $this['config']->set('app.locale', $locale); + + $this['translator']->setLocale($locale); + + $this['events']->fire('locale.changed', array($locale)); + } + + /** + * Get the service providers that have been loaded. + * + * @return array + */ + public function getLoadedProviders() + { + return $this->loadedProviders; + } + + /** + * Set the application's deferred services. + * + * @param array $services + * @return void + */ + public function setDeferredServices(array $services) + { + $this->deferredServices = $services; + } + + /** + * Get or set the request class for the application. + * + * @param string $class + * @return string + */ + public static function requestClass($class = null) + { + if ( ! is_null($class)) static::$requestClass = $class; + + return static::$requestClass; + } + + /** + * Call a method on the default request class. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public static function onRequest($method, $parameters = array()) + { + return forward_static_call_array(array(static::requestClass(), $method), $parameters); + } + + /** + * Dynamically access application services. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this[$key]; + } + + /** + * Dynamically set application services. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this[$key] = $value; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Artisan.php b/vendor/laravel/framework/src/Illuminate/Foundation/Artisan.php new file mode 100755 index 0000000..f4d5a67 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Artisan.php @@ -0,0 +1,85 @@ +app = $app; + } + + /** + * Run an Artisan console command by name. + * + * @param string $command + * @param array $parameters + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return void + */ + public function call($command, array $parameters = array(), OutputInterface $output = null) + { + $artisan = $this->getArtisan(); + + $parameters['command'] = $command; + + // Unless an output interface implementation was specifically passed to us we + // will use the "NullOutput" implementation by default to keep any writing + // suppressed so it doesn't leak out to the browser or any other source. + $output = $output ?: new NullOutput; + + $input = new ArrayInput($parameters); + + return $artisan->find($command)->run($input, $output); + } + + /** + * Get the Artisan console instance. + * + * @return \Illuminate\Console\Application + */ + protected function getArtisan() + { + if ( ! is_null($this->artisan)) return $this->artisan; + + $this->app->loadDeferredProviders(); + + return $this->artisan = ConsoleApplication::start($this->app); + } + + /** + * Dynamically pass all missing methods to console Artisan. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return call_user_func_array(array($this->app['artisan'], $method), $parameters); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/AssetPublisher.php b/vendor/laravel/framework/src/Illuminate/Foundation/AssetPublisher.php new file mode 100755 index 0000000..5a9ee38 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/AssetPublisher.php @@ -0,0 +1,92 @@ +files = $files; + $this->publishPath = $publishPath; + } + + /** + * Copy all assets from a given path to the publish path. + * + * @param string $name + * @param string $source + * @return bool + */ + public function publish($name, $source) + { + $destination = $this->publishPath."/packages/{$name}"; + + $success = $this->files->copyDirectory($source, $destination); + + if ( ! $success) + { + throw new \RuntimeException("Unable to publish assets."); + } + + return $success; + } + + /** + * Publish a given package's assets to the publish path. + * + * @param string $package + * @param string $packagePath + * @return bool + */ + public function publishPackage($package, $packagePath = null) + { + $packagePath = $packagePath ?: $this->packagePath; + + // Once we have the package path we can just create the source and destination + // path and copy the directory from one to the other. The directory copy is + // recursive so all nested files and directories will get copied as well. + $source = $packagePath."/{$package}/public"; + + return $this->publish($package, $source); + } + + /** + * Set the default package path. + * + * @param string $packagePath + * @return void + */ + public function setPackagePath($packagePath) + { + $this->packagePath = $packagePath; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Composer.php b/vendor/laravel/framework/src/Illuminate/Foundation/Composer.php new file mode 100755 index 0000000..b8eec83 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Composer.php @@ -0,0 +1,98 @@ +files = $files; + $this->workingPath = $workingPath; + } + + /** + * Regenerate the Composer autoloader files. + * + * @param string $extra + * @return void + */ + public function dumpAutoloads($extra = '') + { + $process = $this->getProcess(); + + $process->setCommandLine(trim($this->findComposer().' dump-autoload '.$extra)); + + $process->run(); + } + + /** + * Regenerate the optimized Composer autoloader files. + * + * @return void + */ + public function dumpOptimized() + { + $this->dumpAutoloads('--optimize'); + } + + /** + * Get the composer command for the environment. + * + * @return string + */ + protected function findComposer() + { + if ($this->files->exists($this->workingPath.'/composer.phar')) + { + return 'php composer.phar'; + } + + return 'composer'; + } + + /** + * Get a new Symfony process instance. + * + * @return \Symfony\Component\Process\Process + */ + protected function getProcess() + { + return new Process('', $this->workingPath); + } + + /** + * Set the working path used by the class. + * + * @param string $path + * @return \Illuminate\Foundation\Composer + */ + public function setWorkingPath($path) + { + $this->workingPath = realpath($path); + + return $this; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/ConfigPublisher.php b/vendor/laravel/framework/src/Illuminate/Foundation/ConfigPublisher.php new file mode 100755 index 0000000..96797c5 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/ConfigPublisher.php @@ -0,0 +1,123 @@ +files = $files; + $this->publishPath = $publishPath; + } + + /** + * Publish configuration files from a given path. + * + * @param string $package + * @param string $source + * @return void + */ + public function publish($package, $source) + { + $destination = $this->publishPath."/packages/{$package}"; + + $this->makeDestination($destination); + + return $this->files->copyDirectory($source, $destination); + } + + /** + * Publish the configuration files for a package. + * + * @param string $package + * @param string $packagePath + * @return void + */ + public function publishPackage($package, $packagePath = null) + { + list($vendor, $name) = explode('/', $package); + + // First we will figure out the source of the package's configuration location + // which we do by convention. Once we have that we will move the files over + // to the "main" configuration directory for this particular application. + $path = $packagePath ?: $this->packagePath; + + $source = $this->getSource($package, $name, $path); + + return $this->publish($package, $source); + } + + /** + * Get the source configuration directory to publish. + * + * @param string $package + * @param string $name + * @param string $packagePath + * @return string + */ + protected function getSource($package, $name, $packagePath) + { + $source = $packagePath."/{$package}/src/config"; + + if ( ! $this->files->isDirectory($source)) + { + throw new \InvalidArgumentException("Configuration not found."); + } + + return $source; + } + + /** + * Create the destination directory if it doesn't exist. + * + * @param string $destination + * @return void + */ + protected function makeDestination($destination) + { + if ( ! $this->files->isDirectory($destination)) + { + $this->files->makeDirectory($destination, 0777, true); + } + } + + /** + * Set the default package path. + * + * @param string $packagePath + * @return void + */ + public function setPackagePath($packagePath) + { + $this->packagePath = $packagePath; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Console/AssetPublishCommand.php b/vendor/laravel/framework/src/Illuminate/Foundation/Console/AssetPublishCommand.php new file mode 100755 index 0000000..a199d94 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Console/AssetPublishCommand.php @@ -0,0 +1,170 @@ +assets = $assets; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + foreach ($this->getPackages() as $package) + { + $this->publishAssets($package); + } + } + + /** + * Publish the assets for a given package name. + * + * @param string $package + * @return void + */ + protected function publishAssets($package) + { + if ( ! is_null($path = $this->getPath())) + { + $this->assets->publish($package, $path); + } + else + { + $this->assets->publishPackage($package); + } + + $this->output->writeln('Assets published for package: '.$package); + } + + /** + * Get the name of the package being published. + * + * @return array + */ + protected function getPackages() + { + if ( ! is_null($package = $this->input->getArgument('package'))) + { + return array($package); + } + elseif ( ! is_null($bench = $this->input->getOption('bench'))) + { + return array($bench); + } + + return $this->findAllAssetPackages(); + } + + /** + * Find all the asset hosting packages in the system. + * + * @return array + */ + protected function findAllAssetPackages() + { + $vendor = $this->laravel['path.base'].'/vendor'; + + $packages = array(); + + foreach (Finder::create()->directories()->in($vendor)->name('public')->depth('< 3') as $package) + { + $packages[] = $package->getRelativePath(); + } + + return $packages; + } + + /** + * Get the specified path to the files. + * + * @return string + */ + protected function getPath() + { + $path = $this->input->getOption('path'); + + // First we will check for an explicitly specified path from the user. If one + // exists we will use that as the path to the assets. This allows the free + // storage of assets wherever is best for this developer's web projects. + if ( ! is_null($path)) + { + return $this->laravel['path.base'].'/'.$path; + } + + // If a "bench" option was specified, we will publish from a workbench as the + // source location. This is mainly just a short-cut for having to manually + // specify the full workbench path using the --path command line option. + $bench = $this->input->getOption('bench'); + + if ( ! is_null($bench)) + { + return $this->laravel['path.base']."/workbench/{$bench}/public"; + } + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return array( + array('package', InputArgument::OPTIONAL, 'The name of package being published.'), + ); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('bench', null, InputOption::VALUE_OPTIONAL, 'The name of the workbench to publish.', null), + + array('path', null, InputOption::VALUE_OPTIONAL, 'The path to the configuration files.', null), + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Console/AutoloadCommand.php b/vendor/laravel/framework/src/Illuminate/Foundation/Console/AutoloadCommand.php new file mode 100755 index 0000000..85587ad --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Console/AutoloadCommand.php @@ -0,0 +1,93 @@ +composer = $composer; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->call('optimize'); + + foreach ($this->findWorkbenches() as $workbench) + { + $this->comment("Running for workbench [{$workbench['name']}]..."); + + $this->composer->setWorkingPath($workbench['path'])->dumpOptimized(); + } + } + + /** + * Get all of the workbench directories. + * + * @return array + */ + protected function findWorkbenches() + { + $results = array(); + + foreach ($this->getWorkbenchComposers() as $file) + { + $results[] = array('name' => $file->getRelativePath(), 'path' => $file->getPath()); + } + + return $results; + } + + /** + * Get all of the workbench composer files. + * + * @return \Symfony\Component\Finder\Finder + */ + protected function getWorkbenchComposers() + { + $workbench = $this->laravel['path.base'].'/workbench'; + + if ( ! is_dir($workbench)) return array(); + + return Finder::create()->files()->in($workbench)->name('composer.json')->depth('< 3'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Console/ChangesCommand.php b/vendor/laravel/framework/src/Illuminate/Foundation/Console/ChangesCommand.php new file mode 100644 index 0000000..7d5d93b --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Console/ChangesCommand.php @@ -0,0 +1,115 @@ +getChangeVersion($this->getChangesArray()); + + $this->writeHeader($version); + + foreach ($changes as $change) + { + $this->line($this->formatMessage($change)); + } + } + + /** + * Write the heading for the change log. + * + * @param string $version + * @return void + */ + protected function writeHeader($version) + { + $this->info($heading = 'Changes For Laravel '.$version); + + $this->comment(str_repeat('-', strlen($heading))); + } + + /** + * Format the given change message. + * + * @param array $change + * @return stirng + */ + protected function formatMessage(array $change) + { + $message = '-> '.$change['message'].''; + + if ( ! is_null($change['backport'])) + { + $message .= ' (Backported to '.$change['backport'].')'; + } + + return $message; + } + + /** + * Get the change list for the specified version. + * + * @param array $changes + * @return array + */ + protected function getChangeVersion(array $changes) + { + $version = $this->argument('version'); + + if (is_null($version)) + { + $latest = head(array_keys($changes)); + + return array($latest, $changes[$latest]); + } + else + { + return array($version, array_get($changes, $version, array())); + } + } + + /** + * Get the changes array from disk. + * + * @return array + */ + protected function getChangesArray() + { + return json_decode(file_get_contents(__DIR__.'/../changes.json'), true); + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return array( + array('version', InputArgument::OPTIONAL, 'The version to list changes for.'), + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Console/ClearCompiledCommand.php b/vendor/laravel/framework/src/Illuminate/Foundation/Console/ClearCompiledCommand.php new file mode 100755 index 0000000..ad1024a --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Console/ClearCompiledCommand.php @@ -0,0 +1,35 @@ +laravel['path.base'].'/bootstrap/compiled.php'); + + @unlink($this->laravel['path.storage'].'/meta/services.json'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Console/CommandMakeCommand.php b/vendor/laravel/framework/src/Illuminate/Foundation/Console/CommandMakeCommand.php new file mode 100755 index 0000000..62434ca --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Console/CommandMakeCommand.php @@ -0,0 +1,160 @@ +files = $files; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $path = $this->getPath(); + + $stub = $this->files->get(__DIR__.'/stubs/command.stub'); + + // We'll grab the class name to determine the file name. Since applications are + // typically using the PSR-0 standards we can safely assume the classes name + // will correspond to what the actual file should be stored as on storage. + $file = $path.'/'.$this->input->getArgument('name').'.php'; + + $this->writeCommand($file, $stub); + } + + /** + * Write the finished command file to disk. + * + * @param string $file + * @param string $stub + * @return void + */ + protected function writeCommand($file, $stub) + { + if ( ! file_exists($file)) + { + $this->files->put($file, $this->formatStub($stub)); + + $this->info('Command created successfully.'); + } + else + { + $this->error('Command already exists!'); + } + } + + /** + * Format the command class stub. + * + * @param string $stub + * @return string + */ + protected function formatStub($stub) + { + $stub = str_replace('{{class}}', $this->input->getArgument('name'), $stub); + + if ( ! is_null($this->option('command'))) + { + $stub = str_replace('command:name', $this->option('command'), $stub); + } + + return $this->addNamespace($stub); + } + + /** + * Add the proper namespace to the command. + * + * @param string $stub + * @return string + */ + protected function addNamespace($stub) + { + if ( ! is_null($namespace = $this->input->getOption('namespace'))) + { + return str_replace('{{namespace}}', ' namespace '.$namespace.';', $stub); + } + else + { + return str_replace('{{namespace}}', '', $stub); + } + } + + /** + * Get the path where the command should be stored. + * + * @return string + */ + protected function getPath() + { + $path = $this->input->getOption('path'); + + if (is_null($path)) + { + return $this->laravel['path'].'/commands'; + } + else + { + return $this->laravel['path.base'].'/'.$path; + } + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return array( + array('name', InputArgument::REQUIRED, 'The name of the command.'), + ); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('command', null, InputOption::VALUE_OPTIONAL, 'The terminal command that should be assigned.', null), + + array('path', null, InputOption::VALUE_OPTIONAL, 'The path where the command should be stored.', null), + + array('namespace', null, InputOption::VALUE_OPTIONAL, 'The command namespace.', null), + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Console/ConfigPublishCommand.php b/vendor/laravel/framework/src/Illuminate/Foundation/Console/ConfigPublishCommand.php new file mode 100755 index 0000000..7eb6e03 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Console/ConfigPublishCommand.php @@ -0,0 +1,104 @@ +config = $config; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $package = $this->input->getArgument('package'); + + if ( ! is_null($path = $this->getPath())) + { + $this->config->publish($package, $path); + } + else + { + $this->config->publishPackage($package); + } + + $this->output->writeln('Configuration published for package: '.$package); + } + + /** + * Get the specified path to the files. + * + * @return string + */ + protected function getPath() + { + $path = $this->input->getOption('path'); + + if ( ! is_null($path)) + { + return $this->laravel['path.base'].'/'.$path; + } + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return array( + array('package', InputArgument::REQUIRED, 'The name of package being published.'), + ); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('path', null, InputOption::VALUE_OPTIONAL, 'The path to the configuration files.', null), + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Console/DownCommand.php b/vendor/laravel/framework/src/Illuminate/Foundation/Console/DownCommand.php new file mode 100755 index 0000000..45b5334 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Console/DownCommand.php @@ -0,0 +1,33 @@ +laravel['path.storage'].'/meta/down'); + + $this->comment('Application is now in maintenance mode.'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Console/KeyGenerateCommand.php b/vendor/laravel/framework/src/Illuminate/Foundation/Console/KeyGenerateCommand.php new file mode 100755 index 0000000..84f21d4 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Console/KeyGenerateCommand.php @@ -0,0 +1,78 @@ +files = $files; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + list($path, $contents) = $this->getKeyFile(); + + $key = $this->getRandomKey(); + + $contents = str_replace($this->laravel['config']['app.key'], $key, $contents); + + $this->files->put($path, $contents); + + $this->info("Application key [$key] set successfully."); + } + + /** + * Get the key file and contents. + * + * @return array + */ + protected function getKeyFile() + { + $env = $this->option('env') ? $this->option('env').'/' : ''; + + $contents = $this->files->get($path = $this->laravel['path']."/config/{$env}app.php"); + + return array($path, $contents); + } + + /** + * Generate a random key for the application. + * + * @return string + */ + protected function getRandomKey() + { + return Str::random(32); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Console/Optimize/config.php b/vendor/laravel/framework/src/Illuminate/Foundation/Console/Optimize/config.php new file mode 100755 index 0000000..2c51660 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Console/Optimize/config.php @@ -0,0 +1,117 @@ +composer = $composer; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->info('Generating optimized class loader'); + + $this->composer->dumpOptimized(); + + if ($this->option('force') or ! $this->laravel['config']['app.debug']) + { + $this->info('Compiling common classes'); + + $this->compileClasses(); + } + else + { + $this->call('clear-compiled'); + } + } + + /** + * Generate the compiled class file. + * + * @return void + */ + protected function compileClasses() + { + $this->registerClassPreloaderCommand(); + + $outputPath = $this->laravel['path.base'].'/bootstrap/compiled.php'; + + $this->callSilent('compile', array( + '--config' => implode(',', $this->getClassFiles()), + '--output' => $outputPath, + '--strip_comments' => 1, + )); + } + + /** + * Get the classes that should be combined and compiled. + * + * @return array + */ + protected function getClassFiles() + { + $app = $this->laravel; + + $core = require __DIR__.'/Optimize/config.php'; + + return array_merge($core, $this->laravel['config']['compile']); + } + + /** + * Register the pre-compiler command instance with Artisan. + * + * @return void + */ + protected function registerClassPreloaderCommand() + { + $this->laravel['artisan']->add(new PreCompileCommand); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('force', null, InputOption::VALUE_NONE, 'Force the compiled class file to be written.'), + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Console/RoutesCommand.php b/vendor/laravel/framework/src/Illuminate/Foundation/Console/RoutesCommand.php new file mode 100755 index 0000000..95dfa31 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Console/RoutesCommand.php @@ -0,0 +1,189 @@ +router = $router; + $this->routes = $router->getRoutes(); + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->table = $this->getHelperSet()->get('table'); + + if (count($this->routes) == 0) + { + return $this->error("Your application doesn't have any routes."); + } + + $this->displayRoutes($this->getRoutes()); + } + + /** + * Compile the routes into a displayable format. + * + * @return array + */ + protected function getRoutes() + { + $results = array(); + + foreach($this->routes as $name => $route) + { + $results[] = $this->getRouteInformation($name, $route); + } + + return $results; + } + + /** + * Get the route information for a given route. + * + * @param string $name + * @param \Symfony\Component\Routing\Route $route + * @return array + */ + protected function getRouteInformation($name, Route $route) + { + $uri = head($route->getMethods()).' '.$route->getPath(); + + $action = $route->getAction() ?: 'Closure'; + + return array( + 'host' => $route->getHost(), + 'uri' => $uri, + 'name' => $this->getRouteName($name), + 'action' => $action, + 'before' => $this->getBeforeFilters($route), + 'after' => $this->getAfterFilters($route)); + } + + /** + * Display the route information on the console. + * + * @param array $routes + * @return void + */ + protected function displayRoutes(array $routes) + { + $headers = array('Domain', 'URI', 'Name', 'Action', 'Before Filters', 'After Filters'); + + $this->table->setHeaders($headers)->setRows($routes); + + $this->table->render($this->getOutput()); + } + + /** + * Get the route name for the given name. + * + * @param string $name + * @return string + */ + protected function getRouteName($name) + { + return str_contains($name, ' ') ? '' : $name; + } + + /** + * Get before filters + * + * @param \Illuminate\Routing\Route $route + * @return string + */ + protected function getBeforeFilters($route) + { + $before = $route->getBeforeFilters(); + + $before = array_unique(array_merge($before, $this->getPatternFilters($route))); + + return implode(', ', $before); + } + + /** + * Get all of the pattern filters matching the route. + * + * @param \Illuminate\Routing\Route $route + * @return array + */ + protected function getPatternFilters($route) + { + $patterns = array(); + + foreach ($route->getMethods() as $method) + { + $inner = $this->router->findPatternFilters($method, $route->getPath()); + + $patterns = array_merge($patterns, $inner); + } + + return $patterns; + } + + /** + * Get after filters + * + * @param Route $route + * @return string + */ + protected function getAfterFilters($route) + { + return implode(', ',$route->getAfterFilters()); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php b/vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php new file mode 100755 index 0000000..69ac5de --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Console/ServeCommand.php @@ -0,0 +1,71 @@ +checkPhpVersion(); + + chdir($this->laravel['path.base']); + + $host = $this->input->getOption('host'); + + $port = $this->input->getOption('port'); + + $public = $this->laravel['path.public']; + + $this->info("Laravel development server started on http://{$host}:{$port}"); + + passthru("php -S {$host}:{$port} -t \"{$public}\" server.php"); + } + + /** + * Check the current PHP version is >= 5.4. + * + * @return void + */ + protected function checkPhpVersion() + { + if (version_compare(PHP_VERSION, '5.4.0', '<')) + { + throw new \Exception('This PHP binary is not version 5.4 or greater.'); + } + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('host', null, InputOption::VALUE_OPTIONAL, 'The host address to serve the application on.', 'localhost'), + + array('port', null, InputOption::VALUE_OPTIONAL, 'The port to serve the application on.', 8000), + ); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Console/TinkerCommand.php b/vendor/laravel/framework/src/Illuminate/Foundation/Console/TinkerCommand.php new file mode 100755 index 0000000..0595f9b --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Console/TinkerCommand.php @@ -0,0 +1,69 @@ +prompt(); + + while ($input != 'quit') + { + // We will wrap the execution of the command in a try / catch block so we + // can easily display the errors in a convenient way instead of having + // them bubble back out to the CLI and stop the entire command loop. + try + { + if (starts_with($input, 'dump ')) + { + $input = 'var_dump('.substr($input, 5).');'; + } + + eval($input); + } + + // If an exception occurs, we will just display the message and keep this + // loop going so we can keep executing commands. However, when a fatal + // a error occurs we have no choice but to bail out of the routines. + catch (\Exception $e) + { + $this->error($e->getMessage()); + } + + $input = $this->prompt(); + } + } + + /** + * Prompt the developer for a command. + * + * @return string + */ + protected function prompt() + { + $dialog = $this->getHelperSet()->get('dialog'); + + return $dialog->ask($this->output, ">", null); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Console/UpCommand.php b/vendor/laravel/framework/src/Illuminate/Foundation/Console/UpCommand.php new file mode 100755 index 0000000..c247dd6 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Console/UpCommand.php @@ -0,0 +1,33 @@ +laravel['path.storage'].'/meta/down'); + + $this->info('Application is now live.'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/command.stub b/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/command.stub new file mode 100644 index 0000000..b8c5e1d --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Console/stubs/command.stub @@ -0,0 +1,67 @@ +files = $files; + $this->manifestPath = $manifestPath; + } + + /** + * Register the application service providers. + * + * @param \Illuminate\Foundation\Application $app + * @param array $providers + * @param string $path + * @return void + */ + public function load(Application $app, array $providers) + { + $manifest = $this->loadManifest(); + + // First we will load the service manifest, which contains information on all + // service providers registered with the application and which services it + // provides. This is used to know which services are "deferred" loaders. + if ($this->shouldRecompile($manifest, $providers)) + { + $manifest = $this->compileManifest($app, $providers); + } + + // If the application is running in the console, we will not lazy load any of + // the service providers. This is mainly because it's not as necessary for + // performance and also so any provided Artisan commands get registered. + if ($app->runningInConsole()) + { + $manifest['eager'] = $manifest['providers']; + } + + // We will go ahead and register all of the eagerly loaded providers with the + // application so their services can be registered with the application as + // a provided service. Then we will set the deferred service list on it. + foreach ($manifest['eager'] as $provider) + { + $app->register($this->createProvider($app, $provider)); + } + + $app->setDeferredServices($manifest['deferred']); + } + + /** + * Compile the application manifest file. + * + * @param \Illuminate\Foundation\Application $app + * @param array $providers + * @return array + */ + protected function compileManifest(Application $app, $providers) + { + // The service manifest should contain a list of all of the providers for + // the application so we can compare it on each request to the service + // and determine if the manifest should be recompiled or is current. + $manifest = $this->freshManifest($providers); + + foreach ($providers as $provider) + { + $instance = $this->createProvider($app, $provider); + + // When recompiling the service manifest, we will spin through each of the + // providers and check if it's a deferred provider or not. If so we'll + // add it's provided services to the manifest and note the provider. + if ($instance->isDeferred()) + { + foreach ($instance->provides() as $service) + { + $manifest['deferred'][$service] = $provider; + } + } + + // If the service providers are not deferred, we will simply add it to an + // of eagerly loaded providers that will be registered with the app on + // each request to the applications instead of being lazy loaded in. + else + { + $manifest['eager'][] = $provider; + } + } + + return $this->writeManifest($manifest); + } + + /** + * Create a new provider instance. + * + * @param \Illuminate\Foundation\Application $app + * @param string $provider + * @return \Illuminate\Support\ServiceProvider + */ + public function createProvider(Application $app, $provider) + { + return new $provider($app); + } + + /** + * Determine if the manifest should be compiled. + * + * @param array $manifest + * @param array $providers + * @return bool + */ + public function shouldRecompile($manifest, $providers) + { + return is_null($manifest) or $manifest['providers'] != $providers; + } + + /** + * Load the service provider manifest JSON file. + * + * @return array + */ + public function loadManifest() + { + $path = $this->manifestPath.'/services.json'; + + // The service manifest is a file containing a JSON representation of every + // service provided by the application and whether its provider is using + // deferred loading or should be eagerly loaded on each request to us. + if ($this->files->exists($path)) + { + return json_decode($this->files->get($path), true); + } + } + + /** + * Write the service manifest file to disk. + * + * @param array $manifest + * @return array + */ + public function writeManifest($manifest) + { + $path = $this->manifestPath.'/services.json'; + + $this->files->put($path, json_encode($manifest)); + + return $manifest; + } + + /** + * Get the manifest file path. + * + * @param \Illuminate\Foundation\Application $app + * @return string + */ + protected function getManifestPath($app) + { + return $this->manifestPath; + } + + /** + * Create a fresh manifest array. + * + * @param array $providers + * @return array + */ + protected function freshManifest(array $providers) + { + list($eager, $deferred) = array(array(), array()); + + return compact('providers', 'eager', 'deferred'); + } + + /** + * Get the filesystem instance. + * + * @return \Illuminate\Filesystem\Filesystem + */ + public function getFilesystem() + { + return $this->files; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php new file mode 100755 index 0000000..58d8541 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php @@ -0,0 +1,46 @@ +app['artisan'] = $this->app->share(function($app) + { + return new Artisan($app); + }); + + $this->app['command.changes'] = $this->app->share(function($app) + { + return new ChangesCommand; + }); + + $this->commands('command.changes'); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('artisan', 'command.changes'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Providers/CommandCreatorServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/CommandCreatorServiceProvider.php new file mode 100755 index 0000000..1103537 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/CommandCreatorServiceProvider.php @@ -0,0 +1,42 @@ +app['command.command.make'] = $this->app->share(function($app) + { + return new CommandMakeCommand($app['files']); + }); + + $this->commands('command.command.make'); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array( + 'command.command.make', + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ComposerServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ComposerServiceProvider.php new file mode 100755 index 0000000..f5e06e3 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ComposerServiceProvider.php @@ -0,0 +1,46 @@ +app['composer'] = $this->app->share(function($app) + { + return new Composer($app['files'], $app['path.base']); + }); + + $this->app['command.dump-autoload'] = $this->app->share(function($app) + { + return new AutoloadCommand($app['composer']); + }); + + $this->commands('command.dump-autoload'); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('composer', 'command.dump-autoload'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Providers/KeyGeneratorServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/KeyGeneratorServiceProvider.php new file mode 100755 index 0000000..3f8ee39 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/KeyGeneratorServiceProvider.php @@ -0,0 +1,40 @@ +app['command.key.generate'] = $this->app->share(function($app) + { + return new KeyGenerateCommand($app['files']); + }); + + $this->commands('command.key.generate'); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('command.key.generate'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Providers/MaintenanceServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/MaintenanceServiceProvider.php new file mode 100755 index 0000000..d23ce76 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/MaintenanceServiceProvider.php @@ -0,0 +1,46 @@ +app['command.up'] = $this->app->share(function($app) + { + return new UpCommand; + }); + + $this->app['command.down'] = $this->app->share(function($app) + { + return new DownCommand; + }); + + $this->commands('command.up', 'command.down'); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('command.up', 'command.down'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Providers/OptimizeServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/OptimizeServiceProvider.php new file mode 100755 index 0000000..6edced4 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/OptimizeServiceProvider.php @@ -0,0 +1,66 @@ +registerOptimizeCommand(); + + $this->registerClearCompiledCommand(); + + $this->commands('command.optimize', 'command.clear-compiled'); + } + + /** + * Register the optimize command. + * + * @return void + */ + protected function registerOptimizeCommand() + { + $this->app['command.optimize'] = $this->app->share(function($app) + { + return new OptimizeCommand($app['composer']); + }); + } + + /** + * Register the compiled file remover command. + * + * @return void + */ + protected function registerClearCompiledCommand() + { + $this->app['command.clear-compiled'] = $this->app->share(function() + { + return new ClearCompiledCommand; + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('command.optimize', 'command.clear-compiled'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Providers/PublisherServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/PublisherServiceProvider.php new file mode 100755 index 0000000..4d09696 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/PublisherServiceProvider.php @@ -0,0 +1,121 @@ +registerAssetPublisher(); + + $this->registerConfigPublisher(); + + $this->commands('command.asset.publish', 'command.config.publish'); + } + + /** + * Register the asset publisher service and command. + * + * @return void + */ + protected function registerAssetPublisher() + { + $this->registerAssetPublishCommand(); + + $this->app['asset.publisher'] = $this->app->share(function($app) + { + $publicPath = $app['path.public']; + + // The asset "publisher" is responsible for moving package's assets into the + // web accessible public directory of an application so they can actually + // be served to the browser. Otherwise, they would be locked in vendor. + $publisher = new AssetPublisher($app['files'], $publicPath); + + $publisher->setPackagePath($app['path.base'].'/vendor'); + + return $publisher; + }); + } + + /** + * Register the asset publish console command. + * + * @return void + */ + protected function registerAssetPublishCommand() + { + $this->app['command.asset.publish'] = $this->app->share(function($app) + { + return new AssetPublishCommand($app['asset.publisher']); + }); + } + + /** + * Register the configuration publisher class and command. + * + * @return void + */ + protected function registerConfigPublisher() + { + $this->registerConfigPublishCommand(); + + $this->app['config.publisher'] = $this->app->share(function($app) + { + $configPath = $app['path'].'/config'; + + // Once we have created the configuration publisher, we will set the default + // package path on the object so that it knows where to find the packages + // that are installed for the application and can move them to the app. + $publisher = new ConfigPublisher($app['files'], $configPath); + + $publisher->setPackagePath($app['path.base'].'/vendor'); + + return $publisher; + }); + } + + /** + * Register the configuration publish console command. + * + * @return void + */ + protected function registerConfigPublishCommand() + { + $this->app['command.config.publish'] = $this->app->share(function($app) + { + return new ConfigPublishCommand($app['config.publisher']); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array( + 'asset.publisher', + 'command.asset.publish', + 'config.publisher', + 'command.config.publish' + ); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Providers/RouteListServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/RouteListServiceProvider.php new file mode 100755 index 0000000..92c0854 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/RouteListServiceProvider.php @@ -0,0 +1,40 @@ +app['command.routes'] = $this->app->share(function($app) + { + return new RoutesCommand($app['router']); + }); + + $this->commands('command.routes'); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('command.routes'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ServerServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ServerServiceProvider.php new file mode 100755 index 0000000..5f91736 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/ServerServiceProvider.php @@ -0,0 +1,40 @@ +app['command.serve'] = $this->app->share(function() + { + return new ServeCommand; + }); + + $this->commands('command.serve'); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('command.serve'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Providers/TinkerServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/TinkerServiceProvider.php new file mode 100755 index 0000000..228b4ea --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Providers/TinkerServiceProvider.php @@ -0,0 +1,40 @@ +app['command.tinker'] = $this->app->share(function() + { + return new TinkerCommand; + }); + + $this->commands('command.tinker'); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('command.tinker'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Client.php b/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Client.php new file mode 100755 index 0000000..a673e8d --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Testing/Client.php @@ -0,0 +1,40 @@ +getRequestParameters($request)); + + $httpRequest->files->replace($this->filterFiles($httpRequest->files->all())); + + return $httpRequest; + } + + /** + * Get the request parameters from a BrowserKit request. + * + * @param \Symfony\Component\BrowserKit\Request $request + * @return array + */ + protected function getRequestParameters(DomRequest $request) + { + return array( + $request->getUri(), $request->getMethod(), $request->getParameters(), $request->getCookies(), + $request->getFiles(), $request->getServer(), $request->getContent() + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php b/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php new file mode 100755 index 0000000..5f66d28 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestCase.php @@ -0,0 +1,346 @@ +refreshApplication(); + } + + /** + * Refresh the application instance. + * + * @return void + */ + protected function refreshApplication() + { + $this->app = $this->createApplication(); + + $this->client = $this->createClient(); + } + + /** + * Call the given URI and return the Response. + * + * @param string $method + * @param string $uri + * @param array $parameters + * @param array $files + * @param array $server + * @param string $content + * @param bool $changeHistory + * @return \Illuminate\Http\Response + */ + public function call() + { + call_user_func_array(array($this->client, 'request'), func_get_args()); + + return $this->client->getResponse(); + } + + /** + * Call the given HTTPS URI and return the Response. + * + * @param string $method + * @param string $uri + * @param array $parameters + * @param array $files + * @param array $server + * @param string $content + * @param bool $changeHistory + * @return \Illuminate\Http\Response + */ + public function callSecure() + { + $parameters = func_get_args(); + + $parameters[1] = 'https://localhost/'.ltrim($parameters[1], '/'); + + return call_user_func_array(array($this, 'call'), $parameters); + } + + /** + * Call a controller action and return the Response. + * + * @param string $method + * @param string $action + * @param array $wildcards + * @param array $parameters + * @param array $files + * @param array $server + * @param string $content + * @param bool $changeHistory + * @return \Illuminate\Http\Response + */ + public function action($method, $action, $wildcards = array(), $parameters = array(), $files = array(), $server = array(), $content = null, $changeHistory = true) + { + $uri = $this->app['url']->action($action, $wildcards, true); + + return $this->call($method, $uri, $parameters, $files, $server, $content, $changeHistory); + } + + /** + * Call a named route and return the Response. + * + * @param string $method + * @param string $name + * @param array $routeParameters + * @param array $parameters + * @param array $files + * @param array $server + * @param string $content + * @param bool $changeHistory + * @return \Illuminate\Http\Response + */ + public function route($method, $name, $routeParameters = array(), $parameters = array(), $files = array(), $server = array(), $content = null, $changeHistory = true) + { + $uri = $this->app['url']->route($name, $routeParameters, true); + + return $this->call($method, $uri, $parameters, $files, $server, $content, $changeHistory); + } + + /** + * Assert that the client response has an OK status code. + * + * @return void + */ + public function assertResponseOk() + { + $response = $this->client->getResponse(); + + $actual = $response->getStatusCode(); + + return $this->assertTrue($response->isOk(), 'Expected status code 200, got ' .$actual); + } + + /** + * Assert that the client response has a given code. + * + * @param int $code + * @return void + */ + public function assertResponseStatus($code) + { + return $this->assertEquals($code, $this->client->getResponse()->getStatusCode()); + } + + /** + * Assert that the response view has a given piece of bound data. + * + * @param string|array $key + * @param mixed $value + * @return void + */ + public function assertViewHas($key, $value = null) + { + if (is_array($key)) return $this->assertViewHasAll($key); + + $response = $this->client->getResponse()->original; + + if ( ! $response instanceof View) + { + return $this->assertTrue(false, 'The response was not a view.'); + } + + if (is_null($value)) + { + $this->assertArrayHasKey($key, $response->getData()); + } + else + { + $this->assertEquals($value, $response->$key); + } + } + + /** + * Assert that the view has a given list of bound data. + * + * @param array $bindings + * @return void + */ + public function assertViewHasAll(array $bindings) + { + foreach ($bindings as $key => $value) + { + if (is_int($key)) + { + $this->assertViewHas($value); + } + else + { + $this->assertViewHas($key, $value); + } + } + } + + /** + * Assert whether the client was redirected to a given URI. + * + * @param string $uri + * @param array $with + * @return void + */ + public function assertRedirectedTo($uri, $with = array()) + { + $response = $this->client->getResponse(); + + $this->assertInstanceOf('Illuminate\Http\RedirectResponse', $response); + + $this->assertEquals($this->app['url']->to($uri), $response->headers->get('Location')); + + $this->assertSessionHasAll($with); + } + + /** + * Assert whether the client was redirected to a given route. + * + * @param string $name + * @param array $parameters + * @param array $with + * @return void + */ + public function assertRedirectedToRoute($name, $parameters = array(), $with = array()) + { + $this->assertRedirectedTo($this->app['url']->route($name, $parameters), $with); + } + + /** + * Assert whether the client was redirected to a given action. + * + * @param string $name + * @param array $parameters + * @param array $with + * @return void + */ + public function assertRedirectedToAction($name, $parameters = array(), $with = array()) + { + $this->assertRedirectedTo($this->app['url']->action($name, $parameters), $with); + } + + /** + * Assert that the session has a given list of values. + * + * @param string|array $key + * @param mixed $value + * @return void + */ + public function assertSessionHas($key, $value = null) + { + if (is_array($key)) return $this->assertSessionHasAll($key); + + if (is_null($value)) + { + $this->assertTrue($this->app['session']->has($key)); + } + else + { + $this->assertEquals($value, $this->app['session']->get($key)); + } + } + + /** + * Assert that the session has a given list of values. + * + * @param array $bindings + * @return void + */ + public function assertSessionHasAll(array $bindings) + { + foreach ($bindings as $key => $value) + { + if (is_int($key)) + { + $this->assertSessionHas($value); + } + else + { + $this->assertSessionHas($key, $value); + } + } + } + + /** + * Assert that the session has errors bound. + * + * @param string|array $bindings + * @param mixed $format + * @return void + */ + public function assertSessionHasErrors($bindings = array(), $format = null) + { + $this->assertSessionHas('errors'); + + $bindings = (array)$bindings; + + $errors = $this->app['session']->get('errors'); + + foreach ($bindings as $key => $value) + { + if (is_int($key)) + { + $this->assertTrue($errors->has($value)); + } + else + { + $this->assertContains($value, $errors->get($key, $format)); + } + } + } + + /** + * Set the currently logged in user for the application. + * + * @param \Illuminate\Auth\UserInterface $user + * @param string $driver + * @return void + */ + public function be(UserInterface $user, $driver = null) + { + $this->app['auth']->driver($driver)->setUser($user); + } + + /** + * Seed a given database connection. + * + * @param string $class + * @return void + */ + public function seed($class = 'DatabaseSeeder') + { + $this->app[$class]->run(); + } + + /** + * Create a new HttpKernel client instance. + * + * @param array $server + * @return \Symfony\Component\HttpKernel\Client + */ + protected function createClient(array $server = array()) + { + return new Client($this->app, $server); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/changes.json b/vendor/laravel/framework/src/Illuminate/Foundation/changes.json new file mode 100644 index 0000000..d433cdc --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/changes.json @@ -0,0 +1,36 @@ +{ + "4.0.x": [ + {"message": "Added implode method to query builder and Colletion class.", "backport": null}, + {"message": "Fixed bug that caused Model->push method to fail.", "backport": null}, + {"message": "Make session cookie HttpOnly by default.", "backport": null}, + {"message": "Added mail.pretend configuration option.", "backport": null}, + {"message": "Query elapsed time is now reported as float instead of string.", "backport": null}, + {"message": "Added Model::query method for generating an empty query builder.", "backport": null}, + {"message": "The @yield Blade directive now accepts a default value as the second argument.", "backport": null}, + {"message": "Fixed bug causing null to be passed to auth.logout event.", "backport": null}, + {"message": "Added polyfill for array_column forward compatibility.", "backport": null}, + {"message": "Passing NULL to validator exists rule as extra condition will do where null check.", "backport": null}, + {"message": "Auth::extend Closures should only return UserProviderInterface implementations.", "backport": null}, + {"message": "Make it easier to extend the Request class.", "backport": null}, + {"message": "Transparent support for APCu cache via 'apc' driver.", "backport": null}, + {"message": "Add morphs short-cut for adding polymorphic schema columns.", "backport": null}, + {"message": "Namespaces are now excluded from guessed model names.", "backport": null}, + {"message": "Added new --command option to command:make Artisan command.", "backport": null}, + {"message": "Added mediumText and longText to schema builder.", "backport": null}, + {"message": "Added support for macros on the Response class.", "backport": null}, + {"message": "Added support for view creators in addition to composers.", "backport": null}, + {"message": "Allow App::down to be bypassed if the event returns null.", "backport": null}, + {"message": "Added Request::format function to get human-readable expected Response format.", "backport": null}, + {"message": "Allow array sizes to be checked by validator.", "backport": null}, + {"message": "Added support for where conditions on unique validation rule.", "backport": null}, + {"message": "Restore method on Eloquent models now fires restoring and restored events.", "backport": null}, + {"message": "Fixed re-population of radio buttons and checkboxes in FormBuilder.", "backport": null}, + {"message": "Postgres ENUMs are now more truly implemented using 'check' constraints.", "backport": null}, + {"message": "Added selectMonth and selectYear to FormBuilder.", "backport": null}, + {"message": "Fix container resolution of default values for non-scalar dependencies.", "backport": null}, + {"message": "Allow optional path to be specified with calling _path helpers.", "backport": null}, + {"message": "Emulate nested transactions in the database connection layer.", "backport": null}, + {"message": "Added new appends property to Eloquent for adding ot arrays and JSON.", "backport": null}, + {"message": "Allow connection to be configurable when using Redis based sessions.", "backport": null} + ] +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Foundation/start.php b/vendor/laravel/framework/src/Illuminate/Foundation/start.php new file mode 100755 index 0000000..659a6f9 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Foundation/start.php @@ -0,0 +1,254 @@ +instance('app', $app); + +/* +|-------------------------------------------------------------------------- +| Check For The Test Environment +|-------------------------------------------------------------------------- +| +| If the "unitTesting" variable is set, it means we are running the unit +| tests for the application and should override this environment here +| so we use the right configuration. The flag gets set by TestCase. +| +*/ + +if (isset($unitTesting)) +{ + $app['env'] = $env = $testEnvironment; +} + +/* +|-------------------------------------------------------------------------- +| Load The Illuminate Facades +|-------------------------------------------------------------------------- +| +| The facades provide a terser static interface over the various parts +| of the application, allowing their methods to be accessed through +| a mixtures of magic methods and facade derivatives. It's slick. +| +*/ + +Facade::clearResolvedInstances(); + +Facade::setFacadeApplication($app); + +/* +|-------------------------------------------------------------------------- +| Register The Configuration Repository +|-------------------------------------------------------------------------- +| +| The configuration repository is used to lazily load in the options for +| this application from the configuration files. The files are easily +| separated by their concerns so they do not become really crowded. +| +*/ + +$config = new Config($app->getConfigLoader(), $env); + +$app->instance('config', $config); + +/* +|-------------------------------------------------------------------------- +| Register Application Exception Handling +|-------------------------------------------------------------------------- +| +| We will go ahead and register the application exception handling here +| which will provide a great output of exception details and a stack +| trace in the case of exceptions while an application is running. +| +*/ + +$app->startExceptionHandling(); + +if ($env != 'testing') ini_set('display_errors', 'Off'); + +/* +|-------------------------------------------------------------------------- +| Set The Console Request If Necessary +|-------------------------------------------------------------------------- +| +| If we're running in a console context, we won't have a host on this +| request so we'll need to re-bind a new request with a URL from a +| configuration file. This will help the URL generator generate. +| +*/ + +if ($app->runningInConsole()) +{ + $app->setRequestForConsoleEnvironment(); +} + +/* +|-------------------------------------------------------------------------- +| Set The Default Timezone +|-------------------------------------------------------------------------- +| +| Here we will set the default timezone for PHP. PHP is notoriously mean +| if the timezone is not explicitly set. This will be used by each of +| the PHP date and date-time functions throughout the application. +| +*/ + +$config = $app['config']['app']; + +date_default_timezone_set($config['timezone']); + +/* +|-------------------------------------------------------------------------- +| Register The Alias Loader +|-------------------------------------------------------------------------- +| +| The alias loader is responsible for lazy loading the class aliases setup +| for the application. We will only register it if the "config" service +| is bound in the application since it contains the alias definitions. +| +*/ + +AliasLoader::getInstance($config['aliases'])->register(); + +/* +|-------------------------------------------------------------------------- +| Enable HTTP Method Override +|-------------------------------------------------------------------------- +| +| Next we will tell the request class to allow HTTP method overriding +| since we use this to simulate PUT and DELETE requests from forms +| as they are not currently supported by plain HTML form setups. +| +*/ + +Request::enableHttpMethodParameterOverride(); + +/* +|-------------------------------------------------------------------------- +| Register The Core Service Providers +|-------------------------------------------------------------------------- +| +| The Illuminate core service providers register all of the core pieces +| of the Illuminate framework including session, caching, encryption +| and more. It's simply a convenient wrapper for the registration. +| +*/ + +$providers = $config['providers']; + +$app->getProviderRepository()->load($app, $providers); + +/* +|-------------------------------------------------------------------------- +| Boot The Application +|-------------------------------------------------------------------------- +| +| Before we handle the requests we need to make sure the application has +| been booted up. The boot process will call the "boot" method on all +| service provider giving all a chance to register their overrides. +| +*/ + +$app->boot(); + +/* +|-------------------------------------------------------------------------- +| Load The Application Start Script +|-------------------------------------------------------------------------- +| +| The start script gives us the application the opportunity to override +| any of the existing IoC bindings, as well as register its own new +| bindings for things like repositories, etc. We'll load it here. +| +*/ + +$path = $app['path'].'/start/global.php'; + +if (file_exists($path)) require $path; + +/* +|-------------------------------------------------------------------------- +| Load The Environment Start Script +|-------------------------------------------------------------------------- +| +| The environment start script is only loaded if it exists for the app +| environment currently active, which allows some actions to happen +| in one environment while not in the other, keeping things clean. +| +*/ + +$path = $app['path']."/start/{$env}.php"; + +if (file_exists($path)) require $path; + +/* +|-------------------------------------------------------------------------- +| Load The Application Routes +|-------------------------------------------------------------------------- +| +| The Application routes are kept separate from the application starting +| just to keep the file a little cleaner. We'll go ahead and load in +| all of the routes now and return the application to the callers. +| +*/ + +if (file_exists($path = $app['path'].'/routes.php')) +{ + require $path; +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Hashing/BcryptHasher.php b/vendor/laravel/framework/src/Illuminate/Hashing/BcryptHasher.php new file mode 100755 index 0000000..214a889 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Hashing/BcryptHasher.php @@ -0,0 +1,60 @@ +rounds; + + $hash = password_hash($value, PASSWORD_BCRYPT, array('cost' => $cost)); + + if ($hash === false) + { + throw new \RuntimeException("Bcrypt hashing not supported."); + } + + return $hash; + } + + /** + * Check the given plain value against a hash. + * + * @param string $value + * @param string $hashedValue + * @param array $options + * @return bool + */ + public function check($value, $hashedValue, array $options = array()) + { + return password_verify($value, $hashedValue); + } + + /** + * Check if the given hash has been hashed using the given options. + * + * @param string $hashedValue + * @param array $options + * @return bool + */ + public function needsRehash($hashedValue, array $options = array()) + { + $cost = isset($options['rounds']) ? $options['rounds'] : $this->rounds; + + return password_needs_rehash($hashedValue, PASSWORD_BCRYPT, array('cost' => $cost)); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Hashing/HashServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Hashing/HashServiceProvider.php new file mode 100755 index 0000000..22291a7 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Hashing/HashServiceProvider.php @@ -0,0 +1,34 @@ +app['hash'] = $this->app->share(function() { return new BcryptHasher; }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('hash'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Hashing/HasherInterface.php b/vendor/laravel/framework/src/Illuminate/Hashing/HasherInterface.php new file mode 100755 index 0000000..b6f089b --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Hashing/HasherInterface.php @@ -0,0 +1,33 @@ +=5.3.0", + "illuminate/support": "4.0.x", + "ircmaxell/password-compat": "1.0.*" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Hashing": "" + } + }, + "target-dir": "Illuminate/Hashing", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Html/FormBuilder.php b/vendor/laravel/framework/src/Illuminate/Html/FormBuilder.php new file mode 100755 index 0000000..99308bc --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Html/FormBuilder.php @@ -0,0 +1,981 @@ +url = $url; + $this->html = $html; + $this->csrfToken = $csrfToken; + } + + /** + * Open up a new HTML form. + * + * @param array $options + * @return string + */ + public function open(array $options = array()) + { + $method = array_get($options, 'method', 'post'); + + // We need to extract the proper method from the attributes. If the method is + // something other than GET or POST we'll use POST since we will spoof the + // actual method since forms don't support the reserved methods in HTML. + $attributes['method'] = $this->getMethod($method); + + $attributes['action'] = $this->getAction($options); + + $attributes['accept-charset'] = 'UTF-8'; + + // If the method is PUT, PATCH or DELETE we will need to add a spoofer hidden + // field that will instruct the Symfony request to pretend the method is a + // different method than it actually is, for convenience from the forms. + $append = $this->getAppendage($method); + + if (isset($options['files']) and $options['files']) + { + $options['enctype'] = 'multipart/form-data'; + } + + // Finally we're ready to create the final form HTML field. We will attribute + // format the array of attributes. We will also add on the appendage which + // is used to spoof requests for this PUT, PATCH, etc. methods on forms. + $attributes = array_merge( + + $attributes, array_except($options, $this->reserved) + + ); + + // Finally, we will concatenate all of the attributes into a single string so + // we can build out the final form open statement. We'll also append on an + // extra value for the hidden _method field if it's needed for the form. + $attributes = $this->html->attributes($attributes); + + return ''.$append; + } + + /** + * Create a new model based form builder. + * + * @param mixed $model + * @param array $options + * @return string + */ + public function model($model, array $options = array()) + { + $this->model = $model; + + return $this->open($options); + } + + /** + * Close the current form. + * + * @return string + */ + public function close() + { + $this->labels = array(); + + $this->model = null; + + return ''; + } + + /** + * Generate a hidden field with the current CSRF token. + * + * @return string + */ + public function token() + { + return $this->hidden('_token', $this->csrfToken); + } + + /** + * Create a form label element. + * + * @param string $name + * @param string $value + * @param array $options + * @return string + */ + public function label($name, $value = null, $options = array()) + { + $this->labels[] = $name; + + $options = $this->html->attributes($options); + + $value = e($this->formatLabel($name, $value)); + + return ''; + } + + /** + * Format the label value. + * + * @param string $name + * @param string|null $value + * @return string + */ + protected function formatLabel($name, $value) + { + return $value ?: ucwords(str_replace('_', ' ', $name)); + } + + /** + * Create a form input field. + * + * @param string $type + * @param string $name + * @param string $value + * @param array $options + * @return string + */ + public function input($type, $name, $value = null, $options = array()) + { + if ( ! isset($options['name'])) $options['name'] = $name; + + // We will get the appropriate value for the given field. We will look for the + // value in the session for the value in the old input data then we'll look + // in the model instance if one is set. Otherwise we will just use empty. + $id = $this->getIdAttribute($name, $options); + + if ( ! in_array($type, $this->skipValueTypes)) + { + $value = $this->getValueAttribute($name, $value); + } + + // Once we have the type, value, and ID we can marge them into the rest of the + // attributes array so we can convert them into their HTML attribute format + // when creating the HTML element. Then, we will return the entire input. + $merge = compact('type', 'value', 'id'); + + $options = array_merge($options, $merge); + + return 'html->attributes($options).'>'; + } + + /** + * Create a text input field. + * + * @param string $name + * @param string $value + * @param array $options + * @return string + */ + public function text($name, $value = null, $options = array()) + { + return $this->input('text', $name, $value, $options); + } + + /** + * Create a password input field. + * + * @param string $name + * @param array $options + * @return string + */ + public function password($name, $options = array()) + { + return $this->input('password', $name, '', $options); + } + + /** + * Create a hidden input field. + * + * @param string $name + * @param string $value + * @param array $options + * @return string + */ + public function hidden($name, $value = null, $options = array()) + { + return $this->input('hidden', $name, $value, $options); + } + + /** + * Create an e-mail input field. + * + * @param string $name + * @param string $value + * @param array $options + * @return string + */ + public function email($name, $value = null, $options = array()) + { + return $this->input('email', $name, $value, $options); + } + + /** + * Create a file input field. + * + * @param string $name + * @param array $options + * @return string + */ + public function file($name, $options = array()) + { + return $this->input('file', $name, null, $options); + } + + /** + * Create a textarea input field. + * + * @param string $name + * @param string $value + * @param array $options + * @return string + */ + public function textarea($name, $value = null, $options = array()) + { + if ( ! isset($options['name'])) $options['name'] = $name; + + // Next we will look for the rows and cols attributes, as each of these are put + // on the textarea element definition. If they are not present, we will just + // assume some sane default values for these attributes for the developer. + $options = $this->setTextAreaSize($options); + + $options['id'] = $this->getIdAttribute($name, $options); + + $value = (string) $this->getValueAttribute($name, $value); + + unset($options['size']); + + // Next we will convert the attributes into a string form. Also we have removed + // the size attribute, as it was merely a short-cut for the rows and cols on + // the element. Then we'll create the final textarea elements HTML for us. + $options = $this->html->attributes($options); + + return ''.e($value).''; + } + + /** + * Set the text area size on the attributes. + * + * @param array $options + * @return array + */ + protected function setTextAreaSize($options) + { + if (isset($options['size'])) + { + return $this->setQuickTextAreaSize($options); + } + + // If the "size" attribute was not specified, we will just look for the regular + // columns and rows attributes, using sane defaults if these do not exist on + // the attributes array. We'll then return this entire options array back. + $cols = array_get($options, 'cols', 50); + + $rows = array_get($options, 'rows', 10); + + return array_merge($options, compact('cols', 'rows')); + } + + /** + * Set the text area size using the quick "size" attribute. + * + * @param array $options + * @return array + */ + protected function setQuickTextAreaSize($options) + { + $segments = explode('x', $options['size']); + + return array_merge($options, array('cols' => $segments[0], 'rows' => $segments[1])); + } + + /** + * Create a select box field. + * + * @param string $name + * @param array $list + * @param string $selected + * @param array $options + * @return string + */ + public function select($name, $list = array(), $selected = null, $options = array()) + { + // When building a select box the "value" attribute is really the selected one + // so we will use that when checking the model or session for a value which + // should provide a convenient method of re-populating the forms on post. + $selected = $this->getValueAttribute($name, $selected); + + $options['id'] = $this->getIdAttribute($name, $options); + + $options['name'] = $name; + + // We will simply loop through the options and build an HTML value for each of + // them until we have an array of HTML declarations. Then we will join them + // all together into one single HTML element that can be put on the form. + $html = array(); + + foreach ($list as $value => $display) + { + $html[] = $this->getSelectOption($display, $value, $selected); + } + + // Once we have all of this HTML, we can join this into a single element after + // formatting the attributes into an HTML "attributes" string, then we will + // build out a final select statement, which will contain all the values. + $options = $this->html->attributes($options); + + $list = implode('', $html); + + return "{$list}"; + } + + /** + * Create a select range field. + * + * @param string $name + * @param string $begin + * @param string $end + * @param string $selected + * @param array $options + * @return string + */ + public function selectRange($name, $begin, $end, $selected = null, $options = array()) + { + $range = array_combine($range = range($begin, $end), $range); + + return $this->select($name, $range, $selected, $options); + } + + /** + * Create a select year field. + * + * @param string $name + * @param string $begin + * @param string $end + * @param string $selected + * @param array $options + * @return string + */ + public function selectYear() + { + return call_user_func_array(array($this, 'selectRange'), func_get_args()); + } + + /** + * Create a select month field. + * + * @param string $name + * @param string $selected + * @param array $options + * @return string + */ + public function selectMonth($name, $selected = null, $options = array()) + { + $months = array(); + + foreach (range(1, 12) as $month) + { + $months[$month] = strftime('%B', mktime(0, 0, 0, $month, 1)); + } + + return $this->select($name, $months, $selected, $options); + } + + /** + * Get the select option for the given value. + * + * @param string $display + * @param string $value + * @param string $selected + * @return string + */ + protected function getSelectOption($display, $value, $selected) + { + if (is_array($display)) + { + return $this->optionGroup($display, $value, $selected); + } + + return $this->option($display, $value, $selected); + } + + /** + * Create an option group form element. + * + * @param array $list + * @param string $label + * @param string $selected + * @return string + */ + protected function optionGroup($list, $label, $selected) + { + $html = array(); + + foreach ($list as $value => $display) + { + $html[] = $this->option($display, $value, $selected); + } + + return ''.implode('', $html).''; + } + + /** + * Create a select element option. + * + * @param string $display + * @param string $value + * @param string $selected + * @return string + */ + protected function option($display, $value, $selected) + { + $selected = $this->getSelectedValue($value, $selected); + + $options = array('value' => e($value), 'selected' => $selected); + + return 'html->attributes($options).'>'.e($display).''; + } + + /** + * Determine if the value is selected. + * + * @param string $value + * @param string $selected + * @return string + */ + protected function getSelectedValue($value, $selected) + { + if (is_array($selected)) + { + return in_array($value, $selected) ? 'selected' : null; + } + + return ((string) $value == (string) $selected) ? 'selected' : null; + } + + /** + * Create a checkbox input field. + * + * @param string $name + * @param mixed $value + * @param bool $checked + * @param array $options + * @return string + */ + public function checkbox($name, $value = 1, $checked = null, $options = array()) + { + return $this->checkable('checkbox', $name, $value, $checked, $options); + } + + /** + * Create a radio button input field. + * + * @param string $name + * @param mixed $value + * @param bool $checked + * @param array $options + * @return string + */ + public function radio($name, $value = null, $checked = null, $options = array()) + { + if (is_null($value)) $value = $name; + + return $this->checkable('radio', $name, $value, $checked, $options); + } + + /** + * Create a checkable input field. + * + * @param string $type + * @param string $name + * @param mixed $value + * @param bool $checked + * @param array $options + * @return string + */ + protected function checkable($type, $name, $value, $checked, $options) + { + $checked = $this->getCheckedState($type, $name, $value, $checked); + + if ($checked) $options['checked'] = 'checked'; + + return $this->input($type, $name, $value, $options); + } + + /** + * Get the check state for a checkable input. + * + * @param string $type + * @param string $name + * @param mixed $value + * @param bool $checked + * @return void + */ + protected function getCheckedState($type, $name, $value, $checked) + { + switch ($type) + { + case 'checkbox': + return $this->getCheckboxCheckedState($name, $value, $checked); + + case 'radio': + return $this->getRadioCheckedState($name, $value, $checked); + + default: + return $this->getValueAttribute($name) == $value; + } + } + + /** + * Get the check state for a checkbox input. + * + * @param string $name + * @param mixed $value + * @param bool $checked + * @return bool + */ + protected function getCheckboxCheckedState($name, $value, $checked) + { + if ( ! $this->oldInputIsEmpty() and is_null($this->old($name))) return false; + + if ($this->missingOldAndModel($name)) return $checked; + + $posted = $this->getValueAttribute($name); + + return is_array($posted) ? in_array($value, $posted) : (bool) $posted; + } + + /** + * Get the check state for a radio input. + * + * @param string $name + * @param mixed $value + * @param bool $checked + * @return bool + */ + protected function getRadioCheckedState($name, $value, $checked) + { + if ($this->missingOldAndModel($name)) return $checked; + + return $this->getValueAttribute($name) == $value; + } + + /** + * Determine if old input or model input exists for a key. + * + * @param string $name + * @return bool + */ + protected function missingOldAndModel($name) + { + return (is_null($this->old($name)) and is_null($this->getModelValueAttribute($name))); + } + + /** + * Create a HTML reset input element. + * + * @param string $value + * @param array $attributes + * @return string + */ + public function reset($value, $attributes = array()) + { + return $this->input('reset', null, $value, $attributes); + } + + /** + * Create a HTML image input element. + * + * @param string $url + * @param string $name + * @param array $attributes + * @return string + */ + public function image($url, $name = null, $attributes = array()) + { + $attributes['src'] = $this->url->asset($url); + + return $this->input('image', $name, null, $attributes); + } + + /** + * Create a submit button element. + * + * @param string $value + * @param array $options + * @return string + */ + public function submit($value = null, $options = array()) + { + return $this->input('submit', null, $value, $options); + } + + /** + * Create a button element. + * + * @param string $value + * @param array $options + * @return string + */ + public function button($value = null, $options = array()) + { + if ( ! array_key_exists('type', $options) ) + { + $options['type'] = 'button'; + } + + return 'html->attributes($options).'>'.$value.''; + } + + /** + * Register a custom form macro. + * + * @param string $name + * @param callable $macro + * @return void + */ + public function macro($name, $macro) + { + $this->macros[$name] = $macro; + } + + /** + * Parse the form action method. + * + * @param string $method + * @return string + */ + protected function getMethod($method) + { + $method = strtoupper($method); + + return $method != 'GET' ? 'POST' : $method; + } + + /** + * Get the form action from the options. + * + * @param array $options + * @return string + */ + protected function getAction(array $options) + { + // We will also check for a "route" or "action" parameter on the array so that + // developers can easily specify a route or controller action when creating + // a form providing a convenient interface for creating the form actions. + if (isset($options['url'])) + { + return $this->getUrlAction($options['url']); + } + + if (isset($options['route'])) + { + return $this->getRouteAction($options['route']); + } + + // If an action is available, we are attempting to open a form to a controller + // action route. So, we will use the URL generator to get the path to these + // actions and return them from the method. Otherwise, we'll use current. + elseif (isset($options['action'])) + { + return $this->getControllerAction($options['action']); + } + + return $this->url->current(); + } + + /** + * Get the action for a "url" option. + * + * @param array|string $options + * @return string + */ + protected function getUrlAction($options) + { + if (is_array($options)) + { + return $this->url->to($options[0], array_slice($options, 1)); + } + + return $this->url->to($options); + } + + /** + * Get the action for a "route" option. + * + * @param array|string $options + * @return string + */ + protected function getRouteAction($options) + { + if (is_array($options)) + { + return $this->url->route($options[0], array_slice($options, 1)); + } + + return $this->url->route($options); + } + + /** + * Get the action for an "action" option. + * + * @param array|string $options + * @return string + */ + protected function getControllerAction($options) + { + if (is_array($options)) + { + return $this->url->action($options[0], array_slice($options, 1)); + } + + return $this->url->action($options); + } + + /** + * Get the form appendage for the given method. + * + * @param string $method + * @return string + */ + protected function getAppendage($method) + { + list($method, $appendage) = array(strtoupper($method), ''); + + // If the HTTP method is in this list of spoofed methods, we will attach the + // method spoofer hidden input to the form. This allows us to use regular + // form to initiate PUT and DELETE requests in addition to the typical. + if (in_array($method, $this->spoofedMethods)) + { + $appendage .= $this->hidden('_method', $method); + } + + // If the method is something other than GET we will go ahead and attach the + // CSRF token to the form, as this can't hurt and is convenient to simply + // always have available on every form the developers creates for them. + if ($method != 'GET') + { + $appendage .= $this->token(); + } + + return $appendage; + } + + /** + * Get the ID attribute for a field name. + * + * @param string $name + * @param array $attributes + * @return string + */ + protected function getIdAttribute($name, $attributes) + { + if (array_key_exists('id', $attributes)) + { + return $attributes['id']; + } + + if (in_array($name, $this->labels)) + { + return $name; + } + } + + /** + * Get the value that should be assigned to the field. + * + * @param string $name + * @param string $value + * @return string + */ + public function getValueAttribute($name, $value = null) + { + if (is_null($name)) return $value; + + if ( ! is_null($this->old($name))) + { + return $this->old($name); + } + + if ( ! is_null($value)) return $value; + + if (isset($this->model)) + { + return $this->getModelValueAttribute($name); + } + } + + /** + * Get the model value that should be assigned to the field. + * + * @param string $name + * @return string + */ + protected function getModelValueAttribute($name) + { + if (is_object($this->model)) + { + return object_get($this->model, $this->transformKey($name)); + } + elseif (is_array($this->model)) + { + return array_get($this->model, $this->transformKey($name)); + } + } + + /** + * Get a value from the session's old input. + * + * @param string $name + * @return string + */ + public function old($name) + { + if (isset($this->session)) + { + return $this->session->getOldInput($this->transformKey($name)); + } + } + + /** + * Determine if the old input is empty. + * + * @return bool + */ + public function oldInputIsEmpty() + { + return (isset($this->session) and count($this->session->getOldInput()) == 0); + } + + /** + * Transform key from array to dot syntax. + * + * @param string $key + * @return string + */ + protected function transformKey($key) + { + return str_replace(array('.', '[]', '[', ']'), array('_', '', '.', ''), $key); + } + + /** + * Get the session store implementation. + * + * @return \Illuminate\Session\Store $session + */ + public function getSessionStore() + { + return $this->session; + } + + /** + * Set the session store implementation. + * + * @param \Illuminate\Session\Store $session + * @return \Illuminate\Html\FormBuilder + */ + public function setSessionStore(Session $session) + { + $this->session = $session; + + return $this; + } + + /** + * Dynamically handle calls to the form builder. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (isset($this->macros[$method])) + { + return call_user_func_array($this->macros[$method], $parameters); + } + + throw new \BadMethodCallException("Method {$method} does not exist."); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Html/HtmlBuilder.php b/vendor/laravel/framework/src/Illuminate/Html/HtmlBuilder.php new file mode 100755 index 0000000..d6699cf --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Html/HtmlBuilder.php @@ -0,0 +1,407 @@ +url = $url; + } + + /** + * Register a custom HTML macro. + * + * @param string $name + * @param callable $macro + * @return void + */ + public function macro($name, $macro) + { + $this->macros[$name] = $macro; + } + + /** + * Convert an HTML string to entities. + * + * @param string $value + * @return string + */ + public function entities($value) + { + return htmlentities($value, ENT_QUOTES, 'UTF-8', false); + } + + /** + * Convert entities to HTML characters. + * + * @param string $value + * @return string + */ + public function decode($value) + { + return html_entity_decode($value, ENT_QUOTES, 'UTF-8'); + } + + /** + * Generate a link to a JavaScript file. + * + * @param string $url + * @param array $attributes + * @return string + */ + public function script($url, $attributes = array()) + { + $attributes['src'] = $this->url->asset($url); + + return 'attributes($attributes).'>'.PHP_EOL; + } + + /** + * Generate a link to a CSS file. + * + * @param string $url + * @param array $attributes + * @return string + */ + public function style($url, $attributes = array()) + { + $defaults = array('media' => 'all', 'type' => 'text/css', 'rel' => 'stylesheet'); + + $attributes = $attributes + $defaults; + + $attributes['href'] = $this->url->asset($url); + + return 'attributes($attributes).'>'.PHP_EOL; + } + + /** + * Generate an HTML image element. + * + * @param string $url + * @param string $alt + * @param array $attributes + * @return string + */ + public function image($url, $alt = null, $attributes = array()) + { + $attributes['alt'] = $alt; + + return 'attributes($attributes).'>'; + } + + /** + * Generate a HTML link. + * + * @param string $url + * @param string $title + * @param array $attributes + * @param bool $secure + * @return string + */ + public function link($url, $title = null, $attributes = array(), $secure = null) + { + $url = $this->url->to($url, array(), $secure); + + if (is_null($title) or $title === false) $title = $url; + + return 'attributes($attributes).'>'.$this->entities($title).''; + } + + /** + * Generate a HTTPS HTML link. + * + * @param string $url + * @param string $title + * @param array $attributes + * @return string + */ + public function secureLink($url, $title = null, $attributes = array()) + { + return $this->link($url, $title, $attributes, true); + } + + /** + * Generate a HTML link to an asset. + * + * @param string $url + * @param string $title + * @param array $attributes + * @param bool $secure + * @return string + */ + public function linkAsset($url, $title = null, $attributes = array(), $secure = null) + { + $url = $this->url->asset($url, $secure); + + return $this->link($url, $title ?: $url, $attributes, $secure); + } + + /** + * Generate a HTTPS HTML link to an asset. + * + * @param string $url + * @param string $title + * @param array $attributes + * @return string + */ + public function linkSecureAsset($url, $title = null, $attributes = array()) + { + return $this->linkAsset($url, $title, $attributes, true); + } + + /** + * Generate a HTML link to a named route. + * + * @param string $name + * @param string $title + * @param array $parameters + * @param array $attributes + * @return string + */ + public function linkRoute($name, $title = null, $parameters = array(), $attributes = array()) + { + return $this->link($this->url->route($name, $parameters), $title, $attributes); + } + + /** + * Generate a HTML link to a controller action. + * + * @param string $action + * @param string $title + * @param array $parameters + * @param array $attributes + * @return string + */ + public function linkAction($action, $title = null, $parameters = array(), $attributes = array()) + { + return $this->link($this->url->action($action, $parameters), $title, $attributes); + } + + /** + * Generate a HTML link to an email address. + * + * @param string $email + * @param string $title + * @param array $attributes + * @return string + */ + public function mailto($email, $title = null, $attributes = array()) + { + $email = $this->email($email); + + $title = $title ?: $email; + + $email = $this->obfuscate('mailto:') . $email; + + return 'attributes($attributes).'>'.$this->entities($title).''; + } + + /** + * Obfuscate an e-mail address to prevent spam-bots from sniffing it. + * + * @param string $email + * @return string + */ + public function email($email) + { + return str_replace('@', '@', $this->obfuscate($email)); + } + + /** + * Generate an ordered list of items. + * + * @param array $list + * @param array $attributes + * @return string + */ + public function ol($list, $attributes = array()) + { + return $this->listing('ol', $list, $attributes); + } + + /** + * Generate an un-ordered list of items. + * + * @param array $list + * @param array $attributes + * @return string + */ + public function ul($list, $attributes = array()) + { + return $this->listing('ul', $list, $attributes); + } + + /** + * Create a listing HTML element. + * + * @param string $type + * @param array $list + * @param array $attributes + * @return string + */ + protected function listing($type, $list, $attributes = array()) + { + $html = ''; + + if (count($list) == 0) return $html; + + // Essentially we will just spin through the list and build the list of the HTML + // elements from the array. We will also handled nested lists in case that is + // present in the array. Then we will build out the final listing elements. + foreach ($list as $key => $value) + { + $html .= $this->listingElement($key, $type, $value); + } + + $attributes = $this->attributes($attributes); + + return "<{$type}{$attributes}>{$html}"; + } + + /** + * Create the HTML for a listing element. + * + * @param mixed $key + * @param string $type + * @param string $value + * @return string + */ + protected function listingElement($key, $type, $value) + { + if (is_array($value)) + { + return $this->nestedListing($key, $type, $value); + } + else + { + return '
  • '.e($value).'
  • '; + } + } + + /** + * Create the HTML for a nested listing attribute. + * + * @param mixed $key + * @param string $type + * @param string $value + * @return string + */ + protected function nestedListing($key, $type, $value) + { + if (is_int($key)) + { + return $this->listing($type, $value); + } + else + { + return '
  • '.$key.$this->listing($type, $value).'
  • '; + } + } + + /** + * Build an HTML attribute string from an array. + * + * @param array $attributes + * @return string + */ + public function attributes($attributes) + { + $html = array(); + + // For numeric keys we will assume that the key and the value are the same + // as this will convert HTML attributes such as "required" to a correct + // form like required="required" instead of using incorrect numerics. + foreach ((array) $attributes as $key => $value) + { + $element = $this->attributeElement($key, $value); + + if ( ! is_null($element)) $html[] = $element; + } + + return count($html) > 0 ? ' '.implode(' ', $html) : ''; + } + + /** + * Build a single attribute element. + * + * @param string $key + * @param string $value + * @return string + */ + protected function attributeElement($key, $value) + { + if (is_numeric($key)) $key = $value; + + if ( ! is_null($value)) return $key.'="'.e($value).'"'; + } + + /** + * Obfuscate a string to prevent spam-bots from sniffing it. + * + * @param string $value + * @return string + */ + public function obfuscate($value) + { + $safe = ''; + + foreach (str_split($value) as $letter) + { + // To properly obfuscate the value, we will randomly convert each letter to + // its entity or hexadecimal representation, keeping a bot from sniffing + // the randomly obfuscated letters out of the string on the responses. + switch (rand(1, 3)) + { + case 1: + $safe .= '&#'.ord($letter).';'; break; + + case 2: + $safe .= '&#x'.dechex(ord($letter)).';'; break; + + case 3: + $safe .= $letter; + } + } + + return $safe; + } + + /** + * Dynamically handle calls to the html class. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (isset($this->macros[$method])) + { + return call_user_func_array($this->macros[$method], $parameters); + } + + throw new \BadMethodCallException("Method {$method} does not exist."); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Html/HtmlServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Html/HtmlServiceProvider.php new file mode 100755 index 0000000..4082e08 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Html/HtmlServiceProvider.php @@ -0,0 +1,64 @@ +registerHtmlBuilder(); + + $this->registerFormBuilder(); + } + + /** + * Register the HTML builder instance. + * + * @return void + */ + protected function registerHtmlBuilder() + { + $this->app['html'] = $this->app->share(function($app) + { + return new HtmlBuilder($app['url']); + }); + } + + /** + * Register the form builder instance. + * + * @return void + */ + protected function registerFormBuilder() + { + $this->app['form'] = $this->app->share(function($app) + { + $form = new FormBuilder($app['html'], $app['url'], $app['session']->getToken()); + + return $form->setSessionStore($app['session']); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('html', 'form'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Html/composer.json b/vendor/laravel/framework/src/Illuminate/Html/composer.json new file mode 100755 index 0000000..eb01588 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Html/composer.json @@ -0,0 +1,32 @@ +{ + "name": "illuminate/html", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "illuminate/http": "4.0.x", + "illuminate/session": "4.0.x", + "illuminate/support": "4.0.x" + }, + "require-dev": { + "mockery/mockery": "0.7.2", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Html": "" + } + }, + "target-dir": "Illuminate/Html", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/laravel/framework/src/Illuminate/Http/JsonResponse.php b/vendor/laravel/framework/src/Illuminate/Http/JsonResponse.php new file mode 100755 index 0000000..1271c61 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Http/JsonResponse.php @@ -0,0 +1,17 @@ +data = $data instanceof JsonableInterface ? $data->toJson() : json_encode($data); + + return $this->update(); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Http/RedirectResponse.php b/vendor/laravel/framework/src/Illuminate/Http/RedirectResponse.php new file mode 100755 index 0000000..bac9688 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Http/RedirectResponse.php @@ -0,0 +1,157 @@ + $v) $this->with($k, $v); + } + else + { + $this->session->flash($key, $value); + } + + return $this; + } + + /** + * Add a cookie to the response. + * + * @param \Symfony\Component\HttpFoundation\Cookie $cookie + * @return \Illuminate\Http\RedirectResponse + */ + public function withCookie(Cookie $cookie) + { + $this->headers->setCookie($cookie); + + return $this; + } + + /** + * Flash an array of input to the session. + * + * @param array $input + * @return \Illuminate\Http\RedirectResponse + */ + public function withInput(array $input = null) + { + $input = $input ?: $this->request->input(); + + $this->session->flashInput($input); + + return $this; + } + + /** + * Flash an array of input to the session. + * + * @param dynamic string + * @return \Illuminate\Http\RedirectResponse + */ + public function onlyInput() + { + return $this->withInput($this->request->only(func_get_args())); + } + + /** + * Flash an array of input to the session. + * + * @param dynamic string + * @return \Illuminate\Http\RedirectResponse + */ + public function exceptInput() + { + return $this->withInput($this->request->except(func_get_args())); + } + + /** + * Flash a container of errors to the session. + * + * @param \Illuminate\Support\Contracts\MessageProviderInterface|array $provider + * @return \Illuminate\Http\RedirectResponse + */ + public function withErrors($provider) + { + if ($provider instanceof MessageProviderInterface) + { + $this->with('errors', $provider->getMessageBag()); + } + else + { + $this->with('errors', new MessageBag((array) $provider)); + } + + return $this; + } + + /** + * Get the request instance. + * + * @return \Illuminate\Http\Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * Set the request instance. + * + * @param \Illuminate\Http\Request $request + * @return void + */ + public function setRequest(Request $request) + { + $this->request = $request; + } + + /** + * Get the session store implementation. + * + * @return \Illuminate\Session\Store + */ + public function getSession() + { + return $this->session; + } + + /** + * Set the session store implementation. + * + * @param \Illuminate\Session\Store $store + * @return void + */ + public function setSession(SessionStore $session) + { + $this->session = $session; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Http/Request.php b/vendor/laravel/framework/src/Illuminate/Http/Request.php new file mode 100755 index 0000000..d4761a7 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Http/Request.php @@ -0,0 +1,502 @@ +getSchemeAndHttpHost().$this->getBaseUrl(), '/'); + } + + /** + * Get the URL (no query string) for the request. + * + * @return string + */ + public function url() + { + return rtrim(preg_replace('/\?.*/', '', $this->getUri()), '/'); + } + + /** + * Get the full URL for the request. + * + * @return string + */ + public function fullUrl() + { + $query = $this->getQueryString(); + + return $query ? $this->url().'?'.$query : $this->url(); + } + + /** + * Get the current path info for the request. + * + * @return string + */ + public function path() + { + $pattern = trim($this->getPathInfo(), '/'); + + return $pattern == '' ? '/' : $pattern; + } + + /** + * Get a segment from the URI (1 based index). + * + * @param string $index + * @param mixed $default + * @return string + */ + public function segment($index, $default = null) + { + $segments = explode('/', trim($this->getPathInfo(), '/')); + + $segments = array_filter($segments, function($v) { return $v != ''; }); + + return array_get($segments, $index - 1, $default); + } + + /** + * Get all of the segments for the request path. + * + * @return array + */ + public function segments() + { + $path = $this->path(); + + return $path == '/' ? array() : explode('/', $path); + } + + /** + * Determine if the current request URI matches a pattern. + * + * @param string $pattern + * @return bool + */ + public function is($pattern) + { + foreach (func_get_args() as $pattern) + { + if (str_is($pattern, $this->path())) + { + return true; + } + } + + return false; + } + + /** + * Determine if the request is the result of an AJAX call. + * + * @return bool + */ + public function ajax() + { + return $this->isXmlHttpRequest(); + } + + /** + * Determine if the request is over HTTPS. + * + * @return bool + */ + public function secure() + { + return $this->isSecure(); + } + + /** + * Determine if the request contains a given input item. + * + * @param string|array $key + * @return bool + */ + public function has($key) + { + if (count(func_get_args()) > 1) + { + foreach (func_get_args() as $value) + { + if ( ! $this->has($value)) return false; + } + + return true; + } + + if (is_bool($this->input($key)) or is_array($this->input($key))) + { + return true; + } + + return trim((string) $this->input($key)) !== ''; + } + + /** + * Get all of the input and files for the request. + * + * @return array + */ + public function all() + { + return $this->input() + $this->files->all(); + } + + /** + * Retrieve an input item from the request. + * + * @param string $key + * @param mixed $default + * @return string + */ + public function input($key = null, $default = null) + { + $input = $this->getInputSource()->all() + $this->query->all(); + + return array_get($input, $key, $default); + } + + /** + * Get a subset of the items from the input data. + * + * @param array $keys + * @return array + */ + public function only($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + return array_only($this->input(), $keys) + array_fill_keys($keys, null); + } + + /** + * Get all of the input except for a specified array of items. + * + * @param array $keys + * @return array + */ + public function except($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + $results = $this->input(); + + foreach ($keys as $key) array_forget($results, $key); + + return $results; + } + + /** + * Retrieve a query string item from the request. + * + * @param string $key + * @param mixed $default + * @return string + */ + public function query($key = null, $default = null) + { + return $this->retrieveItem('query', $key, $default); + } + + /** + * Retrieve a cookie from the request. + * + * @param string $key + * @param mixed $default + * @return string + */ + public function cookie($key = null, $default = null) + { + return $this->retrieveItem('cookies', $key, $default); + } + + /** + * Retrieve a file from the request. + * + * @param string $key + * @param mixed $default + * @return \Symfony\Component\HttpFoundation\File\UploadedFile + */ + public function file($key = null, $default = null) + { + return $this->retrieveItem('files', $key, $default); + } + + /** + * Determine if the uploaded data contains a file. + * + * @param string $key + * @return bool + */ + public function hasFile($key) + { + return $this->files->has($key) and ! is_null($this->file($key)); + } + + /** + * Retrieve a header from the request. + * + * @param string $key + * @param mixed $default + * @return string + */ + public function header($key = null, $default = null) + { + return $this->retrieveItem('headers', $key, $default); + } + + /** + * Retrieve a server variable from the request. + * + * @param string $key + * @param mixed $default + * @return string + */ + public function server($key = null, $default = null) + { + return $this->retrieveItem('server', $key, $default); + } + + /** + * Retrieve an old input item. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function old($key = null, $default = null) + { + return $this->getSessionStore()->getOldInput($key, $default); + } + + /** + * Flash the input for the current request to the session. + * + * @param string $filter + * @param array $keys + * @return void + */ + public function flash($filter = null, $keys = array()) + { + $flash = ( ! is_null($filter)) ? $this->$filter($keys) : $this->input(); + + $this->getSessionStore()->flashInput($flash); + } + + /** + * Flash only some of the input to the session. + * + * @param dynamic string + * @return void + */ + public function flashOnly($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + return $this->flash('only', $keys); + } + + /** + * Flash only some of the input to the session. + * + * @param dynamic string + * @return void + */ + public function flashExcept($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + return $this->flash('except', $keys); + } + + /** + * Flush all of the old input from the session. + * + * @return void + */ + public function flush() + { + $this->getSessionStore()->flashInput(array()); + } + + /** + * Retrieve a parameter item from a given source. + * + * @param string $source + * @param string $key + * @param mixed $default + * @return string + */ + protected function retrieveItem($source, $key, $default) + { + if (is_null($key)) + { + return $this->$source->all(); + } + else + { + return $this->$source->get($key, $default, true); + } + } + + /** + * Merge new input into the current request's input array. + * + * @param array $input + * @return void + */ + public function merge(array $input) + { + $this->getInputSource()->add($input); + } + + /** + * Replace the input for the current request. + * + * @param array $input + * @return void + */ + public function replace(array $input) + { + $this->getInputSource()->replace($input); + } + + /** + * Get the JSON payload for the request. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function json($key = null, $default = null) + { + if ( ! isset($this->json)) + { + $this->json = new ParameterBag((array) json_decode($this->getContent(), true)); + } + + if (is_null($key)) return $this->json; + + return array_get($this->json->all(), $key, $default); + } + + /** + * Get the input source for the request. + * + * @return \Symfony\Component\HttpFoundation\ParameterBag + */ + protected function getInputSource() + { + if ($this->isJson()) return $this->json(); + + return $this->getMethod() == 'GET' ? $this->query : $this->request; + } + + /** + * Determine if the request is sending JSON. + * + * @return bool + */ + public function isJson() + { + return str_contains($this->header('CONTENT_TYPE'), '/json'); + } + + /** + * Determine if the current request is asking for JSON in return. + * + * @return bool + */ + public function wantsJson() + { + $acceptable = $this->getAcceptableContentTypes(); + + return isset($acceptable[0]) and $acceptable[0] == 'application/json'; + } + + /** + * Get the data format expected in the response. + * + * @return string + */ + public function format($default = 'html') + { + foreach ($this->getAcceptableContentTypes() as $type) + { + if ($format = $this->getFormat($type)) return $format; + } + + return $default; + } + + /** + * Get the Illuminate session store implementation. + * + * @return \Illuminate\Session\Store + */ + public function getSessionStore() + { + if ( ! isset($this->sessionStore)) + { + throw new \RuntimeException("Session store not set on request."); + } + + return $this->sessionStore; + } + + /** + * Set the Illuminate session store implementation. + * + * @param \Illuminate\Session\Store $session + * @return void + */ + public function setSessionStore(SessionStore $session) + { + $this->sessionStore = $session; + } + + /** + * Determine if the session store has been set. + * + * @return bool + */ + public function hasSessionStore() + { + return isset($this->sessionStore); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Http/Response.php b/vendor/laravel/framework/src/Illuminate/Http/Response.php new file mode 100755 index 0000000..972f646 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Http/Response.php @@ -0,0 +1,109 @@ +headers->set($key, $value, $replace); + + return $this; + } + + /** + * Add a cookie to the response. + * + * @param \Symfony\Component\HttpFoundation\Cookie $cookie + * @return \Illuminate\Http\Response + */ + public function withCookie(Cookie $cookie) + { + $this->headers->setCookie($cookie); + + return $this; + } + + /** + * Set the content on the response. + * + * @param mixed $content + * @return void + */ + public function setContent($content) + { + $this->original = $content; + + // If the content is "JSONable" we will set the appropriate header and convert + // the content to JSON. This is useful when returning something like models + // from routes that will be automatically transformed to their JSON form. + if ($this->shouldBeJson($content)) + { + $this->headers->set('Content-Type', 'application/json'); + + $content = $this->morphToJson($content); + } + + // If this content implements the "RenderableInterface", then we will call the + // render method on the object so we will avoid any "__toString" exceptions + // that might be thrown and have their errors obscured by PHP's handling. + elseif ($content instanceof RenderableInterface) + { + $content = $content->render(); + } + + return parent::setContent($content); + } + + /** + * Morph the given content into JSON. + * + * @param mixed $content + * @return string + */ + protected function morphToJson($content) + { + if ($content instanceof JsonableInterface) return $content->toJson(); + + return json_encode($content); + } + + /** + * Determine if the given content should be turned into JSON. + * + * @param mixed $content + * @return bool + */ + protected function shouldBeJson($content) + { + return $content instanceof JsonableInterface or is_array($content); + } + + /** + * Get the original response content. + * + * @return mixed + */ + public function getOriginalContent() + { + return $this->original; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Http/composer.json b/vendor/laravel/framework/src/Illuminate/Http/composer.json new file mode 100755 index 0000000..73b9026 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Http/composer.json @@ -0,0 +1,31 @@ +{ + "name": "illuminate/http", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "illuminate/session": "4.0.x", + "illuminate/support": "4.0.x", + "symfony/http-foundation": "2.3.*" + }, + "require-dev": { + "mockery/mockery": "0.7.2", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Http": "" + } + }, + "target-dir": "Illuminate/Http", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/laravel/framework/src/Illuminate/Log/LogServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Log/LogServiceProvider.php new file mode 100755 index 0000000..3e7a574 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Log/LogServiceProvider.php @@ -0,0 +1,44 @@ +app['events']); + + $this->app->instance('log', $logger); + + // If the setup Closure has been bound in the container, we will resolve it + // and pass in the logger instance. This allows this to defer all of the + // logger class setup until the last possible second, improving speed. + if (isset($this->app['log.setup'])) + { + call_user_func($this->app['log.setup'], $logger); + } + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('log'); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Log/Writer.php b/vendor/laravel/framework/src/Illuminate/Log/Writer.php new file mode 100755 index 0000000..0422fd1 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Log/Writer.php @@ -0,0 +1,213 @@ +monolog = $monolog; + + if (isset($dispatcher)) + { + $this->dispatcher = $dispatcher; + } + } + + /** + * Register a file log handler. + * + * @param string $path + * @param string $level + * @return void + */ + public function useFiles($path, $level = 'debug') + { + $level = $this->parseLevel($level); + + $this->monolog->pushHandler(new StreamHandler($path, $level)); + } + + /** + * Register a daily file log handler. + * + * @param string $path + * @param int $days + * @param string $level + * @return void + */ + public function useDailyFiles($path, $days = 0, $level = 'debug') + { + $level = $this->parseLevel($level); + + $this->monolog->pushHandler(new RotatingFileHandler($path, $days, $level)); + } + + /** + * Parse the string level into a Monolog constant. + * + * @param string $level + * @return int + */ + protected function parseLevel($level) + { + switch ($level) + { + case 'debug': + return MonologLogger::DEBUG; + + case 'info': + return MonologLogger::INFO; + + case 'notice': + return MonologLogger::NOTICE; + + case 'warning': + return MonologLogger::WARNING; + + case 'error': + return MonologLogger::ERROR; + + case 'critical': + return MonologLogger::CRITICAL; + + case 'alert': + return MonologLogger::ALERT; + + case 'emergency': + return MonologLogger::EMERGENCY; + + default: + throw new \InvalidArgumentException("Invalid log level."); + } + } + + /** + * Get the underlying Monolog instance. + * + * @return \Monolog\Logger + */ + public function getMonolog() + { + return $this->monolog; + } + + /** + * Register a new callback handler for when + * a log event is triggered. + * + * @param Closure $callback + * @return void + */ + public function listen(Closure $callback) + { + if ( ! isset($this->dispatcher)) + { + throw new \RuntimeException("Events dispatcher has not been set."); + } + + $this->dispatcher->listen('illuminate.log', $callback); + } + + /** + * Get the event dispatcher instance. + * + * @return \Illuminate\Events\Dispatcher + */ + public function getEventDispatcher() + { + return $this->dispatcher; + } + + /** + * Set the event dispatcher instance. + * + * @param \Illuminate\Events\Dispatcher + * @return void + */ + public function setEventDispatcher(Dispatcher $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * Fires a log event. + * + * @param string $level + * @param array $parameters + * @return void + */ + protected function fireLogEvent($level, $message, array $context = array()) + { + // If the event dispatcher is set, we will pass along the parameters to the + // log listeners. These are useful for building profilers or other tools + // that aggregate all of the log messages for a given "request" cycle. + if (isset($this->dispatcher)) + { + $this->dispatcher->fire('illuminate.log', compact('level', 'message', 'context')); + } + } + + /** + * Dynamically handle error additions. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + if (in_array($method, $this->levels)) + { + call_user_func_array(array($this, 'fireLogEvent'), array_merge(array($method), $parameters)); + + $method = 'add'.ucfirst($method); + + return call_user_func_array(array($this->monolog, $method), $parameters); + } + + throw new \BadMethodCallException("Method [$method] does not exist."); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Log/composer.json b/vendor/laravel/framework/src/Illuminate/Log/composer.json new file mode 100755 index 0000000..b898bf2 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Log/composer.json @@ -0,0 +1,32 @@ +{ + "name": "illuminate/log", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "illuminate/support": "4.0.x", + "monolog/monolog": "1.3.*" + }, + "require-dev": { + "mockery/mockery": "0.7.2", + "phpunit/phpunit": "3.7.*", + "illuminate/events": "4.0.x" + }, + "autoload": { + "psr-0": { + "Illuminate\\Log": "" + } + }, + "target-dir": "Illuminate/Log", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/laravel/framework/src/Illuminate/Mail/MailServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Mail/MailServiceProvider.php new file mode 100755 index 0000000..2f4de28 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Mail/MailServiceProvider.php @@ -0,0 +1,177 @@ +registerSwiftMailer(); + + $this->app['mailer'] = $this->app->share(function($app) + { + // Once we have create the mailer instance, we will set a container instance + // on the mailer. This allows us to resolve mailer classes via containers + // for maximum testability on said classes instead of passing Closures. + $mailer = new Mailer($app['view'], $app['swift.mailer']); + + $mailer->setLogger($app['log'])->setQueue($app['queue']); + + $mailer->setContainer($app); + + // If a "from" address is set, we will set it on the mailer so that all mail + // messages sent by the applications will utilize the same "from" address + // on each one, which makes the developer's life a lot more convenient. + $from = $app['config']['mail.from']; + + if (is_array($from) and isset($from['address'])) + { + $mailer->alwaysFrom($from['address'], $from['name']); + } + + // Here we will determine if the mailer should be in "pretend" mode for this + // environment, which will simply write out e-mail to the logs instead of + // sending it over the web, which is useful for local dev enviornments. + $pretend = $app['config']->get('mail.pretend', false); + + $mailer->pretend($pretend); + + return $mailer; + }); + } + + /** + * Register the Swift Mailer instance. + * + * @return void + */ + protected function registerSwiftMailer() + { + $config = $this->app['config']['mail']; + + $this->registerSwiftTransport($config); + + // Once we have the transporter registered, we will register the actual Swift + // mailer instance, passing in the transport instances, which allows us to + // override this transporter instances during app start-up if necessary. + $this->app['swift.mailer'] = $this->app->share(function($app) + { + return new Swift_Mailer($app['swift.transport']); + }); + } + + /** + * Register the Swift Transport instance. + * + * @param array $config + * @return void + */ + protected function registerSwiftTransport($config) + { + switch ($config['driver']) + { + case 'smtp': + return $this->registerSmtpTransport($config); + + case 'sendmail': + return $this->registerSendmailTransport($config); + + case 'mail': + return $this->registerMailTransport($config); + + default: + throw new \InvalidArgumentException('Invalid mail driver.'); + } + } + + /** + * Register the SMTP Swift Transport instance. + * + * @param array $config + * @return void + */ + protected function registerSmtpTransport($config) + { + $this->app['swift.transport'] = $this->app->share(function($app) use ($config) + { + extract($config); + + // The Swift SMTP transport instance will allow us to use any SMTP backend + // for delivering mail such as Sendgrid, Amazon SMS, or a custom server + // a developer has available. We will just pass this configured host. + $transport = SmtpTransport::newInstance($host, $port); + + if (isset($encryption)) + { + $transport->setEncryption($encryption); + } + + // Once we have the transport we will check for the presence of a username + // and password. If we have it we will set the credentials on the Swift + // transporter instance so that we'll properly authenticate delivery. + if (isset($username)) + { + $transport->setUsername($username); + + $transport->setPassword($password); + } + + return $transport; + }); + } + + /** + * Register the Sendmail Swift Transport instance. + * + * @param array $config + * @return void + */ + protected function registerSendmailTransport($config) + { + $this->app['swift.transport'] = $this->app->share(function($app) use ($config) + { + return SendmailTransport::newInstance($config['sendmail']); + }); + } + + /** + * Register the Mail Swift Transport instance. + * + * @param array $config + * @return void + */ + protected function registerMailTransport($config) + { + $this->app['swift.transport'] = $this->app->share(function() + { + return MailTransport::newInstance(); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('mailer', 'swift.mailer', 'swift.transport'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Mail/Mailer.php b/vendor/laravel/framework/src/Illuminate/Mail/Mailer.php new file mode 100755 index 0000000..c157482 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Mail/Mailer.php @@ -0,0 +1,444 @@ +views = $views; + $this->swift = $swift; + } + + /** + * Set the global from address and name. + * + * @param string $address + * @param string $name + * @return void + */ + public function alwaysFrom($address, $name = null) + { + $this->from = compact('address', 'name'); + } + + /** + * Send a new message when only a plain part. + * + * @param string $view + * @param array $data + * @param mixed $callback + * @return void + */ + public function plain($view, array $data, $callback) + { + return $this->send(array('text' => $view), $data, $callback); + } + + /** + * Send a new message using a view. + * + * @param string|array $view + * @param array $data + * @param Closure|string $callback + * @return void + */ + public function send($view, array $data, $callback) + { + // First we need to parse the view, which could either be a string or an array + // containing both an HTML and plain text versions of the view which should + // be used when sending an e-mail. We will extract both of them out here. + list($view, $plain) = $this->parseView($view); + + $data['message'] = $message = $this->createMessage(); + + $this->callMessageBuilder($callback, $message); + + // Once we have retrieved the view content for the e-mail we will set the body + // of this message using the HTML type, which will provide a simple wrapper + // to creating view based emails that are able to receive arrays of data. + $this->addContent($message, $view, $plain, $data); + + $message = $message->getSwiftMessage(); + + return $this->sendSwiftMessage($message); + } + + /** + * Queue a new e-mail message for sending. + * + * @param string|array $view + * @param array $data + * @param Closure|string $callback + * @param string $queue + * @return void + */ + public function queue($view, array $data, $callback, $queue = null) + { + $callback = $this->buildQueueCallable($callback); + + $this->queue->push('mailer@handleQueuedMessage', compact('view', 'data', 'callback'), $queue); + } + + /** + * Queue a new e-mail message for sending on the given queue. + * + * @param string|array $view + * @param array $data + * @param Closure|string $callback + * @param string $queue + * @return void + */ + public function queueOn($queue, $view, array $data, $callback) + { + return $this->queue($view, $data, $callback, $queue); + } + + /** + * Queue a new e-mail message for sending after (n) seconds. + * + * @param int $delay + * @param string|array $view + * @param array $data + * @param Closure|string $callback + * @param string $queue + * @return void + */ + public function later($delay, $view, array $data, $callback, $queue = null) + { + $callback = $this->buildQueueCallable($callback); + + $this->queue->later($delay, 'mailer@handleQueuedMessage', compact('view', 'data', 'callback'), $queue); + } + + /** + * Queue a new e-mail message for sending after (n) seconds on the given queue. + * + * @param string $queue + * @param int $delay + * @param string|array $view + * @param array $data + * @param Closure|string $callback + * @return void + */ + public function laterOn($queue, $delay, $view, array $data, $callback) + { + return $this->later($delay, $view, $data, $callback, $queue); + } + + /** + * Build the callable for a queued e-mail job. + * + * @param mixed $callback + * @return mixed + */ + protected function buildQueueCallable($callback) + { + if ( ! $callback instanceof Closure) return $callback; + + return serialize(new SerializableClosure($callback)); + } + + /** + * Handle a queued e-mail message job. + * + * @param \Illuminate\Queue\Jobs\Job $job + * @param array $data + * @return void + */ + public function handleQueuedMessage($job, $data) + { + $this->send($data['view'], $data['data'], $this->getQueuedCallable($data)); + + $job->delete(); + } + + /** + * Get the true callable for a queued e-mail message. + * + * @param array $data + * @return mixed + */ + protected function getQueuedCallable(array $data) + { + if (str_contains($data['callback'], 'SerializableClosure')) + { + return with(unserialize($data['callback']))->getClosure(); + } + + return $data['callback']; + } + + /** + * Add the content to a given message. + * + * @param \Illuminate\Mail\Message $message + * @param string $view + * @param string $plain + * @param array $data + * @return void + */ + protected function addContent($message, $view, $plain, $data) + { + if (isset($view)) + { + $message->setBody($this->getView($view, $data), 'text/html'); + } + + if (isset($plain)) + { + $message->addPart($this->getView($plain, $data), 'text/plain'); + } + } + + /** + * Parse the given view name or array. + * + * @param string|array $view + * @return array + */ + protected function parseView($view) + { + if (is_string($view)) return array($view, null); + + // If the given view is an array with numeric keys, we will just assume that + // both a "pretty" and "plain" view were provided, so we will return this + // array as is, since must should contain both views with numeric keys. + if (is_array($view) and isset($view[0])) + { + return $view; + } + + // If the view is an array, but doesn't contain numeric keys, we will assume + // the the views are being explicitly specified and will extract them via + // named keys instead, allowing the developers to use one or the other. + elseif (is_array($view)) + { + return array( + array_get($view, 'html'), array_get($view, 'text') + ); + } + + throw new \InvalidArgumentException("Invalid view."); + } + + /** + * Send a Swift Message instance. + * + * @param Swift_Message $message + * @return void + */ + protected function sendSwiftMessage($message) + { + if ( ! $this->pretending) + { + return $this->swift->send($message); + } + elseif (isset($this->logger)) + { + $this->logMessage($message); + } + } + + /** + * Log that a message was sent. + * + * @param Swift_Message $message + * @return void + */ + protected function logMessage($message) + { + $emails = implode(', ', array_keys((array) $message->getTo())); + + $this->logger->info("Pretending to mail message to: {$emails}"); + } + + /** + * Call the provided message builder. + * + * @param Closure|string $callback + * @param \Illuminate\Mail\Message $message + * @return void + */ + protected function callMessageBuilder($callback, $message) + { + if ($callback instanceof Closure) + { + return call_user_func($callback, $message); + } + elseif (is_string($callback)) + { + return $this->container[$callback]->mail($message); + } + + throw new \InvalidArgumentException("Callback is not valid."); + } + + /** + * Create a new message instance. + * + * @return \Illuminate\Mail\Message + */ + protected function createMessage() + { + $message = new Message(new Swift_Message); + + // If a global from address has been specified we will set it on every message + // instances so the developer does not have to repeat themselves every time + // they create a new message. We will just go ahead and push the address. + if (isset($this->from['address'])) + { + $message->from($this->from['address'], $this->from['name']); + } + + return $message; + } + + /** + * Render the given view. + * + * @param string $view + * @param array $data + * @return \Illuminate\View\View + */ + protected function getView($view, $data) + { + return $this->views->make($view, $data)->render(); + } + + /** + * Tell the mailer to not really send messages. + * + * @param bool $value + * @return void + */ + public function pretend($value = true) + { + $this->pretending = $value; + } + + /** + * Get the view environment instance. + * + * @return \Illuminate\View\Environment + */ + public function getViewEnvironment() + { + return $this->views; + } + + /** + * Get the Swift Mailer instance. + * + * @return Swift_Mailer + */ + public function getSwiftMailer() + { + return $this->swift; + } + + /** + * Set the Swift Mailer instance. + * + * @param Swift_Mailer $swift + * @return void + */ + public function setSwiftMailer($swift) + { + $this->swift = $swift; + } + + /** + * Set the log writer instance. + * + * @param \Illuminate\Log\Writer $logger + * @return \Illuminate\Mail\Mailer + */ + public function setLogger(Writer $logger) + { + $this->logger = $logger; + + return $this; + } + + /** + * Set the queue manager instance. + * + * @param \Illuminate\Queue\QueueManager $queue + * @return \Illuminate\Mail\Mailer + */ + public function setQueue(QueueManager $queue) + { + $this->queue = $queue; + + return $this; + } + + /** + * Set the IoC container instance. + * + * @param \Illuminate\Container\Container $container + * @return void + */ + public function setContainer(Container $container) + { + $this->container = $container; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Mail/Message.php b/vendor/laravel/framework/src/Illuminate/Mail/Message.php new file mode 100755 index 0000000..dac1bd0 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Mail/Message.php @@ -0,0 +1,295 @@ +swift = $swift; + } + + /** + * Add a "from" address to the message. + * + * @param string $address + * @param string $name + * @return \Illuminate\Mail\Message + */ + public function from($address, $name = null) + { + $this->swift->setFrom($address, $name); + + return $this; + } + + /** + * Set the "sender" of the message. + * + * @param string $address + * @param string $name + * @return \Illuminate\Mail\Message + */ + public function sender($address, $name = null) + { + $this->swift->setSender($address, $name); + + return $this; + } + + /** + * Set the "return path" of the message. + * + * @param string $address + * @return \Illuminate\Mail\Message + */ + public function returnPath($address) + { + $this->swift->setReturnPath($address); + + return $this; + } + + /** + * Add a recipient to the message. + * + * @param string|array $address + * @param string $name + * @return \Illuminate\Mail\Message + */ + public function to($address, $name = null) + { + return $this->addAddresses($address, $name, 'To'); + } + + /** + * Add a carbon copy to the message. + * + * @param string $address + * @param string $name + * @return \Illuminate\Mail\Message + */ + public function cc($address, $name = null) + { + return $this->addAddresses($address, $name, 'Cc'); + } + + /** + * Add a blind carbon copy to the message. + * + * @param string $address + * @param string $name + * @return \Illuminate\Mail\Message + */ + public function bcc($address, $name = null) + { + return $this->addAddresses($address, $name, 'Bcc'); + } + + /** + * Add a reply to address to the message. + * + * @param string $address + * @param string $name + * @return \Illuminate\Mail\Message + */ + public function replyTo($address, $name = null) + { + return $this->addAddresses($address, $name, 'ReplyTo'); + } + + /** + * Add a recipient to the message. + * + * @param string|array $address + * @param string $name + * @return \Illuminate\Mail\Message + */ + protected function addAddresses($address, $name, $type) + { + if (is_array($address)) + { + $this->swift->{"set{$type}"}($address, $name); + } + else + { + $this->swift->{"add{$type}"}($address, $name); + } + + return $this; + } + + /** + * Set the subject of the message. + * + * @param string $subject + * @return \Illuminate\Mail\Message + */ + public function subject($subject) + { + $this->swift->setSubject($subject); + + return $this; + } + + /** + * Set the message priority level. + * + * @param int $level + * @return \Illuminate\Mail\Message + */ + public function priority($level) + { + $this->swift->setPriority($level); + + return $this; + } + + /** + * Attach a file to the message. + * + * @param string $file + * @param array $options + * @return \Illuminate\Mail\Message + */ + public function attach($file, array $options = array()) + { + $attachment = $this->createAttachmentFromPath($file); + + return $this->prepAttachment($attachment, $options); + } + + /** + * Create a Swift Attachment instance. + * + * @param string $file + * @return Swift_Attachment + */ + protected function createAttachmentFromPath($file) + { + return Swift_Attachment::fromPath($file); + } + + /** + * Attach in-memory data as an attachment. + * + * @param string $data + * @param string $name + * @param array $options + * @return \Illuminate\Mail\Message + */ + public function attachData($data, $name, array $options = array()) + { + $attachment = $this->createAttachmentFromData($data, $name); + + return $this->prepAttachment($attachment, $options); + } + + /** + * Create a Swift Attachment instance from data. + * + * @param string $data + * @param string $name + * @return Swift_Attachment + */ + protected function createAttachmentFromData($data, $name) + { + return Swift_Attachment::newInstance($data, $name); + } + + /** + * Embed a file in the message and get the CID. + * + * @param string $file + * @return string + */ + public function embed($file) + { + return $this->swift->embed(Swift_Image::fromPath($file)); + } + + /** + * Embed in-memory data in the message and get the CID. + * + * @param string $data + * @param string $name + * @param string $contentType + * @return string + */ + public function embedData($data, $name, $contentType = null) + { + $image = Swift_Image::newInstance($data, $name, $contentType); + + return $this->swift->embed($image); + } + + /** + * Prepare and attach the given attachment. + * + * @param Swift_Attachment $attachment + * @param array $options + * @return \Illuminate\Mail\Message + */ + protected function prepAttachment($attachment, $options = array()) + { + // First we will check for a MIME type on the message, which instructs the + // mail client on what type of attachment the file is so that it may be + // downloaded correctly by the user. The MIME option is not required. + if (isset($options['mime'])) + { + $attachment->setContentType($options['mime']); + } + + // If an alternative name was given as an option, we will set that on this + // attachment so that it will be downloaded with the desired names from + // the developer, otherwise the default file names will get assigned. + if (isset($options['as'])) + { + $attachment->setFilename($options['as']); + } + + $this->swift->attach($attachment); + + return $this; + } + + /** + * Get the underlying Swift Message instance. + * + * @return Swift_Message + */ + public function getSwiftMessage() + { + return $this->swift; + } + + /** + * Dynamically pass missing methods to the Swift instance. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + $callable = array($this->swift, $method); + + return call_user_func_array($callable, $parameters); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Mail/composer.json b/vendor/laravel/framework/src/Illuminate/Mail/composer.json new file mode 100755 index 0000000..99e39bf --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Mail/composer.json @@ -0,0 +1,35 @@ +{ + "name": "illuminate/mail", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "illuminate/container": "4.0.x", + "illuminate/log": "4.0.x", + "illuminate/support": "4.0.x", + "illuminate/view": "4.0.x", + "swiftmailer/swiftmailer": "4.3.*" + }, + "require-dev": { + "illuminate/queue": "4.0.x", + "mockery/mockery": "0.7.2", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Mail": "" + } + }, + "target-dir": "Illuminate/Mail", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Pagination/BootstrapPresenter.php b/vendor/laravel/framework/src/Illuminate/Pagination/BootstrapPresenter.php new file mode 100755 index 0000000..fc24056 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Pagination/BootstrapPresenter.php @@ -0,0 +1,255 @@ +paginator = $paginator; + $this->lastPage = $this->paginator->getLastPage(); + $this->currentPage = $this->paginator->getCurrentPage(); + } + + /** + * Render the Bootstrap pagination contents. + * + * @return string + */ + public function render() + { + // The hard-coded thirteen represents the minimum number of pages we need to + // be able to create a sliding page window. If we have less than that, we + // will just render a simple range of page links insteadof the sliding. + if ($this->lastPage < 13) + { + $content = $this->getPageRange(1, $this->lastPage); + } + else + { + $content = $this->getPageSlider(); + } + + return $this->getPrevious().$content.$this->getNext(); + } + + /** + * Create a range of pagination links. + * + * @param int $start + * @param int $end + * @return string + */ + public function getPageRange($start, $end) + { + $pages = array(); + + for ($page = $start; $page <= $end; $page++) + { + // If the current page is equal to the page we're iterating on, we will create a + // disabled link for that page. Otherwise, we can create a typical active one + // for the link. These views use the "Twitter Bootstrap" styles by default. + if ($this->currentPage == $page) + { + $pages[] = '
  • '.$page.'
  • '; + } + else + { + $pages[] = $this->getLink($page); + } + } + + return implode('', $pages); + } + + /** + * Create a pagination slider link window. + * + * @return string + */ + protected function getPageSlider() + { + $window = 6; + + // If the current page is very close to the beginning of the page range, we will + // just render the beginning of the page range, followed by the last 2 of the + // links in this list, since we will not have room to create a full slider. + if ($this->currentPage <= $window) + { + $ending = $this->getFinish(); + + return $this->getPageRange(1, $window + 2).$ending; + } + + // If the current page is close to the ending of the page range we will just get + // this first couple pages, followed by a larger window of these ending pages + // since we're too close to the end of the list to create a full on slider. + elseif ($this->currentPage >= $this->lastPage - $window) + { + $start = $this->lastPage - 8; + + $content = $this->getPageRange($start, $this->lastPage); + + return $this->getStart().$content; + } + + // If we have enough room on both sides of the current page to build a slider we + // will surround it with both the beginning and ending caps, with this window + // of pages in the middle providing a Google style sliding paginator setup. + else + { + $content = $this->getAdjacentRange(); + + return $this->getStart().$content.$this->getFinish(); + } + } + + /** + * Get the page range for the current page window. + * + * @return string + */ + public function getAdjacentRange() + { + return $this->getPageRange($this->currentPage - 3, $this->currentPage + 3); + } + + /** + * Create the beginning leader of a pagination slider. + * + * @return string + */ + public function getStart() + { + return $this->getPageRange(1, 2).$this->getDots(); + } + + /** + * Create the ending cap of a pagination slider. + * + * @return string + */ + public function getFinish() + { + $content = $this->getPageRange($this->lastPage - 1, $this->lastPage); + + return $this->getDots().$content; + } + + /** + * Get the previous page pagination element. + * + * @param string $text + * @return string + */ + public function getPrevious($text = '«') + { + // If the current page is less than or equal to one, it means we can't go any + // further back in the pages, so we will render a disabled previous button + // when that is the case. Otherwise, we will give it an active "status". + if ($this->currentPage <= 1) + { + return '
  • '.$text.'
  • '; + } + else + { + $url = $this->paginator->getUrl($this->currentPage - 1); + + return '
  • '.$text.'
  • '; + } + } + + /** + * Get the next page pagination element. + * + * @param string $text + * @return string + */ + public function getNext($text = '»') + { + // If the current page is greater than or equal to the last page, it means we + // can't go any further into the pages, as we're already on this last page + // that is available, so we will make it the "next" link style disabled. + if ($this->currentPage >= $this->lastPage) + { + return '
  • '.$text.'
  • '; + } + else + { + $url = $this->paginator->getUrl($this->currentPage + 1); + + return '
  • '.$text.'
  • '; + } + } + + /** + * Get a pagination "dot" element. + * + * @return string + */ + public function getDots() + { + return '
  • ...
  • '; + } + + /** + * Create a pagination slider link. + * + * @param mixed $page + * @return string + */ + public function getLink($page) + { + $url = $this->paginator->getUrl($page); + + return '
  • '.$page.'
  • '; + } + + /** + * Set the value of the current page. + * + * @param int $page + * @return void + */ + public function setCurrentPage($page) + { + $this->currentPage = $page; + } + + /** + * Set the value of the last page. + * + * @param int $page + * @return void + */ + public function setLastPage($page) + { + $this->lastPage = $page; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Pagination/Environment.php b/vendor/laravel/framework/src/Illuminate/Pagination/Environment.php new file mode 100755 index 0000000..c31489a --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Pagination/Environment.php @@ -0,0 +1,285 @@ +view = $view; + $this->trans = $trans; + $this->request = $request; + $this->pageName = $pageName; + $this->setupPaginationEnvironment(); + } + + /** + * Setup the pagination environment. + * + * @return void + */ + protected function setupPaginationEnvironment() + { + $this->view->addNamespace('pagination', __DIR__.'/views'); + } + + /** + * Get a new paginator instance. + * + * @param array $items + * @param int $total + * @param int $perPage + * @return \Illuminate\Pagination\Paginator + */ + public function make(array $items, $total, $perPage) + { + $paginator = new Paginator($this, $items, $total, $perPage); + + return $paginator->setupPaginationContext(); + } + + /** + * Get the pagination view. + * + * @param \Illuminate\Pagination\Paginator $paginator + * @return \Illuminate\View\View + */ + public function getPaginationView(Paginator $paginator) + { + $data = array('environment' => $this, 'paginator' => $paginator); + + return $this->view->make($this->getViewName(), $data); + } + + /** + * Get the number of the current page. + * + * @return int + */ + public function getCurrentPage() + { + $page = (int) $this->currentPage ?: $this->request->query->get($this->pageName, 1); + + if ($page < 1 or filter_var($page, FILTER_VALIDATE_INT) === false) + { + return 1; + } + + return $page; + } + + /** + * Set the number of the current page. + * + * @param int $number + * @return void + */ + public function setCurrentPage($number) + { + $this->currentPage = $number; + } + + /** + * Get the root URL for the request. + * + * @return string + */ + public function getCurrentUrl() + { + return $this->baseUrl ?: $this->request->url(); + } + + /** + * Set the base URL in use by the paginator. + * + * @param string $baseUrl + * @return void + */ + public function setBaseUrl($baseUrl) + { + $this->baseUrl = $baseUrl; + } + + /** + * Set the input page parameter name used by the paginator. + * + * @param string $pageName + * @return void + */ + public function setPageName($pageName) + { + $this->pageName = $pageName; + } + + /** + * Get the input page parameter name used by the paginator. + * + * @return string + */ + public function getPageName() + { + return $this->pageName; + } + + /** + * Get the name of the pagination view. + * + * @return string + */ + public function getViewName() + { + return $this->viewName ?: 'pagination::slider'; + } + + /** + * Set the name of the pagination view. + * + * @param string $viewName + * @return void + */ + public function setViewName($viewName) + { + $this->viewName = $viewName; + } + + /** + * Get the locale of the paginator. + * + * @return string + */ + public function getLocale() + { + return $this->locale; + } + + /** + * Set the locale of the paginator. + * + * @param string $locale + * @return void + */ + public function setLocale($locale) + { + $this->locale = $locale; + } + + /** + * Get the active request instance. + * + * @return \Symfony\Component\HttpFoundation\Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * Set the active request instance. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @return void + */ + public function setRequest(Request $request) + { + $this->request = $request; + } + + /** + * Get the current view driver. + * + * @return \Illuminate\View\Environment + */ + public function getViewDriver() + { + return $this->view; + } + + /** + * Set the current view driver. + * + * @param \Illuminate\View\Environment $view + * @return void + */ + public function setViewDriver(ViewEnvironment $view) + { + $this->view = $view; + } + + /** + * Get the translator instance. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public function getTranslator() + { + return $this->trans; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Pagination/PaginationServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Pagination/PaginationServiceProvider.php new file mode 100755 index 0000000..06125e2 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Pagination/PaginationServiceProvider.php @@ -0,0 +1,41 @@ +app['paginator'] = $this->app->share(function($app) + { + $paginator = new Environment($app['request'], $app['view'], $app['translator']); + + $paginator->setViewName($app['config']['view.pagination']); + + return $paginator; + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('paginator'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Pagination/Paginator.php b/vendor/laravel/framework/src/Illuminate/Pagination/Paginator.php new file mode 100755 index 0000000..1994c1b --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Pagination/Paginator.php @@ -0,0 +1,425 @@ +env = $env; + $this->total = $total; + $this->items = $items; + $this->perPage = $perPage; + } + + /** + * Setup the pagination context (current and last page). + * + * @return \Illuminate\Pagination\Paginator + */ + public function setupPaginationContext() + { + $this->calculateCurrentAndLastPages(); + + $this->calculateItemRanges(); + + return $this; + } + + /** + * Calculate the current and last pages for this instance. + * + * @return void + */ + protected function calculateCurrentAndLastPages() + { + $this->lastPage = ceil($this->total / $this->perPage); + + $this->currentPage = $this->calculateCurrentPage($this->lastPage); + } + + /** + * Calculate the first and last item number for this instance. + * + * @return void + */ + protected function calculateItemRanges() + { + $this->from = $this->total ? ($this->currentPage - 1) * $this->perPage + 1 : 0; + + $this->to = min($this->total, $this->currentPage * $this->perPage); + } + + /** + * Get the current page for the request. + * + * @param int $lastPage + * @return int + */ + protected function calculateCurrentPage($lastPage) + { + $page = $this->env->getCurrentPage(); + + // The page number will get validated and adjusted if it either less than one + // or greater than the last page available based on the count of the given + // items array. If it's greater than the last, we'll give back the last. + if (is_numeric($page) and $page > $lastPage) + { + return $lastPage > 0 ? $lastPage : 1; + } + + return $this->isValidPageNumber($page) ? (int) $page : 1; + } + + /** + * Determine if the given value is a valid page number. + * + * @param int $page + * @return bool + */ + protected function isValidPageNumber($page) + { + return $page >= 1 and filter_var($page, FILTER_VALIDATE_INT) !== false; + } + + /** + * Get the pagination links view. + * + * @return \Illuminate\View\View + */ + public function links() + { + return $this->env->getPaginationView($this); + } + + /** + * Get a URL for a given page number. + * + * @param int $page + * @return string + */ + public function getUrl($page) + { + $parameters = array( + $this->env->getPageName() => $page, + ); + + // If we have any extra query string key / value pairs that need to be added + // onto the URL, we will put them in query string form and then attach it + // to the URL. This allows for extra information like sortings storage. + if (count($this->query) > 0) + { + $parameters = array_merge($parameters, $this->query); + } + + return $this->env->getCurrentUrl().'?'.http_build_query($parameters, null, '&'); + } + + /** + * Add a query string value to the paginator. + * + * @param string $key + * @param string $value + * @return \Illuminate\Pagination\Paginator + */ + public function appends($key, $value = null) + { + if (is_array($key)) return $this->appendArray($key); + + return $this->addQuery($key, $value); + } + + /** + * Add an array of query string values. + * + * @param array $keys + * @return \Illuminate\Pagination\Paginator + */ + protected function appendArray(array $keys) + { + foreach ($keys as $key => $value) + { + $this->addQuery($key, $value); + } + + return $this; + } + + /** + * Add a query string value to the paginator. + * + * @param string $key + * @param string $value + * @return \Illuminate\Pagination\Paginator + */ + public function addQuery($key, $value) + { + $this->query[$key] = $value; + + return $this; + } + + /** + * Get the current page for the request. + * + * @return int + */ + public function getCurrentPage() + { + return $this->currentPage; + } + + /** + * Get the last page that should be available. + * + * @return int + */ + public function getLastPage() + { + return $this->lastPage; + } + + /** + * Get the number of the first item on the paginator. + * + * @return int + */ + public function getFrom() + { + return $this->from; + } + + /** + * Get the number of the last item on the paginator. + * + * @return int + */ + public function getTo() + { + return $this->to; + } + + /** + * Get the number of items to be displayed per page. + * + * @return int + */ + public function getPerPage() + { + return $this->perPage; + } + + /** + * Get a collection instance containing the items. + * + * @return \Illuminate\Support\Collection + */ + public function getCollection() + { + return new Collection($this->items); + } + + /** + * Get the items being paginated. + * + * @return array + */ + public function getItems() + { + return $this->items; + } + + /** + * Set the items being paginated. + * + * @param mixed $items + * @return void + */ + public function setItems($items) + { + $this->items = $items; + } + + /** + * Get the total number of items in the collection. + * + * @return int + */ + public function getTotal() + { + return $this->total; + } + + /** + * Set the base URL in use by the paginator. + * + * @param string $baseUrl + * @return void + */ + public function setBaseUrl($baseUrl) + { + $this->env->setBaseUrl($baseUrl); + } + + /** + * Get the pagination environment. + * + * @return \Illuminate\Pagination\Environment + */ + public function getEnvironment() + { + return $this->env; + } + + /** + * Get an iterator for the items. + * + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->items); + } + + /** + * Determine if the list of items is empty or not. + * + * @return bool + */ + public function isEmpty() + { + return empty($this->items); + } + + /** + * Get the number of items for the current page. + * + * @return int + */ + public function count() + { + return count($this->items); + } + + /** + * Determine if the given item exists. + * + * @param mixed $key + * @return bool + */ + public function offsetExists($key) + { + return array_key_exists($key, $this->items); + } + + /** + * Get the item at the given offset. + * + * @param mixed $key + * @return mixed + */ + public function offsetGet($key) + { + return $this->items[$key]; + } + + /** + * Set the item at the given offset. + * + * @param mixed $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value) + { + $this->items[$key] = $value; + } + + /** + * Unset the item at the given key. + * + * @param mixed $key + * @return void + */ + public function offsetUnset($key) + { + unset($this->items[$key]); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Pagination/composer.json b/vendor/laravel/framework/src/Illuminate/Pagination/composer.json new file mode 100755 index 0000000..479310e --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Pagination/composer.json @@ -0,0 +1,34 @@ +{ + "name": "illuminate/pagination", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "illuminate/http": "4.0.x", + "illuminate/support": "4.0.x", + "illuminate/view": "4.0.x", + "symfony/http-foundation": "2.3.*", + "symfony/translation": "2.3.*" + }, + "require-dev": { + "mockery/mockery": "0.7.2", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Pagination": "" + } + }, + "target-dir": "Illuminate/Pagination", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/laravel/framework/src/Illuminate/Pagination/views/simple.php b/vendor/laravel/framework/src/Illuminate/Pagination/views/simple.php new file mode 100755 index 0000000..3a13482 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Pagination/views/simple.php @@ -0,0 +1,15 @@ +getTranslator(); +?> + +getLastPage() > 1): ?> +
      + getPrevious($trans->trans('pagination.previous')); + + echo $presenter->getNext($trans->trans('pagination.next')); + ?> +
    + diff --git a/vendor/laravel/framework/src/Illuminate/Pagination/views/slider.php b/vendor/laravel/framework/src/Illuminate/Pagination/views/slider.php new file mode 100755 index 0000000..0583e71 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Pagination/views/slider.php @@ -0,0 +1,11 @@ + + +getLastPage() > 1): ?> + + \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Queue/BeanstalkdQueue.php b/vendor/laravel/framework/src/Illuminate/Queue/BeanstalkdQueue.php new file mode 100755 index 0000000..74c59a6 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/BeanstalkdQueue.php @@ -0,0 +1,106 @@ +default = $default; + $this->pheanstalk = $pheanstalk; + } + + /** + * Push a new job onto the queue. + * + * @param string $job + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function push($job, $data = '', $queue = null) + { + $payload = $this->createPayload($job, $data); + + return $this->pheanstalk->useTube($this->getQueue($queue))->put($payload); + } + + /** + * Push a new job onto the queue after a delay. + * + * @param int $delay + * @param string $job + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function later($delay, $job, $data = '', $queue = null) + { + $payload = $this->createPayload($job, $data); + + $pheanstalk = $this->pheanstalk->useTube($this->getQueue($queue)); + + return $pheanstalk->put($payload, Pheanstalk::DEFAULT_PRIORITY, $delay); + } + + /** + * Pop the next job off of the queue. + * + * @param string $queue + * @return \Illuminate\Queue\Jobs\Job|null + */ + public function pop($queue = null) + { + $job = $this->pheanstalk->watchOnly($this->getQueue($queue))->reserve(0); + + if ($job instanceof Pheanstalk_Job) + { + return new BeanstalkdJob($this->container, $this->pheanstalk, $job); + } + } + + /** + * Get the queue or return the default. + * + * @param string|null $queue + * @return string + */ + protected function getQueue($queue) + { + return $queue ?: $this->default; + } + + /** + * Get the underlying Pheanstalk instance. + * + * @return Pheanstalk + */ + public function getPheanstalk() + { + return $this->pheanstalk; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Queue/Connectors/BeanstalkdConnector.php b/vendor/laravel/framework/src/Illuminate/Queue/Connectors/BeanstalkdConnector.php new file mode 100755 index 0000000..1a3855d --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/Connectors/BeanstalkdConnector.php @@ -0,0 +1,21 @@ +crypt = $crypt; + $this->request = $request; + } + + /** + * Establish a queue connection. + * + * @param array $config + * @return \Illuminate\Queue\QueueInterface + */ + public function connect(array $config) + { + $ironConfig = array('token' => $config['token'], 'project_id' => $config['project']); + + if (isset($config['host'])) $ironConfig['host'] = $config['host']; + + return new IronQueue(new IronMQ($ironConfig), $this->crypt, $this->request, $config['queue']); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Queue/Connectors/SqsConnector.php b/vendor/laravel/framework/src/Illuminate/Queue/Connectors/SqsConnector.php new file mode 100755 index 0000000..0ef5ab1 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/Connectors/SqsConnector.php @@ -0,0 +1,25 @@ + $config['key'], 'secret' => $config['secret'], 'region' => $config['region'], + + )); + + return new SqsQueue($sqs, $config['queue']); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Queue/Connectors/SyncConnector.php b/vendor/laravel/framework/src/Illuminate/Queue/Connectors/SyncConnector.php new file mode 100755 index 0000000..8a81c9f --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/Connectors/SyncConnector.php @@ -0,0 +1,18 @@ +listener = $listener; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->listener->setEnvironment($this->laravel->environment()); + + $delay = $this->input->getOption('delay'); + + // The memory limit is the amount of memory we will allow the script to occupy + // before killing it and letting a process manager restart it for us, which + // is to protect us against any memory leaks that will be in the scripts. + $memory = $this->input->getOption('memory'); + + $connection = $this->input->getArgument('connection'); + + $timeout = $this->input->getOption('timeout'); + + // We need to get the right queue for the connection which is set in the queue + // configuration file for the application. We will pull it based on the set + // connection being run for the queue operation currently being executed. + $queue = $this->getQueue($connection); + + $this->listener->listen($connection, $queue, $delay, $memory, $timeout); + } + + /** + * Get the name of the queue connection to listen on. + * + * @param string $connection + * @return string + */ + protected function getQueue($connection) + { + if (is_null($connection)) + { + $connection = $this->laravel['config']['queue.default']; + } + + $queue = $this->laravel['config']->get("queue.connections.{$connection}.queue", 'default'); + + return $this->input->getOption('queue') ?: $queue; + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return array( + array('connection', InputArgument::OPTIONAL, 'The name of connection'), + ); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('queue', null, InputOption::VALUE_OPTIONAL, 'The queue to listen on', null), + + array('delay', null, InputOption::VALUE_OPTIONAL, 'Amount of time to delay failed jobs', 0), + + array('memory', null, InputOption::VALUE_OPTIONAL, 'The memory limit in megabytes', 128), + + array('timeout', null, InputOption::VALUE_OPTIONAL, 'Seconds a job may run before timing out', 60), + ); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Queue/Console/SubscribeCommand.php b/vendor/laravel/framework/src/Illuminate/Queue/Console/SubscribeCommand.php new file mode 100755 index 0000000..50a4c2e --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/Console/SubscribeCommand.php @@ -0,0 +1,151 @@ +laravel['queue']->connection(); + + if ( ! $iron instanceof IronQueue) + { + throw new RuntimeException("Iron.io based queue must be default."); + } + + $iron->getIron()->updateQueue($this->argument('queue'), $this->getQueueOptions()); + + $this->line('Queue subscriber added: '.$this->argument('url').''); + } + + /** + * Get the queue options. + * + * @return array + */ + protected function getQueueOptions() + { + return array( + 'push_type' => $this->getPushType(), 'subscribers' => $this->getSubscriberList() + ); + } + + /** + * Get the push type for the queue. + * + * @return string + */ + protected function getPushType() + { + if ($this->option('type')) return $this->option('type'); + + try + { + return $this->getQueue()->push_type; + } + catch (\Exception $e) + { + return 'multicast'; + } + } + + /** + * Get the current subscribers for the queue. + * + * @return array + */ + protected function getSubscriberList() + { + $subscribers = $this->getCurrentSubscribers(); + + $subscribers[] = array('url' => $this->argument('url')); + + return $subscribers; + } + + /** + * Get the current subscriber list. + * + * @return array + */ + protected function getCurrentSubscribers() + { + try + { + return $this->getQueue()->subscribers; + } + catch (\Exception $e) + { + return array(); + } + } + + /** + * Get the queue inforamtion from Iron.io. + * + * @return \StdClass + */ + protected function getQueue() + { + if (isset($this->meta)) return $this->meta; + + return $this->meta = $this->laravel['queue']->getIron()->getQueue($this->argument('queue')); + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return array( + array('queue', InputArgument::REQUIRED, 'The name of Iron.io queue.'), + + array('url', InputArgument::REQUIRED, 'The URL to be subscribed.'), + ); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('type', null, InputOption::VALUE_OPTIONAL, 'The push type for the queue.'), + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php b/vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php new file mode 100755 index 0000000..e10e55d --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php @@ -0,0 +1,95 @@ +worker = $worker; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $queue = $this->input->getOption('queue'); + + $delay = $this->input->getOption('delay'); + + // The memory limit is the amount of memory we will allow the script to occupy + // before killing it and letting a process manager restart it for us, which + // is to protect us against any memory leaks that will be in the scripts. + $memory = $this->input->getOption('memory'); + + $connection = $this->input->getArgument('connection'); + + $this->worker->pop($connection, $queue, $delay, $memory, $this->input->getOption('sleep')); + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return array( + array('connection', InputArgument::OPTIONAL, 'The name of connection', null), + ); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('queue', null, InputOption::VALUE_OPTIONAL, 'The queue to listen on'), + + array('delay', null, InputOption::VALUE_OPTIONAL, 'Amount of time to delay failed jobs', 0), + + array('memory', null, InputOption::VALUE_OPTIONAL, 'The memory limit in megabytes', 128), + + array('sleep', null, InputOption::VALUE_NONE, 'Whether the worker should sleep when no job is available'), + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Queue/IlluminateQueueClosure.php b/vendor/laravel/framework/src/Illuminate/Queue/IlluminateQueueClosure.php new file mode 100755 index 0000000..4f58325 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/IlluminateQueueClosure.php @@ -0,0 +1,19 @@ +iron = $iron; + $this->crypt = $crypt; + $this->request = $request; + $this->default = $default; + } + + /** + * Push a new job onto the queue. + * + * @param string $job + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function push($job, $data = '', $queue = null) + { + $payload = $this->createPayload($job, $data); + + return $this->iron->postMessage($this->getQueue($queue), $payload)->id; + } + + /** + * Push a new job onto the queue after a delay. + * + * @param int $delay + * @param string $job + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function later($delay, $job, $data = '', $queue = null) + { + $payload = $this->createPayload($job, $data); + + return $this->iron->postMessage($this->getQueue($queue), $payload, compact('delay'))->id; + } + + /** + * Pop the next job off of the queue. + * + * @param string $queue + * @return \Illuminate\Queue\Jobs\Job|null + */ + public function pop($queue = null) + { + $queue = $this->getQueue($queue); + + $job = $this->iron->getMessage($queue); + + // If we were able to pop a message off of the queue, we will need to decrypt + // the message body, as all Iron.io messages are encrypted, since the push + // queues will be a security hazard to unsuspecting developers using it. + if ( ! is_null($job)) + { + $job->body = $this->crypt->decrypt($job->body); + + return new IronJob($this->container, $this->iron, $job, $queue); + } + } + + /** + * Marshal a push queue request and fire the job. + * + * @return Illuminate\Http\Response + */ + public function marshal() + { + $this->createPushedIronJob($this->marshalPushedJob())->fire(); + + return new Response('OK'); + } + + /** + * Marshal out the pushed job and payload. + * + * @return StdClass + */ + protected function marshalPushedJob() + { + $r = $this->request; + + $body = $this->crypt->decrypt($r->getContent()); + + return (object) array( + 'id' => $r->header('iron-message-id'), 'body' => $body, 'pushed' => true, + ); + } + + /** + * Create a new IronJob for a pushed job. + * + * @param \StdClass $job + * @return \Illuminate\Queue\Jobs\IronJob + */ + protected function createPushedIronJob($job) + { + return new IronJob($this->container, $this->iron, $job, $this->default, true); + } + + /** + * Create a payload string from the given job and data. + * + * @param string $job + * @param mixed $data + * @return string + */ + protected function createPayload($job, $data = '') + { + return $this->crypt->encrypt(parent::createPayload($job, $data)); + } + + /** + * Get the queue or return the default. + * + * @param string|null $queue + * @return string + */ + public function getQueue($queue) + { + return $queue ?: $this->default; + } + + /** + * Get the underlying IronMQ instance. + * + * @return IronMQ + */ + public function getIron() + { + return $this->iron; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Queue/Jobs/BeanstalkdJob.php b/vendor/laravel/framework/src/Illuminate/Queue/Jobs/BeanstalkdJob.php new file mode 100755 index 0000000..c99c798 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/Jobs/BeanstalkdJob.php @@ -0,0 +1,125 @@ +job = $job; + $this->container = $container; + $this->pheanstalk = $pheanstalk; + } + + /** + * Fire the job. + * + * @return void + */ + public function fire() + { + $this->resolveAndFire(json_decode($this->job->getData(), true)); + } + + /** + * Delete the job from the queue. + * + * @return void + */ + public function delete() + { + $this->pheanstalk->delete($this->job); + } + + /** + * Release the job back into the queue. + * + * @param int $delay + * @return void + */ + public function release($delay = 0) + { + $priority = Pheanstalk::DEFAULT_PRIORITY; + + $this->pheanstalk->release($this->job, $priority, $delay); + } + + /** + * Get the number of times the job has been attempted. + * + * @return int + */ + public function attempts() + { + $stats = $this->pheanstalk->statsJob($this->job); + + return (int) $stats->reserves; + } + + /** + * Get the job identifier. + * + * @return string + */ + public function getJobId() + { + return $this->job->getId(); + } + + /** + * Get the IoC container instance. + * + * @return \Illuminate\Container + */ + public function getContainer() + { + return $this->container; + } + + /** + * Get the underlying Pheanstalk instance. + * + * @return Pheanstalk + */ + public function getPheanstalk() + { + return $this->pheanstalk; + } + + /** + * Get the underlying Pheanstalk job. + * + * @return Pheanstalk_Job + */ + public function getPheanstalkJob() + { + return $this->job; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Queue/Jobs/IronJob.php b/vendor/laravel/framework/src/Illuminate/Queue/Jobs/IronJob.php new file mode 100755 index 0000000..cc4cc67 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/Jobs/IronJob.php @@ -0,0 +1,149 @@ +job = $job; + $this->iron = $iron; + $this->queue = $queue; + $this->pushed = $pushed; + $this->container = $container; + } + + /** + * Fire the job. + * + * @return void + */ + public function fire() + { + $this->resolveAndFire(json_decode($this->job->body, true)); + } + + /** + * Delete the job from the queue. + * + * @return void + */ + public function delete() + { + if (isset($this->job->pushed)) return; + + $this->iron->deleteMessage($this->queue, $this->job->id); + } + + /** + * Release the job back into the queue. + * + * @param int $delay + * @return void + */ + public function release($delay = 0) + { + if ( ! $this->pushed) + { + $this->iron->releaseMessage($this->queue, $this->job->id, $delay); + } + else + { + throw new \LogicException("Pushed jobs may not be released."); + } + } + + /** + * Get the number of times the job has been attempted. + * + * @return int + */ + public function attempts() + { + throw new \LogicException("This driver doesn't support attempt counting."); + } + + /** + * Get the job identifier. + * + * @return string + */ + public function getJobId() + { + return $this->job->id; + } + + /** + * Get the IoC container instance. + * + * @return \Illuminate\Container\Container + */ + public function getContainer() + { + return $this->container; + } + + /** + * Get the underlying IronMQ instance. + * + * @return IronMQ + */ + public function getIron() + { + return $this->iron; + } + + /** + * Get the underlying IronMQ job. + * + * @return array + */ + public function getIronJob() + { + return $this->job; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php b/vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php new file mode 100755 index 0000000..8213f5a --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php @@ -0,0 +1,97 @@ +parseJob($payload['job']); + + $this->instance = $this->resolve($class); + + $this->instance->{$method}($this, $payload['data']); + } + + /** + * Resolve the given job handler. + * + * @param string $class + * @return mixed + */ + protected function resolve($class) + { + return $this->container->make($class); + } + + /** + * Parse the job declaration into class and method. + * + * @param string $job + * @return array + */ + protected function parseJob($job) + { + $segments = explode('@', $job); + + return count($segments) > 1 ? $segments : array($segments[0], 'fire'); + } + + /** + * Determine if job should be auto-deleted. + * + * @return bool + */ + public function autoDelete() + { + return isset($this->instance->delete); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Queue/Jobs/SqsJob.php b/vendor/laravel/framework/src/Illuminate/Queue/Jobs/SqsJob.php new file mode 100755 index 0000000..a3160da --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/Jobs/SqsJob.php @@ -0,0 +1,134 @@ +sqs = $sqs; + $this->job = $job; + $this->queue = $queue; + $this->container = $container; + } + + /** + * Fire the job. + * + * @return void + */ + public function fire() + { + $this->resolveAndFire(json_decode($this->job['Body'], true)); + } + + /** + * Delete the job from the queue. + * + * @return void + */ + public function delete() + { + $this->sqs->deleteMessage(array( + + 'QueueUrl' => $this->queue, 'ReceiptHandle' => $this->job['ReceiptHandle'], + + )); + } + + /** + * Release the job back into the queue. + * + * @param int $delay + * @return void + */ + public function release($delay = 0) + { + // SQS job releases are handled by the server configuration... + } + + /** + * Get the number of times the job has been attempted. + * + * @return int + */ + public function attempts() + { + return (int) $this->job['Attributes']['ApproximateReceiveCount']; + } + + /** + * Get the job identifier. + * + * @return string + */ + public function getJobId() + { + return $this->job['MessageId']; + } + + /** + * Get the IoC container instance. + * + * @return \Illuminate\Container + */ + public function getContainer() + { + return $this->container; + } + + /** + * Get the underlying SQS client instance. + * + * @return \Aws\Sqs\SqsClient + */ + public function getSqs() + { + return $this->sqs; + } + + /** + * Get the underlying raw SQS job. + * + * @return array + */ + public function getSqsJob() + { + return $this->job; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Queue/Jobs/SyncJob.php b/vendor/laravel/framework/src/Illuminate/Queue/Jobs/SyncJob.php new file mode 100755 index 0000000..cac2495 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/Jobs/SyncJob.php @@ -0,0 +1,95 @@ +job = $job; + $this->data = $data; + $this->container = $container; + } + + /** + * Fire the job. + * + * @return void + */ + public function fire() + { + if ($this->job instanceof Closure) + { + call_user_func($this->job, $this, $this->data); + } + else + { + $this->resolveAndFire(array('job' => $this->job, 'data' => $this->data)); + } + } + + /** + * Delete the job from the queue. + * + * @return void + */ + public function delete() + { + // + } + + /** + * Release the job back into the queue. + * + * @param int $delay + * @return void + */ + public function release($delay = 0) + { + // + } + + /** + * Get the number of times the job has been attempted. + * + * @return int + */ + public function attempts() + { + return 1; + } + + /** + * Get the job identifier. + * + * @return string + */ + public function getJobId() + { + return ''; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Queue/Listener.php b/vendor/laravel/framework/src/Illuminate/Queue/Listener.php new file mode 100755 index 0000000..760f630 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/Listener.php @@ -0,0 +1,143 @@ +commandPath = $commandPath; + $this->environment = $environment; + } + + /** + * Listen to the given queue connection. + * + * @param string $connection + * @param string $queue + * @param string $delay + * @param string $memory + * @param int $timeout + * @return void + */ + public function listen($connection, $queue, $delay, $memory, $timeout = 60) + { + $process = $this->makeProcess($connection, $queue, $delay, $memory, $timeout); + + while(true) + { + $this->runProcess($process, $memory); + } + } + + /** + * Run the given process. + * + * @param \Symfony\Component\Process\Process $process + * @param int $memory + * @return void + */ + public function runProcess(Process $process, $memory) + { + $process->run(); + + // Once we have run the job we'll go check if the memory limit has been + // exceeded for the script. If it has, we will kill this script so a + // process managers will restart this with a clean slate of memory. + if ($this->memoryExceeded($memory)) + { + $this->stop(); return; + } + } + + /** + * Create a new Symfony process for the worker. + * + * @param string $connection + * @param string $queue + * @param int $delay + * @param int $memory + * @param int $timeout + * @return \Symfony\Component\Process\Process + */ + public function makeProcess($connection, $queue, $delay, $memory, $timeout) + { + $string = 'php artisan queue:work %s --queue="%s" --delay=%s --memory=%s --sleep'; + + // If the environment is set, we will append it to the command string so the + // workers will run under the specified environment. Otherwise, they will + // just run under the production environment which is not always right. + if (isset($this->environment)) + { + $string .= ' --env='.$this->environment; + } + + $command = sprintf($string, $connection, $queue, $delay, $memory); + + return new Process($command, $this->commandPath, null, null, $timeout); + } + + /** + * Determine if the memory limit has been exceeded. + * + * @param int $memoryLimit + * @return bool + */ + public function memoryExceeded($memoryLimit) + { + return (memory_get_usage() / 1024 / 1024) >= $memoryLimit; + } + + /** + * Stop listening and bail out of the script. + * + * @return void + */ + public function stop() + { + die; + } + + /** + * Get the current listener environment. + * + * @return string + */ + public function getEnvironment() + { + return $this->environment; + } + + /** + * Set the current environment. + * + * @param string $environment + * @return void + */ + public function setEnvironment($environment) + { + $this->environment = $environment; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Queue/Queue.php b/vendor/laravel/framework/src/Illuminate/Queue/Queue.php new file mode 100755 index 0000000..fb934a0 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/Queue.php @@ -0,0 +1,70 @@ +createClosurePayload($job, $data)); + } + else + { + return json_encode(array('job' => $job, 'data' => $data)); + } + } + + /** + * Create a payload string for the given Closure job. + * + * @param \Closure $job + * @param mixed $data + * @return string + */ + protected function createClosurePayload($job, $data) + { + $closure = serialize(new SerializableClosure($job)); + + return array('job' => 'IlluminateQueueClosure', 'data' => compact('closure')); + } + + /** + * Set the IoC container instance. + * + * @param \Illuminate\Container $container + * @return void + */ + public function setContainer(Container $container) + { + $this->container = $container; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Queue/QueueInterface.php b/vendor/laravel/framework/src/Illuminate/Queue/QueueInterface.php new file mode 100755 index 0000000..ea17f43 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/QueueInterface.php @@ -0,0 +1,34 @@ +app = $app; + } + + /** + * Resolve a queue connection instance. + * + * @param string $name + * @return \Illuminate\Queue\QueueInterface + */ + public function connection($name = null) + { + $name = $name ?: $this->getDefault(); + + // If the connection has not been resolved yet we will resolve it now as all + // of the connections are resolved when they are actually needed so we do + // not make any unnecessary connection to the various queue end-points. + if ( ! isset($this->connections[$name])) + { + $this->connections[$name] = $this->resolve($name); + + $this->connections[$name]->setContainer($this->app); + } + + return $this->connections[$name]; + } + + /** + * Resolve a queue connection. + * + * @param string $name + * @return \Illuminate\Queue\QueueInterface + */ + protected function resolve($name) + { + $config = $this->getConfig($name); + + return $this->getConnector($config['driver'])->connect($config); + } + + /** + * Get the connector for a given driver. + * + * @param string $driver + * @return \Illuminate\Queue\Connectors\ConnectorInterface + */ + protected function getConnector($driver) + { + if (isset($this->connectors[$driver])) + { + return call_user_func($this->connectors[$driver]); + } + + throw new \InvalidArgumentException("No connector for [$driver]"); + } + + /** + * Add a queue connection resolver. + * + * @param string $driver + * @param Closure $resolver + * @return void + */ + public function addConnector($driver, Closure $resolver) + { + $this->connectors[$driver] = $resolver; + } + + /** + * Get the queue connection configuration. + * + * @param string $name + * @return array + */ + protected function getConfig($name) + { + return $this->app['config']["queue.connections.{$name}"]; + } + + /** + * Get the name of the default queue connection. + * + * @return string + */ + protected function getDefault() + { + return $this->app['config']['queue.default']; + } + + /** + * Dynamically pass calls to the default connection. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + $callable = array($this->connection(), $method); + + return call_user_func_array($callable, $parameters); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Queue/QueueServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Queue/QueueServiceProvider.php new file mode 100755 index 0000000..84a7ff6 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/QueueServiceProvider.php @@ -0,0 +1,222 @@ +registerManager(); + + $this->registerWorker(); + + $this->registerListener(); + + $this->registerSubscriber(); + } + + /** + * Register the queue manager. + * + * @return void + */ + protected function registerManager() + { + $me = $this; + + $this->app['queue'] = $this->app->share(function($app) use ($me) + { + // Once we have an instance of the queue manager, we will register the various + // resolvers for the queue connectors. These connectors are responsible for + // creating the classes that accept queue configs and instantiate queues. + $manager = new QueueManager($app); + + $me->registerConnectors($manager); + + return $manager; + }); + } + + /** + * Register the queue worker. + * + * @return void + */ + protected function registerWorker() + { + $this->registerWorkCommand(); + + $this->app['queue.worker'] = $this->app->share(function($app) + { + return new Worker($app['queue']); + }); + } + + /** + * Register the queue worker console command. + * + * @return void + */ + protected function registerWorkCommand() + { + $app = $this->app; + + $app['command.queue.work'] = $app->share(function($app) + { + return new WorkCommand($app['queue.worker']); + }); + + $this->commands('command.queue.work'); + } + + /** + * Register the queue listener. + * + * @return void + */ + protected function registerListener() + { + $this->registerListenCommand(); + + $this->app['queue.listener'] = $this->app->share(function($app) + { + return new Listener($app['path.base']); + }); + } + + /** + * Register the queue listener console command. + * + * @return void + */ + protected function registerListenCommand() + { + $app = $this->app; + + $app['command.queue.listen'] = $app->share(function($app) + { + return new ListenCommand($app['queue.listener']); + }); + + $this->commands('command.queue.listen'); + } + + /** + * Register the push queue subscribe command. + * + * @return void + */ + protected function registerSubscriber() + { + $app = $this->app; + + $app['command.queue.subscribe'] = $app->share(function($app) + { + return new SubscribeCommand; + }); + + $this->commands('command.queue.subscribe'); + } + + /** + * Register the connectors on the queue manager. + * + * @param \Illuminate\Queue\QueueManager $manager + * @return void + */ + public function registerConnectors($manager) + { + foreach (array('Sync', 'Beanstalkd', 'Sqs', 'Iron') as $connector) + { + $this->{"register{$connector}Connector"}($manager); + } + } + + /** + * Register the Sync queue connector. + * + * @param \Illuminate\Queue\QueueManager $manager + * @return void + */ + protected function registerSyncConnector($manager) + { + $manager->addConnector('sync', function() + { + return new SyncConnector; + }); + } + + /** + * Register the Beanstalkd queue connector. + * + * @param \Illuminate\Queue\QueueManager $manager + * @return void + */ + protected function registerBeanstalkdConnector($manager) + { + $manager->addConnector('beanstalkd', function() + { + return new BeanstalkdConnector; + }); + } + + /** + * Register the Amazon SQS queue connector. + * + * @param \Illuminate\Queue\QueueManager $manager + * @return void + */ + protected function registerSqsConnector($manager) + { + $manager->addConnector('sqs', function() + { + return new SqsConnector; + }); + } + + /** + * Register the IronMQ queue connector. + * + * @param \Illuminate\Queue\QueueManager $manager + * @return void + */ + protected function registerIronConnector($manager) + { + $app = $this->app; + + $manager->addConnector('iron', function() use ($app) + { + return new IronConnector($app['encrypter'], $app['request']); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('queue', 'queue.worker', 'queue.listener', 'command.queue.work', 'command.queue.listen', 'command.queue.subscribe'); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Queue/SqsQueue.php b/vendor/laravel/framework/src/Illuminate/Queue/SqsQueue.php new file mode 100755 index 0000000..5ce9d1b --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/SqsQueue.php @@ -0,0 +1,113 @@ +sqs = $sqs; + $this->default = $default; + } + + /** + * Push a new job onto the queue. + * + * @param string $job + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function push($job, $data = '', $queue = null) + { + $payload = $this->createPayload($job, $data); + + $response = $this->sqs->sendMessage(array('QueueUrl' => $this->getQueue($queue), 'MessageBody' => $payload)); + + return $response->get('MessageId'); + } + + /** + * Push a new job onto the queue after a delay. + * + * @param int $delay + * @param string $job + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function later($delay, $job, $data = '', $queue = null) + { + $payload = $this->createPayload($job, $data); + + return $this->sqs->sendMessage(array( + + 'QueueUrl' => $this->getQueue($queue), 'MessageBody' => $payload, 'DelaySeconds' => $delay, + + ))->get('MessageId'); + } + + /** + * Pop the next job off of the queue. + * + * @param string $queue + * @return \Illuminate\Queue\Jobs\Job|null + */ + public function pop($queue = null) + { + $queue = $this->getQueue($queue); + + $response = $this->sqs->receiveMessage( + array('QueueUrl' => $queue, 'AttributeNames' => array('ApproximateReceiveCount')) + ); + + if (count($response['Messages']) > 0) + { + return new SqsJob($this->container, $this->sqs, $queue, $response['Messages'][0]); + } + } + + /** + * Get the queue or return the default. + * + * @param string|null $queue + * @return string + */ + protected function getQueue($queue) + { + return $queue ?: $this->default; + } + + /** + * Get the underlying SQS instance. + * + * @return \Aws\Sqs\SqsClient + */ + public function getSqs() + { + return $this->sqs; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Queue/SyncQueue.php b/vendor/laravel/framework/src/Illuminate/Queue/SyncQueue.php new file mode 100755 index 0000000..7ebb10a --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/SyncQueue.php @@ -0,0 +1,54 @@ +resolveJob($job, $data)->fire(); + + return 0; + } + + /** + * Push a new job onto the queue after a delay. + * + * @param int $delay + * @param string $job + * @param mixed $data + * @param string $queue + * @return mixed + */ + public function later($delay, $job, $data = '', $queue = null) + { + return $this->push($job, $data, $queue); + } + + /** + * Pop the next job off of the queue. + * + * @param string $queue + * @return \Illuminate\Queue\Jobs\Job|null + */ + public function pop($queue = null) {} + + /** + * Resolve a Sync job instance. + * + * @param string $job + * @param string $data + * @return \Illuminate\Queue\Jobs\SyncJob + */ + protected function resolveJob($job, $data) + { + return new Jobs\SyncJob($this->container, $job, $data); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Queue/Worker.php b/vendor/laravel/framework/src/Illuminate/Queue/Worker.php new file mode 100755 index 0000000..77641e1 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/Worker.php @@ -0,0 +1,116 @@ +manager = $manager; + } + + /** + * Listen to the given queue. + * + * @param string $connection + * @param string $queue + * @param int $delay + * @param int $memory + * @param bool $sleep + * @return void + */ + public function pop($connection, $queue = null, $delay = 0, $memory = 128, $sleep = false) + { + $connection = $this->manager->connection($connection); + + $job = $connection->pop($queue); + + // If we're able to pull a job off of the stack, we will process it and + // then make sure we are not exceeding our memory limits for the run + // which is to protect against run-away memory leakages from here. + if ( ! is_null($job)) + { + $this->process($job, $delay); + } + elseif ($sleep) + { + $this->sleep(1); + } + } + + /** + * Process a given job from the queue. + * + * @param \Illuminate\Queue\Jobs\Job $job + * @param int $delay + * @return void + */ + public function process(Job $job, $delay) + { + try + { + // First we will fire off the job. Once it is done we will see if it will + // be auto-deleted after processing and if so we will go ahead and run + // the delete method on the job. Otherwise we will just keep moving. + $job->fire(); + + if ($job->autoDelete()) $job->delete(); + } + + catch (\Exception $e) + { + // If we catch an exception, we will attempt to release the job back onto + // the queue so it is not lost. This will let is be retried at a later + // time by another listener (or the same one). We will do that here. + $job->release($delay); + + throw $e; + } + } + + /** + * Sleep the script for a given number of seconds. + * + * @param int $seconds + * @return void + */ + public function sleep($seconds) + { + sleep($seconds); + } + + /** + * Get the queue manager instance. + * + * @return \Illuminate\Queue\QueueManager + */ + public function getManager() + { + return $this->manager; + } + + /** + * Set the queue manager instance. + * + * @param \Illuminate\Queue\QueueManager $manager + * @return void + */ + public function setManager(QueueManager $manager) + { + $this->manager = $manager; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Queue/composer.json b/vendor/laravel/framework/src/Illuminate/Queue/composer.json new file mode 100755 index 0000000..230e6f4 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Queue/composer.json @@ -0,0 +1,38 @@ +{ + "name": "illuminate/queue", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "illuminate/console": "4.0.x", + "illuminate/container": "4.0.x", + "illuminate/http": "4.0.x", + "illuminate/support": "4.0.x", + "symfony/process": "2.3.*" + }, + "require-dev": { + "aws/aws-sdk-php": "2.1.*", + "mockery/mockery": "0.7.2", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Queue": "" + }, + "classmap": [ + "IlluminateQueueClosure.php" + ] + }, + "target-dir": "Illuminate/Queue", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Redis/Database.php b/vendor/laravel/framework/src/Illuminate/Redis/Database.php new file mode 100755 index 0000000..e5b5d91 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Redis/Database.php @@ -0,0 +1,96 @@ +clients = $this->createAggregateClient($servers); + } + else + { + $this->clients = $this->createSingleClients($servers); + } + } + + /** + * Create a new aggregate client supporting sharding. + * + * @param array $servers + * @return array + */ + protected function createAggregateClient(array $servers) + { + $servers = array_except($servers, array('cluster')); + + return array('default' => new \Predis\Client(array_values($servers))); + } + + /** + * Create an array of single connection clients. + * + * @param array $servers + * @return array + */ + protected function createSingleClients(array $servers) + { + $clients = array(); + + foreach ($servers as $key => $server) + { + $clients[$key] = new \Predis\Client($server); + } + + return $clients; + } + + /** + * Get a specific Redis connection instance. + * + * @param string $name + * @return \Predis\Connection\SingleConnectionInterface + */ + public function connection($name = 'default') + { + return $this->clients[$name ?: 'default']; + } + + /** + * Run a command against the Redis database. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function command($method, array $parameters = array()) + { + return call_user_func_array(array($this->clients['default'], $method), $parameters); + } + + /** + * Dynamically make a Redis command. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->command($method, $parameters); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Redis/RedisServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Redis/RedisServiceProvider.php new file mode 100755 index 0000000..6f79471 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Redis/RedisServiceProvider.php @@ -0,0 +1,37 @@ +app['redis'] = $this->app->share(function($app) + { + return new Database($app['config']['database.redis']); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('redis'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Redis/composer.json b/vendor/laravel/framework/src/Illuminate/Redis/composer.json new file mode 100755 index 0000000..ea7b94f --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Redis/composer.json @@ -0,0 +1,31 @@ +{ + "name": "illuminate/redis", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "illuminate/support": "4.0.x", + "predis/predis": "0.*" + }, + "require-dev": { + "mockery/mockery": "0.7.2", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Redis": "" + } + }, + "target-dir": "Illuminate/Redis", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/laravel/framework/src/Illuminate/Routing/Console/MakeControllerCommand.php b/vendor/laravel/framework/src/Illuminate/Routing/Console/MakeControllerCommand.php new file mode 100755 index 0000000..5b8181a --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Routing/Console/MakeControllerCommand.php @@ -0,0 +1,158 @@ +path = $path; + $this->generator = $generator; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $this->generateController(); + } + + /** + * Generate a new resourceful controller file. + * + * @return void + */ + protected function generateController() + { + $controller = $this->input->getArgument('name'); + + // Once we have the controller and resource that we are going to be generating + // we will grab the path and options. We allow the developers to include or + // exclude given methods from the resourceful controllers we're building. + $path = $this->getPath(); + + $options = $this->getBuildOptions(); + + // Finally, we're ready to generate the actual controller file on disk and let + // the developer start using it. The controller will be stored in the right + // place based on the naemspace of this controller specified by commands. + $this->generator->make($controller, $path, $options); + + $this->info('Controller created successfully!'); + } + + /** + * Get the path in which to store the controller. + * + * @return string + */ + protected function getPath() + { + if ( ! is_null($this->input->getOption('path'))) + { + return $this->laravel['path.base'].'/'.$this->input->getOption('path'); + } + + return $this->path; + } + + /** + * Get the options for controller generation. + * + * @return array + */ + protected function getBuildOptions() + { + $only = $this->explodeOption('only'); + + $except = $this->explodeOption('except'); + + return compact('only', 'except'); + } + + /** + * Get and explode a given input option. + * + * @param string $name + * @return array + */ + protected function explodeOption($name) + { + $option = $this->input->getOption($name); + + return is_null($option) ? array() : explode(',', $option); + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return array( + array('name', InputArgument::REQUIRED, 'The name of the controller class'), + ); + } + + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('only', null, InputOption::VALUE_OPTIONAL, 'The methods that should be included'), + + array('except', null, InputOption::VALUE_OPTIONAL, 'The methods that should be excluded'), + + array('path', null, InputOption::VALUE_OPTIONAL, 'Where to place the controller'), + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Routing/ControllerServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Routing/ControllerServiceProvider.php new file mode 100755 index 0000000..c91ab2f --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Routing/ControllerServiceProvider.php @@ -0,0 +1,77 @@ +registerParser(); + + $this->registerGenerator(); + + $this->commands('command.controller.make'); + } + + /** + * Register the filter parser instance. + * + * @return void + */ + protected function registerParser() + { + $this->app['filter.parser'] = $this->app->share(function($app) + { + return new FilterParser; + }); + } + + /** + * Register the controller generator command. + * + * @return void + */ + protected function registerGenerator() + { + $this->app['command.controller.make'] = $this->app->share(function($app) + { + // The controller generator is responsible for building resourceful controllers + // quickly and easily for the developers via the Artisan CLI. We'll go ahead + // and register this command instances in this container for registration. + $path = $app['path'].'/controllers'; + + $generator = new ControllerGenerator($app['files']); + + return new MakeControllerCommand($generator, $path); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array( + 'filter.parser', 'command.controller.make' + ); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Routing/Controllers/After.php b/vendor/laravel/framework/src/Illuminate/Routing/Controllers/After.php new file mode 100755 index 0000000..7c193a7 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Routing/Controllers/After.php @@ -0,0 +1,3 @@ +prepareFilter($filter, $options); + + $this->filters[] = new Before($options); + } + + /** + * Register a new "after" filter on the controller. + * + * @param string $filter + * @param array $options + * @return void + */ + public function afterFilter($filter, array $options = array()) + { + $options = $this->prepareFilter($filter, $options); + + $this->filters[] = new After($options); + } + + /** + * Prepare a filter and return the options. + * + * @param string $filter + * @param array $options + * @return array + */ + protected function prepareFilter($filter, $options) + { + // When the given filter is a Closure, we will store it off in an array of the + // callback filters, keyed off the object hash of these Closures and we can + // easily retrieve it if a filter is determined to apply to this request. + if ($filter instanceof Closure) + { + $options['run'] = $hash = spl_object_hash($filter); + + $this->callbackFilters[$hash] = $filter; + } + else + { + $options['run'] = $filter; + } + + return $options; + } + + /** + * Execute an action on the controller. + * + * @param \Illuminate\Container\Container $container + * @param \Illuminate\Routing\Router $router + * @param string $method + * @param array $parameters + * @return \Symfony\Component\HttpFoundation\Response + */ + public function callAction(Container $container, Router $router, $method, $parameters) + { + $this->filterParser = $container['filter.parser']; + + // If no response was returned from the before filters, we'll call the regular + // action on the controller and prepare the response. Then we will call the + // after filters on the controller to wrap up any last minute processing. + $response = $this->callBeforeFilters($router, $method); + + $this->setupLayout(); + + if (is_null($response)) + { + $response = $this->callMethod($method, $parameters); + } + + // If no response is returned from the controller action and a layout is being + // used we will assume we want to just return the layout view as any nested + // views were probably bound on this view during this controller actions. + if (is_null($response) and ! is_null($this->layout)) + { + $response = $this->layout; + } + + return $this->processResponse($router, $method, $response); + } + + /** + * Call the given action with the given parameters. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + protected function callMethod($method, $parameters) + { + return call_user_func_array(array($this, $method), $parameters); + } + + /** + * Process a controller action response. + * + * @param \Illuminate\Routing\Router $router + * @param string $method + * @param mixed $response + * @return \Symfony\Component\HttpFoundation\Response + */ + protected function processResponse($router, $method, $response) + { + $request = $router->getRequest(); + + // The after filters give the developers one last chance to do any last minute + // processing on the response. The response has already been converted to a + // full Response object and will also be handed off to the after filters. + $response = $router->prepare($response, $request); + + $this->callAfterFilters($router, $method, $response); + + return $response; + } + + /** + * Call the before filters on the controller. + * + * @param \Illuminate\Routing\Router $router + * @param string $method + * @return mixed + */ + protected function callBeforeFilters($router, $method) + { + $response = null; + + $route = $router->getCurrentRoute(); + + // When running the before filters we will simply spin through the list of the + // filters and call each one on the current route objects, which will place + // the proper parameters on the filter call, including the requests data. + $request = $router->getRequest(); + + $filters = $this->getBeforeFilters($request, $method); + + foreach ($filters as $filter) + { + $response = $this->callFilter($route, $filter, $request); + + if ( ! is_null($response)) return $response; + } + } + + /** + * Get the before filters for the controller. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param string $method + * @return array + */ + protected function getBeforeFilters($request, $method) + { + $class = 'Illuminate\Routing\Controllers\Before'; + + return $this->filterParser->parse($this, $request, $method, $class); + } + + /** + * Call the after filters on the controller. + * + * @param \Illuminate\Routing\Router $router + * @param string $method + * @param \Symfony\Component\HttpFoundation\Response $response + * @return mixed + */ + protected function callAfterFilters($router, $method, $response) + { + $route = $router->getCurrentRoute(); + + // When running the before filters we will simply spin through the list of the + // filters and call each one on the current route objects, which will place + // the proper parameters on the filter call, including the requests data. + $request = $router->getRequest(); + + $filters = $this->getAfterFilters($request, $method); + + foreach ($filters as $filter) + { + $this->callFilter($route, $filter, $request, array($response)); + } + } + + /** + * Get the after filters for the controller. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param string $method + * @return array + */ + protected function getAfterFilters($request, $method) + { + $class = 'Illuminate\Routing\Controllers\After'; + + return $this->filterParser->parse($this, $request, $method, $class); + } + + /** + * Call the given route filter. + * + * @param \Illuminate\Routing\Route $route + * @param string $filter + * @param \Symfony\Component\HttpFoundation\Request $request + * @param array $parameters + * @return mixed + */ + protected function callFilter($route, $filter, $request, $parameters = array()) + { + if (isset($this->callbackFilters[$filter])) + { + $callback = $this->callbackFilters[$filter]; + + return call_user_func_array($callback, array_merge(array($route, $request), $parameters)); + } + + return $route->callFilter($filter, $request, $parameters); + } + + /** + * Setup the layout used by the controller. + * + * @return void + */ + protected function setupLayout() {} + + /** + * Get the code registered filters. + * + * @return array + */ + public function getControllerFilters() + { + return $this->filters; + } + + /** + * Handle calls to missing methods on the controller. + * + * @param array $parameters + * @return mixed + */ + public function missingMethod($parameters) + { + throw new NotFoundHttpException("Controller method not found."); + } + + /** + * Handle calls to missing methods on the controller. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->missingMethod($parameters); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Routing/Controllers/Filter.php b/vendor/laravel/framework/src/Illuminate/Routing/Controllers/Filter.php new file mode 100755 index 0000000..319fef7 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Routing/Controllers/Filter.php @@ -0,0 +1,133 @@ +prepareValues($values) as $key => $value) + { + $this->$key = $value; + } + } + + /** + * Prepare the values for setting. + * + * @param array $values + * @return void + */ + protected function prepareValues($values) + { + if (isset($values['on'])) + { + $values['on'] = (array) $values['on']; + + // If the "get" method is present in an "on" constraint for the annotation we + // will add the "head" method as well, since the "head" method is supposed + // to function basically identically to the get methods on the back-end. + if (in_array('get', $values['on'])) + { + $values['on'][] = 'head'; + } + } + + return $values; + } + + /** + * Determine if the filter applies to a request and method. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param string $method + * @return bool + */ + public function applicable(Request $request, $method) + { + foreach (array('Request', 'OnlyMethod', 'ExceptMethod') as $excluder) + { + // We'll simply check the excluder method and see if the annotation does + // not apply based on that rule. If it does not, we will go ahead and + // return false since we know an annotation is not even applicable. + $excluder = "excludedBy{$excluder}"; + + if ($this->$excluder($request, $method)) return false; + } + + return true; + } + + /** + * Determine if the filter applies based on the "on" rule. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param string $method + * @return bool + */ + protected function excludedByRequest($request, $method) + { + $http = strtolower($request->getMethod()); + + return isset($this->on) and ! in_array($http, (array) $this->on); + } + + /** + * Determine if the filter applies based on the "only" rule. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param string $method + * @return bool + */ + protected function excludedByOnlyMethod($request, $method) + { + return isset($this->only) and ! in_array($method, (array) $this->only); + } + + /** + * Determine if the filter applies based on the "except" rule. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param string $method + * @return bool + */ + protected function excludedByExceptMethod($request, $method) + { + return isset($this->except) and in_array($method, (array) $this->except); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Routing/Controllers/FilterParser.php b/vendor/laravel/framework/src/Illuminate/Routing/Controllers/FilterParser.php new file mode 100755 index 0000000..1c1f537 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Routing/Controllers/FilterParser.php @@ -0,0 +1,81 @@ +getCodeFilters($controller, $request, $method, $filter); + } + + /** + * Get the filters that were specified in code. + * + * @param \Illuminate\Routing\Controllers\Controller $controller + * @param \Symfony\Component\HttpFoundation\Request $request + * @param string $method + * @param string $filter + * @return array + */ + protected function getCodeFilters($controller, $request, $method, $filter) + { + $filters = $this->filterByClass($controller->getControllerFilters(), $filter); + + return $this->getNames($this->filter($filters, $request, $method)); + } + + /** + * Filter the annotation instances by class name. + * + * @param array $filters + * @param string $filter + * @return array + */ + protected function filterByClass($filters, $filter) + { + return array_filter($filters, function($annotation) use ($filter) + { + return $annotation instanceof $filter; + }); + } + + /** + * Filter the annotation instances by request and method. + * + * @param array $filters + * @param \Symfony\Component\HttpFoundation\Request $request + * @param string $method + * @return array + */ + protected function filter($filters, $request, $method) + { + $filtered = array_filter($filters, function($annotation) use ($request, $method) + { + return $annotation->applicable($request, $method); + }); + + return array_values($filtered); + } + + /** + * Get the filter names from an array of filter objects. + * + * @param array $filters + * @return array + */ + protected function getNames(array $filters) + { + return array_map(function($filter) { return $filter->run; }, $filters); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Routing/Controllers/Inspector.php b/vendor/laravel/framework/src/Illuminate/Routing/Controllers/Inspector.php new file mode 100644 index 0000000..6a1f0ed --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Routing/Controllers/Inspector.php @@ -0,0 +1,138 @@ +getMethods() as $method) + { + if ($this->isRoutable($method, $reflection->name)) + { + $data = $this->getMethodData($method, $prefix); + + // If the routable method is an index method, we will create a special index + // route which is simply the prefix and the verb and does not contain any + // the wildcard place-holders that each "typical" routes would contain. + if ($data['plain'] == $prefix.'/index') + { + $routable[$method->name][] = $data; + + $routable[$method->name][] = $this->getIndexData($data, $prefix); + } + + // If the routable method is not a special index method, we will just add in + // the data to the returned results straight away. We do not need to make + // any special routes for this scenario but only just add these routes. + else + { + $routable[$method->name][] = $data; + } + } + } + + return $routable; + } + + /** + * Determine if the given controller method is routable. + * + * @param ReflectionMethod $method + * @param string $controller + * @return bool + */ + public function isRoutable(ReflectionMethod $method, $controller) + { + if ($method->class == 'Illuminate\Routing\Controllers\Controller') return false; + + return $method->isPublic() and starts_with($method->name, $this->verbs); + } + + /** + * Get the method data for a given method. + * + * @param ReflectionMethod $method + * @return array + */ + public function getMethodData(ReflectionMethod $method, $prefix) + { + $verb = $this->getVerb($name = $method->name); + + $uri = $this->addUriWildcards($plain = $this->getPlainUri($name, $prefix)); + + return compact('verb', 'plain', 'uri'); + } + + /** + * Get the routable data for an index method. + * + * @param array $data + * @param string $prefix + * @return array + */ + protected function getIndexData($data, $prefix) + { + return array('verb' => $data['verb'], 'plain' => $prefix, 'uri' => $prefix); + } + + /** + * Extract the verb from a controller action. + * + * @param string $name + * @return string + */ + public function getVerb($name) + { + return head(explode('_', snake_case($name))); + } + + /** + * Determine the URI from the given method name. + * + * @param string $name + * @param string $prefix + * @return string + */ + public function getPlainUri($name, $prefix) + { + return $prefix.'/'.implode('-', array_slice(explode('_', snake_case($name)), 1)); + } + + /** + * Add wildcards to the given URI. + * + * @param string $uri + * @return string + */ + public function addUriWildcards($uri) + { + return $uri.'/{v1?}/{v2?}/{v3?}/{v4?}/{v5?}'; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Routing/Generators/ControllerGenerator.php b/vendor/laravel/framework/src/Illuminate/Routing/Generators/ControllerGenerator.php new file mode 100755 index 0000000..cfd3eb9 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Routing/Generators/ControllerGenerator.php @@ -0,0 +1,209 @@ +files = $files; + } + + /** + * Create a new resourceful controller file. + * + * @param string $controller + * @param string $path + * @param array $options + * @return void + */ + public function make($controller, $path, array $options = array()) + { + $stub = $this->addMethods($this->getController($controller), $options); + + $this->writeFile($stub, $controller, $path); + + return false; + } + + /** + * Write the completed stub to disk. + * + * @param string $stub + * @param string $controller + * @param string $path + * @return void + */ + protected function writeFile($stub, $controller, $path) + { + if (str_contains($controller, '\\')) + { + $this->makeDirectory($controller, $path); + } + + $controller = str_replace('\\', DIRECTORY_SEPARATOR, $controller); + + if ( ! $this->files->exists($fullPath = $path."/{$controller}.php")) + { + return $this->files->put($fullPath, $stub); + } + } + + /** + * Create the directory for the controller. + * + * @param string $controller + * @param string $path + * @return void + */ + protected function makeDirectory($controller, $path) + { + $directory = $this->getDirectory($controller); + + if ( ! $this->files->isDirectory($full = $path.'/'.$directory)) + { + $this->files->makeDirectory($full, 0777, true); + } + } + + /** + * Get the directory the controller should live in. + * + * @param string $controller + * @return string + */ + protected function getDirectory($controller) + { + return implode('/', array_slice(explode('\\', $controller), 0, -1)); + } + + /** + * Get the controller class stub. + * + * @param string $controller + * @return string + */ + protected function getController($controller) + { + $stub = $this->files->get(__DIR__.'/stubs/controller.stub'); + + // We will explode out the controller name on the naemspace delimiter so we + // are able to replace a namespace in this stub file. If no namespace is + // provided we'll just clear out the namespace place-holder locations. + $segments = explode('\\', $controller); + + $stub = $this->replaceNamespace($segments, $stub); + + return str_replace('{{class}}', last($segments), $stub); + } + + /** + * Replace the namespace on the controller. + * + * @param array $segments + * @param string $stub + * @return string + */ + protected function replaceNamespace(array $segments, $stub) + { + if (count($segments) > 1) + { + $namespace = implode('\\', array_slice($segments, 0, -1)); + + return str_replace('{{namespace}}', ' namespace '.$namespace.';', $stub); + } + else + { + return str_replace('{{namespace}}', '', $stub); + } + } + + /** + * Add the method stubs to the controller. + * + * @param string $stub + * @param array $options + * @return string + */ + protected function addMethods($stub, array $options) + { + // Once we have the applicable methods, we can just spin through those methods + // and add each one to our array of method stubs. Then we will implode them + // them all with end-of-line characters and return the final joined list. + $stubs = $this->getMethodStubs($options); + + $methods = implode(PHP_EOL.PHP_EOL, $stubs); + + return str_replace('{{methods}}', $methods, $stub); + } + + /** + * Get all of the method stubs for the given options. + * + * @param array $options + * @return array + */ + protected function getMethodStubs($options) + { + $stubs = array(); + + // Each stub is conveniently kept in its own file so we can just grab the ones + // we need from disk to build the controller file. Once we have them all in + // an array we will return this list of methods so they can be joined up. + foreach ($this->getMethods($options) as $method) + { + $stubs[] = $this->files->get(__DIR__."/stubs/{$method}.stub"); + } + + return $stubs; + } + + /** + * Get the applicable methods based on the options. + * + * @param array $options + * @return array + */ + protected function getMethods($options) + { + if (isset($options['only']) and count($options['only']) > 0) + { + return $options['only']; + } + elseif (isset($options['except']) and count($options['except']) > 0) + { + return array_diff($this->defaults, $options['except']); + } + + return $this->defaults; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Routing/Generators/stubs/controller.stub b/vendor/laravel/framework/src/Illuminate/Routing/Generators/stubs/controller.stub new file mode 100644 index 0000000..17d5f59 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Routing/Generators/stubs/controller.stub @@ -0,0 +1,7 @@ +generator = $generator; + } + + /** + * Create a new redirect response to the "home" route. + * + * @param int $status + * @return \Illuminate\Http\RedirectResponse + */ + public function home($status = 302) + { + return $this->to($this->generator->route('home'), $status); + } + + /** + * Create a new redirect response to the previous location. + * + * @param int $status + * @param array $headers + * @return \Illuminate\Http\RedirectResponse + */ + public function back($status = 302, $headers = array()) + { + $back = $this->generator->getRequest()->headers->get('referer'); + + return $this->createRedirect($back, $status, $headers); + } + + /** + * Create a new redirect response to the current URI. + * + * @param int $status + * @param array $headers + * @return \Illuminate\Http\RedirectResponse + */ + public function refresh($status = 302, $headers = array()) + { + return $this->to($this->generator->getRequest()->path(), $status, $headers); + } + + /** + * Create a new redirect response, while putting the current URL in the session. + * + * @param string $path + * @param int $status + * @param array $headers + * @param bool $secure + * @return \Illuminate\Http\RedirectResponse + */ + public function guest($path, $status = 302, $headers = array(), $secure = null) + { + $this->session->put('url.intended', $this->generator->full()); + + return $this->to($path, $status, $headers, $secure); + } + + /** + * Create a new redirect response to the previously intended location. + * + * @param string $default + * @param int $status + * @param array $headers + * @param bool $secure + * @return \Illuminate\Http\RedirectResponse + */ + public function intended($default, $status = 302, $headers = array(), $secure = null) + { + $path = $this->session->get('url.intended', $default); + + $this->session->forget('url.intended'); + + return $this->to($path, $status, $headers, $secure); + } + + /** + * Create a new redirect response to the given path. + * + * @param string $path + * @param int $status + * @param array $headers + * @param bool $secure + * @return \Illuminate\Http\RedirectResponse + */ + public function to($path, $status = 302, $headers = array(), $secure = null) + { + $path = $this->generator->to($path, array(), $secure); + + return $this->createRedirect($path, $status, $headers); + } + + /** + * Create a new redirect response to the given HTTPS path. + * + * @param string $path + * @param int $status + * @param array $headers + * @return \Illuminate\Http\RedirectResponse + */ + public function secure($path, $status = 302, $headers = array()) + { + return $this->to($path, $status, $headers, true); + } + + /** + * Create a new redirect response to a named route. + * + * @param string $route + * @param array $parameters + * @param int $status + * @param array $headers + * @return \Illuminate\Http\RedirectResponse + */ + public function route($route, $parameters = array(), $status = 302, $headers = array()) + { + $path = $this->generator->route($route, $parameters); + + return $this->to($path, $status, $headers); + } + + /** + * Create a new redirect response to a controller action. + * + * @param string $action + * @param array $parameters + * @param int $status + * @param array $headers + * @return \Illuminate\Http\RedirectResponse + */ + public function action($action, $parameters = array(), $status = 302, $headers = array()) + { + $path = $this->generator->action($action, $parameters); + + return $this->to($path, $status, $headers); + } + + /** + * Create a new redirect response. + * + * @param string $path + * @param int $status + * @param array $headers + * @return \Illuminate\Http\RedirectResponse + */ + protected function createRedirect($path, $status, $headers) + { + $redirect = new RedirectResponse($path, $status, $headers); + + if (isset($this->session)) + { + $redirect->setSession($this->session); + } + + $redirect->setRequest($this->generator->getRequest()); + + return $redirect; + } + + /** + * Get the URL generator instance. + * + * @return \Illuminate\Routing\UrlGenerator + */ + public function getUrlGenerator() + { + return $this->generator; + } + + /** + * Set the active session store. + * + * @param \Illuminate\Session\Store $session + * @return void + */ + public function setSession(SessionStore $session) + { + $this->session = $session; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Routing/Route.php b/vendor/laravel/framework/src/Illuminate/Routing/Route.php new file mode 100755 index 0000000..53c1577 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Routing/Route.php @@ -0,0 +1,481 @@ +parsedParameters = null; + + // We will only call the router callable if no "before" middlewares returned + // a response. If they do, we will consider that the response to requests + // so that the request "lifecycle" will be easily halted for filtering. + $response = $this->callBeforeFilters($request); + + if ( ! isset($response)) + { + $response = $this->callCallable(); + } + + // If the response is from a filter we want to note that so that we can skip + // the "after" filters which should only run when the route method is run + // for the incoming request. Otherwise only app level filters will run. + else + { + $fromFilter = true; + } + + $response = $this->router->prepare($response, $request); + + // Once we have the "prepared" response, we will iterate through every after + // filter and call each of them with the request and the response so they + // can perform any final work that needs to be done after a route call. + if ( ! isset($fromFilter)) + { + $this->callAfterFilters($request, $response); + } + + return $response; + } + + /** + * Call the callable Closure attached to the route. + * + * @return mixed + */ + protected function callCallable() + { + $variables = array_values($this->getParametersWithoutDefaults()); + + return call_user_func_array($this->getOption('_call'), $variables); + } + + /** + * Call all of the before filters on the route. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @return mixed + */ + protected function callBeforeFilters(Request $request) + { + $before = $this->getAllBeforeFilters($request); + + $response = null; + + // Once we have each middlewares, we will simply iterate through them and call + // each one of them with the request. We will set the response variable to + // whatever it may return so that it may override the request processes. + foreach ($before as $filter) + { + $response = $this->callFilter($filter, $request); + + if ( ! is_null($response)) return $response; + } + } + + /** + * Get all of the before filters to run on the route. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @return array + */ + protected function getAllBeforeFilters(Request $request) + { + $before = $this->getBeforeFilters(); + + $patterns = $this->router->findPatternFilters($request->getMethod(), $request->getPathInfo()); + + return array_merge($before, $patterns); + } + + /** + * Call all of the "after" filters for a route. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param \Symfony\Component\HttpFoundation\Response $response + * @return void + */ + protected function callAfterFilters(Request $request, $response) + { + foreach ($this->getAfterFilters() as $filter) + { + $this->callFilter($filter, $request, array($response)); + } + } + + /** + * Call a given filter with the parameters. + * + * @param string $name + * @param \Symfony\Component\HttpFoundation\Request $request + * @param array $params + * @return mixed + */ + public function callFilter($name, Request $request, array $params = array()) + { + if ( ! $this->router->filtersEnabled()) return; + + $merge = array($this->router->getCurrentRoute(), $request); + + $params = array_merge($merge, $params); + + // Next we will parse the filter name to extract out any parameters and adding + // any parameters specified in a filter name to the end of the lists of our + // parameters, since the ones at the beginning are typically very static. + list($name, $params) = $this->parseFilter($name, $params); + + if ( ! is_null($callable = $this->router->getFilter($name))) + { + return call_user_func_array($callable, $params); + } + } + + /** + * Parse a filter name and add any parameters to the array. + * + * @param string $name + * @param array $parameters + * @return array + */ + protected function parseFilter($name, $parameters = array()) + { + if (str_contains($name, ':')) + { + // If the filter name contains a colon, we will assume that the developer + // is passing along some parameters with the name, and we will explode + // out the name and paramters, merging the parameters onto the list. + $segments = explode(':', $name); + + $name = $segments[0]; + + // We will merge the arguments specified in the filter name into the list + // of existing parameters. We'll send them at the end since any values + // at the front are usually static such as request, response, route. + $arguments = explode(',', $segments[1]); + + $parameters = array_merge($parameters, $arguments); + } + + return array($name, $parameters); + } + + /** + * Get a parameter by name from the route. + * + * @param string $name + * @param mixed $default + * @return string + */ + public function getParameter($name, $default = null) + { + return array_get($this->getParameters(), $name, $default); + } + + /** + * Get the parameters to the callback. + * + * @return array + */ + public function getParameters() + { + // If we have already parsed the parameters, we will just return the listing + // that we already parsed as some of these may have been resolved through + // a binder that uses a database repository and shouldn't be run again. + if (isset($this->parsedParameters)) + { + return $this->parsedParameters; + } + + $variables = $this->compile()->getVariables(); + + // To get the parameter array, we need to spin the names of the variables on + // the compiled route and match them to the parameters that we got when a + // route is matched by the router, as routes instances don't have them. + $parameters = array(); + + foreach ($variables as $variable) + { + $parameters[$variable] = $this->resolveParameter($variable); + } + + return $this->parsedParameters = $parameters; + } + + /** + * Resolve a parameter value for the route. + * + * @param string $key + * @return mixed + */ + protected function resolveParameter($key) + { + $value = $this->parameters[$key]; + + // If the parameter has a binder, we will call the binder to resolve the real + // value for the parameters. The binders could make a database call to get + // a User object for example or may transform the input in some fashion. + if ($this->router->hasBinder($key)) + { + return $this->router->performBinding($key, $value, $this); + } + + return $value; + } + + /** + * Get the route parameters without missing defaults. + * + * @return array + */ + public function getParametersWithoutDefaults() + { + $parameters = $this->getParameters(); + + foreach ($parameters as $key => $value) + { + // When calling functions using call_user_func_array, we don't want to write + // over any existing default parameters, so we will remove every optional + // parameter from the list that did not get a specified value on route. + if ($this->isMissingDefault($key, $value)) + { + unset($parameters[$key]); + } + } + + return $parameters; + } + + /** + * Determine if a route parameter is really a missing default. + * + * @param string $key + * @param mixed $value + * @return bool + */ + protected function isMissingDefault($key, $value) + { + return $this->isOptional($key) and is_null($value); + } + + /** + * Determine if a given key is optional. + * + * @param string $key + * @return bool + */ + public function isOptional($key) + { + return array_key_exists($key, $this->getDefaults()); + } + + /** + * Get the keys of the variables on the route. + * + * @return array + */ + public function getParameterKeys() + { + return $this->compile()->getVariables(); + } + + /** + * Force a given parameter to match a regular expression. + * + * @param string $name + * @param string $expression + * @return \Illuminate\Routing\Route + */ + public function where($name, $expression = null) + { + if (is_array($name)) return $this->setArrayOfWheres($name); + + $this->setRequirement($name, $expression); + + return $this; + } + + /** + * Force a given parameters to match the expressions. + * + * @param array $wheres + * @return \Illuminate\Routing\Route + */ + protected function setArrayOfWheres(array $wheres) + { + foreach ($wheres as $name => $expression) + { + $this->where($name, $expression); + } + + return $this; + } + + /** + * Set the default value for a parameter. + * + * @param string $key + * @param mixed $value + * @return \Illuminate\Routing\Route + */ + public function defaults($key, $value) + { + $this->setDefault($key, $value); + + return $this; + } + + /** + * Set the before filters on the route. + * + * @param dynamic + * @return \Illuminate\Routing\Route + */ + public function before() + { + $this->setBeforeFilters(func_get_args()); + + return $this; + } + + /** + * Set the after filters on the route. + * + * @param dynamic + * @return \Illuminate\Routing\Route + */ + public function after() + { + $this->setAfterFilters(func_get_args()); + + return $this; + } + + /** + * Get the name of the action (if any) used by the route. + * + * @return string + */ + public function getAction() + { + return $this->getOption('_uses'); + } + + /** + * Get the before filters on the route. + * + * @return array + */ + public function getBeforeFilters() + { + return $this->getOption('_before') ?: array(); + } + + /** + * Set the before filters on the route. + * + * @param string $value + * @return void + */ + public function setBeforeFilters($value) + { + $filters = $this->parseFilterValue($value); + + $this->setOption('_before', array_merge($this->getBeforeFilters(), $filters)); + } + + /** + * Get the after filters on the route. + * + * @return array + */ + public function getAfterFilters() + { + return $this->getOption('_after') ?: array(); + } + + /** + * Set the after filters on the route. + * + * @param string $value + * @return void + */ + public function setAfterFilters($value) + { + $filters = $this->parseFilterValue($value); + + $this->setOption('_after', array_merge($this->getAfterFilters(), $filters)); + } + + /** + * Parse the given filters for setting. + * + * @param array|string $value + * @return array + */ + protected function parseFilterValue($value) + { + $results = array(); + + foreach ((array) $value as $filters) + { + $results = array_merge($results, explode('|', $filters)); + } + + return $results; + } + + /** + * Set the matching parameter array on the route. + * + * @param array $parameters + * @return void + */ + public function setParameters($parameters) + { + $this->parameters = $parameters; + } + + /** + * Set the Router instance on the route. + * + * @param \Illuminate\Routing\Router $router + * @return \Illuminate\Routing\Route + */ + public function setRouter(Router $router) + { + $this->router = $router; + + return $this; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Routing/Router.php b/vendor/laravel/framework/src/Illuminate/Routing/Router.php new file mode 100755 index 0000000..b41a21f --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Routing/Router.php @@ -0,0 +1,1640 @@ +container = $container; + + $this->routes = new RouteCollection; + + $this->bind('_missing', function($v) { return explode('/', $v); }); + } + + /** + * Add a new route to the collection. + * + * @param string $pattern + * @param mixed $action + * @return \Illuminate\Routing\Route + */ + public function get($pattern, $action) + { + return $this->createRoute('get', $pattern, $action); + } + + /** + * Add a new route to the collection. + * + * @param string $pattern + * @param mixed $action + * @return \Illuminate\Routing\Route + */ + public function post($pattern, $action) + { + return $this->createRoute('post', $pattern, $action); + } + + /** + * Add a new route to the collection. + * + * @param string $pattern + * @param mixed $action + * @return \Illuminate\Routing\Route + */ + public function put($pattern, $action) + { + return $this->createRoute('put', $pattern, $action); + } + + /** + * Add a new route to the collection. + * + * @param string $pattern + * @param mixed $action + * @return \Illuminate\Routing\Route + */ + public function patch($pattern, $action) + { + return $this->createRoute('patch', $pattern, $action); + } + + /** + * Add a new route to the collection. + * + * @param string $pattern + * @param mixed $action + * @return \Illuminate\Routing\Route + */ + public function delete($pattern, $action) + { + return $this->createRoute('delete', $pattern, $action); + } + + /** + * Add a new route to the collection. + * + * @param string $pattern + * @param mixed $action + * @return \Illuminate\Routing\Route + */ + public function options($pattern, $action) + { + return $this->createRoute('options', $pattern, $action); + } + + /** + * Add a new route to the collection. + * + * @param string $method + * @param string $pattern + * @param mixed $action + * @return \Illuminate\Routing\Route + */ + public function match($method, $pattern, $action) + { + return $this->createRoute($method, $pattern, $action); + } + + /** + * Add a new route to the collection. + * + * @param string $pattern + * @param mixed $action + * @return \Illuminate\Routing\Route + */ + public function any($pattern, $action) + { + return $this->createRoute('get|post|put|patch|delete', $pattern, $action); + } + + /** + * Register an array of controllers with wildcard routing. + * + * @param array $controllers + * @return void + */ + public function controllers(array $controllers) + { + foreach ($controllers as $uri => $name) + { + $this->controller($uri, $name); + } + } + + /** + * Route a controller to a URI with wildcard routing. + * + * @param string $uri + * @param string $controller + * @param array $names + * @return \Illuminate\Routing\Route + */ + public function controller($uri, $controller, $names = array()) + { + $routable = $this->getInspector()->getRoutable($controller, $uri); + + // When a controller is routed using this method, we use Reflection to parse + // out all of the routable methods for the controller, then register each + // route explicitly for the developers, so reverse routing is possible. + foreach ($routable as $method => $routes) + { + foreach ($routes as $route) + { + $this->registerInspected($route, $controller, $method, $names); + } + } + + $this->addFallthroughRoute($controller, $uri); + } + + /** + * Register an inspected controller route. + * + * @param array $route + * @param string $controller + * @param string $method + * @param array $names + * @return void + */ + protected function registerInspected($route, $controller, $method, &$names) + { + $action = array('uses' => $controller.'@'.$method); + + // If a given controller method has been named, we will assign the name to the + // controller action array, which provides for a short-cut to method naming + // so you don't have to define an individual route for these controllers. + $action['as'] = array_pull($names, $method); + + $this->{$route['verb']}($route['uri'], $action); + } + + /** + * Add a fallthrough route for a controller. + * + * @param string $controller + * @param string $uri + * @return void + */ + protected function addFallthroughRoute($controller, $uri) + { + $missing = $this->any($uri.'/{_missing}', $controller.'@missingMethod'); + + $missing->where('_missing', '(.*)'); + } + + /** + * Route a resource to a controller. + * + * @param string $resource + * @param string $controller + * @param array $options + * @return void + */ + public function resource($resource, $controller, array $options = array()) + { + // If the resource name contains a slash, we will assume the developer wishes to + // register these resource routes with a prefix so we will set that up out of + // the box so they don't have to mess with it. Otherwise, we will continue. + if (str_contains($resource, '/')) + { + $this->prefixedResource($resource, $controller, $options); + + return; + } + + // We need to extract the base resource from the resource name. Nested resources + // are supported in the framework, but we need to know what name to use for a + // place-holder on the route wildcards, which should be the base resources. + $base = $this->getBaseResource($resource); + + $defaults = $this->resourceDefaults; + + foreach ($this->getResourceMethods($defaults, $options) as $method) + { + $this->{'addResource'.ucfirst($method)}($resource, $base, $controller); + } + } + + /** + * Build a set of prefixed resource routes. + * + * @param string $resource + * @param string $controller + * @param array $options + * @return void + */ + protected function prefixedResource($resource, $controller, array $options) + { + list($resource, $prefix) = $this->extractResourcePrefix($resource); + + $me = $this; + + return $this->group(array('prefix' => $prefix), function() use ($me, $resource, $controller, $options) + { + $me->resource($resource, $controller, $options); + }); + } + + /** + * Extract the resource and prefix from a resource name. + * + * @param string $resource + * @return array + */ + protected function extractResourcePrefix($resource) + { + $segments = explode('/', $resource); + + return array($segments[count($segments) - 1], implode('/', array_slice($segments, 0, -1))); + } + + /** + * Get the applicable resource methods. + * + * @param array $defaults + * @param array $options + * @return array + */ + protected function getResourceMethods($defaults, $options) + { + if (isset($options['only'])) + { + return array_intersect($defaults, $options['only']); + } + elseif (isset($options['except'])) + { + return array_diff($defaults, $options['except']); + } + + return $defaults; + } + + /** + * Add the index method for a resourceful route. + * + * @param string $name + * @param string $base + * @param string $controller + * @return void + */ + protected function addResourceIndex($name, $base, $controller) + { + $action = $this->getResourceAction($name, $controller, 'index'); + + return $this->get($this->getResourceUri($name), $action); + } + + /** + * Add the create method for a resourceful route. + * + * @param string $name + * @param string $base + * @param string $controller + * @return void + */ + protected function addResourceCreate($name, $base, $controller) + { + $action = $this->getResourceAction($name, $controller, 'create'); + + return $this->get($this->getResourceUri($name).'/create', $action); + } + + /** + * Add the store method for a resourceful route. + * + * @param string $name + * @param string $base + * @param string $controller + * @return void + */ + protected function addResourceStore($name, $base, $controller) + { + $action = $this->getResourceAction($name, $controller, 'store'); + + return $this->post($this->getResourceUri($name), $action); + } + + /** + * Add the show method for a resourceful route. + * + * @param string $name + * @param string $base + * @param string $controller + * @return void + */ + protected function addResourceShow($name, $base, $controller) + { + $uri = $this->getResourceUri($name).'/{'.$base.'}'; + + return $this->get($uri, $this->getResourceAction($name, $controller, 'show')); + } + + /** + * Add the edit method for a resourceful route. + * + * @param string $name + * @param string $base + * @param string $controller + * @return void + */ + protected function addResourceEdit($name, $base, $controller) + { + $uri = $this->getResourceUri($name).'/{'.$base.'}/edit'; + + return $this->get($uri, $this->getResourceAction($name, $controller, 'edit')); + } + + /** + * Add the update method for a resourceful route. + * + * @param string $name + * @param string $base + * @param string $controller + * @return void + */ + protected function addResourceUpdate($name, $base, $controller) + { + $this->addPutResourceUpdate($name, $base, $controller); + + return $this->addPatchResourceUpdate($name, $base, $controller); + } + + /** + * Add the update method for a resourceful route. + * + * @param string $name + * @param string $base + * @param string $controller + * @return void + */ + protected function addPutResourceUpdate($name, $base, $controller) + { + $uri = $this->getResourceUri($name).'/{'.$base.'}'; + + return $this->put($uri, $this->getResourceAction($name, $controller, 'update')); + } + + /** + * Add the update method for a resourceful route. + * + * @param string $name + * @param string $base + * @param string $controller + * @return void + */ + protected function addPatchResourceUpdate($name, $base, $controller) + { + $uri = $this->getResourceUri($name).'/{'.$base.'}'; + + $this->patch($uri, $controller.'@update'); + } + + /** + * Add the destroy method for a resourceful route. + * + * @param string $name + * @param string $base + * @param string $controller + * @return void + */ + protected function addResourceDestroy($name, $base, $controller) + { + $uri = $this->getResourceUri($name).'/{'.$base.'}'; + + return $this->delete($uri, $this->getResourceAction($name, $controller, 'destroy')); + } + + /** + * Get the base resource URI for a given resource. + * + * @param string $resource + * @return string + */ + public function getResourceUri($resource) + { + // To create the nested resource URI, we will simply explode the segments and + // create a base URI for each of them, then join all of them back together + // with slashes. This should create the properly nested resource routes. + if ( ! str_contains($resource, '.')) return $resource; + + $segments = explode('.', $resource); + + $nested = $this->getNestedResourceUri($segments); + + // Once we have built the base URI, we'll remove the wildcard holder for this + // base resource name so that the individual route adders can suffix these + // paths however they need to, as some do not have any wildcards at all. + $last = $this->getResourceWildcard(last($segments)); + + return str_replace('/{'.$last.'}', '', $nested); + } + + /** + * Get the URI for a nested resource segment array. + * + * @param array $segments + * @return string + */ + protected function getNestedResourceUri(array $segments) + { + $me = $this; + + // We will spin through the segments and create a place-holder for each of the + // resource segments, as well as the resource itself. Then we should get an + // entire string for the resource URI that contains all nested resources. + return implode('/', array_map(function($s) use ($me) + { + return $s.'/{'.$me->getResourceWildcard($s).'}'; + + }, $segments)); + } + + /** + * Get the action array for a resource route. + * + * @param string $resource + * @param string $controller + * @param string $method + * @return array + */ + protected function getResourceAction($resource, $controller, $method) + { + $name = $resource.'.'.$method; + + // If we have a group stack, we will append the full prefix onto the resource + // route name so that we don't override other route with the same name but + // a different prefix. We'll then return out the complete action arrays. + $name = $this->getResourceName($resource, $method); + + return array('as' => $name, 'uses' => $controller.'@'.$method); + } + + /** + * Get the name for a given resource. + * + * @param string $resource + * @param string $name + * @return string + */ + protected function getResourceName($resource, $method) + { + if (count($this->groupStack) == 0) return $resource.'.'.$method; + + return $this->getResourcePrefix($resource, $method); + } + + /** + * Get the resource prefix for a resource route. + * + * @param string $resource + * @param string $method + * @return string + */ + protected function getResourcePrefix($resource, $method) + { + $prefix = str_replace('/', '.', $this->getGroupPrefix()); + + if ($prefix != '') $prefix .= '.'; + + return "{$prefix}{$resource}.{$method}"; + } + + /** + * Get the base resource from a resource name. + * + * @param string $resource + * @return string + */ + protected function getBaseResource($resource) + { + $segments = explode('.', $resource); + + return $this->getResourceWildcard($segments[count($segments) - 1]); + } + + /** + * Format a resource wildcard parameter. + * + * @param string $value + * @return string + */ + public function getResourceWildcard($value) + { + return str_replace('-', '_', $value); + } + + /** + * Create a route group with shared attributes. + * + * @param array $attributes + * @param Closure $callback + * @return void + */ + public function group(array $attributes, Closure $callback) + { + $this->updateGroupStack($attributes); + + call_user_func($callback); + + array_pop($this->groupStack); + } + + /** + * Update the group stack array. + * + * @param array $attributes + * @return void + */ + protected function updateGroupStack(array $attributes) + { + if (count($this->groupStack) > 0) + { + $last = $this->groupStack[count($this->groupStack) - 1]; + + $this->groupStack[] = array_merge_recursive($last, $attributes); + } + else + { + $this->groupStack[] = $attributes; + } + } + + /** + * Create a new route instance. + * + * @param string $method + * @param string $pattern + * @param mixed $action + * @return \Illuminate\Routing\Route + */ + protected function createRoute($method, $pattern, $action) + { + // We will force the action parameters to be an array just for convenience. + // This will let us examine it for other attributes like middlewares or + // a specific HTTP schemes the route only responds to, such as HTTPS. + if ( ! is_array($action)) + { + $action = $this->parseAction($action); + } + + $groupCount = count($this->groupStack); + + // If there are attributes being grouped across routes we will merge those + // attributes into the action array so that they will get shared across + // the routes. The route can override the attribute by specifying it. + if ($groupCount > 0) + { + $index = $groupCount - 1; + + $action = $this->mergeGroup($action, $index); + } + + // Next we will parse the pattern and add any specified prefix to the it so + // a common URI prefix may be specified for a group of routes easily and + // without having to specify them all for every route that is defined. + list($pattern, $optional) = $this->getOptional($pattern); + + if (isset($action['prefix'])) + { + $prefix = $action['prefix']; + + $pattern = $this->addPrefix($pattern, $prefix); + } + + // We will create the routes, setting the Closure callbacks on the instance + // so we can easily access it later. If there are other parameters on a + // routes we'll also set those requirements as well such as defaults. + $route = with(new Route($pattern))->setOptions(array( + + '_call' => $this->getCallback($action), + + ))->setRouter($this)->addRequirements($this->patterns); + + $route->setRequirement('_method', $method); + + // Once we have created the route, we will add them to our route collection + // which contains all the other routes and is used to match on incoming + // URL and their appropriate route destination and on URL generation. + $this->setAttributes($route, $action, $optional); + + $name = $this->getName($method, $pattern, $action); + + $this->routes->add($name, $route); + + return $route; + } + + /** + * Parse the given route action into array form. + * + * @param mixed $action + * @return array + */ + protected function parseAction($action) + { + // If the action is just a Closure we'll stick it in an array and just send + // it back out. However if it's a string we'll just assume it's meant to + // route into a controller action and change it to a controller array. + if ($action instanceof Closure) + { + return array($action); + } + elseif (is_string($action)) + { + return array('uses' => $action); + } + + throw new \InvalidArgumentException("Unroutable action."); + } + + /** + * Merge the current group stack into a given action. + * + * @param array $action + * @param int $index + * @return array + */ + protected function mergeGroup($action, $index) + { + $prefix = $this->mergeGroupPrefix($action); + + $action = array_merge_recursive($this->groupStack[$index], $action); + + // If we have a prefix, we will override the merged prefix with this correctly + // concatenated one since prefixes shouldn't merge like the other groupable + // attributes on the action. Then we can return this final merged arrays. + if ($prefix != '') $action['prefix'] = $prefix; + + return $action; + } + + /** + * Get the full group prefix for the current stack. + * + * @return string + */ + protected function getGroupPrefix() + { + if (count($this->groupStack) > 0) + { + $group = $this->groupStack[count($this->groupStack) - 1]; + + if (isset($group['prefix'])) + { + if (is_array($group['prefix'])) return implode('/', $group['prefix']); + + return $group['prefix']; + } + } + + return ''; + } + + /** + * Get the fully merged prefix for a given action. + * + * @param array $action + * @return string + */ + protected function mergeGroupPrefix($action) + { + $prefix = isset($action['prefix']) ? $action['prefix'] : ''; + + return trim($this->getGroupPrefix().'/'.$prefix, '/'); + } + + /** + * Add the given prefix to the given URI pattern. + * + * @param string $pattern + * @param string $prefix + * @return string + */ + protected function addPrefix($pattern, $prefix) + { + $pattern = trim($prefix, '/').'/'.ltrim($pattern, '/'); + + return trim($pattern, '/'); + } + + /** + * Set the attributes and requirements on the route. + * + * @param \Illuminate\Routing\Route $route + * @param array $action + * @param array $optional + * @return void + */ + protected function setAttributes(Route $route, $action, $optional) + { + // First we will set the requirement for the HTTP schemes. Some routes may + // only respond to requests using the HTTPS scheme, while others might + // respond to all, regardless of the scheme, so we'll set that here. + if (in_array('https', $action)) + { + $route->setRequirement('_scheme', 'https'); + } + + if (in_array('http', $action)) + { + $route->setRequirement('_scheme', 'http'); + } + + // Once the scheme requirements have been made, we will set the before and + // after middleware options, which will be used to run any middlewares + // by the consuming library, making halting the request cycles easy. + if (isset($action['before'])) + { + $route->setBeforeFilters($action['before']); + } + + if (isset($action['after'])) + { + $route->setAfterFilters($action['after']); + } + + // If there is a "uses" key on the route it means it is using a controller + // instead of a Closures route. So, we'll need to set that as an option + // on the route so we can easily do reverse routing ot the route URI. + if (isset($action['uses'])) + { + $route->setOption('_uses', $action['uses']); + } + + if (isset($action['domain'])) + { + $route->setHost($action['domain']); + } + + // Finally we will go through and set all of the default variables to null + // so the developer doesn't have to manually specify one each time they + // are declared on a route. This is simply for developer convenience. + foreach ($optional as $key) + { + $route->setDefault($key, null); + } + } + + /** + * Modify the pattern and extract optional parameters. + * + * @param string $pattern + * @return array + */ + protected function getOptional($pattern) + { + $optional = array(); + + preg_match_all('#\{(\w+)\?\}#', $pattern, $matches); + + // For each matching value, we will extract the name of the optional values + // and add it to our array, then we will replace the place-holder to be + // a valid place-holder minus this optional indicating question mark. + foreach ($matches[0] as $key => $value) + { + $optional[] = $name = $matches[1][$key]; + + $pattern = str_replace($value, '{'.$name.'}', $pattern); + } + + return array($pattern, $optional); + } + + /** + * Get the name of the route. + * + * @param string $method + * @param string $pattern + * @param array $action + * @return string + */ + protected function getName($method, $pattern, array $action) + { + if (isset($action['as'])) return $action['as']; + + $domain = isset($action['domain']) ? $action['domain'].' ' : ''; + + return "{$domain}{$method} {$pattern}"; + } + + /** + * Get the callback from the given action array. + * + * @param array $action + * @return Closure + */ + protected function getCallback(array $action) + { + foreach ($action as $key => $attribute) + { + // If the action has a "uses" key, the route is pointing to a controller + // action instead of using a Closure. So, we'll create a Closure that + // resolves the controller instances and calls the needed function. + if ($key === 'uses') + { + return $this->createControllerCallback($attribute); + } + elseif ($attribute instanceof Closure) + { + return $attribute; + } + } + } + + /** + * Create the controller callback for a route. + * + * @param string $attribute + * @return Closure + */ + protected function createControllerCallback($attribute) + { + $ioc = $this->container; + + $me = $this; + + // We'll return a Closure that is able to resolve the controller instance and + // call the appropriate method on the controller, passing in the arguments + // it receives. Controllers are created with the IoC container instance. + return function() use ($me, $ioc, $attribute) + { + list($controller, $method) = explode('@', $attribute); + + $route = $me->getCurrentRoute(); + + // We will extract the passed in parameters off of the route object so we will + // pass them off to the controller method as arguments. We will not get the + // defaults so that the controllers will be able to use its own defaults. + $args = array_values($route->getParametersWithoutDefaults()); + + $instance = $ioc->make($controller); + + return $instance->callAction($ioc, $me, $method, $args); + }; + } + + /** + * Get the response for a given request. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @return \Symfony\Component\HttpFoundation\Response + */ + public function dispatch(Request $request) + { + $this->currentRequest = $request; + + // First we will call the "before" global middlware, which we'll give a chance + // to override the normal requests process when a response is returned by a + // middleware. Otherwise we'll call the route just like a normal request. + $response = $this->callGlobalFilter($request, 'before'); + + if ( ! is_null($response)) + { + $response = $this->prepare($response, $request); + } + + // Once we have the route, we can just run it to get the responses, which will + // always be instances of the Response class. Once we have the responses we + // will execute the global "after" middlewares to finish off the request. + else + { + $this->currentRoute = $route = $this->findRoute($request); + + $response = $route->run($request); + } + + // Finally after the route has been run we can call the after and close global + // filters for the request, giving a chance for any final processing to get + // done before the response gets returned back to the user's web browser. + $this->callAfterFilter($request, $response); + + return $response; + } + + /** + * Match the given request to a route object. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @return \Illuminate\Routing\Route + */ + protected function findRoute(Request $request) + { + // We will catch any exceptions thrown during routing and convert it to a + // HTTP Kernel equivalent exception, since that is a more generic type + // that's used by the Illuminate foundation framework for responses. + try + { + $path = $request->getPathInfo(); + + $parameters = $this->getUrlMatcher($request)->match($path); + } + + // The Symfony routing component's exceptions implement this interface we + // can type-hint it to make sure we're only providing special handling + // for those exceptions, and not other random exceptions that occur. + catch (ExceptionInterface $e) + { + $this->handleRoutingException($e); + } + + $route = $this->routes->get($parameters['_route']); + + // If we found a route, we will grab the actual route objects out of this + // route collection and set the matching parameters on the instance so + // we will easily access them later if the route action is executed. + $route->setParameters($parameters); + + return $route; + } + + /** + * Register a "before" routing filter. + * + * @param Closure|string $callback + * @return void + */ + public function before($callback) + { + $this->globalFilters['before'][] = $this->buildGlobalFilter($callback); + } + + /** + * Register an "after" routing filter. + * + * @param Closure|string $callback + * @return void + */ + public function after($callback) + { + $this->globalFilters['after'][] = $this->buildGlobalFilter($callback); + } + + /** + * Register a "close" routing filter. + * + * @param Closure|string $callback + * @return void + */ + public function close($callback) + { + $this->globalFilters['close'][] = $this->buildGlobalFilter($callback); + } + + /** + * Register a "finish" routing filters. + * + * @param Closure|string $callback + * @return void + */ + public function finish($callback) + { + $this->globalFilters['finish'][] = $this->buildGlobalFilter($callback); + } + + /** + * Build a global filter definition for the router. + * + * @param Closure|string $callback + * @return Closure + */ + protected function buildGlobalFilter($callback) + { + if (is_string($callback)) + { + $container = $this->container; + + // When the given "callback" is actually a string, we will assume that it is + // a filter class that we need to resolve out of an IoC container to call + // the filter method on the instance, passing in the arguments we take. + return function() use ($callback, $container) + { + $callable = array($container->make($callback), 'filter'); + + return call_user_func_array($callable, func_get_args()); + }; + } + else + { + return $callback; + } + } + + /** + * Register a new filter with the application. + * + * @param string $name + * @param Closure|string $callback + * @return void + */ + public function filter($name, $callback) + { + $this->filters[$name] = $callback; + } + + /** + * Get a registered filter callback. + * + * @param string $name + * @return Closure + */ + public function getFilter($name) + { + if (array_key_exists($name, $this->filters)) + { + $filter = $this->filters[$name]; + + // If the filter is a string, it means we are using a class based Filter which + // allows for the easier testing of the filter's methods rather than trying + // to test a Closure. So, we will resolve the class out of the container. + if (is_string($filter)) + { + return $this->getClassBasedFilter($filter); + } + + return $filter; + } + } + + /** + * Get a callable array for a class based filter. + * + * @param string $filter + * @return array + */ + protected function getClassBasedFilter($filter) + { + if (str_contains($filter, '@')) + { + list($class, $method) = explode('@', $filter); + + return array($this->container->make($class), $method); + } + + return array($this->container->make($filter), 'filter'); + } + + /** + * Tie a registered filter to a URI pattern. + * + * @param string $pattern + * @param string|array $names + * @param array|null $methods + * @return void + */ + public function when($pattern, $names, $methods = null) + { + foreach ((array) $names as $name) + { + if ( ! is_null($methods)) $methods = array_change_key_case((array) $methods); + + $this->patternFilters[$pattern][] = compact('name', 'methods'); + } + } + + /** + * Find the patterned filters matching a request. + * + * @param string $method + * @param string $path + * @return array + */ + public function findPatternFilters($method, $path) + { + $results = array(); + + foreach ($this->patternFilters as $pattern => $filters) + { + // To find the pattern middlewares for a request, we just need to check the + // registered patterns against the path info for the current request to + // the application, and if it matches we'll merge in the middlewares. + if (str_is('/'.$pattern, $path)) + { + $merge = $this->filterPatternsByMethod($method, $filters); + + $results = array_merge($results, $merge); + } + } + + return $results; + } + + /** + * Filter pattern filters that don't apply to the request verb. + * + * @param string $method + * @param array $filters + * @return array + */ + protected function filterPatternsByMethod($method, $filters) + { + $results = array(); + + $method = strtolower($method); + + // The idea here is to check and see if the pattern filter applies to this HTTP + // request based on the request methods. Pattern filters might be limited by + // the request verb to make it simply to assign to the given verb at once. + foreach ($filters as $filter) + { + if (is_null($filter['methods']) or in_array($method, $filter['methods'])) + { + $results[] = $filter['name']; + } + } + + return $results; + } + + /** + * Call the "after" global filters. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param \Symfony\Component\HttpFoundation\Response $response + * @return mixed + */ + protected function callAfterFilter(Request $request, SymfonyResponse $response) + { + $this->callGlobalFilter($request, 'after', array($response)); + } + + /** + * Call the finish" global filter. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param \Symfony\Component\HttpFoundation\Response $response + * @return mixed + */ + public function callFinishFilter(Request $request, SymfonyResponse $response) + { + $this->callGlobalFilter($request, 'finish', array($response)); + } + + /** + * Call the "close" global filter. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param \Symfony\Component\HttpFoundation\Response $response + * @return mixed + */ + public function callCloseFilter(Request $request, SymfonyResponse $response) + { + $this->callGlobalFilter($request, 'close', array($response)); + } + + /** + * Call a given global filter with the parameters. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param string $name + * @param array $parameters + * @return mixed + */ + protected function callGlobalFilter(Request $request, $name, array $parameters = array()) + { + if ( ! $this->filtersEnabled()) return; + + array_unshift($parameters, $request); + + if (isset($this->globalFilters[$name])) + { + // There may be multiple handlers registered for a global middleware so we + // will need to spin through each one and execute each of them and will + // return back first non-null responses we come across from a filter. + foreach ($this->globalFilters[$name] as $filter) + { + $response = call_user_func_array($filter, $parameters); + + if ( ! is_null($response)) return $response; + } + } + } + + /** + * Set a global where pattern on all routes + * + * @param string $key + * @param string $pattern + * @return void + */ + public function pattern($key, $pattern) + { + $this->patterns[$key] = $pattern; + } + + /** + * Register a model binder for a wildcard. + * + * @param string $key + * @param string $class + * @return void + */ + public function model($key, $class, Closure $callback = null) + { + return $this->bind($key, function($value) use ($class, $callback) + { + if (is_null($value)) return null; + + // For model binders, we will attempt to retrieve the model using the find + // method on the model instance. If we cannot retrieve the models we'll + // throw a not found exception otherwise we will return the instance. + if ( ! is_null($model = with(new $class)->find($value))) + { + return $model; + } + + // If a callback was supplied to the method we will call that to determine + // what we should do when the model is not found. This just gives these + // developer a little greater flexibility to decide what will happen. + if ($callback instanceof Closure) + { + return call_user_func($callback); + } + + throw new NotFoundHttpException; + }); + } + + /** + * Register a custom parameter binder. + * + * @param string $key + * @param mixed $binder + */ + public function bind($key, $binder) + { + $this->binders[str_replace('-', '_', $key)] = $binder; + } + + /** + * Determine if a given key has a registered binder. + * + * @param string $key + * @return bool + */ + public function hasBinder($key) + { + return isset($this->binders[$key]); + } + + /** + * Call a binder for a given wildcard. + * + * @param string $key + * @param mixed $value + * @param \Illuminate\Routing\Route $route + * @return mixed + */ + public function performBinding($key, $value, $route) + { + return call_user_func($this->binders[$key], $value, $route); + } + + /** + * Prepare the given value as a Response object. + * + * @param mixed $value + * @param \Illuminate\Http\Request $request + * @return \Symfony\Component\HttpFoundation\Response + */ + public function prepare($value, Request $request) + { + if ( ! $value instanceof SymfonyResponse) $value = new Response($value); + + return $value->prepare($request); + } + + /** + * Convert routing exception to HttpKernel version. + * + * @param Exception $e + * @return void + */ + protected function handleRoutingException(\Exception $e) + { + if ($e instanceof ResourceNotFoundException) + { + throw new NotFoundHttpException($e->getMessage()); + } + + // The method not allowed exception is essentially a HTTP 405 error, so we + // will grab the allowed methods when converting into the HTTP Kernel's + // version of the exact error. This gives us a good RESTful API site. + elseif ($e instanceof MethodNotAllowedException) + { + $allowed = $e->getAllowedMethods(); + + throw new MethodNotAllowedHttpException($allowed, $e->getMessage()); + } + } + + /** + * Get the current route name. + * + * @return string|null + */ + public function currentRouteName() + { + foreach ($this->routes->all() as $name => $route) + { + if ($route === $this->currentRoute) return $name; + } + } + + /** + * Determine if the current route has a given name. + * + * @param string $name + * @return bool + */ + public function currentRouteNamed($name) + { + $route = $this->routes->get($name); + + return ! is_null($route) and $route === $this->currentRoute; + } + + /** + * Get the current route action. + * + * @return string|null + */ + public function currentRouteAction() + { + $currentRoute = $this->currentRoute; + + if ( ! is_null($currentRoute)) return $currentRoute->getOption('_uses'); + } + + /** + * Determine if the current route uses a given controller action. + * + * @param string $action + * @return bool + */ + public function currentRouteUses($action) + { + return $this->currentRouteAction() === $action; + } + + /** + * Determine if route filters are enabled. + * + * @return bool + */ + public function filtersEnabled() + { + return $this->runFilters; + } + + /** + * Enable the running of filters. + * + * @return void + */ + public function enableFilters() + { + $this->runFilters = true; + } + + /** + * Disable the running of all filters. + * + * @return void + */ + public function disableFilters() + { + $this->runFilters = false; + } + + /** + * Retrieve the entire route collection. + * + * @return \Symfony\Component\Routing\RouteCollection + */ + public function getRoutes() + { + return $this->routes; + } + + /** + * Get the current request being dispatched. + * + * @return \Symfony\Component\HttpFoundation\Request + */ + public function getRequest() + { + return $this->currentRequest; + } + + /** + * Get the current route being executed. + * + * @return \Illuminate\Routing\Route + */ + public function getCurrentRoute() + { + return $this->currentRoute; + } + + /** + * Set the current route on the router. + * + * @param \Illuminate\Routing\Route $route + * @return void + */ + public function setCurrentRoute(Route $route) + { + $this->currentRoute = $route; + } + + /** + * Get the filters defined on the router. + * + * @return array + */ + public function getFilters() + { + return $this->filters; + } + + /** + * Get the global filters defined on the router. + * + * @return array + */ + public function getGlobalFilters() + { + return $this->globalFilters; + } + + /** + * Create a new URL matcher instance. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @return \Symfony\Component\Routing\Matcher\UrlMatcher + */ + protected function getUrlMatcher(Request $request) + { + $context = new RequestContext; + + $context->fromRequest($request); + + return new UrlMatcher($this->routes, $context); + } + + /** + * Get the controller inspector instance. + * + * @return \Illuminate\Routing\Controllers\Inspector + */ + public function getInspector() + { + return $this->inspector ?: new Controllers\Inspector; + } + + /** + * Set the controller inspector instance. + * + * @param \Illuminate\Routing\Controllers\Inspector $inspector + * @return void + */ + public function setInspector(Inspector $inspector) + { + $this->inspector = $inspector; + } + + /** + * Get the container used by the router. + * + * @return \Illuminate\Container\Container + */ + public function getContainer() + { + return $this->container; + } + + /** + * Set the container instance on the router. + * + * @param \Illuminate\Container\Container $container + * @return void + */ + public function setContainer(Container $container) + { + $this->container = $container; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Routing/RoutingServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Routing/RoutingServiceProvider.php new file mode 100755 index 0000000..8b35d23 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Routing/RoutingServiceProvider.php @@ -0,0 +1,85 @@ +registerRouter(); + + $this->registerUrlGenerator(); + + $this->registerRedirector(); + } + + /** + * Register the router instance. + * + * @return void + */ + protected function registerRouter() + { + $this->app['router'] = $this->app->share(function($app) + { + $router = new Router($app); + + // If the current application environment is "testing", we will disable the + // routing filters, since they can be tested independently of the routes + // and just get in the way of our typical controller testing concerns. + if ($app['env'] == 'testing') + { + $router->disableFilters(); + } + + return $router; + }); + } + + /** + * Register the URL generator service. + * + * @return void + */ + protected function registerUrlGenerator() + { + $this->app['url'] = $this->app->share(function($app) + { + // The URL generator needs the route collection that exists on the router. + // Keep in mind this is an object, so we're passing by references here + // and all the registered routes will be available to the generator. + $routes = $app['router']->getRoutes(); + + return new UrlGenerator($routes, $app['request']); + }); + } + + /** + * Register the Redirector service. + * + * @return void + */ + protected function registerRedirector() + { + $this->app['redirect'] = $this->app->share(function($app) + { + $redirector = new Redirector($app['url']); + + // If the session is set on the application instance, we'll inject it into + // the redirector instance. This allows the redirect responses to allow + // for the quite convenient "with" methods that flash to the session. + if (isset($app['session'])) + { + $redirector->setSession($app['session']); + } + + return $redirector; + }); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Routing/UrlGenerator.php b/vendor/laravel/framework/src/Illuminate/Routing/UrlGenerator.php new file mode 100755 index 0000000..5c53aa4 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Routing/UrlGenerator.php @@ -0,0 +1,342 @@ +routes = $routes; + + $this->setRequest($request); + } + + /** + * Get the full URL for the current request. + * + * @return string + */ + public function full() + { + return $this->request->fullUrl(); + } + + /** + * Get the current URL for the request. + * + * @return string + */ + public function current() + { + return $this->to($this->request->getPathInfo()); + } + + /** + * Get the URL for the previous request. + * + * @return string + */ + public function previous() + { + return $this->to($this->request->headers->get('referer')); + } + + /** + * Generate a absolute URL to the given path. + * + * @param string $path + * @param mixed $parameters + * @param bool $secure + * @return string + */ + public function to($path, $parameters = array(), $secure = null) + { + if ($this->isValidUrl($path)) return $path; + + $scheme = $this->getScheme($secure); + + // Once we have the scheme we will compile the "tail" by collapsing the values + // into a single string delimited by slashes. This just makes it convenient + // for passing the array of parameters to this URL as a list of segments. + $tail = implode('/', (array) $parameters); + + $root = $this->getRootUrl($scheme); + + return trim($root.'/'.trim($path.'/'.$tail, '/'), '/'); + } + + /** + * Generate a secure, absolute URL to the given path. + * + * @param string $path + * @param array $parameters + * @return string + */ + public function secure($path, $parameters = array()) + { + return $this->to($path, $parameters, true); + } + + /** + * Generate a URL to an application asset. + * + * @param string $path + * @param bool $secure + * @return string + */ + public function asset($path, $secure = null) + { + if ($this->isValidUrl($path)) return $path; + + // Once we get the root URL, we will check to see if it contains an index.php + // file in the paths. If it does, we will remove it since it is not needed + // for asset paths, but only for routes to endpoints in the application. + $root = $this->getRootUrl($this->getScheme($secure)); + + return $this->removeIndex($root).'/'.trim($path, '/'); + } + + /** + * Remove the index.php file from a path. + * + * @param string $root + * @return string + */ + protected function removeIndex($root) + { + $i = 'index.php'; + + return str_contains($root, $i) ? str_replace('/'.$i, '', $root) : $root; + } + + /** + * Generate a URL to a secure asset. + * + * @param string $path + * @return string + */ + public function secureAsset($path) + { + return $this->asset($path, true); + } + + /** + * Get the scheme for a raw URL. + * + * @param bool $secure + * @return string + */ + protected function getScheme($secure) + { + if (is_null($secure)) + { + return $this->request->getScheme().'://'; + } + else + { + return $secure ? 'https://' : 'http://'; + } + } + + /** + * Get the URL to a named route. + * + * @param string $name + * @param mixed $parameters + * @param bool $absolute + * @return string + */ + public function route($name, $parameters = array(), $absolute = true) + { + $route = $this->routes->get($name); + + $parameters = (array) $parameters; + + if (isset($route) and $this->usingQuickParameters($parameters)) + { + $parameters = $this->buildParameterList($route, $parameters); + } + + return $this->generator->generate($name, $parameters, $absolute); + } + + /** + * Determine if we're short circuiting the parameter list. + * + * @param array $parameters + * @return bool + */ + protected function usingQuickParameters(array $parameters) + { + return count($parameters) > 0 and is_numeric(head(array_keys($parameters))); + } + + /** + * Build the parameter list for short circuit parameters. + * + * @param \Illuminate\Routing\Route $route + * @param array $params + * @return array + */ + protected function buildParameterList($route, array $params) + { + $keys = $route->getParameterKeys(); + + // If the number of keys is less than the number of parameters on a route + // we'll fill out the parameter arrays with empty bindings on the rest + // of the spots until they are equal so we can run an array combine. + if (count($params) < count($keys)) + { + $difference = count($keys) - count($params); + + $params += array_fill(count($params), $difference, null); + } + + return array_combine($keys, $params); + } + + /** + * Get the URL to a controller action. + * + * @param string $action + * @param mixed $parameters + * @param bool $absolute + * @return string + */ + public function action($action, $parameters = array(), $absolute = true) + { + // First we'll check to see if we have already rendered a URL for an action + // so that we don't have to loop through all of the routes again on each + // iteration through the loop. If we have it, we can just return that. + if (isset($this->actionMap[$action])) + { + $name = $this->actionMap[$action]; + + return $this->route($name, $parameters, $absolute); + } + + // If haven't already mapped this action to a URI yet, we will need to spin + // through all of the routes looking for routes that routes to the given + // controller's action, then we will cache them off and build the URL. + foreach ($this->routes as $name => $route) + { + if ($action == $route->getOption('_uses')) + { + $this->actionMap[$action] = $name; + + return $this->route($name, $parameters, $absolute); + } + } + + throw new InvalidArgumentException("Unknown action [$action]."); + } + + /** + * Get the base URL for the request. + * + * @param string $scheme + * @return string + */ + protected function getRootUrl($scheme) + { + $root = $this->request->root(); + + $start = starts_with($root, 'http://') ? 'http://' : 'https://'; + + return preg_replace('~'.$start.'~', $scheme, $root, 1); + } + + /** + * Determine if the given path is a valid URL. + * + * @param string $path + * @return bool + */ + public function isValidUrl($path) + { + if (starts_with($path, array('#', '//', 'mailto:', 'tel:'))) return true; + + return filter_var($path, FILTER_VALIDATE_URL) !== false; + } + + /** + * Get the request instance. + * + * @return \Symfony\Component\HttpFoundation\Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * Set the current request instance. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @return void + */ + public function setRequest(Request $request) + { + $this->request = $request; + + $context = new RequestContext; + + $context->fromRequest($this->request); + + $this->generator = new SymfonyGenerator($this->routes, $context); + } + + /** + * Get the Symfony URL generator instance. + * + * @return \Symfony\Component\Routing\Generator\UrlGenerator + */ + public function getGenerator() + { + return $this->generator; + } + + /** + * Set the Symfony URL generator instance. + * + * @param \Symfony\Component\Routing\Generator\UrlGenerator $generator + * @return void + */ + public function setGenerator(SymfonyGenerator $generator) + { + $this->generator = $generator; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Routing/composer.json b/vendor/laravel/framework/src/Illuminate/Routing/composer.json new file mode 100755 index 0000000..5ed345d --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Routing/composer.json @@ -0,0 +1,37 @@ +{ + "name": "illuminate/routing", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "illuminate/container": "4.0.x", + "illuminate/http": "4.0.x", + "illuminate/session": "4.0.x", + "illuminate/support": "4.0.x", + "symfony/http-foundation": "2.3.*", + "symfony/http-kernel": "2.3.*", + "symfony/routing": "2.3.*" + }, + "require-dev": { + "illuminate/console": "4.0.x", + "illuminate/filesystem": "4.0.x", + "mockery/mockery": "0.7.2", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Routing": "" + } + }, + "target-dir": "Illuminate/Routing", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/laravel/framework/src/Illuminate/Session/CacheBasedSessionHandler.php b/vendor/laravel/framework/src/Illuminate/Session/CacheBasedSessionHandler.php new file mode 100755 index 0000000..1e4832e --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Session/CacheBasedSessionHandler.php @@ -0,0 +1,92 @@ +cache = $cache; + $this->minutes = $minutes; + } + + /** + * {@inheritDoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritDoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function read($sessionId) + { + return $this->cache->get($sessionId) ?: ''; + } + + /** + * {@inheritDoc} + */ + public function write($sessionId, $data) + { + return $this->cache->put($sessionId, $data, $this->minutes); + } + + /** + * {@inheritDoc} + */ + public function destroy($sessionId) + { + return $this->cache->forget($sessionId); + } + + /** + * {@inheritDoc} + */ + public function gc($lifetime) + { + return true; + } + + /** + * Get the underlying cache repository. + * + * @return \Illuminate\Cache\Repository + */ + public function getCache() + { + return $this->cache; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Session/CommandsServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Session/CommandsServiceProvider.php new file mode 100755 index 0000000..294e661 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Session/CommandsServiceProvider.php @@ -0,0 +1,41 @@ +app; + + $app['command.session.database'] = $app->share(function($app) + { + return new Console\MakeTableCommand($app['files']); + }); + + $this->commands('command.session.database'); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('command.session.database'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Session/Console/MakeTableCommand.php b/vendor/laravel/framework/src/Illuminate/Session/Console/MakeTableCommand.php new file mode 100755 index 0000000..dfb3a41 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Session/Console/MakeTableCommand.php @@ -0,0 +1,72 @@ +files = $files; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $fullPath = $this->createBaseMigration(); + + $this->files->put($fullPath, $this->files->get(__DIR__.'/stubs/database.stub')); + + $this->info('Migration created successfully!'); + } + + /** + * Create a base migration file for the session. + * + * @return string + */ + protected function createBaseMigration() + { + $name = 'create_session_table'; + + $path = $this->laravel['path'].'/database/migrations'; + + return $this->laravel['migration.creator']->create($name, $path); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Session/Console/stubs/database.stub b/vendor/laravel/framework/src/Illuminate/Session/Console/stubs/database.stub new file mode 100644 index 0000000..2aac433 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Session/Console/stubs/database.stub @@ -0,0 +1,32 @@ +string('id')->unique(); + $t->text('payload'); + $t->integer('last_activity'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('sessions'); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Session/CookieSessionHandler.php b/vendor/laravel/framework/src/Illuminate/Session/CookieSessionHandler.php new file mode 100755 index 0000000..d812a03 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Session/CookieSessionHandler.php @@ -0,0 +1,88 @@ +cookie = $cookie; + $this->minutes = $minutes; + } + + /** + * {@inheritDoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritDoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function read($sessionId) + { + return $this->cookie->get($sessionId) ?: ''; + } + + /** + * {@inheritDoc} + */ + public function write($sessionId, $data) + { + $this->setCookie($this->cookie->make($sessionId, $data, $this->minutes)); + } + + /** + * {@inheritDoc} + */ + public function destroy($sessionId) + { + $this->setCookie($this->cookie->forget($sessionId)); + } + + /** + * Set the given cookie in the headers. + * + * @param \Symfony\Component\HttpFoundation\Cookie $cookie + * @return void + */ + protected function setCookie($cookie) + { + if (headers_sent()) return; + + setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); + } + + /** + * {@inheritDoc} + */ + public function gc($lifetime) + { + return true; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Session/SessionManager.php b/vendor/laravel/framework/src/Illuminate/Session/SessionManager.php new file mode 100755 index 0000000..0be8f39 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Session/SessionManager.php @@ -0,0 +1,197 @@ +buildSession(parent::callCustomCreator($driver)); + } + + /** + * Create an instance of the "array" session driver. + * + * @return \Illuminate\Session\Store + */ + protected function createArrayDriver() + { + return new Store(new MockArraySessionStorage); + } + + /** + * Create an instance of the "cookie" session driver. + * + * @return \Illuminate\Session\Store + */ + protected function createCookieDriver() + { + $lifetime = $this->app['config']['session.lifetime']; + + return $this->buildSession(new CookieSessionHandler($this->app['cookie'], $lifetime)); + } + + /** + * Create an instance of the native session driver. + * + * @return \Illuminate\Session\Session + */ + protected function createNativeDriver() + { + $path = $this->app['config']['session.files']; + + return $this->buildSession(new NativeFileSessionHandler($path)); + } + + /** + * Create an instance of the database session driver. + * + * @return \Illuminate\Session\Store + */ + protected function createDatabaseDriver() + { + $connection = $this->getDatabaseConnection(); + + $table = $connection->getTablePrefix().$this->app['config']['session.table']; + + return $this->buildSession(new PdoSessionHandler($connection->getPdo(), $this->getDatabaseOptions($table))); + } + + /** + * Get the database connection for the database driver. + * + * @return \Illuminate\Database\Connection + */ + protected function getDatabaseConnection() + { + $connection = $this->app['config']['session.connection']; + + return $this->app['db']->connection($connection); + } + + /** + * Get the database session options. + * + * @return array + */ + protected function getDatabaseOptions($table) + { + return array('db_table' => $table, 'db_id_col' => 'id', 'db_data_col' => 'payload', 'db_time_col' => 'last_activity'); + } + + /** + * Create an instance of the APC session driver. + * + * @return \Illuminate\Session\CacheDrivenStore + */ + protected function createApcDriver() + { + return $this->createCacheBased('apc'); + } + + /** + * Create an instance of the Memcached session driver. + * + * @return \Illuminate\Session\CacheDrivenStore + */ + protected function createMemcachedDriver() + { + return $this->createCacheBased('memcached'); + } + + /** + * Create an instance of the Wincache session driver. + * + * @return \Illuminate\Session\CacheDrivenStore + */ + protected function createWincacheDriver() + { + return $this->createCacheBased('wincache'); + } + + /** + * Create an instance of the Redis session driver. + * + * @return \Illuminate\Session\CacheDrivenStore + */ + protected function createRedisDriver() + { + $handler = $this->createCacheHandler('redis'); + + $handler->getCache()->getStore()->setConnection($this->app['config']['session.connection']); + + return $this->buildSession($handler); + } + + /** + * Create an instance of a cache driven driver. + * + * @return \Illuminate\Session\CacheDrivenStore + */ + protected function createCacheBased($driver) + { + return $this->buildSession($this->createCacheHandler($driver)); + } + + /** + * Create the cache based session handler instance. + * + * @param string $driver + * @return \Illuminate\Session\CacheBasedSessionHandler + */ + protected function createCacheHandler($driver) + { + $minutes = $this->app['config']['session.lifetime']; + + return new CacheBasedSessionHandler($this->app['cache']->driver($driver), $minutes); + } + + /** + * Build the session instance. + * + * @param \SessionHandlerInterface $handler + * @return \Illuminate\Session\Store + */ + protected function buildSession($handler) + { + return new Store(new NativeSessionStorage($this->getOptions(), $handler)); + } + + /** + * Get the session options. + * + * @return array + */ + protected function getOptions() + { + $config = $this->app['config']['session']; + + return array( + 'cookie_domain' => $config['domain'], 'cookie_lifetime' => $config['lifetime'] * 60, + 'cookie_path' => $config['path'], 'cookie_httponly' => '1', 'name' => $config['cookie'], + 'gc_divisor' => $config['lottery'][1], 'gc_probability' => $config['lottery'][0], + ); + } + + /** + * Get the default session driver name. + * + * @return string + */ + protected function getDefaultDriver() + { + return $this->app['config']['session.driver']; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Session/SessionServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Session/SessionServiceProvider.php new file mode 100755 index 0000000..be8c440 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Session/SessionServiceProvider.php @@ -0,0 +1,180 @@ +registerSessionEvents(); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->setupDefaultDriver(); + + $this->registerSessionManager(); + + $this->registerSessionDriver(); + } + + /** + * Setup the default session driver for the application. + * + * @return void + */ + protected function setupDefaultDriver() + { + if ($this->app->runningInConsole()) + { + $this->app['config']['session.driver'] = 'array'; + } + } + + /** + * Register the session manager instance. + * + * @return void + */ + protected function registerSessionManager() + { + $this->app['session.manager'] = $this->app->share(function($app) + { + return new SessionManager($app); + }); + } + + /** + * Register the session driver instance. + * + * @return void + */ + protected function registerSessionDriver() + { + $this->app['session'] = $this->app->share(function($app) + { + // First, we will create the session manager which is responsible for the + // creation of the various session drivers when they are needed by the + // application instance, and will resolve them on a lazy load basis. + $manager = $app['session.manager']; + + return $manager->driver(); + }); + } + + /** + * Register the events needed for session management. + * + * @return void + */ + protected function registerSessionEvents() + { + $config = $this->app['config']['session']; + + // The session needs to be started and closed, so we will register a before + // and after events to do all stuff for us. This will manage the loading + // the session "payloads", as well as writing them after each request. + if ( ! is_null($config['driver'])) + { + $this->registerBootingEvent(); + + $this->registerCloseEvent(); + } + } + + /** + * Register the session booting event. + * + * @return void + */ + protected function registerBootingEvent() + { + $this->app->booting(function($app) + { + $app['session']->start(); + }); + } + + /** + * Register the session close event. + * + * @return void + */ + protected function registerCloseEvent() + { + if ($this->getDriver() == 'array') return; + + // The cookie toucher is responsbile for updating the expire time on the cookie + // so that it is refreshed for each page load. Otherwise it is only set here + // once by PHP and never updated on each subsequent page load of the apps. + $this->registerCookieToucher(); + + $app = $this->app; + + $this->app->close(function() use ($app) + { + $app['session']->save(); + }); + } + + /** + * Update the session cookie lifetime on each page load. + * + * @return void + */ + protected function registerCookieToucher() + { + $me = $this; + + $this->app->close(function() use ($me) + { + if ( ! headers_sent()) $me->touchSessionCookie(); + }); + } + + /** + * Update the session identifier cookie with a new expire time. + * + * @return void + */ + public function touchSessionCookie() + { + $config = $this->app['config']['session']; + + $expire = $this->getExpireTime($config); + + setcookie($config['cookie'], session_id(), $expire, $config['path'], $config['domain'], false, true); + } + + /** + * Get the new session cookie expire time. + * + * @param array $config + * @return int + */ + protected function getExpireTime($config) + { + return $config['lifetime'] == 0 ? 0 : time() + ($config['lifetime'] * 60); + } + + /** + * Get the session driver name. + * + * @return string + */ + protected function getDriver() + { + return $this->app['config']['session.driver']; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Session/Store.php b/vendor/laravel/framework/src/Illuminate/Session/Store.php new file mode 100755 index 0000000..c758ed4 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Session/Store.php @@ -0,0 +1,252 @@ +has('_token')) $this->put('_token', str_random(40)); + } + + /** + * {@inheritdoc} + */ + public function save() + { + $this->ageFlashData(); + + return parent::save(); + } + + /** + * Age the flash data for the session. + * + * @return void + */ + protected function ageFlashData() + { + foreach ($this->get('flash.old', array()) as $old) { $this->forget($old); } + + $this->put('flash.old', $this->get('flash.new', array())); + + $this->put('flash.new', array()); + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + return ! is_null($this->get($name)); + } + + /** + * {@inheritdoc} + */ + public function get($name, $default = null) + { + return array_get($this->all(), $name, $default); + } + + /** + * Determine if the session contains old input. + * + * @param string $key + * @return bool + */ + public function hasOldInput($key = null) + { + return ! is_null($this->getOldInput($key)); + } + + /** + * Get the requested item from the flashed input array. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function getOldInput($key = null, $default = null) + { + $input = $this->get('_old_input', array()); + + // Input that is flashed to the session can be easily retrieved by the + // developer, making repopulating old forms and the like much more + // convenient, since the request's previous input is available. + if (is_null($key)) return $input; + + return array_get($input, $key, $default); + } + + /** + * Get the CSRF token value. + * + * @return string + */ + public function getToken() + { + return $this->token(); + } + + /** + * Get the CSRF token value. + * + * @return string + */ + public function token() + { + return $this->get('_token'); + } + + /** + * Put a key / value pair in the session. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function put($key, $value) + { + $all = $this->all(); + + array_set($all, $key, $value); + + $this->replace($all); + } + + /** + * Push a value onto a session array. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function push($key, $value) + { + $array = $this->get($key, array()); + + $array[] = $value; + + $this->put($key, $array); + } + + /** + * Flash a key / value pair to the session. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function flash($key, $value) + { + $this->put($key, $value); + + $this->push('flash.new', $key); + + $this->removeFromOldFlashData(array($key)); + } + + /** + * Flash an input array to the session. + * + * @param array $value + * @return void + */ + public function flashInput(array $value) + { + return $this->flash('_old_input', $value); + } + + /** + * Reflash all of the session flash data. + * + * @return void + */ + public function reflash() + { + $this->mergeNewFlashes($this->get('flash.old')); + + $this->put('flash.old', array()); + } + + /** + * Reflash a subset of the current flash data. + * + * @param array|dynamic $keys + * @return void + */ + public function keep($keys = null) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + $this->mergeNewFlashes($keys); + + $this->removeFromOldFlashData($keys); + } + + /** + * Merge new flash keys into the new flash array. + * + * @param array $keys + * @return void + */ + protected function mergeNewFlashes(array $keys) + { + $values = array_unique(array_merge($this->get('flash.new'), $keys)); + + $this->put('flash.new', $values); + } + + /** + * Remove the given keys from the old flash data. + * + * @param array $keys + * @return void + */ + protected function removeFromOldFlashData(array $keys) + { + $this->put('flash.old', array_diff($this->get('flash.old', array()), $keys)); + } + + /** + * Remove an item from the session. + * + * @param string $key + * @return void + */ + public function forget($key) + { + $all = $this->all(); + + array_forget($all, $key); + + $this->replace($all); + } + + /** + * Remove all of the items from the session. + * + * @return void + */ + public function flush() + { + return $this->clear(); + } + + /** + * Generate a new session identifier. + * + * @return string + */ + public function regenerate() + { + return $this->migrate(); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Session/TokenMismatchException.php b/vendor/laravel/framework/src/Illuminate/Session/TokenMismatchException.php new file mode 100755 index 0000000..25ce0f7 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Session/TokenMismatchException.php @@ -0,0 +1,3 @@ +=5.3.0", + "illuminate/cache": "4.0.x", + "illuminate/cookie": "4.0.x", + "illuminate/encryption": "4.0.x", + "illuminate/support": "4.0.x", + "symfony/http-foundation": "2.3.*" + }, + "require-dev": { + "illuminate/console": "4.0.x", + "mockery/mockery": ">=0.7.2", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Session": "" + } + }, + "target-dir": "Illuminate/Session", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Support/ClassLoader.php b/vendor/laravel/framework/src/Illuminate/Support/ClassLoader.php new file mode 100755 index 0000000..c1f6fbe --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Support/ClassLoader.php @@ -0,0 +1,114 @@ +items = $items; + } + + /** + * Create a new collection instance if the value isn't one already. + * + * @param mixed $items + * @return \Illuminate\Support\Collection + */ + public static function make($items) + { + if (is_null($items)) return new static; + + if ($items instanceof Collection) return $items; + + return new static(is_array($items) ? $items : array($items)); + } + + /** + * Determine if an item exists in the collection by key. + * + * @param mixed $key + * @return bool + */ + public function has($key) + { + return array_key_exists($key, $this->items); + } + + /** + * Get an item from the collection by key. + * + * @param mixed $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + if (array_key_exists($key, $this->items)) + { + return $this->items[$key]; + } + + return value($default); + } + + /** + * Get all of the items in the collection. + * + * @return array + */ + public function all() + { + return $this->items; + } + + /** + * Put an item in the collection by key. + * + * @param mixed $key + * @param mixed $value + * @return void + */ + public function put($key, $value) + { + $this->items[$key] = $value; + } + + /** + * Get the first item from the collection. + * + * @return mixed|null + */ + public function first() + { + return count($this->items) > 0 ? reset($this->items) : null; + } + + /** + * Get the last item from the collection. + * + * @return mixed|null + */ + public function last() + { + return count($this->items) > 0 ? end($this->items) : null; + } + + /** + * Get and remove the first item from the collection. + * + * @return mixed|null + */ + public function shift() + { + return array_shift($this->items); + } + + /** + * Push an item onto the beginning of the collection. + * + * @param mixed $value + * @return void + */ + public function push($value) + { + array_unshift($this->items, $value); + } + + /** + * Get and remove the last item from the collection. + * + * @return mixed|null + */ + public function pop() + { + return array_pop($this->items); + } + + /** + * Remove an item from the collection by key. + * + * @param mixed $key + * @return void + */ + public function forget($key) + { + unset($this->items[$key]); + } + + /** + * Execute a callback over each item. + * + * @param Closure $callback + * @return \Illuminate\Support\Collection + */ + public function each(Closure $callback) + { + array_map($callback, $this->items); + + return $this; + } + + /** + * Run a map over each of the items. + * + * @param Closure $callback + * @return array + */ + public function map(Closure $callback) + { + return new static(array_map($callback, $this->items)); + } + + /** + * Run a filter over each of the items. + * + * @param Closure $callback + * @return \Illuminate\Support\Collection + */ + public function filter(Closure $callback) + { + return new static(array_filter($this->items, $callback)); + } + + /** + * Sort through each item with a callback. + * + * @param Closure $callback + * @return \Illuminate\Support\Collection + */ + public function sort(Closure $callback) + { + uasort($this->items, $callback); + + return $this; + } + + /** + * Sort the collection using the given Closure. + * + * @param \Closure $callback + * @return \Illuminate\Support\Collection + */ + public function sortBy(Closure $callback) + { + $results = array(); + + // First we will loop through the items and get the comparator from a callback + // function which we were given. Then, we will sort the returned values and + // and grab the corresponding values for the sorted keys from this array. + foreach ($this->items as $key => $value) + { + $results[$key] = $callback($value); + } + + asort($results); + + // Once we have sorted all of the keys in the array, we will loop through them + // and grab the corresponding model so we can set the underlying items list + // to the sorted version. Then we'll just return the collection instance. + foreach (array_keys($results) as $key) + { + $results[$key] = $this->items[$key]; + } + + $this->items = $results; + + return $this; + } + + /** + * Reverse items order. + * + * @return \Illuminate\Support\Collection + */ + public function reverse() + { + return new static(array_reverse($this->items)); + } + + /** + * Reset the keys on the underlying array. + * + * @return \Illuminate\Support\Collection + */ + public function values() + { + $this->items = array_values($this->items); + + return $this; + } + + /** + * Fetch a nested element of the collection. + * + * @param string $key + * @return \Illuminate\Support\Collection + */ + public function fetch($key) + { + return new static(array_fetch($this->items, $key)); + } + + /** + * Get a flattened array of the items in the collection. + * + * @return array + */ + public function flatten() + { + return new static(array_flatten($this->items)); + } + + /** + * Collapse the collection items into a single array. + * + * @return \Illuminate\Support\Collection + */ + public function collapse() + { + $results = array(); + + foreach ($this->items as $values) + { + $results = array_merge($results, $values); + } + + return new static($results); + } + + /** + * Merge items with the collection items. + * + * @param \Illuminate\Support\Collection|\Illuminate\Support\Contracts\ArrayableInterface|array $items + * @return \Illuminate\Support\Collection + */ + public function merge($items) + { + if ($items instanceof Collection) + { + $items = $items->all(); + } + elseif ($items instanceof ArrayableInterface) + { + $items = $items->toArray(); + } + + $results = array_merge($this->items, $items); + + return new static($results); + } + + /** + * Slice the underlying collection array. + * + * @param int $offset + * @param int $length + * @param bool $preserveKeys + * @return \Illuminate\Support\Collection + */ + public function slice($offset, $length = null, $preserveKeys = false) + { + return new static(array_slice($this->items, $offset, $length, $preserveKeys)); + } + + /** + * Take the first or last {$limit} items. + * + * @param int $limit + * @return \Illuminate\Support\Collection + */ + public function take($limit = null) + { + if ($limit < 0) return $this->slice($limit, abs($limit)); + + return $this->slice(0, $limit); + } + + /** + * Get an array with the values of a given key. + * + * @param string $value + * @param string $key + * @return array + */ + public function lists($value, $key = null) + { + $results = array(); + + foreach ($this->items as $item) + { + $itemValue = $this->getListValue($item, $value); + + // If the key is "null", we will just append the value to the array and keep + // looping. Otherwise we will key the array using the value of the key we + // received from the developer. Then we'll return the final array form. + if (is_null($key)) + { + $results[] = $itemValue; + } + else + { + $itemKey = $this->getListValue($item, $key); + + $results[$itemKey] = $itemValue; + } + } + + return $results; + } + + /** + * Get the value of a list item object. + * + * @param mixed $item + * @param mixed $key + * @return mixed + */ + protected function getListValue($item, $key) + { + return is_object($item) ? $item->{$key} : $item[$key]; + } + + /** + * Concatenate values of a given key as a string. + * + * @param string $value + * @param string $glue + * @return string + */ + public function implode($value, $glue = null) + { + if (is_null($glue)) return implode($this->lists($value)); + + return implode($glue, $this->lists($value)); + } + + /** + * Determine if the collection is empty or not. + * + * @return bool + */ + public function isEmpty() + { + return empty($this->items); + } + + /** + * Get the collection of items as a plain array. + * + * @return array + */ + public function toArray() + { + return array_map(function($value) + { + return $value instanceof ArrayableInterface ? $value->toArray() : $value; + + }, $this->items); + } + + /** + * Get the collection of items as JSON. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->toArray(), $options); + } + + /** + * Get an iterator for the items. + * + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->items); + } + + /** + * Count the number of items in the collection. + * + * @return int + */ + public function count() + { + return count($this->items); + } + + /** + * Determine if an item exists at an offset. + * + * @param mixed $key + * @return bool + */ + public function offsetExists($key) + { + return array_key_exists($key, $this->items); + } + + /** + * Get an item at a given offset. + * + * @param mixed $key + * @return mixed + */ + public function offsetGet($key) + { + return $this->items[$key]; + } + + /** + * Set the item at a given offset. + * + * @param mixed $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value) + { + if (is_null($key)) + { + $this->items[] = $value; + } + else + { + $this->items[$key] = $value; + } + } + + /** + * Unset the item at a given offset. + * + * @param string $key + * @return void + */ + public function offsetUnset($key) + { + unset($this->items[$key]); + } + + /** + * Convert the collection to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->toJson(); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Support/Contracts/ArrayableInterface.php b/vendor/laravel/framework/src/Illuminate/Support/Contracts/ArrayableInterface.php new file mode 100755 index 0000000..d5b22ef --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Support/Contracts/ArrayableInterface.php @@ -0,0 +1,12 @@ +getEngineResolver()->resolve('blade')->getCompiler(); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Support/Facades/Cache.php b/vendor/laravel/framework/src/Illuminate/Support/Facades/Cache.php new file mode 100755 index 0000000..7138ef1 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Support/Facades/Cache.php @@ -0,0 +1,12 @@ +instance(static::getFacadeAccessor(), $instance); + } + + /** + * Initiate a mock expectation on the facade. + * + * @param dynamic + * @return \Mockery\Expectation + */ + public static function shouldReceive() + { + $name = static::getFacadeAccessor(); + + if (static::isMock()) + { + $mock = static::$resolvedInstance[$name]; + } + else + { + static::$resolvedInstance[$name] = $mock = \Mockery::mock(static::getMockableClass($name)); + + static::$app->instance($name, $mock); + } + + return call_user_func_array(array($mock, 'shouldReceive'), func_get_args()); + } + + /** + * Determines whether a mock is set as the instance of the facade. + * + * @return bool + */ + protected static function isMock() + { + $name = static::getFacadeAccessor(); + + return isset(static::$resolvedInstance[$name]) and static::$resolvedInstance[$name] instanceof MockInterface; + } + + /** + * Get the mockable class for the bound instance. + * + * @return string + */ + protected static function getMockableClass() + { + return get_class(static::getFacadeRoot()); + } + + /** + * Get the root object behind the facade. + * + * @return mixed + */ + public static function getFacadeRoot() + { + return static::resolveFacadeInstance(static::getFacadeAccessor()); + } + + /** + * Get the registered name of the component. + * + * @return string + */ + protected static function getFacadeAccessor() + { + throw new \RuntimeException("Facade does not implement getFacadeAccessor method."); + } + + /** + * Resolve the facade root instance from the container. + * + * @param string $name + * @return mixed + */ + protected static function resolveFacadeInstance($name) + { + if (is_object($name)) return $name; + + if (isset(static::$resolvedInstance[$name])) + { + return static::$resolvedInstance[$name]; + } + + return static::$resolvedInstance[$name] = static::$app[$name]; + } + + /** + * Clear a resolved facade instance. + * + * @param string $name + * @return void + */ + public static function clearResolvedInstance($name) + { + unset(static::$resolvedInstance[$name]); + } + + /** + * Clear all of the resolved instances. + * + * @return void + */ + public static function clearResolvedInstances() + { + static::$resolvedInstance = array(); + } + + /** + * Get the application instance behind the facade. + * + * @return \Illuminate\Foundation\Application + */ + public static function getFacadeApplication() + { + return static::$app; + } + + /** + * Set the application instance. + * + * @param \Illuminate\Foundation\Application $app + * @return void + */ + public static function setFacadeApplication($app) + { + static::$app = $app; + } + + /** + * Handle dynamic, static calls to the object. + * + * @param string $method + * @param array $args + * @return mixed + */ + public static function __callStatic($method, $args) + { + $instance = static::resolveFacadeInstance(static::getFacadeAccessor()); + + switch (count($args)) + { + case 0: + return $instance->$method(); + + case 1: + return $instance->$method($args[0]); + + case 2: + return $instance->$method($args[0], $args[1]); + + case 3: + return $instance->$method($args[0], $args[1], $args[2]); + + case 4: + return $instance->$method($args[0], $args[1], $args[2], $args[3]); + + default: + return call_user_func_array(array($instance, $method), $args); + } + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Support/Facades/File.php b/vendor/laravel/framework/src/Illuminate/Support/Facades/File.php new file mode 100755 index 0000000..51d9917 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Support/Facades/File.php @@ -0,0 +1,12 @@ +input($key, $default); + } + + /** + * Get the registered name of the component. + * + * @return string + */ + protected static function getFacadeAccessor() { return 'request'; } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Support/Facades/Lang.php b/vendor/laravel/framework/src/Illuminate/Support/Facades/Lang.php new file mode 100755 index 0000000..6b4c066 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Support/Facades/Lang.php @@ -0,0 +1,12 @@ +make($view, $data), $status, $headers); + } + + /** + * Return a new JSON response from the application. + * + * @param string|array $data + * @param int $status + * @param array $headers + * @return \Illuminate\Http\JsonResponse + */ + public static function json($data = array(), $status = 200, array $headers = array()) + { + if ($data instanceof ArrayableInterface) + { + $data = $data->toArray(); + } + + return new JsonResponse($data, $status, $headers); + } + + /** + * Return a new streamed response from the application. + * + * @param Closure $callback + * @param int $status + * @param array $headers + * @return \Symfony\Component\HttpFoundation\StreamedResponse + */ + public static function stream($callback, $status = 200, array $headers = array()) + { + return new \Symfony\Component\HttpFoundation\StreamedResponse($callback, $status, $headers); + } + + /** + * Create a new file download response. + * + * @param SplFileInfo|string $file + * @param string $name + * @param array $headers + * @return \Symfony\Component\HttpFoundation\BinaryFileResponse + */ + public static function download($file, $name = null, array $headers = array()) + { + $response = new BinaryFileResponse($file, 200, $headers, true, 'attachment'); + + if ( ! is_null($name)) + { + return $response->setContentDisposition('attachment', $name); + } + + return $response; + } + + /** + * Register a macro with the Response class. + * + * @param string $name + * @param callable $callback + * @return void + */ + public static function macro($name, $callback) + { + static::$macros[$name] = $callback; + } + + /** + * Handle dynamic calls into Response macros. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public static function __callStatic($method, $parameters) + { + if (isset(static::$macros[$method])) + { + return call_user_func_array(static::$macros[$method], func_get_args()); + } + + throw new \BadMethodCallException("Call to undefined method $method"); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Support/Facades/Route.php b/vendor/laravel/framework/src/Illuminate/Support/Facades/Route.php new file mode 100755 index 0000000..b730967 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Support/Facades/Route.php @@ -0,0 +1,34 @@ +currentRouteNamed($name); + } + + /** + * Determine if the current route uses a given controller action. + * + * @param string $action + * @return bool + */ + public static function uses($action) + { + return static::$app['router']->currentRouteUses($action); + } + + /** + * Get the registered name of the component. + * + * @return string + */ + protected static function getFacadeAccessor() { return 'router'; } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Support/Facades/Schema.php b/vendor/laravel/framework/src/Illuminate/Support/Facades/Schema.php new file mode 100755 index 0000000..a3f101b --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Support/Facades/Schema.php @@ -0,0 +1,26 @@ +connection($name)->getSchemaBuilder(); + } + + /** + * Get the registered name of the component. + * + * @return string + */ + protected static function getFacadeAccessor() + { + return static::$app['db']->connection()->getSchemaBuilder(); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Support/Facades/Session.php b/vendor/laravel/framework/src/Illuminate/Support/Facades/Session.php new file mode 100755 index 0000000..622458e --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Support/Facades/Session.php @@ -0,0 +1,12 @@ + $value) + { + $this->attributes[$key] = $value; + } + } + + /** + * Get an attribute from the container. + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + if (array_key_exists($key, $this->attributes)) + { + return $this->attributes[$key]; + } + + return value($default); + } + + /** + * Get the attributes from the container. + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Determine if the given offset exists. + * + * @param string $offset + * @return bool + */ + public function offsetExists($offset) + { + return isset($this->{$offset}); + } + + /** + * Get the value for a given offset. + * + * @param string $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->{$offset}; + } + + /** + * Set the value at the given offset. + * + * @param string $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value) + { + $this->{$offset} = $value; + } + + /** + * Unset the value at the given offset. + * + * @param string $offset + * @return void + */ + public function offsetUnset($offset) + { + unset($this->{$offset}); + } + + /** + * Handle dynamic calls to the container to set attributes. + * + * @param string $method + * @param array $parameters + * @return \Illuminate\Support\Fluent + */ + public function __call($method, $parameters) + { + $this->attributes[$method] = count($parameters) > 0 ? $parameters[0] : true; + + return $this; + } + + /** + * Dynamically retrieve the value of an attribute. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->get($key); + } + + /** + * Dynamically set the value of an attribute. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this->attributes[$key] = $value; + } + + /** + * Dynamically check if an attribute is set. + * + * @param string $key + * @return void + */ + public function __isset($key) + { + return isset($this->attributes[$key]); + } + + /** + * Dynamically unset an attribute. + * + * @param string $key + * @return void + */ + public function __unset($key) + { + unset($this->attributes[$key]); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Support/Manager.php b/vendor/laravel/framework/src/Illuminate/Support/Manager.php new file mode 100755 index 0000000..e7f03f9 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Support/Manager.php @@ -0,0 +1,130 @@ +app = $app; + } + + /** + * Get a driver instance. + * + * @param string $driver + * @return mixed + */ + public function driver($driver = null) + { + $driver = $driver ?: $this->getDefaultDriver(); + + // If the given driver has not been created before, we will create the instances + // here and cache it so we can return it next time very quickly. If their is + // already a driver created by this name, we'll just return that instance. + if ( ! isset($this->drivers[$driver])) + { + $this->drivers[$driver] = $this->createDriver($driver); + } + + return $this->drivers[$driver]; + } + + /** + * Create a new driver instance. + * + * @param string $driver + * @return mixed + */ + protected function createDriver($driver) + { + $method = 'create'.ucfirst($driver).'Driver'; + + // We'll check to see if a creator method exists for the given driver. If not we + // will check for a custom driver creator, which allows developers to create + // drivers using their own customized driver creator Closure to create it. + if (isset($this->customCreators[$driver])) + { + return $this->callCustomCreator($driver); + } + elseif (method_exists($this, $method)) + { + return $this->$method(); + } + + throw new \InvalidArgumentException("Driver [$driver] not supported."); + } + + /** + * Call a custom driver creator. + * + * @param string $driver + * @return mixed + */ + protected function callCustomCreator($driver) + { + return $this->customCreators[$driver]($this->app); + } + + /** + * Register a custom driver creator Closure. + * + * @param string $driver + * @param Closure $callback + * @return void + */ + public function extend($driver, Closure $callback) + { + $this->customCreators[$driver] = $callback; + } + + /** + * Get all of the created "drivers". + * + * @return array + */ + public function getDrivers() + { + return $this->drivers; + } + + /** + * Dynamically call the default driver instance. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return call_user_func_array(array($this->driver(), $method), $parameters); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Support/MessageBag.php b/vendor/laravel/framework/src/Illuminate/Support/MessageBag.php new file mode 100755 index 0000000..b4528c1 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Support/MessageBag.php @@ -0,0 +1,289 @@ + $value) + { + $this->messages[$key] = (array) $value; + } + } + + /** + * Add a message to the bag. + * + * @param string $key + * @param string $message + * @return \Illuminate\Support\MessageBag + */ + public function add($key, $message) + { + if ($this->isUnique($key, $message)) + { + $this->messages[$key][] = $message; + } + + return $this; + } + + /** + * Merge a new array of messages into the bag. + * + * @param array $messages + * @return \Illuminate\Support\MessageBag + */ + public function merge(array $messages) + { + $this->messages = array_merge_recursive($this->messages, $messages); + + return $this; + } + + /** + * Determine if a key and message combination already exists. + * + * @param string $key + * @param string $message + * @return bool + */ + protected function isUnique($key, $message) + { + $messages = (array) $this->messages; + + return ! isset($messages[$key]) or ! in_array($message, $messages[$key]); + } + + /** + * Determine if messages exist for a given key. + * + * @param string $key + * @return bool + */ + public function has($key = null) + { + return $this->first($key) !== ''; + } + + /** + * Get the first message from the bag for a given key. + * + * @param string $key + * @param string $format + * @return string + */ + public function first($key = null, $format = null) + { + $messages = is_null($key) ? $this->all($format) : $this->get($key, $format); + + return (count($messages) > 0) ? $messages[0] : ''; + } + + /** + * Get all of the messages from the bag for a given key. + * + * @param string $key + * @param string $format + * @return array + */ + public function get($key, $format = null) + { + $format = $this->checkFormat($format); + + // If the message exists in the container, we will transform it and return + // the message. Otherwise, we'll return an empty array since the entire + // methods is to return back an array of messages in the first place. + if (array_key_exists($key, $this->messages)) + { + return $this->transform($this->messages[$key], $format, $key); + } + + return array(); + } + + /** + * Get all of the messages for every key in the bag. + * + * @param string $format + * @return array + */ + public function all($format = null) + { + $format = $this->checkFormat($format); + + $all = array(); + + foreach ($this->messages as $key => $messages) + { + $all = array_merge($all, $this->transform($messages, $format, $key)); + } + + return $all; + } + + /** + * Format an array of messages. + * + * @param array $messages + * @param string $format + * @param string $messageKey + * @return array + */ + protected function transform($messages, $format, $messageKey) + { + $messages = (array) $messages; + + // We will simply spin through the given messages and transform each one + // replacing the :message place holder with the real message allowing + // the messages to be easily formatted to each developer's desires. + foreach ($messages as $key => &$message) + { + $replace = array(':message', ':key'); + + $message = str_replace($replace, array($message, $messageKey), $format); + } + + return $messages; + } + + /** + * Get the appropriate format based on the given format. + * + * @param string $format + * @return string + */ + protected function checkFormat($format) + { + return ($format === null) ? $this->format : $format; + } + + /** + * Get the raw messages in the container. + * + * @return array + */ + public function getMessages() + { + return $this->messages; + } + + /** + * Get the messages for the instance. + * + * @return \Illuminate\Support\MessageBag + */ + public function getMessageBag() + { + return $this; + } + + /** + * Get the default message format. + * + * @return string + */ + public function getFormat() + { + return $this->format; + } + + /** + * Set the default message format. + * + * @param string $format + * @return \Illuminate\Support\MessageBag + */ + public function setFormat($format = ':message') + { + $this->format = $format; + + return $this; + } + + /** + * Determine if the message bag has any messages. + * + * @return bool + */ + public function isEmpty() + { + return ! $this->any(); + } + + /** + * Determine if the message bag has any messages. + * + * @return bool + */ + public function any() + { + return $this->count() > 0; + } + + /** + * Get the number of messages in the container. + * + * @return int + */ + public function count() + { + return count($this->messages); + } + + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray() + { + return $this->getMessages(); + } + + /** + * Convert the object to its JSON representation. + * + * @param int $options + * @return string + */ + public function toJson($options = 0) + { + return json_encode($this->toArray(), $options); + } + + /** + * Convert the message bag to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->toJson(); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Support/NamespacedItemResolver.php b/vendor/laravel/framework/src/Illuminate/Support/NamespacedItemResolver.php new file mode 100755 index 0000000..82d8a56 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Support/NamespacedItemResolver.php @@ -0,0 +1,109 @@ +parsed[$key])) + { + return $this->parsed[$key]; + } + + $segments = explode('.', $key); + + // If the key does not contain a double colon, it means the key is not in a + // namespace, and is just a regular configuration item. Namespaces are a + // tool for organizing configuration items for things such as modules. + if (strpos($key, '::') === false) + { + $parsed = $this->parseBasicSegments($segments); + } + else + { + $parsed = $this->parseNamespacedSegments($key); + } + + // Once we have the parsed array of this key's elements, such as its groups + // and namespace, we will cache each array inside a simple list that has + // the key and the parsed array for quick look-ups for later requests. + return $this->parsed[$key] = $parsed; + } + + /** + * Parse an array of basic segments. + * + * @param array $segments + * @return array + */ + protected function parseBasicSegments(array $segments) + { + // The first segment in a basic array will always be the group, so we can go + // ahead and grab that segment. If there is only one total segment we are + // just pulling an entire group out of the array and not a single item. + $group = $segments[0]; + + if (count($segments) == 1) + { + return array(null, $group, null); + } + + // If there is more than one segment in the group, ite means we are pulling + // a specific item out of a groups and will need to return the item name + // as well as the group so we know which item to pull from the arrays. + else + { + $item = implode('.', array_slice($segments, 1)); + + return array(null, $group, $item); + } + } + + /** + * Parse an array of namespaced segments. + * + * @param string $key + * @return array + */ + protected function parseNamespacedSegments($key) + { + list($namespace, $item) = explode('::', $key); + + // First we'll just explode the first segment to get the namespace and group + // since the item should be in the remaining segments. Once we have these + // two pieces of data we can proceed with parsing out the item's value. + $itemSegments = explode('.', $item); + + $groupAndItem = array_slice($this->parseBasicSegments($itemSegments), 1); + + return array_merge(array($namespace), $groupAndItem); + } + + /** + * Set the parsed value of a key. + * + * @param string $key + * @param array $parsed + * @return void + */ + public function setParsedKey($key, $parsed) + { + $this->parsed[$key] = $parsed; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Support/Pluralizer.php b/vendor/laravel/framework/src/Illuminate/Support/Pluralizer.php new file mode 100755 index 0000000..5ffc48e --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Support/Pluralizer.php @@ -0,0 +1,243 @@ + "$1zes", + '/^(ox)$/i' => "$1en", + '/([m|l])ouse$/i' => "$1ice", + '/(matr|vert|ind)ix|ex$/i' => "$1ices", + '/(x|ch|ss|sh)$/i' => "$1es", + '/([^aeiouy]|qu)y$/i' => "$1ies", + '/(hive)$/i' => "$1s", + '/(?:([^f])fe|([lr])f)$/i' => "$1$2ves", + '/(shea|lea|loa|thie)f$/i' => "$1ves", + '/sis$/i' => "ses", + '/([ti])um$/i' => "$1a", + '/(tomat|potat|ech|her|vet)o$/i' => "$1oes", + '/(bu)s$/i' => "$1ses", + '/(alias)$/i' => "$1es", + '/(octop)us$/i' => "$1i", + '/(ax|test)is$/i' => "$1es", + '/(us)$/i' => "$1es", + '/s$/i' => "s", + '/$/' => "s", + ); + + /** + * Singular word form rules. + * + * @var array + */ + protected static $singular = array( + '/(quiz)zes$/i' => "$1", + '/(matr)ices$/i' => "$1ix", + '/(vert|ind)ices$/i' => "$1ex", + '/^(ox)en$/i' => "$1", + '/(alias)es$/i' => "$1", + '/(octop|vir)i$/i' => "$1us", + '/(cris|ax|test)es$/i' => "$1is", + '/(shoe)s$/i' => "$1", + '/(o)es$/i' => "$1", + '/(bus)es$/i' => "$1", + '/([m|l])ice$/i' => "$1ouse", + '/(x|ch|ss|sh)es$/i' => "$1", + '/(m)ovies$/i' => "$1ovie", + '/(s)eries$/i' => "$1eries", + '/([^aeiouy]|qu)ies$/i' => "$1y", + '/([lr])ves$/i' => "$1f", + '/(tive)s$/i' => "$1", + '/(hive)s$/i' => "$1", + '/(li|wi|kni)ves$/i' => "$1fe", + '/(shea|loa|lea|thie)ves$/i' => "$1f", + '/(^analy)ses$/i' => "$1sis", + '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => "$1$2sis", + '/([ti])a$/i' => "$1um", + '/(n)ews$/i' => "$1ews", + '/(h|bl)ouses$/i' => "$1ouse", + '/(corpse)s$/i' => "$1", + '/(us)es$/i' => "$1", + '/(us|ss)$/i' => "$1", + '/s$/i' => "", + ); + + /** + * Irregular word forms. + * + * @var array + */ + protected static $irregular = array( + 'child' => 'children', + 'foot' => 'feet', + 'goose' => 'geese', + 'man' => 'men', + 'move' => 'moves', + 'person' => 'people', + 'sex' => 'sexes', + 'tooth' => 'teeth', + ); + + /** + * Uncountable word forms. + * + * @var array + */ + protected static $uncountable = array( + 'audio', + 'equipment', + 'deer', + 'fish', + 'gold', + 'information', + 'money', + 'rice', + 'police', + 'series', + 'sheep', + 'species', + 'moose', + 'chassis', + 'traffic', + ); + + /** + * The cached copies of the plural inflections. + * + * @var array + */ + protected static $pluralCache = array(); + + /** + * The cached copies of the singular inflections. + * + * @var array + */ + protected static $singularCache = array(); + + /** + * Get the singular form of the given word. + * + * @param string $value + * @return string + */ + public static function singular($value) + { + if (isset(static::$singularCache[$value])) + { + return static::$singularCache[$value]; + } + + $result = static::inflect($value, static::$singular, static::$irregular); + + return static::$singularCache[$value] = $result ?: $value; + } + + /** + * Get the plural form of the given word. + * + * @param string $value + * @param int $count + * @return string + */ + public static function plural($value, $count = 2) + { + if ($count == 1) return $value; + + // First we'll check the cache of inflected values. We cache each word that + // is inflected so we don't have to spin through the regular expressions + // on each subsequent method calls for this word by the app developer. + if (isset(static::$pluralCache[$value])) + { + return static::$pluralCache[$value]; + } + + $irregular = array_flip(static::$irregular); + + // When doing the singular to plural transformation, we'll flip the irregular + // array since we need to swap sides on the keys and values. After we have + // the transformed value we will cache it in memory for faster look-ups. + $plural = static::$plural; + + $result = static::inflect($value, $plural, $irregular); + + return static::$pluralCache[$value] = $result; + } + + /** + * Perform auto inflection on an English word. + * + * @param string $value + * @param array $source + * @param array $irregular + * @return string + */ + protected static function inflect($value, $source, $irregular) + { + if (static::uncountable($value)) return $value; + + // Next, we will check the "irregular" patterns which contain words that are + // not easily summarized in regular expression rules, like "children" and + // "teeth", both of which cannot get inflected using our typical rules. + foreach ($irregular as $irregular => $pattern) + { + if (preg_match($pattern = '/'.$pattern.'$/i', $value)) + { + $irregular = static::matchCase($irregular, $value); + + return preg_replace($pattern, $irregular, $value); + } + } + + // Finally, we'll spin through the array of regular expressions and look for + // matches for the word. If we find a match, we will cache and return the + // transformed value so we will quickly look it up on subsequent calls. + foreach ($source as $pattern => $inflected) + { + if (preg_match($pattern, $value)) + { + $inflected = preg_replace($pattern, $inflected, $value); + + return static::matchCase($inflected, $value); + } + } + } + + /** + * Determine if the given value is uncountable. + * + * @param string $value + * @return bool + */ + protected static function uncountable($value) + { + return in_array(strtolower($value), static::$uncountable); + } + + /** + * Attempt to match the case on two strings. + * + * @param string $value + * @param string $comparison + * @return string + */ + protected static function matchCase($value, $comparison) + { + $functions = array('mb_strtolower', 'mb_strtoupper', 'ucfirst', 'ucwords'); + + foreach ($functions as $function) + { + if (call_user_func($function, $comparison) === $comparison) + { + return call_user_func($function, $value); + } + } + + return $value; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Support/SerializableClosure.php b/vendor/laravel/framework/src/Illuminate/Support/SerializableClosure.php new file mode 100755 index 0000000..ca68a6f --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Support/SerializableClosure.php @@ -0,0 +1,198 @@ + + * @author Jeremy Lindblom <@jeremeamia> + */ +class SerializableClosure implements Serializable { + + /** + * The Closure instance. + * + * @var \Closure + */ + protected $closure; + + /** + * The ReflectionFunction instance of the Closure. + * + * @var \ReflectionFunction + */ + protected $reflection; + + /** + * The code contained by the Closure. + * + * @var string + */ + protected $code; + + /** + * Create a new serializable Closure instance. + * + * @param \Closure $closure + * @return void + */ + public function __construct(Closure $closure) + { + $this->closure = $closure; + + $this->reflection = new ReflectionFunction($closure); + } + + /** + * Get the code for the Closure. + * + * @return string + */ + public function getCode() + { + return $this->code ?: $this->code = $this->getCodeFromFile(); + } + + /** + * Extract the code from the Closure's file. + * + * @return string + */ + protected function getCodeFromFile() + { + $file = $this->getFile(); + + $code = ''; + + // Next, we will just loop through the lines of the file until we get to the end + // of the Closure. Then, we will return the complete contents of this Closure + // so it can be serialized with these variables and stored for later usage. + while ($file->key() < $this->reflection->getEndLine()) + { + $code .= $file->current(); $file->next(); + } + + $begin = strpos($code, 'function('); + + return substr($code, $begin, strrpos($code, '}') - $begin + 1); + } + + /** + * Get an SplObjectFile object at the starting line of the Closure. + * + * @return \SplFileObject + */ + protected function getFile() + { + $file = new SplFileObject($this->reflection->getFileName()); + + $file->seek($this->reflection->getStartLine() - 1); + + return $file; + } + + /** + * Get the variables used by the Closure. + * + * @return array + */ + public function getVariables() + { + if ( ! $this->getUseIndex()) return array(); + + $staticVariables = $this->reflection->getStaticVariables(); + + // When looping through the variables, we will only take the variables that are + // specified in the use clause, and will not take any other static variables + // that may be used by the Closures, allowing this to re-create its state. + $usedVariables = array(); + + foreach ($this->getUseClauseVariables() as $variable) + { + $variable = trim($variable, ' $&'); + + $usedVariables[$variable] = $staticVariables[$variable]; + } + + return $usedVariables; + } + + /** + * Get the variables from the "use" clause. + * + * @return array + */ + protected function getUseClauseVariables() + { + $begin = strpos($code = $this->getCode(), '(', $this->getUseIndex()) + 1; + + return explode(',', substr($code, $begin, strpos($code, ')', $begin) - $begin)); + } + + /** + * Get the index location of the "use" clause. + * + * @return int + */ + protected function getUseIndex() + { + return stripos(strtok($this->getCode(), PHP_EOL), ' use '); + } + + /** + * Serialize the Closure instance. + * + * @return string + */ + public function serialize() + { + return serialize(array( + 'code' => $this->getCode(), 'variables' => $this->getVariables() + )); + } + + /** + * Unserialize the Closure instance. + * + * @param string $serialized + * @return void + */ + public function unserialize($serialized) + { + $payload = unserialize($serialized); + + // We will extract the variables into the current scope so that as the Closure + // is built it will inherit the scope it had before it was serialized which + // will emulate the Closures existing in that scope instead of right now. + extract($payload['variables']); + + eval('$this->closure = '.$payload['code'].';'); + + $this->reflection = new ReflectionFunction($this->closure); + } + + /** + * Get the unserialized Closure instance. + * + * @return \Closure + */ + public function getClosure() + { + return $this->closure; + } + + /** + * Invoke the contained Closure. + * + * @return mixed + */ + public function __invoke() + { + return call_user_func_array($this->closure, func_get_args()); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Support/ServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Support/ServiceProvider.php new file mode 100755 index 0000000..0d03972 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Support/ServiceProvider.php @@ -0,0 +1,213 @@ +app = $app; + } + + /** + * Bootstrap the application events. + * + * @return void + */ + public function boot() {} + + /** + * Register the service provider. + * + * @return void + */ + abstract public function register(); + + /** + * Register the package's component namespaces. + * + * @param string $package + * @param string $namespace + * @param string $path + * @return void + */ + public function package($package, $namespace = null, $path = null) + { + $namespace = $this->getPackageNamespace($package, $namespace); + + // In this method we will register the configuration package for the package + // so that the configuration options cleanly cascade into the application + // folder to make the developers lives much easier in maintaining them. + $path = $path ?: $this->guessPackagePath(); + + $config = $path.'/config'; + + if ($this->app['files']->isDirectory($config)) + { + $this->app['config']->package($package, $config, $namespace); + } + + // Next we will check for any "language" components. If language files exist + // we will register them with this given package's namespace so that they + // may be accessed using the translation facilities of the application. + $lang = $path.'/lang'; + + if ($this->app['files']->isDirectory($lang)) + { + $this->app['translator']->addNamespace($namespace, $lang); + } + + // Next, we will see if the application view folder contains a folder for the + // package and namespace. If it does, we'll give that folder precedence on + // the loader list for the views so the package views can be overridden. + $appView = $this->getAppViewPath($package, $namespace); + + if ($this->app['files']->isDirectory($appView)) + { + $this->app['view']->addNamespace($namespace, $appView); + } + + // Finally we will register the view namespace so that we can access each of + // the views available in this package. We use a standard convention when + // registering the paths to every package's views and other components. + $view = $path.'/views'; + + if ($this->app['files']->isDirectory($view)) + { + $this->app['view']->addNamespace($namespace, $view); + } + } + + /** + * Guess the package path for the provider. + * + * @return string + */ + public function guessPackagePath() + { + $reflect = new ReflectionClass($this); + + // We want to get the class that is closest to this base class in the chain of + // classes extending it. That should be the original service provider given + // by the package and should allow us to guess the location of resources. + $chain = $this->getClassChain($reflect); + + $path = $chain[count($chain) - 2]->getFileName(); + + return realpath(dirname($path).'/../../'); + } + + /** + * Get a class from the ReflectionClass inheritance chain. + * + * @param ReflectionClass $reflection + * @return array + */ + protected function getClassChain(ReflectionClass $reflect) + { + $classes = array(); + + // This loop essentially walks the inheritance chain of the classes starting + // at the most "childish" class and walks back up to this class. Once we + // get to the end of the chain we will bail out and return the offset. + while ($reflect !== false) + { + $classes[] = $reflect; + + $reflect = $reflect->getParentClass(); + } + + return $classes; + } + + /** + * Determine the namespace for a package. + * + * @param string $package + * @param string $namespace + * @return string + */ + protected function getPackageNamespace($package, $namespace) + { + if (is_null($namespace)) + { + list($vendor, $namespace) = explode('/', $package); + } + + return $namespace; + } + + /** + * Register the package's custom Artisan commands. + * + * @param array $commands + * @return void + */ + public function commands($commands) + { + $commands = is_array($commands) ? $commands : func_get_args(); + + // To register the commands with Artisan, we will grab each of the arguments + // passed into the method and listen for Artisan "start" event which will + // give us the Artisan console instance which we will give commands to. + $events = $this->app['events']; + + $events->listen('artisan.start', function($artisan) use ($commands) + { + $artisan->resolveCommands($commands); + }); + } + + /** + * Get the application package view path. + * + * @param string $package + * @param string $namespace + * @return string + */ + protected function getAppViewPath($package, $namespace) + { + return $this->app['path']."/views/packages/{$package}/{$namespace}"; + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array(); + } + + /** + * Determine if the provider is deferred. + * + * @return bool + */ + public function isDeferred() + { + return $this->defer; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Support/Str.php b/vendor/laravel/framework/src/Illuminate/Support/Str.php new file mode 100755 index 0000000..2ba5095 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Support/Str.php @@ -0,0 +1,346 @@ +=5.3.0" + }, + "require-dev": { + "patchwork/utf8": "1.1.*", + "mockery/mockery": "0.7.2", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Support": "" + }, + "files": [ + "Illuminate/Support/helpers.php" + ] + }, + "target-dir": "Illuminate/Support", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/laravel/framework/src/Illuminate/Support/helpers.php b/vendor/laravel/framework/src/Illuminate/Support/helpers.php new file mode 100755 index 0000000..d4e02b9 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Support/helpers.php @@ -0,0 +1,935 @@ +action($name, $parameters, $absolute); + } +} + +if ( ! function_exists('app')) +{ + /** + * Get the root Facade application instance. + * + * @param string $make + * @return mixed + */ + function app($make = null) + { + if ( ! is_null($make)) + { + return app()->make($make); + } + + return Illuminate\Support\Facades\Facade::getFacadeApplication(); + } +} + +if ( ! function_exists('app_path')) +{ + /** + * Get the path to the application folder. + * + * @param string $path + * @return string + */ + function app_path($path = '') + { + return app('path').($path ? '/'.$path : $path); + } +} + +if ( ! function_exists('array_add')) +{ + /** + * Add an element to an array if it doesn't exist. + * + * @param array $array + * @param string $key + * @param mixed $value + * @return array + */ + function array_add($array, $key, $value) + { + if ( ! isset($array[$key])) $array[$key] = $value; + + return $array; + } +} + +if ( ! function_exists('array_build')) +{ + /** + * Build a new array using a callback. + * + * @param array $array + * @param \Closure $callback + * @return array + */ + function array_build($array, Closure $callback) + { + $results = array(); + + foreach ($array as $key => $value) + { + list($innerKey, $innerValue) = call_user_func($callback, $key, $value); + + $results[$innerKey] = $innerValue; + } + + return $results; + } +} + +if ( ! function_exists('array_divide')) +{ + /** + * Divide an array into two arrays. One with keys and the other with values. + * + * @param array $array + * @return array + */ + function array_divide($array) + { + return array(array_keys($array), array_values($array)); + } +} + +if ( ! function_exists('array_dot')) +{ + /** + * Flatten a multi-dimensional associative array with dots. + * + * @param array $array + * @param string $prepend + * @return array + */ + function array_dot($array, $prepend = '') + { + $results = array(); + + foreach ($array as $key => $value) + { + if (is_array($value)) + { + $results = array_merge($results, array_dot($value, $prepend.$key.'.')); + } + else + { + $results[$prepend.$key] = $value; + } + } + + return $results; + } +} + +if ( ! function_exists('array_except')) +{ + /** + * Get all of the given array except for a specified array of items. + * + * @param array $array + * @param array $keys + * @return array + */ + function array_except($array, $keys) + { + return array_diff_key($array, array_flip((array) $keys)); + } +} + +if ( ! function_exists('array_fetch')) +{ + /** + * Fetch a flattened array of a nested array element. + * + * @param array $array + * @param string $key + * @return array + */ + function array_fetch($array, $key) + { + foreach (explode('.', $key) as $segment) + { + $results = array(); + + foreach ($array as $value) + { + $value = (array) $value; + + $results[] = $value[$segment]; + } + + $array = array_values($results); + } + + return array_values($results); + } +} + +if ( ! function_exists('array_first')) +{ + /** + * Return the first element in an array passing a given truth test. + * + * @param array $array + * @param Closure $callback + * @param mixed $default + * @return mixed + */ + function array_first($array, $callback, $default = null) + { + foreach ($array as $key => $value) + { + if (call_user_func($callback, $key, $value)) return $value; + } + + return value($default); + } +} + +if ( ! function_exists('array_flatten')) +{ + /** + * Flatten a multi-dimensional array into a single level. + * + * @param array $array + * @return array + */ + function array_flatten($array) + { + $return = array(); + + array_walk_recursive($array, function($x) use (&$return) { $return[] = $x; }); + + return $return; + } +} + +if ( ! function_exists('array_forget')) +{ + /** + * Remove an array item from a given array using "dot" notation. + * + * @param array $array + * @param string $key + * @return void + */ + function array_forget(&$array, $key) + { + $keys = explode('.', $key); + + while (count($keys) > 1) + { + $key = array_shift($keys); + + if ( ! isset($array[$key]) or ! is_array($array[$key])) + { + return; + } + + $array =& $array[$key]; + } + + unset($array[array_shift($keys)]); + } +} + +if ( ! function_exists('array_get')) +{ + /** + * Get an item from an array using "dot" notation. + * + * @param array $array + * @param string $key + * @param mixed $default + * @return mixed + */ + function array_get($array, $key, $default = null) + { + if (is_null($key)) return $array; + + if (isset($array[$key])) return $array[$key]; + + foreach (explode('.', $key) as $segment) + { + if ( ! is_array($array) or ! array_key_exists($segment, $array)) + { + return value($default); + } + + $array = $array[$segment]; + } + + return $array; + } +} + +if ( ! function_exists('array_only')) +{ + /** + * Get a subset of the items from the given array. + * + * @param array $array + * @param array $keys + * @return array + */ + function array_only($array, $keys) + { + return array_intersect_key($array, array_flip((array) $keys)); + } +} + +if ( ! function_exists('array_pluck')) +{ + /** + * Pluck an array of values from an array. + * + * @param array $array + * @param string $key + * @return array + */ + function array_pluck($array, $key) + { + return array_map(function($value) use ($key) + { + return is_object($value) ? $value->$key : $value[$key]; + + }, $array); + } +} + +if ( ! function_exists('array_pull')) +{ + /** + * Get a value from the array, and remove it. + * + * @param array $array + * @param string $key + * @return mixed + */ + function array_pull(&$array, $key) + { + $value = array_get($array, $key); + + array_forget($array, $key); + + return $value; + } +} + +if ( ! function_exists('array_set')) +{ + /** + * Set an array item to a given value using "dot" notation. + * + * If no key is given to the method, the entire array will be replaced. + * + * @param array $array + * @param string $key + * @param mixed $value + * @return array + */ + function array_set(&$array, $key, $value) + { + if (is_null($key)) return $array = $value; + + $keys = explode('.', $key); + + while (count($keys) > 1) + { + $key = array_shift($keys); + + // If the key doesn't exist at this depth, we will just create an empty array + // to hold the next value, allowing us to create the arrays to hold final + // values at the correct depth. Then we'll keep digging into the array. + if ( ! isset($array[$key]) or ! is_array($array[$key])) + { + $array[$key] = array(); + } + + $array =& $array[$key]; + } + + $array[array_shift($keys)] = $value; + + return $array; + } +} + +if ( ! function_exists('array_sort')) +{ + /** + * Sort the array using the given Closure. + * + * @param array $array + * @param \Closure $callback + * @return array + */ + function array_sort($array, Closure $callback) + { + return Illuminate\Support\Collection::make($array)->sortBy($callback)->all(); + } +} + +if ( ! function_exists('asset')) +{ + /** + * Generate an asset path for the application. + * + * @param string $path + * @param bool $secure + * @return string + */ + function asset($path, $secure = null) + { + return app('url')->asset($path, $secure); + } +} + +if ( ! function_exists('base_path')) +{ + /** + * Get the path to the base of the install. + * + * @return string + */ + function base_path($path = '') + { + return app()->make('path.base').($path ? '/'.$path : $path); + } +} + +if ( ! function_exists('camel_case')) +{ + /** + * Convert a value to camel case. + * + * @param string $value + * @return string + */ + function camel_case($value) + { + return Illuminate\Support\Str::camel($value); + } +} + +if ( ! function_exists('class_basename')) +{ + /** + * Get the class "basename" of the given object / class. + * + * @param string|object $class + * @return string + */ + function class_basename($class) + { + $class = is_object($class) ? get_class($class) : $class; + + return basename(str_replace('\\', '/', $class)); + } +} + +if ( ! function_exists('csrf_token')) +{ + /** + * Get the CSRF token value. + * + * @return string + */ + function csrf_token() + { + $session = app('session'); + + if (isset($session)) + { + return $session->getToken(); + } + else + { + throw new RuntimeException("Application session store not set."); + } + } +} + +if ( ! function_exists('dd')) +{ + /** + * Dump the passed variables and end the script. + * + * @param dynamic mixed + * @return void + */ + function dd() + { + array_map(function($x) { var_dump($x); }, func_get_args()); die; + } +} + +if ( ! function_exists('e')) +{ + /** + * Escape HTML entities in a string. + * + * @param string $value + * @return string + */ + function e($value) + { + return htmlentities($value, ENT_QUOTES, 'UTF-8', false); + } +} + +if ( ! function_exists('ends_with')) +{ + /** + * Determine if a given string ends with a given needle. + * + * @param string $haystack + * @param string|array $needle + * @return bool + */ + function ends_with($haystack, $needle) + { + return Illuminate\Support\Str::endsWith($haystack, $needle); + } +} + +if ( ! function_exists('head')) +{ + /** + * Get the first element of an array. Useful for method chaining. + * + * @param array $array + * @return mixed + */ + function head($array) + { + return reset($array); + } +} + +if ( ! function_exists('link_to')) +{ + /** + * Generate a HTML link. + * + * @param string $url + * @param string $title + * @param array $attributes + * @param bool $secure + * @return string + */ + function link_to($url, $title = null, $attributes = array(), $secure = null) + { + return app('html')->link($url, $title, $attributes, $secure); + } +} + +if ( ! function_exists('last')) +{ + /** + * Get the last element from an array. + * + * @param array $array + * @return mixed + */ + function last($array) + { + return end($array); + } +} + +if ( ! function_exists('link_to_asset')) +{ + /** + * Generate a HTML link to an asset. + * + * @param string $url + * @param string $title + * @param array $attributes + * @param bool $secure + * @return string + */ + function link_to_asset($url, $title = null, $attributes = array(), $secure = null) + { + return app('html')->linkAsset($url, $title, $attributes, $secure); + } +} + +if ( ! function_exists('link_to_route')) +{ + /** + * Generate a HTML link to a named route. + * + * @param string $name + * @param string $title + * @param array $parameters + * @param array $attributes + * @return string + */ + function link_to_route($name, $title = null, $parameters = array(), $attributes = array()) + { + return app('html')->linkRoute($name, $title, $parameters, $attributes); + } +} + +if ( ! function_exists('link_to_action')) +{ + /** + * Generate a HTML link to a controller action. + * + * @param string $action + * @param string $title + * @param array $parameters + * @param array $attributes + * @return string + */ + function link_to_action($action, $title = null, $parameters = array(), $attributes = array()) + { + return app('html')->linkAction($action, $title, $parameters, $attributes); + } +} + +if ( ! function_exists('object_get')) +{ + /** + * Get an item from an object using "dot" notation. + * + * @param object $object + * @param string $key + * @param mixed $default + * @return mixed + */ + function object_get($object, $key, $default = null) + { + if (is_null($key)) return $object; + + foreach (explode('.', $key) as $segment) + { + if ( ! is_object($object) or ! isset($object->{$segment})) + { + return value($default); + } + + $object = $object->{$segment}; + } + + return $object; + } +} + +if ( ! function_exists('preg_replace_sub')) +{ + /** + * Replace a given pattern with each value in the array in sequentially. + * + * @param string $pattern + * @param array $replacements + * @param string $subject + * @return string + */ + function preg_replace_sub($pattern, &$replacements, $subject) + { + return preg_replace_callback($pattern, function($match) use (&$replacements) + { + return array_shift($replacements); + + }, $subject); + } +} + +if ( ! function_exists('public_path')) +{ + /** + * Get the path to the public folder. + * + * @return string + */ + function public_path($path = '') + { + return app()->make('path.public').($path ? '/'.$path : $path); + } +} + +if ( ! function_exists('route')) +{ + /** + * Generate a URL to a named route. + * + * @param string $route + * @param string $parameters + * @param bool $absolute + * @return string + */ + function route($route, $parameters = array(), $absolute = true) + { + return app('url')->route($route, $parameters, $absolute); + } +} + +if ( ! function_exists('secure_asset')) +{ + /** + * Generate an asset path for the application. + * + * @param string $path + * @return string + */ + function secure_asset($path) + { + return asset($path, true); + } +} + +if ( ! function_exists('secure_url')) +{ + /** + * Generate a HTTPS url for the application. + * + * @param string $path + * @param mixed $parameters + * @return string + */ + function secure_url($path, $parameters = array()) + { + return url($path, $parameters, true); + } +} + +if ( ! function_exists('snake_case')) +{ + /** + * Convert a string to snake case. + * + * @param string $value + * @param string $delimiter + * @return string + */ + function snake_case($value, $delimiter = '_') + { + return Illuminate\Support\Str::snake($value, $delimiter); + } +} + +if ( ! function_exists('starts_with')) +{ + /** + * Determine if a string starts with a given needle. + * + * @param string $haystack + * @param string|array $needle + * @return bool + */ + function starts_with($haystack, $needle) + { + return Illuminate\Support\Str::startsWith($haystack, $needle); + } +} + +if ( ! function_exists('storage_path')) +{ + /** + * Get the path to the storage folder. + * + * @return string + */ + function storage_path($path = '') + { + return app('path.storage').($path ? '/'.$path : $path); + } +} + +if ( ! function_exists('str_contains')) +{ + /** + * Determine if a given string contains a given sub-string. + * + * @param string $haystack + * @param string|array $needle + * @return bool + */ + function str_contains($haystack, $needle) + { + return Illuminate\Support\Str::contains($haystack, $needle); + } +} + +if ( ! function_exists('str_finish')) +{ + /** + * Cap a string with a single instance of a given value. + * + * @param string $value + * @param string $cap + * @return string + */ + function str_finish($value, $cap) + { + return Illuminate\Support\Str::finish($value, $cap); + } +} + +if ( ! function_exists('str_is')) +{ + /** + * Determine if a given string matches a given pattern. + * + * @param string $pattern + * @param string $value + * @return bool + */ + function str_is($pattern, $value) + { + return Illuminate\Support\Str::is($pattern, $value); + } +} + +if ( ! function_exists('str_plural')) +{ + /** + * Get the plural form of an English word. + * + * @param string $value + * @param int $count + * @return string + */ + function str_plural($value, $count = 2) + { + return Illuminate\Support\Str::plural($value, $count); + } +} + +if ( ! function_exists('str_random')) +{ + /** + * Generate a "random" alpha-numeric string. + * + * Should not be considered sufficient for cryptography, etc. + * + * @param int $length + * @return string + */ + function str_random($length = 16) + { + return Illuminate\Support\Str::random($length); + } +} + +if ( ! function_exists('str_singular')) +{ + /** + * Get the singular form of an English word. + * + * @param string $value + * @return string + */ + function str_singular($value) + { + return Illuminate\Support\Str::singular($value); + } +} + +if ( ! function_exists('studly_case')) +{ + /** + * Convert a value to studly caps case. + * + * @param string $value + * @return string + */ + function studly_case($value) + { + return Illuminate\Support\Str::studly($value); + } +} + +if ( ! function_exists('trans')) +{ + /** + * Translate the given message. + * + * @param string $id + * @param array $parameters + * @param string $domain + * @param string $locale + * @return string + */ + function trans($id, $parameters = array(), $domain = 'messages', $locale = null) + { + return app('translator')->trans($id, $parameters, $domain, $locale); + } +} + +if ( ! function_exists('trans_choice')) +{ + /** + * Translates the given message based on a count. + * + * @param string $id + * @param int $number + * @param array $parameters + * @param string $domain + * @param string $locale + * @return string + */ + function trans_choice($id, $number, array $parameters = array(), $domain = 'messages', $locale = null) + { + return app('translator')->transChoice($id, $number, $parameters, $domain, $locale); + } +} + +if ( ! function_exists('url')) +{ + /** + * Generate a url for the application. + * + * @param string $path + * @param mixed $parameters + * @param bool $secure + * @return string + */ + function url($path = null, $parameters = array(), $secure = null) + { + return app('url')->to($path, $parameters, $secure); + } +} + +if ( ! function_exists('value')) +{ + /** + * Return the default value of the given value. + * + * @param mixed $value + * @return mixed + */ + function value($value) + { + return $value instanceof Closure ? $value() : $value; + } +} + +if ( ! function_exists('with')) +{ + /** + * Return the given object. Useful for chaining. + * + * @param mixed $object + * @return mixed + */ + function with($object) + { + return $object; + } +} diff --git a/vendor/laravel/framework/src/Illuminate/Translation/FileLoader.php b/vendor/laravel/framework/src/Illuminate/Translation/FileLoader.php new file mode 100755 index 0000000..7d495d2 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Translation/FileLoader.php @@ -0,0 +1,109 @@ +path = $path; + $this->files = $files; + } + + /** + * Load the messages for the given locale. + * + * @param string $locale + * @param string $group + * @param string $namespace + * @return array + */ + public function load($locale, $group, $namespace = null) + { + if (is_null($namespace) or $namespace == '*') + { + return $this->loadPath($this->path, $locale, $group); + } + else + { + return $this->loadNamespaced($locale, $group, $namespace); + } + } + + /** + * Load a namespaced translation group. + * + * @param string $locale + * @param string $group + * @param string $namespace + * @return array + */ + protected function loadNamespaced($locale, $group, $namespace) + { + if (isset($this->hints[$namespace])) + { + return $this->loadPath($this->hints[$namespace], $locale, $group); + } + + return array(); + } + + /** + * Load a locale from a given path. + * + * @param string $path + * @param string $locale + * @param string $group + * @return array + */ + protected function loadPath($path, $locale, $group) + { + if ($this->files->exists($full = "{$path}/{$locale}/{$group}.php")) + { + return $this->files->getRequire($full); + } + + return array(); + } + + /** + * Add a new namespace to the loader. + * + * @param string $namespace + * @param string $hint + * @return void + */ + public function addNamespace($namespace, $hint) + { + $this->hints[$namespace] = $hint; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Translation/LoaderInterface.php b/vendor/laravel/framework/src/Illuminate/Translation/LoaderInterface.php new file mode 100755 index 0000000..5a8a75a --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Translation/LoaderInterface.php @@ -0,0 +1,24 @@ +registerLoader(); + + $this->app['translator'] = $this->app->share(function($app) + { + $loader = $app['translation.loader']; + + // When registering the translator component, we'll need to set the default + // locale as well as the fallback locale. So, we'll grab the application + // configuration so we can easily get both of these values from there. + $locale = $app['config']['app.locale']; + + $trans = new Translator($loader, $locale); + + return $trans; + }); + } + + /** + * Register the translation line loader. + * + * @return void + */ + protected function registerLoader() + { + $this->app['translation.loader'] = $this->app->share(function($app) + { + return new FileLoader($app['files'], $app['path'].'/lang'); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('translator', 'translation.loader'); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Translation/Translator.php b/vendor/laravel/framework/src/Illuminate/Translation/Translator.php new file mode 100755 index 0000000..f5b7187 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Translation/Translator.php @@ -0,0 +1,311 @@ +loader = $loader; + $this->locale = $locale; + } + + /** + * Determine if a translation exists. + * + * @param string $key + * @param string $locale + * @return bool + */ + public function has($key, $locale = null) + { + return $this->get($key, array(), $locale) !== $key; + } + + /** + * Get the translation for the given key. + * + * @param string $key + * @param array $replace + * @param string $locale + * @return string + */ + public function get($key, array $replace = array(), $locale = null) + { + list($namespace, $group, $item) = $this->parseKey($key); + + // Here we will get the locale that should be used for the language line. If one + // was not passed, we will use the default locales which was given to us when + // the translator was instantiated. Then, we can load the lines and return. + $locale = $locale ?: $this->getLocale(); + + $this->load($namespace, $group, $locale); + + $line = $this->getLine( + $namespace, $group, $locale, $item, $replace + ); + + // If the line doesn't exist, we will return back the key which was requested as + // that will be quick to spot in the UI if language keys are wrong or missing + // from the application's language files. Otherwise we can return the line. + if (is_null($line)) return $key; + + return $line; + } + + /** + * Retrieve a language line out the loaded array. + * + * @param string $namespace + * @param string $group + * @param string $locale + * @param string $item + * @param array $replace + * @return string|null + */ + protected function getLine($namespace, $group, $locale, $item, array $replace) + { + $line = array_get($this->loaded[$namespace][$group][$locale], $item); + + if ($line) return $this->makeReplacements($line, $replace); + } + + /** + * Make the place-holder replacements on a line. + * + * @param string $line + * @param array $replace + * @return string + */ + protected function makeReplacements($line, array $replace) + { + $replace = $this->sortReplacements($replace); + + foreach ($replace as $key => $value) + { + $line = str_replace(':'.$key, $value, $line); + } + + return $line; + } + + /** + * Sort the replacements array. + * + * @param array $replace + * @return array + */ + protected function sortReplacements(array $replace) + { + return with(new Collection($replace))->sortBy(function($r) + { + return mb_strlen($r) * -1; + }); + } + + /** + * Get a translation according to an integer value. + * + * @param string $key + * @param int $number + * @param array $replace + * @param string $locale + * @return string + */ + public function choice($key, $number, array $replace = array(), $locale = null) + { + $line = $this->get($key, $replace, $locale = $locale ?: $this->locale); + + array_unshift($replace, $number); + + return $this->makeReplacements($this->getSelector()->choose($line, $number, $locale), $replace); + } + + /** + * Get the translation for a given key. + * + * @param string $id + * @param array $parameters + * @param string $domain + * @param string $locale + * @return string + */ + public function trans($id, array $parameters = array(), $domain = 'messages', $locale = null) + { + return $this->get($id, $parameters, $locale); + } + + /** + * Get a translation according to an integer value. + * + * @param string $id + * @param int $number + * @param array $parameters + * @param string $domain + * @param string $locale + * @return string + */ + public function transChoice($id, $number, array $parameters = array(), $domain = 'messages', $locale = null) + { + return $this->choice($id, $number, $parameters, $locale); + } + + /** + * Load the specified language group. + * + * @param string $namespace + * @param string $group + * @param string $locale + * @return void + */ + public function load($namespace, $group, $locale) + { + if ($this->isLoaded($namespace, $group, $locale)) return; + + // The loader is responsible for returning the array of language lines for the + // given namespace, group, and locale. We'll set the lines in this array of + // lines that have already been loaded so that we can easily access them. + $lines = $this->loader->load($locale, $group, $namespace); + + $this->loaded[$namespace][$group][$locale] = $lines; + } + + /** + * Determine if the given group has been loaded. + * + * @param string $namespace + * @param string $group + * @param string $locale + * @return bool + */ + protected function isLoaded($namespace, $group, $locale) + { + return isset($this->loaded[$namespace][$group][$locale]); + } + + /** + * Add a new namespace to the loader. + * + * @param string $namespace + * @param string $hint + * @return void + */ + public function addNamespace($namespace, $hint) + { + $this->loader->addNamespace($namespace, $hint); + } + + /** + * Parse a key into namespace, group, and item. + * + * @param string $key + * @return array + */ + public function parseKey($key) + { + $segments = parent::parseKey($key); + + if (is_null($segments[0])) $segments[0] = '*'; + + return $segments; + } + + /** + * Get the message selector instance. + * + * @return \Symfony\Component\Translation\MessageSelector + */ + public function getSelector() + { + if ( ! isset($this->selector)) + { + $this->selector = new MessageSelector; + } + + return $this->selector; + } + + /** + * Set the message selector instance. + * + * @param \Symfony\Component\Translation\MessageSelector $selector + * @return void + */ + public function setSelector(MessageSelector $selector) + { + $this->selector = $selector; + } + + /** + * Get the language line loader implementation. + * + * @return \Illuminate\Translation\LoaderInterface + */ + public function getLoader() + { + return $this->loader; + } + + /** + * Get the default locale being used. + * + * @return string + */ + public function locale() + { + return $this->getLocale(); + } + + /** + * Get the default locale being used. + * + * @return string + */ + public function getLocale() + { + return $this->locale; + } + + /** + * Set the default locale. + * + * @param string $locale + * @return void + */ + public function setLocale($locale) + { + $this->locale = $locale; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Translation/composer.json b/vendor/laravel/framework/src/Illuminate/Translation/composer.json new file mode 100755 index 0000000..5918d46 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Translation/composer.json @@ -0,0 +1,32 @@ +{ + "name": "illuminate/translation", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "illuminate/filesystem": "4.0.x", + "illuminate/support": "4.0.x", + "symfony/translation": "2.3.*" + }, + "require-dev": { + "mockery/mockery": "0.7.2", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Translation": "" + } + }, + "target-dir": "Illuminate/Translation", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/laravel/framework/src/Illuminate/Validation/DatabasePresenceVerifier.php b/vendor/laravel/framework/src/Illuminate/Validation/DatabasePresenceVerifier.php new file mode 100755 index 0000000..93f2df3 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Validation/DatabasePresenceVerifier.php @@ -0,0 +1,103 @@ +db = $db; + } + + /** + * Count the number of objects in a collection having the given value. + * + * @param string $collection + * @param string $column + * @param string $value + * @param int $excludeId + * @param string $idColumn + * @param array $extra + * @return int + */ + public function getCount($collection, $column, $value, $excludeId = null, $idColumn = null, array $extra = array()) + { + $query = $this->table($collection)->where($column, '=', $value); + + if ( ! is_null($excludeId) and $excludeId != 'NULL') + { + $query->where($idColumn ?: 'id', '<>', $excludeId); + } + + foreach ($extra as $key => $extraValue) + { + $query->where($key, $extraValue == 'NULL' ? null : $extraValue); + } + + return $query->count(); + } + + /** + * Count the number of objects in a collection with the given values. + * + * @param string $collection + * @param string $column + * @param array $values + * @param array $extra + * @return int + */ + public function getMultiCount($collection, $column, array $values, array $extra = array()) + { + $query = $this->table($collection)->whereIn($column, $values); + + foreach ($extra as $key => $extraValue) + { + $query->where($key, $extraValue == 'NULL' ? null : $extraValue); + } + + return $query->count(); + } + + /** + * Get a query builder for the given table. + * + * @param string $table + * @return \Illuminate\Database\Query\Builder + */ + protected function table($table) + { + return $this->db->connection($this->connection)->table($table); + } + + /** + * Set the connection to be used. + * + * @param string $connection + * @return void + */ + public function setConnection($connection) + { + $this->connection = $connection; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Validation/Factory.php b/vendor/laravel/framework/src/Illuminate/Validation/Factory.php new file mode 100755 index 0000000..f221447 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Validation/Factory.php @@ -0,0 +1,190 @@ +container = $container; + $this->translator = $translator; + } + + /** + * Create a new Validator instance. + * + * @param array $data + * @param array $rules + * @param array $messages + * @return \Illuminate\Validation\Validator + */ + public function make(array $data, array $rules, array $messages = array()) + { + // The presence verifier is responsible for checking the unique and exists data + // for the validator. It is behind an interface so that multiple versions of + // it may be written besides database. We'll inject it into the validator. + $validator = $this->resolve($data, $rules, $messages); + + if ( ! is_null($this->verifier)) + { + $validator->setPresenceVerifier($this->verifier); + } + + // Next we'll set the IoC container instance of the validator, which is used to + // resolves out class baesd validator extensions. If it's not set then these + // types of extensions will not be possible on these validation instances. + if ( ! is_null($this->container)) + { + $validator->setContainer($this->container); + } + + $validator->addExtensions($this->extensions); + + // Next, we will add the implicit extensions, which are similar to the required + // and accepted rule in that they are run even if the attributes is not in a + // array of data that is given to a validator instances via instantiation. + $implicit = $this->implicitExtensions; + + $validator->addImplicitExtensions($implicit); + + return $validator; + } + + /** + * Resolve a new Validator instance. + * + * @param array $data + * @param array $rules + * @param array $messages + * @return \Illuminate\Validation\Validator + */ + protected function resolve($data, $rules, $messages) + { + if (is_null($this->resolver)) + { + return new Validator($this->translator, $data, $rules, $messages); + } + else + { + return call_user_func($this->resolver, $this->translator, $data, $rules, $messages); + } + } + + /** + * Register a custom validator extension. + * + * @param string $rule + * @param Closure|string $extension + * @return void + */ + public function extend($rule, $extension) + { + $this->extensions[$rule] = $extension; + } + + /** + * Register a custom implicit validator extension. + * + * @param string $rule + * @param Closure $extension + * @return void + */ + public function extendImplicit($rule, Closure $extension) + { + $this->implicitExtensions[$rule] = $extension; + } + + /** + * Set the Validator instance resolver. + * + * @param Closure $resolver + * @return void + */ + public function resolver(Closure $resolver) + { + $this->resolver = $resolver; + } + + /** + * Get the Translator implementation. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public function getTranslator() + { + return $this->translator; + } + + /** + * Get the Presence Verifier implementation. + * + * @return \Illuminate\Validation\PresenceVerifierInterface + */ + public function getPresenceVerifier() + { + return $this->verifier; + } + + /** + * Set the Presence Verifier implementation. + * + * @param \Illuminate\Validation\PresenceVerifierInterface $presenceVerifier + * @return void + */ + public function setPresenceVerifier(PresenceVerifierInterface $presenceVerifier) + { + $this->verifier = $presenceVerifier; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Validation/PresenceVerifierInterface.php b/vendor/laravel/framework/src/Illuminate/Validation/PresenceVerifierInterface.php new file mode 100755 index 0000000..120a918 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Validation/PresenceVerifierInterface.php @@ -0,0 +1,29 @@ +registerPresenceVerifier(); + + $this->app['validator'] = $this->app->share(function($app) + { + $validator = new Factory($app['translator'], $app); + + // The validation presence verifier is responsible for determining the existence + // of values in a given data collection, typically a relational database or + // other persistent data stores. And it is used to check for uniqueness. + if (isset($app['validation.presence'])) + { + $validator->setPresenceVerifier($app['validation.presence']); + } + + return $validator; + }); + } + + /** + * Register the database presence verifier. + * + * @return void + */ + protected function registerPresenceVerifier() + { + $this->app['validation.presence'] = $this->app->share(function($app) + { + return new DatabasePresenceVerifier($app['db']); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('validator', 'validation.presence'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Validation/Validator.php b/vendor/laravel/framework/src/Illuminate/Validation/Validator.php new file mode 100755 index 0000000..28df76f --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Validation/Validator.php @@ -0,0 +1,1816 @@ +translator = $translator; + $this->customMessages = $messages; + $this->data = $this->parseData($data); + $this->rules = $this->explodeRules($rules); + } + + /** + * Parse the data and hydrate the files array. + * + * @param array $data + * @return array + */ + protected function parseData(array $data) + { + $this->files = array(); + + foreach ($data as $key => $value) + { + // If this value is an instance of the HttpFoundation File class we will + // remove it from the data array and add it to the files array, which + // is used to conveniently separate out the files from other datas. + if ($value instanceof File) + { + $this->files[$key] = $value; + + unset($data[$key]); + } + } + + return $data; + } + + /** + * Explode the rules into an array of rules. + * + * @param string|array $rules + * @return array + */ + protected function explodeRules($rules) + { + foreach ($rules as $key => &$rule) + { + $rule = (is_string($rule)) ? explode('|', $rule) : $rule; + } + + return $rules; + } + + /** + * Determine if the data passes the validation rules. + * + * @return bool + */ + public function passes() + { + $this->messages = new MessageBag; + + // We'll spin through each rule, validating the attributes attached to that + // rule. Any error messages will be added to the containers with each of + // the other error messages, returning true if we don't have messages. + foreach ($this->rules as $attribute => $rules) + { + foreach ($rules as $rule) + { + $this->validate($attribute, $rule); + } + } + + return count($this->messages->all()) === 0; + } + + /** + * Determine if the data fails the validation rules. + * + * @return bool + */ + public function fails() + { + return ! $this->passes(); + } + + /** + * Validate a given attribute against a rule. + * + * @param string $attribute + * @param string $rule + * @return void + */ + protected function validate($attribute, $rule) + { + list($rule, $parameters) = $this->parseRule($rule); + + // We will get the value for the given attribute from the array of data and then + // verify that the attribute is indeed validatable. Unless the rule implies + // that the attribute is required, rules are not run for missing values. + $value = $this->getValue($attribute); + + $validatable = $this->isValidatable($rule, $attribute, $value); + + $method = "validate{$rule}"; + + if ($validatable and ! $this->$method($attribute, $value, $parameters, $this)) + { + $this->addFailure($attribute, $rule, $parameters); + } + } + + /** + * Get the value of a given attribute. + * + * @param string $attribute + * @return mixed + */ + protected function getValue($attribute) + { + if ( ! is_null($value = array_get($this->data, $attribute))) + { + return $value; + } + elseif ( ! is_null($value = array_get($this->files, $attribute))) + { + return $value; + } + } + + /** + * Determine if the attribute is validatable. + * + * @param string $rule + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function isValidatable($rule, $attribute, $value) + { + return $this->validateRequired($attribute, $value) or $this->isImplicit($rule); + } + + /** + * Determine if a given rule implies the attribute is required. + * + * @param string $rule + * @return bool + */ + protected function isImplicit($rule) + { + return in_array($rule, $this->implicitRules); + } + + /** + * Add a failed rule and error message to the collection. + * + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return void + */ + protected function addFailure($attribute, $rule, $parameters) + { + $this->addError($attribute, $rule, $parameters); + + $this->failedRules[$attribute][$rule] = $parameters; + } + + /** + * Add an error message to the validator's collection of messages. + * + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return void + */ + protected function addError($attribute, $rule, $parameters) + { + $message = $this->getMessage($attribute, $rule); + + $message = $this->doReplacements($message, $attribute, $rule, $parameters); + + $this->messages->add($attribute, $message); + } + + /** + * Validate that a required attribute exists. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateRequired($attribute, $value) + { + if (is_null($value)) + { + return false; + } + elseif (is_string($value) and trim($value) === '') + { + return false; + } + elseif ($value instanceof File) + { + return (string) $value->getPath() != ''; + } + + return true; + } + + /** + * Determine if any of the given attributes fail the required test. + * + * @param array $attributes + * @return bool + */ + protected function anyFailingRequired(array $attributes) + { + foreach ($attributes as $key) + { + if ( ! $this->validateRequired($key, $this->getValue($key))) + { + return true; + } + } + + return false; + } + + /** + * Validate that an attribute exists when another attribute exists + * + * @param string $attribute + * @param mixed $value + * @param mixed $parameters + * @return bool + */ + protected function validateRequiredWith($attribute, $value, $parameters) + { + $other = $parameters[0]; + + if ($this->getPresentCount($parameters) != count($parameters)) + { + return true; + } + + if ($this->anyFailingRequired($parameters)) + { + return true; + } + + return $this->validateRequired($attribute, $value); + } + + /** + * Validate that an attribute exists when another attribute does not exists + * + * @param string $attribute + * @param mixed $value + * @param mixed $parameters + * @return bool + */ + protected function validateRequiredWithout($attribute, $value, $parameters) + { + if ($this->anyFailingRequired($parameters)) + { + return $this->validateRequired($attribute, $value); + } + + return true; + } + + /** + * Validate that an attribute exists when another attribute has a given value. + * + * @param string $attribute + * @param mixed $value + * @param mixed $parameters + * @return bool + */ + protected function validateRequiredIf($attribute, $value, $parameters) + { + if ($parameters[1] == array_get($this->data, $parameters[0])) + { + return $this->validateRequired($attribute, $value); + } + + return true; + } + + /** + * Get the number of attributes in a list that are present. + * + * @param array $attributes + * @return int + */ + protected function getPresentCount($attributes) + { + $count = 0; + + foreach ($attributes as $key) + { + if (isset($this->data[$key]) or isset($this->files[$key])) + { + $count++; + } + } + + return $count; + } + + /** + * Validate that an attribute has a matching confirmation. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateConfirmed($attribute, $value) + { + return $this->validateSame($attribute, $value, array($attribute.'_confirmation')); + } + + /** + * Validate that two attributes match. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return void + */ + protected function validateSame($attribute, $value, $parameters) + { + $other = $parameters[0]; + + return isset($this->data[$other]) and $value == $this->data[$other]; + } + + /** + * Validate that an attribute is different from another attribute. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateDifferent($attribute, $value, $parameters) + { + $other = $parameters[0]; + + return isset($this->data[$other]) and $value != $this->data[$other]; + } + + /** + * Validate that an attribute was "accepted". + * + * This validation rule implies the attribute is "required". + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateAccepted($attribute, $value) + { + $acceptable = array('yes', 'on', 1); + + return $this->validateRequired($attribute, $value) and in_array($value, $acceptable); + } + + /** + * Validate that an attribute is an array. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateArray($attribute, $value) + { + return is_array($value); + } + + /** + * Validate that an attribute is numeric. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateNumeric($attribute, $value) + { + return is_numeric($value); + } + + /** + * Validate that an attribute is an integer. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateInteger($attribute, $value) + { + return filter_var($value, FILTER_VALIDATE_INT) !== false; + } + + /** + * Validate that an attribute has a given number of digits. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateDigits($attribute, $value, $parameters) + { + return strlen((string) $value) == $parameters[0]; + } + + /** + * Validate that an attribute is between a given number of digits. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateDigitsBetween($attribute, $value, $parameters) + { + $length = strlen((string) $value); + + return $length >= $parameters[0] and $length <= $parameters[1]; + } + + /** + * Validate the size of an attribute. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateSize($attribute, $value, $parameters) + { + return $this->getSize($attribute, $value) == $parameters[0]; + } + + /** + * Validate the size of an attribute is between a set of values. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateBetween($attribute, $value, $parameters) + { + $size = $this->getSize($attribute, $value); + + return $size >= $parameters[0] and $size <= $parameters[1]; + } + + /** + * Validate the size of an attribute is greater than a minimum value. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateMin($attribute, $value, $parameters) + { + return $this->getSize($attribute, $value) >= $parameters[0]; + } + + /** + * Validate the size of an attribute is less than a maximum value. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateMax($attribute, $value, $parameters) + { + return $this->getSize($attribute, $value) <= $parameters[0]; + } + + /* + * Get the size of an attribute. + * + * @param string $attribute + * @param mixed $value + * @return mixed + */ + protected function getSize($attribute, $value) + { + $hasNumeric = $this->hasRule($attribute, $this->numericRules); + + // This method will determine if the attribute is a number, string, or file and + // return the proper size accordingly. If it is a number, then number itself + // is the size. If it is a file, we take kilobytes, and for a string the + // entire length of the string will be considered the attribute size. + if (is_numeric($value) and $hasNumeric) + { + return $this->data[$attribute]; + } + elseif (is_array($value)) + { + return count($value); + } + elseif ($value instanceof File) + { + return $value->getSize() / 1024; + } + else + { + return $this->getStringSize($value); + } + } + + /** + * Get the size of a string. + * + * @param string $value + * @return int + */ + protected function getStringSize($value) + { + if (function_exists('mb_strlen')) return mb_strlen($value); + + return strlen($value); + } + + /** + * Validate an attribute is contained within a list of values. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateIn($attribute, $value, $parameters) + { + return in_array($value, $parameters); + } + + /** + * Validate an attribute is not contained within a list of values. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateNotIn($attribute, $value, $parameters) + { + return ! in_array($value, $parameters); + } + + /** + * Validate the uniqueness of an attribute value on a given database table. + * + * If a database column is not specified, the attribute will be used. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateUnique($attribute, $value, $parameters) + { + $table = $parameters[0]; + + // The second parameter position holds the name of the column that needs to + // be verified as unique. If this parameter isn't specified we will just + // assume that this column to be verified shares the attribute's name. + $column = isset($parameters[1]) ? $parameters[1] : $attribute; + + list($idColumn, $id) = array(null, null); + + if (isset($parameters[2])) + { + list($idColumn, $id) = $this->getUniqueIds($parameters); + + if (strtolower($id) == 'null') $id = null; + } + + // The presence verifier is responsible for counting rows within this store + // mechanism which might be a relational database or any other permanent + // data store like Redis, etc. We will use it to determine uniqueness. + $verifier = $this->getPresenceVerifier(); + + $extra = $this->getUniqueExtra($parameters); + + return $verifier->getCount( + + $table, $column, $value, $id, $idColumn, $extra + + ) == 0; + } + + /** + * Get the excluded ID column and value for the unique rule. + * + * @param array $parameters + * @return array + */ + protected function getUniqueIds($parameters) + { + $idColumn = isset($parameters[3]) ? $parameters[3] : 'id'; + + return array($idColumn, $parameters[2]); + } + + /** + * Get the extra conditions for a unique rule. + * + * @param array $parameters + * @return array + */ + protected function getUniqueExtra($parameters) + { + if (isset($parameters[4])) + { + return $this->getExtraConditions(array_slice($parameters, 4)); + } + else + { + return array(); + } + } + + /** + * Validate the existence of an attribute value in a database table. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateExists($attribute, $value, $parameters) + { + $table = $parameters[0]; + + // The second parameter position holds the name of the column that should be + // verified as existing. If this parameter is not specified we will guess + // that the columns being "verified" shares the given attribute's name. + $column = isset($parameters[1]) ? $parameters[1] : $attribute; + + $expected = (is_array($value)) ? count($value) : 1; + + return $this->getExistCount($table, $column, $value, $parameters) >= $expected; + } + + /** + * Get the number of records that exist in storage. + * + * @param string $table + * @param string $column + * @param mixed $value + * @param array $parameters + * @return int + */ + protected function getExistCount($table, $column, $value, $parameters) + { + $verifier = $this->getPresenceVerifier(); + + $extra = $this->getExtraExistConditions($parameters); + + if (is_array($value)) + { + return $verifier->getMultiCount($table, $column, $value, $extra); + } + else + { + return $verifier->getCount($table, $column, $value, null, null, $extra); + } + } + + /** + * Get the extra exist conditions. + * + * @param array $parameters + * @return array + */ + protected function getExtraExistConditions(array $parameters) + { + return $this->getExtraConditions(array_values(array_slice($parameters, 2))); + } + + /** + * Get the extra conditions for a unique / exists rule. + * + * @param array $segments + * @return array + */ + protected function getExtraConditions(array $segments) + { + $extra = array(); + + for ($i = 0; $i < count($segments); $i = $i + 2) + { + $extra[$segments[$i]] = $segments[$i + 1]; + } + + return $extra; + } + + /** + * Validate that an attribute is a valid IP. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateIp($attribute, $value) + { + return filter_var($value, FILTER_VALIDATE_IP) !== false; + } + + /** + * Validate that an attribute is a valid e-mail address. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateEmail($attribute, $value) + { + return filter_var($value, FILTER_VALIDATE_EMAIL) !== false; + } + + /** + * Validate that an attribute is a valid URL. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateUrl($attribute, $value) + { + return filter_var($value, FILTER_VALIDATE_URL) !== false; + } + + /** + * Validate that an attribute is an active URL. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateActiveUrl($attribute, $value) + { + $url = str_replace(array('http://', 'https://', 'ftp://'), '', strtolower($value)); + + return checkdnsrr($url); + } + + /** + * Validate the MIME type of a file is an image MIME type. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateImage($attribute, $value) + { + return $this->validateMimes($attribute, $value, array('jpeg', 'png', 'gif', 'bmp')); + } + + /** + * Validate the MIME type of a file upload attribute is in a set of MIME types. + * + * @param string $attribute + * @param array $value + * @param array $parameters + * @return bool + */ + protected function validateMimes($attribute, $value, $parameters) + { + if ( ! $value instanceof File or $value->getPath() == '') + { + return true; + } + + // The Symfony File class should do a decent job of guessing the extension + // based on the true MIME type so we'll just loop through the array of + // extensions and compare it to the guessed extension of the files. + return in_array($value->guessExtension(), $parameters); + } + + /** + * Validate that an attribute contains only alphabetic characters. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateAlpha($attribute, $value) + { + return preg_match('/^([a-z])+$/i', $value); + } + + /** + * Validate that an attribute contains only alpha-numeric characters. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateAlphaNum($attribute, $value) + { + return preg_match('/^([a-z0-9])+$/i', $value); + } + + /** + * Validate that an attribute contains only alpha-numeric characters, dashes, and underscores. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateAlphaDash($attribute, $value) + { + return preg_match('/^([a-z0-9_-])+$/i', $value); + } + + /** + * Validate that an attribute passes a regular expression check. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateRegex($attribute, $value, $parameters) + { + return preg_match($parameters[0], $value); + } + + /** + * Validate that an attribute is a valid date. + * + * @param string $attribute + * @param mixed $value + * @return bool + */ + protected function validateDate($attribute, $value) + { + if ($value instanceof DateTime) return true; + + if (strtotime($value) === false) return false; + + $date = date_parse($value); + + return checkdate($date['month'], $date['day'], $date['year']); + } + + /** + * Validate that an attribute matches a date format. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateDateFormat($attribute, $value, $parameters) + { + $parsed = date_parse_from_format($parameters[0], $value); + + return $parsed['error_count'] === 0; + } + + /** + * Validate the date is before a given date. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateBefore($attribute, $value, $parameters) + { + return strtotime($value) < strtotime($parameters[0]); + } + + /** + * Validate the date is after a given date. + * + * @param string $attribute + * @param mixed $value + * @param array $parameters + * @return bool + */ + protected function validateAfter($attribute, $value, $parameters) + { + return strtotime($value) > strtotime($parameters[0]); + } + + /** + * Get the validation message for an attribute and rule. + * + * @param string $attribute + * @param string $rule + * @return string + */ + protected function getMessage($attribute, $rule) + { + $lowerRule = strtolower(snake_case($rule)); + + $inlineMessage = $this->getInlineMessage($attribute, $lowerRule); + + // First we will retrieve the custom message for the validation rule if one + // exists. If a custom validation message is being used we'll return the + // custom message, otherwise we'll keep searching for a valid message. + if ( ! is_null($inlineMessage)) + { + return $inlineMessage; + } + + $customKey = "validation.custom.{$attribute}.{$lowerRule}"; + + $customMessage = $this->translator->trans($customKey); + + // First we check for a custom defined validation message for the attribute + // and rule. This allows the developer to specify specific messages for + // only some attributes and rules that need to get specially formed. + if ($customMessage !== $customKey) + { + return $customMessage; + } + + // If the rule being validated is a "size" rule, we will need to gather the + // specific error message for the type of attribute being validated such + // as a number, file or string which all have different message types. + elseif (in_array($rule, $this->sizeRules)) + { + return $this->getSizeMessage($attribute, $rule); + } + + // Finally, if on developer specified messages have been set, and no other + // special messages apply for this rule, we will just pull the default + // messages out of the translator service for this validation rule. + else + { + $key = "validation.{$lowerRule}"; + + return $this->translator->trans($key); + } + } + + /** + * Get the inline message for a rule if it exists. + * + * @param string $attribute + * @param string $lowerRule + * @return string + */ + protected function getInlineMessage($attribute, $lowerRule) + { + $keys = array("{$attribute}.{$lowerRule}", $lowerRule); + + // First we will check for a custom message for an attribute specific rule + // message for the fields, then we will check for a general custom line + // that is not attribute specific. If we find either we'll return it. + foreach ($keys as $key) + { + if (isset($this->customMessages[$key])) + { + return $this->customMessages[$key]; + } + } + } + + /** + * Get the proper error message for an attribute and size rule. + * + * @param string $attribute + * @param string $rule + * @return string + */ + protected function getSizeMessage($attribute, $rule) + { + $lowerRule = strtolower(snake_case($rule)); + + // There are three different types of size validations. The attribute may be + // either a number, file, or string so we will check a few things to know + // which type of value it is and return the correct line for that type. + $type = $this->getAttributeType($attribute); + + $key = "validation.{$lowerRule}.{$type}"; + + return $this->translator->trans($key); + } + + /** + * Get the data type of the given attribute. + * + * @param string $attribute + * @return string + */ + protected function getAttributeType($attribute) + { + // We assume that the attributes present in the file array are files so that + // means that if the attribute does not have a numeric rule and the files + // list doesn't have it we'll just consider it a string by elimination. + if ($this->hasRule($attribute, $this->numericRules)) + { + return 'numeric'; + } + elseif ($this->hasRule($attribute, array('Array'))) + { + return 'array'; + } + elseif (array_key_exists($attribute, $this->files)) + { + return 'file'; + } + + return 'string'; + } + + /** + * Replace all error message place-holders with actual values. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function doReplacements($message, $attribute, $rule, $parameters) + { + $message = str_replace(':attribute', $this->getAttribute($attribute), $message); + + if (method_exists($this, $replacer = "replace{$rule}")) + { + $message = $this->$replacer($message, $attribute, $rule, $parameters); + } + + return $message; + } + + /** + * Transform an array of attributes to their displayable form. + * + * @param array $values + * @return array + */ + protected function getAttributeList(array $values) + { + $attributes = array(); + + // For each attribute in the list we will simply get its displayable form as + // this is convenient when replacing lists of parameters like some of the + // replacement functions do when formatting out the validation message. + foreach ($values as $key => $value) + { + $attributes[$key] = $this->getAttribute($value); + } + + return $attributes; + } + + /** + * Get the displayable name of the attribute. + * + * @param string $attribute + * @return string + */ + protected function getAttribute($attribute) + { + // The developer may dynamically specify the array of custom attributes + // on this Validator instance. If the attribute exists in this array + // it takes precedence over all other ways we can pull attributes. + if (isset($this->customAttributes[$attribute])) + { + return $this->customAttributes[$attribute]; + } + + $key = "validation.attributes.{$attribute}"; + + // We allow for the developer to specify language lines for each of the + // attributes allowing for more displayable counterparts of each of + // the attributes. This provides the ability for simple formats. + if (($line = $this->translator->trans($key)) !== $key) + { + return $line; + } + + // If no language line has been specified for the attribute all of the + // underscores are removed from the attribute name and that will be + // used as default versions of the attribute's displayable names. + else + { + return str_replace('_', ' ', $attribute); + } + } + + /** + * Replace all place-holders for the between rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceBetween($message, $attribute, $rule, $parameters) + { + return str_replace(array(':min', ':max'), $parameters, $message); + } + + /** + * Replace all place-holders for the digits rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceDigits($message, $attribute, $rule, $parameters) + { + return str_replace(':digits', $parameters[0], $message); + } + + /** + * Replace all place-holders for the digits (between) rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceDigitsBetween($message, $attribute, $rule, $parameters) + { + return str_replace(array(':min', ':max'), $parameters, $message); + } + + /** + * Replace all place-holders for the size rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceSize($message, $attribute, $rule, $parameters) + { + return str_replace(':size', $parameters[0], $message); + } + + /** + * Replace all place-holders for the min rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceMin($message, $attribute, $rule, $parameters) + { + return str_replace(':min', $parameters[0], $message); + } + + /** + * Replace all place-holders for the max rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceMax($message, $attribute, $rule, $parameters) + { + return str_replace(':max', $parameters[0], $message); + } + + /** + * Replace all place-holders for the in rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceIn($message, $attribute, $rule, $parameters) + { + return str_replace(':values', implode(', ', $parameters), $message); + } + + /** + * Replace all place-holders for the not_in rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceNotIn($message, $attribute, $rule, $parameters) + { + return str_replace(':values', implode(', ', $parameters), $message); + } + + /** + * Replace all place-holders for the mimes rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceMimes($message, $attribute, $rule, $parameters) + { + return str_replace(':values', implode(', ', $parameters), $message); + } + + /** + * Replace all place-holders for the required_with rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceRequiredWith($message, $attribute, $rule, $parameters) + { + $parameters = $this->getAttributeList($parameters); + + return str_replace(':values', implode(' / ', $parameters), $message); + } + + /** + * Replace all place-holders for the required_without rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceRequiredWithout($message, $attribute, $rule, $parameters) + { + $parameters = $this->getAttributeList($parameters); + + return str_replace(':values', implode(' / ', $parameters), $message); + } + + /** + * Replace all place-holders for the required_if rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceRequiredIf($message, $attribute, $rule, $parameters) + { + $parameters[0] = $this->getAttribute($parameters[0]); + + return str_replace(array(':other', ':value'), $parameters, $message); + } + + /** + * Replace all place-holders for the same rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceSame($message, $attribute, $rule, $parameters) + { + return str_replace(':other', $this->getAttribute($parameters[0]), $message); + } + + /** + * Replace all place-holders for the different rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceDifferent($message, $attribute, $rule, $parameters) + { + return str_replace(':other', $this->getAttribute($parameters[0]), $message); + } + + /** + * Replace all place-holders for the date_format rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceDateFormat($message, $attribute, $rule, $parameters) + { + return str_replace(':format', $parameters[0], $message); + } + + /** + * Replace all place-holders for the before rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceBefore($message, $attribute, $rule, $parameters) + { + return str_replace(':date', $parameters[0], $message); + } + + /** + * Replace all place-holders for the after rule. + * + * @param string $message + * @param string $attribute + * @param string $rule + * @param array $parameters + * @return string + */ + protected function replaceAfter($message, $attribute, $rule, $parameters) + { + return str_replace(':date', $parameters[0], $message); + } + + /** + * Determine if the given attribute has a rule in the given set. + * + * @param string $attribute + * @param array $rules + * @return bool + */ + protected function hasRule($attribute, $rules) + { + // To determine if the attribute has a rule in the ruleset, we will spin + // through each of the rules assigned to the attribute and parse them + // all, then check to see if the parsed rules exists in the arrays. + foreach ($this->rules[$attribute] as $rule) + { + list($rule, $parameters) = $this->parseRule($rule); + + if (in_array($rule, $rules)) return true; + } + + return false; + } + + /** + * Extract the rule name and parameters from a rule. + * + * @param string $rule + * @return array + */ + protected function parseRule($rule) + { + $parameters = array(); + + // The format for specifying validation rules and parameters follows an + // easy {rule}:{parameters} formatting convention. For instance the + // rule "Max:3" states that the value may only be three letters. + if (strpos($rule, ':') !== false) + { + list($rule, $parameter) = explode(':', $rule, 2); + + $parameters = $this->parseParameters($rule, $parameter); + } + + return array(studly_case($rule), $parameters); + } + + /** + * Parse a parameter list. + * + * @param string $rule + * @param string $parameter + * @return array + */ + protected function parseParameters($rule, $parameter) + { + if (strtolower($rule) == 'regex') return array($parameter); + + return str_getcsv($parameter); + } + + /** + * Get the array of custom validator extensions. + * + * @return array + */ + public function getExtensions() + { + return $this->extensions; + } + + /** + * Register an array of custom validator extensions. + * + * @param array $extensions + * @return void + */ + public function addExtensions(array $extensions) + { + $this->extensions = array_merge($this->extensions, $extensions); + } + + /** + * Register an array of custom implicit validator extensions. + * + * @param array $extensions + * @return void + */ + public function addImplicitExtensions(array $extensions) + { + $this->addExtensions($extensions); + + foreach ($extensions as $rule => $extension) + { + $this->implicitRules[] = studly_case($rule); + } + } + + /** + * Register a custom validator extension. + * + * @param string $rule + * @param Closure|string $extension + * @return void + */ + public function addExtension($rule, $extension) + { + $this->extensions[$rule] = $extension; + } + + /** + * Register a custom implicit validator extension. + * + * @param string $rule + * @param Closure $extension + * @return void + */ + public function addImplicitExtension($rule, Closure $extension) + { + $this->addExtension($rule, $extension); + + $this->implicitRules[] = studly_case($rule); + } + + /** + * Get the data under validation. + * + * @return array + */ + public function getData() + { + return $this->data; + } + + /** + * Set the data under validation. + * + * @param array $data + * @return void + */ + public function setData(array $data) + { + $this->data = $this->parseData($data); + } + + /** + * Get the validation rules. + * + * @return array + */ + public function getRules() + { + return $this->rules; + } + + /** + * Set the custom attributes on the validator. + * + * @param array $attributes + * @return \Illuminate\Validation\Validator + */ + public function setAttributeNames(array $attributes) + { + $this->customAttributes = $attributes; + + return $this; + } + + /** + * Get the files under validation. + * + * @return array + */ + public function getFiles() + { + return $this->files; + } + + /** + * Set the files under validation. + * + * @param array $files + * @return \Illuminate\Validation\Validator + */ + public function setFiles(array $files) + { + $this->files = $files; + + return $this; + } + + /** + * Get the Presence Verifier implementation. + * + * @return \Illuminate\Validation\PresenceVerifierInterface + */ + public function getPresenceVerifier() + { + if ( ! isset($this->presenceVerifier)) + { + throw new \RuntimeException("Presence verifier has not been set."); + } + + return $this->presenceVerifier; + } + + /** + * Set the Presence Verifier implementation. + * + * @param \Illuminate\Validation\PresenceVerifierInterface $presenceVerifier + * @return void + */ + public function setPresenceVerifier(PresenceVerifierInterface $presenceVerifier) + { + $this->presenceVerifier = $presenceVerifier; + } + + /** + * Get the Translator implementation. + * + * @return \Symfony\Component\Translation\TranslatorInterface + */ + public function getTranslator() + { + return $this->translator; + } + + /** + * Set the Translator implementation. + * + * @param \Symfony\Component\Translation\TranslatorInterface $translator + * @return void + */ + public function setTranslator(TranslatorInterface $translator) + { + $this->translator = $translator; + } + + /** + * Get the custom messages for the validator + * + * @return array + */ + public function getCustomMessages() + { + return $this->customMessages; + } + + /** + * Set the custom messages for the validator + * + * @param array $messages + * @return void + */ + public function setCustomMessages(array $messages) + { + $this->customMessages = array_merge($this->customMessages, $messages); + } + + /** + * Get the failed validation rules. + * + * @return array + */ + public function failed() + { + return $this->failedRules; + } + + /** + * Get the message container for the validator. + * + * @return \Illuminate\Support\MessageBag + */ + public function messages() + { + return $this->messages; + } + + /** + * An alternative more semantic shortcut to the message container. + * + * @return \Illuminate\Support\MessageBag + */ + public function errors() + { + return $this->messages; + } + + /** + * Get the messages for the instance. + * + * @return \Illuminate\Support\MessageBag + */ + public function getMessageBag() + { + return $this->messages(); + } + + /** + * Set the IoC container instance. + * + * @param \Illuminate\Container\Container $container + * @return void + */ + public function setContainer(Container $container) + { + $this->container = $container; + } + + /** + * Call a custom validator extension. + * + * @param string $rule + * @param array $parameters + * @return bool + */ + protected function callExtension($rule, $parameters) + { + $callback = $this->extensions[$rule]; + + if ($callback instanceof Closure) + { + return call_user_func_array($callback, $parameters); + } + elseif (is_string($callback)) + { + return $this->callClassBasedExtension($callback, $parameters); + } + } + + /** + * Call a class baesd validator extension. + * + * @param string $callback + * @param array $parameters + * @return bool + */ + protected function callClassBasedExtension($callback, $parameters) + { + list($class, $method) = explode('@', $callback); + + return call_user_func_array(array($this->container->make($class), $method), $parameters); + } + + /** + * Handle dynamic calls to class methods. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + $rule = snake_case(substr($method, 8)); + + if (isset($this->extensions[$rule])) + { + return $this->callExtension($rule, $parameters); + } + + throw new \BadMethodCallException("Method [$method] does not exist."); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Validation/composer.json b/vendor/laravel/framework/src/Illuminate/Validation/composer.json new file mode 100755 index 0000000..14c4c14 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Validation/composer.json @@ -0,0 +1,34 @@ +{ + "name": "illuminate/validation", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "illuminate/container": "4.0.x", + "illuminate/support": "4.0.x", + "symfony/http-foundation": "2.3.*", + "symfony/translation": "2.3.*" + }, + "require-dev": { + "illuminate/database": "4.0.x", + "mockery/mockery": "0.7.2", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Validation": "" + } + }, + "target-dir": "Illuminate/Validation", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/View/Compilers/BladeCompiler.php b/vendor/laravel/framework/src/Illuminate/View/Compilers/BladeCompiler.php new file mode 100755 index 0000000..82c8c6c --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/View/Compilers/BladeCompiler.php @@ -0,0 +1,450 @@ +compileString($this->files->get($path)); + + if ( ! is_null($this->cachePath)) + { + $this->files->put($this->getCompiledPath($path), $contents); + } + } + + /** + * Compile the given Blade template contents. + * + * @param string $value + * @return string + */ + public function compileString($value) + { + foreach ($this->compilers as $compiler) + { + $value = $this->{"compile{$compiler}"}($value); + } + + return $value; + } + + /** + * Register a custom Blade compiler. + * + * @param Closure $compiler + * @return void + */ + public function extend(Closure $compiler) + { + $this->extensions[] = $compiler; + } + + /** + * Execute the user defined extensions. + * + * @param string $value + * @return string + */ + protected function compileExtensions($value) + { + foreach ($this->extensions as $compiler) + { + $value = call_user_func($compiler, $value, $this); + } + + return $value; + } + + /** + * Compile Blade template extensions into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileExtends($value) + { + // By convention, Blade views using template inheritance must begin with the + // @extends expression, otherwise they will not be compiled with template + // inheritance. So, if they do not start with that we will just return. + if (strpos($value, '@extends') !== 0) + { + return $value; + } + + $lines = preg_split("/(\r?\n)/", $value); + + // Next, we just want to split the values by lines, and create an expression + // to include the parent layout at the end of the templates. Which allows + // the sections to get registered before the parent view gets rendered. + $lines = $this->compileLayoutExtends($lines); + + return implode("\r\n", array_slice($lines, 1)); + } + + /** + * Compile the proper template inheritance for the lines. + * + * @param array $lines + * @return array + */ + protected function compileLayoutExtends($lines) + { + $pattern = $this->createMatcher('extends'); + + $lines[] = preg_replace($pattern, '$1@include$2', $lines[0]); + + return $lines; + } + + /** + * Compile Blade comments into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileComments($value) + { + $pattern = sprintf('/%s--((.|\s)*?)--%s/', $this->contentTags[0], $this->contentTags[1]); + + return preg_replace($pattern, '', $value); + } + + /** + * Compile Blade echos into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileEchos($value) + { + $difference = strlen($this->contentTags[0]) - strlen($this->escapedTags[0]); + + if ($difference > 0) + { + return $this->compileEscapedEchos($this->compileRegularEchos($value)); + } + + return $this->compileRegularEchos($this->compileEscapedEchos($value)); + } + + /** + * Compile the "regular" echo statements. + * + * @param string $value + * @return string + */ + protected function compileRegularEchos($value) + { + $pattern = sprintf('/%s\s*(.+?)\s*%s/s', $this->contentTags[0], $this->contentTags[1]); + + return preg_replace($pattern, '', $value); + } + + /** + * Compile the escaped echo statements. + * + * @param string $value + * @return string + */ + protected function compileEscapedEchos($value) + { + $pattern = sprintf('/%s\s*(.+?)\s*%s/s', $this->escapedTags[0], $this->escapedTags[1]); + + return preg_replace($pattern, '', $value); + } + + /** + * Compile Blade structure openings into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileOpenings($value) + { + $pattern = '/(?(R)\((?:[^\(\)]|(?R))*\)|(?', $value); + } + + /** + * Compile Blade structure closings into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileClosings($value) + { + $pattern = '/(\s*)@(endif|endforeach|endfor|endwhile)(\s*)/'; + + return preg_replace($pattern, '$1$3', $value); + } + + /** + * Compile Blade else statements into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileElse($value) + { + $pattern = $this->createPlainMatcher('else'); + + return preg_replace($pattern, '$1$2', $value); + } + + /** + * Compile Blade unless statements into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileUnless($value) + { + $pattern = $this->createMatcher('unless'); + + return preg_replace($pattern, '$1', $value); + } + + /** + * Compile Blade end unless statements into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileEndUnless($value) + { + $pattern = $this->createPlainMatcher('endunless'); + + return preg_replace($pattern, '$1$2', $value); + } + + /** + * Compile Blade include statements into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileIncludes($value) + { + $pattern = $this->createOpenMatcher('include'); + + $replace = '$1make$2, array_except(get_defined_vars(), array(\'__data\', \'__path\')))->render(); ?>'; + + return preg_replace($pattern, $replace, $value); + } + + /** + * Compile Blade each statements into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileEach($value) + { + $pattern = $this->createMatcher('each'); + + return preg_replace($pattern, '$1renderEach$2; ?>', $value); + } + + /** + * Compile Blade yield statements into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileYields($value) + { + $pattern = $this->createMatcher('yield'); + + return preg_replace($pattern, '$1yieldContent$2; ?>', $value); + } + + /** + * Compile Blade show statements into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileShows($value) + { + $pattern = $this->createPlainMatcher('show'); + + return preg_replace($pattern, '$1yieldSection(); ?>$2', $value); + } + + /** + * Compile Blade language and language choice statements into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileLanguage($value) + { + $pattern = $this->createMatcher('lang'); + + $value = preg_replace($pattern, '$1', $value); + + $pattern = $this->createMatcher('choice'); + + return preg_replace($pattern, '$1', $value); + } + + /** + * Compile Blade section start statements into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileSectionStart($value) + { + $pattern = $this->createMatcher('section'); + + return preg_replace($pattern, '$1startSection$2; ?>', $value); + } + + /** + * Compile Blade section stop statements into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileSectionStop($value) + { + $pattern = $this->createPlainMatcher('stop'); + + $value = preg_replace($pattern, '$1stopSection(); ?>$2', $value); + + $pattern = $this->createPlainMatcher('endsection'); + + return preg_replace($pattern, '$1stopSection(); ?>$2', $value); + } + + /** + * Compile Blade section stop statements into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileSectionOverwrite($value) + { + $pattern = $this->createPlainMatcher('overwrite'); + + return preg_replace($pattern, '$1stopSection(true); ?>$2', $value); + } + + /** + * Get the regular expression for a generic Blade function. + * + * @param string $function + * @return string + */ + public function createMatcher($function) + { + return '/(?{$property} = array(preg_quote($openTag), preg_quote($closeTag)); + } + + /** + * Sets the escaped content tags used for the compiler. + * + * @param string $openTag + * @param string $closeTag + * @return void + */ + public function setEscapedContentTags($openTag, $closeTag) + { + $this->setContentTags($openTag, $closeTag, true); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/View/Compilers/Compiler.php b/vendor/laravel/framework/src/Illuminate/View/Compilers/Compiler.php new file mode 100755 index 0000000..3a55035 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/View/Compilers/Compiler.php @@ -0,0 +1,68 @@ +files = $files; + $this->cachePath = $cachePath; + } + + /** + * Get the path to the compiled version of a view. + * + * @param string $path + * @return string + */ + public function getCompiledPath($path) + { + return $this->cachePath.'/'.md5($path); + } + + /** + * Determine if the view at the given path is expired. + * + * @param string $path + * @return bool + */ + public function isExpired($path) + { + $compiled = $this->getCompiledPath($path); + + // If the compiled file doesn't exist we will indicate that the view is expired + // so that it can be re-compiled. Else, we will verify the last modification + // of the views is less than the modification times of the compiled views. + if ( ! $this->cachePath or ! $this->files->exists($compiled)) + { + return true; + } + + $lastModified = $this->files->lastModified($path); + + return $lastModified >= $this->files->lastModified($compiled); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/View/Compilers/CompilerInterface.php b/vendor/laravel/framework/src/Illuminate/View/Compilers/CompilerInterface.php new file mode 100755 index 0000000..800856a --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/View/Compilers/CompilerInterface.php @@ -0,0 +1,29 @@ +compiler = $compiler; + } + + /** + * Get the evaluated contents of the view. + * + * @param \Illuminate\View\Environment $environment + * @param string $view + * @param array $data + * @return string + */ + public function get($path, array $data = array()) + { + // If this given view has expired, which means it has simply been edited since + // it was last compiled, we will re-compile the views so we can evaluate a + // fresh copy of the view. We'll pass the compiler the path of the view. + if ($this->compiler->isExpired($path)) + { + $this->compiler->compile($path); + } + + $compiled = $this->compiler->getCompiledPath($path); + + return $this->evaluatePath($compiled, $data); + } + + /** + * Get the compiler implementation. + * + * @return \Illuminate\View\Compilers\CompilerInterface + */ + public function getCompiler() + { + return $this->compiler; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/View/Engines/Engine.php b/vendor/laravel/framework/src/Illuminate/View/Engines/Engine.php new file mode 100755 index 0000000..f7e5ba1 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/View/Engines/Engine.php @@ -0,0 +1,32 @@ +lastRendered; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/View/Engines/EngineInterface.php b/vendor/laravel/framework/src/Illuminate/View/Engines/EngineInterface.php new file mode 100755 index 0000000..4137d25 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/View/Engines/EngineInterface.php @@ -0,0 +1,14 @@ +resolvers[$engine] = $resolver; + } + + /** + * Resolver an engine instance by name. + * + * @param string $engine + * @return \Illuminate\View\Engines\EngineInterface + */ + public function resolve($engine) + { + if ( ! isset($this->resolved[$engine])) + { + $this->resolved[$engine] = call_user_func($this->resolvers[$engine]); + } + + return $this->resolved[$engine]; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/View/Engines/PhpEngine.php b/vendor/laravel/framework/src/Illuminate/View/Engines/PhpEngine.php new file mode 100755 index 0000000..832bea2 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/View/Engines/PhpEngine.php @@ -0,0 +1,59 @@ +evaluatePath($path, $data); + } + + /** + * Get the evaluated contents of the view at the given path. + * + * @param string $__path + * @param array $__data + * @return string + */ + protected function evaluatePath($__path, $__data) + { + ob_start(); + + extract($__data); + + // We'll evaluate the contents of the view inside a try/catch block so we can + // flush out any stray output that might get out before an error occurs or + // an exception is thrown. This prevents any partial views from leaking. + try + { + include $__path; + } + catch (\Exception $e) + { + $this->handleViewException($e); + } + + return ob_get_clean(); + } + + /** + * Handle a view exception. + * + * @param Exception $e + * @return void + */ + protected function handleViewException($e) + { + ob_get_clean(); throw $e; + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/View/Environment.php b/vendor/laravel/framework/src/Illuminate/View/Environment.php new file mode 100755 index 0000000..d54e787 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/View/Environment.php @@ -0,0 +1,703 @@ + 'blade', 'php' => 'php'); + + /** + * The view composer events. + * + * @var array + */ + protected $composers = array(); + + /** + * All of the finished, captured sections. + * + * @var array + */ + protected $sections = array(); + + /** + * The stack of in-progress sections. + * + * @var array + */ + protected $sectionStack = array(); + + /** + * The number of active rendering operations. + * + * @var int + */ + protected $renderCount = 0; + + /** + * Create a new view environment instance. + * + * @param \Illuminate\View\Engines\EngineResolver $engines + * @param \Illuminate\View\ViewFinderInterface $finder + * @param \Illuminate\Events\Dispatcher $events + * @return void + */ + public function __construct(EngineResolver $engines, ViewFinderInterface $finder, Dispatcher $events) + { + $this->finder = $finder; + $this->events = $events; + $this->engines = $engines; + + $this->share('__env', $this); + } + + /** + * Get a evaluated view contents for the given view. + * + * @param string $view + * @param array $data + * @param array $mergeData + * @return \Illuminate\View\View + */ + public function make($view, $data = array(), $mergeData = array()) + { + $path = $this->finder->find($view); + + $data = array_merge($mergeData, $this->parseData($data)); + + $this->callCreator($view = new View($this, $this->getEngineFromPath($path), $view, $path, $data)); + + return $view; + } + + /** + * Parse the given data into a raw array. + * + * @param mixed $data + * @return array + */ + protected function parseData($data) + { + return $data instanceof Arrayable ? $data->toArray() : $data; + } + + /** + * Get a evaluated view contents for a named view. + * + * @param string $view + * @param mixed $data + * @return \Illuminate\View\View + */ + public function of($view, $data = array()) + { + return $this->make($this->names[$view], $data); + } + + /** + * Register a named view. + * + * @param string $view + * @param string $name + * @return void + */ + public function name($view, $name) + { + $this->names[$name] = $view; + } + + /** + * Determine if a given view exists. + * + * @param string $view + * @return bool + */ + public function exists($view) + { + try + { + $this->finder->find($view); + } + catch (\InvalidArgumentException $e) + { + return false; + } + + return true; + } + + /** + * Get the rendered contents of a partial from a loop. + * + * @param string $view + * @param array $data + * @param string $iterator + * @param string $empty + * @return string + */ + public function renderEach($view, $data, $iterator, $empty = 'raw|') + { + $result = ''; + + // If is actually data in the array, we will loop through the data and append + // an instance of the partial view to the final result HTML passing in the + // iterated value of this data array, allowing the views to access them. + if (count($data) > 0) + { + foreach ($data as $key => $value) + { + $data = array('key' => $key, $iterator => $value); + + $result .= $this->make($view, $data)->render(); + } + } + + // If there is no data in the array, we will render the contents of the empty + // view. Alternatively, the "empty view" could be a raw string that begins + // with "raw|" for convenience and to let this know that it is a string. + else + { + if (starts_with($empty, 'raw|')) + { + $result = substr($empty, 4); + } + else + { + $result = $this->make($empty)->render(); + } + } + + return $result; + } + + /** + * Get the appropriate view engine for the given path. + * + * @param string $path + * @return \Illuminate\View\Engines\EngineInterface + */ + protected function getEngineFromPath($path) + { + $engine = $this->extensions[$this->getExtension($path)]; + + return $this->engines->resolve($engine); + } + + /** + * Get the extension used by the view file. + * + * @param string $path + * @return string + */ + protected function getExtension($path) + { + $extensions = array_keys($this->extensions); + + return array_first($extensions, function($key, $value) use ($path) + { + return ends_with($path, $value); + }); + } + + /** + * Add a piece of shared data to the environment. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function share($key, $value = null) + { + if ( ! is_array($key)) return $this->shared[$key] = $value; + + foreach ($key as $innerKey => $innerValue) + { + $this->share($innerKey, $innerValue); + } + } + + /** + * Register a view creator event. + * + * @param array|string $views + * @param \Closure|string $callback + * @return array + */ + public function creator($views, $callback) + { + $creators = array(); + + foreach ((array) $views as $view) + { + $creators[] = $this->addViewEvent($view, $callback, 'creating: '); + } + + return $creators; + } + + /** + * Register a view composer event. + * + * @param array|string $views + * @param \Closure|string $callback + * @return array + */ + public function composer($views, $callback) + { + $composers = array(); + + foreach ((array) $views as $view) + { + $composers[] = $this->addViewEvent($view, $callback); + } + + return $composers; + } + + /** + * Add an event for a given view. + * + * @param string $view + * @param Closure|string $callback + * @param string $prefix + * @return Closure + */ + protected function addViewEvent($view, $callback, $prefix = 'composing: ') + { + if ($callback instanceof Closure) + { + $this->events->listen($prefix.$view, $callback); + + return $callback; + } + elseif (is_string($callback)) + { + return $this->addClassEvent($view, $callback, $prefix); + } + } + + /** + * Register a class based view composer. + * + * @param string $view + * @param string $class + * @param string $prefix + * @return \Closure + */ + protected function addClassEvent($view, $class, $prefix) + { + $name = $prefix.$view; + + // When registering a class based view "composer", we will simply resolve the + // classes from the application IoC container then call the compose method + // on the instance. This allows for convenient, testable view composers. + $callback = $this->buildClassEventCallback($class, $prefix); + + $this->events->listen($name, $callback); + + return $callback; + } + + /** + * Build a class based container callback Closure. + * + * @param string $class + * @param string $prefix + * @return \Closure + */ + protected function buildClassEventCallback($class, $prefix) + { + $container = $this->container; + + list($class, $method) = $this->parseClassEvent($class, $prefix); + + // Once we have the class and method name, we can build the Closure to resolve + // the instance out of the IoC container and call the method on it with the + // given arguments that are passed to the Closure as the composer's data. + return function() use ($class, $method, $container) + { + $callable = array($container->make($class), $method); + + return call_user_func_array($callable, func_get_args()); + }; + } + + /** + * Parse a class based composer name. + * + * @param string $class + * @param string $prefix + * @return array + */ + protected function parseClassEvent($class, $prefix) + { + if (str_contains($class, '@')) + { + return explode('@', $class); + } + else + { + $method = str_contains($prefix, 'composing') ? 'compose' : 'create'; + + return array($class, $method); + } + } + + /** + * Call the composer for a given view. + * + * @param \Illuminate\View\View $view + * @return void + */ + public function callComposer(View $view) + { + $this->events->fire('composing: '.$view->getName(), array($view)); + } + + /** + * Call the creator for a given view. + * + * @param \Illuminate\View\View $view + * @return void + */ + public function callCreator(View $view) + { + $this->events->fire('creating: '.$view->getName(), array($view)); + } + + /** + * Start injecting content into a section. + * + * @param string $section + * @param string $content + * @return void + */ + public function startSection($section, $content = '') + { + if ($content === '') + { + ob_start() and $this->sectionStack[] = $section; + } + else + { + $this->extendSection($section, $content); + } + } + + /** + * Inject inline content into a section. + * + * @param string $section + * @param string $content + * @return void + */ + public function inject($section, $content) + { + return $this->startSection($section, $content); + } + + /** + * Stop injecting content into a section and return its contents. + * + * @return string + */ + public function yieldSection() + { + return $this->yieldContent($this->stopSection()); + } + + /** + * Stop injecting content into a section. + * + * @param bool $overwrite + * @return string + */ + public function stopSection($overwrite = false) + { + $last = array_pop($this->sectionStack); + + if ($overwrite) + { + $this->sections[$last] = ob_get_clean(); + } + else + { + $this->extendSection($last, ob_get_clean()); + } + + return $last; + } + + /** + * Append content to a given section. + * + * @param string $section + * @param string $content + * @return void + */ + protected function extendSection($section, $content) + { + if (isset($this->sections[$section])) + { + $content = str_replace('@parent', $content, $this->sections[$section]); + + $this->sections[$section] = $content; + } + else + { + $this->sections[$section] = $content; + } + } + + /** + * Get the string contents of a section. + * + * @param string $section + * @param string $default + * @return string + */ + public function yieldContent($section, $default = '') + { + return isset($this->sections[$section]) ? $this->sections[$section] : $default; + } + + /** + * Flush all of the section contents. + * + * @return void + */ + public function flushSections() + { + $this->sections = array(); + + $this->sectionStack = array(); + } + + /** + * Increment the rendering counter. + * + * @return void + */ + public function incrementRender() + { + $this->renderCount++; + } + + /** + * Decrement the rendering counter. + * + * @return void + */ + public function decrementRender() + { + $this->renderCount--; + } + + /** + * Check if there are no active render operations. + * + * @return bool + */ + public function doneRendering() + { + return $this->renderCount == 0; + } + + /** + * Add a location to the array of view locations. + * + * @param string $location + * @return void + */ + public function addLocation($location) + { + $this->finder->addLocation($location); + } + + /** + * Add a new namespace to the loader. + * + * @param string $namespace + * @param string|array $hints + * @return void + */ + public function addNamespace($namespace, $hints) + { + $this->finder->addNamespace($namespace, $hints); + } + + /** + * Register a valid view extension and its engine. + * + * @param string $extension + * @param string $engine + * @param Closure $resolver + * @return void + */ + public function addExtension($extension, $engine, $resolver = null) + { + $this->finder->addExtension($extension); + + if (isset($resolver)) + { + $this->engines->register($engine, $resolver); + } + + unset($this->extensions[$engine]); + + $this->extensions = array_merge(array($extension => $engine), $this->extensions); + } + + /** + * Get the extension to engine bindings. + * + * @return array + */ + public function getExtensions() + { + return $this->extensions; + } + + /** + * Get the engine resolver instance. + * + * @return \Illuminate\View\Engines\EngineResolver + */ + public function getEngineResolver() + { + return $this->engines; + } + + /** + * Get the view finder instance. + * + * @return \Illuminate\View\ViewFinderInterface + */ + public function getFinder() + { + return $this->finder; + } + + /** + * Get the event dispatcher instance. + * + * @return \Illuminate\Events\Dispatcher + */ + public function getDispatcher() + { + return $this->events; + } + + /** + * Set the event dispatcher instance. + * + * @param \Illuminate\Events\Dispatcher + * @return void + */ + public function setDispatcher(Dispatcher $events) + { + $this->events = $events; + } + + /** + * Get the IoC container instance. + * + * @return \Illuminate\Container\Container + */ + public function getContainer() + { + return $this->container; + } + + /** + * Set the IoC container instance. + * + * @param \Illuminate\Container\Container $container + * @return void + */ + public function setContainer(Container $container) + { + $this->container = $container; + } + + /** + * Get all of the shared data for the environment. + * + * @return array + */ + public function getShared() + { + return $this->shared; + } + + /** + * Get the entire array of sections. + * + * @return array + */ + public function getSections() + { + return $this->sections; + } + + /** + * Get all of the registered named views in environment. + * + * @return array + */ + public function getNames() + { + return $this->names; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/View/FileViewFinder.php b/vendor/laravel/framework/src/Illuminate/View/FileViewFinder.php new file mode 100755 index 0000000..21807de --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/View/FileViewFinder.php @@ -0,0 +1,227 @@ +files = $files; + $this->paths = $paths; + + if (isset($extensions)) + { + $this->extensions = $extensions; + } + } + + /** + * Get the fully qualified location of the view. + * + * @param string $name + * @return string + */ + public function find($name) + { + if (strpos($name, '::') !== false) return $this->findNamedPathView($name); + + return $this->findInPaths($name, $this->paths); + } + + /** + * Get the path to a template with a named path. + * + * @param string $name + * @return string + */ + protected function findNamedPathView($name) + { + list($namespace, $view) = $this->getNamespaceSegments($name); + + return $this->findInPaths($view, $this->hints[$namespace]); + } + + /** + * Get the segments of a template with a named path. + * + * @param string $name + * @return array + */ + protected function getNamespaceSegments($name) + { + $segments = explode('::', $name); + + if (count($segments) != 2) + { + throw new \InvalidArgumentException("View [$name] has an invalid name."); + } + + if ( ! isset($this->hints[$segments[0]])) + { + throw new \InvalidArgumentException("No hint path defined for [{$segments[0]}]."); + } + + return $segments; + } + + /** + * Find the given view in the list of paths. + * + * @param string $name + * @param array $paths + * @return string + */ + protected function findInPaths($name, $paths) + { + foreach ((array) $paths as $path) + { + foreach ($this->getPossibleViewFiles($name) as $file) + { + if ($this->files->exists($viewPath = $path.'/'.$file)) + { + return $viewPath; + } + } + } + + throw new \InvalidArgumentException("View [$name] not found."); + } + + /** + * Get an array of possible view files. + * + * @param string $name + * @return array + */ + protected function getPossibleViewFiles($name) + { + return array_map(function($extension) use ($name) + { + return str_replace('.', '/', $name).'.'.$extension; + + }, $this->extensions); + } + + /** + * Add a location to the finder. + * + * @param string $location + * @return void + */ + public function addLocation($location) + { + $this->paths[] = $location; + } + + /** + * Add a namespace hint to the finder. + * + * @param string $namespace + * @param string|array $hints + * @return void + */ + public function addNamespace($namespace, $hints) + { + $hints = (array) $hints; + + if (isset($this->hints[$namespace])) + { + $hints = array_merge($this->hints[$namespace], $hints); + } + + $this->hints[$namespace] = $hints; + } + + /** + * Register an extension with the view finder. + * + * @param string $extension + * @return void + */ + public function addExtension($extension) + { + if (($index = array_search($extension, $this->extensions)) !== false) + { + unset($this->extensions[$index]); + } + + array_unshift($this->extensions, $extension); + } + + /** + * Get the filesystem instance. + * + * @return \Illuminate\Filesystem\Filesystem + */ + public function getFilesystem() + { + return $this->files; + } + + /** + * Get the active view paths. + * + * @return array + */ + public function getPaths() + { + return $this->paths; + } + + /** + * Get the namespace to file path hints. + * + * @return array + */ + public function getHints() + { + return $this->hints; + } + + /** + * Get registered extensions. + * + * @return array + */ + public function getExtensions() + { + return $this->extensions; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/View/View.php b/vendor/laravel/framework/src/Illuminate/View/View.php new file mode 100755 index 0000000..cc73d2a --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/View/View.php @@ -0,0 +1,317 @@ +view = $view; + $this->path = $path; + $this->engine = $engine; + $this->environment = $environment; + + $this->data = $data instanceof Arrayable ? $data->toArray() : (array) $data; + } + + /** + * Get the string contents of the view. + * + * @return string + */ + public function render() + { + $env = $this->environment; + + // We will keep track of the amount of views being rendered so we can flush + // the section after the complete rendering operation is done. This will + // clear out the sections for any separate views that may be rendered. + $env->incrementRender(); + + $env->callComposer($this); + + $contents = trim($this->getContents()); + + // Once we've finished rendering the view, we'll decrement the render count + // then if we are at the bottom of the stack we'll flush out sections as + // they might interfere with totally separate view's evaluations later. + $env->decrementRender(); + + if ($env->doneRendering()) $env->flushSections(); + + return $contents; + } + + /** + * Get the evaluated contents of the view. + * + * @return string + */ + protected function getContents() + { + return $this->engine->get($this->path, $this->gatherData()); + } + + /** + * Get the data bound to the view instance. + * + * @return array + */ + protected function gatherData() + { + $data = array_merge($this->environment->getShared(), $this->data); + + foreach ($data as $key => $value) + { + if ($value instanceof Renderable) + { + $data[$key] = $value->render(); + } + } + + return $data; + } + + /** + * Add a piece of data to the view. + * + * @param string|array $key + * @param mixed $value + * @return \Illuminate\View\View + */ + public function with($key, $value = null) + { + if (is_array($key)) + { + $this->data = array_merge($this->data, $key); + } + else + { + $this->data[$key] = $value; + } + + return $this; + } + + /** + * Add a view instance to the view data. + * + * @param string $key + * @param string $view + * @param array $data + * @return \Illuminate\View\View + */ + public function nest($key, $view, array $data = array()) + { + return $this->with($key, $this->environment->make($view, $data)); + } + + /** + * Get the view environment instance. + * + * @return \Illuminate\View\Environment + */ + public function getEnvironment() + { + return $this->environment; + } + + /** + * Get the view's rendering engine. + * + * @return \Illuminate\View\Engines\EngineInterface + */ + public function getEngine() + { + return $this->engine; + } + + /** + * Get the name of the view. + * + * @return string + */ + public function getName() + { + return $this->view; + } + + /** + * Get the array of view data. + * + * @return array + */ + public function getData() + { + return $this->data; + } + + /** + * Get the path to the view file. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Set the path to the view. + * + * @param string $path + * @return void + */ + public function setPath($path) + { + $this->path = $path; + } + + /** + * Determine if a piece of data is bound. + * + * @param string $key + * @return bool + */ + public function offsetExists($key) + { + return array_key_exists($key, $this->data); + } + + /** + * Get a piece of bound data to the view. + * + * @param string $key + * @return mixed + */ + public function offsetGet($key) + { + return $this->data[$key]; + } + + /** + * Set a piece of data on the view. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value) + { + $this->with($key, $value); + } + + /** + * Unset a piece of data from the view. + * + * @param string $key + * @return void + */ + public function offsetUnset($key) + { + unset($this->data[$key]); + } + + /** + * Get a piece of data from the view. + * + * @return mixed + */ + public function __get($key) + { + return $this->data[$key]; + } + + /** + * Set a piece of data on the view. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this->with($key, $value); + } + + /** + * Check if a piece of data is bound to the view. + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + return isset($this->data[$key]); + } + + /** + * Remove a piece of bound data from the view. + * + * @param string $key + * @return bool + */ + public function __unset($key) + { + unset($this->data[$key]); + } + + /** + * Get the string contents of the view. + * + * @return string + */ + public function __toString() + { + return $this->render(); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/View/ViewFinderInterface.php b/vendor/laravel/framework/src/Illuminate/View/ViewFinderInterface.php new file mode 100755 index 0000000..361b230 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/View/ViewFinderInterface.php @@ -0,0 +1,38 @@ +registerEngineResolver(); + + $this->registerViewFinder(); + + // Once the other components have been registered we're ready to include the + // view environment and session binder. The session binder will bind onto + // the "before" application event and add errors into shared view data. + $this->registerEnvironment(); + + $this->registerSessionBinder(); + } + + /** + * Register the engine resolver instance. + * + * @return void + */ + public function registerEngineResolver() + { + list($me, $app) = array($this, $this->app); + + $app['view.engine.resolver'] = $app->share(function($app) use ($me) + { + $resolver = new EngineResolver; + + // Next we will register the various engines with the resolver so that the + // environment can resolve the engines it needs for various views based + // on the extension of view files. We call a method for each engines. + foreach (array('php', 'blade') as $engine) + { + $me->{'register'.ucfirst($engine).'Engine'}($resolver); + } + + return $resolver; + }); + } + + /** + * Register the PHP engine implementation. + * + * @param \Illuminate\View\Engines\EngineResolver $resolver + * @return void + */ + public function registerPhpEngine($resolver) + { + $resolver->register('php', function() { return new PhpEngine; }); + } + + /** + * Register the Blade engine implementation. + * + * @param \Illuminate\View\Engines\EngineResolver $resolver + * @return void + */ + public function registerBladeEngine($resolver) + { + $app = $this->app; + + $resolver->register('blade', function() use ($app) + { + $cache = $app['path.storage'].'/views'; + + // The Compiler engine requires an instance of the CompilerInterface, which in + // this case will be the Blade compiler, so we'll first create the compiler + // instance to pass into the engine so it can compile the views properly. + $compiler = new BladeCompiler($app['files'], $cache); + + return new CompilerEngine($compiler, $app['files']); + }); + } + + /** + * Register the view finder implementation. + * + * @return void + */ + public function registerViewFinder() + { + $this->app['view.finder'] = $this->app->share(function($app) + { + $paths = $app['config']['view.paths']; + + return new FileViewFinder($app['files'], $paths); + }); + } + + /** + * Register the view environment. + * + * @return void + */ + public function registerEnvironment() + { + $this->app['view'] = $this->app->share(function($app) + { + // Next we need to grab the engine resolver instance that will be used by the + // environment. The resolver will be used by an environment to get each of + // the various engine implementations such as plain PHP or Blade engine. + $resolver = $app['view.engine.resolver']; + + $finder = $app['view.finder']; + + $env = new Environment($resolver, $finder, $app['events']); + + // We will also set the container instance on this view environment since the + // view composers may be classes registered in the container, which allows + // for great testable, flexible composers for the application developer. + $env->setContainer($app); + + $env->share('app', $app); + + return $env; + }); + } + + /** + * Register the session binder for the view environment. + * + * @return void + */ + protected function registerSessionBinder() + { + list($app, $me) = array($this->app, $this); + + $app->booted(function() use ($app, $me) + { + // If the current session has an "errors" variable bound to it, we will share + // its value with all view instances so the views can easily access errors + // without having to bind. An empty bag is set when there aren't errors. + if ($me->sessionHasErrors($app)) + { + $errors = $app['session']->get('errors'); + + $app['view']->share('errors', $errors); + } + + // Putting the errors in the view for every view allows the developer to just + // assume that some errors are always available, which is convenient since + // they don't have to continually run checks for the presence of errors. + else + { + $app['view']->share('errors', new MessageBag); + } + }); + } + + /** + * Determine if the application session has errors. + * + * @param \Illuminate\Foundation\Application $app + * @return bool + */ + public function sessionHasErrors($app) + { + $config = $app['config']['session']; + + if (isset($app['session']) and ! is_null($config['driver'])) + { + return $app['session']->has('errors'); + } + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/View/composer.json b/vendor/laravel/framework/src/Illuminate/View/composer.json new file mode 100755 index 0000000..c32a2bc --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/View/composer.json @@ -0,0 +1,33 @@ +{ + "name": "illuminate/view", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "php": ">=5.3.0", + "illuminate/container": "4.0.x", + "illuminate/events": "4.0.x", + "illuminate/filesystem": "4.0.x", + "illuminate/support": "4.0.x" + }, + "require-dev": { + "mockery/mockery": "0.7.2", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\View": "" + } + }, + "target-dir": "Illuminate/View", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/laravel/framework/src/Illuminate/Workbench/Console/WorkbenchMakeCommand.php b/vendor/laravel/framework/src/Illuminate/Workbench/Console/WorkbenchMakeCommand.php new file mode 100755 index 0000000..46ed697 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Workbench/Console/WorkbenchMakeCommand.php @@ -0,0 +1,137 @@ +creator = $creator; + } + + /** + * Execute the console command. + * + * @return void + */ + public function fire() + { + $workbench = $this->runCreator($this->buildPackage()); + + $this->info('Package workbench created!'); + + $this->callComposerUpdate($workbench); + } + + /** + * Run the package creator class for a given Package. + * + * @param \Illuminate\Workbench\Package $package + * @return string + */ + protected function runCreator($package) + { + $path = $this->laravel['path.base'].'/workbench'; + + $plain = ! $this->option('resources'); + + return $this->creator->create($package, $path, $plain); + } + + /** + * Call the composer update routine on the path. + * + * @param string $path + * @return void + */ + protected function callComposerUpdate($path) + { + chdir($path); + + passthru('composer install --dev'); + } + + /** + * Build the package details from user input. + * + * @return \Illuminate\Workbench\Package + */ + protected function buildPackage() + { + list($vendor, $name) = $this->getPackageSegments(); + + $config = $this->laravel['config']['workbench']; + + return new Package($vendor, $name, $config['name'], $config['email']); + } + + /** + * Get the package vendor and name segments from the input. + * + * @return array + */ + protected function getPackageSegments() + { + $package = $this->argument('package'); + + return array_map('studly_case', explode('/', $package, 2)); + } + + /** + * Get the console command arguments. + * + * @return array + */ + protected function getArguments() + { + return array( + array('package', InputArgument::REQUIRED, 'The name (vendor/name) of the package.'), + ); + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return array( + array('resources', null, InputOption::VALUE_NONE, 'Create Laravel specific directories.'), + ); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Workbench/Package.php b/vendor/laravel/framework/src/Illuminate/Workbench/Package.php new file mode 100755 index 0000000..b87f8c2 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Workbench/Package.php @@ -0,0 +1,76 @@ +name = $name; + $this->email = $email; + $this->vendor = $vendor; + $this->author = $author; + $this->lowerName = snake_case($name, '-'); + $this->lowerVendor = snake_case($vendor, '-'); + } + + /** + * Get the full package name. + * + * @return string + */ + public function getFullName() + { + return $this->lowerVendor.'/'.$this->lowerName; + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Workbench/PackageCreator.php b/vendor/laravel/framework/src/Illuminate/Workbench/PackageCreator.php new file mode 100755 index 0000000..110b606 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Workbench/PackageCreator.php @@ -0,0 +1,371 @@ +files = $files; + } + + /** + * Create a new package stub. + * + * @param \Illuminate\Workbench\Package $package + * @param string $path + * @param bool $plain + * @return string + */ + public function create(Package $package, $path, $plain = true) + { + $directory = $this->createDirectory($package, $path); + + // To create the package, we will spin through a list of building blocks that + // make up each package. We'll then call the method to build that block on + // the class, which keeps the actual building of stuff nice and cleaned. + foreach ($this->getBlocks($plain) as $block) + { + $this->{"write{$block}"}($package, $directory, $plain); + } + + return $directory; + } + + /** + * Create a package with all resource directories. + * + * @param Package $package + * @param string $path + * @return void + */ + public function createWithResources(Package $package, $path) + { + return $this->create($package, $path, false); + } + + /** + * Get the blocks for a given package. + * + * @param bool $plain + * @return array + */ + protected function getBlocks($plain) + { + return $plain ? $this->basicBlocks : $this->blocks; + } + + /** + * Write the support files to the package root. + * + * @param \Illuminate\Workbench\Package $package + * @param string $directory + * @return void + */ + public function writeSupportFiles(Package $package, $directory, $plain) + { + foreach (array('PhpUnit', 'Travis', 'Composer', 'Ignore') as $file) + { + $this->{"write{$file}File"}($package, $directory, $plain); + } + } + + /** + * Write the PHPUnit stub file. + * + * @param \Illuminate\Workbench\Package $package + * @param string $directory + * @return void + */ + protected function writePhpUnitFile(Package $package, $directory) + { + $stub = __DIR__.'/stubs/phpunit.xml'; + + $this->files->copy($stub, $directory.'/phpunit.xml'); + } + + /** + * Write the Travis stub file. + * + * @param \Illuminate\Workbench\Package $package + * @param string $directory + * @return void + */ + protected function writeTravisFile(Package $package, $directory) + { + $stub = __DIR__.'/stubs/.travis.yml'; + + $this->files->copy($stub, $directory.'/.travis.yml'); + } + + /** + * Write the Composer.json stub file. + * + * @param \Illuminate\Workbench\Package $package + * @param string $directory + * @return void + */ + protected function writeComposerFile(Package $package, $directory, $plain) + { + $stub = $this->getComposerStub($plain); + + $stub = $this->formatPackageStub($package, $stub); + + $this->files->put($directory.'/composer.json', $stub); + } + + /** + * Get the Composer.json stub file contents. + * + * @param bool $plain + * @return string + */ + protected function getComposerStub($plain) + { + if ($plain) return $this->files->get(__DIR__.'/stubs/plain.composer.json'); + + return $this->files->get(__DIR__.'/stubs/composer.json'); + } + + /** + * Write the stub .gitignore file for the package. + * + * @param \Illuminate\Workbench\Package $package + * @param string $directory + * @return void + */ + public function writeIgnoreFile(Package $package, $directory, $plain) + { + $this->files->copy(__DIR__.'/stubs/gitignore.txt', $directory.'/.gitignore'); + } + + /** + * Create the support directories for a package. + * + * @param \Illuminate\Workbench\Package $package + * @param string $directory + * @return void + */ + public function writeSupportDirectories(Package $package, $directory) + { + foreach (array('config', 'lang', 'migrations', 'views') as $support) + { + $this->writeSupportDirectory($package, $support, $directory); + } + } + + /** + * Write a specific support directory for the package. + * + * @param \Illuminate\Workbench\Package $package + * @param string $support + * @param string $directory + * @return void + */ + protected function writeSupportDirectory(Package $package, $support, $directory) + { + // Once we create the source directory, we will write an empty file to the + // directory so that it will be kept in source control allowing the dev + // to go ahead and push these components to Github right on creation. + $path = $directory.'/src/'.$support; + + $this->files->makeDirectory($path, 0777, true); + + $this->files->put($path.'/.gitkeep', ''); + } + + /** + * Create the public directory for the package. + * + * @param \Illuminate\Workbench\Package $package + * @param string $directory + * @return void + */ + public function writePublicDirectory(Package $package, $directory, $plain) + { + if ($plain) return; + + $this->files->makeDirectory($directory.'/public'); + + $this->files->put($directory.'/public/.gitkeep', ''); + } + + /** + * Create the test directory for the package. + * + * @param \Illuminate\Workbench\Package $package + * @param string $directory + * @return void + */ + public function writeTestDirectory(Package $package, $directory) + { + $this->files->makeDirectory($directory.'/tests'); + + $this->files->put($directory.'/tests/.gitkeep', ''); + } + + /** + * Write the stub ServiceProvider for the package. + * + * @param \Illuminate\Workbench\Package $package + * @param string $directory + * @return void + */ + public function writeServiceProvider(Package $package, $directory, $plain) + { + // Once we have the service provider stub, we will need to format it and make + // the necessary replacements to the class, namespaces, etc. Then we'll be + // able to write it out into the package's workbench directory for them. + $stub = $this->getProviderStub($package, $plain); + + $this->writeProviderStub($package, $directory, $stub); + } + + /** + * Write the service provider stub for the package. + * + * @param \Illuminate\Workbench\Package $package + * @param string $directory + * @param string $stub + * @return void + */ + protected function writeProviderStub(Package $package, $directory, $stub) + { + $path = $this->createClassDirectory($package, $directory); + + // The primary source directory where the package's classes will live may not + // exist yet, so we will need to create it before we write these providers + // out to that location. We'll go ahead and create now here before then. + $file = $path.'/'.$package->name.'ServiceProvider.php'; + + $this->files->put($file, $stub); + } + + /** + * Get the stub for a ServiceProvider. + * + * @param \Illuminate\Workbench\Package $package + * @param bool $plain + * @return string + */ + protected function getProviderStub(Package $package, $plain) + { + return $this->formatPackageStub($package, $this->getProviderFile($plain)); + } + + /** + * Load the raw service provider file. + * + * @param bool $plain + * @return string + */ + protected function getProviderFile($plain) + { + if ($plain) + { + return $this->files->get(__DIR__.'/stubs/plain.provider.stub'); + } + else + { + return $this->files->get(__DIR__.'/stubs/provider.stub'); + } + } + + /** + * Create the main source directory for the package. + * + * @param \Illuminate\Workbench\Package $package + * @param string $directory + * @return string + */ + protected function createClassDirectory(Package $package, $directory) + { + $path = $directory.'/src/'.$package->vendor.'/'.$package->name; + + if ( ! $this->files->isDirectory($path)) + { + $this->files->makeDirectory($path, 0777, true); + } + + return $path; + } + + /** + * Format a generic package stub file. + * + * @param \Illuminate\Workbench\Package $package + * @param string $stub + * @return string + */ + protected function formatPackageStub(Package $package, $stub) + { + foreach (get_object_vars($package) as $key => $value) + { + $stub = str_replace('{{'.snake_case($key).'}}', $value, $stub); + } + + return $stub; + } + + /** + * Create a workbench directory for the package. + * + * @param \Illuminate\Workbench\Package $package + * @param string $path + * @return string + */ + protected function createDirectory(Package $package, $path) + { + $fullPath = $path.'/'.$package->getFullName(); + + // If the directory doesn't exist, we will go ahead and create the package + // directory in the workbench location. We will use this entire package + // name when creating the directory to avoid any potential conflicts. + if ( ! $this->files->isDirectory($fullPath)) + { + $this->files->makeDirectory($fullPath, 0777, true); + + return $fullPath; + } + + throw new \InvalidArgumentException("Package exists."); + } + +} diff --git a/vendor/laravel/framework/src/Illuminate/Workbench/Starter.php b/vendor/laravel/framework/src/Illuminate/Workbench/Starter.php new file mode 100755 index 0000000..655d8b8 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Workbench/Starter.php @@ -0,0 +1,29 @@ +in($path)->files()->name('autoload.php')->depth('<= 3'); + + foreach ($autoloads as $file) + { + $files->requireOnce($file->getRealPath()); + } + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Workbench/WorkbenchServiceProvider.php b/vendor/laravel/framework/src/Illuminate/Workbench/WorkbenchServiceProvider.php new file mode 100755 index 0000000..49b0a60 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Workbench/WorkbenchServiceProvider.php @@ -0,0 +1,45 @@ +app['package.creator'] = $this->app->share(function($app) + { + return new PackageCreator($app['files']); + }); + + $this->app['command.workbench'] = $this->app->share(function($app) + { + return new WorkbenchMakeCommand($app['package.creator']); + }); + + $this->commands('command.workbench'); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('package.creator', 'command.workbench'); + } + +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Workbench/composer.json b/vendor/laravel/framework/src/Illuminate/Workbench/composer.json new file mode 100755 index 0000000..3ea92b8 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Workbench/composer.json @@ -0,0 +1,32 @@ +{ + "name": "illuminate/workbench", + "license": "MIT", + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "require": { + "illuminate/filesystem": "4.0.x", + "illuminate/support": "4.0.x", + "symfony/finder": "2.3.*" + }, + "require-dev": { + "illuminate/console": "4.0.x", + "mockery/mockery": "0.7.2", + "phpunit/phpunit": "3.7.*" + }, + "autoload": { + "psr-0": { + "Illuminate\\Workbench": "" + } + }, + "target-dir": "Illuminate/Workbench", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Workbench/stubs/.travis.yml b/vendor/laravel/framework/src/Illuminate/Workbench/stubs/.travis.yml new file mode 100755 index 0000000..0a1c1cb --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Workbench/stubs/.travis.yml @@ -0,0 +1,12 @@ +language: php + +php: + - 5.3 + - 5.4 + - 5.5 + +before_script: + - curl -s http://getcomposer.org/installer | php + - php composer.phar install --dev + +script: phpunit \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Workbench/stubs/composer.json b/vendor/laravel/framework/src/Illuminate/Workbench/stubs/composer.json new file mode 100755 index 0000000..0150fea --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Workbench/stubs/composer.json @@ -0,0 +1,23 @@ +{ + "name": "{{lower_vendor}}/{{lower_name}}", + "description": "", + "authors": [ + { + "name": "{{author}}", + "email": "{{email}}" + } + ], + "require": { + "php": ">=5.3.0", + "illuminate/support": "4.0.x" + }, + "autoload": { + "classmap": [ + "src/migrations" + ], + "psr-0": { + "{{vendor}}\\{{name}}": "src/" + } + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Workbench/stubs/gitignore.txt b/vendor/laravel/framework/src/Illuminate/Workbench/stubs/gitignore.txt new file mode 100755 index 0000000..2c1fc0c --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Workbench/stubs/gitignore.txt @@ -0,0 +1,4 @@ +/vendor +composer.phar +composer.lock +.DS_Store \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Workbench/stubs/phpunit.xml b/vendor/laravel/framework/src/Illuminate/Workbench/stubs/phpunit.xml new file mode 100755 index 0000000..e89ac6d --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Workbench/stubs/phpunit.xml @@ -0,0 +1,18 @@ + + + + + ./tests/ + + + \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Workbench/stubs/plain.composer.json b/vendor/laravel/framework/src/Illuminate/Workbench/stubs/plain.composer.json new file mode 100755 index 0000000..5cd0290 --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Workbench/stubs/plain.composer.json @@ -0,0 +1,20 @@ +{ + "name": "{{lower_vendor}}/{{lower_name}}", + "description": "", + "authors": [ + { + "name": "{{author}}", + "email": "{{email}}" + } + ], + "require": { + "php": ">=5.3.0", + "illuminate/support": "4.0.x" + }, + "autoload": { + "psr-0": { + "{{vendor}}\\{{name}}": "src/" + } + }, + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/vendor/laravel/framework/src/Illuminate/Workbench/stubs/plain.provider.stub b/vendor/laravel/framework/src/Illuminate/Workbench/stubs/plain.provider.stub new file mode 100644 index 0000000..6892cfa --- /dev/null +++ b/vendor/laravel/framework/src/Illuminate/Workbench/stubs/plain.provider.stub @@ -0,0 +1,34 @@ +package('{{lower_vendor}}/{{lower_name}}'); + } + + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + // + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array(); + } + +} \ No newline at end of file diff --git a/vendor/monolog/monolog/CHANGELOG.mdown b/vendor/monolog/monolog/CHANGELOG.mdown new file mode 100644 index 0000000..2df1e0f --- /dev/null +++ b/vendor/monolog/monolog/CHANGELOG.mdown @@ -0,0 +1,110 @@ +### 1.6.0 (2013-07-29) + + * Added HipChatHandler to send logs to a HipChat chat room + * Added ErrorLogHandler to send logs to PHP's error_log function + * Added NewRelicHandler to send logs to NewRelic's service + * Added Monolog\ErrorHandler helper class to register a Logger as exception/error/fatal handler + * Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel + * Added stack traces output when normalizing exceptions (json output & co) + * Added Monolog\Logger::API constant (currently 1) + * Added support for ChromePHP's v4.0 extension + * Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel + * Added support for sending messages to multiple users at once with the PushoverHandler + * Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler) + * Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now + * Fixed issue in RotatingFileHandler when an open_basedir restriction is active + * Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0 + * Fixed SyslogHandler issue when many were used concurrently with different facilities + +### 1.5.0 (2013-04-23) + + * Added ProcessIdProcessor to inject the PID in log records + * Added UidProcessor to inject a unique identifier to all log records of one request/run + * Added support for previous exceptions in the LineFormatter exception serialization + * Added Monolog\Logger::getLevels() to get all available levels + * Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle + +### 1.4.1 (2013-04-01) + + * Fixed exception formatting in the LineFormatter to be more minimalistic + * Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0 + * Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days + * Fixed WebProcessor array access so it checks for data presence + * Fixed Buffer, Group and FingersCrossed handlers to make use of their processors + +### 1.4.0 (2013-02-13) + + * Added RedisHandler to log to Redis via the Predis library or the phpredis extension + * Added ZendMonitorHandler to log to the Zend Server monitor + * Added the possibility to pass arrays of handlers and processors directly in the Logger constructor + * Added `$useSSL` option to the PushoverHandler which is enabled by default + * Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously + * Fixed header injection capability in the NativeMailHandler + +### 1.3.1 (2013-01-11) + + * Fixed LogstashFormatter to be usable with stream handlers + * Fixed GelfMessageFormatter levels on Windows + +### 1.3.0 (2013-01-08) + + * Added PSR-3 compliance, the `Monolog\Logger` class is now an instance of `Psr\Log\LoggerInterface` + * Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance + * Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash) + * Added PushoverHandler to send mobile notifications + * Added CouchDBHandler and DoctrineCouchDBHandler + * Added RavenHandler to send data to Sentry servers + * Added support for the new MongoClient class in MongoDBHandler + * Added microsecond precision to log records' timestamps + * Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing + the oldest entries + * Fixed normalization of objects with cyclic references + +### 1.2.1 (2012-08-29) + + * Added new $logopts arg to SyslogHandler to provide custom openlog options + * Fixed fatal error in SyslogHandler + +### 1.2.0 (2012-08-18) + + * Added AmqpHandler (for use with AMQP servers) + * Added CubeHandler + * Added NativeMailerHandler::addHeader() to send custom headers in mails + * Added the possibility to specify more than one recipient in NativeMailerHandler + * Added the possibility to specify float timeouts in SocketHandler + * Added NOTICE and EMERGENCY levels to conform with RFC 5424 + * Fixed the log records to use the php default timezone instead of UTC + * Fixed BufferHandler not being flushed properly on PHP fatal errors + * Fixed normalization of exotic resource types + * Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog + +### 1.1.0 (2012-04-23) + + * Added Monolog\Logger::isHandling() to check if a handler will + handle the given log level + * Added ChromePHPHandler + * Added MongoDBHandler + * Added GelfHandler (for use with Graylog2 servers) + * Added SocketHandler (for use with syslog-ng for example) + * Added NormalizerFormatter + * Added the possibility to change the activation strategy of the FingersCrossedHandler + * Added possibility to show microseconds in logs + * Added `server` and `referer` to WebProcessor output + +### 1.0.2 (2011-10-24) + + * Fixed bug in IE with large response headers and FirePHPHandler + +### 1.0.1 (2011-08-25) + + * Added MemoryPeakUsageProcessor and MemoryUsageProcessor + * Added Monolog\Logger::getName() to get a logger's channel name + +### 1.0.0 (2011-07-06) + + * Added IntrospectionProcessor to get info from where the logger was called + * Fixed WebProcessor in CLI + +### 1.0.0-RC1 (2011-07-01) + + * Initial release diff --git a/vendor/monolog/monolog/LICENSE b/vendor/monolog/monolog/LICENSE new file mode 100644 index 0000000..5df1c39 --- /dev/null +++ b/vendor/monolog/monolog/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/monolog/monolog/README.mdown b/vendor/monolog/monolog/README.mdown new file mode 100644 index 0000000..1d5d13c --- /dev/null +++ b/vendor/monolog/monolog/README.mdown @@ -0,0 +1,248 @@ +Monolog - Logging for PHP 5.3+ [![Build Status](https://secure.travis-ci.org/Seldaek/monolog.png)](http://travis-ci.org/Seldaek/monolog) +============================== + +[![Total Downloads](https://poser.pugx.org/monolog/monolog/downloads.png)](https://packagist.org/packages/monolog/monolog) +[![Latest Stable Version](https://poser.pugx.org/monolog/monolog/v/stable.png)](https://packagist.org/packages/monolog/monolog) + + +Monolog sends your logs to files, sockets, inboxes, databases and various +web services. See the complete list of handlers below. Special handlers +allow you to build advanced logging strategies. + +This library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +interface that you can type-hint against in your own libraries to keep +a maximum of interoperability. You can also use it in your applications to +make sure you can always use another compatible logger at a later time. + +Usage +----- + +```php +pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); + +// add records to the log +$log->addWarning('Foo'); +$log->addError('Bar'); +``` + +Core Concepts +------------- + +Every `Logger` instance has a channel (name) and a stack of handlers. Whenever +you add a record to the logger, it traverses the handler stack. Each handler +decides whether it handled fully the record, and if so, the propagation of the +record ends there. + +This allows for flexible logging setups, for example having a `StreamHandler` at +the bottom of the stack that will log anything to disk, and on top of that add +a `MailHandler` that will send emails only when an error message is logged. +Handlers also have a `$bubble` property which defines whether they block the +record or not if they handled it. In this example, setting the `MailHandler`'s +`$bubble` argument to false means that records handled by the `MailHandler` will +not propagate to the `StreamHandler` anymore. + +You can create many `Logger`s, each defining a channel (e.g.: db, request, +router, ..) and each of them combining various handlers, which can be shared +or not. The channel is reflected in the logs and allows you to easily see or +filter records. + +Each Handler also has a Formatter, a default one with settings that make sense +will be created if you don't set one. The formatters normalize and format +incoming records so that they can be used by the handlers to output useful +information. + +Custom severity levels are not available. Only the eight +[RFC 5424](http://tools.ietf.org/html/rfc5424) levels (debug, info, notice, +warning, error, critical, alert, emergency) are present for basic filtering +purposes, but for sorting and other use cases that would require +flexibility, you should add Processors to the Logger that can add extra +information (tags, user ip, ..) to the records before they are handled. + +Log Levels +---------- + +Monolog supports all 8 logging levels defined in +[RFC 5424](http://tools.ietf.org/html/rfc5424), but unless you specifically +need syslog compatibility, it is advised to only use DEBUG, INFO, WARNING, +ERROR, CRITICAL, ALERT. + +- **DEBUG** (100): Detailed debug information. + +- **INFO** (200): Interesting events. Examples: User logs in, SQL logs. + +- NOTICE (250): Normal but significant events. + +- **WARNING** (300): Exceptional occurrences that are not errors. Examples: + Use of deprecated APIs, poor use of an API, undesirable things that are not + necessarily wrong. + +- **ERROR** (400): Runtime errors that do not require immediate action but + should typically be logged and monitored. + +- **CRITICAL** (500): Critical conditions. Example: Application component + unavailable, unexpected exception. + +- **ALERT** (550): Action must be taken immediately. Example: Entire website + down, database unavailable, etc. This should trigger the SMS alerts and wake + you up. + +- EMERGENCY (600): Emergency: system is unusable. + +Docs +==== + +**See the `doc` directory for more detailed documentation. +The following is only a list of all parts that come with Monolog.** + +Handlers +-------- + +### Log to files and syslog + +- _StreamHandler_: Logs records into any PHP stream, use this for log files. +- _RotatingFileHandler_: Logs records to a file and creates one logfile per day. + It will also delete files older than `$maxFiles`. You should use + [logrotate](http://linuxcommand.org/man_pages/logrotate8.html) for high profile + setups though, this is just meant as a quick and dirty solution. +- _SyslogHandler_: Logs records to the syslog. +- _ErrorLogHandler_: Logs records to PHP's + [`error_log()`](http://docs.php.net/manual/en/function.error-log.php) function. + +### Send alerts and emails + +- _NativeMailerHandler_: Sends emails using PHP's + [`mail()`](http://php.net/manual/en/function.mail.php) function. +- _SwiftMailerHandler_: Sends emails using a [`Swift_Mailer`](http://swiftmailer.org/) instance. +- _PushoverHandler_: Sends mobile notifications via the [Pushover](https://www.pushover.net/) API. +- _HipChatHandler_: Logs records to a [HipChat](http://hipchat.com) chat room using its API. + +### Log specific servers and networked logging + +- _SocketHandler_: Logs records to [sockets](http://php.net/fsockopen), use this + for UNIX and TCP sockets. See an [example](https://github.com/Seldaek/monolog/blob/master/doc/sockets.md). +- _AmqpHandler_: Logs records to an [amqp](http://www.amqp.org/) compatible + server. Requires the [php-amqp](http://pecl.php.net/package/amqp) extension (1.0+). +- _GelfHandler_: Logs records to a [Graylog2](http://www.graylog2.org) server. +- _CubeHandler_: Logs records to a [Cube](http://square.github.com/cube/) server. +- _RavenHandler_: Logs records to a [Sentry](http://getsentry.com/) server using + [raven](https://packagist.org/packages/raven/raven). +- _ZendMonitorHandler_: Logs records to the Zend Monitor present in Zend Server. +- _NewRelicHandler_: Logs records to a [NewRelic](http://newrelic.com/) application. + +### Logging in development + +- _FirePHPHandler_: Handler for [FirePHP](http://www.firephp.org/), providing + inline `console` messages within [FireBug](http://getfirebug.com/). +- _ChromePHPHandler_: Handler for [ChromePHP](http://www.chromephp.com/), providing + inline `console` messages within Chrome. + +### Log to databases + +- _RedisHandler_: Logs records to a [redis](http://redis.io) server. +- _MongoDBHandler_: Handler to write records in MongoDB via a + [Mongo](http://pecl.php.net/package/mongo) extension connection. +- _CouchDBHandler_: Logs records to a CouchDB server. +- _DoctrineCouchDBHandler_: Logs records to a CouchDB server via the Doctrine CouchDB ODM. + +### Wrappers / Special Handlers + +- _FingersCrossedHandler_: A very interesting wrapper. It takes a logger as + parameter and will accumulate log records of all levels until a record + exceeds the defined severity level. At which point it delivers all records, + including those of lower severity, to the handler it wraps. This means that + until an error actually happens you will not see anything in your logs, but + when it happens you will have the full information, including debug and info + records. This provides you with all the information you need, but only when + you need it. +- _NullHandler_: Any record it can handle will be thrown away. This can be used + to put on top of an existing handler stack to disable it temporarily. +- _BufferHandler_: This handler will buffer all the log records it receives + until `close()` is called at which point it will call `handleBatch()` on the + handler it wraps with all the log messages at once. This is very useful to + send an email with all records at once for example instead of having one mail + for every log record. +- _GroupHandler_: This handler groups other handlers. Every record received is + sent to all the handlers it is configured with. +- _TestHandler_: Used for testing, it records everything that is sent to it and + has accessors to read out the information. + +Formatters +---------- + +- _LineFormatter_: Formats a log record into a one-line string. +- _NormalizerFormatter_: Normalizes objects/resources down to strings so a record can easily be serialized/encoded. +- _JsonFormatter_: Encodes a log record into json. +- _WildfireFormatter_: Used to format log records into the Wildfire/FirePHP protocol, only useful for the FirePHPHandler. +- _ChromePHPFormatter_: Used to format log records into the ChromePHP format, only useful for the ChromePHPHandler. +- _GelfFormatter_: Used to format log records into Gelf message instances, only useful for the GelfHandler. +- _LogstashFormatter_: Used to format log records into [logstash](http://logstash.net/) event json, useful for any handler listed under inputs [here](http://logstash.net/docs/1.1.5/). + +Processors +---------- + +- _IntrospectionProcessor_: Adds the line/file/class/method from which the log call originated. +- _WebProcessor_: Adds the current request URI, request method and client IP to a log record. +- _MemoryUsageProcessor_: Adds the current memory usage to a log record. +- _MemoryPeakUsageProcessor_: Adds the peak memory usage to a log record. +- _ProcessIdProcessor_: Adds the process id to a log record. +- _UidProcessor_: Adds a unique identifier to a log record. + +Utilities +--------- + +- _ErrorHandler_: The `Monolog\ErrorHandler` class allows you to easily register + a Logger instance as an exception handler, error handler or fatal error handler. +- _ErrorLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain log + level is reached. +- _ChannelLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain + log level is reached, depending on which channel received the log record. + +About +===== + +Requirements +------------ + +- Any flavor of PHP 5.3 or above should do +- [optional] PHPUnit 3.5+ to execute the test suite (phpunit --version) + +Submitting bugs and feature requests +------------------------------------ + +Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues) + +Frameworks Integration +---------------------- + +- Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) + can be used very easily with Monolog since it implements the interface. +- [Symfony2](http://symfony.com) comes out of the box with Monolog. +- [Silex](http://silex.sensiolabs.org/) comes out of the box with Monolog. +- [Laravel4](http://laravel.com/) comes out of the box with Monolog. +- [PPI](http://www.ppi.io/) comes out of the box with Monolog. +- [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin. +- [Slim](http://www.slimframework.com/) is usable with Monolog via the [Slim-Monolog](https://github.com/Flynsarmy/Slim-Monolog) log writer. + +Author +------ + +Jordi Boggiano - -
    +See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) which participated in this project. + +License +------- + +Monolog is licensed under the MIT License - see the `LICENSE` file for details + +Acknowledgements +---------------- + +This library is heavily inspired by Python's [Logbook](http://packages.python.org/Logbook/) +library, although most concepts have been adjusted to fit to the PHP world. diff --git a/vendor/monolog/monolog/composer.json b/vendor/monolog/monolog/composer.json new file mode 100644 index 0000000..c89e188 --- /dev/null +++ b/vendor/monolog/monolog/composer.json @@ -0,0 +1,39 @@ +{ + "name": "monolog/monolog", + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "keywords": ["log", "logging", "psr-3"], + "homepage": "http://github.com/Seldaek/monolog", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "require-dev": { + "mlehner/gelf-php": "1.0.*", + "raven/raven": "0.5.*", + "doctrine/couchdb": "dev-master" + }, + "suggest": { + "mlehner/gelf-php": "Allow sending log messages to a GrayLog2 server", + "raven/raven": "Allow sending log messages to a Sentry server", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server" + }, + "autoload": { + "psr-0": {"Monolog": "src/"} + }, + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + } +} diff --git a/vendor/monolog/monolog/doc/extending.md b/vendor/monolog/monolog/doc/extending.md new file mode 100644 index 0000000..fcd7af2 --- /dev/null +++ b/vendor/monolog/monolog/doc/extending.md @@ -0,0 +1,76 @@ +Extending Monolog +================= + +Monolog is fully extensible, allowing you to adapt your logger to your needs. + +Writing your own handler +------------------------ + +Monolog provides many built-in handlers. But if the one you need does not +exist, you can write it and use it in your logger. The only requirement is +to implement `Monolog\Handler\HandlerInterface`. + +Let's write a PDOHandler to log records to a database. We will extend the +abstract class provided by Monolog to keep things DRY. + +```php +pdo = $pdo; + parent::__construct($level, $bubble); + } + + protected function write(array $record) + { + if (!$this->initialized) { + $this->initialize(); + } + + $this->statement->execute(array( + 'channel' => $record['channel'], + 'level' => $record['level'], + 'message' => $record['formatted'], + 'time' => $record['datetime']->format('U'), + )); + } + + private function initialize() + { + $this->pdo->exec( + 'CREATE TABLE IF NOT EXISTS monolog ' + .'(channel VARCHAR(255), level INTEGER, message LONGTEXT, time INTEGER UNSIGNED)' + ); + $this->statement = $this->pdo->prepare( + 'INSERT INTO monolog (channel, level, message, time) VALUES (:channel, :level, :message, :time)' + ); + + $this->initialized = true; + } +} +``` + +You can now use this handler in your logger: + +```php +pushHandler(new PDOHandler(new PDO('sqlite:logs.sqlite')); + +// You can now use your logger +$logger->addInfo('My logger is now ready'); +``` + +The `Monolog\Handler\AbstractProcessingHandler` class provides most of the +logic needed for the handler, including the use of processors and the formatting +of the record (which is why we use ``$record['formatted']`` instead of ``$record['message']``). diff --git a/vendor/monolog/monolog/doc/sockets.md b/vendor/monolog/monolog/doc/sockets.md new file mode 100644 index 0000000..fad30a9 --- /dev/null +++ b/vendor/monolog/monolog/doc/sockets.md @@ -0,0 +1,37 @@ +Sockets Handler +=============== + +This handler allows you to write your logs to sockets using [fsockopen](http://php.net/fsockopen) +or [pfsockopen](http://php.net/pfsockopen). + +Persistent sockets are mainly useful in web environments where you gain some performance not closing/opening +the connections between requests. + +Basic Example +------------- + +```php +setPersistent(true); + +// Now add the handler +$logger->pushHandler($handler, Logger::DEBUG); + +// You can now use your logger +$logger->addInfo('My logger is now ready'); + +``` + +In this example, using syslog-ng, you should see the log on the log server: + + cweb1 [2012-02-26 00:12:03] my_logger.INFO: My logger is now ready [] [] + diff --git a/vendor/monolog/monolog/doc/usage.md b/vendor/monolog/monolog/doc/usage.md new file mode 100644 index 0000000..07efa78 --- /dev/null +++ b/vendor/monolog/monolog/doc/usage.md @@ -0,0 +1,158 @@ +Using Monolog +============= + +Installation +------------ + +Monolog is available on Packagist ([monolog/monolog](http://packagist.org/packages/monolog/monolog)) +and as such installable via [Composer](http://getcomposer.org/). + +If you do not use Composer, you can grab the code from GitHub, and use any +PSR-0 compatible autoloader (e.g. the [Symfony2 ClassLoader component](https://github.com/symfony/ClassLoader)) +to load Monolog classes. + +Configuring a logger +-------------------- + +Here is a basic setup to log to a file and to firephp on the DEBUG level: + +```php +pushHandler(new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG)); +$logger->pushHandler(new FirePHPHandler()); + +// You can now use your logger +$logger->addInfo('My logger is now ready'); +``` + +Let's explain it. The first step is to create the logger instance which will +be used in your code. The argument is a channel name, which is useful when +you use several loggers (see below for more details about it). + +The logger itself does not know how to handle a record. It delegates it to +some handlers. The code above registers two handlers in the stack to allow +handling records in two different ways. + +Note that the FirePHPHandler is called first as it is added on top of the +stack. This allows you to temporarily add a logger with bubbling disabled if +you want to override other configured loggers. + +Adding extra data in the records +-------------------------------- + +Monolog provides two different ways to add extra informations along the simple +textual message. + +### Using the logging context + +The first way is the context, allowing to pass an array of data along the +record: + +```php +addInfo('Adding a new user', array('username' => 'Seldaek')); +``` + +Simple handlers (like the StreamHandler for instance) will simply format +the array to a string but richer handlers can take advantage of the context +(FirePHP is able to display arrays in pretty way for instance). + +### Using processors + +The second way is to add extra data for all records by using a processor. +Processors can be any callable. They will get the record as parameter and +must return it after having eventually changed the `extra` part of it. Let's +write a processor adding some dummy data in the record: + +```php +pushProcessor(function ($record) { + $record['extra']['dummy'] = 'Hello world!'; + + return $record; +}); +``` + +Monolog provides some built-in processors that can be used in your project. +Look at the README file for the list. + +> Tip: processors can also be registered on a specific handler instead of + the logger to apply only for this handler. + +Leveraging channels +------------------- + +Channels are a great way to identify to which part of the application a record +is related. This is useful in big applications (and is leveraged by +MonologBundle in Symfony2). + +Picture two loggers sharing a handler that writes to a single log file. +Channels would allow you to identify the logger that issued every record. +You can easily grep through the log files filtering this or that channel. + +```php +pushHandler($stream); +$logger->pushHandler($firephp); + +// Create a logger for the security-related stuff with a different channel +$securityLogger = new Logger('security'); +$securityLogger->pushHandler($stream); +$securityLogger->pushHandler($firephp); +``` + +Customizing log format +---------------------- + +In Monolog it's easy to customize the format of the logs written into files, +sockets, mails, databases and other handlers. Most of the handlers use the + +```php +$record['formatted'] +``` + +value to be automatically put into the log device. This value depends on the +formatter settings. You can choose between predefined formatter classes or +write your own (e.g. a multiline text file for human-readable output). + +To configure a predefined formatter class, just set it as the handler's field: + +```php +// the default date format is "Y-m-d H:i:s" +$dateFormat = "Y n j, g:i a"; +// the default output format is "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n" +$output = "%datetime% > %level_name% > %message% %context% %extra%\n"; +// finally, create a formatter +$formatter = new LineFormatter($output, $dateFormat); + +// Create a handler +$stream = new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG); +$stream->setFormatter($formatter); +// bind it to a logger object +$securityLogger = new Logger('security'); +$securityLogger->pushHandler($stream); +``` + +You may also reuse the same formatter between multiple handlers and share those +handlers between multiple loggers. diff --git a/vendor/monolog/monolog/phpunit.xml.dist b/vendor/monolog/monolog/phpunit.xml.dist new file mode 100644 index 0000000..1754570 --- /dev/null +++ b/vendor/monolog/monolog/phpunit.xml.dist @@ -0,0 +1,15 @@ + + + + + + tests/Monolog/ + + + + + + src/Monolog/ + + + diff --git a/vendor/monolog/monolog/src/Monolog/ErrorHandler.php b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php new file mode 100644 index 0000000..dd65132 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/ErrorHandler.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; + +/** + * Monolog error handler + * + * A facility to enable logging of runtime errors, exceptions and fatal errors. + * + * Quick setup: ErrorHandler::register($logger); + * + * @author Jordi Boggiano + */ +class ErrorHandler +{ + private $logger; + + private $previousExceptionHandler; + private $uncaughtExceptionLevel; + + private $previousErrorHandler; + private $errorLevelMap; + + private $fatalLevel; + private $reservedMemory; + private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR); + + public function __construct(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * Registers a new ErrorHandler for a given Logger + * + * By default it will handle errors, exceptions and fatal errors + * + * @param LoggerInterface $logger + * @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling + * @param int|false $exceptionLevel a LogLevel::* constant, or false to disable exception handling + * @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling + * @return ErrorHandler + */ + public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null) + { + $handler = new static($logger); + if ($errorLevelMap !== false) { + $handler->registerErrorHandler($errorLevelMap); + } + if ($exceptionLevel !== false) { + $handler->registerExceptionHandler($exceptionLevel); + } + if ($fatalLevel !== false) { + $handler->registerFatalHandler($fatalLevel); + } + + return $handler; + } + + public function registerExceptionHandler($level = null, $callPrevious = true) + { + $prev = set_exception_handler(array($this, 'handleException')); + $this->uncaughtExceptionLevel = $level === null ? LogLevel::ERROR : $level; + if ($callPrevious && $prev) { + $this->previousExceptionHandler = $prev; + } + } + + public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1) + { + $prev = set_error_handler(array($this, 'handleError'), $errorTypes); + $this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap); + if ($callPrevious) { + $this->previousErrorHandler = $prev ?: true; + } + } + + public function registerFatalHandler($level = null, $reservedMemorySize = 20) + { + register_shutdown_function(array($this, 'handleFatalError')); + + $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); + $this->fatalLevel = $level === null ? LogLevel::ALERT : $level; + } + + protected function defaultErrorLevelMap() + { + return array( + E_ERROR => LogLevel::CRITICAL, + E_WARNING => LogLevel::WARNING, + E_PARSE => LogLevel::ALERT, + E_NOTICE => LogLevel::NOTICE, + E_CORE_ERROR => LogLevel::CRITICAL, + E_CORE_WARNING => LogLevel::WARNING, + E_COMPILE_ERROR => LogLevel::ALERT, + E_COMPILE_WARNING => LogLevel::WARNING, + E_USER_ERROR => LogLevel::ERROR, + E_USER_WARNING => LogLevel::WARNING, + E_USER_NOTICE => LogLevel::NOTICE, + E_STRICT => LogLevel::NOTICE, + E_RECOVERABLE_ERROR => LogLevel::ERROR, + E_DEPRECATED => LogLevel::NOTICE, + E_USER_DEPRECATED => LogLevel::NOTICE, + ); + } + + /** + * @private + */ + public function handleException(\Exception $e) + { + $this->logger->log($this->uncaughtExceptionLevel, 'Uncaught exception', array('exception' => $e)); + + if ($this->previousExceptionHandler) { + call_user_func($this->previousExceptionHandler, $e); + } + } + + /** + * @private + */ + public function handleError($code, $message, $file = '', $line = 0, $context = array()) + { + if (!(error_reporting() & $code)) { + return; + } + + $level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL; + $this->logger->log($level, self::codeToString($code).': '.$message, array('file' => $file, 'line' => $line)); + + if ($this->previousErrorHandler === true) { + return false; + } elseif ($this->previousErrorHandler) { + return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context); + } + } + + /** + * @private + */ + public function handleFatalError() + { + $this->reservedMemory = null; + + $lastError = error_get_last(); + if ($lastError && in_array($lastError['type'], self::$fatalErrors)) { + $this->logger->log( + $this->fatalLevel, + 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'], + array('file' => $lastError['file'], 'line' => $lastError['line']) + ); + } + } + + private static function codeToString($code) + { + switch ($code) { + case E_ERROR: + return 'E_ERROR'; + case E_WARNING: + return 'E_WARNING'; + case E_PARSE: + return 'E_PARSE'; + case E_NOTICE: + return 'E_NOTICE'; + case E_CORE_ERROR: + return 'E_CORE_ERROR'; + case E_CORE_WARNING: + return 'E_CORE_WARNING'; + case E_COMPILE_ERROR: + return 'E_COMPILE_ERROR'; + case E_COMPILE_WARNING: + return 'E_COMPILE_WARNING'; + case E_USER_ERROR: + return 'E_USER_ERROR'; + case E_USER_WARNING: + return 'E_USER_WARNING'; + case E_USER_NOTICE: + return 'E_USER_NOTICE'; + case E_STRICT: + return 'E_STRICT'; + case E_RECOVERABLE_ERROR: + return 'E_RECOVERABLE_ERROR'; + case E_DEPRECATED: + return 'E_DEPRECATED'; + case E_USER_DEPRECATED: + return 'E_USER_DEPRECATED'; + } + + return 'Unknown PHP error'; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php new file mode 100644 index 0000000..56d3e27 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/ChromePHPFormatter.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Formats a log message according to the ChromePHP array format + * + * @author Christophe Coevoet + */ +class ChromePHPFormatter implements FormatterInterface +{ + /** + * Translates Monolog log levels to Wildfire levels. + */ + private $logLevels = array( + Logger::DEBUG => 'log', + Logger::INFO => 'info', + Logger::NOTICE => 'info', + Logger::WARNING => 'warn', + Logger::ERROR => 'error', + Logger::CRITICAL => 'error', + Logger::ALERT => 'error', + Logger::EMERGENCY => 'error', + ); + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $backtrace = 'unknown'; + if (isset($record['extra']['file']) && isset($record['extra']['line'])) { + $backtrace = $record['extra']['file'].' : '.$record['extra']['line']; + unset($record['extra']['file']); + unset($record['extra']['line']); + } + + $message = array('message' => $record['message']); + if ($record['context']) { + $message['context'] = $record['context']; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + } + if (count($message) === 1) { + $message = reset($message); + } + + return array( + $record['channel'], + $message, + $backtrace, + $this->logLevels[$record['level']], + ); + } + + public function formatBatch(array $records) + { + $formatted = array(); + + foreach ($records as $record) { + $formatted[] = $this->format($record); + } + + return $formatted; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php new file mode 100644 index 0000000..b5de751 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/FormatterInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Interface for formatters + * + * @author Jordi Boggiano + */ +interface FormatterInterface +{ + /** + * Formats a log record. + * + * @param array $record A record to format + * @return mixed The formatted record + */ + public function format(array $record); + + /** + * Formats a set of log records. + * + * @param array $records A set of records to format + * @return mixed The formatted set of records + */ + public function formatBatch(array $records); +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php new file mode 100644 index 0000000..aa01f49 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/GelfMessageFormatter.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Gelf\Message; + +/** + * Serializes a log message to GELF + * @see http://www.graylog2.org/about/gelf + * + * @author Matt Lehner + */ +class GelfMessageFormatter extends NormalizerFormatter +{ + /** + * @var string the name of the system for the Gelf log message + */ + protected $systemName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected $contextPrefix; + + /** + * Translates Monolog log levels to Graylog2 log priorities. + */ + private $logLevels = array( + Logger::DEBUG => 7, + Logger::INFO => 6, + Logger::NOTICE => 5, + Logger::WARNING => 4, + Logger::ERROR => 3, + Logger::CRITICAL => 2, + Logger::ALERT => 1, + Logger::EMERGENCY => 0, + ); + + public function __construct($systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_') + { + parent::__construct('U.u'); + + $this->systemName = $systemName ?: gethostname(); + + $this->extraPrefix = $extraPrefix; + $this->contextPrefix = $contextPrefix; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + $message = new Message(); + $message + ->setTimestamp($record['datetime']) + ->setShortMessage((string) $record['message']) + ->setFacility($record['channel']) + ->setHost($this->systemName) + ->setLine(isset($record['extra']['line']) ? $record['extra']['line'] : null) + ->setFile(isset($record['extra']['file']) ? $record['extra']['file'] : null) + ->setLevel($this->logLevels[$record['level']]); + + // Do not duplicate these values in the additional fields + unset($record['extra']['line']); + unset($record['extra']['file']); + + foreach ($record['extra'] as $key => $val) { + $message->setAdditional($this->extraPrefix . $key, is_scalar($val) ? $val : $this->toJson($val)); + } + + foreach ($record['context'] as $key => $val) { + $message->setAdditional($this->contextPrefix . $key, is_scalar($val) ? $val : $this->toJson($val)); + } + + return $message; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php new file mode 100644 index 0000000..822af0e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/JsonFormatter.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Encodes whatever record data is passed to it as json + * + * This can be useful to log to databases or remote APIs + * + * @author Jordi Boggiano + */ +class JsonFormatter implements FormatterInterface +{ + /** + * {@inheritdoc} + */ + public function format(array $record) + { + return json_encode($record); + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + return json_encode($records); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php new file mode 100644 index 0000000..a96fb27 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LineFormatter.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Formats incoming records into a one-line string + * + * This is especially useful for logging to files + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +class LineFormatter extends NormalizerFormatter +{ + const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"; + + protected $format; + + /** + * @param string $format The format of the message + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct($format = null, $dateFormat = null) + { + $this->format = $format ?: static::SIMPLE_FORMAT; + parent::__construct($dateFormat); + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $vars = parent::format($record); + + $output = $this->format; + foreach ($vars['extra'] as $var => $val) { + if (false !== strpos($output, '%extra.'.$var.'%')) { + $output = str_replace('%extra.'.$var.'%', $this->convertToString($val), $output); + unset($vars['extra'][$var]); + } + } + foreach ($vars as $var => $val) { + $output = str_replace('%'.$var.'%', $this->convertToString($val), $output); + } + + return $output; + } + + public function formatBatch(array $records) + { + $message = ''; + foreach ($records as $record) { + $message .= $this->format($record); + } + + return $message; + } + + protected function normalize($data) + { + if (is_bool($data) || is_null($data)) { + return var_export($data, true); + } + + if ($data instanceof \Exception) { + $previousText = ''; + if ($previous = $data->getPrevious()) { + do { + $previousText .= ', '.get_class($previous).': '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine(); + } while ($previous = $previous->getPrevious()); + } + + return '[object] ('.get_class($data).': '.$data->getMessage().' at '.$data->getFile().':'.$data->getLine().$previousText.')'; + } + + return parent::normalize($data); + } + + protected function convertToString($data) + { + if (null === $data || is_scalar($data)) { + return (string) $data; + } + + $data = $this->normalize($data); + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return $this->toJson($data); + } + + return str_replace('\\/', '/', json_encode($data)); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php new file mode 100644 index 0000000..7aa8ad3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/LogstashFormatter.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * Serializes a log message to Logstash Event Format + * + * @see http://logstash.net/ + * @see https://github.com/logstash/logstash/blob/master/lib/logstash/event.rb + * + * @author Tim Mower + */ +class LogstashFormatter extends NormalizerFormatter +{ + /** + * @var string the name of the system for the Logstash log message, used to fill the @source field + */ + protected $systemName; + + /** + * @var string an application name for the Logstash log message, used to fill the @type field + */ + protected $applicationName; + + /** + * @var string a prefix for 'extra' fields from the Monolog record (optional) + */ + protected $extraPrefix; + + /** + * @var string a prefix for 'context' fields from the Monolog record (optional) + */ + protected $contextPrefix; + + /** + * @param string $applicationName the application that sends the data, used as the "type" field of logstash + * @param string $systemName the system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine + * @param string $extraPrefix prefix for extra keys inside logstash "fields" + * @param string $contextPrefix prefix for context keys inside logstash "fields", defaults to ctxt_ + */ + public function __construct($applicationName, $systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_') + { + //log stash requires a ISO 8601 format date + parent::__construct('c'); + + $this->systemName = $systemName ?: gethostname(); + $this->applicationName = $applicationName; + + $this->extraPrefix = $extraPrefix; + $this->contextPrefix = $contextPrefix; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + $record = parent::format($record); + $message = array( + '@timestamp' => $record['datetime'], + '@message' => $record['message'], + '@tags' => array($record['channel']), + '@source' => $this->systemName + ); + + if ($this->applicationName) { + $message['@type'] = $this->applicationName; + } + $message['@fields'] = array(); + $message['@fields']['channel'] = $record['channel']; + $message['@fields']['level'] = $record['level']; + + if (isset($record['extra']['server'])) { + $message['@source_host'] = $record['extra']['server']; + } + if (isset($record['extra']['url'])) { + $message['@source_path'] = $record['extra']['url']; + } + foreach ($record['extra'] as $key => $val) { + $message['@fields'][$this->extraPrefix . $key] = $val; + } + + foreach ($record['context'] as $key => $val) { + $message['@fields'][$this->contextPrefix . $key] = $val; + } + + return json_encode($message) . "\n"; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php new file mode 100644 index 0000000..765fed4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/NormalizerFormatter.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Exception; + +/** + * Normalizes incoming records to remove objects/resources so it's easier to dump to various targets + * + * @author Jordi Boggiano + */ +class NormalizerFormatter implements FormatterInterface +{ + const SIMPLE_DATE = "Y-m-d H:i:s"; + + protected $dateFormat; + + /** + * @param string $dateFormat The format of the timestamp: one supported by DateTime::format + */ + public function __construct($dateFormat = null) + { + $this->dateFormat = $dateFormat ?: static::SIMPLE_DATE; + } + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + return $this->normalize($record); + } + + /** + * {@inheritdoc} + */ + public function formatBatch(array $records) + { + foreach ($records as $key => $record) { + $records[$key] = $this->format($record); + } + + return $records; + } + + protected function normalize($data) + { + if (null === $data || is_scalar($data)) { + return $data; + } + + if (is_array($data) || $data instanceof \Traversable) { + $normalized = array(); + + $count = 1; + foreach ($data as $key => $value) { + if ($count++ >= 1000) { + $normalized['...'] = 'Over 1000 items, aborting normalization'; + break; + } + $normalized[$key] = $this->normalize($value); + } + + return $normalized; + } + + if ($data instanceof \DateTime) { + return $data->format($this->dateFormat); + } + + if (is_object($data)) { + if ($data instanceof Exception) { + return $this->normalizeException($data); + } + + return sprintf("[object] (%s: %s)", get_class($data), $this->toJson($data, true)); + } + + if (is_resource($data)) { + return '[resource]'; + } + + return '[unknown('.gettype($data).')]'; + } + + protected function normalizeException(Exception $e) + { + $data = array( + 'class' => get_class($e), + 'message' => $e->getMessage(), + 'file' => $e->getFile().':'.$e->getLine(), + ); + + $trace = $e->getTrace(); + array_shift($trace); + foreach ($trace as $frame) { + if (isset($frame['file'])) { + $data['trace'][] = $frame['file'].':'.$frame['line']; + } else { + $data['trace'][] = json_encode($frame); + } + } + + if ($previous = $e->getPrevious()) { + $data['previous'] = $this->normalizeException($previous); + } + + return $data; + } + + protected function toJson($data, $ignoreErrors = false) + { + // suppress json_encode errors since it's twitchy with some inputs + if ($ignoreErrors) { + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return @json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + return @json_encode($data); + } + + if (version_compare(PHP_VERSION, '5.4.0', '>=')) { + return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + return json_encode($data); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php new file mode 100644 index 0000000..b3e9b18 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Formatter/WildfireFormatter.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +/** + * Serializes a log message according to Wildfire's header requirements + * + * @author Eric Clemmons (@ericclemmons) + * @author Christophe Coevoet + * @author Kirill chEbba Chebunin + */ +class WildfireFormatter extends NormalizerFormatter +{ + /** + * Translates Monolog log levels to Wildfire levels. + */ + private $logLevels = array( + Logger::DEBUG => 'LOG', + Logger::INFO => 'INFO', + Logger::NOTICE => 'INFO', + Logger::WARNING => 'WARN', + Logger::ERROR => 'ERROR', + Logger::CRITICAL => 'ERROR', + Logger::ALERT => 'ERROR', + Logger::EMERGENCY => 'ERROR', + ); + + /** + * {@inheritdoc} + */ + public function format(array $record) + { + // Retrieve the line and file if set and remove them from the formatted extra + $file = $line = ''; + if (isset($record['extra']['file'])) { + $file = $record['extra']['file']; + unset($record['extra']['file']); + } + if (isset($record['extra']['line'])) { + $line = $record['extra']['line']; + unset($record['extra']['line']); + } + + $record = $this->normalize($record); + $message = array('message' => $record['message']); + $handleError = false; + if ($record['context']) { + $message['context'] = $record['context']; + $handleError = true; + } + if ($record['extra']) { + $message['extra'] = $record['extra']; + $handleError = true; + } + if (count($message) === 1) { + $message = reset($message); + } + + // Create JSON object describing the appearance of the message in the console + $json = $this->toJson(array( + array( + 'Type' => $this->logLevels[$record['level']], + 'File' => $file, + 'Line' => $line, + 'Label' => $record['channel'], + ), + $message, + ), $handleError); + + // The message itself is a serialization of the above JSON object + it's length + return sprintf( + '%s|%s|', + strlen($json), + $json + ); + } + + public function formatBatch(array $records) + { + throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter'); + } + + protected function normalize($data) + { + if (is_object($data) && !$data instanceof \DateTime) { + return $data; + } + + return parent::normalize($data); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php new file mode 100644 index 0000000..81fc7a3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractHandler.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\FormatterInterface; +use Monolog\Formatter\LineFormatter; + +/** + * Base Handler class providing the Handler structure + * + * @author Jordi Boggiano + */ +abstract class AbstractHandler implements HandlerInterface +{ + protected $level = Logger::DEBUG; + protected $bubble = true; + + /** + * @var FormatterInterface + */ + protected $formatter; + protected $processors = array(); + + /** + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + $this->level = $level; + $this->bubble = $bubble; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return $record['level'] >= $this->level; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + foreach ($records as $record) { + $this->handle($record); + } + } + + /** + * Closes the handler. + * + * This will be called automatically when the object is destroyed + */ + public function close() + { + } + + /** + * {@inheritdoc} + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + if (!$this->formatter) { + $this->formatter = $this->getDefaultFormatter(); + } + + return $this->formatter; + } + + /** + * Sets minimum logging level at which this handler will be triggered. + * + * @param integer $level + * @return self + */ + public function setLevel($level) + { + $this->level = $level; + + return $this; + } + + /** + * Gets minimum logging level at which this handler will be triggered. + * + * @return integer + */ + public function getLevel() + { + return $this->level; + } + + /** + * Sets the bubbling behavior. + * + * @param Boolean $bubble true means that this handler allows bubbling. + * false means that bubbling is not permitted. + * @return self + */ + public function setBubble($bubble) + { + $this->bubble = $bubble; + + return $this; + } + + /** + * Gets the bubbling behavior. + * + * @return Boolean true means that this handler allows bubbling. + * false means that bubbling is not permitted. + */ + public function getBubble() + { + return $this->bubble; + } + + public function __destruct() + { + try { + $this->close(); + } catch (\Exception $e) { + // do nothing + } + } + + /** + * Gets the default formatter. + * + * @return FormatterInterface + */ + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php new file mode 100644 index 0000000..e1e5b89 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AbstractProcessingHandler.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Base Handler class providing the Handler structure + * + * Classes extending it should (in most cases) only implement write($record) + * + * @author Jordi Boggiano + * @author Christophe Coevoet + */ +abstract class AbstractProcessingHandler extends AbstractHandler +{ + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + $record = $this->processRecord($record); + + $record['formatted'] = $this->getFormatter()->format($record); + + $this->write($record); + + return false === $this->bubble; + } + + /** + * Writes the record down to the log of the implementing handler + * + * @param array $record + * @return void + */ + abstract protected function write(array $record); + + /** + * Processes a record. + * + * @param array $record + * @return array + */ + protected function processRecord(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php new file mode 100644 index 0000000..0070343 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/AmqpHandler.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\JsonFormatter; + +class AmqpHandler extends AbstractProcessingHandler +{ + /** + * @var \AMQPExchange $exchange + */ + protected $exchange; + + /** + * @param \AMQPExchange $exchange AMQP exchange, ready for use + * @param string $exchangeName + * @param int $level + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(\AMQPExchange $exchange, $exchangeName = 'log', $level = Logger::DEBUG, $bubble = true) + { + $this->exchange = $exchange; + $this->exchange->setName($exchangeName); + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $data = $record["formatted"]; + + $routingKey = sprintf( + '%s.%s', + substr($record['level_name'], 0, 4), + $record['channel'] + ); + + $this->exchange->publish( + $data, + strtolower($routingKey), + 0, + array( + 'delivery_mode' => 2, + 'Content-type' => 'application/json' + ) + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new JsonFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php new file mode 100644 index 0000000..6a80273 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/BufferHandler.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Buffers all records until closing the handler and then pass them as batch. + * + * This is useful for a MailHandler to send only one mail per request instead of + * sending one per log message. + * + * @author Christophe Coevoet + */ +class BufferHandler extends AbstractHandler +{ + protected $handler; + protected $bufferSize = 0; + protected $bufferLimit; + protected $flushOnOverflow; + protected $buffer = array(); + + /** + * @param HandlerInterface $handler Handler. + * @param integer $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded + */ + public function __construct(HandlerInterface $handler, $bufferLimit = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false) + { + parent::__construct($level, $bubble); + $this->handler = $handler; + $this->bufferLimit = (int) $bufferLimit; + $this->flushOnOverflow = $flushOnOverflow; + + // __destructor() doesn't get called on Fatal errors + register_shutdown_function(array($this, 'close')); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) { + if ($this->flushOnOverflow) { + $this->flush(); + } else { + array_shift($this->buffer); + $this->bufferSize--; + } + } + + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + $this->buffer[] = $record; + $this->bufferSize++; + + return false === $this->bubble; + } + + public function flush() + { + if ($this->bufferSize === 0) { + return; + } + + $this->handler->handleBatch($this->buffer); + $this->bufferSize = 0; + $this->buffer = array(); + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->flush(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php new file mode 100644 index 0000000..705400b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ChromePHPHandler.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\ChromePHPFormatter; +use Monolog\Logger; + +/** + * Handler sending logs to the ChromePHP extension (http://www.chromephp.com/) + * + * @author Christophe Coevoet + */ +class ChromePHPHandler extends AbstractProcessingHandler +{ + /** + * Version of the extension + */ + const VERSION = '4.0'; + + /** + * Header name + */ + const HEADER_NAME = 'X-ChromeLogger-Data'; + + protected static $initialized = false; + + /** + * Tracks whether we sent too much data + * + * Chrome limits the headers to 256KB, so when we sent 240KB we stop sending + * + * @var Boolean + */ + protected static $overflowed = false; + + protected static $json = array( + 'version' => self::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array(), + ); + + protected static $sendHeaders = true; + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $messages = array(); + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + $messages[] = $this->processRecord($record); + } + + if (!empty($messages)) { + $messages = $this->getFormatter()->formatBatch($messages); + self::$json['rows'] = array_merge(self::$json['rows'], $messages); + $this->send(); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new ChromePHPFormatter(); + } + + /** + * Creates & sends header for a record + * + * @see sendHeader() + * @see send() + * @param array $record + */ + protected function write(array $record) + { + self::$json['rows'][] = $record['formatted']; + + $this->send(); + } + + /** + * Sends the log header + * + * @see sendHeader() + */ + protected function send() + { + if (self::$overflowed) { + return; + } + + if (!self::$initialized) { + self::$sendHeaders = $this->headersAccepted(); + self::$json['request_uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; + + self::$initialized = true; + } + + $json = @json_encode(self::$json); + $data = base64_encode(utf8_encode($json)); + if (strlen($data) > 240*1024) { + self::$overflowed = true; + + $record = array( + 'message' => 'Incomplete logs, chrome header size limit reached', + 'context' => array(), + 'level' => Logger::WARNING, + 'level_name' => Logger::getLevelName(Logger::WARNING), + 'channel' => 'monolog', + 'datetime' => new \DateTime(), + 'extra' => array(), + ); + self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record); + $json = @json_encode(self::$json); + $data = base64_encode(utf8_encode($json)); + } + + $this->sendHeader(self::HEADER_NAME, $data); + } + + /** + * Send header string to the client + * + * @param string $header + * @param string $content + */ + protected function sendHeader($header, $content) + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Verifies if the headers are accepted by the current user agent + * + * @return Boolean + */ + protected function headersAccepted() + { + return !isset($_SERVER['HTTP_USER_AGENT']) + || preg_match('{\bChrome/\d+[\.\d+]*\b}', $_SERVER['HTTP_USER_AGENT']); + } + + /** + * BC getter for the sendHeaders property that has been made static + */ + public function __get($property) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + return static::$sendHeaders; + } + + /** + * BC setter for the sendHeaders property that has been made static + */ + public function __set($property, $value) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + static::$sendHeaders = $value; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php new file mode 100644 index 0000000..4877b34 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/CouchDBHandler.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\JsonFormatter; +use Monolog\Logger; + +/** + * CouchDB handler + * + * @author Markus Bachmann + */ +class CouchDBHandler extends AbstractProcessingHandler +{ + private $options; + + public function __construct(array $options = array(), $level = Logger::DEBUG, $bubble = true) + { + $this->options = array_merge(array( + 'host' => 'localhost', + 'port' => 5984, + 'dbname' => 'logger', + 'username' => null, + 'password' => null, + ), $options); + + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $basicAuth = null; + if ($this->options['username']) { + $basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']); + } + + $url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname']; + $context = stream_context_create(array( + 'http' => array( + 'method' => 'POST', + 'content' => $record['formatted'], + 'ignore_errors' => true, + 'max_redirects' => 0, + 'header' => 'Content-type: application/json', + ) + )); + + if (false === @file_get_contents($url, null, $context)) { + throw new \RuntimeException(sprintf('Could not connect to %s', $url)); + } + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new JsonFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php new file mode 100644 index 0000000..0b2f21b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/CubeHandler.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Logs to Cube. + * + * @link http://square.github.com/cube/ + * @author Wan Chen + */ +class CubeHandler extends AbstractProcessingHandler +{ + private $udpConnection = null; + private $httpConnection = null; + private $scheme = null; + private $host = null; + private $port = null; + private $acceptedSchemes = array('http', 'udp'); + + /** + * Create a Cube handler + * + * @throws UnexpectedValueException when given url is not a valid url. + * A valid url must consists of three parts : protocol://host:port + * Only valid protocol used by Cube are http and udp + */ + public function __construct($url, $level = Logger::DEBUG, $bubble = true) + { + $urlInfos = parse_url($url); + + if (!isset($urlInfos['scheme']) || !isset($urlInfos['host']) || !isset($urlInfos['port'])) { + throw new \UnexpectedValueException('URL "'.$url.'" is not valid'); + } + + if (!in_array($urlInfos['scheme'], $this->acceptedSchemes)) { + throw new \UnexpectedValueException( + 'Invalid protocol (' . $urlInfos['scheme'] . ').' + . ' Valid options are ' . implode(', ', $this->acceptedSchemes)); + } + + $this->scheme = $urlInfos['scheme']; + $this->host = $urlInfos['host']; + $this->port = $urlInfos['port']; + + parent::__construct($level, $bubble); + } + + /** + * Establish a connection to an UDP socket + * + * @throws LogicException when unable to connect to the socket + */ + protected function connectUdp() + { + if (!extension_loaded('sockets')) { + throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler'); + } + + $this->udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); + if (!$this->udpConnection) { + throw new \LogicException('Unable to create a socket'); + } + + if (!socket_connect($this->udpConnection, $this->host, $this->port)) { + throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port); + } + } + + /** + * Establish a connection to a http server + */ + protected function connectHttp() + { + if (!extension_loaded('curl')) { + throw new \LogicException('The curl extension is needed to use http URLs with the CubeHandler'); + } + + $this->httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); + + if (!$this->httpConnection) { + throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port); + } + + curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST"); + curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $date = $record['datetime']; + + $data = array('time' => $date->format('Y-m-d\TH:i:s.u')); + unset($record['datetime']); + + if (isset($record['context']['type'])) { + $data['type'] = $record['context']['type']; + unset($record['context']['type']); + } else { + $data['type'] = $record['channel']; + } + + $data['data'] = $record['context']; + $data['data']['level'] = $record['level']; + + $this->{'write'.$this->scheme}(json_encode($data)); + } + + private function writeUdp($data) + { + if (!$this->udpConnection) { + $this->connectUdp(); + } + + socket_send($this->udpConnection, $data, strlen($data), 0); + } + + private function writeHttp($data) + { + if (!$this->httpConnection) { + $this->connectHttp(); + } + + curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']'); + curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array( + 'Content-Type: application/json', + 'Content-Length: ' . strlen('['.$data.']')) + ); + + return curl_exec($this->httpConnection); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php new file mode 100644 index 0000000..b91ffec --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/DoctrineCouchDBHandler.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; +use Doctrine\CouchDB\CouchDBClient; + +/** + * CouchDB handler for Doctrine CouchDB ODM + * + * @author Markus Bachmann + */ +class DoctrineCouchDBHandler extends AbstractProcessingHandler +{ + private $client; + + public function __construct(CouchDBClient $client, $level = Logger::DEBUG, $bubble = true) + { + $this->client = $client; + parent::__construct($level, $bubble); + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + $this->client->postDocument($record['formatted']); + } + + protected function getDefaultFormatter() + { + return new NormalizerFormatter; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php new file mode 100644 index 0000000..9e11c3b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ErrorLogHandler.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to PHP error_log() handler. + * + * @author Elan Ruusamäe + */ +class ErrorLogHandler extends AbstractProcessingHandler +{ + const OPERATING_SYSTEM = 0; + const SAPI = 4; + + protected $messageType; + + /** + * @param integer $messageType Says where the error should go. + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($messageType = self::OPERATING_SYSTEM, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + if (false === in_array($messageType, self::getAvailableTypes())) { + $message = sprintf('The given message type "%s" is not supported', print_r($messageType, true)); + throw new \InvalidArgumentException($message); + } + + $this->messageType = $messageType; + } + + /** + * @return array With all available types + */ + public static function getAvailableTypes() + { + return array( + self::OPERATING_SYSTEM, + self::SAPI, + ); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + error_log((string) $record['formatted'], $this->messageType); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php new file mode 100644 index 0000000..c3e42ef --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ActivationStrategyInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +/** + * Interface for activation strategies for the FingersCrossedHandler. + * + * @author Johannes M. Schmitt + */ +interface ActivationStrategyInterface +{ + /** + * Returns whether the given record activates the handler. + * + * @param array $record + * @return Boolean + */ + public function isHandlerActivated(array $record); +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php new file mode 100644 index 0000000..646d57a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ChannelLevelActivationStrategy.php @@ -0,0 +1,57 @@ + +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code. +*/ + +namespace Monolog\Handler\FingersCrossed; + +/** + * Channel and Error level based monolog activation strategy. Allows to trigger activation + * based on level per channel. e.g. trigger activation on level 'ERROR' by default, except + * for records of the 'sql' channel; those should trigger activation on level 'WARN'. + * + * Example: + * + * + * $activationStrategy = new ChannelLevelActivationStrategy( + * Logger::CRITICAL, + * array( + * 'request' => Logger::ALERT, + * 'sensitive' => Logger::ERROR, + * ) + * ); + * $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy); + * + * + * @author Mike Meessen + */ +class ChannelLevelActivationStrategy implements ActivationStrategyInterface +{ + private $defaultActionLevel; + private $channelToActionLevel; + + /** + * @param int $defaultActionLevel The default action level to be used if the record's category doesn't match any + * @param array $categoryToActionLevel An array that maps channel names to action levels. + */ + public function __construct($defaultActionLevel, $channelToActionLevel = array()) + { + $this->defaultActionLevel = $defaultActionLevel; + $this->channelToActionLevel = $channelToActionLevel; + } + + public function isHandlerActivated(array $record) + { + if (isset($this->channelToActionLevel[$record['channel']])) { + return $record['level'] >= $this->channelToActionLevel[$record['channel']]; + } + + return $record['level'] >= $this->defaultActionLevel; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php new file mode 100644 index 0000000..7cd8ef1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossed/ErrorLevelActivationStrategy.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\FingersCrossed; + +/** + * Error level based activation strategy. + * + * @author Johannes M. Schmitt + */ +class ErrorLevelActivationStrategy implements ActivationStrategyInterface +{ + private $actionLevel; + + public function __construct($actionLevel) + { + $this->actionLevel = $actionLevel; + } + + public function isHandlerActivated(array $record) + { + return $record['level'] >= $this->actionLevel; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php new file mode 100644 index 0000000..bc51e4e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FingersCrossedHandler.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Handler\FingersCrossed\ActivationStrategyInterface; +use Monolog\Logger; + +/** + * Buffers all records until a certain level is reached + * + * The advantage of this approach is that you don't get any clutter in your log files. + * Only requests which actually trigger an error (or whatever your actionLevel is) will be + * in the logs, but they will contain all records, not only those above the level threshold. + * + * You can find the various activation strategies in the + * Monolog\Handler\FingersCrossed\ namespace. + * + * @author Jordi Boggiano + */ +class FingersCrossedHandler extends AbstractHandler +{ + protected $handler; + protected $activationStrategy; + protected $buffering = true; + protected $bufferSize; + protected $buffer = array(); + protected $stopBuffering; + + /** + * @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler). + * @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action + * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $stopBuffering Whether the handler should stop buffering after being triggered (default true) + */ + public function __construct($handler, $activationStrategy = null, $bufferSize = 0, $bubble = true, $stopBuffering = true) + { + if (null === $activationStrategy) { + $activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING); + } + + // convert simple int activationStrategy to an object + if (!$activationStrategy instanceof ActivationStrategyInterface) { + $activationStrategy = new ErrorLevelActivationStrategy($activationStrategy); + } + + $this->handler = $handler; + $this->activationStrategy = $activationStrategy; + $this->bufferSize = $bufferSize; + $this->bubble = $bubble; + $this->stopBuffering = $stopBuffering; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + if ($this->buffering) { + $this->buffer[] = $record; + if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) { + array_shift($this->buffer); + } + if ($this->activationStrategy->isHandlerActivated($record)) { + if ($this->stopBuffering) { + $this->buffering = false; + } + if (!$this->handler instanceof HandlerInterface) { + if (!is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } + $this->handler = call_user_func($this->handler, $record, $this); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } + } + $this->handler->handleBatch($this->buffer); + $this->buffer = array(); + } + } else { + $this->handler->handle($record); + } + + return false === $this->bubble; + } + + /** + * Resets the state of the handler. Stops forwarding records to the wrapped handler. + */ + public function reset() + { + $this->buffering = true; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php new file mode 100644 index 0000000..46a039a --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/FirePHPHandler.php @@ -0,0 +1,184 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\WildfireFormatter; + +/** + * Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol. + * + * @author Eric Clemmons (@ericclemmons) + */ +class FirePHPHandler extends AbstractProcessingHandler +{ + /** + * WildFire JSON header message format + */ + const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2'; + + /** + * FirePHP structure for parsing messages & their presentation + */ + const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1'; + + /** + * Must reference a "known" plugin, otherwise headers won't display in FirePHP + */ + const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3'; + + /** + * Header prefix for Wildfire to recognize & parse headers + */ + const HEADER_PREFIX = 'X-Wf'; + + /** + * Whether or not Wildfire vendor-specific headers have been generated & sent yet + */ + protected static $initialized = false; + + /** + * Shared static message index between potentially multiple handlers + * @var int + */ + protected static $messageIndex = 1; + + protected static $sendHeaders = true; + + /** + * Base header creation function used by init headers & record headers + * + * @param array $meta Wildfire Plugin, Protocol & Structure Indexes + * @param string $message Log message + * @return array Complete header string ready for the client as key and message as value + */ + protected function createHeader(array $meta, $message) + { + $header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta)); + + return array($header => $message); + } + + /** + * Creates message header from record + * + * @see createHeader() + * @param array $record + * @return string + */ + protected function createRecordHeader(array $record) + { + // Wildfire is extensible to support multiple protocols & plugins in a single request, + // but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake. + return $this->createHeader( + array(1, 1, 1, self::$messageIndex++), + $record['formatted'] + ); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new WildfireFormatter(); + } + + /** + * Wildfire initialization headers to enable message parsing + * + * @see createHeader() + * @see sendHeader() + * @return array + */ + protected function getInitHeaders() + { + // Initial payload consists of required headers for Wildfire + return array_merge( + $this->createHeader(array('Protocol', 1), self::PROTOCOL_URI), + $this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI), + $this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI) + ); + } + + /** + * Send header string to the client + * + * @param string $header + * @param string $content + */ + protected function sendHeader($header, $content) + { + if (!headers_sent() && self::$sendHeaders) { + header(sprintf('%s: %s', $header, $content)); + } + } + + /** + * Creates & sends header for a record, ensuring init headers have been sent prior + * + * @see sendHeader() + * @see sendInitHeaders() + * @param array $record + */ + protected function write(array $record) + { + // WildFire-specific headers must be sent prior to any messages + if (!self::$initialized) { + self::$sendHeaders = $this->headersAccepted(); + + foreach ($this->getInitHeaders() as $header => $content) { + $this->sendHeader($header, $content); + } + + self::$initialized = true; + } + + $header = $this->createRecordHeader($record); + $this->sendHeader(key($header), current($header)); + } + + /** + * Verifies if the headers are accepted by the current user agent + * + * @return Boolean + */ + protected function headersAccepted() + { + return !isset($_SERVER['HTTP_USER_AGENT']) + || preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT']) + || isset($_SERVER['HTTP_X_FIREPHP_VERSION']); + } + + /** + * BC getter for the sendHeaders property that has been made static + */ + public function __get($property) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + return static::$sendHeaders; + } + + /** + * BC setter for the sendHeaders property that has been made static + */ + public function __set($property, $value) + { + if ('sendHeaders' !== $property) { + throw new \InvalidArgumentException('Undefined property '.$property); + } + + static::$sendHeaders = $value; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php new file mode 100644 index 0000000..34d48e7 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/GelfHandler.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\IMessagePublisher; +use Monolog\Logger; +use Monolog\Handler\AbstractProcessingHandler; +use Monolog\Formatter\GelfMessageFormatter; + +/** + * Handler to send messages to a Graylog2 (http://www.graylog2.org) server + * + * @author Matt Lehner + */ +class GelfHandler extends AbstractProcessingHandler +{ + /** + * @var Gelf\IMessagePublisher the publisher object that sends the message to the server + */ + protected $publisher; + + /** + * @param Gelf\IMessagePublisher $publisher a publisher object + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(IMessagePublisher $publisher, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->publisher = $publisher; + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->publisher = null; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->publisher->publish($record['formatted']); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new GelfMessageFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php new file mode 100644 index 0000000..99384d3 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/GroupHandler.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Forwards records to multiple handlers + * + * @author Lenar Lõhmus + */ +class GroupHandler extends AbstractHandler +{ + protected $handlers; + + /** + * @param array $handlers Array of Handlers. + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(array $handlers, $bubble = true) + { + foreach ($handlers as $handler) { + if (!$handler instanceof HandlerInterface) { + throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.'); + } + } + + $this->handlers = $handlers; + $this->bubble = $bubble; + } + + /** + * {@inheritdoc} + */ + public function isHandling(array $record) + { + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($this->processors) { + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + } + + foreach ($this->handlers as $handler) { + $handler->handle($record); + } + + return false === $this->bubble; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + foreach ($this->handlers as $handler) { + $handler->handleBatch($records); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php new file mode 100644 index 0000000..4e7d639 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HandlerInterface.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\FormatterInterface; + +/** + * Interface that all Monolog Handlers must implement + * + * @author Jordi Boggiano + */ +interface HandlerInterface +{ + /** + * Checks whether the given record will be handled by this handler. + * + * This is mostly done for performance reasons, to avoid calling processors for nothing. + * + * Handlers should still check the record levels within handle(), returning false in isHandling() + * is no guarantee that handle() will not be called, and isHandling() might not be called + * for a given record. + * + * @param array $record + * + * @return Boolean + */ + public function isHandling(array $record); + + /** + * Handles a record. + * + * All records may be passed to this method, and the handler should discard + * those that it does not want to handle. + * + * The return value of this function controls the bubbling process of the handler stack. + * Unless the bubbling is interrupted (by returning true), the Logger class will keep on + * calling further handlers in the stack with a given log record. + * + * @param array $record The record to handle + * @return Boolean true means that this handler handled the record, and that bubbling is not permitted. + * false means the record was either not processed or that this handler allows bubbling. + */ + public function handle(array $record); + + /** + * Handles a set of records at once. + * + * @param array $records The records to handle (an array of record arrays) + */ + public function handleBatch(array $records); + + /** + * Adds a processor in the stack. + * + * @param callable $callback + * @return self + */ + public function pushProcessor($callback); + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + public function popProcessor(); + + /** + * Sets the formatter. + * + * @param FormatterInterface $formatter + * @return self + */ + public function setFormatter(FormatterInterface $formatter); + + /** + * Gets the formatter. + * + * @return FormatterInterface + */ + public function getFormatter(); +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php new file mode 100644 index 0000000..e1402a0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/HipChatHandler.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through the hipchat api to a hipchat room + * + * Notes: + * API token - HipChat API token + * Room - HipChat Room Id or name, where messages are sent + * Name - Name used to send the message (from) + * notify - Should the message trigger a notification in the clients + * + * @author Rafael Dohms + * @see https://www.hipchat.com/docs/api + */ +class HipChatHandler extends SocketHandler +{ + /** + * @var string + */ + private $token; + + /** + * @var array + */ + private $room; + + /** + * @var string + */ + private $name; + + /** + * @var boolean + */ + private $notify; + + /** + * @param string $token HipChat API Token + * @param string $room The room that should be alerted of the message (Id or Name) + * @param string $name Name used in the "from" field + * @param bool $notify Trigger a notification in clients or not + * @param int $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $useSSL Whether to connect via SSL. + */ + public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true) + { + $connectionString = $useSSL ? 'ssl://api.hipchat.com:443' : 'api.hipchat.com:80'; + parent::__construct($connectionString, $level, $bubble); + + $this->token = $token; + $this->name = $name; + $this->notify = $notify; + $this->room = $room; + } + + /** + * {@inheritdoc} + * + * @param array $record + * @return string + */ + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + /** + * Builds the body of API call + * + * @param array $record + * @return string + */ + private function buildContent($record) + { + $dataArray = array( + 'from' => $this->name, + 'room_id' => $this->room, + 'notify' => $this->notify, + 'message' => $record['formatted'], + 'message_format' => 'text', + 'color' => $this->getAlertColor($record['level']), + ); + + return http_build_query($dataArray); + } + + /** + * Builds the header of the API Call + * + * @param string $content + * @return string + */ + private function buildHeader($content) + { + $header = "POST /v1/rooms/message?format=json&auth_token=".$this->token." HTTP/1.1\r\n"; + $header .= "Host: api.hipchat.com\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + /** + * Assigns a color to each level of log records. + * + * @param integer $level + * @return string + */ + protected function getAlertColor($level) + { + switch (true) { + case $level >= Logger::ERROR: + return 'red'; + case $level >= Logger::WARNING: + return 'yellow'; + case $level >= Logger::INFO: + return 'green'; + case $level == Logger::DEBUG: + return 'gray'; + default: + return 'yellow'; + } + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + public function write(array $record) + { + parent::write($record); + $this->closeSocket(); + } + +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php new file mode 100644 index 0000000..8629272 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MailHandler.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Base class for all mail handlers + * + * @author Gyula Sallai + */ +abstract class MailHandler extends AbstractProcessingHandler +{ + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $messages = array(); + + foreach ($records as $record) { + if ($record['level'] < $this->level) { + continue; + } + $messages[] = $this->processRecord($record); + } + + if (!empty($messages)) { + $this->send((string) $this->getFormatter()->formatBatch($messages), $messages); + } + } + + /** + * Send a mail with the given content + * + * @param string $content + * @param array $records the array of log records that formed this content + */ + abstract protected function send($content, array $records); + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->send((string) $record['formatted'], array($record)); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php new file mode 100644 index 0000000..0cb21cd --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MissingExtensionException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +/** + * Exception can be thrown if an extension for an handler is missing + * + * @author Christian Bergau + */ +class MissingExtensionException extends \Exception +{ + +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php new file mode 100644 index 0000000..5a59201 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/MongoDBHandler.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\NormalizerFormatter; + +/** + * Logs to a MongoDB database. + * + * usage example: + * + * $log = new Logger('application'); + * $mongodb = new MongoDBHandler(new \Mongo("mongodb://localhost:27017"), "logs", "prod"); + * $log->pushHandler($mongodb); + * + * @author Thomas Tourlourat + */ +class MongoDBHandler extends AbstractProcessingHandler +{ + private $mongoCollection; + + public function __construct($mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true) + { + if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo)) { + throw new \InvalidArgumentException('MongoClient or Mongo instance required'); + } + + $this->mongoCollection = $mongo->selectCollection($database, $collection); + + parent::__construct($level, $bubble); + } + + protected function write(array $record) + { + $this->mongoCollection->save($record["formatted"]); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new NormalizerFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php new file mode 100644 index 0000000..3033c0b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NativeMailerHandler.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * NativeMailerHandler uses the mail() function to send the emails + * + * @author Christophe Coevoet + */ +class NativeMailerHandler extends MailHandler +{ + protected $to; + protected $subject; + protected $headers = array( + 'Content-type: text/plain; charset=utf-8' + ); + protected $maxColumnWidth; + + /** + * @param string|array $to The receiver of the mail + * @param string $subject The subject of the mail + * @param string $from The sender of the mail + * @param integer $level The minimum logging level at which this handler will be triggered + * @param boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param int $maxColumnWidth The maximum column width that the message lines will have + */ + public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubble = true, $maxColumnWidth = 70) + { + parent::__construct($level, $bubble); + $this->to = is_array($to) ? $to : array($to); + $this->subject = $subject; + $this->addHeader(sprintf('From: %s', $from)); + $this->maxColumnWidth = $maxColumnWidth; + } + + /** + * @param string|array $headers Custom added headers + */ + public function addHeader($headers) + { + foreach ((array) $headers as $header) { + if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) { + throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons'); + } + $this->headers[] = $header; + } + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $content = wordwrap($content, $this->maxColumnWidth); + $headers = implode("\r\n", $this->headers) . "\r\n"; + foreach ($this->to as $to) { + mail($to, $this->subject, $content, $headers); + } + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php new file mode 100644 index 0000000..757baa8 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NewRelicHandler.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Class to record a log on a NewRelic application + * + * @see https://newrelic.com/docs/php/new-relic-for-php + */ +class NewRelicHandler extends AbstractProcessingHandler +{ + /** + * Name of the New Relic application that will receive logs from this handler. + * + * @var string + */ + protected $appName; + + /** + * {@inheritDoc} + * + * @param string $appName + */ + public function __construct($level = Logger::ERROR, $bubble = true, $appName = null) + { + parent::__construct($level, $bubble); + + $this->appName = $appName; + } + + /** + * {@inheritDoc} + */ + protected function write(array $record) + { + if (!$this->isNewRelicEnabled()) { + throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler'); + } + + if ($appName = $this->getAppName($record['context'])) { + $this->setNewRelicAppName($appName); + } + + if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { + newrelic_notice_error($record['message'], $record['context']['exception']); + unset($record['context']['exception']); + } else { + newrelic_notice_error($record['message']); + } + + foreach ($record['context'] as $key => $parameter) { + newrelic_add_custom_parameter($key, $parameter); + } + } + + /** + * Checks whether the NewRelic extension is enabled in the system. + * + * @return bool + */ + protected function isNewRelicEnabled() + { + return extension_loaded('newrelic'); + } + + /** + * Returns the appname where this log should be sent. Each log can override the default appname, set in this + * handler's constructor, by providing the appname in its context. + * + * @param array $context + * @return null|string + */ + protected function getAppName(array $context) + { + if (isset($context['appname'])) { + return $context['appname']; + } + + return $this->appName; + } + + /** + * Sets the NewRelic application that should receive this log. + * + * @param string $appName + */ + protected function setNewRelicAppName($appName) + { + newrelic_set_appname($appName); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php new file mode 100644 index 0000000..3754e45 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/NullHandler.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Blackhole + * + * Any record it can handle will be thrown away. This can be used + * to put on top of an existing stack to override it temporarily. + * + * @author Jordi Boggiano + */ +class NullHandler extends AbstractHandler +{ + /** + * @param integer $level The minimum logging level at which this handler will be triggered + */ + public function __construct($level = Logger::DEBUG) + { + parent::__construct($level, false); + } + + /** + * {@inheritdoc} + */ + public function handle(array $record) + { + if ($record['level'] < $this->level) { + return false; + } + + return true; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php new file mode 100644 index 0000000..9408a03 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/PushoverHandler.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through the pushover api to mobile phones + * + * @author Sebastian Göttschkes + * @see https://www.pushover.net/api + */ +class PushoverHandler extends SocketHandler +{ + private $token; + private $users; + private $title; + private $user; + private $retry; + private $expire; + + private $highPriorityLevel; + private $emergencyLevel; + + /** + * @param string $token Pushover api token + * @param string|array $users Pushover user id or array of ids the message will be sent to + * @param string $title Title sent to the Pushover API + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param Boolean $useSSL Whether to connect via SSL. Required when pushing messages to users that are not + * the pushover.net app owner. OpenSSL is required for this option. + * @param integer $highPriorityLevel The minimum logging level at which this handler will start + * sending "high priority" requests to the Pushover API + * @param integer $emergencyLevel The minimum logging level at which this handler will start + * sending "emergency" requests to the Pushover API + * @param integer $retry The retry parameter specifies how often (in seconds) the Pushover servers will send the same notification to the user. + * @param integer $expire The expire parameter specifies how many seconds your notification will continue to be retried for (every retry seconds). + */ + public function __construct($token, $users, $title = null, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $highPriorityLevel = Logger::CRITICAL, $emergencyLevel = Logger::EMERGENCY, $retry = 30, $expire = 25200) + { + $connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80'; + parent::__construct($connectionString, $level, $bubble); + + $this->token = $token; + $this->users = (array) $users; + $this->title = $title ?: gethostname(); + $this->highPriorityLevel = $highPriorityLevel; + $this->emergencyLevel = $emergencyLevel; + $this->retry = $retry; + $this->expire = $expire; + } + + protected function generateDataStream($record) + { + $content = $this->buildContent($record); + + return $this->buildHeader($content) . $content; + } + + private function buildContent($record) + { + // Pushover has a limit of 512 characters on title and message combined. + $maxMessageLength = 512 - strlen($this->title); + $message = substr($record['message'], 0, $maxMessageLength); + $timestamp = $record['datetime']->getTimestamp(); + + $dataArray = array( + 'token' => $this->token, + 'user' => $this->user, + 'message' => $message, + 'title' => $this->title, + 'timestamp' => $timestamp + ); + + if ($record['level'] >= $this->emergencyLevel) { + $dataArray['priority'] = 2; + $dataArray['retry'] = $this->retry; + $dataArray['expire'] = $this->expire; + } elseif ($record['level'] >= $this->highPriorityLevel) { + $dataArray['priority'] = 1; + } + + return http_build_query($dataArray); + } + + private function buildHeader($content) + { + $header = "POST /1/messages.json HTTP/1.1\r\n"; + $header .= "Host: api.pushover.net\r\n"; + $header .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $header .= "Content-Length: " . strlen($content) . "\r\n"; + $header .= "\r\n"; + + return $header; + } + + public function write(array $record) + { + foreach ($this->users as $user) { + $this->user = $user; + + parent::write($record); + $this->closeSocket(); + } + + $this->user = null; + } + + public function setHighPriorityLevel($value) + { + $this->highPriorityLevel = $value; + } + + public function setEmergencyLevel($value) + { + $this->emergencyLevel = $value; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php new file mode 100644 index 0000000..8835e66 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RavenHandler.php @@ -0,0 +1,167 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; +use Monolog\Logger; +use Monolog\Handler\AbstractProcessingHandler; +use Raven_Client; + +/** + * Handler to send messages to a Sentry (https://github.com/dcramer/sentry) server + * using raven-php (https://github.com/getsentry/raven-php) + * + * @author Marc Abramowitz + */ +class RavenHandler extends AbstractProcessingHandler +{ + /** + * Translates Monolog log levels to Raven log levels. + */ + private $logLevels = array( + Logger::DEBUG => Raven_Client::DEBUG, + Logger::INFO => Raven_Client::INFO, + Logger::NOTICE => Raven_Client::INFO, + Logger::WARNING => Raven_Client::WARNING, + Logger::ERROR => Raven_Client::ERROR, + Logger::CRITICAL => Raven_Client::FATAL, + Logger::ALERT => Raven_Client::FATAL, + Logger::EMERGENCY => Raven_Client::FATAL, + ); + + /** + * @var Raven_Client the client object that sends the message to the server + */ + protected $ravenClient; + + /** + * @var LineFormatter The formatter to use for the logs generated via handleBatch() + */ + protected $batchFormatter; + + /** + * @param Raven_Client $ravenClient + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + + $this->ravenClient = $ravenClient; + } + + /** + * {@inheritdoc} + */ + public function handleBatch(array $records) + { + $level = $this->level; + + // filter records based on their level + $records = array_filter($records, function($record) use ($level) { + return $record['level'] >= $level; + }); + + if (!$records) { + return; + } + + // the record with the highest severity is the "main" one + $record = array_reduce($records, function($highest, $record) { + if ($record['level'] >= $highest['level']) { + $highest = $record; + + return $highest; + } + }); + + // the other ones are added as a context item + $logs = array(); + foreach ($records as $r) { + $logs[] = $this->processRecord($r); + } + + if ($logs) { + $record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs); + } + + $this->handle($record); + } + + /** + * Sets the formatter for the logs generated by handleBatch(). + * + * @param FormatterInterface $formatter + */ + public function setBatchFormatter(FormatterInterface $formatter) + { + $this->batchFormatter = $formatter; + } + + /** + * Gets the formatter for the logs generated by handleBatch(). + * + * @return FormatterInterface + */ + public function getBatchFormatter() + { + if (!$this->batchFormatter) { + $this->batchFormatter = $this->getDefaultBatchFormatter(); + } + + return $this->batchFormatter; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $options = array(); + $options['level'] = $this->logLevels[$record['level']]; + if (!empty($record['context'])) { + $options['extra']['context'] = $record['context']; + } + if (!empty($record['extra'])) { + $options['extra']['extra'] = $record['extra']; + } + + if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) { + $options['extra']['message'] = $record['formatted']; + $this->ravenClient->captureException($record['context']['exception'], $options); + + return; + } + + $this->ravenClient->captureMessage($record['formatted'], array(), $options); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('[%channel%] %message%'); + } + + /** + * Gets the default formatter for the logs generated by handleBatch(). + * + * @return FormatterInterface + */ + protected function getDefaultBatchFormatter() + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php new file mode 100644 index 0000000..51a8e7d --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RedisHandler.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +/** + * Logs to a Redis key using rpush + * + * usage example: + * + * $log = new Logger('application'); + * $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs", "prod"); + * $log->pushHandler($redis); + * + * @author Thomas Tourlourat + */ +class RedisHandler extends AbstractProcessingHandler +{ + private $redisClient; + private $redisKey; + + # redis instance, key to use + public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true) + { + if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) { + throw new \InvalidArgumentException('Predis\Client or Redis instance required'); + } + + $this->redisClient = $redis; + $this->redisKey = $key; + + parent::__construct($level, $bubble); + } + + protected function write(array $record) + { + $this->redisClient->rpush($this->redisKey, $record["formatted"]); + } + + /** + * {@inheritDoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter(); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php new file mode 100644 index 0000000..0850134 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/RotatingFileHandler.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores logs to files that are rotated every day and a limited number of files are kept. + * + * This rotation is only intended to be used as a workaround. Using logrotate to + * handle the rotation is strongly encouraged when you can use it. + * + * @author Christophe Coevoet + * @author Jordi Boggiano + */ +class RotatingFileHandler extends StreamHandler +{ + protected $filename; + protected $maxFiles; + protected $mustRotate; + protected $nextRotation; + + /** + * @param string $filename + * @param integer $maxFiles The maximal amount of files to keep (0 means unlimited) + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($filename, $maxFiles = 0, $level = Logger::DEBUG, $bubble = true) + { + $this->filename = $filename; + $this->maxFiles = (int) $maxFiles; + $this->nextRotation = new \DateTime('tomorrow'); + + parent::__construct($this->getTimedFilename(), $level, $bubble); + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + + if (true === $this->mustRotate) { + $this->rotate(); + } + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + // on the first record written, if the log is new, we should rotate (once per day) + if (null === $this->mustRotate) { + $this->mustRotate = !file_exists($this->url); + } + + if ($this->nextRotation < $record['datetime']) { + $this->mustRotate = true; + $this->close(); + } + + parent::write($record); + } + + /** + * Rotates the files. + */ + protected function rotate() + { + // update filename + $this->url = $this->getTimedFilename(); + $this->nextRotation = new \DateTime('tomorrow'); + + // skip GC of old logs if files are unlimited + if (0 === $this->maxFiles) { + return; + } + + $fileInfo = pathinfo($this->filename); + $glob = $fileInfo['dirname'].'/'.$fileInfo['filename'].'-*'; + if (!empty($fileInfo['extension'])) { + $glob .= '.'.$fileInfo['extension']; + } + $logFiles = glob($glob); + if ($this->maxFiles >= count($logFiles)) { + // no files to remove + return; + } + + // Sorting the files by name to remove the older ones + usort($logFiles, function($a, $b) { + return strcmp($b, $a); + }); + + foreach (array_slice($logFiles, $this->maxFiles) as $file) { + if (is_writable($file)) { + unlink($file); + } + } + } + + protected function getTimedFilename() + { + $fileInfo = pathinfo($this->filename); + $timedFilename = $fileInfo['dirname'].'/'.$fileInfo['filename'].'-'.date('Y-m-d'); + if (!empty($fileInfo['extension'])) { + $timedFilename .= '.'.$fileInfo['extension']; + } + + return $timedFilename; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php new file mode 100644 index 0000000..4faa327 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SocketHandler.php @@ -0,0 +1,285 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to any socket - uses fsockopen() or pfsockopen(). + * + * @author Pablo de Leon Belloc + * @see http://php.net/manual/en/function.fsockopen.php + */ +class SocketHandler extends AbstractProcessingHandler +{ + private $connectionString; + private $connectionTimeout; + private $resource; + private $timeout = 0; + private $persistent = false; + private $errno; + private $errstr; + + /** + * @param string $connectionString Socket connection string + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + $this->connectionString = $connectionString; + $this->connectionTimeout = (float) ini_get('default_socket_timeout'); + } + + /** + * Connect (if necessary) and write to the socket + * + * @param array $record + * + * @throws \UnexpectedValueException + * @throws \RuntimeException + */ + public function write(array $record) + { + $this->connectIfNotConnected(); + $data = $this->generateDataStream($record); + $this->writeToSocket($data); + } + + /** + * We will not close a PersistentSocket instance so it can be reused in other requests. + */ + public function close() + { + if (!$this->isPersistent()) { + $this->closeSocket(); + } + } + + /** + * Close socket, if open + */ + public function closeSocket() + { + if (is_resource($this->resource)) { + fclose($this->resource); + $this->resource = null; + } + } + + /** + * Set socket connection to nbe persistent. It only has effect before the connection is initiated. + * + * @param type $boolean + */ + public function setPersistent($boolean) + { + $this->persistent = (boolean) $boolean; + } + + /** + * Set connection timeout. Only has effect before we connect. + * + * @param float $seconds + * + * @see http://php.net/manual/en/function.fsockopen.php + */ + public function setConnectionTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->connectionTimeout = (float) $seconds; + } + + /** + * Set write timeout. Only has effect before we connect. + * + * @param float $seconds + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + public function setTimeout($seconds) + { + $this->validateTimeout($seconds); + $this->timeout = (float) $seconds; + } + + /** + * Get current connection string + * + * @return string + */ + public function getConnectionString() + { + return $this->connectionString; + } + + /** + * Get persistent setting + * + * @return boolean + */ + public function isPersistent() + { + return $this->persistent; + } + + /** + * Get current connection timeout setting + * + * @return float + */ + public function getConnectionTimeout() + { + return $this->connectionTimeout; + } + + /** + * Get current in-transfer timeout + * + * @return float + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Check to see if the socket is currently available. + * + * UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details. + * + * @return boolean + */ + public function isConnected() + { + return is_resource($this->resource) + && !feof($this->resource); // on TCP - other party can close connection. + } + + /** + * Wrapper to allow mocking + */ + protected function pfsockopen() + { + return @pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + */ + protected function fsockopen() + { + return @fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout); + } + + /** + * Wrapper to allow mocking + * + * @see http://php.net/manual/en/function.stream-set-timeout.php + */ + protected function streamSetTimeout() + { + $seconds = floor($this->timeout); + $microseconds = round(($this->timeout - $seconds)*1e6); + + return stream_set_timeout($this->resource, $seconds, $microseconds); + } + + /** + * Wrapper to allow mocking + */ + protected function fwrite($data) + { + return @fwrite($this->resource, $data); + } + + /** + * Wrapper to allow mocking + */ + protected function streamGetMetadata() + { + return stream_get_meta_data($this->resource); + } + + private function validateTimeout($value) + { + $ok = filter_var($value, FILTER_VALIDATE_FLOAT); + if ($ok === false || $value < 0) { + throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)"); + } + } + + private function connectIfNotConnected() + { + if ($this->isConnected()) { + return; + } + $this->connect(); + } + + protected function generateDataStream($record) + { + return (string) $record['formatted']; + } + + private function connect() + { + $this->createSocketResource(); + $this->setSocketTimeout(); + } + + private function createSocketResource() + { + if ($this->isPersistent()) { + $resource = $this->pfsockopen(); + } else { + $resource = $this->fsockopen(); + } + if (!$resource) { + throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)"); + } + $this->resource = $resource; + } + + private function setSocketTimeout() + { + if (!$this->streamSetTimeout()) { + throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()"); + } + } + + private function writeToSocket($data) + { + $length = strlen($data); + $sent = 0; + while ($this->isConnected() && $sent < $length) { + if (0 == $sent) { + $chunk = $this->fwrite($data); + } else { + $chunk = $this->fwrite(substr($data, $sent)); + } + if ($chunk === false) { + throw new \RuntimeException("Could not write to socket"); + } + $sent += $chunk; + $socketInfo = $this->streamGetMetadata(); + if ($socketInfo['timed_out']) { + throw new \RuntimeException("Write timed-out"); + } + } + if (!$this->isConnected() && $sent < $length) { + throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)"); + } + } + +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php new file mode 100644 index 0000000..96ce7fc --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/StreamHandler.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Stores to any stream resource + * + * Can be used to store into php://stderr, remote and local files, etc. + * + * @author Jordi Boggiano + */ +class StreamHandler extends AbstractProcessingHandler +{ + protected $stream; + protected $url; + + /** + * @param string $stream + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct($stream, $level = Logger::DEBUG, $bubble = true) + { + parent::__construct($level, $bubble); + if (is_resource($stream)) { + $this->stream = $stream; + } else { + $this->url = $stream; + } + } + + /** + * {@inheritdoc} + */ + public function close() + { + if (is_resource($this->stream)) { + fclose($this->stream); + } + $this->stream = null; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (null === $this->stream) { + if (!$this->url) { + throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().'); + } + $errorMessage = null; + set_error_handler(function ($code, $msg) use (&$errorMessage) { + $errorMessage = preg_replace('{^fopen\(.*?\): }', '', $msg); + }); + $this->stream = fopen($this->url, 'a'); + restore_error_handler(); + if (!is_resource($this->stream)) { + $this->stream = null; + throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: '.$errorMessage, $this->url)); + } + } + fwrite($this->stream, (string) $record['formatted']); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php new file mode 100644 index 0000000..ca03cca --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SwiftMailerHandler.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * SwiftMailerHandler uses Swift_Mailer to send the emails + * + * @author Gyula Sallai + */ +class SwiftMailerHandler extends MailHandler +{ + protected $mailer; + protected $message; + + /** + * @param \Swift_Mailer $mailer The mailer to use + * @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct(\Swift_Mailer $mailer, $message, $level = Logger::ERROR, $bubble = true) + { + parent::__construct($level, $bubble); + $this->mailer = $mailer; + if (!$message instanceof \Swift_Message && is_callable($message)) { + $message = call_user_func($message); + } + if (!$message instanceof \Swift_Message) { + throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it'); + } + $this->message = $message; + } + + /** + * {@inheritdoc} + */ + protected function send($content, array $records) + { + $message = clone $this->message; + $message->setBody($content); + + $this->mailer->send($message); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php new file mode 100644 index 0000000..924ccf4 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/SyslogHandler.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +/** + * Logs to syslog service. + * + * usage example: + * + * $log = new Logger('application'); + * $syslog = new SyslogHandler('myfacility', 'local6'); + * $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%"); + * $syslog->setFormatter($formatter); + * $log->pushHandler($syslog); + * + * @author Sven Paulus + */ +class SyslogHandler extends AbstractProcessingHandler +{ + protected $ident; + protected $logopts; + protected $facility; + + /** + * Translates Monolog log levels to syslog log priorities. + */ + private $logLevels = array( + Logger::DEBUG => LOG_DEBUG, + Logger::INFO => LOG_INFO, + Logger::NOTICE => LOG_NOTICE, + Logger::WARNING => LOG_WARNING, + Logger::ERROR => LOG_ERR, + Logger::CRITICAL => LOG_CRIT, + Logger::ALERT => LOG_ALERT, + Logger::EMERGENCY => LOG_EMERG, + ); + + /** + * List of valid log facility names. + */ + private $facilities = array( + 'auth' => LOG_AUTH, + 'authpriv' => LOG_AUTHPRIV, + 'cron' => LOG_CRON, + 'daemon' => LOG_DAEMON, + 'kern' => LOG_KERN, + 'lpr' => LOG_LPR, + 'mail' => LOG_MAIL, + 'news' => LOG_NEWS, + 'syslog' => LOG_SYSLOG, + 'user' => LOG_USER, + 'uucp' => LOG_UUCP, + ); + + /** + * @param string $ident + * @param mixed $facility + * @param integer $level The minimum logging level at which this handler will be triggered + * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not + * @param int $logopts Option flags for the openlog() call, defaults to LOG_PID + */ + public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $logopts = LOG_PID) + { + parent::__construct($level, $bubble); + + if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->facilities['local0'] = LOG_LOCAL0; + $this->facilities['local1'] = LOG_LOCAL1; + $this->facilities['local2'] = LOG_LOCAL2; + $this->facilities['local3'] = LOG_LOCAL3; + $this->facilities['local4'] = LOG_LOCAL4; + $this->facilities['local5'] = LOG_LOCAL5; + $this->facilities['local6'] = LOG_LOCAL6; + $this->facilities['local7'] = LOG_LOCAL7; + } + + // convert textual description of facility to syslog constant + if (array_key_exists(strtolower($facility), $this->facilities)) { + $facility = $this->facilities[strtolower($facility)]; + } elseif (!in_array($facility, array_values($this->facilities), true)) { + throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given'); + } + + $this->ident = $ident; + $this->logopts = $logopts; + $this->facility = $facility; + } + + /** + * {@inheritdoc} + */ + public function close() + { + closelog(); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + if (!openlog($this->ident, $this->logopts, $this->facility)) { + throw new \LogicException('Can\'t open syslog for ident "'.$this->ident.'" and facility "'.$this->facility.'"'); + } + syslog($this->logLevels[$record['level']], (string) $record['formatted']); + } + + /** + * {@inheritdoc} + */ + protected function getDefaultFormatter() + { + return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%'); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php new file mode 100644 index 0000000..085d9e1 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/TestHandler.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Used for testing purposes. + * + * It records all records and gives you access to them for verification. + * + * @author Jordi Boggiano + */ +class TestHandler extends AbstractProcessingHandler +{ + protected $records = array(); + protected $recordsByLevel = array(); + + public function getRecords() + { + return $this->records; + } + + public function hasEmergency($record) + { + return $this->hasRecord($record, Logger::EMERGENCY); + } + + public function hasAlert($record) + { + return $this->hasRecord($record, Logger::ALERT); + } + + public function hasCritical($record) + { + return $this->hasRecord($record, Logger::CRITICAL); + } + + public function hasError($record) + { + return $this->hasRecord($record, Logger::ERROR); + } + + public function hasWarning($record) + { + return $this->hasRecord($record, Logger::WARNING); + } + + public function hasNotice($record) + { + return $this->hasRecord($record, Logger::NOTICE); + } + + public function hasInfo($record) + { + return $this->hasRecord($record, Logger::INFO); + } + + public function hasDebug($record) + { + return $this->hasRecord($record, Logger::DEBUG); + } + + public function hasEmergencyRecords() + { + return isset($this->recordsByLevel[Logger::EMERGENCY]); + } + + public function hasAlertRecords() + { + return isset($this->recordsByLevel[Logger::ALERT]); + } + + public function hasCriticalRecords() + { + return isset($this->recordsByLevel[Logger::CRITICAL]); + } + + public function hasErrorRecords() + { + return isset($this->recordsByLevel[Logger::ERROR]); + } + + public function hasWarningRecords() + { + return isset($this->recordsByLevel[Logger::WARNING]); + } + + public function hasNoticeRecords() + { + return isset($this->recordsByLevel[Logger::NOTICE]); + } + + public function hasInfoRecords() + { + return isset($this->recordsByLevel[Logger::INFO]); + } + + public function hasDebugRecords() + { + return isset($this->recordsByLevel[Logger::DEBUG]); + } + + protected function hasRecord($record, $level) + { + if (!isset($this->recordsByLevel[$level])) { + return false; + } + + if (is_array($record)) { + $record = $record['message']; + } + + foreach ($this->recordsByLevel[$level] as $rec) { + if ($rec['message'] === $record) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php new file mode 100644 index 0000000..f22cf21 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Handler/ZendMonitorHandler.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Formatter\NormalizerFormatter; +use Monolog\Logger; + +/** + * Handler sending logs to Zend Monitor + * + * @author Christian Bergau + */ +class ZendMonitorHandler extends AbstractProcessingHandler +{ + /** + * Monolog level / ZendMonitor Custom Event priority map + * + * @var array + */ + protected $levelMap = array( + Logger::DEBUG => 1, + Logger::INFO => 2, + Logger::NOTICE => 3, + Logger::WARNING => 4, + Logger::ERROR => 5, + Logger::CRITICAL => 6, + Logger::ALERT => 7, + Logger::EMERGENCY => 0, + ); + + /** + * Construct + * + * @param int $level + * @param bool $bubble + * @throws MissingExtensionException + */ + public function __construct($level = Logger::DEBUG, $bubble = true) + { + if (!function_exists('zend_monitor_custom_event')) { + throw new MissingExtensionException('You must have Zend Server installed in order to use this handler'); + } + parent::__construct($level, $bubble); + } + + /** + * {@inheritdoc} + */ + protected function write(array $record) + { + $this->writeZendMonitorCustomEvent( + $this->levelMap[$record['level']], + $record['message'], + $record['formatted'] + ); + } + + /** + * Write a record to Zend Monitor + * + * @param int $level + * @param string $message + * @param array $formatted + */ + protected function writeZendMonitorCustomEvent($level, $message, $formatted) + { + zend_monitor_custom_event($level, $message, $formatted); + } + + /** + * {@inheritdoc} + */ + public function getDefaultFormatter() + { + return new NormalizerFormatter(); + } + + /** + * Get the level map + * + * @return array + */ + public function getLevelMap() + { + return $this->levelMap; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Logger.php b/vendor/monolog/monolog/src/Monolog/Logger.php new file mode 100644 index 0000000..65f9257 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Logger.php @@ -0,0 +1,574 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\HandlerInterface; +use Monolog\Handler\StreamHandler; +use Psr\Log\LoggerInterface; +use Psr\Log\InvalidArgumentException; + +/** + * Monolog log channel + * + * It contains a stack of Handlers and a stack of Processors, + * and uses them to store records that are added to it. + * + * @author Jordi Boggiano + */ +class Logger implements LoggerInterface +{ + /** + * Detailed debug information + */ + const DEBUG = 100; + + /** + * Interesting events + * + * Examples: User logs in, SQL logs. + */ + const INFO = 200; + + /** + * Uncommon events + */ + const NOTICE = 250; + + /** + * Exceptional occurrences that are not errors + * + * Examples: Use of deprecated APIs, poor use of an API, + * undesirable things that are not necessarily wrong. + */ + const WARNING = 300; + + /** + * Runtime errors + */ + const ERROR = 400; + + /** + * Critical conditions + * + * Example: Application component unavailable, unexpected exception. + */ + const CRITICAL = 500; + + /** + * Action must be taken immediately + * + * Example: Entire website down, database unavailable, etc. + * This should trigger the SMS alerts and wake you up. + */ + const ALERT = 550; + + /** + * Urgent alert. + */ + const EMERGENCY = 600; + + /** + * Monolog API version + * + * This is only bumped when API breaks are done and should + * follow the major version of the library + * + * @var int + */ + const API = 1; + + protected static $levels = array( + 100 => 'DEBUG', + 200 => 'INFO', + 250 => 'NOTICE', + 300 => 'WARNING', + 400 => 'ERROR', + 500 => 'CRITICAL', + 550 => 'ALERT', + 600 => 'EMERGENCY', + ); + + /** + * @var DateTimeZone + */ + protected static $timezone; + + protected $name; + + /** + * The handler stack + * + * @var array of Monolog\Handler\HandlerInterface + */ + protected $handlers; + + /** + * Processors that will process all log records + * + * To process records of a single handler instead, add the processor on that specific handler + * + * @var array of callables + */ + protected $processors; + + /** + * @param string $name The logging channel + * @param array $handlers Optional stack of handlers, the first one in the array is called first, etc. + * @param array $processors Optional array of processors + */ + public function __construct($name, array $handlers = array(), array $processors = array()) + { + $this->name = $name; + $this->handlers = $handlers; + $this->processors = $processors; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Pushes a handler on to the stack. + * + * @param HandlerInterface $handler + */ + public function pushHandler(HandlerInterface $handler) + { + array_unshift($this->handlers, $handler); + } + + /** + * Pops a handler from the stack + * + * @return HandlerInterface + */ + public function popHandler() + { + if (!$this->handlers) { + throw new \LogicException('You tried to pop from an empty handler stack.'); + } + + return array_shift($this->handlers); + } + + /** + * Adds a processor on to the stack. + * + * @param callable $callback + */ + public function pushProcessor($callback) + { + if (!is_callable($callback)) { + throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given'); + } + array_unshift($this->processors, $callback); + } + + /** + * Removes the processor on top of the stack and returns it. + * + * @return callable + */ + public function popProcessor() + { + if (!$this->processors) { + throw new \LogicException('You tried to pop from an empty processor stack.'); + } + + return array_shift($this->processors); + } + + /** + * Adds a log record. + * + * @param integer $level The logging level + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addRecord($level, $message, array $context = array()) + { + if (!$this->handlers) { + $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG)); + } + + if (!static::$timezone) { + static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); + } + + $record = array( + 'message' => (string) $message, + 'context' => $context, + 'level' => $level, + 'level_name' => static::getLevelName($level), + 'channel' => $this->name, + 'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone)->setTimezone(static::$timezone), + 'extra' => array(), + ); + // check if any handler will handle this message + $handlerKey = null; + foreach ($this->handlers as $key => $handler) { + if ($handler->isHandling($record)) { + $handlerKey = $key; + break; + } + } + // none found + if (null === $handlerKey) { + return false; + } + + // found at least one, process message and dispatch it + foreach ($this->processors as $processor) { + $record = call_user_func($processor, $record); + } + while (isset($this->handlers[$handlerKey]) && + false === $this->handlers[$handlerKey]->handle($record)) { + $handlerKey++; + } + + return true; + } + + /** + * Adds a log record at the DEBUG level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addDebug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addInfo($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + + /** + * Adds a log record at the NOTICE level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addNotice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addWarning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addError($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addCritical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addAlert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function addEmergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Gets all supported logging levels. + * + * @return array Assoc array with human-readable level names => level codes. + */ + public static function getLevels() + { + return array_flip(static::$levels); + } + + /** + * Gets the name of the logging level. + * + * @param integer $level + * @return string + */ + public static function getLevelName($level) + { + if (!isset(static::$levels[$level])) { + throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels))); + } + + return static::$levels[$level]; + } + + /** + * Checks whether the Logger has a handler that listens on the given level + * + * @param integer $level + * @return Boolean + */ + public function isHandling($level) + { + $record = array( + 'level' => $level, + ); + + foreach ($this->handlers as $handler) { + if ($handler->isHandling($record)) { + return true; + } + } + + return false; + } + + /** + * Adds a log record at an arbitrary level. + * + * This method allows for compatibility with common interfaces. + * + * @param mixed $level The log level + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function log($level, $message, array $context = array()) + { + if (is_string($level) && defined(__CLASS__.'::'.strtoupper($level))) { + $level = constant(__CLASS__.'::'.strtoupper($level)); + } + + return $this->addRecord($level, $message, $context); + } + + /** + * Adds a log record at the DEBUG level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function debug($message, array $context = array()) + { + return $this->addRecord(static::DEBUG, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function info($message, array $context = array()) + { + return $this->addRecord(static::INFO, $message, $context); + } + + /** + * Adds a log record at the INFO level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function notice($message, array $context = array()) + { + return $this->addRecord(static::NOTICE, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function warn($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the WARNING level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function warning($message, array $context = array()) + { + return $this->addRecord(static::WARNING, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function err($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the ERROR level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function error($message, array $context = array()) + { + return $this->addRecord(static::ERROR, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function crit($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the CRITICAL level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function critical($message, array $context = array()) + { + return $this->addRecord(static::CRITICAL, $message, $context); + } + + /** + * Adds a log record at the ALERT level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function alert($message, array $context = array()) + { + return $this->addRecord(static::ALERT, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function emerg($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } + + /** + * Adds a log record at the EMERGENCY level. + * + * This method allows for compatibility with common interfaces. + * + * @param string $message The log message + * @param array $context The log context + * @return Boolean Whether the record has been processed + */ + public function emergency($message, array $context = array()) + { + return $this->addRecord(static::EMERGENCY, $message, $context); + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php new file mode 100644 index 0000000..b126218 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/IntrospectionProcessor.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects line/file:class/function where the log message came from + * + * Warning: This only works if the handler processes the logs directly. + * If you put the processor on a handler that is behind a FingersCrossedHandler + * for example, the processor will only be called once the trigger level is reached, + * and all the log records will have the same file/line/.. data from the call that + * triggered the FingersCrossedHandler. + * + * @author Jordi Boggiano + */ +class IntrospectionProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $trace = debug_backtrace(); + + // skip first since it's always the current method + array_shift($trace); + // the call_user_func call is also skipped + array_shift($trace); + + $i = 0; + while (isset($trace[$i]['class']) && false !== strpos($trace[$i]['class'], 'Monolog\\')) { + $i++; + } + + // we should have the call source now + $record['extra'] = array_merge( + $record['extra'], + array( + 'file' => isset($trace[$i-1]['file']) ? $trace[$i-1]['file'] : null, + 'line' => isset($trace[$i-1]['line']) ? $trace[$i-1]['line'] : null, + 'class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : null, + 'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null, + ) + ); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php new file mode 100644 index 0000000..e48672b --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryPeakUsageProcessor.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects memory_get_peak_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryPeakUsageProcessor extends MemoryProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $bytes = memory_get_peak_usage($this->realUsage); + $formatted = self::formatBytes($bytes); + + $record['extra'] = array_merge( + $record['extra'], + array( + 'memory_peak_usage' => $formatted, + ) + ); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php new file mode 100644 index 0000000..7551043 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryProcessor.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Some methods that are common for all memory processors + * + * @author Rob Jensen + */ +abstract class MemoryProcessor +{ + protected $realUsage; + + /** + * @param boolean $realUsage + */ + public function __construct($realUsage = true) + { + $this->realUsage = (boolean) $realUsage; + } + + /** + * Formats bytes into a human readable string + * + * @param int $bytes + * @return string + */ + protected static function formatBytes($bytes) + { + $bytes = (int) $bytes; + + if ($bytes > 1024*1024) { + return round($bytes/1024/1024, 2).' MB'; + } elseif ($bytes > 1024) { + return round($bytes/1024, 2).' KB'; + } + + return $bytes . ' B'; + } + +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php new file mode 100644 index 0000000..2c4a807 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/MemoryUsageProcessor.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects memory_get_usage in all records + * + * @see Monolog\Processor\MemoryProcessor::__construct() for options + * @author Rob Jensen + */ +class MemoryUsageProcessor extends MemoryProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $bytes = memory_get_usage($this->realUsage); + $formatted = self::formatBytes($bytes); + + $record['extra'] = array_merge( + $record['extra'], + array( + 'memory_usage' => $formatted, + ) + ); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php new file mode 100644 index 0000000..9bd5e5e --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/ProcessIdProcessor.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Adds value of getmypid into records + * + * @author Andreas Hörnicke + */ +class ProcessIdProcessor +{ + private static $pid; + + public function __construct() + { + if (null === self::$pid) { + self::$pid = getmypid(); + } + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + $record['extra']['process_id'] = self::$pid; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php new file mode 100644 index 0000000..b63fccc --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/PsrLogMessageProcessor.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Processes a record's message according to PSR-3 rules + * + * It replaces {foo} with the value from $context['foo'] + * + * @author Jordi Boggiano + */ +class PsrLogMessageProcessor +{ + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + if (false === strpos($record['message'], '{')) { + return $record; + } + + $replacements = array(); + foreach ($record['context'] as $key => $val) { + $replacements['{'.$key.'}'] = $val; + } + + $record['message'] = strtr($record['message'], $replacements); + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php new file mode 100644 index 0000000..80270d0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/UidProcessor.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Adds a unique identifier into records + * + * @author Simon Mönch + */ +class UidProcessor +{ + private $uid; + + public function __construct($length = 7) + { + if (!is_int($length) || $length > 32 || $length < 1) { + throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32'); + } + + $this->uid = substr(hash('md5', uniqid('', true)), 0, $length); + } + + public function __invoke(array $record) + { + $record['extra']['uid'] = $this->uid; + + return $record; + } +} diff --git a/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php new file mode 100644 index 0000000..9916cc0 --- /dev/null +++ b/vendor/monolog/monolog/src/Monolog/Processor/WebProcessor.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +/** + * Injects url/method and remote IP of the current web request in all records + * + * @author Jordi Boggiano + */ +class WebProcessor +{ + protected $serverData; + + /** + * @param mixed $serverData array or object w/ ArrayAccess that provides access to the $_SERVER data + */ + public function __construct($serverData = null) + { + if (null === $serverData) { + $this->serverData =& $_SERVER; + } elseif (is_array($serverData) || $serverData instanceof \ArrayAccess) { + $this->serverData = $serverData; + } else { + throw new \UnexpectedValueException('$serverData must be an array or object implementing ArrayAccess.'); + } + } + + /** + * @param array $record + * @return array + */ + public function __invoke(array $record) + { + // skip processing if for some reason request data + // is not present (CLI or wonky SAPIs) + if (!isset($this->serverData['REQUEST_URI'])) { + return $record; + } + + $record['extra'] = array_merge( + $record['extra'], + array( + 'url' => $this->serverData['REQUEST_URI'], + 'ip' => isset($this->serverData['REMOTE_ADDR']) ? $this->serverData['REMOTE_ADDR'] : null, + 'http_method' => isset($this->serverData['REQUEST_METHOD']) ? $this->serverData['REQUEST_METHOD'] : null, + 'server' => isset($this->serverData['SERVER_NAME']) ? $this->serverData['SERVER_NAME'] : null, + 'referrer' => isset($this->serverData['HTTP_REFERER']) ? $this->serverData['HTTP_REFERER'] : null, + ) + ); + + return $record; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php new file mode 100644 index 0000000..a9a3f30 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/ErrorHandlerTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\TestHandler; + +class ErrorHandlerTest extends \PHPUnit_Framework_TestCase +{ + public function testHandleError() + { + $logger = new Logger('test', array($handler = new TestHandler)); + $errHandler = new ErrorHandler($logger); + + $errHandler->registerErrorHandler(array(E_USER_NOTICE => Logger::EMERGENCY), false); + trigger_error('Foo', E_USER_ERROR); + $this->assertCount(1, $handler->getRecords()); + $this->assertTrue($handler->hasErrorRecords()); + trigger_error('Foo', E_USER_NOTICE); + $this->assertCount(2, $handler->getRecords()); + $this->assertTrue($handler->hasEmergencyRecords()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php new file mode 100644 index 0000000..e7f7334 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/ChromePHPFormatterTest.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class ChromePHPFormatterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Formatter\ChromePHPFormatter::format + */ + public function testDefaultFormat() + { + $formatter = new ChromePHPFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertEquals( + array( + 'meh', + array( + 'message' => 'log', + 'context' => array('from' => 'logger'), + 'extra' => array('ip' => '127.0.0.1'), + ), + 'unknown', + 'error' + ), + $message + ); + } + + /** + * @covers Monolog\Formatter\ChromePHPFormatter::format + */ + public function testFormatWithFileAndLine() + { + $formatter = new ChromePHPFormatter(); + $record = array( + 'level' => Logger::CRITICAL, + 'level_name' => 'CRITICAL', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1', 'file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertEquals( + array( + 'meh', + array( + 'message' => 'log', + 'context' => array('from' => 'logger'), + 'extra' => array('ip' => '127.0.0.1'), + ), + 'test : 14', + 'error' + ), + $message + ); + } + + /** + * @covers Monolog\Formatter\ChromePHPFormatter::format + */ + public function testFormatWithoutContext() + { + $formatter = new ChromePHPFormatter(); + $record = array( + 'level' => Logger::DEBUG, + 'level_name' => 'DEBUG', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertEquals( + array( + 'meh', + 'log', + 'unknown', + 'log' + ), + $message + ); + } + + /** + * @covers Monolog\Formatter\ChromePHPFormatter::formatBatch + */ + public function testBatchFormatThrowException() + { + $formatter = new ChromePHPFormatter(); + $records = array( + array( + 'level' => Logger::INFO, + 'level_name' => 'INFO', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ), + array( + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log2', + ), + ); + + $this->assertEquals( + array( + array( + 'meh', + 'log', + 'unknown', + 'info' + ), + array( + 'foo', + 'log2', + 'unknown', + 'warn' + ), + ), + $formatter->formatBatch($records) + ); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php new file mode 100644 index 0000000..a1db166 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/GelfMessageFormatterTest.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; + +class GelfMessageFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + if (!class_exists("Gelf\Message")) { + $this->markTestSkipped("mlehner/gelf-php not installed"); + } + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testDefaultFormatter() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals(0, $message->getTimestamp()); + $this->assertEquals('log', $message->getShortMessage()); + $this->assertEquals('meh', $message->getFacility()); + $this->assertEquals(null, $message->getLine()); + $this->assertEquals(null, $message->getFile()); + $this->assertEquals(3, $message->getLevel()); + $this->assertNotEmpty($message->getHost()); + + $formatter = new GelfMessageFormatter('mysystem'); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals('mysystem', $message->getHost()); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithFileAndLine() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + $this->assertEquals('test', $message->getFile()); + $this->assertEquals(14, $message->getLine()); + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithContext() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log' + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_ctxt_from', $message_array); + $this->assertEquals('logger', $message_array['_ctxt_from']); + + // Test with extraPrefix + $formatter = new GelfMessageFormatter(null, null, 'CTX'); + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_CTXfrom', $message_array); + $this->assertEquals('logger', $message_array['_CTXfrom']); + + } + + /** + * @covers Monolog\Formatter\GelfMessageFormatter::format + */ + public function testFormatWithExtra() + { + $formatter = new GelfMessageFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log' + ); + + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_key', $message_array); + $this->assertEquals('pair', $message_array['_key']); + + // Test with extraPrefix + $formatter = new GelfMessageFormatter(null, 'EXT'); + $message = $formatter->format($record); + + $this->assertInstanceOf('Gelf\Message', $message); + + $message_array = $message->toArray(); + + $this->assertArrayHasKey('_EXTkey', $message_array); + $this->assertEquals('pair', $message_array['_EXTkey']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php new file mode 100644 index 0000000..ba6152c --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/JsonFormatterTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Monolog\TestCase; + +class JsonFormatterTest extends TestCase +{ + /** + * @covers Monolog\Formatter\JsonFormatter::format + */ + public function testFormat() + { + $formatter = new JsonFormatter(); + $record = $this->getRecord(); + $this->assertEquals(json_encode($record), $formatter->format($record)); + } + + /** + * @covers Monolog\Formatter\JsonFormatter::formatBatch + */ + public function testFormatBatch() + { + $formatter = new JsonFormatter(); + $records = array( + $this->getRecord(Logger::WARNING), + $this->getRecord(Logger::DEBUG), + ); + $this->assertEquals(json_encode($records), $formatter->formatBatch($records)); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php new file mode 100644 index 0000000..fa3fa59 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/LineFormatterTest.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * @covers Monolog\Formatter\LineFormatter + */ +class LineFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function testDefFormatWithString() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'context' => array(), + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array(), + )); + $this->assertEquals('['.date('Y-m-d').'] log.WARNING: foo [] []'."\n", $message); + } + + public function testDefFormatWithArrayContext() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array(), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + ) + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foo {"foo":"bar","baz":"qux"} []'."\n", $message); + } + + public function testDefFormatExtras() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log [] {"ip":"127.0.0.1"}'."\n", $message); + } + + public function testFormatExtras() + { + $formatter = new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra.file% %extra%\n", 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array('ip' => '127.0.0.1', 'file' => 'test'), + 'message' => 'log', + )); + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log [] test {"ip":"127.0.0.1"}'."\n", $message); + } + + public function testDefFormatWithObject() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array('foo' => new TestFoo, 'bar' => new TestBar, 'baz' => array(), 'res' => fopen('php://memory', 'rb')), + 'message' => 'foobar', + )); + + $this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foobar [] {"foo":"[object] (Monolog\\\\Formatter\\\\TestFoo: {\\"foo\\":\\"foo\\"})","bar":"[object] (Monolog\\\\Formatter\\\\TestBar: {})","baz":[],"res":"[resource]"}'."\n", $message); + } + + public function testDefFormatWithException() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'core', + 'context' => array('exception' => new \RuntimeException('Foo')), + 'datetime' => new \DateTime, + 'extra' => array(), + 'message' => 'foobar', + )); + + $path = str_replace('\\/', '/', json_encode(__FILE__)); + + $this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {"exception":"[object] (RuntimeException: Foo at '.substr($path, 1, -1).':'.(__LINE__-8).')"} []'."\n", $message); + } + + public function testDefFormatWithPreviousException() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $previous = new \LogicException('Wut?'); + $message = $formatter->format(array( + 'level_name' => 'CRITICAL', + 'channel' => 'core', + 'context' => array('exception' => new \RuntimeException('Foo', 0, $previous)), + 'datetime' => new \DateTime, + 'extra' => array(), + 'message' => 'foobar', + )); + + $path = str_replace('\\/', '/', json_encode(__FILE__)); + + $this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {"exception":"[object] (RuntimeException: Foo at '.substr($path, 1, -1).':'.(__LINE__-8).', LogicException: Wut? at '.substr($path, 1, -1).':'.(__LINE__-12).')"} []'."\n", $message); + } + + public function testBatchFormat() + { + $formatter = new LineFormatter(null, 'Y-m-d'); + $message = $formatter->formatBatch(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + )); + $this->assertEquals('['.date('Y-m-d').'] test.CRITICAL: bar [] []'."\n".'['.date('Y-m-d').'] log.WARNING: foo [] []'."\n", $message); + } +} + +class TestFoo +{ + public $foo = 'foo'; +} + +class TestBar +{ + public function __toString() + { + return 'bar'; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php new file mode 100644 index 0000000..6b012de --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/LogstashFormatterTest.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; +use Monolog\Formatter\LogstashFormatter; + +class LogstashFormatterTest extends \PHPUnit_Framework_TestCase +{ + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testDefaultFormatter() + { + $formatter = new LogstashFormatter('test', 'hostname'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals("1970-01-01T00:00:00+00:00", $message['@timestamp']); + $this->assertEquals('log', $message['@message']); + $this->assertEquals('meh', $message['@fields']['channel']); + $this->assertContains('meh', $message['@tags']); + $this->assertEquals(Logger::ERROR, $message['@fields']['level']); + $this->assertEquals('test', $message['@type']); + $this->assertEquals('hostname', $message['@source']); + + $formatter = new LogstashFormatter('mysystem'); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('mysystem', $message['@type']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithFileAndLine() + { + $formatter = new LogstashFormatter('test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertEquals('test', $message['@fields']['file']); + $this->assertEquals(14, $message['@fields']['line']); + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithContext() + { + $formatter = new LogstashFormatter('test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log' + ); + + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('ctxt_from', $message_array); + $this->assertEquals('logger', $message_array['ctxt_from']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, null, 'CTX'); + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('CTXfrom', $message_array); + $this->assertEquals('logger', $message_array['CTXfrom']); + + } + + /** + * @covers Monolog\Formatter\LogstashFormatter::format + */ + public function testFormatWithExtra() + { + $formatter = new LogstashFormatter('test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log' + ); + + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('key', $message_array); + $this->assertEquals('pair', $message_array['key']); + + // Test with extraPrefix + $formatter = new LogstashFormatter('test', null, 'EXT'); + $message = json_decode($formatter->format($record), true); + + $message_array = $message['@fields']; + + $this->assertArrayHasKey('EXTkey', $message_array); + $this->assertEquals('pair', $message_array['EXTkey']); + } + + public function testFormatWithApplicationName() + { + $formatter = new LogstashFormatter('app', 'test'); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('key' => 'pair'), + 'message' => 'log' + ); + + $message = json_decode($formatter->format($record), true); + + $this->assertArrayHasKey('@type', $message); + $this->assertEquals('app', $message['@type']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php new file mode 100644 index 0000000..7477871 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +/** + * @covers Monolog\Formatter\NormalizerFormatter + */ +class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase +{ + public function testFormat() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $formatted = $formatter->format(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => new \DateTime, + 'extra' => array('foo' => new TestFooNorm, 'bar' => new TestBarNorm, 'baz' => array(), 'res' => fopen('php://memory', 'rb')), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + ), + )); + + $this->assertEquals(array( + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'message' => 'foo', + 'datetime' => date('Y-m-d'), + 'extra' => array( + 'foo' => '[object] (Monolog\\Formatter\\TestFooNorm: {"foo":"foo"})', + 'bar' => '[object] (Monolog\\Formatter\\TestBarNorm: {})', + 'baz' => array(), + 'res' => '[resource]', + ), + 'context' => array( + 'foo' => 'bar', + 'baz' => 'qux', + ) + ), $formatted); + } + + public function testFormatExceptions() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $e = new \LogicException('bar'); + $e2 = new \RuntimeException('foo', 0, $e); + $formatted = $formatter->format(array( + 'exception' => $e2, + )); + + $this->assertGreaterThan(5, count($formatted['exception']['trace'])); + $this->assertTrue(isset($formatted['exception']['previous'])); + unset($formatted['exception']['trace'], $formatted['exception']['previous']); + + $this->assertEquals(array( + 'exception' => array( + 'class' => get_class($e2), + 'message' => $e2->getMessage(), + 'file' => $e2->getFile().':'.$e2->getLine(), + ) + ), $formatted); + } + + public function testBatchFormat() + { + $formatter = new NormalizerFormatter('Y-m-d'); + $formatted = $formatter->formatBatch(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => new \DateTime, + 'extra' => array(), + ), + )); + $this->assertEquals(array( + array( + 'level_name' => 'CRITICAL', + 'channel' => 'test', + 'message' => 'bar', + 'context' => array(), + 'datetime' => date('Y-m-d'), + 'extra' => array(), + ), + array( + 'level_name' => 'WARNING', + 'channel' => 'log', + 'message' => 'foo', + 'context' => array(), + 'datetime' => date('Y-m-d'), + 'extra' => array(), + ), + ), $formatted); + } + + /** + * Test issue #137 + */ + public function testIgnoresRecursiveObjectReferences() + { + // set up the recursion + $foo = new \stdClass(); + $bar = new \stdClass(); + + $foo->bar = $bar; + $bar->foo = $foo; + + // set an error handler to assert that the error is not raised anymore + $that = $this; + set_error_handler(function ($level, $message, $file, $line, $context) use ($that) { + if (error_reporting() & $level) { + restore_error_handler(); + $that->fail("$message should not be raised"); + } + }); + + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + $res = $reflMethod->invoke($formatter, array($foo, $bar), true); + + restore_error_handler(); + + $this->assertEquals(@json_encode(array($foo, $bar)), $res); + } + + public function testIgnoresInvalidTypes() + { + // set up the recursion + $resource = fopen(__FILE__, 'r'); + + // set an error handler to assert that the error is not raised anymore + $that = $this; + set_error_handler(function ($level, $message, $file, $line, $context) use ($that) { + if (error_reporting() & $level) { + restore_error_handler(); + $that->fail("$message should not be raised"); + } + }); + + $formatter = new NormalizerFormatter(); + $reflMethod = new \ReflectionMethod($formatter, 'toJson'); + $reflMethod->setAccessible(true); + $res = $reflMethod->invoke($formatter, array($resource), true); + + restore_error_handler(); + + $this->assertEquals(@json_encode(array($resource)), $res); + } +} + +class TestFooNorm +{ + public $foo = 'foo'; +} + +class TestBarNorm +{ + public function __toString() + { + return 'bar'; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php new file mode 100644 index 0000000..0b07e33 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Formatter/WildfireFormatterTest.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Formatter; + +use Monolog\Logger; + +class WildfireFormatterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testDefaultFormat() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1'), + 'message' => 'log', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '125|[{"Type":"ERROR","File":"","Line":"","Label":"meh"},' + .'{"message":"log","context":{"from":"logger"},"extra":{"ip":"127.0.0.1"}}]|', + $message + ); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testFormatWithFileAndLine() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array('from' => 'logger'), + 'datetime' => new \DateTime("@0"), + 'extra' => array('ip' => '127.0.0.1', 'file' => 'test', 'line' => 14), + 'message' => 'log', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '129|[{"Type":"ERROR","File":"test","Line":14,"Label":"meh"},' + .'{"message":"log","context":{"from":"logger"},"extra":{"ip":"127.0.0.1"}}]|', + $message + ); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::format + */ + public function testFormatWithoutContext() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $message = $wildfire->format($record); + + $this->assertEquals( + '58|[{"Type":"ERROR","File":"","Line":"","Label":"meh"},"log"]|', + $message + ); + } + + /** + * @covers Monolog\Formatter\WildfireFormatter::formatBatch + * @expectedException BadMethodCallException + */ + public function testBatchFormatThrowException() + { + $wildfire = new WildfireFormatter(); + $record = array( + 'level' => Logger::ERROR, + 'level_name' => 'ERROR', + 'channel' => 'meh', + 'context' => array(), + 'datetime' => new \DateTime("@0"), + 'extra' => array(), + 'message' => 'log', + ); + + $wildfire->formatBatch(array($record)); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Functional/Handler/FirePHPHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Functional/Handler/FirePHPHandlerTest.php new file mode 100644 index 0000000..65b5309 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Functional/Handler/FirePHPHandlerTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +spl_autoload_register(function($class) { + $file = __DIR__.'/../../../../src/'.strtr($class, '\\', '/').'.php'; + if (file_exists($file)) { + require $file; + + return true; + } +}); + +use Monolog\Logger; +use Monolog\Handler\FirePHPHandler; +use Monolog\Handler\ChromePHPHandler; + +$logger = new Logger('firephp'); +$logger->pushHandler(new FirePHPHandler); +$logger->pushHandler(new ChromePHPHandler()); + +$logger->addDebug('Debug'); +$logger->addInfo('Info'); +$logger->addWarning('Warning'); +$logger->addError('Error'); diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php new file mode 100644 index 0000000..01d522f --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/AbstractHandlerTest.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Processor\WebProcessor; + +class AbstractHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\AbstractHandler::__construct + * @covers Monolog\Handler\AbstractHandler::getLevel + * @covers Monolog\Handler\AbstractHandler::setLevel + * @covers Monolog\Handler\AbstractHandler::getBubble + * @covers Monolog\Handler\AbstractHandler::setBubble + * @covers Monolog\Handler\AbstractHandler::getFormatter + * @covers Monolog\Handler\AbstractHandler::setFormatter + */ + public function testConstructAndGetSet() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array(Logger::WARNING, false)); + $this->assertEquals(Logger::WARNING, $handler->getLevel()); + $this->assertEquals(false, $handler->getBubble()); + + $handler->setLevel(Logger::ERROR); + $handler->setBubble(true); + $handler->setFormatter($formatter = new LineFormatter); + $this->assertEquals(Logger::ERROR, $handler->getLevel()); + $this->assertEquals(true, $handler->getBubble()); + $this->assertSame($formatter, $handler->getFormatter()); + } + + /** + * @covers Monolog\Handler\AbstractHandler::handleBatch + */ + public function testHandleBatch() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + $handler->expects($this->exactly(2)) + ->method('handle'); + $handler->handleBatch(array($this->getRecord(), $this->getRecord())); + } + + /** + * @covers Monolog\Handler\AbstractHandler::isHandling + */ + public function testIsHandling() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array(Logger::WARNING, false)); + $this->assertTrue($handler->isHandling($this->getRecord())); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractHandler::getFormatter + * @covers Monolog\Handler\AbstractHandler::getDefaultFormatter + */ + public function testGetFormatterInitializesDefault() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + $this->assertInstanceOf('Monolog\Formatter\LineFormatter', $handler->getFormatter()); + } + + /** + * @covers Monolog\Handler\AbstractHandler::pushProcessor + * @covers Monolog\Handler\AbstractHandler::popProcessor + * @expectedException LogicException + */ + public function testPushPopProcessor() + { + $logger = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + + $logger->pushProcessor($processor1); + $logger->pushProcessor($processor2); + + $this->assertEquals($processor2, $logger->popProcessor()); + $this->assertEquals($processor1, $logger->popProcessor()); + $logger->popProcessor(); + } + + /** + * @covers Monolog\Handler\AbstractHandler::pushProcessor + * @expectedException InvalidArgumentException + */ + public function testPushProcessorWithNonCallable() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler'); + + $handler->pushProcessor(new \stdClass()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php new file mode 100644 index 0000000..3485bdf --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/AbstractProcessingHandlerTest.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Processor\WebProcessor; + +class AbstractProcessingHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleLowerLevelMessage() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::WARNING, true)); + $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleBubbling() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::DEBUG, true)); + $this->assertFalse($handler->handle($this->getRecord())); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleNotBubbling() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::DEBUG, false)); + $this->assertTrue($handler->handle($this->getRecord())); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::handle + */ + public function testHandleIsFalseWhenNotHandled() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::WARNING, false)); + $this->assertTrue($handler->handle($this->getRecord())); + $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\AbstractProcessingHandler::processRecord + */ + public function testProcessRecord() + { + $handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler'); + $handler->pushProcessor(new WebProcessor(array( + 'REQUEST_URI' => '', + 'REQUEST_METHOD' => '', + 'REMOTE_ADDR' => '', + 'SERVER_NAME' => '', + ))); + $handledRecord = null; + $handler->expects($this->once()) + ->method('write') + ->will($this->returnCallback(function($record) use (&$handledRecord) { + $handledRecord = $record; + })) + ; + $handler->handle($this->getRecord()); + $this->assertEquals(5, count($handledRecord['extra'])); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/AmqpExchangeMock.php b/vendor/monolog/monolog/tests/Monolog/Handler/AmqpExchangeMock.php new file mode 100644 index 0000000..3415c82 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/AmqpExchangeMock.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +class AmqpExchangeMock extends \AMQPExchange +{ + protected $messages = array(); + + public function __construct() + { + } + + public function publish($message, $routing_key, $params = 0, $attributes = array()) + { + $this->messages[] = array($message, $routing_key, $params, $attributes); + + return true; + } + + public function getMessages() + { + return $this->messages; + } + + public function setName($name) + { + return true; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php new file mode 100644 index 0000000..7369091 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/AmqpHandlerTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\RotatingFileHandler + */ +class AmqpHandlerTest extends TestCase +{ + public function setUp() + { + if (!class_exists('AMQPConnection') || !class_exists('AMQPExchange')) { + $this->markTestSkipped("amqp-php not installed"); + } + + if (!class_exists('AMQPChannel')) { + $this->markTestSkipped("Please update AMQP to version >= 1.0"); + } + } + + public function testHandle() + { + $exchange = $this->getExchange(); + + $handler = new AmqpHandler($exchange, 'log'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + array( + 'message' => 'test', + 'context' => array( + 'data' => array(), + 'foo' => 34, + ), + 'level' => 300, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'extra' => array(), + ), + 'warn.test', + 0, + array( + 'delivery_mode' => 2, + 'Content-type' => 'application/json' + ) + ); + + $handler->handle($record); + + $messages = $exchange->getMessages(); + $this->assertCount(1, $messages); + $messages[0][0] = json_decode($messages[0][0], true); + unset($messages[0][0]['datetime']); + $this->assertEquals($expected, $messages[0]); + } + + protected function getExchange() + { + return new AmqpExchangeMock(); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php new file mode 100644 index 0000000..beb08cf --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/BufferHandlerTest.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class BufferHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\BufferHandler::__construct + * @covers Monolog\Handler\BufferHandler::handle + * @covers Monolog\Handler\BufferHandler::close + */ + public function testHandleBuffers() + { + $test = new TestHandler(); + $handler = new BufferHandler($test); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + $handler->close(); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + + /** + * @covers Monolog\Handler\BufferHandler::close + * @covers Monolog\Handler\BufferHandler::flush + */ + public function testDestructPropagatesRecords() + { + $test = new TestHandler(); + $handler = new BufferHandler($test); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->__destruct(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleBufferLimit() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 2); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertFalse($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleBufferLimitWithFlushOnOverflow() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 3, Logger::DEBUG, true, true); + + // send two records + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertCount(0, $test->getRecords()); + + // overflow + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasDebugRecords()); + $this->assertCount(3, $test->getRecords()); + + // should buffer again + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertCount(3, $test->getRecords()); + + $handler->close(); + $this->assertCount(5, $test->getRecords()); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleLevel() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 0, Logger::INFO); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->close(); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertFalse($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::flush + */ + public function testFlush() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 0); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->flush(); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new BufferHandler($test); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->flush(); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php new file mode 100644 index 0000000..9fa4d50 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/ChromePHPHandlerTest.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\ChromePHPHandler + */ +class ChromePHPHandlerTest extends TestCase +{ + protected function setUp() + { + TestChromePHPHandler::reset(); + } + + public function testHeaders() + { + $handler = new TestChromePHPHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array( + 'version' => ChromePHPHandler::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array( + 'test', + 'test', + ), + 'request_uri' => '', + )))) + ); + + $this->assertEquals($expected, $handler->getHeaders()); + } + + public function testHeadersOverflow() + { + $handler = new TestChromePHPHandler(); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING, str_repeat('a', 150*1024))); + + // overflow chrome headers limit + $handler->handle($this->getRecord(Logger::WARNING, str_repeat('a', 100*1024))); + + $expected = array( + 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array( + 'version' => ChromePHPHandler::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array( + array( + 'test', + 'test', + 'unknown', + 'log', + ), + array( + 'test', + str_repeat('a', 150*1024), + 'unknown', + 'warn', + ), + array( + 'monolog', + 'Incomplete logs, chrome header size limit reached', + 'unknown', + 'warn', + ), + ), + 'request_uri' => '', + )))) + ); + + $this->assertEquals($expected, $handler->getHeaders()); + } + + public function testConcurrentHandlers() + { + $handler = new TestChromePHPHandler(); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $handler2 = new TestChromePHPHandler(); + $handler2->setFormatter($this->getIdentityFormatter()); + $handler2->handle($this->getRecord(Logger::DEBUG)); + $handler2->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array( + 'version' => ChromePHPHandler::VERSION, + 'columns' => array('label', 'log', 'backtrace', 'type'), + 'rows' => array( + 'test', + 'test', + 'test', + 'test', + ), + 'request_uri' => '', + )))) + ); + + $this->assertEquals($expected, $handler2->getHeaders()); + } +} + +class TestChromePHPHandler extends ChromePHPHandler +{ + protected $headers = array(); + + public static function reset() + { + self::$initialized = false; + self::$overflowed = false; + self::$json['rows'] = array(); + } + + protected function sendHeader($header, $content) + { + $this->headers[$header] = $content; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php new file mode 100644 index 0000000..78a1d15 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/CouchDBHandlerTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class CouchDBHandlerTest extends TestCase +{ + public function testHandle() + { + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + 'message' => 'test', + 'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34), + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'datetime' => $record['datetime']->format('Y-m-d H:i:s'), + 'extra' => array(), + ); + + $handler = new CouchDBHandler(); + + try { + $handler->handle($record); + } catch (\RuntimeException $e) { + $this->markTestSkipped('Could not connect to couchdb server on http://localhost:5984'); + } + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php new file mode 100644 index 0000000..d67da90 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/DoctrineCouchDBHandlerTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class DoctrineCouchDBHandlerTest extends TestCase +{ + protected function setup() + { + if (!class_exists('Doctrine\CouchDB\CouchDBClient')) { + $this->markTestSkipped('The "doctrine/couchdb" package is not installed'); + } + } + + public function testHandle() + { + $client = $this->getMockBuilder('Doctrine\\CouchDB\\CouchDBClient') + ->setMethods(array('postDocument')) + ->disableOriginalConstructor() + ->getMock(); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + 'message' => 'test', + 'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34), + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'datetime' => $record['datetime']->format('Y-m-d H:i:s'), + 'extra' => array(), + ); + + $client->expects($this->once()) + ->method('postDocument') + ->with($expected); + + $handler = new DoctrineCouchDBHandler($client); + $handler->handle($record); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.php new file mode 100644 index 0000000..19bfdb5 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/ErrorLogHandlerTest.php @@ -0,0 +1,43 @@ +handle($this->getRecord(Logger::ERROR)); + + $this->assertStringMatchesFormat('[%s] test.ERROR: test [] []', $GLOBALS['error_log'][0]); + $this->assertSame($GLOBALS['error_log'][1], $type); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php new file mode 100644 index 0000000..7713e6a --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/FingersCrossedHandlerTest.php @@ -0,0 +1,189 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy; +use Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy; + +class FingersCrossedHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossedHandler::handle + */ + public function testHandleBuffers() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 3); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + */ + public function testHandleStopsBufferingAfterTrigger() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @covers Monolog\Handler\FingersCrossedHandler::reset + */ + public function testHandleRestartBufferingAfterReset() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->reset(); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + */ + public function testHandleRestartBufferingAfterBeingTriggeredWhenStopBufferingIsDisabled() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::WARNING, 0, false, false); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + */ + public function testHandleBufferLimit() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::WARNING, 2); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertFalse($test->hasDebugRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + */ + public function testHandleWithCallback() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler(function($record, $handler) use ($test) { + return $test; + }); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertFalse($test->hasInfoRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 3); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + * @expectedException RuntimeException + */ + public function testHandleWithBadCallbackThrowsException() + { + $handler = new FingersCrossedHandler(function($record, $handler) { + return 'foo'; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::isHandling + */ + public function testIsHandlingAlways() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::ERROR); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::isHandlerActivated + */ + public function testErrorLevelActivationStrategy() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Logger::WARNING)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::__construct + * @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::isHandlerActivated + */ + public function testChannelLevelActivationStrategy() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, new ChannelLevelActivationStrategy(Logger::ERROR, array('othertest' => Logger::DEBUG))); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertFalse($test->hasWarningRecords()); + $record = $this->getRecord(Logger::DEBUG); + $record['channel'] = 'othertest'; + $handler->handle($record); + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasWarningRecords()); + } + + /** + * @covers Monolog\Handler\FingersCrossedHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new FingersCrossedHandler($test, Logger::INFO); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php new file mode 100644 index 0000000..2b7b76d --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/FirePHPHandlerTest.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\FirePHPHandler + */ +class FirePHPHandlerTest extends TestCase +{ + public function setUp() + { + TestFirePHPHandler::reset(); + } + + public function testHeaders() + { + $handler = new TestFirePHPHandler; + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-Wf-Protocol-1' => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2', + 'X-Wf-1-Structure-1' => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1', + 'X-Wf-1-Plugin-1' => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3', + 'X-Wf-1-1-1-1' => 'test', + 'X-Wf-1-1-1-2' => 'test', + ); + + $this->assertEquals($expected, $handler->getHeaders()); + } + + public function testConcurrentHandlers() + { + $handler = new TestFirePHPHandler; + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::WARNING)); + + $handler2 = new TestFirePHPHandler; + $handler2->setFormatter($this->getIdentityFormatter()); + $handler2->handle($this->getRecord(Logger::DEBUG)); + $handler2->handle($this->getRecord(Logger::WARNING)); + + $expected = array( + 'X-Wf-Protocol-1' => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2', + 'X-Wf-1-Structure-1' => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1', + 'X-Wf-1-Plugin-1' => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3', + 'X-Wf-1-1-1-1' => 'test', + 'X-Wf-1-1-1-2' => 'test', + ); + + $expected2 = array( + 'X-Wf-1-1-1-3' => 'test', + 'X-Wf-1-1-1-4' => 'test', + ); + + $this->assertEquals($expected, $handler->getHeaders()); + $this->assertEquals($expected2, $handler2->getHeaders()); + } +} + +class TestFirePHPHandler extends FirePHPHandler +{ + protected $headers = array(); + + public static function reset() + { + self::$initialized = false; + self::$messageIndex = 1; + } + + protected function sendHeader($header, $content) + { + $this->headers[$header] = $content; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/Fixtures/.gitkeep b/vendor/monolog/monolog/tests/Monolog/Handler/Fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php new file mode 100644 index 0000000..8e9b9f8 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/GelfHandlerTest.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\GelfMessageFormatter; + +class GelfHandlerTest extends TestCase +{ + public function setUp() + { + if (!class_exists("Gelf\MessagePublisher") || !class_exists("Gelf\Message")) { + $this->markTestSkipped("mlehner/gelf-php not installed"); + } + + require_once __DIR__ . '/GelfMocks.php'; + } + + /** + * @covers Monolog\Handler\GelfHandler::__construct + */ + public function testConstruct() + { + $handler = new GelfHandler($this->getMessagePublisher()); + $this->assertInstanceOf('Monolog\Handler\GelfHandler', $handler); + } + + protected function getHandler($messagePublisher) + { + $handler = new GelfHandler($messagePublisher); + + return $handler; + } + + protected function getMessagePublisher() + { + return new MockMessagePublisher('localhost'); + } + + public function testDebug() + { + $messagePublisher = $this->getMessagePublisher(); + $handler = $this->getHandler($messagePublisher); + + $record = $this->getRecord(Logger::DEBUG, "A test debug message"); + $handler->handle($record); + + $this->assertEquals(7, $messagePublisher->lastMessage->getLevel()); + $this->assertEquals('test', $messagePublisher->lastMessage->getFacility()); + $this->assertEquals($record['message'], $messagePublisher->lastMessage->getShortMessage()); + $this->assertEquals(null, $messagePublisher->lastMessage->getFullMessage()); + } + + public function testWarning() + { + $messagePublisher = $this->getMessagePublisher(); + $handler = $this->getHandler($messagePublisher); + + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $handler->handle($record); + + $this->assertEquals(4, $messagePublisher->lastMessage->getLevel()); + $this->assertEquals('test', $messagePublisher->lastMessage->getFacility()); + $this->assertEquals($record['message'], $messagePublisher->lastMessage->getShortMessage()); + $this->assertEquals(null, $messagePublisher->lastMessage->getFullMessage()); + } + + public function testInjectedGelfMessageFormatter() + { + $messagePublisher = $this->getMessagePublisher(); + $handler = $this->getHandler($messagePublisher); + + $handler->setFormatter(new GelfMessageFormatter('mysystem', 'EXT', 'CTX')); + + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $record['extra']['blarg'] = 'yep'; + $record['context']['from'] = 'logger'; + $handler->handle($record); + + $this->assertEquals('mysystem', $messagePublisher->lastMessage->getHost()); + $this->assertArrayHasKey('_EXTblarg', $messagePublisher->lastMessage->toArray()); + $this->assertArrayHasKey('_CTXfrom', $messagePublisher->lastMessage->toArray()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/GelfMocks.php b/vendor/monolog/monolog/tests/Monolog/Handler/GelfMocks.php new file mode 100644 index 0000000..dda8711 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/GelfMocks.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Gelf\MessagePublisher; +use Gelf\Message; + +class MockMessagePublisher extends MessagePublisher +{ + public function publish(Message $message) + { + $this->lastMessage = $message; + } + + public $lastMessage = null; +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php new file mode 100644 index 0000000..c6298a6 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/GroupHandlerTest.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class GroupHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\GroupHandler::__construct + * @expectedException InvalidArgumentException + */ + public function testConstructorOnlyTakesHandler() + { + new GroupHandler(array(new TestHandler(), "foo")); + } + + /** + * @covers Monolog\Handler\GroupHandler::__construct + * @covers Monolog\Handler\GroupHandler::handle + */ + public function testHandle() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new GroupHandler($testHandlers); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\GroupHandler::handleBatch + */ + public function testHandleBatch() + { + $testHandlers = array(new TestHandler(), new TestHandler()); + $handler = new GroupHandler($testHandlers); + $handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO))); + foreach ($testHandlers as $test) { + $this->assertTrue($test->hasDebugRecords()); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue(count($test->getRecords()) === 2); + } + } + + /** + * @covers Monolog\Handler\GroupHandler::isHandling + */ + public function testIsHandling() + { + $testHandlers = array(new TestHandler(Logger::ERROR), new TestHandler(Logger::WARNING)); + $handler = new GroupHandler($testHandlers); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::ERROR))); + $this->assertTrue($handler->isHandling($this->getRecord(Logger::WARNING))); + $this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG))); + } + + /** + * @covers Monolog\Handler\GroupHandler::handle + */ + public function testHandleUsesProcessors() + { + $test = new TestHandler(); + $handler = new GroupHandler(array($test)); + $handler->pushProcessor(function ($record) { + $record['extra']['foo'] = true; + + return $record; + }); + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertTrue($test->hasWarningRecords()); + $records = $test->getRecords(); + $this->assertTrue($records[0]['extra']['foo']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php new file mode 100644 index 0000000..c393cf4 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/HipChatHandlerTest.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Rafael Dohms + * @see https://www.hipchat.com/docs/api + */ +class HipChatHandlerTest extends TestCase +{ + + private $res; + private $handler; + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/v1\/rooms\/message\?format=json&auth_token=.* HTTP\/1.1\\r\\nHost: api.hipchat.com\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + /** + * @depends testWriteHeader + */ + public function testWriteContent($content) + { + $this->assertRegexp('/from=Monolog&room_id=room1¬ify=0&message=test1&message_format=text&color=red$/', $content); + } + + public function testWriteWithComplexMessage() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Backup of database "example" finished in 16 minutes.')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/message=Backup\+of\+database\+%22example%22\+finished\+in\+16\+minutes\./', $content); + } + + /** + * @dataProvider provideLevelColors + */ + public function testWriteWithErrorLevelsAndColors($level, $expectedColor) + { + $this->createHandler(); + $this->handler->handle($this->getRecord($level, 'Backup of database "example" finished in 16 minutes.')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/color='.$expectedColor.'/', $content); + } + + public function provideLevelColors() + { + return array( + array(Logger::DEBUG, 'gray'), + array(Logger::INFO, 'green'), + array(Logger::WARNING, 'yellow'), + array(Logger::ERROR, 'red'), + array(Logger::CRITICAL, 'red'), + array(Logger::ALERT, 'red'), + array(Logger::EMERGENCY,'red'), + array(Logger::NOTICE, 'green'), + ); + } + + private function createHandler($token = 'myToken', $room = 'room1', $name = 'Monolog', $notify = false) + { + $constructorArgs = array($token, $room, $name, $notify, Logger::DEBUG); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\HipChatHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter($this->getIdentityFormatter()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php new file mode 100644 index 0000000..6754f3d --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/MailHandlerTest.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\TestCase; + +class MailHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\MailHandler::handleBatch + */ + public function testHandleBatch() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->once()) + ->method('formatBatch'); // Each record is formatted + + $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler'); + $handler->expects($this->once()) + ->method('send'); + $handler->expects($this->never()) + ->method('write'); // write is for individual records + + $handler->setFormatter($formatter); + + $handler->handleBatch($this->getMultipleRecords()); + } + + /** + * @covers Monolog\Handler\MailHandler::handleBatch + */ + public function testHandleBatchNotSendsMailIfMessagesAreBelowLevel() + { + $records = array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + ); + + $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler'); + $handler->expects($this->never()) + ->method('send'); + $handler->setLevel(Logger::ERROR); + + $handler->handleBatch($records); + } + + /** + * @covers Monolog\Handler\MailHandler::write + */ + public function testHandle() + { + $handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler'); + + $record = $this->getRecord(); + $records = array($record); + $records[0]['formatted'] = '['.$record['datetime']->format('Y-m-d H:i:s').'] test.WARNING: test [] []'."\n"; + + $handler->expects($this->once()) + ->method('send') + ->with($records[0]['formatted'], $records); + + $handler->handle($record); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php b/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php new file mode 100644 index 0000000..fbaab9b --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/MockRavenClient.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Raven_Client; + +class MockRavenClient extends Raven_Client +{ + public function capture($data, $stack, $vars = null) + { + $this->lastData = $data; + $this->lastStack = $stack; + } + + public $lastData; + public $lastStack; +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php new file mode 100644 index 0000000..ce3433e --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/MongoDBHandlerTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class MongoDBHandlerTest extends TestCase +{ + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorShouldThrowExceptionForInvalidMongo() + { + new MongoDBHandler(new \stdClass(), 'DB', 'Collection'); + } + + public function testHandle() + { + $mongo = $this->getMock('Mongo', array('selectCollection')); + $collection = $this->getMock('stdClass', array('save')); + + $mongo->expects($this->once()) + ->method('selectCollection') + ->with('DB', 'Collection') + ->will($this->returnValue($collection)); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $expected = array( + 'message' => 'test', + 'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34), + 'level' => Logger::WARNING, + 'level_name' => 'WARNING', + 'channel' => 'test', + 'datetime' => $record['datetime']->format('Y-m-d H:i:s'), + 'extra' => array(), + ); + + $collection->expects($this->once()) + ->method('save') + ->with($expected); + + $handler = new MongoDBHandler($mongo, 'DB', 'Collection'); + $handler->handle($record); + } +} + +if (!class_exists('Mongo')) { + class Mongo + { + public function selectCollection() {} + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php new file mode 100644 index 0000000..50ceace --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/NativeMailerHandlerTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +class NativeMailerHandlerTest extends TestCase +{ + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorHeaderInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', "receiver@example.org\r\nFrom: faked@attacker.org"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterHeaderInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->addHeader("Content-Type: text/html\r\nFrom: faked@attacker.org"); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testSetterArrayHeaderInjection() + { + $mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org'); + $mailer->addHeader(array("Content-Type: text/html\r\nFrom: faked@attacker.org")); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php new file mode 100644 index 0000000..f959a36 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/NewRelicHandlerTest.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Psr\Log\LogLevel; + +class NewRelicHandlerTest extends TestCase +{ + public static $appname; + + public function setUp() + { + $this::$appname = null; + } + + /** + * @expectedException Monolog\Handler\MissingExtensionException + */ + public function testThehandlerThrowsAnExceptionIfTheNRExtensionIsNotLoaded() + { + $handler = new StubNewRelicHandlerWithoutExtension(); + $handler->handle($this->getRecord(Logger::ERROR)); + } + + public function testThehandlerCanHandleTheRecord() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR)); + } + + public function testThehandlerCanAddParamsToTheNewRelicTrace() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR, 'log message', array('a' => 'b'))); + } + + public function testTheAppNameIsNullByDefault() + { + $handler = new StubNewRelicHandler(); + $handler->handle($this->getRecord(Logger::ERROR, 'log message')); + + $this->assertEquals(null, $this::$appname); + } + + public function testTheAppNameCanBeInjectedFromtheConstructor() + { + $handler = new StubNewRelicHandler(LogLevel::ALERT, false, 'myAppName'); + $handler->handle($this->getRecord(Logger::ERROR, 'log message')); + + $this->assertEquals('myAppName', $this::$appname); + } + + public function testTheAppNameCanBeOverriddenFromEachLog() + { + $handler = new StubNewRelicHandler(LogLevel::ALERT, false, 'myAppName'); + $handler->handle($this->getRecord(Logger::ERROR, 'log message', array('appname' => 'logAppName'))); + + $this->assertEquals('logAppName', $this::$appname); + } +} + +class StubNewRelicHandlerWithoutExtension extends NewRelicHandler +{ + protected function isNewRelicEnabled() + { + return false; + } +} + +class StubNewRelicHandler extends NewRelicHandler +{ + protected function isNewRelicEnabled() + { + return true; + } +} + +function newrelic_notice_error() +{ + return true; +} + +function newrelic_set_appname($appname) +{ + return NewRelicHandlerTest::$appname = $appname; +} + +function newrelic_add_custom_parameter() +{ + return true; +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php new file mode 100644 index 0000000..292df78 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/NullHandlerTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\NullHandler::handle + */ +class NullHandlerTest extends TestCase +{ + public function testHandle() + { + $handler = new NullHandler(); + $this->assertTrue($handler->handle($this->getRecord())); + } + + public function testHandleLowerLevelRecord() + { + $handler = new NullHandler(Logger::WARNING); + $this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG))); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php new file mode 100644 index 0000000..e048a02 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/PushoverHandlerTest.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * Almost all examples (expected header, titles, messages) taken from + * https://www.pushover.net/api + * @author Sebastian Göttschkes + * @see https://www.pushover.net/api + */ +class PushoverHandlerTest extends TestCase +{ + + private $res; + private $handler; + + public function testWriteHeader() + { + $this->createHandler(); + $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/POST \/1\/messages.json HTTP\/1.1\\r\\nHost: api.pushover.net\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content); + + return $content; + } + + /** + * @depends testWriteHeader + */ + public function testWriteContent($content) + { + $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}$/', $content); + } + + public function testWriteWithComplexTitle() + { + $this->createHandler('myToken', 'myUser', 'Backup finished - SQL1', Logger::EMERGENCY); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/title=Backup\+finished\+-\+SQL1/', $content); + } + + public function testWriteWithComplexMessage() + { + $this->createHandler(); + $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'Backup of database "example" finished in 16 minutes.')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/message=Backup\+of\+database\+%22example%22\+finished\+in\+16\+minutes\./', $content); + } + + public function testWriteWithTooLongMessage() + { + $message = str_pad('test', 520, 'a'); + $this->createHandler(); + $this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications + $this->handler->handle($this->getRecord(Logger::CRITICAL, $message)); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $expectedMessage = substr($message, 0, 505); + + $this->assertRegexp('/message=' . $expectedMessage . '&title/', $content); + } + + public function testWriteWithHighPriority() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}&priority=1$/', $content); + } + + public function testWriteWithEmergencyPriority() + { + $this->createHandler(); + $this->handler->handle($this->getRecord(Logger::EMERGENCY, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog×tamp=\d{10}&priority=2&retry=30&expire=25200$/', $content); + } + + public function testWriteToMultipleUsers() + { + $this->createHandler('myToken', array('userA', 'userB')); + $this->handler->handle($this->getRecord(Logger::EMERGENCY, 'test1')); + fseek($this->res, 0); + $content = fread($this->res, 1024); + + $this->assertRegexp('/token=myToken&user=userA&message=test1&title=Monolog×tamp=\d{10}&priority=2&retry=30&expire=25200POST/', $content); + $this->assertRegexp('/token=myToken&user=userB&message=test1&title=Monolog×tamp=\d{10}&priority=2&retry=30&expire=25200$/', $content); + } + + private function createHandler($token = 'myToken', $user = 'myUser', $title = 'Monolog') + { + $constructorArgs = array($token, $user, $title); + $this->res = fopen('php://memory', 'a'); + $this->handler = $this->getMock( + '\Monolog\Handler\PushoverHandler', + array('fsockopen', 'streamSetTimeout', 'closeSocket'), + $constructorArgs + ); + + $reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->handler, 'localhost:1234'); + + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + $this->handler->expects($this->any()) + ->method('closeSocket') + ->will($this->returnValue(true)); + + $this->handler->setFormatter($this->getIdentityFormatter()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php new file mode 100644 index 0000000..9c13c3f --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/RavenHandlerTest.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\RavenHandler; + +class RavenHandlerTest extends TestCase +{ + public function setUp() + { + if (!class_exists("Raven_Client")) { + $this->markTestSkipped("raven/raven not installed"); + } + + require_once __DIR__ . '/MockRavenClient.php'; + } + + /** + * @covers Monolog\Handler\RavenHandler::__construct + */ + public function testConstruct() + { + $handler = new RavenHandler($this->getRavenClient()); + $this->assertInstanceOf('Monolog\Handler\RavenHandler', $handler); + } + + protected function getHandler($ravenClient) + { + $handler = new RavenHandler($ravenClient); + + return $handler; + } + + protected function getRavenClient() + { + $dsn = 'http://43f6017361224d098402974103bfc53d:a6a0538fc2934ba2bed32e08741b2cd3@marca.python.live.cheggnet.com:9000/1'; + + return new MockRavenClient($dsn); + } + + public function testDebug() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $record = $this->getRecord(Logger::DEBUG, "A test debug message"); + $handler->handle($record); + + $this->assertEquals($ravenClient::DEBUG, $ravenClient->lastData['level']); + $this->assertContains($record['message'], $ravenClient->lastData['message']); + } + + public function testWarning() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $record = $this->getRecord(Logger::WARNING, "A test warning message"); + $handler->handle($record); + + $this->assertEquals($ravenClient::WARNING, $ravenClient->lastData['level']); + $this->assertContains($record['message'], $ravenClient->lastData['message']); + } + + public function testException() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + try { + $this->methodThatThrowsAnException(); + } catch (\Exception $e) { + $record = $this->getRecord(Logger::ERROR, $e->getMessage(), array('exception' => $e)); + $handler->handle($record); + } + + $this->assertEquals($record['message'], $ravenClient->lastData['message']); + } + + public function testHandleBatch() + { + $records = $this->getMultipleRecords(); + + $logFormatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $logFormatter->expects($this->once())->method('formatBatch'); + + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->once())->method('format'); + + $handler = $this->getHandler($this->getRavenClient()); + $handler->setBatchFormatter($logFormatter); + $handler->setFormatter($formatter); + $handler->handleBatch($records); + } + + public function testHandleBatchDoNothingIfRecordsAreBelowLevel() + { + $records = array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + ); + + $handler = $this->getMock('Monolog\Handler\RavenHandler', null, array($this->getRavenClient())); + $handler->expects($this->never())->method('handle'); + $handler->setLevel(Logger::ERROR); + $handler->handleBatch($records); + } + + public function testGetSetBatchFormatter() + { + $ravenClient = $this->getRavenClient(); + $handler = $this->getHandler($ravenClient); + + $handler->setBatchFormatter($formatter = new LineFormatter()); + $this->assertSame($formatter, $handler->getBatchFormatter()); + } + + private function methodThatThrowsAnException() + { + throw new \Exception('This is an exception'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php new file mode 100644 index 0000000..3629f8a --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/RedisHandlerTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; + +class RedisHandlerTest extends TestCase +{ + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorShouldThrowExceptionForInvalidRedis() + { + new RedisHandler(new \stdClass(), 'key'); + } + + public function testConstructorShouldWorkWithPredis() + { + $redis = $this->getMock('Predis\Client'); + $this->assertInstanceof('Monolog\Handler\RedisHandler', new RedisHandler($redis, 'key')); + } + + public function testConstructorShouldWorkWithRedis() + { + $redis = $this->getMock('Redis'); + $this->assertInstanceof('Monolog\Handler\RedisHandler', new RedisHandler($redis, 'key')); + } + + public function testPredisHandle() + { + $redis = $this->getMock('Predis\Client', array('rpush')); + + // Predis\Client uses rpush + $redis->expects($this->once()) + ->method('rpush') + ->with('key', 'test'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key'); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } + + public function testRedisHandle() + { + $redis = $this->getMock('Redis', array('rpush')); + + // Redis uses rPush + $redis->expects($this->once()) + ->method('rPush') + ->with('key', 'test'); + + $record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34)); + + $handler = new RedisHandler($redis, 'key'); + $handler->setFormatter(new LineFormatter("%message%")); + $handler->handle($record); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php new file mode 100644 index 0000000..f4cefda --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/RotatingFileHandlerTest.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +/** + * @covers Monolog\Handler\RotatingFileHandler + */ +class RotatingFileHandlerTest extends TestCase +{ + public function setUp() + { + $dir = __DIR__.'/Fixtures'; + chmod($dir, 0777); + if (!is_writable($dir)) { + $this->markTestSkipped($dir.' must be writeable to test the RotatingFileHandler.'); + } + } + + public function testRotationCreatesNewFile() + { + touch(__DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400).'.rot'); + + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot'); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord()); + + $log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot'; + $this->assertTrue(file_exists($log)); + $this->assertEquals('test', file_get_contents($log)); + } + + /** + * @dataProvider rotationTests + */ + public function testRotation($createFile) + { + touch($old1 = __DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400).'.rot'); + touch($old2 = __DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400 * 2).'.rot'); + touch($old3 = __DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400 * 3).'.rot'); + touch($old4 = __DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400 * 4).'.rot'); + + $log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot'; + + if ($createFile) { + touch($log); + } + + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord()); + + $handler->close(); + + $this->assertTrue(file_exists($log)); + $this->assertTrue(file_exists($old1)); + $this->assertEquals($createFile, file_exists($old2)); + $this->assertEquals($createFile, file_exists($old3)); + $this->assertEquals($createFile, file_exists($old4)); + $this->assertEquals('test', file_get_contents($log)); + } + + public function rotationTests() + { + return array( + 'Rotation is triggered when the file of the current day is not present' + => array(true), + 'Rotation is not triggered when the file is already present' + => array(false), + ); + } + + public function testReuseCurrentFile() + { + $log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot'; + file_put_contents($log, "foo"); + $handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot'); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord()); + $this->assertEquals('footest', file_get_contents($log)); + } + + public function tearDown() + { + foreach (glob(__DIR__.'/Fixtures/*.rot') as $file) { + unlink($file); + } + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php new file mode 100644 index 0000000..c642bea --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SocketHandlerTest.php @@ -0,0 +1,283 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Pablo de Leon Belloc + */ +class SocketHandlerTest extends TestCase +{ + /** + * @var Monolog\Handler\SocketHandler + */ + private $handler; + + /** + * @var resource + */ + private $res; + + /** + * @expectedException UnexpectedValueException + */ + public function testInvalidHostname() + { + $this->createHandler('garbage://here'); + $this->writeRecord('data'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testBadConnectionTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setConnectionTimeout(-1); + } + + public function testSetConnectionTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setConnectionTimeout(10.1); + $this->assertEquals(10.1, $this->handler->getConnectionTimeout()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testBadTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setTimeout(-1); + } + + public function testSetTimeout() + { + $this->createHandler('localhost:1234'); + $this->handler->setTimeout(10.25); + $this->assertEquals(10.25, $this->handler->getTimeout()); + } + + public function testSetConnectionString() + { + $this->createHandler('tcp://localhost:9090'); + $this->assertEquals('tcp://localhost:9090', $this->handler->getConnectionString()); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownOnFsockopenError() + { + $this->setMockHandler(array('fsockopen')); + $this->handler->expects($this->once()) + ->method('fsockopen') + ->will($this->returnValue(false)); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownOnPfsockopenError() + { + $this->setMockHandler(array('pfsockopen')); + $this->handler->expects($this->once()) + ->method('pfsockopen') + ->will($this->returnValue(false)); + $this->handler->setPersistent(true); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testExceptionIsThrownIfCannotSetTimeout() + { + $this->setMockHandler(array('streamSetTimeout')); + $this->handler->expects($this->once()) + ->method('streamSetTimeout') + ->will($this->returnValue(false)); + $this->writeRecord('Hello world'); + } + + /** + * @expectedException RuntimeException + */ + public function testWriteFailsOnIfFwriteReturnsFalse() + { + $this->setMockHandler(array('fwrite')); + + $callback = function($arg) { + $map = array( + 'Hello world' => 6, + 'world' => false, + ); + + return $map[$arg]; + }; + + $this->handler->expects($this->exactly(2)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + + $this->writeRecord('Hello world'); + } + + /** + * @expectedException RuntimeException + */ + public function testWriteFailsIfStreamTimesOut() + { + $this->setMockHandler(array('fwrite', 'streamGetMetadata')); + + $callback = function($arg) { + $map = array( + 'Hello world' => 6, + 'world' => 5, + ); + + return $map[$arg]; + }; + + $this->handler->expects($this->exactly(1)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + $this->handler->expects($this->exactly(1)) + ->method('streamGetMetadata') + ->will($this->returnValue(array('timed_out' => true))); + + $this->writeRecord('Hello world'); + } + + /** + * @expectedException RuntimeException + */ + public function testWriteFailsOnIncompleteWrite() + { + $this->setMockHandler(array('fwrite', 'streamGetMetadata')); + + $res = $this->res; + $callback = function($string) use ($res) { + fclose($res); + + return strlen('Hello'); + }; + + $this->handler->expects($this->exactly(1)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + $this->handler->expects($this->exactly(1)) + ->method('streamGetMetadata') + ->will($this->returnValue(array('timed_out' => false))); + + $this->writeRecord('Hello world'); + } + + public function testWriteWithMemoryFile() + { + $this->setMockHandler(); + $this->writeRecord('test1'); + $this->writeRecord('test2'); + $this->writeRecord('test3'); + fseek($this->res, 0); + $this->assertEquals('test1test2test3', fread($this->res, 1024)); + } + + public function testWriteWithMock() + { + $this->setMockHandler(array('fwrite')); + + $callback = function($arg) { + $map = array( + 'Hello world' => 6, + 'world' => 5, + ); + + return $map[$arg]; + }; + + $this->handler->expects($this->exactly(2)) + ->method('fwrite') + ->will($this->returnCallback($callback)); + + $this->writeRecord('Hello world'); + } + + public function testClose() + { + $this->setMockHandler(); + $this->writeRecord('Hello world'); + $this->assertInternalType('resource', $this->res); + $this->handler->close(); + $this->assertFalse(is_resource($this->res), "Expected resource to be closed after closing handler"); + } + + public function testCloseDoesNotClosePersistentSocket() + { + $this->setMockHandler(); + $this->handler->setPersistent(true); + $this->writeRecord('Hello world'); + $this->assertTrue(is_resource($this->res)); + $this->handler->close(); + $this->assertTrue(is_resource($this->res)); + } + + private function createHandler($connectionString) + { + $this->handler = new SocketHandler($connectionString); + $this->handler->setFormatter($this->getIdentityFormatter()); + } + + private function writeRecord($string) + { + $this->handler->handle($this->getRecord(Logger::WARNING, $string)); + } + + private function setMockHandler(array $methods = array()) + { + $this->res = fopen('php://memory', 'a'); + + $defaultMethods = array('fsockopen', 'pfsockopen', 'streamSetTimeout'); + $newMethods = array_diff($methods, $defaultMethods); + + $finalMethods = array_merge($defaultMethods, $newMethods); + + $this->handler = $this->getMock( + '\Monolog\Handler\SocketHandler', $finalMethods, array('localhost:1234') + ); + + if (!in_array('fsockopen', $methods)) { + $this->handler->expects($this->any()) + ->method('fsockopen') + ->will($this->returnValue($this->res)); + } + + if (!in_array('pfsockopen', $methods)) { + $this->handler->expects($this->any()) + ->method('pfsockopen') + ->will($this->returnValue($this->res)); + } + + if (!in_array('streamSetTimeout', $methods)) { + $this->handler->expects($this->any()) + ->method('streamSetTimeout') + ->will($this->returnValue(true)); + } + + $this->handler->setFormatter($this->getIdentityFormatter()); + } + +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php new file mode 100644 index 0000000..63d4fef --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/StreamHandlerTest.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +class StreamHandlerTest extends TestCase +{ + /** + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWrite() + { + $handle = fopen('php://memory', 'a+'); + $handler = new StreamHandler($handle); + $handler->setFormatter($this->getIdentityFormatter()); + $handler->handle($this->getRecord(Logger::WARNING, 'test')); + $handler->handle($this->getRecord(Logger::WARNING, 'test2')); + $handler->handle($this->getRecord(Logger::WARNING, 'test3')); + fseek($handle, 0); + $this->assertEquals('testtest2test3', fread($handle, 100)); + } + + /** + * @covers Monolog\Handler\StreamHandler::close + */ + public function testClose() + { + $handle = fopen('php://memory', 'a+'); + $handler = new StreamHandler($handle); + $this->assertTrue(is_resource($handle)); + $handler->close(); + $this->assertFalse(is_resource($handle)); + } + + /** + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteCreatesTheStreamResource() + { + $handler = new StreamHandler('php://memory'); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException LogicException + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteMissingResource() + { + $handler = new StreamHandler(null); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException UnexpectedValueException + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteInvalidResource() + { + $handler = new StreamHandler('bogus://url'); + $handler->handle($this->getRecord()); + } + + /** + * @expectedException UnexpectedValueException + * @covers Monolog\Handler\StreamHandler::__construct + * @covers Monolog\Handler\StreamHandler::write + */ + public function testWriteNonExistingResource() + { + $handler = new StreamHandler('/foo/bar/baz/'.rand(0, 10000)); + $handler->handle($this->getRecord()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php new file mode 100644 index 0000000..98219ac --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/SyslogHandlerTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; +use Monolog\Logger; + +class SyslogHandlerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Handler\SyslogHandler::__construct + */ + public function testConstruct() + { + $handler = new SyslogHandler('test'); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + + $handler = new SyslogHandler('test', LOG_USER); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + + $handler = new SyslogHandler('test', 'user'); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + + $handler = new SyslogHandler('test', LOG_USER, Logger::DEBUG, true, LOG_PERROR); + $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); + } + + /** + * @covers Monolog\Handler\SyslogHandler::__construct + */ + public function testConstructInvalidFacility() + { + $this->setExpectedException('UnexpectedValueException'); + $handler = new SyslogHandler('test', 'unknown'); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php new file mode 100644 index 0000000..801d80a --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/TestHandlerTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @covers Monolog\Handler\TestHandler + */ +class TestHandlerTest extends TestCase +{ + /** + * @dataProvider methodProvider + */ + public function testHandler($method, $level) + { + $handler = new TestHandler; + $record = $this->getRecord($level, 'test'.$method); + $this->assertFalse($handler->{'has'.$method}($record)); + $this->assertFalse($handler->{'has'.$method.'Records'}()); + $handler->handle($record); + + $this->assertFalse($handler->{'has'.$method}('bar')); + $this->assertTrue($handler->{'has'.$method}($record)); + $this->assertTrue($handler->{'has'.$method}('test'.$method)); + $this->assertTrue($handler->{'has'.$method.'Records'}()); + + $records = $handler->getRecords(); + unset($records[0]['formatted']); + $this->assertEquals(array($record), $records); + } + + public function methodProvider() + { + return array( + array('Emergency', Logger::EMERGENCY), + array('Alert' , Logger::ALERT), + array('Critical' , Logger::CRITICAL), + array('Error' , Logger::ERROR), + array('Warning' , Logger::WARNING), + array('Info' , Logger::INFO), + array('Notice' , Logger::NOTICE), + array('Debug' , Logger::DEBUG), + ); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php b/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php new file mode 100644 index 0000000..416039e --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Handler/ZendMonitorHandlerTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; + +class ZendMonitorHandlerTest extends TestCase +{ + protected $zendMonitorHandler; + + public function setUp() + { + if (!function_exists('zend_monitor_custom_event')) { + $this->markTestSkipped('ZendServer is not installed'); + } + } + + /** + * @covers Monolog\Handler\ZendMonitorHandler::write + */ + public function testWrite() + { + $record = $this->getRecord(); + $formatterResult = array( + 'message' => $record['message'] + ); + + $zendMonitor = $this->getMockBuilder('Monolog\Handler\ZendMonitorHandler') + ->setMethods(array('writeZendMonitorCustomEvent', 'getDefaultFormatter')) + ->getMock(); + + $formatterMock = $this->getMockBuilder('Monolog\Formatter\NormalizerFormatter') + ->disableOriginalConstructor() + ->getMock(); + + $formatterMock->expects($this->once()) + ->method('format') + ->will($this->returnValue($formatterResult)); + + $zendMonitor->expects($this->once()) + ->method('getDefaultFormatter') + ->will($this->returnValue($formatterMock)); + + $levelMap = $zendMonitor->getLevelMap(); + + $zendMonitor->expects($this->once()) + ->method('writeZendMonitorCustomEvent') + ->with($levelMap[$record['level']], $record['message'], $formatterResult); + + $zendMonitor->handle($record); + } + + /** + * @covers Monolog\Handler\ZendMonitorHandler::getDefaultFormatter + */ + public function testGetDefaultFormatterReturnsNormalizerFormatter() + { + $zendMonitor = new ZendMonitorHandler(); + $this->assertInstanceOf('Monolog\Formatter\NormalizerFormatter', $zendMonitor->getDefaultFormatter()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/LoggerTest.php b/vendor/monolog/monolog/tests/Monolog/LoggerTest.php new file mode 100644 index 0000000..8bcbbf9 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/LoggerTest.php @@ -0,0 +1,409 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Processor\WebProcessor; +use Monolog\Handler\TestHandler; + +class LoggerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Monolog\Logger::getName + */ + public function testGetName() + { + $logger = new Logger('foo'); + $this->assertEquals('foo', $logger->getName()); + } + + /** + * @covers Monolog\Logger::getLevelName + */ + public function testGetLevelName() + { + $this->assertEquals('ERROR', Logger::getLevelName(Logger::ERROR)); + } + + /** + * @covers Monolog\Logger::getLevelName + * @expectedException InvalidArgumentException + */ + public function testGetLevelNameThrows() + { + Logger::getLevelName(5); + } + + /** + * @covers Monolog\Logger::__construct + */ + public function testChannel() + { + $logger = new Logger('foo'); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->addWarning('test'); + list($record) = $handler->getRecords(); + $this->assertEquals('foo', $record['channel']); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testLog() + { + $logger = new Logger(__METHOD__); + + $handler = $this->getMock('Monolog\Handler\NullHandler', array('handle')); + $handler->expects($this->once()) + ->method('handle'); + $logger->pushHandler($handler); + + $this->assertTrue($logger->addWarning('test')); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testLogNotHandled() + { + $logger = new Logger(__METHOD__); + + $handler = $this->getMock('Monolog\Handler\NullHandler', array('handle'), array(Logger::ERROR)); + $handler->expects($this->never()) + ->method('handle'); + $logger->pushHandler($handler); + + $this->assertFalse($logger->addWarning('test')); + } + + public function testHandlersInCtor() + { + $handler1 = new TestHandler; + $handler2 = new TestHandler; + $logger = new Logger(__METHOD__, array($handler1, $handler2)); + + $this->assertEquals($handler1, $logger->popHandler()); + $this->assertEquals($handler2, $logger->popHandler()); + } + + public function testProcessorsInCtor() + { + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + $logger = new Logger(__METHOD__, array(), array($processor1, $processor2)); + + $this->assertEquals($processor1, $logger->popProcessor()); + $this->assertEquals($processor2, $logger->popProcessor()); + } + + /** + * @covers Monolog\Logger::pushHandler + * @covers Monolog\Logger::popHandler + * @expectedException LogicException + */ + public function testPushPopHandler() + { + $logger = new Logger(__METHOD__); + $handler1 = new TestHandler; + $handler2 = new TestHandler; + + $logger->pushHandler($handler1); + $logger->pushHandler($handler2); + + $this->assertEquals($handler2, $logger->popHandler()); + $this->assertEquals($handler1, $logger->popHandler()); + $logger->popHandler(); + } + + /** + * @covers Monolog\Logger::pushProcessor + * @covers Monolog\Logger::popProcessor + * @expectedException LogicException + */ + public function testPushPopProcessor() + { + $logger = new Logger(__METHOD__); + $processor1 = new WebProcessor; + $processor2 = new WebProcessor; + + $logger->pushProcessor($processor1); + $logger->pushProcessor($processor2); + + $this->assertEquals($processor2, $logger->popProcessor()); + $this->assertEquals($processor1, $logger->popProcessor()); + $logger->popProcessor(); + } + + /** + * @covers Monolog\Logger::pushProcessor + * @expectedException InvalidArgumentException + */ + public function testPushProcessorWithNonCallable() + { + $logger = new Logger(__METHOD__); + + $logger->pushProcessor(new \stdClass()); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testProcessorsAreExecuted() + { + $logger = new Logger(__METHOD__); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->pushProcessor(function($record) { + $record['extra']['win'] = true; + + return $record; + }); + $logger->addError('test'); + list($record) = $handler->getRecords(); + $this->assertTrue($record['extra']['win']); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testProcessorsAreCalledOnlyOnce() + { + $logger = new Logger(__METHOD__); + $handler = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler->expects($this->any()) + ->method('handle') + ->will($this->returnValue(true)) + ; + $logger->pushHandler($handler); + + $processor = $this->getMockBuilder('Monolog\Processor\WebProcessor') + ->disableOriginalConstructor() + ->setMethods(array('__invoke')) + ->getMock() + ; + $processor->expects($this->once()) + ->method('__invoke') + ->will($this->returnArgument(0)) + ; + $logger->pushProcessor($processor); + + $logger->addError('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testProcessorsNotCalledWhenNotHandled() + { + $logger = new Logger(__METHOD__); + $handler = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler); + $that = $this; + $logger->pushProcessor(function($record) use ($that) { + $that->fail('The processor should not be called'); + }); + $logger->addAlert('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testHandlersNotCalledBeforeFirstHandling() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->never()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler1->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler1); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler2); + + $handler3 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler3->expects($this->once()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + $handler3->expects($this->never()) + ->method('handle') + ; + $logger->pushHandler($handler3); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testBubblingWhenTheHandlerReturnsFalse() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler1->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler1); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(false)) + ; + $logger->pushHandler($handler2); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::addRecord + */ + public function testNotBubblingWhenTheHandlerReturnsTrue() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler1->expects($this->never()) + ->method('handle') + ; + $logger->pushHandler($handler1); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + $handler2->expects($this->once()) + ->method('handle') + ->will($this->returnValue(true)) + ; + $logger->pushHandler($handler2); + + $logger->debug('test'); + } + + /** + * @covers Monolog\Logger::isHandling + */ + public function testIsHandling() + { + $logger = new Logger(__METHOD__); + + $handler1 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler1->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(false)) + ; + + $logger->pushHandler($handler1); + $this->assertFalse($logger->isHandling(Logger::DEBUG)); + + $handler2 = $this->getMock('Monolog\Handler\HandlerInterface'); + $handler2->expects($this->any()) + ->method('isHandling') + ->will($this->returnValue(true)) + ; + + $logger->pushHandler($handler2); + $this->assertTrue($logger->isHandling(Logger::DEBUG)); + } + + /** + * @dataProvider logMethodProvider + * @covers Monolog\Logger::addDebug + * @covers Monolog\Logger::addInfo + * @covers Monolog\Logger::addNotice + * @covers Monolog\Logger::addWarning + * @covers Monolog\Logger::addError + * @covers Monolog\Logger::addCritical + * @covers Monolog\Logger::addAlert + * @covers Monolog\Logger::addEmergency + * @covers Monolog\Logger::debug + * @covers Monolog\Logger::info + * @covers Monolog\Logger::notice + * @covers Monolog\Logger::warn + * @covers Monolog\Logger::err + * @covers Monolog\Logger::crit + * @covers Monolog\Logger::alert + * @covers Monolog\Logger::emerg + */ + public function testLogMethods($method, $expectedLevel) + { + $logger = new Logger('foo'); + $handler = new TestHandler; + $logger->pushHandler($handler); + $logger->{$method}('test'); + list($record) = $handler->getRecords(); + $this->assertEquals($expectedLevel, $record['level']); + } + + public function logMethodProvider() + { + return array( + // monolog methods + array('addDebug', Logger::DEBUG), + array('addInfo', Logger::INFO), + array('addNotice', Logger::NOTICE), + array('addWarning', Logger::WARNING), + array('addError', Logger::ERROR), + array('addCritical', Logger::CRITICAL), + array('addAlert', Logger::ALERT), + array('addEmergency', Logger::EMERGENCY), + + // ZF/Sf2 compat methods + array('debug', Logger::DEBUG), + array('info', Logger::INFO), + array('notice', Logger::NOTICE), + array('warn', Logger::WARNING), + array('err', Logger::ERROR), + array('crit', Logger::CRITICAL), + array('alert', Logger::ALERT), + array('emerg', Logger::EMERGENCY), + ); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php new file mode 100644 index 0000000..9adbe17 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/IntrospectionProcessorTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; +use Monolog\Handler\TestHandler; + +class IntrospectionProcessorTest extends TestCase +{ + public function getHandler() + { + $processor = new IntrospectionProcessor(); + $handler = new TestHandler(); + $handler->pushProcessor($processor); + + return $handler; + } + + public function testProcessorFromClass() + { + $handler = $this->getHandler(); + $tester = new \Acme\Tester; + $tester->test($handler, $this->getRecord()); + list($record) = $handler->getRecords(); + $this->assertEquals(__FILE__, $record['extra']['file']); + $this->assertEquals(58, $record['extra']['line']); + $this->assertEquals('Acme\Tester', $record['extra']['class']); + $this->assertEquals('test', $record['extra']['function']); + } + + public function testProcessorFromFunc() + { + $handler = $this->getHandler(); + \Acme\tester($handler, $this->getRecord()); + list($record) = $handler->getRecords(); + $this->assertEquals(__FILE__, $record['extra']['file']); + $this->assertEquals(64, $record['extra']['line']); + $this->assertEquals(null, $record['extra']['class']); + $this->assertEquals('Acme\tester', $record['extra']['function']); + } +} + +namespace Acme; + +class Tester +{ + public function test($handler, $record) + { + $handler->handle($record); + } +} + +function tester($handler, $record) +{ + $handler->handle($record); +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php new file mode 100644 index 0000000..4bdf22c --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/MemoryPeakUsageProcessorTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class MemoryPeakUsageProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\MemoryPeakUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessor() + { + $processor = new MemoryPeakUsageProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_peak_usage', $record['extra']); + $this->assertRegExp('#[0-9.]+ (M|K)?B$#', $record['extra']['memory_peak_usage']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php new file mode 100644 index 0000000..a30d6de --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/MemoryUsageProcessorTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class MemoryUsageProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\MemoryUsageProcessor::__invoke + * @covers Monolog\Processor\MemoryProcessor::formatBytes + */ + public function testProcessor() + { + $processor = new MemoryUsageProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('memory_usage', $record['extra']); + $this->assertRegExp('#[0-9.]+ (M|K)?B$#', $record['extra']['memory_usage']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php new file mode 100644 index 0000000..458d2a3 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/ProcessIdProcessorTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class ProcessIdProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\ProcessIdProcessor::__invoke + */ + public function testProcessor() + { + $processor = new ProcessIdProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('process_id', $record['extra']); + $this->assertInternalType('int', $record['extra']['process_id']); + $this->assertGreaterThan(0, $record['extra']['process_id']); + $this->assertEquals(getmypid(), $record['extra']['process_id']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php new file mode 100644 index 0000000..7ced62c --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/UidProcessorTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class UidProcessorTest extends TestCase +{ + /** + * @covers Monolog\Processor\UidProcessor::__invoke + */ + public function testProcessor() + { + $processor = new UidProcessor(); + $record = $processor($this->getRecord()); + $this->assertArrayHasKey('uid', $record['extra']); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php b/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php new file mode 100644 index 0000000..04a5422 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/Processor/WebProcessorTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Processor; + +use Monolog\TestCase; + +class WebProcessorTest extends TestCase +{ + public function testProcessor() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'HTTP_REFERER' => 'D', + 'SERVER_NAME' => 'F', + ); + + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertEquals($server['REQUEST_URI'], $record['extra']['url']); + $this->assertEquals($server['REMOTE_ADDR'], $record['extra']['ip']); + $this->assertEquals($server['REQUEST_METHOD'], $record['extra']['http_method']); + $this->assertEquals($server['HTTP_REFERER'], $record['extra']['referrer']); + $this->assertEquals($server['SERVER_NAME'], $record['extra']['server']); + } + + public function testProcessorDoNothingIfNoRequestUri() + { + $server = array( + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + ); + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertEmpty($record['extra']); + } + + public function testProcessorReturnNullIfNoHttpReferer() + { + $server = array( + 'REQUEST_URI' => 'A', + 'REMOTE_ADDR' => 'B', + 'REQUEST_METHOD' => 'C', + 'SERVER_NAME' => 'F', + ); + $processor = new WebProcessor($server); + $record = $processor($this->getRecord()); + $this->assertNull($record['extra']['referrer']); + } + + /** + * @expectedException UnexpectedValueException + */ + public function testInvalidData() + { + new WebProcessor(new \stdClass); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php b/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php new file mode 100644 index 0000000..ab89944 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/PsrLogCompatTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +use Monolog\Handler\TestHandler; +use Monolog\Formatter\LineFormatter; +use Monolog\Processor\PsrLogMessageProcessor; +use Psr\Log\Test\LoggerInterfaceTest; + +class PsrLogCompatTest extends LoggerInterfaceTest +{ + private $handler; + + public function getLogger() + { + $logger = new Logger('foo'); + $logger->pushHandler($handler = new TestHandler); + $logger->pushProcessor(new PsrLogMessageProcessor); + $handler->setFormatter(new LineFormatter('%level_name% %message%')); + + $this->handler = $handler; + + return $logger; + } + + public function getLogs() + { + $convert = function ($record) { + $lower = function ($match) { + return strtolower($match[0]); + }; + + return preg_replace_callback('{^[A-Z]+}', $lower, $record['formatted']); + }; + + return array_map($convert, $this->handler->getRecords()); + } +} diff --git a/vendor/monolog/monolog/tests/Monolog/TestCase.php b/vendor/monolog/monolog/tests/Monolog/TestCase.php new file mode 100644 index 0000000..1067b91 --- /dev/null +++ b/vendor/monolog/monolog/tests/Monolog/TestCase.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog; + +class TestCase extends \PHPUnit_Framework_TestCase +{ + /** + * @return array Record + */ + protected function getRecord($level = Logger::WARNING, $message = 'test', $context = array()) + { + return array( + 'message' => $message, + 'context' => $context, + 'level' => $level, + 'level_name' => Logger::getLevelName($level), + 'channel' => 'test', + 'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true))), + 'extra' => array(), + ); + } + + /** + * @return array + */ + protected function getMultipleRecords() + { + return array( + $this->getRecord(Logger::DEBUG, 'debug message 1'), + $this->getRecord(Logger::DEBUG, 'debug message 2'), + $this->getRecord(Logger::INFO, 'information'), + $this->getRecord(Logger::WARNING, 'warning'), + $this->getRecord(Logger::ERROR, 'error') + ); + } + + /** + * @return Monolog\Formatter\FormatterInterface + */ + protected function getIdentityFormatter() + { + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); + $formatter->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function($record) { return $record['message']; })); + + return $formatter; + } +} diff --git a/vendor/monolog/monolog/tests/bootstrap.php b/vendor/monolog/monolog/tests/bootstrap.php new file mode 100644 index 0000000..189f4a6 --- /dev/null +++ b/vendor/monolog/monolog/tests/bootstrap.php @@ -0,0 +1,13 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$loader = require_once __DIR__ . "/../vendor/autoload.php"; +$loader->add('Monolog\\', __DIR__); diff --git a/vendor/nesbot/carbon/.gitignore b/vendor/nesbot/carbon/.gitignore new file mode 100644 index 0000000..08db745 --- /dev/null +++ b/vendor/nesbot/carbon/.gitignore @@ -0,0 +1,2 @@ +vendor +composer.phar \ No newline at end of file diff --git a/vendor/nesbot/carbon/.travis.yml b/vendor/nesbot/carbon/.travis.yml new file mode 100644 index 0000000..2ac5894 --- /dev/null +++ b/vendor/nesbot/carbon/.travis.yml @@ -0,0 +1,11 @@ +language: php + +php: + - 5.3 + - 5.4 + - 5.5 + +before_script: + - composer install + +script: phpunit --coverage-text \ No newline at end of file diff --git a/vendor/nesbot/carbon/LICENSE b/vendor/nesbot/carbon/LICENSE new file mode 100644 index 0000000..8957ee6 --- /dev/null +++ b/vendor/nesbot/carbon/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) Brian Nesbitt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/vendor/nesbot/carbon/composer.json b/vendor/nesbot/carbon/composer.json new file mode 100644 index 0000000..6884b93 --- /dev/null +++ b/vendor/nesbot/carbon/composer.json @@ -0,0 +1,27 @@ +{ + "name": "nesbot/carbon", + "type": "library", + "description": "A simple API extension for DateTime.", + "keywords": [ + "date", + "time", + "DateTime" + ], + "homepage": "https://github.com/briannesbitt/Carbon", + "license": "MIT", + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-0": { + "Carbon": "src" + } + } +} \ No newline at end of file diff --git a/vendor/nesbot/carbon/history.md b/vendor/nesbot/carbon/history.md new file mode 100644 index 0000000..9381316 --- /dev/null +++ b/vendor/nesbot/carbon/history.md @@ -0,0 +1,41 @@ +1.3.0 / 2013-08-21 +================== + + * Added modifier methods firstOfMonth(), lastOfMonth(), nthOfMonth(), next(), previous(), and so on + * Added modifiers startOfWeek() and endOfWeek() + * Added testing helpers to allow mocking of new Carbon(), new Carbon('now') and Carbon::now() + * Added formatLocalized() to format a string using strftime() with the current locale + * Improved diffInSeconds() + * Improved [add|sub][Years|Months|Days|Hours|Minutes|Seconds|Weeks] + * Docblocks everywhere ;( + * Magic class properties + * Added PHP 5.5 to travis test coverage + * General Code cleanup + +1.2.0 / 2012-10-14 +================== + + * Added history.md + * Implemented __isset() (thanks @flevour) + * Simplified tomorrow()/yesterday() to rely on today()... more DRY + * Simplified __set() and fixed exception text + * Updated readme + +1.1.0 / 2012-09-16 +================== + + * Updated composer.json + * Added better error messaging for failed readme generation + * Fixed readme typos + * Added static helpers `today()`, `tomorrow()`, `yesterday()` + * Simplified `now()` code + +1.0.1 / 2012-09-10 +================== + + * Added travis-ci.org + +1.0.0 / 2012-09-10 +================== + + * Initial release diff --git a/vendor/nesbot/carbon/phpunit.xml.dist b/vendor/nesbot/carbon/phpunit.xml.dist new file mode 100644 index 0000000..2acc50d --- /dev/null +++ b/vendor/nesbot/carbon/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + + src/Carbon + + + + + + tests + + + \ No newline at end of file diff --git a/vendor/nesbot/carbon/readme.md b/vendor/nesbot/carbon/readme.md new file mode 100644 index 0000000..1794cbb --- /dev/null +++ b/vendor/nesbot/carbon/readme.md @@ -0,0 +1,768 @@ +> **This file is autogenerated. Please see the [Contributing](#about-contributing) section from more information.** + +# Carbon + +[![Build Status](https://secure.travis-ci.org/briannesbitt/Carbon.png)](http://travis-ci.org/briannesbitt/Carbon) + +A simple API extension for DateTime with PHP 5.3+ + +```php +printf("Right now is %s", Carbon::now()->toDateTimeString()); +printf("Right now in Vancouver is %s", Carbon::now('America/Vancouver')); //implicit __toString() +$tomorrow = Carbon::now()->addDay(); +$lastWeek = Carbon::now()->subWeek(); +$nextSummerOlympics = Carbon::createFromDate(2012)->addYears(4); + +$officialDate = Carbon::now()->toRFC2822String(); + +$howOldAmI = Carbon::createFromDate(1975, 5, 21)->age; + +$noonTodayLondonTime = Carbon::createFromTime(12, 0, 0, 'Europe/London'); + +$worldWillEnd = Carbon::createFromDate(2012, 12, 21, 'GMT'); + +// Don't really want to die so mock now +Carbon::setTestNow(Carbon::createFromDate(2000, 1, 1)); + +// comparisons are always done in UTC +if (Carbon::now()->gte($worldWillEnd)) { + die(); +} + +// Phew! Return to normal behaviour +Carbon::setTestNow(); + +if (Carbon::now()->isWeekend()) { + echo 'Party!'; +} +echo Carbon::now()->subMinutes(2)->diffForHumans(); // '2 minutes ago' + +// ... but also does 'from now', 'after' and 'before' +// rolling up to seconds, minutes, hours, days, months, years + +$daysSinceEpoch = Carbon::createFromTimeStamp(0)->diffInDays(); +``` + +## README Contents + +* [Installation](#install) + * [Requirements](#requirements) + * [With composer](#install-composer) + * [Without composer](#install-nocomposer) +* [API](#api) + * [Instantiation](#api-instantiation) + * [Testing Aids](#api-testing) + * [Getters](#api-getters) + * [Setters](#api-setters) + * [Fluent Setters](#api-settersfluent) + * [IsSet](#api-isset) + * [String Formatting and Localization](#api-formatting) + * [Common Formats](#api-commonformats) + * [Comparison](#api-comparison) + * [Addition and Subtraction](#api-addsub) + * [Difference](#api-difference) + * [Difference for Humans](#api-humandiff) + * [Modifiers](#api-modifiers) + * [Constants](#api-constants) +* [About](#about) + * [Contributing](#about-contributing) + * [Author](#about-author) + * [License](#about-license) + * [History](#about-history) + * [Why the name Carbon?](#about-whyname) + + +## Installation + + +### Requirements + +- Any flavour of PHP 5.3+ should do +- [optional] PHPUnit to execute the test suite + + +### With Composer + +The easiest way to install Carbon is via [composer](http://getcomposer.org/). Create the following `composer.json` file and run the `php composer.phar install` command to install it. + +```json +{ + "require": { + "nesbot/Carbon": "*" + } +} +``` + +```php + +### Without Composer + +Why are you not using [composer](http://getcomposer.org/)? Download [Carbon.php](https://github.com/briannesbitt/Carbon/blob/master/Carbon/Carbon.php) from the repo and save the file into your project path somewhere. + +```php + +## API + +The Carbon class is [inherited](http://php.net/manual/en/keyword.extends.php) from the PHP [DateTime](http://www.php.net/manual/en/class.datetime.php) class. + +```php + **Note: I live in Ottawa, Ontario, Canada and if the timezone is not specified in the examples then the default of 'America/Toronto' is to be assumed. Typically Ottawa is -0500 but when daylight savings time is on we are -0400.** + +Special care has been taken to ensure timezones are handled correctly, and where appropriate are based on the underlying DateTime implementation. For example all comparisons are done in UTC or in the timezone of the datetime being used. + +```php +$dtToronto = Carbon::createFromDate(2012, 1, 1, 'America/Toronto'); +$dtVancouver = Carbon::createFromDate(2012, 1, 1, 'America/Vancouver'); + +echo $dtVancouver->diffInHours($dtToronto); // 3 +``` + +Also `is` comparisons are done in the timezone of the provided Carbon instance. For example my current timezone is -13 hours from Tokyo. So `Carbon::now('Asia/Tokyo')->isToday()` would only return false for any time past 1 PM my time. This doesn't make sense since `now()` in tokyo is always today in Tokyo. Thus the comparison to `now()` is done in the same timezone as the current instance. + + +### Instantiation + +There are several different methods available to create a new instance of Carbon. First there is a constructor. It overrides the [parent constructor](http://www.php.net/manual/en/datetime.construct.php) and you are best to read about the first parameter from the PHP manual and understand the date/time string formats it accepts. You'll hopefully find yourself rarely using the constructor but rather relying on the explicit static methods for improved readability. + +```php +$carbon = new Carbon(); // equivalent to Carbon::now() +$carbon = new Carbon('first day of January 2008', 'America/Vancouver'); +echo get_class($carbon); // 'Carbon\Carbon' +``` + +You'll notice above that the timezone (2nd) parameter was passed as a string rather than a `\DateTimeZone` instance. All DateTimeZone parameters have been augmented so you can pass a DateTimeZone instance or a string and the timezone will be created for you. This is again shown in the next example which also introduces the `now()` function. + +```php +$now = Carbon::now(); + +$nowInLondonTz = Carbon::now(new DateTimeZone('Europe/London')); + +// or just pass the timezone as a string +$nowInLondonTz = Carbon::now('Europe/London'); +``` + +If you really love your fluid method calls and get frustrated by the extra line or ugly pair of brackets necessary when using the constructor you'll enjoy the `parse` method. + +```php +echo (new Carbon('first day of December 2008'))->addWeeks(2); // 2008-12-15 00:00:00 +echo Carbon::parse('first day of December 2008')->addWeeks(2); // 2008-12-15 00:00:00 +``` + +To accompany `now()`, a few other static instantiation helpers exist to create widely known instances. The only thing to really notice here is that `today()`, `tomorrow()` and `yesterday()`, besides behaving as expected, all accept a timezone parameter and each has their time value set to `00:00:00`. + +```php +$now = Carbon::now(); +echo $now; // 2013-08-21 00:33:54 +$today = Carbon::today(); +echo $today; // 2013-08-21 00:00:00 +$tomorrow = Carbon::tomorrow('Europe/London'); +echo $tomorrow; // 2013-08-22 00:00:00 +$yesterday = Carbon::yesterday(); +echo $yesterday; // 2013-08-20 00:00:00 +``` + +The next group of static helpers are the `createXXX()` helpers. Most of the static `create` functions allow you to provide as many or as few arguments as you want and will provide default values for all others. Generally default values are the current date, time or timezone. Higher values will wrap appropriately but invalid values will throw an `InvalidArgumentException` with an informative message. The message is obtained from an [DateTime::getLastErrors()](http://php.net/manual/en/datetime.getlasterrors.php) call. + +```php +Carbon::createFromDate($year, $month, $day, $tz); +Carbon::createFromTime($hour, $minute, $second, $tz); +Carbon::create($year, $month, $day, $hour, $minute, $second, $tz); +``` + +`createFromDate()` will default the time to now. `createFromTime()` will default the date to today. `create()` will default any null parameter to the current respective value. As before, the `$tz` defaults to the current timezone and otherwise can be a DateTimeZone instance or simply a string timezone value. The only special case for default values (mimicking the underlying PHP library) occurs when an hour value is specified but no minutes or seconds, they will get defaulted to 0. + +```php +$xmasThisYear = Carbon::createFromDate(null, 12, 25); // Year defaults to current year +$Y2K = Carbon::create(2000, 1, 1, 0, 0, 0); +$alsoY2K = Carbon::create(1999, 12, 31, 24); +$noonLondonTz = Carbon::createFromTime(12, 0, 0, 'Europe/London'); + +// A two digit minute could not be found +try { Carbon::create(1975, 5, 21, 22, -2, 0); } catch(InvalidArgumentException $x) { echo $x->getMessage(); } +``` + +```php +Carbon::createFromFormat($format, $time, $tz); +``` + +`createFromFormat()` is mostly a wrapper for the base php function [DateTime::createFromFormat](http://php.net/manual/en/datetime.createfromformat.php). The difference being again the `$tz` argument can be a DateTimeZone instance or a string timezone value. Also, if there are errors with the format this function will call the `DateTime::getLastErrors()` method and then throw a `InvalidArgumentException` with the errors as the message. If you look at the source for the `createXX()` functions above, they all make a call to `createFromFormat()`. + +```php +echo Carbon::createFromFormat('Y-m-d H', '1975-05-21 22')->toDateTimeString(); // 1975-05-21 22:00:00 +``` + +The final two create functions are for working with [unix timestamps](http://en.wikipedia.org/wiki/Unix_time). The first will create a Carbon instance equal to the given timestamp and will set the timezone as well or default it to the current timezone. The second, `createFromTimestampUTC()`, is different in that the timezone will remain UTC (GMT). The second acts the same as `Carbon::createFromFormat('@'.$timestamp)` but I have just made it a little more explicit. Negative timestamps are also allowed. + +```php +echo Carbon::createFromTimeStamp(-1)->toDateTimeString(); // 1969-12-31 18:59:59 +echo Carbon::createFromTimeStamp(-1, 'Europe/London')->toDateTimeString(); // 1970-01-01 00:59:59 +echo Carbon::createFromTimeStampUTC(-1)->toDateTimeString(); // 1969-12-31 23:59:59 +``` + +You can also create a `copy()` of an existing Carbon instance. As expected the date, time and timezone values are all copied to the new instance. + +```php +$dt = Carbon::now(); +echo $dt->diffInYears($dt->copy()->addYear()); // 1 + +// $dt was unchanged and still holds the value of Carbon:now() +``` + +Finally, if you find yourself inheriting a `\DateTime` instance from another library, fear not! You can create a `Carbon` instance via a friendly `instance()` function. + +```php +$dt = new \DateTime('first day of January 2008'); // <== instance from another API +$carbon = Carbon::instance($dt); +echo get_class($carbon); // 'Carbon\Carbon' +echo $carbon->toDateTimeString(); // 2008-01-01 00:00:00 +``` + + +### Testing Aids + +The testing methods allow you to set a Carbon instance (real or mock) to be returned when a "now" instance is created. The provided instance will be returned specifically under the following conditions: +- A call to the static now() method, ex. Carbon::now() +- When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) +- When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + +```php +$knownDate = Carbon::create(2001, 5, 21, 12); // create testing date +Carbon::setTestNow($knownDate); // set the mock (of course this could be a real mock object) +echo Carbon::now(); // 2001-05-21 12:00:00 +echo new Carbon(); // 2001-05-21 12:00:00 +echo Carbon::parse(); // 2001-05-21 12:00:00 +echo new Carbon('now'); // 2001-05-21 12:00:00 +echo Carbon::parse('now'); // 2001-05-21 12:00:00 +var_dump(Carbon::hasTestNow()); // bool(true) +Carbon::setTestNow(); // clear the mock +var_dump(Carbon::hasTestNow()); // bool(false) +echo Carbon::now(); // 2013-08-21 00:33:54 +``` + +A more meaning full example: + +```php +class SeasonalProduct +{ + protected $price; + + public function __construct($price) + { + $this->price = $price; + } + + public function getPrice() { + $multiplier = 1; + if (Carbon::now()->month == 12) { + $multiplier = 2; + } + + return $this->price * $multiplier; + } +} + +$product = new SeasonalProduct(100); +Carbon::setTestNow(Carbon::parse('first day of March 2000')); +echo $product->getPrice(); // 100 +Carbon::setTestNow(Carbon::parse('first day of December 2000')); +echo $product->getPrice(); // 200 +Carbon::setTestNow(Carbon::parse('first day of May 2000')); +echo $product->getPrice(); // 100 +Carbon::setTestNow(); +``` + + +### Getters + +The getters are implemented via PHP's `__get()` method. This enables you to access the value as if it was a property rather than a function call. + +```php +$dt = Carbon::create(2012, 9, 5, 23, 26, 11); + +// These getters specifically return integers, ie intval() +var_dump($dt->year); // int(2012) +var_dump($dt->month); // int(9) +var_dump($dt->day); // int(5) +var_dump($dt->hour); // int(23) +var_dump($dt->minute); // int(26) +var_dump($dt->second); // int(11) +var_dump($dt->dayOfWeek); // int(3) +var_dump($dt->dayOfYear); // int(248) +var_dump($dt->weekOfYear); // int(36) +var_dump($dt->daysInMonth); // int(30) +var_dump($dt->timestamp); // int(1346901971) +var_dump(Carbon::createFromDate(1975, 5, 21)->age); // int(38) calculated vs now in the same tz +var_dump($dt->quarter); // int(3) + +// Returns an int of seconds difference from UTC (+/- sign included) +var_dump(Carbon::createFromTimestampUTC(0)->offset); // int(0) +var_dump(Carbon::createFromTimestamp(0)->offset); // int(-18000) + +// Returns an int of hours difference from UTC (+/- sign included) +var_dump(Carbon::createFromTimestamp(0)->offsetHours); // int(-5) + +// Indicates if day light savings time is on +var_dump(Carbon::createFromDate(2012, 1, 1)->dst); // bool(false) + +// Gets the DateTimeZone instance +echo get_class(Carbon::now()->timezone); // DateTimeZone +echo get_class(Carbon::now()->tz); // DateTimeZone + +// Gets the DateTimeZone instance name, shortcut for ->timezone->getName() +echo Carbon::now()->timezoneName; // America/Toronto +echo Carbon::now()->tzName; // America/Toronto +``` + + +### Setters + +The following setters are implemented via PHP's `__set()` method. Its good to take note here that none of the setters, with the obvious exception of explicitly setting the timezone, will change the timezone of the instance. Specifically, setting the timestamp will not set the corresponding timezone to UTC. + +```php +$dt = Carbon::now(); + +$dt->year = 1975; +$dt->month = 13; // would force year++ and month = 1 +$dt->month = 5; +$dt->day = 21; +$dt->hour = 22; +$dt->minute = 32; +$dt->second = 5; + +$dt->timestamp = 169957925; // This will not change the timezone + +// Set the timezone via DateTimeZone instance or string +$dt->timezone = new DateTimeZone('Europe/London'); +$dt->timezone = 'Europe/London'; +$dt->tz = 'Europe/London'; +``` + + +### Fluent Setters + +No arguments are optional for the setters, but there are enough variety in the function definitions that you shouldn't need them anyway. Its good to take note here that none of the setters, with the obvious exception of explicitly setting the timezone, will change the timezone of the instance. Specifically, setting the timestamp will not set the corresponding timezone to UTC. + +```php +$dt = Carbon::now(); + +$dt->year(1975)->month(5)->day(21)->hour(22)->minute(32)->second(5)->toDateTimeString(); +$dt->setDate(1975, 5, 21)->setTime(22, 32, 5)->toDateTimeString(); +$dt->setDateTime(1975, 5, 21, 22, 32, 5)->toDateTimeString(); + +$dt->timestamp(169957925)->timezone('Europe/London'); + +$dt->tz('America/Toronto')->setTimezone('America/Vancouver'); +``` + + +### IsSet + +The PHP function `__isset()` is implemented. This was done as some external systems (ex. [Twig](http://twig.sensiolabs.org/doc/recipes.html#using-dynamic-object-properties)) validate the existence of a property before using it. This is done using the `isset()` or `empty()` method. You can read more about these on the PHP site: [__isset()](http://www.php.net/manual/en/language.oop5.overloading.php#object.isset), [isset()](http://www.php.net/manual/en/function.isset.php), [empty()](http://www.php.net/manual/en/function.empty.php). + +```php +var_dump(isset(Carbon::now()->iDoNotExist)); // bool(false) +var_dump(isset(Carbon::now()->hour)); // bool(true) +var_dump(empty(Carbon::now()->iDoNotExist)); // bool(true) +var_dump(empty(Carbon::now()->year)); // bool(false) +``` + + +### String Formatting and Localization + +All of the available `toXXXString()` methods rely on the base class method [DateTime::format()](http://php.net/manual/en/datetime.format.php). You'll notice the `__toString()` method is defined which allows a Carbon instance to be printed as a pretty date time string when used in a string context. + +```php +$dt = Carbon::create(1975, 12, 25, 14, 15, 16); + +var_dump($dt->toDateTimeString() == $dt); // bool(true) => uses __toString() +echo $dt->toDateString(); // 1975-12-25 +echo $dt->toFormattedDateString(); // Dec 25, 1975 +echo $dt->toTimeString(); // 14:15:16 +echo $dt->toDateTimeString(); // 1975-12-25 14:15:16 +echo $dt->toDayDateTimeString(); // Thu, Dec 25, 1975 2:15 PM + +// ... of course format() is still available +echo $dt->format('l jS \\of F Y h:i:s A'); // Thursday 25th of December 1975 02:15:16 PM +``` + +Unfortunately the base class DateTime does not have any localization support. To begin localization support a `formatLocalized($format)` method has been added. The implementation makes a call to [strftime](http://www.php.net/strftime) using the current instance timestamp. If you first set the current locale with [setlocale()](http://www.php.net/setlocale) then the string returned will be formatted in the correct locale. + +```php +setlocale(LC_TIME, 'German'); +echo $dt->formatLocalized('%A %d %B %Y'); // Donnerstag 25 Dezember 1975 +setlocale(LC_TIME, ''); +echo $dt->formatLocalized('%A %d %B %Y'); // Thursday 25 December 1975 +``` + + +## Common Formats + +The following are wrappers for the common formats provided in the [DateTime class](http://www.php.net/manual/en/class.datetime.php). + +```php +$dt = Carbon::now(); + +echo $dt->toATOMString(); // same as $dt->format(DateTime::ATOM); +echo $dt->toCOOKIEString(); +echo $dt->toISO8601String(); +echo $dt->toRFC822String(); +echo $dt->toRFC850String(); +echo $dt->toRFC1036String(); +echo $dt->toRFC1123String(); +echo $dt->toRFC2822String(); +echo $dt->toRFC3339String(); +echo $dt->toRSSString(); +echo $dt->toW3CString(); +``` + + +### Comparison + +Simple comparison is offered up via the following functions. Remember that the comparison is done in the UTC timezone so things aren't always as they seem. + +```php +$first = Carbon::create(2012, 9, 5, 23, 26, 11); +$second = Carbon::create(2012, 9, 5, 20, 26, 11, 'America/Vancouver'); + +echo $first->toDateTimeString(); // 2012-09-05 23:26:11 +echo $second->toDateTimeString(); // 2012-09-05 20:26:11 + +var_dump($first->eq($second)); // bool(true) +var_dump($first->ne($second)); // bool(false) +var_dump($first->gt($second)); // bool(false) +var_dump($first->gte($second)); // bool(true) +var_dump($first->lt($second)); // bool(false) +var_dump($first->lte($second)); // bool(true) + +$first->setDateTime(2012, 1, 1, 0, 0, 0); +$second->setDateTime(2012, 1, 1, 0, 0, 0); // Remember tz is 'America/Vancouver' + +var_dump($first->eq($second)); // bool(false) +var_dump($first->ne($second)); // bool(true) +var_dump($first->gt($second)); // bool(false) +var_dump($first->gte($second)); // bool(false) +var_dump($first->lt($second)); // bool(true) +var_dump($first->lte($second)); // bool(true) +``` + +To determine if the current instance is between two other instances you can use the aptly named `between()` method. The third parameter indicates if an equal to comparison should be done. The default is true which determines if its between or equal to the boundaries. + +```php +$first = Carbon::create(2012, 9, 5, 1); +$second = Carbon::create(2012, 9, 5, 5); +var_dump(Carbon::create(2012, 9, 5, 3)->between($first, $second)); // bool(true) +var_dump(Carbon::create(2012, 9, 5, 5)->between($first, $second)); // bool(true) +var_dump(Carbon::create(2012, 9, 5, 5)->between($first, $second, false)); // bool(false) +``` + +To handle the most used cases there are some simple helper functions that hopefully are obvious from their names. For the methods that compare to `now()` (ex. isToday()) in some manner the `now()` is created in the same timezone as the instance. + +```php +$dt = Carbon::now(); + +$dt->isWeekday(); +$dt->isWeekend(); +$dt->isYesterday(); +$dt->isToday(); +$dt->isTomorrow(); +$dt->isFuture(); +$dt->isPast(); +$dt->isLeapYear(); +``` + + +### Addition and Subtraction + +The default DateTime provides a couple of different methods for easily adding and subtracting time. There is `modify()`, `add()` and `sub()`. `modify()` takes a *magical* date/time format string, 'last day of next month', that it parses and applies the modification while `add()` and `sub()` use a `DateInterval` class thats not so obvious, `new \DateInterval('P6YT5M')`. Hopefully using these fluent functions will be more clear and easier to read after not seeing your code for a few weeks. But of course I don't make you choose since the base class functions are still available. + +```php +$dt = Carbon::create(2012, 1, 31, 0); + +echo $dt->toDateTimeString(); // 2012-01-31 00:00:00 + +echo $dt->addYears(5); // 2017-01-31 00:00:00 +echo $dt->addYear(); // 2018-01-31 00:00:00 +echo $dt->subYear(); // 2017-01-31 00:00:00 +echo $dt->subYears(5); // 2012-01-31 00:00:00 + +echo $dt->addMonths(60); // 2017-01-31 00:00:00 +echo $dt->addMonth(); // 2017-03-03 00:00:00 equivalent of $dt->month($dt->month + 1); so it wraps +echo $dt->subMonth(); // 2017-02-03 00:00:00 +echo $dt->subMonths(60); // 2012-02-03 00:00:00 + +echo $dt->addDays(29); // 2012-03-03 00:00:00 +echo $dt->addDay(); // 2012-03-04 00:00:00 +echo $dt->subDay(); // 2012-03-03 00:00:00 +echo $dt->subDays(29); // 2012-02-03 00:00:00 + +echo $dt->addWeekdays(4); // 2012-02-09 00:00:00 +echo $dt->addWeekday(); // 2012-02-10 00:00:00 +echo $dt->subWeekday(); // 2012-02-09 00:00:00 +echo $dt->subWeekdays(4); // 2012-02-03 00:00:00 + +echo $dt->addWeeks(3); // 2012-02-24 00:00:00 +echo $dt->addWeek(); // 2012-03-02 00:00:00 +echo $dt->subWeek(); // 2012-02-24 00:00:00 +echo $dt->subWeeks(3); // 2012-02-03 00:00:00 + +echo $dt->addHours(24); // 2012-02-04 00:00:00 +echo $dt->addHour(); // 2012-02-04 01:00:00 +echo $dt->subHour(); // 2012-02-04 00:00:00 +echo $dt->subHours(24); // 2012-02-03 00:00:00 + +echo $dt->addMinutes(61); // 2012-02-03 01:01:00 +echo $dt->addMinute(); // 2012-02-03 01:02:00 +echo $dt->subMinute(); // 2012-02-03 01:01:00 +echo $dt->subMinutes(61); // 2012-02-03 00:00:00 + +echo $dt->addSeconds(61); // 2012-02-03 00:01:01 +echo $dt->addSecond(); // 2012-02-03 00:01:02 +echo $dt->subSecond(); // 2012-02-03 00:01:01 +echo $dt->subSeconds(61); // 2012-02-03 00:00:00 +``` + +For fun you can also pass negative values to `addXXX()`, in fact that's how `subXXX()` is implemented. + + +### Difference + +These functions always return the **total difference** expressed in the specified time requested. This differs from the base class `diff()` function where an interval of 61 seconds would be returned as 1 minute and 1 second via a `DateInterval` instance. The `diffInMinutes()` function would simply return 1. All values are truncated and not rounded. Each function below has a default first parameter which is the Carbon instance to compare to, or null if you want to use `now()`. The 2nd parameter again is optional and indicates if you want the return value to be the absolute value or a relative value that might have a `-` (negative) sign if the passed in date is less than the current instance. This will default to true, return the absolute value. The comparisons are done in UTC. + +```php +// Carbon::diffInYears(Carbon $dt = null, $abs = true) + +echo Carbon::now('America/Vancouver')->diffInSeconds(Carbon::now('Europe/London')); // 0 + +$dtOttawa = Carbon::createFromDate(2000, 1, 1, 'America/Toronto'); +$dtVancouver = Carbon::createFromDate(2000, 1, 1, 'America/Vancouver'); +echo $dtOttawa->diffInHours($dtVancouver); // 3 + +echo $dtOttawa->diffInHours($dtVancouver, false); // 3 +echo $dtVancouver->diffInHours($dtOttawa, false); // -3 + +$dt = Carbon::create(2012, 1, 31, 0); +echo $dt->diffInDays($dt->copy()->addMonth()); // 31 +echo $dt->diffInDays($dt->copy()->subMonth(), false); // -31 + +$dt = Carbon::create(2012, 4, 30, 0); +echo $dt->diffInDays($dt->copy()->addMonth()); // 30 +echo $dt->diffInDays($dt->copy()->addWeek()); // 7 + +$dt = Carbon::create(2012, 1, 1, 0); +echo $dt->diffInMinutes($dt->copy()->addSeconds(59)); // 0 +echo $dt->diffInMinutes($dt->copy()->addSeconds(60)); // 1 +echo $dt->diffInMinutes($dt->copy()->addSeconds(119)); // 1 +echo $dt->diffInMinutes($dt->copy()->addSeconds(120)); // 2 + +// others that are defined +// diffInYears(), diffInMonths(), diffInDays() +// diffInHours(), diffInMinutes(), diffInSeconds() +``` + + +### Difference for Humans + +It is easier for humans to read `1 month ago` compared to 30 days ago. This is a common function seen in most date libraries so I thought I would add it here as well. It uses approximations for month being 30 days which then equates a year to 360 days. The lone argument for the function is the other Carbon instance to diff against, and of course it defaults to `now()` if not specified. + +This method will add a phrase after the difference value relative to the instance and the passed in instance. There are 4 possibilities: + +* When comparing a value in the past to default now: + * 1 hour ago + * 5 months ago + +* When comparing a value in the future to default now: + * 1 hour from now + * 5 months from now + +* When comparing a value in the past to another value: + * 1 hour before + * 5 months before + +* When comparing a value in the future to another value: + * 1 hour after + * 5 months after + +```php +// The most typical usage is for comments +// The instance is the date the comment was created and its being compared to default now() +echo Carbon::now()->subDays(5)->diffForHumans(); // 5 days ago + +echo Carbon::now()->diffForHumans(Carbon::now()->subYear()); // 1 year after + +$dt = Carbon::createFromDate(2011, 2, 1); + +echo $dt->diffForHumans($dt->copy()->addMonth()); // 28 days before +echo $dt->diffForHumans($dt->copy()->subMonth()); // 1 month after + +echo Carbon::now()->addSeconds(5)->diffForHumans(); // 5 seconds from now +``` + + +### Modifiers + +These group of methods perform helpful modifications to the current instance. Most of them are self explanatory from their names... or at least should be. You'll also notice that the startOfXXX() methods set the time to 00:00:00 and the endOfXXX() methods set the time to 23:59:59. + +```php +$dt = Carbon::create(2012, 1, 31, 12, 0, 0); +echo $dt->startOfDay(); // 2012-01-31 00:00:00 + +$dt = Carbon::create(2012, 1, 31, 12, 0, 0); +echo $dt->endOfDay(); // 2012-01-31 23:59:59 + +$dt = Carbon::create(2012, 1, 31, 12, 0, 0); +echo $dt->startOfMonth(); // 2012-01-01 00:00:00 + +$dt = Carbon::create(2012, 1, 31, 12, 0, 0); +echo $dt->endOfMonth(); // 2012-01-31 23:59:59 + +$dt = Carbon::create(2012, 1, 31, 12, 0, 0); +echo $dt->startOfWeek(); // 2012-01-30 00:00:00 +var_dump($dt->dayOfWeek == Carbon::MONDAY); // bool(true) : ISO8601 week starts on Monday + +$dt = Carbon::create(2012, 1, 31, 12, 0, 0); +echo $dt->endOfWeek(); // 2012-02-05 23:59:59 +var_dump($dt->dayOfWeek == Carbon::SUNDAY); // bool(true) : ISO8601 week ends on Sunday + +$dt = Carbon::create(2012, 1, 31, 12, 0, 0); +echo $dt->next(Carbon::WEDNESDAY); // 2012-02-01 00:00:00 +var_dump($dt->dayOfWeek == Carbon::WEDNESDAY); // bool(true) + +$dt = Carbon::create(2012, 1, 1, 12, 0, 0); +echo $dt->next(); // 2012-01-08 00:00:00 + +$dt = Carbon::create(2012, 1, 31, 12, 0, 0); +echo $dt->previous(Carbon::WEDNESDAY); // 2012-01-25 00:00:00 +var_dump($dt->dayOfWeek == Carbon::WEDNESDAY); // bool(true) + +$dt = Carbon::create(2012, 1, 1, 12, 0, 0); +echo $dt->previous(); // 2011-12-25 00:00:00 + +// others that are defined that are similar +// firstOfMonth(), lastOfMonth(), nthOfMonth() +// firstOfQuarter(), lastOfQuarter(), nthOfQuarter() +// firstOfYear(), lastOfYear(), nthOfYear() +``` + + +### Constants + +The following constants are defined in the Carbon class. + +* SUNDAY = 0 +* MONDAY = 1 +* TUESDAY = 2 +* WEDNESDAY = 3 +* THURSDAY = 4 +* FRIDAY = 5 +* SATURDAY = 6 +* MONTHS_PER_YEAR = 12 +* HOURS_PER_DAY = 24 +* MINUTES_PER_HOUR = 60 +* SECONDS_PER_MINUTE = 60 + +```php +$dt = Carbon::createFromDate(2012, 10, 6); +if ($dt->dayOfWeek === Carbon::SATURDAY) { + echo 'Place bets on Ottawa Senators Winning!'; +} +``` + + +## About + + +### Contributing + +I hate reading a readme.md file that has code errors and/or sample output that is incorrect. I tried something new with this project and wrote a quick readme parser that can **lint** sample source code or **execute** and inject the actual result into a generated readme. + +> **Don't make changes to the `readme.md` directly!!** + +Change the `readme.src.md` and then use the `readme.php` to generate the new `readme.md` file. It can be run at the command line using `php readme.php` from the project root. Maybe someday I'll extract this out to another project or at least run it with a post receive hook, but for now its just a local tool, deal with it. + +The commands are quickly explained below. To see some examples you can view the raw `readme.src.md` file in this repo. + +`{{::lint()}}` + +The `lint` command is meant for confirming the code is valid and will `eval()` the code passed into the function. Assuming there were no errors, the executed source code will then be injected back into the text replacing out the `{{::lint()}}`. When you look at the raw `readme.src.md` you will see that the code can span several lines. Remember the code is executed in the context of the running script so any variables will be available for the rest of the file. + + {{::lint($var = 'brian nesbitt';)}} => $var = 'brian nesbitt'; + +> As mentioned the `$var` can later be echo'd and you would get 'brian nesbitt' as all of the source is executed in the same scope. + +`{{varName::exec()}}` and `{{varName_eval}}` + +The `exec` command begins by performing an `eval()` on the code passed into the function. The executed source code will then be injected back into the text replacing out the `{{varName::exec()}}`. This will also create a variable named `varName_eval` that you can then place anywhere in the file and it will get replaced with the output of the `eval()`. You can use any type of output (`echo`, `printf`, `var_dump` etc) statement to return the result value as an output buffer is setup to capture the output. + + {{exVarName::exec(echo $var;)}} => echo $var; + {{exVarName_eval}} => brian nesbitt // $var is still set from above + +`/*pad()*/` + +The `pad()` is a special source modifier. This will pad the code block to the indicated number of characters using spaces. Its particularly handy for aligning `//` comments when showing results. + + {{exVarName1::exec(echo 12345;/*pad(20)*/)}} // {{exVarName1_eval}} + {{exVarName2::exec(echo 6;/*pad(20)*/)}} // {{exVarName2_eval}} + +... would generate to: + + echo 12345; // 12345 + echo 6; // 6 + +Apart from the readme the typical steps can be used to contribute your own improvements. + +* Fork +* Clone +* PHPUnit +* Branch +* PHPUnit +* Code +* PHPUnit +* Commit +* Push +* Pull request +* Relax and play Castle Crashers + + +### Author + +Brian Nesbitt - - + + +### License + +Carbon is licensed under the MIT License - see the `LICENSE` file for details + + +### History + +You can view the history of the Carbon project in the [history file](https://github.com/briannesbitt/Carbon/blob/master/history.md). + + +### Why the name Carbon? + +Read about [Carbon Dating](http://en.wikipedia.org/wiki/Radiocarbon_dating) \ No newline at end of file diff --git a/vendor/nesbot/carbon/readme.php b/vendor/nesbot/carbon/readme.php new file mode 100644 index 0000000..b759324 --- /dev/null +++ b/vendor/nesbot/carbon/readme.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require 'src/Carbon/Carbon.php'; + +use Carbon\Carbon; + +date_default_timezone_set('America/Toronto'); + +$readme = file_get_contents('readme.src.md'); + +$pre_src = 'use Carbon\Carbon; '; + +// {{intro::exec(echo Carbon::now()->subMinutes(2)->diffForHumans();)}} +preg_match_all('@{{(\w*)::(\w+)\((.+)\)}}@sU', $readme, $matches, PREG_SET_ORDER); + +foreach ($matches as $match) { + + list($orig, $name, $cmd, $src) = $match; + + $src = trim($src, "\n\r"); + + ob_start(); + $result = eval($pre_src . $src); + + $ob = ob_get_clean(); + + if ($result === false) { + echo "Failed lint check.". PHP_EOL . PHP_EOL; + + $error = error_get_last(); + if ($error != null) { + echo $error['message'] . ' on line ' . $error['line'] . PHP_EOL . PHP_EOL; + } + + echo "---- eval'd source ---- " . PHP_EOL . PHP_EOL; + + $i = 1; + foreach (preg_split("/$[\n\r]^/m", $src) as $ln) { + printf('%3s : %s%s', $i++, $ln, PHP_EOL); + } + + exit(1); + } + + // remove the extra newline from a var_dump + if (strpos($src, 'var_dump(') === 0) { + $ob = trim($ob); + } + + // Add any necessary padding to lineup comments + if (preg_match('@/\*pad\(([0-9]+)\)\*/@', $src, $matches)) { + $src = preg_replace('@/\*pad\(([0-9]+)\)\*/@', '', $src); + $src = str_pad($src, intval($matches[1])); + } + + // Inject the source code + $readme = str_replace($orig, $src, $readme); + + // Inject the eval'd result + if ($cmd == 'exec') { + $readme = str_replace('{{'.$name.'_eval}}', $ob, $readme); + } +} + +// allow for escaping a command +$readme = str_replace('\{\{', '{{', $readme); + +file_put_contents('readme.md', $readme); diff --git a/vendor/nesbot/carbon/readme.src.md b/vendor/nesbot/carbon/readme.src.md new file mode 100644 index 0000000..b557041 --- /dev/null +++ b/vendor/nesbot/carbon/readme.src.md @@ -0,0 +1,786 @@ +> **This file is autogenerated. Please see the [Contributing](#about-contributing) section from more information.** + +# Carbon + +[![Build Status](https://secure.travis-ci.org/briannesbitt/Carbon.png)](http://travis-ci.org/briannesbitt/Carbon) + +A simple API extension for DateTime with PHP 5.3+ + +```php +{{::lint( +printf("Right now is %s", Carbon::now()->toDateTimeString()); +printf("Right now in Vancouver is %s", Carbon::now('America/Vancouver')); //implicit __toString() +$tomorrow = Carbon::now()->addDay(); +$lastWeek = Carbon::now()->subWeek(); +$nextSummerOlympics = Carbon::createFromDate(2012)->addYears(4); + +$officialDate = Carbon::now()->toRFC2822String(); + +$howOldAmI = Carbon::createFromDate(1975, 5, 21)->age; + +$noonTodayLondonTime = Carbon::createFromTime(12, 0, 0, 'Europe/London'); + +$worldWillEnd = Carbon::createFromDate(2012, 12, 21, 'GMT'); + +// Don't really want to die so mock now +Carbon::setTestNow(Carbon::createFromDate(2000, 1, 1)); + +// comparisons are always done in UTC +if (Carbon::now()->gte($worldWillEnd)) { + die(); +} + +// Phew! Return to normal behaviour +Carbon::setTestNow(); + +if (Carbon::now()->isWeekend()) { + echo 'Party!'; +} +)}} +{{intro::exec(echo Carbon::now()->subMinutes(2)->diffForHumans();)}} // '{{intro_eval}}' + +// ... but also does 'from now', 'after' and 'before' +// rolling up to seconds, minutes, hours, days, months, years + +{{::lint( +$daysSinceEpoch = Carbon::createFromTimeStamp(0)->diffInDays(); +)}} +``` + +## README Contents + +* [Installation](#install) + * [Requirements](#requirements) + * [With composer](#install-composer) + * [Without composer](#install-nocomposer) +* [API](#api) + * [Instantiation](#api-instantiation) + * [Testing Aids](#api-testing) + * [Getters](#api-getters) + * [Setters](#api-setters) + * [Fluent Setters](#api-settersfluent) + * [IsSet](#api-isset) + * [String Formatting and Localization](#api-formatting) + * [Common Formats](#api-commonformats) + * [Comparison](#api-comparison) + * [Addition and Subtraction](#api-addsub) + * [Difference](#api-difference) + * [Difference for Humans](#api-humandiff) + * [Modifiers](#api-modifiers) + * [Constants](#api-constants) +* [About](#about) + * [Contributing](#about-contributing) + * [Author](#about-author) + * [License](#about-license) + * [History](#about-history) + * [Why the name Carbon?](#about-whyname) + + +## Installation + + +### Requirements + +- Any flavour of PHP 5.3+ should do +- [optional] PHPUnit to execute the test suite + + +### With Composer + +The easiest way to install Carbon is via [composer](http://getcomposer.org/). Create the following `composer.json` file and run the `php composer.phar install` command to install it. + +```json +{ + "require": { + "nesbot/Carbon": "*" + } +} +``` + +```php + +### Without Composer + +Why are you not using [composer](http://getcomposer.org/)? Download [Carbon.php](https://github.com/briannesbitt/Carbon/blob/master/Carbon/Carbon.php) from the repo and save the file into your project path somewhere. + +```php + +## API + +The Carbon class is [inherited](http://php.net/manual/en/keyword.extends.php) from the PHP [DateTime](http://www.php.net/manual/en/class.datetime.php) class. + +```php + **Note: I live in Ottawa, Ontario, Canada and if the timezone is not specified in the examples then the default of 'America/Toronto' is to be assumed. Typically Ottawa is -0500 but when daylight savings time is on we are -0400.** + +Special care has been taken to ensure timezones are handled correctly, and where appropriate are based on the underlying DateTime implementation. For example all comparisons are done in UTC or in the timezone of the datetime being used. + +```php +{{::lint($dtToronto = Carbon::createFromDate(2012, 1, 1, 'America/Toronto');)}} +{{::lint($dtVancouver = Carbon::createFromDate(2012, 1, 1, 'America/Vancouver');)}} + +{{tz::exec(echo $dtVancouver->diffInHours($dtToronto);)}} // {{tz_eval}} +``` + +Also `is` comparisons are done in the timezone of the provided Carbon instance. For example my current timezone is -13 hours from Tokyo. So `Carbon::now('Asia/Tokyo')->isToday()` would only return false for any time past 1 PM my time. This doesn't make sense since `now()` in tokyo is always today in Tokyo. Thus the comparison to `now()` is done in the same timezone as the current instance. + + +### Instantiation + +There are several different methods available to create a new instance of Carbon. First there is a constructor. It overrides the [parent constructor](http://www.php.net/manual/en/datetime.construct.php) and you are best to read about the first parameter from the PHP manual and understand the date/time string formats it accepts. You'll hopefully find yourself rarely using the constructor but rather relying on the explicit static methods for improved readability. + +```php +{{::lint($carbon = new Carbon();/*pad(40)*/)}} // equivalent to Carbon::now() +{{::lint($carbon = new Carbon('first day of January 2008', 'America/Vancouver');)}} +{{ctorType::exec(echo get_class($carbon);/*pad(40)*/)}} // '{{ctorType_eval}}' +``` + +You'll notice above that the timezone (2nd) parameter was passed as a string rather than a `\DateTimeZone` instance. All DateTimeZone parameters have been augmented so you can pass a DateTimeZone instance or a string and the timezone will be created for you. This is again shown in the next example which also introduces the `now()` function. + +```php +{{::lint( +$now = Carbon::now(); + +$nowInLondonTz = Carbon::now(new DateTimeZone('Europe/London')); + +// or just pass the timezone as a string +$nowInLondonTz = Carbon::now('Europe/London'); +)}} +``` + +If you really love your fluid method calls and get frustrated by the extra line or ugly pair of brackets necessary when using the constructor you'll enjoy the `parse` method. + +```php +{{parse1::exec(echo (new Carbon('first day of December 2008'))->addWeeks(2);/*pad(65)*/)}} // {{parse1_eval}} +{{parse2::exec(echo Carbon::parse('first day of December 2008')->addWeeks(2);/*pad(65)*/)}} // {{parse2_eval}} +``` + +To accompany `now()`, a few other static instantiation helpers exist to create widely known instances. The only thing to really notice here is that `today()`, `tomorrow()` and `yesterday()`, besides behaving as expected, all accept a timezone parameter and each has their time value set to `00:00:00`. + +```php +{{::lint($now = Carbon::now();)}} +{{now::exec(echo $now;/*pad(40)*/)}} // {{now_eval}} +{{::lint($today = Carbon::today();)}} +{{today::exec(echo $today;/*pad(40)*/)}} // {{today_eval}} +{{::lint($tomorrow = Carbon::tomorrow('Europe/London');)}} +{{tomorrow::exec(echo $tomorrow;/*pad(40)*/)}} // {{tomorrow_eval}} +{{::lint($yesterday = Carbon::yesterday();)}} +{{yesterday::exec(echo $yesterday;/*pad(40)*/)}} // {{yesterday_eval}} +``` + +The next group of static helpers are the `createXXX()` helpers. Most of the static `create` functions allow you to provide as many or as few arguments as you want and will provide default values for all others. Generally default values are the current date, time or timezone. Higher values will wrap appropriately but invalid values will throw an `InvalidArgumentException` with an informative message. The message is obtained from an [DateTime::getLastErrors()](http://php.net/manual/en/datetime.getlasterrors.php) call. + +```php +Carbon::createFromDate($year, $month, $day, $tz); +Carbon::createFromTime($hour, $minute, $second, $tz); +Carbon::create($year, $month, $day, $hour, $minute, $second, $tz); +``` + +`createFromDate()` will default the time to now. `createFromTime()` will default the date to today. `create()` will default any null parameter to the current respective value. As before, the `$tz` defaults to the current timezone and otherwise can be a DateTimeZone instance or simply a string timezone value. The only special case for default values (mimicking the underlying PHP library) occurs when an hour value is specified but no minutes or seconds, they will get defaulted to 0. + +```php +{{::lint( +$xmasThisYear = Carbon::createFromDate(null, 12, 25); // Year defaults to current year +$Y2K = Carbon::create(2000, 1, 1, 0, 0, 0); +$alsoY2K = Carbon::create(1999, 12, 31, 24); +$noonLondonTz = Carbon::createFromTime(12, 0, 0, 'Europe/London'); +)}} + +// {{createFromDateException_eval}} +{{createFromDateException::exec(try { Carbon::create(1975, 5, 21, 22, -2, 0); } catch(InvalidArgumentException $x) { echo $x->getMessage(); })}} +``` + +```php +Carbon::createFromFormat($format, $time, $tz); +``` + +`createFromFormat()` is mostly a wrapper for the base php function [DateTime::createFromFormat](http://php.net/manual/en/datetime.createfromformat.php). The difference being again the `$tz` argument can be a DateTimeZone instance or a string timezone value. Also, if there are errors with the format this function will call the `DateTime::getLastErrors()` method and then throw a `InvalidArgumentException` with the errors as the message. If you look at the source for the `createXX()` functions above, they all make a call to `createFromFormat()`. + +```php +{{createFromFormat1::exec(echo Carbon::createFromFormat('Y-m-d H', '1975-05-21 22')->toDateTimeString();)}} // {{createFromFormat1_eval}} +``` + +The final two create functions are for working with [unix timestamps](http://en.wikipedia.org/wiki/Unix_time). The first will create a Carbon instance equal to the given timestamp and will set the timezone as well or default it to the current timezone. The second, `createFromTimestampUTC()`, is different in that the timezone will remain UTC (GMT). The second acts the same as `Carbon::createFromFormat('@'.$timestamp)` but I have just made it a little more explicit. Negative timestamps are also allowed. + +```php +{{createFromTimeStamp1::exec(echo Carbon::createFromTimeStamp(-1)->toDateTimeString();/*pad(80)*/)}} // {{createFromTimeStamp1_eval}} +{{createFromTimeStamp2::exec(echo Carbon::createFromTimeStamp(-1, 'Europe/London')->toDateTimeString();/*pad(80)*/)}} // {{createFromTimeStamp2_eval}} +{{createFromTimeStampUTC::exec(echo Carbon::createFromTimeStampUTC(-1)->toDateTimeString();/*pad(80)*/)}} // {{createFromTimeStampUTC_eval}} +``` + +You can also create a `copy()` of an existing Carbon instance. As expected the date, time and timezone values are all copied to the new instance. + +```php +{{::lint($dt = Carbon::now();)}} +{{copy2::exec(echo $dt->diffInYears($dt->copy()->addYear());)}} // {{copy2_eval}} + +// $dt was unchanged and still holds the value of Carbon:now() +``` + +Finally, if you find yourself inheriting a `\DateTime` instance from another library, fear not! You can create a `Carbon` instance via a friendly `instance()` function. + +```php +{{::lint($dt = new \DateTime('first day of January 2008');)}} // <== instance from another API +{{::lint($carbon = Carbon::instance($dt);)}} +{{ctorType1::exec(echo get_class($carbon);/*pad(54)*/)}} // '{{ctorType1_eval}}' +{{ctorType2::exec(echo $carbon->toDateTimeString();/*pad(54)*/)}} // {{ctorType2_eval}} +``` + + +### Testing Aids + +The testing methods allow you to set a Carbon instance (real or mock) to be returned when a "now" instance is created. The provided instance will be returned specifically under the following conditions: +- A call to the static now() method, ex. Carbon::now() +- When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) +- When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + +```php +{{::lint($knownDate = Carbon::create(2001, 5, 21, 12);/*pad(54)*/)}} // create testing date +{{::lint(Carbon::setTestNow($knownDate);/*pad(54)*/)}} // set the mock (of course this could be a real mock object) +{{testaid1::exec(echo Carbon::now();/*pad(54)*/)}} // {{testaid1_eval}} +{{testaid2::exec(echo new Carbon();/*pad(54)*/)}} // {{testaid2_eval}} +{{testaid3::exec(echo Carbon::parse();/*pad(54)*/)}} // {{testaid3_eval}} +{{testaid4::exec(echo new Carbon('now');/*pad(54)*/)}} // {{testaid4_eval}} +{{testaid5::exec(echo Carbon::parse('now');/*pad(54)*/)}} // {{testaid5_eval}} +{{hasTestNow::exec(var_dump(Carbon::hasTestNow());/*pad(54)*/)}} // {{hasTestNow_eval}} +{{::lint(Carbon::setTestNow();/*pad(54)*/)}} // clear the mock +{{hasTestNowNo::exec(var_dump(Carbon::hasTestNow());/*pad(54)*/)}} // {{hasTestNowNo_eval}} +{{backToNormal::exec(echo Carbon::now();/*pad(54)*/)}} // {{backToNormal_eval}} +``` + +A more meaning full example: + +```php +{{::lint( +class SeasonalProduct +{ + protected $price; + + public function __construct($price) + { + $this->price = $price; + } + + public function getPrice() { + $multiplier = 1; + if (Carbon::now()->month == 12) { + $multiplier = 2; + } + + return $this->price * $multiplier; + } +} + +$product = new SeasonalProduct(100); +)}} +{{::lint(Carbon::setTestNow(Carbon::parse('first day of March 2000'));/*pad(40)*/)}} +{{product1::exec(echo $product->getPrice();/*pad(70)*/)}} // {{product1_eval}} +{{::lint(Carbon::setTestNow(Carbon::parse('first day of December 2000'));/*pad(40)*/)}} +{{product2::exec(echo $product->getPrice();/*pad(70)*/)}} // {{product2_eval}} +{{::lint(Carbon::setTestNow(Carbon::parse('first day of May 2000'));/*pad(40)*/)}} +{{product3::exec(echo $product->getPrice();/*pad(70)*/)}} // {{product3_eval}} +{{::lint(Carbon::setTestNow();)}} +``` + + +### Getters + +The getters are implemented via PHP's `__get()` method. This enables you to access the value as if it was a property rather than a function call. + +```php +{{::lint($dt = Carbon::create(2012, 9, 5, 23, 26, 11);)}} + +// These getters specifically return integers, ie intval() +{{getyear::exec(var_dump($dt->year);/*pad(54)*/)}} // {{getyear_eval}} +{{getmonth::exec(var_dump($dt->month);/*pad(54)*/)}} // {{getmonth_eval}} +{{getday::exec(var_dump($dt->day);/*pad(54)*/)}} // {{getday_eval}} +{{gethour::exec(var_dump($dt->hour);/*pad(54)*/)}} // {{gethour_eval}} +{{getminute::exec(var_dump($dt->minute);/*pad(54)*/)}} // {{getminute_eval}} +{{getsecond::exec(var_dump($dt->second);/*pad(54)*/)}} // {{getsecond_eval}} +{{getdow::exec(var_dump($dt->dayOfWeek);/*pad(54)*/)}} // {{getdow_eval}} +{{getdoy::exec(var_dump($dt->dayOfYear);/*pad(54)*/)}} // {{getdoy_eval}} +{{getwoy::exec(var_dump($dt->weekOfYear);/*pad(54)*/)}} // {{getwoy_eval}} +{{getdnm::exec(var_dump($dt->daysInMonth);/*pad(54)*/)}} // {{getdnm_eval}} +{{getts::exec(var_dump($dt->timestamp);/*pad(54)*/)}} // {{getts_eval}} +{{getage::exec(var_dump(Carbon::createFromDate(1975, 5, 21)->age);/*pad(54)*/)}} // {{getage_eval}} calculated vs now in the same tz +{{getq::exec(var_dump($dt->quarter);/*pad(54)*/)}} // {{getq_eval}} + +// Returns an int of seconds difference from UTC (+/- sign included) +{{get1::exec(var_dump(Carbon::createFromTimestampUTC(0)->offset);/*pad(54)*/)}} // {{get1_eval}} +{{get2::exec(var_dump(Carbon::createFromTimestamp(0)->offset);/*pad(54)*/)}} // {{get2_eval}} + +// Returns an int of hours difference from UTC (+/- sign included) +{{get3::exec(var_dump(Carbon::createFromTimestamp(0)->offsetHours);/*pad(54)*/)}} // {{get3_eval}} + +// Indicates if day light savings time is on +{{get4::exec(var_dump(Carbon::createFromDate(2012, 1, 1)->dst);/*pad(54)*/)}} // {{get4_eval}} + +// Gets the DateTimeZone instance +{{get5::exec(echo get_class(Carbon::now()->timezone);/*pad(54)*/)}} // {{get5_eval}} +{{get6::exec(echo get_class(Carbon::now()->tz);/*pad(54)*/)}} // {{get6_eval}} + +// Gets the DateTimeZone instance name, shortcut for ->timezone->getName() +{{get7::exec(echo Carbon::now()->timezoneName;/*pad(54)*/)}} // {{get7_eval}} +{{get8::exec(echo Carbon::now()->tzName;/*pad(54)*/)}} // {{get8_eval}} +``` + + +### Setters + +The following setters are implemented via PHP's `__set()` method. Its good to take note here that none of the setters, with the obvious exception of explicitly setting the timezone, will change the timezone of the instance. Specifically, setting the timestamp will not set the corresponding timezone to UTC. + +```php +{{::lint( +$dt = Carbon::now(); + +$dt->year = 1975; +$dt->month = 13; // would force year++ and month = 1 +$dt->month = 5; +$dt->day = 21; +$dt->hour = 22; +$dt->minute = 32; +$dt->second = 5; + +$dt->timestamp = 169957925; // This will not change the timezone + +// Set the timezone via DateTimeZone instance or string +$dt->timezone = new DateTimeZone('Europe/London'); +$dt->timezone = 'Europe/London'; +$dt->tz = 'Europe/London'; +)}} +``` + + +### Fluent Setters + +No arguments are optional for the setters, but there are enough variety in the function definitions that you shouldn't need them anyway. Its good to take note here that none of the setters, with the obvious exception of explicitly setting the timezone, will change the timezone of the instance. Specifically, setting the timestamp will not set the corresponding timezone to UTC. + +```php +{{::lint( +$dt = Carbon::now(); + +$dt->year(1975)->month(5)->day(21)->hour(22)->minute(32)->second(5)->toDateTimeString(); +$dt->setDate(1975, 5, 21)->setTime(22, 32, 5)->toDateTimeString(); +$dt->setDateTime(1975, 5, 21, 22, 32, 5)->toDateTimeString(); + +$dt->timestamp(169957925)->timezone('Europe/London'); + +$dt->tz('America/Toronto')->setTimezone('America/Vancouver'); +)}} +``` + + +### IsSet + +The PHP function `__isset()` is implemented. This was done as some external systems (ex. [Twig](http://twig.sensiolabs.org/doc/recipes.html#using-dynamic-object-properties)) validate the existence of a property before using it. This is done using the `isset()` or `empty()` method. You can read more about these on the PHP site: [__isset()](http://www.php.net/manual/en/language.oop5.overloading.php#object.isset), [isset()](http://www.php.net/manual/en/function.isset.php), [empty()](http://www.php.net/manual/en/function.empty.php). + +```php +{{isset1::exec(var_dump(isset(Carbon::now()->iDoNotExist));/*pad(50)*/)}} // {{isset1_eval}} +{{isset2::exec(var_dump(isset(Carbon::now()->hour));/*pad(50)*/)}} // {{isset2_eval}} +{{isset3::exec(var_dump(empty(Carbon::now()->iDoNotExist));/*pad(50)*/)}} // {{isset3_eval}} +{{isset4::exec(var_dump(empty(Carbon::now()->year));/*pad(50)*/)}} // {{isset4_eval}} +``` + + +### String Formatting and Localization + +All of the available `toXXXString()` methods rely on the base class method [DateTime::format()](http://php.net/manual/en/datetime.format.php). You'll notice the `__toString()` method is defined which allows a Carbon instance to be printed as a pretty date time string when used in a string context. + +```php +{{::lint($dt = Carbon::create(1975, 12, 25, 14, 15, 16);)}} + +{{format1::exec(var_dump($dt->toDateTimeString() == $dt);/*pad(50)*/)}} // {{format1_eval}} => uses __toString() +{{format2::exec(echo $dt->toDateString();/*pad(50)*/)}} // {{format2_eval}} +{{format3::exec(echo $dt->toFormattedDateString();/*pad(50)*/)}} // {{format3_eval}} +{{format4::exec(echo $dt->toTimeString();/*pad(50)*/)}} // {{format4_eval}} +{{format5::exec(echo $dt->toDateTimeString();/*pad(50)*/)}} // {{format5_eval}} +{{format6::exec(echo $dt->toDayDateTimeString();/*pad(50)*/)}} // {{format6_eval}} + +// ... of course format() is still available +{{format7::exec(echo $dt->format('l jS \\of F Y h:i:s A');/*pad(50)*/)}} // {{format7_eval}} +``` + +Unfortunately the base class DateTime does not have any localization support. To begin localization support a `formatLocalized($format)` method has been added. The implementation makes a call to [strftime](http://www.php.net/strftime) using the current instance timestamp. If you first set the current locale with [setlocale()](http://www.php.net/setlocale) then the string returned will be formatted in the correct locale. + +```php +{{::lint(setlocale(LC_TIME, 'German');/*pad(50)*/)}} +{{format20::exec(echo $dt->formatLocalized('%A %d %B %Y');/*pad(50)*/)}} // {{format20_eval}} +{{::lint(setlocale(LC_TIME, '');/*pad(50)*/)}} +{{format21::exec(echo $dt->formatLocalized('%A %d %B %Y');/*pad(50)*/)}} // {{format21_eval}} +``` + + +## Common Formats + +The following are wrappers for the common formats provided in the [DateTime class](http://www.php.net/manual/en/class.datetime.php). + +```php +$dt = Carbon::now(); + +echo $dt->toATOMString(); // same as $dt->format(DateTime::ATOM); +echo $dt->toCOOKIEString(); +echo $dt->toISO8601String(); +echo $dt->toRFC822String(); +echo $dt->toRFC850String(); +echo $dt->toRFC1036String(); +echo $dt->toRFC1123String(); +echo $dt->toRFC2822String(); +echo $dt->toRFC3339String(); +echo $dt->toRSSString(); +echo $dt->toW3CString(); +``` + + +### Comparison + +Simple comparison is offered up via the following functions. Remember that the comparison is done in the UTC timezone so things aren't always as they seem. + +```php +{{::lint($first = Carbon::create(2012, 9, 5, 23, 26, 11);)}} +{{::lint($second = Carbon::create(2012, 9, 5, 20, 26, 11, 'America/Vancouver');)}} + +{{compare1::exec(echo $first->toDateTimeString();/*pad(50)*/)}} // {{compare1_eval}} +{{compare2::exec(echo $second->toDateTimeString();/*pad(50)*/)}} // {{compare2_eval}} + +{{compare3::exec(var_dump($first->eq($second));/*pad(50)*/)}} // {{compare3_eval}} +{{compare4::exec(var_dump($first->ne($second));/*pad(50)*/)}} // {{compare4_eval}} +{{compare5::exec(var_dump($first->gt($second));/*pad(50)*/)}} // {{compare5_eval}} +{{compare6::exec(var_dump($first->gte($second));/*pad(50)*/)}} // {{compare6_eval}} +{{compare7::exec(var_dump($first->lt($second));/*pad(50)*/)}} // {{compare7_eval}} +{{compare8::exec(var_dump($first->lte($second));/*pad(50)*/)}} // {{compare8_eval}} + +{{::lint($first->setDateTime(2012, 1, 1, 0, 0, 0);)}} +{{::lint($second->setDateTime(2012, 1, 1, 0, 0, 0);/*pad(50)*/)}} // Remember tz is 'America/Vancouver' + +{{compare9::exec(var_dump($first->eq($second));/*pad(50)*/)}} // {{compare9_eval}} +{{compare10::exec(var_dump($first->ne($second));/*pad(50)*/)}} // {{compare10_eval}} +{{compare11::exec(var_dump($first->gt($second));/*pad(50)*/)}} // {{compare11_eval}} +{{compare12::exec(var_dump($first->gte($second));/*pad(50)*/)}} // {{compare12_eval}} +{{compare13::exec(var_dump($first->lt($second));/*pad(50)*/)}} // {{compare13_eval}} +{{compare14::exec(var_dump($first->lte($second));/*pad(50)*/)}} // {{compare14_eval}} +``` + +To determine if the current instance is between two other instances you can use the aptly named `between()` method. The third parameter indicates if an equal to comparison should be done. The default is true which determines if its between or equal to the boundaries. + +```php +{{::lint($first = Carbon::create(2012, 9, 5, 1);)}} +{{::lint($second = Carbon::create(2012, 9, 5, 5);)}} +{{between1::exec(var_dump(Carbon::create(2012, 9, 5, 3)->between($first, $second));/*pad(75)*/)}} // {{between1_eval}} +{{between2::exec(var_dump(Carbon::create(2012, 9, 5, 5)->between($first, $second));/*pad(75)*/)}} // {{between2_eval}} +{{between3::exec(var_dump(Carbon::create(2012, 9, 5, 5)->between($first, $second, false));/*pad(75)*/)}} // {{between3_eval}} +``` + +To handle the most used cases there are some simple helper functions that hopefully are obvious from their names. For the methods that compare to `now()` (ex. isToday()) in some manner the `now()` is created in the same timezone as the instance. + +```php +{{::lint( +$dt = Carbon::now(); + +$dt->isWeekday(); +$dt->isWeekend(); +$dt->isYesterday(); +$dt->isToday(); +$dt->isTomorrow(); +$dt->isFuture(); +$dt->isPast(); +$dt->isLeapYear(); +)}} +``` + + +### Addition and Subtraction + +The default DateTime provides a couple of different methods for easily adding and subtracting time. There is `modify()`, `add()` and `sub()`. `modify()` takes a *magical* date/time format string, 'last day of next month', that it parses and applies the modification while `add()` and `sub()` use a `DateInterval` class thats not so obvious, `new \DateInterval('P6YT5M')`. Hopefully using these fluent functions will be more clear and easier to read after not seeing your code for a few weeks. But of course I don't make you choose since the base class functions are still available. + +```php +{{::lint($dt = Carbon::create(2012, 1, 31, 0);)}} + +{{addsub1::exec(echo $dt->toDateTimeString();/*pad(40)*/)}} // {{addsub1_eval}} + +{{addsub2::exec(echo $dt->addYears(5);/*pad(40)*/)}} // {{addsub2_eval}} +{{addsub3::exec(echo $dt->addYear();/*pad(40)*/)}} // {{addsub3_eval}} +{{addsub4::exec(echo $dt->subYear();/*pad(40)*/)}} // {{addsub4_eval}} +{{addsub5::exec(echo $dt->subYears(5);/*pad(40)*/)}} // {{addsub5_eval}} + +{{addsub6::exec(echo $dt->addMonths(60);/*pad(40)*/)}} // {{addsub6_eval}} +{{addsub7::exec(echo $dt->addMonth();/*pad(40)*/)}} // {{addsub7_eval}} equivalent of $dt->month($dt->month + 1); so it wraps +{{addsub8::exec(echo $dt->subMonth();/*pad(40)*/)}} // {{addsub8_eval}} +{{addsub9::exec(echo $dt->subMonths(60);/*pad(40)*/)}} // {{addsub9_eval}} + +{{addsub10::exec(echo $dt->addDays(29);/*pad(40)*/)}} // {{addsub10_eval}} +{{addsub11::exec(echo $dt->addDay();/*pad(40)*/)}} // {{addsub11_eval}} +{{addsub12::exec(echo $dt->subDay();/*pad(40)*/)}} // {{addsub12_eval}} +{{addsub13::exec(echo $dt->subDays(29);/*pad(40)*/)}} // {{addsub13_eval}} + +{{addsub14::exec(echo $dt->addWeekdays(4);/*pad(40)*/)}} // {{addsub14_eval}} +{{addsub15::exec(echo $dt->addWeekday();/*pad(40)*/)}} // {{addsub15_eval}} +{{addsub16::exec(echo $dt->subWeekday();/*pad(40)*/)}} // {{addsub16_eval}} +{{addsub17::exec(echo $dt->subWeekdays(4);/*pad(40)*/)}} // {{addsub17_eval}} + +{{addsub18::exec(echo $dt->addWeeks(3);/*pad(40)*/)}} // {{addsub18_eval}} +{{addsub19::exec(echo $dt->addWeek();/*pad(40)*/)}} // {{addsub19_eval}} +{{addsub20::exec(echo $dt->subWeek();/*pad(40)*/)}} // {{addsub20_eval}} +{{addsub21::exec(echo $dt->subWeeks(3);/*pad(40)*/)}} // {{addsub21_eval}} + +{{addsub22::exec(echo $dt->addHours(24);/*pad(40)*/)}} // {{addsub22_eval}} +{{addsub23::exec(echo $dt->addHour();/*pad(40)*/)}} // {{addsub23_eval}} +{{addsub24::exec(echo $dt->subHour();/*pad(40)*/)}} // {{addsub24_eval}} +{{addsub25::exec(echo $dt->subHours(24);/*pad(40)*/)}} // {{addsub25_eval}} + +{{addsub26::exec(echo $dt->addMinutes(61);/*pad(40)*/)}} // {{addsub26_eval}} +{{addsub27::exec(echo $dt->addMinute();/*pad(40)*/)}} // {{addsub27_eval}} +{{addsub28::exec(echo $dt->subMinute();/*pad(40)*/)}} // {{addsub28_eval}} +{{addsub29::exec(echo $dt->subMinutes(61);/*pad(40)*/)}} // {{addsub29_eval}} + +{{addsub30::exec(echo $dt->addSeconds(61);/*pad(40)*/)}} // {{addsub30_eval}} +{{addsub31::exec(echo $dt->addSecond();/*pad(40)*/)}} // {{addsub31_eval}} +{{addsub32::exec(echo $dt->subSecond();/*pad(40)*/)}} // {{addsub32_eval}} +{{addsub33::exec(echo $dt->subSeconds(61);/*pad(40)*/)}} // {{addsub33_eval}} +``` + +For fun you can also pass negative values to `addXXX()`, in fact that's how `subXXX()` is implemented. + + +### Difference + +These functions always return the **total difference** expressed in the specified time requested. This differs from the base class `diff()` function where an interval of 61 seconds would be returned as 1 minute and 1 second via a `DateInterval` instance. The `diffInMinutes()` function would simply return 1. All values are truncated and not rounded. Each function below has a default first parameter which is the Carbon instance to compare to, or null if you want to use `now()`. The 2nd parameter again is optional and indicates if you want the return value to be the absolute value or a relative value that might have a `-` (negative) sign if the passed in date is less than the current instance. This will default to true, return the absolute value. The comparisons are done in UTC. + +```php +// Carbon::diffInYears(Carbon $dt = null, $abs = true) + +{{diff1::exec(echo Carbon::now('America/Vancouver')->diffInSeconds(Carbon::now('Europe/London'));)}} // {{diff1_eval}} + +{{::lint($dtOttawa = Carbon::createFromDate(2000, 1, 1, 'America/Toronto');)}} +{{::lint($dtVancouver = Carbon::createFromDate(2000, 1, 1, 'America/Vancouver');)}} +{{diff4::exec(echo $dtOttawa->diffInHours($dtVancouver);/*pad(70)*/)}} // {{diff4_eval}} + +{{diff5::exec(echo $dtOttawa->diffInHours($dtVancouver, false);/*pad(70)*/)}} // {{diff5_eval}} +{{diff6::exec(echo $dtVancouver->diffInHours($dtOttawa, false);/*pad(70)*/)}} // {{diff6_eval}} + +{{::lint($dt = Carbon::create(2012, 1, 31, 0);)}} +{{diff8::exec(echo $dt->diffInDays($dt->copy()->addMonth());/*pad(70)*/)}} // {{diff8_eval}} +{{diff9::exec(echo $dt->diffInDays($dt->copy()->subMonth(), false);/*pad(70)*/)}} // {{diff9_eval}} + +{{::lint($dt = Carbon::create(2012, 4, 30, 0);)}} +{{diff11::exec(echo $dt->diffInDays($dt->copy()->addMonth());/*pad(70)*/)}} // {{diff11_eval}} +{{diff12::exec(echo $dt->diffInDays($dt->copy()->addWeek());/*pad(70)*/)}} // {{diff12_eval}} + +{{::lint($dt = Carbon::create(2012, 1, 1, 0);)}} +{{diff14::exec(echo $dt->diffInMinutes($dt->copy()->addSeconds(59));/*pad(70)*/)}} // {{diff14_eval}} +{{diff15::exec(echo $dt->diffInMinutes($dt->copy()->addSeconds(60));/*pad(70)*/)}} // {{diff15_eval}} +{{diff16::exec(echo $dt->diffInMinutes($dt->copy()->addSeconds(119));/*pad(70)*/)}} // {{diff16_eval}} +{{diff17::exec(echo $dt->diffInMinutes($dt->copy()->addSeconds(120));/*pad(70)*/)}} // {{diff17_eval}} + +// others that are defined +// diffInYears(), diffInMonths(), diffInDays() +// diffInHours(), diffInMinutes(), diffInSeconds() +``` + + +### Difference for Humans + +It is easier for humans to read `1 month ago` compared to 30 days ago. This is a common function seen in most date libraries so I thought I would add it here as well. It uses approximations for month being 30 days which then equates a year to 360 days. The lone argument for the function is the other Carbon instance to diff against, and of course it defaults to `now()` if not specified. + +This method will add a phrase after the difference value relative to the instance and the passed in instance. There are 4 possibilities: + +* When comparing a value in the past to default now: + * 1 hour ago + * 5 months ago + +* When comparing a value in the future to default now: + * 1 hour from now + * 5 months from now + +* When comparing a value in the past to another value: + * 1 hour before + * 5 months before + +* When comparing a value in the future to another value: + * 1 hour after + * 5 months after + +```php +// The most typical usage is for comments +// The instance is the date the comment was created and its being compared to default now() +{{humandiff1::exec(echo Carbon::now()->subDays(5)->diffForHumans();/*pad(62)*/)}} // {{humandiff1_eval}} + +{{humandiff2::exec(echo Carbon::now()->diffForHumans(Carbon::now()->subYear());/*pad(62)*/)}} // {{humandiff2_eval}} + +{{::lint($dt = Carbon::createFromDate(2011, 2, 1);)}} + +{{humandiff4::exec(echo $dt->diffForHumans($dt->copy()->addMonth());/*pad(62)*/)}} // {{humandiff4_eval}} +{{humandiff5::exec(echo $dt->diffForHumans($dt->copy()->subMonth());/*pad(62)*/)}} // {{humandiff5_eval}} + +{{humandiff6::exec(echo Carbon::now()->addSeconds(5)->diffForHumans();/*pad(62)*/)}} // {{humandiff6_eval}} +``` + + +### Modifiers + +These group of methods perform helpful modifications to the current instance. Most of them are self explanatory from their names... or at least should be. You'll also notice that the startOfXXX() methods set the time to 00:00:00 and the endOfXXX() methods set the time to 23:59:59. + +```php +{{::lint($dt = Carbon::create(2012, 1, 31, 12, 0, 0);/*pad(40)*/)}} +{{modifier1::exec(echo $dt->startOfDay();/*pad(50)*/)}} // {{modifier1_eval}} + +{{::lint($dt = Carbon::create(2012, 1, 31, 12, 0, 0);)}} +{{modifier2::exec(echo $dt->endOfDay();/*pad(50)*/)}} // {{modifier2_eval}} + +{{::lint($dt = Carbon::create(2012, 1, 31, 12, 0, 0);)}} +{{modifier3::exec(echo $dt->startOfMonth();/*pad(50)*/)}} // {{modifier3_eval}} + +{{::lint($dt = Carbon::create(2012, 1, 31, 12, 0, 0);)}} +{{modifier4::exec(echo $dt->endOfMonth();/*pad(50)*/)}} // {{modifier4_eval}} + +{{::lint($dt = Carbon::create(2012, 1, 31, 12, 0, 0);)}} +{{modifier5::exec(echo $dt->startOfWeek();/*pad(50)*/)}} // {{modifier5_eval}} +{{modifier6::exec(var_dump($dt->dayOfWeek == Carbon::MONDAY);/*pad(50)*/)}} // {{modifier6_eval}} : ISO8601 week starts on Monday + +{{::lint($dt = Carbon::create(2012, 1, 31, 12, 0, 0);)}} +{{modifier7::exec(echo $dt->endOfWeek();/*pad(50)*/)}} // {{modifier7_eval}} +{{modifier8::exec(var_dump($dt->dayOfWeek == Carbon::SUNDAY);/*pad(50)*/)}} // {{modifier8_eval}} : ISO8601 week ends on Sunday + +{{::lint($dt = Carbon::create(2012, 1, 31, 12, 0, 0);)}} +{{modifier9::exec(echo $dt->next(Carbon::WEDNESDAY);/*pad(50)*/)}} // {{modifier9_eval}} +{{modifier10::exec(var_dump($dt->dayOfWeek == Carbon::WEDNESDAY);/*pad(50)*/)}} // {{modifier10_eval}} + +{{::lint($dt = Carbon::create(2012, 1, 1, 12, 0, 0);)}} +{{modifier11::exec(echo $dt->next();/*pad(50)*/)}} // {{modifier11_eval}} + +{{::lint($dt = Carbon::create(2012, 1, 31, 12, 0, 0);)}} +{{modifier12::exec(echo $dt->previous(Carbon::WEDNESDAY);/*pad(50)*/)}} // {{modifier12_eval}} +{{modifier13::exec(var_dump($dt->dayOfWeek == Carbon::WEDNESDAY);/*pad(50)*/)}} // {{modifier13_eval}} + +{{::lint($dt = Carbon::create(2012, 1, 1, 12, 0, 0);)}} +{{modifier14::exec(echo $dt->previous();/*pad(50)*/)}} // {{modifier14_eval}} + +// others that are defined that are similar +// firstOfMonth(), lastOfMonth(), nthOfMonth() +// firstOfQuarter(), lastOfQuarter(), nthOfQuarter() +// firstOfYear(), lastOfYear(), nthOfYear() +``` + + +### Constants + +The following constants are defined in the Carbon class. + +* SUNDAY = 0 +* MONDAY = 1 +* TUESDAY = 2 +* WEDNESDAY = 3 +* THURSDAY = 4 +* FRIDAY = 5 +* SATURDAY = 6 +* MONTHS_PER_YEAR = 12 +* HOURS_PER_DAY = 24 +* MINUTES_PER_HOUR = 60 +* SECONDS_PER_MINUTE = 60 + +```php +{{::lint( +$dt = Carbon::createFromDate(2012, 10, 6); +if ($dt->dayOfWeek === Carbon::SATURDAY) { + echo 'Place bets on Ottawa Senators Winning!'; +} +)}} +``` + + +## About + + +### Contributing + +I hate reading a readme.md file that has code errors and/or sample output that is incorrect. I tried something new with this project and wrote a quick readme parser that can **lint** sample source code or **execute** and inject the actual result into a generated readme. + +> **Don't make changes to the `readme.md` directly!!** + +Change the `readme.src.md` and then use the `readme.php` to generate the new `readme.md` file. It can be run at the command line using `php readme.php` from the project root. Maybe someday I'll extract this out to another project or at least run it with a post receive hook, but for now its just a local tool, deal with it. + +The commands are quickly explained below. To see some examples you can view the raw `readme.src.md` file in this repo. + +`\{\{::lint()}}` + +The `lint` command is meant for confirming the code is valid and will `eval()` the code passed into the function. Assuming there were no errors, the executed source code will then be injected back into the text replacing out the `\{\{::lint()}}`. When you look at the raw `readme.src.md` you will see that the code can span several lines. Remember the code is executed in the context of the running script so any variables will be available for the rest of the file. + + \{\{::lint($var = 'brian nesbitt';)}} => {{::lint($var = 'brian nesbitt';)}} + +> As mentioned the `$var` can later be echo'd and you would get 'brian nesbitt' as all of the source is executed in the same scope. + +`\{\{varName::exec()}}` and `{{varName_eval}}` + +The `exec` command begins by performing an `eval()` on the code passed into the function. The executed source code will then be injected back into the text replacing out the `\{\{varName::exec()}}`. This will also create a variable named `varName_eval` that you can then place anywhere in the file and it will get replaced with the output of the `eval()`. You can use any type of output (`echo`, `printf`, `var_dump` etc) statement to return the result value as an output buffer is setup to capture the output. + + \{\{exVarName::exec(echo $var;)}} => {{exVarName::exec(echo $var;)}} + \{\{exVarName_eval}} => {{exVarName_eval}} // $var is still set from above + +`/*pad()*/` + +The `pad()` is a special source modifier. This will pad the code block to the indicated number of characters using spaces. Its particularly handy for aligning `//` comments when showing results. + + \{\{exVarName1::exec(echo 12345;/*pad(20)*/)}} // \{\{exVarName1_eval}} + \{\{exVarName2::exec(echo 6;/*pad(20)*/)}} // \{\{exVarName2_eval}} + +... would generate to: + + {{exVarName1::exec(echo 12345;/*pad(20)*/)}} // {{exVarName1_eval}} + {{exVarName2::exec(echo 6;/*pad(20)*/)}} // {{exVarName2_eval}} + +Apart from the readme the typical steps can be used to contribute your own improvements. + +* Fork +* Clone +* PHPUnit +* Branch +* PHPUnit +* Code +* PHPUnit +* Commit +* Push +* Pull request +* Relax and play Castle Crashers + + +### Author + +Brian Nesbitt - - + + +### License + +Carbon is licensed under the MIT License - see the `LICENSE` file for details + + +### History + +You can view the history of the Carbon project in the [history file](https://github.com/briannesbitt/Carbon/blob/master/history.md). + + +### Why the name Carbon? + +Read about [Carbon Dating](http://en.wikipedia.org/wiki/Radiocarbon_dating) \ No newline at end of file diff --git a/vendor/nesbot/carbon/src/Carbon/Carbon.php b/vendor/nesbot/carbon/src/Carbon/Carbon.php new file mode 100644 index 0000000..ca68d47 --- /dev/null +++ b/vendor/nesbot/carbon/src/Carbon/Carbon.php @@ -0,0 +1,1897 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carbon; + +use DateTime; +use DateTimeZone; +use InvalidArgumentException; + +/** + * A simple API extension for DateTime + * + * @property integer $year + * @property integer $month + * @property integer $day + * @property integer $hour + * @property integer $minute + * @property integer $second + * @property integer $timestamp seconds since the Unix Epoch + * @property-read integer $dayOfWeek 0 (for Sunday) through 6 (for Saturday) + * @property-read integer $dayOfYear 0 through 365 + * @property-read integer $weekOfYear ISO-8601 week number of year, weeks starting on Monday + * @property-read integer $daysInMonth number of days in the given month + * @property-read integer $age does a diffInYears() with default parameters + * @property-read integer $quarter the quarter of this instance, 1 - 4 + * @property-read integer $offset the timezone offset in seconds from UTC + * @property-read integer $offsetHours the timezone offset in hours from UTC + * @property-read integer $dst daylight savings time indicator, 1 if DST, 0 otherwise + * @property-read string $timezoneName + * @property-read string $tzName + * + * @property-read DateTimeZone $timezone the current timezone + * @property-read DateTimeZone $tz alias of timezone + * @property-write DateTimeZone|string $timezone the current timezone + * @property-write DateTimeZone|string $tz alias of timezone + * + */ +class Carbon extends DateTime +{ + /** + * The day constants + */ + const SUNDAY = 0; + const MONDAY = 1; + const TUESDAY = 2; + const WEDNESDAY = 3; + const THURSDAY = 4; + const FRIDAY = 5; + const SATURDAY = 6; + + /** + * Names of days of the week. + * + * @var array + */ + private static $days = array( + self::SUNDAY => 'Sunday', + self::MONDAY => 'Monday', + self::TUESDAY => 'Tuesday', + self::WEDNESDAY => 'Wednesday', + self::THURSDAY => 'Thursday', + self::FRIDAY => 'Friday', + self::SATURDAY => 'Saturday' + ); + + /** + * Number of X in Y + */ + const MONTHS_PER_YEAR = 12; + const HOURS_PER_DAY = 24; + const MINUTES_PER_HOUR = 60; + const SECONDS_PER_MINUTE = 60; + + /** + * A test Carbon instance to be returned when now instances are created + * + * @var Carbon + */ + protected static $testNow; + + /** + * Creates a DateTimeZone from a string or a DateTimeZone + * + * @param DateTimeZone|string $object + * + * @return DateTimeZone + */ + protected static function safeCreateDateTimeZone($object) + { + if ($object instanceof DateTimeZone) { + return $object; + } + + $tz = @timezone_open((string) $object); + + if ($tz === false) { + throw new InvalidArgumentException('Unknown or bad timezone ('.$object.')'); + } + + return $tz; + } + + /////////////////////////////////////////////////////////////////// + //////////////////////////// CONSTRUCTORS ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Create a new Carbon instance. + * + * Please see the testing aids section (specifically static::setTestNow()) + * for more on the possibility of this constructor returning a test instance. + * + * @param string $time + * @param DateTimeZone|string $tz + */ + public function __construct($time = null, $tz = null) + { + // If the class has a test now set and we are trying to create a now() + // instance then override as required + if (static::hasTestNow() && (empty($time) || $time === 'now')) { + $time = static::getTestNow()->toDateTimeString(); + $tz = static::getTestNow()->tz; + } + + if ($tz !== null) { + parent::__construct($time, self::safeCreateDateTimeZone($tz)); + } else { + parent::__construct($time); + } + } + + /** + * Create a Carbon instance from a DateTime one + * + * @param DateTime $dt + * + * @return Carbon + */ + public static function instance(DateTime $dt) + { + return new static($dt->format('Y-m-d H:i:s'), $dt->getTimeZone()); + } + + /** + * Create a carbon instance from a string. This is an alias for the + * constructor that allows better fluent syntax as it allows you to do + * Carbon::parse('Monday next week')->fn() rather than + * (new Carbon('Monday next week'))->fn() + * + * @param string $time + * @param DateTimeZone|string $tz + */ + public static function parse($time = null, $tz = null) + { + return new static($time, $tz); + } + + /** + * Get a Carbon instance for the current date and time + * + * @param DateTimeZone|string $tz + * + * @return Carbon + */ + public static function now($tz = null) + { + return new static(null, $tz); + } + + /** + * Create a Carbon instance for today + * + * @param DateTimeZone|string $tz + * + * @return Carbon + */ + public static function today($tz = null) + { + return static::now($tz)->startOfDay(); + } + + /** + * Create a Carbon instance for tomorrow + * + * @param DateTimeZone|string $tz + * + * @return Carbon + */ + public static function tomorrow($tz = null) + { + return static::today($tz)->addDay(); + } + + /** + * Create a Carbon instance for yesterday + * + * @param DateTimeZone|string $tz + * + * @return Carbon + */ + public static function yesterday($tz = null) + { + return static::today($tz)->subDay(); + } + + /** + * Create a new Carbon instance from a specific date and time. + * + * If any of $year, $month or $day are set to null their now() values + * will be used. + * + * If $hour is null it will be set to its now() value and the default values + * for $minute and $second will be their now() values. + * If $hour is not null then the default values for $minute and $second + * will be 0. + * + * @param integer $year + * @param integer $month + * @param integer $day + * @param integer $hour + * @param integer $minute + * @param integer $second + * @param DateTimeZone|string $tz + * + * @return Carbon + */ + public static function create($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null) + { + $year = ($year === null) ? date('Y') : $year; + $month = ($month === null) ? date('n') : $month; + $day = ($day === null) ? date('j') : $day; + + if ($hour === null) { + $hour = date('G'); + $minute = ($minute === null) ? date('i') : $minute; + $second = ($second === null) ? date('s') : $second; + } else { + $minute = ($minute === null) ? 0 : $minute; + $second = ($second === null) ? 0 : $second; + } + + return self::createFromFormat('Y-n-j G:i:s', sprintf('%s-%s-%s %s:%02s:%02s', $year, $month, $day, $hour, $minute, $second), $tz); + } + + /** + * Create a Carbon instance from just a date. The time portion is set to now. + * + * @param integer $year + * @param integer $month + * @param integer $day + * @param DateTimeZone|string $tz + * + * @return Carbon + */ + public static function createFromDate($year = null, $month = null, $day = null, $tz = null) + { + return self::create($year, $month, $day, null, null, null, $tz); + } + + /** + * Create a Carbon instance from just a time. The date portion is set to today. + * + * @param integer $hour + * @param integer $minute + * @param integer $second + * @param DateTimeZone|string $tz + * + * @return Carbon + */ + public static function createFromTime($hour = null, $minute = null, $second = null, $tz = null) + { + return self::create(null, null, null, $hour, $minute, $second, $tz); + } + + /** + * Create a Carbon instance from a specific format + * + * @param string $format + * @param string $time + * @param DateTimeZone|string $tz + * + * @return Carbon + */ + public static function createFromFormat($format, $time, $tz = null) + { + if ($tz !== null) { + $dt = parent::createFromFormat($format, $time, self::safeCreateDateTimeZone($tz)); + } else { + $dt = parent::createFromFormat($format, $time); + } + + if ($dt instanceof DateTime) { + return self::instance($dt); + } + + $errors = DateTime::getLastErrors(); + throw new InvalidArgumentException(implode(PHP_EOL, $errors['errors'])); + } + + /** + * Create a Carbon instance from a timestamp + * + * @param integer $timestamp + * @param DateTimeZone|string $tz + * + * @return Carbon + */ + public static function createFromTimestamp($timestamp, $tz = null) + { + return self::now($tz)->setTimestamp($timestamp); + } + + /** + * Create a Carbon instance from an UTC timestamp + * + * @param integer $timestamp + * + * @return Carbon + */ + public static function createFromTimestampUTC($timestamp) + { + return new static('@'.$timestamp); + } + + /** + * Get a copy of the instance + * + * @return Carbon + */ + public function copy() + { + return self::instance($this); + } + + /////////////////////////////////////////////////////////////////// + ///////////////////////// GETTERS AND SETTERS ///////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Get a part of the Carbon object + * + * @param string $name + * + * @return string|integer|DateTimeZone + */ + public function __get($name) + { + switch ($name) { + case 'year': + return intval($this->format('Y')); + + case 'month': + return intval($this->format('n')); + + case 'day': + return intval($this->format('j')); + + case 'hour': + return intval($this->format('G')); + + case 'minute': + return intval($this->format('i')); + + case 'second': + return intval($this->format('s')); + + case 'dayOfWeek': + return intval($this->format('w')); + + case 'dayOfYear': + return intval($this->format('z')); + + case 'weekOfYear': + return intval($this->format('W')); + + case 'daysInMonth': + return intval($this->format('t')); + + case 'timestamp': + return intval($this->format('U')); + + case 'age': + return intval($this->diffInYears()); + + case 'quarter': + return intval(($this->month - 1) / 3) + 1; + + case 'offset': + return $this->getOffset(); + + case 'offsetHours': + return $this->getOffset() / self::SECONDS_PER_MINUTE / self::MINUTES_PER_HOUR; + + case 'dst': + return $this->format('I') == '1'; + + case 'timezone': + return $this->getTimezone(); + + case 'timezoneName': + return $this->getTimezone()->getName(); + + case 'tz': + return $this->timezone; + + case 'tzName': + return $this->timezoneName; + + default: + throw new InvalidArgumentException(sprintf("Unknown getter '%s'", $name)); + } + } + + /** + * Check if an attribute exists on the object + * + * @param string $name + * + * @return boolean + */ + public function __isset($name) + { + try { + $this->__get($name); + } catch (InvalidArgumentException $e) { + return false; + } + + return true; + } + + /** + * Set a part of the Carbon object + * + * @param string $name + * @param string|integer|DateTimeZone $value + */ + public function __set($name, $value) + { + switch ($name) { + case 'year': + parent::setDate($value, $this->month, $this->day); + break; + case 'month': + parent::setDate($this->year, $value, $this->day); + break; + case 'day': + parent::setDate($this->year, $this->month, $value); + break; + case 'hour': + parent::setTime($value, $this->minute, $this->second); + break; + case 'minute': + parent::setTime($this->hour, $value, $this->second); + break; + case 'second': + parent::setTime($this->hour, $this->minute, $value); + break; + case 'timestamp': + parent::setTimestamp($value); + break; + case 'timezone': + $this->setTimezone($value); + break; + case 'tz': + $this->setTimezone($value); + break; + default: + throw new InvalidArgumentException(sprintf("Unknown setter '%s'", $name)); + } + } + + /** + * Set the instance's year + * + * @param integer $value + * + * @return self + */ + public function year($value) + { + $this->year = $value; + + return $this; + } + + /** + * Set the instance's month + * + * @param integer $value + * + * @return self + */ + public function month($value) + { + $this->month = $value; + + return $this; + } + + /** + * Set the instance's day + * + * @param integer $value + * + * @return self + */ + public function day($value) + { + $this->day = $value; + + return $this; + } + + /** + * Set the date all together + * + * @param integer $year + * @param integer $month + * @param integer $day + * + * @return self + */ + public function setDate($year, $month, $day) + { + return $this->year($year)->month($month)->day($day); + } + + /** + * Set the instance's hour + * + * @param integer $value + * + * @return self + */ + public function hour($value) + { + $this->hour = $value; + + return $this; + } + + /** + * Set the instance's minute + * + * @param integer $value + * + * @return self + */ + public function minute($value) + { + $this->minute = $value; + + return $this; + } + + /** + * Set the instance's second + * + * @param integer $value + * + * @return self + */ + public function second($value) + { + $this->second = $value; + + return $this; + } + + /** + * Set the time all together + * + * @param integer $hour + * @param integer $minute + * @param integer $second + * + * @return self + */ + public function setTime($hour, $minute, $second = 0) + { + return $this->hour($hour)->minute($minute)->second($second); + } + + /** + * Set the date and time all together + * + * @param integer $year + * @param integer $month + * @param integer $day + * @param integer $hour + * @param integer $minute + * @param integer $second + * + * @return self + */ + public function setDateTime($year, $month, $day, $hour, $minute, $second) + { + return $this->setDate($year, $month, $day)->setTime($hour, $minute, $second); + } + + /** + * Set the instance's timestamp + * + * @param integer $value + * + * @return self + */ + public function timestamp($value) + { + $this->timestamp = $value; + + return $this; + } + + /** + * Alias for setTimezone() + * + * @param DateTimeZone|string $value + * + * @return self + */ + public function timezone($value) + { + return $this->setTimezone($value); + } + + /** + * Alias for setTimezone() + * + * @param DateTimeZone|string $value + * + * @return self + */ + public function tz($value) + { + return $this->setTimezone($value); + } + + /** + * Set the instance's timezone from a string or object + * + * @param DateTimeZone|string $value + */ + public function setTimezone($value) + { + parent::setTimezone(self::safeCreateDateTimeZone($value)); + + return $this; + } + + /////////////////////////////////////////////////////////////////// + ///////////////////////// TESTING AIDS //////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Set a Carbon instance (real or mock) to be returned when a "now" + * instance is created. The provided instance will be returned + * specifically under the following conditions: + * - A call to the static now() method, ex. Carbon::now() + * - When a null (or blank string) is passed to the constructor or parse(), ex. new Carbon(null) + * - When the string "now" is passed to the constructor or parse(), ex. new Carbon('now') + * + * Note the timezone parameter was left out of the examples above and + * has no affect as the mock value will be returned regardless of its value. + * + * To clear the test instance call this method using the default + * parameter of null. + * + * @param Carbon $testNow + */ + public static function setTestNow(Carbon $testNow = null) + { + static::$testNow = $testNow; + } + + /** + * Get the Carbon instance (real or mock) to be returned when a "now" + * instance is created. + * + * @return Carbon the current instance used for testing + */ + public static function getTestNow() + { + return static::$testNow; + } + + /** + * Determine if there is a valid test instance set. A valid test instance + * is anything that is not null. + * + * @return boolean true if there is a test instance, otherwise false + */ + public static function hasTestNow() + { + return static::getTestNow() !== null; + } + + /////////////////////////////////////////////////////////////////// + /////////////////////// STRING FORMATTING ///////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Format the instance with the current locale. You can set the current + * locale using setlocale() http://php.net/setlocale. + * + * @param string $format + * + * @return string + */ + public function formatLocalized($format) + { + return strftime($format, $this->timestamp); + } + + /** + * Format the instance with date and time + * + * @return string + */ + public function __toString() + { + return $this->toDateTimeString(); + } + + /** + * Format the instance as date + * + * @return string + */ + public function toDateString() + { + return $this->format('Y-m-d'); + } + + /** + * Format the instance as a readable date + * + * @return string + */ + public function toFormattedDateString() + { + return $this->format('M j, Y'); + } + + /** + * Format the instance as time + * + * @return string + */ + public function toTimeString() + { + return $this->format('H:i:s'); + } + + /** + * Format the instance as date and time + * + * @return string + */ + public function toDateTimeString() + { + return $this->format('Y-m-d H:i:s'); + } + + /** + * Format the instance with day, date and time + * + * @return string + */ + public function toDayDateTimeString() + { + return $this->format('D, M j, Y g:i A'); + } + + /** + * Format the instance as ATOM + * + * @return string + */ + public function toATOMString() + { + return $this->format(DateTime::ATOM); + } + + /** + * Format the instance as COOKIE + * + * @return string + */ + public function toCOOKIEString() + { + return $this->format(DateTime::COOKIE); + } + + /** + * Format the instance as ISO8601 + * + * @return string + */ + public function toISO8601String() + { + return $this->format(DateTime::ISO8601); + } + + /** + * Format the instance as RFC822 + * + * @return string + */ + public function toRFC822String() + { + return $this->format(DateTime::RFC822); + } + + /** + * Format the instance as RFC850 + * + * @return string + */ + public function toRFC850String() + { + return $this->format(DateTime::RFC850); + } + + /** + * Format the instance as RFC1036 + * + * @return string + */ + public function toRFC1036String() + { + return $this->format(DateTime::RFC1036); + } + + /** + * Format the instance as RFC1123 + * + * @return string + */ + public function toRFC1123String() + { + return $this->format(DateTime::RFC1123); + } + + /** + * Format the instance as RFC2822 + * + * @return string + */ + public function toRFC2822String() + { + return $this->format(DateTime::RFC2822); + } + + /** + * Format the instance as RFC3339 + * + * @return string + */ + public function toRFC3339String() + { + return $this->format(DateTime::RFC3339); + } + + /** + * Format the instance as RSS + * + * @return string + */ + public function toRSSString() + { + return $this->format(DateTime::RSS); + } + + /** + * Format the instance as W3C + * + * @return string + */ + public function toW3CString() + { + return $this->format(DateTime::W3C); + } + + /////////////////////////////////////////////////////////////////// + ////////////////////////// COMPARISONS //////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Determines if the instance is equal to another + * + * @param Carbon $dt + * + * @return boolean + */ + public function eq(Carbon $dt) + { + return $this == $dt; + } + + /** + * Determines if the instance is not equal to another + * + * @param Carbon $dt + * + * @return boolean + */ + public function ne(Carbon $dt) + { + return !$this->eq($dt); + } + + /** + * Determines if the instance is greater (after) than another + * + * @param Carbon $dt + * + * @return boolean + */ + public function gt(Carbon $dt) + { + return $this > $dt; + } + + /** + * Determines if the instance is greater (after) than or equal to another + * + * @param Carbon $dt + * + * @return boolean + */ + public function gte(Carbon $dt) + { + return $this >= $dt; + } + + /** + * Determines if the instance is less (before) than another + * + * @param Carbon $dt + * + * @return boolean + */ + public function lt(Carbon $dt) + { + return $this < $dt; + } + + /** + * Determines if the instance is less (before) or equal to another + * + * @param Carbon $dt + * + * @return boolean + */ + public function lte(Carbon $dt) + { + return $this <= $dt; + } + + /** + * Determines if the instance is between two others + * + * @param Carbon $dt1 + * @param Carbon $dt2 + * @param boolean $equal Indicates if a > and < comparison should be used or <= or >= + * + * @return boolean + */ + public function between(Carbon $dt1, Carbon $dt2, $equal = true) + { + if ($dt1->gt($dt2)) { + $temp = $dt1; + $dt1 = $dt2; + $dt2 = $temp; + } + + if ($equal) { + return $this->gte($dt1) && $this->lte($dt2); + } else { + return $this->gt($dt1) && $this->lt($dt2); + } + } + + /** + * Determines if the instance is a weekday + * + * @return boolean + */ + public function isWeekday() + { + return ($this->dayOfWeek != self::SUNDAY && $this->dayOfWeek != self::SATURDAY); + } + + /** + * Determines if the instance is a weekend day + * + * @return boolean + */ + public function isWeekend() + { + return !$this->isWeekDay(); + } + + /** + * Determines if the instance is yesterday + * + * @return boolean + */ + public function isYesterday() + { + return $this->toDateString() === self::now($this->tz)->subDay()->toDateString(); + } + + /** + * Determines if the instance is today + * + * @return boolean + */ + public function isToday() + { + return $this->toDateString() === self::now($this->tz)->toDateString(); + } + + /** + * Determines if the instance is tomorrow + * + * @return boolean + */ + public function isTomorrow() + { + return $this->toDateString() === self::now($this->tz)->addDay()->toDateString(); + } + + /** + * Determines if the instance is in the future, ie. greater (after) than now + * + * @return boolean + */ + public function isFuture() + { + return $this->gt(self::now($this->tz)); + } + + /** + * Determines if the instance is in the past, ie. less (before) than now + * + * @return boolean + */ + public function isPast() + { + return !$this->isFuture(); + } + + /** + * Determines if the instance is a leap year + * + * @return boolean + */ + public function isLeapYear() + { + return $this->format('L') == '1'; + } + + /////////////////////////////////////////////////////////////////// + /////////////////// ADDITIONS AND SUBSTRACTIONS /////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Add years to the instance. Positive $value travel forward while + * negative $value travel into the past. + * + * @param integer $value + * + * @return self + */ + public function addYears($value) + { + return $this->modify(intval($value) . ' year'); + } + + /** + * Add a year to the instance + * + * @return self + */ + public function addYear() + { + return $this->addYears(1); + } + + /** + * Remove a year from the instance + * + * @return self + */ + public function subYear() + { + return $this->addYears(-1); + } + + /** + * Remove years from the instance. + * + * @param integer $value + * + * @return self + */ + public function subYears($value) + { + return $this->addYears(-1 * $value); + } + + /** + * Add months to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param integer $value + * + * @return self + */ + public function addMonths($value) + { + return $this->modify(intval($value) . ' month'); + } + + /** + * Add a month to the instance + * + * @return self + */ + public function addMonth() + { + return $this->addMonths(1); + } + + /** + * Remove a month from the instance + * + * @return self + */ + public function subMonth() + { + return $this->addMonths(-1); + } + + /** + * Remove months from the instance + * + * @param integer $value + * + * @return self + */ + public function subMonths($value) + { + return $this->addMonths(-1 * $value); + } + + /** + * Add days to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param integer $value + * + * @return self + */ + public function addDays($value) + { + return $this->modify(intval($value) . ' day'); + } + + /** + * Add a day to the instance + * + * @return self + */ + public function addDay() + { + return $this->addDays(1); + } + + /** + * Remove a day from the instance + * + * @return self + */ + public function subDay() + { + return $this->addDays(-1); + } + + /** + * Remove days from the instance + * + * @param integer $value + * + * @return self + */ + public function subDays($value) + { + return $this->addDays(-1 * $value); + } + + /** + * Add weekdays to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param integer $value + * + * @return self + */ + public function addWeekdays($value) + { + return $this->modify(intval($value) . ' weekday'); + } + + /** + * Add a weekday to the instance + * + * @return self + */ + public function addWeekday() + { + return $this->addWeekdays(1); + } + + /** + * Remove a weekday from the instance + * + * @return self + */ + public function subWeekday() + { + return $this->addWeekdays(-1); + } + + /** + * Remove weekdays from the instance + * + * @param integer $value + * + * @return self + */ + public function subWeekdays($value) + { + return $this->addWeekdays(-1 * $value); + } + + /** + * Add weeks to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param integer $value + * + * @return self + */ + public function addWeeks($value) + { + return $this->modify(intval($value) . ' week'); + } + + /** + * Add a week to the instance + * + * @return self + */ + public function addWeek() + { + return $this->addWeeks(1); + } + + /** + * Remove a week from the instance + * + * @return self + */ + public function subWeek() + { + return $this->addWeeks(-1); + } + + /** + * Remove weeks to the instance + * + * @param integer $value + * + * @return self + */ + public function subWeeks($value) + { + return $this->addWeeks(-1 * $value); + } + + /** + * Add hours to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param integer $value + * + * @return self + */ + public function addHours($value) + { + return $this->modify(intval($value) . ' hour'); + } + + /** + * Add an hour to the instance + * + * @return self + */ + public function addHour() + { + return $this->addHours(1); + } + + /** + * Remove an hour from the instance + * + * @return self + */ + public function subHour() + { + return $this->addHours(-1); + } + + /** + * Remove hours from the instance + * + * @param integer $value + * + * @return self + */ + public function subHours($value) + { + return $this->addHours(-1 * $value); + } + + /** + * Add minutes to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param integer $value + * + * @return self + */ + public function addMinutes($value) + { + return $this->modify(intval($value) . ' minute'); + } + + /** + * Add a minute to the instance + * + * @return self + */ + public function addMinute() + { + return $this->addMinutes(1); + } + + /** + * Remove a minute from the instance + * + * @return self + */ + public function subMinute() + { + return $this->addMinutes(-1); + } + + /** + * Remove minutes from the instance + * + * @param integer $value + * + * @return self + */ + public function subMinutes($value) + { + return $this->addMinutes(-1 * $value); + } + + /** + * Add seconds to the instance. Positive $value travels forward while + * negative $value travels into the past. + * + * @param integer $value + * + * @return self + */ + public function addSeconds($value) + { + return $this->modify(intval($value) . ' second'); + } + + /** + * Add a second to the instance + * + * @return self + */ + public function addSecond() + { + return $this->addSeconds(1); + } + + /** + * Remove a second from the instance + * + * @return self + */ + public function subSecond() + { + return $this->addSeconds(-1); + } + + /** + * Remove seconds from the instance + * + * @param integer $value + * + * @return self + */ + public function subSeconds($value) + { + return $this->addSeconds(-1 * $value); + } + + /////////////////////////////////////////////////////////////////// + /////////////////////////// DIFFERENCES /////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Get the difference in years + * + * @param Carbon $dt + * @param boolean $abs Get the absolute of the difference + * + * @return integer + */ + public function diffInYears(Carbon $dt = null, $abs = true) + { + $dt = ($dt === null) ? static::now($this->tz) : $dt; + $sign = ($abs) ? '' : '%r'; + + return intval($this->diff($dt)->format($sign.'%y')); + } + + /** + * Get the difference in months + * + * @param Carbon $dt + * @param boolean $abs Get the absolute of the difference + * + * @return integer + */ + public function diffInMonths(Carbon $dt = null, $abs = true) + { + $dt = ($dt === null) ? static::now($this->tz) : $dt; + list($sign, $years, $months) = explode(':', $this->diff($dt)->format('%r:%y:%m')); + $value = ($years * self::MONTHS_PER_YEAR) + $months; + + if ($sign === '-' && !$abs) { + $value = $value * -1; + } + + return $value; + } + + /** + * Get the difference in days + * + * @param Carbon $dt + * @param boolean $abs Get the absolute of the difference + * + * @return integer + */ + public function diffInDays(Carbon $dt = null, $abs = true) + { + $dt = ($dt === null) ? static::now($this->tz) : $dt; + $sign = ($abs) ? '' : '%r'; + + return intval($this->diff($dt)->format($sign.'%a')); + } + + /** + * Get the difference in hours + * + * @param Carbon $dt + * @param boolean $abs Get the absolute of the difference + * + * @return integer + */ + public function diffInHours(Carbon $dt = null, $abs = true) + { + $dt = ($dt === null) ? static::now($this->tz) : $dt; + + return intval($this->diffInMinutes($dt, $abs) / self::MINUTES_PER_HOUR); + } + + /** + * Get the difference in minutes + * + * @param Carbon $dt + * @param boolean $abs Get the absolute of the difference + * + * @return integer + */ + public function diffInMinutes(Carbon $dt = null, $abs = true) + { + $dt = ($dt === null) ? static::now($this->tz) : $dt; + + return intval($this->diffInSeconds($dt, $abs) / self::SECONDS_PER_MINUTE); + } + + /** + * Get the difference in seconds + * + * @param Carbon $dt + * @param boolean $abs Get the absolute of the difference + * + * @return integer + */ + public function diffInSeconds(Carbon $dt = null, $abs = true) + { + $dt = ($dt === null) ? static::now($this->tz) : $dt; + $value = $dt->getTimestamp() - $this->getTimestamp(); + + return $abs ? abs($value) : $value; + } + + /** + * Get the difference in a human readable format. + * + * When comparing a value in the past to default now: + * 1 hour ago + * 5 months ago + * + * When comparing a value in the future to default now: + * 1 hour from now + * 5 months from now + * + * When comparing a value in the past to another value: + * 1 hour before + * 5 months before + * + * When comparing a value in the future to another value: + * 1 hour after + * 5 months after + * + * @param Carbon $other + * + * @return string + */ + public function diffForHumans(Carbon $other = null) + { + $txt = ''; + + $isNow = $other === null; + + if ($isNow) { + $other = self::now(); + } + + $isFuture = $this->gt($other); + + $delta = $other->diffInSeconds($this); + + // 30 days per month, 365 days per year... good enough!! + $divs = array( + 'second' => self::SECONDS_PER_MINUTE, + 'minute' => self::MINUTES_PER_HOUR, + 'hour' => self::HOURS_PER_DAY, + 'day' => 30, + 'month' => self::MONTHS_PER_YEAR + ); + + $unit = 'year'; + + foreach ($divs as $divUnit => $divValue) { + if ($delta < $divValue) { + $unit = $divUnit; + break; + } + + $delta = floor($delta / $divValue); + } + + if ($delta == 0) { + $delta = 1; + } + + $txt = $delta . ' ' . $unit; + $txt .= $delta == 1 ? '' : 's'; + + if ($isNow) { + if ($isFuture) { + return $txt . ' from now'; + } + + return $txt . ' ago'; + } + + if ($isFuture) { + return $txt . ' after'; + } + + return $txt . ' before'; + } + + /////////////////////////////////////////////////////////////////// + //////////////////////////// MODIFIERS //////////////////////////// + /////////////////////////////////////////////////////////////////// + + /** + * Resets the time to 00:00:00 + * + * @return self + */ + public function startOfDay() + { + return $this->hour(0)->minute(0)->second(0); + } + + /** + * Resets the time to 23:59:59 + * + * @return self + */ + public function endOfDay() + { + return $this->hour(23)->minute(59)->second(59); + } + + /** + * Resets the date to the first day of the month and the time to 00:00:00 + * + * @return self + */ + public function startOfMonth() + { + return $this->startOfDay()->day(1); + } + + /** + * Resets the date to end of the month and time to 23:59:59 + * + * @return self + */ + public function endOfMonth() + { + return $this->day($this->daysInMonth)->endOfDay(); + } + + /** + * Resets the date to the first day of the ISO-8601 week (Monday) and the time to 00:00:00 + * + * @return self + */ + public function startOfWeek() + { + return $this->setISODate($this->year, $this->weekOfYear, 1)->startOfDay(); + } + + /** + * Resets the date to end of the ISO-8601 week (Sunday) and time to 23:59:59 + * + * @return self + */ + public function endOfWeek() + { + return $this->setISODate($this->year, $this->weekOfYear, 7)->endOfDay(); + } + + /** + * Modify to the next occurance of a given day of the week. + * If no dayOfWeek is provided, modify to the next occurance + * of the current day of the week. Use the supplied consts + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $dayOfWeek + * + * @return mixed + */ + public function next($dayOfWeek = null) + { + $this->startOfDay(); + if ($dayOfWeek === null) { + $dayOfWeek = $this->dayOfWeek; + } + + return $this->modify('next ' . self::$days[$dayOfWeek]); + } + + /** + * Modify to the previous occurance of a given day of the week. + * If no dayOfWeek is provided, modify to the previous occurance + * of the current day of the week. Use the supplied consts + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $dayOfWeek + * + * @return mixed + */ + public function previous($dayOfWeek = null) + { + $this->startOfDay(); + if ($dayOfWeek === null) { + $dayOfWeek = $this->dayOfWeek; + } + + return $this->modify('last ' . self::$days[$dayOfWeek]); + } + + /** + * Modify to the first occurance of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * first day of the current month. Use the supplied consts + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $dayOfWeek + * + * @return mixed + */ + public function firstOfMonth($dayOfWeek = null) + { + $this->startOfDay(); + if ($dayOfWeek === null) { + return $this->day(1); + } + + return $this->modify('first ' . self::$days[$dayOfWeek] . ' of ' . $this->format('F') . ' ' . $this->year); + } + + /** + * Modify to the last occurance of a given day of the week + * in the current month. If no dayOfWeek is provided, modify to the + * last day of the current month. Use the supplied consts + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $dayOfWeek + * + * @return mixed + */ + public function lastOfMonth($dayOfWeek = null) + { + $this->startOfDay(); + if ($dayOfWeek === null) { + return $this->day($this->daysInMonth); + } + + return $this->modify('last ' . self::$days[$dayOfWeek] . ' of ' . $this->format('F') . ' ' . $this->year); + } + + /** + * Modify to the given occurance of a given day of the week + * in the current month. If the calculated occurance is outside the scope + * of the current month, then return false and no modifications are made. + * Use the supplied consts to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfMonth($nth, $dayOfWeek) + { + $dt = $this->copy(); + $dt->firstOfMonth(); + $month = $dt->month; + $year = $dt->year; + $dt->modify('+' . $nth . ' ' . self::$days[$dayOfWeek]); + if ($month !== $dt->month || $year !== $dt->year) return false; + return $this->modify($dt); + } + + /** + * Modify to the first occurance of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * first day of the current quarter. Use the supplied consts + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $dayOfWeek + * + * @return mixed + */ + public function firstOfQuarter($dayOfWeek = null) + { + $this->month(($this->quarter * 3) - 2); + + return $this->firstOfMonth($dayOfWeek); + } + + /** + * Modify to the last occurance of a given day of the week + * in the current quarter. If no dayOfWeek is provided, modify to the + * last day of the current quarter. Use the supplied consts + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $dayOfWeek + * + * @return mixed + */ + public function lastOfQuarter($dayOfWeek = null) + { + $this->month(($this->quarter * 3)); + + return $this->lastOfMonth($dayOfWeek); + } + + /** + * Modify to the given occurance of a given day of the week + * in the current quarter. If the calculated occurance is outside the scope + * of the current quarter, then return false and no modifications are made. + * Use the supplied consts to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfQuarter($nth, $dayOfWeek) + { + $dt = $this->copy(); + $dt->month(($this->quarter * 3)); + $last_month = $dt->month; + $year = $dt->year; + $dt->firstOfQuarter(); + $dt->modify('+' . $nth . ' ' . self::$days[$dayOfWeek]); + if ($last_month < $dt->month || $year !== $dt->year) return false; + return $this->modify($dt); + } + + /** + * Modify to the first occurance of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * first day of the current year. Use the supplied consts + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $dayOfWeek + * + * @return mixed + */ + public function firstOfYear($dayOfWeek = null) + { + $this->month(1); + + return $this->firstOfMonth($dayOfWeek); + } + + /** + * Modify to the last occurance of a given day of the week + * in the current year. If no dayOfWeek is provided, modify to the + * last day of the current year. Use the supplied consts + * to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $dayOfWeek + * + * @return mixed + */ + public function lastOfYear($dayOfWeek = null) + { + $this->month(12); + + return $this->lastOfMonth($dayOfWeek); + } + + /** + * Modify to the given occurance of a given day of the week + * in the current year. If the calculated occurance is outside the scope + * of the current year, then return false and no modifications are made. + * Use the supplied consts to indicate the desired dayOfWeek, ex. static::MONDAY. + * + * @param int $nth + * @param int $dayOfWeek + * + * @return mixed + */ + public function nthOfYear($nth, $dayOfWeek) + { + $dt = $this->copy(); + $year = $dt->year; + $dt->firstOfYear(); + $dt->modify('+' . $nth . ' ' . self::$days[$dayOfWeek]); + if ($year !== $dt->year) return false; + return $this->modify($dt); + } +} diff --git a/vendor/nesbot/carbon/tests/AddTest.php b/vendor/nesbot/carbon/tests/AddTest.php new file mode 100644 index 0000000..aa6bca0 --- /dev/null +++ b/vendor/nesbot/carbon/tests/AddTest.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class AddTest extends TestFixture +{ + public function testAddYearsPositive() + { + $this->assertSame(1976, Carbon::createFromDate(1975)->addYears(1)->year); + } + public function testAddYearsZero() + { + $this->assertSame(1975, Carbon::createFromDate(1975)->addYears(0)->year); + } + public function testAddYearsNegative() + { + $this->assertSame(1974, Carbon::createFromDate(1975)->addYears(-1)->year); + } + + public function testAddYear() + { + $this->assertSame(1976, Carbon::createFromDate(1975)->addYear()->year); + } + + public function testAddMonthsPositive() + { + $this->assertSame(1, Carbon::createFromDate(1975, 12)->addMonths(1)->month); + } + public function testAddMonthsZero() + { + $this->assertSame(12, Carbon::createFromDate(1975, 12)->addMonths(0)->month); + } + public function testAddMonthsNegative() + { + $this->assertSame(11, Carbon::createFromDate(1975, 12, 1)->addMonths(-1)->month); + } + + public function testAddMonth() + { + $this->assertSame(1, Carbon::createFromDate(1975, 12)->addMonth()->month); + } + public function testAddMonthWithOverflow() + { + $this->assertSame(3, Carbon::createFromDate(2012, 1, 31)->addMonth()->month); + } + + public function testAddDaysPositive() + { + $this->assertSame(1, Carbon::createFromDate(1975, 5, 31)->addDays(1)->day); + } + public function testAddDaysZero() + { + $this->assertSame(31, Carbon::createFromDate(1975, 5, 31)->addDays(0)->day); + } + public function testAddDaysNegative() + { + $this->assertSame(30, Carbon::createFromDate(1975, 5, 31)->addDays(-1)->day); + } + + public function testAddDay() + { + $this->assertSame(1, Carbon::createFromDate(1975, 5, 31)->addDay()->day); + } + + public function testAddWeekdaysPositive() + { + $this->assertSame(17, Carbon::createFromDate(2012, 1, 4)->addWeekdays(9)->day); + } + public function testAddWeekdaysZero() + { + $this->assertSame(4, Carbon::createFromDate(2012, 1, 4)->addWeekdays(0)->day); + } + public function testAddWeekdaysNegative() + { + $this->assertSame(18, Carbon::createFromDate(2012, 1, 31)->addWeekdays(-9)->day); + } + + public function testAddWeekday() + { + $this->assertSame(9, Carbon::createFromDate(2012, 1, 6)->addWeekday()->day); + } + + public function testAddWeeksPositive() + { + $this->assertSame(28, Carbon::createFromDate(1975, 5, 21)->addWeeks(1)->day); + } + public function testAddWeeksZero() + { + $this->assertSame(21, Carbon::createFromDate(1975, 5, 21)->addWeeks(0)->day); + } + public function testAddWeeksNegative() + { + $this->assertSame(14, Carbon::createFromDate(1975, 5, 21)->addWeeks(-1)->day); + } + + public function testAddWeek() + { + $this->assertSame(28, Carbon::createFromDate(1975, 5, 21)->addWeek()->day); + } + + public function testAddHoursPositive() + { + $this->assertSame(1, Carbon::createFromTime(0)->addHours(1)->hour); + } + public function testAddHoursZero() + { + $this->assertSame(0, Carbon::createFromTime(0)->addHours(0)->hour); + } + public function testAddHoursNegative() + { + $this->assertSame(23, Carbon::createFromTime(0)->addHours(-1)->hour); + } + + public function testAddHour() + { + $this->assertSame(1, Carbon::createFromTime(0)->addHour()->hour); + } + + public function testAddMinutesPositive() + { + $this->assertSame(1, Carbon::createFromTime(0, 0)->addMinutes(1)->minute); + } + public function testAddMinutesZero() + { + $this->assertSame(0, Carbon::createFromTime(0, 0)->addMinutes(0)->minute); + } + public function testAddMinutesNegative() + { + $this->assertSame(59, Carbon::createFromTime(0, 0)->addMinutes(-1)->minute); + } + + public function testAddMinute() + { + $this->assertSame(1, Carbon::createFromTime(0, 0)->addMinute()->minute); + } + + public function testAddSecondsPositive() + { + $this->assertSame(1, Carbon::createFromTime(0, 0, 0)->addSeconds(1)->second); + } + public function testAddSecondsZero() + { + $this->assertSame(0, Carbon::createFromTime(0, 0, 0)->addSeconds(0)->second); + } + public function testAddSecondsNegative() + { + $this->assertSame(59, Carbon::createFromTime(0, 0, 0)->addSeconds(-1)->second); + } + + public function testAddSecond() + { + $this->assertSame(1, Carbon::createFromTime(0, 0, 0)->addSecond()->second); + } +} diff --git a/vendor/nesbot/carbon/tests/ComparisonTest.php b/vendor/nesbot/carbon/tests/ComparisonTest.php new file mode 100644 index 0000000..5a5ec49 --- /dev/null +++ b/vendor/nesbot/carbon/tests/ComparisonTest.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class ComparisonTest extends TestFixture +{ + public function testEqualToTrue() + { + $this->assertTrue(Carbon::createFromDate(2000, 1, 1)->eq(Carbon::createFromDate(2000, 1, 1))); + } + public function testEqualToFalse() + { + $this->assertFalse(Carbon::createFromDate(2000, 1, 1)->eq(Carbon::createFromDate(2000, 1, 2))); + } + public function testEqualWithTimezoneTrue() + { + $this->assertTrue(Carbon::create(2000, 1, 1, 12, 0, 0, 'America/Toronto')->eq(Carbon::create(2000, 1, 1, 9, 0, 0, 'America/Vancouver'))); + } + public function testEqualWithTimezoneFalse() + { + $this->assertFalse(Carbon::createFromDate(2000, 1, 1, 'America/Toronto')->eq(Carbon::createFromDate(2000, 1, 1, 'America/Vancouver'))); + } + + public function testNotEqualToTrue() + { + $this->assertTrue(Carbon::createFromDate(2000, 1, 1)->ne(Carbon::createFromDate(2000, 1, 2))); + } + public function testNotEqualToFalse() + { + $this->assertFalse(Carbon::createFromDate(2000, 1, 1)->ne(Carbon::createFromDate(2000, 1, 1))); + } + public function testNotEqualWithTimezone() + { + $this->assertTrue(Carbon::createFromDate(2000, 1, 1, 'America/Toronto')->ne(Carbon::createFromDate(2000, 1, 1, 'America/Vancouver'))); + } + + public function testGreaterThanTrue() + { + $this->assertTrue(Carbon::createFromDate(2000, 1, 1)->gt(Carbon::createFromDate(1999, 12, 31))); + } + public function testGreaterThanFalse() + { + $this->assertFalse(Carbon::createFromDate(2000, 1, 1)->gt(Carbon::createFromDate(2000, 1, 2))); + } + public function testGreaterThanWithTimezoneTrue() + { + $dt1 = Carbon::create(2000, 1, 1, 12, 0, 0, 'America/Toronto'); + $dt2 = Carbon::create(2000, 1, 1, 8, 59, 59, 'America/Vancouver'); + $this->assertTrue($dt1->gt($dt2)); + } + public function testGreaterThanWithTimezoneFalse() + { + $dt1 = Carbon::create(2000, 1, 1, 12, 0, 0, 'America/Toronto'); + $dt2 = Carbon::create(2000, 1, 1, 9, 0, 1, 'America/Vancouver'); + $this->assertFalse($dt1->gt($dt2)); + } + + public function testGreaterThanOrEqualTrue() + { + $this->assertTrue(Carbon::createFromDate(2000, 1, 1)->gte(Carbon::createFromDate(1999, 12, 31))); + } + public function testGreaterThanOrEqualTrueEqual() + { + $this->assertTrue(Carbon::createFromDate(2000, 1, 1)->gte(Carbon::createFromDate(2000, 1, 1))); + } + public function testGreaterThanOrEqualFalse() + { + $this->assertFalse(Carbon::createFromDate(2000, 1, 1)->gte(Carbon::createFromDate(2000, 1, 2))); + } + + public function testLessThanTrue() + { + $this->assertTrue(Carbon::createFromDate(2000, 1, 1)->lt(Carbon::createFromDate(2000, 1, 2))); + } + public function testLessThanFalse() + { + $this->assertFalse(Carbon::createFromDate(2000, 1, 1)->lt(Carbon::createFromDate(1999, 12, 31))); + } + + public function testLessThanOrEqualTrue() + { + $this->assertTrue(Carbon::createFromDate(2000, 1, 1)->lte(Carbon::createFromDate(2000, 1, 2))); + } + public function testLessThanOrEqualTrueEqual() + { + $this->assertTrue(Carbon::createFromDate(2000, 1, 1)->lte(Carbon::createFromDate(2000, 1, 1))); + } + public function testLessThanOrEqualFalse() + { + $this->assertFalse(Carbon::createFromDate(2000, 1, 1)->lte(Carbon::createFromDate(1999, 12, 31))); + } + + public function testBetweenEqualTrue() + { + $this->assertTrue(Carbon::createFromDate(2000, 1, 15)->between(Carbon::createFromDate(2000, 1, 1), Carbon::createFromDate(2000, 1, 31), true)); + } + public function testBetweenNotEqualTrue() + { + $this->assertTrue(Carbon::createFromDate(2000, 1, 15)->between(Carbon::createFromDate(2000, 1, 1), Carbon::createFromDate(2000, 1, 31), false)); + } + public function testBetweenEqualFalse() + { + $this->assertFalse(Carbon::createFromDate(1999, 12, 31)->between(Carbon::createFromDate(2000, 1, 1), Carbon::createFromDate(2000, 1, 31), true)); + } + public function testBetweenNotEqualFalse() + { + $this->assertFalse(Carbon::createFromDate(2000, 1, 1)->between(Carbon::createFromDate(2000, 1, 1), Carbon::createFromDate(2000, 1, 31), false)); + } + public function testBetweenEqualSwitchTrue() + { + $this->assertTrue(Carbon::createFromDate(2000, 1, 15)->between(Carbon::createFromDate(2000, 1, 31), Carbon::createFromDate(2000, 1, 1), true)); + } + public function testBetweenNotEqualSwitchTrue() + { + $this->assertTrue(Carbon::createFromDate(2000, 1, 15)->between(Carbon::createFromDate(2000, 1, 31), Carbon::createFromDate(2000, 1, 1), false)); + } + public function testBetweenEqualSwitchFalse() + { + $this->assertFalse(Carbon::createFromDate(1999, 12, 31)->between(Carbon::createFromDate(2000, 1, 31), Carbon::createFromDate(2000, 1, 1), true)); + } + public function testBetweenNotEqualSwitchFalse() + { + $this->assertFalse(Carbon::createFromDate(2000, 1, 1)->between(Carbon::createFromDate(2000, 1, 31), Carbon::createFromDate(2000, 1, 1), false)); + } +} diff --git a/vendor/nesbot/carbon/tests/ConstructTest.php b/vendor/nesbot/carbon/tests/ConstructTest.php new file mode 100644 index 0000000..8891b81 --- /dev/null +++ b/vendor/nesbot/carbon/tests/ConstructTest.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class ConstructTest extends TestFixture +{ + public function testCreatesAnInstanceDefaultToNow() + { + $c = new Carbon(); + $now = Carbon::now(); + $this->assertEquals('Carbon\Carbon', get_class($c)); + $this->assertEquals($now->tzName, $c->tzName); + $this->assertCarbon($c, $now->year, $now->month, $now->day, $now->hour, $now->minute, $now->second); + } + public function testParseCreatesAnInstanceDefaultToNow() + { + $c = Carbon::parse(); + $now = Carbon::now(); + $this->assertEquals('Carbon\Carbon', get_class($c)); + $this->assertEquals($now->tzName, $c->tzName); + $this->assertCarbon($c, $now->year, $now->month, $now->day, $now->hour, $now->minute, $now->second); + } + + public function testWithFancyString() + { + $c = new Carbon('first day of January 2008'); + $this->assertCarbon($c, 2008, 1, 1, 0, 0, 0); + } + public function testParseWithFancyString() + { + $c = Carbon::parse('first day of January 2008'); + $this->assertCarbon($c, 2008, 1, 1, 0, 0, 0); + } + + public function testDefaultTimezone() + { + $c = new Carbon('now'); + $this->assertSame('America/Toronto', $c->tzName); + } + public function testParseWithDefaultTimezone() + { + $c = Carbon::parse('now'); + $this->assertSame('America/Toronto', $c->tzName); + } + + public function testSettingTimezone() + { + $c = new Carbon('now', new \DateTimeZone('Europe/London')); + $this->assertSame('Europe/London', $c->tzName); + $this->assertSame(1, $c->offsetHours); + } + public function testParseSettingTimezone() + { + $c = Carbon::parse('now', new \DateTimeZone('Europe/London')); + $this->assertSame('Europe/London', $c->tzName); + $this->assertSame(1, $c->offsetHours); + } + + public function testSettingTimezoneWithString() + { + $c = new Carbon('now', 'Asia/Tokyo'); + $this->assertSame('Asia/Tokyo', $c->tzName); + $this->assertSame(9, $c->offsetHours); + } + public function testParseSettingTimezoneWithString() + { + $c = Carbon::parse('now', 'Asia/Tokyo'); + $this->assertSame('Asia/Tokyo', $c->tzName); + $this->assertSame(9, $c->offsetHours); + } +} diff --git a/vendor/nesbot/carbon/tests/CopyTest.php b/vendor/nesbot/carbon/tests/CopyTest.php new file mode 100644 index 0000000..cf2c723 --- /dev/null +++ b/vendor/nesbot/carbon/tests/CopyTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class CopyTest extends TestFixture +{ + public function testCopy() + { + $dating = Carbon::now(); + $dating2 = $dating->copy(); + $this->assertNotSame($dating, $dating2); + } + + public function testCopyEnsureTzIsCopied() + { + $dating = Carbon::createFromDate(2000, 1, 1, 'Europe/London'); + $dating2 = $dating->copy(); + $this->assertSame($dating->tzName, $dating2->tzName); + $this->assertSame($dating->offset, $dating2->offset); + } +} diff --git a/vendor/nesbot/carbon/tests/CreateFromDateTest.php b/vendor/nesbot/carbon/tests/CreateFromDateTest.php new file mode 100644 index 0000000..f8ebbc0 --- /dev/null +++ b/vendor/nesbot/carbon/tests/CreateFromDateTest.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class CreateFromDateTest extends TestFixture +{ + public function testCreateFromDateWithDefaults() + { + $d = Carbon::createFromDate(); + $this->assertSame($d->timestamp, Carbon::create(null, null, null, null, null, null)->timestamp); + } + + public function testCreateFromDate() + { + $d = Carbon::createFromDate(1975, 5, 21); + $this->assertCarbon($d, 1975, 5, 21); + } + + public function testCreateFromDateWithYear() + { + $d = Carbon::createFromDate(1975); + $this->assertSame(1975, $d->year); + } + + public function testCreateFromDateWithMonth() + { + $d = Carbon::createFromDate(null, 5); + $this->assertSame(5, $d->month); + } + + public function testCreateFromDateWithDay() + { + $d = Carbon::createFromDate(null, null, 21); + $this->assertSame(21, $d->day); + } + + public function testCreateFromDateWithTimezone() + { + $d = Carbon::createFromDate(1975, 5, 21, 'Europe/London'); + $this->assertCarbon($d, 1975, 5, 21); + $this->assertSame('Europe/London', $d->tzName); + } + + public function testCreateFromDateWithDateTimeZone() + { + $d = Carbon::createFromDate(1975, 5, 21, new \DateTimeZone('Europe/London')); + $this->assertCarbon($d, 1975, 5, 21); + $this->assertSame('Europe/London', $d->tzName); + } +} diff --git a/vendor/nesbot/carbon/tests/CreateFromFormatTest.php b/vendor/nesbot/carbon/tests/CreateFromFormatTest.php new file mode 100644 index 0000000..9cc69ec --- /dev/null +++ b/vendor/nesbot/carbon/tests/CreateFromFormatTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class CreateFromFormatTest extends TestFixture +{ + public function testCreateFromFormatReturnsCarbon() + { + $d = Carbon::createFromFormat('Y-m-d H:i:s', '1975-05-21 22:32:11'); + $this->assertCarbon($d, 1975, 5, 21, 22, 32, 11); + $this->assertTrue($d instanceof Carbon); + } + + public function testCreateFromFormatWithTimezoneString() + { + $d = Carbon::createFromFormat('Y-m-d H:i:s', '1975-05-21 22:32:11', 'Europe/London'); + $this->assertCarbon($d, 1975, 5, 21, 22, 32, 11); + $this->assertSame('Europe/London', $d->tzName); + } + + public function testCreateFromFormatWithTimezone() + { + $d = Carbon::createFromFormat('Y-m-d H:i:s', '1975-05-21 22:32:11', new \DateTimeZone('Europe/London')); + $this->assertCarbon($d, 1975, 5, 21, 22, 32, 11); + $this->assertSame('Europe/London', $d->tzName); + } +} diff --git a/vendor/nesbot/carbon/tests/CreateFromTimeTest.php b/vendor/nesbot/carbon/tests/CreateFromTimeTest.php new file mode 100644 index 0000000..c2b9b56 --- /dev/null +++ b/vendor/nesbot/carbon/tests/CreateFromTimeTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class CreateFromTimeTest extends TestFixture +{ + public function testCreateFromDateWithDefaults() + { + $d = Carbon::createFromTime(); + $this->assertSame($d->timestamp, Carbon::create(null, null, null, null, null, null)->timestamp); + } + + public function testCreateFromDate() + { + $d = Carbon::createFromTime(23, 5, 21); + $this->assertCarbon($d, Carbon::now()->year, Carbon::now()->month, Carbon::now()->day, 23, 5, 21); + } + + public function testCreateFromTimeWithHour() + { + $d = Carbon::createFromTime(22); + $this->assertSame(22, $d->hour); + $this->assertSame(0, $d->minute); + $this->assertSame(0, $d->second); + } + + public function testCreateFromTimeWithMinute() + { + $d = Carbon::createFromTime(null, 5); + $this->assertSame(5, $d->minute); + } + + public function testCreateFromTimeWithSecond() + { + $d = Carbon::createFromTime(null, null, 21); + $this->assertSame(21, $d->second); + } + + public function testCreateFromTimeWithDateTimeZone() + { + $d = Carbon::createFromTime(12, 0, 0, new \DateTimeZone('Europe/London')); + $this->assertCarbon($d, Carbon::now()->year, Carbon::now()->month, Carbon::now()->day, 12, 0, 0); + $this->assertSame('Europe/London', $d->tzName); + } + public function testCreateFromTimeWithTimeZoneString() + { + $d = Carbon::createFromTime(12, 0, 0, 'Europe/London'); + $this->assertCarbon($d, Carbon::now()->year, Carbon::now()->month, Carbon::now()->day, 12, 0, 0); + $this->assertSame('Europe/London', $d->tzName); + } +} diff --git a/vendor/nesbot/carbon/tests/CreateFromTimestampTest.php b/vendor/nesbot/carbon/tests/CreateFromTimestampTest.php new file mode 100644 index 0000000..b5a54ce --- /dev/null +++ b/vendor/nesbot/carbon/tests/CreateFromTimestampTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class CreateFromTimestampTest extends TestFixture +{ + public function testCreateReturnsDatingInstance() + { + $d = Carbon::createFromTimestamp(Carbon::create(1975, 5, 21, 22, 32, 5)->timestamp); + $this->assertCarbon($d, 1975, 5, 21, 22, 32, 5); + } + + public function testCreateFromTimestampUsesDefaultTimezone() + { + $d = Carbon::createFromTimestamp(0); + + // We know Toronto is -5 since no DST in Jan + $this->assertSame(1969, $d->year); + $this->assertSame(-5 * 3600, $d->offset); + } + + public function testCreateFromTimestampWithDateTimeZone() + { + $d = Carbon::createFromTimestamp(0, new \DateTimeZone('UTC')); + $this->assertSame('UTC', $d->tzName); + $this->assertCarbon($d, 1970, 1, 1, 0, 0, 0); + } + public function testCreateFromTimestampWithString() + { + $d = Carbon::createFromTimestamp(0, 'UTC'); + $this->assertCarbon($d, 1970, 1, 1, 0, 0, 0); + $this->assertTrue($d->offset === 0); + $this->assertSame('UTC', $d->tzName); + } + + public function testCreateFromTimestampGMTDoesNotUseDefaultTimezone() + { + $d = Carbon::createFromTimestampUTC(0); + $this->assertCarbon($d, 1970, 1, 1, 0, 0, 0); + $this->assertTrue($d->offset === 0); + } +} diff --git a/vendor/nesbot/carbon/tests/CreateTest.php b/vendor/nesbot/carbon/tests/CreateTest.php new file mode 100644 index 0000000..c79d4bd --- /dev/null +++ b/vendor/nesbot/carbon/tests/CreateTest.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class CreateTest extends TestFixture +{ + public function testCreateReturnsDatingInstance() + { + $d = Carbon::create(); + $this->assertTrue($d instanceof Carbon); + } + + public function testCreateWithDefaults() + { + $d = Carbon::create(); + $this->assertSame($d->timestamp, Carbon::now()->timestamp); + } + + public function testCreateWithYear() + { + $d = Carbon::create(2012); + $this->assertSame(2012, $d->year); + } + public function testCreateWithInvalidYear() + { + $this->setExpectedException('InvalidArgumentException'); + $d = Carbon::create(-3); + } + + public function testCreateWithMonth() + { + $d = Carbon::create(null, 3); + $this->assertSame(3, $d->month); + } + public function testCreateWithInvalidMonth() + { + $this->setExpectedException('InvalidArgumentException'); + $d = Carbon::create(null, -5); + } + public function testCreateMonthWraps() + { + $d = Carbon::create(2011, 0, 1, 0, 0, 0); + $this->assertCarbon($d, 2010, 12, 1, 0, 0, 0); + } + + public function testCreateWithDay() + { + $d = Carbon::create(null, null, 21); + $this->assertSame(21, $d->day); + } + public function testCreateWithInvalidDay() + { + $this->setExpectedException('InvalidArgumentException'); + $d = Carbon::create(null, null, -4); + } + public function testCreateDayWraps() + { + $d = Carbon::create(2011, 1, 40, 0, 0, 0); + $this->assertCarbon($d, 2011, 2, 9, 0, 0, 0); + } + + public function testCreateWithHourAndDefaultMinSecToZero() + { + $d = Carbon::create(null, null, null, 14); + $this->assertSame(14, $d->hour); + $this->assertSame(0, $d->minute); + $this->assertSame(0, $d->second); + } + public function testCreateWithInvalidHour() + { + $this->setExpectedException('InvalidArgumentException'); + $d = Carbon::create(null, null, null, -1); + } + public function testCreateHourWraps() + { + $d = Carbon::create(2011, 1, 1, 24, 0, 0); + $this->assertCarbon($d, 2011, 1, 2, 0, 0, 0); + } + + public function testCreateWithMinute() + { + $d = Carbon::create(null, null, null, null, 58); + $this->assertSame(58, $d->minute); + } + public function testCreateWithInvalidMinute() + { + $this->setExpectedException('InvalidArgumentException'); + $d = Carbon::create(2011, 1, 1, 0, -2, 0); + } + public function testCreateMinuteWraps() + { + $d = Carbon::create(2011, 1, 1, 0, 62, 0); + $this->assertCarbon($d, 2011, 1, 1, 1, 2, 0); + } + + public function testCreateWithSecond() + { + $d = Carbon::create(null, null, null, null, null, 59); + $this->assertSame(59, $d->second); + } + public function testCreateWithInvalidSecond() + { + $this->setExpectedException('InvalidArgumentException'); + $d = Carbon::create(null, null, null, null, null, -2); + } + public function testCreateSecondsWrap() + { + $d = Carbon::create(2012, 1, 1, 0, 0, 61); + $this->assertCarbon($d, 2012, 1, 1, 0, 1, 1); + } + + public function testCreateWithDateTimeZone() + { + $d = Carbon::create(2012, 1, 1, 0, 0, 0, new \DateTimeZone('Europe/London')); + $this->assertCarbon($d, 2012, 1, 1, 0, 0, 0); + $this->assertSame('Europe/London', $d->tzName); + } + public function testCreateWithTimeZoneString() + { + $d = Carbon::create(2012, 1, 1, 0, 0, 0, 'Europe/London'); + $this->assertCarbon($d, 2012, 1, 1, 0, 0, 0); + $this->assertSame('Europe/London', $d->tzName); + } +} diff --git a/vendor/nesbot/carbon/tests/DayOfWeekModifiersTest.php b/vendor/nesbot/carbon/tests/DayOfWeekModifiersTest.php new file mode 100644 index 0000000..0de8048 --- /dev/null +++ b/vendor/nesbot/carbon/tests/DayOfWeekModifiersTest.php @@ -0,0 +1,256 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class DayOfWeekModifiersTest extends TestFixture +{ + public function testStartOfWeek() + { + $d = Carbon::create(1980, 8, 7, 12, 11, 9)->startOfWeek(); + $this->assertCarbon($d, 1980, 8, 4, 0, 0, 0); + } + + public function testStartOfWeekFromWeekStart() + { + $d = Carbon::createFromDate(1980, 8, 4)->startOfWeek(); + $this->assertCarbon($d, 1980, 8, 4, 0, 0, 0); + } + + public function testEndOfWeek() + { + $d = Carbon::create(1980, 8, 7, 11, 12, 13)->endOfWeek(); + $this->assertCarbon($d, 1980, 8, 10, 23, 59, 59); + } + + public function testEndOfWeekFromWeekEnd() + { + $d = Carbon::createFromDate(1980, 8, 9)->endOfWeek(); + $this->assertCarbon($d, 1980, 8, 10, 23, 59, 59); + } + + public function testNext() + { + $d = Carbon::createFromDate(1975, 5, 21)->next(); + $this->assertCarbon($d, 1975, 5, 28, 0, 0, 0); + } + + public function testNextMonday() + { + $d = Carbon::createFromDate(1975, 5, 21)->next(Carbon::MONDAY); + $this->assertCarbon($d, 1975, 5, 26, 0, 0, 0); + } + + public function testNextSaturday() + { + $d = Carbon::createFromDate(1975, 5, 21)->next(6); + $this->assertCarbon($d, 1975, 5, 24, 0, 0, 0); + } + + public function testNextTimestamp() + { + $d = Carbon::createFromDate(1975, 11, 14)->next(); + $this->assertCarbon($d, 1975, 11, 21, 0, 0, 0); + } + + public function testPrevious() + { + $d = Carbon::createFromDate(1975, 5, 21)->previous(); + $this->assertCarbon($d, 1975, 5, 14, 0, 0, 0); + } + + public function testPreviousMonday() + { + $d = Carbon::createFromDate(1975, 5, 21)->previous(Carbon::MONDAY); + $this->assertCarbon($d, 1975, 5, 19, 0, 0, 0); + } + + public function testPreviousSaturday() + { + $d = Carbon::createFromDate(1975, 5, 21)->previous(6); + $this->assertCarbon($d, 1975, 5, 17, 0, 0, 0); + } + + public function testPreviousTimestamp() + { + $d = Carbon::createFromDate(1975, 11, 28)->previous(); + $this->assertCarbon($d, 1975, 11, 21, 0, 0, 0); + } + + public function testFirstDayOfMonth() + { + $d = Carbon::createFromDate(1975, 11, 21)->firstOfMonth(); + $this->assertCarbon($d, 1975, 11, 1, 0, 0, 0); + } + + public function testFirstWednesdayOfMonth() + { + $d = Carbon::createFromDate(1975, 11, 21)->firstOfMonth(Carbon::WEDNESDAY); + $this->assertCarbon($d, 1975, 11, 5, 0, 0, 0); + } + + public function testFirstFridayOfMonth() + { + $d = Carbon::createFromDate(1975, 11, 21)->firstOfMonth(5); + $this->assertCarbon($d, 1975, 11, 7, 0, 0, 0); + } + + public function testLastDayOfMonth() + { + $d = Carbon::createFromDate(1975, 12, 5)->lastOfMonth(); + $this->assertCarbon($d, 1975, 12, 31, 0, 0, 0); + } + + public function testLastTuesdayOfMonth() + { + $d = Carbon::createFromDate(1975, 12, 1)->lastOfMonth(Carbon::TUESDAY); + $this->assertCarbon($d, 1975, 12, 30, 0, 0, 0); + } + + public function testLastFridayOfMonth() + { + $d = Carbon::createFromDate(1975, 12, 5)->lastOfMonth(5); + $this->assertCarbon($d, 1975, 12, 26, 0, 0, 0); + } + + public function testNthOfMonthOutsideScope() + { + $this->assertFalse(Carbon::createFromDate(1975, 12, 5)->nthOfMonth(6, Carbon::MONDAY)); + } + + public function testNthOfMonthOutsideYear() + { + $this->assertFalse(Carbon::createFromDate(1975, 12, 5)->nthOfMonth(55, Carbon::MONDAY)); + } + + public function test2ndMondayOfMonth() + { + $d = Carbon::createFromDate(1975, 12, 5)->nthOfMonth(2, Carbon::MONDAY); + $this->assertCarbon($d, 1975, 12, 8, 0, 0, 0); + } + + public function test3rdWednesdayOfMonth() + { + $d = Carbon::createFromDate(1975, 12, 5)->nthOfMonth(3, 3); + $this->assertCarbon($d, 1975, 12, 17, 0, 0, 0); + } + + public function testFirstDayOfQuarter() + { + $d = Carbon::createFromDate(1975, 11, 21)->firstOfQuarter(); + $this->assertCarbon($d, 1975, 10, 1, 0, 0, 0); + } + + public function testFirstWednesdayOfQuarter() + { + $d = Carbon::createFromDate(1975, 11, 21)->firstOfQuarter(Carbon::WEDNESDAY); + $this->assertCarbon($d, 1975, 10, 1, 0, 0, 0); + } + + public function testFirstFridayOfQuarter() + { + $d = Carbon::createFromDate(1975, 11, 21)->firstOfQuarter(5); + $this->assertCarbon($d, 1975, 10, 3, 0, 0, 0); + } + + public function testLastDayOfQuarter() + { + $d = Carbon::createFromDate(1975, 8, 5)->lastOfQuarter(); + $this->assertCarbon($d, 1975, 9, 30, 0, 0, 0); + } + + public function testLastTuesdayOfQuarter() + { + $d = Carbon::createFromDate(1975, 8, 1)->lastOfQuarter(Carbon::TUESDAY); + $this->assertCarbon($d, 1975, 9, 30, 0, 0, 0); + } + + public function testLastFridayOfQuarter() + { + $d = Carbon::createFromDate(1975, 7, 5)->lastOfQuarter(5); + $this->assertCarbon($d, 1975, 9, 26, 0, 0, 0); + } + + public function testNthOfQuarterOutsideScope() + { + $this->assertFalse(Carbon::createFromDate(1975, 1, 5)->nthOfQuarter(20, Carbon::MONDAY)); + } + + public function testNthOfQuarterOutsideYear() + { + $this->assertFalse(Carbon::createFromDate(1975, 1, 5)->nthOfQuarter(55, Carbon::MONDAY)); + } + + public function test2ndMondayOfQuarter() + { + $d = Carbon::createFromDate(1975, 8, 5)->nthOfQuarter(2, Carbon::MONDAY); + $this->assertCarbon($d, 1975, 7, 14, 0, 0, 0); + } + + public function test3rdWednesdayOfQuarter() + { + $d = Carbon::createFromDate(1975, 8, 5)->nthOfQuarter(3, 3); + $this->assertCarbon($d, 1975, 7, 16, 0, 0, 0); + } + + public function testFirstDayOfYear() + { + $d = Carbon::createFromDate(1975, 11, 21)->firstOfYear(); + $this->assertCarbon($d, 1975, 1, 1, 0, 0, 0); + } + + public function testFirstWednesdayOfYear() + { + $d = Carbon::createFromDate(1975, 11, 21)->firstOfYear(Carbon::WEDNESDAY); + $this->assertCarbon($d, 1975, 1, 1, 0, 0, 0); + } + + public function testFirstFridayOfYear() + { + $d = Carbon::createFromDate(1975, 11, 21)->firstOfYear(5); + $this->assertCarbon($d, 1975, 1, 3, 0, 0, 0); + } + + public function testLastDayOfYear() + { + $d = Carbon::createFromDate(1975, 8, 5)->lastOfYear(); + $this->assertCarbon($d, 1975, 12, 31, 0, 0, 0); + } + + public function testLastTuesdayOfYear() + { + $d = Carbon::createFromDate(1975, 8, 1)->lastOfYear(Carbon::TUESDAY); + $this->assertCarbon($d, 1975, 12, 30, 0, 0, 0); + } + + public function testLastFridayOfYear() + { + $d = Carbon::createFromDate(1975, 7, 5)->lastOfYear(5); + $this->assertCarbon($d, 1975, 12, 26, 0, 0, 0); + } + + public function testNthOfYearOutsideScope() + { + $this->assertFalse(Carbon::createFromDate(1975, 1, 5)->nthOfYear(55, Carbon::MONDAY)); + } + + public function test2ndMondayOfYear() + { + $d = Carbon::createFromDate(1975, 8, 5)->nthOfYear(2, Carbon::MONDAY); + $this->assertCarbon($d, 1975, 1, 13, 0, 0, 0); + } + + public function test3rdWednesdayOfYear() + { + $d = Carbon::createFromDate(1975, 8, 5)->nthOfYear(3, 3); + $this->assertCarbon($d, 1975, 1, 15, 0, 0, 0); + } +} diff --git a/vendor/nesbot/carbon/tests/DiffTest.php b/vendor/nesbot/carbon/tests/DiffTest.php new file mode 100644 index 0000000..af9b8b5 --- /dev/null +++ b/vendor/nesbot/carbon/tests/DiffTest.php @@ -0,0 +1,436 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class DiffTest extends TestFixture +{ + public function testDiffInYearsPositive() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(1, $dt->diffInYears($dt->copy()->addYear())); + } + public function testDiffInYearsNegativeWithSign() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(-1, $dt->diffInYears($dt->copy()->subYear(), false)); + } + public function testDiffInYearsNegativeNoSign() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(1, $dt->diffInYears($dt->copy()->subYear())); + } + public function testDiffInYearsVsDefaultNow() + { + $this->assertSame(1, Carbon::now()->subYear()->diffInYears()); + } + public function testDiffInYearsEnsureIsTruncated() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(1, $dt->diffInYears($dt->copy()->addYear()->addMonths(7))); + } + + public function testDiffInMonthsPositive() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(13, $dt->diffInMonths($dt->copy()->addYear()->addMonth())); + } + public function testDiffInMonthsNegativeWithSign() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(-11, $dt->diffInMonths($dt->copy()->subYear()->addMonth(), false)); + } + public function testDiffInMonthsNegativeNoSign() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(11, $dt->diffInMonths($dt->copy()->subYear()->addMonth())); + } + public function testDiffInMonthsVsDefaultNow() + { + $this->assertSame(12, Carbon::now()->subYear()->diffInMonths()); + } + public function testDiffInMonthsEnsureIsTruncated() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(1, $dt->diffInMonths($dt->copy()->addMonth()->addDays(16))); + } + + public function testDiffInDaysPositive() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(366, $dt->diffInDays($dt->copy()->addYear())); + } + public function testDiffInDaysNegativeWithSign() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(-365, $dt->diffInDays($dt->copy()->subYear(), false)); + } + public function testDiffInDaysNegativeNoSign() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(365, $dt->diffInDays($dt->copy()->subYear())); + } + public function testDiffInDaysVsDefaultNow() + { + $this->assertSame(7, Carbon::now()->subWeek()->diffInDays()); + } + public function testDiffInDaysEnsureIsTruncated() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(1, $dt->diffInDays($dt->copy()->addDay()->addHours(13))); + } + + public function testDiffInHoursPositive() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(26, $dt->diffInHours($dt->copy()->addDay()->addHours(2))); + } + public function testDiffInHoursNegativeWithSign() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(-22, $dt->diffInHours($dt->copy()->subDay()->addHours(2), false)); + } + public function testDiffInHoursNegativeNoSign() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(22, $dt->diffInHours($dt->copy()->subDay()->addHours(2))); + } + public function testDiffInHoursVsDefaultNow() + { + $this->assertSame(48, Carbon::now()->subDays(2)->diffInHours()); + } + public function testDiffInHoursEnsureIsTruncated() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(1, $dt->diffInHours($dt->copy()->addHour()->addMinutes(31))); + } + + public function testDiffInMinutesPositive() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(62, $dt->diffInMinutes($dt->copy()->addHour()->addMinutes(2))); + } + public function testDiffInMinutesPositiveAlot() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(1502, $dt->diffInMinutes($dt->copy()->addHours(25)->addMinutes(2))); + } + public function testDiffInMinutesNegativeWithSign() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(-58, $dt->diffInMinutes($dt->copy()->subHour()->addMinutes(2), false)); + } + public function testDiffInMinutesNegativeNoSign() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(58, $dt->diffInMinutes($dt->copy()->subHour()->addMinutes(2))); + } + public function testDiffInMinutesVsDefaultNow() + { + $this->assertSame(60, Carbon::now()->subHour()->diffInMinutes()); + } + public function testDiffInMinutesEnsureIsTruncated() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(1, $dt->diffInMinutes($dt->copy()->addMinute()->addSeconds(31))); + } + + public function testDiffInSecondsPositive() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(62, $dt->diffInSeconds($dt->copy()->addMinute()->addSeconds(2))); + } + public function testDiffInSecondsPositiveAlot() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(7202, $dt->diffInSeconds($dt->copy()->addHours(2)->addSeconds(2))); + } + public function testDiffInSecondsNegativeWithSign() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(-58, $dt->diffInSeconds($dt->copy()->subMinute()->addSeconds(2), false)); + } + public function testDiffInSecondsNegativeNoSign() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(58, $dt->diffInSeconds($dt->copy()->subMinute()->addSeconds(2))); + } + public function testDiffInSecondsVsDefaultNow() + { + $this->assertSame(3600, Carbon::now()->subHour()->diffInSeconds()); + } + public function testDiffInSecondsEnsureIsTruncated() + { + $dt = Carbon::createFromDate(2000, 1, 1); + $this->assertSame(1, $dt->diffInSeconds($dt->copy()->addSeconds(1.9))); + } + + public function testDiffInSecondsWithTimezones() + { + $dtOttawa = Carbon::createFromDate(2000, 1, 1, 'America/Toronto'); + $dtVancouver = Carbon::createFromDate(2000, 1, 1, 'America/Vancouver'); + $this->assertSame(3*60*60, $dtOttawa->diffInSeconds($dtVancouver)); + } + public function testDiffInSecondsWithTimezonesAndVsDefault() + { + $dt = Carbon::now('America/Vancouver'); + $this->assertSame(0, $dt->diffInSeconds()); + } + + public function testDiffForHumansNowAndSecond() + { + $d = Carbon::now(); + $this->assertSame('1 second ago', $d->diffForHumans()); + } + public function testDiffForHumansNowAndSecondWithTimezone() + { + $d = Carbon::now('America/Vancouver'); + $this->assertSame('1 second ago', $d->diffForHumans()); + } + public function testDiffForHumansNowAndSeconds() + { + $d = Carbon::now()->subSeconds(2); + $this->assertSame('2 seconds ago', $d->diffForHumans()); + } + public function testDiffForHumansNowAndMinute() + { + $d = Carbon::now()->subMinute(); + $this->assertSame('1 minute ago', $d->diffForHumans()); + } + public function testDiffForHumansNowAndMinutes() + { + $d = Carbon::now()->subMinutes(2); + $this->assertSame('2 minutes ago', $d->diffForHumans()); + } + public function testDiffForHumansNowAndHour() + { + $d = Carbon::now()->subHour(); + $this->assertSame('1 hour ago', $d->diffForHumans()); + } + public function testDiffForHumansNowAndHours() + { + $d = Carbon::now()->subHours(2); + $this->assertSame('2 hours ago', $d->diffForHumans()); + } + public function testDiffForHumansNowAndDay() + { + $d = Carbon::now()->subDay(); + $this->assertSame('1 day ago', $d->diffForHumans()); + } + public function testDiffForHumansNowAndDays() + { + $d = Carbon::now()->subDays(2); + $this->assertSame('2 days ago', $d->diffForHumans()); + } + public function testDiffForHumansNowAndMonth() + { + $d = Carbon::now()->subMonth(); + $this->assertSame('1 month ago', $d->diffForHumans()); + } + public function testDiffForHumansNowAndMonths() + { + $d = Carbon::now()->subMonths(2); + $this->assertSame('2 months ago', $d->diffForHumans()); + } + public function testDiffForHumansNowAndYear() + { + $d = Carbon::now()->subYear(); + $this->assertSame('1 year ago', $d->diffForHumans()); + } + public function testDiffForHumansNowAndYears() + { + $d = Carbon::now()->subYears(2); + $this->assertSame('2 years ago', $d->diffForHumans()); + } + + public function testDiffForHumansNowAndFutureSecond() + { + $d = Carbon::now()->addSecond(); + $this->assertSame('1 second from now', $d->diffForHumans()); + } + public function testDiffForHumansNowAndFutureSeconds() + { + $d = Carbon::now()->addSeconds(2); + $this->assertSame('2 seconds from now', $d->diffForHumans()); + } + public function testDiffForHumansNowAndFutureMinute() + { + $d = Carbon::now()->addMinute(); + $this->assertSame('1 minute from now', $d->diffForHumans()); + } + public function testDiffForHumansNowAndFutureMinutes() + { + $d = Carbon::now()->addMinutes(2); + $this->assertSame('2 minutes from now', $d->diffForHumans()); + } + public function testDiffForHumansNowAndFutureHour() + { + $d = Carbon::now()->addHour(); + $this->assertSame('1 hour from now', $d->diffForHumans()); + } + public function testDiffForHumansNowAndFutureHours() + { + $d = Carbon::now()->addHours(2); + $this->assertSame('2 hours from now', $d->diffForHumans()); + } + public function testDiffForHumansNowAndFutureDay() + { + $d = Carbon::now()->addDay(); + $this->assertSame('1 day from now', $d->diffForHumans()); + } + public function testDiffForHumansNowAndFutureDays() + { + $d = Carbon::now()->addDays(2); + $this->assertSame('2 days from now', $d->diffForHumans()); + } + public function testDiffForHumansNowAndFutureMonth() + { + $d = Carbon::now()->addMonth(); + $this->assertSame('1 month from now', $d->diffForHumans()); + } + public function testDiffForHumansNowAndFutureMonths() + { + $d = Carbon::now()->addMonths(2); + $this->assertSame('2 months from now', $d->diffForHumans()); + } + public function testDiffForHumansNowAndFutureYear() + { + $d = Carbon::now()->addYear(); + $this->assertSame('1 year from now', $d->diffForHumans()); + } + public function testDiffForHumansNowAndFutureYears() + { + $d = Carbon::now()->addYears(2); + $this->assertSame('2 years from now', $d->diffForHumans()); + } + + public function testDiffForHumansOtherAndSecond() + { + $d = Carbon::now()->addSecond(); + $this->assertSame('1 second before', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndSeconds() + { + $d = Carbon::now()->addSeconds(2); + $this->assertSame('2 seconds before', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndMinute() + { + $d = Carbon::now()->addMinute(); + $this->assertSame('1 minute before', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndMinutes() + { + $d = Carbon::now()->addMinutes(2); + $this->assertSame('2 minutes before', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndHour() + { + $d = Carbon::now()->addHour(); + $this->assertSame('1 hour before', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndHours() + { + $d = Carbon::now()->addHours(2); + $this->assertSame('2 hours before', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndDay() + { + $d = Carbon::now()->addDay(); + $this->assertSame('1 day before', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndDays() + { + $d = Carbon::now()->addDays(2); + $this->assertSame('2 days before', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndMonth() + { + $d = Carbon::now()->addMonth(); + $this->assertSame('1 month before', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndMonths() + { + $d = Carbon::now()->addMonths(2); + $this->assertSame('2 months before', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndYear() + { + $d = Carbon::now()->addYear(); + $this->assertSame('1 year before', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndYears() + { + $d = Carbon::now()->addYears(2); + $this->assertSame('2 years before', Carbon::now()->diffForHumans($d)); + } + + public function testDiffForHumansOtherAndFutureSecond() + { + $d = Carbon::now()->subSecond(); + $this->assertSame('1 second after', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndFutureSeconds() + { + $d = Carbon::now()->subSeconds(2); + $this->assertSame('2 seconds after', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndFutureMinute() + { + $d = Carbon::now()->subMinute(); + $this->assertSame('1 minute after', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndFutureMinutes() + { + $d = Carbon::now()->subMinutes(2); + $this->assertSame('2 minutes after', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndFutureHour() + { + $d = Carbon::now()->subHour(); + $this->assertSame('1 hour after', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndFutureHours() + { + $d = Carbon::now()->subHours(2); + $this->assertSame('2 hours after', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndFutureDay() + { + $d = Carbon::now()->subDay(); + $this->assertSame('1 day after', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndFutureDays() + { + $d = Carbon::now()->subDays(2); + $this->assertSame('2 days after', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndFutureMonth() + { + $d = Carbon::now()->subMonth(); + $this->assertSame('1 month after', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndFutureMonths() + { + $d = Carbon::now()->subMonths(2); + $this->assertSame('2 months after', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndFutureYear() + { + $d = Carbon::now()->subYear(); + $this->assertSame('1 year after', Carbon::now()->diffForHumans($d)); + } + public function testDiffForHumansOtherAndFutureYears() + { + $d = Carbon::now()->subYears(2); + $this->assertSame('2 years after', Carbon::now()->diffForHumans($d)); + } +} diff --git a/vendor/nesbot/carbon/tests/FluidSettersTest.php b/vendor/nesbot/carbon/tests/FluidSettersTest.php new file mode 100644 index 0000000..e7176f3 --- /dev/null +++ b/vendor/nesbot/carbon/tests/FluidSettersTest.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class FluidSettersTest extends TestFixture +{ + public function testFluidYearSetter() + { + $d = Carbon::now(); + $this->assertTrue($d->year(1995) instanceof Carbon); + $this->assertSame(1995, $d->year); + } + + public function testFluidMonthSetter() + { + $d = Carbon::now(); + $this->assertTrue($d->month(3) instanceof Carbon); + $this->assertSame(3, $d->month); + } + public function testFluidMonthSetterWithWrap() + { + $d = Carbon::createFromDate(2012, 8, 21); + $this->assertTrue($d->month(13) instanceof Carbon); + $this->assertSame(1, $d->month); + } + + public function testFluidDaySetter() + { + $d = Carbon::now(); + $this->assertTrue($d->day(2) instanceof Carbon); + $this->assertSame(2, $d->day); + } + public function testFluidDaySetterWithWrap() + { + $d = Carbon::createFromDate(2000, 1, 1); + $this->assertTrue($d->day(32) instanceof Carbon); + $this->assertSame(1, $d->day); + } + + public function testFluidSetDate() + { + $d = Carbon::createFromDate(2000, 1, 1); + $this->assertTrue($d->setDate(1995, 13, 32) instanceof Carbon); + $this->assertCarbon($d, 1996, 2, 1); + } + + public function testFluidHourSetter() + { + $d = Carbon::now(); + $this->assertTrue($d->hour(2) instanceof Carbon); + $this->assertSame(2, $d->hour); + } + public function testFluidHourSetterWithWrap() + { + $d = Carbon::now(); + $this->assertTrue($d->hour(25) instanceof Carbon); + $this->assertSame(1, $d->hour); + } + + public function testFluidMinuteSetter() + { + $d = Carbon::now(); + $this->assertTrue($d->minute(2) instanceof Carbon); + $this->assertSame(2, $d->minute); + } + public function testFluidMinuteSetterWithWrap() + { + $d = Carbon::now(); + $this->assertTrue($d->minute(61) instanceof Carbon); + $this->assertSame(1, $d->minute); + } + + public function testFluidSecondSetter() + { + $d = Carbon::now(); + $this->assertTrue($d->second(2) instanceof Carbon); + $this->assertSame(2, $d->second); + } + public function testFluidSecondSetterWithWrap() + { + $d = Carbon::now(); + $this->assertTrue($d->second(62) instanceof Carbon); + $this->assertSame(2, $d->second); + } + + public function testFluidSetTime() + { + $d = Carbon::createFromDate(2000, 1, 1); + $this->assertTrue($d->setTime(25, 61, 61) instanceof Carbon); + $this->assertCarbon($d, 2000, 1, 2, 2, 2, 1); + } + + public function testFluidTimestampSetter() + { + $d = Carbon::now(); + $this->assertTrue($d->timestamp(10) instanceof Carbon); + $this->assertSame(10, $d->timestamp); + } +} diff --git a/vendor/nesbot/carbon/tests/GettersTest.php b/vendor/nesbot/carbon/tests/GettersTest.php new file mode 100644 index 0000000..4b84702 --- /dev/null +++ b/vendor/nesbot/carbon/tests/GettersTest.php @@ -0,0 +1,198 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class GettersTest extends TestFixture +{ + public function testGettersThrowExceptionOnUnknownGetter() + { + $this->setExpectedException('InvalidArgumentException'); + Carbon::create(1234, 5, 6, 7, 8, 9)->sdfsdfss; + } + public function testYearGetter() + { + $d = Carbon::create(1234, 5, 6, 7, 8, 9); + $this->assertSame(1234, $d->year); + } + public function testMonthGetter() + { + $d = Carbon::create(1234, 5, 6, 7, 8, 9); + $this->assertSame(5, $d->month); + } + public function testDayGetter() + { + $d = Carbon::create(1234, 5, 6, 7, 8, 9); + $this->assertSame(6, $d->day); + } + public function testHourGetter() + { + $d = Carbon::create(1234, 5, 6, 7, 8, 9); + $this->assertSame(7, $d->hour); + } + public function testMinuteGetter() + { + $d = Carbon::create(1234, 5, 6, 7, 8, 9); + $this->assertSame(8, $d->minute); + } + public function testSecondGetter() + { + $d = Carbon::create(1234, 5, 6, 7, 8, 9); + $this->assertSame(9, $d->second); + } + public function testDayOfWeeGetter() + { + $d = Carbon::create(2012, 5, 7, 7, 8, 9); + $this->assertSame(Carbon::MONDAY, $d->dayOfWeek); + } + public function testDayOfYearGetter() + { + $d = Carbon::createFromDate(2012, 5, 7); + $this->assertSame(127, $d->dayOfYear); + } + public function testDaysInMonthGetter() + { + $d = Carbon::createFromDate(2012, 5, 7); + $this->assertSame(31, $d->daysInMonth); + } + public function testTimestampGetter() + { + $d = Carbon::create(); + $d->setTimezone('GMT'); + $this->assertSame(0, $d->setDateTime(1970, 1, 1, 0, 0, 0)->timestamp); + } + + public function testGetAge() + { + $d = Carbon::now(); + $this->assertSame(0, $d->age); + } + public function testGetAgeWithRealAge() + { + $d = Carbon::createFromDate(1975, 5, 21); + $age = intval(substr(date('Ymd') - date('Ymd', $d->timestamp), 0, -4)); + + $this->assertSame($age, $d->age); + } + + public function testGetQuarterFirst() + { + $d = Carbon::createFromDate(2012, 1, 1); + $this->assertSame(1, $d->quarter); + } + public function testGetQuarterFirstEnd() + { + $d = Carbon::createFromDate(2012, 3, 31); + $this->assertSame(1, $d->quarter); + } + public function testGetQuarterSecond() + { + $d = Carbon::createFromDate(2012, 4, 1); + $this->assertSame(2, $d->quarter); + } + public function testGetQuarterThird() + { + $d = Carbon::createFromDate(2012, 7, 1); + $this->assertSame(3, $d->quarter); + } + public function testGetQuarterFourth() + { + $d = Carbon::createFromDate(2012, 10, 1); + $this->assertSame(4, $d->quarter); + } + public function testGetQuarterFirstLast() + { + $d = Carbon::createFromDate(2012, 12, 31); + $this->assertSame(4, $d->quarter); + } + + public function testGetDstFalse() + { + $this->assertFalse(Carbon::createFromDate(2012, 1, 1, 'America/Toronto')->dst); + } + public function testGetDstTrue() + { + $this->assertTrue(Carbon::createFromDate(2012, 7, 1, 'America/Toronto')->dst); + } + + public function testOffsetForTorontoWithDST() + { + $this->assertSame(-18000, Carbon::createFromDate(2012, 1, 1, 'America/Toronto')->offset); + } + public function testOffsetForTorontoNoDST() + { + $this->assertSame(-14400, Carbon::createFromDate(2012, 6, 1, 'America/Toronto')->offset); + } + public function testOffsetForGMT() + { + $this->assertSame(0, Carbon::createFromDate(2012, 6, 1, 'GMT')->offset); + } + public function testOffsetHoursForTorontoWithDST() + { + $this->assertSame(-5, Carbon::createFromDate(2012, 1, 1, 'America/Toronto')->offsetHours); + } + public function testOffsetHoursForTorontoNoDST() + { + $this->assertSame(-4, Carbon::createFromDate(2012, 6, 1, 'America/Toronto')->offsetHours); + } + public function testOffsetHoursForGMT() + { + $this->assertSame(0, Carbon::createFromDate(2012, 6, 1, 'GMT')->offsetHours); + } + + public function testIsLeapYearTrue() + { + $this->assertTrue(Carbon::createFromDate(2012, 1, 1)->isLeapYear()); + } + public function testIsLeapYearFalse() + { + $this->assertFalse(Carbon::createFromDate(2011, 1, 1)->isLeapYear()); + } + + public function testWeekOfYearFirstWeek() + { + $this->assertSame(52, Carbon::createFromDate(2012, 1, 1)->weekOfYear); + $this->assertSame(1, Carbon::createFromDate(2012, 1, 2)->weekOfYear); + } + public function testWeekOfYearLastWeek() + { + $this->assertSame(52, Carbon::createFromDate(2012, 12, 30)->weekOfYear); + $this->assertSame(1, Carbon::createFromDate(2012, 12, 31)->weekOfYear); + } + + public function testGetTimezone() + { + $dt = Carbon::createFromDate(2000, 1, 1, 'America/Toronto'); + $this->assertSame('America/Toronto', $dt->timezone->getName()); + } + public function testGetTz() + { + $dt = Carbon::createFromDate(2000, 1, 1, 'America/Toronto'); + $this->assertSame('America/Toronto', $dt->tz->getName()); + } + public function testGetTimezoneName() + { + $dt = Carbon::createFromDate(2000, 1, 1, 'America/Toronto'); + $this->assertSame('America/Toronto', $dt->timezoneName); + } + public function testGetTzName() + { + $dt = Carbon::createFromDate(2000, 1, 1, 'America/Toronto'); + $this->assertSame('America/Toronto', $dt->tzName); + } + + public function testInvalidGetter() + { + $this->setExpectedException('InvalidArgumentException'); + $d = Carbon::now(); + $bb = $d->doesNotExit; + } +} diff --git a/vendor/nesbot/carbon/tests/InstanceTest.php b/vendor/nesbot/carbon/tests/InstanceTest.php new file mode 100644 index 0000000..15dbf59 --- /dev/null +++ b/vendor/nesbot/carbon/tests/InstanceTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class InstanceTest extends TestFixture +{ + public function testInstanceFromDateTime() + { + $dating = Carbon::instance(\DateTime::createFromFormat('Y-m-d H:i:s', '1975-05-21 22:32:11')); + $this->assertCarbon($dating, 1975, 5, 21, 22, 32, 11); + } + + public function testInstanceFromDateTimeKeepsTimezoneName() + { + $dating = Carbon::instance(\DateTime::createFromFormat('Y-m-d H:i:s', '1975-05-21 22:32:11')->setTimezone(new \DateTimeZone('America/Vancouver'))); + $this->assertSame('America/Vancouver', $dating->tzName); + } +} diff --git a/vendor/nesbot/carbon/tests/IsTest.php b/vendor/nesbot/carbon/tests/IsTest.php new file mode 100644 index 0000000..e44f87c --- /dev/null +++ b/vendor/nesbot/carbon/tests/IsTest.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class IsTest extends TestFixture +{ + public function testIsWeekdayTrue() + { + $this->assertTrue(Carbon::createFromDate(2012, 1, 2)->isWeekday()); + } + public function testIsWeekdayFalse() + { + $this->assertFalse(Carbon::createFromDate(2012, 1, 1)->isWeekday()); + } + public function testIsWeekendTrue() + { + $this->assertTrue(Carbon::createFromDate(2012, 1, 1)->isWeekend()); + } + public function testIsWeekendFalse() + { + $this->assertFalse(Carbon::createFromDate(2012, 1, 2)->isWeekend()); + } + + public function testIsYesterdayTrue() + { + $this->assertTrue(Carbon::now()->subDay()->isYesterday()); + } + public function testIsYesterdayFalseWithToday() + { + $this->assertFalse(Carbon::now()->endOfDay()->isYesterday()); + } + public function testIsYesterdayFalseWith2Days() + { + $this->assertFalse(Carbon::now()->subDays(2)->startOfDay()->isYesterday()); + } + + public function testIsTodayTrue() + { + $this->assertTrue(Carbon::now()->isToday()); + } + public function testIsTodayFalseWithYesterday() + { + $this->assertFalse(Carbon::now()->subDay()->endOfDay()->isToday()); + } + public function testIsTodayFalseWithTomorrow() + { + $this->assertFalse(Carbon::now()->addDay()->startOfDay()->isToday()); + } + public function testIsTodayWithTimezone() + { + $this->assertTrue(Carbon::now('Asia/Tokyo')->isToday()); + } + + public function testIsTomorrowTrue() + { + $this->assertTrue(Carbon::now()->addDay()->isTomorrow()); + } + public function testIsTomorrowFalseWithToday() + { + $this->assertFalse(Carbon::now()->endOfDay()->isTomorrow()); + } + public function testIsTomorrowFalseWith2Days() + { + $this->assertFalse(Carbon::now()->addDays(2)->startOfDay()->isTomorrow()); + } + + public function testIsFutureTrue() + { + $this->assertTrue(Carbon::now()->addSecond()->isFuture()); + } + public function testIsFutureFalse() + { + $this->assertFalse(Carbon::now()->isFuture()); + } + public function testIsFutureFalseInThePast() + { + $this->assertFalse(Carbon::now()->subSecond()->isFuture()); + } + + public function testIsPastTrue() + { + $this->assertTrue(Carbon::now()->subSecond()->isPast()); + } + public function testIsPast() + { + $this->assertFalse(Carbon::now()->addSecond()->isPast()); + } +} diff --git a/vendor/nesbot/carbon/tests/IssetTest.php b/vendor/nesbot/carbon/tests/IssetTest.php new file mode 100644 index 0000000..e66c97c --- /dev/null +++ b/vendor/nesbot/carbon/tests/IssetTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class IssetTest extends TestFixture +{ + public function testIssetReturnFalseForUnknownProperty() + { + $this->assertFalse(isset(Carbon::create(1234, 5, 6, 7, 8, 9)->sdfsdfss)); + } + public function testIssetReturnTrueForProperties() + { + $properties = array('year', 'month', 'day', 'hour', 'minute', 'second', 'dayOfWeek', 'dayOfYear', 'daysInMonth', 'timestamp', 'age', 'quarter', 'dst', 'offset', 'offsetHours', 'timezone', 'timezoneName', 'tz', 'tzName'); + foreach ($properties as $property) { + $this->assertTrue(isset(Carbon::create(1234, 5, 6, 7, 8, 9)->$property)); + } + } +} diff --git a/vendor/nesbot/carbon/tests/NowAndOtherStaticHelpersTest.php b/vendor/nesbot/carbon/tests/NowAndOtherStaticHelpersTest.php new file mode 100644 index 0000000..c89b9cf --- /dev/null +++ b/vendor/nesbot/carbon/tests/NowAndOtherStaticHelpersTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class NowAndOtherStaticHelpersTest extends TestFixture +{ + public function testNow() + { + $dt = Carbon::now(); + $this->assertSame(time(), $dt->timestamp); + } + public function testNowWithTimezone() + { + $dt = Carbon::now('Europe/London'); + $this->assertSame(time(), $dt->timestamp); + $this->assertSame('Europe/London', $dt->tzName); + } + + public function testToday() + { + $dt = Carbon::today(); + $this->assertSame(date('Y-m-d 00:00:00'), $dt->toDateTimeString()); + } + public function testTodayWithTimezone() + { + $dt = Carbon::today('Europe/London'); + $dt2 = new \DateTime('now', new \DateTimeZone('Europe/London')); + $this->assertSame($dt2->format('Y-m-d 00:00:00'), $dt->toDateTimeString()); + } + + public function testTomorrow() + { + $dt = Carbon::tomorrow(); + $dt2 = new \DateTime('tomorrow'); + $this->assertSame($dt2->format('Y-m-d 00:00:00'), $dt->toDateTimeString()); + } + public function testTomorrowWithTimezone() + { + $dt = Carbon::tomorrow('Europe/London'); + $dt2 = new \DateTime('tomorrow', new \DateTimeZone('Europe/London')); + $this->assertSame($dt2->format('Y-m-d 00:00:00'), $dt->toDateTimeString()); + } + + public function testYesterday() + { + $dt = Carbon::yesterday(); + $dt2 = new \DateTime('yesterday'); + $this->assertSame($dt2->format('Y-m-d 00:00:00'), $dt->toDateTimeString()); + } + public function testYesterdayWithTimezone() + { + $dt = Carbon::yesterday('Europe/London'); + $dt2 = new \DateTime('yesterday', new \DateTimeZone('Europe/London')); + $this->assertSame($dt2->format('Y-m-d 00:00:00'), $dt->toDateTimeString()); + } +} diff --git a/vendor/nesbot/carbon/tests/SettersTest.php b/vendor/nesbot/carbon/tests/SettersTest.php new file mode 100644 index 0000000..dccbbd1 --- /dev/null +++ b/vendor/nesbot/carbon/tests/SettersTest.php @@ -0,0 +1,187 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class SettersTest extends TestFixture +{ + public function testYearSetter() + { + $d = Carbon::now(); + $d->year = 1995; + $this->assertSame(1995, $d->year); + } + + public function testMonthSetter() + { + $d = Carbon::now(); + $d->month = 3; + $this->assertSame(3, $d->month); + } + public function testMonthSetterWithWrap() + { + $d = Carbon::now(); + $d->month = 13; + $this->assertSame(1, $d->month); + } + + public function testDaySetter() + { + $d = Carbon::now(); + $d->day = 2; + $this->assertSame(2, $d->day); + } + public function testDaySetterWithWrap() + { + $d = Carbon::createFromDate(2012, 8, 5); + $d->day = 32; + $this->assertSame(1, $d->day); + } + + public function testHourSetter() + { + $d = Carbon::now(); + $d->hour = 2; + $this->assertSame(2, $d->hour); + } + public function testHourSetterWithWrap() + { + $d = Carbon::now(); + $d->hour = 25; + $this->assertSame(1, $d->hour); + } + + public function testMinuteSetter() + { + $d = Carbon::now(); + $d->minute = 2; + $this->assertSame(2, $d->minute); + } + public function testMinuteSetterWithWrap() + { + $d = Carbon::now(); + $d->minute = 65; + $this->assertSame(5, $d->minute); + } + + public function testSecondSetter() + { + $d = Carbon::now(); + $d->second = 2; + $this->assertSame(2, $d->second); + } + public function testSecondSetterWithWrap() + { + $d = Carbon::now(); + $d->second = 65; + $this->assertSame(5, $d->second); + } + + public function testTimestampSetter() + { + $d = Carbon::now(); + $d->timestamp = 10; + $this->assertSame(10, $d->timestamp); + + $d->setTimestamp(11); + $this->assertSame(11, $d->timestamp); + } + + public function testSetTimezoneWithInvalidTimezone() + { + $this->setExpectedException('InvalidArgumentException'); + $d = Carbon::now(); + $d->setTimezone('sdf'); + } + public function testTimezoneWithInvalidTimezone() + { + $d = Carbon::now(); + + try { + $d->timezone = 'sdf'; + $this->fail('InvalidArgumentException was not been raised.'); + } catch (InvalidArgumentException $expected) {} + + try { + $d->timezone('sdf'); + $this->fail('InvalidArgumentException was not been raised.'); + } catch (InvalidArgumentException $expected) {} + } + public function testTzWithInvalidTimezone() + { + $d = Carbon::now(); + + try { + $d->tz = 'sdf'; + $this->fail('InvalidArgumentException was not been raised.'); + } catch (InvalidArgumentException $expected) {} + + try { + $d->tz('sdf'); + $this->fail('InvalidArgumentException was not been raised.'); + } catch (InvalidArgumentException $expected) {} + } + public function testSetTimezoneUsingString() + { + $d = Carbon::now(); + $d->setTimezone('America/Toronto'); + $this->assertSame('America/Toronto', $d->tzName); + } + public function testTimezoneUsingString() + { + $d = Carbon::now(); + $d->timezone = 'America/Toronto'; + $this->assertSame('America/Toronto', $d->tzName); + + $d->timezone('America/Vancouver'); + $this->assertSame('America/Vancouver', $d->tzName); + } + public function testTzUsingString() + { + $d = Carbon::now(); + $d->tz = 'America/Toronto'; + $this->assertSame('America/Toronto', $d->tzName); + + $d->tz('America/Vancouver'); + $this->assertSame('America/Vancouver', $d->tzName); + } + public function testSetTimezoneUsingDateTimeZone() + { + $d = Carbon::now(); + $d->setTimezone(new \DateTimeZone('America/Toronto')); + $this->assertSame('America/Toronto', $d->tzName); + } + public function testTimezoneUsingDateTimeZone() + { + $d = Carbon::now(); + $d->timezone = new \DateTimeZone('America/Toronto'); + $this->assertSame('America/Toronto', $d->tzName); + + $d->timezone(new \DateTimeZone('America/Vancouver')); + $this->assertSame('America/Vancouver', $d->tzName); + } + public function testTzUsingDateTimeZone() + { + $d = Carbon::now(); + $d->tz = new \DateTimeZone('America/Toronto'); + $this->assertSame('America/Toronto', $d->tzName); + + $d->tz(new \DateTimeZone('America/Vancouver')); + $this->assertSame('America/Vancouver', $d->tzName); + } + + public function testInvalidSetter() + { + $this->setExpectedException('InvalidArgumentException'); + $d = Carbon::now(); + $d->doesNotExit = 'bb'; + } +} diff --git a/vendor/nesbot/carbon/tests/StartEndOfTest.php b/vendor/nesbot/carbon/tests/StartEndOfTest.php new file mode 100644 index 0000000..e64270c --- /dev/null +++ b/vendor/nesbot/carbon/tests/StartEndOfTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class StartEndOfTest extends TestFixture +{ + public function testStartOfDay() + { + $dt = Carbon::now(); + $this->assertTrue($dt->startOfDay() instanceof Carbon); + $this->assertCarbon($dt, $dt->year, $dt->month, $dt->day, 0, 0, 0); + } + public function testEndOfDay() + { + $dt = Carbon::now(); + $this->assertTrue($dt->endOfDay() instanceof Carbon); + $this->assertCarbon($dt, $dt->year, $dt->month, $dt->day, 23, 59, 59); + } + + public function testStartOfMonthIsFluid() + { + $dt = Carbon::now(); + $this->assertTrue($dt->startOfMonth() instanceof Carbon); + } + public function testStartOfMonthFromNow() + { + $dt = Carbon::now()->startOfMonth(); + $this->assertCarbon($dt, $dt->year, $dt->month, 1, 0, 0, 0); + } + public function testStartOfMonthFromLastDay() + { + $dt = Carbon::create(2000, 1, 31, 2, 3, 4)->startOfMonth(); + $this->assertCarbon($dt, 2000, 1, 1, 0, 0, 0); + } + + public function testEndOfMonthIsFluid() + { + $dt = Carbon::now(); + $this->assertTrue($dt->endOfMonth() instanceof Carbon); + } + public function testEndOfMonth() + { + $dt = Carbon::create(2000, 1, 1, 2, 3, 4)->endOfMonth(); + $this->assertCarbon($dt, 2000, 1, 31, 23, 59, 59); + } + public function testEndOfMonthFromLastDay() + { + $dt = Carbon::create(2000, 1, 31, 2, 3, 4)->endOfMonth(); + $this->assertCarbon($dt, 2000, 1, 31, 23, 59, 59); + } +} diff --git a/vendor/nesbot/carbon/tests/StringsTest.php b/vendor/nesbot/carbon/tests/StringsTest.php new file mode 100644 index 0000000..9080cac --- /dev/null +++ b/vendor/nesbot/carbon/tests/StringsTest.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class StringsTest extends TestFixture +{ + public function testToString() + { + $d = Carbon::now(); + $this->assertSame(Carbon::now()->toDateTimeString(), ''.$d); + } + + public function testToDateString() + { + $d = Carbon::create(1975, 12, 25, 14, 15, 16); + $this->assertSame('1975-12-25', $d->toDateString()); + } + public function testToFormattedDateString() + { + $d = Carbon::create(1975, 12, 25, 14, 15, 16); + $this->assertSame('Dec 25, 1975', $d->toFormattedDateString()); + } + public function testToLocalizedFormattedDateString() + { + /**************** + + Working out a Travis issue on how to set a different locale + other than EN to test this. + + + $cache = setlocale(LC_TIME, 0); + setlocale(LC_TIME, 'German'); + $d = Carbon::create(1975, 12, 25, 14, 15, 16); + $this->assertSame('Donnerstag 25 Dezember 1975', $d->formatLocalized('%A %d %B %Y')); + setlocale(LC_TIME, $cache); + + *****************/ + } + public function testToTimeString() + { + $d = Carbon::create(1975, 12, 25, 14, 15, 16); + $this->assertSame('14:15:16', $d->toTimeString()); + } + public function testToDateTimeString() + { + $d = Carbon::create(1975, 12, 25, 14, 15, 16); + $this->assertSame('1975-12-25 14:15:16', $d->toDateTimeString()); + } + public function testToDateTimeStringWithPaddedZeroes() + { + $d = Carbon::create(2000, 5, 2, 4, 3, 4); + $this->assertSame('2000-05-02 04:03:04', $d->toDateTimeString()); + } + public function testToDayDateTimeString() + { + $d = Carbon::create(1975, 12, 25, 14, 15, 16); + $this->assertSame('Thu, Dec 25, 1975 2:15 PM', $d->toDayDateTimeString()); + } + + public function testToATOMString() + { + $d = Carbon::create(1975, 12, 25, 14, 15, 16); + $this->assertSame('1975-12-25T14:15:16-05:00', $d->toATOMString()); + } + public function testToCOOKIEString() + { + $d = Carbon::create(1975, 12, 25, 14, 15, 16); + $this->assertSame('Thursday, 25-Dec-75 14:15:16 EST', $d->toCOOKIEString()); + } + public function testToISO8601String() + { + $d = Carbon::create(1975, 12, 25, 14, 15, 16); + $this->assertSame('1975-12-25T14:15:16-0500', $d->toISO8601String()); + } + public function testToRC822String() + { + $d = Carbon::create(1975, 12, 25, 14, 15, 16); + $this->assertSame('Thu, 25 Dec 75 14:15:16 -0500', $d->toRFC822String()); + } + public function testToRFC850String() + { + $d = Carbon::create(1975, 12, 25, 14, 15, 16); + $this->assertSame('Thursday, 25-Dec-75 14:15:16 EST', $d->toRFC850String()); + } + public function testToRFC1036String() + { + $d = Carbon::create(1975, 12, 25, 14, 15, 16); + $this->assertSame('Thu, 25 Dec 75 14:15:16 -0500', $d->toRFC1036String()); + } + public function testToRFC1123String() + { + $d = Carbon::create(1975, 12, 25, 14, 15, 16); + $this->assertSame('Thu, 25 Dec 1975 14:15:16 -0500', $d->toRFC1123String()); + } + public function testToRFC2822String() + { + $d = Carbon::create(1975, 12, 25, 14, 15, 16); + $this->assertSame('Thu, 25 Dec 1975 14:15:16 -0500', $d->toRFC2822String()); + } + public function testToRFC3339String() + { + $d = Carbon::create(1975, 12, 25, 14, 15, 16); + $this->assertSame('1975-12-25T14:15:16-05:00', $d->toRFC3339String()); + } + public function testToRSSString() + { + $d = Carbon::create(1975, 12, 25, 14, 15, 16); + $this->assertSame('Thu, 25 Dec 1975 14:15:16 -0500', $d->toRSSString()); + } + public function testToW3CString() + { + $d = Carbon::create(1975, 12, 25, 14, 15, 16); + $this->assertSame('1975-12-25T14:15:16-05:00', $d->toW3CString()); + } +} diff --git a/vendor/nesbot/carbon/tests/SubTest.php b/vendor/nesbot/carbon/tests/SubTest.php new file mode 100644 index 0000000..47df78c --- /dev/null +++ b/vendor/nesbot/carbon/tests/SubTest.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class SubTest extends TestFixture +{ + public function testSubYearsPositive() + { + $this->assertSame(1974, Carbon::createFromDate(1975)->subYears(1)->year); + } + public function testSubYearsZero() + { + $this->assertSame(1975, Carbon::createFromDate(1975)->subYears(0)->year); + } + public function testSubYearsNegative() + { + $this->assertSame(1976, Carbon::createFromDate(1975)->subYears(-1)->year); + } + + public function testSubYear() + { + $this->assertSame(1974, Carbon::createFromDate(1975)->subYear()->year); + } + + public function testSubMonthsPositive() + { + $this->assertSame(12, Carbon::createFromDate(1975, 1, 1)->subMonths(1)->month); + } + public function testSubMonthsZero() + { + $this->assertSame(1, Carbon::createFromDate(1975, 1, 1)->subMonths(0)->month); + } + public function testSubMonthsNegative() + { + $this->assertSame(2, Carbon::createFromDate(1975, 1, 1)->subMonths(-1)->month); + } + + public function testSubMonth() + { + $this->assertSame(12, Carbon::createFromDate(1975, 1, 1)->subMonth()->month); + } + + public function testSubDaysPositive() + { + $this->assertSame(30, Carbon::createFromDate(1975, 5, 1)->subDays(1)->day); + } + public function testSubDaysZero() + { + $this->assertSame(1, Carbon::createFromDate(1975, 5, 1)->subDays(0)->day); + } + public function testSubDaysNegative() + { + $this->assertSame(2, Carbon::createFromDate(1975, 5, 1)->subDays(-1)->day); + } + + public function testSubDay() + { + $this->assertSame(30, Carbon::createFromDate(1975, 5, 1)->subDay()->day); + } + + public function testSubWeekdaysPositive() + { + $this->assertSame(22, Carbon::createFromDate(2012, 1, 4)->subWeekdays(9)->day); + } + public function testSubWeekdaysZero() + { + $this->assertSame(4, Carbon::createFromDate(2012, 1, 4)->subWeekdays(0)->day); + } + public function testSubWeekdaysNegative() + { + $this->assertSame(13, Carbon::createFromDate(2012, 1, 31)->subWeekdays(-9)->day); + } + + public function testSubWeekday() + { + $this->assertSame(6, Carbon::createFromDate(2012, 1, 9)->subWeekday()->day); + } + + public function testSubWeeksPositive() + { + $this->assertSame(14, Carbon::createFromDate(1975, 5, 21)->subWeeks(1)->day); + } + public function testSubWeeksZero() + { + $this->assertSame(21, Carbon::createFromDate(1975, 5, 21)->subWeeks(0)->day); + } + public function testSubWeeksNegative() + { + $this->assertSame(28, Carbon::createFromDate(1975, 5, 21)->subWeeks(-1)->day); + } + + public function testSubWeek() + { + $this->assertSame(14, Carbon::createFromDate(1975, 5, 21)->subWeek()->day); + } + + public function testSubHoursPositive() + { + $this->assertSame(23, Carbon::createFromTime(0)->subHours(1)->hour); + } + public function testSubHoursZero() + { + $this->assertSame(0, Carbon::createFromTime(0)->subHours(0)->hour); + } + public function testSubHoursNegative() + { + $this->assertSame(1, Carbon::createFromTime(0)->subHours(-1)->hour); + } + + public function testSubHour() + { + $this->assertSame(23, Carbon::createFromTime(0)->subHour()->hour); + } + + public function testSubMinutesPositive() + { + $this->assertSame(59, Carbon::createFromTime(0, 0)->subMinutes(1)->minute); + } + public function testSubMinutesZero() + { + $this->assertSame(0, Carbon::createFromTime(0, 0)->subMinutes(0)->minute); + } + public function testSubMinutesNegative() + { + $this->assertSame(1, Carbon::createFromTime(0, 0)->subMinutes(-1)->minute); + } + + public function testSubMinute() + { + $this->assertSame(59, Carbon::createFromTime(0, 0)->subMinute()->minute); + } + + public function testSubSecondsPositive() + { + $this->assertSame(59, Carbon::createFromTime(0, 0, 0)->subSeconds(1)->second); + } + public function testSubSecondsZero() + { + $this->assertSame(0, Carbon::createFromTime(0, 0, 0)->subSeconds(0)->second); + } + public function testSubSecondsNegative() + { + $this->assertSame(1, Carbon::createFromTime(0, 0, 0)->subSeconds(-1)->second); + } + + public function testSubSecond() + { + $this->assertSame(59, Carbon::createFromTime(0, 0, 0)->subSecond()->second); + } +} diff --git a/vendor/nesbot/carbon/tests/TestFixture.php b/vendor/nesbot/carbon/tests/TestFixture.php new file mode 100644 index 0000000..fa5d670 --- /dev/null +++ b/vendor/nesbot/carbon/tests/TestFixture.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +require __DIR__.'/../vendor/autoload.php'; + +use Carbon\Carbon; + +class TestFixture extends \PHPUnit_Framework_TestCase +{ + private $saveTz; + + protected function setUp() + { + //save current timezone + $this->saveTz = date_default_timezone_get(); + + date_default_timezone_set('America/Toronto'); + } + + protected function tearDown() + { + date_default_timezone_set($this->saveTz); + } + + protected function assertCarbon(Carbon $d, $year, $month, $day, $hour = null, $minute = null, $second = null) + { + $this->assertSame($year, $d->year, 'Carbon->year'); + $this->assertSame($month, $d->month, 'Carbon->month'); + $this->assertSame($day, $d->day, 'Carbon->day'); + + if ($hour !== null) { + $this->assertSame($hour, $d->hour, 'Carbon->hour'); + } + + if ($minute !== null) { + $this->assertSame($minute, $d->minute, 'Carbon->minute'); + } + + if ($second !== null) { + $this->assertSame($second, $d->second, 'Carbon->second'); + } + } +} diff --git a/vendor/nesbot/carbon/tests/TestingAidsTest.php b/vendor/nesbot/carbon/tests/TestingAidsTest.php new file mode 100644 index 0000000..5da9e39 --- /dev/null +++ b/vendor/nesbot/carbon/tests/TestingAidsTest.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Carbon\Carbon; + +class TestingAidsTest extends TestFixture +{ + public function testTestingAidsWithTestNowNotSet() + { + Carbon::setTestNow(); + + $this->assertFalse(Carbon::hasTestNow()); + $this->assertNull(Carbon::getTestNow()); + } + public function testTestingAidsWithTestNowSet() + { + $notNow = Carbon::yesterday(); + Carbon::setTestNow($notNow); + + $this->assertTrue(Carbon::hasTestNow()); + $this->assertSame($notNow, Carbon::getTestNow()); + } + + public function testConstructorWithTestValueSet() + { + $notNow = Carbon::yesterday(); + Carbon::setTestNow($notNow); + + $this->assertEquals($notNow, new Carbon()); + $this->assertEquals($notNow, new Carbon(null)); + $this->assertEquals($notNow, new Carbon('')); + $this->assertEquals($notNow, new Carbon('now')); + } + + public function testNowWithTestValueSet() + { + $notNow = Carbon::yesterday(); + Carbon::setTestNow($notNow); + + $this->assertEquals($notNow, Carbon::now()); + } + + public function testParseWithTestValueSet() + { + $notNow = Carbon::yesterday(); + Carbon::setTestNow($notNow); + + $this->assertEquals($notNow, Carbon::parse()); + $this->assertEquals($notNow, Carbon::parse(null)); + $this->assertEquals($notNow, Carbon::parse('')); + $this->assertEquals($notNow, Carbon::parse('now')); + } +} diff --git a/vendor/nikic/php-parser/.travis.yml b/vendor/nikic/php-parser/.travis.yml new file mode 100644 index 0000000..e5ec7c9 --- /dev/null +++ b/vendor/nikic/php-parser/.travis.yml @@ -0,0 +1,7 @@ +language: php + +php: + - 5.2 + - 5.3 + - 5.4 + - 5.5 \ No newline at end of file diff --git a/vendor/nikic/php-parser/CHANGELOG.md b/vendor/nikic/php-parser/CHANGELOG.md new file mode 100644 index 0000000..55b7aa4 --- /dev/null +++ b/vendor/nikic/php-parser/CHANGELOG.md @@ -0,0 +1,155 @@ +Version 0.9.5-dev +----------------- + +Nothing yet. + +Version 0.9.4 (25.08.2013) +-------------------------- +* [PHP 5.5] Add support for `ClassName::class`. This is parsed as an `Expr_ClassConstFetch` with `'class'` being the + constant name. + +* Syntax errors now include information on expected tokens and mimic the format of PHP's own (pre 5.4) error messages. + Example: + + Old: Unexpected token T_STATIC on line 1 + New: Syntax error, unexpected T_STATIC, expecting T_STRING or T_NS_SEPARATOR or '{' + +* `PHPParser_PrettyPrinter_Zend` was renamed to `PHPParser_PrettyPrinter_Default` as the default pretty printer only + very loosely applies the Zend Coding Standard. The class `PHPParser_PrettyPrinter_Zend` extends + `PHPParser_PrettyPrinter_Default` to maintain backwards compatibility. + +* The pretty printer now prints namespaces in semicolon-style if possible (i.e. if the file does not contain a global + namespace declaration). + +* Added `prettyPrintFile(array $stmts)` method which will pretty print a file of statements including the opening + `` at the start and end + of files using inline HTML. + +* There now is a builder for interfaces (`PHPParser_Builder_Interface`). + +* An interface for the node traversation has been added: `PHPParser_NodeTraverserInterface` + +* Fix pretty printing of `include` expressions (precedence information was missing). + +* Fix "undefined index" notices when generating the expected tokens for a syntax error. + +* Improve performance of `PrettyPrinter` construction by no longer using the `uniqid()` function. + +Version 0.9.3 (22.11.2012) +-------------------------- + +* [BC] As `list()` in `foreach` is now supported the structure of list assignments changed: + + 1. There is no longer a dedicated `AssignList` node; instead a normal `Assign` node is used with a `List` as `var`. + 2. Nested lists are now `List` nodes too, instead of just arrays. + +* [BC] As arbitrary expressions are allowed in `empty()` now its subnode was renamed from `var` to `expr`. + +* [BC] The protected `pSafe()` method in `PrettyPrinterAbstract` was renamed to `pNoIndent()`. + +* [PHP 5.5] Add support for arbitrary expressions in `empty()`. + +* [PHP 5.5] Add support for constant array / string dereferencing. + Examples: `"foo"[2]`, `[1, 2, 3][2]` + +* [PHP 5.5] Add support for `yield` expressions. This adds a new `Yield` expression type, with subnodes `key` and + `value`. + +* [PHP 5.5] Add support for `finally`. This adds a new `finallyStmts` subnode to the `TryCatch` node. If there is no + finally clause it will be `null`. + +* [PHP 5.5] Add support for `list()` destructuring of `foreach` values. + Example: `foreach ($coords as list($x, $y)) { ... }` + +* Improve pretty printing of expressions by printing less unnecessary parentheses. In particular concatenations are now + printed as `$a . $b . $c . $d . $e` rather than `$a . ($b . ($c . ($d . $e)))`. This is implemented by taking operator + associativity into account. New protected methods added to the pretty printer are `pPrec()`, `pInfixOp()`, + `pPrefixOp()` and `pPostfixOp()`. This also fixes an issue with extraneous parentheses in closure bodies. + +* Fix formatting of fall-through `case` statements in the Zend pretty printer. + +* Fix parsing of `$foo =& new Bar`. It is now properly parsed as `AssignRef` (instead of `Assign`). + +* Fix assignment of `$endAttributes`. Sometimes the attributes of the token right after the node were assigned, rather + than the attributes of the last token in the node. + +* `rebuildParser.php` is now designed to be run from the command line rather than from the browser. + +Version 0.9.2 (07.07.2012) +-------------------------- + +* Add `Class->getMethods()` function, which returns all methods contained in the `stmts` array of the class node. This + does not take inherited methods into account. + +* Add `isPublic()`, `isProtected()`, `isPrivate()`. `isAbstract()`, `isFinal()` and `isStatic()` accessors to the + `ClassMethod`, `Property` and `Class` nodes. (`Property` and `Class` obviously only have the accessors relevant to + them.) + +* Fix parsing of new expressions in parentheses, e.g. `return(new Foo);`. + +* [BC] Due to the below changes nodes now optionally accept an `$attributes` array as the + last parameter, instead of the previously used `$line` and `$docComment` parameters. + +* Add mechanism for adding attributes to nodes in the lexer. + + The following attributes are now added by default: + + * `startLine`: The line the node started in. + * `endLine`: The line the node ended in. + * `comments`: An array of comments. The comments are instances of `PHPParser_Comment` + (or `PHPParser_Comment_Doc` for doc comments). + + The methods `getLine()` and `setLine()` still exist and function as before, but internally + operator on the `startLine` attribute. + + `getDocComment()` also continues to exist. It returns the last comment in the `comments` + attribute if it is a doc comment, otherwise `null`. As `getDocComment()` now returns a + comment object (which can be modified using `->setText()`) the `setDocComment()` method was + removed. Comment objects implement a `__toString()` method, so `getDocComment()` should + continue to work properly with old code. + +* [BC] Use inject-once approach for lexer: + + Now the lexer is injected only once when creating the parser. Instead of + + $parser = new PHPParser_Parser; + $parser->parse(new PHPParser_Lexer($code)); + $parser->parse(new PHPParser_Lexer($code2)); + + you write: + + $parser = new PHPParser_Parser(new PHPParser_Lexer); + $parser->parse($code); + $parser->parse($code2); + +* Fix `NameResolver` visitor to also resolve class names in `catch` blocks. + +Version 0.9.1 (24.04.2012) +-------------------------- + +* Add ability to add attributes to nodes: + + It is now possible to add attributes to a node using `$node->setAttribute('name', 'value')` and to retrieve them using + `$node->getAttribute('name' [, 'default'])`. Additionally the existance of an attribute can be checked with + `$node->hasAttribute('name')` and all attributes can be returned using `$node->getAttributes()`. + +* Add code generation features: Builders and templates. + + For more infos, see the [code generation documentation][1]. + +* [BC] Don't traverse nodes merged by another visitor: + + If a NodeVisitor returns an array of nodes to merge, these will no longer be traversed by all other visitors. This + behavior only caused problems. + +* Fix line numbers for some list structures. +* Fix XML unserialization of empty nodes. +* Fix parsing of integers that overflow into floats. +* Fix emulation of NOWDOC and binary floats. + +Version 0.9.0 (05.01.2012) +-------------------------- + +First version. + + [1]: https://github.com/nikic/PHP-Parser/blob/master/doc/3_Code_generation.markdown \ No newline at end of file diff --git a/vendor/nikic/php-parser/LICENSE b/vendor/nikic/php-parser/LICENSE new file mode 100644 index 0000000..443210b --- /dev/null +++ b/vendor/nikic/php-parser/LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2011 by Nikita Popov. + +Some rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * The names of the contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vendor/nikic/php-parser/README.md b/vendor/nikic/php-parser/README.md new file mode 100644 index 0000000..8711eab --- /dev/null +++ b/vendor/nikic/php-parser/README.md @@ -0,0 +1,78 @@ +PHP Parser +========== + +This is a PHP 5.5 (and older) parser written in PHP. It's purpose is to simplify static code analysis and +manipulation. + +Documentation can be found in the [`doc/`][1] directory. + +***Note: This project is experimental, so the API is subject to change.*** + +In a Nutshell +------------- + +Basically, the parser does nothing more than turn some PHP code into an abstract syntax tree. ("nothing +more" is kind of sarcastic here as PHP has a ... uhm, let's just say "not nice" ... grammar, which makes +parsing PHP very hard.) + +For example, if you stick this code in the parser: + +```php +=5.2" + }, + "autoload": { + "psr-0": { "PHPParser": "lib/" } + }, + "extra": { + "branch-alias": { + "dev-master": "0.9-dev" + } + } +} diff --git a/vendor/nikic/php-parser/doc/0_Introduction.markdown b/vendor/nikic/php-parser/doc/0_Introduction.markdown new file mode 100644 index 0000000..d4b0b7b --- /dev/null +++ b/vendor/nikic/php-parser/doc/0_Introduction.markdown @@ -0,0 +1,81 @@ +Introduction +============ + +This project is a PHP 5.5 (and older) parser **written in PHP itself**. + +What is this for? +----------------- + +A parser is useful for [static analysis][0] and manipulation of code and basically any other +application dealing with code programmatically. A parser constructs an [Abstract Syntax Tree][1] +(AST) of the code and thus allows dealing with it in an abstract and robust way. + +There are other ways of dealing with source code. One that PHP supports natively is using the +token stream generated by [`token_get_all`][2]. The token stream is much more low level than +the AST and thus has different applications: It allows to also analyze the exact formatting of +a file. On the other hand the token stream is much harder to deal with for more complex analysis. +For example an AST abstracts away the fact that in PHP variables can be written as `$foo`, but also +as `$$bar`, `${'foobar'}` or even `${!${''}=barfoo()}`. You don't have to worry about recognizing +all the different syntaxes from a stream of tokens. + +Another questions is: Why would I want to have a PHP parser *written in PHP*? Well, PHP might not be +a language especially suited for fast parsing, but processing the AST is much easier in PHP than it +would be in other, faster languages like C. Furthermore the people most probably wanting to do +programmatic PHP code analysis are incidentally PHP developers, not C developers. + +What can it parse? +------------------ + +The parser uses a PHP 5.5 compliant grammar, which is backwards compatible with at least PHP 5.4, PHP 5.3 +and PHP 5.2 (and maybe older). + +As the parser is based on the tokens returned by `token_get_all` (which is only able to lex the PHP +version it runs on), additionally a wrapper for emulating new tokens from 5.3, 5.4 and 5.5 is provided. This +allows to parse PHP 5.5 source code running on PHP 5.2, for example. This emulation is very hacky and not +yet perfect, but it should work well on any sane code. + +What output does it produce? +---------------------------- + +The parser produces an [Abstract Syntax Tree][1] (AST) also known as a node tree. How this looks like +can best be seen in an example. The program `parse($code); +} catch (PHPParser_Error $e) { + echo 'Parse Error: ', $e->getMessage(); +} +``` + +The `parse` method will return an array of statement nodes (`$stmts`). + +### Emulative lexer + +Instead of `PHPParser_Lexer` one can also use `PHPParser_Lexer_Emulative`. This class will emulate tokens +of newer PHP versions and as such allow parsing PHP 5.5 on PHP 5.2, for example. So if you want to parse +PHP code of newer versions than the one you are running, you should use the emulative lexer. + +Node tree +--------- + +If you use the above code with `$code = "subNodeName`. The `Stmt_Echo` node has only one subnode `exprs`. So in order to access it +in the above example you would write `$stmts[0]->exprs`. If you wanted to access name of the function +call, you would write `$stmts[0]->exprs[1]->name`. + +All nodes also define a `getType()` method that returns the node type (the type is the class name +without the `PHPParser_Node_` prefix). + +It is possible to associate custom metadata with a node using the `setAttribute()` method. This data +can then be retrieved using `hasAttribute()`, `getAttribute()` and `getAttributes()`. + +By default the lexer adds the `startLine`, `endLine` and `comments` attributes. `comments` is an array +of `PHPParser_Comment[_Doc]` instances. + +The start line can also be accessed using `getLine()`/`setLine()` (instead of `getAttribute('startLine')`). +The last doc comment from the `comments` attribute can be obtained using `getDocComment()`. + +Pretty printer +-------------- + +The pretty printer component compiles the AST back to PHP code. As the parser does not retain formatting +information the formatting is done using a specified scheme. Currently there is only one scheme available, +namely `PHPParser_PrettyPrinter_Default`. + +```php +parse($code); + + // change + $stmts[0] // the echo statement + ->exprs // sub expressions + [0] // the first of them (the string node) + ->value // it's value, i.e. 'Hi ' + = 'Hallo '; // change to 'Hallo ' + + // pretty print + $code = 'prettyPrint($stmts); + + echo $code; +} catch (PHPParser_Error $e) { + echo 'Parse Error: ', $e->getMessage(); +} +``` + +The above code will output: + + parse`, then changed and then +again converted to code using `PHPParser_PrettyPrinter_Default->prettyPrint`. + +The `prettyPrint` method pretty prints a statements array. It is also possible to pretty print only a +single expression using `prettyPrintExpr`. + +Node traversation +----------------- + +The above pretty printing example used the fact that the source code was known and thus it was easy to +write code that accesses a certain part of a node tree and changes it. Normally this is not the case. +Usually you want to change / analyze code in a generic way, where you don't know how the node tree is +going to look like. + +For this purpose the parser provides a component for traversing and visiting the node tree. The basic +structure of a program using this `PHPParser_NodeTraverser` looks like this: + +```php +addVisitor(new MyNodeVisitor); + +try { + // parse + $stmts = $parser->parse($code); + + // traverse + $stmts = $traverser->traverse($stmts); + + // pretty print + $code = 'prettyPrint($stmts); + + echo $code; +} catch (PHPParser_Error $e) { + echo 'Parse Error: ', $e->getMessage(); +} +``` + +A same node visitor for this code might look like this: + +```php +value = 'foo'; + } + } +} +``` + +The above node visitor would change all string literals in the program to `'foo'`. + +All visitors must implement the `PHPParser_NodeVisitor` interface, which defined the following four +methods: + + public function beforeTraverse(array $nodes); + public function enterNode(PHPParser_Node $node); + public function leaveNode(PHPParser_Node $node); + public function afterTraverse(array $nodes); + +The `beforeTraverse` method is called once before the traversal begins and is passed the nodes the +traverser was called with. This method can be used for resetting values before traversation or +preparing the tree for traversal. + +The `afterTraverse` method is similar to the `beforeTraverse` method, with the only difference that +it is called once after the traversal. + +The `enterNode` and `leaveNode` methods are called on every node, the former when it is entered, +i.e. before its subnodes are traversed, the latter when it is left. + +All four methods can either return the changed node or not return at all (i.e. `null`) in which +case the current node is not changed. The `leaveNode` method can furthermore return two special +values: If `false` is returned the current node will be removed from the parent array. If an `array` +is returned the current node will be merged into the parent array at the offset of the current node. +I.e. if in `array(A, B, C)` the node `B` should be replaced with `array(X, Y, Z)` the result will be +`array(A, X, Y, Z, C)`. + +Instead of manually implementing the `NodeVisitor` interface you can also extend the `NodeVisitorAbstract` +class, which will define empty default implementations for all the above methods. + +The NameResolver node visitor +----------------------------- + +One visitor is already bundled with the package: `PHPParser_NodeVisitor_NameResolver`. This visitor +helps you work with namespaced code by trying to resolve most names to fully qualified ones. + +For example, consider the following code: + + use A as B; + new B\C(); + +In order to know that `B\C` really is `A\C` you would need to track aliases and namespaces yourself. +The `NameResolver` takes care of that and resolves names as far as possible. + +After running it most names will be fully qualified. The only names that will stay unqualified are +unqualified function and constant names. These are resolved at runtime and thus the visitor can't +know which function they are referring to. In most cases this is a non-issue as the global functions +are meant. + +Also the `NameResolver` adds a `namespacedName` subnode to class, function and constant declarations +that contains the namespaced name instead of only the shortname that is available via `name`. + +Example: Converting namespaced code to pseudo namespaces +-------------------------------------------------------- + +A small example to understand the concept: We want to convert namespaced code to pseudo namespaces +so it works on 5.2, i.e. names like `A\\B` should be converted to `A_B`. Note that such conversions +are fairly complicated if you take PHP's dynamic features into account, so our conversion will +assume that no dynamic features are used. + +We start off with the following base code: + +```php +addVisitor(new PHPParser_NodeVisitor_NameResolver); // we will need resolved names +$traverser->addVisitor(new NodeVisitor_NamespaceConverter); // our own node visitor + +// iterate over all .php files in the directory +$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(IN_DIR)); +$files = new RegexIterator($files, '/\.php$/'); + +foreach ($files as $file) { + try { + // read the file that should be converted + $code = file_get_contents($file); + + // parse + $stmts = $parser->parse($code); + + // traverse + $stmts = $traverser->traverse($stmts); + + // pretty print + $code = 'prettyPrint($stmts); + + // write the converted file to the target directory + file_put_contents( + substr_replace($file->getPathname(), OUT_DIR, 0, strlen(IN_DIR)), + $code + ); + } catch (PHPParser_Error $e) { + echo 'Parse Error: ', $e->getMessage(); + } +} +``` + +Now lets start with the main code, the `NodeVisitor_NamespaceConverter`. One thing it needs to do +is convert `A\\B` style names to `A_B` style ones. + +```php +toString('_')); + } + } +} +``` + +The above code profits from the fact that the `NameResolver` already resolved all names as far as +possible, so we don't need to do that. All the need to create a string with the name parts separated +by underscores instead of backslashes. This is what `$node->toString('_')` does. (If you want to +create a name with backslashes either write `$node->toString()` or `(string) $node`.) Then we create +a new name from the string and return it. Returning a new node replaces the old node. + +Another thing we need to do is change the class/function/const declarations. Currently they contain +only the shortname (i.e. the last part of the name), but they need to contain the complete class +name: + +```php +toString('_')); + } elseif ($node instanceof PHPParser_Node_Stmt_Class + || $node instanceof PHPParser_Node_Stmt_Interface + || $node instanceof PHPParser_Node_Stmt_Function) { + $node->name = $node->namespacedName->toString('_'); + } elseif ($node instanceof PHPParser_Node_Stmt_Const) { + foreach ($node->consts as $const) { + $const->name = $const->namespacedName->toString('_'); + } + } + } +} +``` + +There is not much more to it than converting the namespaced name to string with `_` as separator. + +The last thing we need to do is remove the `namespace` and `use` statements: + +```php +toString('_')); + } elseif ($node instanceof PHPParser_Node_Stmt_Class + || $node instanceof PHPParser_Node_Stmt_Interface + || $node instanceof PHPParser_Node_Stmt_Function) { + $node->name = $node->namespacedName->toString('_'); + } elseif ($node instanceof PHPParser_Node_Stmt_Const) { + foreach ($node->consts as $const) { + $const->name = $const->namespacedName->toString('_'); + } + } elseif ($node instanceof PHPParser_Node_Stmt_Namespace) { + // returning an array merges is into the parent array + return $node->stmts; + } elseif ($node instanceof PHPParser_Node_Stmt_Use) { + // returning false removed the node altogether + return false; + } + } +} +``` + +That's all. \ No newline at end of file diff --git a/vendor/nikic/php-parser/doc/3_Other_node_tree_representations.markdown b/vendor/nikic/php-parser/doc/3_Other_node_tree_representations.markdown new file mode 100644 index 0000000..e4fc080 --- /dev/null +++ b/vendor/nikic/php-parser/doc/3_Other_node_tree_representations.markdown @@ -0,0 +1,201 @@ +Other node tree representations +=============================== + +It is possible to convert the AST in several textual representations, which serve different uses. + +Simple serialization +-------------------- + +It is possible to serialize the node tree using `serialize()` and also unserialize it using +`unserialize()`. The output is not human readable and not easily processable from anything +but PHP, but it is compact and generates fast. The main application thus is in caching. + +Human readable dumping +---------------------- + +Furthermore it is possible to dump nodes into a human readable form using the `dump` method of +`PHPParser_NodeDumper`. This can be used for debugging. + +```php +parse($code); + + echo '
    ' . htmlspecialchars($nodeDumper->dump($stmts)) . '
    '; +} catch (PHPParser_Error $e) { + echo 'Parse Error: ', $e->getMessage(); +} +``` + +The above output will have an output looking roughly like this: + +``` +array( + 0: Stmt_Function( + byRef: false + params: array( + 0: Param( + name: msg + default: null + type: null + byRef: false + ) + ) + stmts: array( + 0: Stmt_Echo( + exprs: array( + 0: Expr_Variable( + name: msg + ) + 1: Scalar_String( + value: + + ) + ) + ) + ) + name: printLine + ) + 1: Expr_FuncCall( + name: Name( + parts: array( + 0: printLine + ) + ) + args: array( + 0: Arg( + value: Scalar_String( + value: Hallo World!!! + ) + byRef: false + ) + ) + ) +) +``` + +Serialization to XML +-------------------- + +It is also possible to serialize the node tree to XML using `PHPParser_Serializer_XML->serialize()` +and to unserialize it using `PHPParser_Unserializer_XML->unserialize()`. This is useful for +interfacing with other languages and applications or for doing transformation using XSLT. + +```php +parse($code); + + echo '
    ' . htmlspecialchars($serializer->serialize($stmts)) . '
    '; +} catch (PHPParser_Error $e) { + echo 'Parse Error: ', $e->getMessage(); +} +``` + +Produces: + +```xml + + + + + + + + + + + + msg + + + + + + + + + + + + + + + + + + + + + msg + + + + + + + + + + + + + + + printLine + + + + + + + + printLine + + + + + + + + + + + Hallo World!!! + + + + + + + + + + + + +``` \ No newline at end of file diff --git a/vendor/nikic/php-parser/doc/4_Code_generation.markdown b/vendor/nikic/php-parser/doc/4_Code_generation.markdown new file mode 100644 index 0000000..9541573 --- /dev/null +++ b/vendor/nikic/php-parser/doc/4_Code_generation.markdown @@ -0,0 +1,265 @@ +Code generation +=============== + +It is also possible to generate code using the parser, by first creating an Abstract Syntax Tree and then using the +pretty printer to convert it to PHP code. To simplify code generation, the project comes with a set of builders for +common structures as well as simple templating support. Both features are described in the following: + +Builders +-------- + +The project provides builders for classes, interfaces, methods, functions, parameters and properties, which +allow creating node trees with a fluid interface, instead of instantiating all nodes manually. + +Here is an example: + +```php +class('SomeClass') + ->extend('SomeOtherClass') + ->implement('A\Few', 'Interfaces') + ->makeAbstract() // ->makeFinal() + + ->addStmt($factory->method('someMethod') + ->makeAbstract() // ->makeFinal() + ->addParam($factory->param('someParam')->setTypeHint('SomeClass')) + ) + + ->addStmt($factory->method('anotherMethod') + ->makeProtected() // ->makePublic() [default], ->makePrivate() + ->addParam($factory->param('someParam')->setDefault('test')) + // it is possible to add manually created nodes + ->addStmt(new PHPParser_Node_Expr_Print(new PHPParser_Node_Expr_Variable('someParam'))) + ) + + // properties will be correctly reordered above the methods + ->addStmt($factory->property('someProperty')->makeProtected()) + ->addStmt($factory->property('anotherProperty')->makePrivate()->setDefault(array(1, 2, 3))) + + ->getNode() +; + +$stmts = array($node); +echo $prettyPrinter->prettyPrint($stmts); +``` + +This will produce the following output with the default pretty printer: + +```php +__name__; + } + + /** + * Sets the __name__. + * + * @param __type__ $__name__ The new __name__ + */ + public function set__Name__($__name__) { + $this->__name__ = $__name__; + } +} +``` + +Using this template we can easily create a class with multiple properties and their respective getters and setters: + +```php + 'title', 'type' => 'string'], + ['name' => 'body', 'type' => 'string'], + ['name' => 'author', 'type' => 'User'], + ['name' => 'timestamp', 'type' => 'DateTime'], +]; + +$class = $factory->class('BlogPost')->implement('Post'); + +foreach ($properties as $propertyPlaceholders) { + $stmts = $template->getStmts($propertyPlaceholders); + + $class->addStmts( + // $stmts contains all statements from the template. So [0] fetches the class statement + // and ->stmts retrieves the methods. + $stmts[0]->stmts + ); +} + +echo $prettyPrinter->prettyPrint(array($class->getNode())); +``` + +The result would look roughly like this: + +```php +title; + } + + /** + * Sets the title. + * + * @param string $title The new title + */ + public function setTitle($title) + { + $this->title = $title; + } + + /** + * Gets the body. + * + * @return string The body + */ + public function getBody() + { + return $this->body; + } + + /** + * Sets the body. + * + * @param string $body The new body + */ + public function setBody($body) + { + $this->body = $body; + } + + /** + * Gets the author. + * + * @return User The author + */ + public function getAuthor() + { + return $this->author; + } + + /** + * Sets the author. + * + * @param User $author The new author + */ + public function setAuthor($author) + { + $this->author = $author; + } + + /** + * Gets the timestamp. + * + * @return DateTime The timestamp + */ + public function getTimestamp() + { + return $this->timestamp; + } + + /** + * Sets the timestamp. + * + * @param DateTime $timestamp The new timestamp + */ + public function setTimestamp($timestamp) + { + $this->timestamp = $timestamp; + } +} +``` + +When using multiple templates it is easier to manage them on the filesystem. They can be loaded using the +`TemplateLoader`: + +```php +load('GetterSetter'); + +// loads ./templates/Collection.php +$collectionTemplate = $loader->load('Collection'); + +// The use of a suffix is optional. The following code for example is equivalent: +$loader = new PHPParser_TemplateLoader($parser, './templates'); + +// loads ./templates/GetterSetter.php +$getterSetterTemplate = $loader->load('GetterSetter.php'); + +// loads ./templates/Collection.php +$collectionTemplate = $loader->load('Collection.php'); +``` \ No newline at end of file diff --git a/vendor/nikic/php-parser/doc/component/Lexer.markdown b/vendor/nikic/php-parser/doc/component/Lexer.markdown new file mode 100644 index 0000000..cfdea4b --- /dev/null +++ b/vendor/nikic/php-parser/doc/component/Lexer.markdown @@ -0,0 +1,114 @@ +Lexer component documentation +============================= + +The lexer is responsible for providing tokens to the parser. The project comes with two lexers: `PHPParser_Lexer` and +`PHPParser_Lexer_Emulative`. The latter is an extension of the former, which adds the ability to emulate tokens of +newer PHP versions and thus allows parsing of new code on older versions. + +A lexer has to define the following public interface: + + startLexing($code); + getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null); + handleHaltCompiler(); + +startLexing +----------- + +The `startLexing` method is invoked when the `parse()` method of the parser is called. It's argument will be whatever +was passed to the `parse()` method. + +Even though `startLexing` is meant to accept a source code string, you could for example overwrite it to accept a file: + +```php +parse('someFile.php')); +var_dump($parser->parse('someOtherFile.php')); +``` + +getNextToken +------------ + +`getNextToken` returns the ID of the next token and sets some additional information in the three variables which it +accepts by-ref. If no more tokens are available it has to return `0`, which is the ID of the `EOF` token. + +The first by-ref variable `$value` should contain the textual content of the token. It is what will be available as `$1` +etc in the parser. + +The other two by-ref variables `$startAttributes` and `$endAttributes` define which attributes will eventually be +assigned to the generated nodes: The parser will take the `$startAttributes` from the first token which is part of the +node and the `$endAttributes` from the last token that is part of the node. + +E.g. if the tokens `T_FUNCTION T_STRING ... '{' ... '}'` constitute a node, then the `$startAttributes` from the +`T_FUNCTION` token will be taken and the `$endAttributes` from the `'}'` token. + +By default the lexer creates the attributes `startLine`, `comments` (both part of `$startAttributes`) and `endLine` +(part of `$endAttributes`). + +If you don't want all these attributes to be added (to reduce memory usage of the AST) you can simply remove them by +overriding the method: + +```php +fileName = $fileName; + parent::startLexing(file_get_contents($fileName)); + } + + public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) { + $tokenId = parent::getNextToken($value, $startAttributes, $endAttributes); + + // we could use either $startAttributes or $endAttributes here, because the fileName is always the same + // (regardless of whether it is the start or end token). We choose $endAttributes, because it is slightly + // more efficient (as the parser has to keep a stack for the $startAttributes). + $endAttributes['fileName'] = $fileName; + + return $tokenId; + } +} +``` + +handleHaltCompiler +------------------ + +The method is invoked whenever a `T_HALT_COMPILER` token is encountered. It has to return the remaining string after the +construct (not including `();`). \ No newline at end of file diff --git a/vendor/nikic/php-parser/grammar/README.md b/vendor/nikic/php-parser/grammar/README.md new file mode 100644 index 0000000..b39b13f --- /dev/null +++ b/vendor/nikic/php-parser/grammar/README.md @@ -0,0 +1,29 @@ +What do all those files mean? +============================= + + * `zend_language_parser.phpy`: PHP grammer written in a pseudo language + * `analyze.php`: Analyzes the `.phpy`-grammer and outputs some info about it + * `rebuildParser.php`: Preprocesses the `.phpy`-grammar and builds the parser using `kmyacc` + * `kmyacc.php.parser`: A `kmyacc` parser prototype file for PHP + +.phpy pseudo language +===================== + +The `.phpy` file is a normal grammer in `kmyacc` (`yacc`) style, with some transformations +applied to it: + + * Nodes are created using the syntax `Name[..., ...]`. This is transformed into + `new PHPParser_Node_Name(..., ..., $attributes)` + * `Name::abc` is transformed to `PHPParser_Node_Name::abc` + * Some function-like constructs are resolved (see `rebuildParser.php` for a list) + * Associative arrays are written as `[key: value, ...]`, which is transformed to + `array('key' => value, ...)` + +Building the parser +=================== + +In order to rebuild the parser, you need [moriyoshi's fork of kmyacc](https://github.com/moriyoshi/kmyacc-forked). +After you compiled/installed it, run the `rebuildParser.php` script. + +By default only the `Parser.php` is built. If you want to additionally build `Parser/Debug.php` and `y.output` run the +script with `--debug`. If you want to retain the preprocessed grammar pass `--keep-tmp-grammar`. \ No newline at end of file diff --git a/vendor/nikic/php-parser/grammar/analyze.php b/vendor/nikic/php-parser/grammar/analyze.php new file mode 100644 index 0000000..50101df --- /dev/null +++ b/vendor/nikic/php-parser/grammar/analyze.php @@ -0,0 +1,96 @@ +\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\') + (?"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+") + (?(?&singleQuotedString)|(?&doubleQuotedString)) + (?/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/) + (?\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+}) +)'; + +const RULE_BLOCK = '(?[a-z_]++):(?[^\'"/{};]*+(?:(?:(?&string)|(?&comment)|(?&code)|/|})[^\'"/{};]*+)*+);'; + +$usedTerminals = array_flip(array( + 'T_VARIABLE', 'T_STRING', 'T_INLINE_HTML', 'T_ENCAPSED_AND_WHITESPACE', + 'T_LNUMBER', 'T_DNUMBER', 'T_CONSTANT_ENCAPSED_STRING', 'T_STRING_VARNAME', 'T_NUM_STRING' +)); +$unusedNonterminals = array_flip(array( + 'case_separator', 'optional_comma' +)); + +function regex($regex) { + return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~'; +} + +function magicSplit($regex, $string) { + $pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string); + + foreach ($pieces as &$piece) { + $piece = trim($piece); + } + + return array_filter($pieces); +} + +echo '
    ';
    +
    +////////////////////
    +////////////////////
    +////////////////////
    +
    +list($defs, $ruleBlocks) = magicSplit('%%', file_get_contents(GRAMMAR_FILE));
    +
    +if ('' !== trim(preg_replace(regex(RULE_BLOCK), '', $ruleBlocks))) {
    +    die('Not all rule blocks were properly recognized!');
    +}
    +
    +preg_match_all(regex(RULE_BLOCK), $ruleBlocks, $ruleBlocksMatches, PREG_SET_ORDER);
    +foreach ($ruleBlocksMatches as $match) {
    +    $ruleBlockName = $match['name'];
    +    $rules = magicSplit('\|', $match['rules']);
    +
    +    foreach ($rules as &$rule) {
    +        $parts = magicSplit('\s+', $rule);
    +        $usedParts = array();
    +
    +        foreach ($parts as $part) {
    +            if ('{' === $part[0]) {
    +                preg_match_all('~\$([0-9]+)~', $part, $backReferencesMatches, PREG_SET_ORDER);
    +                foreach ($backReferencesMatches as $match) {
    +                    $usedParts[$match[1]] = true;
    +                }
    +            }
    +        }
    +
    +        $i = 1;
    +        foreach ($parts as &$part) {
    +            if ('/' === $part[0]) {
    +                continue;
    +            }
    +
    +            if (isset($usedParts[$i])) {
    +                if ('\'' === $part[0] || '{' === $part[0]
    +                    || (ctype_upper($part[0]) && !isset($usedTerminals[$part]))
    +                    || (ctype_lower($part[0]) && isset($unusedNonterminals[$part]))
    +                ) {
    +                    $part = '' . $part . '';
    +                } else {
    +                    $part = '' . $part . '';
    +                }
    +            } elseif ((ctype_upper($part[0]) && isset($usedTerminals[$part]))
    +                      || (ctype_lower($part[0]) && !isset($unusedNonterminals[$part]))
    +
    +            ) {
    +                $part = '' . $part . '';
    +            }
    +
    +            ++$i;
    +        }
    +
    +        $rule = implode(' ', $parts);
    +    }
    +
    +    echo $ruleBlockName, ':', "\n", '      ', implode("\n" . '    | ', $rules), "\n", ';', "\n\n";
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/grammar/kmyacc.php.parser b/vendor/nikic/php-parser/grammar/kmyacc.php.parser
    new file mode 100644
    index 0000000..757c752
    --- /dev/null
    +++ b/vendor/nikic/php-parser/grammar/kmyacc.php.parser
    @@ -0,0 +1,361 @@
    +yyval
    +#semval($,%t) $this->yyval
    +#semval(%n) $this->yyastk[$this->stackPos-(%l-%n)]
    +#semval(%n,%t) $this->yyastk[$this->stackPos-(%l-%n)]
    +#include;
    +
    +/* This is an automatically GENERATED file, which should not be manually edited.
    + * Instead edit one of the following:
    + *  * the grammar file grammar/zend_language_parser.phpy
    + *  * the parser skeleton grammar/kymacc.php.parser
    + *  * the preprocessing script grammar/rebuildParser.php
    + *
    + * The skeleton for this parser was written by Moriyoshi Koizumi and is based on
    + * the work by Masato Bito and is in the PUBLIC DOMAIN.
    + */
    +#if -t
    +class #(-p)_Debug extends #(-p)
    +#endif
    +#ifnot -t
    +class #(-p)
    +#endif
    +{
    +#ifnot -t
    +    const TOKEN_NONE    = -1;
    +    const TOKEN_INVALID = #(YYBADCH);
    +
    +    const TOKEN_MAP_SIZE = #(YYMAXLEX);
    +
    +    const YYLAST       = #(YYLAST);
    +    const YY2TBLSTATE  = #(YY2TBLSTATE);
    +    const YYGLAST      = #(YYGLAST);
    +    const YYNLSTATES   = #(YYNLSTATES);
    +    const YYUNEXPECTED = #(YYUNEXPECTED);
    +    const YYDEFAULT    = #(YYDEFAULT);
    +
    +    // {{{ Tokens
    +#tokenval
    +    const %s = %n;
    +#endtokenval
    +    // }}}
    +
    +    /* @var array Map of token ids to their respective names */
    +    protected static $terminals = array(
    +        #listvar terminals
    +        , "???"
    +    );
    +
    +    /* @var array Map which translates lexer tokens to internal tokens */
    +    protected static $translate = array(
    +        #listvar yytranslate
    +    );
    +
    +    protected static $yyaction = array(
    +        #listvar yyaction
    +    );
    +
    +    protected static $yycheck = array(
    +        #listvar yycheck
    +    );
    +
    +    protected static $yybase = array(
    +        #listvar yybase
    +    );
    +
    +    protected static $yydefault = array(
    +        #listvar yydefault
    +    );
    +
    +    protected static $yygoto = array(
    +        #listvar yygoto
    +    );
    +
    +    protected static $yygcheck = array(
    +        #listvar yygcheck
    +    );
    +
    +    protected static $yygbase = array(
    +        #listvar yygbase
    +    );
    +
    +    protected static $yygdefault = array(
    +        #listvar yygdefault
    +    );
    +
    +    protected static $yylhs = array(
    +        #listvar yylhs
    +    );
    +
    +    protected static $yylen = array(
    +        #listvar yylen
    +    );
    +
    +    protected $yyval;
    +    protected $yyastk;
    +    protected $stackPos;
    +    protected $lexer;
    +
    +    /**
    +     * Creates a parser instance.
    +     *
    +     * @param PHPParser_Lexer $lexer A lexer
    +     */
    +    public function __construct(PHPParser_Lexer $lexer) {
    +        $this->lexer = $lexer;
    +    }
    +#endif
    +#if -t
    +    protected static $yyproduction = array(
    +        #production-strings;
    +    );
    +
    +    protected function yyprintln($msg) {
    +        echo $msg, "\n";
    +    }
    +
    +    protected function YYTRACE_NEWSTATE($state, $tokenId) {
    +        $this->yyprintln(
    +            '% State ' . $state
    +          . ', Lookahead ' . ($tokenId == self::TOKEN_NONE ? '--none--' : self::$terminals[$tokenId])
    +        );
    +    }
    +
    +    protected function YYTRACE_READ($tokenId) {
    +        $this->yyprintln('% Reading ' . self::$terminals[$tokenId]);
    +    }
    +
    +    protected function YYTRACE_SHIFT($tokenId) {
    +        $this->yyprintln('% Shift ' . self::$terminals[$tokenId]);
    +    }
    +
    +    protected function YYTRACE_ACCEPT() {
    +        $this->yyprintln('% Accepted.');
    +    }
    +
    +    protected function YYTRACE_REDUCE($n) {
    +        $this->yyprintln('% Reduce by (' . $n . ') ' . self::$yyproduction[$n]);
    +    }
    +
    +    protected function YYTRACE_POP($state) {
    +        $this->yyprintln('% Recovering, uncovers state ' . $state);
    +    }
    +
    +    protected function YYTRACE_DISCARD($tokenId) {
    +        $this->yyprintln('% Discard ' . self::$terminals[$tokenId]);
    +    }
    +#endif
    +
    +    /**
    +#ifnot -t
    +     * Parses PHP code into a node tree.
    +#endif
    +#if -t
    +     * Parses PHP code into a node tree and prints out debugging information.
    +#endif
    +     *
    +     * @param string $code The source code to parse
    +     *
    +     * @return PHPParser_Node[] Array of statements
    +     */
    +    public function parse($code) {
    +        $this->lexer->startLexing($code);
    +
    +        // We start off with no lookahead-token
    +        $tokenId = self::TOKEN_NONE;
    +
    +        // The attributes for a node are taken from the first and last token of the node.
    +        // From the first token only the startAttributes are taken and from the last only
    +        // the endAttributes. Both are merged using the array union operator (+).
    +        $startAttributes = array('startLine' => 1);
    +        $endAttributes   = array();
    +
    +        // In order to figure out the attributes for the starting token, we have to keep
    +        // them in a stack
    +        $attributeStack = array($startAttributes);
    +
    +        // Start off in the initial state and keep a stack of previous states
    +        $state = 0;
    +        $stateStack = array($state);
    +
    +        // AST stack (?)
    +        $this->yyastk = array();
    +
    +        // Current position in the stack(s)
    +        $this->stackPos = 0;
    +
    +        for (;;) {
    +#if -t
    +            $this->YYTRACE_NEWSTATE($state, $tokenId);
    +
    +#endif
    +            if (self::$yybase[$state] == 0) {
    +                $yyn = self::$yydefault[$state];
    +            } else {
    +                if ($tokenId === self::TOKEN_NONE) {
    +                    // Fetch the next token id from the lexer and fetch additional info by-ref.
    +                    // The end attributes are fetched into a temporary variable and only set once the token is really
    +                    // shifted (not during read). Otherwise you would sometimes get off-by-one errors, when a rule is
    +                    // reduced after a token was read but not yet shifted.
    +                    $origTokenId = $this->lexer->getNextToken($tokenValue, $startAttributes, $nextEndAttributes);
    +
    +                    // map the lexer token id to the internally used token id's
    +                    $tokenId = $origTokenId >= 0 && $origTokenId < self::TOKEN_MAP_SIZE
    +                        ? self::$translate[$origTokenId]
    +                        : self::TOKEN_INVALID;
    +
    +                    if ($tokenId === self::TOKEN_INVALID) {
    +                        throw new RangeException(sprintf(
    +                            'The lexer returned an invalid token (id=%d, value=%s)',
    +                            $origTokenId, $tokenValue
    +                        ));
    +                    }
    +
    +                    $attributeStack[$this->stackPos] = $startAttributes;
    +#if -t
    +
    +                    $this->YYTRACE_READ($tokenId);
    +#endif
    +                }
    +
    +                if ((($yyn = self::$yybase[$state] + $tokenId) >= 0
    +                     && $yyn < self::YYLAST && self::$yycheck[$yyn] == $tokenId
    +                     || ($state < self::YY2TBLSTATE
    +                        && ($yyn = self::$yybase[$state + self::YYNLSTATES] + $tokenId) >= 0
    +                        && $yyn < self::YYLAST
    +                        && self::$yycheck[$yyn] == $tokenId))
    +                    && ($yyn = self::$yyaction[$yyn]) != self::YYDEFAULT) {
    +                    /*
    +                     * >= YYNLSTATE: shift and reduce
    +                     * > 0: shift
    +                     * = 0: accept
    +                     * < 0: reduce
    +                     * = -YYUNEXPECTED: error
    +                     */
    +                    if ($yyn > 0) {
    +                        /* shift */
    +#if -t
    +                        $this->YYTRACE_SHIFT($tokenId);
    +
    +#endif
    +                        ++$this->stackPos;
    +
    +                        $stateStack[$this->stackPos]     = $state = $yyn;
    +                        $this->yyastk[$this->stackPos]   = $tokenValue;
    +                        $attributeStack[$this->stackPos] = $startAttributes;
    +                        $endAttributes = $nextEndAttributes;
    +                        $tokenId = self::TOKEN_NONE;
    +
    +                        if ($yyn < self::YYNLSTATES)
    +                            continue;
    +
    +                        /* $yyn >= YYNLSTATES means shift-and-reduce */
    +                        $yyn -= self::YYNLSTATES;
    +                    } else {
    +                        $yyn = -$yyn;
    +                    }
    +                } else {
    +                    $yyn = self::$yydefault[$state];
    +                }
    +            }
    +
    +            for (;;) {
    +                /* reduce/error */
    +                if ($yyn == 0) {
    +                    /* accept */
    +#if -t
    +                    $this->YYTRACE_ACCEPT();
    +#endif
    +                    return $this->yyval;
    +                } elseif ($yyn != self::YYUNEXPECTED) {
    +                    /* reduce */
    +#if -t
    +                    $this->YYTRACE_REDUCE($yyn);
    +#endif
    +                    try {
    +                        $this->{'yyn' . $yyn}(
    +                            $attributeStack[$this->stackPos - self::$yylen[$yyn]]
    +                            + $endAttributes
    +                        );
    +                    } catch (PHPParser_Error $e) {
    +                        if (-1 === $e->getRawLine()) {
    +                            $e->setRawLine($startAttributes['startLine']);
    +                        }
    +
    +                        throw $e;
    +                    }
    +
    +                    /* Goto - shift nonterminal */
    +                    $this->stackPos -= self::$yylen[$yyn];
    +                    $yyn = self::$yylhs[$yyn];
    +                    if (($yyp = self::$yygbase[$yyn] + $stateStack[$this->stackPos]) >= 0
    +                         && $yyp < self::YYGLAST
    +                         && self::$yygcheck[$yyp] == $yyn) {
    +                        $state = self::$yygoto[$yyp];
    +                    } else {
    +                        $state = self::$yygdefault[$yyn];
    +                    }
    +
    +                    ++$this->stackPos;
    +
    +                    $stateStack[$this->stackPos]     = $state;
    +                    $this->yyastk[$this->stackPos]   = $this->yyval;
    +                    $attributeStack[$this->stackPos] = $startAttributes;
    +                } else {
    +                    /* error */
    +                    $expected = array();
    +
    +                    $base = self::$yybase[$state];
    +                    for ($i = 0; $i < self::TOKEN_MAP_SIZE; ++$i) {
    +                        $n = $base + $i;
    +                        if ($n >= 0 && $n < self::YYLAST && self::$yycheck[$n] == $i
    +                         || $state < self::YY2TBLSTATE
    +                            && ($n = self::$yybase[$state + self::YYNLSTATES] + $i) >= 0
    +                            && $n < self::YYLAST && self::$yycheck[$n] == $i
    +                        ) {
    +                            if (self::$yyaction[$n] != self::YYUNEXPECTED) {
    +                                if (count($expected) == 4) {
    +                                    /* Too many expected tokens */
    +                                    $expected = array();
    +                                    break;
    +                                }
    +
    +                                $expected[] = self::$terminals[$i];
    +                            }
    +                        }
    +                    }
    +
    +                    $expectedString = '';
    +                    if ($expected) {
    +                        $expectedString = ', expecting ' . implode(' or ', $expected);
    +                    }
    +
    +                    throw new PHPParser_Error(
    +                        'Syntax error, unexpected ' . self::$terminals[$tokenId] . $expectedString,
    +                        $startAttributes['startLine']
    +                    );
    +                }
    +
    +                if ($state < self::YYNLSTATES)
    +                    break;
    +                /* >= YYNLSTATES means shift-and-reduce */
    +                $yyn = $state - self::YYNLSTATES;
    +            }
    +        }
    +    }
    +#ifnot -t
    +#reduce
    +
    +    protected function yyn%n($attributes) {
    +        %b
    +    }
    +#noact
    +
    +    protected function yyn%n() {
    +        $this->yyval = $this->yyastk[$this->stackPos];
    +    }
    +#endreduce
    +#endif
    +}
    +#tailcode;
    diff --git a/vendor/nikic/php-parser/grammar/rebuildParser.php b/vendor/nikic/php-parser/grammar/rebuildParser.php
    new file mode 100644
    index 0000000..0aa1f8b
    --- /dev/null
    +++ b/vendor/nikic/php-parser/grammar/rebuildParser.php
    @@ -0,0 +1,225 @@
    +\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\')
    +    (?"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+")
    +    (?(?&singleQuotedString)|(?&doubleQuotedString))
    +    (?/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/)
    +    (?\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+})
    +)';
    +
    +const PARAMS = '\[(?[^[\]]*+(?:\[(?¶ms)\][^[\]]*+)*+)\]';
    +const ARGS   = '\((?[^()]*+(?:\((?&args)\)[^()]*+)*+)\)';
    +
    +///////////////////
    +/// Main script ///
    +///////////////////
    +
    +echo 'Building temporary preproprocessed grammar file.', "\n";
    +
    +$grammarCode = file_get_contents($grammarFile);
    +
    +$grammarCode = resolveConstants($grammarCode);
    +$grammarCode = resolveNodes($grammarCode);
    +$grammarCode = resolveMacros($grammarCode);
    +$grammarCode = resolveArrays($grammarCode);
    +
    +file_put_contents($tmpGrammarFile, $grammarCode);
    +
    +echo "Building parser.\n";
    +$output = trim(shell_exec("$kmyacc -l -m $skeletonFile -p PHPParser_Parser $tmpGrammarFile 2>&1"));
    +echo "Output: \"$output\"\n";
    +
    +moveFileWithDirCheck($tmpResultFile, $parserResultFile);
    +
    +if ($optionDebug) {
    +    echo "Building debug parser.\n";
    +    $output = trim(shell_exec("$kmyacc -t -v -l -m $skeletonFile -p PHPParser_Parser $tmpGrammarFile 2>&1"));
    +    echo "Output: \"$output\"\n";
    +
    +    moveFileWithDirCheck($tmpResultFile, $debugParserResultFile);
    +}
    +
    +if (!$optionKeepTmpGrammar) {
    +    unlink($tmpGrammarFile);
    +}
    +
    +///////////////////////////////
    +/// Preprocessing functions ///
    +///////////////////////////////
    +
    +function resolveConstants($code) {
    +    return preg_replace('~[A-Z][a-zA-Z_]++::~', 'PHPParser_Node_$0', $code);
    +}
    +
    +function resolveNodes($code) {
    +    return preg_replace_callback(
    +        '~(?[A-Z][a-zA-Z_]++)\s*' . PARAMS . '~',
    +        function($matches) {
    +            // recurse
    +            $matches['params'] = resolveNodes($matches['params']);
    +
    +            $params = magicSplit(
    +                '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
    +                $matches['params']
    +            );
    +
    +            $paramCode = '';
    +            foreach ($params as $param) {
    +                $paramCode .= $param . ', ';
    +            }
    +
    +            return 'new PHPParser_Node_' . $matches['name'] . '(' . $paramCode . '$attributes)';
    +        },
    +        $code
    +    );
    +}
    +
    +function resolveMacros($code) {
    +    return preg_replace_callback(
    +        '~\b(?)(?!array\()(?[a-z][A-Za-z]++)' . ARGS . '~',
    +        function($matches) {
    +            // recurse
    +            $matches['args'] = resolveMacros($matches['args']);
    +
    +            $name = $matches['name'];
    +            $args = magicSplit(
    +                '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
    +                $matches['args']
    +            );
    +
    +            if ('error' == $name) {
    +                assertArgs(1, $args, $name);
    +
    +                return 'throw new PHPParser_Error(' . $args[0] . ')';
    +            }
    +
    +            if ('init' == $name) {
    +                return '$$ = array(' . implode(', ', $args) . ')';
    +            }
    +
    +            if ('push' == $name) {
    +                assertArgs(2, $args, $name);
    +
    +                return $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0];
    +            }
    +
    +            if ('pushNormalizing' == $name) {
    +                assertArgs(2, $args, $name);
    +
    +                return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); } else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }';
    +            }
    +
    +            if ('toArray' == $name) {
    +                assertArgs(1, $args, $name);
    +
    +                return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
    +            }
    +
    +            if ('parseVar' == $name) {
    +                assertArgs(1, $args, $name);
    +
    +                return 'substr(' . $args[0] . ', 1)';
    +            }
    +
    +            if ('parseEncapsed' == $name) {
    +                assertArgs(2, $args, $name);
    +
    +                return 'foreach (' . $args[0] . ' as &$s) { if (is_string($s)) { $s = PHPParser_Node_Scalar_String::parseEscapeSequences($s, ' . $args[1] . '); } }';
    +            }
    +
    +            if ('parseEncapsedDoc' == $name) {
    +                assertArgs(1, $args, $name);
    +
    +                return 'foreach (' . $args[0] . ' as &$s) { if (is_string($s)) { $s = PHPParser_Node_Scalar_String::parseEscapeSequences($s, null); } } $s = preg_replace(\'~(\r\n|\n|\r)$~\', \'\', $s); if (\'\' === $s) array_pop(' . $args[0] . ');';
    +            }
    +
    +            throw new Exception(sprintf('Unknown macro "%s"', $name));
    +        },
    +        $code
    +    );
    +}
    +
    +function assertArgs($num, $args, $name) {
    +    if ($num != count($args)) {
    +        die('Wrong argument count for ' . $name . '().');
    +    }
    +}
    +
    +function resolveArrays($code) {
    +    return preg_replace_callback(
    +        '~' . PARAMS . '~',
    +        function ($matches) {
    +            $elements = magicSplit(
    +                '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
    +                $matches['params']
    +            );
    +
    +            // don't convert [] to array, it might have different meaning
    +            if (empty($elements)) {
    +                return $matches[0];
    +            }
    +
    +            $elementCodes = array();
    +            foreach ($elements as $element) {
    +                // convert only arrays where all elements have keys
    +                if (false === strpos($element, ':')) {
    +                    return $matches[0];
    +                }
    +
    +                list($key, $value) = explode(':', $element, 2);
    +                $elementCodes[] = "'" . $key . "' =>" . $value;
    +            }
    +
    +            return 'array(' . implode(', ', $elementCodes) . ')';
    +        },
    +        $code
    +    );
    +}
    +
    +function moveFileWithDirCheck($fromPath, $toPath) {
    +    $dir = dirname($toPath);
    +    if (!is_dir($dir)) {
    +        mkdir($dir, 0777, true);
    +    }
    +    rename($fromPath, $toPath);
    +}
    +
    +//////////////////////////////
    +/// Regex helper functions ///
    +//////////////////////////////
    +
    +function regex($regex) {
    +    return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~';
    +}
    +
    +function magicSplit($regex, $string) {
    +    $pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string);
    +
    +    foreach ($pieces as &$piece) {
    +        $piece = trim($piece);
    +    }
    +
    +    return array_filter($pieces);
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/grammar/zend_language_parser.phpy b/vendor/nikic/php-parser/grammar/zend_language_parser.phpy
    new file mode 100644
    index 0000000..85d72b2
    --- /dev/null
    +++ b/vendor/nikic/php-parser/grammar/zend_language_parser.phpy
    @@ -0,0 +1,906 @@
    +%pure_parser
    +%expect 2
    +
    +%left T_INCLUDE T_INCLUDE_ONCE T_EVAL T_REQUIRE T_REQUIRE_ONCE
    +%left ','
    +%left T_LOGICAL_OR
    +%left T_LOGICAL_XOR
    +%left T_LOGICAL_AND
    +%right T_PRINT
    +%right T_YIELD
    +%left '=' T_PLUS_EQUAL T_MINUS_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL T_SL_EQUAL T_SR_EQUAL
    +%left '?' ':'
    +%left T_BOOLEAN_OR
    +%left T_BOOLEAN_AND
    +%left '|'
    +%left '^'
    +%left '&'
    +%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL
    +%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
    +%left T_SL T_SR
    +%left '+' '-' '.'
    +%left '*' '/' '%'
    +%right '!'
    +%nonassoc T_INSTANCEOF
    +%right '~' T_INC T_DEC T_INT_CAST T_DOUBLE_CAST T_STRING_CAST T_ARRAY_CAST T_OBJECT_CAST T_BOOL_CAST T_UNSET_CAST '@'
    +%right '['
    +%nonassoc T_NEW T_CLONE
    +%token T_EXIT
    +%token T_IF
    +%left T_ELSEIF
    +%left T_ELSE
    +%left T_ENDIF
    +%token T_LNUMBER
    +%token T_DNUMBER
    +%token T_STRING
    +%token T_STRING_VARNAME
    +%token T_VARIABLE
    +%token T_NUM_STRING
    +%token T_INLINE_HTML
    +%token T_CHARACTER
    +%token T_BAD_CHARACTER
    +%token T_ENCAPSED_AND_WHITESPACE
    +%token T_CONSTANT_ENCAPSED_STRING
    +%token T_ECHO
    +%token T_DO
    +%token T_WHILE
    +%token T_ENDWHILE
    +%token T_FOR
    +%token T_ENDFOR
    +%token T_FOREACH
    +%token T_ENDFOREACH
    +%token T_DECLARE
    +%token T_ENDDECLARE
    +%token T_AS
    +%token T_SWITCH
    +%token T_ENDSWITCH
    +%token T_CASE
    +%token T_DEFAULT
    +%token T_BREAK
    +%token T_CONTINUE
    +%token T_GOTO
    +%token T_FUNCTION
    +%token T_CONST
    +%token T_RETURN
    +%token T_TRY
    +%token T_CATCH
    +%token T_FINALLY
    +%token T_THROW
    +%token T_USE
    +%token T_INSTEADOF
    +%token T_GLOBAL
    +%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC
    +%token T_VAR
    +%token T_UNSET
    +%token T_ISSET
    +%token T_EMPTY
    +%token T_HALT_COMPILER
    +%token T_CLASS
    +%token T_TRAIT
    +%token T_INTERFACE
    +%token T_EXTENDS
    +%token T_IMPLEMENTS
    +%token T_OBJECT_OPERATOR
    +%token T_DOUBLE_ARROW
    +%token T_LIST
    +%token T_ARRAY
    +%token T_CALLABLE
    +%token T_CLASS_C
    +%token T_TRAIT_C
    +%token T_METHOD_C
    +%token T_FUNC_C
    +%token T_LINE
    +%token T_FILE
    +%token T_COMMENT
    +%token T_DOC_COMMENT
    +%token T_OPEN_TAG
    +%token T_OPEN_TAG_WITH_ECHO
    +%token T_CLOSE_TAG
    +%token T_WHITESPACE
    +%token T_START_HEREDOC
    +%token T_END_HEREDOC
    +%token T_DOLLAR_OPEN_CURLY_BRACES
    +%token T_CURLY_OPEN
    +%token T_PAAMAYIM_NEKUDOTAYIM
    +%token T_NAMESPACE
    +%token T_NS_C
    +%token T_DIR
    +%token T_NS_SEPARATOR
    +
    +%%
    +
    +start:
    +    top_statement_list                                      { $$ = Stmt_Namespace::postprocess($1); }
    +;
    +
    +top_statement_list:
    +      top_statement_list top_statement                      { pushNormalizing($1, $2); }
    +    | /* empty */                                           { init(); }
    +;
    +
    +namespace_name:
    +      T_STRING                                              { init($1); }
    +    | namespace_name T_NS_SEPARATOR T_STRING                { push($1, $3); }
    +;
    +
    +top_statement:
    +      statement                                             { $$ = $1; }
    +    | function_declaration_statement                        { $$ = $1; }
    +    | class_declaration_statement                           { $$ = $1; }
    +    | T_HALT_COMPILER
    +          { $$ = Stmt_HaltCompiler[$this->lexer->handleHaltCompiler()]; }
    +    | T_NAMESPACE namespace_name ';'                        { $$ = Stmt_Namespace[Name[$2], null]; }
    +    | T_NAMESPACE namespace_name '{' top_statement_list '}' { $$ = Stmt_Namespace[Name[$2], $4]; }
    +    | T_NAMESPACE '{' top_statement_list '}'                { $$ = Stmt_Namespace[null,     $3]; }
    +    | T_USE use_declarations ';'                            { $$ = Stmt_Use[$2]; }
    +    | T_CONST constant_declaration_list ';'                 { $$ = Stmt_Const[$2]; }
    +;
    +
    +use_declarations:
    +      use_declarations ',' use_declaration                  { push($1, $3); }
    +    | use_declaration                                       { init($1); }
    +;
    +
    +use_declaration:
    +      namespace_name                                        { $$ = Stmt_UseUse[Name[$1], null]; }
    +    | namespace_name T_AS T_STRING                          { $$ = Stmt_UseUse[Name[$1], $3]; }
    +    | T_NS_SEPARATOR namespace_name                         { $$ = Stmt_UseUse[Name[$2], null]; }
    +    | T_NS_SEPARATOR namespace_name T_AS T_STRING           { $$ = Stmt_UseUse[Name[$2], $4]; }
    +;
    +
    +constant_declaration_list:
    +      constant_declaration_list ',' constant_declaration    { push($1, $3); }
    +    | constant_declaration                                  { init($1); }
    +;
    +
    +constant_declaration:
    +    T_STRING '=' static_scalar                              { $$ = Const[$1, $3]; }
    +;
    +
    +inner_statement_list:
    +      inner_statement_list inner_statement                  { pushNormalizing($1, $2); }
    +    | /* empty */                                           { init(); }
    +;
    +
    +inner_statement:
    +      statement                                             { $$ = $1; }
    +    | function_declaration_statement                        { $$ = $1; }
    +    | class_declaration_statement                           { $$ = $1; }
    +    | T_HALT_COMPILER                                       { error('__halt_compiler() can only be used from the outermost scope'); }
    +;
    +
    +statement:
    +      '{' inner_statement_list '}'                          { $$ = $2; }
    +    | T_IF parentheses_expr statement elseif_list else_single
    +          { $$ = Stmt_If[$2, [stmts: toArray($3), elseifs: $4, else: $5]]; }
    +    | T_IF parentheses_expr ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';'
    +          { $$ = Stmt_If[$2, [stmts: $4, elseifs: $5, else: $6]]; }
    +    | T_WHILE parentheses_expr while_statement              { $$ = Stmt_While[$2, $3]; }
    +    | T_DO statement T_WHILE parentheses_expr ';'           { $$ = Stmt_Do   [$4, toArray($2)]; }
    +    | T_FOR '(' for_expr ';'  for_expr ';' for_expr ')' for_statement
    +          { $$ = Stmt_For[[init: $3, cond: $5, loop: $7, stmts: $9]]; }
    +    | T_SWITCH parentheses_expr switch_case_list            { $$ = Stmt_Switch[$2, $3]; }
    +    | T_BREAK ';'                                           { $$ = Stmt_Break[null]; }
    +    | T_BREAK expr ';'                                      { $$ = Stmt_Break[$2]; }
    +    | T_CONTINUE ';'                                        { $$ = Stmt_Continue[null]; }
    +    | T_CONTINUE expr ';'                                   { $$ = Stmt_Continue[$2]; }
    +    | T_RETURN ';'                                          { $$ = Stmt_Return[null]; }
    +    | T_RETURN expr ';'                                     { $$ = Stmt_Return[$2]; }
    +    | yield_expr ';'                                        { $$ = $1; }
    +    | T_GLOBAL global_var_list ';'                          { $$ = Stmt_Global[$2]; }
    +    | T_STATIC static_var_list ';'                          { $$ = Stmt_Static[$2]; }
    +    | T_ECHO expr_list ';'                                  { $$ = Stmt_Echo[$2]; }
    +    | T_INLINE_HTML                                         { $$ = Stmt_InlineHTML[$1]; }
    +    | expr ';'                                              { $$ = $1; }
    +    | T_UNSET '(' variables_list ')' ';'                    { $$ = Stmt_Unset[$3]; }
    +    | T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement
    +          { $$ = Stmt_Foreach[$3, $5[0], [keyVar: null, byRef: $5[1], stmts: $7]]; }
    +    | T_FOREACH '(' expr T_AS variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement
    +          { $$ = Stmt_Foreach[$3, $7[0], [keyVar: $5, byRef: $7[1], stmts: $9]]; }
    +    | T_DECLARE '(' declare_list ')' declare_statement      { $$ = Stmt_Declare[$3, $5]; }
    +    | ';'                                                   { $$ = array(); /* means: no statement */ }
    +    | T_TRY '{' inner_statement_list '}' catches optional_finally
    +          { $$ = Stmt_TryCatch[$3, $5, $6]; }
    +    | T_THROW expr ';'                                      { $$ = Stmt_Throw[$2]; }
    +    | T_GOTO T_STRING ';'                                   { $$ = Stmt_Goto[$2]; }
    +    | T_STRING ':'                                          { $$ = Stmt_Label[$1]; }
    +;
    +
    +catches:
    +      /* empty */                                           { init(); }
    +    | catches catch                                         { push($1, $2); }
    +;
    +
    +catch:
    +    T_CATCH '(' name T_VARIABLE ')' '{' inner_statement_list '}'
    +        { $$ = Stmt_Catch[$3, parseVar($4), $7]; }
    +;
    +
    +optional_finally:
    +      /* empty */                                           { $$ = null; }
    +    | T_FINALLY '{' inner_statement_list '}'                { $$ = $3; }
    +;
    +
    +variables_list:
    +      variable                                              { init($1); }
    +    | variables_list ',' variable                           { push($1, $3); }
    +;
    +
    +optional_ref:
    +      /* empty */                                           { $$ = false; }
    +    | '&'                                                   { $$ = true; }
    +;
    +
    +function_declaration_statement:
    +    T_FUNCTION optional_ref T_STRING '(' parameter_list ')' '{' inner_statement_list '}'
    +        { $$ = Stmt_Function[$3, [byRef: $2, params: $5, stmts: $8]]; }
    +;
    +
    +class_declaration_statement:
    +      class_entry_type T_STRING extends_from implements_list '{' class_statement_list '}'
    +          { $$ = Stmt_Class[$2, [type: $1, extends: $3, implements: $4, stmts: $6]]; }
    +    | T_INTERFACE T_STRING interface_extends_list '{' class_statement_list '}'
    +          { $$ = Stmt_Interface[$2, [extends: $3, stmts: $5]]; }
    +    | T_TRAIT T_STRING '{' class_statement_list '}'
    +          { $$ = Stmt_Trait[$2, $4]; }
    +;
    +
    +class_entry_type:
    +      T_CLASS                                               { $$ = 0; }
    +    | T_ABSTRACT T_CLASS                                    { $$ = Stmt_Class::MODIFIER_ABSTRACT; }
    +    | T_FINAL T_CLASS                                       { $$ = Stmt_Class::MODIFIER_FINAL; }
    +;
    +
    +extends_from:
    +      /* empty */                                           { $$ = null; }
    +    | T_EXTENDS name                                        { $$ = $2; }
    +;
    +
    +interface_extends_list:
    +      /* empty */                                           { $$ = array(); }
    +    | T_EXTENDS name_list                                   { $$ = $2; }
    +;
    +
    +implements_list:
    +      /* empty */                                           { $$ = array(); }
    +    | T_IMPLEMENTS name_list                                { $$ = $2; }
    +;
    +
    +name_list:
    +      name                                                  { init($1); }
    +    | name_list ',' name                                    { push($1, $3); }
    +;
    +
    +for_statement:
    +      statement                                             { $$ = toArray($1); }
    +    | ':' inner_statement_list T_ENDFOR ';'                 { $$ = $2; }
    +;
    +
    +foreach_statement:
    +      statement                                             { $$ = toArray($1); }
    +    | ':' inner_statement_list T_ENDFOREACH ';'             { $$ = $2; }
    +;
    +
    +declare_statement:
    +      statement                                             { $$ = toArray($1); }
    +    | ':' inner_statement_list T_ENDDECLARE ';'             { $$ = $2; }
    +;
    +
    +declare_list:
    +      declare_list_element                                  { init($1); }
    +    | declare_list ',' declare_list_element                 { push($1, $3); }
    +;
    +
    +declare_list_element:
    +      T_STRING '=' static_scalar                            { $$ = Stmt_DeclareDeclare[$1, $3]; }
    +;
    +
    +switch_case_list:
    +      '{' case_list '}'                                     { $$ = $2; }
    +    | '{' ';' case_list '}'                                 { $$ = $3; }
    +    | ':' case_list T_ENDSWITCH ';'                         { $$ = $2; }
    +    | ':' ';' case_list T_ENDSWITCH ';'                     { $$ = $3; }
    +;
    +
    +case_list:
    +      /* empty */                                           { init(); }
    +    | case_list case                                        { push($1, $2); }
    +;
    +
    +case:
    +      T_CASE expr case_separator inner_statement_list       { $$ = Stmt_Case[$2, $4]; }
    +    | T_DEFAULT case_separator inner_statement_list         { $$ = Stmt_Case[null, $3]; }
    +;
    +
    +case_separator:
    +      ':'
    +    | ';'
    +;
    +
    +while_statement:
    +      statement                                             { $$ = toArray($1); }
    +    | ':' inner_statement_list T_ENDWHILE ';'               { $$ = $2; }
    +;
    +
    +elseif_list:
    +      /* empty */                                           { init(); }
    +    | elseif_list elseif                                    { push($1, $2); }
    +;
    +
    +elseif:
    +      T_ELSEIF parentheses_expr statement                   { $$ = Stmt_ElseIf[$2, toArray($3)]; }
    +;
    +
    +new_elseif_list:
    +      /* empty */                                           { init(); }
    +    | new_elseif_list new_elseif                            { push($1, $2); }
    +;
    +
    +new_elseif:
    +     T_ELSEIF parentheses_expr ':' inner_statement_list     { $$ = Stmt_ElseIf[$2, $4]; }
    +;
    +
    +else_single:
    +      /* empty */                                           { $$ = null; }
    +    | T_ELSE statement                                      { $$ = Stmt_Else[toArray($2)]; }
    +;
    +
    +new_else_single:
    +      /* empty */                                           { $$ = null; }
    +    | T_ELSE ':' inner_statement_list                       { $$ = Stmt_Else[$3]; }
    +;
    +
    +foreach_variable:
    +      variable                                              { $$ = array($1, false); }
    +    | '&' variable                                          { $$ = array($2, true); }
    +    | list_expr                                             { $$ = array($1, false); }
    +;
    +
    +parameter_list:
    +      non_empty_parameter_list                              { $$ = $1; }
    +    | /* empty */                                           { $$ = array(); }
    +;
    +
    +non_empty_parameter_list:
    +      parameter                                             { init($1); }
    +    | non_empty_parameter_list ',' parameter                { push($1, $3); }
    +;
    +
    +parameter:
    +      optional_class_type optional_ref T_VARIABLE
    +          { $$ = Param[parseVar($3), null, $1, $2]; }
    +    | optional_class_type optional_ref T_VARIABLE '=' static_scalar
    +          { $$ = Param[parseVar($3), $5, $1, $2]; }
    +;
    +
    +optional_class_type:
    +      /* empty */                                           { $$ = null; }
    +    | name                                                  { $$ = $1; }
    +    | T_ARRAY                                               { $$ = 'array'; }
    +    | T_CALLABLE                                            { $$ = 'callable'; }
    +;
    +
    +argument_list:
    +      '(' ')'                                               { $$ = array(); }
    +    | '(' non_empty_argument_list ')'                       { $$ = $2; }
    +    | '(' yield_expr ')'                                    { $$ = array(Arg[$2, false]); }
    +;
    +
    +non_empty_argument_list:
    +      argument                                              { init($1); }
    +    | non_empty_argument_list ',' argument                  { push($1, $3); }
    +;
    +
    +argument:
    +      expr                                                  { $$ = Arg[$1, false]; }
    +    | '&' variable                                          { $$ = Arg[$2, true]; }
    +;
    +
    +global_var_list:
    +      global_var_list ',' global_var                        { push($1, $3); }
    +    | global_var                                            { init($1); }
    +;
    +
    +global_var:
    +      T_VARIABLE                                            { $$ = Expr_Variable[parseVar($1)]; }
    +    | '$' variable                                          { $$ = Expr_Variable[$2]; }
    +    | '$' '{' expr '}'                                      { $$ = Expr_Variable[$3]; }
    +;
    +
    +static_var_list:
    +      static_var_list ',' static_var                        { push($1, $3); }
    +    | static_var                                            { init($1); }
    +;
    +
    +static_var:
    +      T_VARIABLE                                            { $$ = Stmt_StaticVar[parseVar($1), null]; }
    +    | T_VARIABLE '=' static_scalar                          { $$ = Stmt_StaticVar[parseVar($1), $3]; }
    +;
    +
    +class_statement_list:
    +      class_statement_list class_statement                  { push($1, $2); }
    +    | /* empty */                                           { init(); }
    +;
    +
    +class_statement:
    +      variable_modifiers property_declaration_list ';'      { $$ = Stmt_Property[$1, $2]; }
    +    | T_CONST constant_declaration_list ';'                 { $$ = Stmt_ClassConst[$2]; }
    +    | method_modifiers T_FUNCTION optional_ref T_STRING '(' parameter_list ')' method_body
    +          { $$ = Stmt_ClassMethod[$4, [type: $1, byRef: $3, params: $6, stmts: $8]]; }
    +    | T_USE name_list trait_adaptations                     { $$ = Stmt_TraitUse[$2, $3]; }
    +;
    +
    +trait_adaptations:
    +      ';'                                                   { $$ = array(); }
    +    | '{' trait_adaptation_list '}'                         { $$ = $2; }
    +;
    +
    +trait_adaptation_list:
    +      /* empty */                                           { init(); }
    +    | trait_adaptation_list trait_adaptation                { push($1, $2); }
    +;
    +
    +trait_adaptation:
    +      trait_method_reference_fully_qualified T_INSTEADOF name_list ';'
    +          { $$ = Stmt_TraitUseAdaptation_Precedence[$1[0], $1[1], $3]; }
    +    | trait_method_reference T_AS member_modifier T_STRING ';'
    +          { $$ = Stmt_TraitUseAdaptation_Alias[$1[0], $1[1], $3, $4]; }
    +    | trait_method_reference T_AS member_modifier ';'
    +          { $$ = Stmt_TraitUseAdaptation_Alias[$1[0], $1[1], $3, null]; }
    +    | trait_method_reference T_AS T_STRING ';'
    +          { $$ = Stmt_TraitUseAdaptation_Alias[$1[0], $1[1], null, $3]; }
    +;
    +
    +trait_method_reference_fully_qualified:
    +      name T_PAAMAYIM_NEKUDOTAYIM T_STRING                  { $$ = array($1, $3); }
    +;
    +trait_method_reference:
    +      trait_method_reference_fully_qualified                { $$ = $1; }
    +    | T_STRING                                              { $$ = array(null, $1); }
    +;
    +
    +method_body:
    +      ';' /* abstract method */                             { $$ = null; }
    +    | '{' inner_statement_list '}'                          { $$ = $2; }
    +;
    +
    +variable_modifiers:
    +      non_empty_member_modifiers                            { $$ = $1; }
    +    | T_VAR                                                 { $$ = Stmt_Class::MODIFIER_PUBLIC; }
    +;
    +
    +method_modifiers:
    +      /* empty */                                           { $$ = Stmt_Class::MODIFIER_PUBLIC; }
    +    | non_empty_member_modifiers                            { $$ = $1; }
    +;
    +
    +non_empty_member_modifiers:
    +      member_modifier                                       { $$ = $1; }
    +    | non_empty_member_modifiers member_modifier            { Stmt_Class::verifyModifier($1, $2); $$ = $1 | $2; }
    +;
    +
    +member_modifier:
    +      T_PUBLIC                                              { $$ = Stmt_Class::MODIFIER_PUBLIC; }
    +    | T_PROTECTED                                           { $$ = Stmt_Class::MODIFIER_PROTECTED; }
    +    | T_PRIVATE                                             { $$ = Stmt_Class::MODIFIER_PRIVATE; }
    +    | T_STATIC                                              { $$ = Stmt_Class::MODIFIER_STATIC; }
    +    | T_ABSTRACT                                            { $$ = Stmt_Class::MODIFIER_ABSTRACT; }
    +    | T_FINAL                                               { $$ = Stmt_Class::MODIFIER_FINAL; }
    +;
    +
    +property_declaration_list:
    +      property_declaration                                  { init($1); }
    +    | property_declaration_list ',' property_declaration    { push($1, $3); }
    +;
    +
    +property_declaration:
    +      T_VARIABLE                                            { $$ = Stmt_PropertyProperty[parseVar($1), null]; }
    +    | T_VARIABLE '=' static_scalar                          { $$ = Stmt_PropertyProperty[parseVar($1), $3]; }
    +;
    +
    +expr_list:
    +      expr_list ',' expr                                    { push($1, $3); }
    +    | expr                                                  { init($1); }
    +;
    +
    +for_expr:
    +      /* empty */                                           { $$ = array(); }
    +    | expr_list                                             { $$ = $1; }
    +;
    +
    +expr:
    +      variable                                              { $$ = $1; }
    +    | list_expr '=' expr                                    { $$ = Expr_Assign[$1, $3]; }
    +    | variable '=' expr                                     { $$ = Expr_Assign[$1, $3]; }
    +    | variable '=' '&' variable                             { $$ = Expr_AssignRef[$1, $4]; }
    +    | variable '=' '&' new_expr                             { $$ = Expr_AssignRef[$1, $4]; }
    +    | new_expr                                              { $$ = $1; }
    +    | T_CLONE expr                                          { $$ = Expr_Clone[$2]; }
    +    | variable T_PLUS_EQUAL expr                            { $$ = Expr_AssignPlus      [$1, $3]; }
    +    | variable T_MINUS_EQUAL expr                           { $$ = Expr_AssignMinus     [$1, $3]; }
    +    | variable T_MUL_EQUAL expr                             { $$ = Expr_AssignMul       [$1, $3]; }
    +    | variable T_DIV_EQUAL expr                             { $$ = Expr_AssignDiv       [$1, $3]; }
    +    | variable T_CONCAT_EQUAL expr                          { $$ = Expr_AssignConcat    [$1, $3]; }
    +    | variable T_MOD_EQUAL expr                             { $$ = Expr_AssignMod       [$1, $3]; }
    +    | variable T_AND_EQUAL expr                             { $$ = Expr_AssignBitwiseAnd[$1, $3]; }
    +    | variable T_OR_EQUAL expr                              { $$ = Expr_AssignBitwiseOr [$1, $3]; }
    +    | variable T_XOR_EQUAL expr                             { $$ = Expr_AssignBitwiseXor[$1, $3]; }
    +    | variable T_SL_EQUAL expr                              { $$ = Expr_AssignShiftLeft [$1, $3]; }
    +    | variable T_SR_EQUAL expr                              { $$ = Expr_AssignShiftRight[$1, $3]; }
    +    | variable T_INC                                        { $$ = Expr_PostInc[$1]; }
    +    | T_INC variable                                        { $$ = Expr_PreInc [$2]; }
    +    | variable T_DEC                                        { $$ = Expr_PostDec[$1]; }
    +    | T_DEC variable                                        { $$ = Expr_PreDec [$2]; }
    +    | expr T_BOOLEAN_OR expr                                { $$ = Expr_BooleanOr [$1, $3]; }
    +    | expr T_BOOLEAN_AND expr                               { $$ = Expr_BooleanAnd[$1, $3]; }
    +    | expr T_LOGICAL_OR expr                                { $$ = Expr_LogicalOr [$1, $3]; }
    +    | expr T_LOGICAL_AND expr                               { $$ = Expr_LogicalAnd[$1, $3]; }
    +    | expr T_LOGICAL_XOR expr                               { $$ = Expr_LogicalXor[$1, $3]; }
    +    | expr '|' expr                                         { $$ = Expr_BitwiseOr [$1, $3]; }
    +    | expr '&' expr                                         { $$ = Expr_BitwiseAnd[$1, $3]; }
    +    | expr '^' expr                                         { $$ = Expr_BitwiseXor[$1, $3]; }
    +    | expr '.' expr                                         { $$ = Expr_Concat    [$1, $3]; }
    +    | expr '+' expr                                         { $$ = Expr_Plus      [$1, $3]; }
    +    | expr '-' expr                                         { $$ = Expr_Minus     [$1, $3]; }
    +    | expr '*' expr                                         { $$ = Expr_Mul       [$1, $3]; }
    +    | expr '/' expr                                         { $$ = Expr_Div       [$1, $3]; }
    +    | expr '%' expr                                         { $$ = Expr_Mod       [$1, $3]; }
    +    | expr T_SL expr                                        { $$ = Expr_ShiftLeft [$1, $3]; }
    +    | expr T_SR expr                                        { $$ = Expr_ShiftRight[$1, $3]; }
    +    | '+' expr %prec T_INC                                  { $$ = Expr_UnaryPlus [$2]; }
    +    | '-' expr %prec T_INC                                  { $$ = Expr_UnaryMinus[$2]; }
    +    | '!' expr                                              { $$ = Expr_BooleanNot[$2]; }
    +    | '~' expr                                              { $$ = Expr_BitwiseNot[$2]; }
    +    | expr T_IS_IDENTICAL expr                              { $$ = Expr_Identical     [$1, $3]; }
    +    | expr T_IS_NOT_IDENTICAL expr                          { $$ = Expr_NotIdentical  [$1, $3]; }
    +    | expr T_IS_EQUAL expr                                  { $$ = Expr_Equal         [$1, $3]; }
    +    | expr T_IS_NOT_EQUAL expr                              { $$ = Expr_NotEqual      [$1, $3]; }
    +    | expr '<' expr                                         { $$ = Expr_Smaller       [$1, $3]; }
    +    | expr T_IS_SMALLER_OR_EQUAL expr                       { $$ = Expr_SmallerOrEqual[$1, $3]; }
    +    | expr '>' expr                                         { $$ = Expr_Greater       [$1, $3]; }
    +    | expr T_IS_GREATER_OR_EQUAL expr                       { $$ = Expr_GreaterOrEqual[$1, $3]; }
    +    | expr T_INSTANCEOF class_name_reference                { $$ = Expr_Instanceof    [$1, $3]; }
    +    | parentheses_expr                                      { $$ = $1; }
    +    /* we need a separate '(' new_expr ')' rule to avoid problems caused by a s/r conflict */
    +    | '(' new_expr ')'                                      { $$ = $2; }
    +    | expr '?' expr ':' expr                                { $$ = Expr_Ternary[$1, $3,   $5]; }
    +    | expr '?' ':' expr                                     { $$ = Expr_Ternary[$1, null, $4]; }
    +    | T_ISSET '(' variables_list ')'                        { $$ = Expr_Isset[$3]; }
    +    | T_EMPTY '(' expr ')'                                  { $$ = Expr_Empty[$3]; }
    +    | T_INCLUDE expr                                        { $$ = Expr_Include[$2, Expr_Include::TYPE_INCLUDE]; }
    +    | T_INCLUDE_ONCE expr                                   { $$ = Expr_Include[$2, Expr_Include::TYPE_INCLUDE_ONCE]; }
    +    | T_EVAL parentheses_expr                               { $$ = Expr_Eval[$2]; }
    +    | T_REQUIRE expr                                        { $$ = Expr_Include[$2, Expr_Include::TYPE_REQUIRE]; }
    +    | T_REQUIRE_ONCE expr                                   { $$ = Expr_Include[$2, Expr_Include::TYPE_REQUIRE_ONCE]; }
    +    | T_INT_CAST expr                                       { $$ = Expr_Cast_Int     [$2]; }
    +    | T_DOUBLE_CAST expr                                    { $$ = Expr_Cast_Double  [$2]; }
    +    | T_STRING_CAST expr                                    { $$ = Expr_Cast_String  [$2]; }
    +    | T_ARRAY_CAST expr                                     { $$ = Expr_Cast_Array   [$2]; }
    +    | T_OBJECT_CAST expr                                    { $$ = Expr_Cast_Object  [$2]; }
    +    | T_BOOL_CAST expr                                      { $$ = Expr_Cast_Bool    [$2]; }
    +    | T_UNSET_CAST expr                                     { $$ = Expr_Cast_Unset   [$2]; }
    +    | T_EXIT exit_expr                                      { $$ = Expr_Exit         [$2]; }
    +    | '@' expr                                              { $$ = Expr_ErrorSuppress[$2]; }
    +    | scalar                                                { $$ = $1; }
    +    | array_expr                                            { $$ = $1; }
    +    | scalar_dereference                                    { $$ = $1; }
    +    | '`' backticks_expr '`'                                { $$ = Expr_ShellExec[$2]; }
    +    | T_PRINT expr                                          { $$ = Expr_Print[$2]; }
    +    | T_YIELD                                               { $$ = Expr_Yield[null, null]; }
    +    | T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars '{' inner_statement_list '}'
    +          { $$ = Expr_Closure[[static: false, byRef: $2, params: $4, uses: $6, stmts: $8]]; }
    +    | T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars '{' inner_statement_list '}'
    +          { $$ = Expr_Closure[[static: true, byRef: $3, params: $5, uses: $7, stmts: $9]]; }
    +;
    +
    +parentheses_expr:
    +      '(' expr ')'                                          { $$ = $2; }
    +    | '(' yield_expr ')'                                    { $$ = $2; }
    +;
    +
    +yield_expr:
    +      T_YIELD expr                                          { $$ = Expr_Yield[$2, null]; }
    +    | T_YIELD expr T_DOUBLE_ARROW expr                      { $$ = Expr_Yield[$4, $2]; }
    +;
    +
    +array_expr:
    +      T_ARRAY '(' array_pair_list ')'                       { $$ = Expr_Array[$3]; }
    +    | '[' array_pair_list ']'                               { $$ = Expr_Array[$2]; }
    +;
    +
    +scalar_dereference:
    +      array_expr '[' dim_offset ']'                         { $$ = Expr_ArrayDimFetch[$1, $3]; }
    +    | T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']'
    +          { $$ = Expr_ArrayDimFetch[Scalar_String[Scalar_String::parse($1)], $3]; }
    +    | scalar_dereference '[' dim_offset ']'                 { $$ = Expr_ArrayDimFetch[$1, $3]; }
    +    /* alternative array syntax missing intentionally */
    +;
    +
    +new_expr:
    +      T_NEW class_name_reference ctor_arguments             { $$ = Expr_New[$2, $3]; }
    +;
    +
    +lexical_vars:
    +      /* empty */                                           { $$ = array(); }
    +    | T_USE '(' lexical_var_list ')'                        { $$ = $3; }
    +;
    +
    +lexical_var_list:
    +      lexical_var                                           { init($1); }
    +    | lexical_var_list ',' lexical_var                      { push($1, $3); }
    +;
    +
    +lexical_var:
    +      optional_ref T_VARIABLE                               { $$ = Expr_ClosureUse[parseVar($2), $1]; }
    +;
    +
    +function_call:
    +      name argument_list                                    { $$ = Expr_FuncCall[$1, $2]; }
    +    | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM T_STRING argument_list
    +          { $$ = Expr_StaticCall[$1, $3, $4]; }
    +    | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' argument_list
    +          { $$ = Expr_StaticCall[$1, $4, $6]; }
    +    | static_property argument_list {
    +            if ($1 instanceof PHPParser_Node_Expr_StaticPropertyFetch) {
    +                $$ = Expr_StaticCall[$1->class, Expr_Variable[$1->name], $2];
    +            } elseif ($1 instanceof PHPParser_Node_Expr_ArrayDimFetch) {
    +                $tmp = $1;
    +                while ($tmp->var instanceof PHPParser_Node_Expr_ArrayDimFetch) {
    +                    $tmp = $tmp->var;
    +                }
    +
    +                $$ = Expr_StaticCall[$tmp->var->class, $1, $2];
    +                $tmp->var = Expr_Variable[$tmp->var->name];
    +            } else {
    +                throw new Exception;
    +            }
    +          }
    +    | variable_without_objects argument_list
    +          { $$ = Expr_FuncCall[$1, $2]; }
    +    | function_call '[' dim_offset ']'                      { $$ = Expr_ArrayDimFetch[$1, $3]; }
    +      /* alternative array syntax missing intentionally */
    +;
    +
    +class_name:
    +      T_STATIC                                              { $$ = Name['static']; }
    +    | name                                                  { $$ = $1; }
    +;
    +
    +name:
    +      namespace_name                                        { $$ = Name[$1]; }
    +    | T_NS_SEPARATOR namespace_name                         { $$ = Name_FullyQualified[$2]; }
    +    | T_NAMESPACE T_NS_SEPARATOR namespace_name             { $$ = Name_Relative[$3]; }
    +;
    +
    +class_name_reference:
    +      class_name                                            { $$ = $1; }
    +    | dynamic_class_name_reference                          { $$ = $1; }
    +;
    +
    +dynamic_class_name_reference:
    +      object_access_for_dcnr                                { $$ = $1; }
    +    | base_variable                                         { $$ = $1; }
    +;
    +
    +class_name_or_var:
    +      class_name                                            { $$ = $1; }
    +    | reference_variable                                    { $$ = $1; }
    +;
    +
    +object_access_for_dcnr:
    +    | base_variable T_OBJECT_OPERATOR object_property
    +          { $$ = Expr_PropertyFetch[$1, $3]; }
    +    | object_access_for_dcnr T_OBJECT_OPERATOR object_property
    +          { $$ = Expr_PropertyFetch[$1, $3]; }
    +    | object_access_for_dcnr '[' dim_offset ']'             { $$ = Expr_ArrayDimFetch[$1, $3]; }
    +    | object_access_for_dcnr '{' expr '}'                   { $$ = Expr_ArrayDimFetch[$1, $3]; }
    +;
    +
    +exit_expr:
    +      /* empty */                                           { $$ = null; }
    +    | '(' ')'                                               { $$ = null; }
    +    | parentheses_expr                                      { $$ = $1; }
    +;
    +
    +backticks_expr:
    +      /* empty */                                           { $$ = array(); }
    +    | T_ENCAPSED_AND_WHITESPACE                             { $$ = array(Scalar_String::parseEscapeSequences($1, '`')); }
    +    | encaps_list                                           { parseEncapsed($1, '`'); $$ = $1; }
    +;
    +
    +ctor_arguments:
    +      /* empty */                                           { $$ = array(); }
    +    | argument_list                                         { $$ = $1; }
    +;
    +
    +common_scalar:
    +      T_LNUMBER                                             { $$ = Scalar_LNumber[Scalar_LNumber::parse($1)]; }
    +    | T_DNUMBER                                             { $$ = Scalar_DNumber[Scalar_DNumber::parse($1)]; }
    +    | T_CONSTANT_ENCAPSED_STRING                            { $$ = Scalar_String[Scalar_String::parse($1)]; }
    +    | T_LINE                                                { $$ = Scalar_LineConst[]; }
    +    | T_FILE                                                { $$ = Scalar_FileConst[]; }
    +    | T_DIR                                                 { $$ = Scalar_DirConst[]; }
    +    | T_CLASS_C                                             { $$ = Scalar_ClassConst[]; }
    +    | T_TRAIT_C                                             { $$ = Scalar_TraitConst[]; }
    +    | T_METHOD_C                                            { $$ = Scalar_MethodConst[]; }
    +    | T_FUNC_C                                              { $$ = Scalar_FuncConst[]; }
    +    | T_NS_C                                                { $$ = Scalar_NSConst[]; }
    +    | T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC
    +          { $$ = Scalar_String[Scalar_String::parseDocString($1, $2)]; }
    +    | T_START_HEREDOC T_END_HEREDOC
    +          { $$ = Scalar_String['']; }
    +    | name                                                  { $$ = Expr_ConstFetch[$1]; }
    +;
    +
    +static_scalar: /* compile-time evaluated scalars */
    +      common_scalar                                         { $$ = $1; }
    +    | class_name T_PAAMAYIM_NEKUDOTAYIM class_const_name    { $$ = Expr_ClassConstFetch[$1, $3]; }
    +    | '+' static_scalar                                     { $$ = Expr_UnaryPlus[$2]; }
    +    | '-' static_scalar                                     { $$ = Expr_UnaryMinus[$2]; }
    +    | T_ARRAY '(' static_array_pair_list ')'                { $$ = Expr_Array[$3]; }
    +    | '[' static_array_pair_list ']'                        { $$ = Expr_Array[$2]; }
    +;
    +
    +scalar:
    +      common_scalar                                         { $$ = $1; }
    +    | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM class_const_name
    +          { $$ = Expr_ClassConstFetch[$1, $3]; }
    +    | '"' encaps_list '"'
    +          { parseEncapsed($2, '"'); $$ = Scalar_Encapsed[$2]; }
    +    | T_START_HEREDOC encaps_list T_END_HEREDOC
    +          { parseEncapsedDoc($2); $$ = Scalar_Encapsed[$2]; }
    +;
    +
    +class_const_name:
    +      T_STRING                                              { $$ = $1; }
    +    | T_CLASS                                               { $$ = 'class'; }
    +;
    +
    +static_array_pair_list:
    +      /* empty */                                           { $$ = array(); }
    +    | non_empty_static_array_pair_list optional_comma       { $$ = $1; }
    +;
    +
    +optional_comma:
    +      /* empty */
    +    | ','
    +;
    +
    +non_empty_static_array_pair_list:
    +      non_empty_static_array_pair_list ',' static_array_pair { push($1, $3); }
    +    | static_array_pair                                      { init($1); }
    +;
    +
    +static_array_pair:
    +      static_scalar T_DOUBLE_ARROW static_scalar            { $$ = Expr_ArrayItem[$3, $1,   false]; }
    +    | static_scalar                                         { $$ = Expr_ArrayItem[$1, null, false]; }
    +;
    +
    +variable:
    +      object_access                                         { $$ = $1; }
    +    | base_variable                                         { $$ = $1; }
    +    | function_call                                         { $$ = $1; }
    +    | new_expr_array_deref                                  { $$ = $1; }
    +;
    +
    +new_expr_array_deref:
    +      '(' new_expr ')' '[' dim_offset ']'                   { $$ = Expr_ArrayDimFetch[$2, $5]; }
    +    | new_expr_array_deref '[' dim_offset ']'               { $$ = Expr_ArrayDimFetch[$1, $3]; }
    +      /* alternative array syntax missing intentionally */
    +;
    +
    +object_access:
    +      variable_or_new_expr T_OBJECT_OPERATOR object_property
    +          { $$ = Expr_PropertyFetch[$1, $3]; }
    +    | variable_or_new_expr T_OBJECT_OPERATOR object_property argument_list
    +          { $$ = Expr_MethodCall[$1, $3, $4]; }
    +    | object_access argument_list                           { $$ = Expr_FuncCall[$1, $2]; }
    +    | object_access '[' dim_offset ']'                      { $$ = Expr_ArrayDimFetch[$1, $3]; }
    +    | object_access '{' expr '}'                            { $$ = Expr_ArrayDimFetch[$1, $3]; }
    +;
    +
    +variable_or_new_expr:
    +      variable                                              { $$ = $1; }
    +    | '(' new_expr ')'                                      { $$ = $2; }
    +;
    +
    +variable_without_objects:
    +      reference_variable                                    { $$ = $1; }
    +    | '$' variable_without_objects                          { $$ = Expr_Variable[$2]; }
    +;
    +
    +base_variable:
    +      variable_without_objects                              { $$ = $1; }
    +    | static_property                                       { $$ = $1; }
    +;
    +
    +static_property:
    +      class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '$' reference_variable
    +          { $$ = Expr_StaticPropertyFetch[$1, $4]; }
    +    | static_property_with_arrays                           { $$ = $1; }
    +;
    +
    +static_property_with_arrays:
    +      class_name_or_var T_PAAMAYIM_NEKUDOTAYIM T_VARIABLE
    +          { $$ = Expr_StaticPropertyFetch[$1, parseVar($3)]; }
    +    | class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '$' '{' expr '}'
    +          { $$ = Expr_StaticPropertyFetch[$1, $5]; }
    +    | static_property_with_arrays '[' dim_offset ']'        { $$ = Expr_ArrayDimFetch[$1, $3]; }
    +    | static_property_with_arrays '{' expr '}'              { $$ = Expr_ArrayDimFetch[$1, $3]; }
    +;
    +
    +reference_variable:
    +      reference_variable '[' dim_offset ']'                 { $$ = Expr_ArrayDimFetch[$1, $3]; }
    +    | reference_variable '{' expr '}'                       { $$ = Expr_ArrayDimFetch[$1, $3]; }
    +    | T_VARIABLE                                            { $$ = Expr_Variable[parseVar($1)]; }
    +    | '$' '{' expr '}'                                      { $$ = Expr_Variable[$3]; }
    +;
    +
    +dim_offset:
    +      /* empty */                                           { $$ = null; }
    +    | expr                                                  { $$ = $1; }
    +;
    +
    +object_property:
    +      T_STRING                                              { $$ = $1; }
    +    | '{' expr '}'                                          { $$ = $2; }
    +    | variable_without_objects                              { $$ = $1; }
    +;
    +
    +list_expr:
    +      T_LIST '(' list_expr_elements ')'                     { $$ = Expr_List[$3]; }
    +;
    +
    +list_expr_elements:
    +      list_expr_elements ',' list_expr_element              { push($1, $3); }
    +    | list_expr_element                                     { init($1); }
    +;
    +
    +list_expr_element:
    +      variable                                              { $$ = $1; }
    +    | list_expr                                             { $$ = $1; }
    +    | /* empty */                                           { $$ = null; }
    +;
    +
    +array_pair_list:
    +      /* empty */                                           { $$ = array(); }
    +    | non_empty_array_pair_list optional_comma              { $$ = $1; }
    +;
    +
    +non_empty_array_pair_list:
    +      non_empty_array_pair_list ',' array_pair              { push($1, $3); }
    +    | array_pair                                            { init($1); }
    +;
    +
    +array_pair:
    +      expr T_DOUBLE_ARROW expr                              { $$ = Expr_ArrayItem[$3, $1,   false]; }
    +    | expr                                                  { $$ = Expr_ArrayItem[$1, null, false]; }
    +    | expr T_DOUBLE_ARROW '&' variable                      { $$ = Expr_ArrayItem[$4, $1,   true]; }
    +    | '&' variable                                          { $$ = Expr_ArrayItem[$2, null, true]; }
    +;
    +
    +encaps_list:
    +      encaps_list encaps_var                                { push($1, $2); }
    +    | encaps_list T_ENCAPSED_AND_WHITESPACE                 { push($1, $2); }
    +    | encaps_var                                            { init($1); }
    +    | T_ENCAPSED_AND_WHITESPACE encaps_var                  { init($1, $2); }
    +;
    +
    +encaps_var:
    +      T_VARIABLE                                            { $$ = Expr_Variable[parseVar($1)]; }
    +    | T_VARIABLE '[' encaps_var_offset ']'                  { $$ = Expr_ArrayDimFetch[Expr_Variable[parseVar($1)], $3]; }
    +    | T_VARIABLE T_OBJECT_OPERATOR T_STRING                 { $$ = Expr_PropertyFetch[Expr_Variable[parseVar($1)], $3]; }
    +    | T_DOLLAR_OPEN_CURLY_BRACES expr '}'                   { $$ = Expr_Variable[$2]; }
    +    | T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}'       { $$ = Expr_Variable[$2]; }
    +    | T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '[' expr ']' '}'
    +          { $$ = Expr_ArrayDimFetch[Expr_Variable[$2], $4]; }
    +    | T_CURLY_OPEN variable '}'                             { $$ = $2; }
    +;
    +
    +encaps_var_offset:
    +      T_STRING                                              { $$ = Scalar_String[$1]; }
    +    | T_NUM_STRING                                          { $$ = Scalar_String[$1]; }
    +    | T_VARIABLE                                            { $$ = Expr_Variable[parseVar($1)]; }
    +;
    +
    +%%
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Autoloader.php b/vendor/nikic/php-parser/lib/PHPParser/Autoloader.php
    new file mode 100644
    index 0000000..314dffb
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Autoloader.php
    @@ -0,0 +1,33 @@
    +name = $name;
    +
    +        $this->type = 0;
    +        $this->extends = null;
    +        $this->implements = array();
    +
    +        $this->uses = $this->constants = $this->properties = $this->methods = array();
    +    }
    +
    +    /**
    +     * Extends a class.
    +     *
    +     * @param PHPParser_Node_Name|string $class Name of class to extend
    +     *
    +     * @return PHPParser_Builder_Class The builder instance (for fluid interface)
    +     */
    +    public function extend($class) {
    +        $this->extends = $this->normalizeName($class);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Implements one or more interfaces.
    +     *
    +     * @param PHPParser_Node_Name|string $interface Name of interface to implement
    +     * @param PHPParser_Node_Name|string $...       More interfaces to implement
    +     *
    +     * @return PHPParser_Builder_Class The builder instance (for fluid interface)
    +     */
    +    public function implement() {
    +        foreach (func_get_args() as $interface) {
    +            $this->implements[] = $this->normalizeName($interface);
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the class abstract.
    +     *
    +     * @return PHPParser_Builder_Class The builder instance (for fluid interface)
    +     */
    +    public function makeAbstract() {
    +        $this->setModifier(PHPParser_Node_Stmt_Class::MODIFIER_ABSTRACT);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the class final.
    +     *
    +     * @return PHPParser_Builder_Class The builder instance (for fluid interface)
    +     */
    +    public function makeFinal() {
    +        $this->setModifier(PHPParser_Node_Stmt_Class::MODIFIER_FINAL);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Adds a statement.
    +     *
    +     * @param PHPParser_Node_Stmt|PHPParser_Builder $stmt The statement to add
    +     *
    +     * @return PHPParser_Builder_Class The builder instance (for fluid interface)
    +     */
    +    public function addStmt($stmt) {
    +        $stmt = $this->normalizeNode($stmt);
    +
    +        $targets = array(
    +            'Stmt_TraitUse'    => &$this->uses,
    +            'Stmt_ClassConst'  => &$this->constants,
    +            'Stmt_Property'    => &$this->properties,
    +            'Stmt_ClassMethod' => &$this->methods,
    +        );
    +
    +        $type = $stmt->getType();
    +        if (!isset($targets[$type])) {
    +            throw new LogicException(sprintf('Unexpected node of type "%s"', $type));
    +        }
    +
    +        $targets[$type][] = $stmt;
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Adds multiple statements.
    +     *
    +     * @param array $stmts The statements to add
    +     *
    +     * @return PHPParser_Builder_Class The builder instance (for fluid interface)
    +     */
    +    public function addStmts(array $stmts) {
    +        foreach ($stmts as $stmt) {
    +            $this->addStmt($stmt);
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Returns the built class node.
    +     *
    +     * @return PHPParser_Node_Stmt_Class The built class node
    +     */
    +    public function getNode() {
    +        return new PHPParser_Node_Stmt_Class($this->name, array(
    +            'type' => $this->type,
    +            'extends' => $this->extends,
    +            'implements' => $this->implements,
    +            'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods),
    +        ));
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Builder/Function.php b/vendor/nikic/php-parser/lib/PHPParser/Builder/Function.php
    new file mode 100644
    index 0000000..462ad96
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Builder/Function.php
    @@ -0,0 +1,109 @@
    +name = $name;
    +
    +        $this->returnByRef = false;
    +        $this->params = array();
    +        $this->stmts = array();
    +    }
    +
    +    /**
    +     * Make the function return by reference.
    +     *
    +     * @return PHPParser_Builder_Function The builder instance (for fluid interface)
    +     */
    +    public function makeReturnByRef() {
    +        $this->returnByRef = true;
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Adds a parameter.
    +     *
    +     * @param PHPParser_Node_Param|PHPParser_Builder_Param $param The parameter to add
    +     *
    +     * @return PHPParser_Builder_Function The builder instance (for fluid interface)
    +     */
    +    public function addParam($param) {
    +        $param = $this->normalizeNode($param);
    +
    +        if (!$param instanceof PHPParser_Node_Param) {
    +            throw new LogicException(sprintf('Expected parameter node, got "%s"', $param->getType()));
    +        }
    +
    +        $this->params[] = $param;
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Adds multiple parameters.
    +     *
    +     * @param array $params The parameters to add
    +     *
    +     * @return PHPParser_Builder_Function The builder instance (for fluid interface)
    +     */
    +    public function addParams(array $params) {
    +        foreach ($params as $param) {
    +            $this->addParam($param);
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Adds a statement.
    +     *
    +     * @param PHPParser_Node|PHPParser_Builder $stmt The statement to add
    +     *
    +     * @return PHPParser_Builder_Function The builder instance (for fluid interface)
    +     */
    +    public function addStmt($stmt) {
    +        $this->stmts[] = $this->normalizeNode($stmt);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Adds multiple statements.
    +     *
    +     * @param array $stmts The statements to add
    +     *
    +     * @return PHPParser_Builder_Function The builder instance (for fluid interface)
    +     */
    +    public function addStmts(array $stmts) {
    +        foreach ($stmts as $stmt) {
    +            $this->addStmt($stmt);
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Returns the built function node.
    +     *
    +     * @return PHPParser_Node_Stmt_Function The built function node
    +     */
    +    public function getNode() {
    +        return new PHPParser_Node_Stmt_Function($this->name, array(
    +            'byRef'  => $this->returnByRef,
    +            'params' => $this->params,
    +            'stmts'  => $this->stmts,
    +        ));
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Builder/Interface.php b/vendor/nikic/php-parser/lib/PHPParser/Builder/Interface.php
    new file mode 100644
    index 0000000..8c76dc9
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Builder/Interface.php
    @@ -0,0 +1,92 @@
    +name = $name;
    +        $this->extends = array();
    +        $this->constants = $this->methods = array();
    +    }
    +
    +    /**
    +     * Extends one or more interfaces.
    +     *
    +     * @param PHPParser_Node_Name|string $interface Name of interface to extend
    +     * @param PHPParser_Node_Name|string $...       More interfaces to extend
    +     *
    +     * @return PHPParser_Builder_Interface The builder instance (for fluid interface)
    +     */
    +    public function extend() {
    +        foreach (func_get_args() as $interface) {
    +            $this->extends[] = $this->normalizeName($interface);
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Adds a statement.
    +     *
    +     * @param PHPParser_Node_Stmt|PHPParser_Builder $stmt The statement to add
    +     *
    +     * @return PHPParser_Builder_Interface The builder instance (for fluid interface)
    +     */
    +    public function addStmt($stmt) {
    +        $stmt = $this->normalizeNode($stmt);
    +
    +        $type = $stmt->getType();
    +        switch ($type) {
    +            case 'Stmt_ClassConst':
    +                $this->constants[] = $stmt;
    +                break;
    +
    +            case 'Stmt_ClassMethod':
    +                // we erase all statements in the body of an interface method
    +                $stmt->stmts = null;
    +                $this->methods[] = $stmt;
    +                break;
    +
    +            default:
    +                throw new LogicException(sprintf('Unexpected node of type "%s"', $type));
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Adds multiple statements.
    +     *
    +     * @param array $stmts The statements to add
    +     *
    +     * @return PHPParser_Builder_Class The builder instance (for fluid interface)
    +     */
    +    public function addStmts(array $stmts) {
    +        foreach ($stmts as $stmt) {
    +            $this->addStmt($stmt);
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Returns the built class node.
    +     *
    +     * @return PHPParser_Node_Stmt_Interface The built interface node
    +     */
    +    public function getNode() {
    +        return new PHPParser_Node_Stmt_Interface($this->name, array(
    +            'extends' => $this->extends,
    +            'stmts' => array_merge($this->constants, $this->methods),
    +        ));
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Builder/Method.php b/vendor/nikic/php-parser/lib/PHPParser/Builder/Method.php
    new file mode 100644
    index 0000000..7244ba5
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Builder/Method.php
    @@ -0,0 +1,187 @@
    +name = $name;
    +
    +        $this->type = 0;
    +        $this->returnByRef = false;
    +        $this->params = array();
    +        $this->stmts = array();
    +    }
    +
    +    /**
    +     * Makes the method public.
    +     *
    +     * @return PHPParser_Builder_Method The builder instance (for fluid interface)
    +     */
    +    public function makePublic() {
    +        $this->setModifier(PHPParser_Node_Stmt_Class::MODIFIER_PUBLIC);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the method protected.
    +     *
    +     * @return PHPParser_Builder_Method The builder instance (for fluid interface)
    +     */
    +    public function makeProtected() {
    +        $this->setModifier(PHPParser_Node_Stmt_Class::MODIFIER_PROTECTED);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the method private.
    +     *
    +     * @return PHPParser_Builder_Method The builder instance (for fluid interface)
    +     */
    +    public function makePrivate() {
    +        $this->setModifier(PHPParser_Node_Stmt_Class::MODIFIER_PRIVATE);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the method static.
    +     *
    +     * @return PHPParser_Builder_Method The builder instance (for fluid interface)
    +     */
    +    public function makeStatic() {
    +        $this->setModifier(PHPParser_Node_Stmt_Class::MODIFIER_STATIC);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the method abstract.
    +     *
    +     * @return PHPParser_Builder_Method The builder instance (for fluid interface)
    +     */
    +    public function makeAbstract() {
    +        if (!empty($this->stmts)) {
    +            throw new LogicException('Cannot make method with statements abstract');
    +        }
    +
    +        $this->setModifier(PHPParser_Node_Stmt_Class::MODIFIER_ABSTRACT);
    +        $this->stmts = null; // abstract methods don't have statements
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the method final.
    +     *
    +     * @return PHPParser_Builder_Method The builder instance (for fluid interface)
    +     */
    +    public function makeFinal() {
    +        $this->setModifier(PHPParser_Node_Stmt_Class::MODIFIER_FINAL);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Make the method return by reference.
    +     *
    +     * @return PHPParser_Builder_Method The builder instance (for fluid interface)
    +     */
    +    public function makeReturnByRef() {
    +        $this->returnByRef = true;
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Adds a parameter.
    +     *
    +     * @param PHPParser_Node_Param|PHPParser_Builder_Param $param The parameter to add
    +     *
    +     * @return PHPParser_Builder_Method The builder instance (for fluid interface)
    +     */
    +    public function addParam($param) {
    +        $param = $this->normalizeNode($param);
    +
    +        if (!$param instanceof PHPParser_Node_Param) {
    +            throw new LogicException(sprintf('Expected parameter node, got "%s"', $param->getType()));
    +        }
    +
    +        $this->params[] = $param;
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Adds multiple parameters.
    +     *
    +     * @param array $params The parameters to add
    +     *
    +     * @return PHPParser_Builder_Method The builder instance (for fluid interface)
    +     */
    +    public function addParams(array $params) {
    +        foreach ($params as $param) {
    +            $this->addParam($param);
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Adds a statement.
    +     *
    +     * @param PHPParser_Node|PHPParser_Builder $stmt The statement to add
    +     *
    +     * @return PHPParser_Builder_Method The builder instance (for fluid interface)
    +     */
    +    public function addStmt($stmt) {
    +        if (null === $this->stmts) {
    +            throw new LogicException('Cannot add statements to an abstract method');
    +        }
    +
    +        $this->stmts[] = $this->normalizeNode($stmt);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Adds multiple statements.
    +     *
    +     * @param array $stmts The statements to add
    +     *
    +     * @return PHPParser_Builder_Method The builder instance (for fluid interface)
    +     */
    +    public function addStmts(array $stmts) {
    +        foreach ($stmts as $stmt) {
    +            $this->addStmt($stmt);
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Returns the built method node.
    +     *
    +     * @return PHPParser_Node_Stmt_ClassMethod The built method node
    +     */
    +    public function getNode() {
    +        return new PHPParser_Node_Stmt_ClassMethod($this->name, array(
    +            'type'   => $this->type !== 0 ? $this->type : PHPParser_Node_Stmt_Class::MODIFIER_PUBLIC,
    +            'byRef'  => $this->returnByRef,
    +            'params' => $this->params,
    +            'stmts'  => $this->stmts,
    +        ));
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Builder/Param.php b/vendor/nikic/php-parser/lib/PHPParser/Builder/Param.php
    new file mode 100644
    index 0000000..4c217a9
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Builder/Param.php
    @@ -0,0 +1,75 @@
    +name = $name;
    +
    +        $this->default = null;
    +        $this->type = null;
    +        $this->byRef = false;
    +    }
    +
    +    /**
    +     * Sets default value for the parameter.
    +     *
    +     * @param mixed $value Default value to use
    +     *
    +     * @return PHPParser_Builder_Param The builder instance (for fluid interface)
    +     */
    +    public function setDefault($value) {
    +        $this->default = $this->normalizeValue($value);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Sets type hint for the parameter.
    +     *
    +     * @param string|PHPParser_Node_Name $type Type hint to use
    +     *
    +     * @return PHPParser_Builder_Param The builder instance (for fluid interface)
    +     */
    +    public function setTypeHint($type) {
    +        if ($type === 'array' || $type === 'callable') {
    +            $this->type = $type;
    +        } else {
    +            $this->type = $this->normalizeName($type);
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Make the parameter accept the value by reference.
    +     *
    +     * @return PHPParser_Builder_Param The builder instance (for fluid interface)
    +     */
    +    public function makeByRef() {
    +        $this->byRef = true;
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Returns the built parameter node.
    +     *
    +     * @return PHPParser_Node_Param The built parameter node
    +     */
    +    public function getNode() {
    +        return new PHPParser_Node_Param(
    +            $this->name, $this->default, $this->type, $this->byRef
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Builder/Property.php b/vendor/nikic/php-parser/lib/PHPParser/Builder/Property.php
    new file mode 100644
    index 0000000..806632c
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Builder/Property.php
    @@ -0,0 +1,92 @@
    +name = $name;
    +
    +        $this->type = 0;
    +        $this->default = null;
    +    }
    +
    +    /**
    +     * Makes the property public.
    +     *
    +     * @return PHPParser_Builder_Property The builder instance (for fluid interface)
    +     */
    +    public function makePublic() {
    +        $this->setModifier(PHPParser_Node_Stmt_Class::MODIFIER_PUBLIC);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the property protected.
    +     *
    +     * @return PHPParser_Builder_Property The builder instance (for fluid interface)
    +     */
    +    public function makeProtected() {
    +        $this->setModifier(PHPParser_Node_Stmt_Class::MODIFIER_PROTECTED);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the property private.
    +     *
    +     * @return PHPParser_Builder_Property The builder instance (for fluid interface)
    +     */
    +    public function makePrivate() {
    +        $this->setModifier(PHPParser_Node_Stmt_Class::MODIFIER_PRIVATE);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Makes the property static.
    +     *
    +     * @return PHPParser_Builder_Property The builder instance (for fluid interface)
    +     */
    +    public function makeStatic() {
    +        $this->setModifier(PHPParser_Node_Stmt_Class::MODIFIER_STATIC);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Sets default value for the property.
    +     *
    +     * @param mixed $value Default value to use
    +     *
    +     * @return PHPParser_Builder_Property The builder instance (for fluid interface)
    +     */
    +    public function setDefault($value) {
    +        $this->default = $this->normalizeValue($value);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Returns the built class node.
    +     *
    +     * @return PHPParser_Node_Stmt_Property The built property node
    +     */
    +    public function getNode() {
    +        return new PHPParser_Node_Stmt_Property(
    +            $this->type !== 0 ? $this->type : PHPParser_Node_Stmt_Class::MODIFIER_PUBLIC,
    +            array(
    +                new PHPParser_Node_Stmt_PropertyProperty($this->name, $this->default)
    +            )
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/BuilderAbstract.php b/vendor/nikic/php-parser/lib/PHPParser/BuilderAbstract.php
    new file mode 100644
    index 0000000..d17b060
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/BuilderAbstract.php
    @@ -0,0 +1,94 @@
    +getNode();
    +        } elseif ($node instanceof PHPParser_Node) {
    +            return $node;
    +        }
    +
    +        throw new LogicException('Expected node or builder object');
    +    }
    +
    +    /**
    +     * Normalizes a name: Converts plain string names to PHPParser_Node_Name.
    +     *
    +     * @param PHPParser_Node_Name|string $name The name to normalize
    +     *
    +     * @return PHPParser_Node_Name The normalized name
    +     */
    +    protected function normalizeName($name) {
    +        if ($name instanceof PHPParser_Node_Name) {
    +            return $name;
    +        } else {
    +            return new PHPParser_Node_Name($name);
    +        }
    +    }
    +
    +    /**
    +     * Normalizes a value: Converts nulls, booleans, integers,
    +     * floats, strings and arrays into their respective nodes
    +     *
    +     * @param mixed $value The value to normalize
    +     *
    +     * @return PHPParser_Node_Expr The normalized value
    +     */
    +    protected function normalizeValue($value) {
    +        if ($value instanceof PHPParser_Node) {
    +            return $value;
    +        } elseif (is_null($value)) {
    +            return new PHPParser_Node_Expr_ConstFetch(
    +                new PHPParser_Node_Name('null')
    +            );
    +        } elseif (is_bool($value)) {
    +            return new PHPParser_Node_Expr_ConstFetch(
    +                new PHPParser_Node_Name($value ? 'true' : 'false')
    +            );
    +        } elseif (is_int($value)) {
    +            return new PHPParser_Node_Scalar_LNumber($value);
    +        } elseif (is_float($value)) {
    +            return new PHPParser_Node_Scalar_DNumber($value);
    +        } elseif (is_string($value)) {
    +            return new PHPParser_Node_Scalar_String($value);
    +        } elseif (is_array($value)) {
    +            $items = array();
    +            $lastKey = -1;
    +            foreach ($value as $itemKey => $itemValue) {
    +                // for consecutive, numeric keys don't generate keys
    +                if (null !== $lastKey && ++$lastKey === $itemKey) {
    +                    $items[] = new PHPParser_Node_Expr_ArrayItem(
    +                        $this->normalizeValue($itemValue)
    +                    );
    +                } else {
    +                    $lastKey = null;
    +                    $items[] = new PHPParser_Node_Expr_ArrayItem(
    +                        $this->normalizeValue($itemValue),
    +                        $this->normalizeValue($itemKey)
    +                    );
    +                }
    +            }
    +
    +            return new PHPParser_Node_Expr_Array($items);
    +        } else {
    +            throw new LogicException('Invalid value');
    +        }
    +    }
    +
    +    /**
    +     * Sets a modifier in the $this->type property.
    +     *
    +     * @param int $modifier Modifier to set
    +     */
    +    protected function setModifier($modifier) {
    +        PHPParser_Node_Stmt_Class::verifyModifier($this->type, $modifier);
    +        $this->type |= $modifier;
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/BuilderFactory.php b/vendor/nikic/php-parser/lib/PHPParser/BuilderFactory.php
    new file mode 100644
    index 0000000..48941dc
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/BuilderFactory.php
    @@ -0,0 +1,87 @@
    +text = $text;
    +        $this->line = $line;
    +    }
    +
    +    /**
    +     * Gets the comment text.
    +     *
    +     * @return string The comment text (including comment delimiters like /*)
    +     */
    +    public function getText() {
    +        return $this->text;
    +    }
    +
    +    /**
    +     * Sets the comment text.
    +     *
    +     * @param string $text The comment text (including comment delimiters like /*)
    +     */
    +    public function setText($text) {
    +        $this->text = $text;
    +    }
    +
    +    /**
    +     * Gets the line number the comment started on.
    +     *
    +     * @return int Line number
    +     */
    +    public function getLine() {
    +        return $this->line;
    +    }
    +
    +    /**
    +     * Sets the line number the comment started on.
    +     *
    +     * @param int $line Line number
    +     */
    +    public function setLine($line) {
    +        $this->line = $line;
    +    }
    +
    +    /**
    +     * Gets the comment text.
    +     *
    +     * @return string The comment text (including comment delimiters like /*)
    +     */
    +    public function __toString() {
    +        return $this->text;
    +    }
    +
    +    /**
    +     * Gets the reformatted comment text.
    +     *
    +     * "Reformatted" here means that we try to clean up the whitespace at the
    +     * starts of the lines. This is necessary because we receive the comments
    +     * without trailing whitespace on the first line, but with trailing whitespace
    +     * on all subsequent lines.
    +     *
    +     * @return mixed|string
    +     */
    +    public function getReformattedText() {
    +        $text = trim($this->text);
    +        if (false === strpos($text, "\n")) {
    +            // Single line comments don't need further processing
    +            return $text;
    +        } elseif (preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\R\s+\*.*)+$)', $text)) {
    +            // Multi line comment of the type
    +            //
    +            //     /*
    +            //      * Some text.
    +            //      * Some more text.
    +            //      */
    +            //
    +            // is handled by replacing the whitespace sequences before the * by a single space
    +            return preg_replace('(^\s+\*)m', ' *', $this->text);
    +        } elseif (preg_match('(^/\*\*?\s*[\r\n])', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) {
    +            // Multi line comment of the type
    +            //
    +            //    /*
    +            //        Some text.
    +            //        Some more text.
    +            //    */
    +            //
    +            // is handled by removing the whitespace sequence on the line before the closing
    +            // */ on all lines. So if the last line is "    */", then "    " is removed at the
    +            // start of all lines.
    +            return preg_replace('(^' . preg_quote($matches[1]) . ')m', '', $text);
    +        } elseif (preg_match('(^/\*\*?\s*(?!\s))', $text, $matches)) {
    +            // Multi line comment of the type
    +            //
    +            //     /* Some text.
    +            //        Some more text.
    +            //        Even more text. */
    +            //
    +            // is handled by taking the length of the "/* " segment and leaving only that
    +            // many space characters before the lines. Thus in the above example only three
    +            // space characters are left at the start of every line.
    +            return preg_replace('(^\s*(?= {' . strlen($matches[0]) . '}(?!\s)))m', '', $text);
    +        }
    +
    +        // No idea how to format this comment, so simply return as is
    +        return $text;
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Comment/Doc.php b/vendor/nikic/php-parser/lib/PHPParser/Comment/Doc.php
    new file mode 100644
    index 0000000..95e4bc9
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Comment/Doc.php
    @@ -0,0 +1,5 @@
    +rawMessage = (string) $message;
    +        $this->rawLine    = (int) $line;
    +        $this->updateMessage();
    +    }
    +
    +    /**
    +     * Gets the error message
    +     *
    +     * @return string Error message
    +     */
    +    public function getRawMessage() {
    +        return $this->rawMessage;
    +    }
    +
    +    /**
    +     * Sets the line of the PHP file the error occurred in.
    +     *
    +     * @param string $message Error message
    +     */
    +    public function setRawMessage($message) {
    +        $this->rawMessage = (string) $message;
    +        $this->updateMessage();
    +    }
    +
    +    /**
    +     * Gets the error line in the PHP file.
    +     *
    +     * @return int Error line in the PHP file
    +     */
    +    public function getRawLine() {
    +        return $this->rawLine;
    +    }
    +
    +    /**
    +     * Sets the line of the PHP file the error occurred in.
    +     *
    +     * @param int $line Error line in the PHP file
    +     */
    +    public function setRawLine($line) {
    +        $this->rawLine = (int) $line;
    +        $this->updateMessage();
    +    }
    +
    +    /**
    +     * Updates the exception message after a change to rawMessage or rawLine.
    +     */
    +    protected function updateMessage() {
    +        $this->message = $this->rawMessage;
    +
    +        if (-1 === $this->rawLine) {
    +            $this->message .= ' on unknown line';
    +        } else {
    +            $this->message .= ' on line ' . $this->rawLine;
    +        }
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Lexer.php b/vendor/nikic/php-parser/lib/PHPParser/Lexer.php
    new file mode 100644
    index 0000000..0ca5d49
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Lexer.php
    @@ -0,0 +1,191 @@
    +tokenMap = $this->createTokenMap();
    +
    +        // map of tokens to drop while lexing (the map is only used for isset lookup,
    +        // that's why the value is simply set to 1; the value is never actually used.)
    +        $this->dropTokens = array_fill_keys(array(T_WHITESPACE, T_OPEN_TAG), 1);
    +    }
    +
    +    /**
    +     * Initializes the lexer for lexing the provided source code.
    +     *
    +     * @param string $code The source code to lex
    +     *
    +     * @throws PHPParser_Error on lexing errors (unterminated comment or unexpected character)
    +     */
    +    public function startLexing($code) {
    +        $this->resetErrors();
    +        $this->tokens = @token_get_all($code);
    +        $this->handleErrors();
    +
    +        $this->code = $code; // keep the code around for __halt_compiler() handling
    +        $this->pos  = -1;
    +        $this->line =  1;
    +    }
    +
    +    protected function resetErrors() {
    +        // clear error_get_last() by forcing an undefined variable error
    +        @$undefinedVariable;
    +    }
    +
    +    protected function handleErrors() {
    +        $error = error_get_last();
    +
    +        if (preg_match(
    +            '~^Unterminated comment starting line ([0-9]+)$~',
    +            $error['message'], $matches
    +        )) {
    +            throw new PHPParser_Error('Unterminated comment', $matches[1]);
    +        }
    +
    +        if (preg_match(
    +            '~^Unexpected character in input:  \'(.)\' \(ASCII=([0-9]+)\)~s',
    +            $error['message'], $matches
    +        )) {
    +            throw new PHPParser_Error(sprintf(
    +                'Unexpected character "%s" (ASCII %d)',
    +                $matches[1], $matches[2]
    +            ));
    +        }
    +
    +        // PHP cuts error message after null byte, so need special case
    +        if (preg_match('~^Unexpected character in input:  \'$~', $error['message'])) {
    +            throw new PHPParser_Error('Unexpected null byte');
    +        }
    +    }
    +
    +    /**
    +     * Fetches the next token.
    +     *
    +     * @param mixed $value           Variable to store token content in
    +     * @param mixed $startAttributes Variable to store start attributes in
    +     * @param mixed $endAttributes   Variable to store end attributes in
    +     *
    +     * @return int Token id
    +     */
    +    public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
    +        $startAttributes = array();
    +        $endAttributes   = array();
    +
    +        while (isset($this->tokens[++$this->pos])) {
    +            $token = $this->tokens[$this->pos];
    +
    +            if (is_string($token)) {
    +                $startAttributes['startLine'] = $this->line;
    +                $endAttributes['endLine']     = $this->line;
    +
    +                // bug in token_get_all
    +                if ('b"' === $token) {
    +                    $value = 'b"';
    +                    return ord('"');
    +                } else {
    +                    $value = $token;
    +                    return ord($token);
    +                }
    +            } else {
    +                $this->line += substr_count($token[1], "\n");
    +
    +                if (T_COMMENT === $token[0]) {
    +                    $startAttributes['comments'][] = new PHPParser_Comment($token[1], $token[2]);
    +                } elseif (T_DOC_COMMENT === $token[0]) {
    +                    $startAttributes['comments'][] = new PHPParser_Comment_Doc($token[1], $token[2]);
    +                } elseif (!isset($this->dropTokens[$token[0]])) {
    +                    $value = $token[1];
    +                    $startAttributes['startLine'] = $token[2];
    +                    $endAttributes['endLine']     = $this->line;
    +
    +                    return $this->tokenMap[$token[0]];
    +                }
    +            }
    +        }
    +
    +        $startAttributes['startLine'] = $this->line;
    +
    +        // 0 is the EOF token
    +        return 0;
    +    }
    +
    +    /**
    +     * Handles __halt_compiler() by returning the text after it.
    +     *
    +     * @return string Remaining text
    +     */
    +    public function handleHaltCompiler() {
    +        // get the length of the text before the T_HALT_COMPILER token
    +        $textBefore = '';
    +        for ($i = 0; $i <= $this->pos; ++$i) {
    +            if (is_string($this->tokens[$i])) {
    +                $textBefore .= $this->tokens[$i];
    +            } else {
    +                $textBefore .= $this->tokens[$i][1];
    +            }
    +        }
    +
    +        // text after T_HALT_COMPILER, still including ();
    +        $textAfter = substr($this->code, strlen($textBefore));
    +
    +        // ensure that it is followed by ();
    +        // this simplifies the situation, by not allowing any comments
    +        // in between of the tokens.
    +        if (!preg_match('~\s*\(\s*\)\s*(?:;|\?>\r?\n?)~', $textAfter, $matches)) {
    +            throw new PHPParser_Error('__halt_compiler must be followed by "();"');
    +        }
    +
    +        // prevent the lexer from returning any further tokens
    +        $this->pos = count($this->tokens);
    +
    +        // return with (); removed
    +        return (string) substr($textAfter, strlen($matches[0])); // (string) converts false to ''
    +    }
    +
    +    /**
    +     * Creates the token map.
    +     *
    +     * The token map maps the PHP internal token identifiers
    +     * to the identifiers used by the Parser. Additionally it
    +     * maps T_OPEN_TAG_WITH_ECHO to T_ECHO and T_CLOSE_TAG to ';'.
    +     *
    +     * @return array The token map
    +     */
    +    protected function createTokenMap() {
    +        $tokenMap = array();
    +
    +        // 256 is the minimum possible token number, as everything below
    +        // it is an ASCII value
    +        for ($i = 256; $i < 1000; ++$i) {
    +            // T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM
    +            if (T_DOUBLE_COLON === $i) {
    +                $tokenMap[$i] = PHPParser_Parser::T_PAAMAYIM_NEKUDOTAYIM;
    +            // T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO
    +            } elseif(T_OPEN_TAG_WITH_ECHO === $i) {
    +                $tokenMap[$i] = PHPParser_Parser::T_ECHO;
    +            // T_CLOSE_TAG is equivalent to ';'
    +            } elseif(T_CLOSE_TAG === $i) {
    +                $tokenMap[$i] = ord(';');
    +            // and the others can be mapped directly
    +            } elseif ('UNKNOWN' !== ($name = token_name($i))
    +                      && defined($name = 'PHPParser_Parser::' . $name)
    +            ) {
    +                $tokenMap[$i] = constant($name);
    +            }
    +        }
    +
    +        return $tokenMap;
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Lexer/Emulative.php b/vendor/nikic/php-parser/lib/PHPParser/Lexer/Emulative.php
    new file mode 100644
    index 0000000..1c74ef0
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Lexer/Emulative.php
    @@ -0,0 +1,200 @@
    + array(
    +                'finally'       => PHPParser_Parser::T_FINALLY,
    +                'yield'         => PHPParser_Parser::T_YIELD,
    +            ),
    +            '5.4.0-dev' => array(
    +                'callable'      => PHPParser_Parser::T_CALLABLE,
    +                'insteadof'     => PHPParser_Parser::T_INSTEADOF,
    +                'trait'         => PHPParser_Parser::T_TRAIT,
    +                '__trait__'     => PHPParser_Parser::T_TRAIT_C,
    +            ),
    +            '5.3.0-dev' => array(
    +                '__dir__'       => PHPParser_Parser::T_DIR,
    +                'goto'          => PHPParser_Parser::T_GOTO,
    +                'namespace'     => PHPParser_Parser::T_NAMESPACE,
    +                '__namespace__' => PHPParser_Parser::T_NS_C,
    +            ),
    +        );
    +
    +        $this->newKeywords = array();
    +        foreach ($newKeywordsPerVersion as $version => $newKeywords) {
    +            if (version_compare(PHP_VERSION, $version, '>=')) {
    +                break;
    +            }
    +
    +            $this->newKeywords += $newKeywords;
    +        }
    +    }
    +
    +    public function startLexing($code) {
    +        $this->inObjectAccess = false;
    +
    +        // on PHP 5.4 don't do anything
    +        if (version_compare(PHP_VERSION, '5.4.0RC1', '>=')) {
    +            parent::startLexing($code);
    +        } else {
    +            $code = $this->preprocessCode($code);
    +            parent::startLexing($code);
    +            $this->postprocessTokens();
    +        }
    +    }
    +
    +    /*
    +     * Replaces new features in the code by ~__EMU__{NAME}__{DATA}__~ sequences.
    +     * ~LABEL~ is never valid PHP code, that's why we can (to some degree) safely
    +     * use it here.
    +     * Later when preprocessing the tokens these sequences will either be replaced
    +     * by real tokens or replaced with their original content (e.g. if they occured
    +     * inside a string, i.e. a place where they don't have a special meaning).
    +     */
    +    protected function preprocessCode($code) {
    +        // binary notation (0b010101101001...)
    +        $code = preg_replace('(\b0b[01]+\b)', '~__EMU__BINARY__$0__~', $code);
    +
    +        if (version_compare(PHP_VERSION, '5.3.0', '<')) {
    +            // namespace separator (backslash not followed by some special characters,
    +            // which are not valid after a NS separator, but would cause problems with
    +            // escape sequence parsing if one would replace the backslash there)
    +            $code = preg_replace('(\\\\(?!["\'`${\\\\]))', '~__EMU__NS__~', $code);
    +
    +            // nowdoc (<<<'ABC'\ncontent\nABC;)
    +            $code = preg_replace_callback(
    +                '((*BSR_ANYCRLF)        # set \R to (?>\r\n|\r|\n)
    +                  (b?<<<[\t ]*\'([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\'\R) # opening token
    +                  ((?:(?!\2;?\R).*\R)*) # content
    +                  (\2)                  # closing token
    +                  (?=;?\R)              # must be followed by newline (with optional semicolon)
    +                 )x',
    +                array($this, 'encodeNowdocCallback'),
    +                $code
    +            );
    +        }
    +
    +        return $code;
    +    }
    +
    +    /*
    +     * As nowdocs can have arbitrary content but LABELs can only contain a certain
    +     * range of characters, the nowdoc content is encoded as hex and separated by
    +     * 'x' tokens. So the result of the encoding will look like this:
    +     * ~__EMU__NOWDOC__{HEX(START_TOKEN)}x{HEX(CONTENT)}x{HEX(END_TOKEN)}~
    +     */
    +    public function encodeNowdocCallback(array $matches) {
    +        return '~__EMU__NOWDOC__'
    +                . bin2hex($matches[1]) . 'x' . bin2hex($matches[3]) . 'x' . bin2hex($matches[4])
    +                . '__~';
    +    }
    +
    +    /*
    +     * Replaces the ~__EMU__...~ sequences with real tokens or their original
    +     * value.
    +     */
    +    protected function postprocessTokens() {
    +        // we need to manually iterate and manage a count because we'll change
    +        // the tokens array on the way
    +        for ($i = 0, $c = count($this->tokens); $i < $c; ++$i) {
    +            // first check that the following tokens are form ~LABEL~,
    +            // then match the __EMU__... sequence.
    +            if ('~' === $this->tokens[$i]
    +                && isset($this->tokens[$i + 2])
    +                && '~' === $this->tokens[$i + 2]
    +                && T_STRING === $this->tokens[$i + 1][0]
    +                && preg_match('(^__EMU__([A-Z]++)__(?:([A-Za-z0-9]++)__)?$)', $this->tokens[$i + 1][1], $matches)
    +            ) {
    +                if ('BINARY' === $matches[1]) {
    +                    // the binary number can either be an integer or a double, so return a LNUMBER
    +                    // or DNUMBER respectively
    +                    $replace = array(
    +                        array(is_int(bindec($matches[2])) ? T_LNUMBER : T_DNUMBER, $matches[2], $this->tokens[$i + 1][2])
    +                    );
    +                } elseif ('NS' === $matches[1]) {
    +                    // a \ single char token is returned here and replaced by a
    +                    // PHPParser_Parser::T_NS_SEPARATOR token in ->getNextToken(). This hacks around
    +                    // the limitations arising from T_NS_SEPARATOR not being defined on 5.3
    +                    $replace = array('\\');
    +                } elseif ('NOWDOC' === $matches[1]) {
    +                    // decode the encoded nowdoc payload; pack('H*' is bin2hex( for 5.3
    +                    list($start, $content, $end) = explode('x', $matches[2]);
    +                    list($start, $content, $end) = array(pack('H*', $start), pack('H*', $content), pack('H*', $end));
    +
    +                    $replace = array();
    +                    $replace[] = array(T_START_HEREDOC, $start, $this->tokens[$i + 1][2]);
    +                    if ('' !== $content) {
    +                        $replace[] = array(T_ENCAPSED_AND_WHITESPACE, $content, -1);
    +                    }
    +                    $replace[] = array(T_END_HEREDOC, $end, -1);
    +                } else {
    +                    // just ignore all other __EMU__ sequences
    +                    continue;
    +                }
    +
    +                array_splice($this->tokens, $i, 3, $replace);
    +                $c -= 3 - count($replace);
    +            // for multichar tokens (e.g. strings) replace any ~__EMU__...~ sequences
    +            // in their content with the original character sequence
    +            } elseif (is_array($this->tokens[$i])
    +                      && 0 !== strpos($this->tokens[$i][1], '__EMU__')
    +            ) {
    +                $this->tokens[$i][1] = preg_replace_callback(
    +                    '(~__EMU__([A-Z]++)__(?:([A-Za-z0-9]++)__)?~)',
    +                    array($this, 'restoreContentCallback'),
    +                    $this->tokens[$i][1]
    +                );
    +            }
    +        }
    +    }
    +
    +    /*
    +     * This method is a callback for restoring EMU sequences in
    +     * multichar tokens (like strings) to their original value.
    +     */
    +    public function restoreContentCallback(array $matches) {
    +        if ('BINARY' === $matches[1]) {
    +            return $matches[2];
    +        } elseif ('NS' === $matches[1]) {
    +            return '\\';
    +        } elseif ('NOWDOC' === $matches[1]) {
    +            list($start, $content, $end) = explode('x', $matches[2]);
    +            return pack('H*', $start) . pack('H*', $content) . pack('H*', $end);
    +        } else {
    +            return $matches[0];
    +        }
    +    }
    +
    +    public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) {
    +        $token = parent::getNextToken($value, $startAttributes, $endAttributes);
    +
    +        // replace new keywords by their respective tokens. This is not done
    +        // if we currently are in an object access (e.g. in $obj->namespace
    +        // "namespace" stays a T_STRING tokens and isn't converted to T_NAMESPACE)
    +        if (PHPParser_Parser::T_STRING === $token && !$this->inObjectAccess) {
    +            if (isset($this->newKeywords[strtolower($value)])) {
    +                return $this->newKeywords[strtolower($value)];
    +            }
    +        // backslashes are replaced by T_NS_SEPARATOR tokens
    +        } elseif (92 === $token) { // ord('\\')
    +            return PHPParser_Parser::T_NS_SEPARATOR;
    +        // keep track of whether we currently are in an object access (after ->)
    +        } elseif (PHPParser_Parser::T_OBJECT_OPERATOR === $token) {
    +            $this->inObjectAccess = true;
    +        } else {
    +            $this->inObjectAccess = false;
    +        }
    +
    +        return $token;
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node.php b/vendor/nikic/php-parser/lib/PHPParser/Node.php
    new file mode 100644
    index 0000000..c47d49d
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node.php
    @@ -0,0 +1,75 @@
    + $value,
    +                'byRef' => $byRef
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Const.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Const.php
    new file mode 100644
    index 0000000..e4e7794
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Const.php
    @@ -0,0 +1,25 @@
    + $name,
    +                'value' => $value,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr.php
    new file mode 100644
    index 0000000..293dab3
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr.php
    @@ -0,0 +1,5 @@
    + $items
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ArrayDimFetch.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ArrayDimFetch.php
    new file mode 100644
    index 0000000..f7d8628
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ArrayDimFetch.php
    @@ -0,0 +1,25 @@
    + $var,
    +                'dim' => $dim
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ArrayItem.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ArrayItem.php
    new file mode 100644
    index 0000000..f3c42ba
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ArrayItem.php
    @@ -0,0 +1,28 @@
    + $key,
    +                'value' => $value,
    +                'byRef' => $byRef
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Assign.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Assign.php
    new file mode 100644
    index 0000000..1619425
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Assign.php
    @@ -0,0 +1,25 @@
    + $var,
    +                'expr' => $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignBitwiseAnd.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignBitwiseAnd.php
    new file mode 100644
    index 0000000..013f1a8
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignBitwiseAnd.php
    @@ -0,0 +1,25 @@
    + $var,
    +                'expr' => $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignBitwiseOr.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignBitwiseOr.php
    new file mode 100644
    index 0000000..c5c4764
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignBitwiseOr.php
    @@ -0,0 +1,25 @@
    + $var,
    +                'expr' => $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignBitwiseXor.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignBitwiseXor.php
    new file mode 100644
    index 0000000..91ed068
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignBitwiseXor.php
    @@ -0,0 +1,25 @@
    + $var,
    +                'expr' => $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignConcat.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignConcat.php
    new file mode 100644
    index 0000000..3f634ae
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignConcat.php
    @@ -0,0 +1,25 @@
    + $var,
    +                'expr' => $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignDiv.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignDiv.php
    new file mode 100644
    index 0000000..7992a66
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignDiv.php
    @@ -0,0 +1,25 @@
    + $var,
    +                'expr' => $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignMinus.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignMinus.php
    new file mode 100644
    index 0000000..62f00b3
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignMinus.php
    @@ -0,0 +1,25 @@
    + $var,
    +                'expr' => $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignMod.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignMod.php
    new file mode 100644
    index 0000000..98cbe75
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignMod.php
    @@ -0,0 +1,25 @@
    + $var,
    +                'expr' => $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignMul.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignMul.php
    new file mode 100644
    index 0000000..63bdae7
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignMul.php
    @@ -0,0 +1,25 @@
    + $var,
    +                'expr' => $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignPlus.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignPlus.php
    new file mode 100644
    index 0000000..99b866c
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignPlus.php
    @@ -0,0 +1,25 @@
    + $var,
    +                'expr' => $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignRef.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignRef.php
    new file mode 100644
    index 0000000..0bcf0b0
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignRef.php
    @@ -0,0 +1,25 @@
    + $var,
    +                'expr' => $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignShiftLeft.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignShiftLeft.php
    new file mode 100644
    index 0000000..f3ec3e5
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignShiftLeft.php
    @@ -0,0 +1,25 @@
    + $var,
    +                'expr' => $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignShiftRight.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignShiftRight.php
    new file mode 100644
    index 0000000..0b4743b
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/AssignShiftRight.php
    @@ -0,0 +1,25 @@
    + $var,
    +                'expr' => $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BitwiseAnd.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BitwiseAnd.php
    new file mode 100644
    index 0000000..fffac44
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BitwiseAnd.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BitwiseNot.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BitwiseNot.php
    new file mode 100644
    index 0000000..635d200
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BitwiseNot.php
    @@ -0,0 +1,22 @@
    + $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BitwiseOr.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BitwiseOr.php
    new file mode 100644
    index 0000000..cebd70d
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BitwiseOr.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BitwiseXor.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BitwiseXor.php
    new file mode 100644
    index 0000000..742ca82
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BitwiseXor.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BooleanAnd.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BooleanAnd.php
    new file mode 100644
    index 0000000..fd7e499
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BooleanAnd.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BooleanNot.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BooleanNot.php
    new file mode 100644
    index 0000000..0a8a24c
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BooleanNot.php
    @@ -0,0 +1,22 @@
    + $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BooleanOr.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BooleanOr.php
    new file mode 100644
    index 0000000..cd03851
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/BooleanOr.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Cast.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Cast.php
    new file mode 100644
    index 0000000..562cccc
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Cast.php
    @@ -0,0 +1,22 @@
    + $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Cast/Array.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Cast/Array.php
    new file mode 100644
    index 0000000..e712d49
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Cast/Array.php
    @@ -0,0 +1,5 @@
    + $class,
    +                'name'  => $name
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Clone.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Clone.php
    new file mode 100644
    index 0000000..1d9d023
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Clone.php
    @@ -0,0 +1,22 @@
    + $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Closure.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Closure.php
    new file mode 100644
    index 0000000..536268d
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Closure.php
    @@ -0,0 +1,35 @@
    + array(): Statements
    +     *                          'params' => array(): Parameters
    +     *                          'uses'   => array(): use()s
    +     *                          'byRef'  => false  : Whether to return by reference
    +     *                          'static' => false  : Whether the closure is static
    +     * @param array $attributes Additional attributes
    +     */
    +    public function __construct(array $subNodes = array(), array $attributes = array()) {
    +        parent::__construct(
    +            $subNodes + array(
    +                'stmts'  => array(),
    +                'params' => array(),
    +                'uses'   => array(),
    +                'byRef'  => false,
    +                'static' => false,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ClosureUse.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ClosureUse.php
    new file mode 100644
    index 0000000..f10b3a7
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ClosureUse.php
    @@ -0,0 +1,25 @@
    + $var,
    +                'byRef' => $byRef
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Concat.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Concat.php
    new file mode 100644
    index 0000000..724cb6b
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Concat.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ConstFetch.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ConstFetch.php
    new file mode 100644
    index 0000000..8a21884
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ConstFetch.php
    @@ -0,0 +1,22 @@
    + $name
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Div.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Div.php
    new file mode 100644
    index 0000000..caa5d2c
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Div.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Empty.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Empty.php
    new file mode 100644
    index 0000000..fb55505
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Empty.php
    @@ -0,0 +1,22 @@
    + $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Equal.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Equal.php
    new file mode 100644
    index 0000000..64861c1
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Equal.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ErrorSuppress.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ErrorSuppress.php
    new file mode 100644
    index 0000000..7222529
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ErrorSuppress.php
    @@ -0,0 +1,22 @@
    + $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Eval.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Eval.php
    new file mode 100644
    index 0000000..0b607b0
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Eval.php
    @@ -0,0 +1,22 @@
    + $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Exit.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Exit.php
    new file mode 100644
    index 0000000..6870b44
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Exit.php
    @@ -0,0 +1,22 @@
    + $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/FuncCall.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/FuncCall.php
    new file mode 100644
    index 0000000..8f85df1
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/FuncCall.php
    @@ -0,0 +1,25 @@
    + $name,
    +                'args' => $args
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Greater.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Greater.php
    new file mode 100644
    index 0000000..2ff3b94
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Greater.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/GreaterOrEqual.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/GreaterOrEqual.php
    new file mode 100644
    index 0000000..015106d
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/GreaterOrEqual.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Identical.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Identical.php
    new file mode 100644
    index 0000000..1f2ac01
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Identical.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Include.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Include.php
    new file mode 100644
    index 0000000..040de25
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Include.php
    @@ -0,0 +1,30 @@
    + $expr,
    +                'type' => $type
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Instanceof.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Instanceof.php
    new file mode 100644
    index 0000000..95da70c
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Instanceof.php
    @@ -0,0 +1,25 @@
    + $expr,
    +                'class' => $class
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Isset.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Isset.php
    new file mode 100644
    index 0000000..b831dad
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Isset.php
    @@ -0,0 +1,22 @@
    + $vars
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/List.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/List.php
    new file mode 100644
    index 0000000..8c491c9
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/List.php
    @@ -0,0 +1,22 @@
    + $vars,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/LogicalAnd.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/LogicalAnd.php
    new file mode 100644
    index 0000000..98609c8
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/LogicalAnd.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/LogicalOr.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/LogicalOr.php
    new file mode 100644
    index 0000000..f472644
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/LogicalOr.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/LogicalXor.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/LogicalXor.php
    new file mode 100644
    index 0000000..16c24ff
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/LogicalXor.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/MethodCall.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/MethodCall.php
    new file mode 100644
    index 0000000..89187e2
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/MethodCall.php
    @@ -0,0 +1,28 @@
    + $var,
    +                'name' => $name,
    +                'args' => $args
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Minus.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Minus.php
    new file mode 100644
    index 0000000..b9c4f46
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Minus.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Mod.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Mod.php
    new file mode 100644
    index 0000000..f3d706f
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Mod.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Mul.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Mul.php
    new file mode 100644
    index 0000000..007c4be
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Mul.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/New.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/New.php
    new file mode 100644
    index 0000000..5faaadc
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/New.php
    @@ -0,0 +1,25 @@
    + $class,
    +                'args'  => $args
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/NotEqual.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/NotEqual.php
    new file mode 100644
    index 0000000..5e6b1be
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/NotEqual.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/NotIdentical.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/NotIdentical.php
    new file mode 100644
    index 0000000..4756cff
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/NotIdentical.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Plus.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Plus.php
    new file mode 100644
    index 0000000..a9ea336
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Plus.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/PostDec.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/PostDec.php
    new file mode 100644
    index 0000000..2fdcfad
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/PostDec.php
    @@ -0,0 +1,22 @@
    + $var
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/PostInc.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/PostInc.php
    new file mode 100644
    index 0000000..93968ff
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/PostInc.php
    @@ -0,0 +1,22 @@
    + $var
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/PreDec.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/PreDec.php
    new file mode 100644
    index 0000000..fdb3c9e
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/PreDec.php
    @@ -0,0 +1,22 @@
    + $var
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/PreInc.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/PreInc.php
    new file mode 100644
    index 0000000..0074966
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/PreInc.php
    @@ -0,0 +1,22 @@
    + $var
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Print.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Print.php
    new file mode 100644
    index 0000000..04eb120
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Print.php
    @@ -0,0 +1,22 @@
    + $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/PropertyFetch.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/PropertyFetch.php
    new file mode 100644
    index 0000000..f77756e
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/PropertyFetch.php
    @@ -0,0 +1,25 @@
    + $var,
    +                'name' => $name
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ShellExec.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ShellExec.php
    new file mode 100644
    index 0000000..86fd2e2
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ShellExec.php
    @@ -0,0 +1,22 @@
    + $parts
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ShiftLeft.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ShiftLeft.php
    new file mode 100644
    index 0000000..8953056
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ShiftLeft.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ShiftRight.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ShiftRight.php
    new file mode 100644
    index 0000000..f5079d4
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/ShiftRight.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Smaller.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Smaller.php
    new file mode 100644
    index 0000000..9f6c862
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Smaller.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/SmallerOrEqual.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/SmallerOrEqual.php
    new file mode 100644
    index 0000000..01741bb
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/SmallerOrEqual.php
    @@ -0,0 +1,25 @@
    + $left,
    +                'right' => $right
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/StaticCall.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/StaticCall.php
    new file mode 100644
    index 0000000..68b8415
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/StaticCall.php
    @@ -0,0 +1,28 @@
    + $class,
    +                'name'  => $name,
    +                'args'  => $args
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/StaticPropertyFetch.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/StaticPropertyFetch.php
    new file mode 100644
    index 0000000..0e7826d
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/StaticPropertyFetch.php
    @@ -0,0 +1,25 @@
    + $class,
    +                'name'  => $name
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Ternary.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Ternary.php
    new file mode 100644
    index 0000000..6b25c34
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Ternary.php
    @@ -0,0 +1,28 @@
    + $cond,
    +                'if'   => $if,
    +                'else' => $else
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/UnaryMinus.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/UnaryMinus.php
    new file mode 100644
    index 0000000..1bf6edd
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/UnaryMinus.php
    @@ -0,0 +1,22 @@
    + $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/UnaryPlus.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/UnaryPlus.php
    new file mode 100644
    index 0000000..588a9e9
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/UnaryPlus.php
    @@ -0,0 +1,22 @@
    + $expr
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Variable.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Variable.php
    new file mode 100644
    index 0000000..8e3ac26
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Variable.php
    @@ -0,0 +1,22 @@
    + $name
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Yield.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Yield.php
    new file mode 100644
    index 0000000..051ab3b
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Expr/Yield.php
    @@ -0,0 +1,25 @@
    + $key,
    +                'value' => $value,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Name.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Name.php
    new file mode 100644
    index 0000000..d553632
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Name.php
    @@ -0,0 +1,168 @@
    + $parts,
    +            ),
    +            $attributes
    +        );
    +    }
    +
    +    /**
    +     * Gets the first part of the name, i.e. everything before the first namespace separator.
    +     *
    +     * @return string First part of the name
    +     */
    +    public function getFirst() {
    +        return $this->parts[0];
    +    }
    +
    +    /**
    +     * Gets the last part of the name, i.e. everything after the last namespace separator.
    +     *
    +     * @return string Last part of the name
    +     */
    +    public function getLast() {
    +        return $this->parts[count($this->parts) - 1];
    +    }
    +
    +    /**
    +     * Checks whether the name is unqualified. (E.g. Name)
    +     *
    +     * @return bool Whether the name is unqualified
    +     */
    +    public function isUnqualified() {
    +        return 1 == count($this->parts);
    +    }
    +
    +    /**
    +     * Checks whether the name is qualified. (E.g. Name\Name)
    +     *
    +     * @return bool Whether the name is qualified
    +     */
    +    public function isQualified() {
    +        return 1 < count($this->parts);
    +    }
    +
    +    /**
    +     * Checks whether the name is fully qualified. (E.g. \Name)
    +     *
    +     * @return bool Whether the name is fully qualified
    +     */
    +    public function isFullyQualified() {
    +        return false;
    +    }
    +
    +    /**
    +     * Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name)
    +     *
    +     * @return bool Whether the name is relative
    +     */
    +    public function isRelative() {
    +        return false;
    +    }
    +
    +    /**
    +     * Returns a string representation of the name by imploding the namespace parts with a separator.
    +     *
    +     * @param string $separator The separator to use (defaults to the namespace separator \)
    +     *
    +     * @return string String representation
    +     */
    +    public function toString($separator = '\\') {
    +        return implode($separator, $this->parts);
    +    }
    +
    +    /**
    +     * Returns a string representation of the name by imploding the namespace parts with the
    +     * namespace separator.
    +     *
    +     * @return string String representation
    +     */
    +    public function __toString() {
    +        return implode('\\', $this->parts);
    +    }
    +
    +    /**
    +     * Sets the whole name.
    +     *
    +     * @param string|array|self $name The name to set the whole name to
    +     */
    +    public function set($name) {
    +        $this->parts = $this->prepareName($name);
    +    }
    +
    +    /**
    +     * Prepends a name to this name.
    +     *
    +     * @param string|array|self $name Name to prepend
    +     */
    +    public function prepend($name) {
    +        $this->parts = array_merge($this->prepareName($name), $this->parts);
    +    }
    +
    +    /**
    +     * Appends a name to this name.
    +     *
    +     * @param string|array|self $name Name to append
    +     */
    +    public function append($name) {
    +        $this->parts = array_merge($this->parts, $this->prepareName($name));
    +    }
    +
    +    /**
    +     * Sets the first part of the name.
    +     *
    +     * @param string|array|self $name The name to set the first part to
    +     */
    +    public function setFirst($name) {
    +        array_splice($this->parts, 0, 1, $this->prepareName($name));
    +    }
    +
    +    /**
    +     * Sets the last part of the name.
    +     *
    +     * @param string|array|self $name The name to set the last part to
    +     */
    +    public function setLast($name) {
    +        array_splice($this->parts, -1, 1, $this->prepareName($name));
    +    }
    +
    +    /**
    +     * Prepares a (string, array or Name node) name for use in name changing methods by converting
    +     * it to an array.
    +     *
    +     * @param string|array|self $name Name to prepare
    +     *
    +     * @return array Prepared name
    +     */
    +    protected function prepareName($name) {
    +        if (is_string($name)) {
    +            return explode('\\', $name);
    +        } elseif (is_array($name)) {
    +            return $name;
    +        } elseif ($name instanceof self) {
    +            return $name->parts;
    +        }
    +
    +        throw new InvalidArgumentException(
    +            'When changing a name you need to pass either a string, an array or a Name node'
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Name/FullyQualified.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Name/FullyQualified.php
    new file mode 100644
    index 0000000..5531ad1
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Name/FullyQualified.php
    @@ -0,0 +1,40 @@
    + $name,
    +                'default' => $default,
    +                'type'    => $type,
    +                'byRef'   => $byRef
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Scalar.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Scalar.php
    new file mode 100644
    index 0000000..a2cfeb2
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Scalar.php
    @@ -0,0 +1,5 @@
    + $value
    +            ),
    +            $attributes
    +        );
    +    }
    +
    +    /**
    +     * Parses a DNUMBER token like PHP would.
    +     *
    +     * @param string $str A string number
    +     *
    +     * @return float The parsed number
    +     */
    +    public static function parse($str) {
    +        // if string contains any of .eE just cast it to float
    +        if (false !== strpbrk($str, '.eE')) {
    +            return (float) $str;
    +        }
    +
    +        // otherwise it's an integer notation that overflowed into a float
    +        // if it starts with 0 it's one of the special integer notations
    +        if ('0' === $str[0]) {
    +            // hex
    +            if ('x' === $str[1] || 'X' === $str[1]) {
    +                return hexdec($str);
    +            }
    +
    +            // bin
    +            if ('b' === $str[1] || 'B' === $str[1]) {
    +                return bindec($str);
    +            }
    +
    +            // oct
    +            // substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit (8 or 9)
    +            // so that only the digits before that are used
    +            return octdec(substr($str, 0, strcspn($str, '89')));
    +        }
    +
    +        // dec
    +        return (float) $str;
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Scalar/DirConst.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Scalar/DirConst.php
    new file mode 100644
    index 0000000..d3be11b
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Scalar/DirConst.php
    @@ -0,0 +1,13 @@
    + $parts
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Scalar/FileConst.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Scalar/FileConst.php
    new file mode 100644
    index 0000000..b0737f0
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Scalar/FileConst.php
    @@ -0,0 +1,13 @@
    + $value
    +            ),
    +            $attributes
    +        );
    +    }
    +
    +    /**
    +     * Parses an LNUMBER token (dec, hex, oct and bin notations) like PHP would.
    +     *
    +     * @param string $str A string number
    +     *
    +     * @return int The parsed number
    +     */
    +    public static function parse($str) {
    +        // handle plain 0 specially
    +        if ('0' === $str) {
    +            return 0;
    +        }
    +
    +        // if first char is 0 (and number isn't 0) it's a special syntax
    +        if ('0' === $str[0]) {
    +            // hex
    +            if ('x' === $str[1] || 'X' === $str[1]) {
    +                return hexdec($str);
    +            }
    +
    +            // bin
    +            if ('b' === $str[1] || 'B' === $str[1]) {
    +                return bindec($str);
    +            }
    +
    +            // oct (intval instead of octdec to get proper cutting behavior with malformed numbers)
    +            return intval($str, 8);
    +        }
    +
    +        // dec
    +        return (int) $str;
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Scalar/LineConst.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Scalar/LineConst.php
    new file mode 100644
    index 0000000..bde002d
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Scalar/LineConst.php
    @@ -0,0 +1,13 @@
    + '\\',
    +        '$'  =>  '$',
    +        'n'  => "\n",
    +        'r'  => "\r",
    +        't'  => "\t",
    +        'f'  => "\f",
    +        'v'  => "\v",
    +        'e'  => "\x1B",
    +    );
    +
    +    /**
    +     * Constructs a string scalar node.
    +     *
    +     * @param string $value      Value of the string
    +     * @param array  $attributes Additional attributes
    +     */
    +    public function __construct($value = '', array $attributes = array()) {
    +        parent::__construct(
    +            array(
    +                'value' => $value
    +            ),
    +            $attributes
    +        );
    +    }
    +
    +    /**
    +     * Parses a string token.
    +     *
    +     * @param string $str String token content
    +     *
    +     * @return string The parsed string
    +     */
    +    public static function parse($str) {
    +        $bLength = 0;
    +        if ('b' === $str[0]) {
    +            $bLength = 1;
    +        }
    +
    +        if ('\'' === $str[$bLength]) {
    +            return str_replace(
    +                array('\\\\', '\\\''),
    +                array(  '\\',   '\''),
    +                substr($str, $bLength + 1, -1)
    +            );
    +        } else {
    +            return self::parseEscapeSequences(substr($str, $bLength + 1, -1), '"');
    +        }
    +    }
    +
    +    /**
    +     * Parses escape sequences in strings (all string types apart from single quoted).
    +     *
    +     * @param string      $str   String without quotes
    +     * @param null|string $quote Quote type
    +     *
    +     * @return string String with escape sequences parsed
    +     */
    +    public static function parseEscapeSequences($str, $quote) {
    +        if (null !== $quote) {
    +            $str = str_replace('\\' . $quote, $quote, $str);
    +        }
    +
    +        return preg_replace_callback(
    +            '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3})~',
    +            array(__CLASS__, 'parseCallback'),
    +            $str
    +        );
    +    }
    +
    +    public static function parseCallback($matches) {
    +        $str = $matches[1];
    +
    +        if (isset(self::$replacements[$str])) {
    +            return self::$replacements[$str];
    +        } elseif ('x' === $str[0] || 'X' === $str[0]) {
    +            return chr(hexdec($str));
    +        } else {
    +            return chr(octdec($str));
    +        }
    +    }
    +
    +    /**
    +     * Parses a constant doc string.
    +     *
    +     * @param string $startToken Doc string start token content (<< $num,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Case.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Case.php
    new file mode 100644
    index 0000000..bf90c6e
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Case.php
    @@ -0,0 +1,25 @@
    + $cond,
    +                'stmts' => $stmts,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Catch.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Catch.php
    new file mode 100644
    index 0000000..336cf2d
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Catch.php
    @@ -0,0 +1,28 @@
    + $type,
    +                'var'   => $var,
    +                'stmts' => $stmts,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Class.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Class.php
    new file mode 100644
    index 0000000..2585d98
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Class.php
    @@ -0,0 +1,102 @@
    + true,
    +        'parent' => true,
    +        'static' => true,
    +    );
    +
    +    /**
    +     * Constructs a class node.
    +     *
    +     * @param string      $name       Name
    +     * @param array       $subNodes   Array of the following optional subnodes:
    +     *                                'type'       => 0      : Type
    +     *                                'extends'    => null   : Name of extended class
    +     *                                'implements' => array(): Names of implemented interfaces
    +     *                                'stmts'      => array(): Statements
    +     * @param array       $attributes Additional attributes
    +     */
    +    public function __construct($name, array $subNodes = array(), array $attributes = array()) {
    +        parent::__construct(
    +            $subNodes + array(
    +                'type'       => 0,
    +                'extends'    => null,
    +                'implements' => array(),
    +                'stmts'      => array(),
    +            ),
    +            $attributes
    +        );
    +        $this->name = $name;
    +
    +        if (isset(self::$specialNames[(string) $this->name])) {
    +            throw new PHPParser_Error(sprintf('Cannot use "%s" as class name as it is reserved', $this->name));
    +        }
    +
    +        if (isset(self::$specialNames[(string) $this->extends])) {
    +            throw new PHPParser_Error(sprintf('Cannot use "%s" as class name as it is reserved', $this->extends));
    +        }
    +
    +        foreach ($this->implements as $interface) {
    +            if (isset(self::$specialNames[(string) $interface])) {
    +                throw new PHPParser_Error(sprintf('Cannot use "%s" as interface name as it is reserved', $interface));
    +            }
    +        }
    +    }
    +
    +    public function isAbstract() {
    +        return (bool) ($this->type & self::MODIFIER_ABSTRACT);
    +    }
    +
    +    public function isFinal() {
    +        return (bool) ($this->type & self::MODIFIER_FINAL);
    +    }
    +
    +    public function getMethods() {
    +        $methods = array();
    +        foreach ($this->stmts as $stmt) {
    +            if ($stmt instanceof PHPParser_Node_Stmt_ClassMethod) {
    +                $methods[] = $stmt;
    +            }
    +        }
    +        return $methods;
    +    }
    +
    +    public static function verifyModifier($a, $b) {
    +        if ($a & 7 && $b & 7) {
    +            throw new PHPParser_Error('Multiple access type modifiers are not allowed');
    +        }
    +
    +        if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) {
    +            throw new PHPParser_Error('Multiple abstract modifiers are not allowed');
    +        }
    +
    +        if ($a & self::MODIFIER_STATIC && $b & self::MODIFIER_STATIC) {
    +            throw new PHPParser_Error('Multiple static modifiers are not allowed');
    +        }
    +
    +        if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) {
    +            throw new PHPParser_Error('Multiple final modifiers are not allowed');
    +        }
    +
    +        if ($a & 48 && $b & 48) {
    +            throw new PHPParser_Error('Cannot use the final and abstract modifier at the same time');
    +        }
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/ClassConst.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/ClassConst.php
    new file mode 100644
    index 0000000..6d0895c
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/ClassConst.php
    @@ -0,0 +1,22 @@
    + $consts,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/ClassMethod.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/ClassMethod.php
    new file mode 100644
    index 0000000..99c0c3d
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/ClassMethod.php
    @@ -0,0 +1,66 @@
    + MODIFIER_PUBLIC: Type
    +     *                                'byRef'  => false          : Whether to return by reference
    +     *                                'params' => array()        : Parameters
    +     *                                'stmts'  => array()        : Statements
    +     * @param array       $attributes Additional attributes
    +     */
    +    public function __construct($name, array $subNodes = array(), array $attributes = array()) {
    +        parent::__construct(
    +            $subNodes + array(
    +                'type'   => PHPParser_Node_Stmt_Class::MODIFIER_PUBLIC,
    +                'byRef'  => false,
    +                'params' => array(),
    +                'stmts'  => array(),
    +            ),
    +            $attributes
    +        );
    +        $this->name = $name;
    +
    +        if (($this->type & PHPParser_Node_Stmt_Class::MODIFIER_STATIC)
    +            && ('__construct' == $this->name || '__destruct' == $this->name || '__clone' == $this->name)
    +        ) {
    +            throw new PHPParser_Error(sprintf('"%s" method cannot be static', $this->name));
    +        }
    +    }
    +
    +    public function isPublic() {
    +        return (bool) ($this->type & PHPParser_Node_Stmt_Class::MODIFIER_PUBLIC);
    +    }
    +
    +    public function isProtected() {
    +        return (bool) ($this->type & PHPParser_Node_Stmt_Class::MODIFIER_PROTECTED);
    +    }
    +
    +    public function isPrivate() {
    +        return (bool) ($this->type & PHPParser_Node_Stmt_Class::MODIFIER_PRIVATE);
    +    }
    +
    +    public function isAbstract() {
    +        return (bool) ($this->type & PHPParser_Node_Stmt_Class::MODIFIER_ABSTRACT);
    +    }
    +
    +    public function isFinal() {
    +        return (bool) ($this->type & PHPParser_Node_Stmt_Class::MODIFIER_FINAL);
    +    }
    +
    +    public function isStatic() {
    +        return (bool) ($this->type & PHPParser_Node_Stmt_Class::MODIFIER_STATIC);
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Const.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Const.php
    new file mode 100644
    index 0000000..9a7ea08
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Const.php
    @@ -0,0 +1,22 @@
    + $consts,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Continue.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Continue.php
    new file mode 100644
    index 0000000..bc82e54
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Continue.php
    @@ -0,0 +1,22 @@
    + $num,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Declare.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Declare.php
    new file mode 100644
    index 0000000..c10083c
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Declare.php
    @@ -0,0 +1,25 @@
    + $declares,
    +                'stmts'    => $stmts,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/DeclareDeclare.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/DeclareDeclare.php
    new file mode 100644
    index 0000000..1526e66
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/DeclareDeclare.php
    @@ -0,0 +1,25 @@
    +value pair node.
    +     *
    +     * @param string              $key        Key
    +     * @param PHPParser_Node_Expr $value      Value
    +     * @param array               $attributes Additional attributes
    +     */
    +    public function __construct($key, PHPParser_Node_Expr $value, array $attributes = array()) {
    +        parent::__construct(
    +            array(
    +                'key'   => $key,
    +                'value' => $value,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Do.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Do.php
    new file mode 100644
    index 0000000..12070c0
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Do.php
    @@ -0,0 +1,25 @@
    + $cond,
    +                'stmts' => $stmts,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Echo.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Echo.php
    new file mode 100644
    index 0000000..77a5d59
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Echo.php
    @@ -0,0 +1,22 @@
    + $exprs,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Else.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Else.php
    new file mode 100644
    index 0000000..18ff649
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Else.php
    @@ -0,0 +1,22 @@
    + $stmts,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/ElseIf.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/ElseIf.php
    new file mode 100644
    index 0000000..ec0cff8
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/ElseIf.php
    @@ -0,0 +1,25 @@
    + $cond,
    +                'stmts' => $stmts,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/For.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/For.php
    new file mode 100644
    index 0000000..870c7a1
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/For.php
    @@ -0,0 +1,32 @@
    + array(): Init expressions
    +     *                          'cond'  => array(): Loop conditions
    +     *                          'loop'  => array(): Loop expressions
    +     *                          'stmts' => array(): Statements
    +     * @param array $attributes Additional attributes
    +     */
    +    public function __construct(array $subNodes = array(), array $attributes = array()) {
    +        parent::__construct(
    +            $subNodes + array(
    +                'init'  => array(),
    +                'cond'  => array(),
    +                'loop'  => array(),
    +                'stmts' => array(),
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Foreach.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Foreach.php
    new file mode 100644
    index 0000000..a209c32
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Foreach.php
    @@ -0,0 +1,35 @@
    + null   : Variable to assign key to
    +     *                                        'byRef'  => false  : Whether to assign value by reference
    +     *                                        'stmts'  => array(): Statements
    +     * @param array               $attributes Additional attributes
    +     */
    +    public function __construct(PHPParser_Node_Expr $expr, PHPParser_Node_Expr $valueVar, array $subNodes = array(), array $attributes = array()) {
    +        parent::__construct(
    +            $subNodes + array(
    +                'keyVar' => null,
    +                'byRef'  => false,
    +                'stmts'  => array(),
    +            ),
    +            $attributes
    +        );
    +        $this->expr     = $expr;
    +        $this->valueVar = $valueVar;
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Function.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Function.php
    new file mode 100644
    index 0000000..f62de9e
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Function.php
    @@ -0,0 +1,32 @@
    + false  : Whether to return by reference
    +     *                           'params' => array(): Parameters
    +     *                           'stmts'  => array(): Statements
    +     * @param array  $attributes Additional attributes
    +     */
    +    public function __construct($name, array $subNodes = array(), array $attributes = array()) {
    +        parent::__construct(
    +            $subNodes + array(
    +                'byRef'  => false,
    +                'params' => array(),
    +                'stmts'  => array(),
    +            ),
    +            $attributes
    +        );
    +        $this->name = $name;
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Global.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Global.php
    new file mode 100644
    index 0000000..ba841ca
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Global.php
    @@ -0,0 +1,22 @@
    + $vars,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Goto.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Goto.php
    new file mode 100644
    index 0000000..8de1020
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Goto.php
    @@ -0,0 +1,22 @@
    + $name,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/HaltCompiler.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/HaltCompiler.php
    new file mode 100644
    index 0000000..0f4c4b1
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/HaltCompiler.php
    @@ -0,0 +1,22 @@
    + $remaining,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/If.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/If.php
    new file mode 100644
    index 0000000..44c150b
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/If.php
    @@ -0,0 +1,33 @@
    + array(): Statements
    +     *                                        'elseifs' => array(): Elseif clauses
    +     *                                        'else'    => null   : Else clause
    +     * @param array               $attributes Additional attributes
    +     */
    +    public function __construct(PHPParser_Node_Expr $cond, array $subNodes = array(), array $attributes = array()) {
    +        parent::__construct(
    +            $subNodes + array(
    +                'stmts'   => array(),
    +                'elseifs' => array(),
    +                'else'    => null,
    +            ),
    +            $attributes
    +        );
    +        $this->cond = $cond;
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/InlineHTML.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/InlineHTML.php
    new file mode 100644
    index 0000000..d0578de
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/InlineHTML.php
    @@ -0,0 +1,22 @@
    + $value,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Interface.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Interface.php
    new file mode 100644
    index 0000000..8fc0241
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Interface.php
    @@ -0,0 +1,45 @@
    + true,
    +        'parent' => true,
    +        'static' => true,
    +    );
    +
    +    /**
    +     * Constructs a class node.
    +     *
    +     * @param string $name       Name
    +     * @param array  $subNodes   Array of the following optional subnodes:
    +     *                           'extends' => array(): Name of extended interfaces
    +     *                           'stmts'   => array(): Statements
    +     * @param array  $attributes Additional attributes
    +     */
    +    public function __construct($name, array $subNodes = array(), array $attributes = array()) {
    +        parent::__construct(
    +            $subNodes + array(
    +                'extends' => array(),
    +                'stmts'   => array(),
    +            ),
    +            $attributes
    +        );
    +        $this->name = $name;
    +
    +        if (isset(self::$specialNames[(string) $this->name])) {
    +            throw new PHPParser_Error(sprintf('Cannot use "%s" as interface name as it is reserved', $this->name));
    +        }
    +
    +        foreach ($this->extends as $interface) {
    +            if (isset(self::$specialNames[(string) $interface])) {
    +                throw new PHPParser_Error(sprintf('Cannot use "%s" as interface name as it is reserved', $interface));
    +            }
    +        }
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Label.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Label.php
    new file mode 100644
    index 0000000..66dc51e
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Label.php
    @@ -0,0 +1,22 @@
    + $name,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Namespace.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Namespace.php
    new file mode 100644
    index 0000000..7647239
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Namespace.php
    @@ -0,0 +1,122 @@
    + true,
    +        'parent' => true,
    +        'static' => true,
    +    );
    +
    +    /**
    +     * Constructs a namespace node.
    +     *
    +     * @param null|PHPParser_Node_Name $name       Name
    +     * @param PHPParser_Node[]         $stmts      Statements
    +     * @param array                    $attributes Additional attributes
    +     */
    +    public function __construct(PHPParser_Node_Name $name = null, $stmts = array(), array $attributes = array()) {
    +        parent::__construct(
    +            array(
    +                'name'  => $name,
    +                'stmts' => $stmts,
    +            ),
    +            $attributes
    +        );
    +
    +        if (isset(self::$specialNames[(string) $this->name])) {
    +            throw new PHPParser_Error(sprintf('Cannot use "%s" as namespace name as it is reserved', $this->name));
    +        }
    +
    +        if (null !== $this->stmts) {
    +            foreach ($this->stmts as $stmt) {
    +                if ($stmt instanceof PHPParser_Node_Stmt_Namespace) {
    +                    throw new PHPParser_Error('Namespace declarations cannot be nested', $stmt->getLine());
    +                }
    +            }
    +        }
    +    }
    +
    +    public static function postprocess(array $stmts) {
    +        // null = not in namespace, false = semicolon style, true = bracket style
    +        $bracketed = null;
    +
    +        // whether any statements that aren't allowed before a namespace declaration are encountered
    +        // (the only valid statement currently is a declare)
    +        $hasNotAllowedStmts = false;
    +
    +        // offsets for semicolon style namespaces
    +        // (required for transplanting the following statements into their ->stmts property)
    +        $nsOffsets = array();
    +
    +        foreach ($stmts as $i => $stmt) {
    +            if ($stmt instanceof PHPParser_Node_Stmt_Namespace) {
    +                // ->stmts is null if semicolon style is used
    +                $currentBracketed = null !== $stmt->stmts;
    +
    +                // if no namespace statement has been encountered yet
    +                if (!isset($bracketed)) {
    +                    // set the namespacing style
    +                    $bracketed = $currentBracketed;
    +
    +                    // and ensure that it isn't preceded by a not allowed statement
    +                    if ($hasNotAllowedStmts) {
    +                        throw new PHPParser_Error('Namespace declaration statement has to be the very first statement in the script', $stmt->getLine());
    +                    }
    +                // otherwise ensure that the style of the current namespace matches the style of
    +                // namespaceing used before in this document
    +                } elseif ($bracketed !== $currentBracketed) {
    +                    throw new PHPParser_Error('Cannot mix bracketed namespace declarations with unbracketed namespace declarations', $stmt->getLine());
    +                }
    +
    +                // for semicolon style namespaces remember the offset
    +                if (!$bracketed) {
    +                    $nsOffsets[] = $i;
    +                }
    +            // declare() and __halt_compiler() are the only valid statements outside of namespace declarations
    +            } elseif (!$stmt instanceof PHPParser_Node_Stmt_Declare
    +                      && !$stmt instanceof PHPParser_Node_Stmt_HaltCompiler
    +            ) {
    +                if (true === $bracketed) {
    +                    throw new PHPParser_Error('No code may exist outside of namespace {}', $stmt->getLine());
    +                }
    +
    +                $hasNotAllowedStmts = true;
    +            }
    +        }
    +
    +        // if bracketed namespaces were used or no namespaces were used at all just return the
    +        // original statements
    +        if (!isset($bracketed) || true === $bracketed) {
    +            return $stmts;
    +        // for semicolon style transplant statements
    +        } else {
    +            // take all statements preceding the first namespace
    +            $newStmts = array_slice($stmts, 0, $nsOffsets[0]);
    +
    +            // iterate over all following namespaces
    +            for ($i = 0, $c = count($nsOffsets); $i < $c; ++$i) {
    +                $newStmts[] = $nsStmt = $stmts[$nsOffsets[$i]];
    +
    +                // the last namespace takes all statements after it
    +                if ($c === $i + 1) {
    +                    $nsStmt->stmts = array_slice($stmts, $nsOffsets[$i] + 1);
    +
    +                    // if the last statement is __halt_compiler() put it outside the namespace
    +                    if (end($nsStmt->stmts) instanceof PHPParser_Node_Stmt_HaltCompiler) {
    +                        $newStmts[] = array_pop($nsStmt->stmts);
    +                    }
    +                // and all the others take all statements between the current and the following one
    +                } else {
    +                    $nsStmt->stmts = array_slice($stmts, $nsOffsets[$i] + 1, $nsOffsets[$i + 1] - $nsOffsets[$i] - 1);
    +                }
    +            }
    +
    +            return $newStmts;
    +        }
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Property.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Property.php
    new file mode 100644
    index 0000000..5cc9d0c
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Property.php
    @@ -0,0 +1,41 @@
    + $type,
    +                'props' => $props,
    +            ),
    +            $attributes
    +        );
    +    }
    +
    +    public function isPublic() {
    +        return (bool) ($this->type & PHPParser_Node_Stmt_Class::MODIFIER_PUBLIC);
    +    }
    +
    +    public function isProtected() {
    +        return (bool) ($this->type & PHPParser_Node_Stmt_Class::MODIFIER_PROTECTED);
    +    }
    +
    +    public function isPrivate() {
    +        return (bool) ($this->type & PHPParser_Node_Stmt_Class::MODIFIER_PRIVATE);
    +    }
    +
    +    public function isStatic() {
    +        return (bool) ($this->type & PHPParser_Node_Stmt_Class::MODIFIER_STATIC);
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/PropertyProperty.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/PropertyProperty.php
    new file mode 100644
    index 0000000..e2854f1
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/PropertyProperty.php
    @@ -0,0 +1,25 @@
    + $name,
    +                'default' => $default,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Return.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Return.php
    new file mode 100644
    index 0000000..4697530
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Return.php
    @@ -0,0 +1,22 @@
    + $expr,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Static.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Static.php
    new file mode 100644
    index 0000000..f44d1ed
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Static.php
    @@ -0,0 +1,22 @@
    + $vars,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/StaticVar.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/StaticVar.php
    new file mode 100644
    index 0000000..3c5b144
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/StaticVar.php
    @@ -0,0 +1,25 @@
    + $name,
    +                'default' => $default,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Switch.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Switch.php
    new file mode 100644
    index 0000000..f7022a0
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Switch.php
    @@ -0,0 +1,25 @@
    + $cond,
    +                'cases' => $cases,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Throw.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Throw.php
    new file mode 100644
    index 0000000..990de1a
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Throw.php
    @@ -0,0 +1,22 @@
    + $expr,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Trait.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Trait.php
    new file mode 100644
    index 0000000..14737a5
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Trait.php
    @@ -0,0 +1,25 @@
    + $name,
    +                'stmts' => $stmts,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/TraitUse.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/TraitUse.php
    new file mode 100644
    index 0000000..8db1b7e
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/TraitUse.php
    @@ -0,0 +1,25 @@
    + $traits,
    +                'adaptations' => $adaptations,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/TraitUseAdaptation.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/TraitUseAdaptation.php
    new file mode 100644
    index 0000000..63b2b27
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/TraitUseAdaptation.php
    @@ -0,0 +1,5 @@
    + $trait,
    +                'method'      => $method,
    +                'newModifier' => $newModifier,
    +                'newName'     => $newName,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/TraitUseAdaptation/Precedence.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/TraitUseAdaptation/Precedence.php
    new file mode 100644
    index 0000000..30ae8e4
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/TraitUseAdaptation/Precedence.php
    @@ -0,0 +1,28 @@
    + $trait,
    +                'method'    => $method,
    +                'insteadof' => $insteadof,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/TryCatch.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/TryCatch.php
    new file mode 100644
    index 0000000..796aae3
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/TryCatch.php
    @@ -0,0 +1,32 @@
    + $stmts,
    +                'catches'      => $catches,
    +                'finallyStmts' => $finallyStmts,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Unset.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Unset.php
    new file mode 100644
    index 0000000..e079c29
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Unset.php
    @@ -0,0 +1,22 @@
    + $vars,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Use.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Use.php
    new file mode 100644
    index 0000000..36dc0b1
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/Use.php
    @@ -0,0 +1,22 @@
    + $uses,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/UseUse.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/UseUse.php
    new file mode 100644
    index 0000000..2287af0
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/UseUse.php
    @@ -0,0 +1,36 @@
    +getLast();
    +        }
    +
    +        if ('self' == $alias || 'parent' == $alias) {
    +            throw new PHPParser_Error(sprintf(
    +                'Cannot use "%s" as "%s" because "%2$s" is a special class name',
    +                $name, $alias
    +            ));
    +        }
    +
    +        parent::__construct(
    +            array(
    +                'name'  => $name,
    +                'alias' => $alias,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/While.php b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/While.php
    new file mode 100644
    index 0000000..4dde965
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Node/Stmt/While.php
    @@ -0,0 +1,25 @@
    + $cond,
    +                'stmts' => $stmts,
    +            ),
    +            $attributes
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/NodeAbstract.php b/vendor/nikic/php-parser/lib/PHPParser/NodeAbstract.php
    new file mode 100644
    index 0000000..e7d0456
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/NodeAbstract.php
    @@ -0,0 +1,125 @@
    +subNodes   = $subNodes;
    +        $this->attributes = $attributes;
    +    }
    +
    +    /**
    +     * Gets the type of the node.
    +     *
    +     * @return string Type of the node
    +     */
    +    public function getType() {
    +        return substr(get_class($this), 15);
    +    }
    +
    +    /**
    +     * Gets the names of the sub nodes.
    +     *
    +     * @return array Names of sub nodes
    +     */
    +    public function getSubNodeNames() {
    +        return array_keys($this->subNodes);
    +    }
    +
    +    /**
    +     * Gets line the node started in.
    +     *
    +     * @return int Line
    +     */
    +    public function getLine() {
    +        return $this->getAttribute('startLine', -1);
    +    }
    +
    +    /**
    +     * Sets line the node started in.
    +     *
    +     * @param int $line Line
    +     */
    +    public function setLine($line) {
    +        $this->setAttribute('startLine', (int) $line);
    +    }
    +
    +    /**
    +     * Gets the doc comment of the node.
    +     *
    +     * The doc comment has to be the last comment associated with the node.
    +     *
    +     * @return null|PHPParser_Comment_Doc Doc comment object or null
    +     */
    +    public function getDocComment() {
    +        $comments = $this->getAttribute('comments');
    +        if (!$comments) {
    +            return null;
    +        }
    +
    +        $lastComment = $comments[count($comments) - 1];
    +        if (!$lastComment instanceof PHPParser_Comment_Doc) {
    +            return null;
    +        }
    +
    +        return $lastComment;
    +    }
    +
    +    /**
    +     * {@inheritDoc}
    +     */
    +    public function setAttribute($key, $value) {
    +        $this->attributes[$key] = $value;
    +    }
    +
    +    /**
    +     * {@inheritDoc}
    +     */
    +    public function hasAttribute($key) {
    +        return array_key_exists($key, $this->attributes);
    +    }
    +
    +    /**
    +     * {@inheritDoc}
    +     */
    +    public function &getAttribute($key, $default = null) {
    +        if (!array_key_exists($key, $this->attributes)) {
    +            return $default;
    +        } else {
    +            return $this->attributes[$key];
    +        }
    +    }
    +
    +    /**
    +     * {@inheritDoc}
    +     */
    +    public function getAttributes() {
    +        return $this->attributes;
    +    }
    +
    +    /* Magic interfaces */
    +
    +    public function &__get($name) {
    +        return $this->subNodes[$name];
    +    }
    +    public function __set($name, $value) {
    +        $this->subNodes[$name] = $value;
    +    }
    +    public function __isset($name) {
    +        return isset($this->subNodes[$name]);
    +    }
    +    public function __unset($name) {
    +        unset($this->subNodes[$name]);
    +    }
    +    public function getIterator() {
    +        return new ArrayIterator($this->subNodes);
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/NodeDumper.php b/vendor/nikic/php-parser/lib/PHPParser/NodeDumper.php
    new file mode 100644
    index 0000000..283d630
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/NodeDumper.php
    @@ -0,0 +1,39 @@
    +getType() . '(';
    +        } elseif (is_array($node)) {
    +            $r = 'array(';
    +        } else {
    +            throw new InvalidArgumentException('Can only dump nodes and arrays.');
    +        }
    +
    +        foreach ($node as $key => $value) {
    +            $r .= "\n" . '    ' . $key . ': ';
    +
    +            if (null === $value) {
    +                $r .= 'null';
    +            } elseif (false === $value) {
    +                $r .= 'false';
    +            } elseif (true === $value) {
    +                $r .= 'true';
    +            } elseif (is_scalar($value)) {
    +                $r .= $value;
    +            } else {
    +                $r .= str_replace("\n", "\n" . '    ', $this->dump($value));
    +            }
    +        }
    +
    +        return $r . "\n" . ')';
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/NodeTraverser.php b/vendor/nikic/php-parser/lib/PHPParser/NodeTraverser.php
    new file mode 100644
    index 0000000..348e78d
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/NodeTraverser.php
    @@ -0,0 +1,118 @@
    +visitors = array();
    +    }
    +
    +    /**
    +     * Adds a visitor.
    +     *
    +     * @param PHPParser_NodeVisitor $visitor Visitor to add
    +     */
    +    public function addVisitor(PHPParser_NodeVisitor $visitor) {
    +        $this->visitors[] = $visitor;
    +    }
    +
    +    /**
    +     * Traverses an array of nodes using the registered visitors.
    +     *
    +     * @param PHPParser_Node[] $nodes Array of nodes
    +     *
    +     * @return PHPParser_Node[] Traversed array of nodes
    +     */
    +    public function traverse(array $nodes) {
    +        foreach ($this->visitors as $visitor) {
    +            if (null !== $return = $visitor->beforeTraverse($nodes)) {
    +                $nodes = $return;
    +            }
    +        }
    +
    +        $nodes = $this->traverseArray($nodes);
    +
    +        foreach ($this->visitors as $visitor) {
    +            if (null !== $return = $visitor->afterTraverse($nodes)) {
    +                $nodes = $return;
    +            }
    +        }
    +
    +        return $nodes;
    +    }
    +
    +    protected function traverseNode(PHPParser_Node $node) {
    +        $node = clone $node;
    +
    +        foreach ($node->getSubNodeNames() as $name) {
    +            $subNode =& $node->$name;
    +
    +            if (is_array($subNode)) {
    +                $subNode = $this->traverseArray($subNode);
    +            } elseif ($subNode instanceof PHPParser_Node) {
    +                foreach ($this->visitors as $visitor) {
    +                    if (null !== $return = $visitor->enterNode($subNode)) {
    +                        $subNode = $return;
    +                    }
    +                }
    +
    +                $subNode = $this->traverseNode($subNode);
    +
    +                foreach ($this->visitors as $visitor) {
    +                    if (null !== $return = $visitor->leaveNode($subNode)) {
    +                        $subNode = $return;
    +                    }
    +                }
    +            }
    +        }
    +
    +        return $node;
    +    }
    +
    +    protected function traverseArray(array $nodes) {
    +        $doNodes = array();
    +
    +        foreach ($nodes as $i => &$node) {
    +            if (is_array($node)) {
    +                $node = $this->traverseArray($node);
    +            } elseif ($node instanceof PHPParser_Node) {
    +                foreach ($this->visitors as $visitor) {
    +                    if (null !== $return = $visitor->enterNode($node)) {
    +                        $node = $return;
    +                    }
    +                }
    +
    +                $node = $this->traverseNode($node);
    +
    +                foreach ($this->visitors as $visitor) {
    +                    $return = $visitor->leaveNode($node);
    +
    +                    if (false === $return) {
    +                        $doNodes[] = array($i, array());
    +                        break;
    +                    } elseif (is_array($return)) {
    +                        $doNodes[] = array($i, $return);
    +                        break;
    +                    } elseif (null !== $return) {
    +                        $node = $return;
    +                    }
    +                }
    +            }
    +        }
    +
    +        if (!empty($doNodes)) {
    +            while (list($i, $replace) = array_pop($doNodes)) {
    +                array_splice($nodes, $i, 1, $replace);
    +            }
    +        }
    +
    +        return $nodes;
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/NodeTraverserInterface.php b/vendor/nikic/php-parser/lib/PHPParser/NodeTraverserInterface.php
    new file mode 100644
    index 0000000..898eaa0
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/NodeTraverserInterface.php
    @@ -0,0 +1,21 @@
    +namespace = null;
    +        $this->aliases   = array();
    +    }
    +
    +    public function enterNode(PHPParser_Node $node) {
    +        if ($node instanceof PHPParser_Node_Stmt_Namespace) {
    +            $this->namespace = $node->name;
    +            $this->aliases   = array();
    +        } elseif ($node instanceof PHPParser_Node_Stmt_UseUse) {
    +            if (isset($this->aliases[$node->alias])) {
    +                throw new PHPParser_Error(
    +                    sprintf(
    +                        'Cannot use "%s" as "%s" because the name is already in use',
    +                        $node->name, $node->alias
    +                    ),
    +                    $node->getLine()
    +                );
    +            }
    +
    +            $this->aliases[$node->alias] = $node->name;
    +        } elseif ($node instanceof PHPParser_Node_Stmt_Class) {
    +            if (null !== $node->extends) {
    +                $node->extends = $this->resolveClassName($node->extends);
    +            }
    +
    +            foreach ($node->implements as &$interface) {
    +                $interface = $this->resolveClassName($interface);
    +            }
    +
    +            $this->addNamespacedName($node);
    +        } elseif ($node instanceof PHPParser_Node_Stmt_Interface) {
    +            foreach ($node->extends as &$interface) {
    +                $interface = $this->resolveClassName($interface);
    +            }
    +
    +            $this->addNamespacedName($node);
    +        } elseif ($node instanceof PHPParser_Node_Stmt_Trait) {
    +            $this->addNamespacedName($node);
    +        } elseif ($node instanceof PHPParser_Node_Stmt_Function) {
    +            $this->addNamespacedName($node);
    +        } elseif ($node instanceof PHPParser_Node_Stmt_Const) {
    +            foreach ($node->consts as $const) {
    +                $this->addNamespacedName($const);
    +            }
    +        } elseif ($node instanceof PHPParser_Node_Expr_StaticCall
    +                  || $node instanceof PHPParser_Node_Expr_StaticPropertyFetch
    +                  || $node instanceof PHPParser_Node_Expr_ClassConstFetch
    +                  || $node instanceof PHPParser_Node_Expr_New
    +                  || $node instanceof PHPParser_Node_Expr_Instanceof
    +        ) {
    +            if ($node->class instanceof PHPParser_Node_Name) {
    +                $node->class = $this->resolveClassName($node->class);
    +            }
    +        } elseif ($node instanceof PHPParser_Node_Stmt_Catch) {
    +            $node->type = $this->resolveClassName($node->type);
    +        } elseif ($node instanceof PHPParser_Node_Expr_FuncCall
    +                  || $node instanceof PHPParser_Node_Expr_ConstFetch
    +        ) {
    +            if ($node->name instanceof PHPParser_Node_Name) {
    +                $node->name = $this->resolveOtherName($node->name);
    +            }
    +        } elseif ($node instanceof PHPParser_Node_Stmt_TraitUse) {
    +            foreach ($node->traits as &$trait) {
    +                $trait = $this->resolveClassName($trait);
    +            }
    +        } elseif ($node instanceof PHPParser_Node_Param
    +                  && $node->type instanceof PHPParser_Node_Name
    +        ) {
    +            $node->type = $this->resolveClassName($node->type);
    +        }
    +    }
    +
    +    protected function resolveClassName(PHPParser_Node_Name $name) {
    +        // don't resolve special class names
    +        if (in_array((string) $name, array('self', 'parent', 'static'))) {
    +            return $name;
    +        }
    +
    +        // fully qualified names are already resolved
    +        if ($name->isFullyQualified()) {
    +            return $name;
    +        }
    +
    +        // resolve aliases (for non-relative names)
    +        if (!$name->isRelative() && isset($this->aliases[$name->getFirst()])) {
    +            $name->setFirst($this->aliases[$name->getFirst()]);
    +        // if no alias exists prepend current namespace
    +        } elseif (null !== $this->namespace) {
    +            $name->prepend($this->namespace);
    +        }
    +
    +        return new PHPParser_Node_Name_FullyQualified($name->parts, $name->getAttributes());
    +    }
    +
    +    protected function resolveOtherName(PHPParser_Node_Name $name) {
    +        // fully qualified names are already resolved and we can't do anything about unqualified
    +        // ones at compiler-time
    +        if ($name->isFullyQualified() || $name->isUnqualified()) {
    +            return $name;
    +        }
    +
    +        // resolve aliases for qualified names
    +        if ($name->isQualified() && isset($this->aliases[$name->getFirst()])) {
    +            $name->setFirst($this->aliases[$name->getFirst()]);
    +        // prepend namespace for relative names
    +        } elseif (null !== $this->namespace) {
    +            $name->prepend($this->namespace);
    +        }
    +
    +        return new PHPParser_Node_Name_FullyQualified($name->parts, $name->getAttributes());
    +    }
    +
    +    protected function addNamespacedName(PHPParser_Node $node) {
    +        if (null !== $this->namespace) {
    +            $node->namespacedName = clone $this->namespace;
    +            $node->namespacedName->append($node->name);
    +        } else {
    +            $node->namespacedName = new PHPParser_Node_Name($node->name, $node->getAttributes());
    +        }
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/NodeVisitorAbstract.php b/vendor/nikic/php-parser/lib/PHPParser/NodeVisitorAbstract.php
    new file mode 100644
    index 0000000..75ae698
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/NodeVisitorAbstract.php
    @@ -0,0 +1,12 @@
    +'",
    +        "T_IS_GREATER_OR_EQUAL",
    +        "T_SL",
    +        "T_SR",
    +        "'+'",
    +        "'-'",
    +        "'.'",
    +        "'*'",
    +        "'/'",
    +        "'%'",
    +        "'!'",
    +        "T_INSTANCEOF",
    +        "'~'",
    +        "T_INC",
    +        "T_DEC",
    +        "T_INT_CAST",
    +        "T_DOUBLE_CAST",
    +        "T_STRING_CAST",
    +        "T_ARRAY_CAST",
    +        "T_OBJECT_CAST",
    +        "T_BOOL_CAST",
    +        "T_UNSET_CAST",
    +        "'@'",
    +        "'['",
    +        "T_NEW",
    +        "T_CLONE",
    +        "T_EXIT",
    +        "T_IF",
    +        "T_ELSEIF",
    +        "T_ELSE",
    +        "T_ENDIF",
    +        "T_LNUMBER",
    +        "T_DNUMBER",
    +        "T_STRING",
    +        "T_STRING_VARNAME",
    +        "T_VARIABLE",
    +        "T_NUM_STRING",
    +        "T_INLINE_HTML",
    +        "T_ENCAPSED_AND_WHITESPACE",
    +        "T_CONSTANT_ENCAPSED_STRING",
    +        "T_ECHO",
    +        "T_DO",
    +        "T_WHILE",
    +        "T_ENDWHILE",
    +        "T_FOR",
    +        "T_ENDFOR",
    +        "T_FOREACH",
    +        "T_ENDFOREACH",
    +        "T_DECLARE",
    +        "T_ENDDECLARE",
    +        "T_AS",
    +        "T_SWITCH",
    +        "T_ENDSWITCH",
    +        "T_CASE",
    +        "T_DEFAULT",
    +        "T_BREAK",
    +        "T_CONTINUE",
    +        "T_GOTO",
    +        "T_FUNCTION",
    +        "T_CONST",
    +        "T_RETURN",
    +        "T_TRY",
    +        "T_CATCH",
    +        "T_FINALLY",
    +        "T_THROW",
    +        "T_USE",
    +        "T_INSTEADOF",
    +        "T_GLOBAL",
    +        "T_STATIC",
    +        "T_ABSTRACT",
    +        "T_FINAL",
    +        "T_PRIVATE",
    +        "T_PROTECTED",
    +        "T_PUBLIC",
    +        "T_VAR",
    +        "T_UNSET",
    +        "T_ISSET",
    +        "T_EMPTY",
    +        "T_HALT_COMPILER",
    +        "T_CLASS",
    +        "T_TRAIT",
    +        "T_INTERFACE",
    +        "T_EXTENDS",
    +        "T_IMPLEMENTS",
    +        "T_OBJECT_OPERATOR",
    +        "T_DOUBLE_ARROW",
    +        "T_LIST",
    +        "T_ARRAY",
    +        "T_CALLABLE",
    +        "T_CLASS_C",
    +        "T_TRAIT_C",
    +        "T_METHOD_C",
    +        "T_FUNC_C",
    +        "T_LINE",
    +        "T_FILE",
    +        "T_START_HEREDOC",
    +        "T_END_HEREDOC",
    +        "T_DOLLAR_OPEN_CURLY_BRACES",
    +        "T_CURLY_OPEN",
    +        "T_PAAMAYIM_NEKUDOTAYIM",
    +        "T_NAMESPACE",
    +        "T_NS_C",
    +        "T_DIR",
    +        "T_NS_SEPARATOR",
    +        "';'",
    +        "'{'",
    +        "'}'",
    +        "'('",
    +        "')'",
    +        "'$'",
    +        "'`'",
    +        "']'",
    +        "'\"'"
    +        , "???"
    +    );
    +
    +    /* @var array Map which translates lexer tokens to internal tokens */
    +    protected static $translate = array(
    +            0,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,   48,  150,  151,  147,   47,   31,  151,
    +          145,  146,   45,   42,    7,   43,   44,   46,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,   26,  142,
    +           36,   13,   38,   25,   60,  151,  151,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,   61,  151,  149,   30,  151,  148,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,  143,   29,  144,   50,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,  151,  151,  151,  151,
    +          151,  151,  151,  151,  151,  151,    1,    2,    3,    4,
    +            5,    6,    8,    9,   10,   11,   12,   14,   15,   16,
    +           17,   18,   19,   20,   21,   22,   23,   24,   27,   28,
    +           32,   33,   34,   35,   37,   39,   40,   41,   49,   51,
    +           52,   53,   54,   55,   56,   57,   58,   59,   62,   63,
    +           64,   65,   66,   67,   68,   69,   70,   71,   72,   73,
    +           74,   75,  151,  151,   76,   77,   78,   79,   80,   81,
    +           82,   83,   84,   85,   86,   87,   88,   89,   90,   91,
    +           92,   93,   94,   95,   96,   97,   98,   99,  100,  101,
    +          102,  103,  104,  105,  106,  107,  108,  109,  110,  111,
    +          112,  113,  114,  115,  116,  117,  118,  119,  120,  121,
    +          122,  123,  124,  125,  126,  127,  128,  129,  130,  131,
    +          132,  151,  151,  151,  151,  151,  151,  133,  134,  135,
    +          136,  137,  138,  139,  140,  141
    +    );
    +
    +    protected static $yyaction = array(
    +           59,   60,  325,   61,   62,-32766,-32766,-32766,  324,   63,
    +           64,-32767,-32767,-32767,-32767,   98,   99,  100,  101,  102,
    +           57,  917,-32766,  298,-32766,-32766,   41,  106,  107,  108,
    +          109,  110,  111,  112,  113,  114,  115,  116,  267,  346,
    +           65,   66,  927,  249,  929,  928,   67,  535,   68,  220,
    +          221,   69,   70,   71,   72,   73,   74,   75,   76,   31,
    +          232,   77,  318,  326,  730,  732,  462,  836,  837,  362,
    +          348,  895,  238,  578,  280,  363,   46,   27,  327,  859,
    +          364,  246,  365,  454,  366,   39,  223,  328,-32766,-32766,
    +        -32766,   36,   37,  367,  333,  360,   38,  368,  329,  423,
    +           78,  848,  122,  278,  279,-32766,  286,-32766,   35,  369,
    +          370,  371,  372,  373,  389,  343,  861,  330,  560,  602,
    +          374,  375,  376,  377,  848,  842,  843,  844,  845,  839,
    +          840,  239,   82,   83,   84, -350,  389,  846,  841,  330,
    +          584,  504,  126,   47,  227,  259,  244,  802,  248,   40,
    +          351,   85,   86,   87,   88,   89,   90,   91,   92,   93,
    +           94,   95,   96,   97,   98,   99,  100,  101,  102,  103,
    +          104,  105,  788,  233,  576,-32766,-32766,-32766,  701,  702,
    +          703,  700,  699,  698,  630,    0,-32766,-32766,-32766,  655,
    +          656,  216,-32766,  215,-32766,-32766,-32766,-32766,-32766,-32767,
    +        -32767,-32767,-32767,-32766,  788,  322,  329,  319,  899,  544,
    +         -117,  257,  128,  277,-32766,-32766,-32766,  369,  370,  889,
    +          693,  261,  895,  225,  226,-32766,  540,  602,  374,  375,
    +          675,  535,  344,-32766,  535,-32766,  895,  376,-32766,-32766,
    +        -32766,  575,-32766,   53,-32766,  322,-32766,  658,  263,-32766,
    +          187,  257,  600,-32766,-32766,-32766,  788,-32766,-32766,-32766,
    +          693,   34,-32766,  535,  350,-32766,  388,-32766,  860,  812,
    +        -32766,-32766,-32766,-32766,-32766,  222,-32766,   54,-32766,   56,
    +          127,-32766,  100,  101,  102,-32766,-32766,-32766,  788,   22,
    +        -32766,-32766,  601,  268,-32766,  924,  259,-32766,  388,  666,
    +          631,  389,-32766,-32766,  330,-32766,  322,  224,  334,-32766,
    +          259,  917,  257,  503,  861,  535,  103,  104,  105,-32766,
    +          233,  693,-32766,-32766,-32766,  118,-32766,  494,-32766,  340,
    +        -32766,  506,  902,-32766,-32766,-32766,  126,-32766,-32766,-32766,
    +          345,-32766,-32766,-32766,  213,  123,-32766,  535,  130,-32766,
    +          388,-32766,  452,  599,-32766,-32766,-32766,-32766,-32766,  119,
    +        -32766,  120,-32766,  788,  233,-32766,  189, -113,  190,-32766,
    +        -32766,-32766,  194,  217,-32766,-32766,  195,  125,-32766,-32766,
    +        -32766,-32766,  388,  188,  685,  858,-32766,-32766,  117,-32766,
    +          329,  319,  353,   28,  509,  788,  597,  277,  357,  468,
    +          680,  369,  370,  516,-32766,-32766,-32766,  131,  287,   49,
    +          540,  602,  374,  375,  477,  478,-32766,  520,-32766,-32766,
    +          528,-32766,  535,-32766,-32766,-32766,-32766,  655,  656,-32766,
    +        -32766,-32766,  263,-32766,  519,-32766,  507,-32766,  542,  129,
    +        -32766,  679,  525,  588,-32766,-32766,-32766,  526,-32766,-32766,
    +        -32766,  690,  530,-32766,  535,  306,-32766,  388,-32766,  541,
    +          511,-32766,-32766,-32766,-32766,-32766,  224,-32766,   50,-32766,
    +           58,  482,-32766,   55,  805,   51,-32766,-32766,-32766,  788,
    +           52,-32766,-32766,  416,  232,-32766,  502,  687,-32766,  388,
    +          445,  491,  229,-32766,-32766,  551,-32766,  922,  549,  415,
    +        -32766,  339,  341,  535,  536,  399,  535,  400,  402,  414,
    +        -32766, -158,  401,-32766,-32766,-32766,  493,-32766,  479,-32766,
    +          475,-32766, -161,  604,-32766,-32766,-32766,  265,-32766,-32766,
    +        -32766,  788,-32766,-32766,-32766,  266,  917,-32766,  535,  256,
    +        -32766,  388,-32766,  342,  212,-32766,-32766,-32766,-32766,-32766,
    +          338,-32766,  471,-32766,  457,  473,-32766,  359,  603,  258,
    +        -32766,-32766,-32766,  788,  255,-32766,-32766,  577,  260,-32766,
    +          376,  579,-32766,  388,  847,  247,    0,-32766,-32766, -350,
    +        -32766,  657,    0,  337,-32766,    0,    0, -351,  245,    0,
    +          535,  121,  193,   42,-32766, -282,  791,-32766,-32766,-32766,
    +            0,-32766,    0,-32766,    0,-32766,    0,    0,-32766,  570,
    +        -32766, -290,-32766,-32766,-32766,  788,-32766,-32766,-32766, -291,
    +          499,-32766,  535,  300,-32766,  388,-32766,  288,  251,-32766,
    +        -32766,-32766,-32766,-32766,  242,-32766,  407,-32766,  684,  340,
    +        -32766,  686,  614,  616,-32766,-32766,-32766,  618,  563,-32766,
    +        -32766,  625,  624,-32766,  633,  580,-32766,  388,  565,  587,
    +          574,  572,-32766,  513,-32766,  512,   45,   44,-32766,  569,
    +          571,  573,  586,  545,  535,  683,  676,  234,-32766,  510,
    +          515,-32766,-32766,-32766,  517,-32766,  522,-32766,   81,-32766,
    +          124,  523,-32766,-32766,-32766,  524,-32766,-32766,-32766,  527,
    +        -32766,-32766,-32766,  505,  529,-32766,  535,  890,-32766,  388,
    +        -32766,  900,  668,-32766,-32766,-32766,-32766,-32766,  827,-32766,
    +          892,-32766,  880,  894,-32766,  191,  192,  896,-32766,-32766,
    +        -32766,  923,  356,-32766,-32766,  623,  926,-32766,  622,  925,
    +        -32766,  388,   32,   33,  185,  568,-32766,  321,-32766,  317,
    +           43,  262,  836,  837,  237,-32766,-32766,  236,   48,-32766,
    +          838,  535,  235,   30,  219,-32766,  218,  214,-32766,-32766,
    +        -32766,  186,-32766,   80,-32766,   79,-32766,-32766,-32766,-32766,
    +          768,  829,  767,-32766,-32766,-32766,  446, -114,-32766,-32766,
    +          854,  659,-32766,  795,  792,-32766,  388,  498,  472,  437,
    +          358,  354,  307,-32766,  289,   25,   24,   23,  442, -113,
    +          842,  843,  844,  845,  839,  840,  309,  786,    0,  480,
    +          874,  855,  846,  841,  329,  319,  921,  826,-32766,  329,
    +        -32766,  277,-32766,-32766,  891,  369,  370,-32766,-32766,-32766,
    +          369,  370,  875,  879,  540,  602,  374,  375,  893,  560,
    +          602,  374,  375,  329,-32766,  811,-32766,-32766,-32766,-32766,
    +        -32766,  799,  797,  798,  369,  370,  263,  329,  796,    0,
    +            0,  329,  543,  560,  602,  374,  375,  598,  369,  370,
    +            0,    0,  369,  370,  329,    0,    0,  560,  602,  374,
    +          375,  560,  602,  374,  375,  369,  370,    0,    0,    0,
    +          329,  691,    0,    0,  560,  602,  374,  375,    0,    0,
    +            0,  369,  370,  329,    0,  790,    0,  329,  501,  591,
    +          560,  602,  374,  375,  369,  370,    0,    0,  369,  370,
    +            0,  329,  593,  560,  602,  374,  375,  560,  602,  374,
    +          375,    0,  369,  370,  492,    0,    0,    0,  514,    0,
    +          486,  560,  602,  374,  375,  329,    0,    0,    0,  329,
    +            0,  561,    0,    0,    0,  789,  369,  370,    0,    0,
    +          369,  370,-32766,-32766,-32766,  560,  602,  374,  375,  560,
    +          602,  374,  375,    0,  329,    0,    0,    0,    0,-32766,
    +            0,-32766,-32766,-32766,-32766,  369,  370,    0,    0,    0,
    +            0,    0,    0,    0,  560,  602,  374,  375
    +    );
    +
    +    protected static $yycheck = array(
    +            2,    3,    4,    5,    6,    8,    9,   10,    7,   11,
    +           12,   36,   37,   38,   39,   40,   41,   42,   43,   44,
    +           61,   76,   25,   73,   27,   28,   13,   14,   15,   16,
    +           17,   18,   19,   20,   21,   22,   23,   24,   61,    7,
    +           42,   43,   71,   76,   73,   74,   48,   71,   50,   51,
    +           52,   53,   54,   55,   56,   57,   58,   59,   60,   61,
    +           62,   63,   64,   65,   51,   52,   76,   69,   70,   71,
    +           71,   73,    7,   75,    7,   77,   78,   79,   80,  134,
    +           82,  122,   84,   81,   86,  135,  136,   89,    8,    9,
    +           10,   93,   94,   95,   96,    7,   98,   99,   96,  122,
    +          102,  134,  143,  105,  106,   25,    7,   27,    7,  107,
    +          108,  113,  114,  115,  138,   26,  117,  141,  116,  117,
    +          118,  119,  124,  125,  134,  127,  128,  129,  130,  131,
    +          132,  133,    8,    9,   10,  122,  138,  139,  140,  141,
    +          142,  143,  143,  145,   31,  147,  148,  146,  150,   25,
    +            7,   27,   28,   29,   30,   31,   32,   33,   34,   35,
    +           36,   37,   38,   39,   40,   41,   42,   43,   44,   45,
    +           46,   47,   12,   49,  142,    8,    9,   10,  106,  107,
    +          108,  109,  110,  111,   26,    0,    8,    9,   10,  125,
    +          126,   31,   25,    7,   27,   28,   29,   30,   31,   32,
    +           33,   34,   35,   25,   12,   97,   96,   97,   71,  142,
    +          146,  103,   61,  103,    8,    9,   10,  107,  108,   73,
    +          112,    7,   73,   31,    7,   65,  116,  117,  118,  119,
    +          142,   71,  143,    8,   71,   75,   73,  124,   78,   79,
    +           80,  142,   82,   61,   84,   97,   86,  146,  138,   89,
    +            7,  103,  144,   93,   94,   95,   12,   65,   98,   99,
    +          112,    7,  102,   71,   71,  105,  106,   75,   71,  106,
    +           78,   79,   80,  113,   82,   31,   84,   61,   86,   61,
    +          143,   89,   42,   43,   44,   93,   94,   95,   12,  146,
    +           98,   99,  144,  147,  102,  144,  147,  105,  106,   73,
    +          142,  138,  142,  143,  141,  113,   97,   31,  145,   65,
    +          147,   76,  103,   71,  117,   71,   45,   46,   47,   75,
    +           49,  112,   78,   79,   80,  143,   82,   71,   84,  141,
    +           86,  143,  146,   89,  142,  143,  143,   93,   94,   95,
    +            7,   65,   98,   99,  123,    7,  102,   71,  143,  105,
    +          106,   75,  147,  144,   78,   79,   80,  113,   82,  143,
    +           84,  143,   86,   12,   49,   89,   13,  146,   13,   93,
    +           94,   95,   13,  147,   98,   99,   13,   26,  102,    8,
    +            9,  105,  106,   13,  142,  150,  142,  143,   13,  113,
    +           96,   97,   66,   67,   26,   12,   31,  103,   66,   67,
    +          144,  107,  108,   26,    8,    9,   10,   91,   92,   61,
    +          116,  117,  118,  119,  100,  101,   65,   26,  142,  143,
    +           26,   25,   71,   27,   28,   29,   75,  125,  126,   78,
    +           79,   80,  138,   82,   26,   84,   26,   86,  144,   26,
    +           89,  142,  143,   26,   93,   94,   95,   26,   65,   98,
    +           99,  142,  143,  102,   71,   72,  105,  106,   75,  142,
    +          143,   78,   79,   80,  113,   82,   31,   84,   61,   86,
    +           61,   68,   89,   61,   73,   61,   93,   94,   95,   12,
    +           61,   98,   99,   88,   62,  102,   71,   71,  105,  106,
    +           88,   71,   88,  142,  143,   71,  113,   71,   71,   71,
    +           65,   71,   71,   71,   71,   71,   71,   71,   71,   71,
    +           75,   88,   73,   78,   79,   80,   73,   82,   73,   84,
    +           73,   86,   73,  117,   89,  142,  143,   76,   93,   94,
    +           95,   12,   65,   98,   99,   76,   76,  102,   71,  121,
    +          105,  106,   75,   80,   88,   78,   79,   80,  113,   82,
    +           96,   84,   90,   86,   90,  103,   89,   96,  117,  104,
    +           93,   94,   95,   12,  120,   98,   99,  142,  120,  102,
    +          124,  142,  105,  106,  134,  122,   -1,  142,  143,  122,
    +          113,  146,   -1,  141,   65,   -1,   -1,  122,  122,   -1,
    +           71,  123,  123,  123,   75,  137,  146,   78,   79,   80,
    +           -1,   82,   -1,   84,   -1,   86,   -1,   -1,   89,  142,
    +          143,  137,   93,   94,   95,   12,   65,   98,   99,  137,
    +          137,  102,   71,  137,  105,  106,   75,  137,  137,   78,
    +           79,   80,  113,   82,  137,   84,  141,   86,  142,  141,
    +           89,  142,  142,  142,   93,   94,   95,  142,  142,   98,
    +           99,  142,  142,  102,  142,  142,  105,  106,  142,  142,
    +          142,  142,  143,  142,  113,  142,  142,  142,   65,  142,
    +          142,  142,  142,  142,   71,  142,  142,  145,   75,  143,
    +          143,   78,   79,   80,  143,   82,  143,   84,  143,   86,
    +          143,  143,   89,  142,  143,  143,   93,   94,   95,  143,
    +           65,   98,   99,  143,  143,  102,   71,  144,  105,  106,
    +           75,  144,  144,   78,   79,   80,  113,   82,  144,   84,
    +          144,   86,  144,  144,   89,   42,   43,  144,   93,   94,
    +           95,  144,  144,   98,   99,  144,  144,  102,  144,  144,
    +          105,  106,  145,  145,   61,  142,  143,  145,  113,  145,
    +          145,  145,   69,   70,  145,   65,   73,  145,  145,  145,
    +           77,   71,  145,  145,  145,   75,  145,  145,   78,   79,
    +           80,  145,   82,  145,   84,  145,   86,  142,  143,   89,
    +          146,  146,  146,   93,   94,   95,  146,  146,   98,   99,
    +          146,  146,  102,  146,  146,  105,  106,  146,  146,  146,
    +          146,  146,  146,  113,  146,  146,  146,  146,  125,  146,
    +          127,  128,  129,  130,  131,  132,  133,  148,   -1,  149,
    +          149,  149,  139,  140,   96,   97,  149,  149,  145,   96,
    +          147,  103,  142,  143,  149,  107,  108,    8,    9,   10,
    +          107,  108,  149,  149,  116,  117,  118,  119,  149,  116,
    +          117,  118,  119,   96,   25,  149,   27,   28,   29,   30,
    +           31,  149,  149,  149,  107,  108,  138,   96,  149,   -1,
    +           -1,   96,  144,  116,  117,  118,  119,  144,  107,  108,
    +           -1,   -1,  107,  108,   96,   -1,   -1,  116,  117,  118,
    +          119,  116,  117,  118,  119,  107,  108,   -1,   -1,   -1,
    +           96,  144,   -1,   -1,  116,  117,  118,  119,   -1,   -1,
    +           -1,  107,  108,   96,   -1,  144,   -1,   96,   83,  144,
    +          116,  117,  118,  119,  107,  108,   -1,   -1,  107,  108,
    +           -1,   96,  144,  116,  117,  118,  119,  116,  117,  118,
    +          119,   -1,  107,  108,   85,   -1,   -1,   -1,  144,   -1,
    +           87,  116,  117,  118,  119,   96,   -1,   -1,   -1,   96,
    +           -1,  144,   -1,   -1,   -1,  144,  107,  108,   -1,   -1,
    +          107,  108,    8,    9,   10,  116,  117,  118,  119,  116,
    +          117,  118,  119,   -1,   96,   -1,   -1,   -1,   -1,   25,
    +           -1,   27,   28,   29,   30,  107,  108,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,  116,  117,  118,  119
    +    );
    +
    +    protected static $yybase = array(
    +            0,  728,  294,  110,  817,  804,    2,  863,  859,  733,
    +          821,  788,  771,  835,  775,  757,  888,  888,  888,  888,
    +          888,  368,  377,  391,  394,  391,  410,   -2,   -2,   -2,
    +          435,  244,  244,  635,  244,  276,  603,  467,  519,  383,
    +          351,  160,  192,  551,  551,  551,  551,  690,  690,  551,
    +          551,  551,  551,  551,  551,  551,  551,  551,  551,  551,
    +          551,  551,  551,  551,  551,  551,  551,  551,  551,  551,
    +          551,  551,  551,  551,  551,  551,  551,  551,  551,  551,
    +          551,  551,  551,  551,  551,  551,  551,  551,  551,  551,
    +          551,  551,  551,  551,  551,  551,  551,  551,  551,  551,
    +          551,  551,  551,  551,  551,  551,  551,  551,  551,  551,
    +          551,  551,  551,  551,  551,  551,  551,  551,  551,  551,
    +          551,  551,  551,  551,  551,  551,  551,  551,  551,  551,
    +          551,  551,  158,  429,  468,  470,  527,  528,  529,  530,
    +          450,  456,  634,  587,  583,  413,  579,  578,  576,  574,
    +          568,  588,  567,  670,  563,  124,  124,  124,  124,  124,
    +          124,  124,  124,  124,  124,  225,  371,  206,  206,  206,
    +          206,  206,  206,  206,  206,  206,  206,  206,  206,  206,
    +          206,  206,  178,  178,   80,  683,  683,  683,  683,  683,
    +          683,  683,  683,  683,  683,  683,   -3,  396,  964,  829,
    +          167,  167,  167,  167,   13,  -25,  -25,  -25,  -25,  148,
    +          108,  209,  113,  113,  446,  446,  422,  547,  163,  163,
    +          163,  163,  163,  163,  163,  163,  163,  163,  449,  415,
    +          240,  240,  614,  614,   64,   64,   64,   64,  302,  -33,
    +          -55,  235,   -1,  256,  451,  137,  137,  137,  459,  440,
    +          460,  193,  271,  271,  271,  -24,  -24,  -24,  -24,  545,
    +          -24,  -24,  -24,  188,  216,  -50,  -50,  -29,  205,  464,
    +          594,  462,  591,  299,  482,  -41,  317,  442,  226,  454,
    +          442,  326,  332,  314,  458,   89,  226,  158,  197,  309,
    +          218,  425,  428,  531,  395,   67,   99,   32,  -23,  182,
    +          146,  143,  402,  640,  636,  186,  151,  465,  101,  -10,
    +          182,  221,  534,   88,    1,  533,  242,  365,  598,  436,
    +          618,  438,  436,  445,  365,  613,  613,  613,  613,  365,
    +          432,  618,  618,  365,  422,  618,  254,  432,  365,  444,
    +          432,  448,  613,  523,  521,  436,  439,  418,  618,  618,
    +          618,  438,  365,  613,  452,  243,  618,  613,  452,  365,
    +          445,  185,  417,  348,  605,  630,  602,  434,  560,  441,
    +          406,  621,  619,  628,  437,  430,  622,  597,  495,  518,
    +          431,  375,  407,  414,  419,  497,  412,  466,  454,  498,
    +          315,  457,  491,  457,  719,  486,  474,  453,  463,  517,
    +          370,  353,  536,  495,  648,  656,  669,  433,  532,  653,
    +          457,  714,  525,  338,  355,  617,  427,  457,  612,  457,
    +          537,  457,  647,  426,  592,  495,  315,  315,  315,  645,
    +          713,  712,  706,  699,  694,  693,  685,  409,  678,  516,
    +          655,   65,  626,  458,  490,  424,  513,  214,  677,  457,
    +          457,  541,  545,  457,  512,  524,  661,  510,  652,  447,
    +          469,  672,  440,  654,  457,  461,  671,  214,  408,  403,
    +          641,  509,  543,  604,  548,  359,  644,  606,  552,  363,
    +          595,  421,  506,  660,  659,  663,  505,  556,  420,  401,
    +          443,  609,  501,  651,  423,  483,  455,  404,  561,  416,
    +          658,  500,  499,  496,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,
    +           -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,
    +           -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,    0,    0,
    +            0,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,
    +           -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,
    +           -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,
    +           -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,
    +           -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,
    +           -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,
    +           -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,
    +           -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,
    +           -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,
    +           -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,   -2,
    +           -2,   -2,   -2,  124,  124,  124,  124,  124,  124,  124,
    +          124,  124,  124,  124,  124,  124,  124,  124,  124,  124,
    +          124,  124,  124,  124,  124,  124,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,  124,  124,  124,  124,
    +          124,  124,  124,  124,  124,  124,  124,  124,  124,  124,
    +          124,  124,  124,  124,  124,  124,  163,  163,  163,  163,
    +          163,  163,  163,  163,  163,  163,  163,  124,  124,  124,
    +          124,  124,  124,  124,  124,    0,  271,  271,  271,  271,
    +           72,   72,   72,  163,  163,  163,  163,  163,  163,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,   72,
    +           72,  271,  271,  163,  163,  -24,  -24,  -24,  -24,  -24,
    +          -50,  -50,  -50,  146,  -24,  -50,  149,  149,  149,  -50,
    +          -50,  -50,  146,    0,    0,    0,    0,    0,    0,    0,
    +          149,    0,    0,    0,  432,  618,    0,    0,    0,  149,
    +          316,  316,  316,  316,  214,  182,    0,  495,  432,    0,
    +          439,  432,    0,    0,    0,  618,    0,    0,    0,    0,
    +            0,    0,  338,  532,  333,  495,    0,    0,    0,    0,
    +            0,    0,    0,  495,  217,  217,    0,    0,  409,    0,
    +            0,    0,    0,  333,    0,    0,  214
    +    );
    +
    +    protected static $yydefault = array(
    +            3,32767,32767,    1,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,  106,   98,  112,   97,
    +          108,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,  377,  377,32767,  334,32767,32767,32767,32767,32767,
    +        32767,32767,32767,  179,  179,  179,32767,32767,32767,  366,
    +          366,  366,  366,  366,  366,  366,  366,  366,  366,32767,
    +        32767,32767,32767,32767,  257,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,  262,  382,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,  238,  239,  241,  242,  178,
    +          367,  131,  263,  381,  177,  205,  207,  256,  206,  183,
    +          188,  189,  190,  191,  192,  193,  194,  195,  196,  197,
    +          198,  182,  235,  234,  203,  331,  331,  334,32767,32767,
    +        32767,32767,32767,32767,32767,32767,  204,  208,  210,  209,
    +          225,  226,  223,  224,  181,  227,  228,  229,  230,  163,
    +          163,  163,32767,32767,  376,  376,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,  164,32767,
    +          217,  218,  292,  292,  122,  122,  122,  122,  122,32767,
    +        32767,32767,32767,32767,  300,32767,32767,32767,32767,32767,
    +          302,32767,  212,  213,  211,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,  339,  301,32767,32767,32767,32767,
    +        32767,32767,32767,32767,  352,  288,32767,32767,32767,  281,
    +        32767,  109,  111,   61,  318,32767,32767,32767,32767,32767,
    +          357,32767,32767,32767,   17,32767,32767,32767,  389,  352,
    +        32767,32767,   19,32767,32767,32767,32767,  233,32767,32767,
    +          356,  350,32767,32767,32767,32767,32767,   65,  297,32767,
    +          303,32767,32767,32767,   65,32767,32767,32767,32767,   65,
    +        32767,  355,  354,   65,32767,  282,  333,32767,   65,   76,
    +        32767,   74,32767,   95,   95,32767,32767,   78,  329,  345,
    +        32767,32767,   65,32767,  270,  333,32767,32767,  270,   65,
    +        32767,32767,    4,  307,32767,32767,32767,32767,32767,32767,
    +        32767,32767,32767,32767,32767,32767,32767,32767,  283,32767,
    +        32767,32767,  253,  254,  341,32767,  342,32767,  281,32767,
    +          221,  200,32767,  202,32767,32767,  286,  289,32767,32767,
    +        32767,  140,32767,  284,32767,  186,32767,32767,32767,32767,
    +          384,32767,32767,  180,32767,32767,32767,  136,32767,   63,
    +        32767,  374,32767,32767,  350,  285,  214,  215,  216,32767,
    +        32767,32767,32767,32767,32767,32767,32767,  351,32767,32767,
    +        32767,  116,32767,  318,32767,32767,32767,   77,32767,  184,
    +          132,32767,32767,  383,32767,32767,32767,32767,32767,32767,
    +          338,32767,32767,32767,   64,32767,32767,   79,32767,32767,
    +          350,32767,32767,32767,32767,  120,32767,32767,32767,  175,
    +        32767,32767,32767,32767,32767,  350,32767,32767,32767,32767,
    +        32767,32767,32767,32767,    4,32767,  157,32767,32767,32767,
    +        32767,32767,32767,32767,   25,   25,    3,   25,  103,   25,
    +          143,    3,   95,   95,   58,  143,   25,  143,   25,   25,
    +           25,   25,   25,   25,   25,  150,   25,   25,   25,   25,
    +           25
    +    );
    +
    +    protected static $yygoto = array(
    +          161,  135,  135,  140,  135,  161,  136,  137,  138,  143,
    +          145,  169,  163,  159,  159,  159,  159,  140,  140,  160,
    +          160,  160,  160,  160,  160,  160,  160,  160,  160,  155,
    +          156,  157,  158,  167,  134,  750,  751,  390,  753,  774,
    +          775,  776,  777,  778,  779,  780,  782,  718,  139,  141,
    +          142,  144,  165,  166,  168,  184,  196,  197,  198,  199,
    +          200,  201,  202,  203,  205,  206,  207,  208,  230,  231,
    +          252,  253,  254,  426,  427,  428,  170,  171,  172,  173,
    +          174,  175,  176,  177,  178,  179,  180,  181,  146,  147,
    +          148,  162,  149,  164,  150,  182,  151,  152,  153,  183,
    +          154,  132,  443,  443,  443,  443,  443,  443,  443,  443,
    +          443,  443,  443,  311,  485,  421,  421,  449,  417,  419,
    +          419,  391,  393,  410,  424,  450,  453,  464,  470,  335,
    +          335,  335,  335,  335,  335,  335,  335,  335,  335,  335,
    +          335,  335,  335,  335,  335,  646,  646,  906,  906,  813,
    +          813,  654,  654,  654,  654,  654,  405,  538,  538,  538,
    +          495,  444,  444,  444,  444,  444,  444,  444,  444,  444,
    +          444,  444,  611,  611,  611,  611,  270,  606,  612,  490,
    +          392,  392,  392,  392,  392,  392,  392,  392,  392,  392,
    +          392,  392,  392,  392,  392,  392,  539,  539,  539,  582,
    +          395,  395,    5,  878,   16,  210,    6,  211,  396,  396,
    +          537,  537,  537,    7,  422,   17,   18,    8,   19,    9,
    +           10,   11,  910,   20,   12,   13,   14,   15,  455,  483,
    +          632,  617,  615,  613,  615,  508,  398,  641,  636,  850,
    +          850,  850,  850,  850,  850,  850,  850,  850,  850,  850,
    +          430,  431,  432,  433,  434,  435,  436,  438,  466,  835,
    +          458,  463,  500,  467,  273,  315,  830,    1,  697,  316,
    +          809,  810,    2,  771,   26,   21,  285,  554,  672,  621,
    +          852,  853,  868,  652,  707,  276,  661,  807,  877,  807,
    +          439,  291,  250,  885,  885,  808,  241,  886,  886,  294,
    +          476,   29,  294,  916,  916,  481,  901,  901,  901,  866,
    +          292,  484,  919,  916,  408,  903,  299,  299,  299,  418,
    +          884,  304,  397,  397,  429,  716,  762,  404,  919,  919,
    +          299,  825,  824,  459,  650,  546,  664,  851,  518,  310,
    +          488,  404,  404,  312,  271,  272,  552,  804,  669,  620,
    +          863,  487,  403,    0,  705,    0,    0,    0,    0,  302,
    +            0,    0,  425,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,    0,  409
    +    );
    +
    +    protected static $yygcheck = array(
    +           24,   24,   24,   24,   24,   24,   24,   24,   24,   24,
    +           24,   24,   24,   24,   24,   24,   24,   24,   24,   24,
    +           24,   24,   24,   24,   24,   24,   24,   24,   24,   24,
    +           24,   24,   24,   24,   24,   24,   24,   24,   24,   24,
    +           24,   24,   24,   24,   24,   24,   24,   24,   24,   24,
    +           24,   24,   24,   24,   24,   24,   24,   24,   24,   24,
    +           24,   24,   24,   24,   24,   24,   24,   24,   24,   24,
    +           24,   24,   24,   24,   24,   24,   24,   24,   24,   24,
    +           24,   24,   24,   24,   24,   24,   24,   24,   24,   24,
    +           24,   24,   24,   24,   24,   24,   24,   24,   24,   24,
    +           24,   24,   38,   38,   38,   38,   38,   38,   38,   38,
    +           38,   38,   38,   32,   32,   32,   32,   32,   32,   32,
    +           32,   32,   32,   32,   32,   32,   32,   32,   32,   38,
    +           38,   38,   38,   38,   38,   38,   38,   38,   38,   38,
    +           38,   38,   38,   38,   38,   53,   53,   53,   53,   38,
    +           38,   38,   38,   38,   38,   38,   75,    6,    6,    6,
    +           38,   92,   92,   92,   92,   92,   92,   92,   92,   92,
    +           92,   92,   38,   38,   38,   38,   48,   38,   38,   38,
    +           89,   89,   89,   89,   89,   89,   89,   89,   89,   89,
    +           89,   89,   89,   89,   89,   89,    7,    7,    7,   31,
    +           89,   89,   13,   57,   13,   44,   13,   44,   92,   92,
    +            5,    5,    5,   13,   83,   13,   13,   13,   13,   13,
    +           13,   13,  112,   13,   13,   13,   13,   13,   21,   21,
    +            5,    5,    5,    5,    5,    5,    5,    5,    5,   99,
    +           99,   99,   99,   99,   99,   99,   99,   99,   99,   99,
    +           84,   84,   84,   84,   84,   84,   84,   84,   84,   57,
    +           40,   40,   40,   46,   46,   46,   15,    2,   72,   72,
    +           57,   57,    2,   15,   15,   15,   15,   12,   12,   12,
    +           12,   12,   12,   12,   12,    4,   59,   57,   57,   57,
    +           15,   28,   98,   91,   91,   57,   98,   90,   90,    4,
    +          101,   15,    4,  113,  113,   15,   91,   91,   91,  104,
    +           39,   30,  113,  113,   39,  110,   96,   96,   96,   39,
    +           91,   29,   95,   95,   25,   75,   76,   25,  113,  113,
    +           96,   97,   97,   39,   55,   10,   60,  100,   50,   96,
    +           39,   25,   25,    9,   48,   48,   11,   87,   61,   47,
    +          103,   82,    4,   -1,   74,   -1,   -1,   -1,   -1,    4,
    +           -1,   -1,    4,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,   -1,
    +           -1,   -1,   -1,   75
    +    );
    +
    +    protected static $yygbase = array(
    +            0,    0, -239,    0,   22,  209,  156,  195,    0,   21,
    +           55,    1,   89, -303,    0,  -52,    0,    0,    0,    0,
    +            0,  184,    0,    0,  -30,  294,    0,    0,  245,  102,
    +           98,  174,  -99,    0,    0,    0,    0,    0,  -83,  -19,
    +           25,    0,    0,    0, -310,    0,    7,   -2, -168,    0,
    +           51,    0,    0,  -67,    0,   96,    0,  -61,    0,  251,
    +           50,    2,    0,    0,    0,    0,    0,    0,    0,    0,
    +            0,    0,   40,    0,   -6,  109,   93,    0,    0,    0,
    +            0,    0,   -7,  182,  200,    0,    0,   23,    0,  -32,
    +           65,   61,  -24,    0,    0,   90,   71,   85,   48,   54,
    +           49,  114,    0,   -5,  122,    0,    0,    0,    0,    0,
    +          100,    0,  188,   63,    0
    +    );
    +
    +    protected static $yygdefault = array(
    +        -32768,  361,    3,  533,  378,  557,  558,  559,  295,  293,
    +          547,  553,  460,    4,  555,  763,  281,  562,  282,  469,
    +          564,  412,  566,  567,  133,  379,  296,  297,  413,  303,
    +          456,  581,  204,  301,  583,  283,  585,  590,  284,  489,
    +          440,  380,  347,  451,  209,  420,  447,  619,  269,  627,
    +          521,  635,  638,  381,  441,  649,  352,  806,  308,  660,
    +          665,  670,  673,  323,  313,  465,  677,  678,  243,  682,
    +          496,  497,  696,  228,  704,  717,  320,  781,  783,  382,
    +          383,  406,  474,  394,  411,  800,  314,  803,  384,  385,
    +          331,  332,  821,  818,  275,  871,  274,  349,  240,  856,
    +          857,  461,  355,  909,  867,  264,  386,  387,  290,  305,
    +          904,  336,  911,  918,  448
    +    );
    +
    +    protected static $yylhs = array(
    +            0,    1,    2,    2,    4,    4,    3,    3,    3,    3,
    +            3,    3,    3,    3,    3,    8,    8,   10,   10,   10,
    +           10,    9,    9,   11,   13,   13,   14,   14,   14,   14,
    +            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
    +            5,    5,    5,    5,    5,    5,    5,    5,    5,    5,
    +            5,    5,    5,    5,    5,    5,    5,    5,   35,   35,
    +           37,   36,   36,   29,   29,   39,   39,    6,    7,    7,
    +            7,   41,   41,   41,   42,   42,   45,   45,   43,   43,
    +           46,   46,   22,   22,   31,   31,   34,   34,   33,   33,
    +           47,   23,   23,   23,   23,   48,   48,   49,   49,   50,
    +           50,   20,   20,   16,   16,   51,   18,   18,   52,   17,
    +           17,   19,   19,   30,   30,   30,   40,   40,   54,   54,
    +           55,   55,   56,   56,   56,   56,   57,   57,   57,   58,
    +           58,   59,   59,   26,   26,   60,   60,   60,   27,   27,
    +           61,   61,   44,   44,   62,   62,   62,   62,   67,   67,
    +           68,   68,   69,   69,   69,   69,   70,   71,   71,   66,
    +           66,   63,   63,   65,   65,   73,   73,   72,   72,   72,
    +           72,   72,   72,   64,   64,   74,   74,   28,   28,   21,
    +           21,   24,   24,   24,   24,   24,   24,   24,   24,   24,
    +           24,   24,   24,   24,   24,   24,   24,   24,   24,   24,
    +           24,   24,   24,   24,   24,   24,   24,   24,   24,   24,
    +           24,   24,   24,   24,   24,   24,   24,   24,   24,   24,
    +           24,   24,   24,   24,   24,   24,   24,   24,   24,   24,
    +           24,   24,   24,   24,   24,   24,   24,   24,   24,   24,
    +           24,   24,   24,   24,   24,   24,   24,   24,   24,   24,
    +           24,   24,   24,   24,   24,   24,   24,   24,   24,   24,
    +           15,   15,   25,   25,   79,   79,   80,   80,   80,   75,
    +           82,   82,   86,   86,   87,   88,   88,   88,   88,   88,
    +           88,   92,   92,   38,   38,   38,   76,   76,   93,   93,
    +           89,   89,   94,   94,   94,   94,   94,   77,   77,   77,
    +           81,   81,   81,   85,   85,   99,   99,   99,   99,   99,
    +           99,   99,   99,   99,   99,   99,   99,   99,   99,   12,
    +           12,   12,   12,   12,   12,   78,   78,   78,   78,  100,
    +          100,  101,  101,  103,  103,  102,  102,  104,  104,   32,
    +           32,   32,   32,  106,  106,  105,  105,  105,  105,  105,
    +          107,  107,   91,   91,   95,   95,   90,   90,  108,  108,
    +          108,  108,   96,   96,   96,   96,   84,   84,   97,   97,
    +           97,   53,  109,  109,  110,  110,  110,   83,   83,  111,
    +          111,  112,  112,  112,  112,   98,   98,   98,   98,  113,
    +          113,  113,  113,  113,  113,  113,  114,  114,  114
    +    );
    +
    +    protected static $yylen = array(
    +            1,    1,    2,    0,    1,    3,    1,    1,    1,    1,
    +            3,    5,    4,    3,    3,    3,    1,    1,    3,    2,
    +            4,    3,    1,    3,    2,    0,    1,    1,    1,    1,
    +            3,    5,    8,    3,    5,    9,    3,    2,    3,    2,
    +            3,    2,    3,    2,    3,    3,    3,    1,    2,    5,
    +            7,    9,    5,    1,    6,    3,    3,    2,    0,    2,
    +            8,    0,    4,    1,    3,    0,    1,    9,    7,    6,
    +            5,    1,    2,    2,    0,    2,    0,    2,    0,    2,
    +            1,    3,    1,    4,    1,    4,    1,    4,    1,    3,
    +            3,    3,    4,    4,    5,    0,    2,    4,    3,    1,
    +            1,    1,    4,    0,    2,    3,    0,    2,    4,    0,
    +            2,    0,    3,    1,    2,    1,    1,    0,    1,    3,
    +            3,    5,    0,    1,    1,    1,    2,    3,    3,    1,
    +            3,    1,    2,    3,    1,    1,    2,    4,    3,    1,
    +            1,    3,    2,    0,    3,    3,    8,    3,    1,    3,
    +            0,    2,    4,    5,    4,    4,    3,    1,    1,    1,
    +            3,    1,    1,    0,    1,    1,    2,    1,    1,    1,
    +            1,    1,    1,    1,    3,    1,    3,    3,    1,    0,
    +            1,    1,    3,    3,    4,    4,    1,    2,    3,    3,
    +            3,    3,    3,    3,    3,    3,    3,    3,    3,    2,
    +            2,    2,    2,    3,    3,    3,    3,    3,    3,    3,
    +            3,    3,    3,    3,    3,    3,    3,    3,    3,    2,
    +            2,    2,    2,    3,    3,    3,    3,    3,    3,    3,
    +            3,    3,    1,    3,    5,    4,    4,    4,    2,    2,
    +            2,    2,    2,    2,    2,    2,    2,    2,    2,    2,
    +            2,    2,    1,    1,    1,    3,    2,    1,    9,   10,
    +            3,    3,    2,    4,    4,    3,    4,    4,    4,    3,
    +            0,    4,    1,    3,    2,    2,    4,    6,    2,    2,
    +            4,    1,    1,    1,    2,    3,    1,    1,    1,    1,
    +            1,    1,    0,    3,    3,    4,    4,    0,    2,    1,
    +            0,    1,    1,    0,    1,    1,    1,    1,    1,    1,
    +            1,    1,    1,    1,    1,    1,    3,    2,    1,    1,
    +            3,    2,    2,    4,    3,    1,    3,    3,    3,    1,
    +            1,    0,    2,    0,    1,    3,    1,    3,    1,    1,
    +            1,    1,    1,    6,    4,    3,    4,    2,    4,    4,
    +            1,    3,    1,    2,    1,    1,    4,    1,    3,    6,
    +            4,    4,    4,    4,    1,    4,    0,    1,    1,    3,
    +            1,    4,    3,    1,    1,    1,    0,    0,    2,    3,
    +            1,    3,    1,    4,    2,    2,    2,    1,    2,    1,
    +            4,    3,    3,    3,    6,    3,    1,    1,    1
    +    );
    +
    +    protected $yyval;
    +    protected $yyastk;
    +    protected $stackPos;
    +    protected $lexer;
    +
    +    /**
    +     * Creates a parser instance.
    +     *
    +     * @param PHPParser_Lexer $lexer A lexer
    +     */
    +    public function __construct(PHPParser_Lexer $lexer) {
    +        $this->lexer = $lexer;
    +    }
    +
    +    /**
    +     * Parses PHP code into a node tree.
    +     *
    +     * @param string $code The source code to parse
    +     *
    +     * @return PHPParser_Node[] Array of statements
    +     */
    +    public function parse($code) {
    +        $this->lexer->startLexing($code);
    +
    +        // We start off with no lookahead-token
    +        $tokenId = self::TOKEN_NONE;
    +
    +        // The attributes for a node are taken from the first and last token of the node.
    +        // From the first token only the startAttributes are taken and from the last only
    +        // the endAttributes. Both are merged using the array union operator (+).
    +        $startAttributes = array('startLine' => 1);
    +        $endAttributes   = array();
    +
    +        // In order to figure out the attributes for the starting token, we have to keep
    +        // them in a stack
    +        $attributeStack = array($startAttributes);
    +
    +        // Start off in the initial state and keep a stack of previous states
    +        $state = 0;
    +        $stateStack = array($state);
    +
    +        // AST stack (?)
    +        $this->yyastk = array();
    +
    +        // Current position in the stack(s)
    +        $this->stackPos = 0;
    +
    +        for (;;) {
    +            if (self::$yybase[$state] == 0) {
    +                $yyn = self::$yydefault[$state];
    +            } else {
    +                if ($tokenId === self::TOKEN_NONE) {
    +                    // Fetch the next token id from the lexer and fetch additional info by-ref.
    +                    // The end attributes are fetched into a temporary variable and only set once the token is really
    +                    // shifted (not during read). Otherwise you would sometimes get off-by-one errors, when a rule is
    +                    // reduced after a token was read but not yet shifted.
    +                    $origTokenId = $this->lexer->getNextToken($tokenValue, $startAttributes, $nextEndAttributes);
    +
    +                    // map the lexer token id to the internally used token id's
    +                    $tokenId = $origTokenId >= 0 && $origTokenId < self::TOKEN_MAP_SIZE
    +                        ? self::$translate[$origTokenId]
    +                        : self::TOKEN_INVALID;
    +
    +                    if ($tokenId === self::TOKEN_INVALID) {
    +                        throw new RangeException(sprintf(
    +                            'The lexer returned an invalid token (id=%d, value=%s)',
    +                            $origTokenId, $tokenValue
    +                        ));
    +                    }
    +
    +                    $attributeStack[$this->stackPos] = $startAttributes;
    +                }
    +
    +                if ((($yyn = self::$yybase[$state] + $tokenId) >= 0
    +                     && $yyn < self::YYLAST && self::$yycheck[$yyn] == $tokenId
    +                     || ($state < self::YY2TBLSTATE
    +                        && ($yyn = self::$yybase[$state + self::YYNLSTATES] + $tokenId) >= 0
    +                        && $yyn < self::YYLAST
    +                        && self::$yycheck[$yyn] == $tokenId))
    +                    && ($yyn = self::$yyaction[$yyn]) != self::YYDEFAULT) {
    +                    /*
    +                     * >= YYNLSTATE: shift and reduce
    +                     * > 0: shift
    +                     * = 0: accept
    +                     * < 0: reduce
    +                     * = -YYUNEXPECTED: error
    +                     */
    +                    if ($yyn > 0) {
    +                        /* shift */
    +                        ++$this->stackPos;
    +
    +                        $stateStack[$this->stackPos]     = $state = $yyn;
    +                        $this->yyastk[$this->stackPos]   = $tokenValue;
    +                        $attributeStack[$this->stackPos] = $startAttributes;
    +                        $endAttributes = $nextEndAttributes;
    +                        $tokenId = self::TOKEN_NONE;
    +
    +                        if ($yyn < self::YYNLSTATES)
    +                            continue;
    +
    +                        /* $yyn >= YYNLSTATES means shift-and-reduce */
    +                        $yyn -= self::YYNLSTATES;
    +                    } else {
    +                        $yyn = -$yyn;
    +                    }
    +                } else {
    +                    $yyn = self::$yydefault[$state];
    +                }
    +            }
    +
    +            for (;;) {
    +                /* reduce/error */
    +                if ($yyn == 0) {
    +                    /* accept */
    +                    return $this->yyval;
    +                } elseif ($yyn != self::YYUNEXPECTED) {
    +                    /* reduce */
    +                    try {
    +                        $this->{'yyn' . $yyn}(
    +                            $attributeStack[$this->stackPos - self::$yylen[$yyn]]
    +                            + $endAttributes
    +                        );
    +                    } catch (PHPParser_Error $e) {
    +                        if (-1 === $e->getRawLine()) {
    +                            $e->setRawLine($startAttributes['startLine']);
    +                        }
    +
    +                        throw $e;
    +                    }
    +
    +                    /* Goto - shift nonterminal */
    +                    $this->stackPos -= self::$yylen[$yyn];
    +                    $yyn = self::$yylhs[$yyn];
    +                    if (($yyp = self::$yygbase[$yyn] + $stateStack[$this->stackPos]) >= 0
    +                         && $yyp < self::YYGLAST
    +                         && self::$yygcheck[$yyp] == $yyn) {
    +                        $state = self::$yygoto[$yyp];
    +                    } else {
    +                        $state = self::$yygdefault[$yyn];
    +                    }
    +
    +                    ++$this->stackPos;
    +
    +                    $stateStack[$this->stackPos]     = $state;
    +                    $this->yyastk[$this->stackPos]   = $this->yyval;
    +                    $attributeStack[$this->stackPos] = $startAttributes;
    +                } else {
    +                    /* error */
    +                    $expected = array();
    +
    +                    $base = self::$yybase[$state];
    +                    for ($i = 0; $i < self::TOKEN_MAP_SIZE; ++$i) {
    +                        $n = $base + $i;
    +                        if ($n >= 0 && $n < self::YYLAST && self::$yycheck[$n] == $i
    +                         || $state < self::YY2TBLSTATE
    +                            && ($n = self::$yybase[$state + self::YYNLSTATES] + $i) >= 0
    +                            && $n < self::YYLAST && self::$yycheck[$n] == $i
    +                        ) {
    +                            if (self::$yyaction[$n] != self::YYUNEXPECTED) {
    +                                if (count($expected) == 4) {
    +                                    /* Too many expected tokens */
    +                                    $expected = array();
    +                                    break;
    +                                }
    +
    +                                $expected[] = self::$terminals[$i];
    +                            }
    +                        }
    +                    }
    +
    +                    $expectedString = '';
    +                    if ($expected) {
    +                        $expectedString = ', expecting ' . implode(' or ', $expected);
    +                    }
    +
    +                    throw new PHPParser_Error(
    +                        'Syntax error, unexpected ' . self::$terminals[$tokenId] . $expectedString,
    +                        $startAttributes['startLine']
    +                    );
    +                }
    +
    +                if ($state < self::YYNLSTATES)
    +                    break;
    +                /* >= YYNLSTATES means shift-and-reduce */
    +                $yyn = $state - self::YYNLSTATES;
    +            }
    +        }
    +    }
    +
    +    protected function yyn0() {
    +        $this->yyval = $this->yyastk[$this->stackPos];
    +    }
    +
    +    protected function yyn1($attributes) {
    +         $this->yyval = PHPParser_Node_Stmt_Namespace::postprocess($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn2($attributes) {
    +         if (is_array($this->yyastk[$this->stackPos-(2-2)])) { $this->yyval = array_merge($this->yyastk[$this->stackPos-(2-1)], $this->yyastk[$this->stackPos-(2-2)]); } else { $this->yyastk[$this->stackPos-(2-1)][] = $this->yyastk[$this->stackPos-(2-2)]; $this->yyval = $this->yyastk[$this->stackPos-(2-1)]; }; 
    +    }
    +
    +    protected function yyn3($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn4($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn5($attributes) {
    +         $this->yyastk[$this->stackPos-(3-1)][] = $this->yyastk[$this->stackPos-(3-3)]; $this->yyval = $this->yyastk[$this->stackPos-(3-1)]; 
    +    }
    +
    +    protected function yyn6($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn7($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn8($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn9($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_HaltCompiler($this->lexer->handleHaltCompiler(), $attributes); 
    +    }
    +
    +    protected function yyn10($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Namespace(new PHPParser_Node_Name($this->yyastk[$this->stackPos-(3-2)], $attributes), null, $attributes); 
    +    }
    +
    +    protected function yyn11($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Namespace(new PHPParser_Node_Name($this->yyastk[$this->stackPos-(5-2)], $attributes), $this->yyastk[$this->stackPos-(5-4)], $attributes); 
    +    }
    +
    +    protected function yyn12($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Namespace(null, $this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn13($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Use($this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn14($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Const($this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn15($attributes) {
    +         $this->yyastk[$this->stackPos-(3-1)][] = $this->yyastk[$this->stackPos-(3-3)]; $this->yyval = $this->yyastk[$this->stackPos-(3-1)]; 
    +    }
    +
    +    protected function yyn16($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn17($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_UseUse(new PHPParser_Node_Name($this->yyastk[$this->stackPos-(1-1)], $attributes), null, $attributes); 
    +    }
    +
    +    protected function yyn18($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_UseUse(new PHPParser_Node_Name($this->yyastk[$this->stackPos-(3-1)], $attributes), $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn19($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_UseUse(new PHPParser_Node_Name($this->yyastk[$this->stackPos-(2-2)], $attributes), null, $attributes); 
    +    }
    +
    +    protected function yyn20($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_UseUse(new PHPParser_Node_Name($this->yyastk[$this->stackPos-(4-2)], $attributes), $this->yyastk[$this->stackPos-(4-4)], $attributes); 
    +    }
    +
    +    protected function yyn21($attributes) {
    +         $this->yyastk[$this->stackPos-(3-1)][] = $this->yyastk[$this->stackPos-(3-3)]; $this->yyval = $this->yyastk[$this->stackPos-(3-1)]; 
    +    }
    +
    +    protected function yyn22($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn23($attributes) {
    +         $this->yyval = new PHPParser_Node_Const($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn24($attributes) {
    +         if (is_array($this->yyastk[$this->stackPos-(2-2)])) { $this->yyval = array_merge($this->yyastk[$this->stackPos-(2-1)], $this->yyastk[$this->stackPos-(2-2)]); } else { $this->yyastk[$this->stackPos-(2-1)][] = $this->yyastk[$this->stackPos-(2-2)]; $this->yyval = $this->yyastk[$this->stackPos-(2-1)]; }; 
    +    }
    +
    +    protected function yyn25($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn26($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn27($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn28($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn29($attributes) {
    +         throw new PHPParser_Error('__halt_compiler() can only be used from the outermost scope'); 
    +    }
    +
    +    protected function yyn30($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(3-2)]; 
    +    }
    +
    +    protected function yyn31($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_If($this->yyastk[$this->stackPos-(5-2)], array('stmts' => is_array($this->yyastk[$this->stackPos-(5-3)]) ? $this->yyastk[$this->stackPos-(5-3)] : array($this->yyastk[$this->stackPos-(5-3)]), 'elseifs' => $this->yyastk[$this->stackPos-(5-4)], 'else' => $this->yyastk[$this->stackPos-(5-5)]), $attributes); 
    +    }
    +
    +    protected function yyn32($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_If($this->yyastk[$this->stackPos-(8-2)], array('stmts' => $this->yyastk[$this->stackPos-(8-4)], 'elseifs' => $this->yyastk[$this->stackPos-(8-5)], 'else' => $this->yyastk[$this->stackPos-(8-6)]), $attributes); 
    +    }
    +
    +    protected function yyn33($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_While($this->yyastk[$this->stackPos-(3-2)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn34($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Do($this->yyastk[$this->stackPos-(5-4)], is_array($this->yyastk[$this->stackPos-(5-2)]) ? $this->yyastk[$this->stackPos-(5-2)] : array($this->yyastk[$this->stackPos-(5-2)]), $attributes); 
    +    }
    +
    +    protected function yyn35($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_For(array('init' => $this->yyastk[$this->stackPos-(9-3)], 'cond' => $this->yyastk[$this->stackPos-(9-5)], 'loop' => $this->yyastk[$this->stackPos-(9-7)], 'stmts' => $this->yyastk[$this->stackPos-(9-9)]), $attributes); 
    +    }
    +
    +    protected function yyn36($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Switch($this->yyastk[$this->stackPos-(3-2)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn37($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Break(null, $attributes); 
    +    }
    +
    +    protected function yyn38($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Break($this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn39($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Continue(null, $attributes); 
    +    }
    +
    +    protected function yyn40($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Continue($this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn41($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Return(null, $attributes); 
    +    }
    +
    +    protected function yyn42($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Return($this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn43($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(2-1)]; 
    +    }
    +
    +    protected function yyn44($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Global($this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn45($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Static($this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn46($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Echo($this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn47($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_InlineHTML($this->yyastk[$this->stackPos-(1-1)], $attributes); 
    +    }
    +
    +    protected function yyn48($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(2-1)]; 
    +    }
    +
    +    protected function yyn49($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Unset($this->yyastk[$this->stackPos-(5-3)], $attributes); 
    +    }
    +
    +    protected function yyn50($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Foreach($this->yyastk[$this->stackPos-(7-3)], $this->yyastk[$this->stackPos-(7-5)][0], array('keyVar' => null, 'byRef' => $this->yyastk[$this->stackPos-(7-5)][1], 'stmts' => $this->yyastk[$this->stackPos-(7-7)]), $attributes); 
    +    }
    +
    +    protected function yyn51($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Foreach($this->yyastk[$this->stackPos-(9-3)], $this->yyastk[$this->stackPos-(9-7)][0], array('keyVar' => $this->yyastk[$this->stackPos-(9-5)], 'byRef' => $this->yyastk[$this->stackPos-(9-7)][1], 'stmts' => $this->yyastk[$this->stackPos-(9-9)]), $attributes); 
    +    }
    +
    +    protected function yyn52($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Declare($this->yyastk[$this->stackPos-(5-3)], $this->yyastk[$this->stackPos-(5-5)], $attributes); 
    +    }
    +
    +    protected function yyn53($attributes) {
    +         $this->yyval = array(); /* means: no statement */ 
    +    }
    +
    +    protected function yyn54($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_TryCatch($this->yyastk[$this->stackPos-(6-3)], $this->yyastk[$this->stackPos-(6-5)], $this->yyastk[$this->stackPos-(6-6)], $attributes); 
    +    }
    +
    +    protected function yyn55($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Throw($this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn56($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Goto($this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn57($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Label($this->yyastk[$this->stackPos-(2-1)], $attributes); 
    +    }
    +
    +    protected function yyn58($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn59($attributes) {
    +         $this->yyastk[$this->stackPos-(2-1)][] = $this->yyastk[$this->stackPos-(2-2)]; $this->yyval = $this->yyastk[$this->stackPos-(2-1)]; 
    +    }
    +
    +    protected function yyn60($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Catch($this->yyastk[$this->stackPos-(8-3)], substr($this->yyastk[$this->stackPos-(8-4)], 1), $this->yyastk[$this->stackPos-(8-7)], $attributes); 
    +    }
    +
    +    protected function yyn61($attributes) {
    +         $this->yyval = null; 
    +    }
    +
    +    protected function yyn62($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(4-3)]; 
    +    }
    +
    +    protected function yyn63($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn64($attributes) {
    +         $this->yyastk[$this->stackPos-(3-1)][] = $this->yyastk[$this->stackPos-(3-3)]; $this->yyval = $this->yyastk[$this->stackPos-(3-1)]; 
    +    }
    +
    +    protected function yyn65($attributes) {
    +         $this->yyval = false; 
    +    }
    +
    +    protected function yyn66($attributes) {
    +         $this->yyval = true; 
    +    }
    +
    +    protected function yyn67($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Function($this->yyastk[$this->stackPos-(9-3)], array('byRef' => $this->yyastk[$this->stackPos-(9-2)], 'params' => $this->yyastk[$this->stackPos-(9-5)], 'stmts' => $this->yyastk[$this->stackPos-(9-8)]), $attributes); 
    +    }
    +
    +    protected function yyn68($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Class($this->yyastk[$this->stackPos-(7-2)], array('type' => $this->yyastk[$this->stackPos-(7-1)], 'extends' => $this->yyastk[$this->stackPos-(7-3)], 'implements' => $this->yyastk[$this->stackPos-(7-4)], 'stmts' => $this->yyastk[$this->stackPos-(7-6)]), $attributes); 
    +    }
    +
    +    protected function yyn69($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Interface($this->yyastk[$this->stackPos-(6-2)], array('extends' => $this->yyastk[$this->stackPos-(6-3)], 'stmts' => $this->yyastk[$this->stackPos-(6-5)]), $attributes); 
    +    }
    +
    +    protected function yyn70($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Trait($this->yyastk[$this->stackPos-(5-2)], $this->yyastk[$this->stackPos-(5-4)], $attributes); 
    +    }
    +
    +    protected function yyn71($attributes) {
    +         $this->yyval = 0; 
    +    }
    +
    +    protected function yyn72($attributes) {
    +         $this->yyval = PHPParser_Node_Stmt_Class::MODIFIER_ABSTRACT; 
    +    }
    +
    +    protected function yyn73($attributes) {
    +         $this->yyval = PHPParser_Node_Stmt_Class::MODIFIER_FINAL; 
    +    }
    +
    +    protected function yyn74($attributes) {
    +         $this->yyval = null; 
    +    }
    +
    +    protected function yyn75($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(2-2)]; 
    +    }
    +
    +    protected function yyn76($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn77($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(2-2)]; 
    +    }
    +
    +    protected function yyn78($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn79($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(2-2)]; 
    +    }
    +
    +    protected function yyn80($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn81($attributes) {
    +         $this->yyastk[$this->stackPos-(3-1)][] = $this->yyastk[$this->stackPos-(3-3)]; $this->yyval = $this->yyastk[$this->stackPos-(3-1)]; 
    +    }
    +
    +    protected function yyn82($attributes) {
    +         $this->yyval = is_array($this->yyastk[$this->stackPos-(1-1)]) ? $this->yyastk[$this->stackPos-(1-1)] : array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn83($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(4-2)]; 
    +    }
    +
    +    protected function yyn84($attributes) {
    +         $this->yyval = is_array($this->yyastk[$this->stackPos-(1-1)]) ? $this->yyastk[$this->stackPos-(1-1)] : array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn85($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(4-2)]; 
    +    }
    +
    +    protected function yyn86($attributes) {
    +         $this->yyval = is_array($this->yyastk[$this->stackPos-(1-1)]) ? $this->yyastk[$this->stackPos-(1-1)] : array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn87($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(4-2)]; 
    +    }
    +
    +    protected function yyn88($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn89($attributes) {
    +         $this->yyastk[$this->stackPos-(3-1)][] = $this->yyastk[$this->stackPos-(3-3)]; $this->yyval = $this->yyastk[$this->stackPos-(3-1)]; 
    +    }
    +
    +    protected function yyn90($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_DeclareDeclare($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn91($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(3-2)]; 
    +    }
    +
    +    protected function yyn92($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(4-3)]; 
    +    }
    +
    +    protected function yyn93($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(4-2)]; 
    +    }
    +
    +    protected function yyn94($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(5-3)]; 
    +    }
    +
    +    protected function yyn95($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn96($attributes) {
    +         $this->yyastk[$this->stackPos-(2-1)][] = $this->yyastk[$this->stackPos-(2-2)]; $this->yyval = $this->yyastk[$this->stackPos-(2-1)]; 
    +    }
    +
    +    protected function yyn97($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Case($this->yyastk[$this->stackPos-(4-2)], $this->yyastk[$this->stackPos-(4-4)], $attributes); 
    +    }
    +
    +    protected function yyn98($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Case(null, $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn99() {
    +        $this->yyval = $this->yyastk[$this->stackPos];
    +    }
    +
    +    protected function yyn100() {
    +        $this->yyval = $this->yyastk[$this->stackPos];
    +    }
    +
    +    protected function yyn101($attributes) {
    +         $this->yyval = is_array($this->yyastk[$this->stackPos-(1-1)]) ? $this->yyastk[$this->stackPos-(1-1)] : array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn102($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(4-2)]; 
    +    }
    +
    +    protected function yyn103($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn104($attributes) {
    +         $this->yyastk[$this->stackPos-(2-1)][] = $this->yyastk[$this->stackPos-(2-2)]; $this->yyval = $this->yyastk[$this->stackPos-(2-1)]; 
    +    }
    +
    +    protected function yyn105($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_ElseIf($this->yyastk[$this->stackPos-(3-2)], is_array($this->yyastk[$this->stackPos-(3-3)]) ? $this->yyastk[$this->stackPos-(3-3)] : array($this->yyastk[$this->stackPos-(3-3)]), $attributes); 
    +    }
    +
    +    protected function yyn106($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn107($attributes) {
    +         $this->yyastk[$this->stackPos-(2-1)][] = $this->yyastk[$this->stackPos-(2-2)]; $this->yyval = $this->yyastk[$this->stackPos-(2-1)]; 
    +    }
    +
    +    protected function yyn108($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_ElseIf($this->yyastk[$this->stackPos-(4-2)], $this->yyastk[$this->stackPos-(4-4)], $attributes); 
    +    }
    +
    +    protected function yyn109($attributes) {
    +         $this->yyval = null; 
    +    }
    +
    +    protected function yyn110($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Else(is_array($this->yyastk[$this->stackPos-(2-2)]) ? $this->yyastk[$this->stackPos-(2-2)] : array($this->yyastk[$this->stackPos-(2-2)]), $attributes); 
    +    }
    +
    +    protected function yyn111($attributes) {
    +         $this->yyval = null; 
    +    }
    +
    +    protected function yyn112($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Else($this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn113($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)], false); 
    +    }
    +
    +    protected function yyn114($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(2-2)], true); 
    +    }
    +
    +    protected function yyn115($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)], false); 
    +    }
    +
    +    protected function yyn116($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn117($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn118($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn119($attributes) {
    +         $this->yyastk[$this->stackPos-(3-1)][] = $this->yyastk[$this->stackPos-(3-3)]; $this->yyval = $this->yyastk[$this->stackPos-(3-1)]; 
    +    }
    +
    +    protected function yyn120($attributes) {
    +         $this->yyval = new PHPParser_Node_Param(substr($this->yyastk[$this->stackPos-(3-3)], 1), null, $this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn121($attributes) {
    +         $this->yyval = new PHPParser_Node_Param(substr($this->yyastk[$this->stackPos-(5-3)], 1), $this->yyastk[$this->stackPos-(5-5)], $this->yyastk[$this->stackPos-(5-1)], $this->yyastk[$this->stackPos-(5-2)], $attributes); 
    +    }
    +
    +    protected function yyn122($attributes) {
    +         $this->yyval = null; 
    +    }
    +
    +    protected function yyn123($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn124($attributes) {
    +         $this->yyval = 'array'; 
    +    }
    +
    +    protected function yyn125($attributes) {
    +         $this->yyval = 'callable'; 
    +    }
    +
    +    protected function yyn126($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn127($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(3-2)]; 
    +    }
    +
    +    protected function yyn128($attributes) {
    +         $this->yyval = array(new PHPParser_Node_Arg($this->yyastk[$this->stackPos-(3-2)], false, $attributes)); 
    +    }
    +
    +    protected function yyn129($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn130($attributes) {
    +         $this->yyastk[$this->stackPos-(3-1)][] = $this->yyastk[$this->stackPos-(3-3)]; $this->yyval = $this->yyastk[$this->stackPos-(3-1)]; 
    +    }
    +
    +    protected function yyn131($attributes) {
    +         $this->yyval = new PHPParser_Node_Arg($this->yyastk[$this->stackPos-(1-1)], false, $attributes); 
    +    }
    +
    +    protected function yyn132($attributes) {
    +         $this->yyval = new PHPParser_Node_Arg($this->yyastk[$this->stackPos-(2-2)], true, $attributes); 
    +    }
    +
    +    protected function yyn133($attributes) {
    +         $this->yyastk[$this->stackPos-(3-1)][] = $this->yyastk[$this->stackPos-(3-3)]; $this->yyval = $this->yyastk[$this->stackPos-(3-1)]; 
    +    }
    +
    +    protected function yyn134($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn135($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Variable(substr($this->yyastk[$this->stackPos-(1-1)], 1), $attributes); 
    +    }
    +
    +    protected function yyn136($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Variable($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn137($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Variable($this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn138($attributes) {
    +         $this->yyastk[$this->stackPos-(3-1)][] = $this->yyastk[$this->stackPos-(3-3)]; $this->yyval = $this->yyastk[$this->stackPos-(3-1)]; 
    +    }
    +
    +    protected function yyn139($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn140($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_StaticVar(substr($this->yyastk[$this->stackPos-(1-1)], 1), null, $attributes); 
    +    }
    +
    +    protected function yyn141($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_StaticVar(substr($this->yyastk[$this->stackPos-(3-1)], 1), $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn142($attributes) {
    +         $this->yyastk[$this->stackPos-(2-1)][] = $this->yyastk[$this->stackPos-(2-2)]; $this->yyval = $this->yyastk[$this->stackPos-(2-1)]; 
    +    }
    +
    +    protected function yyn143($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn144($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_Property($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn145($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_ClassConst($this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn146($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_ClassMethod($this->yyastk[$this->stackPos-(8-4)], array('type' => $this->yyastk[$this->stackPos-(8-1)], 'byRef' => $this->yyastk[$this->stackPos-(8-3)], 'params' => $this->yyastk[$this->stackPos-(8-6)], 'stmts' => $this->yyastk[$this->stackPos-(8-8)]), $attributes); 
    +    }
    +
    +    protected function yyn147($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_TraitUse($this->yyastk[$this->stackPos-(3-2)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn148($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn149($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(3-2)]; 
    +    }
    +
    +    protected function yyn150($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn151($attributes) {
    +         $this->yyastk[$this->stackPos-(2-1)][] = $this->yyastk[$this->stackPos-(2-2)]; $this->yyval = $this->yyastk[$this->stackPos-(2-1)]; 
    +    }
    +
    +    protected function yyn152($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_TraitUseAdaptation_Precedence($this->yyastk[$this->stackPos-(4-1)][0], $this->yyastk[$this->stackPos-(4-1)][1], $this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn153($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_TraitUseAdaptation_Alias($this->yyastk[$this->stackPos-(5-1)][0], $this->yyastk[$this->stackPos-(5-1)][1], $this->yyastk[$this->stackPos-(5-3)], $this->yyastk[$this->stackPos-(5-4)], $attributes); 
    +    }
    +
    +    protected function yyn154($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_TraitUseAdaptation_Alias($this->yyastk[$this->stackPos-(4-1)][0], $this->yyastk[$this->stackPos-(4-1)][1], $this->yyastk[$this->stackPos-(4-3)], null, $attributes); 
    +    }
    +
    +    protected function yyn155($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_TraitUseAdaptation_Alias($this->yyastk[$this->stackPos-(4-1)][0], $this->yyastk[$this->stackPos-(4-1)][1], null, $this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn156($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)]); 
    +    }
    +
    +    protected function yyn157($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn158($attributes) {
    +         $this->yyval = array(null, $this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn159($attributes) {
    +         $this->yyval = null; 
    +    }
    +
    +    protected function yyn160($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(3-2)]; 
    +    }
    +
    +    protected function yyn161($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn162($attributes) {
    +         $this->yyval = PHPParser_Node_Stmt_Class::MODIFIER_PUBLIC; 
    +    }
    +
    +    protected function yyn163($attributes) {
    +         $this->yyval = PHPParser_Node_Stmt_Class::MODIFIER_PUBLIC; 
    +    }
    +
    +    protected function yyn164($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn165($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn166($attributes) {
    +         PHPParser_Node_Stmt_Class::verifyModifier($this->yyastk[$this->stackPos-(2-1)], $this->yyastk[$this->stackPos-(2-2)]); $this->yyval = $this->yyastk[$this->stackPos-(2-1)] | $this->yyastk[$this->stackPos-(2-2)]; 
    +    }
    +
    +    protected function yyn167($attributes) {
    +         $this->yyval = PHPParser_Node_Stmt_Class::MODIFIER_PUBLIC; 
    +    }
    +
    +    protected function yyn168($attributes) {
    +         $this->yyval = PHPParser_Node_Stmt_Class::MODIFIER_PROTECTED; 
    +    }
    +
    +    protected function yyn169($attributes) {
    +         $this->yyval = PHPParser_Node_Stmt_Class::MODIFIER_PRIVATE; 
    +    }
    +
    +    protected function yyn170($attributes) {
    +         $this->yyval = PHPParser_Node_Stmt_Class::MODIFIER_STATIC; 
    +    }
    +
    +    protected function yyn171($attributes) {
    +         $this->yyval = PHPParser_Node_Stmt_Class::MODIFIER_ABSTRACT; 
    +    }
    +
    +    protected function yyn172($attributes) {
    +         $this->yyval = PHPParser_Node_Stmt_Class::MODIFIER_FINAL; 
    +    }
    +
    +    protected function yyn173($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn174($attributes) {
    +         $this->yyastk[$this->stackPos-(3-1)][] = $this->yyastk[$this->stackPos-(3-3)]; $this->yyval = $this->yyastk[$this->stackPos-(3-1)]; 
    +    }
    +
    +    protected function yyn175($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_PropertyProperty(substr($this->yyastk[$this->stackPos-(1-1)], 1), null, $attributes); 
    +    }
    +
    +    protected function yyn176($attributes) {
    +         $this->yyval = new PHPParser_Node_Stmt_PropertyProperty(substr($this->yyastk[$this->stackPos-(3-1)], 1), $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn177($attributes) {
    +         $this->yyastk[$this->stackPos-(3-1)][] = $this->yyastk[$this->stackPos-(3-3)]; $this->yyval = $this->yyastk[$this->stackPos-(3-1)]; 
    +    }
    +
    +    protected function yyn178($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn179($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn180($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn181($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn182($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Assign($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn183($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Assign($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn184($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_AssignRef($this->yyastk[$this->stackPos-(4-1)], $this->yyastk[$this->stackPos-(4-4)], $attributes); 
    +    }
    +
    +    protected function yyn185($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_AssignRef($this->yyastk[$this->stackPos-(4-1)], $this->yyastk[$this->stackPos-(4-4)], $attributes); 
    +    }
    +
    +    protected function yyn186($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn187($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Clone($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn188($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_AssignPlus($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn189($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_AssignMinus($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn190($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_AssignMul($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn191($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_AssignDiv($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn192($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_AssignConcat($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn193($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_AssignMod($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn194($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_AssignBitwiseAnd($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn195($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_AssignBitwiseOr($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn196($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_AssignBitwiseXor($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn197($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_AssignShiftLeft($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn198($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_AssignShiftRight($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn199($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_PostInc($this->yyastk[$this->stackPos-(2-1)], $attributes); 
    +    }
    +
    +    protected function yyn200($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_PreInc($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn201($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_PostDec($this->yyastk[$this->stackPos-(2-1)], $attributes); 
    +    }
    +
    +    protected function yyn202($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_PreDec($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn203($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_BooleanOr($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn204($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_BooleanAnd($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn205($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_LogicalOr($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn206($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_LogicalAnd($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn207($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_LogicalXor($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn208($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_BitwiseOr($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn209($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_BitwiseAnd($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn210($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_BitwiseXor($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn211($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Concat($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn212($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Plus($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn213($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Minus($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn214($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Mul($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn215($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Div($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn216($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Mod($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn217($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ShiftLeft($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn218($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ShiftRight($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn219($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_UnaryPlus($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn220($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_UnaryMinus($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn221($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_BooleanNot($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn222($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_BitwiseNot($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn223($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Identical($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn224($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_NotIdentical($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn225($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Equal($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn226($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_NotEqual($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn227($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Smaller($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn228($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_SmallerOrEqual($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn229($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Greater($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn230($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_GreaterOrEqual($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn231($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Instanceof($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn232($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn233($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(3-2)]; 
    +    }
    +
    +    protected function yyn234($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Ternary($this->yyastk[$this->stackPos-(5-1)], $this->yyastk[$this->stackPos-(5-3)], $this->yyastk[$this->stackPos-(5-5)], $attributes); 
    +    }
    +
    +    protected function yyn235($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Ternary($this->yyastk[$this->stackPos-(4-1)], null, $this->yyastk[$this->stackPos-(4-4)], $attributes); 
    +    }
    +
    +    protected function yyn236($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Isset($this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn237($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Empty($this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn238($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Include($this->yyastk[$this->stackPos-(2-2)], PHPParser_Node_Expr_Include::TYPE_INCLUDE, $attributes); 
    +    }
    +
    +    protected function yyn239($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Include($this->yyastk[$this->stackPos-(2-2)], PHPParser_Node_Expr_Include::TYPE_INCLUDE_ONCE, $attributes); 
    +    }
    +
    +    protected function yyn240($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Eval($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn241($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Include($this->yyastk[$this->stackPos-(2-2)], PHPParser_Node_Expr_Include::TYPE_REQUIRE, $attributes); 
    +    }
    +
    +    protected function yyn242($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Include($this->yyastk[$this->stackPos-(2-2)], PHPParser_Node_Expr_Include::TYPE_REQUIRE_ONCE, $attributes); 
    +    }
    +
    +    protected function yyn243($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Cast_Int($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn244($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Cast_Double($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn245($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Cast_String($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn246($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Cast_Array($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn247($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Cast_Object($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn248($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Cast_Bool($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn249($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Cast_Unset($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn250($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Exit($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn251($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ErrorSuppress($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn252($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn253($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn254($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn255($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ShellExec($this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn256($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Print($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn257($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Yield(null, null, $attributes); 
    +    }
    +
    +    protected function yyn258($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Closure(array('static' => false, 'byRef' => $this->yyastk[$this->stackPos-(9-2)], 'params' => $this->yyastk[$this->stackPos-(9-4)], 'uses' => $this->yyastk[$this->stackPos-(9-6)], 'stmts' => $this->yyastk[$this->stackPos-(9-8)]), $attributes); 
    +    }
    +
    +    protected function yyn259($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Closure(array('static' => true, 'byRef' => $this->yyastk[$this->stackPos-(10-3)], 'params' => $this->yyastk[$this->stackPos-(10-5)], 'uses' => $this->yyastk[$this->stackPos-(10-7)], 'stmts' => $this->yyastk[$this->stackPos-(10-9)]), $attributes); 
    +    }
    +
    +    protected function yyn260($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(3-2)]; 
    +    }
    +
    +    protected function yyn261($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(3-2)]; 
    +    }
    +
    +    protected function yyn262($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Yield($this->yyastk[$this->stackPos-(2-2)], null, $attributes); 
    +    }
    +
    +    protected function yyn263($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Yield($this->yyastk[$this->stackPos-(4-4)], $this->yyastk[$this->stackPos-(4-2)], $attributes); 
    +    }
    +
    +    protected function yyn264($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Array($this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn265($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Array($this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn266($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayDimFetch($this->yyastk[$this->stackPos-(4-1)], $this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn267($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayDimFetch(new PHPParser_Node_Scalar_String(PHPParser_Node_Scalar_String::parse($this->yyastk[$this->stackPos-(4-1)]), $attributes), $this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn268($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayDimFetch($this->yyastk[$this->stackPos-(4-1)], $this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn269($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_New($this->yyastk[$this->stackPos-(3-2)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn270($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn271($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(4-3)]; 
    +    }
    +
    +    protected function yyn272($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn273($attributes) {
    +         $this->yyastk[$this->stackPos-(3-1)][] = $this->yyastk[$this->stackPos-(3-3)]; $this->yyval = $this->yyastk[$this->stackPos-(3-1)]; 
    +    }
    +
    +    protected function yyn274($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ClosureUse(substr($this->yyastk[$this->stackPos-(2-2)], 1), $this->yyastk[$this->stackPos-(2-1)], $attributes); 
    +    }
    +
    +    protected function yyn275($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_FuncCall($this->yyastk[$this->stackPos-(2-1)], $this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn276($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_StaticCall($this->yyastk[$this->stackPos-(4-1)], $this->yyastk[$this->stackPos-(4-3)], $this->yyastk[$this->stackPos-(4-4)], $attributes); 
    +    }
    +
    +    protected function yyn277($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_StaticCall($this->yyastk[$this->stackPos-(6-1)], $this->yyastk[$this->stackPos-(6-4)], $this->yyastk[$this->stackPos-(6-6)], $attributes); 
    +    }
    +
    +    protected function yyn278($attributes) {
    +        
    +            if ($this->yyastk[$this->stackPos-(2-1)] instanceof PHPParser_Node_Expr_StaticPropertyFetch) {
    +                $this->yyval = new PHPParser_Node_Expr_StaticCall($this->yyastk[$this->stackPos-(2-1)]->class, new PHPParser_Node_Expr_Variable($this->yyastk[$this->stackPos-(2-1)]->name, $attributes), $this->yyastk[$this->stackPos-(2-2)], $attributes);
    +            } elseif ($this->yyastk[$this->stackPos-(2-1)] instanceof PHPParser_Node_Expr_ArrayDimFetch) {
    +                $tmp = $this->yyastk[$this->stackPos-(2-1)];
    +                while ($tmp->var instanceof PHPParser_Node_Expr_ArrayDimFetch) {
    +                    $tmp = $tmp->var;
    +                }
    +
    +                $this->yyval = new PHPParser_Node_Expr_StaticCall($tmp->var->class, $this->yyastk[$this->stackPos-(2-1)], $this->yyastk[$this->stackPos-(2-2)], $attributes);
    +                $tmp->var = new PHPParser_Node_Expr_Variable($tmp->var->name, $attributes);
    +            } else {
    +                throw new Exception;
    +            }
    +          
    +    }
    +
    +    protected function yyn279($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_FuncCall($this->yyastk[$this->stackPos-(2-1)], $this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn280($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayDimFetch($this->yyastk[$this->stackPos-(4-1)], $this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn281($attributes) {
    +         $this->yyval = new PHPParser_Node_Name('static', $attributes); 
    +    }
    +
    +    protected function yyn282($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn283($attributes) {
    +         $this->yyval = new PHPParser_Node_Name($this->yyastk[$this->stackPos-(1-1)], $attributes); 
    +    }
    +
    +    protected function yyn284($attributes) {
    +         $this->yyval = new PHPParser_Node_Name_FullyQualified($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn285($attributes) {
    +         $this->yyval = new PHPParser_Node_Name_Relative($this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn286($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn287($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn288($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn289($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn290($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn291($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn292() {
    +        $this->yyval = $this->yyastk[$this->stackPos];
    +    }
    +
    +    protected function yyn293($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_PropertyFetch($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn294($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_PropertyFetch($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn295($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayDimFetch($this->yyastk[$this->stackPos-(4-1)], $this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn296($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayDimFetch($this->yyastk[$this->stackPos-(4-1)], $this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn297($attributes) {
    +         $this->yyval = null; 
    +    }
    +
    +    protected function yyn298($attributes) {
    +         $this->yyval = null; 
    +    }
    +
    +    protected function yyn299($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn300($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn301($attributes) {
    +         $this->yyval = array(PHPParser_Node_Scalar_String::parseEscapeSequences($this->yyastk[$this->stackPos-(1-1)], '`')); 
    +    }
    +
    +    protected function yyn302($attributes) {
    +         foreach ($this->yyastk[$this->stackPos-(1-1)] as &$s) { if (is_string($s)) { $s = PHPParser_Node_Scalar_String::parseEscapeSequences($s, '`'); } }; $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn303($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn304($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn305($attributes) {
    +         $this->yyval = new PHPParser_Node_Scalar_LNumber(PHPParser_Node_Scalar_LNumber::parse($this->yyastk[$this->stackPos-(1-1)]), $attributes); 
    +    }
    +
    +    protected function yyn306($attributes) {
    +         $this->yyval = new PHPParser_Node_Scalar_DNumber(PHPParser_Node_Scalar_DNumber::parse($this->yyastk[$this->stackPos-(1-1)]), $attributes); 
    +    }
    +
    +    protected function yyn307($attributes) {
    +         $this->yyval = new PHPParser_Node_Scalar_String(PHPParser_Node_Scalar_String::parse($this->yyastk[$this->stackPos-(1-1)]), $attributes); 
    +    }
    +
    +    protected function yyn308($attributes) {
    +         $this->yyval = new PHPParser_Node_Scalar_LineConst($attributes); 
    +    }
    +
    +    protected function yyn309($attributes) {
    +         $this->yyval = new PHPParser_Node_Scalar_FileConst($attributes); 
    +    }
    +
    +    protected function yyn310($attributes) {
    +         $this->yyval = new PHPParser_Node_Scalar_DirConst($attributes); 
    +    }
    +
    +    protected function yyn311($attributes) {
    +         $this->yyval = new PHPParser_Node_Scalar_ClassConst($attributes); 
    +    }
    +
    +    protected function yyn312($attributes) {
    +         $this->yyval = new PHPParser_Node_Scalar_TraitConst($attributes); 
    +    }
    +
    +    protected function yyn313($attributes) {
    +         $this->yyval = new PHPParser_Node_Scalar_MethodConst($attributes); 
    +    }
    +
    +    protected function yyn314($attributes) {
    +         $this->yyval = new PHPParser_Node_Scalar_FuncConst($attributes); 
    +    }
    +
    +    protected function yyn315($attributes) {
    +         $this->yyval = new PHPParser_Node_Scalar_NSConst($attributes); 
    +    }
    +
    +    protected function yyn316($attributes) {
    +         $this->yyval = new PHPParser_Node_Scalar_String(PHPParser_Node_Scalar_String::parseDocString($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-2)]), $attributes); 
    +    }
    +
    +    protected function yyn317($attributes) {
    +         $this->yyval = new PHPParser_Node_Scalar_String('', $attributes); 
    +    }
    +
    +    protected function yyn318($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ConstFetch($this->yyastk[$this->stackPos-(1-1)], $attributes); 
    +    }
    +
    +    protected function yyn319($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn320($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ClassConstFetch($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn321($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_UnaryPlus($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn322($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_UnaryMinus($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn323($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Array($this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn324($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Array($this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn325($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn326($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ClassConstFetch($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn327($attributes) {
    +         foreach ($this->yyastk[$this->stackPos-(3-2)] as &$s) { if (is_string($s)) { $s = PHPParser_Node_Scalar_String::parseEscapeSequences($s, '"'); } }; $this->yyval = new PHPParser_Node_Scalar_Encapsed($this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn328($attributes) {
    +         foreach ($this->yyastk[$this->stackPos-(3-2)] as &$s) { if (is_string($s)) { $s = PHPParser_Node_Scalar_String::parseEscapeSequences($s, null); } } $s = preg_replace('~(\r\n|\n|\r)$~', '', $s); if ('' === $s) array_pop($this->yyastk[$this->stackPos-(3-2)]);; $this->yyval = new PHPParser_Node_Scalar_Encapsed($this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn329($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn330($attributes) {
    +         $this->yyval = 'class'; 
    +    }
    +
    +    protected function yyn331($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn332($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(2-1)]; 
    +    }
    +
    +    protected function yyn333() {
    +        $this->yyval = $this->yyastk[$this->stackPos];
    +    }
    +
    +    protected function yyn334() {
    +        $this->yyval = $this->yyastk[$this->stackPos];
    +    }
    +
    +    protected function yyn335($attributes) {
    +         $this->yyastk[$this->stackPos-(3-1)][] = $this->yyastk[$this->stackPos-(3-3)]; $this->yyval = $this->yyastk[$this->stackPos-(3-1)]; 
    +    }
    +
    +    protected function yyn336($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn337($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayItem($this->yyastk[$this->stackPos-(3-3)], $this->yyastk[$this->stackPos-(3-1)], false, $attributes); 
    +    }
    +
    +    protected function yyn338($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayItem($this->yyastk[$this->stackPos-(1-1)], null, false, $attributes); 
    +    }
    +
    +    protected function yyn339($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn340($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn341($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn342($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn343($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayDimFetch($this->yyastk[$this->stackPos-(6-2)], $this->yyastk[$this->stackPos-(6-5)], $attributes); 
    +    }
    +
    +    protected function yyn344($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayDimFetch($this->yyastk[$this->stackPos-(4-1)], $this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn345($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_PropertyFetch($this->yyastk[$this->stackPos-(3-1)], $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn346($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_MethodCall($this->yyastk[$this->stackPos-(4-1)], $this->yyastk[$this->stackPos-(4-3)], $this->yyastk[$this->stackPos-(4-4)], $attributes); 
    +    }
    +
    +    protected function yyn347($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_FuncCall($this->yyastk[$this->stackPos-(2-1)], $this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn348($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayDimFetch($this->yyastk[$this->stackPos-(4-1)], $this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn349($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayDimFetch($this->yyastk[$this->stackPos-(4-1)], $this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn350($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn351($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(3-2)]; 
    +    }
    +
    +    protected function yyn352($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn353($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Variable($this->yyastk[$this->stackPos-(2-2)], $attributes); 
    +    }
    +
    +    protected function yyn354($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn355($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn356($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_StaticPropertyFetch($this->yyastk[$this->stackPos-(4-1)], $this->yyastk[$this->stackPos-(4-4)], $attributes); 
    +    }
    +
    +    protected function yyn357($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn358($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_StaticPropertyFetch($this->yyastk[$this->stackPos-(3-1)], substr($this->yyastk[$this->stackPos-(3-3)], 1), $attributes); 
    +    }
    +
    +    protected function yyn359($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_StaticPropertyFetch($this->yyastk[$this->stackPos-(6-1)], $this->yyastk[$this->stackPos-(6-5)], $attributes); 
    +    }
    +
    +    protected function yyn360($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayDimFetch($this->yyastk[$this->stackPos-(4-1)], $this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn361($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayDimFetch($this->yyastk[$this->stackPos-(4-1)], $this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn362($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayDimFetch($this->yyastk[$this->stackPos-(4-1)], $this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn363($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayDimFetch($this->yyastk[$this->stackPos-(4-1)], $this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn364($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Variable(substr($this->yyastk[$this->stackPos-(1-1)], 1), $attributes); 
    +    }
    +
    +    protected function yyn365($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Variable($this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn366($attributes) {
    +         $this->yyval = null; 
    +    }
    +
    +    protected function yyn367($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn368($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn369($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(3-2)]; 
    +    }
    +
    +    protected function yyn370($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn371($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_List($this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn372($attributes) {
    +         $this->yyastk[$this->stackPos-(3-1)][] = $this->yyastk[$this->stackPos-(3-3)]; $this->yyval = $this->yyastk[$this->stackPos-(3-1)]; 
    +    }
    +
    +    protected function yyn373($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn374($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn375($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(1-1)]; 
    +    }
    +
    +    protected function yyn376($attributes) {
    +         $this->yyval = null; 
    +    }
    +
    +    protected function yyn377($attributes) {
    +         $this->yyval = array(); 
    +    }
    +
    +    protected function yyn378($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(2-1)]; 
    +    }
    +
    +    protected function yyn379($attributes) {
    +         $this->yyastk[$this->stackPos-(3-1)][] = $this->yyastk[$this->stackPos-(3-3)]; $this->yyval = $this->yyastk[$this->stackPos-(3-1)]; 
    +    }
    +
    +    protected function yyn380($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn381($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayItem($this->yyastk[$this->stackPos-(3-3)], $this->yyastk[$this->stackPos-(3-1)], false, $attributes); 
    +    }
    +
    +    protected function yyn382($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayItem($this->yyastk[$this->stackPos-(1-1)], null, false, $attributes); 
    +    }
    +
    +    protected function yyn383($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayItem($this->yyastk[$this->stackPos-(4-4)], $this->yyastk[$this->stackPos-(4-1)], true, $attributes); 
    +    }
    +
    +    protected function yyn384($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayItem($this->yyastk[$this->stackPos-(2-2)], null, true, $attributes); 
    +    }
    +
    +    protected function yyn385($attributes) {
    +         $this->yyastk[$this->stackPos-(2-1)][] = $this->yyastk[$this->stackPos-(2-2)]; $this->yyval = $this->yyastk[$this->stackPos-(2-1)]; 
    +    }
    +
    +    protected function yyn386($attributes) {
    +         $this->yyastk[$this->stackPos-(2-1)][] = $this->yyastk[$this->stackPos-(2-2)]; $this->yyval = $this->yyastk[$this->stackPos-(2-1)]; 
    +    }
    +
    +    protected function yyn387($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(1-1)]); 
    +    }
    +
    +    protected function yyn388($attributes) {
    +         $this->yyval = array($this->yyastk[$this->stackPos-(2-1)], $this->yyastk[$this->stackPos-(2-2)]); 
    +    }
    +
    +    protected function yyn389($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Variable(substr($this->yyastk[$this->stackPos-(1-1)], 1), $attributes); 
    +    }
    +
    +    protected function yyn390($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayDimFetch(new PHPParser_Node_Expr_Variable(substr($this->yyastk[$this->stackPos-(4-1)], 1), $attributes), $this->yyastk[$this->stackPos-(4-3)], $attributes); 
    +    }
    +
    +    protected function yyn391($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_PropertyFetch(new PHPParser_Node_Expr_Variable(substr($this->yyastk[$this->stackPos-(3-1)], 1), $attributes), $this->yyastk[$this->stackPos-(3-3)], $attributes); 
    +    }
    +
    +    protected function yyn392($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Variable($this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn393($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Variable($this->yyastk[$this->stackPos-(3-2)], $attributes); 
    +    }
    +
    +    protected function yyn394($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_ArrayDimFetch(new PHPParser_Node_Expr_Variable($this->yyastk[$this->stackPos-(6-2)], $attributes), $this->yyastk[$this->stackPos-(6-4)], $attributes); 
    +    }
    +
    +    protected function yyn395($attributes) {
    +         $this->yyval = $this->yyastk[$this->stackPos-(3-2)]; 
    +    }
    +
    +    protected function yyn396($attributes) {
    +         $this->yyval = new PHPParser_Node_Scalar_String($this->yyastk[$this->stackPos-(1-1)], $attributes); 
    +    }
    +
    +    protected function yyn397($attributes) {
    +         $this->yyval = new PHPParser_Node_Scalar_String($this->yyastk[$this->stackPos-(1-1)], $attributes); 
    +    }
    +
    +    protected function yyn398($attributes) {
    +         $this->yyval = new PHPParser_Node_Expr_Variable(substr($this->yyastk[$this->stackPos-(1-1)], 1), $attributes); 
    +    }
    +}
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/PrettyPrinter/Default.php b/vendor/nikic/php-parser/lib/PHPParser/PrettyPrinter/Default.php
    new file mode 100644
    index 0000000..ad2b4b7
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/PrettyPrinter/Default.php
    @@ -0,0 +1,725 @@
    +type ? (is_string($node->type) ? $node->type : $this->p($node->type)) . ' ' : '')
    +             . ($node->byRef ? '&' : '')
    +             . '$' . $node->name
    +             . ($node->default ? ' = ' . $this->p($node->default) : '');
    +    }
    +
    +    public function pArg(PHPParser_Node_Arg $node) {
    +        return ($node->byRef ? '&' : '') . $this->p($node->value);
    +    }
    +
    +    public function pConst(PHPParser_Node_Const $node) {
    +        return $node->name . ' = ' . $this->p($node->value);
    +    }
    +
    +    // Names
    +
    +    public function pName(PHPParser_Node_Name $node) {
    +        return implode('\\', $node->parts);
    +    }
    +
    +    public function pName_FullyQualified(PHPParser_Node_Name_FullyQualified $node) {
    +        return '\\' . implode('\\', $node->parts);
    +    }
    +
    +    public function pName_Relative(PHPParser_Node_Name_Relative $node) {
    +        return 'namespace\\' . implode('\\', $node->parts);
    +    }
    +
    +    // Magic Constants
    +
    +    public function pScalar_ClassConst(PHPParser_Node_Scalar_ClassConst $node) {
    +        return '__CLASS__';
    +    }
    +
    +    public function pScalar_TraitConst(PHPParser_Node_Scalar_TraitConst $node) {
    +        return '__TRAIT__';
    +    }
    +
    +    public function pScalar_DirConst(PHPParser_Node_Scalar_DirConst $node) {
    +        return '__DIR__';
    +    }
    +
    +    public function pScalar_FileConst(PHPParser_Node_Scalar_FileConst $node) {
    +        return '__FILE__';
    +    }
    +
    +    public function pScalar_FuncConst(PHPParser_Node_Scalar_FuncConst $node) {
    +        return '__FUNCTION__';
    +    }
    +
    +    public function pScalar_LineConst(PHPParser_Node_Scalar_LineConst $node) {
    +        return '__LINE__';
    +    }
    +
    +    public function pScalar_MethodConst(PHPParser_Node_Scalar_MethodConst $node) {
    +        return '__METHOD__';
    +    }
    +
    +    public function pScalar_NSConst(PHPParser_Node_Scalar_NSConst $node) {
    +        return '__NAMESPACE__';
    +    }
    +
    +    // Scalars
    +
    +    public function pScalar_String(PHPParser_Node_Scalar_String $node) {
    +        return '\'' . $this->pNoIndent(addcslashes($node->value, '\'\\')) . '\'';
    +    }
    +
    +    public function pScalar_Encapsed(PHPParser_Node_Scalar_Encapsed $node) {
    +        return '"' . $this->pEncapsList($node->parts, '"') . '"';
    +    }
    +
    +    public function pScalar_LNumber(PHPParser_Node_Scalar_LNumber $node) {
    +        return (string) $node->value;
    +    }
    +
    +    public function pScalar_DNumber(PHPParser_Node_Scalar_DNumber $node) {
    +        $stringValue = (string) $node->value;
    +
    +        // ensure that number is really printed as float
    +        return ctype_digit($stringValue) ? $stringValue . '.0' : $stringValue;
    +    }
    +
    +    // Assignments
    +
    +    public function pExpr_Assign(PHPParser_Node_Expr_Assign $node) {
    +        return $this->pInfixOp('Expr_Assign', $node->var, ' = ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignRef(PHPParser_Node_Expr_AssignRef $node) {
    +        return $this->pInfixOp('Expr_AssignRef', $node->var, ' =& ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignPlus(PHPParser_Node_Expr_AssignPlus $node) {
    +        return $this->pInfixOp('Expr_AssignPlus', $node->var, ' += ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignMinus(PHPParser_Node_Expr_AssignMinus $node) {
    +        return $this->pInfixOp('Expr_AssignMinus', $node->var, ' -= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignMul(PHPParser_Node_Expr_AssignMul $node) {
    +        return $this->pInfixOp('Expr_AssignMul', $node->var, ' *= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignDiv(PHPParser_Node_Expr_AssignDiv $node) {
    +        return $this->pInfixOp('Expr_AssignDiv', $node->var, ' /= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignConcat(PHPParser_Node_Expr_AssignConcat $node) {
    +        return $this->pInfixOp('Expr_AssignConcat', $node->var, ' .= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignMod(PHPParser_Node_Expr_AssignMod $node) {
    +        return $this->pInfixOp('Expr_AssignMod', $node->var, ' %= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignBitwiseAnd(PHPParser_Node_Expr_AssignBitwiseAnd $node) {
    +        return $this->pInfixOp('Expr_AssignBitwiseAnd', $node->var, ' &= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignBitwiseOr(PHPParser_Node_Expr_AssignBitwiseOr $node) {
    +        return $this->pInfixOp('Expr_AssignBitwiseOr', $node->var, ' |= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignBitwiseXor(PHPParser_Node_Expr_AssignBitwiseXor $node) {
    +        return $this->pInfixOp('Expr_AssignBitwiseXor', $node->var, ' ^= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignShiftLeft(PHPParser_Node_Expr_AssignShiftLeft $node) {
    +        return $this->pInfixOp('Expr_AssignShiftLeft', $node->var, ' <<= ', $node->expr);
    +    }
    +
    +    public function pExpr_AssignShiftRight(PHPParser_Node_Expr_AssignShiftRight $node) {
    +        return $this->pInfixOp('Expr_AssignShiftRight', $node->var, ' >>= ', $node->expr);
    +    }
    +
    +    // Binary expressions
    +
    +    public function pExpr_Plus(PHPParser_Node_Expr_Plus $node) {
    +        return $this->pInfixOp('Expr_Plus', $node->left, ' + ', $node->right);
    +    }
    +
    +    public function pExpr_Minus(PHPParser_Node_Expr_Minus $node) {
    +        return $this->pInfixOp('Expr_Minus', $node->left, ' - ', $node->right);
    +    }
    +
    +    public function pExpr_Mul(PHPParser_Node_Expr_Mul $node) {
    +        return $this->pInfixOp('Expr_Mul', $node->left, ' * ', $node->right);
    +    }
    +
    +    public function pExpr_Div(PHPParser_Node_Expr_Div $node) {
    +        return $this->pInfixOp('Expr_Div', $node->left, ' / ', $node->right);
    +    }
    +
    +    public function pExpr_Concat(PHPParser_Node_Expr_Concat $node) {
    +        return $this->pInfixOp('Expr_Concat', $node->left, ' . ', $node->right);
    +    }
    +
    +    public function pExpr_Mod(PHPParser_Node_Expr_Mod $node) {
    +        return $this->pInfixOp('Expr_Mod', $node->left, ' % ', $node->right);
    +    }
    +
    +    public function pExpr_BooleanAnd(PHPParser_Node_Expr_BooleanAnd $node) {
    +        return $this->pInfixOp('Expr_BooleanAnd', $node->left, ' && ', $node->right);
    +    }
    +
    +    public function pExpr_BooleanOr(PHPParser_Node_Expr_BooleanOr $node) {
    +        return $this->pInfixOp('Expr_BooleanOr', $node->left, ' || ', $node->right);
    +    }
    +
    +    public function pExpr_BitwiseAnd(PHPParser_Node_Expr_BitwiseAnd $node) {
    +        return $this->pInfixOp('Expr_BitwiseAnd', $node->left, ' & ', $node->right);
    +    }
    +
    +    public function pExpr_BitwiseOr(PHPParser_Node_Expr_BitwiseOr $node) {
    +        return $this->pInfixOp('Expr_BitwiseOr', $node->left, ' | ', $node->right);
    +    }
    +
    +    public function pExpr_BitwiseXor(PHPParser_Node_Expr_BitwiseXor $node) {
    +        return $this->pInfixOp('Expr_BitwiseXor', $node->left, ' ^ ', $node->right);
    +    }
    +
    +    public function pExpr_ShiftLeft(PHPParser_Node_Expr_ShiftLeft $node) {
    +        return $this->pInfixOp('Expr_ShiftLeft', $node->left, ' << ', $node->right);
    +    }
    +
    +    public function pExpr_ShiftRight(PHPParser_Node_Expr_ShiftRight $node) {
    +        return $this->pInfixOp('Expr_ShiftRight', $node->left, ' >> ', $node->right);
    +    }
    +
    +    public function pExpr_LogicalAnd(PHPParser_Node_Expr_LogicalAnd $node) {
    +        return $this->pInfixOp('Expr_LogicalAnd', $node->left, ' and ', $node->right);
    +    }
    +
    +    public function pExpr_LogicalOr(PHPParser_Node_Expr_LogicalOr $node) {
    +        return $this->pInfixOp('Expr_LogicalOr', $node->left, ' or ', $node->right);
    +    }
    +
    +    public function pExpr_LogicalXor(PHPParser_Node_Expr_LogicalXor $node) {
    +        return $this->pInfixOp('Expr_LogicalXor', $node->left, ' xor ', $node->right);
    +    }
    +
    +    public function pExpr_Equal(PHPParser_Node_Expr_Equal $node) {
    +        return $this->pInfixOp('Expr_Equal', $node->left, ' == ', $node->right);
    +    }
    +
    +    public function pExpr_NotEqual(PHPParser_Node_Expr_NotEqual $node) {
    +        return $this->pInfixOp('Expr_NotEqual', $node->left, ' != ', $node->right);
    +    }
    +
    +    public function pExpr_Identical(PHPParser_Node_Expr_Identical $node) {
    +        return $this->pInfixOp('Expr_Identical', $node->left, ' === ', $node->right);
    +    }
    +
    +    public function pExpr_NotIdentical(PHPParser_Node_Expr_NotIdentical $node) {
    +        return $this->pInfixOp('Expr_NotIdentical', $node->left, ' !== ', $node->right);
    +    }
    +
    +    public function pExpr_Greater(PHPParser_Node_Expr_Greater $node) {
    +        return $this->pInfixOp('Expr_Greater', $node->left, ' > ', $node->right);
    +    }
    +
    +    public function pExpr_GreaterOrEqual(PHPParser_Node_Expr_GreaterOrEqual $node) {
    +        return $this->pInfixOp('Expr_GreaterOrEqual', $node->left, ' >= ', $node->right);
    +    }
    +
    +    public function pExpr_Smaller(PHPParser_Node_Expr_Smaller $node) {
    +        return $this->pInfixOp('Expr_Smaller', $node->left, ' < ', $node->right);
    +    }
    +
    +    public function pExpr_SmallerOrEqual(PHPParser_Node_Expr_SmallerOrEqual $node) {
    +        return $this->pInfixOp('Expr_SmallerOrEqual', $node->left, ' <= ', $node->right);
    +    }
    +
    +    public function pExpr_Instanceof(PHPParser_Node_Expr_Instanceof $node) {
    +        return $this->pInfixOp('Expr_Instanceof', $node->expr, ' instanceof ', $node->class);
    +    }
    +
    +    // Unary expressions
    +
    +    public function pExpr_BooleanNot(PHPParser_Node_Expr_BooleanNot $node) {
    +        return $this->pPrefixOp('Expr_BooleanNot', '!', $node->expr);
    +    }
    +
    +    public function pExpr_BitwiseNot(PHPParser_Node_Expr_BitwiseNot $node) {
    +        return $this->pPrefixOp('Expr_BitwiseNot', '~', $node->expr);
    +    }
    +
    +    public function pExpr_UnaryMinus(PHPParser_Node_Expr_UnaryMinus $node) {
    +        return $this->pPrefixOp('Expr_UnaryMinus', '-', $node->expr);
    +    }
    +
    +    public function pExpr_UnaryPlus(PHPParser_Node_Expr_UnaryPlus $node) {
    +        return $this->pPrefixOp('Expr_UnaryPlus', '+', $node->expr);
    +    }
    +
    +    public function pExpr_PreInc(PHPParser_Node_Expr_PreInc $node) {
    +        return $this->pPrefixOp('Expr_PreInc', '++', $node->var);
    +    }
    +
    +    public function pExpr_PreDec(PHPParser_Node_Expr_PreDec $node) {
    +        return $this->pPrefixOp('Expr_PreDec', '--', $node->var);
    +    }
    +
    +    public function pExpr_PostInc(PHPParser_Node_Expr_PostInc $node) {
    +        return $this->pPostfixOp('Expr_PostInc', $node->var, '++');
    +    }
    +
    +    public function pExpr_PostDec(PHPParser_Node_Expr_PostDec $node) {
    +        return $this->pPostfixOp('Expr_PostDec', $node->var, '--');
    +    }
    +
    +    public function pExpr_ErrorSuppress(PHPParser_Node_Expr_ErrorSuppress $node) {
    +        return $this->pPrefixOp('Expr_ErrorSuppress', '@', $node->expr);
    +    }
    +
    +    // Casts
    +
    +    public function pExpr_Cast_Int(PHPParser_Node_Expr_Cast_Int $node) {
    +        return $this->pPrefixOp('Expr_Cast_Int', '(int) ', $node->expr);
    +    }
    +
    +    public function pExpr_Cast_Double(PHPParser_Node_Expr_Cast_Double $node) {
    +        return $this->pPrefixOp('Expr_Cast_Double', '(double) ', $node->expr);
    +    }
    +
    +    public function pExpr_Cast_String(PHPParser_Node_Expr_Cast_String $node) {
    +        return $this->pPrefixOp('Expr_Cast_String', '(string) ', $node->expr);
    +    }
    +
    +    public function pExpr_Cast_Array(PHPParser_Node_Expr_Cast_Array $node) {
    +        return $this->pPrefixOp('Expr_Cast_Array', '(array) ', $node->expr);
    +    }
    +
    +    public function pExpr_Cast_Object(PHPParser_Node_Expr_Cast_Object $node) {
    +        return $this->pPrefixOp('Expr_Cast_Object', '(object) ', $node->expr);
    +    }
    +
    +    public function pExpr_Cast_Bool(PHPParser_Node_Expr_Cast_Bool $node) {
    +        return $this->pPrefixOp('Expr_Cast_Bool', '(bool) ', $node->expr);
    +    }
    +
    +    public function pExpr_Cast_Unset(PHPParser_Node_Expr_Cast_Unset $node) {
    +        return $this->pPrefixOp('Expr_Cast_Unset', '(unset) ', $node->expr);
    +    }
    +
    +    // Function calls and similar constructs
    +
    +    public function pExpr_FuncCall(PHPParser_Node_Expr_FuncCall $node) {
    +        return $this->p($node->name) . '(' . $this->pCommaSeparated($node->args) . ')';
    +    }
    +
    +    public function pExpr_MethodCall(PHPParser_Node_Expr_MethodCall $node) {
    +        return $this->pVarOrNewExpr($node->var) . '->' . $this->pObjectProperty($node->name)
    +             . '(' . $this->pCommaSeparated($node->args) . ')';
    +    }
    +
    +    public function pExpr_StaticCall(PHPParser_Node_Expr_StaticCall $node) {
    +        return $this->p($node->class) . '::'
    +             . ($node->name instanceof PHPParser_Node_Expr
    +                ? ($node->name instanceof PHPParser_Node_Expr_Variable
    +                   || $node->name instanceof PHPParser_Node_Expr_ArrayDimFetch
    +                   ? $this->p($node->name)
    +                   : '{' . $this->p($node->name) . '}')
    +                : $node->name)
    +             . '(' . $this->pCommaSeparated($node->args) . ')';
    +    }
    +
    +    public function pExpr_Empty(PHPParser_Node_Expr_Empty $node) {
    +        return 'empty(' . $this->p($node->expr) . ')';
    +    }
    +
    +    public function pExpr_Isset(PHPParser_Node_Expr_Isset $node) {
    +        return 'isset(' . $this->pCommaSeparated($node->vars) . ')';
    +    }
    +
    +    public function pExpr_Print(PHPParser_Node_Expr_Print $node) {
    +        return 'print ' . $this->p($node->expr);
    +    }
    +
    +    public function pExpr_Eval(PHPParser_Node_Expr_Eval $node) {
    +        return 'eval(' . $this->p($node->expr) . ')';
    +    }
    +
    +    public function pExpr_Include(PHPParser_Node_Expr_Include $node) {
    +        static $map = array(
    +            PHPParser_Node_Expr_Include::TYPE_INCLUDE      => 'include',
    +            PHPParser_Node_Expr_Include::TYPE_INCLUDE_ONCE => 'include_once',
    +            PHPParser_Node_Expr_Include::TYPE_REQUIRE      => 'require',
    +            PHPParser_Node_Expr_Include::TYPE_REQUIRE_ONCE => 'require_once',
    +        );
    +
    +        return $map[$node->type] . ' ' . $this->p($node->expr);
    +    }
    +
    +    public function pExpr_List(PHPParser_Node_Expr_List $node) {
    +        $pList = array();
    +        foreach ($node->vars as $var) {
    +            if (null === $var) {
    +                $pList[] = '';
    +            } else {
    +                $pList[] = $this->p($var);
    +            }
    +        }
    +
    +        return 'list(' . implode(', ', $pList) . ')';
    +    }
    +
    +    // Other
    +
    +    public function pExpr_Variable(PHPParser_Node_Expr_Variable $node) {
    +        if ($node->name instanceof PHPParser_Node_Expr) {
    +            return '${' . $this->p($node->name) . '}';
    +        } else {
    +            return '$' . $node->name;
    +        }
    +    }
    +
    +    public function pExpr_Array(PHPParser_Node_Expr_Array $node) {
    +        return 'array(' . $this->pCommaSeparated($node->items) . ')';
    +    }
    +
    +    public function pExpr_ArrayItem(PHPParser_Node_Expr_ArrayItem $node) {
    +        return (null !== $node->key ? $this->p($node->key) . ' => ' : '')
    +             . ($node->byRef ? '&' : '') . $this->p($node->value);
    +    }
    +
    +    public function pExpr_ArrayDimFetch(PHPParser_Node_Expr_ArrayDimFetch $node) {
    +        return $this->pVarOrNewExpr($node->var)
    +             . '[' . (null !== $node->dim ? $this->p($node->dim) : '') . ']';
    +    }
    +
    +    public function pExpr_ConstFetch(PHPParser_Node_Expr_ConstFetch $node) {
    +        return $this->p($node->name);
    +    }
    +
    +    public function pExpr_ClassConstFetch(PHPParser_Node_Expr_ClassConstFetch $node) {
    +        return $this->p($node->class) . '::' . $node->name;
    +    }
    +
    +    public function pExpr_PropertyFetch(PHPParser_Node_Expr_PropertyFetch $node) {
    +        return $this->pVarOrNewExpr($node->var) . '->' . $this->pObjectProperty($node->name);
    +    }
    +
    +    public function pExpr_StaticPropertyFetch(PHPParser_Node_Expr_StaticPropertyFetch $node) {
    +        return $this->p($node->class) . '::$' . $this->pObjectProperty($node->name);
    +    }
    +
    +    public function pExpr_ShellExec(PHPParser_Node_Expr_ShellExec $node) {
    +        return '`' . $this->pEncapsList($node->parts, '`') . '`';
    +    }
    +
    +    public function pExpr_Closure(PHPParser_Node_Expr_Closure $node) {
    +        return ($node->static ? 'static ' : '')
    +             . 'function ' . ($node->byRef ? '&' : '')
    +             . '(' . $this->pCommaSeparated($node->params) . ')'
    +             . (!empty($node->uses) ? ' use(' . $this->pCommaSeparated($node->uses) . ')': '')
    +             . ' {' . "\n" . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pExpr_ClosureUse(PHPParser_Node_Expr_ClosureUse $node) {
    +        return ($node->byRef ? '&' : '') . '$' . $node->var;
    +    }
    +
    +    public function pExpr_New(PHPParser_Node_Expr_New $node) {
    +        return 'new ' . $this->p($node->class) . '(' . $this->pCommaSeparated($node->args) . ')';
    +    }
    +
    +    public function pExpr_Clone(PHPParser_Node_Expr_Clone $node) {
    +        return 'clone ' . $this->p($node->expr);
    +    }
    +
    +    public function pExpr_Ternary(PHPParser_Node_Expr_Ternary $node) {
    +        // a bit of cheating: we treat the ternary as a binary op where the ?...: part is the operator.
    +        // this is okay because the part between ? and : never needs parentheses.
    +        return $this->pInfixOp('Expr_Ternary',
    +            $node->cond, ' ?' . (null !== $node->if ? ' ' . $this->p($node->if) . ' ' : '') . ': ', $node->else
    +        );
    +    }
    +
    +    public function pExpr_Exit(PHPParser_Node_Expr_Exit $node) {
    +        return 'die' . (null !== $node->expr ? '(' . $this->p($node->expr) . ')' : '');
    +    }
    +
    +    public function pExpr_Yield(PHPParser_Node_Expr_Yield $node) {
    +        if ($node->value === null) {
    +            return 'yield';
    +        } else {
    +            // this is a bit ugly, but currently there is no way to detect whether the parentheses are necessary
    +            return '(yield '
    +                 . ($node->key !== null ? $this->p($node->key) . ' => ' : '')
    +                 . $this->p($node->value)
    +                 . ')';
    +        }
    +    }
    +
    +    // Declarations
    +
    +    public function pStmt_Namespace(PHPParser_Node_Stmt_Namespace $node) {
    +        if ($this->canUseSemicolonNamespaces) {
    +            return 'namespace ' . $this->p($node->name) . ';' . "\n\n" . $this->pStmts($node->stmts, false);
    +        } else {
    +            return 'namespace' . (null !== $node->name ? ' ' . $this->p($node->name) : '')
    +                 . ' {' . "\n" . $this->pStmts($node->stmts) . "\n" . '}';
    +        }
    +    }
    +
    +    public function pStmt_Use(PHPParser_Node_Stmt_Use $node) {
    +        return 'use ' . $this->pCommaSeparated($node->uses) . ';';
    +    }
    +
    +    public function pStmt_UseUse(PHPParser_Node_Stmt_UseUse $node) {
    +        return $this->p($node->name)
    +             . ($node->name->getLast() !== $node->alias ? ' as ' . $node->alias : '');
    +    }
    +
    +    public function pStmt_Interface(PHPParser_Node_Stmt_Interface $node) {
    +        return 'interface ' . $node->name
    +             . (!empty($node->extends) ? ' extends ' . $this->pCommaSeparated($node->extends) : '')
    +             . "\n" . '{' . "\n" . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_Class(PHPParser_Node_Stmt_Class $node) {
    +        return $this->pModifiers($node->type)
    +             . 'class ' . $node->name
    +             . (null !== $node->extends ? ' extends ' . $this->p($node->extends) : '')
    +             . (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '')
    +             . "\n" . '{' . "\n" . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_Trait(PHPParser_Node_Stmt_Trait $node) {
    +        return 'trait ' . $node->name
    +             . "\n" . '{' . "\n" . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_TraitUse(PHPParser_Node_Stmt_TraitUse $node) {
    +        return 'use ' . $this->pCommaSeparated($node->traits)
    +             . (empty($node->adaptations)
    +                ? ';'
    +                : ' {' . "\n" . $this->pStmts($node->adaptations) . "\n" . '}');
    +    }
    +
    +    public function pStmt_TraitUseAdaptation_Precedence(PHPParser_Node_Stmt_TraitUseAdaptation_Precedence $node) {
    +        return $this->p($node->trait) . '::' . $node->method
    +             . ' insteadof ' . $this->pCommaSeparated($node->insteadof) . ';';
    +    }
    +
    +    public function pStmt_TraitUseAdaptation_Alias(PHPParser_Node_Stmt_TraitUseAdaptation_Alias $node) {
    +        return (null !== $node->trait ? $this->p($node->trait) . '::' : '')
    +             . $node->method . ' as'
    +             . (null !== $node->newModifier ? ' ' . $this->pModifiers($node->newModifier) : '')
    +             . (null !== $node->newName     ? ' ' . $node->newName                        : '')
    +             . ';';
    +    }
    +
    +    public function pStmt_Property(PHPParser_Node_Stmt_Property $node) {
    +        return $this->pModifiers($node->type) . $this->pCommaSeparated($node->props) . ';';
    +    }
    +
    +    public function pStmt_PropertyProperty(PHPParser_Node_Stmt_PropertyProperty $node) {
    +        return '$' . $node->name
    +             . (null !== $node->default ? ' = ' . $this->p($node->default) : '');
    +    }
    +
    +    public function pStmt_ClassMethod(PHPParser_Node_Stmt_ClassMethod $node) {
    +        return $this->pModifiers($node->type)
    +             . 'function ' . ($node->byRef ? '&' : '') . $node->name
    +             . '(' . $this->pCommaSeparated($node->params) . ')'
    +             . (null !== $node->stmts
    +                ? "\n" . '{' . "\n" . $this->pStmts($node->stmts) . "\n" . '}'
    +                : ';');
    +    }
    +
    +    public function pStmt_ClassConst(PHPParser_Node_Stmt_ClassConst $node) {
    +        return 'const ' . $this->pCommaSeparated($node->consts) . ';';
    +    }
    +
    +    public function pStmt_Function(PHPParser_Node_Stmt_Function $node) {
    +        return 'function ' . ($node->byRef ? '&' : '') . $node->name
    +             . '(' . $this->pCommaSeparated($node->params) . ')'
    +             . "\n" . '{' . "\n" . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_Const(PHPParser_Node_Stmt_Const $node) {
    +        return 'const ' . $this->pCommaSeparated($node->consts) . ';';
    +    }
    +
    +    public function pStmt_Declare(PHPParser_Node_Stmt_Declare $node) {
    +        return 'declare (' . $this->pCommaSeparated($node->declares) . ') {'
    +             . "\n" . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_DeclareDeclare(PHPParser_Node_Stmt_DeclareDeclare $node) {
    +        return $node->key . ' = ' . $this->p($node->value);
    +    }
    +
    +    // Control flow
    +
    +    public function pStmt_If(PHPParser_Node_Stmt_If $node) {
    +        return 'if (' . $this->p($node->cond) . ') {'
    +             . "\n" . $this->pStmts($node->stmts) . "\n" . '}'
    +             . $this->pImplode($node->elseifs)
    +             . (null !== $node->else ? $this->p($node->else) : '');
    +    }
    +
    +    public function pStmt_Elseif(PHPParser_Node_Stmt_Elseif $node) {
    +        return ' elseif (' . $this->p($node->cond) . ') {'
    +             . "\n" . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_Else(PHPParser_Node_Stmt_Else $node) {
    +        return ' else {' . "\n" . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_For(PHPParser_Node_Stmt_For $node) {
    +        return 'for ('
    +             . $this->pCommaSeparated($node->init) . ';' . (!empty($node->cond) ? ' ' : '')
    +             . $this->pCommaSeparated($node->cond) . ';' . (!empty($node->loop) ? ' ' : '')
    +             . $this->pCommaSeparated($node->loop)
    +             . ') {' . "\n" . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_Foreach(PHPParser_Node_Stmt_Foreach $node) {
    +        return 'foreach (' . $this->p($node->expr) . ' as '
    +             . (null !== $node->keyVar ? $this->p($node->keyVar) . ' => ' : '')
    +             . ($node->byRef ? '&' : '') . $this->p($node->valueVar) . ') {'
    +             . "\n" . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_While(PHPParser_Node_Stmt_While $node) {
    +        return 'while (' . $this->p($node->cond) . ') {'
    +             . "\n" . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_Do(PHPParser_Node_Stmt_Do $node) {
    +        return 'do {' . "\n" . $this->pStmts($node->stmts) . "\n"
    +             . '} while (' . $this->p($node->cond) . ');';
    +    }
    +
    +    public function pStmt_Switch(PHPParser_Node_Stmt_Switch $node) {
    +        return 'switch (' . $this->p($node->cond) . ') {'
    +             . "\n" . $this->pStmts($node->cases) . "\n" . '}';
    +    }
    +
    +    public function pStmt_Case(PHPParser_Node_Stmt_Case $node) {
    +        return (null !== $node->cond ? 'case ' . $this->p($node->cond) : 'default') . ':'
    +             . ($node->stmts ? "\n" . $this->pStmts($node->stmts) : '');
    +    }
    +
    +    public function pStmt_TryCatch(PHPParser_Node_Stmt_TryCatch $node) {
    +        return 'try {' . "\n" . $this->pStmts($node->stmts) . "\n" . '}'
    +             . $this->pImplode($node->catches)
    +             . ($node->finallyStmts !== null
    +                ? ' finally {' . "\n" . $this->pStmts($node->finallyStmts) . "\n" . '}'
    +                : '');
    +    }
    +
    +    public function pStmt_Catch(PHPParser_Node_Stmt_Catch $node) {
    +        return ' catch (' . $this->p($node->type) . ' $' . $node->var . ') {'
    +             . "\n" . $this->pStmts($node->stmts) . "\n" . '}';
    +    }
    +
    +    public function pStmt_Break(PHPParser_Node_Stmt_Break $node) {
    +        return 'break' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';';
    +    }
    +
    +    public function pStmt_Continue(PHPParser_Node_Stmt_Continue $node) {
    +        return 'continue' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';';
    +    }
    +
    +    public function pStmt_Return(PHPParser_Node_Stmt_Return $node) {
    +        return 'return' . (null !== $node->expr ? ' ' . $this->p($node->expr) : '') . ';';
    +    }
    +
    +    public function pStmt_Throw(PHPParser_Node_Stmt_Throw $node) {
    +        return 'throw ' . $this->p($node->expr) . ';';
    +    }
    +
    +    public function pStmt_Label(PHPParser_Node_Stmt_Label $node) {
    +        return $node->name . ':';
    +    }
    +
    +    public function pStmt_Goto(PHPParser_Node_Stmt_Goto $node) {
    +        return 'goto ' . $node->name . ';';
    +    }
    +
    +    // Other
    +
    +    public function pStmt_Echo(PHPParser_Node_Stmt_Echo $node) {
    +        return 'echo ' . $this->pCommaSeparated($node->exprs) . ';';
    +    }
    +
    +    public function pStmt_Static(PHPParser_Node_Stmt_Static $node) {
    +        return 'static ' . $this->pCommaSeparated($node->vars) . ';';
    +    }
    +
    +    public function pStmt_Global(PHPParser_Node_Stmt_Global $node) {
    +        return 'global ' . $this->pCommaSeparated($node->vars) . ';';
    +    }
    +
    +    public function pStmt_StaticVar(PHPParser_Node_Stmt_StaticVar $node) {
    +        return '$' . $node->name
    +             . (null !== $node->default ? ' = ' . $this->p($node->default) : '');
    +    }
    +
    +    public function pStmt_Unset(PHPParser_Node_Stmt_Unset $node) {
    +        return 'unset(' . $this->pCommaSeparated($node->vars) . ');';
    +    }
    +
    +    public function pStmt_InlineHTML(PHPParser_Node_Stmt_InlineHTML $node) {
    +        return '?>' . $this->pNoIndent("\n" . $node->value) . 'remaining;
    +    }
    +
    +    // Helpers
    +
    +    public function pObjectProperty($node) {
    +        if ($node instanceof PHPParser_Node_Expr) {
    +            return '{' . $this->p($node) . '}';
    +        } else {
    +            return $node;
    +        }
    +    }
    +
    +    public function pModifiers($modifiers) {
    +        return ($modifiers & PHPParser_Node_Stmt_Class::MODIFIER_PUBLIC    ? 'public '    : '')
    +             . ($modifiers & PHPParser_Node_Stmt_Class::MODIFIER_PROTECTED ? 'protected ' : '')
    +             . ($modifiers & PHPParser_Node_Stmt_Class::MODIFIER_PRIVATE   ? 'private '   : '')
    +             . ($modifiers & PHPParser_Node_Stmt_Class::MODIFIER_STATIC    ? 'static '    : '')
    +             . ($modifiers & PHPParser_Node_Stmt_Class::MODIFIER_ABSTRACT  ? 'abstract '  : '')
    +             . ($modifiers & PHPParser_Node_Stmt_Class::MODIFIER_FINAL     ? 'final '     : '');
    +    }
    +
    +    public function pEncapsList(array $encapsList, $quote) {
    +        $return = '';
    +        foreach ($encapsList as $element) {
    +            if (is_string($element)) {
    +                $return .= addcslashes($element, "\n\r\t\f\v$" . $quote . "\\");
    +            } else {
    +                $return .= '{' . $this->p($element) . '}';
    +            }
    +        }
    +
    +        return $return;
    +    }
    +
    +    public function pVarOrNewExpr(PHPParser_Node $node) {
    +        if ($node instanceof PHPParser_Node_Expr_New) {
    +            return '(' . $this->p($node) . ')';
    +        } else {
    +            return $this->p($node);
    +        }
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/PrettyPrinter/Zend.php b/vendor/nikic/php-parser/lib/PHPParser/PrettyPrinter/Zend.php
    new file mode 100644
    index 0000000..f9fd0c4
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/PrettyPrinter/Zend.php
    @@ -0,0 +1,10 @@
    + array( 1,  1),
    +        'Expr_PreInc'           => array( 1,  1),
    +        'Expr_PreDec'           => array( 1,  1),
    +        'Expr_PostInc'          => array( 1, -1),
    +        'Expr_PostDec'          => array( 1, -1),
    +        'Expr_UnaryPlus'        => array( 1,  1),
    +        'Expr_UnaryMinus'       => array( 1,  1),
    +        'Expr_Cast_Int'         => array( 1,  1),
    +        'Expr_Cast_Double'      => array( 1,  1),
    +        'Expr_Cast_String'      => array( 1,  1),
    +        'Expr_Cast_Array'       => array( 1,  1),
    +        'Expr_Cast_Object'      => array( 1,  1),
    +        'Expr_Cast_Bool'        => array( 1,  1),
    +        'Expr_Cast_Unset'       => array( 1,  1),
    +        'Expr_ErrorSuppress'    => array( 1,  1),
    +        'Expr_Instanceof'       => array( 2,  0),
    +        'Expr_BooleanNot'       => array( 3,  1),
    +        'Expr_Mul'              => array( 4, -1),
    +        'Expr_Div'              => array( 4, -1),
    +        'Expr_Mod'              => array( 4, -1),
    +        'Expr_Plus'             => array( 5, -1),
    +        'Expr_Minus'            => array( 5, -1),
    +        'Expr_Concat'           => array( 5, -1),
    +        'Expr_ShiftLeft'        => array( 6, -1),
    +        'Expr_ShiftRight'       => array( 6, -1),
    +        'Expr_Smaller'          => array( 7,  0),
    +        'Expr_SmallerOrEqual'   => array( 7,  0),
    +        'Expr_Greater'          => array( 7,  0),
    +        'Expr_GreaterOrEqual'   => array( 7,  0),
    +        'Expr_Equal'            => array( 8,  0),
    +        'Expr_NotEqual'         => array( 8,  0),
    +        'Expr_Identical'        => array( 8,  0),
    +        'Expr_NotIdentical'     => array( 8,  0),
    +        'Expr_BitwiseAnd'       => array( 9, -1),
    +        'Expr_BitwiseXor'       => array(10, -1),
    +        'Expr_BitwiseOr'        => array(11, -1),
    +        'Expr_BooleanAnd'       => array(12, -1),
    +        'Expr_BooleanOr'        => array(13, -1),
    +        'Expr_Ternary'          => array(14, -1),
    +        // parser uses %left for assignments, but they really behave as %right
    +        'Expr_Assign'           => array(15,  1),
    +        'Expr_AssignRef'        => array(15,  1),
    +        'Expr_AssignPlus'       => array(15,  1),
    +        'Expr_AssignMinus'      => array(15,  1),
    +        'Expr_AssignMul'        => array(15,  1),
    +        'Expr_AssignDiv'        => array(15,  1),
    +        'Expr_AssignConcat'     => array(15,  1),
    +        'Expr_AssignMod'        => array(15,  1),
    +        'Expr_AssignBitwiseAnd' => array(15,  1),
    +        'Expr_AssignBitwiseOr'  => array(15,  1),
    +        'Expr_AssignBitwiseXor' => array(15,  1),
    +        'Expr_AssignShiftLeft'  => array(15,  1),
    +        'Expr_AssignShiftRight' => array(15,  1),
    +        'Expr_LogicalAnd'       => array(16, -1),
    +        'Expr_LogicalXor'       => array(17, -1),
    +        'Expr_LogicalOr'        => array(18, -1),
    +        'Expr_Include'          => array(19, -1),
    +    );
    +
    +    protected $noIndentToken;
    +    protected $canUseSemicolonNamespaces;
    +
    +    public function __construct() {
    +        $this->noIndentToken = '_NO_INDENT_' . mt_rand();
    +    }
    +
    +    /**
    +     * Pretty prints an array of statements.
    +     *
    +     * @param PHPParser_Node[] $stmts Array of statements
    +     *
    +     * @return string Pretty printed statements
    +     */
    +    public function prettyPrint(array $stmts) {
    +        $this->preprocessNodes($stmts);
    +
    +        return str_replace("\n" . $this->noIndentToken, "\n", $this->pStmts($stmts, false));
    +    }
    +
    +    /**
    +     * Pretty prints an expression.
    +     *
    +     * @param PHPParser_Node_Expr $node Expression node
    +     *
    +     * @return string Pretty printed node
    +     */
    +    public function prettyPrintExpr(PHPParser_Node_Expr $node) {
    +        return str_replace("\n" . $this->noIndentToken, "\n", $this->p($node));
    +    }
    +
    +    /**
    +     * Pretty prints a file of statements (includes the opening prettyPrint($stmts));
    +
    +        $p = preg_replace('/^\?>\n?/', '', $p, -1, $count);
    +        $p = preg_replace('/<\?php$/', '', $p);
    +
    +        if (!$count) {
    +            $p = "canUseSemicolonNamespaces = true;
    +        foreach ($nodes as $node) {
    +            if ($node instanceof PHPParser_Node_Stmt_Namespace && null === $node->name) {
    +                $this->canUseSemicolonNamespaces = false;
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Pretty prints an array of nodes (statements) and indents them optionally.
    +     *
    +     * @param PHPParser_Node[] $nodes  Array of nodes
    +     * @param bool             $indent Whether to indent the printed nodes
    +     *
    +     * @return string Pretty printed statements
    +     */
    +    protected function pStmts(array $nodes, $indent = true) {
    +        $pNodes = array();
    +        foreach ($nodes as $node) {
    +            $pNodes[] = $this->pComments($node->getAttribute('comments', array()))
    +                      . $this->p($node)
    +                      . ($node instanceof PHPParser_Node_Expr ? ';' : '');
    +        }
    +
    +        if ($indent) {
    +            return '    ' . preg_replace(
    +                '~\n(?!$|' . $this->noIndentToken . ')~',
    +                "\n" . '    ',
    +                implode("\n", $pNodes)
    +            );
    +        } else {
    +            return implode("\n", $pNodes);
    +        }
    +    }
    +
    +    /**
    +     * Pretty prints a node.
    +     *
    +     * @param PHPParser_Node $node Node to be pretty printed
    +     *
    +     * @return string Pretty printed node
    +     */
    +    protected function p(PHPParser_Node $node) {
    +        return $this->{'p' . $node->getType()}($node);
    +    }
    +
    +    protected function pInfixOp($type, PHPParser_Node $leftNode, $operatorString, PHPParser_Node $rightNode) {
    +        list($precedence, $associativity) = $this->precedenceMap[$type];
    +
    +        return $this->pPrec($leftNode, $precedence, $associativity, -1)
    +             . $operatorString
    +             . $this->pPrec($rightNode, $precedence, $associativity, 1);
    +    }
    +
    +    protected function pPrefixOp($type, $operatorString, PHPParser_Node $node) {
    +        list($precedence, $associativity) = $this->precedenceMap[$type];
    +        return $operatorString . $this->pPrec($node, $precedence, $associativity, 1);
    +    }
    +
    +    protected function pPostfixOp($type, PHPParser_Node $node, $operatorString) {
    +        list($precedence, $associativity) = $this->precedenceMap[$type];
    +        return $this->pPrec($node, $precedence, $associativity, -1) . $operatorString;
    +    }
    +
    +    /**
    +     * Prints an expression node with the least amount of parentheses necessary to preserve the meaning.
    +     *
    +     * @param PHPParser_Node $node                Node to pretty print
    +     * @param int            $parentPrecedence    Precedence of the parent operator
    +     * @param int            $parentAssociativity Associativity of parent operator
    +     *                                            (-1 is left, 0 is nonassoc, 1 is right)
    +     * @param int            $childPosition       Position of the node relative to the operator
    +     *                                            (-1 is left, 1 is right)
    +     *
    +     * @return string The pretty printed node
    +     */
    +    protected function pPrec(PHPParser_Node $node, $parentPrecedence, $parentAssociativity, $childPosition) {
    +        $type = $node->getType();
    +        if (isset($this->precedenceMap[$type])) {
    +            $childPrecedence = $this->precedenceMap[$type][0];
    +            if ($childPrecedence > $parentPrecedence
    +                || ($parentPrecedence == $childPrecedence && $parentAssociativity != $childPosition)
    +            ) {
    +                return '(' . $this->{'p' . $type}($node) . ')';
    +            }
    +        }
    +
    +        return $this->{'p' . $type}($node);
    +    }
    +
    +    /**
    +     * Pretty prints an array of nodes and implodes the printed values.
    +     *
    +     * @param PHPParser_Node[] $nodes Array of Nodes to be printed
    +     * @param string           $glue  Character to implode with
    +     *
    +     * @return string Imploded pretty printed nodes
    +     */
    +    protected function pImplode(array $nodes, $glue = '') {
    +        $pNodes = array();
    +        foreach ($nodes as $node) {
    +            $pNodes[] = $this->p($node);
    +        }
    +
    +        return implode($glue, $pNodes);
    +    }
    +
    +    /**
    +     * Pretty prints an array of nodes and implodes the printed values with commas.
    +     *
    +     * @param PHPParser_Node[] $nodes Array of Nodes to be printed
    +     *
    +     * @return string Comma separated pretty printed nodes
    +     */
    +    protected function pCommaSeparated(array $nodes) {
    +        return $this->pImplode($nodes, ', ');
    +    }
    +
    +    /**
    +     * Signals the pretty printer that a string shall not be indented.
    +     *
    +     * @param string $string Not to be indented string
    +     *
    +     * @return mixed String marked with $this->noIndentToken's.
    +     */
    +    protected function pNoIndent($string) {
    +        return str_replace("\n", "\n" . $this->noIndentToken, $string);
    +    }
    +
    +    protected function pComments(array $comments) {
    +        $result = '';
    +
    +        foreach ($comments as $comment) {
    +            $result .= $comment->getReformattedText() . "\n";
    +        }
    +
    +        return $result;
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Serializer.php b/vendor/nikic/php-parser/lib/PHPParser/Serializer.php
    new file mode 100644
    index 0000000..e63decc
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Serializer.php
    @@ -0,0 +1,13 @@
    +writer = new XMLWriter;
    +        $this->writer->openMemory();
    +        $this->writer->setIndent(true);
    +    }
    +
    +    public function serialize(array $nodes) {
    +        $this->writer->flush();
    +        $this->writer->startDocument('1.0', 'UTF-8');
    +
    +        $this->writer->startElement('AST');
    +        $this->writer->writeAttribute('xmlns:node',      'http://nikic.github.com/PHPParser/XML/node');
    +        $this->writer->writeAttribute('xmlns:subNode',   'http://nikic.github.com/PHPParser/XML/subNode');
    +        $this->writer->writeAttribute('xmlns:attribute', 'http://nikic.github.com/PHPParser/XML/attribute');
    +        $this->writer->writeAttribute('xmlns:scalar',    'http://nikic.github.com/PHPParser/XML/scalar');
    +
    +        $this->_serialize($nodes);
    +
    +        $this->writer->endElement();
    +
    +        return $this->writer->outputMemory();
    +    }
    +
    +    protected function _serialize($node) {
    +        if ($node instanceof PHPParser_Node) {
    +            $this->writer->startElement('node:' . $node->getType());
    +
    +            foreach ($node->getAttributes() as $name => $value) {
    +                $this->writer->startElement('attribute:' . $name);
    +                $this->_serialize($value);
    +                $this->writer->endElement();
    +            }
    +
    +            foreach ($node as $name => $subNode) {
    +                $this->writer->startElement('subNode:' . $name);
    +                $this->_serialize($subNode);
    +                $this->writer->endElement();
    +            }
    +
    +            $this->writer->endElement();
    +        } elseif ($node instanceof PHPParser_Comment) {
    +            $this->writer->startElement('comment');
    +            $this->writer->writeAttribute('isDocComment', $node instanceof PHPParser_Comment_Doc ? 'true' : 'false');
    +            $this->writer->writeAttribute('line', $node->getLine());
    +            $this->writer->text($node->getText());
    +            $this->writer->endElement();
    +        } elseif (is_array($node)) {
    +            $this->writer->startElement('scalar:array');
    +            foreach ($node as $subNode) {
    +                $this->_serialize($subNode);
    +            }
    +            $this->writer->endElement();
    +        } elseif (is_string($node)) {
    +            $this->writer->writeElement('scalar:string', $node);
    +        } elseif (is_int($node)) {
    +            $this->writer->writeElement('scalar:int', $node);
    +        } elseif (is_float($node)) {
    +            $this->writer->writeElement('scalar:float', $node);
    +        } elseif (true === $node) {
    +            $this->writer->writeElement('scalar:true');
    +        } elseif (false === $node) {
    +            $this->writer->writeElement('scalar:false');
    +        } elseif (null === $node) {
    +            $this->writer->writeElement('scalar:null');
    +        } else {
    +            throw new InvalidArgumentException('Unexpected node type');
    +        }
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Template.php b/vendor/nikic/php-parser/lib/PHPParser/Template.php
    new file mode 100644
    index 0000000..506b0d5
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Template.php
    @@ -0,0 +1,72 @@
    +parser   = $parser;
    +        $this->template = $template;
    +    }
    +
    +    /**
    +     * Get the statements of the template with the passed in placeholders
    +     * replaced.
    +     *
    +     * @param array $placeholders Placeholders
    +     *
    +     * @return PHPParser_Node[] Statements
    +     */
    +    public function getStmts(array $placeholders) {
    +        return $this->parser->parse(
    +            $this->getTemplateWithPlaceholdersReplaced($placeholders)
    +        );
    +    }
    +
    +    protected function getTemplateWithPlaceholdersReplaced(array $placeholders) {
    +        if (empty($placeholders)) {
    +            return $this->template;
    +        }
    +
    +        return strtr($this->template, $this->preparePlaceholders($placeholders));
    +    }
    +
    +    /*
    +     * Prepare the placeholders for replacement. This means that
    +     * a) all placeholders will be surrounded with __.
    +     * b) ucfirst/lcfirst variations of the placeholders are generated.
    +     *
    +     * E.g. for an input array of ['foo' => 'bar'] the result will be
    +     * ['__foo__' => 'bar', '__Foo__' => 'Bar'].
    +     */
    +    protected function preparePlaceholders(array $placeholders) {
    +        $preparedPlaceholders = array();
    +
    +        foreach ($placeholders as $name => $value) {
    +            $preparedPlaceholders['__' . $name . '__'] = $value;
    +
    +            if (ctype_lower($name[0])) {
    +                $ucfirstName = ucfirst($name);
    +                if (!isset($placeholders[$ucfirstName])) {
    +                    $preparedPlaceholders['__' . $ucfirstName . '__'] = ucfirst($value);
    +                }
    +            }
    +
    +            if (ctype_upper($name[0])) {
    +                $lcfirstName = lcfirst($name);
    +                if (!isset($placeholders[$lcfirstName])) {
    +                    $preparedPlaceholders['__' . $lcfirstName . '__'] = lcfirst($value);
    +                }
    +            }
    +        }
    +
    +        return $preparedPlaceholders;
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/TemplateLoader.php b/vendor/nikic/php-parser/lib/PHPParser/TemplateLoader.php
    new file mode 100644
    index 0000000..dc9d26d
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/TemplateLoader.php
    @@ -0,0 +1,48 @@
    +parser  = $parser;
    +        $this->baseDir = $baseDir;
    +        $this->suffix  = $suffix;
    +    }
    +
    +    /**
    +     * Loads the template with the specified name.
    +     *
    +     * @param string $name The name of template
    +     *
    +     * @return PHPParser_Template The loaded template
    +     */
    +    public function load($name) {
    +        $file = $this->baseDir . '/' . $name . $this->suffix;
    +
    +        if (!is_file($file)) {
    +            throw new InvalidArgumentException(
    +                sprintf('The file "%s" does not exist', $file)
    +            );
    +        }
    +
    +        return new PHPParser_Template($this->parser, file_get_contents($file));
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/lib/PHPParser/Unserializer.php b/vendor/nikic/php-parser/lib/PHPParser/Unserializer.php
    new file mode 100644
    index 0000000..34808c8
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/PHPParser/Unserializer.php
    @@ -0,0 +1,13 @@
    +reader = new XMLReader;
    +    }
    +
    +    public function unserialize($string) {
    +        $this->reader->XML($string);
    +
    +        $this->reader->read();
    +        if ('AST' !== $this->reader->name) {
    +            throw new DomainException('AST root element not found');
    +        }
    +
    +        return $this->read($this->reader->depth);
    +    }
    +
    +    protected function read($depthLimit, $throw = true, &$nodeFound = null) {
    +        $nodeFound = true;
    +        while ($this->reader->read() && $depthLimit < $this->reader->depth) {
    +            if (XMLReader::ELEMENT !== $this->reader->nodeType) {
    +                continue;
    +            }
    +
    +            if ('node' === $this->reader->prefix) {
    +                return $this->readNode();
    +            } elseif ('scalar' === $this->reader->prefix) {
    +                return $this->readScalar();
    +            } elseif ('comment' === $this->reader->name) {
    +                return $this->readComment();
    +            } else {
    +                throw new DomainException(sprintf('Unexpected node of type "%s"', $this->reader->name));
    +            }
    +        }
    +
    +        $nodeFound = false;
    +        if ($throw) {
    +            throw new DomainException('Expected node or scalar');
    +        }
    +    }
    +
    +    protected function readNode()
    +    {
    +        $className = 'PHPParser_Node_' . $this->reader->localName;
    +
    +        // create the node without calling it's constructor
    +        $node = unserialize(
    +            sprintf(
    +                "O:%d:\"%s\":2:{s:11:\"\0*\0subNodes\";a:0:{}s:13:\"\0*\0attributes\";a:0:{}}",
    +                strlen($className), $className
    +            )
    +        );
    +
    +        $depthLimit = $this->reader->depth;
    +        while ($this->reader->read() && $depthLimit < $this->reader->depth) {
    +            if (XMLReader::ELEMENT !== $this->reader->nodeType) {
    +                continue;
    +            }
    +
    +            $type = $this->reader->prefix;
    +            if ('subNode' !== $type && 'attribute' !== $type) {
    +                throw new DomainException(
    +                    sprintf('Expected sub node or attribute, got node of type "%s"', $this->reader->name)
    +                );
    +            }
    +
    +            $name = $this->reader->localName;
    +            $value = $this->read($this->reader->depth);
    +
    +            if ('subNode' === $type) {
    +                $node->$name = $value;
    +            } else {
    +                $node->setAttribute($name, $value);
    +            }
    +        }
    +
    +        return $node;
    +    }
    +
    +    protected function readScalar() {
    +        switch ($name = $this->reader->localName) {
    +            case 'array':
    +                $depth = $this->reader->depth;
    +                $array = array();
    +                while (true) {
    +                    $node = $this->read($depth, false, $nodeFound);
    +                    if (!$nodeFound) {
    +                        break;
    +                    }
    +                    $array[] = $node;
    +                }
    +                return $array;
    +            case 'string':
    +                return $this->reader->readString();
    +            case 'int':
    +                $text = $this->reader->readString();
    +                if (false === $int = filter_var($text, FILTER_VALIDATE_INT)) {
    +                    throw new DomainException(sprintf('"%s" is not a valid integer', $text));
    +                }
    +                return $int;
    +            case 'float':
    +                $text = $this->reader->readString();
    +                if (false === $float = filter_var($text, FILTER_VALIDATE_FLOAT)) {
    +                    throw new DomainException(sprintf('"%s" is not a valid float', $text));
    +                }
    +                return $float;
    +            case 'true':
    +            case 'false':
    +            case 'null':
    +                if (!$this->reader->isEmptyElement) {
    +                    throw new DomainException(sprintf('"%s" scalar must be empty', $name));
    +                }
    +                return constant($name);
    +            default:
    +                throw new DomainException(sprintf('Unknown scalar type "%s"', $name));
    +        }
    +    }
    +
    +    protected function readComment() {
    +        $className = $this->reader->getAttribute('isDocComment') === 'true'
    +            ? 'PHPParser_Comment_Doc'
    +            : 'PHPParser_Comment'
    +        ;
    +        return new $className(
    +            $this->reader->readString(),
    +            $this->reader->getAttribute('line')
    +        );
    +    }
    +}
    diff --git a/vendor/nikic/php-parser/lib/bootstrap.php b/vendor/nikic/php-parser/lib/bootstrap.php
    new file mode 100644
    index 0000000..5b812b4
    --- /dev/null
    +++ b/vendor/nikic/php-parser/lib/bootstrap.php
    @@ -0,0 +1,14 @@
    +
    +
    +
    +    
    +        
    +            ./test/
    +        
    +    
    +
    +    
    +        
    +            ./lib/PHPParser/
    +        
    +    
    +
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/Builder/ClassTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/Builder/ClassTest.php
    new file mode 100644
    index 0000000..6591ca7
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/Builder/ClassTest.php
    @@ -0,0 +1,91 @@
    +createClassBuilder('SomeLogger')
    +            ->extend('BaseLogger')
    +            ->implement('Namespaced\Logger', new PHPParser_Node_Name('SomeInterface'))
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Stmt_Class('SomeLogger', array(
    +                'extends' => new PHPParser_Node_Name('BaseLogger'),
    +                'implements' => array(
    +                    new PHPParser_Node_Name('Namespaced\Logger'),
    +                    new PHPParser_Node_Name('SomeInterface')
    +                ),
    +            )),
    +            $node
    +        );
    +    }
    +
    +    public function testAbstract() {
    +        $node = $this->createClassBuilder('Test')
    +            ->makeAbstract()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Stmt_Class('Test', array(
    +                'type' => PHPParser_Node_Stmt_Class::MODIFIER_ABSTRACT
    +            )),
    +            $node
    +        );
    +    }
    +
    +    public function testFinal() {
    +        $node = $this->createClassBuilder('Test')
    +            ->makeFinal()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Stmt_Class('Test', array(
    +                'type' => PHPParser_Node_Stmt_Class::MODIFIER_FINAL
    +            )),
    +            $node
    +        );
    +    }
    +
    +    public function testStatementOrder() {
    +        $method = new PHPParser_Node_Stmt_ClassMethod('testMethod');
    +        $property = new PHPParser_Node_Stmt_Property(
    +            PHPParser_Node_Stmt_Class::MODIFIER_PUBLIC,
    +            array(new PHPParser_Node_Stmt_PropertyProperty('testProperty'))
    +        );
    +        $const = new PHPParser_Node_Stmt_ClassConst(array(
    +            new PHPParser_Node_Const('TEST_CONST', new PHPParser_Node_Scalar_String('ABC'))
    +        ));
    +        $use = new PHPParser_Node_Stmt_TraitUse(array(new PHPParser_Node_Name('SomeTrait')));
    +
    +        $node = $this->createClassBuilder('Test')
    +            ->addStmt($method)
    +            ->addStmt($property)
    +            ->addStmts(array($const, $use))
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Stmt_Class('Test', array(
    +                'stmts' => array($use, $const, $property, $method)
    +            )),
    +            $node
    +        );
    +    }
    +
    +    /**
    +     * @expectedException LogicException
    +     * @expectedExceptionMessage Unexpected node of type "Stmt_Echo"
    +     */
    +    public function testInvalidStmtError() {
    +        $this->createClassBuilder('Test')
    +            ->addStmt(new PHPParser_Node_Stmt_Echo(array()))
    +        ;
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/Builder/FunctionTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/Builder/FunctionTest.php
    new file mode 100644
    index 0000000..2992f82
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/Builder/FunctionTest.php
    @@ -0,0 +1,70 @@
    +createFunctionBuilder('test')
    +            ->makeReturnByRef()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Stmt_Function('test', array(
    +                'byRef' => true
    +            )),
    +            $node
    +        );
    +    }
    +
    +    public function testParams() {
    +        $param1 = new PHPParser_Node_Param('test1');
    +        $param2 = new PHPParser_Node_Param('test2');
    +        $param3 = new PHPParser_Node_Param('test3');
    +
    +        $node = $this->createFunctionBuilder('test')
    +            ->addParam($param1)
    +            ->addParams(array($param2, $param3))
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Stmt_Function('test', array(
    +                'params' => array($param1, $param2, $param3)
    +            )),
    +            $node
    +        );
    +    }
    +
    +    public function testStmts() {
    +        $stmt1 = new PHPParser_Node_Expr_Print(new PHPParser_Node_Scalar_String('test1'));
    +        $stmt2 = new PHPParser_Node_Expr_Print(new PHPParser_Node_Scalar_String('test2'));
    +        $stmt3 = new PHPParser_Node_Expr_Print(new PHPParser_Node_Scalar_String('test3'));
    +
    +        $node = $this->createFunctionBuilder('test')
    +            ->addStmt($stmt1)
    +            ->addStmts(array($stmt2, $stmt3))
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Stmt_Function('test', array(
    +                'stmts' => array($stmt1, $stmt2, $stmt3)
    +            )),
    +            $node
    +        );
    +    }
    +
    +    /**
    +     * @expectedException LogicException
    +     * @expectedExceptionMessage Expected parameter node, got "Name"
    +     */
    +    public function testInvalidParamError() {
    +        $this->createFunctionBuilder('test')
    +            ->addParam(new PHPParser_Node_Name('foo'))
    +        ;
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/Builder/InterfaceTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/Builder/InterfaceTest.php
    new file mode 100644
    index 0000000..fb36338
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/Builder/InterfaceTest.php
    @@ -0,0 +1,91 @@
    +builder = new PHPParser_Builder_Interface('Contract');
    +    }
    +
    +    private function dump($node) {
    +        $pp = new PHPParser_PrettyPrinter_Default();
    +        return $pp->prettyPrint(array($node));
    +    }
    +
    +    public function testEmpty() {
    +        $contract = $this->builder->getNode();
    +        $this->assertInstanceOf('PHPParser_Node_Stmt_Interface', $contract);
    +        $this->assertEquals('Contract', $contract->name);
    +    }
    +
    +    public function testExtending() {
    +        $contract = $this->builder->extend('Space\Root1', 'Root2')->getNode();
    +        $this->assertEquals(
    +            new PHPParser_Node_Stmt_Interface('Contract', array(
    +                'extends' => array(
    +                    new PHPParser_Node_Name('Space\Root1'),
    +                    new PHPParser_Node_Name('Root2')
    +                ),
    +            )), $contract
    +        );
    +    }
    +
    +    public function testAddMethod() {
    +        $method = new PHPParser_Node_Stmt_ClassMethod('doSomething');
    +        $contract = $this->builder->addStmt($method)->getNode();
    +        $this->assertEquals(array($method), $contract->stmts);
    +    }
    +
    +    public function testAddConst() {
    +        $const = new PHPParser_Node_Stmt_ClassConst(array(
    +            new PHPParser_Node_Const('SPEED_OF_LIGHT', new PHPParser_Node_Scalar_DNumber(299792458))
    +        ));
    +        $contract = $this->builder->addStmt($const)->getNode();
    +        $this->assertEquals(299792458, $contract->stmts[0]->consts[0]->value->value);
    +    }
    +
    +    public function testOrder() {
    +        $const = new PHPParser_Node_Stmt_ClassConst(array(
    +            new PHPParser_Node_Const('SPEED_OF_LIGHT', new PHPParser_Node_Scalar_DNumber(299792458))
    +        ));
    +        $method = new PHPParser_Node_Stmt_ClassMethod('doSomething');
    +        $contract = $this->builder
    +            ->addStmt($method)
    +            ->addStmt($const)
    +            ->getNode()
    +        ;
    +
    +        $this->assertInstanceOf('PHPParser_Node_Stmt_ClassConst', $contract->stmts[0]);
    +        $this->assertInstanceOf('PHPParser_Node_Stmt_ClassMethod', $contract->stmts[1]);
    +    }
    +
    +    /**
    +     * @expectedException LogicException
    +     * @expectedExceptionMessage Unexpected node of type "Stmt_PropertyProperty"
    +     */
    +    public function testInvalidStmtError() {
    +        $this->builder->addStmt(new PHPParser_Node_Stmt_PropertyProperty('invalid'));
    +    }
    +
    +    public function testFullFunctional() {
    +        $const = new PHPParser_Node_Stmt_ClassConst(array(
    +            new PHPParser_Node_Const('SPEED_OF_LIGHT', new PHPParser_Node_Scalar_DNumber(299792458))
    +        ));
    +        $method = new PHPParser_Node_Stmt_ClassMethod('doSomething');
    +        $contract = $this->builder
    +            ->addStmt($method)
    +            ->addStmt($const)
    +            ->getNode()
    +        ;
    +
    +        eval($this->dump($contract));
    +
    +        $this->assertTrue(interface_exists('Contract', false));
    +    }
    +}
    +
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/Builder/MethodTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/Builder/MethodTest.php
    new file mode 100644
    index 0000000..6f4624a
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/Builder/MethodTest.php
    @@ -0,0 +1,137 @@
    +createMethodBuilder('test')
    +            ->makePublic()
    +            ->makeAbstract()
    +            ->makeStatic()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Stmt_ClassMethod('test', array(
    +                'type' => PHPParser_Node_Stmt_Class::MODIFIER_PUBLIC
    +                        | PHPParser_Node_Stmt_Class::MODIFIER_ABSTRACT
    +                        | PHPParser_Node_Stmt_Class::MODIFIER_STATIC,
    +                'stmts' => null,
    +            )),
    +            $node
    +        );
    +
    +        $node = $this->createMethodBuilder('test')
    +            ->makeProtected()
    +            ->makeFinal()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Stmt_ClassMethod('test', array(
    +                'type' => PHPParser_Node_Stmt_Class::MODIFIER_PROTECTED
    +                        | PHPParser_Node_Stmt_Class::MODIFIER_FINAL
    +            )),
    +            $node
    +        );
    +
    +        $node = $this->createMethodBuilder('test')
    +            ->makePrivate()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Stmt_ClassMethod('test', array(
    +                'type' => PHPParser_Node_Stmt_Class::MODIFIER_PRIVATE
    +            )),
    +            $node
    +        );
    +    }
    +
    +    public function testReturnByRef() {
    +        $node = $this->createMethodBuilder('test')
    +            ->makeReturnByRef()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Stmt_ClassMethod('test', array(
    +                'byRef' => true
    +            )),
    +            $node
    +        );
    +    }
    +
    +    public function testParams() {
    +        $param1 = new PHPParser_Node_Param('test1');
    +        $param2 = new PHPParser_Node_Param('test2');
    +        $param3 = new PHPParser_Node_Param('test3');
    +
    +        $node = $this->createMethodBuilder('test')
    +            ->addParam($param1)
    +            ->addParams(array($param2, $param3))
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Stmt_ClassMethod('test', array(
    +                'params' => array($param1, $param2, $param3)
    +            )),
    +            $node
    +        );
    +    }
    +
    +    public function testStmts() {
    +        $stmt1 = new PHPParser_Node_Expr_Print(new PHPParser_Node_Scalar_String('test1'));
    +        $stmt2 = new PHPParser_Node_Expr_Print(new PHPParser_Node_Scalar_String('test2'));
    +        $stmt3 = new PHPParser_Node_Expr_Print(new PHPParser_Node_Scalar_String('test3'));
    +
    +        $node = $this->createMethodBuilder('test')
    +            ->addStmt($stmt1)
    +            ->addStmts(array($stmt2, $stmt3))
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Stmt_ClassMethod('test', array(
    +                'stmts' => array($stmt1, $stmt2, $stmt3)
    +            )),
    +            $node
    +        );
    +    }
    +
    +    /**
    +     * @expectedException LogicException
    +     * @expectedExceptionMessage Cannot add statements to an abstract method
    +     */
    +    public function testAddStmtToAbstractMethodError() {
    +        $this->createMethodBuilder('test')
    +            ->makeAbstract()
    +            ->addStmt(new PHPParser_Node_Expr_Print(new PHPParser_Node_Scalar_String('test')))
    +        ;
    +    }
    +
    +    /**
    +     * @expectedException LogicException
    +     * @expectedExceptionMessage Cannot make method with statements abstract
    +     */
    +    public function testMakeMethodWithStmtsAbstractError() {
    +        $this->createMethodBuilder('test')
    +            ->addStmt(new PHPParser_Node_Expr_Print(new PHPParser_Node_Scalar_String('test')))
    +            ->makeAbstract()
    +        ;
    +    }
    +
    +    /**
    +     * @expectedException LogicException
    +     * @expectedExceptionMessage Expected parameter node, got "Name"
    +     */
    +    public function testInvalidParamError() {
    +        $this->createMethodBuilder('test')
    +            ->addParam(new PHPParser_Node_Name('foo'))
    +        ;
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/Builder/ParamTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/Builder/ParamTest.php
    new file mode 100644
    index 0000000..4010ea8
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/Builder/ParamTest.php
    @@ -0,0 +1,118 @@
    +createParamBuilder('test')
    +            ->setDefault($value)
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals($expectedValueNode, $node->default);
    +    }
    +
    +    public function provideTestDefaultValues() {
    +        return array(
    +            array(
    +                null,
    +                new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('null'))
    +            ),
    +            array(
    +                true,
    +                new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('true'))
    +            ),
    +            array(
    +                false,
    +                new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('false'))
    +            ),
    +            array(
    +                31415,
    +                new PHPParser_Node_Scalar_LNumber(31415)
    +            ),
    +            array(
    +                3.1415,
    +                new PHPParser_Node_Scalar_DNumber(3.1415)
    +            ),
    +            array(
    +                'Hallo World',
    +                new PHPParser_Node_Scalar_String('Hallo World')
    +            ),
    +            array(
    +                array(1, 2, 3),
    +                new PHPParser_Node_Expr_Array(array(
    +                    new PHPParser_Node_Expr_ArrayItem(new PHPParser_Node_Scalar_LNumber(1)),
    +                    new PHPParser_Node_Expr_ArrayItem(new PHPParser_Node_Scalar_LNumber(2)),
    +                    new PHPParser_Node_Expr_ArrayItem(new PHPParser_Node_Scalar_LNumber(3)),
    +                ))
    +            ),
    +            array(
    +                array('foo' => 'bar', 'bar' => 'foo'),
    +                new PHPParser_Node_Expr_Array(array(
    +                    new PHPParser_Node_Expr_ArrayItem(
    +                        new PHPParser_Node_Scalar_String('bar'),
    +                        new PHPParser_Node_Scalar_String('foo')
    +                    ),
    +                    new PHPParser_Node_Expr_ArrayItem(
    +                        new PHPParser_Node_Scalar_String('foo'),
    +                        new PHPParser_Node_Scalar_String('bar')
    +                    ),
    +                ))
    +            ),
    +            array(
    +                new PHPParser_Node_Scalar_DirConst,
    +                new PHPParser_Node_Scalar_DirConst
    +            )
    +        );
    +    }
    +
    +    public function testTypeHints() {
    +        $node = $this->createParamBuilder('test')
    +            ->setTypeHint('array')
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Param('test', null, 'array'),
    +            $node
    +        );
    +
    +        $node = $this->createParamBuilder('test')
    +            ->setTypeHint('callable')
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Param('test', null, 'callable'),
    +            $node
    +        );
    +
    +        $node = $this->createParamBuilder('test')
    +            ->setTypeHint('Some\Class')
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Param('test', null, new PHPParser_Node_Name('Some\Class')),
    +            $node
    +        );
    +    }
    +
    +    public function testByRef() {
    +        $node = $this->createParamBuilder('test')
    +            ->makeByRef()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Param('test', null, null, true),
    +            $node
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/Builder/PropertyTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/Builder/PropertyTest.php
    new file mode 100644
    index 0000000..fa880b2
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/Builder/PropertyTest.php
    @@ -0,0 +1,123 @@
    +createPropertyBuilder('test')
    +            ->makePrivate()
    +            ->makeStatic()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Stmt_Property(
    +                PHPParser_Node_Stmt_Class::MODIFIER_PRIVATE
    +              | PHPParser_Node_Stmt_Class::MODIFIER_STATIC,
    +                array(
    +                    new PHPParser_Node_Stmt_PropertyProperty('test')
    +                )
    +            ),
    +            $node
    +        );
    +
    +        $node = $this->createPropertyBuilder('test')
    +            ->makeProtected()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Stmt_Property(
    +                PHPParser_Node_Stmt_Class::MODIFIER_PROTECTED,
    +                array(
    +                    new PHPParser_Node_Stmt_PropertyProperty('test')
    +                )
    +            ),
    +            $node
    +        );
    +
    +        $node = $this->createPropertyBuilder('test')
    +            ->makePublic()
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Stmt_Property(
    +                PHPParser_Node_Stmt_Class::MODIFIER_PUBLIC,
    +                array(
    +                    new PHPParser_Node_Stmt_PropertyProperty('test')
    +                )
    +            ),
    +            $node
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider provideTestDefaultValues
    +     */
    +    public function testDefaultValues($value, $expectedValueNode) {
    +        $node = $this->createPropertyBuilder('test')
    +            ->setDefault($value)
    +            ->getNode()
    +        ;
    +
    +        $this->assertEquals($expectedValueNode, $node->props[0]->default);
    +    }
    +
    +    public function provideTestDefaultValues() {
    +        return array(
    +            array(
    +                null,
    +                new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('null'))
    +            ),
    +            array(
    +                true,
    +                new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('true'))
    +            ),
    +            array(
    +                false,
    +                new PHPParser_Node_Expr_ConstFetch(new PHPParser_Node_Name('false'))
    +            ),
    +            array(
    +                31415,
    +                new PHPParser_Node_Scalar_LNumber(31415)
    +            ),
    +            array(
    +                3.1415,
    +                new PHPParser_Node_Scalar_DNumber(3.1415)
    +            ),
    +            array(
    +                'Hallo World',
    +                new PHPParser_Node_Scalar_String('Hallo World')
    +            ),
    +            array(
    +                array(1, 2, 3),
    +                new PHPParser_Node_Expr_Array(array(
    +                    new PHPParser_Node_Expr_ArrayItem(new PHPParser_Node_Scalar_LNumber(1)),
    +                    new PHPParser_Node_Expr_ArrayItem(new PHPParser_Node_Scalar_LNumber(2)),
    +                    new PHPParser_Node_Expr_ArrayItem(new PHPParser_Node_Scalar_LNumber(3)),
    +                ))
    +            ),
    +            array(
    +                array('foo' => 'bar', 'bar' => 'foo'),
    +                new PHPParser_Node_Expr_Array(array(
    +                    new PHPParser_Node_Expr_ArrayItem(
    +                        new PHPParser_Node_Scalar_String('bar'),
    +                        new PHPParser_Node_Scalar_String('foo')
    +                    ),
    +                    new PHPParser_Node_Expr_ArrayItem(
    +                        new PHPParser_Node_Scalar_String('foo'),
    +                        new PHPParser_Node_Scalar_String('bar')
    +                    ),
    +                ))
    +            ),
    +            array(
    +                new PHPParser_Node_Scalar_DirConst,
    +                new PHPParser_Node_Scalar_DirConst
    +            )
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/BuilderFactoryTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/BuilderFactoryTest.php
    new file mode 100644
    index 0000000..0eaf8a9
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/BuilderFactoryTest.php
    @@ -0,0 +1,23 @@
    +assertInstanceOf($className, $factory->$methodName('test'));
    +    }
    +
    +    public function provideTestFactory() {
    +        return array(
    +            array('class',     'PHPParser_Builder_Class'),
    +            array('interface', 'PHPParser_Builder_Interface'),
    +            array('method',    'PHPParser_Builder_Method'),
    +            array('function',  'PHPParser_Builder_Function'),
    +            array('property',  'PHPParser_Builder_Property'),
    +            array('param',     'PHPParser_Builder_Param'),
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/CodeTestAbstract.php b/vendor/nikic/php-parser/test/PHPParser/Tests/CodeTestAbstract.php
    new file mode 100644
    index 0000000..d315385
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/CodeTestAbstract.php
    @@ -0,0 +1,51 @@
    +assertEquals('/* Some comment */', $comment->getText());
    +        $this->assertEquals('/* Some comment */', (string) $comment);
    +        $this->assertEquals(1, $comment->getLine());
    +
    +        $comment->setText('/* Some other comment */');
    +        $comment->setLine(10);
    +
    +        $this->assertEquals('/* Some other comment */', $comment->getText());
    +        $this->assertEquals('/* Some other comment */', (string) $comment);
    +        $this->assertEquals(10, $comment->getLine());
    +    }
    +
    +    /**
    +     * @dataProvider provideTestReformatting
    +     */
    +    public function testReformatting($commentText, $reformattedText) {
    +        $comment = new PHPParser_Comment($commentText);
    +        $this->assertEquals($reformattedText, $comment->getReformattedText());
    +    }
    +
    +    public function provideTestReformatting() {
    +        return array(
    +            array('// Some text' . "\n", '// Some text'),
    +            array('/* Some text */', '/* Some text */'),
    +            array(
    +                '/**
    +     * Some text.
    +     * Some more text.
    +     */',
    +                '/**
    + * Some text.
    + * Some more text.
    + */'
    +            ),
    +            array(
    +                '/*
    +        Some text.
    +        Some more text.
    +    */',
    +                '/*
    +    Some text.
    +    Some more text.
    +*/'
    +            ),
    +            array(
    +                '/* Some text.
    +       More text.
    +       Even more text. */',
    +                '/* Some text.
    +   More text.
    +   Even more text. */'
    +            ),
    +            // invalid comment -> no reformatting
    +            array(
    +                'hallo
    +    world',
    +                'hallo
    +    world',
    +            ),
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/ErrorTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/ErrorTest.php
    new file mode 100644
    index 0000000..de21cdd
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/ErrorTest.php
    @@ -0,0 +1,33 @@
    +assertEquals('Some error', $error->getRawMessage());
    +        $this->assertEquals(10, $error->getRawLine());
    +        $this->assertEquals('Some error on line 10', $error->getMessage());
    +
    +        return $error;
    +    }
    +
    +    /**
    +     * @depends testConstruct
    +     */
    +    public function testSetMessageAndLine(PHPParser_Error $error) {
    +        $error->setRawMessage('Some other error');
    +        $error->setRawLine(15);
    +
    +        $this->assertEquals('Some other error', $error->getRawMessage());
    +        $this->assertEquals(15, $error->getRawLine());
    +        $this->assertEquals('Some other error on line 15', $error->getMessage());
    +    }
    +
    +    public function testUnknownLine() {
    +        $error = new PHPParser_Error('Some error');
    +
    +        $this->assertEquals(-1, $error->getRawLine());
    +        $this->assertEquals('Some error on unknown line', $error->getMessage());
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/Lexer/EmulativeTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/Lexer/EmulativeTest.php
    new file mode 100644
    index 0000000..80468bc
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/Lexer/EmulativeTest.php
    @@ -0,0 +1,103 @@
    +lexer = new PHPParser_Lexer_Emulative;
    +    }
    +
    +    /**
    +     * @dataProvider provideTestReplaceKeywords
    +     */
    +    public function testReplaceKeywords($keyword, $expectedToken) {
    +        $this->lexer->startLexing('assertEquals($expectedToken, $this->lexer->getNextToken());
    +        $this->assertEquals(0, $this->lexer->getNextToken());
    +    }
    +
    +    /**
    +     * @dataProvider provideTestReplaceKeywords
    +     */
    +    public function testNoReplaceKeywordsAfterObjectOperator($keyword) {
    +        $this->lexer->startLexing('' . $keyword);
    +
    +        $this->assertEquals(PHPParser_Parser::T_OBJECT_OPERATOR, $this->lexer->getNextToken());
    +        $this->assertEquals(PHPParser_Parser::T_STRING, $this->lexer->getNextToken());
    +        $this->assertEquals(0, $this->lexer->getNextToken());
    +    }
    +
    +    public function provideTestReplaceKeywords() {
    +        return array(
    +            // PHP 5.5
    +            array('finally',       PHPParser_Parser::T_FINALLY),
    +            array('yield',         PHPParser_Parser::T_YIELD),
    +
    +            // PHP 5.4
    +            array('callable',      PHPParser_Parser::T_CALLABLE),
    +            array('insteadof',     PHPParser_Parser::T_INSTEADOF),
    +            array('trait',         PHPParser_Parser::T_TRAIT),
    +            array('__TRAIT__',     PHPParser_Parser::T_TRAIT_C),
    +
    +            // PHP 5.3
    +            array('__DIR__',       PHPParser_Parser::T_DIR),
    +            array('goto',          PHPParser_Parser::T_GOTO),
    +            array('namespace',     PHPParser_Parser::T_NAMESPACE),
    +            array('__NAMESPACE__', PHPParser_Parser::T_NS_C),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider provideTestLexNewFeatures
    +     */
    +    public function testLexNewFeatures($code, array $expectedTokens) {
    +        $this->lexer->startLexing('assertEquals($expectedTokenType, $this->lexer->getNextToken($text));
    +            $this->assertEquals($expectedTokenText, $text);
    +        }
    +        $this->assertEquals(0, $this->lexer->getNextToken());
    +    }
    +
    +    /**
    +     * @dataProvider provideTestLexNewFeatures
    +     */
    +    public function testLeaveStuffAloneInStrings($code) {
    +        $stringifiedToken = '"' . addcslashes($code, '"\\') . '"';
    +        $this->lexer->startLexing('assertEquals(PHPParser_Parser::T_CONSTANT_ENCAPSED_STRING, $this->lexer->getNextToken($text));
    +        $this->assertEquals($stringifiedToken, $text);
    +        $this->assertEquals(0, $this->lexer->getNextToken());
    +    }
    +
    +    public function provideTestLexNewFeatures() {
    +        return array(
    +            array('0b1010110', array(
    +                array(PHPParser_Parser::T_LNUMBER, '0b1010110'),
    +            )),
    +            array('0b1011010101001010110101010010101011010101010101101011001110111100', array(
    +                array(PHPParser_Parser::T_DNUMBER, '0b1011010101001010110101010010101011010101010101101011001110111100'),
    +            )),
    +            array('\\', array(
    +                array(PHPParser_Parser::T_NS_SEPARATOR, '\\'),
    +            )),
    +            array("<<<'NOWDOC'\nNOWDOC;\n", array(
    +                array(PHPParser_Parser::T_START_HEREDOC, "<<<'NOWDOC'\n"),
    +                array(PHPParser_Parser::T_END_HEREDOC, 'NOWDOC'),
    +                array(ord(';'), ';'),
    +            )),
    +            array("<<<'NOWDOC'\nFoobar\nNOWDOC;\n", array(
    +                array(PHPParser_Parser::T_START_HEREDOC, "<<<'NOWDOC'\n"),
    +                array(PHPParser_Parser::T_ENCAPSED_AND_WHITESPACE, "Foobar\n"),
    +                array(PHPParser_Parser::T_END_HEREDOC, 'NOWDOC'),
    +                array(ord(';'), ';'),
    +            )),
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/LexerTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/LexerTest.php
    new file mode 100644
    index 0000000..9a983eb
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/LexerTest.php
    @@ -0,0 +1,145 @@
    +lexer = new PHPParser_Lexer;
    +    }
    +
    +    /**
    +     * @dataProvider provideTestError
    +     */
    +    public function testError($code, $message) {
    +        try {
    +            $this->lexer->startLexing($code);
    +        } catch (PHPParser_Error $e) {
    +            $this->assertEquals($message, $e->getMessage());
    +
    +            return;
    +        }
    +
    +        $this->fail('Expected PHPParser_Error');
    +    }
    +
    +    public function provideTestError() {
    +        return array(
    +            array('lexer->startLexing($code);
    +        while ($id = $this->lexer->getNextToken($value, $startAttributes, $endAttributes)) {
    +            $token = array_shift($tokens);
    +
    +            $this->assertEquals($token[0], $id);
    +            $this->assertEquals($token[1], $value);
    +            $this->assertEquals($token[2], $startAttributes);
    +            $this->assertEquals($token[3], $endAttributes);
    +        }
    +    }
    +
    +    public function provideTestLex() {
    +        return array(
    +            // tests conversion of closing PHP tag and drop of whitespace and opening tags
    +            array(
    +                'plaintext',
    +                array(
    +                    array(
    +                        PHPParser_Parser::T_STRING, 'tokens',
    +                        array('startLine' => 1), array('endLine' => 1)
    +                    ),
    +                    array(
    +                        ord(';'), '?>',
    +                        array('startLine' => 1), array('endLine' => 1)
    +                    ),
    +                    array(
    +                        PHPParser_Parser::T_INLINE_HTML, 'plaintext',
    +                        array('startLine' => 1), array('endLine' => 1)
    +                    ),
    +                )
    +            ),
    +            // tests line numbers
    +            array(
    +                ' 2), array('endLine' => 2)
    +                    ),
    +                    array(
    +                        PHPParser_Parser::T_STRING, 'token',
    +                        array('startLine' => 2), array('endLine' => 2)
    +                    ),
    +                    array(
    +                        ord('$'), '$',
    +                        array(
    +                            'startLine' => 3,
    +                            'comments' => array(new PHPParser_Comment_Doc('/** doc' . "\n" . 'comment */', 2))
    +                        ),
    +                        array('endLine' => 3)
    +                    ),
    +                )
    +            ),
    +            // tests comment extraction
    +            array(
    +                ' 2,
    +                            'comments' => array(
    +                                new PHPParser_Comment('/* comment */', 1),
    +                                new PHPParser_Comment('// comment' . "\n", 1),
    +                                new PHPParser_Comment_Doc('/** docComment 1 */', 2),
    +                                new PHPParser_Comment_Doc('/** docComment 2 */', 2),
    +                            ),
    +                        ),
    +                        array('endLine' => 2)
    +                    ),
    +                )
    +            ),
    +            // tests differing start and end line
    +            array(
    +                ' 1), array('endLine' => 2)
    +                    ),
    +                )
    +            ),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider provideTestHaltCompiler
    +     */
    +    public function testHandleHaltCompiler($code, $remaining) {
    +        $this->lexer->startLexing($code);
    +
    +        while (PHPParser_Parser::T_HALT_COMPILER !== $this->lexer->getNextToken());
    +
    +        $this->assertEquals($this->lexer->handleHaltCompiler(), $remaining);
    +        $this->assertEquals(0, $this->lexer->getNextToken());
    +    }
    +
    +    public function provideTestHaltCompiler() {
    +        return array(
    +            array('Remaining Text', 'Remaining Text'),
    +            //array('assertEquals(array('foo', 'bar'), $name->parts);
    +
    +        $name = new PHPParser_Node_Name('foo\bar');
    +        $this->assertEquals(array('foo', 'bar'), $name->parts);
    +    }
    +
    +    public function testGet() {
    +        $name = new PHPParser_Node_Name('foo');
    +        $this->assertEquals('foo', $name->getFirst());
    +        $this->assertEquals('foo', $name->getLast());
    +
    +        $name = new PHPParser_Node_Name('foo\bar');
    +        $this->assertEquals('foo', $name->getFirst());
    +        $this->assertEquals('bar', $name->getLast());
    +    }
    +
    +    public function testToString() {
    +        $name = new PHPParser_Node_Name('foo\bar');
    +
    +        $this->assertEquals('foo\bar', (string) $name);
    +        $this->assertEquals('foo\bar', $name->toString());
    +        $this->assertEquals('foo_bar', $name->toString('_'));
    +    }
    +
    +    public function testSet() {
    +        $name = new PHPParser_Node_Name('foo');
    +
    +        $name->set('foo\bar');
    +        $this->assertEquals('foo\bar', $name->toString());
    +
    +        $name->set(array('foo', 'bar'));
    +        $this->assertEquals('foo\bar', $name->toString());
    +
    +        $name->set(new PHPParser_Node_Name('foo\bar'));
    +        $this->assertEquals('foo\bar', $name->toString());
    +    }
    +
    +    public function testSetFirst() {
    +        $name = new PHPParser_Node_Name('foo');
    +
    +        $name->setFirst('bar');
    +        $this->assertEquals('bar', $name->toString());
    +
    +        $name->setFirst('A\B');
    +        $this->assertEquals('A\B', $name->toString());
    +
    +        $name->setFirst('C');
    +        $this->assertEquals('C\B', $name->toString());
    +
    +        $name->setFirst('D\E');
    +        $this->assertEquals('D\E\B', $name->toString());
    +    }
    +
    +    public function testSetLast() {
    +        $name = new PHPParser_Node_Name('foo');
    +
    +        $name->setLast('bar');
    +        $this->assertEquals('bar', $name->toString());
    +
    +        $name->setLast('A\B');
    +        $this->assertEquals('A\B', $name->toString());
    +
    +        $name->setLast('C');
    +        $this->assertEquals('A\C', $name->toString());
    +
    +        $name->setLast('D\E');
    +        $this->assertEquals('A\D\E', $name->toString());
    +    }
    +
    +    public function testAppend() {
    +        $name = new PHPParser_Node_Name('foo');
    +
    +        $name->append('bar');
    +        $this->assertEquals('foo\bar', $name->toString());
    +
    +        $name->append('bar\foo');
    +        $this->assertEquals('foo\bar\bar\foo', $name->toString());
    +    }
    +
    +    public function testPrepend() {
    +        $name = new PHPParser_Node_Name('foo');
    +
    +        $name->prepend('bar');
    +        $this->assertEquals('bar\foo', $name->toString());
    +
    +        $name->prepend('foo\bar');
    +        $this->assertEquals('foo\bar\bar\foo', $name->toString());
    +    }
    +
    +    public function testIs() {
    +        $name = new PHPParser_Node_Name('foo');
    +        $this->assertTrue ($name->isUnqualified());
    +        $this->assertFalse($name->isQualified());
    +        $this->assertFalse($name->isFullyQualified());
    +        $this->assertFalse($name->isRelative());
    +
    +        $name = new PHPParser_Node_Name('foo\bar');
    +        $this->assertFalse($name->isUnqualified());
    +        $this->assertTrue ($name->isQualified());
    +        $this->assertFalse($name->isFullyQualified());
    +        $this->assertFalse($name->isRelative());
    +
    +        $name = new PHPParser_Node_Name_FullyQualified('foo');
    +        $this->assertFalse($name->isUnqualified());
    +        $this->assertFalse($name->isQualified());
    +        $this->assertTrue ($name->isFullyQualified());
    +        $this->assertFalse($name->isRelative());
    +
    +        $name = new PHPParser_Node_Name_Relative('foo');
    +        $this->assertFalse($name->isUnqualified());
    +        $this->assertFalse($name->isQualified());
    +        $this->assertFalse($name->isFullyQualified());
    +        $this->assertTrue ($name->isRelative());
    +    }
    +
    +    /**
    +     * @expectedException        InvalidArgumentException
    +     * @expectedExceptionMessage When changing a name you need to pass either a string, an array or a Name node
    +     */
    +    public function testInvalidArg() {
    +        $name = new PHPParser_Node_Name('foo');
    +        $name->set(new stdClass);
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/Node/Scalar/StringTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/Node/Scalar/StringTest.php
    new file mode 100644
    index 0000000..04dd35b
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/Node/Scalar/StringTest.php
    @@ -0,0 +1,59 @@
    +assertEquals(
    +            $expected,
    +            PHPParser_Node_Scalar_String::parseEscapeSequences($string, $quote)
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider provideTestParse
    +     */
    +    public function testCreate($expected, $string) {
    +        $this->assertEquals(
    +            $expected,
    +            PHPParser_Node_Scalar_String::parse($string)
    +        );
    +    }
    +
    +    public function provideTestParseEscapeSequences() {
    +        return array(
    +            array('"',              '\\"',              '"'),
    +            array('\\"',            '\\"',              '`'),
    +            array('\\"\\`',         '\\"\\`',           null),
    +            array("\\\$\n\r\t\f\v", '\\\\\$\n\r\t\f\v', null),
    +            array("\x1B",           '\e',               null),
    +            array(chr(255),         '\xFF',             null),
    +            array(chr(255),         '\377',             null),
    +            array(chr(0),           '\400',             null),
    +            array("\0",             '\0',               null),
    +            array('\xFF',           '\\\\xFF',          null),
    +        );
    +    }
    +
    +    public function provideTestParse() {
    +        $tests = array(
    +            array('A', '\'A\''),
    +            array('A', 'b\'A\''),
    +            array('A', '"A"'),
    +            array('A', 'b"A"'),
    +            array('\\', '\'\\\\\''),
    +            array('\'', '\'\\\'\''),
    +        );
    +
    +        foreach ($this->provideTestParseEscapeSequences() as $i => $test) {
    +            // skip second and third tests, they aren't for double quotes
    +            if ($i != 1 && $i != 2) {
    +                $tests[] = array($test[0], '"' . $test[1] . '"');
    +            }
    +        }
    +
    +        return $tests;
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/Node/Stmt/ClassMethodTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/Node/Stmt/ClassMethodTest.php
    new file mode 100644
    index 0000000..ddabe86
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/Node/Stmt/ClassMethodTest.php
    @@ -0,0 +1,35 @@
    + constant('PHPParser_Node_Stmt_Class::MODIFIER_' . strtoupper($modifier))
    +        ));
    +
    +        $this->assertTrue($node->{'is' . $modifier}());
    +    }
    +
    +    /**
    +     * @dataProvider provideModifiers
    +     */
    +    public function testNoModifiers($modifier) {
    +        $node = new PHPParser_Node_Stmt_ClassMethod('foo', array('type' => 0));
    +
    +        $this->assertFalse($node->{'is' . $modifier}());
    +    }
    +
    +    public function provideModifiers() {
    +        return array(
    +            array('public'),
    +            array('protected'),
    +            array('private'),
    +            array('abstract'),
    +            array('final'),
    +            array('static'),
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/Node/Stmt/ClassTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/Node/Stmt/ClassTest.php
    new file mode 100644
    index 0000000..3904815
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/Node/Stmt/ClassTest.php
    @@ -0,0 +1,40 @@
    + PHPParser_Node_Stmt_Class::MODIFIER_ABSTRACT));
    +        $this->assertTrue($class->isAbstract());
    +
    +        $class = new PHPParser_Node_Stmt_Class('Foo');
    +        $this->assertFalse($class->isAbstract());
    +    }
    +
    +    public function testIsFinal() {
    +        $class = new PHPParser_Node_Stmt_Class('Foo', array('type' => PHPParser_Node_Stmt_Class::MODIFIER_FINAL));
    +        $this->assertTrue($class->isFinal());
    +
    +        $class = new PHPParser_Node_Stmt_Class('Foo');
    +        $this->assertFalse($class->isFinal());
    +    }
    +
    +    public function testGetMethods() {
    +        $methods = array(
    +            new PHPParser_Node_Stmt_ClassMethod('foo'),
    +            new PHPParser_Node_Stmt_ClassMethod('bar'),
    +            new PHPParser_Node_Stmt_ClassMethod('fooBar'),
    +        );
    +        $class = new PHPParser_Node_Stmt_Class('Foo', array(
    +            'stmts' => array(
    +                new PHPParser_Node_Stmt_TraitUse(array()),
    +                $methods[0],
    +                new PHPParser_Node_Stmt_Const(array()),
    +                $methods[1],
    +                new PHPParser_Node_Stmt_Property(0, array()),
    +                $methods[2],
    +            )
    +        ));
    +
    +        $this->assertEquals($methods, $class->getMethods());
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/Node/Stmt/PropertyTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/Node/Stmt/PropertyTest.php
    new file mode 100644
    index 0000000..6c3f38b
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/Node/Stmt/PropertyTest.php
    @@ -0,0 +1,34 @@
    +assertTrue($node->{'is' . $modifier}());
    +    }
    +
    +    /**
    +     * @dataProvider provideModifiers
    +     */
    +    public function testNoModifiers($modifier) {
    +        $node = new PHPParser_Node_Stmt_Property(0, array());
    +
    +        $this->assertFalse($node->{'is' . $modifier}());
    +    }
    +
    +    public function provideModifiers() {
    +        return array(
    +            array('public'),
    +            array('protected'),
    +            array('private'),
    +            array('static'),
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/NodeAbstractTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/NodeAbstractTest.php
    new file mode 100644
    index 0000000..767340e
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/NodeAbstractTest.php
    @@ -0,0 +1,96 @@
    + 10,
    +            'comments'  => array(
    +                new PHPParser_Comment('// Comment' . "\n"),
    +                new PHPParser_Comment_Doc('/** doc comment */'),
    +            ),
    +        );
    +
    +        $node = $this->getMockForAbstractClass(
    +            'PHPParser_NodeAbstract',
    +            array(
    +                array(
    +                    'subNode' => 'value'
    +                ),
    +                $attributes
    +            ),
    +            'PHPParser_Node_Dummy'
    +        );
    +
    +        $this->assertEquals('Dummy', $node->getType());
    +        $this->assertEquals(array('subNode'), $node->getSubNodeNames());
    +        $this->assertEquals(10, $node->getLine());
    +        $this->assertEquals('/** doc comment */', $node->getDocComment());
    +        $this->assertEquals('value', $node->subNode);
    +        $this->assertTrue(isset($node->subNode));
    +        $this->assertEquals($attributes, $node->getAttributes());
    +
    +        return $node;
    +    }
    +
    +    /**
    +     * @depends testConstruct
    +     */
    +    public function testGetDocComment(PHPParser_Node $node) {
    +        $this->assertEquals('/** doc comment */', $node->getDocComment());
    +        array_pop($node->getAttribute('comments')); // remove doc comment
    +        $this->assertNull($node->getDocComment());
    +        array_pop($node->getAttribute('comments')); // remove comment
    +        $this->assertNull($node->getDocComment());
    +    }
    +
    +    /**
    +     * @depends testConstruct
    +     */
    +    public function testChange(PHPParser_Node $node) {
    +        // change of line
    +        $node->setLine(15);
    +        $this->assertEquals(15, $node->getLine());
    +
    +        // direct modification
    +        $node->subNode = 'newValue';
    +        $this->assertEquals('newValue', $node->subNode);
    +
    +        // indirect modification
    +        $subNode =& $node->subNode;
    +        $subNode = 'newNewValue';
    +        $this->assertEquals('newNewValue', $node->subNode);
    +
    +        // removal
    +        unset($node->subNode);
    +        $this->assertFalse(isset($node->subNode));
    +    }
    +
    +    public function testAttributes() {
    +        /** @var $node PHPParser_Node */
    +        $node = $this->getMockForAbstractClass('PHPParser_NodeAbstract');
    +
    +        $this->assertEmpty($node->getAttributes());
    +
    +        $node->setAttribute('key', 'value');
    +        $this->assertTrue($node->hasAttribute('key'));
    +        $this->assertEquals('value', $node->getAttribute('key'));
    +
    +        $this->assertFalse($node->hasAttribute('doesNotExist'));
    +        $this->assertNull($node->getAttribute('doesNotExist'));
    +        $this->assertEquals('default', $node->getAttribute('doesNotExist', 'default'));
    +
    +        $node->setAttribute('null', null);
    +        $this->assertTrue($node->hasAttribute('null'));
    +        $this->assertNull($node->getAttribute('null'));
    +        $this->assertNull($node->getAttribute('null', 'default'));
    +
    +        $this->assertEquals(
    +            array(
    +                'key'  => 'value',
    +                'null' => null,
    +            ),
    +            $node->getAttributes()
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/NodeDumperTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/NodeDumperTest.php
    new file mode 100644
    index 0000000..5ea29d5
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/NodeDumperTest.php
    @@ -0,0 +1,66 @@
    +assertEquals($dump, $dumper->dump($node));
    +    }
    +
    +    public function provideTestDump() {
    +        return array(
    +            array(
    +                array(),
    +'array(
    +)'
    +            ),
    +            array(
    +                array('Foo', 'Bar', 'Key' => 'FooBar'),
    +'array(
    +    0: Foo
    +    1: Bar
    +    Key: FooBar
    +)'
    +            ),
    +            array(
    +                new PHPParser_Node_Name(array('Hallo', 'World')),
    +'Name(
    +    parts: array(
    +        0: Hallo
    +        1: World
    +    )
    +)'
    +            ),
    +            array(
    +                new PHPParser_Node_Expr_Array(array(
    +                    new PHPParser_Node_Expr_ArrayItem(new PHPParser_Node_Scalar_String('Foo'))
    +                )),
    +'Expr_Array(
    +    items: array(
    +        0: Expr_ArrayItem(
    +            key: null
    +            value: Scalar_String(
    +                value: Foo
    +            )
    +            byRef: false
    +        )
    +    )
    +)'
    +            ),
    +        );
    +    }
    +
    +    /**
    +     * @expectedException        InvalidArgumentException
    +     * @expectedExceptionMessage Can only dump nodes and arrays.
    +     */
    +    public function testError() {
    +        $dumper = new PHPParser_NodeDumper;
    +        $dumper->dump(new stdClass);
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/NodeTraverserTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/NodeTraverserTest.php
    new file mode 100644
    index 0000000..e072cb2
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/NodeTraverserTest.php
    @@ -0,0 +1,125 @@
    +getMock('PHPParser_NodeVisitor');
    +
    +        $visitor->expects($this->at(0))->method('beforeTraverse')->with($stmts);
    +        $visitor->expects($this->at(1))->method('enterNode')->with($echoNode);
    +        $visitor->expects($this->at(2))->method('enterNode')->with($str1Node);
    +        $visitor->expects($this->at(3))->method('leaveNode')->with($str1Node);
    +        $visitor->expects($this->at(4))->method('enterNode')->with($str2Node);
    +        $visitor->expects($this->at(5))->method('leaveNode')->with($str2Node);
    +        $visitor->expects($this->at(6))->method('leaveNode')->with($echoNode);
    +        $visitor->expects($this->at(7))->method('afterTraverse')->with($stmts);
    +
    +        $traverser = new PHPParser_NodeTraverser;
    +        $traverser->addVisitor($visitor);
    +
    +        $this->assertEquals($stmts, $traverser->traverse($stmts));
    +    }
    +
    +    public function testModifying() {
    +        $str1Node  = new PHPParser_Node_Scalar_String('Foo');
    +        $str2Node  = new PHPParser_Node_Scalar_String('Bar');
    +        $printNode = new PHPParser_Node_Expr_Print($str1Node);
    +
    +        // first visitor changes the node, second verifies the change
    +        $visitor1 = $this->getMock('PHPParser_NodeVisitor');
    +        $visitor2 = $this->getMock('PHPParser_NodeVisitor');
    +
    +        // replace empty statements with string1 node
    +        $visitor1->expects($this->at(0))->method('beforeTraverse')->with(array())
    +                 ->will($this->returnValue(array($str1Node)));
    +        $visitor2->expects($this->at(0))->method('beforeTraverse')->with(array($str1Node));
    +
    +        // replace string1 node with print node
    +        $visitor1->expects($this->at(1))->method('enterNode')->with($str1Node)
    +                 ->will($this->returnValue($printNode));
    +        $visitor2->expects($this->at(1))->method('enterNode')->with($printNode);
    +
    +        // replace string1 node with string2 node
    +        $visitor1->expects($this->at(2))->method('enterNode')->with($str1Node)
    +                 ->will($this->returnValue($str2Node));
    +        $visitor2->expects($this->at(2))->method('enterNode')->with($str2Node);
    +
    +        // replace string2 node with string1 node again
    +        $visitor1->expects($this->at(3))->method('leaveNode')->with($str2Node)
    +                 ->will($this->returnValue($str1Node));
    +        $visitor2->expects($this->at(3))->method('leaveNode')->with($str1Node);
    +
    +        // replace print node with string1 node again
    +        $visitor1->expects($this->at(4))->method('leaveNode')->with($printNode)
    +                 ->will($this->returnValue($str1Node));
    +        $visitor2->expects($this->at(4))->method('leaveNode')->with($str1Node);
    +
    +        // replace string1 node with empty statements again
    +        $visitor1->expects($this->at(5))->method('afterTraverse')->with(array($str1Node))
    +                 ->will($this->returnValue(array()));
    +        $visitor2->expects($this->at(5))->method('afterTraverse')->with(array());
    +
    +        $traverser = new PHPParser_NodeTraverser;
    +        $traverser->addVisitor($visitor1);
    +        $traverser->addVisitor($visitor2);
    +
    +        // as all operations are reversed we end where we start
    +        $this->assertEquals(array(), $traverser->traverse(array()));
    +    }
    +
    +    public function testRemove() {
    +        $str1Node = new PHPParser_Node_Scalar_String('Foo');
    +        $str2Node = new PHPParser_Node_Scalar_String('Bar');
    +
    +        $visitor = $this->getMock('PHPParser_NodeVisitor');
    +
    +        // remove the string1 node, leave the string2 node
    +        $visitor->expects($this->at(2))->method('leaveNode')->with($str1Node)
    +                ->will($this->returnValue(false));
    +
    +        $traverser = new PHPParser_NodeTraverser;
    +        $traverser->addVisitor($visitor);
    +
    +        $this->assertEquals(array($str2Node), $traverser->traverse(array($str1Node, $str2Node)));
    +    }
    +
    +    public function testMerge() {
    +        $strStart  = new PHPParser_Node_Scalar_String('Start');
    +        $strMiddle = new PHPParser_Node_Scalar_String('End');
    +        $strEnd    = new PHPParser_Node_Scalar_String('Middle');
    +        $strR1     = new PHPParser_Node_Scalar_String('Replacement 1');
    +        $strR2     = new PHPParser_Node_Scalar_String('Replacement 2');
    +
    +        $visitor = $this->getMock('PHPParser_NodeVisitor');
    +
    +        // replace strMiddle with strR1 and strR2 by merge
    +        $visitor->expects($this->at(4))->method('leaveNode')->with($strMiddle)
    +                ->will($this->returnValue(array($strR1, $strR2)));
    +
    +        $traverser = new PHPParser_NodeTraverser;
    +        $traverser->addVisitor($visitor);
    +
    +        $this->assertEquals(
    +            array($strStart, $strR1, $strR2, $strEnd),
    +            $traverser->traverse(array($strStart, $strMiddle, $strEnd))
    +        );
    +    }
    +
    +    public function testDeepArray() {
    +        $strNode = new PHPParser_Node_Scalar_String('Foo');
    +        $stmts = array(array(array($strNode)));
    +
    +        $visitor = $this->getMock('PHPParser_NodeVisitor');
    +        $visitor->expects($this->at(1))->method('enterNode')->with($strNode);
    +
    +        $traverser = new PHPParser_NodeTraverser;
    +        $traverser->addVisitor($visitor);
    +
    +        $this->assertEquals($stmts, $traverser->traverse($stmts));
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/NodeVisitor/NameResolverTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/NodeVisitor/NameResolverTest.php
    new file mode 100644
    index 0000000..49eaf69
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/NodeVisitor/NameResolverTest.php
    @@ -0,0 +1,225 @@
    +addVisitor(new PHPParser_NodeVisitor_NameResolver);
    +
    +        $stmts = $parser->parse($code);
    +        $stmts = $traverser->traverse($stmts);
    +
    +        $this->assertEquals($expectedCode, $prettyPrinter->prettyPrint($stmts));
    +    }
    +
    +    /**
    +     * @covers PHPParser_NodeVisitor_NameResolver
    +     */
    +    public function testResolveLocations() {
    +        $code = <<addVisitor(new PHPParser_NodeVisitor_NameResolver);
    +
    +        $stmts = $parser->parse($code);
    +        $stmts = $traverser->traverse($stmts);
    +
    +        $this->assertEquals($expectedCode, $prettyPrinter->prettyPrint($stmts));
    +    }
    +
    +    public function testNoResolveSpecialName() {
    +        $stmts = array(new PHPParser_Node_Expr_New(new PHPParser_Node_Name('self')));
    +
    +        $traverser = new PHPParser_NodeTraverser;
    +        $traverser->addVisitor(new PHPParser_NodeVisitor_NameResolver);
    +
    +        $this->assertEquals($stmts, $traverser->traverse($stmts));
    +    }
    +
    +    protected function createNamespacedAndNonNamespaced(array $stmts) {
    +        return array(
    +            new PHPParser_Node_Stmt_Namespace(new PHPParser_Node_Name('NS'), $stmts),
    +            new PHPParser_Node_Stmt_Namespace(null,                          $stmts),
    +        );
    +    }
    +
    +    public function testAddNamespacedName() {
    +        $stmts = $this->createNamespacedAndNonNamespaced(array(
    +            new PHPParser_Node_Stmt_Class('A'),
    +            new PHPParser_Node_Stmt_Interface('B'),
    +            new PHPParser_Node_Stmt_Function('C'),
    +            new PHPParser_Node_Stmt_Const(array(
    +                new PHPParser_Node_Const('D', new PHPParser_Node_Scalar_String('E'))
    +            )),
    +        ));
    +
    +        $traverser = new PHPParser_NodeTraverser;
    +        $traverser->addVisitor(new PHPParser_NodeVisitor_NameResolver);
    +
    +        $stmts = $traverser->traverse($stmts);
    +
    +        $this->assertEquals('NS\\A', (string) $stmts[0]->stmts[0]->namespacedName);
    +        $this->assertEquals('NS\\B', (string) $stmts[0]->stmts[1]->namespacedName);
    +        $this->assertEquals('NS\\C', (string) $stmts[0]->stmts[2]->namespacedName);
    +        $this->assertEquals('NS\\D', (string) $stmts[0]->stmts[3]->consts[0]->namespacedName);
    +        $this->assertEquals('A',     (string) $stmts[1]->stmts[0]->namespacedName);
    +        $this->assertEquals('B',     (string) $stmts[1]->stmts[1]->namespacedName);
    +        $this->assertEquals('C',     (string) $stmts[1]->stmts[2]->namespacedName);
    +        $this->assertEquals('D',     (string) $stmts[1]->stmts[3]->consts[0]->namespacedName);
    +    }
    +
    +    public function testAddTraitNamespacedName() {
    +        $stmts = $this->createNamespacedAndNonNamespaced(array(
    +            new PHPParser_Node_Stmt_Trait('A')
    +        ));
    +
    +        $traverser = new PHPParser_NodeTraverser;
    +        $traverser->addVisitor(new PHPParser_NodeVisitor_NameResolver);
    +
    +        $stmts = $traverser->traverse($stmts);
    +
    +        $this->assertEquals('NS\\A', (string) $stmts[0]->stmts[0]->namespacedName);
    +        $this->assertEquals('A',     (string) $stmts[1]->stmts[0]->namespacedName);
    +    }
    +
    +    /**
    +     * @expectedException        PHPParser_Error
    +     * @expectedExceptionMessage Cannot use "C" as "B" because the name is already in use on line 2
    +     */
    +    public function testAlreadyInUseError() {
    +        $stmts = array(
    +            new PHPParser_Node_Stmt_Use(array(
    +                new PHPParser_Node_Stmt_UseUse(new PHPParser_Node_Name('A\B'), 'B', array('startLine' => 1)),
    +                new PHPParser_Node_Stmt_UseUse(new PHPParser_Node_Name('C'),   'B', array('startLine' => 2)),
    +            ))
    +        );
    +
    +        $traverser = new PHPParser_NodeTraverser;
    +        $traverser->addVisitor(new PHPParser_NodeVisitor_NameResolver);
    +        $traverser->traverse($stmts);
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/ParserTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/ParserTest.php
    new file mode 100644
    index 0000000..cf45066
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/ParserTest.php
    @@ -0,0 +1,44 @@
    +parse($code);
    +        $this->assertEquals(
    +            $this->canonicalize($dump),
    +            $this->canonicalize($dumper->dump($stmts)),
    +            $name
    +        );
    +    }
    +
    +    public function provideTestParse() {
    +        return $this->getTests(dirname(__FILE__) . '/../../code/parser', 'test');
    +    }
    +
    +    /**
    +     * @dataProvider provideTestParseFail
    +     */
    +    public function testParseFail($name, $code, $msg) {
    +        $parser = new PHPParser_Parser(new PHPParser_Lexer_Emulative);
    +
    +        try {
    +            $parser->parse($code);
    +
    +            $this->fail(sprintf('"%s": Expected PHPParser_Error', $name));
    +        } catch (PHPParser_Error $e) {
    +            $this->assertEquals($msg, $e->getMessage(), $name);
    +        }
    +    }
    +
    +    public function provideTestParseFail() {
    +        return $this->getTests(dirname(__FILE__) . '/../../code/parser', 'test-fail');
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/PrettyPrinterTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/PrettyPrinterTest.php
    new file mode 100644
    index 0000000..11933e6
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/PrettyPrinterTest.php
    @@ -0,0 +1,42 @@
    +parse($code);
    +        $this->assertEquals(
    +            $this->canonicalize($dump),
    +            $this->canonicalize($prettyPrinter->$method($stmts)),
    +            $name
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider provideTestPrettyPrint
    +     * @covers PHPParser_PrettyPrinter_Default
    +     */
    +    public function testPrettyPrint($name, $code, $dump) {
    +        $this->doTestPrettyPrintMethod('prettyPrint', $name, $code, $dump);
    +    }
    +
    +    /**
    +     * @dataProvider provideTestPrettyPrintFile
    +     * @covers PHPParser_PrettyPrinter_Default
    +     */
    +    public function testPrettyPrintFile($name, $code, $dump) {
    +        $this->doTestPrettyPrintMethod('prettyPrintFile', $name, $code, $dump);
    +    }
    +
    +    public function provideTestPrettyPrint() {
    +        return $this->getTests(dirname(__FILE__) . '/../../code/prettyPrinter', 'test');
    +    }
    +
    +    public function provideTestPrettyPrintFile() {
    +        return $this->getTests(dirname(__FILE__) . '/../../code/prettyPrinter', 'file-test');
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/Serializer/XMLTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/Serializer/XMLTest.php
    new file mode 100644
    index 0000000..9a0c191
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/Serializer/XMLTest.php
    @@ -0,0 +1,152 @@
    +
    +     */
    +    public function testSerialize() {
    +        $code = <<
    +
    + 
    +  
    +   
    +    
    +     // comment
    +
    +     /** doc comment */
    +    
    +   
    +   
    +    4
    +   
    +   
    +    6
    +   
    +   
    +    
    +   
    +   
    +    
    +     
    +      
    +       4
    +      
    +      
    +       4
    +      
    +      
    +       a
    +      
    +      
    +       
    +        
    +         4
    +        
    +        
    +         4
    +        
    +        
    +         0
    +        
    +       
    +      
    +      
    +       
    +      
    +      
    +       
    +      
    +     
    +     
    +      
    +       4
    +      
    +      
    +       4
    +      
    +      
    +       b
    +      
    +      
    +       
    +        
    +         4
    +        
    +        
    +         4
    +        
    +        
    +         1
    +        
    +       
    +      
    +      
    +       
    +      
    +      
    +       
    +      
    +     
    +    
    +   
    +   
    +    
    +     
    +      
    +       5
    +      
    +      
    +       5
    +      
    +      
    +       
    +        
    +         
    +          5
    +         
    +         
    +          5
    +         
    +         
    +          Foo
    +         
    +        
    +       
    +      
    +     
    +    
    +   
    +   
    +    functionName
    +   
    +  
    + 
    +
    +XML;
    +
    +        $parser     = new PHPParser_Parser(new PHPParser_Lexer);
    +        $serializer = new PHPParser_Serializer_XML;
    +
    +        $stmts = $parser->parse($code);
    +        $this->assertXmlStringEqualsXmlString($xml, $serializer->serialize($stmts));
    +    }
    +
    +    /**
    +     * @expectedException        InvalidArgumentException
    +     * @expectedExceptionMessage Unexpected node type
    +     */
    +    public function testError() {
    +        $serializer = new PHPParser_Serializer_XML;
    +        $serializer->serialize(array(new stdClass));
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/TemplateLoaderTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/TemplateLoaderTest.php
    new file mode 100644
    index 0000000..baa5735
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/TemplateLoaderTest.php
    @@ -0,0 +1,48 @@
    +load('TemplateLoaderTest.php');
    +        $this->assertInstanceOf('PHPParser_Template', $template);
    +    }
    +
    +    public function testLoadWithSuffix() {
    +        $templateLoader = new PHPParser_TemplateLoader(
    +            new PHPParser_Parser(new PHPParser_Lexer),
    +            dirname(__FILE__), '.php'
    +        );
    +
    +        // load this file as a template, as we don't really care about the contents
    +        $template = $templateLoader->load('TemplateLoaderTest');
    +        $this->assertInstanceOf('PHPParser_Template', $template);
    +    }
    +
    +    /**
    +     * @expectedException InvalidArgumentException
    +     */
    +    public function testNonexistentBaseDirectoryError() {
    +        new PHPParser_TemplateLoader(
    +            new PHPParser_Parser(new PHPParser_Lexer),
    +            dirname(__FILE__) . '/someDirectoryThatDoesNotExist'
    +        );
    +    }
    +
    +    /**
    +     * @expectedException InvalidArgumentException
    +     */
    +    public function testNonexistentFileError() {
    +        $templateLoader = new PHPParser_TemplateLoader(
    +            new PHPParser_Parser(new PHPParser_Lexer),
    +            dirname(__FILE__)
    +        );
    +
    +        $templateLoader->load('SomeTemplateThatDoesNotExist');
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/PHPParser/Tests/TemplateTest.php b/vendor/nikic/php-parser/test/PHPParser/Tests/TemplateTest.php
    new file mode 100644
    index 0000000..a926c5c
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/PHPParser/Tests/TemplateTest.php
    @@ -0,0 +1,59 @@
    +assertEquals(
    +            $expectedPrettyPrint,
    +            $prettyPrinter->prettyPrint($template->getStmts($placeholders))
    +        );
    +    }
    +
    +    public function provideTestPlaceholderReplacement() {
    +        return array(
    +            array(
    +                ' 'foo'),
    +                '$foo + $Foo;'
    +            ),
    +            array(
    +                ' 'Foo'),
    +                '$foo + $Foo;'
    +            ),
    +            array(
    +                ' 'foo', 'Name' => 'Bar'),
    +                '$foo + $Bar;'
    +            ),
    +            array(
    +                ' 'Bar', 'name' => 'foo'),
    +                '$foo + $Bar;'
    +            ),
    +            array(
    +                ' 'infix'),
    +                '$prefixInfixSuffix;'
    +            ),
    +            array(
    +                ' 'foo'),
    +                '$_foo_;'
    +            ),
    +            array(
    +                '
    +
    + 
    +  
    +   1
    +  
    +  
    +   
    +    // comment
    +
    +    /** doc comment */
    +   
    +  
    +  
    +   Test
    +  
    + 
    +
    +XML;
    +
    +        $unserializer  = new PHPParser_Unserializer_XML;
    +        $this->assertEquals(
    +            new PHPParser_Node_Scalar_String('Test', array(
    +                'startLine' => 1,
    +                'comments'  => array(
    +                    new PHPParser_Comment('// comment' . "\n", 2),
    +                    new PHPParser_Comment_Doc('/** doc comment */', 3),
    +                ),
    +            )),
    +            $unserializer->unserialize($xml)
    +        );
    +    }
    +
    +    public function testEmptyNode() {
    +        $xml = <<
    +
    + 
    +
    +XML;
    +
    +        $unserializer  = new PHPParser_Unserializer_XML;
    +
    +        $this->assertEquals(
    +            new PHPParser_Node_Scalar_ClassConst,
    +            $unserializer->unserialize($xml)
    +        );
    +    }
    +
    +    public function testScalars() {
    +        $xml = <<
    +
    + 
    +  
    +  
    +  test
    +  
    +  
    +  1
    +  1
    +  1.5
    +  
    +  
    +  
    + 
    +
    +XML;
    +        $result = array(
    +            array(), array(),
    +            'test', '', '',
    +            1,
    +            1, 1.5,
    +            true, false, null
    +        );
    +
    +        $unserializer  = new PHPParser_Unserializer_XML;
    +        $this->assertEquals($result, $unserializer->unserialize($xml));
    +    }
    +
    +    /**
    +     * @expectedException        DomainException
    +     * @expectedExceptionMessage AST root element not found
    +     */
    +    public function testWrongRootElementError() {
    +        $xml = <<
    +
    +XML;
    +
    +        $unserializer = new PHPParser_Unserializer_XML;
    +        $unserializer->unserialize($xml);
    +    }
    +
    +    /**
    +     * @dataProvider             provideTestErrors
    +     */
    +    public function testErrors($xml, $errorMsg) {
    +        $this->setExpectedException('DomainException', $errorMsg);
    +
    +        $xml = <<
    +
    + $xml
    +
    +XML;
    +
    +        $unserializer = new PHPParser_Unserializer_XML;
    +        $unserializer->unserialize($xml);
    +    }
    +
    +    public function provideTestErrors() {
    +        return array(
    +            array('test',   '"true" scalar must be empty'),
    +            array('test', '"false" scalar must be empty'),
    +            array('test',   '"null" scalar must be empty'),
    +            array('bar',      'Unknown scalar type "foo"'),
    +            array('x',        '"x" is not a valid int'),
    +            array('x',    '"x" is not a valid float'),
    +            array('',                                  'Expected node or scalar'),
    +            array('test',           'Unexpected node of type "foo:bar"'),
    +            array(
    +                'test',
    +                'Expected sub node or attribute, got node of type "foo:bar"'
    +            ),
    +            array(
    +                '',
    +                'Expected node or scalar'
    +            ),
    +        );
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/code/parser/expr/arrayDef.test b/vendor/nikic/php-parser/test/code/parser/expr/arrayDef.test
    new file mode 100644
    index 0000000..7ea11c7
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/code/parser/expr/arrayDef.test
    @@ -0,0 +1,139 @@
    +Array definitions
    +-----
    + 'd', 'e' => &$f);
    +
    +// short array syntax
    +[];
    +[1, 2, 3];
    +['a' => 'b'];
    +-----
    +array(
    +    0: Expr_Array(
    +        items: array(
    +        )
    +    )
    +    1: Expr_Array(
    +        items: array(
    +            0: Expr_ArrayItem(
    +                key: null
    +                value: Scalar_String(
    +                    value: a
    +                )
    +                byRef: false
    +            )
    +        )
    +    )
    +    2: Expr_Array(
    +        items: array(
    +            0: Expr_ArrayItem(
    +                key: null
    +                value: Scalar_String(
    +                    value: a
    +                )
    +                byRef: false
    +            )
    +        )
    +    )
    +    3: Expr_Array(
    +        items: array(
    +            0: Expr_ArrayItem(
    +                key: null
    +                value: Scalar_String(
    +                    value: a
    +                )
    +                byRef: false
    +            )
    +            1: Expr_ArrayItem(
    +                key: null
    +                value: Scalar_String(
    +                    value: b
    +                )
    +                byRef: false
    +            )
    +        )
    +    )
    +    4: Expr_Array(
    +        items: array(
    +            0: Expr_ArrayItem(
    +                key: null
    +                value: Scalar_String(
    +                    value: a
    +                )
    +                byRef: false
    +            )
    +            1: Expr_ArrayItem(
    +                key: null
    +                value: Expr_Variable(
    +                    name: b
    +                )
    +                byRef: true
    +            )
    +            2: Expr_ArrayItem(
    +                key: Scalar_String(
    +                    value: c
    +                )
    +                value: Scalar_String(
    +                    value: d
    +                )
    +                byRef: false
    +            )
    +            3: Expr_ArrayItem(
    +                key: Scalar_String(
    +                    value: e
    +                )
    +                value: Expr_Variable(
    +                    name: f
    +                )
    +                byRef: true
    +            )
    +        )
    +    )
    +    5: Expr_Array(
    +        items: array(
    +        )
    +    )
    +    6: Expr_Array(
    +        items: array(
    +            0: Expr_ArrayItem(
    +                key: null
    +                value: Scalar_LNumber(
    +                    value: 1
    +                )
    +                byRef: false
    +            )
    +            1: Expr_ArrayItem(
    +                key: null
    +                value: Scalar_LNumber(
    +                    value: 2
    +                )
    +                byRef: false
    +            )
    +            2: Expr_ArrayItem(
    +                key: null
    +                value: Scalar_LNumber(
    +                    value: 3
    +                )
    +                byRef: false
    +            )
    +        )
    +    )
    +    7: Expr_Array(
    +        items: array(
    +            0: Expr_ArrayItem(
    +                key: Scalar_String(
    +                    value: a
    +                )
    +                value: Scalar_String(
    +                    value: b
    +                )
    +                byRef: false
    +            )
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/code/parser/expr/assign.test b/vendor/nikic/php-parser/test/code/parser/expr/assign.test
    new file mode 100644
    index 0000000..e6916ad
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/code/parser/expr/assign.test
    @@ -0,0 +1,225 @@
    +Assignments
    +-----
    +>= $b;
    +
    +// by ref assign
    +$a =& $b;
    +$a =& new B;
    +
    +// list() assign
    +list($a) = $b;
    +list($a, , $b) = $c;
    +list($a, list(, $c), $d) = $e;
    +
    +// inc/dec
    +++$a;
    +$a++;
    +--$a;
    +$a--;
    +-----
    +array(
    +    0: Expr_Assign(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    1: Expr_AssignBitwiseAnd(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    2: Expr_AssignBitwiseOr(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    3: Expr_AssignBitwiseXor(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    4: Expr_AssignConcat(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    5: Expr_AssignDiv(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    6: Expr_AssignMinus(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    7: Expr_AssignMod(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    8: Expr_AssignMul(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    9: Expr_AssignPlus(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    10: Expr_AssignShiftLeft(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    11: Expr_AssignShiftRight(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    12: Expr_AssignRef(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    13: Expr_AssignRef(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        expr: Expr_New(
    +            class: Name(
    +                parts: array(
    +                    0: B
    +                )
    +            )
    +            args: array(
    +            )
    +        )
    +    )
    +    14: Expr_Assign(
    +        var: Expr_List(
    +            vars: array(
    +                0: Expr_Variable(
    +                    name: a
    +                )
    +            )
    +        )
    +        expr: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    15: Expr_Assign(
    +        var: Expr_List(
    +            vars: array(
    +                0: Expr_Variable(
    +                    name: a
    +                )
    +                1: null
    +                2: Expr_Variable(
    +                    name: b
    +                )
    +            )
    +        )
    +        expr: Expr_Variable(
    +            name: c
    +        )
    +    )
    +    16: Expr_Assign(
    +        var: Expr_List(
    +            vars: array(
    +                0: Expr_Variable(
    +                    name: a
    +                )
    +                1: Expr_List(
    +                    vars: array(
    +                        0: null
    +                        1: Expr_Variable(
    +                            name: c
    +                        )
    +                    )
    +                )
    +                2: Expr_Variable(
    +                    name: d
    +                )
    +            )
    +        )
    +        expr: Expr_Variable(
    +            name: e
    +        )
    +    )
    +    17: Expr_PreInc(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +    )
    +    18: Expr_PostInc(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +    )
    +    19: Expr_PreDec(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +    )
    +    20: Expr_PostDec(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/code/parser/expr/cast.test b/vendor/nikic/php-parser/test/code/parser/expr/cast.test
    new file mode 100644
    index 0000000..3c54ba7
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/code/parser/expr/cast.test
    @@ -0,0 +1,72 @@
    +Casts
    +-----
    + $b;
    +$a >= $b;
    +$a == $b;
    +$a === $b;
    +$a != $b;
    +$a !== $b;
    +$a instanceof B;
    +$a instanceof $b;
    +-----
    +array(
    +    0: Expr_Smaller(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    1: Expr_SmallerOrEqual(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    2: Expr_Greater(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    3: Expr_GreaterOrEqual(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    4: Expr_Equal(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    5: Expr_Identical(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    6: Expr_NotEqual(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    7: Expr_NotIdentical(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    8: Expr_Instanceof(
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        class: Name(
    +            parts: array(
    +                0: B
    +            )
    +        )
    +    )
    +    9: Expr_Instanceof(
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        class: Expr_Variable(
    +            name: b
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/code/parser/expr/errorSuppress.test b/vendor/nikic/php-parser/test/code/parser/expr/errorSuppress.test
    new file mode 100644
    index 0000000..ce3fce9
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/code/parser/expr/errorSuppress.test
    @@ -0,0 +1,12 @@
    +Error suppression
    +-----
    +b['c']();
    +
    +// array dereferencing
    +a()['b'];
    +-----
    +array(
    +    0: Expr_FuncCall(
    +        name: Name(
    +            parts: array(
    +                0: a
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    1: Expr_FuncCall(
    +        name: Expr_Variable(
    +            name: a
    +        )
    +        args: array(
    +        )
    +    )
    +    2: Expr_FuncCall(
    +        name: Expr_Variable(
    +            name: Scalar_String(
    +                value: a
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    3: Expr_FuncCall(
    +        name: Expr_Variable(
    +            name: Expr_Variable(
    +                name: a
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    4: Expr_FuncCall(
    +        name: Expr_Variable(
    +            name: Expr_Variable(
    +                name: Expr_Variable(
    +                    name: a
    +                )
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    5: Expr_FuncCall(
    +        name: Expr_ArrayDimFetch(
    +            var: Expr_Variable(
    +                name: a
    +            )
    +            dim: Scalar_String(
    +                value: b
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    6: Expr_FuncCall(
    +        name: Expr_ArrayDimFetch(
    +            var: Expr_Variable(
    +                name: a
    +            )
    +            dim: Scalar_String(
    +                value: b
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    7: Expr_FuncCall(
    +        name: Expr_ArrayDimFetch(
    +            var: Expr_PropertyFetch(
    +                var: Expr_Variable(
    +                    name: a
    +                )
    +                name: b
    +            )
    +            dim: Scalar_String(
    +                value: c
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    8: Expr_ArrayDimFetch(
    +        var: Expr_FuncCall(
    +            name: Name(
    +                parts: array(
    +                    0: a
    +                )
    +            )
    +            args: array(
    +            )
    +        )
    +        dim: Scalar_String(
    +            value: b
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/newDeref.test b/vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/newDeref.test
    new file mode 100644
    index 0000000..5e36ff8
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/newDeref.test
    @@ -0,0 +1,70 @@
    +New expression dereferencing
    +-----
    +b;
    +(new A)->b();
    +(new A)['b'];
    +(new A)['b']['c'];
    +-----
    +array(
    +    0: Expr_PropertyFetch(
    +        var: Expr_New(
    +            class: Name(
    +                parts: array(
    +                    0: A
    +                )
    +            )
    +            args: array(
    +            )
    +        )
    +        name: b
    +    )
    +    1: Expr_MethodCall(
    +        var: Expr_New(
    +            class: Name(
    +                parts: array(
    +                    0: A
    +                )
    +            )
    +            args: array(
    +            )
    +        )
    +        name: b
    +        args: array(
    +        )
    +    )
    +    2: Expr_ArrayDimFetch(
    +        var: Expr_New(
    +            class: Name(
    +                parts: array(
    +                    0: A
    +                )
    +            )
    +            args: array(
    +            )
    +        )
    +        dim: Scalar_String(
    +            value: b
    +        )
    +    )
    +    3: Expr_ArrayDimFetch(
    +        var: Expr_ArrayDimFetch(
    +            var: Expr_New(
    +                class: Name(
    +                    parts: array(
    +                        0: A
    +                    )
    +                )
    +                args: array(
    +                )
    +            )
    +            dim: Scalar_String(
    +                value: b
    +            )
    +        )
    +        dim: Scalar_String(
    +            value: c
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/objectAccess.test b/vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/objectAccess.test
    new file mode 100644
    index 0000000..9dd1858
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/objectAccess.test
    @@ -0,0 +1,118 @@
    +Object access
    +-----
    +b;
    +$a->b['c'];
    +$a->b{'c'};
    +
    +// method call variations
    +$a->b();
    +$a->{'b'}();
    +$a->$b();
    +$a->$b['c']();
    +
    +// array dereferencing
    +$a->b()['c'];
    +$a->b(){'c'}; // invalid PHP: drop Support?
    +-----
    +array(
    +    0: Expr_PropertyFetch(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        name: b
    +    )
    +    1: Expr_ArrayDimFetch(
    +        var: Expr_PropertyFetch(
    +            var: Expr_Variable(
    +                name: a
    +            )
    +            name: b
    +        )
    +        dim: Scalar_String(
    +            value: c
    +        )
    +    )
    +    2: Expr_ArrayDimFetch(
    +        var: Expr_PropertyFetch(
    +            var: Expr_Variable(
    +                name: a
    +            )
    +            name: b
    +        )
    +        dim: Scalar_String(
    +            value: c
    +        )
    +    )
    +    3: Expr_MethodCall(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        name: b
    +        args: array(
    +        )
    +    )
    +    4: Expr_MethodCall(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        name: Scalar_String(
    +            value: b
    +        )
    +        args: array(
    +        )
    +    )
    +    5: Expr_MethodCall(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        name: Expr_Variable(
    +            name: b
    +        )
    +        args: array(
    +        )
    +    )
    +    6: Expr_MethodCall(
    +        var: Expr_Variable(
    +            name: a
    +        )
    +        name: Expr_ArrayDimFetch(
    +            var: Expr_Variable(
    +                name: b
    +            )
    +            dim: Scalar_String(
    +                value: c
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    7: Expr_ArrayDimFetch(
    +        var: Expr_MethodCall(
    +            var: Expr_Variable(
    +                name: a
    +            )
    +            name: b
    +            args: array(
    +            )
    +        )
    +        dim: Scalar_String(
    +            value: c
    +        )
    +    )
    +    8: Expr_ArrayDimFetch(
    +        var: Expr_MethodCall(
    +            var: Expr_Variable(
    +                name: a
    +            )
    +            name: b
    +            args: array(
    +            )
    +        )
    +        dim: Scalar_String(
    +            value: c
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/simpleArrayAccess.test b/vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/simpleArrayAccess.test
    new file mode 100644
    index 0000000..ea3f9ef
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/code/parser/expr/fetchAndCall/simpleArrayAccess.test
    @@ -0,0 +1,62 @@
    +Simple array access
    +-----
    +> $b;
    +
    +// associativity
    +$a * $b * $c;
    +$a * ($b * $c);
    +
    +// precedence
    +$a + $b * $c;
    +($a + $b) * $c;
    +-----
    +array(
    +    0: Expr_BitwiseNot(
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +    )
    +    1: Expr_UnaryPlus(
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +    )
    +    2: Expr_UnaryMinus(
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +    )
    +    3: Expr_BitwiseAnd(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    4: Expr_BitwiseOr(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    5: Expr_BitwiseXor(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    6: Expr_Concat(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    7: Expr_Div(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    8: Expr_Minus(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    9: Expr_Mod(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    10: Expr_Mul(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    11: Expr_Plus(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    12: Expr_ShiftLeft(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    13: Expr_ShiftRight(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    14: Expr_Mul(
    +        left: Expr_Mul(
    +            left: Expr_Variable(
    +                name: a
    +            )
    +            right: Expr_Variable(
    +                name: b
    +            )
    +        )
    +        right: Expr_Variable(
    +            name: c
    +        )
    +    )
    +    15: Expr_Mul(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Mul(
    +            left: Expr_Variable(
    +                name: b
    +            )
    +            right: Expr_Variable(
    +                name: c
    +            )
    +        )
    +    )
    +    16: Expr_Plus(
    +        left: Expr_Variable(
    +            name: a
    +        )
    +        right: Expr_Mul(
    +            left: Expr_Variable(
    +                name: b
    +            )
    +            right: Expr_Variable(
    +                name: c
    +            )
    +        )
    +    )
    +    17: Expr_Mul(
    +        left: Expr_Plus(
    +            left: Expr_Variable(
    +                name: a
    +            )
    +            right: Expr_Variable(
    +                name: b
    +            )
    +        )
    +        right: Expr_Variable(
    +            name: c
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/code/parser/expr/new.test b/vendor/nikic/php-parser/test/code/parser/expr/new.test
    new file mode 100644
    index 0000000..daca29c
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/code/parser/expr/new.test
    @@ -0,0 +1,139 @@
    +New
    +-----
    +b();
    +new $a->b->c();
    +new $a->b['c']();
    +new $a->b{'c'}();
    +
    +// test regression introduces by new dereferencing syntax
    +(new A);
    +-----
    +array(
    +    0: Expr_New(
    +        class: Name(
    +            parts: array(
    +                0: A
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    1: Expr_New(
    +        class: Name(
    +            parts: array(
    +                0: A
    +            )
    +        )
    +        args: array(
    +            0: Arg(
    +                value: Expr_Variable(
    +                    name: b
    +                )
    +                byRef: false
    +            )
    +        )
    +    )
    +    2: Expr_New(
    +        class: Expr_Variable(
    +            name: a
    +        )
    +        args: array(
    +        )
    +    )
    +    3: Expr_New(
    +        class: Expr_ArrayDimFetch(
    +            var: Expr_Variable(
    +                name: a
    +            )
    +            dim: Scalar_String(
    +                value: b
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    4: Expr_New(
    +        class: Expr_StaticPropertyFetch(
    +            class: Name(
    +                parts: array(
    +                    0: A
    +                )
    +            )
    +            name: b
    +        )
    +        args: array(
    +        )
    +    )
    +    5: Expr_New(
    +        class: Expr_PropertyFetch(
    +            var: Expr_Variable(
    +                name: a
    +            )
    +            name: b
    +        )
    +        args: array(
    +        )
    +    )
    +    6: Expr_New(
    +        class: Expr_PropertyFetch(
    +            var: Expr_PropertyFetch(
    +                var: Expr_Variable(
    +                    name: a
    +                )
    +                name: b
    +            )
    +            name: c
    +        )
    +        args: array(
    +        )
    +    )
    +    7: Expr_New(
    +        class: Expr_ArrayDimFetch(
    +            var: Expr_PropertyFetch(
    +                var: Expr_Variable(
    +                    name: a
    +                )
    +                name: b
    +            )
    +            dim: Scalar_String(
    +                value: c
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    8: Expr_New(
    +        class: Expr_ArrayDimFetch(
    +            var: Expr_PropertyFetch(
    +                var: Expr_Variable(
    +                    name: a
    +                )
    +                name: b
    +            )
    +            dim: Scalar_String(
    +                value: c
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +    9: Expr_New(
    +        class: Name(
    +            parts: array(
    +                0: A
    +            )
    +        )
    +        args: array(
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/code/parser/expr/print.test b/vendor/nikic/php-parser/test/code/parser/expr/print.test
    new file mode 100644
    index 0000000..d07afda
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/code/parser/expr/print.test
    @@ -0,0 +1,12 @@
    +Print
    +-----
    +c test
    +EOS;
    +
    +// comment to force line break before EOF
    +-----
    +array(
    +    0: Scalar_String(
    +        value:
    +    )
    +    1: Scalar_String(
    +        value:
    +    )
    +    2: Scalar_String(
    +        value: Test '" $a \n
    +    )
    +    3: Scalar_String(
    +        value: Test '" $a
    +
    +    )
    +    4: Scalar_Encapsed(
    +        parts: array(
    +            0: Test
    +            1: Expr_Variable(
    +                name: a
    +            )
    +        )
    +    )
    +    5: Scalar_Encapsed(
    +        parts: array(
    +            0: Test
    +            1: Expr_Variable(
    +                name: a
    +            )
    +            2:  and
    +            3: Expr_PropertyFetch(
    +                var: Expr_Variable(
    +                    name: b
    +                )
    +                name: c
    +            )
    +            4:  test
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/code/parser/scalar/encapsedString.test b/vendor/nikic/php-parser/test/code/parser/scalar/encapsedString.test
    new file mode 100644
    index 0000000..717f844
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/code/parser/scalar/encapsedString.test
    @@ -0,0 +1,148 @@
    +Encapsed strings
    +-----
    +B";
    +"$A[B]";
    +"$A[0]";
    +"$A[0x0]";
    +"$A[$B]";
    +"{$A}";
    +"{$A['B']}";
    +"${A}";
    +"${A['B']}";
    +"${$A}";
    +"A $B C";
    +b"$A";
    +-----
    +array(
    +    0: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_Variable(
    +                name: A
    +            )
    +        )
    +    )
    +    1: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_PropertyFetch(
    +                var: Expr_Variable(
    +                    name: A
    +                )
    +                name: B
    +            )
    +        )
    +    )
    +    2: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_ArrayDimFetch(
    +                var: Expr_Variable(
    +                    name: A
    +                )
    +                dim: Scalar_String(
    +                    value: B
    +                )
    +            )
    +        )
    +    )
    +    3: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_ArrayDimFetch(
    +                var: Expr_Variable(
    +                    name: A
    +                )
    +                dim: Scalar_String(
    +                    value: 0
    +                )
    +            )
    +        )
    +    )
    +    4: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_ArrayDimFetch(
    +                var: Expr_Variable(
    +                    name: A
    +                )
    +                dim: Scalar_String(
    +                    value: 0x0
    +                )
    +            )
    +        )
    +    )
    +    5: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_ArrayDimFetch(
    +                var: Expr_Variable(
    +                    name: A
    +                )
    +                dim: Expr_Variable(
    +                    name: B
    +                )
    +            )
    +        )
    +    )
    +    6: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_Variable(
    +                name: A
    +            )
    +        )
    +    )
    +    7: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_ArrayDimFetch(
    +                var: Expr_Variable(
    +                    name: A
    +                )
    +                dim: Scalar_String(
    +                    value: B
    +                )
    +            )
    +        )
    +    )
    +    8: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_Variable(
    +                name: A
    +            )
    +        )
    +    )
    +    9: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_ArrayDimFetch(
    +                var: Expr_Variable(
    +                    name: A
    +                )
    +                dim: Scalar_String(
    +                    value: B
    +                )
    +            )
    +        )
    +    )
    +    10: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_Variable(
    +                name: Expr_Variable(
    +                    name: A
    +                )
    +            )
    +        )
    +    )
    +    11: Scalar_Encapsed(
    +        parts: array(
    +            0: A
    +            1: Expr_Variable(
    +                name: B
    +            )
    +            2:  C
    +        )
    +    )
    +    12: Scalar_Encapsed(
    +        parts: array(
    +            0: Expr_Variable(
    +                name: A
    +            )
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/code/parser/scalar/float.test b/vendor/nikic/php-parser/test/code/parser/scalar/float.test
    new file mode 100644
    index 0000000..c91b7ac
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/code/parser/scalar/float.test
    @@ -0,0 +1,70 @@
    +Different float syntaxes
    +-----
    + float overflows
    +// (all are actually the same number, just in different representations)
    +18446744073709551615;
    +0xFFFFFFFFFFFFFFFF;
    +01777777777777777777777;
    +0177777777777777777777787;
    +0b1111111111111111111111111111111111111111111111111111111111111111;
    +-----
    +array(
    +    0: Scalar_DNumber(
    +        value: 0
    +    )
    +    1: Scalar_DNumber(
    +        value: 0
    +    )
    +    2: Scalar_DNumber(
    +        value: 0
    +    )
    +    3: Scalar_DNumber(
    +        value: 0
    +    )
    +    4: Scalar_DNumber(
    +        value: 0
    +    )
    +    5: Scalar_DNumber(
    +        value: 0
    +    )
    +    6: Scalar_DNumber(
    +        value: 0
    +    )
    +    7: Scalar_DNumber(
    +        value: 302000000000
    +    )
    +    8: Scalar_DNumber(
    +        value: 3.002E+102
    +    )
    +    9: Scalar_DNumber(
    +        value: INF
    +    )
    +    10: Scalar_DNumber(
    +        value: @@{ 0xFFFFFFFFFFFFFFFF }@@
    +    )
    +    11: Scalar_DNumber(
    +        value: @@{ 0xFFFFFFFFFFFFFFFF }@@
    +    )
    +    12: Scalar_DNumber(
    +        value: @@{ 0xFFFFFFFFFFFFFFFF }@@
    +    )
    +    13: Scalar_DNumber(
    +        value: @@{ 0xFFFFFFFFFFFFFFFF }@@
    +    )
    +    14: Scalar_DNumber(
    +        value: @@{ 0xFFFFFFFFFFFFFFFF }@@
    +    )
    +)
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/code/parser/scalar/int.test b/vendor/nikic/php-parser/test/code/parser/scalar/int.test
    new file mode 100644
    index 0000000..17a5785
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/code/parser/scalar/int.test
    @@ -0,0 +1,47 @@
    +Different integer syntaxes
    +-----
    + 'baz']
    +) {}
    +-----
    +array(
    +    0: Stmt_Function(
    +        byRef: false
    +        params: array(
    +            0: Param(
    +                name: b
    +                default: Expr_ConstFetch(
    +                    name: Name(
    +                        parts: array(
    +                            0: null
    +                        )
    +                    )
    +                )
    +                type: null
    +                byRef: false
    +            )
    +            1: Param(
    +                name: c
    +                default: Scalar_String(
    +                    value: foo
    +                )
    +                type: null
    +                byRef: false
    +            )
    +            2: Param(
    +                name: d
    +                default: Expr_ClassConstFetch(
    +                    class: Name(
    +                        parts: array(
    +                            0: A
    +                        )
    +                    )
    +                    name: B
    +                )
    +                type: null
    +                byRef: false
    +            )
    +            3: Param(
    +                name: f
    +                default: Expr_UnaryPlus(
    +                    expr: Scalar_LNumber(
    +                        value: 1
    +                    )
    +                )
    +                type: null
    +                byRef: false
    +            )
    +            4: Param(
    +                name: g
    +                default: Expr_UnaryMinus(
    +                    expr: Scalar_DNumber(
    +                        value: 1
    +                    )
    +                )
    +                type: null
    +                byRef: false
    +            )
    +            5: Param(
    +                name: h
    +                default: Expr_Array(
    +                    items: array(
    +                    )
    +                )
    +                type: null
    +                byRef: false
    +            )
    +            6: Param(
    +                name: i
    +                default: Expr_Array(
    +                    items: array(
    +                    )
    +                )
    +                type: null
    +                byRef: false
    +            )
    +            7: Param(
    +                name: j
    +                default: Expr_Array(
    +                    items: array(
    +                        0: Expr_ArrayItem(
    +                            key: null
    +                            value: Scalar_String(
    +                                value: foo
    +                            )
    +                            byRef: false
    +                        )
    +                    )
    +                )
    +                type: null
    +                byRef: false
    +            )
    +            8: Param(
    +                name: k
    +                default: Expr_Array(
    +                    items: array(
    +                        0: Expr_ArrayItem(
    +                            key: null
    +                            value: Scalar_String(
    +                                value: foo
    +                            )
    +                            byRef: false
    +                        )
    +                        1: Expr_ArrayItem(
    +                            key: Scalar_String(
    +                                value: bar
    +                            )
    +                            value: Scalar_String(
    +                                value: baz
    +                            )
    +                            byRef: false
    +                        )
    +                    )
    +                )
    +                type: null
    +                byRef: false
    +            )
    +        )
    +        stmts: array(
    +        )
    +        name: a
    +    )
    +)
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/code/parser/stmt/function/generator.test b/vendor/nikic/php-parser/test/code/parser/stmt/function/generator.test
    new file mode 100644
    index 0000000..e5a5716
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/code/parser/stmt/function/generator.test
    @@ -0,0 +1,227 @@
    +Generators (yield expression
    +-----
    + $value;
    +
    +    // expressions
    +    $data = yield;
    +    $data = (yield $value);
    +    $data = (yield $key => $value);
    +
    +    // yield in language constructs with their own parentheses
    +    if (yield $foo); elseif (yield $foo);
    +    if (yield $foo): elseif (yield $foo): endif;
    +    while (yield $foo);
    +    do {} while (yield $foo);
    +    switch (yield $foo) {}
    +    die(yield $foo);
    +
    +    // yield in function calls
    +    func(yield $foo);
    +    $foo->func(yield $foo);
    +    new Foo(yield $foo);
    +}
    +-----
    +array(
    +    0: Stmt_Function(
    +        byRef: false
    +        params: array(
    +        )
    +        stmts: array(
    +            0: Expr_Yield(
    +                key: null
    +                value: null
    +            )
    +            1: Expr_Yield(
    +                key: null
    +                value: Expr_Variable(
    +                    name: value
    +                )
    +            )
    +            2: Expr_Yield(
    +                key: Expr_Variable(
    +                    name: key
    +                )
    +                value: Expr_Variable(
    +                    name: value
    +                )
    +            )
    +            3: Expr_Assign(
    +                var: Expr_Variable(
    +                    name: data
    +                )
    +                expr: Expr_Yield(
    +                    key: null
    +                    value: null
    +                )
    +            )
    +            4: Expr_Assign(
    +                var: Expr_Variable(
    +                    name: data
    +                )
    +                expr: Expr_Yield(
    +                    key: null
    +                    value: Expr_Variable(
    +                        name: value
    +                    )
    +                )
    +            )
    +            5: Expr_Assign(
    +                var: Expr_Variable(
    +                    name: data
    +                )
    +                expr: Expr_Yield(
    +                    key: Expr_Variable(
    +                        name: key
    +                    )
    +                    value: Expr_Variable(
    +                        name: value
    +                    )
    +                )
    +            )
    +            6: Stmt_If(
    +                stmts: array(
    +                )
    +                elseifs: array(
    +                    0: Stmt_ElseIf(
    +                        cond: Expr_Yield(
    +                            key: null
    +                            value: Expr_Variable(
    +                                name: foo
    +                            )
    +                        )
    +                        stmts: array(
    +                        )
    +                    )
    +                )
    +                else: null
    +                cond: Expr_Yield(
    +                    key: null
    +                    value: Expr_Variable(
    +                        name: foo
    +                    )
    +                )
    +            )
    +            7: Stmt_If(
    +                stmts: array(
    +                )
    +                elseifs: array(
    +                    0: Stmt_ElseIf(
    +                        cond: Expr_Yield(
    +                            key: null
    +                            value: Expr_Variable(
    +                                name: foo
    +                            )
    +                        )
    +                        stmts: array(
    +                        )
    +                    )
    +                )
    +                else: null
    +                cond: Expr_Yield(
    +                    key: null
    +                    value: Expr_Variable(
    +                        name: foo
    +                    )
    +                )
    +            )
    +            8: Stmt_While(
    +                cond: Expr_Yield(
    +                    key: null
    +                    value: Expr_Variable(
    +                        name: foo
    +                    )
    +                )
    +                stmts: array(
    +                )
    +            )
    +            9: Stmt_Do(
    +                cond: Expr_Yield(
    +                    key: null
    +                    value: Expr_Variable(
    +                        name: foo
    +                    )
    +                )
    +                stmts: array(
    +                )
    +            )
    +            10: Stmt_Switch(
    +                cond: Expr_Yield(
    +                    key: null
    +                    value: Expr_Variable(
    +                        name: foo
    +                    )
    +                )
    +                cases: array(
    +                )
    +            )
    +            11: Expr_Exit(
    +                expr: Expr_Yield(
    +                    key: null
    +                    value: Expr_Variable(
    +                        name: foo
    +                    )
    +                )
    +            )
    +            12: Expr_FuncCall(
    +                name: Name(
    +                    parts: array(
    +                        0: func
    +                    )
    +                )
    +                args: array(
    +                    0: Arg(
    +                        value: Expr_Yield(
    +                            key: null
    +                            value: Expr_Variable(
    +                                name: foo
    +                            )
    +                        )
    +                        byRef: false
    +                    )
    +                )
    +            )
    +            13: Expr_MethodCall(
    +                var: Expr_Variable(
    +                    name: foo
    +                )
    +                name: func
    +                args: array(
    +                    0: Arg(
    +                        value: Expr_Yield(
    +                            key: null
    +                            value: Expr_Variable(
    +                                name: foo
    +                            )
    +                        )
    +                        byRef: false
    +                    )
    +                )
    +            )
    +            14: Expr_New(
    +                class: Name(
    +                    parts: array(
    +                        0: Foo
    +                    )
    +                )
    +                args: array(
    +                    0: Arg(
    +                        value: Expr_Yield(
    +                            key: null
    +                            value: Expr_Variable(
    +                                name: foo
    +                            )
    +                        )
    +                        byRef: false
    +                    )
    +                )
    +            )
    +        )
    +        name: gen
    +    )
    +)
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/code/parser/stmt/function/specialVars.test b/vendor/nikic/php-parser/test/code/parser/stmt/function/specialVars.test
    new file mode 100644
    index 0000000..3670e55
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/code/parser/stmt/function/specialVars.test
    @@ -0,0 +1,50 @@
    +Special function variables
    +-----
    +
    +Hallo World!
    +-----
    +array(
    +    0: Expr_Variable(
    +        name: a
    +    )
    +    1: Stmt_HaltCompiler(
    +        remaining: Hallo World!
    +    )
    +)
    +-----
    +
    +B
    +
    + $c) {}
    +foreach ($a as $b => &$c) {}
    +foreach ($a as list($a, $b)) {}
    +foreach ($a as $a => list($b, , $c)) {}
    +
    +// foreach on expression
    +foreach (array() as $b) {}
    +
    +// alternative syntax
    +foreach ($a as $b):
    +endforeach;
    +-----
    +array(
    +    0: Stmt_Foreach(
    +        keyVar: null
    +        byRef: false
    +        stmts: array(
    +        )
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        valueVar: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    1: Stmt_Foreach(
    +        keyVar: null
    +        byRef: true
    +        stmts: array(
    +        )
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        valueVar: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    2: Stmt_Foreach(
    +        keyVar: Expr_Variable(
    +            name: b
    +        )
    +        byRef: false
    +        stmts: array(
    +        )
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        valueVar: Expr_Variable(
    +            name: c
    +        )
    +    )
    +    3: Stmt_Foreach(
    +        keyVar: Expr_Variable(
    +            name: b
    +        )
    +        byRef: true
    +        stmts: array(
    +        )
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        valueVar: Expr_Variable(
    +            name: c
    +        )
    +    )
    +    4: Stmt_Foreach(
    +        keyVar: null
    +        byRef: false
    +        stmts: array(
    +        )
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        valueVar: Expr_List(
    +            vars: array(
    +                0: Expr_Variable(
    +                    name: a
    +                )
    +                1: Expr_Variable(
    +                    name: b
    +                )
    +            )
    +        )
    +    )
    +    5: Stmt_Foreach(
    +        keyVar: Expr_Variable(
    +            name: a
    +        )
    +        byRef: false
    +        stmts: array(
    +        )
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        valueVar: Expr_List(
    +            vars: array(
    +                0: Expr_Variable(
    +                    name: b
    +                )
    +                1: null
    +                2: Expr_Variable(
    +                    name: c
    +                )
    +            )
    +        )
    +    )
    +    6: Stmt_Foreach(
    +        keyVar: null
    +        byRef: false
    +        stmts: array(
    +        )
    +        expr: Expr_Array(
    +            items: array(
    +            )
    +        )
    +        valueVar: Expr_Variable(
    +            name: b
    +        )
    +    )
    +    7: Stmt_Foreach(
    +        keyVar: null
    +        byRef: false
    +        stmts: array(
    +        )
    +        expr: Expr_Variable(
    +            name: a
    +        )
    +        valueVar: Expr_Variable(
    +            name: b
    +        )
    +    )
    +)
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/code/parser/stmt/loop/while.test b/vendor/nikic/php-parser/test/code/parser/stmt/loop/while.test
    new file mode 100644
    index 0000000..65f6b23
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/code/parser/stmt/loop/while.test
    @@ -0,0 +1,25 @@
    +While loop
    +-----
    +
    +Hi!
    +-----
    +array(
    +    0: Stmt_Declare(
    +        declares: array(
    +            0: Stmt_DeclareDeclare(
    +                key: A
    +                value: Scalar_String(
    +                    value: B
    +                )
    +            )
    +        )
    +        stmts: array(
    +        )
    +    )
    +    1: Stmt_Namespace(
    +        name: Name(
    +            parts: array(
    +                0: B
    +            )
    +        )
    +        stmts: array(
    +        )
    +    )
    +    2: Stmt_HaltCompiler(
    +        remaining: Hi!
    +    )
    +)
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/code/parser/stmt/namespace/outsideStmt.test-fail b/vendor/nikic/php-parser/test/code/parser/stmt/namespace/outsideStmt.test-fail
    new file mode 100644
    index 0000000..98c0460
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/code/parser/stmt/namespace/outsideStmt.test-fail
    @@ -0,0 +1,13 @@
    +There (mostly) can't be statements outside of namespaces
    +-----
    +
    +HTML
    +-----
    +
    +HTML
    +-----
    +HTML
    +
    +HTML
    +-----
    +HTML
    +
    +HTML
    +-----
    +HTML
    +
    +HTML
    +
    +HTML
    +-----
    +HTML
    +
    +HTML
    +
    +HTML
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/code/prettyPrinter/namespaces.test b/vendor/nikic/php-parser/test/code/prettyPrinter/namespaces.test
    new file mode 100644
    index 0000000..82bbf6d
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/code/prettyPrinter/namespaces.test
    @@ -0,0 +1,58 @@
    +Namespaces
    +-----
    + 0) > (1 < 0);
    +
    +// this will currently unnecessarily print !($a = $b). This is correct as far as operator precedence is concerned, but
    +// the parentheses are not really necessary, because = takes only variables as the left hand side operator.
    +!$a = $b;
    +-----
    +echo 'abc' . 'cde' . 'fgh';
    +echo 'abc' . ('cde' . 'fgh');
    +echo 'abc' . 1 + 2 . 'fgh';
    +echo 'abc' . (1 + 2) . 'fgh';
    +echo 1 * 2 + 3 / 4 % 5 . 6;
    +echo 1 * (2 + 3) / (4 % (5 . 6));
    +$a = $b = $c = $d = $f && true;
    +($a = $b = $c = $d = $f) && true;
    +$a = $b = $c = $d = $f and true;
    +$a = $b = $c = $d = ($f and true);
    +$a ? $b : $c ? $d : $e ? $f : $g;
    +$a ? $b : ($c ? $d : ($e ? $f : $g));
    +$a ? $b ? $c : $d : $f;
    +(1 > 0) > (1 < 0);
    +// this will currently unnecessarily print !($a = $b). This is correct as far as operator precedence is concerned, but
    +// the parentheses are not really necessary, because = takes only variables as the left hand side operator.
    +!($a = $b);
    \ No newline at end of file
    diff --git a/vendor/nikic/php-parser/test/code/prettyPrinter/switch.test b/vendor/nikic/php-parser/test/code/prettyPrinter/switch.test
    new file mode 100644
    index 0000000..0733a48
    --- /dev/null
    +++ b/vendor/nikic/php-parser/test/code/prettyPrinter/switch.test
    @@ -0,0 +1,35 @@
    +switch/case/default
    +-----
    + 0) {
    +    if (count($options) === 1 && $options[0] === '--no-progress') {
    +        $SHOW_PROGRESS = false;
    +    } else {
    +        showHelp('Invalid option passed!');
    +    }
    +}
    +
    +$TEST_TYPE = $arguments[0];
    +$DIR       = $arguments[1];
    +
    +if ('Symfony' === $TEST_TYPE) {
    +    function filter_func($path) {
    +        return preg_match('~\.php(?:\.cache)?$~', $path) && false === strpos($path, 'skeleton');
    +    };
    +} elseif ('PHP' === $TEST_TYPE) {
    +    function filter_func($path) {
    +        return preg_match('~\.phpt$~', $path);
    +    };
    +} else {
    +    showHelp('Test type must be either "Symfony" or "PHP"!');
    +}
    +
    +require_once dirname(__FILE__) . '/../lib/PHPParser/Autoloader.php';
    +PHPParser_Autoloader::register();
    +
    +$parser        = new PHPParser_Parser(new PHPParser_Lexer_Emulative);
    +$prettyPrinter = new PHPParser_PrettyPrinter_Default;
    +$nodeDumper    = new PHPParser_NodeDumper;
    +
    +$parseFail = $ppFail = $compareFail = $count = 0;
    +
    +$readTime = $parseTime = $ppTime = $reparseTime = $compareTime = 0;
    +$totalStartTime = microtime(true);
    +
    +foreach (new RecursiveIteratorIterator(
    +             new RecursiveDirectoryIterator($DIR),
    +             RecursiveIteratorIterator::LEAVES_ONLY)
    +         as $file) {
    +    if (!filter_func($file)) {
    +        continue;
    +    }
    +
    +    $startTime = microtime(true);
    +    $code = file_get_contents($file);
    +    $readTime += microtime(true) - $startTime;
    +
    +    if ('PHP' === $TEST_TYPE) {
    +        if (preg_match('~(?:
    +# skeleton files
    +  ext.gmp.tests.001
    +| ext.skeleton.tests.001
    +# multibyte encoded files
    +| ext.mbstring.tests.zend_multibyte-01
    +| Zend.tests.multibyte.multibyte_encoding_001
    +| Zend.tests.multibyte.multibyte_encoding_004
    +| Zend.tests.multibyte.multibyte_encoding_005
    +# token_get_all bug (https://bugs.php.net/bug.php?id=60097)
    +| Zend.tests.bug47516
    +# pretty print difference due to INF vs 1e1000
    +| ext.standard.tests.general_functions.bug27678
    +| tests.lang.bug24640
    +)\.phpt$~x', $file)) {
    +            continue;
    +        }
    +
    +        if (!preg_match('~--FILE--\s*(.*?)--[A-Z]+--~s', $code, $matches)) {
    +            continue;
    +        }
    +        if (preg_match('~--EXPECT(?:F|REGEX)?--\s*(?:Parse|Fatal) error~', $code)) {
    +            continue;
    +        }
    +
    +        $code = $matches[1];
    +    }
    +
    +    set_time_limit(10);
    +
    +    ++$count;
    +
    +    if ($SHOW_PROGRESS) {
    +        echo substr(str_pad('Testing file ' . $count . ': ' . substr($file, strlen($DIR)), 79), 0, 79), "\r";
    +    }
    +
    +    try {
    +        $startTime = microtime(true);
    +        $stmts = $parser->parse($code);
    +        $parseTime += microtime(true) - $startTime;
    +
    +        $startTime = microtime(true);
    +        $code = 'prettyPrint($stmts);
    +        $ppTime += microtime(true) - $startTime;
    +
    +        try {
    +            $startTime = microtime(true);
    +            $ppStmts = $parser->parse($code);
    +            $reparseTime += microtime(true) - $startTime;
    +
    +            $startTime = microtime(true);
    +            $same = $nodeDumper->dump($stmts) == $nodeDumper->dump($ppStmts);
    +            $compareTime += microtime(true) - $startTime;
    +
    +            if (!$same) {
    +                echo $file, ":\n    Result of initial parse and parse after pretty print differ\n";
    +
    +                ++$compareFail;
    +            }
    +        } catch (PHPParser_Error $e) {
    +            echo $file, ":\n    Parse of pretty print failed with message: {$e->getMessage()}\n";
    +
    +            ++$ppFail;
    +        }
    +    } catch (PHPParser_Error $e) {
    +        echo $file, ":\n    Parse failed with message: {$e->getMessage()}\n";
    +
    +        ++$parseFail;
    +    }
    +}
    +
    +if (0 === $parseFail && 0 === $ppFail && 0 === $compareFail) {
    +    echo "\n\n", 'All tests passed.', "\n";
    +} else {
    +    echo "\n\n", '==========', "\n\n", 'There were: ', "\n";
    +    if (0 !== $parseFail) {
    +        echo '    ', $parseFail,   ' parse failures.',        "\n";
    +    }
    +    if (0 !== $ppFail) {
    +        echo '    ', $ppFail,      ' pretty print failures.', "\n";
    +    }
    +    if (0 !== $compareFail) {
    +        echo '    ', $compareFail, ' compare failures.',      "\n";
    +    }
    +}
    +
    +echo "\n",
    +     'Tested files:         ', $count,        "\n",
    +     "\n",
    +     'Reading files took:   ', $readTime,    "\n",
    +     'Parsing took:         ', $parseTime,   "\n",
    +     'Pretty printing took: ', $ppTime,      "\n",
    +     'Reparsing took:       ', $reparseTime, "\n",
    +     'Comparing took:       ', $compareTime, "\n",
    +     "\n",
    +     'Total time:           ', microtime(true) - $totalStartTime, "\n",
    +     'Maximum memory usage: ', memory_get_peak_usage(true), "\n";
    \ No newline at end of file
    diff --git a/vendor/patchwork/utf8/README.md b/vendor/patchwork/utf8/README.md
    new file mode 100644
    index 0000000..62e95fa
    --- /dev/null
    +++ b/vendor/patchwork/utf8/README.md
    @@ -0,0 +1,125 @@
    +Patchwork UTF-8
    +===============
    +
    +Patchwork UTF-8 provides both :
    +
    +- a portability layer for Unicode handling in PHP, and
    +- a class that mirrors the quasi complete set of native string functions,
    +  enhanced to UTF-8 [grapheme clusters](http://unicode.org/reports/tr29/)
    +  awareness.
    +
    +It can also serve as a documentation source referencing the practical problems
    +that arise when handling UTF-8 in PHP: Unicode concepts, related algorithms,
    +bugs in PHP core, workarounds, etc.
    +
    +Portability
    +-----------
    +
    +Unicode handling in PHP is best performed using a combo of `mbstring`, `iconv`,
    +`intl` and `pcre` with the `u` flag enabled. But when an application is expected
    +to run on many servers, you should be aware that these 4 extensions are not
    +always enabled.
    +
    +Patchwork UTF-8 provides pure PHP implementations for 3 of those 4 extensions.
    +Here is the set of portability-fallbacks that are currently implemented:
    +
    +- *utf8_encode, utf8_decode*,
    +- `mbstring`: *mb_convert_encoding, mb_decode_mimeheader, mb_encode_mimeheader,
    +  mb_convert_case, mb_internal_encoding, mb_list_encodings, mb_strlen,
    +  mb_strpos, mb_strrpos, mb_strtolower, mb_strtoupper, mb_substitute_character,
    +  mb_substr, mb_stripos, mb_stristr, mb_strrchr, mb_strrichr, mb_strripos,
    +  mb_strstr*,
    +- `iconv`: *iconv, iconv_mime_decode, iconv_mime_decode_headers,
    +  iconv_get_encoding, iconv_set_encoding, iconv_mime_encode, ob_iconv_handler,
    +  iconv_strlen, iconv_strpos, iconv_strrpos, iconv_substr*,
    +- `intl`: *Normalizer, grapheme_extract, grapheme_stripos, grapheme_stristr,
    +  grapheme_strlen, grapheme_strpos, grapheme_strripos, grapheme_strrpos,
    +  grapheme_strstr, grapheme_substr*.
    +
    +`pcre` compiled with unicode support is required.
    +
    +Patchwork\Utf8
    +--------------
    +
    +[Grapheme clusters](http://unicode.org/reports/tr29/) should always be
    +considered when working with generic Unicode strings. The `Patchwork\Utf8`
    +class implements the quasi-complete set of native string functions that need
    +UTF-8 grapheme clusters awareness. Function names, arguments and behavior
    +carefully replicates native PHP string functions so that usage is very easy.
    +
    +Some more functions are also provided to help handling UTF-8 strings:
    +
    +- *isUtf8()*: checks if a string contains well formed UTF-8 data,
    +- *toAscii()*: generic UTF-8 to ASCII transliteration,
    +- *strtocasefold()*: unicode transformation for caseless matching,
    +- *strtonatfold()*: generic case sensitive transformation for collation matching
    +
    +Mirrored string functions are:
    +*strlen, substr, strpos, stripos, strrpos, strripos, strstr, stristr, strrchr,
    +strrichr, strtolower, strtoupper, wordwrap, chr, count_chars, ltrim, ord, rtrim,
    +trim, str_ireplace, str_pad, str_shuffle, str_split, str_word_count, strcmp,
    +strnatcmp, strcasecmp, strnatcasecmp, strncasecmp, strncmp, strcspn, strpbrk,
    +strrev, strspn, strtr, substr_compare, substr_count, substr_replace, ucfirst,
    +lcfirst, ucwords, number_format, utf8_encode, utf8_decode*.
    +
    +Missing are *printf*-family functions.
    +
    +Usage
    +-----
    +
    +The recommended way to install Patchwork UTF-8 is [through
    +composer](http://getcomposer.org). Just create a `composer.json` file and run
    +the `php composer.phar install` command to install it:
    +
    +    {
    +        "require": {
    +            "patchwork/utf8": "1.1.*"
    +        }
    +    }
    +
    +Then, early in your bootstrap sequence, you have to configure your environment:
    +
    +```php
    +\Patchwork\Utf8\Bootup::initAll(); // Enables the portablity layer and configures PHP for UTF-8
    +\Patchwork\Utf8\Bootup::filterRequestUri(); // Redirects to an UTF-8 encoded URL if it's not already the case
    +\Patchwork\Utf8\Bootup::filterRequestInputs(); // Sanitizes HTTP inputs to UTF-8 NFC
    +```
    +
    +Run `phpunit` in the `tests/` directory to see the code in action.
    +
    +Make sure that you are confident about using UTF-8 by reading
    +[Character Sets / Character Encoding Issues](http://www.phpwact.org/php/i18n/charsets)
    +and [Handling UTF-8 with PHP](http://www.phpwact.org/php/i18n/utf-8),
    +or [PHP et UTF-8](http://julp.lescigales.org/articles/3-php-et-utf-8.html) for french readers.
    +
    +You should also get familar with the concept of
    +[Unicode Normalization](http://en.wikipedia.org/wiki/Unicode_equivalence) and
    +[Grapheme Clusters](http://unicode.org/reports/tr29/).
    +
    +Do not blindly replace all use of PHP's string functions. Most of the time you
    +will not need to, and you will be introducing a significant performance overhead
    +to your application.
    +
    +Screen your input on the *outer perimeter* so that only well formed UTF-8 pass
    +through. When dealing with badly formed UTF-8, you should not try to fix it.
    +Instead, consider it as ISO-8859-1 and use `utf8_encode()` to get an UTF-8
    +string. Don't forget also to choose one unicode normalization form and stick to
    +it. NFC is the most in use today.
    +
    +This library is orthogonal to `mbstring.func_overload` and will not work if the
    +php.ini setting is enabled.
    +
    +Licensing
    +---------
    +
    +Patchwork\Utf8 is free software; you can redistribute it and/or modify it under
    +the terms of the (at your option):
    +- [Apache License v2.0](http://apache.org/licenses/LICENSE-2.0.txt), or
    +- [GNU General Public License v2.0](http://gnu.org/licenses/gpl-2.0.txt).
    +
    +Unicode handling requires tedious work to be implemented and maintained on the
    +long run. As such, contributions such as unit tests, bug reports, comments or
    +patches licensed under both licenses are really welcomed.
    +
    +I hope many projects could adopt this code and together help solve the unicode
    +subject for PHP.
    diff --git a/vendor/patchwork/utf8/class/Normalizer.php b/vendor/patchwork/utf8/class/Normalizer.php
    new file mode 100644
    index 0000000..3acd5f3
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Normalizer.php
    @@ -0,0 +1,17 @@
    + 'utf-8',
    +        'ascii' => 'us-ascii',
    +        'tis-620' => 'iso-8859-11',
    +        'cp1250' => 'windows-1250',
    +        'cp1251' => 'windows-1251',
    +        'cp1252' => 'windows-1252',
    +        'cp1253' => 'windows-1253',
    +        'cp1254' => 'windows-1254',
    +        'cp1255' => 'windows-1255',
    +        'cp1256' => 'windows-1256',
    +        'cp1257' => 'windows-1257',
    +        'cp1258' => 'windows-1258',
    +        'shift-jis' => 'cp932',
    +        'shift_jis' => 'cp932',
    +        'latin1' => 'iso-8859-1',
    +        'latin2' => 'iso-8859-2',
    +        'latin3' => 'iso-8859-3',
    +        'latin4' => 'iso-8859-4',
    +        'latin5' => 'iso-8859-9',
    +        'latin6' => 'iso-8859-10',
    +        'latin7' => 'iso-8859-13',
    +        'latin8' => 'iso-8859-14',
    +        'latin9' => 'iso-8859-15',
    +        'latin10' => 'iso-8859-16',
    +        'iso8859-1' => 'iso-8859-1',
    +        'iso8859-2' => 'iso-8859-2',
    +        'iso8859-3' => 'iso-8859-3',
    +        'iso8859-4' => 'iso-8859-4',
    +        'iso8859-5' => 'iso-8859-5',
    +        'iso8859-6' => 'iso-8859-6',
    +        'iso8859-7' => 'iso-8859-7',
    +        'iso8859-8' => 'iso-8859-8',
    +        'iso8859-9' => 'iso-8859-9',
    +        'iso8859-10' => 'iso-8859-10',
    +        'iso8859-11' => 'iso-8859-11',
    +        'iso8859-12' => 'iso-8859-12',
    +        'iso8859-13' => 'iso-8859-13',
    +        'iso8859-14' => 'iso-8859-14',
    +        'iso8859-15' => 'iso-8859-15',
    +        'iso8859-16' => 'iso-8859-16',
    +        'iso_8859-1' => 'iso-8859-1',
    +        'iso_8859-2' => 'iso-8859-2',
    +        'iso_8859-3' => 'iso-8859-3',
    +        'iso_8859-4' => 'iso-8859-4',
    +        'iso_8859-5' => 'iso-8859-5',
    +        'iso_8859-6' => 'iso-8859-6',
    +        'iso_8859-7' => 'iso-8859-7',
    +        'iso_8859-8' => 'iso-8859-8',
    +        'iso_8859-9' => 'iso-8859-9',
    +        'iso_8859-10' => 'iso-8859-10',
    +        'iso_8859-11' => 'iso-8859-11',
    +        'iso_8859-12' => 'iso-8859-12',
    +        'iso_8859-13' => 'iso-8859-13',
    +        'iso_8859-14' => 'iso-8859-14',
    +        'iso_8859-15' => 'iso-8859-15',
    +        'iso_8859-16' => 'iso-8859-16',
    +        'iso88591' => 'iso-8859-1',
    +        'iso88592' => 'iso-8859-2',
    +        'iso88593' => 'iso-8859-3',
    +        'iso88594' => 'iso-8859-4',
    +        'iso88595' => 'iso-8859-5',
    +        'iso88596' => 'iso-8859-6',
    +        'iso88597' => 'iso-8859-7',
    +        'iso88598' => 'iso-8859-8',
    +        'iso88599' => 'iso-8859-9',
    +        'iso885910' => 'iso-8859-10',
    +        'iso885911' => 'iso-8859-11',
    +        'iso885912' => 'iso-8859-12',
    +        'iso885913' => 'iso-8859-13',
    +        'iso885914' => 'iso-8859-14',
    +        'iso885915' => 'iso-8859-15',
    +        'iso885916' => 'iso-8859-16',
    +    ),
    +
    +    $translit_map = array(),
    +    $convert_map = array(),
    +    $error_handler,
    +    $last_error,
    +
    +    $ulen_mask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4),
    +    $is_valid_utf8;
    +
    +
    +    static function iconv($in_charset, $out_charset, $str)
    +    {
    +        if ('' === (string) $str) return '';
    +
    +
    +        // Prepare for //IGNORE and //TRANSLIT
    +
    +        $TRANSLIT = $IGNORE = '';
    +
    +        $out_charset = strtolower($out_charset);
    +        $in_charset  = strtolower($in_charset );
    +
    +        '' === $out_charset && $out_charset = 'iso-8859-1';
    +        '' ===  $in_charset &&  $in_charset = 'iso-8859-1';
    +
    +        if ('//translit' === substr($out_charset, -10))
    +        {
    +            $TRANSLIT = '//TRANSLIT';
    +            $out_charset = substr($out_charset, 0, -10);
    +        }
    +
    +        if ('//ignore' === substr($out_charset, -8))
    +        {
    +            $IGNORE = '//IGNORE';
    +            $out_charset = substr($out_charset, 0, -8);
    +        }
    +
    +        '//translit' === substr($in_charset, -10) && $in_charset = substr($in_charset, 0, -10);
    +        '//ignore'   === substr($in_charset,  -8) && $in_charset = substr($in_charset, 0,  -8);
    +
    +        isset(self::$alias[ $in_charset]) &&  $in_charset = self::$alias[ $in_charset];
    +        isset(self::$alias[$out_charset]) && $out_charset = self::$alias[$out_charset];
    +
    +
    +        // Load charset maps
    +
    +        if ( ('utf-8' !==  $in_charset && !self::loadMap('from.',  $in_charset,  $in_map))
    +          || ('utf-8' !== $out_charset && !self::loadMap(  'to.', $out_charset, $out_map)) )
    +        {
    +            user_error(sprintf(self::ERROR_WRONG_CHARSET, $in_charset, $out_charset));
    +            return false;
    +        }
    +
    +
    +        if ('utf-8' !== $in_charset)
    +        {
    +            // Convert input to UTF-8
    +            $result = '';
    +            if (self::map_to_utf8($result, $in_map, $str, $IGNORE)) $str = $result;
    +            else $str = false;
    +            self::$is_valid_utf8 = true;
    +        }
    +        else
    +        {
    +            self::$is_valid_utf8 = preg_match('//u', $str);
    +
    +            if (!self::$is_valid_utf8 && !$IGNORE)
    +            {
    +                user_error(self::ERROR_ILLEGAL_CHARACTER);
    +                return false;
    +            }
    +
    +            if ('utf-8' === $out_charset)
    +            {
    +                // UTF-8 validation
    +                $str = self::utf8_to_utf8($str, $IGNORE);
    +            }
    +        }
    +
    +        if ('utf-8' !== $out_charset && false !== $str)
    +        {
    +            // Convert output to UTF-8
    +            $result = '';
    +            if (self::map_from_utf8($result, $out_map, $str, $IGNORE, $TRANSLIT)) return $result;
    +            else return false;
    +        }
    +        else return $str;
    +    }
    +
    +    static function iconv_mime_decode_headers($str, $mode = 0, $charset = INF)
    +    {
    +        INF === $charset && $charset = self::$internal_encoding;
    +
    +        false !== strpos($str, "\r") && $str = strtr(str_replace("\r\n", "\n", $str), "\r", "\n");
    +        $str = explode("\n\n", $str, 2);
    +
    +        $headers = array();
    +
    +        $str = preg_split('/\n(?![ \t])/', $str[0]);
    +        foreach ($str as $str)
    +        {
    +            $str = self::iconv_mime_decode($str, $mode, $charset);
    +            if (false === $str) return false;
    +            $str = explode(':', $str, 2);
    +
    +            if (2 === count($str))
    +            {
    +                if (isset($headers[$str[0]]))
    +                {
    +                    is_array($headers[$str[0]]) || $headers[$str[0]] = array($headers[$str[0]]);
    +                    $headers[$str[0]][] = ltrim($str[1]);
    +                }
    +                else $headers[$str[0]] = ltrim($str[1]);
    +            }
    +        }
    +
    +        return $headers;
    +    }
    +
    +    static function iconv_mime_decode($str, $mode = 0, $charset = INF)
    +    {
    +        INF === $charset && $charset = self::$internal_encoding;
    +        if (ICONV_MIME_DECODE_CONTINUE_ON_ERROR & $mode) $charset .= '//IGNORE';
    +
    +        false !== strpos($str, "\r") && $str = strtr(str_replace("\r\n", "\n", $str), "\r", "\n");
    +        $str = preg_split('/\n(?![ \t])/', rtrim($str), 2);
    +        $str = preg_replace('/[ \t]*\n[ \t]+/', ' ', rtrim($str[0]));
    +        $str = preg_split('/=\?([^?]+)\?([bqBQ])\?(.*?)\?=/', $str, -1, PREG_SPLIT_DELIM_CAPTURE);
    +
    +        $result = self::iconv('utf-8', $charset, $str[0]);
    +        if (false === $result) return false;
    +
    +        $i = 1;
    +        $len = count($str);
    +
    +        while ($i < $len)
    +        {
    +            $c = strtolower($str[$i]);
    +            if ( (ICONV_MIME_DECODE_CONTINUE_ON_ERROR & $mode)
    +              && 'utf-8' !== $c
    +              && !isset(self::$alias[$c])
    +              && !self::loadMap('from.', $c,  $d) ) $d = false;
    +            else if ('B' === strtoupper($str[$i+1])) $d = base64_decode($str[$i+2]);
    +            else $d = rawurldecode(strtr(str_replace('%', '%25', $str[$i+2]), '=_', '% '));
    +
    +            if (false !== $d)
    +            {
    +                $result .= self::iconv($c, $charset, $d);
    +                $d = self::iconv('utf-8' , $charset, $str[$i+3]);
    +                if ('' !== trim($d)) $result .= $d;
    +            }
    +            else if (ICONV_MIME_DECODE_CONTINUE_ON_ERROR & $mode)
    +            {
    +                $result .= "=?{$str[$i]}?{$str[$i+1]}?{$str[$i+2]}?={$str[$i+3]}";
    +            }
    +            else
    +            {
    +                $result = false;
    +                break;
    +            }
    +
    +            $i += 4;
    +        }
    +
    +        return $result;
    +    }
    +
    +    static function iconv_get_encoding($type = 'all')
    +    {
    +        switch ($type)
    +        {
    +        case 'input_encoding'   : return self::$input_encoding;
    +        case 'output_encoding'  : return self::$output_encoding;
    +        case 'internal_encoding': return self::$internal_encoding;
    +        }
    +
    +        return array(
    +            'input_encoding'    => self::$input_encoding,
    +            'output_encoding'   => self::$output_encoding,
    +            'internal_encoding' => self::$internal_encoding
    +        );
    +    }
    +
    +    static function iconv_set_encoding($type, $charset)
    +    {
    +        switch ($type)
    +        {
    +        case 'input_encoding'   : self::$input_encoding    = $charset; break;
    +        case 'output_encoding'  : self::$output_encoding   = $charset; break;
    +        case 'internal_encoding': self::$internal_encoding = $charset; break;
    +
    +        default: return false;
    +        }
    +
    +        return true;
    +    }
    +
    +    static function iconv_mime_encode($field_name, $field_value, $pref = INF)
    +    {
    +        is_array($pref) || $pref = array();
    +
    +        $pref += array(
    +            'scheme'           => 'B',
    +            'input-charset'    => self::$internal_encoding,
    +            'output-charset'   => self::$internal_encoding,
    +            'line-length'      => 76,
    +            'line-break-chars' => "\r\n"
    +        );
    +
    +        preg_match('/[\x80-\xFF]/', $field_name) && $field_name = '';
    +
    +        $scheme = strtoupper(substr($pref['scheme'], 0, 1));
    +        $in  = strtolower($pref['input-charset']);
    +        $out = strtolower($pref['output-charset']);
    +
    +        if ('utf-8' !== $in && false === $field_value = self::iconv($in, 'utf-8', $field_value)) return false;
    +
    +        preg_match_all('/./us', $field_value, $chars);
    +
    +        $chars = isset($chars[0]) ? $chars[0] : array();
    +
    +        $line_break  = (int) $pref['line-length'];
    +        $line_start  = "=?{$pref['output-charset']}?{$scheme}?";
    +        $line_length = strlen($field_name) + 2 + strlen($line_start) + 2;
    +        $line_offset = strlen($line_start) + 3;
    +        $line_data   = '';
    +
    +        $field_value = array();
    +
    +        $Q = 'Q' === $scheme;
    +
    +        foreach ($chars as $c)
    +        {
    +            if ('utf-8' !== $out && false === $c = self::iconv('utf-8', $out, $c)) return false;
    +
    +            $o = $Q
    +                ? $c = preg_replace_callback(
    +                    '/[=_\?\x00-\x1F\x80-\xFF]/',
    +                    array(__CLASS__, 'qp_byte_callback'),
    +                    $c
    +                )
    +                : base64_encode($line_data . $c);
    +
    +            if (isset($o[$line_break - $line_length]))
    +            {
    +                $Q || $line_data = base64_encode($line_data);
    +                $field_value[] = $line_start . $line_data . '?=';
    +                $line_length = $line_offset;
    +                $line_data = '';
    +            }
    +
    +            $line_data .= $c;
    +            $Q && $line_length += strlen($c);
    +        }
    +
    +        if ('' !== $line_data)
    +        {
    +            $Q || $line_data = base64_encode($line_data);
    +            $field_value[] = $line_start . $line_data . '?=';
    +        }
    +
    +        return $field_name . ': ' . implode($pref['line-break-chars'] . ' ', $field_value);
    +    }
    +
    +    static function ob_iconv_handler($buffer, $mode)
    +    {
    +        return self::iconv(self::$internal_encoding, self::$output_encoding, $buffer);
    +    }
    +
    +    static function iconv_strlen($s, $encoding = INF)
    +    {
    +/**/    if (extension_loaded('xml'))
    +            return self::strlen1($s, $encoding);
    +/**/    else
    +            return self::strlen2($s, $encoding);
    +    }
    +
    +    static function strlen1($s, $encoding = INF)
    +    {
    +        INF === $encoding && $encoding = self::$internal_encoding;
    +        if (0 !== strncasecmp($encoding, 'utf-8', 5) && false === $s = self::iconv($encoding, 'utf-8', $s)) return false;
    +
    +        return strlen(utf8_decode($s));
    +    }
    +
    +    static function strlen2($s, $encoding = INF)
    +    {
    +        INF === $encoding && $encoding = self::$internal_encoding;
    +        if (0 !== strncasecmp($encoding, 'utf-8', 5) && false === $s = self::iconv($encoding, 'utf-8', $s)) return false;
    +
    +        $ulen_mask = self::$ulen_mask;
    +
    +        $i = 0; $j = 0;
    +        $len = strlen($s);
    +
    +        while ($i < $len)
    +        {
    +            $u = $s[$i] & "\xF0";
    +            $i += isset($ulen_mask[$u]) ? $ulen_mask[$u] : 1;
    +            ++$j;
    +        }
    +
    +        return $j;
    +    }
    +
    +    static function iconv_strpos($haystack, $needle, $offset = 0, $encoding = INF)
    +    {
    +        INF === $encoding && $encoding = self::$internal_encoding;
    +
    +        if (0 !== strncasecmp($encoding, 'utf-8', 5))
    +        {
    +            if (false === $haystack = self::iconv($encoding, 'utf-8', $haystack)) return false;
    +            if (false === $needle = self::iconv($encoding, 'utf-8', $needle)) return false;
    +        }
    +
    +        if ($offset = (int) $offset) $haystack = self::iconv_substr($haystack, $offset, 2147483647, 'utf-8');
    +        $pos = strpos($haystack, $needle);
    +        return false === $pos ? false : ($offset + ($pos ? self::iconv_strlen(substr($haystack, 0, $pos), 'utf-8') : 0));
    +    }
    +
    +    static function iconv_strrpos($haystack, $needle, $encoding = INF)
    +    {
    +        INF === $encoding && $encoding = self::$internal_encoding;
    +
    +        if (0 !== strncasecmp($encoding, 'utf-8', 5))
    +        {
    +            if (false === $haystack = self::iconv($encoding, 'utf-8', $haystack)) return false;
    +            if (false === $needle = self::iconv($encoding, 'utf-8', $needle)) return false;
    +        }
    +
    +        $pos = isset($needle[0]) ? strrpos($haystack, $needle) : false;
    +        return false === $pos ? false : self::iconv_strlen($pos ? substr($haystack, 0, $pos) : $haystack, 'utf-8');
    +    }
    +
    +    static function iconv_substr($s, $start, $length = 2147483647, $encoding = INF)
    +    {
    +        INF === $encoding && $encoding = self::$internal_encoding;
    +        if (0 === strncasecmp($encoding, 'utf-8', 5)) $encoding = INF;
    +        else if (false === $s = self::iconv($encoding, 'utf-8', $s)) return false;
    +
    +        $slen = self::iconv_strlen($s, 'utf-8');
    +        $start = (int) $start;
    +
    +        if (0 > $start) $start += $slen;
    +        if (0 > $start) return false;
    +        if ($start >= $slen) return false;
    +
    +        $rx = $slen - $start;
    +
    +        if (0 > $length) $length += $rx;
    +        if (0 === $length) return '';
    +        if (0 > $length) return false;
    +
    +        if ($length > $rx) $length = $rx;
    +
    +        $rx = '/^' . ($start ? self::preg_offset($start) : '') . '(' . self::preg_offset($length) . ')/u';
    +
    +        $s = preg_match($rx, $s, $s) ? $s[1] : '';
    +
    +        if (INF === $encoding) return $s;
    +        else return self::iconv('utf-8', $encoding, $s);
    +    }
    +
    +    protected static function loadMap($type, $charset, &$map)
    +    {
    +        if (!isset(self::$convert_map[$type . $charset]))
    +        {
    +            if (false === $map = self::getData($type . $charset))
    +            {
    +                if ('to.' === $type && self::loadMap('from.', $charset, $map)) $map = array_flip($map);
    +                else return false;
    +            }
    +
    +            self::$convert_map[$type . $charset] = $map;
    +        }
    +        else $map = self::$convert_map[$type . $charset];
    +
    +        return true;
    +    }
    +
    +    protected static function utf8_to_utf8($str, $IGNORE)
    +    {
    +        $ulen_mask = self::$ulen_mask;
    +        $valid     = self::$is_valid_utf8;
    +
    +        $u = $str;
    +        $i = $j = 0;
    +        $len = strlen($str);
    +
    +        while ($i < $len)
    +        {
    +            if ($str[$i] < "\x80") $u[$j++] = $str[$i++];
    +            else
    +            {
    +                $ulen = $str[$i] & "\xF0";
    +                $ulen = isset($ulen_mask[$ulen]) ? $ulen_mask[$ulen] : 1;
    +                $uchr = substr($str, $i, $ulen);
    +
    +                if (1 === $ulen || !($valid || preg_match('/^.$/us', $uchr)))
    +                {
    +                    if ($IGNORE)
    +                    {
    +                        ++$i;
    +                        continue;
    +                    }
    +
    +                    user_error(self::ERROR_ILLEGAL_CHARACTER);
    +                    return false;
    +                }
    +                else $i += $ulen;
    +
    +                $u[$j++] = $uchr[0];
    +
    +                   isset($uchr[1]) && 0 !== ($u[$j++] = $uchr[1])
    +                && isset($uchr[2]) && 0 !== ($u[$j++] = $uchr[2])
    +                && isset($uchr[3]) && 0 !== ($u[$j++] = $uchr[3]);
    +            }
    +        }
    +
    +        return substr($u, 0, $j);
    +    }
    +
    +    protected static function map_to_utf8(&$result, $map, $str, $IGNORE)
    +    {
    +        $len = strlen($str);
    +        for ($i = 0; $i < $len; ++$i)
    +        {
    +            if (isset($str[$i+1], $map[$str[$i] . $str[$i+1]])) $result .= $map[$str[$i] . $str[++$i]];
    +            else if (isset($map[$str[$i]])) $result .= $map[$str[$i]];
    +            else if (!$IGNORE)
    +            {
    +                user_error(self::ERROR_ILLEGAL_CHARACTER);
    +                return false;
    +            }
    +        }
    +
    +        return true;
    +    }
    +
    +    protected static function map_from_utf8(&$result, $map, $str, $IGNORE, $TRANSLIT)
    +    {
    +        $ulen_mask = self::$ulen_mask;
    +        $valid     = self::$is_valid_utf8;
    +
    +        if ($TRANSLIT) self::$translit_map or self::$translit_map = self::getData('translit');
    +
    +        $i = 0;
    +        $len = strlen($str);
    +
    +        while ($i < $len)
    +        {
    +            if ($str[$i] < "\x80") $uchr = $str[$i++];
    +            else
    +            {
    +                $ulen = $str[$i] & "\xF0";
    +                $ulen = isset($ulen_mask[$ulen]) ? $ulen_mask[$ulen] : 1;
    +                $uchr = substr($str, $i, $ulen);
    +
    +                if ($IGNORE && (1 === $ulen || !($valid || preg_match('/^.$/us', $uchr))))
    +                {
    +                    ++$i;
    +                    continue;
    +                }
    +                else $i += $ulen;
    +            }
    +
    +            if (isset($map[$uchr]))
    +            {
    +                $result .= $map[$uchr];
    +            }
    +            else if ($TRANSLIT)
    +            {
    +                if (isset(self::$translit_map[$uchr]))
    +                {
    +                    $uchr = self::$translit_map[$uchr];
    +                }
    +                else if ($uchr >= "\xC3\x80")
    +                {
    +                    $uchr = \Normalizer::normalize($uchr, \Normalizer::NFD);
    +                    $uchr = preg_split('/(.)/', $uchr, 2, PREG_SPLIT_DELIM_CAPTURE);
    +
    +                    if (isset($uchr[2][0])) $uchr = $uchr[1];
    +                    else if ($IGNORE) continue;
    +                    else return false;
    +                }
    +
    +                $str = $uchr . substr($str, $i);
    +                $len = strlen($str);
    +                $i = 0;
    +            }
    +            else if (!$IGNORE)
    +            {
    +                return false;
    +            }
    +        }
    +
    +        return true;
    +    }
    +
    +    protected static function qp_byte_callback($m)
    +    {
    +        return '=' . strtoupper(dechex(ord($m[0])));
    +    }
    +
    +    protected static function preg_offset($offset)
    +    {
    +        $rx = array();
    +        $offset = (int) $offset;
    +
    +        while ($offset > 65535)
    +        {
    +            $rx[] = '.{65535}';
    +            $offset -= 65535;
    +        }
    +
    +        return implode('', $rx) . '.{' . $offset . '}';
    +    }
    +
    +    protected static function getData($file)
    +    {
    +        $file = __DIR__ . '/charset/' . $file . '.ser';
    +        if (file_exists($file)) return unserialize(file_get_contents($file));
    +        else return false;
    +    }
    +}
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/Intl.php b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/Intl.php
    new file mode 100644
    index 0000000..ea9bb83
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/Intl.php
    @@ -0,0 +1,139 @@
    + $size || 0 > $start || 0 > $type || 2 < $type) return false;
    +        if (0 === $size) return '';
    +
    +        $next = $start;
    +
    +        $s = preg_split('/(' . GRAPHEME_CLUSTER_RX . ')/u', "\r\n" .  $s, $size + 1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
    +
    +        if (!isset($s[1])) return false;
    +
    +        $i = 1;
    +        $ret = '';
    +
    +        do
    +        {
    +            if (GRAPHEME_EXTR_COUNT === $type) --$size;
    +            else if (GRAPHEME_EXTR_MAXBYTES === $type) $size -= strlen($s[$i]);
    +            else $size -= iconv_strlen($s[$i], 'UTF-8//IGNORE');
    +
    +            if ($size >= 0) $ret .= $s[$i];
    +        }
    +        while (isset($s[++$i]) && $size > 0);
    +
    +        $next += strlen($ret);
    +
    +        return $ret;
    +    }
    +
    +    static function grapheme_strlen($s)
    +    {
    +        $s = (string) $s;
    +        preg_replace('/' . GRAPHEME_CLUSTER_RX . '/u', '', $s, -1, $len);
    +        return 0 === $len && '' !== $s ? null : $len;
    +    }
    +
    +    static function grapheme_substr($s, $start, $len = 2147483647)
    +    {
    +        preg_match_all('/' . GRAPHEME_CLUSTER_RX . '/u', $s, $s);
    +
    +        $slen = count($s[0]);
    +        $start = (int) $start;
    +
    +        if (0 > $start) $start += $slen;
    +        if (0 > $start) return false;
    +        if ($start >= $slen) return false;
    +
    +        $rem = $slen - $start;
    +
    +        if (0 > $len) $len += $rem;
    +        if (0 === $len) return '';
    +        if (0 > $len) return false;
    +        if ($len > $rem) $len = $rem;
    +
    +        return implode('', array_slice($s[0], $start, $len));
    +    }
    +
    +    static function grapheme_substr_workaround62759($s, $start, $len)
    +    {
    +        // Intl based http://bugs.php.net/62759 and 55562 workaround
    +
    +        if (2147483647 == $len) return grapheme_substr($s, $start);
    +
    +        $slen = grapheme_strlen($s);
    +        $start = (int) $start;
    +
    +        if (0 > $start) $start += $slen;
    +        if (0 > $start) return false;
    +        if ($start >= $slen) return false;
    +
    +        $rem = $slen - $start;
    +
    +        if (0 > $len) $len += $rem;
    +        if (0 === $len) return '';
    +        if (0 > $len) return false;
    +        if ($len > $rem) $len = $rem;
    +
    +        return grapheme_substr($s, $start, $len);
    +    }
    +
    +    static function grapheme_strpos  ($s, $needle, $offset = 0) {return self::grapheme_position($s, $needle, $offset, 0);}
    +    static function grapheme_stripos ($s, $needle, $offset = 0) {return self::grapheme_position($s, $needle, $offset, 1);}
    +    static function grapheme_strrpos ($s, $needle, $offset = 0) {return self::grapheme_position($s, $needle, $offset, 2);}
    +    static function grapheme_strripos($s, $needle, $offset = 0) {return self::grapheme_position($s, $needle, $offset, 3);}
    +    static function grapheme_stristr ($s, $needle, $before_needle = false) {return mb_stristr($s, $needle, $before_needle, 'UTF-8');}
    +    static function grapheme_strstr  ($s, $needle, $before_needle = false) {return mb_strstr ($s, $needle, $before_needle, 'UTF-8');}
    +
    +
    +    protected static function grapheme_position($s, $needle, $offset, $mode)
    +    {
    +        if ($offset > 0) $s = (string) self::grapheme_substr($s, $offset);
    +        else if ($offset < 0) $offset = 0;
    +        if ('' === (string) $needle) return false;
    +        if ('' === (string) $s) return false;
    +
    +        switch ($mode)
    +        {
    +        case 0: $needle = iconv_strpos ($s, $needle, 0, 'UTF-8'); break;
    +        case 1: $needle = mb_stripos   ($s, $needle, 0, 'UTF-8'); break;
    +        case 2: $needle = iconv_strrpos($s, $needle,    'UTF-8'); break;
    +        default: $needle = mb_strripos ($s, $needle, 0, 'UTF-8'); break;
    +        }
    +
    +        return $needle ? self::grapheme_strlen(iconv_substr($s, 0, $needle, 'UTF-8')) + $offset : $needle;
    +    }
    +}
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/Mbstring.php b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/Mbstring.php
    new file mode 100644
    index 0000000..709ea00
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/Mbstring.php
    @@ -0,0 +1,340 @@
    + 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4);
    +
    +        $i = 0;
    +        $len = strlen($s);
    +
    +        while ($i < $len)
    +        {
    +            $ulen = $s[$i] < "\x80" ? 1 : $ulen_mask[$s[$i] & "\xF0"];
    +            $uchr = substr($s, $i, $ulen);
    +            $i += $ulen;
    +
    +            if (isset($map[$uchr]))
    +            {
    +                $uchr = $map[$uchr];
    +                $nlen = strlen($uchr);
    +
    +                if ($nlen == $ulen)
    +                {
    +                    $nlen = $i;
    +                    do $s[--$nlen] = $uchr[--$ulen];
    +                    while ($ulen);
    +                }
    +                else
    +                {
    +                    $s = substr_replace($s, $uchr, $i - $ulen, $ulen);
    +                    $len += $nlen - $ulen;
    +                    $i   += $nlen - $ulen;
    +                }
    +            }
    +        }
    +
    +        if (MB_CASE_TITLE == $mode)
    +        {
    +            $s = preg_replace_callback('/\b\p{Ll}/u', array(__CLASS__, 'title_case_callback'), $s);
    +        }
    +
    +        if (INF === $encoding) return $s;
    +        else return iconv('UTF-8', $encoding, $s);
    +    }
    +
    +    static function mb_internal_encoding($encoding = INF)
    +    {
    +        if (INF === $encoding) return self::$internal_encoding;
    +
    +        if ('UTF-8' === strtoupper($encoding) || false !== @iconv($encoding, $encoding, ' '))
    +        {
    +            self::$internal_encoding = $encoding;
    +            return true;
    +        }
    +
    +        return false;
    +    }
    +
    +    static function mb_list_encodings()
    +    {
    +        return array('UTF-8');
    +    }
    +
    +    static function mb_strlen($s, $encoding = INF)
    +    {
    +        INF === $encoding && $encoding = self::$internal_encoding;
    +        return iconv_strlen($s, $encoding . '//IGNORE');
    +    }
    +
    +    static function mb_strpos ($haystack, $needle, $offset = 0, $encoding = INF)
    +    {
    +        INF === $encoding && $encoding = self::$internal_encoding;
    +        if ('' === (string) $needle)
    +        {
    +            user_error(__METHOD__ . ': Empty delimiter', E_USER_WARNING);
    +            return false;
    +        }
    +        else return iconv_strpos($haystack, $needle, $offset, $encoding . '//IGNORE');
    +    }
    +
    +    static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = INF)
    +    {
    +        INF === $encoding && $encoding = self::$internal_encoding;
    +
    +        if ($offset != (int) $offset)
    +        {
    +            $offset = 0;
    +        }
    +        else if ($offset = (int) $offset)
    +        {
    +            $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding);
    +        }
    +
    +        $pos = iconv_strrpos($haystack, $needle, $encoding . '//IGNORE');
    +
    +        return false !== $pos ? $offset + $pos : false;
    +    }
    +
    +    static function mb_strtolower($s, $encoding = INF)
    +    {
    +        return self::mb_convert_case($s, MB_CASE_LOWER, $encoding);
    +    }
    +
    +    static function mb_strtoupper($s, $encoding = INF)
    +    {
    +        return self::mb_convert_case($s, MB_CASE_UPPER, $encoding);
    +    }
    +
    +    static function mb_substitute_character($c = INF)
    +    {
    +        return INF !== $c ? false : 'none';
    +    }
    +
    +    static function mb_substr($s, $start, $length = 2147483647, $encoding = INF)
    +    {
    +        INF === $encoding && $encoding = self::$internal_encoding;
    +
    +        if ($start < 0)
    +        {
    +            $start = iconv_strlen($s, $encoding . '//IGNORE') + $start;
    +            if ($start < 0) $start = 0;
    +        }
    +
    +        if ($length < 0)
    +        {
    +            $length = iconv_strlen($s, $encoding . '//IGNORE') + $length - $start;
    +            if ($length < 0) return '';
    +        }
    +
    +        return (string) iconv_substr($s, $start, $length, $encoding . '//IGNORE');
    +    }
    +
    +    static function mb_stripos($haystack, $needle, $offset = 0, $encoding = INF)
    +    {
    +        INF === $encoding && $encoding = self::$internal_encoding;
    +        $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
    +        $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
    +        return self::mb_strpos($haystack, $needle, $offset, $encoding);
    +    }
    +
    +    static function mb_stristr($haystack, $needle, $part = false, $encoding = INF)
    +    {
    +        $pos = self::mb_stripos($haystack, $needle, 0, $encoding);
    +        return self::getSubpart($pos, $part, $haystack, $encoding);
    +    }
    +
    +    static function mb_strrchr($haystack, $needle, $part = false, $encoding = INF)
    +    {
    +        INF === $encoding && $encoding = self::$internal_encoding;
    +        $needle = self::mb_substr($needle, 0, 1, $encoding);
    +        $pos = iconv_strrpos($haystack, $needle, $encoding);
    +        return self::getSubpart($pos, $part, $haystack, $encoding);
    +    }
    +
    +    static function mb_strrichr($haystack, $needle, $part = false, $encoding = INF)
    +    {
    +        $needle = self::mb_substr($needle, 0, 1, $encoding);
    +        $pos = self::mb_strripos($haystack, $needle, $encoding);
    +        return self::getSubpart($pos, $part, $haystack, $encoding);
    +    }
    +
    +    static function mb_strripos($haystack, $needle, $offset = 0, $encoding = INF)
    +    {
    +        INF === $encoding && $encoding = self::$internal_encoding;
    +        $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
    +        $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
    +        return self::mb_strrpos($haystack, $needle, $offset, $encoding);
    +    }
    +
    +    static function mb_strstr($haystack, $needle, $part = false, $encoding = INF)
    +    {
    +        $pos = strpos($haystack, $needle);
    +        if (false === $pos) return false;
    +        if ($part) return substr($haystack, 0, $pos);
    +        else return substr($haystack, $pos);
    +    }
    +
    +
    +    protected static function getSubpart($pos, $part, $haystack, $encoding)
    +    {
    +        INF === $encoding && $encoding = self::$internal_encoding;
    +
    +        if (false === $pos) return false;
    +        if ($part) return self::mb_substr($haystack,    0,       $pos, $encoding);
    +        else return self::mb_substr($haystack, $pos, 2147483647, $encoding);
    +    }
    +
    +    protected static function html_encoding_callback($m)
    +    {
    +        return htmlentities($m[0], ENT_COMPAT, 'UTF-8');
    +    }
    +
    +    protected static function title_case_callback($s)
    +    {
    +        return self::mb_convert_case($s[0], MB_CASE_UPPER, 'UTF-8');
    +    }
    +
    +    protected static function getData($file)
    +    {
    +        $file = __DIR__ . '/unidata/' . $file . '.ser';
    +        if (file_exists($file)) return unserialize(file_get_contents($file));
    +        else return false;
    +    }
    +}
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/Normalizer.php b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/Normalizer.php
    new file mode 100644
    index 0000000..2f62288
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/Normalizer.php
    @@ -0,0 +1,295 @@
    + 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4),
    +    $ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";
    +
    +
    +    static function isNormalized($s, $form = self::NFC)
    +    {
    +        if (strspn($s, self::$ASCII) === strlen($s)) return true;
    +        if (self::NFC === $form && preg_match('//u', $s) && !preg_match('/[^\x00-\x{2FF}]/u', $s)) return true;
    +        return false; // Pretend false as quick checks implementented in PHP won't be so quick
    +    }
    +
    +    static function normalize($s, $form = self::NFC)
    +    {
    +        if (!preg_match('//u', $s)) return false;
    +
    +        switch ($form)
    +        {
    +        case self::NONE: return $s;
    +        case self::NFC:  $C = true;  $K = false; break;
    +        case self::NFD:  $C = false; $K = false; break;
    +        case self::NFKC: $C = true;  $K = true;  break;
    +        case self::NFKD: $C = false; $K = true;  break;
    +        default: return false;
    +        }
    +
    +        if (!strlen($s)) return '';
    +
    +        if ($K && empty(self::$KD)) self::$KD = self::getData('compatibilityDecomposition');
    +
    +        if (empty(self::$D))
    +        {
    +            self::$D = self::getData('canonicalDecomposition');
    +            self::$cC = self::getData('combiningClass');
    +        }
    +
    +        if ($C)
    +        {
    +            if (empty(self::$C)) self::$C = self::getData('canonicalComposition');
    +            return self::recompose(self::decompose($s, $K));
    +        }
    +        else return self::decompose($s, $K);
    +    }
    +
    +    protected static function recompose($s)
    +    {
    +        $ASCII = self::$ASCII;
    +        $compMap = self::$C;
    +        $combClass = self::$cC;
    +        $ulen_mask = self::$ulen_mask;
    +
    +        $result = $tail = '';
    +
    +        $i = $s[0] < "\x80" ? 1 : $ulen_mask[$s[0] & "\xF0"];
    +        $len = strlen($s);
    +
    +        $last_uchr = substr($s, 0, $i);
    +        $last_ucls = isset($combClass[$last_uchr]) ? 256 : 0;
    +
    +        while ($i < $len)
    +        {
    +            if ($s[$i] < "\x80")
    +            {
    +                // ASCII chars
    +
    +                if ($tail)
    +                {
    +                    $last_uchr .= $tail;
    +                    $tail = '';
    +                }
    +
    +                if ($j = strspn($s, $ASCII, $i+1))
    +                {
    +                    $last_uchr .= substr($s, $i, $j);
    +                    $i += $j;
    +                }
    +
    +                $result .= $last_uchr;
    +                $last_uchr = $s[$i];
    +                ++$i;
    +            }
    +            else
    +            {
    +                $ulen = $ulen_mask[$s[$i] & "\xF0"];
    +                $uchr = substr($s, $i, $ulen);
    +
    +                if ($last_uchr < "\xE1\x84\x80" || "\xE1\x84\x92" < $last_uchr
    +                    ||   $uchr < "\xE1\x85\xA1" || "\xE1\x85\xB5" < $uchr
    +                    || $last_ucls)
    +                {
    +                    // Table lookup and combining chars composition
    +
    +                    $ucls = isset($combClass[$uchr]) ? $combClass[$uchr] : 0;
    +
    +                    if (isset($compMap[$last_uchr . $uchr]) && (!$last_ucls || $last_ucls < $ucls))
    +                    {
    +                        $last_uchr = $compMap[$last_uchr . $uchr];
    +                    }
    +                    else if ($last_ucls = $ucls) $tail .= $uchr;
    +                    else
    +                    {
    +                        if ($tail)
    +                        {
    +                            $last_uchr .= $tail;
    +                            $tail = '';
    +                        }
    +
    +                        $result .= $last_uchr;
    +                        $last_uchr = $uchr;
    +                    }
    +                }
    +                else
    +                {
    +                    // Hangul chars
    +
    +                    $L = ord($last_uchr[2]) - 0x80;
    +                    $V = ord($uchr[2]) - 0xA1;
    +                    $T = 0;
    +
    +                    $uchr = substr($s, $i + $ulen, 3);
    +
    +                    if ("\xE1\x86\xA7" <= $uchr && $uchr <= "\xE1\x87\x82")
    +                    {
    +                        $T = ord($uchr[2]) - 0xA7;
    +                        0 > $T && $T += 0x40;
    +                        $ulen += 3;
    +                    }
    +
    +                    $L = 0xAC00 + ($L * 21 + $V) * 28 + $T;
    +                    $last_uchr = chr(0xE0 | $L>>12) . chr(0x80 | $L>>6 & 0x3F) . chr(0x80 | $L & 0x3F);
    +                }
    +
    +                $i += $ulen;
    +            }
    +        }
    +
    +        return $result . $last_uchr . $tail;
    +    }
    +
    +    protected static function decompose($s, $c)
    +    {
    +        $result = '';
    +
    +        $ASCII = self::$ASCII;
    +        $decompMap = self::$D;
    +        $combClass = self::$cC;
    +        $ulen_mask = self::$ulen_mask;
    +        if ($c) $compatMap = self::$KD;
    +
    +        $c = array();
    +        $i = 0;
    +        $len = strlen($s);
    +
    +        while ($i < $len)
    +        {
    +            if ($s[$i] < "\x80")
    +            {
    +                // ASCII chars
    +
    +                if ($c)
    +                {
    +                    ksort($c);
    +                    $result .= implode('', $c);
    +                    $c = array();
    +                }
    +
    +                $j = 1 + strspn($s, $ASCII, $i+1);
    +                $result .= substr($s, $i, $j);
    +                $i += $j;
    +            }
    +            else
    +            {
    +                $ulen = $ulen_mask[$s[$i] & "\xF0"];
    +                $uchr = substr($s, $i, $ulen);
    +                $i += $ulen;
    +
    +                if (isset($combClass[$uchr]))
    +                {
    +                    // Combining chars, for sorting
    +
    +                    isset($c[$combClass[$uchr]]) || $c[$combClass[$uchr]] = '';
    +                    $c[$combClass[$uchr]] .= isset($compatMap[$uchr]) ? $compatMap[$uchr] : (isset($decompMap[$uchr]) ? $decompMap[$uchr] : $uchr);
    +                }
    +                else
    +                {
    +                    if ($c)
    +                    {
    +                        ksort($c);
    +                        $result .= implode('', $c);
    +                        $c = array();
    +                    }
    +
    +                    if ($uchr < "\xEA\xB0\x80" || "\xED\x9E\xA3" < $uchr)
    +                    {
    +                        // Table lookup
    +
    +                        $j = isset($compatMap[$uchr]) ? $compatMap[$uchr] : (isset($decompMap[$uchr]) ? $decompMap[$uchr] : $uchr);
    +
    +                        if ($uchr != $j)
    +                        {
    +                            $uchr = $j;
    +
    +                            $j = strlen($uchr);
    +                            $ulen = $uchr[0] < "\x80" ? 1 : $ulen_mask[$uchr[0] & "\xF0"];
    +
    +                            if ($ulen != $j)
    +                            {
    +                                // Put trailing chars in $s
    +
    +                                $j -= $ulen;
    +                                $i -= $j;
    +
    +                                if (0 > $i)
    +                                {
    +                                    $s = str_repeat(' ', -$i) . $s;
    +                                    $len -= $i;
    +                                    $i = 0;
    +                                }
    +
    +                                while ($j--) $s[$i+$j] = $uchr[$ulen+$j];
    +
    +                                $uchr = substr($uchr, 0, $ulen);
    +                            }
    +                        }
    +                    }
    +                    else
    +                    {
    +                        // Hangul chars
    +
    +                        $uchr = unpack('C*', $uchr);
    +                        $j = (($uchr[1]-224) << 12) + (($uchr[2]-128) << 6) + $uchr[3] - 0xAC80;
    +
    +                        $uchr = "\xE1\x84" . chr(0x80 + (int)  ($j / 588))
    +                              . "\xE1\x85" . chr(0xA1 + (int) (($j % 588) / 28));
    +
    +                        if ($j %= 28)
    +                        {
    +                            $uchr .= $j < 25
    +                                ? ("\xE1\x86" . chr(0xA7 + $j))
    +                                : ("\xE1\x87" . chr(0x67 + $j));
    +                        }
    +                    }
    +
    +                    $result .= $uchr;
    +                }
    +            }
    +        }
    +
    +        if ($c)
    +        {
    +            ksort($c);
    +            $result .= implode('', $c);
    +        }
    +
    +        return $result;
    +    }
    +
    +    protected static function getData($file)
    +    {
    +        $file = __DIR__ . '/unidata/' . $file . '.ser';
    +        if (file_exists($file)) return unserialize(file_get_contents($file));
    +        else return false;
    +    }
    +}
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/Xml.php b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/Xml.php
    new file mode 100644
    index 0000000..85487b9
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/Xml.php
    @@ -0,0 +1,60 @@
    +";s:1:">";s:1:"?";s:1:"?";s:1:"@";s:1:"@";s:1:"A";s:1:"A";s:1:"B";s:1:"B";s:1:"C";s:1:"C";s:1:"D";s:1:"D";s:1:"E";s:1:"E";s:1:"F";s:1:"F";s:1:"G";s:1:"G";s:1:"H";s:1:"H";s:1:"I";s:1:"I";s:1:"J";s:1:"J";s:1:"K";s:1:"K";s:1:"L";s:1:"L";s:1:"M";s:1:"M";s:1:"N";s:1:"N";s:1:"O";s:1:"O";s:1:"P";s:1:"P";s:1:"Q";s:1:"Q";s:1:"R";s:1:"R";s:1:"S";s:1:"S";s:1:"T";s:1:"T";s:1:"U";s:1:"U";s:1:"V";s:1:"V";s:1:"W";s:1:"W";s:1:"X";s:1:"X";s:1:"Y";s:1:"Y";s:1:"Z";s:1:"Z";s:1:"[";s:1:"[";s:1:"\";s:1:"\";s:1:"]";s:1:"]";s:1:"^";s:1:"^";s:1:"_";s:1:"_";s:1:"`";s:3:"‘";s:1:"a";s:1:"a";s:1:"b";s:1:"b";s:1:"c";s:1:"c";s:1:"d";s:1:"d";s:1:"e";s:1:"e";s:1:"f";s:1:"f";s:1:"g";s:1:"g";s:1:"h";s:1:"h";s:1:"i";s:1:"i";s:1:"j";s:1:"j";s:1:"k";s:1:"k";s:1:"l";s:1:"l";s:1:"m";s:1:"m";s:1:"n";s:1:"n";s:1:"o";s:1:"o";s:1:"p";s:1:"p";s:1:"q";s:1:"q";s:1:"r";s:1:"r";s:1:"s";s:1:"s";s:1:"t";s:1:"t";s:1:"u";s:1:"u";s:1:"v";s:1:"v";s:1:"w";s:1:"w";s:1:"x";s:1:"x";s:1:"y";s:1:"y";s:1:"z";s:1:"z";s:1:"{";s:1:"{";s:1:"|";s:1:"|";s:1:"}";s:1:"}";s:1:"~";s:1:"~";s:1:"¡";s:2:"¡";s:1:"¢";s:2:"¢";s:1:"£";s:2:"£";s:1:"¤";s:3:"â„";s:1:"¥";s:2:"Â¥";s:1:"¦";s:2:"Æ’";s:1:"§";s:2:"§";s:1:"¨";s:2:"¤";s:1:"©";s:1:"'";s:1:"ª";s:3:"“";s:1:"«";s:2:"«";s:1:"¬";s:3:"‹";s:1:"­";s:3:"›";s:1:"®";s:3:"ï¬";s:1:"¯";s:3:"fl";s:1:"±";s:3:"–";s:1:"²";s:3:"†";s:1:"³";s:3:"‡";s:1:"´";s:2:"·";s:1:"¶";s:2:"¶";s:1:"·";s:3:"•";s:1:"¸";s:3:"‚";s:1:"¹";s:3:"„";s:1:"º";s:3:"â€";s:1:"»";s:2:"»";s:1:"¼";s:3:"…";s:1:"½";s:3:"‰";s:1:"¿";s:2:"¿";s:1:"Á";s:1:"`";s:1:"Â";s:2:"´";s:1:"Ã";s:2:"ˆ";s:1:"Ä";s:2:"Ëœ";s:1:"Å";s:2:"¯";s:1:"Æ";s:2:"˘";s:1:"Ç";s:2:"Ë™";s:1:"È";s:2:"¨";s:1:"Ê";s:2:"Ëš";s:1:"Ë";s:2:"¸";s:1:"Í";s:2:"Ë";s:1:"Î";s:2:"Ë›";s:1:"Ï";s:2:"ˇ";s:1:"Ð";s:3:"—";s:1:"á";s:2:"Æ";s:1:"ã";s:2:"ª";s:1:"è";s:2:"Å";s:1:"é";s:2:"Ø";s:1:"ê";s:2:"Å’";s:1:"ë";s:2:"º";s:1:"ñ";s:2:"æ";s:1:"õ";s:2:"ı";s:1:"ø";s:2:"Å‚";s:1:"ù";s:2:"ø";s:1:"ú";s:2:"Å“";s:1:"û";s:2:"ß";}
    \ No newline at end of file
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.symbol.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.symbol.ser
    new file mode 100644
    index 0000000..889217b
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.symbol.ser
    @@ -0,0 +1 @@
    +a:189:{s:1:" ";s:1:" ";s:1:"!";s:1:"!";s:1:""";s:3:"∀";s:1:"#";s:1:"#";s:1:"$";s:3:"∃";s:1:"%";s:1:"%";s:1:"&";s:1:"&";s:1:"'";s:3:"∋";s:1:"(";s:1:"(";s:1:")";s:1:")";s:1:"*";s:3:"∗";s:1:"+";s:1:"+";s:1:",";s:1:",";s:1:"-";s:3:"−";s:1:".";s:1:".";s:1:"/";s:1:"/";i:0;s:1:"0";i:1;s:1:"1";i:2;s:1:"2";i:3;s:1:"3";i:4;s:1:"4";i:5;s:1:"5";i:6;s:1:"6";i:7;s:1:"7";i:8;s:1:"8";i:9;s:1:"9";s:1:":";s:1:":";s:1:";";s:1:";";s:1:"<";s:1:"<";s:1:"=";s:1:"=";s:1:">";s:1:">";s:1:"?";s:1:"?";s:1:"@";s:3:"≅";s:1:"A";s:2:"Α";s:1:"B";s:2:"Î’";s:1:"C";s:2:"Χ";s:1:"D";s:2:"Δ";s:1:"E";s:2:"Ε";s:1:"F";s:2:"Φ";s:1:"G";s:2:"Γ";s:1:"H";s:2:"Η";s:1:"I";s:2:"Ι";s:1:"J";s:2:"Ï‘";s:1:"K";s:2:"Κ";s:1:"L";s:2:"Λ";s:1:"M";s:2:"Μ";s:1:"N";s:2:"Î";s:1:"O";s:2:"Ο";s:1:"P";s:2:"Π";s:1:"Q";s:2:"Θ";s:1:"R";s:2:"Ρ";s:1:"S";s:2:"Σ";s:1:"T";s:2:"Τ";s:1:"U";s:2:"Î¥";s:1:"V";s:2:"Ï‚";s:1:"W";s:2:"Ω";s:1:"X";s:2:"Ξ";s:1:"Y";s:2:"Ψ";s:1:"Z";s:2:"Ζ";s:1:"[";s:1:"[";s:1:"\";s:3:"∴";s:1:"]";s:1:"]";s:1:"^";s:3:"⊥";s:1:"_";s:1:"_";s:1:"`";s:3:"";s:1:"a";s:2:"α";s:1:"b";s:2:"β";s:1:"c";s:2:"χ";s:1:"d";s:2:"δ";s:1:"e";s:2:"ε";s:1:"f";s:2:"φ";s:1:"g";s:2:"γ";s:1:"h";s:2:"η";s:1:"i";s:2:"ι";s:1:"j";s:2:"Ï•";s:1:"k";s:2:"κ";s:1:"l";s:2:"λ";s:1:"m";s:2:"µ";s:1:"n";s:2:"ν";s:1:"o";s:2:"ο";s:1:"p";s:2:"Ï€";s:1:"q";s:2:"θ";s:1:"r";s:2:"Ï";s:1:"s";s:2:"σ";s:1:"t";s:2:"Ï„";s:1:"u";s:2:"Ï…";s:1:"v";s:2:"Ï–";s:1:"w";s:2:"ω";s:1:"x";s:2:"ξ";s:1:"y";s:2:"ψ";s:1:"z";s:2:"ζ";s:1:"{";s:1:"{";s:1:"|";s:1:"|";s:1:"}";s:1:"}";s:1:"~";s:3:"∼";s:1:" ";s:3:"€";s:1:"¡";s:2:"Ï’";s:1:"¢";s:3:"′";s:1:"£";s:3:"≤";s:1:"¤";s:3:"â„";s:1:"¥";s:3:"∞";s:1:"¦";s:2:"Æ’";s:1:"§";s:3:"♣";s:1:"¨";s:3:"♦";s:1:"©";s:3:"♥";s:1:"ª";s:3:"â™ ";s:1:"«";s:3:"↔";s:1:"¬";s:3:"â†";s:1:"­";s:3:"↑";s:1:"®";s:3:"→";s:1:"¯";s:3:"↓";s:1:"°";s:2:"°";s:1:"±";s:2:"±";s:1:"²";s:3:"″";s:1:"³";s:3:"≥";s:1:"´";s:2:"×";s:1:"µ";s:3:"âˆ";s:1:"¶";s:3:"∂";s:1:"·";s:3:"•";s:1:"¸";s:2:"÷";s:1:"¹";s:3:"≠";s:1:"º";s:3:"≡";s:1:"»";s:3:"≈";s:1:"¼";s:3:"…";s:1:"½";s:3:"";s:1:"¾";s:3:"";s:1:"¿";s:3:"↵";s:1:"À";s:3:"ℵ";s:1:"Á";s:3:"â„‘";s:1:"Â";s:3:"ℜ";s:1:"Ã";s:3:"℘";s:1:"Ä";s:3:"⊗";s:1:"Å";s:3:"⊕";s:1:"Æ";s:3:"∅";s:1:"Ç";s:3:"∩";s:1:"È";s:3:"∪";s:1:"É";s:3:"⊃";s:1:"Ê";s:3:"⊇";s:1:"Ë";s:3:"⊄";s:1:"Ì";s:3:"⊂";s:1:"Í";s:3:"⊆";s:1:"Î";s:3:"∈";s:1:"Ï";s:3:"∉";s:1:"Ð";s:3:"∠";s:1:"Ñ";s:3:"∇";s:1:"Ò";s:3:"";s:1:"Ó";s:3:"ï›™";s:1:"Ô";s:3:"ï››";s:1:"Õ";s:3:"âˆ";s:1:"Ö";s:3:"√";s:1:"×";s:3:"â‹…";s:1:"Ø";s:2:"¬";s:1:"Ù";s:3:"∧";s:1:"Ú";s:3:"∨";s:1:"Û";s:3:"⇔";s:1:"Ü";s:3:"â‡";s:1:"Ý";s:3:"⇑";s:1:"Þ";s:3:"⇒";s:1:"ß";s:3:"⇓";s:1:"à";s:3:"â—Š";s:1:"á";s:3:"〈";s:1:"â";s:3:"";s:1:"ã";s:3:"";s:1:"ä";s:3:"";s:1:"å";s:3:"∑";s:1:"æ";s:3:"";s:1:"ç";s:3:"";s:1:"è";s:3:"";s:1:"é";s:3:"";s:1:"ê";s:3:"";s:1:"ë";s:3:"";s:1:"ì";s:3:"";s:1:"í";s:3:"";s:1:"î";s:3:"";s:1:"ï";s:3:"";s:1:"ñ";s:3:"〉";s:1:"ò";s:3:"∫";s:1:"ó";s:3:"⌠";s:1:"ô";s:3:"";s:1:"õ";s:3:"⌡";s:1:"ö";s:3:"";s:1:"÷";s:3:"";s:1:"ø";s:3:"";s:1:"ù";s:3:"";s:1:"ú";s:3:"";s:1:"û";s:3:"";s:1:"ü";s:3:"";s:1:"ý";s:3:"";s:1:"þ";s:3:"";}
    \ No newline at end of file
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.turkish.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.turkish.ser
    new file mode 100644
    index 0000000..a3651e6
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.turkish.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.us-ascii-quotes.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.us-ascii-quotes.ser
    new file mode 100644
    index 0000000..f10af33
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.us-ascii-quotes.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.us-ascii.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.us-ascii.ser
    new file mode 100644
    index 0000000..3a2f7e4
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.us-ascii.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1250.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1250.ser
    new file mode 100644
    index 0000000..9e799cb
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1250.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1251.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1251.ser
    new file mode 100644
    index 0000000..6592885
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1251.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1252.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1252.ser
    new file mode 100644
    index 0000000..cccc26c
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1252.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1253.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1253.ser
    new file mode 100644
    index 0000000..13c5a0b
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1253.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1254.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1254.ser
    new file mode 100644
    index 0000000..96d6972
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1254.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1255.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1255.ser
    new file mode 100644
    index 0000000..c366bfd
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1255.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1256.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1256.ser
    new file mode 100644
    index 0000000..cc98d2c
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1256.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1257.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1257.ser
    new file mode 100644
    index 0000000..2a52206
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1257.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1258.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1258.ser
    new file mode 100644
    index 0000000..114dd84
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.windows-1258.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.x-mac-ce.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.x-mac-ce.ser
    new file mode 100644
    index 0000000..246603d
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.x-mac-ce.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.x-mac-cyrillic.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.x-mac-cyrillic.ser
    new file mode 100644
    index 0000000..3f606d6
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.x-mac-cyrillic.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.x-mac-greek.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.x-mac-greek.ser
    new file mode 100644
    index 0000000..c4b66d9
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.x-mac-greek.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.x-mac-icelandic.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.x-mac-icelandic.ser
    new file mode 100644
    index 0000000..15b35b1
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.x-mac-icelandic.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.x-mac-roman.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.x-mac-roman.ser
    new file mode 100644
    index 0000000..a39e96a
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.x-mac-roman.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.zdingbat.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.zdingbat.ser
    new file mode 100644
    index 0000000..3a894d2
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/from.zdingbat.ser
    @@ -0,0 +1 @@
    +a:202:{s:1:" ";s:1:" ";s:1:"!";s:3:"âœ";s:1:""";s:3:"✂";s:1:"#";s:3:"✃";s:1:"$";s:3:"✄";s:1:"%";s:3:"☎";s:1:"&";s:3:"✆";s:1:"'";s:3:"✇";s:1:"(";s:3:"✈";s:1:")";s:3:"✉";s:1:"*";s:3:"☛";s:1:"+";s:3:"☞";s:1:",";s:3:"✌";s:1:"-";s:3:"âœ";s:1:".";s:3:"✎";s:1:"/";s:3:"âœ";i:0;s:3:"âœ";i:1;s:3:"✑";i:2;s:3:"✒";i:3;s:3:"✓";i:4;s:3:"✔";i:5;s:3:"✕";i:6;s:3:"✖";i:7;s:3:"✗";i:8;s:3:"✘";i:9;s:3:"✙";s:1:":";s:3:"✚";s:1:";";s:3:"✛";s:1:"<";s:3:"✜";s:1:"=";s:3:"âœ";s:1:">";s:3:"✞";s:1:"?";s:3:"✟";s:1:"@";s:3:"✠";s:1:"A";s:3:"✡";s:1:"B";s:3:"✢";s:1:"C";s:3:"✣";s:1:"D";s:3:"✤";s:1:"E";s:3:"✥";s:1:"F";s:3:"✦";s:1:"G";s:3:"✧";s:1:"H";s:3:"★";s:1:"I";s:3:"✩";s:1:"J";s:3:"✪";s:1:"K";s:3:"✫";s:1:"L";s:3:"✬";s:1:"M";s:3:"✭";s:1:"N";s:3:"✮";s:1:"O";s:3:"✯";s:1:"P";s:3:"✰";s:1:"Q";s:3:"✱";s:1:"R";s:3:"✲";s:1:"S";s:3:"✳";s:1:"T";s:3:"✴";s:1:"U";s:3:"✵";s:1:"V";s:3:"✶";s:1:"W";s:3:"✷";s:1:"X";s:3:"✸";s:1:"Y";s:3:"✹";s:1:"Z";s:3:"✺";s:1:"[";s:3:"✻";s:1:"\";s:3:"✼";s:1:"]";s:3:"✽";s:1:"^";s:3:"✾";s:1:"_";s:3:"✿";s:1:"`";s:3:"â€";s:1:"a";s:3:"â";s:1:"b";s:3:"â‚";s:1:"c";s:3:"âƒ";s:1:"d";s:3:"â„";s:1:"e";s:3:"â…";s:1:"f";s:3:"â†";s:1:"g";s:3:"â‡";s:1:"h";s:3:"âˆ";s:1:"i";s:3:"â‰";s:1:"j";s:3:"âŠ";s:1:"k";s:3:"â‹";s:1:"l";s:3:"â—";s:1:"m";s:3:"â";s:1:"n";s:3:"â– ";s:1:"o";s:3:"â";s:1:"p";s:3:"â";s:1:"q";s:3:"â‘";s:1:"r";s:3:"â’";s:1:"s";s:3:"â–²";s:1:"t";s:3:"â–¼";s:1:"u";s:3:"â—†";s:1:"v";s:3:"â–";s:1:"w";s:3:"â——";s:1:"x";s:3:"â˜";s:1:"y";s:3:"â™";s:1:"z";s:3:"âš";s:1:"{";s:3:"â›";s:1:"|";s:3:"âœ";s:1:"}";s:3:"â";s:1:"~";s:3:"âž";s:1:"€";s:3:"";s:1:"";s:3:"";s:1:"‚";s:3:"";s:1:"ƒ";s:3:"";s:1:"„";s:3:"";s:1:"…";s:3:"";s:1:"†";s:3:"ï£";s:1:"‡";s:3:"";s:1:"ˆ";s:3:"";s:1:"‰";s:3:"";s:1:"Š";s:3:"";s:1:"‹";s:3:"";s:1:"Œ";s:3:"";s:1:"";s:3:"";s:1:"¡";s:3:"â¡";s:1:"¢";s:3:"â¢";s:1:"£";s:3:"â£";s:1:"¤";s:3:"â¤";s:1:"¥";s:3:"â¥";s:1:"¦";s:3:"â¦";s:1:"§";s:3:"â§";s:1:"¨";s:3:"♣";s:1:"©";s:3:"♦";s:1:"ª";s:3:"♥";s:1:"«";s:3:"â™ ";s:1:"¬";s:3:"â‘ ";s:1:"­";s:3:"â‘¡";s:1:"®";s:3:"â‘¢";s:1:"¯";s:3:"â‘£";s:1:"°";s:3:"⑤";s:1:"±";s:3:"â‘¥";s:1:"²";s:3:"⑦";s:1:"³";s:3:"â‘§";s:1:"´";s:3:"⑨";s:1:"µ";s:3:"â‘©";s:1:"¶";s:3:"â¶";s:1:"·";s:3:"â·";s:1:"¸";s:3:"â¸";s:1:"¹";s:3:"â¹";s:1:"º";s:3:"âº";s:1:"»";s:3:"â»";s:1:"¼";s:3:"â¼";s:1:"½";s:3:"â½";s:1:"¾";s:3:"â¾";s:1:"¿";s:3:"â¿";s:1:"À";s:3:"➀";s:1:"Á";s:3:"âž";s:1:"Â";s:3:"âž‚";s:1:"Ã";s:3:"➃";s:1:"Ä";s:3:"âž„";s:1:"Å";s:3:"âž…";s:1:"Æ";s:3:"➆";s:1:"Ç";s:3:"➇";s:1:"È";s:3:"➈";s:1:"É";s:3:"➉";s:1:"Ê";s:3:"➊";s:1:"Ë";s:3:"âž‹";s:1:"Ì";s:3:"➌";s:1:"Í";s:3:"âž";s:1:"Î";s:3:"➎";s:1:"Ï";s:3:"âž";s:1:"Ð";s:3:"âž";s:1:"Ñ";s:3:"âž‘";s:1:"Ò";s:3:"âž’";s:1:"Ó";s:3:"âž“";s:1:"Ô";s:3:"âž”";s:1:"Õ";s:3:"→";s:1:"Ö";s:3:"↔";s:1:"×";s:3:"↕";s:1:"Ø";s:3:"➘";s:1:"Ù";s:3:"âž™";s:1:"Ú";s:3:"âžš";s:1:"Û";s:3:"âž›";s:1:"Ü";s:3:"âžœ";s:1:"Ý";s:3:"âž";s:1:"Þ";s:3:"âžž";s:1:"ß";s:3:"➟";s:1:"à";s:3:"âž ";s:1:"á";s:3:"âž¡";s:1:"â";s:3:"➢";s:1:"ã";s:3:"➣";s:1:"ä";s:3:"➤";s:1:"å";s:3:"➥";s:1:"æ";s:3:"➦";s:1:"ç";s:3:"âž§";s:1:"è";s:3:"➨";s:1:"é";s:3:"âž©";s:1:"ê";s:3:"➪";s:1:"ë";s:3:"âž«";s:1:"ì";s:3:"➬";s:1:"í";s:3:"âž­";s:1:"î";s:3:"âž®";s:1:"ï";s:3:"➯";s:1:"ñ";s:3:"âž±";s:1:"ò";s:3:"âž²";s:1:"ó";s:3:"âž³";s:1:"ô";s:3:"âž´";s:1:"õ";s:3:"âžµ";s:1:"ö";s:3:"âž¶";s:1:"÷";s:3:"âž·";s:1:"ø";s:3:"➸";s:1:"ù";s:3:"âž¹";s:1:"ú";s:3:"➺";s:1:"û";s:3:"âž»";s:1:"ü";s:3:"âž¼";s:1:"ý";s:3:"âž½";s:1:"þ";s:3:"âž¾";}
    \ No newline at end of file
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/to.gsm0338.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/to.gsm0338.ser
    new file mode 100644
    index 0000000..e675fc3
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/to.gsm0338.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/to.mazovia.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/to.mazovia.ser
    new file mode 100644
    index 0000000..70a534f
    Binary files /dev/null and b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/to.mazovia.ser differ
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/to.stdenc.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/to.stdenc.ser
    new file mode 100644
    index 0000000..0cb4628
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/to.stdenc.ser
    @@ -0,0 +1 @@
    +a:154:{s:2:" ";s:1:" ";s:2:"­";s:1:"-";s:3:"∕";s:1:"¤";s:3:"∙";s:1:"´";s:2:"ˉ";s:1:"Å";s:1:" ";s:1:" ";s:1:"!";s:1:"!";s:1:""";s:1:""";s:1:"#";s:1:"#";s:1:"$";s:1:"$";s:1:"%";s:1:"%";s:1:"&";s:1:"&";s:3:"’";s:1:"'";s:1:"(";s:1:"(";s:1:")";s:1:")";s:1:"*";s:1:"*";s:1:"+";s:1:"+";s:1:",";s:1:",";s:1:"-";s:1:"-";s:1:".";s:1:".";s:1:"/";s:1:"/";i:0;i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:7;i:7;i:8;i:8;i:9;i:9;s:1:":";s:1:":";s:1:";";s:1:";";s:1:"<";s:1:"<";s:1:"=";s:1:"=";s:1:">";s:1:">";s:1:"?";s:1:"?";s:1:"@";s:1:"@";s:1:"A";s:1:"A";s:1:"B";s:1:"B";s:1:"C";s:1:"C";s:1:"D";s:1:"D";s:1:"E";s:1:"E";s:1:"F";s:1:"F";s:1:"G";s:1:"G";s:1:"H";s:1:"H";s:1:"I";s:1:"I";s:1:"J";s:1:"J";s:1:"K";s:1:"K";s:1:"L";s:1:"L";s:1:"M";s:1:"M";s:1:"N";s:1:"N";s:1:"O";s:1:"O";s:1:"P";s:1:"P";s:1:"Q";s:1:"Q";s:1:"R";s:1:"R";s:1:"S";s:1:"S";s:1:"T";s:1:"T";s:1:"U";s:1:"U";s:1:"V";s:1:"V";s:1:"W";s:1:"W";s:1:"X";s:1:"X";s:1:"Y";s:1:"Y";s:1:"Z";s:1:"Z";s:1:"[";s:1:"[";s:1:"\";s:1:"\";s:1:"]";s:1:"]";s:1:"^";s:1:"^";s:1:"_";s:1:"_";s:3:"‘";s:1:"`";s:1:"a";s:1:"a";s:1:"b";s:1:"b";s:1:"c";s:1:"c";s:1:"d";s:1:"d";s:1:"e";s:1:"e";s:1:"f";s:1:"f";s:1:"g";s:1:"g";s:1:"h";s:1:"h";s:1:"i";s:1:"i";s:1:"j";s:1:"j";s:1:"k";s:1:"k";s:1:"l";s:1:"l";s:1:"m";s:1:"m";s:1:"n";s:1:"n";s:1:"o";s:1:"o";s:1:"p";s:1:"p";s:1:"q";s:1:"q";s:1:"r";s:1:"r";s:1:"s";s:1:"s";s:1:"t";s:1:"t";s:1:"u";s:1:"u";s:1:"v";s:1:"v";s:1:"w";s:1:"w";s:1:"x";s:1:"x";s:1:"y";s:1:"y";s:1:"z";s:1:"z";s:1:"{";s:1:"{";s:1:"|";s:1:"|";s:1:"}";s:1:"}";s:1:"~";s:1:"~";s:2:"¡";s:1:"¡";s:2:"¢";s:1:"¢";s:2:"£";s:1:"£";s:3:"â„";s:1:"¤";s:2:"Â¥";s:1:"¥";s:2:"Æ’";s:1:"¦";s:2:"§";s:1:"§";s:2:"¤";s:1:"¨";s:1:"'";s:1:"©";s:3:"“";s:1:"ª";s:2:"«";s:1:"«";s:3:"‹";s:1:"¬";s:3:"›";s:1:"­";s:3:"ï¬";s:1:"®";s:3:"fl";s:1:"¯";s:3:"–";s:1:"±";s:3:"†";s:1:"²";s:3:"‡";s:1:"³";s:2:"·";s:1:"´";s:2:"¶";s:1:"¶";s:3:"•";s:1:"·";s:3:"‚";s:1:"¸";s:3:"„";s:1:"¹";s:3:"â€";s:1:"º";s:2:"»";s:1:"»";s:3:"…";s:1:"¼";s:3:"‰";s:1:"½";s:2:"¿";s:1:"¿";s:1:"`";s:1:"Á";s:2:"´";s:1:"Â";s:2:"ˆ";s:1:"Ã";s:2:"Ëœ";s:1:"Ä";s:2:"¯";s:1:"Å";s:2:"˘";s:1:"Æ";s:2:"Ë™";s:1:"Ç";s:2:"¨";s:1:"È";s:2:"Ëš";s:1:"Ê";s:2:"¸";s:1:"Ë";s:2:"Ë";s:1:"Í";s:2:"Ë›";s:1:"Î";s:2:"ˇ";s:1:"Ï";s:3:"—";s:1:"Ð";s:2:"Æ";s:1:"á";s:2:"ª";s:1:"ã";s:2:"Å";s:1:"è";s:2:"Ø";s:1:"é";s:2:"Å’";s:1:"ê";s:2:"º";s:1:"ë";s:2:"æ";s:1:"ñ";s:2:"ı";s:1:"õ";s:2:"Å‚";s:1:"ø";s:2:"ø";s:1:"ù";s:2:"Å“";s:1:"ú";s:2:"ß";s:1:"û";}
    \ No newline at end of file
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/to.symbol.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/to.symbol.ser
    new file mode 100644
    index 0000000..fc61505
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/to.symbol.ser
    @@ -0,0 +1 @@
    +a:194:{s:2:" ";s:1:" ";s:3:"∆";s:1:"D";s:3:"Ω";s:1:"W";s:2:"μ";s:1:"m";s:3:"∕";s:1:"¤";s:1:" ";s:1:" ";s:1:"!";s:1:"!";s:3:"∀";s:1:""";s:1:"#";s:1:"#";s:3:"∃";s:1:"$";s:1:"%";s:1:"%";s:1:"&";s:1:"&";s:3:"∋";s:1:"'";s:1:"(";s:1:"(";s:1:")";s:1:")";s:3:"∗";s:1:"*";s:1:"+";s:1:"+";s:1:",";s:1:",";s:3:"−";s:1:"-";s:1:".";s:1:".";s:1:"/";s:1:"/";i:0;i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:7;i:7;i:8;i:8;i:9;i:9;s:1:":";s:1:":";s:1:";";s:1:";";s:1:"<";s:1:"<";s:1:"=";s:1:"=";s:1:">";s:1:">";s:1:"?";s:1:"?";s:3:"≅";s:1:"@";s:2:"Α";s:1:"A";s:2:"Î’";s:1:"B";s:2:"Χ";s:1:"C";s:2:"Δ";s:1:"D";s:2:"Ε";s:1:"E";s:2:"Φ";s:1:"F";s:2:"Γ";s:1:"G";s:2:"Η";s:1:"H";s:2:"Ι";s:1:"I";s:2:"Ï‘";s:1:"J";s:2:"Κ";s:1:"K";s:2:"Λ";s:1:"L";s:2:"Μ";s:1:"M";s:2:"Î";s:1:"N";s:2:"Ο";s:1:"O";s:2:"Π";s:1:"P";s:2:"Θ";s:1:"Q";s:2:"Ρ";s:1:"R";s:2:"Σ";s:1:"S";s:2:"Τ";s:1:"T";s:2:"Î¥";s:1:"U";s:2:"Ï‚";s:1:"V";s:2:"Ω";s:1:"W";s:2:"Ξ";s:1:"X";s:2:"Ψ";s:1:"Y";s:2:"Ζ";s:1:"Z";s:1:"[";s:1:"[";s:3:"∴";s:1:"\";s:1:"]";s:1:"]";s:3:"⊥";s:1:"^";s:1:"_";s:1:"_";s:3:"";s:1:"`";s:2:"α";s:1:"a";s:2:"β";s:1:"b";s:2:"χ";s:1:"c";s:2:"δ";s:1:"d";s:2:"ε";s:1:"e";s:2:"φ";s:1:"f";s:2:"γ";s:1:"g";s:2:"η";s:1:"h";s:2:"ι";s:1:"i";s:2:"Ï•";s:1:"j";s:2:"κ";s:1:"k";s:2:"λ";s:1:"l";s:2:"µ";s:1:"m";s:2:"ν";s:1:"n";s:2:"ο";s:1:"o";s:2:"Ï€";s:1:"p";s:2:"θ";s:1:"q";s:2:"Ï";s:1:"r";s:2:"σ";s:1:"s";s:2:"Ï„";s:1:"t";s:2:"Ï…";s:1:"u";s:2:"Ï–";s:1:"v";s:2:"ω";s:1:"w";s:2:"ξ";s:1:"x";s:2:"ψ";s:1:"y";s:2:"ζ";s:1:"z";s:1:"{";s:1:"{";s:1:"|";s:1:"|";s:1:"}";s:1:"}";s:3:"∼";s:1:"~";s:3:"€";s:1:" ";s:2:"Ï’";s:1:"¡";s:3:"′";s:1:"¢";s:3:"≤";s:1:"£";s:3:"â„";s:1:"¤";s:3:"∞";s:1:"¥";s:2:"Æ’";s:1:"¦";s:3:"♣";s:1:"§";s:3:"♦";s:1:"¨";s:3:"♥";s:1:"©";s:3:"â™ ";s:1:"ª";s:3:"↔";s:1:"«";s:3:"â†";s:1:"¬";s:3:"↑";s:1:"­";s:3:"→";s:1:"®";s:3:"↓";s:1:"¯";s:2:"°";s:1:"°";s:2:"±";s:1:"±";s:3:"″";s:1:"²";s:3:"≥";s:1:"³";s:2:"×";s:1:"´";s:3:"âˆ";s:1:"µ";s:3:"∂";s:1:"¶";s:3:"•";s:1:"·";s:2:"÷";s:1:"¸";s:3:"≠";s:1:"¹";s:3:"≡";s:1:"º";s:3:"≈";s:1:"»";s:3:"…";s:1:"¼";s:3:"";s:1:"½";s:3:"";s:1:"¾";s:3:"↵";s:1:"¿";s:3:"ℵ";s:1:"À";s:3:"â„‘";s:1:"Á";s:3:"ℜ";s:1:"Â";s:3:"℘";s:1:"Ã";s:3:"⊗";s:1:"Ä";s:3:"⊕";s:1:"Å";s:3:"∅";s:1:"Æ";s:3:"∩";s:1:"Ç";s:3:"∪";s:1:"È";s:3:"⊃";s:1:"É";s:3:"⊇";s:1:"Ê";s:3:"⊄";s:1:"Ë";s:3:"⊂";s:1:"Ì";s:3:"⊆";s:1:"Í";s:3:"∈";s:1:"Î";s:3:"∉";s:1:"Ï";s:3:"∠";s:1:"Ð";s:3:"∇";s:1:"Ñ";s:3:"";s:1:"Ò";s:3:"ï›™";s:1:"Ó";s:3:"ï››";s:1:"Ô";s:3:"âˆ";s:1:"Õ";s:3:"√";s:1:"Ö";s:3:"â‹…";s:1:"×";s:2:"¬";s:1:"Ø";s:3:"∧";s:1:"Ù";s:3:"∨";s:1:"Ú";s:3:"⇔";s:1:"Û";s:3:"â‡";s:1:"Ü";s:3:"⇑";s:1:"Ý";s:3:"⇒";s:1:"Þ";s:3:"⇓";s:1:"ß";s:3:"â—Š";s:1:"à";s:3:"〈";s:1:"á";s:3:"";s:1:"â";s:3:"";s:1:"ã";s:3:"";s:1:"ä";s:3:"∑";s:1:"å";s:3:"";s:1:"æ";s:3:"";s:1:"ç";s:3:"";s:1:"è";s:3:"";s:1:"é";s:3:"";s:1:"ê";s:3:"";s:1:"ë";s:3:"";s:1:"ì";s:3:"";s:1:"í";s:3:"";s:1:"î";s:3:"";s:1:"ï";s:3:"〉";s:1:"ñ";s:3:"∫";s:1:"ò";s:3:"⌠";s:1:"ó";s:3:"";s:1:"ô";s:3:"⌡";s:1:"õ";s:3:"";s:1:"ö";s:3:"";s:1:"÷";s:3:"";s:1:"ø";s:3:"";s:1:"ù";s:3:"";s:1:"ú";s:3:"";s:1:"û";s:3:"";s:1:"ü";s:3:"";s:1:"ý";s:3:"";s:1:"þ";}
    \ No newline at end of file
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/to.zdingbat.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/to.zdingbat.ser
    new file mode 100644
    index 0000000..1f293bf
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/to.zdingbat.ser
    @@ -0,0 +1 @@
    +a:203:{s:2:" ";s:1:" ";s:1:" ";s:1:" ";s:3:"âœ";s:1:"!";s:3:"✂";s:1:""";s:3:"✃";s:1:"#";s:3:"✄";s:1:"$";s:3:"☎";s:1:"%";s:3:"✆";s:1:"&";s:3:"✇";s:1:"'";s:3:"✈";s:1:"(";s:3:"✉";s:1:")";s:3:"☛";s:1:"*";s:3:"☞";s:1:"+";s:3:"✌";s:1:",";s:3:"âœ";s:1:"-";s:3:"✎";s:1:".";s:3:"âœ";s:1:"/";s:3:"âœ";i:0;s:3:"✑";i:1;s:3:"✒";i:2;s:3:"✓";i:3;s:3:"✔";i:4;s:3:"✕";i:5;s:3:"✖";i:6;s:3:"✗";i:7;s:3:"✘";i:8;s:3:"✙";i:9;s:3:"✚";s:1:":";s:3:"✛";s:1:";";s:3:"✜";s:1:"<";s:3:"âœ";s:1:"=";s:3:"✞";s:1:">";s:3:"✟";s:1:"?";s:3:"✠";s:1:"@";s:3:"✡";s:1:"A";s:3:"✢";s:1:"B";s:3:"✣";s:1:"C";s:3:"✤";s:1:"D";s:3:"✥";s:1:"E";s:3:"✦";s:1:"F";s:3:"✧";s:1:"G";s:3:"★";s:1:"H";s:3:"✩";s:1:"I";s:3:"✪";s:1:"J";s:3:"✫";s:1:"K";s:3:"✬";s:1:"L";s:3:"✭";s:1:"M";s:3:"✮";s:1:"N";s:3:"✯";s:1:"O";s:3:"✰";s:1:"P";s:3:"✱";s:1:"Q";s:3:"✲";s:1:"R";s:3:"✳";s:1:"S";s:3:"✴";s:1:"T";s:3:"✵";s:1:"U";s:3:"✶";s:1:"V";s:3:"✷";s:1:"W";s:3:"✸";s:1:"X";s:3:"✹";s:1:"Y";s:3:"✺";s:1:"Z";s:3:"✻";s:1:"[";s:3:"✼";s:1:"\";s:3:"✽";s:1:"]";s:3:"✾";s:1:"^";s:3:"✿";s:1:"_";s:3:"â€";s:1:"`";s:3:"â";s:1:"a";s:3:"â‚";s:1:"b";s:3:"âƒ";s:1:"c";s:3:"â„";s:1:"d";s:3:"â…";s:1:"e";s:3:"â†";s:1:"f";s:3:"â‡";s:1:"g";s:3:"âˆ";s:1:"h";s:3:"â‰";s:1:"i";s:3:"âŠ";s:1:"j";s:3:"â‹";s:1:"k";s:3:"â—";s:1:"l";s:3:"â";s:1:"m";s:3:"â– ";s:1:"n";s:3:"â";s:1:"o";s:3:"â";s:1:"p";s:3:"â‘";s:1:"q";s:3:"â’";s:1:"r";s:3:"â–²";s:1:"s";s:3:"â–¼";s:1:"t";s:3:"â—†";s:1:"u";s:3:"â–";s:1:"v";s:3:"â——";s:1:"w";s:3:"â˜";s:1:"x";s:3:"â™";s:1:"y";s:3:"âš";s:1:"z";s:3:"â›";s:1:"{";s:3:"âœ";s:1:"|";s:3:"â";s:1:"}";s:3:"âž";s:1:"~";s:3:"";s:1:"€";s:3:"";s:1:"";s:3:"";s:1:"‚";s:3:"";s:1:"ƒ";s:3:"";s:1:"„";s:3:"";s:1:"…";s:3:"ï£";s:1:"†";s:3:"";s:1:"‡";s:3:"";s:1:"ˆ";s:3:"";s:1:"‰";s:3:"";s:1:"Š";s:3:"";s:1:"‹";s:3:"";s:1:"Œ";s:3:"";s:1:"";s:3:"â¡";s:1:"¡";s:3:"â¢";s:1:"¢";s:3:"â£";s:1:"£";s:3:"â¤";s:1:"¤";s:3:"â¥";s:1:"¥";s:3:"â¦";s:1:"¦";s:3:"â§";s:1:"§";s:3:"♣";s:1:"¨";s:3:"♦";s:1:"©";s:3:"♥";s:1:"ª";s:3:"â™ ";s:1:"«";s:3:"â‘ ";s:1:"¬";s:3:"â‘¡";s:1:"­";s:3:"â‘¢";s:1:"®";s:3:"â‘£";s:1:"¯";s:3:"⑤";s:1:"°";s:3:"â‘¥";s:1:"±";s:3:"⑦";s:1:"²";s:3:"â‘§";s:1:"³";s:3:"⑨";s:1:"´";s:3:"â‘©";s:1:"µ";s:3:"â¶";s:1:"¶";s:3:"â·";s:1:"·";s:3:"â¸";s:1:"¸";s:3:"â¹";s:1:"¹";s:3:"âº";s:1:"º";s:3:"â»";s:1:"»";s:3:"â¼";s:1:"¼";s:3:"â½";s:1:"½";s:3:"â¾";s:1:"¾";s:3:"â¿";s:1:"¿";s:3:"➀";s:1:"À";s:3:"âž";s:1:"Á";s:3:"âž‚";s:1:"Â";s:3:"➃";s:1:"Ã";s:3:"âž„";s:1:"Ä";s:3:"âž…";s:1:"Å";s:3:"➆";s:1:"Æ";s:3:"➇";s:1:"Ç";s:3:"➈";s:1:"È";s:3:"➉";s:1:"É";s:3:"➊";s:1:"Ê";s:3:"âž‹";s:1:"Ë";s:3:"➌";s:1:"Ì";s:3:"âž";s:1:"Í";s:3:"➎";s:1:"Î";s:3:"âž";s:1:"Ï";s:3:"âž";s:1:"Ð";s:3:"âž‘";s:1:"Ñ";s:3:"âž’";s:1:"Ò";s:3:"âž“";s:1:"Ó";s:3:"âž”";s:1:"Ô";s:3:"→";s:1:"Õ";s:3:"↔";s:1:"Ö";s:3:"↕";s:1:"×";s:3:"➘";s:1:"Ø";s:3:"âž™";s:1:"Ù";s:3:"âžš";s:1:"Ú";s:3:"âž›";s:1:"Û";s:3:"âžœ";s:1:"Ü";s:3:"âž";s:1:"Ý";s:3:"âžž";s:1:"Þ";s:3:"➟";s:1:"ß";s:3:"âž ";s:1:"à";s:3:"âž¡";s:1:"á";s:3:"➢";s:1:"â";s:3:"➣";s:1:"ã";s:3:"➤";s:1:"ä";s:3:"➥";s:1:"å";s:3:"➦";s:1:"æ";s:3:"âž§";s:1:"ç";s:3:"➨";s:1:"è";s:3:"âž©";s:1:"é";s:3:"➪";s:1:"ê";s:3:"âž«";s:1:"ë";s:3:"➬";s:1:"ì";s:3:"âž­";s:1:"í";s:3:"âž®";s:1:"î";s:3:"➯";s:1:"ï";s:3:"âž±";s:1:"ñ";s:3:"âž²";s:1:"ò";s:3:"âž³";s:1:"ó";s:3:"âž´";s:1:"ô";s:3:"âžµ";s:1:"õ";s:3:"âž¶";s:1:"ö";s:3:"âž·";s:1:"÷";s:3:"➸";s:1:"ø";s:3:"âž¹";s:1:"ù";s:3:"➺";s:1:"ú";s:3:"âž»";s:1:"û";s:3:"âž¼";s:1:"ü";s:3:"âž½";s:1:"ý";s:3:"âž¾";s:1:"þ";}
    \ No newline at end of file
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/translit.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/translit.ser
    new file mode 100644
    index 0000000..3fd8411
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/charset/translit.ser
    @@ -0,0 +1 @@
    +a:3959:{s:2:"µ";s:2:"μ";s:2:"¼";s:7:" 1â„4 ";s:2:"½";s:7:" 1â„2 ";s:2:"¾";s:7:" 3â„4 ";s:2:"IJ";s:2:"IJ";s:2:"ij";s:2:"ij";s:2:"Ä¿";s:3:"L·";s:2:"Å€";s:3:"l·";s:2:"ʼn";s:3:"ʼn";s:2:"Å¿";s:1:"s";s:2:"Ç„";s:3:"DŽ";s:2:"Ç…";s:3:"Dž";s:2:"dž";s:3:"dž";s:2:"LJ";s:2:"LJ";s:2:"Lj";s:2:"Lj";s:2:"lj";s:2:"lj";s:2:"ÇŠ";s:2:"NJ";s:2:"Ç‹";s:2:"Nj";s:2:"ÇŒ";s:2:"nj";s:2:"DZ";s:2:"DZ";s:2:"Dz";s:2:"Dz";s:2:"dz";s:2:"dz";s:2:"Ï";s:2:"β";s:2:"Ï‘";s:2:"θ";s:2:"Ï’";s:2:"Î¥";s:2:"Ï•";s:2:"φ";s:2:"Ï–";s:2:"Ï€";s:2:"ϰ";s:2:"κ";s:2:"ϱ";s:2:"Ï";s:2:"ϲ";s:2:"Ï‚";s:2:"Ï´";s:2:"Θ";s:2:"ϵ";s:2:"ε";s:2:"Ϲ";s:2:"Σ";s:2:"Ö‡";s:4:"Õ¥Ö‚";s:2:"Ùµ";s:4:"اٴ";s:2:"Ù¶";s:4:"وٴ";s:2:"Ù·";s:4:"Û‡Ù´";s:2:"Ù¸";s:4:"يٴ";s:3:"ำ";s:6:"à¹à¸²";s:3:"ຳ";s:6:"à»àº²";s:3:"ໜ";s:6:"ຫນ";s:3:"à»";s:6:"ຫມ";s:3:"ཷ";s:6:"ྲà¾";s:3:"ཹ";s:6:"ླà¾";s:3:"ẚ";s:3:"aʾ";s:3:"․";s:1:".";s:3:"‥";s:2:"..";s:3:"…";s:3:"...";s:3:"″";s:6:"′′";s:3:"‴";s:9:"′′′";s:3:"‶";s:6:"‵‵";s:3:"‷";s:9:"‵‵‵";s:3:"‼";s:2:"!!";s:3:"â‡";s:2:"??";s:3:"âˆ";s:2:"?!";s:3:"â‰";s:2:"!?";s:3:"â—";s:12:"′′′′";s:3:"₨";s:2:"Rs";s:3:"â„€";s:3:"a/c";s:3:"â„";s:3:"a/s";s:3:"â„‚";s:1:"C";s:3:"℃";s:3:"°C";s:3:"â„…";s:3:"c/o";s:3:"℆";s:3:"c/u";s:3:"ℇ";s:2:"Æ";s:3:"℉";s:3:"°F";s:3:"ℊ";s:1:"g";s:3:"â„‹";s:1:"H";s:3:"ℌ";s:1:"H";s:3:"â„";s:1:"H";s:3:"ℎ";s:1:"h";s:3:"â„";s:2:"ħ";s:3:"â„";s:1:"I";s:3:"â„‘";s:1:"I";s:3:"â„’";s:1:"L";s:3:"â„“";s:1:"l";s:3:"â„•";s:1:"N";s:3:"â„–";s:2:"No";s:3:"â„™";s:1:"P";s:3:"ℚ";s:1:"Q";s:3:"â„›";s:1:"R";s:3:"ℜ";s:1:"R";s:3:"â„";s:1:"R";s:3:"â„¡";s:3:"TEL";s:3:"ℤ";s:1:"Z";s:3:"ℨ";s:1:"Z";s:3:"ℬ";s:1:"B";s:3:"â„­";s:1:"C";s:3:"ℯ";s:1:"e";s:3:"â„°";s:1:"E";s:3:"ℱ";s:1:"F";s:3:"ℳ";s:1:"M";s:3:"â„´";s:1:"o";s:3:"ℵ";s:2:"×";s:3:"â„¶";s:2:"ב";s:3:"â„·";s:2:"×’";s:3:"ℸ";s:2:"ד";s:3:"ℹ";s:1:"i";s:3:"â„»";s:3:"FAX";s:3:"ℼ";s:2:"Ï€";s:3:"ℽ";s:2:"γ";s:3:"ℾ";s:2:"Γ";s:3:"â„¿";s:2:"Π";s:3:"â…€";s:3:"∑";s:3:"â……";s:1:"D";s:3:"â…†";s:1:"d";s:3:"â…‡";s:1:"e";s:3:"â…ˆ";s:1:"i";s:3:"â…‰";s:1:"j";s:3:"â…";s:7:" 1â„7 ";s:3:"â…‘";s:7:" 1â„9 ";s:3:"â…’";s:8:" 1â„10 ";s:3:"â…“";s:7:" 1â„3 ";s:3:"â…”";s:7:" 2â„3 ";s:3:"â…•";s:7:" 1â„5 ";s:3:"â…–";s:7:" 2â„5 ";s:3:"â…—";s:7:" 3â„5 ";s:3:"â…˜";s:7:" 4â„5 ";s:3:"â…™";s:7:" 1â„6 ";s:3:"â…š";s:7:" 5â„6 ";s:3:"â…›";s:7:" 1â„8 ";s:3:"â…œ";s:7:" 3â„8 ";s:3:"â…";s:7:" 5â„8 ";s:3:"â…ž";s:7:" 7â„8 ";s:3:"â…Ÿ";s:6:" 1â„ ";s:3:"â… ";s:1:"I";s:3:"â…¡";s:2:"II";s:3:"â…¢";s:3:"III";s:3:"â…£";s:2:"IV";s:3:"â…¤";s:1:"V";s:3:"â…¥";s:2:"VI";s:3:"â…¦";s:3:"VII";s:3:"â…§";s:4:"VIII";s:3:"â…¨";s:2:"IX";s:3:"â…©";s:1:"X";s:3:"â…ª";s:2:"XI";s:3:"â…«";s:3:"XII";s:3:"â…¬";s:1:"L";s:3:"â…­";s:1:"C";s:3:"â…®";s:1:"D";s:3:"â…¯";s:1:"M";s:3:"â…°";s:1:"i";s:3:"â…±";s:2:"ii";s:3:"â…²";s:3:"iii";s:3:"â…³";s:2:"iv";s:3:"â…´";s:1:"v";s:3:"â…µ";s:2:"vi";s:3:"â…¶";s:3:"vii";s:3:"â…·";s:4:"viii";s:3:"â…¸";s:2:"ix";s:3:"â…¹";s:1:"x";s:3:"â…º";s:2:"xi";s:3:"â…»";s:3:"xii";s:3:"â…¼";s:1:"l";s:3:"â…½";s:1:"c";s:3:"â…¾";s:1:"d";s:3:"â…¿";s:1:"m";s:3:"↉";s:7:" 0â„3 ";s:3:"∬";s:6:"∫∫";s:3:"∭";s:9:"∫∫∫";s:3:"∯";s:6:"∮∮";s:3:"∰";s:9:"∮∮∮";s:3:"â‘ ";s:3:"(1)";s:3:"â‘¡";s:3:"(2)";s:3:"â‘¢";s:3:"(3)";s:3:"â‘£";s:3:"(4)";s:3:"⑤";s:3:"(5)";s:3:"â‘¥";s:3:"(6)";s:3:"⑦";s:3:"(7)";s:3:"â‘§";s:3:"(8)";s:3:"⑨";s:3:"(9)";s:3:"â‘©";s:4:"(10)";s:3:"⑪";s:4:"(11)";s:3:"â‘«";s:4:"(12)";s:3:"⑬";s:4:"(13)";s:3:"â‘­";s:4:"(14)";s:3:"â‘®";s:4:"(15)";s:3:"⑯";s:4:"(16)";s:3:"â‘°";s:4:"(17)";s:3:"⑱";s:4:"(18)";s:3:"⑲";s:4:"(19)";s:3:"⑳";s:4:"(20)";s:3:"â‘´";s:3:"(1)";s:3:"⑵";s:3:"(2)";s:3:"â‘¶";s:3:"(3)";s:3:"â‘·";s:3:"(4)";s:3:"⑸";s:3:"(5)";s:3:"⑹";s:3:"(6)";s:3:"⑺";s:3:"(7)";s:3:"â‘»";s:3:"(8)";s:3:"⑼";s:3:"(9)";s:3:"⑽";s:4:"(10)";s:3:"⑾";s:4:"(11)";s:3:"â‘¿";s:4:"(12)";s:3:"â’€";s:4:"(13)";s:3:"â’";s:4:"(14)";s:3:"â’‚";s:4:"(15)";s:3:"â’ƒ";s:4:"(16)";s:3:"â’„";s:4:"(17)";s:3:"â’…";s:4:"(18)";s:3:"â’†";s:4:"(19)";s:3:"â’‡";s:4:"(20)";s:3:"â’ˆ";s:2:"1.";s:3:"â’‰";s:2:"2.";s:3:"â’Š";s:2:"3.";s:3:"â’‹";s:2:"4.";s:3:"â’Œ";s:2:"5.";s:3:"â’";s:2:"6.";s:3:"â’Ž";s:2:"7.";s:3:"â’";s:2:"8.";s:3:"â’";s:2:"9.";s:3:"â’‘";s:3:"10.";s:3:"â’’";s:3:"11.";s:3:"â’“";s:3:"12.";s:3:"â’”";s:3:"13.";s:3:"â’•";s:3:"14.";s:3:"â’–";s:3:"15.";s:3:"â’—";s:3:"16.";s:3:"â’˜";s:3:"17.";s:3:"â’™";s:3:"18.";s:3:"â’š";s:3:"19.";s:3:"â’›";s:3:"20.";s:3:"â’œ";s:3:"(a)";s:3:"â’";s:3:"(b)";s:3:"â’ž";s:3:"(c)";s:3:"â’Ÿ";s:3:"(d)";s:3:"â’ ";s:3:"(e)";s:3:"â’¡";s:3:"(f)";s:3:"â’¢";s:3:"(g)";s:3:"â’£";s:3:"(h)";s:3:"â’¤";s:3:"(i)";s:3:"â’¥";s:3:"(j)";s:3:"â’¦";s:3:"(k)";s:3:"â’§";s:3:"(l)";s:3:"â’¨";s:3:"(m)";s:3:"â’©";s:3:"(n)";s:3:"â’ª";s:3:"(o)";s:3:"â’«";s:3:"(p)";s:3:"â’¬";s:3:"(q)";s:3:"â’­";s:3:"(r)";s:3:"â’®";s:3:"(s)";s:3:"â’¯";s:3:"(t)";s:3:"â’°";s:3:"(u)";s:3:"â’±";s:3:"(v)";s:3:"â’²";s:3:"(w)";s:3:"â’³";s:3:"(x)";s:3:"â’´";s:3:"(y)";s:3:"â’µ";s:3:"(z)";s:3:"â’¶";s:3:"(A)";s:3:"â’·";s:3:"(B)";s:3:"â’¸";s:3:"(C)";s:3:"â’¹";s:3:"(D)";s:3:"â’º";s:3:"(E)";s:3:"â’»";s:3:"(F)";s:3:"â’¼";s:3:"(G)";s:3:"â’½";s:3:"(H)";s:3:"â’¾";s:3:"(I)";s:3:"â’¿";s:3:"(J)";s:3:"â“€";s:3:"(K)";s:3:"â“";s:3:"(L)";s:3:"â“‚";s:3:"(M)";s:3:"Ⓝ";s:3:"(N)";s:3:"â“„";s:3:"(O)";s:3:"â“…";s:3:"(P)";s:3:"Ⓠ";s:3:"(Q)";s:3:"Ⓡ";s:3:"(R)";s:3:"Ⓢ";s:3:"(S)";s:3:"Ⓣ";s:3:"(T)";s:3:"Ⓤ";s:3:"(U)";s:3:"â“‹";s:3:"(V)";s:3:"Ⓦ";s:3:"(W)";s:3:"â“";s:3:"(X)";s:3:"Ⓨ";s:3:"(Y)";s:3:"â“";s:3:"(Z)";s:3:"â“";s:3:"(a)";s:3:"â“‘";s:3:"(b)";s:3:"â“’";s:3:"(c)";s:3:"â““";s:3:"(d)";s:3:"â“”";s:3:"(e)";s:3:"â“•";s:3:"(f)";s:3:"â“–";s:3:"(g)";s:3:"â“—";s:3:"(h)";s:3:"ⓘ";s:3:"(i)";s:3:"â“™";s:3:"(j)";s:3:"ⓚ";s:3:"(k)";s:3:"â“›";s:3:"(l)";s:3:"ⓜ";s:3:"(m)";s:3:"â“";s:3:"(n)";s:3:"ⓞ";s:3:"(o)";s:3:"ⓟ";s:3:"(p)";s:3:"â“ ";s:3:"(q)";s:3:"â“¡";s:3:"(r)";s:3:"â“¢";s:3:"(s)";s:3:"â“£";s:3:"(t)";s:3:"ⓤ";s:3:"(u)";s:3:"â“¥";s:3:"(v)";s:3:"ⓦ";s:3:"(w)";s:3:"â“§";s:3:"(x)";s:3:"ⓨ";s:3:"(y)";s:3:"â“©";s:3:"(z)";s:3:"⓪";s:3:"(0)";s:3:"⨌";s:12:"∫∫∫∫";s:3:"â©´";s:3:"::=";s:3:"⩵";s:2:"==";s:3:"â©¶";s:3:"===";s:3:"⺟";s:3:"æ¯";s:3:"⻳";s:3:"龟";s:3:"â¼€";s:3:"一";s:3:"â¼";s:3:"丨";s:3:"⼂";s:3:"丶";s:3:"⼃";s:3:"丿";s:3:"⼄";s:3:"ä¹™";s:3:"â¼…";s:3:"亅";s:3:"⼆";s:3:"二";s:3:"⼇";s:3:"亠";s:3:"⼈";s:3:"人";s:3:"⼉";s:3:"å„¿";s:3:"⼊";s:3:"å…¥";s:3:"⼋";s:3:"å…«";s:3:"⼌";s:3:"冂";s:3:"â¼";s:3:"冖";s:3:"⼎";s:3:"冫";s:3:"â¼";s:3:"几";s:3:"â¼";s:3:"凵";s:3:"⼑";s:3:"刀";s:3:"â¼’";s:3:"力";s:3:"⼓";s:3:"勹";s:3:"â¼”";s:3:"匕";s:3:"⼕";s:3:"匚";s:3:"â¼–";s:3:"匸";s:3:"â¼—";s:3:"å";s:3:"⼘";s:3:"åœ";s:3:"â¼™";s:3:"å©";s:3:"⼚";s:3:"厂";s:3:"â¼›";s:3:"厶";s:3:"⼜";s:3:"åˆ";s:3:"â¼";s:3:"å£";s:3:"⼞";s:3:"å›—";s:3:"⼟";s:3:"土";s:3:"â¼ ";s:3:"士";s:3:"⼡";s:3:"夂";s:3:"â¼¢";s:3:"夊";s:3:"â¼£";s:3:"夕";s:3:"⼤";s:3:"大";s:3:"â¼¥";s:3:"女";s:3:"⼦";s:3:"å­";s:3:"â¼§";s:3:"宀";s:3:"⼨";s:3:"寸";s:3:"⼩";s:3:"å°";s:3:"⼪";s:3:"å°¢";s:3:"⼫";s:3:"å°¸";s:3:"⼬";s:3:"å±®";s:3:"â¼­";s:3:"å±±";s:3:"â¼®";s:3:"å·›";s:3:"⼯";s:3:"å·¥";s:3:"â¼°";s:3:"å·±";s:3:"â¼±";s:3:"å·¾";s:3:"â¼²";s:3:"å¹²";s:3:"â¼³";s:3:"幺";s:3:"â¼´";s:3:"广";s:3:"â¼µ";s:3:"å»´";s:3:"â¼¶";s:3:"廾";s:3:"â¼·";s:3:"弋";s:3:"⼸";s:3:"弓";s:3:"â¼¹";s:3:"å½";s:3:"⼺";s:3:"彡";s:3:"â¼»";s:3:"å½³";s:3:"â¼¼";s:3:"心";s:3:"â¼½";s:3:"戈";s:3:"â¼¾";s:3:"戶";s:3:"⼿";s:3:"手";s:3:"â½€";s:3:"支";s:3:"â½";s:3:"æ”´";s:3:"⽂";s:3:"æ–‡";s:3:"⽃";s:3:"æ–—";s:3:"⽄";s:3:"æ–¤";s:3:"â½…";s:3:"æ–¹";s:3:"⽆";s:3:"æ— ";s:3:"⽇";s:3:"æ—¥";s:3:"⽈";s:3:"æ›°";s:3:"⽉";s:3:"月";s:3:"⽊";s:3:"木";s:3:"⽋";s:3:"欠";s:3:"⽌";s:3:"æ­¢";s:3:"â½";s:3:"æ­¹";s:3:"⽎";s:3:"殳";s:3:"â½";s:3:"毋";s:3:"â½";s:3:"比";s:3:"⽑";s:3:"毛";s:3:"â½’";s:3:"æ°";s:3:"⽓";s:3:"æ°”";s:3:"â½”";s:3:"æ°´";s:3:"⽕";s:3:"ç«";s:3:"â½–";s:3:"爪";s:3:"â½—";s:3:"父";s:3:"⽘";s:3:"爻";s:3:"â½™";s:3:"爿";s:3:"⽚";s:3:"片";s:3:"â½›";s:3:"牙";s:3:"⽜";s:3:"牛";s:3:"â½";s:3:"犬";s:3:"⽞";s:3:"玄";s:3:"⽟";s:3:"玉";s:3:"â½ ";s:3:"瓜";s:3:"⽡";s:3:"瓦";s:3:"â½¢";s:3:"甘";s:3:"â½£";s:3:"生";s:3:"⽤";s:3:"用";s:3:"â½¥";s:3:"ç”°";s:3:"⽦";s:3:"ç–‹";s:3:"â½§";s:3:"ç–’";s:3:"⽨";s:3:"ç™¶";s:3:"⽩";s:3:"白";s:3:"⽪";s:3:"çš®";s:3:"⽫";s:3:"çš¿";s:3:"⽬";s:3:"ç›®";s:3:"â½­";s:3:"矛";s:3:"â½®";s:3:"矢";s:3:"⽯";s:3:"石";s:3:"â½°";s:3:"示";s:3:"â½±";s:3:"禸";s:3:"â½²";s:3:"禾";s:3:"â½³";s:3:"ç©´";s:3:"â½´";s:3:"ç«‹";s:3:"â½µ";s:3:"竹";s:3:"â½¶";s:3:"ç±³";s:3:"â½·";s:3:"糸";s:3:"⽸";s:3:"ç¼¶";s:3:"â½¹";s:3:"网";s:3:"⽺";s:3:"羊";s:3:"â½»";s:3:"ç¾½";s:3:"â½¼";s:3:"è€";s:3:"â½½";s:3:"而";s:3:"â½¾";s:3:"耒";s:3:"⽿";s:3:"耳";s:3:"â¾€";s:3:"è¿";s:3:"â¾";s:3:"肉";s:3:"⾂";s:3:"臣";s:3:"⾃";s:3:"自";s:3:"⾄";s:3:"至";s:3:"â¾…";s:3:"臼";s:3:"⾆";s:3:"舌";s:3:"⾇";s:3:"舛";s:3:"⾈";s:3:"舟";s:3:"⾉";s:3:"艮";s:3:"⾊";s:3:"色";s:3:"⾋";s:3:"艸";s:3:"⾌";s:3:"è™";s:3:"â¾";s:3:"虫";s:3:"⾎";s:3:"è¡€";s:3:"â¾";s:3:"行";s:3:"â¾";s:3:"è¡£";s:3:"⾑";s:3:"襾";s:3:"â¾’";s:3:"見";s:3:"⾓";s:3:"è§’";s:3:"â¾”";s:3:"言";s:3:"⾕";s:3:"è°·";s:3:"â¾–";s:3:"豆";s:3:"â¾—";s:3:"豕";s:3:"⾘";s:3:"豸";s:3:"â¾™";s:3:"è²";s:3:"⾚";s:3:"赤";s:3:"â¾›";s:3:"èµ°";s:3:"⾜";s:3:"è¶³";s:3:"â¾";s:3:"身";s:3:"⾞";s:3:"車";s:3:"⾟";s:3:"è¾›";s:3:"â¾ ";s:3:"è¾°";s:3:"⾡";s:3:"è¾µ";s:3:"â¾¢";s:3:"é‚‘";s:3:"â¾£";s:3:"é…‰";s:3:"⾤";s:3:"釆";s:3:"â¾¥";s:3:"里";s:3:"⾦";s:3:"金";s:3:"â¾§";s:3:"é•·";s:3:"⾨";s:3:"é–€";s:3:"⾩";s:3:"阜";s:3:"⾪";s:3:"éš¶";s:3:"⾫";s:3:"éš¹";s:3:"⾬";s:3:"雨";s:3:"â¾­";s:3:"é‘";s:3:"â¾®";s:3:"éž";s:3:"⾯";s:3:"é¢";s:3:"â¾°";s:3:"é©";s:3:"â¾±";s:3:"韋";s:3:"â¾²";s:3:"韭";s:3:"â¾³";s:3:"音";s:3:"â¾´";s:3:"é ";s:3:"â¾µ";s:3:"風";s:3:"â¾¶";s:3:"飛";s:3:"â¾·";s:3:"食";s:3:"⾸";s:3:"首";s:3:"â¾¹";s:3:"香";s:3:"⾺";s:3:"馬";s:3:"â¾»";s:3:"骨";s:3:"â¾¼";s:3:"高";s:3:"â¾½";s:3:"髟";s:3:"â¾¾";s:3:"鬥";s:3:"⾿";s:3:"鬯";s:3:"â¿€";s:3:"鬲";s:3:"â¿";s:3:"鬼";s:3:"â¿‚";s:3:"é­š";s:3:"⿃";s:3:"é³¥";s:3:"â¿„";s:3:"é¹µ";s:3:"â¿…";s:3:"鹿";s:3:"⿆";s:3:"麥";s:3:"⿇";s:3:"麻";s:3:"⿈";s:3:"黃";s:3:"⿉";s:3:"é»";s:3:"⿊";s:3:"黑";s:3:"â¿‹";s:3:"黹";s:3:"⿌";s:3:"黽";s:3:"â¿";s:3:"鼎";s:3:"⿎";s:3:"鼓";s:3:"â¿";s:3:"é¼ ";s:3:"â¿";s:3:"é¼»";s:3:"â¿‘";s:3:"齊";s:3:"â¿’";s:3:"é½’";s:3:"â¿“";s:3:"é¾";s:3:"â¿”";s:3:"龜";s:3:"â¿•";s:3:"é¾ ";s:3:" ";s:1:" ";s:3:"〶";s:3:"〒";s:3:"〸";s:3:"å";s:3:"〹";s:3:"å„";s:3:"〺";s:3:"å…";s:3:"ㄱ";s:3:"á„€";s:3:"ㄲ";s:3:"á„";s:3:"ㄳ";s:3:"ᆪ";s:3:"ã„´";s:3:"á„‚";s:3:"ㄵ";s:3:"ᆬ";s:3:"ã„¶";s:3:"ᆭ";s:3:"ã„·";s:3:"ᄃ";s:3:"ㄸ";s:3:"á„„";s:3:"ㄹ";s:3:"á„…";s:3:"ㄺ";s:3:"ᆰ";s:3:"ã„»";s:3:"ᆱ";s:3:"ㄼ";s:3:"ᆲ";s:3:"ㄽ";s:3:"ᆳ";s:3:"ㄾ";s:3:"ᆴ";s:3:"ã„¿";s:3:"ᆵ";s:3:"ã…€";s:3:"ᄚ";s:3:"ã…";s:3:"ᄆ";s:3:"ã…‚";s:3:"ᄇ";s:3:"ã…ƒ";s:3:"ᄈ";s:3:"ã…„";s:3:"á„¡";s:3:"ã……";s:3:"ᄉ";s:3:"ã…†";s:3:"ᄊ";s:3:"ã…‡";s:3:"á„‹";s:3:"ã…ˆ";s:3:"ᄌ";s:3:"ã…‰";s:3:"á„";s:3:"ã…Š";s:3:"ᄎ";s:3:"ã…‹";s:3:"á„";s:3:"ã…Œ";s:3:"á„";s:3:"ã…";s:3:"á„‘";s:3:"ã…Ž";s:3:"á„’";s:3:"ã…";s:3:"á…¡";s:3:"ã…";s:3:"á…¢";s:3:"ã…‘";s:3:"á…£";s:3:"ã…’";s:3:"á…¤";s:3:"ã…“";s:3:"á…¥";s:3:"ã…”";s:3:"á…¦";s:3:"ã…•";s:3:"á…§";s:3:"ã…–";s:3:"á…¨";s:3:"ã…—";s:3:"á…©";s:3:"ã…˜";s:3:"á…ª";s:3:"ã…™";s:3:"á…«";s:3:"ã…š";s:3:"á…¬";s:3:"ã…›";s:3:"á…­";s:3:"ã…œ";s:3:"á…®";s:3:"ã…";s:3:"á…¯";s:3:"ã…ž";s:3:"á…°";s:3:"ã…Ÿ";s:3:"á…±";s:3:"ã… ";s:3:"á…²";s:3:"ã…¡";s:3:"á…³";s:3:"ã…¢";s:3:"á…´";s:3:"ã…£";s:3:"á…µ";s:3:"ã…¤";s:3:"á… ";s:3:"ã…¥";s:3:"á„”";s:3:"ã…¦";s:3:"á„•";s:3:"ã…§";s:3:"ᇇ";s:3:"ã…¨";s:3:"ᇈ";s:3:"ã…©";s:3:"ᇌ";s:3:"ã…ª";s:3:"ᇎ";s:3:"ã…«";s:3:"ᇓ";s:3:"ã…¬";s:3:"ᇗ";s:3:"ã…­";s:3:"ᇙ";s:3:"ã…®";s:3:"ᄜ";s:3:"ã…¯";s:3:"á‡";s:3:"ã…°";s:3:"ᇟ";s:3:"ã…±";s:3:"á„";s:3:"ã…²";s:3:"ᄞ";s:3:"ã…³";s:3:"á„ ";s:3:"ã…´";s:3:"á„¢";s:3:"ã…µ";s:3:"á„£";s:3:"ã…¶";s:3:"á„§";s:3:"ã…·";s:3:"á„©";s:3:"ã…¸";s:3:"á„«";s:3:"ã…¹";s:3:"ᄬ";s:3:"ã…º";s:3:"á„­";s:3:"ã…»";s:3:"á„®";s:3:"ã…¼";s:3:"ᄯ";s:3:"ã…½";s:3:"ᄲ";s:3:"ã…¾";s:3:"á„¶";s:3:"ã…¿";s:3:"á…€";s:3:"ㆀ";s:3:"á…‡";s:3:"ã†";s:3:"á…Œ";s:3:"ㆂ";s:3:"ᇱ";s:3:"ㆃ";s:3:"ᇲ";s:3:"ㆄ";s:3:"á…—";s:3:"ㆅ";s:3:"á…˜";s:3:"ㆆ";s:3:"á…™";s:3:"ㆇ";s:3:"ᆄ";s:3:"ㆈ";s:3:"ᆅ";s:3:"ㆉ";s:3:"ᆈ";s:3:"ㆊ";s:3:"ᆑ";s:3:"ㆋ";s:3:"ᆒ";s:3:"ㆌ";s:3:"ᆔ";s:3:"ã†";s:3:"ᆞ";s:3:"ㆎ";s:3:"ᆡ";s:3:"㈀";s:5:"(á„€)";s:3:"ãˆ";s:5:"(á„‚)";s:3:"㈂";s:5:"(ᄃ)";s:3:"㈃";s:5:"(á„…)";s:3:"㈄";s:5:"(ᄆ)";s:3:"㈅";s:5:"(ᄇ)";s:3:"㈆";s:5:"(ᄉ)";s:3:"㈇";s:5:"(á„‹)";s:3:"㈈";s:5:"(ᄌ)";s:3:"㈉";s:5:"(ᄎ)";s:3:"㈊";s:5:"(á„)";s:3:"㈋";s:5:"(á„)";s:3:"㈌";s:5:"(á„‘)";s:3:"ãˆ";s:5:"(á„’)";s:3:"㈎";s:8:"(가)";s:3:"ãˆ";s:8:"(á„‚á…¡)";s:3:"ãˆ";s:8:"(다)";s:3:"㈑";s:8:"(á„…á…¡)";s:3:"㈒";s:8:"(마)";s:3:"㈓";s:8:"(바)";s:3:"㈔";s:8:"(사)";s:3:"㈕";s:8:"(á„‹á…¡)";s:3:"㈖";s:8:"(자)";s:3:"㈗";s:8:"(차)";s:3:"㈘";s:8:"(á„á…¡)";s:3:"㈙";s:8:"(á„á…¡)";s:3:"㈚";s:8:"(á„‘á…¡)";s:3:"㈛";s:8:"(á„’á…¡)";s:3:"㈜";s:8:"(주)";s:3:"ãˆ";s:17:"(오전)";s:3:"㈞";s:14:"(á„‹á…©á„’á…®)";s:3:"㈠";s:5:"(一)";s:3:"㈡";s:5:"(二)";s:3:"㈢";s:5:"(三)";s:3:"㈣";s:5:"(å››)";s:3:"㈤";s:5:"(五)";s:3:"㈥";s:5:"(å…­)";s:3:"㈦";s:5:"(七)";s:3:"㈧";s:5:"(å…«)";s:3:"㈨";s:5:"(ä¹)";s:3:"㈩";s:5:"(å)";s:3:"㈪";s:5:"(月)";s:3:"㈫";s:5:"(ç«)";s:3:"㈬";s:5:"(æ°´)";s:3:"㈭";s:5:"(木)";s:3:"㈮";s:5:"(金)";s:3:"㈯";s:5:"(土)";s:3:"㈰";s:5:"(æ—¥)";s:3:"㈱";s:5:"(æ ª)";s:3:"㈲";s:5:"(有)";s:3:"㈳";s:5:"(社)";s:3:"㈴";s:5:"(å)";s:3:"㈵";s:5:"(特)";s:3:"㈶";s:5:"(財)";s:3:"㈷";s:5:"(ç¥)";s:3:"㈸";s:5:"(労)";s:3:"㈹";s:5:"(代)";s:3:"㈺";s:5:"(呼)";s:3:"㈻";s:5:"(å­¦)";s:3:"㈼";s:5:"(監)";s:3:"㈽";s:5:"(ä¼)";s:3:"㈾";s:5:"(資)";s:3:"㈿";s:5:"(å”)";s:3:"㉀";s:5:"(祭)";s:3:"ã‰";s:5:"(休)";s:3:"㉂";s:5:"(自)";s:3:"㉃";s:5:"(至)";s:3:"㉄";s:5:"(å•)";s:3:"㉅";s:5:"(å¹¼)";s:3:"㉆";s:5:"(æ–‡)";s:3:"㉇";s:5:"(ç®)";s:3:"ã‰";s:3:"PTE";s:3:"㉑";s:4:"(21)";s:3:"㉒";s:4:"(22)";s:3:"㉓";s:4:"(23)";s:3:"㉔";s:4:"(24)";s:3:"㉕";s:4:"(25)";s:3:"㉖";s:4:"(26)";s:3:"㉗";s:4:"(27)";s:3:"㉘";s:4:"(28)";s:3:"㉙";s:4:"(29)";s:3:"㉚";s:4:"(30)";s:3:"㉛";s:4:"(31)";s:3:"㉜";s:4:"(32)";s:3:"ã‰";s:4:"(33)";s:3:"㉞";s:4:"(34)";s:3:"㉟";s:4:"(35)";s:3:"㉠";s:5:"(á„€)";s:3:"㉡";s:5:"(á„‚)";s:3:"㉢";s:5:"(ᄃ)";s:3:"㉣";s:5:"(á„…)";s:3:"㉤";s:5:"(ᄆ)";s:3:"㉥";s:5:"(ᄇ)";s:3:"㉦";s:5:"(ᄉ)";s:3:"㉧";s:5:"(á„‹)";s:3:"㉨";s:5:"(ᄌ)";s:3:"㉩";s:5:"(ᄎ)";s:3:"㉪";s:5:"(á„)";s:3:"㉫";s:5:"(á„)";s:3:"㉬";s:5:"(á„‘)";s:3:"㉭";s:5:"(á„’)";s:3:"㉮";s:8:"(가)";s:3:"㉯";s:8:"(á„‚á…¡)";s:3:"㉰";s:8:"(다)";s:3:"㉱";s:8:"(á„…á…¡)";s:3:"㉲";s:8:"(마)";s:3:"㉳";s:8:"(바)";s:3:"㉴";s:8:"(사)";s:3:"㉵";s:8:"(á„‹á…¡)";s:3:"㉶";s:8:"(자)";s:3:"㉷";s:8:"(차)";s:3:"㉸";s:8:"(á„á…¡)";s:3:"㉹";s:8:"(á„á…¡)";s:3:"㉺";s:8:"(á„‘á…¡)";s:3:"㉻";s:8:"(á„’á…¡)";s:3:"㉼";s:17:"(참고)";s:3:"㉽";s:14:"(주의)";s:3:"㉾";s:8:"(á„‹á…®)";s:3:"㊀";s:5:"(一)";s:3:"ãŠ";s:5:"(二)";s:3:"㊂";s:5:"(三)";s:3:"㊃";s:5:"(å››)";s:3:"㊄";s:5:"(五)";s:3:"㊅";s:5:"(å…­)";s:3:"㊆";s:5:"(七)";s:3:"㊇";s:5:"(å…«)";s:3:"㊈";s:5:"(ä¹)";s:3:"㊉";s:5:"(å)";s:3:"㊊";s:5:"(月)";s:3:"㊋";s:5:"(ç«)";s:3:"㊌";s:5:"(æ°´)";s:3:"ãŠ";s:5:"(木)";s:3:"㊎";s:5:"(金)";s:3:"ãŠ";s:5:"(土)";s:3:"ãŠ";s:5:"(æ—¥)";s:3:"㊑";s:5:"(æ ª)";s:3:"㊒";s:5:"(有)";s:3:"㊓";s:5:"(社)";s:3:"㊔";s:5:"(å)";s:3:"㊕";s:5:"(特)";s:3:"㊖";s:5:"(財)";s:3:"㊗";s:5:"(ç¥)";s:3:"㊘";s:5:"(労)";s:3:"㊙";s:5:"(秘)";s:3:"㊚";s:5:"(ç”·)";s:3:"㊛";s:5:"(女)";s:3:"㊜";s:5:"(é©)";s:3:"ãŠ";s:5:"(優)";s:3:"㊞";s:5:"(å°)";s:3:"㊟";s:5:"(注)";s:3:"㊠";s:5:"(é …)";s:3:"㊡";s:5:"(休)";s:3:"㊢";s:5:"(写)";s:3:"㊣";s:5:"(æ­£)";s:3:"㊤";s:5:"(上)";s:3:"㊥";s:5:"(中)";s:3:"㊦";s:5:"(下)";s:3:"㊧";s:5:"(å·¦)";s:3:"㊨";s:5:"(å³)";s:3:"㊩";s:5:"(医)";s:3:"㊪";s:5:"(å®—)";s:3:"㊫";s:5:"(å­¦)";s:3:"㊬";s:5:"(監)";s:3:"㊭";s:5:"(ä¼)";s:3:"㊮";s:5:"(資)";s:3:"㊯";s:5:"(å”)";s:3:"㊰";s:5:"(夜)";s:3:"㊱";s:4:"(36)";s:3:"㊲";s:4:"(37)";s:3:"㊳";s:4:"(38)";s:3:"㊴";s:4:"(39)";s:3:"㊵";s:4:"(40)";s:3:"㊶";s:4:"(41)";s:3:"㊷";s:4:"(42)";s:3:"㊸";s:4:"(43)";s:3:"㊹";s:4:"(44)";s:3:"㊺";s:4:"(45)";s:3:"㊻";s:4:"(46)";s:3:"㊼";s:4:"(47)";s:3:"㊽";s:4:"(48)";s:3:"㊾";s:4:"(49)";s:3:"㊿";s:4:"(50)";s:3:"ã‹€";s:4:"1月";s:3:"ã‹";s:4:"2月";s:3:"ã‹‚";s:4:"3月";s:3:"㋃";s:4:"4月";s:3:"ã‹„";s:4:"5月";s:3:"ã‹…";s:4:"6月";s:3:"㋆";s:4:"7月";s:3:"㋇";s:4:"8月";s:3:"㋈";s:4:"9月";s:3:"㋉";s:5:"10月";s:3:"㋊";s:5:"11月";s:3:"ã‹‹";s:5:"12月";s:3:"㋌";s:2:"Hg";s:3:"ã‹";s:3:"erg";s:3:"㋎";s:2:"eV";s:3:"ã‹";s:3:"LTD";s:3:"ã‹";s:5:"(ã‚¢)";s:3:"ã‹‘";s:5:"(イ)";s:3:"ã‹’";s:5:"(ウ)";s:3:"ã‹“";s:5:"(エ)";s:3:"ã‹”";s:5:"(オ)";s:3:"ã‹•";s:5:"(ã‚«)";s:3:"ã‹–";s:5:"(ã‚­)";s:3:"ã‹—";s:5:"(ク)";s:3:"㋘";s:5:"(ケ)";s:3:"ã‹™";s:5:"(コ)";s:3:"㋚";s:5:"(サ)";s:3:"ã‹›";s:5:"(ã‚·)";s:3:"㋜";s:5:"(ス)";s:3:"ã‹";s:5:"(ã‚»)";s:3:"㋞";s:5:"(ソ)";s:3:"㋟";s:5:"(ã‚¿)";s:3:"ã‹ ";s:5:"(ãƒ)";s:3:"ã‹¡";s:5:"(ツ)";s:3:"ã‹¢";s:5:"(テ)";s:3:"ã‹£";s:5:"(ト)";s:3:"㋤";s:5:"(ナ)";s:3:"ã‹¥";s:5:"(ニ)";s:3:"㋦";s:5:"(ヌ)";s:3:"ã‹§";s:5:"(ãƒ)";s:3:"㋨";s:5:"(ノ)";s:3:"ã‹©";s:5:"(ãƒ)";s:3:"㋪";s:5:"(ヒ)";s:3:"ã‹«";s:5:"(フ)";s:3:"㋬";s:5:"(ヘ)";s:3:"ã‹­";s:5:"(ホ)";s:3:"ã‹®";s:5:"(マ)";s:3:"㋯";s:5:"(ミ)";s:3:"ã‹°";s:5:"(ム)";s:3:"㋱";s:5:"(メ)";s:3:"㋲";s:5:"(モ)";s:3:"㋳";s:5:"(ヤ)";s:3:"ã‹´";s:5:"(ユ)";s:3:"㋵";s:5:"(ヨ)";s:3:"ã‹¶";s:5:"(ラ)";s:3:"ã‹·";s:5:"(リ)";s:3:"㋸";s:5:"(ル)";s:3:"㋹";s:5:"(レ)";s:3:"㋺";s:5:"(ロ)";s:3:"ã‹»";s:5:"(ワ)";s:3:"㋼";s:5:"(ヰ)";s:3:"㋽";s:5:"(ヱ)";s:3:"㋾";s:5:"(ヲ)";s:3:"㌀";s:12:"アパート";s:3:"ãŒ";s:12:"アルファ";s:3:"㌂";s:12:"アンペア";s:3:"㌃";s:9:"アール";s:3:"㌄";s:12:"イニング";s:3:"㌅";s:9:"インãƒ";s:3:"㌆";s:9:"ウォン";s:3:"㌇";s:15:"エスクード";s:3:"㌈";s:12:"エーカー";s:3:"㌉";s:9:"オンス";s:3:"㌊";s:9:"オーム";s:3:"㌋";s:9:"カイリ";s:3:"㌌";s:12:"カラット";s:3:"ãŒ";s:12:"カロリー";s:3:"㌎";s:9:"ガロン";s:3:"ãŒ";s:9:"ガンマ";s:3:"ãŒ";s:6:"ギガ";s:3:"㌑";s:9:"ギニー";s:3:"㌒";s:12:"キュリー";s:3:"㌓";s:12:"ギルダー";s:3:"㌔";s:6:"キロ";s:3:"㌕";s:15:"キログラム";s:3:"㌖";s:18:"キロメートル";s:3:"㌗";s:15:"キロワット";s:3:"㌘";s:9:"グラム";s:3:"㌙";s:15:"グラムトン";s:3:"㌚";s:15:"クルゼイロ";s:3:"㌛";s:12:"クローãƒ";s:3:"㌜";s:9:"ケース";s:3:"ãŒ";s:9:"コルナ";s:3:"㌞";s:9:"コーãƒ";s:3:"㌟";s:12:"サイクル";s:3:"㌠";s:15:"サンãƒãƒ¼ãƒ ";s:3:"㌡";s:12:"シリング";s:3:"㌢";s:9:"センãƒ";s:3:"㌣";s:9:"セント";s:3:"㌤";s:9:"ダース";s:3:"㌥";s:6:"デシ";s:3:"㌦";s:6:"ドル";s:3:"㌧";s:6:"トン";s:3:"㌨";s:6:"ナノ";s:3:"㌩";s:9:"ノット";s:3:"㌪";s:9:"ãƒã‚¤ãƒ„";s:3:"㌫";s:15:"パーセント";s:3:"㌬";s:9:"パーツ";s:3:"㌭";s:12:"ãƒãƒ¼ãƒ¬ãƒ«";s:3:"㌮";s:15:"ピアストル";s:3:"㌯";s:9:"ピクル";s:3:"㌰";s:6:"ピコ";s:3:"㌱";s:6:"ビル";s:3:"㌲";s:15:"ファラッド";s:3:"㌳";s:12:"フィート";s:3:"㌴";s:15:"ブッシェル";s:3:"㌵";s:9:"フラン";s:3:"㌶";s:15:"ヘクタール";s:3:"㌷";s:6:"ペソ";s:3:"㌸";s:9:"ペニヒ";s:3:"㌹";s:9:"ヘルツ";s:3:"㌺";s:9:"ペンス";s:3:"㌻";s:9:"ページ";s:3:"㌼";s:9:"ベータ";s:3:"㌽";s:12:"ãƒã‚¤ãƒ³ãƒˆ";s:3:"㌾";s:9:"ボルト";s:3:"㌿";s:6:"ホン";s:3:"ã€";s:9:"ãƒãƒ³ãƒ‰";s:3:"ã";s:9:"ホール";s:3:"ã‚";s:9:"ホーン";s:3:"ãƒ";s:12:"マイクロ";s:3:"ã„";s:9:"マイル";s:3:"ã…";s:9:"マッãƒ";s:3:"ã†";s:9:"マルク";s:3:"ã‡";s:15:"マンション";s:3:"ãˆ";s:12:"ミクロン";s:3:"ã‰";s:6:"ミリ";s:3:"ãŠ";s:15:"ミリãƒãƒ¼ãƒ«";s:3:"ã‹";s:6:"メガ";s:3:"ãŒ";s:12:"メガトン";s:3:"ã";s:12:"メートル";s:3:"ãŽ";s:9:"ヤード";s:3:"ã";s:9:"ヤール";s:3:"ã";s:9:"ユアン";s:3:"ã‘";s:12:"リットル";s:3:"ã’";s:6:"リラ";s:3:"ã“";s:9:"ルピー";s:3:"ã”";s:12:"ルーブル";s:3:"ã•";s:6:"レム";s:3:"ã–";s:15:"レントゲン";s:3:"ã—";s:9:"ワット";s:3:"ã˜";s:4:"0点";s:3:"ã™";s:4:"1点";s:3:"ãš";s:4:"2点";s:3:"ã›";s:4:"3点";s:3:"ãœ";s:4:"4点";s:3:"ã";s:4:"5点";s:3:"ãž";s:4:"6点";s:3:"ãŸ";s:4:"7点";s:3:"ã ";s:4:"8点";s:3:"ã¡";s:4:"9点";s:3:"ã¢";s:5:"10点";s:3:"ã£";s:5:"11点";s:3:"ã¤";s:5:"12点";s:3:"ã¥";s:5:"13点";s:3:"ã¦";s:5:"14点";s:3:"ã§";s:5:"15点";s:3:"ã¨";s:5:"16点";s:3:"ã©";s:5:"17点";s:3:"ãª";s:5:"18点";s:3:"ã«";s:5:"19点";s:3:"ã¬";s:5:"20点";s:3:"ã­";s:5:"21点";s:3:"ã®";s:5:"22点";s:3:"ã¯";s:5:"23点";s:3:"ã°";s:5:"24点";s:3:"ã±";s:3:"hPa";s:3:"ã²";s:2:"da";s:3:"ã³";s:2:"AU";s:3:"ã´";s:3:"bar";s:3:"ãµ";s:2:"oV";s:3:"ã¶";s:2:"pc";s:3:"ã·";s:2:"dm";s:3:"ã¸";s:4:"dm²";s:3:"ã¹";s:4:"dm³";s:3:"ãº";s:2:"IU";s:3:"ã»";s:6:"å¹³æˆ";s:3:"ã¼";s:6:"昭和";s:3:"ã½";s:6:"大正";s:3:"ã¾";s:6:"明治";s:3:"ã¿";s:12:"æ ªå¼ä¼šç¤¾";s:3:"㎀";s:2:"pA";s:3:"ãŽ";s:2:"nA";s:3:"㎂";s:3:"μA";s:3:"㎃";s:2:"mA";s:3:"㎄";s:2:"kA";s:3:"㎅";s:2:"KB";s:3:"㎆";s:2:"MB";s:3:"㎇";s:2:"GB";s:3:"㎈";s:3:"cal";s:3:"㎉";s:4:"kcal";s:3:"㎊";s:2:"pF";s:3:"㎋";s:2:"nF";s:3:"㎌";s:3:"μF";s:3:"ãŽ";s:3:"μg";s:3:"㎎";s:2:"mg";s:3:"ãŽ";s:2:"kg";s:3:"ãŽ";s:2:"Hz";s:3:"㎑";s:3:"kHz";s:3:"㎒";s:3:"MHz";s:3:"㎓";s:3:"GHz";s:3:"㎔";s:3:"THz";s:3:"㎕";s:5:"μℓ";s:3:"㎖";s:4:"mâ„“";s:3:"㎗";s:4:"dâ„“";s:3:"㎘";s:4:"kâ„“";s:3:"㎙";s:2:"fm";s:3:"㎚";s:2:"nm";s:3:"㎛";s:3:"μm";s:3:"㎜";s:2:"mm";s:3:"ãŽ";s:2:"cm";s:3:"㎞";s:2:"km";s:3:"㎟";s:4:"mm²";s:3:"㎠";s:4:"cm²";s:3:"㎡";s:3:"m²";s:3:"㎢";s:4:"km²";s:3:"㎣";s:4:"mm³";s:3:"㎤";s:4:"cm³";s:3:"㎥";s:3:"m³";s:3:"㎦";s:4:"km³";s:3:"㎧";s:5:"m∕s";s:3:"㎨";s:7:"m∕s²";s:3:"㎩";s:2:"Pa";s:3:"㎪";s:3:"kPa";s:3:"㎫";s:3:"MPa";s:3:"㎬";s:3:"GPa";s:3:"㎭";s:3:"rad";s:3:"㎮";s:7:"rad∕s";s:3:"㎯";s:9:"rad∕s²";s:3:"㎰";s:2:"ps";s:3:"㎱";s:2:"ns";s:3:"㎲";s:3:"μs";s:3:"㎳";s:2:"ms";s:3:"㎴";s:2:"pV";s:3:"㎵";s:2:"nV";s:3:"㎶";s:3:"μV";s:3:"㎷";s:2:"mV";s:3:"㎸";s:2:"kV";s:3:"㎹";s:2:"MV";s:3:"㎺";s:2:"pW";s:3:"㎻";s:2:"nW";s:3:"㎼";s:3:"μW";s:3:"㎽";s:2:"mW";s:3:"㎾";s:2:"kW";s:3:"㎿";s:2:"MW";s:3:"ã€";s:3:"kΩ";s:3:"ã";s:3:"MΩ";s:3:"ã‚";s:4:"a.m.";s:3:"ãƒ";s:2:"Bq";s:3:"ã„";s:2:"cc";s:3:"ã…";s:2:"cd";s:3:"ã†";s:6:"C∕kg";s:3:"ã‡";s:3:"Co.";s:3:"ãˆ";s:2:"dB";s:3:"ã‰";s:2:"Gy";s:3:"ãŠ";s:2:"ha";s:3:"ã‹";s:2:"HP";s:3:"ãŒ";s:2:"in";s:3:"ã";s:2:"KK";s:3:"ãŽ";s:2:"KM";s:3:"ã";s:2:"kt";s:3:"ã";s:2:"lm";s:3:"ã‘";s:2:"ln";s:3:"ã’";s:3:"log";s:3:"ã“";s:2:"lx";s:3:"ã”";s:2:"mb";s:3:"ã•";s:3:"mil";s:3:"ã–";s:3:"mol";s:3:"ã—";s:2:"PH";s:3:"ã˜";s:4:"p.m.";s:3:"ã™";s:3:"PPM";s:3:"ãš";s:2:"PR";s:3:"ã›";s:2:"sr";s:3:"ãœ";s:2:"Sv";s:3:"ã";s:2:"Wb";s:3:"ãž";s:5:"V∕m";s:3:"ãŸ";s:5:"A∕m";s:3:"ã ";s:4:"1æ—¥";s:3:"ã¡";s:4:"2æ—¥";s:3:"ã¢";s:4:"3æ—¥";s:3:"ã£";s:4:"4æ—¥";s:3:"ã¤";s:4:"5æ—¥";s:3:"ã¥";s:4:"6æ—¥";s:3:"ã¦";s:4:"7æ—¥";s:3:"ã§";s:4:"8æ—¥";s:3:"ã¨";s:4:"9æ—¥";s:3:"ã©";s:5:"10æ—¥";s:3:"ãª";s:5:"11æ—¥";s:3:"ã«";s:5:"12æ—¥";s:3:"ã¬";s:5:"13æ—¥";s:3:"ã­";s:5:"14æ—¥";s:3:"ã®";s:5:"15æ—¥";s:3:"ã¯";s:5:"16æ—¥";s:3:"ã°";s:5:"17æ—¥";s:3:"ã±";s:5:"18æ—¥";s:3:"ã²";s:5:"19æ—¥";s:3:"ã³";s:5:"20æ—¥";s:3:"ã´";s:5:"21æ—¥";s:3:"ãµ";s:5:"22æ—¥";s:3:"ã¶";s:5:"23æ—¥";s:3:"ã·";s:5:"24æ—¥";s:3:"ã¸";s:5:"25æ—¥";s:3:"ã¹";s:5:"26æ—¥";s:3:"ãº";s:5:"27æ—¥";s:3:"ã»";s:5:"28æ—¥";s:3:"ã¼";s:5:"29æ—¥";s:3:"ã½";s:5:"30æ—¥";s:3:"ã¾";s:5:"31æ—¥";s:3:"ã¿";s:3:"gal";s:3:"豈";s:3:"豈";s:3:"ï¤";s:3:"æ›´";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"å¥";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"å–‡";s:3:"奈";s:3:"奈";s:3:"ï¤";s:3:"懶";s:3:"癩";s:3:"癩";s:3:"ï¤";s:3:"ç¾…";s:3:"ï¤";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"é‚";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"æ´›";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"çž";s:3:"落";s:3:"è½";s:3:"酪";s:3:"é…ª";s:3:"駱";s:3:"é§±";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"åµ";s:3:"ï¤";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"åµ";s:3:"濫";s:3:"æ¿«";s:3:"藍";s:3:"è—";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"è Ÿ";s:3:"廊";s:3:"廊";s:3:"朗";s:3:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"æ“„";s:3:"櫓";s:3:"æ«“";s:3:"爐";s:3:"çˆ";s:3:"盧";s:3:"ç›§";s:3:"老";s:3:"è€";s:3:"蘆";s:3:"蘆";s:3:"虜";s:3:"虜";s:3:"路";s:3:"è·¯";s:3:"露";s:3:"露";s:3:"魯";s:3:"é­¯";s:3:"鷺";s:3:"é·º";s:3:"碌";s:3:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"ç¶ ";s:3:"菉";s:3:"è‰";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"ï¥";s:3:"è«–";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"ç± ";s:3:"聾";s:3:"è¾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"é›·";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"å±¢";s:3:"樓";s:3:"樓";s:3:"ï¥";s:3:"æ·š";s:3:"漏";s:3:"æ¼";s:3:"ï¥";s:3:"ç´¯";s:3:"ï¥";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"å‹’";s:3:"肋";s:3:"è‚‹";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"ç¶¾";s:3:"菱";s:3:"è±";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"æ‹";s:3:"樂";s:3:"樂";s:3:"ï¥";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:3:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:3:"ç•°";s:3:"北";s:3:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"ä¸";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"ç´¢";s:3:"參";s:3:"åƒ";s:3:"塞";s:3:"塞";s:3:"省";s:3:"çœ";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:3:"殺";s:3:"辰";s:3:"è¾°";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:3:"è‹¥";s:3:"掠";s:3:"掠";s:3:"略";s:3:"ç•¥";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"å…©";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"æ¢";s:3:"糧";s:3:"ç³§";s:3:"良";s:3:"良";s:3:"諒";s:3:"è«’";s:3:"量";s:3:"é‡";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"å‘‚";s:3:"ï¦";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"æ—…";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"é–­";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"æ­·";s:3:"ï¦";s:3:"è½¢";s:3:"年";s:3:"å¹´";s:3:"ï¦";s:3:"æ†";s:3:"ï¦";s:3:"戀";s:3:"撚";s:3:"æ’š";s:3:"漣";s:3:"æ¼£";s:3:"煉";s:3:"ç…‰";s:3:"璉";s:3:"ç’‰";s:3:"秊";s:3:"ç§Š";s:3:"練";s:3:"ç·´";s:3:"聯";s:3:"è¯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"è“®";s:3:"連";s:3:"連";s:3:"鍊";s:3:"éŠ";s:3:"列";s:3:"列";s:3:"ï¦";s:3:"劣";s:3:"咽";s:3:"å’½";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"說";s:3:"說";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"æ»";s:3:"殮";s:3:"æ®®";s:3:"簾";s:3:"ç°¾";s:3:"獵";s:3:"çµ";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"寧";s:3:"寧";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"ç‘©";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"è†";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"é›¶";s:3:"靈";s:3:"éˆ";s:3:"領";s:3:"é ˜";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"å°¿";s:3:"料";s:3:"æ–™";s:3:"樂";s:3:"樂";s:3:"ï§€";s:3:"燎";s:3:"ï§";s:3:"療";s:3:"ï§‚";s:3:"蓼";s:3:"遼";s:3:"é¼";s:3:"ï§„";s:3:"é¾";s:3:"ï§…";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"æ»";s:3:"柳";s:3:"柳";s:3:"ï§Š";s:3:"æµ";s:3:"ï§‹";s:3:"溜";s:3:"ï§Œ";s:3:"ç‰";s:3:"ï§";s:3:"ç•™";s:3:"ï§Ž";s:3:"ç¡«";s:3:"ï§";s:3:"ç´";s:3:"ï§";s:3:"類";s:3:"ï§‘";s:3:"å…­";s:3:"ï§’";s:3:"戮";s:3:"ï§“";s:3:"陸";s:3:"ï§”";s:3:"倫";s:3:"ï§•";s:3:"å´™";s:3:"ï§–";s:3:"æ·ª";s:3:"ï§—";s:3:"輪";s:3:"律";s:3:"律";s:3:"ï§™";s:3:"æ…„";s:3:"ï§š";s:3:"æ —";s:3:"ï§›";s:3:"率";s:3:"ï§œ";s:3:"隆";s:3:"ï§";s:3:"利";s:3:"ï§ž";s:3:"å";s:3:"ï§Ÿ";s:3:"å±¥";s:3:"ï§ ";s:3:"易";s:3:"ï§¡";s:3:"æŽ";s:3:"ï§¢";s:3:"梨";s:3:"ï§£";s:3:"æ³¥";s:3:"理";s:3:"ç†";s:3:"ï§¥";s:3:"ç—¢";s:3:"罹";s:3:"ç½¹";s:3:"ï§§";s:3:"è£";s:3:"裡";s:3:"裡";s:3:"ï§©";s:3:"里";s:3:"離";s:3:"離";s:3:"ï§«";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"ï§­";s:3:"å";s:3:"ï§®";s:3:"ç‡";s:3:"璘";s:3:"ç’˜";s:3:"ï§°";s:3:"è—º";s:3:"ï§±";s:3:"隣";s:3:"ï§²";s:3:"é±—";s:3:"ï§³";s:3:"麟";s:3:"ï§´";s:3:"æž—";s:3:"ï§µ";s:3:"æ·‹";s:3:"ï§¶";s:3:"臨";s:3:"ï§·";s:3:"ç«‹";s:3:"笠";s:3:"笠";s:3:"ï§¹";s:3:"ç²’";s:3:"狀";s:3:"ç‹€";s:3:"ï§»";s:3:"ç‚™";s:3:"ï§¼";s:3:"è­˜";s:3:"ï§½";s:3:"什";s:3:"ï§¾";s:3:"茶";s:3:"ï§¿";s:3:"刺";s:3:"切";s:3:"切";s:3:"ï¨";s:3:"度";s:3:"拓";s:3:"æ‹“";s:3:"糖";s:3:"ç³–";s:3:"宅";s:3:"å®…";s:3:"洞";s:3:"æ´ž";s:3:"暴";s:3:"æš´";s:3:"輻";s:3:"è¼»";s:3:"行";s:3:"行";s:3:"降";s:3:"é™";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"å…€";s:3:"ï¨";s:3:"å—€";s:3:"﨎";s:1:"";s:3:"ï¨";s:1:"";s:3:"ï¨";s:3:"塚";s:3:"﨑";s:1:"";s:3:"晴";s:3:"æ™´";s:3:"﨓";s:1:"";s:3:"﨔";s:1:"";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:3:"ç¦";s:3:"靖";s:3:"é–";s:3:"ï¨";s:3:"ç²¾";s:3:"羽";s:3:"ç¾½";s:3:"﨟";s:1:"";s:3:"蘒";s:3:"蘒";s:3:"﨡";s:1:"";s:3:"諸";s:3:"諸";s:3:"﨣";s:1:"";s:3:"﨤";s:1:"";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"﨧";s:1:"";s:3:"﨨";s:1:"";s:3:"﨩";s:1:"";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"é¶´";s:3:"郞";s:3:"郞";s:3:"隷";s:3:"éš·";s:3:"侮";s:3:"ä¾®";s:3:"僧";s:3:"僧";s:3:"免";s:3:"å…";s:3:"勉";s:3:"勉";s:3:"勤";s:3:"勤";s:3:"卑";s:3:"å‘";s:3:"喝";s:3:"å–";s:3:"嘆";s:3:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"å¡€";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:3:"å±®";s:3:"悔";s:3:"æ‚”";s:3:"慨";s:3:"æ…¨";s:3:"憎";s:3:"憎";s:3:"ï©€";s:3:"懲";s:3:"ï©";s:3:"æ•";s:3:"ï©‚";s:3:"æ—¢";s:3:"暑";s:3:"æš‘";s:3:"ï©„";s:3:"梅";s:3:"ï©…";s:3:"æµ·";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"æ¼¢";s:3:"煮";s:3:"ç…®";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"ç¢";s:3:"ï©‹";s:3:"碑";s:3:"社";s:3:"社";s:3:"ï©";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"ï©";s:3:"ç¥";s:3:"ï©";s:3:"祖";s:3:"ï©‘";s:3:"ç¥";s:3:"ï©’";s:3:"ç¦";s:3:"ï©“";s:3:"禎";s:3:"ï©”";s:3:"ç©€";s:3:"ï©•";s:3:"çª";s:3:"ï©–";s:3:"節";s:3:"ï©—";s:3:"ç·´";s:3:"縉";s:3:"縉";s:3:"ï©™";s:3:"ç¹";s:3:"署";s:3:"ç½²";s:3:"ï©›";s:3:"者";s:3:"臭";s:3:"臭";s:3:"ï©";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"著";s:3:"è‘—";s:3:"ï© ";s:3:"è¤";s:3:"ï©¡";s:3:"視";s:3:"ï©¢";s:3:"è¬";s:3:"ï©£";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"ï©¥";s:3:"è´ˆ";s:3:"辶";s:3:"è¾¶";s:3:"ï©§";s:3:"逸";s:3:"難";s:3:"難";s:3:"ï©©";s:3:"響";s:3:"頻";s:3:"é »";s:3:"ï©«";s:3:"æµ";s:3:"𤋮";s:4:"𤋮";s:3:"ï©­";s:3:"舘";s:3:"ï©°";s:3:"並";s:3:"况";s:3:"况";s:3:"全";s:3:"å…¨";s:3:"侀";s:3:"ä¾€";s:3:"ï©´";s:3:"å……";s:3:"冀";s:3:"冀";s:3:"ï©¶";s:3:"勇";s:3:"ï©·";s:3:"勺";s:3:"喝";s:3:"å–";s:3:"啕";s:3:"å••";s:3:"喙";s:3:"å–™";s:3:"ï©»";s:3:"å—¢";s:3:"塚";s:3:"塚";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"ï©¿";s:3:"奔";s:3:"婢";s:3:"å©¢";s:3:"ïª";s:3:"嬨";s:3:"廒";s:3:"å»’";s:3:"廙";s:3:"å»™";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"å¾­";s:3:"惘";s:3:"惘";s:3:"慎";s:3:"æ…Ž";s:3:"愈";s:3:"愈";s:3:"憎";s:3:"憎";s:3:"慠";s:3:"æ… ";s:3:"懲";s:3:"懲";s:3:"戴";s:3:"戴";s:3:"ïª";s:3:"æ„";s:3:"搜";s:3:"æœ";s:3:"ïª";s:3:"æ‘’";s:3:"ïª";s:3:"æ•–";s:3:"晴";s:3:"æ™´";s:3:"朗";s:3:"朗";s:3:"望";s:3:"望";s:3:"杖";s:3:"æ–";s:3:"歹";s:3:"æ­¹";s:3:"殺";s:3:"殺";s:3:"流";s:3:"æµ";s:3:"滛";s:3:"æ»›";s:3:"滋";s:3:"滋";s:3:"漢";s:3:"æ¼¢";s:3:"瀞";s:3:"瀞";s:3:"煮";s:3:"ç…®";s:3:"ïª";s:3:"çž§";s:3:"爵";s:3:"爵";s:3:"犯";s:3:"犯";s:3:"猪";s:3:"猪";s:3:"瑱";s:3:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"ç”»";s:3:"瘝";s:3:"ç˜";s:3:"瘟";s:3:"瘟";s:3:"益";s:3:"益";s:3:"盛";s:3:"ç››";s:3:"直";s:3:"ç›´";s:3:"睊";s:3:"çŠ";s:3:"着";s:3:"ç€";s:3:"磌";s:3:"磌";s:3:"窱";s:3:"窱";s:3:"節";s:3:"節";s:3:"类";s:3:"ç±»";s:3:"絛";s:3:"çµ›";s:3:"練";s:3:"ç·´";s:3:"缾";s:3:"ç¼¾";s:3:"者";s:3:"者";s:3:"荒";s:3:"è’";s:3:"華";s:3:"è¯";s:3:"蝹";s:3:"è¹";s:3:"襁";s:3:"è¥";s:3:"覆";s:3:"覆";s:3:"視";s:3:"視";s:3:"調";s:3:"調";s:3:"諸";s:3:"諸";s:3:"請";s:3:"è«‹";s:3:"謁";s:3:"è¬";s:3:"諾";s:3:"諾";s:3:"諭";s:3:"è«­";s:3:"謹";s:3:"謹";s:3:"ï«€";s:3:"變";s:3:"ï«";s:3:"è´ˆ";s:3:"ï«‚";s:3:"輸";s:3:"遲";s:3:"é²";s:3:"ï«„";s:3:"醙";s:3:"ï«…";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"難";s:3:"難";s:3:"靖";s:3:"é–";s:3:"韛";s:3:"韛";s:3:"響";s:3:"響";s:3:"ï«‹";s:3:"é ‹";s:3:"頻";s:3:"é »";s:3:"ï«";s:3:"鬒";s:3:"龜";s:3:"龜";s:3:"ï«";s:4:"𢡊";s:3:"ï«";s:4:"𢡄";s:3:"ï«‘";s:4:"ð£•";s:3:"ï«’";s:3:"ã®";s:3:"ï«“";s:3:"䀘";s:3:"ï«”";s:3:"䀹";s:3:"ï«•";s:4:"𥉉";s:3:"ï«–";s:4:"ð¥³";s:3:"ï«—";s:4:"𧻓";s:3:"齃";s:3:"齃";s:3:"ï«™";s:3:"龎";s:3:"ff";s:2:"ff";s:3:"ï¬";s:2:"fi";s:3:"fl";s:2:"fl";s:3:"ffi";s:3:"ffi";s:3:"ffl";s:3:"ffl";s:3:"ſt";s:3:"Å¿t";s:3:"st";s:2:"st";s:3:"ﬓ";s:4:"Õ´Õ¶";s:3:"ﬔ";s:4:"Õ´Õ¥";s:3:"ﬕ";s:4:"Õ´Õ«";s:3:"ﬖ";s:4:"Õ¾Õ¶";s:3:"ﬗ";s:4:"Õ´Õ­";s:3:"ﬠ";s:2:"×¢";s:3:"ﬡ";s:2:"×";s:3:"ﬢ";s:2:"ד";s:3:"ﬣ";s:2:"×”";s:3:"ﬤ";s:2:"×›";s:3:"ﬥ";s:2:"ל";s:3:"ﬦ";s:2:"×";s:3:"ﬧ";s:2:"ר";s:3:"ﬨ";s:2:"ת";s:3:"﬩";s:1:"+";s:3:"ï­";s:4:"×ל";s:3:"﹉";s:3:"‾";s:3:"﹊";s:3:"‾";s:3:"﹋";s:3:"‾";s:3:"﹌";s:3:"‾";s:3:"ï¹";s:1:"_";s:3:"﹎";s:1:"_";s:3:"ï¹";s:1:"_";s:3:"ï¹";s:1:",";s:3:"﹑";s:3:"ã€";s:3:"ï¹’";s:1:".";s:3:"ï¹”";s:1:";";s:3:"﹕";s:1:":";s:3:"ï¹–";s:1:"?";s:3:"ï¹—";s:1:"!";s:3:"﹘";s:3:"—";s:3:"ï¹™";s:1:"(";s:3:"﹚";s:1:")";s:3:"ï¹›";s:1:"{";s:3:"﹜";s:1:"}";s:3:"ï¹";s:3:"〔";s:3:"﹞";s:3:"〕";s:3:"﹟";s:1:"#";s:3:"ï¹ ";s:1:"&";s:3:"﹡";s:1:"*";s:3:"ï¹¢";s:1:"+";s:3:"ï¹£";s:1:"-";s:3:"﹤";s:1:"<";s:3:"ï¹¥";s:1:">";s:3:"﹦";s:1:"=";s:3:"﹨";s:1:"\";s:3:"﹩";s:1:"$";s:3:"﹪";s:1:"%";s:3:"﹫";s:1:"@";s:3:"ï¼";s:1:"!";s:3:""";s:1:""";s:3:"#";s:1:"#";s:3:"$";s:1:"$";s:3:"ï¼…";s:1:"%";s:3:"&";s:1:"&";s:3:"'";s:1:"'";s:3:"(";s:1:"(";s:3:")";s:1:")";s:3:"*";s:1:"*";s:3:"+";s:1:"+";s:3:",";s:1:",";s:3:"ï¼";s:1:"-";s:3:".";s:1:".";s:3:"ï¼";s:1:"/";s:3:"ï¼";s:1:"0";s:3:"1";s:1:"1";s:3:"ï¼’";s:1:"2";s:3:"3";s:1:"3";s:3:"ï¼”";s:1:"4";s:3:"5";s:1:"5";s:3:"ï¼–";s:1:"6";s:3:"ï¼—";s:1:"7";s:3:"8";s:1:"8";s:3:"ï¼™";s:1:"9";s:3:":";s:1:":";s:3:"ï¼›";s:1:";";s:3:"<";s:1:"<";s:3:"ï¼";s:1:"=";s:3:">";s:1:">";s:3:"?";s:1:"?";s:3:"ï¼ ";s:1:"@";s:3:"A";s:1:"A";s:3:"ï¼¢";s:1:"B";s:3:"ï¼£";s:1:"C";s:3:"D";s:1:"D";s:3:"ï¼¥";s:1:"E";s:3:"F";s:1:"F";s:3:"ï¼§";s:1:"G";s:3:"H";s:1:"H";s:3:"I";s:1:"I";s:3:"J";s:1:"J";s:3:"K";s:1:"K";s:3:"L";s:1:"L";s:3:"ï¼­";s:1:"M";s:3:"ï¼®";s:1:"N";s:3:"O";s:1:"O";s:3:"ï¼°";s:1:"P";s:3:"ï¼±";s:1:"Q";s:3:"ï¼²";s:1:"R";s:3:"ï¼³";s:1:"S";s:3:"ï¼´";s:1:"T";s:3:"ï¼µ";s:1:"U";s:3:"ï¼¶";s:1:"V";s:3:"ï¼·";s:1:"W";s:3:"X";s:1:"X";s:3:"ï¼¹";s:1:"Y";s:3:"Z";s:1:"Z";s:3:"ï¼»";s:1:"[";s:3:"ï¼¼";s:1:"\";s:3:"ï¼½";s:1:"]";s:3:"ï¼¾";s:1:"^";s:3:"_";s:1:"_";s:3:"ï½€";s:1:"`";s:3:"ï½";s:1:"a";s:3:"b";s:1:"b";s:3:"c";s:1:"c";s:3:"d";s:1:"d";s:3:"ï½…";s:1:"e";s:3:"f";s:1:"f";s:3:"g";s:1:"g";s:3:"h";s:1:"h";s:3:"i";s:1:"i";s:3:"j";s:1:"j";s:3:"k";s:1:"k";s:3:"l";s:1:"l";s:3:"ï½";s:1:"m";s:3:"n";s:1:"n";s:3:"ï½";s:1:"o";s:3:"ï½";s:1:"p";s:3:"q";s:1:"q";s:3:"ï½’";s:1:"r";s:3:"s";s:1:"s";s:3:"ï½”";s:1:"t";s:3:"u";s:1:"u";s:3:"ï½–";s:1:"v";s:3:"ï½—";s:1:"w";s:3:"x";s:1:"x";s:3:"ï½™";s:1:"y";s:3:"z";s:1:"z";s:3:"ï½›";s:1:"{";s:3:"|";s:1:"|";s:3:"ï½";s:1:"}";s:3:"~";s:1:"~";s:3:"⦅";s:3:"⦅";s:3:"ï½ ";s:3:"⦆";s:3:"。";s:3:"。";s:3:"ï½¢";s:3:"「";s:3:"ï½£";s:3:"ã€";s:3:"、";s:3:"ã€";s:3:"ï½¥";s:3:"・";s:3:"ヲ";s:3:"ヲ";s:3:"ï½§";s:3:"ã‚¡";s:3:"ィ";s:3:"ã‚£";s:3:"ゥ";s:3:"ã‚¥";s:3:"ェ";s:3:"ã‚§";s:3:"ォ";s:3:"ã‚©";s:3:"ャ";s:3:"ャ";s:3:"ï½­";s:3:"ュ";s:3:"ï½®";s:3:"ョ";s:3:"ッ";s:3:"ッ";s:3:"ï½°";s:3:"ー";s:3:"ï½±";s:3:"ã‚¢";s:3:"ï½²";s:3:"イ";s:3:"ï½³";s:3:"ウ";s:3:"ï½´";s:3:"エ";s:3:"ï½µ";s:3:"オ";s:3:"ï½¶";s:3:"ã‚«";s:3:"ï½·";s:3:"ã‚­";s:3:"ク";s:3:"ク";s:3:"ï½¹";s:3:"ケ";s:3:"コ";s:3:"コ";s:3:"ï½»";s:3:"サ";s:3:"ï½¼";s:3:"ã‚·";s:3:"ï½½";s:3:"ス";s:3:"ï½¾";s:3:"ã‚»";s:3:"ソ";s:3:"ソ";s:3:"ï¾€";s:3:"ã‚¿";s:3:"ï¾";s:3:"ãƒ";s:3:"ツ";s:3:"ツ";s:3:"テ";s:3:"テ";s:3:"ト";s:3:"ト";s:3:"ï¾…";s:3:"ナ";s:3:"ニ";s:3:"ニ";s:3:"ヌ";s:3:"ヌ";s:3:"ネ";s:3:"ãƒ";s:3:"ノ";s:3:"ノ";s:3:"ハ";s:3:"ãƒ";s:3:"ヒ";s:3:"ヒ";s:3:"フ";s:3:"フ";s:3:"ï¾";s:3:"ヘ";s:3:"ホ";s:3:"ホ";s:3:"ï¾";s:3:"マ";s:3:"ï¾";s:3:"ミ";s:3:"ム";s:3:"ム";s:3:"ï¾’";s:3:"メ";s:3:"モ";s:3:"モ";s:3:"ï¾”";s:3:"ヤ";s:3:"ユ";s:3:"ユ";s:3:"ï¾–";s:3:"ヨ";s:3:"ï¾—";s:3:"ラ";s:3:"リ";s:3:"リ";s:3:"ï¾™";s:3:"ル";s:3:"レ";s:3:"レ";s:3:"ï¾›";s:3:"ロ";s:3:"ワ";s:3:"ワ";s:3:"ï¾";s:3:"ン";s:3:"゙";s:3:"ã‚™";s:3:"゚";s:3:"゚";s:3:"ï¾ ";s:3:"ã…¤";s:3:"ᄀ";s:3:"ㄱ";s:3:"ï¾¢";s:3:"ㄲ";s:3:"ï¾£";s:3:"ㄳ";s:3:"ᄂ";s:3:"ã„´";s:3:"ï¾¥";s:3:"ㄵ";s:3:"ᆭ";s:3:"ã„¶";s:3:"ï¾§";s:3:"ã„·";s:3:"ᄄ";s:3:"ㄸ";s:3:"ᄅ";s:3:"ㄹ";s:3:"ᆰ";s:3:"ㄺ";s:3:"ᆱ";s:3:"ã„»";s:3:"ᆲ";s:3:"ㄼ";s:3:"ï¾­";s:3:"ㄽ";s:3:"ï¾®";s:3:"ㄾ";s:3:"ᆵ";s:3:"ã„¿";s:3:"ï¾°";s:3:"ã…€";s:3:"ï¾±";s:3:"ã…";s:3:"ï¾²";s:3:"ã…‚";s:3:"ï¾³";s:3:"ã…ƒ";s:3:"ï¾´";s:3:"ã…„";s:3:"ï¾µ";s:3:"ã……";s:3:"ï¾¶";s:3:"ã…†";s:3:"ï¾·";s:3:"ã…‡";s:3:"ᄌ";s:3:"ã…ˆ";s:3:"ï¾¹";s:3:"ã…‰";s:3:"ᄎ";s:3:"ã…Š";s:3:"ï¾»";s:3:"ã…‹";s:3:"ï¾¼";s:3:"ã…Œ";s:3:"ï¾½";s:3:"ã…";s:3:"ï¾¾";s:3:"ã…Ž";s:3:"ï¿‚";s:3:"ã…";s:3:"ᅢ";s:3:"ã…";s:3:"ï¿„";s:3:"ã…‘";s:3:"ï¿…";s:3:"ã…’";s:3:"ᅥ";s:3:"ã…“";s:3:"ᅦ";s:3:"ã…”";s:3:"ᅧ";s:3:"ã…•";s:3:"ï¿‹";s:3:"ã…–";s:3:"ᅩ";s:3:"ã…—";s:3:"ï¿";s:3:"ã…˜";s:3:"ᅫ";s:3:"ã…™";s:3:"ï¿";s:3:"ã…š";s:3:"ï¿’";s:3:"ã…›";s:3:"ï¿“";s:3:"ã…œ";s:3:"ï¿”";s:3:"ã…";s:3:"ï¿•";s:3:"ã…ž";s:3:"ï¿–";s:3:"ã…Ÿ";s:3:"ï¿—";s:3:"ã… ";s:3:"ᅳ";s:3:"ã…¡";s:3:"ï¿›";s:3:"ã…¢";s:3:"ᅵ";s:3:"ã…£";s:3:"ï¿ ";s:2:"¢";s:3:"ï¿¡";s:2:"£";s:3:"ï¿¢";s:2:"¬";s:3:"ï¿£";s:2:"¯";s:3:"¦";s:2:"¦";s:3:"ï¿¥";s:2:"Â¥";s:3:"₩";s:3:"â‚©";s:3:"│";s:3:"│";s:3:"ï¿©";s:3:"â†";s:3:"↑";s:3:"↑";s:3:"ï¿«";s:3:"→";s:3:"↓";s:3:"↓";s:3:"ï¿­";s:3:"â– ";s:3:"ï¿®";s:3:"â—‹";s:4:"ð€";s:1:"A";s:4:"ð";s:1:"B";s:4:"ð‚";s:1:"C";s:4:"ðƒ";s:1:"D";s:4:"ð„";s:1:"E";s:4:"ð…";s:1:"F";s:4:"ð†";s:1:"G";s:4:"ð‡";s:1:"H";s:4:"ðˆ";s:1:"I";s:4:"ð‰";s:1:"J";s:4:"ðŠ";s:1:"K";s:4:"ð‹";s:1:"L";s:4:"ðŒ";s:1:"M";s:4:"ð";s:1:"N";s:4:"ðŽ";s:1:"O";s:4:"ð";s:1:"P";s:4:"ð";s:1:"Q";s:4:"ð‘";s:1:"R";s:4:"ð’";s:1:"S";s:4:"ð“";s:1:"T";s:4:"ð”";s:1:"U";s:4:"ð•";s:1:"V";s:4:"ð–";s:1:"W";s:4:"ð—";s:1:"X";s:4:"ð˜";s:1:"Y";s:4:"ð™";s:1:"Z";s:4:"ðš";s:1:"a";s:4:"ð›";s:1:"b";s:4:"ðœ";s:1:"c";s:4:"ð";s:1:"d";s:4:"ðž";s:1:"e";s:4:"ðŸ";s:1:"f";s:4:"ð ";s:1:"g";s:4:"ð¡";s:1:"h";s:4:"ð¢";s:1:"i";s:4:"ð£";s:1:"j";s:4:"ð¤";s:1:"k";s:4:"ð¥";s:1:"l";s:4:"ð¦";s:1:"m";s:4:"ð§";s:1:"n";s:4:"ð¨";s:1:"o";s:4:"ð©";s:1:"p";s:4:"ðª";s:1:"q";s:4:"ð«";s:1:"r";s:4:"ð¬";s:1:"s";s:4:"ð­";s:1:"t";s:4:"ð®";s:1:"u";s:4:"ð¯";s:1:"v";s:4:"ð°";s:1:"w";s:4:"ð±";s:1:"x";s:4:"ð²";s:1:"y";s:4:"ð³";s:1:"z";s:4:"ð´";s:1:"A";s:4:"ðµ";s:1:"B";s:4:"ð¶";s:1:"C";s:4:"ð·";s:1:"D";s:4:"ð¸";s:1:"E";s:4:"ð¹";s:1:"F";s:4:"ðº";s:1:"G";s:4:"ð»";s:1:"H";s:4:"ð¼";s:1:"I";s:4:"ð½";s:1:"J";s:4:"ð¾";s:1:"K";s:4:"ð¿";s:1:"L";s:4:"ð‘€";s:1:"M";s:4:"ð‘";s:1:"N";s:4:"ð‘‚";s:1:"O";s:4:"ð‘ƒ";s:1:"P";s:4:"ð‘„";s:1:"Q";s:4:"ð‘…";s:1:"R";s:4:"ð‘†";s:1:"S";s:4:"ð‘‡";s:1:"T";s:4:"ð‘ˆ";s:1:"U";s:4:"ð‘‰";s:1:"V";s:4:"ð‘Š";s:1:"W";s:4:"ð‘‹";s:1:"X";s:4:"ð‘Œ";s:1:"Y";s:4:"ð‘";s:1:"Z";s:4:"ð‘Ž";s:1:"a";s:4:"ð‘";s:1:"b";s:4:"ð‘";s:1:"c";s:4:"ð‘‘";s:1:"d";s:4:"ð‘’";s:1:"e";s:4:"ð‘“";s:1:"f";s:4:"ð‘”";s:1:"g";s:4:"ð‘–";s:1:"i";s:4:"ð‘—";s:1:"j";s:4:"ð‘˜";s:1:"k";s:4:"ð‘™";s:1:"l";s:4:"ð‘š";s:1:"m";s:4:"ð‘›";s:1:"n";s:4:"ð‘œ";s:1:"o";s:4:"ð‘";s:1:"p";s:4:"ð‘ž";s:1:"q";s:4:"ð‘Ÿ";s:1:"r";s:4:"ð‘ ";s:1:"s";s:4:"ð‘¡";s:1:"t";s:4:"ð‘¢";s:1:"u";s:4:"ð‘£";s:1:"v";s:4:"ð‘¤";s:1:"w";s:4:"ð‘¥";s:1:"x";s:4:"ð‘¦";s:1:"y";s:4:"ð‘§";s:1:"z";s:4:"ð‘¨";s:1:"A";s:4:"ð‘©";s:1:"B";s:4:"ð‘ª";s:1:"C";s:4:"ð‘«";s:1:"D";s:4:"ð‘¬";s:1:"E";s:4:"ð‘­";s:1:"F";s:4:"ð‘®";s:1:"G";s:4:"ð‘¯";s:1:"H";s:4:"ð‘°";s:1:"I";s:4:"ð‘±";s:1:"J";s:4:"ð‘²";s:1:"K";s:4:"ð‘³";s:1:"L";s:4:"ð‘´";s:1:"M";s:4:"ð‘µ";s:1:"N";s:4:"ð‘¶";s:1:"O";s:4:"ð‘·";s:1:"P";s:4:"ð‘¸";s:1:"Q";s:4:"ð‘¹";s:1:"R";s:4:"ð‘º";s:1:"S";s:4:"ð‘»";s:1:"T";s:4:"ð‘¼";s:1:"U";s:4:"ð‘½";s:1:"V";s:4:"ð‘¾";s:1:"W";s:4:"ð‘¿";s:1:"X";s:4:"ð’€";s:1:"Y";s:4:"ð’";s:1:"Z";s:4:"ð’‚";s:1:"a";s:4:"ð’ƒ";s:1:"b";s:4:"ð’„";s:1:"c";s:4:"ð’…";s:1:"d";s:4:"ð’†";s:1:"e";s:4:"ð’‡";s:1:"f";s:4:"ð’ˆ";s:1:"g";s:4:"ð’‰";s:1:"h";s:4:"ð’Š";s:1:"i";s:4:"ð’‹";s:1:"j";s:4:"ð’Œ";s:1:"k";s:4:"ð’";s:1:"l";s:4:"ð’Ž";s:1:"m";s:4:"ð’";s:1:"n";s:4:"ð’";s:1:"o";s:4:"ð’‘";s:1:"p";s:4:"ð’’";s:1:"q";s:4:"ð’“";s:1:"r";s:4:"ð’”";s:1:"s";s:4:"ð’•";s:1:"t";s:4:"ð’–";s:1:"u";s:4:"ð’—";s:1:"v";s:4:"ð’˜";s:1:"w";s:4:"ð’™";s:1:"x";s:4:"ð’š";s:1:"y";s:4:"ð’›";s:1:"z";s:4:"ð’œ";s:1:"A";s:4:"ð’ž";s:1:"C";s:4:"ð’Ÿ";s:1:"D";s:4:"ð’¢";s:1:"G";s:4:"ð’¥";s:1:"J";s:4:"ð’¦";s:1:"K";s:4:"ð’©";s:1:"N";s:4:"ð’ª";s:1:"O";s:4:"ð’«";s:1:"P";s:4:"ð’¬";s:1:"Q";s:4:"ð’®";s:1:"S";s:4:"ð’¯";s:1:"T";s:4:"ð’°";s:1:"U";s:4:"ð’±";s:1:"V";s:4:"ð’²";s:1:"W";s:4:"ð’³";s:1:"X";s:4:"ð’´";s:1:"Y";s:4:"ð’µ";s:1:"Z";s:4:"ð’¶";s:1:"a";s:4:"ð’·";s:1:"b";s:4:"ð’¸";s:1:"c";s:4:"ð’¹";s:1:"d";s:4:"ð’»";s:1:"f";s:4:"ð’½";s:1:"h";s:4:"ð’¾";s:1:"i";s:4:"ð’¿";s:1:"j";s:4:"ð“€";s:1:"k";s:4:"ð“";s:1:"l";s:4:"ð“‚";s:1:"m";s:4:"ð“ƒ";s:1:"n";s:4:"ð“…";s:1:"p";s:4:"ð“†";s:1:"q";s:4:"ð“‡";s:1:"r";s:4:"ð“ˆ";s:1:"s";s:4:"ð“‰";s:1:"t";s:4:"ð“Š";s:1:"u";s:4:"ð“‹";s:1:"v";s:4:"ð“Œ";s:1:"w";s:4:"ð“";s:1:"x";s:4:"ð“Ž";s:1:"y";s:4:"ð“";s:1:"z";s:4:"ð“";s:1:"A";s:4:"ð“‘";s:1:"B";s:4:"ð“’";s:1:"C";s:4:"ð““";s:1:"D";s:4:"ð“”";s:1:"E";s:4:"ð“•";s:1:"F";s:4:"ð“–";s:1:"G";s:4:"ð“—";s:1:"H";s:4:"ð“˜";s:1:"I";s:4:"ð“™";s:1:"J";s:4:"ð“š";s:1:"K";s:4:"ð“›";s:1:"L";s:4:"ð“œ";s:1:"M";s:4:"ð“";s:1:"N";s:4:"ð“ž";s:1:"O";s:4:"ð“Ÿ";s:1:"P";s:4:"ð“ ";s:1:"Q";s:4:"ð“¡";s:1:"R";s:4:"ð“¢";s:1:"S";s:4:"ð“£";s:1:"T";s:4:"ð“¤";s:1:"U";s:4:"ð“¥";s:1:"V";s:4:"ð“¦";s:1:"W";s:4:"ð“§";s:1:"X";s:4:"ð“¨";s:1:"Y";s:4:"ð“©";s:1:"Z";s:4:"ð“ª";s:1:"a";s:4:"ð“«";s:1:"b";s:4:"ð“¬";s:1:"c";s:4:"ð“­";s:1:"d";s:4:"ð“®";s:1:"e";s:4:"ð“¯";s:1:"f";s:4:"ð“°";s:1:"g";s:4:"ð“±";s:1:"h";s:4:"ð“²";s:1:"i";s:4:"ð“³";s:1:"j";s:4:"ð“´";s:1:"k";s:4:"ð“µ";s:1:"l";s:4:"ð“¶";s:1:"m";s:4:"ð“·";s:1:"n";s:4:"ð“¸";s:1:"o";s:4:"ð“¹";s:1:"p";s:4:"ð“º";s:1:"q";s:4:"ð“»";s:1:"r";s:4:"ð“¼";s:1:"s";s:4:"ð“½";s:1:"t";s:4:"ð“¾";s:1:"u";s:4:"ð“¿";s:1:"v";s:4:"ð”€";s:1:"w";s:4:"ð”";s:1:"x";s:4:"ð”‚";s:1:"y";s:4:"ð”ƒ";s:1:"z";s:4:"ð”„";s:1:"A";s:4:"ð”…";s:1:"B";s:4:"ð”‡";s:1:"D";s:4:"ð”ˆ";s:1:"E";s:4:"ð”‰";s:1:"F";s:4:"ð”Š";s:1:"G";s:4:"ð”";s:1:"J";s:4:"ð”Ž";s:1:"K";s:4:"ð”";s:1:"L";s:4:"ð”";s:1:"M";s:4:"ð”‘";s:1:"N";s:4:"ð”’";s:1:"O";s:4:"ð”“";s:1:"P";s:4:"ð””";s:1:"Q";s:4:"ð”–";s:1:"S";s:4:"ð”—";s:1:"T";s:4:"ð”˜";s:1:"U";s:4:"ð”™";s:1:"V";s:4:"ð”š";s:1:"W";s:4:"ð”›";s:1:"X";s:4:"ð”œ";s:1:"Y";s:4:"ð”ž";s:1:"a";s:4:"ð”Ÿ";s:1:"b";s:4:"ð” ";s:1:"c";s:4:"ð”¡";s:1:"d";s:4:"ð”¢";s:1:"e";s:4:"ð”£";s:1:"f";s:4:"ð”¤";s:1:"g";s:4:"ð”¥";s:1:"h";s:4:"ð”¦";s:1:"i";s:4:"ð”§";s:1:"j";s:4:"ð”¨";s:1:"k";s:4:"ð”©";s:1:"l";s:4:"ð”ª";s:1:"m";s:4:"ð”«";s:1:"n";s:4:"ð”¬";s:1:"o";s:4:"ð”­";s:1:"p";s:4:"ð”®";s:1:"q";s:4:"ð”¯";s:1:"r";s:4:"ð”°";s:1:"s";s:4:"ð”±";s:1:"t";s:4:"ð”²";s:1:"u";s:4:"ð”³";s:1:"v";s:4:"ð”´";s:1:"w";s:4:"ð”µ";s:1:"x";s:4:"ð”¶";s:1:"y";s:4:"ð”·";s:1:"z";s:4:"ð”¸";s:1:"A";s:4:"ð”¹";s:1:"B";s:4:"ð”»";s:1:"D";s:4:"ð”¼";s:1:"E";s:4:"ð”½";s:1:"F";s:4:"ð”¾";s:1:"G";s:4:"ð•€";s:1:"I";s:4:"ð•";s:1:"J";s:4:"ð•‚";s:1:"K";s:4:"ð•ƒ";s:1:"L";s:4:"ð•„";s:1:"M";s:4:"ð•†";s:1:"O";s:4:"ð•Š";s:1:"S";s:4:"ð•‹";s:1:"T";s:4:"ð•Œ";s:1:"U";s:4:"ð•";s:1:"V";s:4:"ð•Ž";s:1:"W";s:4:"ð•";s:1:"X";s:4:"ð•";s:1:"Y";s:4:"ð•’";s:1:"a";s:4:"ð•“";s:1:"b";s:4:"ð•”";s:1:"c";s:4:"ð••";s:1:"d";s:4:"ð•–";s:1:"e";s:4:"ð•—";s:1:"f";s:4:"ð•˜";s:1:"g";s:4:"ð•™";s:1:"h";s:4:"ð•š";s:1:"i";s:4:"ð•›";s:1:"j";s:4:"ð•œ";s:1:"k";s:4:"ð•";s:1:"l";s:4:"ð•ž";s:1:"m";s:4:"ð•Ÿ";s:1:"n";s:4:"ð• ";s:1:"o";s:4:"ð•¡";s:1:"p";s:4:"ð•¢";s:1:"q";s:4:"ð•£";s:1:"r";s:4:"ð•¤";s:1:"s";s:4:"ð•¥";s:1:"t";s:4:"ð•¦";s:1:"u";s:4:"ð•§";s:1:"v";s:4:"ð•¨";s:1:"w";s:4:"ð•©";s:1:"x";s:4:"ð•ª";s:1:"y";s:4:"ð•«";s:1:"z";s:4:"ð•¬";s:1:"A";s:4:"ð•­";s:1:"B";s:4:"ð•®";s:1:"C";s:4:"ð•¯";s:1:"D";s:4:"ð•°";s:1:"E";s:4:"ð•±";s:1:"F";s:4:"ð•²";s:1:"G";s:4:"ð•³";s:1:"H";s:4:"ð•´";s:1:"I";s:4:"ð•µ";s:1:"J";s:4:"ð•¶";s:1:"K";s:4:"ð•·";s:1:"L";s:4:"ð•¸";s:1:"M";s:4:"ð•¹";s:1:"N";s:4:"ð•º";s:1:"O";s:4:"ð•»";s:1:"P";s:4:"ð•¼";s:1:"Q";s:4:"ð•½";s:1:"R";s:4:"ð•¾";s:1:"S";s:4:"ð•¿";s:1:"T";s:4:"ð–€";s:1:"U";s:4:"ð–";s:1:"V";s:4:"ð–‚";s:1:"W";s:4:"ð–ƒ";s:1:"X";s:4:"ð–„";s:1:"Y";s:4:"ð–…";s:1:"Z";s:4:"ð–†";s:1:"a";s:4:"ð–‡";s:1:"b";s:4:"ð–ˆ";s:1:"c";s:4:"ð–‰";s:1:"d";s:4:"ð–Š";s:1:"e";s:4:"ð–‹";s:1:"f";s:4:"ð–Œ";s:1:"g";s:4:"ð–";s:1:"h";s:4:"ð–Ž";s:1:"i";s:4:"ð–";s:1:"j";s:4:"ð–";s:1:"k";s:4:"ð–‘";s:1:"l";s:4:"ð–’";s:1:"m";s:4:"ð–“";s:1:"n";s:4:"ð–”";s:1:"o";s:4:"ð–•";s:1:"p";s:4:"ð––";s:1:"q";s:4:"ð–—";s:1:"r";s:4:"ð–˜";s:1:"s";s:4:"ð–™";s:1:"t";s:4:"ð–š";s:1:"u";s:4:"ð–›";s:1:"v";s:4:"ð–œ";s:1:"w";s:4:"ð–";s:1:"x";s:4:"ð–ž";s:1:"y";s:4:"ð–Ÿ";s:1:"z";s:4:"ð– ";s:1:"A";s:4:"ð–¡";s:1:"B";s:4:"ð–¢";s:1:"C";s:4:"ð–£";s:1:"D";s:4:"ð–¤";s:1:"E";s:4:"ð–¥";s:1:"F";s:4:"ð–¦";s:1:"G";s:4:"ð–§";s:1:"H";s:4:"ð–¨";s:1:"I";s:4:"ð–©";s:1:"J";s:4:"ð–ª";s:1:"K";s:4:"ð–«";s:1:"L";s:4:"ð–¬";s:1:"M";s:4:"ð–­";s:1:"N";s:4:"ð–®";s:1:"O";s:4:"ð–¯";s:1:"P";s:4:"ð–°";s:1:"Q";s:4:"ð–±";s:1:"R";s:4:"ð–²";s:1:"S";s:4:"ð–³";s:1:"T";s:4:"ð–´";s:1:"U";s:4:"ð–µ";s:1:"V";s:4:"ð–¶";s:1:"W";s:4:"ð–·";s:1:"X";s:4:"ð–¸";s:1:"Y";s:4:"ð–¹";s:1:"Z";s:4:"ð–º";s:1:"a";s:4:"ð–»";s:1:"b";s:4:"ð–¼";s:1:"c";s:4:"ð–½";s:1:"d";s:4:"ð–¾";s:1:"e";s:4:"ð–¿";s:1:"f";s:4:"ð—€";s:1:"g";s:4:"ð—";s:1:"h";s:4:"ð—‚";s:1:"i";s:4:"ð—ƒ";s:1:"j";s:4:"ð—„";s:1:"k";s:4:"ð—…";s:1:"l";s:4:"ð—†";s:1:"m";s:4:"ð—‡";s:1:"n";s:4:"ð—ˆ";s:1:"o";s:4:"ð—‰";s:1:"p";s:4:"ð—Š";s:1:"q";s:4:"ð—‹";s:1:"r";s:4:"ð—Œ";s:1:"s";s:4:"ð—";s:1:"t";s:4:"ð—Ž";s:1:"u";s:4:"ð—";s:1:"v";s:4:"ð—";s:1:"w";s:4:"ð—‘";s:1:"x";s:4:"ð—’";s:1:"y";s:4:"ð—“";s:1:"z";s:4:"ð—”";s:1:"A";s:4:"ð—•";s:1:"B";s:4:"ð—–";s:1:"C";s:4:"ð——";s:1:"D";s:4:"ð—˜";s:1:"E";s:4:"ð—™";s:1:"F";s:4:"ð—š";s:1:"G";s:4:"ð—›";s:1:"H";s:4:"ð—œ";s:1:"I";s:4:"ð—";s:1:"J";s:4:"ð—ž";s:1:"K";s:4:"ð—Ÿ";s:1:"L";s:4:"ð— ";s:1:"M";s:4:"ð—¡";s:1:"N";s:4:"ð—¢";s:1:"O";s:4:"ð—£";s:1:"P";s:4:"ð—¤";s:1:"Q";s:4:"ð—¥";s:1:"R";s:4:"ð—¦";s:1:"S";s:4:"ð—§";s:1:"T";s:4:"ð—¨";s:1:"U";s:4:"ð—©";s:1:"V";s:4:"ð—ª";s:1:"W";s:4:"ð—«";s:1:"X";s:4:"ð—¬";s:1:"Y";s:4:"ð—­";s:1:"Z";s:4:"ð—®";s:1:"a";s:4:"ð—¯";s:1:"b";s:4:"ð—°";s:1:"c";s:4:"ð—±";s:1:"d";s:4:"ð—²";s:1:"e";s:4:"ð—³";s:1:"f";s:4:"ð—´";s:1:"g";s:4:"ð—µ";s:1:"h";s:4:"ð—¶";s:1:"i";s:4:"ð—·";s:1:"j";s:4:"ð—¸";s:1:"k";s:4:"ð—¹";s:1:"l";s:4:"ð—º";s:1:"m";s:4:"ð—»";s:1:"n";s:4:"ð—¼";s:1:"o";s:4:"ð—½";s:1:"p";s:4:"ð—¾";s:1:"q";s:4:"ð—¿";s:1:"r";s:4:"ð˜€";s:1:"s";s:4:"ð˜";s:1:"t";s:4:"ð˜‚";s:1:"u";s:4:"ð˜ƒ";s:1:"v";s:4:"ð˜„";s:1:"w";s:4:"ð˜…";s:1:"x";s:4:"ð˜†";s:1:"y";s:4:"ð˜‡";s:1:"z";s:4:"ð˜ˆ";s:1:"A";s:4:"ð˜‰";s:1:"B";s:4:"ð˜Š";s:1:"C";s:4:"ð˜‹";s:1:"D";s:4:"ð˜Œ";s:1:"E";s:4:"ð˜";s:1:"F";s:4:"ð˜Ž";s:1:"G";s:4:"ð˜";s:1:"H";s:4:"ð˜";s:1:"I";s:4:"ð˜‘";s:1:"J";s:4:"ð˜’";s:1:"K";s:4:"ð˜“";s:1:"L";s:4:"ð˜”";s:1:"M";s:4:"ð˜•";s:1:"N";s:4:"ð˜–";s:1:"O";s:4:"ð˜—";s:1:"P";s:4:"ð˜˜";s:1:"Q";s:4:"ð˜™";s:1:"R";s:4:"ð˜š";s:1:"S";s:4:"ð˜›";s:1:"T";s:4:"ð˜œ";s:1:"U";s:4:"ð˜";s:1:"V";s:4:"ð˜ž";s:1:"W";s:4:"ð˜Ÿ";s:1:"X";s:4:"ð˜ ";s:1:"Y";s:4:"ð˜¡";s:1:"Z";s:4:"ð˜¢";s:1:"a";s:4:"ð˜£";s:1:"b";s:4:"ð˜¤";s:1:"c";s:4:"ð˜¥";s:1:"d";s:4:"ð˜¦";s:1:"e";s:4:"ð˜§";s:1:"f";s:4:"ð˜¨";s:1:"g";s:4:"ð˜©";s:1:"h";s:4:"ð˜ª";s:1:"i";s:4:"ð˜«";s:1:"j";s:4:"ð˜¬";s:1:"k";s:4:"ð˜­";s:1:"l";s:4:"ð˜®";s:1:"m";s:4:"ð˜¯";s:1:"n";s:4:"ð˜°";s:1:"o";s:4:"ð˜±";s:1:"p";s:4:"ð˜²";s:1:"q";s:4:"ð˜³";s:1:"r";s:4:"ð˜´";s:1:"s";s:4:"ð˜µ";s:1:"t";s:4:"ð˜¶";s:1:"u";s:4:"ð˜·";s:1:"v";s:4:"ð˜¸";s:1:"w";s:4:"ð˜¹";s:1:"x";s:4:"ð˜º";s:1:"y";s:4:"ð˜»";s:1:"z";s:4:"ð˜¼";s:1:"A";s:4:"ð˜½";s:1:"B";s:4:"ð˜¾";s:1:"C";s:4:"ð˜¿";s:1:"D";s:4:"ð™€";s:1:"E";s:4:"ð™";s:1:"F";s:4:"ð™‚";s:1:"G";s:4:"ð™ƒ";s:1:"H";s:4:"ð™„";s:1:"I";s:4:"ð™…";s:1:"J";s:4:"ð™†";s:1:"K";s:4:"ð™‡";s:1:"L";s:4:"ð™ˆ";s:1:"M";s:4:"ð™‰";s:1:"N";s:4:"ð™Š";s:1:"O";s:4:"ð™‹";s:1:"P";s:4:"ð™Œ";s:1:"Q";s:4:"ð™";s:1:"R";s:4:"ð™Ž";s:1:"S";s:4:"ð™";s:1:"T";s:4:"ð™";s:1:"U";s:4:"ð™‘";s:1:"V";s:4:"ð™’";s:1:"W";s:4:"ð™“";s:1:"X";s:4:"ð™”";s:1:"Y";s:4:"ð™•";s:1:"Z";s:4:"ð™–";s:1:"a";s:4:"ð™—";s:1:"b";s:4:"ð™˜";s:1:"c";s:4:"ð™™";s:1:"d";s:4:"ð™š";s:1:"e";s:4:"ð™›";s:1:"f";s:4:"ð™œ";s:1:"g";s:4:"ð™";s:1:"h";s:4:"ð™ž";s:1:"i";s:4:"ð™Ÿ";s:1:"j";s:4:"ð™ ";s:1:"k";s:4:"ð™¡";s:1:"l";s:4:"ð™¢";s:1:"m";s:4:"ð™£";s:1:"n";s:4:"ð™¤";s:1:"o";s:4:"ð™¥";s:1:"p";s:4:"ð™¦";s:1:"q";s:4:"ð™§";s:1:"r";s:4:"ð™¨";s:1:"s";s:4:"ð™©";s:1:"t";s:4:"ð™ª";s:1:"u";s:4:"ð™«";s:1:"v";s:4:"ð™¬";s:1:"w";s:4:"ð™­";s:1:"x";s:4:"ð™®";s:1:"y";s:4:"ð™¯";s:1:"z";s:4:"ð™°";s:1:"A";s:4:"ð™±";s:1:"B";s:4:"ð™²";s:1:"C";s:4:"ð™³";s:1:"D";s:4:"ð™´";s:1:"E";s:4:"ð™µ";s:1:"F";s:4:"ð™¶";s:1:"G";s:4:"ð™·";s:1:"H";s:4:"ð™¸";s:1:"I";s:4:"ð™¹";s:1:"J";s:4:"ð™º";s:1:"K";s:4:"ð™»";s:1:"L";s:4:"ð™¼";s:1:"M";s:4:"ð™½";s:1:"N";s:4:"ð™¾";s:1:"O";s:4:"ð™¿";s:1:"P";s:4:"ðš€";s:1:"Q";s:4:"ðš";s:1:"R";s:4:"ðš‚";s:1:"S";s:4:"ðšƒ";s:1:"T";s:4:"ðš„";s:1:"U";s:4:"ðš…";s:1:"V";s:4:"ðš†";s:1:"W";s:4:"ðš‡";s:1:"X";s:4:"ðšˆ";s:1:"Y";s:4:"ðš‰";s:1:"Z";s:4:"ðšŠ";s:1:"a";s:4:"ðš‹";s:1:"b";s:4:"ðšŒ";s:1:"c";s:4:"ðš";s:1:"d";s:4:"ðšŽ";s:1:"e";s:4:"ðš";s:1:"f";s:4:"ðš";s:1:"g";s:4:"ðš‘";s:1:"h";s:4:"ðš’";s:1:"i";s:4:"ðš“";s:1:"j";s:4:"ðš”";s:1:"k";s:4:"ðš•";s:1:"l";s:4:"ðš–";s:1:"m";s:4:"ðš—";s:1:"n";s:4:"ðš˜";s:1:"o";s:4:"ðš™";s:1:"p";s:4:"ðšš";s:1:"q";s:4:"ðš›";s:1:"r";s:4:"ðšœ";s:1:"s";s:4:"ðš";s:1:"t";s:4:"ðšž";s:1:"u";s:4:"ðšŸ";s:1:"v";s:4:"ðš ";s:1:"w";s:4:"ðš¡";s:1:"x";s:4:"ðš¢";s:1:"y";s:4:"ðš£";s:1:"z";s:4:"ðš¤";s:2:"ı";s:4:"ðš¥";s:2:"È·";s:4:"ðš¨";s:2:"Α";s:4:"ðš©";s:2:"Î’";s:4:"ðšª";s:2:"Γ";s:4:"ðš«";s:2:"Δ";s:4:"ðš¬";s:2:"Ε";s:4:"ðš­";s:2:"Ζ";s:4:"ðš®";s:2:"Η";s:4:"ðš¯";s:2:"Θ";s:4:"ðš°";s:2:"Ι";s:4:"ðš±";s:2:"Κ";s:4:"ðš²";s:2:"Λ";s:4:"ðš³";s:2:"Μ";s:4:"ðš´";s:2:"Î";s:4:"ðšµ";s:2:"Ξ";s:4:"ðš¶";s:2:"Ο";s:4:"ðš·";s:2:"Π";s:4:"ðš¸";s:2:"Ρ";s:4:"ðš¹";s:2:"Ï´";s:4:"ðšº";s:2:"Σ";s:4:"ðš»";s:2:"Τ";s:4:"ðš¼";s:2:"Î¥";s:4:"ðš½";s:2:"Φ";s:4:"ðš¾";s:2:"Χ";s:4:"ðš¿";s:2:"Ψ";s:4:"ð›€";s:2:"Ω";s:4:"ð›";s:3:"∇";s:4:"ð›‚";s:2:"α";s:4:"ð›ƒ";s:2:"β";s:4:"ð›„";s:2:"γ";s:4:"ð›…";s:2:"δ";s:4:"ð›†";s:2:"ε";s:4:"ð›‡";s:2:"ζ";s:4:"ð›ˆ";s:2:"η";s:4:"ð›‰";s:2:"θ";s:4:"ð›Š";s:2:"ι";s:4:"ð›‹";s:2:"κ";s:4:"ð›Œ";s:2:"λ";s:4:"ð›";s:2:"μ";s:4:"ð›Ž";s:2:"ν";s:4:"ð›";s:2:"ξ";s:4:"ð›";s:2:"ο";s:4:"ð›‘";s:2:"Ï€";s:4:"ð›’";s:2:"Ï";s:4:"ð›“";s:2:"Ï‚";s:4:"ð›”";s:2:"σ";s:4:"ð›•";s:2:"Ï„";s:4:"ð›–";s:2:"Ï…";s:4:"ð›—";s:2:"φ";s:4:"ð›˜";s:2:"χ";s:4:"ð›™";s:2:"ψ";s:4:"ð›š";s:2:"ω";s:4:"ð››";s:3:"∂";s:4:"ð›œ";s:2:"ϵ";s:4:"ð›";s:2:"Ï‘";s:4:"ð›ž";s:2:"ϰ";s:4:"ð›Ÿ";s:2:"Ï•";s:4:"ð› ";s:2:"ϱ";s:4:"ð›¡";s:2:"Ï–";s:4:"ð›¢";s:2:"Α";s:4:"ð›£";s:2:"Î’";s:4:"ð›¤";s:2:"Γ";s:4:"ð›¥";s:2:"Δ";s:4:"ð›¦";s:2:"Ε";s:4:"ð›§";s:2:"Ζ";s:4:"ð›¨";s:2:"Η";s:4:"ð›©";s:2:"Θ";s:4:"ð›ª";s:2:"Ι";s:4:"ð›«";s:2:"Κ";s:4:"ð›¬";s:2:"Λ";s:4:"ð›­";s:2:"Μ";s:4:"ð›®";s:2:"Î";s:4:"ð›¯";s:2:"Ξ";s:4:"ð›°";s:2:"Ο";s:4:"ð›±";s:2:"Π";s:4:"ð›²";s:2:"Ρ";s:4:"ð›³";s:2:"Ï´";s:4:"ð›´";s:2:"Σ";s:4:"ð›µ";s:2:"Τ";s:4:"ð›¶";s:2:"Î¥";s:4:"ð›·";s:2:"Φ";s:4:"ð›¸";s:2:"Χ";s:4:"ð›¹";s:2:"Ψ";s:4:"ð›º";s:2:"Ω";s:4:"ð›»";s:3:"∇";s:4:"ð›¼";s:2:"α";s:4:"ð›½";s:2:"β";s:4:"ð›¾";s:2:"γ";s:4:"ð›¿";s:2:"δ";s:4:"ðœ€";s:2:"ε";s:4:"ðœ";s:2:"ζ";s:4:"ðœ‚";s:2:"η";s:4:"ðœƒ";s:2:"θ";s:4:"ðœ„";s:2:"ι";s:4:"ðœ…";s:2:"κ";s:4:"ðœ†";s:2:"λ";s:4:"ðœ‡";s:2:"μ";s:4:"ðœˆ";s:2:"ν";s:4:"ðœ‰";s:2:"ξ";s:4:"ðœŠ";s:2:"ο";s:4:"ðœ‹";s:2:"Ï€";s:4:"ðœŒ";s:2:"Ï";s:4:"ðœ";s:2:"Ï‚";s:4:"ðœŽ";s:2:"σ";s:4:"ðœ";s:2:"Ï„";s:4:"ðœ";s:2:"Ï…";s:4:"ðœ‘";s:2:"φ";s:4:"ðœ’";s:2:"χ";s:4:"ðœ“";s:2:"ψ";s:4:"ðœ”";s:2:"ω";s:4:"ðœ•";s:3:"∂";s:4:"ðœ–";s:2:"ϵ";s:4:"ðœ—";s:2:"Ï‘";s:4:"ðœ˜";s:2:"ϰ";s:4:"ðœ™";s:2:"Ï•";s:4:"ðœš";s:2:"ϱ";s:4:"ðœ›";s:2:"Ï–";s:4:"ðœœ";s:2:"Α";s:4:"ðœ";s:2:"Î’";s:4:"ðœž";s:2:"Γ";s:4:"ðœŸ";s:2:"Δ";s:4:"ðœ ";s:2:"Ε";s:4:"ðœ¡";s:2:"Ζ";s:4:"ðœ¢";s:2:"Η";s:4:"ðœ£";s:2:"Θ";s:4:"ðœ¤";s:2:"Ι";s:4:"ðœ¥";s:2:"Κ";s:4:"ðœ¦";s:2:"Λ";s:4:"ðœ§";s:2:"Μ";s:4:"ðœ¨";s:2:"Î";s:4:"ðœ©";s:2:"Ξ";s:4:"ðœª";s:2:"Ο";s:4:"ðœ«";s:2:"Π";s:4:"ðœ¬";s:2:"Ρ";s:4:"ðœ­";s:2:"Ï´";s:4:"ðœ®";s:2:"Σ";s:4:"ðœ¯";s:2:"Τ";s:4:"ðœ°";s:2:"Î¥";s:4:"ðœ±";s:2:"Φ";s:4:"ðœ²";s:2:"Χ";s:4:"ðœ³";s:2:"Ψ";s:4:"ðœ´";s:2:"Ω";s:4:"ðœµ";s:3:"∇";s:4:"ðœ¶";s:2:"α";s:4:"ðœ·";s:2:"β";s:4:"ðœ¸";s:2:"γ";s:4:"ðœ¹";s:2:"δ";s:4:"ðœº";s:2:"ε";s:4:"ðœ»";s:2:"ζ";s:4:"ðœ¼";s:2:"η";s:4:"ðœ½";s:2:"θ";s:4:"ðœ¾";s:2:"ι";s:4:"ðœ¿";s:2:"κ";s:4:"ð€";s:2:"λ";s:4:"ð";s:2:"μ";s:4:"ð‚";s:2:"ν";s:4:"ðƒ";s:2:"ξ";s:4:"ð„";s:2:"ο";s:4:"ð…";s:2:"Ï€";s:4:"ð†";s:2:"Ï";s:4:"ð‡";s:2:"Ï‚";s:4:"ðˆ";s:2:"σ";s:4:"ð‰";s:2:"Ï„";s:4:"ðŠ";s:2:"Ï…";s:4:"ð‹";s:2:"φ";s:4:"ðŒ";s:2:"χ";s:4:"ð";s:2:"ψ";s:4:"ðŽ";s:2:"ω";s:4:"ð";s:3:"∂";s:4:"ð";s:2:"ϵ";s:4:"ð‘";s:2:"Ï‘";s:4:"ð’";s:2:"ϰ";s:4:"ð“";s:2:"Ï•";s:4:"ð”";s:2:"ϱ";s:4:"ð•";s:2:"Ï–";s:4:"ð–";s:2:"Α";s:4:"ð—";s:2:"Î’";s:4:"ð˜";s:2:"Γ";s:4:"ð™";s:2:"Δ";s:4:"ðš";s:2:"Ε";s:4:"ð›";s:2:"Ζ";s:4:"ðœ";s:2:"Η";s:4:"ð";s:2:"Θ";s:4:"ðž";s:2:"Ι";s:4:"ðŸ";s:2:"Κ";s:4:"ð ";s:2:"Λ";s:4:"ð¡";s:2:"Μ";s:4:"ð¢";s:2:"Î";s:4:"ð£";s:2:"Ξ";s:4:"ð¤";s:2:"Ο";s:4:"ð¥";s:2:"Π";s:4:"ð¦";s:2:"Ρ";s:4:"ð§";s:2:"Ï´";s:4:"ð¨";s:2:"Σ";s:4:"ð©";s:2:"Τ";s:4:"ðª";s:2:"Î¥";s:4:"ð«";s:2:"Φ";s:4:"ð¬";s:2:"Χ";s:4:"ð­";s:2:"Ψ";s:4:"ð®";s:2:"Ω";s:4:"ð¯";s:3:"∇";s:4:"ð°";s:2:"α";s:4:"ð±";s:2:"β";s:4:"ð²";s:2:"γ";s:4:"ð³";s:2:"δ";s:4:"ð´";s:2:"ε";s:4:"ðµ";s:2:"ζ";s:4:"ð¶";s:2:"η";s:4:"ð·";s:2:"θ";s:4:"ð¸";s:2:"ι";s:4:"ð¹";s:2:"κ";s:4:"ðº";s:2:"λ";s:4:"ð»";s:2:"μ";s:4:"ð¼";s:2:"ν";s:4:"ð½";s:2:"ξ";s:4:"ð¾";s:2:"ο";s:4:"ð¿";s:2:"Ï€";s:4:"ðž€";s:2:"Ï";s:4:"ðž";s:2:"Ï‚";s:4:"ðž‚";s:2:"σ";s:4:"ðžƒ";s:2:"Ï„";s:4:"ðž„";s:2:"Ï…";s:4:"ðž…";s:2:"φ";s:4:"ðž†";s:2:"χ";s:4:"ðž‡";s:2:"ψ";s:4:"ðžˆ";s:2:"ω";s:4:"ðž‰";s:3:"∂";s:4:"ðžŠ";s:2:"ϵ";s:4:"ðž‹";s:2:"Ï‘";s:4:"ðžŒ";s:2:"ϰ";s:4:"ðž";s:2:"Ï•";s:4:"ðžŽ";s:2:"ϱ";s:4:"ðž";s:2:"Ï–";s:4:"ðž";s:2:"Α";s:4:"ðž‘";s:2:"Î’";s:4:"ðž’";s:2:"Γ";s:4:"ðž“";s:2:"Δ";s:4:"ðž”";s:2:"Ε";s:4:"ðž•";s:2:"Ζ";s:4:"ðž–";s:2:"Η";s:4:"ðž—";s:2:"Θ";s:4:"ðž˜";s:2:"Ι";s:4:"ðž™";s:2:"Κ";s:4:"ðžš";s:2:"Λ";s:4:"ðž›";s:2:"Μ";s:4:"ðžœ";s:2:"Î";s:4:"ðž";s:2:"Ξ";s:4:"ðžž";s:2:"Ο";s:4:"ðžŸ";s:2:"Π";s:4:"ðž ";s:2:"Ρ";s:4:"ðž¡";s:2:"Ï´";s:4:"ðž¢";s:2:"Σ";s:4:"ðž£";s:2:"Τ";s:4:"ðž¤";s:2:"Î¥";s:4:"ðž¥";s:2:"Φ";s:4:"ðž¦";s:2:"Χ";s:4:"ðž§";s:2:"Ψ";s:4:"ðž¨";s:2:"Ω";s:4:"ðž©";s:3:"∇";s:4:"ðžª";s:2:"α";s:4:"ðž«";s:2:"β";s:4:"ðž¬";s:2:"γ";s:4:"ðž­";s:2:"δ";s:4:"ðž®";s:2:"ε";s:4:"ðž¯";s:2:"ζ";s:4:"ðž°";s:2:"η";s:4:"ðž±";s:2:"θ";s:4:"ðž²";s:2:"ι";s:4:"ðž³";s:2:"κ";s:4:"ðž´";s:2:"λ";s:4:"ðžµ";s:2:"μ";s:4:"ðž¶";s:2:"ν";s:4:"ðž·";s:2:"ξ";s:4:"ðž¸";s:2:"ο";s:4:"ðž¹";s:2:"Ï€";s:4:"ðžº";s:2:"Ï";s:4:"ðž»";s:2:"Ï‚";s:4:"ðž¼";s:2:"σ";s:4:"ðž½";s:2:"Ï„";s:4:"ðž¾";s:2:"Ï…";s:4:"ðž¿";s:2:"φ";s:4:"ðŸ€";s:2:"χ";s:4:"ðŸ";s:2:"ψ";s:4:"ðŸ‚";s:2:"ω";s:4:"ðŸƒ";s:3:"∂";s:4:"ðŸ„";s:2:"ϵ";s:4:"ðŸ…";s:2:"Ï‘";s:4:"ðŸ†";s:2:"ϰ";s:4:"ðŸ‡";s:2:"Ï•";s:4:"ðŸˆ";s:2:"ϱ";s:4:"ðŸ‰";s:2:"Ï–";s:4:"ðŸŠ";s:2:"Ïœ";s:4:"ðŸ‹";s:2:"Ï";s:4:"ðŸŽ";s:1:"0";s:4:"ðŸ";s:1:"1";s:4:"ðŸ";s:1:"2";s:4:"ðŸ‘";s:1:"3";s:4:"ðŸ’";s:1:"4";s:4:"ðŸ“";s:1:"5";s:4:"ðŸ”";s:1:"6";s:4:"ðŸ•";s:1:"7";s:4:"ðŸ–";s:1:"8";s:4:"ðŸ—";s:1:"9";s:4:"ðŸ˜";s:1:"0";s:4:"ðŸ™";s:1:"1";s:4:"ðŸš";s:1:"2";s:4:"ðŸ›";s:1:"3";s:4:"ðŸœ";s:1:"4";s:4:"ðŸ";s:1:"5";s:4:"ðŸž";s:1:"6";s:4:"ðŸŸ";s:1:"7";s:4:"ðŸ ";s:1:"8";s:4:"ðŸ¡";s:1:"9";s:4:"ðŸ¢";s:1:"0";s:4:"ðŸ£";s:1:"1";s:4:"ðŸ¤";s:1:"2";s:4:"ðŸ¥";s:1:"3";s:4:"ðŸ¦";s:1:"4";s:4:"ðŸ§";s:1:"5";s:4:"ðŸ¨";s:1:"6";s:4:"ðŸ©";s:1:"7";s:4:"ðŸª";s:1:"8";s:4:"ðŸ«";s:1:"9";s:4:"ðŸ¬";s:1:"0";s:4:"ðŸ­";s:1:"1";s:4:"ðŸ®";s:1:"2";s:4:"ðŸ¯";s:1:"3";s:4:"ðŸ°";s:1:"4";s:4:"ðŸ±";s:1:"5";s:4:"ðŸ²";s:1:"6";s:4:"ðŸ³";s:1:"7";s:4:"ðŸ´";s:1:"8";s:4:"ðŸµ";s:1:"9";s:4:"ðŸ¶";s:1:"0";s:4:"ðŸ·";s:1:"1";s:4:"ðŸ¸";s:1:"2";s:4:"ðŸ¹";s:1:"3";s:4:"ðŸº";s:1:"4";s:4:"ðŸ»";s:1:"5";s:4:"ðŸ¼";s:1:"6";s:4:"ðŸ½";s:1:"7";s:4:"ðŸ¾";s:1:"8";s:4:"ðŸ¿";s:1:"9";s:4:"𞸀";s:2:"ا";s:4:"ðž¸";s:2:"ب";s:4:"𞸂";s:2:"ج";s:4:"𞸃";s:2:"د";s:4:"𞸅";s:2:"Ùˆ";s:4:"𞸆";s:2:"ز";s:4:"𞸇";s:2:"Ø­";s:4:"𞸈";s:2:"Ø·";s:4:"𞸉";s:2:"ÙŠ";s:4:"𞸊";s:2:"Ùƒ";s:4:"𞸋";s:2:"Ù„";s:4:"𞸌";s:2:"Ù…";s:4:"ðž¸";s:2:"Ù†";s:4:"𞸎";s:2:"س";s:4:"ðž¸";s:2:"ع";s:4:"ðž¸";s:2:"Ù";s:4:"𞸑";s:2:"ص";s:4:"𞸒";s:2:"Ù‚";s:4:"𞸓";s:2:"ر";s:4:"𞸔";s:2:"Ø´";s:4:"𞸕";s:2:"ت";s:4:"𞸖";s:2:"Ø«";s:4:"𞸗";s:2:"Ø®";s:4:"𞸘";s:2:"ذ";s:4:"𞸙";s:2:"ض";s:4:"𞸚";s:2:"ظ";s:4:"𞸛";s:2:"غ";s:4:"𞸜";s:2:"Ù®";s:4:"ðž¸";s:2:"Úº";s:4:"𞸞";s:2:"Ú¡";s:4:"𞸟";s:2:"Ù¯";s:4:"𞸡";s:2:"ب";s:4:"𞸢";s:2:"ج";s:4:"𞸤";s:2:"Ù‡";s:4:"𞸧";s:2:"Ø­";s:4:"𞸩";s:2:"ÙŠ";s:4:"𞸪";s:2:"Ùƒ";s:4:"𞸫";s:2:"Ù„";s:4:"𞸬";s:2:"Ù…";s:4:"𞸭";s:2:"Ù†";s:4:"𞸮";s:2:"س";s:4:"𞸯";s:2:"ع";s:4:"𞸰";s:2:"Ù";s:4:"𞸱";s:2:"ص";s:4:"𞸲";s:2:"Ù‚";s:4:"𞸴";s:2:"Ø´";s:4:"𞸵";s:2:"ت";s:4:"𞸶";s:2:"Ø«";s:4:"𞸷";s:2:"Ø®";s:4:"𞸹";s:2:"ض";s:4:"𞸻";s:2:"غ";s:4:"𞹂";s:2:"ج";s:4:"𞹇";s:2:"Ø­";s:4:"𞹉";s:2:"ÙŠ";s:4:"𞹋";s:2:"Ù„";s:4:"ðž¹";s:2:"Ù†";s:4:"𞹎";s:2:"س";s:4:"ðž¹";s:2:"ع";s:4:"𞹑";s:2:"ص";s:4:"ðž¹’";s:2:"Ù‚";s:4:"ðž¹”";s:2:"Ø´";s:4:"ðž¹—";s:2:"Ø®";s:4:"ðž¹™";s:2:"ض";s:4:"ðž¹›";s:2:"غ";s:4:"ðž¹";s:2:"Úº";s:4:"𞹟";s:2:"Ù¯";s:4:"𞹡";s:2:"ب";s:4:"ðž¹¢";s:2:"ج";s:4:"𞹤";s:2:"Ù‡";s:4:"ðž¹§";s:2:"Ø­";s:4:"𞹨";s:2:"Ø·";s:4:"𞹩";s:2:"ÙŠ";s:4:"𞹪";s:2:"Ùƒ";s:4:"𞹬";s:2:"Ù…";s:4:"ðž¹­";s:2:"Ù†";s:4:"ðž¹®";s:2:"س";s:4:"𞹯";s:2:"ع";s:4:"ðž¹°";s:2:"Ù";s:4:"ðž¹±";s:2:"ص";s:4:"ðž¹²";s:2:"Ù‚";s:4:"ðž¹´";s:2:"Ø´";s:4:"ðž¹µ";s:2:"ت";s:4:"ðž¹¶";s:2:"Ø«";s:4:"ðž¹·";s:2:"Ø®";s:4:"ðž¹¹";s:2:"ض";s:4:"𞹺";s:2:"ظ";s:4:"ðž¹»";s:2:"غ";s:4:"ðž¹¼";s:2:"Ù®";s:4:"ðž¹¾";s:2:"Ú¡";s:4:"𞺀";s:2:"ا";s:4:"ðžº";s:2:"ب";s:4:"𞺂";s:2:"ج";s:4:"𞺃";s:2:"د";s:4:"𞺄";s:2:"Ù‡";s:4:"𞺅";s:2:"Ùˆ";s:4:"𞺆";s:2:"ز";s:4:"𞺇";s:2:"Ø­";s:4:"𞺈";s:2:"Ø·";s:4:"𞺉";s:2:"ÙŠ";s:4:"𞺋";s:2:"Ù„";s:4:"𞺌";s:2:"Ù…";s:4:"ðžº";s:2:"Ù†";s:4:"𞺎";s:2:"س";s:4:"ðžº";s:2:"ع";s:4:"ðžº";s:2:"Ù";s:4:"𞺑";s:2:"ص";s:4:"𞺒";s:2:"Ù‚";s:4:"𞺓";s:2:"ر";s:4:"𞺔";s:2:"Ø´";s:4:"𞺕";s:2:"ت";s:4:"𞺖";s:2:"Ø«";s:4:"𞺗";s:2:"Ø®";s:4:"𞺘";s:2:"ذ";s:4:"𞺙";s:2:"ض";s:4:"𞺚";s:2:"ظ";s:4:"𞺛";s:2:"غ";s:4:"𞺡";s:2:"ب";s:4:"𞺢";s:2:"ج";s:4:"𞺣";s:2:"د";s:4:"𞺥";s:2:"Ùˆ";s:4:"𞺦";s:2:"ز";s:4:"𞺧";s:2:"Ø­";s:4:"𞺨";s:2:"Ø·";s:4:"𞺩";s:2:"ÙŠ";s:4:"𞺫";s:2:"Ù„";s:4:"𞺬";s:2:"Ù…";s:4:"𞺭";s:2:"Ù†";s:4:"𞺮";s:2:"س";s:4:"𞺯";s:2:"ع";s:4:"𞺰";s:2:"Ù";s:4:"𞺱";s:2:"ص";s:4:"𞺲";s:2:"Ù‚";s:4:"𞺳";s:2:"ر";s:4:"𞺴";s:2:"Ø´";s:4:"𞺵";s:2:"ت";s:4:"𞺶";s:2:"Ø«";s:4:"𞺷";s:2:"Ø®";s:4:"𞺸";s:2:"ذ";s:4:"𞺹";s:2:"ض";s:4:"𞺺";s:2:"ظ";s:4:"𞺻";s:2:"غ";s:4:"🄀";s:2:"0.";s:4:"ðŸ„";s:2:"0,";s:4:"🄂";s:2:"1,";s:4:"🄃";s:2:"2,";s:4:"🄄";s:2:"3,";s:4:"🄅";s:2:"4,";s:4:"🄆";s:2:"5,";s:4:"🄇";s:2:"6,";s:4:"🄈";s:2:"7,";s:4:"🄉";s:2:"8,";s:4:"🄊";s:2:"9,";s:4:"ðŸ„";s:3:"(A)";s:4:"🄑";s:3:"(B)";s:4:"🄒";s:3:"(C)";s:4:"🄓";s:3:"(D)";s:4:"🄔";s:3:"(E)";s:4:"🄕";s:3:"(F)";s:4:"🄖";s:3:"(G)";s:4:"🄗";s:3:"(H)";s:4:"🄘";s:3:"(I)";s:4:"🄙";s:3:"(J)";s:4:"🄚";s:3:"(K)";s:4:"🄛";s:3:"(L)";s:4:"🄜";s:3:"(M)";s:4:"ðŸ„";s:3:"(N)";s:4:"🄞";s:3:"(O)";s:4:"🄟";s:3:"(P)";s:4:"🄠";s:3:"(Q)";s:4:"🄡";s:3:"(R)";s:4:"🄢";s:3:"(S)";s:4:"🄣";s:3:"(T)";s:4:"🄤";s:3:"(U)";s:4:"🄥";s:3:"(V)";s:4:"🄦";s:3:"(W)";s:4:"🄧";s:3:"(X)";s:4:"🄨";s:3:"(Y)";s:4:"🄩";s:3:"(Z)";s:4:"🄪";s:7:"〔S〕";s:4:"🄫";s:3:"(C)";s:4:"🄬";s:3:"(R)";s:4:"🄭";s:4:"(CD)";s:4:"🄮";s:4:"(WZ)";s:4:"🄰";s:1:"A";s:4:"🄱";s:1:"B";s:4:"🄲";s:1:"C";s:4:"🄳";s:1:"D";s:4:"🄴";s:1:"E";s:4:"🄵";s:1:"F";s:4:"🄶";s:1:"G";s:4:"🄷";s:1:"H";s:4:"🄸";s:1:"I";s:4:"🄹";s:1:"J";s:4:"🄺";s:1:"K";s:4:"🄻";s:1:"L";s:4:"🄼";s:1:"M";s:4:"🄽";s:1:"N";s:4:"🄾";s:1:"O";s:4:"🄿";s:1:"P";s:4:"🅀";s:1:"Q";s:4:"ðŸ…";s:1:"R";s:4:"🅂";s:1:"S";s:4:"🅃";s:1:"T";s:4:"🅄";s:1:"U";s:4:"🅅";s:1:"V";s:4:"🅆";s:1:"W";s:4:"🅇";s:1:"X";s:4:"🅈";s:1:"Y";s:4:"🅉";s:1:"Z";s:4:"🅊";s:2:"HV";s:4:"🅋";s:2:"MV";s:4:"🅌";s:2:"SD";s:4:"ðŸ…";s:2:"SS";s:4:"🅎";s:3:"PPV";s:4:"ðŸ…";s:2:"WC";s:4:"ðŸ†";s:2:"DJ";s:4:"🈀";s:6:"ã»ã‹";s:4:"ðŸˆ";s:6:"ココ";s:4:"🈂";s:3:"サ";s:4:"ðŸˆ";s:3:"手";s:4:"🈑";s:3:"å­—";s:4:"🈒";s:3:"åŒ";s:4:"🈓";s:3:"デ";s:4:"🈔";s:3:"二";s:4:"🈕";s:3:"多";s:4:"🈖";s:3:"è§£";s:4:"🈗";s:3:"天";s:4:"🈘";s:3:"交";s:4:"🈙";s:3:"映";s:4:"🈚";s:3:"ç„¡";s:4:"🈛";s:3:"æ–™";s:4:"🈜";s:3:"å‰";s:4:"ðŸˆ";s:3:"後";s:4:"🈞";s:3:"å†";s:4:"🈟";s:3:"æ–°";s:4:"🈠";s:3:"åˆ";s:4:"🈡";s:3:"終";s:4:"🈢";s:3:"生";s:4:"🈣";s:3:"販";s:4:"🈤";s:3:"声";s:4:"🈥";s:3:"å¹";s:4:"🈦";s:3:"æ¼”";s:4:"🈧";s:3:"投";s:4:"🈨";s:3:"æ•";s:4:"🈩";s:3:"一";s:4:"🈪";s:3:"三";s:4:"🈫";s:3:"éŠ";s:4:"🈬";s:3:"å·¦";s:4:"🈭";s:3:"中";s:4:"🈮";s:3:"å³";s:4:"🈯";s:3:"指";s:4:"🈰";s:3:"èµ°";s:4:"🈱";s:3:"打";s:4:"🈲";s:3:"ç¦";s:4:"🈳";s:3:"空";s:4:"🈴";s:3:"åˆ";s:4:"🈵";s:3:"満";s:4:"🈶";s:3:"有";s:4:"🈷";s:3:"月";s:4:"🈸";s:3:"申";s:4:"🈹";s:3:"割";s:4:"🈺";s:3:"å–¶";s:4:"🉀";s:9:"〔本〕";s:4:"ðŸ‰";s:9:"〔三〕";s:4:"🉂";s:9:"〔二〕";s:4:"🉃";s:9:"〔安〕";s:4:"🉄";s:9:"〔点〕";s:4:"🉅";s:9:"〔打〕";s:4:"🉆";s:9:"〔盗〕";s:4:"🉇";s:9:"〔å‹ã€•";s:4:"🉈";s:9:"〔敗〕";s:4:"ðŸ‰";s:5:"(å¾—)";s:4:"🉑";s:5:"(å¯)";s:4:"丽";s:3:"丽";s:4:"ð¯ ";s:3:"丸";s:4:"乁";s:3:"ä¹";s:4:"𠄢";s:4:"ð „¢";s:4:"你";s:3:"ä½ ";s:4:"侮";s:3:"ä¾®";s:4:"侻";s:3:"ä¾»";s:4:"倂";s:3:"倂";s:4:"偺";s:3:"åº";s:4:"備";s:3:"å‚™";s:4:"僧";s:3:"僧";s:4:"像";s:3:"åƒ";s:4:"㒞";s:3:"ã’ž";s:4:"ð¯ ";s:4:"𠘺";s:4:"免";s:3:"å…";s:4:"ð¯ ";s:3:"å…”";s:4:"ð¯ ";s:3:"å…¤";s:4:"具";s:3:"å…·";s:4:"𠔜";s:4:"𠔜";s:4:"㒹";s:3:"ã’¹";s:4:"內";s:3:"å…§";s:4:"再";s:3:"å†";s:4:"𠕋";s:4:"ð •‹";s:4:"冗";s:3:"冗";s:4:"冤";s:3:"冤";s:4:"仌";s:3:"仌";s:4:"冬";s:3:"冬";s:4:"况";s:3:"况";s:4:"𩇟";s:4:"𩇟";s:4:"ð¯ ";s:3:"凵";s:4:"刃";s:3:"刃";s:4:"㓟";s:3:"㓟";s:4:"刻";s:3:"刻";s:4:"剆";s:3:"剆";s:4:"割";s:3:"割";s:4:"剷";s:3:"剷";s:4:"㔕";s:3:"㔕";s:4:"勇";s:3:"勇";s:4:"勉";s:3:"勉";s:4:"勤";s:3:"勤";s:4:"勺";s:3:"勺";s:4:"包";s:3:"包";s:4:"匆";s:3:"匆";s:4:"北";s:3:"北";s:4:"卉";s:3:"å‰";s:4:"卑";s:3:"å‘";s:4:"博";s:3:"åš";s:4:"即";s:3:"å³";s:4:"卽";s:3:"å½";s:4:"卿";s:3:"å¿";s:4:"卿";s:3:"å¿";s:4:"卿";s:3:"å¿";s:4:"𠨬";s:4:"𠨬";s:4:"灰";s:3:"ç°";s:4:"及";s:3:"åŠ";s:4:"叟";s:3:"åŸ";s:4:"𠭣";s:4:"ð ­£";s:4:"叫";s:3:"å«";s:4:"叱";s:3:"å±";s:4:"吆";s:3:"å†";s:4:"咞";s:3:"å’ž";s:4:"吸";s:3:"å¸";s:4:"呈";s:3:"呈";s:4:"周";s:3:"周";s:4:"咢";s:3:"å’¢";s:4:"ð¯¡";s:3:"å“¶";s:4:"唐";s:3:"å”";s:4:"啓";s:3:"å•“";s:4:"啣";s:3:"å•£";s:4:"善";s:3:"å–„";s:4:"善";s:3:"å–„";s:4:"喙";s:3:"å–™";s:4:"喫";s:3:"å–«";s:4:"喳";s:3:"å–³";s:4:"嗂";s:3:"å—‚";s:4:"圖";s:3:"圖";s:4:"嘆";s:3:"嘆";s:4:"ð¯¡";s:3:"圗";s:4:"噑";s:3:"噑";s:4:"ð¯¡";s:3:"å™´";s:4:"ð¯¡";s:3:"切";s:4:"壮";s:3:"壮";s:4:"城";s:3:"城";s:4:"埴";s:3:"埴";s:4:"堍";s:3:"å ";s:4:"型";s:3:"åž‹";s:4:"堲";s:3:"å ²";s:4:"報";s:3:"å ±";s:4:"墬";s:3:"墬";s:4:"𡓤";s:4:"𡓤";s:4:"売";s:3:"売";s:4:"壷";s:3:"壷";s:4:"夆";s:3:"夆";s:4:"ð¯¡";s:3:"多";s:4:"夢";s:3:"夢";s:4:"奢";s:3:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:4:"姬";s:3:"姬";s:4:"娛";s:3:"娛";s:4:"娧";s:3:"娧";s:4:"姘";s:3:"姘";s:4:"婦";s:3:"婦";s:4:"㛮";s:3:"ã›®";s:4:"㛼";s:3:"㛼";s:4:"嬈";s:3:"嬈";s:4:"嬾";s:3:"嬾";s:4:"嬾";s:3:"嬾";s:4:"𡧈";s:4:"𡧈";s:4:"寃";s:3:"寃";s:4:"寘";s:3:"寘";s:4:"寧";s:3:"寧";s:4:"寳";s:3:"寳";s:4:"𡬘";s:4:"𡬘";s:4:"寿";s:3:"寿";s:4:"将";s:3:"å°†";s:4:"当";s:3:"当";s:4:"尢";s:3:"å°¢";s:4:"㞁";s:3:"ãž";s:4:"屠";s:3:"å± ";s:4:"屮";s:3:"å±®";s:4:"峀";s:3:"å³€";s:4:"岍";s:3:"å²";s:4:"𡷤";s:4:"ð¡·¤";s:4:"嵃";s:3:"嵃";s:4:"𡷦";s:4:"ð¡·¦";s:4:"嵮";s:3:"åµ®";s:4:"嵫";s:3:"嵫";s:4:"嵼";s:3:"åµ¼";s:4:"ð¯¢";s:3:"å·¡";s:4:"巢";s:3:"å·¢";s:4:"㠯";s:3:"ã ¯";s:4:"巽";s:3:"å·½";s:4:"帨";s:3:"帨";s:4:"帽";s:3:"帽";s:4:"幩";s:3:"幩";s:4:"㡢";s:3:"ã¡¢";s:4:"𢆃";s:4:"𢆃";s:4:"㡼";s:3:"㡼";s:4:"庰";s:3:"庰";s:4:"庳";s:3:"庳";s:4:"ð¯¢";s:3:"庶";s:4:"廊";s:3:"廊";s:4:"ð¯¢";s:4:"𪎒";s:4:"ð¯¢";s:3:"廾";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"舁";s:3:"èˆ";s:4:"弢";s:3:"å¼¢";s:4:"弢";s:3:"å¼¢";s:4:"㣇";s:3:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:4:"形";s:3:"å½¢";s:4:"彫";s:3:"彫";s:4:"㣣";s:3:"㣣";s:4:"徚";s:3:"徚";s:4:"ð¯¢";s:3:"å¿";s:4:"志";s:3:"å¿—";s:4:"忹";s:3:"忹";s:4:"悁";s:3:"æ‚";s:4:"㤺";s:3:"㤺";s:4:"㤜";s:3:"㤜";s:4:"悔";s:3:"æ‚”";s:4:"𢛔";s:4:"𢛔";s:4:"惇";s:3:"惇";s:4:"慈";s:3:"æ…ˆ";s:4:"慌";s:3:"æ…Œ";s:4:"慎";s:3:"æ…Ž";s:4:"慌";s:3:"æ…Œ";s:4:"慺";s:3:"æ…º";s:4:"憎";s:3:"憎";s:4:"憲";s:3:"憲";s:4:"憤";s:3:"憤";s:4:"憯";s:3:"憯";s:4:"懞";s:3:"懞";s:4:"懲";s:3:"懲";s:4:"懶";s:3:"懶";s:4:"成";s:3:"æˆ";s:4:"戛";s:3:"戛";s:4:"扝";s:3:"æ‰";s:4:"抱";s:3:"抱";s:4:"拔";s:3:"æ‹”";s:4:"捐";s:3:"æ";s:4:"𢬌";s:4:"𢬌";s:4:"挽";s:3:"挽";s:4:"拼";s:3:"拼";s:4:"捨";s:3:"æ¨";s:4:"掃";s:3:"掃";s:4:"揤";s:3:"æ¤";s:4:"𢯱";s:4:"𢯱";s:4:"搢";s:3:"æ¢";s:4:"揅";s:3:"æ…";s:4:"ð¯£";s:3:"掩";s:4:"㨮";s:3:"㨮";s:4:"摩";s:3:"æ‘©";s:4:"摾";s:3:"摾";s:4:"撝";s:3:"æ’";s:4:"摷";s:3:"æ‘·";s:4:"㩬";s:3:"㩬";s:4:"敏";s:3:"æ•";s:4:"敬";s:3:"敬";s:4:"𣀊";s:4:"𣀊";s:4:"旣";s:3:"æ—£";s:4:"書";s:3:"書";s:4:"ð¯£";s:3:"晉";s:4:"㬙";s:3:"㬙";s:4:"ð¯£";s:3:"æš‘";s:4:"ð¯£";s:3:"㬈";s:4:"㫤";s:3:"㫤";s:4:"冒";s:3:"冒";s:4:"冕";s:3:"冕";s:4:"最";s:3:"最";s:4:"暜";s:3:"æšœ";s:4:"肭";s:3:"è‚­";s:4:"䏙";s:3:"ä™";s:4:"朗";s:3:"朗";s:4:"望";s:3:"望";s:4:"朡";s:3:"朡";s:4:"杞";s:3:"æž";s:4:"杓";s:3:"æ“";s:4:"ð¯£";s:4:"ð£ƒ";s:4:"㭉";s:3:"ã­‰";s:4:"柺";s:3:"柺";s:4:"枅";s:3:"æž…";s:4:"桒";s:3:"æ¡’";s:4:"梅";s:3:"梅";s:4:"𣑭";s:4:"𣑭";s:4:"梎";s:3:"梎";s:4:"栟";s:3:"æ Ÿ";s:4:"椔";s:3:"椔";s:4:"㮝";s:3:"ã®";s:4:"楂";s:3:"楂";s:4:"榣";s:3:"榣";s:4:"槪";s:3:"槪";s:4:"檨";s:3:"檨";s:4:"𣚣";s:4:"𣚣";s:4:"櫛";s:3:"æ«›";s:4:"㰘";s:3:"ã°˜";s:4:"次";s:3:"次";s:4:"𣢧";s:4:"𣢧";s:4:"歔";s:3:"æ­”";s:4:"㱎";s:3:"㱎";s:4:"歲";s:3:"æ­²";s:4:"殟";s:3:"殟";s:4:"殺";s:3:"殺";s:4:"殻";s:3:"æ®»";s:4:"𣪍";s:4:"ð£ª";s:4:"𡴋";s:4:"ð¡´‹";s:4:"𣫺";s:4:"𣫺";s:4:"汎";s:3:"汎";s:4:"𣲼";s:4:"𣲼";s:4:"沿";s:3:"沿";s:4:"泍";s:3:"æ³";s:4:"汧";s:3:"æ±§";s:4:"洖";s:3:"æ´–";s:4:"派";s:3:"æ´¾";s:4:"ð¯¤";s:3:"æµ·";s:4:"流";s:3:"æµ";s:4:"浩";s:3:"浩";s:4:"浸";s:3:"浸";s:4:"涅";s:3:"æ¶…";s:4:"𣴞";s:4:"𣴞";s:4:"洴";s:3:"æ´´";s:4:"港";s:3:"港";s:4:"湮";s:3:"æ¹®";s:4:"㴳";s:3:"ã´³";s:4:"滋";s:3:"滋";s:4:"滇";s:3:"滇";s:4:"ð¯¤";s:4:"𣻑";s:4:"淹";s:3:"æ·¹";s:4:"ð¯¤";s:3:"æ½®";s:4:"ð¯¤";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:4:"濆";s:3:"濆";s:4:"瀹";s:3:"瀹";s:4:"瀞";s:3:"瀞";s:4:"瀛";s:3:"瀛";s:4:"㶖";s:3:"ã¶–";s:4:"灊";s:3:"çŠ";s:4:"災";s:3:"ç½";s:4:"灷";s:3:"ç·";s:4:"炭";s:3:"ç‚­";s:4:"𠔥";s:4:"𠔥";s:4:"煅";s:3:"ç……";s:4:"ð¯¤";s:4:"𤉣";s:4:"熜";s:3:"熜";s:4:"𤎫";s:4:"𤎫";s:4:"爨";s:3:"爨";s:4:"爵";s:3:"爵";s:4:"牐";s:3:"ç‰";s:4:"𤘈";s:4:"𤘈";s:4:"犀";s:3:"犀";s:4:"犕";s:3:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:4:"獺";s:3:"çº";s:4:"王";s:3:"王";s:4:"㺬";s:3:"㺬";s:4:"玥";s:3:"玥";s:4:"㺸";s:3:"㺸";s:4:"㺸";s:3:"㺸";s:4:"瑇";s:3:"瑇";s:4:"瑜";s:3:"瑜";s:4:"瑱";s:3:"瑱";s:4:"璅";s:3:"ç’…";s:4:"瓊";s:3:"瓊";s:4:"㼛";s:3:"ã¼›";s:4:"甤";s:3:"甤";s:4:"𤰶";s:4:"𤰶";s:4:"甾";s:3:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"異";s:3:"ç•°";s:4:"𢆟";s:4:"𢆟";s:4:"瘐";s:3:"ç˜";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"ð¥„";s:4:"㿼";s:3:"㿼";s:4:"䀈";s:3:"䀈";s:4:"直";s:3:"ç›´";s:4:"ð¯¥";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:4:"眞";s:3:"眞";s:4:"真";s:3:"真";s:4:"真";s:3:"真";s:4:"睊";s:3:"çŠ";s:4:"䀹";s:3:"䀹";s:4:"瞋";s:3:"çž‹";s:4:"䁆";s:3:"ä†";s:4:"䂖";s:3:"ä‚–";s:4:"ð¯¥";s:4:"ð¥";s:4:"硎";s:3:"硎";s:4:"ð¯¥";s:3:"碌";s:4:"ð¯¥";s:3:"磌";s:4:"䃣";s:3:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"祖";s:3:"祖";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:4:"福";s:3:"ç¦";s:4:"秫";s:3:"ç§«";s:4:"䄯";s:3:"䄯";s:4:"穀";s:3:"ç©€";s:4:"穊";s:3:"穊";s:4:"穏";s:3:"ç©";s:4:"𥥼";s:4:"𥥼";s:4:"ð¯¥";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"竮";s:3:"ç«®";s:4:"䈂";s:3:"䈂";s:4:"𥮫";s:4:"𥮫";s:4:"篆";s:3:"篆";s:4:"築";s:3:"築";s:4:"䈧";s:3:"䈧";s:4:"𥲀";s:4:"𥲀";s:4:"糒";s:3:"ç³’";s:4:"䊠";s:3:"䊠";s:4:"糨";s:3:"糨";s:4:"糣";s:3:"ç³£";s:4:"紀";s:3:"ç´€";s:4:"𥾆";s:4:"𥾆";s:4:"絣";s:3:"çµ£";s:4:"䌁";s:3:"äŒ";s:4:"緇";s:3:"ç·‡";s:4:"縂";s:3:"縂";s:4:"繅";s:3:"ç¹…";s:4:"䌴";s:3:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:4:"䍙";s:3:"ä™";s:4:"𦋙";s:4:"𦋙";s:4:"罺";s:3:"罺";s:4:"𦌾";s:4:"𦌾";s:4:"羕";s:3:"羕";s:4:"翺";s:3:"翺";s:4:"者";s:3:"者";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:4:"聠";s:3:"è ";s:4:"𦖨";s:4:"𦖨";s:4:"聰";s:3:"è°";s:4:"𣍟";s:4:"ð£Ÿ";s:4:"ð¯¦";s:3:"ä•";s:4:"育";s:3:"育";s:4:"脃";s:3:"脃";s:4:"䐋";s:3:"ä‹";s:4:"脾";s:3:"脾";s:4:"媵";s:3:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:4:"舁";s:3:"èˆ";s:4:"舄";s:3:"舄";s:4:"ð¯¦";s:3:"辞";s:4:"䑫";s:3:"ä‘«";s:4:"ð¯¦";s:3:"芑";s:4:"ð¯¦";s:3:"芋";s:4:"芝";s:3:"èŠ";s:4:"劳";s:3:"劳";s:4:"花";s:3:"花";s:4:"芳";s:3:"芳";s:4:"芽";s:3:"芽";s:4:"苦";s:3:"苦";s:4:"𦬼";s:4:"𦬼";s:4:"若";s:3:"è‹¥";s:4:"茝";s:3:"èŒ";s:4:"荣";s:3:"è£";s:4:"莭";s:3:"莭";s:4:"茣";s:3:"茣";s:4:"ð¯¦";s:3:"莽";s:4:"菧";s:3:"è§";s:4:"著";s:3:"è‘—";s:4:"荓";s:3:"è“";s:4:"菊";s:3:"èŠ";s:4:"菌";s:3:"èŒ";s:4:"菜";s:3:"èœ";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:4:"䔫";s:3:"䔫";s:4:"蓱";s:3:"蓱";s:4:"蓳";s:3:"蓳";s:4:"蔖";s:3:"è”–";s:4:"𧏊";s:4:"ð§Š";s:4:"蕤";s:3:"蕤";s:4:"𦼬";s:4:"𦼬";s:4:"䕝";s:3:"ä•";s:4:"䕡";s:3:"ä•¡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:4:"䕫";s:3:"ä•«";s:4:"虐";s:3:"è™";s:4:"虜";s:3:"虜";s:4:"虧";s:3:"è™§";s:4:"虩";s:3:"虩";s:4:"蚩";s:3:"èš©";s:4:"蚈";s:3:"蚈";s:4:"蜎";s:3:"蜎";s:4:"蛢";s:3:"蛢";s:4:"蝹";s:3:"è¹";s:4:"蜨";s:3:"蜨";s:4:"蝫";s:3:"è«";s:4:"螆";s:3:"螆";s:4:"䗗";s:3:"ä——";s:4:"蟡";s:3:"蟡";s:4:"ð¯§";s:3:"è ";s:4:"䗹";s:3:"ä—¹";s:4:"衠";s:3:"è¡ ";s:4:"衣";s:3:"è¡£";s:4:"𧙧";s:4:"ð§™§";s:4:"裗";s:3:"裗";s:4:"裞";s:3:"裞";s:4:"䘵";s:3:"䘵";s:4:"裺";s:3:"裺";s:4:"㒻";s:3:"ã’»";s:4:"𧢮";s:4:"ð§¢®";s:4:"𧥦";s:4:"𧥦";s:4:"ð¯§";s:3:"äš¾";s:4:"䛇";s:3:"䛇";s:4:"ð¯§";s:3:"誠";s:4:"ð¯§";s:3:"è«­";s:4:"變";s:3:"變";s:4:"豕";s:3:"豕";s:4:"𧲨";s:4:"𧲨";s:4:"貫";s:3:"貫";s:4:"賁";s:3:"è³";s:4:"贛";s:3:"è´›";s:4:"起";s:3:"èµ·";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"ð  „";s:4:"跋";s:3:"è·‹";s:4:"趼";s:3:"è¶¼";s:4:"跰";s:3:"è·°";s:4:"ð¯§";s:4:"𠣞";s:4:"軔";s:3:"è»”";s:4:"輸";s:3:"輸";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:4:"邔";s:3:"é‚”";s:4:"郱";s:3:"郱";s:4:"鄑";s:3:"é„‘";s:4:"𨜮";s:4:"𨜮";s:4:"鄛";s:3:"é„›";s:4:"鈸";s:3:"鈸";s:4:"鋗";s:3:"é‹—";s:4:"鋘";s:3:"鋘";s:4:"鉼";s:3:"鉼";s:4:"鏹";s:3:"é¹";s:4:"鐕";s:3:"é•";s:4:"𨯺";s:4:"𨯺";s:4:"開";s:3:"é–‹";s:4:"䦕";s:3:"䦕";s:4:"閷";s:3:"é–·";s:4:"𨵷";s:4:"𨵷";s:4:"䧦";s:3:"䧦";s:4:"雃";s:3:"雃";s:4:"嶲";s:3:"å¶²";s:4:"霣";s:3:"霣";s:4:"𩅅";s:4:"ð©……";s:4:"𩈚";s:4:"𩈚";s:4:"䩮";s:3:"ä©®";s:4:"䩶";s:3:"ä©¶";s:4:"韠";s:3:"韠";s:4:"𩐊";s:4:"ð©Š";s:4:"䪲";s:3:"䪲";s:4:"𩒖";s:4:"ð©’–";s:4:"頋";s:3:"é ‹";s:4:"頋";s:3:"é ‹";s:4:"頩";s:3:"é ©";s:4:"ð¯¨";s:4:"ð©–¶";s:4:"飢";s:3:"飢";s:4:"䬳";s:3:"䬳";s:4:"餩";s:3:"餩";s:4:"馧";s:3:"馧";s:4:"駂";s:3:"é§‚";s:4:"駾";s:3:"é§¾";s:4:"䯎";s:3:"䯎";s:4:"𩬰";s:4:"𩬰";s:4:"鬒";s:3:"鬒";s:4:"鱀";s:3:"é±€";s:4:"鳽";s:3:"é³½";s:4:"ð¯¨";s:3:"䳎";s:4:"䳭";s:3:"ä³­";s:4:"ð¯¨";s:3:"éµ§";s:4:"ð¯¨";s:4:"𪃎";s:4:"䳸";s:3:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:4:"麻";s:3:"麻";s:4:"䵖";s:3:"äµ–";s:4:"黹";s:3:"黹";s:4:"黾";s:3:"黾";s:4:"鼅";s:3:"é¼…";s:4:"鼏";s:3:"é¼";s:4:"鼖";s:3:"é¼–";s:4:"鼻";s:3:"é¼»";s:4:"ð¯¨";s:4:"𪘀";s:2:"Æ";s:2:"AE";s:2:"Ã";s:1:"D";s:2:"Ø";s:1:"O";s:2:"Þ";s:2:"TH";s:2:"ß";s:2:"ss";s:2:"æ";s:2:"ae";s:2:"ð";s:1:"d";s:2:"ø";s:1:"o";s:2:"þ";s:2:"th";s:2:"Ä";s:1:"D";s:2:"Ä‘";s:1:"d";s:2:"Ħ";s:1:"H";s:2:"ħ";s:1:"h";s:2:"ı";s:1:"i";s:2:"ĸ";s:1:"q";s:2:"Å";s:1:"L";s:2:"Å‚";s:1:"l";s:2:"ÅŠ";s:1:"N";s:2:"Å‹";s:1:"n";s:2:"Å’";s:2:"OE";s:2:"Å“";s:2:"oe";s:2:"Ŧ";s:1:"T";s:2:"ŧ";s:1:"t";s:2:"Æ€";s:1:"b";s:2:"Æ";s:1:"B";s:2:"Æ‚";s:1:"B";s:2:"ƃ";s:1:"b";s:2:"Ƈ";s:1:"C";s:2:"ƈ";s:1:"c";s:2:"Ɖ";s:1:"D";s:2:"ÆŠ";s:1:"D";s:2:"Æ‹";s:1:"D";s:2:"ÆŒ";s:1:"d";s:2:"Æ";s:1:"E";s:2:"Æ‘";s:1:"F";s:2:"Æ’";s:1:"f";s:2:"Æ“";s:1:"G";s:2:"Æ•";s:2:"hv";s:2:"Æ–";s:1:"I";s:2:"Æ—";s:1:"I";s:2:"Ƙ";s:1:"K";s:2:"Æ™";s:1:"k";s:2:"Æš";s:1:"l";s:2:"Æ";s:1:"N";s:2:"Æž";s:1:"n";s:2:"Æ¢";s:2:"OI";s:2:"Æ£";s:2:"oi";s:2:"Ƥ";s:1:"P";s:2:"Æ¥";s:1:"p";s:2:"Æ«";s:1:"t";s:2:"Ƭ";s:1:"T";s:2:"Æ­";s:1:"t";s:2:"Æ®";s:1:"T";s:2:"Ʋ";s:1:"V";s:2:"Ƴ";s:1:"Y";s:2:"Æ´";s:1:"y";s:2:"Ƶ";s:1:"Z";s:2:"ƶ";s:1:"z";s:2:"Ǥ";s:1:"G";s:2:"Ç¥";s:1:"g";s:2:"È¡";s:1:"d";s:2:"Ȥ";s:1:"Z";s:2:"È¥";s:1:"z";s:2:"È´";s:1:"l";s:2:"ȵ";s:1:"n";s:2:"ȶ";s:1:"t";s:2:"È·";s:1:"j";s:2:"ȸ";s:2:"db";s:2:"ȹ";s:2:"qp";s:2:"Ⱥ";s:1:"A";s:2:"È»";s:1:"C";s:2:"ȼ";s:1:"c";s:2:"Ƚ";s:1:"L";s:2:"Ⱦ";s:1:"T";s:2:"È¿";s:1:"s";s:2:"É€";s:1:"z";s:2:"Ƀ";s:1:"B";s:2:"É„";s:1:"U";s:2:"Ɇ";s:1:"E";s:2:"ɇ";s:1:"e";s:2:"Ɉ";s:1:"J";s:2:"ɉ";s:1:"j";s:2:"ÉŒ";s:1:"R";s:2:"É";s:1:"r";s:2:"ÉŽ";s:1:"Y";s:2:"É";s:1:"y";s:2:"É“";s:1:"b";s:2:"É•";s:1:"c";s:2:"É–";s:1:"d";s:2:"É—";s:1:"d";s:2:"É›";s:1:"e";s:2:"ÉŸ";s:1:"j";s:2:"É ";s:1:"g";s:2:"É¡";s:1:"g";s:2:"É¢";s:1:"G";s:2:"ɦ";s:1:"h";s:2:"ɧ";s:1:"h";s:2:"ɨ";s:1:"i";s:2:"ɪ";s:1:"I";s:2:"É«";s:1:"l";s:2:"ɬ";s:1:"l";s:2:"É­";s:1:"l";s:2:"ɱ";s:1:"m";s:2:"ɲ";s:1:"n";s:2:"ɳ";s:1:"n";s:2:"É´";s:1:"N";s:2:"ɶ";s:2:"OE";s:2:"ɼ";s:1:"r";s:2:"ɽ";s:1:"r";s:2:"ɾ";s:1:"r";s:2:"Ê€";s:1:"R";s:2:"Ê‚";s:1:"s";s:2:"ʈ";s:1:"t";s:2:"ʉ";s:1:"u";s:2:"Ê‹";s:1:"v";s:2:"Ê";s:1:"Y";s:2:"Ê";s:1:"z";s:2:"Ê‘";s:1:"z";s:2:"Ê™";s:1:"B";s:2:"Ê›";s:1:"G";s:2:"Êœ";s:1:"H";s:2:"Ê";s:1:"j";s:2:"ÊŸ";s:1:"L";s:2:"Ê ";s:1:"q";s:2:"Ê£";s:2:"dz";s:2:"Ê¥";s:2:"dz";s:2:"ʦ";s:2:"ts";s:2:"ʪ";s:2:"ls";s:2:"Ê«";s:2:"lz";s:3:"á´€";s:1:"A";s:3:"á´";s:2:"AE";s:3:"á´ƒ";s:1:"B";s:3:"á´„";s:1:"C";s:3:"á´…";s:1:"D";s:3:"á´†";s:1:"D";s:3:"á´‡";s:1:"E";s:3:"á´Š";s:1:"J";s:3:"á´‹";s:1:"K";s:3:"á´Œ";s:1:"L";s:3:"á´";s:1:"M";s:3:"á´";s:1:"O";s:3:"á´˜";s:1:"P";s:3:"á´›";s:1:"T";s:3:"á´œ";s:1:"U";s:3:"á´ ";s:1:"V";s:3:"á´¡";s:1:"W";s:3:"á´¢";s:1:"Z";s:3:"ᵫ";s:2:"ue";s:3:"ᵬ";s:1:"b";s:3:"áµ­";s:1:"d";s:3:"áµ®";s:1:"f";s:3:"ᵯ";s:1:"m";s:3:"áµ°";s:1:"n";s:3:"áµ±";s:1:"p";s:3:"áµ²";s:1:"r";s:3:"áµ³";s:1:"r";s:3:"áµ´";s:1:"s";s:3:"áµµ";s:1:"t";s:3:"áµ¶";s:1:"z";s:3:"ᵺ";s:2:"th";s:3:"áµ»";s:1:"I";s:3:"áµ½";s:1:"p";s:3:"áµ¾";s:1:"U";s:3:"á¶€";s:1:"b";s:3:"á¶";s:1:"d";s:3:"á¶‚";s:1:"f";s:3:"ᶃ";s:1:"g";s:3:"á¶„";s:1:"k";s:3:"á¶…";s:1:"l";s:3:"ᶆ";s:1:"m";s:3:"ᶇ";s:1:"n";s:3:"ᶈ";s:1:"p";s:3:"ᶉ";s:1:"r";s:3:"á¶Š";s:1:"s";s:3:"á¶Œ";s:1:"v";s:3:"á¶";s:1:"x";s:3:"á¶Ž";s:1:"z";s:3:"á¶";s:1:"a";s:3:"á¶‘";s:1:"d";s:3:"á¶’";s:1:"e";s:3:"á¶“";s:1:"e";s:3:"á¶–";s:1:"i";s:3:"á¶™";s:1:"u";s:3:"ẜ";s:1:"s";s:3:"áº";s:1:"s";s:3:"ẞ";s:2:"SS";s:3:"Ỻ";s:2:"LL";s:3:"á»»";s:2:"ll";s:3:"Ỽ";s:1:"V";s:3:"ỽ";s:1:"v";s:3:"Ỿ";s:1:"Y";s:3:"ỿ";s:1:"y";s:2:"©";s:3:"(C)";s:2:"®";s:3:"(R)";s:3:"â‚ ";s:2:"CE";s:3:"â‚¢";s:2:"Cr";s:3:"â‚£";s:3:"Fr.";s:3:"₤";s:2:"L.";s:3:"â‚§";s:3:"Pts";s:3:"₹";s:2:"Rs";s:3:"℞";s:2:"Rx";s:3:"〇";s:1:"0";s:3:"‘";s:1:"'";s:3:"’";s:1:"'";s:3:"‚";s:1:",";s:3:"‛";s:1:"'";s:3:"“";s:1:""";s:3:"â€";s:1:""";s:3:"„";s:2:",,";s:3:"‟";s:1:""";s:3:"′";s:1:"'";s:3:"ã€";s:1:""";s:3:"〞";s:1:""";s:2:"«";s:2:"<<";s:2:"»";s:2:">>";s:3:"‹";s:1:"<";s:3:"›";s:1:">";s:3:"â€";s:1:"-";s:3:"‑";s:1:"-";s:3:"‒";s:1:"-";s:3:"–";s:1:"-";s:3:"—";s:1:"-";s:3:"―";s:1:"-";s:3:"︱";s:1:"-";s:3:"︲";s:1:"-";s:3:"‖";s:2:"||";s:3:"â„";s:1:"/";s:3:"â…";s:1:"[";s:3:"â†";s:1:"]";s:3:"âŽ";s:1:"*";s:3:"ã€";s:1:",";s:3:"。";s:1:".";s:3:"〈";s:1:"<";s:3:"〉";s:1:">";s:3:"《";s:2:"<<";s:3:"》";s:2:">>";s:3:"〔";s:1:"[";s:3:"〕";s:1:"]";s:3:"〘";s:1:"[";s:3:"〙";s:1:"]";s:3:"〚";s:1:"[";s:3:"〛";s:1:"]";s:3:"ï¸";s:1:",";s:3:"︑";s:1:",";s:3:"︒";s:1:".";s:3:"︓";s:1:":";s:3:"︔";s:1:";";s:3:"︕";s:1:"!";s:3:"︖";s:1:"?";s:3:"︙";s:3:"...";s:3:"︰";s:2:"..";s:3:"︵";s:1:"(";s:3:"︶";s:1:")";s:3:"︷";s:1:"{";s:3:"︸";s:1:"}";s:3:"︹";s:1:"[";s:3:"︺";s:1:"]";s:3:"︽";s:2:"<<";s:3:"︾";s:2:">>";s:3:"︿";s:1:"<";s:3:"ï¹€";s:1:">";s:3:"﹇";s:1:"[";s:3:"﹈";s:1:"]";s:2:"×";s:1:"*";s:2:"÷";s:1:"/";s:3:"−";s:1:"-";s:3:"∕";s:1:"/";s:3:"∖";s:1:"\";s:3:"∣";s:1:"|";s:3:"∥";s:2:"||";s:3:"≪";s:2:"<<";s:3:"≫";s:2:">>";s:3:"⦅";s:2:"((";s:3:"⦆";s:2:"))";}
    \ No newline at end of file
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/unidata/canonicalComposition.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/unidata/canonicalComposition.ser
    new file mode 100644
    index 0000000..e75cfcf
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/unidata/canonicalComposition.ser
    @@ -0,0 +1 @@
    +a:933:{s:3:"AÌ€";s:2:"À";s:3:"AÌ";s:2:"Ã";s:3:"AÌ‚";s:2:"Â";s:3:"Ã";s:2:"Ã";s:3:"Ä";s:2:"Ä";s:3:"AÌŠ";s:2:"Ã…";s:3:"Ç";s:2:"Ç";s:3:"EÌ€";s:2:"È";s:3:"EÌ";s:2:"É";s:3:"EÌ‚";s:2:"Ê";s:3:"Ë";s:2:"Ë";s:3:"IÌ€";s:2:"ÃŒ";s:3:"IÌ";s:2:"Ã";s:3:"IÌ‚";s:2:"ÃŽ";s:3:"Ï";s:2:"Ã";s:3:"Ñ";s:2:"Ñ";s:3:"OÌ€";s:2:"Ã’";s:3:"OÌ";s:2:"Ó";s:3:"OÌ‚";s:2:"Ô";s:3:"Õ";s:2:"Õ";s:3:"Ö";s:2:"Ö";s:3:"UÌ€";s:2:"Ù";s:3:"UÌ";s:2:"Ú";s:3:"UÌ‚";s:2:"Û";s:3:"Ü";s:2:"Ü";s:3:"YÌ";s:2:"Ã";s:3:"aÌ€";s:2:"à";s:3:"aÌ";s:2:"á";s:3:"aÌ‚";s:2:"â";s:3:"ã";s:2:"ã";s:3:"ä";s:2:"ä";s:3:"aÌŠ";s:2:"Ã¥";s:3:"ç";s:2:"ç";s:3:"eÌ€";s:2:"è";s:3:"eÌ";s:2:"é";s:3:"eÌ‚";s:2:"ê";s:3:"ë";s:2:"ë";s:3:"iÌ€";s:2:"ì";s:3:"iÌ";s:2:"í";s:3:"iÌ‚";s:2:"î";s:3:"ï";s:2:"ï";s:3:"ñ";s:2:"ñ";s:3:"oÌ€";s:2:"ò";s:3:"oÌ";s:2:"ó";s:3:"oÌ‚";s:2:"ô";s:3:"õ";s:2:"õ";s:3:"ö";s:2:"ö";s:3:"uÌ€";s:2:"ù";s:3:"uÌ";s:2:"ú";s:3:"uÌ‚";s:2:"û";s:3:"ü";s:2:"ü";s:3:"yÌ";s:2:"ý";s:3:"ÿ";s:2:"ÿ";s:3:"AÌ„";s:2:"Ä€";s:3:"aÌ„";s:2:"Ä";s:3:"Ă";s:2:"Ä‚";s:3:"ă";s:2:"ă";s:3:"Ą";s:2:"Ä„";s:3:"ą";s:2:"Ä…";s:3:"CÌ";s:2:"Ć";s:3:"cÌ";s:2:"ć";s:3:"CÌ‚";s:2:"Ĉ";s:3:"cÌ‚";s:2:"ĉ";s:3:"Ċ";s:2:"ÄŠ";s:3:"ċ";s:2:"Ä‹";s:3:"CÌŒ";s:2:"ÄŒ";s:3:"cÌŒ";s:2:"Ä";s:3:"DÌŒ";s:2:"ÄŽ";s:3:"dÌŒ";s:2:"Ä";s:3:"EÌ„";s:2:"Ä’";s:3:"eÌ„";s:2:"Ä“";s:3:"Ĕ";s:2:"Ä”";s:3:"ĕ";s:2:"Ä•";s:3:"Ė";s:2:"Ä–";s:3:"ė";s:2:"Ä—";s:3:"Ę";s:2:"Ę";s:3:"ę";s:2:"Ä™";s:3:"EÌŒ";s:2:"Äš";s:3:"eÌŒ";s:2:"Ä›";s:3:"GÌ‚";s:2:"Äœ";s:3:"gÌ‚";s:2:"Ä";s:3:"Ğ";s:2:"Äž";s:3:"ğ";s:2:"ÄŸ";s:3:"Ġ";s:2:"Ä ";s:3:"ġ";s:2:"Ä¡";s:3:"Ģ";s:2:"Ä¢";s:3:"ģ";s:2:"Ä£";s:3:"HÌ‚";s:2:"Ĥ";s:3:"hÌ‚";s:2:"Ä¥";s:3:"Ĩ";s:2:"Ĩ";s:3:"ĩ";s:2:"Ä©";s:3:"IÌ„";s:2:"Ī";s:3:"iÌ„";s:2:"Ä«";s:3:"Ĭ";s:2:"Ĭ";s:3:"ĭ";s:2:"Ä­";s:3:"Į";s:2:"Ä®";s:3:"į";s:2:"į";s:3:"İ";s:2:"İ";s:3:"JÌ‚";s:2:"Ä´";s:3:"jÌ‚";s:2:"ĵ";s:3:"Ķ";s:2:"Ķ";s:3:"ķ";s:2:"Ä·";s:3:"LÌ";s:2:"Ĺ";s:3:"lÌ";s:2:"ĺ";s:3:"Ļ";s:2:"Ä»";s:3:"ļ";s:2:"ļ";s:3:"LÌŒ";s:2:"Ľ";s:3:"lÌŒ";s:2:"ľ";s:3:"NÌ";s:2:"Ń";s:3:"nÌ";s:2:"Å„";s:3:"Ņ";s:2:"Å…";s:3:"ņ";s:2:"ņ";s:3:"NÌŒ";s:2:"Ň";s:3:"nÌŒ";s:2:"ň";s:3:"OÌ„";s:2:"ÅŒ";s:3:"oÌ„";s:2:"Å";s:3:"Ŏ";s:2:"ÅŽ";s:3:"ŏ";s:2:"Å";s:3:"OÌ‹";s:2:"Å";s:3:"oÌ‹";s:2:"Å‘";s:3:"RÌ";s:2:"Å”";s:3:"rÌ";s:2:"Å•";s:3:"Ŗ";s:2:"Å–";s:3:"ŗ";s:2:"Å—";s:3:"RÌŒ";s:2:"Ř";s:3:"rÌŒ";s:2:"Å™";s:3:"SÌ";s:2:"Åš";s:3:"sÌ";s:2:"Å›";s:3:"SÌ‚";s:2:"Åœ";s:3:"sÌ‚";s:2:"Å";s:3:"Ş";s:2:"Åž";s:3:"ş";s:2:"ÅŸ";s:3:"SÌŒ";s:2:"Å ";s:3:"sÌŒ";s:2:"Å¡";s:3:"Ţ";s:2:"Å¢";s:3:"ţ";s:2:"Å£";s:3:"TÌŒ";s:2:"Ť";s:3:"tÌŒ";s:2:"Å¥";s:3:"Ũ";s:2:"Ũ";s:3:"ũ";s:2:"Å©";s:3:"UÌ„";s:2:"Ū";s:3:"uÌ„";s:2:"Å«";s:3:"Ŭ";s:2:"Ŭ";s:3:"ŭ";s:2:"Å­";s:3:"UÌŠ";s:2:"Å®";s:3:"uÌŠ";s:2:"ů";s:3:"UÌ‹";s:2:"Ű";s:3:"uÌ‹";s:2:"ű";s:3:"Ų";s:2:"Ų";s:3:"ų";s:2:"ų";s:3:"WÌ‚";s:2:"Å´";s:3:"wÌ‚";s:2:"ŵ";s:3:"YÌ‚";s:2:"Ŷ";s:3:"yÌ‚";s:2:"Å·";s:3:"Ÿ";s:2:"Ÿ";s:3:"ZÌ";s:2:"Ź";s:3:"zÌ";s:2:"ź";s:3:"Ż";s:2:"Å»";s:3:"ż";s:2:"ż";s:3:"ZÌŒ";s:2:"Ž";s:3:"zÌŒ";s:2:"ž";s:3:"OÌ›";s:2:"Æ ";s:3:"oÌ›";s:2:"Æ¡";s:3:"UÌ›";s:2:"Ư";s:3:"uÌ›";s:2:"ư";s:3:"AÌŒ";s:2:"Ç";s:3:"aÌŒ";s:2:"ÇŽ";s:3:"IÌŒ";s:2:"Ç";s:3:"iÌŒ";s:2:"Ç";s:3:"OÌŒ";s:2:"Ç‘";s:3:"oÌŒ";s:2:"Ç’";s:3:"UÌŒ";s:2:"Ç“";s:3:"uÌŒ";s:2:"Ç”";s:4:"Ǖ";s:2:"Ç•";s:4:"ǖ";s:2:"Ç–";s:4:"ÜÌ";s:2:"Ç—";s:4:"üÌ";s:2:"ǘ";s:4:"Ǚ";s:2:"Ç™";s:4:"ǚ";s:2:"Çš";s:4:"Ǜ";s:2:"Ç›";s:4:"ǜ";s:2:"Çœ";s:4:"Ǟ";s:2:"Çž";s:4:"ǟ";s:2:"ÇŸ";s:4:"Ǡ";s:2:"Ç ";s:4:"ǡ";s:2:"Ç¡";s:4:"Ǣ";s:2:"Ç¢";s:4:"ǣ";s:2:"Ç£";s:3:"GÌŒ";s:2:"Ǧ";s:3:"gÌŒ";s:2:"ǧ";s:3:"KÌŒ";s:2:"Ǩ";s:3:"kÌŒ";s:2:"Ç©";s:3:"Ǫ";s:2:"Ǫ";s:3:"ǫ";s:2:"Ç«";s:4:"Ǭ";s:2:"Ǭ";s:4:"ǭ";s:2:"Ç­";s:4:"Æ·ÌŒ";s:2:"Ç®";s:4:"Ê’ÌŒ";s:2:"ǯ";s:3:"jÌŒ";s:2:"ǰ";s:3:"GÌ";s:2:"Ç´";s:3:"gÌ";s:2:"ǵ";s:3:"NÌ€";s:2:"Ǹ";s:3:"nÌ€";s:2:"ǹ";s:4:"Ã…Ì";s:2:"Ǻ";s:4:"Ã¥Ì";s:2:"Ç»";s:4:"ÆÌ";s:2:"Ǽ";s:4:"æÌ";s:2:"ǽ";s:4:"ØÌ";s:2:"Ǿ";s:4:"øÌ";s:2:"Ç¿";s:3:"AÌ";s:2:"È€";s:3:"aÌ";s:2:"È";s:3:"AÌ‘";s:2:"È‚";s:3:"aÌ‘";s:2:"ȃ";s:3:"EÌ";s:2:"È„";s:3:"eÌ";s:2:"È…";s:3:"EÌ‘";s:2:"Ȇ";s:3:"eÌ‘";s:2:"ȇ";s:3:"IÌ";s:2:"Ȉ";s:3:"iÌ";s:2:"ȉ";s:3:"IÌ‘";s:2:"ÈŠ";s:3:"iÌ‘";s:2:"È‹";s:3:"OÌ";s:2:"ÈŒ";s:3:"oÌ";s:2:"È";s:3:"OÌ‘";s:2:"ÈŽ";s:3:"oÌ‘";s:2:"È";s:3:"RÌ";s:2:"È";s:3:"rÌ";s:2:"È‘";s:3:"RÌ‘";s:2:"È’";s:3:"rÌ‘";s:2:"È“";s:3:"UÌ";s:2:"È”";s:3:"uÌ";s:2:"È•";s:3:"UÌ‘";s:2:"È–";s:3:"uÌ‘";s:2:"È—";s:3:"Ș";s:2:"Ș";s:3:"ș";s:2:"È™";s:3:"Ț";s:2:"Èš";s:3:"ț";s:2:"È›";s:3:"HÌŒ";s:2:"Èž";s:3:"hÌŒ";s:2:"ÈŸ";s:3:"Ȧ";s:2:"Ȧ";s:3:"ȧ";s:2:"ȧ";s:3:"Ȩ";s:2:"Ȩ";s:3:"ȩ";s:2:"È©";s:4:"Ȫ";s:2:"Ȫ";s:4:"ȫ";s:2:"È«";s:4:"Ȭ";s:2:"Ȭ";s:4:"ȭ";s:2:"È­";s:3:"Ȯ";s:2:"È®";s:3:"ȯ";s:2:"ȯ";s:4:"Ȱ";s:2:"Ȱ";s:4:"ȱ";s:2:"ȱ";s:3:"YÌ„";s:2:"Ȳ";s:3:"yÌ„";s:2:"ȳ";s:4:"¨Ì";s:2:"Î…";s:4:"ΑÌ";s:2:"Ά";s:4:"ΕÌ";s:2:"Έ";s:4:"ΗÌ";s:2:"Ή";s:4:"ΙÌ";s:2:"Ί";s:4:"ΟÌ";s:2:"ÎŒ";s:4:"Î¥Ì";s:2:"ÎŽ";s:4:"ΩÌ";s:2:"Î";s:4:"ÏŠÌ";s:2:"Î";s:4:"Ϊ";s:2:"Ϊ";s:4:"Ϋ";s:2:"Ϋ";s:4:"αÌ";s:2:"ά";s:4:"εÌ";s:2:"έ";s:4:"ηÌ";s:2:"ή";s:4:"ιÌ";s:2:"ί";s:4:"Ï‹Ì";s:2:"ΰ";s:4:"ϊ";s:2:"ÏŠ";s:4:"ϋ";s:2:"Ï‹";s:4:"οÌ";s:2:"ÏŒ";s:4:"Ï…Ì";s:2:"Ï";s:4:"ωÌ";s:2:"ÏŽ";s:4:"Ï’Ì";s:2:"Ï“";s:4:"ϔ";s:2:"Ï”";s:4:"Ѐ";s:2:"Ѐ";s:4:"Ё";s:2:"Ð";s:4:"ГÌ";s:2:"Ѓ";s:4:"Ї";s:2:"Ї";s:4:"КÌ";s:2:"ÐŒ";s:4:"Ѝ";s:2:"Ð";s:4:"Ў";s:2:"ÐŽ";s:4:"Й";s:2:"Й";s:4:"й";s:2:"й";s:4:"ѐ";s:2:"Ñ";s:4:"ё";s:2:"Ñ‘";s:4:"гÌ";s:2:"Ñ“";s:4:"ї";s:2:"Ñ—";s:4:"кÌ";s:2:"Ñœ";s:4:"ѝ";s:2:"Ñ";s:4:"ў";s:2:"Ñž";s:4:"Ñ´Ì";s:2:"Ѷ";s:4:"ѵÌ";s:2:"Ñ·";s:4:"Ӂ";s:2:"Ó";s:4:"ӂ";s:2:"Ó‚";s:4:"Ð̆";s:2:"Ó";s:4:"ӑ";s:2:"Ó‘";s:4:"Ð̈";s:2:"Ó’";s:4:"ӓ";s:2:"Ó“";s:4:"Ӗ";s:2:"Ó–";s:4:"ӗ";s:2:"Ó—";s:4:"Ӛ";s:2:"Óš";s:4:"ӛ";s:2:"Ó›";s:4:"Ӝ";s:2:"Óœ";s:4:"ӝ";s:2:"Ó";s:4:"Ӟ";s:2:"Óž";s:4:"ӟ";s:2:"ÓŸ";s:4:"Ӣ";s:2:"Ó¢";s:4:"ӣ";s:2:"Ó£";s:4:"Ӥ";s:2:"Ó¤";s:4:"ӥ";s:2:"Ó¥";s:4:"Ӧ";s:2:"Ó¦";s:4:"ӧ";s:2:"Ó§";s:4:"Ӫ";s:2:"Óª";s:4:"ӫ";s:2:"Ó«";s:4:"Ӭ";s:2:"Ó¬";s:4:"Ñ̈";s:2:"Ó­";s:4:"Ӯ";s:2:"Ó®";s:4:"ӯ";s:2:"Ó¯";s:4:"Ӱ";s:2:"Ó°";s:4:"ӱ";s:2:"Ó±";s:4:"Ӳ";s:2:"Ó²";s:4:"ӳ";s:2:"Ó³";s:4:"Ӵ";s:2:"Ó´";s:4:"ӵ";s:2:"Óµ";s:4:"Ӹ";s:2:"Ó¸";s:4:"ӹ";s:2:"Ó¹";s:4:"آ";s:2:"Ø¢";s:4:"أ";s:2:"Ø£";s:4:"ÙˆÙ”";s:2:"ؤ";s:4:"إ";s:2:"Ø¥";s:4:"ÙŠÙ”";s:2:"ئ";s:4:"Û•Ù”";s:2:"Û€";s:4:"ÛÙ”";s:2:"Û‚";s:4:"Û’Ù”";s:2:"Û“";s:6:"ऩ";s:3:"ऩ";s:6:"ऱ";s:3:"ऱ";s:6:"ऴ";s:3:"ऴ";s:6:"ো";s:3:"à§‹";s:6:"ৌ";s:3:"à§Œ";s:6:"ୈ";s:3:"à­ˆ";s:6:"ୋ";s:3:"à­‹";s:6:"ୌ";s:3:"à­Œ";s:6:"ஔ";s:3:"à®”";s:6:"ொ";s:3:"ொ";s:6:"ோ";s:3:"ோ";s:6:"ௌ";s:3:"ௌ";s:6:"ై";s:3:"ై";s:6:"ೀ";s:3:"à³€";s:6:"ೇ";s:3:"ೇ";s:6:"ೈ";s:3:"ೈ";s:6:"ೊ";s:3:"ೊ";s:6:"ೋ";s:3:"ೋ";s:6:"ൊ";s:3:"ൊ";s:6:"ോ";s:3:"ോ";s:6:"ൌ";s:3:"ൌ";s:6:"ේ";s:3:"à·š";s:6:"à·™à·";s:3:"à·œ";s:6:"ෝ";s:3:"à·";s:6:"ෞ";s:3:"à·ž";s:6:"ဦ";s:3:"ဦ";s:6:"ᬆ";s:3:"ᬆ";s:6:"ᬈ";s:3:"ᬈ";s:6:"ᬊ";s:3:"ᬊ";s:6:"ᬌ";s:3:"ᬌ";s:6:"á¬á¬µ";s:3:"ᬎ";s:6:"ᬒ";s:3:"ᬒ";s:6:"ᬻ";s:3:"ᬻ";s:6:"ᬽ";s:3:"ᬽ";s:6:"ᭀ";s:3:"á­€";s:6:"ᭁ";s:3:"á­";s:6:"ᭃ";s:3:"á­ƒ";s:3:"AÌ¥";s:3:"Ḁ";s:3:"aÌ¥";s:3:"á¸";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"BÌ£";s:3:"Ḅ";s:3:"bÌ£";s:3:"ḅ";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:4:"ÇÌ";s:3:"Ḉ";s:4:"çÌ";s:3:"ḉ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"DÌ£";s:3:"Ḍ";s:3:"dÌ£";s:3:"á¸";s:3:"Ḏ";s:3:"Ḏ";s:3:"ḏ";s:3:"á¸";s:3:"Ḑ";s:3:"á¸";s:3:"ḑ";s:3:"ḑ";s:3:"DÌ­";s:3:"Ḓ";s:3:"dÌ­";s:3:"ḓ";s:4:"Ä’Ì€";s:3:"Ḕ";s:4:"ḕ";s:3:"ḕ";s:4:"Ä’Ì";s:3:"Ḗ";s:4:"Ä“Ì";s:3:"ḗ";s:3:"EÌ­";s:3:"Ḙ";s:3:"eÌ­";s:3:"ḙ";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:4:"Ḝ";s:3:"Ḝ";s:4:"ḝ";s:3:"á¸";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"GÌ„";s:3:"Ḡ";s:3:"gÌ„";s:3:"ḡ";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"HÌ£";s:3:"Ḥ";s:3:"hÌ£";s:3:"ḥ";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"HÌ®";s:3:"Ḫ";s:3:"hÌ®";s:3:"ḫ";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:4:"ÃÌ";s:3:"Ḯ";s:4:"ïÌ";s:3:"ḯ";s:3:"KÌ";s:3:"Ḱ";s:3:"kÌ";s:3:"ḱ";s:3:"KÌ£";s:3:"Ḳ";s:3:"kÌ£";s:3:"ḳ";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"LÌ£";s:3:"Ḷ";s:3:"lÌ£";s:3:"ḷ";s:5:"Ḹ";s:3:"Ḹ";s:5:"ḹ";s:3:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"LÌ­";s:3:"Ḽ";s:3:"lÌ­";s:3:"ḽ";s:3:"MÌ";s:3:"Ḿ";s:3:"mÌ";s:3:"ḿ";s:3:"Ṁ";s:3:"á¹€";s:3:"ṁ";s:3:"á¹";s:3:"MÌ£";s:3:"Ṃ";s:3:"mÌ£";s:3:"ṃ";s:3:"Ṅ";s:3:"Ṅ";s:3:"ṅ";s:3:"á¹…";s:3:"NÌ£";s:3:"Ṇ";s:3:"nÌ£";s:3:"ṇ";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"NÌ­";s:3:"Ṋ";s:3:"nÌ­";s:3:"ṋ";s:4:"ÕÌ";s:3:"Ṍ";s:4:"õÌ";s:3:"á¹";s:4:"Ṏ";s:3:"Ṏ";s:4:"ṏ";s:3:"á¹";s:4:"Ṑ";s:3:"á¹";s:4:"ÅÌ€";s:3:"ṑ";s:4:"ÅŒÌ";s:3:"á¹’";s:4:"ÅÌ";s:3:"ṓ";s:3:"PÌ";s:3:"á¹”";s:3:"pÌ";s:3:"ṕ";s:3:"Ṗ";s:3:"á¹–";s:3:"ṗ";s:3:"á¹—";s:3:"Ṙ";s:3:"Ṙ";s:3:"ṙ";s:3:"á¹™";s:3:"RÌ£";s:3:"Ṛ";s:3:"rÌ£";s:3:"á¹›";s:5:"Ṝ";s:3:"Ṝ";s:5:"ṝ";s:3:"á¹";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"á¹ ";s:3:"ṡ";s:3:"ṡ";s:3:"SÌ£";s:3:"á¹¢";s:3:"sÌ£";s:3:"á¹£";s:4:"Ṥ";s:3:"Ṥ";s:4:"ṥ";s:3:"á¹¥";s:4:"Ṧ";s:3:"Ṧ";s:4:"ṧ";s:3:"á¹§";s:5:"Ṩ";s:3:"Ṩ";s:5:"ṩ";s:3:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"TÌ£";s:3:"Ṭ";s:3:"tÌ£";s:3:"á¹­";s:3:"Ṯ";s:3:"á¹®";s:3:"ṯ";s:3:"ṯ";s:3:"TÌ­";s:3:"á¹°";s:3:"tÌ­";s:3:"á¹±";s:3:"Ṳ";s:3:"á¹²";s:3:"ṳ";s:3:"á¹³";s:3:"Ṵ";s:3:"á¹´";s:3:"ṵ";s:3:"á¹µ";s:3:"UÌ­";s:3:"á¹¶";s:3:"uÌ­";s:3:"á¹·";s:4:"ŨÌ";s:3:"Ṹ";s:4:"Å©Ì";s:3:"á¹¹";s:4:"Ṻ";s:3:"Ṻ";s:4:"ṻ";s:3:"á¹»";s:3:"Ṽ";s:3:"á¹¼";s:3:"ṽ";s:3:"á¹½";s:3:"VÌ£";s:3:"á¹¾";s:3:"vÌ£";s:3:"ṿ";s:3:"WÌ€";s:3:"Ẁ";s:3:"wÌ€";s:3:"áº";s:3:"WÌ";s:3:"Ẃ";s:3:"wÌ";s:3:"ẃ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"WÌ£";s:3:"Ẉ";s:3:"wÌ£";s:3:"ẉ";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"ẍ";s:3:"áº";s:3:"Ẏ";s:3:"Ẏ";s:3:"ẏ";s:3:"áº";s:3:"ZÌ‚";s:3:"áº";s:3:"zÌ‚";s:3:"ẑ";s:3:"ZÌ£";s:3:"Ẓ";s:3:"zÌ£";s:3:"ẓ";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"wÌŠ";s:3:"ẘ";s:3:"yÌŠ";s:3:"ẙ";s:4:"ẛ";s:3:"ẛ";s:3:"AÌ£";s:3:"Ạ";s:3:"aÌ£";s:3:"ạ";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:4:"ÂÌ";s:3:"Ấ";s:4:"âÌ";s:3:"ấ";s:4:"Ầ";s:3:"Ầ";s:4:"ầ";s:3:"ầ";s:4:"Ẩ";s:3:"Ẩ";s:4:"ẩ";s:3:"ẩ";s:4:"Ẫ";s:3:"Ẫ";s:4:"ẫ";s:3:"ẫ";s:5:"Ậ";s:3:"Ậ";s:5:"ậ";s:3:"ậ";s:4:"Ä‚Ì";s:3:"Ắ";s:4:"ăÌ";s:3:"ắ";s:4:"Ằ";s:3:"Ằ";s:4:"ằ";s:3:"ằ";s:4:"Ẳ";s:3:"Ẳ";s:4:"ẳ";s:3:"ẳ";s:4:"Ẵ";s:3:"Ẵ";s:4:"ẵ";s:3:"ẵ";s:5:"Ặ";s:3:"Ặ";s:5:"ặ";s:3:"ặ";s:3:"EÌ£";s:3:"Ẹ";s:3:"eÌ£";s:3:"ẹ";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:4:"ÊÌ";s:3:"Ế";s:4:"êÌ";s:3:"ế";s:4:"Ề";s:3:"Ề";s:4:"ề";s:3:"á»";s:4:"Ể";s:3:"Ể";s:4:"ể";s:3:"ể";s:4:"Ễ";s:3:"Ễ";s:4:"ễ";s:3:"á»…";s:5:"Ệ";s:3:"Ệ";s:5:"ệ";s:3:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"IÌ£";s:3:"Ị";s:3:"iÌ£";s:3:"ị";s:3:"OÌ£";s:3:"Ọ";s:3:"oÌ£";s:3:"á»";s:3:"Ỏ";s:3:"Ỏ";s:3:"ỏ";s:3:"á»";s:4:"ÔÌ";s:3:"á»";s:4:"ôÌ";s:3:"ố";s:4:"Ồ";s:3:"á»’";s:4:"ồ";s:3:"ồ";s:4:"Ổ";s:3:"á»”";s:4:"ổ";s:3:"ổ";s:4:"Ỗ";s:3:"á»–";s:4:"ỗ";s:3:"á»—";s:5:"Ộ";s:3:"Ộ";s:5:"á»Ì‚";s:3:"á»™";s:4:"Æ Ì";s:3:"Ớ";s:4:"Æ¡Ì";s:3:"á»›";s:4:"Ờ";s:3:"Ờ";s:4:"ờ";s:3:"á»";s:4:"Ở";s:3:"Ở";s:4:"ở";s:3:"ở";s:4:"Ỡ";s:3:"á» ";s:4:"ỡ";s:3:"ỡ";s:4:"Ợ";s:3:"Ợ";s:4:"ợ";s:3:"ợ";s:3:"UÌ£";s:3:"Ụ";s:3:"uÌ£";s:3:"ụ";s:3:"Ủ";s:3:"Ủ";s:3:"ủ";s:3:"á»§";s:4:"ƯÌ";s:3:"Ứ";s:4:"ưÌ";s:3:"ứ";s:4:"Ừ";s:3:"Ừ";s:4:"ừ";s:3:"ừ";s:4:"Ử";s:3:"Ử";s:4:"ử";s:3:"á»­";s:4:"Ữ";s:3:"á»®";s:4:"ữ";s:3:"ữ";s:4:"Ự";s:3:"á»°";s:4:"ự";s:3:"á»±";s:3:"YÌ€";s:3:"Ỳ";s:3:"yÌ€";s:3:"ỳ";s:3:"YÌ£";s:3:"á»´";s:3:"yÌ£";s:3:"ỵ";s:3:"Ỷ";s:3:"á»¶";s:3:"ỷ";s:3:"á»·";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:4:"ἀ";s:3:"á¼€";s:4:"ἁ";s:3:"á¼";s:5:"ἂ";s:3:"ἂ";s:5:"á¼Ì€";s:3:"ἃ";s:5:"á¼€Ì";s:3:"ἄ";s:5:"á¼Ì";s:3:"á¼…";s:5:"ἆ";s:3:"ἆ";s:5:"á¼Í‚";s:3:"ἇ";s:4:"Ἀ";s:3:"Ἀ";s:4:"Ἁ";s:3:"Ἁ";s:5:"Ἂ";s:3:"Ἂ";s:5:"Ἃ";s:3:"Ἃ";s:5:"ἈÌ";s:3:"Ἄ";s:5:"ἉÌ";s:3:"á¼";s:5:"Ἆ";s:3:"Ἆ";s:5:"Ἇ";s:3:"á¼";s:4:"ἐ";s:3:"á¼";s:4:"ἑ";s:3:"ἑ";s:5:"á¼Ì€";s:3:"á¼’";s:5:"ἓ";s:3:"ἓ";s:5:"á¼Ì";s:3:"á¼”";s:5:"ἑÌ";s:3:"ἕ";s:4:"Ἐ";s:3:"Ἐ";s:4:"Ἑ";s:3:"á¼™";s:5:"Ἒ";s:3:"Ἒ";s:5:"Ἓ";s:3:"á¼›";s:5:"ἘÌ";s:3:"Ἔ";s:5:"á¼™Ì";s:3:"á¼";s:4:"ἠ";s:3:"á¼ ";s:4:"ἡ";s:3:"ἡ";s:5:"ἢ";s:3:"á¼¢";s:5:"ἣ";s:3:"á¼£";s:5:"á¼ Ì";s:3:"ἤ";s:5:"ἡÌ";s:3:"á¼¥";s:5:"á¼ Í‚";s:3:"ἦ";s:5:"ἧ";s:3:"á¼§";s:4:"Ἠ";s:3:"Ἠ";s:4:"Ἡ";s:3:"Ἡ";s:5:"Ἢ";s:3:"Ἢ";s:5:"Ἣ";s:3:"Ἣ";s:5:"ἨÌ";s:3:"Ἤ";s:5:"ἩÌ";s:3:"á¼­";s:5:"Ἦ";s:3:"á¼®";s:5:"Ἧ";s:3:"Ἧ";s:4:"ἰ";s:3:"á¼°";s:4:"ἱ";s:3:"á¼±";s:5:"á¼°Ì€";s:3:"á¼²";s:5:"ἳ";s:3:"á¼³";s:5:"á¼°Ì";s:3:"á¼´";s:5:"á¼±Ì";s:3:"á¼µ";s:5:"á¼°Í‚";s:3:"á¼¶";s:5:"ἷ";s:3:"á¼·";s:4:"Ἰ";s:3:"Ἰ";s:4:"Ἱ";s:3:"á¼¹";s:5:"Ἲ";s:3:"Ἲ";s:5:"Ἳ";s:3:"á¼»";s:5:"ἸÌ";s:3:"á¼¼";s:5:"á¼¹Ì";s:3:"á¼½";s:5:"Ἶ";s:3:"á¼¾";s:5:"Ἷ";s:3:"Ἷ";s:4:"ὀ";s:3:"á½€";s:4:"ὁ";s:3:"á½";s:5:"ὂ";s:3:"ὂ";s:5:"á½Ì€";s:3:"ὃ";s:5:"á½€Ì";s:3:"ὄ";s:5:"á½Ì";s:3:"á½…";s:4:"Ὀ";s:3:"Ὀ";s:4:"Ὁ";s:3:"Ὁ";s:5:"Ὂ";s:3:"Ὂ";s:5:"Ὃ";s:3:"Ὃ";s:5:"ὈÌ";s:3:"Ὄ";s:5:"ὉÌ";s:3:"á½";s:4:"Ï…Ì“";s:3:"á½";s:4:"Ï…Ì”";s:3:"ὑ";s:5:"á½Ì€";s:3:"á½’";s:5:"ὓ";s:3:"ὓ";s:5:"á½Ì";s:3:"á½”";s:5:"ὑÌ";s:3:"ὕ";s:5:"á½Í‚";s:3:"á½–";s:5:"ὗ";s:3:"á½—";s:4:"Ὑ";s:3:"á½™";s:5:"Ὓ";s:3:"á½›";s:5:"á½™Ì";s:3:"á½";s:5:"Ὗ";s:3:"Ὗ";s:4:"ὠ";s:3:"á½ ";s:4:"ὡ";s:3:"ὡ";s:5:"ὢ";s:3:"á½¢";s:5:"ὣ";s:3:"á½£";s:5:"á½ Ì";s:3:"ὤ";s:5:"ὡÌ";s:3:"á½¥";s:5:"á½ Í‚";s:3:"ὦ";s:5:"ὧ";s:3:"á½§";s:4:"Ὠ";s:3:"Ὠ";s:4:"Ὡ";s:3:"Ὡ";s:5:"Ὢ";s:3:"Ὢ";s:5:"Ὣ";s:3:"Ὣ";s:5:"ὨÌ";s:3:"Ὤ";s:5:"ὩÌ";s:3:"á½­";s:5:"Ὦ";s:3:"á½®";s:5:"Ὧ";s:3:"Ὧ";s:4:"ὰ";s:3:"á½°";s:4:"ὲ";s:3:"á½²";s:4:"ὴ";s:3:"á½´";s:4:"ὶ";s:3:"á½¶";s:4:"ὸ";s:3:"ὸ";s:4:"Ï…Ì€";s:3:"ὺ";s:4:"ὼ";s:3:"á½¼";s:5:"ᾀ";s:3:"á¾€";s:5:"á¼Í…";s:3:"á¾";s:5:"ᾂ";s:3:"ᾂ";s:5:"ᾃ";s:3:"ᾃ";s:5:"ᾄ";s:3:"ᾄ";s:5:"á¼…Í…";s:3:"á¾…";s:5:"ᾆ";s:3:"ᾆ";s:5:"ᾇ";s:3:"ᾇ";s:5:"ᾈ";s:3:"ᾈ";s:5:"ᾉ";s:3:"ᾉ";s:5:"ᾊ";s:3:"ᾊ";s:5:"ᾋ";s:3:"ᾋ";s:5:"ᾌ";s:3:"ᾌ";s:5:"á¼Í…";s:3:"á¾";s:5:"ᾎ";s:3:"ᾎ";s:5:"á¼Í…";s:3:"á¾";s:5:"á¼ Í…";s:3:"á¾";s:5:"ᾑ";s:3:"ᾑ";s:5:"ᾒ";s:3:"á¾’";s:5:"ᾓ";s:3:"ᾓ";s:5:"ᾔ";s:3:"á¾”";s:5:"ᾕ";s:3:"ᾕ";s:5:"ᾖ";s:3:"á¾–";s:5:"á¼§Í…";s:3:"á¾—";s:5:"ᾘ";s:3:"ᾘ";s:5:"ᾙ";s:3:"á¾™";s:5:"ᾚ";s:3:"ᾚ";s:5:"ᾛ";s:3:"á¾›";s:5:"ᾜ";s:3:"ᾜ";s:5:"á¼­Í…";s:3:"á¾";s:5:"ᾞ";s:3:"ᾞ";s:5:"ᾟ";s:3:"ᾟ";s:5:"á½ Í…";s:3:"á¾ ";s:5:"ᾡ";s:3:"ᾡ";s:5:"ᾢ";s:3:"á¾¢";s:5:"ᾣ";s:3:"á¾£";s:5:"ᾤ";s:3:"ᾤ";s:5:"ᾥ";s:3:"á¾¥";s:5:"ᾦ";s:3:"ᾦ";s:5:"á½§Í…";s:3:"á¾§";s:5:"ᾨ";s:3:"ᾨ";s:5:"ᾩ";s:3:"ᾩ";s:5:"ᾪ";s:3:"ᾪ";s:5:"ᾫ";s:3:"ᾫ";s:5:"ᾬ";s:3:"ᾬ";s:5:"á½­Í…";s:3:"á¾­";s:5:"ᾮ";s:3:"á¾®";s:5:"ᾯ";s:3:"ᾯ";s:4:"ᾰ";s:3:"á¾°";s:4:"ᾱ";s:3:"á¾±";s:5:"á½°Í…";s:3:"á¾²";s:4:"ᾳ";s:3:"á¾³";s:4:"ᾴ";s:3:"á¾´";s:4:"ᾶ";s:3:"á¾¶";s:5:"á¾¶Í…";s:3:"á¾·";s:4:"Ᾰ";s:3:"Ᾰ";s:4:"Ᾱ";s:3:"á¾¹";s:4:"Ὰ";s:3:"Ὰ";s:4:"ᾼ";s:3:"á¾¼";s:4:"῁";s:3:"á¿";s:5:"á½´Í…";s:3:"á¿‚";s:4:"ῃ";s:3:"ῃ";s:4:"ῄ";s:3:"á¿„";s:4:"ῆ";s:3:"ῆ";s:5:"ῇ";s:3:"ῇ";s:4:"Ὲ";s:3:"Ὲ";s:4:"Ὴ";s:3:"Ὴ";s:4:"ῌ";s:3:"ῌ";s:5:"῍";s:3:"á¿";s:5:"᾿Ì";s:3:"῎";s:5:"῏";s:3:"á¿";s:4:"ῐ";s:3:"á¿";s:4:"ῑ";s:3:"á¿‘";s:4:"ÏŠÌ€";s:3:"á¿’";s:4:"ῖ";s:3:"á¿–";s:4:"ÏŠÍ‚";s:3:"á¿—";s:4:"Ῐ";s:3:"Ῐ";s:4:"Ῑ";s:3:"á¿™";s:4:"Ὶ";s:3:"Ὶ";s:5:"῝";s:3:"á¿";s:5:"῾Ì";s:3:"῞";s:5:"῟";s:3:"῟";s:4:"ῠ";s:3:"á¿ ";s:4:"Ï…Ì„";s:3:"á¿¡";s:4:"ῢ";s:3:"á¿¢";s:4:"ÏÌ“";s:3:"ῤ";s:4:"ÏÌ”";s:3:"á¿¥";s:4:"Ï…Í‚";s:3:"ῦ";s:4:"ῧ";s:3:"á¿§";s:4:"Ῠ";s:3:"Ῠ";s:4:"Ῡ";s:3:"á¿©";s:4:"Ὺ";s:3:"Ὺ";s:4:"Ῥ";s:3:"Ῥ";s:4:"῭";s:3:"á¿­";s:5:"ῲ";s:3:"ῲ";s:4:"ῳ";s:3:"ῳ";s:4:"ÏŽÍ…";s:3:"á¿´";s:4:"ῶ";s:3:"á¿¶";s:5:"á¿¶Í…";s:3:"á¿·";s:4:"Ὸ";s:3:"Ὸ";s:4:"Ὼ";s:3:"Ὼ";s:4:"ῼ";s:3:"ῼ";s:5:"â†Ì¸";s:3:"↚";s:5:"↛";s:3:"↛";s:5:"↮";s:3:"↮";s:5:"â‡Ì¸";s:3:"â‡";s:5:"⇎";s:3:"⇎";s:5:"⇏";s:3:"â‡";s:5:"∄";s:3:"∄";s:5:"∉";s:3:"∉";s:5:"∌";s:3:"∌";s:5:"∤";s:3:"∤";s:5:"∦";s:3:"∦";s:5:"≁";s:3:"â‰";s:5:"≄";s:3:"≄";s:5:"≇";s:3:"≇";s:5:"≉";s:3:"≉";s:3:"≠";s:3:"≠";s:5:"≢";s:3:"≢";s:5:"â‰Ì¸";s:3:"≭";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:5:"≰";s:3:"≰";s:5:"≱";s:3:"≱";s:5:"≴";s:3:"≴";s:5:"≵";s:3:"≵";s:5:"≸";s:3:"≸";s:5:"≹";s:3:"≹";s:5:"⊀";s:3:"⊀";s:5:"⊁";s:3:"âŠ";s:5:"⊄";s:3:"⊄";s:5:"⊅";s:3:"⊅";s:5:"⊈";s:3:"⊈";s:5:"⊉";s:3:"⊉";s:5:"⊬";s:3:"⊬";s:5:"⊭";s:3:"⊭";s:5:"⊮";s:3:"⊮";s:5:"⊯";s:3:"⊯";s:5:"⋠";s:3:"â‹ ";s:5:"⋡";s:3:"â‹¡";s:5:"⋢";s:3:"â‹¢";s:5:"⋣";s:3:"â‹£";s:5:"⋪";s:3:"⋪";s:5:"⋫";s:3:"â‹«";s:5:"⋬";s:3:"⋬";s:5:"⋭";s:3:"â‹­";s:6:"ã‹ã‚™";s:3:"ãŒ";s:6:"ãã‚™";s:3:"ãŽ";s:6:"ãã‚™";s:3:"ã";s:6:"ã‘ã‚™";s:3:"ã’";s:6:"ã“ã‚™";s:3:"ã”";s:6:"ã•ã‚™";s:3:"ã–";s:6:"ã—ã‚™";s:3:"ã˜";s:6:"ã™ã‚™";s:3:"ãš";s:6:"ã›ã‚™";s:3:"ãœ";s:6:"ãã‚™";s:3:"ãž";s:6:"ãŸã‚™";s:3:"ã ";s:6:"ã¡ã‚™";s:3:"ã¢";s:6:"ã¤ã‚™";s:3:"ã¥";s:6:"ã¦ã‚™";s:3:"ã§";s:6:"ã¨ã‚™";s:3:"ã©";s:6:"ã¯ã‚™";s:3:"ã°";s:6:"ã¯ã‚š";s:3:"ã±";s:6:"ã²ã‚™";s:3:"ã³";s:6:"ã²ã‚š";s:3:"ã´";s:6:"ãµã‚™";s:3:"ã¶";s:6:"ãµã‚š";s:3:"ã·";s:6:"ã¸ã‚™";s:3:"ã¹";s:6:"ã¸ã‚š";s:3:"ãº";s:6:"ã»ã‚™";s:3:"ã¼";s:6:"ã»ã‚š";s:3:"ã½";s:6:"ã†ã‚™";s:3:"ã‚”";s:6:"ã‚ã‚™";s:3:"ゞ";s:6:"ã‚«ã‚™";s:3:"ガ";s:6:"ã‚­ã‚™";s:3:"ã‚®";s:6:"グ";s:3:"ã‚°";s:6:"ゲ";s:3:"ゲ";s:6:"ゴ";s:3:"ã‚´";s:6:"ザ";s:3:"ã‚¶";s:6:"ã‚·ã‚™";s:3:"ジ";s:6:"ズ";s:3:"ズ";s:6:"ゼ";s:3:"ゼ";s:6:"ゾ";s:3:"ゾ";s:6:"ã‚¿ã‚™";s:3:"ダ";s:6:"ãƒã‚™";s:3:"ヂ";s:6:"ヅ";s:3:"ヅ";s:6:"デ";s:3:"デ";s:6:"ド";s:3:"ド";s:6:"ãƒã‚™";s:3:"ãƒ";s:6:"ãƒã‚š";s:3:"パ";s:6:"ビ";s:3:"ビ";s:6:"ピ";s:3:"ピ";s:6:"ブ";s:3:"ブ";s:6:"プ";s:3:"プ";s:6:"ベ";s:3:"ベ";s:6:"ペ";s:3:"ペ";s:6:"ボ";s:3:"ボ";s:6:"ポ";s:3:"ãƒ";s:6:"ヴ";s:3:"ヴ";s:6:"ヷ";s:3:"ヷ";s:6:"ヸ";s:3:"ヸ";s:6:"ヹ";s:3:"ヹ";s:6:"ヺ";s:3:"ヺ";s:6:"ヾ";s:3:"ヾ";s:8:"𑂚";s:4:"ð‘‚š";s:8:"𑂜";s:4:"ð‘‚œ";s:8:"𑂫";s:4:"ð‘‚«";s:8:"𑄮";s:4:"ð‘„®";s:8:"𑄯";s:4:"𑄯";}
    \ No newline at end of file
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/unidata/canonicalDecomposition.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/unidata/canonicalDecomposition.ser
    new file mode 100644
    index 0000000..c1ee2b6
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/unidata/canonicalDecomposition.ser
    @@ -0,0 +1 @@
    +a:2053:{s:2:"À";s:3:"AÌ€";s:2:"Ã";s:3:"AÌ";s:2:"Â";s:3:"AÌ‚";s:2:"Ã";s:3:"Ã";s:2:"Ä";s:3:"Ä";s:2:"Ã…";s:3:"AÌŠ";s:2:"Ç";s:3:"Ç";s:2:"È";s:3:"EÌ€";s:2:"É";s:3:"EÌ";s:2:"Ê";s:3:"EÌ‚";s:2:"Ë";s:3:"Ë";s:2:"ÃŒ";s:3:"IÌ€";s:2:"Ã";s:3:"IÌ";s:2:"ÃŽ";s:3:"IÌ‚";s:2:"Ã";s:3:"Ï";s:2:"Ñ";s:3:"Ñ";s:2:"Ã’";s:3:"OÌ€";s:2:"Ó";s:3:"OÌ";s:2:"Ô";s:3:"OÌ‚";s:2:"Õ";s:3:"Õ";s:2:"Ö";s:3:"Ö";s:2:"Ù";s:3:"UÌ€";s:2:"Ú";s:3:"UÌ";s:2:"Û";s:3:"UÌ‚";s:2:"Ü";s:3:"Ü";s:2:"Ã";s:3:"YÌ";s:2:"à";s:3:"aÌ€";s:2:"á";s:3:"aÌ";s:2:"â";s:3:"aÌ‚";s:2:"ã";s:3:"ã";s:2:"ä";s:3:"ä";s:2:"Ã¥";s:3:"aÌŠ";s:2:"ç";s:3:"ç";s:2:"è";s:3:"eÌ€";s:2:"é";s:3:"eÌ";s:2:"ê";s:3:"eÌ‚";s:2:"ë";s:3:"ë";s:2:"ì";s:3:"iÌ€";s:2:"í";s:3:"iÌ";s:2:"î";s:3:"iÌ‚";s:2:"ï";s:3:"ï";s:2:"ñ";s:3:"ñ";s:2:"ò";s:3:"oÌ€";s:2:"ó";s:3:"oÌ";s:2:"ô";s:3:"oÌ‚";s:2:"õ";s:3:"õ";s:2:"ö";s:3:"ö";s:2:"ù";s:3:"uÌ€";s:2:"ú";s:3:"uÌ";s:2:"û";s:3:"uÌ‚";s:2:"ü";s:3:"ü";s:2:"ý";s:3:"yÌ";s:2:"ÿ";s:3:"ÿ";s:2:"Ä€";s:3:"AÌ„";s:2:"Ä";s:3:"aÌ„";s:2:"Ä‚";s:3:"Ă";s:2:"ă";s:3:"ă";s:2:"Ä„";s:3:"Ą";s:2:"Ä…";s:3:"ą";s:2:"Ć";s:3:"CÌ";s:2:"ć";s:3:"cÌ";s:2:"Ĉ";s:3:"CÌ‚";s:2:"ĉ";s:3:"cÌ‚";s:2:"ÄŠ";s:3:"Ċ";s:2:"Ä‹";s:3:"ċ";s:2:"ÄŒ";s:3:"CÌŒ";s:2:"Ä";s:3:"cÌŒ";s:2:"ÄŽ";s:3:"DÌŒ";s:2:"Ä";s:3:"dÌŒ";s:2:"Ä’";s:3:"EÌ„";s:2:"Ä“";s:3:"eÌ„";s:2:"Ä”";s:3:"Ĕ";s:2:"Ä•";s:3:"ĕ";s:2:"Ä–";s:3:"Ė";s:2:"Ä—";s:3:"ė";s:2:"Ę";s:3:"Ę";s:2:"Ä™";s:3:"ę";s:2:"Äš";s:3:"EÌŒ";s:2:"Ä›";s:3:"eÌŒ";s:2:"Äœ";s:3:"GÌ‚";s:2:"Ä";s:3:"gÌ‚";s:2:"Äž";s:3:"Ğ";s:2:"ÄŸ";s:3:"ğ";s:2:"Ä ";s:3:"Ġ";s:2:"Ä¡";s:3:"ġ";s:2:"Ä¢";s:3:"Ģ";s:2:"Ä£";s:3:"ģ";s:2:"Ĥ";s:3:"HÌ‚";s:2:"Ä¥";s:3:"hÌ‚";s:2:"Ĩ";s:3:"Ĩ";s:2:"Ä©";s:3:"ĩ";s:2:"Ī";s:3:"IÌ„";s:2:"Ä«";s:3:"iÌ„";s:2:"Ĭ";s:3:"Ĭ";s:2:"Ä­";s:3:"ĭ";s:2:"Ä®";s:3:"Į";s:2:"į";s:3:"į";s:2:"İ";s:3:"İ";s:2:"Ä´";s:3:"JÌ‚";s:2:"ĵ";s:3:"jÌ‚";s:2:"Ķ";s:3:"Ķ";s:2:"Ä·";s:3:"ķ";s:2:"Ĺ";s:3:"LÌ";s:2:"ĺ";s:3:"lÌ";s:2:"Ä»";s:3:"Ļ";s:2:"ļ";s:3:"ļ";s:2:"Ľ";s:3:"LÌŒ";s:2:"ľ";s:3:"lÌŒ";s:2:"Ń";s:3:"NÌ";s:2:"Å„";s:3:"nÌ";s:2:"Å…";s:3:"Ņ";s:2:"ņ";s:3:"ņ";s:2:"Ň";s:3:"NÌŒ";s:2:"ň";s:3:"nÌŒ";s:2:"ÅŒ";s:3:"OÌ„";s:2:"Å";s:3:"oÌ„";s:2:"ÅŽ";s:3:"Ŏ";s:2:"Å";s:3:"ŏ";s:2:"Å";s:3:"OÌ‹";s:2:"Å‘";s:3:"oÌ‹";s:2:"Å”";s:3:"RÌ";s:2:"Å•";s:3:"rÌ";s:2:"Å–";s:3:"Ŗ";s:2:"Å—";s:3:"ŗ";s:2:"Ř";s:3:"RÌŒ";s:2:"Å™";s:3:"rÌŒ";s:2:"Åš";s:3:"SÌ";s:2:"Å›";s:3:"sÌ";s:2:"Åœ";s:3:"SÌ‚";s:2:"Å";s:3:"sÌ‚";s:2:"Åž";s:3:"Ş";s:2:"ÅŸ";s:3:"ş";s:2:"Å ";s:3:"SÌŒ";s:2:"Å¡";s:3:"sÌŒ";s:2:"Å¢";s:3:"Ţ";s:2:"Å£";s:3:"ţ";s:2:"Ť";s:3:"TÌŒ";s:2:"Å¥";s:3:"tÌŒ";s:2:"Ũ";s:3:"Ũ";s:2:"Å©";s:3:"ũ";s:2:"Ū";s:3:"UÌ„";s:2:"Å«";s:3:"uÌ„";s:2:"Ŭ";s:3:"Ŭ";s:2:"Å­";s:3:"ŭ";s:2:"Å®";s:3:"UÌŠ";s:2:"ů";s:3:"uÌŠ";s:2:"Ű";s:3:"UÌ‹";s:2:"ű";s:3:"uÌ‹";s:2:"Ų";s:3:"Ų";s:2:"ų";s:3:"ų";s:2:"Å´";s:3:"WÌ‚";s:2:"ŵ";s:3:"wÌ‚";s:2:"Ŷ";s:3:"YÌ‚";s:2:"Å·";s:3:"yÌ‚";s:2:"Ÿ";s:3:"Ÿ";s:2:"Ź";s:3:"ZÌ";s:2:"ź";s:3:"zÌ";s:2:"Å»";s:3:"Ż";s:2:"ż";s:3:"ż";s:2:"Ž";s:3:"ZÌŒ";s:2:"ž";s:3:"zÌŒ";s:2:"Æ ";s:3:"OÌ›";s:2:"Æ¡";s:3:"oÌ›";s:2:"Ư";s:3:"UÌ›";s:2:"ư";s:3:"uÌ›";s:2:"Ç";s:3:"AÌŒ";s:2:"ÇŽ";s:3:"aÌŒ";s:2:"Ç";s:3:"IÌŒ";s:2:"Ç";s:3:"iÌŒ";s:2:"Ç‘";s:3:"OÌŒ";s:2:"Ç’";s:3:"oÌŒ";s:2:"Ç“";s:3:"UÌŒ";s:2:"Ç”";s:3:"uÌŒ";s:2:"Ç•";s:5:"Ǖ";s:2:"Ç–";s:5:"ǖ";s:2:"Ç—";s:5:"ÜÌ";s:2:"ǘ";s:5:"üÌ";s:2:"Ç™";s:5:"Ǚ";s:2:"Çš";s:5:"ǚ";s:2:"Ç›";s:5:"Ǜ";s:2:"Çœ";s:5:"ǜ";s:2:"Çž";s:5:"Ǟ";s:2:"ÇŸ";s:5:"ǟ";s:2:"Ç ";s:5:"Ǡ";s:2:"Ç¡";s:5:"ǡ";s:2:"Ç¢";s:4:"Ǣ";s:2:"Ç£";s:4:"ǣ";s:2:"Ǧ";s:3:"GÌŒ";s:2:"ǧ";s:3:"gÌŒ";s:2:"Ǩ";s:3:"KÌŒ";s:2:"Ç©";s:3:"kÌŒ";s:2:"Ǫ";s:3:"Ǫ";s:2:"Ç«";s:3:"ǫ";s:2:"Ǭ";s:5:"Ǭ";s:2:"Ç­";s:5:"ǭ";s:2:"Ç®";s:4:"Æ·ÌŒ";s:2:"ǯ";s:4:"Ê’ÌŒ";s:2:"ǰ";s:3:"jÌŒ";s:2:"Ç´";s:3:"GÌ";s:2:"ǵ";s:3:"gÌ";s:2:"Ǹ";s:3:"NÌ€";s:2:"ǹ";s:3:"nÌ€";s:2:"Ǻ";s:5:"AÌŠÌ";s:2:"Ç»";s:5:"aÌŠÌ";s:2:"Ǽ";s:4:"ÆÌ";s:2:"ǽ";s:4:"æÌ";s:2:"Ǿ";s:4:"ØÌ";s:2:"Ç¿";s:4:"øÌ";s:2:"È€";s:3:"AÌ";s:2:"È";s:3:"aÌ";s:2:"È‚";s:3:"AÌ‘";s:2:"ȃ";s:3:"aÌ‘";s:2:"È„";s:3:"EÌ";s:2:"È…";s:3:"eÌ";s:2:"Ȇ";s:3:"EÌ‘";s:2:"ȇ";s:3:"eÌ‘";s:2:"Ȉ";s:3:"IÌ";s:2:"ȉ";s:3:"iÌ";s:2:"ÈŠ";s:3:"IÌ‘";s:2:"È‹";s:3:"iÌ‘";s:2:"ÈŒ";s:3:"OÌ";s:2:"È";s:3:"oÌ";s:2:"ÈŽ";s:3:"OÌ‘";s:2:"È";s:3:"oÌ‘";s:2:"È";s:3:"RÌ";s:2:"È‘";s:3:"rÌ";s:2:"È’";s:3:"RÌ‘";s:2:"È“";s:3:"rÌ‘";s:2:"È”";s:3:"UÌ";s:2:"È•";s:3:"uÌ";s:2:"È–";s:3:"UÌ‘";s:2:"È—";s:3:"uÌ‘";s:2:"Ș";s:3:"Ș";s:2:"È™";s:3:"ș";s:2:"Èš";s:3:"Ț";s:2:"È›";s:3:"ț";s:2:"Èž";s:3:"HÌŒ";s:2:"ÈŸ";s:3:"hÌŒ";s:2:"Ȧ";s:3:"Ȧ";s:2:"ȧ";s:3:"ȧ";s:2:"Ȩ";s:3:"Ȩ";s:2:"È©";s:3:"ȩ";s:2:"Ȫ";s:5:"Ȫ";s:2:"È«";s:5:"ȫ";s:2:"Ȭ";s:5:"Ȭ";s:2:"È­";s:5:"ȭ";s:2:"È®";s:3:"Ȯ";s:2:"ȯ";s:3:"ȯ";s:2:"Ȱ";s:5:"Ȱ";s:2:"ȱ";s:5:"ȱ";s:2:"Ȳ";s:3:"YÌ„";s:2:"ȳ";s:3:"yÌ„";s:2:"Í€";s:2:"Ì€";s:2:"Í";s:2:"Ì";s:2:"̓";s:2:"Ì“";s:2:"Í„";s:4:"̈Ì";s:2:"Í´";s:2:"ʹ";s:2:";";s:1:";";s:2:"Î…";s:4:"¨Ì";s:2:"Ά";s:4:"ΑÌ";s:2:"·";s:2:"·";s:2:"Έ";s:4:"ΕÌ";s:2:"Ή";s:4:"ΗÌ";s:2:"Ί";s:4:"ΙÌ";s:2:"ÎŒ";s:4:"ΟÌ";s:2:"ÎŽ";s:4:"Î¥Ì";s:2:"Î";s:4:"ΩÌ";s:2:"Î";s:6:"ϊÌ";s:2:"Ϊ";s:4:"Ϊ";s:2:"Ϋ";s:4:"Ϋ";s:2:"ά";s:4:"αÌ";s:2:"έ";s:4:"εÌ";s:2:"ή";s:4:"ηÌ";s:2:"ί";s:4:"ιÌ";s:2:"ΰ";s:6:"ϋÌ";s:2:"ÏŠ";s:4:"ϊ";s:2:"Ï‹";s:4:"ϋ";s:2:"ÏŒ";s:4:"οÌ";s:2:"Ï";s:4:"Ï…Ì";s:2:"ÏŽ";s:4:"ωÌ";s:2:"Ï“";s:4:"Ï’Ì";s:2:"Ï”";s:4:"ϔ";s:2:"Ѐ";s:4:"Ѐ";s:2:"Ð";s:4:"Ё";s:2:"Ѓ";s:4:"ГÌ";s:2:"Ї";s:4:"Ї";s:2:"ÐŒ";s:4:"КÌ";s:2:"Ð";s:4:"Ѝ";s:2:"ÐŽ";s:4:"Ў";s:2:"Й";s:4:"Й";s:2:"й";s:4:"й";s:2:"Ñ";s:4:"ѐ";s:2:"Ñ‘";s:4:"ё";s:2:"Ñ“";s:4:"гÌ";s:2:"Ñ—";s:4:"ї";s:2:"Ñœ";s:4:"кÌ";s:2:"Ñ";s:4:"ѝ";s:2:"Ñž";s:4:"ў";s:2:"Ѷ";s:4:"Ñ´Ì";s:2:"Ñ·";s:4:"ѵÌ";s:2:"Ó";s:4:"Ӂ";s:2:"Ó‚";s:4:"ӂ";s:2:"Ó";s:4:"Ð̆";s:2:"Ó‘";s:4:"ӑ";s:2:"Ó’";s:4:"Ð̈";s:2:"Ó“";s:4:"ӓ";s:2:"Ó–";s:4:"Ӗ";s:2:"Ó—";s:4:"ӗ";s:2:"Óš";s:4:"Ӛ";s:2:"Ó›";s:4:"ӛ";s:2:"Óœ";s:4:"Ӝ";s:2:"Ó";s:4:"ӝ";s:2:"Óž";s:4:"Ӟ";s:2:"ÓŸ";s:4:"ӟ";s:2:"Ó¢";s:4:"Ӣ";s:2:"Ó£";s:4:"ӣ";s:2:"Ó¤";s:4:"Ӥ";s:2:"Ó¥";s:4:"ӥ";s:2:"Ó¦";s:4:"Ӧ";s:2:"Ó§";s:4:"ӧ";s:2:"Óª";s:4:"Ӫ";s:2:"Ó«";s:4:"ӫ";s:2:"Ó¬";s:4:"Ӭ";s:2:"Ó­";s:4:"Ñ̈";s:2:"Ó®";s:4:"Ӯ";s:2:"Ó¯";s:4:"ӯ";s:2:"Ó°";s:4:"Ӱ";s:2:"Ó±";s:4:"ӱ";s:2:"Ó²";s:4:"Ӳ";s:2:"Ó³";s:4:"ӳ";s:2:"Ó´";s:4:"Ӵ";s:2:"Óµ";s:4:"ӵ";s:2:"Ó¸";s:4:"Ӹ";s:2:"Ó¹";s:4:"ӹ";s:2:"Ø¢";s:4:"آ";s:2:"Ø£";s:4:"أ";s:2:"ؤ";s:4:"ÙˆÙ”";s:2:"Ø¥";s:4:"إ";s:2:"ئ";s:4:"ÙŠÙ”";s:2:"Û€";s:4:"Û•Ù”";s:2:"Û‚";s:4:"ÛÙ”";s:2:"Û“";s:4:"Û’Ù”";s:3:"ऩ";s:6:"ऩ";s:3:"ऱ";s:6:"ऱ";s:3:"ऴ";s:6:"ऴ";s:3:"क़";s:6:"क़";s:3:"ख़";s:6:"ख़";s:3:"ग़";s:6:"ग़";s:3:"ज़";s:6:"ज़";s:3:"ड़";s:6:"ड़";s:3:"à¥";s:6:"ढ़";s:3:"फ़";s:6:"फ़";s:3:"य़";s:6:"य़";s:3:"à§‹";s:6:"ো";s:3:"à§Œ";s:6:"ৌ";s:3:"à§œ";s:6:"ড়";s:3:"à§";s:6:"ঢ়";s:3:"à§Ÿ";s:6:"য়";s:3:"ਲ਼";s:6:"ਲ਼";s:3:"ਸ਼";s:6:"ਸ਼";s:3:"à©™";s:6:"ਖ਼";s:3:"ਗ਼";s:6:"ਗ਼";s:3:"à©›";s:6:"ਜ਼";s:3:"ਫ਼";s:6:"ਫ਼";s:3:"à­ˆ";s:6:"ୈ";s:3:"à­‹";s:6:"ୋ";s:3:"à­Œ";s:6:"ୌ";s:3:"à­œ";s:6:"ଡ଼";s:3:"à­";s:6:"ଢ଼";s:3:"à®”";s:6:"ஔ";s:3:"ொ";s:6:"ொ";s:3:"ோ";s:6:"ோ";s:3:"ௌ";s:6:"ௌ";s:3:"ై";s:6:"ై";s:3:"à³€";s:6:"ೀ";s:3:"ೇ";s:6:"ೇ";s:3:"ೈ";s:6:"ೈ";s:3:"ೊ";s:6:"ೊ";s:3:"ೋ";s:9:"ೋ";s:3:"ൊ";s:6:"ൊ";s:3:"ോ";s:6:"ോ";s:3:"ൌ";s:6:"ൌ";s:3:"à·š";s:6:"ේ";s:3:"à·œ";s:6:"à·™à·";s:3:"à·";s:9:"à·™à·à·Š";s:3:"à·ž";s:6:"ෞ";s:3:"གྷ";s:6:"གྷ";s:3:"à½";s:6:"ཌྷ";s:3:"དྷ";s:6:"དྷ";s:3:"བྷ";s:6:"བྷ";s:3:"ཛྷ";s:6:"ཛྷ";s:3:"ཀྵ";s:6:"ཀྵ";s:3:"ཱི";s:6:"ཱི";s:3:"ཱུ";s:6:"ཱུ";s:3:"ྲྀ";s:6:"ྲྀ";s:3:"ླྀ";s:6:"ླྀ";s:3:"à¾";s:6:"ཱྀ";s:3:"ྒྷ";s:6:"ྒྷ";s:3:"à¾";s:6:"ྜྷ";s:3:"ྡྷ";s:6:"ྡྷ";s:3:"ྦྷ";s:6:"ྦྷ";s:3:"ྫྷ";s:6:"ྫྷ";s:3:"ྐྵ";s:6:"à¾à¾µ";s:3:"ဦ";s:6:"ဦ";s:3:"ᬆ";s:6:"ᬆ";s:3:"ᬈ";s:6:"ᬈ";s:3:"ᬊ";s:6:"ᬊ";s:3:"ᬌ";s:6:"ᬌ";s:3:"ᬎ";s:6:"á¬á¬µ";s:3:"ᬒ";s:6:"ᬒ";s:3:"ᬻ";s:6:"ᬻ";s:3:"ᬽ";s:6:"ᬽ";s:3:"á­€";s:6:"ᭀ";s:3:"á­";s:6:"ᭁ";s:3:"á­ƒ";s:6:"ᭃ";s:3:"Ḁ";s:3:"AÌ¥";s:3:"á¸";s:3:"aÌ¥";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"BÌ£";s:3:"ḅ";s:3:"bÌ£";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:3:"Ḉ";s:5:"ÇÌ";s:3:"ḉ";s:5:"çÌ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"DÌ£";s:3:"á¸";s:3:"dÌ£";s:3:"Ḏ";s:3:"Ḏ";s:3:"á¸";s:3:"ḏ";s:3:"á¸";s:3:"Ḑ";s:3:"ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"DÌ­";s:3:"ḓ";s:3:"dÌ­";s:3:"Ḕ";s:5:"Ḕ";s:3:"ḕ";s:5:"ḕ";s:3:"Ḗ";s:5:"EÌ„Ì";s:3:"ḗ";s:5:"eÌ„Ì";s:3:"Ḙ";s:3:"EÌ­";s:3:"ḙ";s:3:"eÌ­";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:3:"Ḝ";s:5:"Ḝ";s:3:"á¸";s:5:"ḝ";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"GÌ„";s:3:"ḡ";s:3:"gÌ„";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"HÌ£";s:3:"ḥ";s:3:"hÌ£";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"HÌ®";s:3:"ḫ";s:3:"hÌ®";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:3:"Ḯ";s:5:"ÏÌ";s:3:"ḯ";s:5:"ïÌ";s:3:"Ḱ";s:3:"KÌ";s:3:"ḱ";s:3:"kÌ";s:3:"Ḳ";s:3:"KÌ£";s:3:"ḳ";s:3:"kÌ£";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"LÌ£";s:3:"ḷ";s:3:"lÌ£";s:3:"Ḹ";s:5:"Ḹ";s:3:"ḹ";s:5:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"LÌ­";s:3:"ḽ";s:3:"lÌ­";s:3:"Ḿ";s:3:"MÌ";s:3:"ḿ";s:3:"mÌ";s:3:"á¹€";s:3:"Ṁ";s:3:"á¹";s:3:"ṁ";s:3:"Ṃ";s:3:"MÌ£";s:3:"ṃ";s:3:"mÌ£";s:3:"Ṅ";s:3:"Ṅ";s:3:"á¹…";s:3:"ṅ";s:3:"Ṇ";s:3:"NÌ£";s:3:"ṇ";s:3:"nÌ£";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"NÌ­";s:3:"ṋ";s:3:"nÌ­";s:3:"Ṍ";s:5:"ÕÌ";s:3:"á¹";s:5:"õÌ";s:3:"Ṏ";s:5:"Ṏ";s:3:"á¹";s:5:"ṏ";s:3:"á¹";s:5:"Ṑ";s:3:"ṑ";s:5:"ṑ";s:3:"á¹’";s:5:"OÌ„Ì";s:3:"ṓ";s:5:"oÌ„Ì";s:3:"á¹”";s:3:"PÌ";s:3:"ṕ";s:3:"pÌ";s:3:"á¹–";s:3:"Ṗ";s:3:"á¹—";s:3:"ṗ";s:3:"Ṙ";s:3:"Ṙ";s:3:"á¹™";s:3:"ṙ";s:3:"Ṛ";s:3:"RÌ£";s:3:"á¹›";s:3:"rÌ£";s:3:"Ṝ";s:5:"Ṝ";s:3:"á¹";s:5:"ṝ";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"á¹ ";s:3:"Ṡ";s:3:"ṡ";s:3:"ṡ";s:3:"á¹¢";s:3:"SÌ£";s:3:"á¹£";s:3:"sÌ£";s:3:"Ṥ";s:5:"SÌ̇";s:3:"á¹¥";s:5:"sÌ̇";s:3:"Ṧ";s:5:"Ṧ";s:3:"á¹§";s:5:"ṧ";s:3:"Ṩ";s:5:"Ṩ";s:3:"ṩ";s:5:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"TÌ£";s:3:"á¹­";s:3:"tÌ£";s:3:"á¹®";s:3:"Ṯ";s:3:"ṯ";s:3:"ṯ";s:3:"á¹°";s:3:"TÌ­";s:3:"á¹±";s:3:"tÌ­";s:3:"á¹²";s:3:"Ṳ";s:3:"á¹³";s:3:"ṳ";s:3:"á¹´";s:3:"Ṵ";s:3:"á¹µ";s:3:"ṵ";s:3:"á¹¶";s:3:"UÌ­";s:3:"á¹·";s:3:"uÌ­";s:3:"Ṹ";s:5:"ŨÌ";s:3:"á¹¹";s:5:"ũÌ";s:3:"Ṻ";s:5:"Ṻ";s:3:"á¹»";s:5:"ṻ";s:3:"á¹¼";s:3:"Ṽ";s:3:"á¹½";s:3:"ṽ";s:3:"á¹¾";s:3:"VÌ£";s:3:"ṿ";s:3:"vÌ£";s:3:"Ẁ";s:3:"WÌ€";s:3:"áº";s:3:"wÌ€";s:3:"Ẃ";s:3:"WÌ";s:3:"ẃ";s:3:"wÌ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"WÌ£";s:3:"ẉ";s:3:"wÌ£";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"áº";s:3:"ẍ";s:3:"Ẏ";s:3:"Ẏ";s:3:"áº";s:3:"ẏ";s:3:"áº";s:3:"ZÌ‚";s:3:"ẑ";s:3:"zÌ‚";s:3:"Ẓ";s:3:"ZÌ£";s:3:"ẓ";s:3:"zÌ£";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"ẘ";s:3:"wÌŠ";s:3:"ẙ";s:3:"yÌŠ";s:3:"ẛ";s:4:"ẛ";s:3:"Ạ";s:3:"AÌ£";s:3:"ạ";s:3:"aÌ£";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:3:"Ấ";s:5:"AÌ‚Ì";s:3:"ấ";s:5:"aÌ‚Ì";s:3:"Ầ";s:5:"Ầ";s:3:"ầ";s:5:"ầ";s:3:"Ẩ";s:5:"Ẩ";s:3:"ẩ";s:5:"ẩ";s:3:"Ẫ";s:5:"Ẫ";s:3:"ẫ";s:5:"ẫ";s:3:"Ậ";s:5:"Ậ";s:3:"ậ";s:5:"ậ";s:3:"Ắ";s:5:"ĂÌ";s:3:"ắ";s:5:"ăÌ";s:3:"Ằ";s:5:"Ằ";s:3:"ằ";s:5:"ằ";s:3:"Ẳ";s:5:"Ẳ";s:3:"ẳ";s:5:"ẳ";s:3:"Ẵ";s:5:"Ẵ";s:3:"ẵ";s:5:"ẵ";s:3:"Ặ";s:5:"Ặ";s:3:"ặ";s:5:"ặ";s:3:"Ẹ";s:3:"EÌ£";s:3:"ẹ";s:3:"eÌ£";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:3:"Ế";s:5:"EÌ‚Ì";s:3:"ế";s:5:"eÌ‚Ì";s:3:"Ề";s:5:"Ề";s:3:"á»";s:5:"ề";s:3:"Ể";s:5:"Ể";s:3:"ể";s:5:"ể";s:3:"Ễ";s:5:"Ễ";s:3:"á»…";s:5:"ễ";s:3:"Ệ";s:5:"Ệ";s:3:"ệ";s:5:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"Ị";s:3:"IÌ£";s:3:"ị";s:3:"iÌ£";s:3:"Ọ";s:3:"OÌ£";s:3:"á»";s:3:"oÌ£";s:3:"Ỏ";s:3:"Ỏ";s:3:"á»";s:3:"ỏ";s:3:"á»";s:5:"OÌ‚Ì";s:3:"ố";s:5:"oÌ‚Ì";s:3:"á»’";s:5:"Ồ";s:3:"ồ";s:5:"ồ";s:3:"á»”";s:5:"Ổ";s:3:"ổ";s:5:"ổ";s:3:"á»–";s:5:"Ỗ";s:3:"á»—";s:5:"ỗ";s:3:"Ộ";s:5:"Ộ";s:3:"á»™";s:5:"ộ";s:3:"Ớ";s:5:"OÌ›Ì";s:3:"á»›";s:5:"oÌ›Ì";s:3:"Ờ";s:5:"Ờ";s:3:"á»";s:5:"ờ";s:3:"Ở";s:5:"Ở";s:3:"ở";s:5:"ở";s:3:"á» ";s:5:"Ỡ";s:3:"ỡ";s:5:"ỡ";s:3:"Ợ";s:5:"Ợ";s:3:"ợ";s:5:"ợ";s:3:"Ụ";s:3:"UÌ£";s:3:"ụ";s:3:"uÌ£";s:3:"Ủ";s:3:"Ủ";s:3:"á»§";s:3:"ủ";s:3:"Ứ";s:5:"UÌ›Ì";s:3:"ứ";s:5:"uÌ›Ì";s:3:"Ừ";s:5:"Ừ";s:3:"ừ";s:5:"ừ";s:3:"Ử";s:5:"Ử";s:3:"á»­";s:5:"ử";s:3:"á»®";s:5:"Ữ";s:3:"ữ";s:5:"ữ";s:3:"á»°";s:5:"Ự";s:3:"á»±";s:5:"ự";s:3:"Ỳ";s:3:"YÌ€";s:3:"ỳ";s:3:"yÌ€";s:3:"á»´";s:3:"YÌ£";s:3:"ỵ";s:3:"yÌ£";s:3:"á»¶";s:3:"Ỷ";s:3:"á»·";s:3:"ỷ";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:3:"á¼€";s:4:"ἀ";s:3:"á¼";s:4:"ἁ";s:3:"ἂ";s:6:"ἂ";s:3:"ἃ";s:6:"ἃ";s:3:"ἄ";s:6:"ἀÌ";s:3:"á¼…";s:6:"ἁÌ";s:3:"ἆ";s:6:"ἆ";s:3:"ἇ";s:6:"ἇ";s:3:"Ἀ";s:4:"Ἀ";s:3:"Ἁ";s:4:"Ἁ";s:3:"Ἂ";s:6:"Ἂ";s:3:"Ἃ";s:6:"Ἃ";s:3:"Ἄ";s:6:"ἈÌ";s:3:"á¼";s:6:"ἉÌ";s:3:"Ἆ";s:6:"Ἆ";s:3:"á¼";s:6:"Ἇ";s:3:"á¼";s:4:"ἐ";s:3:"ἑ";s:4:"ἑ";s:3:"á¼’";s:6:"ἒ";s:3:"ἓ";s:6:"ἓ";s:3:"á¼”";s:6:"ἐÌ";s:3:"ἕ";s:6:"ἑÌ";s:3:"Ἐ";s:4:"Ἐ";s:3:"á¼™";s:4:"Ἑ";s:3:"Ἒ";s:6:"Ἒ";s:3:"á¼›";s:6:"Ἓ";s:3:"Ἔ";s:6:"ἘÌ";s:3:"á¼";s:6:"ἙÌ";s:3:"á¼ ";s:4:"ἠ";s:3:"ἡ";s:4:"ἡ";s:3:"á¼¢";s:6:"ἢ";s:3:"á¼£";s:6:"ἣ";s:3:"ἤ";s:6:"ἠÌ";s:3:"á¼¥";s:6:"ἡÌ";s:3:"ἦ";s:6:"ἦ";s:3:"á¼§";s:6:"ἧ";s:3:"Ἠ";s:4:"Ἠ";s:3:"Ἡ";s:4:"Ἡ";s:3:"Ἢ";s:6:"Ἢ";s:3:"Ἣ";s:6:"Ἣ";s:3:"Ἤ";s:6:"ἨÌ";s:3:"á¼­";s:6:"ἩÌ";s:3:"á¼®";s:6:"Ἦ";s:3:"Ἧ";s:6:"Ἧ";s:3:"á¼°";s:4:"ἰ";s:3:"á¼±";s:4:"ἱ";s:3:"á¼²";s:6:"ἲ";s:3:"á¼³";s:6:"ἳ";s:3:"á¼´";s:6:"ἰÌ";s:3:"á¼µ";s:6:"ἱÌ";s:3:"á¼¶";s:6:"ἶ";s:3:"á¼·";s:6:"ἷ";s:3:"Ἰ";s:4:"Ἰ";s:3:"á¼¹";s:4:"Ἱ";s:3:"Ἲ";s:6:"Ἲ";s:3:"á¼»";s:6:"Ἳ";s:3:"á¼¼";s:6:"ἸÌ";s:3:"á¼½";s:6:"ἹÌ";s:3:"á¼¾";s:6:"Ἶ";s:3:"Ἷ";s:6:"Ἷ";s:3:"á½€";s:4:"ὀ";s:3:"á½";s:4:"ὁ";s:3:"ὂ";s:6:"ὂ";s:3:"ὃ";s:6:"ὃ";s:3:"ὄ";s:6:"ὀÌ";s:3:"á½…";s:6:"ὁÌ";s:3:"Ὀ";s:4:"Ὀ";s:3:"Ὁ";s:4:"Ὁ";s:3:"Ὂ";s:6:"Ὂ";s:3:"Ὃ";s:6:"Ὃ";s:3:"Ὄ";s:6:"ὈÌ";s:3:"á½";s:6:"ὉÌ";s:3:"á½";s:4:"Ï…Ì“";s:3:"ὑ";s:4:"Ï…Ì”";s:3:"á½’";s:6:"ὒ";s:3:"ὓ";s:6:"ὓ";s:3:"á½”";s:6:"Ï…Ì“Ì";s:3:"ὕ";s:6:"Ï…Ì”Ì";s:3:"á½–";s:6:"ὖ";s:3:"á½—";s:6:"ὗ";s:3:"á½™";s:4:"Ὑ";s:3:"á½›";s:6:"Ὓ";s:3:"á½";s:6:"ὙÌ";s:3:"Ὗ";s:6:"Ὗ";s:3:"á½ ";s:4:"ὠ";s:3:"ὡ";s:4:"ὡ";s:3:"á½¢";s:6:"ὢ";s:3:"á½£";s:6:"ὣ";s:3:"ὤ";s:6:"ὠÌ";s:3:"á½¥";s:6:"ὡÌ";s:3:"ὦ";s:6:"ὦ";s:3:"á½§";s:6:"ὧ";s:3:"Ὠ";s:4:"Ὠ";s:3:"Ὡ";s:4:"Ὡ";s:3:"Ὢ";s:6:"Ὢ";s:3:"Ὣ";s:6:"Ὣ";s:3:"Ὤ";s:6:"ὨÌ";s:3:"á½­";s:6:"ὩÌ";s:3:"á½®";s:6:"Ὦ";s:3:"Ὧ";s:6:"Ὧ";s:3:"á½°";s:4:"ὰ";s:3:"á½±";s:4:"αÌ";s:3:"á½²";s:4:"ὲ";s:3:"á½³";s:4:"εÌ";s:3:"á½´";s:4:"ὴ";s:3:"á½µ";s:4:"ηÌ";s:3:"á½¶";s:4:"ὶ";s:3:"á½·";s:4:"ιÌ";s:3:"ὸ";s:4:"ὸ";s:3:"á½¹";s:4:"οÌ";s:3:"ὺ";s:4:"Ï…Ì€";s:3:"á½»";s:4:"Ï…Ì";s:3:"á½¼";s:4:"ὼ";s:3:"á½½";s:4:"ωÌ";s:3:"á¾€";s:6:"ᾀ";s:3:"á¾";s:6:"ᾁ";s:3:"ᾂ";s:8:"ᾂ";s:3:"ᾃ";s:8:"ᾃ";s:3:"ᾄ";s:8:"ἀÌÍ…";s:3:"á¾…";s:8:"ἁÌÍ…";s:3:"ᾆ";s:8:"ᾆ";s:3:"ᾇ";s:8:"ᾇ";s:3:"ᾈ";s:6:"ᾈ";s:3:"ᾉ";s:6:"ᾉ";s:3:"ᾊ";s:8:"ᾊ";s:3:"ᾋ";s:8:"ᾋ";s:3:"ᾌ";s:8:"ἈÌÍ…";s:3:"á¾";s:8:"ἉÌÍ…";s:3:"ᾎ";s:8:"ᾎ";s:3:"á¾";s:8:"ᾏ";s:3:"á¾";s:6:"ᾐ";s:3:"ᾑ";s:6:"ᾑ";s:3:"á¾’";s:8:"ᾒ";s:3:"ᾓ";s:8:"ᾓ";s:3:"á¾”";s:8:"ἠÌÍ…";s:3:"ᾕ";s:8:"ἡÌÍ…";s:3:"á¾–";s:8:"ᾖ";s:3:"á¾—";s:8:"ᾗ";s:3:"ᾘ";s:6:"ᾘ";s:3:"á¾™";s:6:"ᾙ";s:3:"ᾚ";s:8:"ᾚ";s:3:"á¾›";s:8:"ᾛ";s:3:"ᾜ";s:8:"ἨÌÍ…";s:3:"á¾";s:8:"ἩÌÍ…";s:3:"ᾞ";s:8:"ᾞ";s:3:"ᾟ";s:8:"ᾟ";s:3:"á¾ ";s:6:"ᾠ";s:3:"ᾡ";s:6:"ᾡ";s:3:"á¾¢";s:8:"ᾢ";s:3:"á¾£";s:8:"ᾣ";s:3:"ᾤ";s:8:"ὠÌÍ…";s:3:"á¾¥";s:8:"ὡÌÍ…";s:3:"ᾦ";s:8:"ᾦ";s:3:"á¾§";s:8:"ᾧ";s:3:"ᾨ";s:6:"ᾨ";s:3:"ᾩ";s:6:"ᾩ";s:3:"ᾪ";s:8:"ᾪ";s:3:"ᾫ";s:8:"ᾫ";s:3:"ᾬ";s:8:"ὨÌÍ…";s:3:"á¾­";s:8:"ὩÌÍ…";s:3:"á¾®";s:8:"ᾮ";s:3:"ᾯ";s:8:"ᾯ";s:3:"á¾°";s:4:"ᾰ";s:3:"á¾±";s:4:"ᾱ";s:3:"á¾²";s:6:"ᾲ";s:3:"á¾³";s:4:"ᾳ";s:3:"á¾´";s:6:"αÌÍ…";s:3:"á¾¶";s:4:"ᾶ";s:3:"á¾·";s:6:"ᾷ";s:3:"Ᾰ";s:4:"Ᾰ";s:3:"á¾¹";s:4:"Ᾱ";s:3:"Ὰ";s:4:"Ὰ";s:3:"á¾»";s:4:"ΑÌ";s:3:"á¾¼";s:4:"ᾼ";s:3:"á¾¾";s:2:"ι";s:3:"á¿";s:4:"῁";s:3:"á¿‚";s:6:"ῂ";s:3:"ῃ";s:4:"ῃ";s:3:"á¿„";s:6:"ηÌÍ…";s:3:"ῆ";s:4:"ῆ";s:3:"ῇ";s:6:"ῇ";s:3:"Ὲ";s:4:"Ὲ";s:3:"Έ";s:4:"ΕÌ";s:3:"Ὴ";s:4:"Ὴ";s:3:"á¿‹";s:4:"ΗÌ";s:3:"ῌ";s:4:"ῌ";s:3:"á¿";s:5:"῍";s:3:"῎";s:5:"᾿Ì";s:3:"á¿";s:5:"῏";s:3:"á¿";s:4:"ῐ";s:3:"á¿‘";s:4:"ῑ";s:3:"á¿’";s:6:"ῒ";s:3:"á¿“";s:6:"ϊÌ";s:3:"á¿–";s:4:"ῖ";s:3:"á¿—";s:6:"ῗ";s:3:"Ῐ";s:4:"Ῐ";s:3:"á¿™";s:4:"Ῑ";s:3:"Ὶ";s:4:"Ὶ";s:3:"á¿›";s:4:"ΙÌ";s:3:"á¿";s:5:"῝";s:3:"῞";s:5:"῾Ì";s:3:"῟";s:5:"῟";s:3:"á¿ ";s:4:"ῠ";s:3:"á¿¡";s:4:"Ï…Ì„";s:3:"á¿¢";s:6:"ῢ";s:3:"á¿£";s:6:"ϋÌ";s:3:"ῤ";s:4:"ÏÌ“";s:3:"á¿¥";s:4:"ÏÌ”";s:3:"ῦ";s:4:"Ï…Í‚";s:3:"á¿§";s:6:"ῧ";s:3:"Ῠ";s:4:"Ῠ";s:3:"á¿©";s:4:"Ῡ";s:3:"Ὺ";s:4:"Ὺ";s:3:"á¿«";s:4:"Î¥Ì";s:3:"Ῥ";s:4:"Ῥ";s:3:"á¿­";s:4:"῭";s:3:"á¿®";s:4:"¨Ì";s:3:"`";s:1:"`";s:3:"ῲ";s:6:"ῲ";s:3:"ῳ";s:4:"ῳ";s:3:"á¿´";s:6:"ωÌÍ…";s:3:"á¿¶";s:4:"ῶ";s:3:"á¿·";s:6:"ῷ";s:3:"Ὸ";s:4:"Ὸ";s:3:"Ό";s:4:"ΟÌ";s:3:"Ὼ";s:4:"Ὼ";s:3:"á¿»";s:4:"ΩÌ";s:3:"ῼ";s:4:"ῼ";s:3:"´";s:2:"´";s:3:" ";s:3:" ";s:3:"â€";s:3:" ";s:3:"Ω";s:2:"Ω";s:3:"K";s:1:"K";s:3:"â„«";s:3:"AÌŠ";s:3:"↚";s:5:"â†Ì¸";s:3:"↛";s:5:"↛";s:3:"↮";s:5:"↮";s:3:"â‡";s:5:"â‡Ì¸";s:3:"⇎";s:5:"⇎";s:3:"â‡";s:5:"⇏";s:3:"∄";s:5:"∄";s:3:"∉";s:5:"∉";s:3:"∌";s:5:"∌";s:3:"∤";s:5:"∤";s:3:"∦";s:5:"∦";s:3:"â‰";s:5:"≁";s:3:"≄";s:5:"≄";s:3:"≇";s:5:"≇";s:3:"≉";s:5:"≉";s:3:"≠";s:3:"≠";s:3:"≢";s:5:"≢";s:3:"≭";s:5:"â‰Ì¸";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:3:"≰";s:5:"≰";s:3:"≱";s:5:"≱";s:3:"≴";s:5:"≴";s:3:"≵";s:5:"≵";s:3:"≸";s:5:"≸";s:3:"≹";s:5:"≹";s:3:"⊀";s:5:"⊀";s:3:"âŠ";s:5:"⊁";s:3:"⊄";s:5:"⊄";s:3:"⊅";s:5:"⊅";s:3:"⊈";s:5:"⊈";s:3:"⊉";s:5:"⊉";s:3:"⊬";s:5:"⊬";s:3:"⊭";s:5:"⊭";s:3:"⊮";s:5:"⊮";s:3:"⊯";s:5:"⊯";s:3:"â‹ ";s:5:"⋠";s:3:"â‹¡";s:5:"⋡";s:3:"â‹¢";s:5:"⋢";s:3:"â‹£";s:5:"⋣";s:3:"⋪";s:5:"⋪";s:3:"â‹«";s:5:"⋫";s:3:"⋬";s:5:"⋬";s:3:"â‹­";s:5:"⋭";s:3:"〈";s:3:"〈";s:3:"〉";s:3:"〉";s:3:"⫝̸";s:5:"â«Ì¸";s:3:"ãŒ";s:6:"ã‹ã‚™";s:3:"ãŽ";s:6:"ãã‚™";s:3:"ã";s:6:"ãã‚™";s:3:"ã’";s:6:"ã‘ã‚™";s:3:"ã”";s:6:"ã“ã‚™";s:3:"ã–";s:6:"ã•ã‚™";s:3:"ã˜";s:6:"ã—ã‚™";s:3:"ãš";s:6:"ã™ã‚™";s:3:"ãœ";s:6:"ã›ã‚™";s:3:"ãž";s:6:"ãã‚™";s:3:"ã ";s:6:"ãŸã‚™";s:3:"ã¢";s:6:"ã¡ã‚™";s:3:"ã¥";s:6:"ã¤ã‚™";s:3:"ã§";s:6:"ã¦ã‚™";s:3:"ã©";s:6:"ã¨ã‚™";s:3:"ã°";s:6:"ã¯ã‚™";s:3:"ã±";s:6:"ã¯ã‚š";s:3:"ã³";s:6:"ã²ã‚™";s:3:"ã´";s:6:"ã²ã‚š";s:3:"ã¶";s:6:"ãµã‚™";s:3:"ã·";s:6:"ãµã‚š";s:3:"ã¹";s:6:"ã¸ã‚™";s:3:"ãº";s:6:"ã¸ã‚š";s:3:"ã¼";s:6:"ã»ã‚™";s:3:"ã½";s:6:"ã»ã‚š";s:3:"ã‚”";s:6:"ã†ã‚™";s:3:"ゞ";s:6:"ã‚ã‚™";s:3:"ガ";s:6:"ã‚«ã‚™";s:3:"ã‚®";s:6:"ã‚­ã‚™";s:3:"ã‚°";s:6:"グ";s:3:"ゲ";s:6:"ゲ";s:3:"ã‚´";s:6:"ゴ";s:3:"ã‚¶";s:6:"ザ";s:3:"ジ";s:6:"ã‚·ã‚™";s:3:"ズ";s:6:"ズ";s:3:"ゼ";s:6:"ゼ";s:3:"ゾ";s:6:"ゾ";s:3:"ダ";s:6:"ã‚¿ã‚™";s:3:"ヂ";s:6:"ãƒã‚™";s:3:"ヅ";s:6:"ヅ";s:3:"デ";s:6:"デ";s:3:"ド";s:6:"ド";s:3:"ãƒ";s:6:"ãƒã‚™";s:3:"パ";s:6:"ãƒã‚š";s:3:"ビ";s:6:"ビ";s:3:"ピ";s:6:"ピ";s:3:"ブ";s:6:"ブ";s:3:"プ";s:6:"プ";s:3:"ベ";s:6:"ベ";s:3:"ペ";s:6:"ペ";s:3:"ボ";s:6:"ボ";s:3:"ãƒ";s:6:"ポ";s:3:"ヴ";s:6:"ヴ";s:3:"ヷ";s:6:"ヷ";s:3:"ヸ";s:6:"ヸ";s:3:"ヹ";s:6:"ヹ";s:3:"ヺ";s:6:"ヺ";s:3:"ヾ";s:6:"ヾ";s:3:"豈";s:3:"豈";s:3:"ï¤";s:3:"æ›´";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"å¥";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"å–‡";s:3:"奈";s:3:"奈";s:3:"ï¤";s:3:"懶";s:3:"癩";s:3:"癩";s:3:"ï¤";s:3:"ç¾…";s:3:"ï¤";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"é‚";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"æ´›";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"çž";s:3:"落";s:3:"è½";s:3:"酪";s:3:"é…ª";s:3:"駱";s:3:"é§±";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"åµ";s:3:"ï¤";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"åµ";s:3:"濫";s:3:"æ¿«";s:3:"藍";s:3:"è—";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"è Ÿ";s:3:"廊";s:3:"廊";s:3:"朗";s:3:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"æ“„";s:3:"櫓";s:3:"æ«“";s:3:"爐";s:3:"çˆ";s:3:"盧";s:3:"ç›§";s:3:"老";s:3:"è€";s:3:"蘆";s:3:"蘆";s:3:"虜";s:3:"虜";s:3:"路";s:3:"è·¯";s:3:"露";s:3:"露";s:3:"魯";s:3:"é­¯";s:3:"鷺";s:3:"é·º";s:3:"碌";s:3:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"ç¶ ";s:3:"菉";s:3:"è‰";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"ï¥";s:3:"è«–";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"ç± ";s:3:"聾";s:3:"è¾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"é›·";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"å±¢";s:3:"樓";s:3:"樓";s:3:"ï¥";s:3:"æ·š";s:3:"漏";s:3:"æ¼";s:3:"ï¥";s:3:"ç´¯";s:3:"ï¥";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"å‹’";s:3:"肋";s:3:"è‚‹";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"ç¶¾";s:3:"菱";s:3:"è±";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"æ‹";s:3:"樂";s:3:"樂";s:3:"ï¥";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:3:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:3:"ç•°";s:3:"北";s:3:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"ä¸";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"ç´¢";s:3:"參";s:3:"åƒ";s:3:"塞";s:3:"塞";s:3:"省";s:3:"çœ";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:3:"殺";s:3:"辰";s:3:"è¾°";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:3:"è‹¥";s:3:"掠";s:3:"掠";s:3:"略";s:3:"ç•¥";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"å…©";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"æ¢";s:3:"糧";s:3:"ç³§";s:3:"良";s:3:"良";s:3:"諒";s:3:"è«’";s:3:"量";s:3:"é‡";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"å‘‚";s:3:"ï¦";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"æ—…";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"é–­";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"æ­·";s:3:"ï¦";s:3:"è½¢";s:3:"年";s:3:"å¹´";s:3:"ï¦";s:3:"æ†";s:3:"ï¦";s:3:"戀";s:3:"撚";s:3:"æ’š";s:3:"漣";s:3:"æ¼£";s:3:"煉";s:3:"ç…‰";s:3:"璉";s:3:"ç’‰";s:3:"秊";s:3:"ç§Š";s:3:"練";s:3:"ç·´";s:3:"聯";s:3:"è¯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"è“®";s:3:"連";s:3:"連";s:3:"鍊";s:3:"éŠ";s:3:"列";s:3:"列";s:3:"ï¦";s:3:"劣";s:3:"咽";s:3:"å’½";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"說";s:3:"說";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"æ»";s:3:"殮";s:3:"æ®®";s:3:"簾";s:3:"ç°¾";s:3:"獵";s:3:"çµ";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"寧";s:3:"寧";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"ç‘©";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"è†";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"é›¶";s:3:"靈";s:3:"éˆ";s:3:"領";s:3:"é ˜";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"å°¿";s:3:"料";s:3:"æ–™";s:3:"樂";s:3:"樂";s:3:"ï§€";s:3:"燎";s:3:"ï§";s:3:"療";s:3:"ï§‚";s:3:"蓼";s:3:"遼";s:3:"é¼";s:3:"ï§„";s:3:"é¾";s:3:"ï§…";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"æ»";s:3:"柳";s:3:"柳";s:3:"ï§Š";s:3:"æµ";s:3:"ï§‹";s:3:"溜";s:3:"ï§Œ";s:3:"ç‰";s:3:"ï§";s:3:"ç•™";s:3:"ï§Ž";s:3:"ç¡«";s:3:"ï§";s:3:"ç´";s:3:"ï§";s:3:"類";s:3:"ï§‘";s:3:"å…­";s:3:"ï§’";s:3:"戮";s:3:"ï§“";s:3:"陸";s:3:"ï§”";s:3:"倫";s:3:"ï§•";s:3:"å´™";s:3:"ï§–";s:3:"æ·ª";s:3:"ï§—";s:3:"輪";s:3:"律";s:3:"律";s:3:"ï§™";s:3:"æ…„";s:3:"ï§š";s:3:"æ —";s:3:"ï§›";s:3:"率";s:3:"ï§œ";s:3:"隆";s:3:"ï§";s:3:"利";s:3:"ï§ž";s:3:"å";s:3:"ï§Ÿ";s:3:"å±¥";s:3:"ï§ ";s:3:"易";s:3:"ï§¡";s:3:"æŽ";s:3:"ï§¢";s:3:"梨";s:3:"ï§£";s:3:"æ³¥";s:3:"理";s:3:"ç†";s:3:"ï§¥";s:3:"ç—¢";s:3:"罹";s:3:"ç½¹";s:3:"ï§§";s:3:"è£";s:3:"裡";s:3:"裡";s:3:"ï§©";s:3:"里";s:3:"離";s:3:"離";s:3:"ï§«";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"ï§­";s:3:"å";s:3:"ï§®";s:3:"ç‡";s:3:"璘";s:3:"ç’˜";s:3:"ï§°";s:3:"è—º";s:3:"ï§±";s:3:"隣";s:3:"ï§²";s:3:"é±—";s:3:"ï§³";s:3:"麟";s:3:"ï§´";s:3:"æž—";s:3:"ï§µ";s:3:"æ·‹";s:3:"ï§¶";s:3:"臨";s:3:"ï§·";s:3:"ç«‹";s:3:"笠";s:3:"笠";s:3:"ï§¹";s:3:"ç²’";s:3:"狀";s:3:"ç‹€";s:3:"ï§»";s:3:"ç‚™";s:3:"ï§¼";s:3:"è­˜";s:3:"ï§½";s:3:"什";s:3:"ï§¾";s:3:"茶";s:3:"ï§¿";s:3:"刺";s:3:"切";s:3:"切";s:3:"ï¨";s:3:"度";s:3:"拓";s:3:"æ‹“";s:3:"糖";s:3:"ç³–";s:3:"宅";s:3:"å®…";s:3:"洞";s:3:"æ´ž";s:3:"暴";s:3:"æš´";s:3:"輻";s:3:"è¼»";s:3:"行";s:3:"行";s:3:"降";s:3:"é™";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"å…€";s:3:"ï¨";s:3:"å—€";s:3:"ï¨";s:3:"塚";s:3:"晴";s:3:"æ™´";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:3:"ç¦";s:3:"靖";s:3:"é–";s:3:"ï¨";s:3:"ç²¾";s:3:"羽";s:3:"ç¾½";s:3:"蘒";s:3:"蘒";s:3:"諸";s:3:"諸";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"é¶´";s:3:"郞";s:3:"郞";s:3:"隷";s:3:"éš·";s:3:"侮";s:3:"ä¾®";s:3:"僧";s:3:"僧";s:3:"免";s:3:"å…";s:3:"勉";s:3:"勉";s:3:"勤";s:3:"勤";s:3:"卑";s:3:"å‘";s:3:"喝";s:3:"å–";s:3:"嘆";s:3:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"å¡€";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:3:"å±®";s:3:"悔";s:3:"æ‚”";s:3:"慨";s:3:"æ…¨";s:3:"憎";s:3:"憎";s:3:"ï©€";s:3:"懲";s:3:"ï©";s:3:"æ•";s:3:"ï©‚";s:3:"æ—¢";s:3:"暑";s:3:"æš‘";s:3:"ï©„";s:3:"梅";s:3:"ï©…";s:3:"æµ·";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"æ¼¢";s:3:"煮";s:3:"ç…®";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"ç¢";s:3:"ï©‹";s:3:"碑";s:3:"社";s:3:"社";s:3:"ï©";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"ï©";s:3:"ç¥";s:3:"ï©";s:3:"祖";s:3:"ï©‘";s:3:"ç¥";s:3:"ï©’";s:3:"ç¦";s:3:"ï©“";s:3:"禎";s:3:"ï©”";s:3:"ç©€";s:3:"ï©•";s:3:"çª";s:3:"ï©–";s:3:"節";s:3:"ï©—";s:3:"ç·´";s:3:"縉";s:3:"縉";s:3:"ï©™";s:3:"ç¹";s:3:"署";s:3:"ç½²";s:3:"ï©›";s:3:"者";s:3:"臭";s:3:"臭";s:3:"ï©";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"著";s:3:"è‘—";s:3:"ï© ";s:3:"è¤";s:3:"ï©¡";s:3:"視";s:3:"ï©¢";s:3:"è¬";s:3:"ï©£";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"ï©¥";s:3:"è´ˆ";s:3:"辶";s:3:"è¾¶";s:3:"ï©§";s:3:"逸";s:3:"難";s:3:"難";s:3:"ï©©";s:3:"響";s:3:"頻";s:3:"é »";s:3:"ï©«";s:3:"æµ";s:3:"𤋮";s:4:"𤋮";s:3:"ï©­";s:3:"舘";s:3:"ï©°";s:3:"並";s:3:"况";s:3:"况";s:3:"全";s:3:"å…¨";s:3:"侀";s:3:"ä¾€";s:3:"ï©´";s:3:"å……";s:3:"冀";s:3:"冀";s:3:"ï©¶";s:3:"勇";s:3:"ï©·";s:3:"勺";s:3:"喝";s:3:"å–";s:3:"啕";s:3:"å••";s:3:"喙";s:3:"å–™";s:3:"ï©»";s:3:"å—¢";s:3:"塚";s:3:"塚";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"ï©¿";s:3:"奔";s:3:"婢";s:3:"å©¢";s:3:"ïª";s:3:"嬨";s:3:"廒";s:3:"å»’";s:3:"廙";s:3:"å»™";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"å¾­";s:3:"惘";s:3:"惘";s:3:"慎";s:3:"æ…Ž";s:3:"愈";s:3:"愈";s:3:"憎";s:3:"憎";s:3:"慠";s:3:"æ… ";s:3:"懲";s:3:"懲";s:3:"戴";s:3:"戴";s:3:"ïª";s:3:"æ„";s:3:"搜";s:3:"æœ";s:3:"ïª";s:3:"æ‘’";s:3:"ïª";s:3:"æ•–";s:3:"晴";s:3:"æ™´";s:3:"朗";s:3:"朗";s:3:"望";s:3:"望";s:3:"杖";s:3:"æ–";s:3:"歹";s:3:"æ­¹";s:3:"殺";s:3:"殺";s:3:"流";s:3:"æµ";s:3:"滛";s:3:"æ»›";s:3:"滋";s:3:"滋";s:3:"漢";s:3:"æ¼¢";s:3:"瀞";s:3:"瀞";s:3:"煮";s:3:"ç…®";s:3:"ïª";s:3:"çž§";s:3:"爵";s:3:"爵";s:3:"犯";s:3:"犯";s:3:"猪";s:3:"猪";s:3:"瑱";s:3:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"ç”»";s:3:"瘝";s:3:"ç˜";s:3:"瘟";s:3:"瘟";s:3:"益";s:3:"益";s:3:"盛";s:3:"ç››";s:3:"直";s:3:"ç›´";s:3:"睊";s:3:"çŠ";s:3:"着";s:3:"ç€";s:3:"磌";s:3:"磌";s:3:"窱";s:3:"窱";s:3:"節";s:3:"節";s:3:"类";s:3:"ç±»";s:3:"絛";s:3:"çµ›";s:3:"練";s:3:"ç·´";s:3:"缾";s:3:"ç¼¾";s:3:"者";s:3:"者";s:3:"荒";s:3:"è’";s:3:"華";s:3:"è¯";s:3:"蝹";s:3:"è¹";s:3:"襁";s:3:"è¥";s:3:"覆";s:3:"覆";s:3:"視";s:3:"視";s:3:"調";s:3:"調";s:3:"諸";s:3:"諸";s:3:"請";s:3:"è«‹";s:3:"謁";s:3:"è¬";s:3:"諾";s:3:"諾";s:3:"諭";s:3:"è«­";s:3:"謹";s:3:"謹";s:3:"ï«€";s:3:"變";s:3:"ï«";s:3:"è´ˆ";s:3:"ï«‚";s:3:"輸";s:3:"遲";s:3:"é²";s:3:"ï«„";s:3:"醙";s:3:"ï«…";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"難";s:3:"難";s:3:"靖";s:3:"é–";s:3:"韛";s:3:"韛";s:3:"響";s:3:"響";s:3:"ï«‹";s:3:"é ‹";s:3:"頻";s:3:"é »";s:3:"ï«";s:3:"鬒";s:3:"龜";s:3:"龜";s:3:"ï«";s:4:"𢡊";s:3:"ï«";s:4:"𢡄";s:3:"ï«‘";s:4:"ð£•";s:3:"ï«’";s:3:"ã®";s:3:"ï«“";s:3:"䀘";s:3:"ï«”";s:3:"䀹";s:3:"ï«•";s:4:"𥉉";s:3:"ï«–";s:4:"ð¥³";s:3:"ï«—";s:4:"𧻓";s:3:"齃";s:3:"齃";s:3:"ï«™";s:3:"龎";s:3:"ï¬";s:4:"×™Ö´";s:3:"ײַ";s:4:"ײַ";s:3:"שׁ";s:4:"ש×";s:3:"שׂ";s:4:"שׂ";s:3:"שּׁ";s:6:"שּ×";s:3:"שּׂ";s:6:"שּׂ";s:3:"אַ";s:4:"×Ö·";s:3:"אָ";s:4:"×Ö¸";s:3:"אּ";s:4:"×Ö¼";s:3:"בּ";s:4:"בּ";s:3:"גּ";s:4:"×’Ö¼";s:3:"דּ";s:4:"דּ";s:3:"הּ";s:4:"×”Ö¼";s:3:"וּ";s:4:"וּ";s:3:"זּ";s:4:"×–Ö¼";s:3:"טּ";s:4:"טּ";s:3:"יּ";s:4:"×™Ö¼";s:3:"ךּ";s:4:"ךּ";s:3:"כּ";s:4:"×›Ö¼";s:3:"לּ";s:4:"לּ";s:3:"מּ";s:4:"מּ";s:3:"ï­€";s:4:"× Ö¼";s:3:"ï­";s:4:"סּ";s:3:"ï­ƒ";s:4:"×£Ö¼";s:3:"ï­„";s:4:"פּ";s:3:"ï­†";s:4:"צּ";s:3:"ï­‡";s:4:"×§Ö¼";s:3:"ï­ˆ";s:4:"רּ";s:3:"ï­‰";s:4:"שּ";s:3:"ï­Š";s:4:"תּ";s:3:"ï­‹";s:4:"וֹ";s:3:"ï­Œ";s:4:"בֿ";s:3:"ï­";s:4:"×›Ö¿";s:3:"ï­Ž";s:4:"פֿ";s:4:"ð‘‚š";s:8:"𑂚";s:4:"ð‘‚œ";s:8:"𑂜";s:4:"ð‘‚«";s:8:"𑂫";s:4:"ð‘„®";s:8:"𑄮";s:4:"𑄯";s:8:"𑄯";s:4:"ð…ž";s:8:"ð…—ð…¥";s:4:"ð…Ÿ";s:8:"ð…˜ð…¥";s:4:"ð… ";s:12:"ð…˜ð…¥ð…®";s:4:"ð…¡";s:12:"ð…˜ð…¥ð…¯";s:4:"ð…¢";s:12:"ð…˜ð…¥ð…°";s:4:"ð…£";s:12:"ð…˜ð…¥ð…±";s:4:"ð…¤";s:12:"ð…˜ð…¥ð…²";s:4:"ð†»";s:8:"ð†¹ð…¥";s:4:"ð†¼";s:8:"ð†ºð…¥";s:4:"ð†½";s:12:"ð†¹ð…¥ð…®";s:4:"ð†¾";s:12:"ð†ºð…¥ð…®";s:4:"ð†¿";s:12:"ð†¹ð…¥ð…¯";s:4:"ð‡€";s:12:"ð†ºð…¥ð…¯";s:4:"丽";s:3:"丽";s:4:"ð¯ ";s:3:"丸";s:4:"乁";s:3:"ä¹";s:4:"𠄢";s:4:"ð „¢";s:4:"你";s:3:"ä½ ";s:4:"侮";s:3:"ä¾®";s:4:"侻";s:3:"ä¾»";s:4:"倂";s:3:"倂";s:4:"偺";s:3:"åº";s:4:"備";s:3:"å‚™";s:4:"僧";s:3:"僧";s:4:"像";s:3:"åƒ";s:4:"㒞";s:3:"ã’ž";s:4:"ð¯ ";s:4:"𠘺";s:4:"免";s:3:"å…";s:4:"ð¯ ";s:3:"å…”";s:4:"ð¯ ";s:3:"å…¤";s:4:"具";s:3:"å…·";s:4:"𠔜";s:4:"𠔜";s:4:"㒹";s:3:"ã’¹";s:4:"內";s:3:"å…§";s:4:"再";s:3:"å†";s:4:"𠕋";s:4:"ð •‹";s:4:"冗";s:3:"冗";s:4:"冤";s:3:"冤";s:4:"仌";s:3:"仌";s:4:"冬";s:3:"冬";s:4:"况";s:3:"况";s:4:"𩇟";s:4:"𩇟";s:4:"ð¯ ";s:3:"凵";s:4:"刃";s:3:"刃";s:4:"㓟";s:3:"㓟";s:4:"刻";s:3:"刻";s:4:"剆";s:3:"剆";s:4:"割";s:3:"割";s:4:"剷";s:3:"剷";s:4:"㔕";s:3:"㔕";s:4:"勇";s:3:"勇";s:4:"勉";s:3:"勉";s:4:"勤";s:3:"勤";s:4:"勺";s:3:"勺";s:4:"包";s:3:"包";s:4:"匆";s:3:"匆";s:4:"北";s:3:"北";s:4:"卉";s:3:"å‰";s:4:"卑";s:3:"å‘";s:4:"博";s:3:"åš";s:4:"即";s:3:"å³";s:4:"卽";s:3:"å½";s:4:"卿";s:3:"å¿";s:4:"卿";s:3:"å¿";s:4:"卿";s:3:"å¿";s:4:"𠨬";s:4:"𠨬";s:4:"灰";s:3:"ç°";s:4:"及";s:3:"åŠ";s:4:"叟";s:3:"åŸ";s:4:"𠭣";s:4:"ð ­£";s:4:"叫";s:3:"å«";s:4:"叱";s:3:"å±";s:4:"吆";s:3:"å†";s:4:"咞";s:3:"å’ž";s:4:"吸";s:3:"å¸";s:4:"呈";s:3:"呈";s:4:"周";s:3:"周";s:4:"咢";s:3:"å’¢";s:4:"ð¯¡";s:3:"å“¶";s:4:"唐";s:3:"å”";s:4:"啓";s:3:"å•“";s:4:"啣";s:3:"å•£";s:4:"善";s:3:"å–„";s:4:"善";s:3:"å–„";s:4:"喙";s:3:"å–™";s:4:"喫";s:3:"å–«";s:4:"喳";s:3:"å–³";s:4:"嗂";s:3:"å—‚";s:4:"圖";s:3:"圖";s:4:"嘆";s:3:"嘆";s:4:"ð¯¡";s:3:"圗";s:4:"噑";s:3:"噑";s:4:"ð¯¡";s:3:"å™´";s:4:"ð¯¡";s:3:"切";s:4:"壮";s:3:"壮";s:4:"城";s:3:"城";s:4:"埴";s:3:"埴";s:4:"堍";s:3:"å ";s:4:"型";s:3:"åž‹";s:4:"堲";s:3:"å ²";s:4:"報";s:3:"å ±";s:4:"墬";s:3:"墬";s:4:"𡓤";s:4:"𡓤";s:4:"売";s:3:"売";s:4:"壷";s:3:"壷";s:4:"夆";s:3:"夆";s:4:"ð¯¡";s:3:"多";s:4:"夢";s:3:"夢";s:4:"奢";s:3:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:4:"姬";s:3:"姬";s:4:"娛";s:3:"娛";s:4:"娧";s:3:"娧";s:4:"姘";s:3:"姘";s:4:"婦";s:3:"婦";s:4:"㛮";s:3:"ã›®";s:4:"㛼";s:3:"㛼";s:4:"嬈";s:3:"嬈";s:4:"嬾";s:3:"嬾";s:4:"嬾";s:3:"嬾";s:4:"𡧈";s:4:"𡧈";s:4:"寃";s:3:"寃";s:4:"寘";s:3:"寘";s:4:"寧";s:3:"寧";s:4:"寳";s:3:"寳";s:4:"𡬘";s:4:"𡬘";s:4:"寿";s:3:"寿";s:4:"将";s:3:"å°†";s:4:"当";s:3:"当";s:4:"尢";s:3:"å°¢";s:4:"㞁";s:3:"ãž";s:4:"屠";s:3:"å± ";s:4:"屮";s:3:"å±®";s:4:"峀";s:3:"å³€";s:4:"岍";s:3:"å²";s:4:"𡷤";s:4:"ð¡·¤";s:4:"嵃";s:3:"嵃";s:4:"𡷦";s:4:"ð¡·¦";s:4:"嵮";s:3:"åµ®";s:4:"嵫";s:3:"嵫";s:4:"嵼";s:3:"åµ¼";s:4:"ð¯¢";s:3:"å·¡";s:4:"巢";s:3:"å·¢";s:4:"㠯";s:3:"ã ¯";s:4:"巽";s:3:"å·½";s:4:"帨";s:3:"帨";s:4:"帽";s:3:"帽";s:4:"幩";s:3:"幩";s:4:"㡢";s:3:"ã¡¢";s:4:"𢆃";s:4:"𢆃";s:4:"㡼";s:3:"㡼";s:4:"庰";s:3:"庰";s:4:"庳";s:3:"庳";s:4:"ð¯¢";s:3:"庶";s:4:"廊";s:3:"廊";s:4:"ð¯¢";s:4:"𪎒";s:4:"ð¯¢";s:3:"廾";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"舁";s:3:"èˆ";s:4:"弢";s:3:"å¼¢";s:4:"弢";s:3:"å¼¢";s:4:"㣇";s:3:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:4:"形";s:3:"å½¢";s:4:"彫";s:3:"彫";s:4:"㣣";s:3:"㣣";s:4:"徚";s:3:"徚";s:4:"ð¯¢";s:3:"å¿";s:4:"志";s:3:"å¿—";s:4:"忹";s:3:"忹";s:4:"悁";s:3:"æ‚";s:4:"㤺";s:3:"㤺";s:4:"㤜";s:3:"㤜";s:4:"悔";s:3:"æ‚”";s:4:"𢛔";s:4:"𢛔";s:4:"惇";s:3:"惇";s:4:"慈";s:3:"æ…ˆ";s:4:"慌";s:3:"æ…Œ";s:4:"慎";s:3:"æ…Ž";s:4:"慌";s:3:"æ…Œ";s:4:"慺";s:3:"æ…º";s:4:"憎";s:3:"憎";s:4:"憲";s:3:"憲";s:4:"憤";s:3:"憤";s:4:"憯";s:3:"憯";s:4:"懞";s:3:"懞";s:4:"懲";s:3:"懲";s:4:"懶";s:3:"懶";s:4:"成";s:3:"æˆ";s:4:"戛";s:3:"戛";s:4:"扝";s:3:"æ‰";s:4:"抱";s:3:"抱";s:4:"拔";s:3:"æ‹”";s:4:"捐";s:3:"æ";s:4:"𢬌";s:4:"𢬌";s:4:"挽";s:3:"挽";s:4:"拼";s:3:"拼";s:4:"捨";s:3:"æ¨";s:4:"掃";s:3:"掃";s:4:"揤";s:3:"æ¤";s:4:"𢯱";s:4:"𢯱";s:4:"搢";s:3:"æ¢";s:4:"揅";s:3:"æ…";s:4:"ð¯£";s:3:"掩";s:4:"㨮";s:3:"㨮";s:4:"摩";s:3:"æ‘©";s:4:"摾";s:3:"摾";s:4:"撝";s:3:"æ’";s:4:"摷";s:3:"æ‘·";s:4:"㩬";s:3:"㩬";s:4:"敏";s:3:"æ•";s:4:"敬";s:3:"敬";s:4:"𣀊";s:4:"𣀊";s:4:"旣";s:3:"æ—£";s:4:"書";s:3:"書";s:4:"ð¯£";s:3:"晉";s:4:"㬙";s:3:"㬙";s:4:"ð¯£";s:3:"æš‘";s:4:"ð¯£";s:3:"㬈";s:4:"㫤";s:3:"㫤";s:4:"冒";s:3:"冒";s:4:"冕";s:3:"冕";s:4:"最";s:3:"最";s:4:"暜";s:3:"æšœ";s:4:"肭";s:3:"è‚­";s:4:"䏙";s:3:"ä™";s:4:"朗";s:3:"朗";s:4:"望";s:3:"望";s:4:"朡";s:3:"朡";s:4:"杞";s:3:"æž";s:4:"杓";s:3:"æ“";s:4:"ð¯£";s:4:"ð£ƒ";s:4:"㭉";s:3:"ã­‰";s:4:"柺";s:3:"柺";s:4:"枅";s:3:"æž…";s:4:"桒";s:3:"æ¡’";s:4:"梅";s:3:"梅";s:4:"𣑭";s:4:"𣑭";s:4:"梎";s:3:"梎";s:4:"栟";s:3:"æ Ÿ";s:4:"椔";s:3:"椔";s:4:"㮝";s:3:"ã®";s:4:"楂";s:3:"楂";s:4:"榣";s:3:"榣";s:4:"槪";s:3:"槪";s:4:"檨";s:3:"檨";s:4:"𣚣";s:4:"𣚣";s:4:"櫛";s:3:"æ«›";s:4:"㰘";s:3:"ã°˜";s:4:"次";s:3:"次";s:4:"𣢧";s:4:"𣢧";s:4:"歔";s:3:"æ­”";s:4:"㱎";s:3:"㱎";s:4:"歲";s:3:"æ­²";s:4:"殟";s:3:"殟";s:4:"殺";s:3:"殺";s:4:"殻";s:3:"æ®»";s:4:"𣪍";s:4:"ð£ª";s:4:"𡴋";s:4:"ð¡´‹";s:4:"𣫺";s:4:"𣫺";s:4:"汎";s:3:"汎";s:4:"𣲼";s:4:"𣲼";s:4:"沿";s:3:"沿";s:4:"泍";s:3:"æ³";s:4:"汧";s:3:"æ±§";s:4:"洖";s:3:"æ´–";s:4:"派";s:3:"æ´¾";s:4:"ð¯¤";s:3:"æµ·";s:4:"流";s:3:"æµ";s:4:"浩";s:3:"浩";s:4:"浸";s:3:"浸";s:4:"涅";s:3:"æ¶…";s:4:"𣴞";s:4:"𣴞";s:4:"洴";s:3:"æ´´";s:4:"港";s:3:"港";s:4:"湮";s:3:"æ¹®";s:4:"㴳";s:3:"ã´³";s:4:"滋";s:3:"滋";s:4:"滇";s:3:"滇";s:4:"ð¯¤";s:4:"𣻑";s:4:"淹";s:3:"æ·¹";s:4:"ð¯¤";s:3:"æ½®";s:4:"ð¯¤";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:4:"濆";s:3:"濆";s:4:"瀹";s:3:"瀹";s:4:"瀞";s:3:"瀞";s:4:"瀛";s:3:"瀛";s:4:"㶖";s:3:"ã¶–";s:4:"灊";s:3:"çŠ";s:4:"災";s:3:"ç½";s:4:"灷";s:3:"ç·";s:4:"炭";s:3:"ç‚­";s:4:"𠔥";s:4:"𠔥";s:4:"煅";s:3:"ç……";s:4:"ð¯¤";s:4:"𤉣";s:4:"熜";s:3:"熜";s:4:"𤎫";s:4:"𤎫";s:4:"爨";s:3:"爨";s:4:"爵";s:3:"爵";s:4:"牐";s:3:"ç‰";s:4:"𤘈";s:4:"𤘈";s:4:"犀";s:3:"犀";s:4:"犕";s:3:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:4:"獺";s:3:"çº";s:4:"王";s:3:"王";s:4:"㺬";s:3:"㺬";s:4:"玥";s:3:"玥";s:4:"㺸";s:3:"㺸";s:4:"㺸";s:3:"㺸";s:4:"瑇";s:3:"瑇";s:4:"瑜";s:3:"瑜";s:4:"瑱";s:3:"瑱";s:4:"璅";s:3:"ç’…";s:4:"瓊";s:3:"瓊";s:4:"㼛";s:3:"ã¼›";s:4:"甤";s:3:"甤";s:4:"𤰶";s:4:"𤰶";s:4:"甾";s:3:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"異";s:3:"ç•°";s:4:"𢆟";s:4:"𢆟";s:4:"瘐";s:3:"ç˜";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"ð¥„";s:4:"㿼";s:3:"㿼";s:4:"䀈";s:3:"䀈";s:4:"直";s:3:"ç›´";s:4:"ð¯¥";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:4:"眞";s:3:"眞";s:4:"真";s:3:"真";s:4:"真";s:3:"真";s:4:"睊";s:3:"çŠ";s:4:"䀹";s:3:"䀹";s:4:"瞋";s:3:"çž‹";s:4:"䁆";s:3:"ä†";s:4:"䂖";s:3:"ä‚–";s:4:"ð¯¥";s:4:"ð¥";s:4:"硎";s:3:"硎";s:4:"ð¯¥";s:3:"碌";s:4:"ð¯¥";s:3:"磌";s:4:"䃣";s:3:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"祖";s:3:"祖";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:4:"福";s:3:"ç¦";s:4:"秫";s:3:"ç§«";s:4:"䄯";s:3:"䄯";s:4:"穀";s:3:"ç©€";s:4:"穊";s:3:"穊";s:4:"穏";s:3:"ç©";s:4:"𥥼";s:4:"𥥼";s:4:"ð¯¥";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"竮";s:3:"ç«®";s:4:"䈂";s:3:"䈂";s:4:"𥮫";s:4:"𥮫";s:4:"篆";s:3:"篆";s:4:"築";s:3:"築";s:4:"䈧";s:3:"䈧";s:4:"𥲀";s:4:"𥲀";s:4:"糒";s:3:"ç³’";s:4:"䊠";s:3:"䊠";s:4:"糨";s:3:"糨";s:4:"糣";s:3:"ç³£";s:4:"紀";s:3:"ç´€";s:4:"𥾆";s:4:"𥾆";s:4:"絣";s:3:"çµ£";s:4:"䌁";s:3:"äŒ";s:4:"緇";s:3:"ç·‡";s:4:"縂";s:3:"縂";s:4:"繅";s:3:"ç¹…";s:4:"䌴";s:3:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:4:"䍙";s:3:"ä™";s:4:"𦋙";s:4:"𦋙";s:4:"罺";s:3:"罺";s:4:"𦌾";s:4:"𦌾";s:4:"羕";s:3:"羕";s:4:"翺";s:3:"翺";s:4:"者";s:3:"者";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:4:"聠";s:3:"è ";s:4:"𦖨";s:4:"𦖨";s:4:"聰";s:3:"è°";s:4:"𣍟";s:4:"ð£Ÿ";s:4:"ð¯¦";s:3:"ä•";s:4:"育";s:3:"育";s:4:"脃";s:3:"脃";s:4:"䐋";s:3:"ä‹";s:4:"脾";s:3:"脾";s:4:"媵";s:3:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:4:"舁";s:3:"èˆ";s:4:"舄";s:3:"舄";s:4:"ð¯¦";s:3:"辞";s:4:"䑫";s:3:"ä‘«";s:4:"ð¯¦";s:3:"芑";s:4:"ð¯¦";s:3:"芋";s:4:"芝";s:3:"èŠ";s:4:"劳";s:3:"劳";s:4:"花";s:3:"花";s:4:"芳";s:3:"芳";s:4:"芽";s:3:"芽";s:4:"苦";s:3:"苦";s:4:"𦬼";s:4:"𦬼";s:4:"若";s:3:"è‹¥";s:4:"茝";s:3:"èŒ";s:4:"荣";s:3:"è£";s:4:"莭";s:3:"莭";s:4:"茣";s:3:"茣";s:4:"ð¯¦";s:3:"莽";s:4:"菧";s:3:"è§";s:4:"著";s:3:"è‘—";s:4:"荓";s:3:"è“";s:4:"菊";s:3:"èŠ";s:4:"菌";s:3:"èŒ";s:4:"菜";s:3:"èœ";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:4:"䔫";s:3:"䔫";s:4:"蓱";s:3:"蓱";s:4:"蓳";s:3:"蓳";s:4:"蔖";s:3:"è”–";s:4:"𧏊";s:4:"ð§Š";s:4:"蕤";s:3:"蕤";s:4:"𦼬";s:4:"𦼬";s:4:"䕝";s:3:"ä•";s:4:"䕡";s:3:"ä•¡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:4:"䕫";s:3:"ä•«";s:4:"虐";s:3:"è™";s:4:"虜";s:3:"虜";s:4:"虧";s:3:"è™§";s:4:"虩";s:3:"虩";s:4:"蚩";s:3:"èš©";s:4:"蚈";s:3:"蚈";s:4:"蜎";s:3:"蜎";s:4:"蛢";s:3:"蛢";s:4:"蝹";s:3:"è¹";s:4:"蜨";s:3:"蜨";s:4:"蝫";s:3:"è«";s:4:"螆";s:3:"螆";s:4:"䗗";s:3:"ä——";s:4:"蟡";s:3:"蟡";s:4:"ð¯§";s:3:"è ";s:4:"䗹";s:3:"ä—¹";s:4:"衠";s:3:"è¡ ";s:4:"衣";s:3:"è¡£";s:4:"𧙧";s:4:"ð§™§";s:4:"裗";s:3:"裗";s:4:"裞";s:3:"裞";s:4:"䘵";s:3:"䘵";s:4:"裺";s:3:"裺";s:4:"㒻";s:3:"ã’»";s:4:"𧢮";s:4:"ð§¢®";s:4:"𧥦";s:4:"𧥦";s:4:"ð¯§";s:3:"äš¾";s:4:"䛇";s:3:"䛇";s:4:"ð¯§";s:3:"誠";s:4:"ð¯§";s:3:"è«­";s:4:"變";s:3:"變";s:4:"豕";s:3:"豕";s:4:"𧲨";s:4:"𧲨";s:4:"貫";s:3:"貫";s:4:"賁";s:3:"è³";s:4:"贛";s:3:"è´›";s:4:"起";s:3:"èµ·";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"ð  „";s:4:"跋";s:3:"è·‹";s:4:"趼";s:3:"è¶¼";s:4:"跰";s:3:"è·°";s:4:"ð¯§";s:4:"𠣞";s:4:"軔";s:3:"è»”";s:4:"輸";s:3:"輸";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:4:"邔";s:3:"é‚”";s:4:"郱";s:3:"郱";s:4:"鄑";s:3:"é„‘";s:4:"𨜮";s:4:"𨜮";s:4:"鄛";s:3:"é„›";s:4:"鈸";s:3:"鈸";s:4:"鋗";s:3:"é‹—";s:4:"鋘";s:3:"鋘";s:4:"鉼";s:3:"鉼";s:4:"鏹";s:3:"é¹";s:4:"鐕";s:3:"é•";s:4:"𨯺";s:4:"𨯺";s:4:"開";s:3:"é–‹";s:4:"䦕";s:3:"䦕";s:4:"閷";s:3:"é–·";s:4:"𨵷";s:4:"𨵷";s:4:"䧦";s:3:"䧦";s:4:"雃";s:3:"雃";s:4:"嶲";s:3:"å¶²";s:4:"霣";s:3:"霣";s:4:"𩅅";s:4:"ð©……";s:4:"𩈚";s:4:"𩈚";s:4:"䩮";s:3:"ä©®";s:4:"䩶";s:3:"ä©¶";s:4:"韠";s:3:"韠";s:4:"𩐊";s:4:"ð©Š";s:4:"䪲";s:3:"䪲";s:4:"𩒖";s:4:"ð©’–";s:4:"頋";s:3:"é ‹";s:4:"頋";s:3:"é ‹";s:4:"頩";s:3:"é ©";s:4:"ð¯¨";s:4:"ð©–¶";s:4:"飢";s:3:"飢";s:4:"䬳";s:3:"䬳";s:4:"餩";s:3:"餩";s:4:"馧";s:3:"馧";s:4:"駂";s:3:"é§‚";s:4:"駾";s:3:"é§¾";s:4:"䯎";s:3:"䯎";s:4:"𩬰";s:4:"𩬰";s:4:"鬒";s:3:"鬒";s:4:"鱀";s:3:"é±€";s:4:"鳽";s:3:"é³½";s:4:"ð¯¨";s:3:"䳎";s:4:"䳭";s:3:"ä³­";s:4:"ð¯¨";s:3:"éµ§";s:4:"ð¯¨";s:4:"𪃎";s:4:"䳸";s:3:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:4:"麻";s:3:"麻";s:4:"䵖";s:3:"äµ–";s:4:"黹";s:3:"黹";s:4:"黾";s:3:"黾";s:4:"鼅";s:3:"é¼…";s:4:"鼏";s:3:"é¼";s:4:"鼖";s:3:"é¼–";s:4:"鼻";s:3:"é¼»";s:4:"ð¯¨";s:4:"𪘀";}
    \ No newline at end of file
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/unidata/combiningClass.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/unidata/combiningClass.ser
    new file mode 100644
    index 0000000..6812d01
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/unidata/combiningClass.ser
    @@ -0,0 +1 @@
    +a:653:{s:2:"Ì€";i:230;s:2:"Ì";i:230;s:2:"Ì‚";i:230;s:2:"̃";i:230;s:2:"Ì„";i:230;s:2:"Ì…";i:230;s:2:"̆";i:230;s:2:"̇";i:230;s:2:"̈";i:230;s:2:"̉";i:230;s:2:"ÌŠ";i:230;s:2:"Ì‹";i:230;s:2:"ÌŒ";i:230;s:2:"Ì";i:230;s:2:"ÌŽ";i:230;s:2:"Ì";i:230;s:2:"Ì";i:230;s:2:"Ì‘";i:230;s:2:"Ì’";i:230;s:2:"Ì“";i:230;s:2:"Ì”";i:230;s:2:"Ì•";i:232;s:2:"Ì–";i:220;s:2:"Ì—";i:220;s:2:"̘";i:220;s:2:"Ì™";i:220;s:2:"Ìš";i:232;s:2:"Ì›";i:216;s:2:"Ìœ";i:220;s:2:"Ì";i:220;s:2:"Ìž";i:220;s:2:"ÌŸ";i:220;s:2:"Ì ";i:220;s:2:"Ì¡";i:202;s:2:"Ì¢";i:202;s:2:"Ì£";i:220;s:2:"̤";i:220;s:2:"Ì¥";i:220;s:2:"̦";i:220;s:2:"̧";i:202;s:2:"̨";i:202;s:2:"Ì©";i:220;s:2:"̪";i:220;s:2:"Ì«";i:220;s:2:"̬";i:220;s:2:"Ì­";i:220;s:2:"Ì®";i:220;s:2:"̯";i:220;s:2:"̰";i:220;s:2:"̱";i:220;s:2:"̲";i:220;s:2:"̳";i:220;s:2:"Ì´";i:1;s:2:"̵";i:1;s:2:"̶";i:1;s:2:"Ì·";i:1;s:2:"̸";i:1;s:2:"̹";i:220;s:2:"̺";i:220;s:2:"Ì»";i:220;s:2:"̼";i:220;s:2:"̽";i:230;s:2:"̾";i:230;s:2:"Ì¿";i:230;s:2:"Í€";i:230;s:2:"Í";i:230;s:2:"Í‚";i:230;s:2:"̓";i:230;s:2:"Í„";i:230;s:2:"Í…";i:240;s:2:"͆";i:230;s:2:"͇";i:220;s:2:"͈";i:220;s:2:"͉";i:220;s:2:"ÍŠ";i:230;s:2:"Í‹";i:230;s:2:"ÍŒ";i:230;s:2:"Í";i:220;s:2:"ÍŽ";i:220;s:2:"Í";i:230;s:2:"Í‘";i:230;s:2:"Í’";i:230;s:2:"Í“";i:220;s:2:"Í”";i:220;s:2:"Í•";i:220;s:2:"Í–";i:220;s:2:"Í—";i:230;s:2:"͘";i:232;s:2:"Í™";i:220;s:2:"Íš";i:220;s:2:"Í›";i:230;s:2:"Íœ";i:233;s:2:"Í";i:234;s:2:"Íž";i:234;s:2:"ÍŸ";i:233;s:2:"Í ";i:234;s:2:"Í¡";i:234;s:2:"Í¢";i:233;s:2:"Í£";i:230;s:2:"ͤ";i:230;s:2:"Í¥";i:230;s:2:"ͦ";i:230;s:2:"ͧ";i:230;s:2:"ͨ";i:230;s:2:"Í©";i:230;s:2:"ͪ";i:230;s:2:"Í«";i:230;s:2:"ͬ";i:230;s:2:"Í­";i:230;s:2:"Í®";i:230;s:2:"ͯ";i:230;s:2:"Òƒ";i:230;s:2:"Ò„";i:230;s:2:"Ò…";i:230;s:2:"Ò†";i:230;s:2:"Ò‡";i:230;s:2:"Ö‘";i:220;s:2:"Ö’";i:230;s:2:"Ö“";i:230;s:2:"Ö”";i:230;s:2:"Ö•";i:230;s:2:"Ö–";i:220;s:2:"Ö—";i:230;s:2:"Ö˜";i:230;s:2:"Ö™";i:230;s:2:"Öš";i:222;s:2:"Ö›";i:220;s:2:"Öœ";i:230;s:2:"Ö";i:230;s:2:"Öž";i:230;s:2:"ÖŸ";i:230;s:2:"Ö ";i:230;s:2:"Ö¡";i:230;s:2:"Ö¢";i:220;s:2:"Ö£";i:220;s:2:"Ö¤";i:220;s:2:"Ö¥";i:220;s:2:"Ö¦";i:220;s:2:"Ö§";i:220;s:2:"Ö¨";i:230;s:2:"Ö©";i:230;s:2:"Öª";i:220;s:2:"Ö«";i:230;s:2:"Ö¬";i:230;s:2:"Ö­";i:222;s:2:"Ö®";i:228;s:2:"Ö¯";i:230;s:2:"Ö°";i:10;s:2:"Ö±";i:11;s:2:"Ö²";i:12;s:2:"Ö³";i:13;s:2:"Ö´";i:14;s:2:"Öµ";i:15;s:2:"Ö¶";i:16;s:2:"Ö·";i:17;s:2:"Ö¸";i:18;s:2:"Ö¹";i:19;s:2:"Öº";i:19;s:2:"Ö»";i:20;s:2:"Ö¼";i:21;s:2:"Ö½";i:22;s:2:"Ö¿";i:23;s:2:"×";i:24;s:2:"ׂ";i:25;s:2:"ׄ";i:230;s:2:"×…";i:220;s:2:"ׇ";i:18;s:2:"Ø";i:230;s:2:"Ø‘";i:230;s:2:"Ø’";i:230;s:2:"Ø“";i:230;s:2:"Ø”";i:230;s:2:"Ø•";i:230;s:2:"Ø–";i:230;s:2:"Ø—";i:230;s:2:"ؘ";i:30;s:2:"Ø™";i:31;s:2:"Øš";i:32;s:2:"Ù‹";i:27;s:2:"ÙŒ";i:28;s:2:"Ù";i:29;s:2:"ÙŽ";i:30;s:2:"Ù";i:31;s:2:"Ù";i:32;s:2:"Ù‘";i:33;s:2:"Ù’";i:34;s:2:"Ù“";i:230;s:2:"Ù”";i:230;s:2:"Ù•";i:220;s:2:"Ù–";i:220;s:2:"Ù—";i:230;s:2:"Ù˜";i:230;s:2:"Ù™";i:230;s:2:"Ùš";i:230;s:2:"Ù›";i:230;s:2:"Ùœ";i:220;s:2:"Ù";i:230;s:2:"Ùž";i:230;s:2:"ÙŸ";i:220;s:2:"Ù°";i:35;s:2:"Û–";i:230;s:2:"Û—";i:230;s:2:"Û˜";i:230;s:2:"Û™";i:230;s:2:"Ûš";i:230;s:2:"Û›";i:230;s:2:"Ûœ";i:230;s:2:"ÛŸ";i:230;s:2:"Û ";i:230;s:2:"Û¡";i:230;s:2:"Û¢";i:230;s:2:"Û£";i:220;s:2:"Û¤";i:230;s:2:"Û§";i:230;s:2:"Û¨";i:230;s:2:"Ûª";i:220;s:2:"Û«";i:230;s:2:"Û¬";i:230;s:2:"Û­";i:220;s:2:"Ü‘";i:36;s:2:"ܰ";i:230;s:2:"ܱ";i:220;s:2:"ܲ";i:230;s:2:"ܳ";i:230;s:2:"Ü´";i:220;s:2:"ܵ";i:230;s:2:"ܶ";i:230;s:2:"Ü·";i:220;s:2:"ܸ";i:220;s:2:"ܹ";i:220;s:2:"ܺ";i:230;s:2:"Ü»";i:220;s:2:"ܼ";i:220;s:2:"ܽ";i:230;s:2:"ܾ";i:220;s:2:"Ü¿";i:230;s:2:"Ý€";i:230;s:2:"Ý";i:230;s:2:"Ý‚";i:220;s:2:"݃";i:230;s:2:"Ý„";i:220;s:2:"Ý…";i:230;s:2:"݆";i:220;s:2:"݇";i:230;s:2:"݈";i:220;s:2:"݉";i:230;s:2:"ÝŠ";i:230;s:2:"ß«";i:230;s:2:"߬";i:230;s:2:"ß­";i:230;s:2:"ß®";i:230;s:2:"߯";i:230;s:2:"ß°";i:230;s:2:"ß±";i:230;s:2:"ß²";i:220;s:2:"ß³";i:230;s:3:"à –";i:230;s:3:"à —";i:230;s:3:"à ˜";i:230;s:3:"à ™";i:230;s:3:"à ›";i:230;s:3:"à œ";i:230;s:3:"à ";i:230;s:3:"à ž";i:230;s:3:"à Ÿ";i:230;s:3:"à  ";i:230;s:3:"à ¡";i:230;s:3:"à ¢";i:230;s:3:"à £";i:230;s:3:"à ¥";i:230;s:3:"à ¦";i:230;s:3:"à §";i:230;s:3:"à ©";i:230;s:3:"à ª";i:230;s:3:"à «";i:230;s:3:"à ¬";i:230;s:3:"à ­";i:230;s:3:"à¡™";i:220;s:3:"࡚";i:220;s:3:"à¡›";i:220;s:3:"ࣤ";i:230;s:3:"ࣥ";i:230;s:3:"ࣦ";i:220;s:3:"ࣧ";i:230;s:3:"ࣨ";i:230;s:3:"ࣩ";i:220;s:3:"࣪";i:230;s:3:"࣫";i:230;s:3:"࣬";i:230;s:3:"࣭";i:220;s:3:"࣮";i:220;s:3:"࣯";i:220;s:3:"ࣰ";i:27;s:3:"ࣱ";i:28;s:3:"ࣲ";i:29;s:3:"ࣳ";i:230;s:3:"ࣴ";i:230;s:3:"ࣵ";i:230;s:3:"ࣶ";i:220;s:3:"ࣷ";i:230;s:3:"ࣸ";i:230;s:3:"ࣹ";i:220;s:3:"ࣺ";i:220;s:3:"ࣻ";i:230;s:3:"ࣼ";i:230;s:3:"ࣽ";i:230;s:3:"ࣾ";i:230;s:3:"़";i:7;s:3:"à¥";i:9;s:3:"॑";i:230;s:3:"॒";i:220;s:3:"॓";i:230;s:3:"॔";i:230;s:3:"়";i:7;s:3:"à§";i:9;s:3:"਼";i:7;s:3:"à©";i:9;s:3:"઼";i:7;s:3:"à«";i:9;s:3:"଼";i:7;s:3:"à­";i:9;s:3:"à¯";i:9;s:3:"à±";i:9;s:3:"ౕ";i:84;s:3:"à±–";i:91;s:3:"಼";i:7;s:3:"à³";i:9;s:3:"àµ";i:9;s:3:"à·Š";i:9;s:3:"ุ";i:103;s:3:"ู";i:103;s:3:"ฺ";i:9;s:3:"่";i:107;s:3:"้";i:107;s:3:"๊";i:107;s:3:"๋";i:107;s:3:"ຸ";i:118;s:3:"ູ";i:118;s:3:"່";i:122;s:3:"້";i:122;s:3:"໊";i:122;s:3:"໋";i:122;s:3:"༘";i:220;s:3:"༙";i:220;s:3:"༵";i:220;s:3:"༷";i:220;s:3:"༹";i:216;s:3:"ཱ";i:129;s:3:"ི";i:130;s:3:"ུ";i:132;s:3:"ེ";i:130;s:3:"ཻ";i:130;s:3:"ོ";i:130;s:3:"ཽ";i:130;s:3:"ྀ";i:130;s:3:"ྂ";i:230;s:3:"ྃ";i:230;s:3:"྄";i:9;s:3:"྆";i:230;s:3:"྇";i:230;s:3:"࿆";i:220;s:3:"့";i:7;s:3:"္";i:9;s:3:"်";i:9;s:3:"á‚";i:220;s:3:"á";i:230;s:3:"áž";i:230;s:3:"áŸ";i:230;s:3:"᜔";i:9;s:3:"᜴";i:9;s:3:"្";i:9;s:3:"áŸ";i:230;s:3:"ᢩ";i:228;s:3:"᤹";i:222;s:3:"᤺";i:230;s:3:"᤻";i:220;s:3:"ᨗ";i:230;s:3:"ᨘ";i:220;s:3:"á© ";i:9;s:3:"᩵";i:230;s:3:"á©¶";i:230;s:3:"á©·";i:230;s:3:"᩸";i:230;s:3:"᩹";i:230;s:3:"᩺";i:230;s:3:"á©»";i:230;s:3:"᩼";i:230;s:3:"á©¿";i:220;s:3:"᬴";i:7;s:3:"á­„";i:9;s:3:"á­«";i:230;s:3:"á­¬";i:220;s:3:"á­­";i:230;s:3:"á­®";i:230;s:3:"á­¯";i:230;s:3:"á­°";i:230;s:3:"á­±";i:230;s:3:"á­²";i:230;s:3:"á­³";i:230;s:3:"᮪";i:9;s:3:"᮫";i:9;s:3:"᯦";i:7;s:3:"᯲";i:9;s:3:"᯳";i:9;s:3:"á°·";i:7;s:3:"á³";i:230;s:3:"᳑";i:230;s:3:"á³’";i:230;s:3:"á³”";i:1;s:3:"᳕";i:220;s:3:"á³–";i:220;s:3:"á³—";i:220;s:3:"᳘";i:220;s:3:"á³™";i:220;s:3:"᳚";i:230;s:3:"á³›";i:230;s:3:"᳜";i:220;s:3:"á³";i:220;s:3:"᳞";i:220;s:3:"᳟";i:220;s:3:"á³ ";i:230;s:3:"á³¢";i:1;s:3:"á³£";i:1;s:3:"᳤";i:1;s:3:"á³¥";i:1;s:3:"᳦";i:1;s:3:"á³§";i:1;s:3:"᳨";i:1;s:3:"á³­";i:220;s:3:"á³´";i:230;s:3:"á·€";i:230;s:3:"á·";i:230;s:3:"á·‚";i:220;s:3:"á·ƒ";i:230;s:3:"á·„";i:230;s:3:"á·…";i:230;s:3:"á·†";i:230;s:3:"á·‡";i:230;s:3:"á·ˆ";i:230;s:3:"á·‰";i:230;s:3:"á·Š";i:220;s:3:"á·‹";i:230;s:3:"á·Œ";i:230;s:3:"á·";i:234;s:3:"á·Ž";i:214;s:3:"á·";i:220;s:3:"á·";i:202;s:3:"á·‘";i:230;s:3:"á·’";i:230;s:3:"á·“";i:230;s:3:"á·”";i:230;s:3:"á·•";i:230;s:3:"á·–";i:230;s:3:"á·—";i:230;s:3:"á·˜";i:230;s:3:"á·™";i:230;s:3:"á·š";i:230;s:3:"á·›";i:230;s:3:"á·œ";i:230;s:3:"á·";i:230;s:3:"á·ž";i:230;s:3:"á·Ÿ";i:230;s:3:"á· ";i:230;s:3:"á·¡";i:230;s:3:"á·¢";i:230;s:3:"á·£";i:230;s:3:"á·¤";i:230;s:3:"á·¥";i:230;s:3:"á·¦";i:230;s:3:"á·¼";i:233;s:3:"á·½";i:220;s:3:"á·¾";i:230;s:3:"á·¿";i:220;s:3:"âƒ";i:230;s:3:"⃑";i:230;s:3:"⃒";i:1;s:3:"⃓";i:1;s:3:"⃔";i:230;s:3:"⃕";i:230;s:3:"⃖";i:230;s:3:"⃗";i:230;s:3:"⃘";i:1;s:3:"⃙";i:1;s:3:"⃚";i:1;s:3:"⃛";i:230;s:3:"⃜";i:230;s:3:"⃡";i:230;s:3:"⃥";i:1;s:3:"⃦";i:1;s:3:"⃧";i:230;s:3:"⃨";i:220;s:3:"⃩";i:230;s:3:"⃪";i:1;s:3:"⃫";i:1;s:3:"⃬";i:220;s:3:"⃭";i:220;s:3:"⃮";i:220;s:3:"⃯";i:220;s:3:"⃰";i:230;s:3:"⳯";i:230;s:3:"â³°";i:230;s:3:"â³±";i:230;s:3:"⵿";i:9;s:3:"â· ";i:230;s:3:"â·¡";i:230;s:3:"â·¢";i:230;s:3:"â·£";i:230;s:3:"â·¤";i:230;s:3:"â·¥";i:230;s:3:"â·¦";i:230;s:3:"â·§";i:230;s:3:"â·¨";i:230;s:3:"â·©";i:230;s:3:"â·ª";i:230;s:3:"â·«";i:230;s:3:"â·¬";i:230;s:3:"â·­";i:230;s:3:"â·®";i:230;s:3:"â·¯";i:230;s:3:"â·°";i:230;s:3:"â·±";i:230;s:3:"â·²";i:230;s:3:"â·³";i:230;s:3:"â·´";i:230;s:3:"â·µ";i:230;s:3:"â·¶";i:230;s:3:"â··";i:230;s:3:"â·¸";i:230;s:3:"â·¹";i:230;s:3:"â·º";i:230;s:3:"â·»";i:230;s:3:"â·¼";i:230;s:3:"â·½";i:230;s:3:"â·¾";i:230;s:3:"â·¿";i:230;s:3:"〪";i:218;s:3:"〫";i:228;s:3:"〬";i:232;s:3:"〭";i:222;s:3:"〮";i:224;s:3:"〯";i:224;s:3:"ã‚™";i:8;s:3:"゚";i:8;s:3:"꙯";i:230;s:3:"ê™´";i:230;s:3:"ꙵ";i:230;s:3:"ê™¶";i:230;s:3:"ê™·";i:230;s:3:"ꙸ";i:230;s:3:"ꙹ";i:230;s:3:"ꙺ";i:230;s:3:"ê™»";i:230;s:3:"꙼";i:230;s:3:"꙽";i:230;s:3:"ꚟ";i:230;s:3:"ê›°";i:230;s:3:"ê›±";i:230;s:3:"ê †";i:9;s:3:"꣄";i:9;s:3:"꣠";i:230;s:3:"꣡";i:230;s:3:"꣢";i:230;s:3:"꣣";i:230;s:3:"꣤";i:230;s:3:"꣥";i:230;s:3:"꣦";i:230;s:3:"꣧";i:230;s:3:"꣨";i:230;s:3:"꣩";i:230;s:3:"꣪";i:230;s:3:"꣫";i:230;s:3:"꣬";i:230;s:3:"꣭";i:230;s:3:"꣮";i:230;s:3:"꣯";i:230;s:3:"꣰";i:230;s:3:"꣱";i:230;s:3:"꤫";i:220;s:3:"꤬";i:220;s:3:"꤭";i:220;s:3:"꥓";i:9;s:3:"꦳";i:7;s:3:"ê§€";i:9;s:3:"ꪰ";i:230;s:3:"ꪲ";i:230;s:3:"ꪳ";i:230;s:3:"ꪴ";i:220;s:3:"ꪷ";i:230;s:3:"ꪸ";i:230;s:3:"ꪾ";i:230;s:3:"꪿";i:230;s:3:"ê«";i:230;s:3:"ê«¶";i:9;s:3:"꯭";i:9;s:3:"ﬞ";i:26;s:3:"︠";i:230;s:3:"︡";i:230;s:3:"︢";i:230;s:3:"︣";i:230;s:3:"︤";i:230;s:3:"︥";i:230;s:3:"︦";i:230;s:4:"ð‡½";i:220;s:4:"ð¨";i:220;s:4:"ð¨";i:230;s:4:"ð¨¸";i:230;s:4:"ð¨¹";i:1;s:4:"ð¨º";i:220;s:4:"ð¨¿";i:9;s:4:"ð‘†";i:9;s:4:"ð‘‚¹";i:9;s:4:"𑂺";i:7;s:4:"ð‘„€";i:230;s:4:"ð‘„";i:230;s:4:"ð‘„‚";i:230;s:4:"ð‘„³";i:9;s:4:"ð‘„´";i:9;s:4:"𑇀";i:9;s:4:"𑚶";i:9;s:4:"𑚷";i:7;s:4:"ð…¥";i:216;s:4:"ð…¦";i:216;s:4:"ð…§";i:1;s:4:"ð…¨";i:1;s:4:"ð…©";i:1;s:4:"ð…­";i:226;s:4:"ð…®";i:216;s:4:"ð…¯";i:216;s:4:"ð…°";i:216;s:4:"ð…±";i:216;s:4:"ð…²";i:216;s:4:"ð…»";i:220;s:4:"ð…¼";i:220;s:4:"ð…½";i:220;s:4:"ð…¾";i:220;s:4:"ð…¿";i:220;s:4:"ð†€";i:220;s:4:"ð†";i:220;s:4:"ð†‚";i:220;s:4:"ð†…";i:230;s:4:"ð††";i:230;s:4:"ð†‡";i:230;s:4:"ð†ˆ";i:230;s:4:"ð†‰";i:230;s:4:"ð†Š";i:220;s:4:"ð†‹";i:220;s:4:"ð†ª";i:230;s:4:"ð†«";i:230;s:4:"ð†¬";i:230;s:4:"ð†­";i:230;s:4:"ð‰‚";i:230;s:4:"ð‰ƒ";i:230;s:4:"ð‰„";i:230;}
    \ No newline at end of file
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/unidata/compatibilityDecomposition.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/unidata/compatibilityDecomposition.ser
    new file mode 100644
    index 0000000..24482dd
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/unidata/compatibilityDecomposition.ser
    @@ -0,0 +1 @@
    +a:3671:{s:2:" ";s:1:" ";s:2:"¨";s:3:" ̈";s:2:"ª";s:1:"a";s:2:"¯";s:3:" Ì„";s:2:"²";s:1:"2";s:2:"³";s:1:"3";s:2:"´";s:3:" Ì";s:2:"µ";s:2:"μ";s:2:"¸";s:3:" ̧";s:2:"¹";s:1:"1";s:2:"º";s:1:"o";s:2:"¼";s:5:"1â„4";s:2:"½";s:5:"1â„2";s:2:"¾";s:5:"3â„4";s:2:"IJ";s:2:"IJ";s:2:"ij";s:2:"ij";s:2:"Ä¿";s:3:"L·";s:2:"Å€";s:3:"l·";s:2:"ʼn";s:3:"ʼn";s:2:"Å¿";s:1:"s";s:2:"Ç„";s:4:"DZÌŒ";s:2:"Ç…";s:4:"DzÌŒ";s:2:"dž";s:4:"dzÌŒ";s:2:"LJ";s:2:"LJ";s:2:"Lj";s:2:"Lj";s:2:"lj";s:2:"lj";s:2:"ÇŠ";s:2:"NJ";s:2:"Ç‹";s:2:"Nj";s:2:"ÇŒ";s:2:"nj";s:2:"DZ";s:2:"DZ";s:2:"Dz";s:2:"Dz";s:2:"dz";s:2:"dz";s:2:"ʰ";s:1:"h";s:2:"ʱ";s:2:"ɦ";s:2:"ʲ";s:1:"j";s:2:"ʳ";s:1:"r";s:2:"Ê´";s:2:"ɹ";s:2:"ʵ";s:2:"É»";s:2:"ʶ";s:2:"Ê";s:2:"Ê·";s:1:"w";s:2:"ʸ";s:1:"y";s:2:"˘";s:3:" ̆";s:2:"Ë™";s:3:" ̇";s:2:"Ëš";s:3:" ÌŠ";s:2:"Ë›";s:3:" ̨";s:2:"Ëœ";s:3:" ̃";s:2:"Ë";s:3:" Ì‹";s:2:"Ë ";s:2:"É£";s:2:"Ë¡";s:1:"l";s:2:"Ë¢";s:1:"s";s:2:"Ë£";s:1:"x";s:2:"ˤ";s:2:"Ê•";s:2:"ͺ";s:3:" Í…";s:2:"΄";s:3:" Ì";s:2:"Î…";s:5:" ̈Ì";s:2:"Ï";s:2:"β";s:2:"Ï‘";s:2:"θ";s:2:"Ï’";s:2:"Î¥";s:2:"Ï“";s:4:"Î¥Ì";s:2:"Ï”";s:4:"Ϋ";s:2:"Ï•";s:2:"φ";s:2:"Ï–";s:2:"Ï€";s:2:"ϰ";s:2:"κ";s:2:"ϱ";s:2:"Ï";s:2:"ϲ";s:2:"Ï‚";s:2:"Ï´";s:2:"Θ";s:2:"ϵ";s:2:"ε";s:2:"Ϲ";s:2:"Σ";s:2:"Ö‡";s:4:"Õ¥Ö‚";s:2:"Ùµ";s:4:"اٴ";s:2:"Ù¶";s:4:"وٴ";s:2:"Ù·";s:4:"Û‡Ù´";s:2:"Ù¸";s:4:"يٴ";s:3:"ำ";s:6:"à¹à¸²";s:3:"ຳ";s:6:"à»àº²";s:3:"ໜ";s:6:"ຫນ";s:3:"à»";s:6:"ຫມ";s:3:"༌";s:3:"་";s:3:"ཷ";s:9:"ྲཱྀ";s:3:"ཹ";s:9:"ླཱྀ";s:3:"ჼ";s:3:"ნ";s:3:"á´¬";s:1:"A";s:3:"á´­";s:2:"Æ";s:3:"á´®";s:1:"B";s:3:"á´°";s:1:"D";s:3:"á´±";s:1:"E";s:3:"á´²";s:2:"ÆŽ";s:3:"á´³";s:1:"G";s:3:"á´´";s:1:"H";s:3:"á´µ";s:1:"I";s:3:"á´¶";s:1:"J";s:3:"á´·";s:1:"K";s:3:"á´¸";s:1:"L";s:3:"á´¹";s:1:"M";s:3:"á´º";s:1:"N";s:3:"á´¼";s:1:"O";s:3:"á´½";s:2:"È¢";s:3:"á´¾";s:1:"P";s:3:"á´¿";s:1:"R";s:3:"áµ€";s:1:"T";s:3:"áµ";s:1:"U";s:3:"ᵂ";s:1:"W";s:3:"ᵃ";s:1:"a";s:3:"ᵄ";s:2:"É";s:3:"áµ…";s:2:"É‘";s:3:"ᵆ";s:3:"á´‚";s:3:"ᵇ";s:1:"b";s:3:"ᵈ";s:1:"d";s:3:"ᵉ";s:1:"e";s:3:"ᵊ";s:2:"É™";s:3:"ᵋ";s:2:"É›";s:3:"ᵌ";s:2:"Éœ";s:3:"áµ";s:1:"g";s:3:"áµ";s:1:"k";s:3:"áµ";s:1:"m";s:3:"ᵑ";s:2:"Å‹";s:3:"áµ’";s:1:"o";s:3:"ᵓ";s:2:"É”";s:3:"áµ”";s:3:"á´–";s:3:"ᵕ";s:3:"á´—";s:3:"áµ–";s:1:"p";s:3:"áµ—";s:1:"t";s:3:"ᵘ";s:1:"u";s:3:"áµ™";s:3:"á´";s:3:"ᵚ";s:2:"ɯ";s:3:"áµ›";s:1:"v";s:3:"ᵜ";s:3:"á´¥";s:3:"áµ";s:2:"β";s:3:"ᵞ";s:2:"γ";s:3:"ᵟ";s:2:"δ";s:3:"áµ ";s:2:"φ";s:3:"ᵡ";s:2:"χ";s:3:"áµ¢";s:1:"i";s:3:"áµ£";s:1:"r";s:3:"ᵤ";s:1:"u";s:3:"áµ¥";s:1:"v";s:3:"ᵦ";s:2:"β";s:3:"áµ§";s:2:"γ";s:3:"ᵨ";s:2:"Ï";s:3:"ᵩ";s:2:"φ";s:3:"ᵪ";s:2:"χ";s:3:"ᵸ";s:2:"н";s:3:"á¶›";s:2:"É’";s:3:"á¶œ";s:1:"c";s:3:"á¶";s:2:"É•";s:3:"á¶ž";s:2:"ð";s:3:"á¶Ÿ";s:2:"Éœ";s:3:"á¶ ";s:1:"f";s:3:"á¶¡";s:2:"ÉŸ";s:3:"á¶¢";s:2:"É¡";s:3:"á¶£";s:2:"É¥";s:3:"ᶤ";s:2:"ɨ";s:3:"á¶¥";s:2:"É©";s:3:"ᶦ";s:2:"ɪ";s:3:"á¶§";s:3:"áµ»";s:3:"ᶨ";s:2:"Ê";s:3:"á¶©";s:2:"É­";s:3:"ᶪ";s:3:"á¶…";s:3:"á¶«";s:2:"ÊŸ";s:3:"ᶬ";s:2:"ɱ";s:3:"á¶­";s:2:"ɰ";s:3:"á¶®";s:2:"ɲ";s:3:"ᶯ";s:2:"ɳ";s:3:"á¶°";s:2:"É´";s:3:"á¶±";s:2:"ɵ";s:3:"á¶²";s:2:"ɸ";s:3:"á¶³";s:2:"Ê‚";s:3:"á¶´";s:2:"ʃ";s:3:"á¶µ";s:2:"Æ«";s:3:"á¶¶";s:2:"ʉ";s:3:"á¶·";s:2:"ÊŠ";s:3:"ᶸ";s:3:"á´œ";s:3:"á¶¹";s:2:"Ê‹";s:3:"ᶺ";s:2:"ÊŒ";s:3:"á¶»";s:1:"z";s:3:"á¶¼";s:2:"Ê";s:3:"á¶½";s:2:"Ê‘";s:3:"á¶¾";s:2:"Ê’";s:3:"á¶¿";s:2:"θ";s:3:"ẚ";s:3:"aʾ";s:3:"ẛ";s:3:"ṡ";s:3:"á¾½";s:3:" Ì“";s:3:"᾿";s:3:" Ì“";s:3:"á¿€";s:3:" Í‚";s:3:"á¿";s:5:" ̈͂";s:3:"á¿";s:5:" ̓̀";s:3:"῎";s:5:" Ì“Ì";s:3:"á¿";s:5:" ̓͂";s:3:"á¿";s:5:" ̔̀";s:3:"῞";s:5:" Ì”Ì";s:3:"῟";s:5:" ̔͂";s:3:"á¿­";s:5:" ̈̀";s:3:"á¿®";s:5:" ̈Ì";s:3:"´";s:3:" Ì";s:3:"῾";s:3:" Ì”";s:3:" ";s:1:" ";s:3:"â€";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:"‑";s:3:"â€";s:3:"‗";s:3:" ̳";s:3:"․";s:1:".";s:3:"‥";s:2:"..";s:3:"…";s:3:"...";s:3:" ";s:1:" ";s:3:"″";s:6:"′′";s:3:"‴";s:9:"′′′";s:3:"‶";s:6:"‵‵";s:3:"‷";s:9:"‵‵‵";s:3:"‼";s:2:"!!";s:3:"‾";s:3:" Ì…";s:3:"â‡";s:2:"??";s:3:"âˆ";s:2:"?!";s:3:"â‰";s:2:"!?";s:3:"â—";s:12:"′′′′";s:3:"âŸ";s:1:" ";s:3:"â°";s:1:"0";s:3:"â±";s:1:"i";s:3:"â´";s:1:"4";s:3:"âµ";s:1:"5";s:3:"â¶";s:1:"6";s:3:"â·";s:1:"7";s:3:"â¸";s:1:"8";s:3:"â¹";s:1:"9";s:3:"âº";s:1:"+";s:3:"â»";s:3:"−";s:3:"â¼";s:1:"=";s:3:"â½";s:1:"(";s:3:"â¾";s:1:")";s:3:"â¿";s:1:"n";s:3:"â‚€";s:1:"0";s:3:"â‚";s:1:"1";s:3:"â‚‚";s:1:"2";s:3:"₃";s:1:"3";s:3:"â‚„";s:1:"4";s:3:"â‚…";s:1:"5";s:3:"₆";s:1:"6";s:3:"₇";s:1:"7";s:3:"₈";s:1:"8";s:3:"₉";s:1:"9";s:3:"₊";s:1:"+";s:3:"â‚‹";s:3:"−";s:3:"₌";s:1:"=";s:3:"â‚";s:1:"(";s:3:"₎";s:1:")";s:3:"â‚";s:1:"a";s:3:"â‚‘";s:1:"e";s:3:"â‚’";s:1:"o";s:3:"â‚“";s:1:"x";s:3:"â‚”";s:2:"É™";s:3:"â‚•";s:1:"h";s:3:"â‚–";s:1:"k";s:3:"â‚—";s:1:"l";s:3:"ₘ";s:1:"m";s:3:"â‚™";s:1:"n";s:3:"ₚ";s:1:"p";s:3:"â‚›";s:1:"s";s:3:"ₜ";s:1:"t";s:3:"₨";s:2:"Rs";s:3:"â„€";s:3:"a/c";s:3:"â„";s:3:"a/s";s:3:"â„‚";s:1:"C";s:3:"℃";s:3:"°C";s:3:"â„…";s:3:"c/o";s:3:"℆";s:3:"c/u";s:3:"ℇ";s:2:"Æ";s:3:"℉";s:3:"°F";s:3:"ℊ";s:1:"g";s:3:"â„‹";s:1:"H";s:3:"ℌ";s:1:"H";s:3:"â„";s:1:"H";s:3:"ℎ";s:1:"h";s:3:"â„";s:2:"ħ";s:3:"â„";s:1:"I";s:3:"â„‘";s:1:"I";s:3:"â„’";s:1:"L";s:3:"â„“";s:1:"l";s:3:"â„•";s:1:"N";s:3:"â„–";s:2:"No";s:3:"â„™";s:1:"P";s:3:"ℚ";s:1:"Q";s:3:"â„›";s:1:"R";s:3:"ℜ";s:1:"R";s:3:"â„";s:1:"R";s:3:"â„ ";s:2:"SM";s:3:"â„¡";s:3:"TEL";s:3:"â„¢";s:2:"TM";s:3:"ℤ";s:1:"Z";s:3:"ℨ";s:1:"Z";s:3:"ℬ";s:1:"B";s:3:"â„­";s:1:"C";s:3:"ℯ";s:1:"e";s:3:"â„°";s:1:"E";s:3:"ℱ";s:1:"F";s:3:"ℳ";s:1:"M";s:3:"â„´";s:1:"o";s:3:"ℵ";s:2:"×";s:3:"â„¶";s:2:"ב";s:3:"â„·";s:2:"×’";s:3:"ℸ";s:2:"ד";s:3:"ℹ";s:1:"i";s:3:"â„»";s:3:"FAX";s:3:"ℼ";s:2:"Ï€";s:3:"ℽ";s:2:"γ";s:3:"ℾ";s:2:"Γ";s:3:"â„¿";s:2:"Π";s:3:"â…€";s:3:"∑";s:3:"â……";s:1:"D";s:3:"â…†";s:1:"d";s:3:"â…‡";s:1:"e";s:3:"â…ˆ";s:1:"i";s:3:"â…‰";s:1:"j";s:3:"â…";s:5:"1â„7";s:3:"â…‘";s:5:"1â„9";s:3:"â…’";s:6:"1â„10";s:3:"â…“";s:5:"1â„3";s:3:"â…”";s:5:"2â„3";s:3:"â…•";s:5:"1â„5";s:3:"â…–";s:5:"2â„5";s:3:"â…—";s:5:"3â„5";s:3:"â…˜";s:5:"4â„5";s:3:"â…™";s:5:"1â„6";s:3:"â…š";s:5:"5â„6";s:3:"â…›";s:5:"1â„8";s:3:"â…œ";s:5:"3â„8";s:3:"â…";s:5:"5â„8";s:3:"â…ž";s:5:"7â„8";s:3:"â…Ÿ";s:4:"1â„";s:3:"â… ";s:1:"I";s:3:"â…¡";s:2:"II";s:3:"â…¢";s:3:"III";s:3:"â…£";s:2:"IV";s:3:"â…¤";s:1:"V";s:3:"â…¥";s:2:"VI";s:3:"â…¦";s:3:"VII";s:3:"â…§";s:4:"VIII";s:3:"â…¨";s:2:"IX";s:3:"â…©";s:1:"X";s:3:"â…ª";s:2:"XI";s:3:"â…«";s:3:"XII";s:3:"â…¬";s:1:"L";s:3:"â…­";s:1:"C";s:3:"â…®";s:1:"D";s:3:"â…¯";s:1:"M";s:3:"â…°";s:1:"i";s:3:"â…±";s:2:"ii";s:3:"â…²";s:3:"iii";s:3:"â…³";s:2:"iv";s:3:"â…´";s:1:"v";s:3:"â…µ";s:2:"vi";s:3:"â…¶";s:3:"vii";s:3:"â…·";s:4:"viii";s:3:"â…¸";s:2:"ix";s:3:"â…¹";s:1:"x";s:3:"â…º";s:2:"xi";s:3:"â…»";s:3:"xii";s:3:"â…¼";s:1:"l";s:3:"â…½";s:1:"c";s:3:"â…¾";s:1:"d";s:3:"â…¿";s:1:"m";s:3:"↉";s:5:"0â„3";s:3:"∬";s:6:"∫∫";s:3:"∭";s:9:"∫∫∫";s:3:"∯";s:6:"∮∮";s:3:"∰";s:9:"∮∮∮";s:3:"â‘ ";s:1:"1";s:3:"â‘¡";s:1:"2";s:3:"â‘¢";s:1:"3";s:3:"â‘£";s:1:"4";s:3:"⑤";s:1:"5";s:3:"â‘¥";s:1:"6";s:3:"⑦";s:1:"7";s:3:"â‘§";s:1:"8";s:3:"⑨";s:1:"9";s:3:"â‘©";s:2:"10";s:3:"⑪";s:2:"11";s:3:"â‘«";s:2:"12";s:3:"⑬";s:2:"13";s:3:"â‘­";s:2:"14";s:3:"â‘®";s:2:"15";s:3:"⑯";s:2:"16";s:3:"â‘°";s:2:"17";s:3:"⑱";s:2:"18";s:3:"⑲";s:2:"19";s:3:"⑳";s:2:"20";s:3:"â‘´";s:3:"(1)";s:3:"⑵";s:3:"(2)";s:3:"â‘¶";s:3:"(3)";s:3:"â‘·";s:3:"(4)";s:3:"⑸";s:3:"(5)";s:3:"⑹";s:3:"(6)";s:3:"⑺";s:3:"(7)";s:3:"â‘»";s:3:"(8)";s:3:"⑼";s:3:"(9)";s:3:"⑽";s:4:"(10)";s:3:"⑾";s:4:"(11)";s:3:"â‘¿";s:4:"(12)";s:3:"â’€";s:4:"(13)";s:3:"â’";s:4:"(14)";s:3:"â’‚";s:4:"(15)";s:3:"â’ƒ";s:4:"(16)";s:3:"â’„";s:4:"(17)";s:3:"â’…";s:4:"(18)";s:3:"â’†";s:4:"(19)";s:3:"â’‡";s:4:"(20)";s:3:"â’ˆ";s:2:"1.";s:3:"â’‰";s:2:"2.";s:3:"â’Š";s:2:"3.";s:3:"â’‹";s:2:"4.";s:3:"â’Œ";s:2:"5.";s:3:"â’";s:2:"6.";s:3:"â’Ž";s:2:"7.";s:3:"â’";s:2:"8.";s:3:"â’";s:2:"9.";s:3:"â’‘";s:3:"10.";s:3:"â’’";s:3:"11.";s:3:"â’“";s:3:"12.";s:3:"â’”";s:3:"13.";s:3:"â’•";s:3:"14.";s:3:"â’–";s:3:"15.";s:3:"â’—";s:3:"16.";s:3:"â’˜";s:3:"17.";s:3:"â’™";s:3:"18.";s:3:"â’š";s:3:"19.";s:3:"â’›";s:3:"20.";s:3:"â’œ";s:3:"(a)";s:3:"â’";s:3:"(b)";s:3:"â’ž";s:3:"(c)";s:3:"â’Ÿ";s:3:"(d)";s:3:"â’ ";s:3:"(e)";s:3:"â’¡";s:3:"(f)";s:3:"â’¢";s:3:"(g)";s:3:"â’£";s:3:"(h)";s:3:"â’¤";s:3:"(i)";s:3:"â’¥";s:3:"(j)";s:3:"â’¦";s:3:"(k)";s:3:"â’§";s:3:"(l)";s:3:"â’¨";s:3:"(m)";s:3:"â’©";s:3:"(n)";s:3:"â’ª";s:3:"(o)";s:3:"â’«";s:3:"(p)";s:3:"â’¬";s:3:"(q)";s:3:"â’­";s:3:"(r)";s:3:"â’®";s:3:"(s)";s:3:"â’¯";s:3:"(t)";s:3:"â’°";s:3:"(u)";s:3:"â’±";s:3:"(v)";s:3:"â’²";s:3:"(w)";s:3:"â’³";s:3:"(x)";s:3:"â’´";s:3:"(y)";s:3:"â’µ";s:3:"(z)";s:3:"â’¶";s:1:"A";s:3:"â’·";s:1:"B";s:3:"â’¸";s:1:"C";s:3:"â’¹";s:1:"D";s:3:"â’º";s:1:"E";s:3:"â’»";s:1:"F";s:3:"â’¼";s:1:"G";s:3:"â’½";s:1:"H";s:3:"â’¾";s:1:"I";s:3:"â’¿";s:1:"J";s:3:"â“€";s:1:"K";s:3:"â“";s:1:"L";s:3:"â“‚";s:1:"M";s:3:"Ⓝ";s:1:"N";s:3:"â“„";s:1:"O";s:3:"â“…";s:1:"P";s:3:"Ⓠ";s:1:"Q";s:3:"Ⓡ";s:1:"R";s:3:"Ⓢ";s:1:"S";s:3:"Ⓣ";s:1:"T";s:3:"Ⓤ";s:1:"U";s:3:"â“‹";s:1:"V";s:3:"Ⓦ";s:1:"W";s:3:"â“";s:1:"X";s:3:"Ⓨ";s:1:"Y";s:3:"â“";s:1:"Z";s:3:"â“";s:1:"a";s:3:"â“‘";s:1:"b";s:3:"â“’";s:1:"c";s:3:"â““";s:1:"d";s:3:"â“”";s:1:"e";s:3:"â“•";s:1:"f";s:3:"â“–";s:1:"g";s:3:"â“—";s:1:"h";s:3:"ⓘ";s:1:"i";s:3:"â“™";s:1:"j";s:3:"ⓚ";s:1:"k";s:3:"â“›";s:1:"l";s:3:"ⓜ";s:1:"m";s:3:"â“";s:1:"n";s:3:"ⓞ";s:1:"o";s:3:"ⓟ";s:1:"p";s:3:"â“ ";s:1:"q";s:3:"â“¡";s:1:"r";s:3:"â“¢";s:1:"s";s:3:"â“£";s:1:"t";s:3:"ⓤ";s:1:"u";s:3:"â“¥";s:1:"v";s:3:"ⓦ";s:1:"w";s:3:"â“§";s:1:"x";s:3:"ⓨ";s:1:"y";s:3:"â“©";s:1:"z";s:3:"⓪";s:1:"0";s:3:"⨌";s:12:"∫∫∫∫";s:3:"â©´";s:3:"::=";s:3:"⩵";s:2:"==";s:3:"â©¶";s:3:"===";s:3:"â±¼";s:1:"j";s:3:"â±½";s:1:"V";s:3:"ⵯ";s:3:"ⵡ";s:3:"⺟";s:3:"æ¯";s:3:"⻳";s:3:"龟";s:3:"â¼€";s:3:"一";s:3:"â¼";s:3:"丨";s:3:"⼂";s:3:"丶";s:3:"⼃";s:3:"丿";s:3:"⼄";s:3:"ä¹™";s:3:"â¼…";s:3:"亅";s:3:"⼆";s:3:"二";s:3:"⼇";s:3:"亠";s:3:"⼈";s:3:"人";s:3:"⼉";s:3:"å„¿";s:3:"⼊";s:3:"å…¥";s:3:"⼋";s:3:"å…«";s:3:"⼌";s:3:"冂";s:3:"â¼";s:3:"冖";s:3:"⼎";s:3:"冫";s:3:"â¼";s:3:"几";s:3:"â¼";s:3:"凵";s:3:"⼑";s:3:"刀";s:3:"â¼’";s:3:"力";s:3:"⼓";s:3:"勹";s:3:"â¼”";s:3:"匕";s:3:"⼕";s:3:"匚";s:3:"â¼–";s:3:"匸";s:3:"â¼—";s:3:"å";s:3:"⼘";s:3:"åœ";s:3:"â¼™";s:3:"å©";s:3:"⼚";s:3:"厂";s:3:"â¼›";s:3:"厶";s:3:"⼜";s:3:"åˆ";s:3:"â¼";s:3:"å£";s:3:"⼞";s:3:"å›—";s:3:"⼟";s:3:"土";s:3:"â¼ ";s:3:"士";s:3:"⼡";s:3:"夂";s:3:"â¼¢";s:3:"夊";s:3:"â¼£";s:3:"夕";s:3:"⼤";s:3:"大";s:3:"â¼¥";s:3:"女";s:3:"⼦";s:3:"å­";s:3:"â¼§";s:3:"宀";s:3:"⼨";s:3:"寸";s:3:"⼩";s:3:"å°";s:3:"⼪";s:3:"å°¢";s:3:"⼫";s:3:"å°¸";s:3:"⼬";s:3:"å±®";s:3:"â¼­";s:3:"å±±";s:3:"â¼®";s:3:"å·›";s:3:"⼯";s:3:"å·¥";s:3:"â¼°";s:3:"å·±";s:3:"â¼±";s:3:"å·¾";s:3:"â¼²";s:3:"å¹²";s:3:"â¼³";s:3:"幺";s:3:"â¼´";s:3:"广";s:3:"â¼µ";s:3:"å»´";s:3:"â¼¶";s:3:"廾";s:3:"â¼·";s:3:"弋";s:3:"⼸";s:3:"弓";s:3:"â¼¹";s:3:"å½";s:3:"⼺";s:3:"彡";s:3:"â¼»";s:3:"å½³";s:3:"â¼¼";s:3:"心";s:3:"â¼½";s:3:"戈";s:3:"â¼¾";s:3:"戶";s:3:"⼿";s:3:"手";s:3:"â½€";s:3:"支";s:3:"â½";s:3:"æ”´";s:3:"⽂";s:3:"æ–‡";s:3:"⽃";s:3:"æ–—";s:3:"⽄";s:3:"æ–¤";s:3:"â½…";s:3:"æ–¹";s:3:"⽆";s:3:"æ— ";s:3:"⽇";s:3:"æ—¥";s:3:"⽈";s:3:"æ›°";s:3:"⽉";s:3:"月";s:3:"⽊";s:3:"木";s:3:"⽋";s:3:"欠";s:3:"⽌";s:3:"æ­¢";s:3:"â½";s:3:"æ­¹";s:3:"⽎";s:3:"殳";s:3:"â½";s:3:"毋";s:3:"â½";s:3:"比";s:3:"⽑";s:3:"毛";s:3:"â½’";s:3:"æ°";s:3:"⽓";s:3:"æ°”";s:3:"â½”";s:3:"æ°´";s:3:"⽕";s:3:"ç«";s:3:"â½–";s:3:"爪";s:3:"â½—";s:3:"父";s:3:"⽘";s:3:"爻";s:3:"â½™";s:3:"爿";s:3:"⽚";s:3:"片";s:3:"â½›";s:3:"牙";s:3:"⽜";s:3:"牛";s:3:"â½";s:3:"犬";s:3:"⽞";s:3:"玄";s:3:"⽟";s:3:"玉";s:3:"â½ ";s:3:"瓜";s:3:"⽡";s:3:"瓦";s:3:"â½¢";s:3:"甘";s:3:"â½£";s:3:"生";s:3:"⽤";s:3:"用";s:3:"â½¥";s:3:"ç”°";s:3:"⽦";s:3:"ç–‹";s:3:"â½§";s:3:"ç–’";s:3:"⽨";s:3:"ç™¶";s:3:"⽩";s:3:"白";s:3:"⽪";s:3:"çš®";s:3:"⽫";s:3:"çš¿";s:3:"⽬";s:3:"ç›®";s:3:"â½­";s:3:"矛";s:3:"â½®";s:3:"矢";s:3:"⽯";s:3:"石";s:3:"â½°";s:3:"示";s:3:"â½±";s:3:"禸";s:3:"â½²";s:3:"禾";s:3:"â½³";s:3:"ç©´";s:3:"â½´";s:3:"ç«‹";s:3:"â½µ";s:3:"竹";s:3:"â½¶";s:3:"ç±³";s:3:"â½·";s:3:"糸";s:3:"⽸";s:3:"ç¼¶";s:3:"â½¹";s:3:"网";s:3:"⽺";s:3:"羊";s:3:"â½»";s:3:"ç¾½";s:3:"â½¼";s:3:"è€";s:3:"â½½";s:3:"而";s:3:"â½¾";s:3:"耒";s:3:"⽿";s:3:"耳";s:3:"â¾€";s:3:"è¿";s:3:"â¾";s:3:"肉";s:3:"⾂";s:3:"臣";s:3:"⾃";s:3:"自";s:3:"⾄";s:3:"至";s:3:"â¾…";s:3:"臼";s:3:"⾆";s:3:"舌";s:3:"⾇";s:3:"舛";s:3:"⾈";s:3:"舟";s:3:"⾉";s:3:"艮";s:3:"⾊";s:3:"色";s:3:"⾋";s:3:"艸";s:3:"⾌";s:3:"è™";s:3:"â¾";s:3:"虫";s:3:"⾎";s:3:"è¡€";s:3:"â¾";s:3:"行";s:3:"â¾";s:3:"è¡£";s:3:"⾑";s:3:"襾";s:3:"â¾’";s:3:"見";s:3:"⾓";s:3:"è§’";s:3:"â¾”";s:3:"言";s:3:"⾕";s:3:"è°·";s:3:"â¾–";s:3:"豆";s:3:"â¾—";s:3:"豕";s:3:"⾘";s:3:"豸";s:3:"â¾™";s:3:"è²";s:3:"⾚";s:3:"赤";s:3:"â¾›";s:3:"èµ°";s:3:"⾜";s:3:"è¶³";s:3:"â¾";s:3:"身";s:3:"⾞";s:3:"車";s:3:"⾟";s:3:"è¾›";s:3:"â¾ ";s:3:"è¾°";s:3:"⾡";s:3:"è¾µ";s:3:"â¾¢";s:3:"é‚‘";s:3:"â¾£";s:3:"é…‰";s:3:"⾤";s:3:"釆";s:3:"â¾¥";s:3:"里";s:3:"⾦";s:3:"金";s:3:"â¾§";s:3:"é•·";s:3:"⾨";s:3:"é–€";s:3:"⾩";s:3:"阜";s:3:"⾪";s:3:"éš¶";s:3:"⾫";s:3:"éš¹";s:3:"⾬";s:3:"雨";s:3:"â¾­";s:3:"é‘";s:3:"â¾®";s:3:"éž";s:3:"⾯";s:3:"é¢";s:3:"â¾°";s:3:"é©";s:3:"â¾±";s:3:"韋";s:3:"â¾²";s:3:"韭";s:3:"â¾³";s:3:"音";s:3:"â¾´";s:3:"é ";s:3:"â¾µ";s:3:"風";s:3:"â¾¶";s:3:"飛";s:3:"â¾·";s:3:"食";s:3:"⾸";s:3:"首";s:3:"â¾¹";s:3:"香";s:3:"⾺";s:3:"馬";s:3:"â¾»";s:3:"骨";s:3:"â¾¼";s:3:"高";s:3:"â¾½";s:3:"髟";s:3:"â¾¾";s:3:"鬥";s:3:"⾿";s:3:"鬯";s:3:"â¿€";s:3:"鬲";s:3:"â¿";s:3:"鬼";s:3:"â¿‚";s:3:"é­š";s:3:"⿃";s:3:"é³¥";s:3:"â¿„";s:3:"é¹µ";s:3:"â¿…";s:3:"鹿";s:3:"⿆";s:3:"麥";s:3:"⿇";s:3:"麻";s:3:"⿈";s:3:"黃";s:3:"⿉";s:3:"é»";s:3:"⿊";s:3:"黑";s:3:"â¿‹";s:3:"黹";s:3:"⿌";s:3:"黽";s:3:"â¿";s:3:"鼎";s:3:"⿎";s:3:"鼓";s:3:"â¿";s:3:"é¼ ";s:3:"â¿";s:3:"é¼»";s:3:"â¿‘";s:3:"齊";s:3:"â¿’";s:3:"é½’";s:3:"â¿“";s:3:"é¾";s:3:"â¿”";s:3:"龜";s:3:"â¿•";s:3:"é¾ ";s:3:" ";s:1:" ";s:3:"〶";s:3:"〒";s:3:"〸";s:3:"å";s:3:"〹";s:3:"å„";s:3:"〺";s:3:"å…";s:3:"ã‚›";s:4:" ã‚™";s:3:"゜";s:4:" ゚";s:3:"ゟ";s:6:"より";s:3:"ヿ";s:6:"コト";s:3:"ㄱ";s:3:"á„€";s:3:"ㄲ";s:3:"á„";s:3:"ㄳ";s:3:"ᆪ";s:3:"ã„´";s:3:"á„‚";s:3:"ㄵ";s:3:"ᆬ";s:3:"ã„¶";s:3:"ᆭ";s:3:"ã„·";s:3:"ᄃ";s:3:"ㄸ";s:3:"á„„";s:3:"ㄹ";s:3:"á„…";s:3:"ㄺ";s:3:"ᆰ";s:3:"ã„»";s:3:"ᆱ";s:3:"ㄼ";s:3:"ᆲ";s:3:"ㄽ";s:3:"ᆳ";s:3:"ㄾ";s:3:"ᆴ";s:3:"ã„¿";s:3:"ᆵ";s:3:"ã…€";s:3:"ᄚ";s:3:"ã…";s:3:"ᄆ";s:3:"ã…‚";s:3:"ᄇ";s:3:"ã…ƒ";s:3:"ᄈ";s:3:"ã…„";s:3:"á„¡";s:3:"ã……";s:3:"ᄉ";s:3:"ã…†";s:3:"ᄊ";s:3:"ã…‡";s:3:"á„‹";s:3:"ã…ˆ";s:3:"ᄌ";s:3:"ã…‰";s:3:"á„";s:3:"ã…Š";s:3:"ᄎ";s:3:"ã…‹";s:3:"á„";s:3:"ã…Œ";s:3:"á„";s:3:"ã…";s:3:"á„‘";s:3:"ã…Ž";s:3:"á„’";s:3:"ã…";s:3:"á…¡";s:3:"ã…";s:3:"á…¢";s:3:"ã…‘";s:3:"á…£";s:3:"ã…’";s:3:"á…¤";s:3:"ã…“";s:3:"á…¥";s:3:"ã…”";s:3:"á…¦";s:3:"ã…•";s:3:"á…§";s:3:"ã…–";s:3:"á…¨";s:3:"ã…—";s:3:"á…©";s:3:"ã…˜";s:3:"á…ª";s:3:"ã…™";s:3:"á…«";s:3:"ã…š";s:3:"á…¬";s:3:"ã…›";s:3:"á…­";s:3:"ã…œ";s:3:"á…®";s:3:"ã…";s:3:"á…¯";s:3:"ã…ž";s:3:"á…°";s:3:"ã…Ÿ";s:3:"á…±";s:3:"ã… ";s:3:"á…²";s:3:"ã…¡";s:3:"á…³";s:3:"ã…¢";s:3:"á…´";s:3:"ã…£";s:3:"á…µ";s:3:"ã…¤";s:3:"á… ";s:3:"ã…¥";s:3:"á„”";s:3:"ã…¦";s:3:"á„•";s:3:"ã…§";s:3:"ᇇ";s:3:"ã…¨";s:3:"ᇈ";s:3:"ã…©";s:3:"ᇌ";s:3:"ã…ª";s:3:"ᇎ";s:3:"ã…«";s:3:"ᇓ";s:3:"ã…¬";s:3:"ᇗ";s:3:"ã…­";s:3:"ᇙ";s:3:"ã…®";s:3:"ᄜ";s:3:"ã…¯";s:3:"á‡";s:3:"ã…°";s:3:"ᇟ";s:3:"ã…±";s:3:"á„";s:3:"ã…²";s:3:"ᄞ";s:3:"ã…³";s:3:"á„ ";s:3:"ã…´";s:3:"á„¢";s:3:"ã…µ";s:3:"á„£";s:3:"ã…¶";s:3:"á„§";s:3:"ã…·";s:3:"á„©";s:3:"ã…¸";s:3:"á„«";s:3:"ã…¹";s:3:"ᄬ";s:3:"ã…º";s:3:"á„­";s:3:"ã…»";s:3:"á„®";s:3:"ã…¼";s:3:"ᄯ";s:3:"ã…½";s:3:"ᄲ";s:3:"ã…¾";s:3:"á„¶";s:3:"ã…¿";s:3:"á…€";s:3:"ㆀ";s:3:"á…‡";s:3:"ã†";s:3:"á…Œ";s:3:"ㆂ";s:3:"ᇱ";s:3:"ㆃ";s:3:"ᇲ";s:3:"ㆄ";s:3:"á…—";s:3:"ㆅ";s:3:"á…˜";s:3:"ㆆ";s:3:"á…™";s:3:"ㆇ";s:3:"ᆄ";s:3:"ㆈ";s:3:"ᆅ";s:3:"ㆉ";s:3:"ᆈ";s:3:"ㆊ";s:3:"ᆑ";s:3:"ㆋ";s:3:"ᆒ";s:3:"ㆌ";s:3:"ᆔ";s:3:"ã†";s:3:"ᆞ";s:3:"ㆎ";s:3:"ᆡ";s:3:"㆒";s:3:"一";s:3:"㆓";s:3:"二";s:3:"㆔";s:3:"三";s:3:"㆕";s:3:"å››";s:3:"㆖";s:3:"上";s:3:"㆗";s:3:"中";s:3:"㆘";s:3:"下";s:3:"㆙";s:3:"甲";s:3:"㆚";s:3:"ä¹™";s:3:"㆛";s:3:"丙";s:3:"㆜";s:3:"ä¸";s:3:"ã†";s:3:"天";s:3:"㆞";s:3:"地";s:3:"㆟";s:3:"人";s:3:"㈀";s:5:"(á„€)";s:3:"ãˆ";s:5:"(á„‚)";s:3:"㈂";s:5:"(ᄃ)";s:3:"㈃";s:5:"(á„…)";s:3:"㈄";s:5:"(ᄆ)";s:3:"㈅";s:5:"(ᄇ)";s:3:"㈆";s:5:"(ᄉ)";s:3:"㈇";s:5:"(á„‹)";s:3:"㈈";s:5:"(ᄌ)";s:3:"㈉";s:5:"(ᄎ)";s:3:"㈊";s:5:"(á„)";s:3:"㈋";s:5:"(á„)";s:3:"㈌";s:5:"(á„‘)";s:3:"ãˆ";s:5:"(á„’)";s:3:"㈎";s:8:"(가)";s:3:"ãˆ";s:8:"(á„‚á…¡)";s:3:"ãˆ";s:8:"(다)";s:3:"㈑";s:8:"(á„…á…¡)";s:3:"㈒";s:8:"(마)";s:3:"㈓";s:8:"(바)";s:3:"㈔";s:8:"(사)";s:3:"㈕";s:8:"(á„‹á…¡)";s:3:"㈖";s:8:"(자)";s:3:"㈗";s:8:"(차)";s:3:"㈘";s:8:"(á„á…¡)";s:3:"㈙";s:8:"(á„á…¡)";s:3:"㈚";s:8:"(á„‘á…¡)";s:3:"㈛";s:8:"(á„’á…¡)";s:3:"㈜";s:8:"(주)";s:3:"ãˆ";s:17:"(오전)";s:3:"㈞";s:14:"(á„‹á…©á„’á…®)";s:3:"㈠";s:5:"(一)";s:3:"㈡";s:5:"(二)";s:3:"㈢";s:5:"(三)";s:3:"㈣";s:5:"(å››)";s:3:"㈤";s:5:"(五)";s:3:"㈥";s:5:"(å…­)";s:3:"㈦";s:5:"(七)";s:3:"㈧";s:5:"(å…«)";s:3:"㈨";s:5:"(ä¹)";s:3:"㈩";s:5:"(å)";s:3:"㈪";s:5:"(月)";s:3:"㈫";s:5:"(ç«)";s:3:"㈬";s:5:"(æ°´)";s:3:"㈭";s:5:"(木)";s:3:"㈮";s:5:"(金)";s:3:"㈯";s:5:"(土)";s:3:"㈰";s:5:"(æ—¥)";s:3:"㈱";s:5:"(æ ª)";s:3:"㈲";s:5:"(有)";s:3:"㈳";s:5:"(社)";s:3:"㈴";s:5:"(å)";s:3:"㈵";s:5:"(特)";s:3:"㈶";s:5:"(財)";s:3:"㈷";s:5:"(ç¥)";s:3:"㈸";s:5:"(労)";s:3:"㈹";s:5:"(代)";s:3:"㈺";s:5:"(呼)";s:3:"㈻";s:5:"(å­¦)";s:3:"㈼";s:5:"(監)";s:3:"㈽";s:5:"(ä¼)";s:3:"㈾";s:5:"(資)";s:3:"㈿";s:5:"(å”)";s:3:"㉀";s:5:"(祭)";s:3:"ã‰";s:5:"(休)";s:3:"㉂";s:5:"(自)";s:3:"㉃";s:5:"(至)";s:3:"㉄";s:3:"å•";s:3:"㉅";s:3:"å¹¼";s:3:"㉆";s:3:"æ–‡";s:3:"㉇";s:3:"ç®";s:3:"ã‰";s:3:"PTE";s:3:"㉑";s:2:"21";s:3:"㉒";s:2:"22";s:3:"㉓";s:2:"23";s:3:"㉔";s:2:"24";s:3:"㉕";s:2:"25";s:3:"㉖";s:2:"26";s:3:"㉗";s:2:"27";s:3:"㉘";s:2:"28";s:3:"㉙";s:2:"29";s:3:"㉚";s:2:"30";s:3:"㉛";s:2:"31";s:3:"㉜";s:2:"32";s:3:"ã‰";s:2:"33";s:3:"㉞";s:2:"34";s:3:"㉟";s:2:"35";s:3:"㉠";s:3:"á„€";s:3:"㉡";s:3:"á„‚";s:3:"㉢";s:3:"ᄃ";s:3:"㉣";s:3:"á„…";s:3:"㉤";s:3:"ᄆ";s:3:"㉥";s:3:"ᄇ";s:3:"㉦";s:3:"ᄉ";s:3:"㉧";s:3:"á„‹";s:3:"㉨";s:3:"ᄌ";s:3:"㉩";s:3:"ᄎ";s:3:"㉪";s:3:"á„";s:3:"㉫";s:3:"á„";s:3:"㉬";s:3:"á„‘";s:3:"㉭";s:3:"á„’";s:3:"㉮";s:6:"가";s:3:"㉯";s:6:"á„‚á…¡";s:3:"㉰";s:6:"다";s:3:"㉱";s:6:"á„…á…¡";s:3:"㉲";s:6:"마";s:3:"㉳";s:6:"바";s:3:"㉴";s:6:"사";s:3:"㉵";s:6:"á„‹á…¡";s:3:"㉶";s:6:"자";s:3:"㉷";s:6:"차";s:3:"㉸";s:6:"á„á…¡";s:3:"㉹";s:6:"á„á…¡";s:3:"㉺";s:6:"á„‘á…¡";s:3:"㉻";s:6:"á„’á…¡";s:3:"㉼";s:15:"참고";s:3:"㉽";s:12:"주의";s:3:"㉾";s:6:"á„‹á…®";s:3:"㊀";s:3:"一";s:3:"ãŠ";s:3:"二";s:3:"㊂";s:3:"三";s:3:"㊃";s:3:"å››";s:3:"㊄";s:3:"五";s:3:"㊅";s:3:"å…­";s:3:"㊆";s:3:"七";s:3:"㊇";s:3:"å…«";s:3:"㊈";s:3:"ä¹";s:3:"㊉";s:3:"å";s:3:"㊊";s:3:"月";s:3:"㊋";s:3:"ç«";s:3:"㊌";s:3:"æ°´";s:3:"ãŠ";s:3:"木";s:3:"㊎";s:3:"金";s:3:"ãŠ";s:3:"土";s:3:"ãŠ";s:3:"æ—¥";s:3:"㊑";s:3:"æ ª";s:3:"㊒";s:3:"有";s:3:"㊓";s:3:"社";s:3:"㊔";s:3:"å";s:3:"㊕";s:3:"特";s:3:"㊖";s:3:"財";s:3:"㊗";s:3:"ç¥";s:3:"㊘";s:3:"労";s:3:"㊙";s:3:"秘";s:3:"㊚";s:3:"ç”·";s:3:"㊛";s:3:"女";s:3:"㊜";s:3:"é©";s:3:"ãŠ";s:3:"優";s:3:"㊞";s:3:"å°";s:3:"㊟";s:3:"注";s:3:"㊠";s:3:"é …";s:3:"㊡";s:3:"休";s:3:"㊢";s:3:"写";s:3:"㊣";s:3:"æ­£";s:3:"㊤";s:3:"上";s:3:"㊥";s:3:"中";s:3:"㊦";s:3:"下";s:3:"㊧";s:3:"å·¦";s:3:"㊨";s:3:"å³";s:3:"㊩";s:3:"医";s:3:"㊪";s:3:"å®—";s:3:"㊫";s:3:"å­¦";s:3:"㊬";s:3:"監";s:3:"㊭";s:3:"ä¼";s:3:"㊮";s:3:"資";s:3:"㊯";s:3:"å”";s:3:"㊰";s:3:"夜";s:3:"㊱";s:2:"36";s:3:"㊲";s:2:"37";s:3:"㊳";s:2:"38";s:3:"㊴";s:2:"39";s:3:"㊵";s:2:"40";s:3:"㊶";s:2:"41";s:3:"㊷";s:2:"42";s:3:"㊸";s:2:"43";s:3:"㊹";s:2:"44";s:3:"㊺";s:2:"45";s:3:"㊻";s:2:"46";s:3:"㊼";s:2:"47";s:3:"㊽";s:2:"48";s:3:"㊾";s:2:"49";s:3:"㊿";s:2:"50";s:3:"ã‹€";s:4:"1月";s:3:"ã‹";s:4:"2月";s:3:"ã‹‚";s:4:"3月";s:3:"㋃";s:4:"4月";s:3:"ã‹„";s:4:"5月";s:3:"ã‹…";s:4:"6月";s:3:"㋆";s:4:"7月";s:3:"㋇";s:4:"8月";s:3:"㋈";s:4:"9月";s:3:"㋉";s:5:"10月";s:3:"㋊";s:5:"11月";s:3:"ã‹‹";s:5:"12月";s:3:"㋌";s:2:"Hg";s:3:"ã‹";s:3:"erg";s:3:"㋎";s:2:"eV";s:3:"ã‹";s:3:"LTD";s:3:"ã‹";s:3:"ã‚¢";s:3:"ã‹‘";s:3:"イ";s:3:"ã‹’";s:3:"ウ";s:3:"ã‹“";s:3:"エ";s:3:"ã‹”";s:3:"オ";s:3:"ã‹•";s:3:"ã‚«";s:3:"ã‹–";s:3:"ã‚­";s:3:"ã‹—";s:3:"ク";s:3:"㋘";s:3:"ケ";s:3:"ã‹™";s:3:"コ";s:3:"㋚";s:3:"サ";s:3:"ã‹›";s:3:"ã‚·";s:3:"㋜";s:3:"ス";s:3:"ã‹";s:3:"ã‚»";s:3:"㋞";s:3:"ソ";s:3:"㋟";s:3:"ã‚¿";s:3:"ã‹ ";s:3:"ãƒ";s:3:"ã‹¡";s:3:"ツ";s:3:"ã‹¢";s:3:"テ";s:3:"ã‹£";s:3:"ト";s:3:"㋤";s:3:"ナ";s:3:"ã‹¥";s:3:"ニ";s:3:"㋦";s:3:"ヌ";s:3:"ã‹§";s:3:"ãƒ";s:3:"㋨";s:3:"ノ";s:3:"ã‹©";s:3:"ãƒ";s:3:"㋪";s:3:"ヒ";s:3:"ã‹«";s:3:"フ";s:3:"㋬";s:3:"ヘ";s:3:"ã‹­";s:3:"ホ";s:3:"ã‹®";s:3:"マ";s:3:"㋯";s:3:"ミ";s:3:"ã‹°";s:3:"ム";s:3:"㋱";s:3:"メ";s:3:"㋲";s:3:"モ";s:3:"㋳";s:3:"ヤ";s:3:"ã‹´";s:3:"ユ";s:3:"㋵";s:3:"ヨ";s:3:"ã‹¶";s:3:"ラ";s:3:"ã‹·";s:3:"リ";s:3:"㋸";s:3:"ル";s:3:"㋹";s:3:"レ";s:3:"㋺";s:3:"ロ";s:3:"ã‹»";s:3:"ワ";s:3:"㋼";s:3:"ヰ";s:3:"㋽";s:3:"ヱ";s:3:"㋾";s:3:"ヲ";s:3:"㌀";s:15:"ã‚¢ãƒã‚šãƒ¼ãƒˆ";s:3:"ãŒ";s:12:"アルファ";s:3:"㌂";s:15:"アンペア";s:3:"㌃";s:9:"アール";s:3:"㌄";s:15:"イニング";s:3:"㌅";s:9:"インãƒ";s:3:"㌆";s:9:"ウォン";s:3:"㌇";s:18:"エスクード";s:3:"㌈";s:12:"エーカー";s:3:"㌉";s:9:"オンス";s:3:"㌊";s:9:"オーム";s:3:"㌋";s:9:"カイリ";s:3:"㌌";s:12:"カラット";s:3:"ãŒ";s:12:"カロリー";s:3:"㌎";s:12:"ガロン";s:3:"ãŒ";s:12:"ガンマ";s:3:"ãŒ";s:12:"ギガ";s:3:"㌑";s:12:"ギニー";s:3:"㌒";s:12:"キュリー";s:3:"㌓";s:18:"ギルダー";s:3:"㌔";s:6:"キロ";s:3:"㌕";s:18:"キログラム";s:3:"㌖";s:18:"キロメートル";s:3:"㌗";s:15:"キロワット";s:3:"㌘";s:12:"グラム";s:3:"㌙";s:18:"グラムトン";s:3:"㌚";s:18:"クルゼイロ";s:3:"㌛";s:12:"クローãƒ";s:3:"㌜";s:9:"ケース";s:3:"ãŒ";s:9:"コルナ";s:3:"㌞";s:12:"コーポ";s:3:"㌟";s:12:"サイクル";s:3:"㌠";s:15:"サンãƒãƒ¼ãƒ ";s:3:"㌡";s:15:"シリング";s:3:"㌢";s:9:"センãƒ";s:3:"㌣";s:9:"セント";s:3:"㌤";s:12:"ダース";s:3:"㌥";s:9:"デシ";s:3:"㌦";s:9:"ドル";s:3:"㌧";s:6:"トン";s:3:"㌨";s:6:"ナノ";s:3:"㌩";s:9:"ノット";s:3:"㌪";s:9:"ãƒã‚¤ãƒ„";s:3:"㌫";s:18:"ãƒã‚šãƒ¼ã‚»ãƒ³ãƒˆ";s:3:"㌬";s:12:"ãƒã‚šãƒ¼ãƒ„";s:3:"㌭";s:15:"ãƒã‚™ãƒ¼ãƒ¬ãƒ«";s:3:"㌮";s:18:"ピアストル";s:3:"㌯";s:12:"ピクル";s:3:"㌰";s:9:"ピコ";s:3:"㌱";s:9:"ビル";s:3:"㌲";s:18:"ファラッド";s:3:"㌳";s:12:"フィート";s:3:"㌴";s:18:"ブッシェル";s:3:"㌵";s:9:"フラン";s:3:"㌶";s:15:"ヘクタール";s:3:"㌷";s:9:"ペソ";s:3:"㌸";s:12:"ペニヒ";s:3:"㌹";s:9:"ヘルツ";s:3:"㌺";s:12:"ペンス";s:3:"㌻";s:15:"ページ";s:3:"㌼";s:12:"ベータ";s:3:"㌽";s:15:"ポイント";s:3:"㌾";s:12:"ボルト";s:3:"㌿";s:6:"ホン";s:3:"ã€";s:15:"ポンド";s:3:"ã";s:9:"ホール";s:3:"ã‚";s:9:"ホーン";s:3:"ãƒ";s:12:"マイクロ";s:3:"ã„";s:9:"マイル";s:3:"ã…";s:9:"マッãƒ";s:3:"ã†";s:9:"マルク";s:3:"ã‡";s:15:"マンション";s:3:"ãˆ";s:12:"ミクロン";s:3:"ã‰";s:6:"ミリ";s:3:"ãŠ";s:18:"ミリãƒã‚™ãƒ¼ãƒ«";s:3:"ã‹";s:9:"メガ";s:3:"ãŒ";s:15:"メガトン";s:3:"ã";s:12:"メートル";s:3:"ãŽ";s:12:"ヤード";s:3:"ã";s:9:"ヤール";s:3:"ã";s:9:"ユアン";s:3:"ã‘";s:12:"リットル";s:3:"ã’";s:6:"リラ";s:3:"ã“";s:12:"ルピー";s:3:"ã”";s:15:"ルーブル";s:3:"ã•";s:6:"レム";s:3:"ã–";s:18:"レントゲン";s:3:"ã—";s:9:"ワット";s:3:"ã˜";s:4:"0点";s:3:"ã™";s:4:"1点";s:3:"ãš";s:4:"2点";s:3:"ã›";s:4:"3点";s:3:"ãœ";s:4:"4点";s:3:"ã";s:4:"5点";s:3:"ãž";s:4:"6点";s:3:"ãŸ";s:4:"7点";s:3:"ã ";s:4:"8点";s:3:"ã¡";s:4:"9点";s:3:"ã¢";s:5:"10点";s:3:"ã£";s:5:"11点";s:3:"ã¤";s:5:"12点";s:3:"ã¥";s:5:"13点";s:3:"ã¦";s:5:"14点";s:3:"ã§";s:5:"15点";s:3:"ã¨";s:5:"16点";s:3:"ã©";s:5:"17点";s:3:"ãª";s:5:"18点";s:3:"ã«";s:5:"19点";s:3:"ã¬";s:5:"20点";s:3:"ã­";s:5:"21点";s:3:"ã®";s:5:"22点";s:3:"ã¯";s:5:"23点";s:3:"ã°";s:5:"24点";s:3:"ã±";s:3:"hPa";s:3:"ã²";s:2:"da";s:3:"ã³";s:2:"AU";s:3:"ã´";s:3:"bar";s:3:"ãµ";s:2:"oV";s:3:"ã¶";s:2:"pc";s:3:"ã·";s:2:"dm";s:3:"ã¸";s:3:"dm2";s:3:"ã¹";s:3:"dm3";s:3:"ãº";s:2:"IU";s:3:"ã»";s:6:"å¹³æˆ";s:3:"ã¼";s:6:"昭和";s:3:"ã½";s:6:"大正";s:3:"ã¾";s:6:"明治";s:3:"ã¿";s:12:"æ ªå¼ä¼šç¤¾";s:3:"㎀";s:2:"pA";s:3:"ãŽ";s:2:"nA";s:3:"㎂";s:3:"μA";s:3:"㎃";s:2:"mA";s:3:"㎄";s:2:"kA";s:3:"㎅";s:2:"KB";s:3:"㎆";s:2:"MB";s:3:"㎇";s:2:"GB";s:3:"㎈";s:3:"cal";s:3:"㎉";s:4:"kcal";s:3:"㎊";s:2:"pF";s:3:"㎋";s:2:"nF";s:3:"㎌";s:3:"μF";s:3:"ãŽ";s:3:"μg";s:3:"㎎";s:2:"mg";s:3:"ãŽ";s:2:"kg";s:3:"ãŽ";s:2:"Hz";s:3:"㎑";s:3:"kHz";s:3:"㎒";s:3:"MHz";s:3:"㎓";s:3:"GHz";s:3:"㎔";s:3:"THz";s:3:"㎕";s:3:"μl";s:3:"㎖";s:2:"ml";s:3:"㎗";s:2:"dl";s:3:"㎘";s:2:"kl";s:3:"㎙";s:2:"fm";s:3:"㎚";s:2:"nm";s:3:"㎛";s:3:"μm";s:3:"㎜";s:2:"mm";s:3:"ãŽ";s:2:"cm";s:3:"㎞";s:2:"km";s:3:"㎟";s:3:"mm2";s:3:"㎠";s:3:"cm2";s:3:"㎡";s:2:"m2";s:3:"㎢";s:3:"km2";s:3:"㎣";s:3:"mm3";s:3:"㎤";s:3:"cm3";s:3:"㎥";s:2:"m3";s:3:"㎦";s:3:"km3";s:3:"㎧";s:5:"m∕s";s:3:"㎨";s:6:"m∕s2";s:3:"㎩";s:2:"Pa";s:3:"㎪";s:3:"kPa";s:3:"㎫";s:3:"MPa";s:3:"㎬";s:3:"GPa";s:3:"㎭";s:3:"rad";s:3:"㎮";s:7:"rad∕s";s:3:"㎯";s:8:"rad∕s2";s:3:"㎰";s:2:"ps";s:3:"㎱";s:2:"ns";s:3:"㎲";s:3:"μs";s:3:"㎳";s:2:"ms";s:3:"㎴";s:2:"pV";s:3:"㎵";s:2:"nV";s:3:"㎶";s:3:"μV";s:3:"㎷";s:2:"mV";s:3:"㎸";s:2:"kV";s:3:"㎹";s:2:"MV";s:3:"㎺";s:2:"pW";s:3:"㎻";s:2:"nW";s:3:"㎼";s:3:"μW";s:3:"㎽";s:2:"mW";s:3:"㎾";s:2:"kW";s:3:"㎿";s:2:"MW";s:3:"ã€";s:3:"kΩ";s:3:"ã";s:3:"MΩ";s:3:"ã‚";s:4:"a.m.";s:3:"ãƒ";s:2:"Bq";s:3:"ã„";s:2:"cc";s:3:"ã…";s:2:"cd";s:3:"ã†";s:6:"C∕kg";s:3:"ã‡";s:3:"Co.";s:3:"ãˆ";s:2:"dB";s:3:"ã‰";s:2:"Gy";s:3:"ãŠ";s:2:"ha";s:3:"ã‹";s:2:"HP";s:3:"ãŒ";s:2:"in";s:3:"ã";s:2:"KK";s:3:"ãŽ";s:2:"KM";s:3:"ã";s:2:"kt";s:3:"ã";s:2:"lm";s:3:"ã‘";s:2:"ln";s:3:"ã’";s:3:"log";s:3:"ã“";s:2:"lx";s:3:"ã”";s:2:"mb";s:3:"ã•";s:3:"mil";s:3:"ã–";s:3:"mol";s:3:"ã—";s:2:"PH";s:3:"ã˜";s:4:"p.m.";s:3:"ã™";s:3:"PPM";s:3:"ãš";s:2:"PR";s:3:"ã›";s:2:"sr";s:3:"ãœ";s:2:"Sv";s:3:"ã";s:2:"Wb";s:3:"ãž";s:5:"V∕m";s:3:"ãŸ";s:5:"A∕m";s:3:"ã ";s:4:"1æ—¥";s:3:"ã¡";s:4:"2æ—¥";s:3:"ã¢";s:4:"3æ—¥";s:3:"ã£";s:4:"4æ—¥";s:3:"ã¤";s:4:"5æ—¥";s:3:"ã¥";s:4:"6æ—¥";s:3:"ã¦";s:4:"7æ—¥";s:3:"ã§";s:4:"8æ—¥";s:3:"ã¨";s:4:"9æ—¥";s:3:"ã©";s:5:"10æ—¥";s:3:"ãª";s:5:"11æ—¥";s:3:"ã«";s:5:"12æ—¥";s:3:"ã¬";s:5:"13æ—¥";s:3:"ã­";s:5:"14æ—¥";s:3:"ã®";s:5:"15æ—¥";s:3:"ã¯";s:5:"16æ—¥";s:3:"ã°";s:5:"17æ—¥";s:3:"ã±";s:5:"18æ—¥";s:3:"ã²";s:5:"19æ—¥";s:3:"ã³";s:5:"20æ—¥";s:3:"ã´";s:5:"21æ—¥";s:3:"ãµ";s:5:"22æ—¥";s:3:"ã¶";s:5:"23æ—¥";s:3:"ã·";s:5:"24æ—¥";s:3:"ã¸";s:5:"25æ—¥";s:3:"ã¹";s:5:"26æ—¥";s:3:"ãº";s:5:"27æ—¥";s:3:"ã»";s:5:"28æ—¥";s:3:"ã¼";s:5:"29æ—¥";s:3:"ã½";s:5:"30æ—¥";s:3:"ã¾";s:5:"31æ—¥";s:3:"ã¿";s:3:"gal";s:3:"ê°";s:3:"ê¯";s:3:"ꟸ";s:2:"Ħ";s:3:"ꟹ";s:2:"Å“";s:3:"ff";s:2:"ff";s:3:"ï¬";s:2:"fi";s:3:"fl";s:2:"fl";s:3:"ffi";s:3:"ffi";s:3:"ffl";s:3:"ffl";s:3:"ſt";s:2:"st";s:3:"st";s:2:"st";s:3:"ﬓ";s:4:"Õ´Õ¶";s:3:"ﬔ";s:4:"Õ´Õ¥";s:3:"ﬕ";s:4:"Õ´Õ«";s:3:"ﬖ";s:4:"Õ¾Õ¶";s:3:"ﬗ";s:4:"Õ´Õ­";s:3:"ﬠ";s:2:"×¢";s:3:"ﬡ";s:2:"×";s:3:"ﬢ";s:2:"ד";s:3:"ﬣ";s:2:"×”";s:3:"ﬤ";s:2:"×›";s:3:"ﬥ";s:2:"ל";s:3:"ﬦ";s:2:"×";s:3:"ﬧ";s:2:"ר";s:3:"ﬨ";s:2:"ת";s:3:"﬩";s:1:"+";s:3:"ï­";s:4:"×ל";s:3:"ï­";s:2:"Ù±";s:3:"ï­‘";s:2:"Ù±";s:3:"ï­’";s:2:"Ù»";s:3:"ï­“";s:2:"Ù»";s:3:"ï­”";s:2:"Ù»";s:3:"ï­•";s:2:"Ù»";s:3:"ï­–";s:2:"Ù¾";s:3:"ï­—";s:2:"Ù¾";s:3:"ï­˜";s:2:"Ù¾";s:3:"ï­™";s:2:"Ù¾";s:3:"ï­š";s:2:"Ú€";s:3:"ï­›";s:2:"Ú€";s:3:"ï­œ";s:2:"Ú€";s:3:"ï­";s:2:"Ú€";s:3:"ï­ž";s:2:"Ùº";s:3:"ï­Ÿ";s:2:"Ùº";s:3:"ï­ ";s:2:"Ùº";s:3:"ï­¡";s:2:"Ùº";s:3:"ï­¢";s:2:"Ù¿";s:3:"ï­£";s:2:"Ù¿";s:3:"ï­¤";s:2:"Ù¿";s:3:"ï­¥";s:2:"Ù¿";s:3:"ï­¦";s:2:"Ù¹";s:3:"ï­§";s:2:"Ù¹";s:3:"ï­¨";s:2:"Ù¹";s:3:"ï­©";s:2:"Ù¹";s:3:"ï­ª";s:2:"Ú¤";s:3:"ï­«";s:2:"Ú¤";s:3:"ï­¬";s:2:"Ú¤";s:3:"ï­­";s:2:"Ú¤";s:3:"ï­®";s:2:"Ú¦";s:3:"ï­¯";s:2:"Ú¦";s:3:"ï­°";s:2:"Ú¦";s:3:"ï­±";s:2:"Ú¦";s:3:"ï­²";s:2:"Ú„";s:3:"ï­³";s:2:"Ú„";s:3:"ï­´";s:2:"Ú„";s:3:"ï­µ";s:2:"Ú„";s:3:"ï­¶";s:2:"Úƒ";s:3:"ï­·";s:2:"Úƒ";s:3:"ï­¸";s:2:"Úƒ";s:3:"ï­¹";s:2:"Úƒ";s:3:"ï­º";s:2:"Ú†";s:3:"ï­»";s:2:"Ú†";s:3:"ï­¼";s:2:"Ú†";s:3:"ï­½";s:2:"Ú†";s:3:"ï­¾";s:2:"Ú‡";s:3:"ï­¿";s:2:"Ú‡";s:3:"ﮀ";s:2:"Ú‡";s:3:"ï®";s:2:"Ú‡";s:3:"ﮂ";s:2:"Ú";s:3:"ﮃ";s:2:"Ú";s:3:"ﮄ";s:2:"ÚŒ";s:3:"ï®…";s:2:"ÚŒ";s:3:"ﮆ";s:2:"ÚŽ";s:3:"ﮇ";s:2:"ÚŽ";s:3:"ﮈ";s:2:"Úˆ";s:3:"ﮉ";s:2:"Úˆ";s:3:"ﮊ";s:2:"Ú˜";s:3:"ﮋ";s:2:"Ú˜";s:3:"ﮌ";s:2:"Ú‘";s:3:"ï®";s:2:"Ú‘";s:3:"ﮎ";s:2:"Ú©";s:3:"ï®";s:2:"Ú©";s:3:"ï®";s:2:"Ú©";s:3:"ﮑ";s:2:"Ú©";s:3:"ï®’";s:2:"Ú¯";s:3:"ﮓ";s:2:"Ú¯";s:3:"ï®”";s:2:"Ú¯";s:3:"ﮕ";s:2:"Ú¯";s:3:"ï®–";s:2:"Ú³";s:3:"ï®—";s:2:"Ú³";s:3:"ﮘ";s:2:"Ú³";s:3:"ï®™";s:2:"Ú³";s:3:"ﮚ";s:2:"Ú±";s:3:"ï®›";s:2:"Ú±";s:3:"ﮜ";s:2:"Ú±";s:3:"ï®";s:2:"Ú±";s:3:"ﮞ";s:2:"Úº";s:3:"ﮟ";s:2:"Úº";s:3:"ï® ";s:2:"Ú»";s:3:"ﮡ";s:2:"Ú»";s:3:"ﮢ";s:2:"Ú»";s:3:"ﮣ";s:2:"Ú»";s:3:"ﮤ";s:4:"Û•Ù”";s:3:"ﮥ";s:4:"Û•Ù”";s:3:"ﮦ";s:2:"Û";s:3:"ï®§";s:2:"Û";s:3:"ﮨ";s:2:"Û";s:3:"ﮩ";s:2:"Û";s:3:"ﮪ";s:2:"Ú¾";s:3:"ﮫ";s:2:"Ú¾";s:3:"ﮬ";s:2:"Ú¾";s:3:"ï®­";s:2:"Ú¾";s:3:"ï®®";s:2:"Û’";s:3:"ﮯ";s:2:"Û’";s:3:"ï®°";s:4:"Û’Ù”";s:3:"ï®±";s:4:"Û’Ù”";s:3:"ﯓ";s:2:"Ú­";s:3:"ﯔ";s:2:"Ú­";s:3:"ﯕ";s:2:"Ú­";s:3:"ﯖ";s:2:"Ú­";s:3:"ﯗ";s:2:"Û‡";s:3:"ﯘ";s:2:"Û‡";s:3:"ﯙ";s:2:"Û†";s:3:"ﯚ";s:2:"Û†";s:3:"ﯛ";s:2:"Ûˆ";s:3:"ﯜ";s:2:"Ûˆ";s:3:"ï¯";s:4:"Û‡Ù´";s:3:"ﯞ";s:2:"Û‹";s:3:"ﯟ";s:2:"Û‹";s:3:"ﯠ";s:2:"Û…";s:3:"ﯡ";s:2:"Û…";s:3:"ﯢ";s:2:"Û‰";s:3:"ﯣ";s:2:"Û‰";s:3:"ﯤ";s:2:"Û";s:3:"ﯥ";s:2:"Û";s:3:"ﯦ";s:2:"Û";s:3:"ﯧ";s:2:"Û";s:3:"ﯨ";s:2:"Ù‰";s:3:"ﯩ";s:2:"Ù‰";s:3:"ﯪ";s:6:"ئا";s:3:"ﯫ";s:6:"ئا";s:3:"ﯬ";s:6:"ÙŠÙ”Û•";s:3:"ﯭ";s:6:"ÙŠÙ”Û•";s:3:"ﯮ";s:6:"ÙŠÙ”Ùˆ";s:3:"ﯯ";s:6:"ÙŠÙ”Ùˆ";s:3:"ﯰ";s:6:"ÙŠÙ”Û‡";s:3:"ﯱ";s:6:"ÙŠÙ”Û‡";s:3:"ﯲ";s:6:"ÙŠÙ”Û†";s:3:"ﯳ";s:6:"ÙŠÙ”Û†";s:3:"ﯴ";s:6:"ÙŠÙ”Ûˆ";s:3:"ﯵ";s:6:"ÙŠÙ”Ûˆ";s:3:"ﯶ";s:6:"ÙŠÙ”Û";s:3:"ﯷ";s:6:"ÙŠÙ”Û";s:3:"ﯸ";s:6:"ÙŠÙ”Û";s:3:"ﯹ";s:6:"ÙŠÙ”Ù‰";s:3:"ﯺ";s:6:"ÙŠÙ”Ù‰";s:3:"ﯻ";s:6:"ÙŠÙ”Ù‰";s:3:"ﯼ";s:2:"ÛŒ";s:3:"ﯽ";s:2:"ÛŒ";s:3:"ﯾ";s:2:"ÛŒ";s:3:"ﯿ";s:2:"ÛŒ";s:3:"ï°€";s:6:"ئج";s:3:"ï°";s:6:"ئح";s:3:"ï°‚";s:6:"ÙŠÙ”Ù…";s:3:"ï°ƒ";s:6:"ÙŠÙ”Ù‰";s:3:"ï°„";s:6:"ÙŠÙ”ÙŠ";s:3:"ï°…";s:4:"بج";s:3:"ï°†";s:4:"بح";s:3:"ï°‡";s:4:"بخ";s:3:"ï°ˆ";s:4:"بم";s:3:"ï°‰";s:4:"بى";s:3:"ï°Š";s:4:"بي";s:3:"ï°‹";s:4:"تج";s:3:"ï°Œ";s:4:"تح";s:3:"ï°";s:4:"تخ";s:3:"ï°Ž";s:4:"تم";s:3:"ï°";s:4:"تى";s:3:"ï°";s:4:"تي";s:3:"ï°‘";s:4:"ثج";s:3:"ï°’";s:4:"ثم";s:3:"ï°“";s:4:"ثى";s:3:"ï°”";s:4:"ثي";s:3:"ï°•";s:4:"جح";s:3:"ï°–";s:4:"جم";s:3:"ï°—";s:4:"حج";s:3:"ï°˜";s:4:"حم";s:3:"ï°™";s:4:"خج";s:3:"ï°š";s:4:"خح";s:3:"ï°›";s:4:"خم";s:3:"ï°œ";s:4:"سج";s:3:"ï°";s:4:"سح";s:3:"ï°ž";s:4:"سخ";s:3:"ï°Ÿ";s:4:"سم";s:3:"ï° ";s:4:"صح";s:3:"ï°¡";s:4:"صم";s:3:"ï°¢";s:4:"ضج";s:3:"ï°£";s:4:"ضح";s:3:"ï°¤";s:4:"ضخ";s:3:"ï°¥";s:4:"ضم";s:3:"ï°¦";s:4:"طح";s:3:"ï°§";s:4:"طم";s:3:"ï°¨";s:4:"ظم";s:3:"ï°©";s:4:"عج";s:3:"ï°ª";s:4:"عم";s:3:"ï°«";s:4:"غج";s:3:"ï°¬";s:4:"غم";s:3:"ï°­";s:4:"ÙØ¬";s:3:"ï°®";s:4:"ÙØ­";s:3:"ï°¯";s:4:"ÙØ®";s:3:"ï°°";s:4:"ÙÙ…";s:3:"ï°±";s:4:"ÙÙ‰";s:3:"ï°²";s:4:"ÙÙŠ";s:3:"ï°³";s:4:"قح";s:3:"ï°´";s:4:"قم";s:3:"ï°µ";s:4:"قى";s:3:"ï°¶";s:4:"قي";s:3:"ï°·";s:4:"كا";s:3:"ï°¸";s:4:"كج";s:3:"ï°¹";s:4:"كح";s:3:"ï°º";s:4:"كخ";s:3:"ï°»";s:4:"كل";s:3:"ï°¼";s:4:"كم";s:3:"ï°½";s:4:"كى";s:3:"ï°¾";s:4:"كي";s:3:"ï°¿";s:4:"لج";s:3:"ï±€";s:4:"لح";s:3:"ï±";s:4:"لخ";s:3:"ﱂ";s:4:"لم";s:3:"ﱃ";s:4:"لى";s:3:"ﱄ";s:4:"لي";s:3:"ï±…";s:4:"مج";s:3:"ﱆ";s:4:"مح";s:3:"ﱇ";s:4:"مخ";s:3:"ﱈ";s:4:"مم";s:3:"ﱉ";s:4:"مى";s:3:"ﱊ";s:4:"مي";s:3:"ﱋ";s:4:"نج";s:3:"ﱌ";s:4:"نح";s:3:"ï±";s:4:"نخ";s:3:"ﱎ";s:4:"نم";s:3:"ï±";s:4:"نى";s:3:"ï±";s:4:"ني";s:3:"ﱑ";s:4:"هج";s:3:"ï±’";s:4:"هم";s:3:"ﱓ";s:4:"هى";s:3:"ï±”";s:4:"هي";s:3:"ﱕ";s:4:"يج";s:3:"ï±–";s:4:"يح";s:3:"ï±—";s:4:"يخ";s:3:"ﱘ";s:4:"يم";s:3:"ï±™";s:4:"يى";s:3:"ﱚ";s:4:"يي";s:3:"ï±›";s:4:"ذٰ";s:3:"ﱜ";s:4:"رٰ";s:3:"ï±";s:4:"ىٰ";s:3:"ﱞ";s:5:" ٌّ";s:3:"ﱟ";s:5:" ÙÙ‘";s:3:"ï± ";s:5:" ÙŽÙ‘";s:3:"ﱡ";s:5:" ÙÙ‘";s:3:"ï±¢";s:5:" ÙÙ‘";s:3:"ï±£";s:5:" ّٰ";s:3:"ﱤ";s:6:"ئر";s:3:"ï±¥";s:6:"ئز";s:3:"ﱦ";s:6:"ÙŠÙ”Ù…";s:3:"ï±§";s:6:"ÙŠÙ”Ù†";s:3:"ﱨ";s:6:"ÙŠÙ”Ù‰";s:3:"ﱩ";s:6:"ÙŠÙ”ÙŠ";s:3:"ﱪ";s:4:"بر";s:3:"ﱫ";s:4:"بز";s:3:"ﱬ";s:4:"بم";s:3:"ï±­";s:4:"بن";s:3:"ï±®";s:4:"بى";s:3:"ﱯ";s:4:"بي";s:3:"ï±°";s:4:"تر";s:3:"ï±±";s:4:"تز";s:3:"ï±²";s:4:"تم";s:3:"ï±³";s:4:"تن";s:3:"ï±´";s:4:"تى";s:3:"ï±µ";s:4:"تي";s:3:"ï±¶";s:4:"ثر";s:3:"ï±·";s:4:"ثز";s:3:"ﱸ";s:4:"ثم";s:3:"ï±¹";s:4:"ثن";s:3:"ﱺ";s:4:"ثى";s:3:"ï±»";s:4:"ثي";s:3:"ï±¼";s:4:"ÙÙ‰";s:3:"ï±½";s:4:"ÙÙŠ";s:3:"ï±¾";s:4:"قى";s:3:"ﱿ";s:4:"قي";s:3:"ï²€";s:4:"كا";s:3:"ï²";s:4:"كل";s:3:"ﲂ";s:4:"كم";s:3:"ﲃ";s:4:"كى";s:3:"ﲄ";s:4:"كي";s:3:"ï²…";s:4:"لم";s:3:"ﲆ";s:4:"لى";s:3:"ﲇ";s:4:"لي";s:3:"ﲈ";s:4:"ما";s:3:"ﲉ";s:4:"مم";s:3:"ﲊ";s:4:"نر";s:3:"ﲋ";s:4:"نز";s:3:"ﲌ";s:4:"نم";s:3:"ï²";s:4:"نن";s:3:"ﲎ";s:4:"نى";s:3:"ï²";s:4:"ني";s:3:"ï²";s:4:"ىٰ";s:3:"ﲑ";s:4:"ير";s:3:"ï²’";s:4:"يز";s:3:"ﲓ";s:4:"يم";s:3:"ï²”";s:4:"ين";s:3:"ﲕ";s:4:"يى";s:3:"ï²–";s:4:"يي";s:3:"ï²—";s:6:"ئج";s:3:"ﲘ";s:6:"ئح";s:3:"ï²™";s:6:"ئخ";s:3:"ﲚ";s:6:"ÙŠÙ”Ù…";s:3:"ï²›";s:6:"ÙŠÙ”Ù‡";s:3:"ﲜ";s:4:"بج";s:3:"ï²";s:4:"بح";s:3:"ﲞ";s:4:"بخ";s:3:"ﲟ";s:4:"بم";s:3:"ï² ";s:4:"به";s:3:"ﲡ";s:4:"تج";s:3:"ï²¢";s:4:"تح";s:3:"ï²£";s:4:"تخ";s:3:"ﲤ";s:4:"تم";s:3:"ï²¥";s:4:"ته";s:3:"ﲦ";s:4:"ثم";s:3:"ï²§";s:4:"جح";s:3:"ﲨ";s:4:"جم";s:3:"ﲩ";s:4:"حج";s:3:"ﲪ";s:4:"حم";s:3:"ﲫ";s:4:"خج";s:3:"ﲬ";s:4:"خم";s:3:"ï²­";s:4:"سج";s:3:"ï²®";s:4:"سح";s:3:"ﲯ";s:4:"سخ";s:3:"ï²°";s:4:"سم";s:3:"ï²±";s:4:"صح";s:3:"ï²²";s:4:"صخ";s:3:"ï²³";s:4:"صم";s:3:"ï²´";s:4:"ضج";s:3:"ï²µ";s:4:"ضح";s:3:"ï²¶";s:4:"ضخ";s:3:"ï²·";s:4:"ضم";s:3:"ﲸ";s:4:"طح";s:3:"ï²¹";s:4:"ظم";s:3:"ﲺ";s:4:"عج";s:3:"ï²»";s:4:"عم";s:3:"ï²¼";s:4:"غج";s:3:"ï²½";s:4:"غم";s:3:"ï²¾";s:4:"ÙØ¬";s:3:"ﲿ";s:4:"ÙØ­";s:3:"ï³€";s:4:"ÙØ®";s:3:"ï³";s:4:"ÙÙ…";s:3:"ﳂ";s:4:"قح";s:3:"ﳃ";s:4:"قم";s:3:"ﳄ";s:4:"كج";s:3:"ï³…";s:4:"كح";s:3:"ﳆ";s:4:"كخ";s:3:"ﳇ";s:4:"كل";s:3:"ﳈ";s:4:"كم";s:3:"ﳉ";s:4:"لج";s:3:"ﳊ";s:4:"لح";s:3:"ﳋ";s:4:"لخ";s:3:"ﳌ";s:4:"لم";s:3:"ï³";s:4:"له";s:3:"ﳎ";s:4:"مج";s:3:"ï³";s:4:"مح";s:3:"ï³";s:4:"مخ";s:3:"ﳑ";s:4:"مم";s:3:"ï³’";s:4:"نج";s:3:"ﳓ";s:4:"نح";s:3:"ï³”";s:4:"نخ";s:3:"ﳕ";s:4:"نم";s:3:"ï³–";s:4:"نه";s:3:"ï³—";s:4:"هج";s:3:"ﳘ";s:4:"هم";s:3:"ï³™";s:4:"هٰ";s:3:"ﳚ";s:4:"يج";s:3:"ï³›";s:4:"يح";s:3:"ﳜ";s:4:"يخ";s:3:"ï³";s:4:"يم";s:3:"ﳞ";s:4:"يه";s:3:"ﳟ";s:6:"ÙŠÙ”Ù…";s:3:"ï³ ";s:6:"ÙŠÙ”Ù‡";s:3:"ﳡ";s:4:"بم";s:3:"ï³¢";s:4:"به";s:3:"ï³£";s:4:"تم";s:3:"ﳤ";s:4:"ته";s:3:"ï³¥";s:4:"ثم";s:3:"ﳦ";s:4:"ثه";s:3:"ï³§";s:4:"سم";s:3:"ﳨ";s:4:"سه";s:3:"ﳩ";s:4:"شم";s:3:"ﳪ";s:4:"شه";s:3:"ﳫ";s:4:"كل";s:3:"ﳬ";s:4:"كم";s:3:"ï³­";s:4:"لم";s:3:"ï³®";s:4:"نم";s:3:"ﳯ";s:4:"نه";s:3:"ï³°";s:4:"يم";s:3:"ï³±";s:4:"يه";s:3:"ï³²";s:6:"Ù€ÙŽÙ‘";s:3:"ï³³";s:6:"Ù€ÙÙ‘";s:3:"ï³´";s:6:"Ù€ÙÙ‘";s:3:"ï³µ";s:4:"طى";s:3:"ï³¶";s:4:"طي";s:3:"ï³·";s:4:"عى";s:3:"ﳸ";s:4:"عي";s:3:"ï³¹";s:4:"غى";s:3:"ﳺ";s:4:"غي";s:3:"ï³»";s:4:"سى";s:3:"ï³¼";s:4:"سي";s:3:"ï³½";s:4:"شى";s:3:"ï³¾";s:4:"شي";s:3:"ﳿ";s:4:"حى";s:3:"ï´€";s:4:"حي";s:3:"ï´";s:4:"جى";s:3:"ï´‚";s:4:"جي";s:3:"ï´ƒ";s:4:"خى";s:3:"ï´„";s:4:"خي";s:3:"ï´…";s:4:"صى";s:3:"ï´†";s:4:"صي";s:3:"ï´‡";s:4:"ضى";s:3:"ï´ˆ";s:4:"ضي";s:3:"ï´‰";s:4:"شج";s:3:"ï´Š";s:4:"شح";s:3:"ï´‹";s:4:"شخ";s:3:"ï´Œ";s:4:"شم";s:3:"ï´";s:4:"شر";s:3:"ï´Ž";s:4:"سر";s:3:"ï´";s:4:"صر";s:3:"ï´";s:4:"ضر";s:3:"ï´‘";s:4:"طى";s:3:"ï´’";s:4:"طي";s:3:"ï´“";s:4:"عى";s:3:"ï´”";s:4:"عي";s:3:"ï´•";s:4:"غى";s:3:"ï´–";s:4:"غي";s:3:"ï´—";s:4:"سى";s:3:"ï´˜";s:4:"سي";s:3:"ï´™";s:4:"شى";s:3:"ï´š";s:4:"شي";s:3:"ï´›";s:4:"حى";s:3:"ï´œ";s:4:"حي";s:3:"ï´";s:4:"جى";s:3:"ï´ž";s:4:"جي";s:3:"ï´Ÿ";s:4:"خى";s:3:"ï´ ";s:4:"خي";s:3:"ï´¡";s:4:"صى";s:3:"ï´¢";s:4:"صي";s:3:"ï´£";s:4:"ضى";s:3:"ï´¤";s:4:"ضي";s:3:"ï´¥";s:4:"شج";s:3:"ï´¦";s:4:"شح";s:3:"ï´§";s:4:"شخ";s:3:"ï´¨";s:4:"شم";s:3:"ï´©";s:4:"شر";s:3:"ï´ª";s:4:"سر";s:3:"ï´«";s:4:"صر";s:3:"ï´¬";s:4:"ضر";s:3:"ï´­";s:4:"شج";s:3:"ï´®";s:4:"شح";s:3:"ï´¯";s:4:"شخ";s:3:"ï´°";s:4:"شم";s:3:"ï´±";s:4:"سه";s:3:"ï´²";s:4:"شه";s:3:"ï´³";s:4:"طم";s:3:"ï´´";s:4:"سج";s:3:"ï´µ";s:4:"سح";s:3:"ï´¶";s:4:"سخ";s:3:"ï´·";s:4:"شج";s:3:"ï´¸";s:4:"شح";s:3:"ï´¹";s:4:"شخ";s:3:"ï´º";s:4:"طم";s:3:"ï´»";s:4:"ظم";s:3:"ï´¼";s:4:"اً";s:3:"ï´½";s:4:"اً";s:3:"ïµ";s:6:"تجم";s:3:"ﵑ";s:6:"تحج";s:3:"ïµ’";s:6:"تحج";s:3:"ﵓ";s:6:"تحم";s:3:"ïµ”";s:6:"تخم";s:3:"ﵕ";s:6:"تمج";s:3:"ïµ–";s:6:"تمح";s:3:"ïµ—";s:6:"تمخ";s:3:"ﵘ";s:6:"جمح";s:3:"ïµ™";s:6:"جمح";s:3:"ﵚ";s:6:"حمي";s:3:"ïµ›";s:6:"حمى";s:3:"ﵜ";s:6:"سحج";s:3:"ïµ";s:6:"سجح";s:3:"ﵞ";s:6:"سجى";s:3:"ﵟ";s:6:"سمح";s:3:"ïµ ";s:6:"سمح";s:3:"ﵡ";s:6:"سمج";s:3:"ïµ¢";s:6:"سمم";s:3:"ïµ£";s:6:"سمم";s:3:"ﵤ";s:6:"صحح";s:3:"ïµ¥";s:6:"صحح";s:3:"ﵦ";s:6:"صمم";s:3:"ïµ§";s:6:"شحم";s:3:"ﵨ";s:6:"شحم";s:3:"ﵩ";s:6:"شجي";s:3:"ﵪ";s:6:"شمخ";s:3:"ﵫ";s:6:"شمخ";s:3:"ﵬ";s:6:"شمم";s:3:"ïµ­";s:6:"شمم";s:3:"ïµ®";s:6:"ضحى";s:3:"ﵯ";s:6:"ضخم";s:3:"ïµ°";s:6:"ضخم";s:3:"ïµ±";s:6:"طمح";s:3:"ïµ²";s:6:"طمح";s:3:"ïµ³";s:6:"طمم";s:3:"ïµ´";s:6:"طمي";s:3:"ïµµ";s:6:"عجم";s:3:"ïµ¶";s:6:"عمم";s:3:"ïµ·";s:6:"عمم";s:3:"ﵸ";s:6:"عمى";s:3:"ïµ¹";s:6:"غمم";s:3:"ﵺ";s:6:"غمي";s:3:"ïµ»";s:6:"غمى";s:3:"ïµ¼";s:6:"ÙØ®Ù…";s:3:"ïµ½";s:6:"ÙØ®Ù…";s:3:"ïµ¾";s:6:"قمح";s:3:"ﵿ";s:6:"قمم";s:3:"ï¶€";s:6:"لحم";s:3:"ï¶";s:6:"لحي";s:3:"ï¶‚";s:6:"لحى";s:3:"ﶃ";s:6:"لجج";s:3:"ï¶„";s:6:"لجج";s:3:"ï¶…";s:6:"لخم";s:3:"ﶆ";s:6:"لخم";s:3:"ﶇ";s:6:"لمح";s:3:"ﶈ";s:6:"لمح";s:3:"ﶉ";s:6:"محج";s:3:"ï¶Š";s:6:"محم";s:3:"ï¶‹";s:6:"محي";s:3:"ï¶Œ";s:6:"مجح";s:3:"ï¶";s:6:"مجم";s:3:"ï¶Ž";s:6:"مخج";s:3:"ï¶";s:6:"مخم";s:3:"ï¶’";s:6:"مجخ";s:3:"ï¶“";s:6:"همج";s:3:"ï¶”";s:6:"همم";s:3:"ï¶•";s:6:"نحم";s:3:"ï¶–";s:6:"نحى";s:3:"ï¶—";s:6:"نجم";s:3:"ﶘ";s:6:"نجم";s:3:"ï¶™";s:6:"نجى";s:3:"ï¶š";s:6:"نمي";s:3:"ï¶›";s:6:"نمى";s:3:"ï¶œ";s:6:"يمم";s:3:"ï¶";s:6:"يمم";s:3:"ï¶ž";s:6:"بخي";s:3:"ï¶Ÿ";s:6:"تجي";s:3:"ï¶ ";s:6:"تجى";s:3:"ï¶¡";s:6:"تخي";s:3:"ï¶¢";s:6:"تخى";s:3:"ï¶£";s:6:"تمي";s:3:"ﶤ";s:6:"تمى";s:3:"ï¶¥";s:6:"جمي";s:3:"ﶦ";s:6:"جحى";s:3:"ï¶§";s:6:"جمى";s:3:"ﶨ";s:6:"سخى";s:3:"ï¶©";s:6:"صحي";s:3:"ﶪ";s:6:"شحي";s:3:"ï¶«";s:6:"ضحي";s:3:"ﶬ";s:6:"لجي";s:3:"ï¶­";s:6:"لمي";s:3:"ï¶®";s:6:"يحي";s:3:"ﶯ";s:6:"يجي";s:3:"ï¶°";s:6:"يمي";s:3:"ï¶±";s:6:"ممي";s:3:"ï¶²";s:6:"قمي";s:3:"ï¶³";s:6:"نحي";s:3:"ï¶´";s:6:"قمح";s:3:"ï¶µ";s:6:"لحم";s:3:"ï¶¶";s:6:"عمي";s:3:"ï¶·";s:6:"كمي";s:3:"ﶸ";s:6:"نجح";s:3:"ï¶¹";s:6:"مخي";s:3:"ﶺ";s:6:"لجم";s:3:"ï¶»";s:6:"كمم";s:3:"ï¶¼";s:6:"لجم";s:3:"ï¶½";s:6:"نجح";s:3:"ï¶¾";s:6:"جحي";s:3:"ï¶¿";s:6:"حجي";s:3:"ï·€";s:6:"مجي";s:3:"ï·";s:6:"Ùمي";s:3:"ï·‚";s:6:"بحي";s:3:"ï·ƒ";s:6:"كمم";s:3:"ï·„";s:6:"عجم";s:3:"ï·…";s:6:"صمم";s:3:"ï·†";s:6:"سخي";s:3:"ï·‡";s:6:"نجي";s:3:"ï·°";s:6:"صلے";s:3:"ï·±";s:6:"قلے";s:3:"ï·²";s:8:"الله";s:3:"ï·³";s:8:"اكبر";s:3:"ï·´";s:8:"محمد";s:3:"ï·µ";s:8:"صلعم";s:3:"ï·¶";s:8:"رسول";s:3:"ï··";s:8:"عليه";s:3:"ï·¸";s:8:"وسلم";s:3:"ï·¹";s:6:"صلى";s:3:"ï·º";s:33:"صلى الله عليه وسلم";s:3:"ï·»";s:15:"جل جلاله";s:3:"ï·¼";s:8:"ریال";s:3:"ï¸";s:1:",";s:3:"︑";s:3:"ã€";s:3:"︒";s:3:"。";s:3:"︓";s:1:":";s:3:"︔";s:1:";";s:3:"︕";s:1:"!";s:3:"︖";s:1:"?";s:3:"︗";s:3:"〖";s:3:"︘";s:3:"〗";s:3:"︙";s:3:"...";s:3:"︰";s:2:"..";s:3:"︱";s:3:"—";s:3:"︲";s:3:"–";s:3:"︳";s:1:"_";s:3:"︴";s:1:"_";s:3:"︵";s:1:"(";s:3:"︶";s:1:")";s:3:"︷";s:1:"{";s:3:"︸";s:1:"}";s:3:"︹";s:3:"〔";s:3:"︺";s:3:"〕";s:3:"︻";s:3:"ã€";s:3:"︼";s:3:"】";s:3:"︽";s:3:"《";s:3:"︾";s:3:"》";s:3:"︿";s:3:"〈";s:3:"ï¹€";s:3:"〉";s:3:"ï¹";s:3:"「";s:3:"﹂";s:3:"ã€";s:3:"﹃";s:3:"『";s:3:"﹄";s:3:"ã€";s:3:"﹇";s:1:"[";s:3:"﹈";s:1:"]";s:3:"﹉";s:3:" Ì…";s:3:"﹊";s:3:" Ì…";s:3:"﹋";s:3:" Ì…";s:3:"﹌";s:3:" Ì…";s:3:"ï¹";s:1:"_";s:3:"﹎";s:1:"_";s:3:"ï¹";s:1:"_";s:3:"ï¹";s:1:",";s:3:"﹑";s:3:"ã€";s:3:"ï¹’";s:1:".";s:3:"ï¹”";s:1:";";s:3:"﹕";s:1:":";s:3:"ï¹–";s:1:"?";s:3:"ï¹—";s:1:"!";s:3:"﹘";s:3:"—";s:3:"ï¹™";s:1:"(";s:3:"﹚";s:1:")";s:3:"ï¹›";s:1:"{";s:3:"﹜";s:1:"}";s:3:"ï¹";s:3:"〔";s:3:"﹞";s:3:"〕";s:3:"﹟";s:1:"#";s:3:"ï¹ ";s:1:"&";s:3:"﹡";s:1:"*";s:3:"ï¹¢";s:1:"+";s:3:"ï¹£";s:1:"-";s:3:"﹤";s:1:"<";s:3:"ï¹¥";s:1:">";s:3:"﹦";s:1:"=";s:3:"﹨";s:1:"\";s:3:"﹩";s:1:"$";s:3:"﹪";s:1:"%";s:3:"﹫";s:1:"@";s:3:"ï¹°";s:3:" Ù‹";s:3:"ï¹±";s:4:"ـً";s:3:"ï¹²";s:3:" ÙŒ";s:3:"ï¹´";s:3:" Ù";s:3:"ï¹¶";s:3:" ÙŽ";s:3:"ï¹·";s:4:"Ù€ÙŽ";s:3:"ﹸ";s:3:" Ù";s:3:"ï¹¹";s:4:"Ù€Ù";s:3:"ﹺ";s:3:" Ù";s:3:"ï¹»";s:4:"Ù€Ù";s:3:"ï¹¼";s:3:" Ù‘";s:3:"ï¹½";s:4:"ـّ";s:3:"ï¹¾";s:3:" Ù’";s:3:"ﹿ";s:4:"ـْ";s:3:"ﺀ";s:2:"Ø¡";s:3:"ïº";s:4:"آ";s:3:"ﺂ";s:4:"آ";s:3:"ﺃ";s:4:"أ";s:3:"ﺄ";s:4:"أ";s:3:"ﺅ";s:4:"ÙˆÙ”";s:3:"ﺆ";s:4:"ÙˆÙ”";s:3:"ﺇ";s:4:"إ";s:3:"ﺈ";s:4:"إ";s:3:"ﺉ";s:4:"ÙŠÙ”";s:3:"ﺊ";s:4:"ÙŠÙ”";s:3:"ﺋ";s:4:"ÙŠÙ”";s:3:"ﺌ";s:4:"ÙŠÙ”";s:3:"ïº";s:2:"ا";s:3:"ﺎ";s:2:"ا";s:3:"ïº";s:2:"ب";s:3:"ïº";s:2:"ب";s:3:"ﺑ";s:2:"ب";s:3:"ﺒ";s:2:"ب";s:3:"ﺓ";s:2:"Ø©";s:3:"ﺔ";s:2:"Ø©";s:3:"ﺕ";s:2:"ت";s:3:"ﺖ";s:2:"ت";s:3:"ﺗ";s:2:"ت";s:3:"ﺘ";s:2:"ت";s:3:"ﺙ";s:2:"Ø«";s:3:"ﺚ";s:2:"Ø«";s:3:"ﺛ";s:2:"Ø«";s:3:"ﺜ";s:2:"Ø«";s:3:"ïº";s:2:"ج";s:3:"ﺞ";s:2:"ج";s:3:"ﺟ";s:2:"ج";s:3:"ﺠ";s:2:"ج";s:3:"ﺡ";s:2:"Ø­";s:3:"ﺢ";s:2:"Ø­";s:3:"ﺣ";s:2:"Ø­";s:3:"ﺤ";s:2:"Ø­";s:3:"ﺥ";s:2:"Ø®";s:3:"ﺦ";s:2:"Ø®";s:3:"ﺧ";s:2:"Ø®";s:3:"ﺨ";s:2:"Ø®";s:3:"ﺩ";s:2:"د";s:3:"ﺪ";s:2:"د";s:3:"ﺫ";s:2:"ذ";s:3:"ﺬ";s:2:"ذ";s:3:"ﺭ";s:2:"ر";s:3:"ﺮ";s:2:"ر";s:3:"ﺯ";s:2:"ز";s:3:"ﺰ";s:2:"ز";s:3:"ﺱ";s:2:"س";s:3:"ﺲ";s:2:"س";s:3:"ﺳ";s:2:"س";s:3:"ﺴ";s:2:"س";s:3:"ﺵ";s:2:"Ø´";s:3:"ﺶ";s:2:"Ø´";s:3:"ﺷ";s:2:"Ø´";s:3:"ﺸ";s:2:"Ø´";s:3:"ﺹ";s:2:"ص";s:3:"ﺺ";s:2:"ص";s:3:"ﺻ";s:2:"ص";s:3:"ﺼ";s:2:"ص";s:3:"ﺽ";s:2:"ض";s:3:"ﺾ";s:2:"ض";s:3:"ﺿ";s:2:"ض";s:3:"ﻀ";s:2:"ض";s:3:"ï»";s:2:"Ø·";s:3:"ﻂ";s:2:"Ø·";s:3:"ﻃ";s:2:"Ø·";s:3:"ﻄ";s:2:"Ø·";s:3:"ï»…";s:2:"ظ";s:3:"ﻆ";s:2:"ظ";s:3:"ﻇ";s:2:"ظ";s:3:"ﻈ";s:2:"ظ";s:3:"ﻉ";s:2:"ع";s:3:"ﻊ";s:2:"ع";s:3:"ﻋ";s:2:"ع";s:3:"ﻌ";s:2:"ع";s:3:"ï»";s:2:"غ";s:3:"ﻎ";s:2:"غ";s:3:"ï»";s:2:"غ";s:3:"ï»";s:2:"غ";s:3:"ﻑ";s:2:"Ù";s:3:"ï»’";s:2:"Ù";s:3:"ﻓ";s:2:"Ù";s:3:"ï»”";s:2:"Ù";s:3:"ﻕ";s:2:"Ù‚";s:3:"ï»–";s:2:"Ù‚";s:3:"ï»—";s:2:"Ù‚";s:3:"ﻘ";s:2:"Ù‚";s:3:"ï»™";s:2:"Ùƒ";s:3:"ﻚ";s:2:"Ùƒ";s:3:"ï»›";s:2:"Ùƒ";s:3:"ﻜ";s:2:"Ùƒ";s:3:"ï»";s:2:"Ù„";s:3:"ﻞ";s:2:"Ù„";s:3:"ﻟ";s:2:"Ù„";s:3:"ï» ";s:2:"Ù„";s:3:"ﻡ";s:2:"Ù…";s:3:"ﻢ";s:2:"Ù…";s:3:"ﻣ";s:2:"Ù…";s:3:"ﻤ";s:2:"Ù…";s:3:"ﻥ";s:2:"Ù†";s:3:"ﻦ";s:2:"Ù†";s:3:"ï»§";s:2:"Ù†";s:3:"ﻨ";s:2:"Ù†";s:3:"ﻩ";s:2:"Ù‡";s:3:"ﻪ";s:2:"Ù‡";s:3:"ﻫ";s:2:"Ù‡";s:3:"ﻬ";s:2:"Ù‡";s:3:"ï»­";s:2:"Ùˆ";s:3:"ï»®";s:2:"Ùˆ";s:3:"ﻯ";s:2:"Ù‰";s:3:"ï»°";s:2:"Ù‰";s:3:"ï»±";s:2:"ÙŠ";s:3:"ﻲ";s:2:"ÙŠ";s:3:"ﻳ";s:2:"ÙŠ";s:3:"ï»´";s:2:"ÙŠ";s:3:"ﻵ";s:6:"لآ";s:3:"ï»¶";s:6:"لآ";s:3:"ï»·";s:6:"لأ";s:3:"ﻸ";s:6:"لأ";s:3:"ﻹ";s:6:"لإ";s:3:"ﻺ";s:6:"لإ";s:3:"ï»»";s:4:"لا";s:3:"ﻼ";s:4:"لا";s:3:"ï¼";s:1:"!";s:3:""";s:1:""";s:3:"#";s:1:"#";s:3:"$";s:1:"$";s:3:"ï¼…";s:1:"%";s:3:"&";s:1:"&";s:3:"'";s:1:"'";s:3:"(";s:1:"(";s:3:")";s:1:")";s:3:"*";s:1:"*";s:3:"+";s:1:"+";s:3:",";s:1:",";s:3:"ï¼";s:1:"-";s:3:".";s:1:".";s:3:"ï¼";s:1:"/";s:3:"ï¼";s:1:"0";s:3:"1";s:1:"1";s:3:"ï¼’";s:1:"2";s:3:"3";s:1:"3";s:3:"ï¼”";s:1:"4";s:3:"5";s:1:"5";s:3:"ï¼–";s:1:"6";s:3:"ï¼—";s:1:"7";s:3:"8";s:1:"8";s:3:"ï¼™";s:1:"9";s:3:":";s:1:":";s:3:"ï¼›";s:1:";";s:3:"<";s:1:"<";s:3:"ï¼";s:1:"=";s:3:">";s:1:">";s:3:"?";s:1:"?";s:3:"ï¼ ";s:1:"@";s:3:"A";s:1:"A";s:3:"ï¼¢";s:1:"B";s:3:"ï¼£";s:1:"C";s:3:"D";s:1:"D";s:3:"ï¼¥";s:1:"E";s:3:"F";s:1:"F";s:3:"ï¼§";s:1:"G";s:3:"H";s:1:"H";s:3:"I";s:1:"I";s:3:"J";s:1:"J";s:3:"K";s:1:"K";s:3:"L";s:1:"L";s:3:"ï¼­";s:1:"M";s:3:"ï¼®";s:1:"N";s:3:"O";s:1:"O";s:3:"ï¼°";s:1:"P";s:3:"ï¼±";s:1:"Q";s:3:"ï¼²";s:1:"R";s:3:"ï¼³";s:1:"S";s:3:"ï¼´";s:1:"T";s:3:"ï¼µ";s:1:"U";s:3:"ï¼¶";s:1:"V";s:3:"ï¼·";s:1:"W";s:3:"X";s:1:"X";s:3:"ï¼¹";s:1:"Y";s:3:"Z";s:1:"Z";s:3:"ï¼»";s:1:"[";s:3:"ï¼¼";s:1:"\";s:3:"ï¼½";s:1:"]";s:3:"ï¼¾";s:1:"^";s:3:"_";s:1:"_";s:3:"ï½€";s:1:"`";s:3:"ï½";s:1:"a";s:3:"b";s:1:"b";s:3:"c";s:1:"c";s:3:"d";s:1:"d";s:3:"ï½…";s:1:"e";s:3:"f";s:1:"f";s:3:"g";s:1:"g";s:3:"h";s:1:"h";s:3:"i";s:1:"i";s:3:"j";s:1:"j";s:3:"k";s:1:"k";s:3:"l";s:1:"l";s:3:"ï½";s:1:"m";s:3:"n";s:1:"n";s:3:"ï½";s:1:"o";s:3:"ï½";s:1:"p";s:3:"q";s:1:"q";s:3:"ï½’";s:1:"r";s:3:"s";s:1:"s";s:3:"ï½”";s:1:"t";s:3:"u";s:1:"u";s:3:"ï½–";s:1:"v";s:3:"ï½—";s:1:"w";s:3:"x";s:1:"x";s:3:"ï½™";s:1:"y";s:3:"z";s:1:"z";s:3:"ï½›";s:1:"{";s:3:"|";s:1:"|";s:3:"ï½";s:1:"}";s:3:"~";s:1:"~";s:3:"⦅";s:3:"⦅";s:3:"ï½ ";s:3:"⦆";s:3:"。";s:3:"。";s:3:"ï½¢";s:3:"「";s:3:"ï½£";s:3:"ã€";s:3:"、";s:3:"ã€";s:3:"ï½¥";s:3:"・";s:3:"ヲ";s:3:"ヲ";s:3:"ï½§";s:3:"ã‚¡";s:3:"ィ";s:3:"ã‚£";s:3:"ゥ";s:3:"ã‚¥";s:3:"ェ";s:3:"ã‚§";s:3:"ォ";s:3:"ã‚©";s:3:"ャ";s:3:"ャ";s:3:"ï½­";s:3:"ュ";s:3:"ï½®";s:3:"ョ";s:3:"ッ";s:3:"ッ";s:3:"ï½°";s:3:"ー";s:3:"ï½±";s:3:"ã‚¢";s:3:"ï½²";s:3:"イ";s:3:"ï½³";s:3:"ウ";s:3:"ï½´";s:3:"エ";s:3:"ï½µ";s:3:"オ";s:3:"ï½¶";s:3:"ã‚«";s:3:"ï½·";s:3:"ã‚­";s:3:"ク";s:3:"ク";s:3:"ï½¹";s:3:"ケ";s:3:"コ";s:3:"コ";s:3:"ï½»";s:3:"サ";s:3:"ï½¼";s:3:"ã‚·";s:3:"ï½½";s:3:"ス";s:3:"ï½¾";s:3:"ã‚»";s:3:"ソ";s:3:"ソ";s:3:"ï¾€";s:3:"ã‚¿";s:3:"ï¾";s:3:"ãƒ";s:3:"ツ";s:3:"ツ";s:3:"テ";s:3:"テ";s:3:"ト";s:3:"ト";s:3:"ï¾…";s:3:"ナ";s:3:"ニ";s:3:"ニ";s:3:"ヌ";s:3:"ヌ";s:3:"ネ";s:3:"ãƒ";s:3:"ノ";s:3:"ノ";s:3:"ハ";s:3:"ãƒ";s:3:"ヒ";s:3:"ヒ";s:3:"フ";s:3:"フ";s:3:"ï¾";s:3:"ヘ";s:3:"ホ";s:3:"ホ";s:3:"ï¾";s:3:"マ";s:3:"ï¾";s:3:"ミ";s:3:"ム";s:3:"ム";s:3:"ï¾’";s:3:"メ";s:3:"モ";s:3:"モ";s:3:"ï¾”";s:3:"ヤ";s:3:"ユ";s:3:"ユ";s:3:"ï¾–";s:3:"ヨ";s:3:"ï¾—";s:3:"ラ";s:3:"リ";s:3:"リ";s:3:"ï¾™";s:3:"ル";s:3:"レ";s:3:"レ";s:3:"ï¾›";s:3:"ロ";s:3:"ワ";s:3:"ワ";s:3:"ï¾";s:3:"ン";s:3:"゙";s:3:"ã‚™";s:3:"゚";s:3:"゚";s:3:"ï¾ ";s:3:"á… ";s:3:"ᄀ";s:3:"á„€";s:3:"ï¾¢";s:3:"á„";s:3:"ï¾£";s:3:"ᆪ";s:3:"ᄂ";s:3:"á„‚";s:3:"ï¾¥";s:3:"ᆬ";s:3:"ᆭ";s:3:"ᆭ";s:3:"ï¾§";s:3:"ᄃ";s:3:"ᄄ";s:3:"á„„";s:3:"ᄅ";s:3:"á„…";s:3:"ᆰ";s:3:"ᆰ";s:3:"ᆱ";s:3:"ᆱ";s:3:"ᆲ";s:3:"ᆲ";s:3:"ï¾­";s:3:"ᆳ";s:3:"ï¾®";s:3:"ᆴ";s:3:"ᆵ";s:3:"ᆵ";s:3:"ï¾°";s:3:"ᄚ";s:3:"ï¾±";s:3:"ᄆ";s:3:"ï¾²";s:3:"ᄇ";s:3:"ï¾³";s:3:"ᄈ";s:3:"ï¾´";s:3:"á„¡";s:3:"ï¾µ";s:3:"ᄉ";s:3:"ï¾¶";s:3:"ᄊ";s:3:"ï¾·";s:3:"á„‹";s:3:"ᄌ";s:3:"ᄌ";s:3:"ï¾¹";s:3:"á„";s:3:"ᄎ";s:3:"ᄎ";s:3:"ï¾»";s:3:"á„";s:3:"ï¾¼";s:3:"á„";s:3:"ï¾½";s:3:"á„‘";s:3:"ï¾¾";s:3:"á„’";s:3:"ï¿‚";s:3:"á…¡";s:3:"ᅢ";s:3:"á…¢";s:3:"ï¿„";s:3:"á…£";s:3:"ï¿…";s:3:"á…¤";s:3:"ᅥ";s:3:"á…¥";s:3:"ᅦ";s:3:"á…¦";s:3:"ᅧ";s:3:"á…§";s:3:"ï¿‹";s:3:"á…¨";s:3:"ᅩ";s:3:"á…©";s:3:"ï¿";s:3:"á…ª";s:3:"ᅫ";s:3:"á…«";s:3:"ï¿";s:3:"á…¬";s:3:"ï¿’";s:3:"á…­";s:3:"ï¿“";s:3:"á…®";s:3:"ï¿”";s:3:"á…¯";s:3:"ï¿•";s:3:"á…°";s:3:"ï¿–";s:3:"á…±";s:3:"ï¿—";s:3:"á…²";s:3:"ᅳ";s:3:"á…³";s:3:"ï¿›";s:3:"á…´";s:3:"ᅵ";s:3:"á…µ";s:3:"ï¿ ";s:2:"¢";s:3:"ï¿¡";s:2:"£";s:3:"ï¿¢";s:2:"¬";s:3:"ï¿£";s:3:" Ì„";s:3:"¦";s:2:"¦";s:3:"ï¿¥";s:2:"Â¥";s:3:"₩";s:3:"â‚©";s:3:"│";s:3:"│";s:3:"ï¿©";s:3:"â†";s:3:"↑";s:3:"↑";s:3:"ï¿«";s:3:"→";s:3:"↓";s:3:"↓";s:3:"ï¿­";s:3:"â– ";s:3:"ï¿®";s:3:"â—‹";s:4:"ð€";s:1:"A";s:4:"ð";s:1:"B";s:4:"ð‚";s:1:"C";s:4:"ðƒ";s:1:"D";s:4:"ð„";s:1:"E";s:4:"ð…";s:1:"F";s:4:"ð†";s:1:"G";s:4:"ð‡";s:1:"H";s:4:"ðˆ";s:1:"I";s:4:"ð‰";s:1:"J";s:4:"ðŠ";s:1:"K";s:4:"ð‹";s:1:"L";s:4:"ðŒ";s:1:"M";s:4:"ð";s:1:"N";s:4:"ðŽ";s:1:"O";s:4:"ð";s:1:"P";s:4:"ð";s:1:"Q";s:4:"ð‘";s:1:"R";s:4:"ð’";s:1:"S";s:4:"ð“";s:1:"T";s:4:"ð”";s:1:"U";s:4:"ð•";s:1:"V";s:4:"ð–";s:1:"W";s:4:"ð—";s:1:"X";s:4:"ð˜";s:1:"Y";s:4:"ð™";s:1:"Z";s:4:"ðš";s:1:"a";s:4:"ð›";s:1:"b";s:4:"ðœ";s:1:"c";s:4:"ð";s:1:"d";s:4:"ðž";s:1:"e";s:4:"ðŸ";s:1:"f";s:4:"ð ";s:1:"g";s:4:"ð¡";s:1:"h";s:4:"ð¢";s:1:"i";s:4:"ð£";s:1:"j";s:4:"ð¤";s:1:"k";s:4:"ð¥";s:1:"l";s:4:"ð¦";s:1:"m";s:4:"ð§";s:1:"n";s:4:"ð¨";s:1:"o";s:4:"ð©";s:1:"p";s:4:"ðª";s:1:"q";s:4:"ð«";s:1:"r";s:4:"ð¬";s:1:"s";s:4:"ð­";s:1:"t";s:4:"ð®";s:1:"u";s:4:"ð¯";s:1:"v";s:4:"ð°";s:1:"w";s:4:"ð±";s:1:"x";s:4:"ð²";s:1:"y";s:4:"ð³";s:1:"z";s:4:"ð´";s:1:"A";s:4:"ðµ";s:1:"B";s:4:"ð¶";s:1:"C";s:4:"ð·";s:1:"D";s:4:"ð¸";s:1:"E";s:4:"ð¹";s:1:"F";s:4:"ðº";s:1:"G";s:4:"ð»";s:1:"H";s:4:"ð¼";s:1:"I";s:4:"ð½";s:1:"J";s:4:"ð¾";s:1:"K";s:4:"ð¿";s:1:"L";s:4:"ð‘€";s:1:"M";s:4:"ð‘";s:1:"N";s:4:"ð‘‚";s:1:"O";s:4:"ð‘ƒ";s:1:"P";s:4:"ð‘„";s:1:"Q";s:4:"ð‘…";s:1:"R";s:4:"ð‘†";s:1:"S";s:4:"ð‘‡";s:1:"T";s:4:"ð‘ˆ";s:1:"U";s:4:"ð‘‰";s:1:"V";s:4:"ð‘Š";s:1:"W";s:4:"ð‘‹";s:1:"X";s:4:"ð‘Œ";s:1:"Y";s:4:"ð‘";s:1:"Z";s:4:"ð‘Ž";s:1:"a";s:4:"ð‘";s:1:"b";s:4:"ð‘";s:1:"c";s:4:"ð‘‘";s:1:"d";s:4:"ð‘’";s:1:"e";s:4:"ð‘“";s:1:"f";s:4:"ð‘”";s:1:"g";s:4:"ð‘–";s:1:"i";s:4:"ð‘—";s:1:"j";s:4:"ð‘˜";s:1:"k";s:4:"ð‘™";s:1:"l";s:4:"ð‘š";s:1:"m";s:4:"ð‘›";s:1:"n";s:4:"ð‘œ";s:1:"o";s:4:"ð‘";s:1:"p";s:4:"ð‘ž";s:1:"q";s:4:"ð‘Ÿ";s:1:"r";s:4:"ð‘ ";s:1:"s";s:4:"ð‘¡";s:1:"t";s:4:"ð‘¢";s:1:"u";s:4:"ð‘£";s:1:"v";s:4:"ð‘¤";s:1:"w";s:4:"ð‘¥";s:1:"x";s:4:"ð‘¦";s:1:"y";s:4:"ð‘§";s:1:"z";s:4:"ð‘¨";s:1:"A";s:4:"ð‘©";s:1:"B";s:4:"ð‘ª";s:1:"C";s:4:"ð‘«";s:1:"D";s:4:"ð‘¬";s:1:"E";s:4:"ð‘­";s:1:"F";s:4:"ð‘®";s:1:"G";s:4:"ð‘¯";s:1:"H";s:4:"ð‘°";s:1:"I";s:4:"ð‘±";s:1:"J";s:4:"ð‘²";s:1:"K";s:4:"ð‘³";s:1:"L";s:4:"ð‘´";s:1:"M";s:4:"ð‘µ";s:1:"N";s:4:"ð‘¶";s:1:"O";s:4:"ð‘·";s:1:"P";s:4:"ð‘¸";s:1:"Q";s:4:"ð‘¹";s:1:"R";s:4:"ð‘º";s:1:"S";s:4:"ð‘»";s:1:"T";s:4:"ð‘¼";s:1:"U";s:4:"ð‘½";s:1:"V";s:4:"ð‘¾";s:1:"W";s:4:"ð‘¿";s:1:"X";s:4:"ð’€";s:1:"Y";s:4:"ð’";s:1:"Z";s:4:"ð’‚";s:1:"a";s:4:"ð’ƒ";s:1:"b";s:4:"ð’„";s:1:"c";s:4:"ð’…";s:1:"d";s:4:"ð’†";s:1:"e";s:4:"ð’‡";s:1:"f";s:4:"ð’ˆ";s:1:"g";s:4:"ð’‰";s:1:"h";s:4:"ð’Š";s:1:"i";s:4:"ð’‹";s:1:"j";s:4:"ð’Œ";s:1:"k";s:4:"ð’";s:1:"l";s:4:"ð’Ž";s:1:"m";s:4:"ð’";s:1:"n";s:4:"ð’";s:1:"o";s:4:"ð’‘";s:1:"p";s:4:"ð’’";s:1:"q";s:4:"ð’“";s:1:"r";s:4:"ð’”";s:1:"s";s:4:"ð’•";s:1:"t";s:4:"ð’–";s:1:"u";s:4:"ð’—";s:1:"v";s:4:"ð’˜";s:1:"w";s:4:"ð’™";s:1:"x";s:4:"ð’š";s:1:"y";s:4:"ð’›";s:1:"z";s:4:"ð’œ";s:1:"A";s:4:"ð’ž";s:1:"C";s:4:"ð’Ÿ";s:1:"D";s:4:"ð’¢";s:1:"G";s:4:"ð’¥";s:1:"J";s:4:"ð’¦";s:1:"K";s:4:"ð’©";s:1:"N";s:4:"ð’ª";s:1:"O";s:4:"ð’«";s:1:"P";s:4:"ð’¬";s:1:"Q";s:4:"ð’®";s:1:"S";s:4:"ð’¯";s:1:"T";s:4:"ð’°";s:1:"U";s:4:"ð’±";s:1:"V";s:4:"ð’²";s:1:"W";s:4:"ð’³";s:1:"X";s:4:"ð’´";s:1:"Y";s:4:"ð’µ";s:1:"Z";s:4:"ð’¶";s:1:"a";s:4:"ð’·";s:1:"b";s:4:"ð’¸";s:1:"c";s:4:"ð’¹";s:1:"d";s:4:"ð’»";s:1:"f";s:4:"ð’½";s:1:"h";s:4:"ð’¾";s:1:"i";s:4:"ð’¿";s:1:"j";s:4:"ð“€";s:1:"k";s:4:"ð“";s:1:"l";s:4:"ð“‚";s:1:"m";s:4:"ð“ƒ";s:1:"n";s:4:"ð“…";s:1:"p";s:4:"ð“†";s:1:"q";s:4:"ð“‡";s:1:"r";s:4:"ð“ˆ";s:1:"s";s:4:"ð“‰";s:1:"t";s:4:"ð“Š";s:1:"u";s:4:"ð“‹";s:1:"v";s:4:"ð“Œ";s:1:"w";s:4:"ð“";s:1:"x";s:4:"ð“Ž";s:1:"y";s:4:"ð“";s:1:"z";s:4:"ð“";s:1:"A";s:4:"ð“‘";s:1:"B";s:4:"ð“’";s:1:"C";s:4:"ð““";s:1:"D";s:4:"ð“”";s:1:"E";s:4:"ð“•";s:1:"F";s:4:"ð“–";s:1:"G";s:4:"ð“—";s:1:"H";s:4:"ð“˜";s:1:"I";s:4:"ð“™";s:1:"J";s:4:"ð“š";s:1:"K";s:4:"ð“›";s:1:"L";s:4:"ð“œ";s:1:"M";s:4:"ð“";s:1:"N";s:4:"ð“ž";s:1:"O";s:4:"ð“Ÿ";s:1:"P";s:4:"ð“ ";s:1:"Q";s:4:"ð“¡";s:1:"R";s:4:"ð“¢";s:1:"S";s:4:"ð“£";s:1:"T";s:4:"ð“¤";s:1:"U";s:4:"ð“¥";s:1:"V";s:4:"ð“¦";s:1:"W";s:4:"ð“§";s:1:"X";s:4:"ð“¨";s:1:"Y";s:4:"ð“©";s:1:"Z";s:4:"ð“ª";s:1:"a";s:4:"ð“«";s:1:"b";s:4:"ð“¬";s:1:"c";s:4:"ð“­";s:1:"d";s:4:"ð“®";s:1:"e";s:4:"ð“¯";s:1:"f";s:4:"ð“°";s:1:"g";s:4:"ð“±";s:1:"h";s:4:"ð“²";s:1:"i";s:4:"ð“³";s:1:"j";s:4:"ð“´";s:1:"k";s:4:"ð“µ";s:1:"l";s:4:"ð“¶";s:1:"m";s:4:"ð“·";s:1:"n";s:4:"ð“¸";s:1:"o";s:4:"ð“¹";s:1:"p";s:4:"ð“º";s:1:"q";s:4:"ð“»";s:1:"r";s:4:"ð“¼";s:1:"s";s:4:"ð“½";s:1:"t";s:4:"ð“¾";s:1:"u";s:4:"ð“¿";s:1:"v";s:4:"ð”€";s:1:"w";s:4:"ð”";s:1:"x";s:4:"ð”‚";s:1:"y";s:4:"ð”ƒ";s:1:"z";s:4:"ð”„";s:1:"A";s:4:"ð”…";s:1:"B";s:4:"ð”‡";s:1:"D";s:4:"ð”ˆ";s:1:"E";s:4:"ð”‰";s:1:"F";s:4:"ð”Š";s:1:"G";s:4:"ð”";s:1:"J";s:4:"ð”Ž";s:1:"K";s:4:"ð”";s:1:"L";s:4:"ð”";s:1:"M";s:4:"ð”‘";s:1:"N";s:4:"ð”’";s:1:"O";s:4:"ð”“";s:1:"P";s:4:"ð””";s:1:"Q";s:4:"ð”–";s:1:"S";s:4:"ð”—";s:1:"T";s:4:"ð”˜";s:1:"U";s:4:"ð”™";s:1:"V";s:4:"ð”š";s:1:"W";s:4:"ð”›";s:1:"X";s:4:"ð”œ";s:1:"Y";s:4:"ð”ž";s:1:"a";s:4:"ð”Ÿ";s:1:"b";s:4:"ð” ";s:1:"c";s:4:"ð”¡";s:1:"d";s:4:"ð”¢";s:1:"e";s:4:"ð”£";s:1:"f";s:4:"ð”¤";s:1:"g";s:4:"ð”¥";s:1:"h";s:4:"ð”¦";s:1:"i";s:4:"ð”§";s:1:"j";s:4:"ð”¨";s:1:"k";s:4:"ð”©";s:1:"l";s:4:"ð”ª";s:1:"m";s:4:"ð”«";s:1:"n";s:4:"ð”¬";s:1:"o";s:4:"ð”­";s:1:"p";s:4:"ð”®";s:1:"q";s:4:"ð”¯";s:1:"r";s:4:"ð”°";s:1:"s";s:4:"ð”±";s:1:"t";s:4:"ð”²";s:1:"u";s:4:"ð”³";s:1:"v";s:4:"ð”´";s:1:"w";s:4:"ð”µ";s:1:"x";s:4:"ð”¶";s:1:"y";s:4:"ð”·";s:1:"z";s:4:"ð”¸";s:1:"A";s:4:"ð”¹";s:1:"B";s:4:"ð”»";s:1:"D";s:4:"ð”¼";s:1:"E";s:4:"ð”½";s:1:"F";s:4:"ð”¾";s:1:"G";s:4:"ð•€";s:1:"I";s:4:"ð•";s:1:"J";s:4:"ð•‚";s:1:"K";s:4:"ð•ƒ";s:1:"L";s:4:"ð•„";s:1:"M";s:4:"ð•†";s:1:"O";s:4:"ð•Š";s:1:"S";s:4:"ð•‹";s:1:"T";s:4:"ð•Œ";s:1:"U";s:4:"ð•";s:1:"V";s:4:"ð•Ž";s:1:"W";s:4:"ð•";s:1:"X";s:4:"ð•";s:1:"Y";s:4:"ð•’";s:1:"a";s:4:"ð•“";s:1:"b";s:4:"ð•”";s:1:"c";s:4:"ð••";s:1:"d";s:4:"ð•–";s:1:"e";s:4:"ð•—";s:1:"f";s:4:"ð•˜";s:1:"g";s:4:"ð•™";s:1:"h";s:4:"ð•š";s:1:"i";s:4:"ð•›";s:1:"j";s:4:"ð•œ";s:1:"k";s:4:"ð•";s:1:"l";s:4:"ð•ž";s:1:"m";s:4:"ð•Ÿ";s:1:"n";s:4:"ð• ";s:1:"o";s:4:"ð•¡";s:1:"p";s:4:"ð•¢";s:1:"q";s:4:"ð•£";s:1:"r";s:4:"ð•¤";s:1:"s";s:4:"ð•¥";s:1:"t";s:4:"ð•¦";s:1:"u";s:4:"ð•§";s:1:"v";s:4:"ð•¨";s:1:"w";s:4:"ð•©";s:1:"x";s:4:"ð•ª";s:1:"y";s:4:"ð•«";s:1:"z";s:4:"ð•¬";s:1:"A";s:4:"ð•­";s:1:"B";s:4:"ð•®";s:1:"C";s:4:"ð•¯";s:1:"D";s:4:"ð•°";s:1:"E";s:4:"ð•±";s:1:"F";s:4:"ð•²";s:1:"G";s:4:"ð•³";s:1:"H";s:4:"ð•´";s:1:"I";s:4:"ð•µ";s:1:"J";s:4:"ð•¶";s:1:"K";s:4:"ð•·";s:1:"L";s:4:"ð•¸";s:1:"M";s:4:"ð•¹";s:1:"N";s:4:"ð•º";s:1:"O";s:4:"ð•»";s:1:"P";s:4:"ð•¼";s:1:"Q";s:4:"ð•½";s:1:"R";s:4:"ð•¾";s:1:"S";s:4:"ð•¿";s:1:"T";s:4:"ð–€";s:1:"U";s:4:"ð–";s:1:"V";s:4:"ð–‚";s:1:"W";s:4:"ð–ƒ";s:1:"X";s:4:"ð–„";s:1:"Y";s:4:"ð–…";s:1:"Z";s:4:"ð–†";s:1:"a";s:4:"ð–‡";s:1:"b";s:4:"ð–ˆ";s:1:"c";s:4:"ð–‰";s:1:"d";s:4:"ð–Š";s:1:"e";s:4:"ð–‹";s:1:"f";s:4:"ð–Œ";s:1:"g";s:4:"ð–";s:1:"h";s:4:"ð–Ž";s:1:"i";s:4:"ð–";s:1:"j";s:4:"ð–";s:1:"k";s:4:"ð–‘";s:1:"l";s:4:"ð–’";s:1:"m";s:4:"ð–“";s:1:"n";s:4:"ð–”";s:1:"o";s:4:"ð–•";s:1:"p";s:4:"ð––";s:1:"q";s:4:"ð–—";s:1:"r";s:4:"ð–˜";s:1:"s";s:4:"ð–™";s:1:"t";s:4:"ð–š";s:1:"u";s:4:"ð–›";s:1:"v";s:4:"ð–œ";s:1:"w";s:4:"ð–";s:1:"x";s:4:"ð–ž";s:1:"y";s:4:"ð–Ÿ";s:1:"z";s:4:"ð– ";s:1:"A";s:4:"ð–¡";s:1:"B";s:4:"ð–¢";s:1:"C";s:4:"ð–£";s:1:"D";s:4:"ð–¤";s:1:"E";s:4:"ð–¥";s:1:"F";s:4:"ð–¦";s:1:"G";s:4:"ð–§";s:1:"H";s:4:"ð–¨";s:1:"I";s:4:"ð–©";s:1:"J";s:4:"ð–ª";s:1:"K";s:4:"ð–«";s:1:"L";s:4:"ð–¬";s:1:"M";s:4:"ð–­";s:1:"N";s:4:"ð–®";s:1:"O";s:4:"ð–¯";s:1:"P";s:4:"ð–°";s:1:"Q";s:4:"ð–±";s:1:"R";s:4:"ð–²";s:1:"S";s:4:"ð–³";s:1:"T";s:4:"ð–´";s:1:"U";s:4:"ð–µ";s:1:"V";s:4:"ð–¶";s:1:"W";s:4:"ð–·";s:1:"X";s:4:"ð–¸";s:1:"Y";s:4:"ð–¹";s:1:"Z";s:4:"ð–º";s:1:"a";s:4:"ð–»";s:1:"b";s:4:"ð–¼";s:1:"c";s:4:"ð–½";s:1:"d";s:4:"ð–¾";s:1:"e";s:4:"ð–¿";s:1:"f";s:4:"ð—€";s:1:"g";s:4:"ð—";s:1:"h";s:4:"ð—‚";s:1:"i";s:4:"ð—ƒ";s:1:"j";s:4:"ð—„";s:1:"k";s:4:"ð—…";s:1:"l";s:4:"ð—†";s:1:"m";s:4:"ð—‡";s:1:"n";s:4:"ð—ˆ";s:1:"o";s:4:"ð—‰";s:1:"p";s:4:"ð—Š";s:1:"q";s:4:"ð—‹";s:1:"r";s:4:"ð—Œ";s:1:"s";s:4:"ð—";s:1:"t";s:4:"ð—Ž";s:1:"u";s:4:"ð—";s:1:"v";s:4:"ð—";s:1:"w";s:4:"ð—‘";s:1:"x";s:4:"ð—’";s:1:"y";s:4:"ð—“";s:1:"z";s:4:"ð—”";s:1:"A";s:4:"ð—•";s:1:"B";s:4:"ð—–";s:1:"C";s:4:"ð——";s:1:"D";s:4:"ð—˜";s:1:"E";s:4:"ð—™";s:1:"F";s:4:"ð—š";s:1:"G";s:4:"ð—›";s:1:"H";s:4:"ð—œ";s:1:"I";s:4:"ð—";s:1:"J";s:4:"ð—ž";s:1:"K";s:4:"ð—Ÿ";s:1:"L";s:4:"ð— ";s:1:"M";s:4:"ð—¡";s:1:"N";s:4:"ð—¢";s:1:"O";s:4:"ð—£";s:1:"P";s:4:"ð—¤";s:1:"Q";s:4:"ð—¥";s:1:"R";s:4:"ð—¦";s:1:"S";s:4:"ð—§";s:1:"T";s:4:"ð—¨";s:1:"U";s:4:"ð—©";s:1:"V";s:4:"ð—ª";s:1:"W";s:4:"ð—«";s:1:"X";s:4:"ð—¬";s:1:"Y";s:4:"ð—­";s:1:"Z";s:4:"ð—®";s:1:"a";s:4:"ð—¯";s:1:"b";s:4:"ð—°";s:1:"c";s:4:"ð—±";s:1:"d";s:4:"ð—²";s:1:"e";s:4:"ð—³";s:1:"f";s:4:"ð—´";s:1:"g";s:4:"ð—µ";s:1:"h";s:4:"ð—¶";s:1:"i";s:4:"ð—·";s:1:"j";s:4:"ð—¸";s:1:"k";s:4:"ð—¹";s:1:"l";s:4:"ð—º";s:1:"m";s:4:"ð—»";s:1:"n";s:4:"ð—¼";s:1:"o";s:4:"ð—½";s:1:"p";s:4:"ð—¾";s:1:"q";s:4:"ð—¿";s:1:"r";s:4:"ð˜€";s:1:"s";s:4:"ð˜";s:1:"t";s:4:"ð˜‚";s:1:"u";s:4:"ð˜ƒ";s:1:"v";s:4:"ð˜„";s:1:"w";s:4:"ð˜…";s:1:"x";s:4:"ð˜†";s:1:"y";s:4:"ð˜‡";s:1:"z";s:4:"ð˜ˆ";s:1:"A";s:4:"ð˜‰";s:1:"B";s:4:"ð˜Š";s:1:"C";s:4:"ð˜‹";s:1:"D";s:4:"ð˜Œ";s:1:"E";s:4:"ð˜";s:1:"F";s:4:"ð˜Ž";s:1:"G";s:4:"ð˜";s:1:"H";s:4:"ð˜";s:1:"I";s:4:"ð˜‘";s:1:"J";s:4:"ð˜’";s:1:"K";s:4:"ð˜“";s:1:"L";s:4:"ð˜”";s:1:"M";s:4:"ð˜•";s:1:"N";s:4:"ð˜–";s:1:"O";s:4:"ð˜—";s:1:"P";s:4:"ð˜˜";s:1:"Q";s:4:"ð˜™";s:1:"R";s:4:"ð˜š";s:1:"S";s:4:"ð˜›";s:1:"T";s:4:"ð˜œ";s:1:"U";s:4:"ð˜";s:1:"V";s:4:"ð˜ž";s:1:"W";s:4:"ð˜Ÿ";s:1:"X";s:4:"ð˜ ";s:1:"Y";s:4:"ð˜¡";s:1:"Z";s:4:"ð˜¢";s:1:"a";s:4:"ð˜£";s:1:"b";s:4:"ð˜¤";s:1:"c";s:4:"ð˜¥";s:1:"d";s:4:"ð˜¦";s:1:"e";s:4:"ð˜§";s:1:"f";s:4:"ð˜¨";s:1:"g";s:4:"ð˜©";s:1:"h";s:4:"ð˜ª";s:1:"i";s:4:"ð˜«";s:1:"j";s:4:"ð˜¬";s:1:"k";s:4:"ð˜­";s:1:"l";s:4:"ð˜®";s:1:"m";s:4:"ð˜¯";s:1:"n";s:4:"ð˜°";s:1:"o";s:4:"ð˜±";s:1:"p";s:4:"ð˜²";s:1:"q";s:4:"ð˜³";s:1:"r";s:4:"ð˜´";s:1:"s";s:4:"ð˜µ";s:1:"t";s:4:"ð˜¶";s:1:"u";s:4:"ð˜·";s:1:"v";s:4:"ð˜¸";s:1:"w";s:4:"ð˜¹";s:1:"x";s:4:"ð˜º";s:1:"y";s:4:"ð˜»";s:1:"z";s:4:"ð˜¼";s:1:"A";s:4:"ð˜½";s:1:"B";s:4:"ð˜¾";s:1:"C";s:4:"ð˜¿";s:1:"D";s:4:"ð™€";s:1:"E";s:4:"ð™";s:1:"F";s:4:"ð™‚";s:1:"G";s:4:"ð™ƒ";s:1:"H";s:4:"ð™„";s:1:"I";s:4:"ð™…";s:1:"J";s:4:"ð™†";s:1:"K";s:4:"ð™‡";s:1:"L";s:4:"ð™ˆ";s:1:"M";s:4:"ð™‰";s:1:"N";s:4:"ð™Š";s:1:"O";s:4:"ð™‹";s:1:"P";s:4:"ð™Œ";s:1:"Q";s:4:"ð™";s:1:"R";s:4:"ð™Ž";s:1:"S";s:4:"ð™";s:1:"T";s:4:"ð™";s:1:"U";s:4:"ð™‘";s:1:"V";s:4:"ð™’";s:1:"W";s:4:"ð™“";s:1:"X";s:4:"ð™”";s:1:"Y";s:4:"ð™•";s:1:"Z";s:4:"ð™–";s:1:"a";s:4:"ð™—";s:1:"b";s:4:"ð™˜";s:1:"c";s:4:"ð™™";s:1:"d";s:4:"ð™š";s:1:"e";s:4:"ð™›";s:1:"f";s:4:"ð™œ";s:1:"g";s:4:"ð™";s:1:"h";s:4:"ð™ž";s:1:"i";s:4:"ð™Ÿ";s:1:"j";s:4:"ð™ ";s:1:"k";s:4:"ð™¡";s:1:"l";s:4:"ð™¢";s:1:"m";s:4:"ð™£";s:1:"n";s:4:"ð™¤";s:1:"o";s:4:"ð™¥";s:1:"p";s:4:"ð™¦";s:1:"q";s:4:"ð™§";s:1:"r";s:4:"ð™¨";s:1:"s";s:4:"ð™©";s:1:"t";s:4:"ð™ª";s:1:"u";s:4:"ð™«";s:1:"v";s:4:"ð™¬";s:1:"w";s:4:"ð™­";s:1:"x";s:4:"ð™®";s:1:"y";s:4:"ð™¯";s:1:"z";s:4:"ð™°";s:1:"A";s:4:"ð™±";s:1:"B";s:4:"ð™²";s:1:"C";s:4:"ð™³";s:1:"D";s:4:"ð™´";s:1:"E";s:4:"ð™µ";s:1:"F";s:4:"ð™¶";s:1:"G";s:4:"ð™·";s:1:"H";s:4:"ð™¸";s:1:"I";s:4:"ð™¹";s:1:"J";s:4:"ð™º";s:1:"K";s:4:"ð™»";s:1:"L";s:4:"ð™¼";s:1:"M";s:4:"ð™½";s:1:"N";s:4:"ð™¾";s:1:"O";s:4:"ð™¿";s:1:"P";s:4:"ðš€";s:1:"Q";s:4:"ðš";s:1:"R";s:4:"ðš‚";s:1:"S";s:4:"ðšƒ";s:1:"T";s:4:"ðš„";s:1:"U";s:4:"ðš…";s:1:"V";s:4:"ðš†";s:1:"W";s:4:"ðš‡";s:1:"X";s:4:"ðšˆ";s:1:"Y";s:4:"ðš‰";s:1:"Z";s:4:"ðšŠ";s:1:"a";s:4:"ðš‹";s:1:"b";s:4:"ðšŒ";s:1:"c";s:4:"ðš";s:1:"d";s:4:"ðšŽ";s:1:"e";s:4:"ðš";s:1:"f";s:4:"ðš";s:1:"g";s:4:"ðš‘";s:1:"h";s:4:"ðš’";s:1:"i";s:4:"ðš“";s:1:"j";s:4:"ðš”";s:1:"k";s:4:"ðš•";s:1:"l";s:4:"ðš–";s:1:"m";s:4:"ðš—";s:1:"n";s:4:"ðš˜";s:1:"o";s:4:"ðš™";s:1:"p";s:4:"ðšš";s:1:"q";s:4:"ðš›";s:1:"r";s:4:"ðšœ";s:1:"s";s:4:"ðš";s:1:"t";s:4:"ðšž";s:1:"u";s:4:"ðšŸ";s:1:"v";s:4:"ðš ";s:1:"w";s:4:"ðš¡";s:1:"x";s:4:"ðš¢";s:1:"y";s:4:"ðš£";s:1:"z";s:4:"ðš¤";s:2:"ı";s:4:"ðš¥";s:2:"È·";s:4:"ðš¨";s:2:"Α";s:4:"ðš©";s:2:"Î’";s:4:"ðšª";s:2:"Γ";s:4:"ðš«";s:2:"Δ";s:4:"ðš¬";s:2:"Ε";s:4:"ðš­";s:2:"Ζ";s:4:"ðš®";s:2:"Η";s:4:"ðš¯";s:2:"Θ";s:4:"ðš°";s:2:"Ι";s:4:"ðš±";s:2:"Κ";s:4:"ðš²";s:2:"Λ";s:4:"ðš³";s:2:"Μ";s:4:"ðš´";s:2:"Î";s:4:"ðšµ";s:2:"Ξ";s:4:"ðš¶";s:2:"Ο";s:4:"ðš·";s:2:"Π";s:4:"ðš¸";s:2:"Ρ";s:4:"ðš¹";s:2:"Θ";s:4:"ðšº";s:2:"Σ";s:4:"ðš»";s:2:"Τ";s:4:"ðš¼";s:2:"Î¥";s:4:"ðš½";s:2:"Φ";s:4:"ðš¾";s:2:"Χ";s:4:"ðš¿";s:2:"Ψ";s:4:"ð›€";s:2:"Ω";s:4:"ð›";s:3:"∇";s:4:"ð›‚";s:2:"α";s:4:"ð›ƒ";s:2:"β";s:4:"ð›„";s:2:"γ";s:4:"ð›…";s:2:"δ";s:4:"ð›†";s:2:"ε";s:4:"ð›‡";s:2:"ζ";s:4:"ð›ˆ";s:2:"η";s:4:"ð›‰";s:2:"θ";s:4:"ð›Š";s:2:"ι";s:4:"ð›‹";s:2:"κ";s:4:"ð›Œ";s:2:"λ";s:4:"ð›";s:2:"μ";s:4:"ð›Ž";s:2:"ν";s:4:"ð›";s:2:"ξ";s:4:"ð›";s:2:"ο";s:4:"ð›‘";s:2:"Ï€";s:4:"ð›’";s:2:"Ï";s:4:"ð›“";s:2:"Ï‚";s:4:"ð›”";s:2:"σ";s:4:"ð›•";s:2:"Ï„";s:4:"ð›–";s:2:"Ï…";s:4:"ð›—";s:2:"φ";s:4:"ð›˜";s:2:"χ";s:4:"ð›™";s:2:"ψ";s:4:"ð›š";s:2:"ω";s:4:"ð››";s:3:"∂";s:4:"ð›œ";s:2:"ε";s:4:"ð›";s:2:"θ";s:4:"ð›ž";s:2:"κ";s:4:"ð›Ÿ";s:2:"φ";s:4:"ð› ";s:2:"Ï";s:4:"ð›¡";s:2:"Ï€";s:4:"ð›¢";s:2:"Α";s:4:"ð›£";s:2:"Î’";s:4:"ð›¤";s:2:"Γ";s:4:"ð›¥";s:2:"Δ";s:4:"ð›¦";s:2:"Ε";s:4:"ð›§";s:2:"Ζ";s:4:"ð›¨";s:2:"Η";s:4:"ð›©";s:2:"Θ";s:4:"ð›ª";s:2:"Ι";s:4:"ð›«";s:2:"Κ";s:4:"ð›¬";s:2:"Λ";s:4:"ð›­";s:2:"Μ";s:4:"ð›®";s:2:"Î";s:4:"ð›¯";s:2:"Ξ";s:4:"ð›°";s:2:"Ο";s:4:"ð›±";s:2:"Π";s:4:"ð›²";s:2:"Ρ";s:4:"ð›³";s:2:"Θ";s:4:"ð›´";s:2:"Σ";s:4:"ð›µ";s:2:"Τ";s:4:"ð›¶";s:2:"Î¥";s:4:"ð›·";s:2:"Φ";s:4:"ð›¸";s:2:"Χ";s:4:"ð›¹";s:2:"Ψ";s:4:"ð›º";s:2:"Ω";s:4:"ð›»";s:3:"∇";s:4:"ð›¼";s:2:"α";s:4:"ð›½";s:2:"β";s:4:"ð›¾";s:2:"γ";s:4:"ð›¿";s:2:"δ";s:4:"ðœ€";s:2:"ε";s:4:"ðœ";s:2:"ζ";s:4:"ðœ‚";s:2:"η";s:4:"ðœƒ";s:2:"θ";s:4:"ðœ„";s:2:"ι";s:4:"ðœ…";s:2:"κ";s:4:"ðœ†";s:2:"λ";s:4:"ðœ‡";s:2:"μ";s:4:"ðœˆ";s:2:"ν";s:4:"ðœ‰";s:2:"ξ";s:4:"ðœŠ";s:2:"ο";s:4:"ðœ‹";s:2:"Ï€";s:4:"ðœŒ";s:2:"Ï";s:4:"ðœ";s:2:"Ï‚";s:4:"ðœŽ";s:2:"σ";s:4:"ðœ";s:2:"Ï„";s:4:"ðœ";s:2:"Ï…";s:4:"ðœ‘";s:2:"φ";s:4:"ðœ’";s:2:"χ";s:4:"ðœ“";s:2:"ψ";s:4:"ðœ”";s:2:"ω";s:4:"ðœ•";s:3:"∂";s:4:"ðœ–";s:2:"ε";s:4:"ðœ—";s:2:"θ";s:4:"ðœ˜";s:2:"κ";s:4:"ðœ™";s:2:"φ";s:4:"ðœš";s:2:"Ï";s:4:"ðœ›";s:2:"Ï€";s:4:"ðœœ";s:2:"Α";s:4:"ðœ";s:2:"Î’";s:4:"ðœž";s:2:"Γ";s:4:"ðœŸ";s:2:"Δ";s:4:"ðœ ";s:2:"Ε";s:4:"ðœ¡";s:2:"Ζ";s:4:"ðœ¢";s:2:"Η";s:4:"ðœ£";s:2:"Θ";s:4:"ðœ¤";s:2:"Ι";s:4:"ðœ¥";s:2:"Κ";s:4:"ðœ¦";s:2:"Λ";s:4:"ðœ§";s:2:"Μ";s:4:"ðœ¨";s:2:"Î";s:4:"ðœ©";s:2:"Ξ";s:4:"ðœª";s:2:"Ο";s:4:"ðœ«";s:2:"Π";s:4:"ðœ¬";s:2:"Ρ";s:4:"ðœ­";s:2:"Θ";s:4:"ðœ®";s:2:"Σ";s:4:"ðœ¯";s:2:"Τ";s:4:"ðœ°";s:2:"Î¥";s:4:"ðœ±";s:2:"Φ";s:4:"ðœ²";s:2:"Χ";s:4:"ðœ³";s:2:"Ψ";s:4:"ðœ´";s:2:"Ω";s:4:"ðœµ";s:3:"∇";s:4:"ðœ¶";s:2:"α";s:4:"ðœ·";s:2:"β";s:4:"ðœ¸";s:2:"γ";s:4:"ðœ¹";s:2:"δ";s:4:"ðœº";s:2:"ε";s:4:"ðœ»";s:2:"ζ";s:4:"ðœ¼";s:2:"η";s:4:"ðœ½";s:2:"θ";s:4:"ðœ¾";s:2:"ι";s:4:"ðœ¿";s:2:"κ";s:4:"ð€";s:2:"λ";s:4:"ð";s:2:"μ";s:4:"ð‚";s:2:"ν";s:4:"ðƒ";s:2:"ξ";s:4:"ð„";s:2:"ο";s:4:"ð…";s:2:"Ï€";s:4:"ð†";s:2:"Ï";s:4:"ð‡";s:2:"Ï‚";s:4:"ðˆ";s:2:"σ";s:4:"ð‰";s:2:"Ï„";s:4:"ðŠ";s:2:"Ï…";s:4:"ð‹";s:2:"φ";s:4:"ðŒ";s:2:"χ";s:4:"ð";s:2:"ψ";s:4:"ðŽ";s:2:"ω";s:4:"ð";s:3:"∂";s:4:"ð";s:2:"ε";s:4:"ð‘";s:2:"θ";s:4:"ð’";s:2:"κ";s:4:"ð“";s:2:"φ";s:4:"ð”";s:2:"Ï";s:4:"ð•";s:2:"Ï€";s:4:"ð–";s:2:"Α";s:4:"ð—";s:2:"Î’";s:4:"ð˜";s:2:"Γ";s:4:"ð™";s:2:"Δ";s:4:"ðš";s:2:"Ε";s:4:"ð›";s:2:"Ζ";s:4:"ðœ";s:2:"Η";s:4:"ð";s:2:"Θ";s:4:"ðž";s:2:"Ι";s:4:"ðŸ";s:2:"Κ";s:4:"ð ";s:2:"Λ";s:4:"ð¡";s:2:"Μ";s:4:"ð¢";s:2:"Î";s:4:"ð£";s:2:"Ξ";s:4:"ð¤";s:2:"Ο";s:4:"ð¥";s:2:"Π";s:4:"ð¦";s:2:"Ρ";s:4:"ð§";s:2:"Θ";s:4:"ð¨";s:2:"Σ";s:4:"ð©";s:2:"Τ";s:4:"ðª";s:2:"Î¥";s:4:"ð«";s:2:"Φ";s:4:"ð¬";s:2:"Χ";s:4:"ð­";s:2:"Ψ";s:4:"ð®";s:2:"Ω";s:4:"ð¯";s:3:"∇";s:4:"ð°";s:2:"α";s:4:"ð±";s:2:"β";s:4:"ð²";s:2:"γ";s:4:"ð³";s:2:"δ";s:4:"ð´";s:2:"ε";s:4:"ðµ";s:2:"ζ";s:4:"ð¶";s:2:"η";s:4:"ð·";s:2:"θ";s:4:"ð¸";s:2:"ι";s:4:"ð¹";s:2:"κ";s:4:"ðº";s:2:"λ";s:4:"ð»";s:2:"μ";s:4:"ð¼";s:2:"ν";s:4:"ð½";s:2:"ξ";s:4:"ð¾";s:2:"ο";s:4:"ð¿";s:2:"Ï€";s:4:"ðž€";s:2:"Ï";s:4:"ðž";s:2:"Ï‚";s:4:"ðž‚";s:2:"σ";s:4:"ðžƒ";s:2:"Ï„";s:4:"ðž„";s:2:"Ï…";s:4:"ðž…";s:2:"φ";s:4:"ðž†";s:2:"χ";s:4:"ðž‡";s:2:"ψ";s:4:"ðžˆ";s:2:"ω";s:4:"ðž‰";s:3:"∂";s:4:"ðžŠ";s:2:"ε";s:4:"ðž‹";s:2:"θ";s:4:"ðžŒ";s:2:"κ";s:4:"ðž";s:2:"φ";s:4:"ðžŽ";s:2:"Ï";s:4:"ðž";s:2:"Ï€";s:4:"ðž";s:2:"Α";s:4:"ðž‘";s:2:"Î’";s:4:"ðž’";s:2:"Γ";s:4:"ðž“";s:2:"Δ";s:4:"ðž”";s:2:"Ε";s:4:"ðž•";s:2:"Ζ";s:4:"ðž–";s:2:"Η";s:4:"ðž—";s:2:"Θ";s:4:"ðž˜";s:2:"Ι";s:4:"ðž™";s:2:"Κ";s:4:"ðžš";s:2:"Λ";s:4:"ðž›";s:2:"Μ";s:4:"ðžœ";s:2:"Î";s:4:"ðž";s:2:"Ξ";s:4:"ðžž";s:2:"Ο";s:4:"ðžŸ";s:2:"Π";s:4:"ðž ";s:2:"Ρ";s:4:"ðž¡";s:2:"Θ";s:4:"ðž¢";s:2:"Σ";s:4:"ðž£";s:2:"Τ";s:4:"ðž¤";s:2:"Î¥";s:4:"ðž¥";s:2:"Φ";s:4:"ðž¦";s:2:"Χ";s:4:"ðž§";s:2:"Ψ";s:4:"ðž¨";s:2:"Ω";s:4:"ðž©";s:3:"∇";s:4:"ðžª";s:2:"α";s:4:"ðž«";s:2:"β";s:4:"ðž¬";s:2:"γ";s:4:"ðž­";s:2:"δ";s:4:"ðž®";s:2:"ε";s:4:"ðž¯";s:2:"ζ";s:4:"ðž°";s:2:"η";s:4:"ðž±";s:2:"θ";s:4:"ðž²";s:2:"ι";s:4:"ðž³";s:2:"κ";s:4:"ðž´";s:2:"λ";s:4:"ðžµ";s:2:"μ";s:4:"ðž¶";s:2:"ν";s:4:"ðž·";s:2:"ξ";s:4:"ðž¸";s:2:"ο";s:4:"ðž¹";s:2:"Ï€";s:4:"ðžº";s:2:"Ï";s:4:"ðž»";s:2:"Ï‚";s:4:"ðž¼";s:2:"σ";s:4:"ðž½";s:2:"Ï„";s:4:"ðž¾";s:2:"Ï…";s:4:"ðž¿";s:2:"φ";s:4:"ðŸ€";s:2:"χ";s:4:"ðŸ";s:2:"ψ";s:4:"ðŸ‚";s:2:"ω";s:4:"ðŸƒ";s:3:"∂";s:4:"ðŸ„";s:2:"ε";s:4:"ðŸ…";s:2:"θ";s:4:"ðŸ†";s:2:"κ";s:4:"ðŸ‡";s:2:"φ";s:4:"ðŸˆ";s:2:"Ï";s:4:"ðŸ‰";s:2:"Ï€";s:4:"ðŸŠ";s:2:"Ïœ";s:4:"ðŸ‹";s:2:"Ï";s:4:"ðŸŽ";s:1:"0";s:4:"ðŸ";s:1:"1";s:4:"ðŸ";s:1:"2";s:4:"ðŸ‘";s:1:"3";s:4:"ðŸ’";s:1:"4";s:4:"ðŸ“";s:1:"5";s:4:"ðŸ”";s:1:"6";s:4:"ðŸ•";s:1:"7";s:4:"ðŸ–";s:1:"8";s:4:"ðŸ—";s:1:"9";s:4:"ðŸ˜";s:1:"0";s:4:"ðŸ™";s:1:"1";s:4:"ðŸš";s:1:"2";s:4:"ðŸ›";s:1:"3";s:4:"ðŸœ";s:1:"4";s:4:"ðŸ";s:1:"5";s:4:"ðŸž";s:1:"6";s:4:"ðŸŸ";s:1:"7";s:4:"ðŸ ";s:1:"8";s:4:"ðŸ¡";s:1:"9";s:4:"ðŸ¢";s:1:"0";s:4:"ðŸ£";s:1:"1";s:4:"ðŸ¤";s:1:"2";s:4:"ðŸ¥";s:1:"3";s:4:"ðŸ¦";s:1:"4";s:4:"ðŸ§";s:1:"5";s:4:"ðŸ¨";s:1:"6";s:4:"ðŸ©";s:1:"7";s:4:"ðŸª";s:1:"8";s:4:"ðŸ«";s:1:"9";s:4:"ðŸ¬";s:1:"0";s:4:"ðŸ­";s:1:"1";s:4:"ðŸ®";s:1:"2";s:4:"ðŸ¯";s:1:"3";s:4:"ðŸ°";s:1:"4";s:4:"ðŸ±";s:1:"5";s:4:"ðŸ²";s:1:"6";s:4:"ðŸ³";s:1:"7";s:4:"ðŸ´";s:1:"8";s:4:"ðŸµ";s:1:"9";s:4:"ðŸ¶";s:1:"0";s:4:"ðŸ·";s:1:"1";s:4:"ðŸ¸";s:1:"2";s:4:"ðŸ¹";s:1:"3";s:4:"ðŸº";s:1:"4";s:4:"ðŸ»";s:1:"5";s:4:"ðŸ¼";s:1:"6";s:4:"ðŸ½";s:1:"7";s:4:"ðŸ¾";s:1:"8";s:4:"ðŸ¿";s:1:"9";s:4:"𞸀";s:2:"ا";s:4:"ðž¸";s:2:"ب";s:4:"𞸂";s:2:"ج";s:4:"𞸃";s:2:"د";s:4:"𞸅";s:2:"Ùˆ";s:4:"𞸆";s:2:"ز";s:4:"𞸇";s:2:"Ø­";s:4:"𞸈";s:2:"Ø·";s:4:"𞸉";s:2:"ÙŠ";s:4:"𞸊";s:2:"Ùƒ";s:4:"𞸋";s:2:"Ù„";s:4:"𞸌";s:2:"Ù…";s:4:"ðž¸";s:2:"Ù†";s:4:"𞸎";s:2:"س";s:4:"ðž¸";s:2:"ع";s:4:"ðž¸";s:2:"Ù";s:4:"𞸑";s:2:"ص";s:4:"𞸒";s:2:"Ù‚";s:4:"𞸓";s:2:"ر";s:4:"𞸔";s:2:"Ø´";s:4:"𞸕";s:2:"ت";s:4:"𞸖";s:2:"Ø«";s:4:"𞸗";s:2:"Ø®";s:4:"𞸘";s:2:"ذ";s:4:"𞸙";s:2:"ض";s:4:"𞸚";s:2:"ظ";s:4:"𞸛";s:2:"غ";s:4:"𞸜";s:2:"Ù®";s:4:"ðž¸";s:2:"Úº";s:4:"𞸞";s:2:"Ú¡";s:4:"𞸟";s:2:"Ù¯";s:4:"𞸡";s:2:"ب";s:4:"𞸢";s:2:"ج";s:4:"𞸤";s:2:"Ù‡";s:4:"𞸧";s:2:"Ø­";s:4:"𞸩";s:2:"ÙŠ";s:4:"𞸪";s:2:"Ùƒ";s:4:"𞸫";s:2:"Ù„";s:4:"𞸬";s:2:"Ù…";s:4:"𞸭";s:2:"Ù†";s:4:"𞸮";s:2:"س";s:4:"𞸯";s:2:"ع";s:4:"𞸰";s:2:"Ù";s:4:"𞸱";s:2:"ص";s:4:"𞸲";s:2:"Ù‚";s:4:"𞸴";s:2:"Ø´";s:4:"𞸵";s:2:"ت";s:4:"𞸶";s:2:"Ø«";s:4:"𞸷";s:2:"Ø®";s:4:"𞸹";s:2:"ض";s:4:"𞸻";s:2:"غ";s:4:"𞹂";s:2:"ج";s:4:"𞹇";s:2:"Ø­";s:4:"𞹉";s:2:"ÙŠ";s:4:"𞹋";s:2:"Ù„";s:4:"ðž¹";s:2:"Ù†";s:4:"𞹎";s:2:"س";s:4:"ðž¹";s:2:"ع";s:4:"𞹑";s:2:"ص";s:4:"ðž¹’";s:2:"Ù‚";s:4:"ðž¹”";s:2:"Ø´";s:4:"ðž¹—";s:2:"Ø®";s:4:"ðž¹™";s:2:"ض";s:4:"ðž¹›";s:2:"غ";s:4:"ðž¹";s:2:"Úº";s:4:"𞹟";s:2:"Ù¯";s:4:"𞹡";s:2:"ب";s:4:"ðž¹¢";s:2:"ج";s:4:"𞹤";s:2:"Ù‡";s:4:"ðž¹§";s:2:"Ø­";s:4:"𞹨";s:2:"Ø·";s:4:"𞹩";s:2:"ÙŠ";s:4:"𞹪";s:2:"Ùƒ";s:4:"𞹬";s:2:"Ù…";s:4:"ðž¹­";s:2:"Ù†";s:4:"ðž¹®";s:2:"س";s:4:"𞹯";s:2:"ع";s:4:"ðž¹°";s:2:"Ù";s:4:"ðž¹±";s:2:"ص";s:4:"ðž¹²";s:2:"Ù‚";s:4:"ðž¹´";s:2:"Ø´";s:4:"ðž¹µ";s:2:"ت";s:4:"ðž¹¶";s:2:"Ø«";s:4:"ðž¹·";s:2:"Ø®";s:4:"ðž¹¹";s:2:"ض";s:4:"𞹺";s:2:"ظ";s:4:"ðž¹»";s:2:"غ";s:4:"ðž¹¼";s:2:"Ù®";s:4:"ðž¹¾";s:2:"Ú¡";s:4:"𞺀";s:2:"ا";s:4:"ðžº";s:2:"ب";s:4:"𞺂";s:2:"ج";s:4:"𞺃";s:2:"د";s:4:"𞺄";s:2:"Ù‡";s:4:"𞺅";s:2:"Ùˆ";s:4:"𞺆";s:2:"ز";s:4:"𞺇";s:2:"Ø­";s:4:"𞺈";s:2:"Ø·";s:4:"𞺉";s:2:"ÙŠ";s:4:"𞺋";s:2:"Ù„";s:4:"𞺌";s:2:"Ù…";s:4:"ðžº";s:2:"Ù†";s:4:"𞺎";s:2:"س";s:4:"ðžº";s:2:"ع";s:4:"ðžº";s:2:"Ù";s:4:"𞺑";s:2:"ص";s:4:"𞺒";s:2:"Ù‚";s:4:"𞺓";s:2:"ر";s:4:"𞺔";s:2:"Ø´";s:4:"𞺕";s:2:"ت";s:4:"𞺖";s:2:"Ø«";s:4:"𞺗";s:2:"Ø®";s:4:"𞺘";s:2:"ذ";s:4:"𞺙";s:2:"ض";s:4:"𞺚";s:2:"ظ";s:4:"𞺛";s:2:"غ";s:4:"𞺡";s:2:"ب";s:4:"𞺢";s:2:"ج";s:4:"𞺣";s:2:"د";s:4:"𞺥";s:2:"Ùˆ";s:4:"𞺦";s:2:"ز";s:4:"𞺧";s:2:"Ø­";s:4:"𞺨";s:2:"Ø·";s:4:"𞺩";s:2:"ÙŠ";s:4:"𞺫";s:2:"Ù„";s:4:"𞺬";s:2:"Ù…";s:4:"𞺭";s:2:"Ù†";s:4:"𞺮";s:2:"س";s:4:"𞺯";s:2:"ع";s:4:"𞺰";s:2:"Ù";s:4:"𞺱";s:2:"ص";s:4:"𞺲";s:2:"Ù‚";s:4:"𞺳";s:2:"ر";s:4:"𞺴";s:2:"Ø´";s:4:"𞺵";s:2:"ت";s:4:"𞺶";s:2:"Ø«";s:4:"𞺷";s:2:"Ø®";s:4:"𞺸";s:2:"ذ";s:4:"𞺹";s:2:"ض";s:4:"𞺺";s:2:"ظ";s:4:"𞺻";s:2:"غ";s:4:"🄀";s:2:"0.";s:4:"ðŸ„";s:2:"0,";s:4:"🄂";s:2:"1,";s:4:"🄃";s:2:"2,";s:4:"🄄";s:2:"3,";s:4:"🄅";s:2:"4,";s:4:"🄆";s:2:"5,";s:4:"🄇";s:2:"6,";s:4:"🄈";s:2:"7,";s:4:"🄉";s:2:"8,";s:4:"🄊";s:2:"9,";s:4:"ðŸ„";s:3:"(A)";s:4:"🄑";s:3:"(B)";s:4:"🄒";s:3:"(C)";s:4:"🄓";s:3:"(D)";s:4:"🄔";s:3:"(E)";s:4:"🄕";s:3:"(F)";s:4:"🄖";s:3:"(G)";s:4:"🄗";s:3:"(H)";s:4:"🄘";s:3:"(I)";s:4:"🄙";s:3:"(J)";s:4:"🄚";s:3:"(K)";s:4:"🄛";s:3:"(L)";s:4:"🄜";s:3:"(M)";s:4:"ðŸ„";s:3:"(N)";s:4:"🄞";s:3:"(O)";s:4:"🄟";s:3:"(P)";s:4:"🄠";s:3:"(Q)";s:4:"🄡";s:3:"(R)";s:4:"🄢";s:3:"(S)";s:4:"🄣";s:3:"(T)";s:4:"🄤";s:3:"(U)";s:4:"🄥";s:3:"(V)";s:4:"🄦";s:3:"(W)";s:4:"🄧";s:3:"(X)";s:4:"🄨";s:3:"(Y)";s:4:"🄩";s:3:"(Z)";s:4:"🄪";s:7:"〔S〕";s:4:"🄫";s:1:"C";s:4:"🄬";s:1:"R";s:4:"🄭";s:2:"CD";s:4:"🄮";s:2:"WZ";s:4:"🄰";s:1:"A";s:4:"🄱";s:1:"B";s:4:"🄲";s:1:"C";s:4:"🄳";s:1:"D";s:4:"🄴";s:1:"E";s:4:"🄵";s:1:"F";s:4:"🄶";s:1:"G";s:4:"🄷";s:1:"H";s:4:"🄸";s:1:"I";s:4:"🄹";s:1:"J";s:4:"🄺";s:1:"K";s:4:"🄻";s:1:"L";s:4:"🄼";s:1:"M";s:4:"🄽";s:1:"N";s:4:"🄾";s:1:"O";s:4:"🄿";s:1:"P";s:4:"🅀";s:1:"Q";s:4:"ðŸ…";s:1:"R";s:4:"🅂";s:1:"S";s:4:"🅃";s:1:"T";s:4:"🅄";s:1:"U";s:4:"🅅";s:1:"V";s:4:"🅆";s:1:"W";s:4:"🅇";s:1:"X";s:4:"🅈";s:1:"Y";s:4:"🅉";s:1:"Z";s:4:"🅊";s:2:"HV";s:4:"🅋";s:2:"MV";s:4:"🅌";s:2:"SD";s:4:"ðŸ…";s:2:"SS";s:4:"🅎";s:3:"PPV";s:4:"ðŸ…";s:2:"WC";s:4:"🅪";s:2:"MC";s:4:"🅫";s:2:"MD";s:4:"ðŸ†";s:2:"DJ";s:4:"🈀";s:6:"ã»ã‹";s:4:"ðŸˆ";s:6:"ココ";s:4:"🈂";s:3:"サ";s:4:"ðŸˆ";s:3:"手";s:4:"🈑";s:3:"å­—";s:4:"🈒";s:3:"åŒ";s:4:"🈓";s:6:"デ";s:4:"🈔";s:3:"二";s:4:"🈕";s:3:"多";s:4:"🈖";s:3:"è§£";s:4:"🈗";s:3:"天";s:4:"🈘";s:3:"交";s:4:"🈙";s:3:"映";s:4:"🈚";s:3:"ç„¡";s:4:"🈛";s:3:"æ–™";s:4:"🈜";s:3:"å‰";s:4:"ðŸˆ";s:3:"後";s:4:"🈞";s:3:"å†";s:4:"🈟";s:3:"æ–°";s:4:"🈠";s:3:"åˆ";s:4:"🈡";s:3:"終";s:4:"🈢";s:3:"生";s:4:"🈣";s:3:"販";s:4:"🈤";s:3:"声";s:4:"🈥";s:3:"å¹";s:4:"🈦";s:3:"æ¼”";s:4:"🈧";s:3:"投";s:4:"🈨";s:3:"æ•";s:4:"🈩";s:3:"一";s:4:"🈪";s:3:"三";s:4:"🈫";s:3:"éŠ";s:4:"🈬";s:3:"å·¦";s:4:"🈭";s:3:"中";s:4:"🈮";s:3:"å³";s:4:"🈯";s:3:"指";s:4:"🈰";s:3:"èµ°";s:4:"🈱";s:3:"打";s:4:"🈲";s:3:"ç¦";s:4:"🈳";s:3:"空";s:4:"🈴";s:3:"åˆ";s:4:"🈵";s:3:"満";s:4:"🈶";s:3:"有";s:4:"🈷";s:3:"月";s:4:"🈸";s:3:"申";s:4:"🈹";s:3:"割";s:4:"🈺";s:3:"å–¶";s:4:"🉀";s:9:"〔本〕";s:4:"ðŸ‰";s:9:"〔三〕";s:4:"🉂";s:9:"〔二〕";s:4:"🉃";s:9:"〔安〕";s:4:"🉄";s:9:"〔点〕";s:4:"🉅";s:9:"〔打〕";s:4:"🉆";s:9:"〔盗〕";s:4:"🉇";s:9:"〔å‹ã€•";s:4:"🉈";s:9:"〔敗〕";s:4:"ðŸ‰";s:3:"å¾—";s:4:"🉑";s:3:"å¯";}
    \ No newline at end of file
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/unidata/lowerCase.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/unidata/lowerCase.ser
    new file mode 100644
    index 0000000..bfe7c4a
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/unidata/lowerCase.ser
    @@ -0,0 +1 @@
    +a:1043:{s:1:"A";s:1:"a";s:1:"B";s:1:"b";s:1:"C";s:1:"c";s:1:"D";s:1:"d";s:1:"E";s:1:"e";s:1:"F";s:1:"f";s:1:"G";s:1:"g";s:1:"H";s:1:"h";s:1:"I";s:1:"i";s:1:"J";s:1:"j";s:1:"K";s:1:"k";s:1:"L";s:1:"l";s:1:"M";s:1:"m";s:1:"N";s:1:"n";s:1:"O";s:1:"o";s:1:"P";s:1:"p";s:1:"Q";s:1:"q";s:1:"R";s:1:"r";s:1:"S";s:1:"s";s:1:"T";s:1:"t";s:1:"U";s:1:"u";s:1:"V";s:1:"v";s:1:"W";s:1:"w";s:1:"X";s:1:"x";s:1:"Y";s:1:"y";s:1:"Z";s:1:"z";s:2:"À";s:2:"à";s:2:"Ã";s:2:"á";s:2:"Â";s:2:"â";s:2:"Ã";s:2:"ã";s:2:"Ä";s:2:"ä";s:2:"Ã…";s:2:"Ã¥";s:2:"Æ";s:2:"æ";s:2:"Ç";s:2:"ç";s:2:"È";s:2:"è";s:2:"É";s:2:"é";s:2:"Ê";s:2:"ê";s:2:"Ë";s:2:"ë";s:2:"ÃŒ";s:2:"ì";s:2:"Ã";s:2:"í";s:2:"ÃŽ";s:2:"î";s:2:"Ã";s:2:"ï";s:2:"Ã";s:2:"ð";s:2:"Ñ";s:2:"ñ";s:2:"Ã’";s:2:"ò";s:2:"Ó";s:2:"ó";s:2:"Ô";s:2:"ô";s:2:"Õ";s:2:"õ";s:2:"Ö";s:2:"ö";s:2:"Ø";s:2:"ø";s:2:"Ù";s:2:"ù";s:2:"Ú";s:2:"ú";s:2:"Û";s:2:"û";s:2:"Ü";s:2:"ü";s:2:"Ã";s:2:"ý";s:2:"Þ";s:2:"þ";s:2:"Ä€";s:2:"Ä";s:2:"Ä‚";s:2:"ă";s:2:"Ä„";s:2:"Ä…";s:2:"Ć";s:2:"ć";s:2:"Ĉ";s:2:"ĉ";s:2:"ÄŠ";s:2:"Ä‹";s:2:"ÄŒ";s:2:"Ä";s:2:"ÄŽ";s:2:"Ä";s:2:"Ä";s:2:"Ä‘";s:2:"Ä’";s:2:"Ä“";s:2:"Ä”";s:2:"Ä•";s:2:"Ä–";s:2:"Ä—";s:2:"Ę";s:2:"Ä™";s:2:"Äš";s:2:"Ä›";s:2:"Äœ";s:2:"Ä";s:2:"Äž";s:2:"ÄŸ";s:2:"Ä ";s:2:"Ä¡";s:2:"Ä¢";s:2:"Ä£";s:2:"Ĥ";s:2:"Ä¥";s:2:"Ħ";s:2:"ħ";s:2:"Ĩ";s:2:"Ä©";s:2:"Ī";s:2:"Ä«";s:2:"Ĭ";s:2:"Ä­";s:2:"Ä®";s:2:"į";s:2:"İ";s:1:"i";s:2:"IJ";s:2:"ij";s:2:"Ä´";s:2:"ĵ";s:2:"Ķ";s:2:"Ä·";s:2:"Ĺ";s:2:"ĺ";s:2:"Ä»";s:2:"ļ";s:2:"Ľ";s:2:"ľ";s:2:"Ä¿";s:2:"Å€";s:2:"Å";s:2:"Å‚";s:2:"Ń";s:2:"Å„";s:2:"Å…";s:2:"ņ";s:2:"Ň";s:2:"ň";s:2:"ÅŠ";s:2:"Å‹";s:2:"ÅŒ";s:2:"Å";s:2:"ÅŽ";s:2:"Å";s:2:"Å";s:2:"Å‘";s:2:"Å’";s:2:"Å“";s:2:"Å”";s:2:"Å•";s:2:"Å–";s:2:"Å—";s:2:"Ř";s:2:"Å™";s:2:"Åš";s:2:"Å›";s:2:"Åœ";s:2:"Å";s:2:"Åž";s:2:"ÅŸ";s:2:"Å ";s:2:"Å¡";s:2:"Å¢";s:2:"Å£";s:2:"Ť";s:2:"Å¥";s:2:"Ŧ";s:2:"ŧ";s:2:"Ũ";s:2:"Å©";s:2:"Ū";s:2:"Å«";s:2:"Ŭ";s:2:"Å­";s:2:"Å®";s:2:"ů";s:2:"Ű";s:2:"ű";s:2:"Ų";s:2:"ų";s:2:"Å´";s:2:"ŵ";s:2:"Ŷ";s:2:"Å·";s:2:"Ÿ";s:2:"ÿ";s:2:"Ź";s:2:"ź";s:2:"Å»";s:2:"ż";s:2:"Ž";s:2:"ž";s:2:"Æ";s:2:"É“";s:2:"Æ‚";s:2:"ƃ";s:2:"Æ„";s:2:"Æ…";s:2:"Ɔ";s:2:"É”";s:2:"Ƈ";s:2:"ƈ";s:2:"Ɖ";s:2:"É–";s:2:"ÆŠ";s:2:"É—";s:2:"Æ‹";s:2:"ÆŒ";s:2:"ÆŽ";s:2:"Ç";s:2:"Æ";s:2:"É™";s:2:"Æ";s:2:"É›";s:2:"Æ‘";s:2:"Æ’";s:2:"Æ“";s:2:"É ";s:2:"Æ”";s:2:"É£";s:2:"Æ–";s:2:"É©";s:2:"Æ—";s:2:"ɨ";s:2:"Ƙ";s:2:"Æ™";s:2:"Æœ";s:2:"ɯ";s:2:"Æ";s:2:"ɲ";s:2:"ÆŸ";s:2:"ɵ";s:2:"Æ ";s:2:"Æ¡";s:2:"Æ¢";s:2:"Æ£";s:2:"Ƥ";s:2:"Æ¥";s:2:"Ʀ";s:2:"Ê€";s:2:"Ƨ";s:2:"ƨ";s:2:"Æ©";s:2:"ʃ";s:2:"Ƭ";s:2:"Æ­";s:2:"Æ®";s:2:"ʈ";s:2:"Ư";s:2:"ư";s:2:"Ʊ";s:2:"ÊŠ";s:2:"Ʋ";s:2:"Ê‹";s:2:"Ƴ";s:2:"Æ´";s:2:"Ƶ";s:2:"ƶ";s:2:"Æ·";s:2:"Ê’";s:2:"Ƹ";s:2:"ƹ";s:2:"Ƽ";s:2:"ƽ";s:2:"Ç„";s:2:"dž";s:2:"Ç…";s:2:"dž";s:2:"LJ";s:2:"lj";s:2:"Lj";s:2:"lj";s:2:"ÇŠ";s:2:"ÇŒ";s:2:"Ç‹";s:2:"ÇŒ";s:2:"Ç";s:2:"ÇŽ";s:2:"Ç";s:2:"Ç";s:2:"Ç‘";s:2:"Ç’";s:2:"Ç“";s:2:"Ç”";s:2:"Ç•";s:2:"Ç–";s:2:"Ç—";s:2:"ǘ";s:2:"Ç™";s:2:"Çš";s:2:"Ç›";s:2:"Çœ";s:2:"Çž";s:2:"ÇŸ";s:2:"Ç ";s:2:"Ç¡";s:2:"Ç¢";s:2:"Ç£";s:2:"Ǥ";s:2:"Ç¥";s:2:"Ǧ";s:2:"ǧ";s:2:"Ǩ";s:2:"Ç©";s:2:"Ǫ";s:2:"Ç«";s:2:"Ǭ";s:2:"Ç­";s:2:"Ç®";s:2:"ǯ";s:2:"DZ";s:2:"dz";s:2:"Dz";s:2:"dz";s:2:"Ç´";s:2:"ǵ";s:2:"Ƕ";s:2:"Æ•";s:2:"Ç·";s:2:"Æ¿";s:2:"Ǹ";s:2:"ǹ";s:2:"Ǻ";s:2:"Ç»";s:2:"Ǽ";s:2:"ǽ";s:2:"Ǿ";s:2:"Ç¿";s:2:"È€";s:2:"È";s:2:"È‚";s:2:"ȃ";s:2:"È„";s:2:"È…";s:2:"Ȇ";s:2:"ȇ";s:2:"Ȉ";s:2:"ȉ";s:2:"ÈŠ";s:2:"È‹";s:2:"ÈŒ";s:2:"È";s:2:"ÈŽ";s:2:"È";s:2:"È";s:2:"È‘";s:2:"È’";s:2:"È“";s:2:"È”";s:2:"È•";s:2:"È–";s:2:"È—";s:2:"Ș";s:2:"È™";s:2:"Èš";s:2:"È›";s:2:"Èœ";s:2:"È";s:2:"Èž";s:2:"ÈŸ";s:2:"È ";s:2:"Æž";s:2:"È¢";s:2:"È£";s:2:"Ȥ";s:2:"È¥";s:2:"Ȧ";s:2:"ȧ";s:2:"Ȩ";s:2:"È©";s:2:"Ȫ";s:2:"È«";s:2:"Ȭ";s:2:"È­";s:2:"È®";s:2:"ȯ";s:2:"Ȱ";s:2:"ȱ";s:2:"Ȳ";s:2:"ȳ";s:2:"Ⱥ";s:3:"â±¥";s:2:"È»";s:2:"ȼ";s:2:"Ƚ";s:2:"Æš";s:2:"Ⱦ";s:3:"ⱦ";s:2:"É";s:2:"É‚";s:2:"Ƀ";s:2:"Æ€";s:2:"É„";s:2:"ʉ";s:2:"É…";s:2:"ÊŒ";s:2:"Ɇ";s:2:"ɇ";s:2:"Ɉ";s:2:"ɉ";s:2:"ÉŠ";s:2:"É‹";s:2:"ÉŒ";s:2:"É";s:2:"ÉŽ";s:2:"É";s:2:"Ͱ";s:2:"ͱ";s:2:"Ͳ";s:2:"ͳ";s:2:"Ͷ";s:2:"Í·";s:2:"Ά";s:2:"ά";s:2:"Έ";s:2:"έ";s:2:"Ή";s:2:"ή";s:2:"Ί";s:2:"ί";s:2:"ÎŒ";s:2:"ÏŒ";s:2:"ÎŽ";s:2:"Ï";s:2:"Î";s:2:"ÏŽ";s:2:"Α";s:2:"α";s:2:"Î’";s:2:"β";s:2:"Γ";s:2:"γ";s:2:"Δ";s:2:"δ";s:2:"Ε";s:2:"ε";s:2:"Ζ";s:2:"ζ";s:2:"Η";s:2:"η";s:2:"Θ";s:2:"θ";s:2:"Ι";s:2:"ι";s:2:"Κ";s:2:"κ";s:2:"Λ";s:2:"λ";s:2:"Μ";s:2:"μ";s:2:"Î";s:2:"ν";s:2:"Ξ";s:2:"ξ";s:2:"Ο";s:2:"ο";s:2:"Π";s:2:"Ï€";s:2:"Ρ";s:2:"Ï";s:2:"Σ";s:2:"σ";s:2:"Τ";s:2:"Ï„";s:2:"Î¥";s:2:"Ï…";s:2:"Φ";s:2:"φ";s:2:"Χ";s:2:"χ";s:2:"Ψ";s:2:"ψ";s:2:"Ω";s:2:"ω";s:2:"Ϊ";s:2:"ÏŠ";s:2:"Ϋ";s:2:"Ï‹";s:2:"Ï";s:2:"Ï—";s:2:"Ϙ";s:2:"Ï™";s:2:"Ïš";s:2:"Ï›";s:2:"Ïœ";s:2:"Ï";s:2:"Ïž";s:2:"ÏŸ";s:2:"Ï ";s:2:"Ï¡";s:2:"Ï¢";s:2:"Ï£";s:2:"Ϥ";s:2:"Ï¥";s:2:"Ϧ";s:2:"ϧ";s:2:"Ϩ";s:2:"Ï©";s:2:"Ϫ";s:2:"Ï«";s:2:"Ϭ";s:2:"Ï­";s:2:"Ï®";s:2:"ϯ";s:2:"Ï´";s:2:"θ";s:2:"Ï·";s:2:"ϸ";s:2:"Ϲ";s:2:"ϲ";s:2:"Ϻ";s:2:"Ï»";s:2:"Ͻ";s:2:"Í»";s:2:"Ͼ";s:2:"ͼ";s:2:"Ï¿";s:2:"ͽ";s:2:"Ѐ";s:2:"Ñ";s:2:"Ð";s:2:"Ñ‘";s:2:"Ђ";s:2:"Ñ’";s:2:"Ѓ";s:2:"Ñ“";s:2:"Є";s:2:"Ñ”";s:2:"Ð…";s:2:"Ñ•";s:2:"І";s:2:"Ñ–";s:2:"Ї";s:2:"Ñ—";s:2:"Ј";s:2:"ј";s:2:"Љ";s:2:"Ñ™";s:2:"Њ";s:2:"Ñš";s:2:"Ћ";s:2:"Ñ›";s:2:"ÐŒ";s:2:"Ñœ";s:2:"Ð";s:2:"Ñ";s:2:"ÐŽ";s:2:"Ñž";s:2:"Ð";s:2:"ÑŸ";s:2:"Ð";s:2:"а";s:2:"Б";s:2:"б";s:2:"Ð’";s:2:"в";s:2:"Г";s:2:"г";s:2:"Д";s:2:"д";s:2:"Е";s:2:"е";s:2:"Ж";s:2:"ж";s:2:"З";s:2:"з";s:2:"И";s:2:"и";s:2:"Й";s:2:"й";s:2:"К";s:2:"к";s:2:"Л";s:2:"л";s:2:"М";s:2:"м";s:2:"Ð";s:2:"н";s:2:"О";s:2:"о";s:2:"П";s:2:"п";s:2:"Р";s:2:"Ñ€";s:2:"С";s:2:"Ñ";s:2:"Т";s:2:"Ñ‚";s:2:"У";s:2:"у";s:2:"Ф";s:2:"Ñ„";s:2:"Ð¥";s:2:"Ñ…";s:2:"Ц";s:2:"ц";s:2:"Ч";s:2:"ч";s:2:"Ш";s:2:"ш";s:2:"Щ";s:2:"щ";s:2:"Ъ";s:2:"ÑŠ";s:2:"Ы";s:2:"Ñ‹";s:2:"Ь";s:2:"ÑŒ";s:2:"Э";s:2:"Ñ";s:2:"Ю";s:2:"ÑŽ";s:2:"Я";s:2:"Ñ";s:2:"Ñ ";s:2:"Ñ¡";s:2:"Ñ¢";s:2:"Ñ£";s:2:"Ѥ";s:2:"Ñ¥";s:2:"Ѧ";s:2:"ѧ";s:2:"Ѩ";s:2:"Ñ©";s:2:"Ѫ";s:2:"Ñ«";s:2:"Ѭ";s:2:"Ñ­";s:2:"Ñ®";s:2:"ѯ";s:2:"Ѱ";s:2:"ѱ";s:2:"Ѳ";s:2:"ѳ";s:2:"Ñ´";s:2:"ѵ";s:2:"Ѷ";s:2:"Ñ·";s:2:"Ѹ";s:2:"ѹ";s:2:"Ѻ";s:2:"Ñ»";s:2:"Ѽ";s:2:"ѽ";s:2:"Ѿ";s:2:"Ñ¿";s:2:"Ò€";s:2:"Ò";s:2:"ÒŠ";s:2:"Ò‹";s:2:"ÒŒ";s:2:"Ò";s:2:"ÒŽ";s:2:"Ò";s:2:"Ò";s:2:"Ò‘";s:2:"Ò’";s:2:"Ò“";s:2:"Ò”";s:2:"Ò•";s:2:"Ò–";s:2:"Ò—";s:2:"Ò˜";s:2:"Ò™";s:2:"Òš";s:2:"Ò›";s:2:"Òœ";s:2:"Ò";s:2:"Òž";s:2:"ÒŸ";s:2:"Ò ";s:2:"Ò¡";s:2:"Ò¢";s:2:"Ò£";s:2:"Ò¤";s:2:"Ò¥";s:2:"Ò¦";s:2:"Ò§";s:2:"Ò¨";s:2:"Ò©";s:2:"Òª";s:2:"Ò«";s:2:"Ò¬";s:2:"Ò­";s:2:"Ò®";s:2:"Ò¯";s:2:"Ò°";s:2:"Ò±";s:2:"Ò²";s:2:"Ò³";s:2:"Ò´";s:2:"Òµ";s:2:"Ò¶";s:2:"Ò·";s:2:"Ò¸";s:2:"Ò¹";s:2:"Òº";s:2:"Ò»";s:2:"Ò¼";s:2:"Ò½";s:2:"Ò¾";s:2:"Ò¿";s:2:"Ó€";s:2:"Ó";s:2:"Ó";s:2:"Ó‚";s:2:"Óƒ";s:2:"Ó„";s:2:"Ó…";s:2:"Ó†";s:2:"Ó‡";s:2:"Óˆ";s:2:"Ó‰";s:2:"ÓŠ";s:2:"Ó‹";s:2:"ÓŒ";s:2:"Ó";s:2:"ÓŽ";s:2:"Ó";s:2:"Ó‘";s:2:"Ó’";s:2:"Ó“";s:2:"Ó”";s:2:"Ó•";s:2:"Ó–";s:2:"Ó—";s:2:"Ó˜";s:2:"Ó™";s:2:"Óš";s:2:"Ó›";s:2:"Óœ";s:2:"Ó";s:2:"Óž";s:2:"ÓŸ";s:2:"Ó ";s:2:"Ó¡";s:2:"Ó¢";s:2:"Ó£";s:2:"Ó¤";s:2:"Ó¥";s:2:"Ó¦";s:2:"Ó§";s:2:"Ó¨";s:2:"Ó©";s:2:"Óª";s:2:"Ó«";s:2:"Ó¬";s:2:"Ó­";s:2:"Ó®";s:2:"Ó¯";s:2:"Ó°";s:2:"Ó±";s:2:"Ó²";s:2:"Ó³";s:2:"Ó´";s:2:"Óµ";s:2:"Ó¶";s:2:"Ó·";s:2:"Ó¸";s:2:"Ó¹";s:2:"Óº";s:2:"Ó»";s:2:"Ó¼";s:2:"Ó½";s:2:"Ó¾";s:2:"Ó¿";s:2:"Ô€";s:2:"Ô";s:2:"Ô‚";s:2:"Ôƒ";s:2:"Ô„";s:2:"Ô…";s:2:"Ô†";s:2:"Ô‡";s:2:"Ôˆ";s:2:"Ô‰";s:2:"ÔŠ";s:2:"Ô‹";s:2:"ÔŒ";s:2:"Ô";s:2:"ÔŽ";s:2:"Ô";s:2:"Ô";s:2:"Ô‘";s:2:"Ô’";s:2:"Ô“";s:2:"Ô”";s:2:"Ô•";s:2:"Ô–";s:2:"Ô—";s:2:"Ô˜";s:2:"Ô™";s:2:"Ôš";s:2:"Ô›";s:2:"Ôœ";s:2:"Ô";s:2:"Ôž";s:2:"ÔŸ";s:2:"Ô ";s:2:"Ô¡";s:2:"Ô¢";s:2:"Ô£";s:2:"Ô¤";s:2:"Ô¥";s:2:"Ô¦";s:2:"Ô§";s:2:"Ô±";s:2:"Õ¡";s:2:"Ô²";s:2:"Õ¢";s:2:"Ô³";s:2:"Õ£";s:2:"Ô´";s:2:"Õ¤";s:2:"Ôµ";s:2:"Õ¥";s:2:"Ô¶";s:2:"Õ¦";s:2:"Ô·";s:2:"Õ§";s:2:"Ô¸";s:2:"Õ¨";s:2:"Ô¹";s:2:"Õ©";s:2:"Ôº";s:2:"Õª";s:2:"Ô»";s:2:"Õ«";s:2:"Ô¼";s:2:"Õ¬";s:2:"Ô½";s:2:"Õ­";s:2:"Ô¾";s:2:"Õ®";s:2:"Ô¿";s:2:"Õ¯";s:2:"Õ€";s:2:"Õ°";s:2:"Õ";s:2:"Õ±";s:2:"Õ‚";s:2:"Õ²";s:2:"Õƒ";s:2:"Õ³";s:2:"Õ„";s:2:"Õ´";s:2:"Õ…";s:2:"Õµ";s:2:"Õ†";s:2:"Õ¶";s:2:"Õ‡";s:2:"Õ·";s:2:"Õˆ";s:2:"Õ¸";s:2:"Õ‰";s:2:"Õ¹";s:2:"ÕŠ";s:2:"Õº";s:2:"Õ‹";s:2:"Õ»";s:2:"ÕŒ";s:2:"Õ¼";s:2:"Õ";s:2:"Õ½";s:2:"ÕŽ";s:2:"Õ¾";s:2:"Õ";s:2:"Õ¿";s:2:"Õ";s:2:"Ö€";s:2:"Õ‘";s:2:"Ö";s:2:"Õ’";s:2:"Ö‚";s:2:"Õ“";s:2:"Öƒ";s:2:"Õ”";s:2:"Ö„";s:2:"Õ•";s:2:"Ö…";s:2:"Õ–";s:2:"Ö†";s:3:"á‚ ";s:3:"â´€";s:3:"á‚¡";s:3:"â´";s:3:"á‚¢";s:3:"â´‚";s:3:"á‚£";s:3:"â´ƒ";s:3:"Ⴄ";s:3:"â´„";s:3:"á‚¥";s:3:"â´…";s:3:"Ⴆ";s:3:"â´†";s:3:"á‚§";s:3:"â´‡";s:3:"Ⴈ";s:3:"â´ˆ";s:3:"á‚©";s:3:"â´‰";s:3:"Ⴊ";s:3:"â´Š";s:3:"á‚«";s:3:"â´‹";s:3:"Ⴌ";s:3:"â´Œ";s:3:"á‚­";s:3:"â´";s:3:"á‚®";s:3:"â´Ž";s:3:"Ⴏ";s:3:"â´";s:3:"á‚°";s:3:"â´";s:3:"Ⴑ";s:3:"â´‘";s:3:"Ⴒ";s:3:"â´’";s:3:"Ⴓ";s:3:"â´“";s:3:"á‚´";s:3:"â´”";s:3:"Ⴕ";s:3:"â´•";s:3:"á‚¶";s:3:"â´–";s:3:"á‚·";s:3:"â´—";s:3:"Ⴘ";s:3:"â´˜";s:3:"Ⴙ";s:3:"â´™";s:3:"Ⴚ";s:3:"â´š";s:3:"á‚»";s:3:"â´›";s:3:"Ⴜ";s:3:"â´œ";s:3:"Ⴝ";s:3:"â´";s:3:"Ⴞ";s:3:"â´ž";s:3:"á‚¿";s:3:"â´Ÿ";s:3:"Ⴠ";s:3:"â´ ";s:3:"áƒ";s:3:"â´¡";s:3:"Ⴢ";s:3:"â´¢";s:3:"Ⴣ";s:3:"â´£";s:3:"Ⴤ";s:3:"â´¤";s:3:"Ⴥ";s:3:"â´¥";s:3:"Ⴧ";s:3:"â´§";s:3:"áƒ";s:3:"â´­";s:3:"Ḁ";s:3:"á¸";s:3:"Ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"ḇ";s:3:"Ḉ";s:3:"ḉ";s:3:"Ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"á¸";s:3:"Ḏ";s:3:"á¸";s:3:"á¸";s:3:"ḑ";s:3:"Ḓ";s:3:"ḓ";s:3:"Ḕ";s:3:"ḕ";s:3:"Ḗ";s:3:"ḗ";s:3:"Ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"ḛ";s:3:"Ḝ";s:3:"á¸";s:3:"Ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"ḭ";s:3:"Ḯ";s:3:"ḯ";s:3:"Ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"ḷ";s:3:"Ḹ";s:3:"ḹ";s:3:"Ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"ḿ";s:3:"á¹€";s:3:"á¹";s:3:"Ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"á¹…";s:3:"Ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"ṋ";s:3:"Ṍ";s:3:"á¹";s:3:"Ṏ";s:3:"á¹";s:3:"á¹";s:3:"ṑ";s:3:"á¹’";s:3:"ṓ";s:3:"á¹”";s:3:"ṕ";s:3:"á¹–";s:3:"á¹—";s:3:"Ṙ";s:3:"á¹™";s:3:"Ṛ";s:3:"á¹›";s:3:"Ṝ";s:3:"á¹";s:3:"Ṟ";s:3:"ṟ";s:3:"á¹ ";s:3:"ṡ";s:3:"á¹¢";s:3:"á¹£";s:3:"Ṥ";s:3:"á¹¥";s:3:"Ṧ";s:3:"á¹§";s:3:"Ṩ";s:3:"ṩ";s:3:"Ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"á¹­";s:3:"á¹®";s:3:"ṯ";s:3:"á¹°";s:3:"á¹±";s:3:"á¹²";s:3:"á¹³";s:3:"á¹´";s:3:"á¹µ";s:3:"á¹¶";s:3:"á¹·";s:3:"Ṹ";s:3:"á¹¹";s:3:"Ṻ";s:3:"á¹»";s:3:"á¹¼";s:3:"á¹½";s:3:"á¹¾";s:3:"ṿ";s:3:"Ẁ";s:3:"áº";s:3:"Ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"áº";s:3:"Ẏ";s:3:"áº";s:3:"áº";s:3:"ẑ";s:3:"Ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẞ";s:2:"ß";s:3:"Ạ";s:3:"ạ";s:3:"Ả";s:3:"ả";s:3:"Ấ";s:3:"ấ";s:3:"Ầ";s:3:"ầ";s:3:"Ẩ";s:3:"ẩ";s:3:"Ẫ";s:3:"ẫ";s:3:"Ậ";s:3:"ậ";s:3:"Ắ";s:3:"ắ";s:3:"Ằ";s:3:"ằ";s:3:"Ẳ";s:3:"ẳ";s:3:"Ẵ";s:3:"ẵ";s:3:"Ặ";s:3:"ặ";s:3:"Ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"ẽ";s:3:"Ế";s:3:"ế";s:3:"Ề";s:3:"á»";s:3:"Ể";s:3:"ể";s:3:"Ễ";s:3:"á»…";s:3:"Ệ";s:3:"ệ";s:3:"Ỉ";s:3:"ỉ";s:3:"Ị";s:3:"ị";s:3:"Ọ";s:3:"á»";s:3:"Ỏ";s:3:"á»";s:3:"á»";s:3:"ố";s:3:"á»’";s:3:"ồ";s:3:"á»”";s:3:"ổ";s:3:"á»–";s:3:"á»—";s:3:"Ộ";s:3:"á»™";s:3:"Ớ";s:3:"á»›";s:3:"Ờ";s:3:"á»";s:3:"Ở";s:3:"ở";s:3:"á» ";s:3:"ỡ";s:3:"Ợ";s:3:"ợ";s:3:"Ụ";s:3:"ụ";s:3:"Ủ";s:3:"á»§";s:3:"Ứ";s:3:"ứ";s:3:"Ừ";s:3:"ừ";s:3:"Ử";s:3:"á»­";s:3:"á»®";s:3:"ữ";s:3:"á»°";s:3:"á»±";s:3:"Ỳ";s:3:"ỳ";s:3:"á»´";s:3:"ỵ";s:3:"á»¶";s:3:"á»·";s:3:"Ỹ";s:3:"ỹ";s:3:"Ỻ";s:3:"á»»";s:3:"Ỽ";s:3:"ỽ";s:3:"Ỿ";s:3:"ỿ";s:3:"Ἀ";s:3:"á¼€";s:3:"Ἁ";s:3:"á¼";s:3:"Ἂ";s:3:"ἂ";s:3:"Ἃ";s:3:"ἃ";s:3:"Ἄ";s:3:"ἄ";s:3:"á¼";s:3:"á¼…";s:3:"Ἆ";s:3:"ἆ";s:3:"á¼";s:3:"ἇ";s:3:"Ἐ";s:3:"á¼";s:3:"á¼™";s:3:"ἑ";s:3:"Ἒ";s:3:"á¼’";s:3:"á¼›";s:3:"ἓ";s:3:"Ἔ";s:3:"á¼”";s:3:"á¼";s:3:"ἕ";s:3:"Ἠ";s:3:"á¼ ";s:3:"Ἡ";s:3:"ἡ";s:3:"Ἢ";s:3:"á¼¢";s:3:"Ἣ";s:3:"á¼£";s:3:"Ἤ";s:3:"ἤ";s:3:"á¼­";s:3:"á¼¥";s:3:"á¼®";s:3:"ἦ";s:3:"Ἧ";s:3:"á¼§";s:3:"Ἰ";s:3:"á¼°";s:3:"á¼¹";s:3:"á¼±";s:3:"Ἲ";s:3:"á¼²";s:3:"á¼»";s:3:"á¼³";s:3:"á¼¼";s:3:"á¼´";s:3:"á¼½";s:3:"á¼µ";s:3:"á¼¾";s:3:"á¼¶";s:3:"Ἷ";s:3:"á¼·";s:3:"Ὀ";s:3:"á½€";s:3:"Ὁ";s:3:"á½";s:3:"Ὂ";s:3:"ὂ";s:3:"Ὃ";s:3:"ὃ";s:3:"Ὄ";s:3:"ὄ";s:3:"á½";s:3:"á½…";s:3:"á½™";s:3:"ὑ";s:3:"á½›";s:3:"ὓ";s:3:"á½";s:3:"ὕ";s:3:"Ὗ";s:3:"á½—";s:3:"Ὠ";s:3:"á½ ";s:3:"Ὡ";s:3:"ὡ";s:3:"Ὢ";s:3:"á½¢";s:3:"Ὣ";s:3:"á½£";s:3:"Ὤ";s:3:"ὤ";s:3:"á½­";s:3:"á½¥";s:3:"á½®";s:3:"ὦ";s:3:"Ὧ";s:3:"á½§";s:3:"ᾈ";s:3:"á¾€";s:3:"ᾉ";s:3:"á¾";s:3:"ᾊ";s:3:"ᾂ";s:3:"ᾋ";s:3:"ᾃ";s:3:"ᾌ";s:3:"ᾄ";s:3:"á¾";s:3:"á¾…";s:3:"ᾎ";s:3:"ᾆ";s:3:"á¾";s:3:"ᾇ";s:3:"ᾘ";s:3:"á¾";s:3:"á¾™";s:3:"ᾑ";s:3:"ᾚ";s:3:"á¾’";s:3:"á¾›";s:3:"ᾓ";s:3:"ᾜ";s:3:"á¾”";s:3:"á¾";s:3:"ᾕ";s:3:"ᾞ";s:3:"á¾–";s:3:"ᾟ";s:3:"á¾—";s:3:"ᾨ";s:3:"á¾ ";s:3:"ᾩ";s:3:"ᾡ";s:3:"ᾪ";s:3:"á¾¢";s:3:"ᾫ";s:3:"á¾£";s:3:"ᾬ";s:3:"ᾤ";s:3:"á¾­";s:3:"á¾¥";s:3:"á¾®";s:3:"ᾦ";s:3:"ᾯ";s:3:"á¾§";s:3:"Ᾰ";s:3:"á¾°";s:3:"á¾¹";s:3:"á¾±";s:3:"Ὰ";s:3:"á½°";s:3:"á¾»";s:3:"á½±";s:3:"á¾¼";s:3:"á¾³";s:3:"Ὲ";s:3:"á½²";s:3:"Έ";s:3:"á½³";s:3:"Ὴ";s:3:"á½´";s:3:"á¿‹";s:3:"á½µ";s:3:"ῌ";s:3:"ῃ";s:3:"Ῐ";s:3:"á¿";s:3:"á¿™";s:3:"á¿‘";s:3:"Ὶ";s:3:"á½¶";s:3:"á¿›";s:3:"á½·";s:3:"Ῠ";s:3:"á¿ ";s:3:"á¿©";s:3:"á¿¡";s:3:"Ὺ";s:3:"ὺ";s:3:"á¿«";s:3:"á½»";s:3:"Ῥ";s:3:"á¿¥";s:3:"Ὸ";s:3:"ὸ";s:3:"Ό";s:3:"á½¹";s:3:"Ὼ";s:3:"á½¼";s:3:"á¿»";s:3:"á½½";s:3:"ῼ";s:3:"ῳ";s:3:"Ω";s:2:"ω";s:3:"K";s:1:"k";s:3:"â„«";s:2:"Ã¥";s:3:"Ⅎ";s:3:"â…Ž";s:3:"â… ";s:3:"â…°";s:3:"â…¡";s:3:"â…±";s:3:"â…¢";s:3:"â…²";s:3:"â…£";s:3:"â…³";s:3:"â…¤";s:3:"â…´";s:3:"â…¥";s:3:"â…µ";s:3:"â…¦";s:3:"â…¶";s:3:"â…§";s:3:"â…·";s:3:"â…¨";s:3:"â…¸";s:3:"â…©";s:3:"â…¹";s:3:"â…ª";s:3:"â…º";s:3:"â…«";s:3:"â…»";s:3:"â…¬";s:3:"â…¼";s:3:"â…­";s:3:"â…½";s:3:"â…®";s:3:"â…¾";s:3:"â…¯";s:3:"â…¿";s:3:"Ↄ";s:3:"ↄ";s:3:"â’¶";s:3:"â“";s:3:"â’·";s:3:"â“‘";s:3:"â’¸";s:3:"â“’";s:3:"â’¹";s:3:"â““";s:3:"â’º";s:3:"â“”";s:3:"â’»";s:3:"â“•";s:3:"â’¼";s:3:"â“–";s:3:"â’½";s:3:"â“—";s:3:"â’¾";s:3:"ⓘ";s:3:"â’¿";s:3:"â“™";s:3:"â“€";s:3:"ⓚ";s:3:"â“";s:3:"â“›";s:3:"â“‚";s:3:"ⓜ";s:3:"Ⓝ";s:3:"â“";s:3:"â“„";s:3:"ⓞ";s:3:"â“…";s:3:"ⓟ";s:3:"Ⓠ";s:3:"â“ ";s:3:"Ⓡ";s:3:"â“¡";s:3:"Ⓢ";s:3:"â“¢";s:3:"Ⓣ";s:3:"â“£";s:3:"Ⓤ";s:3:"ⓤ";s:3:"â“‹";s:3:"â“¥";s:3:"Ⓦ";s:3:"ⓦ";s:3:"â“";s:3:"â“§";s:3:"Ⓨ";s:3:"ⓨ";s:3:"â“";s:3:"â“©";s:3:"â°€";s:3:"â°°";s:3:"â°";s:3:"â°±";s:3:"â°‚";s:3:"â°²";s:3:"â°ƒ";s:3:"â°³";s:3:"â°„";s:3:"â°´";s:3:"â°…";s:3:"â°µ";s:3:"â°†";s:3:"â°¶";s:3:"â°‡";s:3:"â°·";s:3:"â°ˆ";s:3:"â°¸";s:3:"â°‰";s:3:"â°¹";s:3:"â°Š";s:3:"â°º";s:3:"â°‹";s:3:"â°»";s:3:"â°Œ";s:3:"â°¼";s:3:"â°";s:3:"â°½";s:3:"â°Ž";s:3:"â°¾";s:3:"â°";s:3:"â°¿";s:3:"â°";s:3:"â±€";s:3:"â°‘";s:3:"â±";s:3:"â°’";s:3:"ⱂ";s:3:"â°“";s:3:"ⱃ";s:3:"â°”";s:3:"ⱄ";s:3:"â°•";s:3:"â±…";s:3:"â°–";s:3:"ⱆ";s:3:"â°—";s:3:"ⱇ";s:3:"â°˜";s:3:"ⱈ";s:3:"â°™";s:3:"ⱉ";s:3:"â°š";s:3:"ⱊ";s:3:"â°›";s:3:"ⱋ";s:3:"â°œ";s:3:"ⱌ";s:3:"â°";s:3:"â±";s:3:"â°ž";s:3:"ⱎ";s:3:"â°Ÿ";s:3:"â±";s:3:"â° ";s:3:"â±";s:3:"â°¡";s:3:"ⱑ";s:3:"â°¢";s:3:"â±’";s:3:"â°£";s:3:"ⱓ";s:3:"â°¤";s:3:"â±”";s:3:"â°¥";s:3:"ⱕ";s:3:"â°¦";s:3:"â±–";s:3:"â°§";s:3:"â±—";s:3:"â°¨";s:3:"ⱘ";s:3:"â°©";s:3:"â±™";s:3:"â°ª";s:3:"ⱚ";s:3:"â°«";s:3:"â±›";s:3:"â°¬";s:3:"ⱜ";s:3:"â°­";s:3:"â±";s:3:"â°®";s:3:"ⱞ";s:3:"â± ";s:3:"ⱡ";s:3:"â±¢";s:2:"É«";s:3:"â±£";s:3:"áµ½";s:3:"Ɽ";s:2:"ɽ";s:3:"â±§";s:3:"ⱨ";s:3:"Ⱪ";s:3:"ⱪ";s:3:"Ⱬ";s:3:"ⱬ";s:3:"â±­";s:2:"É‘";s:3:"â±®";s:2:"ɱ";s:3:"Ɐ";s:2:"É";s:3:"â±°";s:2:"É’";s:3:"â±²";s:3:"â±³";s:3:"â±µ";s:3:"â±¶";s:3:"â±¾";s:2:"È¿";s:3:"Ɀ";s:2:"É€";s:3:"â²€";s:3:"â²";s:3:"Ⲃ";s:3:"ⲃ";s:3:"Ⲅ";s:3:"â²…";s:3:"Ⲇ";s:3:"ⲇ";s:3:"Ⲉ";s:3:"ⲉ";s:3:"Ⲋ";s:3:"ⲋ";s:3:"Ⲍ";s:3:"â²";s:3:"Ⲏ";s:3:"â²";s:3:"â²";s:3:"ⲑ";s:3:"â²’";s:3:"ⲓ";s:3:"â²”";s:3:"ⲕ";s:3:"â²–";s:3:"â²—";s:3:"Ⲙ";s:3:"â²™";s:3:"Ⲛ";s:3:"â²›";s:3:"Ⲝ";s:3:"â²";s:3:"Ⲟ";s:3:"ⲟ";s:3:"â² ";s:3:"ⲡ";s:3:"â²¢";s:3:"â²£";s:3:"Ⲥ";s:3:"â²¥";s:3:"Ⲧ";s:3:"â²§";s:3:"Ⲩ";s:3:"ⲩ";s:3:"Ⲫ";s:3:"ⲫ";s:3:"Ⲭ";s:3:"â²­";s:3:"â²®";s:3:"ⲯ";s:3:"â²°";s:3:"â²±";s:3:"â²²";s:3:"â²³";s:3:"â²´";s:3:"â²µ";s:3:"â²¶";s:3:"â²·";s:3:"Ⲹ";s:3:"â²¹";s:3:"Ⲻ";s:3:"â²»";s:3:"â²¼";s:3:"â²½";s:3:"â²¾";s:3:"ⲿ";s:3:"â³€";s:3:"â³";s:3:"Ⳃ";s:3:"ⳃ";s:3:"Ⳅ";s:3:"â³…";s:3:"Ⳇ";s:3:"ⳇ";s:3:"Ⳉ";s:3:"ⳉ";s:3:"Ⳋ";s:3:"ⳋ";s:3:"Ⳍ";s:3:"â³";s:3:"Ⳏ";s:3:"â³";s:3:"â³";s:3:"ⳑ";s:3:"â³’";s:3:"ⳓ";s:3:"â³”";s:3:"ⳕ";s:3:"â³–";s:3:"â³—";s:3:"Ⳙ";s:3:"â³™";s:3:"Ⳛ";s:3:"â³›";s:3:"Ⳝ";s:3:"â³";s:3:"Ⳟ";s:3:"ⳟ";s:3:"â³ ";s:3:"ⳡ";s:3:"â³¢";s:3:"â³£";s:3:"Ⳬ";s:3:"ⳬ";s:3:"â³­";s:3:"â³®";s:3:"â³²";s:3:"â³³";s:3:"Ꙁ";s:3:"ê™";s:3:"Ꙃ";s:3:"ꙃ";s:3:"Ꙅ";s:3:"ê™…";s:3:"Ꙇ";s:3:"ꙇ";s:3:"Ꙉ";s:3:"ꙉ";s:3:"Ꙋ";s:3:"ꙋ";s:3:"Ꙍ";s:3:"ê™";s:3:"Ꙏ";s:3:"ê™";s:3:"ê™";s:3:"ꙑ";s:3:"ê™’";s:3:"ꙓ";s:3:"ê™”";s:3:"ꙕ";s:3:"ê™–";s:3:"ê™—";s:3:"Ꙙ";s:3:"ê™™";s:3:"Ꙛ";s:3:"ê™›";s:3:"Ꙝ";s:3:"ê™";s:3:"Ꙟ";s:3:"ꙟ";s:3:"ê™ ";s:3:"ꙡ";s:3:"Ꙣ";s:3:"ꙣ";s:3:"Ꙥ";s:3:"ꙥ";s:3:"Ꙧ";s:3:"ê™§";s:3:"Ꙩ";s:3:"ꙩ";s:3:"Ꙫ";s:3:"ꙫ";s:3:"Ꙭ";s:3:"ê™­";s:3:"Ꚁ";s:3:"êš";s:3:"êš‚";s:3:"ꚃ";s:3:"êš„";s:3:"êš…";s:3:"Ꚇ";s:3:"ꚇ";s:3:"Ꚉ";s:3:"ꚉ";s:3:"Ꚋ";s:3:"êš‹";s:3:"Ꚍ";s:3:"êš";s:3:"Ꚏ";s:3:"êš";s:3:"êš";s:3:"êš‘";s:3:"êš’";s:3:"êš“";s:3:"êš”";s:3:"êš•";s:3:"êš–";s:3:"êš—";s:3:"Ꜣ";s:3:"ꜣ";s:3:"Ꜥ";s:3:"ꜥ";s:3:"Ꜧ";s:3:"ꜧ";s:3:"Ꜩ";s:3:"ꜩ";s:3:"Ꜫ";s:3:"ꜫ";s:3:"Ꜭ";s:3:"ꜭ";s:3:"Ꜯ";s:3:"ꜯ";s:3:"Ꜳ";s:3:"ꜳ";s:3:"Ꜵ";s:3:"ꜵ";s:3:"Ꜷ";s:3:"ꜷ";s:3:"Ꜹ";s:3:"ꜹ";s:3:"Ꜻ";s:3:"ꜻ";s:3:"Ꜽ";s:3:"ꜽ";s:3:"Ꜿ";s:3:"ꜿ";s:3:"ê€";s:3:"ê";s:3:"ê‚";s:3:"êƒ";s:3:"ê„";s:3:"ê…";s:3:"ê†";s:3:"ê‡";s:3:"êˆ";s:3:"ê‰";s:3:"êŠ";s:3:"ê‹";s:3:"êŒ";s:3:"ê";s:3:"êŽ";s:3:"ê";s:3:"ê";s:3:"ê‘";s:3:"ê’";s:3:"ê“";s:3:"ê”";s:3:"ê•";s:3:"ê–";s:3:"ê—";s:3:"ê˜";s:3:"ê™";s:3:"êš";s:3:"ê›";s:3:"êœ";s:3:"ê";s:3:"êž";s:3:"êŸ";s:3:"ê ";s:3:"ê¡";s:3:"ê¢";s:3:"ê£";s:3:"ê¤";s:3:"ê¥";s:3:"ê¦";s:3:"ê§";s:3:"ê¨";s:3:"ê©";s:3:"êª";s:3:"ê«";s:3:"ê¬";s:3:"ê­";s:3:"ê®";s:3:"ê¯";s:3:"ê¹";s:3:"êº";s:3:"ê»";s:3:"ê¼";s:3:"ê½";s:3:"áµ¹";s:3:"ê¾";s:3:"ê¿";s:3:"Ꞁ";s:3:"êž";s:3:"êž‚";s:3:"ꞃ";s:3:"êž„";s:3:"êž…";s:3:"Ꞇ";s:3:"ꞇ";s:3:"êž‹";s:3:"ꞌ";s:3:"êž";s:2:"É¥";s:3:"êž";s:3:"êž‘";s:3:"êž’";s:3:"êž“";s:3:"êž ";s:3:"êž¡";s:3:"Ꞣ";s:3:"ꞣ";s:3:"Ꞥ";s:3:"ꞥ";s:3:"Ꞧ";s:3:"êž§";s:3:"Ꞩ";s:3:"êž©";s:3:"Ɦ";s:2:"ɦ";s:3:"A";s:3:"ï½";s:3:"ï¼¢";s:3:"b";s:3:"ï¼£";s:3:"c";s:3:"D";s:3:"d";s:3:"ï¼¥";s:3:"ï½…";s:3:"F";s:3:"f";s:3:"ï¼§";s:3:"g";s:3:"H";s:3:"h";s:3:"I";s:3:"i";s:3:"J";s:3:"j";s:3:"K";s:3:"k";s:3:"L";s:3:"l";s:3:"ï¼­";s:3:"ï½";s:3:"ï¼®";s:3:"n";s:3:"O";s:3:"ï½";s:3:"ï¼°";s:3:"ï½";s:3:"ï¼±";s:3:"q";s:3:"ï¼²";s:3:"ï½’";s:3:"ï¼³";s:3:"s";s:3:"ï¼´";s:3:"ï½”";s:3:"ï¼µ";s:3:"u";s:3:"ï¼¶";s:3:"ï½–";s:3:"ï¼·";s:3:"ï½—";s:3:"X";s:3:"x";s:3:"ï¼¹";s:3:"ï½™";s:3:"Z";s:3:"z";s:4:"ð€";s:4:"ð¨";s:4:"ð";s:4:"ð©";s:4:"ð‚";s:4:"ðª";s:4:"ðƒ";s:4:"ð«";s:4:"ð„";s:4:"ð¬";s:4:"ð…";s:4:"ð­";s:4:"ð†";s:4:"ð®";s:4:"ð‡";s:4:"ð¯";s:4:"ðˆ";s:4:"ð°";s:4:"ð‰";s:4:"ð±";s:4:"ðŠ";s:4:"ð²";s:4:"ð‹";s:4:"ð³";s:4:"ðŒ";s:4:"ð´";s:4:"ð";s:4:"ðµ";s:4:"ðŽ";s:4:"ð¶";s:4:"ð";s:4:"ð·";s:4:"ð";s:4:"ð¸";s:4:"ð‘";s:4:"ð¹";s:4:"ð’";s:4:"ðº";s:4:"ð“";s:4:"ð»";s:4:"ð”";s:4:"ð¼";s:4:"ð•";s:4:"ð½";s:4:"ð–";s:4:"ð¾";s:4:"ð—";s:4:"ð¿";s:4:"ð˜";s:4:"ð‘€";s:4:"ð™";s:4:"ð‘";s:4:"ðš";s:4:"ð‘‚";s:4:"ð›";s:4:"ð‘ƒ";s:4:"ðœ";s:4:"ð‘„";s:4:"ð";s:4:"ð‘…";s:4:"ðž";s:4:"ð‘†";s:4:"ðŸ";s:4:"ð‘‡";s:4:"ð ";s:4:"ð‘ˆ";s:4:"ð¡";s:4:"ð‘‰";s:4:"ð¢";s:4:"ð‘Š";s:4:"ð£";s:4:"ð‘‹";s:4:"ð¤";s:4:"ð‘Œ";s:4:"ð¥";s:4:"ð‘";s:4:"ð¦";s:4:"ð‘Ž";s:4:"ð§";s:4:"ð‘";}
    \ No newline at end of file
    diff --git a/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/unidata/upperCase.ser b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/unidata/upperCase.ser
    new file mode 100644
    index 0000000..a3182d4
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/PHP/Shim/unidata/upperCase.ser
    @@ -0,0 +1 @@
    +a:1051:{s:1:"a";s:1:"A";s:1:"b";s:1:"B";s:1:"c";s:1:"C";s:1:"d";s:1:"D";s:1:"e";s:1:"E";s:1:"f";s:1:"F";s:1:"g";s:1:"G";s:1:"h";s:1:"H";s:1:"i";s:1:"I";s:1:"j";s:1:"J";s:1:"k";s:1:"K";s:1:"l";s:1:"L";s:1:"m";s:1:"M";s:1:"n";s:1:"N";s:1:"o";s:1:"O";s:1:"p";s:1:"P";s:1:"q";s:1:"Q";s:1:"r";s:1:"R";s:1:"s";s:1:"S";s:1:"t";s:1:"T";s:1:"u";s:1:"U";s:1:"v";s:1:"V";s:1:"w";s:1:"W";s:1:"x";s:1:"X";s:1:"y";s:1:"Y";s:1:"z";s:1:"Z";s:2:"µ";s:2:"Μ";s:2:"à";s:2:"À";s:2:"á";s:2:"Ã";s:2:"â";s:2:"Â";s:2:"ã";s:2:"Ã";s:2:"ä";s:2:"Ä";s:2:"Ã¥";s:2:"Ã…";s:2:"æ";s:2:"Æ";s:2:"ç";s:2:"Ç";s:2:"è";s:2:"È";s:2:"é";s:2:"É";s:2:"ê";s:2:"Ê";s:2:"ë";s:2:"Ë";s:2:"ì";s:2:"ÃŒ";s:2:"í";s:2:"Ã";s:2:"î";s:2:"ÃŽ";s:2:"ï";s:2:"Ã";s:2:"ð";s:2:"Ã";s:2:"ñ";s:2:"Ñ";s:2:"ò";s:2:"Ã’";s:2:"ó";s:2:"Ó";s:2:"ô";s:2:"Ô";s:2:"õ";s:2:"Õ";s:2:"ö";s:2:"Ö";s:2:"ø";s:2:"Ø";s:2:"ù";s:2:"Ù";s:2:"ú";s:2:"Ú";s:2:"û";s:2:"Û";s:2:"ü";s:2:"Ü";s:2:"ý";s:2:"Ã";s:2:"þ";s:2:"Þ";s:2:"ÿ";s:2:"Ÿ";s:2:"Ä";s:2:"Ä€";s:2:"ă";s:2:"Ä‚";s:2:"Ä…";s:2:"Ä„";s:2:"ć";s:2:"Ć";s:2:"ĉ";s:2:"Ĉ";s:2:"Ä‹";s:2:"ÄŠ";s:2:"Ä";s:2:"ÄŒ";s:2:"Ä";s:2:"ÄŽ";s:2:"Ä‘";s:2:"Ä";s:2:"Ä“";s:2:"Ä’";s:2:"Ä•";s:2:"Ä”";s:2:"Ä—";s:2:"Ä–";s:2:"Ä™";s:2:"Ę";s:2:"Ä›";s:2:"Äš";s:2:"Ä";s:2:"Äœ";s:2:"ÄŸ";s:2:"Äž";s:2:"Ä¡";s:2:"Ä ";s:2:"Ä£";s:2:"Ä¢";s:2:"Ä¥";s:2:"Ĥ";s:2:"ħ";s:2:"Ħ";s:2:"Ä©";s:2:"Ĩ";s:2:"Ä«";s:2:"Ī";s:2:"Ä­";s:2:"Ĭ";s:2:"į";s:2:"Ä®";s:2:"ı";s:1:"I";s:2:"ij";s:2:"IJ";s:2:"ĵ";s:2:"Ä´";s:2:"Ä·";s:2:"Ķ";s:2:"ĺ";s:2:"Ĺ";s:2:"ļ";s:2:"Ä»";s:2:"ľ";s:2:"Ľ";s:2:"Å€";s:2:"Ä¿";s:2:"Å‚";s:2:"Å";s:2:"Å„";s:2:"Ń";s:2:"ņ";s:2:"Å…";s:2:"ň";s:2:"Ň";s:2:"Å‹";s:2:"ÅŠ";s:2:"Å";s:2:"ÅŒ";s:2:"Å";s:2:"ÅŽ";s:2:"Å‘";s:2:"Å";s:2:"Å“";s:2:"Å’";s:2:"Å•";s:2:"Å”";s:2:"Å—";s:2:"Å–";s:2:"Å™";s:2:"Ř";s:2:"Å›";s:2:"Åš";s:2:"Å";s:2:"Åœ";s:2:"ÅŸ";s:2:"Åž";s:2:"Å¡";s:2:"Å ";s:2:"Å£";s:2:"Å¢";s:2:"Å¥";s:2:"Ť";s:2:"ŧ";s:2:"Ŧ";s:2:"Å©";s:2:"Ũ";s:2:"Å«";s:2:"Ū";s:2:"Å­";s:2:"Ŭ";s:2:"ů";s:2:"Å®";s:2:"ű";s:2:"Ű";s:2:"ų";s:2:"Ų";s:2:"ŵ";s:2:"Å´";s:2:"Å·";s:2:"Ŷ";s:2:"ź";s:2:"Ź";s:2:"ż";s:2:"Å»";s:2:"ž";s:2:"Ž";s:2:"Å¿";s:1:"S";s:2:"Æ€";s:2:"Ƀ";s:2:"ƃ";s:2:"Æ‚";s:2:"Æ…";s:2:"Æ„";s:2:"ƈ";s:2:"Ƈ";s:2:"ÆŒ";s:2:"Æ‹";s:2:"Æ’";s:2:"Æ‘";s:2:"Æ•";s:2:"Ƕ";s:2:"Æ™";s:2:"Ƙ";s:2:"Æš";s:2:"Ƚ";s:2:"Æž";s:2:"È ";s:2:"Æ¡";s:2:"Æ ";s:2:"Æ£";s:2:"Æ¢";s:2:"Æ¥";s:2:"Ƥ";s:2:"ƨ";s:2:"Ƨ";s:2:"Æ­";s:2:"Ƭ";s:2:"ư";s:2:"Ư";s:2:"Æ´";s:2:"Ƴ";s:2:"ƶ";s:2:"Ƶ";s:2:"ƹ";s:2:"Ƹ";s:2:"ƽ";s:2:"Ƽ";s:2:"Æ¿";s:2:"Ç·";s:2:"Ç…";s:2:"Ç„";s:2:"dž";s:2:"Ç„";s:2:"Lj";s:2:"LJ";s:2:"lj";s:2:"LJ";s:2:"Ç‹";s:2:"ÇŠ";s:2:"ÇŒ";s:2:"ÇŠ";s:2:"ÇŽ";s:2:"Ç";s:2:"Ç";s:2:"Ç";s:2:"Ç’";s:2:"Ç‘";s:2:"Ç”";s:2:"Ç“";s:2:"Ç–";s:2:"Ç•";s:2:"ǘ";s:2:"Ç—";s:2:"Çš";s:2:"Ç™";s:2:"Çœ";s:2:"Ç›";s:2:"Ç";s:2:"ÆŽ";s:2:"ÇŸ";s:2:"Çž";s:2:"Ç¡";s:2:"Ç ";s:2:"Ç£";s:2:"Ç¢";s:2:"Ç¥";s:2:"Ǥ";s:2:"ǧ";s:2:"Ǧ";s:2:"Ç©";s:2:"Ǩ";s:2:"Ç«";s:2:"Ǫ";s:2:"Ç­";s:2:"Ǭ";s:2:"ǯ";s:2:"Ç®";s:2:"Dz";s:2:"DZ";s:2:"dz";s:2:"DZ";s:2:"ǵ";s:2:"Ç´";s:2:"ǹ";s:2:"Ǹ";s:2:"Ç»";s:2:"Ǻ";s:2:"ǽ";s:2:"Ǽ";s:2:"Ç¿";s:2:"Ǿ";s:2:"È";s:2:"È€";s:2:"ȃ";s:2:"È‚";s:2:"È…";s:2:"È„";s:2:"ȇ";s:2:"Ȇ";s:2:"ȉ";s:2:"Ȉ";s:2:"È‹";s:2:"ÈŠ";s:2:"È";s:2:"ÈŒ";s:2:"È";s:2:"ÈŽ";s:2:"È‘";s:2:"È";s:2:"È“";s:2:"È’";s:2:"È•";s:2:"È”";s:2:"È—";s:2:"È–";s:2:"È™";s:2:"Ș";s:2:"È›";s:2:"Èš";s:2:"È";s:2:"Èœ";s:2:"ÈŸ";s:2:"Èž";s:2:"È£";s:2:"È¢";s:2:"È¥";s:2:"Ȥ";s:2:"ȧ";s:2:"Ȧ";s:2:"È©";s:2:"Ȩ";s:2:"È«";s:2:"Ȫ";s:2:"È­";s:2:"Ȭ";s:2:"ȯ";s:2:"È®";s:2:"ȱ";s:2:"Ȱ";s:2:"ȳ";s:2:"Ȳ";s:2:"ȼ";s:2:"È»";s:2:"È¿";s:3:"â±¾";s:2:"É€";s:3:"Ɀ";s:2:"É‚";s:2:"É";s:2:"ɇ";s:2:"Ɇ";s:2:"ɉ";s:2:"Ɉ";s:2:"É‹";s:2:"ÉŠ";s:2:"É";s:2:"ÉŒ";s:2:"É";s:2:"ÉŽ";s:2:"É";s:3:"Ɐ";s:2:"É‘";s:3:"â±­";s:2:"É’";s:3:"â±°";s:2:"É“";s:2:"Æ";s:2:"É”";s:2:"Ɔ";s:2:"É–";s:2:"Ɖ";s:2:"É—";s:2:"ÆŠ";s:2:"É™";s:2:"Æ";s:2:"É›";s:2:"Æ";s:2:"É ";s:2:"Æ“";s:2:"É£";s:2:"Æ”";s:2:"É¥";s:3:"êž";s:2:"ɦ";s:3:"Ɦ";s:2:"ɨ";s:2:"Æ—";s:2:"É©";s:2:"Æ–";s:2:"É«";s:3:"â±¢";s:2:"ɯ";s:2:"Æœ";s:2:"ɱ";s:3:"â±®";s:2:"ɲ";s:2:"Æ";s:2:"ɵ";s:2:"ÆŸ";s:2:"ɽ";s:3:"Ɽ";s:2:"Ê€";s:2:"Ʀ";s:2:"ʃ";s:2:"Æ©";s:2:"ʈ";s:2:"Æ®";s:2:"ʉ";s:2:"É„";s:2:"ÊŠ";s:2:"Ʊ";s:2:"Ê‹";s:2:"Ʋ";s:2:"ÊŒ";s:2:"É…";s:2:"Ê’";s:2:"Æ·";s:2:"Í…";s:2:"Ι";s:2:"ͱ";s:2:"Ͱ";s:2:"ͳ";s:2:"Ͳ";s:2:"Í·";s:2:"Ͷ";s:2:"Í»";s:2:"Ͻ";s:2:"ͼ";s:2:"Ͼ";s:2:"ͽ";s:2:"Ï¿";s:2:"ά";s:2:"Ά";s:2:"έ";s:2:"Έ";s:2:"ή";s:2:"Ή";s:2:"ί";s:2:"Ί";s:2:"α";s:2:"Α";s:2:"β";s:2:"Î’";s:2:"γ";s:2:"Γ";s:2:"δ";s:2:"Δ";s:2:"ε";s:2:"Ε";s:2:"ζ";s:2:"Ζ";s:2:"η";s:2:"Η";s:2:"θ";s:2:"Θ";s:2:"ι";s:2:"Ι";s:2:"κ";s:2:"Κ";s:2:"λ";s:2:"Λ";s:2:"μ";s:2:"Μ";s:2:"ν";s:2:"Î";s:2:"ξ";s:2:"Ξ";s:2:"ο";s:2:"Ο";s:2:"Ï€";s:2:"Π";s:2:"Ï";s:2:"Ρ";s:2:"Ï‚";s:2:"Σ";s:2:"σ";s:2:"Σ";s:2:"Ï„";s:2:"Τ";s:2:"Ï…";s:2:"Î¥";s:2:"φ";s:2:"Φ";s:2:"χ";s:2:"Χ";s:2:"ψ";s:2:"Ψ";s:2:"ω";s:2:"Ω";s:2:"ÏŠ";s:2:"Ϊ";s:2:"Ï‹";s:2:"Ϋ";s:2:"ÏŒ";s:2:"ÎŒ";s:2:"Ï";s:2:"ÎŽ";s:2:"ÏŽ";s:2:"Î";s:2:"Ï";s:2:"Î’";s:2:"Ï‘";s:2:"Θ";s:2:"Ï•";s:2:"Φ";s:2:"Ï–";s:2:"Π";s:2:"Ï—";s:2:"Ï";s:2:"Ï™";s:2:"Ϙ";s:2:"Ï›";s:2:"Ïš";s:2:"Ï";s:2:"Ïœ";s:2:"ÏŸ";s:2:"Ïž";s:2:"Ï¡";s:2:"Ï ";s:2:"Ï£";s:2:"Ï¢";s:2:"Ï¥";s:2:"Ϥ";s:2:"ϧ";s:2:"Ϧ";s:2:"Ï©";s:2:"Ϩ";s:2:"Ï«";s:2:"Ϫ";s:2:"Ï­";s:2:"Ϭ";s:2:"ϯ";s:2:"Ï®";s:2:"ϰ";s:2:"Κ";s:2:"ϱ";s:2:"Ρ";s:2:"ϲ";s:2:"Ϲ";s:2:"ϵ";s:2:"Ε";s:2:"ϸ";s:2:"Ï·";s:2:"Ï»";s:2:"Ϻ";s:2:"а";s:2:"Ð";s:2:"б";s:2:"Б";s:2:"в";s:2:"Ð’";s:2:"г";s:2:"Г";s:2:"д";s:2:"Д";s:2:"е";s:2:"Е";s:2:"ж";s:2:"Ж";s:2:"з";s:2:"З";s:2:"и";s:2:"И";s:2:"й";s:2:"Й";s:2:"к";s:2:"К";s:2:"л";s:2:"Л";s:2:"м";s:2:"М";s:2:"н";s:2:"Ð";s:2:"о";s:2:"О";s:2:"п";s:2:"П";s:2:"Ñ€";s:2:"Р";s:2:"Ñ";s:2:"С";s:2:"Ñ‚";s:2:"Т";s:2:"у";s:2:"У";s:2:"Ñ„";s:2:"Ф";s:2:"Ñ…";s:2:"Ð¥";s:2:"ц";s:2:"Ц";s:2:"ч";s:2:"Ч";s:2:"ш";s:2:"Ш";s:2:"щ";s:2:"Щ";s:2:"ÑŠ";s:2:"Ъ";s:2:"Ñ‹";s:2:"Ы";s:2:"ÑŒ";s:2:"Ь";s:2:"Ñ";s:2:"Э";s:2:"ÑŽ";s:2:"Ю";s:2:"Ñ";s:2:"Я";s:2:"Ñ";s:2:"Ѐ";s:2:"Ñ‘";s:2:"Ð";s:2:"Ñ’";s:2:"Ђ";s:2:"Ñ“";s:2:"Ѓ";s:2:"Ñ”";s:2:"Є";s:2:"Ñ•";s:2:"Ð…";s:2:"Ñ–";s:2:"І";s:2:"Ñ—";s:2:"Ї";s:2:"ј";s:2:"Ј";s:2:"Ñ™";s:2:"Љ";s:2:"Ñš";s:2:"Њ";s:2:"Ñ›";s:2:"Ћ";s:2:"Ñœ";s:2:"ÐŒ";s:2:"Ñ";s:2:"Ð";s:2:"Ñž";s:2:"ÐŽ";s:2:"ÑŸ";s:2:"Ð";s:2:"Ñ¡";s:2:"Ñ ";s:2:"Ñ£";s:2:"Ñ¢";s:2:"Ñ¥";s:2:"Ѥ";s:2:"ѧ";s:2:"Ѧ";s:2:"Ñ©";s:2:"Ѩ";s:2:"Ñ«";s:2:"Ѫ";s:2:"Ñ­";s:2:"Ѭ";s:2:"ѯ";s:2:"Ñ®";s:2:"ѱ";s:2:"Ѱ";s:2:"ѳ";s:2:"Ѳ";s:2:"ѵ";s:2:"Ñ´";s:2:"Ñ·";s:2:"Ѷ";s:2:"ѹ";s:2:"Ѹ";s:2:"Ñ»";s:2:"Ѻ";s:2:"ѽ";s:2:"Ѽ";s:2:"Ñ¿";s:2:"Ѿ";s:2:"Ò";s:2:"Ò€";s:2:"Ò‹";s:2:"ÒŠ";s:2:"Ò";s:2:"ÒŒ";s:2:"Ò";s:2:"ÒŽ";s:2:"Ò‘";s:2:"Ò";s:2:"Ò“";s:2:"Ò’";s:2:"Ò•";s:2:"Ò”";s:2:"Ò—";s:2:"Ò–";s:2:"Ò™";s:2:"Ò˜";s:2:"Ò›";s:2:"Òš";s:2:"Ò";s:2:"Òœ";s:2:"ÒŸ";s:2:"Òž";s:2:"Ò¡";s:2:"Ò ";s:2:"Ò£";s:2:"Ò¢";s:2:"Ò¥";s:2:"Ò¤";s:2:"Ò§";s:2:"Ò¦";s:2:"Ò©";s:2:"Ò¨";s:2:"Ò«";s:2:"Òª";s:2:"Ò­";s:2:"Ò¬";s:2:"Ò¯";s:2:"Ò®";s:2:"Ò±";s:2:"Ò°";s:2:"Ò³";s:2:"Ò²";s:2:"Òµ";s:2:"Ò´";s:2:"Ò·";s:2:"Ò¶";s:2:"Ò¹";s:2:"Ò¸";s:2:"Ò»";s:2:"Òº";s:2:"Ò½";s:2:"Ò¼";s:2:"Ò¿";s:2:"Ò¾";s:2:"Ó‚";s:2:"Ó";s:2:"Ó„";s:2:"Óƒ";s:2:"Ó†";s:2:"Ó…";s:2:"Óˆ";s:2:"Ó‡";s:2:"ÓŠ";s:2:"Ó‰";s:2:"ÓŒ";s:2:"Ó‹";s:2:"ÓŽ";s:2:"Ó";s:2:"Ó";s:2:"Ó€";s:2:"Ó‘";s:2:"Ó";s:2:"Ó“";s:2:"Ó’";s:2:"Ó•";s:2:"Ó”";s:2:"Ó—";s:2:"Ó–";s:2:"Ó™";s:2:"Ó˜";s:2:"Ó›";s:2:"Óš";s:2:"Ó";s:2:"Óœ";s:2:"ÓŸ";s:2:"Óž";s:2:"Ó¡";s:2:"Ó ";s:2:"Ó£";s:2:"Ó¢";s:2:"Ó¥";s:2:"Ó¤";s:2:"Ó§";s:2:"Ó¦";s:2:"Ó©";s:2:"Ó¨";s:2:"Ó«";s:2:"Óª";s:2:"Ó­";s:2:"Ó¬";s:2:"Ó¯";s:2:"Ó®";s:2:"Ó±";s:2:"Ó°";s:2:"Ó³";s:2:"Ó²";s:2:"Óµ";s:2:"Ó´";s:2:"Ó·";s:2:"Ó¶";s:2:"Ó¹";s:2:"Ó¸";s:2:"Ó»";s:2:"Óº";s:2:"Ó½";s:2:"Ó¼";s:2:"Ó¿";s:2:"Ó¾";s:2:"Ô";s:2:"Ô€";s:2:"Ôƒ";s:2:"Ô‚";s:2:"Ô…";s:2:"Ô„";s:2:"Ô‡";s:2:"Ô†";s:2:"Ô‰";s:2:"Ôˆ";s:2:"Ô‹";s:2:"ÔŠ";s:2:"Ô";s:2:"ÔŒ";s:2:"Ô";s:2:"ÔŽ";s:2:"Ô‘";s:2:"Ô";s:2:"Ô“";s:2:"Ô’";s:2:"Ô•";s:2:"Ô”";s:2:"Ô—";s:2:"Ô–";s:2:"Ô™";s:2:"Ô˜";s:2:"Ô›";s:2:"Ôš";s:2:"Ô";s:2:"Ôœ";s:2:"ÔŸ";s:2:"Ôž";s:2:"Ô¡";s:2:"Ô ";s:2:"Ô£";s:2:"Ô¢";s:2:"Ô¥";s:2:"Ô¤";s:2:"Ô§";s:2:"Ô¦";s:2:"Õ¡";s:2:"Ô±";s:2:"Õ¢";s:2:"Ô²";s:2:"Õ£";s:2:"Ô³";s:2:"Õ¤";s:2:"Ô´";s:2:"Õ¥";s:2:"Ôµ";s:2:"Õ¦";s:2:"Ô¶";s:2:"Õ§";s:2:"Ô·";s:2:"Õ¨";s:2:"Ô¸";s:2:"Õ©";s:2:"Ô¹";s:2:"Õª";s:2:"Ôº";s:2:"Õ«";s:2:"Ô»";s:2:"Õ¬";s:2:"Ô¼";s:2:"Õ­";s:2:"Ô½";s:2:"Õ®";s:2:"Ô¾";s:2:"Õ¯";s:2:"Ô¿";s:2:"Õ°";s:2:"Õ€";s:2:"Õ±";s:2:"Õ";s:2:"Õ²";s:2:"Õ‚";s:2:"Õ³";s:2:"Õƒ";s:2:"Õ´";s:2:"Õ„";s:2:"Õµ";s:2:"Õ…";s:2:"Õ¶";s:2:"Õ†";s:2:"Õ·";s:2:"Õ‡";s:2:"Õ¸";s:2:"Õˆ";s:2:"Õ¹";s:2:"Õ‰";s:2:"Õº";s:2:"ÕŠ";s:2:"Õ»";s:2:"Õ‹";s:2:"Õ¼";s:2:"ÕŒ";s:2:"Õ½";s:2:"Õ";s:2:"Õ¾";s:2:"ÕŽ";s:2:"Õ¿";s:2:"Õ";s:2:"Ö€";s:2:"Õ";s:2:"Ö";s:2:"Õ‘";s:2:"Ö‚";s:2:"Õ’";s:2:"Öƒ";s:2:"Õ“";s:2:"Ö„";s:2:"Õ”";s:2:"Ö…";s:2:"Õ•";s:2:"Ö†";s:2:"Õ–";s:3:"áµ¹";s:3:"ê½";s:3:"áµ½";s:3:"â±£";s:3:"á¸";s:3:"Ḁ";s:3:"ḃ";s:3:"Ḃ";s:3:"ḅ";s:3:"Ḅ";s:3:"ḇ";s:3:"Ḇ";s:3:"ḉ";s:3:"Ḉ";s:3:"ḋ";s:3:"Ḋ";s:3:"á¸";s:3:"Ḍ";s:3:"á¸";s:3:"Ḏ";s:3:"ḑ";s:3:"á¸";s:3:"ḓ";s:3:"Ḓ";s:3:"ḕ";s:3:"Ḕ";s:3:"ḗ";s:3:"Ḗ";s:3:"ḙ";s:3:"Ḙ";s:3:"ḛ";s:3:"Ḛ";s:3:"á¸";s:3:"Ḝ";s:3:"ḟ";s:3:"Ḟ";s:3:"ḡ";s:3:"Ḡ";s:3:"ḣ";s:3:"Ḣ";s:3:"ḥ";s:3:"Ḥ";s:3:"ḧ";s:3:"Ḧ";s:3:"ḩ";s:3:"Ḩ";s:3:"ḫ";s:3:"Ḫ";s:3:"ḭ";s:3:"Ḭ";s:3:"ḯ";s:3:"Ḯ";s:3:"ḱ";s:3:"Ḱ";s:3:"ḳ";s:3:"Ḳ";s:3:"ḵ";s:3:"Ḵ";s:3:"ḷ";s:3:"Ḷ";s:3:"ḹ";s:3:"Ḹ";s:3:"ḻ";s:3:"Ḻ";s:3:"ḽ";s:3:"Ḽ";s:3:"ḿ";s:3:"Ḿ";s:3:"á¹";s:3:"á¹€";s:3:"ṃ";s:3:"Ṃ";s:3:"á¹…";s:3:"Ṅ";s:3:"ṇ";s:3:"Ṇ";s:3:"ṉ";s:3:"Ṉ";s:3:"ṋ";s:3:"Ṋ";s:3:"á¹";s:3:"Ṍ";s:3:"á¹";s:3:"Ṏ";s:3:"ṑ";s:3:"á¹";s:3:"ṓ";s:3:"á¹’";s:3:"ṕ";s:3:"á¹”";s:3:"á¹—";s:3:"á¹–";s:3:"á¹™";s:3:"Ṙ";s:3:"á¹›";s:3:"Ṛ";s:3:"á¹";s:3:"Ṝ";s:3:"ṟ";s:3:"Ṟ";s:3:"ṡ";s:3:"á¹ ";s:3:"á¹£";s:3:"á¹¢";s:3:"á¹¥";s:3:"Ṥ";s:3:"á¹§";s:3:"Ṧ";s:3:"ṩ";s:3:"Ṩ";s:3:"ṫ";s:3:"Ṫ";s:3:"á¹­";s:3:"Ṭ";s:3:"ṯ";s:3:"á¹®";s:3:"á¹±";s:3:"á¹°";s:3:"á¹³";s:3:"á¹²";s:3:"á¹µ";s:3:"á¹´";s:3:"á¹·";s:3:"á¹¶";s:3:"á¹¹";s:3:"Ṹ";s:3:"á¹»";s:3:"Ṻ";s:3:"á¹½";s:3:"á¹¼";s:3:"ṿ";s:3:"á¹¾";s:3:"áº";s:3:"Ẁ";s:3:"ẃ";s:3:"Ẃ";s:3:"ẅ";s:3:"Ẅ";s:3:"ẇ";s:3:"Ẇ";s:3:"ẉ";s:3:"Ẉ";s:3:"ẋ";s:3:"Ẋ";s:3:"áº";s:3:"Ẍ";s:3:"áº";s:3:"Ẏ";s:3:"ẑ";s:3:"áº";s:3:"ẓ";s:3:"Ẓ";s:3:"ẕ";s:3:"Ẕ";s:3:"ẛ";s:3:"á¹ ";s:3:"ạ";s:3:"Ạ";s:3:"ả";s:3:"Ả";s:3:"ấ";s:3:"Ấ";s:3:"ầ";s:3:"Ầ";s:3:"ẩ";s:3:"Ẩ";s:3:"ẫ";s:3:"Ẫ";s:3:"ậ";s:3:"Ậ";s:3:"ắ";s:3:"Ắ";s:3:"ằ";s:3:"Ằ";s:3:"ẳ";s:3:"Ẳ";s:3:"ẵ";s:3:"Ẵ";s:3:"ặ";s:3:"Ặ";s:3:"ẹ";s:3:"Ẹ";s:3:"ẻ";s:3:"Ẻ";s:3:"ẽ";s:3:"Ẽ";s:3:"ế";s:3:"Ế";s:3:"á»";s:3:"Ề";s:3:"ể";s:3:"Ể";s:3:"á»…";s:3:"Ễ";s:3:"ệ";s:3:"Ệ";s:3:"ỉ";s:3:"Ỉ";s:3:"ị";s:3:"Ị";s:3:"á»";s:3:"Ọ";s:3:"á»";s:3:"Ỏ";s:3:"ố";s:3:"á»";s:3:"ồ";s:3:"á»’";s:3:"ổ";s:3:"á»”";s:3:"á»—";s:3:"á»–";s:3:"á»™";s:3:"Ộ";s:3:"á»›";s:3:"Ớ";s:3:"á»";s:3:"Ờ";s:3:"ở";s:3:"Ở";s:3:"ỡ";s:3:"á» ";s:3:"ợ";s:3:"Ợ";s:3:"ụ";s:3:"Ụ";s:3:"á»§";s:3:"Ủ";s:3:"ứ";s:3:"Ứ";s:3:"ừ";s:3:"Ừ";s:3:"á»­";s:3:"Ử";s:3:"ữ";s:3:"á»®";s:3:"á»±";s:3:"á»°";s:3:"ỳ";s:3:"Ỳ";s:3:"ỵ";s:3:"á»´";s:3:"á»·";s:3:"á»¶";s:3:"ỹ";s:3:"Ỹ";s:3:"á»»";s:3:"Ỻ";s:3:"ỽ";s:3:"Ỽ";s:3:"ỿ";s:3:"Ỿ";s:3:"á¼€";s:3:"Ἀ";s:3:"á¼";s:3:"Ἁ";s:3:"ἂ";s:3:"Ἂ";s:3:"ἃ";s:3:"Ἃ";s:3:"ἄ";s:3:"Ἄ";s:3:"á¼…";s:3:"á¼";s:3:"ἆ";s:3:"Ἆ";s:3:"ἇ";s:3:"á¼";s:3:"á¼";s:3:"Ἐ";s:3:"ἑ";s:3:"á¼™";s:3:"á¼’";s:3:"Ἒ";s:3:"ἓ";s:3:"á¼›";s:3:"á¼”";s:3:"Ἔ";s:3:"ἕ";s:3:"á¼";s:3:"á¼ ";s:3:"Ἠ";s:3:"ἡ";s:3:"Ἡ";s:3:"á¼¢";s:3:"Ἢ";s:3:"á¼£";s:3:"Ἣ";s:3:"ἤ";s:3:"Ἤ";s:3:"á¼¥";s:3:"á¼­";s:3:"ἦ";s:3:"á¼®";s:3:"á¼§";s:3:"Ἧ";s:3:"á¼°";s:3:"Ἰ";s:3:"á¼±";s:3:"á¼¹";s:3:"á¼²";s:3:"Ἲ";s:3:"á¼³";s:3:"á¼»";s:3:"á¼´";s:3:"á¼¼";s:3:"á¼µ";s:3:"á¼½";s:3:"á¼¶";s:3:"á¼¾";s:3:"á¼·";s:3:"Ἷ";s:3:"á½€";s:3:"Ὀ";s:3:"á½";s:3:"Ὁ";s:3:"ὂ";s:3:"Ὂ";s:3:"ὃ";s:3:"Ὃ";s:3:"ὄ";s:3:"Ὄ";s:3:"á½…";s:3:"á½";s:3:"ὑ";s:3:"á½™";s:3:"ὓ";s:3:"á½›";s:3:"ὕ";s:3:"á½";s:3:"á½—";s:3:"Ὗ";s:3:"á½ ";s:3:"Ὠ";s:3:"ὡ";s:3:"Ὡ";s:3:"á½¢";s:3:"Ὢ";s:3:"á½£";s:3:"Ὣ";s:3:"ὤ";s:3:"Ὤ";s:3:"á½¥";s:3:"á½­";s:3:"ὦ";s:3:"á½®";s:3:"á½§";s:3:"Ὧ";s:3:"á½°";s:3:"Ὰ";s:3:"á½±";s:3:"á¾»";s:3:"á½²";s:3:"Ὲ";s:3:"á½³";s:3:"Έ";s:3:"á½´";s:3:"Ὴ";s:3:"á½µ";s:3:"á¿‹";s:3:"á½¶";s:3:"Ὶ";s:3:"á½·";s:3:"á¿›";s:3:"ὸ";s:3:"Ὸ";s:3:"á½¹";s:3:"Ό";s:3:"ὺ";s:3:"Ὺ";s:3:"á½»";s:3:"á¿«";s:3:"á½¼";s:3:"Ὼ";s:3:"á½½";s:3:"á¿»";s:3:"á¾€";s:3:"ᾈ";s:3:"á¾";s:3:"ᾉ";s:3:"ᾂ";s:3:"ᾊ";s:3:"ᾃ";s:3:"ᾋ";s:3:"ᾄ";s:3:"ᾌ";s:3:"á¾…";s:3:"á¾";s:3:"ᾆ";s:3:"ᾎ";s:3:"ᾇ";s:3:"á¾";s:3:"á¾";s:3:"ᾘ";s:3:"ᾑ";s:3:"á¾™";s:3:"á¾’";s:3:"ᾚ";s:3:"ᾓ";s:3:"á¾›";s:3:"á¾”";s:3:"ᾜ";s:3:"ᾕ";s:3:"á¾";s:3:"á¾–";s:3:"ᾞ";s:3:"á¾—";s:3:"ᾟ";s:3:"á¾ ";s:3:"ᾨ";s:3:"ᾡ";s:3:"ᾩ";s:3:"á¾¢";s:3:"ᾪ";s:3:"á¾£";s:3:"ᾫ";s:3:"ᾤ";s:3:"ᾬ";s:3:"á¾¥";s:3:"á¾­";s:3:"ᾦ";s:3:"á¾®";s:3:"á¾§";s:3:"ᾯ";s:3:"á¾°";s:3:"Ᾰ";s:3:"á¾±";s:3:"á¾¹";s:3:"á¾³";s:3:"á¾¼";s:3:"á¾¾";s:2:"Ι";s:3:"ῃ";s:3:"ῌ";s:3:"á¿";s:3:"Ῐ";s:3:"á¿‘";s:3:"á¿™";s:3:"á¿ ";s:3:"Ῠ";s:3:"á¿¡";s:3:"á¿©";s:3:"á¿¥";s:3:"Ῥ";s:3:"ῳ";s:3:"ῼ";s:3:"â…Ž";s:3:"Ⅎ";s:3:"â…°";s:3:"â… ";s:3:"â…±";s:3:"â…¡";s:3:"â…²";s:3:"â…¢";s:3:"â…³";s:3:"â…£";s:3:"â…´";s:3:"â…¤";s:3:"â…µ";s:3:"â…¥";s:3:"â…¶";s:3:"â…¦";s:3:"â…·";s:3:"â…§";s:3:"â…¸";s:3:"â…¨";s:3:"â…¹";s:3:"â…©";s:3:"â…º";s:3:"â…ª";s:3:"â…»";s:3:"â…«";s:3:"â…¼";s:3:"â…¬";s:3:"â…½";s:3:"â…­";s:3:"â…¾";s:3:"â…®";s:3:"â…¿";s:3:"â…¯";s:3:"ↄ";s:3:"Ↄ";s:3:"â“";s:3:"â’¶";s:3:"â“‘";s:3:"â’·";s:3:"â“’";s:3:"â’¸";s:3:"â““";s:3:"â’¹";s:3:"â“”";s:3:"â’º";s:3:"â“•";s:3:"â’»";s:3:"â“–";s:3:"â’¼";s:3:"â“—";s:3:"â’½";s:3:"ⓘ";s:3:"â’¾";s:3:"â“™";s:3:"â’¿";s:3:"ⓚ";s:3:"â“€";s:3:"â“›";s:3:"â“";s:3:"ⓜ";s:3:"â“‚";s:3:"â“";s:3:"Ⓝ";s:3:"ⓞ";s:3:"â“„";s:3:"ⓟ";s:3:"â“…";s:3:"â“ ";s:3:"Ⓠ";s:3:"â“¡";s:3:"Ⓡ";s:3:"â“¢";s:3:"Ⓢ";s:3:"â“£";s:3:"Ⓣ";s:3:"ⓤ";s:3:"Ⓤ";s:3:"â“¥";s:3:"â“‹";s:3:"ⓦ";s:3:"Ⓦ";s:3:"â“§";s:3:"â“";s:3:"ⓨ";s:3:"Ⓨ";s:3:"â“©";s:3:"â“";s:3:"â°°";s:3:"â°€";s:3:"â°±";s:3:"â°";s:3:"â°²";s:3:"â°‚";s:3:"â°³";s:3:"â°ƒ";s:3:"â°´";s:3:"â°„";s:3:"â°µ";s:3:"â°…";s:3:"â°¶";s:3:"â°†";s:3:"â°·";s:3:"â°‡";s:3:"â°¸";s:3:"â°ˆ";s:3:"â°¹";s:3:"â°‰";s:3:"â°º";s:3:"â°Š";s:3:"â°»";s:3:"â°‹";s:3:"â°¼";s:3:"â°Œ";s:3:"â°½";s:3:"â°";s:3:"â°¾";s:3:"â°Ž";s:3:"â°¿";s:3:"â°";s:3:"â±€";s:3:"â°";s:3:"â±";s:3:"â°‘";s:3:"ⱂ";s:3:"â°’";s:3:"ⱃ";s:3:"â°“";s:3:"ⱄ";s:3:"â°”";s:3:"â±…";s:3:"â°•";s:3:"ⱆ";s:3:"â°–";s:3:"ⱇ";s:3:"â°—";s:3:"ⱈ";s:3:"â°˜";s:3:"ⱉ";s:3:"â°™";s:3:"ⱊ";s:3:"â°š";s:3:"ⱋ";s:3:"â°›";s:3:"ⱌ";s:3:"â°œ";s:3:"â±";s:3:"â°";s:3:"ⱎ";s:3:"â°ž";s:3:"â±";s:3:"â°Ÿ";s:3:"â±";s:3:"â° ";s:3:"ⱑ";s:3:"â°¡";s:3:"â±’";s:3:"â°¢";s:3:"ⱓ";s:3:"â°£";s:3:"â±”";s:3:"â°¤";s:3:"ⱕ";s:3:"â°¥";s:3:"â±–";s:3:"â°¦";s:3:"â±—";s:3:"â°§";s:3:"ⱘ";s:3:"â°¨";s:3:"â±™";s:3:"â°©";s:3:"ⱚ";s:3:"â°ª";s:3:"â±›";s:3:"â°«";s:3:"ⱜ";s:3:"â°¬";s:3:"â±";s:3:"â°­";s:3:"ⱞ";s:3:"â°®";s:3:"ⱡ";s:3:"â± ";s:3:"â±¥";s:2:"Ⱥ";s:3:"ⱦ";s:2:"Ⱦ";s:3:"ⱨ";s:3:"â±§";s:3:"ⱪ";s:3:"Ⱪ";s:3:"ⱬ";s:3:"Ⱬ";s:3:"â±³";s:3:"â±²";s:3:"â±¶";s:3:"â±µ";s:3:"â²";s:3:"â²€";s:3:"ⲃ";s:3:"Ⲃ";s:3:"â²…";s:3:"Ⲅ";s:3:"ⲇ";s:3:"Ⲇ";s:3:"ⲉ";s:3:"Ⲉ";s:3:"ⲋ";s:3:"Ⲋ";s:3:"â²";s:3:"Ⲍ";s:3:"â²";s:3:"Ⲏ";s:3:"ⲑ";s:3:"â²";s:3:"ⲓ";s:3:"â²’";s:3:"ⲕ";s:3:"â²”";s:3:"â²—";s:3:"â²–";s:3:"â²™";s:3:"Ⲙ";s:3:"â²›";s:3:"Ⲛ";s:3:"â²";s:3:"Ⲝ";s:3:"ⲟ";s:3:"Ⲟ";s:3:"ⲡ";s:3:"â² ";s:3:"â²£";s:3:"â²¢";s:3:"â²¥";s:3:"Ⲥ";s:3:"â²§";s:3:"Ⲧ";s:3:"ⲩ";s:3:"Ⲩ";s:3:"ⲫ";s:3:"Ⲫ";s:3:"â²­";s:3:"Ⲭ";s:3:"ⲯ";s:3:"â²®";s:3:"â²±";s:3:"â²°";s:3:"â²³";s:3:"â²²";s:3:"â²µ";s:3:"â²´";s:3:"â²·";s:3:"â²¶";s:3:"â²¹";s:3:"Ⲹ";s:3:"â²»";s:3:"Ⲻ";s:3:"â²½";s:3:"â²¼";s:3:"ⲿ";s:3:"â²¾";s:3:"â³";s:3:"â³€";s:3:"ⳃ";s:3:"Ⳃ";s:3:"â³…";s:3:"Ⳅ";s:3:"ⳇ";s:3:"Ⳇ";s:3:"ⳉ";s:3:"Ⳉ";s:3:"ⳋ";s:3:"Ⳋ";s:3:"â³";s:3:"Ⳍ";s:3:"â³";s:3:"Ⳏ";s:3:"ⳑ";s:3:"â³";s:3:"ⳓ";s:3:"â³’";s:3:"ⳕ";s:3:"â³”";s:3:"â³—";s:3:"â³–";s:3:"â³™";s:3:"Ⳙ";s:3:"â³›";s:3:"Ⳛ";s:3:"â³";s:3:"Ⳝ";s:3:"ⳟ";s:3:"Ⳟ";s:3:"ⳡ";s:3:"â³ ";s:3:"â³£";s:3:"â³¢";s:3:"ⳬ";s:3:"Ⳬ";s:3:"â³®";s:3:"â³­";s:3:"â³³";s:3:"â³²";s:3:"â´€";s:3:"á‚ ";s:3:"â´";s:3:"á‚¡";s:3:"â´‚";s:3:"á‚¢";s:3:"â´ƒ";s:3:"á‚£";s:3:"â´„";s:3:"Ⴄ";s:3:"â´…";s:3:"á‚¥";s:3:"â´†";s:3:"Ⴆ";s:3:"â´‡";s:3:"á‚§";s:3:"â´ˆ";s:3:"Ⴈ";s:3:"â´‰";s:3:"á‚©";s:3:"â´Š";s:3:"Ⴊ";s:3:"â´‹";s:3:"á‚«";s:3:"â´Œ";s:3:"Ⴌ";s:3:"â´";s:3:"á‚­";s:3:"â´Ž";s:3:"á‚®";s:3:"â´";s:3:"Ⴏ";s:3:"â´";s:3:"á‚°";s:3:"â´‘";s:3:"Ⴑ";s:3:"â´’";s:3:"Ⴒ";s:3:"â´“";s:3:"Ⴓ";s:3:"â´”";s:3:"á‚´";s:3:"â´•";s:3:"Ⴕ";s:3:"â´–";s:3:"á‚¶";s:3:"â´—";s:3:"á‚·";s:3:"â´˜";s:3:"Ⴘ";s:3:"â´™";s:3:"Ⴙ";s:3:"â´š";s:3:"Ⴚ";s:3:"â´›";s:3:"á‚»";s:3:"â´œ";s:3:"Ⴜ";s:3:"â´";s:3:"Ⴝ";s:3:"â´ž";s:3:"Ⴞ";s:3:"â´Ÿ";s:3:"á‚¿";s:3:"â´ ";s:3:"Ⴠ";s:3:"â´¡";s:3:"áƒ";s:3:"â´¢";s:3:"Ⴢ";s:3:"â´£";s:3:"Ⴣ";s:3:"â´¤";s:3:"Ⴤ";s:3:"â´¥";s:3:"Ⴥ";s:3:"â´§";s:3:"Ⴧ";s:3:"â´­";s:3:"áƒ";s:3:"ê™";s:3:"Ꙁ";s:3:"ꙃ";s:3:"Ꙃ";s:3:"ê™…";s:3:"Ꙅ";s:3:"ꙇ";s:3:"Ꙇ";s:3:"ꙉ";s:3:"Ꙉ";s:3:"ꙋ";s:3:"Ꙋ";s:3:"ê™";s:3:"Ꙍ";s:3:"ê™";s:3:"Ꙏ";s:3:"ꙑ";s:3:"ê™";s:3:"ꙓ";s:3:"ê™’";s:3:"ꙕ";s:3:"ê™”";s:3:"ê™—";s:3:"ê™–";s:3:"ê™™";s:3:"Ꙙ";s:3:"ê™›";s:3:"Ꙛ";s:3:"ê™";s:3:"Ꙝ";s:3:"ꙟ";s:3:"Ꙟ";s:3:"ꙡ";s:3:"ê™ ";s:3:"ꙣ";s:3:"Ꙣ";s:3:"ꙥ";s:3:"Ꙥ";s:3:"ê™§";s:3:"Ꙧ";s:3:"ꙩ";s:3:"Ꙩ";s:3:"ꙫ";s:3:"Ꙫ";s:3:"ê™­";s:3:"Ꙭ";s:3:"êš";s:3:"Ꚁ";s:3:"ꚃ";s:3:"êš‚";s:3:"êš…";s:3:"êš„";s:3:"ꚇ";s:3:"Ꚇ";s:3:"ꚉ";s:3:"Ꚉ";s:3:"êš‹";s:3:"Ꚋ";s:3:"êš";s:3:"Ꚍ";s:3:"êš";s:3:"Ꚏ";s:3:"êš‘";s:3:"êš";s:3:"êš“";s:3:"êš’";s:3:"êš•";s:3:"êš”";s:3:"êš—";s:3:"êš–";s:3:"ꜣ";s:3:"Ꜣ";s:3:"ꜥ";s:3:"Ꜥ";s:3:"ꜧ";s:3:"Ꜧ";s:3:"ꜩ";s:3:"Ꜩ";s:3:"ꜫ";s:3:"Ꜫ";s:3:"ꜭ";s:3:"Ꜭ";s:3:"ꜯ";s:3:"Ꜯ";s:3:"ꜳ";s:3:"Ꜳ";s:3:"ꜵ";s:3:"Ꜵ";s:3:"ꜷ";s:3:"Ꜷ";s:3:"ꜹ";s:3:"Ꜹ";s:3:"ꜻ";s:3:"Ꜻ";s:3:"ꜽ";s:3:"Ꜽ";s:3:"ꜿ";s:3:"Ꜿ";s:3:"ê";s:3:"ê€";s:3:"êƒ";s:3:"ê‚";s:3:"ê…";s:3:"ê„";s:3:"ê‡";s:3:"ê†";s:3:"ê‰";s:3:"êˆ";s:3:"ê‹";s:3:"êŠ";s:3:"ê";s:3:"êŒ";s:3:"ê";s:3:"êŽ";s:3:"ê‘";s:3:"ê";s:3:"ê“";s:3:"ê’";s:3:"ê•";s:3:"ê”";s:3:"ê—";s:3:"ê–";s:3:"ê™";s:3:"ê˜";s:3:"ê›";s:3:"êš";s:3:"ê";s:3:"êœ";s:3:"êŸ";s:3:"êž";s:3:"ê¡";s:3:"ê ";s:3:"ê£";s:3:"ê¢";s:3:"ê¥";s:3:"ê¤";s:3:"ê§";s:3:"ê¦";s:3:"ê©";s:3:"ê¨";s:3:"ê«";s:3:"êª";s:3:"ê­";s:3:"ê¬";s:3:"ê¯";s:3:"ê®";s:3:"êº";s:3:"ê¹";s:3:"ê¼";s:3:"ê»";s:3:"ê¿";s:3:"ê¾";s:3:"êž";s:3:"Ꞁ";s:3:"ꞃ";s:3:"êž‚";s:3:"êž…";s:3:"êž„";s:3:"ꞇ";s:3:"Ꞇ";s:3:"ꞌ";s:3:"êž‹";s:3:"êž‘";s:3:"êž";s:3:"êž“";s:3:"êž’";s:3:"êž¡";s:3:"êž ";s:3:"ꞣ";s:3:"Ꞣ";s:3:"ꞥ";s:3:"Ꞥ";s:3:"êž§";s:3:"Ꞧ";s:3:"êž©";s:3:"Ꞩ";s:3:"ï½";s:3:"A";s:3:"b";s:3:"ï¼¢";s:3:"c";s:3:"ï¼£";s:3:"d";s:3:"D";s:3:"ï½…";s:3:"ï¼¥";s:3:"f";s:3:"F";s:3:"g";s:3:"ï¼§";s:3:"h";s:3:"H";s:3:"i";s:3:"I";s:3:"j";s:3:"J";s:3:"k";s:3:"K";s:3:"l";s:3:"L";s:3:"ï½";s:3:"ï¼­";s:3:"n";s:3:"ï¼®";s:3:"ï½";s:3:"O";s:3:"ï½";s:3:"ï¼°";s:3:"q";s:3:"ï¼±";s:3:"ï½’";s:3:"ï¼²";s:3:"s";s:3:"ï¼³";s:3:"ï½”";s:3:"ï¼´";s:3:"u";s:3:"ï¼µ";s:3:"ï½–";s:3:"ï¼¶";s:3:"ï½—";s:3:"ï¼·";s:3:"x";s:3:"X";s:3:"ï½™";s:3:"ï¼¹";s:3:"z";s:3:"Z";s:4:"ð¨";s:4:"ð€";s:4:"ð©";s:4:"ð";s:4:"ðª";s:4:"ð‚";s:4:"ð«";s:4:"ðƒ";s:4:"ð¬";s:4:"ð„";s:4:"ð­";s:4:"ð…";s:4:"ð®";s:4:"ð†";s:4:"ð¯";s:4:"ð‡";s:4:"ð°";s:4:"ðˆ";s:4:"ð±";s:4:"ð‰";s:4:"ð²";s:4:"ðŠ";s:4:"ð³";s:4:"ð‹";s:4:"ð´";s:4:"ðŒ";s:4:"ðµ";s:4:"ð";s:4:"ð¶";s:4:"ðŽ";s:4:"ð·";s:4:"ð";s:4:"ð¸";s:4:"ð";s:4:"ð¹";s:4:"ð‘";s:4:"ðº";s:4:"ð’";s:4:"ð»";s:4:"ð“";s:4:"ð¼";s:4:"ð”";s:4:"ð½";s:4:"ð•";s:4:"ð¾";s:4:"ð–";s:4:"ð¿";s:4:"ð—";s:4:"ð‘€";s:4:"ð˜";s:4:"ð‘";s:4:"ð™";s:4:"ð‘‚";s:4:"ðš";s:4:"ð‘ƒ";s:4:"ð›";s:4:"ð‘„";s:4:"ðœ";s:4:"ð‘…";s:4:"ð";s:4:"ð‘†";s:4:"ðž";s:4:"ð‘‡";s:4:"ðŸ";s:4:"ð‘ˆ";s:4:"ð ";s:4:"ð‘‰";s:4:"ð¡";s:4:"ð‘Š";s:4:"ð¢";s:4:"ð‘‹";s:4:"ð£";s:4:"ð‘Œ";s:4:"ð¤";s:4:"ð‘";s:4:"ð¥";s:4:"ð‘Ž";s:4:"ð¦";s:4:"ð‘";s:4:"ð§";}
    \ No newline at end of file
    diff --git a/vendor/patchwork/utf8/class/Patchwork/Utf8.php b/vendor/patchwork/utf8/class/Patchwork/Utf8.php
    new file mode 100644
    index 0000000..87202ef
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/Utf8.php
    @@ -0,0 +1,498 @@
    + PHP_VERSION_ID || 50500 == PHP_VERSION_ID)
    +/**/    {
    +            // Don't use grapheme_stripos because of https://bugs.php.net/61860
    +            if ($offset < 0) $offset = 0;
    +            if (!$needle = mb_stripos($s, $needle, $offset, 'UTF-8')) return $needle;
    +            return grapheme_strlen(iconv_substr($s, 0, $needle, 'UTF-8'));
    +/**/    }
    +/**/    else
    +/**/    {
    +            return grapheme_stripos($s, $needle, $offset);
    +/**/    }
    +    }
    +
    +    static function strripos($s, $needle, $offset = 0)
    +    {
    +/**/    if (50418 > PHP_VERSION_ID || 50500 == PHP_VERSION_ID)
    +/**/    {
    +            // Don't use grapheme_strripos because of https://bugs.php.net/61860
    +            if ($offset < 0) $offset = 0;
    +            if (!$needle = mb_strripos($s, $needle, $offset, 'UTF-8')) return $needle;
    +            return grapheme_strlen(iconv_substr($s, 0, $needle, 'UTF-8'));
    +/**/    }
    +/**/    else
    +/**/    {
    +            return grapheme_strripos($s, $needle, $offset);
    +/**/    }
    +    }
    +
    +    static function stristr($s, $needle, $before_needle = false)
    +    {
    +        if ('' === (string) $needle) return false;
    +        return mb_stristr($s, $needle, $before_needle, 'UTF-8');
    +    }
    +
    +    static function strstr  ($s, $needle, $before_needle = false) {return grapheme_strstr($s, $needle, $before_needle);}
    +    static function strrchr ($s, $needle, $before_needle = false) {return mb_strrchr ($s, $needle, $before_needle, 'UTF-8');}
    +    static function strrichr($s, $needle, $before_needle = false) {return mb_strrichr($s, $needle, $before_needle, 'UTF-8');}
    +
    +    static function strtolower($s, $form = n::NFC) {if (n::isNormalized($s = mb_strtolower($s, 'UTF-8'), $form)) return $s; return n::normalize($s, $form);}
    +    static function strtoupper($s, $form = n::NFC) {if (n::isNormalized($s = mb_strtoupper($s, 'UTF-8'), $form)) return $s; return n::normalize($s, $form);}
    +
    +    static function wordwrap($s, $width = 75, $break = "\n", $cut = false)
    +    {
    +        // This implementation could be extended to handle unicode word boundaries,
    +        // but that's enough work for today (see http://www.unicode.org/reports/tr29/)
    +
    +        $width = (int) $width;
    +        $s = explode($break, $s);
    +
    +        $iLen = count($s);
    +        $result = array();
    +        $line = '';
    +        $lineLen = 0;
    +
    +        for ($i = 0; $i < $iLen; ++$i)
    +        {
    +            $words = explode(' ', $s[$i]);
    +            $line && $result[] = $line;
    +            $lineLen = grapheme_strlen($line);
    +            $jLen = count($words);
    +
    +            for ($j = 0; $j < $jLen; ++$j)
    +            {
    +                $w = $words[$j];
    +                $wLen = grapheme_strlen($w);
    +
    +                if ($lineLen + $wLen < $width)
    +                {
    +                    if ($j) $line .= ' ';
    +                    $line .= $w;
    +                    $lineLen += $wLen + 1;
    +                }
    +                else
    +                {
    +                    if ($j || $i) $result[] = $line;
    +                    $line = '';
    +                    $lineLen = 0;
    +
    +                    if ($cut && $wLen > $width)
    +                    {
    +                        $w = self::str_split($w);
    +
    +                        do
    +                        {
    +                            $result[] = implode('', array_slice($w, 0, $width));
    +                            $line = implode('', $w = array_slice($w, $width));
    +                            $lineLen = $wLen -= $width;
    +                        }
    +                        while ($wLen > $width);
    +
    +                        $w = implode('', $w);
    +                    }
    +
    +                    $line = $w;
    +                    $lineLen = $wLen;
    +                }
    +            }
    +        }
    +
    +        $line && $result[] = $line;
    +
    +        return implode($break, $result);
    +    }
    +
    +    static function chr($c)
    +    {
    +        if (0x80 > $c %= 0x200000) return chr($c);
    +        if (0x800 > $c) return chr(0xC0 | $c>>6) . chr(0x80 | $c & 0x3F);
    +        if (0x10000 > $c) return chr(0xE0 | $c>>12) . chr(0x80 | $c>>6 & 0x3F) . chr(0x80 | $c & 0x3F);
    +        return chr(0xF0 | $c>>18) . chr(0x80 | $c>>12 & 0x3F) . chr(0x80 | $c>>6 & 0x3F) . chr(0x80 | $c & 0x3F);
    +    }
    +
    +    static function count_chars($s, $mode = 0)
    +    {
    +        if (1 != $mode) user_error(__METHOD__ . '(): the only allowed $mode is 1', E_USER_WARNING);
    +        $s = self::str_split($s);
    +        return array_count_values($s);
    +    }
    +
    +    static function ltrim($s, $charlist = INF)
    +    {
    +        $charlist = INF === $charlist ? '\s' : self::rxClass($charlist);
    +        return preg_replace("/^{$charlist}+/u", '', $s);
    +    }
    +
    +    static function ord($s)
    +    {
    +        $a = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
    +        if (0xF0 <= $a) return (($a - 0xF0)<<18) + (($s[2] - 0x80)<<12) + (($s[3] - 0x80)<<6) + $s[4] - 0x80;
    +        if (0xE0 <= $a) return (($a - 0xE0)<<12) + (($s[2] - 0x80)<<6) + $s[3] - 0x80;
    +        if (0xC0 <= $a) return (($a - 0xC0)<<6) + $s[2] - 0x80;
    +        return $a;
    +    }
    +
    +    static function rtrim($s, $charlist = INF)
    +    {
    +        $charlist = INF === $charlist ? '\s' : self::rxClass($charlist);
    +        return preg_replace("/{$charlist}+$/u", '', $s);
    +    }
    +
    +    static function trim($s, $charlist = INF) {return self::rtrim(self::ltrim($s, $charlist), $charlist);}
    +
    +    static function str_ireplace($search, $replace, $subject, &$count = null)
    +    {
    +        $search = (array) $search;
    +        foreach ($search as &$s) $s = '' !== (string) $s ? '/' . preg_quote($s, '/') . '/ui' : '/^(?<=.)$/';
    +        $subject = preg_replace($search, $replace, $subject, -1, $replace);
    +        $count = $replace;
    +        return $subject;
    +    }
    +
    +    static function str_pad($s, $len, $pad = ' ', $type = STR_PAD_RIGHT)
    +    {
    +        $slen = grapheme_strlen($s);
    +        if ($len <= $slen) return $s;
    +
    +        $padlen = grapheme_strlen($pad);
    +        $freelen = $len - $slen;
    +        $len = $freelen % $padlen;
    +
    +        if (STR_PAD_RIGHT == $type) return $s . str_repeat($pad, $freelen / $padlen) . ($len ? grapheme_substr($pad, 0, $len) : '');
    +        if (STR_PAD_LEFT  == $type) return      str_repeat($pad, $freelen / $padlen) . ($len ? grapheme_substr($pad, 0, $len) : '') . $s;
    +        if (STR_PAD_BOTH  == $type)
    +        {
    +            $freelen /= 2;
    +
    +            $type = ceil($freelen);
    +            $len = $type % $padlen;
    +            $s .= str_repeat($pad, $type / $padlen) . ($len ? grapheme_substr($pad, 0, $len) : '');
    +
    +            $type = floor($freelen);
    +            $len = $type % $padlen;
    +            return str_repeat($pad, $type / $padlen) . ($len ? grapheme_substr($pad, 0, $len) : '') . $s;
    +        }
    +
    +        user_error(__METHOD__ . '(): Padding type has to be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH', E_USER_WARNING);
    +    }
    +
    +    static function str_shuffle($s)
    +    {
    +        $s = self::str_split($s);
    +        shuffle($s);
    +        return implode('', $s);
    +    }
    +
    +    static function str_split($s, $len = 1)
    +    {
    +        if (1 > $len = (int) $len)
    +        {
    +            $len = func_get_arg(1);
    +            return str_split($s, $len);
    +        }
    +
    +/**/    if (extension_loaded('intl'))
    +/**/    {
    +            $a = array();
    +            $p = 0;
    +            $l = strlen($s);
    +
    +            while ($p < $l) $a[] = grapheme_extract($s, 1, GRAPHEME_EXTR_COUNT, $p, $p);
    +/**/    }
    +/**/    else
    +/**/    {
    +            preg_match_all('/' . GRAPHEME_CLUSTER_RX . '/u', $s, $a);
    +            $a = $a[0];
    +/**/    }
    +
    +        if (1 == $len) return $a;
    +
    +        $s = array();
    +        $p = -1;
    +
    +        foreach ($a as $l => $a)
    +        {
    +            if ($l % $len) $s[$p] .= $a;
    +            else $s[++$p] = $a;
    +        }
    +
    +        return $s;
    +    }
    +
    +    static function str_word_count($s, $format = 0, $charlist = '')
    +    {
    +        $charlist = self::rxClass($charlist, '\pL');
    +        $s = preg_split("/({$charlist}+(?:[\p{Pd}’']{$charlist}+)*)/u", $s, -1, PREG_SPLIT_DELIM_CAPTURE);
    +
    +        $charlist = array();
    +        $len = count($s);
    +
    +        if (1 == $format) for ($i = 1; $i < $len; $i+=2) $charlist[] = $s[$i];
    +        else if (2 == $format)
    +        {
    +            $offset = grapheme_strlen($s[0]);
    +            for ($i = 1; $i < $len; $i+=2)
    +            {
    +                $charlist[$offset] = $s[$i];
    +                $offset += grapheme_strlen($s[$i]) + grapheme_strlen($s[$i+1]);
    +            }
    +        }
    +        else $charlist = ($len - 1) / 2;
    +
    +        return $charlist;
    +    }
    +
    +    static function strcmp       ($a, $b) {return (string) $a === (string) $b ? 0 : strcmp(n::normalize($a, n::NFD), n::normalize($b, n::NFD));}
    +    static function strnatcmp    ($a, $b) {return (string) $a === (string) $b ? 0 : strnatcmp(self::strtonatfold($a), self::strtonatfold($b));}
    +    static function strcasecmp   ($a, $b) {return self::strcmp   (self::strtocasefold($a), self::strtocasefold($b));}
    +    static function strnatcasecmp($a, $b) {return self::strnatcmp(self::strtocasefold($a), self::strtocasefold($b));}
    +    static function strncasecmp  ($a, $b, $len) {return self::strncmp(self::strtocasefold($a), self::strtocasefold($b), $len);}
    +    static function strncmp      ($a, $b, $len) {return self::strcmp(self::substr($a, 0, $len), self::substr($b, 0, $len));}
    +
    +    static function strcspn($s, $charlist, $start = 0, $len = 2147483647)
    +    {
    +        if ('' === (string) $charlist) return null;
    +        if ($start || 2147483647 != $len) $s = self::substr($s, $start, $len);
    +
    +        return preg_match('/^(.*?)' . self::rxClass($charlist) . '/us', $s, $len) ? grapheme_strlen($len[1]) : grapheme_strlen($s);
    +    }
    +
    +    static function strpbrk($s, $charlist)
    +    {
    +        if (preg_match('/' . self::rxClass($charlist) . '/us', $s, $m)) return substr($s, strpos($s, $m[0]));
    +        else return false;
    +    }
    +
    +    static function strrev($s)
    +    {
    +        $s = self::str_split($s);
    +        return implode('', array_reverse($s));
    +    }
    +
    +    static function strspn($s, $mask, $start = 0, $len = 2147483647)
    +    {
    +        if ($start || 2147483647 != $len) $s = self::substr($s, $start, $len);
    +        return preg_match('/^' . self::rxClass($mask) . '+/u', $s, $s) ? grapheme_strlen($s[0]) : 0;
    +    }
    +
    +    static function strtr($s, $from, $to = INF)
    +    {
    +        if (INF !== $to)
    +        {
    +            $from = self::str_split($from);
    +            $to   = self::str_split($to);
    +
    +            $a = count($from);
    +            $b = count($to);
    +
    +                 if ($a > $b) $from = array_slice($from, 0, $b);
    +            else if ($a < $b) $to   = array_slice($to  , 0, $a);
    +
    +            $from = array_combine($from, $to);
    +        }
    +
    +        return strtr($s, $from);
    +    }
    +
    +    static function substr_compare($a, $b, $offset, $len = 2147483647, $i = 0)
    +    {
    +        $a = self::substr($a, $offset, $len);
    +        return $i ? self::strcasecmp($a, $b) : self::strcmp($a, $b);
    +    }
    +
    +    static function substr_count($s, $needle, $offset = 0, $len = 2147483647)
    +    {
    +        return substr_count(self::substr($s, $offset, $len), $needle);
    +    }
    +
    +    static function substr_replace($s, $replace, $start, $len = 2147483647)
    +    {
    +        $s       = self::str_split($s);
    +        $replace = self::str_split($replace);
    +        array_splice($s, $start, $len, $replace);
    +        return implode('', $s);
    +    }
    +
    +    static function ucfirst($s)
    +    {
    +        $c = iconv_substr($s, 0, 1, 'UTF-8');
    +        return self::ucwords($c) . substr($s, strlen($c));
    +    }
    +
    +    static function lcfirst($s)
    +    {
    +        $c = iconv_substr($s, 0, 1, 'UTF-8');
    +        return mb_strtolower($c, 'UTF-8') . substr($s, strlen($c));
    +    }
    +
    +    static function ucwords($s)
    +    {
    +        return mb_convert_case($s, MB_CASE_TITLE, 'UTF-8');
    +    }
    +
    +    static function number_format($number, $decimals = 0, $dec_point = '.', $thousands_sep = ',')
    +    {
    +/**/    if (PHP_VERSION_ID < 50400)
    +/**/    {
    +            if (isset($thousands_sep[1]) || isset($dec_point[1]))
    +            {
    +                return str_replace(
    +                    array('.', ','),
    +                    array($dec_point, $thousands_sep),
    +                    number_format($number, $decimals, '.', ',')
    +                );
    +            }
    +/**/    }
    +
    +        return number_format($number, $decimals, $dec_point, $thousands_sep);
    +    }
    +
    +    static function utf8_encode($s)
    +    {
    +        $s = utf8_encode($s);
    +        if (false === strpos($s, "\xC2")) return $s;
    +        else return str_replace(self::$cp1252, self::$utf8, $s);
    +    }
    +
    +    static function utf8_decode($s)
    +    {
    +        $s = str_replace(self::$utf8, self::$cp1252, $s);
    +        return utf8_decode($s);
    +    }
    +
    +
    +    protected static function rxClass($s, $class = '')
    +    {
    +        $class = array($class);
    +
    +        foreach (self::str_split($s) as $s)
    +        {
    +            if ('-' === $s) $class[0] = '-' . $class[0];
    +            else if (!isset($s[2])) $class[0] .= preg_quote($s, '/');
    +            else if (1 === iconv_strlen($s, 'UTF-8')) $class[0] .= $s;
    +            else $class[] = $s;
    +        }
    +
    +        $class[0] = '[' . $class[0] . ']';
    +
    +        if (1 === count($class)) return $class[0];
    +        else return '(?:' . implode('|', $class) . ')';
    +    }
    +
    +    protected static function getData($file)
    +    {
    +        $file = __DIR__ . '/Utf8/data/' . $file . '.ser';
    +        if (file_exists($file)) return unserialize(file_get_contents($file));
    +        else return false;
    +    }
    +}
    diff --git a/vendor/patchwork/utf8/class/Patchwork/Utf8/Bootup.php b/vendor/patchwork/utf8/class/Patchwork/Utf8/Bootup.php
    new file mode 100644
    index 0000000..015b6ac
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/Utf8/Bootup.php
    @@ -0,0 +1,223 @@
    += "\x80" && false !== $w && isset($pre_lead_comb[0]) && preg_match('/^\p{Mn}/u', $v))
    +                    {
    +                        // Prevent leading combining chars
    +                        // for NFC-safe concatenations.
    +                        $v = $pre_lead_comb . $v;
    +                    }
    +                }
    +            }
    +
    +            reset($a[$i]);
    +            unset($a[$i]);
    +        }
    +    }
    +}
    diff --git a/vendor/patchwork/utf8/class/Patchwork/Utf8/Bootup/iconv.php b/vendor/patchwork/utf8/class/Patchwork/Utf8/Bootup/iconv.php
    new file mode 100644
    index 0000000..2d2d6b2
    --- /dev/null
    +++ b/vendor/patchwork/utf8/class/Patchwork/Utf8/Bootup/iconv.php
    @@ -0,0 +1,48 @@
    +";i:204;s:2:"<<";i:205;s:2:">>";i:206;s:1:"[";i:207;s:1:"]";i:208;s:1:"[";i:209;s:1:"]";i:210;s:1:"[";i:211;s:1:"]";i:212;s:1:",";i:213;s:1:".";i:214;s:1:"[";i:215;s:1:"]";i:216;s:2:"<<";i:217;s:2:">>";i:218;s:1:"<";i:219;s:1:">";i:220;s:1:"/";i:221;s:2:"||";i:222;s:2:"((";i:223;s:2:"))";}}
    \ No newline at end of file
    diff --git a/vendor/patchwork/utf8/composer.json b/vendor/patchwork/utf8/composer.json
    new file mode 100644
    index 0000000..2180c25
    --- /dev/null
    +++ b/vendor/patchwork/utf8/composer.json
    @@ -0,0 +1,24 @@
    +{
    +    "name": "patchwork/utf8",
    +    "type": "library",
    +    "description": "UTF-8 strings handling for PHP 5.3: portable, performant and extended",
    +    "keywords": ["utf8","utf-8","unicode","i18n"],
    +    "homepage": "https://github.com/nicolas-grekas/Patchwork-UTF8",
    +    "license": "(Apache-2.0 or GPL-2.0)",
    +    "authors": [
    +        {
    +            "name": "Nicolas Grekas",
    +            "email": "p@tchwork.com",
    +            "role": "Developer"
    +        }
    +    ],
    +    "require": {
    +        "php": ">=5.3.0"
    +    },
    +    "autoload": {
    +        "psr-0": {
    +            "Patchwork": "class/",
    +            "Normalizer": "class/"
    +        }
    +    }
    +}
    diff --git a/vendor/predis/predis/.gitignore b/vendor/predis/predis/.gitignore
    new file mode 100644
    index 0000000..dacaf3a
    --- /dev/null
    +++ b/vendor/predis/predis/.gitignore
    @@ -0,0 +1,7 @@
    +*.tgz
    +*.phar
    +phpunit.xml
    +package.xml
    +composer.lock
    +experiments/
    +vendor/
    diff --git a/vendor/predis/predis/.travis.yml b/vendor/predis/predis/.travis.yml
    new file mode 100644
    index 0000000..0842b04
    --- /dev/null
    +++ b/vendor/predis/predis/.travis.yml
    @@ -0,0 +1,13 @@
    +language: php
    +php:
    +  - 5.3
    +  - 5.4
    +  - 5.5
    +branches:
    +  except:
    +    - v0.5
    +    - v0.6
    +    - php5.2_backport
    +    - documentation
    +services: redis-server
    +script: phpunit -c phpunit.xml.travisci
    diff --git a/vendor/predis/predis/CHANGELOG.NAMING.md b/vendor/predis/predis/CHANGELOG.NAMING.md
    new file mode 100644
    index 0000000..2edcaa2
    --- /dev/null
    +++ b/vendor/predis/predis/CHANGELOG.NAMING.md
    @@ -0,0 +1,53 @@
    +# Namespaces, interfaces and classes renamed in Predis v0.8 #
    +____________________________________________
    +
    +Some namespaces, interfaces and classes in Predis v0.8 have been renamed to follow a common rule inspired
    +by the naming conventions adopted by the Symfony2 project. This is a list of all the changes:
    +
    +### Renamed namespaces ###
    +
    +  - `Predis\Network` => `Predis\Connection`
    +  - `Predis\Profiles` => `Predis\Profile`
    +  - `Predis\Iterators` => `Predis\Iterator`
    +  - `Predis\Options` => `Predis\Option`
    +  - `Predis\Commands` => `Predis\Command`
    +  - `Predis\Commands\Processors` => `Predis\Command\Processor`
    +
    +### Renamed interfaces ###
    +
    +  - `Predis\IReplyObject` => `Predis\ResponseObjectInterface`
    +  - `Predis\IRedisServerError` => `Predis\ResponseErrorInterface`
    +  - `Predis\Options\IOption` => `Predis\Option\OptionInterface`
    +  - `Predis\Options\IClientOptions` => `Predis\Option\ClientOptionsInterface`
    +  - `Predis\Profile\IServerProfile` => `Predis\Profile\ServerProfileInterface`
    +  - `Predis\Pipeline\IPipelineExecutor` => `Predis\Pipeline\PipelineExecutorInterface`
    +  - `Predis\Distribution\INodeKeyGenerator` => `Predis\Distribution\HashGeneratorInterface`
    +  - `Predis\Distribution\IDistributionStrategy` => `Predis\Distribution\DistributionStrategyInterface`
    +  - `Predis\Protocol\IProtocolProcessor` => `Predis\Protocol\ProtocolInterface`
    +  - `Predis\Protocol\IResponseReader` => `Predis\Protocol\ResponseReaderInterface`
    +  - `Predis\Protocol\IResponseHandler` => `Predis\Protocol\ResponseHandlerInterface`
    +  - `Predis\Protocol\ICommandSerializer` => `Predis\Protocol\CommandSerializerInterface`
    +  - `Predis\Protocol\IComposableProtocolProcessor` => `Predis\Protocol\ComposableProtocolInterface`
    +  - `Predis\Network\IConnection` => `Predis\Connection\ConnectionInterface`
    +  - `Predis\Network\IConnectionSingle` => `Predis\Connection\SingleConnectionInterface`
    +  - `Predis\Network\IConnectionComposable` => `Predis\Connection\ComposableConnectionInterface`
    +  - `Predis\Network\IConnectionCluster` => `Predis\Connection\ClusterConnectionInterface`
    +  - `Predis\Network\IConnectionReplication` => `Predis\Connection\ReplicationConnectionInterface`
    +  - `Predis\Commands\ICommand` => `Predis\Command\CommandInterface`
    +  - `Predis\Commands\IPrefixable` => `Predis\Command\PrefixableCommandInterface`
    +  - `Predis\Command\Processor\ICommandProcessor` => `Predis\Command\Processor\CommandProcessorInterface`
    +  - `Predis\Command\Processor\ICommandProcessorChain` => `Predis\Command\Processor\CommandProcessorChainInterface`
    +  - `Predis\Command\Processor\IProcessingSupport` => `Predis\Command\Processor\CommandProcessingInterface`
    +
    +### Renamed classes ###
    +
    +  - `Predis\Commands\Command` => `Predis\Command\AbstractCommand`
    +  - `Predis\Network\ConnectionBase` => `Predis\Connection\AbstractConnection`
    +
    +### Classes or interfaces moved to different namespaces ###
    +
    +  - `Predis\MonitorContext` => `Predis\Monitor\MonitorContext`
    +  - `Predis\ConnectionParameters` => `Predis\Connection\ConnectionParameters`
    +  - `Predis\ConnectionParametersInterface` => `Predis\Connection\ConnectionParametersInterface`
    +  - `Predis\ConnectionFactory` => `Predis\Connection\ConnectionFactory`
    +  - `Predis\ConnectionFactoryInterface` => `Predis\Connection\ConnectionFactoryInterface`
    diff --git a/vendor/predis/predis/CHANGELOG.md b/vendor/predis/predis/CHANGELOG.md
    new file mode 100644
    index 0000000..273564e
    --- /dev/null
    +++ b/vendor/predis/predis/CHANGELOG.md
    @@ -0,0 +1,573 @@
    +v0.8.4 (2013-07-27)
    +===============================================================================
    +
    +- Added `DUMP` and `RESTORE` to the server profile for Redis 2.6.
    +
    +- Connection exceptions now report basic host details in their messages.
    +
    +- Allow `Predis\Connection\PhpiredisConnection` to use a random IP when a host
    +  actually has several IPs (ISSUE #116).
    +
    +- __FIX__: allow `HMSET` when using a cluster of Redis nodes with client-side
    +  sharding or redis-cluster (ISSUE #106).
    +
    +- __FIX__: set `WITHSCORES` modifer for `ZRANGE`, `ZREVRANGE`, `ZRANGEBYSCORE`
    +  and `ZREVRANGEBYSCORE` only when the options array passed to these commands
    +  has `WITHSCORES` set to `true` (ISSUE #107).
    +
    +- __FIX__: scripted commands falling back from `EVALSHA` to `EVAL` resulted in
    +  PHP errors when using a prefixed client (ISSUE #109).
    +
    +- __FIX__: `Predis\PubSub\DispatcherLoop` now works properly when using key
    +  prefixing (ISSUE #114).
    +
    +
    +v0.8.3 (2013-02-18)
    +===============================================================================
    +
    +- Added `CLIENT SETNAME` and `CLIENT GETNAME` (ISSUE #102).
    +
    +- Implemented the `Predis\Connection\PhpiredisStreamConnection` class using the
    +  `phpiredis` extension like `Predis\Connection\PhpiredisStreamConnection`, but
    +  without requiring the `socket` extension since it relies on PHP's streams.
    +
    +- Added support for the TCP_NODELAY flag via the `tcp_nodelay` parameter for
    +  stream-based connections, namely `Predis\Connection\StreamConnection` and
    +  `Predis\Connection\PhpiredisStreamConnection` (requires PHP >= 5.4.0).
    +
    +- Updated the aggregated connection class for redis-cluster to work with 16384
    +  hash slots instead of 4096 to reflect the recent change from redis unstable
    +  ([see this commit](https://github.com/antirez/redis/commit/ebd666d)).
    +
    +- The constructor of `Predis\Client` now accepts a callable as first argument
    +  returning `Predis\Connection\ConnectionInterface`. Users can create their
    +  own self-contained strategies to create and set up the underlying connection.
    +
    +- Users should return `0` from `Predis\Command\ScriptedCommand::getKeysCount()`
    +  instead of `FALSE` to indicate that all of the arguments of a Lua script must
    +  be used to populate `ARGV[]`. This does not represent a breaking change.
    +
    +- The `Predis\Helpers` class has been deprecated and it will be removed in
    +  future releases.
    +
    +
    +v0.8.2 (2013-02-03)
    +===============================================================================
    +
    +- Added `Predis\Session\SessionHandler` to make it easy to store PHP sessions
    +  on Redis using Predis. Please note that this class needs either PHP >= 5.4.0
    +  or a polyfill for PHP's `SessionHandlerInterface`.
    +
    +- Added the ability to get the default value of a client option directly from
    +  `Predis\Option\ClientOption` using the `getDefault()` method by passing the
    +  option name or its instance.
    +
    +- __FIX__: the standard pipeline executor was not using the response parser
    +  methods associated to commands to process raw responses (ISSUE #101).
    +
    +
    +v0.8.1 (2013-01-19)
    +===============================================================================
    +
    +- The `connections` client option can now accept a callable object returning
    +  an instance of `Predis\Connection\ConnectionFactoryInterface`.
    +
    +- Client options accepting callable objects as factories now pass their actual
    +  instance to the callable as the second argument.
    +
    +- `Predis\Command\Processor\KeyPrefixProcessor` can now be directly casted to
    +  string to obtain the current prefix, useful with string interpolation.
    +
    +- Added an optional callable argument to `Predis\Cluster\Distribution\HashRing`
    +  and `Predis\Cluster\Distribution\KetamaPureRing` constructor that can be used
    +  to customize how the distributor should extract the connection hash when
    +  initializing the nodes distribution (ISSUE #36).
    +
    +- Correctly handle `TTL` and `PTTL` returning -2 on non existing keys starting
    +  with Redis 2.8.
    +
    +- __FIX__: a missing use directive in `Predis\Transaction\MultiExecContext`
    +  caused PHP errors when Redis did not return `+QUEUED` replies to commands
    +  when inside a MULTI / EXEC context.
    +
    +- __FIX__: the `parseResponse()` method implemented for a scripted command was
    +  ignored when retrying to execute a Lua script by falling back to `EVAL` after
    +  a `-NOSCRIPT` error (ISSUE #94).
    +
    +- __FIX__: when subclassing `Predis\Client` the `getClientFor()` method returns
    +  a new instance of the subclass instead of a new instance of `Predis\Client`.
    +
    +
    +v0.8.0 (2012-10-23)
    +===============================================================================
    +
    +- The default server profile for Redis is now `2.6`.
    +
    +- Certain connection parameters have been renamed:
    +
    +  - `connection_async` is now `async_connect`
    +  - `connection_timeout` is now `timeout`
    +  - `connection_persistent` is now `persistent`
    +
    +- The `throw_errors` connection parameter has been removed and replaced by the
    +  new `exceptions` client option since exceptions on `-ERR` replies returned by
    +  Redis are not generated by connection classes anymore but instead are thrown
    +  by the client class and other abstractions such as pipeline contexts.
    +
    +- Added smart support for redis-cluster (Redis v3.0) in addition to the usual
    +  cluster implementation that uses client-side sharding.
    +
    +- Various namespaces and classes have been renamed to follow rules inspired by
    +  the Symfony2 naming conventions.
    +
    +- The second argument of the constructor of `Predis\Client` does not accept
    +  strings or instances of `Predis\Profile\ServerProfileInterface` anymore.
    +  To specify a server profile you must explicitly set `profile` in the array
    +  of client options.
    +
    +- `Predis\Command\ScriptedCommand` internally relies on `EVALSHA` instead of
    +  `EVAL` thus avoiding to send Lua scripts bodies on each request. The client
    +  automatically resends the command falling back to `EVAL` when Redis returns a
    +  `-NOSCRIPT` error. Automatic fallback to `EVAL` does not work with pipelines,
    +  inside a `MULTI / EXEC` context or with plain `EVALSHA` commands.
    +
    +- Complex responses are no more parsed by connection classes as they must be
    +  processed by consumer classes using the handler associated to the issued
    +  command. This means that executing commands directly on connections only
    +  returns simple Redis types, but nothing changes when using `Predis\Client`
    +  or the provided abstractions for pipelines and transactions.
    +
    +- Iterators for multi-bulk replies now skip the response parsing method of the
    +  command that generated the response and are passed directly to user code.
    +  Pipeline and transaction objects still consume automatically iterators.
    +
    +- Cluster and replication connections now extend a new common interface,
    +  `Predis\Connection\AggregatedConnectionInterface`.
    +
    +- `Predis\Connection\MasterSlaveReplication` now uses an external strategy
    +  class to handle the logic for checking readable / writable commands and Lua
    +  scripts.
    +
    +- Command pipelines have been optimized for both speed and code cleanness, but
    +  at the cost of bringing a breaking change in the signature of the interface
    +  for pipeline executors.
    +
    +- Added a new pipeline executor that sends commands wrapped in a MULTI / EXEC
    +  context to make the execution atomic: if a pipeline fails at a certain point
    +  then the whole pipeline is discarded.
    +
    +- The key-hashing mechanism for commands is now handled externally and is no
    +  more a competence of each command class. This change is neeeded to support
    +  both client-side sharding and Redis cluster.
    +
    +- `Predis\Options\Option` is now abstract, see `Predis\Option\AbstractOption`.
    +
    +
    +v0.7.3 (2012-06-01)
    +===============================================================================
    +
    +- New commands available in the Redis v2.6 profile (dev): `BITOP`, `BITCOUNT`.
    +
    +- When the number of keys `Predis\Commands\ScriptedCommand` is negative, Predis
    +  will count from the end of the arguments list to calculate the actual number
    +  of keys that will be interpreted as elements for `KEYS` by the underlying
    +  `EVAL` command.
    +
    +- __FIX__: `examples\CustomDistributionStrategy.php` had a mistyped constructor
    +  call and produced a bad distribution due to an error as pointed in ISSUE #63.
    +  This bug is limited to the above mentioned example and does not affect the
    +  classes implemented in the `Predis\Distribution` namespace.
    +
    +- __FIX__: `Predis\Commands\ServerEvalSHA::getScriptHash()` was calculating the
    +  hash while it just needs to return the first argument of the command.
    +
    +- __FIX__: `Predis\Autoloader` has been modified to allow cascading autoloaders
    +  for the `Predis` namespace.
    +
    +
    +v0.7.2 (2012-04-01)
    +===============================================================================
    +
    +- Added `2.6` in the server profiles aliases list for the upcoming Redis 2.6.
    +  `2.4` is still the default server profile. `dev` now targets Redis 2.8.
    +
    +- Connection instances can be serialized and unserialized using `serialize()`
    +  and `unserialize()`. This is handy in certain scenarios such as client-side
    +  clustering or replication to lower the overhead of initializing a connection
    +  object with many sub-connections since unserializing them can be up to 5x
    +  times faster.
    +
    +- Reworked the default autoloader to make it faster. It is also possible to
    +  prepend it in PHP's autoload stack.
    +
    +- __FIX__: fixed parsing of the payload returned by `MONITOR` with Redis 2.6.
    +
    +
    +v0.7.1 (2011-12-27)
    +===============================================================================
    +
    +- The PEAR channel on PearHub has been deprecated in favour of `pear.nrk.io`.
    +
    +- Miscellaneous minor fixes.
    +
    +- Added transparent support for master / slave replication configurations where
    +  write operations are performed on the master server and read operations are
    +  routed to one of the slaves. Please refer to ISSUE #21 for a bit of history
    +  and more details about replication support in Predis.
    +
    +- The `profile` client option now accepts a callable object used to initialize
    +  a new instance of `Predis\Profiles\IServerProfile`.
    +
    +- Exposed a method for MULTI / EXEC contexts that adds the ability to execute
    +  instances of Redis commands against transaction objects.
    +
    +
    +v0.7.0 (2011-12-11)
    +===============================================================================
    +
    +- Predis now adheres to the PSR-0 standard which means that there is no more a
    +  single file holding all the classes of the library, but multiple files (one
    +  for each class). You can use any PSR-0 compatible autoloader to load Predis
    +  or just leverage the default one shipped with the library by requiring the
    +  `Predis/Autoloader.php` and call `Predis\Autoloader::register()`.
    +
    +- The default server profile for Redis is now 2.4. The `dev` profile supports
    +  all the features of Redis 2.6 (currently unstable) such as Lua scripting.
    +
    +- Support for long aliases (method names) for Redis commands has been dropped.
    +
    +- Redis 1.0 is no more supported. From now on Predis will use only the unified
    +  protocol to serialize commands.
    +
    +- It is possible to prefix keys transparently on a client-level basis with the
    +  new `prefix` client option.
    +
    +- An external connection factory is used to initialize new connection instances
    +  and developers can now register their own connection classes using the new
    +  `connections` client option.
    +
    +- It is possible to connect locally to Redis using UNIX domain sockets. Just
    +  use `unix:///path/to/redis.sock` or a named array just like in the following
    +  example: `array('scheme' => 'unix', 'path' => '/path/to/redis.sock');`.
    +
    +- If the `phpiredis` extension is loaded by PHP, it is now possible to use an
    +  alternative connection class that leverages it to make Predis faster on many
    +  cases, especially when dealing with big multibulk replies, with the the only
    +  downside that persistent connections are not supported. Please refer to the
    +  documentation to see how to activate this class using the new `connections`
    +  client option.
    +
    +- Predis is capable to talk with Webdis, albeit with some limitations such as
    +  the lack of pipelining and transactions, just by using the `http` scheme in
    +  in the connection parameters. All is needed is PHP with the `curl` and the
    +  `phpiredis` extensions loaded.
    +
    +- Way too many changes in the public API to make a list here, we just tried to
    +  make all the Redis commands compatible with previous releases of v0.6 so that
    +  you do not have to worry if you are simply using Predis as a client. Probably
    +  the only breaking changes that should be mentioned here are:
    +
    +  - `throw_on_error` has been renamed to `throw_errors` and it is a connection
    +    parameter instead of a client option, along with `iterable_multibulk`.
    +
    +  - `key_distribution` has been removed from the client options. To customize
    +    the distribution strategy you must provide a callable object to the new
    +    `cluster` client option to configure and then return a new instance of
    +    `Predis\Network\IConnectionCluster`.
    +
    +  - `Predis\Client::create()` has been removed. Just use the constructor to set
    +    up a new instance of `Predis\Client`.
    +
    +  - `Predis\Client::pipelineSafe()` was deprecated in Predis v0.6.1 and now has
    +    finally removed. Use `Predis\Client::pipeline(array('safe' => true))`.
    +
    +  - `Predis\Client::rawCommand()` has been removed due to inconsistencies with
    +    the underlying connection abstractions. You can still get the raw resource
    +    out of a connection with `Predis\Network\IConnectionSingle::getResource()`
    +    so that you can talk directly with Redis.
    +
    +- The `Predis\MultiBulkCommand` class has been merged into `Predis\Command` and
    +  thus removed. Serialization of commands is now a competence of connections.
    +
    +- The `Predis\IConnection` interface has been splitted into two new interfaces:
    +  `Predis\Network\IConnectionSingle` and `Predis\Network\IConnectionCluster`.
    +
    +- The constructor of `Predis\Client` now accepts more type of arguments such as
    +  instances of `Predis\IConnectionParameters` and `Predis\Network\IConnection`.
    +
    +
    +v0.6.6 (2011-04-01)
    +===============================================================================
    +
    +- Switched to Redis 2.2 as the default server profile (there are no changes
    +  that would break compatibility with previous releases). Long command names
    +  are no more supported by default but if you need them you can still require
    +  `Predis_Compatibility.php` to avoid breaking compatibility.
    +
    +- Added a `VERSION` constant to `Predis\Client`.
    +
    +- Some performance improvements for multibulk replies (parsing them is about
    +  16% faster than the previous version). A few core classes have been heavily
    +  optimized to reduce overhead when creating new instances.
    +
    +- Predis now uses by default a new protocol reader, more lightweight and
    +  faster than the default handler-based one. Users can revert to the old
    +  protocol reader with the `reader` client option set to `composable`.
    +  This client option can also accept custom reader classes implementing the
    +  new `Predis\IResponseReader` interface.
    +
    +- Added support for connecting to Redis using UNIX domain sockets (ISSUE #25).
    +
    +- The `read_write_timeout` connection parameter can now be set to 0 or false
    +  to disable read and write timeouts on connections. The old behaviour of -1
    +  is still intact.
    +
    +- `ZUNIONSTORE` and `ZINTERSTORE` can accept an array to specify a list of the
    +  source keys to be used to populate the destination key.
    +
    +- `MGET`, `SINTER`, `SUNION` and `SDIFF` can accept an array to specify a list
    +  of keys. `SINTERSTORE`, `SUNIONSTORE` and `SDIFFSTORE` can also accept an
    +  array to specify the list of source keys.
    +
    +- `SUBSCRIBE` and `PSUBSCRIBE` can accept a list of channels for subscription.
    +
    +- __FIX__: some client-side clean-ups for `MULTI/EXEC` were handled incorrectly
    +  in a couple of corner cases (ISSUE #27).
    +
    +
    +v0.6.5 (2011-02-12)
    +===============================================================================
    +
    +- __FIX__: due to an untested internal change introduced in v0.6.4, a wrong
    +  handling of bulk reads of zero-length values was producing protocol
    +  desynchronization errors (ISSUE #20).
    +
    +
    +v0.6.4 (2011-02-12)
    +===============================================================================
    +
    +- Various performance improvements (15% ~ 25%) especially when dealing with
    +  long multibulk replies or when using clustered connections.
    +
    +- Added the `on_retry` option to `Predis\MultiExecBlock` that can be used to
    +  specify an external callback (or any callable object) that gets invoked
    +  whenever a transaction is aborted by the server.
    +
    +- Added inline (p)subscribtion via options when initializing an instance of
    +  `Predis\PubSubContext`.
    +
    +
    +v0.6.3 (2011-01-01)
    +===============================================================================
    +
    +- New commands available in the Redis v2.2 profile (dev):
    +  - Strings: `SETRANGE`, `GETRANGE`, `SETBIT`, `GETBIT`
    +  - Lists  : `BRPOPLPUSH`
    +
    +- The abstraction for `MULTI/EXEC` transactions has been dramatically improved
    +  by providing support for check-and-set (CAS) operations when using Redis >=
    +  2.2. Aborted transactions can also be optionally replayed in automatic up
    +  to a user-defined number of times, after which a `Predis\AbortedMultiExec`
    +  exception is thrown.
    +
    +
    +v0.6.2 (2010-11-28)
    +===============================================================================
    +
    +- Minor internal improvements and clean ups.
    +
    +- New commands available in the Redis v2.2 profile (dev):
    +  - Strings: `STRLEN`
    +  - Lists  : `LINSERT`, `RPUSHX`, `LPUSHX`
    +  - ZSets  : `ZREVRANGEBYSCORE`
    +  - Misc.  : `PERSIST`
    +
    +- WATCH also accepts a single array parameter with the keys that should be
    +  monitored during a transaction.
    +
    +- Improved the behaviour of `Predis\MultiExecBlock` in certain corner cases.
    +
    +- Improved parameters checking for the SORT command.
    +
    +- __FIX__: the `STORE` parameter for the `SORT` command didn't work correctly
    +  when using `0` as the target key (ISSUE #13).
    +
    +- __FIX__: the methods for `UNWATCH` and `DISCARD` do not break anymore method
    +  chaining with `Predis\MultiExecBlock`.
    +
    +
    +v0.6.1 (2010-07-11)
    +===============================================================================
    +
    +- Minor internal improvements and clean ups.
    +
    +- New commands available in the Redis v2.2 profile (dev):
    +  - Misc.  : `WATCH`, `UNWATCH`
    +
    +- Optional modifiers for `ZRANGE`, `ZREVRANGE` and `ZRANGEBYSCORE` queries are
    +  supported using an associative array passed as the last argument of their
    +  respective methods.
    +
    +- The `LIMIT` modifier for `ZRANGEBYSCORE` can be specified using either:
    +  - an indexed array: `array($offset, $count)`
    +  - an associative array: `array('offset' => $offset, 'count' => $count)`
    +
    +- The method `Predis\Client::__construct()` now accepts also instances of
    +  `Predis\ConnectionParameters`.
    +
    +- `Predis\MultiExecBlock` and `Predis\PubSubContext` now throw an exception
    +  when trying to create their instances using a profile that does not
    +  support the required Redis commands or when the client is connected to
    +  a cluster of connections.
    +
    +- Various improvements to `Predis\MultiExecBlock`:
    +  - fixes and more consistent behaviour across various usage cases.
    +  - support for `WATCH` and `UNWATCH` when using the current development
    +    profile (Redis v2.2) and aborted transactions.
    +
    +- New signature for `Predis\Client::multiExec()` which is now able to accept
    +  an array of options for the underlying instance of `Predis\MultiExecBlock`.
    +  Backwards compatibility with previous releases of Predis is ensured.
    +
    +- New signature for `Predis\Client::pipeline()` which is now able to accept
    +  an array of options for the underlying instance of Predis\CommandPipeline.
    +  Backwards compatibility with previous releases of Predis is ensured.
    +  The method `Predis\Client::pipelineSafe()` is to be considered deprecated.
    +
    +- __FIX__: The `WEIGHT` modifier for `ZUNIONSTORE` and `ZINTERSTORE` was
    +  handled incorrectly with more than two weights specified.
    +
    +
    +v0.6.0 (2010-05-24)
    +===============================================================================
    +
    +- Switched to the new multi-bulk request protocol for all of the commands
    +  in the Redis 1.2 and Redis 2.0 profiles. Inline and bulk requests are now
    +  deprecated as they will be removed in future releases of Redis.
    +
    +- The default server profile is `2.0` (targeting Redis 2.0.x). If you are
    +  using older versions of Redis, it is highly recommended that you specify
    +  which server profile the client should use (e.g. `1.2` when connecting
    +  to instances of Redis 1.2.x).
    +
    +- Support for Redis 1.0 is now optional and it is provided by requiring
    +  'Predis_Compatibility.php' before creating an instance of `Predis\Client`.
    +
    +- New commands added to the Redis 2.0 profile since Predis 0.5.1:
    +  - Strings: `SETEX`, `APPEND`, `SUBSTR`
    +  - ZSets  : `ZCOUNT`, `ZRANK`, `ZUNIONSTORE`, `ZINTERSTORE`, `ZREMBYRANK`,
    +             `ZREVRANK`
    +  - Hashes : `HSET`, `HSETNX`, `HMSET`, `HINCRBY`, `HGET`, `HMGET`, `HDEL`,
    +             `HEXISTS`, `HLEN`, `HKEYS`, `HVALS`, `HGETALL`
    +  - PubSub : `PUBLISH`, `SUBSCRIBE`, `UNSUBSCRIBE`
    +  - Misc.  : `DISCARD`, `CONFIG`
    +
    +- Introduced client-level options with the new `Predis\ClientOptions` class.
    +  Options can be passed to the constructor of `Predis\Client` in its second
    +  argument as an array or an instance of `Predis\ClientOptions`. For brevity's
    +  sake and compatibility with older versions, the constructor still accepts
    +  an instance of `Predis\RedisServerProfile` in its second argument. The
    +  currently supported client options are:
    +
    +  - `profile` [default: `2.0` as of Predis 0.6.0]: specifies which server
    +    profile to use when connecting to Redis. This option accepts an instance
    +    of `Predis\RedisServerProfile` or a string that indicates the version.
    +
    +  - `key_distribution` [default: `Predis\Distribution\HashRing`]: specifies
    +    which key distribution strategy to use to distribute keys among the
    +    servers that compose a cluster. This option accepts an instance of
    +    `Predis\Distribution\IDistributionStrategy` so that users can implement
    +    their own key distribution strategy. `Predis\Distribution\KetamaPureRing`
    +    is an alternative distribution strategy providing a pure-PHP implementation
    +    of the same algorithm used by libketama.
    +
    +  - `throw_on_error` [default: `TRUE`]: server errors can optionally be handled
    +    "silently": instead of throwing an exception, the client returns an error
    +    response type.
    +
    +  - `iterable_multibulk` [EXPERIMENTAL - default: `FALSE`]: in addition to the
    +    classic way of fetching a whole multibulk reply into an array, the client
    +    can now optionally stream a multibulk reply down to the user code by using
    +    PHP iterators. It is just a little bit slower, but it can save a lot of
    +    memory in certain scenarios.
    +
    +- New parameters for connections:
    +
    +  - `alias` [default: not set]: every connection can now be identified by an
    +    alias that is useful to get a specific connections when connected to a
    +    cluster of Redis servers.
    +  - `weight` [default: not set]: allows to balance keys asymmetrically across
    +    multiple servers. This is useful when you have servers with different
    +    amounts of memory to distribute the load of your keys accordingly.
    +  - `connection_async` [default: `FALSE`]: estabilish connections to servers
    +    in a non-blocking way, so that the client is not blocked while the socket
    +    resource performs the actual connection.
    +  - `connection_persistent` [default: `FALSE`]: the underlying socket resource
    +    is left open when a script ends its lifecycle. Persistent connections can
    +    lead to unpredictable or strange behaviours, so they should be used with
    +    extreme care.
    +
    +- Introduced the `Predis\Pipeline\IPipelineExecutor` interface. Classes that
    +  implements this interface are used internally by the `Predis\CommandPipeline`
    +  class to change the behaviour of the pipeline when writing/reading commands
    +  from one or multiple servers. Here is the list of the default executors:
    +
    +  - `Predis\Pipeline\StandardExecutor`: exceptions generated by server errors
    +    might be thrown depending on the options passed to the client (see the
    +    `throw_on_error` client option). Instead, protocol or network errors always
    +    throw exceptions. This is the default executor for single and clustered
    +    connections and shares the same behaviour of Predis 0.5.x.
    +  - `Predis\Pipeline\SafeExecutor`: exceptions generated by server, protocol
    +    or network errors are not thrown but returned in the response array as
    +    instances of `Predis\ResponseError` or `Predis\CommunicationException`.
    +  - `Predis\Pipeline\SafeClusterExecutor`: this executor shares the same
    +    behaviour of `Predis\Pipeline\SafeExecutor` but it is geared towards
    +    clustered connections.
    +
    +- Support for PUB/SUB is handled by the new `Predis\PubSubContext` class, which
    +  could also be used to build a callback dispatcher for PUB/SUB scenarios.
    +
    +- When connected to a cluster of connections, it is now possible to get a
    +  new `Predis\Client` instance for a single connection of the cluster by
    +  passing its alias/index to the new `Predis\Client::getClientFor()` method.
    +
    +- `Predis\CommandPipeline` and `Predis\MultiExecBlock` return their instances
    +  when invokink commands, thus allowing method chaining in pipelines and
    +  multi-exec blocks.
    +
    +- `Predis\MultiExecBlock` can handle the new `DISCARD` command.
    +
    +- Connections now support float values for the `connection_timeout` parameter
    +  to express timeouts with a microsecond resolution.
    +
    +- __FIX__: TCP connections now respect the read/write timeout parameter when
    +  reading the payload of server responses. Previously, `stream_get_contents()`
    +  was being used internally to read data from a connection but it looks like
    +  PHP does not honour the specified timeout for socket streams when inside
    +  this function.
    +
    +- __FIX__: The `GET` parameter for the `SORT` command now accepts also multiple
    +  key patterns by passing an array of strings. (ISSUE #1).
    +
    +* __FIX__: Replies to the `DEL` command return the number of elements deleted
    +  by the server and not 0 or 1 interpreted as a boolean response. (ISSUE #4).
    +
    +
    +v0.5.1 (2010-01-23)
    +===============================================================================
    +
    +* `RPOPLPUSH` has been changed from bulk command to inline command in Redis
    +  1.2.1, so `ListPopLastPushHead` now extends `InlineCommand`. The old behavior
    +  is still available via the `ListPopLastPushHeadBulk` class so that you can
    +  override the server profile if you need the old (and uncorrect) behaviour
    +  when connecting to a Redis 1.2.0 instance.
    +
    +* Added missing support for `BGREWRITEAOF` for Redis >= 1.2.0.
    +
    +* Implemented a factory method for the `RedisServerProfile` class to ease the
    +  creation of new server profile instances based on a version string.
    +
    +
    +v0.5.0 (2010-01-09)
    +===============================================================================
    +* First versioned release of Predis
    diff --git a/vendor/predis/predis/CONTRIBUTING.md b/vendor/predis/predis/CONTRIBUTING.md
    new file mode 100644
    index 0000000..d25dc3c
    --- /dev/null
    +++ b/vendor/predis/predis/CONTRIBUTING.md
    @@ -0,0 +1,40 @@
    +## Filing bug reports ##
    +
    +Bugs or feature requests can be posted online on the [GitHub issues](http://github.com/nrk/predis/issues)
    +section of the project.
    +
    +When reporting bugs, in addition to the obvious description of your issue you __must__ always provide
    +some essential information about your environment such as:
    +
    +  1. version of Predis (check the `VERSION` file or the `Predis\Client::VERSION` constant).
    +  2. version of Redis (check the `redis_version` field returned by [`INFO`](http://redis.io/commands/info)).
    +  3. version of PHP.
    +  4. name and version of the operating system.
    +  5. when possible, a small snippet of code that reproduces the issue.
    +
    +__Think about it__: we do not have a crystal ball and cannot predict things and peer into the unknown,
    +so please provide as much details as possible to help us isolating issues and fix them.
    +
    +__Never__ use GitHub issues to post generic questions about Predis! When you have questions about
    +how Predis works or how it can be used, please just hop me an email and I will get back to you as
    +soon as possible.
    +
    +
    +## Contributing code ##
    +
    +If you want to work on Predis, it is highly recommended that you first run the test suite in order to
    +check that everything is OK, and report strange behaviours or bugs. When modifying Predis please make
    +sure that no warnings or notices are emitted by PHP by running the interpreter in your development
    +environment with the `error_reporting` variable set to `E_ALL | E_STRICT`.
    +
    +The recommended way to contribute to Predis is to fork the project on GitHub, create new topic branches
    +on your newly created repository to fix or add features (possibly with tests covering your modifications)
    +and then open a new pull request with a description of the applied changes. Obviously you can use any
    +other Git hosting provider of your preference.
    +
    +When writing code please follow the [basic coding (PSR-1)](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md)
    +and [coding style (PSR-2)](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)
    +standards and stick with the conventions used in Predis to name classes and interfaces.
    +
    +Please also follow some basic [commit guidelines](http://git-scm.com/book/ch5-2.html#Commit-Guidelines)
    +before opening pull requests.
    diff --git a/vendor/predis/predis/FAQ.md b/vendor/predis/predis/FAQ.md
    new file mode 100644
    index 0000000..41e576a
    --- /dev/null
    +++ b/vendor/predis/predis/FAQ.md
    @@ -0,0 +1,160 @@
    +# Some frequently asked questions about Predis #
    +_________________________________________________
    +
    +
    +### What is the point of Predis? ###
    +
    +The main point of Predis is about offering a highly customizable client for Redis that can be easily
    +extended by developers while still being reasonabily fast. With Predis you can swap almost any class
    +used internally with your own custom implementation: you can build connection classes, or new
    +distribution strategies for client-side sharding, or class handlers to replace existing commands or
    +add new ones. All of this can be achieved without messing with the source code of the library and
    +directly in your own application. Given the fast pace at which Redis is developed and adds new
    +features, this can be a great asset that allows you to add new and still missing features or commands,
    +or change the behaviour of the library without the need to break your dependencies in production code
    +(well, at least to some degree).
    +
    +### Does Predis support UNIX domain sockets and persistent connections? ###
    +
    +Yes. Obviously, persistent connections actually work when using PHP configured as a persistent process that
    +gets recycled between requests (see [PHP-FPM](http://php-fpm.org/)).
    +
    +
    +### Does Predis support transparent (de)serialization of values? ###
    +
    +No, and it will not ever do that for you by default. The reason behind this decision is that serialization
    +is usually something that developers prefer to customize depending on their needs and can not be easily
    +generalized when using Redis because of the many possible access patterns for the data. This does not
    +mean that it is impossible to have such a feature, you can leverage Predis' extensibility to define your
    +own serialization-aware commands. See [here](http://github.com/nrk/predis/issues/29#issuecomment-1202624)
    +for more details on how to implement such a feature with a practical example.
    +
    +
    +### How can I force Predis to connect to Redis before sending any command? ###
    +
    +Explicitly connecting to Redis is usually not needed since the client library relies on lazily initialized
    +connections to the server, but this behavior can be inconvenient in certain scenarios when you absolutely
    +need to do an upfront check to detect if the server is up and running and eventually catch exceptions on
    +failures. In this case developers can use `Predis\Client::connect()` to explicitly connect to the server:
    +
    +```php
    +$client = new Predis\Client();
    +
    +try {
    +    $client->connect();
    +} catch (Predis\Connection\ConnectionException $exception) {
    +    // We could not connect to Redis! Your handling code goes here.
    +}
    +
    +$client->info();
    +```
    +
    +
    +### How Predis implements abstraction of Redis commands? ###
    +
    +The approach used in Predis to implement the abstraction of Redis commands is quite simple. By default
    +every command in the library follows exactly the same argument list as defined in the great online
    +[Redis documentation](http://redis.io/commands) which makes things pretty easy if you already know how
    +Redis works or if you need to look up how to use certain commands. Alternatively, variadic commands can
    +accept an array for keys or values (depending on the command) instead of a list of arguments. See for
    +example how [RPUSH](http://redis.io/commands/rpush) or [HMSET](http://redis.io/commands/hmset) work:
    +
    +```php
    +$client->rpush('my:list', 'value1', 'value2', 'value3');                 // values as arguments
    +$client->rpush('my:list', array('value1', 'value2', 'value3'));          // values as single argument array
    +
    +$client->hmset('my:hash', 'field1', 'value1', 'field2', 'value2');       // values as arguments
    +$client->hmset('my:hash', array('field1'=>'value1', 'field2'=>'value2'); // values as single named array
    +```
    +
    +The only exception to this _rule_ is the [SORT](http://redis.io/commands/sort) command for which modifiers are
    +[passed using a named array](tests/Predis/Command/KeySortTest.php#L56-77).
    +
    +
    +
    +# Frequently asked questions about performances #
    +_________________________________________________
    +
    +
    +### Predis is a pure-PHP implementation: it can not be fast enough! ###
    +
    +It really depends, but most of the times the answer is: _yes, it is fast enough_. I will give you
    +a couple of easy numbers using a single Predis client with PHP 5.4.7 (custom build) and Redis 2.2
    +(localhost) under Ubuntu 12.04.1 (running on a Intel Q6600):
    +
    +    21500 SET/sec using 12 bytes for both key and value
    +    21000 GET/sec while retrieving the very same values
    +    0.130 seconds to fetch 30000 keys using _KEYS *_.
    +
    +How does it compare with a nice C-based extension such as [__phpredis__](http://github.com/nicolasff/phpredis)?
    +
    +    30100 SET/sec using 12 bytes for both key and value
    +    29400 GET/sec while retrieving the very same values
    +    0.035 seconds to fetch 30000 keys using "KEYS *"".
    +
    +Wow, __phpredis__ looks so much faster! Well we are comparing a C extension with a pure-PHP library so
    +lower numbers are quite expected, but there is a fundamental flaw in them: is this really how you are
    +going to use Redis in your application? Are you really going to send thousands of commands in a for-loop
    +for each page request using a single client instance? If so, well I guess you are probably doing something
    +wrong. Also, if you need to SET or GET multiple keys you should definitely use commands such as MSET and
    +MGET. You can also use pipelining to get more performances when this technique can be used.
    +
    +There is one more thing. We have tested the overhead of Predis by connecting on a localhost instance of
    +Redis, but how these numbers change when we hit the network by connecting to instances of Redis that
    +reside on other servers?
    +
    +    Using Predis:
    +    3200 SET/sec using 12 bytes for both key and value
    +    3200 GET/sec while retrieving the very same values
    +    0.132 seconds to fetch 30000 keys using "KEYS *".
    +
    +    Using phpredis:
    +    3500 SET/sec using 12 bytes for both key and value
    +    3500 GET/sec while retrieving the very same values
    +    0.045 seconds to fetch 30000 keys using "KEYS *".
    +
    +There you go, you get almost the same average numbers and the reason is quite simple: network latency
    +is a real performance killer and you cannot do (almost) anything about that. As a disclaimer, please
    +remember that we are measuring the overhead of client libraries implementations and the effects of the
    +network round-trip time, we are not really measuring how fast Redis is. Redis shines the best with
    +thousands of concurrent clients doing requests! Also, actual performances should be measured according
    +to how your application will use Redis.
    +
    +
    +### I am convinced, but performances for multi-bulk replies (e.g. _KEYS *_) are still worse ###
    +
    +Fair enough, but there is actually an option for you if you need even more speed and it consists on
    +installing __[phpiredis](http://github.com/nrk/phpiredis)__ (note the additional _i_ in the name)
    +and let Predis using it. __phpiredis__ is a C-based extension that wraps __hiredis__ (the official
    +Redis C client library) with a thin layer that exposes its features to PHP. You can choose between
    +two different connection backend classes: `Predis\Connection\PhpiredisConnection` (it depends on the
    +`socket` extension) and `Predis\Connection\PhpiredisStreamConnection` (it uses PHP's native streams).
    +You will now get the benefits of a faster protocol parser just by adding a couple of lines of code:
    +
    +```php
    +$client = new Predis\Client('tcp://127.0.0.1', array(
    +    'connections' => array(
    +    	'tcp'  => 'Predis\Connection\PhpiredisConnection',
    +    	'unix' => 'Predis\Connection\PhpiredisConnection',
    +	),
    +));
    +```
    +
    +As simple as it is, nothing will really change in the way you use the library in your application. So,
    +how fast is it now? There are not much improvements for inline or short bulk replies (e.g. _SET_ or
    +_GET_), but the speed for parsing multi-bulk replies is now on par with phpredis:
    +
    +    Using Predis with a phpiredis-based connection to fetch 30000 keys using _KEYS *_:
    +
    +    0.035 seconds from a local Redis instance
    +    0.047 seconds from a remote Redis instance
    +
    +
    +### If I need to install a C extension to get better performances, why not using phpredis? ###
    +
    +Good question. Generically speaking, if you need absolute uber-speed using localhost instances of Redis
    +and you do not care about abstractions built around some Redis features such as MULTI / EXEC, or if you
    +do not need any kind of extensibility or guaranteed backwards compatibility with different versions of
    +Redis (Predis currently supports from 1.2 up to 2.6, and even the current development version), then
    +using __phpredis__ can make sense for you. Otherwise, Predis is perfect for the job. __phpiredis__
    +can give you a nice speed bump, but using it is not mandatory.
    diff --git a/vendor/predis/predis/LICENSE b/vendor/predis/predis/LICENSE
    new file mode 100644
    index 0000000..e0b0536
    --- /dev/null
    +++ b/vendor/predis/predis/LICENSE
    @@ -0,0 +1,22 @@
    +Copyright (c) 2009-2013 Daniele Alessandri
    +
    +Permission is hereby granted, free of charge, to any person
    +obtaining a copy of this software and associated documentation
    +files (the "Software"), to deal in the Software without
    +restriction, including without limitation the rights to use,
    +copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the
    +Software is furnished to do so, subject to the following
    +conditions:
    +
    +The above copyright notice and this permission notice shall be
    +included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    +OTHER DEALINGS IN THE SOFTWARE.
    diff --git a/vendor/predis/predis/README.md b/vendor/predis/predis/README.md
    new file mode 100644
    index 0000000..afbb007
    --- /dev/null
    +++ b/vendor/predis/predis/README.md
    @@ -0,0 +1,263 @@
    +# Predis #
    +[![Latest Stable Version](https://poser.pugx.org/predis/predis/v/stable.png)](https://packagist.org/packages/predis/predis)
    +[![Total Downloads](https://poser.pugx.org/predis/predis/downloads.png)](https://packagist.org/packages/predis/predis)
    +
    +Predis is a flexible and feature-complete PHP (>= 5.3) client library for the Redis key-value store.
    +
    +The library does not require any additional extension loaded in PHP but it can be optionally paired
    +with the [phpiredis](https://github.com/nrk/phpiredis) C-based extension to lower the overhead of
    +serializing and parsing the Redis protocol. Predis is also available in an asynchronous fashion
    +through the experimental client provided by the [Predis\Async](http://github.com/nrk/predis-async)
    +library.
    +
    +For a list of frequently asked questions about Predis see our [FAQ](FAQ.md).
    +More details are available on the [official wiki](http://wiki.github.com/nrk/predis) of the project.
    +
    +
    +## Main features ##
    +
    +- Wide range of Redis versions supported (from __1.2__ to __2.6__ and unstable) using server profiles.
    +- Smart support for [redis-cluster](http://redis.io/topics/cluster-spec) (Redis >= 3.0).
    +- Client-side sharding via consistent hashing or custom distribution strategies.
    +- Support for master / slave replication configurations (write on master, read from slaves).
    +- Transparent key prefixing strategy capable of handling any command known that has keys in its arguments.
    +- Command pipelining on single and aggregated connections.
    +- Abstraction for Redis transactions (Redis >= 2.0) with support for CAS operations (Redis >= 2.2).
    +- Abstraction for Lua scripting (Redis >= 2.6) capable of automatically switching between `EVAL` and `EVALSHA`.
    +- Connections to Redis instances are lazily established upon the first call to a command by the client.
    +- Ability to connect to Redis using TCP/IP or UNIX domain sockets with support for persistent connections.
    +- Ability to specify alternative connection classes to use different types of network or protocol backends.
    +- Flexible system to define and register your own set of commands or server profiles to client instances.
    +
    +
    +## How to use Predis ##
    +
    +Predis is available on [Packagist](http://packagist.org/packages/predis/predis) for an easy installation
    +using [Composer](http://packagist.org/about-composer). Composer helps you manage dependencies for your
    +projects and libraries without much hassle which makes it the preferred way to get up and running with
    +new applications. Alternatively, the library is available on our [own PEAR channel](http://pear.nrk.io)
    +for a more traditional installation via PEAR. Zip and tar.gz archives are also downloadable from GitHub
    +by browsing the list of [tagged releases](http://github.com/nrk/predis/tags).
    +
    +
    +### Loading the library ###
    +
    +Predis relies on the autoloading features of PHP to load its files when needed and complies with the
    +[PSR-0 standard](http://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) which makes it
    +compatible with most of the major frameworks and libraries. Autoloading in your application is handled
    +automatically when managing the dependencies with Composer, but you can also leverage its own autoloader
    +class if you are going to use it in a project or script without any PSR-0 compliant autoloading facility:
    +
    +```php
    +set('foo', 'bar');
    +$value = $redis->get('foo');
    +```
    +
    +It is possible to specify the various connection parameters using URI strings or named arrays:
    +
    +```php
    +$redis = new Predis\Client('tcp://10.0.0.1:6379');
    +
    +// is equivalent to:
    +
    +$redis = new Predis\Client(array(
    +    'scheme' => 'tcp',
    +    'host'   => '10.0.0.1',
    +    'port'   => 6379,
    +));
    +```
    +
    +
    +### Pipelining commands to multiple instances of Redis with client-side sharding ###
    +
    +Pipelining helps with performances when there is the need to send many commands to a server in one go.
    +Furthermore, pipelining works transparently even on aggregated connections. To achieve this, Predis
    +supports client-side sharding using consistent-hashing on keys while clustered connections are supported
    +natively by the client class.
    +
    +```php
    +$redis = new Predis\Client(array(
    +    array('host' => '10.0.0.1', 'port' => 6379),
    +    array('host' => '10.0.0.2', 'port' => 6379)
    +));
    +
    +$replies = $redis->pipeline(function ($pipe) {
    +    for ($i = 0; $i < 1000; $i++) {
    +        $pipe->set("key:$i", str_pad($i, 4, '0', 0));
    +        $pipe->get("key:$i");
    +    }
    +});
    +```
    +
    +
    +### Multiple and customizable connection backends ###
    +
    +Predis can optionally use different connection backends to connect to Redis. Two of them leverage
    +the [phpiredis](http://github.com/nrk/phpiredis) C-based extension resulting in a major speed bump
    +especially when dealing with long multibulk replies, namely `Predis\Connection\PhpiredisConnection`
    +(the `socket` extension is also required) and `Predis\Connection\StreamPhpiredisConnection` (it
    +does not require additional extensions since it relies on PHP's native streams). Both of them can
    +connect to Redis using standard TCP/IP connections or UNIX domain sockets:
    +
    +```php
    +$client = new Predis\Client('tcp://127.0.0.1', array(
    +    'connections' => array(
    +        'tcp'  => 'Predis\Connection\PhpiredisConnection',
    +        'unix' => 'Predis\Connection\PhpiredisStreamConnection',
    +    )
    +));
    +```
    +
    +Developers can also create their own connection backends to add support for new protocols, extend
    +existing ones or provide different implementations. Connection backend classes must implement
    +`Predis\Connection\SingleConnectionInterface` or extend `Predis\Connection\AbstractConnection`:
    +
    +```php
    +class MyConnectionClass implements Predis\Connection\SingleConnectionInterface
    +{
    +    // implementation goes here
    +}
    +
    +// Let Predis automatically use your own class to handle connections identified by the tcp scheme.
    +$client = new Predis\Client('tcp://127.0.0.1', array(
    +    'connections' => array('tcp' => 'MyConnectionClass')
    +));
    +```
    +
    +For a more in-depth insight on how to create new connection backends you can look at the actual
    +implementation of the classes contained in the `Predis\Connection` namespace.
    +
    +
    +### Defining and registering new commands on the client at runtime ###
    +
    +Let's suppose Redis just added the support for a brand new feature associated with a new command. If
    +you want to start using the above mentioned new feature right away without messing with Predis source
    +code or waiting for it to find its way into a stable Predis release, then you can start off by creating
    +a new class that matches the command type and its behaviour and then bind it to a client instance at
    +runtime. Actually, it is easier done than said:
    +
    +```php
    +class BrandNewRedisCommand extends Predis\Command\AbstractCommand
    +{
    +    public function getId()
    +    {
    +        return 'NEWCMD';
    +    }
    +}
    +
    +$redis = new Predis\Client();
    +$redis->getProfile()->defineCommand('newcmd', 'BrandNewRedisCommand');
    +$redis->newcmd();
    +```
    +
    +
    +### Abstraction for handling Lua scripts as plain Redis commands ###
    +
    +A scripted command in Predis is an abstraction for [Lua scripting](http://redis.io/commands/eval)
    +with Redis >= 2.6 that allows to use a Lua script as if it was a plain Redis command registered
    +in the server profile being used by the client instance. Internally, scripted commands use
    +[EVALSHA](http://redis.io/commands/evalsha) to refer to a Lua script by its SHA1 hash in order
    +to save bandwidth, but they are capable of falling back to [EVAL](http://redis.io/commands/eval)
    +when needed:
    +
    +```php
    +class ListPushRandomValue extends Predis\Command\ScriptedCommand
    +{
    +    public function getKeysCount()
    +    {
    +        return 1;
    +    }
    +
    +    public function getScript()
    +    {
    +        return
    +<<getProfile()->defineCommand('lpushrand', 'ListPushRandomValue');
    +
    +$value = $client->lpushrand('random_values', $seed = mt_rand());
    +```
    +
    +
    +## Test suite ##
    +
    +__ATTENTION__: Do not ever run the test suite shipped with Predis against instances of Redis running in
    +production environments or containing data you are interested in!
    +
    +Predis has a comprehensive test suite covering every aspect of the library. The suite performs integration
    +tests against a running instance of Redis (>= 2.4.0 is required) to verify the correct behaviour of the
    +implementation of each command and automatically skips commands not defined in the selected version of
    +Redis. If you do not have Redis up and running, integration tests can be disabled. By default, the test
    +suite is configured to execute integration tests using the server profile for Redis v2.4 (which is the
    +current stable version of Redis). You can optionally run the suite against a Redis instance built from
    +the `unstable` branch with the development profile by changing the `REDIS_SERVER_VERSION` to `dev` in
    +the `phpunit.xml` file. More details on testing Predis can be found in [the tests README](tests/README.md).
    +
    +Predis uses Travis CI for continuous integration. You can find the results of the test suite and the build
    +history [on its project page](http://travis-ci.org/nrk/predis).
    +
    +
    +## Dependencies ##
    +
    +- PHP >= 5.3.2
    +- PHPUnit >= 3.5.0 (needed to run the test suite)
    +
    +## Links ##
    +
    +### Project ###
    +- [Source code](http://github.com/nrk/predis/)
    +- [Wiki](http://wiki.github.com/nrk/predis/)
    +- [Issue tracker](http://github.com/nrk/predis/issues)
    +- [PEAR channel](http://pear.nrk.io)
    +
    +### Related ###
    +- [Redis](http://redis.io/)
    +- [PHP](http://php.net/)
    +- [PHPUnit](http://www.phpunit.de/)
    +- [Git](http://git-scm.com/)
    +
    +## Author ##
    +
    +- [Daniele Alessandri](mailto:suppakilla@gmail.com) ([twitter](http://twitter.com/JoL1hAHN))
    +
    +## Contributors ##
    +
    +- [Lorenzo Castelli](http://github.com/lcastelli)
    +- [Jordi Boggiano](http://github.com/Seldaek) ([twitter](http://twitter.com/seldaek))
    +- [Sebastian Waisbrot](http://github.com/seppo0010) ([twitter](http://twitter.com/seppo0010))
    +  for his past work on extending [phpiredis](http://github.com/nrk/phpiredis) for Predis.
    +
    +## License ##
    +
    +The code for Predis is distributed under the terms of the MIT license (see [LICENSE](LICENSE)).
    diff --git a/vendor/predis/predis/VERSION b/vendor/predis/predis/VERSION
    new file mode 100644
    index 0000000..b60d719
    --- /dev/null
    +++ b/vendor/predis/predis/VERSION
    @@ -0,0 +1 @@
    +0.8.4
    diff --git a/vendor/predis/predis/autoload.php b/vendor/predis/predis/autoload.php
    new file mode 100644
    index 0000000..be08eb1
    --- /dev/null
    +++ b/vendor/predis/predis/autoload.php
    @@ -0,0 +1,14 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +require __DIR__.'/lib/Predis/Autoloader.php';
    +
    +Predis\Autoloader::register();
    diff --git a/vendor/predis/predis/bin/create-phar.php b/vendor/predis/predis/bin/create-phar.php
    new file mode 100755
    index 0000000..fe0d31b
    --- /dev/null
    +++ b/vendor/predis/predis/bin/create-phar.php
    @@ -0,0 +1,71 @@
    +#!/usr/bin/env php
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +// -------------------------------------------------------------------------- //
    +// In order to be able to execute this script to create a Phar archive of Predis,
    +// the Phar module must be loaded and the "phar.readonly" directive php.ini must
    +// be set to "off". You can change the values in the $options array to customize
    +// the creation of the Phar archive to better suit your needs.
    +// -------------------------------------------------------------------------- //
    +
    +$options = array(
    +    'name'           => 'predis',
    +    'project_path'   => __DIR__ . '/../lib/',
    +    'compression'    => Phar::NONE,
    +    'append_version' => true,
    +);
    +
    +function getPharFilename($options)
    +{
    +    $filename = $options['name'];
    +
    +    // NOTE: do not consider "append_version" with Phar compression do to a bug in
    +    // Phar::compress() when renaming phar archives containing dots in their name.
    +    if ($options['append_version'] && $options['compression'] === Phar::NONE) {
    +        $versionFile = @fopen(__DIR__ . '/../VERSION', 'r');
    +
    +        if ($versionFile === false) {
    +            throw new Exception("Could not locate the VERSION file.");
    +        }
    +
    +        $version = trim(fgets($versionFile));
    +        fclose($versionFile);
    +        $filename .= "_$version";
    +    }
    +
    +    return "$filename.phar";
    +}
    +
    +function getPharStub($options)
    +{
    +    return <<compress($options['compression']);
    +$phar->setStub(getPharStub($options));
    +$phar->buildFromDirectory($options['project_path']);
    diff --git a/vendor/predis/predis/bin/create-single-file.php b/vendor/predis/predis/bin/create-single-file.php
    new file mode 100755
    index 0000000..f2c0ad6
    --- /dev/null
    +++ b/vendor/predis/predis/bin/create-single-file.php
    @@ -0,0 +1,606 @@
    +#!/usr/bin/env php
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +// -------------------------------------------------------------------------- //
    +// This script can be used to automatically glue all the .php files of Predis
    +// into a single monolithic script file that can be used without an autoloader,
    +// just like the other previous versions of the library.
    +//
    +// Much of its complexity is due to the fact that we cannot simply join PHP
    +// files, but namespaces and classes definitions must follow a precise order
    +// when dealing with subclassing and inheritance.
    +//
    +// The current implementation is pretty naïve, but it should do for now.
    +// -------------------------------------------------------------------------- //
    +
    +class CommandLine
    +{
    +    public static function getOptions()
    +    {
    +        $parameters = array(
    +            's:'  => 'source:',
    +            'o:'  => 'output:',
    +            'e:'  => 'exclude:',
    +            'E:'  => 'exclude-classes:',
    +        );
    +
    +        $getops = getopt(implode(array_keys($parameters)), $parameters);
    +
    +        $options = array(
    +            'source'  => __DIR__ . "/../lib/",
    +            'output'  => PredisFile::NS_ROOT . '.php',
    +            'exclude' => array(),
    +        );
    +
    +        foreach ($getops as $option => $value) {
    +            switch ($option) {
    +                case 's':
    +                case 'source':
    +                    $options['source'] = $value;
    +                    break;
    +
    +                case 'o':
    +                case 'output':
    +                    $options['output'] = $value;
    +                    break;
    +
    +                case 'E':
    +                case 'exclude-classes':
    +                    $options['exclude'] = @file($value, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: $value;
    +                    break;
    +
    +                case 'e':
    +                case 'exclude':
    +                    $options['exclude'] = is_array($value) ? $value : array($value);
    +                    break;
    +            }
    +        }
    +
    +        return $options;
    +    }
    +}
    +
    +class PredisFile
    +{
    +    const NS_ROOT = 'Predis';
    +
    +    private $namespaces;
    +
    +    public function __construct()
    +    {
    +        $this->namespaces = array();
    +    }
    +
    +    public static function from($libraryPath, Array $exclude = array())
    +    {
    +        $nsroot = self::NS_ROOT;
    +        $predisFile = new PredisFile();
    +        $libIterator = new RecursiveDirectoryIterator("$libraryPath$nsroot");
    +
    +        foreach (new RecursiveIteratorIterator($libIterator) as $classFile)
    +        {
    +            if (!$classFile->isFile()) {
    +                continue;
    +            }
    +
    +            $namespace = strtr(str_replace($libraryPath, '', $classFile->getPath()), '/', '\\');
    +
    +            if (in_array(sprintf('%s\\%s', $namespace, $classFile->getBasename('.php')), $exclude)) {
    +                continue;
    +            }
    +
    +            $phpNamespace = $predisFile->getNamespace($namespace);
    +
    +            if ($phpNamespace === false) {
    +                $phpNamespace = new PhpNamespace($namespace);
    +                $predisFile->addNamespace($phpNamespace);
    +            }
    +
    +            $phpClass = new PhpClass($phpNamespace, $classFile);
    +        }
    +
    +        return $predisFile;
    +    }
    +
    +    public function addNamespace(PhpNamespace $namespace)
    +    {
    +        if (isset($this->namespaces[(string)$namespace])) {
    +            throw new InvalidArgumentException("Duplicated namespace");
    +        }
    +        $this->namespaces[(string)$namespace] = $namespace;
    +    }
    +
    +    public function getNamespaces()
    +    {
    +        return $this->namespaces;
    +    }
    +
    +    public function getNamespace($namespace)
    +    {
    +        if (!isset($this->namespaces[$namespace])) {
    +            return false;
    +        }
    +
    +        return $this->namespaces[$namespace];
    +    }
    +
    +    public function getClassByFQN($classFqn)
    +    {
    +        if (($nsLastPos = strrpos($classFqn, '\\')) !== false) {
    +            $namespace = $this->getNamespace(substr($classFqn, 0, $nsLastPos));
    +            if ($namespace === false) {
    +                return null;
    +            }
    +            $className = substr($classFqn, $nsLastPos + 1);
    +
    +            return $namespace->getClass($className);
    +        }
    +
    +        return null;
    +    }
    +
    +    private function calculateDependencyScores(&$classes, $fqn)
    +    {
    +        if (!isset($classes[$fqn])) {
    +            $classes[$fqn] = 0;
    +        }
    +
    +        $classes[$fqn] += 1;
    +
    +        if (($phpClass = $this->getClassByFQN($fqn)) === null) {
    +            throw new RuntimeException(
    +                "Cannot found the class $fqn which is required by other subclasses. Are you missing a file?"
    +            );
    +        }
    +
    +        foreach ($phpClass->getDependencies() as $fqn) {
    +            $this->calculateDependencyScores($classes, $fqn);
    +        }
    +    }
    +
    +    private function getDependencyScores()
    +    {
    +        $classes = array();
    +
    +        foreach ($this->getNamespaces() as $phpNamespace) {
    +            foreach ($phpNamespace->getClasses() as $phpClass) {
    +                $this->calculateDependencyScores($classes, $phpClass->getFQN());
    +            }
    +        }
    +
    +        return $classes;
    +    }
    +
    +    private function getOrderedNamespaces($dependencyScores)
    +    {
    +        $namespaces = array_fill_keys(array_unique(
    +            array_map(
    +                function ($fqn) { return PhpNamespace::extractName($fqn); },
    +                array_keys($dependencyScores)
    +            )
    +        ), 0);
    +
    +        foreach ($dependencyScores as $classFqn => $score) {
    +            $namespaces[PhpNamespace::extractName($classFqn)] += $score;
    +        }
    +
    +        arsort($namespaces);
    +
    +        return array_keys($namespaces);
    +    }
    +
    +    private function getOrderedClasses(PhpNamespace $phpNamespace, $classes)
    +    {
    +        $nsClassesFQNs = array_map(function ($cl) { return $cl->getFQN(); }, $phpNamespace->getClasses());
    +        $nsOrderedClasses = array();
    +
    +        foreach ($nsClassesFQNs as $nsClassFQN) {
    +            $nsOrderedClasses[$nsClassFQN] = $classes[$nsClassFQN];
    +        }
    +
    +        arsort($nsOrderedClasses);
    +
    +        return array_keys($nsOrderedClasses);
    +    }
    +
    +    public function getPhpCode()
    +    {
    +        $buffer = array("getDependencyScores();
    +        $namespaces = $this->getOrderedNamespaces($classes);
    +
    +        foreach ($namespaces as $namespace) {
    +            $phpNamespace = $this->getNamespace($namespace);
    +
    +            // generate namespace directive
    +            $buffer[] = $phpNamespace->getPhpCode();
    +            $buffer[] = "\n";
    +
    +            // generate use directives
    +            $useDirectives = $phpNamespace->getUseDirectives();
    +            if (count($useDirectives) > 0) {
    +                $buffer[] = $useDirectives->getPhpCode();
    +                $buffer[] = "\n";
    +            }
    +
    +            // generate classes bodies
    +            $nsClasses = $this->getOrderedClasses($phpNamespace, $classes);
    +            foreach ($nsClasses as $classFQN) {
    +                $buffer[] = $this->getClassByFQN($classFQN)->getPhpCode();
    +                $buffer[] = "\n\n";
    +            }
    +
    +            $buffer[] = "/* " . str_repeat("-", 75) . " */";
    +            $buffer[] = "\n\n";
    +        }
    +
    +        return implode($buffer);
    +    }
    +
    +    public function saveTo($outputFile)
    +    {
    +        // TODO: add more sanity checks
    +        if ($outputFile === null || $outputFile === '') {
    +            throw new InvalidArgumentException('You must specify a valid output file');
    +        }
    +        file_put_contents($outputFile, $this->getPhpCode());
    +    }
    +}
    +
    +class PhpNamespace implements IteratorAggregate
    +{
    +    private $namespace;
    +    private $classes;
    +
    +    public function __construct($namespace)
    +    {
    +        $this->namespace = $namespace;
    +        $this->classes = array();
    +        $this->useDirectives = new PhpUseDirectives($this);
    +    }
    +
    +    public static function extractName($fqn)
    +    {
    +        $nsSepLast = strrpos($fqn, '\\');
    +        if ($nsSepLast === false) {
    +            return $fqn;
    +        }
    +        $ns = substr($fqn, 0, $nsSepLast);
    +
    +        return $ns !== '' ? $ns : null;
    +    }
    +
    +    public function addClass(PhpClass $class)
    +    {
    +        $this->classes[$class->getName()] = $class;
    +    }
    +
    +    public function getClass($className)
    +    {
    +        if (isset($this->classes[$className])) {
    +            return $this->classes[$className];
    +        }
    +    }
    +
    +    public function getClasses()
    +    {
    +        return array_values($this->classes);
    +    }
    +
    +    public function getIterator()
    +    {
    +        return new \ArrayIterator($this->getClasses());
    +    }
    +
    +    public function getUseDirectives()
    +    {
    +        return $this->useDirectives;
    +    }
    +
    +    public function getPhpCode()
    +    {
    +        return "namespace $this->namespace;\n";
    +    }
    +
    +    public function __toString()
    +    {
    +        return $this->namespace;
    +    }
    +}
    +
    +class PhpUseDirectives implements Countable, IteratorAggregate
    +{
    +    private $use;
    +    private $aliases;
    +    private $namespace;
    +
    +    public function __construct(PhpNamespace $namespace)
    +    {
    +        $this->use = array();
    +        $this->aliases = array();
    +        $this->namespace = $namespace;
    +    }
    +
    +    public function add($use, $as = null)
    +    {
    +        if (in_array($use, $this->use)) {
    +            return;
    +        }
    +
    +        $this->use[] = $use;
    +        $this->aliases[$as ?: PhpClass::extractName($use)] = $use;
    +    }
    +
    +    public function getList()
    +    {
    +        return $this->use;
    +    }
    +
    +    public function getIterator()
    +    {
    +        return new \ArrayIterator($this->getList());
    +    }
    +
    +    public function getPhpCode()
    +    {
    +        $reducer = function ($str, $use) {
    +            return $str .= "use $use;\n";
    +        };
    +
    +        return array_reduce($this->getList(), $reducer, '');
    +    }
    +
    +    public function getNamespace()
    +    {
    +        return $this->namespace;
    +    }
    +
    +    public function getFQN($className)
    +    {
    +        if (($nsSepFirst = strpos($className, '\\')) === false) {
    +            if (isset($this->aliases[$className])) {
    +                return $this->aliases[$className];
    +            }
    +
    +            return (string)$this->getNamespace() . "\\$className";
    +        }
    +
    +        if ($nsSepFirst != 0) {
    +            throw new InvalidArgumentException("Partially qualified names are not supported");
    +        }
    +
    +        return $className;
    +    }
    +
    +    public function count()
    +    {
    +        return count($this->use);
    +    }
    +}
    +
    +class PhpClass
    +{
    +    const LICENSE_HEADER = <<
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +LICENSE;
    +
    +    private $namespace;
    +    private $file;
    +    private $body;
    +    private $implements;
    +    private $extends;
    +    private $name;
    +
    +    public function __construct(PhpNamespace $namespace, SplFileInfo $classFile)
    +    {
    +        $this->namespace = $namespace;
    +        $this->file = $classFile;
    +        $this->implements = array();
    +        $this->extends = array();
    +
    +        $this->extractData();
    +        $namespace->addClass($this);
    +    }
    +
    +    public static function extractName($fqn)
    +    {
    +        $nsSepLast = strrpos($fqn, '\\');
    +        if ($nsSepLast === false) {
    +            return $fqn;
    +        }
    +
    +        return substr($fqn, $nsSepLast + 1);
    +    }
    +
    +    private function extractData()
    +    {
    +        $useDirectives = $this->getNamespace()->getUseDirectives();
    +
    +        $useExtractor = function ($m) use ($useDirectives) {
    +            $useDirectives->add(($namespacedPath = $m[1]));
    +        };
    +
    +        $classBuffer = stream_get_contents(fopen($this->getFile()->getPathname(), 'r'));
    +
    +        $classBuffer = str_replace(self::LICENSE_HEADER, '', $classBuffer);
    +
    +        $classBuffer = preg_replace('/<\?php\s?\\n\s?/', '', $classBuffer);
    +        $classBuffer = preg_replace('/\s?\?>\n?/ms', '', $classBuffer);
    +        $classBuffer = preg_replace('/namespace\s+[\w\d_\\\\]+;\s?/', '', $classBuffer);
    +        $classBuffer = preg_replace_callback('/use\s+([\w\d_\\\\]+)(\s+as\s+.*)?;\s?\n?/', $useExtractor, $classBuffer);
    +
    +        $this->body = trim($classBuffer);
    +
    +        $this->extractHierarchy();
    +    }
    +
    +    private function extractHierarchy()
    +    {
    +        $implements = array();
    +        $extends =  array();
    +
    +        $extractor = function ($iterator, $callback) {
    +            $className = '';
    +            $iterator->seek($iterator->key() + 1);
    +
    +            while ($iterator->valid()) {
    +                $token = $iterator->current();
    +
    +                if (is_string($token)) {
    +                    if (preg_match('/\s?,\s?/', $token)) {
    +                        $callback(trim($className));
    +                        $className = '';
    +                    } else if ($token == '{') {
    +                        $callback(trim($className));
    +                        return;
    +                    }
    +                }
    +
    +                switch ($token[0]) {
    +                    case T_NS_SEPARATOR:
    +                        $className .= '\\';
    +                        break;
    +
    +                    case T_STRING:
    +                        $className .= $token[1];
    +                        break;
    +
    +                    case T_IMPLEMENTS:
    +                    case T_EXTENDS:
    +                        $callback(trim($className));
    +                        $iterator->seek($iterator->key() - 1);
    +                        return;
    +                }
    +
    +                $iterator->next();
    +            }
    +        };
    +
    +        $tokens = token_get_all("getPhpCode()));
    +        $iterator = new ArrayIterator($tokens);
    +
    +        while ($iterator->valid()) {
    +            $token = $iterator->current();
    +            if (is_string($token)) {
    +                $iterator->next();
    +                continue;
    +            }
    +
    +            switch ($token[0]) {
    +                case T_CLASS:
    +                case T_INTERFACE:
    +                    $iterator->seek($iterator->key() + 2);
    +                    $tk = $iterator->current();
    +                    $this->name = $tk[1];
    +                    break;
    +
    +                case T_IMPLEMENTS:
    +                    $extractor($iterator, function ($fqn) use (&$implements) {
    +                        $implements[] = $fqn;
    +                    });
    +                    break;
    +
    +                case T_EXTENDS:
    +                    $extractor($iterator, function ($fqn) use (&$extends) {
    +                        $extends[] = $fqn;
    +                    });
    +                    break;
    +            }
    +
    +            $iterator->next();
    +        }
    +
    +        $this->implements = $this->guessFQN($implements);
    +        $this->extends = $this->guessFQN($extends);
    +    }
    +
    +    public function guessFQN($classes)
    +    {
    +        $useDirectives = $this->getNamespace()->getUseDirectives();
    +        return array_map(array($useDirectives, 'getFQN'), $classes);
    +    }
    +
    +    public function getImplementedInterfaces($all = false)
    +    {
    +        if ($all) {
    +            return $this->implements;
    +        }
    +
    +        return array_filter(
    +            $this->implements,
    +            function ($cn) { return strpos($cn, 'Predis\\') === 0; }
    +        );
    +    }
    +
    +    public function getExtendedClasses($all = false)
    +    {
    +        if ($all) {
    +            return $this->extemds;
    +        }
    +
    +        return array_filter(
    +            $this->extends,
    +            function ($cn) { return strpos($cn, 'Predis\\') === 0; }
    +        );
    +    }
    +
    +    public function getDependencies($all = false)
    +    {
    +        return array_merge(
    +            $this->getImplementedInterfaces($all),
    +            $this->getExtendedClasses($all)
    +        );
    +    }
    +
    +    public function getNamespace()
    +    {
    +        return $this->namespace;
    +    }
    +
    +    public function getFile()
    +    {
    +        return $this->file;
    +    }
    +
    +    public function getName()
    +    {
    +        return $this->name;
    +    }
    +
    +    public function getFQN()
    +    {
    +        return (string)$this->getNamespace() . '\\' . $this->name;
    +    }
    +
    +    public function getPhpCode()
    +    {
    +        return $this->body;
    +    }
    +
    +    public function __toString()
    +    {
    +        return "class " . $this->getName() . '{ ... }';
    +    }
    +}
    +
    +/* -------------------------------------------------------------------------- */
    +
    +$options = CommandLine::getOptions();
    +$predisFile = PredisFile::from($options['source'], $options['exclude']);
    +$predisFile->saveTo($options['output']);
    diff --git a/vendor/predis/predis/bin/generate-command-test.php b/vendor/predis/predis/bin/generate-command-test.php
    new file mode 100755
    index 0000000..51ccde3
    --- /dev/null
    +++ b/vendor/predis/predis/bin/generate-command-test.php
    @@ -0,0 +1,273 @@
    +#!/usr/bin/env php
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +// -------------------------------------------------------------------------- //
    +// This script can be used to automatically generate a file with the scheleton
    +// of a test case to test a Redis command by specifying the name of the class
    +// in the Predis\Command namespace (only classes in this namespace are valid).
    +// For example, to generate a test case for SET (which is represented by the
    +// Predis\Command\StringSet class):
    +//
    +//   $ ./bin/generate-command-test.php --class=StringSet
    +//
    +// Here is a list of optional arguments:
    +//
    +// --realm: each command has its own realm (commands that operate on strings,
    +// lists, sets and such) but while this realm is usually inferred from the name
    +// of the specified class, sometimes it can be useful to override it with a
    +// custom one.
    +//
    +// --output: write the generated test case to the specified path instead of
    +// the default one.
    +//
    +// --overwrite: pre-existing test files are not overwritten unless this option
    +// is explicitly specified.
    +// -------------------------------------------------------------------------- //
    +
    +use Predis\Command\CommandInterface;
    +use Predis\Command\PrefixableCommandInterface;
    +
    +class CommandTestCaseGenerator
    +{
    +    private $options;
    +
    +    public function __construct(Array $options)
    +    {
    +        if (!isset($options['class'])) {
    +            throw new RuntimeException("Missing 'class' option.");
    +        }
    +        $this->options = $options;
    +    }
    +
    +    public static function fromCommandLine()
    +    {
    +        $parameters = array(
    +            'c:'  => 'class:',
    +            'r::' => 'realm::',
    +            'o::' => 'output::',
    +            'x::' => 'overwrite::'
    +        );
    +
    +        $getops = getopt(implode(array_keys($parameters)), $parameters);
    +
    +        $options = array(
    +            'overwrite' => false,
    +            'tests' => __DIR__.'/../tests',
    +        );
    +
    +        foreach ($getops as $option => $value) {
    +            switch ($option) {
    +                case 'c':
    +                case 'class':
    +                    $options['class'] = $value;
    +                    break;
    +
    +                case 'r':
    +                case 'realm':
    +                    $options['realm'] = $value;
    +                    break;
    +
    +                case 'o':
    +                case 'output':
    +                    $options['output'] = $value;
    +                    break;
    +
    +                case 'x':
    +                case 'overwrite':
    +                    $options['overwrite'] = true;
    +                    break;
    +            }
    +        }
    +
    +        if (!isset($options['class'])) {
    +            throw new RuntimeException("Missing 'class' option.");
    +        }
    +
    +        $options['fqn'] = "Predis\\Command\\{$options['class']}";
    +        $options['path'] = "Predis/Command/{$options['class']}.php";
    +
    +        $source = __DIR__.'/../lib/'.$options['path'];
    +        if (!file_exists($source)) {
    +            throw new RuntimeException("Cannot find class file for {$options['fqn']} in $source.");
    +        }
    +
    +        if (!isset($options['output'])) {
    +            $options['output'] = sprintf("%s/%s", $options['tests'], str_replace('.php', 'Test.php', $options['path']));
    +        }
    +
    +        return new self($options);
    +    }
    +
    +    protected function getTestRealm()
    +    {
    +        if (isset($this->options['realm'])) {
    +            if (!$this->options['realm']) {
    +                throw new RuntimeException('Invalid value for realm has been sepcified (empty).');
    +            }
    +            return $this->options['realm'];
    +        }
    +
    +        $fqnParts = explode('\\', $this->options['fqn']);
    +        $class = array_pop($fqnParts);
    +        list($realm,) = preg_split('/([[:upper:]][[:lower:]]+)/', $class, 2, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
    +
    +        return strtolower($realm);
    +    }
    +
    +    public function generate()
    +    {
    +        $reflection = new ReflectionClass($class = $this->options['fqn']);
    +
    +        if (!$reflection->isInstantiable()) {
    +            throw new RuntimeException("Class $class must be instantiable, abstract classes or interfaces are not allowed.");
    +        }
    +        if (!$reflection->implementsInterface('Predis\Command\CommandInterface')) {
    +            throw new RuntimeException("Class $class must implement Predis\Command\CommandInterface.");
    +        }
    +
    +        $instance = $reflection->newInstance();
    +        $buffer = $this->getTestCaseBuffer($instance);
    +
    +        return $buffer;
    +    }
    +
    +    public function save()
    +    {
    +        $options = $this->options;
    +        if (file_exists($options['output']) && !$options['overwrite']) {
    +            throw new RuntimeException("File {$options['output']} already exist. Specify the --overwrite option to overwrite the existing file.");
    +        }
    +        file_put_contents($options['output'], $this->generate());
    +    }
    +
    +    protected function getTestCaseBuffer(CommandInterface $instance)
    +    {
    +        $id = $instance->getId();
    +        $fqn = get_class($instance);
    +        $fqnParts = explode('\\', $fqn);
    +        $class = array_pop($fqnParts) . "Test";
    +        $realm = $this->getTestRealm();
    +
    +        $buffer =<<
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-$realm
    + */
    +class $class extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return '$fqn';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return '$id';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        \$this->markTestIncomplete('This test has not been implemented yet.');
    +
    +        \$arguments = array(/* add arguments */);
    +        \$expected = array(/* add arguments */);
    +
    +        \$command = \$this->getCommand();
    +        \$command->setArguments(\$arguments);
    +
    +        \$this->assertSame(\$expected, \$command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        \$this->markTestIncomplete('This test has not been implemented yet.');
    +
    +        \$raw = null;
    +        \$expected = null;
    +
    +        \$command = \$this->getCommand();
    +
    +        \$this->assertSame(\$expected, \$command->parseResponse(\$raw));
    +    }
    +
    +PHP;
    +
    +        if ($instance instanceof PrefixableCommandInterface) {
    +            $buffer .=<<markTestIncomplete('This test has not been implemented yet.');
    +
    +        \$arguments = array(/* add arguments */);
    +        \$expected = array(/* add arguments */);
    +
    +        \$command = \$this->getCommandWithArgumentsArray(\$arguments);
    +        \$command->prefixKeys('prefix:');
    +
    +        \$this->assertSame(\$expected, \$command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        \$command = \$this->getCommand();
    +        \$command->prefixKeys('prefix:');
    +
    +        \$this->assertSame(array(), \$command->getArguments());
    +    }
    +
    +PHP;
    +        }
    +
    +        return "$buffer}\n";
    +    }
    +}
    +
    +// ------------------------------------------------------------------------- //
    +
    +require __DIR__.'/../autoload.php';
    +
    +$generator = CommandTestCaseGenerator::fromCommandLine();
    +$generator->save();
    diff --git a/vendor/predis/predis/composer.json b/vendor/predis/predis/composer.json
    new file mode 100644
    index 0000000..3b1c2fd
    --- /dev/null
    +++ b/vendor/predis/predis/composer.json
    @@ -0,0 +1,25 @@
    +{
    +    "name": "predis/predis",
    +    "type": "library",
    +    "description": "Flexible and feature-complete PHP client library for Redis",
    +    "keywords": ["nosql", "redis", "predis"],
    +    "homepage": "http://github.com/nrk/predis",
    +    "license": "MIT",
    +    "authors": [
    +        {
    +            "name": "Daniele Alessandri",
    +            "email": "suppakilla@gmail.com",
    +            "homepage": "http://clorophilla.net"
    +        }
    +    ],
    +    "require": {
    +        "php": ">=5.3.2"
    +    },
    +    "suggest": {
    +        "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol",
    +        "ext-curl": "Allows access to Webdis when paired with phpiredis"
    +    },
    +    "autoload": {
    +        "psr-0": {"Predis": "lib/"}
    +    }
    +}
    diff --git a/vendor/predis/predis/examples/CustomDistributionStrategy.php b/vendor/predis/predis/examples/CustomDistributionStrategy.php
    new file mode 100644
    index 0000000..052b6c0
    --- /dev/null
    +++ b/vendor/predis/predis/examples/CustomDistributionStrategy.php
    @@ -0,0 +1,89 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +require 'SharedConfigurations.php';
    +
    +// Developers can customize the distribution strategy used by the client
    +// to distribute keys among a cluster of servers simply by creating a class
    +// that implements Predis\Distribution\DistributionStrategyInterface.
    +
    +use Predis\Connection\PredisCluster;
    +use Predis\Cluster\Distribution\DistributionStrategyInterface;
    +use Predis\Cluster\Hash\HashGeneratorInterface;
    +
    +class NaiveDistributionStrategy implements DistributionStrategyInterface, HashGeneratorInterface
    +{
    +    private $nodes;
    +    private $nodesCount;
    +
    +    public function __construct()
    +    {
    +        $this->nodes = array();
    +        $this->nodesCount = 0;
    +    }
    +
    +    public function add($node, $weight = null)
    +    {
    +        $this->nodes[] = $node;
    +        $this->nodesCount++;
    +    }
    +
    +    public function remove($node)
    +    {
    +        $this->nodes = array_filter($this->nodes, function ($n) use ($node) {
    +            return $n !== $node;
    +        });
    +
    +        $this->nodesCount = count($this->nodes);
    +    }
    +
    +    public function get($key)
    +    {
    +        if (0 === $count = $this->nodesCount) {
    +            throw new RuntimeException('No connections');
    +        }
    +
    +        return $this->nodes[$count > 1 ? abs($key % $count) : 0];
    +    }
    +
    +    public function hash($value)
    +    {
    +        return crc32($value);
    +    }
    +
    +    public function getHashGenerator()
    +    {
    +        return $this;
    +    }
    +}
    +
    +$options = array(
    +    'cluster' => function () {
    +        $distributor = new NaiveDistributionStrategy();
    +        $cluster = new PredisCluster($distributor);
    +
    +        return $cluster;
    +    },
    +);
    +
    +$client = new Predis\Client($multiple_servers, $options);
    +
    +for ($i = 0; $i < 100; $i++) {
    +    $client->set("key:$i", str_pad($i, 4, '0', 0));
    +    $client->get("key:$i");
    +}
    +
    +$server1 = $client->getClientFor('first')->info();
    +$server2 = $client->getClientFor('second')->info();
    +
    +printf("Server '%s' has %d keys while server '%s' has %d keys.\n",
    +    'first', $server1['db15']['keys'], 'second', $server2['db15']['keys']
    +);
    diff --git a/vendor/predis/predis/examples/DispatcherLoop.php b/vendor/predis/predis/examples/DispatcherLoop.php
    new file mode 100644
    index 0000000..f5b8a99
    --- /dev/null
    +++ b/vendor/predis/predis/examples/DispatcherLoop.php
    @@ -0,0 +1,78 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +require 'SharedConfigurations.php';
    +
    +/*
    +This is a basic example on how to use the Predis\DispatcherLoop class.
    +
    +To see this example in action you can just use redis-cli and publish some
    +messages to the 'events' and 'control' channel, e.g.:
    +
    +./redis-cli
    +PUBLISH events first
    +PUBLISH events second
    +PUBLISH events third
    +PUBLISH control terminate_dispatcher
    +*/
    +
    +// Create a client and disable r/w timeout on the socket
    +$client = new Predis\Client($single_server + array('read_write_timeout' => 0));
    +
    +// Create a Predis\DispatcherLoop instance and attach a bunch of callbacks.
    +$dispatcher = new Predis\PubSub\DispatcherLoop($client);
    +
    +// Demonstrate how to use a callable class as a callback for Predis\DispatcherLoop.
    +class EventsListener implements Countable
    +{
    +    private $events;
    +
    +    public function __construct()
    +    {
    +        $this->events = array();
    +    }
    +
    +    public function count()
    +    {
    +        return count($this->events);
    +    }
    +
    +    public function getEvents()
    +    {
    +        return $this->events;
    +    }
    +
    +    public function __invoke($payload)
    +    {
    +        $this->events[] = $payload;
    +    }
    +}
    +
    +// Attach our callable class to the dispatcher.
    +$dispatcher->attachCallback('events', ($events = new EventsListener()));
    +
    +// Attach a function to control the dispatcher loop termination with a message.
    +$dispatcher->attachCallback('control', function ($payload) use ($dispatcher) {
    +    if ($payload === 'terminate_dispatcher') {
    +        $dispatcher->stop();
    +    }
    +});
    +
    +// Run the dispatcher loop until the callback attached to the 'control' channel
    +// receives 'terminate_dispatcher' as a message.
    +$dispatcher->run();
    +
    +// Display our achievements!
    +echo "We received {$events->count()} messages!\n";
    +
    +// Say goodbye :-)
    +$info = $client->info();
    +print_r("Goodbye from Redis v{$info['redis_version']}!\n");
    diff --git a/vendor/predis/predis/examples/KeyPrefixes.php b/vendor/predis/predis/examples/KeyPrefixes.php
    new file mode 100644
    index 0000000..4979676
    --- /dev/null
    +++ b/vendor/predis/predis/examples/KeyPrefixes.php
    @@ -0,0 +1,37 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +require 'SharedConfigurations.php';
    +
    +// Predis ships with a KeyPrefixProcessor class that is used to transparently
    +// prefix each key before sending commands to Redis, even for complex commands
    +// such as SORT, ZUNIONSTORE and ZINTERSTORE. Key prefixes are useful to create
    +// user-level namespaces for you keyspace, thus eliminating the need for separate
    +// logical databases.
    +
    +$client = new Predis\Client($single_server, array('prefix' => 'nrk:'));
    +
    +$client->mset(array('foo' => 'bar', 'lol' => 'wut'));
    +var_dump($client->mget('foo', 'lol'));
    +/*
    +array(2) {
    +  [0]=> string(3) "bar"
    +  [1]=> string(3) "wut"
    +}
    +*/
    +
    +var_dump($client->keys('*'));
    +/*
    +array(2) {
    +  [0]=> string(7) "nrk:foo"
    +  [1]=> string(7) "nrk:lol"
    +}
    +*/
    diff --git a/vendor/predis/predis/examples/MasterSlaveReplication.php b/vendor/predis/predis/examples/MasterSlaveReplication.php
    new file mode 100644
    index 0000000..df7e16c
    --- /dev/null
    +++ b/vendor/predis/predis/examples/MasterSlaveReplication.php
    @@ -0,0 +1,52 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +require 'SharedConfigurations.php';
    +
    +// Predis supports master / slave replication scenarios where write operations are
    +// performed on the master server and read operations are executed against one of
    +// the slaves. The behaviour of commands or EVAL scripts can be customized at will.
    +// As soon as a write operation is performed, all the subsequent requests (reads
    +// or writes) will be served by the master server.
    +//
    +// This example must be executed with the second Redis server acting as the slave
    +// of the first one using the SLAVEOF command.
    +//
    +
    +$parameters = array(
    +    'tcp://127.0.0.1:6379?database=15&alias=master',
    +    'tcp://127.0.0.1:6380?database=15&alias=slave',
    +);
    +
    +$options = array('replication' => true);
    +
    +$client = new Predis\Client($parameters, $options);
    +
    +// Read operation.
    +$exists = $client->exists('foo') ? 'yes' : 'no';
    +$current = $client->getConnection()->getCurrent()->getParameters();
    +echo "Does 'foo' exist on {$current->alias}? $exists.\n";
    +
    +// Write operation.
    +$client->set('foo', 'bar');
    +$current = $client->getConnection()->getCurrent()->getParameters();
    +echo "Now 'foo' has been set to 'bar' on {$current->alias}!\n";
    +
    +// Read operation.
    +$bar = $client->get('foo');
    +$current = $client->getConnection()->getCurrent()->getParameters();
    +echo "We just fetched 'foo' from {$current->alias} and its value is '$bar'.\n";
    +
    +/* OUTPUT:
    +Does 'foo' exist on slave? yes.
    +Now 'foo' has been set to 'bar' on master!
    +We just fetched 'foo' from master and its value is 'bar'.
    +*/
    diff --git a/vendor/predis/predis/examples/MasterSlaveReplicationComplex.php b/vendor/predis/predis/examples/MasterSlaveReplicationComplex.php
    new file mode 100644
    index 0000000..786d595
    --- /dev/null
    +++ b/vendor/predis/predis/examples/MasterSlaveReplicationComplex.php
    @@ -0,0 +1,84 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +require 'SharedConfigurations.php';
    +
    +// Predis allows to set Lua scripts as read-only operations in the context of
    +// replication. This works for both EVAL and EVALSHA and also for the client-side
    +// abstraction built upon them (Predis\Command\ScriptedCommand). This example
    +// shows a slightly more complex configuration that injects a new scripted command
    +// in the server profile used by the new client instance and marks it marks it as
    +// a read-only operation for replication so that it will be executed on slaves.
    +
    +use Predis\Command\ScriptedCommand;
    +use Predis\Connection\MasterSlaveReplication;
    +use Predis\Profile\ServerProfile;
    +use Predis\Replication\ReplicationStrategy;
    +
    +// ------------------------------------------------------------------------- //
    +
    +// Define a new scripted command that returns all the fields
    +// of a variable number of hashes with a single roundtrip.
    +
    +class HashMultipleGetAll extends ScriptedCommand {
    +    const BODY = << function ($options, $option) {
    +        $profile = $options->getDefault($option);
    +        $profile->defineCommand('hmgetall', 'HashMultipleGetAll');
    +
    +        return $profile;
    +    },
    +    'replication' => function ($options) {
    +        $strategy = new ReplicationStrategy();
    +        $strategy->setScriptReadOnly(HashMultipleGetAll::BODY);
    +
    +        $replication = new MasterSlaveReplication($strategy);
    +
    +        return $replication;
    +    },
    +);
    +
    +// ------------------------------------------------------------------------- //
    +
    +$client = new Predis\Client($parameters, $options);
    +
    +// Execute the following commands on the master server using redis-cli:
    +// $ ./redis-cli HMSET metavars foo bar hoge piyo
    +// $ ./redis-cli HMSET servers master host1 slave host2
    +
    +$hashes = $client->hmgetall('metavars', 'servers');
    +
    +$replication = $client->getConnection();
    +$stillOnSlave = $replication->getCurrent() === $replication->getConnectionById('slave');
    +
    +echo "Is still on slave? ", $stillOnSlave ? 'YES' : 'NO', "!\n";
    +var_export($hashes);
    diff --git a/vendor/predis/predis/examples/MonitorContext.php b/vendor/predis/predis/examples/MonitorContext.php
    new file mode 100644
    index 0000000..e1cad74
    --- /dev/null
    +++ b/vendor/predis/predis/examples/MonitorContext.php
    @@ -0,0 +1,44 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +require 'SharedConfigurations.php';
    +
    +// This is a basic example on how to use the Predis\MonitorContext class.
    +// You can use redis-cli to send commands to the same Redis instance your client is
    +// connected to, and then type "ECHO QUIT_MONITOR" in redis-cli when you want to
    +// exit the monitor loop and terminate this script in a graceful way.
    +
    +// Create a client and disable r/w timeout on the socket.
    +$client = new Predis\Client($single_server + array('read_write_timeout' => 0));
    +
    +// Use only one instance of DateTime, we will update the timestamp later.
    +$timestamp = new DateTime();
    +
    +foreach (($monitor = $client->monitor()) as $event) {
    +    $timestamp->setTimestamp((int) $event->timestamp);
    +
    +    // If we notice a ECHO command with the message QUIT_MONITOR, we close the
    +    // monitor context and then break the loop.
    +    if ($event->command === 'ECHO' && $event->arguments === '"QUIT_MONITOR"') {
    +        echo "Exiting the monitor loop...\n";
    +        $monitor->closeContext();
    +        break;
    +    }
    +
    +    echo "* Received {$event->command} on DB {$event->database} at {$timestamp->format(DateTime::W3C)}\n";
    +    if (isset($event->arguments)) {
    +        echo "    Arguments: {$event->arguments}\n";
    +    }
    +}
    +
    +// Say goodbye :-)
    +$info = $client->info();
    +print_r("Goodbye from Redis v{$info['redis_version']}!\n");
    diff --git a/vendor/predis/predis/examples/MultiBulkReplyIterators.php b/vendor/predis/predis/examples/MultiBulkReplyIterators.php
    new file mode 100644
    index 0000000..27c51b9
    --- /dev/null
    +++ b/vendor/predis/predis/examples/MultiBulkReplyIterators.php
    @@ -0,0 +1,56 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +require 'SharedConfigurations.php';
    +
    +// Operations such as LRANGE, ZRANGE and others can potentially generate replies
    +// containing a huge number of items. In some corner cases, such replies might
    +// end up exhausting the maximum allowed memory allocated for a PHP process.
    +// Multibulk iterators can be handy because they allow you to stream multibulk
    +// replies using plain old PHP iterators, making it possible to iterate them with
    +// a classic `foreach` loop and avoiding to consume an excessive amount of memory.
    +//
    +// PS: please note that multibulk iterators are supported only by the standard
    +// connection backend class (Predis\Connection\StreamConnection) and not the
    +// phpiredis-based one (Predis\Connection\PhpiredisConnection).
    +
    +// Create a client and force the connection to use iterable multibulk responses.
    +$client = new Predis\Client($single_server + array('iterable_multibulk' => true));
    +
    +// Prepare an hash with some fields and their respective values.
    +$client->hmset('metavars', array('foo' => 'bar', 'hoge' => 'piyo', 'lol' => 'wut'));
    +
    +// By default multibulk iterators iterate over the reply as a list of items...
    +foreach ($client->hgetall('metavars') as $index => $item) {
    +    echo "[$index] $item\n";
    +}
    +
    +/* OUTPUT:
    +[0] foo
    +[1] bar
    +[2] hoge
    +[3] piyo
    +[4] lol
    +[5] wut
    +*/
    +
    +// ... but certain multibulk replies are better represented as lists of tuples.
    +foreach ($client->hgetall('metavars')->asTuple() as $index => $kv) {
    +    list($key, $value) = $kv;
    +
    +    echo "[$index] $key => $value\n";
    +}
    +
    +/* OUTPUT:
    +[0] foo => bar
    +[1] hoge => piyo
    +[2] lol => wut
    +*/
    diff --git a/vendor/predis/predis/examples/MultiExecTransactionsWithCAS.php b/vendor/predis/predis/examples/MultiExecTransactionsWithCAS.php
    new file mode 100644
    index 0000000..6450ab8
    --- /dev/null
    +++ b/vendor/predis/predis/examples/MultiExecTransactionsWithCAS.php
    @@ -0,0 +1,51 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +require 'SharedConfigurations.php';
    +
    +// This is an implementation of an atomic client-side ZPOP using the support for
    +// check-and-set (CAS) operations with MULTI/EXEC transactions, as described in
    +// "WATCH explained" from http://redis.io/topics/transactions
    +//
    +// First, populate your database with a tiny sample data set:
    +//
    +// ./redis-cli
    +// SELECT 15
    +// ZADD zset 1 a
    +// ZADD zset 2 b
    +// ZADD zset 3 c
    +
    +function zpop($client, $key)
    +{
    +    $element = null;
    +    $options = array(
    +        'cas'   => true,    // Initialize with support for CAS operations
    +        'watch' => $key,    // Key that needs to be WATCHed to detect changes
    +        'retry' => 3,       // Number of retries on aborted transactions, after
    +                            // which the client bails out with an exception.
    +    );
    +
    +    $client->multiExec($options, function ($tx) use ($key, &$element) {
    +        @list($element) = $tx->zrange($key, 0, 0);
    +
    +        if (isset($element)) {
    +            $tx->multi();   // With CAS, MULTI *must* be explicitly invoked.
    +            $tx->zrem($key, $element);
    +        }
    +    });
    +
    +    return $element;
    +}
    +
    +$client = new Predis\Client($single_server);
    +$zpopped = zpop($client, 'zset');
    +
    +echo isset($zpopped) ? "ZPOPed $zpopped" : "Nothing to ZPOP!", "\n";
    diff --git a/vendor/predis/predis/examples/MultipleSetAndGet.php b/vendor/predis/predis/examples/MultipleSetAndGet.php
    new file mode 100644
    index 0000000..864f827
    --- /dev/null
    +++ b/vendor/predis/predis/examples/MultipleSetAndGet.php
    @@ -0,0 +1,38 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +require 'SharedConfigurations.php';
    +
    +// redis can set keys and their relative values in one go
    +// using MSET, then the same values can be retrieved with
    +// a single command using MGET.
    +
    +$mkv = array(
    +    'usr:0001' => 'First user',
    +    'usr:0002' => 'Second user',
    +    'usr:0003' => 'Third user'
    +);
    +
    +$client = new Predis\Client($single_server);
    +
    +$client->mset($mkv);
    +$retval = $client->mget(array_keys($mkv));
    +
    +print_r($retval);
    +
    +/* OUTPUT:
    +Array
    +(
    +    [0] => First user
    +    [1] => Second user
    +    [2] => Third user
    +)
    +*/
    diff --git a/vendor/predis/predis/examples/PipelineContext.php b/vendor/predis/predis/examples/PipelineContext.php
    new file mode 100644
    index 0000000..57fa638
    --- /dev/null
    +++ b/vendor/predis/predis/examples/PipelineContext.php
    @@ -0,0 +1,47 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +require 'SharedConfigurations.php';
    +
    +// When you have a whole set of consecutive commands to send to
    +// a redis server, you can use a pipeline to improve performances.
    +
    +$client = new Predis\Client($single_server);
    +
    +$replies = $client->pipeline(function ($pipe) {
    +    $pipe->ping();
    +    $pipe->flushdb();
    +    $pipe->incrby('counter', 10);
    +    $pipe->incrby('counter', 30);
    +    $pipe->exists('counter');
    +    $pipe->get('counter');
    +    $pipe->mget('does_not_exist', 'counter');
    +});
    +
    +print_r($replies);
    +
    +/* OUTPUT:
    +Array
    +(
    +    [0] => 1
    +    [1] => 1
    +    [2] => 10
    +    [3] => 40
    +    [4] => 1
    +    [5] => 40
    +    [6] => Array
    +        (
    +            [0] =>
    +            [1] => 40
    +        )
    +
    +)
    +*/
    diff --git a/vendor/predis/predis/examples/PubSubContext.php b/vendor/predis/predis/examples/PubSubContext.php
    new file mode 100644
    index 0000000..d08eac6
    --- /dev/null
    +++ b/vendor/predis/predis/examples/PubSubContext.php
    @@ -0,0 +1,59 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +require 'SharedConfigurations.php';
    +
    +// Redis 2.0 features new commands that allow clients to subscribe for
    +// events published on certain channels (PUBSUB).
    +
    +// Create a client and disable r/w timeout on the socket
    +$client = new Predis\Client($single_server + array('read_write_timeout' => 0));
    +
    +// Initialize a new pubsub context
    +$pubsub = $client->pubSub();
    +
    +// Subscribe to your channels
    +$pubsub->subscribe('control_channel', 'notifications');
    +
    +// Start processing the pubsup messages. Open a terminal and use redis-cli
    +// to push messages to the channels. Examples:
    +//   ./redis-cli PUBLISH notifications "this is a test"
    +//   ./redis-cli PUBLISH control_channel quit_loop
    +foreach ($pubsub as $message) {
    +    switch ($message->kind) {
    +        case 'subscribe':
    +            echo "Subscribed to {$message->channel}\n";
    +            break;
    +
    +        case 'message':
    +            if ($message->channel == 'control_channel') {
    +                if ($message->payload == 'quit_loop') {
    +                    echo "Aborting pubsub loop...\n";
    +                    $pubsub->unsubscribe();
    +                } else {
    +                    echo "Received an unrecognized command: {$message->payload}.\n";
    +                }
    +            } else {
    +                echo "Received the following message from {$message->channel}:\n",
    +                     "  {$message->payload}\n\n";
    +            }
    +            break;
    +    }
    +}
    +
    +// Always unset the pubsub context instance when you are done! The
    +// class destructor will take care of cleanups and prevent protocol
    +// desynchronizations between the client and the server.
    +unset($pubsub);
    +
    +// Say goodbye :-)
    +$info = $client->info();
    +print_r("Goodbye from Redis v{$info['redis_version']}!\n");
    diff --git a/vendor/predis/predis/examples/ServerSideScripting.php b/vendor/predis/predis/examples/ServerSideScripting.php
    new file mode 100644
    index 0000000..ac236e6
    --- /dev/null
    +++ b/vendor/predis/predis/examples/ServerSideScripting.php
    @@ -0,0 +1,66 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +require 'SharedConfigurations.php';
    +
    +// This example will not work with versions of Redis < 2.6.
    +//
    +// Additionally to the EVAL command defined in the current development profile, the new
    +// Predis\Command\ScriptedCommand base class can be used to build an higher abstraction
    +// for our "scripted" commands so that they will appear just like any other command on
    +// the client-side. This is a quick example used to implement INCREX.
    +
    +use Predis\Command\ScriptedCommand;
    +
    +class IncrementExistingKeysBy extends ScriptedCommand
    +{
    +    public function getKeysCount()
    +    {
    +        // Tell Predis to use all the arguments but the last one as arguments
    +        // for KEYS. The last one will be used to populate ARGV.
    +        return -1;
    +    }
    +
    +    public function getScript()
    +    {
    +        return
    +<<getProfile()->defineCommand('increxby', 'IncrementExistingKeysBy');
    +
    +$client->mset('foo', 10, 'foobar', 100);
    +
    +var_export($client->increxby('foo', 'foofoo', 'foobar', 50));
    +
    +/*
    +array (
    +  0 => 60,
    +  1 => NULL,
    +  2 => 150,
    +)
    +*/
    diff --git a/vendor/predis/predis/examples/SessionHandler.php b/vendor/predis/predis/examples/SessionHandler.php
    new file mode 100644
    index 0000000..0ff6c95
    --- /dev/null
    +++ b/vendor/predis/predis/examples/SessionHandler.php
    @@ -0,0 +1,39 @@
    += 5.4 but can be used on PHP 5.3 if a polyfill for
    +// SessionHandlerInterface (see http://www.php.net/class.sessionhandlerinterface.php)
    +// is provided either by you or an external package like `symfony/http-foundation`.
    +
    +if (!interface_exists('SessionHandlerInterface')) {
    +    die("ATTENTION: the session handler implemented by Predis needs PHP >= 5.4.0 or a polyfill ".
    +        "for \SessionHandlerInterface either provided by you or an external package.\n");
    +}
    +
    +// Instantiate a new client just like you would normally do. We'll prefix our session keys here.
    +$client = new Predis\Client($single_server, array('prefix' => 'sessions:'));
    +
    +// Set `gc_maxlifetime` so that a session will be expired after 5 seconds since last access.
    +$handler = new Predis\Session\SessionHandler($client, array('gc_maxlifetime' => 5));
    +
    +// Register our session handler (it uses `session_set_save_handler()` internally).
    +$handler->register();
    +
    +// Set a fixed session ID just for the sake of our example.
    +session_id('example_session_id');
    +
    +session_start();
    +
    +if (isset($_SESSION['foo'])) {
    +    echo "Session has `foo` set to {$_SESSION['foo']}\n";
    +} else {
    +    $_SESSION['foo'] = $value = mt_rand();
    +    echo "Empty session, `foo` has been set with $value\n";
    +}
    diff --git a/vendor/predis/predis/examples/SharedConfigurations.php b/vendor/predis/predis/examples/SharedConfigurations.php
    new file mode 100644
    index 0000000..20ca6b5
    --- /dev/null
    +++ b/vendor/predis/predis/examples/SharedConfigurations.php
    @@ -0,0 +1,33 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +require __DIR__.'/../autoload.php';
    +
    +$single_server = array(
    +    'host'     => '127.0.0.1',
    +    'port'     => 6379,
    +    'database' => 15
    +);
    +
    +$multiple_servers = array(
    +    array(
    +       'host'     => '127.0.0.1',
    +       'port'     => 6379,
    +       'database' => 15,
    +       'alias'    => 'first',
    +    ),
    +    array(
    +       'host'     => '127.0.0.1',
    +       'port'     => 6380,
    +       'database' => 15,
    +       'alias'    => 'second',
    +    ),
    +);
    diff --git a/vendor/predis/predis/examples/SimpleDebuggableConnection.php b/vendor/predis/predis/examples/SimpleDebuggableConnection.php
    new file mode 100644
    index 0000000..0d9e4b5
    --- /dev/null
    +++ b/vendor/predis/predis/examples/SimpleDebuggableConnection.php
    @@ -0,0 +1,88 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +require 'SharedConfigurations.php';
    +
    +use Predis\Command\CommandInterface;
    +use Predis\Connection\StreamConnection;
    +
    +class SimpleDebuggableConnection extends StreamConnection
    +{
    +    private $tstart = 0;
    +    private $debugBuffer = array();
    +
    +    public function connect()
    +    {
    +        $this->tstart = microtime(true);
    +
    +        parent::connect();
    +    }
    +
    +    private function storeDebug(CommandInterface $command, $direction)
    +    {
    +        $firtsArg  = $command->getArgument(0);
    +        $timestamp = round(microtime(true) - $this->tstart, 4);
    +
    +        $debug  = $command->getId();
    +        $debug .= isset($firtsArg) ? " $firtsArg " : ' ';
    +        $debug .= "$direction $this";
    +        $debug .= " [{$timestamp}s]";
    +
    +        $this->debugBuffer[] = $debug;
    +    }
    +
    +    public function writeCommand(CommandInterface $command)
    +    {
    +        parent::writeCommand($command);
    +
    +        $this->storeDebug($command, '->');
    +    }
    +
    +    public function readResponse(CommandInterface $command)
    +    {
    +        $reply = parent::readResponse($command);
    +        $this->storeDebug($command, '<-');
    +
    +        return $reply;
    +    }
    +
    +    public function getDebugBuffer()
    +    {
    +        return $this->debugBuffer;
    +    }
    +}
    +
    +$options = array(
    +    'connections' => array(
    +        'tcp' => 'SimpleDebuggableConnection',
    +    ),
    +);
    +
    +$client = new Predis\Client($single_server, $options);
    +$client->set('foo', 'bar');
    +$client->get('foo');
    +$client->info();
    +
    +print_r($client->getConnection()->getDebugBuffer());
    +
    +/* OUTPUT:
    +Array
    +(
    +    [0] => SELECT 15 -> 127.0.0.1:6379 [0.0008s]
    +    [1] => SELECT 15 <- 127.0.0.1:6379 [0.0012s]
    +    [2] => SET foo -> 127.0.0.1:6379 [0.0014s]
    +    [3] => SET foo <- 127.0.0.1:6379 [0.0014s]
    +    [4] => GET foo -> 127.0.0.1:6379 [0.0016s]
    +    [5] => GET foo <- 127.0.0.1:6379 [0.0018s]
    +    [6] => INFO -> 127.0.0.1:6379 [0.002s]
    +    [7] => INFO <- 127.0.0.1:6379 [0.0025s]
    +)
    +*/
    diff --git a/vendor/predis/predis/examples/SimpleSetAndGet.php b/vendor/predis/predis/examples/SimpleSetAndGet.php
    new file mode 100644
    index 0000000..3082053
    --- /dev/null
    +++ b/vendor/predis/predis/examples/SimpleSetAndGet.php
    @@ -0,0 +1,25 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +require 'SharedConfigurations.php';
    +
    +// simple set and get scenario
    +
    +$client = new Predis\Client($single_server);
    +
    +$client->set('library', 'predis');
    +$retval = $client->get('library');
    +
    +var_dump($retval);
    +
    +/* OUTPUT
    +string(6) "predis"
    +*/
    diff --git a/vendor/predis/predis/lib/Predis/Autoloader.php b/vendor/predis/predis/lib/Predis/Autoloader.php
    new file mode 100644
    index 0000000..d48635a
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Autoloader.php
    @@ -0,0 +1,62 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +/**
    + * Implements a lightweight PSR-0 compliant autoloader.
    + *
    + * @author Eric Naeseth 
    + * @author Daniele Alessandri 
    + */
    +class Autoloader
    +{
    +    private $directory;
    +    private $prefix;
    +    private $prefixLength;
    +
    +    /**
    +     * @param string $baseDirectory Base directory where the source files are located.
    +     */
    +    public function __construct($baseDirectory = __DIR__)
    +    {
    +        $this->directory = $baseDirectory;
    +        $this->prefix = __NAMESPACE__ . '\\';
    +        $this->prefixLength = strlen($this->prefix);
    +    }
    +
    +    /**
    +     * Registers the autoloader class with the PHP SPL autoloader.
    +     *
    +     * @param boolean $prepend Prepend the autoloader on the stack instead of appending it.
    +     */
    +    public static function register($prepend = false)
    +    {
    +        spl_autoload_register(array(new self, 'autoload'), true, $prepend);
    +    }
    +
    +    /**
    +     * Loads a class from a file using its fully qualified name.
    +     *
    +     * @param string $className Fully qualified name of a class.
    +     */
    +    public function autoload($className)
    +    {
    +        if (0 === strpos($className, $this->prefix)) {
    +            $parts = explode('\\', substr($className, $this->prefixLength));
    +            $filepath = $this->directory.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts).'.php';
    +
    +            if (is_file($filepath)) {
    +                require($filepath);
    +            }
    +        }
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/BasicClientInterface.php b/vendor/predis/predis/lib/Predis/BasicClientInterface.php
    new file mode 100644
    index 0000000..a25f410
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/BasicClientInterface.php
    @@ -0,0 +1,31 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +use Predis\Command\CommandInterface;
    +
    +/**
    + * Defines the interface of a basic client object or abstraction that
    + * can send commands to Redis.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface BasicClientInterface
    +{
    +    /**
    +     * Executes the specified Redis command.
    +     *
    +     * @param CommandInterface $command A Redis command.
    +     * @return mixed
    +     */
    +    public function executeCommand(CommandInterface $command);
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Client.php b/vendor/predis/predis/lib/Predis/Client.php
    new file mode 100644
    index 0000000..8abef13
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Client.php
    @@ -0,0 +1,436 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +use Predis\Command\CommandInterface;
    +use Predis\Command\ScriptedCommand;
    +use Predis\Connection\AggregatedConnectionInterface;
    +use Predis\Connection\ConnectionInterface;
    +use Predis\Connection\ConnectionFactoryInterface;
    +use Predis\Monitor\MonitorContext;
    +use Predis\Option\ClientOptions;
    +use Predis\Option\ClientOptionsInterface;
    +use Predis\Pipeline\PipelineContext;
    +use Predis\Profile\ServerProfile;
    +use Predis\PubSub\PubSubContext;
    +use Predis\Transaction\MultiExecContext;
    +
    +/**
    + * Main class that exposes the most high-level interface to interact with Redis.
    + *
    + * @author Daniele Alessandri 
    + */
    +class Client implements ClientInterface
    +{
    +    const VERSION = '0.8.4';
    +
    +    private $options;
    +    private $profile;
    +    private $connection;
    +
    +    /**
    +     * Initializes a new client with optional connection parameters and client options.
    +     *
    +     * @param mixed $parameters Connection parameters for one or multiple servers.
    +     * @param mixed $options Options that specify certain behaviours for the client.
    +     */
    +    public function __construct($parameters = null, $options = null)
    +    {
    +        $this->options = $this->filterOptions($options);
    +        $this->profile = $this->options->profile;
    +        $this->connection = $this->initializeConnection($parameters);
    +    }
    +
    +    /**
    +     * Creates an instance of Predis\Option\ClientOptions from various types of
    +     * arguments (string, array, Predis\Profile\ServerProfile) or returns the
    +     * passed object if it is an instance of Predis\Option\ClientOptions.
    +     *
    +     * @param mixed $options Client options.
    +     * @return ClientOptions
    +     */
    +    protected function filterOptions($options)
    +    {
    +        if (!isset($options)) {
    +            return new ClientOptions();
    +        }
    +
    +        if (is_array($options)) {
    +            return new ClientOptions($options);
    +        }
    +
    +        if ($options instanceof ClientOptionsInterface) {
    +            return $options;
    +        }
    +
    +        throw new \InvalidArgumentException("Invalid type for client options");
    +    }
    +
    +    /**
    +     * Initializes one or multiple connection (cluster) objects from various
    +     * types of arguments (string, array) or returns the passed object if it
    +     * implements Predis\Connection\ConnectionInterface.
    +     *
    +     * @param mixed $parameters Connection parameters or instance.
    +     * @return ConnectionInterface
    +     */
    +    protected function initializeConnection($parameters)
    +    {
    +        if ($parameters instanceof ConnectionInterface) {
    +            return $parameters;
    +        }
    +
    +        if (is_array($parameters) && isset($parameters[0])) {
    +            $options = $this->options;
    +            $replication = isset($options->replication) && $options->replication;
    +            $connection = $options->{$replication ? 'replication' : 'cluster'};
    +
    +            return $options->connections->createAggregated($connection, $parameters);
    +        }
    +
    +        if (is_callable($parameters)) {
    +            $connection = call_user_func($parameters, $this->options);
    +
    +            if (!$connection instanceof ConnectionInterface) {
    +                throw new \InvalidArgumentException(
    +                    'Callable parameters must return instances of Predis\Connection\ConnectionInterface'
    +                );
    +            }
    +
    +            return $connection;
    +        }
    +
    +        return $this->options->connections->create($parameters);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getProfile()
    +    {
    +        return $this->profile;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getOptions()
    +    {
    +        return $this->options;
    +    }
    +
    +    /**
    +     * Returns the connection factory object used by the client.
    +     *
    +     * @return ConnectionFactoryInterface
    +     */
    +    public function getConnectionFactory()
    +    {
    +        return $this->options->connections;
    +    }
    +
    +    /**
    +     * Returns a new instance of a client for the specified connection when the
    +     * client is connected to a cluster. The new instance will use the same
    +     * options of the original client.
    +     *
    +     * @return Client
    +     */
    +    public function getClientFor($connectionID)
    +    {
    +        if (!$connection = $this->getConnectionById($connectionID)) {
    +            throw new \InvalidArgumentException("Invalid connection ID: '$connectionID'");
    +        }
    +
    +        return new static($connection, $this->options);
    +    }
    +
    +    /**
    +     * Opens the connection to the server.
    +     */
    +    public function connect()
    +    {
    +        $this->connection->connect();
    +    }
    +
    +    /**
    +     * Disconnects from the server.
    +     */
    +    public function disconnect()
    +    {
    +        $this->connection->disconnect();
    +    }
    +
    +    /**
    +     * Disconnects from the server.
    +     *
    +     * This method is an alias of disconnect().
    +     */
    +    public function quit()
    +    {
    +        $this->disconnect();
    +    }
    +
    +    /**
    +     * Checks if the underlying connection is connected to Redis.
    +     *
    +     * @return Boolean True means that the connection is open.
    +     *                 False means that the connection is closed.
    +     */
    +    public function isConnected()
    +    {
    +        return $this->connection->isConnected();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getConnection()
    +    {
    +        return $this->connection;
    +    }
    +
    +    /**
    +     * Retrieves a single connection out of an aggregated connections instance.
    +     *
    +     * @param string $connectionId Index or alias of the connection.
    +     * @return Connection\SingleConnectionInterface
    +     */
    +    public function getConnectionById($connectionId)
    +    {
    +        if (!$this->connection instanceof AggregatedConnectionInterface) {
    +            throw new NotSupportedException('Retrieving connections by ID is supported only when using aggregated connections');
    +        }
    +
    +        return $this->connection->getConnectionById($connectionId);
    +    }
    +
    +    /**
    +     * Dynamically invokes a Redis command with the specified arguments.
    +     *
    +     * @param string $method The name of a Redis command.
    +     * @param array $arguments The arguments for the command.
    +     * @return mixed
    +     */
    +    public function __call($method, $arguments)
    +    {
    +        $command = $this->profile->createCommand($method, $arguments);
    +        $response = $this->connection->executeCommand($command);
    +
    +        if ($response instanceof ResponseObjectInterface) {
    +            if ($response instanceof ResponseErrorInterface) {
    +                $response = $this->onResponseError($command, $response);
    +            }
    +
    +            return $response;
    +        }
    +
    +        return $command->parseResponse($response);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function createCommand($method, $arguments = array())
    +    {
    +        return $this->profile->createCommand($method, $arguments);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function executeCommand(CommandInterface $command)
    +    {
    +        $response = $this->connection->executeCommand($command);
    +
    +        if ($response instanceof ResponseObjectInterface) {
    +            if ($response instanceof ResponseErrorInterface) {
    +                $response = $this->onResponseError($command, $response);
    +            }
    +
    +            return $response;
    +        }
    +
    +        return $command->parseResponse($response);
    +    }
    +
    +    /**
    +     * Handles -ERR responses returned by Redis.
    +     *
    +     * @param CommandInterface $command The command that generated the error.
    +     * @param ResponseErrorInterface $response The error response instance.
    +     * @return mixed
    +     */
    +    protected function onResponseError(CommandInterface $command, ResponseErrorInterface $response)
    +    {
    +        if ($command instanceof ScriptedCommand && $response->getErrorType() === 'NOSCRIPT') {
    +            $eval = $this->createCommand('eval');
    +            $eval->setRawArguments($command->getEvalArguments());
    +
    +            $response = $this->executeCommand($eval);
    +
    +            if (!$response instanceof ResponseObjectInterface) {
    +                $response = $command->parseResponse($response);
    +            }
    +
    +            return $response;
    +        }
    +
    +        if ($this->options->exceptions) {
    +            throw new ServerException($response->getMessage());
    +        }
    +
    +        return $response;
    +    }
    +
    +    /**
    +     * Calls the specified initializer method on $this with 0, 1 or 2 arguments.
    +     *
    +     * TODO: Invert $argv and $initializer.
    +     *
    +     * @param array $argv Arguments for the initializer.
    +     * @param string $initializer The initializer method.
    +     * @return mixed
    +     */
    +    private function sharedInitializer($argv, $initializer)
    +    {
    +        switch (count($argv)) {
    +            case 0:
    +                return $this->$initializer();
    +
    +            case 1:
    +                list($arg0) = $argv;
    +                return is_array($arg0) ? $this->$initializer($arg0) : $this->$initializer(null, $arg0);
    +
    +            case 2:
    +                list($arg0, $arg1) = $argv;
    +                return $this->$initializer($arg0, $arg1);
    +
    +            default:
    +                return $this->$initializer($this, $argv);
    +        }
    +    }
    +
    +    /**
    +     * Creates a new pipeline context and returns it, or returns the results of
    +     * a pipeline executed inside the optionally provided callable object.
    +     *
    +     * @param mixed $arg,... Options for the context, a callable object, or both.
    +     * @return PipelineContext|array
    +     */
    +    public function pipeline(/* arguments */)
    +    {
    +        return $this->sharedInitializer(func_get_args(), 'initPipeline');
    +    }
    +
    +    /**
    +     * Pipeline context initializer.
    +     *
    +     * @param array $options Options for the context.
    +     * @param mixed $callable Optional callable object used to execute the context.
    +     * @return PipelineContext|array
    +     */
    +    protected function initPipeline(Array $options = null, $callable = null)
    +    {
    +        $executor = isset($options['executor']) ? $options['executor'] : null;
    +
    +        if (is_callable($executor)) {
    +            $executor = call_user_func($executor, $this, $options);
    +        }
    +
    +        $pipeline = new PipelineContext($this, $executor);
    +        $replies  = $this->pipelineExecute($pipeline, $callable);
    +
    +        return $replies;
    +    }
    +
    +    /**
    +     * Executes a pipeline context when a callable object is passed.
    +     *
    +     * @param array $options Options of the context initialization.
    +     * @param mixed $callable Optional callable object used to execute the context.
    +     * @return PipelineContext|array
    +     */
    +    private function pipelineExecute(PipelineContext $pipeline, $callable)
    +    {
    +        return isset($callable) ? $pipeline->execute($callable) : $pipeline;
    +    }
    +
    +    /**
    +     * Creates a new transaction context and returns it, or returns the results of
    +     * a transaction executed inside the optionally provided callable object.
    +     *
    +     * @param mixed $arg,... Options for the context, a callable object, or both.
    +     * @return MultiExecContext|array
    +     */
    +    public function multiExec(/* arguments */)
    +    {
    +        return $this->sharedInitializer(func_get_args(), 'initMultiExec');
    +    }
    +
    +    /**
    +     * Transaction context initializer.
    +     *
    +     * @param array $options Options for the context.
    +     * @param mixed $callable Optional callable object used to execute the context.
    +     * @return MultiExecContext|array
    +     */
    +    protected function initMultiExec(Array $options = null, $callable = null)
    +    {
    +        $transaction = new MultiExecContext($this, $options ?: array());
    +        return isset($callable) ? $transaction->execute($callable) : $transaction;
    +    }
    +
    +    /**
    +     * Creates a new Publish / Subscribe context and returns it, or executes it
    +     * inside the optionally provided callable object.
    +     *
    +     * @param mixed $arg,... Options for the context, a callable object, or both.
    +     * @return MultiExecContext|array
    +     */
    +    public function pubSub(/* arguments */)
    +    {
    +        return $this->sharedInitializer(func_get_args(), 'initPubSub');
    +    }
    +
    +    /**
    +     * Publish / Subscribe context initializer.
    +     *
    +     * @param array $options Options for the context.
    +     * @param mixed $callable Optional callable object used to execute the context.
    +     * @return PubSubContext
    +     */
    +    protected function initPubSub(Array $options = null, $callable = null)
    +    {
    +        $pubsub = new PubSubContext($this, $options);
    +
    +        if (!isset($callable)) {
    +            return $pubsub;
    +        }
    +
    +        foreach ($pubsub as $message) {
    +            if (call_user_func($callable, $pubsub, $message) === false) {
    +                $pubsub->closeContext();
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Returns a new monitor context.
    +     *
    +     * @return MonitorContext
    +     */
    +    public function monitor()
    +    {
    +        return new MonitorContext($this);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/ClientException.php b/vendor/predis/predis/lib/Predis/ClientException.php
    new file mode 100644
    index 0000000..6c07aaf
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/ClientException.php
    @@ -0,0 +1,21 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +/**
    + * Exception class that identifies client-side errors.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ClientException extends PredisException
    +{
    +}
    diff --git a/vendor/predis/predis/lib/Predis/ClientInterface.php b/vendor/predis/predis/lib/Predis/ClientInterface.php
    new file mode 100644
    index 0000000..767c3da
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/ClientInterface.php
    @@ -0,0 +1,66 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +use Predis\Connection\ConnectionInterface;
    +use Predis\Option\ClientOptionsInterface;
    +use Predis\Profile\ServerProfileInterface;
    +
    +/**
    + * Interface defining the most important parts needed to create an
    + * high-level Redis client object that can interact with other
    + * building blocks of Predis.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface ClientInterface extends BasicClientInterface
    +{
    +    /**
    +     * Returns the server profile used by the client.
    +     *
    +     * @return ServerProfileInterface
    +     */
    +    public function getProfile();
    +
    +    /**
    +     * Returns the client options specified upon initialization.
    +     *
    +     * @return ClientOptionsInterface
    +     */
    +    public function getOptions();
    +
    +    /**
    +     * Opens the connection to the server.
    +     */
    +    public function connect();
    +
    +    /**
    +     * Disconnects from the server.
    +     */
    +    public function disconnect();
    +
    +    /**
    +     * Returns the underlying connection instance.
    +     *
    +     * @return ConnectionInterface
    +     */
    +    public function getConnection();
    +
    +    /**
    +     * Creates a new instance of the specified Redis command.
    +     *
    +     * @param string $method The name of a Redis command.
    +     * @param array $arguments The arguments for the command.
    +     * @return Command\CommandInterface
    +     */
    +    public function createCommand($method, $arguments = array());
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Cluster/CommandHashStrategyInterface.php b/vendor/predis/predis/lib/Predis/Cluster/CommandHashStrategyInterface.php
    new file mode 100644
    index 0000000..bfea050
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Cluster/CommandHashStrategyInterface.php
    @@ -0,0 +1,42 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Cluster;
    +
    +use Predis\Command\CommandInterface;
    +
    +/**
    + * Interface for classes defining the strategy used to calculate an hash
    + * out of keys extracted from supported commands.
    + *
    + * This is mostly useful to support clustering via client-side sharding.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface CommandHashStrategyInterface
    +{
    +    /**
    +     * Returns the hash for the given command using the specified algorithm, or null
    +     * if the command cannot be hashed.
    +     *
    +     * @param CommandInterface $command Command to be hashed.
    +     * @return int
    +     */
    +    public function getHash(CommandInterface $command);
    +
    +    /**
    +     * Returns the hash for the given key using the specified algorithm.
    +     *
    +     * @param string $key Key to be hashed.
    +     * @return string
    +     */
    +    public function getKeyHash($key);
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Cluster/Distribution/DistributionStrategyInterface.php b/vendor/predis/predis/lib/Predis/Cluster/Distribution/DistributionStrategyInterface.php
    new file mode 100644
    index 0000000..2b07936
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Cluster/Distribution/DistributionStrategyInterface.php
    @@ -0,0 +1,52 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Cluster\Distribution;
    +
    +use Predis\Cluster\Hash\HashGeneratorInterface;
    +
    +/**
    + * A distributor implements the logic to automatically distribute
    + * keys among several nodes for client-side sharding.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface DistributionStrategyInterface
    +{
    +    /**
    +     * Adds a node to the distributor with an optional weight.
    +     *
    +     * @param mixed $node Node object.
    +     * @param int $weight Weight for the node.
    +     */
    +    public function add($node, $weight = null);
    +
    +    /**
    +     * Removes a node from the distributor.
    +     *
    +     * @param mixed $node Node object.
    +     */
    +    public function remove($node);
    +
    +    /**
    +     * Gets a node from the distributor using the computed hash of a key.
    +     *
    +     * @return mixed
    +     */
    +    public function get($key);
    +
    +    /**
    +     * Returns the underlying hash generator instance.
    +     *
    +     * @return HashGeneratorInterface
    +     */
    +    public function getHashGenerator();
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Cluster/Distribution/EmptyRingException.php b/vendor/predis/predis/lib/Predis/Cluster/Distribution/EmptyRingException.php
    new file mode 100644
    index 0000000..ed08ca3
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Cluster/Distribution/EmptyRingException.php
    @@ -0,0 +1,21 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Cluster\Distribution;
    +
    +/**
    + * Exception class that identifies empty rings.
    + *
    + * @author Daniele Alessandri 
    + */
    +class EmptyRingException extends \Exception
    +{
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Cluster/Distribution/HashRing.php b/vendor/predis/predis/lib/Predis/Cluster/Distribution/HashRing.php
    new file mode 100644
    index 0000000..8958929
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Cluster/Distribution/HashRing.php
    @@ -0,0 +1,246 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Cluster\Distribution;
    +
    +use Predis\Cluster\Hash\HashGeneratorInterface;
    +
    +/**
    + * This class implements an hashring-based distributor that uses the same
    + * algorithm of memcache to distribute keys in a cluster using client-side
    + * sharding.
    + *
    + * @author Daniele Alessandri 
    + * @author Lorenzo Castelli 
    + */
    +class HashRing implements DistributionStrategyInterface, HashGeneratorInterface
    +{
    +    const DEFAULT_REPLICAS = 128;
    +    const DEFAULT_WEIGHT   = 100;
    +
    +    private $ring;
    +    private $ringKeys;
    +    private $ringKeysCount;
    +    private $replicas;
    +    private $nodeHashCallback;
    +    private $nodes = array();
    +
    +    /**
    +     * @param int $replicas Number of replicas in the ring.
    +     * @param mixed $nodeHashCallback Callback returning the string used to calculate the hash of a node.
    +     */
    +    public function __construct($replicas = self::DEFAULT_REPLICAS, $nodeHashCallback = null)
    +    {
    +        $this->replicas = $replicas;
    +        $this->nodeHashCallback = $nodeHashCallback;
    +    }
    +
    +    /**
    +     * Adds a node to the ring with an optional weight.
    +     *
    +     * @param mixed $node Node object.
    +     * @param int $weight Weight for the node.
    +     */
    +    public function add($node, $weight = null)
    +    {
    +        // In case of collisions in the hashes of the nodes, the node added
    +        // last wins, thus the order in which nodes are added is significant.
    +        $this->nodes[] = array('object' => $node, 'weight' => (int) $weight ?: $this::DEFAULT_WEIGHT);
    +        $this->reset();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function remove($node)
    +    {
    +        // A node is removed by resetting the ring so that it's recreated from
    +        // scratch, in order to reassign possible hashes with collisions to the
    +        // right node according to the order in which they were added in the
    +        // first place.
    +        for ($i = 0; $i < count($this->nodes); ++$i) {
    +            if ($this->nodes[$i]['object'] === $node) {
    +                array_splice($this->nodes, $i, 1);
    +                $this->reset();
    +                break;
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Resets the distributor.
    +     */
    +    private function reset()
    +    {
    +        unset(
    +            $this->ring,
    +            $this->ringKeys,
    +            $this->ringKeysCount
    +        );
    +    }
    +
    +    /**
    +     * Returns the initialization status of the distributor.
    +     *
    +     * @return Boolean
    +     */
    +    private function isInitialized()
    +    {
    +        return isset($this->ringKeys);
    +    }
    +
    +    /**
    +     * Calculates the total weight of all the nodes in the distributor.
    +     *
    +     * @return int
    +     */
    +    private function computeTotalWeight()
    +    {
    +        $totalWeight = 0;
    +
    +        foreach ($this->nodes as $node) {
    +            $totalWeight += $node['weight'];
    +        }
    +
    +        return $totalWeight;
    +    }
    +
    +    /**
    +     * Initializes the distributor.
    +     */
    +    private function initialize()
    +    {
    +        if ($this->isInitialized()) {
    +            return;
    +        }
    +
    +        if (!$this->nodes) {
    +            throw new EmptyRingException('Cannot initialize empty hashring');
    +        }
    +
    +        $this->ring = array();
    +        $totalWeight = $this->computeTotalWeight();
    +        $nodesCount = count($this->nodes);
    +
    +        foreach ($this->nodes as $node) {
    +            $weightRatio = $node['weight'] / $totalWeight;
    +            $this->addNodeToRing($this->ring, $node, $nodesCount, $this->replicas, $weightRatio);
    +        }
    +
    +        ksort($this->ring, SORT_NUMERIC);
    +        $this->ringKeys = array_keys($this->ring);
    +        $this->ringKeysCount = count($this->ringKeys);
    +    }
    +
    +    /**
    +     * Implements the logic needed to add a node to the hashring.
    +     *
    +     * @param array $ring Source hashring.
    +     * @param mixed $node Node object to be added.
    +     * @param int $totalNodes Total number of nodes.
    +     * @param int $replicas Number of replicas in the ring.
    +     * @param float $weightRatio Weight ratio for the node.
    +     */
    +    protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio)
    +    {
    +        $nodeObject = $node['object'];
    +        $nodeHash = $this->getNodeHash($nodeObject);
    +        $replicas = (int) round($weightRatio * $totalNodes * $replicas);
    +
    +        for ($i = 0; $i < $replicas; $i++) {
    +            $key = crc32("$nodeHash:$i");
    +            $ring[$key] = $nodeObject;
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getNodeHash($nodeObject)
    +    {
    +        if ($this->nodeHashCallback === null) {
    +            return (string) $nodeObject;
    +        }
    +
    +        return call_user_func($this->nodeHashCallback, $nodeObject);
    +    }
    +
    +    /**
    +     * Calculates the hash for the specified value.
    +     *
    +     * @param string $value Input value.
    +     * @return int
    +     */
    +    public function hash($value)
    +    {
    +        return crc32($value);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function get($key)
    +    {
    +        return $this->ring[$this->getNodeKey($key)];
    +    }
    +
    +    /**
    +     * Calculates the corrisponding key of a node distributed in the hashring.
    +     *
    +     * @param int $key Computed hash of a key.
    +     * @return int
    +     */
    +    private function getNodeKey($key)
    +    {
    +        $this->initialize();
    +        $ringKeys = $this->ringKeys;
    +        $upper = $this->ringKeysCount - 1;
    +        $lower = 0;
    +
    +        while ($lower <= $upper) {
    +            $index = ($lower + $upper) >> 1;
    +            $item  = $ringKeys[$index];
    +
    +            if ($item > $key) {
    +                $upper = $index - 1;
    +            } else if ($item < $key) {
    +                $lower = $index + 1;
    +            } else {
    +                return $item;
    +            }
    +        }
    +
    +        return $ringKeys[$this->wrapAroundStrategy($upper, $lower, $this->ringKeysCount)];
    +    }
    +
    +    /**
    +     * Implements a strategy to deal with wrap-around errors during binary searches.
    +     *
    +     * @param int $upper
    +     * @param int $lower
    +     * @param int $ringKeysCount
    +     * @return int
    +     */
    +    protected function wrapAroundStrategy($upper, $lower, $ringKeysCount)
    +    {
    +        // Binary search for the last item in ringkeys with a value less or
    +        // equal to the key. If no such item exists, return the last item.
    +        return $upper >= 0 ? $upper : $ringKeysCount - 1;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getHashGenerator()
    +    {
    +        return $this;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Cluster/Distribution/KetamaPureRing.php b/vendor/predis/predis/lib/Predis/Cluster/Distribution/KetamaPureRing.php
    new file mode 100644
    index 0000000..d68fa95
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Cluster/Distribution/KetamaPureRing.php
    @@ -0,0 +1,70 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Cluster\Distribution;
    +
    +/**
    + * This class implements an hashring-based distributor that uses the same
    + * algorithm of libketama to distribute keys in a cluster using client-side
    + * sharding.
    + *
    + * @author Daniele Alessandri 
    + * @author Lorenzo Castelli 
    + */
    +class KetamaPureRing extends HashRing
    +{
    +    const DEFAULT_REPLICAS = 160;
    +
    +    /**
    +     * @param mixed $nodeHashCallback Callback returning the string used to calculate the hash of a node.
    +     */
    +    public function __construct($nodeHashCallback = null)
    +    {
    +        parent::__construct($this::DEFAULT_REPLICAS, $nodeHashCallback);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function addNodeToRing(&$ring, $node, $totalNodes, $replicas, $weightRatio)
    +    {
    +        $nodeObject = $node['object'];
    +        $nodeHash = $this->getNodeHash($nodeObject);
    +        $replicas = (int) floor($weightRatio * $totalNodes * ($replicas / 4));
    +
    +        for ($i = 0; $i < $replicas; $i++) {
    +            $unpackedDigest = unpack('V4', md5("$nodeHash-$i", true));
    +
    +            foreach ($unpackedDigest as $key) {
    +                $ring[$key] = $nodeObject;
    +            }
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function hash($value)
    +    {
    +        $hash = unpack('V', md5($value, true));
    +        return $hash[1];
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function wrapAroundStrategy($upper, $lower, $ringKeysCount)
    +    {
    +        // Binary search for the first item in _ringkeys with a value greater
    +        // or equal to the key. If no such item exists, return the first item.
    +        return $lower < $ringKeysCount ? $lower : 0;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Cluster/Hash/CRC16HashGenerator.php b/vendor/predis/predis/lib/Predis/Cluster/Hash/CRC16HashGenerator.php
    new file mode 100644
    index 0000000..f4c17f2
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Cluster/Hash/CRC16HashGenerator.php
    @@ -0,0 +1,72 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Cluster\Hash;
    +
    +/**
    + * This class implements the CRC-CCITT-16 algorithm used by redis-cluster.
    + *
    + * @author Daniele Alessandri 
    + */
    +class CRC16HashGenerator implements HashGeneratorInterface
    +{
    +    private static $CCITT_16 = array(
    +        0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
    +        0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
    +        0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
    +        0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
    +        0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
    +        0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
    +        0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
    +        0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
    +        0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
    +        0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
    +        0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
    +        0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
    +        0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
    +        0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
    +        0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
    +        0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
    +        0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
    +        0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
    +        0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
    +        0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
    +        0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
    +        0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
    +        0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
    +        0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
    +        0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
    +        0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
    +        0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
    +        0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
    +        0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
    +        0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
    +        0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
    +        0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0,
    +    );
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function hash($value)
    +    {
    +        // CRC-CCITT-16 algorithm
    +        $crc = 0;
    +        $CCITT_16 = self::$CCITT_16;
    +        $strlen = strlen($value);
    +
    +        for ($i = 0; $i < $strlen; $i++) {
    +            $crc = (($crc << 8) ^ $CCITT_16[($crc >> 8) ^ ord($value[$i])]) & 0xFFFF;
    +        }
    +
    +        return $crc;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Cluster/Hash/HashGeneratorInterface.php b/vendor/predis/predis/lib/Predis/Cluster/Hash/HashGeneratorInterface.php
    new file mode 100644
    index 0000000..891320d
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Cluster/Hash/HashGeneratorInterface.php
    @@ -0,0 +1,29 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Cluster\Hash;
    +
    +/**
    + * A generator of node keys implements the logic used to calculate the hash of
    + * a key to distribute the respective operations among nodes.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface HashGeneratorInterface
    +{
    +    /**
    +     * Generates an hash that is used by the distributor algorithm
    +     *
    +     * @param string $value Value used to generate the hash.
    +     * @return int
    +     */
    +    public function hash($value);
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Cluster/PredisClusterHashStrategy.php b/vendor/predis/predis/lib/Predis/Cluster/PredisClusterHashStrategy.php
    new file mode 100644
    index 0000000..9b9b303
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Cluster/PredisClusterHashStrategy.php
    @@ -0,0 +1,385 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Cluster;
    +
    +use Predis\Cluster\Hash\HashGeneratorInterface;
    +use Predis\Command\CommandInterface;
    +use Predis\Command\ScriptedCommand;
    +
    +/**
    + * Default class used by Predis for client-side sharding to calculate
    + * hashes out of keys of supported commands.
    + *
    + * @author Daniele Alessandri 
    + */
    +class PredisClusterHashStrategy implements CommandHashStrategyInterface
    +{
    +    private $commands;
    +    private $hashGenerator;
    +
    +    /**
    +     * @param HashGeneratorInterface $hashGenerator Hash generator instance.
    +     */
    +    public function __construct(HashGeneratorInterface $hashGenerator)
    +    {
    +        $this->commands = $this->getDefaultCommands();
    +        $this->hashGenerator = $hashGenerator;
    +    }
    +
    +    /**
    +     * Returns the default map of supported commands with their handlers.
    +     *
    +     * @return array
    +     */
    +    protected function getDefaultCommands()
    +    {
    +        $keyIsFirstArgument = array($this, 'getKeyFromFirstArgument');
    +        $keysAreAllArguments = array($this, 'getKeyFromAllArguments');
    +
    +        return array(
    +            /* commands operating on the key space */
    +            'EXISTS'                => $keyIsFirstArgument,
    +            'DEL'                   => $keysAreAllArguments,
    +            'TYPE'                  => $keyIsFirstArgument,
    +            'EXPIRE'                => $keyIsFirstArgument,
    +            'EXPIREAT'              => $keyIsFirstArgument,
    +            'PERSIST'               => $keyIsFirstArgument,
    +            'PEXPIRE'               => $keyIsFirstArgument,
    +            'PEXPIREAT'             => $keyIsFirstArgument,
    +            'TTL'                   => $keyIsFirstArgument,
    +            'PTTL'                  => $keyIsFirstArgument,
    +            'SORT'                  => $keyIsFirstArgument, // TODO
    +            'DUMP'                  => $keyIsFirstArgument,
    +            'RESTORE'               => $keyIsFirstArgument,
    +
    +            /* commands operating on string values */
    +            'APPEND'                => $keyIsFirstArgument,
    +            'DECR'                  => $keyIsFirstArgument,
    +            'DECRBY'                => $keyIsFirstArgument,
    +            'GET'                   => $keyIsFirstArgument,
    +            'GETBIT'                => $keyIsFirstArgument,
    +            'MGET'                  => $keysAreAllArguments,
    +            'SET'                   => $keyIsFirstArgument,
    +            'GETRANGE'              => $keyIsFirstArgument,
    +            'GETSET'                => $keyIsFirstArgument,
    +            'INCR'                  => $keyIsFirstArgument,
    +            'INCRBY'                => $keyIsFirstArgument,
    +            'SETBIT'                => $keyIsFirstArgument,
    +            'SETEX'                 => $keyIsFirstArgument,
    +            'MSET'                  => array($this, 'getKeyFromInterleavedArguments'),
    +            'MSETNX'                => array($this, 'getKeyFromInterleavedArguments'),
    +            'SETNX'                 => $keyIsFirstArgument,
    +            'SETRANGE'              => $keyIsFirstArgument,
    +            'STRLEN'                => $keyIsFirstArgument,
    +            'SUBSTR'                => $keyIsFirstArgument,
    +            'BITOP'                 => array($this, 'getKeyFromBitOp'),
    +            'BITCOUNT'              => $keyIsFirstArgument,
    +
    +            /* commands operating on lists */
    +            'LINSERT'               => $keyIsFirstArgument,
    +            'LINDEX'                => $keyIsFirstArgument,
    +            'LLEN'                  => $keyIsFirstArgument,
    +            'LPOP'                  => $keyIsFirstArgument,
    +            'RPOP'                  => $keyIsFirstArgument,
    +            'RPOPLPUSH'             => $keysAreAllArguments,
    +            'BLPOP'                 => array($this, 'getKeyFromBlockingListCommands'),
    +            'BRPOP'                 => array($this, 'getKeyFromBlockingListCommands'),
    +            'BRPOPLPUSH'            => array($this, 'getKeyFromBlockingListCommands'),
    +            'LPUSH'                 => $keyIsFirstArgument,
    +            'LPUSHX'                => $keyIsFirstArgument,
    +            'RPUSH'                 => $keyIsFirstArgument,
    +            'RPUSHX'                => $keyIsFirstArgument,
    +            'LRANGE'                => $keyIsFirstArgument,
    +            'LREM'                  => $keyIsFirstArgument,
    +            'LSET'                  => $keyIsFirstArgument,
    +            'LTRIM'                 => $keyIsFirstArgument,
    +
    +            /* commands operating on sets */
    +            'SADD'                  => $keyIsFirstArgument,
    +            'SCARD'                 => $keyIsFirstArgument,
    +            'SDIFF'                 => $keysAreAllArguments,
    +            'SDIFFSTORE'            => $keysAreAllArguments,
    +            'SINTER'                => $keysAreAllArguments,
    +            'SINTERSTORE'           => $keysAreAllArguments,
    +            'SUNION'                => $keysAreAllArguments,
    +            'SUNIONSTORE'           => $keysAreAllArguments,
    +            'SISMEMBER'             => $keyIsFirstArgument,
    +            'SMEMBERS'              => $keyIsFirstArgument,
    +            'SPOP'                  => $keyIsFirstArgument,
    +            'SRANDMEMBER'           => $keyIsFirstArgument,
    +            'SREM'                  => $keyIsFirstArgument,
    +
    +            /* commands operating on sorted sets */
    +            'ZADD'                  => $keyIsFirstArgument,
    +            'ZCARD'                 => $keyIsFirstArgument,
    +            'ZCOUNT'                => $keyIsFirstArgument,
    +            'ZINCRBY'               => $keyIsFirstArgument,
    +            'ZINTERSTORE'           => array($this, 'getKeyFromZsetAggregationCommands'),
    +            'ZRANGE'                => $keyIsFirstArgument,
    +            'ZRANGEBYSCORE'         => $keyIsFirstArgument,
    +            'ZRANK'                 => $keyIsFirstArgument,
    +            'ZREM'                  => $keyIsFirstArgument,
    +            'ZREMRANGEBYRANK'       => $keyIsFirstArgument,
    +            'ZREMRANGEBYSCORE'      => $keyIsFirstArgument,
    +            'ZREVRANGE'             => $keyIsFirstArgument,
    +            'ZREVRANGEBYSCORE'      => $keyIsFirstArgument,
    +            'ZREVRANK'              => $keyIsFirstArgument,
    +            'ZSCORE'                => $keyIsFirstArgument,
    +            'ZUNIONSTORE'           => array($this, 'getKeyFromZsetAggregationCommands'),
    +
    +            /* commands operating on hashes */
    +            'HDEL'                  => $keyIsFirstArgument,
    +            'HEXISTS'               => $keyIsFirstArgument,
    +            'HGET'                  => $keyIsFirstArgument,
    +            'HGETALL'               => $keyIsFirstArgument,
    +            'HMGET'                 => $keyIsFirstArgument,
    +            'HMSET'                 => $keyIsFirstArgument,
    +            'HINCRBY'               => $keyIsFirstArgument,
    +            'HINCRBYFLOAT'          => $keyIsFirstArgument,
    +            'HKEYS'                 => $keyIsFirstArgument,
    +            'HLEN'                  => $keyIsFirstArgument,
    +            'HSET'                  => $keyIsFirstArgument,
    +            'HSETNX'                => $keyIsFirstArgument,
    +            'HVALS'                 => $keyIsFirstArgument,
    +
    +            /* scripting */
    +            'EVAL'                  => array($this, 'getKeyFromScriptingCommands'),
    +            'EVALSHA'               => array($this, 'getKeyFromScriptingCommands'),
    +        );
    +    }
    +
    +    /**
    +     * Returns the list of IDs for the supported commands.
    +     *
    +     * @return array
    +     */
    +    public function getSupportedCommands()
    +    {
    +        return array_keys($this->commands);
    +    }
    +
    +    /**
    +     * Sets an handler for the specified command ID.
    +     *
    +     * The signature of the callback must have a single parameter
    +     * of type Predis\Command\CommandInterface.
    +     *
    +     * When the callback argument is omitted or NULL, the previously
    +     * associated handler for the specified command ID is removed.
    +     *
    +     * @param string $commandId The ID of the command to be handled.
    +     * @param mixed $callback A valid callable object or NULL.
    +     */
    +    public function setCommandHandler($commandId, $callback = null)
    +    {
    +        $commandId = strtoupper($commandId);
    +
    +        if (!isset($callback)) {
    +            unset($this->commands[$commandId]);
    +            return;
    +        }
    +
    +        if (!is_callable($callback)) {
    +            throw new \InvalidArgumentException("Callback must be a valid callable object or NULL");
    +        }
    +
    +        $this->commands[$commandId] = $callback;
    +    }
    +
    +    /**
    +     * Extracts the key from the first argument of a command instance.
    +     *
    +     * @param CommandInterface $command Command instance.
    +     * @return string
    +     */
    +    protected function getKeyFromFirstArgument(CommandInterface $command)
    +    {
    +        return $command->getArgument(0);
    +    }
    +
    +    /**
    +     * Extracts the key from a command with multiple keys only when all keys
    +     * in the arguments array produce the same hash.
    +     *
    +     * @param CommandInterface $command Command instance.
    +     * @return string
    +     */
    +    protected function getKeyFromAllArguments(CommandInterface $command)
    +    {
    +        $arguments = $command->getArguments();
    +
    +        if ($this->checkSameHashForKeys($arguments)) {
    +            return $arguments[0];
    +        }
    +    }
    +
    +    /**
    +     * Extracts the key from a command with multiple keys only when all keys
    +     * in the arguments array produce the same hash.
    +     *
    +     * @param CommandInterface $command Command instance.
    +     * @return string
    +     */
    +    protected function getKeyFromInterleavedArguments(CommandInterface $command)
    +    {
    +        $arguments = $command->getArguments();
    +        $keys = array();
    +
    +        for ($i = 0; $i < count($arguments); $i += 2) {
    +            $keys[] = $arguments[$i];
    +        }
    +
    +        if ($this->checkSameHashForKeys($keys)) {
    +            return $arguments[0];
    +        }
    +    }
    +
    +    /**
    +     * Extracts the key from BLPOP and BRPOP commands.
    +     *
    +     * @param CommandInterface $command Command instance.
    +     * @return string
    +     */
    +    protected function getKeyFromBlockingListCommands(CommandInterface $command)
    +    {
    +        $arguments = $command->getArguments();
    +
    +        if ($this->checkSameHashForKeys(array_slice($arguments, 0, count($arguments) - 1))) {
    +            return $arguments[0];
    +        }
    +    }
    +
    +    /**
    +     * Extracts the key from BITOP command.
    +     *
    +     * @param CommandInterface $command Command instance.
    +     * @return string
    +     */
    +    protected function getKeyFromBitOp(CommandInterface $command)
    +    {
    +        $arguments = $command->getArguments();
    +
    +        if ($this->checkSameHashForKeys(array_slice($arguments, 1, count($arguments)))) {
    +            return $arguments[1];
    +        }
    +    }
    +
    +    /**
    +     * Extracts the key from ZINTERSTORE and ZUNIONSTORE commands.
    +     *
    +     * @param CommandInterface $command Command instance.
    +     * @return string
    +     */
    +    protected function getKeyFromZsetAggregationCommands(CommandInterface $command)
    +    {
    +        $arguments = $command->getArguments();
    +        $keys = array_merge(array($arguments[0]), array_slice($arguments, 2, $arguments[1]));
    +
    +        if ($this->checkSameHashForKeys($keys)) {
    +            return $arguments[0];
    +        }
    +    }
    +
    +    /**
    +     * Extracts the key from EVAL and EVALSHA commands.
    +     *
    +     * @param CommandInterface $command Command instance.
    +     * @return string
    +     */
    +    protected function getKeyFromScriptingCommands(CommandInterface $command)
    +    {
    +        if ($command instanceof ScriptedCommand) {
    +            $keys = $command->getKeys();
    +        } else {
    +            $keys = array_slice($args = $command->getArguments(), 2, $args[1]);
    +        }
    +
    +        if ($keys && $this->checkSameHashForKeys($keys)) {
    +            return $keys[0];
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getHash(CommandInterface $command)
    +    {
    +        $hash = $command->getHash();
    +
    +        if (!isset($hash) && isset($this->commands[$cmdID = $command->getId()])) {
    +            $key = call_user_func($this->commands[$cmdID], $command);
    +
    +            if (isset($key)) {
    +                $hash = $this->getKeyHash($key);
    +                $command->setHash($hash);
    +            }
    +        }
    +
    +        return $hash;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getKeyHash($key)
    +    {
    +        $key = $this->extractKeyTag($key);
    +        $hash = $this->hashGenerator->hash($key);
    +
    +        return $hash;
    +    }
    +
    +    /**
    +     * Checks if the specified array of keys will generate the same hash.
    +     *
    +     * @param array $keys Array of keys.
    +     * @return Boolean
    +     */
    +    protected function checkSameHashForKeys(Array $keys)
    +    {
    +        if (!$count = count($keys)) {
    +            return false;
    +        }
    +
    +        $currentKey = $this->extractKeyTag($keys[0]);
    +
    +        for ($i = 1; $i < $count; $i++) {
    +            $nextKey = $this->extractKeyTag($keys[$i]);
    +
    +            if ($currentKey !== $nextKey) {
    +                return false;
    +            }
    +
    +            $currentKey = $nextKey;
    +        }
    +
    +        return true;
    +    }
    +
    +    /**
    +     * Returns only the hashable part of a key (delimited by "{...}"), or the
    +     * whole key if a key tag is not found in the string.
    +     *
    +     * @param string $key A key.
    +     * @return string
    +     */
    +    protected function extractKeyTag($key)
    +    {
    +        if (false !== $start = strpos($key, '{')) {
    +            if (false !== $end = strpos($key, '}', $start)) {
    +                $key = substr($key, ++$start, $end - $start);
    +            }
    +        }
    +
    +        return $key;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Cluster/RedisClusterHashStrategy.php b/vendor/predis/predis/lib/Predis/Cluster/RedisClusterHashStrategy.php
    new file mode 100644
    index 0000000..bec2bf5
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Cluster/RedisClusterHashStrategy.php
    @@ -0,0 +1,288 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Cluster;
    +
    +use Predis\Cluster\Hash\CRC16HashGenerator;
    +use Predis\Command\CommandInterface;
    +use Predis\Command\ScriptedCommand;
    +
    +/**
    + * Default class used by Predis to calculate hashes out of keys of
    + * commands supported by redis-cluster.
    + *
    + * @author Daniele Alessandri 
    + */
    +class RedisClusterHashStrategy implements CommandHashStrategyInterface
    +{
    +    private $commands;
    +    private $hashGenerator;
    +
    +    /**
    +     *
    +     */
    +    public function __construct()
    +    {
    +        $this->commands = $this->getDefaultCommands();
    +        $this->hashGenerator = new CRC16HashGenerator();
    +    }
    +
    +    /**
    +     * Returns the default map of supported commands with their handlers.
    +     *
    +     * @return array
    +     */
    +    protected function getDefaultCommands()
    +    {
    +        $keyIsFirstArgument = array($this, 'getKeyFromFirstArgument');
    +
    +        return array(
    +            /* commands operating on the key space */
    +            'EXISTS'                => $keyIsFirstArgument,
    +            'DEL'                   => array($this, 'getKeyFromAllArguments'),
    +            'TYPE'                  => $keyIsFirstArgument,
    +            'EXPIRE'                => $keyIsFirstArgument,
    +            'EXPIREAT'              => $keyIsFirstArgument,
    +            'PERSIST'               => $keyIsFirstArgument,
    +            'PEXPIRE'               => $keyIsFirstArgument,
    +            'PEXPIREAT'             => $keyIsFirstArgument,
    +            'TTL'                   => $keyIsFirstArgument,
    +            'PTTL'                  => $keyIsFirstArgument,
    +            'SORT'                  => $keyIsFirstArgument, // TODO
    +
    +            /* commands operating on string values */
    +            'APPEND'                => $keyIsFirstArgument,
    +            'DECR'                  => $keyIsFirstArgument,
    +            'DECRBY'                => $keyIsFirstArgument,
    +            'GET'                   => $keyIsFirstArgument,
    +            'GETBIT'                => $keyIsFirstArgument,
    +            'MGET'                  => array($this, 'getKeyFromAllArguments'),
    +            'SET'                   => $keyIsFirstArgument,
    +            'GETRANGE'              => $keyIsFirstArgument,
    +            'GETSET'                => $keyIsFirstArgument,
    +            'INCR'                  => $keyIsFirstArgument,
    +            'INCRBY'                => $keyIsFirstArgument,
    +            'SETBIT'                => $keyIsFirstArgument,
    +            'SETEX'                 => $keyIsFirstArgument,
    +            'MSET'                  => array($this, 'getKeyFromInterleavedArguments'),
    +            'MSETNX'                => array($this, 'getKeyFromInterleavedArguments'),
    +            'SETNX'                 => $keyIsFirstArgument,
    +            'SETRANGE'              => $keyIsFirstArgument,
    +            'STRLEN'                => $keyIsFirstArgument,
    +            'SUBSTR'                => $keyIsFirstArgument,
    +            'BITCOUNT'              => $keyIsFirstArgument,
    +
    +            /* commands operating on lists */
    +            'LINSERT'               => $keyIsFirstArgument,
    +            'LINDEX'                => $keyIsFirstArgument,
    +            'LLEN'                  => $keyIsFirstArgument,
    +            'LPOP'                  => $keyIsFirstArgument,
    +            'RPOP'                  => $keyIsFirstArgument,
    +            'BLPOP'                 => array($this, 'getKeyFromBlockingListCommands'),
    +            'BRPOP'                 => array($this, 'getKeyFromBlockingListCommands'),
    +            'LPUSH'                 => $keyIsFirstArgument,
    +            'LPUSHX'                => $keyIsFirstArgument,
    +            'RPUSH'                 => $keyIsFirstArgument,
    +            'RPUSHX'                => $keyIsFirstArgument,
    +            'LRANGE'                => $keyIsFirstArgument,
    +            'LREM'                  => $keyIsFirstArgument,
    +            'LSET'                  => $keyIsFirstArgument,
    +            'LTRIM'                 => $keyIsFirstArgument,
    +
    +            /* commands operating on sets */
    +            'SADD'                  => $keyIsFirstArgument,
    +            'SCARD'                 => $keyIsFirstArgument,
    +            'SISMEMBER'             => $keyIsFirstArgument,
    +            'SMEMBERS'              => $keyIsFirstArgument,
    +            'SPOP'                  => $keyIsFirstArgument,
    +            'SRANDMEMBER'           => $keyIsFirstArgument,
    +            'SREM'                  => $keyIsFirstArgument,
    +
    +            /* commands operating on sorted sets */
    +            'ZADD'                  => $keyIsFirstArgument,
    +            'ZCARD'                 => $keyIsFirstArgument,
    +            'ZCOUNT'                => $keyIsFirstArgument,
    +            'ZINCRBY'               => $keyIsFirstArgument,
    +            'ZRANGE'                => $keyIsFirstArgument,
    +            'ZRANGEBYSCORE'         => $keyIsFirstArgument,
    +            'ZRANK'                 => $keyIsFirstArgument,
    +            'ZREM'                  => $keyIsFirstArgument,
    +            'ZREMRANGEBYRANK'       => $keyIsFirstArgument,
    +            'ZREMRANGEBYSCORE'      => $keyIsFirstArgument,
    +            'ZREVRANGE'             => $keyIsFirstArgument,
    +            'ZREVRANGEBYSCORE'      => $keyIsFirstArgument,
    +            'ZREVRANK'              => $keyIsFirstArgument,
    +            'ZSCORE'                => $keyIsFirstArgument,
    +
    +            /* commands operating on hashes */
    +            'HDEL'                  => $keyIsFirstArgument,
    +            'HEXISTS'               => $keyIsFirstArgument,
    +            'HGET'                  => $keyIsFirstArgument,
    +            'HGETALL'               => $keyIsFirstArgument,
    +            'HMGET'                 => $keyIsFirstArgument,
    +            'HMSET'                 => $keyIsFirstArgument,
    +            'HINCRBY'               => $keyIsFirstArgument,
    +            'HINCRBYFLOAT'          => $keyIsFirstArgument,
    +            'HKEYS'                 => $keyIsFirstArgument,
    +            'HLEN'                  => $keyIsFirstArgument,
    +            'HSET'                  => $keyIsFirstArgument,
    +            'HSETNX'                => $keyIsFirstArgument,
    +            'HVALS'                 => $keyIsFirstArgument,
    +
    +            /* scripting */
    +            'EVAL'                  => array($this, 'getKeyFromScriptingCommands'),
    +            'EVALSHA'               => array($this, 'getKeyFromScriptingCommands'),
    +        );
    +    }
    +
    +    /**
    +     * Returns the list of IDs for the supported commands.
    +     *
    +     * @return array
    +     */
    +    public function getSupportedCommands()
    +    {
    +        return array_keys($this->commands);
    +    }
    +
    +    /**
    +     * Sets an handler for the specified command ID.
    +     *
    +     * The signature of the callback must have a single parameter
    +     * of type Predis\Command\CommandInterface.
    +     *
    +     * When the callback argument is omitted or NULL, the previously
    +     * associated handler for the specified command ID is removed.
    +     *
    +     * @param string $commandId The ID of the command to be handled.
    +     * @param mixed $callback A valid callable object or NULL.
    +     */
    +    public function setCommandHandler($commandId, $callback = null)
    +    {
    +        $commandId = strtoupper($commandId);
    +
    +        if (!isset($callback)) {
    +            unset($this->commands[$commandId]);
    +            return;
    +        }
    +
    +        if (!is_callable($callback)) {
    +            throw new \InvalidArgumentException("Callback must be a valid callable object or NULL");
    +        }
    +
    +        $this->commands[$commandId] = $callback;
    +    }
    +
    +    /**
    +     * Extracts the key from the first argument of a command instance.
    +     *
    +     * @param CommandInterface $command Command instance.
    +     * @return string
    +     */
    +    protected function getKeyFromFirstArgument(CommandInterface $command)
    +    {
    +        return $command->getArgument(0);
    +    }
    +
    +    /**
    +     * Extracts the key from a command that can accept multiple keys ensuring
    +     * that only one key is actually specified to comply with redis-cluster.
    +     *
    +     * @param CommandInterface $command Command instance.
    +     * @return string
    +     */
    +    protected function getKeyFromAllArguments(CommandInterface $command)
    +    {
    +        $arguments = $command->getArguments();
    +
    +        if (count($arguments) === 1) {
    +            return $arguments[0];
    +        }
    +    }
    +
    +    /**
    +     * Extracts the key from a command that can accept multiple keys ensuring
    +     * that only one key is actually specified to comply with redis-cluster.
    +     *
    +     * @param CommandInterface $command Command instance.
    +     * @return string
    +     */
    +    protected function getKeyFromInterleavedArguments(CommandInterface $command)
    +    {
    +        $arguments = $command->getArguments();
    +
    +        if (count($arguments) === 2) {
    +            return $arguments[0];
    +        }
    +    }
    +
    +    /**
    +     * Extracts the key from BLPOP and BRPOP commands ensuring that only one key
    +     * is actually specified to comply with redis-cluster.
    +     *
    +     * @param CommandInterface $command Command instance.
    +     * @return string
    +     */
    +    protected function getKeyFromBlockingListCommands(CommandInterface $command)
    +    {
    +        $arguments = $command->getArguments();
    +
    +        if (count($arguments) === 2) {
    +            return $arguments[0];
    +        }
    +    }
    +
    +    /**
    +     * Extracts the key from EVAL and EVALSHA commands.
    +     *
    +     * @param CommandInterface $command Command instance.
    +     * @return string
    +     */
    +    protected function getKeyFromScriptingCommands(CommandInterface $command)
    +    {
    +        if ($command instanceof ScriptedCommand) {
    +            $keys = $command->getKeys();
    +        } else {
    +            $keys = array_slice($args = $command->getArguments(), 2, $args[1]);
    +        }
    +
    +        if (count($keys) === 1) {
    +            return $keys[0];
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getHash(CommandInterface $command)
    +    {
    +        $hash = $command->getHash();
    +
    +        if (!isset($hash) && isset($this->commands[$cmdID = $command->getId()])) {
    +            $key = call_user_func($this->commands[$cmdID], $command);
    +
    +            if (isset($key)) {
    +                $hash = $this->hashGenerator->hash($key);
    +                $command->setHash($hash);
    +            }
    +        }
    +
    +        return $hash;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getKeyHash($key)
    +    {
    +        return $this->hashGenerator->hash($key);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/AbstractCommand.php b/vendor/predis/predis/lib/Predis/Command/AbstractCommand.php
    new file mode 100644
    index 0000000..0584a9b
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/AbstractCommand.php
    @@ -0,0 +1,162 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * Base class for Redis commands.
    + *
    + * @author Daniele Alessandri 
    + */
    +abstract class AbstractCommand implements CommandInterface
    +{
    +    private $hash;
    +    private $arguments = array();
    +
    +    /**
    +     * Returns a filtered array of the arguments.
    +     *
    +     * @param array $arguments List of arguments.
    +     * @return array
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        return $arguments;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function setArguments(Array $arguments)
    +    {
    +        $this->arguments = $this->filterArguments($arguments);
    +        unset($this->hash);
    +    }
    +
    +    /**
    +     * Sets the arguments array without filtering.
    +     *
    +     * @param array $arguments List of arguments.
    +     */
    +    public function setRawArguments(Array $arguments)
    +    {
    +        $this->arguments = $arguments;
    +        unset($this->hash);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getArguments()
    +    {
    +        return $this->arguments;
    +    }
    +
    +    /**
    +     * Gets the argument from the arguments list at the specified index.
    +     *
    +     * @param array $arguments Position of the argument.
    +     */
    +    public function getArgument($index)
    +    {
    +        if (isset($this->arguments[$index])) {
    +            return $this->arguments[$index];
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function setHash($hash)
    +    {
    +        $this->hash = $hash;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getHash()
    +    {
    +        if (isset($this->hash)) {
    +            return $this->hash;
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return $data;
    +    }
    +
    +    /**
    +     * Helper function used to reduce a list of arguments to a string.
    +     *
    +     * @param string $accumulator Temporary string.
    +     * @param string $argument Current argument.
    +     * @return string
    +     */
    +    protected function toStringArgumentReducer($accumulator, $argument)
    +    {
    +        if (strlen($argument) > 32) {
    +            $argument = substr($argument, 0, 32) . '[...]';
    +        }
    +
    +        $accumulator .= " $argument";
    +
    +        return $accumulator;
    +    }
    +
    +    /**
    +     * Returns a partial string representation of the command with its arguments.
    +     *
    +     * @return string
    +     */
    +    public function __toString()
    +    {
    +        return array_reduce(
    +            $this->getArguments(),
    +            array($this, 'toStringArgumentReducer'),
    +            $this->getId()
    +        );
    +    }
    +
    +    /**
    +     * Normalizes the arguments array passed to a Redis command.
    +     *
    +     * @param array $arguments Arguments for a command.
    +     * @return array
    +     */
    +    public static function normalizeArguments(Array $arguments)
    +    {
    +        if (count($arguments) === 1 && is_array($arguments[0])) {
    +            return $arguments[0];
    +        }
    +
    +        return $arguments;
    +    }
    +
    +    /**
    +     * Normalizes the arguments array passed to a variadic Redis command.
    +     *
    +     * @param array $arguments Arguments for a command.
    +     * @return array
    +     */
    +    public static function normalizeVariadic(Array $arguments)
    +    {
    +        if (count($arguments) === 2 && is_array($arguments[1])) {
    +            return array_merge(array($arguments[0]), $arguments[1]);
    +        }
    +
    +        return $arguments;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/CommandInterface.php b/vendor/predis/predis/lib/Predis/Command/CommandInterface.php
    new file mode 100644
    index 0000000..0be8c81
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/CommandInterface.php
    @@ -0,0 +1,76 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * Defines an abstraction representing a Redis command.
    + * @author Daniele Alessandri 
    + */
    +interface CommandInterface
    +{
    +    /**
    +     * Gets the ID of a Redis command.
    +     *
    +     * @return string
    +     */
    +    public function getId();
    +
    +    /**
    +     * Set the hash for the command.
    +     *
    +     * @param int $hash Calculated hash.
    +     */
    +    public function setHash($hash);
    +
    +    /**
    +     * Returns the hash of the command.
    +     *
    +     * @return int
    +     */
    +    public function getHash();
    +
    +    /**
    +     * Sets the arguments for the command.
    +     *
    +     * @param array $arguments List of arguments.
    +     */
    +    public function setArguments(Array $arguments);
    +
    +    /**
    +     * Sets the raw arguments for the command without processing them.
    +     *
    +     * @param array $arguments List of arguments.
    +     */
    +    public function setRawArguments(Array $arguments);
    +
    +    /**
    +     * Gets the arguments of the command.
    +     *
    +     * @return array
    +     */
    +    public function getArguments();
    +
    +    /**
    +     * Gets the argument of the command at the specified index.
    +     *
    +     * @return array
    +     */
    +    public function getArgument($index);
    +
    +    /**
    +     * Parses a reply buffer and returns a PHP object.
    +     *
    +     * @param string $data Binary string containing the whole reply.
    +     * @return mixed
    +     */
    +    public function parseResponse($data);
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ConnectionAuth.php b/vendor/predis/predis/lib/Predis/Command/ConnectionAuth.php
    new file mode 100644
    index 0000000..de0d3ee
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ConnectionAuth.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/auth
    + * @author Daniele Alessandri 
    + */
    +class ConnectionAuth extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'AUTH';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ConnectionEcho.php b/vendor/predis/predis/lib/Predis/Command/ConnectionEcho.php
    new file mode 100644
    index 0000000..dd9d072
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ConnectionEcho.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/echo
    + * @author Daniele Alessandri 
    + */
    +class ConnectionEcho extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'ECHO';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ConnectionPing.php b/vendor/predis/predis/lib/Predis/Command/ConnectionPing.php
    new file mode 100644
    index 0000000..cf29416
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ConnectionPing.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/ping
    + * @author Daniele Alessandri 
    + */
    +class ConnectionPing extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'PING';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return $data === 'PONG' ? true : false;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ConnectionQuit.php b/vendor/predis/predis/lib/Predis/Command/ConnectionQuit.php
    new file mode 100644
    index 0000000..641cc35
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ConnectionQuit.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/quit
    + * @author Daniele Alessandri 
    + */
    +class ConnectionQuit extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'QUIT';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ConnectionSelect.php b/vendor/predis/predis/lib/Predis/Command/ConnectionSelect.php
    new file mode 100644
    index 0000000..51b96b7
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ConnectionSelect.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/select
    + * @author Daniele Alessandri 
    + */
    +class ConnectionSelect extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SELECT';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/HashDelete.php b/vendor/predis/predis/lib/Predis/Command/HashDelete.php
    new file mode 100644
    index 0000000..9584678
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/HashDelete.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/hdel
    + * @author Daniele Alessandri 
    + */
    +class HashDelete extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'HDEL';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        return self::normalizeVariadic($arguments);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/HashExists.php b/vendor/predis/predis/lib/Predis/Command/HashExists.php
    new file mode 100644
    index 0000000..a9d620a
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/HashExists.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/hexists
    + * @author Daniele Alessandri 
    + */
    +class HashExists extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'HEXISTS';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return (bool) $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/HashGet.php b/vendor/predis/predis/lib/Predis/Command/HashGet.php
    new file mode 100644
    index 0000000..b18583a
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/HashGet.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/hget
    + * @author Daniele Alessandri 
    + */
    +class HashGet extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'HGET';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/HashGetAll.php b/vendor/predis/predis/lib/Predis/Command/HashGetAll.php
    new file mode 100644
    index 0000000..51a745f
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/HashGetAll.php
    @@ -0,0 +1,41 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/hgetall
    + * @author Daniele Alessandri 
    + */
    +class HashGetAll extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'HGETALL';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        $result = array();
    +
    +        for ($i = 0; $i < count($data); $i++) {
    +            $result[$data[$i]] = $data[++$i];
    +        }
    +
    +        return $result;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/HashGetMultiple.php b/vendor/predis/predis/lib/Predis/Command/HashGetMultiple.php
    new file mode 100644
    index 0000000..d533489
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/HashGetMultiple.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/hmget
    + * @author Daniele Alessandri 
    + */
    +class HashGetMultiple extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'HMGET';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        return self::normalizeVariadic($arguments);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/HashIncrementBy.php b/vendor/predis/predis/lib/Predis/Command/HashIncrementBy.php
    new file mode 100644
    index 0000000..1c43e63
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/HashIncrementBy.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/hincrby
    + * @author Daniele Alessandri 
    + */
    +class HashIncrementBy extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'HINCRBY';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/HashIncrementByFloat.php b/vendor/predis/predis/lib/Predis/Command/HashIncrementByFloat.php
    new file mode 100644
    index 0000000..7597058
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/HashIncrementByFloat.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/hincrbyfloat
    + * @author Daniele Alessandri 
    + */
    +class HashIncrementByFloat extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'HINCRBYFLOAT';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/HashKeys.php b/vendor/predis/predis/lib/Predis/Command/HashKeys.php
    new file mode 100644
    index 0000000..279f09a
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/HashKeys.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/hkeys
    + * @author Daniele Alessandri 
    + */
    +class HashKeys extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'HKEYS';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/HashLength.php b/vendor/predis/predis/lib/Predis/Command/HashLength.php
    new file mode 100644
    index 0000000..b2a59c0
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/HashLength.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/hlen
    + * @author Daniele Alessandri 
    + */
    +class HashLength extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'HLEN';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/HashSet.php b/vendor/predis/predis/lib/Predis/Command/HashSet.php
    new file mode 100644
    index 0000000..588afc4
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/HashSet.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/hset
    + * @author Daniele Alessandri 
    + */
    +class HashSet extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'HSET';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return (bool) $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/HashSetMultiple.php b/vendor/predis/predis/lib/Predis/Command/HashSetMultiple.php
    new file mode 100644
    index 0000000..f84485f
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/HashSetMultiple.php
    @@ -0,0 +1,47 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/hmset
    + * @author Daniele Alessandri 
    + */
    +class HashSetMultiple extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'HMSET';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        if (count($arguments) === 2 && is_array($arguments[1])) {
    +            $flattenedKVs = array($arguments[0]);
    +            $args = $arguments[1];
    +
    +            foreach ($args as $k => $v) {
    +                $flattenedKVs[] = $k;
    +                $flattenedKVs[] = $v;
    +            }
    +
    +            return $flattenedKVs;
    +        }
    +
    +        return $arguments;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/HashSetPreserve.php b/vendor/predis/predis/lib/Predis/Command/HashSetPreserve.php
    new file mode 100644
    index 0000000..3e9890a
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/HashSetPreserve.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/hsetnx
    + * @author Daniele Alessandri 
    + */
    +class HashSetPreserve extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'HSETNX';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return (bool) $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/HashValues.php b/vendor/predis/predis/lib/Predis/Command/HashValues.php
    new file mode 100644
    index 0000000..fec3116
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/HashValues.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/hvals
    + * @author Daniele Alessandri 
    + */
    +class HashValues extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'HVALS';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeyDelete.php b/vendor/predis/predis/lib/Predis/Command/KeyDelete.php
    new file mode 100644
    index 0000000..4e43141
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeyDelete.php
    @@ -0,0 +1,43 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/del
    + * @author Daniele Alessandri 
    + */
    +class KeyDelete extends AbstractCommand implements PrefixableCommandInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'DEL';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        return self::normalizeArguments($arguments);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function prefixKeys($prefix)
    +    {
    +        PrefixHelpers::all($this, $prefix);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeyDump.php b/vendor/predis/predis/lib/Predis/Command/KeyDump.php
    new file mode 100644
    index 0000000..55cb90b
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeyDump.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/dump
    + * @author Daniele Alessandri 
    + */
    +class KeyDump extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'DUMP';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeyExists.php b/vendor/predis/predis/lib/Predis/Command/KeyExists.php
    new file mode 100644
    index 0000000..7f877cd
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeyExists.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/exists
    + * @author Daniele Alessandri 
    + */
    +class KeyExists extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'EXISTS';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return (bool) $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeyExpire.php b/vendor/predis/predis/lib/Predis/Command/KeyExpire.php
    new file mode 100644
    index 0000000..9db8e02
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeyExpire.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/expire
    + * @author Daniele Alessandri 
    + */
    +class KeyExpire extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'EXPIRE';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return (bool) $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeyExpireAt.php b/vendor/predis/predis/lib/Predis/Command/KeyExpireAt.php
    new file mode 100644
    index 0000000..1f6db38
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeyExpireAt.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/expireat
    + * @author Daniele Alessandri 
    + */
    +class KeyExpireAt extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'EXPIREAT';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return (bool) $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeyKeys.php b/vendor/predis/predis/lib/Predis/Command/KeyKeys.php
    new file mode 100644
    index 0000000..3b973ed
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeyKeys.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/keys
    + * @author Daniele Alessandri 
    + */
    +class KeyKeys extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'KEYS';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeyKeysV12x.php b/vendor/predis/predis/lib/Predis/Command/KeyKeysV12x.php
    new file mode 100644
    index 0000000..2e0d3c9
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeyKeysV12x.php
    @@ -0,0 +1,28 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/keys
    + * @author Daniele Alessandri 
    + * @deprecated
    + */
    +class KeyKeysV12x extends KeyKeys
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return explode(' ', $data);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeyMove.php b/vendor/predis/predis/lib/Predis/Command/KeyMove.php
    new file mode 100644
    index 0000000..9e2a64d
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeyMove.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/move
    + * @author Daniele Alessandri 
    + */
    +class KeyMove extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'MOVE';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return (bool) $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeyPersist.php b/vendor/predis/predis/lib/Predis/Command/KeyPersist.php
    new file mode 100644
    index 0000000..5c6b23c
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeyPersist.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/persist
    + * @author Daniele Alessandri 
    + */
    +class KeyPersist extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'PERSIST';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return (bool) $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeyPreciseExpire.php b/vendor/predis/predis/lib/Predis/Command/KeyPreciseExpire.php
    new file mode 100644
    index 0000000..3fb8678
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeyPreciseExpire.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/pexpire
    + * @author Daniele Alessandri 
    + */
    +class KeyPreciseExpire extends KeyExpire
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'PEXPIRE';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeyPreciseExpireAt.php b/vendor/predis/predis/lib/Predis/Command/KeyPreciseExpireAt.php
    new file mode 100644
    index 0000000..14697f2
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeyPreciseExpireAt.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/pexpireat
    + * @author Daniele Alessandri 
    + */
    +class KeyPreciseExpireAt extends KeyExpireAt
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'PEXPIREAT';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeyPreciseTimeToLive.php b/vendor/predis/predis/lib/Predis/Command/KeyPreciseTimeToLive.php
    new file mode 100644
    index 0000000..92e3a02
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeyPreciseTimeToLive.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/pttl
    + * @author Daniele Alessandri 
    + */
    +class KeyPreciseTimeToLive extends KeyTimeToLive
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'PTTL';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeyRandom.php b/vendor/predis/predis/lib/Predis/Command/KeyRandom.php
    new file mode 100644
    index 0000000..520a368
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeyRandom.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/randomkey
    + * @author Daniele Alessandri 
    + */
    +class KeyRandom extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'RANDOMKEY';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return $data !== '' ? $data : null;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeyRename.php b/vendor/predis/predis/lib/Predis/Command/KeyRename.php
    new file mode 100644
    index 0000000..49af1ff
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeyRename.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/rename
    + * @author Daniele Alessandri 
    + */
    +class KeyRename extends AbstractCommand implements PrefixableCommandInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'RENAME';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function prefixKeys($prefix)
    +    {
    +        PrefixHelpers::all($this, $prefix);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeyRenamePreserve.php b/vendor/predis/predis/lib/Predis/Command/KeyRenamePreserve.php
    new file mode 100644
    index 0000000..1db8b7d
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeyRenamePreserve.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/renamenx
    + * @author Daniele Alessandri 
    + */
    +class KeyRenamePreserve extends KeyRename
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'RENAMENX';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return (bool) $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeyRestore.php b/vendor/predis/predis/lib/Predis/Command/KeyRestore.php
    new file mode 100644
    index 0000000..623cef6
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeyRestore.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/restore
    + * @author Daniele Alessandri 
    + */
    +class KeyRestore extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'RESTORE';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeySort.php b/vendor/predis/predis/lib/Predis/Command/KeySort.php
    new file mode 100644
    index 0000000..549469a
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeySort.php
    @@ -0,0 +1,117 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/sort
    + * @author Daniele Alessandri 
    + */
    +class KeySort extends AbstractCommand implements PrefixableCommandInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SORT';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        if (count($arguments) === 1) {
    +            return $arguments;
    +        }
    +
    +        $query = array($arguments[0]);
    +        $sortParams = array_change_key_case($arguments[1], CASE_UPPER);
    +
    +        if (isset($sortParams['BY'])) {
    +            $query[] = 'BY';
    +            $query[] = $sortParams['BY'];
    +        }
    +
    +        if (isset($sortParams['GET'])) {
    +            $getargs = $sortParams['GET'];
    +
    +            if (is_array($getargs)) {
    +                foreach ($getargs as $getarg) {
    +                    $query[] = 'GET';
    +                    $query[] = $getarg;
    +                }
    +            } else {
    +                $query[] = 'GET';
    +                $query[] = $getargs;
    +            }
    +        }
    +
    +        if (isset($sortParams['LIMIT']) &&
    +            is_array($sortParams['LIMIT']) &&
    +            count($sortParams['LIMIT']) == 2) {
    +
    +            $query[] = 'LIMIT';
    +            $query[] = $sortParams['LIMIT'][0];
    +            $query[] = $sortParams['LIMIT'][1];
    +        }
    +
    +        if (isset($sortParams['SORT'])) {
    +            $query[] = strtoupper($sortParams['SORT']);
    +        }
    +
    +        if (isset($sortParams['ALPHA']) && $sortParams['ALPHA'] == true) {
    +            $query[] = 'ALPHA';
    +        }
    +
    +        if (isset($sortParams['STORE'])) {
    +            $query[] = 'STORE';
    +            $query[] = $sortParams['STORE'];
    +        }
    +
    +        return $query;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function prefixKeys($prefix)
    +    {
    +        if ($arguments = $this->getArguments()) {
    +            $arguments[0] = "$prefix{$arguments[0]}";
    +
    +            if (($count = count($arguments)) > 1) {
    +                for ($i = 1; $i < $count; $i++) {
    +                    switch ($arguments[$i]) {
    +                        case 'BY':
    +                        case 'STORE':
    +                            $arguments[$i] = "$prefix{$arguments[++$i]}";
    +                            break;
    +
    +                        case 'GET':
    +                            $value = $arguments[++$i];
    +                            if ($value !== '#') {
    +                                $arguments[$i] = "$prefix$value";
    +                            }
    +                            break;
    +
    +                        case 'LIMIT';
    +                            $i += 2;
    +                            break;
    +                    }
    +                }
    +            }
    +
    +            $this->setRawArguments($arguments);
    +        }
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeyTimeToLive.php b/vendor/predis/predis/lib/Predis/Command/KeyTimeToLive.php
    new file mode 100644
    index 0000000..103885a
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeyTimeToLive.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/ttl
    + * @author Daniele Alessandri 
    + */
    +class KeyTimeToLive extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'TTL';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/KeyType.php b/vendor/predis/predis/lib/Predis/Command/KeyType.php
    new file mode 100644
    index 0000000..b2baf9b
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/KeyType.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/type
    + * @author Daniele Alessandri 
    + */
    +class KeyType extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'TYPE';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ListIndex.php b/vendor/predis/predis/lib/Predis/Command/ListIndex.php
    new file mode 100644
    index 0000000..9762ebf
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ListIndex.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/lindex
    + * @author Daniele Alessandri 
    + */
    +class ListIndex extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'LINDEX';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ListInsert.php b/vendor/predis/predis/lib/Predis/Command/ListInsert.php
    new file mode 100644
    index 0000000..289f9b4
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ListInsert.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/linsert
    + * @author Daniele Alessandri 
    + */
    +class ListInsert extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'LINSERT';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ListLength.php b/vendor/predis/predis/lib/Predis/Command/ListLength.php
    new file mode 100644
    index 0000000..c46dad4
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ListLength.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/llen
    + * @author Daniele Alessandri 
    + */
    +class ListLength extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'LLEN';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ListPopFirst.php b/vendor/predis/predis/lib/Predis/Command/ListPopFirst.php
    new file mode 100644
    index 0000000..f1afc4e
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ListPopFirst.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/lpop
    + * @author Daniele Alessandri 
    + */
    +class ListPopFirst extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'LPOP';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ListPopFirstBlocking.php b/vendor/predis/predis/lib/Predis/Command/ListPopFirstBlocking.php
    new file mode 100644
    index 0000000..0ada467
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ListPopFirstBlocking.php
    @@ -0,0 +1,48 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/blpop
    + * @author Daniele Alessandri 
    + */
    +class ListPopFirstBlocking extends AbstractCommand implements PrefixableCommandInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'BLPOP';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        if (count($arguments) === 2 && is_array($arguments[0])) {
    +            list($arguments, $timeout) = $arguments;
    +            array_push($arguments, $timeout);
    +        }
    +
    +        return $arguments;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function prefixKeys($prefix)
    +    {
    +        PrefixHelpers::skipLast($this, $prefix);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ListPopLast.php b/vendor/predis/predis/lib/Predis/Command/ListPopLast.php
    new file mode 100644
    index 0000000..a161f90
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ListPopLast.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/rpop
    + * @author Daniele Alessandri 
    + */
    +class ListPopLast extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'RPOP';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ListPopLastBlocking.php b/vendor/predis/predis/lib/Predis/Command/ListPopLastBlocking.php
    new file mode 100644
    index 0000000..fc3d421
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ListPopLastBlocking.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/brpop
    + * @author Daniele Alessandri 
    + */
    +class ListPopLastBlocking extends ListPopFirstBlocking
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'BRPOP';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ListPopLastPushHead.php b/vendor/predis/predis/lib/Predis/Command/ListPopLastPushHead.php
    new file mode 100644
    index 0000000..761b612
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ListPopLastPushHead.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/rpoplpush
    + * @author Daniele Alessandri 
    + */
    +class ListPopLastPushHead extends AbstractCommand implements PrefixableCommandInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'RPOPLPUSH';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function prefixKeys($prefix)
    +    {
    +        PrefixHelpers::all($this, $prefix);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ListPopLastPushHeadBlocking.php b/vendor/predis/predis/lib/Predis/Command/ListPopLastPushHeadBlocking.php
    new file mode 100644
    index 0000000..3ef1407
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ListPopLastPushHeadBlocking.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/brpoplpush
    + * @author Daniele Alessandri 
    + */
    +class ListPopLastPushHeadBlocking extends AbstractCommand implements PrefixableCommandInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'BRPOPLPUSH';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function prefixKeys($prefix)
    +    {
    +        PrefixHelpers::skipLast($this, $prefix);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ListPushHead.php b/vendor/predis/predis/lib/Predis/Command/ListPushHead.php
    new file mode 100644
    index 0000000..6dcb61b
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ListPushHead.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/lpush
    + * @author Daniele Alessandri 
    + */
    +class ListPushHead extends ListPushTail
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'LPUSH';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ListPushHeadX.php b/vendor/predis/predis/lib/Predis/Command/ListPushHeadX.php
    new file mode 100644
    index 0000000..2cb671e
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ListPushHeadX.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/lpushx
    + * @author Daniele Alessandri 
    + */
    +class ListPushHeadX extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'LPUSHX';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ListPushTail.php b/vendor/predis/predis/lib/Predis/Command/ListPushTail.php
    new file mode 100644
    index 0000000..6a0f2e0
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ListPushTail.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/rpush
    + * @author Daniele Alessandri 
    + */
    +class ListPushTail extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'RPUSH';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        return self::normalizeVariadic($arguments);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ListPushTailX.php b/vendor/predis/predis/lib/Predis/Command/ListPushTailX.php
    new file mode 100644
    index 0000000..af42a52
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ListPushTailX.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/rpushx
    + * @author Daniele Alessandri 
    + */
    +class ListPushTailX extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'RPUSHX';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ListRange.php b/vendor/predis/predis/lib/Predis/Command/ListRange.php
    new file mode 100644
    index 0000000..2b2e945
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ListRange.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/lrange
    + * @author Daniele Alessandri 
    + */
    +class ListRange extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'LRANGE';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ListRemove.php b/vendor/predis/predis/lib/Predis/Command/ListRemove.php
    new file mode 100644
    index 0000000..98f8854
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ListRemove.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/lrem
    + * @author Daniele Alessandri 
    + */
    +class ListRemove extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'LREM';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ListSet.php b/vendor/predis/predis/lib/Predis/Command/ListSet.php
    new file mode 100644
    index 0000000..0ea1ee7
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ListSet.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/lset
    + * @author Daniele Alessandri 
    + */
    +class ListSet extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'LSET';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ListTrim.php b/vendor/predis/predis/lib/Predis/Command/ListTrim.php
    new file mode 100644
    index 0000000..80d73e5
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ListTrim.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/ltrim
    + * @author Daniele Alessandri 
    + */
    +class ListTrim extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'LTRIM';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/PrefixHelpers.php b/vendor/predis/predis/lib/Predis/Command/PrefixHelpers.php
    new file mode 100644
    index 0000000..0c37d6f
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/PrefixHelpers.php
    @@ -0,0 +1,108 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * Class that defines a few helpers method for prefixing keys.
    + *
    + * @author Daniele Alessandri 
    + */
    +class PrefixHelpers
    +{
    +    /**
    +     * Applies the specified prefix only the first argument.
    +     *
    +     * @param CommandInterface $command Command instance.
    +     * @param string $prefix Prefix string.
    +     */
    +    public static function first(CommandInterface $command, $prefix)
    +    {
    +        if ($arguments = $command->getArguments()) {
    +            $arguments[0] = "$prefix{$arguments[0]}";
    +            $command->setRawArguments($arguments);
    +        }
    +    }
    +
    +    /**
    +     * Applies the specified prefix to all the arguments.
    +     *
    +     * @param CommandInterface $command Command instance.
    +     * @param string $prefix Prefix string.
    +     */
    +    public static function all(CommandInterface $command, $prefix)
    +    {
    +        if ($arguments = $command->getArguments()) {
    +            foreach ($arguments as &$key) {
    +                $key = "$prefix$key";
    +            }
    +
    +            $command->setRawArguments($arguments);
    +        }
    +    }
    +
    +    /**
    +     * Applies the specified prefix only to even arguments in the list.
    +     *
    +     * @param CommandInterface $command Command instance.
    +     * @param string $prefix Prefix string.
    +     */
    +    public static function interleaved(CommandInterface $command, $prefix)
    +    {
    +        if ($arguments = $command->getArguments()) {
    +            $length = count($arguments);
    +
    +            for ($i = 0; $i < $length; $i += 2) {
    +                $arguments[$i] = "$prefix{$arguments[$i]}";
    +            }
    +
    +            $command->setRawArguments($arguments);
    +        }
    +    }
    +
    +    /**
    +     * Applies the specified prefix to all the arguments but the first one.
    +     *
    +     * @param CommandInterface $command Command instance.
    +     * @param string $prefix Prefix string.
    +     */
    +    public static function skipFirst(CommandInterface $command, $prefix)
    +    {
    +        if ($arguments = $command->getArguments()) {
    +            $length = count($arguments);
    +
    +            for ($i = 1; $i < $length; $i++) {
    +                $arguments[$i] = "$prefix{$arguments[$i]}";
    +            }
    +
    +            $command->setRawArguments($arguments);
    +        }
    +    }
    +
    +    /**
    +     * Applies the specified prefix to all the arguments but the last one.
    +     *
    +     * @param CommandInterface $command Command instance.
    +     * @param string $prefix Prefix string.
    +     */
    +    public static function skipLast(CommandInterface $command, $prefix)
    +    {
    +        if ($arguments = $command->getArguments()) {
    +            $length = count($arguments);
    +
    +            for ($i = 0; $i < $length - 1; $i++) {
    +                $arguments[$i] = "$prefix{$arguments[$i]}";
    +            }
    +
    +            $command->setRawArguments($arguments);
    +        }
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/PrefixableCommand.php b/vendor/predis/predis/lib/Predis/Command/PrefixableCommand.php
    new file mode 100644
    index 0000000..7112e87
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/PrefixableCommand.php
    @@ -0,0 +1,31 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * Base class for Redis commands with prefixable keys.
    + *
    + * @author Daniele Alessandri 
    + */
    +abstract class PrefixableCommand extends AbstractCommand implements PrefixableCommandInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function prefixKeys($prefix)
    +    {
    +        if ($arguments = $this->getArguments()) {
    +            $arguments[0] = "$prefix{$arguments[0]}";
    +            $this->setRawArguments($arguments);
    +        }
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/PrefixableCommandInterface.php b/vendor/predis/predis/lib/Predis/Command/PrefixableCommandInterface.php
    new file mode 100644
    index 0000000..acb99a2
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/PrefixableCommandInterface.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * Defines a command whose keys can be prefixed.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface PrefixableCommandInterface
    +{
    +    /**
    +     * Prefixes all the keys found in the arguments of the command.
    +     *
    +     * @param string $prefix String used to prefix the keys.
    +     */
    +    public function prefixKeys($prefix);
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/Processor/CommandProcessingInterface.php b/vendor/predis/predis/lib/Predis/Command/Processor/CommandProcessingInterface.php
    new file mode 100644
    index 0000000..376d465
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/Processor/CommandProcessingInterface.php
    @@ -0,0 +1,34 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command\Processor;
    +
    +/**
    + * Defines an object that can process commands using command processors.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface CommandProcessingInterface
    +{
    +    /**
    +     * Associates a command processor.
    +     *
    +     * @param CommandProcessorInterface $processor The command processor.
    +     */
    +    public function setProcessor(CommandProcessorInterface $processor);
    +
    +    /**
    +     * Returns the associated command processor.
    +     *
    +     * @return CommandProcessorInterface
    +     */
    +    public function getProcessor();
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/Processor/CommandProcessorChainInterface.php b/vendor/predis/predis/lib/Predis/Command/Processor/CommandProcessorChainInterface.php
    new file mode 100644
    index 0000000..2985ac9
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/Processor/CommandProcessorChainInterface.php
    @@ -0,0 +1,42 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command\Processor;
    +
    +/**
    + * A command processor chain processes a command using multiple chained command
    + * processor before it is sent to Redis.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface CommandProcessorChainInterface extends CommandProcessorInterface, \IteratorAggregate, \Countable
    +{
    +    /**
    +     * Adds a command processor.
    +     *
    +     * @param CommandProcessorInterface $processor A command processor.
    +     */
    +    public function add(CommandProcessorInterface $processor);
    +
    +    /**
    +     * Removes a command processor from the chain.
    +     *
    +     * @param CommandProcessorInterface $processor A command processor.
    +     */
    +    public function remove(CommandProcessorInterface $processor);
    +
    +    /**
    +     * Returns an ordered list of the command processors in the chain.
    +     *
    +     * @return array
    +     */
    +    public function getProcessors();
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/Processor/CommandProcessorInterface.php b/vendor/predis/predis/lib/Predis/Command/Processor/CommandProcessorInterface.php
    new file mode 100644
    index 0000000..e23eade
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/Processor/CommandProcessorInterface.php
    @@ -0,0 +1,29 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command\Processor;
    +
    +use Predis\Command\CommandInterface;
    +
    +/**
    + * A command processor processes commands before they are sent to Redis.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface CommandProcessorInterface
    +{
    +    /**
    +     * Processes a Redis command.
    +     *
    +     * @param CommandInterface $command Redis command.
    +     */
    +    public function process(CommandInterface $command);
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/Processor/KeyPrefixProcessor.php b/vendor/predis/predis/lib/Predis/Command/Processor/KeyPrefixProcessor.php
    new file mode 100644
    index 0000000..ed0d17f
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/Processor/KeyPrefixProcessor.php
    @@ -0,0 +1,72 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command\Processor;
    +
    +use Predis\Command\CommandInterface;
    +use Predis\Command\PrefixableCommandInterface;
    +
    +/**
    + * Command processor that is used to prefix the keys contained in the arguments
    + * of a Redis command.
    + *
    + * @author Daniele Alessandri 
    + */
    +class KeyPrefixProcessor implements CommandProcessorInterface
    +{
    +    private $prefix;
    +
    +    /**
    +     * @param string $prefix Prefix for the keys.
    +     */
    +    public function __construct($prefix)
    +    {
    +        $this->setPrefix($prefix);
    +    }
    +
    +    /**
    +     * Sets a prefix that is applied to all the keys.
    +     *
    +     * @param string $prefix Prefix for the keys.
    +     */
    +    public function setPrefix($prefix)
    +    {
    +        $this->prefix = $prefix;
    +    }
    +
    +    /**
    +     * Gets the current prefix.
    +     *
    +     * @return string
    +     */
    +    public function getPrefix()
    +    {
    +        return $this->prefix;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function process(CommandInterface $command)
    +    {
    +        if ($command instanceof PrefixableCommandInterface && $command->getArguments()) {
    +            $command->prefixKeys($this->prefix);
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __toString()
    +    {
    +        return $this->getPrefix();
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/Processor/ProcessorChain.php b/vendor/predis/predis/lib/Predis/Command/Processor/ProcessorChain.php
    new file mode 100644
    index 0000000..fb4b109
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/Processor/ProcessorChain.php
    @@ -0,0 +1,130 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command\Processor;
    +
    +use Predis\Command\CommandInterface;
    +
    +/**
    + * Default implementation of a command processors chain.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ProcessorChain implements CommandProcessorChainInterface, \ArrayAccess
    +{
    +    private $processors = array();
    +
    +    /**
    +     * @param array $processors List of instances of CommandProcessorInterface.
    +     */
    +    public function __construct($processors = array())
    +    {
    +        foreach ($processors as $processor) {
    +            $this->add($processor);
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function add(CommandProcessorInterface $processor)
    +    {
    +        $this->processors[] = $processor;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function remove(CommandProcessorInterface $processor)
    +    {
    +        if (false !== $index = array_search($processor, $this->processors, true)) {
    +            unset($this[$index]);
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function process(CommandInterface $command)
    +    {
    +        for ($i = 0; $i < $count = count($this->processors); $i++) {
    +            $this->processors[$i]->process($command);
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getProcessors()
    +    {
    +        return $this->processors;
    +    }
    +
    +    /**
    +     * Returns an iterator over the list of command processor in the chain.
    +     *
    +     * @return \ArrayIterator
    +     */
    +    public function getIterator()
    +    {
    +        return new \ArrayIterator($this->processors);
    +    }
    +
    +    /**
    +     * Returns the number of command processors in the chain.
    +     *
    +     * @return int
    +     */
    +    public function count()
    +    {
    +        return count($this->processors);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function offsetExists($index)
    +    {
    +        return isset($this->processors[$index]);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function offsetGet($index)
    +    {
    +        return $this->processors[$index];
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function offsetSet($index, $processor)
    +    {
    +        if (!$processor instanceof CommandProcessorInterface) {
    +            throw new \InvalidArgumentException(
    +                'A processor chain can hold only instances of classes implementing '.
    +                'the Predis\Command\Processor\CommandProcessorInterface interface'
    +            );
    +        }
    +
    +        $this->processors[$index] = $processor;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function offsetUnset($index)
    +    {
    +        unset($this->processors[$index]);
    +        $this->processors = array_values($this->processors);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/PubSubPublish.php b/vendor/predis/predis/lib/Predis/Command/PubSubPublish.php
    new file mode 100644
    index 0000000..b2ecce5
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/PubSubPublish.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/publish
    + * @author Daniele Alessandri 
    + */
    +class PubSubPublish extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'PUBLISH';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/PubSubSubscribe.php b/vendor/predis/predis/lib/Predis/Command/PubSubSubscribe.php
    new file mode 100644
    index 0000000..eb8be65
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/PubSubSubscribe.php
    @@ -0,0 +1,43 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/subscribe
    + * @author Daniele Alessandri 
    + */
    +class PubSubSubscribe extends AbstractCommand implements PrefixableCommandInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SUBSCRIBE';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        return self::normalizeArguments($arguments);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function prefixKeys($prefix)
    +    {
    +        PrefixHelpers::all($this, $prefix);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/PubSubSubscribeByPattern.php b/vendor/predis/predis/lib/Predis/Command/PubSubSubscribeByPattern.php
    new file mode 100644
    index 0000000..7629e6d
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/PubSubSubscribeByPattern.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/psubscribe
    + * @author Daniele Alessandri 
    + */
    +class PubSubSubscribeByPattern extends PubSubSubscribe
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'PSUBSCRIBE';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/PubSubUnsubscribe.php b/vendor/predis/predis/lib/Predis/Command/PubSubUnsubscribe.php
    new file mode 100644
    index 0000000..ba70fe4
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/PubSubUnsubscribe.php
    @@ -0,0 +1,43 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/unsubscribe
    + * @author Daniele Alessandri 
    + */
    +class PubSubUnsubscribe extends AbstractCommand implements PrefixableCommandInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'UNSUBSCRIBE';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        return self::normalizeArguments($arguments);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function prefixKeys($prefix)
    +    {
    +        PrefixHelpers::all($this, $prefix);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/PubSubUnsubscribeByPattern.php b/vendor/predis/predis/lib/Predis/Command/PubSubUnsubscribeByPattern.php
    new file mode 100644
    index 0000000..f846e65
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/PubSubUnsubscribeByPattern.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/punsubscribe
    + * @author Daniele Alessandri 
    + */
    +class PubSubUnsubscribeByPattern extends PubSubUnsubscribe
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'PUNSUBSCRIBE';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ScriptedCommand.php b/vendor/predis/predis/lib/Predis/Command/ScriptedCommand.php
    new file mode 100644
    index 0000000..9c0065c
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ScriptedCommand.php
    @@ -0,0 +1,76 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * Base class used to implement an higher level abstraction for "virtual"
    + * commands based on EVAL.
    + *
    + * @link http://redis.io/commands/eval
    + * @author Daniele Alessandri 
    + */
    +abstract class ScriptedCommand extends ServerEvalSHA
    +{
    +    /**
    +     * Gets the body of a Lua script.
    +     *
    +     * @return string
    +     */
    +    public abstract function getScript();
    +
    +    /**
    +     * Specifies the number of arguments that should be considered as keys.
    +     *
    +     * The default behaviour for the base class is to return 0 to indicate that
    +     * all the elements of the arguments array should be considered as keys, but
    +     * subclasses can enforce a static number of keys.
    +     *
    +     * @return int
    +     */
    +    protected function getKeysCount()
    +    {
    +        return 0;
    +    }
    +
    +    /**
    +     * Returns the elements from the arguments that are identified as keys.
    +     *
    +     * @return array
    +     */
    +    public function getKeys()
    +    {
    +        return array_slice($this->getArguments(), 2, $this->getKeysCount());
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        if (($numkeys = $this->getKeysCount()) && $numkeys < 0) {
    +            $numkeys = count($arguments) + $numkeys;
    +        }
    +
    +        return array_merge(array(sha1($this->getScript()), (int) $numkeys), $arguments);
    +    }
    +
    +    /**
    +     * @return array
    +     */
    +    public function getEvalArguments()
    +    {
    +        $arguments = $this->getArguments();
    +        $arguments[0] = $this->getScript();
    +
    +        return $arguments;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerBackgroundRewriteAOF.php b/vendor/predis/predis/lib/Predis/Command/ServerBackgroundRewriteAOF.php
    new file mode 100644
    index 0000000..c4b4007
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerBackgroundRewriteAOF.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/bgrewriteaof
    + * @author Daniele Alessandri 
    + */
    +class ServerBackgroundRewriteAOF extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'BGREWRITEAOF';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return $data == 'Background append only file rewriting started';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerBackgroundSave.php b/vendor/predis/predis/lib/Predis/Command/ServerBackgroundSave.php
    new file mode 100644
    index 0000000..5993201
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerBackgroundSave.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/bgsave
    + * @author Daniele Alessandri 
    + */
    +class ServerBackgroundSave extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'BGSAVE';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return $data === 'Background saving started' ? true : $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerClient.php b/vendor/predis/predis/lib/Predis/Command/ServerClient.php
    new file mode 100644
    index 0000000..d020f67
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerClient.php
    @@ -0,0 +1,70 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/client
    + * @author Daniele Alessandri 
    + */
    +class ServerClient extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'CLIENT';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        $args = array_change_key_case($this->getArguments(), CASE_UPPER);
    +
    +        switch (strtoupper($args[0])) {
    +            case 'LIST':
    +                return $this->parseClientList($data);
    +            case 'KILL':
    +            case 'GETNAME':
    +            case 'SETNAME':
    +            default:
    +                return $data;
    +        }
    +    }
    +
    +    /**
    +     * Parses the reply buffer and returns the list of clients returned by
    +     * the CLIENT LIST command.
    +     *
    +     * @param string $data Reply buffer
    +     * @return array
    +     */
    +    protected function parseClientList($data)
    +    {
    +        $clients = array();
    +
    +        foreach (explode("\n", $data, -1) as $clientData) {
    +            $client = array();
    +
    +            foreach (explode(' ', $clientData) as $kv) {
    +                @list($k, $v) = explode('=', $kv);
    +                $client[$k] = $v;
    +            }
    +
    +            $clients[] = $client;
    +        }
    +
    +        return $clients;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerConfig.php b/vendor/predis/predis/lib/Predis/Command/ServerConfig.php
    new file mode 100644
    index 0000000..251efdd
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerConfig.php
    @@ -0,0 +1,47 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/config-set
    + * @link http://redis.io/commands/config-get
    + * @link http://redis.io/commands/config-resetstat
    + * @author Daniele Alessandri 
    + */
    +class ServerConfig extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'CONFIG';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        if (is_array($data)) {
    +            $result = array();
    +
    +            for ($i = 0; $i < count($data); $i++) {
    +                $result[$data[$i]] = $data[++$i];
    +            }
    +
    +            return $result;
    +        }
    +
    +        return $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerDatabaseSize.php b/vendor/predis/predis/lib/Predis/Command/ServerDatabaseSize.php
    new file mode 100644
    index 0000000..51dcac6
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerDatabaseSize.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/dbsize
    + * @author Daniele Alessandri 
    + */
    +class ServerDatabaseSize extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'DBSIZE';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerEval.php b/vendor/predis/predis/lib/Predis/Command/ServerEval.php
    new file mode 100644
    index 0000000..0c1f182
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerEval.php
    @@ -0,0 +1,51 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/eval
    + * @author Daniele Alessandri 
    + */
    +class ServerEval extends AbstractCommand implements PrefixableCommandInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'EVAL';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function prefixKeys($prefix)
    +    {
    +        if ($arguments = $this->getArguments()) {
    +            for ($i = 2; $i < $arguments[1] + 2; $i++) {
    +                $arguments[$i] = "$prefix{$arguments[$i]}";
    +            }
    +
    +            $this->setRawArguments($arguments);
    +        }
    +    }
    +
    +    /**
    +     * Calculates the SHA1 hash of the body of the script.
    +     *
    +     * @return string SHA1 hash.
    +     */
    +    public function getScriptHash()
    +    {
    +        return sha1($this->getArgument(0));
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerEvalSHA.php b/vendor/predis/predis/lib/Predis/Command/ServerEvalSHA.php
    new file mode 100644
    index 0000000..a670390
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerEvalSHA.php
    @@ -0,0 +1,37 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/evalsha
    + * @author Daniele Alessandri 
    + */
    +class ServerEvalSHA extends ServerEval
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'EVALSHA';
    +    }
    +
    +    /**
    +     * Returns the SHA1 hash of the body of the script.
    +     *
    +     * @return string SHA1 hash.
    +     */
    +    public function getScriptHash()
    +    {
    +        return $this->getArgument(0);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerFlushAll.php b/vendor/predis/predis/lib/Predis/Command/ServerFlushAll.php
    new file mode 100644
    index 0000000..a50218c
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerFlushAll.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/flushall
    + * @author Daniele Alessandri 
    + */
    +class ServerFlushAll extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'FLUSHALL';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerFlushDatabase.php b/vendor/predis/predis/lib/Predis/Command/ServerFlushDatabase.php
    new file mode 100644
    index 0000000..8b6b6a2
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerFlushDatabase.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/flushdb
    + * @author Daniele Alessandri 
    + */
    +class ServerFlushDatabase extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'FLUSHDB';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerInfo.php b/vendor/predis/predis/lib/Predis/Command/ServerInfo.php
    new file mode 100644
    index 0000000..6dd351f
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerInfo.php
    @@ -0,0 +1,100 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/info
    + * @author Daniele Alessandri 
    + */
    +class ServerInfo extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'INFO';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        $info      = array();
    +        $infoLines = preg_split('/\r?\n/', $data);
    +
    +        foreach ($infoLines as $row) {
    +            @list($k, $v) = explode(':', $row);
    +
    +            if ($row === '' || !isset($v)) {
    +                continue;
    +            }
    +
    +            if (!preg_match('/^db\d+$/', $k)) {
    +                if ($k === 'allocation_stats') {
    +                    $info[$k] = $this->parseAllocationStats($v);
    +                    continue;
    +                }
    +
    +                $info[$k] = $v;
    +            } else {
    +                $info[$k] = $this->parseDatabaseStats($v);
    +            }
    +        }
    +
    +        return $info;
    +    }
    +
    +    /**
    +     * Parses the reply buffer and extracts the statistics of each logical DB.
    +     *
    +     * @param string $str Reply buffer.
    +     * @return array
    +     */
    +    protected function parseDatabaseStats($str)
    +    {
    +        $db = array();
    +
    +        foreach (explode(',', $str) as $dbvar) {
    +            list($dbvk, $dbvv) = explode('=', $dbvar);
    +            $db[trim($dbvk)] = $dbvv;
    +        }
    +
    +        return $db;
    +    }
    +
    +    /**
    +     * Parses the reply buffer and extracts the allocation statistics.
    +     *
    +     * @param string $str Reply buffer.
    +     * @return array
    +     */
    +    protected function parseAllocationStats($str)
    +    {
    +        $stats = array();
    +
    +        foreach (explode(',', $str) as $kv) {
    +            @list($size, $objects, $extra) = explode('=', $kv);
    +
    +            // hack to prevent incorrect values when parsing the >=256 key
    +            if (isset($extra)) {
    +                $size = ">=$objects";
    +                $objects = $extra;
    +            }
    +
    +            $stats[$size] = $objects;
    +        }
    +
    +        return $stats;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerInfoV26x.php b/vendor/predis/predis/lib/Predis/Command/ServerInfoV26x.php
    new file mode 100644
    index 0000000..457fe30
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerInfoV26x.php
    @@ -0,0 +1,60 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/info
    + * @author Daniele Alessandri 
    + */
    +class ServerInfoV26x extends ServerInfo
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        $info = array();
    +        $current = null;
    +        $infoLines = preg_split('/\r?\n/', $data);
    +
    +        if (isset($infoLines[0]) && $infoLines[0][0] !== '#') {
    +            return parent::parseResponse($data);
    +        }
    +
    +        foreach ($infoLines as $row) {
    +            if ($row === '') {
    +                continue;
    +            }
    +
    +            if (preg_match('/^# (\w+)$/', $row, $matches)) {
    +                $info[$matches[1]] = array();
    +                $current = &$info[$matches[1]];
    +                continue;
    +            }
    +
    +            list($k, $v) = explode(':', $row);
    +
    +            if (!preg_match('/^db\d+$/', $k)) {
    +                if ($k === 'allocation_stats') {
    +                    $current[$k] = $this->parseAllocationStats($v);
    +                    continue;
    +                }
    +
    +                $current[$k] = $v;
    +            } else {
    +                $current[$k] = $this->parseDatabaseStats($v);
    +            }
    +        }
    +
    +        return $info;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerLastSave.php b/vendor/predis/predis/lib/Predis/Command/ServerLastSave.php
    new file mode 100644
    index 0000000..33a1e93
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerLastSave.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/lastsave
    + * @author Daniele Alessandri 
    + */
    +class ServerLastSave extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'LASTSAVE';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerMonitor.php b/vendor/predis/predis/lib/Predis/Command/ServerMonitor.php
    new file mode 100644
    index 0000000..7f4a83f
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerMonitor.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/monitor
    + * @author Daniele Alessandri 
    + */
    +class ServerMonitor extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'MONITOR';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerObject.php b/vendor/predis/predis/lib/Predis/Command/ServerObject.php
    new file mode 100644
    index 0000000..989fbd7
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerObject.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/object
    + * @author Daniele Alessandri 
    + */
    +class ServerObject extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'OBJECT';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerSave.php b/vendor/predis/predis/lib/Predis/Command/ServerSave.php
    new file mode 100644
    index 0000000..6dd3c79
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerSave.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/save
    + * @author Daniele Alessandri 
    + */
    +class ServerSave extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SAVE';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerScript.php b/vendor/predis/predis/lib/Predis/Command/ServerScript.php
    new file mode 100644
    index 0000000..5b66866
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerScript.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/script
    + * @author Daniele Alessandri 
    + */
    +class ServerScript extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SCRIPT';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerShutdown.php b/vendor/predis/predis/lib/Predis/Command/ServerShutdown.php
    new file mode 100644
    index 0000000..b0278b0
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerShutdown.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/shutdown
    + * @author Daniele Alessandri 
    + */
    +class ServerShutdown extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SHUTDOWN';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerSlaveOf.php b/vendor/predis/predis/lib/Predis/Command/ServerSlaveOf.php
    new file mode 100644
    index 0000000..94f9f69
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerSlaveOf.php
    @@ -0,0 +1,39 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/slaveof
    + * @author Daniele Alessandri 
    + */
    +class ServerSlaveOf extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SLAVEOF';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        if (count($arguments) === 0 || $arguments[0] === 'NO ONE') {
    +            return array('NO', 'ONE');
    +        }
    +
    +        return $arguments;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerSlowlog.php b/vendor/predis/predis/lib/Predis/Command/ServerSlowlog.php
    new file mode 100644
    index 0000000..ad4155f
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerSlowlog.php
    @@ -0,0 +1,50 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/slowlog
    + * @author Daniele Alessandri 
    + */
    +class ServerSlowlog extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SLOWLOG';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        if (is_array($data)) {
    +            $log = array();
    +
    +            foreach ($data as $index => $entry) {
    +                $log[$index] = array(
    +                    'id' => $entry[0],
    +                    'timestamp' => $entry[1],
    +                    'duration' => $entry[2],
    +                    'command' => $entry[3],
    +                );
    +            }
    +
    +            return $log;
    +        }
    +
    +        return $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ServerTime.php b/vendor/predis/predis/lib/Predis/Command/ServerTime.php
    new file mode 100644
    index 0000000..1797637
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ServerTime.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/time
    + * @author Daniele Alessandri 
    + */
    +class ServerTime extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'TIME';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/SetAdd.php b/vendor/predis/predis/lib/Predis/Command/SetAdd.php
    new file mode 100644
    index 0000000..f03e02b
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/SetAdd.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/sadd
    + * @author Daniele Alessandri 
    + */
    +class SetAdd extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SADD';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        return self::normalizeVariadic($arguments);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/SetCardinality.php b/vendor/predis/predis/lib/Predis/Command/SetCardinality.php
    new file mode 100644
    index 0000000..8e6e48e
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/SetCardinality.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/scard
    + * @author Daniele Alessandri 
    + */
    +class SetCardinality extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SCARD';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/SetDifference.php b/vendor/predis/predis/lib/Predis/Command/SetDifference.php
    new file mode 100644
    index 0000000..14d3f21
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/SetDifference.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/sdiff
    + * @author Daniele Alessandri 
    + */
    +class SetDifference extends SetIntersection
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SDIFF';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/SetDifferenceStore.php b/vendor/predis/predis/lib/Predis/Command/SetDifferenceStore.php
    new file mode 100644
    index 0000000..1fae8b7
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/SetDifferenceStore.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/sdiffstore
    + * @author Daniele Alessandri 
    + */
    +class SetDifferenceStore extends SetIntersectionStore
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SDIFFSTORE';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/SetIntersection.php b/vendor/predis/predis/lib/Predis/Command/SetIntersection.php
    new file mode 100644
    index 0000000..c6590d2
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/SetIntersection.php
    @@ -0,0 +1,43 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/sinter
    + * @author Daniele Alessandri 
    + */
    +class SetIntersection extends AbstractCommand implements PrefixableCommandInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SINTER';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        return self::normalizeArguments($arguments);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function prefixKeys($prefix)
    +    {
    +        PrefixHelpers::all($this, $prefix);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/SetIntersectionStore.php b/vendor/predis/predis/lib/Predis/Command/SetIntersectionStore.php
    new file mode 100644
    index 0000000..4e764e0
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/SetIntersectionStore.php
    @@ -0,0 +1,47 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/sinterstore
    + * @author Daniele Alessandri 
    + */
    +class SetIntersectionStore extends AbstractCommand implements PrefixableCommandInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SINTERSTORE';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        if (count($arguments) === 2 && is_array($arguments[1])) {
    +            return array_merge(array($arguments[0]), $arguments[1]);
    +        }
    +
    +        return $arguments;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function prefixKeys($prefix)
    +    {
    +        PrefixHelpers::all($this, $prefix);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/SetIsMember.php b/vendor/predis/predis/lib/Predis/Command/SetIsMember.php
    new file mode 100644
    index 0000000..0773520
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/SetIsMember.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/sismember
    + * @author Daniele Alessandri 
    + */
    +class SetIsMember extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SISMEMBER';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return (bool) $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/SetMembers.php b/vendor/predis/predis/lib/Predis/Command/SetMembers.php
    new file mode 100644
    index 0000000..3ebef0a
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/SetMembers.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/smembers
    + * @author Daniele Alessandri 
    + */
    +class SetMembers extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SMEMBERS';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/SetMove.php b/vendor/predis/predis/lib/Predis/Command/SetMove.php
    new file mode 100644
    index 0000000..01678ec
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/SetMove.php
    @@ -0,0 +1,43 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/smove
    + * @author Daniele Alessandri 
    + */
    +class SetMove extends AbstractCommand implements PrefixableCommandInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SMOVE';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function prefixKeys($prefix)
    +    {
    +        PrefixHelpers::skipLast($this, $prefix);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return (bool) $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/SetPop.php b/vendor/predis/predis/lib/Predis/Command/SetPop.php
    new file mode 100644
    index 0000000..ebfec0c
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/SetPop.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/spop
    + * @author Daniele Alessandri 
    + */
    +class SetPop extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SPOP';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/SetRandomMember.php b/vendor/predis/predis/lib/Predis/Command/SetRandomMember.php
    new file mode 100644
    index 0000000..a6b6062
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/SetRandomMember.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/srandmember
    + * @author Daniele Alessandri 
    + */
    +class SetRandomMember extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SRANDMEMBER';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/SetRemove.php b/vendor/predis/predis/lib/Predis/Command/SetRemove.php
    new file mode 100644
    index 0000000..f7cb577
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/SetRemove.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/srem
    + * @author Daniele Alessandri 
    + */
    +class SetRemove extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SREM';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        return self::normalizeVariadic($arguments);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/SetUnion.php b/vendor/predis/predis/lib/Predis/Command/SetUnion.php
    new file mode 100644
    index 0000000..c9f322b
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/SetUnion.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/sunion
    + * @author Daniele Alessandri 
    + */
    +class SetUnion extends SetIntersection
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SUNION';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/SetUnionStore.php b/vendor/predis/predis/lib/Predis/Command/SetUnionStore.php
    new file mode 100644
    index 0000000..daf66c3
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/SetUnionStore.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/sunionstore
    + * @author Daniele Alessandri 
    + */
    +class SetUnionStore extends SetIntersectionStore
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SUNIONSTORE';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringAppend.php b/vendor/predis/predis/lib/Predis/Command/StringAppend.php
    new file mode 100644
    index 0000000..8d14614
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringAppend.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/append
    + * @author Daniele Alessandri 
    + */
    +class StringAppend extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'APPEND';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringBitCount.php b/vendor/predis/predis/lib/Predis/Command/StringBitCount.php
    new file mode 100644
    index 0000000..19d534b
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringBitCount.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/bitcount
    + * @author Daniele Alessandri 
    + */
    +class StringBitCount extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'BITCOUNT';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringBitOp.php b/vendor/predis/predis/lib/Predis/Command/StringBitOp.php
    new file mode 100644
    index 0000000..0236bb1
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringBitOp.php
    @@ -0,0 +1,49 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/bitop
    + * @author Daniele Alessandri 
    + */
    +class StringBitOp extends AbstractCommand implements PrefixableCommandInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'BITOP';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        if (count($arguments) === 3 && is_array($arguments[2])) {
    +            list($operation, $destination, ) = $arguments;
    +            $arguments = $arguments[2];
    +            array_unshift($arguments, $operation, $destination);
    +        }
    +
    +        return $arguments;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function prefixKeys($prefix)
    +    {
    +        PrefixHelpers::skipFirst($this, $prefix);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringDecrement.php b/vendor/predis/predis/lib/Predis/Command/StringDecrement.php
    new file mode 100644
    index 0000000..0e45cd0
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringDecrement.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/decr
    + * @author Daniele Alessandri 
    + */
    +class StringDecrement extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'DECR';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringDecrementBy.php b/vendor/predis/predis/lib/Predis/Command/StringDecrementBy.php
    new file mode 100644
    index 0000000..e716e0e
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringDecrementBy.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/decrby
    + * @author Daniele Alessandri 
    + */
    +class StringDecrementBy extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'DECRBY';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringGet.php b/vendor/predis/predis/lib/Predis/Command/StringGet.php
    new file mode 100644
    index 0000000..851c710
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringGet.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/get
    + * @author Daniele Alessandri 
    + */
    +class StringGet extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'GET';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringGetBit.php b/vendor/predis/predis/lib/Predis/Command/StringGetBit.php
    new file mode 100644
    index 0000000..954c39f
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringGetBit.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/getbit
    + * @author Daniele Alessandri 
    + */
    +class StringGetBit extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'GETBIT';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringGetMultiple.php b/vendor/predis/predis/lib/Predis/Command/StringGetMultiple.php
    new file mode 100644
    index 0000000..71d9187
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringGetMultiple.php
    @@ -0,0 +1,43 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/mget
    + * @author Daniele Alessandri 
    + */
    +class StringGetMultiple extends AbstractCommand implements PrefixableCommandInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'MGET';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        return self::normalizeArguments($arguments);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function prefixKeys($prefix)
    +    {
    +        PrefixHelpers::all($this, $prefix);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringGetRange.php b/vendor/predis/predis/lib/Predis/Command/StringGetRange.php
    new file mode 100644
    index 0000000..aed4853
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringGetRange.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/getrange
    + * @author Daniele Alessandri 
    + */
    +class StringGetRange extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'GETRANGE';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringGetSet.php b/vendor/predis/predis/lib/Predis/Command/StringGetSet.php
    new file mode 100644
    index 0000000..d4b8e23
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringGetSet.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/getset
    + * @author Daniele Alessandri 
    + */
    +class StringGetSet extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'GETSET';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringIncrement.php b/vendor/predis/predis/lib/Predis/Command/StringIncrement.php
    new file mode 100644
    index 0000000..4848afc
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringIncrement.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/incr
    + * @author Daniele Alessandri 
    + */
    +class StringIncrement extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'INCR';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringIncrementBy.php b/vendor/predis/predis/lib/Predis/Command/StringIncrementBy.php
    new file mode 100644
    index 0000000..7fa3188
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringIncrementBy.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/incrby
    + * @author Daniele Alessandri 
    + */
    +class StringIncrementBy extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'INCRBY';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringIncrementByFloat.php b/vendor/predis/predis/lib/Predis/Command/StringIncrementByFloat.php
    new file mode 100644
    index 0000000..7e14dff
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringIncrementByFloat.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/incrbyfloat
    + * @author Daniele Alessandri 
    + */
    +class StringIncrementByFloat extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'INCRBYFLOAT';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringPreciseSetExpire.php b/vendor/predis/predis/lib/Predis/Command/StringPreciseSetExpire.php
    new file mode 100644
    index 0000000..da2cbad
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringPreciseSetExpire.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/psetex
    + * @author Daniele Alessandri 
    + */
    +class StringPreciseSetExpire extends StringSetExpire
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'PSETEX';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringSet.php b/vendor/predis/predis/lib/Predis/Command/StringSet.php
    new file mode 100644
    index 0000000..eb3739b
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringSet.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/set
    + * @author Daniele Alessandri 
    + */
    +class StringSet extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SET';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringSetBit.php b/vendor/predis/predis/lib/Predis/Command/StringSetBit.php
    new file mode 100644
    index 0000000..a92b418
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringSetBit.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/setbit
    + * @author Daniele Alessandri 
    + */
    +class StringSetBit extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SETBIT';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringSetExpire.php b/vendor/predis/predis/lib/Predis/Command/StringSetExpire.php
    new file mode 100644
    index 0000000..c235e2b
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringSetExpire.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/setex
    + * @author Daniele Alessandri 
    + */
    +class StringSetExpire extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SETEX';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringSetMultiple.php b/vendor/predis/predis/lib/Predis/Command/StringSetMultiple.php
    new file mode 100644
    index 0000000..cda5157
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringSetMultiple.php
    @@ -0,0 +1,55 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/mset
    + * @author Daniele Alessandri 
    + */
    +class StringSetMultiple extends AbstractCommand implements PrefixableCommandInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'MSET';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        if (count($arguments) === 1 && is_array($arguments[0])) {
    +            $flattenedKVs = array();
    +            $args = $arguments[0];
    +
    +            foreach ($args as $k => $v) {
    +                $flattenedKVs[] = $k;
    +                $flattenedKVs[] = $v;
    +            }
    +
    +            return $flattenedKVs;
    +        }
    +
    +        return $arguments;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function prefixKeys($prefix)
    +    {
    +        PrefixHelpers::interleaved($this, $prefix);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringSetMultiplePreserve.php b/vendor/predis/predis/lib/Predis/Command/StringSetMultiplePreserve.php
    new file mode 100644
    index 0000000..961422d
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringSetMultiplePreserve.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/msetnx
    + * @author Daniele Alessandri 
    + */
    +class StringSetMultiplePreserve extends StringSetMultiple
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'MSETNX';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return (bool) $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringSetPreserve.php b/vendor/predis/predis/lib/Predis/Command/StringSetPreserve.php
    new file mode 100644
    index 0000000..d6e4a81
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringSetPreserve.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/setnx
    + * @author Daniele Alessandri 
    + */
    +class StringSetPreserve extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SETNX';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return (bool) $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringSetRange.php b/vendor/predis/predis/lib/Predis/Command/StringSetRange.php
    new file mode 100644
    index 0000000..da30f32
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringSetRange.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/setrange
    + * @author Daniele Alessandri 
    + */
    +class StringSetRange extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SETRANGE';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringStrlen.php b/vendor/predis/predis/lib/Predis/Command/StringStrlen.php
    new file mode 100644
    index 0000000..1b839ad
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringStrlen.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/strlen
    + * @author Daniele Alessandri 
    + */
    +class StringStrlen extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'STRLEN';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/StringSubstr.php b/vendor/predis/predis/lib/Predis/Command/StringSubstr.php
    new file mode 100644
    index 0000000..671fc16
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/StringSubstr.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/substr
    + * @author Daniele Alessandri 
    + */
    +class StringSubstr extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'SUBSTR';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/TransactionDiscard.php b/vendor/predis/predis/lib/Predis/Command/TransactionDiscard.php
    new file mode 100644
    index 0000000..67010b0
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/TransactionDiscard.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/discard
    + * @author Daniele Alessandri 
    + */
    +class TransactionDiscard extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'DISCARD';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/TransactionExec.php b/vendor/predis/predis/lib/Predis/Command/TransactionExec.php
    new file mode 100644
    index 0000000..5141454
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/TransactionExec.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/exec
    + * @author Daniele Alessandri 
    + */
    +class TransactionExec extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'EXEC';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/TransactionMulti.php b/vendor/predis/predis/lib/Predis/Command/TransactionMulti.php
    new file mode 100644
    index 0000000..8f4ccf1
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/TransactionMulti.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/multi
    + * @author Daniele Alessandri 
    + */
    +class TransactionMulti extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'MULTI';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/TransactionUnwatch.php b/vendor/predis/predis/lib/Predis/Command/TransactionUnwatch.php
    new file mode 100644
    index 0000000..697e09f
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/TransactionUnwatch.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/unwatch
    + * @author Daniele Alessandri 
    + */
    +class TransactionUnwatch extends AbstractCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'UNWATCH';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return (bool) $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/TransactionWatch.php b/vendor/predis/predis/lib/Predis/Command/TransactionWatch.php
    new file mode 100644
    index 0000000..a1dd9a2
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/TransactionWatch.php
    @@ -0,0 +1,55 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/watch
    + * @author Daniele Alessandri 
    + */
    +class TransactionWatch extends AbstractCommand implements PrefixableCommandInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'WATCH';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        if (isset($arguments[0]) && is_array($arguments[0])) {
    +            return $arguments[0];
    +        }
    +
    +        return $arguments;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function prefixKeys($prefix)
    +    {
    +        PrefixHelpers::all($this, $prefix);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        return (bool) $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ZSetAdd.php b/vendor/predis/predis/lib/Predis/Command/ZSetAdd.php
    new file mode 100644
    index 0000000..e0b1c53
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ZSetAdd.php
    @@ -0,0 +1,46 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/zadd
    + * @author Daniele Alessandri 
    + */
    +class ZSetAdd extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'ZADD';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        if (count($arguments) === 2 && is_array($arguments[1])) {
    +            $flattened = array($arguments[0]);
    +
    +            foreach($arguments[1] as $member => $score) {
    +                $flattened[] = $score;
    +                $flattened[] = $member;
    +            }
    +
    +            return $flattened;
    +        }
    +
    +        return $arguments;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ZSetCardinality.php b/vendor/predis/predis/lib/Predis/Command/ZSetCardinality.php
    new file mode 100644
    index 0000000..4e4432e
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ZSetCardinality.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/zcard
    + * @author Daniele Alessandri 
    + */
    +class ZSetCardinality extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'ZCARD';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ZSetCount.php b/vendor/predis/predis/lib/Predis/Command/ZSetCount.php
    new file mode 100644
    index 0000000..2d8aa9c
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ZSetCount.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/zcount
    + * @author Daniele Alessandri 
    + */
    +class ZSetCount extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'ZCOUNT';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ZSetIncrementBy.php b/vendor/predis/predis/lib/Predis/Command/ZSetIncrementBy.php
    new file mode 100644
    index 0000000..a32bfab
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ZSetIncrementBy.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/zincrby
    + * @author Daniele Alessandri 
    + */
    +class ZSetIncrementBy extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'ZINCRBY';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ZSetIntersectionStore.php b/vendor/predis/predis/lib/Predis/Command/ZSetIntersectionStore.php
    new file mode 100644
    index 0000000..9f6b109
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ZSetIntersectionStore.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/zinterstore
    + * @author Daniele Alessandri 
    + */
    +class ZSetIntersectionStore extends ZSetUnionStore
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'ZINTERSTORE';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ZSetRange.php b/vendor/predis/predis/lib/Predis/Command/ZSetRange.php
    new file mode 100644
    index 0000000..7403873
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ZSetRange.php
    @@ -0,0 +1,102 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/zrange
    + * @author Daniele Alessandri 
    + */
    +class ZSetRange extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'ZRANGE';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        if (count($arguments) === 4) {
    +            $lastType = gettype($arguments[3]);
    +
    +            if ($lastType === 'string' && strtoupper($arguments[3]) === 'WITHSCORES') {
    +                // Used for compatibility with older versions
    +                $arguments[3] = array('WITHSCORES' => true);
    +                $lastType = 'array';
    +            }
    +
    +            if ($lastType === 'array') {
    +                $options = $this->prepareOptions(array_pop($arguments));
    +                return array_merge($arguments, $options);
    +            }
    +        }
    +
    +        return $arguments;
    +    }
    +
    +    /**
    +     * Returns a list of options and modifiers compatible with Redis.
    +     *
    +     * @param array $options List of options.
    +     * @return array
    +     */
    +    protected function prepareOptions($options)
    +    {
    +        $opts = array_change_key_case($options, CASE_UPPER);
    +        $finalizedOpts = array();
    +
    +        if (!empty($opts['WITHSCORES'])) {
    +            $finalizedOpts[] = 'WITHSCORES';
    +        }
    +
    +        return $finalizedOpts;
    +    }
    +
    +    /**
    +     * Checks for the presence of the WITHSCORES modifier.
    +     *
    +     * @return Boolean
    +     */
    +    protected function withScores()
    +    {
    +        $arguments = $this->getArguments();
    +
    +        if (count($arguments) < 4) {
    +            return false;
    +        }
    +
    +        return strtoupper($arguments[3]) === 'WITHSCORES';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function parseResponse($data)
    +    {
    +        if ($this->withScores()) {
    +            $result = array();
    +
    +            for ($i = 0; $i < count($data); $i++) {
    +                $result[] = array($data[$i], $data[++$i]);
    +            }
    +
    +            return $result;
    +        }
    +
    +        return $data;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ZSetRangeByScore.php b/vendor/predis/predis/lib/Predis/Command/ZSetRangeByScore.php
    new file mode 100644
    index 0000000..cfe73aa
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ZSetRangeByScore.php
    @@ -0,0 +1,67 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/zrangebyscore
    + * @author Daniele Alessandri 
    + */
    +class ZSetRangeByScore extends ZSetRange
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'ZRANGEBYSCORE';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function prepareOptions($options)
    +    {
    +        $opts = array_change_key_case($options, CASE_UPPER);
    +        $finalizedOpts = array();
    +
    +        if (isset($opts['LIMIT']) && is_array($opts['LIMIT'])) {
    +            $limit = array_change_key_case($opts['LIMIT'], CASE_UPPER);
    +
    +            $finalizedOpts[] = 'LIMIT';
    +            $finalizedOpts[] = isset($limit['OFFSET']) ? $limit['OFFSET'] : $limit[0];
    +            $finalizedOpts[] = isset($limit['COUNT']) ? $limit['COUNT'] : $limit[1];
    +        }
    +
    +        return array_merge($finalizedOpts, parent::prepareOptions($options));
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function withScores()
    +    {
    +        $arguments = $this->getArguments();
    +
    +        for ($i = 3; $i < count($arguments); $i++) {
    +            switch (strtoupper($arguments[$i])) {
    +                case 'WITHSCORES':
    +                    return true;
    +
    +                case 'LIMIT':
    +                    $i += 2;
    +                    break;
    +            }
    +        }
    +
    +        return false;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ZSetRank.php b/vendor/predis/predis/lib/Predis/Command/ZSetRank.php
    new file mode 100644
    index 0000000..41c2ae2
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ZSetRank.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/zrank
    + * @author Daniele Alessandri 
    + */
    +class ZSetRank extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'ZRANK';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ZSetRemove.php b/vendor/predis/predis/lib/Predis/Command/ZSetRemove.php
    new file mode 100644
    index 0000000..ae7208d
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ZSetRemove.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/zrem
    + * @author Daniele Alessandri 
    + */
    +class ZSetRemove extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'ZREM';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        return self::normalizeVariadic($arguments);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ZSetRemoveRangeByRank.php b/vendor/predis/predis/lib/Predis/Command/ZSetRemoveRangeByRank.php
    new file mode 100644
    index 0000000..45b9028
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ZSetRemoveRangeByRank.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/zremrangebyrank
    + * @author Daniele Alessandri 
    + */
    +class ZSetRemoveRangeByRank extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'ZREMRANGEBYRANK';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ZSetRemoveRangeByScore.php b/vendor/predis/predis/lib/Predis/Command/ZSetRemoveRangeByScore.php
    new file mode 100644
    index 0000000..a2b3ad8
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ZSetRemoveRangeByScore.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/zremrangebyscore
    + * @author Daniele Alessandri 
    + */
    +class ZSetRemoveRangeByScore extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'ZREMRANGEBYSCORE';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ZSetReverseRange.php b/vendor/predis/predis/lib/Predis/Command/ZSetReverseRange.php
    new file mode 100644
    index 0000000..e7344e0
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ZSetReverseRange.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/zrevrange
    + * @author Daniele Alessandri 
    + */
    +class ZSetReverseRange extends ZSetRange
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'ZREVRANGE';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ZSetReverseRangeByScore.php b/vendor/predis/predis/lib/Predis/Command/ZSetReverseRangeByScore.php
    new file mode 100644
    index 0000000..cded7c1
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ZSetReverseRangeByScore.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/zrevrangebyscore
    + * @author Daniele Alessandri 
    + */
    +class ZSetReverseRangeByScore extends ZSetRangeByScore
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'ZREVRANGEBYSCORE';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ZSetReverseRank.php b/vendor/predis/predis/lib/Predis/Command/ZSetReverseRank.php
    new file mode 100644
    index 0000000..feb1048
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ZSetReverseRank.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/zrevrank
    + * @author Daniele Alessandri 
    + */
    +class ZSetReverseRank extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'ZREVRANK';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ZSetScore.php b/vendor/predis/predis/lib/Predis/Command/ZSetScore.php
    new file mode 100644
    index 0000000..8455e4e
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ZSetScore.php
    @@ -0,0 +1,27 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/zscore
    + * @author Daniele Alessandri 
    + */
    +class ZSetScore extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'ZSCORE';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Command/ZSetUnionStore.php b/vendor/predis/predis/lib/Predis/Command/ZSetUnionStore.php
    new file mode 100644
    index 0000000..ba97176
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Command/ZSetUnionStore.php
    @@ -0,0 +1,93 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +/**
    + * @link http://redis.io/commands/zunionstore
    + * @author Daniele Alessandri 
    + */
    +class ZSetUnionStore extends PrefixableCommand
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getId()
    +    {
    +        return 'ZUNIONSTORE';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function filterArguments(Array $arguments)
    +    {
    +        $options = array();
    +        $argc = count($arguments);
    +
    +        if ($argc > 2 && is_array($arguments[$argc - 1])) {
    +            $options = $this->prepareOptions(array_pop($arguments));
    +        }
    +
    +        if (is_array($arguments[1])) {
    +            $arguments = array_merge(
    +                array($arguments[0], count($arguments[1])),
    +                $arguments[1]
    +            );
    +        }
    +
    +        return array_merge($arguments, $options);
    +    }
    +
    +    /**
    +     * Returns a list of options and modifiers compatible with Redis.
    +     *
    +     * @param array $options List of options.
    +     * @return array
    +     */
    +    private function prepareOptions($options)
    +    {
    +        $opts = array_change_key_case($options, CASE_UPPER);
    +        $finalizedOpts = array();
    +
    +        if (isset($opts['WEIGHTS']) && is_array($opts['WEIGHTS'])) {
    +            $finalizedOpts[] = 'WEIGHTS';
    +
    +            foreach ($opts['WEIGHTS'] as $weight) {
    +                $finalizedOpts[] = $weight;
    +            }
    +        }
    +
    +        if (isset($opts['AGGREGATE'])) {
    +            $finalizedOpts[] = 'AGGREGATE';
    +            $finalizedOpts[] = $opts['AGGREGATE'];
    +        }
    +
    +        return $finalizedOpts;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function prefixKeys($prefix)
    +    {
    +        if ($arguments = $this->getArguments()) {
    +            $arguments[0] = "$prefix{$arguments[0]}";
    +            $length = ((int) $arguments[1]) + 2;
    +
    +            for ($i = 2; $i < $length; $i++) {
    +                $arguments[$i] = "$prefix{$arguments[$i]}";
    +            }
    +
    +            $this->setRawArguments($arguments);
    +        }
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/CommunicationException.php b/vendor/predis/predis/lib/Predis/CommunicationException.php
    new file mode 100644
    index 0000000..c9e75e2
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/CommunicationException.php
    @@ -0,0 +1,76 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +use Predis\Connection\SingleConnectionInterface;
    +
    +/**
    + * Base exception class for network-related errors.
    + *
    + * @author Daniele Alessandri 
    + */
    +abstract class CommunicationException extends PredisException
    +{
    +    private $connection;
    +
    +    /**
    +     * @param SingleConnectionInterface $connection Connection that generated the exception.
    +     * @param string $message Error message.
    +     * @param int $code Error code.
    +     * @param \Exception $innerException Inner exception for wrapping the original error.
    +     */
    +    public function __construct(
    +        SingleConnectionInterface $connection, $message = null, $code = null, \Exception $innerException = null
    +    ) {
    +        parent::__construct($message, $code, $innerException);
    +        $this->connection = $connection;
    +    }
    +
    +    /**
    +     * Gets the connection that generated the exception.
    +     *
    +     * @return SingleConnectionInterface
    +     */
    +    public function getConnection()
    +    {
    +        return $this->connection;
    +    }
    +
    +    /**
    +     * Indicates if the receiver should reset the underlying connection.
    +     *
    +     * @return Boolean
    +     */
    +    public function shouldResetConnection()
    +    {
    +        return true;
    +    }
    +
    +    /**
    +     * Offers a generic and reusable method to handle exceptions generated by
    +     * a connection object.
    +     *
    +     * @param CommunicationException $exception Exception.
    +     */
    +    public static function handle(CommunicationException $exception)
    +    {
    +        if ($exception->shouldResetConnection()) {
    +            $connection = $exception->getConnection();
    +
    +            if ($connection->isConnected()) {
    +                $connection->disconnect();
    +            }
    +        }
    +
    +        throw $exception;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/AbstractConnection.php b/vendor/predis/predis/lib/Predis/Connection/AbstractConnection.php
    new file mode 100644
    index 0000000..96a84ae
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/AbstractConnection.php
    @@ -0,0 +1,225 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use Predis\ClientException;
    +use Predis\CommunicationException;
    +use Predis\NotSupportedException;
    +use Predis\Command\CommandInterface;
    +use Predis\Protocol\ProtocolException;
    +
    +/**
    + * Base class with the common logic used by connection classes to communicate with Redis.
    + *
    + * @author Daniele Alessandri 
    + */
    +abstract class AbstractConnection implements SingleConnectionInterface
    +{
    +    private $resource;
    +    private $cachedId;
    +
    +    protected $parameters;
    +    protected $initCmds = array();
    +
    +    /**
    +     * @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
    +     */
    +    public function __construct(ConnectionParametersInterface $parameters)
    +    {
    +        $this->parameters = $this->checkParameters($parameters);
    +    }
    +
    +    /**
    +     * Disconnects from the server and destroys the underlying resource when
    +     * PHP's garbage collector kicks in.
    +     */
    +    public function __destruct()
    +    {
    +        $this->disconnect();
    +    }
    +
    +    /**
    +     * Checks some of the parameters used to initialize the connection.
    +     *
    +     * @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
    +     */
    +    protected function checkParameters(ConnectionParametersInterface $parameters)
    +    {
    +        switch ($parameters->scheme) {
    +            case 'unix':
    +                if (!isset($parameters->path)) {
    +                    throw new \InvalidArgumentException('Missing UNIX domain socket path');
    +                }
    +
    +            case 'tcp':
    +                return $parameters;
    +
    +            default:
    +                throw new \InvalidArgumentException("Invalid scheme: {$parameters->scheme}");
    +        }
    +    }
    +
    +    /**
    +     * Creates the underlying resource used to communicate with Redis.
    +     *
    +     * @return mixed
    +     */
    +    protected abstract function createResource();
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function isConnected()
    +    {
    +        return isset($this->resource);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function connect()
    +    {
    +        if ($this->isConnected()) {
    +            throw new ClientException('Connection already estabilished');
    +        }
    +
    +        $this->resource = $this->createResource();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function disconnect()
    +    {
    +        unset($this->resource);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function pushInitCommand(CommandInterface $command)
    +    {
    +        $this->initCmds[] = $command;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function executeCommand(CommandInterface $command)
    +    {
    +        $this->writeCommand($command);
    +        return $this->readResponse($command);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function readResponse(CommandInterface $command)
    +    {
    +        return $this->read();
    +    }
    +
    +    /**
    +     * Helper method to handle connection errors.
    +     *
    +     * @param string $message Error message.
    +     * @param int $code Error code.
    +     */
    +    protected function onConnectionError($message, $code = null)
    +    {
    +        CommunicationException::handle(new ConnectionException($this, "$message [{$this->parameters->scheme}://{$this->getIdentifier()}]", $code));
    +    }
    +
    +    /**
    +     * Helper method to handle protocol errors.
    +     *
    +     * @param string $message Error message.
    +     */
    +    protected function onProtocolError($message)
    +    {
    +        CommunicationException::handle(new ProtocolException($this, "$message [{$this->parameters->scheme}://{$this->getIdentifier()}]"));
    +    }
    +
    +    /**
    +     * Helper method to handle not supported connection parameters.
    +     *
    +     * @param string $option Name of the option.
    +     * @param mixed $parameters Parameters used to initialize the connection.
    +     */
    +    protected function onInvalidOption($option, $parameters = null)
    +    {
    +        $class = get_called_class();
    +        $message = "Invalid option for connection $class: $option";
    +
    +        if (isset($parameters)) {
    +            $message .= sprintf(' [%s => %s]', $option, $parameters->{$option});
    +        }
    +
    +        throw new NotSupportedException($message);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getResource()
    +    {
    +        if (isset($this->resource)) {
    +            return $this->resource;
    +        }
    +
    +        $this->connect();
    +
    +        return $this->resource;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getParameters()
    +    {
    +        return $this->parameters;
    +    }
    +
    +    /**
    +     * Gets an identifier for the connection.
    +     *
    +     * @return string
    +     */
    +    protected function getIdentifier()
    +    {
    +        if ($this->parameters->scheme === 'unix') {
    +            return $this->parameters->path;
    +        }
    +
    +        return "{$this->parameters->host}:{$this->parameters->port}";
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __toString()
    +    {
    +        if (!isset($this->cachedId)) {
    +            $this->cachedId = $this->getIdentifier();
    +        }
    +
    +        return $this->cachedId;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __sleep()
    +    {
    +        return array('parameters', 'initCmds');
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/AggregatedConnectionInterface.php b/vendor/predis/predis/lib/Predis/Connection/AggregatedConnectionInterface.php
    new file mode 100644
    index 0000000..bd1065c
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/AggregatedConnectionInterface.php
    @@ -0,0 +1,55 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use Predis\Command\CommandInterface;
    +
    +/**
    + * Defines a virtual connection composed by multiple connection objects.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface AggregatedConnectionInterface extends ConnectionInterface
    +{
    +    /**
    +     * Adds a connection instance to the aggregated connection.
    +     *
    +     * @param SingleConnectionInterface $connection Instance of a connection.
    +     */
    +    public function add(SingleConnectionInterface $connection);
    +
    +    /**
    +     * Removes the specified connection instance from the aggregated
    +     * connection.
    +     *
    +     * @param SingleConnectionInterface $connection Instance of a connection.
    +     * @return Boolean Returns true if the connection was in the pool.
    +     */
    +    public function remove(SingleConnectionInterface $connection);
    +
    +    /**
    +     * Gets the actual connection instance in charge of the specified command.
    +     *
    +     * @param CommandInterface $command Instance of a Redis command.
    +     * @return SingleConnectionInterface
    +     */
    +    public function getConnection(CommandInterface $command);
    +
    +    /**
    +     * Retrieves a connection instance from the aggregated connection
    +     * using an alias.
    +     *
    +     * @param string $connectionId Alias of a connection
    +     * @return SingleConnectionInterface
    +     */
    +    public function getConnectionById($connectionId);
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/ClusterConnectionInterface.php b/vendor/predis/predis/lib/Predis/Connection/ClusterConnectionInterface.php
    new file mode 100644
    index 0000000..956ef2c
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/ClusterConnectionInterface.php
    @@ -0,0 +1,22 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +/**
    + * Defines a cluster of Redis servers formed by aggregating multiple
    + * connection objects.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface ClusterConnectionInterface extends AggregatedConnectionInterface
    +{
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/ComposableConnectionInterface.php b/vendor/predis/predis/lib/Predis/Connection/ComposableConnectionInterface.php
    new file mode 100644
    index 0000000..8b63536
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/ComposableConnectionInterface.php
    @@ -0,0 +1,57 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use Predis\Protocol\ProtocolInterface;
    +
    +/**
    + * Defines a connection object used to communicate with a single Redis server
    + * that leverages an external protocol processor to handle pluggable protocol
    + * handlers.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface ComposableConnectionInterface extends SingleConnectionInterface
    +{
    +    /**
    +     * Sets the protocol processor used by the connection.
    +     *
    +     * @param ProtocolInterface $protocol Protocol processor.
    +     */
    +    public function setProtocol(ProtocolInterface $protocol);
    +
    +    /**
    +     * Gets the protocol processor used by the connection.
    +     */
    +    public function getProtocol();
    +
    +    /**
    +     * Writes a buffer that contains a serialized Redis command.
    +     *
    +     * @param string $buffer Serialized Redis command.
    +     */
    +    public function writeBytes($buffer);
    +
    +    /**
    +     * Reads a specified number of bytes from the connection.
    +     *
    +     * @param string
    +     */
    +    public function readBytes($length);
    +
    +    /**
    +     * Reads a line from the connection.
    +     *
    +     * @param string
    +     */
    +    public function readLine();
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/ComposableStreamConnection.php b/vendor/predis/predis/lib/Predis/Connection/ComposableStreamConnection.php
    new file mode 100644
    index 0000000..34aaa9b
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/ComposableStreamConnection.php
    @@ -0,0 +1,135 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use Predis\Command\CommandInterface;
    +use Predis\Protocol\ProtocolInterface;
    +use Predis\Protocol\Text\TextProtocol;
    +
    +/**
    + * Connection abstraction to Redis servers based on PHP's stream that uses an
    + * external protocol processor defining the protocol used for the communication.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ComposableStreamConnection extends StreamConnection implements ComposableConnectionInterface
    +{
    +    private $protocol;
    +
    +    /**
    +     * @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
    +     * @param ProtocolInterface $protocol A protocol processor.
    +     */
    +    public function __construct(ConnectionParametersInterface $parameters, ProtocolInterface $protocol = null)
    +    {
    +        $this->parameters = $this->checkParameters($parameters);
    +        $this->protocol = $protocol ?: new TextProtocol();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function setProtocol(ProtocolInterface $protocol)
    +    {
    +        if ($protocol === null) {
    +            throw new \InvalidArgumentException("The protocol instance cannot be a null value");
    +        }
    +
    +        $this->protocol = $protocol;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getProtocol()
    +    {
    +        return $this->protocol;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function writeBytes($buffer)
    +    {
    +        parent::writeBytes($buffer);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function readBytes($length)
    +    {
    +        if ($length <= 0) {
    +            throw new \InvalidArgumentException('Length parameter must be greater than 0');
    +        }
    +
    +        $value  = '';
    +        $socket = $this->getResource();
    +
    +        do {
    +            $chunk = fread($socket, $length);
    +
    +            if ($chunk === false || $chunk === '') {
    +                $this->onConnectionError('Error while reading bytes from the server');
    +            }
    +
    +            $value .= $chunk;
    +        } while (($length -= strlen($chunk)) > 0);
    +
    +        return $value;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function readLine()
    +    {
    +        $value  = '';
    +        $socket = $this->getResource();
    +
    +        do {
    +            $chunk = fgets($socket);
    +
    +            if ($chunk === false || $chunk === '') {
    +                $this->onConnectionError('Error while reading line from the server');
    +            }
    +
    +            $value .= $chunk;
    +        } while (substr($value, -2) !== "\r\n");
    +
    +        return substr($value, 0, -2);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function writeCommand(CommandInterface $command)
    +    {
    +        $this->protocol->write($this, $command);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function read()
    +    {
    +        return $this->protocol->read($this);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __sleep()
    +    {
    +        return array_diff(array_merge(parent::__sleep(), array('protocol')), array('mbiterable'));
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/ConnectionException.php b/vendor/predis/predis/lib/Predis/Connection/ConnectionException.php
    new file mode 100644
    index 0000000..ef2e9d7
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/ConnectionException.php
    @@ -0,0 +1,23 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use Predis\CommunicationException;
    +
    +/**
    + * Exception class that identifies connection-related errors.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ConnectionException extends CommunicationException
    +{
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/ConnectionFactory.php b/vendor/predis/predis/lib/Predis/Connection/ConnectionFactory.php
    new file mode 100644
    index 0000000..5527232
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/ConnectionFactory.php
    @@ -0,0 +1,181 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use Predis\Profile\ServerProfile;
    +use Predis\Profile\ServerProfileInterface;
    +
    +/**
    + * Provides a default factory for Redis connections that maps URI schemes
    + * to connection classes implementing Predis\Connection\SingleConnectionInterface.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ConnectionFactory implements ConnectionFactoryInterface
    +{
    +    protected $schemes;
    +    protected $profile;
    +
    +    /**
    +     * Initializes a new instance of the default connection factory class used by Predis.
    +     *
    +     * @param ServerProfileInterface $profile Server profile used to initialize new connections.
    +     */
    +    public function __construct(ServerProfileInterface $profile = null)
    +    {
    +        $this->schemes = $this->getDefaultSchemes();
    +        $this->profile = $profile;
    +    }
    +
    +    /**
    +     * Returns a named array that maps URI schemes to connection classes.
    +     *
    +     * @return array Map of URI schemes and connection classes.
    +     */
    +    protected function getDefaultSchemes()
    +    {
    +        return array(
    +            'tcp'  => 'Predis\Connection\StreamConnection',
    +            'unix' => 'Predis\Connection\StreamConnection',
    +            'http' => 'Predis\Connection\WebdisConnection',
    +        );
    +    }
    +
    +    /**
    +     * Checks if the provided argument represents a valid connection class
    +     * implementing Predis\Connection\SingleConnectionInterface. Optionally,
    +     * callable objects are used for lazy initialization of connection objects.
    +     *
    +     * @param mixed $initializer FQN of a connection class or a callable for lazy initialization.
    +     * @return mixed
    +     */
    +    protected function checkInitializer($initializer)
    +    {
    +        if (is_callable($initializer)) {
    +            return $initializer;
    +        }
    +
    +        $initializerReflection = new \ReflectionClass($initializer);
    +
    +        if (!$initializerReflection->isSubclassOf('Predis\Connection\SingleConnectionInterface')) {
    +            throw new \InvalidArgumentException(
    +                'A connection initializer must be a valid connection class or a callable object'
    +            );
    +        }
    +
    +        return $initializer;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function define($scheme, $initializer)
    +    {
    +        $this->schemes[$scheme] = $this->checkInitializer($initializer);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function undefine($scheme)
    +    {
    +        unset($this->schemes[$scheme]);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function create($parameters)
    +    {
    +        if (!$parameters instanceof ConnectionParametersInterface) {
    +            $parameters = new ConnectionParameters($parameters ?: array());
    +        }
    +
    +        $scheme = $parameters->scheme;
    +
    +        if (!isset($this->schemes[$scheme])) {
    +            throw new \InvalidArgumentException("Unknown connection scheme: $scheme");
    +        }
    +
    +        $initializer = $this->schemes[$scheme];
    +
    +        if (is_callable($initializer)) {
    +            $connection = call_user_func($initializer, $parameters, $this);
    +        } else {
    +            $connection = new $initializer($parameters);
    +            $this->prepareConnection($connection);
    +        }
    +
    +        if (!$connection instanceof SingleConnectionInterface) {
    +            throw new \InvalidArgumentException(
    +                'Objects returned by connection initializers must implement ' .
    +                'Predis\Connection\SingleConnectionInterface'
    +            );
    +        }
    +
    +        return $connection;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function createAggregated(AggregatedConnectionInterface $connection, Array $parameters)
    +    {
    +        foreach ($parameters as $node) {
    +            $connection->add($node instanceof SingleConnectionInterface ? $node : $this->create($node));
    +        }
    +
    +        return $connection;
    +    }
    +
    +    /**
    +     * Prepares a connection object after its initialization.
    +     *
    +     * @param SingleConnectionInterface $connection Instance of a connection object.
    +     */
    +    protected function prepareConnection(SingleConnectionInterface $connection)
    +    {
    +        if (isset($this->profile)) {
    +            $parameters = $connection->getParameters();
    +
    +            if (isset($parameters->password)) {
    +                $command = $this->profile->createCommand('auth', array($parameters->password));
    +                $connection->pushInitCommand($command);
    +            }
    +
    +            if (isset($parameters->database)) {
    +                $command = $this->profile->createCommand('select', array($parameters->database));
    +                $connection->pushInitCommand($command);
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Sets the server profile used to create initialization commands for connections.
    +     *
    +     * @param ServerProfileInterface $profile Server profile instance.
    +     */
    +    public function setProfile(ServerProfileInterface $profile)
    +    {
    +        $this->profile = $profile;
    +    }
    +
    +    /**
    +     * Returns the server profile used to create initialization commands for connections.
    +     *
    +     * @return ServerProfileInterface
    +     */
    +    public function getProfile()
    +    {
    +        return $this->profile;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/ConnectionFactoryInterface.php b/vendor/predis/predis/lib/Predis/Connection/ConnectionFactoryInterface.php
    new file mode 100644
    index 0000000..0ac6be7
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/ConnectionFactoryInterface.php
    @@ -0,0 +1,53 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +/**
    + * Interface that must be implemented by classes that provide their own mechanism
    + * to create and initialize new instances of Predis\Connection\SingleConnectionInterface.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface ConnectionFactoryInterface
    +{
    +    /**
    +     * Defines or overrides the connection class identified by a scheme prefix.
    +     *
    +     * @param string $scheme URI scheme identifying the connection class.
    +     * @param mixed $initializer FQN of a connection class or a callable object for lazy initialization.
    +     */
    +    public function define($scheme, $initializer);
    +
    +    /**
    +     * Undefines the connection identified by a scheme prefix.
    +     *
    +     * @param string $scheme Parameters for the connection.
    +     */
    +    public function undefine($scheme);
    +
    +    /**
    +     * Creates a new connection object.
    +     *
    +     * @param mixed $parameters Parameters for the connection.
    +     * @return SingleConnectionInterface
    +     */
    +    public function create($parameters);
    +
    +    /**
    +     * Prepares an aggregation of connection objects.
    +     *
    +     * @param AggregatedConnectionInterface $cluster Instance of an aggregated connection class.
    +     * @param array $parameters List of parameters for each connection object.
    +     * @return AggregatedConnectionInterface
    +     */
    +    public function createAggregated(AggregatedConnectionInterface $cluster, Array $parameters);
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/ConnectionInterface.php b/vendor/predis/predis/lib/Predis/Connection/ConnectionInterface.php
    new file mode 100644
    index 0000000..b30de09
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/ConnectionInterface.php
    @@ -0,0 +1,63 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use Predis\Command\CommandInterface;
    +
    +/**
    + * Defines a connection object used to communicate with one or multiple
    + * Redis servers.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface ConnectionInterface
    +{
    +    /**
    +     * Opens the connection.
    +     */
    +    public function connect();
    +
    +    /**
    +     * Closes the connection.
    +     */
    +    public function disconnect();
    +
    +    /**
    +     * Returns if the connection is open.
    +     *
    +     * @return Boolean
    +     */
    +    public function isConnected();
    +
    +    /**
    +     * Write a Redis command on the connection.
    +     *
    +     * @param CommandInterface $command Instance of a Redis command.
    +     */
    +    public function writeCommand(CommandInterface $command);
    +
    +    /**
    +     * Reads the reply for a Redis command from the connection.
    +     *
    +     * @param CommandInterface $command Instance of a Redis command.
    +     * @return mixed
    +     */
    +    public function readResponse(CommandInterface $command);
    +
    +    /**
    +     * Writes a Redis command to the connection and reads back the reply.
    +     *
    +     * @param CommandInterface $command Instance of a Redis command.
    +     * @return mixed
    +     */
    +    public function executeCommand(CommandInterface $command);
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/ConnectionParameters.php b/vendor/predis/predis/lib/Predis/Connection/ConnectionParameters.php
    new file mode 100644
    index 0000000..2127843
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/ConnectionParameters.php
    @@ -0,0 +1,185 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use Predis\ClientException;
    +
    +/**
    + * Handles parsing and validation of connection parameters.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ConnectionParameters implements ConnectionParametersInterface
    +{
    +    private $parameters;
    +
    +    private static $defaults = array(
    +        'scheme' => 'tcp',
    +        'host' => '127.0.0.1',
    +        'port' => 6379,
    +        'timeout' => 5.0,
    +    );
    +
    +    /**
    +     * @param string|array Connection parameters in the form of an URI string or a named array.
    +     */
    +    public function __construct($parameters = array())
    +    {
    +        if (!is_array($parameters)) {
    +            $parameters = self::parseURI($parameters);
    +        }
    +
    +        $this->parameters = $this->filter($parameters) + $this->getDefaults();
    +    }
    +
    +    /**
    +     * Returns some default parameters with their values.
    +     *
    +     * @return array
    +     */
    +    protected function getDefaults()
    +    {
    +        return self::$defaults;
    +    }
    +
    +    /**
    +     * Returns cast functions for user-supplied parameter values.
    +     *
    +     * @return array
    +     */
    +    protected function getValueCasters()
    +    {
    +        return array(
    +            'port' => 'self::castInteger',
    +            'async_connect' => 'self::castBoolean',
    +            'persistent' => 'self::castBoolean',
    +            'timeout' => 'self::castFloat',
    +            'read_write_timeout' => 'self::castFloat',
    +            'iterable_multibulk' => 'self::castBoolean',
    +        );
    +    }
    +
    +    /**
    +     * Validates value as boolean.
    +     *
    +     * @param mixed $value Input value.
    +     * @return boolean
    +     */
    +    private static function castBoolean($value)
    +    {
    +        return (bool) $value;
    +    }
    +
    +    /**
    +     * Validates value as float.
    +     *
    +     * @param mixed $value Input value.
    +     * @return float
    +     */
    +    private static function castFloat($value)
    +    {
    +        return (float) $value;
    +    }
    +
    +    /**
    +     * Validates value as integer.
    +     *
    +     * @param mixed $value Input value.
    +     * @return int
    +     */
    +    private static function castInteger($value)
    +    {
    +        return (int) $value;
    +    }
    +
    +    /**
    +     * Parses an URI string and returns an array of connection parameters.
    +     *
    +     * @param string $uri Connection string.
    +     * @return array
    +     */
    +    public static function parseURI($uri)
    +    {
    +        if (stripos($uri, 'unix') === 0) {
    +            // Hack to support URIs for UNIX sockets with minimal effort.
    +            $uri = str_ireplace('unix:///', 'unix://localhost/', $uri);
    +        }
    +
    +        if (!($parsed = @parse_url($uri)) || !isset($parsed['host'])) {
    +            throw new ClientException("Invalid URI: $uri");
    +        }
    +
    +        if (isset($parsed['query'])) {
    +            foreach (explode('&', $parsed['query']) as $kv) {
    +                @list($k, $v) = explode('=', $kv);
    +                $parsed[$k] = $v;
    +            }
    +
    +            unset($parsed['query']);
    +        }
    +
    +        return $parsed;
    +    }
    +
    +    /**
    +     * Validates and converts each value of the connection parameters array.
    +     *
    +     * @param array $parameters Connection parameters.
    +     * @return array
    +     */
    +    private function filter(Array $parameters)
    +    {
    +        if ($parameters) {
    +            $casters = array_intersect_key($this->getValueCasters(), $parameters);
    +
    +            foreach ($casters as $parameter => $caster) {
    +                $parameters[$parameter] = call_user_func($caster, $parameters[$parameter]);
    +            }
    +        }
    +
    +        return $parameters;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __get($parameter)
    +    {
    +        if (isset($this->{$parameter})) {
    +            return $this->parameters[$parameter];
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __isset($parameter)
    +    {
    +        return isset($this->parameters[$parameter]);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function toArray()
    +    {
    +        return $this->parameters;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __sleep()
    +    {
    +        return array('parameters');
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/ConnectionParametersInterface.php b/vendor/predis/predis/lib/Predis/Connection/ConnectionParametersInterface.php
    new file mode 100644
    index 0000000..3e3cb38
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/ConnectionParametersInterface.php
    @@ -0,0 +1,44 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +/**
    + * Interface that must be implemented by classes that provide their own mechanism
    + * to parse and handle connection parameters.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface ConnectionParametersInterface
    +{
    +    /**
    +     * Checks if the specified parameters is set.
    +     *
    +     * @param string $property Name of the property.
    +     * @return Boolean
    +     */
    +    public function __isset($parameter);
    +
    +    /**
    +     * Returns the value of the specified parameter.
    +     *
    +     * @param string $parameter Name of the parameter.
    +     * @return mixed
    +     */
    +    public function __get($parameter);
    +
    +    /**
    +     * Returns an array representation of the connection parameters.
    +     *
    +     * @return array
    +     */
    +    public function toArray();
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/MasterSlaveReplication.php b/vendor/predis/predis/lib/Predis/Connection/MasterSlaveReplication.php
    new file mode 100644
    index 0000000..91c8de4
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/MasterSlaveReplication.php
    @@ -0,0 +1,261 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use Predis\Command\CommandInterface;
    +use Predis\Replication\ReplicationStrategy;
    +
    +/**
    + * Aggregated connection class used by to handle replication with a
    + * group of servers in a master/slave configuration.
    + *
    + * @author Daniele Alessandri 
    + */
    +class MasterSlaveReplication implements ReplicationConnectionInterface
    +{
    +    protected $strategy;
    +    protected $master;
    +    protected $slaves;
    +    protected $current;
    +
    +    /**
    +     *
    +     */
    +    public function __construct(ReplicationStrategy $strategy = null)
    +    {
    +        $this->slaves = array();
    +        $this->strategy = $strategy ?: new ReplicationStrategy();
    +    }
    +
    +    /**
    +     * Checks if one master and at least one slave have been defined.
    +     */
    +    protected function check()
    +    {
    +        if (!isset($this->master) || !$this->slaves) {
    +            throw new \RuntimeException('Replication needs a master and at least one slave.');
    +        }
    +    }
    +
    +    /**
    +     * Resets the connection state.
    +     */
    +    protected function reset()
    +    {
    +        $this->current = null;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function add(SingleConnectionInterface $connection)
    +    {
    +        $alias = $connection->getParameters()->alias;
    +
    +        if ($alias === 'master') {
    +            $this->master = $connection;
    +        } else {
    +            $this->slaves[$alias ?: count($this->slaves)] = $connection;
    +        }
    +
    +        $this->reset();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function remove(SingleConnectionInterface $connection)
    +    {
    +        if ($connection->getParameters()->alias === 'master') {
    +            $this->master = null;
    +            $this->reset();
    +
    +            return true;
    +        } else {
    +            if (($id = array_search($connection, $this->slaves, true)) !== false) {
    +                unset($this->slaves[$id]);
    +                $this->reset();
    +
    +                return true;
    +            }
    +        }
    +
    +        return false;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getConnection(CommandInterface $command)
    +    {
    +        if ($this->current === null) {
    +            $this->check();
    +            $this->current = $this->strategy->isReadOperation($command) ? $this->pickSlave() : $this->master;
    +
    +            return $this->current;
    +        }
    +
    +        if ($this->current === $this->master) {
    +            return $this->current;
    +        }
    +
    +        if (!$this->strategy->isReadOperation($command)) {
    +            $this->current = $this->master;
    +        }
    +
    +        return $this->current;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getConnectionById($connectionId)
    +    {
    +        if ($connectionId === 'master') {
    +            return $this->master;
    +        }
    +
    +        if (isset($this->slaves[$connectionId])) {
    +            return $this->slaves[$connectionId];
    +        }
    +
    +        return null;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function switchTo($connection)
    +    {
    +        $this->check();
    +
    +        if (!$connection instanceof SingleConnectionInterface) {
    +            $connection = $this->getConnectionById($connection);
    +        }
    +        if ($connection !== $this->master && !in_array($connection, $this->slaves, true)) {
    +            throw new \InvalidArgumentException('The specified connection is not valid.');
    +        }
    +
    +        $this->current = $connection;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getCurrent()
    +    {
    +        return $this->current;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getMaster()
    +    {
    +        return $this->master;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getSlaves()
    +    {
    +        return array_values($this->slaves);
    +    }
    +
    +    /**
    +     * Returns the underlying replication strategy.
    +     *
    +     * @return ReplicationStrategy
    +     */
    +    public function getReplicationStrategy()
    +    {
    +        return $this->strategy;
    +    }
    +
    +    /**
    +     * Returns a random slave.
    +     *
    +     * @return SingleConnectionInterface
    +     */
    +    protected function pickSlave()
    +    {
    +        return $this->slaves[array_rand($this->slaves)];
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function isConnected()
    +    {
    +        return $this->current ? $this->current->isConnected() : false;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function connect()
    +    {
    +        if ($this->current === null) {
    +            $this->check();
    +            $this->current = $this->pickSlave();
    +        }
    +
    +        $this->current->connect();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function disconnect()
    +    {
    +        if ($this->master) {
    +            $this->master->disconnect();
    +        }
    +
    +        foreach ($this->slaves as $connection) {
    +            $connection->disconnect();
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function writeCommand(CommandInterface $command)
    +    {
    +        $this->getConnection($command)->writeCommand($command);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function readResponse(CommandInterface $command)
    +    {
    +        return $this->getConnection($command)->readResponse($command);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function executeCommand(CommandInterface $command)
    +    {
    +        return $this->getConnection($command)->executeCommand($command);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __sleep()
    +    {
    +        return array('master', 'slaves', 'strategy');
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/PhpiredisConnection.php b/vendor/predis/predis/lib/Predis/Connection/PhpiredisConnection.php
    new file mode 100644
    index 0000000..ac6ff40
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/PhpiredisConnection.php
    @@ -0,0 +1,389 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use Predis\NotSupportedException;
    +use Predis\ResponseError;
    +use Predis\ResponseQueued;
    +use Predis\Command\CommandInterface;
    +
    +/**
    + * This class provides the implementation of a Predis connection that uses the
    + * PHP socket extension for network communication and wraps the phpiredis C
    + * extension (PHP bindings for hiredis) to parse the Redis protocol. Everything
    + * is highly experimental (even the very same phpiredis since it is quite new),
    + * so use it at your own risk.
    + *
    + * This class is mainly intended to provide an optional low-overhead alternative
    + * for processing replies from Redis compared to the standard pure-PHP classes.
    + * Differences in speed when dealing with short inline replies are practically
    + * nonexistent, the actual speed boost is for long multibulk replies when this
    + * protocol processor can parse and return replies very fast.
    + *
    + * For instructions on how to build and install the phpiredis extension, please
    + * consult the repository of the project.
    + *
    + * The connection parameters supported by this class are:
    + *
    + *  - scheme: it can be either 'tcp' or 'unix'.
    + *  - host: hostname or IP address of the server.
    + *  - port: TCP port of the server.
    + *  - timeout: timeout to perform the connection.
    + *  - read_write_timeout: timeout of read / write operations.
    + *
    + * @link http://github.com/nrk/phpiredis
    + * @author Daniele Alessandri 
    + */
    +class PhpiredisConnection extends AbstractConnection
    +{
    +    const ERR_MSG_EXTENSION = 'The %s extension must be loaded in order to be able to use this connection class';
    +
    +    private $reader;
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __construct(ConnectionParametersInterface $parameters)
    +    {
    +        $this->checkExtensions();
    +        $this->initializeReader();
    +
    +        parent::__construct($parameters);
    +    }
    +
    +    /**
    +     * Disconnects from the server and destroys the underlying resource and the
    +     * protocol reader resource when PHP's garbage collector kicks in.
    +     */
    +    public function __destruct()
    +    {
    +        phpiredis_reader_destroy($this->reader);
    +
    +        parent::__destruct();
    +    }
    +
    +    /**
    +     * Checks if the socket and phpiredis extensions are loaded in PHP.
    +     */
    +    private function checkExtensions()
    +    {
    +        if (!function_exists('socket_create')) {
    +            throw new NotSupportedException(sprintf(self::ERR_MSG_EXTENSION, 'socket'));
    +        }
    +        if (!function_exists('phpiredis_reader_create')) {
    +            throw new NotSupportedException(sprintf(self::ERR_MSG_EXTENSION, 'phpiredis'));
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function checkParameters(ConnectionParametersInterface $parameters)
    +    {
    +        if (isset($parameters->iterable_multibulk)) {
    +            $this->onInvalidOption('iterable_multibulk', $parameters);
    +        }
    +        if (isset($parameters->persistent)) {
    +            $this->onInvalidOption('persistent', $parameters);
    +        }
    +
    +        return parent::checkParameters($parameters);
    +    }
    +
    +    /**
    +     * Initializes the protocol reader resource.
    +     */
    +    private function initializeReader()
    +    {
    +        $reader = phpiredis_reader_create();
    +
    +        phpiredis_reader_set_status_handler($reader, $this->getStatusHandler());
    +        phpiredis_reader_set_error_handler($reader, $this->getErrorHandler());
    +
    +        $this->reader = $reader;
    +    }
    +
    +    /**
    +     * Gets the handler used by the protocol reader to handle status replies.
    +     *
    +     * @return \Closure
    +     */
    +    private function getStatusHandler()
    +    {
    +        return function ($payload) {
    +            switch ($payload) {
    +                case 'OK':
    +                    return true;
    +
    +                case 'QUEUED':
    +                    return new ResponseQueued();
    +
    +                default:
    +                    return $payload;
    +            }
    +        };
    +    }
    +
    +    /**
    +     * Gets the handler used by the protocol reader to handle Redis errors.
    +     *
    +     * @param Boolean $throw_errors Specify if Redis errors throw exceptions.
    +     * @return \Closure
    +     */
    +    private function getErrorHandler()
    +    {
    +        return function ($errorMessage) {
    +            return new ResponseError($errorMessage);
    +        };
    +    }
    +
    +    /**
    +     * Helper method used to throw exceptions on socket errors.
    +     */
    +    private function emitSocketError()
    +    {
    +        $errno  = socket_last_error();
    +        $errstr = socket_strerror($errno);
    +
    +        $this->disconnect();
    +
    +        $this->onConnectionError(trim($errstr), $errno);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function createResource()
    +    {
    +        $parameters = $this->parameters;
    +
    +        $isUnix = $this->parameters->scheme === 'unix';
    +        $domain = $isUnix ? AF_UNIX : AF_INET;
    +        $protocol = $isUnix ? 0 : SOL_TCP;
    +
    +        $socket = @call_user_func('socket_create', $domain, SOCK_STREAM, $protocol);
    +        if (!is_resource($socket)) {
    +            $this->emitSocketError();
    +        }
    +
    +        $this->setSocketOptions($socket, $parameters);
    +
    +        return $socket;
    +    }
    +
    +    /**
    +     * Sets options on the socket resource from the connection parameters.
    +     *
    +     * @param resource $socket Socket resource.
    +     * @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
    +     */
    +    private function setSocketOptions($socket, ConnectionParametersInterface $parameters)
    +    {
    +        if ($parameters->scheme !== 'tcp') {
    +            return;
    +        }
    +
    +        if (!socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1)) {
    +            $this->emitSocketError();
    +        }
    +
    +        if (!socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1)) {
    +            $this->emitSocketError();
    +        }
    +
    +        if (isset($parameters->read_write_timeout)) {
    +            $rwtimeout = $parameters->read_write_timeout;
    +            $timeoutSec = floor($rwtimeout);
    +            $timeoutUsec = ($rwtimeout - $timeoutSec) * 1000000;
    +
    +            $timeout = array(
    +                'sec' => $timeoutSec,
    +                'usec' => $timeoutUsec,
    +            );
    +
    +            if (!socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, $timeout)) {
    +                $this->emitSocketError();
    +            }
    +
    +            if (!socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout)) {
    +                $this->emitSocketError();
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Gets the address from the connection parameters.
    +     *
    +     * @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
    +     * @return string
    +     */
    +    private function getAddress(ConnectionParametersInterface $parameters)
    +    {
    +        if ($parameters->scheme === 'unix') {
    +            return $parameters->path;
    +        }
    +
    +        $host = $parameters->host;
    +
    +        if (ip2long($host) === false) {
    +            if (($addresses = gethostbynamel($host)) === false) {
    +                $this->onConnectionError("Cannot resolve the address of $host");
    +            }
    +            return $addresses[array_rand($addresses)];
    +        }
    +
    +        return $host;
    +    }
    +
    +    /**
    +     * Opens the actual connection to the server with a timeout.
    +     *
    +     * @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
    +     * @return string
    +     */
    +    private function connectWithTimeout(ConnectionParametersInterface $parameters)
    +    {
    +        $host = self::getAddress($parameters);
    +        $socket = $this->getResource();
    +
    +        socket_set_nonblock($socket);
    +
    +        if (@socket_connect($socket, $host, $parameters->port) === false) {
    +            $error = socket_last_error();
    +            if ($error != SOCKET_EINPROGRESS && $error != SOCKET_EALREADY) {
    +                $this->emitSocketError();
    +            }
    +        }
    +
    +        socket_set_block($socket);
    +
    +        $null = null;
    +        $selectable = array($socket);
    +
    +        $timeout = $parameters->timeout;
    +        $timeoutSecs = floor($timeout);
    +        $timeoutUSecs = ($timeout - $timeoutSecs) * 1000000;
    +
    +        $selected = socket_select($selectable, $selectable, $null, $timeoutSecs, $timeoutUSecs);
    +
    +        if ($selected === 2) {
    +            $this->onConnectionError('Connection refused', SOCKET_ECONNREFUSED);
    +        }
    +        if ($selected === 0) {
    +            $this->onConnectionError('Connection timed out', SOCKET_ETIMEDOUT);
    +        }
    +        if ($selected === false) {
    +            $this->emitSocketError();
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function connect()
    +    {
    +        parent::connect();
    +
    +        $this->connectWithTimeout($this->parameters);
    +
    +        if ($this->initCmds) {
    +            $this->sendInitializationCommands();
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function disconnect()
    +    {
    +        if ($this->isConnected()) {
    +            socket_close($this->getResource());
    +            parent::disconnect();
    +        }
    +    }
    +
    +    /**
    +     * Sends the initialization commands to Redis when the connection is opened.
    +     */
    +    private function sendInitializationCommands()
    +    {
    +        foreach ($this->initCmds as $command) {
    +            $this->writeCommand($command);
    +        }
    +        foreach ($this->initCmds as $command) {
    +            $this->readResponse($command);
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function write($buffer)
    +    {
    +        $socket = $this->getResource();
    +
    +        while (($length = strlen($buffer)) > 0) {
    +            $written = socket_write($socket, $buffer, $length);
    +
    +            if ($length === $written) {
    +                return;
    +            }
    +            if ($written === false) {
    +                $this->onConnectionError('Error while writing bytes to the server');
    +            }
    +
    +            $buffer = substr($buffer, $written);
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function read()
    +    {
    +        $socket = $this->getResource();
    +        $reader = $this->reader;
    +
    +        while (($state = phpiredis_reader_get_state($reader)) === PHPIREDIS_READER_STATE_INCOMPLETE) {
    +            if (@socket_recv($socket, $buffer, 4096, 0) === false || $buffer === '') {
    +                $this->emitSocketError();
    +            }
    +
    +            phpiredis_reader_feed($reader, $buffer);
    +        }
    +
    +        if ($state === PHPIREDIS_READER_STATE_COMPLETE) {
    +            return phpiredis_reader_get_reply($reader);
    +        } else {
    +            $this->onProtocolError(phpiredis_reader_get_error($reader));
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function writeCommand(CommandInterface $command)
    +    {
    +        $cmdargs = $command->getArguments();
    +        array_unshift($cmdargs, $command->getId());
    +        $this->write(phpiredis_format_command($cmdargs));
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __wakeup()
    +    {
    +        $this->checkExtensions();
    +        $this->initializeReader();
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/PhpiredisStreamConnection.php b/vendor/predis/predis/lib/Predis/Connection/PhpiredisStreamConnection.php
    new file mode 100644
    index 0000000..a7df197
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/PhpiredisStreamConnection.php
    @@ -0,0 +1,197 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use Predis\NotSupportedException;
    +use Predis\ResponseError;
    +use Predis\ResponseQueued;
    +use Predis\Command\CommandInterface;
    +
    +/**
    + * This class provides the implementation of a Predis connection that uses PHP's
    + * streams for network communication and wraps the phpiredis C extension (PHP
    + * bindings for hiredis) to parse and serialize the Redis protocol. Everything
    + * is highly experimental (even the very same phpiredis since it is quite new),
    + * so use it at your own risk.
    + *
    + * This class is mainly intended to provide an optional low-overhead alternative
    + * for processing replies from Redis compared to the standard pure-PHP classes.
    + * Differences in speed when dealing with short inline replies are practically
    + * nonexistent, the actual speed boost is for long multibulk replies when this
    + * protocol processor can parse and return replies very fast.
    + *
    + * For instructions on how to build and install the phpiredis extension, please
    + * consult the repository of the project.
    + *
    + * The connection parameters supported by this class are:
    + *
    + *  - scheme: it can be either 'tcp' or 'unix'.
    + *  - host: hostname or IP address of the server.
    + *  - port: TCP port of the server.
    + *  - timeout: timeout to perform the connection.
    + *  - read_write_timeout: timeout of read / write operations.
    + *  - async_connect: performs the connection asynchronously.
    + *  - tcp_nodelay: enables or disables Nagle's algorithm for coalescing.
    + *  - persistent: the connection is left intact after a GC collection.
    + *
    + * @link https://github.com/nrk/phpiredis
    + * @author Daniele Alessandri 
    + */
    +class PhpiredisStreamConnection extends StreamConnection
    +{
    +    private $reader;
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __construct(ConnectionParametersInterface $parameters)
    +    {
    +        $this->checkExtensions();
    +        $this->initializeReader();
    +
    +        parent::__construct($parameters);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __destruct()
    +    {
    +        phpiredis_reader_destroy($this->reader);
    +
    +        parent::__destruct();
    +    }
    +
    +    /**
    +     * Checks if the phpiredis extension is loaded in PHP.
    +     */
    +    protected function checkExtensions()
    +    {
    +        if (!function_exists('phpiredis_reader_create')) {
    +            throw new NotSupportedException(
    +                'The phpiredis extension must be loaded in order to be able to use this connection class'
    +            );
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function checkParameters(ConnectionParametersInterface $parameters)
    +    {
    +        if (isset($parameters->iterable_multibulk)) {
    +            $this->onInvalidOption('iterable_multibulk', $parameters);
    +        }
    +
    +        return parent::checkParameters($parameters);
    +    }
    +
    +    /**
    +     * Initializes the protocol reader resource.
    +     */
    +    protected function initializeReader()
    +    {
    +        $reader = phpiredis_reader_create();
    +
    +        phpiredis_reader_set_status_handler($reader, $this->getStatusHandler());
    +        phpiredis_reader_set_error_handler($reader, $this->getErrorHandler());
    +
    +        $this->reader = $reader;
    +    }
    +
    +    /**
    +     * Gets the handler used by the protocol reader to handle status replies.
    +     *
    +     * @return \Closure
    +     */
    +    protected function getStatusHandler()
    +    {
    +        return function ($payload) {
    +            switch ($payload) {
    +                case 'OK':
    +                    return true;
    +
    +                case 'QUEUED':
    +                    return new ResponseQueued();
    +
    +                default:
    +                    return $payload;
    +            }
    +        };
    +    }
    +
    +    /**
    +     * Gets the handler used by the protocol reader to handle Redis errors.
    +     *
    +     * @param Boolean $throw_errors Specify if Redis errors throw exceptions.
    +     * @return \Closure
    +     */
    +    protected function getErrorHandler()
    +    {
    +        return function ($errorMessage) {
    +            return new ResponseError($errorMessage);
    +        };
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function read()
    +    {
    +        $socket = $this->getResource();
    +        $reader = $this->reader;
    +
    +        while (PHPIREDIS_READER_STATE_INCOMPLETE === $state = phpiredis_reader_get_state($reader)) {
    +            $buffer = fread($socket, 4096);
    +
    +            if ($buffer === false || $buffer === '') {
    +                $this->onConnectionError('Error while reading bytes from the server');
    +                return;
    +            }
    +
    +            phpiredis_reader_feed($reader, $buffer);
    +        }
    +
    +        if ($state === PHPIREDIS_READER_STATE_COMPLETE) {
    +            return phpiredis_reader_get_reply($reader);
    +        } else {
    +            $this->onProtocolError(phpiredis_reader_get_error($reader));
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function writeCommand(CommandInterface $command)
    +    {
    +        $cmdargs = $command->getArguments();
    +        array_unshift($cmdargs, $command->getId());
    +        $this->writeBytes(phpiredis_format_command($cmdargs));
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __sleep()
    +    {
    +        return array_diff(parent::__sleep(), array('mbiterable'));
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __wakeup()
    +    {
    +        $this->checkExtensions();
    +        $this->initializeReader();
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/PredisCluster.php b/vendor/predis/predis/lib/Predis/Connection/PredisCluster.php
    new file mode 100644
    index 0000000..1290bce
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/PredisCluster.php
    @@ -0,0 +1,232 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use Predis\Cluster\CommandHashStrategyInterface;
    +use Predis\NotSupportedException;
    +use Predis\Cluster\PredisClusterHashStrategy;
    +use Predis\Cluster\Distribution\DistributionStrategyInterface;
    +use Predis\Cluster\Distribution\HashRing;
    +use Predis\Command\CommandInterface;
    +
    +/**
    + * Abstraction for a cluster of aggregated connections to various Redis servers
    + * implementing client-side sharding based on pluggable distribution strategies.
    + *
    + * @author Daniele Alessandri 
    + * @todo Add the ability to remove connections from pool.
    + */
    +class PredisCluster implements ClusterConnectionInterface, \IteratorAggregate, \Countable
    +{
    +    private $pool;
    +    private $strategy;
    +    private $distributor;
    +
    +    /**
    +     * @param DistributionStrategyInterface $distributor Distribution strategy used by the cluster.
    +     */
    +    public function __construct(DistributionStrategyInterface $distributor = null)
    +    {
    +        $distributor = $distributor ?: new HashRing();
    +
    +        $this->pool = array();
    +        $this->strategy = new PredisClusterHashStrategy($distributor->getHashGenerator());
    +        $this->distributor = $distributor;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function isConnected()
    +    {
    +        foreach ($this->pool as $connection) {
    +            if ($connection->isConnected()) {
    +                return true;
    +            }
    +        }
    +
    +        return false;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function connect()
    +    {
    +        foreach ($this->pool as $connection) {
    +            $connection->connect();
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function disconnect()
    +    {
    +        foreach ($this->pool as $connection) {
    +            $connection->disconnect();
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function add(SingleConnectionInterface $connection)
    +    {
    +        $parameters = $connection->getParameters();
    +
    +        if (isset($parameters->alias)) {
    +            $this->pool[$parameters->alias] = $connection;
    +        } else {
    +            $this->pool[] = $connection;
    +        }
    +
    +        $weight = isset($parameters->weight) ? $parameters->weight : null;
    +        $this->distributor->add($connection, $weight);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function remove(SingleConnectionInterface $connection)
    +    {
    +        if (($id = array_search($connection, $this->pool, true)) !== false) {
    +            unset($this->pool[$id]);
    +            $this->distributor->remove($connection);
    +
    +            return true;
    +        }
    +
    +        return false;
    +    }
    +
    +    /**
    +     * Removes a connection instance using its alias or index.
    +     *
    +     * @param string $connectionId Alias or index of a connection.
    +     * @return Boolean Returns true if the connection was in the pool.
    +     */
    +    public function removeById($connectionId)
    +    {
    +        if ($connection = $this->getConnectionById($connectionId)) {
    +            return $this->remove($connection);
    +        }
    +
    +        return false;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getConnection(CommandInterface $command)
    +    {
    +        $hash = $this->strategy->getHash($command);
    +
    +        if (!isset($hash)) {
    +            throw new NotSupportedException("Cannot use {$command->getId()} with a cluster of connections");
    +        }
    +
    +        $node = $this->distributor->get($hash);
    +
    +        return $node;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getConnectionById($connectionId)
    +    {
    +        return isset($this->pool[$connectionId]) ? $this->pool[$connectionId] : null;
    +    }
    +
    +    /**
    +     * Retrieves a connection instance from the cluster using a key.
    +     *
    +     * @param string $key Key of a Redis value.
    +     * @return SingleConnectionInterface
    +     */
    +    public function getConnectionByKey($key)
    +    {
    +        $hash = $this->strategy->getKeyHash($key);
    +        $node = $this->distributor->get($hash);
    +
    +        return $node;
    +    }
    +
    +    /**
    +     * Returns the underlying command hash strategy used to hash
    +     * commands by their keys.
    +     *
    +     * @return CommandHashStrategyInterface
    +     */
    +    public function getCommandHashStrategy()
    +    {
    +        return $this->strategy;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function count()
    +    {
    +        return count($this->pool);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getIterator()
    +    {
    +        return new \ArrayIterator($this->pool);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function writeCommand(CommandInterface $command)
    +    {
    +        $this->getConnection($command)->writeCommand($command);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function readResponse(CommandInterface $command)
    +    {
    +        return $this->getConnection($command)->readResponse($command);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function executeCommand(CommandInterface $command)
    +    {
    +        return $this->getConnection($command)->executeCommand($command);
    +    }
    +
    +    /**
    +     * Executes the specified Redis command on all the nodes of a cluster.
    +     *
    +     * @param CommandInterface $command A Redis command.
    +     * @return array
    +     */
    +    public function executeCommandOnNodes(CommandInterface $command)
    +    {
    +        $replies = array();
    +
    +        foreach ($this->pool as $connection) {
    +            $replies[] = $connection->executeCommand($command);
    +        }
    +
    +        return $replies;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/RedisCluster.php b/vendor/predis/predis/lib/Predis/Connection/RedisCluster.php
    new file mode 100644
    index 0000000..7be4562
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/RedisCluster.php
    @@ -0,0 +1,384 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use Predis\ClientException;
    +use Predis\Cluster\CommandHashStrategyInterface;
    +use Predis\NotSupportedException;
    +use Predis\ResponseErrorInterface;
    +use Predis\Cluster\RedisClusterHashStrategy;
    +use Predis\Command\CommandInterface;
    +
    +/**
    + * Abstraction for Redis cluster (Redis v3.0).
    + *
    + * @author Daniele Alessandri 
    + */
    +class RedisCluster implements ClusterConnectionInterface, \IteratorAggregate, \Countable
    +{
    +    private $pool;
    +    private $slots;
    +    private $slotsMap;
    +    private $slotsPerNode;
    +    private $strategy;
    +    private $connections;
    +
    +    /**
    +     * @param ConnectionFactoryInterface $connections Connection factory object.
    +     */
    +    public function __construct(ConnectionFactoryInterface $connections = null)
    +    {
    +        $this->pool = array();
    +        $this->slots = array();
    +        $this->strategy = new RedisClusterHashStrategy();
    +        $this->connections = $connections ?: new ConnectionFactory();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function isConnected()
    +    {
    +        foreach ($this->pool as $connection) {
    +            if ($connection->isConnected()) {
    +                return true;
    +            }
    +        }
    +
    +        return false;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function connect()
    +    {
    +        foreach ($this->pool as $connection) {
    +            $connection->connect();
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function disconnect()
    +    {
    +        foreach ($this->pool as $connection) {
    +            $connection->disconnect();
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function add(SingleConnectionInterface $connection)
    +    {
    +        $this->pool[(string) $connection] = $connection;
    +        unset(
    +            $this->slotsMap,
    +            $this->slotsPerNode
    +        );
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function remove(SingleConnectionInterface $connection)
    +    {
    +        if (($id = array_search($connection, $this->pool, true)) !== false) {
    +            unset(
    +                $this->pool[$id],
    +                $this->slotsMap,
    +                $this->slotsPerNode
    +            );
    +
    +            return true;
    +        }
    +
    +        return false;
    +    }
    +
    +    /**
    +     * Removes a connection instance using its alias or index.
    +     *
    +     * @param string $connectionId Alias or index of a connection.
    +     * @return Boolean Returns true if the connection was in the pool.
    +     */
    +    public function removeById($connectionId)
    +    {
    +        if (isset($this->pool[$connectionId])) {
    +            unset(
    +                $this->pool[$connectionId],
    +                $this->slotsMap,
    +                $this->slotsPerNode
    +            );
    +
    +            return true;
    +        }
    +
    +        return false;
    +    }
    +
    +    /**
    +     * Builds the slots map for the cluster.
    +     *
    +     * @return array
    +     */
    +    public function buildSlotsMap()
    +    {
    +        $this->slotsMap = array();
    +        $this->slotsPerNode = (int) (16384 / count($this->pool));
    +
    +        foreach ($this->pool as $connectionID => $connection) {
    +            $parameters = $connection->getParameters();
    +
    +            if (!isset($parameters->slots)) {
    +                continue;
    +            }
    +
    +            list($first, $last) = explode('-', $parameters->slots, 2);
    +            $this->setSlots($first, $last, $connectionID);
    +        }
    +
    +        return $this->slotsMap;
    +    }
    +
    +    /**
    +     * Returns the current slots map for the cluster.
    +     *
    +     * @return array
    +     */
    +    public function getSlotsMap()
    +    {
    +        if (!isset($this->slotsMap)) {
    +            $this->slotsMap = array();
    +        }
    +
    +        return $this->slotsMap;
    +    }
    +
    +    /**
    +     * Preassociate a connection to a set of slots to avoid runtime guessing.
    +     *
    +     * @todo Check type or existence of the specified connection.
    +     * @todo Cluster loses the slots assigned with this methods when adding / removing connections.
    +     *
    +     * @param int $first Initial slot.
    +     * @param int $last Last slot.
    +     * @param SingleConnectionInterface|string $connection ID or connection instance.
    +     */
    +    public function setSlots($first, $last, $connection)
    +    {
    +        if ($first < 0x0000 || $first > 0x3FFF || $last < 0x0000 || $last > 0x3FFF || $last < $first) {
    +            throw new \OutOfBoundsException("Invalid slot values for $connection: [$first-$last]");
    +        }
    +
    +        $this->slotsMap = $this->getSlotsMap() + array_fill($first, $last - $first + 1, (string) $connection);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getConnection(CommandInterface $command)
    +    {
    +        $hash = $this->strategy->getHash($command);
    +
    +        if (!isset($hash)) {
    +            throw new NotSupportedException("Cannot use {$command->getId()} with redis-cluster");
    +        }
    +
    +        $slot = $hash & 0x3FFF;
    +
    +        if (isset($this->slots[$slot])) {
    +            return $this->slots[$slot];
    +        }
    +
    +        $this->slots[$slot] = $connection = $this->pool[$this->guessNode($slot)];
    +
    +        return $connection;
    +    }
    +
    +    /**
    +     * Returns the connection associated to the specified slot.
    +     *
    +     * @param int $slot Slot ID.
    +     * @return SingleConnectionInterface
    +     */
    +    public function getConnectionBySlot($slot)
    +    {
    +        if ($slot < 0x0000 || $slot > 0x3FFF) {
    +            throw new \OutOfBoundsException("Invalid slot value [$slot]");
    +        }
    +
    +        if (isset($this->slots[$slot])) {
    +            return $this->slots[$slot];
    +        }
    +
    +        return $this->pool[$this->guessNode($slot)];
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getConnectionById($connectionId)
    +    {
    +        return isset($this->pool[$connectionId]) ? $this->pool[$connectionId] : null;
    +    }
    +
    +    /**
    +     * Tries guessing the correct node associated to the given slot using a precalculated
    +     * slots map or the same logic used by redis-trib to initialize a redis cluster.
    +     *
    +     * @param int $slot Slot ID.
    +     * @return string
    +     */
    +    protected function guessNode($slot)
    +    {
    +        if (!isset($this->slotsMap)) {
    +            $this->buildSlotsMap();
    +        }
    +
    +        if (isset($this->slotsMap[$slot])) {
    +            return $this->slotsMap[$slot];
    +        }
    +
    +        $index = min((int) ($slot / $this->slotsPerNode), count($this->pool) - 1);
    +        $nodes = array_keys($this->pool);
    +
    +        return $nodes[$index];
    +    }
    +
    +    /**
    +     * Handles -MOVED or -ASK replies by re-executing the command on the server
    +     * specified by the Redis reply.
    +     *
    +     * @param CommandInterface $command Command that generated the -MOVE or -ASK reply.
    +     * @param string $request Type of request (either 'MOVED' or 'ASK').
    +     * @param string $details Parameters of the MOVED/ASK request.
    +     * @return mixed
    +     */
    +    protected function onMoveRequest(CommandInterface $command, $request, $details)
    +    {
    +        list($slot, $host) = explode(' ', $details, 2);
    +        $connection = $this->getConnectionById($host);
    +
    +        if (!isset($connection)) {
    +            $parameters = array('host' => null, 'port' => null);
    +            list($parameters['host'], $parameters['port']) = explode(':', $host, 2);
    +            $connection = $this->connections->create($parameters);
    +        }
    +
    +        switch ($request) {
    +            case 'MOVED':
    +                $this->move($connection, $slot);
    +                return $this->executeCommand($command);
    +
    +            case 'ASK':
    +                return $connection->executeCommand($command);
    +
    +            default:
    +                throw new ClientException("Unexpected request type for a move request: $request");
    +        }
    +    }
    +
    +    /**
    +     * Assign the connection instance to a new slot and adds it to the
    +     * pool if the connection was not already part of the pool.
    +     *
    +     * @param SingleConnectionInterface $connection Connection instance
    +     * @param int $slot Target slot.
    +     */
    +    protected function move(SingleConnectionInterface $connection, $slot)
    +    {
    +        $this->pool[(string) $connection] = $connection;
    +        $this->slots[(int) $slot] = $connection;
    +    }
    +
    +    /**
    +     * Returns the underlying command hash strategy used to hash
    +     * commands by their keys.
    +     *
    +     * @return CommandHashStrategyInterface
    +     */
    +    public function getCommandHashStrategy()
    +    {
    +        return $this->strategy;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function count()
    +    {
    +        return count($this->pool);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getIterator()
    +    {
    +        return new \ArrayIterator(array_values($this->pool));
    +    }
    +
    +    /**
    +     * Handles -ERR replies from Redis.
    +     *
    +     * @param CommandInterface $command Command that generated the -ERR reply.
    +     * @param ResponseErrorInterface $error Redis error reply object.
    +     * @return mixed
    +     */
    +    protected function handleServerError(CommandInterface $command, ResponseErrorInterface $error)
    +    {
    +        list($type, $details) = explode(' ', $error->getMessage(), 2);
    +
    +        switch ($type) {
    +            case 'MOVED':
    +            case 'ASK':
    +                return $this->onMoveRequest($command, $type, $details);
    +
    +            default:
    +                return $error;
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function writeCommand(CommandInterface $command)
    +    {
    +        $this->getConnection($command)->writeCommand($command);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function readResponse(CommandInterface $command)
    +    {
    +        return $this->getConnection($command)->readResponse($command);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function executeCommand(CommandInterface $command)
    +    {
    +        $connection = $this->getConnection($command);
    +        $reply = $connection->executeCommand($command);
    +
    +        if ($reply instanceof ResponseErrorInterface) {
    +            return $this->handleServerError($command, $reply);
    +        }
    +
    +        return $reply;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/ReplicationConnectionInterface.php b/vendor/predis/predis/lib/Predis/Connection/ReplicationConnectionInterface.php
    new file mode 100644
    index 0000000..7142dfa
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/ReplicationConnectionInterface.php
    @@ -0,0 +1,48 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +/**
    + * Defines a group of Redis servers in a master/slave replication configuration.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface ReplicationConnectionInterface extends AggregatedConnectionInterface
    +{
    +    /**
    +     * Switches the internal connection object being used.
    +     *
    +     * @param string $connection Alias of a connection
    +     */
    +    public function switchTo($connection);
    +
    +    /**
    +     * Retrieves the connection object currently being used.
    +     *
    +     * @return SingleConnectionInterface
    +     */
    +    public function getCurrent();
    +
    +    /**
    +     * Retrieves the connection object to the master Redis server.
    +     *
    +     * @return SingleConnectionInterface
    +     */
    +    public function getMaster();
    +
    +    /**
    +     * Retrieves a list of connection objects to slaves Redis servers.
    +     *
    +     * @return SingleConnectionInterface
    +     */
    +    public function getSlaves();
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/SingleConnectionInterface.php b/vendor/predis/predis/lib/Predis/Connection/SingleConnectionInterface.php
    new file mode 100644
    index 0000000..9b080c2
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/SingleConnectionInterface.php
    @@ -0,0 +1,58 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use Predis\Command\CommandInterface;
    +
    +/**
    + * Defines a connection object used to communicate with a single Redis server.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface SingleConnectionInterface extends ConnectionInterface
    +{
    +    /**
    +     * Returns a string representation of the connection.
    +     *
    +     * @return string
    +     */
    +    public function __toString();
    +
    +    /**
    +     * Returns the underlying resource used to communicate with a Redis server.
    +     *
    +     * @return mixed
    +     */
    +    public function getResource();
    +
    +    /**
    +     * Gets the parameters used to initialize the connection object.
    +     *
    +     * @return ConnectionParametersInterface
    +     */
    +    public function getParameters();
    +
    +    /**
    +     * Pushes the instance of a Redis command to the queue of commands executed
    +     * when the actual connection to a server is estabilished.
    +     *
    +     * @param CommandInterface $command Instance of a Redis command.
    +     */
    +    public function pushInitCommand(CommandInterface $command);
    +
    +    /**
    +     * Reads a reply from the server.
    +     *
    +     * @return mixed
    +     */
    +    public function read();
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/StreamConnection.php b/vendor/predis/predis/lib/Predis/Connection/StreamConnection.php
    new file mode 100644
    index 0000000..424bf67
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/StreamConnection.php
    @@ -0,0 +1,304 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use Predis\ResponseError;
    +use Predis\ResponseQueued;
    +use Predis\Command\CommandInterface;
    +use Predis\Iterator\MultiBulkResponseSimple;
    +
    +/**
    + * Standard connection to Redis servers implemented on top of PHP's streams.
    + * The connection parameters supported by this class are:
    + *
    + *  - scheme: it can be either 'tcp' or 'unix'.
    + *  - host: hostname or IP address of the server.
    + *  - port: TCP port of the server.
    + *  - timeout: timeout to perform the connection.
    + *  - read_write_timeout: timeout of read / write operations.
    + *  - async_connect: performs the connection asynchronously.
    + *  - tcp_nodelay: enables or disables Nagle's algorithm for coalescing.
    + *  - persistent: the connection is left intact after a GC collection.
    + *  - iterable_multibulk: multibulk replies treated as iterable objects.
    + *
    + * @author Daniele Alessandri 
    + */
    +class StreamConnection extends AbstractConnection
    +{
    +    private $mbiterable;
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __construct(ConnectionParametersInterface $parameters)
    +    {
    +        $this->mbiterable = (bool) $parameters->iterable_multibulk;
    +
    +        parent::__construct($parameters);
    +    }
    +
    +    /**
    +     * Disconnects from the server and destroys the underlying resource when
    +     * PHP's garbage collector kicks in only if the connection has not been
    +     * marked as persistent.
    +     */
    +    public function __destruct()
    +    {
    +        if (isset($this->parameters) && !$this->parameters->persistent) {
    +            $this->disconnect();
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function createResource()
    +    {
    +        $parameters = $this->parameters;
    +        $initializer = "{$parameters->scheme}StreamInitializer";
    +
    +        return $this->$initializer($parameters);
    +    }
    +
    +    /**
    +     * Initializes a TCP stream resource.
    +     *
    +     * @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
    +     * @return resource
    +     */
    +    private function tcpStreamInitializer(ConnectionParametersInterface $parameters)
    +    {
    +        $uri = "tcp://{$parameters->host}:{$parameters->port}/";
    +        $flags = STREAM_CLIENT_CONNECT;
    +
    +        if (isset($parameters->async_connect) && $parameters->async_connect) {
    +            $flags |= STREAM_CLIENT_ASYNC_CONNECT;
    +        }
    +        if (isset($parameters->persistent) && $parameters->persistent) {
    +            $flags |= STREAM_CLIENT_PERSISTENT;
    +        }
    +
    +        $resource = @stream_socket_client($uri, $errno, $errstr, $parameters->timeout, $flags);
    +
    +        if (!$resource) {
    +            $this->onConnectionError(trim($errstr), $errno);
    +        }
    +
    +        if (isset($parameters->read_write_timeout)) {
    +            $rwtimeout = $parameters->read_write_timeout;
    +            $rwtimeout = $rwtimeout > 0 ? $rwtimeout : -1;
    +            $timeoutSeconds  = floor($rwtimeout);
    +            $timeoutUSeconds = ($rwtimeout - $timeoutSeconds) * 1000000;
    +            stream_set_timeout($resource, $timeoutSeconds, $timeoutUSeconds);
    +        }
    +
    +        if (isset($parameters->tcp_nodelay) && version_compare(PHP_VERSION, '5.4.0') >= 0) {
    +            $socket = socket_import_stream($resource);
    +            socket_set_option($socket, SOL_TCP, TCP_NODELAY, (int) $parameters->tcp_nodelay);
    +        }
    +
    +        return $resource;
    +    }
    +
    +    /**
    +     * Initializes a UNIX stream resource.
    +     *
    +     * @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
    +     * @return resource
    +     */
    +    private function unixStreamInitializer(ConnectionParametersInterface $parameters)
    +    {
    +        $uri = "unix://{$parameters->path}";
    +        $flags = STREAM_CLIENT_CONNECT;
    +
    +        if ($parameters->persistent) {
    +            $flags |= STREAM_CLIENT_PERSISTENT;
    +        }
    +
    +        $resource = @stream_socket_client($uri, $errno, $errstr, $parameters->timeout, $flags);
    +
    +        if (!$resource) {
    +            $this->onConnectionError(trim($errstr), $errno);
    +        }
    +
    +        return $resource;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function connect()
    +    {
    +        parent::connect();
    +
    +        if ($this->initCmds) {
    +            $this->sendInitializationCommands();
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function disconnect()
    +    {
    +        if ($this->isConnected()) {
    +            fclose($this->getResource());
    +            parent::disconnect();
    +        }
    +    }
    +
    +    /**
    +     * Sends the initialization commands to Redis when the connection is opened.
    +     */
    +    private function sendInitializationCommands()
    +    {
    +        foreach ($this->initCmds as $command) {
    +            $this->writeCommand($command);
    +        }
    +        foreach ($this->initCmds as $command) {
    +            $this->readResponse($command);
    +        }
    +    }
    +
    +    /**
    +     * Performs a write operation on the stream of the buffer containing a
    +     * command serialized with the Redis wire protocol.
    +     *
    +     * @param string $buffer Redis wire protocol representation of a command.
    +     */
    +    protected function writeBytes($buffer)
    +    {
    +        $socket = $this->getResource();
    +
    +        while (($length = strlen($buffer)) > 0) {
    +            $written = fwrite($socket, $buffer);
    +
    +            if ($length === $written) {
    +                return;
    +            }
    +            if ($written === false || $written === 0) {
    +                $this->onConnectionError('Error while writing bytes to the server');
    +            }
    +
    +            $buffer = substr($buffer, $written);
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function read()
    +    {
    +        $socket = $this->getResource();
    +        $chunk  = fgets($socket);
    +
    +        if ($chunk === false || $chunk === '') {
    +            $this->onConnectionError('Error while reading line from the server');
    +        }
    +
    +        $prefix  = $chunk[0];
    +        $payload = substr($chunk, 1, -2);
    +
    +        switch ($prefix) {
    +            case '+':    // inline
    +                switch ($payload) {
    +                    case 'OK':
    +                        return true;
    +
    +                    case 'QUEUED':
    +                        return new ResponseQueued();
    +
    +                    default:
    +                        return $payload;
    +                }
    +
    +            case '$':    // bulk
    +                $size = (int) $payload;
    +                if ($size === -1) {
    +                    return null;
    +                }
    +
    +                $bulkData = '';
    +                $bytesLeft = ($size += 2);
    +
    +                do {
    +                    $chunk = fread($socket, min($bytesLeft, 4096));
    +
    +                    if ($chunk === false || $chunk === '') {
    +                        $this->onConnectionError('Error while reading bytes from the server');
    +                    }
    +
    +                    $bulkData .= $chunk;
    +                    $bytesLeft = $size - strlen($bulkData);
    +                } while ($bytesLeft > 0);
    +
    +                return substr($bulkData, 0, -2);
    +
    +            case '*':    // multi bulk
    +                $count = (int) $payload;
    +
    +                if ($count === -1) {
    +                    return null;
    +                }
    +                if ($this->mbiterable) {
    +                    return new MultiBulkResponseSimple($this, $count);
    +                }
    +
    +                $multibulk = array();
    +
    +                for ($i = 0; $i < $count; $i++) {
    +                    $multibulk[$i] = $this->read();
    +                }
    +
    +                return $multibulk;
    +
    +            case ':':    // integer
    +                return (int) $payload;
    +
    +            case '-':    // error
    +                return new ResponseError($payload);
    +
    +            default:
    +                $this->onProtocolError("Unknown prefix: '$prefix'");
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function writeCommand(CommandInterface $command)
    +    {
    +        $commandId = $command->getId();
    +        $arguments = $command->getArguments();
    +
    +        $cmdlen = strlen($commandId);
    +        $reqlen = count($arguments) + 1;
    +
    +        $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandId}\r\n";
    +
    +        for ($i = 0; $i < $reqlen - 1; $i++) {
    +            $argument = $arguments[$i];
    +            $arglen = strlen($argument);
    +            $buffer .= "\${$arglen}\r\n{$argument}\r\n";
    +        }
    +
    +        $this->writeBytes($buffer);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __sleep()
    +    {
    +        return array_merge(parent::__sleep(), array('mbiterable'));
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Connection/WebdisConnection.php b/vendor/predis/predis/lib/Predis/Connection/WebdisConnection.php
    new file mode 100644
    index 0000000..4c1ea89
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Connection/WebdisConnection.php
    @@ -0,0 +1,335 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use Predis\NotSupportedException;
    +use Predis\ResponseError;
    +use Predis\Command\CommandInterface;
    +use Predis\Connection\ConnectionException;
    +use Predis\Protocol\ProtocolException;
    +
    +/**
    + * This class implements a Predis connection that actually talks with Webdis
    + * instead of connecting directly to Redis. It relies on the cURL extension to
    + * communicate with the web server and the phpiredis extension to parse the
    + * protocol of the replies returned in the http response bodies.
    + *
    + * Some features are not yet available or they simply cannot be implemented:
    + *   - Pipelining commands.
    + *   - Publish / Subscribe.
    + *   - MULTI / EXEC transactions (not yet supported by Webdis).
    + *
    + * The connection parameters supported by this class are:
    + *
    + *  - scheme: must be 'http'.
    + *  - host: hostname or IP address of the server.
    + *  - port: TCP port of the server.
    + *  - timeout: timeout to perform the connection.
    + *  - user: username for authentication.
    + *  - pass: password for authentication.
    + *
    + * @link http://webd.is
    + * @link http://github.com/nicolasff/webdis
    + * @link http://github.com/seppo0010/phpiredis
    + * @author Daniele Alessandri 
    + */
    +class WebdisConnection implements SingleConnectionInterface
    +{
    +    const ERR_MSG_EXTENSION = 'The %s extension must be loaded in order to be able to use this connection class';
    +
    +    private $parameters;
    +    private $resource;
    +    private $reader;
    +
    +    /**
    +     * @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
    +     */
    +    public function __construct(ConnectionParametersInterface $parameters)
    +    {
    +        $this->checkExtensions();
    +
    +        if ($parameters->scheme !== 'http') {
    +            throw new \InvalidArgumentException("Invalid scheme: {$parameters->scheme}");
    +        }
    +
    +        $this->parameters = $parameters;
    +        $this->resource = $this->initializeCurl($parameters);
    +        $this->reader = $this->initializeReader($parameters);
    +    }
    +
    +    /**
    +     * Frees the underlying cURL and protocol reader resources when PHP's
    +     * garbage collector kicks in.
    +     */
    +    public function __destruct()
    +    {
    +        curl_close($this->resource);
    +        phpiredis_reader_destroy($this->reader);
    +    }
    +
    +    /**
    +     * Helper method used to throw on unsupported methods.
    +     */
    +    private function throwNotSupportedException($function)
    +    {
    +        $class = __CLASS__;
    +        throw new NotSupportedException("The method $class::$function() is not supported");
    +    }
    +
    +    /**
    +     * Checks if the cURL and phpiredis extensions are loaded in PHP.
    +     */
    +    private function checkExtensions()
    +    {
    +        if (!function_exists('curl_init')) {
    +            throw new NotSupportedException(sprintf(self::ERR_MSG_EXTENSION, 'curl'));
    +        }
    +
    +        if (!function_exists('phpiredis_reader_create')) {
    +            throw new NotSupportedException(sprintf(self::ERR_MSG_EXTENSION, 'phpiredis'));
    +        }
    +    }
    +
    +    /**
    +     * Initializes cURL.
    +     *
    +     * @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
    +     * @return resource
    +     */
    +    private function initializeCurl(ConnectionParametersInterface $parameters)
    +    {
    +        $options = array(
    +            CURLOPT_FAILONERROR => true,
    +            CURLOPT_CONNECTTIMEOUT_MS => $parameters->timeout * 1000,
    +            CURLOPT_URL => "{$parameters->scheme}://{$parameters->host}:{$parameters->port}",
    +            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    +            CURLOPT_POST => true,
    +            CURLOPT_WRITEFUNCTION => array($this, 'feedReader'),
    +        );
    +
    +        if (isset($parameters->user, $parameters->pass)) {
    +            $options[CURLOPT_USERPWD] = "{$parameters->user}:{$parameters->pass}";
    +        }
    +
    +        curl_setopt_array($resource = curl_init(), $options);
    +
    +        return $resource;
    +    }
    +
    +    /**
    +     * Initializes phpiredis' protocol reader.
    +     *
    +     * @param ConnectionParametersInterface $parameters Parameters used to initialize the connection.
    +     * @return resource
    +     */
    +    private function initializeReader(ConnectionParametersInterface $parameters)
    +    {
    +        $reader = phpiredis_reader_create();
    +
    +        phpiredis_reader_set_status_handler($reader, $this->getStatusHandler());
    +        phpiredis_reader_set_error_handler($reader, $this->getErrorHandler());
    +
    +        return $reader;
    +    }
    +
    +    /**
    +     * Gets the handler used by the protocol reader to handle status replies.
    +     *
    +     * @return \Closure
    +     */
    +    protected function getStatusHandler()
    +    {
    +        return function ($payload) {
    +            return $payload === 'OK' ? true : $payload;
    +        };
    +    }
    +
    +    /**
    +     * Gets the handler used by the protocol reader to handle Redis errors.
    +     *
    +     * @return \Closure
    +     */
    +    protected function getErrorHandler()
    +    {
    +        return function ($errorMessage) {
    +            return new ResponseError($errorMessage);
    +        };
    +    }
    +
    +    /**
    +     * Feeds phpredis' reader resource with the data read from the network.
    +     *
    +     * @param resource $resource Reader resource.
    +     * @param string $buffer Buffer with the reply read from the network.
    +     * @return int
    +     */
    +    protected function feedReader($resource, $buffer)
    +    {
    +        phpiredis_reader_feed($this->reader, $buffer);
    +
    +        return strlen($buffer);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function connect()
    +    {
    +        // NOOP
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function disconnect()
    +    {
    +        // NOOP
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function isConnected()
    +    {
    +        return true;
    +    }
    +
    +    /**
    +     * Checks if the specified command is supported by this connection class.
    +     *
    +     * @param CommandInterface $command The instance of a Redis command.
    +     * @return string
    +     */
    +    protected function getCommandId(CommandInterface $command)
    +    {
    +        switch (($commandId = $command->getId())) {
    +            case 'AUTH':
    +            case 'SELECT':
    +            case 'MULTI':
    +            case 'EXEC':
    +            case 'WATCH':
    +            case 'UNWATCH':
    +            case 'DISCARD':
    +            case 'MONITOR':
    +                throw new NotSupportedException("Disabled command: {$command->getId()}");
    +
    +            default:
    +                return $commandId;
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function writeCommand(CommandInterface $command)
    +    {
    +        $this->throwNotSupportedException(__FUNCTION__);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function readResponse(CommandInterface $command)
    +    {
    +        $this->throwNotSupportedException(__FUNCTION__);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function executeCommand(CommandInterface $command)
    +    {
    +        $resource = $this->resource;
    +        $commandId = $this->getCommandId($command);
    +
    +        if ($arguments = $command->getArguments()) {
    +            $arguments = implode('/', array_map('urlencode', $arguments));
    +            $serializedCommand = "$commandId/$arguments.raw";
    +        } else {
    +            $serializedCommand = "$commandId.raw";
    +        }
    +
    +        curl_setopt($resource, CURLOPT_POSTFIELDS, $serializedCommand);
    +
    +        if (curl_exec($resource) === false) {
    +            $error = curl_error($resource);
    +            $errno = curl_errno($resource);
    +            throw new ConnectionException($this, trim($error), $errno);
    +        }
    +
    +        if (phpiredis_reader_get_state($this->reader) !== PHPIREDIS_READER_STATE_COMPLETE) {
    +            throw new ProtocolException($this, phpiredis_reader_get_error($this->reader));
    +        }
    +
    +        return phpiredis_reader_get_reply($this->reader);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getResource()
    +    {
    +        return $this->resource;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getParameters()
    +    {
    +        return $this->parameters;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function pushInitCommand(CommandInterface $command)
    +    {
    +        $this->throwNotSupportedException(__FUNCTION__);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function read()
    +    {
    +        $this->throwNotSupportedException(__FUNCTION__);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __toString()
    +    {
    +        return "{$this->parameters->host}:{$this->parameters->port}";
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __sleep()
    +    {
    +        return array('parameters');
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __wakeup()
    +    {
    +        $this->checkExtensions();
    +        $parameters = $this->getParameters();
    +
    +        $this->resource = $this->initializeCurl($parameters);
    +        $this->reader = $this->initializeReader($parameters);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/ExecutableContextInterface.php b/vendor/predis/predis/lib/Predis/ExecutableContextInterface.php
    new file mode 100644
    index 0000000..9b0017a
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/ExecutableContextInterface.php
    @@ -0,0 +1,29 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +/**
    + * Defines the interface of a basic client object or abstraction that
    + * can send commands to Redis.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface ExecutableContextInterface
    +{
    +    /**
    +     * Starts the execution of the context.
    +     *
    +     * @param mixed $callable Optional callback for execution.
    +     * @return array
    +     */
    +    public function execute($callable = null);
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Helpers.php b/vendor/predis/predis/lib/Predis/Helpers.php
    new file mode 100644
    index 0000000..33fa33a
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Helpers.php
    @@ -0,0 +1,73 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +/**
    + * Defines a few helper methods.
    + *
    + * @author Daniele Alessandri 
    + * @deprecated Deprecated since v0.8.3.
    + */
    +class Helpers
    +{
    +    /**
    +     * Offers a generic and reusable method to handle exceptions generated by
    +     * a connection object.
    +     *
    +     * @deprecated Deprecated since v0.8.3 - moved in Predis\CommunicationException::handle()
    +     * @param CommunicationException $exception Exception.
    +     */
    +    public static function onCommunicationException(CommunicationException $exception)
    +    {
    +        if ($exception->shouldResetConnection()) {
    +            $connection = $exception->getConnection();
    +
    +            if ($connection->isConnected()) {
    +                $connection->disconnect();
    +            }
    +        }
    +
    +        throw $exception;
    +    }
    +
    +    /**
    +     * Normalizes the arguments array passed to a Redis command.
    +     *
    +     * @deprecated Deprecated since v0.8.3 - moved in Predis\Command\AbstractCommand::normalizeArguments()
    +     * @param array $arguments Arguments for a command.
    +     * @return array
    +     */
    +    public static function filterArrayArguments(Array $arguments)
    +    {
    +        if (count($arguments) === 1 && is_array($arguments[0])) {
    +            return $arguments[0];
    +        }
    +
    +        return $arguments;
    +    }
    +
    +    /**
    +     * Normalizes the arguments array passed to a variadic Redis command.
    +     *
    +     * @deprecated Deprecated since v0.8.3 - moved in Predis\Command\AbstractCommand::normalizeVariadic()
    +     * @param array $arguments Arguments for a command.
    +     * @return array
    +     */
    +    public static function filterVariadicValues(Array $arguments)
    +    {
    +        if (count($arguments) === 2 && is_array($arguments[1])) {
    +            return array_merge(array($arguments[0]), $arguments[1]);
    +        }
    +
    +        return $arguments;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Iterator/MultiBulkResponse.php b/vendor/predis/predis/lib/Predis/Iterator/MultiBulkResponse.php
    new file mode 100644
    index 0000000..a7bbad8
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Iterator/MultiBulkResponse.php
    @@ -0,0 +1,100 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Iterator;
    +
    +use Predis\ResponseObjectInterface;
    +
    +/**
    + * Iterator that abstracts the access to multibulk replies and allows
    + * them to be consumed by user's code in a streaming fashion.
    + *
    + * @author Daniele Alessandri 
    + */
    +abstract class MultiBulkResponse implements \Iterator, \Countable, ResponseObjectInterface
    +{
    +    protected $position;
    +    protected $current;
    +    protected $replySize;
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function rewind()
    +    {
    +        // NOOP
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function current()
    +    {
    +        return $this->current;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function key()
    +    {
    +        return $this->position;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function next()
    +    {
    +        if (++$this->position < $this->replySize) {
    +            $this->current = $this->getValue();
    +        }
    +
    +        return $this->position;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function valid()
    +    {
    +        return $this->position < $this->replySize;
    +    }
    +
    +    /**
    +     * Returns the number of items of the whole multibulk reply.
    +     *
    +     * This method should be used to get the size of the current multibulk
    +     * reply without using iterator_count, which actually consumes the
    +     * iterator to calculate the size (rewinding is not supported).
    +     *
    +     * @return int
    +     */
    +    public function count()
    +    {
    +        return $this->replySize;
    +    }
    +
    +    /**
    +     * Returns the current position of the iterator.
    +     *
    +     * @return int
    +     */
    +    public function getPosition()
    +    {
    +        return $this->position;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected abstract function getValue();
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Iterator/MultiBulkResponseSimple.php b/vendor/predis/predis/lib/Predis/Iterator/MultiBulkResponseSimple.php
    new file mode 100644
    index 0000000..2dee722
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Iterator/MultiBulkResponseSimple.php
    @@ -0,0 +1,89 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Iterator;
    +
    +use Predis\Connection\SingleConnectionInterface;
    +
    +/**
    + * Streams a multibulk reply.
    + *
    + * @author Daniele Alessandri 
    + */
    +class MultiBulkResponseSimple extends MultiBulkResponse
    +{
    +    private $connection;
    +
    +    /**
    +     * @param SingleConnectionInterface $connection Connection to Redis.
    +     * @param int $size Number of elements of the multibulk reply.
    +     */
    +    public function __construct(SingleConnectionInterface $connection, $size)
    +    {
    +        $this->connection = $connection;
    +        $this->position = 0;
    +        $this->current = $size > 0 ? $this->getValue() : null;
    +        $this->replySize = $size;
    +    }
    +
    +    /**
    +     * Handles the synchronization of the client with the Redis protocol
    +     * then PHP's garbage collector kicks in (e.g. then the iterator goes
    +     * out of the scope of a foreach).
    +     */
    +    public function __destruct()
    +    {
    +        $this->sync(true);
    +    }
    +
    +    /**
    +     * Synchronizes the client with the queued elements that have not been
    +     * read from the connection by consuming the rest of the multibulk reply,
    +     * or simply by dropping the connection.
    +     *
    +     * @param Boolean $drop True to synchronize the client by dropping the connection.
    +     *                      False to synchronize the client by consuming the multibulk reply.
    +     */
    +    public function sync($drop = false)
    +    {
    +        if ($drop == true) {
    +            if ($this->valid()) {
    +                $this->position = $this->replySize;
    +                $this->connection->disconnect();
    +            }
    +        } else {
    +            while ($this->valid()) {
    +                $this->next();
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Reads the next item of the multibulk reply from the server.
    +     *
    +     * @return mixed
    +     */
    +    protected function getValue()
    +    {
    +        return $this->connection->read();
    +    }
    +
    +    /**
    +     * Returns an iterator that reads the multi-bulk response as
    +     * list of tuples.
    +     *
    +     * @return MultiBulkResponseTuple
    +     */
    +    public function asTuple()
    +    {
    +        return new MultiBulkResponseTuple($this);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Iterator/MultiBulkResponseTuple.php b/vendor/predis/predis/lib/Predis/Iterator/MultiBulkResponseTuple.php
    new file mode 100644
    index 0000000..39d7828
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Iterator/MultiBulkResponseTuple.php
    @@ -0,0 +1,83 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Iterator;
    +
    +/**
    + * Abstracts the access to a streamable list of tuples represented
    + * as a multibulk reply that alternates keys and values.
    + *
    + * @author Daniele Alessandri 
    + */
    +class MultiBulkResponseTuple extends MultiBulkResponse implements \OuterIterator
    +{
    +    private $iterator;
    +
    +    /**
    +     * @param MultiBulkResponseSimple $iterator Multibulk reply iterator.
    +     */
    +    public function __construct(MultiBulkResponseSimple $iterator)
    +    {
    +        $this->checkPreconditions($iterator);
    +
    +        $virtualSize = count($iterator) / 2;
    +        $this->iterator = $iterator;
    +        $this->position = $iterator->getPosition();
    +        $this->current = $virtualSize > 0 ? $this->getValue() : null;
    +        $this->replySize = $virtualSize;
    +    }
    +
    +    /**
    +     * Checks for valid preconditions.
    +     *
    +     * @param MultiBulkResponseSimple $iterator Multibulk reply iterator.
    +     */
    +    protected function checkPreconditions(MultiBulkResponseSimple $iterator)
    +    {
    +        if ($iterator->getPosition() !== 0) {
    +            throw new \RuntimeException('Cannot initialize a tuple iterator with an already initiated iterator');
    +        }
    +
    +        if (($size = count($iterator)) % 2 !== 0) {
    +            throw new \UnexpectedValueException("Invalid reply size for a tuple iterator [$size]");
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getInnerIterator()
    +    {
    +        return $this->iterator;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __destruct()
    +    {
    +        $this->iterator->sync(true);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getValue()
    +    {
    +        $k = $this->iterator->current();
    +        $this->iterator->next();
    +
    +        $v = $this->iterator->current();
    +        $this->iterator->next();
    +
    +        return array($k, $v);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Monitor/MonitorContext.php b/vendor/predis/predis/lib/Predis/Monitor/MonitorContext.php
    new file mode 100644
    index 0000000..a053b0b
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Monitor/MonitorContext.php
    @@ -0,0 +1,165 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Monitor;
    +
    +use Predis\ClientInterface;
    +use Predis\NotSupportedException;
    +use Predis\Connection\AggregatedConnectionInterface;
    +
    +/**
    + * Client-side abstraction of a Redis MONITOR context.
    + *
    + * @author Daniele Alessandri 
    + */
    +class MonitorContext implements \Iterator
    +{
    +    private $client;
    +    private $isValid;
    +    private $position;
    +
    +    /**
    +     * @param ClientInterface $client Client instance used by the context.
    +     */
    +    public function __construct(ClientInterface $client)
    +    {
    +        $this->checkCapabilities($client);
    +        $this->client = $client;
    +        $this->openContext();
    +    }
    +
    +    /**
    +     * Automatically closes the context when PHP's garbage collector kicks in.
    +     */
    +    public function __destruct()
    +    {
    +        $this->closeContext();
    +    }
    +
    +    /**
    +     * Checks if the passed client instance satisfies the required conditions
    +     * needed to initialize a monitor context.
    +     *
    +     * @param ClientInterface $client Client instance used by the context.
    +     */
    +    private function checkCapabilities(ClientInterface $client)
    +    {
    +        if ($client->getConnection() instanceof AggregatedConnectionInterface) {
    +            throw new NotSupportedException('Cannot initialize a monitor context when using aggregated connections');
    +        }
    +        if ($client->getProfile()->supportsCommand('monitor') === false) {
    +            throw new NotSupportedException('The current profile does not support the MONITOR command');
    +        }
    +    }
    +
    +    /**
    +     * Initializes the context and sends the MONITOR command to the server.
    +     */
    +    protected function openContext()
    +    {
    +        $this->isValid = true;
    +        $monitor = $this->client->createCommand('monitor');
    +        $this->client->executeCommand($monitor);
    +    }
    +
    +    /**
    +     * Closes the context. Internally this is done by disconnecting from server
    +     * since there is no way to terminate the stream initialized by MONITOR.
    +     */
    +    public function closeContext()
    +    {
    +        $this->client->disconnect();
    +        $this->isValid = false;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function rewind()
    +    {
    +        // NOOP
    +    }
    +
    +    /**
    +     * Returns the last message payload retrieved from the server.
    +     *
    +     * @return Object
    +     */
    +    public function current()
    +    {
    +        return $this->getValue();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function key()
    +    {
    +        return $this->position;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function next()
    +    {
    +        $this->position++;
    +    }
    +
    +    /**
    +     * Checks if the the context is still in a valid state to continue.
    +     *
    +     * @return Boolean
    +     */
    +    public function valid()
    +    {
    +        return $this->isValid;
    +    }
    +
    +    /**
    +     * Waits for a new message from the server generated by MONITOR and
    +     * returns it when available.
    +     *
    +     * @return Object
    +     */
    +    private function getValue()
    +    {
    +        $database = 0;
    +        $client = null;
    +        $event = $this->client->getConnection()->read();
    +
    +        $callback = function ($matches) use (&$database, &$client) {
    +            if (2 === $count = count($matches)) {
    +                // Redis <= 2.4
    +                $database = (int) $matches[1];
    +            }
    +
    +            if (4 === $count) {
    +                // Redis >= 2.6
    +                $database = (int) $matches[2];
    +                $client = $matches[3];
    +            }
    +
    +            return ' ';
    +        };
    +
    +        $event = preg_replace_callback('/ \(db (\d+)\) | \[(\d+) (.*?)\] /', $callback, $event, 1);
    +        @list($timestamp, $command, $arguments) = explode(' ', $event, 3);
    +
    +        return (object) array(
    +            'timestamp' => (float) $timestamp,
    +            'database'  => $database,
    +            'client'    => $client,
    +            'command'   => substr($command, 1, -1),
    +            'arguments' => $arguments,
    +        );
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/NotSupportedException.php b/vendor/predis/predis/lib/Predis/NotSupportedException.php
    new file mode 100644
    index 0000000..631b820
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/NotSupportedException.php
    @@ -0,0 +1,22 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +/**
    + * Exception class generated when trying to use features not
    + * supported by certain classes or abstractions.
    + *
    + * @author Daniele Alessandri 
    + */
    +class NotSupportedException extends PredisException
    +{
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Option/AbstractOption.php b/vendor/predis/predis/lib/Predis/Option/AbstractOption.php
    new file mode 100644
    index 0000000..92506cd
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Option/AbstractOption.php
    @@ -0,0 +1,48 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +/**
    + * Implements a client option.
    + *
    + * @author Daniele Alessandri 
    + */
    +abstract class AbstractOption implements OptionInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function filter(ClientOptionsInterface $options, $value)
    +    {
    +        return $value;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getDefault(ClientOptionsInterface $options)
    +    {
    +        return null;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __invoke(ClientOptionsInterface $options, $value)
    +    {
    +        if (isset($value)) {
    +            return $this->filter($options, $value);
    +        }
    +
    +        return $this->getDefault($options);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Option/ClientCluster.php b/vendor/predis/predis/lib/Predis/Option/ClientCluster.php
    new file mode 100644
    index 0000000..dff626f
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Option/ClientCluster.php
    @@ -0,0 +1,95 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +use Predis\Connection\ClusterConnectionInterface;
    +use Predis\Connection\PredisCluster;
    +use Predis\Connection\RedisCluster;
    +
    +/**
    + * Option class that returns a connection cluster to be used by a client.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ClientCluster extends AbstractOption
    +{
    +    /**
    +     * Checks if the specified value is a valid instance of ClusterConnectionInterface.
    +     *
    +     * @param ClusterConnectionInterface $cluster Instance of a connection cluster.
    +     * @return ClusterConnectionInterface
    +     */
    +    protected function checkInstance($cluster)
    +    {
    +        if (!$cluster instanceof ClusterConnectionInterface) {
    +            throw new \InvalidArgumentException('Instance of Predis\Connection\ClusterConnectionInterface expected');
    +        }
    +
    +        return $cluster;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function filter(ClientOptionsInterface $options, $value)
    +    {
    +        if (is_callable($value)) {
    +            return $this->checkInstance(call_user_func($value, $options, $this));
    +        }
    +
    +        $initializer = $this->getInitializer($options, $value);
    +
    +        return $this->checkInstance($initializer());
    +    }
    +
    +    /**
    +     * Returns an initializer for the specified FQN or type.
    +     *
    +     * @param string $fqnOrType Type of cluster or FQN of a class implementing ClusterConnectionInterface.
    +     * @param ClientOptionsInterface $options Instance of the client options.
    +     * @return \Closure
    +     */
    +    protected function getInitializer(ClientOptionsInterface $options, $fqnOrType)
    +    {
    +        switch ($fqnOrType) {
    +            case 'predis':
    +                return function () {
    +                    return new PredisCluster();
    +                };
    +
    +            case 'redis':
    +                return function () use ($options) {
    +                    $connectionFactory = $options->connections;
    +                    $cluster = new RedisCluster($connectionFactory);
    +
    +                    return $cluster;
    +                };
    +
    +            default:
    +                // TODO: we should not even allow non-string values here.
    +                if (is_string($fqnOrType) && !class_exists($fqnOrType)) {
    +                    throw new \InvalidArgumentException("Class $fqnOrType does not exist");
    +                }
    +                return function () use ($fqnOrType) {
    +                    return new $fqnOrType();
    +                };
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getDefault(ClientOptionsInterface $options)
    +    {
    +        return new PredisCluster();
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Option/ClientConnectionFactory.php b/vendor/predis/predis/lib/Predis/Option/ClientConnectionFactory.php
    new file mode 100644
    index 0000000..e671e52
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Option/ClientConnectionFactory.php
    @@ -0,0 +1,73 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +use Predis\Connection\ConnectionFactory;
    +use Predis\Connection\ConnectionFactoryInterface;
    +
    +/**
    + * Option class that returns a connection factory to be used by a client.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ClientConnectionFactory extends AbstractOption
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function filter(ClientOptionsInterface $options, $value)
    +    {
    +        if ($value instanceof ConnectionFactoryInterface) {
    +            return $value;
    +        }
    +
    +        if (is_array($value)) {
    +            $factory = $this->getDefault($options);
    +
    +            foreach ($value as $scheme => $initializer) {
    +                $factory->define($scheme, $initializer);
    +            }
    +
    +            return $factory;
    +        }
    +
    +        if (is_callable($value)) {
    +            $factory = call_user_func($value, $options, $this);
    +
    +            if (!$factory instanceof ConnectionFactoryInterface) {
    +                throw new \InvalidArgumentException('Instance of Predis\Connection\ConnectionFactoryInterface expected');
    +            }
    +
    +            return $factory;
    +        }
    +
    +        if (@class_exists($value)) {
    +            $factory = new $value();
    +
    +            if (!$factory instanceof ConnectionFactoryInterface) {
    +                throw new \InvalidArgumentException("Class $value must be an instance of Predis\Connection\ConnectionFactoryInterface");
    +            }
    +
    +            return $factory;
    +        }
    +
    +        throw new \InvalidArgumentException('Invalid value for the connections option');
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getDefault(ClientOptionsInterface $options)
    +    {
    +        return new ConnectionFactory($options->profile);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Option/ClientExceptions.php b/vendor/predis/predis/lib/Predis/Option/ClientExceptions.php
    new file mode 100644
    index 0000000..ad422da
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Option/ClientExceptions.php
    @@ -0,0 +1,36 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +/**
    + * Option class used to specify if the client should throw server exceptions.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ClientExceptions extends AbstractOption
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function filter(ClientOptionsInterface $options, $value)
    +    {
    +        return (bool) $value;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getDefault(ClientOptionsInterface $options)
    +    {
    +        return true;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Option/ClientOptions.php b/vendor/predis/predis/lib/Predis/Option/ClientOptions.php
    new file mode 100644
    index 0000000..d835524
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Option/ClientOptions.php
    @@ -0,0 +1,125 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +/**
    + * Class that manages client options with filtering and conversion.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ClientOptions implements ClientOptionsInterface
    +{
    +    private $handlers;
    +    private $defined;
    +    private $options = array();
    +
    +    /**
    +     * @param array $options Array of client options.
    +     */
    +    public function __construct(Array $options = array())
    +    {
    +        $this->handlers = $this->initialize($options);
    +        $this->defined = array_fill_keys(array_keys($options), true);
    +    }
    +
    +    /**
    +     * Ensures that the default options are initialized.
    +     *
    +     * @return array
    +     */
    +    protected function getDefaultOptions()
    +    {
    +        return array(
    +            'profile' => new ClientProfile(),
    +            'connections' => new ClientConnectionFactory(),
    +            'cluster' => new ClientCluster(),
    +            'replication' => new ClientReplication(),
    +            'prefix' => new ClientPrefix(),
    +            'exceptions' => new ClientExceptions(),
    +        );
    +    }
    +
    +    /**
    +     * Initializes client options handlers.
    +     *
    +     * @param array $options List of client options values.
    +     * @return array
    +     */
    +    protected function initialize(Array $options)
    +    {
    +        $handlers = $this->getDefaultOptions();
    +
    +        foreach ($options as $option => $value) {
    +            if (isset($handlers[$option])) {
    +                $handler = $handlers[$option];
    +                $handlers[$option] = function ($options) use ($handler, $value) {
    +                    return $handler->filter($options, $value);
    +                };
    +            } else {
    +                $this->options[$option] = $value;
    +            }
    +        }
    +
    +        return $handlers;
    +    }
    +
    +    /**
    +     * Checks if the specified option is set.
    +     *
    +     * @param string $option Name of the option.
    +     * @return Boolean
    +     */
    +    public function __isset($option)
    +    {
    +        return isset($this->defined[$option]);
    +    }
    +
    +    /**
    +     * Returns the value of the specified option.
    +     *
    +     * @param string $option Name of the option.
    +     * @return mixed
    +     */
    +    public function __get($option)
    +    {
    +        if (isset($this->options[$option])) {
    +            return $this->options[$option];
    +        }
    +
    +        if (isset($this->handlers[$option])) {
    +            $handler = $this->handlers[$option];
    +            $value = $handler instanceof OptionInterface ? $handler->getDefault($this) : $handler($this);
    +            $this->options[$option] = $value;
    +
    +            return $value;
    +        }
    +    }
    +
    +    /**
    +     * Returns the default value for the specified option.
    +     *
    +     * @param string|OptionInterface $option Name or instance of the option.
    +     * @return mixed
    +     */
    +    public function getDefault($option)
    +    {
    +        if ($option instanceof OptionInterface) {
    +            return $option->getDefault($this);
    +        }
    +
    +        $options = $this->getDefaultOptions();
    +
    +        if (isset($options[$option])) {
    +            return $options[$option]->getDefault($this);
    +        }
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Option/ClientOptionsInterface.php b/vendor/predis/predis/lib/Predis/Option/ClientOptionsInterface.php
    new file mode 100644
    index 0000000..543e18a
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Option/ClientOptionsInterface.php
    @@ -0,0 +1,21 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +/**
    + * Marker interface defining a client options bag.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface ClientOptionsInterface
    +{
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Option/ClientPrefix.php b/vendor/predis/predis/lib/Predis/Option/ClientPrefix.php
    new file mode 100644
    index 0000000..01a166a
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Option/ClientPrefix.php
    @@ -0,0 +1,30 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +use Predis\Command\Processor\KeyPrefixProcessor;
    +
    +/**
    + * Option class that handles the prefixing of keys in commands.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ClientPrefix extends AbstractOption
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function filter(ClientOptionsInterface $options, $value)
    +    {
    +        return new KeyPrefixProcessor($value);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Option/ClientProfile.php b/vendor/predis/predis/lib/Predis/Option/ClientProfile.php
    new file mode 100644
    index 0000000..660066d
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Option/ClientProfile.php
    @@ -0,0 +1,61 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +use Predis\Profile\ServerProfile;
    +use Predis\Profile\ServerProfileInterface;
    +
    +/**
    + * Option class that handles server profiles to be used by a client.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ClientProfile extends AbstractOption
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function filter(ClientOptionsInterface $options, $value)
    +    {
    +        if (is_string($value)) {
    +            $value = ServerProfile::get($value);
    +
    +            if (isset($options->prefix)) {
    +                $value->setProcessor($options->prefix);
    +            }
    +        }
    +
    +        if (is_callable($value)) {
    +            $value = call_user_func($value, $options, $this);
    +        }
    +
    +        if (!$value instanceof ServerProfileInterface) {
    +            throw new \InvalidArgumentException('Invalid value for the profile option');
    +        }
    +
    +        return $value;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getDefault(ClientOptionsInterface $options)
    +    {
    +        $profile = ServerProfile::getDefault();
    +
    +        if (isset($options->prefix)) {
    +            $profile->setProcessor($options->prefix);
    +        }
    +
    +        return $profile;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Option/ClientReplication.php b/vendor/predis/predis/lib/Predis/Option/ClientReplication.php
    new file mode 100644
    index 0000000..61e0268
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Option/ClientReplication.php
    @@ -0,0 +1,78 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +use Predis\Connection\MasterSlaveReplication;
    +use Predis\Connection\ReplicationConnectionInterface;
    +
    +/**
    + * Option class that returns a replication connection be used by a client.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ClientReplication extends AbstractOption
    +{
    +    /**
    +     * Checks if the specified value is a valid instance of ReplicationConnectionInterface.
    +     *
    +     * @param ReplicationConnectionInterface $connection Instance of a replication connection.
    +     * @return ReplicationConnectionInterface
    +     */
    +    protected function checkInstance($connection)
    +    {
    +        if (!$connection instanceof ReplicationConnectionInterface) {
    +            throw new \InvalidArgumentException('Instance of Predis\Connection\ReplicationConnectionInterface expected');
    +        }
    +
    +        return $connection;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function filter(ClientOptionsInterface $options, $value)
    +    {
    +        if (is_callable($value)) {
    +            $connection = call_user_func($value, $options, $this);
    +
    +            if (!$connection instanceof ReplicationConnectionInterface) {
    +                throw new \InvalidArgumentException('Instance of Predis\Connection\ReplicationConnectionInterface expected');
    +            }
    +
    +            return $connection;
    +        }
    +
    +        if (is_string($value)) {
    +            if (!class_exists($value)) {
    +                throw new \InvalidArgumentException("Class $value does not exist");
    +            }
    +
    +            if (!($connection = new $value()) instanceof ReplicationConnectionInterface) {
    +                throw new \InvalidArgumentException('Instance of Predis\Connection\ReplicationConnectionInterface expected');
    +            }
    +
    +            return $connection;
    +        }
    +
    +        if ($value == true) {
    +            return $this->getDefault($options);
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getDefault(ClientOptionsInterface $options)
    +    {
    +        return new MasterSlaveReplication();
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Option/CustomOption.php b/vendor/predis/predis/lib/Predis/Option/CustomOption.php
    new file mode 100644
    index 0000000..067907b
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Option/CustomOption.php
    @@ -0,0 +1,89 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +/**
    + * Implements a generic class used to dynamically define a client option.
    + *
    + * @author Daniele Alessandri 
    + */
    +class CustomOption implements OptionInterface
    +{
    +    private $filter;
    +    private $default;
    +
    +    /**
    +     * @param array $options List of options
    +     */
    +    public function __construct(Array $options = array())
    +    {
    +        $this->filter = $this->ensureCallable($options, 'filter');
    +        $this->default = $this->ensureCallable($options, 'default');
    +    }
    +
    +    /**
    +     * Checks if the specified value in the options array is a callable object.
    +     *
    +     * @param array $options Array of options
    +     * @param string $key Target option.
    +     */
    +    private function ensureCallable($options, $key)
    +    {
    +        if (!isset($options[$key])) {
    +            return;
    +        }
    +
    +        if (is_callable($callable = $options[$key])) {
    +            return $callable;
    +        }
    +
    +        throw new \InvalidArgumentException("The parameter $key must be callable");
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function filter(ClientOptionsInterface $options, $value)
    +    {
    +        if (isset($value)) {
    +            if ($this->filter === null) {
    +                return $value;
    +            }
    +
    +            return call_user_func($this->filter, $options, $value);
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getDefault(ClientOptionsInterface $options)
    +    {
    +        if (!isset($this->default)) {
    +            return;
    +        }
    +
    +        return call_user_func($this->default, $options);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function __invoke(ClientOptionsInterface $options, $value)
    +    {
    +        if (isset($value)) {
    +            return $this->filter($options, $value);
    +        }
    +
    +        return $this->getDefault($options);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Option/OptionInterface.php b/vendor/predis/predis/lib/Predis/Option/OptionInterface.php
    new file mode 100644
    index 0000000..415b8c1
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Option/OptionInterface.php
    @@ -0,0 +1,45 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +/**
    + * Interface that defines a client option.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface OptionInterface
    +{
    +    /**
    +     * Filters (and optionally converts) the passed value.
    +     *
    +     * @param mixed $value Input value.
    +     * @return mixed
    +     */
    +    public function filter(ClientOptionsInterface $options, $value);
    +
    +    /**
    +     * Returns a default value for the option.
    +     *
    +     * @param mixed $value Input value.
    +     * @return mixed
    +     */
    +    public function getDefault(ClientOptionsInterface $options);
    +
    +    /**
    +     * Filters a value and, if no value is specified, returns
    +     * the default one defined by the option.
    +     *
    +     * @param mixed $value Input value.
    +     * @return mixed
    +     */
    +    public function __invoke(ClientOptionsInterface $options, $value);
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Pipeline/FireAndForgetExecutor.php b/vendor/predis/predis/lib/Predis/Pipeline/FireAndForgetExecutor.php
    new file mode 100644
    index 0000000..3e04799
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Pipeline/FireAndForgetExecutor.php
    @@ -0,0 +1,55 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Pipeline;
    +
    +use SplQueue;
    +use Predis\Connection\ConnectionInterface;
    +use Predis\Connection\ReplicationConnectionInterface;
    +
    +/**
    + * Implements a pipeline executor strategy that writes a list of commands to
    + * the connection object but does not read back their replies.
    + *
    + * @author Daniele Alessandri 
    + */
    +class FireAndForgetExecutor implements PipelineExecutorInterface
    +{
    +    /**
    +     * Allows the pipeline executor to perform operations on the
    +     * connection before starting to execute the commands stored
    +     * in the pipeline.
    +     *
    +     * @param ConnectionInterface $connection Connection instance.
    +     */
    +    protected function checkConnection(ConnectionInterface $connection)
    +    {
    +        if ($connection instanceof ReplicationConnectionInterface) {
    +            $connection->switchTo('master');
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function execute(ConnectionInterface $connection, SplQueue $commands)
    +    {
    +        $this->checkConnection($connection);
    +
    +        while (!$commands->isEmpty()) {
    +            $connection->writeCommand($commands->dequeue());
    +        }
    +
    +        $connection->disconnect();
    +
    +        return array();
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Pipeline/MultiExecExecutor.php b/vendor/predis/predis/lib/Predis/Pipeline/MultiExecExecutor.php
    new file mode 100644
    index 0000000..d9550bd
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Pipeline/MultiExecExecutor.php
    @@ -0,0 +1,168 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Pipeline;
    +
    +use Iterator;
    +use SplQueue;
    +use Predis\ClientException;
    +use Predis\ResponseErrorInterface;
    +use Predis\ResponseObjectInterface;
    +use Predis\ServerException;
    +use Predis\Connection\ConnectionInterface;
    +use Predis\Connection\SingleConnectionInterface;
    +use Predis\Profile\ServerProfile;
    +use Predis\Profile\ServerProfileInterface;
    +
    +/**
    + * Implements a pipeline executor that wraps the whole pipeline
    + * in a MULTI / EXEC context to make sure that it is executed
    + * correctly.
    + *
    + * @author Daniele Alessandri 
    + */
    +class MultiExecExecutor implements PipelineExecutorInterface
    +{
    +    protected $profile;
    +
    +    /**
    +     *
    +     */
    +    public function __construct(ServerProfileInterface $profile = null)
    +    {
    +        $this->setProfile($profile ?: ServerProfile::getDefault());
    +    }
    +
    +    /**
    +     * Allows the pipeline executor to perform operations on the
    +     * connection before starting to execute the commands stored
    +     * in the pipeline.
    +     *
    +     * @param ConnectionInterface $connection Connection instance.
    +     */
    +    protected function checkConnection(ConnectionInterface $connection)
    +    {
    +        if (!$connection instanceof SingleConnectionInterface) {
    +            $class = __CLASS__;
    +            throw new ClientException("$class can be used only with single connections");
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function execute(ConnectionInterface $connection, SplQueue $commands)
    +    {
    +        $this->checkConnection($connection);
    +
    +        $cmd = $this->profile->createCommand('multi');
    +        $connection->executeCommand($cmd);
    +
    +        foreach ($commands as $command) {
    +            $connection->writeCommand($command);
    +        }
    +
    +        foreach ($commands as $command) {
    +            $response = $connection->readResponse($command);
    +
    +            if ($response instanceof ResponseErrorInterface) {
    +                $cmd = $this->profile->createCommand('discard');
    +                $connection->executeCommand($cmd);
    +
    +                throw new ServerException($response->getMessage());
    +            }
    +        }
    +
    +        $cmd = $this->profile->createCommand('exec');
    +        $responses = $connection->executeCommand($cmd);
    +
    +        if (!isset($responses)) {
    +            throw new ClientException('The underlying transaction has been aborted by the server');
    +        }
    +
    +        if (count($responses) !== count($commands)) {
    +            throw new ClientException("Invalid number of replies [expected: ".count($commands)." - actual: ".count($responses)."]");
    +        }
    +
    +        $consumer = $responses instanceof Iterator ? 'consumeIteratorResponse' : 'consumeArrayResponse';
    +
    +        return $this->$consumer($commands, $responses);
    +    }
    +
    +    /**
    +     * Consumes an iterator response returned by EXEC.
    +     *
    +     * @param SplQueue $commands Pipelined commands
    +     * @param Iterator $responses Responses returned by EXEC.
    +     * @return array
    +     */
    +    protected function consumeIteratorResponse(SplQueue $commands, Iterator $responses)
    +    {
    +        $values = array();
    +
    +        foreach ($responses as $response) {
    +            $command = $commands->dequeue();
    +
    +            if ($response instanceof ResponseObjectInterface) {
    +                if ($response instanceof Iterator) {
    +                    $response = iterator_to_array($response);
    +                    $values[] = $command->parseResponse($response);
    +                } else {
    +                    $values[] = $response;
    +                }
    +            } else {
    +                $values[] = $command->parseResponse($response);
    +            }
    +        }
    +
    +        return $values;
    +    }
    +
    +    /**
    +     * Consumes an array response returned by EXEC.
    +     *
    +     * @param SplQueue $commands Pipelined commands
    +     * @param Array $responses Responses returned by EXEC.
    +     * @return array
    +     */
    +    protected function consumeArrayResponse(SplQueue $commands, Array &$responses)
    +    {
    +        $size = count($commands);
    +        $values = array();
    +
    +        for ($i = 0; $i < $size; $i++) {
    +            $command = $commands->dequeue();
    +            $response = $responses[$i];
    +
    +            if ($response instanceof ResponseObjectInterface) {
    +                $values[$i] = $response;
    +            } else {
    +                $values[$i] = $command->parseResponse($response);
    +            }
    +
    +            unset($responses[$i]);
    +        }
    +
    +        return $values;
    +    }
    +
    +    /**
    +     * @param ServerProfileInterface $profile Server profile.
    +     */
    +    public function setProfile(ServerProfileInterface $profile)
    +    {
    +        if (!$profile->supportsCommands(array('multi', 'exec', 'discard'))) {
    +            throw new ClientException('The specified server profile must support MULTI, EXEC and DISCARD.');
    +        }
    +
    +        $this->profile = $profile;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Pipeline/PipelineContext.php b/vendor/predis/predis/lib/Predis/Pipeline/PipelineContext.php
    new file mode 100644
    index 0000000..92b09d8
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Pipeline/PipelineContext.php
    @@ -0,0 +1,186 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Pipeline;
    +
    +use SplQueue;
    +use Predis\BasicClientInterface;
    +use Predis\ClientException;
    +use Predis\ClientInterface;
    +use Predis\ExecutableContextInterface;
    +use Predis\Command\CommandInterface;
    +
    +/**
    + * Abstraction of a pipeline context where write and read operations
    + * of commands and their replies over the network are pipelined.
    + *
    + * @author Daniele Alessandri 
    + */
    +class PipelineContext implements BasicClientInterface, ExecutableContextInterface
    +{
    +    private $client;
    +    private $executor;
    +    private $pipeline;
    +
    +    private $replies = array();
    +    private $running = false;
    +
    +    /**
    +     * @param ClientInterface $client Client instance used by the context.
    +     * @param PipelineExecutorInterface $executor Pipeline executor instace.
    +     */
    +    public function __construct(ClientInterface $client, PipelineExecutorInterface $executor = null)
    +    {
    +        $this->client = $client;
    +        $this->executor = $executor ?: $this->createExecutor($client);
    +        $this->pipeline = new SplQueue();
    +    }
    +
    +    /**
    +     * Returns a pipeline executor depending on the kind of the underlying
    +     * connection and the passed options.
    +     *
    +     * @param ClientInterface $client Client instance used by the context.
    +     * @return PipelineExecutorInterface
    +     */
    +    protected function createExecutor(ClientInterface $client)
    +    {
    +        $options = $client->getOptions();
    +
    +        if (isset($options->exceptions)) {
    +            return new StandardExecutor($options->exceptions);
    +        }
    +
    +        return new StandardExecutor();
    +    }
    +
    +    /**
    +     * Queues a command into the pipeline buffer.
    +     *
    +     * @param string $method Command ID.
    +     * @param array $arguments Arguments for the command.
    +     * @return PipelineContext
    +     */
    +    public function __call($method, $arguments)
    +    {
    +        $command = $this->client->createCommand($method, $arguments);
    +        $this->recordCommand($command);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Queues a command instance into the pipeline buffer.
    +     *
    +     * @param CommandInterface $command Command to queue in the buffer.
    +     */
    +    protected function recordCommand(CommandInterface $command)
    +    {
    +        $this->pipeline->enqueue($command);
    +    }
    +
    +    /**
    +     * Queues a command instance into the pipeline buffer.
    +     *
    +     * @param CommandInterface $command Command to queue in the buffer.
    +     */
    +    public function executeCommand(CommandInterface $command)
    +    {
    +        $this->recordCommand($command);
    +    }
    +
    +    /**
    +     * Flushes the buffer that holds the queued commands.
    +     *
    +     * @param Boolean $send Specifies if the commands in the buffer should be sent to Redis.
    +     * @return PipelineContext
    +     */
    +    public function flushPipeline($send = true)
    +    {
    +        if ($send && !$this->pipeline->isEmpty()) {
    +            $connection = $this->client->getConnection();
    +            $replies = $this->executor->execute($connection, $this->pipeline);
    +            $this->replies = array_merge($this->replies, $replies);
    +        } else {
    +            $this->pipeline = new SplQueue();
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Marks the running status of the pipeline.
    +     *
    +     * @param Boolean $bool True if the pipeline is running.
    +     *                      False if the pipeline is not running.
    +     */
    +    private function setRunning($bool)
    +    {
    +        if ($bool === true && $this->running === true) {
    +            throw new ClientException("This pipeline is already opened");
    +        }
    +
    +        $this->running = $bool;
    +    }
    +
    +    /**
    +     * Handles the actual execution of the whole pipeline.
    +     *
    +     * @param mixed $callable Optional callback for execution.
    +     * @return array
    +     */
    +    public function execute($callable = null)
    +    {
    +        if ($callable && !is_callable($callable)) {
    +            throw new \InvalidArgumentException('Argument passed must be a callable object');
    +        }
    +
    +        $this->setRunning(true);
    +        $pipelineBlockException = null;
    +
    +        try {
    +            if ($callable !== null) {
    +                call_user_func($callable, $this);
    +            }
    +            $this->flushPipeline();
    +        } catch (\Exception $exception) {
    +            $pipelineBlockException = $exception;
    +        }
    +
    +        $this->setRunning(false);
    +
    +        if ($pipelineBlockException !== null) {
    +            throw $pipelineBlockException;
    +        }
    +
    +        return $this->replies;
    +    }
    +
    +    /**
    +     * Returns the underlying client instance used by the pipeline object.
    +     *
    +     * @return ClientInterface
    +     */
    +    public function getClient()
    +    {
    +        return $this->client;
    +    }
    +
    +    /**
    +     * Returns the underlying pipeline executor used by the pipeline object.
    +     *
    +     * @return PipelineExecutorInterface
    +     */
    +    public function getExecutor()
    +    {
    +        return $this->executor;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Pipeline/PipelineExecutorInterface.php b/vendor/predis/predis/lib/Predis/Pipeline/PipelineExecutorInterface.php
    new file mode 100644
    index 0000000..76f1c44
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Pipeline/PipelineExecutorInterface.php
    @@ -0,0 +1,33 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Pipeline;
    +
    +use SplQueue;
    +use Predis\Connection\ConnectionInterface;
    +
    +/**
    + * Defines a strategy to write a list of commands to the network
    + * and read back their replies.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface PipelineExecutorInterface
    +{
    +    /**
    +     * Writes a list of commands to the network and reads back their replies.
    +     *
    +     * @param ConnectionInterface $connection Connection to Redis.
    +     * @param SplQueue $commands Commands queued for execution.
    +     * @return array
    +     */
    +    public function execute(ConnectionInterface $connection, SplQueue $commands);
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Pipeline/SafeClusterExecutor.php b/vendor/predis/predis/lib/Predis/Pipeline/SafeClusterExecutor.php
    new file mode 100644
    index 0000000..b362230
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Pipeline/SafeClusterExecutor.php
    @@ -0,0 +1,72 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Pipeline;
    +
    +use SplQueue;
    +use Predis\CommunicationException;
    +use Predis\Connection\ConnectionInterface;
    +
    +/**
    + * Implements a pipeline executor strategy for connection clusters that does
    + * not fail when an error is encountered, but adds the returned error in the
    + * replies array.
    + *
    + * @author Daniele Alessandri 
    + */
    +class SafeClusterExecutor implements PipelineExecutorInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function execute(ConnectionInterface $connection, SplQueue $commands)
    +    {
    +        $size = count($commands);
    +        $values = array();
    +        $connectionExceptions = array();
    +
    +        foreach ($commands as $command) {
    +            $cmdConnection = $connection->getConnection($command);
    +
    +            if (isset($connectionExceptions[spl_object_hash($cmdConnection)])) {
    +                continue;
    +            }
    +
    +            try {
    +                $cmdConnection->writeCommand($command);
    +            } catch (CommunicationException $exception) {
    +                $connectionExceptions[spl_object_hash($cmdConnection)] = $exception;
    +            }
    +        }
    +
    +        for ($i = 0; $i < $size; $i++) {
    +            $command = $commands->dequeue();
    +
    +            $cmdConnection = $connection->getConnection($command);
    +            $connectionObjectHash = spl_object_hash($cmdConnection);
    +
    +            if (isset($connectionExceptions[$connectionObjectHash])) {
    +                $values[$i] = $connectionExceptions[$connectionObjectHash];
    +                continue;
    +            }
    +
    +            try {
    +                $response = $cmdConnection->readResponse($command);
    +                $values[$i] = $response instanceof \Iterator ? iterator_to_array($response) : $response;
    +            } catch (CommunicationException $exception) {
    +                $values[$i] = $exception;
    +                $connectionExceptions[$connectionObjectHash] = $exception;
    +            }
    +        }
    +
    +        return $values;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Pipeline/SafeExecutor.php b/vendor/predis/predis/lib/Predis/Pipeline/SafeExecutor.php
    new file mode 100644
    index 0000000..2d693c0
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Pipeline/SafeExecutor.php
    @@ -0,0 +1,57 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Pipeline;
    +
    +use SplQueue;
    +use Predis\CommunicationException;
    +use Predis\Connection\ConnectionInterface;
    +
    +/**
    + * Implements a pipeline executor strategy that does not fail when an error is
    + * encountered, but adds the returned error in the replies array.
    + *
    + * @author Daniele Alessandri 
    + */
    +class SafeExecutor implements PipelineExecutorInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function execute(ConnectionInterface $connection, SplQueue $commands)
    +    {
    +        $size = count($commands);
    +        $values = array();
    +
    +        foreach ($commands as $command) {
    +            try {
    +                $connection->writeCommand($command);
    +            } catch (CommunicationException $exception) {
    +                return array_fill(0, $size, $exception);
    +            }
    +        }
    +
    +        for ($i = 0; $i < $size; $i++) {
    +            $command = $commands->dequeue();
    +
    +            try {
    +                $response = $connection->readResponse($command);
    +                $values[$i] = $response instanceof \Iterator ? iterator_to_array($response) : $response;
    +            } catch (CommunicationException $exception) {
    +                $toAdd = count($commands) - count($values);
    +                $values = array_merge($values, array_fill(0, $toAdd, $exception));
    +                break;
    +            }
    +        }
    +
    +        return $values;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Pipeline/StandardExecutor.php b/vendor/predis/predis/lib/Predis/Pipeline/StandardExecutor.php
    new file mode 100644
    index 0000000..805395a
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Pipeline/StandardExecutor.php
    @@ -0,0 +1,122 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Pipeline;
    +
    +use Iterator;
    +use SplQueue;
    +use Predis\ResponseErrorInterface;
    +use Predis\ResponseObjectInterface;
    +use Predis\ServerException;
    +use Predis\Command\CommandInterface;
    +use Predis\Connection\ConnectionInterface;
    +use Predis\Connection\ReplicationConnectionInterface;
    +
    +/**
    + * Implements the standard pipeline executor strategy used
    + * to write a list of commands and read their replies over
    + * a connection to Redis.
    + *
    + * @author Daniele Alessandri 
    + */
    +class StandardExecutor implements PipelineExecutorInterface
    +{
    +    protected $exceptions;
    +
    +    /**
    +     * @param bool $exceptions Specifies if the executor should throw exceptions on server errors.
    +     */
    +    public function __construct($exceptions = true)
    +    {
    +        $this->exceptions = (bool) $exceptions;
    +    }
    +
    +    /**
    +     * Allows the pipeline executor to perform operations on the
    +     * connection before starting to execute the commands stored
    +     * in the pipeline.
    +     *
    +     * @param ConnectionInterface $connection Connection instance.
    +     */
    +    protected function checkConnection(ConnectionInterface $connection)
    +    {
    +        if ($connection instanceof ReplicationConnectionInterface) {
    +            $connection->switchTo('master');
    +        }
    +    }
    +
    +    /**
    +     * Handles a response object.
    +     *
    +     * @param ConnectionInterface $connection
    +     * @param CommandInterface $command
    +     * @param ResponseObjectInterface $response
    +     * @return mixed
    +     */
    +    protected function onResponseObject(ConnectionInterface $connection, CommandInterface $command, ResponseObjectInterface $response)
    +    {
    +        if ($response instanceof ResponseErrorInterface) {
    +            return $this->onResponseError($connection, $response);
    +        }
    +
    +        if ($response instanceof Iterator) {
    +            return $command->parseResponse(iterator_to_array($response));
    +        }
    +
    +        return $response;
    +    }
    +
    +    /**
    +     * Handles -ERR responses returned by Redis.
    +     *
    +     * @param ConnectionInterface $connection The connection that returned the error.
    +     * @param ResponseErrorInterface $response The error response instance.
    +     */
    +    protected function onResponseError(ConnectionInterface $connection, ResponseErrorInterface $response)
    +    {
    +        if (!$this->exceptions) {
    +            return $response;
    +        }
    +
    +        // Force disconnection to prevent protocol desynchronization.
    +        $connection->disconnect();
    +        $message = $response->getMessage();
    +
    +        throw new ServerException($message);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function execute(ConnectionInterface $connection, SplQueue $commands)
    +    {
    +        $this->checkConnection($connection);
    +
    +        foreach ($commands as $command) {
    +            $connection->writeCommand($command);
    +        }
    +
    +        $values = array();
    +
    +        while (!$commands->isEmpty()) {
    +            $command = $commands->dequeue();
    +            $response = $connection->readResponse($command);
    +
    +            if ($response instanceof ResponseObjectInterface) {
    +                $values[] = $this->onResponseObject($connection, $command, $response);
    +            } else {
    +                $values[] = $command->parseResponse($response);
    +            }
    +        }
    +
    +        return $values;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/PredisException.php b/vendor/predis/predis/lib/Predis/PredisException.php
    new file mode 100644
    index 0000000..122bde1
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/PredisException.php
    @@ -0,0 +1,21 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +/**
    + * Base exception class for Predis-related errors.
    + *
    + * @author Daniele Alessandri 
    + */
    +abstract class PredisException extends \Exception
    +{
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Profile/ServerProfile.php b/vendor/predis/predis/lib/Predis/Profile/ServerProfile.php
    new file mode 100644
    index 0000000..9adb2f4
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Profile/ServerProfile.php
    @@ -0,0 +1,227 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Profile;
    +
    +use Predis\ClientException;
    +use Predis\Command\Processor\CommandProcessingInterface;
    +use Predis\Command\Processor\CommandProcessorInterface;
    +
    +/**
    + * Base class that implements common functionalities of server profiles.
    + *
    + * @author Daniele Alessandri 
    + */
    +abstract class ServerProfile implements ServerProfileInterface, CommandProcessingInterface
    +{
    +    private static $profiles;
    +
    +    private $commands;
    +    private $processor;
    +
    +    /**
    +     *
    +     */
    +    public function __construct()
    +    {
    +        $this->commands = $this->getSupportedCommands();
    +    }
    +
    +    /**
    +     * Returns a map of all the commands supported by the profile and their
    +     * actual PHP classes.
    +     *
    +     * @return array
    +     */
    +    protected abstract function getSupportedCommands();
    +
    +    /**
    +     * Returns the default server profile.
    +     *
    +     * @return ServerProfileInterface
    +     */
    +    public static function getDefault()
    +    {
    +        return self::get('default');
    +    }
    +
    +    /**
    +     * Returns the development server profile.
    +     *
    +     * @return ServerProfileInterface
    +     */
    +    public static function getDevelopment()
    +    {
    +        return self::get('dev');
    +    }
    +
    +    /**
    +     * Returns a map of all the server profiles supported by default and their
    +     * actual PHP classes.
    +     *
    +     * @return array
    +     */
    +    private static function getDefaultProfiles()
    +    {
    +        return array(
    +            '1.2'     => 'Predis\Profile\ServerVersion12',
    +            '2.0'     => 'Predis\Profile\ServerVersion20',
    +            '2.2'     => 'Predis\Profile\ServerVersion22',
    +            '2.4'     => 'Predis\Profile\ServerVersion24',
    +            '2.6'     => 'Predis\Profile\ServerVersion26',
    +            'default' => 'Predis\Profile\ServerVersion26',
    +            'dev'     => 'Predis\Profile\ServerVersionNext',
    +        );
    +    }
    +
    +    /**
    +     * Registers a new server profile.
    +     *
    +     * @param string $alias Profile version or alias.
    +     * @param string $profileClass FQN of a class implementing Predis\Profile\ServerProfileInterface.
    +     */
    +    public static function define($alias, $profileClass)
    +    {
    +        if (!isset(self::$profiles)) {
    +            self::$profiles = self::getDefaultProfiles();
    +        }
    +
    +        $profileReflection = new \ReflectionClass($profileClass);
    +
    +        if (!$profileReflection->isSubclassOf('Predis\Profile\ServerProfileInterface')) {
    +            throw new \InvalidArgumentException("Cannot register '$profileClass' as it is not a valid profile class");
    +        }
    +
    +        self::$profiles[$alias] = $profileClass;
    +    }
    +
    +    /**
    +     * Returns the specified server profile.
    +     *
    +     * @param string $version Profile version or alias.
    +     * @return ServerProfileInterface
    +     */
    +    public static function get($version)
    +    {
    +        if (!isset(self::$profiles)) {
    +            self::$profiles = self::getDefaultProfiles();
    +        }
    +
    +        if (!isset(self::$profiles[$version])) {
    +            throw new ClientException("Unknown server profile: $version");
    +        }
    +
    +        $profile = self::$profiles[$version];
    +
    +        return new $profile();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function supportsCommands(Array $commands)
    +    {
    +        foreach ($commands as $command) {
    +            if (!$this->supportsCommand($command)) {
    +                return false;
    +            }
    +        }
    +
    +        return true;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function supportsCommand($command)
    +    {
    +        return isset($this->commands[strtolower($command)]);
    +    }
    +
    +    /**
    +     * Returns the FQN of the class that represent the specified command ID
    +     * registered in the current server profile.
    +     *
    +     * @param string $command Command ID.
    +     * @return string
    +     */
    +    public function getCommandClass($command)
    +    {
    +        if (isset($this->commands[$command = strtolower($command)])) {
    +            return $this->commands[$command];
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function createCommand($method, $arguments = array())
    +    {
    +        $method = strtolower($method);
    +
    +        if (!isset($this->commands[$method])) {
    +            throw new ClientException("'$method' is not a registered Redis command");
    +        }
    +
    +        $commandClass = $this->commands[$method];
    +        $command = new $commandClass();
    +        $command->setArguments($arguments);
    +
    +        if (isset($this->processor)) {
    +            $this->processor->process($command);
    +        }
    +
    +        return $command;
    +    }
    +
    +    /**
    +     * Defines a new commands in the server profile.
    +     *
    +     * @param string $alias Command ID.
    +     * @param string $command FQN of a class implementing Predis\Command\CommandInterface.
    +     */
    +    public function defineCommand($alias, $command)
    +    {
    +        $commandReflection = new \ReflectionClass($command);
    +
    +        if (!$commandReflection->isSubclassOf('Predis\Command\CommandInterface')) {
    +            throw new \InvalidArgumentException("Cannot register '$command' as it is not a valid Redis command");
    +        }
    +
    +        $this->commands[strtolower($alias)] = $command;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function setProcessor(CommandProcessorInterface $processor = null)
    +    {
    +        $this->processor = $processor;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getProcessor()
    +    {
    +        return $this->processor;
    +    }
    +
    +    /**
    +     * Returns the version of server profile as its string representation.
    +     *
    +     * @return string
    +     */
    +    public function __toString()
    +    {
    +        return $this->getVersion();
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Profile/ServerProfileInterface.php b/vendor/predis/predis/lib/Predis/Profile/ServerProfileInterface.php
    new file mode 100644
    index 0000000..39477cb
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Profile/ServerProfileInterface.php
    @@ -0,0 +1,56 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Profile;
    +
    +use Predis\Command\CommandInterface;
    +
    +/**
    + * A server profile defines features and commands supported by certain
    + * versions of Redis. Instances of Predis\Client should use a server
    + * profile matching the version of Redis in use.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface ServerProfileInterface
    +{
    +    /**
    +     * Gets a profile version corresponding to a Redis version.
    +     *
    +     * @return string
    +     */
    +    public function getVersion();
    +
    +    /**
    +     * Checks if the profile supports the specified command.
    +     *
    +     * @param string $command Command ID.
    +     * @return Boolean
    +     */
    +    public function supportsCommand($command);
    +
    +    /**
    +     * Checks if the profile supports the specified list of commands.
    +     *
    +     * @param array $commands List of command IDs.
    +     * @return string
    +     */
    +    public function supportsCommands(Array $commands);
    +
    +    /**
    +     * Creates a new command instance.
    +     *
    +     * @param string $method Command ID.
    +     * @param array $arguments Arguments for the command.
    +     * @return CommandInterface
    +     */
    +    public function createCommand($method, $arguments = array());
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Profile/ServerVersion12.php b/vendor/predis/predis/lib/Predis/Profile/ServerVersion12.php
    new file mode 100644
    index 0000000..4e339a7
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Profile/ServerVersion12.php
    @@ -0,0 +1,125 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Profile;
    +
    +/**
    + * Server profile for Redis v1.2.x.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ServerVersion12 extends ServerProfile
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getVersion()
    +    {
    +        return '1.2';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getSupportedCommands()
    +    {
    +        return array(
    +            /* ---------------- Redis 1.2 ---------------- */
    +
    +            /* commands operating on the key space */
    +            'exists'                    => 'Predis\Command\KeyExists',
    +            'del'                       => 'Predis\Command\KeyDelete',
    +            'type'                      => 'Predis\Command\KeyType',
    +            'keys'                      => 'Predis\Command\KeyKeysV12x',
    +            'randomkey'                 => 'Predis\Command\KeyRandom',
    +            'rename'                    => 'Predis\Command\KeyRename',
    +            'renamenx'                  => 'Predis\Command\KeyRenamePreserve',
    +            'expire'                    => 'Predis\Command\KeyExpire',
    +            'expireat'                  => 'Predis\Command\KeyExpireAt',
    +            'ttl'                       => 'Predis\Command\KeyTimeToLive',
    +            'move'                      => 'Predis\Command\KeyMove',
    +            'sort'                      => 'Predis\Command\KeySort',
    +
    +            /* commands operating on string values */
    +            'set'                       => 'Predis\Command\StringSet',
    +            'setnx'                     => 'Predis\Command\StringSetPreserve',
    +            'mset'                      => 'Predis\Command\StringSetMultiple',
    +            'msetnx'                    => 'Predis\Command\StringSetMultiplePreserve',
    +            'get'                       => 'Predis\Command\StringGet',
    +            'mget'                      => 'Predis\Command\StringGetMultiple',
    +            'getset'                    => 'Predis\Command\StringGetSet',
    +            'incr'                      => 'Predis\Command\StringIncrement',
    +            'incrby'                    => 'Predis\Command\StringIncrementBy',
    +            'decr'                      => 'Predis\Command\StringDecrement',
    +            'decrby'                    => 'Predis\Command\StringDecrementBy',
    +
    +            /* commands operating on lists */
    +            'rpush'                     => 'Predis\Command\ListPushTail',
    +            'lpush'                     => 'Predis\Command\ListPushHead',
    +            'llen'                      => 'Predis\Command\ListLength',
    +            'lrange'                    => 'Predis\Command\ListRange',
    +            'ltrim'                     => 'Predis\Command\ListTrim',
    +            'lindex'                    => 'Predis\Command\ListIndex',
    +            'lset'                      => 'Predis\Command\ListSet',
    +            'lrem'                      => 'Predis\Command\ListRemove',
    +            'lpop'                      => 'Predis\Command\ListPopFirst',
    +            'rpop'                      => 'Predis\Command\ListPopLast',
    +            'rpoplpush'                 => 'Predis\Command\ListPopLastPushHead',
    +
    +            /* commands operating on sets */
    +            'sadd'                      => 'Predis\Command\SetAdd',
    +            'srem'                      => 'Predis\Command\SetRemove',
    +            'spop'                      => 'Predis\Command\SetPop',
    +            'smove'                     => 'Predis\Command\SetMove',
    +            'scard'                     => 'Predis\Command\SetCardinality',
    +            'sismember'                 => 'Predis\Command\SetIsMember',
    +            'sinter'                    => 'Predis\Command\SetIntersection',
    +            'sinterstore'               => 'Predis\Command\SetIntersectionStore',
    +            'sunion'                    => 'Predis\Command\SetUnion',
    +            'sunionstore'               => 'Predis\Command\SetUnionStore',
    +            'sdiff'                     => 'Predis\Command\SetDifference',
    +            'sdiffstore'                => 'Predis\Command\SetDifferenceStore',
    +            'smembers'                  => 'Predis\Command\SetMembers',
    +            'srandmember'               => 'Predis\Command\SetRandomMember',
    +
    +            /* commands operating on sorted sets */
    +            'zadd'                      => 'Predis\Command\ZSetAdd',
    +            'zincrby'                   => 'Predis\Command\ZSetIncrementBy',
    +            'zrem'                      => 'Predis\Command\ZSetRemove',
    +            'zrange'                    => 'Predis\Command\ZSetRange',
    +            'zrevrange'                 => 'Predis\Command\ZSetReverseRange',
    +            'zrangebyscore'             => 'Predis\Command\ZSetRangeByScore',
    +            'zcard'                     => 'Predis\Command\ZSetCardinality',
    +            'zscore'                    => 'Predis\Command\ZSetScore',
    +            'zremrangebyscore'          => 'Predis\Command\ZSetRemoveRangeByScore',
    +
    +            /* connection related commands */
    +            'ping'                      => 'Predis\Command\ConnectionPing',
    +            'auth'                      => 'Predis\Command\ConnectionAuth',
    +            'select'                    => 'Predis\Command\ConnectionSelect',
    +            'echo'                      => 'Predis\Command\ConnectionEcho',
    +            'quit'                      => 'Predis\Command\ConnectionQuit',
    +
    +            /* remote server control commands */
    +            'info'                      => 'Predis\Command\ServerInfo',
    +            'slaveof'                   => 'Predis\Command\ServerSlaveOf',
    +            'monitor'                   => 'Predis\Command\ServerMonitor',
    +            'dbsize'                    => 'Predis\Command\ServerDatabaseSize',
    +            'flushdb'                   => 'Predis\Command\ServerFlushDatabase',
    +            'flushall'                  => 'Predis\Command\ServerFlushAll',
    +            'save'                      => 'Predis\Command\ServerSave',
    +            'bgsave'                    => 'Predis\Command\ServerBackgroundSave',
    +            'lastsave'                  => 'Predis\Command\ServerLastSave',
    +            'shutdown'                  => 'Predis\Command\ServerShutdown',
    +            'bgrewriteaof'              => 'Predis\Command\ServerBackgroundRewriteAOF',
    +        );
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Profile/ServerVersion20.php b/vendor/predis/predis/lib/Predis/Profile/ServerVersion20.php
    new file mode 100644
    index 0000000..42b0b08
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Profile/ServerVersion20.php
    @@ -0,0 +1,174 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Profile;
    +
    +/**
    + * Server profile for Redis v2.0.x.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ServerVersion20 extends ServerProfile
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getVersion()
    +    {
    +        return '2.0';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getSupportedCommands()
    +    {
    +        return array(
    +            /* ---------------- Redis 1.2 ---------------- */
    +
    +            /* commands operating on the key space */
    +            'exists'                    => 'Predis\Command\KeyExists',
    +            'del'                       => 'Predis\Command\KeyDelete',
    +            'type'                      => 'Predis\Command\KeyType',
    +            'keys'                      => 'Predis\Command\KeyKeys',
    +            'randomkey'                 => 'Predis\Command\KeyRandom',
    +            'rename'                    => 'Predis\Command\KeyRename',
    +            'renamenx'                  => 'Predis\Command\KeyRenamePreserve',
    +            'expire'                    => 'Predis\Command\KeyExpire',
    +            'expireat'                  => 'Predis\Command\KeyExpireAt',
    +            'ttl'                       => 'Predis\Command\KeyTimeToLive',
    +            'move'                      => 'Predis\Command\KeyMove',
    +            'sort'                      => 'Predis\Command\KeySort',
    +
    +            /* commands operating on string values */
    +            'set'                       => 'Predis\Command\StringSet',
    +            'setnx'                     => 'Predis\Command\StringSetPreserve',
    +            'mset'                      => 'Predis\Command\StringSetMultiple',
    +            'msetnx'                    => 'Predis\Command\StringSetMultiplePreserve',
    +            'get'                       => 'Predis\Command\StringGet',
    +            'mget'                      => 'Predis\Command\StringGetMultiple',
    +            'getset'                    => 'Predis\Command\StringGetSet',
    +            'incr'                      => 'Predis\Command\StringIncrement',
    +            'incrby'                    => 'Predis\Command\StringIncrementBy',
    +            'decr'                      => 'Predis\Command\StringDecrement',
    +            'decrby'                    => 'Predis\Command\StringDecrementBy',
    +
    +            /* commands operating on lists */
    +            'rpush'                     => 'Predis\Command\ListPushTail',
    +            'lpush'                     => 'Predis\Command\ListPushHead',
    +            'llen'                      => 'Predis\Command\ListLength',
    +            'lrange'                    => 'Predis\Command\ListRange',
    +            'ltrim'                     => 'Predis\Command\ListTrim',
    +            'lindex'                    => 'Predis\Command\ListIndex',
    +            'lset'                      => 'Predis\Command\ListSet',
    +            'lrem'                      => 'Predis\Command\ListRemove',
    +            'lpop'                      => 'Predis\Command\ListPopFirst',
    +            'rpop'                      => 'Predis\Command\ListPopLast',
    +            'rpoplpush'                 => 'Predis\Command\ListPopLastPushHead',
    +
    +            /* commands operating on sets */
    +            'sadd'                      => 'Predis\Command\SetAdd',
    +            'srem'                      => 'Predis\Command\SetRemove',
    +            'spop'                      => 'Predis\Command\SetPop',
    +            'smove'                     => 'Predis\Command\SetMove',
    +            'scard'                     => 'Predis\Command\SetCardinality',
    +            'sismember'                 => 'Predis\Command\SetIsMember',
    +            'sinter'                    => 'Predis\Command\SetIntersection',
    +            'sinterstore'               => 'Predis\Command\SetIntersectionStore',
    +            'sunion'                    => 'Predis\Command\SetUnion',
    +            'sunionstore'               => 'Predis\Command\SetUnionStore',
    +            'sdiff'                     => 'Predis\Command\SetDifference',
    +            'sdiffstore'                => 'Predis\Command\SetDifferenceStore',
    +            'smembers'                  => 'Predis\Command\SetMembers',
    +            'srandmember'               => 'Predis\Command\SetRandomMember',
    +
    +            /* commands operating on sorted sets */
    +            'zadd'                      => 'Predis\Command\ZSetAdd',
    +            'zincrby'                   => 'Predis\Command\ZSetIncrementBy',
    +            'zrem'                      => 'Predis\Command\ZSetRemove',
    +            'zrange'                    => 'Predis\Command\ZSetRange',
    +            'zrevrange'                 => 'Predis\Command\ZSetReverseRange',
    +            'zrangebyscore'             => 'Predis\Command\ZSetRangeByScore',
    +            'zcard'                     => 'Predis\Command\ZSetCardinality',
    +            'zscore'                    => 'Predis\Command\ZSetScore',
    +            'zremrangebyscore'          => 'Predis\Command\ZSetRemoveRangeByScore',
    +
    +            /* connection related commands */
    +            'ping'                      => 'Predis\Command\ConnectionPing',
    +            'auth'                      => 'Predis\Command\ConnectionAuth',
    +            'select'                    => 'Predis\Command\ConnectionSelect',
    +            'echo'                      => 'Predis\Command\ConnectionEcho',
    +            'quit'                      => 'Predis\Command\ConnectionQuit',
    +
    +            /* remote server control commands */
    +            'info'                      => 'Predis\Command\ServerInfo',
    +            'slaveof'                   => 'Predis\Command\ServerSlaveOf',
    +            'monitor'                   => 'Predis\Command\ServerMonitor',
    +            'dbsize'                    => 'Predis\Command\ServerDatabaseSize',
    +            'flushdb'                   => 'Predis\Command\ServerFlushDatabase',
    +            'flushall'                  => 'Predis\Command\ServerFlushAll',
    +            'save'                      => 'Predis\Command\ServerSave',
    +            'bgsave'                    => 'Predis\Command\ServerBackgroundSave',
    +            'lastsave'                  => 'Predis\Command\ServerLastSave',
    +            'shutdown'                  => 'Predis\Command\ServerShutdown',
    +            'bgrewriteaof'              => 'Predis\Command\ServerBackgroundRewriteAOF',
    +
    +
    +            /* ---------------- Redis 2.0 ---------------- */
    +
    +            /* commands operating on string values */
    +            'setex'                     => 'Predis\Command\StringSetExpire',
    +            'append'                    => 'Predis\Command\StringAppend',
    +            'substr'                    => 'Predis\Command\StringSubstr',
    +
    +            /* commands operating on lists */
    +            'blpop'                     => 'Predis\Command\ListPopFirstBlocking',
    +            'brpop'                     => 'Predis\Command\ListPopLastBlocking',
    +
    +            /* commands operating on sorted sets */
    +            'zunionstore'               => 'Predis\Command\ZSetUnionStore',
    +            'zinterstore'               => 'Predis\Command\ZSetIntersectionStore',
    +            'zcount'                    => 'Predis\Command\ZSetCount',
    +            'zrank'                     => 'Predis\Command\ZSetRank',
    +            'zrevrank'                  => 'Predis\Command\ZSetReverseRank',
    +            'zremrangebyrank'           => 'Predis\Command\ZSetRemoveRangeByRank',
    +
    +            /* commands operating on hashes */
    +            'hset'                      => 'Predis\Command\HashSet',
    +            'hsetnx'                    => 'Predis\Command\HashSetPreserve',
    +            'hmset'                     => 'Predis\Command\HashSetMultiple',
    +            'hincrby'                   => 'Predis\Command\HashIncrementBy',
    +            'hget'                      => 'Predis\Command\HashGet',
    +            'hmget'                     => 'Predis\Command\HashGetMultiple',
    +            'hdel'                      => 'Predis\Command\HashDelete',
    +            'hexists'                   => 'Predis\Command\HashExists',
    +            'hlen'                      => 'Predis\Command\HashLength',
    +            'hkeys'                     => 'Predis\Command\HashKeys',
    +            'hvals'                     => 'Predis\Command\HashValues',
    +            'hgetall'                   => 'Predis\Command\HashGetAll',
    +
    +            /* transactions */
    +            'multi'                     => 'Predis\Command\TransactionMulti',
    +            'exec'                      => 'Predis\Command\TransactionExec',
    +            'discard'                   => 'Predis\Command\TransactionDiscard',
    +
    +            /* publish - subscribe */
    +            'subscribe'                 => 'Predis\Command\PubSubSubscribe',
    +            'unsubscribe'               => 'Predis\Command\PubSubUnsubscribe',
    +            'psubscribe'                => 'Predis\Command\PubSubSubscribeByPattern',
    +            'punsubscribe'              => 'Predis\Command\PubSubUnsubscribeByPattern',
    +            'publish'                   => 'Predis\Command\PubSubPublish',
    +
    +            /* remote server control commands */
    +            'config'                    => 'Predis\Command\ServerConfig',
    +        );
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Profile/ServerVersion22.php b/vendor/predis/predis/lib/Predis/Profile/ServerVersion22.php
    new file mode 100644
    index 0000000..e97f422
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Profile/ServerVersion22.php
    @@ -0,0 +1,204 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Profile;
    +
    +/**
    + * Server profile for Redis v2.2.x.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ServerVersion22 extends ServerProfile
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getVersion()
    +    {
    +        return '2.2';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getSupportedCommands()
    +    {
    +        return array(
    +            /* ---------------- Redis 1.2 ---------------- */
    +
    +            /* commands operating on the key space */
    +            'exists'                    => 'Predis\Command\KeyExists',
    +            'del'                       => 'Predis\Command\KeyDelete',
    +            'type'                      => 'Predis\Command\KeyType',
    +            'keys'                      => 'Predis\Command\KeyKeys',
    +            'randomkey'                 => 'Predis\Command\KeyRandom',
    +            'rename'                    => 'Predis\Command\KeyRename',
    +            'renamenx'                  => 'Predis\Command\KeyRenamePreserve',
    +            'expire'                    => 'Predis\Command\KeyExpire',
    +            'expireat'                  => 'Predis\Command\KeyExpireAt',
    +            'ttl'                       => 'Predis\Command\KeyTimeToLive',
    +            'move'                      => 'Predis\Command\KeyMove',
    +            'sort'                      => 'Predis\Command\KeySort',
    +
    +            /* commands operating on string values */
    +            'set'                       => 'Predis\Command\StringSet',
    +            'setnx'                     => 'Predis\Command\StringSetPreserve',
    +            'mset'                      => 'Predis\Command\StringSetMultiple',
    +            'msetnx'                    => 'Predis\Command\StringSetMultiplePreserve',
    +            'get'                       => 'Predis\Command\StringGet',
    +            'mget'                      => 'Predis\Command\StringGetMultiple',
    +            'getset'                    => 'Predis\Command\StringGetSet',
    +            'incr'                      => 'Predis\Command\StringIncrement',
    +            'incrby'                    => 'Predis\Command\StringIncrementBy',
    +            'decr'                      => 'Predis\Command\StringDecrement',
    +            'decrby'                    => 'Predis\Command\StringDecrementBy',
    +
    +            /* commands operating on lists */
    +            'rpush'                     => 'Predis\Command\ListPushTail',
    +            'lpush'                     => 'Predis\Command\ListPushHead',
    +            'llen'                      => 'Predis\Command\ListLength',
    +            'lrange'                    => 'Predis\Command\ListRange',
    +            'ltrim'                     => 'Predis\Command\ListTrim',
    +            'lindex'                    => 'Predis\Command\ListIndex',
    +            'lset'                      => 'Predis\Command\ListSet',
    +            'lrem'                      => 'Predis\Command\ListRemove',
    +            'lpop'                      => 'Predis\Command\ListPopFirst',
    +            'rpop'                      => 'Predis\Command\ListPopLast',
    +            'rpoplpush'                 => 'Predis\Command\ListPopLastPushHead',
    +
    +            /* commands operating on sets */
    +            'sadd'                      => 'Predis\Command\SetAdd',
    +            'srem'                      => 'Predis\Command\SetRemove',
    +            'spop'                      => 'Predis\Command\SetPop',
    +            'smove'                     => 'Predis\Command\SetMove',
    +            'scard'                     => 'Predis\Command\SetCardinality',
    +            'sismember'                 => 'Predis\Command\SetIsMember',
    +            'sinter'                    => 'Predis\Command\SetIntersection',
    +            'sinterstore'               => 'Predis\Command\SetIntersectionStore',
    +            'sunion'                    => 'Predis\Command\SetUnion',
    +            'sunionstore'               => 'Predis\Command\SetUnionStore',
    +            'sdiff'                     => 'Predis\Command\SetDifference',
    +            'sdiffstore'                => 'Predis\Command\SetDifferenceStore',
    +            'smembers'                  => 'Predis\Command\SetMembers',
    +            'srandmember'               => 'Predis\Command\SetRandomMember',
    +
    +            /* commands operating on sorted sets */
    +            'zadd'                      => 'Predis\Command\ZSetAdd',
    +            'zincrby'                   => 'Predis\Command\ZSetIncrementBy',
    +            'zrem'                      => 'Predis\Command\ZSetRemove',
    +            'zrange'                    => 'Predis\Command\ZSetRange',
    +            'zrevrange'                 => 'Predis\Command\ZSetReverseRange',
    +            'zrangebyscore'             => 'Predis\Command\ZSetRangeByScore',
    +            'zcard'                     => 'Predis\Command\ZSetCardinality',
    +            'zscore'                    => 'Predis\Command\ZSetScore',
    +            'zremrangebyscore'          => 'Predis\Command\ZSetRemoveRangeByScore',
    +
    +            /* connection related commands */
    +            'ping'                      => 'Predis\Command\ConnectionPing',
    +            'auth'                      => 'Predis\Command\ConnectionAuth',
    +            'select'                    => 'Predis\Command\ConnectionSelect',
    +            'echo'                      => 'Predis\Command\ConnectionEcho',
    +            'quit'                      => 'Predis\Command\ConnectionQuit',
    +
    +            /* remote server control commands */
    +            'info'                      => 'Predis\Command\ServerInfo',
    +            'slaveof'                   => 'Predis\Command\ServerSlaveOf',
    +            'monitor'                   => 'Predis\Command\ServerMonitor',
    +            'dbsize'                    => 'Predis\Command\ServerDatabaseSize',
    +            'flushdb'                   => 'Predis\Command\ServerFlushDatabase',
    +            'flushall'                  => 'Predis\Command\ServerFlushAll',
    +            'save'                      => 'Predis\Command\ServerSave',
    +            'bgsave'                    => 'Predis\Command\ServerBackgroundSave',
    +            'lastsave'                  => 'Predis\Command\ServerLastSave',
    +            'shutdown'                  => 'Predis\Command\ServerShutdown',
    +            'bgrewriteaof'              => 'Predis\Command\ServerBackgroundRewriteAOF',
    +
    +
    +            /* ---------------- Redis 2.0 ---------------- */
    +
    +            /* commands operating on string values */
    +            'setex'                     => 'Predis\Command\StringSetExpire',
    +            'append'                    => 'Predis\Command\StringAppend',
    +            'substr'                    => 'Predis\Command\StringSubstr',
    +
    +            /* commands operating on lists */
    +            'blpop'                     => 'Predis\Command\ListPopFirstBlocking',
    +            'brpop'                     => 'Predis\Command\ListPopLastBlocking',
    +
    +            /* commands operating on sorted sets */
    +            'zunionstore'               => 'Predis\Command\ZSetUnionStore',
    +            'zinterstore'               => 'Predis\Command\ZSetIntersectionStore',
    +            'zcount'                    => 'Predis\Command\ZSetCount',
    +            'zrank'                     => 'Predis\Command\ZSetRank',
    +            'zrevrank'                  => 'Predis\Command\ZSetReverseRank',
    +            'zremrangebyrank'           => 'Predis\Command\ZSetRemoveRangeByRank',
    +
    +            /* commands operating on hashes */
    +            'hset'                      => 'Predis\Command\HashSet',
    +            'hsetnx'                    => 'Predis\Command\HashSetPreserve',
    +            'hmset'                     => 'Predis\Command\HashSetMultiple',
    +            'hincrby'                   => 'Predis\Command\HashIncrementBy',
    +            'hget'                      => 'Predis\Command\HashGet',
    +            'hmget'                     => 'Predis\Command\HashGetMultiple',
    +            'hdel'                      => 'Predis\Command\HashDelete',
    +            'hexists'                   => 'Predis\Command\HashExists',
    +            'hlen'                      => 'Predis\Command\HashLength',
    +            'hkeys'                     => 'Predis\Command\HashKeys',
    +            'hvals'                     => 'Predis\Command\HashValues',
    +            'hgetall'                   => 'Predis\Command\HashGetAll',
    +
    +            /* transactions */
    +            'multi'                     => 'Predis\Command\TransactionMulti',
    +            'exec'                      => 'Predis\Command\TransactionExec',
    +            'discard'                   => 'Predis\Command\TransactionDiscard',
    +
    +            /* publish - subscribe */
    +            'subscribe'                 => 'Predis\Command\PubSubSubscribe',
    +            'unsubscribe'               => 'Predis\Command\PubSubUnsubscribe',
    +            'psubscribe'                => 'Predis\Command\PubSubSubscribeByPattern',
    +            'punsubscribe'              => 'Predis\Command\PubSubUnsubscribeByPattern',
    +            'publish'                   => 'Predis\Command\PubSubPublish',
    +
    +            /* remote server control commands */
    +            'config'                    => 'Predis\Command\ServerConfig',
    +
    +
    +            /* ---------------- Redis 2.2 ---------------- */
    +
    +            /* commands operating on the key space */
    +            'persist'                   => 'Predis\Command\KeyPersist',
    +
    +            /* commands operating on string values */
    +            'strlen'                    => 'Predis\Command\StringStrlen',
    +            'setrange'                  => 'Predis\Command\StringSetRange',
    +            'getrange'                  => 'Predis\Command\StringGetRange',
    +            'setbit'                    => 'Predis\Command\StringSetBit',
    +            'getbit'                    => 'Predis\Command\StringGetBit',
    +
    +            /* commands operating on lists */
    +            'rpushx'                    => 'Predis\Command\ListPushTailX',
    +            'lpushx'                    => 'Predis\Command\ListPushHeadX',
    +            'linsert'                   => 'Predis\Command\ListInsert',
    +            'brpoplpush'                => 'Predis\Command\ListPopLastPushHeadBlocking',
    +
    +            /* commands operating on sorted sets */
    +            'zrevrangebyscore'          => 'Predis\Command\ZSetReverseRangeByScore',
    +
    +            /* transactions */
    +            'watch'                     => 'Predis\Command\TransactionWatch',
    +            'unwatch'                   => 'Predis\Command\TransactionUnwatch',
    +
    +            /* remote server control commands */
    +            'object'                    => 'Predis\Command\ServerObject',
    +            'slowlog'                   => 'Predis\Command\ServerSlowlog',
    +        );
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Profile/ServerVersion24.php b/vendor/predis/predis/lib/Predis/Profile/ServerVersion24.php
    new file mode 100644
    index 0000000..20569c6
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Profile/ServerVersion24.php
    @@ -0,0 +1,210 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Profile;
    +
    +/**
    + * Server profile for Redis v2.4.x.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ServerVersion24 extends ServerProfile
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getVersion()
    +    {
    +        return '2.4';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getSupportedCommands()
    +    {
    +        return array(
    +            /* ---------------- Redis 1.2 ---------------- */
    +
    +            /* commands operating on the key space */
    +            'exists'                    => 'Predis\Command\KeyExists',
    +            'del'                       => 'Predis\Command\KeyDelete',
    +            'type'                      => 'Predis\Command\KeyType',
    +            'keys'                      => 'Predis\Command\KeyKeys',
    +            'randomkey'                 => 'Predis\Command\KeyRandom',
    +            'rename'                    => 'Predis\Command\KeyRename',
    +            'renamenx'                  => 'Predis\Command\KeyRenamePreserve',
    +            'expire'                    => 'Predis\Command\KeyExpire',
    +            'expireat'                  => 'Predis\Command\KeyExpireAt',
    +            'ttl'                       => 'Predis\Command\KeyTimeToLive',
    +            'move'                      => 'Predis\Command\KeyMove',
    +            'sort'                      => 'Predis\Command\KeySort',
    +
    +            /* commands operating on string values */
    +            'set'                       => 'Predis\Command\StringSet',
    +            'setnx'                     => 'Predis\Command\StringSetPreserve',
    +            'mset'                      => 'Predis\Command\StringSetMultiple',
    +            'msetnx'                    => 'Predis\Command\StringSetMultiplePreserve',
    +            'get'                       => 'Predis\Command\StringGet',
    +            'mget'                      => 'Predis\Command\StringGetMultiple',
    +            'getset'                    => 'Predis\Command\StringGetSet',
    +            'incr'                      => 'Predis\Command\StringIncrement',
    +            'incrby'                    => 'Predis\Command\StringIncrementBy',
    +            'decr'                      => 'Predis\Command\StringDecrement',
    +            'decrby'                    => 'Predis\Command\StringDecrementBy',
    +
    +            /* commands operating on lists */
    +            'rpush'                     => 'Predis\Command\ListPushTail',
    +            'lpush'                     => 'Predis\Command\ListPushHead',
    +            'llen'                      => 'Predis\Command\ListLength',
    +            'lrange'                    => 'Predis\Command\ListRange',
    +            'ltrim'                     => 'Predis\Command\ListTrim',
    +            'lindex'                    => 'Predis\Command\ListIndex',
    +            'lset'                      => 'Predis\Command\ListSet',
    +            'lrem'                      => 'Predis\Command\ListRemove',
    +            'lpop'                      => 'Predis\Command\ListPopFirst',
    +            'rpop'                      => 'Predis\Command\ListPopLast',
    +            'rpoplpush'                 => 'Predis\Command\ListPopLastPushHead',
    +
    +            /* commands operating on sets */
    +            'sadd'                      => 'Predis\Command\SetAdd',
    +            'srem'                      => 'Predis\Command\SetRemove',
    +            'spop'                      => 'Predis\Command\SetPop',
    +            'smove'                     => 'Predis\Command\SetMove',
    +            'scard'                     => 'Predis\Command\SetCardinality',
    +            'sismember'                 => 'Predis\Command\SetIsMember',
    +            'sinter'                    => 'Predis\Command\SetIntersection',
    +            'sinterstore'               => 'Predis\Command\SetIntersectionStore',
    +            'sunion'                    => 'Predis\Command\SetUnion',
    +            'sunionstore'               => 'Predis\Command\SetUnionStore',
    +            'sdiff'                     => 'Predis\Command\SetDifference',
    +            'sdiffstore'                => 'Predis\Command\SetDifferenceStore',
    +            'smembers'                  => 'Predis\Command\SetMembers',
    +            'srandmember'               => 'Predis\Command\SetRandomMember',
    +
    +            /* commands operating on sorted sets */
    +            'zadd'                      => 'Predis\Command\ZSetAdd',
    +            'zincrby'                   => 'Predis\Command\ZSetIncrementBy',
    +            'zrem'                      => 'Predis\Command\ZSetRemove',
    +            'zrange'                    => 'Predis\Command\ZSetRange',
    +            'zrevrange'                 => 'Predis\Command\ZSetReverseRange',
    +            'zrangebyscore'             => 'Predis\Command\ZSetRangeByScore',
    +            'zcard'                     => 'Predis\Command\ZSetCardinality',
    +            'zscore'                    => 'Predis\Command\ZSetScore',
    +            'zremrangebyscore'          => 'Predis\Command\ZSetRemoveRangeByScore',
    +
    +            /* connection related commands */
    +            'ping'                      => 'Predis\Command\ConnectionPing',
    +            'auth'                      => 'Predis\Command\ConnectionAuth',
    +            'select'                    => 'Predis\Command\ConnectionSelect',
    +            'echo'                      => 'Predis\Command\ConnectionEcho',
    +            'quit'                      => 'Predis\Command\ConnectionQuit',
    +
    +            /* remote server control commands */
    +            'info'                      => 'Predis\Command\ServerInfo',
    +            'slaveof'                   => 'Predis\Command\ServerSlaveOf',
    +            'monitor'                   => 'Predis\Command\ServerMonitor',
    +            'dbsize'                    => 'Predis\Command\ServerDatabaseSize',
    +            'flushdb'                   => 'Predis\Command\ServerFlushDatabase',
    +            'flushall'                  => 'Predis\Command\ServerFlushAll',
    +            'save'                      => 'Predis\Command\ServerSave',
    +            'bgsave'                    => 'Predis\Command\ServerBackgroundSave',
    +            'lastsave'                  => 'Predis\Command\ServerLastSave',
    +            'shutdown'                  => 'Predis\Command\ServerShutdown',
    +            'bgrewriteaof'              => 'Predis\Command\ServerBackgroundRewriteAOF',
    +
    +
    +            /* ---------------- Redis 2.0 ---------------- */
    +
    +            /* commands operating on string values */
    +            'setex'                     => 'Predis\Command\StringSetExpire',
    +            'append'                    => 'Predis\Command\StringAppend',
    +            'substr'                    => 'Predis\Command\StringSubstr',
    +
    +            /* commands operating on lists */
    +            'blpop'                     => 'Predis\Command\ListPopFirstBlocking',
    +            'brpop'                     => 'Predis\Command\ListPopLastBlocking',
    +
    +            /* commands operating on sorted sets */
    +            'zunionstore'               => 'Predis\Command\ZSetUnionStore',
    +            'zinterstore'               => 'Predis\Command\ZSetIntersectionStore',
    +            'zcount'                    => 'Predis\Command\ZSetCount',
    +            'zrank'                     => 'Predis\Command\ZSetRank',
    +            'zrevrank'                  => 'Predis\Command\ZSetReverseRank',
    +            'zremrangebyrank'           => 'Predis\Command\ZSetRemoveRangeByRank',
    +
    +            /* commands operating on hashes */
    +            'hset'                      => 'Predis\Command\HashSet',
    +            'hsetnx'                    => 'Predis\Command\HashSetPreserve',
    +            'hmset'                     => 'Predis\Command\HashSetMultiple',
    +            'hincrby'                   => 'Predis\Command\HashIncrementBy',
    +            'hget'                      => 'Predis\Command\HashGet',
    +            'hmget'                     => 'Predis\Command\HashGetMultiple',
    +            'hdel'                      => 'Predis\Command\HashDelete',
    +            'hexists'                   => 'Predis\Command\HashExists',
    +            'hlen'                      => 'Predis\Command\HashLength',
    +            'hkeys'                     => 'Predis\Command\HashKeys',
    +            'hvals'                     => 'Predis\Command\HashValues',
    +            'hgetall'                   => 'Predis\Command\HashGetAll',
    +
    +            /* transactions */
    +            'multi'                     => 'Predis\Command\TransactionMulti',
    +            'exec'                      => 'Predis\Command\TransactionExec',
    +            'discard'                   => 'Predis\Command\TransactionDiscard',
    +
    +            /* publish - subscribe */
    +            'subscribe'                 => 'Predis\Command\PubSubSubscribe',
    +            'unsubscribe'               => 'Predis\Command\PubSubUnsubscribe',
    +            'psubscribe'                => 'Predis\Command\PubSubSubscribeByPattern',
    +            'punsubscribe'              => 'Predis\Command\PubSubUnsubscribeByPattern',
    +            'publish'                   => 'Predis\Command\PubSubPublish',
    +
    +            /* remote server control commands */
    +            'config'                    => 'Predis\Command\ServerConfig',
    +
    +
    +            /* ---------------- Redis 2.2 ---------------- */
    +
    +            /* commands operating on the key space */
    +            'persist'                   => 'Predis\Command\KeyPersist',
    +
    +            /* commands operating on string values */
    +            'strlen'                    => 'Predis\Command\StringStrlen',
    +            'setrange'                  => 'Predis\Command\StringSetRange',
    +            'getrange'                  => 'Predis\Command\StringGetRange',
    +            'setbit'                    => 'Predis\Command\StringSetBit',
    +            'getbit'                    => 'Predis\Command\StringGetBit',
    +
    +            /* commands operating on lists */
    +            'rpushx'                    => 'Predis\Command\ListPushTailX',
    +            'lpushx'                    => 'Predis\Command\ListPushHeadX',
    +            'linsert'                   => 'Predis\Command\ListInsert',
    +            'brpoplpush'                => 'Predis\Command\ListPopLastPushHeadBlocking',
    +
    +            /* commands operating on sorted sets */
    +            'zrevrangebyscore'          => 'Predis\Command\ZSetReverseRangeByScore',
    +
    +            /* transactions */
    +            'watch'                     => 'Predis\Command\TransactionWatch',
    +            'unwatch'                   => 'Predis\Command\TransactionUnwatch',
    +
    +            /* remote server control commands */
    +            'object'                    => 'Predis\Command\ServerObject',
    +            'slowlog'                   => 'Predis\Command\ServerSlowlog',
    +
    +
    +            /* ---------------- Redis 2.4 ---------------- */
    +
    +            /* remote server control commands */
    +            'client'                    => 'Predis\Command\ServerClient',
    +        );
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Profile/ServerVersion26.php b/vendor/predis/predis/lib/Predis/Profile/ServerVersion26.php
    new file mode 100644
    index 0000000..af3c0d2
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Profile/ServerVersion26.php
    @@ -0,0 +1,238 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Profile;
    +
    +/**
    + * Server profile for Redis v2.6.x.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ServerVersion26 extends ServerProfile
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getVersion()
    +    {
    +        return '2.6';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getSupportedCommands()
    +    {
    +        return array(
    +            /* ---------------- Redis 1.2 ---------------- */
    +
    +            /* commands operating on the key space */
    +            'exists'                    => 'Predis\Command\KeyExists',
    +            'del'                       => 'Predis\Command\KeyDelete',
    +            'type'                      => 'Predis\Command\KeyType',
    +            'keys'                      => 'Predis\Command\KeyKeys',
    +            'randomkey'                 => 'Predis\Command\KeyRandom',
    +            'rename'                    => 'Predis\Command\KeyRename',
    +            'renamenx'                  => 'Predis\Command\KeyRenamePreserve',
    +            'expire'                    => 'Predis\Command\KeyExpire',
    +            'expireat'                  => 'Predis\Command\KeyExpireAt',
    +            'ttl'                       => 'Predis\Command\KeyTimeToLive',
    +            'move'                      => 'Predis\Command\KeyMove',
    +            'sort'                      => 'Predis\Command\KeySort',
    +            'dump'                      => 'Predis\Command\KeyDump',
    +            'restore'                   => 'Predis\Command\KeyRestore',
    +
    +            /* commands operating on string values */
    +            'set'                       => 'Predis\Command\StringSet',
    +            'setnx'                     => 'Predis\Command\StringSetPreserve',
    +            'mset'                      => 'Predis\Command\StringSetMultiple',
    +            'msetnx'                    => 'Predis\Command\StringSetMultiplePreserve',
    +            'get'                       => 'Predis\Command\StringGet',
    +            'mget'                      => 'Predis\Command\StringGetMultiple',
    +            'getset'                    => 'Predis\Command\StringGetSet',
    +            'incr'                      => 'Predis\Command\StringIncrement',
    +            'incrby'                    => 'Predis\Command\StringIncrementBy',
    +            'decr'                      => 'Predis\Command\StringDecrement',
    +            'decrby'                    => 'Predis\Command\StringDecrementBy',
    +
    +            /* commands operating on lists */
    +            'rpush'                     => 'Predis\Command\ListPushTail',
    +            'lpush'                     => 'Predis\Command\ListPushHead',
    +            'llen'                      => 'Predis\Command\ListLength',
    +            'lrange'                    => 'Predis\Command\ListRange',
    +            'ltrim'                     => 'Predis\Command\ListTrim',
    +            'lindex'                    => 'Predis\Command\ListIndex',
    +            'lset'                      => 'Predis\Command\ListSet',
    +            'lrem'                      => 'Predis\Command\ListRemove',
    +            'lpop'                      => 'Predis\Command\ListPopFirst',
    +            'rpop'                      => 'Predis\Command\ListPopLast',
    +            'rpoplpush'                 => 'Predis\Command\ListPopLastPushHead',
    +
    +            /* commands operating on sets */
    +            'sadd'                      => 'Predis\Command\SetAdd',
    +            'srem'                      => 'Predis\Command\SetRemove',
    +            'spop'                      => 'Predis\Command\SetPop',
    +            'smove'                     => 'Predis\Command\SetMove',
    +            'scard'                     => 'Predis\Command\SetCardinality',
    +            'sismember'                 => 'Predis\Command\SetIsMember',
    +            'sinter'                    => 'Predis\Command\SetIntersection',
    +            'sinterstore'               => 'Predis\Command\SetIntersectionStore',
    +            'sunion'                    => 'Predis\Command\SetUnion',
    +            'sunionstore'               => 'Predis\Command\SetUnionStore',
    +            'sdiff'                     => 'Predis\Command\SetDifference',
    +            'sdiffstore'                => 'Predis\Command\SetDifferenceStore',
    +            'smembers'                  => 'Predis\Command\SetMembers',
    +            'srandmember'               => 'Predis\Command\SetRandomMember',
    +
    +            /* commands operating on sorted sets */
    +            'zadd'                      => 'Predis\Command\ZSetAdd',
    +            'zincrby'                   => 'Predis\Command\ZSetIncrementBy',
    +            'zrem'                      => 'Predis\Command\ZSetRemove',
    +            'zrange'                    => 'Predis\Command\ZSetRange',
    +            'zrevrange'                 => 'Predis\Command\ZSetReverseRange',
    +            'zrangebyscore'             => 'Predis\Command\ZSetRangeByScore',
    +            'zcard'                     => 'Predis\Command\ZSetCardinality',
    +            'zscore'                    => 'Predis\Command\ZSetScore',
    +            'zremrangebyscore'          => 'Predis\Command\ZSetRemoveRangeByScore',
    +
    +            /* connection related commands */
    +            'ping'                      => 'Predis\Command\ConnectionPing',
    +            'auth'                      => 'Predis\Command\ConnectionAuth',
    +            'select'                    => 'Predis\Command\ConnectionSelect',
    +            'echo'                      => 'Predis\Command\ConnectionEcho',
    +            'quit'                      => 'Predis\Command\ConnectionQuit',
    +
    +            /* remote server control commands */
    +            'info'                      => 'Predis\Command\ServerInfo',
    +            'slaveof'                   => 'Predis\Command\ServerSlaveOf',
    +            'monitor'                   => 'Predis\Command\ServerMonitor',
    +            'dbsize'                    => 'Predis\Command\ServerDatabaseSize',
    +            'flushdb'                   => 'Predis\Command\ServerFlushDatabase',
    +            'flushall'                  => 'Predis\Command\ServerFlushAll',
    +            'save'                      => 'Predis\Command\ServerSave',
    +            'bgsave'                    => 'Predis\Command\ServerBackgroundSave',
    +            'lastsave'                  => 'Predis\Command\ServerLastSave',
    +            'shutdown'                  => 'Predis\Command\ServerShutdown',
    +            'bgrewriteaof'              => 'Predis\Command\ServerBackgroundRewriteAOF',
    +
    +
    +            /* ---------------- Redis 2.0 ---------------- */
    +
    +            /* commands operating on string values */
    +            'setex'                     => 'Predis\Command\StringSetExpire',
    +            'append'                    => 'Predis\Command\StringAppend',
    +            'substr'                    => 'Predis\Command\StringSubstr',
    +
    +            /* commands operating on lists */
    +            'blpop'                     => 'Predis\Command\ListPopFirstBlocking',
    +            'brpop'                     => 'Predis\Command\ListPopLastBlocking',
    +
    +            /* commands operating on sorted sets */
    +            'zunionstore'               => 'Predis\Command\ZSetUnionStore',
    +            'zinterstore'               => 'Predis\Command\ZSetIntersectionStore',
    +            'zcount'                    => 'Predis\Command\ZSetCount',
    +            'zrank'                     => 'Predis\Command\ZSetRank',
    +            'zrevrank'                  => 'Predis\Command\ZSetReverseRank',
    +            'zremrangebyrank'           => 'Predis\Command\ZSetRemoveRangeByRank',
    +
    +            /* commands operating on hashes */
    +            'hset'                      => 'Predis\Command\HashSet',
    +            'hsetnx'                    => 'Predis\Command\HashSetPreserve',
    +            'hmset'                     => 'Predis\Command\HashSetMultiple',
    +            'hincrby'                   => 'Predis\Command\HashIncrementBy',
    +            'hget'                      => 'Predis\Command\HashGet',
    +            'hmget'                     => 'Predis\Command\HashGetMultiple',
    +            'hdel'                      => 'Predis\Command\HashDelete',
    +            'hexists'                   => 'Predis\Command\HashExists',
    +            'hlen'                      => 'Predis\Command\HashLength',
    +            'hkeys'                     => 'Predis\Command\HashKeys',
    +            'hvals'                     => 'Predis\Command\HashValues',
    +            'hgetall'                   => 'Predis\Command\HashGetAll',
    +
    +            /* transactions */
    +            'multi'                     => 'Predis\Command\TransactionMulti',
    +            'exec'                      => 'Predis\Command\TransactionExec',
    +            'discard'                   => 'Predis\Command\TransactionDiscard',
    +
    +            /* publish - subscribe */
    +            'subscribe'                 => 'Predis\Command\PubSubSubscribe',
    +            'unsubscribe'               => 'Predis\Command\PubSubUnsubscribe',
    +            'psubscribe'                => 'Predis\Command\PubSubSubscribeByPattern',
    +            'punsubscribe'              => 'Predis\Command\PubSubUnsubscribeByPattern',
    +            'publish'                   => 'Predis\Command\PubSubPublish',
    +
    +            /* remote server control commands */
    +            'config'                    => 'Predis\Command\ServerConfig',
    +
    +
    +            /* ---------------- Redis 2.2 ---------------- */
    +
    +            /* commands operating on the key space */
    +            'persist'                   => 'Predis\Command\KeyPersist',
    +
    +            /* commands operating on string values */
    +            'strlen'                    => 'Predis\Command\StringStrlen',
    +            'setrange'                  => 'Predis\Command\StringSetRange',
    +            'getrange'                  => 'Predis\Command\StringGetRange',
    +            'setbit'                    => 'Predis\Command\StringSetBit',
    +            'getbit'                    => 'Predis\Command\StringGetBit',
    +
    +            /* commands operating on lists */
    +            'rpushx'                    => 'Predis\Command\ListPushTailX',
    +            'lpushx'                    => 'Predis\Command\ListPushHeadX',
    +            'linsert'                   => 'Predis\Command\ListInsert',
    +            'brpoplpush'                => 'Predis\Command\ListPopLastPushHeadBlocking',
    +
    +            /* commands operating on sorted sets */
    +            'zrevrangebyscore'          => 'Predis\Command\ZSetReverseRangeByScore',
    +
    +            /* transactions */
    +            'watch'                     => 'Predis\Command\TransactionWatch',
    +            'unwatch'                   => 'Predis\Command\TransactionUnwatch',
    +
    +            /* remote server control commands */
    +            'object'                    => 'Predis\Command\ServerObject',
    +            'slowlog'                   => 'Predis\Command\ServerSlowlog',
    +
    +
    +            /* ---------------- Redis 2.4 ---------------- */
    +
    +            /* remote server control commands */
    +            'client'                    => 'Predis\Command\ServerClient',
    +
    +
    +            /* ---------------- Redis 2.6 ---------------- */
    +
    +            /* commands operating on the key space */
    +            'pttl'                      => 'Predis\Command\KeyPreciseTimeToLive',
    +            'pexpire'                   => 'Predis\Command\KeyPreciseExpire',
    +            'pexpireat'                 => 'Predis\Command\KeyPreciseExpireAt',
    +
    +            /* commands operating on string values */
    +            'psetex'                    => 'Predis\Command\StringPreciseSetExpire',
    +            'incrbyfloat'               => 'Predis\Command\StringIncrementByFloat',
    +            'bitop'                     => 'Predis\Command\StringBitOp',
    +            'bitcount'                  => 'Predis\Command\StringBitCount',
    +
    +            /* commands operating on hashes */
    +            'hincrbyfloat'              => 'Predis\Command\HashIncrementByFloat',
    +
    +            /* scripting */
    +            'eval'                      => 'Predis\Command\ServerEval',
    +            'evalsha'                   => 'Predis\Command\ServerEvalSHA',
    +            'script'                    => 'Predis\Command\ServerScript',
    +
    +            /* remote server control commands */
    +            'info'                      => 'Predis\Command\ServerInfoV26x',
    +            'time'                      => 'Predis\Command\ServerTime',
    +        );
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Profile/ServerVersionNext.php b/vendor/predis/predis/lib/Predis/Profile/ServerVersionNext.php
    new file mode 100644
    index 0000000..695388a
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Profile/ServerVersionNext.php
    @@ -0,0 +1,36 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Profile;
    +
    +/**
    + * Server profile for the current unstable version of Redis.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ServerVersionNext extends ServerVersion26
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getVersion()
    +    {
    +        return '2.8';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getSupportedCommands()
    +    {
    +        return array_merge(parent::getSupportedCommands(), array());
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Protocol/CommandSerializerInterface.php b/vendor/predis/predis/lib/Predis/Protocol/CommandSerializerInterface.php
    new file mode 100644
    index 0000000..2671aec
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Protocol/CommandSerializerInterface.php
    @@ -0,0 +1,30 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol;
    +
    +use Predis\Command\CommandInterface;
    +
    +/**
    + * Interface that defines a custom serializer for Redis commands.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface CommandSerializerInterface
    +{
    +    /**
    +     * Serializes a Redis command.
    +     *
    +     * @param CommandInterface $command Redis command.
    +     * @return string
    +     */
    +    public function serialize(CommandInterface $command);
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Protocol/ComposableProtocolInterface.php b/vendor/predis/predis/lib/Predis/Protocol/ComposableProtocolInterface.php
    new file mode 100644
    index 0000000..56990a0
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Protocol/ComposableProtocolInterface.php
    @@ -0,0 +1,50 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol;
    +
    +/**
    + * Interface that defines a customizable protocol processor that serializes
    + * Redis commands and parses replies returned by the server to PHP objects
    + * using a pluggable set of classes defining the underlying wire protocol.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface ComposableProtocolInterface extends ProtocolInterface
    +{
    +    /**
    +     * Sets the command serializer to be used by the protocol processor.
    +     *
    +     * @param CommandSerializerInterface $serializer Command serializer.
    +     */
    +    public function setSerializer(CommandSerializerInterface $serializer);
    +
    +    /**
    +     * Returns the command serializer used by the protocol processor.
    +     *
    +     * @return CommandSerializerInterface
    +     */
    +    public function getSerializer();
    +
    +    /**
    +     * Sets the response reader to be used by the protocol processor.
    +     *
    +     * @param ResponseReaderInterface $reader Response reader.
    +     */
    +    public function setReader(ResponseReaderInterface $reader);
    +
    +    /**
    +     * Returns the response reader used by the protocol processor.
    +     *
    +     * @return ResponseReaderInterface
    +     */
    +    public function getReader();
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Protocol/ProtocolException.php b/vendor/predis/predis/lib/Predis/Protocol/ProtocolException.php
    new file mode 100644
    index 0000000..6539021
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Protocol/ProtocolException.php
    @@ -0,0 +1,24 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol;
    +
    +use Predis\CommunicationException;
    +
    +/**
    + * Exception class that identifies errors encountered while
    + * handling the Redis wire protocol.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ProtocolException extends CommunicationException
    +{
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Protocol/ProtocolInterface.php b/vendor/predis/predis/lib/Predis/Protocol/ProtocolInterface.php
    new file mode 100644
    index 0000000..5595d35
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Protocol/ProtocolInterface.php
    @@ -0,0 +1,40 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol;
    +
    +use Predis\Command\CommandInterface;
    +use Predis\Connection\ComposableConnectionInterface;
    +
    +/**
    + * Interface that defines a protocol processor that serializes Redis commands
    + * and parses replies returned by the server to PHP objects.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface ProtocolInterface extends ResponseReaderInterface
    +{
    +    /**
    +     * Writes a Redis command on the specified connection.
    +     *
    +     * @param ComposableConnectionInterface $connection Connection to Redis.
    +     * @param CommandInterface $command Redis command.
    +     */
    +    public function write(ComposableConnectionInterface $connection, CommandInterface $command);
    +
    +    /**
    +     * Sets the options for the protocol processor.
    +     *
    +     * @param string $option Name of the option.
    +     * @param mixed $value Value of the option.
    +     */
    +    public function setOption($option, $value);
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Protocol/ResponseHandlerInterface.php b/vendor/predis/predis/lib/Predis/Protocol/ResponseHandlerInterface.php
    new file mode 100644
    index 0000000..0ea2ca4
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Protocol/ResponseHandlerInterface.php
    @@ -0,0 +1,32 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol;
    +
    +use Predis\Connection\ComposableConnectionInterface;
    +
    +/**
    + * Interface that defines an handler able to parse a reply.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface ResponseHandlerInterface
    +{
    +    /**
    +     * Parses a type of reply returned by Redis and reads more data from the
    +     * connection if needed.
    +     *
    +     * @param ComposableConnectionInterface $connection Connection to Redis.
    +     * @param string $payload Initial payload of the reply.
    +     * @return mixed
    +     */
    +    function handle(ComposableConnectionInterface $connection, $payload);
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Protocol/ResponseReaderInterface.php b/vendor/predis/predis/lib/Predis/Protocol/ResponseReaderInterface.php
    new file mode 100644
    index 0000000..0428112
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Protocol/ResponseReaderInterface.php
    @@ -0,0 +1,31 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol;
    +
    +use Predis\Connection\ComposableConnectionInterface;
    +
    +/**
    + * Interface that defines a response reader able to parse replies returned by
    + * Redis and deserialize them to PHP objects.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface ResponseReaderInterface
    +{
    +    /**
    +     * Reads replies from a connection to Redis and deserializes them.
    +     *
    +     * @param ComposableConnectionInterface $connection Connection to Redis.
    +     * @return mixed
    +     */
    +    public function read(ComposableConnectionInterface $connection);
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Protocol/Text/ComposableTextProtocol.php b/vendor/predis/predis/lib/Predis/Protocol/Text/ComposableTextProtocol.php
    new file mode 100644
    index 0000000..1429990
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Protocol/Text/ComposableTextProtocol.php
    @@ -0,0 +1,129 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use Predis\Command\CommandInterface;
    +use Predis\Connection\ComposableConnectionInterface;
    +use Predis\Protocol\ResponseReaderInterface;
    +use Predis\Protocol\CommandSerializerInterface;
    +use Predis\Protocol\ComposableProtocolInterface;
    +
    +/**
    + * Implements a customizable protocol processor that uses the standard Redis
    + * wire protocol to serialize Redis commands and parse replies returned by
    + * the server using a pluggable set of classes.
    + *
    + * @link http://redis.io/topics/protocol
    + * @author Daniele Alessandri 
    + */
    +class ComposableTextProtocol implements ComposableProtocolInterface
    +{
    +    private $serializer;
    +    private $reader;
    +
    +    /**
    +     * @param array $options Set of options used to initialize the protocol processor.
    +     */
    +    public function __construct(Array $options = array())
    +    {
    +        $this->setSerializer(new TextCommandSerializer());
    +        $this->setReader(new TextResponseReader());
    +
    +        if (count($options) > 0) {
    +            $this->initializeOptions($options);
    +        }
    +    }
    +
    +    /**
    +     * Initializes the protocol processor using a set of options.
    +     *
    +     * @param array $options Set of options.
    +     */
    +    private function initializeOptions(Array $options)
    +    {
    +        foreach ($options as $k => $v) {
    +            $this->setOption($k, $v);
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function setOption($option, $value)
    +    {
    +        switch ($option) {
    +            case 'iterable_multibulk':
    +                $handler = $value ? new ResponseMultiBulkStreamHandler() : new ResponseMultiBulkHandler();
    +                $this->reader->setHandler(TextProtocol::PREFIX_MULTI_BULK, $handler);
    +                break;
    +
    +            default:
    +                throw new \InvalidArgumentException("The option $option is not supported by the current protocol");
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function serialize(CommandInterface $command)
    +    {
    +        return $this->serializer->serialize($command);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function write(ComposableConnectionInterface $connection, CommandInterface $command)
    +    {
    +        $connection->writeBytes($this->serializer->serialize($command));
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function read(ComposableConnectionInterface $connection)
    +    {
    +        return $this->reader->read($connection);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function setSerializer(CommandSerializerInterface $serializer)
    +    {
    +        $this->serializer = $serializer;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getSerializer()
    +    {
    +        return $this->serializer;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function setReader(ResponseReaderInterface $reader)
    +    {
    +        $this->reader = $reader;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getReader()
    +    {
    +        return $this->reader;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Protocol/Text/ResponseBulkHandler.php b/vendor/predis/predis/lib/Predis/Protocol/Text/ResponseBulkHandler.php
    new file mode 100644
    index 0000000..7b9a965
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Protocol/Text/ResponseBulkHandler.php
    @@ -0,0 +1,53 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use Predis\CommunicationException;
    +use Predis\Connection\ComposableConnectionInterface;
    +use Predis\Protocol\ProtocolException;
    +use Predis\Protocol\ResponseHandlerInterface;
    +
    +/**
    + * Implements a response handler for bulk replies using the standard wire
    + * protocol defined by Redis.
    + *
    + * @link http://redis.io/topics/protocol
    + * @author Daniele Alessandri 
    + */
    +class ResponseBulkHandler implements ResponseHandlerInterface
    +{
    +    /**
    +     * Handles a bulk reply returned by Redis.
    +     *
    +     * @param ComposableConnectionInterface $connection Connection to Redis.
    +     * @param string $lengthString Bytes size of the bulk reply.
    +     * @return string
    +     */
    +    public function handle(ComposableConnectionInterface $connection, $lengthString)
    +    {
    +        $length = (int) $lengthString;
    +
    +        if ("$length" !== $lengthString) {
    +            CommunicationException::handle(new ProtocolException(
    +                $connection, "Cannot parse '$lengthString' as bulk length"
    +            ));
    +        }
    +
    +        if ($length >= 0) {
    +            return substr($connection->readBytes($length + 2), 0, -2);
    +        }
    +
    +        if ($length == -1) {
    +            return null;
    +        }
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Protocol/Text/ResponseErrorHandler.php b/vendor/predis/predis/lib/Predis/Protocol/Text/ResponseErrorHandler.php
    new file mode 100644
    index 0000000..a1e27d3
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Protocol/Text/ResponseErrorHandler.php
    @@ -0,0 +1,37 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use Predis\ResponseError;
    +use Predis\Connection\ComposableConnectionInterface;
    +use Predis\Protocol\ResponseHandlerInterface;
    +
    +/**
    + * Implements a response handler for error replies using the standard wire
    + * protocol defined by Redis.
    + *
    + * This handler returns a reply object to notify the user that an error has
    + * occurred on the server.
    + *
    + * @link http://redis.io/topics/protocol
    + * @author Daniele Alessandri 
    + */
    +class ResponseErrorHandler implements ResponseHandlerInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function handle(ComposableConnectionInterface $connection, $errorMessage)
    +    {
    +        return new ResponseError($errorMessage);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Protocol/Text/ResponseIntegerHandler.php b/vendor/predis/predis/lib/Predis/Protocol/Text/ResponseIntegerHandler.php
    new file mode 100644
    index 0000000..36556f3
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Protocol/Text/ResponseIntegerHandler.php
    @@ -0,0 +1,49 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use Predis\CommunicationException;
    +use Predis\Connection\ComposableConnectionInterface;
    +use Predis\Protocol\ProtocolException;
    +use Predis\Protocol\ResponseHandlerInterface;
    +
    +/**
    + * Implements a response handler for integer replies using the standard wire
    + * protocol defined by Redis.
    + *
    + * @link http://redis.io/topics/protocol
    + * @author Daniele Alessandri 
    + */
    +class ResponseIntegerHandler implements ResponseHandlerInterface
    +{
    +    /**
    +     * Handles an integer reply returned by Redis.
    +     *
    +     * @param ComposableConnectionInterface $connection Connection to Redis.
    +     * @param string $number String representation of an integer.
    +     * @return int
    +     */
    +    public function handle(ComposableConnectionInterface $connection, $number)
    +    {
    +        if (is_numeric($number)) {
    +            return (int) $number;
    +        }
    +
    +        if ($number !== 'nil') {
    +            CommunicationException::handle(new ProtocolException(
    +                $connection, "Cannot parse '$number' as numeric response"
    +            ));
    +        }
    +
    +        return null;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Protocol/Text/ResponseMultiBulkHandler.php b/vendor/predis/predis/lib/Predis/Protocol/Text/ResponseMultiBulkHandler.php
    new file mode 100644
    index 0000000..2f31f32
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Protocol/Text/ResponseMultiBulkHandler.php
    @@ -0,0 +1,72 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use Predis\CommunicationException;
    +use Predis\Connection\ComposableConnectionInterface;
    +use Predis\Protocol\ProtocolException;
    +use Predis\Protocol\ResponseHandlerInterface;
    +
    +/**
    + * Implements a response handler for multi-bulk replies using the standard
    + * wire protocol defined by Redis.
    + *
    + * @link http://redis.io/topics/protocol
    + * @author Daniele Alessandri 
    + */
    +class ResponseMultiBulkHandler implements ResponseHandlerInterface
    +{
    +    /**
    +     * Handles a multi-bulk reply returned by Redis.
    +     *
    +     * @param ComposableConnectionInterface $connection Connection to Redis.
    +     * @param string $lengthString Number of items in the multi-bulk reply.
    +     * @return array
    +     */
    +    public function handle(ComposableConnectionInterface $connection, $lengthString)
    +    {
    +        $length = (int) $lengthString;
    +
    +        if ("$length" !== $lengthString) {
    +            CommunicationException::handle(new ProtocolException(
    +                $connection, "Cannot parse '$lengthString' as multi-bulk length"
    +            ));
    +        }
    +
    +        if ($length === -1) {
    +            return null;
    +        }
    +
    +        $list = array();
    +
    +        if ($length > 0) {
    +            $handlersCache = array();
    +            $reader = $connection->getProtocol()->getReader();
    +
    +            for ($i = 0; $i < $length; $i++) {
    +                $header = $connection->readLine();
    +                $prefix = $header[0];
    +
    +                if (isset($handlersCache[$prefix])) {
    +                    $handler = $handlersCache[$prefix];
    +                } else {
    +                    $handler = $reader->getHandler($prefix);
    +                    $handlersCache[$prefix] = $handler;
    +                }
    +
    +                $list[$i] = $handler->handle($connection, substr($header, 1));
    +            }
    +        }
    +
    +        return $list;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Protocol/Text/ResponseMultiBulkStreamHandler.php b/vendor/predis/predis/lib/Predis/Protocol/Text/ResponseMultiBulkStreamHandler.php
    new file mode 100644
    index 0000000..77bc19f
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Protocol/Text/ResponseMultiBulkStreamHandler.php
    @@ -0,0 +1,48 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use Predis\CommunicationException;
    +use Predis\Connection\ComposableConnectionInterface;
    +use Predis\Iterator\MultiBulkResponseSimple;
    +use Predis\Protocol\ProtocolException;
    +use Predis\Protocol\ResponseHandlerInterface;
    +
    +/**
    + * Implements a response handler for iterable multi-bulk replies using the
    + * standard wire protocol defined by Redis.
    + *
    + * @link http://redis.io/topics/protocol
    + * @author Daniele Alessandri 
    + */
    +class ResponseMultiBulkStreamHandler implements ResponseHandlerInterface
    +{
    +    /**
    +     * Handles a multi-bulk reply returned by Redis in a streamable fashion.
    +     *
    +     * @param ComposableConnectionInterface $connection Connection to Redis.
    +     * @param string $lengthString Number of items in the multi-bulk reply.
    +     * @return MultiBulkResponseSimple
    +     */
    +    public function handle(ComposableConnectionInterface $connection, $lengthString)
    +    {
    +        $length = (int) $lengthString;
    +
    +        if ("$length" != $lengthString) {
    +            CommunicationException::handle(new ProtocolException(
    +                $connection, "Cannot parse '$lengthString' as multi-bulk length"
    +            ));
    +        }
    +
    +        return new MultiBulkResponseSimple($connection, $length);
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Protocol/Text/ResponseStatusHandler.php b/vendor/predis/predis/lib/Predis/Protocol/Text/ResponseStatusHandler.php
    new file mode 100644
    index 0000000..978996a
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Protocol/Text/ResponseStatusHandler.php
    @@ -0,0 +1,43 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use Predis\ResponseQueued;
    +use Predis\Connection\ComposableConnectionInterface;
    +use Predis\Protocol\ResponseHandlerInterface;
    +
    +/**
    + * Implements a response handler for status replies using the standard wire
    + * protocol defined by Redis.
    + *
    + * @link http://redis.io/topics/protocol
    + * @author Daniele Alessandri 
    + */
    +class ResponseStatusHandler implements ResponseHandlerInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function handle(ComposableConnectionInterface $connection, $status)
    +    {
    +        switch ($status) {
    +            case 'OK':
    +                return true;
    +
    +            case 'QUEUED':
    +                return new ResponseQueued();
    +
    +            default:
    +                return $status;
    +        }
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Protocol/Text/TextCommandSerializer.php b/vendor/predis/predis/lib/Predis/Protocol/Text/TextCommandSerializer.php
    new file mode 100644
    index 0000000..c16b277
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Protocol/Text/TextCommandSerializer.php
    @@ -0,0 +1,47 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use Predis\Command\CommandInterface;
    +use Predis\Protocol\CommandSerializerInterface;
    +
    +/**
    + * Implements a pluggable command serializer using the standard  wire protocol
    + * defined by Redis.
    + *
    + * @link http://redis.io/topics/protocol
    + * @author Daniele Alessandri 
    + */
    +class TextCommandSerializer implements CommandSerializerInterface
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function serialize(CommandInterface $command)
    +    {
    +        $commandId = $command->getId();
    +        $arguments = $command->getArguments();
    +
    +        $cmdlen = strlen($commandId);
    +        $reqlen = count($arguments) + 1;
    +
    +        $buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandId}\r\n";
    +
    +        for ($i = 0; $i < $reqlen - 1; $i++) {
    +            $argument = $arguments[$i];
    +            $arglen = strlen($argument);
    +            $buffer .= "\${$arglen}\r\n{$argument}\r\n";
    +        }
    +
    +        return $buffer;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Protocol/Text/TextProtocol.php b/vendor/predis/predis/lib/Predis/Protocol/Text/TextProtocol.php
    new file mode 100644
    index 0000000..04d5bca
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Protocol/Text/TextProtocol.php
    @@ -0,0 +1,136 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use Predis\CommunicationException;
    +use Predis\ResponseError;
    +use Predis\ResponseQueued;
    +use Predis\Command\CommandInterface;
    +use Predis\Connection\ComposableConnectionInterface;
    +use Predis\Iterator\MultiBulkResponseSimple;
    +use Predis\Protocol\ProtocolException;
    +use Predis\Protocol\ProtocolInterface;
    +
    +/**
    + * Implements a protocol processor for the standard wire protocol defined by Redis.
    + *
    + * @link http://redis.io/topics/protocol
    + * @author Daniele Alessandri 
    + */
    +class TextProtocol implements ProtocolInterface
    +{
    +    const NEWLINE = "\r\n";
    +    const OK      = 'OK';
    +    const ERROR   = 'ERR';
    +    const QUEUED  = 'QUEUED';
    +    const NULL    = 'nil';
    +
    +    const PREFIX_STATUS     = '+';
    +    const PREFIX_ERROR      = '-';
    +    const PREFIX_INTEGER    = ':';
    +    const PREFIX_BULK       = '$';
    +    const PREFIX_MULTI_BULK = '*';
    +
    +    const BUFFER_SIZE = 4096;
    +
    +    private $mbiterable;
    +    private $serializer;
    +
    +    /**
    +     *
    +     */
    +    public function __construct()
    +    {
    +        $this->mbiterable = false;
    +        $this->serializer = new TextCommandSerializer();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function write(ComposableConnectionInterface $connection, CommandInterface $command)
    +    {
    +        $connection->writeBytes($this->serializer->serialize($command));
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function read(ComposableConnectionInterface $connection)
    +    {
    +        $chunk = $connection->readLine();
    +        $prefix = $chunk[0];
    +        $payload = substr($chunk, 1);
    +
    +        switch ($prefix) {
    +            case '+':    // inline
    +                switch ($payload) {
    +                    case 'OK':
    +                        return true;
    +
    +                    case 'QUEUED':
    +                        return new ResponseQueued();
    +
    +                    default:
    +                        return $payload;
    +                }
    +
    +            case '$':    // bulk
    +                $size = (int) $payload;
    +                if ($size === -1) {
    +                    return null;
    +                }
    +                return substr($connection->readBytes($size + 2), 0, -2);
    +
    +            case '*':    // multi bulk
    +                $count = (int) $payload;
    +
    +                if ($count === -1) {
    +                    return null;
    +                }
    +                if ($this->mbiterable) {
    +                    return new MultiBulkResponseSimple($connection, $count);
    +                }
    +
    +                $multibulk = array();
    +
    +                for ($i = 0; $i < $count; $i++) {
    +                    $multibulk[$i] = $this->read($connection);
    +                }
    +
    +                return $multibulk;
    +
    +            case ':':    // integer
    +                return (int) $payload;
    +
    +            case '-':    // error
    +                return new ResponseError($payload);
    +
    +            default:
    +                CommunicationException::handle(new ProtocolException(
    +                    $connection, "Unknown prefix: '$prefix'"
    +                ));
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function setOption($option, $value)
    +    {
    +        switch ($option) {
    +            case 'iterable_multibulk':
    +                $this->mbiterable = (bool) $value;
    +                break;
    +        }
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Protocol/Text/TextResponseReader.php b/vendor/predis/predis/lib/Predis/Protocol/Text/TextResponseReader.php
    new file mode 100644
    index 0000000..5364a21
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Protocol/Text/TextResponseReader.php
    @@ -0,0 +1,113 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use Predis\CommunicationException;
    +use Predis\Connection\ComposableConnectionInterface;
    +use Predis\Protocol\ProtocolException;
    +use Predis\Protocol\ResponseHandlerInterface;
    +use Predis\Protocol\ResponseReaderInterface;
    +
    +/**
    + * Implements a pluggable response reader using the standard wire protocol
    + * defined by Redis.
    + *
    + * @link http://redis.io/topics/protocol
    + * @author Daniele Alessandri 
    + */
    +class TextResponseReader implements ResponseReaderInterface
    +{
    +    private $handlers;
    +
    +    /**
    +     *
    +     */
    +    public function __construct()
    +    {
    +        $this->handlers = $this->getDefaultHandlers();
    +    }
    +
    +    /**
    +     * Returns the default set of response handlers for all the type of replies
    +     * that can be returned by Redis.
    +     */
    +    private function getDefaultHandlers()
    +    {
    +        return array(
    +            TextProtocol::PREFIX_STATUS     => new ResponseStatusHandler(),
    +            TextProtocol::PREFIX_ERROR      => new ResponseErrorHandler(),
    +            TextProtocol::PREFIX_INTEGER    => new ResponseIntegerHandler(),
    +            TextProtocol::PREFIX_BULK       => new ResponseBulkHandler(),
    +            TextProtocol::PREFIX_MULTI_BULK => new ResponseMultiBulkHandler(),
    +        );
    +    }
    +
    +    /**
    +     * Sets a response handler for a certain prefix that identifies a type of
    +     * reply that can be returned by Redis.
    +     *
    +     * @param string $prefix Identifier for a type of reply.
    +     * @param ResponseHandlerInterface $handler Response handler for the reply.
    +     */
    +    public function setHandler($prefix, ResponseHandlerInterface $handler)
    +    {
    +        $this->handlers[$prefix] = $handler;
    +    }
    +
    +    /**
    +     * Returns the response handler associated to a certain type of reply that
    +     * can be returned by Redis.
    +     *
    +     * @param string $prefix Identifier for a type of reply.
    +     * @return ResponseHandlerInterface
    +     */
    +    public function getHandler($prefix)
    +    {
    +        if (isset($this->handlers[$prefix])) {
    +            return $this->handlers[$prefix];
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function read(ComposableConnectionInterface $connection)
    +    {
    +        $header = $connection->readLine();
    +
    +        if ($header === '') {
    +            $this->protocolError($connection, 'Unexpected empty header');
    +        }
    +
    +        $prefix = $header[0];
    +
    +        if (!isset($this->handlers[$prefix])) {
    +            $this->protocolError($connection, "Unknown prefix: '$prefix'");
    +        }
    +
    +        $handler = $this->handlers[$prefix];
    +
    +        return $handler->handle($connection, substr($header, 1));
    +    }
    +
    +    /**
    +     * Helper method used to handle a protocol error generated while reading a
    +     * reply from a connection to Redis.
    +     *
    +     * @param ComposableConnectionInterface $connection Connection to Redis that generated the error.
    +     * @param string $message Error message.
    +     */
    +    private function protocolError(ComposableConnectionInterface $connection, $message)
    +    {
    +        CommunicationException::handle(new ProtocolException($connection, $message));
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/PubSub/AbstractPubSubContext.php b/vendor/predis/predis/lib/Predis/PubSub/AbstractPubSubContext.php
    new file mode 100644
    index 0000000..87186b2
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/PubSub/AbstractPubSubContext.php
    @@ -0,0 +1,206 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\PubSub;
    +
    +/**
    + * Client-side abstraction of a Publish / Subscribe context.
    + *
    + * @author Daniele Alessandri 
    + */
    +abstract class AbstractPubSubContext implements \Iterator
    +{
    +    const SUBSCRIBE    = 'subscribe';
    +    const UNSUBSCRIBE  = 'unsubscribe';
    +    const PSUBSCRIBE   = 'psubscribe';
    +    const PUNSUBSCRIBE = 'punsubscribe';
    +    const MESSAGE      = 'message';
    +    const PMESSAGE     = 'pmessage';
    +
    +    const STATUS_VALID       = 1;	// 0b0001
    +    const STATUS_SUBSCRIBED  = 2;	// 0b0010
    +    const STATUS_PSUBSCRIBED = 4;	// 0b0100
    +
    +    private $position = null;
    +    private $statusFlags = self::STATUS_VALID;
    +
    +    /**
    +     * Automatically closes the context when PHP's garbage collector kicks in.
    +     */
    +    public function __destruct()
    +    {
    +        $this->closeContext(true);
    +    }
    +
    +    /**
    +     * Checks if the specified flag is valid in the state of the context.
    +     *
    +     * @param int $value Flag.
    +     * @return Boolean
    +     */
    +    protected function isFlagSet($value)
    +    {
    +        return ($this->statusFlags & $value) === $value;
    +    }
    +
    +    /**
    +     * Subscribes to the specified channels.
    +     *
    +     * @param mixed $arg,... One or more channel names.
    +     */
    +    public function subscribe(/* arguments */)
    +    {
    +        $this->writeCommand(self::SUBSCRIBE, func_get_args());
    +        $this->statusFlags |= self::STATUS_SUBSCRIBED;
    +    }
    +
    +    /**
    +     * Unsubscribes from the specified channels.
    +     *
    +     * @param mixed $arg,... One or more channel names.
    +     */
    +    public function unsubscribe(/* arguments */)
    +    {
    +        $this->writeCommand(self::UNSUBSCRIBE, func_get_args());
    +    }
    +
    +    /**
    +     * Subscribes to the specified channels using a pattern.
    +     *
    +     * @param mixed $arg,... One or more channel name patterns.
    +     */
    +    public function psubscribe(/* arguments */)
    +    {
    +        $this->writeCommand(self::PSUBSCRIBE, func_get_args());
    +        $this->statusFlags |= self::STATUS_PSUBSCRIBED;
    +    }
    +
    +    /**
    +     * Unsubscribes from the specified channels using a pattern.
    +     *
    +     * @param mixed $arg,... One or more channel name patterns.
    +     */
    +    public function punsubscribe(/* arguments */)
    +    {
    +        $this->writeCommand(self::PUNSUBSCRIBE, func_get_args());
    +    }
    +
    +    /**
    +     * Closes the context by unsubscribing from all the subscribed channels.
    +     * Optionally, the context can be forcefully closed by dropping the
    +     * underlying connection.
    +     *
    +     * @param Boolean $force Forcefully close the context by closing the connection.
    +     * @return Boolean Returns false if there are no pending messages.
    +     */
    +    public function closeContext($force = false)
    +    {
    +        if (!$this->valid()) {
    +            return false;
    +        }
    +
    +        if ($force) {
    +            $this->invalidate();
    +            $this->disconnect();
    +        } else {
    +            if ($this->isFlagSet(self::STATUS_SUBSCRIBED)) {
    +                $this->unsubscribe();
    +            }
    +            if ($this->isFlagSet(self::STATUS_PSUBSCRIBED)) {
    +                $this->punsubscribe();
    +            }
    +        }
    +
    +        return !$force;
    +    }
    +
    +    /**
    +     * Closes the underlying connection on forced disconnection.
    +     */
    +    protected abstract function disconnect();
    +
    +    /**
    +     * Writes a Redis command on the underlying connection.
    +     *
    +     * @param string $method ID of the command.
    +     * @param array $arguments List of arguments.
    +     */
    +    protected abstract function writeCommand($method, $arguments);
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function rewind()
    +    {
    +        // NOOP
    +    }
    +
    +    /**
    +     * Returns the last message payload retrieved from the server and generated
    +     * by one of the active subscriptions.
    +     *
    +     * @return array
    +     */
    +    public function current()
    +    {
    +        return $this->getValue();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function key()
    +    {
    +        return $this->position;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function next()
    +    {
    +        if ($this->valid()) {
    +            $this->position++;
    +        }
    +
    +        return $this->position;
    +    }
    +
    +    /**
    +     * Checks if the the context is still in a valid state to continue.
    +     *
    +     * @return Boolean
    +     */
    +    public function valid()
    +    {
    +        $isValid = $this->isFlagSet(self::STATUS_VALID);
    +        $subscriptionFlags = self::STATUS_SUBSCRIBED | self::STATUS_PSUBSCRIBED;
    +        $hasSubscriptions = ($this->statusFlags & $subscriptionFlags) > 0;
    +
    +        return $isValid && $hasSubscriptions;
    +    }
    +
    +    /**
    +     * Resets the state of the context.
    +     */
    +    protected function invalidate()
    +    {
    +        $this->statusFlags = 0;	// 0b0000;
    +    }
    +
    +    /**
    +     * Waits for a new message from the server generated by one of the active
    +     * subscriptions and returns it when available.
    +     *
    +     * @return array
    +     */
    +    protected abstract function getValue();
    +}
    diff --git a/vendor/predis/predis/lib/Predis/PubSub/DispatcherLoop.php b/vendor/predis/predis/lib/Predis/PubSub/DispatcherLoop.php
    new file mode 100644
    index 0000000..3254897
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/PubSub/DispatcherLoop.php
    @@ -0,0 +1,170 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\PubSub;
    +
    +use Predis\ClientInterface;
    +
    +/**
    + * Method-dispatcher loop built around the client-side abstraction of a Redis
    + * Publish / Subscribe context.
    + *
    + * @author Daniele Alessandri 
    + */
    +class DispatcherLoop
    +{
    +    private $pubSubContext;
    +
    +    protected $callbacks;
    +    protected $defaultCallback;
    +    protected $subscriptionCallback;
    +
    +    /**
    +     * @param ClientInterface $client Client instance used by the context.
    +     */
    +    public function __construct(ClientInterface $client)
    +    {
    +        $this->callbacks = array();
    +        $this->pubSubContext = $client->pubSub();
    +    }
    +
    +    /**
    +     * Checks if the passed argument is a valid callback.
    +     *
    +     * @param mixed $callable A callback.
    +     */
    +    protected function validateCallback($callable)
    +    {
    +        if (!is_callable($callable)) {
    +            throw new \InvalidArgumentException("A valid callable object must be provided");
    +        }
    +    }
    +
    +    /**
    +     * Returns the underlying Publish / Subscribe context.
    +     *
    +     * @return PubSubContext
    +     */
    +    public function getPubSubContext()
    +    {
    +        return $this->pubSubContext;
    +    }
    +
    +    /**
    +     * Sets a callback that gets invoked upon new subscriptions.
    +     *
    +     * @param mixed $callable A callback.
    +     */
    +    public function subscriptionCallback($callable = null)
    +    {
    +        if (isset($callable)) {
    +            $this->validateCallback($callable);
    +        }
    +
    +        $this->subscriptionCallback = $callable;
    +    }
    +
    +    /**
    +     * Sets a callback that gets invoked when a message is received on a
    +     * channel that does not have an associated callback.
    +     *
    +     * @param mixed $callable A callback.
    +     */
    +    public function defaultCallback($callable = null)
    +    {
    +        if (isset($callable)) {
    +            $this->validateCallback($callable);
    +        }
    +
    +        $this->subscriptionCallback = $callable;
    +    }
    +
    +    /**
    +     * Binds a callback to a channel.
    +     *
    +     * @param string $channel Channel name.
    +     * @param Callable $callback A callback.
    +     */
    +    public function attachCallback($channel, $callback)
    +    {
    +        $callbackName = $this->getPrefixKeys() . $channel;
    +
    +        $this->validateCallback($callback);
    +        $this->callbacks[$callbackName] = $callback;
    +        $this->pubSubContext->subscribe($channel);
    +    }
    +
    +    /**
    +     * Stops listening to a channel and removes the associated callback.
    +     *
    +     * @param string $channel Redis channel.
    +     */
    +    public function detachCallback($channel)
    +    {
    +        $callbackName = $this->getPrefixKeys() . $channel;
    +
    +        if (isset($this->callbacks[$callbackName])) {
    +            unset($this->callbacks[$callbackName]);
    +            $this->pubSubContext->unsubscribe($channel);
    +        }
    +    }
    +
    +    /**
    +     * Starts the dispatcher loop.
    +     */
    +    public function run()
    +    {
    +        foreach ($this->pubSubContext as $message) {
    +            $kind = $message->kind;
    +
    +            if ($kind !== PubSubContext::MESSAGE && $kind !== PubSubContext::PMESSAGE) {
    +                if (isset($this->subscriptionCallback)) {
    +                    $callback = $this->subscriptionCallback;
    +                    call_user_func($callback, $message);
    +                }
    +
    +                continue;
    +            }
    +
    +            if (isset($this->callbacks[$message->channel])) {
    +                $callback = $this->callbacks[$message->channel];
    +                call_user_func($callback, $message->payload);
    +            } else if (isset($this->defaultCallback)) {
    +                $callback = $this->defaultCallback;
    +                call_user_func($callback, $message);
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Terminates the dispatcher loop.
    +     */
    +    public function stop()
    +    {
    +        $this->pubSubContext->closeContext();
    +    }
    +
    +    /**
    +     * Return the prefix of the keys
    +     *
    +     * @return string
    +     */
    +    protected function getPrefixKeys()
    +    {
    +        $options = $this->pubSubContext->getClient()->getOptions();
    +
    +        if (isset($options->prefix)) {
    +            return $options->prefix->getPrefix();
    +        }
    +
    +        return '';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/PubSub/PubSubContext.php b/vendor/predis/predis/lib/Predis/PubSub/PubSubContext.php
    new file mode 100644
    index 0000000..50f8dc1
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/PubSub/PubSubContext.php
    @@ -0,0 +1,139 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\PubSub;
    +
    +use Predis\ClientException;
    +use Predis\ClientInterface;
    +use Predis\Command\AbstractCommand as Command;
    +use Predis\NotSupportedException;
    +use Predis\Connection\AggregatedConnectionInterface;
    +
    +/**
    + * Client-side abstraction of a Publish / Subscribe context.
    + *
    + * @author Daniele Alessandri 
    + */
    +class PubSubContext extends AbstractPubSubContext
    +{
    +    private $client;
    +    private $options;
    +
    +    /**
    +     * @param ClientInterface $client Client instance used by the context.
    +     * @param array $options Options for the context initialization.
    +     */
    +    public function __construct(ClientInterface $client, Array $options = null)
    +    {
    +        $this->checkCapabilities($client);
    +        $this->options = $options ?: array();
    +        $this->client = $client;
    +
    +        $this->genericSubscribeInit('subscribe');
    +        $this->genericSubscribeInit('psubscribe');
    +    }
    +
    +    /**
    +     * Returns the underlying client instance used by the pub/sub iterator.
    +     *
    +     * @return ClientInterface
    +     */
    +    public function getClient()
    +    {
    +        return $this->client;
    +    }
    +
    +    /**
    +     * Checks if the passed client instance satisfies the required conditions
    +     * needed to initialize a Publish / Subscribe context.
    +     *
    +     * @param ClientInterface $client Client instance used by the context.
    +     */
    +    private function checkCapabilities(ClientInterface $client)
    +    {
    +        if ($client->getConnection() instanceof AggregatedConnectionInterface) {
    +            throw new NotSupportedException('Cannot initialize a PUB/SUB context when using aggregated connections');
    +        }
    +
    +        $commands = array('publish', 'subscribe', 'unsubscribe', 'psubscribe', 'punsubscribe');
    +
    +        if ($client->getProfile()->supportsCommands($commands) === false) {
    +            throw new NotSupportedException('The current profile does not support PUB/SUB related commands');
    +        }
    +    }
    +
    +    /**
    +     * This method shares the logic to handle both SUBSCRIBE and PSUBSCRIBE.
    +     *
    +     * @param string $subscribeAction Type of subscription.
    +     */
    +    private function genericSubscribeInit($subscribeAction)
    +    {
    +        if (isset($this->options[$subscribeAction])) {
    +            $this->$subscribeAction($this->options[$subscribeAction]);
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function writeCommand($method, $arguments)
    +    {
    +        $arguments = Command::normalizeArguments($arguments);
    +        $command = $this->client->createCommand($method, $arguments);
    +        $this->client->getConnection()->writeCommand($command);
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function disconnect()
    +    {
    +        $this->client->disconnect();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getValue()
    +    {
    +        $response = $this->client->getConnection()->read();
    +
    +        switch ($response[0]) {
    +            case self::SUBSCRIBE:
    +            case self::UNSUBSCRIBE:
    +            case self::PSUBSCRIBE:
    +            case self::PUNSUBSCRIBE:
    +                if ($response[2] === 0) {
    +                    $this->invalidate();
    +                }
    +
    +            case self::MESSAGE:
    +                return (object) array(
    +                    'kind'    => $response[0],
    +                    'channel' => $response[1],
    +                    'payload' => $response[2],
    +                );
    +
    +            case self::PMESSAGE:
    +                return (object) array(
    +                    'kind'    => $response[0],
    +                    'pattern' => $response[1],
    +                    'channel' => $response[2],
    +                    'payload' => $response[3],
    +                );
    +
    +            default:
    +                $message = "Received an unknown message type {$response[0]} inside of a pubsub context";
    +                throw new ClientException($message);
    +        }
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Replication/ReplicationStrategy.php b/vendor/predis/predis/lib/Predis/Replication/ReplicationStrategy.php
    new file mode 100644
    index 0000000..3d45eaf
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Replication/ReplicationStrategy.php
    @@ -0,0 +1,218 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Replication;
    +
    +use Predis\NotSupportedException;
    +use Predis\Command\CommandInterface;
    +
    +/**
    + * Defines a strategy for master/reply replication.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ReplicationStrategy
    +{
    +    protected $disallowed;
    +    protected $readonly;
    +    protected $readonlySHA1;
    +
    +    /**
    +     *
    +     */
    +    public function __construct()
    +    {
    +        $this->disallowed = $this->getDisallowedOperations();
    +        $this->readonly = $this->getReadOnlyOperations();
    +        $this->readonlySHA1 = array();
    +    }
    +
    +    /**
    +     * Returns if the specified command performs a read-only operation
    +     * against a key stored on Redis.
    +     *
    +     * @param CommandInterface $command Instance of Redis command.
    +     * @return Boolean
    +     */
    +    public function isReadOperation(CommandInterface $command)
    +    {
    +        if (isset($this->disallowed[$id = $command->getId()])) {
    +            throw new NotSupportedException("The command $id is not allowed in replication mode");
    +        }
    +
    +        if (isset($this->readonly[$id])) {
    +            if (true === $readonly = $this->readonly[$id]) {
    +                return true;
    +            }
    +
    +            return call_user_func($readonly, $command);
    +        }
    +
    +        if (($eval = $id === 'EVAL') || $id === 'EVALSHA') {
    +            $sha1 = $eval ? sha1($command->getArgument(0)) : $command->getArgument(0);
    +
    +            if (isset($this->readonlySHA1[$sha1])) {
    +                if (true === $readonly = $this->readonlySHA1[$sha1]) {
    +                    return true;
    +                }
    +
    +                return call_user_func($readonly, $command);
    +            }
    +        }
    +
    +        return false;
    +    }
    +
    +    /**
    +     * Returns if the specified command is disallowed in a master/slave
    +     * replication context.
    +     *
    +     * @param CommandInterface $command Instance of Redis command.
    +     * @return Boolean
    +     */
    +    public function isDisallowedOperation(CommandInterface $command)
    +    {
    +        return isset($this->disallowed[$command->getId()]);
    +    }
    +
    +    /**
    +     * Checks if a SORT command is a readable operation by parsing the arguments
    +     * array of the specified commad instance.
    +     *
    +     * @param CommandInterface $command Instance of Redis command.
    +     * @return Boolean
    +     */
    +    protected function isSortReadOnly(CommandInterface $command)
    +    {
    +        $arguments = $command->getArguments();
    +        return ($c = count($arguments)) === 1 ? true : $arguments[$c - 2] !== 'STORE';
    +    }
    +
    +    /**
    +     * Marks a command as a read-only operation. When the behaviour of a
    +     * command can be decided only at runtime depending on its arguments,
    +     * a callable object can be provided to dynamically check if the passed
    +     * instance of a command performs write operations or not.
    +     *
    +     * @param string $commandID ID of the command.
    +     * @param mixed $readonly A boolean or a callable object.
    +     */
    +    public function setCommandReadOnly($commandID, $readonly = true)
    +    {
    +        $commandID = strtoupper($commandID);
    +
    +        if ($readonly) {
    +            $this->readonly[$commandID] = $readonly;
    +        } else {
    +            unset($this->readonly[$commandID]);
    +        }
    +    }
    +
    +    /**
    +     * Marks a Lua script for EVAL and EVALSHA as a read-only operation. When
    +     * the behaviour of a script can be decided only at runtime depending on
    +     * its arguments, a callable object can be provided to dynamically check
    +     * if the passed instance of EVAL or EVALSHA performs write operations or
    +     * not.
    +     *
    +     * @param string $script Body of the Lua script.
    +     * @param mixed $readonly A boolean or a callable object.
    +     */
    +    public function setScriptReadOnly($script, $readonly = true)
    +    {
    +        $sha1 = sha1($script);
    +
    +        if ($readonly) {
    +            $this->readonlySHA1[$sha1] = $readonly;
    +        } else {
    +            unset($this->readonlySHA1[$sha1]);
    +        }
    +    }
    +
    +    /**
    +     * Returns the default list of disallowed commands.
    +     *
    +     * @return array
    +     */
    +    protected function getDisallowedOperations()
    +    {
    +        return array(
    +            'SHUTDOWN'          => true,
    +            'INFO'              => true,
    +            'DBSIZE'            => true,
    +            'LASTSAVE'          => true,
    +            'CONFIG'            => true,
    +            'MONITOR'           => true,
    +            'SLAVEOF'           => true,
    +            'SAVE'              => true,
    +            'BGSAVE'            => true,
    +            'BGREWRITEAOF'      => true,
    +            'SLOWLOG'           => true,
    +        );
    +    }
    +
    +    /**
    +     * Returns the default list of commands performing read-only operations.
    +     *
    +     * @return array
    +     */
    +    protected function getReadOnlyOperations()
    +    {
    +        return array(
    +            'EXISTS'            => true,
    +            'TYPE'              => true,
    +            'KEYS'              => true,
    +            'RANDOMKEY'         => true,
    +            'TTL'               => true,
    +            'GET'               => true,
    +            'MGET'              => true,
    +            'SUBSTR'            => true,
    +            'STRLEN'            => true,
    +            'GETRANGE'          => true,
    +            'GETBIT'            => true,
    +            'LLEN'              => true,
    +            'LRANGE'            => true,
    +            'LINDEX'            => true,
    +            'SCARD'             => true,
    +            'SISMEMBER'         => true,
    +            'SINTER'            => true,
    +            'SUNION'            => true,
    +            'SDIFF'             => true,
    +            'SMEMBERS'          => true,
    +            'SRANDMEMBER'       => true,
    +            'ZRANGE'            => true,
    +            'ZREVRANGE'         => true,
    +            'ZRANGEBYSCORE'     => true,
    +            'ZREVRANGEBYSCORE'  => true,
    +            'ZCARD'             => true,
    +            'ZSCORE'            => true,
    +            'ZCOUNT'            => true,
    +            'ZRANK'             => true,
    +            'ZREVRANK'          => true,
    +            'HGET'              => true,
    +            'HMGET'             => true,
    +            'HEXISTS'           => true,
    +            'HLEN'              => true,
    +            'HKEYS'             => true,
    +            'HVALS'             => true,
    +            'HGETALL'           => true,
    +            'PING'              => true,
    +            'AUTH'              => true,
    +            'SELECT'            => true,
    +            'ECHO'              => true,
    +            'QUIT'              => true,
    +            'OBJECT'            => true,
    +            'BITCOUNT'          => true,
    +            'TIME'              => true,
    +            'SORT'              => array($this, 'isSortReadOnly'),
    +        );
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/ResponseError.php b/vendor/predis/predis/lib/Predis/ResponseError.php
    new file mode 100644
    index 0000000..f6f310e
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/ResponseError.php
    @@ -0,0 +1,58 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +/**
    + * Represents an error returned by Redis (-ERR replies) during the execution
    + * of a command on the server.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ResponseError implements ResponseErrorInterface
    +{
    +    private $message;
    +
    +    /**
    +     * @param string $message Error message returned by Redis
    +     */
    +    public function __construct($message)
    +    {
    +        $this->message = $message;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getMessage()
    +    {
    +        return $this->message;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getErrorType()
    +    {
    +        list($errorType, ) = explode(' ', $this->getMessage(), 2);
    +        return $errorType;
    +    }
    +
    +    /**
    +     * Converts the object to its string representation.
    +     *
    +     * @return string
    +     */
    +    public function __toString()
    +    {
    +        return $this->getMessage();
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/ResponseErrorInterface.php b/vendor/predis/predis/lib/Predis/ResponseErrorInterface.php
    new file mode 100644
    index 0000000..8c1a005
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/ResponseErrorInterface.php
    @@ -0,0 +1,35 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +/**
    + * Represents an error returned by Redis (replies identified by "-" in the
    + * Redis response protocol) during the execution of an operation on the server.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface ResponseErrorInterface extends ResponseObjectInterface
    +{
    +    /**
    +     * Returns the error message
    +     *
    +     * @return string
    +     */
    +    public function getMessage();
    +
    +    /**
    +     * Returns the error type (e.g. ERR, ASK, MOVED)
    +     *
    +     * @return string
    +     */
    +    public function getErrorType();
    +}
    diff --git a/vendor/predis/predis/lib/Predis/ResponseObjectInterface.php b/vendor/predis/predis/lib/Predis/ResponseObjectInterface.php
    new file mode 100644
    index 0000000..ba81783
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/ResponseObjectInterface.php
    @@ -0,0 +1,21 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +/**
    + * Represents a complex reply object from Redis.
    + *
    + * @author Daniele Alessandri 
    + */
    +interface ResponseObjectInterface
    +{
    +}
    diff --git a/vendor/predis/predis/lib/Predis/ResponseQueued.php b/vendor/predis/predis/lib/Predis/ResponseQueued.php
    new file mode 100644
    index 0000000..c9786fb
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/ResponseQueued.php
    @@ -0,0 +1,53 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +/**
    + * Represents a +QUEUED response returned by Redis as a reply to each command
    + * executed inside a MULTI/ EXEC transaction.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ResponseQueued implements ResponseObjectInterface
    +{
    +    /**
    +     * Converts the object to its string representation.
    +     *
    +     * @return string
    +     */
    +    public function __toString()
    +    {
    +        return 'QUEUED';
    +    }
    +
    +    /**
    +     * Returns the value of the specified property.
    +     *
    +     * @param string $property Name of the property.
    +     * @return mixed
    +     */
    +    public function __get($property)
    +    {
    +        return $property === 'queued';
    +    }
    +
    +    /**
    +     * Checks if the specified property is set.
    +     *
    +     * @param string $property Name of the property.
    +     * @return Boolean
    +     */
    +    public function __isset($property)
    +    {
    +        return $property === 'queued';
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/ServerException.php b/vendor/predis/predis/lib/Predis/ServerException.php
    new file mode 100644
    index 0000000..0a60c7e
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/ServerException.php
    @@ -0,0 +1,42 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +/**
    + * Exception class that identifies server-side Redis errors.
    + *
    + * @author Daniele Alessandri 
    + */
    +class ServerException extends PredisException implements ResponseErrorInterface
    +{
    +    /**
    +     * Gets the type of the error returned by Redis.
    +     *
    +     * @return string
    +     */
    +    public function getErrorType()
    +    {
    +        list($errorType, ) = explode(' ', $this->getMessage(), 2);
    +
    +        return $errorType;
    +    }
    +
    +    /**
    +     * Converts the exception to an instance of ResponseError.
    +     *
    +     * @return ResponseError
    +     */
    +    public function toResponseError()
    +    {
    +        return new ResponseError($this->getMessage());
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Session/SessionHandler.php b/vendor/predis/predis/lib/Predis/Session/SessionHandler.php
    new file mode 100644
    index 0000000..eba793a
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Session/SessionHandler.php
    @@ -0,0 +1,141 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Session;
    +
    +use SessionHandlerInterface;
    +use Predis\ClientInterface;
    +
    +/**
    + * Session handler class that relies on Predis\Client to store PHP's sessions
    + * data into one or multiple Redis servers.
    + *
    + * This class is mostly intended for PHP 5.4 but it can be used under PHP 5.3 provided
    + * that a polyfill for `SessionHandlerInterface` is defined by either you or an external
    + * package such as `symfony/http-foundation`.
    + *
    + * @author Daniele Alessandri 
    + */
    +class SessionHandler implements SessionHandlerInterface
    +{
    +    protected $client;
    +    protected $ttl;
    +
    +    /**
    +     * @param ClientInterface $client Fully initialized client instance.
    +     * @param array $options Session handler options.
    +     */
    +    public function __construct(ClientInterface $client, Array $options = array())
    +    {
    +        $this->client = $client;
    +        $this->ttl = (int) (isset($options['gc_maxlifetime']) ? $options['gc_maxlifetime'] : ini_get('session.gc_maxlifetime'));
    +    }
    +
    +    /**
    +     * Registers the handler instance as the current session handler.
    +     */
    +    public function register()
    +    {
    +        if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
    +            session_set_save_handler($this, true);
    +        } else {
    +            session_set_save_handler(
    +                array($this, 'open'),
    +                array($this, 'close'),
    +                array($this, 'read'),
    +                array($this, 'write'),
    +                array($this, 'destroy'),
    +                array($this, 'gc')
    +            );
    +        }
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function open($save_path, $session_id)
    +    {
    +        // NOOP
    +
    +        return true;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function close()
    +    {
    +        // NOOP
    +
    +        return true;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function gc($maxlifetime)
    +    {
    +        // NOOP
    +
    +        return true;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function read($session_id)
    +    {
    +        if ($data = $this->client->get($session_id)) {
    +            return $data;
    +        }
    +
    +        return '';
    +    }
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function write($session_id, $session_data)
    +    {
    +        $this->client->setex($session_id, $this->ttl, $session_data);
    +
    +        return true;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function destroy($session_id)
    +    {
    +        $this->client->del($session_id);
    +
    +        return true;
    +    }
    +
    +    /**
    +     * Returns the underlying client instance.
    +     *
    +     * @return ClientInterface
    +     */
    +    public function getClient()
    +    {
    +        return $this->client;
    +    }
    +
    +    /**
    +     * Returns the session max lifetime value.
    +     *
    +     * @return int
    +     */
    +    public function getMaxLifeTime()
    +    {
    +        return $this->ttl;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Transaction/AbortedMultiExecException.php b/vendor/predis/predis/lib/Predis/Transaction/AbortedMultiExecException.php
    new file mode 100644
    index 0000000..bccaa87
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Transaction/AbortedMultiExecException.php
    @@ -0,0 +1,45 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Transaction;
    +
    +use Predis\PredisException;
    +
    +/**
    + * Exception class that identifies MULTI / EXEC transactions aborted by Redis.
    + *
    + * @author Daniele Alessandri 
    + */
    +class AbortedMultiExecException extends PredisException
    +{
    +    private $transaction;
    +
    +    /**
    +     * @param MultiExecContext $transaction Transaction that generated the exception.
    +     * @param string $message Error message.
    +     * @param int $code Error code.
    +     */
    +    public function __construct(MultiExecContext $transaction, $message, $code = null)
    +    {
    +        parent::__construct($message, $code);
    +        $this->transaction = $transaction;
    +    }
    +
    +    /**
    +     * Returns the transaction that generated the exception.
    +     *
    +     * @return MultiExecContext
    +     */
    +    public function getTransaction()
    +    {
    +        return $this->transaction;
    +    }
    +}
    diff --git a/vendor/predis/predis/lib/Predis/Transaction/MultiExecContext.php b/vendor/predis/predis/lib/Predis/Transaction/MultiExecContext.php
    new file mode 100644
    index 0000000..882c2b4
    --- /dev/null
    +++ b/vendor/predis/predis/lib/Predis/Transaction/MultiExecContext.php
    @@ -0,0 +1,449 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Transaction;
    +
    +use SplQueue;
    +use Predis\BasicClientInterface;
    +use Predis\ClientException;
    +use Predis\ClientInterface;
    +use Predis\CommunicationException;
    +use Predis\ExecutableContextInterface;
    +use Predis\NotSupportedException;
    +use Predis\ResponseErrorInterface;
    +use Predis\ResponseQueued;
    +use Predis\ServerException;
    +use Predis\Command\CommandInterface;
    +use Predis\Connection\AggregatedConnectionInterface;
    +use Predis\Protocol\ProtocolException;
    +
    +/**
    + * Client-side abstraction of a Redis transaction based on MULTI / EXEC.
    + *
    + * @author Daniele Alessandri 
    + */
    +class MultiExecContext implements BasicClientInterface, ExecutableContextInterface
    +{
    +    const STATE_RESET       = 0;    // 0b00000
    +    const STATE_INITIALIZED = 1;    // 0b00001
    +    const STATE_INSIDEBLOCK = 2;    // 0b00010
    +    const STATE_DISCARDED   = 4;    // 0b00100
    +    const STATE_CAS         = 8;    // 0b01000
    +    const STATE_WATCH       = 16;   // 0b10000
    +
    +    private $state;
    +    private $canWatch;
    +
    +    protected $client;
    +    protected $options;
    +    protected $commands;
    +
    +    /**
    +     * @param ClientInterface $client Client instance used by the context.
    +     * @param array $options Options for the context initialization.
    +     */
    +    public function __construct(ClientInterface $client, Array $options = null)
    +    {
    +        $this->checkCapabilities($client);
    +        $this->options = $options ?: array();
    +        $this->client = $client;
    +        $this->reset();
    +    }
    +
    +    /**
    +     * Sets the internal state flags.
    +     *
    +     * @param int $flags Set of flags
    +     */
    +    protected function setState($flags)
    +    {
    +        $this->state = $flags;
    +    }
    +
    +    /**
    +     * Gets the internal state flags.
    +     *
    +     * @return int
    +     */
    +    protected function getState()
    +    {
    +        return $this->state;
    +    }
    +
    +    /**
    +     * Sets one or more flags.
    +     *
    +     * @param int $flags Set of flags
    +     */
    +    protected function flagState($flags)
    +    {
    +        $this->state |= $flags;
    +    }
    +
    +    /**
    +     * Resets one or more flags.
    +     *
    +     * @param int $flags Set of flags
    +     */
    +    protected function unflagState($flags)
    +    {
    +        $this->state &= ~$flags;
    +    }
    +
    +    /**
    +     * Checks is a flag is set.
    +     *
    +     * @param int $flags Flag
    +     * @return Boolean
    +     */
    +    protected function checkState($flags)
    +    {
    +        return ($this->state & $flags) === $flags;
    +    }
    +
    +    /**
    +     * Checks if the passed client instance satisfies the required conditions
    +     * needed to initialize a transaction context.
    +     *
    +     * @param ClientInterface $client Client instance used by the context.
    +     */
    +    private function checkCapabilities(ClientInterface $client)
    +    {
    +        if ($client->getConnection() instanceof AggregatedConnectionInterface) {
    +            throw new NotSupportedException('Cannot initialize a MULTI/EXEC context when using aggregated connections');
    +        }
    +
    +        $profile = $client->getProfile();
    +
    +        if ($profile->supportsCommands(array('multi', 'exec', 'discard')) === false) {
    +            throw new NotSupportedException('The current profile does not support MULTI, EXEC and DISCARD');
    +        }
    +
    +        $this->canWatch = $profile->supportsCommands(array('watch', 'unwatch'));
    +    }
    +
    +    /**
    +     * Checks if WATCH and UNWATCH are supported by the server profile.
    +     */
    +    private function isWatchSupported()
    +    {
    +        if ($this->canWatch === false) {
    +            throw new NotSupportedException('The current profile does not support WATCH and UNWATCH');
    +        }
    +    }
    +
    +    /**
    +     * Resets the state of a transaction.
    +     */
    +    protected function reset()
    +    {
    +        $this->setState(self::STATE_RESET);
    +        $this->commands = new SplQueue();
    +    }
    +
    +    /**
    +     * Initializes a new transaction.
    +     */
    +    protected function initialize()
    +    {
    +        if ($this->checkState(self::STATE_INITIALIZED)) {
    +            return;
    +        }
    +
    +        $options = $this->options;
    +
    +        if (isset($options['cas']) && $options['cas']) {
    +            $this->flagState(self::STATE_CAS);
    +        }
    +        if (isset($options['watch'])) {
    +            $this->watch($options['watch']);
    +        }
    +
    +        $cas = $this->checkState(self::STATE_CAS);
    +        $discarded = $this->checkState(self::STATE_DISCARDED);
    +
    +        if (!$cas || ($cas && $discarded)) {
    +            $this->client->multi();
    +
    +            if ($discarded) {
    +                $this->unflagState(self::STATE_CAS);
    +            }
    +        }
    +
    +        $this->unflagState(self::STATE_DISCARDED);
    +        $this->flagState(self::STATE_INITIALIZED);
    +    }
    +
    +    /**
    +     * Dynamically invokes a Redis command with the specified arguments.
    +     *
    +     * @param string $method Command ID.
    +     * @param array $arguments Arguments for the command.
    +     * @return mixed
    +     */
    +    public function __call($method, $arguments)
    +    {
    +        $command = $this->client->createCommand($method, $arguments);
    +        $response = $this->executeCommand($command);
    +
    +        return $response;
    +    }
    +
    +    /**
    +     * Executes the specified Redis command.
    +     *
    +     * @param CommandInterface $command A Redis command.
    +     * @return mixed
    +     */
    +    public function executeCommand(CommandInterface $command)
    +    {
    +        $this->initialize();
    +        $response = $this->client->executeCommand($command);
    +
    +        if ($this->checkState(self::STATE_CAS)) {
    +            return $response;
    +        }
    +
    +        if (!$response instanceof ResponseQueued) {
    +            $this->onProtocolError('The server did not respond with a QUEUED status reply');
    +        }
    +
    +        $this->commands->enqueue($command);
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Executes WATCH on one or more keys.
    +     *
    +     * @param string|array $keys One or more keys.
    +     * @return mixed
    +     */
    +    public function watch($keys)
    +    {
    +        $this->isWatchSupported();
    +
    +        if ($this->checkState(self::STATE_INITIALIZED) && !$this->checkState(self::STATE_CAS)) {
    +            throw new ClientException('WATCH after MULTI is not allowed');
    +        }
    +
    +        $reply = $this->client->watch($keys);
    +        $this->flagState(self::STATE_WATCH);
    +
    +        return $reply;
    +    }
    +
    +    /**
    +     * Finalizes the transaction on the server by executing MULTI on the server.
    +     *
    +     * @return MultiExecContext
    +     */
    +    public function multi()
    +    {
    +        if ($this->checkState(self::STATE_INITIALIZED | self::STATE_CAS)) {
    +            $this->unflagState(self::STATE_CAS);
    +            $this->client->multi();
    +        } else {
    +            $this->initialize();
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Executes UNWATCH.
    +     *
    +     * @return MultiExecContext
    +     */
    +    public function unwatch()
    +    {
    +        $this->isWatchSupported();
    +        $this->unflagState(self::STATE_WATCH);
    +        $this->__call('unwatch', array());
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Resets a transaction by UNWATCHing the keys that are being WATCHed and
    +     * DISCARDing the pending commands that have been already sent to the server.
    +     *
    +     * @return MultiExecContext
    +     */
    +    public function discard()
    +    {
    +        if ($this->checkState(self::STATE_INITIALIZED)) {
    +            $command = $this->checkState(self::STATE_CAS) ? 'unwatch' : 'discard';
    +            $this->client->$command();
    +            $this->reset();
    +            $this->flagState(self::STATE_DISCARDED);
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Executes the whole transaction.
    +     *
    +     * @return mixed
    +     */
    +    public function exec()
    +    {
    +        return $this->execute();
    +    }
    +
    +    /**
    +     * Checks the state of the transaction before execution.
    +     *
    +     * @param mixed $callable Callback for execution.
    +     */
    +    private function checkBeforeExecution($callable)
    +    {
    +        if ($this->checkState(self::STATE_INSIDEBLOCK)) {
    +            throw new ClientException("Cannot invoke 'execute' or 'exec' inside an active client transaction block");
    +        }
    +
    +        if ($callable) {
    +            if (!is_callable($callable)) {
    +                throw new \InvalidArgumentException('Argument passed must be a callable object');
    +            }
    +
    +            if (!$this->commands->isEmpty()) {
    +                $this->discard();
    +                throw new ClientException('Cannot execute a transaction block after using fluent interface');
    +            }
    +        }
    +
    +        if (isset($this->options['retry']) && !isset($callable)) {
    +            $this->discard();
    +            throw new \InvalidArgumentException('Automatic retries can be used only when a transaction block is provided');
    +        }
    +    }
    +
    +    /**
    +     * Handles the actual execution of the whole transaction.
    +     *
    +     * @param mixed $callable Optional callback for execution.
    +     * @return array
    +     */
    +    public function execute($callable = null)
    +    {
    +        $this->checkBeforeExecution($callable);
    +
    +        $reply = null;
    +        $values = array();
    +        $attempts = isset($this->options['retry']) ? (int) $this->options['retry'] : 0;
    +
    +        do {
    +            if ($callable !== null) {
    +                $this->executeTransactionBlock($callable);
    +            }
    +
    +            if ($this->commands->isEmpty()) {
    +                if ($this->checkState(self::STATE_WATCH)) {
    +                    $this->discard();
    +                }
    +
    +                return;
    +            }
    +
    +            $reply = $this->client->exec();
    +
    +            if ($reply === null) {
    +                if ($attempts === 0) {
    +                    $message = 'The current transaction has been aborted by the server';
    +                    throw new AbortedMultiExecException($this, $message);
    +                }
    +
    +                $this->reset();
    +
    +                if (isset($this->options['on_retry']) && is_callable($this->options['on_retry'])) {
    +                    call_user_func($this->options['on_retry'], $this, $attempts);
    +                }
    +
    +                continue;
    +            }
    +
    +            break;
    +        } while ($attempts-- > 0);
    +
    +        $exec = $reply instanceof \Iterator ? iterator_to_array($reply) : $reply;
    +        $commands = $this->commands;
    +
    +        $size = count($exec);
    +        if ($size !== count($commands)) {
    +            $this->onProtocolError("EXEC returned an unexpected number of replies");
    +        }
    +
    +        $clientOpts = $this->client->getOptions();
    +        $useExceptions = isset($clientOpts->exceptions) ? $clientOpts->exceptions : true;
    +
    +        for ($i = 0; $i < $size; $i++) {
    +            $commandReply = $exec[$i];
    +
    +            if ($commandReply instanceof ResponseErrorInterface && $useExceptions) {
    +                $message = $commandReply->getMessage();
    +                throw new ServerException($message);
    +            }
    +
    +            if ($commandReply instanceof \Iterator) {
    +                $commandReply = iterator_to_array($commandReply);
    +            }
    +
    +            $values[$i] = $commands->dequeue()->parseResponse($commandReply);
    +        }
    +
    +        return $values;
    +    }
    +
    +    /**
    +     * Passes the current transaction context to a callable block for execution.
    +     *
    +     * @param mixed $callable Callback.
    +     */
    +    protected function executeTransactionBlock($callable)
    +    {
    +        $blockException = null;
    +        $this->flagState(self::STATE_INSIDEBLOCK);
    +
    +        try {
    +            call_user_func($callable, $this);
    +        } catch (CommunicationException $exception) {
    +            $blockException = $exception;
    +        } catch (ServerException $exception) {
    +            $blockException = $exception;
    +        } catch (\Exception $exception) {
    +            $blockException = $exception;
    +            $this->discard();
    +        }
    +
    +        $this->unflagState(self::STATE_INSIDEBLOCK);
    +
    +        if ($blockException !== null) {
    +            throw $blockException;
    +        }
    +    }
    +
    +    /**
    +     * Helper method that handles protocol errors encountered inside a transaction.
    +     *
    +     * @param string $message Error message.
    +     */
    +    private function onProtocolError($message)
    +    {
    +        // Since a MULTI/EXEC block cannot be initialized when using aggregated
    +        // connections, we can safely assume that Predis\Client::getConnection()
    +        // will always return an instance of Predis\Connection\SingleConnectionInterface.
    +        CommunicationException::handle(new ProtocolException(
    +            $this->client->getConnection(), $message
    +        ));
    +    }
    +}
    diff --git a/vendor/predis/predis/package.ini b/vendor/predis/predis/package.ini
    new file mode 100644
    index 0000000..09b8347
    --- /dev/null
    +++ b/vendor/predis/predis/package.ini
    @@ -0,0 +1,37 @@
    +; This file is meant to be used with Onion http://c9s.github.com/Onion/
    +; For instructions on how to build a PEAR package of Predis please follow
    +; the instructions at this URL:
    +;
    +; https://github.com/c9s/Onion#a-quick-tutorial-for-building-pear-package
    +;
    +
    +[package]
    +name        = "Predis"
    +desc        = "Flexible and feature-complete PHP client library for Redis"
    +homepage    = "http://github.com/nrk/predis"
    +license     = "MIT"
    +version     = "0.8.4"
    +stability   = "stable"
    +channel     = "pear.nrk.io"
    +
    +author      = "Daniele Alessandri \"nrk\" "
    +
    +[require]
    +php = ">= 5.3.2"
    +pearinstaller = "1.4.1"
    +
    +[roles]
    +*.xml.dist = test
    +*.md = doc
    +LICENSE = doc
    +lib = php
    +
    +[optional phpiredis]
    +hint = "Add support for faster protocol handling with phpiredis"
    +extensions[] = socket
    +extensions[] = phpiredis
    +
    +[optional webdis]
    +hint = "Add support for Webdis"
    +extensions[] = curl
    +extensions[] = phpiredis
    diff --git a/vendor/predis/predis/phpunit.xml.dist b/vendor/predis/predis/phpunit.xml.dist
    new file mode 100644
    index 0000000..1a6a012
    --- /dev/null
    +++ b/vendor/predis/predis/phpunit.xml.dist
    @@ -0,0 +1,39 @@
    +
    +
    +
    +    
    +        
    +            tests/Predis/
    +        
    +    
    +
    +    
    +        
    +            ext-phpiredis
    +            ext-curl
    +            realm-webdis
    +            
    +            
    +            
    +            
    +        
    +    
    +
    +    
    +        
    +            lib/Predis/
    +        
    +    
    +
    +    
    +        
    +        
    +        
    +        
    +        
    +
    +        
    +        
    +        
    +    
    +
    diff --git a/vendor/predis/predis/phpunit.xml.travisci b/vendor/predis/predis/phpunit.xml.travisci
    new file mode 100644
    index 0000000..6621124
    --- /dev/null
    +++ b/vendor/predis/predis/phpunit.xml.travisci
    @@ -0,0 +1,43 @@
    +
    +
    +
    +    
    +        
    +            tests/Predis/
    +        
    +    
    +
    +    
    +        
    +            ext-phpiredis
    +            ext-curl
    +            realm-webdis
    +            
    +            
    +            
    +            
    +        
    +    
    +
    +    
    +        
    +            lib/Predis/
    +        
    +    
    +
    +    
    +        
    +    
    +
    +    
    +        
    +        
    +        
    +        
    +        
    +
    +        
    +        
    +        
    +    
    +
    diff --git a/vendor/predis/predis/tests/PHPUnit/ArrayHasSameValuesConstraint.php b/vendor/predis/predis/tests/PHPUnit/ArrayHasSameValuesConstraint.php
    new file mode 100644
    index 0000000..c19cb78
    --- /dev/null
    +++ b/vendor/predis/predis/tests/PHPUnit/ArrayHasSameValuesConstraint.php
    @@ -0,0 +1,54 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +use \PHPUnit_Framework_Constraint;
    +use \PHPUnit_Framework_ExpectationFailedException;
    +
    +/**
    + * Constraint that accepts arrays with the same elements but different order.
    + */
    +class ArrayHasSameValuesConstraint extends PHPUnit_Framework_Constraint
    +{
    +    protected $array;
    +
    +    /**
    +     * @param array $array
    +     */
    +    public function __construct($array)
    +    {
    +        $this->array = $array;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function evaluate($other, $description = '', $returnResult = FALSE)
    +    {
    +        $description = $description ?: 'Failed asserting that two arrays have the same elements.';
    +
    +        if (count($this->array) !== count($other)) {
    +            throw new PHPUnit_Framework_ExpectationFailedException($description);
    +        }
    +        if (array_diff($this->array, $other)) {
    +            throw new PHPUnit_Framework_ExpectationFailedException($description);
    +        }
    +
    +        return true;
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function toString()
    +    {
    +        return 'two arrays have the same elements.';
    +    }
    +}
    \ No newline at end of file
    diff --git a/vendor/predis/predis/tests/PHPUnit/CommandTestCase.php b/vendor/predis/predis/tests/PHPUnit/CommandTestCase.php
    new file mode 100644
    index 0000000..a032494
    --- /dev/null
    +++ b/vendor/predis/predis/tests/PHPUnit/CommandTestCase.php
    @@ -0,0 +1,200 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Client;
    +use Predis\Profile\ServerProfile;
    +use Predis\Profile\ServerProfileInterface;
    +
    +/**
    + *
    + */
    +abstract class CommandTestCase extends StandardTestCase
    +{
    +    /**
    +     * Returns the expected command.
    +     *
    +     * @return CommandInterface|string Instance or FQN of the expected command.
    +     */
    +    protected abstract function getExpectedCommand();
    +
    +    /**
    +     * Returns the expected command ID.
    +     *
    +     * @return string
    +     */
    +    protected abstract function getExpectedId();
    +
    +    /**
    +     * Returns a new command instance.
    +     *
    +     * @return CommandInterface
    +     */
    +    protected function getCommand()
    +    {
    +        $command = $this->getExpectedCommand();
    +
    +        return $command instanceof CommandInterface ? $command : new $command();
    +    }
    +
    +    /**
    +     * Return the server profile used during the tests.
    +     *
    +     * @return ServerProfileInterface
    +     */
    +    protected function getProfile()
    +    {
    +        return ServerProfile::get(REDIS_SERVER_VERSION);
    +    }
    +
    +    /**
    +     * Returns a new client instance.
    +     *
    +     * @param Boolean $connect Flush selected database before returning the client.
    +     * @return Client
    +     */
    +    protected function getClient($flushdb = true)
    +    {
    +        $profile = $this->getProfile();
    +
    +        if (!$profile->supportsCommand($id = $this->getExpectedId())) {
    +            $this->markTestSkipped("The profile {$profile->getVersion()} does not support command {$id}");
    +        }
    +
    +        $parameters = array(
    +            'host' => REDIS_SERVER_HOST,
    +            'port' => REDIS_SERVER_PORT,
    +        );
    +
    +        $options = array(
    +            'profile' => $profile
    +        );
    +
    +        $client = new Client($parameters, $options);
    +        $client->connect();
    +        $client->select(REDIS_SERVER_DBNUM);
    +
    +        if ($flushdb) {
    +            $client->flushdb();
    +        }
    +
    +        return $client;
    +    }
    +
    +    /**
    +     * Returns wether the command is prefixable or not.
    +     *
    +     * @return Boolean
    +     */
    +    protected function isPrefixable()
    +    {
    +        return $this->getCommand() instanceof PrefixableCommandInterface;
    +    }
    +
    +    /**
    +     * Returns a new command instance with the specified arguments.
    +     *
    +     * @param ... List of arguments for the command.
    +     * @return CommandInterface
    +     */
    +    protected function getCommandWithArguments(/* arguments */)
    +    {
    +        return $this->getCommandWithArgumentsArray(func_get_args());
    +    }
    +
    +    /**
    +     * Returns a new command instance with the specified arguments.
    +     *
    +     * @param array $arguments Arguments for the command.
    +     * @return CommandInterface
    +     */
    +    protected function getCommandWithArgumentsArray(Array $arguments)
    +    {
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        return $command;
    +    }
    +
    +    /**
    +     * Sleep the test case with microseconds resolution.
    +     *
    +     * @param float $seconds Seconds to sleep.
    +     */
    +    protected function sleep($seconds)
    +    {
    +        usleep($seconds * 1000000);
    +    }
    +
    +    /**
    +     * Asserts that two arrays have the same values, even if with different order.
    +     *
    +     * @param Array $expected Expected array.
    +     * @param Array $actual Actual array.
    +     */
    +    protected function assertSameValues(Array $expected, Array $actual)
    +    {
    +        $this->assertThat($expected, new \ArrayHasSameValuesConstraint($actual));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCommandId()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertInstanceOf('Predis\Command\CommandInterface', $command);
    +        $this->assertEquals($this->getExpectedId(), $command->getId());
    +    }
    +
    +    /**
    +     * @param  string $expectedVersion
    +     * @param  string $message Optional message.
    +     * @throws \RuntimeException when unable to retrieve server info or redis version
    +     * @throws \PHPUnit_Framework_SkippedTestError when expected redis version is not met
    +     */
    +    protected function markTestSkippedOnRedisVersionBelow($expectedVersion, $message = '')
    +    {
    +        $client = $this->getClient();
    +        $info = array_change_key_case($client->info());
    +
    +        if (isset($info['server']['redis_version'])) {
    +            // Redis >= 2.6
    +            $version = $info['server']['redis_version'];
    +        } else if (isset($info['redis_version'])) {
    +            // Redis < 2.6
    +            $version = $info['redis_version'];
    +        } else {
    +            throw new \RuntimeException('Unable to retrieve server info');
    +        }
    +
    +        if (version_compare($version, $expectedVersion) <= -1) {
    +            $this->markTestSkipped($message ?: "Test requires Redis $expectedVersion, current is $version.");
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testRawArguments()
    +    {
    +        $expected = array('1st', '2nd', '3rd', '4th');
    +
    +        $command = $this->getCommand();
    +        $command->setRawArguments($expected);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/PHPUnit/ConnectionTestCase.php b/vendor/predis/predis/tests/PHPUnit/ConnectionTestCase.php
    new file mode 100644
    index 0000000..fd7c1a3
    --- /dev/null
    +++ b/vendor/predis/predis/tests/PHPUnit/ConnectionTestCase.php
    @@ -0,0 +1,360 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + * @group realm-connection
    + */
    +abstract class ConnectionTestCase extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     * @group slow
    +     * @expectedException Predis\Connection\ConnectionException
    +     */
    +    public function testThrowExceptionWhenUnableToConnect()
    +    {
    +        $parameters = array('host' => '169.254.10.10', 'timeout' => 0.5);
    +        $connection = $this->getConnection($profile, false, $parameters);
    +        $connection->executeCommand($this->getProfile()->createCommand('ping'));
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- INTEGRATION TESTS --------------------------------------------- //
    +    // ******************************************************************** //
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testConnectForcesConnection()
    +    {
    +        $connection = $this->getConnection();
    +
    +        $this->assertFalse($connection->isConnected());
    +        $connection->connect();
    +        $this->assertTrue($connection->isConnected());
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ClientException
    +     * @expectedExceptionMessage Connection already estabilished
    +     */
    +    public function testThrowsExceptionOnConnectWhenAlreadyConnected()
    +    {
    +        $connection = $this->getConnection();
    +
    +        $connection->connect();
    +        $connection->connect();
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testDisconnectForcesDisconnection()
    +    {
    +        $connection = $this->getConnection();
    +
    +        $connection->connect();
    +        $this->assertTrue($connection->isConnected());
    +
    +        $connection->disconnect();
    +        $this->assertFalse($connection->isConnected());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDoesNotThrowExceptionOnDisconnectWhenAlreadyDisconnected()
    +    {
    +        $connection = $this->getConnection();
    +
    +        $this->assertFalse($connection->isConnected());
    +        $connection->disconnect();
    +        $this->assertFalse($connection->isConnected());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testGetResourceForcesConnection()
    +    {
    +        $connection = $this->getConnection();
    +
    +        $this->assertFalse($connection->isConnected());
    +        $this->assertInternalType('resource', $connection->getResource());
    +        $this->assertTrue($connection->isConnected());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testSendingCommandForcesConnection()
    +    {
    +        $connection = $this->getConnection($profile);
    +        $cmdPing = $profile->createCommand('ping');
    +
    +        $this->assertSame('PONG', $connection->executeCommand($cmdPing));
    +        $this->assertTrue($connection->isConnected());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testExecutesCommandOnServer()
    +    {
    +        $connection = $this->getConnection($profile);
    +
    +        $cmdPing = $this->getMock($profile->getCommandClass('ping'), array('parseResponse'));
    +        $cmdPing->expects($this->never())
    +                ->method('parseResponse');
    +
    +        $this->assertSame('PONG', $connection->executeCommand($cmdPing));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testWritesCommandToServer()
    +    {
    +        $connection = $this->getConnection($profile);
    +
    +        $cmdPing = $this->getMock($profile->getCommandClass('ping'), array('parseResponse'));
    +        $cmdPing->expects($this->never())
    +                ->method('parseResponse');
    +
    +        $connection->writeCommand($cmdPing);
    +        $connection->disconnect();
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReadsCommandFromServer()
    +    {
    +        $connection = $this->getConnection($profile);
    +
    +        $cmdPing = $this->getMock($profile->getCommandClass('ping'), array('parseResponse'));
    +        $cmdPing->expects($this->never())
    +                ->method('parseResponse');
    +
    +        $connection->writeCommand($cmdPing);
    +        $this->assertSame('PONG', $connection->readResponse($cmdPing));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testIsAbleToWriteMultipleCommandsAndReadThemBackForPipelining()
    +    {
    +        $connection = $this->getConnection($profile);
    +
    +        $cmdPing = $this->getMock($profile->getCommandClass('ping'), array('parseResponse'));
    +        $cmdPing->expects($this->never())
    +                ->method('parseResponse');
    +
    +        $cmdEcho = $this->getMock($profile->getCommandClass('echo'), array('parseResponse'));
    +        $cmdEcho->setArguments(array('ECHOED'));
    +        $cmdEcho->expects($this->never())
    +                ->method('parseResponse');
    +
    +        $connection = $this->getConnection();
    +
    +        $connection->writeCommand($cmdPing);
    +        $connection->writeCommand($cmdEcho);
    +
    +        $this->assertSame('PONG', $connection->readResponse($cmdPing));
    +        $this->assertSame('ECHOED', $connection->readResponse($cmdEcho));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testSendsInitializationCommandsOnConnection()
    +    {
    +        $connection = $this->getConnection($profile, true);
    +
    +        $cmdPing = $this->getMock($profile->getCommandClass('ping'), array('getArguments'));
    +        $cmdPing->expects($this->once())
    +                ->method('getArguments')
    +                ->will($this->returnValue(array()));
    +
    +        $cmdEcho = $this->getMock($profile->getCommandClass('echo'), array('getArguments'));
    +        $cmdEcho->expects($this->once())
    +                ->method('getArguments')
    +                ->will($this->returnValue(array('ECHOED')));
    +
    +        $connection->pushInitCommand($cmdPing);
    +        $connection->pushInitCommand($cmdEcho);
    +
    +        $connection->connect();
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReadsStatusReplies()
    +    {
    +        $connection = $this->getConnection($profile, true);
    +
    +        $connection->writeCommand($profile->createCommand('set', array('foo', 'bar')));
    +        $this->assertTrue($connection->read());
    +
    +        $connection->writeCommand($profile->createCommand('ping'));
    +        $this->assertSame('PONG', $connection->read());
    +
    +        $connection->writeCommand($profile->createCommand('multi'));
    +        $connection->writeCommand($profile->createCommand('ping'));
    +        $this->assertTrue($connection->read());
    +        $this->assertInstanceOf('Predis\ResponseQueued', $connection->read());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReadsBulkReplies()
    +    {
    +        $connection = $this->getConnection($profile, true);
    +
    +        $connection->executeCommand($profile->createCommand('set', array('foo', 'bar')));
    +
    +        $connection->writeCommand($profile->createCommand('get', array('foo')));
    +        $this->assertSame('bar', $connection->read());
    +
    +        $connection->writeCommand($profile->createCommand('get', array('hoge')));
    +        $this->assertNull($connection->read());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReadsIntegerReplies()
    +    {
    +        $connection = $this->getConnection($profile, true);
    +
    +        $connection->executeCommand($profile->createCommand('rpush', array('metavars', 'foo', 'hoge', 'lol')));
    +        $connection->writeCommand($profile->createCommand('llen', array('metavars')));
    +
    +        $this->assertSame(3, $connection->read());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReadsErrorRepliesAsResponseErrorObjects()
    +    {
    +        $connection = $this->getConnection($profile, true);
    +
    +        $connection->executeCommand($profile->createCommand('set', array('foo', 'bar')));
    +        $connection->writeCommand($profile->createCommand('rpush', array('foo', 'baz')));
    +
    +        $this->assertInstanceOf('Predis\ResponseError', $error = $connection->read());
    +        $this->assertRegExp('/[ERR|WRONGTYPE] Operation against a key holding the wrong kind of value/', $error->getMessage());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReadsMultibulkRepliesAsArrays()
    +    {
    +        $connection = $this->getConnection($profile, true);
    +
    +        $connection->executeCommand($profile->createCommand('rpush', array('metavars', 'foo', 'hoge', 'lol')));
    +        $connection->writeCommand($profile->createCommand('lrange', array('metavars', 0, -1)));
    +
    +        $this->assertSame(array('foo', 'hoge', 'lol'), $connection->read());
    +    }
    +
    +    /**
    +     * @group connected
    +     * @group slow
    +     * @expectedException Predis\Connection\ConnectionException
    +     * @expectedExceptionMessage Connection timed out
    +     */
    +    public function testThrowsExceptionOnConnectionTimeout()
    +    {
    +        $connection = $this->getConnection($_, false, array('host' => '169.254.10.10', 'timeout' => 0.5));
    +
    +        $connection->connect();
    +    }
    +
    +    /**
    +     * @group connected
    +     * @group slow
    +     * @expectedException Predis\Connection\ConnectionException
    +     */
    +    public function testThrowsExceptionOnReadWriteTimeout()
    +    {
    +        $connection = $this->getConnection($profile, true, array('read_write_timeout' => 0.5));
    +
    +        $connection->executeCommand($profile->createCommand('brpop', array('foo', 3)));
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Returns a named array with the default connection parameters and their values.
    +     *
    +     * @return Array Default connection parameters.
    +     */
    +    protected function getDefaultParametersArray()
    +    {
    +        return array(
    +            'scheme' => 'tcp',
    +            'host' => REDIS_SERVER_HOST,
    +            'port' => REDIS_SERVER_PORT,
    +            'database' => REDIS_SERVER_DBNUM,
    +            'read_write_timeout' => 2,
    +        );
    +    }
    +
    +    /**
    +     * Returns a new instance of connection parameters.
    +     *
    +     * @param array $additional Additional connection parameters.
    +     * @return ConnectionParameters Default connection parameters.
    +     */
    +    protected function getParameters($additional = array())
    +    {
    +        $parameters = array_merge($this->getDefaultParametersArray(), $additional);
    +        $parameters = new ConnectionParameters($parameters);
    +
    +        return $parameters;
    +    }
    +
    +    /**
    +     * Returns a new instance of server profile.
    +     *
    +     * @param array $additional Additional connection parameters.
    +     * @return ServerProfile
    +     */
    +    protected function getProfile($version = null)
    +    {
    +        return ServerProfile::get($version ?: REDIS_SERVER_VERSION);
    +    }
    +
    +    /**
    +     * Returns a new instance of a connection instance.
    +     *
    +     * @param ServerProfile $profile Reference to the server profile instance.
    +     * @param Boolean $initialize Push default initialization commands (SELECT and FLUSHDB).
    +     * @param array $parameters Additional connection parameters.
    +     * @return StreamConnection
    +     */
    +    protected abstract function getConnection(&$profile = null, $initialize = false, Array $parameters = array());
    +}
    diff --git a/vendor/predis/predis/tests/PHPUnit/DistributionStrategyTestCase.php b/vendor/predis/predis/tests/PHPUnit/DistributionStrategyTestCase.php
    new file mode 100644
    index 0000000..1097d48
    --- /dev/null
    +++ b/vendor/predis/predis/tests/PHPUnit/DistributionStrategyTestCase.php
    @@ -0,0 +1,67 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Cluster\Distribution;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +abstract class DistributionStrategyTestCase extends StandardTestCase
    +{
    +    /**
    +     * Returns a new instance of the tested distributor.
    +     *
    +     * @return Predis\Cluster\Distribution\DistributionStrategyInterface
    +     */
    +    protected abstract function getDistributorInstance();
    +
    +    /**
    +     * Returns a list of nodes from the hashring.
    +     *
    +     * @param DistributionStrategyInterface $ring Hashring instance.
    +     * @param int $iterations Number of nodes to fetch.
    +     * @return array Nodes from the hashring.
    +     */
    +    protected function getNodes(DistributionStrategyInterface $ring, $iterations = 10)
    +    {
    +        $nodes = array();
    +
    +        for ($i = 0; $i < $iterations; $i++) {
    +            $key = $ring->hash($i * $i);
    +            $nodes[] = $ring->get($key);
    +        }
    +
    +        return $nodes;
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testEmptyRingThrowsException()
    +    {
    +        $this->setExpectedException('Predis\Cluster\Distribution\EmptyRingException');
    +
    +        $ring = $this->getDistributorInstance();
    +        $ring->get('nodekey');
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testRemoveOnEmptyRingDoesNotThrowException()
    +    {
    +        $ring = $this->getDistributorInstance();
    +
    +        $this->assertNull($ring->remove('node'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/PHPUnit/ServerVersionTestCase.php b/vendor/predis/predis/tests/PHPUnit/ServerVersionTestCase.php
    new file mode 100644
    index 0000000..aef28a3
    --- /dev/null
    +++ b/vendor/predis/predis/tests/PHPUnit/ServerVersionTestCase.php
    @@ -0,0 +1,77 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Profile;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +abstract class ServerVersionTestCase extends StandardTestCase
    +{
    +    /**
    +     * Returns a new instance of the tested profile.
    +     *
    +     * @return ServerProfileInterface
    +     */
    +    protected abstract function getProfileInstance();
    +
    +    /**
    +     * Returns the expected version string for the tested profile.
    +     *
    +     * @return string Version string.
    +     */
    +    protected abstract function getExpectedVersion();
    +
    +    /**
    +     * Returns the expected list of commands supported by the tested profile.
    +     *
    +     * @return array List of supported commands.
    +     */
    +    protected abstract function getExpectedCommands();
    +
    +    /**
    +     * Returns the list of commands supported by the current
    +     * server profile.
    +     *
    +     * @param ServerProfileInterface $profile Server profile instance.
    +     * @return array
    +     */
    +    protected function getCommands(ServerProfileInterface $profile)
    +    {
    +        $commands = $profile->getSupportedCommands();
    +
    +        return array_keys($commands);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetVersion()
    +    {
    +        $profile = $this->getProfileInstance();
    +
    +        $this->assertEquals($this->getExpectedVersion(), $profile->getVersion());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSupportedCommands()
    +    {
    +        $profile = $this->getProfileInstance();
    +        $expected = $this->getExpectedCommands();
    +        $commands = $this->getCommands($profile);
    +
    +        $this->assertSame($expected, $commands);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/ClientExceptionTest.php b/vendor/predis/predis/tests/Predis/ClientExceptionTest.php
    new file mode 100644
    index 0000000..13e6c94
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/ClientExceptionTest.php
    @@ -0,0 +1,43 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class ClientExceptionTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExceptionMessage()
    +    {
    +        $message = 'This is a client exception.';
    +
    +        $this->setExpectedException('Predis\ClientException', $message);
    +
    +        throw new ClientException($message);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExceptionClass()
    +    {
    +        $exception = new ClientException();
    +
    +        $this->assertInstanceOf('Predis\ClientException', $exception);
    +        $this->assertInstanceOf('Predis\PredisException', $exception);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/ClientTest.php b/vendor/predis/predis/tests/Predis/ClientTest.php
    new file mode 100644
    index 0000000..5d3f16b
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/ClientTest.php
    @@ -0,0 +1,797 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Connection\ConnectionFactory;
    +use Predis\Connection\MasterSlaveReplication;
    +use Predis\Connection\PredisCluster;
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + *
    + */
    +class ClientTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithoutArguments()
    +    {
    +        $client = new Client();
    +
    +        $connection = $client->getConnection();
    +        $this->assertInstanceOf('Predis\Connection\SingleConnectionInterface', $connection);
    +
    +        $parameters = $connection->getParameters();
    +        $this->assertSame($parameters->host, '127.0.0.1');
    +        $this->assertSame($parameters->port, 6379);
    +
    +        $options = $client->getOptions();
    +        $this->assertSame($options->profile->getVersion(), ServerProfile::getDefault()->getVersion());
    +
    +        $this->assertFalse($client->isConnected());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithNullArgument()
    +    {
    +        $client = new Client(null);
    +
    +        $connection = $client->getConnection();
    +        $this->assertInstanceOf('Predis\Connection\SingleConnectionInterface', $connection);
    +
    +        $parameters = $connection->getParameters();
    +        $this->assertSame($parameters->host, '127.0.0.1');
    +        $this->assertSame($parameters->port, 6379);
    +
    +        $options = $client->getOptions();
    +        $this->assertSame($options->profile->getVersion(), ServerProfile::getDefault()->getVersion());
    +
    +        $this->assertFalse($client->isConnected());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithNullAndNullArguments()
    +    {
    +        $client = new Client(null, null);
    +
    +        $connection = $client->getConnection();
    +        $this->assertInstanceOf('Predis\Connection\SingleConnectionInterface', $connection);
    +
    +        $parameters = $connection->getParameters();
    +        $this->assertSame($parameters->host, '127.0.0.1');
    +        $this->assertSame($parameters->port, 6379);
    +
    +        $options = $client->getOptions();
    +        $this->assertSame($options->profile->getVersion(), ServerProfile::getDefault()->getVersion());
    +
    +        $this->assertFalse($client->isConnected());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithArrayArgument()
    +    {
    +        $client = new Client($arg1 = array('host' => 'localhost', 'port' => 7000));
    +
    +        $parameters = $client->getConnection()->getParameters();
    +        $this->assertSame($parameters->host, $arg1['host']);
    +        $this->assertSame($parameters->port, $arg1['port']);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithArrayOfArrayArgument()
    +    {
    +        $arg1 = array(
    +            array('host' => 'localhost', 'port' => 7000),
    +            array('host' => 'localhost', 'port' => 7001),
    +        );
    +
    +        $client = new Client($arg1);
    +
    +        $this->assertInstanceOf('Predis\Connection\ClusterConnectionInterface', $client->getConnection());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithStringArgument()
    +    {
    +        $client = new Client('tcp://localhost:7000');
    +
    +        $parameters = $client->getConnection()->getParameters();
    +        $this->assertSame($parameters->host, 'localhost');
    +        $this->assertSame($parameters->port, 7000);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithArrayOfStringArgument()
    +    {
    +        $client = new Client($arg1 = array('tcp://localhost:7000', 'tcp://localhost:7001'));
    +
    +        $this->assertInstanceOf('Predis\Connection\ClusterConnectionInterface', $client->getConnection());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithArrayOfConnectionsArgument()
    +    {
    +        $connection1 = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection2 = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +
    +        $client = new Client(array($connection1, $connection2));
    +
    +        $this->assertInstanceOf('Predis\Connection\ClusterConnectionInterface', $cluster = $client->getConnection());
    +        $this->assertSame($connection1, $cluster->getConnectionById(0));
    +        $this->assertSame($connection2, $cluster->getConnectionById(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithConnectionArgument()
    +    {
    +        $factory = new ConnectionFactory();
    +        $connection = $factory->create('tcp://localhost:7000');
    +
    +        $client = new Client($connection);
    +
    +        $this->assertInstanceOf('Predis\Connection\SingleConnectionInterface', $client->getConnection());
    +        $this->assertSame($connection, $client->getConnection());
    +
    +        $parameters = $client->getConnection()->getParameters();
    +        $this->assertSame($parameters->host, 'localhost');
    +        $this->assertSame($parameters->port, 7000);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithClusterArgument()
    +    {
    +        $cluster = new PredisCluster();
    +
    +        $factory = new ConnectionFactory();
    +        $factory->createAggregated($cluster, array('tcp://localhost:7000', 'tcp://localhost:7001'));
    +
    +        $client = new Client($cluster);
    +
    +        $this->assertInstanceOf('Predis\Connection\ClusterConnectionInterface', $client->getConnection());
    +        $this->assertSame($cluster, $client->getConnection());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithReplicationArgument()
    +    {
    +        $replication = new MasterSlaveReplication();
    +
    +        $factory = new ConnectionFactory();
    +        $factory->createAggregated($replication, array('tcp://host1?alias=master', 'tcp://host2?alias=slave'));
    +
    +        $client = new Client($replication);
    +
    +        $this->assertInstanceOf('Predis\Connection\ReplicationConnectionInterface', $client->getConnection());
    +        $this->assertSame($replication, $client->getConnection());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithCallableArgument()
    +    {
    +        $connection = $this->getMock('Predis\Connection\ConnectionInterface');
    +
    +        $callable = $this->getMock('stdClass', array('__invoke'));
    +        $callable->expects($this->once())
    +                 ->method('__invoke')
    +                 ->with($this->isInstanceOf('Predis\Option\ClientOptions'))
    +                 ->will($this->returnValue($connection));
    +
    +        $client = new Client($callable);
    +
    +        $this->assertSame($connection, $client->getConnection());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException InvalidArgumentException
    +     * @expectedExceptionMessage Callable parameters must return instances of Predis\Connection\ConnectionInterface
    +     */
    +    public function testConstructorWithCallableArgumentButInvalidReturnType()
    +    {
    +        $wrongType = $this->getMock('stdClass');
    +
    +        $callable = $this->getMock('stdClass', array('__invoke'));
    +        $callable->expects($this->once())
    +                 ->method('__invoke')
    +                 ->with($this->isInstanceOf('Predis\Option\ClientOptions'))
    +                 ->will($this->returnValue($wrongType));
    +
    +        $client = new Client($callable);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithNullAndArrayArgument()
    +    {
    +        $factory = $this->getMock('Predis\Connection\ConnectionFactoryInterface');
    +
    +        $arg2 = array('profile' => '2.0', 'prefix' => 'prefix:', 'connections' => $factory);
    +        $client = new Client(null, $arg2);
    +
    +        $profile = $client->getProfile();
    +        $this->assertSame($profile->getVersion(), ServerProfile::get('2.0')->getVersion());
    +        $this->assertInstanceOf('Predis\Command\Processor\KeyPrefixProcessor', $profile->getProcessor());
    +        $this->assertSame('prefix:', $profile->getProcessor()->getPrefix());
    +
    +        $this->assertSame($factory, $client->getConnectionFactory());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithArrayAndOptionReplicationArgument()
    +    {
    +        $arg1 = array('tcp://host1?alias=master', 'tcp://host2?alias=slave');
    +        $arg2 = array('replication' => true);
    +        $client = new Client($arg1, $arg2);
    +
    +        $this->assertInstanceOf('Predis\Connection\ReplicationConnectionInterface', $connection = $client->getConnection());
    +        $this->assertSame('host1', $connection->getConnectionById('master')->getParameters()->host);
    +        $this->assertSame('host2', $connection->getConnectionById('slave')->getParameters()->host);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConnectAndDisconnect()
    +    {
    +        $connection = $this->getMock('Predis\Connection\ConnectionInterface');
    +        $connection->expects($this->once())->method('connect');
    +        $connection->expects($this->once())->method('disconnect');
    +
    +        $client = new Client($connection);
    +        $client->connect();
    +        $client->disconnect();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testIsConnectedChecksConnectionState()
    +    {
    +        $connection = $this->getMock('Predis\Connection\ConnectionInterface');
    +        $connection->expects($this->once())->method('isConnected');
    +
    +        $client = new Client($connection);
    +        $client->isConnected();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testQuitIsAliasForDisconnect()
    +    {
    +        $connection = $this->getMock('Predis\Connection\ConnectionInterface');
    +        $connection->expects($this->once())->method('disconnect');
    +
    +        $client = new Client($connection);
    +        $client->quit();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCreatesNewCommandUsingSpecifiedProfile()
    +    {
    +        $ping = ServerProfile::getDefault()->createCommand('ping', array());
    +
    +        $profile = $this->getMock('Predis\Profile\ServerProfileInterface');
    +        $profile->expects($this->once())
    +                ->method('createCommand')
    +                ->with('ping', array())
    +                ->will($this->returnValue($ping));
    +
    +        $client = new Client(null, array('profile' => $profile));
    +        $this->assertSame($ping, $client->createCommand('ping', array()));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecuteCommandReturnsParsedReplies()
    +    {
    +        $profile = ServerProfile::getDefault();
    +
    +        $ping = $profile->createCommand('ping', array());
    +        $hgetall = $profile->createCommand('hgetall', array('metavars', 'foo', 'hoge'));
    +
    +        $connection= $this->getMock('Predis\Connection\ConnectionInterface');
    +        $connection->expects($this->at(0))
    +                   ->method('executeCommand')
    +                   ->with($ping)
    +                   ->will($this->returnValue('PONG'));
    +        $connection->expects($this->at(1))
    +                   ->method('executeCommand')
    +                   ->with($hgetall)
    +                   ->will($this->returnValue(array('foo', 'bar', 'hoge', 'piyo')));
    +
    +        $client = new Client($connection);
    +
    +        $this->assertTrue($client->executeCommand($ping));
    +        $this->assertSame(array('foo' => 'bar', 'hoge' => 'piyo'), $client->executeCommand($hgetall));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testExecuteCommandThrowsExceptionOnRedisError()
    +    {
    +        $ping = ServerProfile::getDefault()->createCommand('ping', array());
    +        $expectedResponse = new ResponseError('ERR Operation against a key holding the wrong kind of value');
    +
    +        $connection= $this->getMock('Predis\Connection\ConnectionInterface');
    +        $connection->expects($this->once())
    +                   ->method('executeCommand')
    +                   ->will($this->returnValue($expectedResponse));
    +
    +        $client = new Client($connection);
    +        $client->executeCommand($ping);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecuteCommandReturnsErrorResponseOnRedisError()
    +    {
    +        $ping = ServerProfile::getDefault()->createCommand('ping', array());
    +        $expectedResponse = new ResponseError('ERR Operation against a key holding the wrong kind of value');
    +
    +        $connection= $this->getMock('Predis\Connection\ConnectionInterface');
    +        $connection->expects($this->once())
    +                   ->method('executeCommand')
    +                   ->will($this->returnValue($expectedResponse));
    +
    +        $client = new Client($connection, array('exceptions' => false));
    +        $response = $client->executeCommand($ping);
    +
    +        $this->assertSame($response, $expectedResponse);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCallingRedisCommandExecutesInstanceOfCommand()
    +    {
    +        $ping = ServerProfile::getDefault()->createCommand('ping', array());
    +
    +        $connection = $this->getMock('Predis\Connection\ConnectionInterface');
    +        $connection->expects($this->once())
    +                   ->method('executeCommand')
    +                   ->with($this->isInstanceOf('Predis\Command\ConnectionPing'))
    +                   ->will($this->returnValue('PONG'));
    +
    +        $profile = $this->getMock('Predis\Profile\ServerProfileInterface');
    +        $profile->expects($this->once())
    +                ->method('createCommand')
    +                ->with('ping', array())
    +                ->will($this->returnValue($ping));
    +
    +        $options = array('profile' => $profile);
    +        $client = $this->getMock('Predis\Client', array('createCommand'), array($connection, $options));
    +
    +        $this->assertTrue($client->ping());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testCallingRedisCommandThrowsExceptionOnServerError()
    +    {
    +        $expectedResponse = new ResponseError('ERR Operation against a key holding the wrong kind of value');
    +
    +        $connection = $this->getMock('Predis\Connection\ConnectionInterface');
    +        $connection->expects($this->once())
    +                   ->method('executeCommand')
    +                   ->with($this->isInstanceOf('Predis\Command\ConnectionPing'))
    +                   ->will($this->returnValue($expectedResponse));
    +
    +        $client = new Client($connection);
    +        $client->ping();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCallingRedisCommandReturnsErrorResponseOnRedisError()
    +    {
    +        $expectedResponse = new ResponseError('ERR Operation against a key holding the wrong kind of value');
    +
    +        $connection = $this->getMock('Predis\Connection\ConnectionInterface');
    +        $connection->expects($this->once())
    +                   ->method('executeCommand')
    +                   ->with($this->isInstanceOf('Predis\Command\ConnectionPing'))
    +                   ->will($this->returnValue($expectedResponse));
    +
    +        $client = new Client($connection, array('exceptions' => false));
    +        $response = $client->ping();
    +
    +        $this->assertSame($response, $expectedResponse);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\ClientException
    +     * @expectedExceptionMessage 'invalidcommand' is not a registered Redis command
    +     */
    +    public function testThrowsExceptionOnNonRegisteredRedisCommand()
    +    {
    +        $client = new Client();
    +        $client->invalidCommand();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetConnectionFromAggregatedConnectionWithAlias()
    +    {
    +        $client = new Client(array('tcp://host1?alias=node01', 'tcp://host2?alias=node02'));
    +
    +        $this->assertInstanceOf('Predis\Connection\ClusterConnectionInterface', $cluster = $client->getConnection());
    +        $this->assertInstanceOf('Predis\Connection\SingleConnectionInterface', $node01 = $client->getConnectionById('node01'));
    +        $this->assertInstanceOf('Predis\Connection\SingleConnectionInterface', $node02 = $client->getConnectionById('node02'));
    +
    +        $this->assertSame('host1', $node01->getParameters()->host);
    +        $this->assertSame('host2', $node02->getParameters()->host);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\NotSupportedException
    +     * @expectedExceptionMessage Retrieving connections by ID is supported only when using aggregated connections
    +     */
    +    public function testGetConnectionByIdWorksOnlyWithAggregatedConnections()
    +    {
    +        $client = new Client();
    +
    +        $client->getConnectionById('node01');
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCreateClientWithConnectionFromAggregatedConnection()
    +    {
    +        $client = new Client(array('tcp://host1?alias=node01', 'tcp://host2?alias=node02'), array('prefix' => 'pfx:'));
    +
    +        $this->assertInstanceOf('Predis\Connection\ClusterConnectionInterface', $cluster = $client->getConnection());
    +        $this->assertInstanceOf('Predis\Connection\SingleConnectionInterface', $node01 = $client->getConnectionById('node01'));
    +        $this->assertInstanceOf('Predis\Connection\SingleConnectionInterface', $node02 = $client->getConnectionById('node02'));
    +
    +        $clientNode02 = $client->getClientFor('node02');
    +
    +        $this->assertInstanceOf('Predis\Client', $clientNode02);
    +        $this->assertSame($node02, $clientNode02->getConnection());
    +        $this->assertSame($client->getOptions(), $clientNode02->getOptions());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetClientForReturnsInstanceOfSubclass()
    +    {
    +        $nodes = array('tcp://host1?alias=node01', 'tcp://host2?alias=node02');
    +        $client = $this->getMock('Predis\Client', array('dummy'), array($nodes), 'SubclassedClient');
    +
    +        $this->assertInstanceOf('SubclassedClient', $client->getClientFor('node02'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPipelineWithoutArgumentsReturnsPipelineContext()
    +    {
    +        $client = new Client();
    +
    +        $this->assertInstanceOf('Predis\Pipeline\PipelineContext', $pipeline = $client->pipeline());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPipelineWithArrayReturnsPipelineContextWithOptions()
    +    {
    +        $client = new Client();
    +        $executor = $this->getMock('Predis\Pipeline\PipelineExecutorInterface');
    +
    +        $options = array('executor' => $executor);
    +        $this->assertInstanceOf('Predis\Pipeline\PipelineContext', $pipeline = $client->pipeline($options));
    +        $this->assertSame($executor, $pipeline->getExecutor());
    +
    +        $options = array('executor' => function ($client, $options) use ($executor) { return $executor; });
    +        $this->assertInstanceOf('Predis\Pipeline\PipelineContext', $pipeline = $client->pipeline($options));
    +        $this->assertSame($executor, $pipeline->getExecutor());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPipelineWithCallableExecutesPipeline()
    +    {
    +        $callable = $this->getMock('stdClass', array('__invoke'));
    +        $callable->expects($this->once())
    +                 ->method('__invoke')
    +                 ->with($this->isInstanceOf('Predis\Pipeline\PipelineContext'));
    +
    +        $client = new Client();
    +        $client->pipeline($callable);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPipelineWithArrayAndCallableExecutesPipelineWithOptions()
    +    {
    +        $executor = $this->getMock('Predis\Pipeline\PipelineExecutorInterface');
    +        $options = array('executor' => $executor);
    +
    +        $test = $this;
    +        $mockCallback = function ($pipeline) use ($executor, $test) {
    +            $reflection = new \ReflectionProperty($pipeline, 'executor');
    +            $reflection->setAccessible(true);
    +
    +            $test->assertSame($executor, $reflection->getValue($pipeline));
    +        };
    +
    +        $callable = $this->getMock('stdClass', array('__invoke'));
    +        $callable->expects($this->once())
    +                 ->method('__invoke')
    +                 ->with($this->isInstanceOf('Predis\Pipeline\PipelineContext'))
    +                 ->will($this->returnCallback($mockCallback));
    +
    +        $client = new Client();
    +        $client->pipeline($options, $callable);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPubSubWithoutArgumentsReturnsPubSubContext()
    +    {
    +        $client = new Client();
    +
    +        $this->assertInstanceOf('Predis\PubSub\PubSubContext', $pubsub = $client->pubSub());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPubSubWithArrayReturnsPubSubContextWithOptions()
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $options = array('subscribe' => 'channel');
    +
    +        $client = new Client($connection);
    +
    +        $this->assertInstanceOf('Predis\PubSub\PubSubContext', $pubsub = $client->pubSub($options));
    +
    +        $reflection = new \ReflectionProperty($pubsub, 'options');
    +        $reflection->setAccessible(true);
    +
    +        $this->assertSame($options, $reflection->getValue($pubsub));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPubSubWithArrayAndCallableExecutesPubSub()
    +    {
    +        // NOTE: we use a subscribe count of 0 in the fake message to trick
    +        //       the context and to make it think that it can be closed
    +        //       since there are no more subscriptions active.
    +
    +        $message = array('subscribe', 'channel', 0);
    +        $options = array('subscribe' => 'channel');
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->once())
    +                   ->method('read')
    +                   ->will($this->returnValue($message));
    +
    +        $callable = $this->getMock('stdClass', array('__invoke'));
    +        $callable->expects($this->once())
    +                 ->method('__invoke');
    +
    +        $client = new Client($connection);
    +        $client->pubSub($options, $callable);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testMultiExecWithoutArgumentsReturnsMultiExecContext()
    +    {
    +        $client = new Client();
    +
    +        $this->assertInstanceOf('Predis\Transaction\MultiExecContext', $pubsub = $client->multiExec());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testMultiExecWithArrayReturnsMultiExecContextWithOptions()
    +    {
    +        $options = array('cas' => true, 'retry' => 3);
    +
    +        $client = new Client();
    +
    +        $this->assertInstanceOf('Predis\Transaction\MultiExecContext', $tx = $client->multiExec($options));
    +
    +        $reflection = new \ReflectionProperty($tx, 'options');
    +        $reflection->setAccessible(true);
    +
    +        $this->assertSame($options, $reflection->getValue($tx));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testMultiExecWithArrayAndCallableExecutesMultiExec()
    +    {
    +        // NOTE: we use CAS since testing the actual MULTI/EXEC context
    +        //       here is not the point.
    +        $options = array('cas' => true, 'retry' => 3);
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->once())
    +                   ->method('executeCommand')
    +                   ->will($this->returnValue(new ResponseQueued()));
    +
    +        $txCallback = function ($tx) {
    +            $tx->ping();
    +        };
    +
    +        $callable = $this->getMock('stdClass', array('__invoke'));
    +        $callable->expects($this->once())
    +                 ->method('__invoke')
    +                 ->will($this->returnCallback($txCallback));
    +
    +        $client = new Client($connection);
    +        $client->multiExec($options, $callable);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testMonitorReturnsMonitorContext()
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $client = new Client($connection);
    +
    +        $this->assertInstanceOf('Predis\Monitor\MonitorContext', $monitor = $client->monitor());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testClientResendScriptedCommandUsingEvalOnNoScriptErrors()
    +    {
    +        $command = $this->getMockForAbstractClass('Predis\Command\ScriptedCommand', array(), '', true, true, true, array('parseResponse'));
    +        $command->expects($this->once())
    +                ->method('getScript')
    +                ->will($this->returnValue('return redis.call(\'exists\', KEYS[1])'));
    +        $command->expects($this->once())
    +                ->method('parseResponse')
    +                ->with('OK')
    +                ->will($this->returnValue(true));
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->at(0))
    +                   ->method('executeCommand')
    +                   ->with($command)
    +                   ->will($this->returnValue(new ResponseError('NOSCRIPT')));
    +        $connection->expects($this->at(1))
    +                   ->method('executeCommand')
    +                   ->with($this->isInstanceOf('Predis\Command\ServerEval'))
    +                   ->will($this->returnValue('OK'));
    +
    +        $client = new Client($connection);
    +
    +        $this->assertTrue($client->executeCommand($command));
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Returns a named array with the default connection parameters and their values.
    +     *
    +     * @return Array Default connection parameters.
    +     */
    +    protected function getDefaultParametersArray()
    +    {
    +        return array(
    +            'scheme' => 'tcp',
    +            'host' => REDIS_SERVER_HOST,
    +            'port' => REDIS_SERVER_PORT,
    +            'database' => REDIS_SERVER_DBNUM,
    +        );
    +    }
    +
    +    /**
    +     * Returns a named array with the default client options and their values.
    +     *
    +     * @return Array Default connection parameters.
    +     */
    +    protected function getDefaultOptionsArray()
    +    {
    +        return array(
    +            'profile' => REDIS_SERVER_VERSION,
    +        );
    +    }
    +
    +    /**
    +     * Returns a named array with the default connection parameters merged with
    +     * the specified additional parameters.
    +     *
    +     * @param Array $additional Additional connection parameters.
    +     * @return Array Connection parameters.
    +     */
    +    protected function getParametersArray(Array $additional)
    +    {
    +        return array_merge($this->getDefaultParametersArray(), $additional);
    +    }
    +
    +    /**
    +     * Returns an URI string representation of the specified connection parameters.
    +     *
    +     * @param Array $parameters Array of connection parameters.
    +     * @return String URI string.
    +     */
    +    protected function getParametersString(Array $parameters)
    +    {
    +        $defaults = $this->getDefaultParametersArray();
    +
    +        $scheme = isset($parameters['scheme']) ? $parameters['scheme'] : $defaults['scheme'];
    +        $host = isset($parameters['host']) ? $parameters['host'] : $defaults['host'];
    +        $port = isset($parameters['port']) ? $parameters['port'] : $defaults['port'];
    +
    +        unset($parameters['scheme'], $parameters['host'], $parameters['port']);
    +        $uriString = "$scheme://$host:$port/?";
    +
    +        foreach ($parameters as $k => $v) {
    +            $uriString .= "$k=$v&";
    +        }
    +
    +        return $uriString;
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Cluster/Distribution/EmptyRingExceptionTest.php b/vendor/predis/predis/tests/Predis/Cluster/Distribution/EmptyRingExceptionTest.php
    new file mode 100644
    index 0000000..9a11e0c
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Cluster/Distribution/EmptyRingExceptionTest.php
    @@ -0,0 +1,31 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Cluster\Distribution;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @todo Not really useful right now.
    + */
    +class EmptyRingExceptionTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExceptionMessage()
    +    {
    +        $message = 'Empty Ring';
    +        $this->setExpectedException('Predis\Cluster\Distribution\EmptyRingException', $message);
    +
    +        throw new EmptyRingException($message);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Cluster/Distribution/HashRingTest.php b/vendor/predis/predis/tests/Predis/Cluster/Distribution/HashRingTest.php
    new file mode 100644
    index 0000000..e618ea0
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Cluster/Distribution/HashRingTest.php
    @@ -0,0 +1,153 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Cluster\Distribution;
    +
    +/**
    + * @todo To be improved.
    + */
    +class HashRingTest extends DistributionStrategyTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getDistributorInstance()
    +    {
    +        return new HashRing();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testHash()
    +    {
    +        $ring = $this->getDistributorInstance();
    +
    +        $this->assertEquals(crc32('foobar'), $ring->hash('foobar'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSingleNodeInRing()
    +    {
    +        $node = '127.0.0.1:7000';
    +
    +        $ring = $this->getDistributorInstance();
    +        $ring->add($node);
    +
    +        $expected = array_fill(0, 20, $node);
    +        $actual = $this->getNodes($ring, 20);
    +
    +        $this->assertSame($expected, $actual);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testMultipleNodesInRing()
    +    {
    +        $nodes = array(
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7002',
    +        );
    +
    +        $ring = $this->getDistributorInstance();
    +        foreach ($nodes as $node) {
    +            $ring->add($node);
    +        }
    +
    +        $expected = array(
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7002',
    +            '127.0.0.1:7002',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7002',
    +            '127.0.0.1:7002',
    +            '127.0.0.1:7002',
    +            '127.0.0.1:7002',
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7002',
    +            '127.0.0.1:7002',
    +            '127.0.0.1:7002',
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7002',
    +        );
    +
    +        $actual = $this->getNodes($ring, 20);
    +
    +        $this->assertSame($expected, $actual);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSubsequendAddAndRemoveFromRing()
    +    {
    +        $ring = $this->getDistributorInstance();
    +
    +        $expected1 = array_fill(0, 10, '127.0.0.1:7000');
    +        $expected3 = array_fill(0, 10, '127.0.0.1:7001');
    +        $expected2 = array(
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7000',
    +        );
    +
    +        $ring->add('127.0.0.1:7000');
    +        $actual1 = $this->getNodes($ring, 10);
    +
    +        $ring->add('127.0.0.1:7001');
    +        $actual2 = $this->getNodes($ring, 10);
    +
    +        $ring->remove('127.0.0.1:7000');
    +        $actual3 = $this->getNodes($ring, 10);
    +
    +        $this->assertSame($expected1, $actual1);
    +        $this->assertSame($expected2, $actual2);
    +        $this->assertSame($expected3, $actual3);
    +    }
    +
    +    /**
    +     * @todo This tests should be moved in Predis\Cluster\Distribution\DistributionStrategyTestCase
    +     * @group disconnected
    +     */
    +    public function testCallbackToGetNodeHash()
    +    {
    +        $node = '127.0.0.1:7000';
    +        $replicas = HashRing::DEFAULT_REPLICAS;
    +        $callable = $this->getMock('stdClass', array('__invoke'));
    +
    +        $callable->expects($this->once())
    +                 ->method('__invoke')
    +                 ->with($node)
    +                 ->will($this->returnValue($node));
    +
    +        $ring = new HashRing($replicas, $callable);
    +        $ring->add($node);
    +
    +        $this->getNodes($ring);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Cluster/Distribution/KetamaPureRingTest.php b/vendor/predis/predis/tests/Predis/Cluster/Distribution/KetamaPureRingTest.php
    new file mode 100644
    index 0000000..b5c3145
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Cluster/Distribution/KetamaPureRingTest.php
    @@ -0,0 +1,153 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Cluster\Distribution;
    +
    +/**
    + * @todo To be improved.
    + */
    +class KetamaPureRingTest extends DistributionStrategyTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getDistributorInstance()
    +    {
    +        return new KetamaPureRing();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testHash()
    +    {
    +        $ring = $this->getDistributorInstance();
    +        list(, $hash) = unpack('V', md5('foobar', true));
    +
    +        $this->assertEquals($hash, $ring->hash('foobar'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSingleNodeInRing()
    +    {
    +        $node = '127.0.0.1:7000';
    +
    +        $ring = $this->getDistributorInstance();
    +        $ring->add($node);
    +
    +        $expected = array_fill(0, 20, $node);
    +        $actual = $this->getNodes($ring, 20);
    +
    +        $this->assertSame($expected, $actual);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testMultipleNodesInRing()
    +    {
    +        $nodes = array(
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7002',
    +        );
    +
    +        $ring = $this->getDistributorInstance();
    +        foreach ($nodes as $node) {
    +            $ring->add($node);
    +        }
    +
    +        $expected = array(
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7002',
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7002',
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7002',
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7002',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7002',
    +        );
    +
    +        $actual = $this->getNodes($ring, 20);
    +
    +        $this->assertSame($expected, $actual);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSubsequendAddAndRemoveFromRing()
    +    {
    +        $ring = $this->getDistributorInstance();
    +
    +        $expected1 = array_fill(0, 10, '127.0.0.1:7000');
    +        $expected3 = array_fill(0, 10, '127.0.0.1:7001');
    +        $expected2 = array(
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7001',
    +            '127.0.0.1:7000',
    +            '127.0.0.1:7001',
    +        );
    +
    +        $ring->add('127.0.0.1:7000');
    +        $actual1 = $this->getNodes($ring, 10);
    +
    +        $ring->add('127.0.0.1:7001');
    +        $actual2 = $this->getNodes($ring, 10);
    +
    +        $ring->remove('127.0.0.1:7000');
    +        $actual3 = $this->getNodes($ring, 10);
    +
    +        $this->assertSame($expected1, $actual1);
    +        $this->assertSame($expected2, $actual2);
    +        $this->assertSame($expected3, $actual3);
    +    }
    +
    +    /**
    +     * @todo This tests should be moved in Predis\Cluster\Distribution\DistributionStrategyTestCase
    +     * @group disconnected
    +     */
    +    public function testCallbackToGetNodeHash()
    +    {
    +        $node = '127.0.0.1:7000';
    +        $callable = $this->getMock('stdClass', array('__invoke'));
    +
    +        $callable->expects($this->once())
    +                 ->method('__invoke')
    +                 ->with($node)
    +                 ->will($this->returnValue($node));
    +
    +        $ring = new KetamaPureRing($callable);
    +        $ring->add($node);
    +
    +        $this->getNodes($ring);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Cluster/PredisClusterHashStrategyTest.php b/vendor/predis/predis/tests/Predis/Cluster/PredisClusterHashStrategyTest.php
    new file mode 100644
    index 0000000..2cf14db
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Cluster/PredisClusterHashStrategyTest.php
    @@ -0,0 +1,372 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Cluster;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Cluster\Distribution\HashRing;
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + *
    + */
    +class PredisClusterHashStrategyTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSupportsKeyTags()
    +    {
    +        // NOTE: 32 and 64 bits PHP runtimes can produce different hash values.
    +        $expected = PHP_INT_SIZE == 4 ? -1938594527 : 2356372769;
    +
    +        $strategy = $this->getHashStrategy();
    +
    +        $this->assertSame($expected, $strategy->getKeyHash('{foo}'));
    +        $this->assertSame($expected, $strategy->getKeyHash('{foo}:bar'));
    +        $this->assertSame($expected, $strategy->getKeyHash('{foo}:baz'));
    +        $this->assertSame($expected, $strategy->getKeyHash('bar:{foo}:bar'));
    +
    +        $this->assertSame(0, $strategy->getKeyHash(''));
    +        $this->assertSame(0, $strategy->getKeyHash('{}'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSupportedCommands()
    +    {
    +        $strategy = $this->getHashStrategy();
    +
    +        $this->assertSame($this->getExpectedCommands(), $strategy->getSupportedCommands());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testReturnsNullOnUnsupportedCommand()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $command = ServerProfile::getDevelopment()->createCommand('ping');
    +
    +        $this->assertNull($strategy->getHash($command));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFirstKeyCommands()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +        $arguments = array('key');
    +
    +        foreach ($this->getExpectedCommands('keys-first') as $commandID) {
    +            $command = $profile->createCommand($commandID, $arguments);
    +            $this->assertNotNull($strategy->getHash($command), $commandID);
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAllKeysCommands()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +        $arguments = array('{key}:1', '{key}:2', '{key}:3', '{key}:4');
    +
    +        foreach ($this->getExpectedCommands('keys-all') as $commandID) {
    +            $command = $profile->createCommand($commandID, $arguments);
    +            $this->assertNotNull($strategy->getHash($command), $commandID);
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testInterleavedKeysCommands()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +        $arguments = array('{key}:1', 'value1', '{key}:2', 'value2');
    +
    +        foreach ($this->getExpectedCommands('keys-interleaved') as $commandID) {
    +            $command = $profile->createCommand($commandID, $arguments);
    +            $this->assertNotNull($strategy->getHash($command), $commandID);
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testKeysForBlockingListCommands()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +        $arguments = array('{key}:1', '{key}:2', 10);
    +
    +        foreach ($this->getExpectedCommands('keys-blockinglist') as $commandID) {
    +            $command = $profile->createCommand($commandID, $arguments);
    +            $this->assertNotNull($strategy->getHash($command), $commandID);
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testKeysForZsetAggregationCommands()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +        $arguments = array('{key}:destination', 2, '{key}:1', '{key}:1', array('aggregate' => 'SUM'));
    +
    +        foreach ($this->getExpectedCommands('keys-zaggregated') as $commandID) {
    +            $command = $profile->createCommand($commandID, $arguments);
    +            $this->assertNotNull($strategy->getHash($command), $commandID);
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testKeysForBitOpCommand()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +        $arguments = array('AND', '{key}:destination', '{key}:src:1', '{key}:src:2');
    +
    +        foreach ($this->getExpectedCommands('keys-bitop') as $commandID) {
    +            $command = $profile->createCommand($commandID, $arguments);
    +            $this->assertNotNull($strategy->getHash($command), $commandID);
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testKeysForScriptCommand()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +        $arguments = array('%SCRIPT%', 2, '{key}:1', '{key}:2', 'value1', 'value2');
    +
    +        foreach ($this->getExpectedCommands('keys-script') as $commandID) {
    +            $command = $profile->createCommand($commandID, $arguments);
    +            $this->assertNotNull($strategy->getHash($command), $commandID);
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testKeysForScriptedCommand()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $arguments = array('{key}:1', '{key}:2', 'value1', 'value2');
    +
    +        $command = $this->getMock('Predis\Command\ScriptedCommand', array('getScript', 'getKeysCount'));
    +        $command->expects($this->once())
    +                ->method('getScript')
    +                ->will($this->returnValue('return true'));
    +        $command->expects($this->exactly(2))
    +                ->method('getKeysCount')
    +                ->will($this->returnValue(2));
    +        $command->setArguments($arguments);
    +
    +        $this->assertNotNull($strategy->getHash($command), "Scripted Command [{$command->getId()}]");
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testUnsettingCommandHandler()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +
    +        $strategy->setCommandHandler('set');
    +        $strategy->setCommandHandler('get', null);
    +
    +        $command = $profile->createCommand('set', array('key', 'value'));
    +        $this->assertNull($strategy->getHash($command));
    +
    +        $command = $profile->createCommand('get', array('key'));
    +        $this->assertNull($strategy->getHash($command));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSettingCustomCommandHandler()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +
    +        $callable = $this->getMock('stdClass', array('__invoke'));
    +        $callable->expects($this->once())
    +                 ->method('__invoke')
    +                 ->with($this->isInstanceOf('Predis\Command\CommandInterface'))
    +                 ->will($this->returnValue('key'));
    +
    +        $strategy->setCommandHandler('get', $callable);
    +
    +        $command = $profile->createCommand('get', array('key'));
    +        $this->assertNotNull($strategy->getHash($command));
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Creates the default hash strategy object.
    +     *
    +     * @return CommandHashStrategyInterface
    +     */
    +    protected function getHashStrategy()
    +    {
    +        $distributor = new HashRing();
    +        $hashGenerator = $distributor->getHashGenerator();
    +        $strategy = new PredisClusterHashStrategy($hashGenerator);
    +
    +        return $strategy;
    +    }
    +
    +    /**
    +     * Returns the list of expected supported commands.
    +     *
    +     * @param string $type Optional type of command (based on its keys)
    +     * @return array
    +     */
    +    protected function getExpectedCommands($type = null)
    +    {
    +        $commands = array(
    +            /* commands operating on the key space */
    +            'EXISTS'                => 'keys-first',
    +            'DEL'                   => 'keys-all',
    +            'TYPE'                  => 'keys-first',
    +            'EXPIRE'                => 'keys-first',
    +            'EXPIREAT'              => 'keys-first',
    +            'PERSIST'               => 'keys-first',
    +            'PEXPIRE'               => 'keys-first',
    +            'PEXPIREAT'             => 'keys-first',
    +            'TTL'                   => 'keys-first',
    +            'PTTL'                  => 'keys-first',
    +            'SORT'                  => 'keys-first', // TODO
    +            'DUMP'                  => 'keys-first',
    +            'RESTORE'               => 'keys-first',
    +
    +            /* commands operating on string values */
    +            'APPEND'                => 'keys-first',
    +            'DECR'                  => 'keys-first',
    +            'DECRBY'                => 'keys-first',
    +            'GET'                   => 'keys-first',
    +            'GETBIT'                => 'keys-first',
    +            'MGET'                  => 'keys-all',
    +            'SET'                   => 'keys-first',
    +            'GETRANGE'              => 'keys-first',
    +            'GETSET'                => 'keys-first',
    +            'INCR'                  => 'keys-first',
    +            'INCRBY'                => 'keys-first',
    +            'SETBIT'                => 'keys-first',
    +            'SETEX'                 => 'keys-first',
    +            'MSET'                  => 'keys-interleaved',
    +            'MSETNX'                => 'keys-interleaved',
    +            'SETNX'                 => 'keys-first',
    +            'SETRANGE'              => 'keys-first',
    +            'STRLEN'                => 'keys-first',
    +            'SUBSTR'                => 'keys-first',
    +            'BITOP'                 => 'keys-bitop',
    +            'BITCOUNT'              => 'keys-first',
    +
    +            /* commands operating on lists */
    +            'LINSERT'               => 'keys-first',
    +            'LINDEX'                => 'keys-first',
    +            'LLEN'                  => 'keys-first',
    +            'LPOP'                  => 'keys-first',
    +            'RPOP'                  => 'keys-first',
    +            'RPOPLPUSH'             => 'keys-all',
    +            'BLPOP'                 => 'keys-blockinglist',
    +            'BRPOP'                 => 'keys-blockinglist',
    +            'BRPOPLPUSH'            => 'keys-blockinglist',
    +            'LPUSH'                 => 'keys-first',
    +            'LPUSHX'                => 'keys-first',
    +            'RPUSH'                 => 'keys-first',
    +            'RPUSHX'                => 'keys-first',
    +            'LRANGE'                => 'keys-first',
    +            'LREM'                  => 'keys-first',
    +            'LSET'                  => 'keys-first',
    +            'LTRIM'                 => 'keys-first',
    +
    +            /* commands operating on sets */
    +            'SADD'                  => 'keys-first',
    +            'SCARD'                 => 'keys-first',
    +            'SDIFF'                 => 'keys-all',
    +            'SDIFFSTORE'            => 'keys-all',
    +            'SINTER'                => 'keys-all',
    +            'SINTERSTORE'           => 'keys-all',
    +            'SUNION'                => 'keys-all',
    +            'SUNIONSTORE'           => 'keys-all',
    +            'SISMEMBER'             => 'keys-first',
    +            'SMEMBERS'              => 'keys-first',
    +            'SPOP'                  => 'keys-first',
    +            'SRANDMEMBER'           => 'keys-first',
    +            'SREM'                  => 'keys-first',
    +
    +            /* commands operating on sorted sets */
    +            'ZADD'                  => 'keys-first',
    +            'ZCARD'                 => 'keys-first',
    +            'ZCOUNT'                => 'keys-first',
    +            'ZINCRBY'               => 'keys-first',
    +            'ZINTERSTORE'           => 'keys-zaggregated',
    +            'ZRANGE'                => 'keys-first',
    +            'ZRANGEBYSCORE'         => 'keys-first',
    +            'ZRANK'                 => 'keys-first',
    +            'ZREM'                  => 'keys-first',
    +            'ZREMRANGEBYRANK'       => 'keys-first',
    +            'ZREMRANGEBYSCORE'      => 'keys-first',
    +            'ZREVRANGE'             => 'keys-first',
    +            'ZREVRANGEBYSCORE'      => 'keys-first',
    +            'ZREVRANK'              => 'keys-first',
    +            'ZSCORE'                => 'keys-first',
    +            'ZUNIONSTORE'           => 'keys-zaggregated',
    +
    +            /* commands operating on hashes */
    +            'HDEL'                  => 'keys-first',
    +            'HEXISTS'               => 'keys-first',
    +            'HGET'                  => 'keys-first',
    +            'HGETALL'               => 'keys-first',
    +            'HMGET'                 => 'keys-first',
    +            'HMSET'                 => 'keys-first',
    +            'HINCRBY'               => 'keys-first',
    +            'HINCRBYFLOAT'          => 'keys-first',
    +            'HKEYS'                 => 'keys-first',
    +            'HLEN'                  => 'keys-first',
    +            'HSET'                  => 'keys-first',
    +            'HSETNX'                => 'keys-first',
    +            'HVALS'                 => 'keys-first',
    +
    +            /* scripting */
    +            'EVAL'                  => 'keys-script',
    +            'EVALSHA'               => 'keys-script',
    +        );
    +
    +        if (isset($type)) {
    +            $commands = array_filter($commands, function ($expectedType) use ($type) {
    +                return $expectedType === $type;
    +            });
    +        }
    +
    +        return array_keys($commands);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Cluster/RedisClusterHashStrategyTest.php b/vendor/predis/predis/tests/Predis/Cluster/RedisClusterHashStrategyTest.php
    new file mode 100644
    index 0000000..cf45cd9
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Cluster/RedisClusterHashStrategyTest.php
    @@ -0,0 +1,365 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Cluster;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + *
    + */
    +class RedisClusterHashStrategyTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDoesNotSupportKeyTags()
    +    {
    +        $strategy = $this->getHashStrategy();
    +
    +        $this->assertSame(35910, $strategy->getKeyHash('{foo}'));
    +        $this->assertSame(60032, $strategy->getKeyHash('{foo}:bar'));
    +        $this->assertSame(27528, $strategy->getKeyHash('{foo}:baz'));
    +        $this->assertSame(34064, $strategy->getKeyHash('bar:{foo}:bar'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSupportedCommands()
    +    {
    +        $strategy = $this->getHashStrategy();
    +
    +        $this->assertSame($this->getExpectedCommands(), $strategy->getSupportedCommands());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testReturnsNullOnUnsupportedCommand()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $command = ServerProfile::getDevelopment()->createCommand('ping');
    +
    +        $this->assertNull($strategy->getHash($command));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFirstKeyCommands()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +        $arguments = array('key');
    +
    +        foreach ($this->getExpectedCommands('keys-first') as $commandID) {
    +            $command = $profile->createCommand($commandID, $arguments);
    +            $this->assertNotNull($strategy->getHash($command), $commandID);
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAllKeysCommandsWithOneKey()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +        $arguments = array('key');
    +
    +        foreach ($this->getExpectedCommands('keys-all') as $commandID) {
    +            $command = $profile->createCommand($commandID, $arguments);
    +            $this->assertNotNull($strategy->getHash($command), $commandID);
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAllKeysCommandsWithMoreKeys()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +        $arguments = array('key1', 'key2');
    +
    +        foreach ($this->getExpectedCommands('keys-all') as $commandID) {
    +            $command = $profile->createCommand($commandID, $arguments);
    +            $this->assertNull($strategy->getHash($command), $commandID);
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testInterleavedKeysCommandsWithOneKey()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +        $arguments = array('key:1', 'value1');
    +
    +        foreach ($this->getExpectedCommands('keys-interleaved') as $commandID) {
    +            $command = $profile->createCommand($commandID, $arguments);
    +            $this->assertNotNull($strategy->getHash($command), $commandID);
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testInterleavedKeysCommandsWithMoreKeys()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +        $arguments = array('key:1', 'value1', 'key:2', 'value2');
    +
    +        foreach ($this->getExpectedCommands('keys-interleaved') as $commandID) {
    +            $command = $profile->createCommand($commandID, $arguments);
    +            $this->assertNull($strategy->getHash($command), $commandID);
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testKeysForBlockingListCommandsWithOneKey()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +        $arguments = array('key:1', 10);
    +
    +        foreach ($this->getExpectedCommands('keys-blockinglist') as $commandID) {
    +            $command = $profile->createCommand($commandID, $arguments);
    +            $this->assertNotNull($strategy->getHash($command), $commandID);
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testKeysForBlockingListCommandsWithMoreKeys()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +        $arguments = array('key:1', 'key:2', 10);
    +
    +        foreach ($this->getExpectedCommands('keys-blockinglist') as $commandID) {
    +            $command = $profile->createCommand($commandID, $arguments);
    +            $this->assertNull($strategy->getHash($command), $commandID);
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testKeysForScriptCommand()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +        $arguments = array('%SCRIPT%', 1, 'key:1', 'value1');
    +
    +        foreach ($this->getExpectedCommands('keys-script') as $commandID) {
    +            $command = $profile->createCommand($commandID, $arguments);
    +            $this->assertNotNull($strategy->getHash($command), $commandID);
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testKeysForScriptedCommand()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $arguments = array('key:1', 'value1');
    +
    +        $command = $this->getMock('Predis\Command\ScriptedCommand', array('getScript', 'getKeysCount'));
    +        $command->expects($this->once())
    +                ->method('getScript')
    +                ->will($this->returnValue('return true'));
    +        $command->expects($this->exactly(2))
    +                ->method('getKeysCount')
    +                ->will($this->returnValue(1));
    +        $command->setArguments($arguments);
    +
    +        $this->assertNotNull($strategy->getHash($command), "Scripted Command [{$command->getId()}]");
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testUnsettingCommandHandler()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +
    +        $strategy->setCommandHandler('set');
    +        $strategy->setCommandHandler('get', null);
    +
    +        $command = $profile->createCommand('set', array('key', 'value'));
    +        $this->assertNull($strategy->getHash($command));
    +
    +        $command = $profile->createCommand('get', array('key'));
    +        $this->assertNull($strategy->getHash($command));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSettingCustomCommandHandler()
    +    {
    +        $strategy = $this->getHashStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +
    +        $callable = $this->getMock('stdClass', array('__invoke'));
    +        $callable->expects($this->once())
    +                 ->method('__invoke')
    +                 ->with($this->isInstanceOf('Predis\Command\CommandInterface'))
    +                 ->will($this->returnValue('key'));
    +
    +        $strategy->setCommandHandler('get', $callable);
    +
    +        $command = $profile->createCommand('get', array('key'));
    +        $this->assertNotNull($strategy->getHash($command));
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Creates the default hash strategy object.
    +     *
    +     * @return CommandHashStrategyInterface
    +     */
    +    protected function getHashStrategy()
    +    {
    +        $strategy = new RedisClusterHashStrategy();
    +
    +        return $strategy;
    +    }
    +
    +    /**
    +     * Returns the list of expected supported commands.
    +     *
    +     * @param string $type Optional type of command (based on its keys)
    +     * @return array
    +     */
    +    protected function getExpectedCommands($type = null)
    +    {
    +        $commands = array(
    +            /* commands operating on the key space */
    +            'EXISTS'                => 'keys-first',
    +            'DEL'                   => 'keys-all',
    +            'TYPE'                  => 'keys-first',
    +            'EXPIRE'                => 'keys-first',
    +            'EXPIREAT'              => 'keys-first',
    +            'PERSIST'               => 'keys-first',
    +            'PEXPIRE'               => 'keys-first',
    +            'PEXPIREAT'             => 'keys-first',
    +            'TTL'                   => 'keys-first',
    +            'PTTL'                  => 'keys-first',
    +            'SORT'                  => 'keys-first', // TODO
    +
    +            /* commands operating on string values */
    +            'APPEND'                => 'keys-first',
    +            'DECR'                  => 'keys-first',
    +            'DECRBY'                => 'keys-first',
    +            'GET'                   => 'keys-first',
    +            'GETBIT'                => 'keys-first',
    +            'MGET'                  => 'keys-all',
    +            'SET'                   => 'keys-first',
    +            'GETRANGE'              => 'keys-first',
    +            'GETSET'                => 'keys-first',
    +            'INCR'                  => 'keys-first',
    +            'INCRBY'                => 'keys-first',
    +            'SETBIT'                => 'keys-first',
    +            'SETEX'                 => 'keys-first',
    +            'MSET'                  => 'keys-interleaved',
    +            'MSETNX'                => 'keys-interleaved',
    +            'SETNX'                 => 'keys-first',
    +            'SETRANGE'              => 'keys-first',
    +            'STRLEN'                => 'keys-first',
    +            'SUBSTR'                => 'keys-first',
    +            'BITCOUNT'              => 'keys-first',
    +
    +            /* commands operating on lists */
    +            'LINSERT'               => 'keys-first',
    +            'LINDEX'                => 'keys-first',
    +            'LLEN'                  => 'keys-first',
    +            'LPOP'                  => 'keys-first',
    +            'RPOP'                  => 'keys-first',
    +            'BLPOP'                 => 'keys-blockinglist',
    +            'BRPOP'                 => 'keys-blockinglist',
    +            'LPUSH'                 => 'keys-first',
    +            'LPUSHX'                => 'keys-first',
    +            'RPUSH'                 => 'keys-first',
    +            'RPUSHX'                => 'keys-first',
    +            'LRANGE'                => 'keys-first',
    +            'LREM'                  => 'keys-first',
    +            'LSET'                  => 'keys-first',
    +            'LTRIM'                 => 'keys-first',
    +
    +            /* commands operating on sets */
    +            'SADD'                  => 'keys-first',
    +            'SCARD'                 => 'keys-first',
    +            'SISMEMBER'             => 'keys-first',
    +            'SMEMBERS'              => 'keys-first',
    +            'SPOP'                  => 'keys-first',
    +            'SRANDMEMBER'           => 'keys-first',
    +            'SREM'                  => 'keys-first',
    +
    +            /* commands operating on sorted sets */
    +            'ZADD'                  => 'keys-first',
    +            'ZCARD'                 => 'keys-first',
    +            'ZCOUNT'                => 'keys-first',
    +            'ZINCRBY'               => 'keys-first',
    +            'ZRANGE'                => 'keys-first',
    +            'ZRANGEBYSCORE'         => 'keys-first',
    +            'ZRANK'                 => 'keys-first',
    +            'ZREM'                  => 'keys-first',
    +            'ZREMRANGEBYRANK'       => 'keys-first',
    +            'ZREMRANGEBYSCORE'      => 'keys-first',
    +            'ZREVRANGE'             => 'keys-first',
    +            'ZREVRANGEBYSCORE'      => 'keys-first',
    +            'ZREVRANK'              => 'keys-first',
    +            'ZSCORE'                => 'keys-first',
    +
    +            /* commands operating on hashes */
    +            'HDEL'                  => 'keys-first',
    +            'HEXISTS'               => 'keys-first',
    +            'HGET'                  => 'keys-first',
    +            'HGETALL'               => 'keys-first',
    +            'HMGET'                 => 'keys-first',
    +            'HMSET'                 => 'keys-first',
    +            'HINCRBY'               => 'keys-first',
    +            'HINCRBYFLOAT'          => 'keys-first',
    +            'HKEYS'                 => 'keys-first',
    +            'HLEN'                  => 'keys-first',
    +            'HSET'                  => 'keys-first',
    +            'HSETNX'                => 'keys-first',
    +            'HVALS'                 => 'keys-first',
    +
    +            /* scripting */
    +            'EVAL'                  => 'keys-script',
    +            'EVALSHA'               => 'keys-script',
    +        );
    +
    +        if (isset($type)) {
    +            $commands = array_filter($commands, function ($expectedType) use ($type) {
    +                return $expectedType === $type;
    +            });
    +        }
    +
    +        return array_keys($commands);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/CommandTest.php b/vendor/predis/predis/tests/Predis/Command/CommandTest.php
    new file mode 100644
    index 0000000..7cb3f88
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/CommandTest.php
    @@ -0,0 +1,174 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class CommandTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testImplementsCorrectInterface()
    +    {
    +        $command = $this->getMockForAbstractClass('Predis\Command\AbstractCommand');
    +
    +        $this->assertInstanceOf('Predis\Command\CommandInterface', $command);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetEmptyArguments()
    +    {
    +        $command = $this->getMockForAbstractClass('Predis\Command\AbstractCommand');
    +
    +        $this->assertEmpty($command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSetRawArguments()
    +    {
    +        $arguments = array('1st', '2nd', '3rd');
    +
    +        $command = $this->getMockForAbstractClass('Predis\Command\AbstractCommand');
    +        $command->setRawArguments($arguments);
    +
    +        $this->assertEquals($arguments, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     *
    +     * @todo Since AbstractCommand::filterArguments is protected we cannot set an expectation
    +     *       for it when AbstractCommand::setArguments() is invoked. I wonder how we can do that.
    +     */
    +    public function testSetArguments()
    +    {
    +        $arguments = array('1st', '2nd', '3rd');
    +
    +        $command = $this->getMockForAbstractClass('Predis\Command\AbstractCommand');
    +        $command->setArguments($arguments);
    +
    +        $this->assertEquals($arguments, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetArgumentAtIndex()
    +    {
    +        $arguments = array('1st', '2nd', '3rd');
    +
    +        $command = $this->getMockForAbstractClass('Predis\Command\AbstractCommand');
    +        $command->setArguments($arguments);
    +
    +        $this->assertEquals($arguments[0], $command->getArgument(0));
    +        $this->assertEquals($arguments[2], $command->getArgument(2));
    +        $this->assertNull($command->getArgument(10));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $response = 'response-buffer';
    +        $command = $this->getMockForAbstractClass('Predis\Command\AbstractCommand');
    +
    +        $this->assertEquals($response, $command->parseResponse($response));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSetAndGetHash()
    +    {
    +        $hash = "key-hash";
    +
    +        $command = $this->getMockForAbstractClass('Predis\Command\AbstractCommand');
    +        $command->setRawArguments(array('key'));
    +
    +        $this->assertNull($command->getHash());
    +
    +        $command->setHash($hash);
    +        $this->assertSame($hash, $command->getHash());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testToString()
    +    {
    +        $expected = 'SET key value';
    +        $arguments = array('key', 'value');
    +
    +        $command = $this->getMockForAbstractClass('Predis\Command\AbstractCommand');
    +        $command->expects($this->once())->method('getId')->will($this->returnValue('SET'));
    +
    +        $command->setRawArguments($arguments);
    +
    +        $this->assertEquals($expected, (string) $command);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testToStringWithLongArguments()
    +    {
    +        $expected = 'SET key abcdefghijklmnopqrstuvwxyz012345[...]';
    +        $arguments = array('key', 'abcdefghijklmnopqrstuvwxyz0123456789');
    +
    +        $command = $this->getMockForAbstractClass('Predis\Command\AbstractCommand');
    +        $command->expects($this->once())->method('getId')->will($this->returnValue('SET'));
    +
    +        $command->setRawArguments($arguments);
    +
    +        $this->assertEquals($expected, (string) $command);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testNormalizeArguments()
    +    {
    +        $arguments = array('arg1', 'arg2', 'arg3', 'arg4');
    +
    +        $this->assertSame($arguments, AbstractCommand::normalizeArguments($arguments));
    +        $this->assertSame($arguments, AbstractCommand::normalizeArguments(array($arguments)));
    +
    +        $arguments = array(array(), array());
    +        $this->assertSame($arguments, AbstractCommand::normalizeArguments($arguments));
    +
    +        $arguments = array(new \stdClass());
    +        $this->assertSame($arguments, AbstractCommand::normalizeArguments($arguments));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testNormalizeVariadic()
    +    {
    +        $arguments = array('key', 'value1', 'value2', 'value3');
    +
    +        $this->assertSame($arguments, AbstractCommand::normalizeVariadic($arguments));
    +        $this->assertSame($arguments, AbstractCommand::normalizeVariadic(array('key', array('value1', 'value2', 'value3'))));
    +
    +        $arguments = array(new \stdClass());
    +        $this->assertSame($arguments, AbstractCommand::normalizeVariadic($arguments));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ConnectionAuthTest.php b/vendor/predis/predis/tests/Predis/Command/ConnectionAuthTest.php
    new file mode 100644
    index 0000000..03cd7a3
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ConnectionAuthTest.php
    @@ -0,0 +1,64 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-connection
    + */
    +class ConnectionAuthTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ConnectionAuth';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'AUTH';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('password');
    +        $expected = array('password');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = null;
    +        $expected = null;
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ConnectionEchoTest.php b/vendor/predis/predis/tests/Predis/Command/ConnectionEchoTest.php
    new file mode 100644
    index 0000000..ef7fa77
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ConnectionEchoTest.php
    @@ -0,0 +1,76 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-connection
    + */
    +class ConnectionEchoTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ConnectionEcho';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'ECHO';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('message');
    +        $expected = array('message');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = 'message';
    +        $expected = 'message';
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testAlwaysReturnsThePassedMessage()
    +    {
    +        $redis = $this->getClient();
    +
    +        $message = 'Can you hear me?';
    +
    +        $this->assertSame($message, $redis->echo($message));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ConnectionPingTest.php b/vendor/predis/predis/tests/Predis/Command/ConnectionPingTest.php
    new file mode 100644
    index 0000000..0b60e7e
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ConnectionPingTest.php
    @@ -0,0 +1,71 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-connection
    + */
    +class ConnectionPingTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ConnectionPing';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'PING';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array();
    +        $expected = array();
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertTrue($command->parseResponse('PONG'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testAlwaysReturnsTrue()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertTrue($redis->ping());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ConnectionQuitTest.php b/vendor/predis/predis/tests/Predis/Command/ConnectionQuitTest.php
    new file mode 100644
    index 0000000..70ac484
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ConnectionQuitTest.php
    @@ -0,0 +1,72 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-connection
    + */
    +class ConnectionQuitTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ConnectionQuit';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'QUIT';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array();
    +        $expected = array();
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertTrue($command->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsTrueWhenClosingConnection()
    +    {
    +        $redis = $this->getClient();
    +        $command = $this->getCommand();
    +
    +        $this->assertTrue($redis->executeCommand($command));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ConnectionSelectTest.php b/vendor/predis/predis/tests/Predis/Command/ConnectionSelectTest.php
    new file mode 100644
    index 0000000..2e9854e
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ConnectionSelectTest.php
    @@ -0,0 +1,86 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-connection
    + */
    +class ConnectionSelectTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ConnectionSelect';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SELECT';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array(10);
    +        $expected = array(10);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertTrue($command->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanSelectDifferentDatabase()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertTrue($redis->select(REDIS_SERVER_DBNUM - 1));
    +        $this->assertFalse($redis->exists('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR invalid DB index
    +     */
    +    public function testThrowsExceptionOnUnexpectedDatabase()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->select(100000000);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/HashDeleteTest.php b/vendor/predis/predis/tests/Predis/Command/HashDeleteTest.php
    new file mode 100644
    index 0000000..adb7c2b
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/HashDeleteTest.php
    @@ -0,0 +1,125 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-hash
    + */
    +class HashDeleteTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\HashDelete';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'HDEL';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'field1', 'field2', 'field3');
    +        $expected = array('key', 'field1', 'field2', 'field3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsFieldsAsSingleArray()
    +    {
    +        $arguments = array('key', array('field1', 'field2', 'field3'));
    +        $expected = array('key', 'field1', 'field2', 'field3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'field1', 'field2', 'field3');
    +        $expected = array('prefix:key', 'field1', 'field2', 'field3');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testDeletesSpecifiedFieldsFromHash()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->hmset('metavars', 'foo', 'bar', 'hoge', 'piyo', 'lol', 'wut');
    +
    +        $this->assertSame(2, $redis->hdel('metavars', 'foo', 'hoge'));
    +        $this->assertSame(0, $redis->hdel('metavars', 'foofoo'));
    +        $this->assertSame(0, $redis->hdel('unknown', 'foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->hdel('foo', 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/HashExistsTest.php b/vendor/predis/predis/tests/Predis/Command/HashExistsTest.php
    new file mode 100644
    index 0000000..7b27b72
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/HashExistsTest.php
    @@ -0,0 +1,114 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-hash
    + */
    +class HashExistsTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\HashExists';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'HEXISTS';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'field');
    +        $expected = array('key', 'field');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertFalse($command->parseResponse(0));
    +        $this->assertTrue($command->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'field');
    +        $expected = array('prefix:key', 'field');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsExistenceOfSpecifiedField()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->hmset('metavars', 'foo', 'bar', 'hoge', 'piyo');
    +
    +        $this->assertTrue($redis->hexists('metavars', 'foo'));
    +        $this->assertFalse($redis->hexists('metavars', 'lol'));
    +        $this->assertFalse($redis->hexists('unknown', 'foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->hexists('foo', 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/HashGetAllTest.php b/vendor/predis/predis/tests/Predis/Command/HashGetAllTest.php
    new file mode 100644
    index 0000000..36c5c79
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/HashGetAllTest.php
    @@ -0,0 +1,115 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-hash
    + */
    +class HashGetAllTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\HashGetAll';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'HGETALL';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('foo', 'bar', 'hoge', 'piyo', 'lol', 'wut');
    +        $expected = array('foo' => 'bar', 'hoge' => 'piyo', 'lol' => 'wut');
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsAllTheFieldsAndTheirValues()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->hmset('metavars', 'foo', 'bar', 'hoge', 'piyo', 'lol', 'wut');
    +
    +        $this->assertSame(array('foo' => 'bar', 'hoge' => 'piyo', 'lol' => 'wut'), $redis->hgetall('metavars'));
    +        $this->assertSame(array(), $redis->hgetall('unknown'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->hgetall('foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/HashGetMultipleTest.php b/vendor/predis/predis/tests/Predis/Command/HashGetMultipleTest.php
    new file mode 100644
    index 0000000..4cabd2b
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/HashGetMultipleTest.php
    @@ -0,0 +1,131 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-hash
    + */
    +class HashGetMultipleTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\HashGetMultiple';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'HMGET';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'field1', 'field2', 'field3');
    +        $expected = array('key', 'field1', 'field2', 'field3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsFieldsAsSingleArray()
    +    {
    +        $arguments = array('key', array('field1', 'field2', 'field3'));
    +        $expected = array('key', 'field1', 'field2', 'field3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('bar', 'piyo', 'wut');
    +        $expected = array('bar', 'piyo', 'wut');
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'field1', 'field2', 'field3');
    +        $expected = array('prefix:key', 'field1', 'field2', 'field3');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsValuesOfSpecifiedFields()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->hmset('metavars', 'foo', 'bar', 'hoge', 'piyo', 'lol', 'wut');
    +
    +        $this->assertSame(array('bar', 'piyo', null), $redis->hmget('metavars', 'foo', 'hoge', 'unknown'));
    +        $this->assertSame(array('bar', 'bar'), $redis->hmget('metavars', 'foo', 'foo'));
    +        $this->assertSame(array(null, null), $redis->hmget('metavars', 'unknown', 'unknown'));
    +        $this->assertSame(array(null, null), $redis->hmget('unknown', 'foo', 'hoge'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->hmget('foo', 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/HashGetTest.php b/vendor/predis/predis/tests/Predis/Command/HashGetTest.php
    new file mode 100644
    index 0000000..b68067b
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/HashGetTest.php
    @@ -0,0 +1,111 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-hash
    + */
    +class HashGetTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\HashGet';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'HGET';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'field');
    +        $expected = array('key', 'field');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame('value', $this->getCommand()->parseResponse('value'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'field');
    +        $expected = array('prefix:key', 'field');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsValueOfSpecifiedField()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->hmset('metavars', 'foo', 'bar', 'hoge', 'piyo');
    +
    +        $this->assertSame('bar', $redis->hget('metavars', 'foo'));
    +        $this->assertNull($redis->hget('metavars', 'lol'));
    +        $this->assertNull($redis->hget('unknown', 'foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->hget('foo', 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/HashIncrementByFloatTest.php b/vendor/predis/predis/tests/Predis/Command/HashIncrementByFloatTest.php
    new file mode 100644
    index 0000000..286ebc5
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/HashIncrementByFloatTest.php
    @@ -0,0 +1,136 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-hash
    + */
    +class HashIncrementByFloatTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\HashIncrementByFloat';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'HINCRBYFLOAT';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'field', 10.5);
    +        $expected = array('key', 'field', 10.5);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(10.5, $this->getCommand()->parseResponse(10.5));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'field', 10.5);
    +        $expected = array('prefix:key', 'field', 10.5);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testIncrementsValueOfFieldByFloat()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame('10.5', $redis->hincrbyfloat('metavars', 'foo', 10.5));
    +        $this->assertSame('10.001', $redis->hincrbyfloat('metavars', 'hoge', 10.001));
    +        $this->assertSame('11', $redis->hincrbyfloat('metavars', 'hoge', 0.999));
    +        $this->assertSame(array('foo' => '10.5', 'hoge' => '11'), $redis->hgetall('metavars'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testDecrementsValueOfFieldByFloat()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame('-10.5', $redis->hincrbyfloat('metavars', 'foo', -10.5));
    +        $this->assertSame('-10.001', $redis->hincrbyfloat('metavars', 'hoge', -10.001));
    +        $this->assertSame('-11', $redis->hincrbyfloat('metavars', 'hoge', -0.999));
    +        $this->assertSame(array('foo' => '-10.5', 'hoge' => '-11'), $redis->hgetall('metavars'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR hash value is not a valid float
    +     */
    +    public function testThrowsExceptionOnStringField()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->hset('metavars', 'foo', 'bar');
    +        $redis->hincrbyfloat('metavars', 'foo', 10.0);
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->hincrbyfloat('foo', 'bar', 10.5);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/HashIncrementByTest.php b/vendor/predis/predis/tests/Predis/Command/HashIncrementByTest.php
    new file mode 100644
    index 0000000..2cf2bbf
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/HashIncrementByTest.php
    @@ -0,0 +1,136 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-hash
    + */
    +class HashIncrementByTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\HashIncrementBy';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'HINCRBY';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'field', 10);
    +        $expected = array('key', 'field', 10);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(10, $this->getCommand()->parseResponse(10));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'field', 10);
    +        $expected = array('prefix:key', 'field', 10);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testIncrementsValueOfFieldByInteger()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(10, $redis->hincrby('metavars', 'foo', 10));
    +        $this->assertSame(5, $redis->hincrby('metavars', 'hoge', 5));
    +        $this->assertSame(15, $redis->hincrby('metavars', 'hoge', 10));
    +        $this->assertSame(array('foo' => '10', 'hoge' => '15'), $redis->hgetall('metavars'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testDecrementsValueOfFieldByInteger()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(-10, $redis->hincrby('metavars', 'foo', -10));
    +        $this->assertSame(-5, $redis->hincrby('metavars', 'hoge', -5));
    +        $this->assertSame(-15, $redis->hincrby('metavars', 'hoge', -10));
    +        $this->assertSame(array('foo' => '-10', 'hoge' => '-15'), $redis->hgetall('metavars'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR hash value is not an integer
    +     */
    +    public function testThrowsExceptionOnStringField()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->hset('metavars', 'foo', 'bar');
    +        $redis->hincrby('metavars', 'foo', 10);
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->hincrby('foo', 'bar', 10);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/HashKeysTest.php b/vendor/predis/predis/tests/Predis/Command/HashKeysTest.php
    new file mode 100644
    index 0000000..04c0a2d
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/HashKeysTest.php
    @@ -0,0 +1,115 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-hash
    + */
    +class HashKeysTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\HashKeys';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'HKEYS';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('foo', 'hoge', 'lol');
    +        $expected = array('foo', 'hoge', 'lol');
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsKeysOfHash()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->hmset('metavars', 'foo', 'bar', 'hoge', 'piyo', 'lol', 'wut');
    +
    +        $this->assertSame(array('foo', 'hoge', 'lol'), $redis->hkeys('metavars'));
    +        $this->assertSame(array(), $redis->hkeys('unknown'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->hkeys('foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/HashLengthTest.php b/vendor/predis/predis/tests/Predis/Command/HashLengthTest.php
    new file mode 100644
    index 0000000..9c950b6
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/HashLengthTest.php
    @@ -0,0 +1,110 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-hash
    + */
    +class HashLengthTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\HashLength';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'HLEN';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsLengthOfHash()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->hmset('metavars', 'foo', 'bar', 'hoge', 'piyo', 'lol', 'wut');
    +
    +        $this->assertSame(3, $redis->hlen('metavars'));
    +        $this->assertSame(0, $redis->hlen('unknown'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->hlen('foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/HashSetMultipleTest.php b/vendor/predis/predis/tests/Predis/Command/HashSetMultipleTest.php
    new file mode 100644
    index 0000000..5f9a1ef
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/HashSetMultipleTest.php
    @@ -0,0 +1,137 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-hash
    + */
    +class HashSetMultipleTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\HashSetMultiple';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'HMSET';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'field1', 'value1', 'field2', 'value2');
    +        $expected = array('key', 'field1', 'value1', 'field2', 'value2');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsFieldsValuesAsSingleArray()
    +    {
    +        $arguments = array('key', array('field1' => 'value1', 'field2' => 'value2'));
    +        $expected = array('key', 'field1', 'value1', 'field2', 'value2');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'field1', 'value1', 'field2', 'value2');
    +        $expected = array('prefix:key', 'field1', 'value1', 'field2', 'value2');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testSetsSpecifiedFieldsOfHash()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertTrue($redis->hmset('metavars', 'foo', 'bar', 'hoge', 'piyo'));
    +        $this->assertSame(array('foo' => 'bar', 'hoge' => 'piyo'), $redis->hgetall('metavars'));
    +
    +        $this->assertTrue($redis->hmset('metavars', 'foo', 'barbar', 'lol', 'wut'));
    +        $this->assertSame(array('foo' => 'barbar', 'hoge' => 'piyo', 'lol' => 'wut'), $redis->hgetall('metavars'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testSetsTheSpecifiedFie()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->hmset('metavars', 'foo', 'bar', 'hoge', 'piyo', 'lol', 'wut');
    +
    +        $this->assertSame(array('foo' => 'bar', 'hoge' => 'piyo', 'lol' => 'wut'), $redis->hgetall('metavars'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('metavars', 'bar');
    +        $redis->hmset('metavars', 'foo', 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/HashSetPreserveTest.php b/vendor/predis/predis/tests/Predis/Command/HashSetPreserveTest.php
    new file mode 100644
    index 0000000..ca1b35d
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/HashSetPreserveTest.php
    @@ -0,0 +1,114 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-hash
    + */
    +class HashSetPreserveTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\HashSetPreserve';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'HSETNX';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'field', 'value');
    +        $expected = array('key', 'field', 'value');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertTrue($command->parseResponse(1));
    +        $this->assertFalse($command->parseResponse(0));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'field', 'value');
    +        $expected = array('prefix:key', 'field', 'value');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testSetsNewFieldsAndPreserversExistingOnes()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertTrue($redis->hsetnx('metavars', 'foo', 'bar'));
    +        $this->assertTrue($redis->hsetnx('metavars', 'hoge', 'piyo'));
    +        $this->assertFalse($redis->hsetnx('metavars', 'foo', 'barbar'));
    +
    +        $this->assertSame(array('bar', 'piyo'), $redis->hmget('metavars', 'foo', 'hoge'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('metavars', 'foo');
    +        $redis->hsetnx('metavars', 'foo', 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/HashSetTest.php b/vendor/predis/predis/tests/Predis/Command/HashSetTest.php
    new file mode 100644
    index 0000000..fee08e4
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/HashSetTest.php
    @@ -0,0 +1,113 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-hash
    + */
    +class HashSetTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\HashSet';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'HSET';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'field', 'value');
    +        $expected = array('key', 'field', 'value');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertTrue($command->parseResponse(1));
    +        $this->assertFalse($command->parseResponse(0));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'field', 'value');
    +        $expected = array('prefix:key', 'field', 'value');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testSetsValueOfSpecifiedField()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertTrue($redis->hset('metavars', 'foo', 'bar'));
    +        $this->assertTrue($redis->hset('metavars', 'hoge', 'piyo'));
    +
    +        $this->assertSame(array('bar', 'piyo'), $redis->hmget('metavars', 'foo', 'hoge'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('metavars', 'foo');
    +        $redis->hset('metavars', 'foo', 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/HashValuesTest.php b/vendor/predis/predis/tests/Predis/Command/HashValuesTest.php
    new file mode 100644
    index 0000000..6cedd8e
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/HashValuesTest.php
    @@ -0,0 +1,115 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-hash
    + */
    +class HashValuesTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\HashValues';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'HVALS';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('foo', 'hoge', 'lol');
    +        $expected = array('foo', 'hoge', 'lol');
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsValuesOfHash()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->hmset('metavars', 'foo', 'bar', 'hoge', 'piyo', 'lol', 'wut');
    +
    +        $this->assertSame(array('bar', 'piyo', 'wut'), $redis->hvals('metavars'));
    +        $this->assertSame(array(), $redis->hvals('unknown'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->hvals('foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeyDeleteTest.php b/vendor/predis/predis/tests/Predis/Command/KeyDeleteTest.php
    new file mode 100644
    index 0000000..f6e9154
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeyDeleteTest.php
    @@ -0,0 +1,119 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-key
    + */
    +class KeyDeleteTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeyDelete';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'DEL';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key1', 'key2', 'key3');
    +        $expected = array('key1', 'key2', 'key3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsAsSingleArray()
    +    {
    +        $arguments = array(array('key1', 'key2', 'key3'));
    +        $expected = array('key1', 'key2', 'key3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertSame(10, $command->parseResponse(10));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key1', 'key2', 'key3');
    +        $expected = array('prefix:key1', 'prefix:key2', 'prefix:key3');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsNumberOfDeletedKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(0, $redis->del('foo'));
    +
    +        $redis->set('foo', 'bar');
    +        $this->assertSame(1, $redis->del('foo'));
    +
    +        $redis->set('foo', 'bar');
    +        $this->assertSame(1, $redis->del('foo', 'hoge'));
    +
    +        $redis->set('foo', 'bar');
    +        $redis->set('hoge', 'piyo');
    +        $this->assertSame(2, $redis->del('foo', 'hoge'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeyDumpTest.php b/vendor/predis/predis/tests/Predis/Command/KeyDumpTest.php
    new file mode 100644
    index 0000000..621ffff
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeyDumpTest.php
    @@ -0,0 +1,89 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-key
    + */
    +class KeyDumpTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeyDump';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'DUMP';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = "\x00\xC0\n\x06\x00\xF8r?\xC5\xFB\xFB_(";
    +        $expected = "\x00\xC0\n\x06\x00\xF8r?\xC5\xFB\xFB_(";
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeyExistsTest.php b/vendor/predis/predis/tests/Predis/Command/KeyExistsTest.php
    new file mode 100644
    index 0000000..581dc25
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeyExistsTest.php
    @@ -0,0 +1,108 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-key
    + */
    +class KeyExistsTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeyExists';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'EXISTS';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertTrue($command->parseResponse(1));
    +        $this->assertFalse($command->parseResponse(0));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsTrueIfKeyExists()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $this->assertTrue($redis->exists('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsFalseIfKeyDoesNotExist()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertFalse($redis->exists('foo'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeyExpireAtTest.php b/vendor/predis/predis/tests/Predis/Command/KeyExpireAtTest.php
    new file mode 100644
    index 0000000..dbe6983
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeyExpireAtTest.php
    @@ -0,0 +1,131 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-key
    + */
    +class KeyExpireAtTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeyExpireAt';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'EXPIREAT';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'ttl');
    +        $expected = array('key', 'ttl');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertTrue($command->parseResponse(1));
    +        $this->assertFalse($command->parseResponse(0));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'value');
    +        $expected = array('prefix:key', 'value');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsFalseOnNonExistingKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertFalse($redis->expireat('foo', 2));
    +    }
    +
    +    /**
    +     * @medium
    +     * @group connected
    +     * @group slow
    +     */
    +    public function testCanExpireKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $now = time();
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertTrue($redis->expireat('foo', $now + 1));
    +        $this->assertLessThanOrEqual(1, $redis->ttl('foo'));
    +
    +        $this->sleep(2);
    +
    +        $this->assertFalse($redis->exists('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testDeletesKeysOnPastUnixTime()
    +    {
    +        $redis = $this->getClient();
    +
    +        $now = time();
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertTrue($redis->expireat('foo', $now - 100));
    +        $this->assertFalse($redis->exists('foo'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeyExpireTest.php b/vendor/predis/predis/tests/Predis/Command/KeyExpireTest.php
    new file mode 100644
    index 0000000..7af4353
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeyExpireTest.php
    @@ -0,0 +1,145 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-key
    + */
    +class KeyExpireTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeyExpire';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'EXPIRE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'ttl');
    +        $expected = array('key', 'ttl');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertTrue($command->parseResponse(1));
    +        $this->assertFalse($command->parseResponse(0));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'value');
    +        $expected = array('prefix:key', 'value');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsFalseOnNonExistingKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertFalse($redis->expire('foo', 2));
    +    }
    +
    +    /**
    +     * @medium
    +     * @group connected
    +     * @group slow
    +     */
    +    public function testCanExpireKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertTrue($redis->expire('foo', 1));
    +        $this->assertSame(1, $redis->ttl('foo'));
    +
    +        $this->sleep(2.0);
    +
    +        $this->assertFalse($redis->exists('foo'));
    +    }
    +
    +    /**
    +     * @medium
    +     * @group connected
    +     * @group slow
    +     */
    +    public function testConsistencyWithTTL()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertTrue($redis->expire('foo', 10));
    +        $this->sleep(1.5);
    +        $this->assertLessThan(10, $redis->ttl('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testDeletesKeysOnNegativeTTL()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertTrue($redis->expire('foo', -10));
    +        $this->assertFalse($redis->exists('foo'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeyKeysTest.php b/vendor/predis/predis/tests/Predis/Command/KeyKeysTest.php
    new file mode 100644
    index 0000000..0bed0fe
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeyKeysTest.php
    @@ -0,0 +1,105 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-key
    + */
    +class KeyKeysTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeyKeys';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'KEYS';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('pattern:*');
    +        $expected = array('pattern:*');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('key1', 'key2', 'key3');
    +        $parsed = array('key1', 'key2', 'key3');
    +
    +        $this->assertSame($parsed, $this->getCommand()->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('pattern');
    +        $expected = array('prefix:pattern');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsArrayOfMatchingKeys()
    +    {
    +        $keys = array('aaa' => 1, 'aba' => 2, 'aca' => 3);
    +        $keysNS = array('metavar:foo' => 'bar', 'metavar:hoge' => 'piyo');
    +        $keysAll = array_merge($keys, $keysNS);
    +
    +        $redis = $this->getClient();
    +        $redis->mset($keysAll);
    +
    +        $this->assertSame(array(), $redis->keys('nomatch:*'));
    +        $this->assertSameValues(array_keys($keysNS), $redis->keys('metavar:*'));
    +        $this->assertSameValues(array_keys($keysAll), $redis->keys('*'));
    +        $this->assertSameValues(array_keys($keys), $redis->keys('a?a'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeyKeysV12xTest.php b/vendor/predis/predis/tests/Predis/Command/KeyKeysV12xTest.php
    new file mode 100644
    index 0000000..10ebb18
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeyKeysV12xTest.php
    @@ -0,0 +1,91 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * We only perform disconnected tests for this commands because
    + * it is too old (Redis v1.2) and expects a different response
    + * format.
    + *
    + * @group commands
    + * @group realm-key
    + */
    +class KeyKeysV12xTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeyKeysV12x';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'KEYS';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('pattern:*');
    +        $expected = array('pattern:*');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = 'key1 key2 key3';
    +        $parsed = array('key1', 'key2', 'key3');
    +
    +        $this->assertSame($parsed, $this->getCommand()->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('pattern');
    +        $expected = array('prefix:pattern');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeyMoveTest.php b/vendor/predis/predis/tests/Predis/Command/KeyMoveTest.php
    new file mode 100644
    index 0000000..d183932
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeyMoveTest.php
    @@ -0,0 +1,121 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-key
    + */
    +class KeyMoveTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeyMove';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'MOVE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 10);
    +        $expected = array('key', 10);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertTrue($command->parseResponse(1));
    +        $this->assertFalse($command->parseResponse(0));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'db');
    +        $expected = array('prefix:key', 'db');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     * @todo This test fails if REDIS_SERVER_DBNUM is 0.
    +     */
    +    public function testMovesKeysToDifferentDatabases()
    +    {
    +        $db = REDIS_SERVER_DBNUM - 1;
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertTrue($redis->move('foo', $db));
    +        $this->assertFalse($redis->exists('foo'));
    +
    +        $redis->select($db);
    +        $this->assertTrue($redis->exists('foo'));
    +
    +        $redis->del('foo');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR index out of range
    +     */
    +    public function testThrowsExceptionOnInvalidDatabases()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +
    +        $redis->move('foo', 100000000);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeyPersistTest.php b/vendor/predis/predis/tests/Predis/Command/KeyPersistTest.php
    new file mode 100644
    index 0000000..090a898
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeyPersistTest.php
    @@ -0,0 +1,123 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-key
    + */
    +class KeyPersistTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeyPersist';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'PERSIST';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertTrue($command->parseResponse(1));
    +        $this->assertFalse($command->parseResponse(0));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRemovesExpireFromKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->expire('foo', 10);
    +
    +        $this->assertTrue($redis->persist('foo'));
    +        $this->assertSame(-1, $redis->ttl('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsFalseOnNonExpiringKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertFalse($redis->persist('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsFalseOnNonExistentKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertFalse($redis->persist('foo'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeyPreciseExpireAtTest.php b/vendor/predis/predis/tests/Predis/Command/KeyPreciseExpireAtTest.php
    new file mode 100644
    index 0000000..356ccaf
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeyPreciseExpireAtTest.php
    @@ -0,0 +1,121 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-key
    + */
    +class KeyPreciseExpireAtTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeyPreciseExpireAt';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'PEXPIREAT';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 100);
    +        $expected = array('key', 100);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertTrue($command->parseResponse(1));
    +        $this->assertFalse($command->parseResponse(0));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'value');
    +        $expected = array('prefix:key', 'value');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @medium
    +     * @group connected
    +     * @group slow
    +     */
    +    public function testCanExpireKeys()
    +    {
    +        $ttl = 1.5;
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertTrue($redis->pexpireat('foo', time() + $ttl * 1000));
    +        $this->assertLessThan($ttl * 1000, $redis->pttl('foo'));
    +
    +        $this->sleep($ttl + 0.5);
    +
    +        $this->assertFalse($redis->exists('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testDeletesKeysOnPastUnixTime()
    +    {
    +        $redis = $this->getClient();
    +
    +        $now = time();
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertTrue($redis->expireat('foo', time() - 100000));
    +        $this->assertFalse($redis->exists('foo'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeyPreciseExpireTest.php b/vendor/predis/predis/tests/Predis/Command/KeyPreciseExpireTest.php
    new file mode 100644
    index 0000000..44dcb36
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeyPreciseExpireTest.php
    @@ -0,0 +1,146 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-key
    + */
    +class KeyPreciseExpireTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeyPreciseExpire';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'PEXPIRE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 100);
    +        $expected = array('key', 100);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertTrue($command->parseResponse(1));
    +        $this->assertFalse($command->parseResponse(0));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'value');
    +        $expected = array('prefix:key', 'value');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsFalseOnNonExistingKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertFalse($redis->pexpire('foo', 20000));
    +    }
    +
    +    /**
    +     * @medium
    +     * @group connected
    +     * @group slow
    +     */
    +    public function testCanExpireKeys()
    +    {
    +        $ttl = 1 * 1000;
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertTrue($redis->pexpire('foo', $ttl));
    +
    +        $this->sleep(1.2);
    +        $this->assertFalse($redis->exists('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @group slow
    +     */
    +    public function testConsistencyWithTTL()
    +    {
    +        $ttl = 1 * 1000;
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertTrue($redis->pexpire('foo', $ttl));
    +
    +        $this->sleep(0.5);
    +        $this->assertLessThanOrEqual($ttl, $redis->pttl('foo'));
    +        $this->assertGreaterThan($ttl - 800, $redis->pttl('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testDeletesKeysOnNegativeTTL()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertTrue($redis->pexpire('foo', -10000));
    +        $this->assertFalse($redis->exists('foo'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeyPreciseTimeToLiveTest.php b/vendor/predis/predis/tests/Predis/Command/KeyPreciseTimeToLiveTest.php
    new file mode 100644
    index 0000000..4b9266d
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeyPreciseTimeToLiveTest.php
    @@ -0,0 +1,122 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-key
    + */
    +class KeyPreciseTimeToLiveTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeyPreciseTimeToLive';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'PTTL';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 10);
    +        $expected = array('key', 10);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertSame(100, $command->parseResponse(100));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 10);
    +        $expected = array('prefix:key', 10);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsTTL()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->expire('foo', 10);
    +
    +        $this->assertLessThanOrEqual(10000, $redis->pttl('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsLessThanZeroOnNonExpiringKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $this->assertSame(-1, $redis->pttl('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @todo PTTL changed in Redis >= 2.8 to return -2 on non existing keys, we
    +     *       should handle this case with a better solution than the current one.
    +     */
    +    public function testReturnsLessThanZeroOnNonExistingKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertLessThanOrEqual(-1, $redis->pttl('foo'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeyRandomTest.php b/vendor/predis/predis/tests/Predis/Command/KeyRandomTest.php
    new file mode 100644
    index 0000000..ac29b2a
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeyRandomTest.php
    @@ -0,0 +1,87 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-key
    + */
    +class KeyRandomTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeyRandom';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'RANDOMKEY';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array();
    +        $expected = array();
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = 'key';
    +        $expected = 'key';
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsFalseOnNonExpiringKeys()
    +    {
    +        $keys = array('key:1' => 1, 'key:2' => 2, 'key:3' => 3);
    +
    +        $redis = $this->getClient();
    +        $redis->mset($keys);
    +
    +        $this->assertContains($redis->randomkey(), array_keys($keys));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsNullOnEmptyDatabase()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertNull($redis->randomkey());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeyRenamePreserveTest.php b/vendor/predis/predis/tests/Predis/Command/KeyRenamePreserveTest.php
    new file mode 100644
    index 0000000..d467ff7
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeyRenamePreserveTest.php
    @@ -0,0 +1,111 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-key
    + */
    +class KeyRenamePreserveTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeyRenamePreserve';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'RENAMENX';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'newkey');
    +        $expected = array('key', 'newkey');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(1));
    +        $this->assertFalse($this->getCommand()->parseResponse(0));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'newkey');
    +        $expected = array('prefix:key', 'prefix:newkey');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRenamesKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertTrue($redis->renamenx('foo', 'foofoo'));
    +        $this->assertFalse($redis->exists('foo'));
    +        $this->assertTrue($redis->exists('foofoo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR no such key
    +     */
    +    public function testReturnsFalseOnNonExistingKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertFalse($redis->renamenx('foo', 'foobar'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeyRenameTest.php b/vendor/predis/predis/tests/Predis/Command/KeyRenameTest.php
    new file mode 100644
    index 0000000..e899d3b
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeyRenameTest.php
    @@ -0,0 +1,110 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-key
    + */
    +class KeyRenameTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeyRename';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'RENAME';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'newkey');
    +        $expected = array('key', 'newkey');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'newkey');
    +        $expected = array('prefix:key', 'prefix:newkey');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRenamesKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertTrue($redis->rename('foo', 'foofoo'));
    +        $this->assertFalse($redis->exists('foo'));
    +        $this->assertTrue($redis->exists('foofoo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR no such key
    +     */
    +    public function testThrowsExceptionOnNonExistingKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rename('foo', 'foobar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeyRestoreTest.php b/vendor/predis/predis/tests/Predis/Command/KeyRestoreTest.php
    new file mode 100644
    index 0000000..4bd1582
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeyRestoreTest.php
    @@ -0,0 +1,84 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-key
    + */
    +class KeyRestoreTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeyRestore';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'RESTORE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 0, "\x00\xC0\n\x06\x00\xF8r?\xC5\xFB\xFB_(");
    +        $expected = array('key', 0, "\x00\xC0\n\x06\x00\xF8r?\xC5\xFB\xFB_(");
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 0, "\x00\xC0\n\x06\x00\xF8r?\xC5\xFB\xFB_(");
    +        $expected = array('prefix:key', 0, "\x00\xC0\n\x06\x00\xF8r?\xC5\xFB\xFB_(");
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeySortTest.php b/vendor/predis/predis/tests/Predis/Command/KeySortTest.php
    new file mode 100644
    index 0000000..8f6ac6f
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeySortTest.php
    @@ -0,0 +1,281 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-key
    + */
    +class KeySortTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeySort';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SORT';
    +    }
    +
    +    /**
    +     * Utility method to to an LPUSH of some unordered values on a key.
    +     *
    +     * @param Predis\Client $redis Redis client instance.
    +     * @param string $key Target key
    +     * @return array
    +     */
    +    protected function lpushUnorderedList(Predis\Client $redis, $key)
    +    {
    +        $list = array(2, 100, 3, 1, 30, 10);
    +        $redis->lpush($key, $list);
    +
    +        return $list;
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $modifiers = array(
    +            'by' => 'by_key_*',
    +            'limit' => array(1, 4),
    +            'get' => array('object_*', '#'),
    +            'sort'  => 'asc',
    +            'alpha' => true,
    +            'store' => 'destination_key',
    +        );
    +        $arguments = array('key', $modifiers);
    +
    +        $expected = array(
    +            'key', 'BY', 'by_key_*', 'GET', 'object_*', 'GET', '#',
    +            'LIMIT', 1, 4, 'ASC', 'ALPHA', 'STORE', 'destination_key'
    +        );
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertEquals($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetModifierCanBeString()
    +    {
    +        $arguments = array('key', array('get' => '#'));
    +        $expected = array('key', 'GET', '#');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('value1', 'value2', 'value3');
    +        $expected = array('value1', 'value2', 'value3');
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $modifiers = array(
    +            'by' => 'by_key_*',
    +            'limit' => array(1, 4),
    +            'get' => array('object_*', '#'),
    +            'sort'  => 'asc',
    +            'alpha' => true,
    +            'store' => 'destination_key',
    +        );
    +        $arguments = array('key', $modifiers);
    +
    +        $expected = array(
    +            'prefix:key', 'BY', 'prefix:by_key_*', 'GET', 'prefix:object_*', 'GET', '#',
    +            'LIMIT', 1, 4, 'ASC', 'ALPHA', 'STORE', 'prefix:destination_key'
    +        );
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testBasicSort()
    +    {
    +        $redis = $this->getClient();
    +        $redis->lpush('list:unordered', $unordered = array(2, 100, 3, 1, 30, 10));
    +
    +        $this->assertEquals(array(1, 2, 3, 10, 30, 100), $redis->sort('list:unordered'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testSortWithAscOrDescModifier()
    +    {
    +        $redis = $this->getClient();
    +        $redis->lpush('list:unordered', $unordered = array(2, 100, 3, 1, 30, 10));
    +
    +        $this->assertEquals(
    +            array(100, 30, 10, 3, 2, 1),
    +            $redis->sort('list:unordered', array(
    +                'sort' => 'desc',
    +            ))
    +        );
    +
    +        $this->assertEquals(
    +            array(1, 2, 3, 10, 30, 100),
    +            $redis->sort('list:unordered', array(
    +                'sort' => 'asc',
    +            ))
    +        );
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testSortWithLimitModifier()
    +    {
    +        $redis = $this->getClient();
    +        $redis->lpush('list:unordered', $unordered = array(2, 100, 3, 1, 30, 10));
    +
    +        $this->assertEquals(
    +            array(1, 2, 3),
    +            $redis->sort('list:unordered', array(
    +                'limit' => array(0, 3),
    +            ))
    +        );
    +
    +        $this->assertEquals(
    +            array(10, 30),
    +            $redis->sort('list:unordered', array(
    +                'limit' => array(3, 2)
    +            ))
    +        );
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testSortWithAlphaModifier()
    +    {
    +        $redis = $this->getClient();
    +        $redis->lpush('list:unordered', $unordered = array(2, 100, 3, 1, 30, 10));
    +
    +        $this->assertEquals(
    +            array(1, 10, 100, 2, 3, 30),
    +            $redis->sort('list:unordered', array(
    +                'alpha' => true
    +            ))
    +        );
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testSortWithStoreModifier()
    +    {
    +        $redis = $this->getClient();
    +        $redis->lpush('list:unordered', $unordered = array(2, 100, 3, 1, 30, 10));
    +
    +        $this->assertEquals(
    +            count($unordered),
    +            $redis->sort('list:unordered', array(
    +                'store' => 'list:ordered'
    +            ))
    +        );
    +
    +        $this->assertEquals(array(1, 2, 3, 10, 30, 100),  $redis->lrange('list:ordered', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testSortWithCombinedModifiers()
    +    {
    +        $redis = $this->getClient();
    +        $redis->lpush('list:unordered', $unordered = array(2, 100, 3, 1, 30, 10));
    +
    +        $this->assertEquals(
    +            array(30, 10, 3, 2),
    +            $redis->sort('list:unordered', array(
    +                'alpha' => false,
    +                'sort'  => 'desc',
    +                'limit' => array(1, 4)
    +            ))
    +        );
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testSortWithGetModifiers()
    +    {
    +        $redis = $this->getClient();
    +        $redis->lpush('list:unordered', $unordered = array(2, 100, 3, 1, 30, 10));
    +
    +        $redis->rpush('list:uids', $uids = array(1003, 1001, 1002, 1000));
    +        $redis->mset($sortget = array(
    +            'uid:1000' => 'foo',  'uid:1001' => 'bar',
    +            'uid:1002' => 'hoge', 'uid:1003' => 'piyo',
    +        ));
    +
    +        $this->assertEquals(array_values($sortget), $redis->sort('list:uids', array('get' => 'uid:*')));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->sort('foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeyTimeToLiveTest.php b/vendor/predis/predis/tests/Predis/Command/KeyTimeToLiveTest.php
    new file mode 100644
    index 0000000..440cf1f
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeyTimeToLiveTest.php
    @@ -0,0 +1,122 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-key
    + */
    +class KeyTimeToLiveTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeyTimeToLive';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'TTL';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 10);
    +        $expected = array('key', 10);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertSame(100, $command->parseResponse(100));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 10);
    +        $expected = array('prefix:key', 10);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsTTL()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->expire('foo', 10);
    +
    +        $this->assertSame(10, $redis->ttl('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsLessThanZeroOnNonExpiringKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $this->assertSame(-1, $redis->ttl('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @todo TTL changed in Redis >= 2.8 to return -2 on non existing keys, we
    +     *       should handle this case with a better solution than the current one.
    +     */
    +    public function testReturnsLessThanZeroOnNonExistingKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertLessThanOrEqual(-1, $redis->ttl('foo'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/KeyTypeTest.php b/vendor/predis/predis/tests/Predis/Command/KeyTypeTest.php
    new file mode 100644
    index 0000000..81d32b4
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/KeyTypeTest.php
    @@ -0,0 +1,109 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-key
    + */
    +class KeyTypeTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\KeyType';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'TYPE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame('type', $this->getCommand()->parseResponse('type'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsTypeOfKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame('none', $redis->type('type:keydoesnotexist'));
    +
    +        $redis->set('type:string', 'foobar');
    +        $this->assertSame('string', $redis->type('type:string'));
    +
    +        $redis->lpush('type:list', 'foobar');
    +        $this->assertSame('list', $redis->type('type:list'));
    +
    +        $redis->sadd('type:set', 'foobar');
    +        $this->assertSame('set', $redis->type('type:set'));
    +
    +        $redis->zadd('type:zset', 0, 'foobar');
    +        $this->assertSame('zset', $redis->type('type:zset'));
    +
    +        $redis->hset('type:hash', 'foo', 'bar');
    +        $this->assertSame('hash', $redis->type('type:hash'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ListIndexTest.php b/vendor/predis/predis/tests/Predis/Command/ListIndexTest.php
    new file mode 100644
    index 0000000..3de7e75
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ListIndexTest.php
    @@ -0,0 +1,126 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-list
    + */
    +class ListIndexTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ListIndex';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'LINDEX';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 1);
    +        $expected = array('key', 1);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(0, $this->getCommand()->parseResponse(0));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 1);
    +        $expected = array('prefix:key', 1);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsElementAtIndex()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', 'b', 'c', 'd', 'e');
    +
    +        $this->assertSame('a', $redis->lindex('letters', 0));
    +        $this->assertSame('c', $redis->lindex('letters', 2));
    +        $this->assertNull($redis->lindex('letters', 100));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsElementAtNegativeIndex()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', 'b', 'c', 'd', 'e');
    +
    +        $this->assertSame('a', $redis->lindex('letters', -0));
    +        $this->assertSame('c', $redis->lindex('letters', -3));
    +        $this->assertSame('e', $redis->lindex('letters', -1));
    +        $this->assertNull($redis->lindex('letters', -100));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->lindex('foo', 0);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ListInsertTest.php b/vendor/predis/predis/tests/Predis/Command/ListInsertTest.php
    new file mode 100644
    index 0000000..2fd3c7e
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ListInsertTest.php
    @@ -0,0 +1,134 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-list
    + */
    +class ListInsertTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ListInsert';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'LINSERT';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'before', 'value1', 'value2');
    +        $expected = array('key', 'before', 'value1', 'value2');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'before', 'value1', 'value2');
    +        $expected = array('prefix:key', 'before', 'value1', 'value2');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsLengthOfListAfterInser()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', 'c', 'e');
    +
    +        $this->assertSame(4, $redis->linsert('letters', 'before', 'c', 'b'));
    +        $this->assertSame(5, $redis->linsert('letters', 'after', 'c', 'd'));
    +        $this->assertSame(array('a', 'b', 'c', 'd', 'e'), $redis->lrange('letters', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsNegativeLengthOnFailedInsert()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', 'c', 'e');
    +
    +        $this->assertSame(-1, $redis->linsert('letters', 'before', 'n', 'm'));
    +        $this->assertSame(-1, $redis->linsert('letters', 'after', 'o', 'p'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsZeroLengthOnNonExistingList()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(0, $redis->linsert('letters', 'after', 'a', 'b'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->linsert('foo', 'BEFORE', 'bar', 'baz');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ListLengthTest.php b/vendor/predis/predis/tests/Predis/Command/ListLengthTest.php
    new file mode 100644
    index 0000000..b36e747
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ListLengthTest.php
    @@ -0,0 +1,121 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-list
    + */
    +class ListLengthTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ListLength';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'LLEN';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsLengthOfList()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', 'b', 'c');
    +        $this->assertSame(3, $redis->llen('letters'));
    +
    +        $redis->rpush('letters', 'd', 'e', 'f');
    +        $this->assertSame(6, $redis->llen('letters'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsZeroLengthOnNonExistingList()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(0, $redis->llen('letters'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->llen('foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ListPopFirstBlockingTest.php b/vendor/predis/predis/tests/Predis/Command/ListPopFirstBlockingTest.php
    new file mode 100644
    index 0000000..080f82e
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ListPopFirstBlockingTest.php
    @@ -0,0 +1,105 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-list
    + * @todo Testing blocking pop operations against Redis using PHP is
    + *       tricky, so we will skip these kind of tests for now.
    + */
    +class ListPopFirstBlockingTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ListPopFirstBlocking';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'BLPOP';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key1', 'key2', 'key3', 10);
    +        $expected = array('key1', 'key2', 'key3', 10);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsKeysAsSingleArray()
    +    {
    +        $arguments = array(array('key1', 'key2', 'key3'), 10);
    +        $expected = array('key1', 'key2', 'key3', 10);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('key', 'value');
    +        $expected = array('key', 'value');
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key1', 'key2', 'key3', 10);
    +        $expected = array('prefix:key1', 'prefix:key2', 'prefix:key3', 10);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ListPopFirstTest.php b/vendor/predis/predis/tests/Predis/Command/ListPopFirstTest.php
    new file mode 100644
    index 0000000..c7a6006
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ListPopFirstTest.php
    @@ -0,0 +1,121 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-list
    + */
    +class ListPopFirstTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ListPopFirst';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'LPOP';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame('element', $this->getCommand()->parseResponse('element'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testPopsTheFirstElementFromList()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', 'b', 'c', 'd');
    +
    +        $this->assertSame('a', $redis->lpop('letters'));
    +        $this->assertSame('b', $redis->lpop('letters'));
    +        $this->assertSame(array('c', 'd'), $redis->lrange('letters', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsNullOnEmptyList()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertNull($redis->lpop('letters'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->lpop('foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ListPopLastBlockingTest.php b/vendor/predis/predis/tests/Predis/Command/ListPopLastBlockingTest.php
    new file mode 100644
    index 0000000..63b3316
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ListPopLastBlockingTest.php
    @@ -0,0 +1,105 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-list
    + * @todo Testing blocking pop operations against Redis using PHP is
    + *       tricky, so we will skip these kind of tests for now.
    + */
    +class ListPopLastBlockingTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ListPopLastBlocking';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'BRPOP';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key1', 'key2', 'key3', 10);
    +        $expected = array('key1', 'key2', 'key3', 10);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsKeysAsSingleArray()
    +    {
    +        $arguments = array(array('key1', 'key2', 'key3'), 10);
    +        $expected = array('key1', 'key2', 'key3', 10);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('key', 'value');
    +        $expected = array('key', 'value');
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key1', 'key2', 'key3', 10);
    +        $expected = array('prefix:key1', 'prefix:key2', 'prefix:key3', 10);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ListPopLastPushHeadBlockingTest.php b/vendor/predis/predis/tests/Predis/Command/ListPopLastPushHeadBlockingTest.php
    new file mode 100644
    index 0000000..ce2f5b7
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ListPopLastPushHeadBlockingTest.php
    @@ -0,0 +1,86 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-list
    + * @todo Testing blocking pop operations against Redis using PHP is
    + *       tricky, so we will skip these kind of tests for now.
    + */
    +class ListPopLastPushHeadBlockingTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ListPopLastPushHeadBlocking';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'BRPOPLPUSH';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key:source', 'key:destination', 10);
    +        $expected = array('key:source', 'key:destination', 10);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame('element', $this->getCommand()->parseResponse('element'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key:source', 'key:destination', 10);
    +        $expected = array('prefix:key:source', 'prefix:key:destination', 10);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ListPopLastPushHeadTest.php b/vendor/predis/predis/tests/Predis/Command/ListPopLastPushHeadTest.php
    new file mode 100644
    index 0000000..0df30cc
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ListPopLastPushHeadTest.php
    @@ -0,0 +1,155 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-list
    + */
    +class ListPopLastPushHeadTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ListPopLastPushHead';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'RPOPLPUSH';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key:source', 'key:destination');
    +        $expected = array('key:source', 'key:destination');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame('element', $this->getCommand()->parseResponse('element'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key:source', 'key:destination');
    +        $expected = array('prefix:key:source', 'prefix:key:destination');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsElementPoppedFromSourceAndPushesToDestination()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters:source', 'a', 'b', 'c');
    +
    +        $this->assertSame('c', $redis->rpoplpush('letters:source', 'letters:destination'));
    +        $this->assertSame('b', $redis->rpoplpush('letters:source', 'letters:destination'));
    +        $this->assertSame('a', $redis->rpoplpush('letters:source', 'letters:destination'));
    +
    +        $this->assertSame(array(), $redis->lrange('letters:source', 0, -1));
    +        $this->assertSame(array('a', 'b', 'c'), $redis->lrange('letters:destination', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsElementPoppedFromSourceAndPushesToSelf()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters:source', 'a', 'b', 'c');
    +
    +        $this->assertSame('c', $redis->rpoplpush('letters:source', 'letters:source'));
    +        $this->assertSame('b', $redis->rpoplpush('letters:source', 'letters:source'));
    +        $this->assertSame('a', $redis->rpoplpush('letters:source', 'letters:source'));
    +
    +        $this->assertSame(array('a', 'b', 'c'), $redis->lrange('letters:source', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsNullOnEmptySource()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertNull($redis->rpoplpush('key:source', 'key:destination'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongTypeOfSourceKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('key:source', 'foo');
    +        $redis->rpoplpush('key:source', 'key:destination');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongTypeOfDestinationKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('key:source', 'foo');
    +        $redis->set('key:destination', 'bar');
    +
    +        $redis->rpoplpush('key:source', 'key:destination');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ListPopLastTest.php b/vendor/predis/predis/tests/Predis/Command/ListPopLastTest.php
    new file mode 100644
    index 0000000..3a66d3e
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ListPopLastTest.php
    @@ -0,0 +1,121 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-list
    + */
    +class ListPopLastTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ListPopLast';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'RPOP';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame('element', $this->getCommand()->parseResponse('element'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testPopsTheLastElementFromList()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', 'b', 'c', 'd');
    +
    +        $this->assertSame('d', $redis->rpop('letters'));
    +        $this->assertSame('c', $redis->rpop('letters'));
    +        $this->assertSame(array('a', 'b'), $redis->lrange('letters', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsNullOnEmptyList()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertNull($redis->rpop('letters'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->rpop('foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ListPushHeadTest.php b/vendor/predis/predis/tests/Predis/Command/ListPushHeadTest.php
    new file mode 100644
    index 0000000..685bca2
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ListPushHeadTest.php
    @@ -0,0 +1,124 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-list
    + */
    +class ListPushHeadTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ListPushHead';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'LPUSH';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'value1', 'value2', 'value3');
    +        $expected = array('key', 'value1', 'value2', 'value3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsValuesAsSingleArray()
    +    {
    +        $arguments = array('key', array('value1', 'value2', 'value3'));
    +        $expected = array('key', 'value1', 'value2', 'value3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'value1', 'value2', 'value3');
    +        $expected = array('prefix:key', 'value1', 'value2', 'value3');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testPushesElementsToHeadOfList()
    +    {
    +        $redis = $this->getClient();
    +
    +        // NOTE: List push operations return the list length since Redis commit 520b5a3
    +        $this->assertSame(1, $redis->lpush('metavars', 'foo'));
    +        $this->assertSame(2, $redis->lpush('metavars', 'hoge'));
    +        $this->assertSame(array('hoge', 'foo'), $redis->lrange('metavars', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('metavars', 'foo');
    +        $redis->lpush('metavars', 'hoge');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ListPushHeadXTest.php b/vendor/predis/predis/tests/Predis/Command/ListPushHeadXTest.php
    new file mode 100644
    index 0000000..67d9bdf
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ListPushHeadXTest.php
    @@ -0,0 +1,122 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-list
    + */
    +class ListPushHeadXTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ListPushHeadX';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'LPUSHX';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'value');
    +        $expected = array('key', 'value');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'value');
    +        $expected = array('prefix:key', 'value');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testPushesElementsToHeadOfExistingList()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->lpush('metavars', 'foo');
    +
    +        $this->assertSame(2, $redis->lpushx('metavars', 'hoge'));
    +        $this->assertSame(array('hoge', 'foo'), $redis->lrange('metavars', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testDoesNotPushElementOnNonExistingKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(0, $redis->lpushx('metavars', 'foo'));
    +        $this->assertSame(0, $redis->lpushx('metavars', 'hoge'));
    +        $this->assertFalse($redis->exists('metavars'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('metavars', 'foo');
    +        $redis->lpushx('metavars', 'hoge');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ListPushTailTest.php b/vendor/predis/predis/tests/Predis/Command/ListPushTailTest.php
    new file mode 100644
    index 0000000..8f69bb0
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ListPushTailTest.php
    @@ -0,0 +1,124 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-list
    + */
    +class ListPushTailTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ListPushTail';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'RPUSH';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'value1', 'value2', 'value3');
    +        $expected = array('key', 'value1', 'value2', 'value3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsValuesAsSingleArray()
    +    {
    +        $arguments = array('key', array('value1', 'value2', 'value3'));
    +        $expected = array('key', 'value1', 'value2', 'value3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'value1', 'value2', 'value3');
    +        $expected = array('prefix:key', 'value1', 'value2', 'value3');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testPushesElementsToHeadOfList()
    +    {
    +        $redis = $this->getClient();
    +
    +        // NOTE: List push operations return the list length since Redis commit 520b5a3
    +        $this->assertSame(1, $redis->rpush('metavars', 'foo'));
    +        $this->assertSame(2, $redis->rpush('metavars', 'hoge'));
    +        $this->assertSame(array('foo', 'hoge'), $redis->lrange('metavars', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('metavars', 'foo');
    +        $redis->rpush('metavars', 'hoge');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ListPushTailXTest.php b/vendor/predis/predis/tests/Predis/Command/ListPushTailXTest.php
    new file mode 100644
    index 0000000..4175de5
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ListPushTailXTest.php
    @@ -0,0 +1,122 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-list
    + */
    +class ListPushTailXTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ListPushTailX';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'RPUSHX';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'value');
    +        $expected = array('key', 'value');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'value');
    +        $expected = array('prefix:key', 'value');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testPushesElementsToHeadOfExistingList()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('metavars', 'foo');
    +
    +        $this->assertSame(2, $redis->rpushx('metavars', 'hoge'));
    +        $this->assertSame(array('foo', 'hoge'), $redis->lrange('metavars', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testDoesNotPushElementOnNonExistingKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(0, $redis->rpushx('metavars', 'foo'));
    +        $this->assertSame(0, $redis->rpushx('metavars', 'hoge'));
    +        $this->assertFalse($redis->exists('metavars'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('metavars', 'foo');
    +        $redis->rpushx('metavars', 'hoge');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ListRangeTest.php b/vendor/predis/predis/tests/Predis/Command/ListRangeTest.php
    new file mode 100644
    index 0000000..ea6f5ef
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ListRangeTest.php
    @@ -0,0 +1,165 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-list
    + */
    +class ListRangeTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ListRange';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'LRANGE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 0, -1);
    +        $expected = array('key', 0, -1);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('value1', 'value2', 'value3');
    +        $expected = array('value1', 'value2', 'value3');
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 0, -1);
    +        $expected = array('prefix:key', 0, -1);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsListSliceWithPositiveStartAndStop()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'l');
    +
    +        $this->assertSame(array('a', 'b', 'c', 'd'), $redis->lrange('letters', 0, 3));
    +        $this->assertSame(array('e', 'f', 'g', 'h'), $redis->lrange('letters', 4, 7));
    +        $this->assertSame(array('a', 'b'), $redis->lrange('letters', 0, 1));
    +        $this->assertSame(array('a'), $redis->lrange('letters', 0, 0));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsListSliceWithPositiveStartAndNegativeStop()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'l');
    +
    +        $this->assertSame(array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'l'), $redis->lrange('letters', 0, -1));
    +        $this->assertSame(array('f'), $redis->lrange('letters', 5, -5));
    +        $this->assertSame(array(), $redis->lrange('letters', 7, -5));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsListSliceWithNegativeStartAndStop()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'l');
    +
    +        $this->assertSame(array('f'), $redis->lrange('letters', -5, -5));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testHandlesStartAndStopOverflow()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'l');
    +
    +        $this->assertSame(array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'l'), $redis->lrange('letters', -100, 100));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsEmptyArrayOnNonExistingList()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array(), $redis->lrange('letters', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('metavars', 'foo');
    +        $redis->lrange('metavars', 0, -1);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ListRemoveTest.php b/vendor/predis/predis/tests/Predis/Command/ListRemoveTest.php
    new file mode 100644
    index 0000000..32e23cb
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ListRemoveTest.php
    @@ -0,0 +1,149 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-list
    + */
    +class ListRemoveTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ListRemove';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'LREM';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 1, 'value');
    +        $expected = array('key', 1, 'value');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 1, 'value');
    +        $expected = array('prefix:key', 1, 'value');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRemovesMatchingElementsFromHeadToTail()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', '_', 'b', '_', 'c', '_', 'd', '_');
    +
    +        $this->assertSame(2, $redis->lrem('letters', 2, '_'));
    +        $this->assertSame(array('a', 'b', 'c', '_', 'd', '_'), $redis->lrange('letters', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRemovesMatchingElementsFromTailToHead()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', '_', 'b', '_', 'c', '_', 'd', '_');
    +
    +        $this->assertSame(2, $redis->lrem('letters', -2, '_'));
    +        $this->assertSame(array('a', '_', 'b', '_', 'c', 'd'), $redis->lrange('letters', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRemovesAllMatchingElements()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', '_', 'b', '_', 'c', '_', 'd', '_');
    +
    +        $this->assertSame(4, $redis->lrem('letters', 0, '_'));
    +        $this->assertSame(array('a', 'b', 'c', 'd'), $redis->lrange('letters', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsZeroOnNonMatchingElementsOrEmptyList()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', 'b', 'c', 'd');
    +
    +        $this->assertSame(0, $redis->lrem('letters', 0, 'z'));
    +        $this->assertSame(0, $redis->lrem('digits', 0, 100));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('metavars', 'foo');
    +        $redis->lrem('metavars', 0, 0);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ListSetTest.php b/vendor/predis/predis/tests/Predis/Command/ListSetTest.php
    new file mode 100644
    index 0000000..124109d
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ListSetTest.php
    @@ -0,0 +1,123 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-list
    + */
    +class ListSetTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ListSet';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'LSET';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 0, 'value');
    +        $expected = array('key', 0, 'value');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 0, 'value');
    +        $expected = array('prefix:key', 0, 'value');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testSetsElementAtSpecifiedIndex()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', 'b', 'c');
    +
    +        $this->assertTrue($redis->lset('letters', 1, 'B'));
    +        $this->assertSame(array('a', 'B', 'c'), $redis->lrange('letters', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR index out of range
    +     */
    +    public function testThrowsExceptionOnIndexOutOfRange()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', 'b', 'c');
    +        $redis->lset('letters', 21, 'z');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('metavars', 'foo');
    +        $redis->lset('metavars', 0, 'hoge');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ListTrimTest.php b/vendor/predis/predis/tests/Predis/Command/ListTrimTest.php
    new file mode 100644
    index 0000000..b543a63
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ListTrimTest.php
    @@ -0,0 +1,155 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-list
    + */
    +class ListTrimTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ListTrim';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'LTRIM';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 0, 1);
    +        $expected = array('key', 0, 1);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 0, 1);
    +        $expected = array('prefix:key', 0, 1);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testTrimsListWithPositiveStartAndStop()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'l');
    +
    +        $this->assertTrue($redis->ltrim('letters', 0, 2));
    +        $this->assertSame(array('a', 'b', 'c'), $redis->lrange('letters', 0, -1));
    +
    +        $redis->flushdb();
    +        $redis->rpush('letters', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'l');
    +
    +        $this->assertTrue($redis->ltrim('letters', 5, 9));
    +        $this->assertSame(array('f', 'g', 'h', 'i', 'l'), $redis->lrange('letters', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testTrimsListWithPositiveStartAndNegativeStop()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'l');
    +
    +        $this->assertTrue($redis->ltrim('letters', 0, -6));
    +        $this->assertSame(array('a', 'b', 'c', 'd', 'e'), $redis->lrange('letters', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testTrimsListWithNegativeStartAndStop()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'l');
    +
    +        $this->assertTrue($redis->ltrim('letters', -5, -5));
    +        $this->assertSame(array('f'), $redis->lrange('letters', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testHandlesStartAndStopOverflow()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('letters', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'l');
    +
    +        $this->assertTrue($redis->ltrim('letters', -100, 100));
    +        $this->assertSame(array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'l'), $redis->lrange('letters', -100, 100));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('metavars', 'foo');
    +        $redis->ltrim('metavars', 0, 1);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/PrefixHelpersTest.php b/vendor/predis/predis/tests/Predis/Command/PrefixHelpersTest.php
    new file mode 100644
    index 0000000..bb81c49
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/PrefixHelpersTest.php
    @@ -0,0 +1,84 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class PrefixHelpersTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixFirst()
    +    {
    +        $arguments = array('1st', '2nd', '3rd', '4th');
    +        $expected = array('prefix:1st', '2nd', '3rd', '4th');
    +
    +        $command = $this->getMockForAbstractClass('Predis\Command\AbstractCommand');
    +        $command->setRawArguments($arguments);
    +
    +        PrefixHelpers::first($command, 'prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixAll()
    +    {
    +        $arguments = array('1st', '2nd', '3rd', '4th');
    +        $expected = array('prefix:1st', 'prefix:2nd', 'prefix:3rd', 'prefix:4th');
    +
    +        $command = $this->getMockForAbstractClass('Predis\Command\AbstractCommand');
    +        $command->setRawArguments($arguments);
    +
    +        PrefixHelpers::all($command, 'prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixInterleaved()
    +    {
    +        $arguments = array('1st', '2nd', '3rd', '4th');
    +        $expected = array('prefix:1st', '2nd', 'prefix:3rd', '4th');
    +
    +        $command = $this->getMockForAbstractClass('Predis\Command\AbstractCommand');
    +        $command->setRawArguments($arguments);
    +
    +        PrefixHelpers::interleaved($command, 'prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixSkipLast()
    +    {
    +        $arguments = array('1st', '2nd', '3rd', '4th');
    +        $expected = array('prefix:1st', 'prefix:2nd', 'prefix:3rd', '4th');
    +
    +        $command = $this->getMockForAbstractClass('Predis\Command\AbstractCommand');
    +        $command->setRawArguments($arguments);
    +
    +        PrefixHelpers::skipLast($command, 'prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/PrefixableCommandTest.php b/vendor/predis/predis/tests/Predis/Command/PrefixableCommandTest.php
    new file mode 100644
    index 0000000..8523e28
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/PrefixableCommandTest.php
    @@ -0,0 +1,54 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class PrefixableCommandTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testImplementsCorrectInterface()
    +    {
    +        $command = $this->getMockForAbstractClass('Predis\Command\PrefixableCommand');
    +
    +        $this->assertInstanceOf('Predis\Command\PrefixableCommandInterface', $command);
    +        $this->assertInstanceOf('Predis\Command\CommandInterface', $command);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAddPrefixToFirstArgument()
    +    {
    +        $command = $this->getMockForAbstractClass('Predis\Command\PrefixableCommand');
    +        $command->setRawArguments(array('key', 'value'));
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array('prefix:key', 'value'), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDoesNotBreakOnEmptyArguments()
    +    {
    +        $command = $this->getMockForAbstractClass('Predis\Command\PrefixableCommand');
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertEmpty($command->getArguments());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/Processor/KeyPrefixProcessorTest.php b/vendor/predis/predis/tests/Predis/Command/Processor/KeyPrefixProcessorTest.php
    new file mode 100644
    index 0000000..16f3194
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/Processor/KeyPrefixProcessorTest.php
    @@ -0,0 +1,111 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command\Processor;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class KeyPrefixProcessorTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithPrefix()
    +    {
    +        $prefix = 'prefix:';
    +        $processor = new KeyPrefixProcessor($prefix);
    +
    +        $this->assertInstanceOf('Predis\Command\Processor\CommandProcessorInterface', $processor);
    +        $this->assertEquals($prefix, $processor->getPrefix());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testChangePrefix()
    +    {
    +        $prefix1 = 'prefix:';
    +        $prefix2 = 'prefix:new:';
    +
    +        $processor = new KeyPrefixProcessor($prefix1);
    +        $this->assertEquals($prefix1, $processor->getPrefix());
    +
    +        $processor->setPrefix($prefix2);
    +        $this->assertEquals($prefix2, $processor->getPrefix());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testProcessPrefixableCommands()
    +    {
    +        $prefix = 'prefix:';
    +
    +        $command = $this->getMock('Predis\Command\PrefixableCommand');
    +        $command->expects($this->once())
    +                ->method('prefixKeys')
    +                ->with($prefix);
    +        $command->expects($this->once())
    +                ->method('getArguments')
    +                ->will($this->returnValue('key'));
    +
    +        $processor = new KeyPrefixProcessor($prefix);
    +
    +        $processor->process($command);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSkipPrefixableCommandsWithNoArguments()
    +    {
    +        $prefix = 'prefix:';
    +
    +        $command = $this->getMock('Predis\Command\PrefixableCommand');
    +        $command->expects($this->never())
    +                ->method('prefixKeys');
    +
    +        $processor = new KeyPrefixProcessor($prefix);
    +
    +        $processor->process($command);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSkipNotPrefixableCommands()
    +    {
    +        $prefix = 'prefix:';
    +        $unprefixed = 'key';
    +        $expected = "$prefix$unprefixed";
    +
    +        $command = $this->getMock('Predis\Command\CommandInterface');
    +        $command->expects($this->never())->method('prefixKeys');
    +
    +        $processor = new KeyPrefixProcessor($prefix);
    +
    +        $processor->process($command);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testInstanceCanBeCastedToString()
    +    {
    +        $prefix = 'prefix:';
    +        $processor = new KeyPrefixProcessor($prefix);
    +
    +        $this->assertEquals($prefix, (string) $processor);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/Processor/ProcessorChainTest.php b/vendor/predis/predis/tests/Predis/Command/Processor/ProcessorChainTest.php
    new file mode 100644
    index 0000000..8c793e2
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/Processor/ProcessorChainTest.php
    @@ -0,0 +1,170 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command\Processor;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class ProcessorChainTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructor()
    +    {
    +        $chain = new ProcessorChain();
    +
    +        $this->assertInstanceOf('Predis\Command\Processor\CommandProcessorInterface', $chain);
    +        $this->assertInstanceOf('Predis\Command\Processor\CommandProcessorChainInterface', $chain);
    +        $this->assertEmpty($chain->getProcessors());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithProcessorsArray()
    +    {
    +        $processors = array(
    +            $this->getMock('Predis\Command\Processor\CommandProcessorInterface'),
    +            $this->getMock('Predis\Command\Processor\CommandProcessorInterface'),
    +        );
    +
    +        $chain = new ProcessorChain($processors);
    +
    +        $this->assertSame($processors, $chain->getProcessors());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCountProcessors()
    +    {
    +        $processors = array(
    +            $this->getMock('Predis\Command\Processor\CommandProcessorInterface'),
    +            $this->getMock('Predis\Command\Processor\CommandProcessorInterface'),
    +        );
    +
    +        $chain = new ProcessorChain($processors);
    +
    +        $this->assertEquals(2, $chain->count());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAddProcessors()
    +    {
    +        $processors = array(
    +            $this->getMock('Predis\Command\Processor\CommandProcessorInterface'),
    +            $this->getMock('Predis\Command\Processor\CommandProcessorInterface'),
    +        );
    +
    +        $chain = new ProcessorChain();
    +        $chain->add($processors[0]);
    +        $chain->add($processors[1]);
    +
    +        $this->assertSame($processors, $chain->getProcessors());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAddMoreProcessors()
    +    {
    +        $processors1 = array(
    +            $this->getMock('Predis\Command\Processor\CommandProcessorInterface'),
    +            $this->getMock('Predis\Command\Processor\CommandProcessorInterface'),
    +        );
    +
    +        $processors2 = array(
    +            $this->getMock('Predis\Command\Processor\CommandProcessorInterface'),
    +            $this->getMock('Predis\Command\Processor\CommandProcessorInterface'),
    +        );
    +
    +        $chain = new ProcessorChain($processors1);
    +        $chain->add($processors2[0]);
    +        $chain->add($processors2[1]);
    +
    +        $this->assertSame(array_merge($processors1, $processors2), $chain->getProcessors());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testRemoveProcessors()
    +    {
    +        $processors = array(
    +            $this->getMock('Predis\Command\Processor\CommandProcessorInterface'),
    +            $this->getMock('Predis\Command\Processor\CommandProcessorInterface'),
    +        );
    +
    +        $chain = new ProcessorChain($processors);
    +
    +        $chain->remove($processors[0]);
    +        $this->assertSame(array($processors[1]), $chain->getProcessors());
    +
    +        $chain->remove($processors[1]);
    +        $this->assertEmpty($chain->getProcessors());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testRemoveProcessorNotInChain()
    +    {
    +        $processor = $this->getMock('Predis\Command\Processor\CommandProcessorInterface');
    +        $processors = array(
    +            $this->getMock('Predis\Command\Processor\CommandProcessorInterface'),
    +            $this->getMock('Predis\Command\Processor\CommandProcessorInterface'),
    +        );
    +
    +        $chain = new ProcessorChain($processors);
    +        $chain->remove($processor);
    +
    +        $this->assertSame($processors, $chain->getProcessors());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testRemoveProcessorFromEmptyChain()
    +    {
    +        $processor = $this->getMock('Predis\Command\Processor\CommandProcessorInterface');
    +
    +        $chain = new ProcessorChain();
    +        $this->assertEmpty($chain->getProcessors());
    +
    +        $chain->remove($processor);
    +        $this->assertEmpty($chain->getProcessors());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testProcessChain()
    +    {
    +        $command = $this->getMock('Predis\Command\CommandInterface');
    +
    +        $processor1 = $this->getMock('Predis\Command\Processor\CommandProcessorInterface');
    +        $processor1->expects($this->once())->method('process')->with($command);
    +
    +        $processor2 = $this->getMock('Predis\Command\Processor\CommandProcessorInterface');
    +        $processor2->expects($this->once())->method('process')->with($command);
    +
    +        $processors = array($processor1, $processor2);
    +
    +        $chain = new ProcessorChain($processors);
    +        $chain->process($command);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/PubSubPublishTest.php b/vendor/predis/predis/tests/Predis/Command/PubSubPublishTest.php
    new file mode 100644
    index 0000000..69d3f59
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/PubSubPublishTest.php
    @@ -0,0 +1,98 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-pubsub
    + */
    +class PubSubPublishTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\PubSubPublish';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'PUBLISH';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('channel', 'message');
    +        $expected = array('channel', 'message');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('channel', 'message');
    +        $expected = array('prefix:channel', 'message');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testPublishesMessagesToChannel()
    +    {
    +        $redis1 = $this->getClient();
    +        $redis2 = $this->getClient();
    +
    +        $redis1->subscribe('channel:foo');
    +
    +        $this->assertSame(1, $redis2->publish('channel:foo', 'bar'));
    +        $this->assertSame(0, $redis2->publish('channel:hoge', 'piyo'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/PubSubSubscribeByPatternTest.php b/vendor/predis/predis/tests/Predis/Command/PubSubSubscribeByPatternTest.php
    new file mode 100644
    index 0000000..94c1a8e
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/PubSubSubscribeByPatternTest.php
    @@ -0,0 +1,184 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-pubsub
    + */
    +class PubSubSubscribeByPatternTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\PubSubSubscribeByPattern';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'PSUBSCRIBE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('channel:foo:*', 'channel:hoge:*');
    +        $expected = array('channel:foo:*', 'channel:hoge:*');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsAsSingleArray()
    +    {
    +        $arguments = array(array('channel:foo:*', 'channel:hoge:*'));
    +        $expected = array('channel:foo:*', 'channel:hoge:*');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('psubscribe', 'channel:*', 1);
    +        $expected = array('psubscribe', 'channel:*', 1);
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('channel:foo:*', 'channel:hoge:*');
    +        $expected = array('prefix:channel:foo:*', 'prefix:channel:hoge:*');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsTheFirstPsubscribedChannelDetails()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array('psubscribe', 'channel:*', 1), $redis->psubscribe('channel:*'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanSendPsubscribeAfterPsubscribe()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array('psubscribe', 'channel:foo:*', 1), $redis->psubscribe('channel:foo:*'));
    +        $this->assertSame(array('psubscribe', 'channel:hoge:*', 2), $redis->psubscribe('channel:hoge:*'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanSendSubscribeAfterPsubscribe()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array('psubscribe', 'channel:foo:*', 1), $redis->psubscribe('channel:foo:*'));
    +        $this->assertSame(array('subscribe', 'channel:foo:bar', 2), $redis->subscribe('channel:foo:bar'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanSendUnsubscribeAfterPsubscribe()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array('psubscribe', 'channel:foo:*', 1), $redis->psubscribe('channel:foo:*'));
    +        $this->assertSame(array('psubscribe', 'channel:hoge:*', 2), $redis->psubscribe('channel:hoge:*'));
    +        $this->assertSame(array('unsubscribe', 'channel:foo:bar', 2), $redis->unsubscribe('channel:foo:bar'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanSendPunsubscribeAfterPsubscribe()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array('psubscribe', 'channel:foo:*', 1), $redis->psubscribe('channel:foo:*'));
    +        $this->assertSame(array('psubscribe', 'channel:hoge:*', 2), $redis->psubscribe('channel:hoge:*'));
    +        $this->assertSame(array('punsubscribe', 'channel:*:*', 2), $redis->punsubscribe('channel:*:*'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanSendQuitAfterPsubscribe()
    +    {
    +        $redis = $this->getClient();
    +        $quit = $this->getProfile()->createCommand('quit');
    +
    +        $this->assertSame(array('subscribe', 'channel1', 1), $redis->subscribe('channel1'));
    +        $this->assertTrue($redis->executeCommand($quit));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context
    +     */
    +    public function testCannotSendOtherCommandsAfterPsubscribe()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->psubscribe('channel:*');
    +        $redis->set('foo', 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/PubSubSubscribeTest.php b/vendor/predis/predis/tests/Predis/Command/PubSubSubscribeTest.php
    new file mode 100644
    index 0000000..3c424d4
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/PubSubSubscribeTest.php
    @@ -0,0 +1,184 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-pubsub
    + */
    +class PubSubSubscribeTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\PubSubSubscribe';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SUBSCRIBE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('channel:foo', 'channel:bar');
    +        $expected = array('channel:foo', 'channel:bar');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsAsSingleArray()
    +    {
    +        $arguments = array(array('channel:foo', 'channel:bar'));
    +        $expected = array('channel:foo', 'channel:bar');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('subscribe', 'channel', 1);
    +        $expected = array('subscribe', 'channel', 1);
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array(array('channel:foo', 'channel:bar'));
    +        $expected = array('prefix:channel:foo', 'prefix:channel:bar');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsTheFirstSubscribedChannelDetails()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array('subscribe', 'channel', 1), $redis->subscribe('channel'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanSendSubscribeAfterSubscribe()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array('subscribe', 'channel:foo', 1), $redis->subscribe('channel:foo'));
    +        $this->assertSame(array('subscribe', 'channel:bar', 2), $redis->subscribe('channel:bar'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanSendPsubscribeAfterSubscribe()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array('subscribe', 'channel:foo', 1), $redis->subscribe('channel:foo'));
    +        $this->assertSame(array('psubscribe', 'channel:*', 2), $redis->psubscribe('channel:*'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanSendUnsubscribeAfterSubscribe()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array('subscribe', 'channel:foo', 1), $redis->subscribe('channel:foo'));
    +        $this->assertSame(array('subscribe', 'channel:bar', 2), $redis->subscribe('channel:bar'));
    +        $this->assertSame(array('unsubscribe', 'channel:foo', 1), $redis->unsubscribe('channel:foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanSendPunsubscribeAfterSubscribe()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array('subscribe', 'channel:foo', 1), $redis->subscribe('channel:foo'));
    +        $this->assertSame(array('subscribe', 'channel:bar', 2), $redis->subscribe('channel:bar'));
    +        $this->assertSame(array('punsubscribe', 'channel:*', 2), $redis->punsubscribe('channel:*'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanSendQuitAfterSubscribe()
    +    {
    +        $redis = $this->getClient();
    +        $quit = $this->getProfile()->createCommand('quit');
    +
    +        $this->assertSame(array('subscribe', 'channel:foo', 1), $redis->subscribe('channel:foo'));
    +        $this->assertTrue($redis->executeCommand($quit));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context
    +     */
    +    public function testCannotSendOtherCommandsAfterSubscribe()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->subscribe('channel:foo');
    +        $redis->set('foo', 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/PubSubUnsubscribeByPatternTest.php b/vendor/predis/predis/tests/Predis/Command/PubSubUnsubscribeByPatternTest.php
    new file mode 100644
    index 0000000..4a1be3a
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/PubSubUnsubscribeByPatternTest.php
    @@ -0,0 +1,152 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-pubsub
    + */
    +class PubSubUnsubscribeByPatternTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\PubSubUnsubscribeByPattern';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'PUNSUBSCRIBE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('channel:foo:*', 'channel:bar:*');
    +        $expected = array('channel:foo:*', 'channel:bar:*');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsAsSingleArray()
    +    {
    +        $arguments = array(array('channel:foo:*', 'channel:bar:*'));
    +        $expected = array('channel:foo:*', 'channel:bar:*');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('punsubscribe', 'channel:*', 1);
    +        $expected = array('punsubscribe', 'channel:*', 1);
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array(array('channel:foo:*', 'channel:bar:*'));
    +        $expected = array('prefix:channel:foo:*', 'prefix:channel:bar:*');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testDoesNotSwitchToSubscribeMode()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array('punsubscribe', 'channel:*', 0), $redis->punsubscribe('channel:*'));
    +        $this->assertSame('echoed', $redis->echo('echoed'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testUnsubscribesFromNotSubscribedChannels()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array('punsubscribe', 'channel:*', 0), $redis->punsubscribe('channel:*'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testUnsubscribesFromSubscribedChannels()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array('subscribe', 'channel:foo', 1), $redis->subscribe('channel:foo'));
    +        $this->assertSame(array('subscribe', 'channel:bar', 2), $redis->subscribe('channel:bar'));
    +        $this->assertSame(array('punsubscribe', 'channel:*', 2), $redis->punsubscribe('channel:*'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @todo Disabled for now, must investigate why this test hangs on PUNSUBSCRIBE.
    +     */
    +    public function __testUnsubscribesFromAllSubscribedChannels()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array('subscribe', 'channel:foo', 1), $redis->subscribe('channel:foo'));
    +        $this->assertSame(array('subscribe', 'channel:bar', 2), $redis->subscribe('channel:bar'));
    +
    +        $this->assertSame(array('punsubscribe', 'channel:foo', 1), $redis->punsubscribe());
    +        $this->assertSame(array('punsubscribe', 'channel:bar', 0), $redis->getConnection()->read());
    +        $this->assertSame('echoed', $redis->echo('echoed'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/PubSubUnsubscribeTest.php b/vendor/predis/predis/tests/Predis/Command/PubSubUnsubscribeTest.php
    new file mode 100644
    index 0000000..b2fd641
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/PubSubUnsubscribeTest.php
    @@ -0,0 +1,152 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-pubsub
    + */
    +class PubSubUnsubscribeTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\PubSubUnsubscribe';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'UNSUBSCRIBE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('channel1', 'channel2', 'channel3');
    +        $expected = array('channel1', 'channel2', 'channel3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsAsSingleArray()
    +    {
    +        $arguments = array(array('channel1', 'channel2', 'channel3'));
    +        $expected = array('channel1', 'channel2', 'channel3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('unsubscribe', 'channel', 1);
    +        $expected = array('unsubscribe', 'channel', 1);
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array(array('channel1', 'channel2', 'channel3'));
    +        $expected = array('prefix:channel1', 'prefix:channel2', 'prefix:channel3');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testDoesNotSwitchToSubscribeMode()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array('unsubscribe', 'channel', 0), $redis->unsubscribe('channel'));
    +        $this->assertSame('echoed', $redis->echo('echoed'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testUnsubscribesFromNotSubscribedChannels()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array('unsubscribe', 'channel', 0), $redis->unsubscribe('channel'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testUnsubscribesFromSubscribedChannels()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array('subscribe', 'channel', 1), $redis->subscribe('channel'));
    +        $this->assertSame(array('unsubscribe', 'channel', 0), $redis->unsubscribe('channel'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testUnsubscribesFromAllSubscribedChannels()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array('subscribe', 'channel:foo', 1), $redis->subscribe('channel:foo'));
    +        $this->assertSame(array('subscribe', 'channel:bar', 2), $redis->subscribe('channel:bar'));
    +
    +        list($_, $unsubscribed1, $_) = $redis->unsubscribe();
    +        list($_, $unsubscribed2, $_) = $redis->getConnection()->read();
    +        $this->assertSameValues(array('channel:foo', 'channel:bar'), array($unsubscribed1, $unsubscribed2));
    +
    +        $this->assertSame('echoed', $redis->echo('echoed'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ScriptedCommandTest.php b/vendor/predis/predis/tests/Predis/Command/ScriptedCommandTest.php
    new file mode 100644
    index 0000000..95c3144
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ScriptedCommandTest.php
    @@ -0,0 +1,201 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group realm-scripting
    + */
    +class ScriptedCommandTest extends StandardTestCase
    +{
    +    const LUA_SCRIPT = 'return { KEYS[1], KEYS[2], ARGV[1], ARGV[2] }';
    +    const LUA_SCRIPT_SHA1 = '6e07f61f502e36d123fe28523076af588f5c315e';
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetArguments()
    +    {
    +        $arguments = array('key1', 'key2', 'value1', 'value2');
    +
    +        $command = $this->getMock('Predis\Command\ScriptedCommand', array('getScript', 'getKeysCount'));
    +        $command->expects($this->once())
    +                ->method('getScript')
    +                ->will($this->returnValue(self::LUA_SCRIPT));
    +        $command->expects($this->once())
    +                ->method('getKeysCount')
    +                ->will($this->returnValue(2));
    +        $command->setArguments($arguments);
    +
    +
    +        $this->assertSame(array_merge(array(self::LUA_SCRIPT_SHA1, 2), $arguments), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetArgumentsWithNegativeKeysCount()
    +    {
    +        $arguments = array('key1', 'key2', 'value1', 'value2');
    +
    +        $command = $this->getMock('Predis\Command\ScriptedCommand', array('getScript', 'getKeysCount'));
    +        $command->expects($this->once())
    +                ->method('getScript')
    +                ->will($this->returnValue(self::LUA_SCRIPT));
    +        $command->expects($this->once())
    +                ->method('getKeysCount')
    +                ->will($this->returnValue(-2));
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame(array_merge(array(self::LUA_SCRIPT_SHA1, 2), $arguments), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetArgumentsWithZeroKeysCount()
    +    {
    +        $arguments = array('value1', 'value2', 'value3');
    +
    +        $command = $this->getMock('Predis\Command\ScriptedCommand', array('getScript', 'getKeysCount'));
    +        $command->expects($this->once())
    +                ->method('getScript')
    +                ->will($this->returnValue(self::LUA_SCRIPT));
    +        $command->expects($this->once())
    +                ->method('getKeysCount')
    +                ->will($this->returnValue(0));
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame(array_merge(array(self::LUA_SCRIPT_SHA1, 0), $arguments), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetKeys()
    +    {
    +        $arguments = array('key1', 'key2', 'value1', 'value2');
    +
    +        $command = $this->getMock('Predis\Command\ScriptedCommand', array('getScript', 'getKeysCount'));
    +        $command->expects($this->once())
    +                ->method('getScript')
    +                ->will($this->returnValue(self::LUA_SCRIPT));
    +        $command->expects($this->exactly(2))
    +                ->method('getKeysCount')
    +                ->will($this->returnValue(2));
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame(array('key1', 'key2'), $command->getKeys());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetKeysWithZeroKeysCount()
    +    {
    +        $arguments = array('value1', 'value2', 'value3');
    +
    +        $command = $this->getMock('Predis\Command\ScriptedCommand', array('getScript', 'getKeysCount'));
    +        $command->expects($this->once())
    +                ->method('getScript')
    +                ->will($this->returnValue(self::LUA_SCRIPT));
    +        $command->expects($this->exactly(2))
    +                ->method('getKeysCount')
    +                ->will($this->returnValue(0));
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame(array(), $command->getKeys());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetKeysWithNegativeKeysCount()
    +    {
    +        $arguments = array('key1', 'key2', 'value1', 'value2');
    +
    +        $command = $this->getMock('Predis\Command\ScriptedCommand', array('getScript', 'getKeysCount'));
    +        $command->expects($this->once())
    +                ->method('getScript')
    +                ->will($this->returnValue(self::LUA_SCRIPT));
    +        $command->expects($this->exactly(2))
    +                ->method('getKeysCount')
    +                ->will($this->returnValue(-2));
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame(array('key1', 'key2'), $command->getKeys());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('foo', 'hoge', 'bar', 'piyo');
    +        $expected = array('prefix:foo', 'prefix:hoge');
    +
    +        $command = $this->getMock('Predis\Command\ScriptedCommand', array('getScript', 'getKeysCount'));
    +        $command->expects($this->once())
    +                ->method('getScript')
    +                ->will($this->returnValue(self::LUA_SCRIPT));
    +        $command->expects($this->exactly(2))
    +                ->method('getKeysCount')
    +                ->will($this->returnValue(2));
    +        $command->setArguments($arguments);
    +
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getKeys());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysWithNegativeKeysCount()
    +    {
    +        $arguments = array('foo', 'hoge', 'bar', 'piyo');
    +        $expected = array('prefix:foo', 'prefix:hoge');
    +
    +        $command = $this->getMock('Predis\Command\ScriptedCommand', array('getScript', 'getKeysCount'));
    +        $command->expects($this->once())
    +                ->method('getScript')
    +                ->will($this->returnValue(self::LUA_SCRIPT));
    +        $command->expects($this->exactly(2))
    +                ->method('getKeysCount')
    +                ->will($this->returnValue(-2));
    +        $command->setArguments($arguments);
    +
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getKeys());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetScriptHash()
    +    {
    +        $arguments = array('key1', 'key2', 'value1', 'value2');
    +
    +        $command = $this->getMock('Predis\Command\ScriptedCommand', array('getScript', 'getKeysCount'));
    +        $command->expects($this->once())
    +                ->method('getScript')
    +                ->will($this->returnValue(self::LUA_SCRIPT));
    +        $command->expects($this->once())
    +                ->method('getKeysCount')
    +                ->will($this->returnValue(2));
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame(self::LUA_SCRIPT_SHA1, $command->getScriptHash());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerBackgroundRewriteAOFTest.php b/vendor/predis/predis/tests/Predis/Command/ServerBackgroundRewriteAOFTest.php
    new file mode 100644
    index 0000000..4fee52f
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerBackgroundRewriteAOFTest.php
    @@ -0,0 +1,56 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-server
    + */
    +class ServerBackgroundRewriteAOFTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerBackgroundRewriteAOF';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'BGREWRITEAOF';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->setArguments(array());
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerBackgroundSaveTest.php b/vendor/predis/predis/tests/Predis/Command/ServerBackgroundSaveTest.php
    new file mode 100644
    index 0000000..0bf7be0
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerBackgroundSaveTest.php
    @@ -0,0 +1,56 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-server
    + */
    +class ServerBackgroundSaveTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerBackgroundSave';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'BGSAVE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->setArguments(array());
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerClientTest.php b/vendor/predis/predis/tests/Predis/Command/ServerClientTest.php
    new file mode 100644
    index 0000000..b45e9d9
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerClientTest.php
    @@ -0,0 +1,223 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-server
    + */
    +class ServerClientTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerClient';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'CLIENT';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsOfClientKill()
    +    {
    +        $arguments = array('kill', '127.0.0.1:45393');
    +        $expected = array('kill', '127.0.0.1:45393');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsOfClientList()
    +    {
    +        $arguments = array('list');
    +        $expected = array('list');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsOfClientGetname()
    +    {
    +        $arguments = $expected = array('getname');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsOfClientSetname()
    +    {
    +        $arguments = $expected = array('setname', 'connection-a');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponseOfClientKill()
    +    {
    +        $command = $this->getCommand();
    +        $command->setArguments(array('kill'));
    +
    +        $this->assertSame(true, $command->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponseOfClientList()
    +    {
    +        $command = $this->getCommand();
    +        $command->setArguments(array('list'));
    +
    +        $raw =<<'127.0.0.1:45393','fd'=>'6','idle'=>'0','flags'=>'N','db'=>'0','sub'=>'0','psub'=>'0'),
    +            array('addr'=>'127.0.0.1:45394','fd'=>'7','idle'=>'0','flags'=>'N','db'=>'0','sub'=>'0','psub'=>'0'),
    +            array('addr'=>'127.0.0.1:45395','fd'=>'8','idle'=>'0','flags'=>'N','db'=>'0','sub'=>'0','psub'=>'0'),
    +        );
    +
    +        $this->assertSame($parsed, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsListOfConnectedClients()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertInternalType('array', $clients = $redis->client('LIST'));
    +        $this->assertGreaterThanOrEqual(1, count($clients));
    +        $this->assertInternalType('array', $clients[0]);
    +        $this->assertArrayHasKey('addr', $clients[0]);
    +        $this->assertArrayHasKey('fd', $clients[0]);
    +        $this->assertArrayHasKey('idle', $clients[0]);
    +        $this->assertArrayHasKey('flags', $clients[0]);
    +        $this->assertArrayHasKey('db', $clients[0]);
    +        $this->assertArrayHasKey('sub', $clients[0]);
    +        $this->assertArrayHasKey('psub', $clients[0]);
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testGetsNameOfConnection()
    +    {
    +         $this->markTestSkippedOnRedisVersionBelow('2.6.9');
    +
    +         $redis = $this->getClient();
    +         $clientName = $redis->client('GETNAME');
    +         $this->assertNull($clientName);
    +
    +         $expectedConnectionName = 'foo-bar';
    +         $this->assertTrue($redis->client('SETNAME', $expectedConnectionName));
    +         $this->assertEquals($expectedConnectionName, $redis->client('GETNAME'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testSetsNameOfConnection()
    +    {
    +         $this->markTestSkippedOnRedisVersionBelow('2.6.9');
    +
    +         $redis = $this->getClient();
    +
    +         $expectedConnectionName = 'foo-baz';
    +         $this->assertTrue($redis->client('SETNAME', $expectedConnectionName));
    +         $this->assertEquals($expectedConnectionName, $redis->client('GETNAME'));
    +    }
    +
    +    /**
    +     * @return array
    +     */
    +    public function invalidConnectionNameProvider()
    +    {
    +        return array(
    +            array('foo space'),
    +            array('foo \n'),
    +            array('foo $'),
    +        );
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @dataProvider invalidConnectionNameProvider
    +     */
    +    public function testInvalidSetNameOfConnection($invalidConnectionName)
    +    {
    +         $this->markTestSkippedOnRedisVersionBelow('2.6.9');
    +
    +         $redis = $this->getClient();
    +         $redis->client('SETNAME', $invalidConnectionName);
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     */
    +    public function testThrowsExceptioOnWrongModifier()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertTrue($redis->client('FOO'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR No such client
    +     */
    +    public function testThrowsExceptionWhenKillingUnknownClient()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertTrue($redis->client('KILL', '127.0.0.1:65535'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerConfigTest.php b/vendor/predis/predis/tests/Predis/Command/ServerConfigTest.php
    new file mode 100644
    index 0000000..bbd3f14
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerConfigTest.php
    @@ -0,0 +1,173 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-server
    + */
    +class ServerConfigTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerConfig';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'CONFIG';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('GET', 'slowlog');
    +        $expected = array('GET', 'slowlog');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponseOfConfigGet()
    +    {
    +        $raw = array('slowlog-log-slower-than','10000','slowlog-max-len','64','loglevel','verbose');
    +        $expected = array(
    +            'slowlog-log-slower-than' => '10000',
    +            'slowlog-max-len' => '64',
    +            'loglevel' => 'verbose',
    +        );
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponseOfConfigSet()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertTrue($command->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponseOfConfigResetstat()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertTrue($command->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsListOfConfigurationValues()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertInternalType('array', $configs = $redis->config('GET', '*'));
    +        $this->assertGreaterThan(1, count($configs));
    +        $this->assertArrayHasKey('loglevel', $configs);
    +        $this->assertArrayHasKey('appendonly', $configs);
    +        $this->assertArrayHasKey('dbfilename', $configs);
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsListOfOneConfigurationEntry()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertInternalType('array', $configs = $redis->config('GET', 'dbfilename'));
    +        $this->assertEquals(1, count($configs));
    +        $this->assertArrayHasKey('dbfilename', $configs);
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsEmptyListOnUnknownConfigurationEntry()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array(), $redis->config('GET', 'foobar'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsTrueOnSuccessfulConfiguration()
    +    {
    +        $redis = $this->getClient();
    +
    +        $previous = $redis->config('GET', 'loglevel');
    +
    +        $this->assertTrue($redis->config('SET', 'loglevel', 'notice'));
    +        $this->assertSame(array('loglevel' => 'notice'), $redis->config('GET', 'loglevel'));
    +
    +        // We set the loglevel configuration to the previous value.
    +        $redis->config('SET', 'loglevel', $previous['loglevel']);
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR Unsupported CONFIG parameter: foo
    +     */
    +    public function testThrowsExceptionWhenSettingUnknownConfiguration()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertFalse($redis->config('SET', 'foo', 'bar'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsTrueOnResetstat()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertTrue($redis->config('RESETSTAT'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     */
    +    public function testThrowsExceptionOnUnknownSubcommand()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertFalse($redis->config('FOO'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerDatabaseSizeTest.php b/vendor/predis/predis/tests/Predis/Command/ServerDatabaseSizeTest.php
    new file mode 100644
    index 0000000..fb889f7
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerDatabaseSizeTest.php
    @@ -0,0 +1,67 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-server
    + */
    +class ServerDatabaseSizeTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerDatabaseSize';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'DBSIZE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->setArguments(array());
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(100, $this->getCommand()->parseResponse(100));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsCurrentSizeOfDatabase()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $this->assertGreaterThan(0, $redis->dbsize());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerEvalSHATest.php b/vendor/predis/predis/tests/Predis/Command/ServerEvalSHATest.php
    new file mode 100644
    index 0000000..d777db0
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerEvalSHATest.php
    @@ -0,0 +1,138 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-scripting
    + */
    +class ServerEvalSHATest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerEvalSHA';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'EVALSHA';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('9d0c0826bde023cc39eebaaf832c32a890f3b088', 1, 'foo', 'bar');
    +        $expected = array('9d0c0826bde023cc39eebaaf832c32a890f3b088', 1, 'foo', 'bar');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertSame('bar', $this->getCommand()->parseResponse('bar'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $sha1 = 'a42059b356c875f0717db19a51f6aaca9ae659ea';
    +
    +        $arguments = array($sha1, 2, 'foo', 'hoge', 'bar', 'piyo');
    +        $expected = array($sha1, 2, 'prefix:foo', 'prefix:hoge', 'bar', 'piyo');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetScriptHash()
    +    {
    +        $command = $this->getCommandWithArgumentsArray(array($sha1 = sha1('return true')), 0);
    +        $this->assertSame($sha1, $command->getScriptHash());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testExecutesSpecifiedLuaScript()
    +    {
    +        $redis = $this->getClient();
    +
    +        $lua = 'return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}';
    +        $sha1 = sha1($lua);
    +        $result = array('foo', 'hoge', 'bar', 'piyo');
    +
    +        $this->assertSame($result, $redis->eval($lua, 2, 'foo', 'hoge', 'bar', 'piyo'));
    +        $this->assertSame($result, $redis->evalsha($sha1, 2, 'foo', 'hoge', 'bar', 'piyo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     */
    +    public function testThrowsExceptionOnWrongNumberOfKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $lua = 'return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}';
    +        $sha1 = sha1($lua);
    +
    +        $redis->eval($lua, 2, 'foo', 'hoge', 'bar', 'piyo');
    +        $redis->evalsha($sha1, 3, 'foo', 'hoge');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     */
    +    public function testThrowsExceptionOnInvalidScript()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->evalsha('ffffffffffffffffffffffffffffffffffffffff', 0);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerEvalTest.php b/vendor/predis/predis/tests/Predis/Command/ServerEvalTest.php
    new file mode 100644
    index 0000000..5aebf6e
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerEvalTest.php
    @@ -0,0 +1,133 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-scripting
    + */
    +class ServerEvalTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerEval';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'EVAL';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('return redis.call("SET", KEYS[1], ARGV[1])', 1, 'foo', 'bar');
    +        $expected = array('return redis.call("SET", KEYS[1], ARGV[1])', 1, 'foo', 'bar');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertSame('bar', $this->getCommand()->parseResponse('bar'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $lua = 'return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}';
    +
    +        $arguments = array($lua, 2, 'foo', 'hoge', 'bar', 'piyo');
    +        $expected = array($lua, 2, 'prefix:foo', 'prefix:hoge', 'bar', 'piyo');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetScriptHash()
    +    {
    +        $command = $this->getCommandWithArgumentsArray(array($lua = 'return true', 0));
    +        $this->assertSame(sha1($lua), $command->getScriptHash());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testExecutesSpecifiedLuaScript()
    +    {
    +        $redis = $this->getClient();
    +
    +        $lua = 'return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}';
    +        $result = array('foo', 'hoge', 'bar', 'piyo');
    +
    +        $this->assertSame($result, $redis->eval($lua, 2, 'foo', 'hoge', 'bar', 'piyo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     */
    +    public function testThrowsExceptionOnWrongNumberOfKeys()
    +    {
    +        $redis = $this->getClient();
    +        $lua = 'return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}';
    +
    +        $redis->eval($lua, 3, 'foo', 'hoge');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     */
    +    public function testThrowsExceptionOnInvalidScript()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->eval('invalid', 0);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerFlushAllTest.php b/vendor/predis/predis/tests/Predis/Command/ServerFlushAllTest.php
    new file mode 100644
    index 0000000..090d33f
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerFlushAllTest.php
    @@ -0,0 +1,56 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-server
    + */
    +class ServerFlushAllTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerFlushAll';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'FLUSHALL';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->setArguments(array());
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerFlushDatabaseTest.php b/vendor/predis/predis/tests/Predis/Command/ServerFlushDatabaseTest.php
    new file mode 100644
    index 0000000..39ae573
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerFlushDatabaseTest.php
    @@ -0,0 +1,69 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-server
    + */
    +class ServerFlushDatabaseTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerFlushDatabase';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'FLUSHDB';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->setArguments(array());
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testFlushesTheEntireLogicalDatabase()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertTrue($redis->flushdb());
    +        $this->assertFalse($redis->exists('foo'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerInfoTest.php b/vendor/predis/predis/tests/Predis/Command/ServerInfoTest.php
    new file mode 100644
    index 0000000..2b9b203
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerInfoTest.php
    @@ -0,0 +1,289 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-server
    + */
    +class ServerInfoTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerInfo';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'INFO';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->setArguments(array());
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw =<< '2.4.4',
    +            'redis_git_sha1' => 'bc62bc5e',
    +            'redis_git_dirty' => '0',
    +            'arch_bits' => '32',
    +            'multiplexing_api' => 'epoll',
    +            'process_id' => '15640',
    +            'uptime_in_seconds' => '792',
    +            'uptime_in_days' => '0',
    +            'lru_clock' => '197890',
    +            'used_cpu_sys' => '0.08',
    +            'used_cpu_user' => '0.10',
    +            'used_cpu_sys_children' => '0.00',
    +            'used_cpu_user_children' => '0.00',
    +            'connected_clients' => '1',
    +            'connected_slaves' => '0',
    +            'client_longest_output_list' => '0',
    +            'client_biggest_input_buf' => '0',
    +            'blocked_clients' => '0',
    +            'used_memory' => '556156',
    +            'used_memory_human' => '543.12K',
    +            'used_memory_rss' => '1396736',
    +            'used_memory_peak' => '547688',
    +            'used_memory_peak_human' => '534.85K',
    +            'mem_fragmentation_ratio' => '2.51',
    +            'mem_allocator' => 'jemalloc-2.2.1',
    +            'loading' => '0',
    +            'aof_enabled' => '0',
    +            'changes_since_last_save' => '0',
    +            'bgsave_in_progress' => '0',
    +            'last_save_time' => '1323183872',
    +            'bgrewriteaof_in_progress' => '0',
    +            'total_connections_received' => '2',
    +            'total_commands_processed' => '1',
    +            'expired_keys' => '0',
    +            'evicted_keys' => '0',
    +            'keyspace_hits' => '0',
    +            'keyspace_misses' => '0',
    +            'pubsub_channels' => '0',
    +            'pubsub_patterns' => '0',
    +            'latest_fork_usec' => '0',
    +            'vm_enabled' => '0',
    +            'role' => 'master',
    +            'db0' => array('keys' => '2', 'expires' => '0'),
    +            'db5' => array('keys' => '1', 'expires' => '0'),
    +        );
    +
    +        $this->assertSame($expected, $this->getCommand()->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanParseResponsesFromRedis30()
    +    {
    +        $raw =<< '2.9.0',
    +            'redis_git_sha1' => '237194b7',
    +            'redis_git_dirty' => '0',
    +            'arch_bits' => '32',
    +            'multiplexing_api' => 'epoll',
    +            'process_id' => '16620',
    +            'tcp_port' => '6379',
    +            'uptime_in_seconds' => '444',
    +            'uptime_in_days' => '0',
    +            'lru_clock' => '198040',
    +            'connected_clients' => '1',
    +            'client_longest_output_list' => '0',
    +            'client_biggest_input_buf' => '0',
    +            'blocked_clients' => '0',
    +            'used_memory' => '628076',
    +            'used_memory_human' => '613.36K',
    +            'used_memory_rss' => '1568768',
    +            'used_memory_peak' => '570056',
    +            'used_memory_peak_human' => '556.70K',
    +            'used_memory_lua' => '14336',
    +            'mem_fragmentation_ratio' => '2.50',
    +            'mem_allocator' => 'jemalloc-2.2.1',
    +            'loading' => '0',
    +            'aof_enabled' => '0',
    +            'changes_since_last_save' => '0',
    +            'bgsave_in_progress' => '0',
    +            'last_save_time' => '1323185719',
    +            'bgrewriteaof_in_progress' => '0',
    +            'total_connections_received' => '4',
    +            'total_commands_processed' => '3',
    +            'rejected_connections' => '0',
    +            'expired_keys' => '0',
    +            'evicted_keys' => '0',
    +            'keyspace_hits' => '0',
    +            'keyspace_misses' => '0',
    +            'pubsub_channels' => '0',
    +            'pubsub_patterns' => '0',
    +            'latest_fork_usec' => '0',
    +            'role' => 'master',
    +            'connected_slaves' => '0',
    +            'used_cpu_sys' => '0.06',
    +            'used_cpu_user' => '0.06',
    +            'used_cpu_sys_children' => '0.00',
    +            'used_cpu_user_children' => '0.00',
    +            'cluster_enabled' => '0',
    +            'db0' => array('keys' => '2', 'expires' => '0'),
    +            'db5' => array('keys' => '1','expires' => '0'),
    +        );
    +
    +        $this->assertSame($expected, $this->getCommand()->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsAnArrayOfInfo()
    +    {
    +        $redis = $this->getClient();
    +        $command = $this->getCommand();
    +
    +        $this->assertInternalType('array', $info = $redis->executeCommand($command));
    +        $this->assertArrayHasKey('redis_version', $info);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerInfoV26xTest.php b/vendor/predis/predis/tests/Predis/Command/ServerInfoV26xTest.php
    new file mode 100644
    index 0000000..76f75c7
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerInfoV26xTest.php
    @@ -0,0 +1,307 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-server
    + */
    +class ServerInfoV26xTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerInfoV26x';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'INFO';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->setArguments(array());
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw =<< array(
    +                'redis_version' => '2.9.0',
    +                'redis_git_sha1' => '237194b7',
    +                'redis_git_dirty' => '0',
    +                'arch_bits' => '32',
    +                'multiplexing_api' => 'epoll',
    +                'process_id' => '16620',
    +                'tcp_port' => '6379',
    +                'uptime_in_seconds' => '444',
    +                'uptime_in_days' => '0',
    +                'lru_clock' => '198040',
    +            ),
    +            'Clients' => array(
    +                'connected_clients' => '1',
    +                'client_longest_output_list' => '0',
    +                'client_biggest_input_buf' => '0',
    +                'blocked_clients' => '0',
    +            ),
    +            'Memory' => array(
    +                'used_memory' => '628076',
    +                'used_memory_human' => '613.36K',
    +                'used_memory_rss' => '1568768',
    +                'used_memory_peak' => '570056',
    +                'used_memory_peak_human' => '556.70K',
    +                'used_memory_lua' => '14336',
    +                'mem_fragmentation_ratio' => '2.50',
    +                'mem_allocator' => 'jemalloc-2.2.1',
    +            ),
    +            'Persistence' => array(
    +                'loading' => '0',
    +                'aof_enabled' => '0',
    +                'changes_since_last_save' => '0',
    +                'bgsave_in_progress' => '0',
    +                'last_save_time' => '1323185719',
    +                'bgrewriteaof_in_progress' => '0',
    +            ),
    +            'Stats' => array(
    +                'total_connections_received' => '4',
    +                'total_commands_processed' => '3',
    +                'rejected_connections' => '0',
    +                'expired_keys' => '0',
    +                'evicted_keys' => '0',
    +                'keyspace_hits' => '0',
    +                'keyspace_misses' => '0',
    +                'pubsub_channels' => '0',
    +                'pubsub_patterns' => '0',
    +                'latest_fork_usec' => '0',
    +            ),
    +            'Replication' => array(
    +                'role' => 'master',
    +                'connected_slaves' => '0',
    +            ),
    +            'CPU' => array(
    +                'used_cpu_sys' => '0.06',
    +                'used_cpu_user' => '0.06',
    +                'used_cpu_sys_children' => '0.00',
    +                'used_cpu_user_children' => '0.00',
    +            ),
    +            'Cluster' => array(
    +                'cluster_enabled' => '0',
    +            ),
    +            'Keyspace' => array(
    +                'db0' => array('keys' => '2', 'expires' => '0'),
    +                'db5' => array('keys' => '1', 'expires' => '0')
    +            ),
    +        );
    +
    +        $this->assertSame($expected, $this->getCommand()->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanParseResponsesFromOlderRedisVersions()
    +    {
    +        $raw =<< '2.4.4',
    +            'redis_git_sha1' => 'bc62bc5e',
    +            'redis_git_dirty' => '0',
    +            'arch_bits' => '32',
    +            'multiplexing_api' => 'epoll',
    +            'process_id' => '15640',
    +            'uptime_in_seconds' => '792',
    +            'uptime_in_days' => '0',
    +            'lru_clock' => '197890',
    +            'used_cpu_sys' => '0.08',
    +            'used_cpu_user' => '0.10',
    +            'used_cpu_sys_children' => '0.00',
    +            'used_cpu_user_children' => '0.00',
    +            'connected_clients' => '1',
    +            'connected_slaves' => '0',
    +            'client_longest_output_list' => '0',
    +            'client_biggest_input_buf' => '0',
    +            'blocked_clients' => '0',
    +            'used_memory' => '556156',
    +            'used_memory_human' => '543.12K',
    +            'used_memory_rss' => '1396736',
    +            'used_memory_peak' => '547688',
    +            'used_memory_peak_human' => '534.85K',
    +            'mem_fragmentation_ratio' => '2.51',
    +            'mem_allocator' => 'jemalloc-2.2.1',
    +            'loading' => '0',
    +            'aof_enabled' => '0',
    +            'changes_since_last_save' => '0',
    +            'bgsave_in_progress' => '0',
    +            'last_save_time' => '1323183872',
    +            'bgrewriteaof_in_progress' => '0',
    +            'total_connections_received' => '2',
    +            'total_commands_processed' => '1',
    +            'expired_keys' => '0',
    +            'evicted_keys' => '0',
    +            'keyspace_hits' => '0',
    +            'keyspace_misses' => '0',
    +            'pubsub_channels' => '0',
    +            'pubsub_patterns' => '0',
    +            'latest_fork_usec' => '0',
    +            'vm_enabled' => '0',
    +            'role' => 'master',
    +            'db0' => array('keys' => '2', 'expires' => '0'),
    +            'db5' => array('keys' => '1', 'expires' => '0'),
    +        );
    +
    +        $this->assertSame($expected, $this->getCommand()->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsAnArrayOfInfo()
    +    {
    +        $redis = $this->getClient();
    +        $command = $this->getCommand();
    +
    +        $this->assertInternalType('array', $info = $redis->executeCommand($command));
    +        $this->assertArrayHasKey('redis_version', isset($info['Server']) ? $info['Server'] : $info);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerLastSaveTest.php b/vendor/predis/predis/tests/Predis/Command/ServerLastSaveTest.php
    new file mode 100644
    index 0000000..15bedf0
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerLastSaveTest.php
    @@ -0,0 +1,66 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-server
    + */
    +class ServerLastSaveTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerLastSave';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'LASTSAVE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->setArguments(array());
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(100, $this->getCommand()->parseResponse(100));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsIntegerValue()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertInternalType('integer', $redis->lastsave());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerMonitorTest.php b/vendor/predis/predis/tests/Predis/Command/ServerMonitorTest.php
    new file mode 100644
    index 0000000..cab231c
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerMonitorTest.php
    @@ -0,0 +1,74 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-server
    + * @group realm-monitor
    + */
    +class ServerMonitorTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerMonitor';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'MONITOR';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->setArguments(array());
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsTrueAndReadsEventsFromTheConnection()
    +    {
    +        $connection = $this->getClient()->getConnection();
    +        $command = $this->getCommand();
    +
    +        $this->assertTrue($connection->executeCommand($command));
    +
    +        // NOTE: Starting with 2.6 Redis does not return the "MONITOR" message after
    +        // +OK to the client that issued the MONITOR command.
    +        if (version_compare($this->getProfile()->getVersion(), '2.4', '<=')) {
    +            $this->assertRegExp('/\d+.\d+(\s?\(db \d+\))? "MONITOR"/', $connection->read());
    +        }
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerObjectTest.php b/vendor/predis/predis/tests/Predis/Command/ServerObjectTest.php
    new file mode 100644
    index 0000000..aea730f
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerObjectTest.php
    @@ -0,0 +1,115 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-server
    + */
    +class ServerObjectTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerObject';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'OBJECT';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('REFCOUNT', 'key');
    +        $expected = array('REFCOUNT', 'key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame('ziplist', $this->getCommand()->parseResponse('ziplist'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testObjectRefcount()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $this->assertInternalType('integer', $redis->object('REFCOUNT', 'foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testObjectIdletime()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $this->assertInternalType('integer', $redis->object('IDLETIME', 'foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testObjectEncoding()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->lpush('list:metavars', 'foo', 'bar');
    +        $this->assertSame('ziplist', $redis->object('ENCODING', 'list:metavars'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsNullOnNonExistingKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertNull($redis->object('REFCOUNT', 'foo'));
    +        $this->assertNull($redis->object('IDLETIME', 'foo'));
    +        $this->assertNull($redis->object('ENCODING', 'foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     */
    +    public function testThrowsExceptionOnInvalidSubcommand()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->object('INVALID', 'foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerSaveTest.php b/vendor/predis/predis/tests/Predis/Command/ServerSaveTest.php
    new file mode 100644
    index 0000000..0be9647
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerSaveTest.php
    @@ -0,0 +1,56 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-server
    + */
    +class ServerSaveTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerSave';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SAVE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->setArguments(array());
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerScriptTest.php b/vendor/predis/predis/tests/Predis/Command/ServerScriptTest.php
    new file mode 100644
    index 0000000..9d69f5c
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerScriptTest.php
    @@ -0,0 +1,110 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-scripting
    + */
    +class ServerScriptTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerScript';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SCRIPT';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('EXISTS', '9d0c0826bde023cc39eebaaf832c32a890f3b088', 'ffffffffffffffffffffffffffffffffffffffff');
    +        $expected = array('EXISTS', '9d0c0826bde023cc39eebaaf832c32a890f3b088', 'ffffffffffffffffffffffffffffffffffffffff');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @todo We should probably convert integers to booleans.
    +     */
    +    public function testExistsReturnAnArrayOfValues()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->eval($lua = 'return true', 0);
    +        $sha1 = sha1($lua);
    +
    +        $this->assertSame(array(1, 0), $redis->script('EXISTS', $sha1, 'ffffffffffffffffffffffffffffffffffffffff'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testLoadReturnsHashOfScripts()
    +    {
    +        $redis = $this->getClient();
    +
    +        $lua = 'return true';
    +        $sha1 = sha1($lua);
    +
    +        $this->assertSame($sha1, $redis->script('LOAD', $lua));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testFlushesExistingScripts()
    +    {
    +        $redis = $this->getClient();
    +
    +        $sha1 = $redis->script('LOAD', 'return true');
    +
    +        $this->assertTrue($redis->script('FLUSH'));
    +        $this->assertSame(array(0), $redis->script('EXISTS', $sha1));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     */
    +    public function testThrowsExceptionOnInvalidSubcommand()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->script('INVALID');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerShutdownTest.php b/vendor/predis/predis/tests/Predis/Command/ServerShutdownTest.php
    new file mode 100644
    index 0000000..13f4fcf
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerShutdownTest.php
    @@ -0,0 +1,48 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-server
    + */
    +class ServerShutdownTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerShutdown';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SHUTDOWN';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->setArguments(array());
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerSlaveOfTest.php b/vendor/predis/predis/tests/Predis/Command/ServerSlaveOfTest.php
    new file mode 100644
    index 0000000..e65d1b7
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerSlaveOfTest.php
    @@ -0,0 +1,87 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-server
    + */
    +class ServerSlaveOfTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerSlaveOf';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SLAVEOF';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsHostPortArray()
    +    {
    +        $arguments = array('127.0.0.1', '80');
    +        $expected = array('127.0.0.1', '80');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsNoOneArray()
    +    {
    +        $arguments = array('NO', 'ONE');
    +        $expected = array('NO', 'ONE');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsNoOneString()
    +    {
    +        $arguments = array('NO ONE');
    +        $expected = array('NO', 'ONE');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerSlowlogTest.php b/vendor/predis/predis/tests/Predis/Command/ServerSlowlogTest.php
    new file mode 100644
    index 0000000..64ab949
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerSlowlogTest.php
    @@ -0,0 +1,120 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * In order to support the output of SLOWLOG, the backend connection
    + * must be able to parse nested multibulk replies deeper than 2 levels.
    + *
    + * @group commands
    + * @group realm-server
    + */
    +class ServerSlowlogTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerSlowlog';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SLOWLOG';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('GET', '2');
    +        $expected = array('GET', '2');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array(array(0, 1323163469, 12451, array('SORT', 'list:unordered')));
    +        $expected = array(
    +            array(
    +                'id' => 0,
    +                'timestamp' => 1323163469,
    +                'duration' => 12451,
    +                'command' => array('SORT', 'list:unordered'),
    +            ),
    +        );
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsAnArrayOfLoggedCommands()
    +    {
    +        $redis = $this->getClient();
    +
    +        $config = $redis->config('get', 'slowlog-log-slower-than');
    +        $threshold = array_pop($config);
    +
    +        $redis->config('set', 'slowlog-log-slower-than', 0);
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertInternalType('array', $slowlog = $redis->slowlog('GET'));
    +        $this->assertGreaterThan(0, count($slowlog));
    +
    +        $this->assertInternalType('array', $slowlog[0]);
    +        $this->assertGreaterThan(0, $slowlog[0]['id']);
    +        $this->assertGreaterThan(0, $slowlog[0]['timestamp']);
    +        $this->assertGreaterThan(0, $slowlog[0]['duration']);
    +        $this->assertInternalType('array', $slowlog[0]['command']);
    +
    +        $redis->config('set', 'slowlog-log-slower-than', $threshold);
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanResetTheLog()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertTrue($redis->slowlog('RESET'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     */
    +    public function testThrowsExceptionOnInvalidSubcommand()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->slowlog('INVALID');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ServerTimeTest.php b/vendor/predis/predis/tests/Predis/Command/ServerTimeTest.php
    new file mode 100644
    index 0000000..19e313d
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ServerTimeTest.php
    @@ -0,0 +1,74 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-server
    + */
    +class ServerTimeTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ServerTime';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'TIME';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array();
    +        $expected = array();
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $expected = array(1331114908, 453990);
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($expected));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsServerTime()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertInternalType('array', $time = $redis->time());
    +        $this->assertInternalType('string', $time[0]);
    +        $this->assertInternalType('string', $time[1]);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/SetAddTest.php b/vendor/predis/predis/tests/Predis/Command/SetAddTest.php
    new file mode 100644
    index 0000000..6452cb9
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/SetAddTest.php
    @@ -0,0 +1,123 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-set
    + */
    +class SetAddTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\SetAdd';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SADD';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'member1', 'member2', 'member3');
    +        $expected = array('key', 'member1', 'member2', 'member3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsValuesAsSingleArray()
    +    {
    +        $arguments = array('key', array('member1', 'member2', 'member3'));
    +        $expected = array('key', 'member1', 'member2', 'member3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'member1', 'member2', 'member3');
    +        $expected = array('prefix:key', 'member1', 'member2', 'member3');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testAddsMembersToSet()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(1, $redis->sadd('letters', 'a'));
    +        $this->assertSame(2, $redis->sadd('letters', 'b', 'c'));
    +        $this->assertSame(0, $redis->sadd('letters', 'b'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('metavars', 'foo');
    +        $redis->sadd('metavars', 'hoge');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/SetCardinalityTest.php b/vendor/predis/predis/tests/Predis/Command/SetCardinalityTest.php
    new file mode 100644
    index 0000000..5a80626
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/SetCardinalityTest.php
    @@ -0,0 +1,119 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-set
    + */
    +class SetCardinalityTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\SetCardinality';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SCARD';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsNumberOfMembers()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters', 'a', 'b', 'c', 'd');
    +
    +        $this->assertSame(4, $redis->scard('letters'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsZeroOnEmptySet()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(0, $redis->scard('letters'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('metavars', 'foo');
    +        $redis->scard('metavars');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/SetDifferenceStoreTest.php b/vendor/predis/predis/tests/Predis/Command/SetDifferenceStoreTest.php
    new file mode 100644
    index 0000000..b575f38
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/SetDifferenceStoreTest.php
    @@ -0,0 +1,142 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-set
    + */
    +class SetDifferenceStoreTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\SetDifferenceStore';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SDIFFSTORE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key:destination', 'key:source1', 'key:source:2');
    +        $expected = array('key:destination', 'key:source1', 'key:source:2');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsSourceKeysAsSingleArray()
    +    {
    +        $arguments = array('key:destination', array('key:source1', 'key:source:2'));
    +        $expected = array('key:destination', 'key:source1', 'key:source:2');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key:destination', 'key:source1', 'key:source:2');
    +        $expected = array('prefix:key:destination', 'prefix:key:source1', 'prefix:key:source:2');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testStoresMembersOfSetOnSingleSet()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters:1st', 'a', 'b', 'c', 'd', 'e', 'f', 'g');
    +
    +        $this->assertSame(7, $redis->sdiffstore('letters:destination', 'letters:1st'));
    +        $this->assertSameValues(array( 'a', 'b', 'c', 'd', 'e', 'f', 'g'), $redis->smembers('letters:destination'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testStoresDifferenceOfMultipleSets()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters:1st', 'a', 'b', 'c', 'd', 'e', 'f', 'g');
    +        $redis->sadd('letters:2nd', 'a', 'c', 'f', 'g');
    +        $redis->sadd('letters:3rd', 'a', 'b', 'e', 'f');
    +
    +        $this->assertSame(3, $redis->sdiffstore('letters:destination', 'letters:1st', 'letters:2nd'));
    +        $this->assertSameValues(array('b', 'd', 'e'), $redis->smembers('letters:destination'));
    +
    +        $this->assertSame(1, $redis->sdiffstore('letters:destination', 'letters:1st', 'letters:2nd', 'letters:3rd'));
    +        $this->assertSameValues(array('d'), $redis->smembers('letters:destination'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongTypeOfSourceKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('set:source', 'foo');
    +        $redis->sdiffstore('set:destination', 'set:source');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/SetDifferenceTest.php b/vendor/predis/predis/tests/Predis/Command/SetDifferenceTest.php
    new file mode 100644
    index 0000000..e8318f1
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/SetDifferenceTest.php
    @@ -0,0 +1,144 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-set
    + */
    +class SetDifferenceTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\SetDifference';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SDIFF';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key1', 'key2', 'key3');
    +        $expected = array('key1', 'key2', 'key3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsAsSingleArray()
    +    {
    +        $arguments = array(array('key1', 'key2', 'key3'));
    +        $expected = array('key1', 'key2', 'key3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('member1', 'member2', 'member3');
    +        $expected = array('member1', 'member2', 'member3');
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key1', 'key2', 'key3');
    +        $expected = array('prefix:key1', 'prefix:key2', 'prefix:key3');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsMembersOnSingleKeyOrNonExistingSetForDifference()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters:1st', 'a', 'b', 'c', 'd', 'e', 'f', 'g');
    +
    +        $this->assertSameValues(array( 'a', 'b', 'c', 'd', 'e', 'f', 'g'), $redis->sdiff('letters:1st'));
    +        $this->assertSameValues(array( 'a', 'b', 'c', 'd', 'e', 'f', 'g'), $redis->sdiff('letters:1st', 'letters:2nd'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsMembersFromDifferenceAmongSets()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters:1st', 'a', 'b', 'c', 'd', 'e', 'f', 'g');
    +        $redis->sadd('letters:2nd', 'a', 'c', 'f', 'g');
    +        $redis->sadd('letters:3rd', 'a', 'b', 'e', 'f');
    +
    +        $this->assertSameValues(array('b', 'd', 'e'), $redis->sdiff('letters:1st', 'letters:2nd'));
    +        $this->assertSameValues(array('d'), $redis->sdiff('letters:1st', 'letters:2nd', 'letters:3rd'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('set:foo', 'a');
    +        $redis->sdiff('set:foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/SetIntersectionStoreTest.php b/vendor/predis/predis/tests/Predis/Command/SetIntersectionStoreTest.php
    new file mode 100644
    index 0000000..cd68398
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/SetIntersectionStoreTest.php
    @@ -0,0 +1,155 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-set
    + */
    +class SetIntersectionStoreTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\SetIntersectionStore';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SINTERSTORE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key:destination', 'key:source1', 'key:source:2');
    +        $expected = array('key:destination', 'key:source1', 'key:source:2');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsSourceKeysAsSingleArray()
    +    {
    +        $arguments = array('key:destination', array('key:source1', 'key:source:2'));
    +        $expected = array('key:destination', 'key:source1', 'key:source:2');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key:destination', 'key:source1', 'key:source:2');
    +        $expected = array('prefix:key:destination', 'prefix:key:source1', 'prefix:key:source:2');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testStoresMembersOfSetOnSingleKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters:1st', 'a', 'b', 'c', 'd', 'e', 'f', 'g');
    +
    +        $this->assertSame(7, $redis->sinterstore('letters:destination', 'letters:1st'));
    +        $this->assertSameValues(array( 'a', 'b', 'c', 'd', 'e', 'f', 'g'), $redis->smembers('letters:destination'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testDoesNotStoreOnNonExistingSetForIntersection()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters:1st', 'a', 'b', 'c', 'd', 'e', 'f', 'g');
    +
    +        $this->assertSame(0, $redis->sinterstore('letters:destination', 'letters:1st', 'letters:2nd'));
    +        $this->assertFalse($redis->exists('letters:destination'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testStoresIntersectionOfMultipleSets()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters:1st', 'a', 'b', 'c', 'd', 'e', 'f', 'g');
    +        $redis->sadd('letters:2nd', 'a', 'c', 'f', 'g');
    +        $redis->sadd('letters:3rd', 'a', 'b', 'e', 'f');
    +
    +        $this->assertSame(4, $redis->sinterstore('letters:destination', 'letters:1st', 'letters:2nd'));
    +        $this->assertSameValues(array('a', 'c', 'f', 'g'), $redis->smembers('letters:destination'));
    +
    +        $this->assertSame(2, $redis->sinterstore('letters:destination', 'letters:1st', 'letters:2nd', 'letters:3rd'));
    +        $this->assertSameValues(array('a', 'f'), $redis->smembers('letters:destination'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongTypeOfSourceKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('set:source', 'foo');
    +        $redis->sinterstore('set:destination', 'set:source');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/SetIntersectionTest.php b/vendor/predis/predis/tests/Predis/Command/SetIntersectionTest.php
    new file mode 100644
    index 0000000..f9f4c16
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/SetIntersectionTest.php
    @@ -0,0 +1,155 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-set
    + */
    +class SetIntersectionTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\SetIntersection';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SINTER';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key1', 'key2', 'key3');
    +        $expected = array('key1', 'key2', 'key3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsAsSingleArray()
    +    {
    +        $arguments = array(array('key1', 'key2', 'key3'));
    +        $expected = array('key1', 'key2', 'key3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('member1', 'member2', 'member3');
    +        $expected = array('member1', 'member2', 'member3');
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key1', 'key2', 'key3');
    +        $expected = array('prefix:key1', 'prefix:key2', 'prefix:key3');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsMembersOfSetOnSingleKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters:1st', 'a', 'b', 'c', 'd', 'e', 'f', 'g');
    +
    +        $this->assertSameValues(array('a', 'b', 'c', 'd', 'e', 'f', 'g'), $redis->sinter('letters:1st'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsEmptyArrayOnNonExistingSetForIntersection()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters:1st', 'a', 'b', 'c', 'd', 'e', 'f', 'g');
    +
    +        $this->assertSameValues(array(), $redis->sinter('letters:1st', 'letters:2nd'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsMembersFromIntersectionAmongSets()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters:1st', 'a', 'b', 'c', 'd', 'e', 'f', 'g');
    +        $redis->sadd('letters:2nd', 'a', 'c', 'f', 'g');
    +        $redis->sadd('letters:3rd', 'a', 'b', 'e', 'f');
    +
    +        $this->assertSameValues(array('a', 'c', 'f', 'g'), $redis->sinter('letters:1st', 'letters:2nd'));
    +        $this->assertSameValues(array('a', 'f'), $redis->sinter('letters:1st', 'letters:2nd', 'letters:3rd'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('set:foo', 'a');
    +        $redis->sinter('set:foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/SetIsMemberTest.php b/vendor/predis/predis/tests/Predis/Command/SetIsMemberTest.php
    new file mode 100644
    index 0000000..30c5e0a
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/SetIsMemberTest.php
    @@ -0,0 +1,123 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-set
    + */
    +class SetIsMemberTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\SetIsMember';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SISMEMBER';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'member');
    +        $expected = array('key', 'member');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertTrue($command->parseResponse(1));
    +        $this->assertFalse($command->parseResponse(0));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'member');
    +        $expected = array('prefix:key', 'member');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsMemberExistenceInSet()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters', 'a', 'b', 'c');
    +
    +        $this->assertTrue($redis->sismember('letters', 'a'));
    +        $this->assertFalse($redis->sismember('letters', 'z'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsFalseOnNonExistingSet()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertFalse($redis->sismember('letters', 'a'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->sismember('foo', 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/SetMembersTest.php b/vendor/predis/predis/tests/Predis/Command/SetMembersTest.php
    new file mode 100644
    index 0000000..2d8cc4b
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/SetMembersTest.php
    @@ -0,0 +1,115 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-set
    + */
    +class SetMembersTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\SetMembers';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SMEMBERS';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('member1', 'member2', 'member3');
    +        $expected = array('member1', 'member2', 'member3');
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsFalseOnNonExistingSet()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters', 'a', 'b', 'c', 'd', 'e');
    +
    +        $this->assertSameValues(array('a', 'b', 'c', 'd', 'e'), $redis->smembers('letters'));
    +        $this->assertSame(array(), $redis->smembers('digits'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->smembers('foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/SetMoveTest.php b/vendor/predis/predis/tests/Predis/Command/SetMoveTest.php
    new file mode 100644
    index 0000000..dda704d
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/SetMoveTest.php
    @@ -0,0 +1,131 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-set
    + */
    +class SetMoveTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\SetMove';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SMOVE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key:source', 'key:destination', 'member');
    +        $expected = array('key:source', 'key:destination', 'member');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +
    +        $this->assertTrue($command->parseResponse(1));
    +        $this->assertFalse($command->parseResponse(0));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key:source', 'key:destination', 'member');
    +        $expected = array('prefix:key:source', 'prefix:key:destination', 'member');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsMemberExistenceInSet()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters:source', 'a', 'b', 'c');
    +
    +        $this->assertTrue($redis->smove('letters:source', 'letters:destination', 'b'));
    +        $this->assertFalse($redis->smove('letters:source', 'letters:destination', 'z'));
    +
    +        $this->assertSameValues(array('a', 'c'), $redis->smembers('letters:source'));
    +        $this->assertSameValues(array('b'), $redis->smembers('letters:destination'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongTypeOfSourceKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('set:source', 'foo');
    +        $redis->sadd('set:destination', 'bar');
    +        $redis->smove('set:destination', 'set:source', 'foo');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongTypeOfDestinationKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('set:source', 'foo');
    +        $redis->set('set:destination', 'bar');
    +        $redis->smove('set:destination', 'set:source', 'foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/SetPopTest.php b/vendor/predis/predis/tests/Predis/Command/SetPopTest.php
    new file mode 100644
    index 0000000..a72bbf0
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/SetPopTest.php
    @@ -0,0 +1,112 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-set
    + */
    +class SetPopTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\SetPop';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SPOP';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame('member', $this->getCommand()->parseResponse('member'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testPopsRandomMemberFromSet()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters', 'a', 'b');
    +
    +        $this->assertContains($redis->spop('letters'), array('a', 'b'));
    +        $this->assertContains($redis->spop('letters'), array('a', 'b'));
    +
    +        $this->assertNull($redis->spop('letters'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->spop('foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/SetRandomMemberTest.php b/vendor/predis/predis/tests/Predis/Command/SetRandomMemberTest.php
    new file mode 100644
    index 0000000..7bc408e
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/SetRandomMemberTest.php
    @@ -0,0 +1,120 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-set
    + */
    +class SetRandomMemberTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\SetRandomMember';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SRANDMEMBER';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame('member', $this->getCommand()->parseResponse('member'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsRandomMemberFromSet()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters', 'a', 'b');
    +
    +        $this->assertContains($redis->srandmember('letters'), array('a', 'b'));
    +        $this->assertContains($redis->srandmember('letters'), array('a', 'b'));
    +
    +        $this->assertSame(2, $redis->scard('letters'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsNullOnNonExistingSet()
    +    {
    +        $this->assertNull($this->getClient()->srandmember('letters'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->srandmember('foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/SetRemoveTest.php b/vendor/predis/predis/tests/Predis/Command/SetRemoveTest.php
    new file mode 100644
    index 0000000..ed6e20c
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/SetRemoveTest.php
    @@ -0,0 +1,127 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-set
    + */
    +class SetRemoveTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\SetRemove';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SREM';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'member1', 'member2', 'member3');
    +        $expected = array('key', 'member1', 'member2', 'member3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsMembersAsSingleArray()
    +    {
    +        $arguments = array('key', array('member1', 'member2', 'member3'));
    +        $expected = array('key', 'member1', 'member2', 'member3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'member1', 'member2', 'member3');
    +        $expected = array('prefix:key', 'member1', 'member2', 'member3');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRemovesMembersFromSet()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters', 'a', 'b', 'c', 'd');
    +
    +        $this->assertSame(1, $redis->srem('letters', 'b'));
    +        $this->assertSame(1, $redis->srem('letters', 'd', 'z'));
    +        $this->assertSameValues(array('a', 'c'), $redis->smembers('letters'));
    +
    +        $this->assertSame(0, $redis->srem('digits', 1));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->srem('foo', 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/SetUnionStoreTest.php b/vendor/predis/predis/tests/Predis/Command/SetUnionStoreTest.php
    new file mode 100644
    index 0000000..9c76d10
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/SetUnionStoreTest.php
    @@ -0,0 +1,142 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-set
    + */
    +class SetUnionStoreTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\SetUnionStore';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SUNIONSTORE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key:destination', 'key:source1', 'key:source:2');
    +        $expected = array('key:destination', 'key:source1', 'key:source:2');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsSourceKeysAsSingleArray()
    +    {
    +        $arguments = array('key:destination', array('key:source1', 'key:source:2'));
    +        $expected = array('key:destination', 'key:source1', 'key:source:2');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key:destination', 'key:source1', 'key:source:2');
    +        $expected = array('prefix:key:destination', 'prefix:key:source1', 'prefix:key:source:2');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testStoresMembersOfSetOnSingleSet()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters:1st', 'a', 'b', 'c', 'd', 'e', 'f', 'g');
    +
    +        $this->assertSame(7, $redis->sunionstore('letters:destination', 'letters:1st'));
    +        $this->assertSameValues(array( 'a', 'b', 'c', 'd', 'e', 'f', 'g'), $redis->smembers('letters:destination'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testStoresUnionOfMultipleSets()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters:1st', 'b', 'd', 'f');
    +        $redis->sadd('letters:2nd', 'a', 'c', 'g');
    +        $redis->sadd('letters:3rd', 'a', 'e', 'f');
    +
    +        $this->assertSame(5, $redis->sunionstore('letters:destination', 'letters:2nd', 'letters:3rd'));
    +        $this->assertSameValues(array('a', 'c', 'e', 'f', 'g'), $redis->smembers('letters:destination'));
    +
    +        $this->assertSame(7, $redis->sunionstore('letters:destination', 'letters:1st', 'letters:2nd', 'letters:3rd'));
    +        $this->assertSameValues(array( 'a', 'b', 'c', 'd', 'e', 'f', 'g'), $redis->smembers('letters:destination'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongTypeOfSourceKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('set:source', 'foo');
    +        $redis->sunionstore('set:destination', 'set:source');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/SetUnionTest.php b/vendor/predis/predis/tests/Predis/Command/SetUnionTest.php
    new file mode 100644
    index 0000000..5f93634
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/SetUnionTest.php
    @@ -0,0 +1,144 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-set
    + */
    +class SetUnionTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\SetUnion';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SUNION';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key1', 'key2', 'key3');
    +        $expected = array('key1', 'key2', 'key3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsAsSingleArray()
    +    {
    +        $arguments = array(array('key1', 'key2', 'key3'));
    +        $expected = array('key1', 'key2', 'key3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('member1', 'member2', 'member3');
    +        $expected = array('member1', 'member2', 'member3');
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key1', 'key2', 'key3');
    +        $expected = array('prefix:key1', 'prefix:key2', 'prefix:key3');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsMembersOnSingleKeyOrNonExistingSetForUnion()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters:1st', 'a', 'b', 'c', 'd', 'e', 'f', 'g');
    +
    +        $this->assertSameValues(array( 'a', 'b', 'c', 'd', 'e', 'f', 'g'), $redis->sunion('letters:1st'));
    +        $this->assertSameValues(array( 'a', 'b', 'c', 'd', 'e', 'f', 'g'), $redis->sunion('letters:1st', 'letters:2nd'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsMembersFromDifferenceAmongSets()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->sadd('letters:1st', 'b', 'd', 'f');
    +        $redis->sadd('letters:2nd', 'a', 'c', 'g');
    +        $redis->sadd('letters:3rd', 'a', 'e', 'f');
    +
    +        $this->assertSameValues(array('a', 'c', 'e', 'f', 'g'), $redis->sunion('letters:2nd', 'letters:3rd'));
    +        $this->assertSameValues(array( 'a', 'b', 'c', 'd', 'e', 'f', 'g'), $redis->sunion('letters:1st', 'letters:2nd', 'letters:3rd'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('set:foo', 'a');
    +        $redis->sunion('set:foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringAppendTest.php b/vendor/predis/predis/tests/Predis/Command/StringAppendTest.php
    new file mode 100644
    index 0000000..847981c
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringAppendTest.php
    @@ -0,0 +1,122 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringAppendTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringAppend';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'APPEND';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'value');
    +        $expected = array('key', 'value');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(10, $this->getCommand()->parseResponse(10));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'value');
    +        $expected = array('prefix:key', 'value');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCreatesNewKeyOnNonExistingKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(3, $redis->append('foo', 'bar'));
    +        $this->assertSame('bar', $redis->get('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsTheLenghtOfTheStringAfterAppend()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertSame(5, $redis->append('foo', '__'));
    +        $this->assertSame(8, $redis->append('foo', 'bar'));
    +        $this->assertSame('bar__bar', $redis->get('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->lpush('metavars', 'foo');
    +        $redis->append('metavars', 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringBitCountTest.php b/vendor/predis/predis/tests/Predis/Command/StringBitCountTest.php
    new file mode 100644
    index 0000000..82ac0da
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringBitCountTest.php
    @@ -0,0 +1,119 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringBitCountTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringBitCount';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'BITCOUNT';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 0, 10);
    +        $expected = array('key', 0, 10);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = 10;
    +        $expected = 10;
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 0, 10);
    +        $expected = array('prefix:key', 0, 10);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsNumberOfBitsSet()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->setbit('key', 1, 1);
    +        $redis->setbit('key', 10, 1);
    +        $redis->setbit('key', 16, 1);
    +        $redis->setbit('key', 22, 1);
    +        $redis->setbit('key', 32, 1);
    +
    +        $this->assertSame(5, $redis->bitcount('key'), 'Count bits set (without range)');
    +        $this->assertSame(3, $redis->bitcount('key', 2, 4), 'Count bits set (with range)');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->lpush('key', 'list');
    +        $redis->bitcount('key');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringBitOpTest.php b/vendor/predis/predis/tests/Predis/Command/StringBitOpTest.php
    new file mode 100644
    index 0000000..dacf4b4
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringBitOpTest.php
    @@ -0,0 +1,204 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringBitOpTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringBitOp';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'BITOP';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('AND', 'key:dst', 'key:01', 'key:02');
    +        $expected = array('AND', 'key:dst', 'key:01', 'key:02');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsKeysAsSingleArray()
    +    {
    +        $arguments = array('AND', 'key:dst', array('key:01', 'key:02'));
    +        $expected = array('AND', 'key:dst', 'key:01', 'key:02');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = 10;
    +        $expected = 10;
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('AND', 'key:dst', 'key:01', 'key:02');
    +        $expected = array('AND', 'prefix:key:dst', 'prefix:key:01', 'prefix:key:02');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanPerformBitwiseAND()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('key:src:1', "h\x80");
    +        $redis->set('key:src:2', "R");
    +
    +        $this->assertSame(2, $redis->bitop('AND', 'key:dst', 'key:src:1', 'key:src:2'));
    +        $this->assertSame("@\x00", $redis->get('key:dst'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanPerformBitwiseOR()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('key:src:1', "h\x80");
    +        $redis->set('key:src:2', "R");
    +
    +        $this->assertSame(2, $redis->bitop('OR', 'key:dst', 'key:src:1', 'key:src:2'));
    +        $this->assertSame("z\x80", $redis->get('key:dst'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanPerformBitwiseXOR()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('key:src:1', "h\x80");
    +        $redis->set('key:src:2', "R");
    +
    +        $this->assertSame(2, $redis->bitop('XOR', 'key:dst', 'key:src:1', 'key:src:2'));
    +        $this->assertSame(":\x80", $redis->get('key:dst'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanPerformBitwiseNOT()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('key:src:1', "h\x80");
    +
    +        $this->assertSame(2, $redis->bitop('NOT', 'key:dst', 'key:src:1'));
    +        $this->assertSame("\x97\x7f", $redis->get('key:dst'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR BITOP NOT must be called with a single source key.
    +     */
    +    public function testBitwiseNOTAcceptsOnlyOneSourceKey()
    +    {
    +        $this->getClient()->bitop('NOT', 'key:dst', 'key:src:1', 'key:src:2');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR syntax error
    +     */
    +    public function testThrowsExceptionOnInvalidOperation()
    +    {
    +        $this->getClient()->bitop('NOOP', 'key:dst', 'key:src:1', 'key:src:2');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnInvalidSourceKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->lpush('key:src:1', 'list');
    +        $redis->bitop('AND', 'key:dst', 'key:src:1', 'key:src:2');
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testDoesNotThrowExceptionOnInvalidDestinationKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->lpush('key:dst', 'list');
    +        $redis->bitop('AND', 'key:dst', 'key:src:1', 'key:src:2');
    +
    +        $this->assertSame('none', $redis->type('key:dst'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringDecrementByTest.php b/vendor/predis/predis/tests/Predis/Command/StringDecrementByTest.php
    new file mode 100644
    index 0000000..a21c969
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringDecrementByTest.php
    @@ -0,0 +1,147 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringDecrementByTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringDecrementBy';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'DECRBY';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 5);
    +        $expected = array('key', 5);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(5, $this->getCommand()->parseResponse(5));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 5);
    +        $expected = array('prefix:key', 5);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCreatesNewKeyOnNonExistingKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(-10, $redis->decrby('foo', 10));
    +        $this->assertEquals(-10, $redis->get('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsTheValueOfTheKeyAfterDecrement()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 10);
    +
    +        $this->assertSame(6, $redis->decrby('foo', 4));
    +        $this->assertSame(0, $redis->decrby('foo', 6));
    +        $this->assertSame(-25, $redis->decrby('foo', 25));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR value is not an integer or out of range
    +     */
    +    public function testThrowsExceptionOnDecrementValueNotInteger()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->decrby('foo', 'bar');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR value is not an integer or out of range
    +     */
    +    public function testThrowsExceptionOnKeyValueNotInteger()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->decrby('foo', 5);
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->lpush('metavars', 'foo');
    +        $redis->decrby('metavars', 10);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringDecrementTest.php b/vendor/predis/predis/tests/Predis/Command/StringDecrementTest.php
    new file mode 100644
    index 0000000..080be9f
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringDecrementTest.php
    @@ -0,0 +1,134 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringDecrementTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringDecrement';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'DECR';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(5, $this->getCommand()->parseResponse(5));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCreatesNewKeyOnNonExistingKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(-1, $redis->decr('foo'));
    +        $this->assertEquals(-1, $redis->get('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsTheValueOfTheKeyAfterDecrement()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 1);
    +
    +        $this->assertSame(0, $redis->decr('foo'));
    +        $this->assertSame(-1, $redis->decr('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR value is not an integer or out of range
    +     */
    +    public function testThrowsExceptionOnKeyValueNotInteger()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->decr('foo');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->lpush('metavars', 'foo');
    +        $redis->decr('metavars');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringGetBitTest.php b/vendor/predis/predis/tests/Predis/Command/StringGetBitTest.php
    new file mode 100644
    index 0000000..e945d26
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringGetBitTest.php
    @@ -0,0 +1,140 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringGetBitTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringGetBit';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'GETBIT';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 100);
    +        $expected = array('key', 100);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +        $this->assertSame(0, $command->parseResponse(0));
    +        $this->assertSame(1, $command->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 100);
    +        $expected = array('prefix:key', 100);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanGetBitsFromString()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('key:binary', "\x80\x00\00\x01");
    +
    +        $this->assertSame(1, $redis->getbit('key:binary', 0));
    +        $this->assertSame(0, $redis->getbit('key:binary', 15));
    +        $this->assertSame(1, $redis->getbit('key:binary', 31));
    +        $this->assertSame(0, $redis->getbit('key:binary', 63));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR bit offset is not an integer or out of range
    +     */
    +    public function testThrowsExceptionOnNegativeOffset()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('key:binary', "\x80\x00\00\x01");
    +        $redis->getbit('key:binary', -1);
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR bit offset is not an integer or out of range
    +     */
    +    public function testThrowsExceptionOnInvalidOffset()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('key:binary', "\x80\x00\00\x01");
    +        $redis->getbit('key:binary', 'invalid');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->lpush('metavars', 'foo');
    +        $redis->getbit('metavars', '1');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringGetMultipleTest.php b/vendor/predis/predis/tests/Predis/Command/StringGetMultipleTest.php
    new file mode 100644
    index 0000000..f1d7a88
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringGetMultipleTest.php
    @@ -0,0 +1,137 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringGetMultipleTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringGetMultiple';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'MGET';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key1', 'key2', 'key3');
    +        $expected = array('key1', 'key2', 'key3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsAsSingleArray()
    +    {
    +        $arguments = array(array('key1', 'key2', 'key3'));
    +        $expected = array('key1', 'key2', 'key3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('value1', 'value2', 'value3');
    +        $expected = array('value1', 'value2', 'value3');
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key1', 'key2', 'key3');
    +        $expected = array('prefix:key1', 'prefix:key2', 'prefix:key3');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsArrayOfValues()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->set('hoge', 'piyo');
    +
    +        $this->assertSame(array('bar', 'piyo'), $redis->mget('foo', 'hoge'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsArrayWithNullValuesOnNonExistingKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(array(null, null), $redis->mget('foo', 'hoge'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testDoesNotThrowExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->lpush('metavars', 'foo');
    +        $this->assertSame(array(null), $redis->mget('metavars'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringGetRangeTest.php b/vendor/predis/predis/tests/Predis/Command/StringGetRangeTest.php
    new file mode 100644
    index 0000000..3f0f74c
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringGetRangeTest.php
    @@ -0,0 +1,125 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringGetRangeTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringGetRange';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'GETRANGE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 5, 10);
    +        $expected = array('key', 5, 10);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame('substring',$this->getCommand()->parseResponse('substring'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 5, 10);
    +        $expected = array('prefix:key', 5, 10);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsSubstring()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('string', 'this is a string');
    +
    +        $this->assertSame('this', $redis->getrange('string', 0, 3));
    +        $this->assertSame('ing', $redis->getrange('string', -3, -1));
    +        $this->assertSame('this is a string', $redis->getrange('string', 0, -1));
    +        $this->assertSame('string', $redis->getrange('string', 10, 100));
    +
    +        $this->assertSame('t', $redis->getrange('string', 0, 0));
    +        $this->assertSame('', $redis->getrange('string', -1, 0));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsEmptyStringOnNonExistingKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame('', $redis->getrange('string', 0, 3));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->lpush('metavars', 'foo');
    +        $redis->getrange('metavars', 0, 5);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringGetSetTest.php b/vendor/predis/predis/tests/Predis/Command/StringGetSetTest.php
    new file mode 100644
    index 0000000..d151fda
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringGetSetTest.php
    @@ -0,0 +1,111 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringGetSetTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringGetSet';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'GETSET';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'value');
    +        $expected = array('key', 'value');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame('value', $this->getCommand()->parseResponse('value'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'value');
    +        $expected = array('prefix:key', 'value');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsPreviousValueOfKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertNull($redis->getset('foo', 'bar'));
    +        $this->assertSame('bar', $redis->getset('foo', 'barbar'));
    +
    +        $redis->set('hoge', 'piyo');
    +        $this->assertSame('piyo', $redis->getset('hoge', 'piyopiyo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->lpush('metavars', 'foo');
    +        $redis->getset('metavars', 'foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringGetTest.php b/vendor/predis/predis/tests/Predis/Command/StringGetTest.php
    new file mode 100644
    index 0000000..7233e22
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringGetTest.php
    @@ -0,0 +1,132 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringGetTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringGet';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'GET';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('foo');
    +        $expected = array('foo');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame('bar', $this->getCommand()->parseResponse('bar'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsStringValue()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertTrue($redis->set('foo', 'bar'));
    +        $this->assertEquals('bar', $redis->get('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsEmptyStringOnEmptyStrings()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', '');
    +
    +        $this->assertTrue($redis->exists('foo'));
    +        $this->assertSame('', $redis->get('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsNullOnNonExistingKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertFalse($redis->exists('foo'));
    +        $this->assertNull($redis->get('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->rpush('metavars', 'foo');
    +        $redis->get('metavars');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringIncrementByFloatTest.php b/vendor/predis/predis/tests/Predis/Command/StringIncrementByFloatTest.php
    new file mode 100644
    index 0000000..f3d0dc7
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringIncrementByFloatTest.php
    @@ -0,0 +1,147 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringIncrementByFloatTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringIncrementByFloat';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'INCRBYFLOAT';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 5.0);
    +        $expected = array('key', 5.0);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(5.0, $this->getCommand()->parseResponse(5.0));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 5.0);
    +        $expected = array('prefix:key', 5.0);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCreatesNewKeyOnNonExistingKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertEquals(10.5, $redis->incrbyfloat('foo', 10.5));
    +        $this->assertEquals(10.5, $redis->get('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsTheValueOfTheKeyAfterIncrement()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 2);
    +
    +        $this->assertEquals(22.123, $redis->incrbyfloat('foo', 20.123));
    +        $this->assertEquals(10, $redis->incrbyfloat('foo', -12.123));
    +        $this->assertEquals(-100.01, $redis->incrbyfloat('foo', -110.01));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR value is not a valid float
    +     */
    +    public function testThrowsExceptionOnDecrementValueNotFloat()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->incrbyfloat('foo', 'bar');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR value is not a valid float
    +     */
    +    public function testThrowsExceptionOnKeyValueNotFloat()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->incrbyfloat('foo', 10.0);
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->lpush('metavars', 'foo');
    +        $redis->incrbyfloat('metavars', 10.0);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringIncrementByTest.php b/vendor/predis/predis/tests/Predis/Command/StringIncrementByTest.php
    new file mode 100644
    index 0000000..711d377
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringIncrementByTest.php
    @@ -0,0 +1,147 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringIncrementByTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringIncrementBy';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'INCRBY';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 5);
    +        $expected = array('key', 5);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(5, $this->getCommand()->parseResponse(5));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 5);
    +        $expected = array('prefix:key', 5);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCreatesNewKeyOnNonExistingKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(10, $redis->incrby('foo', 10));
    +        $this->assertEquals(10, $redis->get('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsTheValueOfTheKeyAfterIncrement()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 2);
    +
    +        $this->assertSame(22, $redis->incrby('foo', 20));
    +        $this->assertSame(10, $redis->incrby('foo', -12));
    +        $this->assertSame(-100, $redis->incrby('foo', -110));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR value is not an integer or out of range
    +     */
    +    public function testThrowsExceptionOnDecrementValueNotInteger()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->incrby('foo', 'bar');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR value is not an integer or out of range
    +     */
    +    public function testThrowsExceptionOnKeyValueNotInteger()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->incrby('foo', 10);
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->lpush('metavars', 'foo');
    +        $redis->incrby('metavars', 10);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringIncrementTest.php b/vendor/predis/predis/tests/Predis/Command/StringIncrementTest.php
    new file mode 100644
    index 0000000..6e2a1fb
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringIncrementTest.php
    @@ -0,0 +1,121 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringIncrementTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringIncrement';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'INCR';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(5, $this->getCommand()->parseResponse(5));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCreatesNewKeyOnNonExistingKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(1, $redis->incr('foo'));
    +        $this->assertEquals(1, $redis->get('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsTheValueOfTheKeyAfterIncrement()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 2);
    +
    +        $this->assertSame(3, $redis->incr('foo'));
    +        $this->assertSame(4, $redis->incr('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->lpush('metavars', 'foo');
    +        $redis->incr('metavars');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringPreciseSetExpireTest.php b/vendor/predis/predis/tests/Predis/Command/StringPreciseSetExpireTest.php
    new file mode 100644
    index 0000000..5ce521f
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringPreciseSetExpireTest.php
    @@ -0,0 +1,141 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringPreciseSetExpireTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringPreciseSetExpire';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'PSETEX';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 10, 'hello');
    +        $expected = array('key', 10, 'hello');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 10, 'hello');
    +        $expected = array('prefix:key', 10, 'hello');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCreatesNewKeyAndSetsTTL()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertTrue($redis->psetex('foo', 10000, 'bar'));
    +        $this->assertTrue($redis->exists('foo'));
    +        $this->assertEquals(10, $redis->ttl('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @group slow
    +     */
    +    public function testKeyExpiresAfterTTL()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->psetex('foo', 50, 'bar');
    +        $this->sleep(0.5);
    +        $this->assertFalse($redis->exists('foo'));;
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR value is not an integer or out of range
    +     */
    +    public function testThrowsExceptionOnNonIntegerTTL()
    +    {
    +        $this->getClient()->psetex('foo', 2.5, 'bar');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR invalid expire time in SETEX
    +     * @todo Should not Redis return PSETEX instead of SETEX here?
    +     */
    +    public function testThrowsExceptionOnZeroTTL()
    +    {
    +        $this->getClient()->psetex('foo', 0, 'bar');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR invalid expire time in SETEX
    +     * @todo Should not Redis return PSETEX instead of SETEX here?
    +     */
    +    public function testThrowsExceptionOnNegativeTTL()
    +    {
    +        $this->getClient()->psetex('foo', -10000, 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringSetBitTest.php b/vendor/predis/predis/tests/Predis/Command/StringSetBitTest.php
    new file mode 100644
    index 0000000..1c1acee
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringSetBitTest.php
    @@ -0,0 +1,156 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringSetBitTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringSetBit';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SETBIT';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 7, 1);
    +        $expected = array('key', 7, 1);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $command = $this->getCommand();
    +        $this->assertSame(0, $command->parseResponse(0));
    +        $this->assertSame(1, $command->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 7, 1);
    +        $expected = array('prefix:key', 7, 1);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanSetBitsOfStrings()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('key:binary', "\x80\x00\00\x01");
    +
    +        $this->assertEquals(1, $redis->setbit('key:binary', 0, 0));
    +        $this->assertEquals(0, $redis->setbit('key:binary', 0, 0));
    +        $this->assertEquals("\x00\x00\00\x01", $redis->get('key:binary'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCreatesNewKeyOnNonExistingKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(0, $redis->setbit('key:binary', 31, 1));
    +        $this->assertSame(0, $redis->setbit('key:binary', 0, 1));
    +        $this->assertSame(4, $redis->strlen('key:binary'));
    +        $this->assertSame("\x80\x00\00\x01", $redis->get('key:binary'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR bit is not an integer or out of range
    +     */
    +    public function testThrowsExceptionOnInvalidBitValue()
    +    {
    +        $redis = $this->getClient()->setbit('key:binary', 10, 255);
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR bit offset is not an integer or out of range
    +     */
    +    public function testThrowsExceptionOnNegativeOffset()
    +    {
    +        $redis = $this->getClient()->setbit('key:binary', -1, 1);
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR bit offset is not an integer or out of range
    +     */
    +    public function testThrowsExceptionOnInvalidOffset()
    +    {
    +        $redis = $this->getClient()->setbit('key:binary', 'invalid', 1);
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->lpush('metavars', 'foo');
    +        $redis->setbit('metavars', 0, 1);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringSetExpireTest.php b/vendor/predis/predis/tests/Predis/Command/StringSetExpireTest.php
    new file mode 100644
    index 0000000..7cc3125
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringSetExpireTest.php
    @@ -0,0 +1,140 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringSetExpireTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringSetExpire';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SETEX';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 10, 'hello');
    +        $expected = array('key', 10, 'hello');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 10, 'hello');
    +        $expected = array('prefix:key', 10, 'hello');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCreatesNewKeyAndSetsTTL()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertTrue($redis->setex('foo', 10, 'bar'));
    +        $this->assertTrue($redis->exists('foo'));
    +        $this->assertEquals(10, $redis->ttl('foo'));
    +    }
    +
    +    /**
    +     * @medium
    +     * @group connected
    +     * @group slow
    +     */
    +    public function testKeyExpiresAfterTTL()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->setex('foo', 1, 'bar');
    +        $this->sleep(2.0);
    +        $this->assertFalse($redis->exists('foo'));;
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR value is not an integer or out of range
    +     */
    +    public function testThrowsExceptionOnNonIntegerTTL()
    +    {
    +        $this->getClient()->setex('foo', 2.5, 'bar');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR invalid expire time in SETEX
    +     */
    +    public function testThrowsExceptionOnZeroTTL()
    +    {
    +        $this->getClient()->setex('foo', 0, 'bar');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR invalid expire time in SETEX
    +     */
    +    public function testThrowsExceptionOnNegativeTTL()
    +    {
    +        $this->getClient()->setex('foo', -10, 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringSetMultiplePreserveTest.php b/vendor/predis/predis/tests/Predis/Command/StringSetMultiplePreserveTest.php
    new file mode 100644
    index 0000000..d2833e3
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringSetMultiplePreserveTest.php
    @@ -0,0 +1,124 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringSetMultiplePreserveTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringSetMultiplePreserve';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'MSETNX';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('foo', 'bar', 'hoge', 'piyo');
    +        $expected = array('foo', 'bar', 'hoge', 'piyo');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsAsSingleNamedArray()
    +    {
    +        $arguments = array(array('foo' => 'bar', 'hoge' => 'piyo'));
    +        $expected = array('foo', 'bar', 'hoge', 'piyo');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(true, $this->getCommand()->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('foo', 'bar', 'hoge', 'piyo');
    +        $expected = array('prefix:foo', 'bar', 'prefix:hoge', 'piyo');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCreatesMultipleKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertTrue($redis->msetnx('foo', 'bar', 'hoge', 'piyo'));
    +        $this->assertSame('bar', $redis->get('foo'));
    +        $this->assertSame('piyo', $redis->get('hoge'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCreatesMultipleKeysAndPreservesExistingOnes()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +
    +        $this->assertFalse($redis->msetnx('foo', 'barbar', 'hoge', 'piyo'));
    +        $this->assertSame('bar', $redis->get('foo'));
    +        $this->assertFalse($redis->exists('hoge'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringSetMultipleTest.php b/vendor/predis/predis/tests/Predis/Command/StringSetMultipleTest.php
    new file mode 100644
    index 0000000..f351aa4
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringSetMultipleTest.php
    @@ -0,0 +1,110 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringSetMultipleTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringSetMultiple';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'MSET';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('foo', 'bar', 'hoge', 'piyo');
    +        $expected = array('foo', 'bar', 'hoge', 'piyo');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsAsSingleNamedArray()
    +    {
    +        $arguments = array(array('foo' => 'bar', 'hoge' => 'piyo'));
    +        $expected = array('foo', 'bar', 'hoge', 'piyo');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(true, $this->getCommand()->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('foo', 'bar', 'hoge', 'piyo');
    +        $expected = array('prefix:foo', 'bar', 'prefix:hoge', 'piyo');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCreatesMultipleKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertTrue($redis->mset('foo', 'bar', 'hoge', 'piyo'));
    +        $this->assertSame('bar', $redis->get('foo'));
    +        $this->assertSame('piyo', $redis->get('hoge'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringSetRangeTest.php b/vendor/predis/predis/tests/Predis/Command/StringSetRangeTest.php
    new file mode 100644
    index 0000000..e5b70c2
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringSetRangeTest.php
    @@ -0,0 +1,150 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringSetRangeTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringSetRange';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SETRANGE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 5, 'range');
    +        $expected = array('key', 5, 'range');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(10, $this->getCommand()->parseResponse(10));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 5, 'range');
    +        $expected = array('prefix:key', 5, 'range');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCreatesNewKeyOnNonExistingKey()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(3, $redis->setrange('foo', 0, 'bar'));
    +        $this->assertSame('bar', $redis->get('foo'));
    +
    +        $this->assertSame(8, $redis->setrange('hoge', 4, 'piyo'));
    +        $this->assertSame("\x00\x00\x00\x00piyo", $redis->get('hoge'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testOverwritesOrAppendBytesInKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'barbar');
    +
    +        $this->assertSame(6, $redis->setrange('foo', 3, 'baz'));
    +        $this->assertSame('barbaz', $redis->get('foo'));
    +
    +        $this->assertEquals(16, $redis->setrange('foo', 10, 'foofoo'));
    +        $this->assertEquals("barbaz\x00\x00\x00\x00foofoo", $redis->get('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testHandlesBinaryData()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(4, $redis->setrange('key:binary', 0, pack('i', -2147483648)));
    +
    +        list($unpacked) = array_values(unpack('i', $redis->get('key:binary')));
    +        $this->assertEquals(-2147483648, $unpacked);
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR offset is out of range
    +     */
    +    public function testThrowsExceptionOnInvalidOffset()
    +    {
    +        $this->getClient()->setrange('var', -1, 'bogus');
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->lpush('metavars', 'foo');
    +        $redis->setrange('metavars', 3, 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringSetTest.php b/vendor/predis/predis/tests/Predis/Command/StringSetTest.php
    new file mode 100644
    index 0000000..004ae0f
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringSetTest.php
    @@ -0,0 +1,96 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringSetTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringSet';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SET';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('foo', 'bar');
    +        $expected = array('foo', 'bar');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'value');
    +        $expected = array('prefix:key', 'value');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testSetStringValue()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertTrue($redis->set('foo', 'bar'));
    +        $this->assertTrue($redis->exists('foo'));
    +        $this->assertEquals('bar', $redis->get('foo'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringStrlenTest.php b/vendor/predis/predis/tests/Predis/Command/StringStrlenTest.php
    new file mode 100644
    index 0000000..947e3e1
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringStrlenTest.php
    @@ -0,0 +1,121 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-string
    + */
    +class StringStrlenTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringStrlen';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'STRLEN';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(4, $this->getCommand()->parseResponse(4));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsTheLengthOfString()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $this->assertSame(3, $redis->strlen('foo'));
    +
    +        $redis->append('foo', 'bar');
    +        $this->assertSame(6, $redis->strlen('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsZeroOnNonExistingKeys()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(0, $redis->strlen('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->lpush('metavars', 'foo');
    +        $redis->strlen('metavars');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/StringSubstrTest.php b/vendor/predis/predis/tests/Predis/Command/StringSubstrTest.php
    new file mode 100644
    index 0000000..2a7a204
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/StringSubstrTest.php
    @@ -0,0 +1,88 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * SUBSTR is actually the old name of GETRANGE in version of Redis <= 2.0.
    + * This command should be considered obsolete and we will perform any kind
    + * of tests against a Redis server for this one.
    + *
    + * @group commands
    + * @group realm-string
    + */
    +class StringSubstrTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\StringSubstr';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'SUBSTR';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 5, 10);
    +        $expected = array('key', 5, 10);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame('substring',$this->getCommand()->parseResponse('substring'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 5, 10);
    +        $expected = array('prefix:key', 5, 10);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/TransactionDiscardTest.php b/vendor/predis/predis/tests/Predis/Command/TransactionDiscardTest.php
    new file mode 100644
    index 0000000..20fa966
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/TransactionDiscardTest.php
    @@ -0,0 +1,82 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-transaction
    + */
    +class TransactionDiscardTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\TransactionDiscard';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'DISCARD';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->setArguments(array());
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testAbortsTransactionAndRestoresNormalFlow()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->multi();
    +
    +        $this->assertInstanceOf('Predis\ResponseQueued', $redis->set('foo', 'bar'));
    +        $this->assertTrue($redis->discard());
    +        $this->assertFalse($redis->exists('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR DISCARD without MULTI
    +     */
    +    public function testThrowsExceptionWhenCallingOutsideTransaction()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->discard();
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/TransactionExecTest.php b/vendor/predis/predis/tests/Predis/Command/TransactionExecTest.php
    new file mode 100644
    index 0000000..fd09ddd
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/TransactionExecTest.php
    @@ -0,0 +1,114 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-transaction
    + */
    +class TransactionExecTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\TransactionExec';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'EXEC';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->setArguments(array());
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('tx1', 'tx2');
    +        $expected = array('tx1', 'tx2');
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testExecutesTransactionAndReturnsArrayOfReplies()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->multi();
    +        $redis->echo('tx1');
    +        $redis->echo('tx2');
    +
    +        $this->assertSame(array('tx1', 'tx2'), $redis->exec());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsEmptyArrayOnEmptyTransactions()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->multi();
    +
    +        $this->assertSame(array(), $redis->exec());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRepliesOfTransactionsAreNotParsed()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->multi();
    +        $redis->ping();
    +        $redis->set('foo', 'bar');
    +        $redis->exists('foo');
    +
    +        $this->assertSame(array('PONG', true, 1), $redis->exec());
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR EXEC without MULTI
    +     */
    +    public function testThrowsExceptionWhenCallingOutsideTransaction()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->exec();
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/TransactionMultiTest.php b/vendor/predis/predis/tests/Predis/Command/TransactionMultiTest.php
    new file mode 100644
    index 0000000..78bfe63
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/TransactionMultiTest.php
    @@ -0,0 +1,93 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-transaction
    + */
    +class TransactionMultiTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\TransactionMulti';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'MULTI';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->setArguments(array());
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testInitializesNewTransaction()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertTrue($redis->multi());
    +        $this->assertSame('QUEUED', (string) $redis->echo('tx1'));
    +        $this->assertSame('QUEUED', (string) $redis->echo('tx2'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testActuallyReturnsReplyObjectAbstraction()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertTrue($redis->multi());
    +        $this->assertInstanceOf('Predis\ResponseObjectInterface', $redis->echo('tx1'));
    +        $this->assertInstanceOf('Predis\ResponseQueued', $redis->echo('tx2'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR MULTI calls can not be nested
    +     */
    +    public function testThrowsExceptionWhenCallingMultiInsideTransaction()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->multi();
    +        $redis->multi();
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/TransactionUnwatchTest.php b/vendor/predis/predis/tests/Predis/Command/TransactionUnwatchTest.php
    new file mode 100644
    index 0000000..c1f8f1f
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/TransactionUnwatchTest.php
    @@ -0,0 +1,86 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-transaction
    + */
    +class TransactionUnwatchTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\TransactionUnwatch';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'UNWATCH';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->setArguments(array());
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testUnwatchWatchedKeys()
    +    {
    +        $redis1 = $this->getClient();
    +        $redis2 = $this->getClient();
    +
    +        $redis1->set('foo', 'bar');
    +        $redis1->watch('foo');
    +        $this->assertTrue($redis1->unwatch());
    +        $redis1->multi();
    +        $redis1->get('foo');
    +
    +        $redis2->set('foo', 'hijacked');
    +
    +        $this->assertSame(array('hijacked'), $redis1->exec());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanBeCalledInsideTransaction()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->multi();
    +        $this->assertInstanceOf('Predis\ResponseQueued', $redis->unwatch());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/TransactionWatchTest.php b/vendor/predis/predis/tests/Predis/Command/TransactionWatchTest.php
    new file mode 100644
    index 0000000..b98673c
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/TransactionWatchTest.php
    @@ -0,0 +1,145 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-transaction
    + */
    +class TransactionWatchTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\TransactionWatch';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'WATCH';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key1', 'key2', 'key3');
    +        $expected = array('key1', 'key2', 'key3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsAsSingleArray()
    +    {
    +        $arguments = array(array('key1', 'key2', 'key3'));
    +        $expected = array('key1', 'key2', 'key3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertTrue($this->getCommand()->parseResponse(true));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key1', 'key2', 'key3');
    +        $expected = array('prefix:key1', 'prefix:key2', 'prefix:key3');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testAbortsTransactionOnExternalWriteOperations()
    +    {
    +        $redis1 = $this->getClient();
    +        $redis2 = $this->getClient();
    +
    +        $redis1->mset('foo', 'bar', 'hoge', 'piyo');
    +
    +        $this->assertTrue($redis1->watch('foo', 'hoge'));
    +        $this->assertTrue($redis1->multi());
    +        $this->assertInstanceOf('Predis\ResponseQueued', $redis1->get('foo'));
    +        $this->assertTrue($redis2->set('foo', 'hijacked'));
    +        $this->assertNull($redis1->exec());
    +        $this->assertSame('hijacked', $redis1->get('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testCanWatchNotYetExistingKeys()
    +    {
    +        $redis1 = $this->getClient();
    +        $redis2 = $this->getClient();
    +
    +        $this->assertTrue($redis1->watch('foo'));
    +        $this->assertTrue($redis1->multi());
    +        $this->assertInstanceOf('Predis\ResponseQueued', $redis1->set('foo', 'bar'));
    +        $this->assertTrue($redis2->set('foo', 'hijacked'));
    +        $this->assertNull($redis1->exec());
    +        $this->assertSame('hijacked', $redis1->get('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR WATCH inside MULTI is not allowed
    +     */
    +    public function testThrowsExceptionWhenCallingInsideTransaction()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->multi();
    +        $redis->watch('foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ZSetAddTest.php b/vendor/predis/predis/tests/Predis/Command/ZSetAddTest.php
    new file mode 100644
    index 0000000..7439442
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ZSetAddTest.php
    @@ -0,0 +1,136 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-zset
    + */
    +class ZSetAddTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ZSetAdd';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'ZADD';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 1, 'member1', 2, 'member2');
    +        $expected = array('key', 1, 'member1', 2, 'member2');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsMembersScoresAsSingleArray()
    +    {
    +        $arguments = array('key', array('member1' => 1, 'member2' => 2));
    +        $expected = array('key', 1, 'member1', 2, 'member2');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'score1', 'member1', 'score2', 'member2');
    +        $expected = array('prefix:key', 'score1', 'member1', 'score2', 'member2');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testAddsOrUpdatesMembersOrderingByScore()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame(5, $redis->zadd('letters', 1, 'a', 2, 'b', 3, 'c', 4, 'd', 5, 'e'));
    +        $this->assertSame(array('a', 'b', 'c', 'd', 'e'), $redis->zrange('letters', 0, -1));
    +
    +        $this->assertSame(1, $redis->zadd('letters', 1, 'e', 8, 'c', 6, 'f'));
    +        $this->assertSame(array('a', 'e', 'b', 'd', 'f', 'c'), $redis->zrange('letters', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testAcceptsFloatValuesAsScore()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', 0.2, 'b', 0.3, 'a', 0.1, 'c');
    +        $this->assertSame(array('c', 'b', 'a'), $redis->zrange('letters', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->zadd('foo', 10, 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ZSetCardinalityTest.php b/vendor/predis/predis/tests/Predis/Command/ZSetCardinalityTest.php
    new file mode 100644
    index 0000000..272286f
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ZSetCardinalityTest.php
    @@ -0,0 +1,110 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-zset
    + */
    +class ZSetCardinalityTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ZSetCardinality';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'ZCARD';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key');
    +        $expected = array('key');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key');
    +        $expected = array('prefix:key');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsSizeOfSortedSet()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', 1, 'a', 2, 'b', 3, 'c');
    +        $this->assertSame(3, $redis->zcard('letters'));
    +
    +        $this->assertSame(0, $redis->zcard('unknown'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->zcard('foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ZSetCountTest.php b/vendor/predis/predis/tests/Predis/Command/ZSetCountTest.php
    new file mode 100644
    index 0000000..19c288c
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ZSetCountTest.php
    @@ -0,0 +1,143 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-zset
    + */
    +class ZSetCountTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ZSetCount';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'ZCOUNT';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 0, 10);
    +        $expected = array('key', 0, 10);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 0, 10);
    +        $expected = array('prefix:key', 0, 10);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsNumberOfElementsInGivenScoreRange()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', 10, 'a', 20, 'b', 30, 'c', 40, 'd', 50, 'e');
    +
    +        $this->assertSame(5, $redis->zcount('letters', 0, 100));
    +        $this->assertSame(5, $redis->zcount('letters', -100, 100));
    +        $this->assertSame(2, $redis->zcount('letters', 25, 45));
    +        $this->assertSame(1, $redis->zcount('letters', 20, 20));
    +        $this->assertSame(0, $redis->zcount('letters', 0, 0));
    +
    +        $this->assertSame(0, $redis->zcount('unknown', 0, 100));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testInfinityScoreIntervals()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', 10, 'a', 20, 'b', 30, 'c', 40, 'd', 50, 'e');
    +
    +        $this->assertSame(3, $redis->zcount('letters', '-inf', 30));
    +        $this->assertSame(3, $redis->zcount('letters', 30, '+inf'));
    +        $this->assertSame(5, $redis->zcount('letters', '-inf', '+inf'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testExclusiveScoreIntervals()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', 10, 'a', 20, 'b', 30, 'c', 40, 'd', 50, 'e');
    +
    +        $this->assertSame(2, $redis->zcount('letters', 10, '(30'));
    +        $this->assertSame(2, $redis->zcount('letters', '(10', 30));
    +        $this->assertSame(1, $redis->zcount('letters', '(10', '(30'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->zcount('foo', 0, 10);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ZSetIncrementByTest.php b/vendor/predis/predis/tests/Predis/Command/ZSetIncrementByTest.php
    new file mode 100644
    index 0000000..45fe321
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ZSetIncrementByTest.php
    @@ -0,0 +1,110 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-zset
    + */
    +class ZSetIncrementByTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ZSetIncrementBy';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'ZINCRBY';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 1.0, 'member');
    +        $expected = array('key', 1.0, 'member');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame('1', $this->getCommand()->parseResponse('1'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 1.0, 'member');
    +        $expected = array('prefix:key', 1.0, 'member');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testIncrementsScoreOfMemberByFloat()
    +    {
    +        $redis = $this->getClient();
    +
    +        $this->assertSame('1', $redis->zincrby('letters', 1, 'member'));
    +        $this->assertSame('0', $redis->zincrby('letters', -1, 'member'));
    +        $this->assertSame('0.5', $redis->zincrby('letters', 0.5, 'member'));
    +        $this->assertSame('-10', $redis->zincrby('letters', -10.5, 'member'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->zincrby('foo', 1, 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ZSetIntersectionStoreTest.php b/vendor/predis/predis/tests/Predis/Command/ZSetIntersectionStoreTest.php
    new file mode 100644
    index 0000000..084b661
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ZSetIntersectionStoreTest.php
    @@ -0,0 +1,204 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-zset
    + */
    +class ZSetIntersectionStoreTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ZSetIntersectionStore';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'ZINTERSTORE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $modifiers = array(
    +            'aggregate' => 'sum',
    +            'weights' => array(10, 100),
    +        );
    +        $arguments = array('zset:destination', 2, 'zset1', 'zset2', $modifiers);
    +
    +        $expected = array(
    +            'zset:destination', 2, 'zset1', 'zset2', 'WEIGHTS', 10, 100, 'AGGREGATE', 'sum'
    +        );
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsSourceKeysAsSingleArray()
    +    {
    +        $modifiers = array(
    +            'aggregate' => 'sum',
    +            'weights' => array(10, 100),
    +        );
    +        $arguments = array('zset:destination', array('zset1', 'zset2'), $modifiers);
    +
    +        $expected = array(
    +            'zset:destination', 2, 'zset1', 'zset2', 'WEIGHTS', 10, 100, 'AGGREGATE', 'sum'
    +        );
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $modifiers = array(
    +            'aggregate' => 'sum',
    +            'weights' => array(10, 100),
    +        );
    +        $arguments = array('zset:destination', 2, 'zset1', 'zset2', $modifiers);
    +
    +        $expected = array(
    +            'prefix:zset:destination', 2, 'prefix:zset1', 'prefix:zset2', 'WEIGHTS', 10, 100, 'AGGREGATE', 'sum'
    +        );
    +
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testStoresIntersectionInNewSortedSet()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters:1st', 1, 'a', 2, 'b', 3, 'c');
    +        $redis->zadd('letters:2nd', 1, 'b', 2, 'c', 3, 'd');
    +
    +        $this->assertSame(2, $redis->zinterstore('letters:out', 2, 'letters:1st', 'letters:2nd'));
    +        $this->assertSame(array(array('b', '3'), array('c', '5')), $redis->zrange('letters:out', 0, -1, 'withscores'));
    +
    +        $this->assertSame(0, $redis->zinterstore('letters:out', 2, 'letters:1st', 'letters:void'));
    +        $this->assertSame(0, $redis->zinterstore('letters:out', 2, 'letters:void', 'letters:2nd'));
    +        $this->assertSame(0, $redis->zinterstore('letters:out', 2, 'letters:void', 'letters:void'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testStoresIntersectionWithAggregateModifier()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters:1st', 1, 'a', 2, 'b', 3, 'c');
    +        $redis->zadd('letters:2nd', 1, 'b', 2, 'c', 3, 'd');
    +
    +        $options = array('aggregate' => 'min');
    +        $this->assertSame(2, $redis->zinterstore('letters:min', 2, 'letters:1st', 'letters:2nd', $options));
    +        $this->assertSame(array(array('b', '1'), array('c', '2')), $redis->zrange('letters:min', 0, -1, 'withscores'));
    +
    +        $options = array('aggregate' => 'max');
    +        $this->assertSame(2, $redis->zinterstore('letters:max', 2, 'letters:1st', 'letters:2nd', $options));
    +        $this->assertSame(array(array('b', '2'), array('c', '3')), $redis->zrange('letters:max', 0, -1, 'withscores'));
    +
    +        $options = array('aggregate' => 'sum');
    +        $this->assertSame(2, $redis->zinterstore('letters:sum', 2, 'letters:1st', 'letters:2nd', $options));
    +        $this->assertSame(array(array('b', '3'), array('c', '5')), $redis->zrange('letters:sum', 0, -1, 'withscores'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testStoresIntersectionWithWeightsModifier()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters:1st', 1, 'a', 2, 'b', 3, 'c');
    +        $redis->zadd('letters:2nd', 1, 'b', 2, 'c', 3, 'd');
    +
    +        $options = array('weights' => array(2, 3));
    +        $this->assertSame(2, $redis->zinterstore('letters:out', 2, 'letters:1st', 'letters:2nd', $options));
    +        $this->assertSame(array(array('b', '7'), array('c', '12')), $redis->zrange('letters:out', 0, -1, 'withscores'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testStoresIntersectionWithCombinedModifiers()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters:1st', 1, 'a', 2, 'b', 3, 'c');
    +        $redis->zadd('letters:2nd', 1, 'b', 2, 'c', 3, 'd');
    +
    +        $options = array('aggregate' => 'max', 'weights' => array(10, 15));
    +        $this->assertSame(2, $redis->zinterstore('letters:out', 2, 'letters:1st', 'letters:2nd', $options));
    +        $this->assertSame(array(array('b', '20'), array('c', '30')), $redis->zrange('letters:out', 0, -1, 'withscores'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->zinterstore('zset:destination', 1, 'foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ZSetRangeByScoreTest.php b/vendor/predis/predis/tests/Predis/Command/ZSetRangeByScoreTest.php
    new file mode 100644
    index 0000000..37110df
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ZSetRangeByScoreTest.php
    @@ -0,0 +1,259 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-zset
    + */
    +class ZSetRangeByScoreTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ZSetRangeByScore';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'ZRANGEBYSCORE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $modifiers = array(
    +            'withscores' => true,
    +            'limit' => array(0, 100),
    +        );
    +
    +        $arguments = array('zset', 0, 100, $modifiers);
    +        $expected = array('zset', 0, 100, 'LIMIT', 0, 100, 'WITHSCORES');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsWithStringWithscores()
    +    {
    +        $arguments = array('zset', 0, 100, 'withscores');
    +        $expected = array('zset', 0, 100, 'WITHSCORES');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsWithNamedLimit()
    +    {
    +        $arguments = array('zset', 0, 100, array('limit' => array('offset' => 1, 'count' => 2)));
    +        $expected = array('zset', 0, 100, 'LIMIT', 1, 2);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('element1', 'element2', 'element3');
    +        $expected = array('element1', 'element2', 'element3');
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponseWithScores()
    +    {
    +        $raw = array('element1', '1', 'element2', '2', 'element3', '3');
    +        $expected = array(array('element1', '1'), array('element2', '2'), array('element3', '3'));
    +
    +        $command = $this->getCommandWithArgumentsArray(array('zset', 0, 1, 'withscores'));
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $modifiers = array(
    +            'withscores' => true,
    +            'limit' => array(0, 100),
    +        );
    +
    +        $arguments = array('zset', 0, 100, $modifiers);
    +        $expected = array('prefix:zset', 0, 100, 'LIMIT', 0, 100, 'WITHSCORES');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAddsWithscoresModifiersOnlyWhenOptionIsTrue()
    +    {
    +        $command = $this->getCommandWithArguments('zset', 0, 100, array('withscores' => true));
    +        $this->assertSame(array('zset', 0, 100, 'WITHSCORES'), $command->getArguments());
    +
    +        $command = $this->getCommandWithArguments('zset', 0, 100, array('withscores' => 1));
    +        $this->assertSame(array('zset', 0, 100, 'WITHSCORES'), $command->getArguments());
    +
    +        $command = $this->getCommandWithArguments('zset', 0, 100, array('withscores' => false));
    +        $this->assertSame(array('zset', 0, 100), $command->getArguments());
    +
    +        $command = $this->getCommandWithArguments('zset', 0, 100, array('withscores' => 0));
    +        $this->assertSame(array('zset', 0, 100), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsElementsInScoreRange()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +
    +        $this->assertSame(array('a'), $redis->zrangebyscore('letters', -10, -10));
    +        $this->assertSame(array('c', 'd', 'e', 'f'), $redis->zrangebyscore('letters', 10, 30));
    +        $this->assertSame(array('d', 'e'), $redis->zrangebyscore('letters', 20, 20));
    +        $this->assertSame(array(), $redis->zrangebyscore('letters', 30, 0));
    +
    +        $this->assertSame(array(), $redis->zrangebyscore('unknown', 0, 30));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testInfinityScoreIntervals()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +
    +        $this->assertSame(array('a', 'b', 'c'), $redis->zrangebyscore('letters', '-inf', 15));
    +        $this->assertSame(array('d', 'e', 'f'), $redis->zrangebyscore('letters', 15, '+inf'));
    +        $this->assertSame(array('a', 'b', 'c', 'd', 'e', 'f'), $redis->zrangebyscore('letters', '-inf', '+inf'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testExclusiveScoreIntervals()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +
    +        $this->assertSame(array('c', 'd', 'e'), $redis->zrangebyscore('letters', 10, '(30'));
    +        $this->assertSame(array('d', 'e', 'f'), $redis->zrangebyscore('letters', '(10', 30));
    +        $this->assertSame(array('d', 'e'), $redis->zrangebyscore('letters', '(10', '(30'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRangeWithWithscoresModifier()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +        $expected = array(array('c', '10'), array('d', '20'), array('e', '20'));
    +
    +        $this->assertSame($expected, $redis->zrangebyscore('letters', 10, 20, 'withscores'));
    +        $this->assertSame($expected, $redis->zrangebyscore('letters', 10, 20, array('withscores' => true)));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRangeWithLimitModifier()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +        $expected = array('d', 'e');
    +
    +        $this->assertSame($expected, $redis->zrangebyscore('letters', 10, 20, array('limit' => array(1, 2))));
    +        $this->assertSame($expected, $redis->zrangebyscore('letters', 10, 20, array('limit' => array('offset' => 1, 'count' => 2))));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRangeWithCombinedModifiers()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +
    +        $options = array('limit' => array(1, 2), 'withscores' => true);
    +        $expected = array(array('d', '20'), array('e', '20'));
    +
    +        $this->assertSame($expected, $redis->zrangebyscore('letters', 10, 20, $options));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->zrangebyscore('foo', 0, 10);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ZSetRangeTest.php b/vendor/predis/predis/tests/Predis/Command/ZSetRangeTest.php
    new file mode 100644
    index 0000000..4ef10b6
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ZSetRangeTest.php
    @@ -0,0 +1,182 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-zset
    + */
    +class ZSetRangeTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ZSetRange';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'ZRANGE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('zset', 0, 100, array('withscores' => true));
    +        $expected = array('zset', 0, 100, 'WITHSCORES');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsWithStringWithscores()
    +    {
    +        $arguments = array('zset', 0, 100, 'withscores');
    +        $expected = array('zset', 0, 100, 'WITHSCORES');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('element1', 'element2', 'element3');
    +        $expected = array('element1', 'element2', 'element3');
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponseWithScores()
    +    {
    +        $raw = array('element1', '1', 'element2', '2', 'element3', '3');
    +        $expected = array(array('element1', '1'), array('element2', '2'), array('element3', '3'));
    +
    +        $command = $this->getCommandWithArgumentsArray(array('zset', 0, 1, 'withscores'));
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('zset', 0, 100, array('withscores' => true));
    +        $expected = array('prefix:zset', 0, 100, 'WITHSCORES');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAddsWithscoresModifiersOnlyWhenOptionIsTrue()
    +    {
    +        $command = $this->getCommandWithArguments('zset', 0, 100, array('withscores' => true));
    +        $this->assertSame(array('zset', 0, 100, 'WITHSCORES'), $command->getArguments());
    +
    +        $command = $this->getCommandWithArguments('zset', 0, 100, array('withscores' => 1));
    +        $this->assertSame(array('zset', 0, 100, 'WITHSCORES'), $command->getArguments());
    +
    +        $command = $this->getCommandWithArguments('zset', 0, 100, array('withscores' => false));
    +        $this->assertSame(array('zset', 0, 100), $command->getArguments());
    +
    +        $command = $this->getCommandWithArguments('zset', 0, 100, array('withscores' => 0));
    +        $this->assertSame(array('zset', 0, 100), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsElementsInRange()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +
    +        $this->assertSame(array(), $redis->zrange('letters', 1, 0));
    +        $this->assertSame(array('a'), $redis->zrange('letters', 0, 0));
    +        $this->assertSame(array('a', 'b', 'c', 'd'), $redis->zrange('letters', 0, 3));
    +
    +        $this->assertSame(array('a', 'b', 'c', 'd', 'e', 'f'), $redis->zrange('letters', 0, -1));
    +        $this->assertSame(array('a', 'b', 'c'), $redis->zrange('letters', 0, -4));
    +        $this->assertSame(array('c'), $redis->zrange('letters', 2, -4));
    +        $this->assertSame(array('a', 'b', 'c', 'd', 'e', 'f'), $redis->zrange('letters', -100, 100));
    +
    +        $this->assertSame(array(), $redis->zrange('unknown', 0, 30));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRangeWithWithscoresModifier()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +        $expected = array(array('c', '10'), array('d', '20'), array('e', '20'));
    +
    +        $this->assertSame($expected, $redis->zrange('letters', 2, 4, 'withscores'));
    +        $this->assertSame($expected, $redis->zrange('letters', 2, 4, array('withscores' => true)));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->zrange('foo', 0, 10);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ZSetRankTest.php b/vendor/predis/predis/tests/Predis/Command/ZSetRankTest.php
    new file mode 100644
    index 0000000..4070a26
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ZSetRankTest.php
    @@ -0,0 +1,113 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-zset
    + */
    +class ZSetRankTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ZSetRank';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'ZRANK';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'member');
    +        $expected = array('key', 'member');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'member');
    +        $expected = array('prefix:key', 'member');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsRank()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +
    +        $this->assertSame(0, $redis->zrank('letters', 'a'));
    +        $this->assertSame(1, $redis->zrank('letters', 'b'));
    +        $this->assertSame(4, $redis->zrank('letters', 'e'));
    +
    +        $this->assertNull($redis->zrank('unknown', 'a'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->zrank('foo', 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ZSetRemoveRangeByRankTest.php b/vendor/predis/predis/tests/Predis/Command/ZSetRemoveRangeByRankTest.php
    new file mode 100644
    index 0000000..01499b5
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ZSetRemoveRangeByRankTest.php
    @@ -0,0 +1,125 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-zset
    + */
    +class ZSetRemoveRangeByRankTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ZSetRemoveRangeByRank';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'ZREMRANGEBYRANK';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 0, 10);
    +        $expected = array('key', 0, 10);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 0, 10);
    +        $expected = array('prefix:key', 0, 10);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRemovesRangeByRank()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +
    +        $this->assertSame(3, $redis->zremrangebyrank('letters', 2, 4));
    +        $this->assertSame(array('a', 'b', 'f'), $redis->zrange('letters', 0, -1));
    +
    +        $this->assertSame(0, $redis->zremrangebyrank('unknown', 0, 30));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRemovesRangeByRankWithNegativeIndex()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +
    +        $this->assertSame(3, $redis->zremrangebyrank('letters', -5, 3));
    +        $this->assertSame(array('a', 'e', 'f'), $redis->zrange('letters', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->zremrangebyrank('foo', 0, 10);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ZSetRemoveRangeByScoreTest.php b/vendor/predis/predis/tests/Predis/Command/ZSetRemoveRangeByScoreTest.php
    new file mode 100644
    index 0000000..3e145a3
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ZSetRemoveRangeByScoreTest.php
    @@ -0,0 +1,125 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-zset
    + */
    +class ZSetRemoveRangeByScoreTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ZSetRemoveRangeByScore';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'ZREMRANGEBYSCORE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 0, 10);
    +        $expected = array('key', 0, 10);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 0, 10);
    +        $expected = array('prefix:key', 0, 10);
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRemovesRangeByScore()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +
    +        $this->assertSame(3, $redis->zremrangebyscore('letters', 5, 20));
    +        $this->assertSame(array('a', 'b', 'f'), $redis->zrange('letters', 0, -1));
    +
    +        $this->assertSame(0, $redis->zremrangebyscore('unknown', 0, 30));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRemovesRangeByExclusiveScore()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +
    +        $this->assertSame(2, $redis->zremrangebyscore('letters', '(10', '(30'));
    +        $this->assertSame(array('a', 'b', 'c', 'f'), $redis->zrange('letters', 0, -1));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->zremrangebyscore('foo', 0, 10);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ZSetRemoveTest.php b/vendor/predis/predis/tests/Predis/Command/ZSetRemoveTest.php
    new file mode 100644
    index 0000000..0fe8ae8
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ZSetRemoveTest.php
    @@ -0,0 +1,112 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-zset
    + */
    +class ZSetRemoveTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ZSetRemove';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'ZREM';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('zset', 'member1', 'member2', 'member3');
    +        $expected = array('zset', 'member1', 'member2', 'member3');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('zset', 'member1', 'member2', 'member3');
    +        $expected = array('prefix:zset', 'member1', 'member2', 'member3');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRemovesSpecifiedMembers()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +
    +        $this->assertSame(3, $redis->zrem('letters', 'b', 'd', 'f', 'z'));
    +        $this->assertSame(array('a', 'c', 'e'), $redis->zrange('letters', 0, -1));
    +
    +        $this->assertSame(0, $redis->zrem('unknown', 'a'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->zrem('foo', 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ZSetReverseRangeByScoreTest.php b/vendor/predis/predis/tests/Predis/Command/ZSetReverseRangeByScoreTest.php
    new file mode 100644
    index 0000000..6cfaa86
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ZSetReverseRangeByScoreTest.php
    @@ -0,0 +1,259 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-zset
    + */
    +class ZSetReverseRangeByScoreTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ZSetReverseRangeByScore';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'ZREVRANGEBYSCORE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $modifiers = array(
    +            'withscores' => true,
    +            'limit' => array(0, 100),
    +        );
    +
    +        $arguments = array('zset', 0, 100, $modifiers);
    +        $expected = array('zset', 0, 100, 'LIMIT', 0, 100, 'WITHSCORES');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsWithStringWithscores()
    +    {
    +        $arguments = array('zset', 0, 100, 'withscores');
    +        $expected = array('zset', 0, 100, 'WITHSCORES');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsWithNamedLimit()
    +    {
    +        $arguments = array('zset', 0, 100, array('limit' => array('offset' => 1, 'count' => 2)));
    +        $expected = array('zset', 0, 100, 'LIMIT', 1, 2);
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('element1', 'element2', 'element3');
    +        $expected = array('element1', 'element2', 'element3');
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponseWithScores()
    +    {
    +        $raw = array('element1', '1', 'element2', '2', 'element3', '3');
    +        $expected = array(array('element1', '1'), array('element2', '2'), array('element3', '3'));
    +
    +        $command = $this->getCommandWithArgumentsArray(array('zset', 0, 1, 'withscores'));
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $modifiers = array(
    +            'withscores' => true,
    +            'limit' => array(0, 100),
    +        );
    +
    +        $arguments = array('zset', 0, 100, $modifiers);
    +        $expected = array('prefix:zset', 0, 100, 'LIMIT', 0, 100, 'WITHSCORES');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAddsWithscoresModifiersOnlyWhenOptionIsTrue()
    +    {
    +        $command = $this->getCommandWithArguments('zset', 0, 100, array('withscores' => true));
    +        $this->assertSame(array('zset', 0, 100, 'WITHSCORES'), $command->getArguments());
    +
    +        $command = $this->getCommandWithArguments('zset', 0, 100, array('withscores' => 1));
    +        $this->assertSame(array('zset', 0, 100, 'WITHSCORES'), $command->getArguments());
    +
    +        $command = $this->getCommandWithArguments('zset', 0, 100, array('withscores' => false));
    +        $this->assertSame(array('zset', 0, 100), $command->getArguments());
    +
    +        $command = $this->getCommandWithArguments('zset', 0, 100, array('withscores' => 0));
    +        $this->assertSame(array('zset', 0, 100), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsElementsInScoreRange()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +
    +        $this->assertSame(array('a'), $redis->zrevrangebyscore('letters', -10, -10));
    +        $this->assertSame(array(), $redis->zrevrangebyscore('letters', 10, 30));
    +        $this->assertSame(array('e', 'd'), $redis->zrevrangebyscore('letters', 20, 20));
    +        $this->assertSame(array('f', 'e', 'd', 'c', 'b'), $redis->zrevrangebyscore('letters', 30, 0));
    +
    +        $this->assertSame(array(), $redis->zrevrangebyscore('unknown', 0, 30));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testInfinityScoreIntervals()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +
    +        $this->assertSame(array('f', 'e', 'd'), $redis->zrevrangebyscore('letters', '+inf', 15));
    +        $this->assertSame(array('c', 'b', 'a'), $redis->zrevrangebyscore('letters', 15, '-inf'));
    +        $this->assertSame(array('f', 'e', 'd', 'c', 'b', 'a'), $redis->zrevrangebyscore('letters', '+inf', '-inf'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testExclusiveScoreIntervals()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +
    +        $this->assertSame(array('e', 'd', 'c'), $redis->zrevrangebyscore('letters', '(30', 10));
    +        $this->assertSame(array('f', 'e', 'd'), $redis->zrevrangebyscore('letters', 30, '(10'));
    +        $this->assertSame(array('e', 'd'), $redis->zrevrangebyscore('letters', '(30', '(10'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRangeWithWithscoresModifier()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +        $expected = array(array('e', '20'), array('d', '20'), array('c', '10'));
    +
    +        $this->assertSame($expected, $redis->zrevrangebyscore('letters', 20, 10, 'withscores'));
    +        $this->assertSame($expected, $redis->zrevrangebyscore('letters', 20, 10, array('withscores' => true)));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRangeWithLimitModifier()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +        $expected = array('d', 'c');
    +
    +        $this->assertSame($expected, $redis->zrevrangebyscore('letters', 20, 10, array('limit' => array(1, 2))));
    +        $this->assertSame($expected, $redis->zrevrangebyscore('letters', 20, 10, array('limit' => array('offset' => 1, 'count' => 2))));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRangeWithCombinedModifiers()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +
    +        $options = array('limit' => array(1, 2), 'withscores' => true);
    +        $expected = array(array('d', '20'), array('c', '10'));
    +
    +        $this->assertSame($expected, $redis->zrevrangebyscore('letters', 20, 10, $options));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->zrevrangebyscore('foo', 0, 10);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ZSetReverseRangeTest.php b/vendor/predis/predis/tests/Predis/Command/ZSetReverseRangeTest.php
    new file mode 100644
    index 0000000..58b17dd
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ZSetReverseRangeTest.php
    @@ -0,0 +1,182 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-zset
    + */
    +class ZSetReverseRangeTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ZSetReverseRange';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'ZREVRANGE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('zset', 0, 100, array('withscores' => true));
    +        $expected = array('zset', 0, 100, 'WITHSCORES');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsWithStringWithscores()
    +    {
    +        $arguments = array('zset', 0, 100, 'withscores');
    +        $expected = array('zset', 0, 100, 'WITHSCORES');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $raw = array('element1', 'element2', 'element3');
    +        $expected = array('element1', 'element2', 'element3');
    +
    +        $command = $this->getCommand();
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponseWithScores()
    +    {
    +        $raw = array('element1', '1', 'element2', '2', 'element3', '3');
    +        $expected = array(array('element1', '1'), array('element2', '2'), array('element3', '3'));
    +
    +        $command = $this->getCommandWithArgumentsArray(array('zset', 0, 1, 'withscores'));
    +
    +        $this->assertSame($expected, $command->parseResponse($raw));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('zset', 0, 100, array('withscores' => true));
    +        $expected = array('prefix:zset', 0, 100, 'WITHSCORES');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAddsWithscoresModifiersOnlyWhenOptionIsTrue()
    +    {
    +        $command = $this->getCommandWithArguments('zset', 0, 100, array('withscores' => true));
    +        $this->assertSame(array('zset', 0, 100, 'WITHSCORES'), $command->getArguments());
    +
    +        $command = $this->getCommandWithArguments('zset', 0, 100, array('withscores' => 1));
    +        $this->assertSame(array('zset', 0, 100, 'WITHSCORES'), $command->getArguments());
    +
    +        $command = $this->getCommandWithArguments('zset', 0, 100, array('withscores' => false));
    +        $this->assertSame(array('zset', 0, 100), $command->getArguments());
    +
    +        $command = $this->getCommandWithArguments('zset', 0, 100, array('withscores' => 0));
    +        $this->assertSame(array('zset', 0, 100), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsElementsInRange()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +
    +        $this->assertSame(array(), $redis->zrevrange('letters', 1, 0));
    +        $this->assertSame(array('f'), $redis->zrevrange('letters', 0, 0));
    +        $this->assertSame(array('f', 'e', 'd', 'c'), $redis->zrevrange('letters', 0, 3));
    +
    +        $this->assertSame(array('f', 'e', 'd', 'c', 'b', 'a'), $redis->zrevrange('letters', 0, -1));
    +        $this->assertSame(array('f', 'e', 'd'), $redis->zrevrange('letters', 0, -4));
    +        $this->assertSame(array('d'), $redis->zrevrange('letters', 2, -4));
    +        $this->assertSame(array('f', 'e', 'd', 'c', 'b', 'a'), $redis->zrevrange('letters', -100, 100));
    +
    +        $this->assertSame(array(), $redis->zrevrange('unknown', 0, 30));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testRangeWithWithscoresModifier()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +        $expected = array(array('d', '20'), array('c', '10'), array('b', '0'));
    +
    +        $this->assertSame($expected, $redis->zrevrange('letters', 2, 4, 'withscores'));
    +        $this->assertSame($expected, $redis->zrevrange('letters', 2, 4, array('withscores' => true)));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->zrevrange('foo', 0, 10);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ZSetReverseRankTest.php b/vendor/predis/predis/tests/Predis/Command/ZSetReverseRankTest.php
    new file mode 100644
    index 0000000..10434d5
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ZSetReverseRankTest.php
    @@ -0,0 +1,113 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-zset
    + */
    +class ZSetReverseRankTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ZSetReverseRank';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'ZREVRANK';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'member');
    +        $expected = array('key', 'member');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'member');
    +        $expected = array('prefix:key', 'member');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsRank()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +
    +        $this->assertSame(5, $redis->zrevrank('letters', 'a'));
    +        $this->assertSame(4, $redis->zrevrank('letters', 'b'));
    +        $this->assertSame(1, $redis->zrevrank('letters', 'e'));
    +
    +        $this->assertNull($redis->zrevrank('unknown', 'a'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->zrevrank('foo', 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ZSetScoreTest.php b/vendor/predis/predis/tests/Predis/Command/ZSetScoreTest.php
    new file mode 100644
    index 0000000..929a427
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ZSetScoreTest.php
    @@ -0,0 +1,113 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-zset
    + */
    +class ZSetScoreTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ZSetScore';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'ZSCORE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $arguments = array('key', 'member');
    +        $expected = array('key', 'member');
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $arguments = array('key', 'member');
    +        $expected = array('prefix:key', 'member');
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReturnsRank()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters', -10, 'a', 0, 'b', 10, 'c', 20, 'd', 20, 'e', 30, 'f');
    +
    +        $this->assertSame('-10', $redis->zscore('letters', 'a'));
    +        $this->assertSame('0', $redis->zscore('letters', 'b'));
    +        $this->assertSame('20', $redis->zscore('letters', 'e'));
    +
    +        $this->assertNull($redis->zscore('unknown', 'a'));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->zscore('foo', 'bar');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Command/ZSetUnionStoreTest.php b/vendor/predis/predis/tests/Predis/Command/ZSetUnionStoreTest.php
    new file mode 100644
    index 0000000..2569c0c
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Command/ZSetUnionStoreTest.php
    @@ -0,0 +1,221 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Command;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @group commands
    + * @group realm-zset
    + */
    +class ZSetUnionStoreTest extends CommandTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedCommand()
    +    {
    +        return 'Predis\Command\ZSetUnionStore';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getExpectedId()
    +    {
    +        return 'ZUNIONSTORE';
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArguments()
    +    {
    +        $modifiers = array(
    +            'aggregate' => 'sum',
    +            'weights' => array(10, 100),
    +        );
    +        $arguments = array('zset:destination', 2, 'zset1', 'zset2', $modifiers);
    +
    +        $expected = array(
    +            'zset:destination', 2, 'zset1', 'zset2', 'WEIGHTS', 10, 100, 'AGGREGATE', 'sum'
    +        );
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArgumentsSourceKeysAsSingleArray()
    +    {
    +        $modifiers = array(
    +            'aggregate' => 'sum',
    +            'weights' => array(10, 100),
    +        );
    +        $arguments = array('zset:destination', array('zset1', 'zset2'), $modifiers);
    +
    +        $expected = array(
    +            'zset:destination', 2, 'zset1', 'zset2', 'WEIGHTS', 10, 100, 'AGGREGATE', 'sum'
    +        );
    +
    +        $command = $this->getCommand();
    +        $command->setArguments($arguments);
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParseResponse()
    +    {
    +        $this->assertSame(1, $this->getCommand()->parseResponse(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeys()
    +    {
    +        $modifiers = array(
    +            'aggregate' => 'sum',
    +            'weights' => array(10, 100),
    +        );
    +        $arguments = array('zset:destination', 2, 'zset1', 'zset2', $modifiers);
    +
    +        $expected = array(
    +            'prefix:zset:destination', 2, 'prefix:zset1', 'prefix:zset2', 'WEIGHTS', 10, 100, 'AGGREGATE', 'sum'
    +        );
    +
    +        $command = $this->getCommandWithArgumentsArray($arguments);
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame($expected, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPrefixKeysIgnoredOnEmptyArguments()
    +    {
    +        $command = $this->getCommand();
    +        $command->prefixKeys('prefix:');
    +
    +        $this->assertSame(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testStoresUnionInNewSortedSet()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters:1st', 1, 'a', 2, 'b', 3, 'c');
    +        $redis->zadd('letters:2nd', 1, 'b', 2, 'c', 3, 'd');
    +
    +        $this->assertSame(4, $redis->zunionstore('letters:out', 2, 'letters:1st', 'letters:2nd'));
    +        $this->assertSame(
    +            array(array('a', '1'), array('b', '3'), array('d', '3'), array('c', '5')),
    +            $redis->zrange('letters:out', 0, -1, 'withscores')
    +        );
    +
    +        $this->assertSame(3, $redis->zunionstore('letters:out', 2, 'letters:1st', 'letters:void'));
    +        $this->assertSame(3, $redis->zunionstore('letters:out', 2, 'letters:void', 'letters:2nd'));
    +        $this->assertSame(0, $redis->zunionstore('letters:out', 2, 'letters:void', 'letters:void'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testStoresUnionWithAggregateModifier()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters:1st', 1, 'a', 2, 'b', 3, 'c');
    +        $redis->zadd('letters:2nd', 1, 'b', 2, 'c', 3, 'd');
    +
    +        $options = array('aggregate' => 'min');
    +        $this->assertSame(4, $redis->zunionstore('letters:min', 2, 'letters:1st', 'letters:2nd', $options));
    +        $this->assertSame(
    +            array(array('a', '1'), array('b', '1'), array('c', '2'), array('d', '3')),
    +            $redis->zrange('letters:min', 0, -1, 'withscores')
    +        );
    +
    +        $options = array('aggregate' => 'max');
    +        $this->assertSame(4, $redis->zunionstore('letters:max', 2, 'letters:1st', 'letters:2nd', $options));
    +        $this->assertSame(
    +            array(array('a', '1'), array('b', '2'), array('c', '3'), array('d', '3')),
    +            $redis->zrange('letters:max', 0, -1, 'withscores')
    +        );
    +
    +        $options = array('aggregate' => 'sum');
    +        $this->assertSame(4, $redis->zunionstore('letters:sum', 2, 'letters:1st', 'letters:2nd', $options));
    +        $this->assertSame(
    +            array(array('a', '1'), array('b', '3'), array('d', '3'), array('c', '5')),
    +            $redis->zrange('letters:sum', 0, -1, 'withscores')
    +        );
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testStoresUnionWithWeightsModifier()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters:1st', 1, 'a', 2, 'b', 3, 'c');
    +        $redis->zadd('letters:2nd', 1, 'b', 2, 'c', 3, 'd');
    +
    +        $options = array('weights' => array(2, 3));
    +        $this->assertSame(4, $redis->zunionstore('letters:out', 2, 'letters:1st', 'letters:2nd', $options));
    +        $this->assertSame(
    +            array(array('a', '2'), array('b', '7'), array('d', '9'), array('c', '12')),
    +            $redis->zrange('letters:out', 0, -1, 'withscores')
    +        );
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testStoresUnionWithCombinedModifiers()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->zadd('letters:1st', 1, 'a', 2, 'b', 3, 'c');
    +        $redis->zadd('letters:2nd', 1, 'b', 2, 'c', 3, 'd');
    +
    +        $options = array('aggregate' => 'max', 'weights' => array(10, 15));
    +        $this->assertSame(4, $redis->zunionstore('letters:out', 2, 'letters:1st', 'letters:2nd', $options));
    +        $this->assertSame(
    +            array(array('a', '10'), array('b', '20'), array('c', '30'), array('d', '45')),
    +            $redis->zrange('letters:out', 0, -1, 'withscores')
    +        );
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage Operation against a key holding the wrong kind of value
    +     */
    +    public function testThrowsExceptionOnWrongType()
    +    {
    +        $redis = $this->getClient();
    +
    +        $redis->set('foo', 'bar');
    +        $redis->zunionstore('zset:destination', 1, 'foo');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/CommunicationExceptionTest.php b/vendor/predis/predis/tests/Predis/CommunicationExceptionTest.php
    new file mode 100644
    index 0000000..b4c2ccd
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/CommunicationExceptionTest.php
    @@ -0,0 +1,114 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Connection\SingleConnectionInterface;
    +
    +/**
    + *
    + */
    +class CommunicationExceptionTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExceptionMessage()
    +    {
    +        $message = 'Connection error message.';
    +        $connection = $this->getMockedConnectionBase();
    +        $exception = $this->getException($connection, $message);
    +
    +        $this->setExpectedException('Predis\CommunicationException', $message);
    +
    +        throw $exception;
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExceptionConnection()
    +    {
    +        $connection = $this->getMockedConnectionBase();
    +        $exception = $this->getException($connection, 'ERROR MESSAGE');
    +
    +        $this->assertSame($connection, $exception->getConnection());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testShouldResetConnection()
    +    {
    +        $connection = $this->getMockedConnectionBase();
    +        $exception = $this->getException($connection, 'ERROR MESSAGE');
    +
    +        $this->assertTrue($exception->shouldResetConnection());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\CommunicationException
    +     * @expectedExceptionMessage Communication error
    +     */
    +    public function testCommunicationExceptionHandling()
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->once())->method('isConnected')->will($this->returnValue(true));
    +        $connection->expects($this->once())->method('disconnect');
    +
    +        $exception = $this->getException($connection, 'Communication error');
    +
    +        CommunicationException::handle($exception);
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Returns a mocked connection instance.
    +     *
    +     * @param mixed $parameters Connection parameters.
    +     * @return SingleConnectionInterface
    +     */
    +    protected function getMockedConnectionBase($parameters = null)
    +    {
    +        $builder = $this->getMockBuilder('Predis\Connection\AbstractConnection');
    +
    +        if ($parameters === null) {
    +            $builder->disableOriginalConstructor();
    +        } else if (!$parameters instanceof ConnectionParametersInterface) {
    +            $parameters = new ConnectionParameters($parameters);
    +        }
    +
    +        return $builder->getMockForAbstractClass(array($parameters));
    +    }
    +
    +    /**
    +     * Returns a connection exception instance.
    +     *
    +     * @param SingleConnectionInterface $message Connection instance.
    +     * @param string $message Exception message.
    +     * @param int $code Exception code.
    +     * @param \Exception $inner Inner exception.
    +     * @return \Exception
    +     */
    +    protected function getException(SingleConnectionInterface $connection, $message, $code = 0, \Exception $inner = null)
    +    {
    +        $arguments = array($connection, $message, $code, $inner);
    +        $mock = $this->getMockForAbstractClass('Predis\CommunicationException', $arguments);
    +
    +        return $mock;
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Connection/ComposableStreamConnectionTest.php b/vendor/predis/predis/tests/Predis/Connection/ComposableStreamConnectionTest.php
    new file mode 100644
    index 0000000..d21b754
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Connection/ComposableStreamConnectionTest.php
    @@ -0,0 +1,124 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + *
    + */
    +class ComposableStreamConnectionTest extends ConnectionTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorDoesNotOpenConnection()
    +    {
    +        $connection = new ComposableStreamConnection($this->getParameters());
    +
    +        $this->assertFalse($connection->isConnected());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExposesParameters()
    +    {
    +        $parameters = $this->getParameters();
    +        $connection = new ComposableStreamConnection($parameters);
    +
    +        $this->assertSame($parameters, $connection->getParameters());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException InvalidArgumentException
    +     * @expectedExceptionMessage Invalid scheme: udp
    +     */
    +    public function testThrowsExceptionOnInvalidScheme()
    +    {
    +        $parameters = $this->getParameters(array('scheme' => 'udp'));
    +        $connection = new ComposableStreamConnection($parameters);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanBeSerialized()
    +    {
    +        $parameters = $this->getParameters(array('alias' => 'redis', 'read_write_timeout' => 10));
    +        $connection = new ComposableStreamConnection($parameters);
    +
    +        $unserialized = unserialize(serialize($connection));
    +
    +        $this->assertEquals($connection, $unserialized);
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- INTEGRATION TESTS --------------------------------------------- //
    +    // ******************************************************************** //
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReadsMultibulkRepliesAsIterators()
    +    {
    +        $connection = $this->getConnection($profile, true);
    +        $connection->getProtocol()->setOption('iterable_multibulk', true);
    +
    +        $connection->executeCommand($profile->createCommand('rpush', array('metavars', 'foo', 'hoge', 'lol')));
    +        $connection->writeCommand($profile->createCommand('lrange', array('metavars', 0, -1)));
    +
    +        $this->assertInstanceOf('Predis\Iterator\MultiBulkResponse', $iterator = $connection->read());
    +        $this->assertSame(array('foo', 'hoge', 'lol'), iterator_to_array($iterator));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\Protocol\ProtocolException
    +     * @expectedExceptionMessage Unknown prefix: 'P'
    +     */
    +    public function testThrowsExceptionOnProtocolDesynchronizationErrors()
    +    {
    +        $connection = $this->getConnection($profile);
    +        $stream = $connection->getResource();
    +
    +        $connection->writeCommand($profile->createCommand('ping'));
    +        fread($stream, 1);
    +
    +        $connection->read();
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getConnection(&$profile = null, $initialize = false, Array $parameters = array())
    +    {
    +        $parameters = $this->getParameters($parameters);
    +        $profile = $this->getProfile();
    +
    +        $connection = new ComposableStreamConnection($parameters);
    +
    +        if ($initialize) {
    +            $connection->pushInitCommand($profile->createCommand('select', array($parameters->database)));
    +            $connection->pushInitCommand($profile->createCommand('flushdb'));
    +        }
    +
    +        return $connection;
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Connection/ConnectionExceptionTest.php b/vendor/predis/predis/tests/Predis/Connection/ConnectionExceptionTest.php
    new file mode 100644
    index 0000000..71cb4b6
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Connection/ConnectionExceptionTest.php
    @@ -0,0 +1,31 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +require_once __DIR__.'/../CommunicationExceptionTest.php';
    +
    +use Predis\CommunicationExceptionTest;
    +use Predis\Connection\SingleConnectionInterface;
    +
    +/**
    + *
    + */
    +class ConnectionExceptionTest extends CommunicationExceptionTest
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getException(SingleConnectionInterface $connection, $message, $code = 0, \Exception $inner = null)
    +    {
    +        return new ConnectionException($connection, $message, $code, $inner);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Connection/ConnectionFactoryTest.php b/vendor/predis/predis/tests/Predis/Connection/ConnectionFactoryTest.php
    new file mode 100644
    index 0000000..99867d9
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Connection/ConnectionFactoryTest.php
    @@ -0,0 +1,385 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class ConnectionFactoryTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testImplementsCorrectInterface()
    +    {
    +        $factory = new ConnectionFactory();
    +
    +        $this->assertInstanceOf('Predis\Connection\ConnectionFactoryInterface', $factory);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCreateConnection()
    +    {
    +        $factory = new ConnectionFactory();
    +
    +        $tcp = new ConnectionParameters(array(
    +            'scheme' => 'tcp',
    +            'host' => 'locahost',
    +        ));
    +
    +        $connection = $factory->create($tcp);
    +        $parameters = $connection->getParameters();
    +        $this->assertInstanceOf('Predis\Connection\StreamConnection', $connection);
    +        $this->assertEquals($tcp->scheme, $parameters->scheme);
    +        $this->assertEquals($tcp->host, $parameters->host);
    +        $this->assertEquals($tcp->database, $parameters->database);
    +
    +
    +        $unix = new ConnectionParameters(array(
    +            'scheme' => 'unix',
    +            'path' => '/tmp/redis.sock',
    +        ));
    +
    +        $connection = $factory->create($tcp);
    +        $parameters = $connection->getParameters();
    +        $this->assertInstanceOf('Predis\Connection\StreamConnection', $connection);
    +        $this->assertEquals($tcp->scheme, $parameters->scheme);
    +        $this->assertEquals($tcp->database, $parameters->database);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCreateConnectionWithNullParameters()
    +    {
    +        $factory = new ConnectionFactory();
    +        $connection = $factory->create(null);
    +        $parameters = $connection->getParameters();
    +
    +        $this->assertInstanceOf('Predis\Connection\SingleConnectionInterface', $connection);
    +        $this->assertEquals('tcp', $parameters->scheme);
    +
    +        $this->assertFalse(isset($parameters->custom));
    +        $this->assertNull($parameters->custom);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCreateConnectionWithArrayParameters()
    +    {
    +        $factory = new ConnectionFactory();
    +        $connection = $factory->create(array('scheme' => 'tcp', 'custom' => 'foobar'));
    +        $parameters = $connection->getParameters();
    +
    +        $this->assertInstanceOf('Predis\Connection\SingleConnectionInterface', $connection);
    +        $this->assertEquals('tcp', $parameters->scheme);
    +
    +        $this->assertTrue(isset($parameters->custom));
    +        $this->assertSame('foobar', $parameters->custom);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCreateConnectionWithStringURI()
    +    {
    +        $factory = new ConnectionFactory();
    +        $connection = $factory->create('tcp://127.0.0.1?custom=foobar');
    +        $parameters = $connection->getParameters();
    +
    +        $this->assertInstanceOf('Predis\Connection\SingleConnectionInterface', $connection);
    +        $this->assertEquals('tcp', $parameters->scheme);
    +
    +        $this->assertTrue(isset($parameters->custom));
    +        $this->assertSame('foobar', $parameters->custom);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCreateConnectionWithoutInitializationCommands()
    +    {
    +        $profile = $this->getMock('Predis\Profile\ServerProfileInterface');
    +        $profile->expects($this->never())->method('create');
    +
    +        $factory = new ConnectionFactory($profile);
    +        $parameters = new ConnectionParameters();
    +        $connection = $factory->create($parameters);
    +
    +        $this->assertInstanceOf('Predis\Connection\SingleConnectionInterface', $connection);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCreateConnectionWithInitializationCommands()
    +    {
    +        $test = $this;
    +        $database = 15;
    +        $password = 'foobar';
    +        $commands = array();
    +
    +        $createCommand = function ($id, $arguments) use ($test, &$commands) {
    +            $commands[$id] = $arguments;
    +            $command = $test->getMock('Predis\Command\CommandInterface');
    +
    +            return $command;
    +        };
    +
    +        $profile = $this->getMock('Predis\Profile\ServerProfileInterface');
    +        $profile->expects($this->exactly(2))
    +                ->method('createCommand')
    +                ->with($this->isType('string'), $this->isType('array'))
    +                ->will($this->returnCallback($createCommand));
    +
    +        $factory = new ConnectionFactory($profile);
    +        $parameters = new ConnectionParameters(array('database' => $database, 'password' => $password));
    +        $connection = $factory->create($parameters);
    +
    +        $this->assertInstanceOf('Predis\Connection\SingleConnectionInterface', $connection);
    +        $this->assertEquals(2, count($commands));   // TODO: assertCount()?
    +        $this->assertEquals(array($database), $commands['select']);
    +        $this->assertEquals(array($password), $commands['auth']);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @todo This test smells but there's no other way around it right now.
    +     */
    +    public function testCreateConnectionWithDatabaseAndPasswordButNoProfile()
    +    {
    +        $parameters = new ConnectionParameters(array('database' => 0, 'password' => 'foobar'));
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->never())
    +                   ->method('getParameters')
    +                   ->will($this->returnValue($parameters));
    +        $connection->expects($this->never())
    +                   ->method('pushInitCommand');
    +
    +        $factory = new ConnectionFactory();
    +
    +        $reflection = new \ReflectionObject($factory);
    +        $prepareConnection = $reflection->getMethod('prepareConnection');
    +        $prepareConnection->setAccessible(true);
    +        $prepareConnection->invoke($factory, $connection);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCreateUndefinedConnection()
    +    {
    +        $scheme = 'unknown';
    +        $this->setExpectedException('InvalidArgumentException', "Unknown connection scheme: $scheme");
    +
    +        $factory = new ConnectionFactory();
    +        $factory->create(new ConnectionParameters(array('scheme' => $scheme)));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDefineConnectionWithFQN()
    +    {
    +        list(, $connectionClass) = $this->getMockConnectionClass();
    +
    +        $parameters = new ConnectionParameters(array('scheme' => 'foobar'));
    +        $factory = new ConnectionFactory();
    +
    +        $factory->define($parameters->scheme, $connectionClass);
    +        $connection = $factory->create($parameters);
    +
    +        $this->assertInstanceOf($connectionClass, $connection);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDefineConnectionWithCallable()
    +    {
    +        list(, $connectionClass) = $this->getMockConnectionClass();
    +
    +        $parameters = new ConnectionParameters(array('scheme' => 'foobar'));
    +
    +        $initializer = function ($parameters) use ($connectionClass) {
    +            return new $connectionClass($parameters);
    +        };
    +
    +        $initializerMock = $this->getMock('stdClass', array('__invoke'));
    +        $initializerMock->expects($this->exactly(2))
    +                        ->method('__invoke')
    +                        ->with($parameters)
    +                        ->will($this->returnCallback($initializer));
    +
    +        $factory = new ConnectionFactory();
    +        $factory->define($parameters->scheme, $initializerMock);
    +        $connection1 = $factory->create($parameters);
    +        $connection2 = $factory->create($parameters);
    +
    +        $this->assertInstanceOf($connectionClass, $connection1);
    +        $this->assertInstanceOf($connectionClass, $connection2);
    +        $this->assertNotSame($connection1, $connection2);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDefineConnectionWithInvalidArgument()
    +    {
    +        $this->setExpectedException('InvalidArgumentException');
    +
    +        $factory = new ConnectionFactory();
    +        $factory->define('foobar', new \stdClass());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testUndefineDefinedConnection()
    +    {
    +        $this->setExpectedException('InvalidArgumentException', 'Unknown connection scheme: tcp');
    +
    +        $factory = new ConnectionFactory();
    +        $factory->undefine('tcp');
    +        $factory->create('tcp://127.0.0.1');
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testUndefineUndefinedConnection()
    +    {
    +        $factory = new ConnectionFactory();
    +        $factory->undefine('unknown');
    +        $connection = $factory->create('tcp://127.0.0.1');
    +
    +        $this->assertInstanceOf('Predis\Connection\SingleConnectionInterface', $connection);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDefineAndUndefineConnection()
    +    {
    +        list(, $connectionClass) = $this->getMockConnectionClass();
    +
    +        $factory = new ConnectionFactory();
    +
    +        $factory->define('redis', $connectionClass);
    +        $this->assertInstanceOf($connectionClass, $factory->create('redis://127.0.0.1'));
    +
    +        $factory->undefine('redis');
    +        $this->setExpectedException('InvalidArgumentException', 'Unknown connection scheme: redis');
    +        $factory->create('redis://127.0.0.1');
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAggregatedConnectionSkipCreationOnConnectionInstance()
    +    {
    +        list(, $connectionClass) = $this->getMockConnectionClass();
    +
    +        $cluster = $this->getMock('Predis\Connection\ClusterConnectionInterface');
    +        $cluster->expects($this->exactly(2))
    +                ->method('add')
    +                ->with($this->isInstanceOf('Predis\Connection\SingleConnectionInterface'));
    +
    +        $factory = $this->getMock('Predis\Connection\ConnectionFactory', array('create'));
    +        $factory->expects($this->never())
    +                ->method('create');
    +
    +        $factory->createAggregated($cluster, array(new $connectionClass(), new $connectionClass()));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAggregatedConnectionWithMixedParameters()
    +    {
    +        list(, $connectionClass) = $this->getMockConnectionClass();
    +
    +        $cluster = $this->getMock('Predis\Connection\ClusterConnectionInterface');
    +        $cluster->expects($this->exactly(4))
    +                ->method('add')
    +                ->with($this->isInstanceOf('Predis\Connection\SingleConnectionInterface'));
    +
    +        $factory = $this->getMock('Predis\Connection\ConnectionFactory', array('create'));
    +        $factory->expects($this->exactly(3))
    +                ->method('create')
    +                ->will($this->returnCallback(function ($_) use ($connectionClass) {
    +                    return new $connectionClass;
    +                }));
    +
    +        $factory->createAggregated($cluster, array(null, 'tcp://127.0.0.1', array('scheme' => 'tcp'), new $connectionClass()));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAggregatedConnectionWithEmptyListOfParameters()
    +    {
    +        $cluster = $this->getMock('Predis\Connection\ClusterConnectionInterface');
    +        $cluster->expects($this->never())->method('add');
    +
    +        $factory = $this->getMock('Predis\Connection\ConnectionFactory', array('create'));
    +        $factory->expects($this->never())->method('create');
    +
    +        $factory->createAggregated($cluster, array());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @todo We might want to add a test for SingleConnectionInterface::pushInitCommand().
    +     */
    +    public function testAggregatedConnectionWithServerProfileArgument()
    +    {
    +        list(, $connectionClass) = $this->getMockConnectionClass();
    +
    +        $cluster = $this->getMock('Predis\Connection\ClusterConnectionInterface');
    +        $profile = $this->getMock('Predis\Profile\ServerProfileInterface');
    +
    +        $factory = $this->getMock('Predis\Connection\ConnectionFactory', array('create'), array($profile));
    +        $factory->expects($this->exactly(2))
    +                ->method('create')
    +                ->with($this->anything())
    +                ->will($this->returnCallback(function ($_) use ($connectionClass) {
    +                    return new $connectionClass();
    +                }));
    +
    +        $nodes = array('tcp://127.0.0.1:7001?password=foo', 'tcp://127.0.0.1:7002?password=bar');
    +        $factory->createAggregated($cluster, $nodes);
    +    }
    +
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Returns a mocked Predis\Connection\SingleConnectionInterface.
    +     *
    +     * @return Array Mock instance and class name
    +     */
    +    protected function getMockConnectionClass()
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +
    +        return array($connection, get_class($connection));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Connection/ConnectionParametersTest.php b/vendor/predis/predis/tests/Predis/Connection/ConnectionParametersTest.php
    new file mode 100644
    index 0000000..d5a95e6
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Connection/ConnectionParametersTest.php
    @@ -0,0 +1,226 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @todo ConnectionParameters::define();
    + * @todo ConnectionParameters::undefine();
    + */
    +class ParametersTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDefaultValues()
    +    {
    +        $defaults = $this->getDefaultParametersArray();
    +        $parameters = new ConnectionParameters();
    +
    +        $this->assertEquals($defaults['scheme'], $parameters->scheme);
    +        $this->assertEquals($defaults['host'], $parameters->host);
    +        $this->assertEquals($defaults['port'], $parameters->port);
    +        $this->assertEquals($defaults['timeout'], $parameters->timeout);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testIsSet()
    +    {
    +        $parameters = new ConnectionParameters();
    +
    +        $this->assertTrue(isset($parameters->scheme));
    +        $this->assertFalse(isset($parameters->unknown));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testUserDefinedParameters()
    +    {
    +        $parameters = new ConnectionParameters(array('port' => 7000, 'custom' => 'foobar'));
    +
    +        $this->assertTrue(isset($parameters->scheme));
    +        $this->assertSame('tcp', $parameters->scheme);
    +
    +        $this->assertTrue(isset($parameters->port));
    +        $this->assertSame(7000, $parameters->port);
    +
    +        $this->assertTrue(isset($parameters->custom));
    +        $this->assertSame('foobar', $parameters->custom);
    +
    +        $this->assertFalse(isset($parameters->unknown));
    +        $this->assertNull($parameters->unknown);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructWithUriString()
    +    {
    +        $defaults = $this->getDefaultParametersArray();
    +
    +        $overrides = array(
    +            'port' => 7000,
    +            'database' => 5,
    +            'iterable_multibulk' => false,
    +            'custom' => 'foobar',
    +        );
    +
    +        $parameters = new ConnectionParameters($this->getParametersString($overrides));
    +
    +        $this->assertEquals($defaults['scheme'], $parameters->scheme);
    +        $this->assertEquals($defaults['host'], $parameters->host);
    +        $this->assertEquals($overrides['port'], $parameters->port);
    +
    +        $this->assertEquals($overrides['database'], $parameters->database);
    +        $this->assertEquals($overrides['iterable_multibulk'], $parameters->iterable_multibulk);
    +
    +        $this->assertTrue(isset($parameters->custom));
    +        $this->assertEquals($overrides['custom'], $parameters->custom);
    +
    +        $this->assertFalse(isset($parameters->unknown));
    +        $this->assertNull($parameters->unknown);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testToArray()
    +    {
    +        $additional = array('port' => 7000, 'custom' => 'foobar');
    +        $parameters = new ConnectionParameters($additional);
    +
    +        $this->assertEquals($this->getParametersArray($additional), $parameters->toArray());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSerialization()
    +    {
    +        $parameters = new ConnectionParameters(array('port' => 7000, 'custom' => 'foobar'));
    +        $unserialized = unserialize(serialize($parameters));
    +
    +        $this->assertEquals($parameters->scheme, $unserialized->scheme);
    +        $this->assertEquals($parameters->port, $unserialized->port);
    +
    +        $this->assertTrue(isset($unserialized->custom));
    +        $this->assertEquals($parameters->custom, $unserialized->custom);
    +
    +        $this->assertFalse(isset($unserialized->unknown));
    +        $this->assertNull($unserialized->unknown);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParsingURI()
    +    {
    +        $uri = 'tcp://10.10.10.10:6400?timeout=0.5&persistent=1';
    +
    +        $expected = array(
    +            'scheme' => 'tcp',
    +            'host' => '10.10.10.10',
    +            'port' => 6400,
    +            'timeout' => '0.5',
    +            'persistent' => '1',
    +        );
    +
    +        $this->assertSame($expected, ConnectionParameters::parseURI($uri));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testParsingUnixDomainURI()
    +    {
    +        $uri = 'unix:///tmp/redis.sock?timeout=0.5&persistent=1';
    +
    +        $expected = array(
    +            'scheme' => 'unix',
    +            'host' => 'localhost',
    +            'path' => '/tmp/redis.sock',
    +            'timeout' => '0.5',
    +            'persistent' => '1',
    +        );
    +
    +        $this->assertSame($expected, ConnectionParameters::parseURI($uri));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\ClientException
    +     * @expectedExceptionMessage Invalid URI: tcp://invalid:uri
    +     */
    +    public function testParsingURIThrowOnInvalidURI()
    +    {
    +        ConnectionParameters::parseURI('tcp://invalid:uri');
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Returns a named array with the default connection parameters and their values.
    +     *
    +     * @return Array Default connection parameters.
    +     */
    +    protected function getDefaultParametersArray()
    +    {
    +        return array(
    +            'scheme' => 'tcp',
    +            'host' => '127.0.0.1',
    +            'port' => 6379,
    +            'timeout' => 5.0,
    +        );
    +    }
    +
    +    /**
    +     * Returns a named array with the default connection parameters merged with
    +     * the specified additional parameters.
    +     *
    +     * @param Array $additional Additional connection parameters.
    +     * @return Array Connection parameters.
    +     */
    +    protected function getParametersArray(Array $additional)
    +    {
    +        return array_merge($this->getDefaultParametersArray(), $additional);
    +    }
    +
    +    /**
    +     * Returns an URI string representation of the specified connection parameters.
    +     *
    +     * @param Array $parameters Array of connection parameters.
    +     * @return String URI string.
    +     */
    +    protected function getParametersString(Array $parameters)
    +    {
    +        $defaults = $this->getDefaultParametersArray();
    +
    +        $scheme = isset($parameters['scheme']) ? $parameters['scheme'] : $defaults['scheme'];
    +        $host = isset($parameters['host']) ? $parameters['host'] : $defaults['host'];
    +        $port = isset($parameters['port']) ? $parameters['port'] : $defaults['port'];
    +
    +        unset($parameters['scheme'], $parameters['host'], $parameters['port']);
    +        $uriString = "$scheme://$host:$port/?";
    +
    +        foreach ($parameters as $k => $v) {
    +            $uriString .= "$k=$v&";
    +        }
    +
    +        return $uriString;
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Connection/MasterSlaveReplicationTest.php b/vendor/predis/predis/tests/Predis/Connection/MasterSlaveReplicationTest.php
    new file mode 100644
    index 0000000..5acf0d7
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Connection/MasterSlaveReplicationTest.php
    @@ -0,0 +1,593 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Profile\ServerProfile;
    +use Predis\Replication\ReplicationStrategy;
    +
    +/**
    + *
    + */
    +class MasterSlaveReplicationTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAddingConnectionsToReplication()
    +    {
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
    +        $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave1);
    +        $replication->add($slave2);
    +
    +        $this->assertSame($master, $replication->getConnectionById('master'));
    +        $this->assertSame($slave1, $replication->getConnectionById('slave1'));
    +        $this->assertSame($slave2, $replication->getConnectionById('slave2'));
    +
    +        $this->assertSame($master, $replication->getMaster());
    +        $this->assertSame(array($slave1, $slave2), $replication->getSlaves());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testRemovingConnectionsFromReplication()
    +    {
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
    +        $slave2 = $this->getMockConnection('tcp://host3?alias=slave2');
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave1);
    +
    +        $this->assertTrue($replication->remove($slave1));
    +        $this->assertFalse($replication->remove($slave2));
    +
    +        $this->assertSame($master, $replication->getMaster());
    +        $this->assertSame(array(), $replication->getSlaves());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException RuntimeException
    +     * @expectedExceptionMessage Replication needs a master and at least one slave
    +     */
    +    public function testThrowsExceptionOnEmptyReplication()
    +    {
    +        $replication = new MasterSlaveReplication();
    +        $replication->connect();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException RuntimeException
    +     * @expectedExceptionMessage Replication needs a master and at least one slave
    +     */
    +    public function testThrowsExceptionOnMissingMaster()
    +    {
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($this->getMockConnection('tcp://host2?alias=slave1'));
    +
    +        $replication->connect();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException RuntimeException
    +     * @expectedExceptionMessage Replication needs a master and at least one slave
    +     */
    +    public function testThrowsExceptionOnMissingSlave()
    +    {
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($this->getMockConnection('tcp://host1?alias=master'));
    +
    +        $replication->connect();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConnectForcesConnectionToOneOfSlaves()
    +    {
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $master->expects($this->never())->method('connect');
    +
    +        $slave = $this->getMockConnection('tcp://host2?alias=slave1');
    +        $slave->expects($this->once())->method('connect');
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave);
    +
    +        $replication->connect();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testIsConnectedReturnsTrueIfAtLeastOneConnectionIsOpen()
    +    {
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $master->expects($this->never())->method('isConnected')->will($this->returnValue(false));
    +
    +        $slave = $this->getMockConnection('tcp://host2?alias=slave1');
    +        $slave->expects($this->once())->method('isConnected')->will($this->returnValue(true));
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave);
    +        $replication->connect();
    +
    +        $this->assertTrue($replication->isConnected());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testIsConnectedReturnsFalseIfAllConnectionsAreClosed()
    +    {
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $master->expects($this->any())->method('isConnected')->will($this->returnValue(false));
    +
    +        $slave = $this->getMockConnection('tcp://host2?alias=slave1');
    +        $slave->expects($this->any())->method('isConnected')->will($this->returnValue(false));
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave);
    +
    +        $this->assertFalse($replication->isConnected());
    +
    +        $replication->connect();
    +        $replication->disconnect();
    +
    +        $this->assertFalse($replication->isConnected());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDisconnectForcesCurrentConnectionToDisconnect()
    +    {
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $master->expects($this->once())->method('disconnect');
    +
    +        $slave = $this->getMockConnection('tcp://host2?alias=slave1');
    +        $slave->expects($this->once())->method('disconnect');
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave);
    +
    +        $replication->disconnect();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanSwitchConnectionByAlias()
    +    {
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave1);
    +
    +        $this->assertNull($replication->getCurrent());
    +
    +        $replication->switchTo('master');
    +        $this->assertSame($master, $replication->getCurrent());
    +        $replication->switchTo('slave1');
    +        $this->assertSame($slave1, $replication->getCurrent());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException InvalidArgumentException
    +     * @expectedExceptionMessage The specified connection is not valid
    +     */
    +    public function testThrowsErrorWhenSwitchingToUnknownConnection()
    +    {
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($this->getMockConnection('tcp://host1?alias=master'));
    +        $replication->add($this->getMockConnection('tcp://host2?alias=slave1'));
    +
    +        $replication->switchTo('unknown');
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testUsesSlavesOnReadOnlyCommands()
    +    {
    +        $profile = ServerProfile::getDefault();
    +
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave1);
    +
    +        $cmd = $profile->createCommand('exists', array('foo'));
    +        $this->assertSame($slave1, $replication->getConnection($cmd));
    +
    +        $cmd = $profile->createCommand('get', array('foo'));
    +        $this->assertSame($slave1, $replication->getConnection($cmd));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testUsesMasterOnWriteCommands()
    +    {
    +        $profile = ServerProfile::getDefault();
    +
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave1);
    +
    +        $cmd = $profile->createCommand('set', array('foo', 'bar'));
    +        $this->assertSame($master, $replication->getConnection($cmd));
    +
    +        $cmd = $profile->createCommand('get', array('foo'));
    +        $this->assertSame($master, $replication->getConnection($cmd));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSwitchesFromSlaveToMasterOnWriteCommands()
    +    {
    +        $profile = ServerProfile::getDefault();
    +
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave1);
    +
    +        $cmd = $profile->createCommand('exists', array('foo'));
    +        $this->assertSame($slave1, $replication->getConnection($cmd));
    +
    +        $cmd = $profile->createCommand('set', array('foo', 'bar'));
    +        $this->assertSame($master, $replication->getConnection($cmd));
    +
    +        $cmd = $profile->createCommand('exists', array('foo'));
    +        $this->assertSame($master, $replication->getConnection($cmd));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testWritesCommandToCorrectConnection()
    +    {
    +        $profile = ServerProfile::getDefault();
    +        $cmdExists = $profile->createCommand('exists', array('foo'));
    +        $cmdSet = $profile->getDefault()->createCommand('set', array('foo', 'bar'));
    +
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $master->expects($this->once())->method('writeCommand')->with($cmdSet);
    +
    +        $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
    +        $slave1->expects($this->once())->method('writeCommand')->with($cmdExists);
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave1);
    +
    +        $replication->writeCommand($cmdExists);
    +        $replication->writeCommand($cmdSet);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testReadsCommandFromCorrectConnection()
    +    {
    +        $profile = ServerProfile::getDefault();
    +        $cmdExists = $profile->createCommand('exists', array('foo'));
    +        $cmdSet = $profile->getDefault()->createCommand('set', array('foo', 'bar'));
    +
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $master->expects($this->once())->method('readResponse')->with($cmdSet);
    +
    +        $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
    +        $slave1->expects($this->once())->method('readResponse')->with($cmdExists);
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave1);
    +
    +        $replication->readResponse($cmdExists);
    +        $replication->readResponse($cmdSet);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecutesCommandOnCorrectConnection()
    +    {
    +        $profile = ServerProfile::getDefault();
    +        $cmdExists = $profile->createCommand('exists', array('foo'));
    +        $cmdSet = $profile->getDefault()->createCommand('set', array('foo', 'bar'));
    +
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $master->expects($this->once())->method('executeCommand')->with($cmdSet);
    +
    +        $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
    +        $slave1->expects($this->once())->method('executeCommand')->with($cmdExists);
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave1);
    +
    +        $replication->executeCommand($cmdExists);
    +        $replication->executeCommand($cmdSet);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testWatchTriggersSwitchToMasterConnection()
    +    {
    +        $profile = ServerProfile::getDefault();
    +        $cmdWatch = $profile->createCommand('watch', array('foo'));
    +
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $master->expects($this->once())->method('executeCommand')->with($cmdWatch);
    +
    +        $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
    +        $slave1->expects($this->never())->method('executeCommand');
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave1);
    +
    +        $replication->executeCommand($cmdWatch);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testMultiTriggersSwitchToMasterConnection()
    +    {
    +        $profile = ServerProfile::getDefault();
    +        $cmdMulti = $profile->createCommand('multi');
    +
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $master->expects($this->once())->method('executeCommand')->with($cmdMulti);
    +
    +        $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
    +        $slave1->expects($this->never())->method('executeCommand');
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave1);
    +
    +        $replication->executeCommand($cmdMulti);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testEvalTriggersSwitchToMasterConnection()
    +    {
    +        $profile = ServerProfile::get('dev');
    +        $cmdEval = $profile->createCommand('eval', array("return redis.call('info')"));
    +
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $master->expects($this->once())->method('executeCommand')->with($cmdEval);
    +
    +        $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
    +        $slave1->expects($this->never())->method('executeCommand');
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave1);
    +
    +        $replication->executeCommand($cmdEval);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSortTriggersSwitchToMasterConnectionOnStoreModifier()
    +    {
    +        $profile = ServerProfile::get('dev');
    +        $cmdSortNormal = $profile->createCommand('sort', array('key'));
    +        $cmdSortStore = $profile->createCommand('sort', array('key', array('store' => 'key:store')));
    +
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $master->expects($this->once())->method('executeCommand')->with($cmdSortStore);
    +
    +        $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
    +        $slave1->expects($this->once())->method('executeCommand')->with($cmdSortNormal);
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave1);
    +
    +        $replication->executeCommand($cmdSortNormal);
    +        $replication->executeCommand($cmdSortStore);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\NotSupportedException
    +     * @expectedExceptionMessage The command INFO is not allowed in replication mode
    +     */
    +    public function testThrowsExceptionOnNonSupportedCommand()
    +    {
    +        $cmd = ServerProfile::getDefault()->createCommand('info');
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($this->getMockConnection('tcp://host1?alias=master'));
    +        $replication->add($this->getMockConnection('tcp://host2?alias=slave1'));
    +
    +        $replication->getConnection($cmd);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanOverrideReadOnlyFlagForCommands()
    +    {
    +        $profile = ServerProfile::getDefault();
    +        $cmdSet = $profile->createCommand('set', array('foo', 'bar'));
    +        $cmdGet = $profile->createCommand('get', array('foo'));
    +
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $master->expects($this->once())->method('executeCommand')->with($cmdGet);
    +
    +        $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
    +        $slave1->expects($this->once())->method('executeCommand')->with($cmdSet);
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave1);
    +
    +        $replication->getReplicationStrategy()->setCommandReadOnly($cmdSet->getId(), true);
    +        $replication->getReplicationStrategy()->setCommandReadOnly($cmdGet->getId(), false);
    +
    +        $replication->executeCommand($cmdSet);
    +        $replication->executeCommand($cmdGet);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAcceptsCallableToOverrideReadOnlyFlagForCommands()
    +    {
    +        $profile = ServerProfile::getDefault();
    +        $cmdExistsFoo = $profile->createCommand('exists', array('foo'));
    +        $cmdExistsBar = $profile->createCommand('exists', array('bar'));
    +
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $master->expects($this->once())->method('executeCommand')->with($cmdExistsBar);
    +
    +        $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
    +        $slave1->expects($this->once())->method('executeCommand')->with($cmdExistsFoo);
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave1);
    +
    +        $replication->getReplicationStrategy()->setCommandReadOnly('exists', function ($cmd) {
    +            list($arg1) = $cmd->getArguments();
    +            return $arg1 === 'foo';
    +        });
    +
    +        $replication->executeCommand($cmdExistsFoo);
    +        $replication->executeCommand($cmdExistsBar);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanSetReadOnlyFlagForEvalScripts()
    +    {
    +        $profile = ServerProfile::get('dev');
    +
    +        $cmdEval = $profile->createCommand('eval', array($script = "return redis.call('info');"));
    +        $cmdEvalSha = $profile->createCommand('evalsha', array($scriptSHA1 = sha1($script)));
    +
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $master->expects($this->never())->method('executeCommand');
    +
    +        $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
    +        $slave1->expects($this->exactly(2))
    +               ->method('executeCommand')
    +               ->with($this->logicalOr($cmdEval, $cmdEvalSha));
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave1);
    +
    +        $replication->getReplicationStrategy()->setScriptReadOnly($script);
    +
    +        $replication->executeCommand($cmdEval);
    +        $replication->executeCommand($cmdEvalSha);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExposesReplicationStrategy()
    +    {
    +        $replication = new MasterSlaveReplication();
    +        $this->assertInstanceOf('Predis\Replication\ReplicationStrategy', $replication->getReplicationStrategy());
    +
    +        $strategy = new ReplicationStrategy();
    +        $replication = new MasterSlaveReplication($strategy);
    +        $this->assertSame($strategy, $replication->getReplicationStrategy());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanBeSerialized()
    +    {
    +        $master = $this->getMockConnection('tcp://host1?alias=master');
    +        $slave1 = $this->getMockConnection('tcp://host2?alias=slave1');
    +
    +        $replication = new MasterSlaveReplication();
    +        $replication->add($master);
    +        $replication->add($slave1);
    +
    +        $unserialized = unserialize(serialize($replication));
    +
    +        $this->assertEquals($master, $unserialized->getConnectionById('master'));
    +        $this->assertEquals($slave1, $unserialized->getConnectionById('slave1'));
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Returns a base mocked connection from Predis\Connection\SingleConnectionInterface.
    +     *
    +     * @param mixed $parameters Optional parameters.
    +     * @return mixed
    +     */
    +    protected function getMockConnection($parameters = null)
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +
    +        if ($parameters) {
    +            $parameters = new ConnectionParameters($parameters);
    +            $hash = "{$parameters->host}:{$parameters->port}";
    +
    +            $connection->expects($this->any())
    +                       ->method('getParameters')
    +                       ->will($this->returnValue($parameters));
    +            $connection->expects($this->any())
    +                       ->method('__toString')
    +                       ->will($this->returnValue($hash));
    +        }
    +
    +        return $connection;
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Connection/PhpiredisConnectionTest.php b/vendor/predis/predis/tests/Predis/Connection/PhpiredisConnectionTest.php
    new file mode 100644
    index 0000000..2ab8909
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Connection/PhpiredisConnectionTest.php
    @@ -0,0 +1,130 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + * @group ext-phpiredis
    + */
    +class PhpiredisConnectionTest extends ConnectionTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorDoesNotOpenConnection()
    +    {
    +        $connection = new PhpiredisConnection($this->getParameters());
    +
    +        $this->assertFalse($connection->isConnected());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExposesParameters()
    +    {
    +        $parameters = $this->getParameters();
    +        $connection = new PhpiredisConnection($parameters);
    +
    +        $this->assertSame($parameters, $connection->getParameters());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException InvalidArgumentException
    +     * @expectedExceptionMessage Invalid scheme: udp
    +     */
    +    public function testThrowsExceptionOnInvalidScheme()
    +    {
    +        $parameters = $this->getParameters(array('scheme' => 'udp'));
    +        $connection = new PhpiredisConnection($parameters);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanBeSerialized()
    +    {
    +        $parameters = $this->getParameters(array('alias' => 'redis', 'read_write_timeout' => 10));
    +        $connection = new PhpiredisConnection($parameters);
    +
    +        $unserialized = unserialize(serialize($connection));
    +
    +        $this->assertInstanceOf('Predis\Connection\PhpiredisConnection', $unserialized);
    +        $this->assertEquals($parameters, $unserialized->getParameters());
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- INTEGRATION TESTS --------------------------------------------- //
    +    // ******************************************************************** //
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testExecutesCommandsOnServer()
    +    {
    +        $connection = $this->getConnection($profile, true);
    +
    +        $cmdPing   = $profile->createCommand('ping');
    +        $cmdEcho   = $profile->createCommand('echo', array('echoed'));
    +        $cmdGet    = $profile->createCommand('get', array('foobar'));
    +        $cmdRpush  = $profile->createCommand('rpush', array('metavars', 'foo', 'hoge', 'lol'));
    +        $cmdLrange = $profile->createCommand('lrange', array('metavars', 0, -1));
    +
    +        $this->assertSame('PONG', $connection->executeCommand($cmdPing));
    +        $this->assertSame('echoed', $connection->executeCommand($cmdEcho));
    +        $this->assertNull($connection->executeCommand($cmdGet));
    +        $this->assertSame(3, $connection->executeCommand($cmdRpush));
    +        $this->assertSame(array('foo', 'hoge', 'lol'), $connection->executeCommand($cmdLrange));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\Protocol\ProtocolException
    +     * @expectedExceptionMessage Protocol error, got "P" as reply type byte
    +     */
    +    public function testThrowsExceptionOnProtocolDesynchronizationErrors()
    +    {
    +        $connection = $this->getConnection($profile);
    +        $socket = $connection->getResource();
    +
    +        $connection->writeCommand($profile->createCommand('ping'));
    +        socket_read($socket, 1);
    +
    +        $connection->read();
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getConnection(&$profile = null, $initialize = false, Array $parameters = array())
    +    {
    +        $parameters = $this->getParameters($parameters);
    +        $profile = $this->getProfile();
    +
    +        $connection = new PhpiredisConnection($parameters);
    +
    +        if ($initialize) {
    +            $connection->pushInitCommand($profile->createCommand('select', array($parameters->database)));
    +            $connection->pushInitCommand($profile->createCommand('flushdb'));
    +        }
    +
    +        return $connection;
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Connection/PhpiredisStreamConnectionTest.php b/vendor/predis/predis/tests/Predis/Connection/PhpiredisStreamConnectionTest.php
    new file mode 100644
    index 0000000..1170139
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Connection/PhpiredisStreamConnectionTest.php
    @@ -0,0 +1,148 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + * @group ext-phpiredis
    + */
    +class PhpiredisStreamConnectionTest extends ConnectionTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorDoesNotOpenConnection()
    +    {
    +        $connection = new PhpiredisStreamConnection($this->getParameters());
    +
    +        $this->assertFalse($connection->isConnected());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExposesParameters()
    +    {
    +        $parameters = $this->getParameters();
    +        $connection = new PhpiredisStreamConnection($parameters);
    +
    +        $this->assertSame($parameters, $connection->getParameters());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException InvalidArgumentException
    +     * @expectedExceptionMessage Invalid scheme: udp
    +     */
    +    public function testThrowsExceptionOnInvalidScheme()
    +    {
    +        $parameters = $this->getParameters(array('scheme' => 'udp'));
    +        $connection = new PhpiredisStreamConnection($parameters);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanBeSerialized()
    +    {
    +        $parameters = $this->getParameters(array('alias' => 'redis', 'read_write_timeout' => 10));
    +        $connection = new PhpiredisStreamConnection($parameters);
    +
    +        $unserialized = unserialize(serialize($connection));
    +
    +        $this->assertInstanceOf('Predis\Connection\PhpiredisStreamConnection', $unserialized);
    +        $this->assertEquals($parameters, $unserialized->getParameters());
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- INTEGRATION TESTS --------------------------------------------- //
    +    // ******************************************************************** //
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testAcceptsTcpNodelayParameter()
    +    {
    +        if (!version_compare(PHP_VERSION, '5.4.0', '>=')) {
    +            $this->markTestSkipped('Setting TCP_NODELAY on PHP socket streams works on PHP >= 5.4.0');
    +        }
    +
    +        $connection = new PhpiredisStreamConnection($this->getParameters(array('tcp_nodelay' => false)));
    +        $connection->connect();
    +        $this->assertTrue($connection->isConnected());
    +
    +        $connection = new PhpiredisStreamConnection($this->getParameters(array('tcp_nodelay' => true)));
    +        $connection->connect();
    +        $this->assertTrue($connection->isConnected());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testExecutesCommandsOnServer()
    +    {
    +        $connection = $this->getConnection($profile, true);
    +
    +        $cmdPing   = $profile->createCommand('ping');
    +        $cmdEcho   = $profile->createCommand('echo', array('echoed'));
    +        $cmdGet    = $profile->createCommand('get', array('foobar'));
    +        $cmdRpush  = $profile->createCommand('rpush', array('metavars', 'foo', 'hoge', 'lol'));
    +        $cmdLrange = $profile->createCommand('lrange', array('metavars', 0, -1));
    +
    +        $this->assertSame('PONG', $connection->executeCommand($cmdPing));
    +        $this->assertSame('echoed', $connection->executeCommand($cmdEcho));
    +        $this->assertNull($connection->executeCommand($cmdGet));
    +        $this->assertSame(3, $connection->executeCommand($cmdRpush));
    +        $this->assertSame(array('foo', 'hoge', 'lol'), $connection->executeCommand($cmdLrange));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\Protocol\ProtocolException
    +     * @expectedExceptionMessage Protocol error, got "P" as reply type byte
    +     */
    +    public function testThrowsExceptionOnProtocolDesynchronizationErrors()
    +    {
    +        $connection = $this->getConnection($profile);
    +        $socket = $connection->getResource();
    +
    +        $connection->writeCommand($profile->createCommand('ping'));
    +        fread($socket, 1);
    +
    +        $connection->read();
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getConnection(&$profile = null, $initialize = false, Array $parameters = array())
    +    {
    +        $parameters = $this->getParameters($parameters);
    +        $profile = $this->getProfile();
    +
    +        $connection = new PhpiredisStreamConnection($parameters);
    +
    +        if ($initialize) {
    +            $connection->pushInitCommand($profile->createCommand('select', array($parameters->database)));
    +            $connection->pushInitCommand($profile->createCommand('flushdb'));
    +        }
    +
    +        return $connection;
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Connection/PredisClusterTest.php b/vendor/predis/predis/tests/Predis/Connection/PredisClusterTest.php
    new file mode 100644
    index 0000000..01d24dc
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Connection/PredisClusterTest.php
    @@ -0,0 +1,405 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + *
    + */
    +class PredisClusterTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExposesCommandHashStrategy()
    +    {
    +        $cluster = new PredisCluster();
    +        $this->assertInstanceOf('Predis\Cluster\PredisClusterHashStrategy', $cluster->getCommandHashStrategy());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAddingConnectionsToCluster()
    +    {
    +        $connection1 = $this->getMockConnection();
    +        $connection2 = $this->getMockConnection();
    +
    +        $cluster = new PredisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $this->assertSame(2, count($cluster));
    +        $this->assertSame($connection1, $cluster->getConnectionById(0));
    +        $this->assertSame($connection2, $cluster->getConnectionById(1));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAddingConnectionsToClusterUsesConnectionAlias()
    +    {
    +        $connection1 = $this->getMockConnection('tcp://host1:7001?alias=node1');
    +        $connection2 = $this->getMockConnection('tcp://host1:7002?alias=node2');
    +
    +        $cluster = new PredisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $this->assertSame(2, count($cluster));
    +        $this->assertSame($connection1, $cluster->getConnectionById('node1'));
    +        $this->assertSame($connection2, $cluster->getConnectionById('node2'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testRemovingConnectionsFromCluster()
    +    {
    +        $connection1 = $this->getMockConnection();
    +        $connection2 = $this->getMockConnection();
    +        $connection3 = $this->getMockConnection();
    +
    +        $cluster = new PredisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $this->assertTrue($cluster->remove($connection1));
    +        $this->assertFalse($cluster->remove($connection3));
    +        $this->assertSame(1, count($cluster));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testRemovingConnectionsFromClusterByAlias()
    +    {
    +        $connection1 = $this->getMockConnection();
    +        $connection2 = $this->getMockConnection('tcp://host1:7001?alias=node2');
    +        $connection3 = $this->getMockConnection('tcp://host1:7002?alias=node3');
    +        $connection4 = $this->getMockConnection('tcp://host1:7003?alias=node4');
    +
    +        $cluster = new PredisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +        $cluster->add($connection3);
    +
    +        $this->assertTrue($cluster->removeById(0));
    +        $this->assertTrue($cluster->removeById('node2'));
    +        $this->assertFalse($cluster->removeById('node4'));
    +        $this->assertSame(1, count($cluster));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConnectForcesAllConnectionsToConnect()
    +    {
    +        $connection1 = $this->getMockConnection();
    +        $connection1->expects($this->once())->method('connect');
    +
    +        $connection2 = $this->getMockConnection();
    +        $connection2->expects($this->once())->method('connect');
    +
    +        $cluster = new PredisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $cluster->connect();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDisconnectForcesAllConnectionsToDisconnect()
    +    {
    +        $connection1 = $this->getMockConnection();
    +        $connection1->expects($this->once())->method('disconnect');
    +
    +        $connection2 = $this->getMockConnection();
    +        $connection2->expects($this->once())->method('disconnect');
    +
    +        $cluster = new PredisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $cluster->disconnect();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testIsConnectedReturnsTrueIfAtLeastOneConnectionIsOpen()
    +    {
    +        $connection1 = $this->getMockConnection();
    +        $connection1->expects($this->once())
    +                    ->method('isConnected')
    +                    ->will($this->returnValue(false));
    +
    +        $connection2 = $this->getMockConnection();
    +        $connection2->expects($this->once())
    +                    ->method('isConnected')
    +                    ->will($this->returnValue(true));
    +
    +        $cluster = new PredisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $this->assertTrue($cluster->isConnected());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testIsConnectedReturnsFalseIfAllConnectionsAreClosed()
    +    {
    +        $connection1 = $this->getMockConnection();
    +        $connection1->expects($this->once())
    +                    ->method('isConnected')
    +                    ->will($this->returnValue(false));
    +
    +        $connection2 = $this->getMockConnection();
    +        $connection2->expects($this->once())
    +                    ->method('isConnected')
    +                    ->will($this->returnValue(false));
    +
    +        $cluster = new PredisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $this->assertFalse($cluster->isConnected());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanReturnAnIteratorForConnections()
    +    {
    +        $connection1 = $this->getMockConnection();
    +        $connection2 = $this->getMockConnection();
    +
    +        $cluster = new PredisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $this->assertInstanceOf('Iterator', $iterator = $cluster->getIterator());
    +        $connections = iterator_to_array($iterator);
    +
    +        $this->assertSame($connection1, $connections[0]);
    +        $this->assertSame($connection2, $connections[1]);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testReturnsCorrectConnectionUsingKey()
    +    {
    +        $connection1 = $this->getMockConnection('tcp://host1:7001');
    +        $connection2 = $this->getMockConnection('tcp://host1:7002');
    +
    +        $cluster = new PredisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $this->assertSame($connection1, $cluster->getConnectionByKey('node01:5431'));
    +        $this->assertSame($connection2, $cluster->getConnectionByKey('node02:3212'));
    +        $this->assertSame($connection1, $cluster->getConnectionByKey('prefix:{node01:5431}'));
    +        $this->assertSame($connection2, $cluster->getConnectionByKey('prefix:{node02:3212}'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testReturnsCorrectConnectionUsingCommandInstance()
    +    {
    +        $profile = ServerProfile::getDefault();
    +
    +        $connection1 = $this->getMockConnection('tcp://host1:7001');
    +        $connection2 = $this->getMockConnection('tcp://host1:7002');
    +
    +        $cluster = new PredisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $set = $profile->createCommand('set', array('node01:5431', 'foobar'));
    +        $get = $profile->createCommand('get', array('node01:5431'));
    +        $this->assertSame($connection1, $cluster->getConnection($set));
    +        $this->assertSame($connection1, $cluster->getConnection($get));
    +
    +        $set = $profile->createCommand('set', array('prefix:{node01:5431}', 'foobar'));
    +        $get = $profile->createCommand('get', array('prefix:{node01:5431}'));
    +        $this->assertSame($connection1, $cluster->getConnection($set));
    +        $this->assertSame($connection1, $cluster->getConnection($get));
    +
    +        $set = $profile->createCommand('set', array('node02:3212', 'foobar'));
    +        $get = $profile->createCommand('get', array('node02:3212'));
    +        $this->assertSame($connection2, $cluster->getConnection($set));
    +        $this->assertSame($connection2, $cluster->getConnection($get));
    +
    +        $set = $profile->createCommand('set', array('prefix:{node02:3212}', 'foobar'));
    +        $get = $profile->createCommand('get', array('prefix:{node02:3212}'));
    +        $this->assertSame($connection2, $cluster->getConnection($set));
    +        $this->assertSame($connection2, $cluster->getConnection($get));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\NotSupportedException
    +     * @expectedExceptionMessage Cannot use PING with a cluster of connections
    +     */
    +    public function testThrowsExceptionOnNonShardableCommand()
    +    {
    +        $ping = ServerProfile::getDefault()->createCommand('ping');
    +
    +        $cluster = new PredisCluster();
    +        $cluster->add($this->getMockConnection());
    +
    +        $cluster->getConnection($ping);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testWritesCommandToCorrectConnection()
    +    {
    +        $command = ServerProfile::getDefault()->createCommand('get', array('node01:5431'));
    +
    +        $connection1 = $this->getMockConnection('tcp://host1:7001');
    +        $connection1->expects($this->once())->method('writeCommand')->with($command);
    +
    +        $connection2 = $this->getMockConnection('tcp://host1:7002');
    +        $connection2->expects($this->never())->method('writeCommand');
    +
    +        $cluster = new PredisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $cluster->writeCommand($command);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testReadsCommandFromCorrectConnection()
    +    {
    +        $command = ServerProfile::getDefault()->createCommand('get', array('node02:3212'));
    +
    +        $connection1 = $this->getMockConnection('tcp://host1:7001');
    +        $connection1->expects($this->never())->method('readResponse');
    +
    +        $connection2 = $this->getMockConnection('tcp://host1:7002');
    +        $connection2->expects($this->once())->method('readResponse')->with($command);
    +
    +        $cluster = new PredisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $cluster->readResponse($command);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecutesCommandOnCorrectConnection()
    +    {
    +        $command = ServerProfile::getDefault()->createCommand('get', array('node01:5431'));
    +
    +        $connection1 = $this->getMockConnection('tcp://host1:7001');
    +        $connection1->expects($this->once())->method('executeCommand')->with($command);
    +
    +        $connection2 = $this->getMockConnection('tcp://host1:7002');
    +        $connection2->expects($this->never())->method('executeCommand');
    +
    +        $cluster = new PredisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $cluster->executeCommand($command);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecuteCommandOnEachNode()
    +    {
    +        $ping = ServerProfile::getDefault()->createCommand('ping', array());
    +
    +        $connection1 = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection1->expects($this->once())
    +                    ->method('executeCommand')
    +                    ->with($ping)
    +                    ->will($this->returnValue(true));
    +
    +        $connection2 = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection2->expects($this->once())
    +                    ->method('executeCommand')
    +                    ->with($ping)
    +                    ->will($this->returnValue(false));
    +
    +        $cluster = new PredisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $this->assertSame(array(true, false), $cluster->executeCommandOnNodes($ping));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanBeSerialized()
    +    {
    +        $connection1 = $this->getMockConnection('tcp://host1?alias=first');
    +        $connection2 = $this->getMockConnection('tcp://host2?alias=second');
    +
    +        $cluster = new PredisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        // We use the following line to initialize the underlying hashring.
    +        $cluster->getConnectionByKey('foo');
    +        $unserialized = unserialize(serialize($cluster));
    +
    +        $this->assertEquals($cluster, $unserialized);
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Returns a base mocked connection from Predis\Connection\SingleConnectionInterface.
    +     *
    +     * @param mixed $parameters Optional parameters.
    +     * @return mixed
    +     */
    +    protected function getMockConnection($parameters = null)
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +
    +        if ($parameters) {
    +            $parameters = new ConnectionParameters($parameters);
    +            $hash = "{$parameters->host}:{$parameters->port}";
    +
    +            $connection->expects($this->any())
    +                       ->method('getParameters')
    +                       ->will($this->returnValue($parameters));
    +            $connection->expects($this->any())
    +                       ->method('__toString')
    +                       ->will($this->returnValue($hash));
    +        }
    +
    +        return $connection;
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Connection/RedisClusterTest.php b/vendor/predis/predis/tests/Predis/Connection/RedisClusterTest.php
    new file mode 100644
    index 0000000..0d63e5c
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Connection/RedisClusterTest.php
    @@ -0,0 +1,632 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\ResponseError;
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + *
    + */
    +class RedisClusterTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExposesCommandHashStrategy()
    +    {
    +        $cluster = new RedisCluster();
    +        $this->assertInstanceOf('Predis\Cluster\RedisClusterHashStrategy', $cluster->getCommandHashStrategy());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAddingConnectionsToCluster()
    +    {
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $this->assertSame(2, count($cluster));
    +        $this->assertSame($connection1, $cluster->getConnectionById('127.0.0.1:6379'));
    +        $this->assertSame($connection2, $cluster->getConnectionById('127.0.0.1:6380'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testRemovingConnectionsFromCluster()
    +    {
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +        $connection3 = $this->getMockConnection('tcp://127.0.0.1:6371');
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $this->assertTrue($cluster->remove($connection1));
    +        $this->assertFalse($cluster->remove($connection3));
    +        $this->assertSame(1, count($cluster));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testRemovingConnectionsFromClusterByAlias()
    +    {
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $this->assertTrue($cluster->removeById('127.0.0.1:6380'));
    +        $this->assertFalse($cluster->removeById('127.0.0.1:6390'));
    +        $this->assertSame(1, count($cluster));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCountReturnsNumberOfConnectionsInPool()
    +    {
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +        $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381');
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +        $cluster->add($connection3);
    +
    +        $this->assertSame(3, count($cluster));
    +
    +        $cluster->remove($connection3);
    +
    +        $this->assertSame(2, count($cluster));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConnectForcesAllConnectionsToConnect()
    +    {
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection1->expects($this->once())->method('connect');
    +
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +        $connection2->expects($this->once())->method('connect');
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $cluster->connect();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDisconnectForcesAllConnectionsToDisconnect()
    +    {
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection1->expects($this->once())->method('disconnect');
    +
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +        $connection2->expects($this->once())->method('disconnect');
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $cluster->disconnect();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testIsConnectedReturnsTrueIfAtLeastOneConnectionIsOpen()
    +    {
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection1->expects($this->once())
    +                    ->method('isConnected')
    +                    ->will($this->returnValue(false));
    +
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +        $connection2->expects($this->once())
    +                    ->method('isConnected')
    +                    ->will($this->returnValue(true));
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $this->assertTrue($cluster->isConnected());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testIsConnectedReturnsFalseIfAllConnectionsAreClosed()
    +    {
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection1->expects($this->once())
    +                    ->method('isConnected')
    +                    ->will($this->returnValue(false));
    +
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +        $connection2->expects($this->once())
    +                    ->method('isConnected')
    +                    ->will($this->returnValue(false));
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $this->assertFalse($cluster->isConnected());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanReturnAnIteratorForConnections()
    +    {
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $this->assertInstanceOf('Iterator', $iterator = $cluster->getIterator());
    +        $connections = iterator_to_array($iterator);
    +
    +        $this->assertSame($connection1, $connections[0]);
    +        $this->assertSame($connection2, $connections[1]);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanAssignConnectionsToCustomSlots()
    +    {
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +        $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381');
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +        $cluster->add($connection3);
    +
    +        $cluster->setSlots(0, 1364, '127.0.0.1:6379');
    +        $cluster->setSlots(1365, 2729, '127.0.0.1:6380');
    +        $cluster->setSlots(2730, 4095, '127.0.0.1:6381');
    +
    +        $expectedMap = array_merge(
    +            array_fill(0, 1365, '127.0.0.1:6379'),
    +            array_fill(1364, 1365, '127.0.0.1:6380'),
    +            array_fill(2729, 1366, '127.0.0.1:6381')
    +        );
    +
    +        $this->assertSame($expectedMap, $cluster->getSlotsMap());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAddingConnectionResetsSlotsMap()
    +    {
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +
    +        $cluster->setSlots(0, 4095, '127.0.0.1:6379');
    +        $this->assertSame(array_fill(0, 4096, '127.0.0.1:6379'), $cluster->getSlotsMap());
    +
    +        $cluster->add($connection2);
    +
    +        $this->assertEmpty($cluster->getSlotsMap());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testRemovingConnectionResetsSlotsMap()
    +    {
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $cluster->setSlots(0, 2047, '127.0.0.1:6379');
    +        $cluster->setSlots(2048, 4095, '127.0.0.1:6380');
    +
    +        $expectedMap = array_merge(
    +            array_fill(0, 2048, '127.0.0.1:6379'),
    +            array_fill(2048, 2048, '127.0.0.1:6380')
    +        );
    +
    +        $this->assertSame($expectedMap, $cluster->getSlotsMap());
    +
    +        $cluster->remove($connection1);
    +        $this->assertEmpty($cluster->getSlotsMap());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanAssignConnectionsToCustomSlotsFromParameters()
    +    {
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379?slots=0-5460');
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380?slots=5461-10921');
    +        $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=10922-16383');
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +        $cluster->add($connection3);
    +
    +        $expectedMap = array_merge(
    +            array_fill(0, 5461, '127.0.0.1:6379'),
    +            array_fill(5460, 5461, '127.0.0.1:6380'),
    +            array_fill(10921, 5462, '127.0.0.1:6381')
    +        );
    +
    +        $cluster->buildSlotsMap();
    +
    +        $this->assertSame($expectedMap, $cluster->getSlotsMap());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testReturnsCorrectConnectionUsingSlotID()
    +    {
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +        $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381');
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +        $cluster->add($connection3);
    +
    +        $this->assertSame($connection1, $cluster->getConnectionBySlot(0));
    +        $this->assertSame($connection2, $cluster->getConnectionBySlot(5461));
    +        $this->assertSame($connection3, $cluster->getConnectionBySlot(10922));
    +
    +        $cluster->setSlots(5461, 7096, '127.0.0.1:6380');
    +        $this->assertSame($connection2, $cluster->getConnectionBySlot(5461));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testReturnsCorrectConnectionUsingCommandInstance()
    +    {
    +        $profile = ServerProfile::getDefault();
    +
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +        $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381');
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +        $cluster->add($connection3);
    +
    +        $set = $profile->createCommand('set', array('node:1001', 'foobar'));
    +        $get = $profile->createCommand('get', array('node:1001'));
    +        $this->assertSame($connection1, $cluster->getConnection($set));
    +        $this->assertSame($connection1, $cluster->getConnection($get));
    +
    +        $set = $profile->createCommand('set', array('node:1048', 'foobar'));
    +        $get = $profile->createCommand('get', array('node:1048'));
    +        $this->assertSame($connection2, $cluster->getConnection($set));
    +        $this->assertSame($connection2, $cluster->getConnection($get));
    +
    +        $set = $profile->createCommand('set', array('node:1082', 'foobar'));
    +        $get = $profile->createCommand('get', array('node:1082'));
    +        $this->assertSame($connection3, $cluster->getConnection($set));
    +        $this->assertSame($connection3, $cluster->getConnection($get));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testWritesCommandToCorrectConnection()
    +    {
    +        $command = ServerProfile::getDefault()->createCommand('get', array('node:1001'));
    +
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection1->expects($this->once())->method('writeCommand')->with($command);
    +
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +        $connection2->expects($this->never())->method('writeCommand');
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $cluster->writeCommand($command);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testReadsCommandFromCorrectConnection()
    +    {
    +        $command = ServerProfile::getDefault()->createCommand('get', array('node:1050'));
    +
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection1->expects($this->never())->method('readResponse');
    +
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +        $connection2->expects($this->once())->method('readResponse')->with($command);
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $cluster->readResponse($command);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDoesNotSupportKeyTags()
    +    {
    +        $profile = ServerProfile::getDefault();
    +
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $set = $profile->createCommand('set', array('{node:1001}:foo', 'foobar'));
    +        $get = $profile->createCommand('get', array('{node:1001}:foo'));
    +        $this->assertSame($connection1, $cluster->getConnection($set));
    +        $this->assertSame($connection1, $cluster->getConnection($get));
    +
    +        $set = $profile->createCommand('set', array('{node:1001}:bar', 'foobar'));
    +        $get = $profile->createCommand('get', array('{node:1001}:bar'));
    +        $this->assertSame($connection2, $cluster->getConnection($set));
    +        $this->assertSame($connection2, $cluster->getConnection($get));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAskResponseWithConnectionInPool()
    +    {
    +        $askResponse = new ResponseError('ASK 1970 127.0.0.1:6380');
    +
    +        $command = ServerProfile::getDefault()->createCommand('get', array('node:1001'));
    +
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection1->expects($this->exactly(2))
    +                    ->method('executeCommand')
    +                    ->with($command)
    +                    ->will($this->onConsecutiveCalls($askResponse, 'foobar'));
    +
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +        $connection2->expects($this->exactly(1))
    +                    ->method('executeCommand')
    +                    ->with($command)
    +                    ->will($this->returnValue('foobar'));
    +
    +        $factory = $this->getMock('Predis\Connection\ConnectionFactory');
    +        $factory->expects($this->never())->method('create');
    +
    +        $cluster = new RedisCluster($factory);
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $this->assertSame('foobar', $cluster->executeCommand($command));
    +        $this->assertSame('foobar', $cluster->executeCommand($command));
    +        $this->assertSame(2, count($cluster));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAskResponseWithConnectionNotInPool()
    +    {
    +        $askResponse = new ResponseError('ASK 1970 127.0.0.1:6381');
    +
    +        $command = ServerProfile::getDefault()->createCommand('get', array('node:1001'));
    +
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection1->expects($this->exactly(2))
    +                    ->method('executeCommand')
    +                    ->with($command)
    +                    ->will($this->onConsecutiveCalls($askResponse, 'foobar'));
    +
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +        $connection2->expects($this->never())
    +                    ->method('executeCommand');
    +
    +        $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381');
    +        $connection3->expects($this->once())
    +                    ->method('executeCommand')
    +                    ->with($command)
    +                    ->will($this->returnValue('foobar'));
    +
    +        $factory = $this->getMock('Predis\Connection\ConnectionFactory');
    +        $factory->expects($this->once())
    +                ->method('create')
    +                ->with(array('host' => '127.0.0.1', 'port' => '6381'))
    +                ->will($this->returnValue($connection3));
    +
    +        $cluster = new RedisCluster($factory);
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $this->assertSame('foobar', $cluster->executeCommand($command));
    +        $this->assertSame('foobar', $cluster->executeCommand($command));
    +        $this->assertSame(2, count($cluster));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testMovedResponseWithConnectionInPool()
    +    {
    +        $movedResponse = new ResponseError('MOVED 1970 127.0.0.1:6380');
    +
    +        $command = ServerProfile::getDefault()->createCommand('get', array('node:1001'));
    +
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection1->expects($this->exactly(1))
    +                    ->method('executeCommand')
    +                    ->with($command)
    +                    ->will($this->returnValue($movedResponse));
    +
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +        $connection2->expects($this->exactly(2))
    +                    ->method('executeCommand')
    +                    ->with($command)
    +                    ->will($this->onConsecutiveCalls('foobar', 'foobar'));
    +
    +
    +        $factory = $this->getMock('Predis\Connection\ConnectionFactory');
    +        $factory->expects($this->never())->method('create');
    +
    +        $cluster = new RedisCluster($factory);
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $this->assertSame('foobar', $cluster->executeCommand($command));
    +        $this->assertSame('foobar', $cluster->executeCommand($command));
    +        $this->assertSame(2, count($cluster));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testMovedResponseWithConnectionNotInPool()
    +    {
    +        $movedResponse = new ResponseError('MOVED 1970 127.0.0.1:6381');
    +
    +        $command = ServerProfile::getDefault()->createCommand('get', array('node:1001'));
    +
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379');
    +        $connection1->expects($this->once())
    +                    ->method('executeCommand')
    +                    ->with($command)
    +                    ->will($this->returnValue($movedResponse));
    +
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380');
    +        $connection2->expects($this->never())
    +                    ->method('executeCommand');
    +
    +        $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381');
    +        $connection3->expects($this->exactly(2))
    +                    ->method('executeCommand')
    +                    ->with($command)
    +                    ->will($this->onConsecutiveCalls('foobar', 'foobar'));
    +
    +        $factory = $this->getMock('Predis\Connection\ConnectionFactory');
    +        $factory->expects($this->once())
    +                ->method('create')
    +                ->with(array('host' => '127.0.0.1', 'port' => '6381'))
    +                ->will($this->returnValue($connection3));
    +
    +        $cluster = new RedisCluster($factory);
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +
    +        $this->assertSame('foobar', $cluster->executeCommand($command));
    +        $this->assertSame('foobar', $cluster->executeCommand($command));
    +        $this->assertSame(3, count($cluster));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\NotSupportedException
    +     * @expectedExceptionMessage Cannot use PING with redis-cluster
    +     */
    +    public function testThrowsExceptionOnNonSupportedCommand()
    +    {
    +        $ping = ServerProfile::getDefault()->createCommand('ping');
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($this->getMockConnection('tcp://127.0.0.1:6379'));
    +
    +        $cluster->getConnection($ping);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanBeSerialized()
    +    {
    +        $connection1 = $this->getMockConnection('tcp://127.0.0.1:6379?slots=0-1364');
    +        $connection2 = $this->getMockConnection('tcp://127.0.0.1:6380?slots=1365-2729');
    +        $connection3 = $this->getMockConnection('tcp://127.0.0.1:6381?slots=2730-4095');
    +
    +        $cluster = new RedisCluster();
    +        $cluster->add($connection1);
    +        $cluster->add($connection2);
    +        $cluster->add($connection3);
    +
    +        $cluster->buildSlotsMap();
    +
    +        $unserialized = unserialize(serialize($cluster));
    +
    +        $this->assertEquals($cluster, $unserialized);
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Returns a base mocked connection from Predis\Connection\SingleConnectionInterface.
    +     *
    +     * @param mixed $parameters Optional parameters.
    +     * @return mixed
    +     */
    +    protected function getMockConnection($parameters = null)
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +
    +        if ($parameters) {
    +            $parameters = new ConnectionParameters($parameters);
    +            $hash = "{$parameters->host}:{$parameters->port}";
    +
    +            $connection->expects($this->any())
    +                       ->method('getParameters')
    +                       ->will($this->returnValue($parameters));
    +            $connection->expects($this->any())
    +                       ->method('__toString')
    +                       ->will($this->returnValue($hash));
    +        }
    +
    +        return $connection;
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Connection/StreamConnectionTest.php b/vendor/predis/predis/tests/Predis/Connection/StreamConnectionTest.php
    new file mode 100644
    index 0000000..251dc3c
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Connection/StreamConnectionTest.php
    @@ -0,0 +1,141 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + *
    + */
    +class StreamConnectionTest extends ConnectionTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorDoesNotOpenConnection()
    +    {
    +        $connection = new StreamConnection($this->getParameters());
    +
    +        $this->assertFalse($connection->isConnected());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExposesParameters()
    +    {
    +        $parameters = $this->getParameters();
    +        $connection = new StreamConnection($parameters);
    +
    +        $this->assertSame($parameters, $connection->getParameters());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException InvalidArgumentException
    +     * @expectedExceptionMessage Invalid scheme: udp
    +     */
    +    public function testThrowsExceptionOnInvalidScheme()
    +    {
    +        $parameters = $this->getParameters(array('scheme' => 'udp'));
    +        $connection = new StreamConnection($parameters);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanBeSerialized()
    +    {
    +        $parameters = $this->getParameters(array('alias' => 'redis', 'read_write_timeout' => 10));
    +        $connection = new StreamConnection($parameters);
    +
    +        $unserialized = unserialize(serialize($connection));
    +
    +        $this->assertEquals($connection, $unserialized);
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- INTEGRATION TESTS --------------------------------------------- //
    +    // ******************************************************************** //
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testAcceptsTcpNodelayParameter()
    +    {
    +        if (!version_compare(PHP_VERSION, '5.4.0', '>=')) {
    +            $this->markTestSkipped('Setting TCP_NODELAY on PHP socket streams works on PHP >= 5.4.0');
    +        }
    +
    +        $connection = new StreamConnection($this->getParameters(array('tcp_nodelay' => false)));
    +        $connection->connect();
    +        $this->assertTrue($connection->isConnected());
    +
    +        $connection = new StreamConnection($this->getParameters(array('tcp_nodelay' => true)));
    +        $connection->connect();
    +        $this->assertTrue($connection->isConnected());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testReadsMultibulkRepliesAsIterators()
    +    {
    +        $connection = $this->getConnection($profile, true, array('iterable_multibulk' => true));
    +
    +        $connection->executeCommand($profile->createCommand('rpush', array('metavars', 'foo', 'hoge', 'lol')));
    +        $connection->writeCommand($profile->createCommand('lrange', array('metavars', 0, -1)));
    +
    +        $this->assertInstanceOf('Predis\Iterator\MultiBulkResponse', $iterator = $connection->read());
    +        $this->assertSame(array('foo', 'hoge', 'lol'), iterator_to_array($iterator));
    +    }
    +
    +    /**
    +     * @group connected
    +     * @expectedException Predis\Protocol\ProtocolException
    +     * @expectedExceptionMessage Unknown prefix: 'P'
    +     */
    +    public function testThrowsExceptionOnProtocolDesynchronizationErrors()
    +    {
    +        $connection = $this->getConnection($profile);
    +        $stream = $connection->getResource();
    +
    +        $connection->writeCommand($profile->createCommand('ping'));
    +        fread($stream, 1);
    +
    +        $connection->read();
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getConnection(&$profile = null, $initialize = false, Array $parameters = array())
    +    {
    +        $parameters = $this->getParameters($parameters);
    +        $profile = $this->getProfile();
    +
    +        $connection = new StreamConnection($parameters);
    +
    +        if ($initialize) {
    +            $connection->pushInitCommand($profile->createCommand('select', array($parameters->database)));
    +            $connection->pushInitCommand($profile->createCommand('flushdb'));
    +        }
    +
    +        return $connection;
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Connection/WebdisConnectionTest.php b/vendor/predis/predis/tests/Predis/Connection/WebdisConnectionTest.php
    new file mode 100644
    index 0000000..758fd02
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Connection/WebdisConnectionTest.php
    @@ -0,0 +1,211 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Connection;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + * @group ext-curl
    + * @group ext-phpiredis
    + * @group realm-connection
    + * @group realm-webdis
    + */
    +class WebdisConnectionTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testIsConnectedAlwaysReturnsTrue()
    +    {
    +        $connection = new WebdisConnection($this->getParameters());
    +
    +        $this->assertTrue($connection->isConnected());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\NotSupportedException
    +     * @expectedExceptionMessage The method Predis\Connection\WebdisConnection::writeCommand() is not supported
    +     */
    +    public function testWritingCommandsIsNotSupported()
    +    {
    +        $connection = new WebdisConnection($this->getParameters());
    +        $connection->writeCommand($this->getProfile()->createCommand('ping'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\NotSupportedException
    +     * @expectedExceptionMessage The method Predis\Connection\WebdisConnection::readResponse() is not supported
    +     */
    +    public function testReadingResponsesIsNotSupported()
    +    {
    +        $connection = new WebdisConnection($this->getParameters());
    +        $connection->readResponse($this->getProfile()->createCommand('ping'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\NotSupportedException
    +     * @expectedExceptionMessage The method Predis\Connection\WebdisConnection::read() is not supported
    +     */
    +    public function testReadingFromConnectionIsNotSupported()
    +    {
    +        $connection = new WebdisConnection($this->getParameters());
    +        $connection->read();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\NotSupportedException
    +     * @expectedExceptionMessage The method Predis\Connection\WebdisConnection::pushInitCommand() is not supported
    +     */
    +    public function testPushingInitCommandsIsNotSupported()
    +    {
    +        $connection = new WebdisConnection($this->getParameters());
    +        $connection->pushInitCommand($this->getProfile()->createCommand('ping'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\NotSupportedException
    +     * @expectedExceptionMessage Disabled command: SELECT
    +     */
    +    public function testRejectCommandSelect()
    +    {
    +        $connection = new WebdisConnection($this->getParameters());
    +        $connection->executeCommand($this->getProfile()->createCommand('select', array(0)));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\NotSupportedException
    +     * @expectedExceptionMessage Disabled command: AUTH
    +     */
    +    public function testRejectCommandAuth()
    +    {
    +        $connection = new WebdisConnection($this->getParameters());
    +        $connection->executeCommand($this->getProfile()->createCommand('auth', array('foobar')));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanBeSerialized()
    +    {
    +        $parameters = $this->getParameters(array('alias' => 'webdis'));
    +        $connection = new WebdisConnection($parameters);
    +
    +        $unserialized = unserialize(serialize($connection));
    +
    +        $this->assertInstanceOf('Predis\Connection\WebdisConnection', $unserialized);
    +        $this->assertEquals($parameters, $unserialized->getParameters());
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- INTEGRATION TESTS --------------------------------------------- //
    +    // ******************************************************************** //
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testExecutesCommandsOnServer()
    +    {
    +        $connection = $this->getConnection($profile);
    +
    +        $cmdPing   = $profile->createCommand('ping');
    +        $cmdEcho   = $profile->createCommand('echo', array('echoed'));
    +        $cmdGet    = $profile->createCommand('get', array('foobar'));
    +        $cmdRpush  = $profile->createCommand('rpush', array('metavars', 'foo', 'hoge', 'lol'));
    +        $cmdLrange = $profile->createCommand('lrange', array('metavars', 0, -1));
    +
    +        $this->assertSame('PONG', $connection->executeCommand($cmdPing));
    +        $this->assertSame('echoed', $connection->executeCommand($cmdEcho));
    +        $this->assertNull($connection->executeCommand($cmdGet));
    +        $this->assertSame(3, $connection->executeCommand($cmdRpush));
    +        $this->assertSame(array('foo', 'hoge', 'lol'), $connection->executeCommand($cmdLrange));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @group slow
    +     * @expectedException Predis\Connection\ConnectionException
    +     */
    +    public function testThrowExceptionWhenUnableToConnect()
    +    {
    +        $parameters = $this->getParameters(array('host' => '169.254.10.10'));
    +        $connection = new WebdisConnection($parameters);
    +        $connection->executeCommand($this->getProfile()->createCommand('ping'));
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Returns a named array with the default connection parameters and their values.
    +     *
    +     * @return Array Default connection parameters.
    +     */
    +    protected function getDefaultParametersArray()
    +    {
    +        return array(
    +            'scheme' => 'http',
    +            'host' => WEBDIS_SERVER_HOST,
    +            'port' => WEBDIS_SERVER_PORT,
    +        );
    +    }
    +
    +    /**
    +     * Returns a new instance of connection parameters.
    +     *
    +     * @param array $additional Additional connection parameters.
    +     * @return ConnectionParameters Default connection parameters.
    +     */
    +    protected function getParameters($additional = array())
    +    {
    +        $parameters = array_merge($this->getDefaultParametersArray(), $additional);
    +        $parameters = new ConnectionParameters($parameters);
    +
    +        return $parameters;
    +    }
    +
    +    /**
    +     * Returns a new instance of server profile.
    +     *
    +     * @param array $additional Additional connection parameters.
    +     * @return ServerProfile
    +     */
    +    protected function getProfile($version = null)
    +    {
    +        return ServerProfile::get($version ?: REDIS_SERVER_VERSION);
    +    }
    +
    +    /**
    +     * Returns a new instance of a connection instance.
    +     *
    +     * @param array $parameters Additional connection parameters.
    +     * @return WebdisConnection
    +     */
    +    protected function getConnection(&$profile = null, Array $parameters = array())
    +    {
    +        $parameters = $this->getParameters($parameters);
    +        $profile = $this->getProfile();
    +
    +        $connection = new WebdisConnection($parameters);
    +        $connection->executeCommand($profile->createCommand('flushdb'));
    +
    +        return $connection;
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/HelpersTest.php b/vendor/predis/predis/tests/Predis/HelpersTest.php
    new file mode 100644
    index 0000000..dbceb67
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/HelpersTest.php
    @@ -0,0 +1,70 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class HelpersTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testOnCommunicationException()
    +    {
    +        $this->setExpectedException('Predis\CommunicationException');
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->once())->method('isConnected')->will($this->returnValue(true));
    +        $connection->expects($this->once())->method('disconnect');
    +
    +        $exception = $this->getMockForAbstractClass('Predis\CommunicationException', array($connection));
    +
    +        Helpers::onCommunicationException($exception);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterArrayArguments()
    +    {
    +        $arguments = array('arg1', 'arg2', 'arg3', 'arg4');
    +
    +        $this->assertSame($arguments, Helpers::filterArrayArguments($arguments));
    +        $this->assertSame($arguments, Helpers::filterArrayArguments(array($arguments)));
    +
    +        $arguments = array(array(), array());
    +        $this->assertSame($arguments, Helpers::filterArrayArguments($arguments));
    +
    +        $arguments = array(new \stdClass());
    +        $this->assertSame($arguments, Helpers::filterArrayArguments($arguments));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterVariadicValues()
    +    {
    +        $arguments = array('key', 'value1', 'value2', 'value3');
    +
    +        $this->assertSame($arguments, Helpers::filterVariadicValues($arguments));
    +        $this->assertSame($arguments, Helpers::filterVariadicValues(array('key', array('value1', 'value2', 'value3'))));
    +
    +        $arguments = array(array(), array());
    +        $this->assertSame($arguments, Helpers::filterArrayArguments($arguments));
    +
    +        $arguments = array(new \stdClass());
    +        $this->assertSame($arguments, Helpers::filterArrayArguments($arguments));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Iterator/MultiBulkResponseSimpleTest.php b/vendor/predis/predis/tests/Predis/Iterator/MultiBulkResponseSimpleTest.php
    new file mode 100644
    index 0000000..3701fe8
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Iterator/MultiBulkResponseSimpleTest.php
    @@ -0,0 +1,139 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Iterator;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Client;
    +
    +/**
    + * @group realm-iterators
    + */
    +class MultiBulkResponseSimpleTest extends StandardTestCase
    +{
    +    /**
    +     * @group connected
    +     */
    +    public function testIterableMultibulk()
    +    {
    +        $client = $this->getClient();
    +        $client->rpush('metavars', 'foo', 'hoge', 'lol');
    +
    +        $this->assertInstanceOf('Iterator', $iterator = $client->lrange('metavars', 0, -1));
    +        $this->assertInstanceOf('Predis\Iterator\MultiBulkResponseSimple', $iterator);
    +        $this->assertTrue($iterator->valid());
    +        $this->assertSame(3, $iterator->count());
    +
    +        $this->assertSame('foo', $iterator->current());
    +        $this->assertSame(1, $iterator->next());
    +        $this->assertTrue($iterator->valid());
    +
    +        $this->assertSame('hoge', $iterator->current());
    +        $this->assertSame(2, $iterator->next());
    +        $this->assertTrue($iterator->valid());
    +
    +        $this->assertSame('lol', $iterator->current());
    +        $this->assertSame(3, $iterator->next());
    +        $this->assertFalse($iterator->valid());
    +
    +        $this->assertTrue($client->ping());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testIterableMultibulkCanBeWrappedAsTupleIterator()
    +    {
    +        $client = $this->getClient();
    +        $client->mset('foo', 'bar', 'hoge', 'piyo');
    +
    +        $this->assertInstanceOf('Predis\Iterator\MultiBulkResponseSimple', $iterator = $client->mget('foo', 'bar'));
    +        $this->assertInstanceOf('Predis\Iterator\MultiBulkResponseTuple', $iterator->asTuple());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testSyncWithFalseConsumesReplyFromUnderlyingConnection()
    +    {
    +        $client = $this->getClient();
    +        $client->rpush('metavars', 'foo', 'hoge', 'lol');
    +
    +        $iterator = $client->lrange('metavars', 0, -1);
    +        $iterator->sync(false);
    +
    +        $this->assertTrue($client->isConnected());
    +        $this->assertTrue($client->ping());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testSyncWithTrueDropsUnderlyingConnection()
    +    {
    +        $client = $this->getClient();
    +        $client->rpush('metavars', 'foo', 'hoge', 'lol');
    +
    +        $iterator = $client->lrange('metavars', 0, -1);
    +        $iterator->sync(true);
    +
    +        $this->assertFalse($client->isConnected());
    +        $this->assertTrue($client->ping());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testGarbageCollectorDropsUnderlyingConnection()
    +    {
    +        $client = $this->getClient();
    +        $client->rpush('metavars', 'foo', 'hoge', 'lol');
    +
    +        $iterator = $client->lrange('metavars', 0, -1);
    +
    +        unset($iterator);
    +
    +        $this->assertFalse($client->isConnected());
    +        $this->assertTrue($client->ping());
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Returns a new client instance.
    +     *
    +     * @return Client
    +     */
    +    protected function getClient()
    +    {
    +        $parameters = array(
    +            'host' => REDIS_SERVER_HOST,
    +            'port' => REDIS_SERVER_PORT,
    +            'iterable_multibulk' => true,
    +            'read_write_timeout' => 2,
    +        );
    +
    +        $options = array(
    +            'profile' => REDIS_SERVER_VERSION,
    +        );
    +
    +        $client = new Client($parameters, $options);
    +        $client->connect();
    +        $client->select(REDIS_SERVER_DBNUM);
    +        $client->flushdb();
    +
    +        return $client;
    +    }
    +
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Iterator/MultiBulkResponseTupleTest.php b/vendor/predis/predis/tests/Predis/Iterator/MultiBulkResponseTupleTest.php
    new file mode 100644
    index 0000000..cb15392
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Iterator/MultiBulkResponseTupleTest.php
    @@ -0,0 +1,125 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Iterator;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Client;
    +
    +/**
    + * @group realm-iterators
    + */
    +class MultiBulkResponseTupleTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     * @expectedException RuntimeException
    +     * @expectedExceptionMessage Cannot initialize a tuple iterator with an already initiated iterator
    +     */
    +    public function testInitiatedMultiBulkIteratorsAreNotValid()
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $iterator = new MultiBulkResponseSimple($connection, 2);
    +        $iterator->next();
    +
    +        new MultiBulkResponseTuple($iterator);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException UnexpectedValueException
    +     * @expectedExceptionMessage Invalid reply size for a tuple iterator [3]
    +     */
    +    public function testMultiBulkWithOddSizesAreInvalid()
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $iterator = new MultiBulkResponseSimple($connection, 3);
    +
    +        new MultiBulkResponseTuple($iterator);
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testIterableMultibulk()
    +    {
    +        $client = $this->getClient();
    +        $client->zadd('metavars', 1, 'foo', 2, 'hoge', 3, 'lol');
    +
    +        $this->assertInstanceOf('OuterIterator', $iterator = $client->zrange('metavars', 0, -1, 'withscores')->asTuple());
    +        $this->assertInstanceOf('Predis\Iterator\MultiBulkResponseTuple', $iterator);
    +        $this->assertInstanceOf('Predis\Iterator\MultiBulkResponseSimple', $iterator->getInnerIterator());
    +        $this->assertTrue($iterator->valid());
    +        $this->assertSame(3, $iterator->count());
    +
    +        $this->assertSame(array('foo', '1'), $iterator->current());
    +        $this->assertSame(1, $iterator->next());
    +        $this->assertTrue($iterator->valid());
    +
    +        $this->assertSame(array('hoge', '2'), $iterator->current());
    +        $this->assertSame(2, $iterator->next());
    +        $this->assertTrue($iterator->valid());
    +
    +        $this->assertSame(array('lol', '3'), $iterator->current());
    +        $this->assertSame(3, $iterator->next());
    +        $this->assertFalse($iterator->valid());
    +
    +        $this->assertTrue($client->ping());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testGarbageCollectorDropsUnderlyingConnection()
    +    {
    +        $client = $this->getClient();
    +        $client->zadd('metavars', 1, 'foo', 2, 'hoge', 3, 'lol');
    +
    +        $iterator = $client->zrange('metavars', 0, -1, 'withscores')->asTuple();
    +
    +        unset($iterator);
    +
    +        $this->assertFalse($client->isConnected());
    +        $this->assertTrue($client->ping());
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Returns a new client instance.
    +     *
    +     * @return Client
    +     */
    +    protected function getClient()
    +    {
    +        $parameters = array(
    +            'host' => REDIS_SERVER_HOST,
    +            'port' => REDIS_SERVER_PORT,
    +            'iterable_multibulk' => true,
    +            'read_write_timeout' => 2,
    +        );
    +
    +        $options = array(
    +            'profile' => REDIS_SERVER_VERSION,
    +        );
    +
    +        $client = new Client($parameters, $options);
    +        $client->connect();
    +        $client->select(REDIS_SERVER_DBNUM);
    +        $client->flushdb();
    +
    +        return $client;
    +    }
    +
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Monitor/MonitorContextTest.php b/vendor/predis/predis/tests/Predis/Monitor/MonitorContextTest.php
    new file mode 100644
    index 0000000..2414584
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Monitor/MonitorContextTest.php
    @@ -0,0 +1,197 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Monitor;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Client;
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + * @group realm-monitor
    + */
    +class MonitorContextTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\NotSupportedException
    +     * @expectedExceptionMessage The current profile does not support the MONITOR command
    +     */
    +    public function testMonitorContextRequireMonitorCommand()
    +    {
    +        $profile = $this->getMock('Predis\Profile\ServerProfileInterface');
    +        $profile->expects($this->once())
    +                ->method('supportsCommand')
    +                ->with('monitor')
    +                ->will($this->returnValue(false));
    +
    +        $client = new Client(null, array('profile' => $profile));
    +        $monitor = new MonitorContext($client);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\NotSupportedException
    +     * @expectedExceptionMessage Cannot initialize a monitor context when using aggregated connections
    +     */
    +    public function testMonitorContextDoesNotWorkOnClusters()
    +    {
    +        $cluster = $this->getMock('Predis\Connection\ClusterConnectionInterface');
    +
    +        $client = new Client($cluster);
    +        $monitor = new MonitorContext($client);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorOpensContext()
    +    {
    +        $cmdMonitor = ServerProfile::getDefault()->createCommand('monitor');
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +
    +        $client = $this->getMock('Predis\Client', array('createCommand', 'executeCommand'), array($connection));
    +        $client->expects($this->once())
    +               ->method('createCommand')
    +               ->with('monitor', array())
    +               ->will($this->returnValue($cmdMonitor));
    +        $client->expects($this->once())
    +               ->method('executeCommand')
    +               ->with($cmdMonitor);
    +
    +        $monitor = new MonitorContext($client);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @todo We should investigate why disconnect is invoked 2 times in this test,
    +     *       but the reason is probably that the GC invokes __destruct() on monitor
    +     *       thus calling $client->disconnect() a second time at the end of the test.
    +     */
    +    public function testClosingContextClosesConnection()
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +
    +        $client = $this->getMock('Predis\Client', array('disconnect'), array($connection));
    +        $client->expects($this->exactly(2))->method('disconnect');
    +
    +        $monitor = new MonitorContext($client);
    +        $monitor->closeContext();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGarbageCollectorRunClosesContext()
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +
    +        $client = $this->getMock('Predis\Client', array('disconnect'), array($connection));
    +        $client->expects($this->once())->method('disconnect');
    +
    +        $monitor = new MonitorContext($client);
    +        unset($monitor);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testReadsMessageFromConnectionToRedis24()
    +    {
    +        $message = '1323367530.939137 (db 15) "MONITOR"';
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->once())
    +                   ->method('read')
    +                   ->will($this->returnValue($message));
    +
    +        $client = new Client($connection);
    +        $monitor = new MonitorContext($client);
    +
    +        $payload = $monitor->current();
    +        $this->assertSame(1323367530, (int) $payload->timestamp);
    +        $this->assertSame(15, $payload->database);
    +        $this->assertNull($payload->client);
    +        $this->assertSame('MONITOR', $payload->command);
    +        $this->assertNull($payload->arguments);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testReadsMessageFromConnectionToRedis26()
    +    {
    +        $message = '1323367530.939137 [15 127.0.0.1:37265] "MONITOR"';
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->once())
    +                   ->method('read')
    +                   ->will($this->returnValue($message));
    +
    +        $client = new Client($connection);
    +        $monitor = new MonitorContext($client);
    +
    +        $payload = $monitor->current();
    +        $this->assertSame(1323367530, (int) $payload->timestamp);
    +        $this->assertSame(15, $payload->database);
    +        $this->assertSame('127.0.0.1:37265', $payload->client);
    +        $this->assertSame('MONITOR', $payload->command);
    +        $this->assertNull($payload->arguments);
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- INTEGRATION TESTS --------------------------------------------- //
    +    // ******************************************************************** //
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testMonitorAgainstRedisServer()
    +    {
    +        $parameters = array(
    +            'host' => REDIS_SERVER_HOST,
    +            'port' => REDIS_SERVER_PORT,
    +            'database' => REDIS_SERVER_DBNUM,
    +            // Prevents suite from handing on broken test
    +            'read_write_timeout' => 2,
    +        );
    +
    +        $options = array('profile' => REDIS_SERVER_VERSION);
    +        $echoed = array();
    +
    +        $producer = new Client($parameters, $options);
    +        $producer->connect();
    +
    +        $consumer = new Client($parameters, $options);
    +        $consumer->connect();
    +
    +        $monitor = new MonitorContext($consumer);
    +
    +        $producer->echo('message1');
    +        $producer->echo('message2');
    +        $producer->echo('QUIT');
    +
    +        foreach ($monitor as $message) {
    +            if ($message->command == 'ECHO') {
    +                $echoed[] = $arguments = trim($message->arguments, '"');
    +                if ($arguments == 'QUIT') {
    +                    $monitor->closeContext();
    +                }
    +            }
    +        }
    +
    +        $this->assertSame(array('message1', 'message2', 'QUIT'), $echoed);
    +        $this->assertFalse($monitor->valid());
    +        $this->assertTrue($consumer->ping());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Option/AbstractOptionTest.php b/vendor/predis/predis/tests/Predis/Option/AbstractOptionTest.php
    new file mode 100644
    index 0000000..9a6c235
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Option/AbstractOptionTest.php
    @@ -0,0 +1,79 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class AbstractOptionTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationReturnsTheSameObject()
    +    {
    +        $value = new \stdClass();
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = $this->getMockForAbstractClass('Predis\Option\AbstractOption');
    +
    +        $this->assertSame($value, $option->filter($options, $value));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDefaultReturnsNull()
    +    {
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = $this->getMockForAbstractClass('Predis\Option\AbstractOption');
    +
    +        $this->assertNull($option->getDefault($options));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testInvokePerformsValidationWhenValueIsSet()
    +    {
    +        $value = new \stdClass();
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +
    +        $option = $this->getMock('Predis\Option\AbstractOption', array('filter', 'getDefault'));
    +        $option->expects($this->once())
    +               ->method('filter')
    +               ->with($options, $value)
    +               ->will($this->returnValue($value));
    +        $option->expects($this->never())->method('getDefault');
    +
    +        $this->assertSame($value, $option($options, $value));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testInvokeReturnsDefaultWhenValueIsNotSet()
    +    {
    +        $expected = new \stdClass();
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +
    +        $option = $this->getMock('Predis\Option\AbstractOption', array('filter', 'getDefault'));
    +        $option->expects($this->never())->method('filter');
    +        $option->expects($this->once())
    +               ->method('getDefault')
    +               ->with($options)
    +               ->will($this->returnValue($expected));
    +
    +        $this->assertSame($expected, $option($options, null));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Option/ClientClusterTest.php b/vendor/predis/predis/tests/Predis/Option/ClientClusterTest.php
    new file mode 100644
    index 0000000..2abfd13
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Option/ClientClusterTest.php
    @@ -0,0 +1,127 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class ClientClusterTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationAcceptsFQNStringAsInitializer()
    +    {
    +        $clusterClass = get_class($this->getMock('Predis\Connection\ClusterConnectionInterface'));
    +
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientCluster();
    +
    +        $cluster = $option->filter($options, $clusterClass);
    +
    +        $this->assertInstanceOf('Predis\Connection\ClusterConnectionInterface', $cluster);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationRecognizesCertainPredefinedShortNames()
    +    {
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientCluster();
    +
    +        $cluster = $option->filter($options, 'predis');
    +
    +        $this->assertInstanceOf('Predis\Connection\ClusterConnectionInterface', $cluster);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationAcceptsCallableObjectAsInitializers()
    +    {
    +        $value = $this->getMock('Predis\Connection\ClusterConnectionInterface');
    +
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientCluster();
    +
    +        $initializer = $this->getMock('stdClass', array('__invoke'));
    +        $initializer->expects($this->once())
    +                    ->method('__invoke')
    +                    ->with($this->isInstanceOf('Predis\Option\ClientOptionsInterface'), $option)
    +                    ->will($this->returnValue($value));
    +
    +        $cluster = $option->filter($options, $initializer, $option);
    +
    +        $this->assertInstanceOf('Predis\Connection\ClusterConnectionInterface', $cluster);
    +        $this->assertSame($value, $cluster);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationThrowsExceptionOnInvalidClassTypes()
    +    {
    +        $this->setExpectedException('InvalidArgumentException');
    +
    +        $connectionClass = get_class($this->getMock('Predis\Connection\SingleConnectionInterface'));
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientCluster();
    +
    +        $option->filter($options, $connectionClass);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationThrowsExceptionOnInvalidShortName()
    +    {
    +        $this->setExpectedException('InvalidArgumentException');
    +
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientCluster();
    +
    +        $option->filter($options, 'unknown');
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationThrowsExceptionOnInvalidObjectReturnedByCallback()
    +    {
    +        $this->setExpectedException('InvalidArgumentException');
    +
    +        $value = function ($options) {
    +            return new \stdClass();
    +        };
    +
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientCluster();
    +
    +        $option->filter($options, $value);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationThrowsExceptionOnInvalidArguments()
    +    {
    +        $this->setExpectedException('InvalidArgumentException');
    +
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientCluster();
    +
    +        $option->filter($options, new \stdClass());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Option/ClientConnectionFactoryTest.php b/vendor/predis/predis/tests/Predis/Option/ClientConnectionFactoryTest.php
    new file mode 100644
    index 0000000..99b256c
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Option/ClientConnectionFactoryTest.php
    @@ -0,0 +1,138 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Connection\ConnectionFactory;
    +
    +/**
    + *
    + */
    +class ClientConnectionFactoryTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationReturnsDefaultFactoryWithSchemeDefinitionsArray()
    +    {
    +        $connectionClass = get_class($this->getMock('Predis\Connection\SingleConnectionInterface'));
    +        $value = array('tcp' => $connectionClass, 'redis' => $connectionClass);
    +
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +
    +        $default = $this->getMock('Predis\Connection\ConnectionFactoryInterface');
    +        $default->expects($this->exactly(2))
    +                ->method('define')
    +                ->with($this->matchesRegularExpression('/^tcp|redis$/'), $connectionClass);
    +
    +        $option = $this->getMock('Predis\Option\ClientConnectionFactory', array('getDefault'));
    +        $option->expects($this->once())
    +               ->method('getDefault')
    +               ->with($options)
    +               ->will($this->returnValue($default));
    +
    +        $factory = $option->filter($options, $value);
    +
    +        $this->assertInstanceOf('Predis\Connection\ConnectionFactoryInterface', $factory);
    +        $this->assertSame($default, $factory);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationAcceptsFactoryInstancesAsValue()
    +    {
    +        $value = $this->getMock('Predis\Connection\ConnectionFactoryInterface');
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +
    +        $option = $this->getMock('Predis\Option\ClientConnectionFactory', array('getDefault'));
    +        $option->expects($this->never())->method('getDefault');
    +
    +        $this->assertSame($value, $option->filter($options, $value));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationAcceptsCallableObjectAsInitializers()
    +    {
    +        $value = $this->getMock('Predis\Connection\ConnectionFactoryInterface');
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientConnectionFactory();
    +
    +        $initializer = $this->getMock('stdClass', array('__invoke'));
    +        $initializer->expects($this->once())
    +                    ->method('__invoke')
    +                    ->with($this->isInstanceOf('Predis\Option\ClientOptionsInterface'), $option)
    +                    ->will($this->returnValue($value));
    +
    +        $cluster = $option->filter($options, $initializer, $option);
    +
    +        $this->assertInstanceOf('Predis\Connection\ConnectionFactoryInterface', $cluster);
    +        $this->assertSame($value, $cluster);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationAcceptsStringAsValue()
    +    {
    +        $factory = 'Predis\Connection\ConnectionFactory';
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +
    +        $option = $this->getMock('Predis\Option\ClientConnectionFactory', array('getDefault'));
    +        $option->expects($this->never())->method('getDefault');
    +
    +        $this->assertInstanceOf($factory, $option->filter($options, $factory));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationThrowsExceptionOnWrongInvalidArguments()
    +    {
    +        $this->setExpectedException('InvalidArgumentException');
    +
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientConnectionFactory();
    +
    +        $option->filter($options, new \stdClass());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testInvokeReturnsSpecifiedFactoryOrDefault()
    +    {
    +        $value = $this->getMock('Predis\Connection\ConnectionFactoryInterface');
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +
    +        $option = $this->getMock('Predis\Option\ClientConnectionFactory', array('filter', 'getDefault'));
    +        $option->expects($this->once())
    +               ->method('filter')
    +               ->with($options, $value)
    +               ->will($this->returnValue($value));
    +        $option->expects($this->never())->method('getDefault');
    +
    +        $this->assertInstanceOf('Predis\Connection\ConnectionFactoryInterface', $option($options, $value));
    +
    +        $option = $this->getMock('Predis\Option\ClientConnectionFactory', array('filter', 'getDefault'));
    +        $option->expects($this->never())->method('filter');
    +        $option->expects($this->once())
    +               ->method('getDefault')
    +               ->with($options)
    +               ->will($this->returnValue($value));
    +
    +        $this->assertInstanceOf('Predis\Connection\ConnectionFactoryInterface', $option($options, null));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Option/ClientExceptionsTest.php b/vendor/predis/predis/tests/Predis/Option/ClientExceptionsTest.php
    new file mode 100644
    index 0000000..27fe298
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Option/ClientExceptionsTest.php
    @@ -0,0 +1,31 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class ClientExceptionsTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDefaultReturnsTrue()
    +    {
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientExceptions();
    +
    +        $this->assertTrue($option->getDefault($options));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Option/ClientOptionsTest.php b/vendor/predis/predis/tests/Predis/Option/ClientOptionsTest.php
    new file mode 100644
    index 0000000..12e57fc
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Option/ClientOptionsTest.php
    @@ -0,0 +1,128 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + * @todo We should test the inner work performed by this class
    + *       using mock objects, but it is quite hard to to that.
    + */
    +class ClientOptionsTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithoutArguments()
    +    {
    +        $options = new ClientOptions();
    +
    +        $this->assertInstanceOf('Predis\Connection\ConnectionFactoryInterface', $options->connections);
    +        $this->assertInstanceOf('Predis\Profile\ServerProfileInterface', $options->profile);
    +        $this->assertInstanceOf('Predis\Connection\ClusterConnectionInterface', $options->cluster);
    +        $this->assertNull($options->prefix);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithArrayArgument()
    +    {
    +        $options = new ClientOptions(array(
    +            'cluster' => 'Predis\Connection\PredisCluster',
    +            'connections' => 'Predis\Connection\ConnectionFactory',
    +            'prefix' => 'prefix:',
    +            'profile' => '2.0',
    +            'exceptions' => false,
    +        ));
    +
    +        $this->assertInstanceOf('Predis\Connection\ConnectionFactoryInterface', $options->connections);
    +        $this->assertInstanceOf('Predis\Profile\ServerProfileInterface', $options->profile);
    +        $this->assertInstanceOf('Predis\Connection\ClusterConnectionInterface', $options->cluster);
    +        $this->assertInstanceOf('Predis\Command\Processor\CommandProcessorInterface', $options->prefix);
    +        $this->assertInternalType('bool', $options->exceptions);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testHandlesCustomOptionsWithoutHandlers()
    +    {
    +        $options = new ClientOptions(array(
    +            'custom' => 'foobar',
    +        ));
    +
    +        $this->assertSame('foobar', $options->custom);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testIsSetReturnsIfOptionHasBeenSetByUser()
    +    {
    +        $options = new ClientOptions(array(
    +            'prefix' => 'prefix:',
    +            'custom' => 'foobar',
    +        ));
    +
    +        $this->assertTrue(isset($options->prefix));
    +        $this->assertTrue(isset($options->custom));
    +        $this->assertFalse(isset($options->profile));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetDefaultUsingOptionName()
    +    {
    +        $options = new ClientOptions();
    +
    +        $this->assertInstanceOf('Predis\Connection\PredisCluster', $options->getDefault('cluster'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetDefaultUsingUnhandledOptionName()
    +    {
    +        $options = new ClientOptions();
    +        $option = new ClientCluster();
    +
    +        $this->assertNull($options->getDefault('foo'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetDefaultUsingOptionInstance()
    +    {
    +        $options = new ClientOptions();
    +        $option = new ClientCluster();
    +
    +        $this->assertInstanceOf('Predis\Connection\PredisCluster', $options->getDefault($option));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetDefaultUsingUnhandledOptionInstance()
    +    {
    +        $options = new ClientOptions();
    +        $option = new CustomOption(array(
    +            'default' => function ($options) {
    +                return 'foo';
    +            },
    +        ));
    +
    +        $this->assertSame('foo', $options->getDefault($option));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Option/ClientPrefixTest.php b/vendor/predis/predis/tests/Predis/Option/ClientPrefixTest.php
    new file mode 100644
    index 0000000..f007c79
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Option/ClientPrefixTest.php
    @@ -0,0 +1,59 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class ClientPrefixTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationReturnsCommandProcessor()
    +    {
    +        $value = 'prefix:';
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientPrefix();
    +
    +        $return = $option->filter($options, $value);
    +
    +        $this->assertInstanceOf('Predis\Command\Processor\CommandProcessorInterface', $return);
    +        $this->assertInstanceOf('Predis\Command\Processor\KeyPrefixProcessor', $return);
    +        $this->assertEquals($value, $return->getPrefix());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDefaultReturnsNull()
    +    {
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientPrefix();
    +
    +        $this->assertNull($option->getDefault($options));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testInvokeReturnsCommandProcessorOrNull()
    +    {
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientPrefix();
    +
    +        $this->assertInstanceOf('Predis\Command\Processor\CommandProcessorInterface', $option($options, 'prefix:'));
    +        $this->assertNull($option($options, null));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Option/ClientProfileTest.php b/vendor/predis/predis/tests/Predis/Option/ClientProfileTest.php
    new file mode 100644
    index 0000000..483f674
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Option/ClientProfileTest.php
    @@ -0,0 +1,211 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Command\Processor\KeyPrefixProcessor;
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + *
    + */
    +class ClientProfileTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationReturnsServerProfileWithStringValue()
    +    {
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientProfile();
    +
    +        $profile = $option->filter($options, '2.0');
    +
    +        $this->assertInstanceOf('Predis\Profile\ServerProfileInterface', $profile);
    +        $this->assertEquals('2.0', $profile->getVersion());
    +        $this->assertNull($profile->getProcessor());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationAcceptsProfileInstancesAsValue()
    +    {
    +        $value = ServerProfile::get('2.0');
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientProfile();
    +
    +        $profile = $option->filter($options, $value);
    +
    +        $this->assertInstanceOf('Predis\Profile\ServerProfileInterface', $profile);
    +        $this->assertEquals('2.0', $profile->getVersion());
    +        $this->assertNull($profile->getProcessor());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationAcceptsCallableObjectAsInitializers()
    +    {
    +        $value = $this->getMock('Predis\Profile\ServerProfileInterface');
    +
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientProfile();
    +
    +        $initializer = $this->getMock('stdClass', array('__invoke'));
    +        $initializer->expects($this->once())
    +                    ->method('__invoke')
    +                    ->with($this->isInstanceOf('Predis\Option\ClientOptionsInterface'), $option)
    +                    ->will($this->returnValue($value));
    +
    +        $profile = $option->filter($options, $initializer, $option);
    +
    +        $this->assertInstanceOf('Predis\Profile\ServerProfileInterface', $profile);
    +        $this->assertSame($value, $profile);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationThrowsExceptionOnWrongInvalidArguments()
    +    {
    +        $this->setExpectedException('InvalidArgumentException');
    +
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientProfile();
    +
    +        $option->filter($options, new \stdClass());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDefaultReturnsDefaultServerProfile()
    +    {
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientProfile();
    +
    +        $profile = $option->getDefault($options);
    +
    +        $this->assertInstanceOf('Predis\Profile\ServerProfileInterface', $profile);
    +        $this->assertInstanceOf(get_class(ServerProfile::getDefault()), $profile);
    +        $this->assertNull($profile->getProcessor());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testInvokeReturnsSpecifiedServerProfileOrDefault()
    +    {
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientProfile();
    +
    +        $profile = $option($options, '2.0');
    +
    +        $this->assertInstanceOf('Predis\Profile\ServerProfileInterface', $profile);
    +        $this->assertEquals('2.0', $profile->getVersion());
    +        $this->assertNull($profile->getProcessor());
    +
    +        $profile = $option($options, null);
    +
    +        $this->assertInstanceOf('Predis\Profile\ServerProfileInterface', $profile);
    +        $this->assertInstanceOf(get_class(ServerProfile::getDefault()), $profile);
    +        $this->assertNull($profile->getProcessor());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @todo Can't we when trap __isset when mocking an interface? Doesn't seem to work here.
    +     */
    +    public function testFilterSetsPrefixProcessorFromClientOptions()
    +    {
    +        $options = $this->getMock('Predis\Option\ClientOptions', array('__isset', '__get'));
    +        $options->expects($this->once())
    +                ->method('__isset')
    +                ->with('prefix')
    +                ->will($this->returnValue(true));
    +        $options->expects($this->once())
    +                ->method('__get')
    +                ->with('prefix')
    +                ->will($this->returnValue(new KeyPrefixProcessor('prefix:')));
    +
    +        $option = new ClientProfile();
    +
    +        $profile = $option->filter($options, '2.0');
    +
    +        $this->assertInstanceOf('Predis\Profile\ServerProfileInterface', $profile);
    +        $this->assertEquals('2.0', $profile->getVersion());
    +        $this->assertInstanceOf('Predis\Command\Processor\KeyPrefixProcessor', $profile->getProcessor());
    +        $this->assertEquals('prefix:', $profile->getProcessor()->getPrefix());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @todo Can't we when trap __isset when mocking an interface? Doesn't seem to work here.
    +     */
    +    public function testDefaultSetsPrefixProcessorFromClientOptions()
    +    {
    +        $options = $this->getMock('Predis\Option\ClientOptions', array('__isset', '__get'));
    +        $options->expects($this->once())
    +                ->method('__isset')
    +                ->with('prefix')
    +                ->will($this->returnValue(true));
    +        $options->expects($this->once())
    +                ->method('__get')
    +                ->with('prefix')
    +                ->will($this->returnValue(new KeyPrefixProcessor('prefix:')));
    +
    +        $option = new ClientProfile();
    +
    +        $profile = $option->getDefault($options);
    +
    +        $this->assertInstanceOf('Predis\Profile\ServerProfileInterface', $profile);
    +        $this->assertInstanceOf(get_class(ServerProfile::getDefault()), $profile);
    +        $this->assertInstanceOf('Predis\Command\Processor\KeyPrefixProcessor', $profile->getProcessor());
    +        $this->assertEquals('prefix:', $profile->getProcessor()->getPrefix());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationDoesNotSetPrefixProcessorWhenValueIsProfileInstance()
    +    {
    +        $options = $this->getMock('Predis\Option\ClientOptions', array('__isset', '__get'));
    +        $options->expects($this->never())->method('__isset');
    +        $options->expects($this->never())->method('__get');
    +
    +        $option = new ClientProfile();
    +
    +        $profile = $option->filter($options, ServerProfile::getDefault());
    +
    +        $this->assertInstanceOf('Predis\Profile\ServerProfileInterface', $profile);
    +        $this->assertNull($profile->getProcessor());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException InvalidArgumentException
    +     * @expectedExceptionMessage Invalid value for the profile option
    +     */
    +    public function testValidationThrowsExceptionOnInvalidObjectReturnedByCallback()
    +    {
    +        $value = function ($options) {
    +            return new \stdClass();
    +        };
    +
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientProfile();
    +
    +        $option->filter($options, $value);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Option/ClientReplicationTest.php b/vendor/predis/predis/tests/Predis/Option/ClientReplicationTest.php
    new file mode 100644
    index 0000000..f8dd87e
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Option/ClientReplicationTest.php
    @@ -0,0 +1,101 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class ClientReplicationTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationAcceptsFQNStringAsInitializer()
    +    {
    +        $replicationClass = get_class($this->getMock('Predis\Connection\ReplicationConnectionInterface'));
    +
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientReplication();
    +
    +        $replication = $option->filter($options, $replicationClass);
    +
    +        $this->assertInstanceOf('Predis\Connection\ReplicationConnectionInterface', $replication);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationAcceptsBooleanValue()
    +    {
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientReplication();
    +
    +        $replication = $option->filter($options, true);
    +        $this->assertInstanceOf('Predis\Connection\ReplicationConnectionInterface', $replication);
    +
    +        $replication = $option->filter($options, false);
    +        $this->assertNull($replication);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testValidationAcceptsCallableObjectAsInitializers()
    +    {
    +        $value = $this->getMock('Predis\Connection\ReplicationConnectionInterface');
    +
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientReplication();
    +
    +        $initializer = $this->getMock('stdClass', array('__invoke'));
    +        $initializer->expects($this->once())
    +                    ->method('__invoke')
    +                    ->with($this->isInstanceOf('Predis\Option\ClientOptionsInterface'), $option)
    +                    ->will($this->returnValue($value));
    +
    +        $replication = $option->filter($options, $initializer, $option);
    +
    +        $this->assertInstanceOf('Predis\Connection\ReplicationConnectionInterface', $replication);
    +        $this->assertSame($value, $replication);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException InvalidArgumentException
    +     */
    +    public function testValidationThrowsExceptionOnInvalidObjectReturnedByCallback()
    +    {
    +        $value = function ($options) {
    +            return new \stdClass();
    +        };
    +
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientReplication();
    +
    +        $option->filter($options, $value);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException InvalidArgumentException
    +     */
    +    public function testValidationThrowsExceptionOnInvalidClassTypes()
    +    {
    +        $connectionClass = get_class($this->getMock('Predis\Connection\SingleConnectionInterface'));
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new ClientReplication();
    +
    +        $option->filter($options, $connectionClass);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Option/CustomOptionTest.php b/vendor/predis/predis/tests/Predis/Option/CustomOptionTest.php
    new file mode 100644
    index 0000000..805cae2
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Option/CustomOptionTest.php
    @@ -0,0 +1,114 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Option;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class CustomOptionTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     * @expectedException InvalidArgumentException
    +     */
    +    public function testConstructorAcceptsOnlyCallablesForFilter()
    +    {
    +        $option = new CustomOption(array('filter' => new \stdClass()));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException InvalidArgumentException
    +     */
    +    public function testConstructorAcceptsOnlyCallablesForDefault()
    +    {
    +        $option = new CustomOption(array('default' => new \stdClass()));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorIgnoresUnrecognizedParameters()
    +    {
    +        $option = new CustomOption(array('unknown' => new \stdClass()));
    +
    +        $this->assertNotNull($option);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFilterWithoutCallbackReturnsValue()
    +    {
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new CustomOption();
    +
    +        $this->assertEquals('test', $option->filter($options, 'test'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDefaultWithoutCallbackReturnsNull()
    +    {
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +        $option = new CustomOption();
    +
    +        $this->assertNull($option->getDefault($options));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testInvokeCallsFilterCallback()
    +    {
    +        $value = 'test';
    +
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +
    +        $filter = $this->getMock('stdClass', array('__invoke'));
    +        $filter->expects($this->once())
    +               ->method('__invoke')
    +               ->with($this->isInstanceOf('Predis\Option\ClientOptionsInterface'), $value)
    +               ->will($this->returnValue(true));
    +
    +        $default = $this->getMock('stdClass', array('__invoke'));
    +        $default->expects($this->never())->method('__invoke');
    +
    +        $option = new CustomOption(array('filter' => $filter, 'default' => $default));
    +
    +        $this->assertTrue($option($options, $value));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testInvokeCallsDefaultCallback()
    +    {
    +        $options = $this->getMock('Predis\Option\ClientOptionsInterface');
    +
    +        $filter = $this->getMock('stdClass', array('__invoke'));
    +        $filter->expects($this->never())->method('__invoke');
    +
    +        $default = $this->getMock('stdClass', array('__invoke'));
    +        $default->expects($this->once())
    +                ->method('__invoke')
    +                ->with($this->isInstanceOf('Predis\Option\ClientOptionsInterface'))
    +                ->will($this->returnValue(true));
    +
    +        $option = new CustomOption(array('filter' => $filter, 'default' => $default));
    +
    +        $this->assertTrue($option($options, null));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Pipeline/FireAndForgetExecutorTest.php b/vendor/predis/predis/tests/Predis/Pipeline/FireAndForgetExecutorTest.php
    new file mode 100644
    index 0000000..e44204d
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Pipeline/FireAndForgetExecutorTest.php
    @@ -0,0 +1,87 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Pipeline;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use SplQueue;
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + *
    + */
    +class FireAndForgetExecutorTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecutorWithSingleConnection()
    +    {
    +        $executor = new FireAndForgetExecutor();
    +        $pipeline = $this->getCommandsQueue();
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->exactly(3))
    +                   ->method('writeCommand');
    +        $connection->expects($this->never())
    +                   ->method('readResponse');
    +
    +        $replies = $executor->execute($connection, $pipeline);
    +
    +        $this->assertTrue($pipeline->isEmpty());
    +        $this->assertEmpty($replies);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecutorWithReplicationConnection()
    +    {
    +        $executor = new FireAndForgetExecutor();
    +        $pipeline = $this->getCommandsQueue();
    +
    +        $connection = $this->getMock('Predis\Connection\ReplicationConnectionInterface');
    +        $connection->expects($this->once())
    +                   ->method('switchTo')
    +                   ->with('master');
    +        $connection->expects($this->exactly(3))
    +                   ->method('writeCommand');
    +        $connection->expects($this->never())
    +                   ->method('readResponse');
    +
    +        $replies = $executor->execute($connection, $pipeline);
    +
    +        $this->assertTrue($pipeline->isEmpty());
    +        $this->assertEmpty($replies);
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Returns a list of queued command instances.
    +     *
    +     * @return SplQueue
    +     */
    +    protected function getCommandsQueue()
    +    {
    +        $profile = ServerProfile::getDevelopment();
    +
    +        $pipeline = new SplQueue();
    +        $pipeline->enqueue($profile->createCommand('ping'));
    +        $pipeline->enqueue($profile->createCommand('ping'));
    +        $pipeline->enqueue($profile->createCommand('ping'));
    +
    +        return $pipeline;
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Pipeline/MultiExecExecutorTest.php b/vendor/predis/predis/tests/Predis/Pipeline/MultiExecExecutorTest.php
    new file mode 100644
    index 0000000..c6a3010
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Pipeline/MultiExecExecutorTest.php
    @@ -0,0 +1,188 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Pipeline;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use ArrayIterator;
    +use SplQueue;
    +use Predis\ResponseError;
    +use Predis\ResponseObjectInterface;
    +use Predis\ResponseQueued;
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + *
    + */
    +class ResponseIteratorStub extends ArrayIterator implements ResponseObjectInterface
    +{
    +}
    +
    +/**
    + *
    + */
    +class MultiExecExecutorTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecutorWithSingleConnection()
    +    {
    +        $executor = new MultiExecExecutor();
    +        $pipeline = $this->getCommandsQueue();
    +        $queued = new ResponseQueued();
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->exactly(2))
    +                   ->method('executeCommand')
    +                   ->will($this->onConsecutiveCalls(true, array('PONG', 'PONG', 'PONG')));
    +        $connection->expects($this->exactly(3))
    +                   ->method('writeCommand');
    +        $connection->expects($this->at(3))
    +                   ->method('readResponse')
    +                   ->will($this->onConsecutiveCalls($queued, $queued, $queued));
    +
    +        $replies = $executor->execute($connection, $pipeline);
    +
    +        $this->assertTrue($pipeline->isEmpty());
    +        $this->assertSame(array(true, true, true), $replies);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecutorWithSingleConnectionReturningIterator()
    +    {
    +        $executor = new MultiExecExecutor();
    +        $pipeline = $this->getCommandsQueue();
    +        $queued = new ResponseQueued();
    +        $execResponse = new ResponseIteratorStub(array('PONG', 'PONG', 'PONG'));
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->exactly(2))
    +                   ->method('executeCommand')
    +                   ->will($this->onConsecutiveCalls(true, $execResponse));
    +        $connection->expects($this->exactly(3))
    +                   ->method('writeCommand');
    +        $connection->expects($this->at(3))
    +                   ->method('readResponse')
    +                   ->will($this->onConsecutiveCalls($queued, $queued, $queued));
    +
    +        $replies = $executor->execute($connection, $pipeline);
    +
    +        $this->assertTrue($pipeline->isEmpty());
    +        $this->assertSame(array(true, true, true), $replies);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\ClientException
    +     * @expectedExceptionMessage The underlying transaction has been aborted by the server
    +     */
    +    public function testExecutorWithAbortedTransaction()
    +    {
    +        $executor = new MultiExecExecutor();
    +        $pipeline = $this->getCommandsQueue();
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->exactly(2))
    +                   ->method('executeCommand')
    +                   ->will($this->onConsecutiveCalls(true, null));
    +
    +        $executor->execute($connection, $pipeline);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR Test error
    +     */
    +    public function testExecutorWithErrorInTransaction()
    +    {
    +        $executor = new MultiExecExecutor();
    +        $pipeline = $this->getCommandsQueue();
    +        $queued = new ResponseQueued();
    +        $error = new ResponseError('ERR Test error');
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->at(0))
    +                   ->method('executeCommand')
    +                   ->will($this->returnValue(true));
    +        $connection->expects($this->exactly(3))
    +                   ->method('readResponse')
    +                   ->will($this->onConsecutiveCalls($queued, $queued, $error));
    +        $connection->expects($this->at(7))
    +                   ->method('executeCommand')
    +                   ->with($this->isInstanceOf('Predis\Command\TransactionDiscard'));
    +
    +        $executor->execute($connection, $pipeline);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecutorWithErrorInCommandResponse()
    +    {
    +        $executor = new MultiExecExecutor();
    +        $pipeline = $this->getCommandsQueue();
    +        $queued = new ResponseQueued();
    +        $error = new ResponseError('ERR Test error');
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->exactly(3))
    +                   ->method('readResponse')
    +                   ->will($this->onConsecutiveCalls($queued, $queued, $queued));
    +        $connection->expects($this->at(7))
    +                   ->method('executeCommand')
    +                   ->will($this->returnValue(array('PONG', 'PONG', $error)));
    +
    +        $replies = $executor->execute($connection, $pipeline);
    +
    +        $this->assertSame(array(true, true, $error), $replies);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\ClientException
    +     * @expectedExceptionMessage Predis\Pipeline\MultiExecExecutor can be used only with single connections
    +     */
    +    public function testExecutorWithAggregatedConnection()
    +    {
    +        $executor = new MultiExecExecutor();
    +        $pipeline = $this->getCommandsQueue();
    +
    +        $connection = $this->getMock('Predis\Connection\ReplicationConnectionInterface');
    +
    +        $replies = $executor->execute($connection, $pipeline);
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Returns a list of queued command instances.
    +     *
    +     * @return SplQueue
    +     */
    +    protected function getCommandsQueue()
    +    {
    +        $profile = ServerProfile::getDevelopment();
    +
    +        $pipeline = new SplQueue();
    +        $pipeline->enqueue($profile->createCommand('ping'));
    +        $pipeline->enqueue($profile->createCommand('ping'));
    +        $pipeline->enqueue($profile->createCommand('ping'));
    +
    +        return $pipeline;
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Pipeline/PipelineContextTest.php b/vendor/predis/predis/tests/Predis/Pipeline/PipelineContextTest.php
    new file mode 100644
    index 0000000..8fce839
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Pipeline/PipelineContextTest.php
    @@ -0,0 +1,435 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Pipeline;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Client;
    +use Predis\ClientException;
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + *
    + */
    +class PipelineContextTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithoutOptions()
    +    {
    +        $client = new Client();
    +        $pipeline = new PipelineContext($client);
    +
    +        $this->assertSame($client, $pipeline->getClient());
    +        $this->assertInstanceOf('Predis\Pipeline\StandardExecutor', $pipeline->getExecutor());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithExecutorArgument()
    +    {
    +        $client = new Client();
    +        $executor = $this->getMock('Predis\Pipeline\PipelineExecutorInterface');
    +
    +        $pipeline = new PipelineContext($client, $executor);
    +        $this->assertSame($executor, $pipeline->getExecutor());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCallDoesNotSendCommandsWithoutExecute()
    +    {
    +        $executor = $this->getMock('Predis\Pipeline\PipelineExecutorInterface');
    +        $executor->expects($this->never())->method('executor');
    +
    +        $pipeline = new PipelineContext(new Client(), $executor);
    +
    +        $pipeline->echo('one');
    +        $pipeline->echo('two');
    +        $pipeline->echo('three');
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCallReturnsPipelineForFluentInterface()
    +    {
    +        $executor = $this->getMock('Predis\Pipeline\PipelineExecutorInterface');
    +        $executor->expects($this->never())->method('executor');
    +
    +        $pipeline = new PipelineContext(new Client(), $executor);
    +
    +        $this->assertSame($pipeline, $pipeline->echo('one'));
    +        $this->assertSame($pipeline, $pipeline->echo('one')->echo('two')->echo('three'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecuteCommandDoesNotSendCommandsWithoutExecute()
    +    {
    +        $profile = ServerProfile::getDefault();
    +
    +        $executor = $this->getMock('Predis\Pipeline\PipelineExecutorInterface');
    +        $executor->expects($this->never())->method('executor');
    +
    +        $pipeline = new PipelineContext(new Client(), $executor);
    +
    +        $pipeline->executeCommand($profile->createCommand('echo', array('one')));
    +        $pipeline->executeCommand($profile->createCommand('echo', array('two')));
    +        $pipeline->executeCommand($profile->createCommand('echo', array('three')));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecuteWithEmptyBuffer()
    +    {
    +        $executor = $this->getMock('Predis\Pipeline\PipelineExecutorInterface');
    +        $executor->expects($this->never())->method('executor');
    +
    +        $pipeline = new PipelineContext(new Client(), $executor);
    +
    +        $this->assertSame(array(), $pipeline->execute());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecuteWithFilledBuffer()
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->exactly(3))
    +                   ->method('writeCommand');
    +        $connection->expects($this->exactly(3))
    +                   ->method('readResponse')
    +                   ->will($this->returnCallback($this->getReadCallback()));
    +
    +        $pipeline = new PipelineContext(new Client($connection));
    +
    +        $pipeline->echo('one');
    +        $pipeline->echo('two');
    +        $pipeline->echo('three');
    +
    +        $pipeline->flushPipeline();
    +
    +        $this->assertSame(array('one', 'two', 'three'), $pipeline->execute());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFlushWithFalseArgumentDiscardsBuffer()
    +    {
    +        $executor = $this->getMock('Predis\Pipeline\PipelineExecutorInterface');
    +        $executor->expects($this->never())->method('executor');
    +
    +        $pipeline = new PipelineContext(new Client(), $executor);
    +
    +        $pipeline->echo('one');
    +        $pipeline->echo('two');
    +        $pipeline->echo('three');
    +
    +        $pipeline->flushPipeline(false);
    +
    +        $this->assertSame(array(), $pipeline->execute());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testFlushHandlesPartialBuffers()
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->exactly(4))
    +                   ->method('writeCommand');
    +        $connection->expects($this->exactly(4))
    +                   ->method('readResponse')
    +                   ->will($this->returnCallback($this->getReadCallback()));
    +
    +        $pipeline = new PipelineContext(new Client($connection));
    +
    +        $pipeline->echo('one');
    +        $pipeline->echo('two');
    +        $pipeline->flushPipeline();
    +        $pipeline->echo('three');
    +        $pipeline->echo('four');
    +
    +        $this->assertSame(array('one', 'two', 'three', 'four'), $pipeline->execute());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecuteAcceptsCallableArgument()
    +    {
    +        $test = $this;
    +        $pipeline = new PipelineContext(new Client());
    +
    +        $callable = function ($pipe) use ($test, $pipeline) {
    +            $test->assertSame($pipeline, $pipe);
    +            $pipe->flushPipeline(false);
    +        };
    +
    +        $pipeline->execute($callable);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException InvalidArgumentException
    +     */
    +    public function testExecuteDoesNotAcceptNonCallableArgument()
    +    {
    +        $noncallable = new \stdClass();
    +
    +        $pipeline = new PipelineContext(new Client());
    +        $pipeline->execute($noncallable);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\ClientException
    +     */
    +    public function testExecuteInsideCallableArgumentThrowsException()
    +    {
    +        $pipeline = new PipelineContext(new Client());
    +
    +        $pipeline->execute(function ($pipe) {
    +            $pipe->execute();
    +        });
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecuteWithCallableArgumentRunsPipelineInCallable()
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->exactly(4))
    +                   ->method('writeCommand');
    +        $connection->expects($this->exactly(4))
    +                   ->method('readResponse')
    +                   ->will($this->returnCallback($this->getReadCallback()));
    +
    +        $pipeline = new PipelineContext(new Client($connection));
    +
    +        $replies = $pipeline->execute(function ($pipe) {
    +            $pipe->echo('one');
    +            $pipe->echo('two');
    +            $pipe->echo('three');
    +            $pipe->echo('four');
    +        });
    +
    +        $this->assertSame(array('one', 'two', 'three', 'four'), $replies);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecuteWithCallableArgumentHandlesExceptions()
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->never())->method('writeCommand');
    +        $connection->expects($this->never())->method('readResponse');
    +
    +        $pipeline = new PipelineContext(new Client($connection));
    +
    +        $exception = null;
    +        $replies = null;
    +
    +        try {
    +            $replies = $pipeline->execute(function ($pipe) {
    +                $pipe->echo('one');
    +                throw new ClientException('TEST');
    +                $pipe->echo('two');
    +            });
    +        } catch (\Exception $ex) {
    +            $exception = $ex;
    +        }
    +
    +        $this->assertInstanceOf('Predis\ClientException', $exception);
    +        $this->assertSame('TEST', $exception->getMessage());
    +        $this->assertNull($replies);
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- INTEGRATION TESTS --------------------------------------------- //
    +    // ******************************************************************** //
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testIntegrationWithFluentInterface()
    +    {
    +        $pipeline = $this->getClient()->pipeline();
    +
    +        $results = $pipeline->echo('one')
    +                            ->echo('two')
    +                            ->echo('three')
    +                            ->execute();
    +
    +        $this->assertSame(array('one', 'two', 'three'), $results);
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testIntegrationWithCallableBlock()
    +    {
    +        $client = $this->getClient();
    +
    +        $results = $client->pipeline(function ($pipe) {
    +            $pipe->set('foo', 'bar');
    +            $pipe->get('foo');
    +        });
    +
    +        $this->assertSame(array(true, 'bar'), $results);
    +        $this->assertTrue($client->exists('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testOutOfBandMessagesInsidePipeline()
    +    {
    +        $oob = null;
    +        $client = $this->getClient();
    +
    +        $results = $client->pipeline(function ($pipe) use (&$oob) {
    +            $pipe->set('foo', 'bar');
    +            $oob = $pipe->getClient()->echo('oob message');
    +            $pipe->get('foo');
    +        });
    +
    +        $this->assertSame(array(true, 'bar'), $results);
    +        $this->assertSame('oob message', $oob);
    +        $this->assertTrue($client->exists('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testIntegrationWithClientExceptionInCallableBlock()
    +    {
    +        $client = $this->getClient();
    +
    +        try {
    +            $client->pipeline(function ($pipe) {
    +                $pipe->set('foo', 'bar');
    +                throw new ClientException('TEST');
    +            });
    +        } catch (\Exception $ex) {
    +            $exception = $ex;
    +        }
    +
    +        $this->assertInstanceOf('Predis\ClientException', $exception);
    +        $this->assertSame('TEST', $exception->getMessage());
    +        $this->assertFalse($client->exists('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testIntegrationWithServerExceptionInCallableBlock()
    +    {
    +        $client = $this->getClient();
    +
    +        try {
    +            $client->pipeline(function ($pipe) {
    +                $pipe->set('foo', 'bar');
    +                // LPUSH on a string key fails, but won't stop
    +                // the pipeline to send the commands.
    +                $pipe->lpush('foo', 'bar');
    +                $pipe->set('hoge', 'piyo');
    +            });
    +        } catch (\Exception $ex) {
    +            $exception = $ex;
    +        }
    +
    +        $this->assertInstanceOf('Predis\ServerException', $exception);
    +        $this->assertTrue($client->exists('foo'));
    +        $this->assertTrue($client->exists('hoge'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testIntegrationWithServerErrorInCallableBlock()
    +    {
    +        $client = $this->getClient(array(), array('exceptions' => false));
    +
    +        $results = $client->pipeline(function ($pipe) {
    +            $pipe->set('foo', 'bar');
    +            $pipe->lpush('foo', 'bar'); // LPUSH on a string key fails.
    +            $pipe->get('foo');
    +        });
    +
    +        $this->assertTrue($results[0]);
    +        $this->assertInstanceOf('Predis\ResponseError', $results[1]);
    +        $this->assertSame('bar', $results[2]);
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Returns a client instance connected to the specified Redis
    +     * server instance to perform integration tests.
    +     *
    +     * @return array Additional connection parameters.
    +     * @return array Additional client options.
    +     * @return Client New client instance.
    +     */
    +    protected function getClient(Array $parameters = array(), Array $options = array())
    +    {
    +        $parameters = array_merge(array(
    +            'scheme' => 'tcp',
    +            'host' => REDIS_SERVER_HOST,
    +            'port' => REDIS_SERVER_PORT,
    +            'database' => REDIS_SERVER_DBNUM,
    +        ), $parameters);
    +
    +        $options = array_merge(array(
    +            'profile' => REDIS_SERVER_VERSION,
    +        ), $options);
    +
    +        $client = new Client($parameters, $options);
    +
    +        $client->connect();
    +        $client->flushdb();
    +
    +        return $client;
    +    }
    +
    +    /**
    +     * Helper method that returns a callback used to emulate a reply
    +     * to an ECHO command.
    +     *
    +     * @return \Closure
    +     */
    +    protected function getReadCallback()
    +    {
    +        return function ($command) {
    +            if (($id = $command->getId()) !== 'ECHO') {
    +                throw new \InvalidArgumentException("Expected ECHO, got {$id}");
    +            }
    +
    +            list($echoed) = $command->getArguments();
    +
    +            return $echoed;
    +        };
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Pipeline/StandardExecutorTest.php b/vendor/predis/predis/tests/Predis/Pipeline/StandardExecutorTest.php
    new file mode 100644
    index 0000000..e5e1c83
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Pipeline/StandardExecutorTest.php
    @@ -0,0 +1,160 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Pipeline;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use SplQueue;
    +use Predis\ResponseError;
    +use Predis\ResponseObjectInterface;
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + *
    + */
    +class StandardExecutorTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecutorWithSingleConnection()
    +    {
    +        $executor = new StandardExecutor();
    +        $pipeline = $this->getCommandsQueue();
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->exactly(3))
    +                   ->method('writeCommand');
    +        $connection->expects($this->exactly(3))
    +                   ->method('readResponse')
    +                   ->will($this->returnValue('PONG'));
    +
    +        $replies = $executor->execute($connection, $pipeline);
    +
    +        $this->assertTrue($pipeline->isEmpty());
    +        $this->assertSame(array(true, true, true), $replies);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecutorWithReplicationConnection()
    +    {
    +        $executor = new StandardExecutor();
    +        $pipeline = $this->getCommandsQueue();
    +
    +        $connection = $this->getMock('Predis\Connection\ReplicationConnectionInterface');
    +        $connection->expects($this->once())
    +                   ->method('switchTo')
    +                   ->with('master');
    +        $connection->expects($this->exactly(3))
    +                   ->method('writeCommand');
    +        $connection->expects($this->exactly(3))
    +                   ->method('readResponse')
    +                   ->will($this->returnValue('PONG'));
    +
    +        $replies = $executor->execute($connection, $pipeline);
    +
    +        $this->assertTrue($pipeline->isEmpty());
    +        $this->assertSame(array(true, true, true), $replies);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecutorDoesNotParseResponseObjects()
    +    {
    +        $executor = new StandardExecutor();
    +        $response = $this->getMock('Predis\ResponseObjectInterface');
    +
    +        $this->simpleResponseObjectTest($executor, $response);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecutorCanReturnRedisErrors()
    +    {
    +        $executor = new StandardExecutor(false);
    +        $response = $this->getMock('Predis\ResponseErrorInterface');
    +
    +        $this->simpleResponseObjectTest($executor, $response);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\ServerException
    +     * @expectedExceptionMessage ERR Test error
    +     */
    +    public function testExecutorCanThrowExceptions()
    +    {
    +        $executor = new StandardExecutor(true);
    +        $pipeline = $this->getCommandsQueue();
    +        $error = new ResponseError('ERR Test error');
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->once())
    +                   ->method('readResponse')
    +                   ->will($this->returnValue($error));
    +
    +        $executor->execute($connection, $pipeline);
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Executes a test for the Predis\ResponseObjectInterface type.
    +     *
    +     * @param PipelineExecutorInterface $executor
    +     * @param ResponseObjectInterface $response
    +     */
    +    protected function simpleResponseObjectTest(PipelineExecutorInterface $executor, ResponseObjectInterface $response)
    +    {
    +        $pipeline = new SplQueue();
    +
    +        $command = $this->getMock('Predis\Command\CommandInterface');
    +        $command->expects($this->never())
    +                ->method('parseResponse');
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->once())
    +                   ->method('writeCommand');
    +        $connection->expects($this->once())
    +                   ->method('readResponse')
    +                   ->will($this->returnValue($response));
    +
    +        $pipeline->enqueue($command);
    +        $replies = $executor->execute($connection, $pipeline);
    +
    +        $this->assertTrue($pipeline->isEmpty());
    +        $this->assertSame(array($response), $replies);
    +    }
    +
    +    /**
    +     * Returns a list of queued command instances.
    +     *
    +     * @return SplQueue
    +     */
    +    protected function getCommandsQueue()
    +    {
    +        $profile = ServerProfile::getDevelopment();
    +
    +        $pipeline = new SplQueue();
    +        $pipeline->enqueue($profile->createCommand('ping'));
    +        $pipeline->enqueue($profile->createCommand('ping'));
    +        $pipeline->enqueue($profile->createCommand('ping'));
    +
    +        return $pipeline;
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/PredisExceptionTest.php b/vendor/predis/predis/tests/Predis/PredisExceptionTest.php
    new file mode 100644
    index 0000000..4cfbc65
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/PredisExceptionTest.php
    @@ -0,0 +1,33 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class PredisExceptionTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExceptionMessage()
    +    {
    +        $message = 'Predis exception message';
    +        $exception = $this->getMockForAbstractClass('Predis\PredisException', array($message));
    +
    +        $this->setExpectedException('Predis\PredisException', $message);
    +
    +        throw $exception;
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Profile/ServerProfileTest.php b/vendor/predis/predis/tests/Predis/Profile/ServerProfileTest.php
    new file mode 100644
    index 0000000..82396cf
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Profile/ServerProfileTest.php
    @@ -0,0 +1,294 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Profile;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Command\Processor\ProcessorChain;
    +
    +/**
    + *
    + */
    +class ServerProfileTest extends StandardTestCase
    +{
    +    const DEFAULT_PROFILE_VERSION = '2.6';
    +    const DEVELOPMENT_PROFILE_VERSION = '2.8';
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetVersion()
    +    {
    +        $profile = ServerProfile::get('2.0');
    +
    +        $this->assertInstanceOf('Predis\Profile\ServerProfileInterface', $profile);
    +        $this->assertEquals('2.0', $profile->getVersion());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetDefault()
    +    {
    +        $profile1 = ServerProfile::get(self::DEFAULT_PROFILE_VERSION);
    +        $profile2 = ServerProfile::get('default');
    +        $profile3 = ServerProfile::getDefault();
    +
    +        $this->assertInstanceOf('Predis\Profile\ServerProfileInterface', $profile1);
    +        $this->assertInstanceOf('Predis\Profile\ServerProfileInterface', $profile2);
    +        $this->assertInstanceOf('Predis\Profile\ServerProfileInterface', $profile3);
    +        $this->assertEquals($profile1->getVersion(), $profile2->getVersion());
    +        $this->assertEquals($profile2->getVersion(), $profile3->getVersion());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetDevelopment()
    +    {
    +        $profile1 = ServerProfile::get('dev');
    +        $profile2 = ServerProfile::getDevelopment();
    +
    +        $this->assertInstanceOf('Predis\Profile\ServerProfileInterface', $profile1);
    +        $this->assertInstanceOf('Predis\Profile\ServerProfileInterface', $profile2);
    +        $this->assertEquals(self::DEVELOPMENT_PROFILE_VERSION, $profile2->getVersion());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\ClientException
    +     * @expectedExceptionMessage Unknown server profile: 1.0
    +     */
    +    public function testGetUndefinedProfile()
    +    {
    +        ServerProfile::get('1.0');
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDefineProfile()
    +    {
    +        $profileClass = get_class($this->getMock('Predis\Profile\ServerProfileInterface'));
    +
    +        ServerProfile::define('mock', $profileClass);
    +
    +        $this->assertInstanceOf($profileClass, ServerProfile::get('mock'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException InvalidArgumentException
    +     * @expectedExceptionMessage Cannot register 'stdClass' as it is not a valid profile class
    +     */
    +    public function testDefineInvalidProfile()
    +    {
    +        ServerProfile::define('bogus', 'stdClass');
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testToString()
    +    {
    +        $this->assertEquals('2.0', (string) ServerProfile::get('2.0'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSupportCommand()
    +    {
    +        $profile = ServerProfile::getDefault();
    +
    +        $this->assertTrue($profile->supportsCommand('info'));
    +        $this->assertTrue($profile->supportsCommand('INFO'));
    +
    +        $this->assertFalse($profile->supportsCommand('unknown'));
    +        $this->assertFalse($profile->supportsCommand('UNKNOWN'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSupportCommands()
    +    {
    +        $profile = ServerProfile::getDefault();
    +
    +        $this->assertTrue($profile->supportsCommands(array('get', 'set')));
    +        $this->assertTrue($profile->supportsCommands(array('GET', 'SET')));
    +
    +        $this->assertFalse($profile->supportsCommands(array('get', 'unknown')));
    +
    +        $this->assertFalse($profile->supportsCommands(array('unknown1', 'unknown2')));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetCommandClass()
    +    {
    +        $profile = ServerProfile::getDefault();
    +
    +        $this->assertSame('Predis\Command\ConnectionPing', $profile->getCommandClass('ping'));
    +        $this->assertSame('Predis\Command\ConnectionPing', $profile->getCommandClass('PING'));
    +
    +        $this->assertNull($profile->getCommandClass('unknown'));
    +        $this->assertNull($profile->getCommandClass('UNKNOWN'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDefineCommand()
    +    {
    +        $profile = ServerProfile::getDefault();
    +        $command = $this->getMock('Predis\Command\CommandInterface');
    +
    +        $profile->defineCommand('mock', get_class($command));
    +
    +        $this->assertTrue($profile->supportsCommand('mock'));
    +        $this->assertTrue($profile->supportsCommand('MOCK'));
    +
    +        $this->assertSame(get_class($command), $profile->getCommandClass('mock'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException InvalidArgumentException
    +     * @expectedExceptionMessage Cannot register 'stdClass' as it is not a valid Redis command
    +     */
    +    public function testDefineInvalidCommand()
    +    {
    +        $profile = ServerProfile::getDefault();
    +
    +        $profile->defineCommand('mock', 'stdClass');
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCreateCommandWithoutArguments()
    +    {
    +        $profile = ServerProfile::getDefault();
    +
    +        $command = $profile->createCommand('info');
    +        $this->assertInstanceOf('Predis\Command\CommandInterface', $command);
    +        $this->assertEquals('INFO', $command->getId());
    +        $this->assertEquals(array(), $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCreateCommandWithArguments()
    +    {
    +        $profile = ServerProfile::getDefault();
    +        $arguments = array('foo', 'bar');
    +
    +        $command = $profile->createCommand('set', $arguments);
    +        $this->assertInstanceOf('Predis\Command\CommandInterface', $command);
    +        $this->assertEquals('SET', $command->getId());
    +        $this->assertEquals($arguments, $command->getArguments());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\ClientException
    +     * @expectedExceptionMessage 'unknown' is not a registered Redis command
    +     */
    +    public function testCreateUndefinedCommand()
    +    {
    +        $profile = ServerProfile::getDefault();
    +        $profile->createCommand('unknown');
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetDefaultProcessor()
    +    {
    +        $profile = ServerProfile::getDefault();
    +
    +        $this->assertNull($profile->getProcessor());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSetProcessor()
    +    {
    +        $processor = $this->getMock('Predis\Command\Processor\CommandProcessorInterface');
    +
    +        $profile = ServerProfile::getDefault();
    +        $profile->setProcessor($processor);
    +
    +        $this->assertSame($processor, $profile->getProcessor());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSetAndUnsetProcessor()
    +    {
    +        $processor = $this->getMock('Predis\Command\Processor\CommandProcessorInterface');
    +        $profile = ServerProfile::getDefault();
    +
    +        $profile->setProcessor($processor);
    +        $this->assertSame($processor, $profile->getProcessor());
    +
    +        $profile->setProcessor(null);
    +        $this->assertNull($profile->getProcessor());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @todo Could it be that objects passed to the return callback of a mocked
    +     *       method are cloned instead of being passed by reference?
    +     */
    +    public function testSingleProcessor()
    +    {
    +        $argsRef = null;
    +
    +        $processor = $this->getMock('Predis\Command\Processor\CommandProcessorInterface');
    +        $processor->expects($this->once())
    +                  ->method('process')
    +                  ->with($this->isInstanceOf('Predis\Command\CommandInterface'))
    +                  ->will($this->returnCallback(function ($cmd) use (&$argsRef) {
    +                        $cmd->setRawArguments($argsRef = array_map('strtoupper', $cmd->getArguments()));
    +                    }));
    +
    +        $profile = ServerProfile::getDefault();
    +        $profile->setProcessor($processor);
    +        $command = $profile->createCommand('set', array('foo', 'bar'));
    +
    +        $this->assertSame(array('FOO', 'BAR'), $argsRef);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testChainOfProcessors()
    +    {
    +        $processor = $this->getMock('Predis\Command\Processor\CommandProcessorInterface');
    +        $processor->expects($this->exactly(2))
    +                  ->method('process');
    +
    +        $chain = new ProcessorChain();
    +        $chain->add($processor);
    +        $chain->add($processor);
    +
    +        $profile = ServerProfile::getDefault();
    +        $profile->setProcessor($chain);
    +        $profile->createCommand('info');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Profile/ServerVersion12Test.php b/vendor/predis/predis/tests/Predis/Profile/ServerVersion12Test.php
    new file mode 100644
    index 0000000..a1747b5
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Profile/ServerVersion12Test.php
    @@ -0,0 +1,116 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Profile;
    +
    +/**
    + *
    + */
    +class ServerVersion12Test extends ServerVersionTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getProfileInstance()
    +    {
    +        return new ServerVersion12();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getExpectedVersion()
    +    {
    +        return '1.2';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getExpectedCommands()
    +    {
    +        return array(
    +            0 => 'exists',
    +            1 => 'del',
    +            2 => 'type',
    +            3 => 'keys',
    +            4 => 'randomkey',
    +            5 => 'rename',
    +            6 => 'renamenx',
    +            7 => 'expire',
    +            8 => 'expireat',
    +            9 => 'ttl',
    +            10 => 'move',
    +            11 => 'sort',
    +            12 => 'set',
    +            13 => 'setnx',
    +            14 => 'mset',
    +            15 => 'msetnx',
    +            16 => 'get',
    +            17 => 'mget',
    +            18 => 'getset',
    +            19 => 'incr',
    +            20 => 'incrby',
    +            21 => 'decr',
    +            22 => 'decrby',
    +            23 => 'rpush',
    +            24 => 'lpush',
    +            25 => 'llen',
    +            26 => 'lrange',
    +            27 => 'ltrim',
    +            28 => 'lindex',
    +            29 => 'lset',
    +            30 => 'lrem',
    +            31 => 'lpop',
    +            32 => 'rpop',
    +            33 => 'rpoplpush',
    +            34 => 'sadd',
    +            35 => 'srem',
    +            36 => 'spop',
    +            37 => 'smove',
    +            38 => 'scard',
    +            39 => 'sismember',
    +            40 => 'sinter',
    +            41 => 'sinterstore',
    +            42 => 'sunion',
    +            43 => 'sunionstore',
    +            44 => 'sdiff',
    +            45 => 'sdiffstore',
    +            46 => 'smembers',
    +            47 => 'srandmember',
    +            48 => 'zadd',
    +            49 => 'zincrby',
    +            50 => 'zrem',
    +            51 => 'zrange',
    +            52 => 'zrevrange',
    +            53 => 'zrangebyscore',
    +            54 => 'zcard',
    +            55 => 'zscore',
    +            56 => 'zremrangebyscore',
    +            57 => 'ping',
    +            58 => 'auth',
    +            59 => 'select',
    +            60 => 'echo',
    +            61 => 'quit',
    +            62 => 'info',
    +            63 => 'slaveof',
    +            64 => 'monitor',
    +            65 => 'dbsize',
    +            66 => 'flushdb',
    +            67 => 'flushall',
    +            68 => 'save',
    +            69 => 'bgsave',
    +            70 => 'lastsave',
    +            71 => 'shutdown',
    +            72 => 'bgrewriteaof',
    +        );
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Profile/ServerVersion20Test.php b/vendor/predis/predis/tests/Predis/Profile/ServerVersion20Test.php
    new file mode 100644
    index 0000000..3b49736
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Profile/ServerVersion20Test.php
    @@ -0,0 +1,148 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Profile;
    +
    +/**
    + *
    + */
    +class ServerVersion20Test extends ServerVersionTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getProfileInstance()
    +    {
    +        return new ServerVersion20();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getExpectedVersion()
    +    {
    +        return '2.0';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getExpectedCommands()
    +    {
    +        return array(
    +            0 => 'exists',
    +            1 => 'del',
    +            2 => 'type',
    +            3 => 'keys',
    +            4 => 'randomkey',
    +            5 => 'rename',
    +            6 => 'renamenx',
    +            7 => 'expire',
    +            8 => 'expireat',
    +            9 => 'ttl',
    +            10 => 'move',
    +            11 => 'sort',
    +            12 => 'set',
    +            13 => 'setnx',
    +            14 => 'mset',
    +            15 => 'msetnx',
    +            16 => 'get',
    +            17 => 'mget',
    +            18 => 'getset',
    +            19 => 'incr',
    +            20 => 'incrby',
    +            21 => 'decr',
    +            22 => 'decrby',
    +            23 => 'rpush',
    +            24 => 'lpush',
    +            25 => 'llen',
    +            26 => 'lrange',
    +            27 => 'ltrim',
    +            28 => 'lindex',
    +            29 => 'lset',
    +            30 => 'lrem',
    +            31 => 'lpop',
    +            32 => 'rpop',
    +            33 => 'rpoplpush',
    +            34 => 'sadd',
    +            35 => 'srem',
    +            36 => 'spop',
    +            37 => 'smove',
    +            38 => 'scard',
    +            39 => 'sismember',
    +            40 => 'sinter',
    +            41 => 'sinterstore',
    +            42 => 'sunion',
    +            43 => 'sunionstore',
    +            44 => 'sdiff',
    +            45 => 'sdiffstore',
    +            46 => 'smembers',
    +            47 => 'srandmember',
    +            48 => 'zadd',
    +            49 => 'zincrby',
    +            50 => 'zrem',
    +            51 => 'zrange',
    +            52 => 'zrevrange',
    +            53 => 'zrangebyscore',
    +            54 => 'zcard',
    +            55 => 'zscore',
    +            56 => 'zremrangebyscore',
    +            57 => 'ping',
    +            58 => 'auth',
    +            59 => 'select',
    +            60 => 'echo',
    +            61 => 'quit',
    +            62 => 'info',
    +            63 => 'slaveof',
    +            64 => 'monitor',
    +            65 => 'dbsize',
    +            66 => 'flushdb',
    +            67 => 'flushall',
    +            68 => 'save',
    +            69 => 'bgsave',
    +            70 => 'lastsave',
    +            71 => 'shutdown',
    +            72 => 'bgrewriteaof',
    +            73 => 'setex',
    +            74 => 'append',
    +            75 => 'substr',
    +            76 => 'blpop',
    +            77 => 'brpop',
    +            78 => 'zunionstore',
    +            79 => 'zinterstore',
    +            80 => 'zcount',
    +            81 => 'zrank',
    +            82 => 'zrevrank',
    +            83 => 'zremrangebyrank',
    +            84 => 'hset',
    +            85 => 'hsetnx',
    +            86 => 'hmset',
    +            87 => 'hincrby',
    +            88 => 'hget',
    +            89 => 'hmget',
    +            90 => 'hdel',
    +            91 => 'hexists',
    +            92 => 'hlen',
    +            93 => 'hkeys',
    +            94 => 'hvals',
    +            95 => 'hgetall',
    +            96 => 'multi',
    +            97 => 'exec',
    +            98 => 'discard',
    +            99 => 'subscribe',
    +            100 => 'unsubscribe',
    +            101 => 'psubscribe',
    +            102 => 'punsubscribe',
    +            103 => 'publish',
    +            104 => 'config',
    +        );
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Profile/ServerVersion22Test.php b/vendor/predis/predis/tests/Predis/Profile/ServerVersion22Test.php
    new file mode 100644
    index 0000000..00f6e48
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Profile/ServerVersion22Test.php
    @@ -0,0 +1,163 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Profile;
    +
    +/**
    + *
    + */
    +class ServerVersion22Test extends ServerVersionTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getProfileInstance()
    +    {
    +        return new ServerVersion22();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getExpectedVersion()
    +    {
    +        return '2.2';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getExpectedCommands()
    +    {
    +        return array(
    +            0 => 'exists',
    +            1 => 'del',
    +            2 => 'type',
    +            3 => 'keys',
    +            4 => 'randomkey',
    +            5 => 'rename',
    +            6 => 'renamenx',
    +            7 => 'expire',
    +            8 => 'expireat',
    +            9 => 'ttl',
    +            10 => 'move',
    +            11 => 'sort',
    +            12 => 'set',
    +            13 => 'setnx',
    +            14 => 'mset',
    +            15 => 'msetnx',
    +            16 => 'get',
    +            17 => 'mget',
    +            18 => 'getset',
    +            19 => 'incr',
    +            20 => 'incrby',
    +            21 => 'decr',
    +            22 => 'decrby',
    +            23 => 'rpush',
    +            24 => 'lpush',
    +            25 => 'llen',
    +            26 => 'lrange',
    +            27 => 'ltrim',
    +            28 => 'lindex',
    +            29 => 'lset',
    +            30 => 'lrem',
    +            31 => 'lpop',
    +            32 => 'rpop',
    +            33 => 'rpoplpush',
    +            34 => 'sadd',
    +            35 => 'srem',
    +            36 => 'spop',
    +            37 => 'smove',
    +            38 => 'scard',
    +            39 => 'sismember',
    +            40 => 'sinter',
    +            41 => 'sinterstore',
    +            42 => 'sunion',
    +            43 => 'sunionstore',
    +            44 => 'sdiff',
    +            45 => 'sdiffstore',
    +            46 => 'smembers',
    +            47 => 'srandmember',
    +            48 => 'zadd',
    +            49 => 'zincrby',
    +            50 => 'zrem',
    +            51 => 'zrange',
    +            52 => 'zrevrange',
    +            53 => 'zrangebyscore',
    +            54 => 'zcard',
    +            55 => 'zscore',
    +            56 => 'zremrangebyscore',
    +            57 => 'ping',
    +            58 => 'auth',
    +            59 => 'select',
    +            60 => 'echo',
    +            61 => 'quit',
    +            62 => 'info',
    +            63 => 'slaveof',
    +            64 => 'monitor',
    +            65 => 'dbsize',
    +            66 => 'flushdb',
    +            67 => 'flushall',
    +            68 => 'save',
    +            69 => 'bgsave',
    +            70 => 'lastsave',
    +            71 => 'shutdown',
    +            72 => 'bgrewriteaof',
    +            73 => 'setex',
    +            74 => 'append',
    +            75 => 'substr',
    +            76 => 'blpop',
    +            77 => 'brpop',
    +            78 => 'zunionstore',
    +            79 => 'zinterstore',
    +            80 => 'zcount',
    +            81 => 'zrank',
    +            82 => 'zrevrank',
    +            83 => 'zremrangebyrank',
    +            84 => 'hset',
    +            85 => 'hsetnx',
    +            86 => 'hmset',
    +            87 => 'hincrby',
    +            88 => 'hget',
    +            89 => 'hmget',
    +            90 => 'hdel',
    +            91 => 'hexists',
    +            92 => 'hlen',
    +            93 => 'hkeys',
    +            94 => 'hvals',
    +            95 => 'hgetall',
    +            96 => 'multi',
    +            97 => 'exec',
    +            98 => 'discard',
    +            99 => 'subscribe',
    +            100 => 'unsubscribe',
    +            101 => 'psubscribe',
    +            102 => 'punsubscribe',
    +            103 => 'publish',
    +            104 => 'config',
    +            105 => 'persist',
    +            106 => 'strlen',
    +            107 => 'setrange',
    +            108 => 'getrange',
    +            109 => 'setbit',
    +            110 => 'getbit',
    +            111 => 'rpushx',
    +            112 => 'lpushx',
    +            113 => 'linsert',
    +            114 => 'brpoplpush',
    +            115 => 'zrevrangebyscore',
    +            116 => 'watch',
    +            117 => 'unwatch',
    +            118 => 'object',
    +            119 => 'slowlog',
    +        );
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Profile/ServerVersion24Test.php b/vendor/predis/predis/tests/Predis/Profile/ServerVersion24Test.php
    new file mode 100644
    index 0000000..5b3bbd9
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Profile/ServerVersion24Test.php
    @@ -0,0 +1,164 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Profile;
    +
    +/**
    + *
    + */
    +class ServerVersion24Test extends ServerVersionTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getProfileInstance()
    +    {
    +        return new ServerVersion24();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getExpectedVersion()
    +    {
    +        return '2.4';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getExpectedCommands()
    +    {
    +        return array(
    +            0 => 'exists',
    +            1 => 'del',
    +            2 => 'type',
    +            3 => 'keys',
    +            4 => 'randomkey',
    +            5 => 'rename',
    +            6 => 'renamenx',
    +            7 => 'expire',
    +            8 => 'expireat',
    +            9 => 'ttl',
    +            10 => 'move',
    +            11 => 'sort',
    +            12 => 'set',
    +            13 => 'setnx',
    +            14 => 'mset',
    +            15 => 'msetnx',
    +            16 => 'get',
    +            17 => 'mget',
    +            18 => 'getset',
    +            19 => 'incr',
    +            20 => 'incrby',
    +            21 => 'decr',
    +            22 => 'decrby',
    +            23 => 'rpush',
    +            24 => 'lpush',
    +            25 => 'llen',
    +            26 => 'lrange',
    +            27 => 'ltrim',
    +            28 => 'lindex',
    +            29 => 'lset',
    +            30 => 'lrem',
    +            31 => 'lpop',
    +            32 => 'rpop',
    +            33 => 'rpoplpush',
    +            34 => 'sadd',
    +            35 => 'srem',
    +            36 => 'spop',
    +            37 => 'smove',
    +            38 => 'scard',
    +            39 => 'sismember',
    +            40 => 'sinter',
    +            41 => 'sinterstore',
    +            42 => 'sunion',
    +            43 => 'sunionstore',
    +            44 => 'sdiff',
    +            45 => 'sdiffstore',
    +            46 => 'smembers',
    +            47 => 'srandmember',
    +            48 => 'zadd',
    +            49 => 'zincrby',
    +            50 => 'zrem',
    +            51 => 'zrange',
    +            52 => 'zrevrange',
    +            53 => 'zrangebyscore',
    +            54 => 'zcard',
    +            55 => 'zscore',
    +            56 => 'zremrangebyscore',
    +            57 => 'ping',
    +            58 => 'auth',
    +            59 => 'select',
    +            60 => 'echo',
    +            61 => 'quit',
    +            62 => 'info',
    +            63 => 'slaveof',
    +            64 => 'monitor',
    +            65 => 'dbsize',
    +            66 => 'flushdb',
    +            67 => 'flushall',
    +            68 => 'save',
    +            69 => 'bgsave',
    +            70 => 'lastsave',
    +            71 => 'shutdown',
    +            72 => 'bgrewriteaof',
    +            73 => 'setex',
    +            74 => 'append',
    +            75 => 'substr',
    +            76 => 'blpop',
    +            77 => 'brpop',
    +            78 => 'zunionstore',
    +            79 => 'zinterstore',
    +            80 => 'zcount',
    +            81 => 'zrank',
    +            82 => 'zrevrank',
    +            83 => 'zremrangebyrank',
    +            84 => 'hset',
    +            85 => 'hsetnx',
    +            86 => 'hmset',
    +            87 => 'hincrby',
    +            88 => 'hget',
    +            89 => 'hmget',
    +            90 => 'hdel',
    +            91 => 'hexists',
    +            92 => 'hlen',
    +            93 => 'hkeys',
    +            94 => 'hvals',
    +            95 => 'hgetall',
    +            96 => 'multi',
    +            97 => 'exec',
    +            98 => 'discard',
    +            99 => 'subscribe',
    +            100 => 'unsubscribe',
    +            101 => 'psubscribe',
    +            102 => 'punsubscribe',
    +            103 => 'publish',
    +            104 => 'config',
    +            105 => 'persist',
    +            106 => 'strlen',
    +            107 => 'setrange',
    +            108 => 'getrange',
    +            109 => 'setbit',
    +            110 => 'getbit',
    +            111 => 'rpushx',
    +            112 => 'lpushx',
    +            113 => 'linsert',
    +            114 => 'brpoplpush',
    +            115 => 'zrevrangebyscore',
    +            116 => 'watch',
    +            117 => 'unwatch',
    +            118 => 'object',
    +            119 => 'slowlog',
    +            120 => 'client',
    +        );
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Profile/ServerVersion26Test.php b/vendor/predis/predis/tests/Predis/Profile/ServerVersion26Test.php
    new file mode 100644
    index 0000000..9409159
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Profile/ServerVersion26Test.php
    @@ -0,0 +1,178 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Profile;
    +
    +/**
    + *
    + */
    +class ServerVersion26Test extends ServerVersionTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getProfileInstance()
    +    {
    +        return new ServerVersion26();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getExpectedVersion()
    +    {
    +        return '2.6';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getExpectedCommands()
    +    {
    +        return array(
    +            0 => 'exists',
    +            1 => 'del',
    +            2 => 'type',
    +            3 => 'keys',
    +            4 => 'randomkey',
    +            5 => 'rename',
    +            6 => 'renamenx',
    +            7 => 'expire',
    +            8 => 'expireat',
    +            9 => 'ttl',
    +            10 => 'move',
    +            11 => 'sort',
    +            12 => 'dump',
    +            13 => 'restore',
    +            14 => 'set',
    +            15 => 'setnx',
    +            16 => 'mset',
    +            17 => 'msetnx',
    +            18 => 'get',
    +            19 => 'mget',
    +            20 => 'getset',
    +            21 => 'incr',
    +            22 => 'incrby',
    +            23 => 'decr',
    +            24 => 'decrby',
    +            25 => 'rpush',
    +            26 => 'lpush',
    +            27 => 'llen',
    +            28 => 'lrange',
    +            29 => 'ltrim',
    +            30 => 'lindex',
    +            31 => 'lset',
    +            32 => 'lrem',
    +            33 => 'lpop',
    +            34 => 'rpop',
    +            35 => 'rpoplpush',
    +            36 => 'sadd',
    +            37 => 'srem',
    +            38 => 'spop',
    +            39 => 'smove',
    +            40 => 'scard',
    +            41 => 'sismember',
    +            42 => 'sinter',
    +            43 => 'sinterstore',
    +            44 => 'sunion',
    +            45 => 'sunionstore',
    +            46 => 'sdiff',
    +            47 => 'sdiffstore',
    +            48 => 'smembers',
    +            49 => 'srandmember',
    +            50 => 'zadd',
    +            51 => 'zincrby',
    +            52 => 'zrem',
    +            53 => 'zrange',
    +            54 => 'zrevrange',
    +            55 => 'zrangebyscore',
    +            56 => 'zcard',
    +            57 => 'zscore',
    +            58 => 'zremrangebyscore',
    +            59 => 'ping',
    +            60 => 'auth',
    +            61 => 'select',
    +            62 => 'echo',
    +            63 => 'quit',
    +            64 => 'info',
    +            65 => 'slaveof',
    +            66 => 'monitor',
    +            67 => 'dbsize',
    +            68 => 'flushdb',
    +            69 => 'flushall',
    +            70 => 'save',
    +            71 => 'bgsave',
    +            72 => 'lastsave',
    +            73 => 'shutdown',
    +            74 => 'bgrewriteaof',
    +            75 => 'setex',
    +            76 => 'append',
    +            77 => 'substr',
    +            78 => 'blpop',
    +            79 => 'brpop',
    +            80 => 'zunionstore',
    +            81 => 'zinterstore',
    +            82 => 'zcount',
    +            83 => 'zrank',
    +            84 => 'zrevrank',
    +            85 => 'zremrangebyrank',
    +            86 => 'hset',
    +            87 => 'hsetnx',
    +            88 => 'hmset',
    +            89 => 'hincrby',
    +            90 => 'hget',
    +            91 => 'hmget',
    +            92 => 'hdel',
    +            93 => 'hexists',
    +            94 => 'hlen',
    +            95 => 'hkeys',
    +            96 => 'hvals',
    +            97 => 'hgetall',
    +            98 => 'multi',
    +            99 => 'exec',
    +            100 => 'discard',
    +            101 => 'subscribe',
    +            102 => 'unsubscribe',
    +            103 => 'psubscribe',
    +            104 => 'punsubscribe',
    +            105 => 'publish',
    +            106 => 'config',
    +            107 => 'persist',
    +            108 => 'strlen',
    +            109 => 'setrange',
    +            110 => 'getrange',
    +            111 => 'setbit',
    +            112 => 'getbit',
    +            113 => 'rpushx',
    +            114 => 'lpushx',
    +            115 => 'linsert',
    +            116 => 'brpoplpush',
    +            117 => 'zrevrangebyscore',
    +            118 => 'watch',
    +            119 => 'unwatch',
    +            120 => 'object',
    +            121 => 'slowlog',
    +            122 => 'client',
    +            123 => 'pttl',
    +            124 => 'pexpire',
    +            125 => 'pexpireat',
    +            126 => 'psetex',
    +            127 => 'incrbyfloat',
    +            128 => 'bitop',
    +            129 => 'bitcount',
    +            130 => 'hincrbyfloat',
    +            131 => 'eval',
    +            132 => 'evalsha',
    +            133 => 'script',
    +            134 => 'time',
    +        );
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Profile/ServerVersionNextTest.php b/vendor/predis/predis/tests/Predis/Profile/ServerVersionNextTest.php
    new file mode 100644
    index 0000000..dde6294
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Profile/ServerVersionNextTest.php
    @@ -0,0 +1,178 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Profile;
    +
    +/**
    + *
    + */
    +class ServerVersionNextTest extends ServerVersionTestCase
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getProfileInstance()
    +    {
    +        return new ServerVersionNext();
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getExpectedVersion()
    +    {
    +        return '2.8';
    +    }
    +
    +    /**
    +     * {@inheritdoc}
    +     */
    +    public function getExpectedCommands()
    +    {
    +        return array(
    +            0 => 'exists',
    +            1 => 'del',
    +            2 => 'type',
    +            3 => 'keys',
    +            4 => 'randomkey',
    +            5 => 'rename',
    +            6 => 'renamenx',
    +            7 => 'expire',
    +            8 => 'expireat',
    +            9 => 'ttl',
    +            10 => 'move',
    +            11 => 'sort',
    +            12 => 'dump',
    +            13 => 'restore',
    +            14 => 'set',
    +            15 => 'setnx',
    +            16 => 'mset',
    +            17 => 'msetnx',
    +            18 => 'get',
    +            19 => 'mget',
    +            20 => 'getset',
    +            21 => 'incr',
    +            22 => 'incrby',
    +            23 => 'decr',
    +            24 => 'decrby',
    +            25 => 'rpush',
    +            26 => 'lpush',
    +            27 => 'llen',
    +            28 => 'lrange',
    +            29 => 'ltrim',
    +            30 => 'lindex',
    +            31 => 'lset',
    +            32 => 'lrem',
    +            33 => 'lpop',
    +            34 => 'rpop',
    +            35 => 'rpoplpush',
    +            36 => 'sadd',
    +            37 => 'srem',
    +            38 => 'spop',
    +            39 => 'smove',
    +            40 => 'scard',
    +            41 => 'sismember',
    +            42 => 'sinter',
    +            43 => 'sinterstore',
    +            44 => 'sunion',
    +            45 => 'sunionstore',
    +            46 => 'sdiff',
    +            47 => 'sdiffstore',
    +            48 => 'smembers',
    +            49 => 'srandmember',
    +            50 => 'zadd',
    +            51 => 'zincrby',
    +            52 => 'zrem',
    +            53 => 'zrange',
    +            54 => 'zrevrange',
    +            55 => 'zrangebyscore',
    +            56 => 'zcard',
    +            57 => 'zscore',
    +            58 => 'zremrangebyscore',
    +            59 => 'ping',
    +            60 => 'auth',
    +            61 => 'select',
    +            62 => 'echo',
    +            63 => 'quit',
    +            64 => 'info',
    +            65 => 'slaveof',
    +            66 => 'monitor',
    +            67 => 'dbsize',
    +            68 => 'flushdb',
    +            69 => 'flushall',
    +            70 => 'save',
    +            71 => 'bgsave',
    +            72 => 'lastsave',
    +            73 => 'shutdown',
    +            74 => 'bgrewriteaof',
    +            75 => 'setex',
    +            76 => 'append',
    +            77 => 'substr',
    +            78 => 'blpop',
    +            79 => 'brpop',
    +            80 => 'zunionstore',
    +            81 => 'zinterstore',
    +            82 => 'zcount',
    +            83 => 'zrank',
    +            84 => 'zrevrank',
    +            85 => 'zremrangebyrank',
    +            86 => 'hset',
    +            87 => 'hsetnx',
    +            88 => 'hmset',
    +            89 => 'hincrby',
    +            90 => 'hget',
    +            91 => 'hmget',
    +            92 => 'hdel',
    +            93 => 'hexists',
    +            94 => 'hlen',
    +            95 => 'hkeys',
    +            96 => 'hvals',
    +            97 => 'hgetall',
    +            98 => 'multi',
    +            99 => 'exec',
    +            100 => 'discard',
    +            101 => 'subscribe',
    +            102 => 'unsubscribe',
    +            103 => 'psubscribe',
    +            104 => 'punsubscribe',
    +            105 => 'publish',
    +            106 => 'config',
    +            107 => 'persist',
    +            108 => 'strlen',
    +            109 => 'setrange',
    +            110 => 'getrange',
    +            111 => 'setbit',
    +            112 => 'getbit',
    +            113 => 'rpushx',
    +            114 => 'lpushx',
    +            115 => 'linsert',
    +            116 => 'brpoplpush',
    +            117 => 'zrevrangebyscore',
    +            118 => 'watch',
    +            119 => 'unwatch',
    +            120 => 'object',
    +            121 => 'slowlog',
    +            122 => 'client',
    +            123 => 'pttl',
    +            124 => 'pexpire',
    +            125 => 'pexpireat',
    +            126 => 'psetex',
    +            127 => 'incrbyfloat',
    +            128 => 'bitop',
    +            129 => 'bitcount',
    +            130 => 'hincrbyfloat',
    +            131 => 'eval',
    +            132 => 'evalsha',
    +            133 => 'script',
    +            134 => 'time',
    +        );
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Protocol/ProtocolExceptionTest.php b/vendor/predis/predis/tests/Predis/Protocol/ProtocolExceptionTest.php
    new file mode 100644
    index 0000000..a8b2ac7
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Protocol/ProtocolExceptionTest.php
    @@ -0,0 +1,31 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol;
    +
    +require_once __DIR__.'/../CommunicationExceptionTest.php';
    +
    +use Predis\CommunicationExceptionTest;
    +use Predis\Connection\SingleConnectionInterface;
    +
    +/**
    + *
    + */
    +class ProtocolExceptionTest extends CommunicationExceptionTest
    +{
    +    /**
    +     * {@inheritdoc}
    +     */
    +    protected function getException(SingleConnectionInterface $connection, $message, $code = 0, \Exception $inner = null)
    +    {
    +        return new ProtocolException($connection, $message, $code, $inner);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Protocol/Text/ComposableTextProtocolTest.php b/vendor/predis/predis/tests/Predis/Protocol/Text/ComposableTextProtocolTest.php
    new file mode 100644
    index 0000000..383ca8c
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Protocol/Text/ComposableTextProtocolTest.php
    @@ -0,0 +1,119 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class ComposableTextProtocolTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCustomSerializer()
    +    {
    +        $serializer = $this->getMock('Predis\Protocol\CommandSerializerInterface');
    +
    +        $protocol = new ComposableTextProtocol();
    +        $protocol->setSerializer($serializer);
    +
    +        $this->assertSame($serializer, $protocol->getSerializer());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCustomReader()
    +    {
    +        $reader = $this->getMock('Predis\Protocol\ResponseReaderInterface');
    +
    +        $protocol = new ComposableTextProtocol();
    +        $protocol->setReader($reader);
    +
    +        $this->assertSame($reader, $protocol->getReader());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConnectionWrite()
    +    {
    +        $serialized = "*1\r\n$4\r\nPING\r\n";
    +
    +        $command = $this->getMock('Predis\Command\CommandInterface');
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +        $serializer = $this->getMock('Predis\Protocol\CommandSerializerInterface');
    +
    +        $protocol = new ComposableTextProtocol();
    +        $protocol->setSerializer($serializer);
    +
    +        $connection->expects($this->once())
    +                   ->method('writeBytes')
    +                   ->with($this->equalTo($serialized));
    +
    +        $serializer->expects($this->once())
    +                   ->method('serialize')
    +                   ->with($command)
    +                   ->will($this->returnValue($serialized));
    +
    +        $protocol->write($connection, $command);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConnectionRead()
    +    {
    +        $serialized = "*1\r\n$4\r\nPING\r\n";
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +        $reader = $this->getMock('Predis\Protocol\ResponseReaderInterface');
    +
    +        $protocol = new ComposableTextProtocol();
    +        $protocol->setReader($reader);
    +
    +        $reader->expects($this->once())
    +                   ->method('read')
    +                   ->with($connection)
    +                   ->will($this->returnValue('bulk'));
    +
    +        $this->assertSame('bulk', $protocol->read($connection));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSetMultibulkOption()
    +    {
    +        $protocol = new ComposableTextProtocol();
    +        $reader = $protocol->getReader();
    +
    +        $protocol->setOption('iterable_multibulk', true);
    +        $this->assertInstanceOf('Predis\Protocol\Text\ResponseMultiBulkStreamHandler', $reader->getHandler('*'));
    +
    +        $protocol->setOption('iterable_multibulk', false);
    +        $this->assertInstanceOf('Predis\Protocol\Text\ResponseMultiBulkHandler', $reader->getHandler('*'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException InvalidArgumentException
    +     * @expectedExceptionMessage The option unknown_option is not supported by the current protocol
    +     */
    +    public function testSetInvalidOption()
    +    {
    +        $protocol = new ComposableTextProtocol();
    +        $protocol->setOption('unknown_option', true);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Protocol/Text/ResponseBulkHandlerTest.php b/vendor/predis/predis/tests/Predis/Protocol/Text/ResponseBulkHandlerTest.php
    new file mode 100644
    index 0000000..813bf37
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Protocol/Text/ResponseBulkHandlerTest.php
    @@ -0,0 +1,91 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class ResponseBulkHandlerTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testZeroLengthBulk()
    +    {
    +        $handler = new ResponseBulkHandler();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->never())->method('readLine');
    +        $connection->expects($this->once())
    +                   ->method('readBytes')
    +                   ->with($this->equalTo(2))
    +                   ->will($this->returnValue("\r\n"));
    +
    +        $this->assertSame('', $handler->handle($connection, '0'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testBulk()
    +    {
    +        $bulk = "This is a bulk string.";
    +        $bulkLengh = (string) strlen($bulk);
    +
    +        $handler = new ResponseBulkHandler();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->never())->method('readLine');
    +        $connection->expects($this->once())
    +                   ->method('readBytes')
    +                   ->with($this->equalTo($bulkLengh + 2))
    +                   ->will($this->returnValue("$bulk\r\n"));
    +
    +        $this->assertSame($bulk, $handler->handle($connection, $bulkLengh));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testNull()
    +    {
    +        $handler = new ResponseBulkHandler();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->never())->method('readLine');
    +        $connection->expects($this->never())->method('readBytes');
    +
    +        $this->assertNull($handler->handle($connection, '-1'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\Protocol\ProtocolException
    +     * @expectedExceptionMessage Cannot parse 'invalid' as bulk length
    +     */
    +    public function testInvalidLength()
    +    {
    +        $handler = new ResponseBulkHandler();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->never())->method('readLine');
    +        $connection->expects($this->never())->method('readBytes');
    +
    +        $handler->handle($connection, 'invalid');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Protocol/Text/ResponseErrorHandlerTest.php b/vendor/predis/predis/tests/Predis/Protocol/Text/ResponseErrorHandlerTest.php
    new file mode 100644
    index 0000000..60ce748
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Protocol/Text/ResponseErrorHandlerTest.php
    @@ -0,0 +1,39 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class ResponseErrorHandlerTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testOk()
    +    {
    +        $handler = new ResponseErrorHandler();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->never())->method('readLine');
    +        $connection->expects($this->never())->method('readBytes');
    +
    +        $message = "ERR Operation against a key holding the wrong kind of value";
    +        $response = $handler->handle($connection, $message);
    +
    +        $this->assertInstanceOf('Predis\ResponseError', $response);
    +        $this->assertSame($message, $response->getMessage());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Protocol/Text/ResponseIntegerHandlerTest.php b/vendor/predis/predis/tests/Predis/Protocol/Text/ResponseIntegerHandlerTest.php
    new file mode 100644
    index 0000000..d7957aa
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Protocol/Text/ResponseIntegerHandlerTest.php
    @@ -0,0 +1,71 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +use Predis\ResponseQueued;
    +
    +/**
    + *
    + */
    +class ResponseIntegerHandlerTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testInteger()
    +    {
    +        $handler = new ResponseIntegerHandler();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->never())->method('readLine');
    +        $connection->expects($this->never())->method('readBytes');
    +
    +        $this->assertSame(0, $handler->handle($connection, '0'));
    +        $this->assertSame(1, $handler->handle($connection, '1'));
    +        $this->assertSame(10, $handler->handle($connection, '10'));
    +        $this->assertSame(-10, $handler->handle($connection, '-10'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testNull()
    +    {
    +        $handler = new ResponseIntegerHandler();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->never())->method('readLine');
    +        $connection->expects($this->never())->method('readBytes');
    +
    +        $this->assertNull($handler->handle($connection, 'nil'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\Protocol\ProtocolException
    +     * @expectedExceptionMessage Cannot parse 'invalid' as numeric response
    +     */
    +    public function testInvalid()
    +    {
    +        $handler = new ResponseIntegerHandler();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->never())->method('readLine');
    +        $connection->expects($this->never())->method('readBytes');
    +
    +        $handler->handle($connection, 'invalid');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Protocol/Text/ResponseMultiBulkHandlerTest.php b/vendor/predis/predis/tests/Predis/Protocol/Text/ResponseMultiBulkHandlerTest.php
    new file mode 100644
    index 0000000..80f5282
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Protocol/Text/ResponseMultiBulkHandlerTest.php
    @@ -0,0 +1,84 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class ResponseMultiBulkHandlerTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testMultiBulk()
    +    {
    +        $handler = new ResponseMultiBulkHandler();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->once())
    +                   ->method('getProtocol')
    +                   ->will($this->returnValue(new ComposableTextProtocol()));
    +
    +        $connection->expects($this->at(1))
    +                   ->method('readLine')
    +                   ->will($this->returnValue("$3"));
    +
    +        $connection->expects($this->at(2))
    +                   ->method('readBytes')
    +                   ->will($this->returnValue("foo\r\n"));
    +
    +        $connection->expects($this->at(3))
    +                   ->method('readLine')
    +                   ->will($this->returnValue("$3"));
    +
    +        $connection->expects($this->at(4))
    +                   ->method('readBytes')
    +                   ->will($this->returnValue("bar\r\n"));
    +
    +        $this->assertSame(array('foo', 'bar'), $handler->handle($connection, '2'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testNull()
    +    {
    +        $handler = new ResponseMultiBulkHandler();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->never())->method('readLine');
    +        $connection->expects($this->never())->method('readBytes');
    +
    +        $this->assertNull($handler->handle($connection, '-1'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\Protocol\ProtocolException
    +     * @expectedExceptionMessage Cannot parse 'invalid' as multi-bulk length
    +     */
    +    public function testInvalid()
    +    {
    +        $handler = new ResponseMultiBulkHandler();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->never())->method('readLine');
    +        $connection->expects($this->never())->method('readBytes');
    +
    +        $handler->handle($connection, 'invalid');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Protocol/Text/ResponseMultiBulkStreamHandlerTest.php b/vendor/predis/predis/tests/Predis/Protocol/Text/ResponseMultiBulkStreamHandlerTest.php
    new file mode 100644
    index 0000000..11dba3d
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Protocol/Text/ResponseMultiBulkStreamHandlerTest.php
    @@ -0,0 +1,52 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class ResponseMultiBulkStreamHandlerTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testOk()
    +    {
    +        $handler = new ResponseMultiBulkStreamHandler();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->never())->method('readLine');
    +        $connection->expects($this->never())->method('readBytes');
    +
    +        $this->assertInstanceOf('Predis\Iterator\MultiBulkResponseSimple', $handler->handle($connection, '1'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\Protocol\ProtocolException
    +     * @expectedExceptionMessage Cannot parse 'invalid' as multi-bulk length
    +     */
    +    public function testInvalid()
    +    {
    +        $handler = new ResponseMultiBulkStreamHandler();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->never())->method('readLine');
    +        $connection->expects($this->never())->method('readBytes');
    +
    +        $handler->handle($connection, 'invalid');
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Protocol/Text/ResponseStatusHandlerTest.php b/vendor/predis/predis/tests/Predis/Protocol/Text/ResponseStatusHandlerTest.php
    new file mode 100644
    index 0000000..d09c853
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Protocol/Text/ResponseStatusHandlerTest.php
    @@ -0,0 +1,66 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +use Predis\ResponseQueued;
    +
    +/**
    + *
    + */
    +class ResponseStatusHandlerTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testOk()
    +    {
    +        $handler = new ResponseStatusHandler();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->never())->method('readLine');
    +        $connection->expects($this->never())->method('readBytes');
    +
    +        $this->assertTrue($handler->handle($connection, 'OK'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testQueued()
    +    {
    +        $handler = new ResponseStatusHandler();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->never())->method('readLine');
    +        $connection->expects($this->never())->method('readBytes');
    +
    +        $this->assertInstanceOf('Predis\ResponseQueued', $handler->handle($connection, 'QUEUED'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testPlainString()
    +    {
    +        $handler = new ResponseStatusHandler();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->never())->method('readLine');
    +        $connection->expects($this->never())->method('readBytes');
    +
    +        $this->assertSame('Background saving started', $handler->handle($connection, 'Background saving started'));
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Protocol/Text/TextCommandSerializerTest.php b/vendor/predis/predis/tests/Predis/Protocol/Text/TextCommandSerializerTest.php
    new file mode 100644
    index 0000000..84482af
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Protocol/Text/TextCommandSerializerTest.php
    @@ -0,0 +1,64 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class TextCommandSerializerTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSerializerIdWithNoArguments()
    +    {
    +        $serializer = new TextCommandSerializer();
    +
    +        $command = $this->getMock('Predis\Command\CommandInterface');
    +
    +        $command->expects($this->once())
    +                ->method('getId')
    +                ->will($this->returnValue('PING'));
    +
    +        $command->expects($this->once())
    +                ->method('getArguments')
    +                ->will($this->returnValue(array()));
    +
    +        $result = $serializer->serialize($command);
    +
    +        $this->assertSame("*1\r\n$4\r\nPING\r\n", $result);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSerializerIdWithArguments()
    +    {
    +        $serializer = new TextCommandSerializer();
    +
    +        $command = $this->getMock('Predis\Command\CommandInterface');
    +
    +        $command->expects($this->once())
    +                ->method('getId')
    +                ->will($this->returnValue('SET'));
    +
    +        $command->expects($this->once())
    +                ->method('getArguments')
    +                ->will($this->returnValue(array('key', 'value')));
    +
    +        $result = $serializer->serialize($command);
    +
    +        $this->assertSame("*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n", $result);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Protocol/Text/TextProtocolTest.php b/vendor/predis/predis/tests/Predis/Protocol/Text/TextProtocolTest.php
    new file mode 100644
    index 0000000..369a151
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Protocol/Text/TextProtocolTest.php
    @@ -0,0 +1,120 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class TextProtocolTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConnectionWrite()
    +    {
    +        $serialized = "*1\r\n$4\r\nPING\r\n";
    +        $protocol = new TextProtocol();
    +
    +        $command = $this->getMock('Predis\Command\CommandInterface');
    +
    +        $command->expects($this->once())
    +                ->method('getId')
    +                ->will($this->returnValue('PING'));
    +
    +        $command->expects($this->once())
    +                ->method('getArguments')
    +                ->will($this->returnValue(array()));
    +
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->once())
    +                   ->method('writeBytes')
    +                   ->with($this->equalTo($serialized));
    +
    +        $protocol->write($connection, $command);
    +    }
    +
    +    /**
    +     * @todo Improve test coverage
    +     * @group disconnected
    +     */
    +    public function testConnectionRead()
    +    {
    +        $protocol = new TextProtocol();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->at(0))
    +                   ->method('readLine')
    +                   ->will($this->returnValue("+OK"));
    +
    +        $connection->expects($this->at(1))
    +                   ->method('readLine')
    +                   ->will($this->returnValue("-ERR error message"));
    +
    +        $connection->expects($this->at(2))
    +                   ->method('readLine')
    +                   ->will($this->returnValue(":2"));
    +
    +        $connection->expects($this->at(3))
    +                   ->method('readLine')
    +                   ->will($this->returnValue("$-1"));
    +
    +        $connection->expects($this->at(4))
    +                   ->method('readLine')
    +                   ->will($this->returnValue("*-1"));
    +
    +        $this->assertTrue($protocol->read($connection));
    +        $this->assertEquals("ERR error message", $protocol->read($connection));
    +        $this->assertSame(2, $protocol->read($connection));
    +        $this->assertNull($protocol->read($connection));
    +        $this->assertNull($protocol->read($connection));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testIterableMultibulkSupport()
    +    {
    +        $protocol = new TextProtocol();
    +        $protocol->setOption('iterable_multibulk', true);
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->once(4))
    +                   ->method('readLine')
    +                   ->will($this->returnValue("*1"));
    +
    +        $this->assertInstanceOf('Predis\Iterator\MultiBulkResponseSimple', $protocol->read($connection));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\Protocol\ProtocolException
    +     * @expectedExceptionMessage Unknown prefix: '!'
    +     */
    +    public function testUnknownResponsePrefix()
    +    {
    +        $protocol = new TextProtocol();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->once())
    +                   ->method('readLine')
    +                   ->will($this->returnValue('!'));
    +
    +        $protocol->read($connection);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Protocol/Text/TextResponseReaderTest.php b/vendor/predis/predis/tests/Predis/Protocol/Text/TextResponseReaderTest.php
    new file mode 100644
    index 0000000..94ddaa1
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Protocol/Text/TextResponseReaderTest.php
    @@ -0,0 +1,123 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Protocol\Text;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class TextResponseReaderTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDefaultHandlers()
    +    {
    +        $reader = new TextResponseReader();
    +
    +        $this->assertInstanceOf('Predis\Protocol\Text\ResponseStatusHandler', $reader->getHandler('+'));
    +        $this->assertInstanceOf('Predis\Protocol\Text\ResponseErrorHandler', $reader->getHandler('-'));
    +        $this->assertInstanceOf('Predis\Protocol\Text\ResponseIntegerHandler', $reader->getHandler(':'));
    +        $this->assertInstanceOf('Predis\Protocol\Text\ResponseBulkHandler', $reader->getHandler('$'));
    +        $this->assertInstanceOf('Predis\Protocol\Text\ResponseMultiBulkHandler', $reader->getHandler('*'));
    +
    +        $this->assertNull($reader->getHandler('!'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testReplaceHandler()
    +    {
    +        $handler = $this->getMock('Predis\Protocol\ResponseHandlerInterface');
    +
    +        $reader = new TextResponseReader();
    +        $reader->setHandler('+', $handler);
    +
    +        $this->assertSame($handler, $reader->getHandler('+'));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testReadResponse()
    +    {
    +        $reader = new TextResponseReader();
    +
    +        $protocol = new ComposableTextProtocol();
    +        $protocol->setReader($reader);
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->at(0))
    +                   ->method('readLine')
    +                   ->will($this->returnValue("+OK"));
    +
    +        $connection->expects($this->at(1))
    +                   ->method('readLine')
    +                   ->will($this->returnValue("-ERR error message"));
    +
    +        $connection->expects($this->at(2))
    +                   ->method('readLine')
    +                   ->will($this->returnValue(":2"));
    +
    +        $connection->expects($this->at(3))
    +                   ->method('readLine')
    +                   ->will($this->returnValue("$-1"));
    +
    +        $connection->expects($this->at(4))
    +                   ->method('readLine')
    +                   ->will($this->returnValue("*-1"));
    +
    +        $this->assertTrue($reader->read($connection));
    +        $this->assertEquals("ERR error message", $reader->read($connection));
    +        $this->assertSame(2, $reader->read($connection));
    +        $this->assertNull($reader->read($connection));
    +        $this->assertNull($reader->read($connection));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\Protocol\ProtocolException
    +     * @expectedExceptionMessage Unexpected empty header
    +     */
    +    public function testEmptyResponseHeader()
    +    {
    +        $reader = new TextResponseReader();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->once())
    +                   ->method('readLine')
    +                   ->will($this->returnValue(''));
    +
    +        $reader->read($connection);
    +    }
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\Protocol\ProtocolException
    +     * @expectedExceptionMessage Unknown prefix: '!'
    +     */
    +    public function testUnknownResponsePrefix()
    +    {
    +        $reader = new TextResponseReader();
    +
    +        $connection = $this->getMock('Predis\Connection\ComposableConnectionInterface');
    +
    +        $connection->expects($this->once())
    +                   ->method('readLine')
    +                   ->will($this->returnValue('!'));
    +
    +        $reader->read($connection);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/PubSub/DispatcherLoopTest.php b/vendor/predis/predis/tests/Predis/PubSub/DispatcherLoopTest.php
    new file mode 100644
    index 0000000..5df8475
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/PubSub/DispatcherLoopTest.php
    @@ -0,0 +1,127 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\PubSub;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Client;
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + * @group realm-pubsub
    + */
    +class DispatcherLoopTest extends StandardTestCase
    +{
    +    // ******************************************************************** //
    +    // ---- INTEGRATION TESTS --------------------------------------------- //
    +    // ******************************************************************** //
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testDispatcherLoopAgainstRedisServer()
    +    {
    +        $parameters = array(
    +            'host' => REDIS_SERVER_HOST,
    +            'port' => REDIS_SERVER_PORT,
    +            'database' => REDIS_SERVER_DBNUM,
    +            // Prevents suite from handing on broken test
    +            'read_write_timeout' => 2,
    +        );
    +
    +        $options = array('profile' => REDIS_SERVER_VERSION);
    +
    +        $producer = new Client($parameters, $options);
    +        $producer->connect();
    +
    +        $consumer = new Client($parameters, $options);
    +        $consumer->connect();
    +
    +        $dispatcher = new DispatcherLoop($consumer);
    +
    +        $function01 = $this->getMock('stdClass', array('__invoke'));
    +        $function01->expects($this->exactly(2))
    +                   ->method('__invoke')
    +                   ->with($this->logicalOr(
    +                       $this->equalTo('01:argument'),
    +                       $this->equalTo('01:quit')
    +                   ))
    +                   ->will($this->returnCallback(function ($arg) use ($dispatcher) {
    +                       if ($arg === '01:quit') {
    +                           $dispatcher->stop();
    +                       }
    +                   }));
    +
    +        $function02 = $this->getMock('stdClass', array('__invoke'));
    +        $function02->expects($this->once())
    +                   ->method('__invoke')
    +                   ->with('02:argument');
    +
    +        $function03 = $this->getMock('stdClass', array('__invoke'));
    +        $function03->expects($this->never())
    +                   ->method('__invoke');
    +
    +        $dispatcher->attachCallback('function:01', $function01);
    +        $dispatcher->attachCallback('function:02', $function02);
    +        $dispatcher->attachCallback('function:03', $function03);
    +
    +        $producer->publish('function:01', '01:argument');
    +        $producer->publish('function:02', '02:argument');
    +        $producer->publish('function:01', '01:quit');
    +
    +        $dispatcher->run();
    +
    +        $this->assertTrue($consumer->ping());
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testDispatcherLoopAgainstRedisServerWithPrefix()
    +    {
    +        $parameters = array(
    +            'host' => REDIS_SERVER_HOST,
    +            'port' => REDIS_SERVER_PORT,
    +            'database' => REDIS_SERVER_DBNUM,
    +            // Prevents suite from handing on broken test
    +            'read_write_timeout' => 2,
    +        );
    +
    +        $options = array('profile' => REDIS_SERVER_VERSION);
    +
    +        $producerNonPfx = new Client($parameters, $options);
    +        $producerNonPfx->connect();
    +
    +        $producerPfx = new Client($parameters, $options + array('prefix' => 'foobar'));
    +        $producerPfx->connect();
    +
    +        $consumer = new Client($parameters, $options + array('prefix' => 'foobar'));
    +        $dispatcher = new DispatcherLoop($consumer);
    +
    +        $callback = $this->getMock('stdClass', array('__invoke'));
    +        $callback->expects($this->exactly(1))
    +                 ->method('__invoke')
    +                 ->with($this->equalTo('arg:prefixed'))
    +                 ->will($this->returnCallback(function ($arg) use ($dispatcher) {
    +                     $dispatcher->stop();
    +                 }));
    +
    +        $dispatcher->attachCallback('callback', $callback);
    +
    +        $producerNonPfx->publish('callback', 'arg:non-prefixed');
    +        $producerPfx->publish('callback', 'arg:prefixed');
    +
    +        $dispatcher->run();
    +
    +        $this->assertTrue($consumer->ping());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/PubSub/PubSubContextTest.php b/vendor/predis/predis/tests/Predis/PubSub/PubSubContextTest.php
    new file mode 100644
    index 0000000..140e8f2
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/PubSub/PubSubContextTest.php
    @@ -0,0 +1,308 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\PubSub;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Client;
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + * @group realm-pubsub
    + */
    +class PubSubContextTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\NotSupportedException
    +     * @expectedExceptionMessage The current profile does not support PUB/SUB related commands
    +     */
    +    public function testPubSubContextRequirePubSubRelatedCommand()
    +    {
    +        $profile = $this->getMock('Predis\Profile\ServerProfileInterface');
    +        $profile->expects($this->any())
    +                ->method('supportsCommands')
    +                ->will($this->returnValue(false));
    +
    +        $client = new Client(null, array('profile' => $profile));
    +        $pubsub = new PubSubContext($client);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\NotSupportedException
    +     * @expectedExceptionMessage Cannot initialize a PUB/SUB context when using aggregated connections
    +     */
    +    public function testPubSubContextDoesNotWorkOnClusters()
    +    {
    +        $cluster = $this->getMock('Predis\Connection\ClusterConnectionInterface');
    +
    +        $client = new Client($cluster);
    +        $pubsub = new PubSubContext($client);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithoutSubscriptionsDoesNotOpenContext()
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +
    +        $client = $this->getMock('Predis\Client', array('executeCommand'), array($connection));
    +        $client->expects($this->never())->method('executeCommand');
    +
    +        $pubsub = new PubSubContext($client);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testConstructorWithSubscriptionsOpensContext()
    +    {
    +        $profile = ServerProfile::get(REDIS_SERVER_VERSION);
    +
    +        $cmdSubscribe = $profile->createCommand('subscribe', array('channel:foo'));
    +        $cmdPsubscribe = $profile->createCommand('psubscribe', array('channels:*'));
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->exactly(2))->method('writeCommand');
    +
    +        $client = $this->getMock('Predis\Client', array('createCommand', 'writeCommand'), array($connection));
    +        $client->expects($this->exactly(2))
    +               ->method('createCommand')
    +               ->with($this->logicalOr($this->equalTo('subscribe'), $this->equalTo('psubscribe')))
    +               ->will($this->returnCallback(function ($id, $args) use ($profile) {
    +                   return $profile->createCommand($id, $args);
    +               }));
    +
    +        $options = array('subscribe' => 'channel:foo', 'psubscribe' => 'channels:*');
    +        $pubsub = new PubSubContext($client, $options);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testClosingContextWithTrueClosesConnection()
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +
    +        $client = $this->getMock('Predis\Client', array('disconnect'), array($connection));
    +        $client->expects($this->exactly(1))->method('disconnect');
    +
    +        $pubsub = new PubSubContext($client, array('subscribe' => 'channel:foo'));
    +
    +        $connection->expects($this->never())->method('writeCommand');
    +
    +        $pubsub->closeContext(true);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testClosingContextWithFalseSendsUnsubscriptions()
    +    {
    +        $profile = ServerProfile::get(REDIS_SERVER_VERSION);
    +        $classUnsubscribe = $profile->getCommandClass('unsubscribe');
    +        $classPunsubscribe = $profile->getCommandClass('punsubscribe');
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +
    +        $client = $this->getMock('Predis\Client', array('disconnect'), array($connection));
    +
    +        $options = array('subscribe' => 'channel:foo', 'psubscribe' => 'channels:*');
    +        $pubsub = new PubSubContext($client, $options);
    +
    +        $connection->expects($this->exactly(2))
    +                   ->method('writeCommand')
    +                   ->with($this->logicalOr(
    +                       $this->isInstanceOf($classUnsubscribe),
    +                       $this->isInstanceOf($classPunsubscribe)
    +                   ));
    +
    +        $pubsub->closeContext(false);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testIsNotValidWhenNotSubscribed()
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $client = $this->getMock('Predis\Client', array('disconnect'), array($connection));
    +
    +        $pubsub = new PubSubContext($client);
    +
    +        $this->assertFalse($pubsub->valid());
    +        $this->assertNull($pubsub->next());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testReadsMessageFromConnection()
    +    {
    +        $rawmessage = array('message', 'channel:foo', 'message from channel');
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->once())->method('read')->will($this->returnValue($rawmessage));
    +
    +        $client = new Client($connection);
    +        $pubsub = new PubSubContext($client, array('subscribe' => 'channel:foo'));
    +
    +        $message = $pubsub->current();
    +        $this->assertSame('message', $message->kind);
    +        $this->assertSame('channel:foo', $message->channel);
    +        $this->assertSame('message from channel', $message->payload);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testReadsPmessageFromConnection()
    +    {
    +        $rawmessage = array('pmessage', 'channel:*', 'channel:foo', 'message from channel');
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->once())->method('read')->will($this->returnValue($rawmessage));
    +
    +        $client = new Client($connection);
    +        $pubsub = new PubSubContext($client, array('psubscribe' => 'channel:*'));
    +
    +        $message = $pubsub->current();
    +        $this->assertSame('pmessage', $message->kind);
    +        $this->assertSame('channel:*', $message->pattern);
    +        $this->assertSame('channel:foo', $message->channel);
    +        $this->assertSame('message from channel', $message->payload);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testReadsSubscriptionMessageFromConnection()
    +    {
    +        $rawmessage = array('subscribe', 'channel:foo', 1);
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->once())->method('read')->will($this->returnValue($rawmessage));
    +
    +        $client = new Client($connection);
    +        $pubsub = new PubSubContext($client, array('subscribe' => 'channel:foo'));
    +
    +        $message = $pubsub->current();
    +        $this->assertSame('subscribe', $message->kind);
    +        $this->assertSame('channel:foo', $message->channel);
    +        $this->assertSame(1, $message->payload);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testReadsUnsubscriptionMessageFromConnection()
    +    {
    +        $rawmessage = array('unsubscribe', 'channel:foo', 1);
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->once())->method('read')->will($this->returnValue($rawmessage));
    +
    +        $client = new Client($connection);
    +        $pubsub = new PubSubContext($client, array('subscribe' => 'channel:foo'));
    +
    +        $message = $pubsub->current();
    +        $this->assertSame('unsubscribe', $message->kind);
    +        $this->assertSame('channel:foo', $message->channel);
    +        $this->assertSame(1, $message->payload);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testUnsubscriptionMessageWithZeroChannelCountInvalidatesContext()
    +    {
    +        $rawmessage = array('unsubscribe', 'channel:foo', 0);
    +
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->once())->method('read')->will($this->returnValue($rawmessage));
    +
    +        $client = new Client($connection);
    +        $pubsub = new PubSubContext($client, array('subscribe' => 'channel:foo'));
    +
    +        $this->assertTrue($pubsub->valid());
    +
    +        $message = $pubsub->current();
    +        $this->assertSame('unsubscribe', $message->kind);
    +        $this->assertSame('channel:foo', $message->channel);
    +        $this->assertSame(0, $message->payload);
    +
    +        $this->assertFalse($pubsub->valid());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testGetUnderlyingClientInstance()
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +
    +        $client = new Client($connection);
    +        $pubsub = new PubSubContext($client);
    +
    +        $this->assertSame($client, $pubsub->getClient());
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- INTEGRATION TESTS --------------------------------------------- //
    +    // ******************************************************************** //
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testPubSubAgainstRedisServer()
    +    {
    +        $parameters = array(
    +            'host' => REDIS_SERVER_HOST,
    +            'port' => REDIS_SERVER_PORT,
    +            'database' => REDIS_SERVER_DBNUM,
    +            // Prevents suite from handing on broken test
    +            'read_write_timeout' => 2,
    +        );
    +
    +        $options = array('profile' => REDIS_SERVER_VERSION);
    +        $messages = array();
    +
    +        $producer = new Client($parameters, $options);
    +        $producer->connect();
    +
    +        $consumer = new Client($parameters, $options);
    +        $consumer->connect();
    +
    +        $pubsub = new PubSubContext($consumer);
    +        $pubsub->subscribe('channel:foo');
    +
    +        $producer->publish('channel:foo', 'message1');
    +        $producer->publish('channel:foo', 'message2');
    +        $producer->publish('channel:foo', 'QUIT');
    +
    +        foreach ($pubsub as $message) {
    +            if ($message->kind !== 'message') {
    +                continue;
    +            }
    +            $messages[] = ($payload = $message->payload);
    +            if ($payload === 'QUIT') {
    +                $pubsub->closeContext();
    +            }
    +        }
    +
    +        $this->assertSame(array('message1', 'message2', 'QUIT'), $messages);
    +        $this->assertFalse($pubsub->valid());
    +        $this->assertTrue($consumer->ping());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Replication/ReplicationStrategyTest.php b/vendor/predis/predis/tests/Predis/Replication/ReplicationStrategyTest.php
    new file mode 100644
    index 0000000..10365a2
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Replication/ReplicationStrategyTest.php
    @@ -0,0 +1,374 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Replication;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Command\CommandInterface;
    +use Predis\Profile\ServerProfile;
    +
    +/**
    + *
    + */
    +class ReplicationStrategyTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testReadCommands()
    +    {
    +        $profile = ServerProfile::getDevelopment();
    +        $strategy = new ReplicationStrategy();
    +
    +        foreach ($this->getExpectedCommands('read') as $commandId) {
    +            $command = $profile->createCommand($commandId);
    +            $this->assertTrue($strategy->isReadOperation($command));
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testWriteCommands()
    +    {
    +        $profile = ServerProfile::getDevelopment();
    +        $strategy = new ReplicationStrategy();
    +
    +        foreach ($this->getExpectedCommands('write') as $commandId) {
    +            $command = $profile->createCommand($commandId);
    +            $this->assertFalse($strategy->isReadOperation($command), $commandId);
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDisallowedCommands()
    +    {
    +        $profile = ServerProfile::getDevelopment();
    +        $strategy = new ReplicationStrategy();
    +
    +        foreach ($this->getExpectedCommands('disallowed') as $commandId) {
    +            $command = $profile->createCommand($commandId);
    +            $this->assertTrue($strategy->isDisallowedOperation($command), $commandId);
    +        }
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSortCommand()
    +    {
    +        $profile = ServerProfile::getDevelopment();
    +        $strategy = new ReplicationStrategy();
    +
    +        $cmdReadSort = $profile->createCommand('SORT', array('key:list'));
    +        $this->assertTrue($strategy->isReadOperation($cmdReadSort), 'SORT [read-only]');
    +
    +        $cmdWriteSort = $profile->createCommand('SORT', array('key:list', array('store' => 'key:stored')));
    +        $this->assertFalse($strategy->isReadOperation($cmdWriteSort), 'SORT [write with STORE]');
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\NotSupportedException
    +     * @expectedExceptionMessage The command INFO is not allowed in replication mode
    +     */
    +    public function testUsingDisallowedCommandThrowsException()
    +    {
    +        $profile = ServerProfile::getDevelopment();
    +        $strategy = new ReplicationStrategy();
    +
    +        $command = $profile->createCommand('INFO');
    +        $strategy->isReadOperation($command);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testDefaultIsWriteOperation()
    +    {
    +        $strategy = new ReplicationStrategy();
    +
    +        $command = $this->getMock('Predis\Command\CommandInterface');
    +        $command->expects($this->any())
    +                ->method('getId')
    +                ->will($this->returnValue('CMDTEST'));
    +
    +        $this->assertFalse($strategy->isReadOperation($command));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanSetCommandAsReadOperation()
    +    {
    +        $strategy = new ReplicationStrategy();
    +
    +        $command = $this->getMock('Predis\Command\CommandInterface');
    +        $command->expects($this->any())
    +                ->method('getId')
    +                ->will($this->returnValue('CMDTEST'));
    +
    +
    +        $strategy->setCommandReadOnly('CMDTEST', true);
    +        $this->assertTrue($strategy->isReadOperation($command));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanSetCommandAsWriteOperation()
    +    {
    +        $strategy = new ReplicationStrategy();
    +
    +        $command = $this->getMock('Predis\Command\CommandInterface');
    +        $command->expects($this->any())
    +                ->method('getId')
    +                ->will($this->returnValue('CMDTEST'));
    +
    +
    +        $strategy->setCommandReadOnly('CMDTEST', false);
    +        $this->assertFalse($strategy->isReadOperation($command));
    +
    +        $strategy->setCommandReadOnly('GET', false);
    +        $this->assertFalse($strategy->isReadOperation($command));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCanUseCallableToCheckCommand()
    +    {
    +        $strategy = new ReplicationStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +
    +        $strategy->setCommandReadOnly('SET', function ($command) {
    +            return $command->getArgument(1) === true;
    +        });
    +
    +        $command = $profile->createCommand('SET', array('trigger', false));
    +        $this->assertFalse($strategy->isReadOperation($command));
    +
    +        $command = $profile->createCommand('SET', array('trigger', true));
    +        $this->assertTrue($strategy->isReadOperation($command));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSetLuaScriptAsReadOperation()
    +    {
    +        $strategy = new ReplicationStrategy();
    +        $profile = ServerProfile::getDevelopment();
    +
    +        $writeScript = 'redis.call("set", "foo", "bar")';
    +        $readScript = 'return true';
    +
    +        $strategy->setScriptReadOnly($readScript, true);
    +
    +        $cmdEval = $profile->createCommand('EVAL', array($writeScript));
    +        $cmdEvalSHA = $profile->createCommand('EVALSHA', array(sha1($writeScript)));
    +        $this->assertFalse($strategy->isReadOperation($cmdEval));
    +        $this->assertFalse($strategy->isReadOperation($cmdEvalSHA));
    +
    +        $cmdEval = $profile->createCommand('EVAL', array($readScript));
    +        $cmdEvalSHA = $profile->createCommand('EVALSHA', array(sha1($readScript)));
    +        $this->assertTrue($strategy->isReadOperation($cmdEval));
    +        $this->assertTrue($strategy->isReadOperation($cmdEvalSHA));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSetLuaScriptAsReadOperationWorksWithScriptedCommand()
    +    {
    +        $strategy = new ReplicationStrategy();
    +
    +        $command = $this->getMock('Predis\Command\ScriptedCommand', array('getScript'));
    +        $command->expects($this->any())
    +                ->method('getScript')
    +                ->will($this->returnValue($script = 'return true'));
    +
    +        $strategy->setScriptReadOnly($script, function ($command) {
    +            return $command->getArgument(2) === true;
    +        });
    +
    +        $command->setArguments(array(false));
    +        $this->assertFalse($strategy->isReadOperation($command));
    +
    +        $command->setArguments(array(true));
    +        $this->assertTrue($strategy->isReadOperation($command));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSetLuaScriptAsReadOperationWorksWithScriptedCommandAndCallableCheck()
    +    {
    +        $strategy = new ReplicationStrategy();
    +
    +        $command = $this->getMock('Predis\Command\ScriptedCommand', array('getScript'));
    +        $command->expects($this->any())
    +                ->method('getScript')
    +                ->will($this->returnValue($script = 'return true'));
    +
    +        $command->setArguments(array('trigger', false));
    +
    +        $strategy->setScriptReadOnly($script, true);
    +
    +        $this->assertTrue($strategy->isReadOperation($command));
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Returns the list of expected supported commands.
    +     *
    +     * @param string $type Optional type of command (based on its keys)
    +     * @return array
    +     */
    +    protected function getExpectedCommands($type = null)
    +    {
    +        $commands = array(
    +            /* commands operating on the connection */
    +            'EXISTS'                => 'read',
    +            'AUTH'                  => 'read',
    +            'SELECT'                => 'read',
    +            'ECHO'                  => 'read',
    +            'QUIT'                  => 'read',
    +            'OBJECT'                => 'read',
    +            'BITCOUNT'              => 'read',
    +            'TIME'                  => 'read',
    +            'SHUTDOWN'              => 'disallowed',
    +            'INFO'                  => 'disallowed',
    +            'DBSIZE'                => 'disallowed',
    +            'LASTSAVE'              => 'disallowed',
    +            'CONFIG'                => 'disallowed',
    +            'MONITOR'               => 'disallowed',
    +            'SLAVEOF'               => 'disallowed',
    +            'SAVE'                  => 'disallowed',
    +            'BGSAVE'                => 'disallowed',
    +            'BGREWRITEAOF'          => 'disallowed',
    +            'SLOWLOG'               => 'disallowed',
    +
    +            /* commands operating on the key space */
    +            'EXISTS'                => 'read',
    +            'DEL'                   => 'write',
    +            'TYPE'                  => 'read',
    +            'EXPIRE'                => 'write',
    +            'EXPIREAT'              => 'write',
    +            'PERSIST'               => 'write',
    +            'PEXPIRE'               => 'write',
    +            'PEXPIREAT'             => 'write',
    +            'TTL'                   => 'read',
    +            'PTTL'                  => 'write',
    +            'SORT'                  => 'variable',
    +            'KEYS'                  => 'read',
    +            'RANDOMKEY'             => 'read',
    +
    +            /* commands operating on string values */
    +            'APPEND'                => 'write',
    +            'DECR'                  => 'write',
    +            'DECRBY'                => 'write',
    +            'GET'                   => 'read',
    +            'GETBIT'                => 'read',
    +            'MGET'                  => 'read',
    +            'SET'                   => 'write',
    +            'GETRANGE'              => 'read',
    +            'GETSET'                => 'write',
    +            'INCR'                  => 'write',
    +            'INCRBY'                => 'write',
    +            'SETBIT'                => 'write',
    +            'SETEX'                 => 'write',
    +            'MSET'                  => 'write',
    +            'MSETNX'                => 'write',
    +            'SETNX'                 => 'write',
    +            'SETRANGE'              => 'write',
    +            'STRLEN'                => 'read',
    +            'SUBSTR'                => 'read',
    +
    +            /* commands operating on lists */
    +            'LINSERT'               => 'write',
    +            'LINDEX'                => 'read',
    +            'LLEN'                  => 'read',
    +            'LPOP'                  => 'write',
    +            'RPOP'                  => 'write',
    +            'BLPOP'                 => 'write',
    +            'BRPOP'                 => 'write',
    +            'LPUSH'                 => 'write',
    +            'LPUSHX'                => 'write',
    +            'RPUSH'                 => 'write',
    +            'RPUSHX'                => 'write',
    +            'LRANGE'                => 'read',
    +            'LREM'                  => 'write',
    +            'LSET'                  => 'write',
    +            'LTRIM'                 => 'write',
    +
    +            /* commands operating on sets */
    +            'SADD'                  => 'write',
    +            'SCARD'                 => 'read',
    +            'SISMEMBER'             => 'read',
    +            'SMEMBERS'              => 'read',
    +            'SPOP'                  => 'write',
    +            'SRANDMEMBER'           => 'read',
    +            'SREM'                  => 'write',
    +            'SINTER'                => 'read',
    +            'SUNION'                => 'read',
    +            'SDIFF'                 => 'read',
    +
    +            /* commands operating on sorted sets */
    +            'ZADD'                  => 'write',
    +            'ZCARD'                 => 'read',
    +            'ZCOUNT'                => 'read',
    +            'ZINCRBY'               => 'write',
    +            'ZRANGE'                => 'read',
    +            'ZRANGEBYSCORE'         => 'read',
    +            'ZRANK'                 => 'read',
    +            'ZREM'                  => 'write',
    +            'ZREMRANGEBYRANK'       => 'write',
    +            'ZREMRANGEBYSCORE'      => 'write',
    +            'ZREVRANGE'             => 'read',
    +            'ZREVRANGEBYSCORE'      => 'read',
    +            'ZREVRANK'              => 'read',
    +            'ZSCORE'                => 'read',
    +
    +            /* commands operating on hashes */
    +            'HDEL'                  => 'write',
    +            'HEXISTS'               => 'read',
    +            'HGET'                  => 'read',
    +            'HGETALL'               => 'read',
    +            'HMGET'                 => 'read',
    +            'HINCRBY'               => 'write',
    +            'HINCRBYFLOAT'          => 'write',
    +            'HKEYS'                 => 'read',
    +            'HLEN'                  => 'read',
    +            'HSET'                  => 'write',
    +            'HSETNX'                => 'write',
    +            'HVALS'                 => 'read',
    +
    +            /* scripting */
    +            'EVAL'                  => 'write',
    +            'EVALSHA'               => 'write',
    +        );
    +
    +        if (isset($type)) {
    +            $commands = array_filter($commands, function ($expectedType) use ($type) {
    +                return $expectedType === $type;
    +            });
    +        }
    +
    +        return array_keys($commands);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/ResponseErrorTest.php b/vendor/predis/predis/tests/Predis/ResponseErrorTest.php
    new file mode 100644
    index 0000000..ec9e8bd
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/ResponseErrorTest.php
    @@ -0,0 +1,63 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class ResponseErrorTest extends StandardTestCase
    +{
    +    const ERR_WRONG_KEY_TYPE = 'ERR Operation against a key holding the wrong kind of value';
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testResponseErrorClass()
    +    {
    +        $error = new ResponseError(self::ERR_WRONG_KEY_TYPE);
    +
    +        $this->assertInstanceOf('Predis\ResponseErrorInterface', $error);
    +        $this->assertInstanceOf('Predis\ResponseObjectInterface', $error);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testErrorMessage()
    +    {
    +        $error = new ResponseError(self::ERR_WRONG_KEY_TYPE);
    +
    +        $this->assertEquals(self::ERR_WRONG_KEY_TYPE, $error->getMessage());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testErrorType()
    +    {
    +        $exception = new ResponseError(self::ERR_WRONG_KEY_TYPE);
    +
    +        $this->assertEquals('ERR', $exception->getErrorType());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testToString()
    +    {
    +        $error = new ResponseError(self::ERR_WRONG_KEY_TYPE);
    +
    +        $this->assertEquals(self::ERR_WRONG_KEY_TYPE, (string) $error);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/ResponseQueuedTest.php b/vendor/predis/predis/tests/Predis/ResponseQueuedTest.php
    new file mode 100644
    index 0000000..fb40011
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/ResponseQueuedTest.php
    @@ -0,0 +1,51 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class ResponseQueuedTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testResponseQueuedClass()
    +    {
    +        $queued = new ResponseQueued();
    +
    +        $this->assertInstanceOf('Predis\ResponseObjectInterface', $queued);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testToString()
    +    {
    +        $queued = new ResponseQueued();
    +
    +        $this->assertEquals('QUEUED', (string) $queued);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testQueuedProperty()
    +    {
    +        $queued = new ResponseQueued();
    +
    +        $this->assertTrue(isset($queued->queued));
    +        $this->assertTrue($queued->queued);
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/ServerExceptionTest.php b/vendor/predis/predis/tests/Predis/ServerExceptionTest.php
    new file mode 100644
    index 0000000..9241044
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/ServerExceptionTest.php
    @@ -0,0 +1,69 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +/**
    + *
    + */
    +class ServerExceptionTest extends StandardTestCase
    +{
    +    const ERR_WRONG_KEY_TYPE = 'ERR Operation against a key holding the wrong kind of value';
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExceptionMessage()
    +    {
    +        $this->setExpectedException('Predis\ServerException', self::ERR_WRONG_KEY_TYPE);
    +
    +        throw new ServerException(self::ERR_WRONG_KEY_TYPE);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExceptionClass()
    +    {
    +        $exception = new ServerException(self::ERR_WRONG_KEY_TYPE);
    +
    +        $this->assertInstanceOf('Predis\ServerException', $exception);
    +        $this->assertInstanceOf('Predis\ResponseErrorInterface', $exception);
    +        $this->assertInstanceOf('Predis\ResponseObjectInterface', $exception);
    +        $this->assertInstanceOf('Predis\PredisException', $exception);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testErrorType()
    +    {
    +        $exception = new ServerException(self::ERR_WRONG_KEY_TYPE);
    +
    +        $this->assertEquals('ERR', $exception->getErrorType());
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testToResponseError()
    +    {
    +        $exception = new ServerException(self::ERR_WRONG_KEY_TYPE);
    +        $error = $exception->toResponseError();
    +
    +        $this->assertInstanceOf('Predis\ResponseError', $error);
    +
    +        $this->assertEquals($exception->getMessage(), $error->getMessage());
    +        $this->assertEquals($exception->getErrorType(), $error->getErrorType());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Transaction/AbortedMultiExecExceptionTest.php b/vendor/predis/predis/tests/Predis/Transaction/AbortedMultiExecExceptionTest.php
    new file mode 100644
    index 0000000..ad4f06e
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Transaction/AbortedMultiExecExceptionTest.php
    @@ -0,0 +1,36 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Transaction;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Client;
    +
    +/**
    + *
    + */
    +class AbortedMultiExecExceptionTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExceptionClass()
    +    {
    +        $client = new Client();
    +        $transaction = new MultiExecContext($client);
    +        $exception = new AbortedMultiExecException($transaction, 'ABORTED');
    +
    +        $this->assertInstanceOf('Predis\PredisException', $exception);
    +        $this->assertSame('ABORTED', $exception->getMessage());
    +        $this->assertSame($transaction, $exception->getTransaction());
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/Predis/Transaction/MultiExecContextTest.php b/vendor/predis/predis/tests/Predis/Transaction/MultiExecContextTest.php
    new file mode 100644
    index 0000000..c22286f
    --- /dev/null
    +++ b/vendor/predis/predis/tests/Predis/Transaction/MultiExecContextTest.php
    @@ -0,0 +1,784 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +namespace Predis\Transaction;
    +
    +use \PHPUnit_Framework_TestCase as StandardTestCase;
    +
    +use Predis\Client;
    +use Predis\ResponseQueued;
    +use Predis\ServerException;
    +use Predis\Command\CommandInterface;
    +
    +/**
    + * @group realm-transaction
    + */
    +class MultiExecContextTest extends StandardTestCase
    +{
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\NotSupportedException
    +     * @expectedExceptionMessage The current profile does not support MULTI, EXEC and DISCARD
    +     */
    +    public function testThrowsExceptionOnUnsupportedMultiExecInProfile()
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $client = new Client($connection, array('profile' => '1.2'));
    +        $tx = new MultiExecContext($client);
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\NotSupportedException
    +     * @expectedExceptionMessage The current profile does not support WATCH and UNWATCH
    +     */
    +    public function testThrowsExceptionOnUnsupportedWatchUnwatchInProfile()
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $client = new Client($connection, array('profile' => '2.0'));
    +        $tx = new MultiExecContext($client, array('options' => 'cas'));
    +
    +        $tx->watch('foo');
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecutionWithFluentInterface()
    +    {
    +        $commands = array();
    +        $expected = array('one', 'two', 'three');
    +
    +        $callback = $this->getExecuteCallback($expected, $commands);
    +        $tx = $this->getMockedTransaction($callback);
    +
    +        $this->assertSame($expected, $tx->echo('one')->echo('two')->echo('three')->execute());
    +        $this->assertSame(array('MULTI', 'ECHO', 'ECHO', 'ECHO', 'EXEC'), self::commandsToIDs($commands));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testExecutionWithCallable()
    +    {
    +        $commands = array();
    +        $expected = array('one', 'two', 'three');
    +
    +        $callback = $this->getExecuteCallback($expected, $commands);
    +        $tx = $this->getMockedTransaction($callback);
    +
    +        $replies = $tx->execute(function ($tx) {
    +            $tx->echo('one');
    +            $tx->echo('two');
    +            $tx->echo('three');
    +        });
    +
    +        $this->assertSame($expected, $replies);
    +        $this->assertSame(array('MULTI', 'ECHO', 'ECHO', 'ECHO', 'EXEC'), self::commandsToIDs($commands));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCannotMixExecutionWithFluentInterfaceAndCallable()
    +    {
    +        $commands = array();
    +
    +        $callback = $this->getExecuteCallback(null, $commands);
    +        $tx = $this->getMockedTransaction($callback);
    +
    +        $exception = null;
    +
    +        try {
    +            $tx->echo('foo')->execute(function ($tx) {
    +                $tx->echo('bar');
    +            });
    +        } catch (\Exception $ex) {
    +            $exception = $ex;
    +        }
    +
    +        $this->assertInstanceOf('Predis\ClientException', $exception);
    +        $this->assertSame(array('MULTI', 'ECHO', 'DISCARD'), self::commandsToIDs($commands));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testEmptyTransactionDoesNotSendMultiExecCommands()
    +    {
    +        $commands = array();
    +
    +        $callback = $this->getExecuteCallback(null, $commands);
    +        $tx = $this->getMockedTransaction($callback);
    +
    +        $replies = $tx->execute(function ($tx) {
    +            // NOOP
    +        });
    +
    +        $this->assertNull($replies);
    +        $this->assertSame(array(), self::commandsToIDs($commands));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\ClientException
    +     * @expectedExceptionMessage Cannot invoke 'execute' or 'exec' inside an active client transaction block
    +     */
    +    public function testThrowsExceptionOnExecInsideTransactionBlock()
    +    {
    +        $commands = array();
    +
    +        $callback = $this->getExecuteCallback(null, $commands);
    +        $tx = $this->getMockedTransaction($callback);
    +
    +        $replies = $tx->execute(function ($tx) {
    +            $tx->exec();
    +        });
    +
    +        $this->assertNull($replies);
    +        $this->assertSame(array(), self::commandsToIDs($commands));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testEmptyTransactionIgnoresDiscard()
    +    {
    +        $commands = array();
    +
    +        $callback = $this->getExecuteCallback(null, $commands);
    +        $tx = $this->getMockedTransaction($callback);
    +
    +        $replies = $tx->execute(function ($tx) {
    +            $tx->discard();
    +        });
    +
    +        $this->assertNull($replies);
    +        $this->assertSame(array(), self::commandsToIDs($commands));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testTransactionWithCommandsSendsDiscard()
    +    {
    +        $commands = array();
    +
    +        $callback = $this->getExecuteCallback(null, $commands);
    +        $tx = $this->getMockedTransaction($callback);
    +
    +        $replies = $tx->execute(function ($tx) {
    +            $tx->set('foo', 'bar');
    +            $tx->get('foo');
    +            $tx->discard();
    +        });
    +
    +        $this->assertNull($replies);
    +        $this->assertSame(array('MULTI', 'SET', 'GET', 'DISCARD'), self::commandsToIDs($commands));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testSendMultiOnCommandsFollowingDiscard()
    +    {
    +        $commands = array();
    +        $expected = array('after DISCARD');
    +
    +        $callback = $this->getExecuteCallback($expected, $commands);
    +        $tx = $this->getMockedTransaction($callback);
    +
    +        $replies = $tx->execute(function ($tx) {
    +            $tx->echo('before DISCARD');
    +            $tx->discard();
    +            $tx->echo('after DISCARD');
    +        });
    +
    +        $this->assertSame($replies, $expected);
    +        $this->assertSame(array('MULTI', 'ECHO', 'DISCARD', 'MULTI', 'ECHO', 'EXEC'), self::commandsToIDs($commands));
    +    }
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\ClientException
    +     */
    +    public function testThrowsExceptionOnWatchInsideMulti()
    +    {
    +        $callback = $this->getExecuteCallback();
    +        $tx = $this->getMockedTransaction($callback);
    +
    +        $tx->echo('foobar')->watch('foo')->execute();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testUnwatchInsideMulti()
    +    {
    +        $commands = array();
    +        $expected = array('foobar', true);
    +
    +        $callback = $this->getExecuteCallback($expected, $commands);
    +        $tx = $this->getMockedTransaction($callback);
    +
    +        $replies = $tx->echo('foobar')->unwatch('foo')->execute();
    +
    +        $this->assertSame($replies, $expected);
    +        $this->assertSame(array('MULTI', 'ECHO', 'UNWATCH', 'EXEC'), self::commandsToIDs($commands));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAutomaticWatchInOptions()
    +    {
    +        $txCommands = $casCommands = array();
    +        $expected = array('bar', 'piyo');
    +        $options = array('watch' => array('foo', 'hoge'));
    +
    +        $callback = $this->getExecuteCallback($expected, $txCommands, $casCommands);
    +        $tx = $this->getMockedTransaction($callback, $options);
    +
    +        $replies = $tx->execute(function ($tx) {
    +            $tx->get('foo');
    +            $tx->get('hoge');
    +        });
    +
    +        $this->assertSame($replies, $expected);
    +        $this->assertSame(array('WATCH'), self::commandsToIDs($casCommands));
    +        $this->assertSame(array('foo', 'hoge'), $casCommands[0]->getArguments());
    +        $this->assertSame(array('MULTI', 'GET', 'GET', 'EXEC'), self::commandsToIDs($txCommands));
    +    }
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCheckAndSetWithFluentInterface()
    +    {
    +        $txCommands = $casCommands = array();
    +        $expected = array('bar', 'piyo');
    +        $options = array('cas' => true, 'watch' => array('foo', 'hoge'));
    +
    +        $callback = $this->getExecuteCallback($expected, $txCommands, $casCommands);
    +        $tx = $this->getMockedTransaction($callback, $options);
    +
    +        $tx->watch('foobar');
    +        $this->assertSame('DUMMY_REPLY', $tx->get('foo'));
    +        $this->assertSame('DUMMY_REPLY', $tx->get('hoge'));
    +
    +        $replies = $tx->multi()
    +                      ->get('foo')
    +                      ->get('hoge')
    +                      ->execute();
    +
    +        $this->assertSame($replies, $expected);
    +        $this->assertSame(array('WATCH', 'WATCH', 'GET', 'GET'), self::commandsToIDs($casCommands));
    +        $this->assertSame(array('MULTI', 'GET', 'GET', 'EXEC'), self::commandsToIDs($txCommands));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCheckAndSetWithBlock()
    +    {
    +        $txCommands = $casCommands = array();
    +        $expected = array('bar', 'piyo');
    +        $options = array('cas' => true, 'watch' => array('foo', 'hoge'));
    +
    +        $callback = $this->getExecuteCallback($expected, $txCommands, $casCommands);
    +        $tx = $this->getMockedTransaction($callback, $options);
    +
    +        $test = $this;
    +        $replies = $tx->execute(function ($tx) use ($test) {
    +            $tx->watch('foobar');
    +
    +            $reply1 = $tx->get('foo');
    +            $reply2 = $tx->get('hoge');
    +
    +            $test->assertSame('DUMMY_REPLY', $reply1);
    +            $test->assertSame('DUMMY_REPLY', $reply2);
    +
    +            $tx->multi();
    +
    +            $tx->get('foo');
    +            $tx->get('hoge');
    +        });
    +
    +        $this->assertSame($replies, $expected);
    +        $this->assertSame(array('WATCH', 'WATCH', 'GET', 'GET'), self::commandsToIDs($casCommands));
    +        $this->assertSame(array('MULTI', 'GET', 'GET', 'EXEC'), self::commandsToIDs($txCommands));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCheckAndSetWithEmptyBlock()
    +    {
    +        $txCommands = $casCommands = array();
    +        $options = array('cas' => true);
    +
    +        $callback = $this->getExecuteCallback(array(), $txCommands, $casCommands);
    +        $tx = $this->getMockedTransaction($callback, $options);
    +
    +        $tx->execute(function ($tx) {
    +            $tx->multi();
    +        });
    +
    +        $this->assertSame(array(), self::commandsToIDs($casCommands));
    +        $this->assertSame(array(), self::commandsToIDs($txCommands));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testCheckAndSetWithoutExec()
    +    {
    +        $txCommands = $casCommands = array();
    +        $options = array('cas' => true);
    +
    +        $callback = $this->getExecuteCallback(array(), $txCommands, $casCommands);
    +        $tx = $this->getMockedTransaction($callback, $options);
    +
    +        $tx->execute(function ($tx) {
    +            $bar = $tx->get('foo');
    +            $tx->set('hoge', 'piyo');
    +        });
    +
    +        $this->assertSame(array('GET', 'SET'), self::commandsToIDs($casCommands));
    +        $this->assertSame(array(), self::commandsToIDs($txCommands));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException InvalidArgumentException
    +     * @expectedExceptionMessage Automatic retries can be used only when a transaction block is provided
    +     */
    +    public function testThrowsExceptionOnAutomaticRetriesWithFluentInterface()
    +    {
    +        $options = array('retry' => 1);
    +
    +        $callback = $this->getExecuteCallback();
    +        $tx = $this->getMockedTransaction($callback, $options);
    +
    +        $tx->echo('message')->execute();
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testAutomaticRetryOnServerSideTransactionAbort()
    +    {
    +        $casCommands = $txCommands = array();
    +        $expected = array('bar');
    +        $options = array('watch' => array('foo', 'bar'), 'retry' => ($attempts = 2) + 1);
    +
    +        $sentinel = $this->getMock('stdClass', array('signal'));
    +        $sentinel->expects($this->exactly($attempts))->method('signal');
    +
    +        $callback = $this->getExecuteCallback($expected, $txCommands, $casCommands);
    +        $tx = $this->getMockedTransaction($callback, $options);
    +
    +        $replies = $tx->execute(function ($tx) use ($sentinel, &$attempts) {
    +            $tx->get('foo');
    +
    +            if ($attempts > 0) {
    +                $attempts -= 1;
    +                $sentinel->signal();
    +
    +                $tx->echo('!!ABORT!!');
    +            }
    +        });
    +
    +        $this->assertSame($replies, $expected);
    +        $this->assertSame(array('WATCH'), self::commandsToIDs($casCommands));
    +        $this->assertSame(array('foo', 'bar'), $casCommands[0]->getArguments());
    +        $this->assertSame(array('MULTI', 'GET', 'EXEC'), self::commandsToIDs($txCommands));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     * @expectedException Predis\Transaction\AbortedMultiExecException
    +     */
    +    public function testThrowsExceptionOnServerSideTransactionAbort()
    +    {
    +        $callback = $this->getExecuteCallback();
    +        $tx = $this->getMockedTransaction($callback);
    +
    +        $replies = $tx->execute(function ($tx) {
    +            $tx->echo('!!ABORT!!');
    +        });
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testHandlesStandardExceptionsInBlock()
    +    {
    +        $commands = array();
    +        $expected = array('foobar', true);
    +
    +        $callback = $this->getExecuteCallback($expected, $commands);
    +        $tx = $this->getMockedTransaction($callback);
    +
    +        $replies = null;
    +
    +        try {
    +            $replies = $tx->execute(function ($tx) {
    +                $tx->set('foo', 'bar');
    +                $tx->get('foo');
    +
    +                throw new \RuntimeException('TEST');
    +            });
    +        } catch (\Exception $ex) {
    +            // NOOP
    +        }
    +
    +        $this->assertNull($replies, $expected);
    +        $this->assertSame(array('MULTI', 'SET', 'GET', 'DISCARD'), self::commandsToIDs($commands));
    +    }
    +
    +    /**
    +     * @group disconnected
    +     */
    +    public function testHandlesServerExceptionsInBlock()
    +    {
    +        $commands = array();
    +        $expected = array('foobar', true);
    +
    +        $callback = $this->getExecuteCallback($expected, $commands);
    +        $tx = $this->getMockedTransaction($callback);
    +
    +        $replies = null;
    +
    +        try {
    +            $replies = $tx->execute(function ($tx) {
    +                $tx->set('foo', 'bar');
    +                $tx->echo('ERR Invalid operation');
    +                $tx->get('foo');
    +            });
    +        } catch (ServerException $ex) {
    +            $tx->discard();
    +        }
    +
    +        $this->assertNull($replies);
    +        $this->assertSame(array('MULTI', 'SET', 'ECHO', 'DISCARD'), self::commandsToIDs($commands));
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- INTEGRATION TESTS --------------------------------------------- //
    +    // ******************************************************************** //
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testIntegrationHandlesStandardExceptionsInBlock()
    +    {
    +        $client = $this->getClient();
    +        $exception = null;
    +
    +        try {
    +            $client->multiExec(function ($tx) {
    +                $tx->set('foo', 'bar');
    +                throw new \RuntimeException("TEST");
    +            });
    +        } catch (\Exception $ex) {
    +            $exception = $ex;
    +        }
    +
    +        $this->assertInstanceOf('RuntimeException', $exception);
    +        $this->assertFalse($client->exists('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testIntegrationThrowsExceptionOnRedisErrorInBlock()
    +    {
    +        $client = $this->getClient();
    +        $exception = null;
    +        $value = (string) rand();
    +
    +        try {
    +            $client->multiExec(function ($tx) use ($value) {
    +                $tx->set('foo', 'bar');
    +                $tx->lpush('foo', 'bar');
    +                $tx->set('foo', $value);
    +            });
    +        } catch (ServerException $ex) {
    +            $exception = $ex;
    +        }
    +
    +        $this->assertInstanceOf('Predis\ResponseErrorInterface', $exception);
    +        $this->assertSame($value, $client->get('foo'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testIntegrationReturnsErrorObjectOnRedisErrorInBlock()
    +    {
    +        $client = $this->getClient(array(), array('exceptions' => false));
    +
    +        $replies = $client->multiExec(function ($tx) {
    +            $tx->set('foo', 'bar');
    +            $tx->lpush('foo', 'bar');
    +            $tx->echo('foobar');
    +        });
    +
    +        $this->assertTrue($replies[0]);
    +        $this->assertInstanceOf('Predis\ResponseErrorInterface', $replies[1]);
    +        $this->assertSame('foobar', $replies[2]);
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testIntegrationSendMultiOnCommandsAfterDiscard()
    +    {
    +        $client = $this->getClient();
    +
    +        $replies = $client->multiExec(function ($tx) {
    +            $tx->set('foo', 'bar');
    +            $tx->discard();
    +            $tx->set('hoge', 'piyo');
    +        });
    +
    +        $this->assertSame(1, count($replies));
    +        $this->assertFalse($client->exists('foo'));
    +        $this->assertTrue($client->exists('hoge'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testIntegrationWritesOnWatchedKeysAbortTransaction()
    +    {
    +        $exception = null;
    +        $client1 = $this->getClient();
    +        $client2 = $this->getClient();
    +
    +        try {
    +            $client1->multiExec(array('watch' => 'sentinel'), function ($tx) use ($client2) {
    +                $tx->set('sentinel', 'client1');
    +                $tx->get('sentinel');
    +                $client2->set('sentinel', 'client2');
    +            });
    +        } catch (AbortedMultiExecException $ex) {
    +            $exception = $ex;
    +        }
    +
    +        $this->assertInstanceOf('Predis\Transaction\AbortedMultiExecException', $exception);
    +        $this->assertSame('client2', $client1->get('sentinel'));
    +    }
    +
    +    /**
    +     * @group connected
    +     */
    +    public function testIntegrationCheckAndSetWithDiscardAndRetry()
    +    {
    +        $client = $this->getClient();
    +
    +        $client->set('foo', 'bar');
    +        $options = array('watch' => 'foo', 'cas' => true);
    +
    +        $replies = $client->multiExec($options, function ($tx) {
    +            $tx->watch('foobar');
    +            $foo = $tx->get('foo');
    +
    +            $tx->multi();
    +            $tx->set('foobar', $foo);
    +            $tx->discard();
    +            $tx->mget('foo', 'foobar');
    +        });
    +
    +        $this->assertInternalType('array', $replies);
    +        $this->assertSame(array(array('bar', null)), $replies);
    +
    +        $hijack = true;
    +        $client2 = $this->getClient();
    +        $client->set('foo', 'bar');
    +
    +        $options = array('watch' => 'foo', 'cas' => true, 'retry' => 1);
    +        $replies = $client->multiExec($options, function ($tx) use ($client2, &$hijack) {
    +            $foo = $tx->get('foo');
    +            $tx->multi();
    +
    +            $tx->set('foobar', $foo);
    +            $tx->discard();
    +
    +            if ($hijack) {
    +                $hijack = false;
    +                $client2->set('foo', 'hijacked!');
    +            }
    +
    +            $tx->mget('foo', 'foobar');
    +        });
    +
    +        $this->assertInternalType('array', $replies);
    +        $this->assertSame(array(array('hijacked!', null)), $replies);
    +    }
    +
    +    // ******************************************************************** //
    +    // ---- HELPER METHODS ------------------------------------------------ //
    +    // ******************************************************************** //
    +
    +    /**
    +     * Returns a mocked instance of Predis\Connection\SingleConnectionInterface
    +     * usingthe specified callback to return values from executeCommand().
    +     *
    +     * @param \Closure $executeCallback
    +     * @return \Predis\Connection\SingleConnectionInterface
    +     */
    +    protected function getMockedConnection($executeCallback)
    +    {
    +        $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
    +        $connection->expects($this->any())
    +                   ->method('executeCommand')
    +                   ->will($this->returnCallback($executeCallback));
    +
    +        return $connection;
    +    }
    +
    +    /**
    +     * Returns a mocked instance of Predis\Transaction\MultiExecContext using
    +     * the specified callback to return values from the executeCommand method
    +     * of the underlying connection.
    +     *
    +     * @param \Closure $executeCallback
    +     * @return MultiExecContext
    +     */
    +    protected function getMockedTransaction($executeCallback, $options = array())
    +    {
    +        $connection = $this->getMockedConnection($executeCallback);
    +        $client = new Client($connection);
    +        $transaction = new MultiExecContext($client, $options);
    +
    +        return $transaction;
    +    }
    +
    +    /**
    +     * Returns a callback that emulates a server-side MULTI/EXEC transaction context.
    +     *
    +     * @param array $expected Expected replies.
    +     * @param array $commands Reference to an array that stores the whole flow of commands.
    +     * @return \Closure
    +     */
    +    protected function getExecuteCallback($expected = array(), &$commands = array(), &$cas = array())
    +    {
    +        $multi = $watch = $abort = false;
    +
    +        return function (CommandInterface $command) use (&$expected, &$commands, &$cas, &$multi, &$watch, &$abort) {
    +            $cmd = $command->getId();
    +
    +            if ($multi || $cmd === 'MULTI') {
    +                $commands[] = $command;
    +            } else {
    +                $cas[] = $command;
    +            }
    +
    +            switch ($cmd) {
    +                case 'WATCH':
    +                    if ($multi) {
    +                        throw new ServerException("ERR $cmd inside MULTI is not allowed");
    +                    }
    +
    +                    return $watch = true;
    +
    +                case 'MULTI':
    +                    if ($multi) {
    +                        throw new ServerException("ERR MULTI calls can not be nested");
    +                    }
    +
    +                    return $multi = true;
    +
    +                case 'EXEC':
    +                    if (!$multi) {
    +                        throw new ServerException("ERR $cmd without MULTI");
    +                    }
    +
    +                    $watch = $multi = false;
    +
    +                    if ($abort) {
    +                        $commands = $cas = array();
    +                        $abort = false;
    +                        return null;
    +                    }
    +
    +                    return $expected;
    +
    +                case 'DISCARD':
    +                    if (!$multi) {
    +                        throw new ServerException("ERR $cmd without MULTI");
    +                    }
    +
    +                    $watch = $multi = false;
    +
    +                    return true;
    +
    +                case 'ECHO':
    +                    @list($trigger) = $command->getArguments();
    +                    if (strpos($trigger, 'ERR ') === 0) {
    +                        throw new ServerException($trigger);
    +                    }
    +
    +                    if ($trigger === '!!ABORT!!' && $multi) {
    +                        $abort = true;
    +                    }
    +
    +                    return new ResponseQueued();
    +
    +                case 'UNWATCH':
    +                    $watch = false;
    +
    +                default:
    +                    return $multi ? new ResponseQueued() : 'DUMMY_REPLY';
    +            }
    +        };
    +    }
    +
    +    /**
    +     * Converts an array of instances of Predis\Command\CommandInterface and
    +     * returns an array containing their IDs.
    +     *
    +     * @param array $commands List of commands instances.
    +     * @return array
    +     */
    +    protected static function commandsToIDs($commands) {
    +        return array_map(function($cmd) { return $cmd->getId(); }, $commands);
    +    }
    +
    +    /**
    +     * Returns a client instance connected to the specified Redis
    +     * server instance to perform integration tests.
    +     *
    +     * @param array Additional connection parameters.
    +     * @param array Additional client options.
    +     * @return Client client instance.
    +     */
    +    protected function getClient(Array $parameters = array(), Array $options = array())
    +    {
    +        $parameters = array_merge(array(
    +            'scheme' => 'tcp',
    +            'host' => REDIS_SERVER_HOST,
    +            'port' => REDIS_SERVER_PORT,
    +            'database' => REDIS_SERVER_DBNUM,
    +        ), $parameters);
    +
    +        $options = array_merge(array(
    +            'profile' => REDIS_SERVER_VERSION
    +        ), $options);
    +
    +        $client = new Client($parameters, $options);
    +
    +        $client->connect();
    +        $client->flushdb();
    +
    +        return $client;
    +    }
    +}
    diff --git a/vendor/predis/predis/tests/README.md b/vendor/predis/predis/tests/README.md
    new file mode 100644
    index 0000000..604c586
    --- /dev/null
    +++ b/vendor/predis/predis/tests/README.md
    @@ -0,0 +1,88 @@
    +# About testing Predis #
    +
    +__ATTENTION__: Do not ever run this test suite against instances of Redis running
    +in production environments or containing data you are interested in! If you still
    +want to test this software on a production server without hitting the database,
    +please read ahead to undestand how to disable integration tests.
    +
    +Predis ships with a comprehensive test suite that uses __PHPUnit__ to cover every
    +aspect of the library. The suite is organized into several unit groups with the
    +PHPUnit `@group` annotation which makes it possible to run only selected groups
    +of tests. The main groups are:
    +
    +  - __disconnected__: generic tests that verify the correct behaviour of the
    +  	library without requiring an active connection to Redis.
    +  - __connected__: integration tests that require an active connection to Redis
    +  - __commands__: tests for the implementation of Redis commands.
    +  - __slow__: tests that might slow down the execution of the test suite, they
    +    can be either __connected__ or __disconnected__.
    +
    +A list of all the available groups in the suite can be obtained by running:
    +
    +```bash
    +$ phpunit --list-groups
    +```
    +Groups of tests can be disabled or enabled via the XML configuration file or the
    +standard command-line test runner. Please note that due to a bug in PHPUnit,
    +older versions ignore the `--group` option when the group is excluded in the XML
    +configuration file. More details about this issue are available on [PHPUnit's bug
    +tracker](http://github.com/sebastianbergmann/phpunit/issues/320).
    +
    +Certain groups of tests that require native extensions, such as `ext-curl` or
    +`ext-phpiredis`, are excluded by default in the XML configuration file. If you want
    +to test Predis using the phpiredis-based connection or against an instance of Webdis,
    +you should remove those groups of tests from the exclusion list in `phpunit.xml`.
    +
    +### Combining groups for inclusion or exclusion with the command-line runner ###
    +
    +```bash
    +$ phpunit --group disconnected --exclude-group commands,slow
    +```
    +
    +### Integration tests ###
    +
    +The suite performs integration tests against a running instance of Redis (>= 2.4.0
    +is required) to verify the correct behaviour of the implementation of each command
    +and certain abstractions implemented in Predis depending on them. These tests are
    +identified by the __connected__ group.
    +
    +Integration tests for commands that are not defined in the specified server profile
    +(see the value of the `REDIS_SERVER_VERSION` constant in `phpunit.xml`) are marked
    +as __skipped__ automatically.
    +
    +By default, the test suite is configured to execute integration tests using the
    +server profile for Redis v2.4 (which is the current stable version of Redis). You
    +can optionally run the suite against a Redis instance built from the `unstable`
    +branch with the development profile by changing the `REDIS_SERVER_VERSION` to `dev`
    +in the `phpunit.xml` file.
    +
    +If you do not have a Redis instance up and running or available for testing, you
    +can completely disable integration tests by excluding the __connected__ group:
    +
    +```bash
    +$ phpunit --exclude-group connected
    +```
    +
    +### Slow tests ###
    +
    +Certain tests can slow down the execution of the test suite. These tests can be disabled
    +by excluding the __slow__ group:
    +
    +```bash
    +$ phpunit --exclude-group slow
    +```
    +
    +### Testing Redis commands ###
    +
    +We also provide an helper script in the `bin` directory that can be used to automatically
    +generate a file with the scheleton of a test case to test a Redis command by specifying
    +the name of the class in the `Predis\Command` namespace (only classes in this namespace
    +are considered valid). For example, to generate a test case for `SET` (represented by the
    +`Predis\Command\StringSet` class):
    +
    +```bash
    +$ ./bin/generate-command-test.php --class=StringSet
    +```
    +Each command has its own realm (commands that operate on strings, lists, sets and such)
    +and this realm is automatically inferred from the name of the specified class. The realm
    +can be also specified manually using the `--realm` option.
    diff --git a/vendor/predis/predis/tests/bootstrap.php b/vendor/predis/predis/tests/bootstrap.php
    new file mode 100644
    index 0000000..43c600e
    --- /dev/null
    +++ b/vendor/predis/predis/tests/bootstrap.php
    @@ -0,0 +1,18 @@
    +
    + *
    + * For the full copyright and license information, please view the LICENSE
    + * file that was distributed with this source code.
    + */
    +
    +require __DIR__.'/../autoload.php';
    +
    +require __DIR__.'/PHPUnit/ArrayHasSameValuesConstraint.php';
    +require __DIR__.'/PHPUnit/CommandTestCase.php';
    +require __DIR__.'/PHPUnit/ConnectionTestCase.php';
    +require __DIR__.'/PHPUnit/ServerVersionTestCase.php';
    +require __DIR__.'/PHPUnit/DistributionStrategyTestCase.php';
    diff --git a/vendor/psr/log/.gitignore b/vendor/psr/log/.gitignore
    new file mode 100644
    index 0000000..22d0d82
    --- /dev/null
    +++ b/vendor/psr/log/.gitignore
    @@ -0,0 +1 @@
    +vendor
    diff --git a/vendor/psr/log/LICENSE b/vendor/psr/log/LICENSE
    new file mode 100644
    index 0000000..474c952
    --- /dev/null
    +++ b/vendor/psr/log/LICENSE
    @@ -0,0 +1,19 @@
    +Copyright (c) 2012 PHP Framework Interoperability Group
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy 
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights 
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
    +copies of the Software, and to permit persons to whom the Software is 
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in 
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    diff --git a/vendor/psr/log/Psr/Log/AbstractLogger.php b/vendor/psr/log/Psr/Log/AbstractLogger.php
    new file mode 100644
    index 0000000..00f9034
    --- /dev/null
    +++ b/vendor/psr/log/Psr/Log/AbstractLogger.php
    @@ -0,0 +1,120 @@
    +log(LogLevel::EMERGENCY, $message, $context);
    +    }
    +
    +    /**
    +     * Action must be taken immediately.
    +     *
    +     * Example: Entire website down, database unavailable, etc. This should
    +     * trigger the SMS alerts and wake you up.
    +     *
    +     * @param string $message
    +     * @param array $context
    +     * @return null
    +     */
    +    public function alert($message, array $context = array())
    +    {
    +        $this->log(LogLevel::ALERT, $message, $context);
    +    }
    +
    +    /**
    +     * Critical conditions.
    +     *
    +     * Example: Application component unavailable, unexpected exception.
    +     *
    +     * @param string $message
    +     * @param array $context
    +     * @return null
    +     */
    +    public function critical($message, array $context = array())
    +    {
    +        $this->log(LogLevel::CRITICAL, $message, $context);
    +    }
    +
    +    /**
    +     * Runtime errors that do not require immediate action but should typically
    +     * be logged and monitored.
    +     *
    +     * @param string $message
    +     * @param array $context
    +     * @return null
    +     */
    +    public function error($message, array $context = array())
    +    {
    +        $this->log(LogLevel::ERROR, $message, $context);
    +    }
    +
    +    /**
    +     * Exceptional occurrences that are not errors.
    +     *
    +     * Example: Use of deprecated APIs, poor use of an API, undesirable things
    +     * that are not necessarily wrong.
    +     *
    +     * @param string $message
    +     * @param array $context
    +     * @return null
    +     */
    +    public function warning($message, array $context = array())
    +    {
    +        $this->log(LogLevel::WARNING, $message, $context);
    +    }
    +
    +    /**
    +     * Normal but significant events.
    +     *
    +     * @param string $message
    +     * @param array $context
    +     * @return null
    +     */
    +    public function notice($message, array $context = array())
    +    {
    +        $this->log(LogLevel::NOTICE, $message, $context);
    +    }
    +
    +    /**
    +     * Interesting events.
    +     *
    +     * Example: User logs in, SQL logs.
    +     *
    +     * @param string $message
    +     * @param array $context
    +     * @return null
    +     */
    +    public function info($message, array $context = array())
    +    {
    +        $this->log(LogLevel::INFO, $message, $context);
    +    }
    +
    +    /**
    +     * Detailed debug information.
    +     *
    +     * @param string $message
    +     * @param array $context
    +     * @return null
    +     */
    +    public function debug($message, array $context = array())
    +    {
    +        $this->log(LogLevel::DEBUG, $message, $context);
    +    }
    +}
    diff --git a/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/vendor/psr/log/Psr/Log/InvalidArgumentException.php
    new file mode 100644
    index 0000000..67f852d
    --- /dev/null
    +++ b/vendor/psr/log/Psr/Log/InvalidArgumentException.php
    @@ -0,0 +1,7 @@
    +logger = $logger;
    +    }
    +}
    diff --git a/vendor/psr/log/Psr/Log/LoggerInterface.php b/vendor/psr/log/Psr/Log/LoggerInterface.php
    new file mode 100644
    index 0000000..476bb96
    --- /dev/null
    +++ b/vendor/psr/log/Psr/Log/LoggerInterface.php
    @@ -0,0 +1,114 @@
    +log(LogLevel::EMERGENCY, $message, $context);
    +    }
    +
    +    /**
    +     * Action must be taken immediately.
    +     *
    +     * Example: Entire website down, database unavailable, etc. This should
    +     * trigger the SMS alerts and wake you up.
    +     *
    +     * @param string $message
    +     * @param array $context
    +     * @return null
    +     */
    +    public function alert($message, array $context = array())
    +    {
    +        $this->log(LogLevel::ALERT, $message, $context);
    +    }
    +
    +    /**
    +     * Critical conditions.
    +     *
    +     * Example: Application component unavailable, unexpected exception.
    +     *
    +     * @param string $message
    +     * @param array $context
    +     * @return null
    +     */
    +    public function critical($message, array $context = array())
    +    {
    +        $this->log(LogLevel::CRITICAL, $message, $context);
    +    }
    +
    +    /**
    +     * Runtime errors that do not require immediate action but should typically
    +     * be logged and monitored.
    +     *
    +     * @param string $message
    +     * @param array $context
    +     * @return null
    +     */
    +    public function error($message, array $context = array())
    +    {
    +        $this->log(LogLevel::ERROR, $message, $context);
    +    }
    +
    +    /**
    +     * Exceptional occurrences that are not errors.
    +     *
    +     * Example: Use of deprecated APIs, poor use of an API, undesirable things
    +     * that are not necessarily wrong.
    +     *
    +     * @param string $message
    +     * @param array $context
    +     * @return null
    +     */
    +    public function warning($message, array $context = array())
    +    {
    +        $this->log(LogLevel::WARNING, $message, $context);
    +    }
    +
    +    /**
    +     * Normal but significant events.
    +     *
    +     * @param string $message
    +     * @param array $context
    +     * @return null
    +     */
    +    public function notice($message, array $context = array())
    +    {
    +        $this->log(LogLevel::NOTICE, $message, $context);
    +    }
    +
    +    /**
    +     * Interesting events.
    +     *
    +     * Example: User logs in, SQL logs.
    +     *
    +     * @param string $message
    +     * @param array $context
    +     * @return null
    +     */
    +    public function info($message, array $context = array())
    +    {
    +        $this->log(LogLevel::INFO, $message, $context);
    +    }
    +
    +    /**
    +     * Detailed debug information.
    +     *
    +     * @param string $message
    +     * @param array $context
    +     * @return null
    +     */
    +    public function debug($message, array $context = array())
    +    {
    +        $this->log(LogLevel::DEBUG, $message, $context);
    +    }
    +
    +    /**
    +     * Logs with an arbitrary level.
    +     *
    +     * @param mixed $level
    +     * @param string $message
    +     * @param array $context
    +     * @return null
    +     */
    +    abstract public function log($level, $message, array $context = array());
    +}
    diff --git a/vendor/psr/log/Psr/Log/NullLogger.php b/vendor/psr/log/Psr/Log/NullLogger.php
    new file mode 100644
    index 0000000..553a3c5
    --- /dev/null
    +++ b/vendor/psr/log/Psr/Log/NullLogger.php
    @@ -0,0 +1,27 @@
    +logger) { }`
    + * blocks.
    + */
    +class NullLogger extends AbstractLogger
    +{
    +    /**
    +     * Logs with an arbitrary level.
    +     *
    +     * @param mixed $level
    +     * @param string $message
    +     * @param array $context
    +     * @return null
    +     */
    +    public function log($level, $message, array $context = array())
    +    {
    +        // noop
    +    }
    +}
    diff --git a/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php
    new file mode 100644
    index 0000000..a932815
    --- /dev/null
    +++ b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php
    @@ -0,0 +1,116 @@
    + "
    +     *
    +     * Example ->error('Foo') would yield "error Foo"
    +     *
    +     * @return string[]
    +     */
    +    abstract function getLogs();
    +
    +    public function testImplements()
    +    {
    +        $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
    +    }
    +
    +    /**
    +     * @dataProvider provideLevelsAndMessages
    +     */
    +    public function testLogsAtAllLevels($level, $message)
    +    {
    +        $logger = $this->getLogger();
    +        $logger->{$level}($message, array('user' => 'Bob'));
    +        $logger->log($level, $message, array('user' => 'Bob'));
    +
    +        $expected = array(
    +            $level.' message of level '.$level.' with context: Bob',
    +            $level.' message of level '.$level.' with context: Bob',
    +        );
    +        $this->assertEquals($expected, $this->getLogs());
    +    }
    +
    +    public function provideLevelsAndMessages()
    +    {
    +        return array(
    +            LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
    +            LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
    +            LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
    +            LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
    +            LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
    +            LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
    +            LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
    +            LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
    +        );
    +    }
    +
    +    /**
    +     * @expectedException Psr\Log\InvalidArgumentException
    +     */
    +    public function testThrowsOnInvalidLevel()
    +    {
    +        $logger = $this->getLogger();
    +        $logger->log('invalid level', 'Foo');
    +    }
    +
    +    public function testContextReplacement()
    +    {
    +        $logger = $this->getLogger();
    +        $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));
    +
    +        $expected = array('info {Message {nothing} Bob Bar a}');
    +        $this->assertEquals($expected, $this->getLogs());
    +    }
    +
    +    public function testObjectCastToString()
    +    {
    +        $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
    +        $dummy->expects($this->once())
    +            ->method('__toString')
    +            ->will($this->returnValue('DUMMY'));
    +
    +        $this->getLogger()->warning($dummy);
    +    }
    +
    +    public function testContextCanContainAnything()
    +    {
    +        $context = array(
    +            'bool' => true,
    +            'null' => null,
    +            'string' => 'Foo',
    +            'int' => 0,
    +            'float' => 0.5,
    +            'nested' => array('with object' => new DummyTest),
    +            'object' => new \DateTime,
    +            'resource' => fopen('php://memory', 'r'),
    +        );
    +
    +        $this->getLogger()->warning('Crazy context data', $context);
    +    }
    +
    +    public function testContextExceptionKeyCanBeExceptionOrOtherValues()
    +    {
    +        $this->getLogger()->warning('Random message', array('exception' => 'oops'));
    +        $this->getLogger()->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));
    +    }
    +}
    +
    +class DummyTest
    +{
    +}
    \ No newline at end of file
    diff --git a/vendor/psr/log/README.md b/vendor/psr/log/README.md
    new file mode 100644
    index 0000000..574bc1c
    --- /dev/null
    +++ b/vendor/psr/log/README.md
    @@ -0,0 +1,45 @@
    +PSR Log
    +=======
    +
    +This repository holds all interfaces/classes/traits related to
    +[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md).
    +
    +Note that this is not a logger of its own. It is merely an interface that
    +describes a logger. See the specification for more details.
    +
    +Usage
    +-----
    +
    +If you need a logger, you can use the interface like this:
    +
    +```php
    +logger = $logger;
    +    }
    +
    +    public function doSomething()
    +    {
    +        if ($this->logger) {
    +            $this->logger->info('Doing work');
    +        }
    +
    +        // do something useful
    +    }
    +}
    +```
    +
    +You can then pick one of the implementations of the interface to get a logger.
    +
    +If you want to implement the interface, you can require this package and
    +implement `Psr\Log\LoggerInterface` in your code. Please read the
    +[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
    +for details.
    diff --git a/vendor/psr/log/composer.json b/vendor/psr/log/composer.json
    new file mode 100644
    index 0000000..6bdcc21
    --- /dev/null
    +++ b/vendor/psr/log/composer.json
    @@ -0,0 +1,17 @@
    +{
    +    "name": "psr/log",
    +    "description": "Common interface for logging libraries",
    +    "keywords": ["psr", "psr-3", "log"],
    +    "license": "MIT",
    +    "authors": [
    +        {
    +            "name": "PHP-FIG",
    +            "homepage": "http://www.php-fig.org/"
    +        }
    +    ],
    +    "autoload": {
    +        "psr-0": {
    +            "Psr\\Log\\": ""
    +        }
    +    }
    +}
    diff --git a/vendor/swiftmailer/swiftmailer/.gitignore b/vendor/swiftmailer/swiftmailer/.gitignore
    new file mode 100644
    index 0000000..9a23ffe
    --- /dev/null
    +++ b/vendor/swiftmailer/swiftmailer/.gitignore
    @@ -0,0 +1,4 @@
    +/tests/acceptance.conf.php
    +/tests/smoke.conf.php
    +/build/*
    +/vendor/
    diff --git a/vendor/swiftmailer/swiftmailer/CHANGES b/vendor/swiftmailer/swiftmailer/CHANGES
    new file mode 100644
    index 0000000..d0aae27
    --- /dev/null
    +++ b/vendor/swiftmailer/swiftmailer/CHANGES
    @@ -0,0 +1,133 @@
    +Changelog
    +=========
    +
    +5.0.2 (2013-08-30)
    +------------------
    +
    + * handled correct exception type while reading IoBuffer output
    +
    +5.0.1 (2013-06-17)
    +------------------
    +
    + * changed the spool to only start the transport when a mail has to be sent
    + * fixed compatibility with PHP 5.2
    + * fixed LICENSE file
    +
    +5.0.0 (2013-04-30)
    +------------------
    +
    + * changed the license from LGPL to MIT
    +
    +4.3.1 (2013-04-11)
    +------------------
    +
    + * removed usage of the native QP encoder when the charset is not UTF-8
    + * fixed usage of uniqid to avoid collisions
    + * made a performance improvement when tokenizing large headers
    + * fixed usage of the PHP native QP encoder on PHP 5.4.7+
    +
    +4.3.0 (2013-01-08)
    +------------------
    +
    + * made the temporary directory configurable via the TMPDIR env variable
    + * added S/MIME signer and encryption support
    +
    +4.2.2 (2012-10-25)
    +------------------
    +
    + * added the possibility to throttle messages per second in ThrottlerPlugin (mostly for Amazon SES)
    + * switched mime.qpcontentencoder to automatically use the PHP native encoder on PHP 5.4.7+
    + * allowed specifying a whitelist with regular expressions in RedirectingPlugin
    +
    +4.2.1 (2012-07-13)
    +------------------
    +
    + * changed the coding standards to PSR-1/2
    + * fixed issue with autoloading
    + * added NativeQpContentEncoder to enhance performance (for PHP 5.3+)
    +
    +4.2.0 (2012-06-29)
    +------------------
    +
    + * added documentation about how to use the Japanese support introduced in 4.1.8
    + * added a way to override the default configuration in a lazy way
    + * changed the PEAR init script to lazy-load the initialization
    + * fixed a bug when calling Swift_Preferences before anything else (regression introduced in 4.1.8)
    +
    +4.1.8 (2012-06-17)
    +------------------
    +
    + * added Japanese iso-2022-jp support
    + * changed the init script to lazy-load the initialization
    + * fixed docblocks (@id) which caused some problems with libraries parsing the dobclocks
    + * fixed Swift_Mime_Headers_IdentificationHeader::setId() when passed an array of ids
    + * fixed encoding of email addresses in headers
    + * added replacements setter to the Decorator plugin
    +
    +4.1.7 (2012-04-26)
    +------------------
    +
    + * fixed QpEncoder safeMapShareId property
    +
    +4.1.6 (2012-03-23)
    +------------------
    +
    + * reduced the size of serialized Messages
    +
    +4.1.5 (2012-01-04)
    +------------------
    +
    + * enforced Swift_Spool::queueMessage() to return a Boolean
    + * made an optimization to the memory spool: start the transport only when required
    + * prevented stream_socket_client() from generating an error and throw a Swift_TransportException instead
    + * fixed a PHP warning when calling to mail() when safe_mode is off
    + * many doc tweaks
    +
    +4.1.4 (2011-12-16)
    +------------------
    +
    + * added a memory spool (Swift_MemorySpool)
    + * fixed too many opened files when sending emails with attachments
    +
    +4.1.3 (2011-10-27)
    +------------------
    +
    + * added STARTTLS support
    + * added missing @return tags on fluent methods
    + * added a MessageLogger plugin that logs all sent messages
    + * added composer.json
    +
    +4.1.2 (2011-09-13)
    +------------------
    +
    + * fixed wrong detection of magic_quotes_runtime
    + * fixed fatal errors when no To or Subject header has been set
    + * fixed charset on parameter header continuations
    + * added documentation about how to install Swiftmailer from the PEAR channel
    + * fixed various typos and markup problem in the documentation
    + * fixed warning when cache directory does not exist
    + * fixed "slashes are escaped" bug
    + * changed require_once() to require() in autoload
    +
    +4.1.1 (2011-07-04)
    +------------------
    +
    + * added missing file in PEAR package
    +
    +4.1.0 (2011-06-30)
    +------------------
    +
    + * documentation has been converted to ReST
    +
    +4.1.0 RC1 (2011-06-17)
    +----------------------
    +
    +New features:
    +
    + * changed the Decorator Plugin to allow replacements in all headers
    + * added Swift_Mime_Grammar and Swift_Validate to validate an email address
    + * modified the autoloader to lazy-initialize Swiftmailer
    + * removed Swift_Mailer::batchSend()
    + * added NullTransport
    + * added new plugins: RedirectingPlugin and ImpersonatePlugin
    + * added a way to send messages asynchronously (Spool)
    diff --git a/vendor/swiftmailer/swiftmailer/LICENSE b/vendor/swiftmailer/swiftmailer/LICENSE
    new file mode 100644
    index 0000000..674bb2a
    --- /dev/null
    +++ b/vendor/swiftmailer/swiftmailer/LICENSE
    @@ -0,0 +1,19 @@
    +Copyright (c) 2013 Fabien Potencier
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is furnished
    +to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
    diff --git a/vendor/swiftmailer/swiftmailer/README b/vendor/swiftmailer/swiftmailer/README
    new file mode 100644
    index 0000000..eb5f8bc
    --- /dev/null
    +++ b/vendor/swiftmailer/swiftmailer/README
    @@ -0,0 +1,16 @@
    +Swift Mailer
    +------------
    +
    +Swift Mailer is a component based mailing solution for PHP 5.
    +It is released under the MIT license.
    +
    +Homepage:      http://swiftmailer.org
    +Documentation: http://swiftmailer.org/docs
    +Mailing List:  http://groups.google.com/group/swiftmailer
    +Bugs:          https://github.com/swiftmailer/swiftmailer/issues
    +Repository:    https://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.
    diff --git a/vendor/swiftmailer/swiftmailer/README.git b/vendor/swiftmailer/swiftmailer/README.git
    new file mode 100644
    index 0000000..b0b4e91
    --- /dev/null
    +++ b/vendor/swiftmailer/swiftmailer/README.git
    @@ -0,0 +1,67 @@
    +This README applies to anyone who checks out the source from git.
    +
    +If you're reading this page on github.com, and you don't have git
    +installed or know about git, you can download this repository by
    +using the "download" button on github.com, right above the file
    +list.
    +
    +PREAMBLE:
    +---------
    +
    +The git repository is structured in the expected way where "master" is the
    +main branch you should use if you want to have bleeding-edge updates.  Any
    +other branch should be ignored since it will likely contain unstable
    +and/or experimental developments.
    +
    +Generally speaking you should feel safe using the "master" branch in
    +production code.  Anything likely to break will be committed to another
    +branch.  Only bugfixes and clean non-breaking feature additions will be
    +performed in master.
    +
    +All releases (post version 4.0.0) are tagged using the version number of
    +that release.  Earlier versions exist in a subversion repository at the
    +old sourceforge project page.
    +
    +
    +WHAT IS SWIFT MAILER?
    +---------------------
    +
    +Swift Mailer is a component based mailing solution for PHP 5.
    +It is released under the MIT license.
    +
    +Homepage:      http://swiftmailer.org/
    +Documentation: http://swiftmailer.org/docs
    +Mailing List:  http://groups.google.com/group/swiftmailer
    +Bugs:          https://github.com/swiftmailer/swiftmailer/issues
    +Repository:    https://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.
    +
    +
    +WHY SO MUCH CLUTTER?
    +--------------------
    +As you can probably see, there are a lot more files in here than you find in
    +the pre-packaged versions.  That's because I store notes (UML, RFCs etc) in
    +the repository.
    +
    +The main library files live in /lib and the tests live in /tests.  You can run
    +the tests by pointing your web browser at /test-suite, or by running the
    +command "php test-suite/run.php".  Some tests will be "skipped" if
    +tests/smoke.conf.php and tests/acceptance.conf.php are not editted.  This is
    +harmless and normal.
    +
    +If you want to create a bundled-up package from subversion you can do so if
    +you have Ant (http://ant.apache.org/) installed.  Simply run "ant package"
    +from this directory and the tar.gz file will be created in the /build
    +directory.
    +
    +Running the command "ant" with no arguments will bundle up the package without
    +compressing it into a tar.gz file.
    +
    +Tests can also be run using "ant test" provided php is on your PATH
    +environment variable.
    +
    +EoM
    diff --git a/vendor/swiftmailer/swiftmailer/VERSION b/vendor/swiftmailer/swiftmailer/VERSION
    new file mode 100644
    index 0000000..369d5f1
    --- /dev/null
    +++ b/vendor/swiftmailer/swiftmailer/VERSION
    @@ -0,0 +1 @@
    +Swift-5.0.2
    diff --git a/vendor/swiftmailer/swiftmailer/build.xml b/vendor/swiftmailer/swiftmailer/build.xml
    new file mode 100644
    index 0000000..c4e234f
    --- /dev/null
    +++ b/vendor/swiftmailer/swiftmailer/build.xml
    @@ -0,0 +1,112 @@
    +
    +
    +  
    +  
    +  
    +  
    +  
    +  
    +  
    +    
    +      
    +    
    +  
    +  
    +  
    +    
    +      
    +      
    +        
    +      
    +    
    +  
    +  
    +  
    +  
    +  
    +  
    +  
    +  
    +  
    +    
    +  
    +  
    +    
    +    
    +    
    +    
    +    
    +  
    +  
    +  
    +  
    +  
    +    
    +    
    +    
    +      
    +    
    +    
    +    
    +    
    +  
    +  
    +  
    +  
    +    
    +      
    +  
    +  
    +  
    +  
    +    
    +      
    +    
    +  
    +  
    +  
    +  
    +    
    +      
    +      
    +        
    +          
    +        
    +      
    +    
    +    
    +      
    +    
    +    
    +      
    +    
    +    
    +      
    +    
    +  
    +  
    +  
    +  
    +    
    +      
    +    
    +    
    +      
    +    
    +  
    +  
    +  
    +  
    +    
    +      
    +      
    +      
    +    
    +  
    +  
    +
    diff --git a/vendor/swiftmailer/swiftmailer/composer.json b/vendor/swiftmailer/swiftmailer/composer.json
    new file mode 100644
    index 0000000..408f1b5
    --- /dev/null
    +++ b/vendor/swiftmailer/swiftmailer/composer.json
    @@ -0,0 +1,28 @@
    +{
    +    "name": "swiftmailer/swiftmailer",
    +    "type": "library",
    +    "description": "Swiftmailer, free feature-rich PHP mailer",
    +    "keywords": ["mail","mailer"],
    +    "homepage": "http://swiftmailer.org",
    +    "license": "MIT",
    +    "authors": [
    +        {
    +            "name": "Chris Corbyn"
    +        },
    +        {
    +            "name": "Fabien Potencier",
    +            "email": "fabien@symfony.com"
    +        }
    +    ],
    +    "require": {
    +        "php": ">=5.2.4"
    +    },
    +    "autoload": {
    +        "files": ["lib/swift_required.php"]
    +    },
    +    "extra": {
    +        "branch-alias": {
    +            "dev-master": "5.1-dev"
    +        }
    +    }
    +}
    diff --git a/vendor/swiftmailer/swiftmailer/create_pear_package.php b/vendor/swiftmailer/swiftmailer/create_pear_package.php
    new file mode 100644
    index 0000000..521d650
    --- /dev/null
    +++ b/vendor/swiftmailer/swiftmailer/create_pear_package.php
    @@ -0,0 +1,42 @@
    + date('Y-m-d'),
    +  'time'          => date('H:m:00'),
    +  'version'       => $argv[1],
    +  'api_version'   => $argv[1],
    +  'stability'     => $argv[2],
    +  'api_stability' => $argv[2],
    +);
    +
    +$context['files'] = '';
    +$path = realpath(dirname(__FILE__).'/lib/classes/Swift');
    +foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::LEAVES_ONLY) as $file)
    +{
    +  if (preg_match('/\.php$/', $file))
    +  {
    +    $name = str_replace($path.'/', '', $file);
    +    $context['files'] .= '        '."\n";
    +  }
    +}
    +
    +$template = file_get_contents(dirname(__FILE__).'/package.xml.tpl');
    +$content = preg_replace_callback('/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/', 'replace_parameters', $template);
    +file_put_contents(dirname(__FILE__).'/package.xml', $content);
    +
    +function replace_parameters($matches)
    +{
    +  global $context;
    +
    +  return isset($context[$matches[1]]) ? $context[$matches[1]] : null;
    +}
    diff --git a/vendor/swiftmailer/swiftmailer/doc/headers.rst b/vendor/swiftmailer/swiftmailer/doc/headers.rst
    new file mode 100644
    index 0000000..5a7b539
    --- /dev/null
    +++ b/vendor/swiftmailer/swiftmailer/doc/headers.rst
    @@ -0,0 +1,742 @@
    +Message Headers
    +===============
    +
    +Sometimes you'll want to add your own headers to a message or modify/remove
    +headers that are already present. You work with the message's HeaderSet to do
    +this.
    +
    +Header Basics
    +-------------
    +
    +All MIME entities in Swift Mailer -- including the message itself --
    +store their headers in a single object called a HeaderSet. This HeaderSet is
    +retrieved with the ``getHeaders()`` method.
    +
    +As mentioned in the previous chapter, everything that forms a part of a message
    +in Swift Mailer is a MIME entity that is represented by an instance of
    +``Swift_Mime_MimeEntity``. This includes -- most notably -- the message object
    +itself, attachments, MIME parts and embedded images. Each of these MIME entities
    +consists of a body and a set of headers that describe the body.
    +
    +For all of the "standard" headers in these MIME entities, such as the
    +``Content-Type``, there are named methods for working with them, such as 
    +``setContentType()`` and ``getContentType()``. This is because headers are a
    +moderately complex area of the library. Each header has a slightly different
    +required structure that it must meet in order to comply with the standards that
    +govern email (and that are checked by spam blockers etc).
    +
    +You fetch the HeaderSet from a MIME entity like so:
    +
    +.. code-block:: php
    +
    +    $message = Swift_Message::newInstance();
    +
    +    // Fetch the HeaderSet from a Message object
    +    $headers = $message->getHeaders();
    +
    +    $attachment = Swift_Attachment::fromPath('document.pdf');
    +
    +    // Fetch the HeaderSet from an attachment object
    +    $headers = $attachment->getHeaders();
    +
    +The job of the HeaderSet is to contain and manage instances of Header objects.
    +Depending upon the MIME entity the HeaderSet came from, the contents of the
    +HeaderSet will be different, since an attachment for example has a different
    +set of headers to those in a message.
    +
    +You can find out what the HeaderSet contains with a quick loop, dumping out
    +the names of the headers:
    +
    +.. code-block:: php
    +
    +    foreach ($headers->getAll() as $header) {
    +      printf("%s
    \n", $header->getFieldName()); + } + + /* + Content-Transfer-Encoding + Content-Type + MIME-Version + Date + Message-ID + From + Subject + To + */ + +You can also dump out the rendered HeaderSet by calling its ``toString()`` +method: + +.. code-block:: php + + echo $headers->toString(); + + /* + Message-ID: <1234869991.499a9ee7f1d5e@swift.generated> + Date: Tue, 17 Feb 2009 22:26:31 +1100 + Subject: Awesome subject! + From: sender@example.org + To: recipient@example.org + MIME-Version: 1.0 + Content-Type: text/plain; charset=utf-8 + Content-Transfer-Encoding: quoted-printable + */ + +Where the complexity comes in is when you want to modify an existing header. +This complexity comes from the fact that each header can be of a slightly +different type (such as a Date header, or a header that contains email +addresses, or a header that has key-value parameters on it!). Each header in the +HeaderSet is an instance of ``Swift_Mime_Header``. They all have common +functionality, but knowing exactly what type of header you're working with will +allow you a little more control. + +You can determine the type of header by comparing the return value of its +``getFieldType()`` method with the constants ``TYPE_TEXT``, +``TYPE_PARAMETERIZED``, ``TYPE_DATE``, ``TYPE_MAILBOX``, ``TYPE_ID`` and +``TYPE_PATH`` which are defined in ``Swift_Mime_Header``. + + +.. code-block:: php + + foreach ($headers->getAll() as $header) { + switch ($header->getFieldType()) { + case Swift_Mime_Header::TYPE_TEXT: $type = 'text'; + break; + case Swift_Mime_Header::TYPE_PARAMETERIZED: $type = 'parameterized'; + break; + case Swift_Mime_Header::TYPE_MAILBOX: $type = 'mailbox'; + break; + case Swift_Mime_Header::TYPE_DATE: $type = 'date'; + break; + case Swift_Mime_Header::TYPE_ID: $type = 'ID'; + break; + case Swift_Mime_Header::TYPE_PATH: $type = 'path'; + break; + } + printf("%s: is a %s header
    \n", $header->getFieldName(), $type); + } + + /* + Content-Transfer-Encoding: is a text header + Content-Type: is a parameterized header + MIME-Version: is a text header + Date: is a date header + Message-ID: is a ID header + From: is a mailbox header + Subject: is a text header + To: is a mailbox header + */ + +Headers can be removed from the set, modified within the set, or added to the +set. + +The following sections show you how to work with the HeaderSet and explain the +details of each implementation of ``Swift_Mime_Header`` that may +exist within the HeaderSet. + +Header Types +------------ + +Because all headers are modeled on different data (dates, addresses, text!) +there are different types of Header in Swift Mailer. Swift Mailer attempts to +categorize all possible MIME headers into more general groups, defined by a +small number of classes. + +Text Headers +~~~~~~~~~~~~ + +Text headers are the simplest type of Header. They contain textual information +with no special information included within it -- for example the Subject +header in a message. + +There's nothing particularly interesting about a text header, though it is +probably the one you'd opt to use if you need to add a custom header to a +message. It represents text just like you'd think it does. If the text +contains characters that are not permitted in a message header (such as new +lines, or non-ascii characters) then the header takes care of encoding the +text so that it can be used. + +No header -- including text headers -- in Swift Mailer is vulnerable to +header-injection attacks. Swift Mailer breaks any attempt at header injection by +encoding the dangerous data into a non-dangerous form. + +It's easy to add a new text header to a HeaderSet. You do this by calling the +HeaderSet's ``addTextHeader()`` method. + +.. code-block:: php + + $message = Swift_Message::newInstance(); + + $headers = $message->getHeaders(); + + $headers->addTextHeader('Your-Header-Name', 'the header value'); + +Changing the value of an existing text header is done by calling it's +``setValue()`` method. + +.. code-block:: php + + $subject = $message->getHeaders()->get('Subject'); + + $subject->setValue('new subject'); + +When output via ``toString()``, a text header produces something like the +following: + +.. code-block:: php + + $subject = $message->getHeaders()->get('Subject'); + + $subject->setValue('amazing subject line'); + + echo $subject->toString(); + + /* + + Subject: amazing subject line + + */ + +If the header contains any characters that are outside of the US-ASCII range +however, they will be encoded. This is nothing to be concerned about since +mail clients will decode them back. + +.. code-block:: php + + $subject = $message->getHeaders()->get('Subject'); + + $subject->setValue('contains – dash'); + + echo $subject->toString(); + + /* + + Subject: contains =?utf-8?Q?=E2=80=93?= dash + + */ + +Parameterized Headers +~~~~~~~~~~~~~~~~~~~~~ + +Parameterized headers are text headers that contain key-value parameters +following the textual content. The Content-Type header of a message is a +parameterized header since it contains charset information after the content +type. + +The parameterized header type is a special type of text header. It extends the +text header by allowing additional information to follow it. All of the methods +from text headers are available in addition to the methods described here. + +Adding a parameterized header to a HeaderSet is done by using the +``addParameterizedHeader()`` method which takes a text value like +``addTextHeader()`` but it also accepts an associative array of +key-value parameters. + +.. code-block:: php + + $message = Swift_Message::newInstance(); + + $headers = $message->getHeaders(); + + $headers->addParameterizedHeader( + 'Header-Name', 'header value', + array('foo' => 'bar') + ); + +To change the text value of the header, call it's ``setValue()`` method just as +you do with text headers. + +To change the parameters in the header, call the header's ``setParameters()`` +method or the ``setParameter()`` method (note the pluralization). + +.. code-block:: php + + $type = $message->getHeaders()->get('Content-Type'); + + // setParameters() takes an associative array + $type->setParameters(array( + 'name' => 'file.txt', + 'charset' => 'iso-8859-1' + )); + + // setParameter() takes two args for $key and $value + $type->setParameter('charset', 'iso-8859-1'); + +When output via ``toString()``, a parameterized header produces something like +the following: + +.. code-block:: php + + $type = $message->getHeaders()->get('Content-Type'); + + $type->setValue('text/html'); + $type->setParameter('charset', 'utf-8'); + + echo $type->toString(); + + /* + + Content-Type: text/html; charset=utf-8 + + */ + +If the header contains any characters that are outside of the US-ASCII range +however, they will be encoded, just like they are for text headers. This is +nothing to be concerned about since mail clients will decode them back. +Likewise, if the parameters contain any non-ascii characters they will be +encoded so that they can be transmitted safely. + +.. code-block:: php + + $attachment = Swift_Attachment::newInstance(); + + $disp = $attachment->getHeaders()->get('Content-Disposition'); + + $disp->setValue('attachment'); + $disp->setParameter('filename', 'report–may.pdf'); + + echo $disp->toString(); + + /* + + Content-Disposition: attachment; filename*=utf-8''report%E2%80%93may.pdf + + */ + +Date Headers +~~~~~~~~~~~~ + +Date headers contains an RFC 2822 formatted date (i.e. what PHP's ``date('r')`` +returns). They are used anywhere a date or time is needed to be presented as a +message header. + +The data on which a date header is modeled is simply a UNIX timestamp such as +that returned by ``time()`` or ``strtotime()``. The timestamp is used to create +a correctly structured RFC 2822 formatted date such as +``Tue, 17 Feb 2009 22:26:31 +1100``. + +The obvious place this header type is used is in the ``Date:`` header of the +message itself. + +It's easy to add a new date header to a HeaderSet. You do this by calling +the HeaderSet's ``addDateHeader()`` method. + +.. code-block:: php + + $message = Swift_Message::newInstance(); + + $headers = $message->getHeaders(); + + $headers->addDateHeader('Your-Header-Name', strtotime('3 days ago')); + +Changing the value of an existing date header is done by calling it's +``setTimestamp()`` method. + +.. code-block:: php + + $date = $message->getHeaders()->get('Date'); + + $date->setTimestamp(time()); + +When output via ``toString()``, a date header produces something like the +following: + +.. code-block:: php + + $date = $message->getHeaders()->get('Date'); + + echo $date->toString(); + + /* + + Date: Wed, 18 Feb 2009 13:35:02 +1100 + + */ + +Mailbox (e-mail address) Headers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Mailbox headers contain one or more email addresses, possibly with +personalized names attached to them. The data on which they are modeled is +represented by an associative array of email addresses and names. + +Mailbox headers are probably the most complex header type to understand in +Swift Mailer because they accept their input as an array which can take various +forms, as described in the previous chapter. + +All of the headers that contain e-mail addresses in a message -- with the +exception of ``Return-Path:`` which has a stricter syntax -- use this header +type. That is, ``To:``, ``From:`` etc. + +You add a new mailbox header to a HeaderSet by calling the HeaderSet's +``addMailboxHeader()`` method. + +.. code-block:: php + + $message = Swift_Message::newInstance(); + + $headers = $message->getHeaders(); + + $headers->addMailboxHeader('Your-Header-Name', array( + 'person1@example.org' => 'Person Name One', + 'person2@example.org', + 'person3@example.org', + 'person4@example.org' => 'Another named person' + )); + +Changing the value of an existing mailbox header is done by calling it's +``setNameAddresses()`` method. + +.. code-block:: php + + $to = $message->getHeaders()->get('To'); + + $to->setNameAddresses(array( + 'joe@example.org' => 'Joe Bloggs', + 'john@example.org' => 'John Doe', + 'no-name@example.org' + )); + +If you don't wish to concern yourself with the complicated accepted input +formats accepted by ``setNameAddresses()`` as described in the previous chapter +and you only want to set one or more addresses (not names) then you can just +use the ``setAddresses()`` method instead. + +.. code-block:: php + + $to = $message->getHeaders()->get('To'); + + $to->setAddresses(array( + 'joe@example.org', + 'john@example.org', + 'no-name@example.org' + )); + +.. note:: + + Both methods will accept the above input format in practice. + +If all you want to do is set a single address in the header, you can use a +string as the input parameter to ``setAddresses()`` and/or +``setNameAddresses()``. + +.. code-block:: php + + $to = $message->getHeaders()->get('To'); + + $to->setAddresses('joe-bloggs@example.org'); + +When output via ``toString()``, a mailbox header produces something like the +following: + +.. code-block:: php + + $to = $message->getHeaders()->get('To'); + + $to->setNameAddresses(array( + 'person1@example.org' => 'Name of Person', + 'person2@example.org', + 'person3@example.org' => 'Another Person' + )); + + echo $to->toString(); + + /* + + To: Name of Person , person2@example.org, Another Person + + + */ + +ID Headers +~~~~~~~~~~ + +ID headers contain identifiers for the entity (or the message). The most +notable ID header is the Message-ID header on the message itself. + +An ID that exists inside an ID header looks more-or-less less like an email +address. For example, ``<1234955437.499becad62ec2@example.org>``. +The part to the left of the @ sign is usually unique, based on the current time +and some random factor. The part on the right is usually a domain name. + +Any ID passed to the header's ``setId()`` method absolutely MUST conform to +this structure, otherwise you'll get an Exception thrown at you by Swift Mailer +(a ``Swift_RfcComplianceException``). This is to ensure that the generated +email complies with relevant RFC documents and therefore is less likely to be +blocked as spam. + +It's easy to add a new ID header to a HeaderSet. You do this by calling +the HeaderSet's ``addIdHeader()`` method. + +.. code-block:: php + + $message = Swift_Message::newInstance(); + + $headers = $message->getHeaders(); + + $headers->addIdHeader('Your-Header-Name', '123456.unqiue@example.org'); + +Changing the value of an existing date header is done by calling its +``setId()`` method. + +.. code-block:: php + + $msgId = $message->getHeaders()->get('Message-ID'); + + $msgId->setId(time() . '.' . uniqid('thing') . '@example.org'); + +When output via ``toString()``, an ID header produces something like the +following: + +.. code-block:: php + + $msgId = $message->getHeaders()->get('Message-ID'); + + echo $msgId->toString(); + + /* + + Message-ID: <1234955437.499becad62ec2@example.org> + + */ + +Path Headers +~~~~~~~~~~~~ + +Path headers are like very-restricted mailbox headers. They contain a single +email address with no associated name. The Return-Path header of a message is +a path header. + +You add a new path header to a HeaderSet by calling the HeaderSet's +``addPathHeader()`` method. + +.. code-block:: php + + $message = Swift_Message::newInstance(); + + $headers = $message->getHeaders(); + + $headers->addPathHeader('Your-Header-Name', 'person@example.org'); + + +Changing the value of an existing path header is done by calling its +``setAddress()`` method. + +.. code-block:: php + + $return = $message->getHeaders()->get('Return-Path'); + + $return->setAddress('my-address@example.org'); + +When output via ``toString()``, a path header produces something like the +following: + +.. code-block:: php + + $return = $message->getHeaders()->get('Return-Path'); + + $return->setAddress('person@example.org'); + + echo $return->toString(); + + /* + + Return-Path: + + */ + +Header Operations +----------------- + +Working with the headers in a message involves knowing how to use the methods +on the HeaderSet and on the individual Headers within the HeaderSet. + +Adding new Headers +~~~~~~~~~~~~~~~~~~ + +New headers can be added to the HeaderSet by using one of the provided +``add..Header()`` methods. + +To add a header to a MIME entity (such as the message): + +Get the HeaderSet from the entity by via its ``getHeaders()`` method. + +* Add the header to the HeaderSet by calling one of the ``add..Header()`` + methods. + +The added header will appear in the message when it is sent. + +.. code-block:: php + + // Adding a custom header to a message + $message = Swift_Message::newInstance(); + $headers = $message->getHeaders(); + $headers->addTextHeader('X-Mine', 'something here'); + + // Adding a custom header to an attachment + $attachment = Swift_Attachment::fromPath('/path/to/doc.pdf'); + $attachment->getHeaders()->addDateHeader('X-Created-Time', time()); + +Retrieving Headers +~~~~~~~~~~~~~~~~~~ + +Headers are retrieved through the HeaderSet's ``get()`` and ``getAll()`` +methods. + +To get a header, or several headers from a MIME entity: + +* Get the HeaderSet from the entity by via its ``getHeaders()`` method. + +* Get the header(s) from the HeaderSet by calling either ``get()`` or + ``getAll()``. + +When using ``get()`` a single header is returned that matches the name (case +insensitive) that is passed to it. When using ``getAll()`` with a header name, +an array of headers with that name are returned. Calling ``getAll()`` with no +arguments returns an array of all headers present in the entity. + +.. note:: + + It's valid for some headers to appear more than once in a message (e.g. + the Received header). For this reason ``getAll()`` exists to fetch all + headers with a specified name. In addition, ``get()`` accepts an optional + numerical index, starting from zero to specify which header you want more + specifically. + +.. note:: + + If you want to modify the contents of the header and you don't know for + sure what type of header it is then you may need to check the type by + calling its ``getFieldType()`` method. + + .. code-block:: php + + $headers = $message->getHeaders(); + + // Get the To: header + $toHeader = $headers->get('To'); + + // Get all headers named "X-Foo" + $fooHeaders = $headers->getAll('X-Foo'); + + // Get the second header named "X-Foo" + $foo = $headers->get('X-Foo', 1); + + // Get all headers that are present + $all = $headers->getAll(); + +Check if a Header Exists +~~~~~~~~~~~~~~~~~~~~~~~~ + +You can check if a named header is present in a HeaderSet by calling its +``has()`` method. + +To check if a header exists: + +* Get the HeaderSet from the entity by via its ``getHeaders()`` method. + +* Call the HeaderSet's ``has()`` method specifying the header you're looking + for. + +If the header exists, ``true`` will be returned or ``false`` if not. + +.. note:: + + It's valid for some headers to appear more than once in a message (e.g. + the Received header). For this reason ``has()`` accepts an optional + numerical index, starting from zero to specify which header you want to + check more specifically. + + .. code-block:: php + + $headers = $message->getHeaders(); + + // Check if the To: header exists + if ($headers->has('To')) { + echo 'To: exists'; + } + + // Check if an X-Foo header exists twice (i.e. check for the 2nd one) + if ($headers->has('X-Foo', 1)) { + echo 'Second X-Foo header exists'; + } + +Removing Headers +~~~~~~~~~~~~~~~~ + +Removing a Header from the HeaderSet is done by calling the HeaderSet's +``remove()`` or ``removeAll()`` methods. + +To remove an existing header: + +* Get the HeaderSet from the entity by via its ``getHeaders()`` method. + +* Call the HeaderSet's ``remove()`` or ``removeAll()`` methods specifying the + header you want to remove. + +When calling ``remove()`` a single header will be removed. When calling +``removeAll()`` all headers with the given name will be removed. If no headers +exist with the given name, no errors will occur. + +.. note:: + + It's valid for some headers to appear more than once in a message (e.g. + the Received header). For this reason ``remove()`` accepts an optional + numerical index, starting from zero to specify which header you want to + check more specifically. For the same reason, ``removeAll()`` exists to + remove all headers that have the given name. + + .. code-block:: php + + $headers = $message->getHeaders(); + + // Remove the Subject: header + $headers->remove('Subject'); + + // Remove all X-Foo headers + $headers->removeAll('X-Foo'); + + // Remove only the second X-Foo header + $headers->remove('X-Foo', 1); + +Modifying a Header's Content +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To change a Header's content you should know what type of header it is and then +call it's appropriate setter method. All headers also have a +``setFieldBodyModel()`` method that accepts a mixed parameter and delegates to +the correct setter. + +To modify an existing header: + +* Get the HeaderSet from the entity by via its ``getHeaders()`` method. + +* Get the Header by using the HeaderSet's ``get()``. + +* Call the Header's appropriate setter method or call the header's + ``setFieldBodyModel()`` method. + +The header will be updated inside the HeaderSet and the changes will be seen +when the message is sent. + +.. code-block:: php + + $headers = $message->getHeaders(); + + // Change the Subject: header + $subj = $headers->get('Subject'); + $subj->setValue('new subject here'); + + // Change the To: header + $to = $headers->get('To'); + $to->setNameAddresses(array( + 'person@example.org' => 'Person', + 'thing@example.org' + )); + + // Using the setFieldBodyModel() just delegates to the correct method + // So here to calls setNameAddresses() + $to->setFieldBodyModel(array( + 'person@example.org' => 'Person', + 'thing@example.org' + )); diff --git a/vendor/swiftmailer/swiftmailer/doc/help-resources.rst b/vendor/swiftmailer/swiftmailer/doc/help-resources.rst new file mode 100644 index 0000000..9820653 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/doc/help-resources.rst @@ -0,0 +1,44 @@ +Getting Help +============ + +There are a number of ways you can get help when using Swift Mailer, depending +upon the nature of your problem. For bug reports and feature requests create a +new ticket in Github. For general advice ask on the Google Group +(swiftmailer). + +Submitting Bugs & Feature Requests +---------------------------------- + +Bugs and feature requests should be posted on Github. + +If you post a bug or request a feature in the forum, or on the Google Group +you will most likely be asked to create a ticket in `Github`_ since it is +the simply not feasible to manage such requests from a number of a different +sources. + +When you go to Github you will be asked to create a username and password +before you can create a ticket. This is free and takes very little time. + +When you create your ticket, do not assign it to any milestones. A developer +will assess your ticket and re-assign it as needed. + +If your ticket is reporting a bug present in the current version, which was +not present in the previous version please include the tag "regression" in +your ticket. + +Github will update you when work is performed on your ticket. + +Ask on the Google Group +----------------------- + +You can seek advice at Google Groups, within the "swiftmailer" `group`_. + +You can post messages to this group if you want help, or there's something you +wish to discuss with the developers and with other users. + +This is probably the fastest way to get help since it is primarily email-based +for most users, though bug reports should not be posted here since they may +not be resolved. + +.. _`Github`: https://github.com/swiftmailer/swiftmailer/issues +.. _`group`: http://groups.google.com/group/swiftmailer diff --git a/vendor/swiftmailer/swiftmailer/doc/including-the-files.rst b/vendor/swiftmailer/swiftmailer/doc/including-the-files.rst new file mode 100644 index 0000000..978dca2 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/doc/including-the-files.rst @@ -0,0 +1,46 @@ +Including Swift Mailer (Autoloading) +==================================== + +If you are using Composer, Swift Mailer will be automatically autoloaded. + +If not, you can use the built-in autoloader by requiring the +``swift_required.php`` file:: + + require_once '/path/to/swift-mailer/lib/swift_required.php'; + + /* rest of code goes here */ + +If you want to override the default Swift Mailer configuration, call the +``init()`` method on the ``Swift`` class and pass it a valid PHP callable (a +PHP function name, a PHP 5.3 anonymous function, ...):: + + require_once '/path/to/swift-mailer/lib/swift_required.php'; + + function swiftmailer_configurator() { + // configure Swift Mailer + + Swift_DependencyContainer::getInstance()->... + Swift_Preferences::getInstance()->... + } + + Swift::init('swiftmailer_configurator'); + + /* rest of code goes here */ + +The advantage of using the ``init()`` method is that your code will be +executed only if you use Swift Mailer in your script. + +.. note:: + + While Swift Mailer's autoloader is designed to play nicely with other + autoloaders, sometimes you may have a need to avoid using Swift Mailer's + autoloader and use your own instead. Include the ``swift_init.php`` + instead of the ``swift_required.php`` if you need to do this. The very + minimum include is the ``swift_init.php`` file since Swift Mailer will not + work without the dependency injection this file sets up: + + .. code-block:: php + + require_once '/path/to/swift-mailer/lib/swift_init.php'; + + /* rest of code goes here */ diff --git a/vendor/swiftmailer/swiftmailer/doc/index.rst b/vendor/swiftmailer/swiftmailer/doc/index.rst new file mode 100644 index 0000000..a1a0a92 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/doc/index.rst @@ -0,0 +1,16 @@ +Swiftmailer +=========== + +.. toctree:: + :maxdepth: 2 + + introduction + overview + installing + help-resources + including-the-files + messages + headers + sending + plugins + japanese diff --git a/vendor/swiftmailer/swiftmailer/doc/installing.rst b/vendor/swiftmailer/swiftmailer/doc/installing.rst new file mode 100644 index 0000000..80bdb42 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/doc/installing.rst @@ -0,0 +1,200 @@ +Installing the Library +====================== + +Installing Swift Mailer is trivial. Usually it's just a case of uploading the +extracted source files to your web server. + +Installing from PEAR +-------------------- + +If you want to install Swift Mailer globally on your machine, the easiest +installation method is using the PEAR channel. + +To install the Swift Mailer PEAR package: + +* Run the command ``pear channel-discover pear.swiftmailer.org``. + +* Then, run the command ``pear install swift/swift``. + +Installing from a Package +------------------------- + +Most users will download a package from the Swift Mailer website and install +Swift Mailer using this. + +If you downloaded Swift Mailer as a ``.tar.gz`` or +``.zip`` file installation is as simple as extracting the archive +and uploading it to your web server. + +Extracting the Library +~~~~~~~~~~~~~~~~~~~~~~ + +You extract the archive by using your favorite unarchiving tool such as +``tar`` or 7-Zip. + +You will need to have access to a program that can open uncompress the +archive. On Windows computers, 7-Zip will work. On Mac and Linux systems you +can use ``tar`` on the command line. + +To extract your downloaded package: + +* Use the "extract" facility of your archiving software. + +The source code will be placed into a directory with the same name as the +archive (e.g. Swift-4.0.0-b1). + +The following example shows the process on Mac OS X and Linux systems using +the ``tar`` command. + +.. code-block:: bash + + $ ls + Swift-4.0.0-dev.tar.gz + $ tar xvzf Swift-4.0.0-dev.tar.gz + Swift-4.0.0-dev/ + Swift-4.0.0-dev/lib/ + Swift-4.0.0-dev/lib/classes/ + Swift-4.0.0-dev/lib/classes/Swift/ + Swift-4.0.0-dev/lib/classes/Swift/ByteStream/ + Swift-4.0.0-dev/lib/classes/Swift/CharacterReader/ + Swift-4.0.0-dev/lib/classes/Swift/CharacterReaderFactory/ + Swift-4.0.0-dev/lib/classes/Swift/CharacterStream/ + Swift-4.0.0-dev/lib/classes/Swift/Encoder/ + + ... etc etc ... + + Swift-4.0.0-dev/tests/unit/Swift/Transport/LoadBalancedTransportTest.php + Swift-4.0.0-dev/tests/unit/Swift/Transport/SendmailTransportTest.php + Swift-4.0.0-dev/tests/unit/Swift/Transport/StreamBufferTest.php + $ cd Swift-4.0.0-dev + $ ls + CHANGES LICENSE ... + $ + +Installing from Git +------------------- + +It's possible to download and install Swift Mailer directly from github.com if +you want to keep up-to-date with ease. + +Swift Mailer's source code is kept in a git repository at github.com so you +can get the source directly from the repository. + +.. note:: + + You do not need to have git installed to use Swift Mailer from github. If + you don't have git installed, go to `github`_ and click the "Download" + button. + +Cloning the Repository +~~~~~~~~~~~~~~~~~~~~~~ + +The repository can be cloned from git://github.com/swiftmailer/swiftmailer.git +using the ``git clone`` command. + +You will need to have ``git`` installed before you can use the +``git clone`` command. + +To clone the repository: + +* Open your favorite terminal environment (command line). + +* Move to the directory you want to clone to. + +* Run the command ``git clone git://github.com/swiftmailer/swiftmailer.git + swiftmailer``. + +The source code will be downloaded into a directory called "swiftmailer". + +The example shows the process on a UNIX-like system such as Linux, BSD or Mac +OS X. + +.. code-block:: bash + + $ cd source_code/ + $ git clone git://github.com/swiftmailer/swiftmailer.git swiftmailer + Initialized empty Git repository in /Users/chris/source_code/swiftmailer/.git/ + remote: Counting objects: 6815, done. + remote: Compressing objects: 100% (2761/2761), done. + remote: Total 6815 (delta 3641), reused 6326 (delta 3286) + Receiving objects: 100% (6815/6815), 4.35 MiB | 162 KiB/s, done. + Resolving deltas: 100% (3641/3641), done. + Checking out files: 100% (1847/1847), done. + $ cd swiftmailer/ + $ ls + CHANGES LICENSE ... + $ + +Uploading to your Host +---------------------- + +You only need to upload the "lib/" directory to your web host for production +use. All other files and directories are support files not needed in +production. + +You will need FTP, ``rsync`` or similar software installed in order to upload +the "lib/" directory to your web host. + +To upload Swift Mailer: + +* Open your FTP program, or a command line if you prefer rsync/scp. + +* Upload the "lib/" directory to your hosting account. + +The files needed to use Swift Mailer should now be accessible to PHP on your +host. + +The following example shows show you can upload the files using +``rsync`` on Linux or OS X. + +.. note:: + + You do not need to place the files inside your web root. They only need to + be in a place where your PHP scripts can "include" them. + + .. code-block:: bash + + $ rsync -rvz lib d11wtq@swiftmailer.org:swiftmailer + building file list ... done + created directory swiftmailer + lib/ + lib/mime_types.php + lib/preferences.php + lib/swift_required.php + lib/classes/ + lib/classes/Swift/ + lib/classes/Swift/Attachment.php + lib/classes/Swift/CharacterReader.php + ... etc etc ... + lib/dependency_maps/ + lib/dependency_maps/cache_deps.php + lib/dependency_maps/mime_deps.php + lib/dependency_maps/transport_deps.php + + sent 151692 bytes received 2974 bytes 5836.45 bytes/sec + total size is 401405 speedup is 2.60 + $ + +.. _`github`: http://github.com/swiftmailer/swiftmailer + +Troubleshooting +--------------- + +Swift Mailer does not work when used with function overloading as implemented +by ``mbstring`` (``mbstring.func_overload`` set to ``2``). A workaround is to +temporarily change the internal encoding to ``ASCII`` when sending an email: + +.. code-block:: php + + if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) + { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('ASCII'); + } + + // Create your message and send it with Swift Mailer + + if (isset($mbEncoding)) + { + mb_internal_encoding($mbEncoding); + } diff --git a/vendor/swiftmailer/swiftmailer/doc/introduction.rst b/vendor/swiftmailer/swiftmailer/doc/introduction.rst new file mode 100644 index 0000000..39ab034 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/doc/introduction.rst @@ -0,0 +1,135 @@ +Introduction +============ + +Swift Mailer is a component-based library for sending e-mails from PHP +applications. + +Organization of this Book +------------------------- + +This book has been written so that those who need information quickly are able +to find what they need, and those who wish to learn more advanced topics can +read deeper into each chapter. + +The book begins with an overview of Swift Mailer, discussing what's included +in the package and preparing you for the remainder of the book. + +It is possible to read this user guide just like any other book (from +beginning to end). Each chapter begins with a discussion of the contents it +contains, followed by a short code sample designed to give you a head start. +As you get further into a chapter you will learn more about Swift Mailer's +capabilities, but often you will be able to head directly to the topic you +wish to learn about. + +Throughout this book you will be presented with code samples, which most +people should find ample to implement Swift Mailer appropriately in their own +projects. We will also use diagrams where appropriate, and where we believe +readers may find it helpful we will discuss some related theory, including +reference to certain documents you are able to find online. + +Code Samples +------------ + +Code samples presented in this book will be displayed on a different colored +background in a monospaced font. Samples are not to be taken as copy & paste +code snippets. + +Code examples are used through the book to clarify what is written in text. +They will sometimes be usable as-is, but they should always be taken as +outline/pseudo code only. + +A code sample will look like this:: + + class AClass + { + ... + } + + //A Comment + $obj = new AClass($arg1, $arg2, ... ); + + /* A note about another way of doing something + $obj = AClass::newInstance($arg1, $arg2, ... ); + + */ + +The presence of 3 dots ``...`` in a code sample indicates that we have left +out a chunk of the code for brevity, they are not actually part of the code. + +We will often place multi-line comments ``/* ... */`` in the code so that we +can show alternative ways of achieving the same result. + +You should read the code examples given and try to understand them. They are +kept concise so that you are not overwhelmed with information. + +History of Swift Mailer +----------------------- + +Swift Mailer began back in 2005 as a one-class project for sending mail over +SMTP. It has since grown into the flexible component-based library that is in +development today. + +Chris Corbyn first posted Swift Mailer on a web forum asking for comments from +other developers. It was never intended as a fully supported open source +project, but members of the forum began to adopt it and make use of it. + +Very quickly feature requests were coming for the ability to add attachments +and use SMTP authentication, along with a number of other "obvious" missing +features. Considering the only alternative was PHPMailer it seemed like a good +time to bring some fresh tools to the table. Chris began working towards a +more component based, PHP5-like approach unlike the existing single-class, +legacy PHP4 approach taken by PHPMailer. + +Members of the forum offered a lot of advice and critique on the code as he +worked through this project and released versions 2 and 3 of the library in +2005 and 2006, which by then had been broken down into smaller classes +offering more flexibility and supporting plugins. To this day the Swift Mailer +team still receive a lot of feature requests from users both on the forum and +in by email. + +Until 2008 Chris was the sole developer of Swift Mailer, but entering 2009 he +gained the support of two experienced developers well-known to him: Paul +Annesley and Christopher Thompson. This has been an extremely welcome change. + +As of September 2009, Chris handed over the maintenance of Swift Mailer to +Fabien Potencier. + +Now 2009 and in its fourth major version Swift Mailer is more object-oriented +and flexible than ever, both from a usability standpoint and from a +development standpoint. + +By no means is Swift Mailer ready to call "finished". There are still many +features that can be added to the library along with the constant refactoring +that happens behind the scenes. + +It's a Library! +--------------- + +Swift Mailer is not an application - it's a library. + +To most experienced developers this is probably an obvious point to make, but +it's certainly worth mentioning. Many people often contact us having gotten +the completely wrong end of the stick in terms of what Swift Mailer is +actually for. + +It's not an application. It does not have a graphical user interface. It +cannot be opened in your web browser directly. + +It's a library (or a framework if you like). It provides a whole lot of +classes that do some very complicated things, so that you don't have to. You +"use" Swift Mailer within an application so that your application can have the +ability to send emails. + +The component-based structure of the library means that you are free to +implement it in a number of different ways and that you can pick and choose +what you want to use. + +An application on the other hand (such as a blog or a forum) is already "put +together" in a particular way, (usually) provides a graphical user interface +and most likely doesn't offer a great deal of integration with your own +application. + +Embrace the structure of the library and use the components it offers to your +advantage. Learning what the components do, rather than blindly copying and +pasting existing code will put you in a great position to build a powerful +application! diff --git a/vendor/swiftmailer/swiftmailer/doc/japanese.rst b/vendor/swiftmailer/swiftmailer/doc/japanese.rst new file mode 100644 index 0000000..34afa7b --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/doc/japanese.rst @@ -0,0 +1,22 @@ +Using Swift Mailer for Japanese Emails +====================================== + +To send emails in Japanese, you need to tweak the default configuration. + +After requiring the Swift Mailer autoloader (by including the +``swift_required.php`` file), call the ``Swift::init()`` method with the +following code:: + + require_once '/path/to/swift-mailer/lib/swift_required.php'; + + Swift::init(function () { + Swift_DependencyContainer::getInstance() + ->register('mime.qpheaderencoder') + ->asAliasOf('mime.base64headerencoder'); + + Swift_Preferences::getInstance()->setCharset('iso-2022-jp'); + }); + + /* rest of code goes here */ + +That's all! diff --git a/vendor/swiftmailer/swiftmailer/doc/messages.rst b/vendor/swiftmailer/swiftmailer/doc/messages.rst new file mode 100644 index 0000000..a3f6a9b --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/doc/messages.rst @@ -0,0 +1,1057 @@ +Creating Messages +================= + +Creating messages in Swift Mailer is done by making use of the various MIME +entities provided with the library. Complex messages can be quickly created +with very little effort. + +Quick Reference for Creating a Message +--------------------------------------- + +You can think of creating a Message as being similar to the steps you perform +when you click the Compose button in your mail client. You give it a subject, +specify some recipients, add any attachments and write your message. + +To create a Message: + +* Call the ``newInstance()`` method of ``Swift_Message``. + +* Set your sender address (``From:``) with ``setFrom()`` or ``setSender()``. + +* Set a subject line with ``setSubject()``. + +* Set recipients with ``setTo()``, ``setCc()`` and/or ``setBcc()``. + +* Set a body with ``setBody()``. + +* Add attachments with ``attach()``. + +.. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the message + $message = Swift_Message::newInstance() + + // Give the message a subject + ->setSubject('Your subject') + + // Set the From address with an associative array + ->setFrom(array('john@doe.com' => 'John Doe')) + + // Set the To addresses with an associative array + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + + // Give it a body + ->setBody('Here is the message itself') + + // And optionally an alternative body + ->addPart('Here is the message itself', 'text/html') + + // Optionally add any attachments + ->attach(Swift_Attachment::fromPath('my-document.pdf')) + ; + +Message Basics +-------------- + +A message is a container for anything you want to send to somebody else. There +are several basic aspects of a message that you should know. + +An e-mail message is made up of several relatively simple entities that are +combined in different ways to achieve different results. All of these entities +have the same fundamental outline but serve a different purpose. The Message +itself can be defined as a MIME entity, an Attachment is a MIME entity, all +MIME parts are MIME entities -- and so on! + +The basic units of each MIME entity -- be it the Message itself, or an +Attachment -- are its Headers and its body: + +.. code-block:: text + + Header-Name: A header value + Other-Header: Another value + + The body content itself + +The Headers of a MIME entity, and its body must conform to some strict +standards defined by various RFC documents. Swift Mailer ensures that these +specifications are followed by using various types of object, including +Encoders and different Header types to generate the entity. + +The Structure of a Message +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Of all of the MIME entities, a message -- ``Swift_Message`` +is the largest and most complex. It has many properties that can be updated +and it can contain other MIME entities -- attachments for example -- +nested inside it. + +A Message has a lot of different Headers which are there to present +information about the message to the recipients' mail client. Most of these +headers will be familiar to the majority of users, but we'll list the basic +ones. Although it's possible to work directly with the Headers of a Message +(or other MIME entity), the standard Headers have accessor methods provided to +abstract away the complex details for you. For example, although the Date on a +message is written with a strict format, you only need to pass a UNIX +timestamp to ``setDate()``. + ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| Header | Description | Accessors | ++===============================+====================================================================================================================================+=============================================+ +| ``Message-ID`` | Identifies this message with a unique ID, usually containing the domain name and time generated | ``getId()`` / ``setId()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Return-Path`` | Specifies where bounces should go (Swift Mailer reads this for other uses) | ``getReturnPath()`` / ``setReturnPath()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``From`` | Specifies the address of the person who the message is from. This can be multiple addresses if multiple people wrote the message. | ``getFrom()`` / ``setFrom()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Sender`` | Specifies the address of the person who physically sent the message (higher precedence than ``From:``) | ``getSender()`` / ``setSender()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``To`` | Specifies the addresses of the intended recipients | ``getTo()`` / ``setTo()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Cc`` | Specifies the addresses of recipients who will be copied in on the message | ``getCc()`` / ``setCc()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Bcc`` | Specifies the addresses of recipients who the message will be blind-copied to. Other recipients will not be aware of these copies. | ``getBcc()`` / ``setBcc()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Reply-To`` | Specifies the address where replies are sent to | ``getReplyTo()`` / ``setReplyTo()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Subject`` | Specifies the subject line that is displayed in the recipients' mail client | ``getSubject()`` / ``setSubject()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Date`` | Specifies the date at which the message was sent | ``getDate()`` / ``setDate()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Content-Type`` | Specifies the format of the message (usually text/plain or text/html) | ``getContentType()`` / ``setContentType()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Content-Transfer-Encoding`` | Specifies the encoding scheme in the message | ``getEncoder()`` / ``setEncoder()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ + +Working with a Message Object +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Although there are a lot of available methods on a message object, you only +need to make use of a small subset of them. Usually you'll use +``setSubject()``, ``setTo()`` and +``setFrom()`` before setting the body of your message with +``setBody()``. + +Calling methods is simple. You just call them like functions, but using the +object operator "``->``" to do so. If you've created +a message object and called it ``$message`` then you'd set a +subject on it like so: + +.. code-block:: php + + require_once 'lib/swift_required.php'; + + $message = Swift_Message::newInstance(); + $message->setSubject('My subject'); + +All MIME entities (including a message) have a ``toString()`` +method that you can call if you want to take a look at what is going to be +sent. For example, if you ``echo +$message->toString();`` you would see something like this: + +.. code-block:: bash + + Message-ID: <1230173678.4952f5eeb1432@swift.generated> + Date: Thu, 25 Dec 2008 13:54:38 +1100 + Subject: Example subject + From: Chris Corbyn + To: Receiver Name + MIME-Version: 1.0 + Content-Type: text/plain; charset=utf-8 + Content-Transfer-Encoding: quoted-printable + + Here is the message + +We'll take a closer look at the methods you use to create your message in the +following sections. + +Adding Content to Your Message +------------------------------ + +Rich content can be added to messages in Swift Mailer with relative ease by +calling methods such as ``setSubject()``, ``setBody()``, ``addPart()`` and +``attach()``. + +Setting the Subject Line +~~~~~~~~~~~~~~~~~~~~~~~~ + +The subject line, displayed in the recipients' mail client can be set with the +``setSubject()`` method, or as a parameter to ``Swift_Message::newInstance()``. + +To set the subject of your Message: + +* Call the ``setSubject()`` method of the Message, or specify it at the time + you create the message. + + .. code-block:: php + + // Pass it as a parameter when you create the message + $message = Swift_Message::newInstance('My amazing subject'); + + // Or set it after like this + $message->setSubject('My amazing subject'); + +Setting the Body Content +~~~~~~~~~~~~~~~~~~~~~~~~ + +The body of the message -- seen when the user opens the message -- +is specified by calling the ``setBody()`` method. If an alternative body is to +be included ``addPart()`` can be used. + +The body of a message is the main part that is read by the user. Often people +want to send a message in HTML format (``text/html``), other +times people want to send in plain text (``text/plain``), or +sometimes people want to send both versions and allow the recipient to choose +how they view the message. + +As a rule of thumb, if you're going to send a HTML email, always include a +plain-text equivalent of the same content so that users who prefer to read +plain text can do so. + +To set the body of your Message: + +* Call the ``setBody()`` method of the Message, or specify it at the time you + create the message. + +* Add any alternative bodies with ``addPart()``. + +If the recipient's mail client offers preferences for displaying text vs. HTML +then the mail client will present that part to the user where available. In +other cases the mail client will display the "best" part it can - usually HTML +if you've included HTML. + +.. code-block:: php + + // Pass it as a parameter when you create the message + $message = Swift_Message::newInstance('Subject here', 'My amazing body'); + + // Or set it after like this + $message->setBody('My amazing body', 'text/html'); + + // Add alternative parts with addPart() + $message->addPart('My amazing body in plain text', 'text/plain'); + +Attaching Files +--------------- + +Attachments are downloadable parts of a message and can be added by calling +the ``attach()`` method on the message. You can add attachments that exist on +disk, or you can create attachments on-the-fly. + +Attachments are actually an interesting area of Swift Mailer and something +that could put a lot of power at your fingertips if you grasp the concept +behind the way a message is held together. + +Although we refer to files sent over e-mails as "attachments" -- because +they're attached to the message -- lots of other parts of the message are +actually "attached" even if we don't refer to these parts as attachments. + +File attachments are created by the ``Swift_Attachment`` class +and then attached to the message via the ``attach()`` method on +it. For all of the "every day" MIME types such as all image formats, word +documents, PDFs and spreadsheets you don't need to explicitly set the +content-type of the attachment, though it would do no harm to do so. For less +common formats you should set the content-type -- which we'll cover in a +moment. + +Attaching Existing Files +~~~~~~~~~~~~~~~~~~~~~~~~ + +Files that already exist, either on disk or at a URL can be attached to a +message with just one line of code, using ``Swift_Attachment::fromPath()``. + +You can attach files that exist locally, or if your PHP installation has +``allow_url_fopen`` turned on you can attach files from other +websites. + +To attach an existing file: + +* Create an attachment with ``Swift_Attachment::fromPath()``. + +* Add the attachment to the message with ``attach()``. + +The attachment will be presented to the recipient as a downloadable file with +the same filename as the one you attached. + +.. code-block:: php + + // Create the attachment + // * Note that you can technically leave the content-type parameter out + $attachment = Swift_Attachment::fromPath('/path/to/image.jpg', 'image/jpeg'); + + // Attach it to the message + $message->attach($attachment); + + + // The two statements above could be written in one line instead + $message->attach(Swift_Attachment::fromPath('/path/to/image.jpg')); + + + // You can attach files from a URL if allow_url_fopen is on in php.ini + $message->attach(Swift_Attachment::fromPath('http://site.tld/logo.png')); + +Setting the Filename +~~~~~~~~~~~~~~~~~~~~ + +Usually you don't need to explicitly set the filename of an attachment because +the name of the attached file will be used by default, but if you want to set +the filename you use the ``setFilename()`` method of the Attachment. + +To change the filename of an attachment: + +* Call its ``setFilename()`` method. + +The attachment will be attached in the normal way, but meta-data sent inside +the email will rename the file to something else. + +.. code-block:: php + + // Create the attachment and call its setFilename() method + $attachment = Swift_Attachment::fromPath('/path/to/image.jpg') + ->setFilename('cool.jpg'); + + + // Because there's a fluid interface, you can do this in one statement + $message->attach( + Swift_Attachment::fromPath('/path/to/image.jpg')->setFilename('cool.jpg') + ); + +Attaching Dynamic Content +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Files that are generated at runtime, such as PDF documents or images created +via GD can be attached directly to a message without writing them out to disk. +Use the standard ``Swift_Attachment::newInstance()`` method. + +To attach dynamically created content: + +* Create your content as you normally would. + +* Create an attachment with ``Swift_Attachment::newInstance()``, specifying + the source data of your content along with a name and the content-type. + +* Add the attachment to the message with ``attach()``. + +The attachment will be presented to the recipient as a downloadable file +with the filename and content-type you specify. + +.. note:: + + If you would usually write the file to disk anyway you should just attach + it with ``Swift_Attachment::fromPath()`` since this will use less memory: + + .. code-block:: php + + // Create your file contents in the normal way, but don't write them to disk + $data = create_my_pdf_data(); + + // Create the attachment with your data + $attachment = Swift_Attachment::newInstance($data, 'my-file.pdf', 'application/pdf'); + + // Attach it to the message + $message->attach($attachment); + + + // You can alternatively use method chaining to build the attachment + $attachment = Swift_Attachment::newInstance() + ->setFilename('my-file.pdf') + ->setContentType('application/pdf') + ->setBody($data) + ; + +Changing the Disposition +~~~~~~~~~~~~~~~~~~~~~~~~ + +Attachments just appear as files that can be saved to the Desktop if desired. +You can make attachment appear inline where possible by using the +``setDisposition()`` method of an attachment. + +To make an attachment appear inline: + +* Call its ``setDisposition()`` method. + +The attachment will be displayed within the email viewing window if the mail +client knows how to display it. + +.. note:: + + If you try to create an inline attachment for a non-displayable file type + such as a ZIP file, the mail client should just present the attachment as + normal: + + .. code-block:: php + + // Create the attachment and call its setDisposition() method + $attachment = Swift_Attachment::fromPath('/path/to/image.jpg') + ->setDisposition('inline'); + + + // Because there's a fluid interface, you can do this in one statement + $message->attach( + Swift_Attachment::fromPath('/path/to/image.jpg')->setDisposition('inline') + ); + +Embedding Inline Media Files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Often people want to include an image or other content inline with a HTML +message. It's easy to do this with HTML linking to remote resources, but this +approach is usually blocked by mail clients. Swift Mailer allows you to embed +your media directly into the message. + +Mail clients usually block downloads from remote resources because this +technique was often abused as a mean of tracking who opened an email. If +you're sending a HTML email and you want to include an image in the message +another approach you can take is to embed the image directly. + +Swift Mailer makes embedding files into messages extremely streamlined. You +embed a file by calling the ``embed()`` method of the message, +which returns a value you can use in a ``src`` or +``href`` attribute in your HTML. + +Just like with attachments, it's possible to embed dynamically generated +content without having an existing file available. + +The embedded files are sent in the email as a special type of attachment that +has a unique ID used to reference them within your HTML attributes. On mail +clients that do not support embedded files they may appear as attachments. + +Although this is commonly done for images, in theory it will work for any +displayable (or playable) media type. Support for other media types (such as +video) is dependent on the mail client however. + +Embedding Existing Files +........................ + +Files that already exist, either on disk or at a URL can be embedded in a +message with just one line of code, using ``Swift_EmbeddedFile::fromPath()``. + +You can embed files that exist locally, or if your PHP installation has +``allow_url_fopen`` turned on you can embed files from other websites. + +To embed an existing file: + +* Create a message object with ``Swift_Message::newInstance()``. + +* Set the body as HTML, and embed a file at the correct point in the message with ``embed()``. + +The file will be displayed with the message inline with the HTML wherever its ID +is used as a ``src`` attribute. + +.. note:: + + ``Swift_Image`` and ``Swift_EmbeddedFile`` are just aliases of one + another. ``Swift_Image`` exists for semantic purposes. + +.. note:: + + You can embed files in two stages if you prefer. Just capture the return + value of ``embed()`` in a variable and use that as the ``src`` attribute. + + .. code-block:: php + + // Create the message + $message = Swift_Message::newInstance('My subject'); + + // Set the body + $message->setBody( + '' . + ' ' . + ' ' . + ' Here is an image Image' . + ' Rest of message' . + ' ' . + '', + 'text/html' // Mark the content-type as HTML + ); + + // You can embed files from a URL if allow_url_fopen is on in php.ini + $message->setBody( + '' . + ' ' . + ' ' . + ' Here is an image Image' . + ' Rest of message' . + ' ' . + '', + 'text/html' + ); + + + // If placing the embed() code inline becomes cumbersome + // it's easy to do this in two steps + $cid = $message->embed(Swift_Image::fromPath('image.png')); + + $message->setBody( + '' . + ' ' . + ' ' . + ' Here is an image Image' . + ' Rest of message' . + ' ' . + '', + 'text/html' // Mark the content-type as HTML + ); + +Embedding Dynamic Content +......................... + +Images that are generated at runtime, such as images created via GD can be +embedded directly to a message without writing them out to disk. Use the +standard ``Swift_Image::newInstance()`` method. + +To embed dynamically created content: + +* Create a message object with ``Swift_Message::newInstance()``. + +* Set the body as HTML, and embed a file at the correct point in the message + with ``embed()``. You will need to specify a filename and a content-type. + +The file will be displayed with the message inline with the HTML wherever its ID +is used as a ``src`` attribute. + +.. note:: + + ``Swift_Image`` and ``Swift_EmbeddedFile`` are just aliases of one + another. ``Swift_Image`` exists for semantic purposes. + +.. note:: + + You can embed files in two stages if you prefer. Just capture the return + value of ``embed()`` in a variable and use that as the ``src`` attribute. + + .. code-block:: php + + // Create your file contents in the normal way, but don't write them to disk + $img_data = create_my_image_data(); + + //Create the message + $message = Swift_Message::newInstance('My subject'); + + //Set the body + $message->setBody( + '' . + ' ' . + ' ' . + ' Here is an image Image' . + ' Rest of message' . + ' ' . + '', + 'text/html' // Mark the content-type as HTML + ); + + + // If placing the embed() code inline becomes cumbersome + // it's easy to do this in two steps + $cid = $message->embed(Swift_Image::newInstance($img_data, 'image.jpg', 'image/jpeg')); + + $message->setBody( + '' . + ' ' . + ' ' . + ' Here is an image Image' . + ' Rest of message' . + ' ' . + '', + 'text/html' // Mark the content-type as HTML + ); + +Adding Recipients to Your Message +--------------------------------- + +Recipients are specified within the message itself via ``setTo()``, ``setCc()`` +and ``setBcc()``. Swift Mailer reads these recipients from the message when it +gets sent so that it knows where to send the message to. + +Message recipients are one of three types: + +* ``To:`` recipients -- the primary recipients (required) + +* ``Cc:`` recipients -- receive a copy of the message (optional) + +* ``Bcc:`` recipients -- hidden from other recipients (optional) + +Each type can contain one, or several addresses. It's possible to list only +the addresses of the recipients, or you can personalize the address by +providing the real name of the recipient. + +Make sure to add only valid email addresses as recipients. If you try to add an +invalid email address with ``setTo()``, ``setCc()`` or ``setBcc()``, Swift +Mailer will throw a ``Swift_RfcComplianceException``. + +If you add recipients automatically based on a data source that may contain +invalid email addresses, you can prevent possible exceptions by validating the +addresses using ``Swift_Validate::email($email)`` and only adding addresses +that validate. Another way would be to wrap your ``setTo()``, ``setCc()`` and +``setBcc()`` calls in a try-catch block and handle the +``Swift_RfcComplianceException`` in the catch block. + +.. sidebar:: Syntax for Addresses + + If you only wish to refer to a single email address (for example your + ``From:`` address) then you can just use a string. + + .. code-block:: php + + $message->setFrom('some@address.tld'); + + If you want to include a name then you must use an associative array. + + .. code-block:: php + + $message->setFrom(array('some@address.tld' => 'The Name')); + + If you want to include multiple addresses then you must use an array. + + .. code-block:: php + + $message->setTo(array('some@address.tld', 'other@address.tld')); + + You can mix personalized (addresses with a name) and non-personalized + addresses in the same list by mixing the use of associative and + non-associative array syntax. + + .. code-block:: php + + $message->setTo(array( + 'recipient-with-name@example.org' => 'Recipient Name One', + 'no-name@example.org', // Note that this is not a key-value pair + 'named-recipient@example.org' => 'Recipient Name Two' + )); + +Setting ``To:`` Recipients +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``To:`` recipients are required in a message and are set with the +``setTo()`` or ``addTo()`` methods of the message. + +To set ``To:`` recipients, create the message object using either +``new Swift_Message( ... )`` or ``Swift_Message::newInstance( ... )``, +then call the ``setTo()`` method with a complete array of addresses, or use the +``addTo()`` method to iteratively add recipients. + +The ``setTo()`` method accepts input in various formats as described earlier in +this chapter. The ``addTo()`` method takes either one or two parameters. The +first being the email address and the second optional parameter being the name +of the recipient. + +``To:`` recipients are visible in the message headers and will be +seen by the other recipients. + +.. note:: + + Multiple calls to ``setTo()`` will not add new recipients -- each + call overrides the previous calls. If you want to iteratively add + recipients, use the ``addTo()`` method. + + .. code-block:: php + + // Using setTo() to set all recipients in one go + $message->setTo(array( + 'person1@example.org', + 'person2@otherdomain.org' => 'Person 2 Name', + 'person3@example.org', + 'person4@example.org', + 'person5@example.org' => 'Person 5 Name' + )); + + // Using addTo() to add recipients iteratively + $message->addTo('person1@example.org'); + $message->addTo('person2@example.org', 'Person 2 Name'); + +Setting ``Cc:`` Recipients +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``Cc:`` recipients are set with the ``setCc()`` or ``addCc()`` methods of the +message. + +To set ``Cc:`` recipients, create the message object using either +``new Swift_Message( ... )`` or ``Swift_Message::newInstance( ... )``, then call +the ``setCc()`` method with a complete array of addresses, or use the +``addCc()`` method to iteratively add recipients. + +The ``setCc()`` method accepts input in various formats as described earlier in +this chapter. The ``addCc()`` method takes either one or two parameters. The +first being the email address and the second optional parameter being the name +of the recipient. + +``Cc:`` recipients are visible in the message headers and will be +seen by the other recipients. + +.. note:: + + Multiple calls to ``setCc()`` will not add new recipients -- each + call overrides the previous calls. If you want to iteratively add Cc: + recipients, use the ``addCc()`` method. + + .. code-block:: php + + // Using setCc() to set all recipients in one go + $message->setCc(array( + 'person1@example.org', + 'person2@otherdomain.org' => 'Person 2 Name', + 'person3@example.org', + 'person4@example.org', + 'person5@example.org' => 'Person 5 Name' + )); + + // Using addCc() to add recipients iteratively + $message->addCc('person1@example.org'); + $message->addCc('person2@example.org', 'Person 2 Name'); + +Setting ``Bcc:`` Recipients +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``Bcc:`` recipients receive a copy of the message without anybody else knowing +it, and are set with the ``setBcc()`` or ``addBcc()`` methods of the message. + +To set ``Bcc:`` recipients, create the message object using either ``new +Swift_Message( ... )`` or ``Swift_Message::newInstance( ... )``, then call the +``setBcc()`` method with a complete array of addresses, or use +the ``addBcc()`` method to iteratively add recipients. + +The ``setBcc()`` method accepts input in various formats as described earlier in +this chapter. The ``addBcc()`` method takes either one or two parameters. The +first being the email address and the second optional parameter being the name +of the recipient. + +Only the individual ``Bcc:`` recipient will see their address in the message +headers. Other recipients (including other ``Bcc:`` recipients) will not see the +address. + +.. note:: + + Multiple calls to ``setBcc()`` will not add new recipients -- each + call overrides the previous calls. If you want to iteratively add Bcc: + recipients, use the ``addBcc()`` method. + + .. code-block:: php + + // Using setBcc() to set all recipients in one go + $message->setBcc(array( + 'person1@example.org', + 'person2@otherdomain.org' => 'Person 2 Name', + 'person3@example.org', + 'person4@example.org', + 'person5@example.org' => 'Person 5 Name' + )); + + // Using addBcc() to add recipients iteratively + $message->addBcc('person1@example.org'); + $message->addBcc('person2@example.org', 'Person 2 Name'); + +Specifying Sender Details +------------------------- + +An email must include information about who sent it. Usually this is managed +by the ``From:`` address, however there are other options. + +The sender information is contained in three possible places: + +* ``From:`` -- the address(es) of who wrote the message (required) + +* ``Sender:`` -- the address of the single person who sent the message + (optional) + +* ``Return-Path:`` -- the address where bounces should go to (optional) + +You must always include a ``From:`` address by using ``setFrom()`` on the +message. Swift Mailer will use this as the default ``Return-Path:`` unless +otherwise specified. + +The ``Sender:`` address exists because the person who actually sent the email +may not be the person who wrote the email. It has a higher precedence than the +``From:`` address and will be used as the ``Return-Path:`` unless otherwise +specified. + +Setting the ``From:`` Address +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A ``From:`` address is required and is set with the ``setFrom()`` method of the +message. ``From:`` addresses specify who actually wrote the email, and usually who sent it. + +What most people probably don't realise is that you can have more than one +``From:`` address if more than one person wrote the email -- for example if an +email was put together by a committee. + +To set the ``From:`` address(es): + +* Call the ``setFrom()`` method on the Message. + +The ``From:`` address(es) are visible in the message headers and +will be seen by the recipients. + +.. note:: + + If you set multiple ``From:`` addresses then you absolutely must set a + ``Sender:`` address to indicate who physically sent the message. + + .. code-block:: php + + // Set a single From: address + $message->setFrom('your@address.tld'); + + // Set a From: address including a name + $message->setFrom(array('your@address.tld' => 'Your Name')); + + // Set multiple From: addresses if multiple people wrote the email + $message->setFrom(array( + 'person1@example.org' => 'Sender One', + 'person2@example.org' => 'Sender Two' + )); + +Setting the ``Sender:`` Address +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A ``Sender:`` address specifies who sent the message and is set with the +``setSender()`` method of the message. + +To set the ``Sender:`` address: + +* Call the ``setSender()`` method on the Message. + +The ``Sender:`` address is visible in the message headers and will be seen by +the recipients. + +This address will be used as the ``Return-Path:`` unless otherwise specified. + +.. note:: + + If you set multiple ``From:`` addresses then you absolutely must set a + ``Sender:`` address to indicate who physically sent the message. + +You must not set more than one sender address on a message because it's not +possible for more than one person to send a single message. + +.. code-block:: php + + $message->setSender('your@address.tld'); + +Setting the ``Return-Path:`` (Bounce) Address +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``Return-Path:`` address specifies where bounce notifications should +be sent and is set with the ``setReturnPath()`` method of the message. + +You can only have one ``Return-Path:`` and it must not include +a personal name. + +To set the ``Return-Path:`` address: + +* Call the ``setReturnPath()`` method on the Message. + +Bounce notifications will be sent to this address. + +.. code-block:: php + + $message->setReturnPath('bounces@address.tld'); + + +Signed/Encrypted Message +------------------------ + +To increase the integrity/security of a message it is possible to sign and/or +encrypt an message using one or multiple signers. + +S/MIME +~~~~~~ + +S/MIME can sign and/or encrypt a message using the OpenSSL extension. + +When signing a message, the signer creates a signature of the entire content of the message (including attachments). + +The certificate and private key must be PEM encoded, and can be either created using for example OpenSSL or +obtained at an official Certificate Authority (CA). + +**The recipient must have the CA certificate in the list of trusted issuers in order to verify the signature.** + +**Make sure the certificate supports emailProtection.** + +When using OpenSSL this can done by the including the *-addtrust emailProtection* parameter when creating the certificate. + +.. code-block:: php + + $message = Swift_SignedMessage::newInstance(); + + $smimeSigner = Swift_Signers_SMimeSigner::newInstance(); + $smimeSigner->setSignCertificate('/path/to/certificate.pem', '/path/to/private-key.pem'); + $message->attachSigner($smimeSigner); + +When the private key is secured using a passphrase use the following instead. + +.. code-block:: php + + $message = Swift_SignedMessage::newInstance(); + + $smimeSigner = Swift_Signers_SMimeSigner::newInstance(); + $smimeSigner->setSignCertificate('/path/to/certificate.pem', array('/path/to/private-key.pem', 'passphrase')); + $message->attachSigner($smimeSigner); + +By default the signature is added as attachment, +making the message still readable for mailing agents not supporting signed messages. + +Storing the message as binary is also possible but not recommended. + +.. code-block:: php + + $smimeSigner->setSignCertificate('/path/to/certificate.pem', '/path/to/private-key.pem', PKCS7_BINARY); + +When encrypting the message (also known as enveloping), the entire message (including attachments) +is encrypted using a certificate, and the recipient can then decrypt the message using corresponding private key. + +Encrypting ensures nobody can read the contents of the message without the private key. + +Normally the recipient provides a certificate for encrypting and keeping the decryption key private. + +Using both signing and encrypting is also possible. + +.. code-block:: php + + $message = Swift_SignedMessage::newInstance(); + + $smimeSigner = Swift_Signers_SMimeSigner::newInstance(); + $smimeSigner->setSignCertificate('/path/to/sign-certificate.pem', '/path/to/private-key.pem'); + $smimeSigner->setEncryptCertificate('/path/to/encrypt-certificate.pem'); + $message->attachSigner($smimeSigner); + +The used encryption cipher can be set as the second parameter of setEncryptCertificate() + +See http://php.net/manual/openssl.ciphers for a list of supported ciphers. + +By default the message is first signed and then encrypted, this can be changed by adding. + +.. code-block:: php + + $smimeSigner->setSignThenEncrypt(false); + +**Changing this is not recommended as most mail agents don't support this none-standard way.** + +Only when having trouble with sign then encrypt method, this should be changed. + +Requesting a Read Receipt +------------------------- + +It is possible to request a read-receipt to be sent to an address when the +email is opened. To request a read receipt set the address with +``setReadReceiptTo()``. + +To request a read receipt: + +* Set the address you want the receipt to be sent to with the + ``setReadReceiptTo()`` method on the Message. + +When the email is opened, if the mail client supports it a notification will be sent to this address. + +.. note:: + + Read receipts won't work for the majority of recipients since many mail + clients auto-disable them. Those clients that will send a read receipt + will make the user aware that one has been requested. + + .. code-block:: php + + $message->setReadReceiptTo('your@address.tld'); + +Setting the Character Set +------------------------- + +The character set of the message (and it's MIME parts) is set with the +``setCharset()`` method. You can also change the global default of UTF-8 by +working with the ``Swift_Preferences`` class. + +Swift Mailer will default to the UTF-8 character set unless otherwise +overridden. UTF-8 will work in most instances since it includes all of the +standard US keyboard characters in addition to most international characters. + +It is absolutely vital however that you know what character set your message +(or it's MIME parts) are written in otherwise your message may be received +completely garbled. + +There are two places in Swift Mailer where you can change the character set: + +* In the ``Swift_Preferences`` class + +* On each individual message and/or MIME part + +To set the character set of your Message: + +* Change the global UTF-8 setting by calling + ``Swift_Preferences::setCharset()``; or + +* Call the ``setCharset()`` method on the message or the MIME part. + + .. code-block:: php + + // Approach 1: Change the global setting (suggested) + Swift_Preferences::getInstance()->setCharset('iso-8859-2'); + + // Approach 2: Call the setCharset() method of the message + $message = Swift_Message::newInstance() + ->setCharset('iso-8859-2'); + + // Approach 3: Specify the charset when setting the body + $message->setBody('My body', 'text/html', 'iso-8859-2'); + + // Approach 4: Specify the charset for each part added + $message->addPart('My part', 'text/plain', 'iso-8859-2'); + +Setting the Line Length +----------------------- + +The length of lines in a message can be changed by using the ``setMaxLineLength()`` method on the message. It should be kept to less than +1000 characters. + +Swift Mailer defaults to using 78 characters per line in a message. This is +done for historical reasons and so that the message can be easily viewed in +plain-text terminals. + +To change the maximum length of lines in your Message: + +* Call the ``setMaxLineLength()`` method on the Message. + +Lines that are longer than the line length specified will be wrapped between +words. + +.. note:: + + You should never set a maximum length longer than 1000 characters + according to RFC 2822. Doing so could have unspecified side-effects such + as truncating parts of your message when it is transported between SMTP + servers. + + .. code-block:: php + + $message->setMaxLineLength(1000); + +Setting the Message Priority +---------------------------- + +You can change the priority of the message with ``setPriority()``. Setting the +priority will not change the way your email is sent -- it is purely an +indicative setting for the recipient. + +The priority of a message is an indication to the recipient what significance +it has. Swift Mailer allows you to set the priority by calling the ``setPriority`` method. This method takes an integer value between 1 and 5: + +* Highest +* High +* Normal +* Low +* Lowest + +To set the message priority: + +* Set the priority as an integer between 1 and 5 with the ``setPriority()`` + method on the Message. + +.. code-block:: php + + // Indicate "High" priority + $message->setPriority(2); diff --git a/vendor/swiftmailer/swiftmailer/doc/overview.rst b/vendor/swiftmailer/swiftmailer/doc/overview.rst new file mode 100644 index 0000000..c912617 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/doc/overview.rst @@ -0,0 +1,161 @@ +Library Overview +================ + +Most features (and more) of your every day mail client software are provided +by Swift Mailer, using object-oriented PHP code as the interface. + +In this chapter we will take a short tour of the various components, which put +together form the Swift Mailer library as a whole. You will learn key +terminology used throughout the rest of this book and you will gain a little +understanding of the classes you will work with as you integrate Swift Mailer +into your application. + +This chapter is intended to prepare you for the information contained in the +subsequent chapters of this book. You may choose to skip this chapter if you +are fairly technically minded, though it is likely to save you some time in +the long run if you at least read between the lines here. + +System Requirements +------------------- + +The basic requirements to operate Swift Mailer are extremely minimal and +easily achieved. Historically, Swift Mailer has supported both PHP 4 and PHP 5 +by following a parallel development workflow. Now in it's fourth major +version, and Swift Mailer operates on servers running PHP 5.2 or higher. + +The library aims to work with as many PHP 5 projects as possible: + +* PHP 5.2 or higher, with the SPL extension (standard) + +* Limited network access to connect to remote SMTP servers + +* 8 MB or more memory limit (Swift Mailer uses around 2 MB) + +Component Breakdown +------------------- + +Swift Mailer is made up of many classes. Each of these classes can be grouped +into a general "component" group which describes the task it is designed to +perform. + +We'll take a brief look at the components which form Swift Mailer in this +section of the book. + +The Mailer +~~~~~~~~~~ + +The mailer class, ``Swift_Mailer`` is the central class in the library where +all of the other components meet one another. ``Swift_Mailer`` acts as a sort +of message dispatcher, communicating with the underlying Transport to deliver +your Message to all intended recipients. + +If you were to dig around in the source code for Swift Mailer you'd notice +that ``Swift_Mailer`` itself is pretty bare. It delegates to other objects for +most tasks and in theory, if you knew the internals of Swift Mailer well you +could by-pass this class entirely. We wouldn't advise doing such a thing +however -- there are reasons this class exists: + +* for consistency, regardless of the Transport used + +* to provide abstraction from the internals in the event internal API changes + are made + +* to provide convenience wrappers around aspects of the internal API + +An instance of ``Swift_Mailer`` is created by the developer before sending any +Messages. + +Transports +~~~~~~~~~~ + +Transports are the classes in Swift Mailer that are responsible for +communicating with a service in order to deliver a Message. There are several +types of Transport in Swift Mailer, all of which implement the Swift_Transport +interface and offer underlying start(), stop() and send() methods. + +Typically you will not need to know how a Transport works under-the-surface, +you will only need to know how to create an instance of one, and which one to +use for your environment. + ++---------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+ +| Class | Features | Pros/cons | ++=================================+=============================================================================================+===============================================================================================================================================+ +| ``Swift_SmtpTransport`` | Sends messages over SMTP; Supports Authentication; Supports Encryption | Very portable; Pleasingly predictable results; Provides good feedback | ++---------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+ +| ``Swift_SendmailTransport`` | Communicates with a locally installed ``sendmail`` executable (Linux/UNIX) | Quick time-to-run; Provides less-accurate feedback than SMTP; Requires ``sendmail`` installation | ++---------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+ +| ``Swift_MailTransport`` | Uses PHP's built-in ``mail()`` function | Very portable; Potentially unpredictable results; Provides extremely weak feedback | ++---------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+ +| ``Swift_LoadBalancedTransport`` | Cycles through a collection of the other Transports to manage load-reduction | Provides graceful fallback if one Transport fails (e.g. an SMTP server is down); Keeps the load on remote services down by spreading the work | ++---------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+ +| ``Swift_FailoverTransport`` | Works in conjunction with a collection of the other Transports to provide high-availability | Provides graceful fallback if one Transport fails (e.g. an SMTP server is down) | ++---------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+ + +MIME Entities +~~~~~~~~~~~~~ + +Everything that forms part of a Message is called a MIME Entity. All MIME +entities in Swift Mailer share a common set of features. There are various +types of MIME entity that serve different purposes such as Attachments and +MIME parts. + +An e-mail message is made up of several relatively simple entities that are +combined in different ways to achieve different results. All of these entities +have the same fundamental outline but serve a different purpose. The Message +itself can be defined as a MIME entity, an Attachment is a MIME entity, all +MIME parts are MIME entities -- and so on! + +The basic units of each MIME entity -- be it the Message itself, or an +Attachment -- are its Headers and its body: + +.. code-block:: text + + Other-Header: Another value + + The body content itself + +The Headers of a MIME entity, and its body must conform to some strict +standards defined by various RFC documents. Swift Mailer ensures that these +specifications are followed by using various types of object, including +Encoders and different Header types to generate the entity. + +Each MIME component implements the base ``Swift_Mime_MimeEntity`` interface, +which offers methods for retrieving Headers, adding new Headers, changing the +Encoder, updating the body and so on! + +All MIME entities have one Header in common -- the Content-Type Header, +updated with the entity's ``setContentType()`` method. + +Encoders +~~~~~~~~ + +Encoders are used to transform the content of Messages generated in Swift +Mailer into a format that is safe to send across the internet and that +conforms to RFC specifications. + +Generally speaking you will not need to interact with the Encoders in Swift +Mailer -- the correct settings will be handled by the library itself. +However they are probably worth a brief mention in the event that you do want +to play with them. + +Both the Headers and the body of all MIME entities (including the Message +itself) use Encoders to ensure the data they contain can be sent over the +internet without becoming corrupted or misinterpreted. + +There are two types of Encoder: Base64 and Quoted-Printable. + +Plugins +~~~~~~~ + +Plugins exist to extend, or modify the behaviour of Swift Mailer. They respond +to Events that are fired within the Transports during sending. + +There are a number of Plugins provided as part of the base Swift Mailer +package and they all follow a common interface to respond to Events fired +within the library. Interfaces are provided to "listen" to each type of Event +fired and to act as desired when a listened-to Event occurs. + +Although several plugins are provided with Swift Mailer out-of-the-box, the +Events system has been specifically designed to make it easy for experienced +object-oriented developers to write their own plugins in order to achieve +goals that may not be possible with the base library. diff --git a/vendor/swiftmailer/swiftmailer/doc/plugins.rst b/vendor/swiftmailer/swiftmailer/doc/plugins.rst new file mode 100644 index 0000000..4a2efa9 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/doc/plugins.rst @@ -0,0 +1,385 @@ +Plugins +======= + +Plugins are provided with Swift Mailer and can be used to extend the behavior +of the library in situations where using simple class inheritance would be more complex. + +AntiFlood Plugin +---------------- + +Many SMTP servers have limits on the number of messages that may be sent +during any single SMTP connection. The AntiFlood plugin provides a way to stay +within this limit while still managing a large number of emails. + +A typical limit for a single connection is 100 emails. If the server you +connect to imposes such a limit, it expects you to disconnect after that +number of emails has been sent. You could manage this manually within a loop, +but the AntiFlood plugin provides the necessary wrapper code so that you don't +need to worry about this logic. + +Regardless of limits imposed by the server, it's usually a good idea to be +conservative with the resources of the SMTP server. Sending will become +sluggish if the server is being over-used so using the AntiFlood plugin will +not be a bad idea even if no limits exist. + +The AntiFlood plugin's logic is basically to disconnect and the immediately +re-connect with the SMTP server every X number of emails sent, where X is a +number you specify to the plugin. + +You can also specify a time period in seconds that Swift Mailer should pause +for between the disconnect/re-connect process. It's a good idea to pause for a +short time (say 30 seconds every 100 emails) simply to give the SMTP server a +chance to process its queue and recover some resources. + +Using the AntiFlood Plugin +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The AntiFlood Plugin -- like all plugins -- is added with the Mailer class' +``registerPlugin()`` method. It takes two constructor parameters: the number of +emails to pause after, and optionally the number of seconds to pause for. + +To use the AntiFlood plugin: + +* Create an instance of the Mailer using any Transport you choose. + +* Create an instance of the ``Swift_Plugins_AntiFloodPlugin`` class, passing + in one or two constructor parameters. + +* Register the plugin using the Mailer's ``registerPlugin()`` method. + +* Continue using Swift Mailer to send messages as normal. + +When Swift Mailer sends messages it will count the number of messages that +have been sent since the last re-connect. Once the number hits your specified +threshold it will disconnect and re-connect, optionally pausing for a +specified amount of time. + +.. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Mailer using any Transport + $mailer = Swift_Mailer::newInstance( + Swift_SmtpTransport::newInstance('smtp.example.org', 25) + ); + + // Use AntiFlood to re-connect after 100 emails + $mailer->registerPlugin(new Swift_Plugins_AntiFloodPlugin(100)); + + // And specify a time in seconds to pause for (30 secs) + $mailer->registerPlugin(new Swift_Plugins_AntiFloodPlugin(100, 30)); + + // Continue sending as normal + for ($lotsOfRecipients as $recipient) { + ... + + $mailer->send( ... ); + } + +Throttler Plugin +---------------- + +If your SMTP server has restrictions in place to limit the rate at which you +send emails, then your code will need to be aware of this rate-limiting. The +Throttler plugin makes Swift Mailer run at a rate-limited speed. + +Many shared hosts don't open their SMTP servers as a free-for-all. Usually +they have policies in place (probably to discourage spammers) that only allow +you to send a fixed number of emails per-hour/day. + +The Throttler plugin supports two modes of rate-limiting and with each, you +will need to do that math to figure out the values you want. The plugin can +limit based on the number of emails per minute, or the number of +bytes-transferred per-minute. + +Using the Throttler Plugin +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Throttler Plugin -- like all plugins -- is added with the Mailer class' +``registerPlugin()`` method. It has two required constructor parameters that +tell it how to do its rate-limiting. + +To use the Throttler plugin: + +* Create an instance of the Mailer using any Transport you choose. + +* Create an instance of the ``Swift_Plugins_ThrottlerPlugin`` class, passing + the number of emails, or bytes you wish to limit by, along with the mode + you're using. + +* Register the plugin using the Mailer's ``registerPlugin()`` method. + +* Continue using Swift Mailer to send messages as normal. + +When Swift Mailer sends messages it will keep track of the rate at which sending +messages is occurring. If it realises that sending is happening too fast, it +will cause your program to ``sleep()`` for enough time to average out the rate. + +.. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Mailer using any Transport + $mailer = Swift_Mailer::newInstance( + Swift_SmtpTransport::newInstance('smtp.example.org', 25) + ); + + // Rate limit to 100 emails per-minute + $mailer->registerPlugin(new Swift_Plugins_ThrottlerPlugin( + 100, Swift_Plugins_ThrottlerPlugin::MESSAGES_PER_MINUTE + )); + + // Rate limit to 10MB per-minute + $mailer->registerPlugin(new Swift_Plugins_ThrottlerPlugin( + 1024 * 1024 * 10, Swift_Plugins_ThrottlerPlugin::BYTES_PER_MINUTE + )); + + // Continue sending as normal + for ($lotsOfRecipients as $recipient) { + ... + + $mailer->send( ... ); + } + +Logger Plugin +------------- + +The Logger plugins helps with debugging during the process of sending. It can +help to identify why an SMTP server is rejecting addresses, or any other +hard-to-find problems that may arise. + +The Logger plugin comes in two parts. There's the plugin itself, along with +one of a number of possible Loggers that you may choose to use. For example, +the logger may output messages directly in realtime, or it may capture +messages in an array. + +One other notable feature is the way in which the Logger plugin changes +Exception messages. If Exceptions are being thrown but the error message does +not provide conclusive information as to the source of the problem (such as an +ambiguous SMTP error) the Logger plugin includes the entire SMTP transcript in +the error message so that debugging becomes a simpler task. + +There are a few available Loggers included with Swift Mailer, but writing your +own implementation is incredibly simple and is achieved by creating a short +class that implements the ``Swift_Plugins_Logger`` interface. + +* ``Swift_Plugins_Loggers_ArrayLogger``: Keeps a collection of log messages + inside an array. The array content can be cleared or dumped out to the + screen. + +* ``Swift_Plugins_Loggers_EchoLogger``: Prints output to the screen in + realtime. Handy for very rudimentary debug output. + +Using the Logger Plugin +~~~~~~~~~~~~~~~~~~~~~~~ + +The Logger Plugin -- like all plugins -- is added with the Mailer class' +``registerPlugin()`` method. It accepts an instance of ``Swift_Plugins_Logger`` +in its constructor. + +To use the Logger plugin: + +* Create an instance of the Mailer using any Transport you choose. + +* Create an instance of the a Logger implementation of + ``Swift_Plugins_Logger``. + +* Create an instance of the ``Swift_Plugins_LoggerPlugin`` class, passing the + created Logger instance to its constructor. + +* Register the plugin using the Mailer's ``registerPlugin()`` method. + +* Continue using Swift Mailer to send messages as normal. + +* Dump the contents of the log with the logger's ``dump()`` method. + +When Swift Mailer sends messages it will keep a log of all the interactions +with the underlying Transport being used. Depending upon the Logger that has +been used the behaviour will differ, but all implementations offer a way to +get the contents of the log. + +.. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Mailer using any Transport + $mailer = Swift_Mailer::newInstance( + Swift_SmtpTransport::newInstance('smtp.example.org', 25) + ); + + // To use the ArrayLogger + $logger = new Swift_Plugins_Loggers_ArrayLogger(); + $mailer->registerPlugin(new Swift_Plugins_LoggerPlugin($logger)); + + // Or to use the Echo Logger + $logger = new Swift_Plugins_Loggers_EchoLogger(); + $mailer->registerPlugin(new Swift_Plugins_LoggerPlugin($logger)); + + // Continue sending as normal + for ($lotsOfRecipients as $recipient) { + ... + + $mailer->send( ... ); + } + + // Dump the log contents + // NOTE: The EchoLogger dumps in realtime so dump() does nothing for it + echo $logger->dump(); + +Decorator Plugin +---------------- + +Often there's a need to send the same message to multiple recipients, but with +tiny variations such as the recipient's name being used inside the message +body. The Decorator plugin aims to provide a solution for allowing these small +differences. + +The decorator plugin works by intercepting the sending process of Swift +Mailer, reading the email address in the To: field and then looking up a set +of replacements for a template. + +While the use of this plugin is simple, it is probably the most commonly +misunderstood plugin due to the way in which it works. The typical mistake +users make is to try registering the plugin multiple times (once for each +recipient) -- inside a loop for example. This is incorrect. + +The Decorator plugin should be registered just once, but containing the list +of all recipients prior to sending. It will use this list of recipients to +find the required replacements during sending. + +Using the Decorator Plugin +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To use the Decorator plugin, simply create an associative array of replacements +based on email addresses and then use the mailer's ``registerPlugin()`` method +to add the plugin. + +First create an associative array of replacements based on the email addresses +you'll be sending the message to. + +.. note:: + + The replacements array becomes a 2-dimensional array whose keys are the + email addresses and whose values are an associative array of replacements + for that email address. The curly braces used in this example can be any + type of syntax you choose, provided they match the placeholders in your + email template. + + .. code-block:: php + + $replacements = array(); + foreach ($users as $user) { + $replacements[$user['email']] = array( + '{username}'=>$user['username'], + '{password}'=>$user['password'] + ); + } + +Now create an instance of the Decorator plugin using this array of replacements +and then register it with the Mailer. Do this only once! + +.. code-block:: php + + $decorator = new Swift_Plugins_DecoratorPlugin($replacements); + + $mailer->registerPlugin($decorator); + +When you create your message, replace elements in the body (and/or the subject +line) with your placeholders. + +.. code-block:: php + + $message = Swift_Message::newInstance() + ->setSubject('Important notice for {username}') + ->setBody( + "Hello {username}, we have reset your password to {password}\n" . + "Please log in and change it at your earliest convenience." + ) + ; + + foreach ($users as $user) { + $message->addTo($user['email']); + } + +When you send this message to each of your recipients listed in your +``$replacements`` array they will receive a message customized for just +themselves. For example, the message used above when received may appear like +this to one user: + +.. code-block:: text + + Subject: Important notice for smilingsunshine2009 + + Hello smilingsunshine2009, we have reset your password to rainyDays + Please log in and change it at your earliest convenience. + +While another use may receive the message as: + +.. code-block:: text + + Subject: Important notice for billy-bo-bob + + Hello billy-bo-bob, we have reset your password to dancingOctopus + Please log in and change it at your earliest convenience. + +While the decorator plugin provides a means to solve this problem, there are +various ways you could tackle this problem without the need for a plugin. +We're trying to come up with a better way ourselves and while we have several +(obvious) ideas we don't quite have the perfect solution to go ahead and +implement it. Watch this space. + +Providing Your Own Replacements Lookup for the Decorator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Filling an array with replacements may not be the best solution for providing +replacement information to the decorator. If you have a more elegant algorithm +that performs replacement lookups on-the-fly you may provide your own +implementation. + +Providing your own replacements lookup implementation for the Decorator is +simply a matter of passing an instance of ``Swift_Plugins_Decorator_Replacements`` to the decorator plugin's constructor, +rather than passing in an array. + +The Replacements interface is very simple to implement since it has just one +method: ``getReplacementsFor($address)``. + +Imagine you want to look up replacements from a database on-the-fly, you might +provide an implementation that does this. You need to create a small class. + +.. code-block:: php + + class DbReplacements implements Swift_Plugins_Decorator_Replacements { + public function getReplacementsFor($address) { + $sql = sprintf( + "SELECT * FROM user WHERE email = '%s'", + mysql_real_escape_string($address) + ); + + $result = mysql_query($sql); + + if ($row = mysql_fetch_assoc($result)) { + return array( + '{username}'=>$row['username'], + '{password}'=>$row['password'] + ); + } + } + } + +Now all you need to do is pass an instance of your class into the Decorator +plugin's constructor instead of passing an array. + +.. code-block:: php + + $decorator = new Swift_Plugins_DecoratorPlugin(new DbReplacements()); + + $mailer->registerPlugin($decorator); + +For each message sent, the plugin will call your class' ``getReplacementsFor()`` +method to find the array of replacements it needs. + +.. note:: + + If your lookup algorithm is case sensitive, you should transform the + ``$address`` argument as appropriate -- for example by passing it + through ``strtolower()``. diff --git a/vendor/swiftmailer/swiftmailer/doc/sending.rst b/vendor/swiftmailer/swiftmailer/doc/sending.rst new file mode 100644 index 0000000..a850fb3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/doc/sending.rst @@ -0,0 +1,607 @@ +Sending Messages +================ + +Quick Reference for Sending a Message +------------------------------------- + +Sending a message is very straightforward. You create a Transport, use it to +create the Mailer, then you use the Mailer to send the message. + +To send a Message: + +* Create a Transport from one of the provided Transports -- + ``Swift_SmtpTransport``, ``Swift_SendmailTransport``, ``Swift_MailTransport`` + or one of the aggregate Transports. + +* Create an instance of the ``Swift_Mailer`` class, using the Transport as + it's constructor parameter. + +* Create a Message. + +* Send the message via the ``send()`` method on the Mailer object. + +.. caution:: + + The ``Swift_SmtpTransport`` and ``Swift_SendmailTransport`` transports use + ``proc_*`` PHP functions, which might not be available on your PHP + installation. You can easily check if that's the case by running the + following PHP script: ``setUsername('your username') + ->setPassword('your password') + ; + + /* + You could alternatively use a different transport such as Sendmail or Mail: + + // Sendmail + $transport = Swift_SendmailTransport::newInstance('/usr/sbin/sendmail -bs'); + + // Mail + $transport = Swift_MailTransport::newInstance(); + */ + + // Create the Mailer using your created Transport + $mailer = Swift_Mailer::newInstance($transport); + + // Create a message + $message = Swift_Message::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself') + ; + + // Send the message + $result = $mailer->send($message); + +Transport Types +~~~~~~~~~~~~~~~ + +A Transport is the component which actually does the sending. You need to +provide a Transport object to the Mailer class and there are several possible +options. + +Typically you will not need to know how a Transport works under-the-surface, +you will only need to know how to create an instance of one, and which one to +use for your environment. + +The SMTP Transport +.................. + +The SMTP Transport sends messages over the (standardized) Simple Message +Transfer Protocol. It can deal with encryption and authentication. + +The SMTP Transport, ``Swift_SmtpTransport`` is without doubt the most commonly +used Transport because it will work on 99% of web servers (I just made that +number up, but you get the idea). All the server needs is the ability to +connect to a remote (or even local) SMTP server on the correct port number +(usually 25). + +SMTP servers often require users to authenticate with a username and password +before any mail can be sent to other domains. This is easily achieved using +Swift Mailer with the SMTP Transport. + +SMTP is a protocol -- in other words it's a "way" of communicating a job +to be done (i.e. sending a message). The SMTP protocol is the fundamental +basis on which messages are delivered all over the internet 7 days a week, 365 +days a year. For this reason it's the most "direct" method of sending messages +you can use and it's the one that will give you the most power and feedback +(such as delivery failures) when using Swift Mailer. + +Because SMTP is generally run as a remote service (i.e. you connect to it over +the network/internet) it's extremely portable from server-to-server. You can +easily store the SMTP server address and port number in a configuration file +within your application and adjust the settings accordingly if the code is +moved or if the SMTP server is changed. + +Some SMTP servers -- Google for example -- use encryption for security reasons. +Swift Mailer supports using both SSL and TLS encryption settings. + +Using the SMTP Transport +^^^^^^^^^^^^^^^^^^^^^^^^ + +The SMTP Transport is easy to use. Most configuration options can be set with +the constructor. + +To use the SMTP Transport you need to know which SMTP server your code needs +to connect to. Ask your web host if you're not sure. Lots of people ask me who +to connect to -- I really can't answer that since it's a setting that's +extremely specific to your hosting environment. + +To use the SMTP Transport: + +* Call ``Swift_SmtpTransport::newInstance()`` with the SMTP server name and + optionally with a port number (defaults to 25). + +* Use the returned object to create the Mailer. + +A connection to the SMTP server will be established upon the first call to +``send()``. + +.. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Transport + $transport = Swift_SmtpTransport::newInstance('smtp.example.org', 25); + + // Create the Mailer using your created Transport + $mailer = Swift_Mailer::newInstance($transport); + + /* + It's also possible to use multiple method calls + + $transport = Swift_SmtpTransport::newInstance() + ->setHost('smtp.example.org') + ->setPort(25) + ; + */ + +Encrypted SMTP +^^^^^^^^^^^^^^ + +You can use SSL or TLS encryption with the SMTP Transport by specifying it as +a parameter or with a method call. + +To use encryption with the SMTP Transport: + +* Pass the encryption setting as a third parameter to + ``Swift_SmtpTransport::newInstance()``; or + +* Call the ``setEncryption()`` method on the Transport. + +A connection to the SMTP server will be established upon the first call to +``send()``. The connection will be initiated with the correct encryption +settings. + +.. note:: + + For SSL or TLS encryption to work your PHP installation must have + appropriate OpenSSL transports wrappers. You can check if "tls" and/or + "ssl" are present in your PHP installation by using the PHP function + ``stream_get_transports()`` + + .. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Transport + $transport = Swift_SmtpTransport::newInstance('smtp.example.org', 587, 'ssl'); + + // Create the Mailer using your created Transport + $mailer = Swift_Mailer::newInstance($transport); + + /* + It's also possible to use multiple method calls + + $transport = Swift_SmtpTransport::newInstance() + ->setHost('smtp.example.org') + ->setPort(587) + ->setEncryption('ssl') + ; + */ + +SMTP with a Username and Password +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Some servers require authentication. You can provide a username and password +with ``setUsername()`` and ``setPassword()`` methods. + +To use a username and password with the SMTP Transport: + +* Create the Transport with ``Swift_SmtpTransport::newInstance()``. + +* Call the ``setUsername()`` and ``setPassword()`` methods on the Transport. + +Your username and password will be used to authenticate upon first connect +when ``send()`` are first used on the Mailer. + +If authentication fails, an Exception of type ``Swift_TransportException`` will +be thrown. + +.. note:: + + If you need to know early whether or not authentication has failed and an + Exception is going to be thrown, call the ``start()`` method on the + created Transport. + + .. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Transport the call setUsername() and setPassword() + $transport = Swift_SmtpTransport::newInstance('smtp.example.org', 25) + ->setUsername('username') + ->setPassword('password') + ; + + // Create the Mailer using your created Transport + $mailer = Swift_Mailer::newInstance($transport); + +The Sendmail Transport +...................... + +The Sendmail Transport sends messages by communicating with a locally +installed MTA -- such as ``sendmail``. + +The Sendmail Transport, ``Swift_SendmailTransport`` does not directly connect to +any remote services. It is designed for Linux servers that have ``sendmail`` +installed. The Transport starts a local ``sendmail`` process and sends messages +to it. Usually the ``sendmail`` process will respond quickly as it spools your +messages to disk before sending them. + +The Transport is named the Sendmail Transport for historical reasons +(``sendmail`` was the "standard" UNIX tool for sending e-mail for years). It +will send messages using other transfer agents such as Exim or Postfix despite +its name, provided they have the relevant sendmail wrappers so that they can be +started with the correct command-line flags. + +It's a common misconception that because the Sendmail Transport returns a +result very quickly it must therefore deliver messages to recipients quickly +-- this is not true. It's not slow by any means, but it's certainly not +faster than SMTP when it comes to getting messages to the intended recipients. +This is because sendmail itself sends the messages over SMTP once they have +been quickly spooled to disk. + +The Sendmail Transport has the potential to be just as smart of the SMTP +Transport when it comes to notifying Swift Mailer about which recipients were +rejected, but in reality the majority of locally installed ``sendmail`` +instances are not configured well enough to provide any useful feedback. As such +Swift Mailer may report successful deliveries where they did in fact fail before +they even left your server. + +You can run the Sendmail Transport in two different modes specified by command +line flags: + +* "``-bs``" runs in SMTP mode so theoretically it will act like the SMTP + Transport + +* "``-t``" runs in piped mode with no feedback, but theoretically faster, + though not advised + +You can think of the Sendmail Transport as a sort of asynchronous SMTP Transport +-- though if you have problems with delivery failures you should try using the +SMTP Transport instead. Swift Mailer isn't doing the work here, it's simply +passing the work to somebody else (i.e. ``sendmail``). + +Using the Sendmail Transport +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To use the Sendmail Transport you simply need to call +``Swift_SendmailTransport::newInstance()`` with the command as a parameter. + +To use the Sendmail Transport you need to know where ``sendmail`` or another MTA +exists on the server. Swift Mailer uses a default value of +``/usr/sbin/sendmail``, which should work on most systems. + +You specify the entire command as a parameter (i.e. including the command line +flags). Swift Mailer supports operational modes of "``-bs``" (default) and +"``-t``". + +.. note:: + + If you run sendmail in "``-t``" mode you will get no feedback as to whether + or not sending has succeeded. Use "``-bs``" unless you have a reason not to. + +To use the Sendmail Transport: + +* Call ``Swift_SendmailTransport::newInstance()`` with the command, including + the correct command line flags. The default is to use ``/usr/sbin/sendmail + -bs`` if this is not specified. + +* Use the returned object to create the Mailer. + +A sendmail process will be started upon the first call to ``send()``. If the +process cannot be started successfully an Exception of type +``Swift_TransportException`` will be thrown. + +.. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Transport + $transport = Swift_SendmailTransport::newInstance('/usr/sbin/exim -bs'); + + // Create the Mailer using your created Transport + $mailer = Swift_Mailer::newInstance($transport); + +The Mail Transport +.................. + +The Mail Transport sends messages by delegating to PHP's internal +``mail()`` function. + +In my experience -- and others' -- the ``mail()`` function is not particularly +predictable, or helpful. + +Quite notably, the ``mail()`` function behaves entirely differently between +Linux and Windows servers. On linux it uses ``sendmail``, but on Windows it uses +SMTP. + +In order for the ``mail()`` function to even work at all ``php.ini`` needs to be +configured correctly, specifying the location of sendmail or of an SMTP server. + +The problem with ``mail()`` is that it "tries" to simplify things to the point +that it actually makes things more complex due to poor interface design. The +developers of Swift Mailer have gone to a lot of effort to make the Mail +Transport work with a reasonable degree of consistency. + +Serious drawbacks when using this Transport are: + +* Unpredictable message headers + +* Lack of feedback regarding delivery failures + +* Lack of support for several plugins that require real-time delivery feedback + +It's a last resort, and we say that with a passion! + +Using the Mail Transport +^^^^^^^^^^^^^^^^^^^^^^^^ + +To use the Mail Transport you simply need to call +``Swift_MailTransport::newInstance()``. It's unlikely you'll need to configure +the Transport. + +To use the Mail Transport: + +* Call ``Swift_MailTransport::newInstance()``. + +* Use the returned object to create the Mailer. + +Messages will be sent using the ``mail()`` function. + +.. note:: + + The ``mail()`` function can take a ``$additional_parameters`` parameter. + Swift Mailer sets this to "``-f%s``" by default, where the "%s" is + substituted with the address of the sender (via a ``sprintf()``) at send + time. You may override this default by passing an argument to + ``newInstance()``. + + .. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Transport + $transport = Swift_MailTransport::newInstance(); + + // Create the Mailer using your created Transport + $mailer = Swift_Mailer::newInstance($transport); + +Available Methods for Sending Messages +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Mailer class offers two methods for sending Messages -- ``send()``. +Each behaves in a slightly different way. + +When a message is sent in Swift Mailer, the Mailer class communicates with +whichever Transport class you have chosen to use. + +Each recipient in the message should either be accepted or rejected by the +Transport. For example, if the domain name on the email address is not +reachable the SMTP Transport may reject the address because it cannot process +it. Whichever method you use -- ``send()`` -- Swift Mailer will return +an integer indicating the number of accepted recipients. + +.. note:: + + It's possible to find out which recipients were rejected -- we'll cover that + later in this chapter. + +Using the ``send()`` Method +........................... + +The ``send()`` method of the ``Swift_Mailer`` class sends a message using +exactly the same logic as your Desktop mail client would use. Just pass it a +Message and get a result. + +To send a Message with ``send()``: + +* Create a Transport from one of the provided Transports -- + ``Swift_SmtpTransport``, ``Swift_SendmailTransport``, + ``Swift_MailTransport`` or one of the aggregate Transports. + +* Create an instance of the ``Swift_Mailer`` class, using the Transport as + it's constructor parameter. + +* Create a Message. + +* Send the message via the ``send()`` method on the Mailer object. + +The message will be sent just like it would be sent if you used your mail +client. An integer is returned which includes the number of successful +recipients. If none of the recipients could be sent to then zero will be +returned, which equates to a boolean ``false``. If you set two +``To:`` recipients and three ``Bcc:`` recipients in the message and all of the +recipients are delivered to successfully then the value 5 will be returned. + +.. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Transport + $transport = Swift_SmtpTransport::newInstance('localhost', 25); + + // Create the Mailer using your created Transport + $mailer = Swift_Mailer::newInstance($transport); + + // Create a message + $message = Swift_Message::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself') + ; + + // Send the message + $numSent = $mailer->send($message); + + printf("Sent %d messages\n", $numSent); + + /* Note that often that only the boolean equivalent of the + return value is of concern (zero indicates FALSE) + + if ($mailer->send($message)) + { + echo "Sent\n"; + } + else + { + echo "Failed\n"; + } + + */ + +Sending Emails in Batch +....................... + +If you want to send a separate message to each recipient so that only their +own address shows up in the ``To:`` field, follow the following recipe: + +* Create a Transport from one of the provided Transports -- + ``Swift_SmtpTransport``, ``Swift_SendmailTransport``, + ``Swift_MailTransport`` or one of the aggregate Transports. + +* Create an instance of the ``Swift_Mailer`` class, using the Transport as + it's constructor parameter. + +* Create a Message. + +* Iterate over the recipients and send message via the ``send()`` method on + the Mailer object. + +Each recipient of the messages receives a different copy with only their own +email address on the ``To:`` field. + +Make sure to add only valid email addresses as recipients. If you try to add an +invalid email address with ``setTo()``, ``setCc()`` or ``setBcc()``, Swift +Mailer will throw a ``Swift_RfcComplianceException``. + +If you add recipients automatically based on a data source that may contain +invalid email addresses, you can prevent possible exceptions by validating the +addresses using ``Swift_Validate::email($email)`` and only adding addresses +that validate. Another way would be to wrap your ``setTo()``, ``setCc()`` and +``setBcc()`` calls in a try-catch block and handle the +``Swift_RfcComplianceException`` in the catch block. + +Handling invalid addresses properly is especially important when sending emails +in large batches since a single invalid address might cause an unhandled +exception and stop the execution or your script early. + +.. note:: + + In the following example, two emails are sent. One to each of + ``receiver@domain.org`` and ``other@domain.org``. These recipients will + not be aware of each other. + + .. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Transport + $transport = Swift_SmtpTransport::newInstance('localhost', 25); + + // Create the Mailer using your created Transport + $mailer = Swift_Mailer::newInstance($transport); + + // Create a message + $message = Swift_Message::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setBody('Here is the message itself') + ; + + // Send the message + $failedRecipients = array(); + $numSent = 0; + $to = array('receiver@domain.org', 'other@domain.org' => 'A name'); + + foreach ($to as $address => $name) + { + if (is_int($address)) { + $message->setTo($name); + } else { + $message->setTo(array($address => $name)); + } + + $numSent += $mailer->send($message, $failedRecipients); + } + + printf("Sent %d messages\n", $numSent); + +Finding out Rejected Addresses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It's possible to get a list of addresses that were rejected by the Transport +by using a by-reference parameter to ``send()``. + +As Swift Mailer attempts to send the message to each address given to it, if a +recipient is rejected it will be added to the array. You can pass an existing +array, otherwise one will be created by-reference. + +Collecting the list of recipients that were rejected can be useful in +circumstances where you need to "prune" a mailing list for example when some +addresses cannot be delivered to. + +Getting Failures By-reference +............................. + +Collecting delivery failures by-reference with the ``send()`` method is as +simple as passing a variable name to the method call. + +To get failed recipients by-reference: + +* Pass a by-reference variable name to the ``send()`` method of the Mailer + class. + +If the Transport rejects any of the recipients, the culprit addresses will be +added to the array provided by-reference. + +.. note:: + + If the variable name does not yet exist, it will be initialized as an + empty array and then failures will be added to that array. If the variable + already exists it will be type-cast to an array and failures will be added + to it. + + .. code-block:: php + + $mailer = Swift_Mailer::newInstance( ... ); + + $message = Swift_Message::newInstance( ... ) + ->setFrom( ... ) + ->setTo(array( + 'receiver@bad-domain.org' => 'Receiver Name', + 'other@domain.org' => 'A name', + 'other-receiver@bad-domain.org' => 'Other Name' + )) + ->setBody( ... ) + ; + + // Pass a variable name to the send() method + if (!$mailer->send($message, $failures)) + { + echo "Failures:"; + print_r($failures); + } + + /* + Failures: + Array ( + 0 => receiver@bad-domain.org, + 1 => other-receiver@bad-domain.org + ) + */ diff --git a/vendor/swiftmailer/swiftmailer/doc/uml/Encoders.graffle b/vendor/swiftmailer/swiftmailer/doc/uml/Encoders.graffle new file mode 100644 index 0000000..f895752 Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/doc/uml/Encoders.graffle differ diff --git a/vendor/swiftmailer/swiftmailer/doc/uml/Mime.graffle b/vendor/swiftmailer/swiftmailer/doc/uml/Mime.graffle new file mode 100644 index 0000000..e1e33cb Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/doc/uml/Mime.graffle differ diff --git a/vendor/swiftmailer/swiftmailer/doc/uml/Transports.graffle b/vendor/swiftmailer/swiftmailer/doc/uml/Transports.graffle new file mode 100644 index 0000000..5670e2b Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/doc/uml/Transports.graffle differ diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php new file mode 100644 index 0000000..ecaf0ef --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php @@ -0,0 +1,81 @@ +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 + ); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php new file mode 100644 index 0000000..87b6428 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php @@ -0,0 +1,183 @@ +_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 + * + * @return integer + * + * @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 = ''; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php new file mode 100644 index 0000000..5c16248 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php @@ -0,0 +1,186 @@ +_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 integer $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 integer $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(); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php new file mode 100644 index 0000000..405f8b0 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php @@ -0,0 +1,225 @@ +_path = $path; + $this->_mode = $writable ? 'w+b' : 'rb'; + + if (function_exists('get_magic_quotes_runtime') && @get_magic_quotes_runtime() == 1) { + $this->_quotes = true; + } + } + + /** + * 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 integer $length + * + * @return string + * + * @throws Swift_IoException + */ + public function read($length) + { + $fp = $this->_getReadHandle(); + if (!feof($fp)) { + if ($this->_quotes) { + ini_set('magic_quotes_runtime', 0); + } + $bytes = fread($fp, $length); + if ($this->_quotes) { + ini_set('magic_quotes_runtime', 1); + } + $this->_offset = ftell($fp); + + return $bytes; + } else { + $this->_resetReadHandle(); + + return false; + } + } + + /** + * Move the internal read pointer to $byteOffset in the stream. + * + * @param integer $byteOffset + * + * @return boolean + */ + public function setReadPointer($byteOffset) + { + if (isset($this->_reader)) { + $this->_seekReadStreamToPosition($byteOffset); + } + $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 . ']' + ); + } + if ($this->_offset <> 0) { + $this->_getReadStreamSeekableStatus(); + $this->_seekReadStreamToPosition($this->_offset); + } + } + + 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 reading */ + private function _resetReadHandle() + { + if (isset($this->_reader)) { + fclose($this->_reader); + $this->_reader = null; + } + } + + /** Check if ReadOnly Stream is seekable */ + private function _getReadStreamSeekableStatus() + { + $metas = stream_get_meta_data($this->_reader); + $this->_seekable = $metas['seekable']; + } + + /** Streams in a readOnly stream ensuring copy if needed */ + private function _seekReadStreamToPosition($offset) + { + if ($this->_seekable===null) { + $this->_getReadStreamSeekableStatus(); + } + if ($this->_seekable === false) { + $currentPos = ftell($this->_reader); + if ($currentPos<$offset) { + $toDiscard = $offset-$currentPos; + fread($this->_reader, $toDiscard); + + return; + } + $this->_copyReadStream(); + } + fseek($this->_reader, $offset, SEEK_SET); + } + + /** Copy a readOnly Stream to ensure seekability */ + private function _copyReadStream() + { + if ($tmpFile = fopen('php://temp/maxmemory:4096', 'w+b')) { + /* We have opened a php:// Stream Should work without problem */ + } elseif (function_exists('sys_get_temp_dir') && is_writable(sys_get_temp_dir()) && ($tmpFile = tmpfile())) { + /* We have opened a tmpfile */ + } else { + throw new Swift_IoException('Unable to copy the file to make it seekable, sys_temp_dir is not writable, php://memory not available'); + } + $currentPos = ftell($this->_reader); + fclose($this->_reader); + $source = fopen($this->_path, 'rb'); + if (!$source) { + throw new Swift_IoException('Unable to open file for copying [' . $this->_path . ']'); + } + fseek($tmpFile, 0, SEEK_SET); + while (!feof($source)) { + fwrite($tmpFile, fread($source, 4096)); + } + fseek($tmpFile, $currentPos, SEEK_SET); + fclose($source); + $this->_reader = $tmpFile; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php new file mode 100644 index 0000000..f35f885 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php @@ -0,0 +1,44 @@ +getPath())) === false) { + throw new Swift_IoException('Failed to get temporary file content.'); + } + + return $content; + } + + public function __destruct() + { + if (file_exists($this->getPath())) { + @unlink($this->getPath()); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader.php new file mode 100644 index 0000000..df64d8a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader.php @@ -0,0 +1,69 @@ + + */ +interface Swift_CharacterReader +{ + const MAP_TYPE_INVALID = 0x01; + const MAP_TYPE_FIXED_LEN = 0x02; + const MAP_TYPE_POSITIONS = 0x03; + + /** + * Returns the complete character map + * + * @param string $string + * @param integer $startOffset + * @param array $currentMap + * @param mixed $ignoredChars + * + * @return integer + */ + public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars); + + /** + * Returns the mapType, see constants. + * + * @return integer + */ + 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 integer[] $bytes + * @param integer $size + * + * @return integer + */ + 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 integer + */ + public function getInitialByteSize(); +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php new file mode 100644 index 0000000..49d7398 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php @@ -0,0 +1,99 @@ + + */ +class Swift_CharacterReader_GenericFixedWidthReader implements Swift_CharacterReader +{ + /** + * The number of bytes in a single character. + * + * @var integer + */ + private $_width; + + /** + * Creates a new GenericFixedWidthReader using $width bytes per character. + * + * @param integer $width + */ + public function __construct($width) + { + $this->_width = $width; + } + + /** + * Returns the complete character map. + * + * @param string $string + * @param integer $startOffset + * @param array $currentMap + * @param mixed $ignoredChars + * + * @return integer + */ + 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 the mapType. + * + * @return integer + */ + 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 + * @param integer $size + * + * @return integer + */ + 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 integer + */ + public function getInitialByteSize() + { + return $this->_width; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/UsAsciiReader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/UsAsciiReader.php new file mode 100644 index 0000000..18f3b04 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/UsAsciiReader.php @@ -0,0 +1,85 @@ +"\x07F") { // Invalid char + $currentMap[$i+$startOffset]=$string[$i]; + } + } + + return $strlen; + } + + /** + * Returns mapType + * + * @return integer 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 + * @param integer $size + * + * @return integer + */ + 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 integer + */ + public function getInitialByteSize() + { + return 1; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/Utf8Reader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/Utf8Reader.php new file mode 100644 index 0000000..dd3a60f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/Utf8Reader.php @@ -0,0 +1,181 @@ + + */ +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 character map. + * + * @param string $string + * @param integer $startOffset + * @param array $currentMap + * @param mixed $ignoredChars + * + * @return integer + */ + 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 */ + $currentMap['p'][$charPos + $foundChars] = $startOffset + $i + $size; + $i += $j - 1; + ++$foundChars; + } + } + + return $foundChars; + } + + /** + * Returns mapType. + * + * @return integer 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 + * @param integer $size + * + * @return integer + */ + 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 integer + */ + public function getInitialByteSize() + { + return 1; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory.php new file mode 100644 index 0000000..d653b81 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory.php @@ -0,0 +1,28 @@ +init(); + } + + public function __wakeup() + { + $this->init(); + } + + public function init() + { + if (count(self::$_map) > 0) { + return; + } + + $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 + self::$_map['utf-?8'] = array( + 'class' => $prefix . 'Utf8Reader', + 'constructor' => array() + ); + + //7-8 bit charsets + self::$_map['(us-)?ascii'] = $singleByte; + self::$_map['(iso|iec)-?8859-?[0-9]+'] = $singleByte; + self::$_map['windows-?125[0-9]'] = $singleByte; + self::$_map['cp-?[0-9]+'] = $singleByte; + self::$_map['ansi'] = $singleByte; + self::$_map['macintosh'] = $singleByte; + self::$_map['koi-?7'] = $singleByte; + self::$_map['koi-?8-?.+'] = $singleByte; + self::$_map['mik'] = $singleByte; + self::$_map['(cork|t1)'] = $singleByte; + self::$_map['v?iscii'] = $singleByte; + + //16 bits + self::$_map['(ucs-?2|utf-?16)'] = $doubleByte; + + //32 bits + self::$_map['(ucs-?4|utf-?32)'] = $fourBytes; + + //Fallback + self::$_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 (self::$_map as $pattern => $spec) { + $re = '/^' . $pattern . '$/D'; + if (preg_match($re, $charset)) { + if (!array_key_exists($pattern, self::$_loaded)) { + $reflector = new ReflectionClass($spec['class']); + if ($reflector->getConstructor()) { + $reader = $reflector->newInstanceArgs($spec['constructor']); + } else { + $reader = $reflector->newInstance(); + } + self::$_loaded[$pattern] = $reader; + } + + return self::$_loaded[$pattern]; + } + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream.php new file mode 100644 index 0000000..2946200 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream.php @@ -0,0 +1,91 @@ +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 integer $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 integer $length + * + * @return integer[] + */ + 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 integer $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); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/NgCharacterStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/NgCharacterStream.php new file mode 100644 index 0000000..98aabab --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/NgCharacterStream.php @@ -0,0 +1,277 @@ + + */ + +class Swift_CharacterStream_NgCharacterStream implements Swift_CharacterStream +{ + /** + * The char reader (lazy-loaded) for the current charset. + * + * @var Swift_CharacterReader + */ + private $_charReader; + + /** + * A factory for creating CharacterReader instances. + * + * @var Swift_CharacterReaderFactory + */ + private $_charReaderFactory; + + /** + * The character set this stream is using. + * + * @var string + */ + private $_charset; + + /** + * The data's stored as-is. + * + * @var string + */ + private $_datas = ''; + + /** + * Number of bytes in the stream + * + * @var integer + */ + private $_datasSize = 0; + + /** + * Map. + * + * @var mixed + */ + private $_map; + + /** + * Map Type. + * + * @var integer + */ + private $_mapType = 0; + + /** + * Number of characters in the stream. + * + * @var integer + */ + private $_charCount = 0; + + /** + * Position in the stream. + * + * @var integer + */ + private $_currentPos = 0; + + /** + * Constructor. + * + * @param Swift_CharacterReaderFactory $factory + * @param string $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 integer $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 integer $length + * + * @return integer[] + */ + 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 integer $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); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ConfigurableSpool.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ConfigurableSpool.php new file mode 100644 index 0000000..58d5275 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ConfigurableSpool.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Base class for Spools (implements time and message limits). + * + * @package Swift + * @author Fabien Potencier + */ +abstract class Swift_ConfigurableSpool implements Swift_Spool +{ + /** The maximum number of messages to send per flush */ + private $_message_limit; + + /** The time limit per flush */ + private $_time_limit; + + /** + * Sets the maximum number of messages to send per flush. + * + * @param integer $limit + */ + public function setMessageLimit($limit) + { + $this->_message_limit = (int) $limit; + } + + /** + * Gets the maximum number of messages to send per flush. + * + * @return integer The limit + */ + public function getMessageLimit() + { + return $this->_message_limit; + } + + /** + * Sets the time limit (in seconds) per flush. + * + * @param integer $limit The limit + */ + public function setTimeLimit($limit) + { + $this->_time_limit = (int) $limit; + } + + /** + * Gets the time limit (in seconds) per flush. + * + * @return integer The limit + */ + public function getTimeLimit() + { + return $this->_time_limit; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php new file mode 100644 index 0000000..b717861 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php @@ -0,0 +1,373 @@ +_store); + } + + /** + * Test if an item is registered in this container with the given name. + * + * @see register() + * + * @param string $itemName + * + * @return boolean + */ + public function has($itemName) + { + return array_key_exists($itemName, $this->_store) + && isset($this->_store[$itemName]['lookupType']); + } + + /** + * Lookup the item with the given $itemName. + * + * @see register() + * + * @param string $itemName + * + * @return mixed + * + * @throws Swift_DependencyException If the dependency is not found + */ + 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. + * @see asNewInstanceOf(), asSharedInstanceOf(), asValue() + * + * @param string $itemName + * + * @return Swift_DependencyContainer + */ + 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()}. + * + * @see withDependencies(), addConstructorValue(), addConstructorLookup() + * + * @param string $className + * + * @return Swift_DependencyContainer + */ + 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. + * + * @see addConstructorValue(), addConstructorLookup() + * + * @param array $lookups + * + * @return Swift_DependencyContainer + */ + 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. + * + * @see withDependencies(), addConstructorLookup() + * + * @param mixed $value + * + * @return Swift_DependencyContainer + */ + 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. + * + * @see withDependencies(), addConstructorValue() + * + * @param string $lookup + * + * @return Swift_DependencyContainer + */ + 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); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyException.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyException.php new file mode 100644 index 0000000..b3f0170 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyException.php @@ -0,0 +1,28 @@ +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) + ); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder.php new file mode 100644 index 0000000..53e88b8 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder.php @@ -0,0 +1,29 @@ += $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) + { + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/QpEncoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/QpEncoder.php new file mode 100644 index 0000000..61cf31b --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/QpEncoder.php @@ -0,0 +1,286 @@ + '=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' + ); + + protected static $_safeMapShare = array(); + + /** + * A map of non-encoded ascii characters. + * + * @var string[] + */ + protected $_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 (!isset(self::$_safeMapShare[$this->getSafeMapShareId()])) { + $this->initSafeMap(); + self::$_safeMapShare[$this->getSafeMapShareId()] = $this->_safeMap; + } else { + $this->_safeMap = self::$_safeMapShare[$this->getSafeMapShareId()]; + } + $this->_filter = $filter; + } + + public function __sleep() + { + return array('_charStream', '_filter'); + } + + public function __wakeup() + { + if (!isset(self::$_safeMapShare[$this->getSafeMapShareId()])) { + $this->initSafeMap(); + self::$_safeMapShare[$this->getSafeMapShareId()] = $this->_safeMap; + } else { + $this->_safeMap = self::$_safeMapShare[$this->getSafeMapShareId()]; + } + } + + protected function getSafeMapShareId() + { + return get_class($this); + } + + protected function initSafeMap() + { + foreach (array_merge( + array(0x09, 0x20), range(0x21, 0x3C), range(0x3E, 0x7E)) as $byte) + { + $this->_safeMap[$byte] = chr($byte); + } + } + + /** + * 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 integer $firstLineOffset, optional + * @param integer $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 integer[] $bytes + * @param integer $size + * + * @return string + */ + protected function _encodeByteSequence(array $bytes, &$size) + { + $ret = ''; + $size=0; + foreach ($bytes as $b) { + if (isset($this->_safeMap[$b])) { + $ret .= $this->_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 integer $size number of bytes to read + * + * @return integer[] + */ + 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 + */ + 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; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Rfc2231Encoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Rfc2231Encoder.php new file mode 100644 index 0000000..37e30c1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Rfc2231Encoder.php @@ -0,0 +1,86 @@ +_charStream = $charStream; + } + + /** + * Takes an unencoded string and produces a string encoded according to + * RFC 2231 from it. + * + * @param string $string + * @param integer $firstLineOffset + * @param integer $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); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoding.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoding.php new file mode 100644 index 0000000..9639194 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoding.php @@ -0,0 +1,66 @@ +lookup($key); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandEvent.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandEvent.php new file mode 100644 index 0000000..fa4f444 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandEvent.php @@ -0,0 +1,67 @@ +_command = $command; + $this->_successCodes = $successCodes; + } + + /** + * Get the command which was sent to the server. + * + * @return string + */ + public function getCommand() + { + return $this->_command; + } + + /** + * Get the numeric response codes which indicate success for this command. + * + * @return integer[] + */ + public function getSuccessCodes() + { + return $this->_successCodes; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandListener.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandListener.php new file mode 100644 index 0000000..6800904 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandListener.php @@ -0,0 +1,26 @@ +_source = $source; + } + + /** + * Get the source object of this event. + * + * @return object + */ + public function getSource() + { + return $this->_source; + } + + /** + * Prevent this Event from bubbling any further up the stack. + * + * @param boolean $cancel, optional + */ + public function cancelBubble($cancel = true) + { + $this->_bubbleCancelled = $cancel; + } + + /** + * Returns true if this Event will not bubble any further up the stack. + * + * @return boolean + */ + public function bubbleCancelled() + { + return $this->_bubbleCancelled; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseEvent.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseEvent.php new file mode 100644 index 0000000..6b9117c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseEvent.php @@ -0,0 +1,68 @@ +_response = $response; + $this->_valid = $valid; + } + + /** + * Get the response which was received from the server. + * + * @return string + */ + public function getResponse() + { + return $this->_response; + } + + /** + * Get the success status of this Event. + * + * @return boolean + */ + public function isValid() + { + return $this->_valid; + } + +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseListener.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseListener.php new file mode 100644 index 0000000..a39ba43 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseListener.php @@ -0,0 +1,26 @@ +_message = $message; + $this->_result = self::RESULT_PENDING; + } + + /** + * Get the Transport used to send the Message. + * + * @return Swift_Transport + */ + public function getTransport() + { + return $this->getSource(); + } + + /** + * Get the Message being sent. + * + * @return Swift_Mime_Message + */ + public function getMessage() + { + return $this->_message; + } + + /** + * Set the array of addresses that failed in sending. + * + * @param array $recipients + */ + public function setFailedRecipients($recipients) + { + $this->_failedRecipients = $recipients; + } + + /** + * Get an recipient addresses which were not accepted for delivery. + * + * @return string[] + */ + public function getFailedRecipients() + { + return $this->_failedRecipients; + } + + /** + * Set the result of sending. + * + * @param integer $result + */ + public function setResult($result) + { + $this->_result = $result; + } + + /** + * Get the result of this Event. + * + * The return value is a bitmask from + * {@see RESULT_PENDING, RESULT_SUCCESS, RESULT_TENTATIVE, RESULT_FAILED} + * + * @return integer + */ + public function getResult() + { + return $this->_result; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendListener.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendListener.php new file mode 100644 index 0000000..bc914f5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendListener.php @@ -0,0 +1,33 @@ +_eventMap = array( + 'Swift_Events_CommandEvent' => 'Swift_Events_CommandListener', + 'Swift_Events_ResponseEvent' => 'Swift_Events_ResponseListener', + 'Swift_Events_SendEvent' => 'Swift_Events_SendListener', + 'Swift_Events_TransportChangeEvent' => 'Swift_Events_TransportChangeListener', + 'Swift_Events_TransportExceptionEvent' => 'Swift_Events_TransportExceptionListener' + ); + } + + /** + * Create a new SendEvent for $source and $message. + * + * @param Swift_Transport $source + * @param Swift_Mime_Message + * + * @return Swift_Events_SendEvent + */ + public function createSendEvent(Swift_Transport $source, Swift_Mime_Message $message) + { + return new Swift_Events_SendEvent($source, $message); + } + + /** + * Create a new CommandEvent for $source and $command. + * + * @param Swift_Transport $source + * @param string $command That will be executed + * @param array $successCodes That are needed + * + * @return Swift_Events_CommandEvent + */ + public function createCommandEvent(Swift_Transport $source, $command, $successCodes = array()) + { + return new Swift_Events_CommandEvent($source, $command, $successCodes); + } + + /** + * Create a new ResponseEvent for $source and $response. + * + * @param Swift_Transport $source + * @param string $response + * @param boolean $valid If the response is valid + * + * @return Swift_Events_ResponseEvent + */ + public function createResponseEvent(Swift_Transport $source, $response, $valid) + { + return new Swift_Events_ResponseEvent($source, $response, $valid); + } + + /** + * Create a new TransportChangeEvent for $source. + * + * @param Swift_Transport $source + * + * @return Swift_Events_TransportChangeEvent + */ + public function createTransportChangeEvent(Swift_Transport $source) + { + return new Swift_Events_TransportChangeEvent($source); + } + + /** + * Create a new TransportExceptionEvent for $source. + * + * @param Swift_Transport $source + * @param Swift_TransportException $ex + * + * @return Swift_Events_TransportExceptionEvent + */ + public function createTransportExceptionEvent(Swift_Transport $source, Swift_TransportException $ex) + { + return new Swift_Events_TransportExceptionEvent($source, $ex); + } + + /** + * Bind an event listener to this dispatcher. + * + * @param Swift_Events_EventListener $listener + */ + public function bindEventListener(Swift_Events_EventListener $listener) + { + foreach ($this->_listeners as $l) { + //Already loaded + if ($l === $listener) { + return; + } + } + $this->_listeners[] = $listener; + } + + /** + * Dispatch the given Event to all suitable listeners. + * + * @param Swift_Events_EventObject $evt + * @param string $target method + */ + public function dispatchEvent(Swift_Events_EventObject $evt, $target) + { + $this->_prepareBubbleQueue($evt); + $this->_bubble($evt, $target); + } + + // -- Private methods + + /** Queue listeners on a stack ready for $evt to be bubbled up it */ + private function _prepareBubbleQueue(Swift_Events_EventObject $evt) + { + $this->_bubbleQueue = array(); + $evtClass = get_class($evt); + foreach ($this->_listeners as $listener) { + if (array_key_exists($evtClass, $this->_eventMap) + && ($listener instanceof $this->_eventMap[$evtClass])) + { + $this->_bubbleQueue[] = $listener; + } + } + } + + /** Bubble $evt up the stack calling $target() on each listener */ + private function _bubble(Swift_Events_EventObject $evt, $target) + { + if (!$evt->bubbleCancelled() && $listener = array_shift($this->_bubbleQueue)) { + $listener->$target($evt); + $this->_bubble($evt, $target); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeEvent.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeEvent.php new file mode 100644 index 0000000..d8b5316 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeEvent.php @@ -0,0 +1,29 @@ +getSource(); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeListener.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeListener.php new file mode 100644 index 0000000..1555037 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeListener.php @@ -0,0 +1,47 @@ +_exception = $ex; + } + + /** + * Get the TransportException thrown. + * + * @return Swift_TransportException + */ + public function getException() + { + return $this->_exception; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionListener.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionListener.php new file mode 100644 index 0000000..709abda --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionListener.php @@ -0,0 +1,26 @@ +createDependenciesFor('transport.failover') + ); + + $this->setTransports($transports); + } + + /** + * Create a new FailoverTransport instance. + * + * @param Swift_Transport[] $transports + * + * @return Swift_FailoverTransport + */ + public static function newInstance($transports = array()) + { + return new self($transports); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileSpool.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileSpool.php new file mode 100644 index 0000000..0f01f40 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileSpool.php @@ -0,0 +1,209 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Stores Messages on the filesystem. + * + * @package Swift + * @author Fabien Potencier + * @author Xavier De Cock + */ +class Swift_FileSpool extends Swift_ConfigurableSpool +{ + /** The spool directory */ + private $_path; + + /** + * File WriteRetry Limit + * + * @var int + */ + private $_retryLimit=10; + + /** + * Create a new FileSpool. + * + * @param string $path + * + * @throws Swift_IoException + */ + public function __construct($path) + { + $this->_path = $path; + + if (!file_exists($this->_path)) { + if (!mkdir($this->_path, 0777, true)) { + throw new Swift_IoException('Unable to create Path ['.$this->_path.']'); + } + } + } + + /** + * Tests if this Spool mechanism has started. + * + * @return boolean + */ + public function isStarted() + { + return true; + } + + /** + * Starts this Spool mechanism. + */ + public function start() + { + } + + /** + * Stops this Spool mechanism. + */ + public function stop() + { + } + + /** + * Allow to manage the enqueuing retry limit. + * + * Default, is ten and allows over 64^20 different fileNames + * + * @param integer $limit + */ + public function setRetryLimit($limit) + { + $this->_retryLimit=$limit; + } + + /** + * Queues a message. + * + * @param Swift_Mime_Message $message The message to store + * + * @return boolean + * + * @throws Swift_IoException + */ + public function queueMessage(Swift_Mime_Message $message) + { + $ser = serialize($message); + $fileName = $this->_path . '/' . $this->getRandomString(10); + for ($i = 0; $i < $this->_retryLimit; ++$i) { + /* We try an exclusive creation of the file. This is an atomic operation, it avoid locking mechanism */ + $fp = @fopen($fileName . '.message', 'x'); + if (false !== $fp) { + if (false === fwrite($fp, $ser)) { + return false; + } + + return fclose($fp); + } else { + /* The file already exists, we try a longer fileName */ + $fileName .= $this->getRandomString(1); + } + } + + throw new Swift_IoException('Unable to create a file for enqueuing Message'); + } + + /** + * Execute a recovery if for any reason a process is sending for too long. + * + * @param integer $timeout in second Defaults is for very slow smtp responses + */ + public function recover($timeout = 900) + { + foreach (new DirectoryIterator($this->_path) as $file) { + $file = $file->getRealPath(); + + if (substr($file, - 16) == '.message.sending') { + $lockedtime = filectime($file); + if ((time() - $lockedtime) > $timeout) { + rename($file, substr($file, 0, - 8)); + } + } + } + } + + /** + * Sends messages using the given transport instance. + * + * @param Swift_Transport $transport A transport instance + * @param string[] $failedRecipients An array of failures by-reference + * + * @return integer The number of sent e-mail's + */ + public function flushQueue(Swift_Transport $transport, &$failedRecipients = null) + { + $directoryIterator = new DirectoryIterator($this->_path); + + /* Start the transport only if there are queued files to send */ + if (!$transport->isStarted()) { + foreach ($directoryIterator as $file) { + if (substr($file->getRealPath(), -8) == '.message') { + $transport->start(); + break; + } + } + } + + $failedRecipients = (array) $failedRecipients; + $count = 0; + $time = time(); + foreach ($directoryIterator as $file) { + $file = $file->getRealPath(); + + if (substr($file, -8) != '.message') { + continue; + } + + /* We try a rename, it's an atomic operation, and avoid locking the file */ + if (rename($file, $file.'.sending')) { + $message = unserialize(file_get_contents($file.'.sending')); + + $count += $transport->send($message, $failedRecipients); + + unlink($file.'.sending'); + } else { + /* This message has just been catched by another process */ + continue; + } + + if ($this->getMessageLimit() && $count >= $this->getMessageLimit()) { + break; + } + + if ($this->getTimeLimit() && (time() - $time) >= $this->getTimeLimit()) { + break; + } + } + + return $count; + } + + /** + * Returns a random string needed to generate a fileName for the queue. + * + * @param integer $count + * + * @return string + */ + protected function getRandomString($count) + { + // This string MUST stay FS safe, avoid special chars + $base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-."; + $ret = ''; + $strlen = strlen($base); + for ($i = 0; $i < $count; ++$i) { + $ret .= $base[((int) rand(0, $strlen - 1))]; + } + + return $ret; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileStream.php new file mode 100644 index 0000000..567633e --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileStream.php @@ -0,0 +1,26 @@ +setFile( + new Swift_ByteStream_FileByteStream($path) + ); + + return $image; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/InputByteStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/InputByteStream.php new file mode 100644 index 0000000..ae81e5d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/InputByteStream.php @@ -0,0 +1,77 @@ +_stream = $stream; + } + + /** + * Set a string into the cache under $itemKey for the namespace $nsKey. + * + * @see MODE_WRITE, MODE_APPEND + * + * @param string $nsKey + * @param string $itemKey + * @param string $string + * @param integer $mode + */ + public function setString($nsKey, $itemKey, $string, $mode) + { + $this->_prepareCache($nsKey); + switch ($mode) { + case self::MODE_WRITE: + $this->_contents[$nsKey][$itemKey] = $string; + break; + case self::MODE_APPEND: + if (!$this->hasKey($nsKey, $itemKey)) { + $this->_contents[$nsKey][$itemKey] = ''; + } + $this->_contents[$nsKey][$itemKey] .= $string; + break; + default: + throw new Swift_SwiftException( + 'Invalid mode [' . $mode . '] used to set nsKey='. + $nsKey . ', itemKey=' . $itemKey + ); + } + } + + /** + * Set a ByteStream into the cache under $itemKey for the namespace $nsKey. + * + * @see MODE_WRITE, MODE_APPEND + * + * @param string $nsKey + * @param string $itemKey + * @param Swift_OutputByteStream $os + * @param integer $mode + */ + public function importFromByteStream($nsKey, $itemKey, Swift_OutputByteStream $os, $mode) + { + $this->_prepareCache($nsKey); + switch ($mode) { + case self::MODE_WRITE: + $this->clearKey($nsKey, $itemKey); + case self::MODE_APPEND: + if (!$this->hasKey($nsKey, $itemKey)) { + $this->_contents[$nsKey][$itemKey] = ''; + } + while (false !== $bytes = $os->read(8192)) { + $this->_contents[$nsKey][$itemKey] .= $bytes; + } + break; + default: + throw new Swift_SwiftException( + 'Invalid mode [' . $mode . '] used to set nsKey='. + $nsKey . ', itemKey=' . $itemKey + ); + } + } + + /** + * Provides a ByteStream which when written to, writes data to $itemKey. + * + * NOTE: The stream will always write in append mode. + * + * @param string $nsKey + * @param string $itemKey + * @param Swift_InputByteStream $writeThrough + * + * @return Swift_InputByteStream + */ + public function getInputByteStream($nsKey, $itemKey, Swift_InputByteStream $writeThrough = null) + { + $is = clone $this->_stream; + $is->setKeyCache($this); + $is->setNsKey($nsKey); + $is->setItemKey($itemKey); + if (isset($writeThrough)) { + $is->setWriteThroughStream($writeThrough); + } + + return $is; + } + + /** + * Get data back out of the cache as a string. + * + * @param string $nsKey + * @param string $itemKey + * + * @return string + */ + public function getString($nsKey, $itemKey) + { + $this->_prepareCache($nsKey); + if ($this->hasKey($nsKey, $itemKey)) { + return $this->_contents[$nsKey][$itemKey]; + } + } + + /** + * Get data back out of the cache as a ByteStream. + * + * @param string $nsKey + * @param string $itemKey + * @param Swift_InputByteStream $is to write the data to + */ + public function exportToByteStream($nsKey, $itemKey, Swift_InputByteStream $is) + { + $this->_prepareCache($nsKey); + $is->write($this->getString($nsKey, $itemKey)); + } + + /** + * Check if the given $itemKey exists in the namespace $nsKey. + * + * @param string $nsKey + * @param string $itemKey + * + * @return boolean + */ + public function hasKey($nsKey, $itemKey) + { + $this->_prepareCache($nsKey); + + return array_key_exists($itemKey, $this->_contents[$nsKey]); + } + + /** + * Clear data for $itemKey in the namespace $nsKey if it exists. + * + * @param string $nsKey + * @param string $itemKey + */ + public function clearKey($nsKey, $itemKey) + { + unset($this->_contents[$nsKey][$itemKey]); + } + + /** + * Clear all data in the namespace $nsKey if it exists. + * + * @param string $nsKey + */ + public function clearAll($nsKey) + { + unset($this->_contents[$nsKey]); + } + + // -- Private methods + + /** + * Initialize the namespace of $nsKey if needed. + * + * @param string $nsKey + */ + private function _prepareCache($nsKey) + { + if (!array_key_exists($nsKey, $this->_contents)) { + $this->_contents[$nsKey] = array(); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php new file mode 100644 index 0000000..740897a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php @@ -0,0 +1,328 @@ +_stream = $stream; + $this->_path = $path; + + if (function_exists('get_magic_quotes_runtime') && @get_magic_quotes_runtime() == 1) { + $this->_quotes = true; + } + } + + /** + * Set a string into the cache under $itemKey for the namespace $nsKey. + * + * @see MODE_WRITE, MODE_APPEND + * + * @param string $nsKey + * @param string $itemKey + * @param string $string + * @param integer $mode + * + * @throws Swift_IoException + */ + public function setString($nsKey, $itemKey, $string, $mode) + { + $this->_prepareCache($nsKey); + switch ($mode) { + case self::MODE_WRITE: + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START); + break; + case self::MODE_APPEND: + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_END); + break; + default: + throw new Swift_SwiftException( + 'Invalid mode [' . $mode . '] used to set nsKey='. + $nsKey . ', itemKey=' . $itemKey + ); + break; + } + fwrite($fp, $string); + $this->_freeHandle($nsKey, $itemKey); + } + + /** + * Set a ByteStream into the cache under $itemKey for the namespace $nsKey. + * + * @see MODE_WRITE, MODE_APPEND + * + * @param string $nsKey + * @param string $itemKey + * @param Swift_OutputByteStream $os + * @param integer $mode + * + * @throws Swift_IoException + */ + public function importFromByteStream($nsKey, $itemKey, Swift_OutputByteStream $os, $mode) + { + $this->_prepareCache($nsKey); + switch ($mode) { + case self::MODE_WRITE: + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START); + break; + case self::MODE_APPEND: + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_END); + break; + default: + throw new Swift_SwiftException( + 'Invalid mode [' . $mode . '] used to set nsKey='. + $nsKey . ', itemKey=' . $itemKey + ); + break; + } + while (false !== $bytes = $os->read(8192)) { + fwrite($fp, $bytes); + } + $this->_freeHandle($nsKey, $itemKey); + } + + /** + * Provides a ByteStream which when written to, writes data to $itemKey. + * + * NOTE: The stream will always write in append mode. + * + * @param string $nsKey + * @param string $itemKey + * @param Swift_InputByteStream $writeThrough + * + * @return Swift_InputByteStream + */ + public function getInputByteStream($nsKey, $itemKey, Swift_InputByteStream $writeThrough = null) + { + $is = clone $this->_stream; + $is->setKeyCache($this); + $is->setNsKey($nsKey); + $is->setItemKey($itemKey); + if (isset($writeThrough)) { + $is->setWriteThroughStream($writeThrough); + } + + return $is; + } + + /** + * Get data back out of the cache as a string. + * + * @param string $nsKey + * @param string $itemKey + * + * @return string + * + * @throws Swift_IoException + */ + public function getString($nsKey, $itemKey) + { + $this->_prepareCache($nsKey); + if ($this->hasKey($nsKey, $itemKey)) { + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START); + if ($this->_quotes) { + ini_set('magic_quotes_runtime', 0); + } + $str = ''; + while (!feof($fp) && false !== $bytes = fread($fp, 8192)) { + $str .= $bytes; + } + if ($this->_quotes) { + ini_set('magic_quotes_runtime', 1); + } + $this->_freeHandle($nsKey, $itemKey); + + return $str; + } + } + + /** + * Get data back out of the cache as a ByteStream. + * + * @param string $nsKey + * @param string $itemKey + * @param Swift_InputByteStream $is to write the data to + */ + public function exportToByteStream($nsKey, $itemKey, Swift_InputByteStream $is) + { + if ($this->hasKey($nsKey, $itemKey)) { + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START); + if ($this->_quotes) { + ini_set('magic_quotes_runtime', 0); + } + while (!feof($fp) && false !== $bytes = fread($fp, 8192)) { + $is->write($bytes); + } + if ($this->_quotes) { + ini_set('magic_quotes_runtime', 1); + } + $this->_freeHandle($nsKey, $itemKey); + } + } + + /** + * Check if the given $itemKey exists in the namespace $nsKey. + * + * @param string $nsKey + * @param string $itemKey + * + * @return boolean + */ + public function hasKey($nsKey, $itemKey) + { + return is_file($this->_path . '/' . $nsKey . '/' . $itemKey); + } + + /** + * Clear data for $itemKey in the namespace $nsKey if it exists. + * + * @param string $nsKey + * @param string $itemKey + */ + public function clearKey($nsKey, $itemKey) + { + if ($this->hasKey($nsKey, $itemKey)) { + $this->_freeHandle($nsKey, $itemKey); + unlink($this->_path . '/' . $nsKey . '/' . $itemKey); + } + } + + /** + * Clear all data in the namespace $nsKey if it exists. + * + * @param string $nsKey + */ + public function clearAll($nsKey) + { + if (array_key_exists($nsKey, $this->_keys)) { + foreach ($this->_keys[$nsKey] as $itemKey=>$null) { + $this->clearKey($nsKey, $itemKey); + } + if (is_dir($this->_path . '/' . $nsKey)) { + rmdir($this->_path . '/' . $nsKey); + } + unset($this->_keys[$nsKey]); + } + } + + // -- Private methods + + /** + * Initialize the namespace of $nsKey if needed. + * + * @param string $nsKey + */ + private function _prepareCache($nsKey) + { + $cacheDir = $this->_path . '/' . $nsKey; + if (!is_dir($cacheDir)) { + if (!mkdir($cacheDir)) { + throw new Swift_IoException('Failed to create cache directory ' . $cacheDir); + } + $this->_keys[$nsKey] = array(); + } + } + + /** + * Get a file handle on the cache item. + * + * @param string $nsKey + * @param string $itemKey + * @param integer $position + * + * @return resource + */ + private function _getHandle($nsKey, $itemKey, $position) + { + if (!isset($this->_keys[$nsKey][$itemKey])) { + $openMode = $this->hasKey($nsKey, $itemKey) + ? 'r+b' + : 'w+b' + ; + $fp = fopen($this->_path . '/' . $nsKey . '/' . $itemKey, $openMode); + $this->_keys[$nsKey][$itemKey] = $fp; + } + if (self::POSITION_START == $position) { + fseek($this->_keys[$nsKey][$itemKey], 0, SEEK_SET); + } elseif (self::POSITION_END == $position) { + fseek($this->_keys[$nsKey][$itemKey], 0, SEEK_END); + } + + return $this->_keys[$nsKey][$itemKey]; + } + + private function _freeHandle($nsKey, $itemKey) + { + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_CURRENT); + fclose($fp); + $this->_keys[$nsKey][$itemKey] = null; + } + + /** + * Destructor. + */ + public function __destruct() + { + foreach ($this->_keys as $nsKey=>$null) { + $this->clearAll($nsKey); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/KeyCacheInputStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/KeyCacheInputStream.php new file mode 100644 index 0000000..f4f8adb --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/KeyCacheInputStream.php @@ -0,0 +1,53 @@ +_keyCache = $keyCache; + } + + /** + * Specify a stream to write through for each write(). + * + * @param Swift_InputByteStream $is + */ + public function setWriteThroughStream(Swift_InputByteStream $is) + { + $this->_writeThrough = $is; + } + + /** + * Writes $bytes to the end of the stream. + * + * @param string $bytes + * @param Swift_InputByteStream $is optional + */ + public function write($bytes, Swift_InputByteStream $is = null) + { + $this->_keyCache->setString( + $this->_nsKey, $this->_itemKey, $bytes, Swift_KeyCache::MODE_APPEND + ); + if (isset($is)) { + $is->write($bytes); + } + if (isset($this->_writeThrough)) { + $this->_writeThrough->write($bytes); + } + } + + /** + * Not used. + */ + public function commit() + { + } + + /** + * Not used. + */ + public function bind(Swift_InputByteStream $is) + { + } + + /** + * Not used. + */ + public function unbind(Swift_InputByteStream $is) + { + } + + /** + * Flush the contents of the stream (empty it) and set the internal pointer + * to the beginning. + */ + public function flushBuffers() + { + $this->_keyCache->clearKey($this->_nsKey, $this->_itemKey); + } + + /** + * Set the nsKey which will be written to. + * + * @param string $nsKey + */ + public function setNsKey($nsKey) + { + $this->_nsKey = $nsKey; + } + + /** + * Set the itemKey which will be written to. + * + * @param string $itemKey + */ + public function setItemKey($itemKey) + { + $this->_itemKey = $itemKey; + } + + /** + * Any implementation should be cloneable, allowing the clone to access a + * separate $nsKey and $itemKey. + */ + public function __clone() + { + $this->_writeThrough = null; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/LoadBalancedTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/LoadBalancedTransport.php new file mode 100644 index 0000000..6eb3db7 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/LoadBalancedTransport.php @@ -0,0 +1,47 @@ +createDependenciesFor('transport.loadbalanced') + ); + + $this->setTransports($transports); + } + + /** + * Create a new LoadBalancedTransport instance. + * + * @param array $transports + * + * @return Swift_LoadBalancedTransport + */ + public static function newInstance($transports = array()) + { + return new self($transports); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php new file mode 100644 index 0000000..6c57939 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php @@ -0,0 +1,47 @@ +createDependenciesFor('transport.mail') + ); + + $this->setExtraParams($extraParams); + } + + /** + * Create a new MailTransport instance. + * + * @param string $extraParams To be passed to mail() + * + * @return Swift_MailTransport + */ + public static function newInstance($extraParams = '-f%s') + { + return new self($extraParams); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php new file mode 100644 index 0000000..b6703de --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php @@ -0,0 +1,115 @@ +_transport = $transport; + } + + /** + * Create a new Mailer instance. + * + * @param Swift_Transport $transport + * + * @return Swift_Mailer + */ + public static function newInstance(Swift_Transport $transport) + { + return new self($transport); + } + + /** + * Create a new class instance of one of the message services. + * + * For example 'mimepart' would create a 'message.mimepart' instance + * + * @param string $service + * + * @return object + */ + public function createMessage($service = 'message') + { + return Swift_DependencyContainer::getInstance() + ->lookup('message.'.$service); + } + + /** + * Send the given Message like it would be sent in a mail client. + * + * All recipients (with the exception of Bcc) will be able to see the other + * recipients this message was sent to. + * + * Recipient/sender data will be retrieved from the Message object. + * + * The return value is the number of recipients who were accepted for + * delivery. + * + * @param Swift_Mime_Message $message + * @param array $failedRecipients An array of failures by-reference + * + * @return integer + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $failedRecipients = (array) $failedRecipients; + + if (!$this->_transport->isStarted()) { + $this->_transport->start(); + } + + $sent = 0; + + try { + $sent = $this->_transport->send($message, $failedRecipients); + } catch (Swift_RfcComplianceException $e) { + foreach ($message->getTo() as $address => $name) { + $failedRecipients[] = $address; + } + } + + return $sent; + } + + /** + * Register a plugin using a known unique key (e.g. myPlugin). + * + * @param Swift_Events_EventListener $plugin + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + $this->_transport->registerPlugin($plugin); + } + + /** + * The Transport used to send messages. + * + * @return Swift_Transport + */ + public function getTransport() + { + return $this->_transport; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/ArrayRecipientIterator.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/ArrayRecipientIterator.php new file mode 100644 index 0000000..37e98da --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/ArrayRecipientIterator.php @@ -0,0 +1,57 @@ +_recipients = $recipients; + } + + /** + * Returns true only if there are more recipients to send to. + * + * @return boolean + */ + public function hasNext() + { + return !empty($this->_recipients); + } + + /** + * Returns an array where the keys are the addresses of recipients and the + * values are the names. e.g. ('foo@bar' => 'Foo') or ('foo@bar' => NULL) + * + * @return array + */ + public function nextRecipient() + { + return array_splice($this->_recipients, 0, 1); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/RecipientIterator.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/RecipientIterator.php new file mode 100644 index 0000000..073bce1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/RecipientIterator.php @@ -0,0 +1,34 @@ + 'Foo') or ('foo@bar' => NULL) + * + * @return array + */ + public function nextRecipient(); +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MemorySpool.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MemorySpool.php new file mode 100644 index 0000000..764b5aa --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MemorySpool.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Stores Messages in memory. + * + * @package Swift + * @author Fabien Potencier + */ +class Swift_MemorySpool implements Swift_Spool +{ + protected $messages = array(); + + /** + * Tests if this Transport mechanism has started. + * + * @return boolean + */ + public function isStarted() + { + return true; + } + + /** + * Starts this Transport mechanism. + */ + public function start() + { + } + + /** + * Stops this Transport mechanism. + */ + public function stop() + { + } + + /** + * Stores a message in the queue. + * + * @param Swift_Mime_Message $message The message to store + * + * @return boolean Whether the operation has succeeded + */ + public function queueMessage(Swift_Mime_Message $message) + { + $this->messages[] = $message; + + return true; + } + + /** + * Sends messages using the given transport instance. + * + * @param Swift_Transport $transport A transport instance + * @param string[] $failedRecipients An array of failures by-reference + * + * @return integer The number of sent emails + */ + public function flushQueue(Swift_Transport $transport, &$failedRecipients = null) + { + if (!$this->messages) { + return 0; + } + + if (!$transport->isStarted()) { + $transport->start(); + } + + $count = 0; + while ($message = array_pop($this->messages)) { + $count += $transport->send($message, $failedRecipients); + } + + return $count; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.php new file mode 100644 index 0000000..158ea25 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.php @@ -0,0 +1,85 @@ +createDependenciesFor('mime.message') + ); + + if (!isset($charset)) { + $charset = Swift_DependencyContainer::getInstance() + ->lookup('properties.charset'); + } + $this->setSubject($subject); + $this->setBody($body); + $this->setCharset($charset); + if ($contentType) { + $this->setContentType($contentType); + } + } + + /** + * Create a new Message. + * + * @param string $subject + * @param string $body + * @param string $contentType + * @param string $charset + * + * @return Swift_Message + */ + public static function newInstance($subject = null, $body = null, $contentType = null, $charset = null) + { + return new self($subject, $body, $contentType, $charset); + } + + /** + * Add a MimePart to this Message. + * + * @param string|Swift_OutputByteStream $body + * @param string $contentType + * @param string $charset + * + * @return Swift_Mime_SimpleMessage + */ + public function addPart($body, $contentType = null, $charset = null) + { + return $this->attach(Swift_MimePart::newInstance( + $body, $contentType, $charset + )); + } + + public function __wakeup() + { + Swift_DependencyContainer::getInstance()->createDependenciesFor('mime.message'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Attachment.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Attachment.php new file mode 100644 index 0000000..faf358f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Attachment.php @@ -0,0 +1,155 @@ +setDisposition('attachment'); + $this->setContentType('application/octet-stream'); + $this->_mimeTypes = $mimeTypes; + } + + /** + * Get the nesting level used for this attachment. + * + * Always returns {@link LEVEL_MIXED}. + * + * @return integer + */ + public function getNestingLevel() + { + return self::LEVEL_MIXED; + } + + /** + * Get the Content-Disposition of this attachment. + * + * By default attachments have a disposition of "attachment". + * + * @return string + */ + public function getDisposition() + { + return $this->_getHeaderFieldModel('Content-Disposition'); + } + + /** + * Set the Content-Disposition of this attachment. + * + * @param string $disposition + * + * @return Swift_Mime_Attachment + */ + public function setDisposition($disposition) + { + if (!$this->_setHeaderFieldModel('Content-Disposition', $disposition)) { + $this->getHeaders()->addParameterizedHeader( + 'Content-Disposition', $disposition + ); + } + + return $this; + } + + /** + * Get the filename of this attachment when downloaded. + * + * @return string + */ + public function getFilename() + { + return $this->_getHeaderParameter('Content-Disposition', 'filename'); + } + + /** + * Set the filename of this attachment. + * + * @param string $filename + * + * @return Swift_Mime_Attachment + */ + public function setFilename($filename) + { + $this->_setHeaderParameter('Content-Disposition', 'filename', $filename); + $this->_setHeaderParameter('Content-Type', 'name', $filename); + + return $this; + } + + /** + * Get the file size of this attachment. + * + * @return integer + */ + public function getSize() + { + return $this->_getHeaderParameter('Content-Disposition', 'size'); + } + + /** + * Set the file size of this attachment. + * + * @param integer $size + * + * @return Swift_Mime_Attachment + */ + public function setSize($size) + { + $this->_setHeaderParameter('Content-Disposition', 'size', $size); + + return $this; + } + + /** + * Set the file that this attachment is for. + * + * @param Swift_FileStream $file + * @param string $contentType optional + * + * @return Swift_Mime_Attachment + */ + public function setFile(Swift_FileStream $file, $contentType = null) + { + $this->setFilename(basename($file->getPath())); + $this->setBody($file, $contentType); + if (!isset($contentType)) { + $extension = strtolower(substr( + $file->getPath(), strrpos($file->getPath(), '.') + 1 + )); + + if (array_key_exists($extension, $this->_mimeTypes)) { + $this->setContentType($this->_mimeTypes[$extension]); + } + } + + return $this; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.php new file mode 100644 index 0000000..bfd41ed --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.php @@ -0,0 +1,26 @@ += $maxLineLength || 76 < $maxLineLength) { + $maxLineLength = 76; + } + + $remainder = 0; + + while (false !== $bytes = $os->read(8190)) { + $encoded = base64_encode($bytes); + $encodedTransformed = ''; + $thisMaxLineLength = $maxLineLength - $remainder - $firstLineOffset; + + while ($thisMaxLineLength < strlen($encoded)) { + $encodedTransformed .= substr($encoded, 0, $thisMaxLineLength) . "\r\n"; + $firstLineOffset = 0; + $encoded = substr($encoded, $thisMaxLineLength); + $thisMaxLineLength = $maxLineLength; + $remainder = 0; + } + + if (0 < $remainingLength = strlen($encoded)) { + $remainder += $remainingLength; + $encodedTransformed .= $encoded; + $encoded = null; + } + + $is->write($encodedTransformed); + } + } + + /** + * Get the name of this encoding scheme. + * Returns the string 'base64'. + * + * @return string + */ + public function getName() + { + return 'base64'; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NativeQpContentEncoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NativeQpContentEncoder.php new file mode 100644 index 0000000..f7dac21 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NativeQpContentEncoder.php @@ -0,0 +1,125 @@ +charset = $charset ? $charset : 'utf-8'; + } + + /** + * Notify this observer that the entity's charset has changed. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->charset = $charset; + } + + /** + * Encode $in to $out. + * + * @param Swift_OutputByteStream $os to read from + * @param Swift_InputByteStream $is to write to + * @param integer $firstLineOffset + * @param integer $maxLineLength 0 indicates the default length for this encoding + * + * @throws RuntimeException + */ + public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0) + { + if ($this->charset !== 'utf-8') { + throw new RuntimeException( + sprintf('Charset "%s" not supported. NativeQpContentEncoder only supports "utf-8"', $this->charset)); + } + + $string = ''; + + while (false !== $bytes = $os->read(8192)) { + $string .= $bytes; + } + + $is->write($this->encodeString($string)); + } + + /** + * Get the MIME name of this content encoding scheme. + * + * @return string + */ + public function getName() + { + return 'quoted-printable'; + } + + /** + * Encode a given string to produce an encoded string. + * + * @param string $string + * @param integer $firstLineOffset if first line needs to be shorter + * @param integer $maxLineLength 0 indicates the default length for this encoding + * + * @return string + * + * @throws RuntimeException + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + if ($this->charset !== 'utf-8') { + throw new RuntimeException( + sprintf('Charset "%s" not supported. NativeQpContentEncoder only supports "utf-8"', $this->charset)); + } + + return $this->_standardize(quoted_printable_encode($string)); + } + + /** + * Make sure CRLF is correct and HT/SPACE are in valid places. + * + * @param string $string + * + * @return string + */ + protected function _standardize($string) + { + // transform CR or LF to CRLF + $string = preg_replace('~=0D(?!=0A)|(?_name = $name; + $this->_canonical = $canonical; + } + + /** + * Encode a given string to produce an encoded string. + * + * @param string $string + * @param integer $firstLineOffset ignored + * @param integer $maxLineLength - 0 means no wrapping will occur + * + * @return string + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + if ($this->_canonical) { + $string = $this->_canonicalize($string); + } + + return $this->_safeWordWrap($string, $maxLineLength, "\r\n"); + } + + /** + * Encode stream $in to stream $out. + * + * @param Swift_OutputByteStream $os + * @param Swift_InputByteStream $is + * @param integer $firstLineOffset ignored + * @param integer $maxLineLength optional, 0 means no wrapping will occur + */ + public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0) + { + $leftOver = ''; + while (false !== $bytes = $os->read(8192)) { + $toencode = $leftOver . $bytes; + if ($this->_canonical) { + $toencode = $this->_canonicalize($toencode); + } + $wrapped = $this->_safeWordWrap($toencode, $maxLineLength, "\r\n"); + $lastLinePos = strrpos($wrapped, "\r\n"); + $leftOver = substr($wrapped, $lastLinePos); + $wrapped = substr($wrapped, 0, $lastLinePos); + + $is->write($wrapped); + } + if (strlen($leftOver)) { + $is->write($leftOver); + } + } + + /** + * Get the name of this encoding scheme. + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Not used. + */ + public function charsetChanged($charset) + { + } + + // -- Private methods + + /** + * A safer (but weaker) wordwrap for unicode. + * + * @param string $string + * @param integer $length + * @param string $le + * + * @return string + */ + private function _safeWordwrap($string, $length = 75, $le = "\r\n") + { + if (0 >= $length) { + return $string; + } + + $originalLines = explode($le, $string); + + $lines = array(); + $lineCount = 0; + + foreach ($originalLines as $originalLine) { + $lines[] = ''; + $currentLine =& $lines[$lineCount++]; + + //$chunks = preg_split('/(?<=[\ \t,\.!\?\-&\+\/])/', $originalLine); + $chunks = preg_split('/(?<=\s)/', $originalLine); + + foreach ($chunks as $chunk) { + if (0 != strlen($currentLine) + && strlen($currentLine . $chunk) > $length) + { + $lines[] = ''; + $currentLine =& $lines[$lineCount++]; + } + $currentLine .= $chunk; + } + } + + return implode("\r\n", $lines); + } + + /** + * Canonicalize string input (fix CRLF). + * + * @param string $string + * + * @return string + */ + private function _canonicalize($string) + { + return str_replace( + array("\r\n", "\r", "\n"), + array("\n", "\n", "\r\n"), + $string + ); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php new file mode 100644 index 0000000..059c53d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php @@ -0,0 +1,125 @@ +_dotEscape = $dotEscape; + parent::__construct($charStream, $filter); + } + + public function __sleep() + { + return array('_charStream', '_filter', '_dotEscape'); + } + + protected function getSafeMapShareId() + { + return get_class($this).($this->_dotEscape ? '.dotEscape' : ''); + } + + protected function initSafeMap() + { + parent::initSafeMap(); + if ($this->_dotEscape) { + /* Encode . as =2e for buggy remote servers */ + unset($this->_safeMap[0x2e]); + } + } + + /** + * Encode stream $in to stream $out. + * + * 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 Swift_OutputByteStream $os output stream + * @param Swift_InputByteStream $is input stream + * @param integer $firstLineOffset + * @param integer $maxLineLength + */ + public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0) + { + if ($maxLineLength > 76 || $maxLineLength <= 0) { + $maxLineLength = 76; + } + + $thisLineLength = $maxLineLength - $firstLineOffset; + + $this->_charStream->flushContents(); + $this->_charStream->importByteStream($os); + + $currentLine = ''; + $prepend = ''; + $size=$lineLen=0; + + 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) { + $is->write($prepend . $this->_standardize($currentLine)); + $currentLine = ''; + $prepend = "=\r\n"; + $thisLineLength = $maxLineLength; + $lineLen=0; + } + $lineLen+=$size; + $currentLine .= $enc; + } + if (strlen($currentLine)) { + $is->write($prepend . $this->_standardize($currentLine)); + } + } + + /** + * Get the name of this encoding scheme. + * Returns the string 'quoted-printable'. + * + * @return string + */ + public function getName() + { + return 'quoted-printable'; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php new file mode 100644 index 0000000..491409a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php @@ -0,0 +1,90 @@ + + */ +class Swift_Mime_ContentEncoder_QpContentEncoderProxy implements Swift_Mime_ContentEncoder +{ + /** + * @var Swift_Mime_ContentEncoder_QpContentEncoder + */ + private $safeEncoder; + + /** + * @var Swift_Mime_ContentEncoder_NativeQpContentEncoder + */ + private $nativeEncoder; + + /** + * @var null|string + */ + private $charset; + + /** + * Constructor. + * + * @param Swift_Mime_ContentEncoder_QpContentEncoder $safeEncoder + * @param Swift_Mime_ContentEncoder_NativeQpContentEncoder $nativeEncoder + * @param string|null $charset + */ + public function __construct(Swift_Mime_ContentEncoder_QpContentEncoder $safeEncoder, Swift_Mime_ContentEncoder_NativeQpContentEncoder $nativeEncoder, $charset) + { + $this->safeEncoder = $safeEncoder; + $this->nativeEncoder = $nativeEncoder; + $this->charset = $charset; + } + + /** + * {@inheritdoc} + */ + public function charsetChanged($charset) + { + $this->charset = $charset; + } + + /** + * {@inheritdoc} + */ + public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0) + { + $this->getEncoder()->encodeByteStream($os, $is, $firstLineOffset, $maxLineLength); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'quoted-printable'; + } + + /** + * {@inheritdoc} + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + return $this->getEncoder()->encodeString($string, $firstLineOffset, $maxLineLength); + } + + /** + * @return Swift_Mime_ContentEncoder + */ + private function getEncoder() + { + return 'utf-8' === $this->charset ? $this->nativeEncoder : $this->safeEncoder; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/RawContentEncoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/RawContentEncoder.php new file mode 100644 index 0000000..8f1f9b5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/RawContentEncoder.php @@ -0,0 +1,65 @@ + + */ +class Swift_Mime_ContentEncoder_RawContentEncoder implements Swift_Mime_ContentEncoder +{ + /** + * Encode a given string to produce an encoded string. + * + * @param string $string + * @param int $firstLineOffset ignored + * @param int $maxLineLength ignored + * @return string + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + return $string; + } + + /** + * Encode stream $in to stream $out. + * + * @param Swift_OutputByteStream $in + * @param Swift_InputByteStream $out + * @param int $firstLineOffset ignored + * @param int $maxLineLength ignored + */ + public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0) + { + while (false !== ($bytes = $os->read(8192))) { + $is->write($bytes); + } + } + + /** + * Get the name of this encoding scheme. + * + * @return string + */ + public function getName() + { + return 'raw'; + } + + /** + * Not used. + */ + public function charsetChanged($charset) + { + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EmbeddedFile.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EmbeddedFile.php new file mode 100644 index 0000000..05e06e2 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EmbeddedFile.php @@ -0,0 +1,47 @@ +setDisposition('inline'); + $this->setId($this->getId()); + } + + /** + * Get the nesting level of this EmbeddedFile. + * + * Returns {@see LEVEL_RELATED}. + * + * @return integer + */ + public function getNestingLevel() + { + return self::LEVEL_RELATED; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EncodingObserver.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EncodingObserver.php new file mode 100644 index 0000000..e7e6f20 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EncodingObserver.php @@ -0,0 +1,26 @@ +init(); + } + + public function __wakeup() + { + $this->init(); + } + + protected function init() + { + if (count(self::$_specials) > 0) { + return; + } + + self::$_specials = array( + '(', ')', '<', '>', '[', ']', + ':', ';', '@', ',', '.', '"' + ); + + /*** Refer to RFC 2822 for ABNF grammar ***/ + + //All basic building blocks + self::$_grammar['NO-WS-CTL'] = '[\x01-\x08\x0B\x0C\x0E-\x19\x7F]'; + self::$_grammar['WSP'] = '[ \t]'; + self::$_grammar['CRLF'] = '(?:\r\n)'; + self::$_grammar['FWS'] = '(?:(?:' . self::$_grammar['WSP'] . '*' . + self::$_grammar['CRLF'] . ')?' . self::$_grammar['WSP'] . ')'; + self::$_grammar['text'] = '[\x00-\x08\x0B\x0C\x0E-\x7F]'; + self::$_grammar['quoted-pair'] = '(?:\\\\' . self::$_grammar['text'] . ')'; + self::$_grammar['ctext'] = '(?:' . self::$_grammar['NO-WS-CTL'] . + '|[\x21-\x27\x2A-\x5B\x5D-\x7E])'; + //Uses recursive PCRE (?1) -- could be a weak point?? + self::$_grammar['ccontent'] = '(?:' . self::$_grammar['ctext'] . '|' . + self::$_grammar['quoted-pair'] . '|(?1))'; + self::$_grammar['comment'] = '(\((?:' . self::$_grammar['FWS'] . '|' . + self::$_grammar['ccontent']. ')*' . self::$_grammar['FWS'] . '?\))'; + self::$_grammar['CFWS'] = '(?:(?:' . self::$_grammar['FWS'] . '?' . + self::$_grammar['comment'] . ')*(?:(?:' . self::$_grammar['FWS'] . '?' . + self::$_grammar['comment'] . ')|' . self::$_grammar['FWS'] . '))'; + self::$_grammar['qtext'] = '(?:' . self::$_grammar['NO-WS-CTL'] . + '|[\x21\x23-\x5B\x5D-\x7E])'; + self::$_grammar['qcontent'] = '(?:' . self::$_grammar['qtext'] . '|' . + self::$_grammar['quoted-pair'] . ')'; + self::$_grammar['quoted-string'] = '(?:' . self::$_grammar['CFWS'] . '?"' . + '(' . self::$_grammar['FWS'] . '?' . self::$_grammar['qcontent'] . ')*' . + self::$_grammar['FWS'] . '?"' . self::$_grammar['CFWS'] . '?)'; + self::$_grammar['atext'] = '[a-zA-Z0-9!#\$%&\'\*\+\-\/=\?\^_`\{\}\|~]'; + self::$_grammar['atom'] = '(?:' . self::$_grammar['CFWS'] . '?' . + self::$_grammar['atext'] . '+' . self::$_grammar['CFWS'] . '?)'; + self::$_grammar['dot-atom-text'] = '(?:' . self::$_grammar['atext'] . '+' . + '(\.' . self::$_grammar['atext'] . '+)*)'; + self::$_grammar['dot-atom'] = '(?:' . self::$_grammar['CFWS'] . '?' . + self::$_grammar['dot-atom-text'] . '+' . self::$_grammar['CFWS'] . '?)'; + self::$_grammar['word'] = '(?:' . self::$_grammar['atom'] . '|' . + self::$_grammar['quoted-string'] . ')'; + self::$_grammar['phrase'] = '(?:' . self::$_grammar['word'] . '+?)'; + self::$_grammar['no-fold-quote'] = '(?:"(?:' . self::$_grammar['qtext'] . + '|' . self::$_grammar['quoted-pair'] . ')*")'; + self::$_grammar['dtext'] = '(?:' . self::$_grammar['NO-WS-CTL'] . + '|[\x21-\x5A\x5E-\x7E])'; + self::$_grammar['no-fold-literal'] = '(?:\[(?:' . self::$_grammar['dtext'] . + '|' . self::$_grammar['quoted-pair'] . ')*\])'; + + //Message IDs + self::$_grammar['id-left'] = '(?:' . self::$_grammar['dot-atom-text'] . '|' . + self::$_grammar['no-fold-quote'] . ')'; + self::$_grammar['id-right'] = '(?:' . self::$_grammar['dot-atom-text'] . '|' . + self::$_grammar['no-fold-literal'] . ')'; + + //Addresses, mailboxes and paths + self::$_grammar['local-part'] = '(?:' . self::$_grammar['dot-atom'] . '|' . + self::$_grammar['quoted-string'] . ')'; + self::$_grammar['dcontent'] = '(?:' . self::$_grammar['dtext'] . '|' . + self::$_grammar['quoted-pair'] . ')'; + self::$_grammar['domain-literal'] = '(?:' . self::$_grammar['CFWS'] . '?\[(' . + self::$_grammar['FWS'] . '?' . self::$_grammar['dcontent'] . ')*?' . + self::$_grammar['FWS'] . '?\]' . self::$_grammar['CFWS'] . '?)'; + self::$_grammar['domain'] = '(?:' . self::$_grammar['dot-atom'] . '|' . + self::$_grammar['domain-literal'] . ')'; + self::$_grammar['addr-spec'] = '(?:' . self::$_grammar['local-part'] . '@' . + self::$_grammar['domain'] . ')'; + } + + /** + * Get the grammar defined for $name token. + * + * @param string $name exactly as written in the RFC + * + * @return string + */ + public function getDefinition($name) + { + if (array_key_exists($name, self::$_grammar)) { + return self::$_grammar[$name]; + } else { + throw new Swift_RfcComplianceException( + "No such grammar '" . $name . "' defined." + ); + } + } + + /** + * Returns the tokens defined in RFC 2822 (and some related RFCs). + * + * @return array + */ + public function getGrammarDefinitions() + { + return self::$_grammar; + } + + /** + * Returns the current special characters used in the syntax which need to be escaped. + * + * @return array + */ + public function getSpecials() + { + return self::$_specials; + } + + /** + * Escape special characters in a string (convert to quoted-pairs). + * + * @param string $token + * @param string[] $include additional chars to escape + * @param string[] $exclude chars from escaping + * + * @return string + */ + public function escapeSpecials($token, $include = array(), $exclude = array()) + { + foreach (array_merge(array('\\'), array_diff(self::$_specials, $exclude), $include) as $char) { + $token = str_replace($char, '\\' . $char, $token); + } + + return $token; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Header.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Header.php new file mode 100644 index 0000000..55d3ab8 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Header.php @@ -0,0 +1,95 @@ +getName(), "\r\n"); + mb_internal_encoding($old); + + return $newstring; + } + + return parent::encodeString($string, $firstLineOffset, $maxLineLength); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php new file mode 100644 index 0000000..c9bbe71 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php @@ -0,0 +1,67 @@ +_safeMap[$byte] = chr($byte); + } + } + + /** + * Get the name of this encoding scheme. + * + * Returns the string 'Q'. + * + * @return string + */ + public function getName() + { + return 'Q'; + } + + /** + * Takes an unencoded string and produces a QP encoded string from it. + * + * @param string $string string to encode + * @param integer $firstLineOffset optional + * @param integer $maxLineLength optional, 0 indicates the default of 76 chars + * + * @return string + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + return str_replace(array(' ', '=20', "=\r\n"), array('_', '_', "\r\n"), + parent::encodeString($string, $firstLineOffset, $maxLineLength) + ); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderFactory.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderFactory.php new file mode 100644 index 0000000..7cab133 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderFactory.php @@ -0,0 +1,80 @@ +setGrammar($grammar); + } + + /** + * Set the character set used in this Header. + * + * @param string $charset + */ + public function setCharset($charset) + { + $this->clearCachedValueIf($charset != $this->_charset); + $this->_charset = $charset; + if (isset($this->_encoder)) { + $this->_encoder->charsetChanged($charset); + } + } + + /** + * Get the character set used in this Header. + * + * @return string + */ + public function getCharset() + { + return $this->_charset; + } + + /** + * Set the language used in this Header. + * + * For example, for US English, 'en-us'. + * This can be unspecified. + * + * @param string $lang + */ + public function setLanguage($lang) + { + $this->clearCachedValueIf($this->_lang != $lang); + $this->_lang = $lang; + } + + /** + * Get the language used in this Header. + * + * @return string + */ + public function getLanguage() + { + return $this->_lang; + } + + /** + * Set the encoder used for encoding the header. + * + * @param Swift_Mime_HeaderEncoder $encoder + */ + public function setEncoder(Swift_Mime_HeaderEncoder $encoder) + { + $this->_encoder = $encoder; + $this->setCachedValue(null); + } + + /** + * Get the encoder used for encoding this Header. + * + * @return Swift_Mime_HeaderEncoder + */ + public function getEncoder() + { + return $this->_encoder; + } + + /** + * Set the grammar used for the header. + * + * @param Swift_Mime_Grammar $grammar + */ + public function setGrammar(Swift_Mime_Grammar $grammar) + { + $this->_grammar = $grammar; + $this->setCachedValue(null); + } + + /** + * Get the grammar used for this Header. + * + * @return Swift_Mime_Grammar + */ + public function getGrammar() + { + return $this->_grammar; + } + + /** + * Get the name of this header (e.g. charset). + * + * @return string + */ + public function getFieldName() + { + return $this->_name; + } + + /** + * Set the maximum length of lines in the header (excluding EOL). + * + * @param integer $lineLength + */ + public function setMaxLineLength($lineLength) + { + $this->clearCachedValueIf($this->_lineLength != $lineLength); + $this->_lineLength = $lineLength; + } + + /** + * Get the maximum permitted length of lines in this Header. + * + * @return int + */ + public function getMaxLineLength() + { + return $this->_lineLength; + } + + /** + * Get this Header rendered as a RFC 2822 compliant string. + * + * @return string + * + * @throws Swift_RfcComplianceException + */ + public function toString() + { + return $this->_tokensToString($this->toTokens()); + } + + /** + * Returns a string representation of this object. + * + * @return string + * + * @see toString() + */ + public function __toString() + { + return $this->toString(); + } + + // -- Points of extension + + /** + * Set the name of this Header field. + * + * @param string $name + */ + protected function setFieldName($name) + { + $this->_name = $name; + } + + /** + * Produces a compliant, formatted RFC 2822 'phrase' based on the string given. + * + * @param Swift_Mime_Header $header + * @param string $string as displayed + * @param string $charset of the text + * @param Swift_Mime_HeaderEncoder $encoder + * @param boolean $shorten the first line to make remove for header name + * + * @return string + */ + protected function createPhrase(Swift_Mime_Header $header, $string, $charset, Swift_Mime_HeaderEncoder $encoder = null, $shorten = false) + { + //Treat token as exactly what was given + $phraseStr = $string; + //If it's not valid + if (!preg_match('/^' . $this->getGrammar()->getDefinition('phrase') . '$/D', $phraseStr)) { + // .. but it is just ascii text, try escaping some characters + // and make it a quoted-string + if (preg_match('/^' . $this->getGrammar()->getDefinition('text') . '*$/D', $phraseStr)) { + $phraseStr = $this->getGrammar()->escapeSpecials( + $phraseStr, array('"'), $this->getGrammar()->getSpecials() + ); + $phraseStr = '"' . $phraseStr . '"'; + } else { // ... otherwise it needs encoding + //Determine space remaining on line if first line + if ($shorten) { + $usedLength = strlen($header->getFieldName() . ': '); + } else { + $usedLength = 0; + } + $phraseStr = $this->encodeWords($header, $string, $usedLength); + } + } + + return $phraseStr; + } + + /** + * Encode needed word tokens within a string of input. + * + * @param Swift_Mime_Header $header + * @param string $input + * @param string $usedLength optional + * + * @return string + */ + protected function encodeWords(Swift_Mime_Header $header, $input, $usedLength = -1) + { + $value = ''; + + $tokens = $this->getEncodableWordTokens($input); + + foreach ($tokens as $token) { + //See RFC 2822, Sect 2.2 (really 2.2 ??) + if ($this->tokenNeedsEncoding($token)) { + //Don't encode starting WSP + $firstChar = substr($token, 0, 1); + switch ($firstChar) { + case ' ': + case "\t": + $value .= $firstChar; + $token = substr($token, 1); + } + + if (-1 == $usedLength) { + $usedLength = strlen($header->getFieldName() . ': ') + strlen($value); + } + $value .= $this->getTokenAsEncodedWord($token, $usedLength); + + $header->setMaxLineLength(76); //Forcefully override + } else { + $value .= $token; + } + } + + return $value; + } + + /** + * Test if a token needs to be encoded or not. + * + * @param string $token + * + * @return boolean + */ + protected function tokenNeedsEncoding($token) + { + return preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token); + } + + /** + * Splits a string into tokens in blocks of words which can be encoded quickly. + * + * @param string $string + * + * @return string[] + */ + protected function getEncodableWordTokens($string) + { + $tokens = array(); + + $encodedToken = ''; + //Split at all whitespace boundaries + foreach (preg_split('~(?=[\t ])~', $string) as $token) { + if ($this->tokenNeedsEncoding($token)) { + $encodedToken .= $token; + } else { + if (strlen($encodedToken) > 0) { + $tokens[] = $encodedToken; + $encodedToken = ''; + } + $tokens[] = $token; + } + } + if (strlen($encodedToken)) { + $tokens[] = $encodedToken; + } + + return $tokens; + } + + /** + * Get a token as an encoded word for safe insertion into headers. + * + * @param string $token token to encode + * @param integer $firstLineOffset optional + * + * @return string + */ + protected function getTokenAsEncodedWord($token, $firstLineOffset = 0) + { + //Adjust $firstLineOffset to account for space needed for syntax + $charsetDecl = $this->_charset; + if (isset($this->_lang)) { + $charsetDecl .= '*' . $this->_lang; + } + $encodingWrapperLength = strlen( + '=?' . $charsetDecl . '?' . $this->_encoder->getName() . '??=' + ); + + if ($firstLineOffset >= 75) { //Does this logic need to be here? + $firstLineOffset = 0; + } + + $encodedTextLines = explode("\r\n", + $this->_encoder->encodeString( + $token, $firstLineOffset, 75 - $encodingWrapperLength, $this->_charset + ) + ); + + if (strtolower($this->_charset) !== 'iso-2022-jp') { // special encoding for iso-2022-jp using mb_encode_mimeheader + foreach ($encodedTextLines as $lineNum => $line) { + $encodedTextLines[$lineNum] = '=?' . $charsetDecl . + '?' . $this->_encoder->getName() . + '?' . $line . '?='; + } + } + + return implode("\r\n ", $encodedTextLines); + } + + /** + * Generates tokens from the given string which include CRLF as individual tokens. + * + * @param string $token + * + * @return string[] + */ + protected function generateTokenLines($token) + { + return preg_split('~(\r\n)~', $token, -1, PREG_SPLIT_DELIM_CAPTURE); + } + + /** + * Set a value into the cache. + * + * @param string $value + */ + protected function setCachedValue($value) + { + $this->_cachedValue = $value; + } + + /** + * Get the value in the cache. + * + * @return string + */ + protected function getCachedValue() + { + return $this->_cachedValue; + } + + /** + * Clear the cached value if $condition is met. + * + * @param boolean $condition + */ + protected function clearCachedValueIf($condition) + { + if ($condition) { + $this->setCachedValue(null); + } + } + + // -- Private methods + + /** + * Generate a list of all tokens in the final header. + * + * @param string $string The string to tokenize + * + * @return array An array of tokens as strings + */ + protected function toTokens($string = null) + { + if (is_null($string)) { + $string = $this->getFieldBody(); + } + + $tokens = array(); + + //Generate atoms; split at all invisible boundaries followed by WSP + foreach (preg_split('~(?=[ \t])~', $string) as $token) { + $newTokens = $this->generateTokenLines($token); + foreach ($newTokens as $newToken) { + $tokens[] = $newToken; + } + } + return $tokens; + } + + /** + * Takes an array of tokens which appear in the header and turns them into + * an RFC 2822 compliant string, adding FWSP where needed. + * + * @param string[] $tokens + * + * @return string + */ + private function _tokensToString(array $tokens) + { + $lineCount = 0; + $headerLines = array(); + $headerLines[] = $this->_name . ': '; + $currentLine =& $headerLines[$lineCount++]; + + //Build all tokens back into compliant header + foreach ($tokens as $i => $token) { + //Line longer than specified maximum or token was just a new line + if (("\r\n" == $token) || + ($i > 0 && strlen($currentLine . $token) > $this->_lineLength) + && 0 < strlen($currentLine)) + { + $headerLines[] = ''; + $currentLine =& $headerLines[$lineCount++]; + } + + //Append token to the line + if ("\r\n" != $token) { + $currentLine .= $token; + } + } + + //Implode with FWS (RFC 2822, 2.2.3) + return implode("\r\n", $headerLines) . "\r\n"; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/DateHeader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/DateHeader.php new file mode 100644 index 0000000..9127cc2 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/DateHeader.php @@ -0,0 +1,127 @@ + + * + *
    + * + * @param string $name of Header + * @param Swift_Mime_Grammar $grammar + */ + public function __construct($name, Swift_Mime_Grammar $grammar) + { + $this->setFieldName($name); + parent::__construct($grammar); + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_DATE; + } + + /** + * Set the model for the field body. + * + * This method takes a UNIX timestamp. + * + * @param integer $model + */ + public function setFieldBodyModel($model) + { + $this->setTimestamp($model); + } + + /** + * Get the model for the field body. + * + * This method returns a UNIX timestamp. + * + * @return mixed + */ + public function getFieldBodyModel() + { + return $this->getTimestamp(); + } + + /** + * Get the UNIX timestamp of the Date in this Header. + * + * @return int + */ + public function getTimestamp() + { + return $this->_timestamp; + } + + /** + * Set the UNIX timestamp of the Date in this Header. + * + * @param integer $timestamp + */ + public function setTimestamp($timestamp) + { + if (!is_null($timestamp)) { + $timestamp = (int) $timestamp; + } + $this->clearCachedValueIf($this->_timestamp != $timestamp); + $this->_timestamp = $timestamp; + } + + /** + * Get the string value of the body in this Header. + * + * This is not necessarily RFC 2822 compliant since folding white space will + * not be added at this stage (see {@link toString()} for that). + * + * @see toString() + * + * @return string + */ + public function getFieldBody() + { + if (!$this->getCachedValue()) { + if (isset($this->_timestamp)) { + $this->setCachedValue(date('r', $this->_timestamp)); + } + } + + return $this->getCachedValue(); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/IdentificationHeader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/IdentificationHeader.php new file mode 100644 index 0000000..1d00015 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/IdentificationHeader.php @@ -0,0 +1,183 @@ +setFieldName($name); + parent::__construct($grammar); + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_ID; + } + + /** + * Set the model for the field body. + * + * This method takes a string ID, or an array of IDs. + * + * @param mixed $model + * + * @throws Swift_RfcComplianceException + */ + public function setFieldBodyModel($model) + { + $this->setId($model); + } + + /** + * Get the model for the field body. + * + * This method returns an array of IDs + * + * @return array + */ + public function getFieldBodyModel() + { + return $this->getIds(); + } + + /** + * Set the ID used in the value of this header. + * + * @param string|array $id + * + * @throws Swift_RfcComplianceException + */ + public function setId($id) + { + $this->setIds(is_array($id) ? $id : array($id)); + } + + /** + * Get the ID used in the value of this Header. + * + * If multiple IDs are set only the first is returned. + * + * @return string + */ + public function getId() + { + if (count($this->_ids) > 0) { + return $this->_ids[0]; + } + } + + /** + * Set a collection of IDs to use in the value of this Header. + * + * @param string[] $ids + * + * @throws Swift_RfcComplianceException + */ + public function setIds(array $ids) + { + $actualIds = array(); + + foreach ($ids as $id) { + $this->_assertValidId($id); + $actualIds[] = $id; + } + + $this->clearCachedValueIf($this->_ids != $actualIds); + $this->_ids = $actualIds; + } + + /** + * Get the list of IDs used in this Header. + * + * @return string[] + */ + public function getIds() + { + return $this->_ids; + } + + /** + * Get the string value of the body in this Header. + * + * This is not necessarily RFC 2822 compliant since folding white space will + * not be added at this stage (see {@see toString()} for that). + * + * @see toString() + * + * @return string + * + * @throws Swift_RfcComplianceException + */ + public function getFieldBody() + { + if (!$this->getCachedValue()) { + $angleAddrs = array(); + + foreach ($this->_ids as $id) { + $angleAddrs[] = '<' . $id . '>'; + } + + $this->setCachedValue(implode(' ', $angleAddrs)); + } + + return $this->getCachedValue(); + } + + /** + * Throws an Exception if the id passed does not comply with RFC 2822. + * + * @param string $id + * + * @throws Swift_RfcComplianceException + */ + private function _assertValidId($id) + { + if (!preg_match( + '/^' . $this->getGrammar()->getDefinition('id-left') . '@' . + $this->getGrammar()->getDefinition('id-right') . '$/D', + $id + )) + { + throw new Swift_RfcComplianceException( + 'Invalid ID given <' . $id . '>' + ); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php new file mode 100644 index 0000000..0fda5ae --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php @@ -0,0 +1,358 @@ +setFieldName($name); + $this->setEncoder($encoder); + parent::__construct($grammar); + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_MAILBOX; + } + + /** + * Set the model for the field body. + * + * This method takes a string, or an array of addresses. + * + * @param mixed $model + * + * @throws Swift_RfcComplianceException + */ + public function setFieldBodyModel($model) + { + $this->setNameAddresses($model); + } + + /** + * Get the model for the field body. + * + * This method returns an associative array like {@link getNameAddresses()} + * + * @return array + * + * @throws Swift_RfcComplianceException + */ + public function getFieldBodyModel() + { + return $this->getNameAddresses(); + } + + /** + * Set a list of mailboxes to be shown in this Header. + * + * The mailboxes can be a simple array of addresses, or an array of + * key=>value pairs where (email => personalName). + * Example: + * + * setNameAddresses(array( + * 'chris@swiftmailer.org' => 'Chris Corbyn', + * 'mark@swiftmailer.org' //No associated personal name + * )); + * ?> + * + * + * @see __construct() + * @see setAddresses() + * @see setValue() + * + * @param string|string[] $mailboxes + * + * @throws Swift_RfcComplianceException + */ + public function setNameAddresses($mailboxes) + { + $this->_mailboxes = $this->normalizeMailboxes((array) $mailboxes); + $this->setCachedValue(null); //Clear any cached value + } + + /** + * Get the full mailbox list of this Header as an array of valid RFC 2822 strings. + * + * Example: + * + * 'Chris Corbyn', + * 'mark@swiftmailer.org' => 'Mark Corbyn') + * ); + * print_r($header->getNameAddressStrings()); + * // array ( + * // 0 => Chris Corbyn , + * // 1 => Mark Corbyn + * // ) + * ?> + * + * + * @see getNameAddresses() + * @see toString() + * + * @return string[] + * + * @throws Swift_RfcComplianceException + */ + public function getNameAddressStrings() + { + return $this->_createNameAddressStrings($this->getNameAddresses()); + } + + /** + * Get all mailboxes in this Header as key=>value pairs. + * + * The key is the address and the value is the name (or null if none set). + * Example: + * + * 'Chris Corbyn', + * 'mark@swiftmailer.org' => 'Mark Corbyn') + * ); + * print_r($header->getNameAddresses()); + * // array ( + * // chris@swiftmailer.org => Chris Corbyn, + * // mark@swiftmailer.org => Mark Corbyn + * // ) + * ?> + * + * + * @see getAddresses() + * @see getNameAddressStrings() + * + * @return string[] + */ + public function getNameAddresses() + { + return $this->_mailboxes; + } + + /** + * Makes this Header represent a list of plain email addresses with no names. + * + * Example: + * + * setAddresses( + * array('one@domain.tld', 'two@domain.tld', 'three@domain.tld') + * ); + * ?> + * + * + * @see setNameAddresses() + * @see setValue() + * + * @param string[] $addresses + * + * @throws Swift_RfcComplianceException + */ + public function setAddresses($addresses) + { + $this->setNameAddresses(array_values((array) $addresses)); + } + + /** + * Get all email addresses in this Header. + * + * @see getNameAddresses() + * + * @return string[] + */ + public function getAddresses() + { + return array_keys($this->_mailboxes); + } + + /** + * Remove one or more addresses from this Header. + * + * @param string|string[] $addresses + */ + public function removeAddresses($addresses) + { + $this->setCachedValue(null); + foreach ((array) $addresses as $address) { + unset($this->_mailboxes[$address]); + } + } + + /** + * Get the string value of the body in this Header. + * + * This is not necessarily RFC 2822 compliant since folding white space will + * not be added at this stage (see {@link toString()} for that). + * + * @see toString() + * + * @return string + * + * @throws Swift_RfcComplianceException + */ + public function getFieldBody() + { + //Compute the string value of the header only if needed + if (is_null($this->getCachedValue())) { + $this->setCachedValue($this->createMailboxListString($this->_mailboxes)); + } + + return $this->getCachedValue(); + } + + // -- Points of extension + + /** + * Normalizes a user-input list of mailboxes into consistent key=>value pairs. + * + * @param string[] $mailboxes + * + * @return string[] + */ + protected function normalizeMailboxes(array $mailboxes) + { + $actualMailboxes = array(); + + foreach ($mailboxes as $key => $value) { + if (is_string($key)) { //key is email addr + $address = $key; + $name = $value; + } else { + $address = $value; + $name = null; + } + $this->_assertValidAddress($address); + $actualMailboxes[$address] = $name; + } + + return $actualMailboxes; + } + + /** + * Produces a compliant, formatted display-name based on the string given. + * + * @param string $displayName as displayed + * @param boolean $shorten the first line to make remove for header name + * + * @return string + */ + protected function createDisplayNameString($displayName, $shorten = false) + { + return $this->createPhrase($this, $displayName, + $this->getCharset(), $this->getEncoder(), $shorten + ); + } + + /** + * Creates a string form of all the mailboxes in the passed array. + * + * @param string[] $mailboxes + * + * @return string + * + * @throws Swift_RfcComplianceException + */ + protected function createMailboxListString(array $mailboxes) + { + return implode(', ', $this->_createNameAddressStrings($mailboxes)); + } + + /** + * Redefine the encoding requirements for mailboxes. + * + * Commas and semicolons are used to separate + * multiple addresses, and should therefore be encoded + * + * @param string $token + * + * @return boolean + */ + protected function tokenNeedsEncoding($token) + { + return preg_match('/[,;]/', $token) || parent::tokenNeedsEncoding($token); + } + + // -- Private methods + + /** + * Return an array of strings conforming the the name-addr spec of RFC 2822. + * + * @param string[] $mailboxes + * + * @return string[] + */ + private function _createNameAddressStrings(array $mailboxes) + { + $strings = array(); + + foreach ($mailboxes as $email => $name) { + $mailboxStr = $email; + if (!is_null($name)) { + $nameStr = $this->createDisplayNameString($name, empty($strings)); + $mailboxStr = $nameStr . ' <' . $mailboxStr . '>'; + } + $strings[] = $mailboxStr; + } + + return $strings; + } + + /** + * Throws an Exception if the address passed does not comply with RFC 2822. + * + * @param string $address + * + * @throws Swift_RfcComplianceException If invalid. + */ + private function _assertValidAddress($address) + { + if (!preg_match('/^' . $this->getGrammar()->getDefinition('addr-spec') . '$/D', + $address)) + { + throw new Swift_RfcComplianceException( + 'Address in mailbox given [' . $address . + '] does not comply with RFC 2822, 3.6.2.' + ); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php new file mode 100644 index 0000000..3ef628c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php @@ -0,0 +1,265 @@ +_paramEncoder = $paramEncoder; + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_PARAMETERIZED; + } + + /** + * Set the character set used in this Header. + * + * @param string $charset + */ + public function setCharset($charset) + { + parent::setCharset($charset); + if (isset($this->_paramEncoder)) { + $this->_paramEncoder->charsetChanged($charset); + } + } + + /** + * Set the value of $parameter. + * + * @param string $parameter + * @param string $value + */ + public function setParameter($parameter, $value) + { + $this->setParameters(array_merge($this->getParameters(), array($parameter => $value))); + } + + /** + * Get the value of $parameter. + * + * @param string $parameter + * + * @return string + */ + public function getParameter($parameter) + { + $params = $this->getParameters(); + + return array_key_exists($parameter, $params) + ? $params[$parameter] + : null; + } + + /** + * Set an associative array of parameter names mapped to values. + * + * @param string[] $parameters + */ + public function setParameters(array $parameters) + { + $this->clearCachedValueIf($this->_params != $parameters); + $this->_params = $parameters; + } + + /** + * Returns an associative array of parameter names mapped to values. + * + * @return string[] + */ + public function getParameters() + { + return $this->_params; + } + + /** + * Get the value of this header prepared for rendering. + * + * @return string + */ + public function getFieldBody() //TODO: Check caching here + { + $body = parent::getFieldBody(); + foreach ($this->_params as $name => $value) { + if (!is_null($value)) { + //Add the parameter + $body .= '; ' . $this->_createParameter($name, $value); + } + } + + return $body; + } + + // -- Protected methods + + /** + * Generate a list of all tokens in the final header. + * + * This doesn't need to be overridden in theory, but it is for implementation + * reasons to prevent potential breakage of attributes. + * + * @param string $string The string to tokenize + * + * @return array An array of tokens as strings + */ + protected function toTokens($string = null) + { + $tokens = parent::toTokens(parent::getFieldBody()); + + //Try creating any parameters + foreach ($this->_params as $name => $value) { + if (!is_null($value)) { + //Add the semi-colon separator + $tokens[count($tokens)-1] .= ';'; + $tokens = array_merge($tokens, $this->generateTokenLines( + ' ' . $this->_createParameter($name, $value) + )); + } + } + + return $tokens; + } + + // -- Private methods + + /** + * Render a RFC 2047 compliant header parameter from the $name and $value. + * + * @param string $name + * @param string $value + * + * @return string + */ + private function _createParameter($name, $value) + { + $origValue = $value; + + $encoded = false; + //Allow room for parameter name, indices, "=" and DQUOTEs + $maxValueLength = $this->getMaxLineLength() - strlen($name . '=*N"";') - 1; + $firstLineOffset = 0; + + //If it's not already a valid parameter value... + if (!preg_match('/^' . self::TOKEN_REGEX . '$/D', $value)) { + //TODO: text, or something else?? + //... and it's not ascii + if (!preg_match('/^' . $this->getGrammar()->getDefinition('text') . '*$/D', $value)) { + $encoded = true; + //Allow space for the indices, charset and language + $maxValueLength = $this->getMaxLineLength() - strlen($name . '*N*="";') - 1; + $firstLineOffset = strlen( + $this->getCharset() . "'" . $this->getLanguage() . "'" + ); + } + } + + //Encode if we need to + if ($encoded || strlen($value) > $maxValueLength) { + if (isset($this->_paramEncoder)) { + $value = $this->_paramEncoder->encodeString( + $origValue, $firstLineOffset, $maxValueLength, $this->getCharset() + ); + } else { //We have to go against RFC 2183/2231 in some areas for interoperability + $value = $this->getTokenAsEncodedWord($origValue); + $encoded = false; + } + } + + $valueLines = isset($this->_paramEncoder) ? explode("\r\n", $value) : array($value); + + //Need to add indices + if (count($valueLines) > 1) { + $paramLines = array(); + foreach ($valueLines as $i => $line) { + $paramLines[] = $name . '*' . $i . + $this->_getEndOfParameterValue($line, true, $i == 0); + } + + return implode(";\r\n ", $paramLines); + } else { + return $name . $this->_getEndOfParameterValue( + $valueLines[0], $encoded, true + ); + } + } + + /** + * Returns the parameter value from the "=" and beyond. + * + * @param string $value to append + * @param boolean $encoded + * @param boolean $firstLine + * + * @return string + */ + private function _getEndOfParameterValue($value, $encoded = false, $firstLine = false) + { + if (!preg_match('/^' . self::TOKEN_REGEX . '$/D', $value)) { + $value = '"' . $value . '"'; + } + $prepend = '='; + if ($encoded) { + $prepend = '*='; + if ($firstLine) { + $prepend = '*=' . $this->getCharset() . "'" . $this->getLanguage() . + "'"; + } + } + + return $prepend . $value; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/PathHeader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/PathHeader.php new file mode 100644 index 0000000..bfecf3f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/PathHeader.php @@ -0,0 +1,146 @@ +setFieldName($name); + parent::__construct($grammar); + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_PATH; + } + + /** + * Set the model for the field body. + * This method takes a string for an address. + * + * @param string $model + * + * @throws Swift_RfcComplianceException + */ + public function setFieldBodyModel($model) + { + $this->setAddress($model); + } + + /** + * Get the model for the field body. + * This method returns a string email address. + * + * @return mixed + */ + public function getFieldBodyModel() + { + return $this->getAddress(); + } + + /** + * Set the Address which should appear in this Header. + * + * @param string $address + * + * @throws Swift_RfcComplianceException + */ + public function setAddress($address) + { + if (is_null($address)) { + $this->_address = null; + } elseif ('' == $address) { + $this->_address = ''; + } else { + $this->_assertValidAddress($address); + $this->_address = $address; + } + $this->setCachedValue(null); + } + + /** + * Get the address which is used in this Header (if any). + * + * Null is returned if no address is set. + * + * @return string + */ + public function getAddress() + { + return $this->_address; + } + + /** + * Get the string value of the body in this Header. + * + * This is not necessarily RFC 2822 compliant since folding white space will + * not be added at this stage (see {@link toString()} for that). + * + * @see toString() + * + * @return string + */ + public function getFieldBody() + { + if (!$this->getCachedValue()) { + if (isset($this->_address)) { + $this->setCachedValue('<' . $this->_address . '>'); + } + } + + return $this->getCachedValue(); + } + + /** + * Throws an Exception if the address passed does not comply with RFC 2822. + * + * @param string $address + * + * @throws Swift_RfcComplianceException If address is invalid + */ + private function _assertValidAddress($address) + { + if (!preg_match('/^' . $this->getGrammar()->getDefinition('addr-spec') . '$/D', + $address)) + { + throw new Swift_RfcComplianceException( + 'Address set in PathHeader does not comply with addr-spec of RFC 2822.' + ); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php new file mode 100644 index 0000000..2de49b4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php @@ -0,0 +1,114 @@ +setFieldName($name); + $this->setEncoder($encoder); + parent::__construct($grammar); + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_TEXT; + } + + /** + * Set the model for the field body. + * + * This method takes a string for the field value. + * + * @param string $model + */ + public function setFieldBodyModel($model) + { + $this->setValue($model); + } + + /** + * Get the model for the field body. + * + * This method returns a string. + * + * @return string + */ + public function getFieldBodyModel() + { + return $this->getValue(); + } + + /** + * Get the (unencoded) value of this header. + * + * @return string + */ + public function getValue() + { + return $this->_value; + } + + /** + * Set the (unencoded) value of this header. + * + * @param string $value + */ + public function setValue($value) + { + $this->clearCachedValueIf($this->_value != $value); + $this->_value = $value; + } + + /** + * Get the value of this header prepared for rendering. + * + * @return string + */ + public function getFieldBody() + { + if (!$this->getCachedValue()) { + $this->setCachedValue( + $this->encodeWords($this, $this->_value) + ); + } + + return $this->getCachedValue(); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Message.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Message.php new file mode 100644 index 0000000..bce3af3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Message.php @@ -0,0 +1,225 @@ + 'Real Name'). + * + * If the second parameter is provided and the first is a string, then $name + * is associated with the address. + * + * @param mixed $address + * @param string $name optional + */ + public function setSender($address, $name = null); + + /** + * Get the sender address for this message. + * + * This has a higher significance than the From address. + * + * @return string + */ + public function getSender(); + + /** + * Set the From address of this message. + * + * It is permissible for multiple From addresses to be set using an array. + * + * If multiple From addresses are used, you SHOULD set the Sender address and + * according to RFC 2822, MUST set the sender address. + * + * An array can be used if display names are to be provided: i.e. + * array('email@address.com' => 'Real Name'). + * + * If the second parameter is provided and the first is a string, then $name + * is associated with the address. + * + * @param mixed $addresses + * @param string $name optional + */ + public function setFrom($addresses, $name = null); + + /** + * Get the From address(es) of this message. + * + * This method always returns an associative array where the keys are the + * addresses. + * + * @return string[] + */ + public function getFrom(); + + /** + * Set the Reply-To address(es). + * + * Any replies from the receiver will be sent to this address. + * + * It is permissible for multiple reply-to addresses to be set using an array. + * + * This method has the same synopsis as {@link setFrom()} and {@link setTo()}. + * + * If the second parameter is provided and the first is a string, then $name + * is associated with the address. + * + * @param mixed $addresses + * @param string $name optional + */ + public function setReplyTo($addresses, $name = null); + + /** + * Get the Reply-To addresses for this message. + * + * This method always returns an associative array where the keys provide the + * email addresses. + * + * @return string[] + */ + public function getReplyTo(); + + /** + * Set the To address(es). + * + * Recipients set in this field will receive a copy of this message. + * + * This method has the same synopsis as {@link setFrom()} and {@link setCc()}. + * + * If the second parameter is provided and the first is a string, then $name + * is associated with the address. + * + * @param mixed $addresses + * @param string $name optional + */ + public function setTo($addresses, $name = null); + + /** + * Get the To addresses for this message. + * + * This method always returns an associative array, whereby the keys provide + * the actual email addresses. + * + * @return string[] + */ + public function getTo(); + + /** + * Set the Cc address(es). + * + * Recipients set in this field will receive a 'carbon-copy' of this message. + * + * This method has the same synopsis as {@link setFrom()} and {@link setTo()}. + * + * @param mixed $addresses + * @param string $name optional + */ + public function setCc($addresses, $name = null); + + /** + * Get the Cc addresses for this message. + * + * This method always returns an associative array, whereby the keys provide + * the actual email addresses. + * + * @return string[] + */ + public function getCc(); + + /** + * Set the Bcc address(es). + * + * Recipients set in this field will receive a 'blind-carbon-copy' of this + * message. + * + * In other words, they will get the message, but any other recipients of the + * message will have no such knowledge of their receipt of it. + * + * This method has the same synopsis as {@link setFrom()} and {@link setTo()}. + * + * @param mixed $addresses + * @param string $name optional + */ + public function setBcc($addresses, $name = null); + + /** + * Get the Bcc addresses for this message. + * + * This method always returns an associative array, whereby the keys provide + * the actual email addresses. + * + * @return string[] + */ + public function getBcc(); +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimeEntity.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimeEntity.php new file mode 100644 index 0000000..bc9f2ad --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimeEntity.php @@ -0,0 +1,117 @@ +setContentType('text/plain'); + if (!is_null($charset)) { + $this->setCharset($charset); + } + } + + /** + * Set the body of this entity, either as a string, or as an instance of + * {@link Swift_OutputByteStream}. + * + * @param mixed $body + * @param string $contentType optional + * @param string $charset optional + * + * @return Swift_Mime_MimePart + */ + public function setBody($body, $contentType = null, $charset = null) + { + if (isset($charset)) { + $this->setCharset($charset); + } + $body = $this->_convertString($body); + + parent::setBody($body, $contentType); + + return $this; + } + + /** + * Get the character set of this entity. + * + * @return string + */ + public function getCharset() + { + return $this->_getHeaderParameter('Content-Type', 'charset'); + } + + /** + * Set the character set of this entity. + * + * @param string $charset + * + * @return Swift_Mime_MimePart + */ + public function setCharset($charset) + { + $this->_setHeaderParameter('Content-Type', 'charset', $charset); + if ($charset !== $this->_userCharset) { + $this->_clearCache(); + } + $this->_userCharset = $charset; + parent::charsetChanged($charset); + + return $this; + } + + /** + * Get the format of this entity (i.e. flowed or fixed). + * + * @return string + */ + public function getFormat() + { + return $this->_getHeaderParameter('Content-Type', 'format'); + } + + /** + * Set the format of this entity (flowed or fixed). + * + * @param string $format + * + * @return Swift_Mime_MimePart + */ + public function setFormat($format) + { + $this->_setHeaderParameter('Content-Type', 'format', $format); + $this->_userFormat = $format; + + return $this; + } + + /** + * Test if delsp is being used for this entity. + * + * @return boolean + */ + public function getDelSp() + { + return ($this->_getHeaderParameter('Content-Type', 'delsp') == 'yes') + ? true + : false; + } + + /** + * Turn delsp on or off for this entity. + * + * @param boolean $delsp + * + * @return Swift_Mime_MimePart + */ + public function setDelSp($delsp = true) + { + $this->_setHeaderParameter('Content-Type', 'delsp', $delsp ? 'yes' : null); + $this->_userDelSp = $delsp; + + return $this; + } + + /** + * Get the nesting level of this entity. + * + * @see LEVEL_TOP, LEVEL_ALTERNATIVE, LEVEL_MIXED, LEVEL_RELATED + * + * @return int + */ + public function getNestingLevel() + { + return $this->_nestingLevel; + } + + /** + * Receive notification that the charset has changed on this document, or a + * parent document. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->setCharset($charset); + } + + // -- Protected methods + + /** Fix the content-type and encoding of this entity */ + protected function _fixHeaders() + { + parent::_fixHeaders(); + if (count($this->getChildren())) { + $this->_setHeaderParameter('Content-Type', 'charset', null); + $this->_setHeaderParameter('Content-Type', 'format', null); + $this->_setHeaderParameter('Content-Type', 'delsp', null); + } else { + $this->setCharset($this->_userCharset); + $this->setFormat($this->_userFormat); + $this->setDelSp($this->_userDelSp); + } + } + + /** Set the nesting level of this entity */ + protected function _setNestingLevel($level) + { + $this->_nestingLevel = $level; + } + + /** Encode charset when charset is not utf-8 */ + protected function _convertString($string) + { + $charset = strtolower($this->getCharset()); + if (!in_array($charset, array('utf-8', 'iso-8859-1', ''))) { + // mb_convert_encoding must be the first one to check, since iconv cannot convert some words. + if (function_exists('mb_convert_encoding')) { + $string = mb_convert_encoding($string, $charset, 'utf-8'); + } elseif (function_exists('iconv')) { + $string = iconv($charset, 'utf-8//TRANSLIT//IGNORE', $string); + } else { + throw new Swift_SwiftException('No suitable convert encoding function (use UTF-8 as your charset or install the mbstring or iconv extension).'); + } + + return $string; + } + + return $string; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ParameterizedHeader.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ParameterizedHeader.php new file mode 100644 index 0000000..95172ec --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ParameterizedHeader.php @@ -0,0 +1,36 @@ +_encoder = $encoder; + $this->_paramEncoder = $paramEncoder; + $this->_grammar = $grammar; + $this->_charset = $charset; + } + + /** + * Create a new Mailbox Header with a list of $addresses. + * + * @param string $name + * @param array|string|null $addresses + * + * @return Swift_Mime_Header + */ + public function createMailboxHeader($name, $addresses = null) + { + $header = new Swift_Mime_Headers_MailboxHeader($name, $this->_encoder, $this->_grammar); + if (isset($addresses)) { + $header->setFieldBodyModel($addresses); + } + $this->_setHeaderCharset($header); + + return $header; + } + + /** + * Create a new Date header using $timestamp (UNIX time). + * @param string $name + * @param integer|null $timestamp + * + * @return Swift_Mime_Header + */ + public function createDateHeader($name, $timestamp = null) + { + $header = new Swift_Mime_Headers_DateHeader($name, $this->_grammar); + if (isset($timestamp)) { + $header->setFieldBodyModel($timestamp); + } + $this->_setHeaderCharset($header); + + return $header; + } + + /** + * Create a new basic text header with $name and $value. + * + * @param string $name + * @param string $value + * + * @return Swift_Mime_Header + */ + public function createTextHeader($name, $value = null) + { + $header = new Swift_Mime_Headers_UnstructuredHeader($name, $this->_encoder, $this->_grammar); + if (isset($value)) { + $header->setFieldBodyModel($value); + } + $this->_setHeaderCharset($header); + + return $header; + } + + /** + * Create a new ParameterizedHeader with $name, $value and $params. + * + * @param string $name + * @param string $value + * @param array $params + * + * @return Swift_Mime_ParameterizedHeader + */ + public function createParameterizedHeader($name, $value = null, + $params = array()) + { + $header = new Swift_Mime_Headers_ParameterizedHeader($name, + $this->_encoder, (strtolower($name) == 'content-disposition') + ? $this->_paramEncoder + : null, + $this->_grammar + ); + if (isset($value)) { + $header->setFieldBodyModel($value); + } + foreach ($params as $k => $v) { + $header->setParameter($k, $v); + } + $this->_setHeaderCharset($header); + + return $header; + } + + /** + * Create a new ID header for Message-ID or Content-ID. + * + * @param string $name + * @param string|array $ids + * + * @return Swift_Mime_Header + */ + public function createIdHeader($name, $ids = null) + { + $header = new Swift_Mime_Headers_IdentificationHeader($name, $this->_grammar); + if (isset($ids)) { + $header->setFieldBodyModel($ids); + } + $this->_setHeaderCharset($header); + + return $header; + } + + /** + * Create a new Path header with an address (path) in it. + * + * @param string $name + * @param string $path + * + * @return Swift_Mime_Header + */ + public function createPathHeader($name, $path = null) + { + $header = new Swift_Mime_Headers_PathHeader($name, $this->_grammar); + if (isset($path)) { + $header->setFieldBodyModel($path); + } + $this->_setHeaderCharset($header); + + return $header; + } + + /** + * Notify this observer that the entity's charset has changed. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->_charset = $charset; + $this->_encoder->charsetChanged($charset); + $this->_paramEncoder->charsetChanged($charset); + } + + // -- Private methods + + /** Apply the charset to the Header */ + private function _setHeaderCharset(Swift_Mime_Header $header) + { + if (isset($this->_charset)) { + $header->setCharset($this->_charset); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderSet.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderSet.php new file mode 100644 index 0000000..e06f936 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderSet.php @@ -0,0 +1,387 @@ +_factory = $factory; + if (isset($charset)) { + $this->setCharset($charset); + } + } + + /** + * Set the charset used by these headers. + * + * @param string $charset + */ + public function setCharset($charset) + { + $this->_charset = $charset; + $this->_factory->charsetChanged($charset); + $this->_notifyHeadersOfCharset($charset); + } + + /** + * Add a new Mailbox Header with a list of $addresses. + * + * @param string $name + * @param array|string $addresses + */ + public function addMailboxHeader($name, $addresses = null) + { + $this->_storeHeader($name, + $this->_factory->createMailboxHeader($name, $addresses)); + } + + /** + * Add a new Date header using $timestamp (UNIX time). + * + * @param string $name + * @param integer $timestamp + */ + public function addDateHeader($name, $timestamp = null) + { + $this->_storeHeader($name, + $this->_factory->createDateHeader($name, $timestamp)); + } + + /** + * Add a new basic text header with $name and $value. + * + * @param string $name + * @param string $value + */ + public function addTextHeader($name, $value = null) + { + $this->_storeHeader($name, + $this->_factory->createTextHeader($name, $value)); + } + + /** + * Add a new ParameterizedHeader with $name, $value and $params. + * + * @param string $name + * @param string $value + * @param array $params + */ + public function addParameterizedHeader($name, $value = null, $params = array()) + { + $this->_storeHeader($name, $this->_factory->createParameterizedHeader($name, $value, $params)); + } + + /** + * Add a new ID header for Message-ID or Content-ID. + * + * @param string $name + * @param string|array $ids + */ + public function addIdHeader($name, $ids = null) + { + $this->_storeHeader($name, $this->_factory->createIdHeader($name, $ids)); + } + + /** + * Add a new Path header with an address (path) in it. + * + * @param string $name + * @param string $path + */ + public function addPathHeader($name, $path = null) + { + $this->_storeHeader($name, $this->_factory->createPathHeader($name, $path)); + } + + /** + * Returns true if at least one header with the given $name exists. + * + * If multiple headers match, the actual one may be specified by $index. + * + * @param string $name + * @param integer $index + * + * @return boolean + */ + public function has($name, $index = 0) + { + $lowerName = strtolower($name); + + return array_key_exists($lowerName, $this->_headers) && array_key_exists($index, $this->_headers[$lowerName]); + } + + /** + * Set a header in the HeaderSet. + * + * The header may be a previously fetched header via {@link get()} or it may + * be one that has been created separately. + * + * If $index is specified, the header will be inserted into the set at this + * offset. + * + * @param Swift_Mime_Header $header + * @param integer $index + */ + public function set(Swift_Mime_Header $header, $index = 0) + { + $this->_storeHeader($header->getFieldName(), $header, $index); + } + + /** + * Get the header with the given $name. + * + * If multiple headers match, the actual one may be specified by $index. + * Returns NULL if none present. + * + * @param string $name + * @param integer $index + * + * @return Swift_Mime_Header + */ + public function get($name, $index = 0) + { + if ($this->has($name, $index)) { + $lowerName = strtolower($name); + + return $this->_headers[$lowerName][$index]; + } + } + + /** + * Get all headers with the given $name. + * + * @param string $name + * + * @return array + */ + public function getAll($name = null) + { + if (!isset($name)) { + $headers = array(); + foreach ($this->_headers as $collection) { + $headers = array_merge($headers, $collection); + } + + return $headers; + } + + $lowerName = strtolower($name); + if (!array_key_exists($lowerName, $this->_headers)) { + return array(); + } + + return $this->_headers[$lowerName]; + } + + /** + * Return the name of all Headers + * + * @return array + */ + public function listAll() + { + $headers = $this->_headers; + if ($this->_canSort()) { + uksort($headers, array($this, '_sortHeaders')); + } + + return array_keys($headers); + } + + /** + * Remove the header with the given $name if it's set. + * + * If multiple headers match, the actual one may be specified by $index. + * + * @param string $name + * @param integer $index + */ + public function remove($name, $index = 0) + { + $lowerName = strtolower($name); + unset($this->_headers[$lowerName][$index]); + } + + /** + * Remove all headers with the given $name. + * + * @param string $name + */ + public function removeAll($name) + { + $lowerName = strtolower($name); + unset($this->_headers[$lowerName]); + } + + /** + * Create a new instance of this HeaderSet. + * + * @return Swift_Mime_HeaderSet + */ + public function newInstance() + { + return new self($this->_factory); + } + + /** + * Define a list of Header names as an array in the correct order. + * + * These Headers will be output in the given order where present. + * + * @param array $sequence + */ + public function defineOrdering(array $sequence) + { + $this->_order = array_flip(array_map('strtolower', $sequence)); + } + + /** + * Set a list of header names which must always be displayed when set. + * + * Usually headers without a field value won't be output unless set here. + * + * @param array $names + */ + public function setAlwaysDisplayed(array $names) + { + $this->_required = array_flip(array_map('strtolower', $names)); + } + + /** + * Notify this observer that the entity's charset has changed. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->setCharset($charset); + } + + /** + * Returns a string with a representation of all headers. + * + * @return string + */ + public function toString() + { + $string = ''; + $headers = $this->_headers; + if ($this->_canSort()) { + uksort($headers, array($this, '_sortHeaders')); + } + foreach ($headers as $collection) { + foreach ($collection as $header) { + if ($this->_isDisplayed($header) || $header->getFieldBody() != '') { + $string .= $header->toString(); + } + } + } + + return $string; + } + + /** + * Returns a string representation of this object. + * + * @return string + * + * @see toString() + */ + public function __toString() + { + return $this->toString(); + } + + // -- Private methods + + /** Save a Header to the internal collection */ + private function _storeHeader($name, Swift_Mime_Header $header, $offset = null) + { + if (!isset($this->_headers[strtolower($name)])) { + $this->_headers[strtolower($name)] = array(); + } + if (!isset($offset)) { + $this->_headers[strtolower($name)][] = $header; + } else { + $this->_headers[strtolower($name)][$offset] = $header; + } + } + + /** Test if the headers can be sorted */ + private function _canSort() + { + return count($this->_order) > 0; + } + + /** uksort() algorithm for Header ordering */ + private function _sortHeaders($a, $b) + { + $lowerA = strtolower($a); + $lowerB = strtolower($b); + $aPos = array_key_exists($lowerA, $this->_order) + ? $this->_order[$lowerA] + : -1; + $bPos = array_key_exists($lowerB, $this->_order) + ? $this->_order[$lowerB] + : -1; + + if ($aPos == -1) { + return 1; + } elseif ($bPos == -1) { + return -1; + } + + return ($aPos < $bPos) ? -1 : 1; + } + + /** Test if the given Header is always displayed */ + private function _isDisplayed(Swift_Mime_Header $header) + { + return array_key_exists(strtolower($header->getFieldName()), $this->_required); + } + + /** Notify all Headers of the new charset */ + private function _notifyHeadersOfCharset($charset) + { + foreach ($this->_headers as $headerGroup) { + foreach ($headerGroup as $header) { + $header->setCharset($charset); + } + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMessage.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMessage.php new file mode 100644 index 0000000..b203644 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMessage.php @@ -0,0 +1,655 @@ +getHeaders()->defineOrdering(array( + 'Return-Path', + 'Received', + 'DKIM-Signature', + 'DomainKey-Signature', + 'Sender', + 'Message-ID', + 'Date', + 'Subject', + 'From', + 'Reply-To', + 'To', + 'Cc', + 'Bcc', + 'MIME-Version', + 'Content-Type', + 'Content-Transfer-Encoding' + )); + $this->getHeaders()->setAlwaysDisplayed(array('Date', 'Message-ID', 'From')); + $this->getHeaders()->addTextHeader('MIME-Version', '1.0'); + $this->setDate(time()); + $this->setId($this->getId()); + $this->getHeaders()->addMailboxHeader('From'); + } + + /** + * Always returns {@link LEVEL_TOP} for a message instance. + * + * @return int + */ + public function getNestingLevel() + { + return self::LEVEL_TOP; + } + + /** + * Set the subject of this message. + * + * @param string $subject + * + * @return Swift_Mime_SimpleMessage + */ + public function setSubject($subject) + { + if (!$this->_setHeaderFieldModel('Subject', $subject)) { + $this->getHeaders()->addTextHeader('Subject', $subject); + } + + return $this; + } + + /** + * Get the subject of this message. + * + * @return string + */ + public function getSubject() + { + return $this->_getHeaderFieldModel('Subject'); + } + + /** + * Set the date at which this message was created. + * + * @param integer $date + * + * @return Swift_Mime_SimpleMessage + */ + public function setDate($date) + { + if (!$this->_setHeaderFieldModel('Date', $date)) { + $this->getHeaders()->addDateHeader('Date', $date); + } + + return $this; + } + + /** + * Get the date at which this message was created. + * + * @return integer + */ + public function getDate() + { + return $this->_getHeaderFieldModel('Date'); + } + + /** + * Set the return-path (the bounce address) of this message. + * + * @param string $address + * + * @return Swift_Mime_SimpleMessage + */ + public function setReturnPath($address) + { + if (!$this->_setHeaderFieldModel('Return-Path', $address)) { + $this->getHeaders()->addPathHeader('Return-Path', $address); + } + + return $this; + } + + /** + * Get the return-path (bounce address) of this message. + * + * @return string + */ + public function getReturnPath() + { + return $this->_getHeaderFieldModel('Return-Path'); + } + + /** + * Set the sender of this message. + * + * This does not override the From field, but it has a higher significance. + * + * @param string $address + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function setSender($address, $name = null) + { + if (!is_array($address) && isset($name)) { + $address = array($address => $name); + } + + if (!$this->_setHeaderFieldModel('Sender', (array) $address)) { + $this->getHeaders()->addMailboxHeader('Sender', (array) $address); + } + + return $this; + } + + /** + * Get the sender of this message. + * + * @return string + */ + public function getSender() + { + return $this->_getHeaderFieldModel('Sender'); + } + + /** + * Add a From: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function addFrom($address, $name = null) + { + $current = $this->getFrom(); + $current[$address] = $name; + + return $this->setFrom($current); + } + + /** + * Set the from address of this message. + * + * You may pass an array of addresses if this message is from multiple people. + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param string $addresses + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function setFrom($addresses, $name = null) + { + if (!is_array($addresses) && isset($name)) { + $addresses = array($addresses => $name); + } + + if (!$this->_setHeaderFieldModel('From', (array) $addresses)) { + $this->getHeaders()->addMailboxHeader('From', (array) $addresses); + } + + return $this; + } + + /** + * Get the from address of this message. + * + * @return string + */ + public function getFrom() + { + return $this->_getHeaderFieldModel('From'); + } + + /** + * Add a Reply-To: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function addReplyTo($address, $name = null) + { + $current = $this->getReplyTo(); + $current[$address] = $name; + + return $this->setReplyTo($current); + } + + /** + * Set the reply-to address of this message. + * + * You may pass an array of addresses if replies will go to multiple people. + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param string $addresses + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function setReplyTo($addresses, $name = null) + { + if (!is_array($addresses) && isset($name)) { + $addresses = array($addresses => $name); + } + + if (!$this->_setHeaderFieldModel('Reply-To', (array) $addresses)) { + $this->getHeaders()->addMailboxHeader('Reply-To', (array) $addresses); + } + + return $this; + } + + /** + * Get the reply-to address of this message. + * + * @return string + */ + public function getReplyTo() + { + return $this->_getHeaderFieldModel('Reply-To'); + } + + /** + * Add a To: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function addTo($address, $name = null) + { + $current = $this->getTo(); + $current[$address] = $name; + + return $this->setTo($current); + } + + /** + * Set the to addresses of this message. + * + * If multiple recipients will receive the message and array should be used. + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param mixed $addresses + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function setTo($addresses, $name = null) + { + if (!is_array($addresses) && isset($name)) { + $addresses = array($addresses => $name); + } + + if (!$this->_setHeaderFieldModel('To', (array) $addresses)) { + $this->getHeaders()->addMailboxHeader('To', (array) $addresses); + } + + return $this; + } + + /** + * Get the To addresses of this message. + * + * @return array + */ + public function getTo() + { + return $this->_getHeaderFieldModel('To'); + } + + /** + * Add a Cc: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function addCc($address, $name = null) + { + $current = $this->getCc(); + $current[$address] = $name; + + return $this->setCc($current); + } + + /** + * Set the Cc addresses of this message. + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param mixed $addresses + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function setCc($addresses, $name = null) + { + if (!is_array($addresses) && isset($name)) { + $addresses = array($addresses => $name); + } + + if (!$this->_setHeaderFieldModel('Cc', (array) $addresses)) { + $this->getHeaders()->addMailboxHeader('Cc', (array) $addresses); + } + + return $this; + } + + /** + * Get the Cc address of this message. + * + * @return array + */ + public function getCc() + { + return $this->_getHeaderFieldModel('Cc'); + } + + /** + * Add a Bcc: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function addBcc($address, $name = null) + { + $current = $this->getBcc(); + $current[$address] = $name; + + return $this->setBcc($current); + } + + /** + * Set the Bcc addresses of this message. + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param mixed $addresses + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function setBcc($addresses, $name = null) + { + if (!is_array($addresses) && isset($name)) { + $addresses = array($addresses => $name); + } + + if (!$this->_setHeaderFieldModel('Bcc', (array) $addresses)) { + $this->getHeaders()->addMailboxHeader('Bcc', (array) $addresses); + } + + return $this; + } + + /** + * Get the Bcc addresses of this message. + * + * @return array + */ + public function getBcc() + { + return $this->_getHeaderFieldModel('Bcc'); + } + + /** + * Set the priority of this message. + * + * The value is an integer where 1 is the highest priority and 5 is the lowest. + * + * @param integer $priority + * + * @return Swift_Mime_SimpleMessage + */ + public function setPriority($priority) + { + $priorityMap = array( + 1 => 'Highest', + 2 => 'High', + 3 => 'Normal', + 4 => 'Low', + 5 => 'Lowest' + ); + $pMapKeys = array_keys($priorityMap); + if ($priority > max($pMapKeys)) { + $priority = max($pMapKeys); + } elseif ($priority < min($pMapKeys)) { + $priority = min($pMapKeys); + } + if (!$this->_setHeaderFieldModel('X-Priority', + sprintf('%d (%s)', $priority, $priorityMap[$priority]))) + { + $this->getHeaders()->addTextHeader('X-Priority', + sprintf('%d (%s)', $priority, $priorityMap[$priority])); + } + + return $this; + } + + /** + * Get the priority of this message. + * + * The returned value is an integer where 1 is the highest priority and 5 + * is the lowest. + * + * @return integer + */ + public function getPriority() + { + list($priority) = sscanf($this->_getHeaderFieldModel('X-Priority'), + '%[1-5]' + ); + + return isset($priority) ? $priority : 3; + } + + /** + * Ask for a delivery receipt from the recipient to be sent to $addresses + * + * @param array $addresses + * + * @return Swift_Mime_SimpleMessage + */ + public function setReadReceiptTo($addresses) + { + if (!$this->_setHeaderFieldModel('Disposition-Notification-To', $addresses)) { + $this->getHeaders() + ->addMailboxHeader('Disposition-Notification-To', $addresses); + } + + return $this; + } + + /** + * Get the addresses to which a read-receipt will be sent. + * + * @return string + */ + public function getReadReceiptTo() + { + return $this->_getHeaderFieldModel('Disposition-Notification-To'); + } + + /** + * Attach a {@link Swift_Mime_MimeEntity} such as an Attachment or MimePart. + * + * @param Swift_Mime_MimeEntity $entity + * + * @return Swift_Mime_SimpleMessage + */ + public function attach(Swift_Mime_MimeEntity $entity) + { + $this->setChildren(array_merge($this->getChildren(), array($entity))); + + return $this; + } + + /** + * Remove an already attached entity. + * + * @param Swift_Mime_MimeEntity $entity + * + * @return Swift_Mime_SimpleMessage + */ + public function detach(Swift_Mime_MimeEntity $entity) + { + $newChildren = array(); + foreach ($this->getChildren() as $child) { + if ($entity !== $child) { + $newChildren[] = $child; + } + } + $this->setChildren($newChildren); + + return $this; + } + + /** + * Attach a {@link Swift_Mime_MimeEntity} and return it's CID source. + * This method should be used when embedding images or other data in a message. + * + * @param Swift_Mime_MimeEntity $entity + * + * @return string + */ + public function embed(Swift_Mime_MimeEntity $entity) + { + $this->attach($entity); + + return 'cid:' . $entity->getId(); + } + + /** + * Get this message as a complete string. + * + * @return string + */ + public function toString() + { + if (count($children = $this->getChildren()) > 0 && $this->getBody() != '') { + $this->setChildren(array_merge(array($this->_becomeMimePart()), $children)); + $string = parent::toString(); + $this->setChildren($children); + } else { + $string = parent::toString(); + } + + return $string; + } + + /** + * Returns a string representation of this object. + * + * @see toString() + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Write this message to a {@link Swift_InputByteStream}. + * + * @param Swift_InputByteStream $is + */ + public function toByteStream(Swift_InputByteStream $is) + { + if (count($children = $this->getChildren()) > 0 && $this->getBody() != '') { + $this->setChildren(array_merge(array($this->_becomeMimePart()), $children)); + parent::toByteStream($is); + $this->setChildren($children); + } else { + parent::toByteStream($is); + } + } + + // -- Protected methods + + /** @see Swift_Mime_SimpleMimeEntity::_getIdField() */ + protected function _getIdField() + { + return 'Message-ID'; + } + + /** Turn the body of this message into a child of itself if needed */ + protected function _becomeMimePart() + { + $part = new parent($this->getHeaders()->newInstance(), $this->getEncoder(), + $this->_getCache(), $this->_getGrammar(), $this->_userCharset + ); + $part->setContentType($this->_userContentType); + $part->setBody($this->getBody()); + $part->setFormat($this->_userFormat); + $part->setDelSp($this->_userDelSp); + $part->_setNestingLevel($this->_getTopNestingLevel()); + + return $part; + } + + // -- Private methods + + /** Get the highest nesting level nested inside this message */ + private function _getTopNestingLevel() + { + $highestLevel = $this->getNestingLevel(); + foreach ($this->getChildren() as $child) { + $childLevel = $child->getNestingLevel(); + if ($highestLevel < $childLevel) { + $highestLevel = $childLevel; + } + } + + return $highestLevel; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php new file mode 100644 index 0000000..36e10ff --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php @@ -0,0 +1,857 @@ + array(self::LEVEL_TOP, self::LEVEL_MIXED), + 'multipart/alternative' => array(self::LEVEL_MIXED, self::LEVEL_ALTERNATIVE), + 'multipart/related' => array(self::LEVEL_ALTERNATIVE, self::LEVEL_RELATED) + ); + + /** A set of filter rules to define what level an entity should be nested at */ + private $_compoundLevelFilters = array(); + + /** The nesting level of this entity */ + private $_nestingLevel = self::LEVEL_ALTERNATIVE; + + /** A KeyCache instance used during encoding and streaming */ + private $_cache; + + /** Direct descendants of this entity */ + private $_immediateChildren = array(); + + /** All descendants of this entity */ + private $_children = array(); + + /** The maximum line length of the body of this entity */ + private $_maxLineLength = 78; + + /** The order in which alternative mime types should appear */ + private $_alternativePartOrder = array( + 'text/plain' => 1, + 'text/html' => 2, + 'multipart/related' => 3 + ); + + /** The CID of this entity */ + private $_id; + + /** The key used for accessing the cache */ + private $_cacheKey; + + protected $_userContentType; + + /** + * Create a new SimpleMimeEntity with $headers, $encoder and $cache. + * + * @param Swift_Mime_HeaderSet $headers + * @param Swift_Mime_ContentEncoder $encoder + * @param Swift_KeyCache $cache + * @param Swift_Mime_Grammar $grammar + */ + public function __construct(Swift_Mime_HeaderSet $headers, Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache, Swift_Mime_Grammar $grammar) + { + $this->_cacheKey = md5(uniqid(getmypid().mt_rand(), true)); + $this->_cache = $cache; + $this->_headers = $headers; + $this->_grammar = $grammar; + $this->setEncoder($encoder); + $this->_headers->defineOrdering(array('Content-Type', 'Content-Transfer-Encoding')); + + // This array specifies that, when the entire MIME document contains + // $compoundLevel, then for each child within $level, if its Content-Type + // is $contentType then it should be treated as if it's level is + // $neededLevel instead. I tried to write that unambiguously! :-\ + // Data Structure: + // array ( + // $compoundLevel => array( + // $level => array( + // $contentType => $neededLevel + // ) + // ) + // ) + + $this->_compoundLevelFilters = array( + (self::LEVEL_ALTERNATIVE + self::LEVEL_RELATED) => array( + self::LEVEL_ALTERNATIVE => array( + 'text/plain' => self::LEVEL_ALTERNATIVE, + 'text/html' => self::LEVEL_RELATED + ) + ) + ); + + $this->_id = $this->getRandomId(); + } + + /** + * Generate a new Content-ID or Message-ID for this MIME entity. + * + * @return string + */ + public function generateId() + { + $this->setId($this->getRandomId()); + + return $this->_id; + } + + /** + * Get the {@link Swift_Mime_HeaderSet} for this entity. + * + * @return Swift_Mime_HeaderSet + */ + public function getHeaders() + { + return $this->_headers; + } + + /** + * Get the nesting level of this entity. + * + * @see LEVEL_TOP, LEVEL_MIXED, LEVEL_RELATED, LEVEL_ALTERNATIVE + * + * @return integer + */ + public function getNestingLevel() + { + return $this->_nestingLevel; + } + + /** + * Get the Content-type of this entity. + * + * @return string + */ + public function getContentType() + { + return $this->_getHeaderFieldModel('Content-Type'); + } + + /** + * Set the Content-type of this entity. + * + * @param string $type + * + * @return Swift_Mime_SimpleMimeEntity + */ + public function setContentType($type) + { + $this->_setContentTypeInHeaders($type); + // Keep track of the value so that if the content-type changes automatically + // due to added child entities, it can be restored if they are later removed + $this->_userContentType = $type; + + return $this; + } + + /** + * Get the CID of this entity. + * + * The CID will only be present in headers if a Content-ID header is present. + * + * @return string + */ + public function getId() + { + return $this->_headers->has($this->_getIdField()) ? current((array) $this->_getHeaderFieldModel($this->_getIdField())) : $this->_id; + } + + /** + * Set the CID of this entity. + * + * @param string $id + * + * @return Swift_Mime_SimpleMimeEntity + */ + public function setId($id) + { + if (!$this->_setHeaderFieldModel($this->_getIdField(), $id)) { + $this->_headers->addIdHeader($this->_getIdField(), $id); + } + $this->_id = $id; + + return $this; + } + + /** + * Get the description of this entity. + * + * This value comes from the Content-Description header if set. + * + * @return string + */ + public function getDescription() + { + return $this->_getHeaderFieldModel('Content-Description'); + } + + /** + * Set the description of this entity. + * + * This method sets a value in the Content-ID header. + * + * @param string $description + * + * @return Swift_Mime_SimpleMimeEntity + */ + public function setDescription($description) + { + if (!$this->_setHeaderFieldModel('Content-Description', $description)) { + $this->_headers->addTextHeader('Content-Description', $description); + } + + return $this; + } + + /** + * Get the maximum line length of the body of this entity. + * + * @return integer + */ + public function getMaxLineLength() + { + return $this->_maxLineLength; + } + + /** + * Set the maximum line length of lines in this body. + * + * Though not enforced by the library, lines should not exceed 1000 chars. + * + * @param integer $length + * + * @return Swift_Mime_SimpleMimeEntity + */ + public function setMaxLineLength($length) + { + $this->_maxLineLength = $length; + + return $this; + } + + /** + * Get all children added to this entity. + * + * @return array of Swift_Mime_Entity + */ + public function getChildren() + { + return $this->_children; + } + + /** + * Set all children of this entity. + * + * @param array $children Swift_Mime_Entity instances + * @param integer $compoundLevel For internal use only + * + * @return Swift_Mime_SimpleMimeEntity + */ + public function setChildren(array $children, $compoundLevel = null) + { + //TODO: Try to refactor this logic + + $compoundLevel = isset($compoundLevel) + ? $compoundLevel + : $this->_getCompoundLevel($children) + ; + + $immediateChildren = array(); + $grandchildren = array(); + $newContentType = $this->_userContentType; + + foreach ($children as $child) { + $level = $this->_getNeededChildLevel($child, $compoundLevel); + if (empty($immediateChildren)) { //first iteration + $immediateChildren = array($child); + } else { + $nextLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel); + if ($nextLevel == $level) { + $immediateChildren[] = $child; + } elseif ($level < $nextLevel) { + //Re-assign immediateChildren to grandchildren + $grandchildren = array_merge($grandchildren, $immediateChildren); + //Set new children + $immediateChildren = array($child); + } else { + $grandchildren[] = $child; + } + } + } + + if (!empty($immediateChildren)) { + $lowestLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel); + + //Determine which composite media type is needed to accommodate the + // immediate children + foreach ($this->_compositeRanges as $mediaType => $range) { + if ($lowestLevel > $range[0] + && $lowestLevel <= $range[1]) + { + $newContentType = $mediaType; + break; + } + } + + //Put any grandchildren in a subpart + if (!empty($grandchildren)) { + $subentity = $this->_createChild(); + $subentity->_setNestingLevel($lowestLevel); + $subentity->setChildren($grandchildren, $compoundLevel); + array_unshift($immediateChildren, $subentity); + } + } + + $this->_immediateChildren = $immediateChildren; + $this->_children = $children; + $this->_setContentTypeInHeaders($newContentType); + $this->_fixHeaders(); + $this->_sortChildren(); + + return $this; + } + + /** + * Get the body of this entity as a string. + * + * @return string + */ + public function getBody() + { + return ($this->_body instanceof Swift_OutputByteStream) + ? $this->_readStream($this->_body) + : $this->_body; + } + + /** + * Set the body of this entity, either as a string, or as an instance of + * {@link Swift_OutputByteStream}. + * + * @param mixed $body + * @param string $contentType optional + * + * @return Swift_Mime_SimpleMimeEntity + */ + public function setBody($body, $contentType = null) + { + if ($body !== $this->_body) { + $this->_clearCache(); + } + + $this->_body = $body; + if (isset($contentType)) { + $this->setContentType($contentType); + } + + return $this; + } + + /** + * Get the encoder used for the body of this entity. + * + * @return Swift_Mime_ContentEncoder + */ + public function getEncoder() + { + return $this->_encoder; + } + + /** + * Set the encoder used for the body of this entity. + * + * @param Swift_Mime_ContentEncoder $encoder + * + * @return Swift_Mime_SimpleMimeEntity + */ + public function setEncoder(Swift_Mime_ContentEncoder $encoder) + { + if ($encoder !== $this->_encoder) { + $this->_clearCache(); + } + + $this->_encoder = $encoder; + $this->_setEncoding($encoder->getName()); + $this->_notifyEncoderChanged($encoder); + + return $this; + } + + /** + * Get the boundary used to separate children in this entity. + * + * @return string + */ + public function getBoundary() + { + if (!isset($this->_boundary)) { + $this->_boundary = '_=_swift_v4_' . time() . '_' . md5(getmypid().mt_rand().uniqid('', true)) . '_=_'; + } + + return $this->_boundary; + } + + /** + * Set the boundary used to separate children in this entity. + * + * @param string $boundary + * + * @return Swift_Mime_SimpleMimeEntity + * + * @throws Swift_RfcComplianceException + */ + public function setBoundary($boundary) + { + $this->_assertValidBoundary($boundary); + $this->_boundary = $boundary; + + return $this; + } + + /** + * Receive notification that the charset of this entity, or a parent entity + * has changed. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->_notifyCharsetChanged($charset); + } + + /** + * Receive notification that the encoder of this entity or a parent entity + * has changed. + * + * @param Swift_Mime_ContentEncoder $encoder + */ + public function encoderChanged(Swift_Mime_ContentEncoder $encoder) + { + $this->_notifyEncoderChanged($encoder); + } + + /** + * Get this entire entity as a string. + * + * @return string + */ + public function toString() + { + $string = $this->_headers->toString(); + $string .= $this->_bodyToString(); + + return $string; + } + + /** + * Get this entire entity as a string. + * + * @return string + */ + protected function _bodyToString() + { + $string = ''; + + if (isset($this->_body) && empty($this->_immediateChildren)) { + if ($this->_cache->hasKey($this->_cacheKey, 'body')) { + $body = $this->_cache->getString($this->_cacheKey, 'body'); + } else { + $body = "\r\n" . $this->_encoder->encodeString($this->getBody(), 0, + $this->getMaxLineLength() + ); + $this->_cache->setString($this->_cacheKey, 'body', $body, + Swift_KeyCache::MODE_WRITE + ); + } + $string .= $body; + } + + if (!empty($this->_immediateChildren)) { + foreach ($this->_immediateChildren as $child) { + $string .= "\r\n\r\n--" . $this->getBoundary() . "\r\n"; + $string .= $child->toString(); + } + $string .= "\r\n\r\n--" . $this->getBoundary() . "--\r\n"; + } + + return $string; + } + + /** + * Returns a string representation of this object. + * + * @see toString() + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Write this entire entity to a {@see Swift_InputByteStream}. + * + * @param Swift_InputByteStream + */ + public function toByteStream(Swift_InputByteStream $is) + { + $is->write($this->_headers->toString()); + $is->commit(); + + $this->_bodyToByteStream($is); + } + + /** + * Write this entire entity to a {@link Swift_InputByteStream}. + * + * @param Swift_InputByteStream + */ + protected function _bodyToByteStream(Swift_InputByteStream $is) + { + if (empty($this->_immediateChildren)) { + if (isset($this->_body)) { + if ($this->_cache->hasKey($this->_cacheKey, 'body')) { + $this->_cache->exportToByteStream($this->_cacheKey, 'body', $is); + } else { + $cacheIs = $this->_cache->getInputByteStream($this->_cacheKey, 'body'); + if ($cacheIs) { + $is->bind($cacheIs); + } + + $is->write("\r\n"); + + if ($this->_body instanceof Swift_OutputByteStream) { + $this->_body->setReadPointer(0); + + $this->_encoder->encodeByteStream($this->_body, $is, 0, $this->getMaxLineLength()); + } else { + $is->write($this->_encoder->encodeString($this->getBody(), 0, $this->getMaxLineLength())); + } + + if ($cacheIs) { + $is->unbind($cacheIs); + } + } + } + } + + if (!empty($this->_immediateChildren)) { + foreach ($this->_immediateChildren as $child) { + $is->write("\r\n\r\n--" . $this->getBoundary() . "\r\n"); + $child->toByteStream($is); + } + $is->write("\r\n\r\n--" . $this->getBoundary() . "--\r\n"); + } + } + + // -- Protected methods + + /** + * Get the name of the header that provides the ID of this entity + */ + protected function _getIdField() + { + return 'Content-ID'; + } + + /** + * Get the model data (usually an array or a string) for $field. + */ + protected function _getHeaderFieldModel($field) + { + if ($this->_headers->has($field)) { + return $this->_headers->get($field)->getFieldBodyModel(); + } + } + + /** + * Set the model data for $field. + */ + protected function _setHeaderFieldModel($field, $model) + { + if ($this->_headers->has($field)) { + $this->_headers->get($field)->setFieldBodyModel($model); + + return true; + } else { + return false; + } + } + + /** + * Get the parameter value of $parameter on $field header. + */ + protected function _getHeaderParameter($field, $parameter) + { + if ($this->_headers->has($field)) { + return $this->_headers->get($field)->getParameter($parameter); + } + } + + /** + * Set the parameter value of $parameter on $field header. + */ + protected function _setHeaderParameter($field, $parameter, $value) + { + if ($this->_headers->has($field)) { + $this->_headers->get($field)->setParameter($parameter, $value); + + return true; + } else { + return false; + } + } + + /** + * Re-evaluate what content type and encoding should be used on this entity. + */ + protected function _fixHeaders() + { + if (count($this->_immediateChildren)) { + $this->_setHeaderParameter('Content-Type', 'boundary', + $this->getBoundary() + ); + $this->_headers->remove('Content-Transfer-Encoding'); + } else { + $this->_setHeaderParameter('Content-Type', 'boundary', null); + $this->_setEncoding($this->_encoder->getName()); + } + } + + /** + * Get the KeyCache used in this entity. + * + * @return Swift_KeyCache + */ + protected function _getCache() + { + return $this->_cache; + } + + /** + * Get the grammar used for validation. + * + * @return Swift_Mime_Grammar + */ + protected function _getGrammar() + { + return $this->_grammar; + } + + /** + * Empty the KeyCache for this entity. + */ + protected function _clearCache() + { + $this->_cache->clearKey($this->_cacheKey, 'body'); + } + + /** + * Returns a random Content-ID or Message-ID. + * + * @return string + */ + protected function getRandomId() + { + $idLeft = md5(getmypid() . '.' . time() . '.' . uniqid(mt_rand(), true)); + $idRight = !empty($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'swift.generated'; + $id = $idLeft . '@' . $idRight; + + try { + $this->_assertValidId($id); + } catch (Swift_RfcComplianceException $e) { + $id = $idLeft . '@swift.generated'; + } + + return $id; + } + + // -- Private methods + + private function _readStream(Swift_OutputByteStream $os) + { + $string = ''; + while (false !== $bytes = $os->read(8192)) { + $string .= $bytes; + } + + return $string; + } + + private function _setEncoding($encoding) + { + if (!$this->_setHeaderFieldModel('Content-Transfer-Encoding', $encoding)) { + $this->_headers->addTextHeader('Content-Transfer-Encoding', $encoding); + } + } + + private function _assertValidBoundary($boundary) + { + if (!preg_match( + '/^[a-z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-z0-9\'\(\)\+_\-,\.\/:=\?]$/Di', + $boundary)) + { + throw new Swift_RfcComplianceException('Mime boundary set is not RFC 2046 compliant.'); + } + } + + private function _setContentTypeInHeaders($type) + { + if (!$this->_setHeaderFieldModel('Content-Type', $type)) { + $this->_headers->addParameterizedHeader('Content-Type', $type); + } + } + + private function _setNestingLevel($level) + { + $this->_nestingLevel = $level; + } + + private function _getCompoundLevel($children) + { + $level = 0; + foreach ($children as $child) { + $level |= $child->getNestingLevel(); + } + + return $level; + } + + private function _getNeededChildLevel($child, $compoundLevel) + { + $filter = array(); + foreach ($this->_compoundLevelFilters as $bitmask => $rules) { + if (($compoundLevel & $bitmask) === $bitmask) { + $filter = $rules + $filter; + } + } + + $realLevel = $child->getNestingLevel(); + $lowercaseType = strtolower($child->getContentType()); + + if (isset($filter[$realLevel]) + && isset($filter[$realLevel][$lowercaseType])) + { + return $filter[$realLevel][$lowercaseType]; + } else { + return $realLevel; + } + } + + private function _createChild() + { + return new self($this->_headers->newInstance(), + $this->_encoder, $this->_cache, $this->_grammar); + } + + private function _notifyEncoderChanged(Swift_Mime_ContentEncoder $encoder) + { + foreach ($this->_immediateChildren as $child) { + $child->encoderChanged($encoder); + } + } + + private function _notifyCharsetChanged($charset) + { + $this->_encoder->charsetChanged($charset); + $this->_headers->charsetChanged($charset); + foreach ($this->_immediateChildren as $child) { + $child->charsetChanged($charset); + } + } + + private function _sortChildren() + { + $shouldSort = false; + foreach ($this->_immediateChildren as $child) { + //NOTE: This include alternative parts moved into a related part + if ($child->getNestingLevel() == self::LEVEL_ALTERNATIVE) { + $shouldSort = true; + break; + } + } + + //Sort in order of preference, if there is one + if ($shouldSort) { + usort($this->_immediateChildren, array($this, '_childSortAlgorithm')); + } + } + + private function _childSortAlgorithm($a, $b) + { + $typePrefs = array(); + $types = array( + strtolower($a->getContentType()), + strtolower($b->getContentType()) + ); + foreach ($types as $type) { + $typePrefs[] = (array_key_exists($type, $this->_alternativePartOrder)) + ? $this->_alternativePartOrder[$type] + : (max($this->_alternativePartOrder) + 1); + } + + return ($typePrefs[0] >= $typePrefs[1]) ? 1 : -1; + } + + // -- Destructor + + /** + * Empties it's own contents from the cache. + */ + public function __destruct() + { + $this->_cache->clearAll($this->_cacheKey); + } + + /** + * Throws an Exception if the id passed does not comply with RFC 2822. + * + * @param string $id + * + * @throws Swift_RfcComplianceException + */ + private function _assertValidId($id) + { + if (!preg_match( + '/^' . $this->_grammar->getDefinition('id-left') . '@' . + $this->_grammar->getDefinition('id-right') . '$/D', + $id + )) + { + throw new Swift_RfcComplianceException( + 'Invalid ID given <' . $id . '>' + ); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php new file mode 100644 index 0000000..10a4f3c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php @@ -0,0 +1,61 @@ +createDependenciesFor('mime.part') + ); + + if (!isset($charset)) { + $charset = Swift_DependencyContainer::getInstance() + ->lookup('properties.charset'); + } + $this->setBody($body); + $this->setCharset($charset); + if ($contentType) { + $this->setContentType($contentType); + } + } + + /** + * Create a new MimePart. + * + * @param string $body + * @param string $contentType + * @param string $charset + * + * @return Swift_Mime_MimePart + */ + public static function newInstance($body = null, $contentType = null, $charset = null) + { + return new self($body, $contentType, $charset); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/NullTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/NullTransport.php new file mode 100644 index 0000000..335c479 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/NullTransport.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Pretends messages have been sent, but just ignores them. + * + * @package Swift + * @author Fabien Potencier + */ +class Swift_NullTransport extends Swift_Transport_NullTransport +{ + /** + * Create a new NullTransport. + */ + public function __construct() + { + call_user_func_array( + array($this, 'Swift_Transport_NullTransport::__construct'), + Swift_DependencyContainer::getInstance() + ->createDependenciesFor('transport.null') + ); + } + + /** + * Create a new NullTransport instance. + * + * @return Swift_NullTransport + */ + public static function newInstance() + { + return new self(); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/OutputByteStream.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/OutputByteStream.php new file mode 100644 index 0000000..2ff7449 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/OutputByteStream.php @@ -0,0 +1,48 @@ +setThreshold($threshold); + $this->setSleepTime($sleep); + $this->_sleeper = $sleeper; + } + + /** + * Set the number of emails to send before restarting. + * + * @param integer $threshold + */ + public function setThreshold($threshold) + { + $this->_threshold = $threshold; + } + + /** + * Get the number of emails to send before restarting. + * + * @return int + */ + public function getThreshold() + { + return $this->_threshold; + } + + /** + * Set the number of seconds to sleep for during a restart. + * + * @param integer $sleep time + */ + public function setSleepTime($sleep) + { + $this->_sleep = $sleep; + } + + /** + * Get the number of seconds to sleep for during a restart. + * + * @return int + */ + public function getSleepTime() + { + return $this->_sleep; + } + + /** + * Invoked immediately before the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + } + + /** + * Invoked immediately after the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + ++$this->_counter; + if ($this->_counter >= $this->_threshold) { + $transport = $evt->getTransport(); + $transport->stop(); + if ($this->_sleep) { + $this->sleep($this->_sleep); + } + $transport->start(); + $this->_counter = 0; + } + } + + /** + * Sleep for $seconds. + * + * @param integer $seconds + */ + public function sleep($seconds) + { + if (isset($this->_sleeper)) { + $this->_sleeper->sleep($seconds); + } else { + sleep($seconds); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php new file mode 100644 index 0000000..8794aac --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php @@ -0,0 +1,166 @@ +getMessage(); + $message->toByteStream($this); + } + + /** + * Invoked immediately following a command being sent. + * + * @param Swift_Events_CommandEvent $evt + */ + public function commandSent(Swift_Events_CommandEvent $evt) + { + $command = $evt->getCommand(); + $this->_out += strlen($command); + } + + /** + * Invoked immediately following a response coming back. + * + * @param Swift_Events_ResponseEvent $evt + */ + public function responseReceived(Swift_Events_ResponseEvent $evt) + { + $response = $evt->getResponse(); + $this->_in += strlen($response); + } + + /** + * Called when a message is sent so that the outgoing counter can be increased. + * + * @param string $bytes + */ + public function write($bytes) + { + $this->_out += strlen($bytes); + 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]); + } + } + } + + /** + * Not used. + */ + public function flushBuffers() + { + foreach ($this->_mirrors as $stream) { + $stream->flushBuffers(); + } + } + + /** + * Get the total number of bytes sent to the server. + * + * @return int + */ + public function getBytesOut() + { + return $this->_out; + } + + /** + * Get the total number of bytes received from the server. + * + * @return int + */ + public function getBytesIn() + { + return $this->_in; + } + + /** + * Reset the internal counters to zero. + */ + public function reset() + { + $this->_out = 0; + $this->_in = 0; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Decorator/Replacements.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Decorator/Replacements.php new file mode 100644 index 0000000..3269c69 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Decorator/Replacements.php @@ -0,0 +1,33 @@ + + * $replacements = array( + * "address1@domain.tld" => array("{a}" => "b", "{c}" => "d"), + * "address2@domain.tld" => array("{a}" => "x", "{c}" => "y") + * ) + *
    + * + * When using an instance of {@link Swift_Plugins_Decorator_Replacements}, + * the object should return just the array of replacements for the address + * given to {@link Swift_Plugins_Decorator_Replacements::getReplacementsFor()}. + * + * @param mixed $replacements Array or Swift_Plugins_Decorator_Replacements + */ + public function __construct($replacements) + { + $this->setReplacements($replacements); + } + + /** + * Sets replacements. + * + * @param mixed $replacements Array or Swift_Plugins_Decorator_Replacements + * + * @see __construct() + */ + public function setReplacements($replacements) + { + if (!($replacements instanceof \Swift_Plugins_Decorator_Replacements)) { + $this->_replacements = (array) $replacements; + } else { + $this->_replacements = $replacements; + } + } + + /** + * Invoked immediately before the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + $message = $evt->getMessage(); + $this->_restoreMessage($message); + $to = array_keys($message->getTo()); + $address = array_shift($to); + if ($replacements = $this->getReplacementsFor($address)) { + $body = $message->getBody(); + $search = array_keys($replacements); + $replace = array_values($replacements); + $bodyReplaced = str_replace( + $search, $replace, $body + ); + if ($body != $bodyReplaced) { + $this->_originalBody = $body; + $message->setBody($bodyReplaced); + } + + foreach ($message->getHeaders()->getAll() as $header) { + $body = $header->getFieldBodyModel(); + $count = 0; + if (is_array($body)) { + $bodyReplaced = array(); + foreach ($body as $key => $value) { + $count1 = 0; + $count2 = 0; + $key = is_string($key) ? str_replace($search, $replace, $key, $count1) : $key; + $value = is_string($value) ? str_replace($search, $replace, $value, $count2) : $value; + $bodyReplaced[$key] = $value; + + if (!$count && ($count1 || $count2)) { + $count = 1; + } + } + } else { + $bodyReplaced = str_replace($search, $replace, $body, $count); + } + + if ($count) { + $this->_originalHeaders[$header->getFieldName()] = $body; + $header->setFieldBodyModel($bodyReplaced); + } + } + + $children = (array) $message->getChildren(); + foreach ($children as $child) { + list($type, ) = sscanf($child->getContentType(), '%[^/]/%s'); + if ('text' == $type) { + $body = $child->getBody(); + $bodyReplaced = str_replace( + $search, $replace, $body + ); + if ($body != $bodyReplaced) { + $child->setBody($bodyReplaced); + $this->_originalChildBodies[$child->getId()] = $body; + } + } + } + $this->_lastMessage = $message; + } + } + + /** + * Find a map of replacements for the address. + * + * If this plugin was provided with a delegate instance of + * {@link Swift_Plugins_Decorator_Replacements} then the call will be + * delegated to it. Otherwise, it will attempt to find the replacements + * from the array provided in the constructor. + * + * If no replacements can be found, an empty value (NULL) is returned. + * + * @param string $address + * + * @return array + */ + public function getReplacementsFor($address) + { + if ($this->_replacements instanceof Swift_Plugins_Decorator_Replacements) { + return $this->_replacements->getReplacementsFor($address); + } else { + return isset($this->_replacements[$address]) + ? $this->_replacements[$address] + : null + ; + } + } + + /** + * Invoked immediately after the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + $this->_restoreMessage($evt->getMessage()); + } + + // -- Private methods + + /** Restore a changed message back to its original state */ + private function _restoreMessage(Swift_Mime_Message $message) + { + if ($this->_lastMessage === $message) { + if (isset($this->_originalBody)) { + $message->setBody($this->_originalBody); + $this->_originalBody = null; + } + if (!empty($this->_originalHeaders)) { + foreach ($message->getHeaders()->getAll() as $header) { + if (array_key_exists($header->getFieldName(), $this->_originalHeaders)) { + $header->setFieldBodyModel($this->_originalHeaders[$header->getFieldName()]); + } + } + $this->_originalHeaders = array(); + } + if (!empty($this->_originalChildBodies)) { + $children = (array) $message->getChildren(); + foreach ($children as $child) { + $id = $child->getId(); + if (array_key_exists($id, $this->_originalChildBodies)) { + $child->setBody($this->_originalChildBodies[$id]); + } + } + $this->_originalChildBodies = array(); + } + $this->_lastMessage = null; + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ImpersonatePlugin.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ImpersonatePlugin.php new file mode 100644 index 0000000..1f1e443 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ImpersonatePlugin.php @@ -0,0 +1,70 @@ +_sender = $sender; + } + + /** + * Invoked immediately before the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + $message = $evt->getMessage(); + $headers = $message->getHeaders(); + + // save current recipients + $headers->addPathHeader('X-Swift-Return-Path', $message->getReturnPath()); + + // replace them with the one to send to + $message->setReturnPath($this->_sender); + } + + /** + * Invoked immediately after the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + $message = $evt->getMessage(); + + // restore original headers + $headers = $message->getHeaders(); + + if ($headers->has('X-Swift-Return-Path')) { + $message->setReturnPath($headers->get('X-Swift-Return-Path')->getAddress()); + $headers->removeAll('X-Swift-Return-Path'); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Logger.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Logger.php new file mode 100644 index 0000000..81c1d9b --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Logger.php @@ -0,0 +1,38 @@ +_logger = $logger; + } + + /** + * Add a log entry. + * + * @param string $entry + */ + public function add($entry) + { + $this->_logger->add($entry); + } + + /** + * Clear the log contents. + */ + public function clear() + { + $this->_logger->clear(); + } + + /** + * Get this log as a string. + * + * @return string + */ + public function dump() + { + return $this->_logger->dump(); + } + + /** + * Invoked immediately following a command being sent. + * + * @param Swift_Events_CommandEvent $evt + */ + public function commandSent(Swift_Events_CommandEvent $evt) + { + $command = $evt->getCommand(); + $this->_logger->add(sprintf(">> %s", $command)); + } + + /** + * Invoked immediately following a response coming back. + * + * @param Swift_Events_ResponseEvent $evt + */ + public function responseReceived(Swift_Events_ResponseEvent $evt) + { + $response = $evt->getResponse(); + $this->_logger->add(sprintf("<< %s", $response)); + } + + /** + * Invoked just before a Transport is started. + * + * @param Swift_Events_TransportChangeEvent $evt + */ + public function beforeTransportStarted(Swift_Events_TransportChangeEvent $evt) + { + $transportName = get_class($evt->getSource()); + $this->_logger->add(sprintf("++ Starting %s", $transportName)); + } + + /** + * Invoked immediately after the Transport is started. + * + * @param Swift_Events_TransportChangeEvent $evt + */ + public function transportStarted(Swift_Events_TransportChangeEvent $evt) + { + $transportName = get_class($evt->getSource()); + $this->_logger->add(sprintf("++ %s started", $transportName)); + } + + /** + * Invoked just before a Transport is stopped. + * + * @param Swift_Events_TransportChangeEvent $evt + */ + public function beforeTransportStopped(Swift_Events_TransportChangeEvent $evt) + { + $transportName = get_class($evt->getSource()); + $this->_logger->add(sprintf("++ Stopping %s", $transportName)); + } + + /** + * Invoked immediately after the Transport is stopped. + * + * @param Swift_Events_TransportChangeEvent $evt + */ + public function transportStopped(Swift_Events_TransportChangeEvent $evt) + { + $transportName = get_class($evt->getSource()); + $this->_logger->add(sprintf("++ %s stopped", $transportName)); + } + + /** + * Invoked as a TransportException is thrown in the Transport system. + * + * @param Swift_Events_TransportExceptionEvent $evt + */ + public function exceptionThrown(Swift_Events_TransportExceptionEvent $evt) + { + $e = $evt->getException(); + $message = $e->getMessage(); + $this->_logger->add(sprintf("!! %s", $message)); + $message .= PHP_EOL; + $message .= 'Log data:' . PHP_EOL; + $message .= $this->_logger->dump(); + $evt->cancelBubble(); + throw new Swift_TransportException($message); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php new file mode 100644 index 0000000..eb362ef --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php @@ -0,0 +1,74 @@ +_size = $size; + } + + /** + * Add a log entry. + * + * @param string $entry + */ + public function add($entry) + { + $this->_log[] = $entry; + while (count($this->_log) > $this->_size) { + array_shift($this->_log); + } + } + + /** + * Clear the log contents. + */ + public function clear() + { + $this->_log = array(); + } + + /** + * Get this log as a string. + * + * @return string + */ + public function dump() + { + return implode(PHP_EOL, $this->_log); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/EchoLogger.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/EchoLogger.php new file mode 100644 index 0000000..c542169 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/EchoLogger.php @@ -0,0 +1,60 @@ +_isHtml = $isHtml; + } + + /** + * Add a log entry. + * + * @param string $entry + */ + public function add($entry) + { + if ($this->_isHtml) { + printf('%s%s%s', htmlspecialchars($entry, ENT_QUOTES), '
    ', PHP_EOL); + } else { + printf('%s%s', $entry, PHP_EOL); + } + } + + /** + * Not implemented. + */ + public function clear() + { + } + + /** + * Not implemented. + */ + public function dump() + { + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.php new file mode 100644 index 0000000..35d5de5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.php @@ -0,0 +1,77 @@ +messages = array(); + } + + /** + * Get the message list + * + * @return array + */ + public function getMessages() + { + return $this->messages; + } + + /** + * Get the message count + * + * @return integer count + */ + public function countMessages() + { + return count($this->messages); + } + + /** + * Empty the message list + * + */ + public function clear() + { + $this->messages = array(); + } + + /** + * Invoked immediately before the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + $this->messages[] = clone $evt->getMessage(); + } + + /** + * Invoked immediately after the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Connection.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Connection.php new file mode 100644 index 0000000..d241721 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Connection.php @@ -0,0 +1,33 @@ +_host = $host; + $this->_port = $port; + $this->_crypto = $crypto; + } + + /** + * Create a new PopBeforeSmtpPlugin for $host and $port. + * + * @param string $host + * @param integer $port + * @param string $crypto as "tls" or "ssl" + * + * @return Swift_Plugins_PopBeforeSmtpPlugin + */ + public static function newInstance($host, $port = 110, $crypto = null) + { + return new self($host, $port, $crypto); + } + + /** + * Set a Pop3Connection to delegate to instead of connecting directly. + * + * @param Swift_Plugins_Pop_Pop3Connection $connection + * + * @return Swift_Plugins_PopBeforeSmtpPlugin + */ + public function setConnection(Swift_Plugins_Pop_Pop3Connection $connection) + { + $this->_connection = $connection; + + return $this; + } + + /** + * Bind this plugin to a specific SMTP transport instance. + * + * @param Swift_Transport + */ + public function bindSmtp(Swift_Transport $smtp) + { + $this->_transport = $smtp; + } + + /** + * Set the connection timeout in seconds (default 10). + * + * @param integer $timeout + * + * @return Swift_Plugins_PopBeforeSmtpPlugin + */ + public function setTimeout($timeout) + { + $this->_timeout = (int) $timeout; + + return $this; + } + + /** + * Set the username to use when connecting (if needed). + * + * @param string $username + * + * @return Swift_Plugins_PopBeforeSmtpPlugin + */ + public function setUsername($username) + { + $this->_username = $username; + + return $this; + } + + /** + * Set the password to use when connecting (if needed). + * + * @param string $password + * + * @return Swift_Plugins_PopBeforeSmtpPlugin + */ + public function setPassword($password) + { + $this->_password = $password; + + return $this; + } + + /** + * Connect to the POP3 host and authenticate. + * + * @throws Swift_Plugins_Pop_Pop3Exception if connection fails + */ + public function connect() + { + if (isset($this->_connection)) { + $this->_connection->connect(); + } else { + if (!isset($this->_socket)) { + if (!$socket = fsockopen( + $this->_getHostString(), $this->_port, $errno, $errstr, $this->_timeout)) + { + throw new Swift_Plugins_Pop_Pop3Exception( + sprintf('Failed to connect to POP3 host [%s]: %s', $this->_host, $errstr) + ); + } + $this->_socket = $socket; + + if (false === $greeting = fgets($this->_socket)) { + throw new Swift_Plugins_Pop_Pop3Exception( + sprintf('Failed to connect to POP3 host [%s]', trim($greeting)) + ); + } + + $this->_assertOk($greeting); + + if ($this->_username) { + $this->_command(sprintf("USER %s\r\n", $this->_username)); + $this->_command(sprintf("PASS %s\r\n", $this->_password)); + } + } + } + } + + /** + * Disconnect from the POP3 host. + */ + public function disconnect() + { + if (isset($this->_connection)) { + $this->_connection->disconnect(); + } else { + $this->_command("QUIT\r\n"); + if (!fclose($this->_socket)) { + throw new Swift_Plugins_Pop_Pop3Exception( + sprintf('POP3 host [%s] connection could not be stopped', $this->_host) + ); + } + $this->_socket = null; + } + } + + /** + * Invoked just before a Transport is started. + * + * @param Swift_Events_TransportChangeEvent $evt + */ + public function beforeTransportStarted(Swift_Events_TransportChangeEvent $evt) + { + if (isset($this->_transport)) { + if ($this->_transport !== $evt->getTransport()) { + return; + } + } + + $this->connect(); + $this->disconnect(); + } + + /** + * Not used. + */ + public function transportStarted(Swift_Events_TransportChangeEvent $evt) + { + } + + /** + * Not used. + */ + public function beforeTransportStopped(Swift_Events_TransportChangeEvent $evt) + { + } + + /** + * Not used. + */ + public function transportStopped(Swift_Events_TransportChangeEvent $evt) + { + } + + // -- Private Methods + + private function _command($command) + { + if (!fwrite($this->_socket, $command)) { + throw new Swift_Plugins_Pop_Pop3Exception( + sprintf('Failed to write command [%s] to POP3 host', trim($command)) + ); + } + + if (false === $response = fgets($this->_socket)) { + throw new Swift_Plugins_Pop_Pop3Exception( + sprintf('Failed to read from POP3 host after command [%s]', trim($command)) + ); + } + + $this->_assertOk($response); + + return $response; + } + + private function _assertOk($response) + { + if (substr($response, 0, 3) != '+OK') { + throw new Swift_Plugins_Pop_Pop3Exception( + sprintf('POP3 command failed [%s]', trim($response)) + ); + } + } + + private function _getHostString() + { + $host = $this->_host; + switch (strtolower($this->_crypto)) { + case 'ssl': + $host = 'ssl://' . $host; + break; + + case 'tls': + $host = 'tls://' . $host; + break; + } + + return $host; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/RedirectingPlugin.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/RedirectingPlugin.php new file mode 100644 index 0000000..a27db78 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/RedirectingPlugin.php @@ -0,0 +1,204 @@ +_recipient = $recipient; + $this->_whitelist = $whitelist; + } + + /** + * Set the recipient of all messages. + * + * @param string $recipient + */ + public function setRecipient($recipient) + { + $this->_recipient = $recipient; + } + + /** + * Get the recipient of all messages. + * + * @return int + */ + public function getRecipient() + { + return $this->_recipient; + } + + /** + * Set a list of regular expressions to whitelist certain recipients + * + * @param array $whitelist + */ + public function setWhitelist(array $whitelist) + { + $this->_whitelist = $whitelist; + } + + /** + * Get the whitelist + * + * @return array + */ + public function getWhitelist() + { + return $this->_whitelist; + } + + /** + * Invoked immediately before the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + $message = $evt->getMessage(); + $headers = $message->getHeaders(); + + // conditionally save current recipients + + if ($headers->has('to')) { + $headers->addMailboxHeader('X-Swift-To', $message->getTo()); + } + + if ($headers->has('cc')) { + $headers->addMailboxHeader('X-Swift-Cc', $message->getCc()); + } + + if ($headers->has('bcc')) { + $headers->addMailboxHeader('X-Swift-Bcc', $message->getBcc()); + } + + // Add hard coded recipient + $message->addTo($this->_recipient); + + // Filter remaining headers against whitelist + $this->_filterHeaderSet($headers, 'To'); + $this->_filterHeaderSet($headers, 'Cc'); + $this->_filterHeaderSet($headers, 'Bcc'); + } + + /** + * Filter header set against a whitelist of regular expressions + * + * @param Swift_Mime_HeaderSet $headerSet + * @param string $type + */ + private function _filterHeaderSet(Swift_Mime_HeaderSet $headerSet, $type) + { + foreach ($headerSet->getAll($type) as $headers) { + $headers->setNameAddresses($this->_filterNameAddresses($headers->getNameAddresses())); + } + } + + /** + * Filtered list of addresses => name pairs + * + * @param array $recipients + * @return array + */ + private function _filterNameAddresses(array $recipients) + { + $filtered = array(); + + foreach ($recipients as $address => $name) { + if ($this->_isWhitelisted($address)) { + $filtered[$address] = $name; + } + } + + return $filtered; + } + + /** + * Matches address against whitelist of regular expressions + * + * @param $recipient + * @return bool + */ + protected function _isWhitelisted($recipient) + { + if ($recipient === $this->_recipient) { + return true; + } + + foreach ($this->_whitelist as $pattern) { + if (preg_match($pattern, $recipient)) { + return true; + } + } + + return false; + } + + /** + * Invoked immediately after the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + $this->_restoreMessage($evt->getMessage()); + } + + // -- Private methods + + private function _restoreMessage(Swift_Mime_Message $message) + { + // restore original headers + $headers = $message->getHeaders(); + + if ($headers->has('X-Swift-To')) { + $message->setTo($headers->get('X-Swift-To')->getNameAddresses()); + $headers->removeAll('X-Swift-To'); + } + + if ($headers->has('X-Swift-Cc')) { + $message->setCc($headers->get('X-Swift-Cc')->getNameAddresses()); + $headers->removeAll('X-Swift-Cc'); + } + + if ($headers->has('X-Swift-Bcc')) { + $message->setBcc($headers->get('X-Swift-Bcc')->getNameAddresses()); + $headers->removeAll('X-Swift-Bcc'); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporter.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporter.php new file mode 100644 index 0000000..0dfa22d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporter.php @@ -0,0 +1,34 @@ +_reporter = $reporter; + } + + /** + * Not used. + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + } + + /** + * Invoked immediately after the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + $message = $evt->getMessage(); + $failures = array_flip($evt->getFailedRecipients()); + foreach ((array) $message->getTo() as $address => $null) { + $this->_reporter->notify( + $message, $address, (array_key_exists($address, $failures) + ? Swift_Plugins_Reporter::RESULT_FAIL + : Swift_Plugins_Reporter::RESULT_PASS) + ); + } + foreach ((array) $message->getCc() as $address => $null) { + $this->_reporter->notify( + $message, $address, (array_key_exists($address, $failures) + ? Swift_Plugins_Reporter::RESULT_FAIL + : Swift_Plugins_Reporter::RESULT_PASS) + ); + } + foreach ((array) $message->getBcc() as $address => $null) { + $this->_reporter->notify( + $message, $address, (array_key_exists($address, $failures) + ? Swift_Plugins_Reporter::RESULT_FAIL + : Swift_Plugins_Reporter::RESULT_PASS) + ); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HitReporter.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HitReporter.php new file mode 100644 index 0000000..844e2a1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HitReporter.php @@ -0,0 +1,61 @@ +_failures_cache[$address])) { + $this->_failures[] = $address; + $this->_failures_cache[$address] = true; + } + } + + /** + * Get an array of addresses for which delivery failed. + * + * @return array + */ + public function getFailedRecipients() + { + return $this->_failures; + } + + /** + * Clear the buffer (empty the list). + */ + public function clear() + { + $this->_failures = $this->_failures_cache = array(); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php new file mode 100644 index 0000000..7b8c188 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php @@ -0,0 +1,41 @@ +" . PHP_EOL; + echo "PASS " . $address . PHP_EOL; + echo "" . PHP_EOL; + flush(); + } else { + echo "
    " . PHP_EOL; + echo "FAIL " . $address . PHP_EOL; + echo "
    " . PHP_EOL; + flush(); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Sleeper.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Sleeper.php new file mode 100644 index 0000000..c491f63 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Sleeper.php @@ -0,0 +1,26 @@ +_rate = $rate; + $this->_mode = $mode; + $this->_sleeper = $sleeper; + $this->_timer = $timer; + } + + /** + * Invoked immediately before the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + $time = $this->getTimestamp(); + if (!isset($this->_start)) { + $this->_start = $time; + } + $duration = $time - $this->_start; + + switch($this->_mode) { + case self::BYTES_PER_MINUTE : + $sleep = $this->_throttleBytesPerMinute($duration); + break; + case self::MESSAGES_PER_SECOND : + $sleep = $this->_throttleMessagesPerSecond($duration); + break; + case self::MESSAGES_PER_MINUTE : + $sleep = $this->_throttleMessagesPerMinute($duration); + break; + default : + $sleep = 0; + break; + } + + if ($sleep > 0) { + $this->sleep($sleep); + } + } + + /** + * Invoked when a Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + parent::sendPerformed($evt); + ++$this->_messages; + } + + /** + * Sleep for $seconds. + * + * @param integer $seconds + */ + public function sleep($seconds) + { + if (isset($this->_sleeper)) { + $this->_sleeper->sleep($seconds); + } else { + sleep($seconds); + } + } + + /** + * Get the current UNIX timestamp. + * + * @return int + */ + public function getTimestamp() + { + if (isset($this->_timer)) { + return $this->_timer->getTimestamp(); + } else { + return time(); + } + } + + // -- Private methods + + /** + * Get a number of seconds to sleep for. + * + * @param integer $timePassed + * + * @return int + */ + private function _throttleBytesPerMinute($timePassed) + { + $expectedDuration = $this->getBytesOut() / ($this->_rate / 60); + + return (int) ceil($expectedDuration - $timePassed); + } + + /** + * Get a number of seconds to sleep for. + * + * @param int $timePassed + * + * @return int + */ + private function _throttleMessagesPerSecond($timePassed) + { + $expectedDuration = $this->_messages / ($this->_rate); + + return (int) ceil($expectedDuration - $timePassed); + } + + /** + * Get a number of seconds to sleep for. + * + * @param integer $timePassed + * + * @return int + */ + private function _throttleMessagesPerMinute($timePassed) + { + $expectedDuration = $this->_messages / ($this->_rate / 60); + + return (int) ceil($expectedDuration - $timePassed); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Timer.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Timer.php new file mode 100644 index 0000000..12dd09b --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Timer.php @@ -0,0 +1,26 @@ +register('properties.charset')->asValue($charset); + + return $this; + } + + /** + * Set the directory where temporary files can be saved. + * + * @param string $dir + * + * @return Swift_Preferences + */ + public function setTempDir($dir) + { + Swift_DependencyContainer::getInstance() + ->register('tempdir')->asValue($dir); + + return $this; + } + + /** + * Set the type of cache to use (i.e. "disk" or "array"). + * + * @param string $type + * + * @return Swift_Preferences + */ + public function setCacheType($type) + { + Swift_DependencyContainer::getInstance() + ->register('cache')->asAliasOf(sprintf('cache.%s', $type)); + + return $this; + } + + /** + * Set the QuotedPrintable dot escaper preference. + * + * @param boolean $dotEscape + * + * @return Swift_Preferences + */ + public function setQPDotEscape($dotEscape) + { + $dotEscape = !empty($dotEscape); + Swift_DependencyContainer::getInstance() + ->register('mime.qpcontentencoder') + ->asNewInstanceOf('Swift_Mime_ContentEncoder_QpContentEncoder') + ->withDependencies(array('mime.charstream', 'mime.bytecanonicalizer')) + ->addConstructorValue($dotEscape); + + return $this; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ReplacementFilterFactory.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ReplacementFilterFactory.php new file mode 100644 index 0000000..4b6eed5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ReplacementFilterFactory.php @@ -0,0 +1,28 @@ +createDependenciesFor('transport.sendmail') + ); + + $this->setCommand($command); + } + + /** + * Create a new SendmailTransport instance. + * + * @param string $command + * + * @return Swift_SendmailTransport + */ + public static function newInstance($command = '/usr/sbin/sendmail -bs') + { + return new self($command); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SignedMessage.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SignedMessage.php new file mode 100644 index 0000000..1a073e4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SignedMessage.php @@ -0,0 +1,165 @@ + + */ +class Swift_SignedMessage extends Swift_Message +{ + /** + * @var Swift_Signers_HeaderSigner[] + */ + private $headerSigners = array(); + + /** + * @var Swift_Signers_BodySigner[] + */ + private $bodySigners = array(); + + /** + * @var array + */ + private $savedMessage = array(); + + /** + * Create a new Message. + * + * @param string $subject + * @param string $body + * @param string $contentType + * @param string $charset + * + * @return Swift_SignedMessage + */ + public static function newInstance($subject = null, $body = null, $contentType = null, $charset = null) + { + return new self($subject, $body, $contentType, $charset); + } + + /** + * Attach a new signature handler to the message. + * + * @param Swift_Signer $signer + * @return Swift_SignedMessage + */ + public function attachSigner(Swift_Signer $signer) + { + if ($signer instanceof Swift_Signers_HeaderSigner) { + $this->headerSigners[] = $signer; + } + elseif ($signer instanceof Swift_Signers_BodySigner) { + $this->bodySigners[] = $signer; + } + + return $this; + } + + /** + * Get this message as a complete string. + * + * @return string + */ + public function toString() + { + $this->saveMessage(); + + $this->doSign(); + $string = parent::toString(); + $this->restoreMessage(); + + return $string; + } + + /** + * Write this message to a {@link Swift_InputByteStream}. + * + * @param Swift_InputByteStream $is + */ + public function toByteStream(Swift_InputByteStream $is) + { + $this->saveMessage(); + $this->doSign(); + + parent::toByteStream($is); + $this->restoreMessage(); + } + + protected function doSign() + { + foreach ($this->bodySigners as $signer) { + $altered = $signer->getAlteredHeaders(); + $this->saveHeaders($altered); + $signer->signMessage($this); + } + + foreach ($this->headerSigners as $signer) { + $altered = $signer->getAlteredHeaders(); + $this->saveHeaders($altered); + $signer->reset(); + + $signer->setHeaders($this->getHeaders()); + + $signer->startBody(); + $this->_bodyToByteStream($signer); + $signer->endBody(); + + $signer->addSignature($this->getHeaders()); + } + } + + protected function saveMessage() + { + $this->savedMessage = array('headers'=> array()); + $this->savedMessage['body'] = $this->getBody(); + $this->savedMessage['children'] = $this->getChildren(); + if (count($this->savedMessage['children']) > 0 && $this->getBody() != '') { + $this->setChildren(array_merge(array($this->_becomeMimePart()), $this->savedMessage['children'])); + $this->setBody(''); + } + } + + protected function saveHeaders(array $altered) + { + foreach ($altered as $head) { + $lc = strtolower($head); + + if (!isset($this->savedMessage['headers'][$lc])) { + $this->savedMessage['headers'][$lc] = $this->getHeaders()->getAll($head); + } + } + } + + protected function restoreHeaders() + { + foreach ($this->savedMessage['headers'] as $name => $savedValue) { + $headers = $this->getHeaders()->getAll($name); + + foreach ($headers as $key => $value) { + if (!isset($savedValue[$key])) { + $this->getHeaders()->remove($name, $key); + } + } + } + } + + protected function restoreMessage() + { + $this->setBody($this->savedMessage['body']); + $this->setChildren($this->savedMessage['children']); + + $this->restoreHeaders(); + $this->savedMessage = array(); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signer.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signer.php new file mode 100644 index 0000000..865f557 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signer.php @@ -0,0 +1,22 @@ + + */ +interface Swift_Signer +{ + public function reset(); +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/BodySigner.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/BodySigner.php new file mode 100644 index 0000000..3a653a3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/BodySigner.php @@ -0,0 +1,35 @@ + + */ +interface Swift_Signers_BodySigner extends Swift_Signer +{ + /** + * Change the Swift_Signed_Message to apply the singing. + * + * @param Swift_Signed_Message $message + * + * @return Swift_Signers_BodySigner + */ + public function signMessage(Swift_SignedMessage $message); + + /** + * Return the list of header a signer might tamper + * + * @return array + */ + public function getAlteredHeaders(); +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DKIMSigner.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DKIMSigner.php new file mode 100644 index 0000000..09a2ccd --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DKIMSigner.php @@ -0,0 +1,669 @@ + + */ +class Swift_Signers_DKIMSigner implements Swift_Signers_HeaderSigner +{ + /** + * PrivateKey + * + * @var string + */ + protected $_privateKey; + + /** + * DomainName + * + * @var string + */ + protected $_domainName; + + /** + * Selector + * + * @var string + */ + protected $_selector; + + /** + * Hash algorithm used + * + * @var string + */ + protected $_hashAlgorithm = 'rsa-sha1'; + + /** + * Body canon method + * + * @var string + */ + protected $_bodyCanon = 'simple'; + + /** + * Header canon method + * + * @var string + */ + protected $_headerCanon = 'simple'; + + /** + * Headers not being signed + * + * @var array + */ + protected $_ignoredHeaders = array(); + + /** + * Signer identity + * + * @var unknown_type + */ + protected $_signerIdentity; + + /** + * BodyLength + * + * @var int + */ + protected $_bodyLen = 0; + + /** + * Maximum signedLen + * + * @var int + */ + protected $_maxLen = PHP_INT_MAX; + + /** + * Embbed bodyLen in signature + * + * @var boolean + */ + protected $_showLen = false; + + /** + * When the signature has been applied (true means time()), false means not embedded + * + * @var mixed + */ + protected $_signatureTimestamp = true; + + /** + * When will the signature expires false means not embedded, if sigTimestamp is auto + * Expiration is relative, otherwhise it's absolute + * + * @var int + */ + protected $_signatureExpiration = false; + + /** + * Must we embed signed headers? + * + * @var boolean + */ + protected $_debugHeaders = false; + + // work variables + /** + * Headers used to generate hash + * + * @var array + */ + protected $_signedHeaders = array(); + + /** + * If debugHeaders is set store debugDatas here + * + * @var string + */ + private $_debugHeadersData = ''; + + /** + * Stores the bodyHash + * + * @var string + */ + private $_bodyHash = ''; + + /** + * Stores the signature header + * + * @var Swift_Mime_Headers_ParameterizedHeader + */ + protected $_dkimHeader; + + /** + * Hash Handler + * + * @var hash_ressource + */ + private $_headerHashHandler; + + private $_bodyHashHandler; + + private $_headerHash; + + private $_headerCanonData = ''; + + private $_bodyCanonEmptyCounter = 0; + + private $_bodyCanonIgnoreStart = 2; + + private $_bodyCanonSpace = false; + + private $_bodyCanonLastChar = null; + + private $_bodyCanonLine = ''; + + private $_bound = array(); + + /** + * Constructor + * + * @param string $privateKey + * @param string $domainName + * @param string $selector + */ + public function __construct($privateKey, $domainName, $selector) + { + $this->_privateKey = $privateKey; + $this->_domainName = $domainName; + $this->_signerIdentity = '@' . $domainName; + $this->_selector = $selector; + } + + public function reset() + { + $this->_headerHash = null; + $this->_signedHeaders = array(); + $this->_headerHashHandler = null; + $this->_bodyHash = null; + $this->_bodyHashHandler = null; + $this->_bodyCanonIgnoreStart = 2; + $this->_bodyCanonEmptyCounter = 0; + $this->_bodyCanonLastChar = NULL; + $this->_bodyCanonSpace = false; + } + + /** + * Writes $bytes to the end of the stream. + * + * Writing may not happen immediately if the stream chooses to buffer. If + * you want to write these bytes with immediate effect, call {@link commit()} + * after calling write(). + * + * This method returns the sequence ID of the write (i.e. 1 for first, 2 for + * second, etc etc). + * + * @param string $bytes + * @return int + * @throws Swift_IoException + */ + public function write($bytes) + { + $this->_canonicalizeBody($bytes); + foreach ($this->_bound as $is) { + $is->write($bytes); + } + } + + /** + * For any bytes that are currently buffered inside the stream, force them + * off the buffer. + * + * @throws Swift_IoException + */ + public function commit() + { + // Nothing to do + return; + } + + /** + * 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) + { + // Don't have to mirror anything + $this->_bound[] = $is; + + return; + } + + /** + * 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) + { + // Don't have to mirror anything + foreach ($this->_bound as $k => $el) { + if ($el == $is) { + unset($this->_bound[$k]); + + return; + } + } + + return; + } + + /** + * Flush the contents of the stream (empty it) and set the internal pointer + * to the beginning. + * + * @throws Swift_IoException + */ + public function flushBuffers() + { + $this->reset(); + } + + /** + * Set hash_algorithm, must be one of rsa-sha256 | rsa-sha1 defaults to rsa-sha256 + * + * @param string $hash + * @return Swift_Signers_DKIMSigner + */ + public function setHashAlgorithm($hash) + { + // Unable to sign with rsa-sha256 + if ($hash == 'rsa-sha1') { + $this->_hashAlgorithm = 'rsa-sha1'; + } else { + $this->_hashAlgorithm = 'rsa-sha256'; + } + + return $this; + } + + /** + * Set the body canonicalization algorithm + * + * @param string $canon + * @return Swift_Signers_DKIMSigner + */ + public function setBodyCanon($canon) + { + if ($canon == 'relaxed') { + $this->_bodyCanon = 'relaxed'; + } else { + $this->_bodyCanon = 'simple'; + } + + return $this; + } + + /** + * Set the header canonicalization algorithm + * + * @param string $canon + * @return Swift_Signers_DKIMSigner + */ + public function setHeaderCanon($canon) + { + if ($canon == 'relaxed') { + $this->_headerCanon = 'relaxed'; + } else { + $this->_headerCanon = 'simple'; + } + + return $this; + } + + /** + * Set the signer identity + * + * @param string $identity + * @return Swift_Signers_DKIMSigner + */ + public function setSignerIdentity($identity) + { + $this->_signerIdentity = $identity; + + return $this; + } + + /** + * Set the length of the body to sign + * + * @param mixed $len (bool or int) + * @return Swift_Signers_DKIMSigner + */ + public function setBodySignedLen($len) + { + if ($len === true) { + $this->_showLen = true; + $this->_maxLen = PHP_INT_MAX; + } elseif ($len === false) { + $this->showLen = false; + $this->_maxLen = PHP_INT_MAX; + } else { + $this->_showLen = true; + $this->_maxLen = (int) $len; + } + + return $this; + } + + /** + * Set the signature timestamp + * + * @param timestamp $time + * @return Swift_Signers_DKIMSigner + */ + public function setSignatureTimestamp($time) + { + $this->_signatureTimestamp = $time; + + return $this; + } + + /** + * Set the signature expiration timestamp + * + * @param timestamp $time + * @return Swift_Signers_DKIMSigner + */ + public function setSignatureExpiration($time) + { + $this->_signatureExpiration = $time; + + return $this; + } + + /** + * Enable / disable the DebugHeaders + * + * @param boolean $debug + * @return Swift_Signers_DKIMSigner + */ + public function setDebugHeaders($debug) + { + $this->_debugHeaders = (bool) $debug; + + return $this; + } + + /** + * Start Body + * + */ + public function startBody() + { + // Init + switch ($this->_hashAlgorithm) { + case 'rsa-sha256' : + $this->_bodyHashHandler = hash_init('sha256'); + break; + case 'rsa-sha1' : + $this->_bodyHashHandler = hash_init('sha1'); + break; + } + $this->_bodyCanonLine = ''; + } + + /** + * End Body + * + */ + public function endBody() + { + $this->_endOfBody(); + } + + /** + * Returns the list of Headers Tampered by this plugin + * + * @return array + */ + public function getAlteredHeaders() + { + if ($this->_debugHeaders) { + return array('DKIM-Signature', 'X-DebugHash'); + } else { + return array('DKIM-Signature'); + } + } + + /** + * Adds an ignored Header + * + * @param string $header_name + * @return Swift_Signers_DKIMSigner + */ + public function ignoreHeader($header_name) + { + $this->_ignoredHeaders[strtolower($header_name)] = true; + + return $this; + } + + /** + * Set the headers to sign + * + * @param Swift_Mime_HeaderSet $headers + * @return Swift_Signers_DKIMSigner + */ + public function setHeaders(Swift_Mime_HeaderSet $headers) + { + $this->_headerCanonData = ''; + // Loop through Headers + $listHeaders = $headers->listAll(); + foreach ($listHeaders as $hName) { + // Check if we need to ignore Header + if (! isset($this->_ignoredHeaders[strtolower($hName)])) { + if ($headers->has($hName)) { + $tmp = $headers->getAll($hName); + foreach ($tmp as $header) { + if ($header->getFieldBody() != '') { + $this->_addHeader($header->toString()); + $this->_signedHeaders[] = $header->getFieldName(); + } + } + } + } + } + + return $this; + } + + /** + * Add the signature to the given Headers + * + * @param Swift_Mime_HeaderSet $headers + * @return Swift_Signers_DKIMSigner + */ + public function addSignature(Swift_Mime_HeaderSet $headers) + { + // Prepare the DKIM-Signature + $params = array('v' => '1', 'a' => $this->_hashAlgorithm, 'bh' => base64_encode($this->_bodyHash), 'd' => $this->_domainName, 'h' => implode(': ', $this->_signedHeaders), 'i' => $this->_signerIdentity, 's' => $this->_selector); + if ($this->_bodyCanon != 'simple') { + $params['c'] = $this->_headerCanon . '/' . $this->_bodyCanon; + } elseif ($this->_headerCanon != 'simple') { + $params['c'] = $this->_headerCanon; + } + if ($this->_showLen) { + $params['l'] = $this->_bodyLen; + } + if ($this->_signatureTimestamp === true) { + $params['t'] = time(); + if ($this->_signatureExpiration !== false) { + $params['x'] = $params['t'] + $this->_signatureExpiration; + } + } else { + if ($this->_signatureTimestamp !== false) { + $params['t'] = $this->_signatureTimestamp; + } + if ($this->_signatureExpiration !== false) { + $params['x'] = $this->_signatureExpiration; + } + } + if ($this->_debugHeaders) { + $params['z'] = implode('|', $this->_debugHeadersData); + } + $string = ''; + foreach ($params as $k => $v) { + $string .= $k . '=' . $v . '; '; + } + $string = trim($string); + $headers->addTextHeader('DKIM-Signature', $string); + // Add the last DKIM-Signature + $tmp = $headers->getAll('DKIM-Signature'); + $this->_dkimHeader = end($tmp); + $this->_addHeader(trim($this->_dkimHeader->toString()) . "\r\n b=", true); + $this->_endOfHeaders(); + if ($this->_debugHeaders) { + $headers->addTextHeader('X-DebugHash', base64_encode($this->_headerHash)); + } + $this->_dkimHeader->setValue($string . " b=" . trim(chunk_split(base64_encode($this->_getEncryptedHash()), 73, " "))); + + return $this; + } + + /* Private helpers */ + + protected function _addHeader($header, $is_sig = false) + { + switch ($this->_headerCanon) { + case 'relaxed' : + // Prepare Header and cascade + $exploded = explode(':', $header, 2); + $name = strtolower(trim($exploded[0])); + $value = str_replace("\r\n", "", $exploded[1]); + $value = preg_replace("/[ \t][ \t]+/", " ", $value); + $header = $name . ":" . trim($value) . ($is_sig ? '' : "\r\n"); + case 'simple' : + // Nothing to do + } + $this->_addToHeaderHash($header); + } + + protected function _endOfHeaders() + { + //$this->_headerHash=hash_final($this->_headerHashHandler, true); + } + + protected function _canonicalizeBody($string) + { + $len = strlen($string); + $canon = ''; + $method = ($this->_bodyCanon == "relaxed"); + for ($i = 0; $i < $len; ++$i) { + if ($this->_bodyCanonIgnoreStart > 0) { + --$this->_bodyCanonIgnoreStart; + continue; + } + switch ($string[$i]) { + case "\r" : + $this->_bodyCanonLastChar = "\r"; + break; + case "\n" : + if ($this->_bodyCanonLastChar == "\r") { + if ($method) { + $this->_bodyCanonSpace = false; + } + if ($this->_bodyCanonLine == '') { + ++$this->_bodyCanonEmptyCounter; + } else { + $this->_bodyCanonLine = ''; + $canon .= "\r\n"; + } + } else { + // Wooops Error + // todo handle it but should never happen + } + break; + case " " : + case "\t" : + if ($method) { + $this->_bodyCanonSpace = true; + break; + } + default : + if ($this->_bodyCanonEmptyCounter > 0) { + $canon .= str_repeat("\r\n", $this->_bodyCanonEmptyCounter); + $this->_bodyCanonEmptyCounter = 0; + } + if ($this->_bodyCanonSpace) { + $this->_bodyCanonLine .= ' '; + $canon .= ' '; + $this->_bodyCanonSpace = false; + } + $this->_bodyCanonLine .= $string[$i]; + $canon .= $string[$i]; + } + } + $this->_addToBodyHash($canon); + } + + protected function _endOfBody() + { + // Add trailing Line return if last line is non empty + if (strlen($this->_bodyCanonLine) > 0) { + $this->_addToBodyHash("\r\n"); + } + $this->_bodyHash = hash_final($this->_bodyHashHandler, true); + } + + private function _addToBodyHash($string) + { + $len = strlen($string); + if ($len > ($new_len = ($this->_maxLen - $this->_bodyLen))) { + $string = substr($string, 0, $new_len); + $len = $new_len; + } + hash_update($this->_bodyHashHandler, $string); + $this->_bodyLen += $len; + } + + private function _addToHeaderHash($header) + { + if ($this->_debugHeaders) { + $this->_debugHeadersData[] = trim($header); + } + $this->_headerCanonData .= $header; + } + + private function _getEncryptedHash() + { + $signature = ''; + switch ($this->_hashAlgorithm) { + case 'rsa-sha1': + $algorithm = 'sha1'; + break; + case 'rsa-sha256': + $algorithm = 'sha256'; + break; + } + $pkeyId=openssl_get_privatekey($this->_privateKey); + if (!$pkeyId) { + throw new Swift_SwiftException('Unable to load DKIM Private Key ['.openssl_error_string().']'); + } + if (openssl_sign($this->_headerCanonData, $signature, $this->_privateKey, $algorithm)) { + return $signature; + } + throw new Swift_SwiftException('Unable to sign DKIM Hash ['.openssl_error_string().']'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DomainKeySigner.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DomainKeySigner.php new file mode 100644 index 0000000..e1cfbc4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DomainKeySigner.php @@ -0,0 +1,504 @@ + + */ +class Swift_Signers_DomainKeySigner implements Swift_Signers_HeaderSigner +{ + /** + * PrivateKey + * + * @var string + */ + protected $_privateKey; + + /** + * DomainName + * + * @var string + */ + protected $_domainName; + + /** + * Selector + * + * @var string + */ + protected $_selector; + + /** + * Hash algorithm used + * + * @var string + */ + protected $_hashAlgorithm = 'rsa-sha1'; + + /** + * Canonisation method + * + * @var string + */ + protected $_canon = 'simple'; + + /** + * Headers not being signed + * + * @var array + */ + protected $_ignoredHeaders = array(); + + /** + * Signer identity + * + * @var unknown_type + */ + protected $_signerIdentity; + + /** + * Must we embed signed headers? + * + * @var boolean + */ + protected $_debugHeaders = false; + + // work variables + /** + * Headers used to generate hash + * + * @var array + */ + private $_signedHeaders = array(); + + /** + * If debugHeaders is set store debugDatas here + * + * @var string + */ + private $_debugHeadersData = ''; + + /** + * Stores the signature header + * + * @var Swift_Mime_Headers_ParameterizedHeader + */ + protected $_domainKeyHeader; + + /** + * Hash Handler + * + * @var hash_ressource + */ + private $_hashHandler; + + private $_hash; + + private $_canonData = ''; + + private $_bodyCanonEmptyCounter = 0; + + private $_bodyCanonIgnoreStart = 2; + + private $_bodyCanonSpace = false; + + private $_bodyCanonLastChar = null; + + private $_bodyCanonLine = ''; + + private $_bound = array(); + + /** + * Constructor + * + * @param string $privateKey + * @param string $domainName + * @param string $selector + */ + public function __construct($privateKey, $domainName, $selector) + { + $this->_privateKey = $privateKey; + $this->_domainName = $domainName; + $this->_signerIdentity = '@' . $domainName; + $this->_selector = $selector; + } + + /** + * Resets internal states + * + * @return Swift_Signers_DomainKeysSigner + */ + public function reset() + { + $this->_hash = null; + $this->_hashHandler = null; + $this->_bodyCanonIgnoreStart = 2; + $this->_bodyCanonEmptyCounter = 0; + $this->_bodyCanonLastChar = NULL; + $this->_bodyCanonSpace = false; + + return $this; + } + + /** + * Writes $bytes to the end of the stream. + * + * Writing may not happen immediately if the stream chooses to buffer. If + * you want to write these bytes with immediate effect, call {@link commit()} + * after calling write(). + * + * This method returns the sequence ID of the write (i.e. 1 for first, 2 for + * second, etc etc). + * + * @param string $bytes + * @return int + * @throws Swift_IoException + * @return Swift_Signers_DomainKeysSigner + */ + public function write($bytes) + { + $this->_canonicalizeBody($bytes); + foreach ($this->_bound as $is) { + $is->write($bytes); + } + + return $this; + } + + /** + * For any bytes that are currently buffered inside the stream, force them + * off the buffer. + * + * @throws Swift_IoException + * @return Swift_Signers_DomainKeysSigner + */ + public function commit() + { + // Nothing to do + return $this; + } + + /** + * 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 + * @return Swift_Signers_DomainKeysSigner + */ + public function bind(Swift_InputByteStream $is) + { + // Don't have to mirror anything + $this->_bound[] = $is; + + return $this; + } + + /** + * 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 + * @return Swift_Signers_DomainKeysSigner + */ + public function unbind(Swift_InputByteStream $is) + { + // Don't have to mirror anything + foreach ($this->_bound as $k => $el) { + if ($el == $is) { + unset($this->_bound[$k]); + + return; + } + } + + return $this; + } + + /** + * Flush the contents of the stream (empty it) and set the internal pointer + * to the beginning. + * + * @throws Swift_IoException + * @return Swift_Signers_DomainKeysSigner + */ + public function flushBuffers() + { + $this->reset(); + + return $this; + } + + /** + * Set hash_algorithm, must be one of rsa-sha256 | rsa-sha1 defaults to rsa-sha256 + * + * @param string $hash + * @return Swift_Signers_DomainKeysSigner + */ + public function setHashAlgorithm($hash) + { + $this->_hashAlgorithm = 'rsa-sha1'; + + return $this; + } + + /** + * Set the canonicalization algorithm + * + * @param string $canon simple | nofws defaults to simple + * @return Swift_Signers_DomainKeysSigner + */ + public function setCanon($canon) + { + if ($canon == 'nofws') { + $this->_canon = 'nofws'; + } else { + $this->_canon = 'simple'; + } + + return $this; + } + + /** + * Set the signer identity + * + * @param string $identity + * @return Swift_Signers_DomainKeySigner + */ + public function setSignerIdentity($identity) + { + $this->_signerIdentity = $identity; + + return $this; + } + + /** + * Enable / disable the DebugHeaders + * + * @param boolean $debug + * @return Swift_Signers_DomainKeySigner + */ + public function setDebugHeaders($debug) + { + $this->_debugHeaders = (bool) $debug; + + return $this; + } + + /** + * Start Body + * + */ + public function startBody() + { + } + + /** + * End Body + * + */ + public function endBody() + { + $this->_endOfBody(); + } + + /** + * Returns the list of Headers Tampered by this plugin + * + * @return array + */ + public function getAlteredHeaders() + { + if ($this->_debugHeaders) { + return array('DomainKey-Signature', 'X-DebugHash'); + } else { + return array('DomainKey-Signature'); + } + } + + /** + * Adds an ignored Header + * + * @param string $header_name + * @return Swift_Signers_DomainKeySigner + */ + public function ignoreHeader($header_name) + { + $this->_ignoredHeaders[strtolower($header_name)] = true; + + return $this; + } + + /** + * Set the headers to sign + * + * @param Swift_Mime_HeaderSet $headers + * @return Swift_Signers_DomainKeySigner + */ + public function setHeaders(Swift_Mime_HeaderSet $headers) + { + $this->_startHash(); + $this->_canonData = ''; + // Loop through Headers + $listHeaders = $headers->listAll(); + foreach ($listHeaders as $hName) { + // Check if we need to ignore Header + if (! isset($this->_ignoredHeaders[strtolower($hName)])) { + if ($headers->has($hName)) { + $tmp = $headers->getAll($hName); + foreach ($tmp as $header) { + if ($header->getFieldBody() != '') { + $this->_addHeader($header->toString()); + $this->_signedHeaders[] = $header->getFieldName(); + } + } + } + } + } + $this->_endOfHeaders(); + + return $this; + } + + /** + * Add the signature to the given Headers + * + * @param Swift_Mime_HeaderSet $headers + * @return Swift_Signers_DomainKeySigner + */ + public function addSignature(Swift_Mime_HeaderSet $headers) + { + // Prepare the DomainKey-Signature Header + $params = array('a' => $this->_hashAlgorithm, 'b' => chunk_split(base64_encode($this->_getEncryptedHash()), 73, " "), 'c' => $this->_canon, 'd' => $this->_domainName, 'h' => implode(': ', $this->_signedHeaders), 'q' => 'dns', 's' => $this->_selector); + $string = ''; + foreach ($params as $k => $v) { + $string .= $k . '=' . $v . '; '; + } + $string = trim($string); + $headers->addTextHeader('DomainKey-Signature', $string); + + return $this; + } + + /* Private helpers */ + + protected function _addHeader($header) + { + switch ($this->_canon) { + case 'nofws' : + // Prepare Header and cascade + $exploded = explode(':', $header, 2); + $name = strtolower(trim($exploded[0])); + $value = str_replace("\r\n", "", $exploded[1]); + $value = preg_replace("/[ \t][ \t]+/", " ", $value); + $header = $name . ":" . trim($value) . "\r\n"; + case 'simple' : + // Nothing to do + } + $this->_addToHash($header); + } + + protected function _endOfHeaders() + { + $this->_bodyCanonEmptyCounter = 1; + } + + protected function _canonicalizeBody($string) + { + $len = strlen($string); + $canon = ''; + $nofws = ($this->_canon == "nofws"); + for ($i = 0; $i < $len; ++$i) { + if ($this->_bodyCanonIgnoreStart > 0) { + --$this->_bodyCanonIgnoreStart; + continue; + } + switch ($string[$i]) { + case "\r" : + $this->_bodyCanonLastChar = "\r"; + break; + case "\n" : + if ($this->_bodyCanonLastChar == "\r") { + if ($nofws) { + $this->_bodyCanonSpace = false; + } + if ($this->_bodyCanonLine == '') { + ++$this->_bodyCanonEmptyCounter; + } else { + $this->_bodyCanonLine = ''; + $canon .= "\r\n"; + } + } else { + // Wooops Error + throw new Swift_SwiftException('Invalid new line sequence in mail found \n without preceding \r'); + } + break; + case " " : + case "\t" : + case "\x09": //HTAB + if ($nofws) { + $this->_bodyCanonSpace = true; + break; + } + default : + if ($this->_bodyCanonEmptyCounter > 0) { + $canon .= str_repeat("\r\n", $this->_bodyCanonEmptyCounter); + $this->_bodyCanonEmptyCounter = 0; + } + $this->_bodyCanonLine .= $string[$i]; + $canon .= $string[$i]; + } + } + $this->_addToHash($canon); + } + + protected function _endOfBody() + { + if (strlen($this->_bodyCanonLine) > 0) { + $this->_addToHash("\r\n"); + } + $this->_hash = hash_final($this->_hashHandler, true); + } + + private function _addToHash($string) + { + $this->_canonData .= $string; + hash_update($this->_hashHandler, $string); + } + + private function _startHash() + { + // Init + switch ($this->_hashAlgorithm) { + case 'rsa-sha1' : + $this->_hashHandler = hash_init('sha1'); + break; + } + $this->_canonLine = ''; + } + + private function _getEncryptedHash() + { + $signature = ''; + $pkeyId=openssl_get_privatekey($this->_privateKey); + if (!$pkeyId) { + throw new Swift_SwiftException('Unable to load DomainKey Private Key ['.openssl_error_string().']'); + } + if (openssl_sign($this->_canonData, $signature, $pkeyId, 'sha1')) { + return $signature; + } + throw new Swift_SwiftException('Unable to sign DomainKey Hash ['.openssl_error_string().']'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/HeaderSigner.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/HeaderSigner.php new file mode 100644 index 0000000..47091db --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/HeaderSigner.php @@ -0,0 +1,67 @@ + + */ +interface Swift_Signers_HeaderSigner extends Swift_Signer, Swift_InputByteStream +{ + /** + * Exclude an header from the signed headers + * + * @param string $header_name + * + * @return Swift_Signers_HeaderSigner + */ + public function ignoreHeader($header_name); + + /** + * Prepare the Signer to get a new Body + * + * @return Swift_Signers_HeaderSigner + */ + public function startBody(); + + /** + * Give the signal that the body has finished streaming + * + * @return Swift_Signers_HeaderSigner + */ + public function endBody(); + + /** + * Give the headers already given + * + * @param Swift_Mime_SimpleHeaderSet $headers + * + * @return Swift_Signers_HeaderSigner + */ + public function setHeaders(Swift_Mime_HeaderSet $headers); + + /** + * Add the header(s) to the headerSet + * + * @param Swift_Mime_HeaderSet $headers + * + * @return Swift_Signers_HeaderSigner + */ + public function addSignature(Swift_Mime_HeaderSet $headers); + + /** + * Return the list of header a signer might tamper + * + * @return array + */ + public function getAlteredHeaders(); +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/SMimeSigner.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/SMimeSigner.php new file mode 100644 index 0000000..3a43b5f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/SMimeSigner.php @@ -0,0 +1,430 @@ + + */ +class Swift_Signers_SMimeSigner implements Swift_Signers_BodySigner +{ + protected $signCertificate; + protected $signPrivateKey; + protected $encryptCert; + protected $signThenEncrypt = true; + protected $signLevel; + protected $encryptLevel; + protected $signOptions; + protected $encryptOptions; + protected $encryptCipher; + + /** + * @var Swift_StreamFilters_StringReplacementFilterFactory + */ + protected $replacementFactory; + + /** + * @var Swift_Mime_HeaderFactory + */ + protected $headerFactory; + + /** + * Constructor. + * + * @param string $certificate + * @param string $privateKey + * @param string $encryptCertificate + */ + public function __construct($signCertificate = null, $signPrivateKey = null, $encryptCertificate = null) + { + if (null !== $signPrivateKey) { + $this->setSignCertificate($signCertificate, $signPrivateKey); + } + + if (null !== $encryptCertificate) { + $this->setEncryptCertificate($encryptCertificate); + } + + $this->replacementFactory = Swift_DependencyContainer::getInstance() + ->lookup('transport.replacementfactory'); + + $this->signOptions = PKCS7_DETACHED; + + // Supported since php5.4 + if (defined('OPENSSL_CIPHER_AES_128_CBC')) { + $this->encryptCipher = OPENSSL_CIPHER_AES_128_CBC; + } else { + $this->encryptCipher = OPENSSL_CIPHER_RC2_128; + } + } + + /** + * Returns an new Swift_Signers_SMimeSigner instance. + * + * @param string $certificate + * @param string $privateKey + * + * @return Swift_Signers_SMimeSigner + */ + public static function newInstance($certificate = null, $privateKey = null) + { + return new self($certificate, $privateKey); + } + + /** + * Set the certificate location to use for signing. + * + * @link http://www.php.net/manual/en/openssl.pkcs7.flags.php + * + * @param string $certificate + * @param string|array $privateKey If the key needs an passphrase use array('file-location', 'passphrase') instead + * @param integer $signOptions Bitwise operator options for openssl_pkcs7_sign() + * + * @return Swift_Signers_SMimeSigner + */ + public function setSignCertificate($certificate, $privateKey = null, $signOptions = PKCS7_DETACHED) + { + $this->signCertificate = 'file://' . str_replace('\\', '/', realpath($certificate)); + + if (null !== $privateKey) { + if (is_array($privateKey)) { + $this->signPrivateKey = $privateKey; + $this->signPrivateKey[0] = 'file://' . str_replace('\\', '/', realpath($privateKey[0])); + } else { + $this->signPrivateKey = 'file://' . str_replace('\\', '/', realpath($privateKey)); + } + } + + $this->signOptions = $signOptions; + + return $this; + } + + /** + * Set the certificate location to use for encryption. + * + * @link http://www.php.net/manual/en/openssl.pkcs7.flags.php + * @link http://nl3.php.net/manual/en/openssl.ciphers.php + * + * @param string|array $recipientCerts Either an single X.509 certificate, or an assoc array of X.509 certificates. + * @param integer $cipher + * + * @return Swift_Signers_SMimeSigner + */ + public function setEncryptCertificate($recipientCerts, $cipher = null) + { + if (is_array($recipientCerts)) { + $this->encryptCert = array(); + + foreach ($recipientCerts as $cert) { + $this->encryptCert[] = 'file://' . str_replace('\\', '/', realpath($cert)); + } + } else { + $this->encryptCert = 'file://' . str_replace('\\', '/', realpath($recipientCerts)); + } + + if (null !== $cipher) { + $this->encryptCipher = $cipher; + } + + return $this; + } + + /** + * @return string + */ + public function getSignCertificate() + { + return $this->signCertificate; + } + + /** + * @return string + */ + public function getSignPrivateKey() + { + return $this->signPrivateKey; + } + + /** + * Set perform signing before encryption. + * + * The default is to first sign the message and then encrypt. + * But some older mail clients, namely Microsoft Outlook 2000 will work when the message first encrypted. + * As this goes against the official specs, its recommended to only use 'encryption -> signing' when specifically targeting these 'broken' clients. + * + * @param string $signThenEncrypt + * + * @return Swift_Signers_SMimeSigner + */ + public function setSignThenEncrypt($signThenEncrypt = true) + { + $this->signThenEncrypt = $signThenEncrypt; + + return $this; + } + + /** + * @return Boolean + */ + public function isSignThenEncrypt() + { + return $this->signThenEncrypt; + } + + /** + * Resets internal states. + * + * @return Swift_Signers_SMimeSigner + */ + public function reset() + { + return $this; + } + + /** + * Change the Swift_SignedMessage to apply the singing. + * + * @param Swift_SignedMessage $message + * + * @return Swift_Signers_SMimeSigner + */ + public function signMessage(Swift_SignedMessage $message) + { + if (null === $this->signCertificate && null === $this->encryptCert) { + return $this; + } + + // Store the message using ByteStream to a file{1} + // Remove all Children + // Sign file{1}, parse the new MIME headers and set them on the primary MimeEntity + // Set the singed-body as the new body (without boundary) + + $messageStream = new Swift_ByteStream_TemporaryFileByteStream(); + $this->toSMimeByteStream($messageStream, $message); + $message->setEncoder(Swift_DependencyContainer::getInstance()->lookup('mime.rawcontentencoder')); + + $message->setChildren(array()); + $this->streamToMime($messageStream, $message); + + } + + /** + * Return the list of header a signer might tamper. + * + * @return array + */ + public function getAlteredHeaders() + { + return array('Content-Type', 'Content-Transfer-Encoding', 'Content-Disposition'); + } + + /** + * @param Swift_InputByteStream $inputStream + * @param Swift_SignedMessage $mimeEntity + */ + protected function toSMimeByteStream(Swift_InputByteStream $inputStream, Swift_SignedMessage $message) + { + $mimeEntity = $this->createMessage($message); + $messageStream = new Swift_ByteStream_TemporaryFileByteStream(); + + $mimeEntity->toByteStream($messageStream); + $messageStream->commit(); + + if (null !== $this->signCertificate && null !== $this->encryptCert) { + $temporaryStream = new Swift_ByteStream_TemporaryFileByteStream(); + + if ($this->signThenEncrypt) { + $this->messageStreamToSignedByteStream($messageStream, $temporaryStream); + $this->messageStreamToEncryptedByteStream($temporaryStream, $inputStream); + } else { + $this->messageStreamToEncryptedByteStream($messageStream, $temporaryStream); + $this->messageStreamToSignedByteStream($temporaryStream, $inputStream); + } + } elseif ($this->signCertificate !== null) { + $this->messageStreamToSignedByteStream($messageStream, $inputStream); + } else { + $this->messageStreamToEncryptedByteStream($messageStream, $inputStream); + } + } + + /** + * @param Swift_SignedMessage $message + * + * @return Swift_Message + */ + protected function createMessage(Swift_SignedMessage $message) + { + $mimeEntity = new Swift_Message('', $message->getBody(), $message->getContentType(), $message->getCharset()); + $mimeEntity->setChildren($message->getChildren()); + + $messageHeaders = $mimeEntity->getHeaders(); + $messageHeaders->remove('Message-ID'); + $messageHeaders->remove('Date'); + $messageHeaders->remove('Subject'); + $messageHeaders->remove('MIME-Version'); + $messageHeaders->remove('To'); + $messageHeaders->remove('From'); + + return $mimeEntity; + } + + /** + * @param Swift_FileStream $outputStream + * @param Swift_InputByteStream $inputStream + * + * @throws Swift_IoException + */ + protected function messageStreamToSignedByteStream(Swift_FileStream $outputStream, Swift_InputByteStream $inputStream) + { + $signedMessageStream = new Swift_ByteStream_TemporaryFileByteStream(); + + if (!openssl_pkcs7_sign($outputStream->getPath(), $signedMessageStream->getPath(), $this->signCertificate, $this->signPrivateKey, array(), $this->signOptions)) { + throw new Swift_IoException(sprintf('Failed to sign S/Mime message. Error: "%s".', openssl_error_string())); + } + + $this->copyFromOpenSSLOutput($signedMessageStream, $inputStream); + } + + /** + * @param Swift_FileStream $outputStream + * @param Swift_InputByteStream $is + * + * @throws Swift_IoException + */ + protected function messageStreamToEncryptedByteStream(Swift_FileStream $outputStream, Swift_InputByteStream $is) + { + $encryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream(); + + if (!openssl_pkcs7_encrypt($outputStream->getPath(), $encryptedMessageStream->getPath(), $this->encryptCert, array(), 0, $this->encryptCipher)) { + throw new Swift_IoException(sprintf('Failed to encrypt S/Mime message. Error: "%s".', openssl_error_string())); + } + + $this->copyFromOpenSSLOutput($encryptedMessageStream, $is); + } + + /** + * @param Swift_OutputByteStream $fromStream + * @param Swift_InputByteStream $toStream + */ + protected function copyFromOpenSSLOutput(Swift_OutputByteStream $fromStream, Swift_InputByteStream $toStream) + { + $bufferLength = 4096; + $filteredStream = new Swift_ByteStream_TemporaryFileByteStream(); + $filteredStream->addFilter($this->replacementFactory->createFilter("\r\n", "\n"), 'CRLF to LF'); + $filteredStream->addFilter($this->replacementFactory->createFilter("\n", "\r\n"), 'LF to CRLF'); + + while (false !== ($buffer = $fromStream->read($bufferLength))) { + $filteredStream->write($buffer); + } + + $filteredStream->flushBuffers(); + + while (false !== ($buffer = $filteredStream->read($bufferLength))) { + $toStream->write($buffer); + } + + $toStream->commit(); + } + + /** + * Merges an OutputByteStream to Swift_SignedMessage. + * + * @param Swift_OutputByteStream $fromStream + * @param Swift_Message $message + */ + protected function streamToMime(Swift_OutputByteStream $fromStream, Swift_Message $message) + { + $bufferLength = 78; + $headerData = ''; + + $fromStream->setReadPointer(0); + + while (($buffer = $fromStream->read($bufferLength)) !== false) { + $headerData .= $buffer; + + if (false !== strpos($buffer, "\r\n\r\n")) { + break; + } + } + + $headersPosEnd = strpos($headerData, "\r\n\r\n"); + $headerData = trim($headerData); + $headerData = substr($headerData, 0, $headersPosEnd); + $headerLines = explode("\r\n", $headerData); + unset($headerData); + + $headers = array(); + $currentHeaderName = ''; + + foreach ($headerLines as $headerLine) { + // Line separated + if (ctype_space($headerLines[0]) || false === strpos($headerLine, ':')) { + $headers[$currentHeaderName] .= ' ' . trim($headerLine); + continue; + } + + $header = explode(':', $headerLine, 2); + $currentHeaderName = strtolower($header[0]); + $headers[$currentHeaderName] = trim($header[1]); + } + + $messageStream = new Swift_ByteStream_TemporaryFileByteStream(); + $messageStream->addFilter($this->replacementFactory->createFilter("\r\n", "\n"), 'CRLF to LF'); + $messageStream->addFilter($this->replacementFactory->createFilter("\n", "\r\n"), 'LF to CRLF'); + + $messageHeaders = $message->getHeaders(); + + // No need to check for 'application/pkcs7-mime', as this is always base64 + if ('multipart/signed;' === substr($headers['content-type'], 0, 17)) { + if (!preg_match('/boundary=("[^"]+"|(?:[^\s]+|$))/is', $headers['content-type'], $contentTypeData)) { + throw new Swift_SwiftException('Failed to find Boundary parameter'); + } + + $boundary = trim($contentTypeData['1'], '"'); + $boundaryLen = strlen($boundary); + + // Skip the header and CRLF CRLF + $fromStream->setReadPointer($headersPosEnd + 4); + + while (false !== ($buffer = $fromStream->read($bufferLength))) { + $messageStream->write($buffer); + } + + $messageStream->commit(); + + $messageHeaders->remove('Content-Transfer-Encoding'); + $message->setContentType($headers['content-type']); + $message->setBoundary($boundary); + $message->setBody($messageStream); + } else { + $fromStream->setReadPointer($headersPosEnd + 4); + + if (null === $this->headerFactory) { + $this->headerFactory = Swift_DependencyContainer::getInstance()->lookup('mime.headerfactory'); + } + + $message->setContentType($headers['content-type']); + $messageHeaders->set($this->headerFactory->createTextHeader('Content-Transfer-Encoding', $headers['content-transfer-encoding'])); + $messageHeaders->set($this->headerFactory->createTextHeader('Content-Disposition', $headers['content-disposition'])); + + while (false !== ($buffer = $fromStream->read($bufferLength))) { + $messageStream->write($buffer); + } + + $messageStream->commit(); + $message->setBody($messageStream); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php new file mode 100644 index 0000000..0ed3100 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php @@ -0,0 +1,59 @@ +createDependenciesFor('transport.smtp') + ); + + $this->setHost($host); + $this->setPort($port); + $this->setEncryption($security); + } + + /** + * Create a new SmtpTransport instance. + * + * @param string $host + * @param integer $port + * @param string $security + * + * @return Swift_SmtpTransport + */ + public static function newInstance($host = 'localhost', $port = 25, $security = null) + { + return new self($host, $port, $security); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Spool.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Spool.php new file mode 100644 index 0000000..981c178 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Spool.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Interface for spools. + * + * @package Swift + * @author Fabien Potencier + */ +interface Swift_Spool +{ + /** + * Starts this Spool mechanism. + */ + public function start(); + + /** + * Stops this Spool mechanism. + */ + public function stop(); + + /** + * Tests if this Spool mechanism has started. + * + * @return boolean + */ + public function isStarted(); + + /** + * Queues a message. + * + * @param Swift_Mime_Message $message The message to store + * + * @return boolean Whether the operation has succeeded + */ + public function queueMessage(Swift_Mime_Message $message); + + /** + * Sends messages using the given transport instance. + * + * @param Swift_Transport $transport A transport instance + * @param string[] $failedRecipients An array of failures by-reference + * + * @return integer The number of sent emails + */ + public function flushQueue(Swift_Transport $transport, &$failedRecipients = null); +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SpoolTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SpoolTransport.php new file mode 100644 index 0000000..8a135d4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SpoolTransport.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Stores Messages in a queue. + * + * @package Swift + * @author Fabien Potencier + */ +class Swift_SpoolTransport extends Swift_Transport_SpoolTransport +{ + /** + * Create a new SpoolTransport. + * + * @param Swift_Spool $spool + */ + public function __construct(Swift_Spool $spool) + { + $arguments = Swift_DependencyContainer::getInstance() + ->createDependenciesFor('transport.spool'); + + $arguments[] = $spool; + + call_user_func_array( + array($this, 'Swift_Transport_SpoolTransport::__construct'), + $arguments + ); + } + + /** + * Create a new SpoolTransport instance. + * + * @param Swift_Spool $spool + * + * @return Swift_SpoolTransport + */ + public static function newInstance(Swift_Spool $spool) + { + return new self($spool); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilter.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilter.php new file mode 100644 index 0000000..8b887bf --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilter.php @@ -0,0 +1,36 @@ +_search = $search; + $this->_index = array(); + $this->_tree = array(); + $this->_replace = array(); + $this->_repSize = array(); + + $tree = null; + $i = null; + $last_size = $size = 0; + foreach ($search as $i => $search_element) { + if ($tree !== null) { + $tree[-1] = min (count($replace) - 1, $i - 1); + $tree[-2] = $last_size; + } + $tree = &$this->_tree; + if (is_array ($search_element)) { + foreach ($search_element as $k => $char) { + $this->_index[$char] = true; + if (!isset($tree[$char])) { + $tree[$char] = array(); + } + $tree = &$tree[$char]; + } + $last_size = $k+1; + $size = max($size, $last_size); + } else { + $last_size = 1; + if (!isset($tree[$search_element])) { + $tree[$search_element] = array(); + } + $tree = &$tree[$search_element]; + $size = max($last_size, $size); + $this->_index[$search_element] = true; + } + } + if ($i !== null) { + $tree[-1] = min (count ($replace) - 1, $i); + $tree[-2] = $last_size; + $this->_treeMaxLen = $size; + } + foreach ($replace as $rep) { + if (!is_array($rep)) { + $rep = array ($rep); + } + $this->_replace[] = $rep; + } + for ($i = count($this->_replace) - 1; $i >= 0; --$i) { + $this->_replace[$i] = $rep = $this->filter($this->_replace[$i], $i); + $this->_repSize[$i] = count($rep); + } + } + + /** + * Returns true if based on the buffer passed more bytes should be buffered. + * + * @param array $buffer + * + * @return boolean + */ + public function shouldBuffer($buffer) + { + $endOfBuffer = end($buffer); + + return isset ($this->_index[$endOfBuffer]); + } + + /** + * Perform the actual replacements on $buffer and return the result. + * + * @param array $buffer + * @param integer $_minReplaces + * + * @return array + */ + public function filter($buffer, $_minReplaces = -1) + { + if ($this->_treeMaxLen == 0) { + return $buffer; + } + + $newBuffer = array(); + $buf_size = count($buffer); + for ($i = 0; $i < $buf_size; ++$i) { + $search_pos = $this->_tree; + $last_found = PHP_INT_MAX; + // We try to find if the next byte is part of a search pattern + for ($j = 0; $j <= $this->_treeMaxLen; ++$j) { + // We have a new byte for a search pattern + if (isset ($buffer [$p = $i + $j]) && isset($search_pos[$buffer[$p]])) { + $search_pos = $search_pos[$buffer[$p]]; + // We have a complete pattern, save, in case we don't find a better match later + if (isset($search_pos[- 1]) && $search_pos[-1] < $last_found + && $search_pos[-1] > $_minReplaces) + { + $last_found = $search_pos[-1]; + $last_size = $search_pos[-2]; + } + } + // We got a complete pattern + elseif ($last_found !== PHP_INT_MAX) { + // Adding replacement datas to output buffer + $rep_size = $this->_repSize[$last_found]; + for ($j = 0; $j < $rep_size; ++$j) { + $newBuffer[] = $this->_replace[$last_found][$j]; + } + // We Move cursor forward + $i += $last_size - 1; + // Edge Case, last position in buffer + if ($i >= $buf_size) { + $newBuffer[] = $buffer[$i]; + } + + // We start the next loop + continue 2; + } else { + // this byte is not in a pattern and we haven't found another pattern + break; + } + } + // Normal byte, move it to output buffer + $newBuffer[] = $buffer[$i]; + } + + return $newBuffer; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilter.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilter.php new file mode 100644 index 0000000..ca7454e --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilter.php @@ -0,0 +1,67 @@ +_search = $search; + $this->_replace = $replace; + } + + /** + * Returns true if based on the buffer passed more bytes should be buffered. + * + * @param string $buffer + * + * @return boolean + */ + public function shouldBuffer($buffer) + { + $endOfBuffer = substr($buffer, -1); + foreach ((array) $this->_search as $needle) { + if (false !== strpos($needle, $endOfBuffer)) { + return true; + } + } + + return false; + } + + /** + * Perform the actual replacements on $buffer and return the result. + * + * @param string $buffer + * + * @return string + */ + public function filter($buffer) + { + return str_replace($this->_search, $this->_replace, $buffer); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php new file mode 100644 index 0000000..bb4af77 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php @@ -0,0 +1,46 @@ +_filters[$search][$replace])) { + if (!isset($this->_filters[$search])) { + $this->_filters[$search] = array(); + } + + if (!isset($this->_filters[$search][$replace])) { + $this->_filters[$search][$replace] = array(); + } + + $this->_filters[$search][$replace] = new Swift_StreamFilters_StringReplacementFilter($search, $replace); + } + + return $this->_filters[$search][$replace]; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php new file mode 100644 index 0000000..f3bcbed --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php @@ -0,0 +1,28 @@ +_eventDispatcher = $dispatcher; + $this->_buffer = $buf; + $this->_lookupHostname(); + } + + /** + * Set the name of the local domain which Swift will identify itself as. + * + * This should be a fully-qualified domain name and should be truly the domain + * you're using. + * + * If your server doesn't have a domain name, use the IP in square + * brackets (i.e. [127.0.0.1]). + * + * @param string $domain + * + * @return Swift_Transport_AbstractSmtpTransport + */ + public function setLocalDomain($domain) + { + $this->_domain = $domain; + + return $this; + } + + /** + * Get the name of the domain Swift will identify as. + * + * @return string + */ + public function getLocalDomain() + { + return $this->_domain; + } + + /** + * Sets the source IP. + * + * @param string $source + */ + public function setSourceIp($source) + { + $this->_sourceIp=$source; + } + + /** + * Returns the IP used to connect to the destination + * + * @return string + */ + public function getSourceIp() + { + return $this->_sourceIp; + } + + /** + * Start the SMTP connection. + */ + public function start() + { + if (!$this->_started) { + if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this)) { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStarted'); + if ($evt->bubbleCancelled()) { + return; + } + } + + try { + $this->_buffer->initialize($this->_getBufferParams()); + } catch (Swift_TransportException $e) { + $this->_throwException($e); + } + $this->_readGreeting(); + $this->_doHeloCommand(); + + if ($evt) { + $this->_eventDispatcher->dispatchEvent($evt, 'transportStarted'); + } + + $this->_started = true; + } + } + + /** + * Test if an SMTP connection has been established. + * + * @return boolean + */ + public function isStarted() + { + return $this->_started; + } + + /** + * Send the given Message. + * + * Recipient/sender data will be retrieved from the Message API. + * The return value is the number of recipients who were accepted for delivery. + * + * @param Swift_Mime_Message $message + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $sent = 0; + $failedRecipients = (array) $failedRecipients; + + if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); + if ($evt->bubbleCancelled()) { + return 0; + } + } + + if (!$reversePath = $this->_getReversePath($message)) { + throw new Swift_TransportException( + 'Cannot send message without a sender address' + ); + } + + $to = (array) $message->getTo(); + $cc = (array) $message->getCc(); + $bcc = (array) $message->getBcc(); + + $message->setBcc(array()); + + try { + $sent += $this->_sendTo($message, $reversePath, $to, $failedRecipients); + $sent += $this->_sendCc($message, $reversePath, $cc, $failedRecipients); + $sent += $this->_sendBcc($message, $reversePath, $bcc, $failedRecipients); + } catch (Exception $e) { + $message->setBcc($bcc); + throw $e; + } + + $message->setBcc($bcc); + + if ($evt) { + if ($sent == count($to) + count($cc) + count($bcc)) { + $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS); + } elseif ($sent > 0) { + $evt->setResult(Swift_Events_SendEvent::RESULT_TENTATIVE); + } else { + $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED); + } + $evt->setFailedRecipients($failedRecipients); + $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + $message->generateId(); //Make sure a new Message ID is used + + return $sent; + } + + /** + * Stop the SMTP connection. + */ + public function stop() + { + if ($this->_started) { + if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this)) { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStopped'); + if ($evt->bubbleCancelled()) { + return; + } + } + + try { + $this->executeCommand("QUIT\r\n", array(221)); + } catch (Swift_TransportException $e) {} + + try { + $this->_buffer->terminate(); + + if ($evt) { + $this->_eventDispatcher->dispatchEvent($evt, 'transportStopped'); + } + } catch (Swift_TransportException $e) { + $this->_throwException($e); + } + } + $this->_started = false; + } + + /** + * Register a plugin. + * + * @param Swift_Events_EventListener $plugin + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + $this->_eventDispatcher->bindEventListener($plugin); + } + + /** + * Reset the current mail transaction. + */ + public function reset() + { + $this->executeCommand("RSET\r\n", array(250)); + } + + /** + * Get the IoBuffer where read/writes are occurring. + * + * @return Swift_Transport_IoBuffer + */ + public function getBuffer() + { + return $this->_buffer; + } + + /** + * Run a command against the buffer, expecting the given response codes. + * + * If no response codes are given, the response will not be validated. + * If codes are given, an exception will be thrown on an invalid response. + * + * @param string $command + * @param int[] $codes + * @param string[] $failures An array of failures by-reference + * + * @return string + */ + public function executeCommand($command, $codes = array(), &$failures = null) + { + $failures = (array) $failures; + $seq = $this->_buffer->write($command); + $response = $this->_getFullResponse($seq); + if ($evt = $this->_eventDispatcher->createCommandEvent($this, $command, $codes)) { + $this->_eventDispatcher->dispatchEvent($evt, 'commandSent'); + } + $this->_assertResponseCode($response, $codes); + + return $response; + } + + // -- Protected methods + + /** Read the opening SMTP greeting */ + protected function _readGreeting() + { + $this->_assertResponseCode($this->_getFullResponse(0), array(220)); + } + + /** Send the HELO welcome */ + protected function _doHeloCommand() + { + $this->executeCommand( + sprintf("HELO %s\r\n", $this->_domain), array(250) + ); + } + + /** Send the MAIL FROM command */ + protected function _doMailFromCommand($address) + { + $this->executeCommand( + sprintf("MAIL FROM: <%s>\r\n", $address), array(250) + ); + } + + /** Send the RCPT TO command */ + protected function _doRcptToCommand($address) + { + $this->executeCommand( + sprintf("RCPT TO: <%s>\r\n", $address), array(250, 251, 252) + ); + } + + /** Send the DATA command */ + protected function _doDataCommand() + { + $this->executeCommand("DATA\r\n", array(354)); + } + + /** Stream the contents of the message over the buffer */ + protected function _streamMessage(Swift_Mime_Message $message) + { + $this->_buffer->setWriteTranslations(array("\r\n." => "\r\n..")); + try { + $message->toByteStream($this->_buffer); + $this->_buffer->flushBuffers(); + } catch (Swift_TransportException $e) { + $this->_throwException($e); + } + $this->_buffer->setWriteTranslations(array()); + $this->executeCommand("\r\n.\r\n", array(250)); + } + + /** Determine the best-use reverse path for this message */ + protected function _getReversePath(Swift_Mime_Message $message) + { + $return = $message->getReturnPath(); + $sender = $message->getSender(); + $from = $message->getFrom(); + $path = null; + if (!empty($return)) { + $path = $return; + } elseif (!empty($sender)) { + // Don't use array_keys + reset($sender); // Reset Pointer to first pos + $path = key($sender); // Get key + } elseif (!empty($from)) { + reset($from); // Reset Pointer to first pos + $path = key($from); // Get key + } + + return $path; + } + + /** Throw a TransportException, first sending it to any listeners */ + protected function _throwException(Swift_TransportException $e) + { + if ($evt = $this->_eventDispatcher->createTransportExceptionEvent($this, $e)) { + $this->_eventDispatcher->dispatchEvent($evt, 'exceptionThrown'); + if (!$evt->bubbleCancelled()) { + throw $e; + } + } else { + throw $e; + } + } + + /** Throws an Exception if a response code is incorrect */ + protected function _assertResponseCode($response, $wanted) + { + list($code) = sscanf($response, '%3d'); + $valid = (empty($wanted) || in_array($code, $wanted)); + + if ($evt = $this->_eventDispatcher->createResponseEvent($this, $response, + $valid)) + { + $this->_eventDispatcher->dispatchEvent($evt, 'responseReceived'); + } + + if (!$valid) { + $this->_throwException( + new Swift_TransportException( + 'Expected response code ' . implode('/', $wanted) . ' but got code ' . + '"' . $code . '", with message "' . $response . '"', + $code) + ); + } + } + + /** Get an entire multi-line response using its sequence number */ + protected function _getFullResponse($seq) + { + $response = ''; + try { + do { + $line = $this->_buffer->readLine($seq); + $response .= $line; + } while (null !== $line && false !== $line && ' ' != $line{3}); + } catch (Swift_IoException $e) { + $this->_throwException( + new Swift_TransportException( + $e->getMessage()) + ); + } catch (Swift_TransportException $e) { + $this->_throwException($e); + } + + return $response; + } + + // -- Private methods + + /** Send an email to the given recipients from the given reverse path */ + private function _doMailTransaction($message, $reversePath, array $recipients, array &$failedRecipients) + { + $sent = 0; + $this->_doMailFromCommand($reversePath); + foreach ($recipients as $forwardPath) { + try { + $this->_doRcptToCommand($forwardPath); + $sent++; + } catch (Swift_TransportException $e) { + $failedRecipients[] = $forwardPath; + } + } + + if ($sent != 0) { + $this->_doDataCommand(); + $this->_streamMessage($message); + } else { + $this->reset(); + } + + return $sent; + } + + /** Send a message to the given To: recipients */ + private function _sendTo(Swift_Mime_Message $message, $reversePath, array $to, array &$failedRecipients) + { + if (empty($to)) { + return 0; + } + + return $this->_doMailTransaction($message, $reversePath, array_keys($to), + $failedRecipients); + } + + /** Send a message to the given Cc: recipients */ + private function _sendCc(Swift_Mime_Message $message, $reversePath, array $cc, array &$failedRecipients) + { + if (empty($cc)) { + return 0; + } + + return $this->_doMailTransaction($message, $reversePath, array_keys($cc), + $failedRecipients); + } + + /** Send a message to all Bcc: recipients */ + private function _sendBcc(Swift_Mime_Message $message, $reversePath, array $bcc, array &$failedRecipients) + { + $sent = 0; + foreach ($bcc as $forwardPath => $name) { + $message->setBcc(array($forwardPath => $name)); + $sent += $this->_doMailTransaction( + $message, $reversePath, array($forwardPath), $failedRecipients + ); + } + + return $sent; + } + + /** Try to determine the hostname of the server this is run on */ + private function _lookupHostname() + { + if (!empty($_SERVER['SERVER_NAME']) + && $this->_isFqdn($_SERVER['SERVER_NAME'])) + { + $this->_domain = $_SERVER['SERVER_NAME']; + } elseif (!empty($_SERVER['SERVER_ADDR'])) { + $this->_domain = sprintf('[%s]', $_SERVER['SERVER_ADDR']); + } + } + + /** Determine is the $hostname is a fully-qualified name */ + private function _isFqdn($hostname) + { + //We could do a really thorough check, but there's really no point + if (false !== $dotPos = strpos($hostname, '.')) { + return ($dotPos > 0) && ($dotPos != strlen($hostname) - 1); + } else { + return false; + } + } + + /** + * Destructor. + */ + public function __destruct() + { + $this->stop(); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php new file mode 100644 index 0000000..80b4712 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php @@ -0,0 +1,83 @@ +executeCommand("AUTH CRAM-MD5\r\n", array(334)); + $challenge = base64_decode(substr($challenge, 4)); + $message = base64_encode( + $username . ' ' . $this->_getResponse($password, $challenge) + ); + $agent->executeCommand(sprintf("%s\r\n", $message), array(235)); + + return true; + } catch (Swift_TransportException $e) { + $agent->executeCommand("RSET\r\n", array(250)); + + return false; + } + } + + /** + * Generate a CRAM-MD5 response from a server challenge. + * + * @param string $secret + * @param string $challenge + * + * @return string + */ + private function _getResponse($secret, $challenge) + { + if (strlen($secret) > 64) { + $secret = pack('H32', md5($secret)); + } + + if (strlen($secret) < 64) { + $secret = str_pad($secret, 64, chr(0)); + } + + $k_ipad = substr($secret, 0, 64) ^ str_repeat(chr(0x36), 64); + $k_opad = substr($secret, 0, 64) ^ str_repeat(chr(0x5C), 64); + + $inner = pack('H32', md5($k_ipad . $challenge)); + $digest = md5($k_opad . $inner); + + return $digest; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php new file mode 100644 index 0000000..deca3a5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php @@ -0,0 +1,53 @@ +executeCommand("AUTH LOGIN\r\n", array(334)); + $agent->executeCommand(sprintf("%s\r\n", base64_encode($username)), array(334)); + $agent->executeCommand(sprintf("%s\r\n", base64_encode($password)), array(235)); + + return true; + } catch (Swift_TransportException $e) { + $agent->executeCommand("RSET\r\n", array(250)); + + return false; + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php new file mode 100644 index 0000000..ffa9af3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php @@ -0,0 +1,52 @@ +executeCommand(sprintf("AUTH PLAIN %s\r\n", $message), array(235)); + + return true; + } catch (Swift_TransportException $e) { + $agent->executeCommand("RSET\r\n", array(250)); + + return false; + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php new file mode 100644 index 0000000..40b0908 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php @@ -0,0 +1,268 @@ +setAuthenticators($authenticators); + } + + /** + * Set the Authenticators which can process a login request. + * + * @param Swift_Transport_Esmtp_Authenticator[] $authenticators + */ + public function setAuthenticators(array $authenticators) + { + $this->_authenticators = $authenticators; + } + + /** + * Get the Authenticators which can process a login request. + * + * @return Swift_Transport_Esmtp_Authenticator[] + */ + public function getAuthenticators() + { + return $this->_authenticators; + } + + /** + * Set the username to authenticate with. + * + * @param string $username + */ + public function setUsername($username) + { + $this->_username = $username; + } + + /** + * Get the username to authenticate with. + * + * @return string + */ + public function getUsername() + { + return $this->_username; + } + + /** + * Set the password to authenticate with. + * + * @param string $password + */ + public function setPassword($password) + { + $this->_password = $password; + } + + /** + * Get the password to authenticate with. + * + * @return string + */ + public function getPassword() + { + return $this->_password; + } + + /** + * Set the auth mode to use to authenticate. + * + * @param string $mode + */ + public function setAuthMode($mode) + { + $this->_auth_mode = $mode; + } + + /** + * Get the auth mode to use to authenticate. + * + * @return string + */ + public function getAuthMode() + { + return $this->_auth_mode; + } + + /** + * Get the name of the ESMTP extension this handles. + * + * @return boolean + */ + public function getHandledKeyword() + { + return 'AUTH'; + } + + /** + * Set the parameters which the EHLO greeting indicated. + * + * @param string[] $parameters + */ + public function setKeywordParams(array $parameters) + { + $this->_esmtpParams = $parameters; + } + + /** + * Runs immediately after a EHLO has been issued. + * + * @param Swift_Transport_SmtpAgent $agent to read/write + */ + public function afterEhlo(Swift_Transport_SmtpAgent $agent) + { + if ($this->_username) { + $count = 0; + foreach ($this->_getAuthenticatorsForAgent() as $authenticator) { + if (in_array(strtolower($authenticator->getAuthKeyword()), + array_map('strtolower', $this->_esmtpParams))) + { + $count++; + if ($authenticator->authenticate($agent, $this->_username, $this->_password)) { + return; + } + } + } + throw new Swift_TransportException( + 'Failed to authenticate on SMTP server with username "' . + $this->_username . '" using ' . $count . ' possible authenticators' + ); + } + } + + /** + * Not used. + */ + public function getMailParams() + { + return array(); + } + + /** + * Not used. + */ + public function getRcptParams() + { + return array(); + } + + /** + * Not used. + */ + public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = array(), &$failedRecipients = null, &$stop = false) + { + } + + /** + * Returns +1, -1 or 0 according to the rules for usort(). + * + * This method is called to ensure extensions can be execute in an appropriate order. + * + * @param string $esmtpKeyword to compare with + * + * @return int + */ + public function getPriorityOver($esmtpKeyword) + { + return 0; + } + + /** + * Returns an array of method names which are exposed to the Esmtp class. + * + * @return string[] + */ + public function exposeMixinMethods() + { + return array('setUsername', 'getUsername', 'setPassword', 'getPassword', 'setAuthMode', 'getAuthMode'); + } + + /** + * Not used. + */ + public function resetState() + { + } + + // -- Protected methods + + /** + * Returns the authenticator list for the given agent. + * + * @param Swift_Transport_SmtpAgent $agent + * + * @return array + */ + protected function _getAuthenticatorsForAgent() + { + if (!$mode = strtolower($this->_auth_mode)) { + return $this->_authenticators; + } + + foreach ($this->_authenticators as $authenticator) { + if (strtolower($authenticator->getAuthKeyword()) == $mode) { + return array($authenticator); + } + } + + throw new Swift_TransportException('Auth mode '.$mode.' is invalid'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Authenticator.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Authenticator.php new file mode 100644 index 0000000..0c6dc2e --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Authenticator.php @@ -0,0 +1,37 @@ +. + * + * @return string[] + */ + public function getMailParams(); + + /** + * Get params which are appended to RCPT TO:<>. + * + * @return string[] + */ + public function getRcptParams(); + + /** + * Runs when a command is due to be sent. + * + * @param Swift_Transport_SmtpAgent $agent to read/write + * @param string $command to send + * @param int[] $codes expected in response + * @param string[] $failedRecipients to collect failures + * @param boolean $stop to be set true by-reference if the command is now sent + */ + public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = array(), &$failedRecipients = null, &$stop = false); + + /** + * Returns +1, -1 or 0 according to the rules for usort(). + * + * This method is called to ensure extensions can be execute in an appropriate order. + * + * @param string $esmtpKeyword to compare with + * + * @return int + */ + public function getPriorityOver($esmtpKeyword); + + /** + * Returns an array of method names which are exposed to the Esmtp class. + * + * @return string[] + */ + public function exposeMixinMethods(); + + /** + * Tells this handler to clear any buffers and reset its state. + */ + public function resetState(); +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php new file mode 100644 index 0000000..19e2d77 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php @@ -0,0 +1,393 @@ + 'tcp', + 'host' => 'localhost', + 'port' => 25, + 'timeout' => 30, + 'blocking' => 1, + 'tls' => false, + 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET + ); + + /** + * Creates a new EsmtpTransport using the given I/O buffer. + * + * @param Swift_Transport_IoBuffer $buf + * @param Swift_Transport_EsmtpHandler[] $extensionHandlers + * @param Swift_Events_EventDispatcher $dispatcher + */ + public function __construct(Swift_Transport_IoBuffer $buf, array $extensionHandlers, Swift_Events_EventDispatcher $dispatcher) + { + parent::__construct($buf, $dispatcher); + $this->setExtensionHandlers($extensionHandlers); + } + + /** + * Set the host to connect to. + * + * @param string $host + * + * @return Swift_Transport_EsmtpTransport + */ + public function setHost($host) + { + $this->_params['host'] = $host; + + return $this; + } + + /** + * Get the host to connect to. + * + * @return string + */ + public function getHost() + { + return $this->_params['host']; + } + + /** + * Set the port to connect to. + * + * @param integer $port + * + * @return Swift_Transport_EsmtpTransport + */ + public function setPort($port) + { + $this->_params['port'] = (int) $port; + + return $this; + } + + /** + * Get the port to connect to. + * + * @return int + */ + public function getPort() + { + return $this->_params['port']; + } + + /** + * Set the connection timeout. + * + * @param integer $timeout seconds + * + * @return Swift_Transport_EsmtpTransport + */ + public function setTimeout($timeout) + { + $this->_params['timeout'] = (int) $timeout; + $this->_buffer->setParam('timeout', (int) $timeout); + + return $this; + } + + /** + * Get the connection timeout. + * + * @return int + */ + public function getTimeout() + { + return $this->_params['timeout']; + } + + /** + * Set the encryption type (tls or ssl) + * + * @param string $encryption + * + * @return Swift_Transport_EsmtpTransport + */ + public function setEncryption($encryption) + { + if ('tls' == $encryption) { + $this->_params['protocol'] = 'tcp'; + $this->_params['tls'] = true; + } else { + $this->_params['protocol'] = $encryption; + $this->_params['tls'] = false; + } + + return $this; + } + + /** + * Get the encryption type. + * + * @return string + */ + public function getEncryption() + { + return $this->_params['tls'] ? 'tls' : $this->_params['protocol']; + } + + /** + * Sets the source IP. + * + * @param string $source + * + * @return Swift_Transport_EsmtpTransport + */ + public function setSourceIp($source) + { + $this->_params['sourceIp']=$source; + + return $this; + } + + /** + * Returns the IP used to connect to the destination. + * + * @return string + */ + public function getSourceIp() + { + return $this->_params['sourceIp']; + } + + /** + * Set ESMTP extension handlers. + * + * @param Swift_Transport_EsmtpHandler[] $handlers + * + * @return Swift_Transport_EsmtpTransport + */ + public function setExtensionHandlers(array $handlers) + { + $assoc = array(); + foreach ($handlers as $handler) { + $assoc[$handler->getHandledKeyword()] = $handler; + } + uasort($assoc, array($this, '_sortHandlers')); + $this->_handlers = $assoc; + $this->_setHandlerParams(); + + return $this; + } + + /** + * Get ESMTP extension handlers. + * + * @return Swift_Transport_EsmtpHandler[] + */ + public function getExtensionHandlers() + { + return array_values($this->_handlers); + } + + /** + * Run a command against the buffer, expecting the given response codes. + * + * If no response codes are given, the response will not be validated. + * If codes are given, an exception will be thrown on an invalid response. + * + * @param string $command + * @param int[] $codes + * @param string[] $failures An array of failures by-reference + * + * @return string + */ + public function executeCommand($command, $codes = array(), &$failures = null) + { + $failures = (array) $failures; + $stopSignal = false; + $response = null; + foreach ($this->_getActiveHandlers() as $handler) { + $response = $handler->onCommand( + $this, $command, $codes, $failures, $stopSignal + ); + if ($stopSignal) { + return $response; + } + } + + return parent::executeCommand($command, $codes, $failures); + } + + // -- Mixin invocation code + + /** Mixin handling method for ESMTP handlers */ + public function __call($method, $args) + { + foreach ($this->_handlers as $handler) { + if (in_array(strtolower($method), + array_map('strtolower', (array) $handler->exposeMixinMethods()) + )) + { + $return = call_user_func_array(array($handler, $method), $args); + //Allow fluid method calls + if (is_null($return) && substr($method, 0, 3) == 'set') { + return $this; + } else { + return $return; + } + } + } + trigger_error('Call to undefined method ' . $method, E_USER_ERROR); + } + + // -- Protected methods + + /** Get the params to initialize the buffer */ + protected function _getBufferParams() + { + return $this->_params; + } + + /** Overridden to perform EHLO instead */ + protected function _doHeloCommand() + { + try { + $response = $this->executeCommand( + sprintf("EHLO %s\r\n", $this->_domain), array(250) + ); + } catch (Swift_TransportException $e) { + return parent::_doHeloCommand(); + } + + if ($this->_params['tls']) { + try { + $this->executeCommand("STARTTLS\r\n", array(220)); + + if (!$this->_buffer->startTLS()) { + throw new Swift_TransportException('Unable to connect with TLS encryption'); + } + + try { + $response = $this->executeCommand( + sprintf("EHLO %s\r\n", $this->_domain), array(250) + ); + } catch (Swift_TransportException $e) { + return parent::_doHeloCommand(); + } + } catch (Swift_TransportException $e) { + $this->_throwException($e); + } + } + + $this->_capabilities = $this->_getCapabilities($response); + $this->_setHandlerParams(); + foreach ($this->_getActiveHandlers() as $handler) { + $handler->afterEhlo($this); + } + } + + /** Overridden to add Extension support */ + protected function _doMailFromCommand($address) + { + $handlers = $this->_getActiveHandlers(); + $params = array(); + foreach ($handlers as $handler) { + $params = array_merge($params, (array) $handler->getMailParams()); + } + $paramStr = !empty($params) ? ' ' . implode(' ', $params) : ''; + $this->executeCommand( + sprintf("MAIL FROM: <%s>%s\r\n", $address, $paramStr), array(250) + ); + } + + /** Overridden to add Extension support */ + protected function _doRcptToCommand($address) + { + $handlers = $this->_getActiveHandlers(); + $params = array(); + foreach ($handlers as $handler) { + $params = array_merge($params, (array) $handler->getRcptParams()); + } + $paramStr = !empty($params) ? ' ' . implode(' ', $params) : ''; + $this->executeCommand( + sprintf("RCPT TO: <%s>%s\r\n", $address, $paramStr), array(250, 251, 252) + ); + } + + // -- Private methods + + /** Determine ESMTP capabilities by function group */ + private function _getCapabilities($ehloResponse) + { + $capabilities = array(); + $ehloResponse = trim($ehloResponse); + $lines = explode("\r\n", $ehloResponse); + array_shift($lines); + foreach ($lines as $line) { + if (preg_match('/^[0-9]{3}[ -]([A-Z0-9-]+)((?:[ =].*)?)$/Di', $line, $matches)) { + $keyword = strtoupper($matches[1]); + $paramStr = strtoupper(ltrim($matches[2], ' =')); + $params = !empty($paramStr) ? explode(' ', $paramStr) : array(); + $capabilities[$keyword] = $params; + } + } + + return $capabilities; + } + + /** Set parameters which are used by each extension handler */ + private function _setHandlerParams() + { + foreach ($this->_handlers as $keyword => $handler) { + if (array_key_exists($keyword, $this->_capabilities)) { + $handler->setKeywordParams($this->_capabilities[$keyword]); + } + } + } + + /** Get ESMTP handlers which are currently ok to use */ + private function _getActiveHandlers() + { + $handlers = array(); + foreach ($this->_handlers as $keyword => $handler) { + if (array_key_exists($keyword, $this->_capabilities)) { + $handlers[] = $handler; + } + } + + return $handlers; + } + + /** Custom sort for extension handler ordering */ + private function _sortHandlers($a, $b) + { + return $a->getPriorityOver($b->getHandledKeyword()); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.php new file mode 100644 index 0000000..d0d3f69 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.php @@ -0,0 +1,90 @@ +_transports); + $sent = 0; + + for ($i = 0; $i < $maxTransports + && $transport = $this->_getNextTransport(); ++$i) + { + try { + if (!$transport->isStarted()) { + $transport->start(); + } + + return $transport->send($message, $failedRecipients); + } catch (Swift_TransportException $e) { + $this->_killCurrentTransport(); + } + } + + if (count($this->_transports) == 0) { + throw new Swift_TransportException( + 'All Transports in FailoverTransport failed, or no Transports available' + ); + } + + return $sent; + } + + // -- Protected methods + + protected function _getNextTransport() + { + if (!isset($this->_currentTransport)) { + $this->_currentTransport = parent::_getNextTransport(); + } + + return $this->_currentTransport; + } + + protected function _killCurrentTransport() + { + $this->_currentTransport = null; + parent::_killCurrentTransport(); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/IoBuffer.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/IoBuffer.php new file mode 100644 index 0000000..7559ebf --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/IoBuffer.php @@ -0,0 +1,69 @@ +_transports = $transports; + $this->_deadTransports = array(); + } + + /** + * Get $transports to delegate to. + * + * @return Swift_Transport[] + */ + public function getTransports() + { + return array_merge($this->_transports, $this->_deadTransports); + } + + /** + * Test if this Transport mechanism has started. + * + * @return boolean + */ + public function isStarted() + { + return count($this->_transports) > 0; + } + + /** + * Start this Transport mechanism. + */ + public function start() + { + $this->_transports = array_merge($this->_transports, $this->_deadTransports); + } + + /** + * Stop this Transport mechanism. + */ + public function stop() + { + foreach ($this->_transports as $transport) { + $transport->stop(); + } + } + + /** + * Send the given Message. + * + * Recipient/sender data will be retrieved from the Message API. + * The return value is the number of recipients who were accepted for delivery. + * + * @param Swift_Mime_Message $message + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $maxTransports = count($this->_transports); + $sent = 0; + + for ($i = 0; $i < $maxTransports + && $transport = $this->_getNextTransport(); ++$i) + { + try { + if (!$transport->isStarted()) { + $transport->start(); + } + if ($sent = $transport->send($message, $failedRecipients)) { + break; + } + } catch (Swift_TransportException $e) { + $this->_killCurrentTransport(); + } + } + + if (count($this->_transports) == 0) { + throw new Swift_TransportException( + 'All Transports in LoadBalancedTransport failed, or no Transports available' + ); + } + + return $sent; + } + + /** + * Register a plugin. + * + * @param Swift_Events_EventListener $plugin + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + foreach ($this->_transports as $transport) { + $transport->registerPlugin($plugin); + } + } + + // -- Protected methods + + /** + * Rotates the transport list around and returns the first instance. + * + * @return Swift_Transport + */ + protected function _getNextTransport() + { + if ($next = array_shift($this->_transports)) { + $this->_transports[] = $next; + } + + return $next; + } + + /** + * Tag the currently used (top of stack) transport as dead/useless. + */ + protected function _killCurrentTransport() + { + if ($transport = array_pop($this->_transports)) { + try { + $transport->stop(); + } catch (Exception $e) { + } + $this->_deadTransports[] = $transport; + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php new file mode 100644 index 0000000..a9bff69 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php @@ -0,0 +1,34 @@ +_invoker = $invoker; + $this->_eventDispatcher = $eventDispatcher; + } + + /** + * Not used. + */ + public function isStarted() + { + return false; + } + + /** + * Not used. + */ + public function start() + { + } + + /** + * Not used. + */ + public function stop() + { + } + + /** + * Set the additional parameters used on the mail() function. + * + * This string is formatted for sprintf() where %s is the sender address. + * + * @param string $params + * + * @return Swift_Transport_MailTransport + */ + public function setExtraParams($params) + { + $this->_extraParams = $params; + + return $this; + } + + /** + * Get the additional parameters used on the mail() function. + * + * This string is formatted for sprintf() where %s is the sender address. + * + * @return string + */ + public function getExtraParams() + { + return $this->_extraParams; + } + + /** + * Send the given Message. + * + * Recipient/sender data will be retrieved from the Message API. + * The return value is the number of recipients who were accepted for delivery. + * + * @param Swift_Mime_Message $message + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $failedRecipients = (array) $failedRecipients; + + if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); + if ($evt->bubbleCancelled()) { + return 0; + } + } + + $count = ( + count((array) $message->getTo()) + + count((array) $message->getCc()) + + count((array) $message->getBcc()) + ); + + $toHeader = $message->getHeaders()->get('To'); + $subjectHeader = $message->getHeaders()->get('Subject'); + + if (!$toHeader) { + throw new Swift_TransportException( + 'Cannot send message without a recipient' + ); + } + $to = $toHeader->getFieldBody(); + $subject = $subjectHeader ? $subjectHeader->getFieldBody() : ''; + + $reversePath = $this->_getReversePath($message); + + //Remove headers that would otherwise be duplicated + $message->getHeaders()->remove('To'); + $message->getHeaders()->remove('Subject'); + + $messageStr = $message->toString(); + + $message->getHeaders()->set($toHeader); + $message->getHeaders()->set($subjectHeader); + + //Separate headers from body + if (false !== $endHeaders = strpos($messageStr, "\r\n\r\n")) { + $headers = substr($messageStr, 0, $endHeaders) . "\r\n"; //Keep last EOL + $body = substr($messageStr, $endHeaders + 4); + } else { + $headers = $messageStr . "\r\n"; + $body = ''; + } + + unset($messageStr); + + if ("\r\n" != PHP_EOL) { + //Non-windows (not using SMTP) + $headers = str_replace("\r\n", PHP_EOL, $headers); + $body = str_replace("\r\n", PHP_EOL, $body); + } else { + //Windows, using SMTP + $headers = str_replace("\r\n.", "\r\n..", $headers); + $body = str_replace("\r\n.", "\r\n..", $body); + } + + if ($this->_invoker->mail($to, $subject, $body, $headers, + sprintf($this->_extraParams, $reversePath))) + { + if ($evt) { + $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS); + $evt->setFailedRecipients($failedRecipients); + $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + } else { + $failedRecipients = array_merge( + $failedRecipients, + array_keys((array) $message->getTo()), + array_keys((array) $message->getCc()), + array_keys((array) $message->getBcc()) + ); + + if ($evt) { + $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED); + $evt->setFailedRecipients($failedRecipients); + $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + $message->generateId(); + + $count = 0; + } + + return $count; + } + + /** + * Register a plugin. + * + * @param Swift_Events_EventListener $plugin + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + $this->_eventDispatcher->bindEventListener($plugin); + } + + // -- Private methods + + /** Determine the best-use reverse path for this message */ + private function _getReversePath(Swift_Mime_Message $message) + { + $return = $message->getReturnPath(); + $sender = $message->getSender(); + $from = $message->getFrom(); + $path = null; + if (!empty($return)) { + $path = $return; + } elseif (!empty($sender)) { + $keys = array_keys($sender); + $path = array_shift($keys); + } elseif (!empty($from)) { + $keys = array_keys($from); + $path = array_shift($keys); + } + + return $path; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/NullTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/NullTransport.php new file mode 100644 index 0000000..ce136d3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/NullTransport.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Pretends messages have been sent, but just ignores them. + * + * @package Swift + * @author Fabien Potencier + */ +class Swift_Transport_NullTransport implements Swift_Transport +{ + /** The event dispatcher from the plugin API */ + private $_eventDispatcher; + + /** + * Constructor. + */ + public function __construct(Swift_Events_EventDispatcher $eventDispatcher) + { + $this->_eventDispatcher = $eventDispatcher; + } + + /** + * Tests if this Transport mechanism has started. + * + * @return boolean + */ + public function isStarted() + { + return true; + } + + /** + * Starts this Transport mechanism. + */ + public function start() + { + } + + /** + * Stops this Transport mechanism. + */ + public function stop() + { + } + + /** + * Sends the given message. + * + * @param Swift_Mime_Message $message + * @param string[] $failedRecipients An array of failures by-reference + * + * @return integer The number of sent emails + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); + if ($evt->bubbleCancelled()) { + return 0; + } + } + + if ($evt) { + $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS); + $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + return 0; + } + + /** + * Register a plugin. + * + * @param Swift_Events_EventListener $plugin + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + $this->_eventDispatcher->bindEventListener($plugin); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SendmailTransport.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SendmailTransport.php new file mode 100644 index 0000000..95c2e4a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SendmailTransport.php @@ -0,0 +1,163 @@ + 30, + 'blocking' => 1, + 'command' => '/usr/sbin/sendmail -bs', + 'type' => Swift_Transport_IoBuffer::TYPE_PROCESS + ); + + /** + * Create a new SendmailTransport with $buf for I/O. + * + * @param Swift_Transport_IoBuffer $buf + * @param Swift_Events_EventDispatcher $dispatcher + */ + public function __construct(Swift_Transport_IoBuffer $buf, Swift_Events_EventDispatcher $dispatcher) + { + parent::__construct($buf, $dispatcher); + } + + /** + * Start the standalone SMTP session if running in -bs mode. + */ + public function start() + { + if (false !== strpos($this->getCommand(), ' -bs')) { + parent::start(); + } + } + + /** + * Set the command to invoke. + * + * If using -t mode you are strongly advised to include -oi or -i in the flags. + * For example: /usr/sbin/sendmail -oi -t + * Swift will append a -f flag if one is not present. + * + * The recommended mode is "-bs" since it is interactive and failure notifications + * are hence possible. + * + * @param string $command + * + * @return Swift_Transport_SendmailTransport + */ + public function setCommand($command) + { + $this->_params['command'] = $command; + + return $this; + } + + /** + * Get the sendmail command which will be invoked. + * + * @return string + */ + public function getCommand() + { + return $this->_params['command']; + } + + /** + * Send the given Message. + * + * Recipient/sender data will be retrieved from the Message API. + * + * The return value is the number of recipients who were accepted for delivery. + * NOTE: If using 'sendmail -t' you will not be aware of any failures until + * they bounce (i.e. send() will always return 100% success). + * + * @param Swift_Mime_Message $message + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $failedRecipients = (array) $failedRecipients; + $command = $this->getCommand(); + $buffer = $this->getBuffer(); + + if (false !== strpos($command, ' -t')) { + if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); + if ($evt->bubbleCancelled()) { + return 0; + } + } + + if (false === strpos($command, ' -f')) { + $command .= ' -f' . $this->_getReversePath($message); + } + + $buffer->initialize(array_merge($this->_params, array('command' => $command))); + + if (false === strpos($command, ' -i') && false === strpos($command, ' -oi')) { + $buffer->setWriteTranslations(array("\r\n" => "\n", "\n." => "\n..")); + } else { + $buffer->setWriteTranslations(array("\r\n"=>"\n")); + } + + $count = count((array) $message->getTo()) + + count((array) $message->getCc()) + + count((array) $message->getBcc()) + ; + $message->toByteStream($buffer); + $buffer->flushBuffers(); + $buffer->setWriteTranslations(array()); + $buffer->terminate(); + + if ($evt) { + $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS); + $evt->setFailedRecipients($failedRecipients); + $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + $message->generateId(); + } elseif (false !== strpos($command, ' -bs')) { + $count = parent::send($message, $failedRecipients); + } else { + $this->_throwException(new Swift_TransportException( + 'Unsupported sendmail command flags [' . $command . ']. ' . + 'Must be one of "-bs" or "-t" but can include additional flags.' + )); + } + + return $count; + } + + // -- Protected methods + + /** Get the params to initialize the buffer */ + protected function _getBufferParams() + { + return $this->_params; + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php new file mode 100644 index 0000000..91157e5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Stores Messages in a queue. + * + * @package Swift + * @author Fabien Potencier + */ +class Swift_Transport_SpoolTransport implements Swift_Transport +{ + /** The spool instance */ + private $_spool; + + /** The event dispatcher from the plugin API */ + private $_eventDispatcher; + + /** + * Constructor. + */ + public function __construct(Swift_Events_EventDispatcher $eventDispatcher, Swift_Spool $spool = null) + { + $this->_eventDispatcher = $eventDispatcher; + $this->_spool = $spool; + } + + /** + * Sets the spool object. + * + * @param Swift_Spool $spool + * + * @return Swift_Transport_SpoolTransport + */ + public function setSpool(Swift_Spool $spool) + { + $this->_spool = $spool; + + return $this; + } + + /** + * Get the spool object. + * + * @return Swift_Spool + */ + public function getSpool() + { + return $this->_spool; + } + + /** + * Tests if this Transport mechanism has started. + * + * @return boolean + */ + public function isStarted() + { + return true; + } + + /** + * Starts this Transport mechanism. + */ + public function start() + { + } + + /** + * Stops this Transport mechanism. + */ + public function stop() + { + } + + /** + * Sends the given message. + * + * @param Swift_Mime_Message $message + * @param string[] $failedRecipients An array of failures by-reference + * + * @return integer The number of sent e-mail's + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); + if ($evt->bubbleCancelled()) { + return 0; + } + } + + $success = $this->_spool->queueMessage($message); + + if ($evt) { + $evt->setResult($success ? Swift_Events_SendEvent::RESULT_SUCCESS : Swift_Events_SendEvent::RESULT_FAILED); + $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + return 1; + } + + /** + * Register a plugin. + * + * @param Swift_Events_EventListener $plugin + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + $this->_eventDispatcher->bindEventListener($plugin); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php new file mode 100644 index 0000000..c42ee00 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php @@ -0,0 +1,315 @@ +_replacementFactory = $replacementFactory; + } + + /** + * Perform any initialization needed, using the given $params. + * + * Parameters will vary depending upon the type of IoBuffer used. + * + * @param array $params + */ + public function initialize(array $params) + { + $this->_params = $params; + switch ($params['type']) { + case self::TYPE_PROCESS: + $this->_establishProcessConnection(); + break; + case self::TYPE_SOCKET: + default: + $this->_establishSocketConnection(); + break; + } + } + + /** + * Set an individual param on the buffer (e.g. switching to SSL). + * + * @param string $param + * @param mixed $value + */ + public function setParam($param, $value) + { + if (isset($this->_stream)) { + switch ($param) { + case 'timeout': + if ($this->_stream) { + stream_set_timeout($this->_stream, $value); + } + break; + + case 'blocking': + if ($this->_stream) { + stream_set_blocking($this->_stream, 1); + } + + } + } + $this->_params[$param] = $value; + } + + public function startTLS() + { + return stream_socket_enable_crypto($this->_stream, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); + } + + /** + * Perform any shutdown logic needed. + */ + public function terminate() + { + if (isset($this->_stream)) { + switch ($this->_params['type']) { + case self::TYPE_PROCESS: + fclose($this->_in); + fclose($this->_out); + proc_close($this->_stream); + break; + case self::TYPE_SOCKET: + default: + fclose($this->_stream); + break; + } + } + $this->_stream = null; + $this->_out = null; + $this->_in = null; + } + + /** + * Set an array of string replacements which should be made on data written + * to the buffer. + * + * This could replace LF with CRLF for example. + * + * @param string[] $replacements + */ + public function setWriteTranslations(array $replacements) + { + foreach ($this->_translations as $search => $replace) { + if (!isset($replacements[$search])) { + $this->removeFilter($search); + unset($this->_translations[$search]); + } + } + + foreach ($replacements as $search => $replace) { + if (!isset($this->_translations[$search])) { + $this->addFilter( + $this->_replacementFactory->createFilter($search, $replace), $search + ); + $this->_translations[$search] = true; + } + } + } + + /** + * Get a line of output (including any CRLF). + * + * The $sequence number comes from any writes and may or may not be used + * depending upon the implementation. + * + * @param integer $sequence of last write to scan from + * + * @return string + * + * @throws Swift_IoException + */ + public function readLine($sequence) + { + if (isset($this->_out) && !feof($this->_out)) { + $line = fgets($this->_out); + if (strlen($line)==0) { + $metas = stream_get_meta_data($this->_out); + if ($metas['timed_out']) { + throw new Swift_IoException( + 'Connection to ' . + $this->_getReadConnectionDescription() . + ' Timed Out' + ); + } + } + + return $line; + } + } + + /** + * 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 integer $length + * + * @return string|boolean + * + * @throws Swift_IoException + */ + public function read($length) + { + if (isset($this->_out) && !feof($this->_out)) { + $ret = fread($this->_out, $length); + if (strlen($ret)==0) { + $metas = stream_get_meta_data($this->_out); + if ($metas['timed_out']) { + throw new Swift_IoException( + 'Connection to ' . + $this->_getReadConnectionDescription() . + ' Timed Out' + ); + } + } + + return $ret; + } + } + + /** Not implemented */ + public function setReadPointer($byteOffset) + { + } + + // -- Protected methods + + /** Flush the stream contents */ + protected function _flush() + { + if (isset($this->_in)) { + fflush($this->_in); + } + } + + /** Write this bytes to the stream */ + protected function _commit($bytes) + { + if (isset($this->_in) + && fwrite($this->_in, $bytes)) + { + return ++$this->_sequence; + } + } + + // -- Private methods + + /** + * Establishes a connection to a remote server. + */ + private function _establishSocketConnection() + { + $host = $this->_params['host']; + if (!empty($this->_params['protocol'])) { + $host = $this->_params['protocol'] . '://' . $host; + } + $timeout = 15; + if (!empty($this->_params['timeout'])) { + $timeout = $this->_params['timeout']; + } + $options = array(); + if (!empty($this->_params['sourceIp'])) { + $options['socket']['bindto']=$this->_params['sourceIp'].':0'; + } + $this->_stream = @stream_socket_client($host.':'.$this->_params['port'], $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, stream_context_create($options)); + if (false === $this->_stream) { + throw new Swift_TransportException( + 'Connection could not be established with host ' . $this->_params['host'] . + ' [' . $errstr . ' #' . $errno . ']' + ); + } + if (!empty($this->_params['blocking'])) { + stream_set_blocking($this->_stream, 1); + } else { + stream_set_blocking($this->_stream, 0); + } + stream_set_timeout($this->_stream, $timeout); + $this->_in =& $this->_stream; + $this->_out =& $this->_stream; + } + + /** + * Opens a process for input/output. + */ + private function _establishProcessConnection() + { + $command = $this->_params['command']; + $descriptorSpec = array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w') + ); + $this->_stream = proc_open($command, $descriptorSpec, $pipes); + stream_set_blocking($pipes[2], 0); + if ($err = stream_get_contents($pipes[2])) { + throw new Swift_TransportException( + 'Process could not be started [' . $err . ']' + ); + } + $this->_in =& $pipes[0]; + $this->_out =& $pipes[1]; + } + + private function _getReadConnectionDescription() + { + switch ($this->_params['type']) { + case self::TYPE_PROCESS: + return 'Process '.$this->_params['command']; + break; + + case self::TYPE_SOCKET: + default: + $host = $this->_params['host']; + if (!empty($this->_params['protocol'])) { + $host = $this->_params['protocol'] . '://' . $host; + } + $host.=':'.$this->_params['port']; + + return $host; + break; + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/classes/Swift/TransportException.php b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/TransportException.php new file mode 100644 index 0000000..48e41d1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/classes/Swift/TransportException.php @@ -0,0 +1,29 @@ + + */ +class Swift_Validate +{ + /** + * Grammar Object + * + * @var Swift_Mime_Grammar + */ + private static $grammar = null; + + /** + * Checks if an e-mail address matches the current grammars. + * + * @param string $email + * + * @return boolean + */ + public static function email($email) + { + if (self::$grammar===null) { + self::$grammar = Swift_DependencyContainer::getInstance() + ->lookup('mime.grammar'); + } + + return preg_match( + '/^' . self::$grammar->getDefinition('addr-spec') . '$/D', + $email + ); + } +} diff --git a/vendor/swiftmailer/swiftmailer/lib/dependency_maps/cache_deps.php b/vendor/swiftmailer/swiftmailer/lib/dependency_maps/cache_deps.php new file mode 100644 index 0000000..6023448 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/dependency_maps/cache_deps.php @@ -0,0 +1,23 @@ +register('cache') + ->asAliasOf('cache.array') + + ->register('tempdir') + ->asValue('/tmp') + + ->register('cache.null') + ->asSharedInstanceOf('Swift_KeyCache_NullKeyCache') + + ->register('cache.array') + ->asSharedInstanceOf('Swift_KeyCache_ArrayKeyCache') + ->withDependencies(array('cache.inputstream')) + + ->register('cache.disk') + ->asSharedInstanceOf('Swift_KeyCache_DiskKeyCache') + ->withDependencies(array('cache.inputstream', 'tempdir')) + + ->register('cache.inputstream') + ->asNewInstanceOf('Swift_KeyCache_SimpleKeyCacheInputStream') +; diff --git a/vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php b/vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php new file mode 100644 index 0000000..64d69d2 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php @@ -0,0 +1,9 @@ +register('message.message') + ->asNewInstanceOf('Swift_Message') + + ->register('message.mimepart') + ->asNewInstanceOf('Swift_MimePart') +; diff --git a/vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php b/vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php new file mode 100644 index 0000000..a13472e --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php @@ -0,0 +1,123 @@ +register('properties.charset') + ->asValue('utf-8') + + ->register('mime.grammar') + ->asSharedInstanceOf('Swift_Mime_Grammar') + + ->register('mime.message') + ->asNewInstanceOf('Swift_Mime_SimpleMessage') + ->withDependencies(array( + 'mime.headerset', + 'mime.qpcontentencoder', + 'cache', + 'mime.grammar', + 'properties.charset' + )) + + ->register('mime.part') + ->asNewInstanceOf('Swift_Mime_MimePart') + ->withDependencies(array( + 'mime.headerset', + 'mime.qpcontentencoder', + 'cache', + 'mime.grammar', + 'properties.charset' + )) + + ->register('mime.attachment') + ->asNewInstanceOf('Swift_Mime_Attachment') + ->withDependencies(array( + 'mime.headerset', + 'mime.base64contentencoder', + 'cache', + 'mime.grammar' + )) + ->addConstructorValue($swift_mime_types) + + ->register('mime.embeddedfile') + ->asNewInstanceOf('Swift_Mime_EmbeddedFile') + ->withDependencies(array( + 'mime.headerset', + 'mime.base64contentencoder', + 'cache', + 'mime.grammar' + )) + ->addConstructorValue($swift_mime_types) + + ->register('mime.headerfactory') + ->asNewInstanceOf('Swift_Mime_SimpleHeaderFactory') + ->withDependencies(array( + 'mime.qpheaderencoder', + 'mime.rfc2231encoder', + 'mime.grammar', + 'properties.charset' + )) + + ->register('mime.headerset') + ->asNewInstanceOf('Swift_Mime_SimpleHeaderSet') + ->withDependencies(array('mime.headerfactory', 'properties.charset')) + + ->register('mime.qpheaderencoder') + ->asNewInstanceOf('Swift_Mime_HeaderEncoder_QpHeaderEncoder') + ->withDependencies(array('mime.charstream')) + + ->register('mime.base64headerencoder') + ->asNewInstanceOf('Swift_Mime_HeaderEncoder_Base64HeaderEncoder') + ->withDependencies(array('mime.charstream')) + + ->register('mime.charstream') + ->asNewInstanceOf('Swift_CharacterStream_NgCharacterStream') + ->withDependencies(array('mime.characterreaderfactory', 'properties.charset')) + + ->register('mime.bytecanonicalizer') + ->asSharedInstanceOf('Swift_StreamFilters_ByteArrayReplacementFilter') + ->addConstructorValue(array(array(0x0D, 0x0A), array(0x0D), array(0x0A))) + ->addConstructorValue(array(array(0x0A), array(0x0A), array(0x0D, 0x0A))) + + ->register('mime.characterreaderfactory') + ->asSharedInstanceOf('Swift_CharacterReaderFactory_SimpleCharacterReaderFactory') + + ->register('mime.safeqpcontentencoder') + ->asNewInstanceOf('Swift_Mime_ContentEncoder_QpContentEncoder') + ->withDependencies(array('mime.charstream', 'mime.bytecanonicalizer')) + + ->register('mime.rawcontentencoder') + ->asNewInstanceOf('Swift_Mime_ContentEncoder_RawContentEncoder') + + ->register('mime.nativeqpcontentencoder') + ->withDependencies(array('properties.charset')) + ->asNewInstanceOf('Swift_Mime_ContentEncoder_NativeQpContentEncoder') + + ->register('mime.qpcontentencoderproxy') + ->asNewInstanceOf('Swift_Mime_ContentEncoder_QpContentEncoderProxy') + ->withDependencies(array('mime.safeqpcontentencoder', 'mime.nativeqpcontentencoder', 'properties.charset')) + + ->register('mime.7bitcontentencoder') + ->asNewInstanceOf('Swift_Mime_ContentEncoder_PlainContentEncoder') + ->addConstructorValue('7bit') + ->addConstructorValue(true) + + ->register('mime.8bitcontentencoder') + ->asNewInstanceOf('Swift_Mime_ContentEncoder_PlainContentEncoder') + ->addConstructorValue('8bit') + ->addConstructorValue(true) + + ->register('mime.base64contentencoder') + ->asSharedInstanceOf('Swift_Mime_ContentEncoder_Base64ContentEncoder') + + ->register('mime.rfc2231encoder') + ->asNewInstanceOf('Swift_Encoder_Rfc2231Encoder') + ->withDependencies(array('mime.charstream')) + + // As of PHP 5.4.7, the quoted_printable_encode() function behaves correctly. + // see https://github.com/php/php-src/commit/18bb426587d62f93c54c40bf8535eb8416603629 + ->register('mime.qpcontentencoder') + ->asAliasOf(version_compare(phpversion(), '5.4.7', '>=') ? 'mime.qpcontentencoderproxy' : 'mime.safeqpcontentencoder') +; + +unset($swift_mime_types); diff --git a/vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php b/vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php new file mode 100644 index 0000000..f5605a0 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php @@ -0,0 +1,68 @@ +register('transport.smtp') + ->asNewInstanceOf('Swift_Transport_EsmtpTransport') + ->withDependencies(array( + 'transport.buffer', + array('transport.authhandler'), + 'transport.eventdispatcher' + )) + + ->register('transport.sendmail') + ->asNewInstanceOf('Swift_Transport_SendmailTransport') + ->withDependencies(array( + 'transport.buffer', + 'transport.eventdispatcher' + )) + + ->register('transport.mail') + ->asNewInstanceOf('Swift_Transport_MailTransport') + ->withDependencies(array('transport.mailinvoker', 'transport.eventdispatcher')) + + ->register('transport.loadbalanced') + ->asNewInstanceOf('Swift_Transport_LoadBalancedTransport') + + ->register('transport.failover') + ->asNewInstanceOf('Swift_Transport_FailoverTransport') + + ->register('transport.spool') + ->asNewInstanceOf('Swift_Transport_SpoolTransport') + ->withDependencies(array('transport.eventdispatcher')) + + ->register('transport.null') + ->asNewInstanceOf('Swift_Transport_NullTransport') + ->withDependencies(array('transport.eventdispatcher')) + + ->register('transport.mailinvoker') + ->asSharedInstanceOf('Swift_Transport_SimpleMailInvoker') + + ->register('transport.buffer') + ->asNewInstanceOf('Swift_Transport_StreamBuffer') + ->withDependencies(array('transport.replacementfactory')) + + ->register('transport.authhandler') + ->asNewInstanceOf('Swift_Transport_Esmtp_AuthHandler') + ->withDependencies(array( + array( + 'transport.crammd5auth', + 'transport.loginauth', + 'transport.plainauth' + ) + )) + + ->register('transport.crammd5auth') + ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_CramMd5Authenticator') + + ->register('transport.loginauth') + ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_LoginAuthenticator') + + ->register('transport.plainauth') + ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_PlainAuthenticator') + + ->register('transport.eventdispatcher') + ->asNewInstanceOf('Swift_Events_SimpleEventDispatcher') + + ->register('transport.replacementfactory') + ->asSharedInstanceOf('Swift_StreamFilters_StringReplacementFilterFactory') +; diff --git a/vendor/swiftmailer/swiftmailer/lib/mime_types.php b/vendor/swiftmailer/swiftmailer/lib/mime_types.php new file mode 100644 index 0000000..3baebb2 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/mime_types.php @@ -0,0 +1,76 @@ + 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'avi' => 'video/avi', + 'bmp' => 'image/bmp', + 'bz2' => 'application/x-bz2', + 'csv' => 'text/csv', + 'dmg' => 'application/x-apple-diskimage', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'eml' => 'message/rfc822', + 'aps' => 'application/postscript', + 'exe' => 'application/x-ms-dos-executable', + 'flv' => 'video/x-flv', + 'gif' => 'image/gif', + 'gz' => 'application/x-gzip', + 'hqx' => 'application/stuffit', + 'htm' => 'text/html', + 'html' => 'text/html', + 'jar' => 'application/x-java-archive', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'm3u' => 'audio/x-mpegurl', + 'm4a' => 'audio/mp4', + 'mdb' => 'application/x-msaccess', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mov' => 'video/quicktime', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'odg' => 'vnd.oasis.opendocument.graphics', + 'odp' => 'vnd.oasis.opendocument.presentation', + 'odt' => 'vnd.oasis.opendocument.text', + 'ods' => 'vnd.oasis.opendocument.spreadsheet', + 'ogg' => 'audio/ogg', + 'pdf' => 'application/pdf', + 'png' => 'image/png', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'ps' => 'application/postscript', + 'rar' => 'application/x-rar-compressed', + 'rtf' => 'application/rtf', + 'tar' => 'application/x-tar', + 'sit' => 'application/x-stuffit', + 'svg' => 'image/svg+xml', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'ttf' => 'application/x-font-truetype', + 'txt' => 'text/plain', + 'vcf' => 'text/x-vcard', + 'wav' => 'audio/wav', + 'wma' => 'audio/x-ms-wma', + 'wmv' => 'audio/x-ms-wmv', + 'xls' => 'application/excel', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xml' => 'application/xml', + 'zip' => 'application/zip' +); diff --git a/vendor/swiftmailer/swiftmailer/lib/preferences.php b/vendor/swiftmailer/swiftmailer/lib/preferences.php new file mode 100644 index 0000000..4223943 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/preferences.php @@ -0,0 +1,35 @@ +setCharset('utf-8'); + +// Without these lines the default caching mechanism is "array" but this uses a lot of memory. +// If possible, use a disk cache to enable attaching large attachments etc. +// You can override the default temporary directory by setting the TMPDIR environment variable. + +// The @ operator in front of is_writable calls is to avoid PHP warnings +// when using open_basedir +$tmp = getenv('TMPDIR'); +if ($tmp && @is_writable($tmp)) { + $preferences + ->setTempDir($tmp) + ->setCacheType('disk'); +} elseif (function_exists('sys_get_temp_dir') && @is_writable(sys_get_temp_dir())) { + $preferences + ->setTempDir(sys_get_temp_dir()) + ->setCacheType('disk'); +} + +// this should only be done when Swiftmailer won't use the native QP content encoder +// see mime_deps.php +if (version_compare(phpversion(), '5.4.7', '<')) { + $preferences->setQPDotEscape(false); +} diff --git a/vendor/swiftmailer/swiftmailer/lib/swift_init.php b/vendor/swiftmailer/swiftmailer/lib/swift_init.php new file mode 100644 index 0000000..5897e6f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/lib/swift_init.php @@ -0,0 +1,28 @@ + + + Some test message + chris@w3style.co.uk + mark@swiftmailer.org + chris.corbyn@sitepoint.com + text/plain + + Here's a recipe for beef stifado + + + text/html + + This is the other part + + + + application/pdf + stifado.pdf + /path/to/stifado.pdf + + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc0821.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc0821.txt new file mode 100644 index 0000000..d877b72 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc0821.txt @@ -0,0 +1,4050 @@ + + + + RFC 821 + + + + + + SIMPLE MAIL TRANSFER PROTOCOL + + + + Jonathan B. Postel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + August 1982 + + + + Information Sciences Institute + University of Southern California + 4676 Admiralty Way + Marina del Rey, California 90291 + + (213) 822-1511 + + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + TABLE OF CONTENTS + + 1. INTRODUCTION .................................................. 1 + + 2. THE SMTP MODEL ................................................ 2 + + 3. THE SMTP PROCEDURE ............................................ 4 + + 3.1. Mail ..................................................... 4 + 3.2. Forwarding ............................................... 7 + 3.3. Verifying and Expanding .................................. 8 + 3.4. Sending and Mailing ..................................... 11 + 3.5. Opening and Closing ..................................... 13 + 3.6. Relaying ................................................ 14 + 3.7. Domains ................................................. 17 + 3.8. Changing Roles .......................................... 18 + + 4. THE SMTP SPECIFICATIONS ...................................... 19 + + 4.1. SMTP Commands ........................................... 19 + 4.1.1. Command Semantics ..................................... 19 + 4.1.2. Command Syntax ........................................ 27 + 4.2. SMTP Replies ............................................ 34 + 4.2.1. Reply Codes by Function Group ......................... 35 + 4.2.2. Reply Codes in Numeric Order .......................... 36 + 4.3. Sequencing of Commands and Replies ...................... 37 + 4.4. State Diagrams .......................................... 39 + 4.5. Details ................................................. 41 + 4.5.1. Minimum Implementation ................................ 41 + 4.5.2. Transparency .......................................... 41 + 4.5.3. Sizes ................................................. 42 + + APPENDIX A: TCP ................................................. 44 + APPENDIX B: NCP ................................................. 45 + APPENDIX C: NITS ................................................ 46 + APPENDIX D: X.25 ................................................ 47 + APPENDIX E: Theory of Reply Codes ............................... 48 + APPENDIX F: Scenarios ........................................... 51 + + GLOSSARY ......................................................... 64 + + REFERENCES ....................................................... 67 + + + + +Network Working Group J. Postel +Request for Comments: DRAFT ISI +Replaces: RFC 788, 780, 772 August 1982 + + SIMPLE MAIL TRANSFER PROTOCOL + + +1. INTRODUCTION + + The objective of Simple Mail Transfer Protocol (SMTP) is to transfer + mail reliably and efficiently. + + SMTP is independent of the particular transmission subsystem and + requires only a reliable ordered data stream channel. Appendices A, + B, C, and D describe the use of SMTP with various transport services. + A Glossary provides the definitions of terms as used in this + document. + + An important feature of SMTP is its capability to relay mail across + transport service environments. A transport service provides an + interprocess communication environment (IPCE). An IPCE may cover one + network, several networks, or a subset of a network. It is important + to realize that transport systems (or IPCEs) are not one-to-one with + networks. A process can communicate directly with another process + through any mutually known IPCE. Mail is an application or use of + interprocess communication. Mail can be communicated between + processes in different IPCEs by relaying through a process connected + to two (or more) IPCEs. More specifically, mail can be relayed + between hosts on different transport systems by a host on both + transport systems. + + + + + + + + + + + + + + + + + + + + + + + + +Postel [Page 1] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + +2. THE SMTP MODEL + + The SMTP design is based on the following model of communication: as + the result of a user mail request, the sender-SMTP establishes a + two-way transmission channel to a receiver-SMTP. The receiver-SMTP + may be either the ultimate destination or an intermediate. SMTP + commands are generated by the sender-SMTP and sent to the + receiver-SMTP. SMTP replies are sent from the receiver-SMTP to the + sender-SMTP in response to the commands. + + Once the transmission channel is established, the SMTP-sender sends a + MAIL command indicating the sender of the mail. If the SMTP-receiver + can accept mail it responds with an OK reply. The SMTP-sender then + sends a RCPT command identifying a recipient of the mail. If the + SMTP-receiver can accept mail for that recipient it responds with an + OK reply; if not, it responds with a reply rejecting that recipient + (but not the whole mail transaction). The SMTP-sender and + SMTP-receiver may negotiate several recipients. When the recipients + have been negotiated the SMTP-sender sends the mail data, terminating + with a special sequence. If the SMTP-receiver successfully processes + the mail data it responds with an OK reply. The dialog is purposely + lock-step, one-at-a-time. + + ------------------------------------------------------------- + + + +----------+ +----------+ + +------+ | | | | + | User |<-->| | SMTP | | + +------+ | Sender- |Commands/Replies| Receiver-| + +------+ | SMTP |<-------------->| SMTP | +------+ + | File |<-->| | and Mail | |<-->| File | + |System| | | | | |System| + +------+ +----------+ +----------+ +------+ + + + Sender-SMTP Receiver-SMTP + + Model for SMTP Use + + Figure 1 + + ------------------------------------------------------------- + + The SMTP provides mechanisms for the transmission of mail; directly + from the sending user's host to the receiving user's host when the + + + +[Page 2] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + two host are connected to the same transport service, or via one or + more relay SMTP-servers when the source and destination hosts are not + connected to the same transport service. + + To be able to provide the relay capability the SMTP-server must be + supplied with the name of the ultimate destination host as well as + the destination mailbox name. + + The argument to the MAIL command is a reverse-path, which specifies + who the mail is from. The argument to the RCPT command is a + forward-path, which specifies who the mail is to. The forward-path + is a source route, while the reverse-path is a return route (which + may be used to return a message to the sender when an error occurs + with a relayed message). + + When the same message is sent to multiple recipients the SMTP + encourages the transmission of only one copy of the data for all the + recipients at the same destination host. + + The mail commands and replies have a rigid syntax. Replies also have + a numeric code. In the following, examples appear which use actual + commands and replies. The complete lists of commands and replies + appears in Section 4 on specifications. + + Commands and replies are not case sensitive. That is, a command or + reply word may be upper case, lower case, or any mixture of upper and + lower case. Note that this is not true of mailbox user names. For + some hosts the user name is case sensitive, and SMTP implementations + must take case to preserve the case of user names as they appear in + mailbox arguments. Host names are not case sensitive. + + Commands and replies are composed of characters from the ASCII + character set [1]. When the transport service provides an 8-bit byte + (octet) transmission channel, each 7-bit character is transmitted + right justified in an octet with the high order bit cleared to zero. + + When specifying the general form of a command or reply, an argument + (or special symbol) will be denoted by a meta-linguistic variable (or + constant), for example, "" or "". Here the + angle brackets indicate these are meta-linguistic variables. + However, some arguments use the angle brackets literally. For + example, an actual reverse-path is enclosed in angle brackets, i.e., + "" is an instance of (the + angle brackets are actually transmitted in the command or reply). + + + + + +Postel [Page 3] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + +3. THE SMTP PROCEDURES + + This section presents the procedures used in SMTP in several parts. + First comes the basic mail procedure defined as a mail transaction. + Following this are descriptions of forwarding mail, verifying mailbox + names and expanding mailing lists, sending to terminals instead of or + in combination with mailboxes, and the opening and closing exchanges. + At the end of this section are comments on relaying, a note on mail + domains, and a discussion of changing roles. Throughout this section + are examples of partial command and reply sequences, several complete + scenarios are presented in Appendix F. + + 3.1. MAIL + + There are three steps to SMTP mail transactions. The transaction + is started with a MAIL command which gives the sender + identification. A series of one or more RCPT commands follows + giving the receiver information. Then a DATA command gives the + mail data. And finally, the end of mail data indicator confirms + the transaction. + + The first step in the procedure is the MAIL command. The + contains the source mailbox. + + MAIL FROM: + + This command tells the SMTP-receiver that a new mail + transaction is starting and to reset all its state tables and + buffers, including any recipients or mail data. It gives the + reverse-path which can be used to report errors. If accepted, + the receiver-SMTP returns a 250 OK reply. + + The can contain more than just a mailbox. The + is a reverse source routing list of hosts and + source mailbox. The first host in the should be + the host sending this command. + + The second step in the procedure is the RCPT command. + + RCPT TO: + + This command gives a forward-path identifying one recipient. + If accepted, the receiver-SMTP returns a 250 OK reply, and + stores the forward-path. If the recipient is unknown the + receiver-SMTP returns a 550 Failure reply. This second step of + the procedure can be repeated any number of times. + + + +[Page 4] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + The can contain more than just a mailbox. The + is a source routing list of hosts and the + destination mailbox. The first host in the + should be the host receiving this command. + + The third step in the procedure is the DATA command. + + DATA + + If accepted, the receiver-SMTP returns a 354 Intermediate reply + and considers all succeeding lines to be the message text. + When the end of text is received and stored the SMTP-receiver + sends a 250 OK reply. + + Since the mail data is sent on the transmission channel the end + of the mail data must be indicated so that the command and + reply dialog can be resumed. SMTP indicates the end of the + mail data by sending a line containing only a period. A + transparency procedure is used to prevent this from interfering + with the user's text (see Section 4.5.2). + + Please note that the mail data includes the memo header + items such as Date, Subject, To, Cc, From [2]. + + The end of mail data indicator also confirms the mail + transaction and tells the receiver-SMTP to now process the + stored recipients and mail data. If accepted, the + receiver-SMTP returns a 250 OK reply. The DATA command should + fail only if the mail transaction was incomplete (for example, + no recipients), or if resources are not available. + + The above procedure is an example of a mail transaction. These + commands must be used only in the order discussed above. + Example 1 (below) illustrates the use of these commands in a mail + transaction. + + + + + + + + + + + + + + +Postel [Page 5] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + ------------------------------------------------------------- + + Example of the SMTP Procedure + + This SMTP example shows mail sent by Smith at host Alpha.ARPA, + to Jones, Green, and Brown at host Beta.ARPA. Here we assume + that host Alpha contacts host Beta directly. + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO: + R: 250 OK + + S: RCPT TO: + R: 550 No such user here + + S: RCPT TO: + R: 250 OK + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + The mail has now been accepted for Jones and Brown. Green did + not have a mailbox at host Beta. + + Example 1 + + ------------------------------------------------------------- + + + + + + + + + + + + + + + + +[Page 6] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + 3.2. FORWARDING + + There are some cases where the destination information in the + is incorrect, but the receiver-SMTP knows the + correct destination. In such cases, one of the following replies + should be used to allow the sender to contact the correct + destination. + + 251 User not local; will forward to + + This reply indicates that the receiver-SMTP knows the user's + mailbox is on another host and indicates the correct + forward-path to use in the future. Note that either the + host or user or both may be different. The receiver takes + responsibility for delivering the message. + + 551 User not local; please try + + This reply indicates that the receiver-SMTP knows the user's + mailbox is on another host and indicates the correct + forward-path to use. Note that either the host or user or + both may be different. The receiver refuses to accept mail + for this user, and the sender must either redirect the mail + according to the information provided or return an error + response to the originating user. + + Example 2 illustrates the use of these responses. + + ------------------------------------------------------------- + + Example of Forwarding + + Either + + S: RCPT TO: + R: 251 User not local; will forward to + + Or + + S: RCPT TO: + R: 551 User not local; please try + + Example 2 + + ------------------------------------------------------------- + + + + +Postel [Page 7] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + 3.3. VERIFYING AND EXPANDING + + SMTP provides as additional features, commands to verify a user + name or expand a mailing list. This is done with the VRFY and + EXPN commands, which have character string arguments. For the + VRFY command, the string is a user name, and the response may + include the full name of the user and must include the mailbox of + the user. For the EXPN command, the string identifies a mailing + list, and the multiline response may include the full name of the + users and must give the mailboxes on the mailing list. + + "User name" is a fuzzy term and used purposely. If a host + implements the VRFY or EXPN commands then at least local mailboxes + must be recognized as "user names". If a host chooses to + recognize other strings as "user names" that is allowed. + + In some hosts the distinction between a mailing list and an alias + for a single mailbox is a bit fuzzy, since a common data structure + may hold both types of entries, and it is possible to have mailing + lists of one mailbox. If a request is made to verify a mailing + list a positive response can be given if on receipt of a message + so addressed it will be delivered to everyone on the list, + otherwise an error should be reported (e.g., "550 That is a + mailing list, not a user"). If a request is made to expand a user + name a positive response can be formed by returning a list + containing one name, or an error can be reported (e.g., "550 That + is a user name, not a mailing list"). + + In the case of a multiline reply (normal for EXPN) exactly one + mailbox is to be specified on each line of the reply. In the case + of an ambiguous request, for example, "VRFY Smith", where there + are two Smith's the response must be "553 User ambiguous". + + The case of verifying a user name is straightforward as shown in + example 3. + + + + + + + + + + + + + + +[Page 8] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + ------------------------------------------------------------- + + Example of Verifying a User Name + + Either + + S: VRFY Smith + R: 250 Fred Smith + + Or + + S: VRFY Smith + R: 251 User not local; will forward to + + Or + + S: VRFY Jones + R: 550 String does not match anything. + + Or + + S: VRFY Jones + R: 551 User not local; please try + + Or + + S: VRFY Gourzenkyinplatz + R: 553 User ambiguous. + + Example 3 + + ------------------------------------------------------------- + + + + + + + + + + + + + + + + + +Postel [Page 9] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + The case of expanding a mailbox list requires a multiline reply as + shown in example 4. + + ------------------------------------------------------------- + + Example of Expanding a Mailing List + + Either + + S: EXPN Example-People + R: 250-Jon Postel + R: 250-Fred Fonebone + R: 250-Sam Q. Smith + R: 250-Quincy Smith <@USC-ISIF.ARPA:Q-Smith@ISI-VAXA.ARPA> + R: 250- + R: 250 + + Or + + S: EXPN Executive-Washroom-List + R: 550 Access Denied to You. + + Example 4 + + ------------------------------------------------------------- + + The character string arguments of the VRFY and EXPN commands + cannot be further restricted due to the variety of implementations + of the user name and mailbox list concepts. On some systems it + may be appropriate for the argument of the EXPN command to be a + file name for a file containing a mailing list, but again there is + a variety of file naming conventions in the Internet. + + The VRFY and EXPN commands are not included in the minimum + implementation (Section 4.5.1), and are not required to work + across relays when they are implemented. + + + + + + + + + + + + + +[Page 10] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + 3.4. SENDING AND MAILING + + The main purpose of SMTP is to deliver messages to user's + mailboxes. A very similar service provided by some hosts is to + deliver messages to user's terminals (provided the user is active + on the host). The delivery to the user's mailbox is called + "mailing", the delivery to the user's terminal is called + "sending". Because in many hosts the implementation of sending is + nearly identical to the implementation of mailing these two + functions are combined in SMTP. However the sending commands are + not included in the required minimum implementation + (Section 4.5.1). Users should have the ability to control the + writing of messages on their terminals. Most hosts permit the + users to accept or refuse such messages. + + The following three command are defined to support the sending + options. These are used in the mail transaction instead of the + MAIL command and inform the receiver-SMTP of the special semantics + of this transaction: + + SEND FROM: + + The SEND command requires that the mail data be delivered to + the user's terminal. If the user is not active (or not + accepting terminal messages) on the host a 450 reply may + returned to a RCPT command. The mail transaction is + successful if the message is delivered the terminal. + + SOML FROM: + + The Send Or MaiL command requires that the mail data be + delivered to the user's terminal if the user is active (and + accepting terminal messages) on the host. If the user is + not active (or not accepting terminal messages) then the + mail data is entered into the user's mailbox. The mail + transaction is successful if the message is delivered either + to the terminal or the mailbox. + + SAML FROM: + + The Send And MaiL command requires that the mail data be + delivered to the user's terminal if the user is active (and + accepting terminal messages) on the host. In any case the + mail data is entered into the user's mailbox. The mail + transaction is successful if the message is delivered the + mailbox. + + + +Postel [Page 11] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + The same reply codes that are used for the MAIL commands are used + for these commands. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 12] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + 3.5. OPENING AND CLOSING + + At the time the transmission channel is opened there is an + exchange to ensure that the hosts are communicating with the hosts + they think they are. + + The following two commands are used in transmission channel + opening and closing: + + HELO + + QUIT + + In the HELO command the host sending the command identifies + itself; the command may be interpreted as saying "Hello, I am + ". + + ------------------------------------------------------------- + + Example of Connection Opening + + R: 220 BBN-UNIX.ARPA Simple Mail Transfer Service Ready + S: HELO USC-ISIF.ARPA + R: 250 BBN-UNIX.ARPA + + Example 5 + + ------------------------------------------------------------- + + ------------------------------------------------------------- + + Example of Connection Closing + + S: QUIT + R: 221 BBN-UNIX.ARPA Service closing transmission channel + + Example 6 + + ------------------------------------------------------------- + + + + + + + + + + +Postel [Page 13] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + 3.6. RELAYING + + The forward-path may be a source route of the form + "@ONE,@TWO:JOE@THREE", where ONE, TWO, and THREE are hosts. This + form is used to emphasize the distinction between an address and a + route. The mailbox is an absolute address, and the route is + information about how to get there. The two concepts should not + be confused. + + Conceptually the elements of the forward-path are moved to the + reverse-path as the message is relayed from one server-SMTP to + another. The reverse-path is a reverse source route, (i.e., a + source route from the current location of the message to the + originator of the message). When a server-SMTP deletes its + identifier from the forward-path and inserts it into the + reverse-path, it must use the name it is known by in the + environment it is sending into, not the environment the mail came + from, in case the server-SMTP is known by different names in + different environments. + + If when the message arrives at an SMTP the first element of the + forward-path is not the identifier of that SMTP the element is not + deleted from the forward-path and is used to determine the next + SMTP to send the message to. In any case, the SMTP adds its own + identifier to the reverse-path. + + Using source routing the receiver-SMTP receives mail to be relayed + to another server-SMTP The receiver-SMTP may accept or reject the + task of relaying the mail in the same way it accepts or rejects + mail for a local user. The receiver-SMTP transforms the command + arguments by moving its own identifier from the forward-path to + the beginning of the reverse-path. The receiver-SMTP then becomes + a sender-SMTP, establishes a transmission channel to the next SMTP + in the forward-path, and sends it the mail. + + The first host in the reverse-path should be the host sending the + SMTP commands, and the first host in the forward-path should be + the host receiving the SMTP commands. + + Notice that the forward-path and reverse-path appear in the SMTP + commands and replies, but not necessarily in the message. That + is, there is no need for these paths and especially this syntax to + appear in the "To:" , "From:", "CC:", etc. fields of the message + header. + + If a server-SMTP has accepted the task of relaying the mail and + + + +[Page 14] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + later finds that the forward-path is incorrect or that the mail + cannot be delivered for whatever reason, then it must construct an + "undeliverable mail" notification message and send it to the + originator of the undeliverable mail (as indicated by the + reverse-path). + + This notification message must be from the server-SMTP at this + host. Of course, server-SMTPs should not send notification + messages about problems with notification messages. One way to + prevent loops in error reporting is to specify a null reverse-path + in the MAIL command of a notification message. When such a + message is relayed it is permissible to leave the reverse-path + null. A MAIL command with a null reverse-path appears as follows: + + MAIL FROM:<> + + An undeliverable mail notification message is shown in example 7. + This notification is in response to a message originated by JOE at + HOSTW and sent via HOSTX to HOSTY with instructions to relay it on + to HOSTZ. What we see in the example is the transaction between + HOSTY and HOSTX, which is the first step in the return of the + notification message. + + + + + + + + + + + + + + + + + + + + + + + + + + + +Postel [Page 15] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + ------------------------------------------------------------- + + Example Undeliverable Mail Notification Message + + S: MAIL FROM:<> + R: 250 ok + S: RCPT TO:<@HOSTX.ARPA:JOE@HOSTW.ARPA> + R: 250 ok + S: DATA + R: 354 send the mail data, end with . + S: Date: 23 Oct 81 11:22:33 + S: From: SMTP@HOSTY.ARPA + S: To: JOE@HOSTW.ARPA + S: Subject: Mail System Problem + S: + S: Sorry JOE, your message to SAM@HOSTZ.ARPA lost. + S: HOSTZ.ARPA said this: + S: "550 No Such User" + S: . + R: 250 ok + + Example 7 + + ------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 16] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + 3.7. DOMAINS + + Domains are a recently introduced concept in the ARPA Internet + mail system. The use of domains changes the address space from a + flat global space of simple character string host names to a + hierarchically structured rooted tree of global addresses. The + host name is replaced by a domain and host designator which is a + sequence of domain element strings separated by periods with the + understanding that the domain elements are ordered from the most + specific to the most general. + + For example, "USC-ISIF.ARPA", "Fred.Cambridge.UK", and + "PC7.LCS.MIT.ARPA" might be host-and-domain identifiers. + + Whenever domain names are used in SMTP only the official names are + used, the use of nicknames or aliases is not allowed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Postel [Page 17] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + 3.8. CHANGING ROLES + + The TURN command may be used to reverse the roles of the two + programs communicating over the transmission channel. + + If program-A is currently the sender-SMTP and it sends the TURN + command and receives an ok reply (250) then program-A becomes the + receiver-SMTP. + + If program-B is currently the receiver-SMTP and it receives the + TURN command and sends an ok reply (250) then program-B becomes + the sender-SMTP. + + To refuse to change roles the receiver sends the 502 reply. + + Please note that this command is optional. It would not normally + be used in situations where the transmission channel is TCP. + However, when the cost of establishing the transmission channel is + high, this command may be quite useful. For example, this command + may be useful in supporting be mail exchange using the public + switched telephone system as a transmission channel, especially if + some hosts poll other hosts for mail exchanges. + + + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 18] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + +4. THE SMTP SPECIFICATIONS + + 4.1. SMTP COMMANDS + + 4.1.1. COMMAND SEMANTICS + + The SMTP commands define the mail transfer or the mail system + function requested by the user. SMTP commands are character + strings terminated by . The command codes themselves are + alphabetic characters terminated by if parameters follow + and otherwise. The syntax of mailboxes must conform to + receiver site conventions. The SMTP commands are discussed + below. The SMTP replies are discussed in the Section 4.2. + + A mail transaction involves several data objects which are + communicated as arguments to different commands. The + reverse-path is the argument of the MAIL command, the + forward-path is the argument of the RCPT command, and the mail + data is the argument of the DATA command. These arguments or + data objects must be transmitted and held pending the + confirmation communicated by the end of mail data indication + which finalizes the transaction. The model for this is that + distinct buffers are provided to hold the types of data + objects, that is, there is a reverse-path buffer, a + forward-path buffer, and a mail data buffer. Specific commands + cause information to be appended to a specific buffer, or cause + one or more buffers to be cleared. + + HELLO (HELO) + + This command is used to identify the sender-SMTP to the + receiver-SMTP. The argument field contains the host name of + the sender-SMTP. + + The receiver-SMTP identifies itself to the sender-SMTP in + the connection greeting reply, and in the response to this + command. + + This command and an OK reply to it confirm that both the + sender-SMTP and the receiver-SMTP are in the initial state, + that is, there is no transaction in progress and all state + tables and buffers are cleared. + + + + + + + +Postel [Page 19] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + MAIL (MAIL) + + This command is used to initiate a mail transaction in which + the mail data is delivered to one or more mailboxes. The + argument field contains a reverse-path. + + The reverse-path consists of an optional list of hosts and + the sender mailbox. When the list of hosts is present, it + is a "reverse" source route and indicates that the mail was + relayed through each host on the list (the first host in the + list was the most recent relay). This list is used as a + source route to return non-delivery notices to the sender. + As each relay host adds itself to the beginning of the list, + it must use its name as known in the IPCE to which it is + relaying the mail rather than the IPCE from which the mail + came (if they are different). In some types of error + reporting messages (for example, undeliverable mail + notifications) the reverse-path may be null (see Example 7). + + This command clears the reverse-path buffer, the + forward-path buffer, and the mail data buffer; and inserts + the reverse-path information from this command into the + reverse-path buffer. + + RECIPIENT (RCPT) + + This command is used to identify an individual recipient of + the mail data; multiple recipients are specified by multiple + use of this command. + + The forward-path consists of an optional list of hosts and a + required destination mailbox. When the list of hosts is + present, it is a source route and indicates that the mail + must be relayed to the next host on the list. If the + receiver-SMTP does not implement the relay function it may + user the same reply it would for an unknown local user + (550). + + When mail is relayed, the relay host must remove itself from + the beginning forward-path and put itself at the beginning + of the reverse-path. When mail reaches its ultimate + destination (the forward-path contains only a destination + mailbox), the receiver-SMTP inserts it into the destination + mailbox in accordance with its host mail conventions. + + + + + +[Page 20] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + For example, mail received at relay host A with arguments + + FROM: + TO:<@HOSTA.ARPA,@HOSTB.ARPA:USERC@HOSTD.ARPA> + + will be relayed on to host B with arguments + + FROM:<@HOSTA.ARPA:USERX@HOSTY.ARPA> + TO:<@HOSTB.ARPA:USERC@HOSTD.ARPA>. + + This command causes its forward-path argument to be appended + to the forward-path buffer. + + DATA (DATA) + + The receiver treats the lines following the command as mail + data from the sender. This command causes the mail data + from this command to be appended to the mail data buffer. + The mail data may contain any of the 128 ASCII character + codes. + + The mail data is terminated by a line containing only a + period, that is the character sequence "." (see + Section 4.5.2 on Transparency). This is the end of mail + data indication. + + The end of mail data indication requires that the receiver + must now process the stored mail transaction information. + This processing consumes the information in the reverse-path + buffer, the forward-path buffer, and the mail data buffer, + and on the completion of this command these buffers are + cleared. If the processing is successful the receiver must + send an OK reply. If the processing fails completely the + receiver must send a failure reply. + + When the receiver-SMTP accepts a message either for relaying + or for final delivery it inserts at the beginning of the + mail data a time stamp line. The time stamp line indicates + the identity of the host that sent the message, and the + identity of the host that received the message (and is + inserting this time stamp), and the date and time the + message was received. Relayed messages will have multiple + time stamp lines. + + When the receiver-SMTP makes the "final delivery" of a + message it inserts at the beginning of the mail data a + + + +Postel [Page 21] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + return path line. The return path line preserves the + information in the from the MAIL command. + Here, final delivery means the message leaves the SMTP + world. Normally, this would mean it has been delivered to + the destination user, but in some cases it may be further + processed and transmitted by another mail system. + + It is possible for the mailbox in the return path be + different from the actual sender's mailbox, for example, + if error responses are to be delivered a special error + handling mailbox rather than the message senders. + + The preceding two paragraphs imply that the final mail data + will begin with a return path line, followed by one or more + time stamp lines. These lines will be followed by the mail + data header and body [2]. See Example 8. + + Special mention is needed of the response and further action + required when the processing following the end of mail data + indication is partially successful. This could arise if + after accepting several recipients and the mail data, the + receiver-SMTP finds that the mail data can be successfully + delivered to some of the recipients, but it cannot be to + others (for example, due to mailbox space allocation + problems). In such a situation, the response to the DATA + command must be an OK reply. But, the receiver-SMTP must + compose and send an "undeliverable mail" notification + message to the originator of the message. Either a single + notification which lists all of the recipients that failed + to get the message, or separate notification messages must + be sent for each failed recipient (see Example 7). All + undeliverable mail notification messages are sent using the + MAIL command (even if they result from processing a SEND, + SOML, or SAML command). + + + + + + + + + + + + + + + +[Page 22] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + ------------------------------------------------------------- + + Example of Return Path and Received Time Stamps + + Return-Path: <@GHI.ARPA,@DEF.ARPA,@ABC.ARPA:JOE@ABC.ARPA> + Received: from GHI.ARPA by JKL.ARPA ; 27 Oct 81 15:27:39 PST + Received: from DEF.ARPA by GHI.ARPA ; 27 Oct 81 15:15:13 PST + Received: from ABC.ARPA by DEF.ARPA ; 27 Oct 81 15:01:59 PST + Date: 27 Oct 81 15:01:01 PST + From: JOE@ABC.ARPA + Subject: Improved Mailing System Installed + To: SAM@JKL.ARPA + + This is to inform you that ... + + Example 8 + + ------------------------------------------------------------- + + SEND (SEND) + + This command is used to initiate a mail transaction in which + the mail data is delivered to one or more terminals. The + argument field contains a reverse-path. This command is + successful if the message is delivered to a terminal. + + The reverse-path consists of an optional list of hosts and + the sender mailbox. When the list of hosts is present, it + is a "reverse" source route and indicates that the mail was + relayed through each host on the list (the first host in the + list was the most recent relay). This list is used as a + source route to return non-delivery notices to the sender. + As each relay host adds itself to the beginning of the list, + it must use its name as known in the IPCE to which it is + relaying the mail rather than the IPCE from which the mail + came (if they are different). + + This command clears the reverse-path buffer, the + forward-path buffer, and the mail data buffer; and inserts + the reverse-path information from this command into the + reverse-path buffer. + + SEND OR MAIL (SOML) + + This command is used to initiate a mail transaction in which + the mail data is delivered to one or more terminals or + + + +Postel [Page 23] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + mailboxes. For each recipient the mail data is delivered to + the recipient's terminal if the recipient is active on the + host (and accepting terminal messages), otherwise to the + recipient's mailbox. The argument field contains a + reverse-path. This command is successful if the message is + delivered to a terminal or the mailbox. + + The reverse-path consists of an optional list of hosts and + the sender mailbox. When the list of hosts is present, it + is a "reverse" source route and indicates that the mail was + relayed through each host on the list (the first host in the + list was the most recent relay). This list is used as a + source route to return non-delivery notices to the sender. + As each relay host adds itself to the beginning of the list, + it must use its name as known in the IPCE to which it is + relaying the mail rather than the IPCE from which the mail + came (if they are different). + + This command clears the reverse-path buffer, the + forward-path buffer, and the mail data buffer; and inserts + the reverse-path information from this command into the + reverse-path buffer. + + SEND AND MAIL (SAML) + + This command is used to initiate a mail transaction in which + the mail data is delivered to one or more terminals and + mailboxes. For each recipient the mail data is delivered to + the recipient's terminal if the recipient is active on the + host (and accepting terminal messages), and for all + recipients to the recipient's mailbox. The argument field + contains a reverse-path. This command is successful if the + message is delivered to the mailbox. + + The reverse-path consists of an optional list of hosts and + the sender mailbox. When the list of hosts is present, it + is a "reverse" source route and indicates that the mail was + relayed through each host on the list (the first host in the + list was the most recent relay). This list is used as a + source route to return non-delivery notices to the sender. + As each relay host adds itself to the beginning of the list, + it must use its name as known in the IPCE to which it is + relaying the mail rather than the IPCE from which the mail + came (if they are different). + + This command clears the reverse-path buffer, the + + + +[Page 24] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + forward-path buffer, and the mail data buffer; and inserts + the reverse-path information from this command into the + reverse-path buffer. + + RESET (RSET) + + This command specifies that the current mail transaction is + to be aborted. Any stored sender, recipients, and mail data + must be discarded, and all buffers and state tables cleared. + The receiver must send an OK reply. + + VERIFY (VRFY) + + This command asks the receiver to confirm that the argument + identifies a user. If it is a user name, the full name of + the user (if known) and the fully specified mailbox are + returned. + + This command has no effect on any of the reverse-path + buffer, the forward-path buffer, or the mail data buffer. + + EXPAND (EXPN) + + This command asks the receiver to confirm that the argument + identifies a mailing list, and if so, to return the + membership of that list. The full name of the users (if + known) and the fully specified mailboxes are returned in a + multiline reply. + + This command has no effect on any of the reverse-path + buffer, the forward-path buffer, or the mail data buffer. + + HELP (HELP) + + This command causes the receiver to send helpful information + to the sender of the HELP command. The command may take an + argument (e.g., any command name) and return more specific + information as a response. + + This command has no effect on any of the reverse-path + buffer, the forward-path buffer, or the mail data buffer. + + + + + + + + +Postel [Page 25] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + NOOP (NOOP) + + This command does not affect any parameters or previously + entered commands. It specifies no action other than that + the receiver send an OK reply. + + This command has no effect on any of the reverse-path + buffer, the forward-path buffer, or the mail data buffer. + + QUIT (QUIT) + + This command specifies that the receiver must send an OK + reply, and then close the transmission channel. + + The receiver should not close the transmission channel until + it receives and replies to a QUIT command (even if there was + an error). The sender should not close the transmission + channel until it send a QUIT command and receives the reply + (even if there was an error response to a previous command). + If the connection is closed prematurely the receiver should + act as if a RSET command had been received (canceling any + pending transaction, but not undoing any previously + completed transaction), the sender should act as if the + command or transaction in progress had received a temporary + error (4xx). + + TURN (TURN) + + This command specifies that the receiver must either (1) + send an OK reply and then take on the role of the + sender-SMTP, or (2) send a refusal reply and retain the role + of the receiver-SMTP. + + If program-A is currently the sender-SMTP and it sends the + TURN command and receives an OK reply (250) then program-A + becomes the receiver-SMTP. Program-A is then in the initial + state as if the transmission channel just opened, and it + then sends the 220 service ready greeting. + + If program-B is currently the receiver-SMTP and it receives + the TURN command and sends an OK reply (250) then program-B + becomes the sender-SMTP. Program-B is then in the initial + state as if the transmission channel just opened, and it + then expects to receive the 220 service ready greeting. + + To refuse to change roles the receiver sends the 502 reply. + + + +[Page 26] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + There are restrictions on the order in which these command may + be used. + + The first command in a session must be the HELO command. + The HELO command may be used later in a session as well. If + the HELO command argument is not acceptable a 501 failure + reply must be returned and the receiver-SMTP must stay in + the same state. + + The NOOP, HELP, EXPN, and VRFY commands can be used at any + time during a session. + + The MAIL, SEND, SOML, or SAML commands begin a mail + transaction. Once started a mail transaction consists of + one of the transaction beginning commands, one or more RCPT + commands, and a DATA command, in that order. A mail + transaction may be aborted by the RSET command. There may + be zero or more transactions in a session. + + If the transaction beginning command argument is not + acceptable a 501 failure reply must be returned and the + receiver-SMTP must stay in the same state. If the commands + in a transaction are out of order a 503 failure reply must + be returned and the receiver-SMTP must stay in the same + state. + + The last command in a session must be the QUIT command. The + QUIT command can not be used at any other time in a session. + + 4.1.2. COMMAND SYNTAX + + The commands consist of a command code followed by an argument + field. Command codes are four alphabetic characters. Upper + and lower case alphabetic characters are to be treated + identically. Thus, any of the following may represent the mail + command: + + MAIL Mail mail MaIl mAIl + + This also applies to any symbols representing parameter values, + such as "TO" or "to" for the forward-path. Command codes and + the argument fields are separated by one or more spaces. + However, within the reverse-path and forward-path arguments + case is important. In particular, in some hosts the user + "smith" is different from the user "Smith". + + + + +Postel [Page 27] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + The argument field consists of a variable length character + string ending with the character sequence . The receiver + is to take no action until this sequence is received. + + Square brackets denote an optional argument field. If the + option is not taken, the appropriate default is implied. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 28] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + The following are the SMTP commands: + + HELO + + MAIL FROM: + + RCPT TO: + + DATA + + RSET + + SEND FROM: + + SOML FROM: + + SAML FROM: + + VRFY + + EXPN + + HELP [ ] + + NOOP + + QUIT + + TURN + + + + + + + + + + + + + + + + + + + + +Postel [Page 29] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + The syntax of the above argument fields (using BNF notation + where applicable) is given below. The "..." notation indicates + that a field may be repeated one or more times. + + ::= + + ::= + + ::= "<" [ ":" ] ">" + + ::= | "," + + ::= "@" + + ::= | "." + + ::= | "#" | "[" "]" + + ::= "@" + + ::= | + + ::=
    + + ::= | + + ::= | + + ::= | | "-" + + ::= | "." + + ::= | + + ::= """ """ + + ::= "\" | "\" | | + + ::= | "\" + + ::= "." "." "." + + ::= | + + ::= + + + + +[Page 30] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + ::= the carriage return character (ASCII code 13) + + ::= the line feed character (ASCII code 10) + + ::= the space character (ASCII code 32) + + ::= one, two, or three digits representing a decimal + integer value in the range 0 through 255 + + ::= any one of the 52 alphabetic characters A through Z + in upper case and a through z in lower case + + ::= any one of the 128 ASCII characters, but not any + or + + ::= any one of the ten digits 0 through 9 + + ::= any one of the 128 ASCII characters except , + , quote ("), or backslash (\) + + ::= any one of the 128 ASCII characters (no exceptions) + + ::= "<" | ">" | "(" | ")" | "[" | "]" | "\" | "." + | "," | ";" | ":" | "@" """ | the control + characters (ASCII codes 0 through 31 inclusive and + 127) + + Note that the backslash, "\", is a quote character, which is + used to indicate that the next character is to be used + literally (instead of its normal interpretation). For example, + "Joe\,Smith" could be used to indicate a single nine character + user field with comma being the fourth character of the field. + + Hosts are generally known by names which are translated to + addresses in each host. Note that the name elements of domains + are the official names -- no use of nicknames or aliases is + allowed. + + Sometimes a host is not known to the translation function and + communication is blocked. To bypass this barrier two numeric + forms are also allowed for host "names". One form is a decimal + integer prefixed by a pound sign, "#", which indicates the + number is the address of the host. Another form is four small + decimal integers separated by dots and enclosed by brackets, + e.g., "[123.255.37.2]", which indicates a 32-bit ARPA Internet + Address in four 8-bit fields. + + + +Postel [Page 31] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + The time stamp line and the return path line are formally + defined as follows: + + ::= "Return-Path:" + + ::= "Received:" + + ::= ";" + + + ::= "FROM" + + ::= "BY" + + ::= [] [] [] [] + + ::= "VIA" + + ::= "WITH" + + ::= "ID" + + ::= "FOR" + + ::= The standard names for links are registered with + the Network Information Center. + + ::= The standard names for protocols are + registered with the Network Information Center. + + ::=
    ::= the one or two decimal integer day of the month in + the range 1 to 31. + + ::= "JAN" | "FEB" | "MAR" | "APR" | "MAY" | "JUN" | + "JUL" | "AUG" | "SEP" | "OCT" | "NOV" | "DEC" + + ::= the two decimal integer year of the century in the + range 00 to 99. + + + + + +[Page 32] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + ::= the two decimal integer hour of the day in the + range 00 to 24. + + ::= the two decimal integer minute of the hour in the + range 00 to 59. + + ::= the two decimal integer second of the minute in the + range 00 to 59. + + ::= "UT" for Universal Time (the default) or other + time zone designator (as in [2]). + + + + ------------------------------------------------------------- + + Return Path Example + + Return-Path: <@CHARLIE.ARPA,@BAKER.ARPA:JOE@ABLE.ARPA> + + Example 9 + + ------------------------------------------------------------- + + ------------------------------------------------------------- + + Time Stamp Line Example + + Received: FROM ABC.ARPA BY XYZ.ARPA ; 22 OCT 81 09:23:59 PDT + + Received: from ABC.ARPA by XYZ.ARPA via TELENET with X25 + id M12345 for Smith@PDQ.ARPA ; 22 OCT 81 09:23:59 PDT + + Example 10 + + ------------------------------------------------------------- + + + + + + + + + + + + + +Postel [Page 33] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + 4.2. SMTP REPLIES + + Replies to SMTP commands are devised to ensure the synchronization + of requests and actions in the process of mail transfer, and to + guarantee that the sender-SMTP always knows the state of the + receiver-SMTP. Every command must generate exactly one reply. + + The details of the command-reply sequence are made explicit in + Section 5.3 on Sequencing and Section 5.4 State Diagrams. + + An SMTP reply consists of a three digit number (transmitted as + three alphanumeric characters) followed by some text. The number + is intended for use by automata to determine what state to enter + next; the text is meant for the human user. It is intended that + the three digits contain enough encoded information that the + sender-SMTP need not examine the text and may either discard it or + pass it on to the user, as appropriate. In particular, the text + may be receiver-dependent and context dependent, so there are + likely to be varying texts for each reply code. A discussion of + the theory of reply codes is given in Appendix E. Formally, a + reply is defined to be the sequence: a three-digit code, , + one line of text, and , or a multiline reply (as defined in + Appendix E). Only the EXPN and HELP commands are expected to + result in multiline replies in normal circumstances, however + multiline replies are allowed for any command. + + + + + + + + + + + + + + + + + + + + + + + + +[Page 34] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + 4.2.1. REPLY CODES BY FUNCTION GROUPS + + 500 Syntax error, command unrecognized + [This may include errors such as command line too long] + 501 Syntax error in parameters or arguments + 502 Command not implemented + 503 Bad sequence of commands + 504 Command parameter not implemented + + 211 System status, or system help reply + 214 Help message + [Information on how to use the receiver or the meaning of a + particular non-standard command; this reply is useful only + to the human user] + + 220 Service ready + 221 Service closing transmission channel + 421 Service not available, + closing transmission channel + [This may be a reply to any command if the service knows it + must shut down] + + 250 Requested mail action okay, completed + 251 User not local; will forward to + 450 Requested mail action not taken: mailbox unavailable + [E.g., mailbox busy] + 550 Requested action not taken: mailbox unavailable + [E.g., mailbox not found, no access] + 451 Requested action aborted: error in processing + 551 User not local; please try + 452 Requested action not taken: insufficient system storage + 552 Requested mail action aborted: exceeded storage allocation + 553 Requested action not taken: mailbox name not allowed + [E.g., mailbox syntax incorrect] + 354 Start mail input; end with . + 554 Transaction failed + + + + + + + + + + + + + +Postel [Page 35] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + 4.2.2. NUMERIC ORDER LIST OF REPLY CODES + + 211 System status, or system help reply + 214 Help message + [Information on how to use the receiver or the meaning of a + particular non-standard command; this reply is useful only + to the human user] + 220 Service ready + 221 Service closing transmission channel + 250 Requested mail action okay, completed + 251 User not local; will forward to + + 354 Start mail input; end with . + + 421 Service not available, + closing transmission channel + [This may be a reply to any command if the service knows it + must shut down] + 450 Requested mail action not taken: mailbox unavailable + [E.g., mailbox busy] + 451 Requested action aborted: local error in processing + 452 Requested action not taken: insufficient system storage + + 500 Syntax error, command unrecognized + [This may include errors such as command line too long] + 501 Syntax error in parameters or arguments + 502 Command not implemented + 503 Bad sequence of commands + 504 Command parameter not implemented + 550 Requested action not taken: mailbox unavailable + [E.g., mailbox not found, no access] + 551 User not local; please try + 552 Requested mail action aborted: exceeded storage allocation + 553 Requested action not taken: mailbox name not allowed + [E.g., mailbox syntax incorrect] + 554 Transaction failed + + + + + + + + + + + + + +[Page 36] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + 4.3. SEQUENCING OF COMMANDS AND REPLIES + + The communication between the sender and receiver is intended to + be an alternating dialogue, controlled by the sender. As such, + the sender issues a command and the receiver responds with a + reply. The sender must wait for this response before sending + further commands. + + One important reply is the connection greeting. Normally, a + receiver will send a 220 "Service ready" reply when the connection + is completed. The sender should wait for this greeting message + before sending any commands. + + Note: all the greeting type replies have the official name of + the server host as the first word following the reply code. + + For example, + + 220 USC-ISIF.ARPA Service ready + + The table below lists alternative success and failure replies for + each command. These must be strictly adhered to; a receiver may + substitute text in the replies, but the meaning and action implied + by the code numbers and by the specific command reply sequence + cannot be altered. + + COMMAND-REPLY SEQUENCES + + Each command is listed with its possible replies. The prefixes + used before the possible replies are "P" for preliminary (not + used in SMTP), "I" for intermediate, "S" for success, "F" for + failure, and "E" for error. The 421 reply (service not + available, closing transmission channel) may be given to any + command if the SMTP-receiver knows it must shut down. This + listing forms the basis for the State Diagrams in Section 4.4. + + CONNECTION ESTABLISHMENT + S: 220 + F: 421 + HELO + S: 250 + E: 500, 501, 504, 421 + MAIL + S: 250 + F: 552, 451, 452 + E: 500, 501, 421 + + + +Postel [Page 37] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + RCPT + S: 250, 251 + F: 550, 551, 552, 553, 450, 451, 452 + E: 500, 501, 503, 421 + DATA + I: 354 -> data -> S: 250 + F: 552, 554, 451, 452 + F: 451, 554 + E: 500, 501, 503, 421 + RSET + S: 250 + E: 500, 501, 504, 421 + SEND + S: 250 + F: 552, 451, 452 + E: 500, 501, 502, 421 + SOML + S: 250 + F: 552, 451, 452 + E: 500, 501, 502, 421 + SAML + S: 250 + F: 552, 451, 452 + E: 500, 501, 502, 421 + VRFY + S: 250, 251 + F: 550, 551, 553 + E: 500, 501, 502, 504, 421 + EXPN + S: 250 + F: 550 + E: 500, 501, 502, 504, 421 + HELP + S: 211, 214 + E: 500, 501, 502, 504, 421 + NOOP + S: 250 + E: 500, 421 + QUIT + S: 221 + E: 500 + TURN + S: 250 + F: 502 + E: 500, 503 + + + + +[Page 38] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + 4.4. STATE DIAGRAMS + + Following are state diagrams for a simple-minded SMTP + implementation. Only the first digit of the reply codes is used. + There is one state diagram for each group of SMTP commands. The + command groupings were determined by constructing a model for each + command and then collecting together the commands with + structurally identical models. + + For each command there are three possible outcomes: "success" + (S), "failure" (F), and "error" (E). In the state diagrams below + we use the symbol B for "begin", and the symbol W for "wait for + reply". + + First, the diagram that represents most of the SMTP commands: + + + 1,3 +---+ + ----------->| E | + | +---+ + | + +---+ cmd +---+ 2 +---+ + | B |---------->| W |---------->| S | + +---+ +---+ +---+ + | + | 4,5 +---+ + ----------->| F | + +---+ + + + This diagram models the commands: + + HELO, MAIL, RCPT, RSET, SEND, SOML, SAML, VRFY, EXPN, HELP, + NOOP, QUIT, TURN. + + + + + + + + + + + + + + + +Postel [Page 39] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + A more complex diagram models the DATA command: + + + +---+ DATA +---+ 1,2 +---+ + | B |---------->| W |-------------------->| E | + +---+ +---+ ------------>+---+ + 3| |4,5 | + | | | + -------------- ----- | + | | | +---+ + | ---------- -------->| S | + | | | | +---+ + | | ------------ + | | | | + V 1,3| |2 | + +---+ data +---+ --------------->+---+ + | |---------->| W | | F | + +---+ +---+-------------------->+---+ + 4,5 + + + Note that the "data" here is a series of lines sent from the + sender to the receiver with no response expected until the last + line is sent. + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 40] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + 4.5. DETAILS + + 4.5.1. MINIMUM IMPLEMENTATION + + In order to make SMTP workable, the following minimum + implementation is required for all receivers: + + COMMANDS -- HELO + MAIL + RCPT + DATA + RSET + NOOP + QUIT + + 4.5.2. TRANSPARENCY + + Without some provision for data transparency the character + sequence "." ends the mail text and cannot be sent + by the user. In general, users are not aware of such + "forbidden" sequences. To allow all user composed text to be + transmitted transparently the following procedures are used. + + 1. Before sending a line of mail text the sender-SMTP checks + the first character of the line. If it is a period, one + additional period is inserted at the beginning of the line. + + 2. When a line of mail text is received by the receiver-SMTP + it checks the line. If the line is composed of a single + period it is the end of mail. If the first character is a + period and there are other characters on the line, the first + character is deleted. + + The mail data may contain any of the 128 ASCII characters. All + characters are to be delivered to the recipient's mailbox + including format effectors and other control characters. If + the transmission channel provides an 8-bit byte (octets) data + stream, the 7-bit ASCII codes are transmitted right justified + in the octets with the high order bits cleared to zero. + + In some systems it may be necessary to transform the data as + it is received and stored. This may be necessary for hosts + that use a different character set than ASCII as their local + character set, or that store data in records rather than + + + + + +Postel [Page 41] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + strings. If such transforms are necessary, they must be + reversible -- especially if such transforms are applied to + mail being relayed. + + 4.5.3. SIZES + + There are several objects that have required minimum maximum + sizes. That is, every implementation must be able to receive + objects of at least these sizes, but must not send objects + larger than these sizes. + + + **************************************************** + * * + * TO THE MAXIMUM EXTENT POSSIBLE, IMPLEMENTATION * + * TECHNIQUES WHICH IMPOSE NO LIMITS ON THE LENGTH * + * OF THESE OBJECTS SHOULD BE USED. * + * * + **************************************************** + + user + + The maximum total length of a user name is 64 characters. + + domain + + The maximum total length of a domain name or number is 64 + characters. + + path + + The maximum total length of a reverse-path or + forward-path is 256 characters (including the punctuation + and element separators). + + command line + + The maximum total length of a command line including the + command word and the is 512 characters. + + reply line + + The maximum total length of a reply line including the + reply code and the is 512 characters. + + + + + +[Page 42] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + text line + + The maximum total length of a text line including the + is 1000 characters (but not counting the leading + dot duplicated for transparency). + + recipients buffer + + The maximum total number of recipients that must be + buffered is 100 recipients. + + + **************************************************** + * * + * TO THE MAXIMUM EXTENT POSSIBLE, IMPLEMENTATION * + * TECHNIQUES WHICH IMPOSE NO LIMITS ON THE LENGTH * + * OF THESE OBJECTS SHOULD BE USED. * + * * + **************************************************** + + Errors due to exceeding these limits may be reported by using + the reply codes, for example: + + 500 Line too long. + + 501 Path too long + + 552 Too many recipients. + + 552 Too much mail data. + + + + + + + + + + + + + + + + + + + +Postel [Page 43] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + +APPENDIX A + + TCP Transport service + + The Transmission Control Protocol [3] is used in the ARPA + Internet, and in any network following the US DoD standards for + internetwork protocols. + + Connection Establishment + + The SMTP transmission channel is a TCP connection established + between the sender process port U and the receiver process port + L. This single full duplex connection is used as the + transmission channel. This protocol is assigned the service + port 25 (31 octal), that is L=25. + + Data Transfer + + The TCP connection supports the transmission of 8-bit bytes. + The SMTP data is 7-bit ASCII characters. Each character is + transmitted as an 8-bit byte with the high-order bit cleared to + zero. + + + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 44] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + +APPENDIX B + + NCP Transport service + + The ARPANET Host-to-Host Protocol [4] (implemented by the Network + Control Program) may be used in the ARPANET. + + Connection Establishment + + The SMTP transmission channel is established via NCP between + the sender process socket U and receiver process socket L. The + Initial Connection Protocol [5] is followed resulting in a pair + of simplex connections. This pair of connections is used as + the transmission channel. This protocol is assigned the + contact socket 25 (31 octal), that is L=25. + + Data Transfer + + The NCP data connections are established in 8-bit byte mode. + The SMTP data is 7-bit ASCII characters. Each character is + transmitted as an 8-bit byte with the high-order bit cleared to + zero. + + + + + + + + + + + + + + + + + + + + + + + + + + + +Postel [Page 45] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + +APPENDIX C + + NITS + + The Network Independent Transport Service [6] may be used. + + Connection Establishment + + The SMTP transmission channel is established via NITS between + the sender process and receiver process. The sender process + executes the CONNECT primitive, and the waiting receiver + process executes the ACCEPT primitive. + + Data Transfer + + The NITS connection supports the transmission of 8-bit bytes. + The SMTP data is 7-bit ASCII characters. Each character is + transmitted as an 8-bit byte with the high-order bit cleared to + zero. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 46] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + +APPENDIX D + + X.25 Transport service + + It may be possible to use the X.25 service [7] as provided by the + Public Data Networks directly, however, it is suggested that a + reliable end-to-end protocol such as TCP be used on top of X.25 + connections. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Postel [Page 47] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + +APPENDIX E + + Theory of Reply Codes + + The three digits of the reply each have a special significance. + The first digit denotes whether the response is good, bad or + incomplete. An unsophisticated sender-SMTP will be able to + determine its next action (proceed as planned, redo, retrench, + etc.) by simply examining this first digit. A sender-SMTP that + wants to know approximately what kind of error occurred (e.g., + mail system error, command syntax error) may examine the second + digit, reserving the third digit for the finest gradation of + information. + + There are five values for the first digit of the reply code: + + 1yz Positive Preliminary reply + + The command has been accepted, but the requested action + is being held in abeyance, pending confirmation of the + information in this reply. The sender-SMTP should send + another command specifying whether to continue or abort + the action. + + [Note: SMTP does not have any commands that allow this + type of reply, and so does not have the continue or + abort commands.] + + 2yz Positive Completion reply + + The requested action has been successfully completed. A + new request may be initiated. + + 3yz Positive Intermediate reply + + The command has been accepted, but the requested action + is being held in abeyance, pending receipt of further + information. The sender-SMTP should send another command + specifying this information. This reply is used in + command sequence groups. + + 4yz Transient Negative Completion reply + + The command was not accepted and the requested action did + not occur. However, the error condition is temporary and + the action may be requested again. The sender should + + + +[Page 48] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + return to the beginning of the command sequence (if any). + It is difficult to assign a meaning to "transient" when + two different sites (receiver- and sender- SMTPs) must + agree on the interpretation. Each reply in this category + might have a different time value, but the sender-SMTP is + encouraged to try again. A rule of thumb to determine if + a reply fits into the 4yz or the 5yz category (see below) + is that replies are 4yz if they can be repeated without + any change in command form or in properties of the sender + or receiver. (E.g., the command is repeated identically + and the receiver does not put up a new implementation.) + + 5yz Permanent Negative Completion reply + + The command was not accepted and the requested action did + not occur. The sender-SMTP is discouraged from repeating + the exact request (in the same sequence). Even some + "permanent" error conditions can be corrected, so the + human user may want to direct the sender-SMTP to + reinitiate the command sequence by direct action at some + point in the future (e.g., after the spelling has been + changed, or the user has altered the account status). + + The second digit encodes responses in specific categories: + + x0z Syntax -- These replies refer to syntax errors, + syntactically correct commands that don't fit any + functional category, and unimplemented or superfluous + commands. + + x1z Information -- These are replies to requests for + information, such as status or help. + + x2z Connections -- These are replies referring to the + transmission channel. + + x3z Unspecified as yet. + + x4z Unspecified as yet. + + x5z Mail system -- These replies indicate the status of + the receiver mail system vis-a-vis the requested + transfer or other mail system action. + + The third digit gives a finer gradation of meaning in each + category specified by the second digit. The list of replies + + + +Postel [Page 49] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + illustrates this. Each reply text is recommended rather than + mandatory, and may even change according to the command with + which it is associated. On the other hand, the reply codes + must strictly follow the specifications in this section. + Receiver implementations should not invent new codes for + slightly different situations from the ones described here, but + rather adapt codes already defined. + + For example, a command such as NOOP whose successful execution + does not offer the sender-SMTP any new information will return + a 250 reply. The response is 502 when the command requests an + unimplemented non-site-specific action. A refinement of that + is the 504 reply for a command that is implemented, but that + requests an unimplemented parameter. + + The reply text may be longer than a single line; in these cases + the complete text must be marked so the sender-SMTP knows when it + can stop reading the reply. This requires a special format to + indicate a multiple line reply. + + The format for multiline replies requires that every line, + except the last, begin with the reply code, followed + immediately by a hyphen, "-" (also known as minus), followed by + text. The last line will begin with the reply code, followed + immediately by , optionally some text, and . + + For example: + 123-First line + 123-Second line + 123-234 text beginning with numbers + 123 The last line + + In many cases the sender-SMTP then simply needs to search for + the reply code followed by at the beginning of a line, and + ignore all preceding lines. In a few cases, there is important + data for the sender in the reply "text". The sender will know + these cases from the current context. + + + + + + + + + + + + +[Page 50] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + +APPENDIX F + + Scenarios + + This section presents complete scenarios of several types of SMTP + sessions. + + A Typical SMTP Transaction Scenario + + This SMTP example shows mail sent by Smith at host USC-ISIF, to + Jones, Green, and Brown at host BBN-UNIX. Here we assume that + host USC-ISIF contacts host BBN-UNIX directly. The mail is + accepted for Jones and Brown. Green does not have a mailbox at + host BBN-UNIX. + + ------------------------------------------------------------- + + R: 220 BBN-UNIX.ARPA Simple Mail Transfer Service Ready + S: HELO USC-ISIF.ARPA + R: 250 BBN-UNIX.ARPA + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO: + R: 250 OK + + S: RCPT TO: + R: 550 No such user here + + S: RCPT TO: + R: 250 OK + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + S: QUIT + R: 221 BBN-UNIX.ARPA Service closing transmission channel + + Scenario 1 + + ------------------------------------------------------------- + + + +Postel [Page 51] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + Aborted SMTP Transaction Scenario + + ------------------------------------------------------------- + + R: 220 MIT-Multics.ARPA Simple Mail Transfer Service Ready + S: HELO ISI-VAXA.ARPA + R: 250 MIT-Multics.ARPA + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO: + R: 250 OK + + S: RCPT TO: + R: 550 No such user here + + S: RSET + R: 250 OK + + S: QUIT + R: 221 MIT-Multics.ARPA Service closing transmission channel + + Scenario 2 + + ------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + +[Page 52] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + Relayed Mail Scenario + + ------------------------------------------------------------- + + Step 1 -- Source Host to Relay Host + + R: 220 USC-ISIE.ARPA Simple Mail Transfer Service Ready + S: HELO MIT-AI.ARPA + R: 250 USC-ISIE.ARPA + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO:<@USC-ISIE.ARPA:Jones@BBN-VAX.ARPA> + R: 250 OK + + S: DATA + R: 354 Start mail input; end with . + S: Date: 2 Nov 81 22:33:44 + S: From: John Q. Public + S: Subject: The Next Meeting of the Board + S: To: Jones@BBN-Vax.ARPA + S: + S: Bill: + S: The next meeting of the board of directors will be + S: on Tuesday. + S: John. + S: . + R: 250 OK + + S: QUIT + R: 221 USC-ISIE.ARPA Service closing transmission channel + + + + + + + + + + + + + + + + + +Postel [Page 53] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + Step 2 -- Relay Host to Destination Host + + R: 220 BBN-VAX.ARPA Simple Mail Transfer Service Ready + S: HELO USC-ISIE.ARPA + R: 250 BBN-VAX.ARPA + + S: MAIL FROM:<@USC-ISIE.ARPA:JQP@MIT-AI.ARPA> + R: 250 OK + + S: RCPT TO: + R: 250 OK + + S: DATA + R: 354 Start mail input; end with . + S: Received: from MIT-AI.ARPA by USC-ISIE.ARPA ; + 2 Nov 81 22:40:10 UT + S: Date: 2 Nov 81 22:33:44 + S: From: John Q. Public + S: Subject: The Next Meeting of the Board + S: To: Jones@BBN-Vax.ARPA + S: + S: Bill: + S: The next meeting of the board of directors will be + S: on Tuesday. + S: John. + S: . + R: 250 OK + + S: QUIT + R: 221 USC-ISIE.ARPA Service closing transmission channel + + Scenario 3 + + ------------------------------------------------------------- + + + + + + + + + + + + + + + +[Page 54] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + Verifying and Sending Scenario + + ------------------------------------------------------------- + + R: 220 SU-SCORE.ARPA Simple Mail Transfer Service Ready + S: HELO MIT-MC.ARPA + R: 250 SU-SCORE.ARPA + + S: VRFY Crispin + R: 250 Mark Crispin + + S: SEND FROM: + R: 250 OK + + S: RCPT TO: + R: 250 OK + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + S: QUIT + R: 221 SU-SCORE.ARPA Service closing transmission channel + + Scenario 4 + + ------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + +Postel [Page 55] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + Sending and Mailing Scenarios + + First the user's name is verified, then an attempt is made to + send to the user's terminal. When that fails, the messages is + mailed to the user's mailbox. + + ------------------------------------------------------------- + + R: 220 SU-SCORE.ARPA Simple Mail Transfer Service Ready + S: HELO MIT-MC.ARPA + R: 250 SU-SCORE.ARPA + + S: VRFY Crispin + R: 250 Mark Crispin + + S: SEND FROM: + R: 250 OK + + S: RCPT TO: + R: 450 User not active now + + S: RSET + R: 250 OK + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO: + R: 250 OK + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + S: QUIT + R: 221 SU-SCORE.ARPA Service closing transmission channel + + Scenario 5 + + ------------------------------------------------------------- + + + + + + +[Page 56] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + Doing the preceding scenario more efficiently. + + ------------------------------------------------------------- + + R: 220 SU-SCORE.ARPA Simple Mail Transfer Service Ready + S: HELO MIT-MC.ARPA + R: 250 SU-SCORE.ARPA + + S: VRFY Crispin + R: 250 Mark Crispin + + S: SOML FROM: + R: 250 OK + + S: RCPT TO: + R: 250 User not active now, so will do mail. + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + S: QUIT + R: 221 SU-SCORE.ARPA Service closing transmission channel + + Scenario 6 + + ------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + +Postel [Page 57] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + Mailing List Scenario + + First each of two mailing lists are expanded in separate sessions + with different hosts. Then the message is sent to everyone that + appeared on either list (but no duplicates) via a relay host. + + ------------------------------------------------------------- + + Step 1 -- Expanding the First List + + R: 220 MIT-AI.ARPA Simple Mail Transfer Service Ready + S: HELO SU-SCORE.ARPA + R: 250 MIT-AI.ARPA + + S: EXPN Example-People + R: 250- + R: 250-Fred Fonebone + R: 250-Xenon Y. Zither + R: 250-Quincy Smith <@USC-ISIF.ARPA:Q-Smith@ISI-VAXA.ARPA> + R: 250- + R: 250 + + S: QUIT + R: 221 MIT-AI.ARPA Service closing transmission channel + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 58] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + Step 2 -- Expanding the Second List + + R: 220 MIT-MC.ARPA Simple Mail Transfer Service Ready + S: HELO SU-SCORE.ARPA + R: 250 MIT-MC.ARPA + + S: EXPN Interested-Parties + R: 250-Al Calico + R: 250- + R: 250-Quincy Smith <@USC-ISIF.ARPA:Q-Smith@ISI-VAXA.ARPA> + R: 250- + R: 250 + + S: QUIT + R: 221 MIT-MC.ARPA Service closing transmission channel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Postel [Page 59] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + Step 3 -- Mailing to All via a Relay Host + + R: 220 USC-ISIE.ARPA Simple Mail Transfer Service Ready + S: HELO SU-SCORE.ARPA + R: 250 USC-ISIE.ARPA + + S: MAIL FROM: + R: 250 OK + S: RCPT TO:<@USC-ISIE.ARPA:ABC@MIT-MC.ARPA> + R: 250 OK + S: RCPT TO:<@USC-ISIE.ARPA:Fonebone@USC-ISIQA.ARPA> + R: 250 OK + S: RCPT TO:<@USC-ISIE.ARPA:XYZ@MIT-AI.ARPA> + R: 250 OK + S: RCPT + TO:<@USC-ISIE.ARPA,@USC-ISIF.ARPA:Q-Smith@ISI-VAXA.ARPA> + R: 250 OK + S: RCPT TO:<@USC-ISIE.ARPA:joe@FOO-UNIX.ARPA> + R: 250 OK + S: RCPT TO:<@USC-ISIE.ARPA:xyz@BAR-UNIX.ARPA> + R: 250 OK + S: RCPT TO:<@USC-ISIE.ARPA:fred@BBN-UNIX.ARPA> + R: 250 OK + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + S: QUIT + R: 221 USC-ISIE.ARPA Service closing transmission channel + + Scenario 7 + + ------------------------------------------------------------- + + + + + + + + + + + + +[Page 60] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + Forwarding Scenarios + + ------------------------------------------------------------- + + R: 220 USC-ISIF.ARPA Simple Mail Transfer Service Ready + S: HELO LBL-UNIX.ARPA + R: 250 USC-ISIF.ARPA + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO: + R: 251 User not local; will forward to + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + S: QUIT + R: 221 USC-ISIF.ARPA Service closing transmission channel + + Scenario 8 + + ------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + +Postel [Page 61] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + ------------------------------------------------------------- + + Step 1 -- Trying the Mailbox at the First Host + + R: 220 USC-ISIF.ARPA Simple Mail Transfer Service Ready + S: HELO LBL-UNIX.ARPA + R: 250 USC-ISIF.ARPA + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO: + R: 251 User not local; will forward to + + S: RSET + R: 250 OK + + S: QUIT + R: 221 USC-ISIF.ARPA Service closing transmission channel + + Step 2 -- Delivering the Mail at the Second Host + + R: 220 USC-ISI.ARPA Simple Mail Transfer Service Ready + S: HELO LBL-UNIX.ARPA + R: 250 USC-ISI.ARPA + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO: + R: OK + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + S: QUIT + R: 221 USC-ISI.ARPA Service closing transmission channel + + Scenario 9 + + ------------------------------------------------------------- + + + + +[Page 62] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + Too Many Recipients Scenario + + ------------------------------------------------------------- + + R: 220 BERKELEY.ARPA Simple Mail Transfer Service Ready + S: HELO USC-ISIF.ARPA + R: 250 BERKELEY.ARPA + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO: + R: 250 OK + + S: RCPT TO: + R: 552 Recipient storage full, try again in another transaction + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO: + R: 250 OK + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + S: QUIT + R: 221 BERKELEY.ARPA Service closing transmission channel + + Scenario 10 + + ------------------------------------------------------------- + + Note that a real implementation must handle many recipients as + specified in Section 4.5.3. + + + +Postel [Page 63] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + +GLOSSARY + + ASCII + + American Standard Code for Information Interchange [1]. + + command + + A request for a mail service action sent by the sender-SMTP to the + receiver-SMTP. + + domain + + The hierarchially structured global character string address of a + host computer in the mail system. + + end of mail data indication + + A special sequence of characters that indicates the end of the + mail data. In particular, the five characters carriage return, + line feed, period, carriage return, line feed, in that order. + + host + + A computer in the internetwork environment on which mailboxes or + SMTP processes reside. + + line + + A a sequence of ASCII characters ending with a . + + mail data + + A sequence of ASCII characters of arbitrary length, which conforms + to the standard set in the Standard for the Format of ARPA + Internet Text Messages (RFC 822 [2]). + + mailbox + + A character string (address) which identifies a user to whom mail + is to be sent. Mailbox normally consists of the host and user + specifications. The standard mailbox naming convention is defined + to be "user@domain". Additionally, the "container" in which mail + is stored. + + + + + +[Page 64] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + receiver-SMTP process + + A process which transfers mail in cooperation with a sender-SMTP + process. It waits for a connection to be established via the + transport service. It receives SMTP commands from the + sender-SMTP, sends replies, and performs the specified operations. + + reply + + A reply is an acknowledgment (positive or negative) sent from + receiver to sender via the transmission channel in response to a + command. The general form of a reply is a completion code + (including error codes) followed by a text string. The codes are + for use by programs and the text is usually intended for human + users. + + sender-SMTP process + + A process which transfers mail in cooperation with a receiver-SMTP + process. A local language may be used in the user interface + command/reply dialogue. The sender-SMTP initiates the transport + service connection. It initiates SMTP commands, receives replies, + and governs the transfer of mail. + + session + + The set of exchanges that occur while the transmission channel is + open. + + transaction + + The set of exchanges required for one message to be transmitted + for one or more recipients. + + transmission channel + + A full-duplex communication path between a sender-SMTP and a + receiver-SMTP for the exchange of commands, replies, and mail + text. + + transport service + + Any reliable stream-oriented data communication services. For + example, NCP, TCP, NITS. + + + + + +Postel [Page 65] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + user + + A human being (or a process on behalf of a human being) wishing to + obtain mail transfer service. In addition, a recipient of + computer mail. + + word + + A sequence of printing characters. + + + + The characters carriage return and line feed (in that order). + + + + The space character. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 66] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + +REFERENCES + + [1] ASCII + + ASCII, "USA Code for Information Interchange", United States of + America Standards Institute, X3.4, 1968. Also in: Feinler, E. + and J. Postel, eds., "ARPANET Protocol Handbook", NIC 7104, for + the Defense Communications Agency by SRI International, Menlo + Park, California, Revised January 1978. + + [2] RFC 822 + + Crocker, D., "Standard for the Format of ARPA Internet Text + Messages," RFC 822, Department of Electrical Engineering, + University of Delaware, August 1982. + + [3] TCP + + Postel, J., ed., "Transmission Control Protocol - DARPA Internet + Program Protocol Specification", RFC 793, USC/Information Sciences + Institute, NTIS AD Number A111091, September 1981. Also in: + Feinler, E. and J. Postel, eds., "Internet Protocol Transition + Workbook", SRI International, Menlo Park, California, March 1982. + + [4] NCP + + McKenzie,A., "Host/Host Protocol for the ARPA Network", NIC 8246, + January 1972. Also in: Feinler, E. and J. Postel, eds., "ARPANET + Protocol Handbook", NIC 7104, for the Defense Communications + Agency by SRI International, Menlo Park, California, Revised + January 1978. + + [5] Initial Connection Protocol + + Postel, J., "Official Initial Connection Protocol", NIC 7101, + 11 June 1971. Also in: Feinler, E. and J. Postel, eds., "ARPANET + Protocol Handbook", NIC 7104, for the Defense Communications + Agency by SRI International, Menlo Park, California, Revised + January 1978. + + [6] NITS + + PSS/SG3, "A Network Independent Transport Service", Study Group 3, + The Post Office PSS Users Group, February 1980. Available from + the DCPU, National Physical Laboratory, Teddington, UK. + + + + +Postel [Page 67] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + [7] X.25 + + CCITT, "Recommendation X.25 - Interface Between Data Terminal + Equipment (DTE) and Data Circuit-terminating Equipment (DCE) for + Terminals Operating in the Packet Mode on Public Data Networks," + CCITT Orange Book, Vol. VIII.2, International Telephone and + Telegraph Consultative Committee, Geneva, 1976. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 68] Postel + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc0822.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc0822.txt new file mode 100644 index 0000000..35b09a3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc0822.txt @@ -0,0 +1,2901 @@ + + + + + + + RFC # 822 + + Obsoletes: RFC #733 (NIC #41952) + + + + + + + + + + + + + STANDARD FOR THE FORMAT OF + + ARPA INTERNET TEXT MESSAGES + + + + + + + August 13, 1982 + + + + + + + Revised by + + David H. Crocker + + + Dept. of Electrical Engineering + University of Delaware, Newark, DE 19711 + Network: DCrocker @ UDel-Relay + + + + + + + + + + + + + + + + Standard for ARPA Internet Text Messages + + + TABLE OF CONTENTS + + + PREFACE .................................................... ii + + 1. INTRODUCTION ........................................... 1 + + 1.1. Scope ............................................ 1 + 1.2. Communication Framework .......................... 2 + + 2. NOTATIONAL CONVENTIONS ................................. 3 + + 3. LEXICAL ANALYSIS OF MESSAGES ........................... 5 + + 3.1. General Description .............................. 5 + 3.2. Header Field Definitions ......................... 9 + 3.3. Lexical Tokens ................................... 10 + 3.4. Clarifications ................................... 11 + + 4. MESSAGE SPECIFICATION .................................. 17 + + 4.1. Syntax ........................................... 17 + 4.2. Forwarding ....................................... 19 + 4.3. Trace Fields ..................................... 20 + 4.4. Originator Fields ................................ 21 + 4.5. Receiver Fields .................................. 23 + 4.6. Reference Fields ................................. 23 + 4.7. Other Fields ..................................... 24 + + 5. DATE AND TIME SPECIFICATION ............................ 26 + + 5.1. Syntax ........................................... 26 + 5.2. Semantics ........................................ 26 + + 6. ADDRESS SPECIFICATION .................................. 27 + + 6.1. Syntax ........................................... 27 + 6.2. Semantics ........................................ 27 + 6.3. Reserved Address ................................. 33 + + 7. BIBLIOGRAPHY ........................................... 34 + + + APPENDIX + + A. EXAMPLES ............................................... 36 + B. SIMPLE FIELD PARSING ................................... 40 + C. DIFFERENCES FROM RFC #733 .............................. 41 + D. ALPHABETICAL LISTING OF SYNTAX RULES ................... 44 + + + August 13, 1982 - i - RFC #822 + + + + + Standard for ARPA Internet Text Messages + + + PREFACE + + + By 1977, the Arpanet employed several informal standards for + the text messages (mail) sent among its host computers. It was + felt necessary to codify these practices and provide for those + features that seemed imminent. The result of that effort was + Request for Comments (RFC) #733, "Standard for the Format of ARPA + Network Text Message", by Crocker, Vittal, Pogran, and Henderson. + The specification attempted to avoid major changes in existing + software, while permitting several new features. + + This document revises the specifications in RFC #733, in + order to serve the needs of the larger and more complex ARPA + Internet. Some of RFC #733's features failed to gain adequate + acceptance. In order to simplify the standard and the software + that follows it, these features have been removed. A different + addressing scheme is used, to handle the case of inter-network + mail; and the concept of re-transmission has been introduced. + + This specification is intended for use in the ARPA Internet. + However, an attempt has been made to free it of any dependence on + that environment, so that it can be applied to other network text + message systems. + + The specification of RFC #733 took place over the course of + one year, using the ARPANET mail environment, itself, to provide + an on-going forum for discussing the capabilities to be included. + More than twenty individuals, from across the country, partici- + pated in the original discussion. The development of this + revised specification has, similarly, utilized network mail-based + group discussion. Both specification efforts greatly benefited + from the comments and ideas of the participants. + + The syntax of the standard, in RFC #733, was originally + specified in the Backus-Naur Form (BNF) meta-language. Ken L. + Harrenstien, of SRI International, was responsible for re-coding + the BNF into an augmented BNF that makes the representation + smaller and easier to understand. + + + + + + + + + + + + + August 13, 1982 - ii - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 1. INTRODUCTION + + 1.1. SCOPE + + This standard specifies a syntax for text messages that are + sent among computer users, within the framework of "electronic + mail". The standard supersedes the one specified in ARPANET + Request for Comments #733, "Standard for the Format of ARPA Net- + work Text Messages". + + In this context, messages are viewed as having an envelope + and contents. The envelope contains whatever information is + needed to accomplish transmission and delivery. The contents + compose the object to be delivered to the recipient. This stan- + dard applies only to the format and some of the semantics of mes- + sage contents. It contains no specification of the information + in the envelope. + + However, some message systems may use information from the + contents to create the envelope. It is intended that this stan- + dard facilitate the acquisition of such information by programs. + + Some message systems may store messages in formats that + differ from the one specified in this standard. This specifica- + tion is intended strictly as a definition of what message content + format is to be passed BETWEEN hosts. + + Note: This standard is NOT intended to dictate the internal for- + mats used by sites, the specific message system features + that they are expected to support, or any of the charac- + teristics of user interface programs that create or read + messages. + + A distinction should be made between what the specification + REQUIRES and what it ALLOWS. Messages can be made complex and + rich with formally-structured components of information or can be + kept small and simple, with a minimum of such information. Also, + the standard simplifies the interpretation of differing visual + formats in messages; only the visual aspect of a message is + affected and not the interpretation of information within it. + Implementors may choose to retain such visual distinctions. + + The formal definition is divided into four levels. The bot- + tom level describes the meta-notation used in this document. The + second level describes basic lexical analyzers that feed tokens + to higher-level parsers. Next is an overall specification for + messages; it permits distinguishing individual fields. Finally, + there is definition of the contents of several structured fields. + + + + August 13, 1982 - 1 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 1.2. COMMUNICATION FRAMEWORK + + Messages consist of lines of text. No special provisions + are made for encoding drawings, facsimile, speech, or structured + text. No significant consideration has been given to questions + of data compression or to transmission and storage efficiency, + and the standard tends to be free with the number of bits con- + sumed. For example, field names are specified as free text, + rather than special terse codes. + + A general "memo" framework is used. That is, a message con- + sists of some information in a rigid format, followed by the main + part of the message, with a format that is not specified in this + document. The syntax of several fields of the rigidly-formated + ("headers") section is defined in this specification; some of + these fields must be included in all messages. + + The syntax that distinguishes between header fields is + specified separately from the internal syntax for particular + fields. This separation is intended to allow simple parsers to + operate on the general structure of messages, without concern for + the detailed structure of individual header fields. Appendix B + is provided to facilitate construction of these parsers. + + In addition to the fields specified in this document, it is + expected that other fields will gain common use. As necessary, + the specifications for these "extension-fields" will be published + through the same mechanism used to publish this document. Users + may also wish to extend the set of fields that they use + privately. Such "user-defined fields" are permitted. + + The framework severely constrains document tone and appear- + ance and is primarily useful for most intra-organization communi- + cations and well-structured inter-organization communication. + It also can be used for some types of inter-process communica- + tion, such as simple file transfer and remote job entry. A more + robust framework might allow for multi-font, multi-color, multi- + dimension encoding of information. A less robust one, as is + present in most single-machine message systems, would more + severely constrain the ability to add fields and the decision to + include specific fields. In contrast with paper-based communica- + tion, it is interesting to note that the RECEIVER of a message + can exercise an extraordinary amount of control over the + message's appearance. The amount of actual control available to + message receivers is contingent upon the capabilities of their + individual message systems. + + + + + + August 13, 1982 - 2 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 2. NOTATIONAL CONVENTIONS + + This specification uses an augmented Backus-Naur Form (BNF) + notation. The differences from standard BNF involve naming rules + and indicating repetition and "local" alternatives. + + 2.1. RULE NAMING + + Angle brackets ("<", ">") are not used, in general. The + name of a rule is simply the name itself, rather than "". + Quotation-marks enclose literal text (which may be upper and/or + lower case). Certain basic rules are in uppercase, such as + SPACE, TAB, CRLF, DIGIT, ALPHA, etc. Angle brackets are used in + rule definitions, and in the rest of this document, whenever + their presence will facilitate discerning the use of rule names. + + 2.2. RULE1 / RULE2: ALTERNATIVES + + Elements separated by slash ("/") are alternatives. There- + fore "foo / bar" will accept foo or bar. + + 2.3. (RULE1 RULE2): LOCAL ALTERNATIVES + + Elements enclosed in parentheses are treated as a single + element. Thus, "(elem (foo / bar) elem)" allows the token + sequences "elem foo elem" and "elem bar elem". + + 2.4. *RULE: REPETITION + + The character "*" preceding an element indicates repetition. + The full form is: + + *element + + indicating at least and at most occurrences of element. + Default values are 0 and infinity so that "*(element)" allows any + number, including zero; "1*element" requires at least one; and + "1*2element" allows one or two. + + 2.5. [RULE]: OPTIONAL + + Square brackets enclose optional elements; "[foo bar]" is + equivalent to "*1(foo bar)". + + 2.6. NRULE: SPECIFIC REPETITION + + "(element)" is equivalent to "*(element)"; that is, + exactly occurrences of (element). Thus 2DIGIT is a 2-digit + number, and 3ALPHA is a string of three alphabetic characters. + + + August 13, 1982 - 3 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 2.7. #RULE: LISTS + + A construct "#" is defined, similar to "*", as follows: + + #element + + indicating at least and at most elements, each separated + by one or more commas (","). This makes the usual form of lists + very easy; a rule such as '(element *("," element))' can be shown + as "1#element". Wherever this construct is used, null elements + are allowed, but do not contribute to the count of elements + present. That is, "(element),,(element)" is permitted, but + counts as only two elements. Therefore, where at least one ele- + ment is required, at least one non-null element must be present. + Default values are 0 and infinity so that "#(element)" allows any + number, including zero; "1#element" requires at least one; and + "1#2element" allows one or two. + + 2.8. ; COMMENTS + + A semi-colon, set off some distance to the right of rule + text, starts a comment that continues to the end of line. This + is a simple way of including useful notes in parallel with the + specifications. + + + + + + + + + + + + + + + + + + + + + + + + + + + + August 13, 1982 - 4 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 3. LEXICAL ANALYSIS OF MESSAGES + + 3.1. GENERAL DESCRIPTION + + A message consists of header fields and, optionally, a body. + The body is simply a sequence of lines containing ASCII charac- + ters. It is separated from the headers by a null line (i.e., a + line with nothing preceding the CRLF). + + 3.1.1. LONG HEADER FIELDS + + Each header field can be viewed as a single, logical line of + ASCII characters, comprising a field-name and a field-body. + For convenience, the field-body portion of this conceptual + entity can be split into a multiple-line representation; this + is called "folding". The general rule is that wherever there + may be linear-white-space (NOT simply LWSP-chars), a CRLF + immediately followed by AT LEAST one LWSP-char may instead be + inserted. Thus, the single line + + To: "Joe & J. Harvey" , JJV @ BBN + + can be represented as: + + To: "Joe & J. Harvey" , + JJV@BBN + + and + + To: "Joe & J. Harvey" + , JJV + @BBN + + and + + To: "Joe & + J. Harvey" , JJV @ BBN + + The process of moving from this folded multiple-line + representation of a header field to its single line represen- + tation is called "unfolding". Unfolding is accomplished by + regarding CRLF immediately followed by a LWSP-char as + equivalent to the LWSP-char. + + Note: While the standard permits folding wherever linear- + white-space is permitted, it is recommended that struc- + tured fields, such as those containing addresses, limit + folding to higher-level syntactic breaks. For address + fields, it is recommended that such folding occur + + + August 13, 1982 - 5 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + between addresses, after the separating comma. + + 3.1.2. STRUCTURE OF HEADER FIELDS + + Once a field has been unfolded, it may be viewed as being com- + posed of a field-name followed by a colon (":"), followed by a + field-body, and terminated by a carriage-return/line-feed. + The field-name must be composed of printable ASCII characters + (i.e., characters that have values between 33. and 126., + decimal, except colon). The field-body may be composed of any + ASCII characters, except CR or LF. (While CR and/or LF may be + present in the actual text, they are removed by the action of + unfolding the field.) + + Certain field-bodies of headers may be interpreted according + to an internal syntax that some systems may wish to parse. + These fields are called "structured fields". Examples + include fields containing dates and addresses. Other fields, + such as "Subject" and "Comments", are regarded simply as + strings of text. + + Note: Any field which has a field-body that is defined as + other than simply is to be treated as a struc- + tured field. + + Field-names, unstructured field bodies and structured + field bodies each are scanned by their own, independent + "lexical" analyzers. + + 3.1.3. UNSTRUCTURED FIELD BODIES + + For some fields, such as "Subject" and "Comments", no struc- + turing is assumed, and they are treated simply as s, as + in the message body. Rules of folding apply to these fields, + so that such field bodies which occupy several lines must + therefore have the second and successive lines indented by at + least one LWSP-char. + + 3.1.4. STRUCTURED FIELD BODIES + + To aid in the creation and reading of structured fields, the + free insertion of linear-white-space (which permits folding + by inclusion of CRLFs) is allowed between lexical tokens. + Rather than obscuring the syntax specifications for these + structured fields with explicit syntax for this linear-white- + space, the existence of another "lexical" analyzer is assumed. + This analyzer does not apply for unstructured field bodies + that are simply strings of text, as described above. The + analyzer provides an interpretation of the unfolded text + + + August 13, 1982 - 6 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + composing the body of the field as a sequence of lexical sym- + bols. + + These symbols are: + + - individual special characters + - quoted-strings + - domain-literals + - comments + - atoms + + The first four of these symbols are self-delimiting. Atoms + are not; they are delimited by the self-delimiting symbols and + by linear-white-space. For the purposes of regenerating + sequences of atoms and quoted-strings, exactly one SPACE is + assumed to exist, and should be used, between them. (Also, in + the "Clarifications" section on "White Space", below, note the + rules about treatment of multiple contiguous LWSP-chars.) + + So, for example, the folded body of an address field + + ":sysmail"@ Some-Group. Some-Org, + Muhammed.(I am the greatest) Ali @(the)Vegas.WBA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + August 13, 1982 - 7 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + is analyzed into the following lexical symbols and types: + + :sysmail quoted string + @ special + Some-Group atom + . special + Some-Org atom + , special + Muhammed atom + . special + (I am the greatest) comment + Ali atom + @ atom + (the) comment + Vegas atom + . special + WBA atom + + The canonical representations for the data in these addresses + are the following strings: + + ":sysmail"@Some-Group.Some-Org + + and + + Muhammed.Ali@Vegas.WBA + + Note: For purposes of display, and when passing such struc- + tured information to other systems, such as mail proto- + col services, there must be NO linear-white-space + between s that are separated by period (".") or + at-sign ("@") and exactly one SPACE between all other + s. Also, headers should be in a folded form. + + + + + + + + + + + + + + + + + + + August 13, 1982 - 8 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 3.2. HEADER FIELD DEFINITIONS + + These rules show a field meta-syntax, without regard for the + particular type or internal syntax. Their purpose is to permit + detection of fields; also, they present to higher-level parsers + an image of each field as fitting on one line. + + field = field-name ":" [ field-body ] CRLF + + field-name = 1* + + field-body = field-body-contents + [CRLF LWSP-char field-body] + + field-body-contents = + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + August 13, 1982 - 9 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 3.3. LEXICAL TOKENS + + The following rules are used to define an underlying lexical + analyzer, which feeds tokens to higher level parsers. See the + ANSI references, in the Bibliography. + + ; ( Octal, Decimal.) + CHAR = ; ( 0-177, 0.-127.) + ALPHA = + ; (101-132, 65.- 90.) + ; (141-172, 97.-122.) + DIGIT = ; ( 60- 71, 48.- 57.) + CTL = ; ( 177, 127.) + CR = ; ( 15, 13.) + LF = ; ( 12, 10.) + SPACE = ; ( 40, 32.) + HTAB = ; ( 11, 9.) + <"> = ; ( 42, 34.) + CRLF = CR LF + + LWSP-char = SPACE / HTAB ; semantics = SPACE + + linear-white-space = 1*([CRLF] LWSP-char) ; semantics = SPACE + ; CRLF => folding + + specials = "(" / ")" / "<" / ">" / "@" ; Must be in quoted- + / "," / ";" / ":" / "\" / <"> ; string, to use + / "." / "[" / "]" ; within a word. + + delimiters = specials / linear-white-space / comment + + text = atoms, specials, + CR & bare LF, but NOT ; comments and + including CRLF> ; quoted-strings are + ; NOT recognized. + + atom = 1* + + quoted-string = <"> *(qtext/quoted-pair) <">; Regular qtext or + ; quoted chars. + + qtext = , ; => may be folded + "\" & CR, and including + linear-white-space> + + domain-literal = "[" *(dtext / quoted-pair) "]" + + + + + August 13, 1982 - 10 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + dtext = may be folded + "]", "\" & CR, & including + linear-white-space> + + comment = "(" *(ctext / quoted-pair / comment) ")" + + ctext = may be folded + ")", "\" & CR, & including + linear-white-space> + + quoted-pair = "\" CHAR ; may quote any char + + phrase = 1*word ; Sequence of words + + word = atom / quoted-string + + + 3.4. CLARIFICATIONS + + 3.4.1. QUOTING + + Some characters are reserved for special interpretation, such + as delimiting lexical tokens. To permit use of these charac- + ters as uninterpreted data, a quoting mechanism is provided. + To quote a character, precede it with a backslash ("\"). + + This mechanism is not fully general. Characters may be quoted + only within a subset of the lexical constructs. In particu- + lar, quoting is limited to use within: + + - quoted-string + - domain-literal + - comment + + Within these constructs, quoting is REQUIRED for CR and "\" + and for the character(s) that delimit the token (e.g., "(" and + ")" for a comment). However, quoting is PERMITTED for any + character. + + Note: In particular, quoting is NOT permitted within atoms. + For example when the local-part of an addr-spec must + contain a special character, a quoted string must be + used. Therefore, a specification such as: + + Full\ Name@Domain + + is not legal and must be specified as: + + "Full Name"@Domain + + + August 13, 1982 - 11 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 3.4.2. WHITE SPACE + + Note: In structured field bodies, multiple linear space ASCII + characters (namely HTABs and SPACEs) are treated as + single spaces and may freely surround any symbol. In + all header fields, the only place in which at least one + LWSP-char is REQUIRED is at the beginning of continua- + tion lines in a folded field. + + When passing text to processes that do not interpret text + according to this standard (e.g., mail protocol servers), then + NO linear-white-space characters should occur between a period + (".") or at-sign ("@") and a . Exactly ONE SPACE should + be used in place of arbitrary linear-white-space and comment + sequences. + + Note: Within systems conforming to this standard, wherever a + member of the list of delimiters is allowed, LWSP-chars + may also occur before and/or after it. + + Writers of mail-sending (i.e., header-generating) programs + should realize that there is no network-wide definition of the + effect of ASCII HT (horizontal-tab) characters on the appear- + ance of text at another network host; therefore, the use of + tabs in message headers, though permitted, is discouraged. + + 3.4.3. COMMENTS + + A comment is a set of ASCII characters, which is enclosed in + matching parentheses and which is not within a quoted-string + The comment construct permits message originators to add text + which will be useful for human readers, but which will be + ignored by the formal semantics. Comments should be retained + while the message is subject to interpretation according to + this standard. However, comments must NOT be included in + other cases, such as during protocol exchanges with mail + servers. + + Comments nest, so that if an unquoted left parenthesis occurs + in a comment string, there must also be a matching right + parenthesis. When a comment acts as the delimiter between a + sequence of two lexical symbols, such as two atoms, it is lex- + ically equivalent with a single SPACE, for the purposes of + regenerating the sequence, such as when passing the sequence + onto a mail protocol server. Comments are detected as such + only within field-bodies of structured fields. + + If a comment is to be "folded" onto multiple lines, then the + syntax for folding must be adhered to. (See the "Lexical + + + August 13, 1982 - 12 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + Analysis of Messages" section on "Folding Long Header Fields" + above, and the section on "Case Independence" below.) Note + that the official semantics therefore do not "see" any + unquoted CRLFs that are in comments, although particular pars- + ing programs may wish to note their presence. For these pro- + grams, it would be reasonable to interpret a "CRLF LWSP-char" + as being a CRLF that is part of the comment; i.e., the CRLF is + kept and the LWSP-char is discarded. Quoted CRLFs (i.e., a + backslash followed by a CR followed by a LF) still must be + followed by at least one LWSP-char. + + 3.4.4. DELIMITING AND QUOTING CHARACTERS + + The quote character (backslash) and characters that delimit + syntactic units are not, generally, to be taken as data that + are part of the delimited or quoted unit(s). In particular, + the quotation-marks that define a quoted-string, the + parentheses that define a comment and the backslash that + quotes a following character are NOT part of the quoted- + string, comment or quoted character. A quotation-mark that is + to be part of a quoted-string, a parenthesis that is to be + part of a comment and a backslash that is to be part of either + must each be preceded by the quote-character backslash ("\"). + Note that the syntax allows any character to be quoted within + a quoted-string or comment; however only certain characters + MUST be quoted to be included as data. These characters are + the ones that are not part of the alternate text group (i.e., + ctext or qtext). + + The one exception to this rule is that a single SPACE is + assumed to exist between contiguous words in a phrase, and + this interpretation is independent of the actual number of + LWSP-chars that the creator places between the words. To + include more than one SPACE, the creator must make the LWSP- + chars be part of a quoted-string. + + Quotation marks that delimit a quoted string and backslashes + that quote the following character should NOT accompany the + quoted-string when the string is passed to processes that do + not interpret data according to this specification (e.g., mail + protocol servers). + + 3.4.5. QUOTED-STRINGS + + Where permitted (i.e., in words in structured fields) quoted- + strings are treated as a single symbol. That is, a quoted- + string is equivalent to an atom, syntactically. If a quoted- + string is to be "folded" onto multiple lines, then the syntax + for folding must be adhered to. (See the "Lexical Analysis of + + + August 13, 1982 - 13 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + Messages" section on "Folding Long Header Fields" above, and + the section on "Case Independence" below.) Therefore, the + official semantics do not "see" any bare CRLFs that are in + quoted-strings; however particular parsing programs may wish + to note their presence. For such programs, it would be rea- + sonable to interpret a "CRLF LWSP-char" as being a CRLF which + is part of the quoted-string; i.e., the CRLF is kept and the + LWSP-char is discarded. Quoted CRLFs (i.e., a backslash fol- + lowed by a CR followed by a LF) are also subject to rules of + folding, but the presence of the quoting character (backslash) + explicitly indicates that the CRLF is data to the quoted + string. Stripping off the first following LWSP-char is also + appropriate when parsing quoted CRLFs. + + 3.4.6. BRACKETING CHARACTERS + + There is one type of bracket which must occur in matched pairs + and may have pairs nested within each other: + + o Parentheses ("(" and ")") are used to indicate com- + ments. + + There are three types of brackets which must occur in matched + pairs, and which may NOT be nested: + + o Colon/semi-colon (":" and ";") are used in address + specifications to indicate that the included list of + addresses are to be treated as a group. + + o Angle brackets ("<" and ">") are generally used to + indicate the presence of a one machine-usable refer- + ence (e.g., delimiting mailboxes), possibly including + source-routing to the machine. + + o Square brackets ("[" and "]") are used to indicate the + presence of a domain-literal, which the appropriate + name-domain is to use directly, bypassing normal + name-resolution mechanisms. + + 3.4.7. CASE INDEPENDENCE + + Except as noted, alphabetic strings may be represented in any + combination of upper and lower case. The only syntactic units + + + + + + + + + August 13, 1982 - 14 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + which requires preservation of case information are: + + - text + - qtext + - dtext + - ctext + - quoted-pair + - local-part, except "Postmaster" + + When matching any other syntactic unit, case is to be ignored. + For example, the field-names "From", "FROM", "from", and even + "FroM" are semantically equal and should all be treated ident- + ically. + + When generating these units, any mix of upper and lower case + alphabetic characters may be used. The case shown in this + specification is suggested for message-creating processes. + + Note: The reserved local-part address unit, "Postmaster", is + an exception. When the value "Postmaster" is being + interpreted, it must be accepted in any mixture of + case, including "POSTMASTER", and "postmaster". + + 3.4.8. FOLDING LONG HEADER FIELDS + + Each header field may be represented on exactly one line con- + sisting of the name of the field and its body, and terminated + by a CRLF; this is what the parser sees. For readability, the + field-body portion of long header fields may be "folded" onto + multiple lines of the actual field. "Long" is commonly inter- + preted to mean greater than 65 or 72 characters. The former + length serves as a limit, when the message is to be viewed on + most simple terminals which use simple display software; how- + ever, the limit is not imposed by this standard. + + Note: Some display software often can selectively fold lines, + to suit the display terminal. In such cases, sender- + provided folding can interfere with the display + software. + + 3.4.9. BACKSPACE CHARACTERS + + ASCII BS characters (Backspace, decimal 8) may be included in + texts and quoted-strings to effect overstriking. However, any + use of backspaces which effects an overstrike to the left of + the beginning of the text or quoted-string is prohibited. + + + + + + August 13, 1982 - 15 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 3.4.10. NETWORK-SPECIFIC TRANSFORMATIONS + + During transmission through heterogeneous networks, it may be + necessary to force data to conform to a network's local con- + ventions. For example, it may be required that a CR be fol- + lowed either by LF, making a CRLF, or by , if the CR is + to stand alone). Such transformations are reversed, when the + message exits that network. + + When crossing network boundaries, the message should be + treated as passing through two modules. It will enter the + first module containing whatever network-specific transforma- + tions that were necessary to permit migration through the + "current" network. It then passes through the modules: + + o Transformation Reversal + + The "current" network's idiosyncracies are removed and + the message is returned to the canonical form speci- + fied in this standard. + + o Transformation + + The "next" network's local idiosyncracies are imposed + on the message. + + ------------------ + From ==> | Remove Net-A | + Net-A | idiosyncracies | + ------------------ + || + \/ + Conformance + with standard + || + \/ + ------------------ + | Impose Net-B | ==> To + | idiosyncracies | Net-B + ------------------ + + + + + + + + + + + + August 13, 1982 - 16 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 4. MESSAGE SPECIFICATION + + 4.1. SYNTAX + + Note: Due to an artifact of the notational conventions, the syn- + tax indicates that, when present, some fields, must be in + a particular order. Header fields are NOT required to + occur in any particular order, except that the message + body must occur AFTER the headers. It is recommended + that, if present, headers be sent in the order "Return- + Path", "Received", "Date", "From", "Subject", "Sender", + "To", "cc", etc. + + This specification permits multiple occurrences of most + fields. Except as noted, their interpretation is not + specified here, and their use is discouraged. + + The following syntax for the bodies of various fields should + be thought of as describing each field body as a single long + string (or line). The "Lexical Analysis of Message" section on + "Long Header Fields", above, indicates how such long strings can + be represented on more than one line in the actual transmitted + message. + + message = fields *( CRLF *text ) ; Everything after + ; first null line + ; is message body + + fields = dates ; Creation time, + source ; author id & one + 1*destination ; address required + *optional-field ; others optional + + source = [ trace ] ; net traversals + originator ; original mail + [ resent ] ; forwarded + + trace = return ; path to sender + 1*received ; receipt tags + + return = "Return-path" ":" route-addr ; return address + + received = "Received" ":" ; one per relay + ["from" domain] ; sending host + ["by" domain] ; receiving host + ["via" atom] ; physical path + *("with" atom) ; link/mail protocol + ["id" msg-id] ; receiver msg id + ["for" addr-spec] ; initial form + + + August 13, 1982 - 17 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + ";" date-time ; time received + + originator = authentic ; authenticated addr + [ "Reply-To" ":" 1#address] ) + + authentic = "From" ":" mailbox ; Single author + / ( "Sender" ":" mailbox ; Actual submittor + "From" ":" 1#mailbox) ; Multiple authors + ; or not sender + + resent = resent-authentic + [ "Resent-Reply-To" ":" 1#address] ) + + resent-authentic = + = "Resent-From" ":" mailbox + / ( "Resent-Sender" ":" mailbox + "Resent-From" ":" 1#mailbox ) + + dates = orig-date ; Original + [ resent-date ] ; Forwarded + + orig-date = "Date" ":" date-time + + resent-date = "Resent-Date" ":" date-time + + destination = "To" ":" 1#address ; Primary + / "Resent-To" ":" 1#address + / "cc" ":" 1#address ; Secondary + / "Resent-cc" ":" 1#address + / "bcc" ":" #address ; Blind carbon + / "Resent-bcc" ":" #address + + optional-field = + / "Message-ID" ":" msg-id + / "Resent-Message-ID" ":" msg-id + / "In-Reply-To" ":" *(phrase / msg-id) + / "References" ":" *(phrase / msg-id) + / "Keywords" ":" #phrase + / "Subject" ":" *text + / "Comments" ":" *text + / "Encrypted" ":" 1#2word + / extension-field ; To be defined + / user-defined-field ; May be pre-empted + + msg-id = "<" addr-spec ">" ; Unique message id + + + + + + + August 13, 1982 - 18 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + extension-field = + + + user-defined-field = + + + 4.2. FORWARDING + + Some systems permit mail recipients to forward a message, + retaining the original headers, by adding some new fields. This + standard supports such a service, through the "Resent-" prefix to + field names. + + Whenever the string "Resent-" begins a field name, the field + has the same semantics as a field whose name does not have the + prefix. However, the message is assumed to have been forwarded + by an original recipient who attached the "Resent-" field. This + new field is treated as being more recent than the equivalent, + original field. For example, the "Resent-From", indicates the + person that forwarded the message, whereas the "From" field indi- + cates the original author. + + Use of such precedence information depends upon partici- + pants' communication needs. For example, this standard does not + dictate when a "Resent-From:" address should receive replies, in + lieu of sending them to the "From:" address. + + Note: In general, the "Resent-" fields should be treated as con- + taining a set of information that is independent of the + set of original fields. Information for one set should + not automatically be taken from the other. The interpre- + tation of multiple "Resent-" fields, of the same type, is + undefined. + + In the remainder of this specification, occurrence of legal + "Resent-" fields are treated identically with the occurrence of + + + + + + + + + August 13, 1982 - 19 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + fields whose names do not contain this prefix. + + 4.3. TRACE FIELDS + + Trace information is used to provide an audit trail of mes- + sage handling. In addition, it indicates a route back to the + sender of the message. + + The list of known "via" and "with" values are registered + with the Network Information Center, SRI International, Menlo + Park, California. + + 4.3.1. RETURN-PATH + + This field is added by the final transport system that + delivers the message to its recipient. The field is intended + to contain definitive information about the address and route + back to the message's originator. + + Note: The "Reply-To" field is added by the originator and + serves to direct replies, whereas the "Return-Path" + field is used to identify a path back to the origina- + tor. + + While the syntax indicates that a route specification is + optional, every attempt should be made to provide that infor- + mation in this field. + + 4.3.2. RECEIVED + + A copy of this field is added by each transport service that + relays the message. The information in the field can be quite + useful for tracing transport problems. + + The names of the sending and receiving hosts and time-of- + receipt may be specified. The "via" parameter may be used, to + indicate what physical mechanism the message was sent over, + such as Arpanet or Phonenet, and the "with" parameter may be + used to indicate the mail-, or connection-, level protocol + that was used, such as the SMTP mail protocol, or X.25 tran- + sport protocol. + + Note: Several "with" parameters may be included, to fully + specify the set of protocols that were used. + + Some transport services queue mail; the internal message iden- + tifier that is assigned to the message may be noted, using the + "id" parameter. When the sending host uses a destination + address specification that the receiving host reinterprets, by + + + August 13, 1982 - 20 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + expansion or transformation, the receiving host may wish to + record the original specification, using the "for" parameter. + For example, when a copy of mail is sent to the member of a + distribution list, this parameter may be used to record the + original address that was used to specify the list. + + 4.4. ORIGINATOR FIELDS + + The standard allows only a subset of the combinations possi- + ble with the From, Sender, Reply-To, Resent-From, Resent-Sender, + and Resent-Reply-To fields. The limitation is intentional. + + 4.4.1. FROM / RESENT-FROM + + This field contains the identity of the person(s) who wished + this message to be sent. The message-creation process should + default this field to be a single, authenticated machine + address, indicating the AGENT (person, system or process) + entering the message. If this is not done, the "Sender" field + MUST be present. If the "From" field IS defaulted this way, + the "Sender" field is optional and is redundant with the + "From" field. In all cases, addresses in the "From" field + must be machine-usable (addr-specs) and may not contain named + lists (groups). + + 4.4.2. SENDER / RESENT-SENDER + + This field contains the authenticated identity of the AGENT + (person, system or process) that sends the message. It is + intended for use when the sender is not the author of the mes- + sage, or to indicate who among a group of authors actually + sent the message. If the contents of the "Sender" field would + be completely redundant with the "From" field, then the + "Sender" field need not be present and its use is discouraged + (though still legal). In particular, the "Sender" field MUST + be present if it is NOT the same as the "From" Field. + + The Sender mailbox specification includes a word sequence + which must correspond to a specific agent (i.e., a human user + or a computer program) rather than a standard address. This + indicates the expectation that the field will identify the + single AGENT (person, system, or process) responsible for + sending the mail and not simply include the name of a mailbox + from which the mail was sent. For example in the case of a + shared login name, the name, by itself, would not be adequate. + The local-part address unit, which refers to this agent, is + expected to be a computer system term, and not (for example) a + generalized person reference which can be used outside the + network text message context. + + + August 13, 1982 - 21 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + Since the critical function served by the "Sender" field is + identification of the agent responsible for sending mail and + since computer programs cannot be held accountable for their + behavior, it is strongly recommended that when a computer pro- + gram generates a message, the HUMAN who is responsible for + that program be referenced as part of the "Sender" field mail- + box specification. + + 4.4.3. REPLY-TO / RESENT-REPLY-TO + + This field provides a general mechanism for indicating any + mailbox(es) to which responses are to be sent. Three typical + uses for this feature can be distinguished. In the first + case, the author(s) may not have regular machine-based mail- + boxes and therefore wish(es) to indicate an alternate machine + address. In the second case, an author may wish additional + persons to be made aware of, or responsible for, replies. A + somewhat different use may be of some help to "text message + teleconferencing" groups equipped with automatic distribution + services: include the address of that service in the "Reply- + To" field of all messages submitted to the teleconference; + then participants can "reply" to conference submissions to + guarantee the correct distribution of any submission of their + own. + + Note: The "Return-Path" field is added by the mail transport + service, at the time of final deliver. It is intended + to identify a path back to the orginator of the mes- + sage. The "Reply-To" field is added by the message + originator and is intended to direct replies. + + 4.4.4. AUTOMATIC USE OF FROM / SENDER / REPLY-TO + + For systems which automatically generate address lists for + replies to messages, the following recommendations are made: + + o The "Sender" field mailbox should be sent notices of + any problems in transport or delivery of the original + messages. If there is no "Sender" field, then the + "From" field mailbox should be used. + + o The "Sender" field mailbox should NEVER be used + automatically, in a recipient's reply message. + + o If the "Reply-To" field exists, then the reply should + go to the addresses indicated in that field and not to + the address(es) indicated in the "From" field. + + + + + August 13, 1982 - 22 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + o If there is a "From" field, but no "Reply-To" field, + the reply should be sent to the address(es) indicated + in the "From" field. + + Sometimes, a recipient may actually wish to communicate with + the person that initiated the message transfer. In such + cases, it is reasonable to use the "Sender" address. + + This recommendation is intended only for automated use of + originator-fields and is not intended to suggest that replies + may not also be sent to other recipients of messages. It is + up to the respective mail-handling programs to decide what + additional facilities will be provided. + + Examples are provided in Appendix A. + + 4.5. RECEIVER FIELDS + + 4.5.1. TO / RESENT-TO + + This field contains the identity of the primary recipients of + the message. + + 4.5.2. CC / RESENT-CC + + This field contains the identity of the secondary (informa- + tional) recipients of the message. + + 4.5.3. BCC / RESENT-BCC + + This field contains the identity of additional recipients of + the message. The contents of this field are not included in + copies of the message sent to the primary and secondary reci- + pients. Some systems may choose to include the text of the + "Bcc" field only in the author(s)'s copy, while others may + also include it in the text sent to all those indicated in the + "Bcc" list. + + 4.6. REFERENCE FIELDS + + 4.6.1. MESSAGE-ID / RESENT-MESSAGE-ID + + This field contains a unique identifier (the local-part + address unit) which refers to THIS version of THIS message. + The uniqueness of the message identifier is guaranteed by the + host which generates it. This identifier is intended to be + machine readable and not necessarily meaningful to humans. A + message identifier pertains to exactly one instantiation of a + particular message; subsequent revisions to the message should + + + August 13, 1982 - 23 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + each receive new message identifiers. + + 4.6.2. IN-REPLY-TO + + The contents of this field identify previous correspon- + dence which this message answers. Note that if message iden- + tifiers are used in this field, they must use the msg-id + specification format. + + 4.6.3. REFERENCES + + The contents of this field identify other correspondence + which this message references. Note that if message identif- + iers are used, they must use the msg-id specification format. + + 4.6.4. KEYWORDS + + This field contains keywords or phrases, separated by + commas. + + 4.7. OTHER FIELDS + + 4.7.1. SUBJECT + + This is intended to provide a summary, or indicate the + nature, of the message. + + 4.7.2. COMMENTS + + Permits adding text comments onto the message without + disturbing the contents of the message's body. + + 4.7.3. ENCRYPTED + + Sometimes, data encryption is used to increase the + privacy of message contents. If the body of a message has + been encrypted, to keep its contents private, the "Encrypted" + field can be used to note the fact and to indicate the nature + of the encryption. The first parameter indicates the + software used to encrypt the body, and the second, optional + is intended to aid the recipient in selecting the + proper decryption key. This code word may be viewed as an + index to a table of keys held by the recipient. + + Note: Unfortunately, headers must contain envelope, as well + as contents, information. Consequently, it is neces- + sary that they remain unencrypted, so that mail tran- + sport services may access them. Since names, + addresses, and "Subject" field contents may contain + + + August 13, 1982 - 24 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + sensitive information, this requirement limits total + message privacy. + + Names of encryption software are registered with the Net- + work Information Center, SRI International, Menlo Park, Cali- + fornia. + + 4.7.4. EXTENSION-FIELD + + A limited number of common fields have been defined in + this document. As network mail requirements dictate, addi- + tional fields may be standardized. To provide user-defined + fields with a measure of safety, in name selection, such + extension-fields will never have names that begin with the + string "X-". + + Names of Extension-fields are registered with the Network + Information Center, SRI International, Menlo Park, California. + + 4.7.5. USER-DEFINED-FIELD + + Individual users of network mail are free to define and + use additional header fields. Such fields must have names + which are not already used in the current specification or in + any definitions of extension-fields, and the overall syntax of + these user-defined-fields must conform to this specification's + rules for delimiting and folding fields. Due to the + extension-field publishing process, the name of a user- + defined-field may be pre-empted + + Note: The prefatory string "X-" will never be used in the + names of Extension-fields. This provides user-defined + fields with a protected set of names. + + + + + + + + + + + + + + + + + + + August 13, 1982 - 25 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 5. DATE AND TIME SPECIFICATION + + 5.1. SYNTAX + + date-time = [ day "," ] date time ; dd mm yy + ; hh:mm:ss zzz + + day = "Mon" / "Tue" / "Wed" / "Thu" + / "Fri" / "Sat" / "Sun" + + date = 1*2DIGIT month 2DIGIT ; day month year + ; e.g. 20 Jun 82 + + month = "Jan" / "Feb" / "Mar" / "Apr" + / "May" / "Jun" / "Jul" / "Aug" + / "Sep" / "Oct" / "Nov" / "Dec" + + time = hour zone ; ANSI and Military + + hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT] + ; 00:00:00 - 23:59:59 + + zone = "UT" / "GMT" ; Universal Time + ; North American : UT + / "EST" / "EDT" ; Eastern: - 5/ - 4 + / "CST" / "CDT" ; Central: - 6/ - 5 + / "MST" / "MDT" ; Mountain: - 7/ - 6 + / "PST" / "PDT" ; Pacific: - 8/ - 7 + / 1ALPHA ; Military: Z = UT; + ; A:-1; (J not used) + ; M:-12; N:+1; Y:+12 + / ( ("+" / "-") 4DIGIT ) ; Local differential + ; hours+min. (HHMM) + + 5.2. SEMANTICS + + If included, day-of-week must be the day implied by the date + specification. + + Time zone may be indicated in several ways. "UT" is Univer- + sal Time (formerly called "Greenwich Mean Time"); "GMT" is per- + mitted as a reference to Universal Time. The military standard + uses a single character for each zone. "Z" is Universal Time. + "A" indicates one hour earlier, and "M" indicates 12 hours ear- + lier; "N" is one hour later, and "Y" is 12 hours later. The + letter "J" is not used. The other remaining two forms are taken + from ANSI standard X3.51-1975. One allows explicit indication of + the amount of offset from UT; the other uses common 3-character + strings for indicating time zones in North America. + + + August 13, 1982 - 26 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 6. ADDRESS SPECIFICATION + + 6.1. SYNTAX + + address = mailbox ; one addressee + / group ; named list + + group = phrase ":" [#mailbox] ";" + + mailbox = addr-spec ; simple address + / phrase route-addr ; name & addr-spec + + route-addr = "<" [route] addr-spec ">" + + route = 1#("@" domain) ":" ; path-relative + + addr-spec = local-part "@" domain ; global address + + local-part = word *("." word) ; uninterpreted + ; case-preserved + + domain = sub-domain *("." sub-domain) + + sub-domain = domain-ref / domain-literal + + domain-ref = atom ; symbolic reference + + 6.2. SEMANTICS + + A mailbox receives mail. It is a conceptual entity which + does not necessarily pertain to file storage. For example, some + sites may choose to print mail on their line printer and deliver + the output to the addressee's desk. + + A mailbox specification comprises a person, system or pro- + cess name reference, a domain-dependent string, and a name-domain + reference. The name reference is optional and is usually used to + indicate the human name of a recipient. The name-domain refer- + ence specifies a sequence of sub-domains. The domain-dependent + string is uninterpreted, except by the final sub-domain; the rest + of the mail service merely transmits it as a literal string. + + 6.2.1. DOMAINS + + A name-domain is a set of registered (mail) names. A name- + domain specification resolves to a subordinate name-domain + specification or to a terminal domain-dependent string. + Hence, domain specification is extensible, permitting any + number of registration levels. + + + August 13, 1982 - 27 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + Name-domains model a global, logical, hierarchical addressing + scheme. The model is logical, in that an address specifica- + tion is related to name registration and is not necessarily + tied to transmission path. The model's hierarchy is a + directed graph, called an in-tree, such that there is a single + path from the root of the tree to any node in the hierarchy. + If more than one path actually exists, they are considered to + be different addresses. + + The root node is common to all addresses; consequently, it is + not referenced. Its children constitute "top-level" name- + domains. Usually, a service has access to its own full domain + specification and to the names of all top-level name-domains. + + The "top" of the domain addressing hierarchy -- a child of the + root -- is indicated by the right-most field, in a domain + specification. Its child is specified to the left, its child + to the left, and so on. + + Some groups provide formal registration services; these con- + stitute name-domains that are independent logically of + specific machines. In addition, networks and machines impli- + citly compose name-domains, since their membership usually is + registered in name tables. + + In the case of formal registration, an organization implements + a (distributed) data base which provides an address-to-route + mapping service for addresses of the form: + + person@registry.organization + + Note that "organization" is a logical entity, separate from + any particular communication network. + + A mechanism for accessing "organization" is universally avail- + able. That mechanism, in turn, seeks an instantiation of the + registry; its location is not indicated in the address specif- + ication. It is assumed that the system which operates under + the name "organization" knows how to find a subordinate regis- + try. The registry will then use the "person" string to deter- + mine where to send the mail specification. + + The latter, network-oriented case permits simple, direct, + attachment-related address specification, such as: + + user@host.network + + Once the network is accessed, it is expected that a message + will go directly to the host and that the host will resolve + + + August 13, 1982 - 28 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + the user name, placing the message in the user's mailbox. + + 6.2.2. ABBREVIATED DOMAIN SPECIFICATION + + Since any number of levels is possible within the domain + hierarchy, specification of a fully qualified address can + become inconvenient. This standard permits abbreviated domain + specification, in a special case: + + For the address of the sender, call the left-most + sub-domain Level N. In a header address, if all of + the sub-domains above (i.e., to the right of) Level N + are the same as those of the sender, then they do not + have to appear in the specification. Otherwise, the + address must be fully qualified. + + This feature is subject to approval by local sub- + domains. Individual sub-domains may require their + member systems, which originate mail, to provide full + domain specification only. When permitted, abbrevia- + tions may be present only while the message stays + within the sub-domain of the sender. + + Use of this mechanism requires the sender's sub-domain + to reserve the names of all top-level domains, so that + full specifications can be distinguished from abbrevi- + ated specifications. + + For example, if a sender's address is: + + sender@registry-A.registry-1.organization-X + + and one recipient's address is: + + recipient@registry-B.registry-1.organization-X + + and another's is: + + recipient@registry-C.registry-2.organization-X + + then ".registry-1.organization-X" need not be specified in the + the message, but "registry-C.registry-2" DOES have to be + specified. That is, the first two addresses may be abbrevi- + ated, but the third address must be fully specified. + + When a message crosses a domain boundary, all addresses must + be specified in the full format, ending with the top-level + name-domain in the right-most field. It is the responsibility + of mail forwarding services to ensure that addresses conform + + + August 13, 1982 - 29 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + with this requirement. In the case of abbreviated addresses, + the relaying service must make the necessary expansions. It + should be noted that it often is difficult for such a service + to locate all occurrences of address abbreviations. For exam- + ple, it will not be possible to find such abbreviations within + the body of the message. The "Return-Path" field can aid + recipients in recovering from these errors. + + Note: When passing any portion of an addr-spec onto a process + which does not interpret data according to this stan- + dard (e.g., mail protocol servers). There must be NO + LWSP-chars preceding or following the at-sign or any + delimiting period ("."), such as shown in the above + examples, and only ONE SPACE between contiguous + s. + + 6.2.3. DOMAIN TERMS + + A domain-ref must be THE official name of a registry, network, + or host. It is a symbolic reference, within a name sub- + domain. At times, it is necessary to bypass standard mechan- + isms for resolving such references, using more primitive + information, such as a network host address rather than its + associated host name. + + To permit such references, this standard provides the domain- + literal construct. Its contents must conform with the needs + of the sub-domain in which it is interpreted. + + Domain-literals which refer to domains within the ARPA Inter- + net specify 32-bit Internet addresses, in four 8-bit fields + noted in decimal, as described in Request for Comments #820, + "Assigned Numbers." For example: + + [10.0.3.19] + + Note: THE USE OF DOMAIN-LITERALS IS STRONGLY DISCOURAGED. It + is permitted only as a means of bypassing temporary + system limitations, such as name tables which are not + complete. + + The names of "top-level" domains, and the names of domains + under in the ARPA Internet, are registered with the Network + Information Center, SRI International, Menlo Park, California. + + 6.2.4. DOMAIN-DEPENDENT LOCAL STRING + + The local-part of an addr-spec in a mailbox specification + (i.e., the host's name for the mailbox) is understood to be + + + August 13, 1982 - 30 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + whatever the receiving mail protocol server allows. For exam- + ple, some systems do not understand mailbox references of the + form "P. D. Q. Bach", but others do. + + This specification treats periods (".") as lexical separators. + Hence, their presence in local-parts which are not quoted- + strings, is detected. However, such occurrences carry NO + semantics. That is, if a local-part has periods within it, an + address parser will divide the local-part into several tokens, + but the sequence of tokens will be treated as one uninter- + preted unit. The sequence will be re-assembled, when the + address is passed outside of the system such as to a mail pro- + tocol service. + + For example, the address: + + First.Last@Registry.Org + + is legal and does not require the local-part to be surrounded + with quotation-marks. (However, "First Last" DOES require + quoting.) The local-part of the address, when passed outside + of the mail system, within the Registry.Org domain, is + "First.Last", again without quotation marks. + + 6.2.5. BALANCING LOCAL-PART AND DOMAIN + + In some cases, the boundary between local-part and domain can + be flexible. The local-part may be a simple string, which is + used for the final determination of the recipient's mailbox. + All other levels of reference are, therefore, part of the + domain. + + For some systems, in the case of abbreviated reference to the + local and subordinate sub-domains, it may be possible to + specify only one reference within the domain part and place + the other, subordinate name-domain references within the + local-part. This would appear as: + + mailbox.sub1.sub2@this-domain + + Such a specification would be acceptable to address parsers + which conform to RFC #733, but do not support this newer + Internet standard. While contrary to the intent of this stan- + dard, the form is legal. + + Also, some sub-domains have a specification syntax which does + not conform to this standard. For example: + + sub-net.mailbox@sub-domain.domain + + + August 13, 1982 - 31 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + uses a different parsing sequence for local-part than for + domain. + + Note: As a rule, the domain specification should contain + fields which are encoded according to the syntax of + this standard and which contain generally-standardized + information. The local-part specification should con- + tain only that portion of the address which deviates + from the form or intention of the domain field. + + 6.2.6. MULTIPLE MAILBOXES + + An individual may have several mailboxes and wish to receive + mail at whatever mailbox is convenient for the sender to + access. This standard does not provide a means of specifying + "any member of" a list of mailboxes. + + A set of individuals may wish to receive mail as a single unit + (i.e., a distribution list). The construct permits + specification of such a list. Recipient mailboxes are speci- + fied within the bracketed part (":" - ";"). A copy of the + transmitted message is to be sent to each mailbox listed. + This standard does not permit recursive specification of + groups within groups. + + While a list must be named, it is not required that the con- + tents of the list be included. In this case, the
    + serves only as an indication of group distribution and would + appear in the form: + + name:; + + Some mail services may provide a group-list distribution + facility, accepting a single mailbox reference, expanding it + to the full distribution list, and relaying the mail to the + list's members. This standard provides no additional syntax + for indicating such a service. Using the address + alternative, while listing one mailbox in it, can mean either + that the mailbox reference will be expanded to a list or that + there is a group with one member. + + 6.2.7. EXPLICIT PATH SPECIFICATION + + At times, a message originator may wish to indicate the + transmission path that a message should follow. This is + called source routing. The normal addressing scheme, used in + an addr-spec, is carefully separated from such information; + the portion of a route-addr is provided for such occa- + sions. It specifies the sequence of hosts and/or transmission + + + August 13, 1982 - 32 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + services that are to be traversed. Both domain-refs and + domain-literals may be used. + + Note: The use of source routing is discouraged. Unless the + sender has special need of path restriction, the choice + of transmission route should be left to the mail tran- + sport service. + + 6.3. RESERVED ADDRESS + + It often is necessary to send mail to a site, without know- + ing any of its valid addresses. For example, there may be mail + system dysfunctions, or a user may wish to find out a person's + correct address, at that site. + + This standard specifies a single, reserved mailbox address + (local-part) which is to be valid at each site. Mail sent to + that address is to be routed to a person responsible for the + site's mail system or to a person with responsibility for general + site operation. The name of the reserved local-part address is: + + Postmaster + + so that "Postmaster@domain" is required to be valid. + + Note: This reserved local-part must be matched without sensi- + tivity to alphabetic case, so that "POSTMASTER", "postmas- + ter", and even "poStmASteR" is to be accepted. + + + + + + + + + + + + + + + + + + + + + + + + August 13, 1982 - 33 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 7. BIBLIOGRAPHY + + + ANSI. "USA Standard Code for Information Interchange," X3.4. + American National Standards Institute: New York (1968). Also + in: Feinler, E. and J. Postel, eds., "ARPANET Protocol Hand- + book", NIC 7104. + + ANSI. "Representations of Universal Time, Local Time Differen- + tials, and United States Time Zone References for Information + Interchange," X3.51-1975. American National Standards Insti- + tute: New York (1975). + + Bemer, R.W., "Time and the Computer." In: Interface Age (Feb. + 1979). + + Bennett, C.J. "JNT Mail Protocol". Joint Network Team, Ruther- + ford and Appleton Laboratory: Didcot, England. + + Bhushan, A.K., Pogran, K.T., Tomlinson, R.S., and White, J.E. + "Standardizing Network Mail Headers," ARPANET Request for + Comments No. 561, Network Information Center No. 18516; SRI + International: Menlo Park (September 1973). + + Birrell, A.D., Levin, R., Needham, R.M., and Schroeder, M.D. + "Grapevine: An Exercise in Distributed Computing," Communica- + tions of the ACM 25, 4 (April 1982), 260-274. + + Crocker, D.H., Vittal, J.J., Pogran, K.T., Henderson, D.A. + "Standard for the Format of ARPA Network Text Message," + ARPANET Request for Comments No. 733, Network Information + Center No. 41952. SRI International: Menlo Park (November + 1977). + + Feinler, E.J. and Postel, J.B. ARPANET Protocol Handbook, Net- + work Information Center No. 7104 (NTIS AD A003890). SRI + International: Menlo Park (April 1976). + + Harary, F. "Graph Theory". Addison-Wesley: Reading, Mass. + (1969). + + Levin, R. and Schroeder, M. "Transport of Electronic Messages + through a Network," TeleInformatics 79, pp. 29-33. North + Holland (1979). Also as Xerox Palo Alto Research Center + Technical Report CSL-79-4. + + Myer, T.H. and Henderson, D.A. "Message Transmission Protocol," + ARPANET Request for Comments, No. 680, Network Information + Center No. 32116. SRI International: Menlo Park (1975). + + + August 13, 1982 - 34 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + NBS. "Specification of Message Format for Computer Based Message + Systems, Recommended Federal Information Processing Standard." + National Bureau of Standards: Gaithersburg, Maryland + (October 1981). + + NIC. Internet Protocol Transition Workbook. Network Information + Center, SRI-International, Menlo Park, California (March + 1982). + + Oppen, D.C. and Dalal, Y.K. "The Clearinghouse: A Decentralized + Agent for Locating Named Objects in a Distributed Environ- + ment," OPD-T8103. Xerox Office Products Division: Palo Alto, + CA. (October 1981). + + Postel, J.B. "Assigned Numbers," ARPANET Request for Comments, + No. 820. SRI International: Menlo Park (August 1982). + + Postel, J.B. "Simple Mail Transfer Protocol," ARPANET Request + for Comments, No. 821. SRI International: Menlo Park (August + 1982). + + Shoch, J.F. "Internetwork naming, addressing and routing," in + Proc. 17th IEEE Computer Society International Conference, pp. + 72-79, Sept. 1978, IEEE Cat. No. 78 CH 1388-8C. + + Su, Z. and Postel, J. "The Domain Naming Convention for Internet + User Applications," ARPANET Request for Comments, No. 819. + SRI International: Menlo Park (August 1982). + + + + + + + + + + + + + + + + + + + + + + + + August 13, 1982 - 35 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + APPENDIX + + + A. EXAMPLES + + A.1. ADDRESSES + + A.1.1. Alfred Neuman + + A.1.2. Neuman@BBN-TENEXA + + These two "Alfred Neuman" examples have identical seman- + tics, as far as the operation of the local host's mail sending + (distribution) program (also sometimes called its "mailer") + and the remote host's mail protocol server are concerned. In + the first example, the "Alfred Neuman" is ignored by the + mailer, as "Neuman@BBN-TENEXA" completely specifies the reci- + pient. The second example contains no superfluous informa- + tion, and, again, "Neuman@BBN-TENEXA" is the intended reci- + pient. + + Note: When the message crosses name-domain boundaries, then + these specifications must be changed, so as to indicate + the remainder of the hierarchy, starting with the top + level. + + A.1.3. "George, Ted" + + This form might be used to indicate that a single mailbox + is shared by several users. The quoted string is ignored by + the originating host's mailer, because "Shared@Group.Arpanet" + completely specifies the destination mailbox. + + A.1.4. Wilt . (the Stilt) Chamberlain@NBA.US + + The "(the Stilt)" is a comment, which is NOT included in + the destination mailbox address handed to the originating + system's mailer. The local-part of the address is the string + "Wilt.Chamberlain", with NO space between the first and second + words. + + A.1.5. Address Lists + + Gourmets: Pompous Person , + Childs@WGBH.Boston, Galloping Gourmet@ + ANT.Down-Under (Australian National Television), + Cheapie@Discount-Liquors;, + Cruisers: Port@Portugal, Jones@SEA;, + Another@Somewhere.SomeOrg + + + August 13, 1982 - 36 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + This group list example points out the use of comments and the + mixing of addresses and groups. + + A.2. ORIGINATOR ITEMS + + A.2.1. Author-sent + + George Jones logs into his host as "Jones". He sends + mail himself. + + From: Jones@Group.Org + + or + + From: George Jones + + A.2.2. Secretary-sent + + George Jones logs in as Jones on his host. His secre- + tary, who logs in as Secy sends mail for him. Replies to the + mail should go to George. + + From: George Jones + Sender: Secy@Other-Group + + A.2.3. Secretary-sent, for user of shared directory + + George Jones' secretary sends mail for George. Replies + should go to George. + + From: George Jones + Sender: Secy@Other-Group + + Note that there need not be a space between "Jones" and the + "<", but adding a space enhances readability (as is the case + in other examples. + + A.2.4. Committee activity, with one author + + George is a member of a committee. He wishes to have any + replies to his message go to all committee members. + + From: George Jones + Sender: Jones@Host + Reply-To: The Committee: Jones@Host.Net, + Smith@Other.Org, + Doe@Somewhere-Else; + + Note that if George had not included himself in the + + + August 13, 1982 - 37 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + enumeration of The Committee, he would not have gotten an + implicit reply; the presence of the "Reply-to" field SUPER- + SEDES the sending of a reply to the person named in the "From" + field. + + A.2.5. Secretary acting as full agent of author + + George Jones asks his secretary (Secy@Host) to send a + message for him in his capacity as Group. He wants his secre- + tary to handle all replies. + + From: George Jones + Sender: Secy@Host + Reply-To: Secy@Host + + A.2.6. Agent for user without online mailbox + + A friend of George's, Sarah, is visiting. George's + secretary sends some mail to a friend of Sarah in computer- + land. Replies should go to George, whose mailbox is Jones at + Registry. + + From: Sarah Friendly + Sender: Secy-Name + Reply-To: Jones@Registry. + + A.2.7. Agent for member of a committee + + George's secretary sends out a message which was authored + jointly by all the members of a committee. Note that the name + of the committee cannot be specified, since names are + not permitted in the From field. + + From: Jones@Host, + Smith@Other-Host, + Doe@Somewhere-Else + Sender: Secy@SHost + + + + + + + + + + + + + + + August 13, 1982 - 38 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + A.3. COMPLETE HEADERS + + A.3.1. Minimum required + + Date: 26 Aug 76 1429 EDT Date: 26 Aug 76 1429 EDT + From: Jones@Registry.Org or From: Jones@Registry.Org + Bcc: To: Smith@Registry.Org + + Note that the "Bcc" field may be empty, while the "To" field + is required to have at least one address. + + A.3.2. Using some of the additional fields + + Date: 26 Aug 76 1430 EDT + From: George Jones + Sender: Secy@SHOST + To: "Al Neuman"@Mad-Host, + Sam.Irving@Other-Host + Message-ID: + + A.3.3. About as complex as you're going to get + + Date : 27 Aug 76 0932 PDT + From : Ken Davis + Subject : Re: The Syntax in the RFC + Sender : KSecy@Other-Host + Reply-To : Sam.Irving@Reg.Organization + To : George Jones , + Al.Neuman@MAD.Publisher + cc : Important folk: + Tom Softwood , + "Sam Irving"@Other-Host;, + Standard Distribution: + /main/davis/people/standard@Other-Host, + "standard.dist.3"@Tops-20-Host>; + Comment : Sam is away on business. He asked me to handle + his mail for him. He'll be able to provide a + more accurate explanation when he returns + next week. + In-Reply-To: , George's message + X-Special-action: This is a sample of user-defined field- + names. There could also be a field-name + "Special-action", but its name might later be + preempted + Message-ID: <4231.629.XYzi-What@Other-Host> + + + + + + + August 13, 1982 - 39 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + B. SIMPLE FIELD PARSING + + Some mail-reading software systems may wish to perform only + minimal processing, ignoring the internal syntax of structured + field-bodies and treating them the same as unstructured-field- + bodies. Such software will need only to distinguish: + + o Header fields from the message body, + + o Beginnings of fields from lines which continue fields, + + o Field-names from field-contents. + + The abbreviated set of syntactic rules which follows will + suffice for this purpose. It describes a limited view of mes- + sages and is a subset of the syntactic rules provided in the main + part of this specification. One small exception is that the con- + tents of field-bodies consist only of text: + + B.1. SYNTAX + + + message = *field *(CRLF *text) + + field = field-name ":" [field-body] CRLF + + field-name = 1* + + field-body = *text [CRLF LWSP-char field-body] + + + B.2. SEMANTICS + + Headers occur before the message body and are terminated by + a null line (i.e., two contiguous CRLFs). + + A line which continues a header field begins with a SPACE or + HTAB character, while a line beginning a field starts with a + printable character which is not a colon. + + A field-name consists of one or more printable characters + (excluding colon, space, and control-characters). A field-name + MUST be contained on one line. Upper and lower case are not dis- + tinguished when comparing field-names. + + + + + + + + August 13, 1982 - 40 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + C. DIFFERENCES FROM RFC #733 + + The following summarizes the differences between this stan- + dard and the one specified in Arpanet Request for Comments #733, + "Standard for the Format of ARPA Network Text Messages". The + differences are listed in the order of their occurrence in the + current specification. + + C.1. FIELD DEFINITIONS + + C.1.1. FIELD NAMES + + These now must be a sequence of printable characters. They + may not contain any LWSP-chars. + + C.2. LEXICAL TOKENS + + C.2.1. SPECIALS + + The characters period ("."), left-square bracket ("["), and + right-square bracket ("]") have been added. For presentation + purposes, and when passing a specification to a system that + does not conform to this standard, periods are to be contigu- + ous with their surrounding lexical tokens. No linear-white- + space is permitted between them. The presence of one LWSP- + char between other tokens is still directed. + + C.2.2. ATOM + + Atoms may not contain SPACE. + + C.2.3. SPECIAL TEXT + + ctext and qtext have had backslash ("\") added to the list of + prohibited characters. + + C.2.4. DOMAINS + + The lexical tokens and have been + added. + + C.3. MESSAGE SPECIFICATION + + C.3.1. TRACE + + The "Return-path:" and "Received:" fields have been specified. + + + + + + August 13, 1982 - 41 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + C.3.2. FROM + + The "From" field must contain machine-usable addresses (addr- + spec). Multiple addresses may be specified, but named-lists + (groups) may not. + + C.3.3. RESENT + + The meta-construct of prefacing field names with the string + "Resent-" has been added, to indicate that a message has been + forwarded by an intermediate recipient. + + C.3.4. DESTINATION + + A message must contain at least one destination address field. + "To" and "CC" are required to contain at least one address. + + C.3.5. IN-REPLY-TO + + The field-body is no longer a comma-separated list, although a + sequence is still permitted. + + C.3.6. REFERENCE + + The field-body is no longer a comma-separated list, although a + sequence is still permitted. + + C.3.7. ENCRYPTED + + A field has been specified that permits senders to indicate + that the body of a message has been encrypted. + + C.3.8. EXTENSION-FIELD + + Extension fields are prohibited from beginning with the char- + acters "X-". + + C.4. DATE AND TIME SPECIFICATION + + C.4.1. SIMPLIFICATION + + Fewer optional forms are permitted and the list of three- + letter time zones has been shortened. + + C.5. ADDRESS SPECIFICATION + + + + + + + August 13, 1982 - 42 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + C.5.1. ADDRESS + + The use of quoted-string, and the ":"-atom-":" construct, have + been removed. An address now is either a single mailbox + reference or is a named list of addresses. The latter indi- + cates a group distribution. + + C.5.2. GROUPS + + Group lists are now required to to have a name. Group lists + may not be nested. + + C.5.3. MAILBOX + + A mailbox specification may indicate a person's name, as + before. Such a named list no longer may specify multiple + mailboxes and may not be nested. + + C.5.4. ROUTE ADDRESSING + + Addresses now are taken to be absolute, global specifications, + independent of transmission paths. The construct has + been provided, to permit explicit specification of transmis- + sion path. RFC #733's use of multiple at-signs ("@") was + intended as a general syntax for indicating routing and/or + hierarchical addressing. The current standard separates these + specifications and only one at-sign is permitted. + + C.5.5. AT-SIGN + + The string " at " no longer is used as an address delimiter. + Only at-sign ("@") serves the function. + + C.5.6. DOMAINS + + Hierarchical, logical name-domains have been added. + + C.6. RESERVED ADDRESS + + The local-part "Postmaster" has been reserved, so that users can + be guaranteed at least one valid address at a site. + + + + + + + + + + + August 13, 1982 - 43 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + D. ALPHABETICAL LISTING OF SYNTAX RULES + + address = mailbox ; one addressee + / group ; named list + addr-spec = local-part "@" domain ; global address + ALPHA = + ; (101-132, 65.- 90.) + ; (141-172, 97.-122.) + atom = 1* + authentic = "From" ":" mailbox ; Single author + / ( "Sender" ":" mailbox ; Actual submittor + "From" ":" 1#mailbox) ; Multiple authors + ; or not sender + CHAR = ; ( 0-177, 0.-127.) + comment = "(" *(ctext / quoted-pair / comment) ")" + CR = ; ( 15, 13.) + CRLF = CR LF + ctext = may be folded + ")", "\" & CR, & including + linear-white-space> + CTL = ; ( 177, 127.) + date = 1*2DIGIT month 2DIGIT ; day month year + ; e.g. 20 Jun 82 + dates = orig-date ; Original + [ resent-date ] ; Forwarded + date-time = [ day "," ] date time ; dd mm yy + ; hh:mm:ss zzz + day = "Mon" / "Tue" / "Wed" / "Thu" + / "Fri" / "Sat" / "Sun" + delimiters = specials / linear-white-space / comment + destination = "To" ":" 1#address ; Primary + / "Resent-To" ":" 1#address + / "cc" ":" 1#address ; Secondary + / "Resent-cc" ":" 1#address + / "bcc" ":" #address ; Blind carbon + / "Resent-bcc" ":" #address + DIGIT = ; ( 60- 71, 48.- 57.) + domain = sub-domain *("." sub-domain) + domain-literal = "[" *(dtext / quoted-pair) "]" + domain-ref = atom ; symbolic reference + dtext = may be folded + "]", "\" & CR, & including + linear-white-space> + extension-field = + + + + August 13, 1982 - 44 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + field = field-name ":" [ field-body ] CRLF + fields = dates ; Creation time, + source ; author id & one + 1*destination ; address required + *optional-field ; others optional + field-body = field-body-contents + [CRLF LWSP-char field-body] + field-body-contents = + + field-name = 1* + group = phrase ":" [#mailbox] ";" + hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT] + ; 00:00:00 - 23:59:59 + HTAB = ; ( 11, 9.) + LF = ; ( 12, 10.) + linear-white-space = 1*([CRLF] LWSP-char) ; semantics = SPACE + ; CRLF => folding + local-part = word *("." word) ; uninterpreted + ; case-preserved + LWSP-char = SPACE / HTAB ; semantics = SPACE + mailbox = addr-spec ; simple address + / phrase route-addr ; name & addr-spec + message = fields *( CRLF *text ) ; Everything after + ; first null line + ; is message body + month = "Jan" / "Feb" / "Mar" / "Apr" + / "May" / "Jun" / "Jul" / "Aug" + / "Sep" / "Oct" / "Nov" / "Dec" + msg-id = "<" addr-spec ">" ; Unique message id + optional-field = + / "Message-ID" ":" msg-id + / "Resent-Message-ID" ":" msg-id + / "In-Reply-To" ":" *(phrase / msg-id) + / "References" ":" *(phrase / msg-id) + / "Keywords" ":" #phrase + / "Subject" ":" *text + / "Comments" ":" *text + / "Encrypted" ":" 1#2word + / extension-field ; To be defined + / user-defined-field ; May be pre-empted + orig-date = "Date" ":" date-time + originator = authentic ; authenticated addr + [ "Reply-To" ":" 1#address] ) + phrase = 1*word ; Sequence of words + + + + + August 13, 1982 - 45 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + qtext = , ; => may be folded + "\" & CR, and including + linear-white-space> + quoted-pair = "\" CHAR ; may quote any char + quoted-string = <"> *(qtext/quoted-pair) <">; Regular qtext or + ; quoted chars. + received = "Received" ":" ; one per relay + ["from" domain] ; sending host + ["by" domain] ; receiving host + ["via" atom] ; physical path + *("with" atom) ; link/mail protocol + ["id" msg-id] ; receiver msg id + ["for" addr-spec] ; initial form + ";" date-time ; time received + + resent = resent-authentic + [ "Resent-Reply-To" ":" 1#address] ) + resent-authentic = + = "Resent-From" ":" mailbox + / ( "Resent-Sender" ":" mailbox + "Resent-From" ":" 1#mailbox ) + resent-date = "Resent-Date" ":" date-time + return = "Return-path" ":" route-addr ; return address + route = 1#("@" domain) ":" ; path-relative + route-addr = "<" [route] addr-spec ">" + source = [ trace ] ; net traversals + originator ; original mail + [ resent ] ; forwarded + SPACE = ; ( 40, 32.) + specials = "(" / ")" / "<" / ">" / "@" ; Must be in quoted- + / "," / ";" / ":" / "\" / <"> ; string, to use + / "." / "[" / "]" ; within a word. + sub-domain = domain-ref / domain-literal + text = atoms, specials, + CR & bare LF, but NOT ; comments and + including CRLF> ; quoted-strings are + ; NOT recognized. + time = hour zone ; ANSI and Military + trace = return ; path to sender + 1*received ; receipt tags + user-defined-field = + + word = atom / quoted-string + + + + + August 13, 1982 - 46 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + zone = "UT" / "GMT" ; Universal Time + ; North American : UT + / "EST" / "EDT" ; Eastern: - 5/ - 4 + / "CST" / "CDT" ; Central: - 6/ - 5 + / "MST" / "MDT" ; Mountain: - 7/ - 6 + / "PST" / "PDT" ; Pacific: - 8/ - 7 + / 1ALPHA ; Military: Z = UT; + <"> = ; ( 42, 34.) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + August 13, 1982 - 47 - RFC #822 + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc1341.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc1341.txt new file mode 100644 index 0000000..1be6f7d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc1341.txt @@ -0,0 +1,5265 @@ + + + + + + + Network Working Group N. Borenstein, Bellcore + Request for Comments: 1341 N. Freed, Innosoft + June 1992 + + + + MIME (Multipurpose Internet Mail Extensions): + + + Mechanisms for Specifying and Describing + the Format of Internet Message Bodies + + + Status of this Memo + + This RFC specifies an IAB standards track protocol for the + Internet community, and requests discussion and suggestions + for improvements. Please refer to the current edition of + the "IAB Official Protocol Standards" for the + standardization state and status of this protocol. + Distribution of this memo is unlimited. + + Abstract + + RFC 822 defines a message representation protocol which + specifies considerable detail about message headers, but + which leaves the message content, or message body, as flat + ASCII text. This document redefines the format of message + bodies to allow multi-part textual and non-textual message + bodies to be represented and exchanged without loss of + information. This is based on earlier work documented in + RFC 934 and RFC 1049, but extends and revises that work. + Because RFC 822 said so little about message bodies, this + document is largely orthogonal to (rather than a revision + of) RFC 822. + + In particular, this document is designed to provide + facilities to include multiple objects in a single message, + to represent body text in character sets other than US- + ASCII, to represent formatted multi-font text messages, to + represent non-textual material such as images and audio + fragments, and generally to facilitate later extensions + defining new types of Internet mail for use by cooperating + mail agents. + + This document does NOT extend Internet mail header fields to + permit anything other than US-ASCII text data. It is + recognized that such extensions are necessary, and they are + the subject of a companion document [RFC -1342]. + + A table of contents appears at the end of this document. + + + + + + + Borenstein & Freed [Page i] + + + + + + + + 1 Introduction + + Since its publication in 1982, RFC 822 [RFC-822] has defined + the standard format of textual mail messages on the + Internet. Its success has been such that the RFC 822 format + has been adopted, wholly or partially, well beyond the + confines of the Internet and the Internet SMTP transport + defined by RFC 821 [RFC-821]. As the format has seen wider + use, a number of limitations have proven increasingly + restrictive for the user community. + + RFC 822 was intended to specify a format for text messages. + As such, non-text messages, such as multimedia messages that + might include audio or images, are simply not mentioned. + Even in the case of text, however, RFC 822 is inadequate for + the needs of mail users whose languages require the use of + character sets richer than US ASCII [US-ASCII]. Since RFC + 822 does not specify mechanisms for mail containing audio, + video, Asian language text, or even text in most European + languages, additional specifications are needed + + One of the notable limitations of RFC 821/822 based mail + systems is the fact that they limit the contents of + electronic mail messages to relatively short lines of + seven-bit ASCII. This forces users to convert any non- + textual data that they may wish to send into seven-bit bytes + representable as printable ASCII characters before invoking + a local mail UA (User Agent, a program with which human + users send and receive mail). Examples of such encodings + currently used in the Internet include pure hexadecimal, + uuencode, the 3-in-4 base 64 scheme specified in RFC 1113, + the Andrew Toolkit Representation [ATK], and many others. + + The limitations of RFC 822 mail become even more apparent as + gateways are designed to allow for the exchange of mail + messages between RFC 822 hosts and X.400 hosts. X.400 [X400] + specifies mechanisms for the inclusion of non-textual body + parts within electronic mail messages. The current + standards for the mapping of X.400 messages to RFC 822 + messages specify that either X.400 non-textual body parts + should be converted to (not encoded in) an ASCII format, or + that they should be discarded, notifying the RFC 822 user + that discarding has occurred. This is clearly undesirable, + as information that a user may wish to receive is lost. + Even though a user's UA may not have the capability of + dealing with the non-textual body part, the user might have + some mechanism external to the UA that can extract useful + information from the body part. Moreover, it does not allow + for the fact that the message may eventually be gatewayed + back into an X.400 message handling system (i.e., the X.400 + message is "tunneled" through Internet mail), where the + non-textual information would definitely become useful + again. + + + + + Borenstein & Freed [Page 1] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + This document describes several mechanisms that combine to + solve most of these problems without introducing any serious + incompatibilities with the existing world of RFC 822 mail. + In particular, it describes: + + 1. A MIME-Version header field, which uses a version number + to declare a message to be conformant with this + specification and allows mail processing agents to + distinguish between such messages and those generated + by older or non-conformant software, which is presumed + to lack such a field. + + 2. A Content-Type header field, generalized from RFC 1049 + [RFC-1049], which can be used to specify the type and + subtype of data in the body of a message and to fully + specify the native representation (encoding) of such + data. + + 2.a. A "text" Content-Type value, which can be used to + represent textual information in a number of + character sets and formatted text description + languages in a standardized manner. + + 2.b. A "multipart" Content-Type value, which can be + used to combine several body parts, possibly of + differing types of data, into a single message. + + 2.c. An "application" Content-Type value, which can be + used to transmit application data or binary data, + and hence, among other uses, to implement an + electronic mail file transfer service. + + 2.d. A "message" Content-Type value, for encapsulating + a mail message. + + 2.e An "image" Content-Type value, for transmitting + still image (picture) data. + + 2.f. An "audio" Content-Type value, for transmitting + audio or voice data. + + 2.g. A "video" Content-Type value, for transmitting + video or moving image data, possibly with audio as + part of the composite video data format. + + 3. A Content-Transfer-Encoding header field, which can be + used to specify an auxiliary encoding that was applied + to the data in order to allow it to pass through mail + transport mechanisms which may have data or character + set limitations. + + 4. Two optional header fields that can be used to further + describe the data in a message body, the Content-ID and + Content-Description header fields. + + + + Borenstein & Freed [Page 2] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + MIME has been carefully designed as an extensible mechanism, + and it is expected that the set of content-type/subtype + pairs and their associated parameters will grow + significantly with time. Several other MIME fields, notably + including character set names, are likely to have new values + defined over time. In order to ensure that the set of such + values is developed in an orderly, well-specified, and + public manner, MIME defines a registration process which + uses the Internet Assigned Numbers Authority (IANA) as a + central registry for such values. Appendix F provides + details about how IANA registration is accomplished. + + Finally, to specify and promote interoperability, Appendix A + of this document provides a basic applicability statement + for a subset of the above mechanisms that defines a minimal + level of "conformance" with this document. + + HISTORICAL NOTE: Several of the mechanisms described in + this document may seem somewhat strange or even baroque at + first reading. It is important to note that compatibility + with existing standards AND robustness across existing + practice were two of the highest priorities of the working + group that developed this document. In particular, + compatibility was always favored over elegance. + + 2 Notations, Conventions, and Generic BNF Grammar + + This document is being published in two versions, one as + plain ASCII text and one as PostScript. The latter is + recommended, though the textual contents are identical. An + Andrew-format copy of this document is also available from + the first author (Borenstein). + + Although the mechanisms specified in this document are all + described in prose, most are also described formally in the + modified BNF notation of RFC 822. Implementors will need to + be familiar with this notation in order to understand this + specification, and are referred to RFC 822 for a complete + explanation of the modified BNF notation. + + Some of the modified BNF in this document makes reference to + syntactic entities that are defined in RFC 822 and not in + this document. A complete formal grammar, then, is obtained + by combining the collected grammar appendix of this document + with that of RFC 822. + + The term CRLF, in this document, refers to the sequence of + the two ASCII characters CR (13) and LF (10) which, taken + together, in this order, denote a line break in RFC 822 + mail. + + The term "character set", wherever it is used in this + document, refers to a coded character set, in the sense of + ISO character set standardization work, and must not be + + + + Borenstein & Freed [Page 3] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + misinterpreted as meaning "a set of characters." + + The term "message", when not further qualified, means either + the (complete or "top-level") message being transferred on a + network, or a message encapsulated in a body of type + "message". + + The term "body part", in this document, means one of the + parts of the body of a multipart entity. A body part has a + header and a body, so it makes sense to speak about the body + of a body part. + + The term "entity", in this document, means either a message + or a body part. All kinds of entities share the property + that they have a header and a body. + + The term "body", when not further qualified, means the body + of an entity, that is the body of either a message or of a + body part. + + Note : the previous four definitions are clearly circular. + This is unavoidable, since the overal structure of a MIME + message is indeed recursive. + + In this document, all numeric and octet values are given in + decimal notation. + + It must be noted that Content-Type values, subtypes, and + parameter names as defined in this document are case- + insensitive. However, parameter values are case-sensitive + unless otherwise specified for the specific parameter. + + FORMATTING NOTE: This document has been carefully formatted + for ease of reading. The PostScript version of this + document, in particular, places notes like this one, which + may be skipped by the reader, in a smaller, italicized, + font, and indents it as well. In the text version, only the + indentation is preserved, so if you are reading the text + version of this you might consider using the PostScript + version instead. However, all such notes will be indented + and preceded by "NOTE:" or some similar introduction, even + in the text version. + + The primary purpose of these non-essential notes is to + convey information about the rationale of this document, or + to place this document in the proper historical or + evolutionary context. Such information may be skipped by + those who are focused entirely on building a compliant + implementation, but may be of use to those who wish to + understand why this document is written as it is. + + For ease of recognition, all BNF definitions have been + placed in a fixed-width font in the PostScript version of + this document. + + + + Borenstein & Freed [Page 4] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 3 The MIME-Version Header Field + + Since RFC 822 was published in 1982, there has really been + only one format standard for Internet messages, and there + has been little perceived need to declare the format + standard in use. This document is an independent document + that complements RFC 822. Although the extensions in this + document have been defined in such a way as to be compatible + with RFC 822, there are still circumstances in which it + might be desirable for a mail-processing agent to know + whether a message was composed with the new standard in + mind. + + Therefore, this document defines a new header field, "MIME- + Version", which is to be used to declare the version of the + Internet message body format standard in use. + + Messages composed in accordance with this document MUST + include such a header field, with the following verbatim + text: + + MIME-Version: 1.0 + + The presence of this header field is an assertion that the + message has been composed in compliance with this document. + + Since it is possible that a future document might extend the + message format standard again, a formal BNF is given for the + content of the MIME-Version field: + + MIME-Version := text + + Thus, future format specifiers, which might replace or + extend "1.0", are (minimally) constrained by the definition + of "text", which appears in RFC 822. + + Note that the MIME-Version header field is required at the + top level of a message. It is not required for each body + part of a multipart entity. It is required for the embedded + headers of a body of type "message" if and only if the + embedded message is itself claimed to be MIME-compliant. + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 5] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 4 The Content-Type Header Field + + The purpose of the Content-Type field is to describe the + data contained in the body fully enough that the receiving + user agent can pick an appropriate agent or mechanism to + present the data to the user, or otherwise deal with the + data in an appropriate manner. + + HISTORICAL NOTE: The Content-Type header field was first + defined in RFC 1049. RFC 1049 Content-types used a simpler + and less powerful syntax, but one that is largely compatible + with the mechanism given here. + + The Content-Type header field is used to specify the nature + of the data in the body of an entity, by giving type and + subtype identifiers, and by providing auxiliary information + that may be required for certain types. After the type and + subtype names, the remainder of the header field is simply a + set of parameters, specified in an attribute/value notation. + The set of meaningful parameters differs for the different + types. The ordering of parameters is not significant. + Among the defined parameters is a "charset" parameter by + which the character set used in the body may be declared. + Comments are allowed in accordance with RFC 822 rules for + structured header fields. + + In general, the top-level Content-Type is used to declare + the general type of data, while the subtype specifies a + specific format for that type of data. Thus, a Content-Type + of "image/xyz" is enough to tell a user agent that the data + is an image, even if the user agent has no knowledge of the + specific image format "xyz". Such information can be used, + for example, to decide whether or not to show a user the raw + data from an unrecognized subtype -- such an action might be + reasonable for unrecognized subtypes of text, but not for + unrecognized subtypes of image or audio. For this reason, + registered subtypes of audio, image, text, and video, should + not contain embedded information that is really of a + different type. Such compound types should be represented + using the "multipart" or "application" types. + + Parameters are modifiers of the content-subtype, and do not + fundamentally affect the requirements of the host system. + Although most parameters make sense only with certain + content-types, others are "global" in the sense that they + might apply to any subtype. For example, the "boundary" + parameter makes sense only for the "multipart" content-type, + but the "charset" parameter might make sense with several + content-types. + + An initial set of seven Content-Types is defined by this + document. This set of top-level names is intended to be + substantially complete. It is expected that additions to + the larger set of supported types can generally be + + + + Borenstein & Freed [Page 6] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + accomplished by the creation of new subtypes of these + initial types. In the future, more top-level types may be + defined only by an extension to this standard. If another + primary type is to be used for any reason, it must be given + a name starting with "X-" to indicate its non-standard + status and to avoid a potential conflict with a future + official name. + + In the Extended BNF notation of RFC 822, a Content-Type + header field value is defined as follows: + + Content-Type := type "/" subtype *[";" parameter] + + type := "application" / "audio" + / "image" / "message" + / "multipart" / "text" + / "video" / x-token + + x-token := + + subtype := token + + parameter := attribute "=" value + + attribute := token + + value := token / quoted-string + + token := 1* + + tspecials := "(" / ")" / "<" / ">" / "@" ; Must be in + / "," / ";" / ":" / "\" / <"> ; quoted-string, + / "/" / "[" / "]" / "?" / "." ; to use within + / "=" ; parameter values + + Note that the definition of "tspecials" is the same as the + RFC 822 definition of "specials" with the addition of the + three characters "/", "?", and "=". + + Note also that a subtype specification is MANDATORY. There + are no default subtypes. + + The type, subtype, and parameter names are not case + sensitive. For example, TEXT, Text, and TeXt are all + equivalent. Parameter values are normally case sensitive, + but certain parameters are interpreted to be case- + insensitive, depending on the intended use. (For example, + multipart boundaries are case-sensitive, but the "access- + type" for message/External-body is not case-sensitive.) + + Beyond this syntax, the only constraint on the definition of + subtype names is the desire that their uses must not + conflict. That is, it would be undesirable to have two + + + + Borenstein & Freed [Page 7] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + different communities using "Content-Type: + application/foobar" to mean two different things. The + process of defining new content-subtypes, then, is not + intended to be a mechanism for imposing restrictions, but + simply a mechanism for publicizing the usages. There are, + therefore, two acceptable mechanisms for defining new + Content-Type subtypes: + + 1. Private values (starting with "X-") may be + defined bilaterally between two cooperating + agents without outside registration or + standardization. + + 2. New standard values must be documented, + registered with, and approved by IANA, as + described in Appendix F. Where intended for + public use, the formats they refer to must + also be defined by a published specification, + and possibly offered for standardization. + + The seven standard initial predefined Content-Types are + detailed in the bulk of this document. They are: + + text -- textual information. The primary subtype, + "plain", indicates plain (unformatted) text. No + special software is required to get the full + meaning of the text, aside from support for the + indicated character set. Subtypes are to be used + for enriched text in forms where application + software may enhance the appearance of the text, + but such software must not be required in order to + get the general idea of the content. Possible + subtypes thus include any readable word processor + format. A very simple and portable subtype, + richtext, is defined in this document. + multipart -- data consisting of multiple parts of + independent data types. Four initial subtypes + are defined, including the primary "mixed" + subtype, "alternative" for representing the same + data in multiple formats, "parallel" for parts + intended to be viewed simultaneously, and "digest" + for multipart entities in which each part is of + type "message". + message -- an encapsulated message. A body of + Content-Type "message" is itself a fully formatted + RFC 822 conformant message which may contain its + own different Content-Type header field. The + primary subtype is "rfc822". The "partial" + subtype is defined for partial messages, to permit + the fragmented transmission of bodies that are + thought to be too large to be passed through mail + transport facilities. Another subtype, + "External-body", is defined for specifying large + bodies by reference to an external data source. + + + + Borenstein & Freed [Page 8] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + image -- image data. Image requires a display device + (such as a graphical display, a printer, or a FAX + machine) to view the information. Initial + subtypes are defined for two widely-used image + formats, jpeg and gif. + audio -- audio data, with initial subtype "basic". + Audio requires an audio output device (such as a + speaker or a telephone) to "display" the contents. + video -- video data. Video requires the capability to + display moving images, typically including + specialized hardware and software. The initial + subtype is "mpeg". + application -- some other kind of data, typically + either uninterpreted binary data or information to + be processed by a mail-based application. The + primary subtype, "octet-stream", is to be used in + the case of uninterpreted binary data, in which + case the simplest recommended action is to offer + to write the information into a file for the user. + Two additional subtypes, "ODA" and "PostScript", + are defined for transporting ODA and PostScript + documents in bodies. Other expected uses for + "application" include spreadsheets, data for + mail-based scheduling systems, and languages for + "active" (computational) email. (Note that active + email entails several securityconsiderations, + which are discussed later in this memo, + particularly in the context of + application/PostScript.) + + Default RFC 822 messages are typed by this protocol as plain + text in the US-ASCII character set, which can be explicitly + specified as "Content-type: text/plain; charset=us-ascii". + If no Content-Type is specified, either by error or by an + older user agent, this default is assumed. In the presence + of a MIME-Version header field, a receiving User Agent can + also assume that plain US-ASCII text was the sender's + intent. In the absence of a MIME-Version specification, + plain US-ASCII text must still be assumed, but the sender's + intent might have been otherwise. + + RATIONALE: In the absence of any Content-Type header field + or MIME-Version header field, it is impossible to be certain + that a message is actually text in the US-ASCII character + set, since it might well be a message that, using the + conventions that predate this document, includes text in + another character set or non-textual data in a manner that + cannot be automatically recognized (e.g., a uuencoded + compressed UNIX tar file). Although there is no fully + acceptable alternative to treating such untyped messages as + "text/plain; charset=us-ascii", implementors should remain + aware that if a message lacks both the MIME-Version and the + Content-Type header fields, it may in practice contain + almost anything. + + + + Borenstein & Freed [Page 9] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + It should be noted that the list of Content-Type values + given here may be augmented in time, via the mechanisms + described above, and that the set of subtypes is expected to + grow substantially. + + When a mail reader encounters mail with an unknown Content- + type value, it should generally treat it as equivalent to + "application/octet-stream", as described later in this + document. + + 5 The Content-Transfer-Encoding Header Field + + Many Content-Types which could usefully be transported via + email are represented, in their "natural" format, as 8-bit + character or binary data. Such data cannot be transmitted + over some transport protocols. For example, RFC 821 + restricts mail messages to 7-bit US-ASCII data with 1000 + character lines. + + It is necessary, therefore, to define a standard mechanism + for re-encoding such data into a 7-bit short-line format. + This document specifies that such encodings will be + indicated by a new "Content-Transfer-Encoding" header field. + The Content-Transfer-Encoding field is used to indicate the + type of transformation that has been used in order to + represent the body in an acceptable manner for transport. + + Unlike Content-Types, a proliferation of Content-Transfer- + Encoding values is undesirable and unnecessary. However, + establishing only a single Content-Transfer-Encoding + mechanism does not seem possible. There is a tradeoff + between the desire for a compact and efficient encoding of + largely-binary data and the desire for a readable encoding + of data that is mostly, but not entirely, 7-bit data. For + this reason, at least two encoding mechanisms are necessary: + a "readable" encoding and a "dense" encoding. + + The Content-Transfer-Encoding field is designed to specify + an invertible mapping between the "native" representation of + a type of data and a representation that can be readily + exchanged using 7 bit mail transport protocols, such as + those defined by RFC 821 (SMTP). This field has not been + defined by any previous standard. The field's value is a + single token specifying the type of encoding, as enumerated + below. Formally: + + Content-Transfer-Encoding := "BASE64" / "QUOTED-PRINTABLE" / + "8BIT" / "7BIT" / + "BINARY" / x-token + + These values are not case sensitive. That is, Base64 and + BASE64 and bAsE64 are all equivalent. An encoding type of + 7BIT requires that the body is already in a seven-bit mail- + ready representation. This is the default value -- that is, + + + + Borenstein & Freed [Page 10] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + "Content-Transfer-Encoding: 7BIT" is assumed if the + Content-Transfer-Encoding header field is not present. + + The values "8bit", "7bit", and "binary" all imply that NO + encoding has been performed. However, they are potentially + useful as indications of the kind of data contained in the + object, and therefore of the kind of encoding that might + need to be performed for transmission in a given transport + system. "7bit" means that the data is all represented as + short lines of US-ASCII data. "8bit" means that the lines + are short, but there may be non-ASCII characters (octets + with the high-order bit set). "Binary" means that not only + may non-ASCII characters be present, but also that the lines + are not necessarily short enough for SMTP transport. + + The difference between "8bit" (or any other conceivable + bit-width token) and the "binary" token is that "binary" + does not require adherence to any limits on line length or + to the SMTP CRLF semantics, while the bit-width tokens do + require such adherence. If the body contains data in any + bit-width other than 7-bit, the appropriate bit-width + Content-Transfer-Encoding token must be used (e.g., "8bit" + for unencoded 8 bit wide data). If the body contains binary + data, the "binary" Content-Transfer-Encoding token must be + used. + + NOTE: The distinction between the Content-Transfer-Encoding + values of "binary," "8bit," etc. may seem unimportant, in + that all of them really mean "none" -- that is, there has + been no encoding of the data for transport. However, clear + labeling will be of enormous value to gateways between + future mail transport systems with differing capabilities in + transporting data that do not meet the restrictions of RFC + 821 transport. + + As of the publication of this document, there are no + standardized Internet transports for which it is legitimate + to include unencoded 8-bit or binary data in mail bodies. + Thus there are no circumstances in which the "8bit" or + "binary" Content-Transfer-Encoding is actually legal on the + Internet. However, in the event that 8-bit or binary mail + transport becomes a reality in Internet mail, or when this + document is used in conjunction with any other 8-bit or + binary-capable transport mechanism, 8-bit or binary bodies + should be labeled as such using this mechanism. + + NOTE: The five values defined for the Content-Transfer- + Encoding field imply nothing about the Content-Type other + than the algorithm by which it was encoded or the transport + system requirements if unencoded. + + Implementors may, if necessary, define new Content- + Transfer-Encoding values, but must use an x-token, which is + a name prefixed by "X-" to indicate its non-standard status, + + + + Borenstein & Freed [Page 11] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + e.g., "Content-Transfer-Encoding: x-my-new-encoding". + However, unlike Content-Types and subtypes, the creation of + new Content-Transfer-Encoding values is explicitly and + strongly discouraged, as it seems likely to hinder + interoperability with little potential benefit. Their use + is allowed only as the result of an agreement between + cooperating user agents. + + If a Content-Transfer-Encoding header field appears as part + of a message header, it applies to the entire body of that + message. If a Content-Transfer-Encoding header field + appears as part of a body part's headers, it applies only to + the body of that body part. If an entity is of type + "multipart" or "message", the Content-Transfer-Encoding is + not permitted to have any value other than a bit width + (e.g., "7bit", "8bit", etc.) or "binary". + + It should be noted that email is character-oriented, so that + the mechanisms described here are mechanisms for encoding + arbitrary byte streams, not bit streams. If a bit stream is + to be encoded via one of these mechanisms, it must first be + converted to an 8-bit byte stream using the network standard + bit order ("big-endian"), in which the earlier bits in a + stream become the higher-order bits in a byte. A bit stream + not ending at an 8-bit boundary must be padded with zeroes. + This document provides a mechanism for noting the addition + of such padding in the case of the application Content-Type, + which has a "padding" parameter. + + The encoding mechanisms defined here explicitly encode all + data in ASCII. Thus, for example, suppose an entity has + header fields such as: + + Content-Type: text/plain; charset=ISO-8859-1 + Content-transfer-encoding: base64 + + This should be interpreted to mean that the body is a base64 + ASCII encoding of data that was originally in ISO-8859-1, + and will be in that character set again after decoding. + + The following sections will define the two standard encoding + mechanisms. The definition of new content-transfer- + encodings is explicitly discouraged and should only occur + when absolutely necessary. All content-transfer-encoding + namespace except that beginning with "X-" is explicitly + reserved to the IANA for future use. Private agreements + about content-transfer-encodings are also explicitly + discouraged. + + Certain Content-Transfer-Encoding values may only be used on + certain Content-Types. In particular, it is expressly + forbidden to use any encodings other than "7bit", "8bit", or + "binary" with any Content-Type that recursively includes + other Content-Type fields, notably the "multipart" and + + + + Borenstein & Freed [Page 12] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + "message" Content-Types. All encodings that are desired for + bodies of type multipart or message must be done at the + innermost level, by encoding the actual body that needs to + be encoded. + + NOTE ON ENCODING RESTRICTIONS: Though the prohibition + against using content-transfer-encodings on data of type + multipart or message may seem overly restrictive, it is + necessary to prevent nested encodings, in which data are + passed through an encoding algorithm multiple times, and + must be decoded multiple times in order to be properly + viewed. Nested encodings add considerable complexity to + user agents: aside from the obvious efficiency problems + with such multiple encodings, they can obscure the basic + structure of a message. In particular, they can imply that + several decoding operations are necessary simply to find out + what types of objects a message contains. Banning nested + encodings may complicate the job of certain mail gateways, + but this seems less of a problem than the effect of nested + encodings on user agents. + + NOTE ON THE RELATIONSHIP BETWEEN CONTENT-TYPE AND CONTENT- + TRANSFER-ENCODING: It may seem that the Content-Transfer- + Encoding could be inferred from the characteristics of the + Content-Type that is to be encoded, or, at the very least, + that certain Content-Transfer-Encodings could be mandated + for use with specific Content-Types. There are several + reasons why this is not the case. First, given the varying + types of transports used for mail, some encodings may be + appropriate for some Content-Type/transport combinations and + not for others. (For example, in an 8-bit transport, no + encoding would be required for text in certain character + sets, while such encodings are clearly required for 7-bit + SMTP.) Second, certain Content-Types may require different + types of transfer encoding under different circumstances. + For example, many PostScript bodies might consist entirely + of short lines of 7-bit data and hence require little or no + encoding. Other PostScript bodies (especially those using + Level 2 PostScript's binary encoding mechanism) may only be + reasonably represented using a binary transport encoding. + Finally, since Content-Type is intended to be an open-ended + specification mechanism, strict specification of an + association between Content-Types and encodings effectively + couples the specification of an application protocol with a + specific lower-level transport. This is not desirable since + the developers of a Content-Type should not have to be aware + of all the transports in use and what their limitations are. + + NOTE ON TRANSLATING ENCODINGS: The quoted-printable and + base64 encodings are designed so that conversion between + them is possible. The only issue that arises in such a + conversion is the handling of line breaks. When converting + from quoted-printable to base64 a line break must be + converted into a CRLF sequence. Similarly, a CRLF sequence + + + + Borenstein & Freed [Page 13] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + in base64 data should be converted to a quoted-printable + line break, but ONLY when converting text data. + + NOTE ON CANONICAL ENCODING MODEL: There was some + confusion, in earlier drafts of this memo, regarding the + model for when email data was to be converted to canonical + form and encoded, and in particular how this process would + affect the treatment of CRLFs, given that the representation + of newlines varies greatly from system to system. For this + reason, a canonical model for encoding is presented as + Appendix H. + + 5.1 Quoted-Printable Content-Transfer-Encoding + + The Quoted-Printable encoding is intended to represent data + that largely consists of octets that correspond to printable + characters in the ASCII character set. It encodes the data + in such a way that the resulting octets are unlikely to be + modified by mail transport. If the data being encoded are + mostly ASCII text, the encoded form of the data remains + largely recognizable by humans. A body which is entirely + ASCII may also be encoded in Quoted-Printable to ensure the + integrity of the data should the message pass through a + character-translating, and/or line-wrapping gateway. + + In this encoding, octets are to be represented as determined + by the following rules: + + Rule #1: (General 8-bit representation) Any octet, + except those indicating a line break according to the + newline convention of the canonical form of the data + being encoded, may be represented by an "=" followed by + a two digit hexadecimal representation of the octet's + value. The digits of the hexadecimal alphabet, for this + purpose, are "0123456789ABCDEF". Uppercase letters must + be + used when sending hexadecimal data, though a robust + implementation may choose to recognize lowercase + letters on receipt. Thus, for example, the value 12 + (ASCII form feed) can be represented by "=0C", and the + value 61 (ASCII EQUAL SIGN) can be represented by + "=3D". Except when the following rules allow an + alternative encoding, this rule is mandatory. + + Rule #2: (Literal representation) Octets with decimal + values of 33 through 60 inclusive, and 62 through 126, + inclusive, MAY be represented as the ASCII characters + which correspond to those octets (EXCLAMATION POINT + through LESS THAN, and GREATER THAN through TILDE, + respectively). + + Rule #3: (White Space): Octets with values of 9 and 32 + MAY be represented as ASCII TAB (HT) and SPACE + characters, respectively, but MUST NOT be so + + + + Borenstein & Freed [Page 14] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + represented at the end of an encoded line. Any TAB (HT) + or SPACE characters on an encoded line MUST thus be + followed on that line by a printable character. In + particular, an "=" at the end of an encoded line, + indicating a soft line break (see rule #5) may follow + one or more TAB (HT) or SPACE characters. It follows + that an octet with value 9 or 32 appearing at the end + of an encoded line must be represented according to + Rule #1. This rule is necessary because some MTAs + (Message Transport Agents, programs which transport + messages from one user to another, or perform a part of + such transfers) are known to pad lines of text with + SPACEs, and others are known to remove "white space" + characters from the end of a line. Therefore, when + decoding a Quoted-Printable body, any trailing white + space on a line must be deleted, as it will necessarily + have been added by intermediate transport agents. + + Rule #4 (Line Breaks): A line break in a text body + part, independent of what its representation is + following the canonical representation of the data + being encoded, must be represented by a (RFC 822) line + break, which is a CRLF sequence, in the Quoted- + Printable encoding. If isolated CRs and LFs, or LF CR + and CR LF sequences are allowed to appear in binary + data according to the canonical form, they must be + represented using the "=0D", "=0A", "=0A=0D" and + "=0D=0A" notations respectively. + + Note that many implementation may elect to encode the + local representation of various content types directly. + In particular, this may apply to plain text material on + systems that use newline conventions other than CRLF + delimiters. Such an implementation is permissible, but + the generation of line breaks must be generalized to + account for the case where alternate representations of + newline sequences are used. + + Rule #5 (Soft Line Breaks): The Quoted-Printable + encoding REQUIRES that encoded lines be no more than 76 + characters long. If longer lines are to be encoded with + the Quoted-Printable encoding, 'soft' line breaks must + be used. An equal sign as the last character on a + encoded line indicates such a non-significant ('soft') + line break in the encoded text. Thus if the "raw" form + of the line is a single unencoded line that says: + + Now's the time for all folk to come to the aid of + their country. + + This can be represented, in the Quoted-Printable + encoding, as + + + + + + Borenstein & Freed [Page 15] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Now's the time = + for all folk to come= + to the aid of their country. + + This provides a mechanism with which long lines are + encoded in such a way as to be restored by the user + agent. The 76 character limit does not count the + trailing CRLF, but counts all other characters, + including any equal signs. + + Since the hyphen character ("-") is represented as itself in + the Quoted-Printable encoding, care must be taken, when + encapsulating a quoted-printable encoded body in a multipart + entity, to ensure that the encapsulation boundary does not + appear anywhere in the encoded body. (A good strategy is to + choose a boundary that includes a character sequence such as + "=_" which can never appear in a quoted-printable body. See + the definition of multipart messages later in this + document.) + + NOTE: The quoted-printable encoding represents something of + a compromise between readability and reliability in + transport. Bodies encoded with the quoted-printable + encoding will work reliably over most mail gateways, but may + not work perfectly over a few gateways, notably those + involving translation into EBCDIC. (In theory, an EBCDIC + gateway could decode a quoted-printable body and re-encode + it using base64, but such gateways do not yet exist.) A + higher level of confidence is offered by the base64 + Content-Transfer-Encoding. A way to get reasonably reliable + transport through EBCDIC gateways is to also quote the ASCII + characters + + !"#$@[\]^`{|}~ + + according to rule #1. See Appendix B for more information. + + Because quoted-printable data is generally assumed to be + line-oriented, it is to be expected that the breaks between + the lines of quoted printable data may be altered in + transport, in the same manner that plain text mail has + always been altered in Internet mail when passing between + systems with differing newline conventions. If such + alterations are likely to constitute a corruption of the + data, it is probably more sensible to use the base64 + encoding rather than the quoted-printable encoding. + + + + + + + + + + + + Borenstein & Freed [Page 16] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 5.2 Base64 Content-Transfer-Encoding + + The Base64 Content-Transfer-Encoding is designed to + represent arbitrary sequences of octets in a form that is + not humanly readable. The encoding and decoding algorithms + are simple, but the encoded data are consistently only about + 33 percent larger than the unencoded data. This encoding is + based on the one used in Privacy Enhanced Mail applications, + as defined in RFC 1113. The base64 encoding is adapted + from RFC 1113, with one change: base64 eliminates the "*" + mechanism for embedded clear text. + + A 65-character subset of US-ASCII is used, enabling 6 bits + to be represented per printable character. (The extra 65th + character, "=", is used to signify a special processing + function.) + + NOTE: This subset has the important property that it is + represented identically in all versions of ISO 646, + including US ASCII, and all characters in the subset are + also represented identically in all versions of EBCDIC. + Other popular encodings, such as the encoding used by the + UUENCODE utility and the base85 encoding specified as part + of Level 2 PostScript, do not share these properties, and + thus do not fulfill the portability requirements a binary + transport encoding for mail must meet. + + The encoding process represents 24-bit groups of input bits + as output strings of 4 encoded characters. Proceeding from + left to right, a 24-bit input group is formed by + concatenating 3 8-bit input groups. These 24 bits are then + treated as 4 concatenated 6-bit groups, each of which is + translated into a single digit in the base64 alphabet. When + encoding a bit stream via the base64 encoding, the bit + stream must be presumed to be ordered with the most- + significant-bit first. That is, the first bit in the stream + will be the high-order bit in the first byte, and the eighth + bit will be the low-order bit in the first byte, and so on. + + Each 6-bit group is used as an index into an array of 64 + printable characters. The character referenced by the index + is placed in the output string. These characters, identified + in Table 1, below, are selected so as to be universally + representable, and the set excludes characters with + particular significance to SMTP (e.g., ".", "CR", "LF") and + to the encapsulation boundaries defined in this document + (e.g., "-"). + + + + + + + + + + + Borenstein & Freed [Page 17] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Table 1: The Base64 Alphabet + + Value Encoding Value Encoding Value Encoding Value + Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + The output stream (encoded bytes) must be represented in + lines of no more than 76 characters each. All line breaks + or other characters not found in Table 1 must be ignored by + decoding software. In base64 data, characters other than + those in Table 1, line breaks, and other white space + probably indicate a transmission error, about which a + warning message or even a message rejection might be + appropriate under some circumstances. + + Special processing is performed if fewer than 24 bits are + available at the end of the data being encoded. A full + encoding quantum is always completed at the end of a body. + When fewer than 24 input bits are available in an input + group, zero bits are added (on the right) to form an + integral number of 6-bit groups. Output character positions + which are not required to represent actual input data are + set to the character "=". Since all base64 input is an + integral number of octets, only the following cases can + arise: (1) the final quantum of encoding input is an + integral multiple of 24 bits; here, the final unit of + encoded output will be an integral multiple of 4 characters + with no "=" padding, (2) the final quantum of encoding input + is exactly 8 bits; here, the final unit of encoded output + will be two characters followed by two "=" padding + characters, or (3) the final quantum of encoding input is + exactly 16 bits; here, the final unit of encoded output will + be three characters followed by one "=" padding character. + + Care must be taken to use the proper octets for line breaks + if base64 encoding is applied directly to text material that + has not been converted to canonical form. In particular, + text line breaks should be converted into CRLF sequences + + + + Borenstein & Freed [Page 18] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + prior to base64 encoding. The important thing to note is + that this may be done directly by the encoder rather than in + a prior canonicalization step in some implementations. + + NOTE: There is no need to worry about quoting apparent + encapsulation boundaries within base64-encoded parts of + multipart entities because no hyphen characters are used in + the base64 encoding. + + 6 Additional Optional Content- Header Fields + + 6.1 Optional Content-ID Header Field + + In constructing a high-level user agent, it may be desirable + to allow one body to make reference to another. + Accordingly, bodies may be labeled using the "Content-ID" + header field, which is syntactically identical to the + "Message-ID" header field: + + Content-ID := msg-id + + Like the Message-ID values, Content-ID values must be + generated to be as unique as possible. + + 6.2 Optional Content-Description Header Field + + The ability to associate some descriptive information with a + given body is often desirable. For example, it may be useful + to mark an "image" body as "a picture of the Space Shuttle + Endeavor." Such text may be placed in the Content- + Description header field. + + Content-Description := *text + + The description is presumed to be given in the US-ASCII + character set, although the mechanism specified in [RFC- + 1342] may be used for non-US-ASCII Content-Description + values. + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 19] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 7 The Predefined Content-Type Values + + This document defines seven initial Content-Type values and + an extension mechanism for private or experimental types. + Further standard types must be defined by new published + specifications. It is expected that most innovation in new + types of mail will take place as subtypes of the seven types + defined here. The most essential characteristics of the + seven content-types are summarized in Appendix G. + + 7.1 The Text Content-Type + + The text Content-Type is intended for sending material which + is principally textual in form. It is the default Content- + Type. A "charset" parameter may be used to indicate the + character set of the body text. The primary subtype of text + is "plain". This indicates plain (unformatted) text. The + default Content-Type for Internet mail is "text/plain; + charset=us-ascii". + + Beyond plain text, there are many formats for representing + what might be known as "extended text" -- text with embedded + formatting and presentation information. An interesting + characteristic of many such representations is that they are + to some extent readable even without the software that + interprets them. It is useful, then, to distinguish them, + at the highest level, from such unreadable data as images, + audio, or text represented in an unreadable form. In the + absence of appropriate interpretation software, it is + reasonable to show subtypes of text to the user, while it is + not reasonable to do so with most nontextual data. + + Such formatted textual data should be represented using + subtypes of text. Plausible subtypes of text are typically + given by the common name of the representation format, e.g., + "text/richtext". + + 7.1.1 The charset parameter + + A critical parameter that may be specified in the Content- + Type field for text data is the character set. This is + specified with a "charset" parameter, as in: + + Content-type: text/plain; charset=us-ascii + + Unlike some other parameter values, the values of the + charset parameter are NOT case sensitive. The default + character set, which must be assumed in the absence of a + charset parameter, is US-ASCII. + + An initial list of predefined character set names can be + found at the end of this section. Additional character sets + may be registered with IANA as described in Appendix F, + although the standardization of their use requires the usual + + + + Borenstein & Freed [Page 20] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + IAB review and approval. Note that if the specified + character set includes 8-bit data, a Content-Transfer- + Encoding header field and a corresponding encoding on the + data are required in order to transmit the body via some + mail transfer protocols, such as SMTP. + + The default character set, US-ASCII, has been the subject of + some confusion and ambiguity in the past. Not only were + there some ambiguities in the definition, there have been + wide variations in practice. In order to eliminate such + ambiguity and variations in the future, it is strongly + recommended that new user agents explicitly specify a + character set via the Content-Type header field. "US-ASCII" + does not indicate an arbitrary seven-bit character code, but + specifies that the body uses character coding that uses the + exact correspondence of codes to characters specified in + ASCII. National use variations of ISO 646 [ISO-646] are NOT + ASCII and their use in Internet mail is explicitly + discouraged. The omission of the ISO 646 character set is + deliberate in this regard. The character set name of "US- + ASCII" explicitly refers to ANSI X3.4-1986 [US-ASCII] only. + The character set name "ASCII" is reserved and must not be + used for any purpose. + + NOTE: RFC 821 explicitly specifies "ASCII", and references + an earlier version of the American Standard. Insofar as one + of the purposes of specifying a Content-Type and character + set is to permit the receiver to unambiguously determine how + the sender intended the coded message to be interpreted, + assuming anything other than "strict ASCII" as the default + would risk unintentional and incompatible changes to the + semantics of messages now being transmitted. This also + implies that messages containing characters coded according + to national variations on ISO 646, or using code-switching + procedures (e.g., those of ISO 2022), as well as 8-bit or + multiple octet character encodings MUST use an appropriate + character set specification to be consistent with this + specification. + + The complete US-ASCII character set is listed in [US-ASCII]. + Note that the control characters including DEL (0-31, 127) + have no defined meaning apart from the combination CRLF + (ASCII values 13 and 10) indicating a new line. Two of the + characters have de facto meanings in wide use: FF (12) often + means "start subsequent text on the beginning of a new + page"; and TAB or HT (9) often (though not always) means + "move the cursor to the next available column after the + current position where the column number is a multiple of 8 + (counting the first column as column 0)." Apart from this, + any use of the control characters or DEL in a body must be + part of a private agreement between the sender and + recipient. Such private agreements are discouraged and + should be replaced by the other capabilities of this + document. + + + + Borenstein & Freed [Page 21] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + NOTE: Beyond US-ASCII, an enormous proliferation of + character sets is possible. It is the opinion of the IETF + working group that a large number of character sets is NOT a + good thing. We would prefer to specify a single character + set that can be used universally for representing all of the + world's languages in electronic mail. Unfortunately, + existing practice in several communities seems to point to + the continued use of multiple character sets in the near + future. For this reason, we define names for a small number + of character sets for which a strong constituent base + exists. It is our hope that ISO 10646 or some other + effort will eventually define a single world character set + which can then be specified for use in Internet mail, but in + the advance of that definition we cannot specify the use of + ISO 10646, Unicode, or any other character set whose + definition is, as of this writing, incomplete. + + The defined charset values are: + + US-ASCII -- as defined in [US-ASCII]. + + ISO-8859-X -- where "X" is to be replaced, as + necessary, for the parts of ISO-8859 [ISO- + 8859]. Note that the ISO 646 character sets + have deliberately been omitted in favor of + their 8859 replacements, which are the + designated character sets for Internet mail. + As of the publication of this document, the + legitimate values for "X" are the digits 1 + through 9. + + Note that the character set used, if anything other than + US-ASCII, must always be explicitly specified in the + Content-Type field. + + No other character set name may be used in Internet mail + without the publication of a formal specification and its + registration with IANA as described in Appendix F, or by + private agreement, in which case the character set name must + begin with "X-". + + Implementors are discouraged from defining new character + sets for mail use unless absolutely necessary. + + The "charset" parameter has been defined primarily for the + purpose of textual data, and is described in this section + for that reason. However, it is conceivable that non- + textual data might also wish to specify a charset value for + some purpose, in which case the same syntax and values + should be used. + + In general, mail-sending software should always use the + "lowest common denominator" character set possible. For + example, if a body contains only US-ASCII characters, it + + + + Borenstein & Freed [Page 22] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + should be marked as being in the US-ASCII character set, not + ISO-8859-1, which, like all the ISO-8859 family of character + sets, is a superset of US-ASCII. More generally, if a + widely-used character set is a subset of another character + set, and a body contains only characters in the widely-used + subset, it should be labeled as being in that subset. This + will increase the chances that the recipient will be able to + view the mail correctly. + + 7.1.2 The Text/plain subtype + + The primary subtype of text is "plain". This indicates + plain (unformatted) text. The default Content-Type for + Internet mail, "text/plain; charset=us-ascii", describes + existing Internet practice, that is, it is the type of body + defined by RFC 822. + + 7.1.3 The Text/richtext subtype + + In order to promote the wider interoperability of simple + formatted text, this document defines an extremely simple + subtype of "text", the "richtext" subtype. This subtype was + designed to meet the following criteria: + + 1. The syntax must be extremely simple to parse, + so that even teletype-oriented mail systems can + easily strip away the formatting information and + leave only the readable text. + + 2. The syntax must be extensible to allow for new + formatting commands that are deemed essential. + + 3. The capabilities must be extremely limited, to + ensure that it can represent no more than is + likely to be representable by the user's primary + word processor. While this limits what can be + sent, it increases the likelihood that what is + sent can be properly displayed. + + 4. The syntax must be compatible with SGML, so + that, with an appropriate DTD (Document Type + Definition, the standard mechanism for defining a + document type using SGML), a general SGML parser + could be made to parse richtext. However, despite + this compatibility, the syntax should be far + simpler than full SGML, so that no SGML knowledge + is required in order to implement it. + + The syntax of "richtext" is very simple. It is assumed, at + the top-level, to be in the US-ASCII character set, unless + of course a different charset parameter was specified in the + Content-type field. All characters represent themselves, + with the exception of the "<" character (ASCII 60), which is + used to mark the beginning of a formatting command. + + + + Borenstein & Freed [Page 23] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Formatting instructions consist of formatting commands + surrounded by angle brackets ("<>", ASCII 60 and 62). Each + formatting command may be no more than 40 characters in + length, all in US-ASCII, restricted to the alphanumeric and + hyphen ("-") characters. Formatting commands may be preceded + by a forward slash or solidus ("/", ASCII 47), making them + negations, and such negations must always exist to balance + the initial opening commands, except as noted below. Thus, + if the formatting command "" appears at some point, + there must later be a "" to balance it. There are + only three exceptions to this "balancing" rule: First, the + command "" is used to represent a literal "<" character. + Second, the command "" is used to represent a required + line break. (Otherwise, CRLFs in the data are treated as + equivalent to a single SPACE character.) Finally, the + command "" is used to represent a page break. (NOTE: + The 40 character limit on formatting commands does not + include the "<", ">", or "/" characters that might be + attached to such commands.) + + Initially defined formatting commands, not all of which will + be implemented by all richtext implementations, include: + + Bold -- causes the subsequent text to be in a bold + font. + Italic -- causes the subsequent text to be in an italic + font. + Fixed -- causes the subsequent text to be in a fixed + width font. + Smaller -- causes the subsequent text to be in a + smaller font. + Bigger -- causes the subsequent text to be in a bigger + font. + Underline -- causes the subsequent text to be + underlined. + Center -- causes the subsequent text to be centered. + FlushLeft -- causes the subsequent text to be left + justified. + FlushRight -- causes the subsequent text to be right + justified. + Indent -- causes the subsequent text to be indented at + the left margin. + IndentRight -- causes the subsequent text to be + indented at the right margin. + Outdent -- causes the subsequent text to be outdented + at the left margin. + OutdentRight -- causes the subsequent text to be + outdented at the right margin. + SamePage -- causes the subsequent text to be grouped, + if possible, on one page. + Subscript -- causes the subsequent text to be + interpreted as a subscript. + + + + + + Borenstein & Freed [Page 24] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Superscript -- causes the subsequent text to be + interpreted as a superscript. + Heading -- causes the subsequent text to be interpreted + as a page heading. + Footing -- causes the subsequent text to be interpreted + as a page footing. + ISO-8859-X (for any value of X that is legal as a + "charset" parameter) -- causes the subsequent text + to be interpreted as text in the appropriate + character set. + US-ASCII -- causes the subsequent text to be + interpreted as text in the US-ASCII character set. + Excerpt -- causes the subsequent text to be interpreted + as a textual excerpt from another source. + Typically this will be displayed using indentation + and an alternate font, but such decisions are up + to the viewer. + Paragraph -- causes the subsequent text to be + interpreted as a single paragraph, with + appropriate paragraph breaks (typically blank + space) before and after. + Signature -- causes the subsequent text to be + interpreted as a "signature". Some systems may + wish to display signatures in a smaller font or + otherwise set them apart from the main text of the + message. + Comment -- causes the subsequent text to be interpreted + as a comment, and hence not shown to the reader. + No-op -- has no effect on the subsequent text. + lt -- is replaced by a literal "<" character. No + balancing is allowed. + nl -- causes a line break. No balancing is + allowed. + np -- causes a page break. No balancing is + allowed. + + Each positive formatting command affects all subsequent text + until the matching negative formatting command. Such pairs + of formatting commands must be properly balanced and nested. + Thus, a proper way to describe text in bold italics is: + + the-text + + or, alternately, + + the-text + + but, in particular, the following is illegal + richtext: + + the-text + + NOTE: The nesting requirement for formatting commands + imposes a slightly higher burden upon the composers of + + + + Borenstein & Freed [Page 25] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + richtext bodies, but potentially simplifies richtext + displayers by allowing them to be stack-based. The main + goal of richtext is to be simple enough to make multifont, + formatted email widely readable, so that those with the + capability of sending it will be able to do so with + confidence. Thus slightly increased complexity in the + composing software was deemed a reasonable tradeoff for + simplified reading software. Nonetheless, implementors of + richtext readers are encouraged to follow the general + Internet guidelines of being conservative in what you send + and liberal in what you accept. Those implementations that + can do so are encouraged to deal reasonably with improperly + nested richtext. + + Implementations must regard any unrecognized formatting + command as equivalent to "No-op", thus facilitating future + extensions to "richtext". Private extensions may be defined + using formatting commands that begin with "X-", by analogy + to Internet mail header field names. + + It is worth noting that no special behavior is required for + the TAB (HT) character. It is recommended, however, that, at + least when fixed-width fonts are in use, the common + semantics of the TAB (HT) character should be observed, + namely that it moves to the next column position that is a + multiple of 8. (In other words, if a TAB (HT) occurs in + column n, where the leftmost column is column 0, then that + TAB (HT) should be replaced by 8-(n mod 8) SPACE + characters.) + + Richtext also differentiates between "hard" and "soft" line + breaks. A line break (CRLF) in the richtext data stream is + interpreted as a "soft" line break, one that is included + only for purposes of mail transport, and is to be treated as + white space by richtext interpreters. To include a "hard" + line break (one that must be displayed as such), the "" + or " formatting constructs should be used. In + general, a soft line break should be treated as white space, + but when soft line breaks immediately follow a or a + tag they should be ignored rather than treated + as white space. + + Putting all this together, the following "text/richtext" + body fragment: + + Now is the time for + all good men + (and women>) to + come + + to the aid of their + + + + + + + Borenstein & Freed [Page 26] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + beloved country. Stupid + quote! -- the end + + represents the following formatted text (which will, no + doubt, look cryptic in the text-only version of this + document): + + Now is the time for all good men (and ) to + come to the aid of their + beloved + + country. -- the end + + Richtext conformance: A minimal richtext implementation is + one that simply converts "" to "<", converts CRLFs to + SPACE, converts to a newline according to local newline + convention, removes everything between a command + and the next balancing command, and removes all + other formatting commands (all text enclosed in angle + brackets). + + NOTE ON THE RELATIONSHIP OF RICHTEXT TO SGML: Richtext is + decidedly not SGML, and must not be used to transport + arbitrary SGML documents. Those who wish to use SGML + document types as a mail transport format must define a new + text or application subtype, e.g., "text/sgml-dtd-whatever" + or "application/sgml-dtd-whatever", depending on the + perceived readability of the DTD in use. Richtext is + designed to be compatible with SGML, and specifically so + that it will be possible to define a richtext DTD if one is + needed. However, this does not imply that arbitrary SGML + can be called richtext, nor that richtext implementors have + any need to understand SGML; the description in this + document is a complete definition of richtext, which is far + simpler than complete SGML. + + NOTE ON THE INTENDED USE OF RICHTEXT: It is recognized that + implementors of future mail systems will want rich text + functionality far beyond that currently defined for + richtext. The intent of richtext is to provide a common + format for expressing that functionality in a form in which + much of it, at least, will be understood by interoperating + software. Thus, in particular, software with a richer + notion of formatted text than richtext can still use + richtext as its basic representation, but can extend it with + new formatting commands and by hiding information specific + to that software system in richtext comments. As such + systems evolve, it is expected that the definition of + richtext will be further refined by future published + specifications, but richtext as defined here provides a + platform on which evolutionary refinements can be based. + + IMPLEMENTATION NOTE: In some environments, it might be + impossible to combine certain richtext formatting commands, + + + + Borenstein & Freed [Page 27] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + whereas in others they might be combined easily. For + example, the combination of and might + produce bold italics on systems that support such fonts, but + there exist systems that can make text bold or italicized, + but not both. In such cases, the most recently issued + recognized formatting command should be preferred. + + One of the major goals in the design of richtext was to make + it so simple that even text-only mailers will implement + richtext-to-plain-text translators, thus increasing the + likelihood that multifont text will become "safe" to use + very widely. To demonstrate this simplicity, an extremely + simple 35-line C program that converts richtext input into + plain text output is included in Appendix D. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 28] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 7.2 The Multipart Content-Type + + In the case of multiple part messages, in which one or more + different sets of data are combined in a single body, a + "multipart" Content-Type field must appear in the entity's + header. The body must then contain one or more "body parts," + each preceded by an encapsulation boundary, and the last one + followed by a closing boundary. Each part starts with an + encapsulation boundary, and then contains a body part + consisting of header area, a blank line, and a body area. + Thus a body part is similar to an RFC 822 message in syntax, + but different in meaning. + + A body part is NOT to be interpreted as actually being an + RFC 822 message. To begin with, NO header fields are + actually required in body parts. A body part that starts + with a blank line, therefore, is allowed and is a body part + for which all default values are to be assumed. In such a + case, the absence of a Content-Type header field implies + that the encapsulation is plain US-ASCII text. The only + header fields that have defined meaning for body parts are + those the names of which begin with "Content-". All other + header fields are generally to be ignored in body parts. + Although they should generally be retained in mail + processing, they may be discarded by gateways if necessary. + Such other fields are permitted to appear in body parts but + should not be depended on. "X-" fields may be created for + experimental or private purposes, with the recognition that + the information they contain may be lost at some gateways. + + The distinction between an RFC 822 message and a body part + is subtle, but important. A gateway between Internet and + X.400 mail, for example, must be able to tell the difference + between a body part that contains an image and a body part + that contains an encapsulated message, the body of which is + an image. In order to represent the latter, the body part + must have "Content-Type: message", and its body (after the + blank line) must be the encapsulated message, with its own + "Content-Type: image" header field. The use of similar + syntax facilitates the conversion of messages to body parts, + and vice versa, but the distinction between the two must be + understood by implementors. (For the special case in which + all parts actually are messages, a "digest" subtype is also + defined.) + + As stated previously, each body part is preceded by an + encapsulation boundary. The encapsulation boundary MUST NOT + appear inside any of the encapsulated parts. Thus, it is + crucial that the composing agent be able to choose and + specify the unique boundary that will separate the parts. + + All present and future subtypes of the "multipart" type must + use an identical syntax. Subtypes may differ in their + semantics, and may impose additional restrictions on syntax, + + + + Borenstein & Freed [Page 29] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + but must conform to the required syntax for the multipart + type. This requirement ensures that all conformant user + agents will at least be able to recognize and separate the + parts of any multipart entity, even of an unrecognized + subtype. + + As stated in the definition of the Content-Transfer-Encoding + field, no encoding other than "7bit", "8bit", or "binary" is + permitted for entities of type "multipart". The multipart + delimiters and header fields are always 7-bit ASCII in any + case, and data within the body parts can be encoded on a + part-by-part basis, with Content-Transfer-Encoding fields + for each appropriate body part. + + Mail gateways, relays, and other mail handling agents are + commonly known to alter the top-level header of an RFC 822 + message. In particular, they frequently add, remove, or + reorder header fields. Such alterations are explicitly + forbidden for the body part headers embedded in the bodies + of messages of type "multipart." + + 7.2.1 Multipart: The common syntax + + All subtypes of "multipart" share a common syntax, defined + in this section. A simple example of a multipart message + also appears in this section. An example of a more complex + multipart message is given in Appendix C. + + The Content-Type field for multipart entities requires one + parameter, "boundary", which is used to specify the + encapsulation boundary. The encapsulation boundary is + defined as a line consisting entirely of two hyphen + characters ("-", decimal code 45) followed by the boundary + parameter value from the Content-Type header field. + + NOTE: The hyphens are for rough compatibility with the + earlier RFC 934 method of message encapsulation, and for + ease of searching for the boundaries in some + implementations. However, it should be noted that multipart + messages are NOT completely compatible with RFC 934 + encapsulations; in particular, they do not obey RFC 934 + quoting conventions for embedded lines that begin with + hyphens. This mechanism was chosen over the RFC 934 + mechanism because the latter causes lines to grow with each + level of quoting. The combination of this growth with the + fact that SMTP implementations sometimes wrap long lines + made the RFC 934 mechanism unsuitable for use in the event + that deeply-nested multipart structuring is ever desired. + + Thus, a typical multipart Content-Type header field might + look like this: + + Content-Type: multipart/mixed; + + + + + Borenstein & Freed [Page 30] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + boundary=gc0p4Jq0M2Yt08jU534c0p + + This indicates that the entity consists of several parts, + each itself with a structure that is syntactically identical + to an RFC 822 message, except that the header area might be + completely empty, and that the parts are each preceded by + the line + + --gc0p4Jq0M2Yt08jU534c0p + + Note that the encapsulation boundary must occur at the + beginning of a line, i.e., following a CRLF, and that that + initial CRLF is considered to be part of the encapsulation + boundary rather than part of the preceding part. The + boundary must be followed immediately either by another CRLF + and the header fields for the next part, or by two CRLFs, in + which case there are no header fields for the next part (and + it is therefore assumed to be of Content-Type text/plain). + + NOTE: The CRLF preceding the encapsulation line is + considered part of the boundary so that it is possible to + have a part that does not end with a CRLF (line break). + Body parts that must be considered to end with line breaks, + therefore, should have two CRLFs preceding the encapsulation + line, the first of which is part of the preceding body part, + and the second of which is part of the encapsulation + boundary. + + The requirement that the encapsulation boundary begins with + a CRLF implies that the body of a multipart entity must + itself begin with a CRLF before the first encapsulation line + -- that is, if the "preamble" area is not used, the entity + headers must be followed by TWO CRLFs. This is indeed how + such entities should be composed. A tolerant mail reading + program, however, may interpret a body of type multipart + that begins with an encapsulation line NOT initiated by a + CRLF as also being an encapsulation boundary, but a + compliant mail sending program must not generate such + entities. + + Encapsulation boundaries must not appear within the + encapsulations, and must be no longer than 70 characters, + not counting the two leading hyphens. + + The encapsulation boundary following the last body part is a + distinguished delimiter that indicates that no further body + parts will follow. Such a delimiter is identical to the + previous delimiters, with the addition of two more hyphens + at the end of the line: + + --gc0p4Jq0M2Yt08jU534c0p-- + + There appears to be room for additional information prior to + the first encapsulation boundary and following the final + + + + Borenstein & Freed [Page 31] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + boundary. These areas should generally be left blank, and + implementations should ignore anything that appears before + the first boundary or after the last one. + + NOTE: These "preamble" and "epilogue" areas are not used + because of the lack of proper typing of these parts and the + lack of clear semantics for handling these areas at + gateways, particularly X.400 gateways. + + NOTE: Because encapsulation boundaries must not appear in + the body parts being encapsulated, a user agent must + exercise care to choose a unique boundary. The boundary in + the example above could have been the result of an algorithm + designed to produce boundaries with a very low probability + of already existing in the data to be encapsulated without + having to prescan the data. Alternate algorithms might + result in more 'readable' boundaries for a recipient with an + old user agent, but would require more attention to the + possibility that the boundary might appear in the + encapsulated part. The simplest boundary possible is + something like "---", with a closing boundary of "-----". + + As a very simple example, the following multipart message + has two parts, both of them plain text, one of them + explicitly typed and one of them implicitly typed: + + From: Nathaniel Borenstein + To: Ned Freed + Subject: Sample message + MIME-Version: 1.0 + Content-type: multipart/mixed; boundary="simple + boundary" + + This is the preamble. It is to be ignored, though it + is a handy place for mail composers to include an + explanatory note to non-MIME compliant readers. + --simple boundary + + This is implicitly typed plain ASCII text. + It does NOT end with a linebreak. + --simple boundary + Content-type: text/plain; charset=us-ascii + + This is explicitly typed plain ASCII text. + It DOES end with a linebreak. + + --simple boundary-- + This is the epilogue. It is also to be ignored. + + The use of a Content-Type of multipart in a body part within + another multipart entity is explicitly allowed. In such + cases, for obvious reasons, care must be taken to ensure + that each nested multipart entity must use a different + boundary delimiter. See Appendix C for an example of nested + + + + Borenstein & Freed [Page 32] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + multipart entities. + + The use of the multipart Content-Type with only a single + body part may be useful in certain contexts, and is + explicitly permitted. + + The only mandatory parameter for the multipart Content-Type + is the boundary parameter, which consists of 1 to 70 + characters from a set of characters known to be very robust + through email gateways, and NOT ending with white space. + (If a boundary appears to end with white space, the white + space must be presumed to have been added by a gateway, and + should be deleted.) It is formally specified by the + following BNF: + + boundary := 0*69 bcharsnospace + + bchars := bcharsnospace / " " + + bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / "+" / + "_" + / "," / "-" / "." / "/" / ":" / "=" / "?" + + Overall, the body of a multipart entity may be specified as + follows: + + multipart-body := preamble 1*encapsulation + close-delimiter epilogue + + encapsulation := delimiter CRLF body-part + + delimiter := CRLF "--" boundary ; taken from Content-Type + field. + ; when content-type is + multipart + ; There must be no space + ; between "--" and boundary. + + close-delimiter := delimiter "--" ; Again, no space before + "--" + + preamble := *text ; to be ignored upon + receipt. + + epilogue := *text ; to be ignored upon + receipt. + + body-part = <"message" as defined in RFC 822, + with all header fields optional, and with the + specified delimiter not occurring anywhere in + the message body, either on a line by itself + or as a substring anywhere. Note that the + + + + + + Borenstein & Freed [Page 33] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + semantics of a part differ from the semantics + of a message, as described in the text.> + + NOTE: Conspicuously missing from the multipart type is a + notion of structured, related body parts. In general, it + seems premature to try to standardize interpart structure + yet. It is recommended that those wishing to provide a more + structured or integrated multipart messaging facility should + define a subtype of multipart that is syntactically + identical, but that always expects the inclusion of a + distinguished part that can be used to specify the structure + and integration of the other parts, probably referring to + them by their Content-ID field. If this approach is used, + other implementations will not recognize the new subtype, + but will treat it as the primary subtype (multipart/mixed) + and will thus be able to show the user the parts that are + recognized. + + 7.2.2 The Multipart/mixed (primary) subtype + + The primary subtype for multipart, "mixed", is intended for + use when the body parts are independent and intended to be + displayed serially. Any multipart subtypes that an + implementation does not recognize should be treated as being + of subtype "mixed". + + 7.2.3 The Multipart/alternative subtype + + The multipart/alternative type is syntactically identical to + multipart/mixed, but the semantics are different. In + particular, each of the parts is an "alternative" version of + the same information. User agents should recognize that the + content of the various parts are interchangeable. The user + agent should either choose the "best" type based on the + user's environment and preferences, or offer the user the + available alternatives. In general, choosing the best type + means displaying only the LAST part that can be displayed. + This may be used, for example, to send mail in a fancy text + format in such a way that it can easily be displayed + anywhere: + + From: Nathaniel Borenstein + To: Ned Freed + Subject: Formatted text mail + MIME-Version: 1.0 + Content-Type: multipart/alternative; boundary=boundary42 + + + --boundary42 + Content-Type: text/plain; charset=us-ascii + + ...plain text version of message goes here.... + + + + + + Borenstein & Freed [Page 34] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + --boundary42 + Content-Type: text/richtext + + .... richtext version of same message goes here ... + --boundary42 + Content-Type: text/x-whatever + + .... fanciest formatted version of same message goes here + ... + --boundary42-- + + In this example, users whose mail system understood the + "text/x-whatever" format would see only the fancy version, + while other users would see only the richtext or plain text + version, depending on the capabilities of their system. + + In general, user agents that compose multipart/alternative + entities should place the body parts in increasing order of + preference, that is, with the preferred format last. For + fancy text, the sending user agent should put the plainest + format first and the richest format last. Receiving user + agents should pick and display the last format they are + capable of displaying. In the case where one of the + alternatives is itself of type "multipart" and contains + unrecognized sub-parts, the user agent may choose either to + show that alternative, an earlier alternative, or both. + + NOTE: From an implementor's perspective, it might seem more + sensible to reverse this ordering, and have the plainest + alternative last. However, placing the plainest alternative + first is the friendliest possible option when + mutlipart/alternative entities are viewed using a non-MIME- + compliant mail reader. While this approach does impose some + burden on compliant mail readers, interoperability with + older mail readers was deemed to be more important in this + case. + + It may be the case that some user agents, if they can + recognize more than one of the formats, will prefer to offer + the user the choice of which format to view. This makes + sense, for example, if mail includes both a nicely-formatted + image version and an easily-edited text version. What is + most critical, however, is that the user not automatically + be shown multiple versions of the same data. Either the + user should be shown the last recognized version or should + explicitly be given the choice. + + + + + + + + + + + + Borenstein & Freed [Page 35] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 7.2.4 The Multipart/digest subtype + + This document defines a "digest" subtype of the multipart + Content-Type. This type is syntactically identical to + multipart/mixed, but the semantics are different. In + particular, in a digest, the default Content-Type value for + a body part is changed from "text/plain" to + "message/rfc822". This is done to allow a more readable + digest format that is largely compatible (except for the + quoting convention) with RFC 934. + + A digest in this format might, then, look something like + this: + + From: Moderator-Address + MIME-Version: 1.0 + Subject: Internet Digest, volume 42 + Content-Type: multipart/digest; + boundary="---- next message ----" + + + ------ next message ---- + + From: someone-else + Subject: my opinion + + ...body goes here ... + + ------ next message ---- + + From: someone-else-again + Subject: my different opinion + + ... another body goes here... + + ------ next message ------ + + 7.2.5 The Multipart/parallel subtype + + This document defines a "parallel" subtype of the multipart + Content-Type. This type is syntactically identical to + multipart/mixed, but the semantics are different. In + particular, in a parallel entity, all of the parts are + intended to be presented in parallel, i.e., simultaneously, + on hardware and software that are capable of doing so. + Composing agents should be aware that many mail readers will + lack this capability and will show the parts serially in any + event. + + + + + + + + + + Borenstein & Freed [Page 36] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 7.3 The Message Content-Type + + It is frequently desirable, in sending mail, to encapsulate + another mail message. For this common operation, a special + Content-Type, "message", is defined. The primary subtype, + message/rfc822, has no required parameters in the Content- + Type field. Additional subtypes, "partial" and "External- + body", do have required parameters. These subtypes are + explained below. + + NOTE: It has been suggested that subtypes of message might + be defined for forwarded or rejected messages. However, + forwarded and rejected messages can be handled as multipart + messages in which the first part contains any control or + descriptive information, and a second part, of type + message/rfc822, is the forwarded or rejected message. + Composing rejection and forwarding messages in this manner + will preserve the type information on the original message + and allow it to be correctly presented to the recipient, and + hence is strongly encouraged. + + As stated in the definition of the Content-Transfer-Encoding + field, no encoding other than "7bit", "8bit", or "binary" is + permitted for messages or parts of type "message". The + message header fields are always US-ASCII in any case, and + data within the body can still be encoded, in which case the + Content-Transfer-Encoding header field in the encapsulated + message will reflect this. Non-ASCII text in the headers of + an encapsulated message can be specified using the + mechanisms described in [RFC-1342]. + + Mail gateways, relays, and other mail handling agents are + commonly known to alter the top-level header of an RFC 822 + message. In particular, they frequently add, remove, or + reorder header fields. Such alterations are explicitly + forbidden for the encapsulated headers embedded in the + bodies of messages of type "message." + + 7.3.1 The Message/rfc822 (primary) subtype + + A Content-Type of "message/rfc822" indicates that the body + contains an encapsulated message, with the syntax of an RFC + 822 message. + + 7.3.2 The Message/Partial subtype + + A subtype of message, "partial", is defined in order to + allow large objects to be delivered as several separate + pieces of mail and automatically reassembled by the + receiving user agent. (The concept is similar to IP + fragmentation/reassembly in the basic Internet Protocols.) + This mechanism can be used when intermediate transport + agents limit the size of individual messages that can be + sent. Content-Type "message/partial" thus indicates that + + + + Borenstein & Freed [Page 37] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + the body contains a fragment of a larger message. + + Three parameters must be specified in the Content-Type field + of type message/partial: The first, "id", is a unique + identifier, as close to a world-unique identifier as + possible, to be used to match the parts together. (In + general, the identifier is essentially a message-id; if + placed in double quotes, it can be any message-id, in + accordance with the BNF for "parameter" given earlier in + this specification.) The second, "number", an integer, is + the part number, which indicates where this part fits into + the sequence of fragments. The third, "total", another + integer, is the total number of parts. This third subfield + is required on the final part, and is optional on the + earlier parts. Note also that these parameters may be given + in any order. + + Thus, part 2 of a 3-part message may have either of the + following header fields: + + Content-Type: Message/Partial; + number=2; total=3; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com"; + + Content-Type: Message/Partial; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com"; + number=2 + + But part 3 MUST specify the total number of parts: + + Content-Type: Message/Partial; + number=3; total=3; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com"; + + Note that part numbering begins with 1, not 0. + + When the parts of a message broken up in this manner are put + together, the result is a complete RFC 822 format message, + which may have its own Content-Type header field, and thus + may contain any other data type. + + Message fragmentation and reassembly: The semantics of a + reassembled partial message must be those of the "inner" + message, rather than of a message containing the inner + message. This makes it possible, for example, to send a + large audio message as several partial messages, and still + have it appear to the recipient as a simple audio message + rather than as an encapsulated message containing an audio + message. That is, the encapsulation of the message is + considered to be "transparent". + + When generating and reassembling the parts of a + message/partial message, the headers of the encapsulated + message must be merged with the headers of the enclosing + + + + Borenstein & Freed [Page 38] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + entities. In this process the following rules must be + observed: + + (1) All of the headers from the initial enclosing + entity (part one), except those that start with + "Content-" and "Message-ID", must be copied, in + order, to the new message. + + (2) Only those headers in the enclosed message + which start with "Content-" and "Message-ID" must + be appended, in order, to the headers of the new + message. Any headers in the enclosed message + which do not start with "Content-" (except for + "Message-ID") will be ignored. + + (3) All of the headers from the second and any + subsequent messages will be ignored. + + For example, if an audio message is broken into two parts, + the first part might look something like this: + + X-Weird-Header-1: Foo + From: Bill@host.com + To: joe@otherhost.com + Subject: Audio mail + Message-ID: id1@host.com + MIME-Version: 1.0 + Content-type: message/partial; + id="ABC@host.com"; + number=1; total=2 + + X-Weird-Header-1: Bar + X-Weird-Header-2: Hello + Message-ID: anotherid@foo.com + Content-type: audio/basic + Content-transfer-encoding: base64 + + ... first half of encoded audio data goes here... + + and the second half might look something like this: + + From: Bill@host.com + To: joe@otherhost.com + Subject: Audio mail + MIME-Version: 1.0 + Message-ID: id2@host.com + Content-type: message/partial; + id="ABC@host.com"; number=2; total=2 + + ... second half of encoded audio data goes here... + + Then, when the fragmented message is reassembled, the + resulting message to be displayed to the user should look + something like this: + + + + Borenstein & Freed [Page 39] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + X-Weird-Header-1: Foo + From: Bill@host.com + To: joe@otherhost.com + Subject: Audio mail + Message-ID: anotherid@foo.com + MIME-Version: 1.0 + Content-type: audio/basic + Content-transfer-encoding: base64 + + ... first half of encoded audio data goes here... + ... second half of encoded audio data goes here... + + It should be noted that, because some message transfer + agents may choose to automatically fragment large messages, + and because such agents may use different fragmentation + thresholds, it is possible that the pieces of a partial + message, upon reassembly, may prove themselves to comprise a + partial message. This is explicitly permitted. + + It should also be noted that the inclusion of a "References" + field in the headers of the second and subsequent pieces of + a fragmented message that references the Message-Id on the + previous piece may be of benefit to mail readers that + understand and track references. However, the generation of + such "References" fields is entirely optional. + + 7.3.3 The Message/External-Body subtype + + The external-body subtype indicates that the actual body + data are not included, but merely referenced. In this case, + the parameters describe a mechanism for accessing the + external data. + + When a message body or body part is of type + "message/external-body", it consists of a header, two + consecutive CRLFs, and the message header for the + encapsulated message. If another pair of consecutive CRLFs + appears, this of course ends the message header for the + encapsulated message. However, since the encapsulated + message's body is itself external, it does NOT appear in the + area that follows. For example, consider the following + message: + + Content-type: message/external-body; access- + type=local-file; + name=/u/nsb/Me.gif + + Content-type: image/gif + + THIS IS NOT REALLY THE BODY! + + The area at the end, which might be called the "phantom + body", is ignored for most external-body messages. However, + it may be used to contain auxilliary information for some + + + + Borenstein & Freed [Page 40] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + such messages, as indeed it is when the access-type is + "mail-server". Of the access-types defined by this + document, the phantom body is used only when the access-type + is "mail-server". In all other cases, the phantom body is + ignored. + + The only always-mandatory parameter for message/external- + body is "access-type"; all of the other parameters may be + mandatory or optional depending on the value of access-type. + + ACCESS-TYPE -- One or more case-insensitive words, + comma-separated, indicating supported access + mechanisms by which the file or data may be + obtained. Values include, but are not limited to, + "FTP", "ANON-FTP", "TFTP", "AFS", "LOCAL-FILE", + and "MAIL-SERVER". Future values, except for + experimental values beginning with "X-", must be + registered with IANA, as described in Appendix F . + + In addition, the following two parameters are optional for + ALL access-types: + + EXPIRATION -- The date (in the RFC 822 "date-time" + syntax, as extended by RFC 1123 to permit 4 digits + in the date field) after which the existence of + the external data is not guaranteed. + + SIZE -- The size (in octets) of the data. The + intent of this parameter is to help the recipient + decide whether or not to expend the necessary + resources to retrieve the external data. + + PERMISSION -- A field that indicates whether or + not it is expected that clients might also attempt + to overwrite the data. By default, or if + permission is "read", the assumption is that they + are not, and that if the data is retrieved once, + it is never needed again. If PERMISSION is "read- + write", this assumption is invalid, and any local + copy must be considered no more than a cache. + "Read" and "Read-write" are the only defined + values of permission. + + The precise semantics of the access-types defined here are + described in the sections that follow. + + 7.3.3.1 The "ftp" and "tftp" access-types + + An access-type of FTP or TFTP indicates that the message + body is accessible as a file using the FTP [RFC-959] or TFTP + [RFC-783] protocols, respectively. For these access-types, + the following additional parameters are mandatory: + + + + + + Borenstein & Freed [Page 41] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + NAME -- The name of the file that contains the + actual body data. + + SITE -- A machine from which the file may be + obtained, using the given protocol + + Before the data is retrieved, using these protocols, the + user will generally need to be asked to provide a login id + and a password for the machine named by the site parameter. + + In addition, the following optional parameters may also + appear when the access-type is FTP or ANON-FTP: + + DIRECTORY -- A directory from which the data named + by NAME should be retrieved. + + MODE -- A transfer mode for retrieving the + information, e.g. "image". + + 7.3.3.2 The "anon-ftp" access-type + + The "anon-ftp" access-type is identical to the "ftp" access + type, except that the user need not be asked to provide a + name and password for the specified site. Instead, the ftp + protocol will be used with login "anonymous" and a password + that corresponds to the user's email address. + + 7.3.3.3 The "local-file" and "afs" access-types + + An access-type of "local-file" indicates that the actual + body is accessible as a file on the local machine. An + access-type of "afs" indicates that the file is accessible + via the global AFS file system. In both cases, only a + single parameter is required: + + NAME -- The name of the file that contains the + actual body data. + + The following optional parameter may be used to describe the + locality of reference for the data, that is, the site or + sites at which the file is expected to be visible: + + SITE -- A domain specifier for a machine or set of + machines that are known to have access to the data + file. Asterisks may be used for wildcard matching + to a part of a domain name, such as + "*.bellcore.com", to indicate a set of machines on + which the data should be directly visible, while a + single asterisk may be used to indicate a file + that is expected to be universally available, + e.g., via a global file system. + + 7.3.3.4 The "mail-server" access-type + + + + + Borenstein & Freed [Page 42] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + The "mail-server" access-type indicates that the actual body + is available from a mail server. The mandatory parameter + for this access-type is: + + SERVER -- The email address of the mail server + from which the actual body data can be obtained. + + Because mail servers accept a variety of syntax, some of + which is multiline, the full command to be sent to a mail + server is not included as a parameter on the content-type + line. Instead, it may be provided as the "phantom body" + when the content-type is message/external-body and the + access-type is mail-server. + + Note that MIME does not define a mail server syntax. + Rather, it allows the inclusion of arbitrary mail server + commands in the phantom body. Implementations should + include the phantom body in the body of the message it sends + to the mail server address to retrieve the relevant data. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 43] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 7.3.3.5 Examples and Further Explanations + + With the emerging possibility of very wide-area file + systems, it becomes very hard to know in advance the set of + machines where a file will and will not be accessible + directly from the file system. Therefore it may make sense + to provide both a file name, to be tried directly, and the + name of one or more sites from which the file is known to be + accessible. An implementation can try to retrieve remote + files using FTP or any other protocol, using anonymous file + retrieval or prompting the user for the necessary name and + password. If an external body is accessible via multiple + mechanisms, the sender may include multiple parts of type + message/external-body within an entity of type + multipart/alternative. + + However, the external-body mechanism is not intended to be + limited to file retrieval, as shown by the mail-server + access-type. Beyond this, one can imagine, for example, + using a video server for external references to video clips. + + If an entity is of type "message/external-body", then the + body of the entity will contain the header fields of the + encapsulated message. The body itself is to be found in the + external location. This means that if the body of the + "message/external-body" message contains two consecutive + CRLFs, everything after those pairs is NOT part of the + message itself. For most message/external-body messages, + this trailing area must simply be ignored. However, it is a + convenient place for additional data that cannot be included + in the content-type header field. In particular, if the + "access-type" value is "mail-server", then the trailing area + must contain commands to be sent to the mail server at the + address given by NAME@SITE, where NAME and SITE are the + values of the NAME and SITE parameters, respectively. + + The embedded message header fields which appear in the body + of the message/external-body data can be used to declare the + Content-type of the external body. Thus a complete + message/external-body message, referring to a document in + PostScript format, might look like this: + + From: Whomever + Subject: whatever + MIME-Version: 1.0 + Message-ID: id1@host.com + Content-Type: multipart/alternative; boundary=42 + + + --42 + Content-Type: message/external-body; + name="BodyFormats.ps"; + + + + + + Borenstein & Freed [Page 44] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + site="thumper.bellcore.com"; + access-type=ANON-FTP; + directory="pub"; + mode="image"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + + --42 + Content-Type: message/external-body; + name="/u/nsb/writing/rfcs/RFC-XXXX.ps"; + site="thumper.bellcore.com"; + access-type=AFS + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + + --42 + Content-Type: message/external-body; + access-type=mail-server + server="listserv@bogus.bitnet"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + + get rfc-xxxx doc + + --42-- + + Like the message/partial type, the message/external-body + type is intended to be transparent, that is, to convey the + data type in the external body rather than to convey a + message with a body of that type. Thus the headers on the + outer and inner parts must be merged using the same rules as + for message/partial. In particular, this means that the + Content-type header is overridden, but the From and Subject + headers are preserved. + + Note that since the external bodies are not transported as + mail, they need not conform to the 7-bit and line length + requirements, but might in fact be binary files. Thus a + Content-Transfer-Encoding is not generally necessary, though + it is permitted. + + Note that the body of a message of type "message/external- + body" is governed by the basic syntax for an RFC 822 + message. In particular, anything before the first + consecutive pair of CRLFs is header information, while + anything after it is body information, which is ignored for + most access-types. + + + + + + + + Borenstein & Freed [Page 45] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 7.4 The Application Content-Type + + The "application" Content-Type is to be used for data which + do not fit in any of the other categories, and particularly + for data to be processed by mail-based uses of application + programs. This is information which must be processed by an + application before it is viewable or usable to a user. + Expected uses for Content-Type application include mail- + based file transfer, spreadsheets, data for mail-based + scheduling systems, and languages for "active" + (computational) email. (The latter, in particular, can pose + security problems which should be understood by + implementors, and are considered in detail in the discussion + of the application/PostScript content-type.) + + For example, a meeting scheduler might define a standard + representation for information about proposed meeting dates. + An intelligent user agent would use this information to + conduct a dialog with the user, and might then send further + mail based on that dialog. More generally, there have been + several "active" messaging languages developed in which + programs in a suitably specialized language are sent through + the mail and automatically run in the recipient's + environment. + + Such applications may be defined as subtypes of the + "application" Content-Type. This document defines three + subtypes: octet-stream, ODA, and PostScript. + + In general, the subtype of application will often be the + name of the application for which the data are intended. + This does not mean, however, that any application program + name may be used freely as a subtype of application. Such + usages must be registered with IANA, as described in + Appendix F. + + 7.4.1 The Application/Octet-Stream (primary) subtype + + The primary subtype of application, "octet-stream", may be + used to indicate that a body contains binary data. The set + of possible parameters includes, but is not limited to: + + NAME -- a suggested name for the binary data if + stored as a file. + + TYPE -- the general type or category of binary + data. This is intended as information for the + human recipient rather than for any automatic + processing. + + CONVERSIONS -- the set of operations that have + been performed on the data before putting it in + the mail (and before any Content-Transfer-Encoding + that might have been applied). If multiple + + + + Borenstein & Freed [Page 46] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + conversions have occurred, they must be separated + by commas and specified in the order they were + applied -- that is, the leftmost conversion must + have occurred first, and conversions are undone + from right to left. Note that NO conversion + values are defined by this document. Any + conversion values that that do not begin with "X-" + must be preceded by a published specification and + by registration with IANA, as described in + Appendix F. + + PADDING -- the number of bits of padding that were + appended to the bitstream comprising the actual + contents to produce the enclosed byte-oriented + data. This is useful for enclosing a bitstream in + a body when the total number of bits is not a + multiple of the byte size. + + The values for these attributes are left undefined at + present, but may require specification in the future. An + example of a common (though UNIX-specific) usage might be: + + Content-Type: application/octet-stream; + name=foo.tar.Z; type=tar; + conversions="x-encrypt,x-compress" + + However, it should be noted that the use of such conversions + is explicitly discouraged due to a lack of portability and + standardization. The use of uuencode is particularly + discouraged, in favor of the Content-Transfer-Encoding + mechanism, which is both more standardized and more portable + across mail boundaries. + + The recommended action for an implementation that receives + application/octet-stream mail is to simply offer to put the + data in a file, with any Content-Transfer-Encoding undone, + or perhaps to use it as input to a user-specified process. + + To reduce the danger of transmitting rogue programs through + the mail, it is strongly recommended that implementations + NOT implement a path-search mechanism whereby an arbitrary + program named in the Content-Type parameter (e.g., an + "interpreter=" parameter) is found and executed using the + mail body as input. + + 7.4.2 The Application/PostScript subtype + + A Content-Type of "application/postscript" indicates a + PostScript program. The language is defined in + [POSTSCRIPT]. It is recommended that Postscript as sent + through email should use Postscript document structuring + conventions if at all possible, and correctly. + + + + + + Borenstein & Freed [Page 47] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + The execution of general-purpose PostScript interpreters + entails serious security risks, and implementors are + discouraged from simply sending PostScript email bodies to + "off-the-shelf" interpreters. While it is usually safe to + send PostScript to a printer, where the potential for harm + is greatly constrained, implementors should consider all of + the following before they add interactive display of + PostScript bodies to their mail readers. + + The remainder of this section outlines some, though probably + not all, of the possible problems with sending PostScript + through the mail. + + Dangerous operations in the PostScript language include, but + may not be limited to, the PostScript operators deletefile, + renamefile, filenameforall, and file. File is only + dangerous when applied to something other than standard + input or output. Implementations may also define additional + nonstandard file operators; these may also pose a threat to + security. Filenameforall, the wildcard file search + operator, may appear at first glance to be harmless. Note, + however, that this operator has the potential to reveal + information about what files the recipient has access to, + and this information may itself be sensitive. Message + senders should avoid the use of potentially dangerous file + operators, since these operators are quite likely to be + unavailable in secure PostScript implementations. Message- + receiving and -displaying software should either completely + disable all potentially dangerous file operators or take + special care not to delegate any special authority to their + operation. These operators should be viewed as being done by + an outside agency when interpreting PostScript documents. + Such disabling and/or checking should be done completely + outside of the reach of the PostScript language itself; care + should be taken to insure that no method exists for + reenabling full-function versions of these operators. + + The PostScript language provides facilities for exiting the + normal interpreter, or server, loop. Changes made in this + "outer" environment are customarily retained across + documents, and may in some cases be retained semipermanently + in nonvolatile memory. The operators associated with exiting + the interpreter loop have the potential to interfere with + subsequent document processing. As such, their unrestrained + use constitutes a threat of service denial. PostScript + operators that exit the interpreter loop include, but may + not be limited to, the exitserver and startjob operators. + Message-sending software should not generate PostScript that + depends on exiting the interpreter loop to operate. The + ability to exit will probably be unavailable in secure + PostScript implementations. Message-receiving and + -displaying software should, if possible, disable the + ability to make retained changes to the PostScript + environment. Eliminate the startjob and exitserver commands. + + + + Borenstein & Freed [Page 48] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + If these commands cannot be eliminated, at least set the + password associated with them to a hard-to-guess value. + + PostScript provides operators for setting system-wide and + device-specific parameters. These parameter settings may be + retained across jobs and may potentially pose a threat to + the correct operation of the interpreter. The PostScript + operators that set system and device parameters include, but + may not be limited to, the setsystemparams and setdevparams + operators. Message-sending software should not generate + PostScript that depends on the setting of system or device + parameters to operate correctly. The ability to set these + parameters will probably be unavailable in secure PostScript + implementations. Message-receiving and -displaying software + should, if possible, disable the ability to change system + and device parameters. If these operators cannot be + disabled, at least set the password associated with them to + a hard-to-guess value. + + Some PostScript implementations provide nonstandard + facilities for the direct loading and execution of machine + code. Such facilities are quite obviously open to + substantial abuse. Message-sending software should not + make use of such features. Besides being totally hardware- + specific, they are also likely to be unavailable in secure + implementations of PostScript. Message-receiving and + -displaying software should not allow such operators to be + used if they exist. + + PostScript is an extensible language, and many, if not most, + implementations of it provide a number of their own + extensions. This document does not deal with such extensions + explicitly since they constitute an unknown factor. + Message-sending software should not make use of nonstandard + extensions; they are likely to be missing from some + implementations. Message-receiving and -displaying software + should make sure that any nonstandard PostScript operators + are secure and don't present any kind of threat. + + It is possible to write PostScript that consumes huge + amounts of various system resources. It is also possible to + write PostScript programs that loop infinitely. Both types + of programs have the potential to cause damage if sent to + unsuspecting recipients. Message-sending software should + avoid the construction and dissemination of such programs, + which is antisocial. Message-receiving and -displaying + software should provide appropriate mechanisms to abort + processing of a document after a reasonable amount of time + has elapsed. In addition, PostScript interpreters should be + limited to the consumption of only a reasonable amount of + any given system resource. + + Finally, bugs may exist in some PostScript interpreters + which could possibly be exploited to gain unauthorized + + + + Borenstein & Freed [Page 49] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + access to a recipient's system. Apart from noting this + possibility, there is no specific action to take to prevent + this, apart from the timely correction of such bugs if any + are found. + + 7.4.3 The Application/ODA subtype + + The "ODA" subtype of application is used to indicate that a + body contains information encoded according to the Office + Document Architecture [ODA] standards, using the ODIF + representation format. For application/oda, the Content- + Type line should also specify an attribute/value pair that + indicates the document application profile (DAP), using the + key word "profile". Thus an appropriate header field might + look like this: + + Content-Type: application/oda; profile=Q112 + + Consult the ODA standard [ODA] for further information. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 50] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 7.5 The Image Content-Type + + A Content-Type of "image" indicates that the bodycontains an + image. The subtype names the specific image format. These + names are case insensitive. Two initial subtypes are "jpeg" + for the JPEG format, JFIF encoding, and "gif" for GIF format + [GIF]. + + The list of image subtypes given here is neither exclusive + nor exhaustive, and is expected to grow as more types are + registered with IANA, as described in Appendix F. + + 7.6 The Audio Content-Type + + A Content-Type of "audio" indicates that the body contains + audio data. Although there is not yet a consensus on an + "ideal" audio format for use with computers, there is a + pressing need for a format capable of providing + interoperable behavior. + + The initial subtype of "basic" is specified to meet this + requirement by providing an absolutely minimal lowest common + denominator audio format. It is expected that richer + formats for higher quality and/or lower bandwidth audio will + be defined by a later document. + + The content of the "audio/basic" subtype is audio encoded + using 8-bit ISDN u-law [PCM]. When this subtype is present, + a sample rate of 8000 Hz and a single channel is assumed. + + 7.7 The Video Content-Type + + A Content-Type of "video" indicates that the body contains a + time-varying-picture image, possibly with color and + coordinated sound. The term "video" is used extremely + generically, rather than with reference to any particular + technology or format, and is not meant to preclude subtypes + such as animated drawings encoded compactly. The subtype + "mpeg" refers to video coded according to the MPEG standard + [MPEG]. + + Note that although in general this document strongly + discourages the mixing of multiple media in a single body, + it is recognized that many so-called "video" formats include + a representation for synchronized audio, and this is + explicitly permitted for subtypes of "video". + + 7.8 Experimental Content-Type Values + + A Content-Type value beginning with the characters "X-" is a + private value, to be used by consenting mail systems by + mutual agreement. Any format without a rigorous and public + definition must be named with an "X-" prefix, and publicly + specified values shall never begin with "X-". (Older + + + + Borenstein & Freed [Page 51] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + versions of the widely-used Andrew system use the "X-BE2" + name, so new systems should probably choose a different + name.) + + In general, the use of "X-" top-level types is strongly + discouraged. Implementors should invent subtypes of the + existing types whenever possible. The invention of new + types is intended to be restricted primarily to the + development of new media types for email, such as digital + odors or holography, and not for new data formats in + general. In many cases, a subtype of application will be + more appropriate than a new top-level type. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 52] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Summary + + Using the MIME-Version, Content-Type, and Content-Transfer- + Encoding header fields, it is possible to include, in a + standardized way, arbitrary types of data objects with RFC + 822 conformant mail messages. No restrictions imposed by + either RFC 821 or RFC 822 are violated, and care has been + taken to avoid problems caused by additional restrictions + imposed by the characteristics of some Internet mail + transport mechanisms (see Appendix B). The "multipart" and + "message" Content-Types allow mixing and hierarchical + structuring of objects of different types in a single + message. Further Content-Types provide a standardized + mechanism for tagging messages or body parts as audio, + image, or several other kinds of data. A distinguished + parameter syntax allows further specification of data format + details, particularly the specification of alternate + character sets. Additional optional header fields provide + mechanisms for certain extensions deemed desirable by many + implementors. Finally, a number of useful Content-Types are + defined for general use by consenting user agents, notably + text/richtext, message/partial, and message/external-body. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 53] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Acknowledgements + + This document is the result of the collective effort of a + large number of people, at several IETF meetings, on the + IETF-SMTP and IETF-822 mailing lists, and elsewhere. + Although any enumeration seems doomed to suffer from + egregious omissions, the following are among the many + contributors to this effort: + + Harald Tveit Alvestrand Timo Lehtinen + Randall Atkinson John R. MacMillan + Philippe Brandon Rick McGowan + Kevin Carosso Leo Mclaughlin + Uhhyung Choi Goli Montaser-Kohsari + Cristian Constantinof Keith Moore + Mark Crispin Tom Moore + Dave Crocker Erik Naggum + Terry Crowley Mark Needleman + Walt Daniels John Noerenberg + Frank Dawson Mats Ohrman + Hitoshi Doi Julian Onions + Kevin Donnelly Michael Patton + Keith Edwards David J. Pepper + Chris Eich Blake C. Ramsdell + Johnny Eriksson Luc Rooijakkers + Craig Everhart Marshall T. Rose + Patrik Faeltstroem Jonathan Rosenberg + Erik E. Fair Jan Rynning + Roger Fajman Harri Salminen + Alain Fontaine Michael Sanderson + James M. Galvin Masahiro Sekiguchi + Philip Gladstone Mark Sherman + Thomas Gordon Keld Simonsen + Phill Gross Bob Smart + James Hamilton Peter Speck + Steve Hardcastle-Kille Henry Spencer + David Herron Einar Stefferud + Bruce Howard Michael Stein + Bill Janssen Klaus Steinberger + Olle Jaernefors Peter Svanberg + Risto Kankkunen James Thompson + Phil Karn Steve Uhler + Alan Katz Stuart Vance + Tim Kehres Erik van der Poel + Neil Katin Guido van Rossum + Kyuho Kim Peter Vanderbilt + Anders Klemets Greg Vaudreuil + John Klensin Ed Vielmetti + Valdis Kletniek Ryan Waldron + Jim Knowles Wally Wedel + Stev Knowles Sven-Ove Westberg + Bob Kummerfeld Brian Wideen + + + + + + Borenstein & Freed [Page 54] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Pekka Kytolaakso John Wobus + Stellan Lagerstr.m Glenn Wright + Vincent Lau Rayan Zachariassen + Donald Lindsay David Zimmerman + The authors apologize for any omissions from this list, + which are certainly unintentional. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 55] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Appendix A -- Minimal MIME-Conformance + + The mechanisms described in this document are open-ended. + It is definitely not expected that all implementations will + support all of the Content-Types described, nor that they + will all share the same extensions. In order to promote + interoperability, however, it is useful to define the + concept of "MIME-conformance" to define a certain level of + implementation that allows the useful interworking of + messages with content that differs from US ASCII text. In + this section, we specify the requirements for such + conformance. + + A mail user agent that is MIME-conformant MUST: + + 1. Always generate a "MIME-Version: 1.0" header + field. + + 2. Recognize the Content-Transfer-Encoding header + field, and decode all received data encoded with + either the quoted-printable or base64 + implementations. Encode any data sent that is + not in seven-bit mail-ready representation using + one of these transformations and include the + appropriate Content-Transfer-Encoding header + field, unless the underlying transport mechanism + supports non-seven-bit data, as SMTP does not. + + 3. Recognize and interpret the Content-Type + header field, and avoid showing users raw data + with a Content-Type field other than text. Be + able to send at least text/plain messages, with + the character set specified as a parameter if it + is not US-ASCII. + + 4. Explicitly handle the following Content-Type + values, to at least the following extents: + + Text: + -- Recognize and display "text" mail + with the character set "US-ASCII." + -- Recognize other character sets at + least to the extent of being able + to inform the user about what + character set the message uses. + -- Recognize the "ISO-8859-*" character + sets to the extent of being able to + display those characters that are + common to ISO-8859-* and US-ASCII, + namely all characters represented + by octet values 0-127. + -- For unrecognized subtypes, show or + offer to show the user the "raw" + version of the data. An ability at + + + + Borenstein & Freed [Page 56] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + least to convert "text/richtext" to + plain text, as shown in Appendix D, + is encouraged, but not required for + conformance. + Message: + --Recognize and display at least the + primary (822) encapsulation. + Multipart: + -- Recognize the primary (mixed) + subtype. Display all relevant + information on the message level + and the body part header level and + then display or offer to display + each of the body parts + individually. + -- Recognize the "alternative" subtype, + and avoid showing the user + redundant parts of + multipart/alternative mail. + -- Treat any unrecognized subtypes as if + they were "mixed". + Application: + -- Offer the ability to remove either of + the two types of Content-Transfer- + Encoding defined in this document + and put the resulting information + in a user file. + + 5. Upon encountering any unrecognized Content- + Type, an implementation must treat it as if it had + a Content-Type of "application/octet-stream" with + no parameter sub-arguments. How such data are + handled is up to an implementation, but likely + options for handling such unrecognized data + include offering the user to write it into a file + (decoded from its mail transport format) or + offering the user to name a program to which the + decoded data should be passed as input. + Unrecognized predefined types, which in a MIME- + conformant mailer might still include audio, + image, or video, should also be treated in this + way. + + A user agent that meets the above conditions is said to be + MIME-conformant. The meaning of this phrase is that it is + assumed to be "safe" to send virtually any kind of + properly-marked data to users of such mail systems, because + such systems will at least be able to treat the data as + undifferentiated binary, and will not simply splash it onto + the screen of unsuspecting users. There is another sense + in which it is always "safe" to send data in a format that + is MIME-conformant, which is that such data will not break + or be broken by any known systems that are conformant with + RFC 821 and RFC 822. User agents that are MIME-conformant + + + + Borenstein & Freed [Page 57] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + have the additional guarantee that the user will not be + shown data that were never intended to be viewed as text. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 58] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Appendix B -- General Guidelines For Sending Email Data + + Internet email is not a perfect, homogeneous system. Mail + may become corrupted at several stages in its travel to a + final destination. Specifically, email sent throughout the + Internet may travel across many networking technologies. + Many networking and mail technologies do not support the + full functionality possible in the SMTP transport + environment. Mail traversing these systems is likely to be + modified in such a way that it can be transported. + + There exist many widely-deployed non-conformant MTAs in the + Internet. These MTAs, speaking the SMTP protocol, alter + messages on the fly to take advantage of the internal data + structure of the hosts they are implemented on, or are just + plain broken. + + The following guidelines may be useful to anyone devising a + data format (Content-Type) that will survive the widest + range of networking technologies and known broken MTAs + unscathed. Note that anything encoded in the base64 + encoding will satisfy these rules, but that some well-known + mechanisms, notably the UNIX uuencode facility, will not. + Note also that anything encoded in the Quoted-Printable + encoding will survive most gateways intact, but possibly not + some gateways to systems that use the EBCDIC character set. + + (1) Under some circumstances the encoding used for + data may change as part of normal gateway or user + agent operation. In particular, conversion from + base64 to quoted-printable and vice versa may be + necessary. This may result in the confusion of + CRLF sequences with line breaks in text body + parts. As such, the persistence of CRLF as + something other than a line break should not be + relied on. + + (2) Many systems may elect to represent and store + text data using local newline conventions. Local + newline conventions may not match the RFC822 CRLF + convention -- systems are known that use plain CR, + plain LF, CRLF, or counted records. The result is + that isolated CR and LF characters are not well + tolerated in general; they may be lost or + converted to delimiters on some systems, and hence + should not be relied on. + + (3) TAB (HT) characters may be misinterpreted or + may be automatically converted to variable numbers + of spaces. This is unavoidable in some + environments, notably those not based on the ASCII + character set. Such conversion is STRONGLY + DISCOURAGED, but it may occur, and mail formats + should not rely on the persistence of TAB (HT) + + + + Borenstein & Freed [Page 59] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + characters. + + (4) Lines longer than 76 characters may be wrapped + or truncated in some environments. Line wrapping + and line truncation are STRONGLY DISCOURAGED, but + unavoidable in some cases. Applications which + require long lines should somehow differentiate + between soft and hard line breaks. (A simple way + to do this is to use the quoted-printable + encoding.) + + (5) Trailing "white space" characters (SPACE, TAB + (HT)) on a line may be discarded by some transport + agents, while other transport agents may pad lines + with these characters so that all lines in a mail + file are of equal length. The persistence of + trailing white space, therefore, should not be + relied on. + + (6) Many mail domains use variations on the ASCII + character set, or use character sets such as + EBCDIC which contain most but not all of the US- + ASCII characters. The correct translation of + characters not in the "invariant" set cannot be + depended on across character converting gateways. + For example, this situation is a problem when + sending uuencoded information across BITNET, an + EBCDIC system. Similar problems can occur without + crossing a gateway, since many Internet hosts use + character sets other than ASCII internally. The + definition of Printable Strings in X.400 adds + further restrictions in certain special cases. In + particular, the only characters that are known to + be consistent across all gateways are the 73 + characters that correspond to the upper and lower + case letters A-Z and a-z, the 10 digits 0-9, and + the following eleven special characters: + + "'" (ASCII code 39) + "(" (ASCII code 40) + ")" (ASCII code 41) + "+" (ASCII code 43) + "," (ASCII code 44) + "-" (ASCII code 45) + "." (ASCII code 46) + "/" (ASCII code 47) + ":" (ASCII code 58) + "=" (ASCII code 61) + "?" (ASCII code 63) + + A maximally portable mail representation, such as + the base64 encoding, will confine itself to + relatively short lines of text in which the only + meaningful characters are taken from this set of + + + + Borenstein & Freed [Page 60] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 73 characters. + + Please note that the above list is NOT a list of recommended + practices for MTAs. RFC 821 MTAs are prohibited from + altering the character of white space or wrapping long + lines. These BAD and illegal practices are known to occur + on established networks, and implementions should be robust + in dealing with the bad effects they can cause. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 61] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Appendix C -- A Complex Multipart Example + + What follows is the outline of a complex multipart message. + This message has five parts to be displayed serially: two + introductory plain text parts, an embedded multipart + message, a richtext part, and a closing encapsulated text + message in a non-ASCII character set. The embedded + multipart message has two parts to be displayed in parallel, + a picture and an audio fragment. + + MIME-Version: 1.0 + From: Nathaniel Borenstein + Subject: A multipart example + Content-Type: multipart/mixed; + boundary=unique-boundary-1 + + This is the preamble area of a multipart message. + Mail readers that understand multipart format + should ignore this preamble. + If you are reading this text, you might want to + consider changing to a mail reader that understands + how to properly display multipart messages. + --unique-boundary-1 + + ...Some text appears here... + [Note that the preceding blank line means + no header fields were given and this is text, + with charset US ASCII. It could have been + done with explicit typing as in the next part.] + + --unique-boundary-1 + Content-type: text/plain; charset=US-ASCII + + This could have been part of the previous part, + but illustrates explicit versus implicit + typing of body parts. + + --unique-boundary-1 + Content-Type: multipart/parallel; + boundary=unique-boundary-2 + + + --unique-boundary-2 + Content-Type: audio/basic + Content-Transfer-Encoding: base64 + + ... base64-encoded 8000 Hz single-channel + u-law-format audio data goes here.... + + --unique-boundary-2 + Content-Type: image/gif + Content-Transfer-Encoding: Base64 + + + + + + Borenstein & Freed [Page 62] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + ... base64-encoded image data goes here.... + + --unique-boundary-2-- + + --unique-boundary-1 + Content-type: text/richtext + + This is richtext. + Isn't it + cool? + + --unique-boundary-1 + Content-Type: message/rfc822 + + From: (name in US-ASCII) + Subject: (subject in US-ASCII) + Content-Type: Text/plain; charset=ISO-8859-1 + Content-Transfer-Encoding: Quoted-printable + + ... Additional text in ISO-8859-1 goes here ... + + --unique-boundary-1-- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 63] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Appendix D -- A Simple Richtext-to-Text Translator in C + + One of the major goals in the design of the richtext subtype + of the text Content-Type is to make formatted text so simple + that even text-only mailers will implement richtext-to- + plain-text translators, thus increasing the likelihood that + multifont text will become "safe" to use very widely. To + demonstrate this simplicity, what follows is an extremely + simple 44-line C program that converts richtext input into + plain text output: + + #include + #include + main() { + int c, i; + char token[50]; + + while((c = getc(stdin)) != EOF) { + if (c == '<') { + for (i=0; (i<49 && (c = getc(stdin)) != '>' + && c != EOF); ++i) { + token[i] = isupper(c) ? tolower(c) : c; + } + if (c == EOF) break; + if (c != '>') while ((c = getc(stdin)) != + '>' + && c != EOF) {;} + if (c == EOF) break; + token[i] = '\0'; + if (!strcmp(token, "lt")) { + putc('<', stdout); + } else if (!strcmp(token, "nl")) { + putc('\n', stdout); + } else if (!strcmp(token, "/paragraph")) { + fputs("\n\n", stdout); + } else if (!strcmp(token, "comment")) { + int commct=1; + while (commct > 0) { + while ((c = getc(stdin)) != '<' + && c != EOF) ; + if (c == EOF) break; + for (i=0; (c = getc(stdin)) != '>' + && c != EOF; ++i) { + token[i] = isupper(c) ? + tolower(c) : c; + } + if (c== EOF) break; + token[i] = NULL; + if (!strcmp(token, "/comment")) -- + commct; + if (!strcmp(token, "comment")) + ++commct; + + + + + + Borenstein & Freed [Page 64] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + } + } /* Ignore all other tokens */ + } else if (c != '\n') putc(c, stdout); + } + putc('\n', stdout); /* for good measure */ + } + It should be noted that one can do considerably better than + this in displaying richtext data on a dumb terminal. In + particular, one can replace font information such as "bold" + with textual emphasis (like *this* or _T_H_I_S_). One can + also properly handle the richtext formatting commands + regarding indentation, justification, and others. However, + the above program is all that is necessary in order to + present richtext on a dumb terminal. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 65] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Appendix E -- Collected Grammar + + This appendix contains the complete BNF grammar for all the + syntax specified by this document. + + By itself, however, this grammar is incomplete. It refers + to several entities that are defined by RFC 822. Rather + than reproduce those definitions here, and risk + unintentional differences between the two, this document + simply refers the reader to RFC 822 for the remaining + definitions. Wherever a term is undefined, it refers to the + RFC 822 definition. + + attribute := token + + body-part = <"message" as defined in RFC 822, + with all header fields optional, and with the + specified delimiter not occurring anywhere in + the message body, either on a line by itself + or as a substring anywhere.> + + boundary := 0*69 bcharsnospace + + bchars := bcharsnospace / " " + + bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / "+" / + "_" + / "," / "-" / "." / "/" / ":" / "=" / "?" + + close-delimiter := delimiter "--" + + Content-Description := *text + + Content-ID := msg-id + + Content-Transfer-Encoding := "BASE64" / "QUOTED- + PRINTABLE" / + "8BIT" / "7BIT" / + "BINARY" / x-token + + Content-Type := type "/" subtype *[";" parameter] + + delimiter := CRLF "--" boundary ; taken from Content-Type + field. + ; when content-type is + multipart + ; There should be no space + ; between "--" and boundary. + + encapsulation := delimiter CRLF body-part + + epilogue := *text ; to be ignored upon + receipt. + + + + + Borenstein & Freed [Page 66] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + MIME-Version := 1*text + + multipart-body := preamble 1*encapsulation close-delimiter + epilogue + + parameter := attribute "=" value + + preamble := *text ; to be ignored upon + receipt. + + subtype := token + + token := 1* + + tspecials := "(" / ")" / "<" / ">" / "@" ; Must be in + / "," / ";" / ":" / "\" / <"> ; quoted-string, + / "/" / "[" / "]" / "?" / "." ; to use within + / "=" ; parameter values + + + type := "application" / "audio" ; case- + insensitive + / "image" / "message" + / "multipart" / "text" + / "video" / x-token + + value := token / quoted-string + + x-token := + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 67] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Appendix F -- IANA Registration Procedures + + MIME has been carefully designed to have extensible + mechanisms, and it is expected that the set of content- + type/subtype pairs and their associated parameters will grow + significantly with time. Several other MIME fields, notably + character set names, access-type parameters for the + message/external-body type, conversions parameters for the + application type, and possibly even Content-Transfer- + Encoding values, are likely to have new values defined over + time. In order to ensure that the set of such values is + developed in an orderly, well-specified, and public manner, + MIME defines a registration process which uses the Internet + Assigned Numbers Authority (IANA) as a central registry for + such values. + + In general, parameters in the content-type header field are + used to convey supplemental information for various content + types, and their use is defined when the content-type and + subtype are defined. New parameters should not be defined + as a way to introduce new functionality. + + In order to simplify and standardize the registration + process, this appendix gives templates for the registration + of new values with IANA. Each of these is given in the form + of an email message template, to be filled in by the + registering party. + + F.1 Registration of New Content-type/subtype Values + + Note that MIME is generally expected to be extended by + subtypes. If a new fundamental top-level type is needed, + its specification should be published as an RFC or + submitted in a form suitable to become an RFC, and be + subject to the Internet standards process. + + To: IANA@isi.edu + Subject: Registration of new MIME content-type/subtype + + MIME type name: + + (If the above is not an existing top-level MIME type, + please explain why an existing type cannot be used.) + + MIME subtype name: + + Required parameters: + + Optional parameters: + + Encoding considerations: + + Security considerations: + + + + + Borenstein & Freed [Page 68] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Published specification: + + (The published specification must be an Internet RFC or + RFC-to-be if a new top-level type is being defined, and + must be a publicly available specification in any + case.) + + Person & email address to contact for further + information: + F.2 Registration of New Character Set Values + + To: IANA@isi.edu + Subject: Registration of new MIME character set value + + MIME character set name: + + Published specification: + + (The published specification must be an Internet RFC or + RFC-to-be or an international standard.) + + Person & email address to contact for further + information: + + F.3 Registration of New Access-type Values for + Message/external-body + + To: IANA@isi.edu + Subject: Registration of new MIME Access-type for + Message/external-body content-type + + MIME access-type name: + + Required parameters: + + Optional parameters: + + Published specification: + + (The published specification must be an Internet RFC or + RFC-to-be.) + + Person & email address to contact for further + information: + + + F.4 Registration of New Conversions Values for Application + + To: IANA@isi.edu + Subject: Registration of new MIME Conversions value + for Application content-type + + MIME Conversions name: + + + + + Borenstein & Freed [Page 69] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Published specification: + + (The published specification must be an Internet RFC or + RFC-to-be.) + + Person & email address to contact for further + information: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 70] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Appendix G -- Summary of the Seven Content-types + + Content-type: text + + Subtypes defined by this document: plain, richtext + + Important Parameters: charset + + Encoding notes: quoted-printable generally preferred if an + encoding is needed and the character set is mostly an + ASCII superset. + + Security considerations: Rich text formats such as TeX and + Troff often contain mechanisms for executing arbitrary + commands or file system operations, and should not be + used automatically unless these security problems have + been addressed. Even plain text may contain control + characters that can be used to exploit the capabilities + of "intelligent" terminals and cause security + violations. User interfaces designed to run on such + terminals should be aware of and try to prevent such + problems. + ________________________________________________________________ + + Content-type: multipart + + Subtypes defined by this document: mixed, alternative, + digest, parallel. + + Important Parameters: boundary + + Encoding notes: No content-transfer-encoding is permitted. + + ________________________________________________________________ + + Content-type: message + + Subtypes defined by this document: rfc822, partial, + external-body + + Important Parameters: id, number, total + + Encoding notes: No content-transfer-encoding is permitted. + + ________________________________________________________________ + + Content-type: application + + Subtypes defined by this document: octet-stream, + postscript, oda + + Important Parameters: profile + + + + + + Borenstein & Freed [Page 71] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Encoding notes: base64 generally preferred for octet-stream + or other unreadable subtypes. + + Security considerations: This type is intended for the + transmission of data to be interpreted by locally-installed + programs. If used, for example, to transmit executable + binary programs or programs in general-purpose interpreted + languages, such as LISP programs or shell scripts, severe + security problems could result. In general, authors of + mail-reading agents are cautioned against giving their + systems the power to execute mail-based application data + without carefully considering the security implications. + While it is certainly possible to define safe application + formats and even safe interpreters for unsafe formats, each + interpreter should be evaluated separately for possible + security problems. + ________________________________________________________________ + + Content-type: image + + Subtypes defined by this document: jpeg, gif + + Important Parameters: none + + Encoding notes: base64 generally preferred + + ________________________________________________________________ + + Content-type: audio + + Subtypes defined by this document: basic + + Important Parameters: none + + Encoding notes: base64 generally preferred + + ________________________________________________________________ + + Content-type: video + + Subtypes defined by this document: mpeg + + Important Parameters: none + + Encoding notes: base64 generally preferred + + + + + + + + + + + + + Borenstein & Freed [Page 72] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Appendix H -- Canonical Encoding Model + + + + There was some confusion, in earlier drafts of this memo, + regarding the model for when email data was to be converted + to canonical form and encoded, and in particular how this + process would affect the treatment of CRLFs, given that the + representation of newlines varies greatly from system to + system. For this reason, a canonical model for encoding is + presented below. + + The process of composing a MIME message part can be modelled + as being done in a number of steps. Note that these steps + are roughly similar to those steps used in RFC1113: + + Step 1. Creation of local form. + + The body part to be transmitted is created in the system's + native format. The native character set is used, and where + appropriate local end of line conventions are used as well. + The may be a UNIX-style text file, or a Sun raster image, or + a VMS indexed file, or audio data in a system-dependent + format stored only in memory, or anything else that + corresponds to the local model for the representation of + some form of information. + + Step 2. Conversion to canonical form. + + The entire body part, including "out-of-band" information + such as record lengths and possibly file attribute + information, is converted to a universal canonical form. + The specific content type of the body part as well as its + associated attributes dictate the nature of the canonical + form that is used. Conversion to the proper canonical form + may involve character set conversion, transformation of + audio data, compression, or various other operations + specific to the various content types. + + For example, in the case of text/plain data, the text must + be converted to a supported character set and lines must be + delimited with CRLF delimiters in accordance with RFC822. + Note that the restriction on line lengths implied by RFC822 + is eliminated if the next step employs either quoted- + printable or base64 encoding. + + Step 3. Apply transfer encoding. + + A Content-Transfer-Encoding appropriate for this body part + is applied. Note that there is no fixed relationship + between the content type and the transfer encoding. In + particular, it may be appropriate to base the choice of + base64 or quoted-printable on character frequency counts + which are specific to a given instance of body part. + + + + Borenstein & Freed [Page 73] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Step 4. Insertion into message. + + The encoded object is inserted into a MIME message with + appropriate body part headers and boundary markers. + + It is vital to note that these steps are only a model; they + are specifically NOT a blueprint for how an actual system + would be built. In particular, the model fails to account + for two common designs: + + 1. In many cases the conversion to a canonical + form prior to encoding will be subsumed into the + encoder itself, which understands local formats + directly. For example, the local newline + convention for text bodyparts might be carried + through to the encoder itself along with knowledge + of what that format is. + + 2. The output of the encoders may have to pass + through one or more additional steps prior to + being transmitted as a message. As such, the + output of the encoder may not be compliant with + the formats specified by RFC822. In particular, + once again it may be appropriate for the + converter's output to be expressed using local + newline conventions rather than using the standard + RFC822 CRLF delimiters. + + Other implementation variations are conceivable as well. + The only important aspect of this discussion is that the + resulting messages are consistent with those produced by the + model described here. + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 74] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + References + + [US-ASCII] Coded Character Set--7-Bit American Standard Code + for Information Interchange, ANSI X3.4-1986. + + [ATK] Borenstein, Nathaniel S., Multimedia Applications + Development with the Andrew Toolkit, Prentice-Hall, 1990. + + [GIF] Graphics Interchange Format (Version 89a), Compuserve, + Inc., Columbus, Ohio, 1990. + + [ISO-2022] International Standard--Information Processing-- + ISO 7-bit and 8-bit coded character sets--Code extension + techniques, ISO 2022:1986. + + [ISO-8859] Information Processing -- 8-bit Single-Byte Coded + Graphic Character Sets -- Part 1: Latin Alphabet No. 1, ISO + 8859-1:1987. Part 2: Latin alphabet No. 2, ISO 8859-2, + 1987. Part 3: Latin alphabet No. 3, ISO 8859-3, 1988. Part + 4: Latin alphabet No. 4, ISO 8859-4, 1988. Part 5: + Latin/Cyrillic alphabet, ISO 8859-5, 1988. Part 6: + Latin/Arabic alphabet, ISO 8859-6, 1987. Part 7: + Latin/Greek alphabet, ISO 8859-7, 1987. Part 8: + Latin/Hebrew alphabet, ISO 8859-8, 1988. Part 9: Latin + alphabet No. 5, ISO 8859-9, 1990. + + [ISO-646] International Standard--Information Processing-- + ISO 7-bit coded character set for information interchange, + ISO 646:1983. + + [MPEG] Video Coding Draft Standard ISO 11172 CD, ISO + IEC/TJC1/SC2/WG11 (Motion Picture Experts Group), May, 1991. + + [ODA] ISO 8613; Information Processing: Text and Office + System; Office Document Architecture (ODA) and Interchange + Format (ODIF), Part 1-8, 1989. + + [PCM] CCITT, Fascicle III.4 - Recommendation G.711, Geneva, + 1972, "Pulse Code Modulation (PCM) of Voice Frequencies". + + [POSTSCRIPT] Adobe Systems, Inc., PostScript Language + Reference Manual, Addison-Wesley, 1985. + + [X400] Schicker, Pietro, "Message Handling Systems, X.400", + Message Handling Systems and Distributed Applications, E. + Stefferud, O-j. Jacobsen, and P. Schicker, eds., North- + Holland, 1989, pp. 3-41. + + [RFC-783] Sollins, K.R. TFTP Protocol (revision 2). June, + 1981, MIT, RFC-783. + + [RFC-821] Postel, J.B. Simple Mail Transfer Protocol. + August, 1982, USC/Information Sciences Institute, RFC-821. + + + + + Borenstein & Freed [Page 75] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + [RFC-822] Crocker, D. Standard for the format of ARPA + Internet text messages. August, 1982, UDEL, RFC-822. + + [RFC-934] Rose, M.T.; Stefferud, E.A. Proposed standard + for message encapsulation. January, 1985, Delaware + and NMA, RFC-934. + + [RFC-959] Postel, J.B.; Reynolds, J.K. File Transfer + Protocol. October, 1985, USC/Information Sciences + Institute, RFC-959. + + [RFC-1049] Sirbu, M.A. Content-Type header field for + Internet messages. March, 1988, CMU, RFC-1049. + + [RFC-1113] Linn, J. Privacy enhancement for Internet + electronic mail: Part I - message encipherment and + authentication procedures. August, 1989, IAB Privacy Task + Force, RFC-1113. + + [RFC-1154] Robinson, D.; Ullmann, R. Encoding header field + for Internet messages. April, 1990, Prime Computer, + Inc., RFC-1154. + + [RFC-1342] Moore, Keith, Representation of Non-Ascii Text in + Internet Message Headers. June, 1992, University of + Tennessee, RFC-1342. + + Security Considerations + + Security issues are discussed in Section 7.4.2 and in + Appendix G. Implementors should pay special attention to + the security implications of any mail content-types that can + cause the remote execution of any actions in the recipient's + environment. In such cases, the discussion of the + applicaton/postscript content-type in Section 7.4.2 may + serve as a model for considering other content-types with + remote execution capabilities. + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 76] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Authors' Addresses + + For more information, the authors of this document may be + contacted via Internet mail: + + Nathaniel S. Borenstein + MRE 2D-296, Bellcore + 445 South St. + Morristown, NJ 07962-1910 + + Phone: +1 201 829 4270 + Fax: +1 201 829 7019 + Email: nsb@bellcore.com + + + Ned Freed + Innosoft International, Inc. + 250 West First Street + Suite 240 + Claremont, CA 91711 + + Phone: +1 714 624 7907 + Fax: +1 714 621 5319 + Email: ned@innosoft.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 77] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + + + + THIS PAGE INTENTIONALLY LEFT BLANK. + + Please discard this page and place the following table of + contents after the title page. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page i] + + + + + + + + + Table of Contents + + + 1 Introduction....................................... 1 + 2 Notations, Conventions, and Generic BNF Grammar.... 3 + 3 The MIME-Version Header Field...................... 5 + 4 The Content-Type Header Field...................... 6 + 5 The Content-Transfer-Encoding Header Field......... 10 + 5.1 Quoted-Printable Content-Transfer-Encoding......... 14 + 5.2 Base64 Content-Transfer-Encoding................... 17 + 6 Additional Optional Content- Header Fields......... 19 + 6.1 Optional Content-ID Header Field................... 19 + 6.2 Optional Content-Description Header Field.......... 19 + 7 The Predefined Content-Type Values................. 20 + 7.1 The Text Content-Type.............................. 20 + 7.1.1 The charset parameter.............................. 20 + 7.1.2 The Text/plain subtype............................. 23 + 7.1.3 The Text/richtext subtype.......................... 23 + 7.2 The Multipart Content-Type......................... 29 + 7.2.1 Multipart: The common syntax...................... 30 + 7.2.2 The Multipart/mixed (primary) subtype.............. 34 + 7.2.3 The Multipart/alternative subtype.................. 34 + 7.2.4 The Multipart/digest subtype....................... 36 + 7.2.5 The Multipart/parallel subtype..................... 36 + 7.3 The Message Content-Type........................... 37 + 7.3.1 The Message/rfc822 (primary) subtype............... 37 + 7.3.2 The Message/Partial subtype........................ 37 + 7.3.3 The Message/External-Body subtype.................. 40 + 7.4 The Application Content-Type....................... 46 + 7.4.1 The Application/Octet-Stream (primary) subtype..... 46 + 7.4.2 The Application/PostScript subtype................. 47 + 7.4.3 The Application/ODA subtype........................ 50 + 7.5 The Image Content-Type............................. 51 + 7.6 The Audio Content-Type............................. 51 + 7.7 The Video Content-Type............................. 51 + 7.8 Experimental Content-Type Values................... 51 + Summary............................................ 53 + Acknowledgements................................... 54 + Appendix A -- Minimal MIME-Conformance............. 56 + Appendix B -- General Guidelines For Sending Email Data59 + Appendix C -- A Complex Multipart Example.......... 62 + Appendix D -- A Simple Richtext-to-Text Translator in C64 + Appendix E -- Collected Grammar.................... 66 + Appendix F -- IANA Registration Procedures......... 68 + F.1 Registration of New Content-type/subtype Values..68 + F.2 Registration of New Character Set Values...... 69 + F.3 Registration of New Access-type Values for Message/external-body69 + F.4 Registration of New Conversions Values for Application69 + Appendix G -- Summary of the Seven Content-types... 71 + Appendix H -- Canonical Encoding Model............. 73 + References......................................... 75 + Security Considerations............................ 76 + Authors' Addresses................................. 77 + + + + Borenstein & Freed [Page ii] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page iii] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc1521.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc1521.txt new file mode 100644 index 0000000..074ba41 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc1521.txt @@ -0,0 +1,4539 @@ + + + + + + +Network Working Group N. Borenstein +Request for Comments: 1521 Bellcore +Obsoletes: 1341 N. Freed +Category: Standards Track Innosoft + September 1993 + + + MIME (Multipurpose Internet Mail Extensions) Part One: + Mechanisms for Specifying and Describing + the Format of Internet Message Bodies + +Status of this Memo + + This RFC specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" for the standardization state and status + of this protocol. Distribution of this memo is unlimited. + +Abstract + + STD 11, RFC 822 defines a message representation protocol which + specifies considerable detail about message headers, but which leaves + the message content, or message body, as flat ASCII text. This + document redefines the format of message bodies to allow multi-part + textual and non-textual message bodies to be represented and + exchanged without loss of information. This is based on earlier work + documented in RFC 934 and STD 11, RFC 1049, but extends and revises + that work. Because RFC 822 said so little about message bodies, this + document is largely orthogonal to (rather than a revision of) RFC + 822. + + In particular, this document is designed to provide facilities to + include multiple objects in a single message, to represent body text + in character sets other than US-ASCII, to represent formatted multi- + font text messages, to represent non-textual material such as images + and audio fragments, and generally to facilitate later extensions + defining new types of Internet mail for use by cooperating mail + agents. + + This document does NOT extend Internet mail header fields to permit + anything other than US-ASCII text data. Such extensions are the + subject of a companion document [RFC-1522]. + + This document is a revision of RFC 1341. Significant differences + from RFC 1341 are summarized in Appendix H. + + + + + +Borenstein & Freed [Page 1] + +RFC 1521 MIME September 1993 + + +Table of Contents + + 1. Introduction....................................... 3 + 2. Notations, Conventions, and Generic BNF Grammar.... 6 + 3. The MIME-Version Header Field...................... 7 + 4. The Content-Type Header Field...................... 9 + 5. The Content-Transfer-Encoding Header Field......... 13 + 5.1. Quoted-Printable Content-Transfer-Encoding......... 18 + 5.2. Base64 Content-Transfer-Encoding................... 21 + 6. Additional Content-Header Fields................... 23 + 6.1. Optional Content-ID Header Field................... 23 + 6.2. Optional Content-Description Header Field.......... 24 + 7. The Predefined Content-Type Values................. 24 + 7.1. The Text Content-Type.............................. 24 + 7.1.1. The charset parameter.............................. 25 + 7.1.2. The Text/plain subtype............................. 28 + 7.2. The Multipart Content-Type......................... 28 + 7.2.1. Multipart: The common syntax...................... 29 + 7.2.2. The Multipart/mixed (primary) subtype.............. 34 + 7.2.3. The Multipart/alternative subtype.................. 34 + 7.2.4. The Multipart/digest subtype....................... 36 + 7.2.5. The Multipart/parallel subtype..................... 37 + 7.2.6. Other Multipart subtypes........................... 37 + 7.3. The Message Content-Type........................... 38 + 7.3.1. The Message/rfc822 (primary) subtype............... 38 + 7.3.2. The Message/Partial subtype........................ 39 + 7.3.3. The Message/External-Body subtype.................. 42 + 7.3.3.1. The "ftp" and "tftp" access-types............... 44 + 7.3.3.2. The "anon-ftp" access-type...................... 45 + 7.3.3.3. The "local-file" and "afs" access-types......... 45 + 7.3.3.4. The "mail-server" access-type................... 45 + 7.3.3.5. Examples and Further Explanations............... 46 + 7.4. The Application Content-Type....................... 49 + 7.4.1. The Application/Octet-Stream (primary) subtype..... 50 + 7.4.2. The Application/PostScript subtype................. 50 + 7.4.3. Other Application subtypes......................... 53 + 7.5. The Image Content-Type............................. 53 + 7.6. The Audio Content-Type............................. 54 + 7.7. The Video Content-Type............................. 54 + 7.8. Experimental Content-Type Values................... 54 + 8. Summary............................................ 56 + 9. Security Considerations............................ 56 + 10. Authors' Addresses................................. 57 + 11. Acknowledgements................................... 58 + Appendix A -- Minimal MIME-Conformance.................... 60 + Appendix B -- General Guidelines For Sending Email Data... 63 + Appendix C -- A Complex Multipart Example................. 66 + Appendix D -- Collected Grammar........................... 68 + + + +Borenstein & Freed [Page 2] + +RFC 1521 MIME September 1993 + + + Appendix E -- IANA Registration Procedures................ 72 + E.1 Registration of New Content-type/subtype Values...... 72 + E.2 Registration of New Access-type Values + for Message/external-body............................ 73 + Appendix F -- Summary of the Seven Content-types.......... 74 + Appendix G -- Canonical Encoding Model.................... 76 + Appendix H -- Changes from RFC 1341....................... 78 + References................................................ 80 + +1. Introduction + + Since its publication in 1982, STD 11, RFC 822 [RFC-822] has defined + the standard format of textual mail messages on the Internet. Its + success has been such that the RFC 822 format has been adopted, + wholly or partially, well beyond the confines of the Internet and the + Internet SMTP transport defined by STD 10, RFC 821 [RFC-821]. As the + format has seen wider use, a number of limitations have proven + increasingly restrictive for the user community. + + RFC 822 was intended to specify a format for text messages. As such, + non-text messages, such as multimedia messages that might include + audio or images, are simply not mentioned. Even in the case of text, + however, RFC 822 is inadequate for the needs of mail users whose + languages require the use of character sets richer than US ASCII + [US-ASCII]. Since RFC 822 does not specify mechanisms for mail + containing audio, video, Asian language text, or even text in most + European languages, additional specifications are needed. + + One of the notable limitations of RFC 821/822 based mail systems is + the fact that they limit the contents of electronic mail messages to + relatively short lines of seven-bit ASCII. This forces users to + convert any non-textual data that they may wish to send into seven- + bit bytes representable as printable ASCII characters before invoking + a local mail UA (User Agent, a program with which human users send + and receive mail). Examples of such encodings currently used in the + Internet include pure hexadecimal, uuencode, the 3-in-4 base 64 + scheme specified in RFC 1421, the Andrew Toolkit Representation + [ATK], and many others. + + The limitations of RFC 822 mail become even more apparent as gateways + are designed to allow for the exchange of mail messages between RFC + 822 hosts and X.400 hosts. X.400 [X400] specifies mechanisms for the + inclusion of non-textual body parts within electronic mail messages. + The current standards for the mapping of X.400 messages to RFC 822 + messages specify either that X.400 non-textual body parts must be + converted to (not encoded in) an ASCII format, or that they must be + discarded, notifying the RFC 822 user that discarding has occurred. + This is clearly undesirable, as information that a user may wish to + + + +Borenstein & Freed [Page 3] + +RFC 1521 MIME September 1993 + + + receive is lost. Even though a user's UA may not have the capability + of dealing with the non-textual body part, the user might have some + mechanism external to the UA that can extract useful information from + the body part. Moreover, it does not allow for the fact that the + message may eventually be gatewayed back into an X.400 message + handling system (i.e., the X.400 message is "tunneled" through + Internet mail), where the non-textual information would definitely + become useful again. + + This document describes several mechanisms that combine to solve most + of these problems without introducing any serious incompatibilities + with the existing world of RFC 822 mail. In particular, it + describes: + + 1. A MIME-Version header field, which uses a version number to + declare a message to be conformant with this specification and + allows mail processing agents to distinguish between such + messages and those generated by older or non-conformant software, + which is presumed to lack such a field. + + 2. A Content-Type header field, generalized from RFC 1049 [RFC-1049], + which can be used to specify the type and subtype of data in the + body of a message and to fully specify the native representation + (encoding) of such data. + + 2.a. A "text" Content-Type value, which can be used to represent + textual information in a number of character sets and + formatted text description languages in a standardized + manner. + + 2.b. A "multipart" Content-Type value, which can be used to + combine several body parts, possibly of differing types of + data, into a single message. + + 2.c. An "application" Content-Type value, which can be used to + transmit application data or binary data, and hence, among + other uses, to implement an electronic mail file transfer + service. + + 2.d. A "message" Content-Type value, for encapsulating another + mail message. + + 2.e An "image" Content-Type value, for transmitting still image + (picture) data. + + 2.f. An "audio" Content-Type value, for transmitting audio or + voice data. + + + + +Borenstein & Freed [Page 4] + +RFC 1521 MIME September 1993 + + + 2.g. A "video" Content-Type value, for transmitting video or + moving image data, possibly with audio as part of the + composite video data format. + + 3. A Content-Transfer-Encoding header field, which can be used to + specify an auxiliary encoding that was applied to the data in + order to allow it to pass through mail transport mechanisms which + may have data or character set limitations. + + 4. Two additional header fields that can be used to further describe + the data in a message body, the Content-ID and Content- + Description header fields. + + MIME has been carefully designed as an extensible mechanism, and it + is expected that the set of content-type/subtype pairs and their + associated parameters will grow significantly with time. Several + other MIME fields, notably including character set names, are likely + to have new values defined over time. In order to ensure that the + set of such values is developed in an orderly, well-specified, and + public manner, MIME defines a registration process which uses the + Internet Assigned Numbers Authority (IANA) as a central registry for + such values. Appendix E provides details about how IANA registration + is accomplished. + + Finally, to specify and promote interoperability, Appendix A of this + document provides a basic applicability statement for a subset of the + above mechanisms that defines a minimal level of "conformance" with + this document. + + HISTORICAL NOTE: Several of the mechanisms described in this + document may seem somewhat strange or even baroque at first + reading. It is important to note that compatibility with existing + standards AND robustness across existing practice were two of the + highest priorities of the working group that developed this + document. In particular, compatibility was always favored over + elegance. + + MIME was first defined and published as RFCs 1341 and 1342 [RFC-1341] + [RFC-1342]. This document is a relatively minor updating of RFC + 1341, and is intended to supersede it. The differences between this + document and RFC 1341 are summarized in Appendix H. Please refer to + the current edition of the "IAB Official Protocol Standards" for the + standardization state and status of this protocol. Several other RFC + documents will be of interest to the MIME implementor, in particular + [RFC 1343], [RFC-1344], and [RFC-1345]. + + + + + + +Borenstein & Freed [Page 5] + +RFC 1521 MIME September 1993 + + +2. Notations, Conventions, and Generic BNF Grammar + + This document is being published in two versions, one as plain ASCII + text and one as PostScript (PostScript is a trademark of Adobe + Systems Incorporated.). While the text version is the official + specification, some will find the PostScript version easier to read. + The textual contents are identical. An Andrew-format copy of this + document is also available from the first author (Borenstein). + + Although the mechanisms specified in this document are all described + in prose, most are also described formally in the modified BNF + notation of RFC 822. Implementors will need to be familiar with this + notation in order to understand this specification, and are referred + to RFC 822 for a complete explanation of the modified BNF notation. + + Some of the modified BNF in this document makes reference to + syntactic entities that are defined in RFC 822 and not in this + document. A complete formal grammar, then, is obtained by combining + the collected grammar appendix of this document with that of RFC 822 + plus the modifications to RFC 822 defined in RFC 1123, which + specifically changes the syntax for `return', `date' and `mailbox'. + + The term CRLF, in this document, refers to the sequence of the two + ASCII characters CR (13) and LF (10) which, taken together, in this + order, denote a line break in RFC 822 mail. + + The term "character set" is used in this document to refer to a + method used with one or more tables to convert encoded text to a + series of octets. This definition is intended to allow various kinds + of text encodings, from simple single-table mappings such as ASCII to + complex table switching methods such as those that use ISO 2022's + techniques. However, a MIME character set name must fully specify + the mapping to be performed. + + The term "message", when not further qualified, means either the + (complete or "top-level") message being transferred on a network, or + a message encapsulated in a body of type "message". + + The term "body part", in this document, means one of the parts of the + body of a multipart entity. A body part has a header and a body, so + it makes sense to speak about the body of a body part. + + The term "entity", in this document, means either a message or a body + part. All kinds of entities share the property that they have a + header and a body. + + The term "body", when not further qualified, means the body of an + entity, that is the body of either a message or of a body part. + + + +Borenstein & Freed [Page 6] + +RFC 1521 MIME September 1993 + + + NOTE: The previous four definitions are clearly circular. This is + unavoidable, since the overall structure of a MIME message is + indeed recursive. + + In this document, all numeric and octet values are given in decimal + notation. + + It must be noted that Content-Type values, subtypes, and parameter + names as defined in this document are case-insensitive. However, + parameter values are case-sensitive unless otherwise specified for + the specific parameter. + + FORMATTING NOTE: This document has been carefully formatted for + ease of reading. The PostScript version of this document, in + particular, places notes like this one, which may be skipped by + the reader, in a smaller, italicized, font, and indents it as + well. In the text version, only the indentation is preserved, so + if you are reading the text version of this you might consider + using the PostScript version instead. However, all such notes will + be indented and preceded by "NOTE:" or some similar introduction, + even in the text version. + + The primary purpose of these non-essential notes is to convey + information about the rationale of this document, or to place this + document in the proper historical or evolutionary context. Such + information may be skipped by those who are focused entirely on + building a conformant implementation, but may be of use to those + who wish to understand why this document is written as it is. + + For ease of recognition, all BNF definitions have been placed in a + fixed-width font in the PostScript version of this document. + +3. The MIME-Version Header Field + + Since RFC 822 was published in 1982, there has really been only one + format standard for Internet messages, and there has been little + perceived need to declare the format standard in use. This document + is an independent document that complements RFC 822. Although the + extensions in this document have been defined in such a way as to be + compatible with RFC 822, there are still circumstances in which it + might be desirable for a mail-processing agent to know whether a + message was composed with the new standard in mind. + + Therefore, this document defines a new header field, "MIME-Version", + which is to be used to declare the version of the Internet message + body format standard in use. + + Messages composed in accordance with this document MUST include such + + + +Borenstein & Freed [Page 7] + +RFC 1521 MIME September 1993 + + + a header field, with the following verbatim text: + + MIME-Version: 1.0 + + The presence of this header field is an assertion that the message + has been composed in compliance with this document. + + Since it is possible that a future document might extend the message + format standard again, a formal BNF is given for the content of the + MIME-Version field: + + version := "MIME-Version" ":" 1*DIGIT "." 1*DIGIT + + Thus, future format specifiers, which might replace or extend "1.0", + are constrained to be two integer fields, separated by a period. If + a message is received with a MIME-version value other than "1.0", it + cannot be assumed to conform with this specification. + + Note that the MIME-Version header field is required at the top level + of a message. It is not required for each body part of a multipart + entity. It is required for the embedded headers of a body of type + "message" if and only if the embedded message is itself claimed to be + MIME-conformant. + + It is not possible to fully specify how a mail reader that conforms + with MIME as defined in this document should treat a message that + might arrive in the future with some value of MIME-Version other than + "1.0". However, conformant software is encouraged to check the + version number and at least warn the user if an unrecognized MIME- + version is encountered. + + It is also worth noting that version control for specific content- + types is not accomplished using the MIME-Version mechanism. In + particular, some formats (such as application/postscript) have + version numbering conventions that are internal to the document + format. Where such conventions exist, MIME does nothing to supersede + them. Where no such conventions exist, a MIME type might use a + "version" parameter in the content-type field if necessary. + + NOTE TO IMPLEMENTORS: All header fields defined in this document, + including MIME-Version, Content-type, etc., are subject to the + general syntactic rules for header fields specified in RFC 822. In + particular, all can include comments, which means that the following + two MIME-Version fields are equivalent: + + MIME-Version: 1.0 + MIME-Version: 1.0 (Generated by GBD-killer 3.7) + + + + +Borenstein & Freed [Page 8] + +RFC 1521 MIME September 1993 + + +4. The Content-Type Header Field + + The purpose of the Content-Type field is to describe the data + contained in the body fully enough that the receiving user agent can + pick an appropriate agent or mechanism to present the data to the + user, or otherwise deal with the data in an appropriate manner. + + HISTORICAL NOTE: The Content-Type header field was first defined in + RFC 1049. RFC 1049 Content-types used a simpler and less powerful + syntax, but one that is largely compatible with the mechanism given + here. + + The Content-Type header field is used to specify the nature of the + data in the body of an entity, by giving type and subtype + identifiers, and by providing auxiliary information that may be + required for certain types. After the type and subtype names, the + remainder of the header field is simply a set of parameters, + specified in an attribute/value notation. The set of meaningful + parameters differs for the different types. In particular, there are + NO globally-meaningful parameters that apply to all content-types. + Global mechanisms are best addressed, in the MIME model, by the + definition of additional Content-* header fields. The ordering of + parameters is not significant. Among the defined parameters is a + "charset" parameter by which the character set used in the body may + be declared. Comments are allowed in accordance with RFC 822 rules + for structured header fields. + + In general, the top-level Content-Type is used to declare the general + type of data, while the subtype specifies a specific format for that + type of data. Thus, a Content-Type of "image/xyz" is enough to tell + a user agent that the data is an image, even if the user agent has no + knowledge of the specific image format "xyz". Such information can + be used, for example, to decide whether or not to show a user the raw + data from an unrecognized subtype -- such an action might be + reasonable for unrecognized subtypes of text, but not for + unrecognized subtypes of image or audio. For this reason, registered + subtypes of audio, image, text, and video, should not contain + embedded information that is really of a different type. Such + compound types should be represented using the "multipart" or + "application" types. + + Parameters are modifiers of the content-subtype, and do not + fundamentally affect the requirements of the host system. Although + most parameters make sense only with certain content-types, others + are "global" in the sense that they might apply to any subtype. For + example, the "boundary" parameter makes sense only for the + "multipart" content-type, but the "charset" parameter might make + sense with several content-types. + + + +Borenstein & Freed [Page 9] + +RFC 1521 MIME September 1993 + + + An initial set of seven Content-Types is defined by this document. + This set of top-level names is intended to be substantially complete. + It is expected that additions to the larger set of supported types + can generally be accomplished by the creation of new subtypes of + these initial types. In the future, more top-level types may be + defined only by an extension to this standard. If another primary + type is to be used for any reason, it must be given a name starting + with "X-" to indicate its non-standard status and to avoid a + potential conflict with a future official name. + + In the Augmented BNF notation of RFC 822, a Content-Type header field + value is defined as follows: + + content := "Content-Type" ":" type "/" subtype *(";" + parameter) + ; case-insensitive matching of type and subtype + + type := "application" / "audio" + / "image" / "message" + / "multipart" / "text" + / "video" / extension-token + ; All values case-insensitive + + extension-token := x-token / iana-token + + iana-token := + + x-token := + + subtype := token ; case-insensitive + + parameter := attribute "=" value + + attribute := token ; case-insensitive + + value := token / quoted-string + + token := 1* + + tspecials := "(" / ")" / "<" / ">" / "@" + / "," / ";" / ":" / "\" / <"> + / "/" / "[" / "]" / "?" / "=" + ; Must be in quoted-string, + ; to use within parameter values + + + +Borenstein & Freed [Page 10] + +RFC 1521 MIME September 1993 + + + Note that the definition of "tspecials" is the same as the RFC 822 + definition of "specials" with the addition of the three characters + "/", "?", and "=", and the removal of ".". + + Note also that a subtype specification is MANDATORY. There are no + default subtypes. + + The type, subtype, and parameter names are not case sensitive. For + example, TEXT, Text, and TeXt are all equivalent. Parameter values + are normally case sensitive, but certain parameters are interpreted + to be case-insensitive, depending on the intended use. (For example, + multipart boundaries are case-sensitive, but the "access-type" for + message/External-body is not case-sensitive.) + + Beyond this syntax, the only constraint on the definition of subtype + names is the desire that their uses must not conflict. That is, it + would be undesirable to have two different communities using + "Content-Type: application/foobar" to mean two different things. The + process of defining new content-subtypes, then, is not intended to be + a mechanism for imposing restrictions, but simply a mechanism for + publicizing the usages. There are, therefore, two acceptable + mechanisms for defining new Content-Type subtypes: + + 1. Private values (starting with "X-") may be + defined bilaterally between two cooperating + agents without outside registration or + standardization. + + 2. New standard values must be documented, + registered with, and approved by IANA, as + described in Appendix E. Where intended for + public use, the formats they refer to must + also be defined by a published specification, + and possibly offered for standardization. + + The seven standard initial predefined Content-Types are detailed in + the bulk of this document. They are: + + text -- textual information. The primary subtype, + "plain", indicates plain (unformatted) text. No + special software is required to get the full + meaning of the text, aside from support for the + indicated character set. Subtypes are to be used + for enriched text in forms where application + software may enhance the appearance of the text, + but such software must not be required in order to + get the general idea of the content. Possible + subtypes thus include any readable word processor + + + +Borenstein & Freed [Page 11] + +RFC 1521 MIME September 1993 + + + format. A very simple and portable subtype, + richtext, was defined in RFC 1341, with a future + revision expected. + + multipart -- data consisting of multiple parts of + independent data types. Four initial subtypes + are defined, including the primary "mixed" + subtype, "alternative" for representing the same + data in multiple formats, "parallel" for parts + intended to be viewed simultaneously, and "digest" + for multipart entities in which each part is of + type "message". + + message -- an encapsulated message. A body of + Content-Type "message" is itself all or part of a + fully formatted RFC 822 conformant message which + may contain its own different Content-Type header + field. The primary subtype is "rfc822". The + "partial" subtype is defined for partial messages, + to permit the fragmented transmission of bodies + that are thought to be too large to be passed + through mail transport facilities. Another + subtype, "External-body", is defined for + specifying large bodies by reference to an + external data source. + + image -- image data. Image requires a display device + (such as a graphical display, a printer, or a FAX + machine) to view the information. Initial + subtypes are defined for two widely-used image + formats, jpeg and gif. + + audio -- audio data, with initial subtype "basic". + Audio requires an audio output device (such as a + speaker or a telephone) to "display" the contents. + + video -- video data. Video requires the capability to + display moving images, typically including + specialized hardware and software. The initial + subtype is "mpeg". + + application -- some other kind of data, typically + either uninterpreted binary data or information to + be processed by a mail-based application. The + primary subtype, "octet-stream", is to be used in + the case of uninterpreted binary data, in which + case the simplest recommended action is to offer + to write the information into a file for the user. + + + +Borenstein & Freed [Page 12] + +RFC 1521 MIME September 1993 + + + An additional subtype, "PostScript", is defined + for transporting PostScript documents in bodies. + Other expected uses for "application" include + spreadsheets, data for mail-based scheduling + systems, and languages for "active" + (computational) email. (Note that active email + and other application data may entail several + security considerations, which are discussed later + in this memo, particularly in the context of + application/PostScript.) + + Default RFC 822 messages are typed by this protocol as plain text in + the US-ASCII character set, which can be explicitly specified as + "Content-type: text/plain; charset=us-ascii". If no Content-Type is + specified, this default is assumed. In the presence of a MIME- + Version header field, a receiving User Agent can also assume that + plain US-ASCII text was the sender's intent. In the absence of a + MIME-Version specification, plain US-ASCII text must still be + assumed, but the sender's intent might have been otherwise. + + RATIONALE: In the absence of any Content-Type header field or + MIME-Version header field, it is impossible to be certain that a + message is actually text in the US-ASCII character set, since it + might well be a message that, using the conventions that predate + this document, includes text in another character set or non- + textual data in a manner that cannot be automatically recognized + (e.g., a uuencoded compressed UNIX tar file). Although there is + no fully acceptable alternative to treating such untyped messages + as "text/plain; charset=us-ascii", implementors should remain + aware that if a message lacks both the MIME-Version and the + Content-Type header fields, it may in practice contain almost + anything. + + It should be noted that the list of Content-Type values given here + may be augmented in time, via the mechanisms described above, and + that the set of subtypes is expected to grow substantially. + + When a mail reader encounters mail with an unknown Content-type + value, it should generally treat it as equivalent to + "application/octet-stream", as described later in this document. + +5. The Content-Transfer-Encoding Header Field + + Many Content-Types which could usefully be transported via email are + represented, in their "natural" format, as 8-bit character or binary + data. Such data cannot be transmitted over some transport protocols. + For example, RFC 821 restricts mail messages to 7-bit US-ASCII data + with lines no longer than 1000 characters. + + + +Borenstein & Freed [Page 13] + +RFC 1521 MIME September 1993 + + + It is necessary, therefore, to define a standard mechanism for re- + encoding such data into a 7-bit short-line format. This document + specifies that such encodings will be indicated by a new "Content- + Transfer-Encoding" header field. The Content-Transfer-Encoding field + is used to indicate the type of transformation that has been used in + order to represent the body in an acceptable manner for transport. + + Unlike Content-Types, a proliferation of Content-Transfer-Encoding + values is undesirable and unnecessary. However, establishing only a + single Content-Transfer-Encoding mechanism does not seem possible. + There is a tradeoff between the desire for a compact and efficient + encoding of largely-binary data and the desire for a readable + encoding of data that is mostly, but not entirely, 7-bit data. For + this reason, at least two encoding mechanisms are necessary: a + "readable" encoding and a "dense" encoding. + + The Content-Transfer-Encoding field is designed to specify an + invertible mapping between the "native" representation of a type of + data and a representation that can be readily exchanged using 7 bit + mail transport protocols, such as those defined by RFC 821 (SMTP). + This field has not been defined by any previous standard. The field's + value is a single token specifying the type of encoding, as + enumerated below. Formally: + + encoding := "Content-Transfer-Encoding" ":" mechanism + + mechanism := "7bit" ; case-insensitive + / "quoted-printable" + / "base64" + / "8bit" + / "binary" + / x-token + + These values are not case sensitive. That is, Base64 and BASE64 and + bAsE64 are all equivalent. An encoding type of 7BIT requires that + the body is already in a seven-bit mail-ready representation. This + is the default value -- that is, "Content-Transfer-Encoding: 7BIT" is + assumed if the Content-Transfer-Encoding header field is not present. + + The values "8bit", "7bit", and "binary" all mean that NO encoding has + been performed. However, they are potentially useful as indications + of the kind of data contained in the object, and therefore of the + kind of encoding that might need to be performed for transmission in + a given transport system. In particular: + + "7bit" means that the data is all represented as short + lines of US-ASCII data. + + + + +Borenstein & Freed [Page 14] + +RFC 1521 MIME September 1993 + + + "8bit" means that the lines are short, but there may be + non-ASCII characters (octets with the high-order + bit set). + + "Binary" means that not only may non-ASCII characters + be present, but also that the lines are not + necessarily short enough for SMTP transport. + + The difference between "8bit" (or any other conceivable bit-width + token) and the "binary" token is that "binary" does not require + adherence to any limits on line length or to the SMTP CRLF semantics, + while the bit-width tokens do require such adherence. If the body + contains data in any bit-width other than 7-bit, the appropriate + bit-width Content-Transfer-Encoding token must be used (e.g., "8bit" + for unencoded 8 bit wide data). If the body contains binary data, + the "binary" Content-Transfer-Encoding token must be used. + + NOTE: The distinction between the Content-Transfer-Encoding values + of "binary", "8bit", etc. may seem unimportant, in that all of + them really mean "none" -- that is, there has been no encoding of + the data for transport. However, clear labeling will be of + enormous value to gateways between future mail transport systems + with differing capabilities in transporting data that do not meet + the restrictions of RFC 821 transport. + + Mail transport for unencoded 8-bit data is defined in RFC-1426 + [RFC-1426]. As of the publication of this document, there are no + standardized Internet mail transports for which it is legitimate + to include unencoded binary data in mail bodies. Thus there are + no circumstances in which the "binary" Content-Transfer-Encoding + is actually legal on the Internet. However, in the event that + binary mail transport becomes a reality in Internet mail, or when + this document is used in conjunction with any other binary-capable + transport mechanism, binary bodies should be labeled as such using + this mechanism. + + NOTE: The five values defined for the Content-Transfer-Encoding + field imply nothing about the Content-Type other than the + algorithm by which it was encoded or the transport system + requirements if unencoded. + + Implementors may, if necessary, define new Content-Transfer-Encoding + values, but must use an x-token, which is a name prefixed by "X-" to + indicate its non-standard status, e.g., "Content-Transfer-Encoding: + x-my-new-encoding". However, unlike Content-Types and subtypes, the + creation of new Content-Transfer-Encoding values is explicitly and + strongly discouraged, as it seems likely to hinder interoperability + with little potential benefit. Their use is allowed only as the + + + +Borenstein & Freed [Page 15] + +RFC 1521 MIME September 1993 + + + result of an agreement between cooperating user agents. + + If a Content-Transfer-Encoding header field appears as part of a + message header, it applies to the entire body of that message. If a + Content-Transfer-Encoding header field appears as part of a body + part's headers, it applies only to the body of that body part. If an + entity is of type "multipart" or "message", the Content-Transfer- + Encoding is not permitted to have any value other than a bit width + (e.g., "7bit", "8bit", etc.) or "binary". + + It should be noted that email is character-oriented, so that the + mechanisms described here are mechanisms for encoding arbitrary octet + streams, not bit streams. If a bit stream is to be encoded via one + of these mechanisms, it must first be converted to an 8-bit byte + stream using the network standard bit order ("big-endian"), in which + the earlier bits in a stream become the higher-order bits in a byte. + A bit stream not ending at an 8-bit boundary must be padded with + zeroes. This document provides a mechanism for noting the addition + of such padding in the case of the application Content-Type, which + has a "padding" parameter. + + The encoding mechanisms defined here explicitly encode all data in + ASCII. Thus, for example, suppose an entity has header fields such + as: + + Content-Type: text/plain; charset=ISO-8859-1 + Content-transfer-encoding: base64 + + This must be interpreted to mean that the body is a base64 ASCII + encoding of data that was originally in ISO-8859-1, and will be in + that character set again after decoding. + + The following sections will define the two standard encoding + mechanisms. The definition of new content-transfer-encodings is + explicitly discouraged and should only occur when absolutely + necessary. All content-transfer-encoding namespace except that + beginning with "X-" is explicitly reserved to the IANA for future + use. Private agreements about content-transfer-encodings are also + explicitly discouraged. + + Certain Content-Transfer-Encoding values may only be used on certain + Content-Types. In particular, it is expressly forbidden to use any + encodings other than "7bit", "8bit", or "binary" with any Content- + Type that recursively includes other Content-Type fields, notably the + "multipart" and "message" Content-Types. All encodings that are + desired for bodies of type multipart or message must be done at the + innermost level, by encoding the actual body that needs to be + encoded. + + + +Borenstein & Freed [Page 16] + +RFC 1521 MIME September 1993 + + + NOTE ON ENCODING RESTRICTIONS: Though the prohibition against + using content-transfer-encodings on data of type multipart or + message may seem overly restrictive, it is necessary to prevent + nested encodings, in which data are passed through an encoding + algorithm multiple times, and must be decoded multiple times in + order to be properly viewed. Nested encodings add considerable + complexity to user agents: aside from the obvious efficiency + problems with such multiple encodings, they can obscure the basic + structure of a message. In particular, they can imply that + several decoding operations are necessary simply to find out what + types of objects a message contains. Banning nested encodings may + complicate the job of certain mail gateways, but this seems less + of a problem than the effect of nested encodings on user agents. + + NOTE ON THE RELATIONSHIP BETWEEN CONTENT-TYPE AND CONTENT- + TRANSFER-ENCODING: It may seem that the Content-Transfer-Encoding + could be inferred from the characteristics of the Content-Type + that is to be encoded, or, at the very least, that certain + Content-Transfer-Encodings could be mandated for use with specific + Content-Types. There are several reasons why this is not the case. + First, given the varying types of transports used for mail, some + encodings may be appropriate for some Content-Type/transport + combinations and not for others. (For example, in an 8-bit + transport, no encoding would be required for text in certain + character sets, while such encodings are clearly required for 7- + bit SMTP.) Second, certain Content-Types may require different + types of transfer encoding under different circumstances. For + example, many PostScript bodies might consist entirely of short + lines of 7-bit data and hence require little or no encoding. + Other PostScript bodies (especially those using Level 2 + PostScript's binary encoding mechanism) may only be reasonably + represented using a binary transport encoding. Finally, since + Content-Type is intended to be an open-ended specification + mechanism, strict specification of an association between + Content-Types and encodings effectively couples the specification + of an application protocol with a specific lower-level transport. + This is not desirable since the developers of a Content-Type + should not have to be aware of all the transports in use and what + their limitations are. + + NOTE ON TRANSLATING ENCODINGS: The quoted-printable and base64 + encodings are designed so that conversion between them is + possible. The only issue that arises in such a conversion is the + handling of line breaks. When converting from quoted-printable to + base64 a line break must be converted into a CRLF sequence. + Similarly, a CRLF sequence in base64 data must be converted to a + quoted-printable line break, but ONLY when converting text data. + + + + +Borenstein & Freed [Page 17] + +RFC 1521 MIME September 1993 + + + NOTE ON CANONICAL ENCODING MODEL: There was some confusion, in + earlier drafts of this memo, regarding the model for when email + data was to be converted to canonical form and encoded, and in + particular how this process would affect the treatment of CRLFs, + given that the representation of newlines varies greatly from + system to system, and the relationship between content-transfer- + encodings and character sets. For this reason, a canonical model + for encoding is presented as Appendix G. + +5.1. Quoted-Printable Content-Transfer-Encoding + + The Quoted-Printable encoding is intended to represent data that + largely consists of octets that correspond to printable characters in + the ASCII character set. It encodes the data in such a way that the + resulting octets are unlikely to be modified by mail transport. If + the data being encoded are mostly ASCII text, the encoded form of the + data remains largely recognizable by humans. A body which is + entirely ASCII may also be encoded in Quoted-Printable to ensure the + integrity of the data should the message pass through a character- + translating, and/or line-wrapping gateway. + + In this encoding, octets are to be represented as determined by the + following rules: + + Rule #1: (General 8-bit representation) Any octet, except those + indicating a line break according to the newline convention of the + canonical (standard) form of the data being encoded, may be + represented by an "=" followed by a two digit hexadecimal + representation of the octet's value. The digits of the + hexadecimal alphabet, for this purpose, are "0123456789ABCDEF". + Uppercase letters must be used when sending hexadecimal data, + though a robust implementation may choose to recognize lowercase + letters on receipt. Thus, for example, the value 12 (ASCII form + feed) can be represented by "=0C", and the value 61 (ASCII EQUAL + SIGN) can be represented by "=3D". Except when the following + rules allow an alternative encoding, this rule is mandatory. + + Rule #2: (Literal representation) Octets with decimal values of 33 + through 60 inclusive, and 62 through 126, inclusive, MAY be + represented as the ASCII characters which correspond to those + octets (EXCLAMATION POINT through LESS THAN, and GREATER THAN + through TILDE, respectively). + + Rule #3: (White Space): Octets with values of 9 and 32 MAY be + represented as ASCII TAB (HT) and SPACE characters, respectively, + but MUST NOT be so represented at the end of an encoded line. Any + TAB (HT) or SPACE characters on an encoded line MUST thus be + followed on that line by a printable character. In particular, an + + + +Borenstein & Freed [Page 18] + +RFC 1521 MIME September 1993 + + + "=" at the end of an encoded line, indicating a soft line break + (see rule #5) may follow one or more TAB (HT) or SPACE characters. + It follows that an octet with value 9 or 32 appearing at the end + of an encoded line must be represented according to Rule #1. This + rule is necessary because some MTAs (Message Transport Agents, + programs which transport messages from one user to another, or + perform a part of such transfers) are known to pad lines of text + with SPACEs, and others are known to remove "white space" + characters from the end of a line. Therefore, when decoding a + Quoted-Printable body, any trailing white space on a line must be + deleted, as it will necessarily have been added by intermediate + transport agents. + + Rule #4 (Line Breaks): A line break in a text body, independent of + what its representation is following the canonical representation + of the data being encoded, must be represented by a (RFC 822) line + break, which is a CRLF sequence, in the Quoted-Printable encoding. + Since the canonical representation of types other than text do not + generally include the representation of line breaks, no hard line + breaks (i.e. line breaks that are intended to be meaningful and + to be displayed to the user) should occur in the quoted-printable + encoding of such types. Of course, occurrences of "=0D", "=0A", + "0A=0D" and "=0D=0A" will eventually be encountered. In general, + however, base64 is preferred over quoted-printable for binary + data. + + Note that many implementations may elect to encode the local + representation of various content types directly, as described in + Appendix G. In particular, this may apply to plain text material + on systems that use newline conventions other than CRLF + delimiters. Such an implementation is permissible, but the + generation of line breaks must be generalized to account for the + case where alternate representations of newline sequences are + used. + + Rule #5 (Soft Line Breaks): The Quoted-Printable encoding REQUIRES + that encoded lines be no more than 76 characters long. If longer + lines are to be encoded with the Quoted-Printable encoding, 'soft' + line breaks must be used. An equal sign as the last character on a + encoded line indicates such a non-significant ('soft') line break + in the encoded text. Thus if the "raw" form of the line is a + single unencoded line that says: + + Now's the time for all folk to come to the aid of + their country. + + This can be represented, in the Quoted-Printable encoding, as + + + + +Borenstein & Freed [Page 19] + +RFC 1521 MIME September 1993 + + + Now's the time = + for all folk to come= + to the aid of their country. + + This provides a mechanism with which long lines are encoded in + such a way as to be restored by the user agent. The 76 character + limit does not count the trailing CRLF, but counts all other + characters, including any equal signs. + + Since the hyphen character ("-") is represented as itself in the + Quoted-Printable encoding, care must be taken, when encapsulating a + quoted-printable encoded body in a multipart entity, to ensure that + the encapsulation boundary does not appear anywhere in the encoded + body. (A good strategy is to choose a boundary that includes a + character sequence such as "=_" which can never appear in a quoted- + printable body. See the definition of multipart messages later in + this document.) + + NOTE: The quoted-printable encoding represents something of a + compromise between readability and reliability in transport. + Bodies encoded with the quoted-printable encoding will work + reliably over most mail gateways, but may not work perfectly over + a few gateways, notably those involving translation into EBCDIC. + (In theory, an EBCDIC gateway could decode a quoted-printable body + and re-encode it using base64, but such gateways do not yet + exist.) A higher level of confidence is offered by the base64 + Content-Transfer-Encoding. A way to get reasonably reliable + transport through EBCDIC gateways is to also quote the ASCII + characters + + !"#$@[\]^`{|}~ + + according to rule #1. See Appendix B for more information. + + Because quoted-printable data is generally assumed to be line- + oriented, it is to be expected that the representation of the breaks + between the lines of quoted printable data may be altered in + transport, in the same manner that plain text mail has always been + altered in Internet mail when passing between systems with differing + newline conventions. If such alterations are likely to constitute a + corruption of the data, it is probably more sensible to use the + base64 encoding rather than the quoted-printable encoding. + + WARNING TO IMPLEMENTORS: If binary data are encoded in quoted- + printable, care must be taken to encode CR and LF characters as "=0D" + and "=0A", respectively. In particular, a CRLF sequence in binary + data should be encoded as "=0D=0A". Otherwise, if CRLF were + represented as a hard line break, it might be incorrectly decoded on + + + +Borenstein & Freed [Page 20] + +RFC 1521 MIME September 1993 + + + platforms with different line break conventions. + + For formalists, the syntax of quoted-printable data is described by + the following grammar: + + quoted-printable := ([*(ptext / SPACE / TAB) ptext] ["="] CRLF) + ; Maximum line length of 76 characters excluding CRLF + + ptext := octet / 127, =, SPACE, or TAB, + ; and is recommended for any characters not listed in + ; Appendix B as "mail-safe". + +5.2. Base64 Content-Transfer-Encoding + + The Base64 Content-Transfer-Encoding is designed to represent + arbitrary sequences of octets in a form that need not be humanly + readable. The encoding and decoding algorithms are simple, but the + encoded data are consistently only about 33 percent larger than the + unencoded data. This encoding is virtually identical to the one used + in Privacy Enhanced Mail (PEM) applications, as defined in RFC 1421. + The base64 encoding is adapted from RFC 1421, with one change: base64 + eliminates the "*" mechanism for embedded clear text. + + A 65-character subset of US-ASCII is used, enabling 6 bits to be + represented per printable character. (The extra 65th character, "=", + is used to signify a special processing function.) + + NOTE: This subset has the important property that it is + represented identically in all versions of ISO 646, including US + ASCII, and all characters in the subset are also represented + identically in all versions of EBCDIC. Other popular encodings, + such as the encoding used by the uuencode utility and the base85 + encoding specified as part of Level 2 PostScript, do not share + these properties, and thus do not fulfill the portability + requirements a binary transport encoding for mail must meet. + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8-bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + When encoding a bit stream via the base64 encoding, the bit stream + must be presumed to be ordered with the most-significant-bit first. + + + +Borenstein & Freed [Page 21] + +RFC 1521 MIME September 1993 + + + That is, the first bit in the stream will be the high-order bit in + the first byte, and the eighth bit will be the low-order bit in the + first byte, and so on. + + Each 6-bit group is used as an index into an array of 64 printable + characters. The character referenced by the index is placed in the + output string. These characters, identified in Table 1, below, are + selected so as to be universally representable, and the set excludes + characters with particular significance to SMTP (e.g., ".", CR, LF) + and to the encapsulation boundaries defined in this document (e.g., + "-"). + + Table 1: The Base64 Alphabet + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + The output stream (encoded bytes) must be represented in lines of no + more than 76 characters each. All line breaks or other characters + not found in Table 1 must be ignored by decoding software. In base64 + data, characters other than those in Table 1, line breaks, and other + white space probably indicate a transmission error, about which a + warning message or even a message rejection might be appropriate + under some circumstances. + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a body. When fewer than 24 input bits + are available in an input group, zero bits are added (on the right) + to form an integral number of 6-bit groups. Padding at the end of + the data is performed using the '=' character. Since all base64 + input is an integral number of octets, only the following cases can + + + +Borenstein & Freed [Page 22] + +RFC 1521 MIME September 1993 + + + arise: (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded output will be + an integral multiple of 4 characters with no "=" padding, (2) the + final quantum of encoding input is exactly 8 bits; here, the final + unit of encoded output will be two characters followed by two "=" + padding characters, or (3) the final quantum of encoding input is + exactly 16 bits; here, the final unit of encoded output will be three + characters followed by one "=" padding character. + + Because it is used only for padding at the end of the data, the + occurrence of any '=' characters may be taken as evidence that the + end of the data has been reached (without truncation in transit). No + such assurance is possible, however, when the number of octets + transmitted was a multiple of three. + + Any characters outside of the base64 alphabet are to be ignored in + base64-encoded data. The same applies to any illegal sequence of + characters in the base64 encoding, such as "=====" + + Care must be taken to use the proper octets for line breaks if base64 + encoding is applied directly to text material that has not been + converted to canonical form. In particular, text line breaks must be + converted into CRLF sequences prior to base64 encoding. The important + thing to note is that this may be done directly by the encoder rather + than in a prior canonicalization step in some implementations. + + NOTE: There is no need to worry about quoting apparent + encapsulation boundaries within base64-encoded parts of multipart + entities because no hyphen characters are used in the base64 + encoding. + +6. Additional Content-Header Fields + +6.1. Optional Content-ID Header Field + + In constructing a high-level user agent, it may be desirable to allow + one body to make reference to another. Accordingly, bodies may be + labeled using the "Content-ID" header field, which is syntactically + identical to the "Message-ID" header field: + + id := "Content-ID" ":" msg-id + Like the Message-ID values, Content-ID values must be generated to be + world-unique. + + The Content-ID value may be used for uniquely identifying MIME + entities in several contexts, particularly for cacheing data + referenced by the message/external-body mechanism. Although the + Content-ID header is generally optional, its use is mandatory in + + + +Borenstein & Freed [Page 23] + +RFC 1521 MIME September 1993 + + + implementations which generate data of the optional MIME Content-type + "message/external-body". That is, each message/external-body entity + must have a Content-ID field to permit cacheing of such data. + + It is also worth noting that the Content-ID value has special + semantics in the case of the multipart/alternative content-type. + This is explained in the section of this document dealing with + multipart/alternative. + +6.2. Optional Content-Description Header Field + + The ability to associate some descriptive information with a given + body is often desirable. For example, it may be useful to mark an + "image" body as "a picture of the Space Shuttle Endeavor." Such text + may be placed in the Content-Description header field. + + description := "Content-Description" ":" *text + + The description is presumed to be given in the US-ASCII character + set, although the mechanism specified in [RFC-1522] may be used for + non-US-ASCII Content-Description values. + +7. The Predefined Content-Type Values + + This document defines seven initial Content-Type values and an + extension mechanism for private or experimental types. Further + standard types must be defined by new published specifications. It + is expected that most innovation in new types of mail will take place + as subtypes of the seven types defined here. The most essential + characteristics of the seven content-types are summarized in Appendix + F. + +7.1 The Text Content-Type + + The text Content-Type is intended for sending material which is + principally textual in form. It is the default Content-Type. A + "charset" parameter may be used to indicate the character set of the + body text for some text subtypes, notably including the primary + subtype, "text/plain", which indicates plain (unformatted) text. The + default Content-Type for Internet mail is "text/plain; charset=us- + ascii". + + Beyond plain text, there are many formats for representing what might + be known as "extended text" -- text with embedded formatting and + presentation information. An interesting characteristic of many such + representations is that they are to some extent readable even without + the software that interprets them. It is useful, then, to + distinguish them, at the highest level, from such unreadable data as + + + +Borenstein & Freed [Page 24] + +RFC 1521 MIME September 1993 + + + images, audio, or text represented in an unreadable form. In the + absence of appropriate interpretation software, it is reasonable to + show subtypes of text to the user, while it is not reasonable to do + so with most nontextual data. + + Such formatted textual data should be represented using subtypes of + text. Plausible subtypes of text are typically given by the common + name of the representation format, e.g., "text/richtext" [RFC-1341]. + +7.1.1. The charset parameter + + A critical parameter that may be specified in the Content-Type field + for text/plain data is the character set. This is specified with a + "charset" parameter, as in: + + Content-type: text/plain; charset=us-ascii + + Unlike some other parameter values, the values of the charset + parameter are NOT case sensitive. The default character set, which + must be assumed in the absence of a charset parameter, is US-ASCII. + + The specification for any future subtypes of "text" must specify + whether or not they will also utilize a "charset" parameter, and may + possibly restrict its values as well. When used with a particular + body, the semantics of the "charset" parameter should be identical to + those specified here for "text/plain", i.e., the body consists + entirely of characters in the given charset. In particular, definers + of future text subtypes should pay close attention the the + implications of multibyte character sets for their subtype + definitions. + + This RFC specifies the definition of the charset parameter for the + purposes of MIME to be a unique mapping of a byte stream to glyphs, a + mapping which does not require external profiling information. + + An initial list of predefined character set names can be found at the + end of this section. Additional character sets may be registered + with IANA, although the standardization of their use requires the + usual IESG [RFC-1340] review and approval. Note that if the + specified character set includes 8-bit data, a Content-Transfer- + Encoding header field and a corresponding encoding on the data are + required in order to transmit the body via some mail transfer + protocols, such as SMTP. + + The default character set, US-ASCII, has been the subject of some + confusion and ambiguity in the past. Not only were there some + ambiguities in the definition, there have been wide variations in + practice. In order to eliminate such ambiguity and variations in the + + + +Borenstein & Freed [Page 25] + +RFC 1521 MIME September 1993 + + + future, it is strongly recommended that new user agents explicitly + specify a character set via the Content-Type header field. "US- + ASCII" does not indicate an arbitrary seven-bit character code, but + specifies that the body uses character coding that uses the exact + correspondence of codes to characters specified in ASCII. National + use variations of ISO 646 [ISO-646] are NOT ASCII and their use in + Internet mail is explicitly discouraged. The omission of the ISO 646 + character set is deliberate in this regard. The character set name + of "US-ASCII" explicitly refers to ANSI X3.4-1986 [US-ASCII] only. + The character set name "ASCII" is reserved and must not be used for + any purpose. + + NOTE: RFC 821 explicitly specifies "ASCII", and references an + earlier version of the American Standard. Insofar as one of the + purposes of specifying a Content-Type and character set is to + permit the receiver to unambiguously determine how the sender + intended the coded message to be interpreted, assuming anything + other than "strict ASCII" as the default would risk unintentional + and incompatible changes to the semantics of messages now being + transmitted. This also implies that messages containing + characters coded according to national variations on ISO 646, or + using code-switching procedures (e.g., those of ISO 2022), as well + as 8-bit or multiple octet character encodings MUST use an + appropriate character set specification to be consistent with this + specification. + + The complete US-ASCII character set is listed in [US-ASCII]. Note + that the control characters including DEL (0-31, 127) have no defined + meaning apart from the combination CRLF (ASCII values 13 and 10) + indicating a new line. Two of the characters have de facto meanings + in wide use: FF (12) often means "start subsequent text on the + beginning of a new page"; and TAB or HT (9) often (though not always) + means "move the cursor to the next available column after the current + position where the column number is a multiple of 8 (counting the + first column as column 0)." Apart from this, any use of the control + characters or DEL in a body must be part of a private agreement + between the sender and recipient. Such private agreements are + discouraged and should be replaced by the other capabilities of this + document. + + NOTE: Beyond US-ASCII, an enormous proliferation of character sets + is possible. It is the opinion of the IETF working group that a + large number of character sets is NOT a good thing. We would + prefer to specify a single character set that can be used + universally for representing all of the world's languages in + electronic mail. Unfortunately, existing practice in several + communities seems to point to the continued use of multiple + character sets in the near future. For this reason, we define + + + +Borenstein & Freed [Page 26] + +RFC 1521 MIME September 1993 + + + names for a small number of character sets for which a strong + constituent base exists. + + The defined charset values are: + + US-ASCII -- as defined in [US-ASCII]. + + ISO-8859-X -- where "X" is to be replaced, as necessary, for the + parts of ISO-8859 [ISO-8859]. Note that the ISO 646 + character sets have deliberately been omitted in favor of + their 8859 replacements, which are the designated character + sets for Internet mail. As of the publication of this + document, the legitimate values for "X" are the digits 1 + through 9. + + The character sets specified above are the ones that were relatively + uncontroversial during the drafting of MIME. This document does not + endorse the use of any particular character set other than US-ASCII, + and recognizes that the future evolution of world character sets + remains unclear. It is expected that in the future, additional + character sets will be registered for use in MIME. + + Note that the character set used, if anything other than US-ASCII, + must always be explicitly specified in the Content-Type field. + + No other character set name may be used in Internet mail without the + publication of a formal specification and its registration with IANA, + or by private agreement, in which case the character set name must + begin with "X-". + + Implementors are discouraged from defining new character sets for + mail use unless absolutely necessary. + + The "charset" parameter has been defined primarily for the purpose of + textual data, and is described in this section for that reason. + However, it is conceivable that non-textual data might also wish to + specify a charset value for some purpose, in which case the same + syntax and values should be used. + + In general, mail-sending software must always use the "lowest common + denominator" character set possible. For example, if a body contains + only US-ASCII characters, it must be marked as being in the US-ASCII + character set, not ISO-8859-1, which, like all the ISO-8859 family of + character sets, is a superset of US-ASCII. More generally, if a + widely-used character set is a subset of another character set, and a + body contains only characters in the widely-used subset, it must be + labeled as being in that subset. This will increase the chances that + the recipient will be able to view the mail correctly. + + + +Borenstein & Freed [Page 27] + +RFC 1521 MIME September 1993 + + +7.1.2. The Text/plain subtype + + The primary subtype of text is "plain". This indicates plain + (unformatted) text. The default Content-Type for Internet mail, + "text/plain; charset=us-ascii", describes existing Internet practice. + That is, it is the type of body defined by RFC 822. + + No other text subtype is defined by this document. + + The formal grammar for the content-type header field for text is as + follows: + + text-type := "text" "/" text-subtype [";" "charset" "=" charset] + + text-subtype := "plain" / extension-token + + charset := "us-ascii"/ "iso-8859-1"/ "iso-8859-2"/ "iso-8859-3" + / "iso-8859-4"/ "iso-8859-5"/ "iso-8859-6"/ "iso-8859-7" + / "iso-8859-8" / "iso-8859-9" / extension-token + ; case insensitive + +7.2. The Multipart Content-Type + + In the case of multiple part entities, in which one or more different + sets of data are combined in a single body, a "multipart" Content- + Type field must appear in the entity's header. The body must then + contain one or more "body parts," each preceded by an encapsulation + boundary, and the last one followed by a closing boundary. Each part + starts with an encapsulation boundary, and then contains a body part + consisting of header area, a blank line, and a body area. Thus a + body part is similar to an RFC 822 message in syntax, but different + in meaning. + + A body part is NOT to be interpreted as actually being an RFC 822 + message. To begin with, NO header fields are actually required in + body parts. A body part that starts with a blank line, therefore, is + allowed and is a body part for which all default values are to be + assumed. In such a case, the absence of a Content-Type header field + implies that the corresponding body is plain US-ASCII text. The only + header fields that have defined meaning for body parts are those the + names of which begin with "Content-". All other header fields are + generally to be ignored in body parts. Although they should + generally be retained in mail processing, they may be discarded by + gateways if necessary. Such other fields are permitted to appear in + body parts but must not be depended on. "X-" fields may be created + for experimental or private purposes, with the recognition that the + information they contain may be lost at some gateways. + + + + +Borenstein & Freed [Page 28] + +RFC 1521 MIME September 1993 + + + NOTE: The distinction between an RFC 822 message and a body part + is subtle, but important. A gateway between Internet and X.400 + mail, for example, must be able to tell the difference between a + body part that contains an image and a body part that contains an + encapsulated message, the body of which is an image. In order to + represent the latter, the body part must have "Content-Type: + message", and its body (after the blank line) must be the + encapsulated message, with its own "Content-Type: image" header + field. The use of similar syntax facilitates the conversion of + messages to body parts, and vice versa, but the distinction + between the two must be understood by implementors. (For the + special case in which all parts actually are messages, a "digest" + subtype is also defined.) + + As stated previously, each body part is preceded by an encapsulation + boundary. The encapsulation boundary MUST NOT appear inside any of + the encapsulated parts. Thus, it is crucial that the composing agent + be able to choose and specify the unique boundary that will separate + the parts. + + All present and future subtypes of the "multipart" type must use an + identical syntax. Subtypes may differ in their semantics, and may + impose additional restrictions on syntax, but must conform to the + required syntax for the multipart type. This requirement ensures + that all conformant user agents will at least be able to recognize + and separate the parts of any multipart entity, even of an + unrecognized subtype. + + As stated in the definition of the Content-Transfer-Encoding field, + no encoding other than "7bit", "8bit", or "binary" is permitted for + entities of type "multipart". The multipart delimiters and header + fields are always represented as 7-bit ASCII in any case (though the + header fields may encode non-ASCII header text as per [RFC-1522]), + and data within the body parts can be encoded on a part-by-part + basis, with Content-Transfer-Encoding fields for each appropriate + body part. + + Mail gateways, relays, and other mail handling agents are commonly + known to alter the top-level header of an RFC 822 message. In + particular, they frequently add, remove, or reorder header fields. + Such alterations are explicitly forbidden for the body part headers + embedded in the bodies of messages of type "multipart." + +7.2.1. Multipart: The common syntax + + All subtypes of "multipart" share a common syntax, defined in this + section. A simple example of a multipart message also appears in + this section. An example of a more complex multipart message is + + + +Borenstein & Freed [Page 29] + +RFC 1521 MIME September 1993 + + + given in Appendix C. + + The Content-Type field for multipart entities requires one parameter, + "boundary", which is used to specify the encapsulation boundary. The + encapsulation boundary is defined as a line consisting entirely of + two hyphen characters ("-", decimal code 45) followed by the boundary + parameter value from the Content-Type header field. + + NOTE: The hyphens are for rough compatibility with the earlier RFC + 934 method of message encapsulation, and for ease of searching for + the boundaries in some implementations. However, it should be + noted that multipart messages are NOT completely compatible with + RFC 934 encapsulations; in particular, they do not obey RFC 934 + quoting conventions for embedded lines that begin with hyphens. + This mechanism was chosen over the RFC 934 mechanism because the + latter causes lines to grow with each level of quoting. The + combination of this growth with the fact that SMTP implementations + sometimes wrap long lines made the RFC 934 mechanism unsuitable + for use in the event that deeply-nested multipart structuring is + ever desired. + + WARNING TO IMPLEMENTORS: The grammar for parameters on the Content- + type field is such that it is often necessary to enclose the + boundaries in quotes on the Content-type line. This is not always + necessary, but never hurts. Implementors should be sure to study the + grammar carefully in order to avoid producing illegal Content-type + fields. Thus, a typical multipart Content-Type header field might + look like this: + + Content-Type: multipart/mixed; + boundary=gc0p4Jq0M2Yt08jU534c0p + + But the following is illegal: + + Content-Type: multipart/mixed; + boundary=gc0p4Jq0M:2Yt08jU534c0p + + (because of the colon) and must instead be represented as + + Content-Type: multipart/mixed; + boundary="gc0p4Jq0M:2Yt08jU534c0p" + + This indicates that the entity consists of several parts, each itself + with a structure that is syntactically identical to an RFC 822 + message, except that the header area might be completely empty, and + that the parts are each preceded by the line + + --gc0p4Jq0M:2Yt08jU534c0p + + + +Borenstein & Freed [Page 30] + +RFC 1521 MIME September 1993 + + + Note that the encapsulation boundary must occur at the beginning of a + line, i.e., following a CRLF, and that the initial CRLF is considered + to be attached to the encapsulation boundary rather than part of the + preceding part. The boundary must be followed immediately either by + another CRLF and the header fields for the next part, or by two + CRLFs, in which case there are no header fields for the next part + (and it is therefore assumed to be of Content-Type text/plain). + + NOTE: The CRLF preceding the encapsulation line is conceptually + attached to the boundary so that it is possible to have a part + that does not end with a CRLF (line break). Body parts that must + be considered to end with line breaks, therefore, must have two + CRLFs preceding the encapsulation line, the first of which is part + of the preceding body part, and the second of which is part of the + encapsulation boundary. + + Encapsulation boundaries must not appear within the encapsulations, + and must be no longer than 70 characters, not counting the two + leading hyphens. + + The encapsulation boundary following the last body part is a + distinguished delimiter that indicates that no further body parts + will follow. Such a delimiter is identical to the previous + delimiters, with the addition of two more hyphens at the end of the + line: + + --gc0p4Jq0M2Yt08jU534c0p-- + + There appears to be room for additional information prior to the + first encapsulation boundary and following the final boundary. These + areas should generally be left blank, and implementations must ignore + anything that appears before the first boundary or after the last + one. + + NOTE: These "preamble" and "epilogue" areas are generally not used + because of the lack of proper typing of these parts and the lack + of clear semantics for handling these areas at gateways, + particularly X.400 gateways. However, rather than leaving the + preamble area blank, many MIME implementations have found this to + be a convenient place to insert an explanatory note for recipients + who read the message with pre-MIME software, since such notes will + be ignored by MIME-compliant software. + + NOTE: Because encapsulation boundaries must not appear in the body + parts being encapsulated, a user agent must exercise care to + choose a unique boundary. The boundary in the example above could + have been the result of an algorithm designed to produce + boundaries with a very low probability of already existing in the + + + +Borenstein & Freed [Page 31] + +RFC 1521 MIME September 1993 + + + data to be encapsulated without having to prescan the data. + Alternate algorithms might result in more 'readable' boundaries + for a recipient with an old user agent, but would require more + attention to the possibility that the boundary might appear in the + encapsulated part. The simplest boundary possible is something + like "---", with a closing boundary of "-----". + + As a very simple example, the following multipart message has two + parts, both of them plain text, one of them explicitly typed and one + of them implicitly typed: + + From: Nathaniel Borenstein + To: Ned Freed + Subject: Sample message + MIME-Version: 1.0 + Content-type: multipart/mixed; boundary="simple + boundary" + + This is the preamble. It is to be ignored, though it + is a handy place for mail composers to include an + explanatory note to non-MIME conformant readers. + --simple boundary + + This is implicitly typed plain ASCII text. + It does NOT end with a linebreak. + --simple boundary + Content-type: text/plain; charset=us-ascii + + This is explicitly typed plain ASCII text. + It DOES end with a linebreak. + + --simple boundary-- + This is the epilogue. It is also to be ignored. + + The use of a Content-Type of multipart in a body part within another + multipart entity is explicitly allowed. In such cases, for obvious + reasons, care must be taken to ensure that each nested multipart + entity must use a different boundary delimiter. See Appendix C for an + example of nested multipart entities. + + The use of the multipart Content-Type with only a single body part + may be useful in certain contexts, and is explicitly permitted. + + The only mandatory parameter for the multipart Content-Type is the + boundary parameter, which consists of 1 to 70 characters from a set + of characters known to be very robust through email gateways, and NOT + ending with white space. (If a boundary appears to end with white + space, the white space must be presumed to have been added by a + + + +Borenstein & Freed [Page 32] + +RFC 1521 MIME September 1993 + + + gateway, and must be deleted.) It is formally specified by the + following BNF: + + boundary := 0*69 bcharsnospace + + bchars := bcharsnospace / " " + + bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / "+" /"_" + / "," / "-" / "." / "/" / ":" / "=" / "?" + + Overall, the body of a multipart entity may be specified as + follows: + + multipart-body := preamble 1*encapsulation + close-delimiter epilogue + + encapsulation := delimiter body-part CRLF + + delimiter := "--" boundary CRLF ; taken from Content-Type field. + ; There must be no space + ; between "--" and boundary. + + close-delimiter := "--" boundary "--" CRLF ; Again, no space + by "--", + + preamble := discard-text ; to be ignored upon receipt. + + epilogue := discard-text ; to be ignored upon receipt. + + discard-text := *(*text CRLF) + + body-part := <"message" as defined in RFC 822, + with all header fields optional, and with the + specified delimiter not occurring anywhere in + the message body, either on a line by itself + or as a substring anywhere. Note that the + semantics of a part differ from the semantics + of a message, as described in the text.> + + NOTE: In certain transport enclaves, RFC 822 restrictions such as + the one that limits bodies to printable ASCII characters may not + be in force. (That is, the transport domains may resemble + standard Internet mail transport as specified in RFC821 and + assumed by RFC822, but without certain restrictions.) The + relaxation of these restrictions should be construed as locally + extending the definition of bodies, for example to include octets + outside of the ASCII range, as long as these extensions are + supported by the transport and adequately documented in the + + + +Borenstein & Freed [Page 33] + +RFC 1521 MIME September 1993 + + + Content-Transfer-Encoding header field. However, in no event are + headers (either message headers or body-part headers) allowed to + contain anything other than ASCII characters. + + NOTE: Conspicuously missing from the multipart type is a notion of + structured, related body parts. In general, it seems premature to + try to standardize interpart structure yet. It is recommended + that those wishing to provide a more structured or integrated + multipart messaging facility should define a subtype of multipart + that is syntactically identical, but that always expects the + inclusion of a distinguished part that can be used to specify the + structure and integration of the other parts, probably referring + to them by their Content-ID field. If this approach is used, + other implementations will not recognize the new subtype, but will + treat it as the primary subtype (multipart/mixed) and will thus be + able to show the user the parts that are recognized. + +7.2.2. The Multipart/mixed (primary) subtype + + The primary subtype for multipart, "mixed", is intended for use when + the body parts are independent and need to be bundled in a particular + order. Any multipart subtypes that an implementation does not + recognize must be treated as being of subtype "mixed". + +7.2.3. The Multipart/alternative subtype + + The multipart/alternative type is syntactically identical to + multipart/mixed, but the semantics are different. In particular, + each of the parts is an "alternative" version of the same + information. + + Systems should recognize that the content of the various parts are + interchangeable. Systems should choose the "best" type based on the + local environment and preferences, in some cases even through user + interaction. As with multipart/mixed, the order of body parts is + significant. In this case, the alternatives appear in an order of + increasing faithfulness to the original content. In general, the best + choice is the LAST part of a type supported by the recipient system's + local environment. + + Multipart/alternative may be used, for example, to send mail in a + fancy text format in such a way that it can easily be displayed + anywhere: + + + + + + + + +Borenstein & Freed [Page 34] + +RFC 1521 MIME September 1993 + + + From: Nathaniel Borenstein + To: Ned Freed + Subject: Formatted text mail + MIME-Version: 1.0 + Content-Type: multipart/alternative; boundary=boundary42 + + --boundary42 + + Content-Type: text/plain; charset=us-ascii + + ...plain text version of message goes here.... + --boundary42 + Content-Type: text/richtext + + .... RFC 1341 richtext version of same message goes here ... + --boundary42 + Content-Type: text/x-whatever + + .... fanciest formatted version of same message goes here + ... + --boundary42-- + + In this example, users whose mail system understood the "text/x- + whatever" format would see only the fancy version, while other users + would see only the richtext or plain text version, depending on the + capabilities of their system. + + In general, user agents that compose multipart/alternative entities + must place the body parts in increasing order of preference, that is, + with the preferred format last. For fancy text, the sending user + agent should put the plainest format first and the richest format + last. Receiving user agents should pick and display the last format + they are capable of displaying. In the case where one of the + alternatives is itself of type "multipart" and contains unrecognized + sub-parts, the user agent may choose either to show that alternative, + an earlier alternative, or both. + + NOTE: From an implementor's perspective, it might seem more + sensible to reverse this ordering, and have the plainest + alternative last. However, placing the plainest alternative first + is the friendliest possible option when multipart/alternative + entities are viewed using a non-MIME-conformant mail reader. + While this approach does impose some burden on conformant mail + readers, interoperability with older mail readers was deemed to be + more important in this case. + + It may be the case that some user agents, if they can recognize more + than one of the formats, will prefer to offer the user the choice of + + + +Borenstein & Freed [Page 35] + +RFC 1521 MIME September 1993 + + + which format to view. This makes sense, for example, if mail + includes both a nicely-formatted image version and an easily-edited + text version. What is most critical, however, is that the user not + automatically be shown multiple versions of the same data. Either + the user should be shown the last recognized version or should be + given the choice. + + NOTE ON THE SEMANTICS OF CONTENT-ID IN MULTIPART/ALTERNATIVE: Each + part of a multipart/alternative entity represents the same data, but + the mappings between the two are not necessarily without information + loss. For example, information is lost when translating ODA to + PostScript or plain text. It is recommended that each part should + have a different Content-ID value in the case where the information + content of the two parts is not identical. However, where the + information content is identical -- for example, where several parts + of type "application/external- body" specify alternate ways to access + the identical data -- the same Content-ID field value should be used, + to optimize any cacheing mechanisms that might be present on the + recipient's end. However, it is recommended that the Content-ID + values used by the parts should not be the same Content-ID value that + describes the multipart/alternative as a whole, if there is any such + Content-ID field. That is, one Content-ID value will refer to the + multipart/alternative entity, while one or more other Content-ID + values will refer to the parts inside it. + +7.2.4. The Multipart/digest subtype + + This document defines a "digest" subtype of the multipart Content- + Type. This type is syntactically identical to multipart/mixed, but + the semantics are different. In particular, in a digest, the default + Content-Type value for a body part is changed from "text/plain" to + "message/rfc822". This is done to allow a more readable digest + format that is largely compatible (except for the quoting convention) + with RFC 934. + + + + + + + + + + + + + + + + + +Borenstein & Freed [Page 36] + +RFC 1521 MIME September 1993 + + + A digest in this format might, then, look something like this: + + From: Moderator-Address + To: Recipient-List + MIME-Version: 1.0 + Subject: Internet Digest, volume 42 + Content-Type: multipart/digest; + boundary="---- next message ----" + + ------ next message ---- + + From: someone-else + Subject: my opinion + + ...body goes here ... + + ------ next message ---- + + From: someone-else-again + Subject: my different opinion + + ... another body goes here... + + ------ next message ------ + +7.2.5. The Multipart/parallel subtype + + This document defines a "parallel" subtype of the multipart Content- + Type. This type is syntactically identical to multipart/mixed, but + the semantics are different. In particular, in a parallel entity, + the order of body parts is not significant. + + A common presentation of this type is to display all of the parts + simultaneously on hardware and software that are capable of doing so. + However, composing agents should be aware that many mail readers will + lack this capability and will show the parts serially in any event. + +7.2.6. Other Multipart subtypes + + Other multipart subtypes are expected in the future. MIME + implementations must in general treat unrecognized subtypes of + multipart as being equivalent to "multipart/mixed". + + The formal grammar for content-type header fields for multipart data + is given by: + + multipart-type := "multipart" "/" multipart-subtype + ";" "boundary" "=" boundary + + + +Borenstein & Freed [Page 37] + +RFC 1521 MIME September 1993 + + + multipart-subtype := "mixed" / "parallel" / "digest" + / "alternative" / extension-token + +7.3. The Message Content-Type + + It is frequently desirable, in sending mail, to encapsulate another + mail message. For this common operation, a special Content-Type, + "message", is defined. The primary subtype, message/rfc822, has no + required parameters in the Content-Type field. Additional subtypes, + "partial" and "External-body", do have required parameters. These + subtypes are explained below. + + NOTE: It has been suggested that subtypes of message might be + defined for forwarded or rejected messages. However, forwarded + and rejected messages can be handled as multipart messages in + which the first part contains any control or descriptive + information, and a second part, of type message/rfc822, is the + forwarded or rejected message. Composing rejection and forwarding + messages in this manner will preserve the type information on the + original message and allow it to be correctly presented to the + recipient, and hence is strongly encouraged. + + As stated in the definition of the Content-Transfer-Encoding field, + no encoding other than "7bit", "8bit", or "binary" is permitted for + messages or parts of type "message". Even stronger restrictions + apply to the subtypes "message/partial" and "message/external-body", + as specified below. The message header fields are always US-ASCII in + any case, and data within the body can still be encoded, in which + case the Content-Transfer-Encoding header field in the encapsulated + message will reflect this. Non-ASCII text in the headers of an + encapsulated message can be specified using the mechanisms described + in [RFC-1522]. + + Mail gateways, relays, and other mail handling agents are commonly + known to alter the top-level header of an RFC 822 message. In + particular, they frequently add, remove, or reorder header fields. + Such alterations are explicitly forbidden for the encapsulated + headers embedded in the bodies of messages of type "message." + +7.3.1. The Message/rfc822 (primary) subtype + + A Content-Type of "message/rfc822" indicates that the body contains + an encapsulated message, with the syntax of an RFC 822 message. + However, unlike top-level RFC 822 messages, it is not required that + each message/rfc822 body must include a "From", "Subject", and at + least one destination header. + + It should be noted that, despite the use of the numbers "822", a + + + +Borenstein & Freed [Page 38] + +RFC 1521 MIME September 1993 + + + message/rfc822 entity can include enhanced information as defined in + this document. In other words, a message/rfc822 message may be a + MIME message. + +7.3.2. The Message/Partial subtype + + A subtype of message, "partial", is defined in order to allow large + objects to be delivered as several separate pieces of mail and + automatically reassembled by the receiving user agent. (The concept + is similar to IP fragmentation/reassembly in the basic Internet + Protocols.) This mechanism can be used when intermediate transport + agents limit the size of individual messages that can be sent. + Content-Type "message/partial" thus indicates that the body contains + a fragment of a larger message. + + Three parameters must be specified in the Content-Type field of type + message/partial: The first, "id", is a unique identifier, as close to + a world-unique identifier as possible, to be used to match the parts + together. (In general, the identifier is essentially a message-id; + if placed in double quotes, it can be any message-id, in accordance + with the BNF for "parameter" given earlier in this specification.) + The second, "number", an integer, is the part number, which indicates + where this part fits into the sequence of fragments. The third, + "total", another integer, is the total number of parts. This third + subfield is required on the final part, and is optional (though + encouraged) on the earlier parts. Note also that these parameters + may be given in any order. + + Thus, part 2 of a 3-part message may have either of the following + header fields: + + Content-Type: Message/Partial; + number=2; total=3; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com" + + Content-Type: Message/Partial; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com"; + number=2 + + But part 3 MUST specify the total number of parts: + + Content-Type: Message/Partial; + number=3; total=3; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com" + + Note that part numbering begins with 1, not 0. + + When the parts of a message broken up in this manner are put + + + +Borenstein & Freed [Page 39] + +RFC 1521 MIME September 1993 + + + together, the result is a complete MIME entity, which may have its + own Content-Type header field, and thus may contain any other data + type. + + Message fragmentation and reassembly: The semantics of a reassembled + partial message must be those of the "inner" message, rather than of + a message containing the inner message. This makes it possible, for + example, to send a large audio message as several partial messages, + and still have it appear to the recipient as a simple audio message + rather than as an encapsulated message containing an audio message. + That is, the encapsulation of the message is considered to be + "transparent". + + When generating and reassembling the parts of a message/partial + message, the headers of the encapsulated message must be merged with + the headers of the enclosing entities. In this process the following + rules must be observed: + + (1) All of the header fields from the initial enclosing entity + (part one), except those that start with "Content-" and the + specific header fields "Message-ID", "Encrypted", and "MIME- + Version", must be copied, in order, to the new message. + + (2) Only those header fields in the enclosed message which start + with "Content-" and "Message-ID", "Encrypted", and "MIME-Version" + must be appended, in order, to the header fields of the new + message. Any header fields in the enclosed message which do not + start with "Content-" (except for "Message-ID", "Encrypted", and + "MIME-Version") will be ignored. + + (3) All of the header fields from the second and any subsequent + messages will be ignored. + + For example, if an audio message is broken into two parts, the first + part might look something like this: + + X-Weird-Header-1: Foo + From: Bill@host.com + To: joe@otherhost.com + Subject: Audio mail + Message-ID: + MIME-Version: 1.0 + Content-type: message/partial; + id="ABC@host.com"; + number=1; total=2 + + X-Weird-Header-1: Bar + X-Weird-Header-2: Hello + + + +Borenstein & Freed [Page 40] + +RFC 1521 MIME September 1993 + + + Message-ID: + MIME-Version: 1.0 + Content-type: audio/basic + Content-transfer-encoding: base64 + + ... first half of encoded audio data goes here... + + and the second half might look something like this: + + From: Bill@host.com + To: joe@otherhost.com + Subject: Audio mail + MIME-Version: 1.0 + Message-ID: + Content-type: message/partial; + id="ABC@host.com"; number=2; total=2 + + ... second half of encoded audio data goes here... + + Then, when the fragmented message is reassembled, the resulting + message to be displayed to the user should look something like this: + + X-Weird-Header-1: Foo + From: Bill@host.com + To: joe@otherhost.com + Subject: Audio mail + Message-ID: + MIME-Version: 1.0 + Content-type: audio/basic + Content-transfer-encoding: base64 + + ... first half of encoded audio data goes here... + ... second half of encoded audio data goes here... + + Note on encoding of MIME entities encapsulated inside message/partial + entities: Because data of type "message" may never be encoded in + base64 or quoted-printable, a problem might arise if message/partial + entities are constructed in an environment that supports binary or + 8-bit transport. The problem is that the binary data would be split + into multiple message/partial objects, each of them requiring binary + transport. If such objects were encountered at a gateway into a 7- + bit transport environment, there would be no way to properly encode + them for the 7-bit world, aside from waiting for all of the parts, + reassembling the message, and then encoding the reassembled data in + base64 or quoted-printable. Since it is possible that different + parts might go through different gateways, even this is not an + acceptable solution. For this reason, it is specified that MIME + entities of type message/partial must always have a content- + + + +Borenstein & Freed [Page 41] + +RFC 1521 MIME September 1993 + + + transfer-encoding of 7-bit (the default). In particular, even in + environments that support binary or 8-bit transport, the use of a + content-transfer-encoding of "8bit" or "binary" is explicitly + prohibited for entities of type message/partial. + + It should be noted that, because some message transfer agents may + choose to automatically fragment large messages, and because such + agents may use different fragmentation thresholds, it is possible + that the pieces of a partial message, upon reassembly, may prove + themselves to comprise a partial message. This is explicitly + permitted. + + It should also be noted that the inclusion of a "References" field in + the headers of the second and subsequent pieces of a fragmented + message that references the Message-Id on the previous piece may be + of benefit to mail readers that understand and track references. + However, the generation of such "References" fields is entirely + optional. + + Finally, it should be noted that the "Encrypted" header field has + been made obsolete by Privacy Enhanced Messaging (PEM), but the rules + above are believed to describe the correct way to treat it if it is + encountered in the context of conversion to and from message/partial + fragments. + +7.3.3. The Message/External-Body subtype + + The external-body subtype indicates that the actual body data are not + included, but merely referenced. In this case, the parameters + describe a mechanism for accessing the external data. + + When an entity is of type "message/external-body", it consists of a + header, two consecutive CRLFs, and the message header for the + encapsulated message. If another pair of consecutive CRLFs appears, + this of course ends the message header for the encapsulated message. + However, since the encapsulated message's body is itself external, it + does NOT appear in the area that follows. For example, consider the + following message: + + Content-type: message/external-body; access- + type=local-file; + + name="/u/nsb/Me.gif" + + Content-type: image/gif + Content-ID: + Content-Transfer-Encoding: binary + + + + +Borenstein & Freed [Page 42] + +RFC 1521 MIME September 1993 + + + THIS IS NOT REALLY THE BODY! + + The area at the end, which might be called the "phantom body", is + ignored for most external-body messages. However, it may be used to + contain auxiliary information for some such messages, as indeed it is + when the access-type is "mail-server". Of the access-types defined + by this document, the phantom body is used only when the access-type + is "mail-server". In all other cases, the phantom body is ignored. + + The only always-mandatory parameter for message/external-body is + "access-type"; all of the other parameters may be mandatory or + optional depending on the value of access-type. + + ACCESS-TYPE -- A case-insensitive word, indicating the supported + access mechanism by which the file or data may be obtained. + Values include, but are not limited to, "FTP", "ANON-FTP", "TFTP", + "AFS", "LOCAL-FILE", and "MAIL-SERVER". Future values, except for + experimental values beginning with "X-" must be registered with + IANA, as described in Appendix E . + + In addition, the following three parameters are optional for ALL + access-types: + + EXPIRATION -- The date (in the RFC 822 "date-time" syntax, as + extended by RFC 1123 to permit 4 digits in the year field) after + which the existence of the external data is not guaranteed. + + SIZE -- The size (in octets) of the data. The intent of this + parameter is to help the recipient decide whether or not to expend + the necessary resources to retrieve the external data. Note that + this describes the size of the data in its canonical form, that + is, before any Content- Transfer-Encoding has been applied or + after the data have been decoded. + + PERMISSION -- A case-insensitive field that indicates whether or + not it is expected that clients might also attempt to overwrite + the data. By default, or if permission is "read", the assumption + is that they are not, and that if the data is retrieved once, it + is never needed again. If PERMISSION is "read-write", this + assumption is invalid, and any local copy must be considered no + more than a cache. "Read" and "Read-write" are the only defined + values of permission. + + The precise semantics of the access-types defined here are described + in the sections that follow. + + The encapsulated headers in ALL message/external-body entities MUST + include a Content-ID header field to give a unique identifier by + + + +Borenstein & Freed [Page 43] + +RFC 1521 MIME September 1993 + + + which to reference the data. This identifier may be used for + cacheing mechanisms, and for recognizing the receipt of the data when + the access-type is "mail-server". + + Note that, as specified here, the tokens that describe external-body + data, such as file names and mail server commands, are required to be + in the US-ASCII character set. If this proves problematic in + practice, a new mechanism may be required as a future extension to + MIME, either as newly defined access-types for message/external-body + or by some other mechanism. + + As with message/partial, it is specified that MIME entities of type + message/external-body must always have a content-transfer-encoding of + 7-bit (the default). In particular, even in environments that + support binary or 8-bit transport, the use of a content-transfer- + encoding of "8bit" or "binary" is explicitly prohibited for entities + of type message/external-body. + +7.3.3.1. The "ftp" and "tftp" access-types + + An access-type of FTP or TFTP indicates that the message body is + accessible as a file using the FTP [RFC-959] or TFTP [RFC-783] + protocols, respectively. For these access-types, the following + additional parameters are mandatory: + + NAME -- The name of the file that contains the actual body data. + + SITE -- A machine from which the file may be obtained, using the + given protocol. This must be a fully qualified domain name, not a + nickname. + + Before any data are retrieved, using FTP, the user will generally + need to be asked to provide a login id and a password for the machine + named by the site parameter. For security reasons, such an id and + password are not specified as content-type parameters, but must be + obtained from the user. + + In addition, the following parameters are optional: + + DIRECTORY -- A directory from which the data named by NAME should + be retrieved. + + MODE -- A case-insensitive string indicating the mode to be used + when retrieving the information. The legal values for access-type + "TFTP" are "NETASCII", "OCTET", and "MAIL", as specified by the + TFTP protocol [RFC-783]. The legal values for access-type "FTP" + are "ASCII", "EBCDIC", "IMAGE", and "LOCALn" where "n" is a + decimal integer, typically 8. These correspond to the + + + +Borenstein & Freed [Page 44] + +RFC 1521 MIME September 1993 + + + representation types "A" "E" "I" and "L n" as specified by the FTP + protocol [RFC-959]. Note that "BINARY" and "TENEX" are not valid + values for MODE, but that "OCTET" or "IMAGE" or "LOCAL8" should be + used instead. IF MODE is not specified, the default value is + "NETASCII" for TFTP and "ASCII" otherwise. + +7.3.3.2. The "anon-ftp" access-type + + The "anon-ftp" access-type is identical to the "ftp" access type, + except that the user need not be asked to provide a name and password + for the specified site. Instead, the ftp protocol will be used with + login "anonymous" and a password that corresponds to the user's email + address. + +7.3.3.3. The "local-file" and "afs" access-types + + An access-type of "local-file" indicates that the actual body is + accessible as a file on the local machine. An access-type of "afs" + indicates that the file is accessible via the global AFS file system. + In both cases, only a single parameter is required: + + NAME -- The name of the file that contains the actual body data. + + The following optional parameter may be used to describe the locality + of reference for the data, that is, the site or sites at which the + file is expected to be visible: + + SITE -- A domain specifier for a machine or set of machines that + are known to have access to the data file. Asterisks may be used + for wildcard matching to a part of a domain name, such as + "*.bellcore.com", to indicate a set of machines on which the data + should be directly visible, while a single asterisk may be used to + indicate a file that is expected to be universally available, + e.g., via a global file system. + +7.3.3.4. The "mail-server" access-type + + The "mail-server" access-type indicates that the actual body is + available from a mail server. The mandatory parameter for this + access-type is: + + SERVER -- The email address of the mail server from which the + actual body data can be obtained. + + Because mail servers accept a variety of syntaxes, some of which is + multiline, the full command to be sent to a mail server is not + included as a parameter on the content-type line. Instead, it is + provided as the "phantom body" when the content-type is + + + +Borenstein & Freed [Page 45] + +RFC 1521 MIME September 1993 + + + message/external-body and the access- type is mail-server. + + An optional parameter for this access-type is: + + SUBJECT -- The subject that is to be used in the mail that is sent + to obtain the data. Note that keying mail servers on Subject lines + is NOT recommended, but such mail servers are known to exist. + + Note that MIME does not define a mail server syntax. Rather, it + allows the inclusion of arbitrary mail server commands in the phantom + body. Implementations must include the phantom body in the body of + the message it sends to the mail server address to retrieve the + relevant data. + + It is worth noting that, unlike other access-types, mail-server + access is asynchronous and will happen at an unpredictable time in + the future. For this reason, it is important that there be a + mechanism by which the returned data can be matched up with the + original message/external-body entity. MIME mailservers must use the + same Content-ID field on the returned message that was used in the + original message/external-body entity, to facilitate such matching. + +7.3.3.5. Examples and Further Explanations + + With the emerging possibility of very wide-area file systems, it + becomes very hard to know in advance the set of machines where a file + will and will not be accessible directly from the file system. + Therefore it may make sense to provide both a file name, to be tried + directly, and the name of one or more sites from which the file is + known to be accessible. An implementation can try to retrieve remote + files using FTP or any other protocol, using anonymous file retrieval + or prompting the user for the necessary name and password. If an + external body is accessible via multiple mechanisms, the sender may + include multiple parts of type message/external-body within an entity + of type multipart/alternative. + + However, the external-body mechanism is not intended to be limited to + file retrieval, as shown by the mail-server access-type. Beyond + this, one can imagine, for example, using a video server for external + references to video clips. + + If an entity is of type "message/external-body", then the body of the + entity will contain the header fields of the encapsulated message. + The body itself is to be found in the external location. This means + that if the body of the "message/external-body" message contains two + consecutive CRLFs, everything after those pairs is NOT part of the + message itself. For most message/external-body messages, this + trailing area must simply be ignored. However, it is a convenient + + + +Borenstein & Freed [Page 46] + +RFC 1521 MIME September 1993 + + + place for additional data that cannot be included in the content-type + header field. In particular, if the "access-type" value is "mail- + server", then the trailing area must contain commands to be sent to + the mail server at the address given by the value of the SERVER + parameter. + + The embedded message header fields which appear in the body of the + message/external-body data must be used to declare the Content-type + of the external body if it is anything other than plain ASCII text, + since the external body does not have a header section to declare its + type. Similarly, any Content-transfer-encoding other than "7bit" + must also be declared here. Thus a complete message/external-body + message, referring to a document in PostScript format, might look + like this: + + From: Whomever + To: Someone + Subject: whatever + MIME-Version: 1.0 + Message-ID: + Content-Type: multipart/alternative; boundary=42 + Content-ID: + + --42 + Content-Type: message/external-body; + name="BodyFormats.ps"; + site="thumper.bellcore.com"; + access-type=ANON-FTP; + directory="pub"; + mode="image"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + Content-ID: + + --42 + Content-Type: message/external-body; + name="/u/nsb/writing/rfcs/RFC-MIME.ps"; + site="thumper.bellcore.com"; + access-type=AFS + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + Content-ID: + + --42 + Content-Type: message/external-body; + access-type=mail-server + + + +Borenstein & Freed [Page 47] + +RFC 1521 MIME September 1993 + + + server="listserv@bogus.bitnet"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + Content-ID: + + get RFC-MIME.DOC + + --42-- + + Note that in the above examples, the default Content-transfer- + encoding of "7bit" is assumed for the external postscript data. + + Like the message/partial type, the message/external-body type is + intended to be transparent, that is, to convey the data type in the + external body rather than to convey a message with a body of that + type. Thus the headers on the outer and inner parts must be merged + using the same rules as for message/partial. In particular, this + means that the Content-type header is overridden, but the From and + Subject headers are preserved. + + Note that since the external bodies are not transported as mail, they + need not conform to the 7-bit and line length requirements, but might + in fact be binary files. Thus a Content-Transfer-Encoding is not + generally necessary, though it is permitted. + + Note that the body of a message of type "message/external-body" is + governed by the basic syntax for an RFC 822 message. In particular, + anything before the first consecutive pair of CRLFs is header + information, while anything after it is body information, which is + ignored for most access-types. + + The formal grammar for content-type header fields for data of type + message is given by: + + message-type := "message" "/" message-subtype + + message-subtype := "rfc822" + / "partial" 2#3partial-param + / "external-body" 1*external-param + / extension-token + + partial-param := (";" "id" "=" value) + / (";" "number" "=" 1*DIGIT) + / (";" "total" "=" 1*DIGIT) + ; id & number required; total required for last part + + external-param := (";" "access-type" "=" atype) + + + +Borenstein & Freed [Page 48] + +RFC 1521 MIME September 1993 + + + / (";" "expiration" "=" date-time) + ; Note that date-time is quoted + / (";" "size" "=" 1*DIGIT) + / (";" "permission" "=" ("read" / "read-write")) + ; Permission is case-insensitive + / (";" "name" "=" value) + / (";" "site" "=" value) + / (";" "dir" "=" value) + / (";" "mode" "=" value) + / (";" "server" "=" value) + / (";" "subject" "=" value) + ; access-type required;others required based on access-type + + atype := "ftp" / "anon-ftp" / "tftp" / "local-file" + / "afs" / "mail-server" / extension-token + ; Case-insensitive + +7.4. The Application Content-Type + + The "application" Content-Type is to be used for data which do not + fit in any of the other categories, and particularly for data to be + processed by mail-based uses of application programs. This is + information which must be processed by an application before it is + viewable or usable to a user. Expected uses for Content-Type + application include mail-based file transfer, spreadsheets, data for + mail-based scheduling systems, and languages for "active" + (computational) email. (The latter, in particular, can pose security + problems which must be understood by implementors, and are considered + in detail in the discussion of the application/PostScript content- + type.) + + For example, a meeting scheduler might define a standard + representation for information about proposed meeting dates. An + intelligent user agent would use this information to conduct a dialog + with the user, and might then send further mail based on that dialog. + More generally, there have been several "active" messaging languages + developed in which programs in a suitably specialized language are + sent through the mail and automatically run in the recipient's + environment. + + Such applications may be defined as subtypes of the "application" + Content-Type. This document defines two subtypes: octet-stream, and + PostScript. + + In general, the subtype of application will often be the name of the + application for which the data are intended. This does not mean, + however, that any application program name may be used freely as a + subtype of application. Such usages (other than subtypes beginning + + + +Borenstein & Freed [Page 49] + +RFC 1521 MIME September 1993 + + + with "x-") must be registered with IANA, as described in Appendix E. + +7.4.1. The Application/Octet-Stream (primary) subtype + + The primary subtype of application, "octet-stream", may be used to + indicate that a body contains binary data. The set of possible + parameters includes, but is not limited to: + + TYPE -- the general type or category of binary data. This is + intended as information for the human recipient rather than for + any automatic processing. + + PADDING -- the number of bits of padding that were appended to the + bit-stream comprising the actual contents to produce the enclosed + byte-oriented data. This is useful for enclosing a bit-stream in + a body when the total number of bits is not a multiple of the byte + size. + + An additional parameter, "conversions", was defined in [RFC-1341] but + has been removed. + + RFC 1341 also defined the use of a "NAME" parameter which gave a + suggested file name to be used if the data were to be written to a + file. This has been deprecated in anticipation of a separate + Content-Disposition header field, to be defined in a subsequent RFC. + + The recommended action for an implementation that receives + application/octet-stream mail is to simply offer to put the data in a + file, with any Content-Transfer-Encoding undone, or perhaps to use it + as input to a user-specified process. + + To reduce the danger of transmitting rogue programs through the mail, + it is strongly recommended that implementations NOT implement a + path-search mechanism whereby an arbitrary program named in the + Content-Type parameter (e.g., an "interpreter=" parameter) is found + and executed using the mail body as input. + +7.4.2. The Application/PostScript subtype + + A Content-Type of "application/postscript" indicates a PostScript + program. Currently two variants of the PostScript language are + allowed; the original level 1 variant is described in [POSTSCRIPT] + and the more recent level 2 variant is described in [POSTSCRIPT2]. + + PostScript is a registered trademark of Adobe Systems, Inc. Use of + the MIME content-type "application/postscript" implies recognition of + that trademark and all the rights it entails. + + + + +Borenstein & Freed [Page 50] + +RFC 1521 MIME September 1993 + + + The PostScript language definition provides facilities for internal + labeling of the specific language features a given program uses. This + labeling, called the PostScript document structuring conventions, is + very general and provides substantially more information than just + the language level. + + The use of document structuring conventions, while not required, is + strongly recommended as an aid to interoperability. Documents which + lack proper structuring conventions cannot be tested to see whether + or not they will work in a given environment. As such, some systems + may assume the worst and refuse to process unstructured documents. + + The execution of general-purpose PostScript interpreters entails + serious security risks, and implementors are discouraged from simply + sending PostScript email bodies to "off-the-shelf" interpreters. + While it is usually safe to send PostScript to a printer, where the + potential for harm is greatly constrained, implementors should + consider all of the following before they add interactive display of + PostScript bodies to their mail readers. + + The remainder of this section outlines some, though probably not all, + of the possible problems with sending PostScript through the mail. + + Dangerous operations in the PostScript language include, but may not + be limited to, the PostScript operators deletefile, renamefile, + filenameforall, and file. File is only dangerous when applied to + something other than standard input or output. Implementations may + also define additional nonstandard file operators; these may also + pose a threat to security. Filenameforall, the wildcard file search + operator, may appear at first glance to be harmless. Note, however, + that this operator has the potential to reveal information about what + files the recipient has access to, and this information may itself be + sensitive. Message senders should avoid the use of potentially + dangerous file operators, since these operators are quite likely to + be unavailable in secure PostScript implementations. Message- + receiving and -displaying software should either completely disable + all potentially dangerous file operators or take special care not to + delegate any special authority to their operation. These operators + should be viewed as being done by an outside agency when interpreting + PostScript documents. Such disabling and/or checking should be done + completely outside of the reach of the PostScript language itself; + care should be taken to insure that no method exists for re-enabling + full-function versions of these operators. + + The PostScript language provides facilities for exiting the normal + interpreter, or server, loop. Changes made in this "outer" + environment are customarily retained across documents, and may in + some cases be retained semipermanently in nonvolatile memory. The + + + +Borenstein & Freed [Page 51] + +RFC 1521 MIME September 1993 + + + operators associated with exiting the interpreter loop have the + potential to interfere with subsequent document processing. As such, + their unrestrained use constitutes a threat of service denial. + PostScript operators that exit the interpreter loop include, but may + not be limited to, the exitserver and startjob operators. Message- + sending software should not generate PostScript that depends on + exiting the interpreter loop to operate. The ability to exit will + probably be unavailable in secure PostScript implementations. + Message-receiving and -displaying software should, if possible, + disable the ability to make retained changes to the PostScript + environment, and eliminate the startjob and exitserver commands. If + these commands cannot be eliminated, the password associated with + them should at least be set to a hard-to-guess value. + + PostScript provides operators for setting system-wide and device- + specific parameters. These parameter settings may be retained across + jobs and may potentially pose a threat to the correct operation of + the interpreter. The PostScript operators that set system and device + parameters include, but may not be limited to, the setsystemparams + and setdevparams operators. Message-sending software should not + generate PostScript that depends on the setting of system or device + parameters to operate correctly. The ability to set these parameters + will probably be unavailable in secure PostScript implementations. + Message-receiving and -displaying software should, if possible, + disable the ability to change system and device parameters. If these + operators cannot be disabled, the password associated with them + should at least be set to a hard-to-guess value. + + Some PostScript implementations provide nonstandard facilities for + the direct loading and execution of machine code. Such facilities + are quite obviously open to substantial abuse. Message-sending + software should not make use of such features. Besides being totally + hardware- specific, they are also likely to be unavailable in secure + implementations of PostScript. Message-receiving and -displaying + software should not allow such operators to be used if they exist. + + PostScript is an extensible language, and many, if not most, + implementations of it provide a number of their own extensions. This + document does not deal with such extensions explicitly since they + constitute an unknown factor. Message-sending software should not + make use of nonstandard extensions; they are likely to be missing + from some implementations. Message-receiving and -displaying software + should make sure that any nonstandard PostScript operators are secure + and don't present any kind of threat. + + It is possible to write PostScript that consumes huge amounts of + various system resources. It is also possible to write PostScript + programs that loop infinitely. Both types of programs have the + + + +Borenstein & Freed [Page 52] + +RFC 1521 MIME September 1993 + + + potential to cause damage if sent to unsuspecting recipients. + Message-sending software should avoid the construction and + dissemination of such programs, which is antisocial. Message- + receiving and -displaying software should provide appropriate + mechanisms to abort processing of a document after a reasonable + amount of time has elapsed. In addition, PostScript interpreters + should be limited to the consumption of only a reasonable amount of + any given system resource. + + Finally, bugs may exist in some PostScript interpreters which could + possibly be exploited to gain unauthorized access to a recipient's + system. Apart from noting this possibility, there is no specific + action to take to prevent this, apart from the timely correction of + such bugs if any are found. + +7.4.3. Other Application subtypes + + It is expected that many other subtypes of application will be + defined in the future. MIME implementations must generally treat any + unrecognized subtypes as being equivalent to application/octet- + stream. + + The formal grammar for content-type header fields for application + data is given by: + + application-type := "application" "/" application-subtype + + application-subtype := ("octet-stream" *stream-param) + / "postscript" / extension-token + + stream-param := (";" "type" "=" value) + / (";" "padding" "=" padding) + + padding := "0" / "1" / "2" / "3" / "4" / "5" / "6" / "7" + +7.5. The Image Content-Type + + A Content-Type of "image" indicates that the body contains an image. + The subtype names the specific image format. These names are case + insensitive. Two initial subtypes are "jpeg" for the JPEG format, + JFIF encoding, and "gif" for GIF format [GIF]. + + The list of image subtypes given here is neither exclusive nor + exhaustive, and is expected to grow as more types are registered with + IANA, as described in Appendix E. + + The formal grammar for the content-type header field for data of type + image is given by: + + + +Borenstein & Freed [Page 53] + +RFC 1521 MIME September 1993 + + + image-type := "image" "/" ("gif" / "jpeg" / extension-token) + +7.6. The Audio Content-Type + + A Content-Type of "audio" indicates that the body contains audio + data. Although there is not yet a consensus on an "ideal" audio + format for use with computers, there is a pressing need for a format + capable of providing interoperable behavior. + + The initial subtype of "basic" is specified to meet this requirement + by providing an absolutely minimal lowest common denominator audio + format. It is expected that richer formats for higher quality and/or + lower bandwidth audio will be defined by a later document. + + The content of the "audio/basic" subtype is audio encoded using 8-bit + ISDN mu-law [PCM]. When this subtype is present, a sample rate of + 8000 Hz and a single channel is assumed. + + The formal grammar for the content-type header field for data of type + audio is given by: + + audio-type := "audio" "/" ("basic" / extension-token) + +7.7. The Video Content-Type + + A Content-Type of "video" indicates that the body contains a time- + varying-picture image, possibly with color and coordinated sound. + The term "video" is used extremely generically, rather than with + reference to any particular technology or format, and is not meant to + preclude subtypes such as animated drawings encoded compactly. The + subtype "mpeg" refers to video coded according to the MPEG standard + [MPEG]. + + Note that although in general this document strongly discourages the + mixing of multiple media in a single body, it is recognized that many + so-called "video" formats include a representation for synchronized + audio, and this is explicitly permitted for subtypes of "video". + + The formal grammar for the content-type header field for data of type + video is given by: + + video-type := "video" "/" ("mpeg" / extension-token) + +7.8. Experimental Content-Type Values + + A Content-Type value beginning with the characters "X-" is a private + value, to be used by consenting mail systems by mutual agreement. + Any format without a rigorous and public definition must be named + + + +Borenstein & Freed [Page 54] + +RFC 1521 MIME September 1993 + + + with an "X-" prefix, and publicly specified values shall never begin + with "X-". (Older versions of the widely-used Andrew system use the + "X-BE2" name, so new systems should probably choose a different + name.) + + In general, the use of "X-" top-level types is strongly discouraged. + Implementors should invent subtypes of the existing types whenever + possible. The invention of new types is intended to be restricted + primarily to the development of new media types for email, such as + digital odors or holography, and not for new data formats in general. + In many cases, a subtype of application will be more appropriate than + a new top-level type. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Borenstein & Freed [Page 55] + +RFC 1521 MIME September 1993 + + +8. Summary + + Using the MIME-Version, Content-Type, and Content-Transfer-Encoding + header fields, it is possible to include, in a standardized way, + arbitrary types of data objects with RFC 822 conformant mail + messages. No restrictions imposed by either RFC 821 or RFC 822 are + violated, and care has been taken to avoid problems caused by + additional restrictions imposed by the characteristics of some + Internet mail transport mechanisms (see Appendix B). The "multipart" + and "message" Content-Types allow mixing and hierarchical structuring + of objects of different types in a single message. Further Content- + Types provide a standardized mechanism for tagging messages or body + parts as audio, image, or several other kinds of data. A + distinguished parameter syntax allows further specification of data + format details, particularly the specification of alternate character + sets. Additional optional header fields provide mechanisms for + certain extensions deemed desirable by many implementors. Finally, a + number of useful Content-Types are defined for general use by + consenting user agents, notably message/partial, and + message/external-body. + +9. Security Considerations + + Security issues are discussed in Section 7.4.2 and in Appendix F. + Implementors should pay special attention to the security + implications of any mail content-types that can cause the remote + execution of any actions in the recipient's environment. In such + cases, the discussion of the application/postscript content-type in + Section 7.4.2 may serve as a model for considering other content- + types with remote execution capabilities. + + + + + + + + + + + + + + + + + + + + + +Borenstein & Freed [Page 56] + +RFC 1521 MIME September 1993 + + +10. Authors' Addresses + + For more information, the authors of this document may be contacted + via Internet mail: + + Nathaniel S. Borenstein + MRE 2D-296, Bellcore + 445 South St. + Morristown, NJ 07962-1910 + + Phone: +1 201 829 4270 + Fax: +1 201 829 7019 + Email: nsb@bellcore.com + + + Ned Freed + Innosoft International, Inc. + 250 West First Street + Suite 240 + Claremont, CA 91711 + + Phone: +1 909 624 7907 + Fax: +1 909 621 5319 + Email: ned@innosoft.com + + MIME is a result of the work of the Internet Engineering Task Force + Working Group on Email Extensions. The chairman of that group, Greg + Vaudreuil, may be reached at: + + Gregory M. Vaudreuil + Tigon Corporation + 17060 Dallas Parkway + Dallas Texas, 75248 + + Phone: +1 214-733-2722 + EMail: gvaudre@cnri.reston.va.us + + + + + + + + + + + + + + + +Borenstein & Freed [Page 57] + +RFC 1521 MIME September 1993 + + +11. Acknowledgements + + This document is the result of the collective effort of a large + number of people, at several IETF meetings, on the IETF-SMTP and + IETF-822 mailing lists, and elsewhere. Although any enumeration + seems doomed to suffer from egregious omissions, the following are + among the many contributors to this effort: + + Harald Tveit Alvestrand Timo Lehtinen + Randall Atkinson John R. MacMillan + Philippe Brandon Rick McGowan + Kevin Carosso Leo Mclaughlin + Uhhyung Choi Goli Montaser-Kohsari + Cristian Constantinof Keith Moore + Mark Crispin Tom Moore + Dave Crocker Erik Naggum + Terry Crowley Mark Needleman + Walt Daniels John Noerenberg + Frank Dawson Mats Ohrman + Hitoshi Doi Julian Onions + Kevin Donnelly Michael Patton + Keith Edwards David J. Pepper + Chris Eich Blake C. Ramsdell + Johnny Eriksson Luc Rooijakkers + Craig Everhart Marshall T. Rose + Patrik Faeltstroem Jonathan Rosenberg + Erik E. Fair Jan Rynning + Roger Fajman Harri Salminen + Alain Fontaine Michael Sanderson + James M. Galvin Masahiro Sekiguchi + Philip Gladstone Mark Sherman + Thomas Gordon Keld Simonsen + Phill Gross Bob Smart + James Hamilton Peter Speck + Steve Hardcastle-Kille Henry Spencer + David Herron Einar Stefferud + Bruce Howard Michael Stein + Bill Janssen Klaus Steinberger + Olle Jaernefors Peter Svanberg + Risto Kankkunen James Thompson + Phil Karn Steve Uhler + Alan Katz Stuart Vance + Tim Kehres Erik van der Poel + Neil Katin Guido van Rossum + Kyuho Kim Peter Vanderbilt + Anders Klemets Greg Vaudreuil + John Klensin Ed Vielmetti + Valdis Kletniek Ryan Waldron + + + +Borenstein & Freed [Page 58] + +RFC 1521 MIME September 1993 + + + Jim Knowles Wally Wedel + Stev Knowles Sven-Ove Westberg + Bob Kummerfeld Brian Wideen + Pekka Kytolaakso John Wobus + Stellan Lagerstrom Glenn Wright + Vincent Lau Rayan Zachariassen + Donald Lindsay David Zimmerman + Marc Andreessen Bob Braden + Brian Capouch Peter Clitherow + Dave Collier-Brown John Coonrod + Stephen Crocker Jim Davis + Axel Deininger Dana S Emery + Martin Forssen Stephen Gildea + Terry Gray Mark Horton + Warner Losh Carlyn Lowery + Laurence Lundblade Charles Lynn + Larry Masinter Michael J. McInerny + Jon Postel Christer Romson + Yutaka Sato Markku Savela + Richard Alan Schafer Larry W. Virden + Rhys Weatherly Jay Weber + Dave Wecker + +The authors apologize for any omissions from this list, which are +certainly unintentional. + + + + + + + + + + + + + + + + + + + + + + + + + + +Borenstein & Freed [Page 59] + +RFC 1521 MIME September 1993 + + +Appendix A -- Minimal MIME-Conformance + + The mechanisms described in this document are open-ended. It is + definitely not expected that all implementations will support all of + the Content-Types described, nor that they will all share the same + extensions. In order to promote interoperability, however, it is + useful to define the concept of "MIME-conformance" to define a + certain level of implementation that allows the useful interworking + of messages with content that differs from US ASCII text. In this + section, we specify the requirements for such conformance. + + A mail user agent that is MIME-conformant MUST: + + 1. Always generate a "MIME-Version: 1.0" header field. + + 2. Recognize the Content-Transfer-Encoding header field, and + decode all received data encoded with either the quoted-printable + or base64 implementations. Encode any data sent that is not in + seven-bit mail-ready representation using one of these + transformations and include the appropriate Content-Transfer- + Encoding header field, unless the underlying transport mechanism + supports non-seven-bit data, as SMTP does not. + + 3. Recognize and interpret the Content-Type header field, and + avoid showing users raw data with a Content-Type field other than + text. Be able to send at least text/plain messages, with the + character set specified as a parameter if it is not US-ASCII. + + 4. Explicitly handle the following Content-Type values, to at + least the following extents: + + Text: + + -- Recognize and display "text" mail + with the character set "US-ASCII." + + -- Recognize other character sets at + least to the extent of being able + to inform the user about what + character set the message uses. + + -- Recognize the "ISO-8859-*" character + sets to the extent of being able to + display those characters that are + common to ISO-8859-* and US-ASCII, + namely all characters represented + by octet values 0-127. + + + + +Borenstein & Freed [Page 60] + +RFC 1521 MIME September 1993 + + + -- For unrecognized subtypes, show or + offer to show the user the "raw" + version of the data after + conversion of the content from + canonical form to local form. + + Message: + + -- Recognize and display at least the + primary (822) encapsulation. + + Multipart: + + -- Recognize the primary (mixed) + subtype. Display all relevant + information on the message level + and the body part header level and + then display or offer to display + each of the body parts individually. + + -- Recognize the "alternative" subtype, + and avoid showing the user + redundant parts of + multipart/alternative mail. + + -- Treat any unrecognized subtypes as if + they were "mixed". + + Application: + + -- Offer the ability to remove either of + the two types of Content-Transfer- + Encoding defined in this document + and put the resulting information + in a user file. + + 5. Upon encountering any unrecognized Content- Type, an + implementation must treat it as if it had a Content-Type of + "application/octet-stream" with no parameter sub-arguments. How + such data are handled is up to an implementation, but likely + options for handling such unrecognized data include offering the + user to write it into a file (decoded from its mail transport + format) or offering the user to name a program to which the + decoded data should be passed as input. Unrecognized predefined + types, which in a MIME-conformant mailer might still include + audio, image, or video, should also be treated in this way. + + A user agent that meets the above conditions is said to be MIME- + + + +Borenstein & Freed [Page 61] + +RFC 1521 MIME September 1993 + + + conformant. The meaning of this phrase is that it is assumed to be + "safe" to send virtually any kind of properly-marked data to users of + such mail systems, because such systems will at least be able to + treat the data as undifferentiated binary, and will not simply splash + it onto the screen of unsuspecting users. There is another sense in + which it is always "safe" to send data in a format that is MIME- + conformant, which is that such data will not break or be broken by + any known systems that are conformant with RFC 821 and RFC 822. User + agents that are MIME-conformant have the additional guarantee that + the user will not be shown data that were never intended to be viewed + as text. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Borenstein & Freed [Page 62] + +RFC 1521 MIME September 1993 + + +Appendix B -- General Guidelines For Sending Email Data + + Internet email is not a perfect, homogeneous system. Mail may become + corrupted at several stages in its travel to a final destination. + Specifically, email sent throughout the Internet may travel across + many networking technologies. Many networking and mail technologies + do not support the full functionality possible in the SMTP transport + environment. Mail traversing these systems is likely to be modified + in such a way that it can be transported. + + There exist many widely-deployed non-conformant MTAs in the Internet. + These MTAs, speaking the SMTP protocol, alter messages on the fly to + take advantage of the internal data structure of the hosts they are + implemented on, or are just plain broken. + + The following guidelines may be useful to anyone devising a data + format (Content-Type) that will survive the widest range of + networking technologies and known broken MTAs unscathed. Note that + anything encoded in the base64 encoding will satisfy these rules, but + that some well-known mechanisms, notably the UNIX uuencode facility, + will not. Note also that anything encoded in the Quoted-Printable + encoding will survive most gateways intact, but possibly not some + gateways to systems that use the EBCDIC character set. + + (1) Under some circumstances the encoding used for data may change + as part of normal gateway or user agent operation. In particular, + conversion from base64 to quoted-printable and vice versa may be + necessary. This may result in the confusion of CRLF sequences with + line breaks in text bodies. As such, the persistence of CRLF as + something other than a line break must not be relied on. + + (2) Many systems may elect to represent and store text data using + local newline conventions. Local newline conventions may not match + the RFC822 CRLF convention -- systems are known that use plain CR, + plain LF, CRLF, or counted records. The result is that isolated + CR and LF characters are not well tolerated in general; they may + be lost or converted to delimiters on some systems, and hence must + not be relied on. + + (3) TAB (HT) characters may be misinterpreted or may be + automatically converted to variable numbers of spaces. This is + unavoidable in some environments, notably those not based on the + ASCII character set. Such conversion is STRONGLY DISCOURAGED, but + it may occur, and mail formats must not rely on the persistence of + TAB (HT) characters. + + (4) Lines longer than 76 characters may be wrapped or truncated in + some environments. Line wrapping and line truncation are STRONGLY + + + +Borenstein & Freed [Page 63] + +RFC 1521 MIME September 1993 + + + DISCOURAGED, but unavoidable in some cases. Applications which + require long lines must somehow differentiate between soft and + hard line breaks. (A simple way to do this is to use the quoted- + printable encoding.) + + (5) Trailing "white space" characters (SPACE, TAB (HT)) on a line + may be discarded by some transport agents, while other transport + agents may pad lines with these characters so that all lines in a + mail file are of equal length. The persistence of trailing white + space, therefore, must not be relied on. + + (6) Many mail domains use variations on the ASCII character set, + or use character sets such as EBCDIC which contain most but not + all of the US-ASCII characters. The correct translation of + characters not in the "invariant" set cannot be depended on across + character converting gateways. For example, this situation is a + problem when sending uuencoded information across BITNET, an + EBCDIC system. Similar problems can occur without crossing a + gateway, since many Internet hosts use character sets other than + ASCII internally. The definition of Printable Strings in X.400 + adds further restrictions in certain special cases. In + particular, the only characters that are known to be consistent + across all gateways are the 73 characters that correspond to the + upper and lower case letters A-Z and a-z, the 10 digits 0-9, and + the following eleven special characters: + + "'" (ASCII code 39) + "(" (ASCII code 40) + ")" (ASCII code 41) + "+" (ASCII code 43) + "," (ASCII code 44) + "-" (ASCII code 45) + "." (ASCII code 46) + "/" (ASCII code 47) + ":" (ASCII code 58) + "=" (ASCII code 61) + "?" (ASCII code 63) + + A maximally portable mail representation, such as the base64 + encoding, will confine itself to relatively short lines of text in + which the only meaningful characters are taken from this set of 73 + characters. + + (7) Some mail transport agents will corrupt data that includes + certain literal strings. In particular, a period (".") alone on a + line is known to be corrupted by some (incorrect) SMTP + implementations, and a line that starts with the five characters + "From " (the fifth character is a SPACE) are commonly corrupted as + + + +Borenstein & Freed [Page 64] + +RFC 1521 MIME September 1993 + + + well. A careful composition agent can prevent these corruptions + by encoding the data (e.g., in the quoted-printable encoding, + "=46rom " in place of "From " at the start of a line, and "=2E" in + place of "." alone on a line. + + Please note that the above list is NOT a list of recommended + practices for MTAs. RFC 821 MTAs are prohibited from altering the + character of white space or wrapping long lines. These BAD and + illegal practices are known to occur on established networks, and + implementations should be robust in dealing with the bad effects they + can cause. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Borenstein & Freed [Page 65] + +RFC 1521 MIME September 1993 + + +Appendix C -- A Complex Multipart Example + + What follows is the outline of a complex multipart message. This + message has five parts to be displayed serially: two introductory + plain text parts, an embedded multipart message, a richtext part, and + a closing encapsulated text message in a non-ASCII character set. + The embedded multipart message has two parts to be displayed in + parallel, a picture and an audio fragment. + + MIME-Version: 1.0 + From: Nathaniel Borenstein + To: Ned Freed + Subject: A multipart example + Content-Type: multipart/mixed; + boundary=unique-boundary-1 + + This is the preamble area of a multipart message. + Mail readers that understand multipart format + should ignore this preamble. + If you are reading this text, you might want to + consider changing to a mail reader that understands + how to properly display multipart messages. + --unique-boundary-1 + + ...Some text appears here... + [Note that the preceding blank line means + no header fields were given and this is text, + with charset US ASCII. It could have been + done with explicit typing as in the next part.] + + --unique-boundary-1 + Content-type: text/plain; charset=US-ASCII + + This could have been part of the previous part, + but illustrates explicit versus implicit + typing of body parts. + + --unique-boundary-1 + Content-Type: multipart/parallel; + boundary=unique-boundary-2 + + + --unique-boundary-2 + Content-Type: audio/basic + Content-Transfer-Encoding: base64 + + ... base64-encoded 8000 Hz single-channel + mu-law-format audio data goes here.... + + + +Borenstein & Freed [Page 66] + +RFC 1521 MIME September 1993 + + + --unique-boundary-2 + Content-Type: image/gif + Content-Transfer-Encoding: base64 + + ... base64-encoded image data goes here.... + + --unique-boundary-2-- + + --unique-boundary-1 + Content-type: text/richtext + + This is richtext. + as defined in RFC 1341 + Isn't it + cool? + + --unique-boundary-1 + Content-Type: message/rfc822 + + From: (mailbox in US-ASCII) + To: (address in US-ASCII) + Subject: (subject in US-ASCII) + Content-Type: Text/plain; charset=ISO-8859-1 + Content-Transfer-Encoding: Quoted-printable + + ... Additional text in ISO-8859-1 goes here ... + + --unique-boundary-1-- + + + + + + + + + + + + + + + + + + + + + + + +Borenstein & Freed [Page 67] + +RFC 1521 MIME September 1993 + + +Appendix D -- Collected Grammar + + This appendix contains the complete BNF grammar for all the syntax + specified by this document. + + By itself, however, this grammar is incomplete. It refers to several + entities that are defined by RFC 822. Rather than reproduce those + definitions here, and risk unintentional differences between the two, + this document simply refers the reader to RFC 822 for the remaining + definitions. Wherever a term is undefined, it refers to the RFC 822 + definition. + + application-subtype := ("octet-stream" *stream-param) + / "postscript" / extension-token + + application-type := "application" "/" application-subtype + + attribute := token ; case-insensitive + + atype := "ftp" / "anon-ftp" / "tftp" / "local-file" + / "afs" / "mail-server" / extension-token + ; Case-insensitive + + audio-type := "audio" "/" ("basic" / extension-token) + + body-part := <"message" as defined in RFC 822, + with all header fields optional, and with the + specified delimiter not occurring anywhere in + the message body, either on a line by itself + or as a substring anywhere.> + + NOTE: In certain transport enclaves, RFC 822 restrictions such as + the one that limits bodies to printable ASCII characters may not + be in force. (That is, the transport domains may resemble + standard Internet mail transport as specified in RFC821 and + assumed by RFC822, but without certain restrictions.) The + relaxation of these restrictions should be construed as locally + extending the definition of bodies, for example to include octets + outside of the ASCII range, as long as these extensions are + supported by the transport and adequately documented in the + Content-Transfer-Encoding header field. However, in no event are + headers (either message headers or body-part headers) allowed to + contain anything other than ASCII characters. + + + + + + + + +Borenstein & Freed [Page 68] + +RFC 1521 MIME September 1993 + + + boundary := 0*69 bcharsnospace + + bchars := bcharsnospace / " " + + bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / "+" / "_" + / "," / "-" / "." / "/" / ":" / "=" / "?" + + charset := "us-ascii" / "iso-8859-1" / "iso-8859-2"/ "iso-8859-3" + / "iso-8859-4" / "iso-8859-5" / "iso-8859-6" / "iso-8859-7" + / "iso-8859-8" / "iso-8859-9" / extension-token + ; case insensitive + + close-delimiter := "--" boundary "--" CRLF;Again,no space by "--", + + content := "Content-Type" ":" type "/" subtype *(";" parameter) + ; case-insensitive matching of type and subtype + + delimiter := "--" boundary CRLF ;taken from Content-Type field. + ; There must be no space + ; between "--" and boundary. + + description := "Content-Description" ":" *text + + discard-text := *(*text CRLF) + + encapsulation := delimiter body-part CRLF + + encoding := "Content-Transfer-Encoding" ":" mechanism + + epilogue := discard-text ; to be ignored upon receipt. + + extension-token := x-token / iana-token + + external-param := (";" "access-type" "=" atype) + / (";" "expiration" "=" date-time) + + ; Note that date-time is quoted + / (";" "size" "=" 1*DIGIT) + / (";" "permission" "=" ("read" / "read-write")) + ; Permission is case-insensitive + / (";" "name" "=" value) + / (";" "site" "=" value) + / (";" "dir" "=" value) + / (";" "mode" "=" value) + / (";" "server" "=" value) + / (";" "subject" "=" value) + ;access-type required; others required based on access-type + + + + +Borenstein & Freed [Page 69] + +RFC 1521 MIME September 1993 + + + iana-token := + + id := "Content-ID" ":" msg-id + + image-type := "image" "/" ("gif" / "jpeg" / extension-token) + + mechanism := "7bit" ; case-insensitive + / "quoted-printable" + / "base64" + / "8bit" + / "binary" + / x-token + + message-subtype := "rfc822" + / "partial" 2#3partial-param + / "external-body" 1*external-param + / extension-token + + message-type := "message" "/" message-subtype + + multipart-body :=preamble 1*encapsulation close-delimiter epilogue + + multipart-subtype := "mixed" / "parallel" / "digest" + / "alternative" / extension-token + + multipart-type := "multipart" "/" multipart-subtype + ";" "boundary" "=" boundary + + octet := "=" 2(DIGIT / "A" / "B" / "C" / "D" / "E" / "F") + ; octet must be used for characters > 127, =, SPACE, or + TAB, + ; and is recommended for any characters not listed in + ; Appendix B as "mail-safe". + + padding := "0" / "1" / "2" / "3" / "4" / "5" / "6" / "7" + + parameter := attribute "=" value + + partial-param := (";" "id" "=" value) + / (";" "number" "=" 1*DIGIT) + / (";" "total" "=" 1*DIGIT) + ; id & number required;total required for last part + + preamble := discard-text ; to be ignored upon receipt. + + ptext := octet / " / "@" + / "," / ";" / ":" / "\" / <"> + / "/" / "[" / "]" / "?" / "=" + ; Must be in quoted-string, + ; to use within parameter values + + + type := "application" / "audio" ; case-insensitive + / "image" / "message" + / "multipart" / "text" + / "video" / extension-token + ; All values case-insensitive + + value := token / quoted-string + + version := "MIME-Version" ":" 1*DIGIT "." 1*DIGIT + + video-type := "video" "/" ("mpeg" / extension-token) + + x-token := + + + + + + + + + + + + + +Borenstein & Freed [Page 71] + +RFC 1521 MIME September 1993 + + +Appendix E -- IANA Registration Procedures + + MIME has been carefully designed to have extensible mechanisms, and + it is expected that the set of content-type/subtype pairs and their + associated parameters will grow significantly with time. Several + other MIME fields, notably character set names, access-type + parameters for the message/external-body type, and possibly even + Content-Transfer-Encoding values, are likely to have new values + defined over time. In order to ensure that the set of such values is + developed in an orderly, well-specified, and public manner, MIME + defines a registration process which uses the Internet Assigned + Numbers Authority (IANA) as a central registry for such values. + + In general, parameters in the content-type header field are used to + convey supplemental information for various content types, and their + use is defined when the content-type and subtype are defined. New + parameters should not be defined as a way to introduce new + functionality. + + In order to simplify and standardize the registration process, this + appendix gives templates for the registration of new values with + IANA. Each of these is given in the form of an email message + template, to be filled in by the registering party. + + E.1 Registration of New Content-type/subtype Values + + Note that MIME is generally expected to be extended by subtypes. If + a new fundamental top-level type is needed, its specification must be + published as an RFC or submitted in a form suitable to become an RFC, + and be subject to the Internet standards process. + + To: IANA@isi.edu + Subject: Registration of new MIME + content-type/subtype + + MIME type name: + + (If the above is not an existing top-level MIME type, + please explain why an existing type cannot be used.) + + MIME subtype name: + + Required parameters: + + Optional parameters: + + Encoding considerations: + + + + +Borenstein & Freed [Page 72] + +RFC 1521 MIME September 1993 + + + Security considerations: + + Published specification: + + (The published specification must be an Internet RFC or + RFC-to-be if a new top-level type is being defined, and + must be a publicly available specification in any + case.) + + Person & email address to contact for further information: + + E.2 Registration of New Access-type Values + for Message/external-body + + To: IANA@isi.edu + Subject: Registration of new MIME Access-type for + Message/external-body content-type + + MIME access-type name: + + Required parameters: + + Optional parameters: + + Published specification: + + (The published specification must be an Internet RFC or + RFC-to-be.) + + Person & email address to contact for further information: + + + + + + + + + + + + + + + + + + + + + +Borenstein & Freed [Page 73] + +RFC 1521 MIME September 1993 + + +Appendix F -- Summary of the Seven Content-types + + Content-type: text + + Subtypes defined by this document: plain + + Important Parameters: charset + + Encoding notes: quoted-printable generally preferred if an encoding + is needed and the character set is mostly an ASCII superset. + + Security considerations: Rich text formats such as TeX and Troff + often contain mechanisms for executing arbitrary commands or file + system operations, and should not be used automatically unless + these security problems have been addressed. Even plain text may + contain control characters that can be used to exploit the + capabilities of "intelligent" terminals and cause security + violations. User interfaces designed to run on such terminals + should be aware of and try to prevent such problems. + + ________________________________________________________ + Content-type: multipart + + Subtypes defined by this document: mixed, alternative, + digest, parallel. + + Important Parameters: boundary + + Encoding notes: No content-transfer-encoding is permitted. + + ________________________________________________________ + Content-type: message + + Subtypes defined by this document: rfc822, partial, external-body + + Important Parameters: id, number, total, access-type, expiration, + size, permission, name, site, directory, mode, server, subject + + Encoding notes: No content-transfer-encoding is permitted. + Specifically, only "7bit" is permitted for "message/partial" or + "message/external-body", and only "7bit", "8bit", or "binary" are + permitted for other subtypes of "message". + ______________________________________________________________ + Content-type: application + + Subtypes defined by this document: octet-stream, postscript + + Important Parameters: type, padding + + + +Borenstein & Freed [Page 74] + +RFC 1521 MIME September 1993 + + + Deprecated Parameters: name and conversions were + defined in RFC 1341. + + Encoding notes: base64 preferred for unreadable subtypes. + + Security considerations: This type is intended for the + transmission of data to be interpreted by locally-installed + programs. If used, for example, to transmit executable + binary programs or programs in general-purpose interpreted + languages, such as LISP programs or shell scripts, severe + security problems could result. Authors of mail-reading + agents are cautioned against giving their systems the power + to execute mail-based application data without carefully + considering the security implications. While it is + certainly possible to define safe application formats and + even safe interpreters for unsafe formats, each interpreter + should be evaluated separately for possible security + problems. + ________________________________________________________________ + Content-type: image + + Subtypes defined by this document: jpeg, gif + + Important Parameters: none + + Encoding notes: base64 generally preferred + ________________________________________________________________ + Content-type: audio + + Subtypes defined by this document: basic + + Important Parameters: none + + Encoding notes: base64 generally preferred + ________________________________________________________________ + Content-type: video + + Subtypes defined by this document: mpeg + + Important Parameters: none + + Encoding notes: base64 generally preferred + + + + + + + + + +Borenstein & Freed [Page 75] + +RFC 1521 MIME September 1993 + + +Appendix G -- Canonical Encoding Model + + There was some confusion, in earlier drafts of this memo, regarding + the model for when email data was to be converted to canonical form + and encoded, and in particular how this process would affect the + treatment of CRLFs, given that the representation of newlines varies + greatly from system to system. For this reason, a canonical model + for encoding is presented below. + + The process of composing a MIME entity can be modeled as being done + in a number of steps. Note that these steps are roughly similar to + those steps used in RFC 1421 and are performed for each 'innermost + level' body: + + Step 1. Creation of local form. + + The body to be transmitted is created in the system's native format. + The native character set is used, and where appropriate local end of + line conventions are used as well. The body may be a UNIX-style text + file, or a Sun raster image, or a VMS indexed file, or audio data in + a system-dependent format stored only in memory, or anything else + that corresponds to the local model for the representation of some + form of information. Fundamentally, the data is created in the + "native" form specified by the type/subtype information. + + Step 2. Conversion to canonical form. + + The entire body, including "out-of-band" information such as record + lengths and possibly file attribute information, is converted to a + universal canonical form. The specific content type of the body as + well as its associated attributes dictate the nature of the canonical + form that is used. Conversion to the proper canonical form may + involve character set conversion, transformation of audio data, + compression, or various other operations specific to the various + content types. If character set conversion is involved, however, + care must be taken to understand the semantics of the content-type, + which may have strong implications for any character set conversion, + e.g. with regard to syntactically meaningful characters in a text + subtype other than "plain". + + For example, in the case of text/plain data, the text must be + converted to a supported character set and lines must be delimited + with CRLF delimiters in accordance with RFC822. Note that the + restriction on line lengths implied by RFC822 is eliminated if the + next step employs either quoted-printable or base64 encoding. + + + + + + +Borenstein & Freed [Page 76] + +RFC 1521 MIME September 1993 + + + Step 3. Apply transfer encoding. + + A Content-Transfer-Encoding appropriate for this body is applied. + Note that there is no fixed relationship between the content type and + the transfer encoding. In particular, it may be appropriate to base + the choice of base64 or quoted-printable on character frequency + counts which are specific to a given instance of a body. + + Step 4. Insertion into entity. + + The encoded object is inserted into a MIME entity with appropriate + headers. The entity is then inserted into the body of a higher-level + entity (message or multipart) if needed. + + It is vital to note that these steps are only a model; they are + specifically NOT a blueprint for how an actual system would be built. + In particular, the model fails to account for two common designs: + + 1. In many cases the conversion to a canonical form prior to + encoding will be subsumed into the encoder itself, which + understands local formats directly. For example, the local + newline convention for text bodies might be carried through to the + encoder itself along with knowledge of what that format is. + + 2. The output of the encoders may have to pass through one or + more additional steps prior to being transmitted as a message. As + such, the output of the encoder may not be conformant with the + formats specified by RFC822. In particular, once again it may be + appropriate for the converter's output to be expressed using local + newline conventions rather than using the standard RFC822 CRLF + delimiters. + + Other implementation variations are conceivable as well. The vital + aspect of this discussion is that, in spite of any optimizations, + collapsings of required steps, or insertion of additional processing, + the resulting messages must be consistent with those produced by the + model described here. For example, a message with the following + header fields: + + Content-type: text/foo; charset=bar + Content-Transfer-Encoding: base64 + + must be first represented in the text/foo form, then (if necessary) + represented in the "bar" character set, and finally transformed via + the base64 algorithm into a mail-safe form. + + + + + + +Borenstein & Freed [Page 77] + +RFC 1521 MIME September 1993 + + +Appendix H -- Changes from RFC 1341 + + This document is a relatively minor revision of RFC 1341. For + the convenience of those familiar with RFC 1341, the technical + changes from that document are summarized in this appendix. + + 1. The definition of "tspecials" has been changed to no longer + include ".". + + 2. The Content-ID field is now mandatory for message/external-body + parts. + + 3. The text/richtext type (including the old Section 7.1.3 and + Appendix D) has been moved to a separate document. + + 4. The rules on header merging for message/partial data have been + changed to treat the Encrypted and MIME-Version headers as special + cases. + + 5. The definition of the external-body access-type parameter has + been changed so that it can only indicate a single access method + (which was all that made sense). + + 6. There is a new "Subject" parameter for message/external-body, + access-type mail-server, to permit MIME-based use of mail servers + that rely on Subject field information. + + 7. The "conversions" parameter for application/octet-stream has been + removed. + + 8. Section 7.4.1 now deprecates the use of the "name" parameter for + application/octet-stream, as this will be superseded in the future by + a Content-Disposition header. + + 9. The formal grammar for multipart bodies has been changed so that + a CRLF is no longer required before the first boundary line. + + 10. MIME entities of type "message/partial" and "message/external- + body" are now required to use only the "7bit" transfer-encoding. + (Specifically, "binary" and "8bit" are not permitted.) + + 11. The "application/oda" content-type has been removed. + + 12. A note has been added to the end of section 7.2.3, explaining + the semantics of Content-ID in a multipart/alternative MIME entity. + + 13. The formal syntax for the "MIME-Version" field has been + tightened, but in a way that is completely compatible with the only + + + +Borenstein & Freed [Page 78] + +RFC 1521 MIME September 1993 + + + version number defined in RFC 1341. + + 14. In Section 7.3.1, the definition of message/rfc822 has been + relaxed regarding mandatory fields. + + All other changes from RFC 1341 were editorial changes and do not + affect the technical content of MIME. Considerable formal grammar + has been added, but this reflects the prose specification that was + already in place. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Borenstein & Freed [Page 79] + +RFC 1521 MIME September 1993 + + +References + + [US-ASCII] Coded Character Set--7-Bit American Standard Code for + Information Interchange, ANSI X3.4-1986. + + [ATK] Borenstein, Nathaniel S., Multimedia Applications Development + with the Andrew Toolkit, Prentice-Hall, 1990. + + [GIF] Graphics Interchange Format (Version 89a), Compuserve, Inc., + Columbus, Ohio, 1990. + + [ISO-2022] International Standard--Information Processing--ISO 7-bit + and 8-bit coded character sets--Code extension techniques, ISO + 2022:1986. + + [ISO-8859] Information Processing -- 8-bit Single-Byte Coded Graphic + Character Sets -- Part 1: Latin Alphabet No. 1, ISO 8859-1:1987. Part + 2: Latin alphabet No. 2, ISO 8859-2, 1987. Part 3: Latin alphabet + No. 3, ISO 8859-3, 1988. Part 4: Latin alphabet No. 4, ISO 8859-4, + 1988. Part 5: Latin/Cyrillic alphabet, ISO 8859-5, 1988. Part 6: + Latin/Arabic alphabet, ISO 8859-6, 1987. Part 7: Latin/Greek + alphabet, ISO 8859-7, 1987. Part 8: Latin/Hebrew alphabet, ISO + 8859-8, 1988. Part 9: Latin alphabet No. 5, ISO 8859-9, 1990. + + [ISO-646] International Standard--Information Processing--ISO 7-bit + coded character set for information interchange, ISO 646:1983. + + [MPEG] Video Coding Draft Standard ISO 11172 CD, ISO IEC/TJC1/SC2/WG11 + (Motion Picture Experts Group), May, 1991. + + [PCM] CCITT, Fascicle III.4 - Recommendation G.711, Geneva, 1972, + "Pulse Code Modulation (PCM) of Voice Frequencies". + + [POSTSCRIPT] Adobe Systems, Inc., PostScript Language Reference + Manual, Addison-Wesley, 1985. + + [POSTSCRIPT2] Adobe Systems, Inc., PostScript Language Reference + Manual, Addison-Wesley, Second Edition, 1990. + + [X400] Schicker, Pietro, "Message Handling Systems, X.400", Message + Handling Systems and Distributed Applications, E. Stefferud, O-j. + Jacobsen, and P. Schicker, eds., North-Holland, 1989, pp. 3-41. + + [RFC-783] Sollins, K., "TFTP Protocol (revision 2)", RFC 783, MIT, + June 1981. + + [RFC-821] Postel, J., "Simple Mail Transfer Protocol", STD 10, RFC + 821, USC/Information Sciences Institute, August 1982. + + + +Borenstein & Freed [Page 80] + +RFC 1521 MIME September 1993 + + + [RFC-822] Crocker, D., "Standard for the Format of ARPA Internet Text + Messages", STD 11, RFC 822, UDEL, August 1982. + + [RFC-934] Rose, M., and E. Stefferud, "Proposed Standard for Message + Encapsulation", RFC 934, Delaware and NMA, January 1985. + + [RFC-959] Postel, J. and J. Reynolds, "File Transfer Protocol", + STD 9, RFC 959, USC/Information Sciences Institute, October 1985. + + [RFC-1049] Sirbu, M., "Content-Type Header Field for Internet + Messages", STD 11, RFC 1049, CMU, March 1988. + + [RFC-1421] Linn, J., "Privacy Enhancement for Internet Electronic Mail: + Part I - Message Encryption and Authentication Procedures", RFC + 1421, IAB IRTF PSRG, IETF PEM WG, February 1993. + + [RFC-1154] Robinson, D. and R. Ullmann, "Encoding Header Field for + Internet Messages", RFC 1154, Prime Computer, Inc., April 1990. + + [RFC-1341] Borenstein, N., and N. Freed, "MIME (Multipurpose Internet + Mail Extensions): Mechanisms for Specifying and Describing the Format + of Internet Message Bodies", RFC 1341, Bellcore, Innosoft, June 1992. + + [RFC-1342] Moore, K., "Representation of Non-Ascii Text in Internet + Message Headers", RFC 1342, University of Tennessee, June 1992. + + [RFC-1343] Borenstein, N., "A User Agent Configuration Mechanism + for Multimedia Mail Format Information", RFC 1343, Bellcore, June + 1992. + + [RFC-1344] Borenstein, N., "Implications of MIME for Internet + Mail Gateways", RFC 1344, Bellcore, June 1992. + + [RFC-1345] Simonsen, K., "Character Mnemonics & Character Sets", + RFC 1345, Rationel Almen Planlaegning, June 1992. + + [RFC-1426] Klensin, J., (WG Chair), Freed, N., (Editor), Rose, M., + Stefferud, E., and D. Crocker, "SMTP Service Extension for 8bit-MIME + transport", RFC 1426, United Nations Universit, Innosoft, Dover Beach + Consulting, Inc., Network Management Associates, Inc., The Branch + Office, February 1993. + + [RFC-1522] Moore, K., "Representation of Non-Ascii Text in Internet + Message Headers" RFC 1522, University of Tennessee, September 1993. + + [RFC-1340] Reynolds, J., and J. Postel, "Assigned Numbers", STD 2, RFC + 1340, USC/Information Sciences Institute, July 1992. + + + + +Borenstein & Freed [Page 81] + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc1854.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc1854.txt new file mode 100644 index 0000000..7b1a975 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc1854.txt @@ -0,0 +1,395 @@ + + + + + + +Network Working Group N. Freed +Request For Comments: 1854 Innosoft International, Inc. +Category: Standards Track A. Cargille, WG Chair + October 1995 + + + SMTP Service Extension + for Command Pipelining + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + This memo defines an extension to the SMTP service whereby a server + can indicate the extent of its ability to accept multiple commands in + a single TCP send operation. Using a single TCP send operation for + multiple commands can improve SMTP performance significantly. + +Introduction + + Although SMTP is widely and robustly deployed, certain extensions may + nevertheless prove useful. In particular, many parts of the Internet + make use of high latency network links. + + SMTP's intrinsic one command-one response structure is significantly + penalized by high latency links, often to the point where the factors + contributing to overall connection time are dominated by the time + spent waiting for responses to individual commands (turnaround time). + + In the best of all worlds it would be possible to simply deploy SMTP + client software that makes use of command pipelining: batching up + multiple commands into single TCP send operations. Unfortunately, the + original SMTP specification [1] did not explicitly state that SMTP + servers must support this. As a result a non-trivial number of + Internet SMTP servers cannot adequately handle command pipelining. + Flaws known to exist in deployed servers include: + + (1) Connection handoff and buffer flushes in the middle of + the SMTP dialogue. Creation of server processes for + incoming SMTP connections is a useful, obvious, and + harmless implementation technique. However, some SMTP + servers defer process forking and connection handoff + + + +Freed & Cargille Standards Track [Page 1] + +RFC 1854 SMTP Pipelining October 1995 + + + until some intermediate point in the SMTP dialogue. + When this is done material read from the TCP connection + and kept in process buffers can be lost. + + (2) Flushing the TCP input buffer when an SMTP command + fails. SMTP commands often fail but there is no reason + to flush the TCP input buffer when this happens. + Nevertheless, some SMTP servers do this. + + (3) Improper processing and promulgation of SMTP command + failures. For example, some SMTP servers will refuse to + accept a DATA command if the last RCPT TO command + fails, paying no attention to the success or failure of + prior RCPT TO command results. Other servers will + accept a DATA command even when all previous RCPT TO + commands have failed. Although it is possible to + accommodate this sort of behavior in a client that + employs command pipelining, it does complicate the + construction of the client unnecessarily. + + This memo uses the mechanism described in [2] to define an extension + to the SMTP service whereby an SMTP server can declare that it is + capable of handling pipelined commands. The SMTP client can then + check for this declaration and use pipelining only when the server + declares itself capable of handling it. + +1. Framework for the Command Pipelining Extension + + The Command Pipelining extension is defined as follows: + + (1) the name of the SMTP service extension is Pipelining; + + (2) the EHLO keyword value associated with the extension is + PIPELINING; + + (3) no parameter is used with the PIPELINING EHLO keyword; + + (4) no additional parameters are added to either the MAIL + FROM or RCPT TO commands. + + (5) no additional SMTP verbs are defined by this extension; + and, + + (6) the next section specifies how support for the + extension affects the behavior of a server and client + SMTP. + + + + + +Freed & Cargille Standards Track [Page 2] + +RFC 1854 SMTP Pipelining October 1995 + + +2. The Pipelining Service Extension + + When a client SMTP wishes to employ command pipelining, it first + issues the EHLO command to the server SMTP. If the server SMTP + responds with code 250 to the EHLO command, and the response includes + the EHLO keyword value PIPELINING, then the server SMTP has indicated + that it can accommodate SMTP command pipelining. + +2.1. Client use of pipelining + + Once the client SMTP has confirmed that support exists for the + pipelining extension, the client SMTP may then elect to transmit + groups of SMTP commands in batches without waiting for a response to + each individual command. In particular, the commands RSET, MAIL FROM, + SEND FROM, SOML FROM, SAML FROM, and RCPT TO can all appear anywhere + in a pipelined command group. The EHLO, DATA, VRFY, EXPN, TURN, + QUIT, and NOOP commands can only appear as the last command in a + group since their success or failure produces a change of state which + the client SMTP must accommodate. (NOOP is included in this group so + it can be used as a synchronization point.) + + Additional commands added by other SMTP extensions may only appear as + the last command in a group unless otherwise specified by the + extensions that define the commands. + + The actual transfer of message content is explicitly allowed to be + the first "command" in a group. That is, the RSET/MAIL FROM sequence + necessary to initiate a new message transaction can be placed in the + same group as the final transfer of the headers and body of the + previous message. + + Client SMTP implementations that employ pipelining MUST check ALL + statuses associated with each command in a group. For example, if + none of the RCPT TO recipient addresses were accepted the client must + then check the response to the DATA command -- the client cannot + assume that the DATA command will be rejected just because none of + the RCPT TO commands worked. If the DATA command was properly + rejected the client SMTP can just issue RSET, but if the DATA command + was accepted the client SMTP should send a single dot. + + Command statuses MUST be coordinated with responses by counting each + separate response and correlating that count with the number of + commands known to have been issued. Multiline responses MUST be + supported. Matching on the basis of either the error code value or + associated text is expressly forbidden. + + Client SMTP implementations MAY elect to operate in a nonblocking + fashion, processing server responses immediately upon receipt, even + + + +Freed & Cargille Standards Track [Page 3] + +RFC 1854 SMTP Pipelining October 1995 + + + if there is still data pending transmission from the client's + previous TCP send operation. If nonblocking operation is not + supported, however, client SMTP implementations MUST also check the + TCP window size and make sure that each group of commands fits + entirely within the window. The window size is usually, but not + always, 4K octets. Failure to perform this check can lead to + deadlock conditions. + + Clients MUST NOT confuse responses to multiple commands with + multiline responses. Each command requires one or more lines of + response, the last line not containing a dash between the response + code and the response string. + +2.2. Server support of pipelining + + A server SMTP implementation that offers the pipelining extension: + + (1) MUST NOT flush or otherwise lose the contents of the + TCP input buffer under any circumstances whatsoever. + + (2) SHOULD issue a positive response to the DATA command if + and only if one or more valid RCPT TO addresses have + been previously received. + + (3) MUST NOT, after issuing a positive response to a DATA + command with no valid recipients and subsequently + receiving an empty message, send any message whatsoever + to anybody. + + (4) SHOULD elect to store responses to grouped RSET, MAIL + FROM, SEND FROM, SOML FROM, SAML FROM, and RCPT TO + commands in an internal buffer so they can sent as a + unit. + + (5) MUST NOT buffer responses to EHLO, DATA, VRFY, EXPN, + TURN, QUIT, and NOOP. + + (6) MUST NOT buffer responses to unrecognized commands. + + (7) MUST send all pending responses immediately whenever + the local TCP input buffer is emptied. + + (8) MUST NOT make assumptions about commands that are yet + to be received. + + (9) SHOULD issue response text that indicates, either + implicitly or explicitly, what command the response + matches. + + + +Freed & Cargille Standards Track [Page 4] + +RFC 1854 SMTP Pipelining October 1995 + + + The overriding intent of these server requirements is to make it as + easy as possible for servers to conform to these pipelining + extensions. + +3. Examples + + Consider the following SMTP dialogue that does not use pipelining: + + S: + C: + S: 220 innosoft.com SMTP service ready + C: HELO dbc.mtview.ca.us + S: 250 innosoft.com + C: MAIL FROM: + S: 250 sender OK + C: RCPT TO: + S: 250 recipient OK + C: RCPT TO: + S: 250 recipient OK + C: RCPT TO: + S: 250 recipient OK + C: DATA + S: 354 enter mail, end with line containing only "." + ... + C: . + S: 250 message sent + C: QUIT + S: 221 goodbye + + The client waits for a server response a total of 9 times in this + simple example. But if pipelining is employed the following dialogue + is possible: + + S: + C: + S: 220 innosoft.com SMTP service ready + C: EHLO dbc.mtview.ca.us + S: 250-innosoft.com + S: 250 PIPELINING + C: MAIL FROM: + C: RCPT TO: + C: RCPT TO: + C: RCPT TO: + C: DATA + S: 250 sender OK + S: 250 recipient OK + S: 250 recipient OK + S: 250 recipient OK + + + +Freed & Cargille Standards Track [Page 5] + +RFC 1854 SMTP Pipelining October 1995 + + + S: 354 enter mail, end with line containing only "." + ... + C: . + C: QUIT + S: 250 message sent + S: 221 goodbye + + The total number of turnarounds has been reduced from 9 to 4. + + The next example illustrates one possible form of behavior when + pipelining is used and all recipients are rejected: + + S: + C: + S: 220 innosoft.com SMTP service ready + C: EHLO dbc.mtview.ca.us + S: 250-innosoft.com + S: 250 PIPELINING + C: MAIL FROM: + C: RCPT TO: + C: RCPT TO: + C: DATA + S: 250 sender OK + S: 550 remote mail to not allowed + S: 550 remote mail to not allowed + S: 554 no valid recipients given + C: QUIT + S: 221 goodbye + + The client SMTP waits for the server 4 times here as well. If the + server SMTP does not check for at least one valid recipient prior to + accepting the DATA command, the following dialogue would result: + + S: + C: + S: 220 innosoft.com SMTP service ready + C: EHLO dbc.mtview.ca.us + S: 250-innosoft.com + S: 250 PIPELINING + C: MAIL FROM: + C: RCPT TO: + C: RCPT TO: + C: DATA + S: 250 sender OK + S: 550 remote mail to not allowed + S: 550 remote mail to not allowed + S: 354 enter mail, end with line containing only "." + C: . + + + +Freed & Cargille Standards Track [Page 6] + +RFC 1854 SMTP Pipelining October 1995 + + + C: QUIT + S: 554 no valid recipients + S: 221 goodbye + +4. Security Considerations + + This RFC does not discuss security issues and is not believed to + raise any security issues not endemic in electronic mail and present + in fully conforming implementations of [1]. + +5. Acknowledgements + + This document is based on the SMTP service extension model presented + in RFC 1425. Marshall Rose's description of SMTP command pipelining + in his book "The Internet Message" also served as a source of + inspiration for this extension. + +6. References + + [1] Postel, J., "Simple Mail Transfer Protocol", STD 10 + RFC 821, USC/Information Sciences Institute, August + 1982. + + [2] Klensin, J., Freed, N., Rose, M., Stefferud, E., + and D. Crocker, "SMTP Service Extensions", RFC 1651, + MCI, Innosoft, Dover Beach Consulting, Inc., + Network Management Associates, Inc., Silicon Graphics, + Inc., July 1994. + +7. Author's Address + + Ned Freed + Innosoft International, Inc. + 1050 East Garvey Avenue South + West Covina, CA 91790 + USA + + Phone: +1 818 919 3600 + Fax: +1 818 919 3614 + EMail: ned@innosoft.com + + + + + + + + + + + +Freed & Cargille Standards Track [Page 7] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2015.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2015.txt new file mode 100644 index 0000000..d075983 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2015.txt @@ -0,0 +1,450 @@ + + + + + +Network Working Group M. Elkins +Request for Comments: 2015 The Aerospace Corporation +Category: Standards Track October 1996 + + + MIME Security with Pretty Good Privacy (PGP) + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + This document describes how Pretty Good Privacy (PGP) can be used to + provide privacy and authentication using the Multipurpose Internet + Mail Extensions (MIME) security content types described in RFC1847. + +1. Introduction + + Previous work on integrating PGP with MIME (including the since + withdrawn application/pgp content type) has suffered from a number of + problems, the most significant of which is the inability to recover + signed message bodies without parsing data structures specific to + PGP. This work makes use of the elegant solution proposed in + RFC1847, which defines security multipart formats for MIME. The + security multiparts clearly separate the signed message body from the + signature, and have a number of other desirable properties. This + document is styled after RFC 1848, which defines MIME Object Security + Services (MOSS) for providing security and authentication. + + This document defines three new content types for implementing + security and privacy with PGP: application/pgp-encrypted, + application/pgp-signature and application/pgp-keys. + +1.1 Compliance + + In order for an implementation to be compliant with this + specification, is it absolutely necessary for it to obey all items + labeled as MUST or REQUIRED. + + + + + + + + +Elkins Standards Track [Page 1] + +RFC 2015 MIME Security with PGP October 1996 + + +2. PGP data formats + + PGP can generate either ASCII armor (described in [3]) or 8-bit + binary output when encrypting data, generating a digital signature, + or extracting public key data. The ASCII armor output is the + REQUIRED method for data transfer. This allows those users who do + not have the means to interpret the formats described in this + document to be able extract and use the PGP information in the + message. + + When the amount of data to be transmitted requires that it be sent in + many parts, the MIME message/partial mechanism should be used rather + than the multipart ASCII armor PGP format. + +3. Content-Transfer-Encoding restrictions + + Multipart/signed and multipart/encrypted are to be treated by agents + as opaque, meaning that the data is not to be altered in any way [1]. + However, many existing mail gateways will detect if the next hop does + not support MIME or 8-bit data and perform conversion to either + Quoted-Printable or Base64. This presents serious problems for + multipart/signed, in particular, where the signature is invalidated + when such an operation occurs. For this reason all data signed + according to this protocol MUST be constrained to 7 bits (8- bit data + should be encoded using either Quoted-Printable or Base64). Note + that this also includes the case where a signed object is also + encrypted (see section 6). This restriction will increase the + likelihood that the signature will be valid upon receipt. + + Data that is ONLY to be encrypted is allowed to contain 8-bit + characters and therefore need not be converted to a 7-bit format. + + Implementor's note: It cannot be stressed enough that applications + using this standard should follow MIME's suggestion that you "be + conservative in what you generate, and liberal in what you accept." + In this particular case it means it would be wise for an + implementation to accept messages with any content-transfer- + encoding, but restrict generation to the 7-bit format required by + this memo. This will allow future compatibility in the event the + Internet SMTP framework becomes 8-bit friendly. + +4. PGP encrypted data + + Before encryption with PGP, the data should be written in MIME + canonical format (body and headers). + + PGP encrypted data is denoted by the "multipart/encrypted" content + type, described in [1], and MUST have a "protocol" parameter value of + + + +Elkins Standards Track [Page 2] + +RFC 2015 MIME Security with PGP October 1996 + + + "application/pgp-encrypted". Note that the value of the parameter + MUST be enclosed in quotes. + + The multipart/encrypted MUST consist of exactly two parts. The first + MIME body part must have a content type of "application/pgp- + encrypted". This body contains the control information. A message + complying with this standard MUST contain a "Version: 1" field in + this body. Since the PGP packet format contains all other + information necessary for decrypting, no other information is + required here. + + The second MIME body part MUST contain the actual encrypted data. It + must be labeled with a content type of "application/octet- stream". + + Example message: + + From: Michael Elkins + To: Michael Elkins + Mime-Version: 1.0 + Content-Type: multipart/encrypted; boundary=foo; + protocol="application/pgp-encrypted" + + --foo + Content-Type: application/pgp-encrypted + + Version: 1 + + --foo + Content-Type: application/octet-stream + + -----BEGIN PGP MESSAGE----- + Version: 2.6.2 + + hIwDY32hYGCE8MkBA/wOu7d45aUxF4Q0RKJprD3v5Z9K1YcRJ2fve87lMlDlx4Oj + eW4GDdBfLbJE7VUpp13N19GL8e/AqbyyjHH4aS0YoTk10QQ9nnRvjY8nZL3MPXSZ + g9VGQxFeGqzykzmykU6A26MSMexR4ApeeON6xzZWfo+0yOqAq6lb46wsvldZ96YA + AABH78hyX7YX4uT1tNCWEIIBoqqvCeIMpp7UQ2IzBrXg6GtukS8NxbukLeamqVW3 + 1yt21DYOjuLzcMNe/JNsD9vDVCvOOG3OCi8= + =zzaA + -----END PGP MESSAGE----- + + --foo-- + +5. PGP signed data + + PGP signed messages are denoted by the "multipart/signed" content + type, described in [1], with a "protocol" parameter which MUST have a + value of "application/pgp-signature" (MUST be quoted). The "micalg" + + + +Elkins Standards Track [Page 3] + +RFC 2015 MIME Security with PGP October 1996 + + + parameter MUST have a value of "pgp-", where identifies the message integrity check (MIC) used to generate + the signature. The currently defined values for are + "md5" for the MD5 checksum, and "sha1" for the SHA.1 algorithm. + + The multipart/signed body MUST consist of exactly two parts. The + first part contains the signed data in MIME canonical format, + including a set of appropriate content headers describing the data. + + The second body MUST contain the PGP digital signature. It MUST be + labeled with a content type of "application/pgp-signature". + + When the PGP digital signature is generated: + + (1) The data to be signed must first be converted to its + type/subtype specific canonical form. For text/plain, this + means conversion to an appropriate character set and conversion + of line endings to the canonical sequence. + + (2) An appropriate Content-Transfer-Encoding is then applied. Each + line of the encoded data MUST end with the canonical + sequence. + + (3) MIME content headers are then added to the body, each ending + with the canonical sequence. + + (4) As described in [1], the digital signature MUST be calculated + over both the data to be signed and its set of content headers. + + (5) The signature MUST be generated detached from the signed data + so that the process does not alter the signed data in any way. + + Example message: + + From: Michael Elkins + To: Michael Elkins + Mime-Version: 1.0 + Content-Type: multipart/signed; boundary=bar; micalg=pgp-md5; + protocol="application/pgp-signature" + + --bar + & Content-Type: text/plain; charset=iso-8859-1 + & Content-Transfer-Encoding: quoted-printable + & + & =A1Hola! + & + & Did you know that talking to yourself is a sign of senility? + & + + + +Elkins Standards Track [Page 4] + +RFC 2015 MIME Security with PGP October 1996 + + + & It's generally a good idea to encode lines that begin with + & From=20because some mail transport agents will insert a greater- + & than (>) sign, thus invalidating the signature. + & + & Also, in some cases it might be desirable to encode any =20 + &railing whitespace that occurs on lines in order to ensure =20 + & that the message signature is not invalidated when passing =20 + & a gateway that modifies such whitespace (like BITNET). =20 + & + & me + + --bar + Content-Type: application/pgp-signature + + -----BEGIN PGP MESSAGE----- + Version: 2.6.2 + + iQCVAwUBMJrRF2N9oWBghPDJAQE9UQQAtl7LuRVndBjrk4EqYBIb3h5QXIX/LC// + jJV5bNvkZIGPIcEmI5iFd9boEgvpirHtIREEqLQRkYNoBActFBZmh9GC3C041WGq + uMbrbxc+nIs1TIKlA08rVi9ig/2Yh7LFrK5Ein57U/W72vgSxLhe/zhdfolT9Brn + HOxEa44b+EI= + =ndaj + -----END PGP MESSAGE----- + + --bar-- + + The "&"s in the previous example indicate the portion of the data + over which the signature was calculated. + + Though not required, it is generally a good idea to use Quoted- + Printable encoding in the first step (writing out the data to be + signed in MIME canonical format) if any of the lines in the data + begin with "From ", and encode the "F". This will avoid an MTA + inserting a ">" in front of the line, thus invalidating the + signature! + + Upon receipt of a signed message, an application MUST: + + (1) Convert line endings to the canonical sequence before + the signature can be verified. This is necessary since the + local MTA may have converted to a local end of line convention. + + (2) Pass both the signed data and its associated content headers + along with the PGP signature to the signature verification + service. + + + + + + +Elkins Standards Track [Page 5] + +RFC 2015 MIME Security with PGP October 1996 + + +6. Encrypted and Signed Data + + Sometimes it is desirable to both digitally sign and then encrypt a + message to be sent. This protocol allows for two methods of + accomplishing this task. + +6.1 RFC1847 Encapsulation + + [1], it is stated that the data should first be signed as a + multipart/signature body, and then encrypted to form the final + multipart/encrypted body, i.e., + + Content-Type: multipart/encrypted; + protocol="application/pgp-encrypted"; boundary=foo + + --foo + Content-Type: application/pgp-encrypted + + Version: 1 + + --foo + Content-Type: application/octet-stream + + -----BEGIN PGP MESSAGE----- + & Content-Type: multipart/signed; micalg=pgp-md5 + & protocol="application/pgp-signature"; boundary=bar + & + & --bar + & Content-Type: text/plain; charset=us-ascii + & + & This message was first signed, and then encrypted. + & + & --bar + & Content-Type: application/pgp-signature + & + & -----BEGIN PGP MESSAGE----- + & Version: 2.6.2 + & + & iQCVAwUBMJrRF2N9oWBghPDJAQE9UQQAtl7LuRVndBjrk4EqYBIb3h5QXIX/LC// + & jJV5bNvkZIGPIcEmI5iFd9boEgvpirHtIREEqLQRkYNoBActFBZmh9GC3C041WGq + & uMbrbxc+nIs1TIKlA08rVi9ig/2Yh7LFrK5Ein57U/W72vgSxLhe/zhdfolT9Brn + & HOxEa44b+EI= + & =ndaj + & -----END PGP MESSAGE----- + & + & --bar-- + -----END PGP MESSAGE----- + + + + +Elkins Standards Track [Page 6] + +RFC 2015 MIME Security with PGP October 1996 + + + --foo-- + + (The text preceded by '&' indicates that it is really + encrypted, but presented as text for clarity.) + +6.2 Combined method + + Versions 2.x of PGP also allow data to be signed and encrypted in one + operation. This method is an acceptable shortcut, and has the + benefit of less overhead. The resulting data should be formed as a + "multipart/encrypted" object as described above. + + Messages which are encrypted and signed in this combined fashion are + REQUIRED to follow the same canonicalization rules as for + multipart/signed objects. + + It is explicitly allowed for an agent to decrypt a combined message + and rewrite it as a multipart/signed object using the signature data + embedded in the encrypted version. + +7. Distribution of PGP public keys + + Content-Type: application/pgp-keys + Required parameters: none + Optional parameters: none + + This is the content type which should be used for relaying public key + blocks. + +8. Notes + + PGP and Pretty Good Privacy are trademarks of Philip Zimmermann. + +9. Security Considerations + + Use of this protocol has the same security considerations as PGP, and + is not known to either increase or decrease the security of messages + using it; see [3] for more information. + +10. Author's Address + + Michael Elkins + P.O. Box 92957 - M1/102 + Los Angeles, CA 90009-2957 + + Phone: +1 310 336 8040 + Fax: +1 310 336 4402 + + + + +Elkins Standards Track [Page 7] + +RFC 2015 MIME Security with PGP October 1996 + + +References + + [1] Galvin, J., Murphy, G., Crocker, S., and N. Freed, "Security + Multiparts for MIME: Multipart/Signed and Multipart/Encrypted", + RFC 1847, October 1995. + + [2] Galvin, J., Murphy, G., Crocker, S., and N. Freed, "MIME Object + Security Services", RFC 1848, October 1995. + + [3] Atkins, D., Stallings, W., and P. Zimmermann, "PGP Message + Exchange Formats", RFC 1991, August 1996. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Elkins Standards Track [Page 8] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2045.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2045.txt new file mode 100644 index 0000000..9f286b1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2045.txt @@ -0,0 +1,1739 @@ + + + + + + +Network Working Group N. Freed +Request for Comments: 2045 Innosoft +Obsoletes: 1521, 1522, 1590 N. Borenstein +Category: Standards Track First Virtual + November 1996 + + + Multipurpose Internet Mail Extensions + (MIME) Part One: + Format of Internet Message Bodies + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + STD 11, RFC 822, defines a message representation protocol specifying + considerable detail about US-ASCII message headers, and leaves the + message content, or message body, as flat US-ASCII text. This set of + documents, collectively called the Multipurpose Internet Mail + Extensions, or MIME, redefines the format of messages to allow for + + (1) textual message bodies in character sets other than + US-ASCII, + + (2) an extensible set of different formats for non-textual + message bodies, + + (3) multi-part message bodies, and + + (4) textual header information in character sets other than + US-ASCII. + + These documents are based on earlier work documented in RFC 934, STD + 11, and RFC 1049, but extends and revises them. Because RFC 822 said + so little about message bodies, these documents are largely + orthogonal to (rather than a revision of) RFC 822. + + This initial document specifies the various headers used to describe + the structure of MIME messages. The second document, RFC 2046, + defines the general structure of the MIME media typing system and + defines an initial set of media types. The third document, RFC 2047, + describes extensions to RFC 822 to allow non-US-ASCII text data in + + + +Freed & Borenstein Standards Track [Page 1] + +RFC 2045 Internet Message Bodies November 1996 + + + Internet mail header fields. The fourth document, RFC 2048, specifies + various IANA registration procedures for MIME-related facilities. The + fifth and final document, RFC 2049, describes MIME conformance + criteria as well as providing some illustrative examples of MIME + message formats, acknowledgements, and the bibliography. + + These documents are revisions of RFCs 1521, 1522, and 1590, which + themselves were revisions of RFCs 1341 and 1342. An appendix in RFC + 2049 describes differences and changes from previous versions. + +Table of Contents + + 1. Introduction ......................................... 3 + 2. Definitions, Conventions, and Generic BNF Grammar .... 5 + 2.1 CRLF ................................................ 5 + 2.2 Character Set ....................................... 6 + 2.3 Message ............................................. 6 + 2.4 Entity .............................................. 6 + 2.5 Body Part ........................................... 7 + 2.6 Body ................................................ 7 + 2.7 7bit Data ........................................... 7 + 2.8 8bit Data ........................................... 7 + 2.9 Binary Data ......................................... 7 + 2.10 Lines .............................................. 7 + 3. MIME Header Fields ................................... 8 + 4. MIME-Version Header Field ............................ 8 + 5. Content-Type Header Field ............................ 10 + 5.1 Syntax of the Content-Type Header Field ............. 12 + 5.2 Content-Type Defaults ............................... 14 + 6. Content-Transfer-Encoding Header Field ............... 14 + 6.1 Content-Transfer-Encoding Syntax .................... 14 + 6.2 Content-Transfer-Encodings Semantics ................ 15 + 6.3 New Content-Transfer-Encodings ...................... 16 + 6.4 Interpretation and Use .............................. 16 + 6.5 Translating Encodings ............................... 18 + 6.6 Canonical Encoding Model ............................ 19 + 6.7 Quoted-Printable Content-Transfer-Encoding .......... 19 + 6.8 Base64 Content-Transfer-Encoding .................... 24 + 7. Content-ID Header Field .............................. 26 + 8. Content-Description Header Field ..................... 27 + 9. Additional MIME Header Fields ........................ 27 + 10. Summary ............................................. 27 + 11. Security Considerations ............................. 27 + 12. Authors' Addresses .................................. 28 + A. Collected Grammar .................................... 29 + + + + + + +Freed & Borenstein Standards Track [Page 2] + +RFC 2045 Internet Message Bodies November 1996 + + +1. Introduction + + Since its publication in 1982, RFC 822 has defined the standard + format of textual mail messages on the Internet. Its success has + been such that the RFC 822 format has been adopted, wholly or + partially, well beyond the confines of the Internet and the Internet + SMTP transport defined by RFC 821. As the format has seen wider use, + a number of limitations have proven increasingly restrictive for the + user community. + + RFC 822 was intended to specify a format for text messages. As such, + non-text messages, such as multimedia messages that might include + audio or images, are simply not mentioned. Even in the case of text, + however, RFC 822 is inadequate for the needs of mail users whose + languages require the use of character sets richer than US-ASCII. + Since RFC 822 does not specify mechanisms for mail containing audio, + video, Asian language text, or even text in most European languages, + additional specifications are needed. + + One of the notable limitations of RFC 821/822 based mail systems is + the fact that they limit the contents of electronic mail messages to + relatively short lines (e.g. 1000 characters or less [RFC-821]) of + 7bit US-ASCII. This forces users to convert any non-textual data + that they may wish to send into seven-bit bytes representable as + printable US-ASCII characters before invoking a local mail UA (User + Agent, a program with which human users send and receive mail). + Examples of such encodings currently used in the Internet include + pure hexadecimal, uuencode, the 3-in-4 base 64 scheme specified in + RFC 1421, the Andrew Toolkit Representation [ATK], and many others. + + The limitations of RFC 822 mail become even more apparent as gateways + are designed to allow for the exchange of mail messages between RFC + 822 hosts and X.400 hosts. X.400 [X400] specifies mechanisms for the + inclusion of non-textual material within electronic mail messages. + The current standards for the mapping of X.400 messages to RFC 822 + messages specify either that X.400 non-textual material must be + converted to (not encoded in) IA5Text format, or that they must be + discarded, notifying the RFC 822 user that discarding has occurred. + This is clearly undesirable, as information that a user may wish to + receive is lost. Even though a user agent may not have the + capability of dealing with the non-textual material, the user might + have some mechanism external to the UA that can extract useful + information from the material. Moreover, it does not allow for the + fact that the message may eventually be gatewayed back into an X.400 + message handling system (i.e., the X.400 message is "tunneled" + through Internet mail), where the non-textual information would + definitely become useful again. + + + + +Freed & Borenstein Standards Track [Page 3] + +RFC 2045 Internet Message Bodies November 1996 + + + This document describes several mechanisms that combine to solve most + of these problems without introducing any serious incompatibilities + with the existing world of RFC 822 mail. In particular, it + describes: + + (1) A MIME-Version header field, which uses a version + number to declare a message to be conformant with MIME + and allows mail processing agents to distinguish + between such messages and those generated by older or + non-conformant software, which are presumed to lack + such a field. + + (2) A Content-Type header field, generalized from RFC 1049, + which can be used to specify the media type and subtype + of data in the body of a message and to fully specify + the native representation (canonical form) of such + data. + + (3) A Content-Transfer-Encoding header field, which can be + used to specify both the encoding transformation that + was applied to the body and the domain of the result. + Encoding transformations other than the identity + transformation are usually applied to data in order to + allow it to pass through mail transport mechanisms + which may have data or character set limitations. + + (4) Two additional header fields that can be used to + further describe the data in a body, the Content-ID and + Content-Description header fields. + + All of the header fields defined in this document are subject to the + general syntactic rules for header fields specified in RFC 822. In + particular, all of these header fields except for Content-Disposition + can include RFC 822 comments, which have no semantic content and + should be ignored during MIME processing. + + Finally, to specify and promote interoperability, RFC 2049 provides a + basic applicability statement for a subset of the above mechanisms + that defines a minimal level of "conformance" with this document. + + HISTORICAL NOTE: Several of the mechanisms described in this set of + documents may seem somewhat strange or even baroque at first reading. + It is important to note that compatibility with existing standards + AND robustness across existing practice were two of the highest + priorities of the working group that developed this set of documents. + In particular, compatibility was always favored over elegance. + + + + + +Freed & Borenstein Standards Track [Page 4] + +RFC 2045 Internet Message Bodies November 1996 + + + Please refer to the current edition of the "Internet Official + Protocol Standards" for the standardization state and status of this + protocol. RFC 822 and STD 3, RFC 1123 also provide essential + background for MIME since no conforming implementation of MIME can + violate them. In addition, several other informational RFC documents + will be of interest to the MIME implementor, in particular RFC 1344, + RFC 1345, and RFC 1524. + +2. Definitions, Conventions, and Generic BNF Grammar + + Although the mechanisms specified in this set of documents are all + described in prose, most are also described formally in the augmented + BNF notation of RFC 822. Implementors will need to be familiar with + this notation in order to understand this set of documents, and are + referred to RFC 822 for a complete explanation of the augmented BNF + notation. + + Some of the augmented BNF in this set of documents makes named + references to syntax rules defined in RFC 822. A complete formal + grammar, then, is obtained by combining the collected grammar + appendices in each document in this set with the BNF of RFC 822 plus + the modifications to RFC 822 defined in RFC 1123 (which specifically + changes the syntax for `return', `date' and `mailbox'). + + All numeric and octet values are given in decimal notation in this + set of documents. All media type values, subtype values, and + parameter names as defined are case-insensitive. However, parameter + values are case-sensitive unless otherwise specified for the specific + parameter. + + FORMATTING NOTE: Notes, such at this one, provide additional + nonessential information which may be skipped by the reader without + missing anything essential. The primary purpose of these non- + essential notes is to convey information about the rationale of this + set of documents, or to place these documents in the proper + historical or evolutionary context. Such information may in + particular be skipped by those who are focused entirely on building a + conformant implementation, but may be of use to those who wish to + understand why certain design choices were made. + +2.1. CRLF + + The term CRLF, in this set of documents, refers to the sequence of + octets corresponding to the two US-ASCII characters CR (decimal value + 13) and LF (decimal value 10) which, taken together, in this order, + denote a line break in RFC 822 mail. + + + + + +Freed & Borenstein Standards Track [Page 5] + +RFC 2045 Internet Message Bodies November 1996 + + +2.2. Character Set + + The term "character set" is used in MIME to refer to a method of + converting a sequence of octets into a sequence of characters. Note + that unconditional and unambiguous conversion in the other direction + is not required, in that not all characters may be representable by a + given character set and a character set may provide more than one + sequence of octets to represent a particular sequence of characters. + + This definition is intended to allow various kinds of character + encodings, from simple single-table mappings such as US-ASCII to + complex table switching methods such as those that use ISO 2022's + techniques, to be used as character sets. However, the definition + associated with a MIME character set name must fully specify the + mapping to be performed. In particular, use of external profiling + information to determine the exact mapping is not permitted. + + NOTE: The term "character set" was originally to describe such + straightforward schemes as US-ASCII and ISO-8859-1 which have a + simple one-to-one mapping from single octets to single characters. + Multi-octet coded character sets and switching techniques make the + situation more complex. For example, some communities use the term + "character encoding" for what MIME calls a "character set", while + using the phrase "coded character set" to denote an abstract mapping + from integers (not octets) to characters. + +2.3. Message + + The term "message", when not further qualified, means either a + (complete or "top-level") RFC 822 message being transferred on a + network, or a message encapsulated in a body of type "message/rfc822" + or "message/partial". + +2.4. Entity + + The term "entity", refers specifically to the MIME-defined header + fields and contents of either a message or one of the parts in the + body of a multipart entity. The specification of such entities is + the essence of MIME. Since the contents of an entity are often + called the "body", it makes sense to speak about the body of an + entity. Any sort of field may be present in the header of an entity, + but only those fields whose names begin with "content-" actually have + any MIME-related meaning. Note that this does NOT imply thay they + have no meaning at all -- an entity that is also a message has non- + MIME header fields whose meanings are defined by RFC 822. + + + + + + +Freed & Borenstein Standards Track [Page 6] + +RFC 2045 Internet Message Bodies November 1996 + + +2.5. Body Part + + The term "body part" refers to an entity inside of a multipart + entity. + +2.6. Body + + The term "body", when not further qualified, means the body of an + entity, that is, the body of either a message or of a body part. + + NOTE: The previous four definitions are clearly circular. This is + unavoidable, since the overall structure of a MIME message is indeed + recursive. + +2.7. 7bit Data + + "7bit data" refers to data that is all represented as relatively + short lines with 998 octets or less between CRLF line separation + sequences [RFC-821]. No octets with decimal values greater than 127 + are allowed and neither are NULs (octets with decimal value 0). CR + (decimal value 13) and LF (decimal value 10) octets only occur as + part of CRLF line separation sequences. + +2.8. 8bit Data + + "8bit data" refers to data that is all represented as relatively + short lines with 998 octets or less between CRLF line separation + sequences [RFC-821]), but octets with decimal values greater than 127 + may be used. As with "7bit data" CR and LF octets only occur as part + of CRLF line separation sequences and no NULs are allowed. + +2.9. Binary Data + + "Binary data" refers to data where any sequence of octets whatsoever + is allowed. + +2.10. Lines + + "Lines" are defined as sequences of octets separated by a CRLF + sequences. This is consistent with both RFC 821 and RFC 822. + "Lines" only refers to a unit of data in a message, which may or may + not correspond to something that is actually displayed by a user + agent. + + + + + + + + +Freed & Borenstein Standards Track [Page 7] + +RFC 2045 Internet Message Bodies November 1996 + + +3. MIME Header Fields + + MIME defines a number of new RFC 822 header fields that are used to + describe the content of a MIME entity. These header fields occur in + at least two contexts: + + (1) As part of a regular RFC 822 message header. + + (2) In a MIME body part header within a multipart + construct. + + The formal definition of these header fields is as follows: + + entity-headers := [ content CRLF ] + [ encoding CRLF ] + [ id CRLF ] + [ description CRLF ] + *( MIME-extension-field CRLF ) + + MIME-message-headers := entity-headers + fields + version CRLF + ; The ordering of the header + ; fields implied by this BNF + ; definition should be ignored. + + MIME-part-headers := entity-headers + [ fields ] + ; Any field not beginning with + ; "content-" can have no defined + ; meaning and may be ignored. + ; The ordering of the header + ; fields implied by this BNF + ; definition should be ignored. + + The syntax of the various specific MIME header fields will be + described in the following sections. + +4. MIME-Version Header Field + + Since RFC 822 was published in 1982, there has really been only one + format standard for Internet messages, and there has been little + perceived need to declare the format standard in use. This document + is an independent specification that complements RFC 822. Although + the extensions in this document have been defined in such a way as to + be compatible with RFC 822, there are still circumstances in which it + might be desirable for a mail-processing agent to know whether a + message was composed with the new standard in mind. + + + +Freed & Borenstein Standards Track [Page 8] + +RFC 2045 Internet Message Bodies November 1996 + + + Therefore, this document defines a new header field, "MIME-Version", + which is to be used to declare the version of the Internet message + body format standard in use. + + Messages composed in accordance with this document MUST include such + a header field, with the following verbatim text: + + MIME-Version: 1.0 + + The presence of this header field is an assertion that the message + has been composed in compliance with this document. + + Since it is possible that a future document might extend the message + format standard again, a formal BNF is given for the content of the + MIME-Version field: + + version := "MIME-Version" ":" 1*DIGIT "." 1*DIGIT + + Thus, future format specifiers, which might replace or extend "1.0", + are constrained to be two integer fields, separated by a period. If + a message is received with a MIME-version value other than "1.0", it + cannot be assumed to conform with this document. + + Note that the MIME-Version header field is required at the top level + of a message. It is not required for each body part of a multipart + entity. It is required for the embedded headers of a body of type + "message/rfc822" or "message/partial" if and only if the embedded + message is itself claimed to be MIME-conformant. + + It is not possible to fully specify how a mail reader that conforms + with MIME as defined in this document should treat a message that + might arrive in the future with some value of MIME-Version other than + "1.0". + + It is also worth noting that version control for specific media types + is not accomplished using the MIME-Version mechanism. In particular, + some formats (such as application/postscript) have version numbering + conventions that are internal to the media format. Where such + conventions exist, MIME does nothing to supersede them. Where no + such conventions exist, a MIME media type might use a "version" + parameter in the content-type field if necessary. + + + + + + + + + + +Freed & Borenstein Standards Track [Page 9] + +RFC 2045 Internet Message Bodies November 1996 + + + NOTE TO IMPLEMENTORS: When checking MIME-Version values any RFC 822 + comment strings that are present must be ignored. In particular, the + following four MIME-Version fields are equivalent: + + MIME-Version: 1.0 + + MIME-Version: 1.0 (produced by MetaSend Vx.x) + + MIME-Version: (produced by MetaSend Vx.x) 1.0 + + MIME-Version: 1.(produced by MetaSend Vx.x)0 + + In the absence of a MIME-Version field, a receiving mail user agent + (whether conforming to MIME requirements or not) may optionally + choose to interpret the body of the message according to local + conventions. Many such conventions are currently in use and it + should be noted that in practice non-MIME messages can contain just + about anything. + + It is impossible to be certain that a non-MIME mail message is + actually plain text in the US-ASCII character set since it might well + be a message that, using some set of nonstandard local conventions + that predate MIME, includes text in another character set or non- + textual data presented in a manner that cannot be automatically + recognized (e.g., a uuencoded compressed UNIX tar file). + +5. Content-Type Header Field + + The purpose of the Content-Type field is to describe the data + contained in the body fully enough that the receiving user agent can + pick an appropriate agent or mechanism to present the data to the + user, or otherwise deal with the data in an appropriate manner. The + value in this field is called a media type. + + HISTORICAL NOTE: The Content-Type header field was first defined in + RFC 1049. RFC 1049 used a simpler and less powerful syntax, but one + that is largely compatible with the mechanism given here. + + The Content-Type header field specifies the nature of the data in the + body of an entity by giving media type and subtype identifiers, and + by providing auxiliary information that may be required for certain + media types. After the media type and subtype names, the remainder + of the header field is simply a set of parameters, specified in an + attribute=value notation. The ordering of parameters is not + significant. + + + + + + +Freed & Borenstein Standards Track [Page 10] + +RFC 2045 Internet Message Bodies November 1996 + + + In general, the top-level media type is used to declare the general + type of data, while the subtype specifies a specific format for that + type of data. Thus, a media type of "image/xyz" is enough to tell a + user agent that the data is an image, even if the user agent has no + knowledge of the specific image format "xyz". Such information can + be used, for example, to decide whether or not to show a user the raw + data from an unrecognized subtype -- such an action might be + reasonable for unrecognized subtypes of text, but not for + unrecognized subtypes of image or audio. For this reason, registered + subtypes of text, image, audio, and video should not contain embedded + information that is really of a different type. Such compound + formats should be represented using the "multipart" or "application" + types. + + Parameters are modifiers of the media subtype, and as such do not + fundamentally affect the nature of the content. The set of + meaningful parameters depends on the media type and subtype. Most + parameters are associated with a single specific subtype. However, a + given top-level media type may define parameters which are applicable + to any subtype of that type. Parameters may be required by their + defining content type or subtype or they may be optional. MIME + implementations must ignore any parameters whose names they do not + recognize. + + For example, the "charset" parameter is applicable to any subtype of + "text", while the "boundary" parameter is required for any subtype of + the "multipart" media type. + + There are NO globally-meaningful parameters that apply to all media + types. Truly global mechanisms are best addressed, in the MIME + model, by the definition of additional Content-* header fields. + + An initial set of seven top-level media types is defined in RFC 2046. + Five of these are discrete types whose content is essentially opaque + as far as MIME processing is concerned. The remaining two are + composite types whose contents require additional handling by MIME + processors. + + This set of top-level media types is intended to be substantially + complete. It is expected that additions to the larger set of + supported types can generally be accomplished by the creation of new + subtypes of these initial types. In the future, more top-level types + may be defined only by a standards-track extension to this standard. + If another top-level type is to be used for any reason, it must be + given a name starting with "X-" to indicate its non-standard status + and to avoid a potential conflict with a future official name. + + + + + +Freed & Borenstein Standards Track [Page 11] + +RFC 2045 Internet Message Bodies November 1996 + + +5.1. Syntax of the Content-Type Header Field + + In the Augmented BNF notation of RFC 822, a Content-Type header field + value is defined as follows: + + content := "Content-Type" ":" type "/" subtype + *(";" parameter) + ; Matching of media type and subtype + ; is ALWAYS case-insensitive. + + type := discrete-type / composite-type + + discrete-type := "text" / "image" / "audio" / "video" / + "application" / extension-token + + composite-type := "message" / "multipart" / extension-token + + extension-token := ietf-token / x-token + + ietf-token := + + x-token := + + subtype := extension-token / iana-token + + iana-token := + + parameter := attribute "=" value + + attribute := token + ; Matching of attributes + ; is ALWAYS case-insensitive. + + value := token / quoted-string + + token := 1* + + tspecials := "(" / ")" / "<" / ">" / "@" / + "," / ";" / ":" / "\" / <"> + "/" / "[" / "]" / "?" / "=" + ; Must be in quoted-string, + ; to use within parameter values + + + +Freed & Borenstein Standards Track [Page 12] + +RFC 2045 Internet Message Bodies November 1996 + + + Note that the definition of "tspecials" is the same as the RFC 822 + definition of "specials" with the addition of the three characters + "/", "?", and "=", and the removal of ".". + + Note also that a subtype specification is MANDATORY -- it may not be + omitted from a Content-Type header field. As such, there are no + default subtypes. + + The type, subtype, and parameter names are not case sensitive. For + example, TEXT, Text, and TeXt are all equivalent top-level media + types. Parameter values are normally case sensitive, but sometimes + are interpreted in a case-insensitive fashion, depending on the + intended use. (For example, multipart boundaries are case-sensitive, + but the "access-type" parameter for message/External-body is not + case-sensitive.) + + Note that the value of a quoted string parameter does not include the + quotes. That is, the quotation marks in a quoted-string are not a + part of the value of the parameter, but are merely used to delimit + that parameter value. In addition, comments are allowed in + accordance with RFC 822 rules for structured header fields. Thus the + following two forms + + Content-type: text/plain; charset=us-ascii (Plain text) + + Content-type: text/plain; charset="us-ascii" + + are completely equivalent. + + Beyond this syntax, the only syntactic constraint on the definition + of subtype names is the desire that their uses must not conflict. + That is, it would be undesirable to have two different communities + using "Content-Type: application/foobar" to mean two different + things. The process of defining new media subtypes, then, is not + intended to be a mechanism for imposing restrictions, but simply a + mechanism for publicizing their definition and usage. There are, + therefore, two acceptable mechanisms for defining new media subtypes: + + (1) Private values (starting with "X-") may be defined + bilaterally between two cooperating agents without + outside registration or standardization. Such values + cannot be registered or standardized. + + (2) New standard values should be registered with IANA as + described in RFC 2048. + + The second document in this set, RFC 2046, defines the initial set of + media types for MIME. + + + +Freed & Borenstein Standards Track [Page 13] + +RFC 2045 Internet Message Bodies November 1996 + + +5.2. Content-Type Defaults + + Default RFC 822 messages without a MIME Content-Type header are taken + by this protocol to be plain text in the US-ASCII character set, + which can be explicitly specified as: + + Content-type: text/plain; charset=us-ascii + + This default is assumed if no Content-Type header field is specified. + It is also recommend that this default be assumed when a + syntactically invalid Content-Type header field is encountered. In + the presence of a MIME-Version header field and the absence of any + Content-Type header field, a receiving User Agent can also assume + that plain US-ASCII text was the sender's intent. Plain US-ASCII + text may still be assumed in the absence of a MIME-Version or the + presence of an syntactically invalid Content-Type header field, but + the sender's intent might have been otherwise. + +6. Content-Transfer-Encoding Header Field + + Many media types which could be usefully transported via email are + represented, in their "natural" format, as 8bit character or binary + data. Such data cannot be transmitted over some transfer protocols. + For example, RFC 821 (SMTP) restricts mail messages to 7bit US-ASCII + data with lines no longer than 1000 characters including any trailing + CRLF line separator. + + It is necessary, therefore, to define a standard mechanism for + encoding such data into a 7bit short line format. Proper labelling + of unencoded material in less restrictive formats for direct use over + less restrictive transports is also desireable. This document + specifies that such encodings will be indicated by a new "Content- + Transfer-Encoding" header field. This field has not been defined by + any previous standard. + +6.1. Content-Transfer-Encoding Syntax + + The Content-Transfer-Encoding field's value is a single token + specifying the type of encoding, as enumerated below. Formally: + + encoding := "Content-Transfer-Encoding" ":" mechanism + + mechanism := "7bit" / "8bit" / "binary" / + "quoted-printable" / "base64" / + ietf-token / x-token + + These values are not case sensitive -- Base64 and BASE64 and bAsE64 + are all equivalent. An encoding type of 7BIT requires that the body + + + +Freed & Borenstein Standards Track [Page 14] + +RFC 2045 Internet Message Bodies November 1996 + + + is already in a 7bit mail-ready representation. This is the default + value -- that is, "Content-Transfer-Encoding: 7BIT" is assumed if the + Content-Transfer-Encoding header field is not present. + +6.2. Content-Transfer-Encodings Semantics + + This single Content-Transfer-Encoding token actually provides two + pieces of information. It specifies what sort of encoding + transformation the body was subjected to and hence what decoding + operation must be used to restore it to its original form, and it + specifies what the domain of the result is. + + The transformation part of any Content-Transfer-Encodings specifies, + either explicitly or implicitly, a single, well-defined decoding + algorithm, which for any sequence of encoded octets either transforms + it to the original sequence of octets which was encoded, or shows + that it is illegal as an encoded sequence. Content-Transfer- + Encodings transformations never depend on any additional external + profile information for proper operation. Note that while decoders + must produce a single, well-defined output for a valid encoding no + such restrictions exist for encoders: Encoding a given sequence of + octets to different, equivalent encoded sequences is perfectly legal. + + Three transformations are currently defined: identity, the "quoted- + printable" encoding, and the "base64" encoding. The domains are + "binary", "8bit" and "7bit". + + The Content-Transfer-Encoding values "7bit", "8bit", and "binary" all + mean that the identity (i.e. NO) encoding transformation has been + performed. As such, they serve simply as indicators of the domain of + the body data, and provide useful information about the sort of + encoding that might be needed for transmission in a given transport + system. The terms "7bit data", "8bit data", and "binary data" are + all defined in Section 2. + + The quoted-printable and base64 encodings transform their input from + an arbitrary domain into material in the "7bit" range, thus making it + safe to carry over restricted transports. The specific definition of + the transformations are given below. + + The proper Content-Transfer-Encoding label must always be used. + Labelling unencoded data containing 8bit characters as "7bit" is not + allowed, nor is labelling unencoded non-line-oriented data as + anything other than "binary" allowed. + + Unlike media subtypes, a proliferation of Content-Transfer-Encoding + values is both undesirable and unnecessary. However, establishing + only a single transformation into the "7bit" domain does not seem + + + +Freed & Borenstein Standards Track [Page 15] + +RFC 2045 Internet Message Bodies November 1996 + + + possible. There is a tradeoff between the desire for a compact and + efficient encoding of largely- binary data and the desire for a + somewhat readable encoding of data that is mostly, but not entirely, + 7bit. For this reason, at least two encoding mechanisms are + necessary: a more or less readable encoding (quoted-printable) and a + "dense" or "uniform" encoding (base64). + + Mail transport for unencoded 8bit data is defined in RFC 1652. As of + the initial publication of this document, there are no standardized + Internet mail transports for which it is legitimate to include + unencoded binary data in mail bodies. Thus there are no + circumstances in which the "binary" Content-Transfer-Encoding is + actually valid in Internet mail. However, in the event that binary + mail transport becomes a reality in Internet mail, or when MIME is + used in conjunction with any other binary-capable mail transport + mechanism, binary bodies must be labelled as such using this + mechanism. + + NOTE: The five values defined for the Content-Transfer-Encoding field + imply nothing about the media type other than the algorithm by which + it was encoded or the transport system requirements if unencoded. + +6.3. New Content-Transfer-Encodings + + Implementors may, if necessary, define private Content-Transfer- + Encoding values, but must use an x-token, which is a name prefixed by + "X-", to indicate its non-standard status, e.g., "Content-Transfer- + Encoding: x-my-new-encoding". Additional standardized Content- + Transfer-Encoding values must be specified by a standards-track RFC. + The requirements such specifications must meet are given in RFC 2048. + As such, all content-transfer-encoding namespace except that + beginning with "X-" is explicitly reserved to the IETF for future + use. + + Unlike media types and subtypes, the creation of new Content- + Transfer-Encoding values is STRONGLY discouraged, as it seems likely + to hinder interoperability with little potential benefit + +6.4. Interpretation and Use + + If a Content-Transfer-Encoding header field appears as part of a + message header, it applies to the entire body of that message. If a + Content-Transfer-Encoding header field appears as part of an entity's + headers, it applies only to the body of that entity. If an entity is + of type "multipart" the Content-Transfer-Encoding is not permitted to + have any value other than "7bit", "8bit" or "binary". Even more + severe restrictions apply to some subtypes of the "message" type. + + + + +Freed & Borenstein Standards Track [Page 16] + +RFC 2045 Internet Message Bodies November 1996 + + + It should be noted that most media types are defined in terms of + octets rather than bits, so that the mechanisms described here are + mechanisms for encoding arbitrary octet streams, not bit streams. If + a bit stream is to be encoded via one of these mechanisms, it must + first be converted to an 8bit byte stream using the network standard + bit order ("big-endian"), in which the earlier bits in a stream + become the higher-order bits in a 8bit byte. A bit stream not ending + at an 8bit boundary must be padded with zeroes. RFC 2046 provides a + mechanism for noting the addition of such padding in the case of the + application/octet-stream media type, which has a "padding" parameter. + + The encoding mechanisms defined here explicitly encode all data in + US-ASCII. Thus, for example, suppose an entity has header fields + such as: + + Content-Type: text/plain; charset=ISO-8859-1 + Content-transfer-encoding: base64 + + This must be interpreted to mean that the body is a base64 US-ASCII + encoding of data that was originally in ISO-8859-1, and will be in + that character set again after decoding. + + Certain Content-Transfer-Encoding values may only be used on certain + media types. In particular, it is EXPRESSLY FORBIDDEN to use any + encodings other than "7bit", "8bit", or "binary" with any composite + media type, i.e. one that recursively includes other Content-Type + fields. Currently the only composite media types are "multipart" and + "message". All encodings that are desired for bodies of type + multipart or message must be done at the innermost level, by encoding + the actual body that needs to be encoded. + + It should also be noted that, by definition, if a composite entity + has a transfer-encoding value such as "7bit", but one of the enclosed + entities has a less restrictive value such as "8bit", then either the + outer "7bit" labelling is in error, because 8bit data are included, + or the inner "8bit" labelling placed an unnecessarily high demand on + the transport system because the actual included data were actually + 7bit-safe. + + NOTE ON ENCODING RESTRICTIONS: Though the prohibition against using + content-transfer-encodings on composite body data may seem overly + restrictive, it is necessary to prevent nested encodings, in which + data are passed through an encoding algorithm multiple times, and + must be decoded multiple times in order to be properly viewed. + Nested encodings add considerable complexity to user agents: Aside + from the obvious efficiency problems with such multiple encodings, + they can obscure the basic structure of a message. In particular, + they can imply that several decoding operations are necessary simply + + + +Freed & Borenstein Standards Track [Page 17] + +RFC 2045 Internet Message Bodies November 1996 + + + to find out what types of bodies a message contains. Banning nested + encodings may complicate the job of certain mail gateways, but this + seems less of a problem than the effect of nested encodings on user + agents. + + Any entity with an unrecognized Content-Transfer-Encoding must be + treated as if it has a Content-Type of "application/octet-stream", + regardless of what the Content-Type header field actually says. + + NOTE ON THE RELATIONSHIP BETWEEN CONTENT-TYPE AND CONTENT-TRANSFER- + ENCODING: It may seem that the Content-Transfer-Encoding could be + inferred from the characteristics of the media that is to be encoded, + or, at the very least, that certain Content-Transfer-Encodings could + be mandated for use with specific media types. There are several + reasons why this is not the case. First, given the varying types of + transports used for mail, some encodings may be appropriate for some + combinations of media types and transports but not for others. (For + example, in an 8bit transport, no encoding would be required for text + in certain character sets, while such encodings are clearly required + for 7bit SMTP.) + + Second, certain media types may require different types of transfer + encoding under different circumstances. For example, many PostScript + bodies might consist entirely of short lines of 7bit data and hence + require no encoding at all. Other PostScript bodies (especially + those using Level 2 PostScript's binary encoding mechanism) may only + be reasonably represented using a binary transport encoding. + Finally, since the Content-Type field is intended to be an open-ended + specification mechanism, strict specification of an association + between media types and encodings effectively couples the + specification of an application protocol with a specific lower-level + transport. This is not desirable since the developers of a media + type should not have to be aware of all the transports in use and + what their limitations are. + +6.5. Translating Encodings + + The quoted-printable and base64 encodings are designed so that + conversion between them is possible. The only issue that arises in + such a conversion is the handling of hard line breaks in quoted- + printable encoding output. When converting from quoted-printable to + base64 a hard line break in the quoted-printable form represents a + CRLF sequence in the canonical form of the data. It must therefore be + converted to a corresponding encoded CRLF in the base64 form of the + data. Similarly, a CRLF sequence in the canonical form of the data + obtained after base64 decoding must be converted to a quoted- + printable hard line break, but ONLY when converting text data. + + + + +Freed & Borenstein Standards Track [Page 18] + +RFC 2045 Internet Message Bodies November 1996 + + +6.6. Canonical Encoding Model + + There was some confusion, in the previous versions of this RFC, + regarding the model for when email data was to be converted to + canonical form and encoded, and in particular how this process would + affect the treatment of CRLFs, given that the representation of + newlines varies greatly from system to system, and the relationship + between content-transfer-encodings and character sets. A canonical + model for encoding is presented in RFC 2049 for this reason. + +6.7. Quoted-Printable Content-Transfer-Encoding + + The Quoted-Printable encoding is intended to represent data that + largely consists of octets that correspond to printable characters in + the US-ASCII character set. It encodes the data in such a way that + the resulting octets are unlikely to be modified by mail transport. + If the data being encoded are mostly US-ASCII text, the encoded form + of the data remains largely recognizable by humans. A body which is + entirely US-ASCII may also be encoded in Quoted-Printable to ensure + the integrity of the data should the message pass through a + character-translating, and/or line-wrapping gateway. + + In this encoding, octets are to be represented as determined by the + following rules: + + (1) (General 8bit representation) Any octet, except a CR or + LF that is part of a CRLF line break of the canonical + (standard) form of the data being encoded, may be + represented by an "=" followed by a two digit + hexadecimal representation of the octet's value. The + digits of the hexadecimal alphabet, for this purpose, + are "0123456789ABCDEF". Uppercase letters must be + used; lowercase letters are not allowed. Thus, for + example, the decimal value 12 (US-ASCII form feed) can + be represented by "=0C", and the decimal value 61 (US- + ASCII EQUAL SIGN) can be represented by "=3D". This + rule must be followed except when the following rules + allow an alternative encoding. + + (2) (Literal representation) Octets with decimal values of + 33 through 60 inclusive, and 62 through 126, inclusive, + MAY be represented as the US-ASCII characters which + correspond to those octets (EXCLAMATION POINT through + LESS THAN, and GREATER THAN through TILDE, + respectively). + + (3) (White Space) Octets with values of 9 and 32 MAY be + represented as US-ASCII TAB (HT) and SPACE characters, + + + +Freed & Borenstein Standards Track [Page 19] + +RFC 2045 Internet Message Bodies November 1996 + + + respectively, but MUST NOT be so represented at the end + of an encoded line. Any TAB (HT) or SPACE characters + on an encoded line MUST thus be followed on that line + by a printable character. In particular, an "=" at the + end of an encoded line, indicating a soft line break + (see rule #5) may follow one or more TAB (HT) or SPACE + characters. It follows that an octet with decimal + value 9 or 32 appearing at the end of an encoded line + must be represented according to Rule #1. This rule is + necessary because some MTAs (Message Transport Agents, + programs which transport messages from one user to + another, or perform a portion of such transfers) are + known to pad lines of text with SPACEs, and others are + known to remove "white space" characters from the end + of a line. Therefore, when decoding a Quoted-Printable + body, any trailing white space on a line must be + deleted, as it will necessarily have been added by + intermediate transport agents. + + (4) (Line Breaks) A line break in a text body, represented + as a CRLF sequence in the text canonical form, must be + represented by a (RFC 822) line break, which is also a + CRLF sequence, in the Quoted-Printable encoding. Since + the canonical representation of media types other than + text do not generally include the representation of + line breaks as CRLF sequences, no hard line breaks + (i.e. line breaks that are intended to be meaningful + and to be displayed to the user) can occur in the + quoted-printable encoding of such types. Sequences + like "=0D", "=0A", "=0A=0D" and "=0D=0A" will routinely + appear in non-text data represented in quoted- + printable, of course. + + Note that many implementations may elect to encode the + local representation of various content types directly + rather than converting to canonical form first, + encoding, and then converting back to local + representation. In particular, this may apply to plain + text material on systems that use newline conventions + other than a CRLF terminator sequence. Such an + implementation optimization is permissible, but only + when the combined canonicalization-encoding step is + equivalent to performing the three steps separately. + + (5) (Soft Line Breaks) The Quoted-Printable encoding + REQUIRES that encoded lines be no more than 76 + characters long. If longer lines are to be encoded + with the Quoted-Printable encoding, "soft" line breaks + + + +Freed & Borenstein Standards Track [Page 20] + +RFC 2045 Internet Message Bodies November 1996 + + + must be used. An equal sign as the last character on a + encoded line indicates such a non-significant ("soft") + line break in the encoded text. + + Thus if the "raw" form of the line is a single unencoded line that + says: + + Now's the time for all folk to come to the aid of their country. + + This can be represented, in the Quoted-Printable encoding, as: + + Now's the time = + for all folk to come= + to the aid of their country. + + This provides a mechanism with which long lines are encoded in such a + way as to be restored by the user agent. The 76 character limit does + not count the trailing CRLF, but counts all other characters, + including any equal signs. + + Since the hyphen character ("-") may be represented as itself in the + Quoted-Printable encoding, care must be taken, when encapsulating a + quoted-printable encoded body inside one or more multipart entities, + to ensure that the boundary delimiter does not appear anywhere in the + encoded body. (A good strategy is to choose a boundary that includes + a character sequence such as "=_" which can never appear in a + quoted-printable body. See the definition of multipart messages in + RFC 2046.) + + NOTE: The quoted-printable encoding represents something of a + compromise between readability and reliability in transport. Bodies + encoded with the quoted-printable encoding will work reliably over + most mail gateways, but may not work perfectly over a few gateways, + notably those involving translation into EBCDIC. A higher level of + confidence is offered by the base64 Content-Transfer-Encoding. A way + to get reasonably reliable transport through EBCDIC gateways is to + also quote the US-ASCII characters + + !"#$@[\]^`{|}~ + + according to rule #1. + + Because quoted-printable data is generally assumed to be line- + oriented, it is to be expected that the representation of the breaks + between the lines of quoted-printable data may be altered in + transport, in the same manner that plain text mail has always been + altered in Internet mail when passing between systems with differing + newline conventions. If such alterations are likely to constitute a + + + +Freed & Borenstein Standards Track [Page 21] + +RFC 2045 Internet Message Bodies November 1996 + + + corruption of the data, it is probably more sensible to use the + base64 encoding rather than the quoted-printable encoding. + + NOTE: Several kinds of substrings cannot be generated according to + the encoding rules for the quoted-printable content-transfer- + encoding, and hence are formally illegal if they appear in the output + of a quoted-printable encoder. This note enumerates these cases and + suggests ways to handle such illegal substrings if any are + encountered in quoted-printable data that is to be decoded. + + (1) An "=" followed by two hexadecimal digits, one or both + of which are lowercase letters in "abcdef", is formally + illegal. A robust implementation might choose to + recognize them as the corresponding uppercase letters. + + (2) An "=" followed by a character that is neither a + hexadecimal digit (including "abcdef") nor the CR + character of a CRLF pair is illegal. This case can be + the result of US-ASCII text having been included in a + quoted-printable part of a message without itself + having been subjected to quoted-printable encoding. A + reasonable approach by a robust implementation might be + to include the "=" character and the following + character in the decoded data without any + transformation and, if possible, indicate to the user + that proper decoding was not possible at this point in + the data. + + (3) An "=" cannot be the ultimate or penultimate character + in an encoded object. This could be handled as in case + (2) above. + + (4) Control characters other than TAB, or CR and LF as + parts of CRLF pairs, must not appear. The same is true + for octets with decimal values greater than 126. If + found in incoming quoted-printable data by a decoder, a + robust implementation might exclude them from the + decoded data and warn the user that illegal characters + were discovered. + + (5) Encoded lines must not be longer than 76 characters, + not counting the trailing CRLF. If longer lines are + found in incoming, encoded data, a robust + implementation might nevertheless decode the lines, and + might report the erroneous encoding to the user. + + + + + + +Freed & Borenstein Standards Track [Page 22] + +RFC 2045 Internet Message Bodies November 1996 + + + WARNING TO IMPLEMENTORS: If binary data is encoded in quoted- + printable, care must be taken to encode CR and LF characters as "=0D" + and "=0A", respectively. In particular, a CRLF sequence in binary + data should be encoded as "=0D=0A". Otherwise, if CRLF were + represented as a hard line break, it might be incorrectly decoded on + platforms with different line break conventions. + + For formalists, the syntax of quoted-printable data is described by + the following grammar: + + quoted-printable := qp-line *(CRLF qp-line) + + qp-line := *(qp-segment transport-padding CRLF) + qp-part transport-padding + + qp-part := qp-section + ; Maximum length of 76 characters + + qp-segment := qp-section *(SPACE / TAB) "=" + ; Maximum length of 76 characters + + qp-section := [*(ptext / SPACE / TAB) ptext] + + ptext := hex-octet / safe-char + + safe-char := + ; Characters not listed as "mail-safe" in + ; RFC 2049 are also not recommended. + + hex-octet := "=" 2(DIGIT / "A" / "B" / "C" / "D" / "E" / "F") + ; Octet must be used for characters > 127, =, + ; SPACEs or TABs at the ends of lines, and is + ; recommended for any character not listed in + ; RFC 2049 as "mail-safe". + + transport-padding := *LWSP-char + ; Composers MUST NOT generate + ; non-zero length transport + ; padding, but receivers MUST + ; be able to handle padding + ; added by message transports. + + IMPORTANT: The addition of LWSP between the elements shown in this + BNF is NOT allowed since this BNF does not specify a structured + header field. + + + + + +Freed & Borenstein Standards Track [Page 23] + +RFC 2045 Internet Message Bodies November 1996 + + +6.8. Base64 Content-Transfer-Encoding + + The Base64 Content-Transfer-Encoding is designed to represent + arbitrary sequences of octets in a form that need not be humanly + readable. The encoding and decoding algorithms are simple, but the + encoded data are consistently only about 33 percent larger than the + unencoded data. This encoding is virtually identical to the one used + in Privacy Enhanced Mail (PEM) applications, as defined in RFC 1421. + + A 65-character subset of US-ASCII is used, enabling 6 bits to be + represented per printable character. (The extra 65th character, "=", + is used to signify a special processing function.) + + NOTE: This subset has the important property that it is represented + identically in all versions of ISO 646, including US-ASCII, and all + characters in the subset are also represented identically in all + versions of EBCDIC. Other popular encodings, such as the encoding + used by the uuencode utility, Macintosh binhex 4.0 [RFC-1741], and + the base85 encoding specified as part of Level 2 PostScript, do not + share these properties, and thus do not fulfill the portability + requirements a binary transport encoding for mail must meet. + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + When encoding a bit stream via the base64 encoding, the bit stream + must be presumed to be ordered with the most-significant-bit first. + That is, the first bit in the stream will be the high-order bit in + the first 8bit byte, and the eighth bit will be the low-order bit in + the first 8bit byte, and so on. + + Each 6-bit group is used as an index into an array of 64 printable + characters. The character referenced by the index is placed in the + output string. These characters, identified in Table 1, below, are + selected so as to be universally representable, and the set excludes + characters with particular significance to SMTP (e.g., ".", CR, LF) + and to the multipart boundary delimiters defined in RFC 2046 (e.g., + "-"). + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 24] + +RFC 2045 Internet Message Bodies November 1996 + + + Table 1: The Base64 Alphabet + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + The encoded output stream must be represented in lines of no more + than 76 characters each. All line breaks or other characters not + found in Table 1 must be ignored by decoding software. In base64 + data, characters other than those in Table 1, line breaks, and other + white space probably indicate a transmission error, about which a + warning message or even a message rejection might be appropriate + under some circumstances. + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a body. When fewer than 24 input bits + are available in an input group, zero bits are added (on the right) + to form an integral number of 6-bit groups. Padding at the end of + the data is performed using the "=" character. Since all base64 + input is an integral number of octets, only the following cases can + arise: (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded output will be + an integral multiple of 4 characters with no "=" padding, (2) the + final quantum of encoding input is exactly 8 bits; here, the final + unit of encoded output will be two characters followed by two "=" + padding characters, or (3) the final quantum of encoding input is + exactly 16 bits; here, the final unit of encoded output will be three + characters followed by one "=" padding character. + + Because it is used only for padding at the end of the data, the + occurrence of any "=" characters may be taken as evidence that the + end of the data has been reached (without truncation in transit). No + + + +Freed & Borenstein Standards Track [Page 25] + +RFC 2045 Internet Message Bodies November 1996 + + + such assurance is possible, however, when the number of octets + transmitted was a multiple of three and no "=" characters are + present. + + Any characters outside of the base64 alphabet are to be ignored in + base64-encoded data. + + Care must be taken to use the proper octets for line breaks if base64 + encoding is applied directly to text material that has not been + converted to canonical form. In particular, text line breaks must be + converted into CRLF sequences prior to base64 encoding. The + important thing to note is that this may be done directly by the + encoder rather than in a prior canonicalization step in some + implementations. + + NOTE: There is no need to worry about quoting potential boundary + delimiters within base64-encoded bodies within multipart entities + because no hyphen characters are used in the base64 encoding. + +7. Content-ID Header Field + + In constructing a high-level user agent, it may be desirable to allow + one body to make reference to another. Accordingly, bodies may be + labelled using the "Content-ID" header field, which is syntactically + identical to the "Message-ID" header field: + + id := "Content-ID" ":" msg-id + + Like the Message-ID values, Content-ID values must be generated to be + world-unique. + + The Content-ID value may be used for uniquely identifying MIME + entities in several contexts, particularly for caching data + referenced by the message/external-body mechanism. Although the + Content-ID header is generally optional, its use is MANDATORY in + implementations which generate data of the optional MIME media type + "message/external-body". That is, each message/external-body entity + must have a Content-ID field to permit caching of such data. + + It is also worth noting that the Content-ID value has special + semantics in the case of the multipart/alternative media type. This + is explained in the section of RFC 2046 dealing with + multipart/alternative. + + + + + + + + +Freed & Borenstein Standards Track [Page 26] + +RFC 2045 Internet Message Bodies November 1996 + + +8. Content-Description Header Field + + The ability to associate some descriptive information with a given + body is often desirable. For example, it may be useful to mark an + "image" body as "a picture of the Space Shuttle Endeavor." Such text + may be placed in the Content-Description header field. This header + field is always optional. + + description := "Content-Description" ":" *text + + The description is presumed to be given in the US-ASCII character + set, although the mechanism specified in RFC 2047 may be used for + non-US-ASCII Content-Description values. + +9. Additional MIME Header Fields + + Future documents may elect to define additional MIME header fields + for various purposes. Any new header field that further describes + the content of a message should begin with the string "Content-" to + allow such fields which appear in a message header to be + distinguished from ordinary RFC 822 message header fields. + + MIME-extension-field := + +10. Summary + + Using the MIME-Version, Content-Type, and Content-Transfer-Encoding + header fields, it is possible to include, in a standardized way, + arbitrary types of data with RFC 822 conformant mail messages. No + restrictions imposed by either RFC 821 or RFC 822 are violated, and + care has been taken to avoid problems caused by additional + restrictions imposed by the characteristics of some Internet mail + transport mechanisms (see RFC 2049). + + The next document in this set, RFC 2046, specifies the initial set of + media types that can be labelled and transported using these headers. + +11. Security Considerations + + Security issues are discussed in the second document in this set, RFC + 2046. + + + + + + + + +Freed & Borenstein Standards Track [Page 27] + +RFC 2045 Internet Message Bodies November 1996 + + +12. Authors' Addresses + + For more information, the authors of this document are best contacted + via Internet mail: + + Ned Freed + Innosoft International, Inc. + 1050 East Garvey Avenue South + West Covina, CA 91790 + USA + + Phone: +1 818 919 3600 + Fax: +1 818 919 3614 + EMail: ned@innosoft.com + + + Nathaniel S. Borenstein + First Virtual Holdings + 25 Washington Avenue + Morristown, NJ 07960 + USA + + Phone: +1 201 540 8967 + Fax: +1 201 993 3032 + EMail: nsb@nsb.fv.com + + + MIME is a result of the work of the Internet Engineering Task Force + Working Group on RFC 822 Extensions. The chairman of that group, + Greg Vaudreuil, may be reached at: + + Gregory M. Vaudreuil + Octel Network Services + 17080 Dallas Parkway + Dallas, TX 75248-1905 + USA + + EMail: Greg.Vaudreuil@Octel.Com + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 28] + +RFC 2045 Internet Message Bodies November 1996 + + +Appendix A -- Collected Grammar + + This appendix contains the complete BNF grammar for all the syntax + specified by this document. + + By itself, however, this grammar is incomplete. It refers by name to + several syntax rules that are defined by RFC 822. Rather than + reproduce those definitions here, and risk unintentional differences + between the two, this document simply refers the reader to RFC 822 + for the remaining definitions. Wherever a term is undefined, it + refers to the RFC 822 definition. + + attribute := token + ; Matching of attributes + ; is ALWAYS case-insensitive. + + composite-type := "message" / "multipart" / extension-token + + content := "Content-Type" ":" type "/" subtype + *(";" parameter) + ; Matching of media type and subtype + ; is ALWAYS case-insensitive. + + description := "Content-Description" ":" *text + + discrete-type := "text" / "image" / "audio" / "video" / + "application" / extension-token + + encoding := "Content-Transfer-Encoding" ":" mechanism + + entity-headers := [ content CRLF ] + [ encoding CRLF ] + [ id CRLF ] + [ description CRLF ] + *( MIME-extension-field CRLF ) + + extension-token := ietf-token / x-token + + hex-octet := "=" 2(DIGIT / "A" / "B" / "C" / "D" / "E" / "F") + ; Octet must be used for characters > 127, =, + ; SPACEs or TABs at the ends of lines, and is + ; recommended for any character not listed in + ; RFC 2049 as "mail-safe". + + iana-token := + + + + +Freed & Borenstein Standards Track [Page 29] + +RFC 2045 Internet Message Bodies November 1996 + + + ietf-token := + + id := "Content-ID" ":" msg-id + + mechanism := "7bit" / "8bit" / "binary" / + "quoted-printable" / "base64" / + ietf-token / x-token + + MIME-extension-field := + + MIME-message-headers := entity-headers + fields + version CRLF + ; The ordering of the header + ; fields implied by this BNF + ; definition should be ignored. + + MIME-part-headers := entity-headers + [fields] + ; Any field not beginning with + ; "content-" can have no defined + ; meaning and may be ignored. + ; The ordering of the header + ; fields implied by this BNF + ; definition should be ignored. + + parameter := attribute "=" value + + ptext := hex-octet / safe-char + + qp-line := *(qp-segment transport-padding CRLF) + qp-part transport-padding + + qp-part := qp-section + ; Maximum length of 76 characters + + qp-section := [*(ptext / SPACE / TAB) ptext] + + qp-segment := qp-section *(SPACE / TAB) "=" + ; Maximum length of 76 characters + + quoted-printable := qp-line *(CRLF qp-line) + + + + + +Freed & Borenstein Standards Track [Page 30] + +RFC 2045 Internet Message Bodies November 1996 + + + safe-char := + ; Characters not listed as "mail-safe" in + ; RFC 2049 are also not recommended. + + subtype := extension-token / iana-token + + token := 1* + + transport-padding := *LWSP-char + ; Composers MUST NOT generate + ; non-zero length transport + ; padding, but receivers MUST + ; be able to handle padding + ; added by message transports. + + tspecials := "(" / ")" / "<" / ">" / "@" / + "," / ";" / ":" / "\" / <"> + "/" / "[" / "]" / "?" / "=" + ; Must be in quoted-string, + ; to use within parameter values + + type := discrete-type / composite-type + + value := token / quoted-string + + version := "MIME-Version" ":" 1*DIGIT "." 1*DIGIT + + x-token := + + + + + + + + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 31] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2046.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2046.txt new file mode 100644 index 0000000..84d90c1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2046.txt @@ -0,0 +1,2467 @@ + + + + + + +Network Working Group N. Freed +Request for Comments: 2046 Innosoft +Obsoletes: 1521, 1522, 1590 N. Borenstein +Category: Standards Track First Virtual + November 1996 + + + Multipurpose Internet Mail Extensions + (MIME) Part Two: + Media Types + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + STD 11, RFC 822 defines a message representation protocol specifying + considerable detail about US-ASCII message headers, but which leaves + the message content, or message body, as flat US-ASCII text. This + set of documents, collectively called the Multipurpose Internet Mail + Extensions, or MIME, redefines the format of messages to allow for + + (1) textual message bodies in character sets other than + US-ASCII, + + (2) an extensible set of different formats for non-textual + message bodies, + + (3) multi-part message bodies, and + + (4) textual header information in character sets other than + US-ASCII. + + These documents are based on earlier work documented in RFC 934, STD + 11, and RFC 1049, but extends and revises them. Because RFC 822 said + so little about message bodies, these documents are largely + orthogonal to (rather than a revision of) RFC 822. + + The initial document in this set, RFC 2045, specifies the various + headers used to describe the structure of MIME messages. This second + document defines the general structure of the MIME media typing + system and defines an initial set of media types. The third document, + RFC 2047, describes extensions to RFC 822 to allow non-US-ASCII text + + + +Freed & Borenstein Standards Track [Page 1] + +RFC 2046 Media Types November 1996 + + + data in Internet mail header fields. The fourth document, RFC 2048, + specifies various IANA registration procedures for MIME-related + facilities. The fifth and final document, RFC 2049, describes MIME + conformance criteria as well as providing some illustrative examples + of MIME message formats, acknowledgements, and the bibliography. + + These documents are revisions of RFCs 1521 and 1522, which themselves + were revisions of RFCs 1341 and 1342. An appendix in RFC 2049 + describes differences and changes from previous versions. + +Table of Contents + + 1. Introduction ......................................... 3 + 2. Definition of a Top-Level Media Type ................. 4 + 3. Overview Of The Initial Top-Level Media Types ........ 4 + 4. Discrete Media Type Values ........................... 6 + 4.1 Text Media Type ..................................... 6 + 4.1.1 Representation of Line Breaks ..................... 7 + 4.1.2 Charset Parameter ................................. 7 + 4.1.3 Plain Subtype ..................................... 11 + 4.1.4 Unrecognized Subtypes ............................. 11 + 4.2 Image Media Type .................................... 11 + 4.3 Audio Media Type .................................... 11 + 4.4 Video Media Type .................................... 12 + 4.5 Application Media Type .............................. 12 + 4.5.1 Octet-Stream Subtype .............................. 13 + 4.5.2 PostScript Subtype ................................ 14 + 4.5.3 Other Application Subtypes ........................ 17 + 5. Composite Media Type Values .......................... 17 + 5.1 Multipart Media Type ................................ 17 + 5.1.1 Common Syntax ..................................... 19 + 5.1.2 Handling Nested Messages and Multiparts ........... 24 + 5.1.3 Mixed Subtype ..................................... 24 + 5.1.4 Alternative Subtype ............................... 24 + 5.1.5 Digest Subtype .................................... 26 + 5.1.6 Parallel Subtype .................................. 27 + 5.1.7 Other Multipart Subtypes .......................... 28 + 5.2 Message Media Type .................................. 28 + 5.2.1 RFC822 Subtype .................................... 28 + 5.2.2 Partial Subtype ................................... 29 + 5.2.2.1 Message Fragmentation and Reassembly ............ 30 + 5.2.2.2 Fragmentation and Reassembly Example ............ 31 + 5.2.3 External-Body Subtype ............................. 33 + 5.2.4 Other Message Subtypes ............................ 40 + 6. Experimental Media Type Values ....................... 40 + 7. Summary .............................................. 41 + 8. Security Considerations .............................. 41 + 9. Authors' Addresses ................................... 42 + + + +Freed & Borenstein Standards Track [Page 2] + +RFC 2046 Media Types November 1996 + + + A. Collected Grammar .................................... 43 + +1. Introduction + + The first document in this set, RFC 2045, defines a number of header + fields, including Content-Type. The Content-Type field is used to + specify the nature of the data in the body of a MIME entity, by + giving media type and subtype identifiers, and by providing auxiliary + information that may be required for certain media types. After the + type and subtype names, the remainder of the header field is simply a + set of parameters, specified in an attribute/value notation. The + ordering of parameters is not significant. + + In general, the top-level media type is used to declare the general + type of data, while the subtype specifies a specific format for that + type of data. Thus, a media type of "image/xyz" is enough to tell a + user agent that the data is an image, even if the user agent has no + knowledge of the specific image format "xyz". Such information can + be used, for example, to decide whether or not to show a user the raw + data from an unrecognized subtype -- such an action might be + reasonable for unrecognized subtypes of "text", but not for + unrecognized subtypes of "image" or "audio". For this reason, + registered subtypes of "text", "image", "audio", and "video" should + not contain embedded information that is really of a different type. + Such compound formats should be represented using the "multipart" or + "application" types. + + Parameters are modifiers of the media subtype, and as such do not + fundamentally affect the nature of the content. The set of + meaningful parameters depends on the media type and subtype. Most + parameters are associated with a single specific subtype. However, a + given top-level media type may define parameters which are applicable + to any subtype of that type. Parameters may be required by their + defining media type or subtype or they may be optional. MIME + implementations must also ignore any parameters whose names they do + not recognize. + + MIME's Content-Type header field and media type mechanism has been + carefully designed to be extensible, and it is expected that the set + of media type/subtype pairs and their associated parameters will grow + significantly over time. Several other MIME facilities, such as + transfer encodings and "message/external-body" access types, are + likely to have new values defined over time. In order to ensure that + the set of such values is developed in an orderly, well-specified, + and public manner, MIME sets up a registration process which uses the + Internet Assigned Numbers Authority (IANA) as a central registry for + MIME's various areas of extensibility. The registration process for + these areas is described in a companion document, RFC 2048. + + + +Freed & Borenstein Standards Track [Page 3] + +RFC 2046 Media Types November 1996 + + + The initial seven standard top-level media type are defined and + described in the remainder of this document. + +2. Definition of a Top-Level Media Type + + The definition of a top-level media type consists of: + + (1) a name and a description of the type, including + criteria for whether a particular type would qualify + under that type, + + (2) the names and definitions of parameters, if any, which + are defined for all subtypes of that type (including + whether such parameters are required or optional), + + (3) how a user agent and/or gateway should handle unknown + subtypes of this type, + + (4) general considerations on gatewaying entities of this + top-level type, if any, and + + (5) any restrictions on content-transfer-encodings for + entities of this top-level type. + +3. Overview Of The Initial Top-Level Media Types + + The five discrete top-level media types are: + + (1) text -- textual information. The subtype "plain" in + particular indicates plain text containing no + formatting commands or directives of any sort. Plain + text is intended to be displayed "as-is". No special + software is required to get the full meaning of the + text, aside from support for the indicated character + set. Other subtypes are to be used for enriched text in + forms where application software may enhance the + appearance of the text, but such software must not be + required in order to get the general idea of the + content. Possible subtypes of "text" thus include any + word processor format that can be read without + resorting to software that understands the format. In + particular, formats that employ embeddded binary + formatting information are not considered directly + readable. A very simple and portable subtype, + "richtext", was defined in RFC 1341, with a further + revision in RFC 1896 under the name "enriched". + + + + + +Freed & Borenstein Standards Track [Page 4] + +RFC 2046 Media Types November 1996 + + + (2) image -- image data. "Image" requires a display device + (such as a graphical display, a graphics printer, or a + FAX machine) to view the information. An initial + subtype is defined for the widely-used image format + JPEG. . subtypes are defined for two widely-used image + formats, jpeg and gif. + + (3) audio -- audio data. "Audio" requires an audio output + device (such as a speaker or a telephone) to "display" + the contents. An initial subtype "basic" is defined in + this document. + + (4) video -- video data. "Video" requires the capability + to display moving images, typically including + specialized hardware and software. An initial subtype + "mpeg" is defined in this document. + + (5) application -- some other kind of data, typically + either uninterpreted binary data or information to be + processed by an application. The subtype "octet- + stream" is to be used in the case of uninterpreted + binary data, in which case the simplest recommended + action is to offer to write the information into a file + for the user. The "PostScript" subtype is also defined + for the transport of PostScript material. Other + expected uses for "application" include spreadsheets, + data for mail-based scheduling systems, and languages + for "active" (computational) messaging, and word + processing formats that are not directly readable. + Note that security considerations may exist for some + types of application data, most notably + "application/PostScript" and any form of active + messaging. These issues are discussed later in this + document. + + The two composite top-level media types are: + + (1) multipart -- data consisting of multiple entities of + independent data types. Four subtypes are initially + defined, including the basic "mixed" subtype specifying + a generic mixed set of parts, "alternative" for + representing the same data in multiple formats, + "parallel" for parts intended to be viewed + simultaneously, and "digest" for multipart entities in + which each part has a default type of "message/rfc822". + + + + + + +Freed & Borenstein Standards Track [Page 5] + +RFC 2046 Media Types November 1996 + + + (2) message -- an encapsulated message. A body of media + type "message" is itself all or a portion of some kind + of message object. Such objects may or may not in turn + contain other entities. The "rfc822" subtype is used + when the encapsulated content is itself an RFC 822 + message. The "partial" subtype is defined for partial + RFC 822 messages, to permit the fragmented transmission + of bodies that are thought to be too large to be passed + through transport facilities in one piece. Another + subtype, "external-body", is defined for specifying + large bodies by reference to an external data source. + + It should be noted that the list of media type values given here may + be augmented in time, via the mechanisms described above, and that + the set of subtypes is expected to grow substantially. + +4. Discrete Media Type Values + + Five of the seven initial media type values refer to discrete bodies. + The content of these types must be handled by non-MIME mechanisms; + they are opaque to MIME processors. + +4.1. Text Media Type + + The "text" media type is intended for sending material which is + principally textual in form. A "charset" parameter may be used to + indicate the character set of the body text for "text" subtypes, + notably including the subtype "text/plain", which is a generic + subtype for plain text. Plain text does not provide for or allow + formatting commands, font attribute specifications, processing + instructions, interpretation directives, or content markup. Plain + text is seen simply as a linear sequence of characters, possibly + interrupted by line breaks or page breaks. Plain text may allow the + stacking of several characters in the same position in the text. + Plain text in scripts like Arabic and Hebrew may also include + facilitites that allow the arbitrary mixing of text segments with + opposite writing directions. + + Beyond plain text, there are many formats for representing what might + be known as "rich text". An interesting characteristic of many such + representations is that they are to some extent readable even without + the software that interprets them. It is useful, then, to + distinguish them, at the highest level, from such unreadable data as + images, audio, or text represented in an unreadable form. In the + absence of appropriate interpretation software, it is reasonable to + show subtypes of "text" to the user, while it is not reasonable to do + so with most nontextual data. Such formatted textual data should be + represented using subtypes of "text". + + + +Freed & Borenstein Standards Track [Page 6] + +RFC 2046 Media Types November 1996 + + +4.1.1. Representation of Line Breaks + + The canonical form of any MIME "text" subtype MUST always represent a + line break as a CRLF sequence. Similarly, any occurrence of CRLF in + MIME "text" MUST represent a line break. Use of CR and LF outside of + line break sequences is also forbidden. + + This rule applies regardless of format or character set or sets + involved. + + NOTE: The proper interpretation of line breaks when a body is + displayed depends on the media type. In particular, while it is + appropriate to treat a line break as a transition to a new line when + displaying a "text/plain" body, this treatment is actually incorrect + for other subtypes of "text" like "text/enriched" [RFC-1896]. + Similarly, whether or not line breaks should be added during display + operations is also a function of the media type. It should not be + necessary to add any line breaks to display "text/plain" correctly, + whereas proper display of "text/enriched" requires the appropriate + addition of line breaks. + + NOTE: Some protocols defines a maximum line length. E.g. SMTP [RFC- + 821] allows a maximum of 998 octets before the next CRLF sequence. + To be transported by such protocols, data which includes too long + segments without CRLF sequences must be encoded with a suitable + content-transfer-encoding. + +4.1.2. Charset Parameter + + A critical parameter that may be specified in the Content-Type field + for "text/plain" data is the character set. This is specified with a + "charset" parameter, as in: + + Content-type: text/plain; charset=iso-8859-1 + + Unlike some other parameter values, the values of the charset + parameter are NOT case sensitive. The default character set, which + must be assumed in the absence of a charset parameter, is US-ASCII. + + The specification for any future subtypes of "text" must specify + whether or not they will also utilize a "charset" parameter, and may + possibly restrict its values as well. For other subtypes of "text" + than "text/plain", the semantics of the "charset" parameter should be + defined to be identical to those specified here for "text/plain", + i.e., the body consists entirely of characters in the given charset. + In particular, definers of future "text" subtypes should pay close + attention to the implications of multioctet character sets for their + subtype definitions. + + + +Freed & Borenstein Standards Track [Page 7] + +RFC 2046 Media Types November 1996 + + + The charset parameter for subtypes of "text" gives a name of a + character set, as "character set" is defined in RFC 2045. The rules + regarding line breaks detailed in the previous section must also be + observed -- a character set whose definition does not conform to + these rules cannot be used in a MIME "text" subtype. + + An initial list of predefined character set names can be found at the + end of this section. Additional character sets may be registered + with IANA. + + Other media types than subtypes of "text" might choose to employ the + charset parameter as defined here, but with the CRLF/line break + restriction removed. Therefore, all character sets that conform to + the general definition of "character set" in RFC 2045 can be + registered for MIME use. + + Note that if the specified character set includes 8-bit characters + and such characters are used in the body, a Content-Transfer-Encoding + header field and a corresponding encoding on the data are required in + order to transmit the body via some mail transfer protocols, such as + SMTP [RFC-821]. + + The default character set, US-ASCII, has been the subject of some + confusion and ambiguity in the past. Not only were there some + ambiguities in the definition, there have been wide variations in + practice. In order to eliminate such ambiguity and variations in the + future, it is strongly recommended that new user agents explicitly + specify a character set as a media type parameter in the Content-Type + header field. "US-ASCII" does not indicate an arbitrary 7-bit + character set, but specifies that all octets in the body must be + interpreted as characters according to the US-ASCII character set. + National and application-oriented versions of ISO 646 [ISO-646] are + usually NOT identical to US-ASCII, and in that case their use in + Internet mail is explicitly discouraged. The omission of the ISO 646 + character set from this document is deliberate in this regard. The + character set name of "US-ASCII" explicitly refers to the character + set defined in ANSI X3.4-1986 [US- ASCII]. The new international + reference version (IRV) of the 1991 edition of ISO 646 is identical + to US-ASCII. The character set name "ASCII" is reserved and must not + be used for any purpose. + + NOTE: RFC 821 explicitly specifies "ASCII", and references an earlier + version of the American Standard. Insofar as one of the purposes of + specifying a media type and character set is to permit the receiver + to unambiguously determine how the sender intended the coded message + to be interpreted, assuming anything other than "strict ASCII" as the + default would risk unintentional and incompatible changes to the + semantics of messages now being transmitted. This also implies that + + + +Freed & Borenstein Standards Track [Page 8] + +RFC 2046 Media Types November 1996 + + + messages containing characters coded according to other versions of + ISO 646 than US-ASCII and the 1991 IRV, or using code-switching + procedures (e.g., those of ISO 2022), as well as 8bit or multiple + octet character encodings MUST use an appropriate character set + specification to be consistent with MIME. + + The complete US-ASCII character set is listed in ANSI X3.4- 1986. + Note that the control characters including DEL (0-31, 127) have no + defined meaning in apart from the combination CRLF (US-ASCII values + 13 and 10) indicating a new line. Two of the characters have de + facto meanings in wide use: FF (12) often means "start subsequent + text on the beginning of a new page"; and TAB or HT (9) often (though + not always) means "move the cursor to the next available column after + the current position where the column number is a multiple of 8 + (counting the first column as column 0)." Aside from these + conventions, any use of the control characters or DEL in a body must + either occur + + (1) because a subtype of text other than "plain" + specifically assigns some additional meaning, or + + (2) within the context of a private agreement between the + sender and recipient. Such private agreements are + discouraged and should be replaced by the other + capabilities of this document. + + NOTE: An enormous proliferation of character sets exist beyond US- + ASCII. A large number of partially or totally overlapping character + sets is NOT a good thing. A SINGLE character set that can be used + universally for representing all of the world's languages in Internet + mail would be preferrable. Unfortunately, existing practice in + several communities seems to point to the continued use of multiple + character sets in the near future. A small number of standard + character sets are, therefore, defined for Internet use in this + document. + + The defined charset values are: + + (1) US-ASCII -- as defined in ANSI X3.4-1986 [US-ASCII]. + + (2) ISO-8859-X -- where "X" is to be replaced, as + necessary, for the parts of ISO-8859 [ISO-8859]. Note + that the ISO 646 character sets have deliberately been + omitted in favor of their 8859 replacements, which are + the designated character sets for Internet mail. As of + the publication of this document, the legitimate values + for "X" are the digits 1 through 10. + + + + +Freed & Borenstein Standards Track [Page 9] + +RFC 2046 Media Types November 1996 + + + Characters in the range 128-159 has no assigned meaning in ISO-8859- + X. Characters with values below 128 in ISO-8859-X have the same + assigned meaning as they do in US-ASCII. + + Part 6 of ISO 8859 (Latin/Arabic alphabet) and part 8 (Latin/Hebrew + alphabet) includes both characters for which the normal writing + direction is right to left and characters for which it is left to + right, but do not define a canonical ordering method for representing + bi-directional text. The charset values "ISO-8859-6" and "ISO-8859- + 8", however, specify that the visual method is used [RFC-1556]. + + All of these character sets are used as pure 7bit or 8bit sets + without any shift or escape functions. The meaning of shift and + escape sequences in these character sets is not defined. + + The character sets specified above are the ones that were relatively + uncontroversial during the drafting of MIME. This document does not + endorse the use of any particular character set other than US-ASCII, + and recognizes that the future evolution of world character sets + remains unclear. + + Note that the character set used, if anything other than US- ASCII, + must always be explicitly specified in the Content-Type field. + + No character set name other than those defined above may be used in + Internet mail without the publication of a formal specification and + its registration with IANA, or by private agreement, in which case + the character set name must begin with "X-". + + Implementors are discouraged from defining new character sets unless + absolutely necessary. + + The "charset" parameter has been defined primarily for the purpose of + textual data, and is described in this section for that reason. + However, it is conceivable that non-textual data might also wish to + specify a charset value for some purpose, in which case the same + syntax and values should be used. + + In general, composition software should always use the "lowest common + denominator" character set possible. For example, if a body contains + only US-ASCII characters, it SHOULD be marked as being in the US- + ASCII character set, not ISO-8859-1, which, like all the ISO-8859 + family of character sets, is a superset of US-ASCII. More generally, + if a widely-used character set is a subset of another character set, + and a body contains only characters in the widely-used subset, it + should be labelled as being in that subset. This will increase the + chances that the recipient will be able to view the resulting entity + correctly. + + + +Freed & Borenstein Standards Track [Page 10] + +RFC 2046 Media Types November 1996 + + +4.1.3. Plain Subtype + + The simplest and most important subtype of "text" is "plain". This + indicates plain text that does not contain any formatting commands or + directives. Plain text is intended to be displayed "as-is", that is, + no interpretation of embedded formatting commands, font attribute + specifications, processing instructions, interpretation directives, + or content markup should be necessary for proper display. The + default media type of "text/plain; charset=us-ascii" for Internet + mail describes existing Internet practice. That is, it is the type + of body defined by RFC 822. + + No other "text" subtype is defined by this document. + +4.1.4. Unrecognized Subtypes + + Unrecognized subtypes of "text" should be treated as subtype "plain" + as long as the MIME implementation knows how to handle the charset. + Unrecognized subtypes which also specify an unrecognized charset + should be treated as "application/octet- stream". + +4.2. Image Media Type + + A media type of "image" indicates that the body contains an image. + The subtype names the specific image format. These names are not + case sensitive. An initial subtype is "jpeg" for the JPEG format + using JFIF encoding [JPEG]. + + The list of "image" subtypes given here is neither exclusive nor + exhaustive, and is expected to grow as more types are registered with + IANA, as described in RFC 2048. + + Unrecognized subtypes of "image" should at a miniumum be treated as + "application/octet-stream". Implementations may optionally elect to + pass subtypes of "image" that they do not specifically recognize to a + secure and robust general-purpose image viewing application, if such + an application is available. + + NOTE: Using of a generic-purpose image viewing application this way + inherits the security problems of the most dangerous type supported + by the application. + +4.3. Audio Media Type + + A media type of "audio" indicates that the body contains audio data. + Although there is not yet a consensus on an "ideal" audio format for + use with computers, there is a pressing need for a format capable of + providing interoperable behavior. + + + +Freed & Borenstein Standards Track [Page 11] + +RFC 2046 Media Types November 1996 + + + The initial subtype of "basic" is specified to meet this requirement + by providing an absolutely minimal lowest common denominator audio + format. It is expected that richer formats for higher quality and/or + lower bandwidth audio will be defined by a later document. + + The content of the "audio/basic" subtype is single channel audio + encoded using 8bit ISDN mu-law [PCM] at a sample rate of 8000 Hz. + + Unrecognized subtypes of "audio" should at a miniumum be treated as + "application/octet-stream". Implementations may optionally elect to + pass subtypes of "audio" that they do not specifically recognize to a + robust general-purpose audio playing application, if such an + application is available. + +4.4. Video Media Type + + A media type of "video" indicates that the body contains a time- + varying-picture image, possibly with color and coordinated sound. + The term 'video' is used in its most generic sense, rather than with + reference to any particular technology or format, and is not meant to + preclude subtypes such as animated drawings encoded compactly. The + subtype "mpeg" refers to video coded according to the MPEG standard + [MPEG]. + + Note that although in general this document strongly discourages the + mixing of multiple media in a single body, it is recognized that many + so-called video formats include a representation for synchronized + audio, and this is explicitly permitted for subtypes of "video". + + Unrecognized subtypes of "video" should at a minumum be treated as + "application/octet-stream". Implementations may optionally elect to + pass subtypes of "video" that they do not specifically recognize to a + robust general-purpose video display application, if such an + application is available. + +4.5. Application Media Type + + The "application" media type is to be used for discrete data which do + not fit in any of the other categories, and particularly for data to + be processed by some type of application program. This is + information which must be processed by an application before it is + viewable or usable by a user. Expected uses for the "application" + media type include file transfer, spreadsheets, data for mail-based + scheduling systems, and languages for "active" (computational) + material. (The latter, in particular, can pose security problems + which must be understood by implementors, and are considered in + detail in the discussion of the "application/PostScript" media type.) + + + + +Freed & Borenstein Standards Track [Page 12] + +RFC 2046 Media Types November 1996 + + + For example, a meeting scheduler might define a standard + representation for information about proposed meeting dates. An + intelligent user agent would use this information to conduct a dialog + with the user, and might then send additional material based on that + dialog. More generally, there have been several "active" messaging + languages developed in which programs in a suitably specialized + language are transported to a remote location and automatically run + in the recipient's environment. + + Such applications may be defined as subtypes of the "application" + media type. This document defines two subtypes: + + octet-stream, and PostScript. + + The subtype of "application" will often be either the name or include + part of the name of the application for which the data are intended. + This does not mean, however, that any application program name may be + used freely as a subtype of "application". + +4.5.1. Octet-Stream Subtype + + The "octet-stream" subtype is used to indicate that a body contains + arbitrary binary data. The set of currently defined parameters is: + + (1) TYPE -- the general type or category of binary data. + This is intended as information for the human recipient + rather than for any automatic processing. + + (2) PADDING -- the number of bits of padding that were + appended to the bit-stream comprising the actual + contents to produce the enclosed 8bit byte-oriented + data. This is useful for enclosing a bit-stream in a + body when the total number of bits is not a multiple of + 8. + + Both of these parameters are optional. + + An additional parameter, "CONVERSIONS", was defined in RFC 1341 but + has since been removed. RFC 1341 also defined the use of a "NAME" + parameter which gave a suggested file name to be used if the data + were to be written to a file. This has been deprecated in + anticipation of a separate Content-Disposition header field, to be + defined in a subsequent RFC. + + The recommended action for an implementation that receives an + "application/octet-stream" entity is to simply offer to put the data + in a file, with any Content-Transfer-Encoding undone, or perhaps to + use it as input to a user-specified process. + + + +Freed & Borenstein Standards Track [Page 13] + +RFC 2046 Media Types November 1996 + + + To reduce the danger of transmitting rogue programs, it is strongly + recommended that implementations NOT implement a path-search + mechanism whereby an arbitrary program named in the Content-Type + parameter (e.g., an "interpreter=" parameter) is found and executed + using the message body as input. + +4.5.2. PostScript Subtype + + A media type of "application/postscript" indicates a PostScript + program. Currently two variants of the PostScript language are + allowed; the original level 1 variant is described in [POSTSCRIPT] + and the more recent level 2 variant is described in [POSTSCRIPT2]. + + PostScript is a registered trademark of Adobe Systems, Inc. Use of + the MIME media type "application/postscript" implies recognition of + that trademark and all the rights it entails. + + The PostScript language definition provides facilities for internal + labelling of the specific language features a given program uses. + This labelling, called the PostScript document structuring + conventions, or DSC, is very general and provides substantially more + information than just the language level. The use of document + structuring conventions, while not required, is strongly recommended + as an aid to interoperability. Documents which lack proper + structuring conventions cannot be tested to see whether or not they + will work in a given environment. As such, some systems may assume + the worst and refuse to process unstructured documents. + + The execution of general-purpose PostScript interpreters entails + serious security risks, and implementors are discouraged from simply + sending PostScript bodies to "off- the-shelf" interpreters. While it + is usually safe to send PostScript to a printer, where the potential + for harm is greatly constrained by typical printer environments, + implementors should consider all of the following before they add + interactive display of PostScript bodies to their MIME readers. + + The remainder of this section outlines some, though probably not all, + of the possible problems with the transport of PostScript entities. + + (1) Dangerous operations in the PostScript language + include, but may not be limited to, the PostScript + operators "deletefile", "renamefile", "filenameforall", + and "file". "File" is only dangerous when applied to + something other than standard input or output. + Implementations may also define additional nonstandard + file operators; these may also pose a threat to + security. "Filenameforall", the wildcard file search + operator, may appear at first glance to be harmless. + + + +Freed & Borenstein Standards Track [Page 14] + +RFC 2046 Media Types November 1996 + + + Note, however, that this operator has the potential to + reveal information about what files the recipient has + access to, and this information may itself be + sensitive. Message senders should avoid the use of + potentially dangerous file operators, since these + operators are quite likely to be unavailable in secure + PostScript implementations. Message receiving and + displaying software should either completely disable + all potentially dangerous file operators or take + special care not to delegate any special authority to + their operation. These operators should be viewed as + being done by an outside agency when interpreting + PostScript documents. Such disabling and/or checking + should be done completely outside of the reach of the + PostScript language itself; care should be taken to + insure that no method exists for re-enabling full- + function versions of these operators. + + (2) The PostScript language provides facilities for exiting + the normal interpreter, or server, loop. Changes made + in this "outer" environment are customarily retained + across documents, and may in some cases be retained + semipermanently in nonvolatile memory. The operators + associated with exiting the interpreter loop have the + potential to interfere with subsequent document + processing. As such, their unrestrained use + constitutes a threat of service denial. PostScript + operators that exit the interpreter loop include, but + may not be limited to, the exitserver and startjob + operators. Message sending software should not + generate PostScript that depends on exiting the + interpreter loop to operate, since the ability to exit + will probably be unavailable in secure PostScript + implementations. Message receiving and displaying + software should completely disable the ability to make + retained changes to the PostScript environment by + eliminating or disabling the "startjob" and + "exitserver" operations. If these operations cannot be + eliminated or completely disabled the password + associated with them should at least be set to a hard- + to-guess value. + + (3) PostScript provides operators for setting system-wide + and device-specific parameters. These parameter + settings may be retained across jobs and may + potentially pose a threat to the correct operation of + the interpreter. The PostScript operators that set + system and device parameters include, but may not be + + + +Freed & Borenstein Standards Track [Page 15] + +RFC 2046 Media Types November 1996 + + + limited to, the "setsystemparams" and "setdevparams" + operators. Message sending software should not + generate PostScript that depends on the setting of + system or device parameters to operate correctly. The + ability to set these parameters will probably be + unavailable in secure PostScript implementations. + Message receiving and displaying software should + disable the ability to change system and device + parameters. If these operators cannot be completely + disabled the password associated with them should at + least be set to a hard-to-guess value. + + (4) Some PostScript implementations provide nonstandard + facilities for the direct loading and execution of + machine code. Such facilities are quite obviously open + to substantial abuse. Message sending software should + not make use of such features. Besides being totally + hardware-specific, they are also likely to be + unavailable in secure implementations of PostScript. + Message receiving and displaying software should not + allow such operators to be used if they exist. + + (5) PostScript is an extensible language, and many, if not + most, implementations of it provide a number of their + own extensions. This document does not deal with such + extensions explicitly since they constitute an unknown + factor. Message sending software should not make use + of nonstandard extensions; they are likely to be + missing from some implementations. Message receiving + and displaying software should make sure that any + nonstandard PostScript operators are secure and don't + present any kind of threat. + + (6) It is possible to write PostScript that consumes huge + amounts of various system resources. It is also + possible to write PostScript programs that loop + indefinitely. Both types of programs have the + potential to cause damage if sent to unsuspecting + recipients. Message-sending software should avoid the + construction and dissemination of such programs, which + is antisocial. Message receiving and displaying + software should provide appropriate mechanisms to abort + processing after a reasonable amount of time has + elapsed. In addition, PostScript interpreters should be + limited to the consumption of only a reasonable amount + of any given system resource. + + + + + +Freed & Borenstein Standards Track [Page 16] + +RFC 2046 Media Types November 1996 + + + (7) It is possible to include raw binary information inside + PostScript in various forms. This is not recommended + for use in Internet mail, both because it is not + supported by all PostScript interpreters and because it + significantly complicates the use of a MIME Content- + Transfer-Encoding. (Without such binary, PostScript + may typically be viewed as line-oriented data. The + treatment of CRLF sequences becomes extremely + problematic if binary and line-oriented data are mixed + in a single Postscript data stream.) + + (8) Finally, bugs may exist in some PostScript interpreters + which could possibly be exploited to gain unauthorized + access to a recipient's system. Apart from noting this + possibility, there is no specific action to take to + prevent this, apart from the timely correction of such + bugs if any are found. + +4.5.3. Other Application Subtypes + + It is expected that many other subtypes of "application" will be + defined in the future. MIME implementations must at a minimum treat + any unrecognized subtypes as being equivalent to "application/octet- + stream". + +5. Composite Media Type Values + + The remaining two of the seven initial Content-Type values refer to + composite entities. Composite entities are handled using MIME + mechanisms -- a MIME processor typically handles the body directly. + +5.1. Multipart Media Type + + In the case of multipart entities, in which one or more different + sets of data are combined in a single body, a "multipart" media type + field must appear in the entity's header. The body must then contain + one or more body parts, each preceded by a boundary delimiter line, + and the last one followed by a closing boundary delimiter line. + After its boundary delimiter line, each body part then consists of a + header area, a blank line, and a body area. Thus a body part is + similar to an RFC 822 message in syntax, but different in meaning. + + A body part is an entity and hence is NOT to be interpreted as + actually being an RFC 822 message. To begin with, NO header fields + are actually required in body parts. A body part that starts with a + blank line, therefore, is allowed and is a body part for which all + default values are to be assumed. In such a case, the absence of a + Content-Type header usually indicates that the corresponding body has + + + +Freed & Borenstein Standards Track [Page 17] + +RFC 2046 Media Types November 1996 + + + a content-type of "text/plain; charset=US-ASCII". + + The only header fields that have defined meaning for body parts are + those the names of which begin with "Content-". All other header + fields may be ignored in body parts. Although they should generally + be retained if at all possible, they may be discarded by gateways if + necessary. Such other fields are permitted to appear in body parts + but must not be depended on. "X-" fields may be created for + experimental or private purposes, with the recognition that the + information they contain may be lost at some gateways. + + NOTE: The distinction between an RFC 822 message and a body part is + subtle, but important. A gateway between Internet and X.400 mail, + for example, must be able to tell the difference between a body part + that contains an image and a body part that contains an encapsulated + message, the body of which is a JPEG image. In order to represent + the latter, the body part must have "Content-Type: message/rfc822", + and its body (after the blank line) must be the encapsulated message, + with its own "Content-Type: image/jpeg" header field. The use of + similar syntax facilitates the conversion of messages to body parts, + and vice versa, but the distinction between the two must be + understood by implementors. (For the special case in which parts + actually are messages, a "digest" subtype is also defined.) + + As stated previously, each body part is preceded by a boundary + delimiter line that contains the boundary delimiter. The boundary + delimiter MUST NOT appear inside any of the encapsulated parts, on a + line by itself or as the prefix of any line. This implies that it is + crucial that the composing agent be able to choose and specify a + unique boundary parameter value that does not contain the boundary + parameter value of an enclosing multipart as a prefix. + + All present and future subtypes of the "multipart" type must use an + identical syntax. Subtypes may differ in their semantics, and may + impose additional restrictions on syntax, but must conform to the + required syntax for the "multipart" type. This requirement ensures + that all conformant user agents will at least be able to recognize + and separate the parts of any multipart entity, even those of an + unrecognized subtype. + + As stated in the definition of the Content-Transfer-Encoding field + [RFC 2045], no encoding other than "7bit", "8bit", or "binary" is + permitted for entities of type "multipart". The "multipart" boundary + delimiters and header fields are always represented as 7bit US-ASCII + in any case (though the header fields may encode non-US-ASCII header + text as per RFC 2047) and data within the body parts can be encoded + on a part-by-part basis, with Content-Transfer-Encoding fields for + each appropriate body part. + + + +Freed & Borenstein Standards Track [Page 18] + +RFC 2046 Media Types November 1996 + + +5.1.1. Common Syntax + + This section defines a common syntax for subtypes of "multipart". + All subtypes of "multipart" must use this syntax. A simple example + of a multipart message also appears in this section. An example of a + more complex multipart message is given in RFC 2049. + + The Content-Type field for multipart entities requires one parameter, + "boundary". The boundary delimiter line is then defined as a line + consisting entirely of two hyphen characters ("-", decimal value 45) + followed by the boundary parameter value from the Content-Type header + field, optional linear whitespace, and a terminating CRLF. + + NOTE: The hyphens are for rough compatibility with the earlier RFC + 934 method of message encapsulation, and for ease of searching for + the boundaries in some implementations. However, it should be noted + that multipart messages are NOT completely compatible with RFC 934 + encapsulations; in particular, they do not obey RFC 934 quoting + conventions for embedded lines that begin with hyphens. This + mechanism was chosen over the RFC 934 mechanism because the latter + causes lines to grow with each level of quoting. The combination of + this growth with the fact that SMTP implementations sometimes wrap + long lines made the RFC 934 mechanism unsuitable for use in the event + that deeply-nested multipart structuring is ever desired. + + WARNING TO IMPLEMENTORS: The grammar for parameters on the Content- + type field is such that it is often necessary to enclose the boundary + parameter values in quotes on the Content-type line. This is not + always necessary, but never hurts. Implementors should be sure to + study the grammar carefully in order to avoid producing invalid + Content-type fields. Thus, a typical "multipart" Content-Type header + field might look like this: + + Content-Type: multipart/mixed; boundary=gc0p4Jq0M2Yt08j34c0p + + But the following is not valid: + + Content-Type: multipart/mixed; boundary=gc0pJq0M:08jU534c0p + + (because of the colon) and must instead be represented as + + Content-Type: multipart/mixed; boundary="gc0pJq0M:08jU534c0p" + + This Content-Type value indicates that the content consists of one or + more parts, each with a structure that is syntactically identical to + an RFC 822 message, except that the header area is allowed to be + completely empty, and that the parts are each preceded by the line + + + + +Freed & Borenstein Standards Track [Page 19] + +RFC 2046 Media Types November 1996 + + + --gc0pJq0M:08jU534c0p + + The boundary delimiter MUST occur at the beginning of a line, i.e., + following a CRLF, and the initial CRLF is considered to be attached + to the boundary delimiter line rather than part of the preceding + part. The boundary may be followed by zero or more characters of + linear whitespace. It is then terminated by either another CRLF and + the header fields for the next part, or by two CRLFs, in which case + there are no header fields for the next part. If no Content-Type + field is present it is assumed to be "message/rfc822" in a + "multipart/digest" and "text/plain" otherwise. + + NOTE: The CRLF preceding the boundary delimiter line is conceptually + attached to the boundary so that it is possible to have a part that + does not end with a CRLF (line break). Body parts that must be + considered to end with line breaks, therefore, must have two CRLFs + preceding the boundary delimiter line, the first of which is part of + the preceding body part, and the second of which is part of the + encapsulation boundary. + + Boundary delimiters must not appear within the encapsulated material, + and must be no longer than 70 characters, not counting the two + leading hyphens. + + The boundary delimiter line following the last body part is a + distinguished delimiter that indicates that no further body parts + will follow. Such a delimiter line is identical to the previous + delimiter lines, with the addition of two more hyphens after the + boundary parameter value. + + --gc0pJq0M:08jU534c0p-- + + NOTE TO IMPLEMENTORS: Boundary string comparisons must compare the + boundary value with the beginning of each candidate line. An exact + match of the entire candidate line is not required; it is sufficient + that the boundary appear in its entirety following the CRLF. + + There appears to be room for additional information prior to the + first boundary delimiter line and following the final boundary + delimiter line. These areas should generally be left blank, and + implementations must ignore anything that appears before the first + boundary delimiter line or after the last one. + + NOTE: These "preamble" and "epilogue" areas are generally not used + because of the lack of proper typing of these parts and the lack of + clear semantics for handling these areas at gateways, particularly + X.400 gateways. However, rather than leaving the preamble area + blank, many MIME implementations have found this to be a convenient + + + +Freed & Borenstein Standards Track [Page 20] + +RFC 2046 Media Types November 1996 + + + place to insert an explanatory note for recipients who read the + message with pre-MIME software, since such notes will be ignored by + MIME-compliant software. + + NOTE: Because boundary delimiters must not appear in the body parts + being encapsulated, a user agent must exercise care to choose a + unique boundary parameter value. The boundary parameter value in the + example above could have been the result of an algorithm designed to + produce boundary delimiters with a very low probability of already + existing in the data to be encapsulated without having to prescan the + data. Alternate algorithms might result in more "readable" boundary + delimiters for a recipient with an old user agent, but would require + more attention to the possibility that the boundary delimiter might + appear at the beginning of some line in the encapsulated part. The + simplest boundary delimiter line possible is something like "---", + with a closing boundary delimiter line of "-----". + + As a very simple example, the following multipart message has two + parts, both of them plain text, one of them explicitly typed and one + of them implicitly typed: + + From: Nathaniel Borenstein + To: Ned Freed + Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST) + Subject: Sample message + MIME-Version: 1.0 + Content-type: multipart/mixed; boundary="simple boundary" + + This is the preamble. It is to be ignored, though it + is a handy place for composition agents to include an + explanatory note to non-MIME conformant readers. + + --simple boundary + + This is implicitly typed plain US-ASCII text. + It does NOT end with a linebreak. + --simple boundary + Content-type: text/plain; charset=us-ascii + + This is explicitly typed plain US-ASCII text. + It DOES end with a linebreak. + + --simple boundary-- + + This is the epilogue. It is also to be ignored. + + + + + + +Freed & Borenstein Standards Track [Page 21] + +RFC 2046 Media Types November 1996 + + + The use of a media type of "multipart" in a body part within another + "multipart" entity is explicitly allowed. In such cases, for obvious + reasons, care must be taken to ensure that each nested "multipart" + entity uses a different boundary delimiter. See RFC 2049 for an + example of nested "multipart" entities. + + The use of the "multipart" media type with only a single body part + may be useful in certain contexts, and is explicitly permitted. + + NOTE: Experience has shown that a "multipart" media type with a + single body part is useful for sending non-text media types. It has + the advantage of providing the preamble as a place to include + decoding instructions. In addition, a number of SMTP gateways move + or remove the MIME headers, and a clever MIME decoder can take a good + guess at multipart boundaries even in the absence of the Content-Type + header and thereby successfully decode the message. + + The only mandatory global parameter for the "multipart" media type is + the boundary parameter, which consists of 1 to 70 characters from a + set of characters known to be very robust through mail gateways, and + NOT ending with white space. (If a boundary delimiter line appears to + end with white space, the white space must be presumed to have been + added by a gateway, and must be deleted.) It is formally specified + by the following BNF: + + boundary := 0*69 bcharsnospace + + bchars := bcharsnospace / " " + + bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / + "+" / "_" / "," / "-" / "." / + "/" / ":" / "=" / "?" + + Overall, the body of a "multipart" entity may be specified as + follows: + + dash-boundary := "--" boundary + ; boundary taken from the value of + ; boundary parameter of the + ; Content-Type field. + + multipart-body := [preamble CRLF] + dash-boundary transport-padding CRLF + body-part *encapsulation + close-delimiter transport-padding + [CRLF epilogue] + + + + + +Freed & Borenstein Standards Track [Page 22] + +RFC 2046 Media Types November 1996 + + + transport-padding := *LWSP-char + ; Composers MUST NOT generate + ; non-zero length transport + ; padding, but receivers MUST + ; be able to handle padding + ; added by message transports. + + encapsulation := delimiter transport-padding + CRLF body-part + + delimiter := CRLF dash-boundary + + close-delimiter := delimiter "--" + + preamble := discard-text + + epilogue := discard-text + + discard-text := *(*text CRLF) *text + ; May be ignored or discarded. + + body-part := MIME-part-headers [CRLF *OCTET] + ; Lines in a body-part must not start + ; with the specified dash-boundary and + ; the delimiter must not appear anywhere + ; in the body part. Note that the + ; semantics of a body-part differ from + ; the semantics of a message, as + ; described in the text. + + OCTET := + + IMPORTANT: The free insertion of linear-white-space and RFC 822 + comments between the elements shown in this BNF is NOT allowed since + this BNF does not specify a structured header field. + + NOTE: In certain transport enclaves, RFC 822 restrictions such as + the one that limits bodies to printable US-ASCII characters may not + be in force. (That is, the transport domains may exist that resemble + standard Internet mail transport as specified in RFC 821 and assumed + by RFC 822, but without certain restrictions.) The relaxation of + these restrictions should be construed as locally extending the + definition of bodies, for example to include octets outside of the + US-ASCII range, as long as these extensions are supported by the + transport and adequately documented in the Content- Transfer-Encoding + header field. However, in no event are headers (either message + headers or body part headers) allowed to contain anything other than + US-ASCII characters. + + + +Freed & Borenstein Standards Track [Page 23] + +RFC 2046 Media Types November 1996 + + + NOTE: Conspicuously missing from the "multipart" type is a notion of + structured, related body parts. It is recommended that those wishing + to provide more structured or integrated multipart messaging + facilities should define subtypes of multipart that are syntactically + identical but define relationships between the various parts. For + example, subtypes of multipart could be defined that include a + distinguished part which in turn is used to specify the relationships + between the other parts, probably referring to them by their + Content-ID field. Old implementations will not recognize the new + subtype if this approach is used, but will treat it as + multipart/mixed and will thus be able to show the user the parts that + are recognized. + +5.1.2. Handling Nested Messages and Multiparts + + The "message/rfc822" subtype defined in a subsequent section of this + document has no terminating condition other than running out of data. + Similarly, an improperly truncated "multipart" entity may not have + any terminating boundary marker, and can turn up operationally due to + mail system malfunctions. + + It is essential that such entities be handled correctly when they are + themselves imbedded inside of another "multipart" structure. MIME + implementations are therefore required to recognize outer level + boundary markers at ANY level of inner nesting. It is not sufficient + to only check for the next expected marker or other terminating + condition. + +5.1.3. Mixed Subtype + + The "mixed" subtype of "multipart" is intended for use when the body + parts are independent and need to be bundled in a particular order. + Any "multipart" subtypes that an implementation does not recognize + must be treated as being of subtype "mixed". + +5.1.4. Alternative Subtype + + The "multipart/alternative" type is syntactically identical to + "multipart/mixed", but the semantics are different. In particular, + each of the body parts is an "alternative" version of the same + information. + + Systems should recognize that the content of the various parts are + interchangeable. Systems should choose the "best" type based on the + local environment and references, in some cases even through user + interaction. As with "multipart/mixed", the order of body parts is + significant. In this case, the alternatives appear in an order of + increasing faithfulness to the original content. In general, the + + + +Freed & Borenstein Standards Track [Page 24] + +RFC 2046 Media Types November 1996 + + + best choice is the LAST part of a type supported by the recipient + system's local environment. + + "Multipart/alternative" may be used, for example, to send a message + in a fancy text format in such a way that it can easily be displayed + anywhere: + + From: Nathaniel Borenstein + To: Ned Freed + Date: Mon, 22 Mar 1993 09:41:09 -0800 (PST) + Subject: Formatted text mail + MIME-Version: 1.0 + Content-Type: multipart/alternative; boundary=boundary42 + + --boundary42 + Content-Type: text/plain; charset=us-ascii + + ... plain text version of message goes here ... + + --boundary42 + Content-Type: text/enriched + + ... RFC 1896 text/enriched version of same message + goes here ... + + --boundary42 + Content-Type: application/x-whatever + + ... fanciest version of same message goes here ... + + --boundary42-- + + In this example, users whose mail systems understood the + "application/x-whatever" format would see only the fancy version, + while other users would see only the enriched or plain text version, + depending on the capabilities of their system. + + In general, user agents that compose "multipart/alternative" entities + must place the body parts in increasing order of preference, that is, + with the preferred format last. For fancy text, the sending user + agent should put the plainest format first and the richest format + last. Receiving user agents should pick and display the last format + they are capable of displaying. In the case where one of the + alternatives is itself of type "multipart" and contains unrecognized + sub-parts, the user agent may choose either to show that alternative, + an earlier alternative, or both. + + + + + +Freed & Borenstein Standards Track [Page 25] + +RFC 2046 Media Types November 1996 + + + NOTE: From an implementor's perspective, it might seem more sensible + to reverse this ordering, and have the plainest alternative last. + However, placing the plainest alternative first is the friendliest + possible option when "multipart/alternative" entities are viewed + using a non-MIME-conformant viewer. While this approach does impose + some burden on conformant MIME viewers, interoperability with older + mail readers was deemed to be more important in this case. + + It may be the case that some user agents, if they can recognize more + than one of the formats, will prefer to offer the user the choice of + which format to view. This makes sense, for example, if a message + includes both a nicely- formatted image version and an easily-edited + text version. What is most critical, however, is that the user not + automatically be shown multiple versions of the same data. Either + the user should be shown the last recognized version or should be + given the choice. + + THE SEMANTICS OF CONTENT-ID IN MULTIPART/ALTERNATIVE: Each part of a + "multipart/alternative" entity represents the same data, but the + mappings between the two are not necessarily without information + loss. For example, information is lost when translating ODA to + PostScript or plain text. It is recommended that each part should + have a different Content-ID value in the case where the information + content of the two parts is not identical. And when the information + content is identical -- for example, where several parts of type + "message/external-body" specify alternate ways to access the + identical data -- the same Content-ID field value should be used, to + optimize any caching mechanisms that might be present on the + recipient's end. However, the Content-ID values used by the parts + should NOT be the same Content-ID value that describes the + "multipart/alternative" as a whole, if there is any such Content-ID + field. That is, one Content-ID value will refer to the + "multipart/alternative" entity, while one or more other Content-ID + values will refer to the parts inside it. + +5.1.5. Digest Subtype + + This document defines a "digest" subtype of the "multipart" Content- + Type. This type is syntactically identical to "multipart/mixed", but + the semantics are different. In particular, in a digest, the default + Content-Type value for a body part is changed from "text/plain" to + "message/rfc822". This is done to allow a more readable digest + format that is largely compatible (except for the quoting convention) + with RFC 934. + + Note: Though it is possible to specify a Content-Type value for a + body part in a digest which is other than "message/rfc822", such as a + "text/plain" part containing a description of the material in the + + + +Freed & Borenstein Standards Track [Page 26] + +RFC 2046 Media Types November 1996 + + + digest, actually doing so is undesireble. The "multipart/digest" + Content-Type is intended to be used to send collections of messages. + If a "text/plain" part is needed, it should be included as a seperate + part of a "multipart/mixed" message. + + A digest in this format might, then, look something like this: + + From: Moderator-Address + To: Recipient-List + Date: Mon, 22 Mar 1994 13:34:51 +0000 + Subject: Internet Digest, volume 42 + MIME-Version: 1.0 + Content-Type: multipart/mixed; + boundary="---- main boundary ----" + + ------ main boundary ---- + + ...Introductory text or table of contents... + + ------ main boundary ---- + Content-Type: multipart/digest; + boundary="---- next message ----" + + ------ next message ---- + + From: someone-else + Date: Fri, 26 Mar 1993 11:13:32 +0200 + Subject: my opinion + + ...body goes here ... + + ------ next message ---- + + From: someone-else-again + Date: Fri, 26 Mar 1993 10:07:13 -0500 + Subject: my different opinion + + ... another body goes here ... + + ------ next message ------ + + ------ main boundary ------ + +5.1.6. Parallel Subtype + + This document defines a "parallel" subtype of the "multipart" + Content-Type. This type is syntactically identical to + "multipart/mixed", but the semantics are different. In particular, + + + +Freed & Borenstein Standards Track [Page 27] + +RFC 2046 Media Types November 1996 + + + in a parallel entity, the order of body parts is not significant. + + A common presentation of this type is to display all of the parts + simultaneously on hardware and software that are capable of doing so. + However, composing agents should be aware that many mail readers will + lack this capability and will show the parts serially in any event. + +5.1.7. Other Multipart Subtypes + + Other "multipart" subtypes are expected in the future. MIME + implementations must in general treat unrecognized subtypes of + "multipart" as being equivalent to "multipart/mixed". + +5.2. Message Media Type + + It is frequently desirable, in sending mail, to encapsulate another + mail message. A special media type, "message", is defined to + facilitate this. In particular, the "rfc822" subtype of "message" is + used to encapsulate RFC 822 messages. + + NOTE: It has been suggested that subtypes of "message" might be + defined for forwarded or rejected messages. However, forwarded and + rejected messages can be handled as multipart messages in which the + first part contains any control or descriptive information, and a + second part, of type "message/rfc822", is the forwarded or rejected + message. Composing rejection and forwarding messages in this manner + will preserve the type information on the original message and allow + it to be correctly presented to the recipient, and hence is strongly + encouraged. + + Subtypes of "message" often impose restrictions on what encodings are + allowed. These restrictions are described in conjunction with each + specific subtype. + + Mail gateways, relays, and other mail handling agents are commonly + known to alter the top-level header of an RFC 822 message. In + particular, they frequently add, remove, or reorder header fields. + These operations are explicitly forbidden for the encapsulated + headers embedded in the bodies of messages of type "message." + +5.2.1. RFC822 Subtype + + A media type of "message/rfc822" indicates that the body contains an + encapsulated message, with the syntax of an RFC 822 message. + However, unlike top-level RFC 822 messages, the restriction that each + "message/rfc822" body must include a "From", "Date", and at least one + destination header is removed and replaced with the requirement that + at least one of "From", "Subject", or "Date" must be present. + + + +Freed & Borenstein Standards Track [Page 28] + +RFC 2046 Media Types November 1996 + + + It should be noted that, despite the use of the numbers "822", a + "message/rfc822" entity isn't restricted to material in strict + conformance to RFC822, nor are the semantics of "message/rfc822" + objects restricted to the semantics defined in RFC822. More + specifically, a "message/rfc822" message could well be a News article + or a MIME message. + + No encoding other than "7bit", "8bit", or "binary" is permitted for + the body of a "message/rfc822" entity. The message header fields are + always US-ASCII in any case, and data within the body can still be + encoded, in which case the Content-Transfer-Encoding header field in + the encapsulated message will reflect this. Non-US-ASCII text in the + headers of an encapsulated message can be specified using the + mechanisms described in RFC 2047. + +5.2.2. Partial Subtype + + The "partial" subtype is defined to allow large entities to be + delivered as several separate pieces of mail and automatically + reassembled by a receiving user agent. (The concept is similar to IP + fragmentation and reassembly in the basic Internet Protocols.) This + mechanism can be used when intermediate transport agents limit the + size of individual messages that can be sent. The media type + "message/partial" thus indicates that the body contains a fragment of + a larger entity. + + Because data of type "message" may never be encoded in base64 or + quoted-printable, a problem might arise if "message/partial" entities + are constructed in an environment that supports binary or 8bit + transport. The problem is that the binary data would be split into + multiple "message/partial" messages, each of them requiring binary + transport. If such messages were encountered at a gateway into a + 7bit transport environment, there would be no way to properly encode + them for the 7bit world, aside from waiting for all of the fragments, + reassembling the inner message, and then encoding the reassembled + data in base64 or quoted-printable. Since it is possible that + different fragments might go through different gateways, even this is + not an acceptable solution. For this reason, it is specified that + entities of type "message/partial" must always have a content- + transfer-encoding of 7bit (the default). In particular, even in + environments that support binary or 8bit transport, the use of a + content- transfer-encoding of "8bit" or "binary" is explicitly + prohibited for MIME entities of type "message/partial". This in turn + implies that the inner message must not use "8bit" or "binary" + encoding. + + + + + + +Freed & Borenstein Standards Track [Page 29] + +RFC 2046 Media Types November 1996 + + + Because some message transfer agents may choose to automatically + fragment large messages, and because such agents may use very + different fragmentation thresholds, it is possible that the pieces of + a partial message, upon reassembly, may prove themselves to comprise + a partial message. This is explicitly permitted. + + Three parameters must be specified in the Content-Type field of type + "message/partial": The first, "id", is a unique identifier, as close + to a world-unique identifier as possible, to be used to match the + fragments together. (In general, the identifier is essentially a + message-id; if placed in double quotes, it can be ANY message-id, in + accordance with the BNF for "parameter" given in RFC 2045.) The + second, "number", an integer, is the fragment number, which indicates + where this fragment fits into the sequence of fragments. The third, + "total", another integer, is the total number of fragments. This + third subfield is required on the final fragment, and is optional + (though encouraged) on the earlier fragments. Note also that these + parameters may be given in any order. + + Thus, the second piece of a 3-piece message may have either of the + following header fields: + + Content-Type: Message/Partial; number=2; total=3; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com" + + Content-Type: Message/Partial; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com"; + number=2 + + But the third piece MUST specify the total number of fragments: + + Content-Type: Message/Partial; number=3; total=3; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com" + + Note that fragment numbering begins with 1, not 0. + + When the fragments of an entity broken up in this manner are put + together, the result is always a complete MIME entity, which may have + its own Content-Type header field, and thus may contain any other + data type. + +5.2.2.1. Message Fragmentation and Reassembly + + The semantics of a reassembled partial message must be those of the + "inner" message, rather than of a message containing the inner + message. This makes it possible, for example, to send a large audio + message as several partial messages, and still have it appear to the + recipient as a simple audio message rather than as an encapsulated + + + +Freed & Borenstein Standards Track [Page 30] + +RFC 2046 Media Types November 1996 + + + message containing an audio message. That is, the encapsulation of + the message is considered to be "transparent". + + When generating and reassembling the pieces of a "message/partial" + message, the headers of the encapsulated message must be merged with + the headers of the enclosing entities. In this process the following + rules must be observed: + + (1) Fragmentation agents must split messages at line + boundaries only. This restriction is imposed because + splits at points other than the ends of lines in turn + depends on message transports being able to preserve + the semantics of messages that don't end with a CRLF + sequence. Many transports are incapable of preserving + such semantics. + + (2) All of the header fields from the initial enclosing + message, except those that start with "Content-" and + the specific header fields "Subject", "Message-ID", + "Encrypted", and "MIME-Version", must be copied, in + order, to the new message. + + (3) The header fields in the enclosed message which start + with "Content-", plus the "Subject", "Message-ID", + "Encrypted", and "MIME-Version" fields, must be + appended, in order, to the header fields of the new + message. Any header fields in the enclosed message + which do not start with "Content-" (except for the + "Subject", "Message-ID", "Encrypted", and "MIME- + Version" fields) will be ignored and dropped. + + (4) All of the header fields from the second and any + subsequent enclosing messages are discarded by the + reassembly process. + +5.2.2.2. Fragmentation and Reassembly Example + + If an audio message is broken into two pieces, the first piece might + look something like this: + + X-Weird-Header-1: Foo + From: Bill@host.com + To: joe@otherhost.com + Date: Fri, 26 Mar 1993 12:59:38 -0500 (EST) + Subject: Audio mail (part 1 of 2) + Message-ID: + MIME-Version: 1.0 + Content-type: message/partial; id="ABC@host.com"; + + + +Freed & Borenstein Standards Track [Page 31] + +RFC 2046 Media Types November 1996 + + + number=1; total=2 + + X-Weird-Header-1: Bar + X-Weird-Header-2: Hello + Message-ID: + Subject: Audio mail + MIME-Version: 1.0 + Content-type: audio/basic + Content-transfer-encoding: base64 + + ... first half of encoded audio data goes here ... + + and the second half might look something like this: + + From: Bill@host.com + To: joe@otherhost.com + Date: Fri, 26 Mar 1993 12:59:38 -0500 (EST) + Subject: Audio mail (part 2 of 2) + MIME-Version: 1.0 + Message-ID: + Content-type: message/partial; + id="ABC@host.com"; number=2; total=2 + + ... second half of encoded audio data goes here ... + + Then, when the fragmented message is reassembled, the resulting + message to be displayed to the user should look something like this: + + X-Weird-Header-1: Foo + From: Bill@host.com + To: joe@otherhost.com + Date: Fri, 26 Mar 1993 12:59:38 -0500 (EST) + Subject: Audio mail + Message-ID: + MIME-Version: 1.0 + Content-type: audio/basic + Content-transfer-encoding: base64 + + ... first half of encoded audio data goes here ... + ... second half of encoded audio data goes here ... + + The inclusion of a "References" field in the headers of the second + and subsequent pieces of a fragmented message that references the + Message-Id on the previous piece may be of benefit to mail readers + that understand and track references. However, the generation of + such "References" fields is entirely optional. + + + + + +Freed & Borenstein Standards Track [Page 32] + +RFC 2046 Media Types November 1996 + + + Finally, it should be noted that the "Encrypted" header field has + been made obsolete by Privacy Enhanced Messaging (PEM) [RFC-1421, + RFC-1422, RFC-1423, RFC-1424], but the rules above are nevertheless + believed to describe the correct way to treat it if it is encountered + in the context of conversion to and from "message/partial" fragments. + +5.2.3. External-Body Subtype + + The external-body subtype indicates that the actual body data are not + included, but merely referenced. In this case, the parameters + describe a mechanism for accessing the external data. + + When a MIME entity is of type "message/external-body", it consists of + a header, two consecutive CRLFs, and the message header for the + encapsulated message. If another pair of consecutive CRLFs appears, + this of course ends the message header for the encapsulated message. + However, since the encapsulated message's body is itself external, it + does NOT appear in the area that follows. For example, consider the + following message: + + Content-type: message/external-body; + access-type=local-file; + name="/u/nsb/Me.jpeg" + + Content-type: image/jpeg + Content-ID: + Content-Transfer-Encoding: binary + + THIS IS NOT REALLY THE BODY! + + The area at the end, which might be called the "phantom body", is + ignored for most external-body messages. However, it may be used to + contain auxiliary information for some such messages, as indeed it is + when the access-type is "mail- server". The only access-type defined + in this document that uses the phantom body is "mail-server", but + other access-types may be defined in the future in other + specifications that use this area. + + The encapsulated headers in ALL "message/external-body" entities MUST + include a Content-ID header field to give a unique identifier by + which to reference the data. This identifier may be used for caching + mechanisms, and for recognizing the receipt of the data when the + access-type is "mail-server". + + Note that, as specified here, the tokens that describe external-body + data, such as file names and mail server commands, are required to be + in the US-ASCII character set. + + + + +Freed & Borenstein Standards Track [Page 33] + +RFC 2046 Media Types November 1996 + + + If this proves problematic in practice, a new mechanism may be + required as a future extension to MIME, either as newly defined + access-types for "message/external-body" or by some other mechanism. + + As with "message/partial", MIME entities of type "message/external- + body" MUST have a content-transfer-encoding of 7bit (the default). + In particular, even in environments that support binary or 8bit + transport, the use of a content- transfer-encoding of "8bit" or + "binary" is explicitly prohibited for entities of type + "message/external-body". + +5.2.3.1. General External-Body Parameters + + The parameters that may be used with any "message/external- body" + are: + + (1) ACCESS-TYPE -- A word indicating the supported access + mechanism by which the file or data may be obtained. + This word is not case sensitive. Values include, but + are not limited to, "FTP", "ANON-FTP", "TFTP", "LOCAL- + FILE", and "MAIL-SERVER". Future values, except for + experimental values beginning with "X-", must be + registered with IANA, as described in RFC 2048. + This parameter is unconditionally mandatory and MUST be + present on EVERY "message/external-body". + + (2) EXPIRATION -- The date (in the RFC 822 "date-time" + syntax, as extended by RFC 1123 to permit 4 digits in + the year field) after which the existence of the + external data is not guaranteed. This parameter may be + used with ANY access-type and is ALWAYS optional. + + (3) SIZE -- The size (in octets) of the data. The intent + of this parameter is to help the recipient decide + whether or not to expend the necessary resources to + retrieve the external data. Note that this describes + the size of the data in its canonical form, that is, + before any Content-Transfer-Encoding has been applied + or after the data have been decoded. This parameter + may be used with ANY access-type and is ALWAYS + optional. + + (4) PERMISSION -- A case-insensitive field that indicates + whether or not it is expected that clients might also + attempt to overwrite the data. By default, or if + permission is "read", the assumption is that they are + not, and that if the data is retrieved once, it is + never needed again. If PERMISSION is "read-write", + + + +Freed & Borenstein Standards Track [Page 34] + +RFC 2046 Media Types November 1996 + + + this assumption is invalid, and any local copy must be + considered no more than a cache. "Read" and "Read- + write" are the only defined values of permission. This + parameter may be used with ANY access-type and is + ALWAYS optional. + + The precise semantics of the access-types defined here are described + in the sections that follow. + +5.2.3.2. The 'ftp' and 'tftp' Access-Types + + An access-type of FTP or TFTP indicates that the message body is + accessible as a file using the FTP [RFC-959] or TFTP [RFC- 783] + protocols, respectively. For these access-types, the following + additional parameters are mandatory: + + (1) NAME -- The name of the file that contains the actual + body data. + + (2) SITE -- A machine from which the file may be obtained, + using the given protocol. This must be a fully + qualified domain name, not a nickname. + + (3) Before any data are retrieved, using FTP, the user will + generally need to be asked to provide a login id and a + password for the machine named by the site parameter. + For security reasons, such an id and password are not + specified as content-type parameters, but must be + obtained from the user. + + In addition, the following parameters are optional: + + (1) DIRECTORY -- A directory from which the data named by + NAME should be retrieved. + + (2) MODE -- A case-insensitive string indicating the mode + to be used when retrieving the information. The valid + values for access-type "TFTP" are "NETASCII", "OCTET", + and "MAIL", as specified by the TFTP protocol [RFC- + 783]. The valid values for access-type "FTP" are + "ASCII", "EBCDIC", "IMAGE", and "LOCALn" where "n" is a + decimal integer, typically 8. These correspond to the + representation types "A" "E" "I" and "L n" as specified + by the FTP protocol [RFC-959]. Note that "BINARY" and + "TENEX" are not valid values for MODE and that "OCTET" + or "IMAGE" or "LOCAL8" should be used instead. IF MODE + is not specified, the default value is "NETASCII" for + TFTP and "ASCII" otherwise. + + + +Freed & Borenstein Standards Track [Page 35] + +RFC 2046 Media Types November 1996 + + +5.2.3.3. The 'anon-ftp' Access-Type + + The "anon-ftp" access-type is identical to the "ftp" access type, + except that the user need not be asked to provide a name and password + for the specified site. Instead, the ftp protocol will be used with + login "anonymous" and a password that corresponds to the user's mail + address. + +5.2.3.4. The 'local-file' Access-Type + + An access-type of "local-file" indicates that the actual body is + accessible as a file on the local machine. Two additional parameters + are defined for this access type: + + (1) NAME -- The name of the file that contains the actual + body data. This parameter is mandatory for the + "local-file" access-type. + + (2) SITE -- A domain specifier for a machine or set of + machines that are known to have access to the data + file. This optional parameter is used to describe the + locality of reference for the data, that is, the site + or sites at which the file is expected to be visible. + Asterisks may be used for wildcard matching to a part + of a domain name, such as "*.bellcore.com", to indicate + a set of machines on which the data should be directly + visible, while a single asterisk may be used to + indicate a file that is expected to be universally + available, e.g., via a global file system. + +5.2.3.5. The 'mail-server' Access-Type + + The "mail-server" access-type indicates that the actual body is + available from a mail server. Two additional parameters are defined + for this access-type: + + (1) SERVER -- The addr-spec of the mail server from which + the actual body data can be obtained. This parameter + is mandatory for the "mail-server" access-type. + + (2) SUBJECT -- The subject that is to be used in the mail + that is sent to obtain the data. Note that keying mail + servers on Subject lines is NOT recommended, but such + mail servers are known to exist. This is an optional + parameter. + + + + + + +Freed & Borenstein Standards Track [Page 36] + +RFC 2046 Media Types November 1996 + + + Because mail servers accept a variety of syntaxes, some of which is + multiline, the full command to be sent to a mail server is not + included as a parameter in the content-type header field. Instead, + it is provided as the "phantom body" when the media type is + "message/external-body" and the access-type is mail-server. + + Note that MIME does not define a mail server syntax. Rather, it + allows the inclusion of arbitrary mail server commands in the phantom + body. Implementations must include the phantom body in the body of + the message it sends to the mail server address to retrieve the + relevant data. + + Unlike other access-types, mail-server access is asynchronous and + will happen at an unpredictable time in the future. For this reason, + it is important that there be a mechanism by which the returned data + can be matched up with the original "message/external-body" entity. + MIME mail servers must use the same Content-ID field on the returned + message that was used in the original "message/external-body" + entities, to facilitate such matching. + +5.2.3.6. External-Body Security Issues + + "Message/external-body" entities give rise to two important security + issues: + + (1) Accessing data via a "message/external-body" reference + effectively results in the message recipient performing + an operation that was specified by the message + originator. It is therefore possible for the message + originator to trick a recipient into doing something + they would not have done otherwise. For example, an + originator could specify a action that attempts + retrieval of material that the recipient is not + authorized to obtain, causing the recipient to + unwittingly violate some security policy. For this + reason, user agents capable of resolving external + references must always take steps to describe the + action they are to take to the recipient and ask for + explicit permisssion prior to performing it. + + The 'mail-server' access-type is particularly + vulnerable, in that it causes the recipient to send a + new message whose contents are specified by the + original message's originator. Given the potential for + abuse, any such request messages that are constructed + should contain a clear indication that they were + generated automatically (e.g. in a Comments: header + field) in an attempt to resolve a MIME + + + +Freed & Borenstein Standards Track [Page 37] + +RFC 2046 Media Types November 1996 + + + "message/external-body" reference. + + (2) MIME will sometimes be used in environments that + provide some guarantee of message integrity and + authenticity. If present, such guarantees may apply + only to the actual direct content of messages -- they + may or may not apply to data accessed through MIME's + "message/external-body" mechanism. In particular, it + may be possible to subvert certain access mechanisms + even when the messaging system itself is secure. + + It should be noted that this problem exists either with + or without the availabilty of MIME mechanisms. A + casual reference to an FTP site containing a document + in the text of a secure message brings up similar + issues -- the only difference is that MIME provides for + automatic retrieval of such material, and users may + place unwarranted trust is such automatic retrieval + mechanisms. + +5.2.3.7. Examples and Further Explanations + + When the external-body mechanism is used in conjunction with the + "multipart/alternative" media type it extends the functionality of + "multipart/alternative" to include the case where the same entity is + provided in the same format but via different accces mechanisms. + When this is done the originator of the message must order the parts + first in terms of preferred formats and then by preferred access + mechanisms. The recipient's viewer should then evaluate the list + both in terms of format and access mechanisms. + + With the emerging possibility of very wide-area file systems, it + becomes very hard to know in advance the set of machines where a file + will and will not be accessible directly from the file system. + Therefore it may make sense to provide both a file name, to be tried + directly, and the name of one or more sites from which the file is + known to be accessible. An implementation can try to retrieve remote + files using FTP or any other protocol, using anonymous file retrieval + or prompting the user for the necessary name and password. If an + external body is accessible via multiple mechanisms, the sender may + include multiple entities of type "message/external-body" within the + body parts of an enclosing "multipart/alternative" entity. + + However, the external-body mechanism is not intended to be limited to + file retrieval, as shown by the mail-server access-type. Beyond + this, one can imagine, for example, using a video server for external + references to video clips. + + + + +Freed & Borenstein Standards Track [Page 38] + +RFC 2046 Media Types November 1996 + + + The embedded message header fields which appear in the body of the + "message/external-body" data must be used to declare the media type + of the external body if it is anything other than plain US-ASCII + text, since the external body does not have a header section to + declare its type. Similarly, any Content-transfer-encoding other + than "7bit" must also be declared here. Thus a complete + "message/external-body" message, referring to an object in PostScript + format, might look like this: + + From: Whomever + To: Someone + Date: Whenever + Subject: whatever + MIME-Version: 1.0 + Message-ID: + Content-Type: multipart/alternative; boundary=42 + Content-ID: + + --42 + Content-Type: message/external-body; name="BodyFormats.ps"; + site="thumper.bellcore.com"; mode="image"; + access-type=ANON-FTP; directory="pub"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + Content-ID: + + --42 + Content-Type: message/external-body; access-type=local-file; + name="/u/nsb/writing/rfcs/RFC-MIME.ps"; + site="thumper.bellcore.com"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + Content-ID: + + --42 + Content-Type: message/external-body; + access-type=mail-server + server="listserv@bogus.bitnet"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + Content-ID: + + get RFC-MIME.DOC + + --42-- + + + +Freed & Borenstein Standards Track [Page 39] + +RFC 2046 Media Types November 1996 + + + Note that in the above examples, the default Content-transfer- + encoding of "7bit" is assumed for the external postscript data. + + Like the "message/partial" type, the "message/external-body" media + type is intended to be transparent, that is, to convey the data type + in the external body rather than to convey a message with a body of + that type. Thus the headers on the outer and inner parts must be + merged using the same rules as for "message/partial". In particular, + this means that the Content-type and Subject fields are overridden, + but the From field is preserved. + + Note that since the external bodies are not transported along with + the external body reference, they need not conform to transport + limitations that apply to the reference itself. In particular, + Internet mail transports may impose 7bit and line length limits, but + these do not automatically apply to binary external body references. + Thus a Content-Transfer-Encoding is not generally necessary, though + it is permitted. + + Note that the body of a message of type "message/external-body" is + governed by the basic syntax for an RFC 822 message. In particular, + anything before the first consecutive pair of CRLFs is header + information, while anything after it is body information, which is + ignored for most access-types. + +5.2.4. Other Message Subtypes + + MIME implementations must in general treat unrecognized subtypes of + "message" as being equivalent to "application/octet-stream". + + Future subtypes of "message" intended for use with email should be + restricted to "7bit" encoding. A type other than "message" should be + used if restriction to "7bit" is not possible. + +6. Experimental Media Type Values + + A media type value beginning with the characters "X-" is a private + value, to be used by consenting systems by mutual agreement. Any + format without a rigorous and public definition must be named with an + "X-" prefix, and publicly specified values shall never begin with + "X-". (Older versions of the widely used Andrew system use the "X- + BE2" name, so new systems should probably choose a different name.) + + In general, the use of "X-" top-level types is strongly discouraged. + Implementors should invent subtypes of the existing types whenever + possible. In many cases, a subtype of "application" will be more + appropriate than a new top-level type. + + + + +Freed & Borenstein Standards Track [Page 40] + +RFC 2046 Media Types November 1996 + + +7. Summary + + The five discrete media types provide provide a standardized + mechanism for tagging entities as "audio", "image", or several other + kinds of data. The composite "multipart" and "message" media types + allow mixing and hierarchical structuring of entities of different + types in a single message. A distinguished parameter syntax allows + further specification of data format details, particularly the + specification of alternate character sets. Additional optional + header fields provide mechanisms for certain extensions deemed + desirable by many implementors. Finally, a number of useful media + types are defined for general use by consenting user agents, notably + "message/partial" and "message/external-body". + +9. Security Considerations + + Security issues are discussed in the context of the + "application/postscript" type, the "message/external-body" type, and + in RFC 2048. Implementors should pay special attention to the + security implications of any media types that can cause the remote + execution of any actions in the recipient's environment. In such + cases, the discussion of the "application/postscript" type may serve + as a model for considering other media types with remote execution + capabilities. + + + + + + + + + + + + + + + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 41] + +RFC 2046 Media Types November 1996 + + +9. Authors' Addresses + + For more information, the authors of this document are best contacted + via Internet mail: + + Ned Freed + Innosoft International, Inc. + 1050 East Garvey Avenue South + West Covina, CA 91790 + USA + + Phone: +1 818 919 3600 + Fax: +1 818 919 3614 + EMail: ned@innosoft.com + + + Nathaniel S. Borenstein + First Virtual Holdings + 25 Washington Avenue + Morristown, NJ 07960 + USA + + Phone: +1 201 540 8967 + Fax: +1 201 993 3032 + EMail: nsb@nsb.fv.com + + + MIME is a result of the work of the Internet Engineering Task Force + Working Group on RFC 822 Extensions. The chairman of that group, + Greg Vaudreuil, may be reached at: + + Gregory M. Vaudreuil + Octel Network Services + 17080 Dallas Parkway + Dallas, TX 75248-1905 + USA + + EMail: Greg.Vaudreuil@Octel.Com + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 42] + +RFC 2046 Media Types November 1996 + + +Appendix A -- Collected Grammar + + This appendix contains the complete BNF grammar for all the syntax + specified by this document. + + By itself, however, this grammar is incomplete. It refers by name to + several syntax rules that are defined by RFC 822. Rather than + reproduce those definitions here, and risk unintentional differences + between the two, this document simply refers the reader to RFC 822 + for the remaining definitions. Wherever a term is undefined, it + refers to the RFC 822 definition. + + boundary := 0*69 bcharsnospace + + bchars := bcharsnospace / " " + + bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / + "+" / "_" / "," / "-" / "." / + "/" / ":" / "=" / "?" + + body-part := <"message" as defined in RFC 822, with all + header fields optional, not starting with the + specified dash-boundary, and with the + delimiter not occurring anywhere in the + body part. Note that the semantics of a + part differ from the semantics of a message, + as described in the text.> + + close-delimiter := delimiter "--" + + dash-boundary := "--" boundary + ; boundary taken from the value of + ; boundary parameter of the + ; Content-Type field. + + delimiter := CRLF dash-boundary + + discard-text := *(*text CRLF) + ; May be ignored or discarded. + + encapsulation := delimiter transport-padding + CRLF body-part + + epilogue := discard-text + + multipart-body := [preamble CRLF] + dash-boundary transport-padding CRLF + body-part *encapsulation + + + +Freed & Borenstein Standards Track [Page 43] + +RFC 2046 Media Types November 1996 + + + close-delimiter transport-padding + [CRLF epilogue] + + preamble := discard-text + + transport-padding := *LWSP-char + ; Composers MUST NOT generate + ; non-zero length transport + ; padding, but receivers MUST + ; be able to handle padding + ; added by message transports. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 44] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2047.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2047.txt new file mode 100644 index 0000000..ff9a744 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2047.txt @@ -0,0 +1,843 @@ + + + + + + +Network Working Group K. Moore +Request for Comments: 2047 University of Tennessee +Obsoletes: 1521, 1522, 1590 November 1996 +Category: Standards Track + + + MIME (Multipurpose Internet Mail Extensions) Part Three: + Message Header Extensions for Non-ASCII Text + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + STD 11, RFC 822, defines a message representation protocol specifying + considerable detail about US-ASCII message headers, and leaves the + message content, or message body, as flat US-ASCII text. This set of + documents, collectively called the Multipurpose Internet Mail + Extensions, or MIME, redefines the format of messages to allow for + + (1) textual message bodies in character sets other than US-ASCII, + + (2) an extensible set of different formats for non-textual message + bodies, + + (3) multi-part message bodies, and + + (4) textual header information in character sets other than US-ASCII. + + These documents are based on earlier work documented in RFC 934, STD + 11, and RFC 1049, but extends and revises them. Because RFC 822 said + so little about message bodies, these documents are largely + orthogonal to (rather than a revision of) RFC 822. + + This particular document is the third document in the series. It + describes extensions to RFC 822 to allow non-US-ASCII text data in + Internet mail header fields. + + + + + + + + + +Moore Standards Track [Page 1] + +RFC 2047 Message Header Extensions November 1996 + + + Other documents in this series include: + + + RFC 2045, which specifies the various headers used to describe + the structure of MIME messages. + + + RFC 2046, which defines the general structure of the MIME media + typing system and defines an initial set of media types, + + + RFC 2048, which specifies various IANA registration procedures + for MIME-related facilities, and + + + RFC 2049, which describes MIME conformance criteria and + provides some illustrative examples of MIME message formats, + acknowledgements, and the bibliography. + + These documents are revisions of RFCs 1521, 1522, and 1590, which + themselves were revisions of RFCs 1341 and 1342. An appendix in RFC + 2049 describes differences and changes from previous versions. + +1. Introduction + + RFC 2045 describes a mechanism for denoting textual body parts which + are coded in various character sets, as well as methods for encoding + such body parts as sequences of printable US-ASCII characters. This + memo describes similar techniques to allow the encoding of non-ASCII + text in various portions of a RFC 822 [2] message header, in a manner + which is unlikely to confuse existing message handling software. + + Like the encoding techniques described in RFC 2045, the techniques + outlined here were designed to allow the use of non-ASCII characters + in message headers in a way which is unlikely to be disturbed by the + quirks of existing Internet mail handling programs. In particular, + some mail relaying programs are known to (a) delete some message + header fields while retaining others, (b) rearrange the order of + addresses in To or Cc fields, (c) rearrange the (vertical) order of + header fields, and/or (d) "wrap" message headers at different places + than those in the original message. In addition, some mail reading + programs are known to have difficulty correctly parsing message + headers which, while legal according to RFC 822, make use of + backslash-quoting to "hide" special characters such as "<", ",", or + ":", or which exploit other infrequently-used features of that + specification. + + While it is unfortunate that these programs do not correctly + interpret RFC 822 headers, to "break" these programs would cause + severe operational problems for the Internet mail system. The + extensions described in this memo therefore do not rely on little- + used features of RFC 822. + + + +Moore Standards Track [Page 2] + +RFC 2047 Message Header Extensions November 1996 + + + Instead, certain sequences of "ordinary" printable ASCII characters + (known as "encoded-words") are reserved for use as encoded data. The + syntax of encoded-words is such that they are unlikely to + "accidentally" appear as normal text in message headers. + Furthermore, the characters used in encoded-words are restricted to + those which do not have special meanings in the context in which the + encoded-word appears. + + Generally, an "encoded-word" is a sequence of printable ASCII + characters that begins with "=?", ends with "?=", and has two "?"s in + between. It specifies a character set and an encoding method, and + also includes the original text encoded as graphic ASCII characters, + according to the rules for that encoding method. + + A mail composer that implements this specification will provide a + means of inputting non-ASCII text in header fields, but will + translate these fields (or appropriate portions of these fields) into + encoded-words before inserting them into the message header. + + A mail reader that implements this specification will recognize + encoded-words when they appear in certain portions of the message + header. Instead of displaying the encoded-word "as is", it will + reverse the encoding and display the original text in the designated + character set. + +NOTES + + This memo relies heavily on notation and terms defined RFC 822 and + RFC 2045. In particular, the syntax for the ABNF used in this memo + is defined in RFC 822, as well as many of the terminal or nonterminal + symbols from RFC 822 are used in the grammar for the header + extensions defined here. Among the symbols defined in RFC 822 and + referenced in this memo are: 'addr-spec', 'atom', 'CHAR', 'comment', + 'CTLs', 'ctext', 'linear-white-space', 'phrase', 'quoted-pair'. + 'quoted-string', 'SPACE', and 'word'. Successful implementation of + this protocol extension requires careful attention to the RFC 822 + definitions of these terms. + + When the term "ASCII" appears in this memo, it refers to the "7-Bit + American Standard Code for Information Interchange", ANSI X3.4-1986. + The MIME charset name for this character set is "US-ASCII". When not + specifically referring to the MIME charset name, this document uses + the term "ASCII", both for brevity and for consistency with RFC 822. + However, implementors are warned that the character set name must be + spelled "US-ASCII" in MIME message and body part headers. + + + + + + +Moore Standards Track [Page 3] + +RFC 2047 Message Header Extensions November 1996 + + + This memo specifies a protocol for the representation of non-ASCII + text in message headers. It specifically DOES NOT define any + translation between "8-bit headers" and pure ASCII headers, nor is + any such translation assumed to be possible. + +2. Syntax of encoded-words + + An 'encoded-word' is defined by the following ABNF grammar. The + notation of RFC 822 is used, with the exception that white space + characters MUST NOT appear between components of an 'encoded-word'. + + encoded-word = "=?" charset "?" encoding "?" encoded-text "?=" + + charset = token ; see section 3 + + encoding = token ; see section 4 + + token = 1* + + especials = "(" / ")" / "<" / ">" / "@" / "," / ";" / ":" / " + <"> / "/" / "[" / "]" / "?" / "." / "=" + + encoded-text = 1* + ; (but see "Use of encoded-words in message + ; headers", section 5) + + Both 'encoding' and 'charset' names are case-independent. Thus the + charset name "ISO-8859-1" is equivalent to "iso-8859-1", and the + encoding named "Q" may be spelled either "Q" or "q". + + An 'encoded-word' may not be more than 75 characters long, including + 'charset', 'encoding', 'encoded-text', and delimiters. If it is + desirable to encode more text than will fit in an 'encoded-word' of + 75 characters, multiple 'encoded-word's (separated by CRLF SPACE) may + be used. + + While there is no limit to the length of a multiple-line header + field, each line of a header field that contains one or more + 'encoded-word's is limited to 76 characters. + + The length restrictions are included both to ease interoperability + through internetwork mail gateways, and to impose a limit on the + amount of lookahead a header parser must employ (while looking for a + final ?= delimiter) before it can decide whether a token is an + "encoded-word" or something else. + + + + + +Moore Standards Track [Page 4] + +RFC 2047 Message Header Extensions November 1996 + + + IMPORTANT: 'encoded-word's are designed to be recognized as 'atom's + by an RFC 822 parser. As a consequence, unencoded white space + characters (such as SPACE and HTAB) are FORBIDDEN within an + 'encoded-word'. For example, the character sequence + + =?iso-8859-1?q?this is some text?= + + would be parsed as four 'atom's, rather than as a single 'atom' (by + an RFC 822 parser) or 'encoded-word' (by a parser which understands + 'encoded-words'). The correct way to encode the string "this is some + text" is to encode the SPACE characters as well, e.g. + + =?iso-8859-1?q?this=20is=20some=20text?= + + The characters which may appear in 'encoded-text' are further + restricted by the rules in section 5. + +3. Character sets + + The 'charset' portion of an 'encoded-word' specifies the character + set associated with the unencoded text. A 'charset' can be any of + the character set names allowed in an MIME "charset" parameter of a + "text/plain" body part, or any character set name registered with + IANA for use with the MIME text/plain content-type. + + Some character sets use code-switching techniques to switch between + "ASCII mode" and other modes. If unencoded text in an 'encoded-word' + contains a sequence which causes the charset interpreter to switch + out of ASCII mode, it MUST contain additional control codes such that + ASCII mode is again selected at the end of the 'encoded-word'. (This + rule applies separately to each 'encoded-word', including adjacent + 'encoded-word's within a single header field.) + + When there is a possibility of using more than one character set to + represent the text in an 'encoded-word', and in the absence of + private agreements between sender and recipients of a message, it is + recommended that members of the ISO-8859-* series be used in + preference to other character sets. + +4. Encodings + + Initially, the legal values for "encoding" are "Q" and "B". These + encodings are described below. The "Q" encoding is recommended for + use when most of the characters to be encoded are in the ASCII + character set; otherwise, the "B" encoding should be used. + Nevertheless, a mail reader which claims to recognize 'encoded-word's + MUST be able to accept either encoding for any character set which it + supports. + + + +Moore Standards Track [Page 5] + +RFC 2047 Message Header Extensions November 1996 + + + Only a subset of the printable ASCII characters may be used in + 'encoded-text'. Space and tab characters are not allowed, so that + the beginning and end of an 'encoded-word' are obvious. The "?" + character is used within an 'encoded-word' to separate the various + portions of the 'encoded-word' from one another, and thus cannot + appear in the 'encoded-text' portion. Other characters are also + illegal in certain contexts. For example, an 'encoded-word' in a + 'phrase' preceding an address in a From header field may not contain + any of the "specials" defined in RFC 822. Finally, certain other + characters are disallowed in some contexts, to ensure reliability for + messages that pass through internetwork mail gateways. + + The "B" encoding automatically meets these requirements. The "Q" + encoding allows a wide range of printable characters to be used in + non-critical locations in the message header (e.g., Subject), with + fewer characters available for use in other locations. + +4.1. The "B" encoding + + The "B" encoding is identical to the "BASE64" encoding defined by RFC + 2045. + +4.2. The "Q" encoding + + The "Q" encoding is similar to the "Quoted-Printable" content- + transfer-encoding defined in RFC 2045. It is designed to allow text + containing mostly ASCII characters to be decipherable on an ASCII + terminal without decoding. + + (1) Any 8-bit value may be represented by a "=" followed by two + hexadecimal digits. For example, if the character set in use + were ISO-8859-1, the "=" character would thus be encoded as + "=3D", and a SPACE by "=20". (Upper case should be used for + hexadecimal digits "A" through "F".) + + (2) The 8-bit hexadecimal value 20 (e.g., ISO-8859-1 SPACE) may be + represented as "_" (underscore, ASCII 95.). (This character may + not pass through some internetwork mail gateways, but its use + will greatly enhance readability of "Q" encoded data with mail + readers that do not support this encoding.) Note that the "_" + always represents hexadecimal 20, even if the SPACE character + occupies a different code position in the character set in use. + + (3) 8-bit values which correspond to printable ASCII characters other + than "=", "?", and "_" (underscore), MAY be represented as those + characters. (But see section 5 for restrictions.) In + particular, SPACE and TAB MUST NOT be represented as themselves + within encoded words. + + + +Moore Standards Track [Page 6] + +RFC 2047 Message Header Extensions November 1996 + + +5. Use of encoded-words in message headers + + An 'encoded-word' may appear in a message header or body part header + according to the following rules: + +(1) An 'encoded-word' may replace a 'text' token (as defined by RFC 822) + in any Subject or Comments header field, any extension message + header field, or any MIME body part field for which the field body + is defined as '*text'. An 'encoded-word' may also appear in any + user-defined ("X-") message or body part header field. + + Ordinary ASCII text and 'encoded-word's may appear together in the + same header field. However, an 'encoded-word' that appears in a + header field defined as '*text' MUST be separated from any adjacent + 'encoded-word' or 'text' by 'linear-white-space'. + +(2) An 'encoded-word' may appear within a 'comment' delimited by "(" and + ")", i.e., wherever a 'ctext' is allowed. More precisely, the RFC + 822 ABNF definition for 'comment' is amended as follows: + + comment = "(" *(ctext / quoted-pair / comment / encoded-word) ")" + + A "Q"-encoded 'encoded-word' which appears in a 'comment' MUST NOT + contain the characters "(", ")" or " + 'encoded-word' that appears in a 'comment' MUST be separated from + any adjacent 'encoded-word' or 'ctext' by 'linear-white-space'. + + It is important to note that 'comment's are only recognized inside + "structured" field bodies. In fields whose bodies are defined as + '*text', "(" and ")" are treated as ordinary characters rather than + comment delimiters, and rule (1) of this section applies. (See RFC + 822, sections 3.1.2 and 3.1.3) + +(3) As a replacement for a 'word' entity within a 'phrase', for example, + one that precedes an address in a From, To, or Cc header. The ABNF + definition for 'phrase' from RFC 822 thus becomes: + + phrase = 1*( encoded-word / word ) + + In this case the set of characters that may be used in a "Q"-encoded + 'encoded-word' is restricted to: . An 'encoded-word' that appears within a + 'phrase' MUST be separated from any adjacent 'word', 'text' or + 'special' by 'linear-white-space'. + + + + + + +Moore Standards Track [Page 7] + +RFC 2047 Message Header Extensions November 1996 + + + These are the ONLY locations where an 'encoded-word' may appear. In + particular: + + + An 'encoded-word' MUST NOT appear in any portion of an 'addr-spec'. + + + An 'encoded-word' MUST NOT appear within a 'quoted-string'. + + + An 'encoded-word' MUST NOT be used in a Received header field. + + + An 'encoded-word' MUST NOT be used in parameter of a MIME + Content-Type or Content-Disposition field, or in any structured + field body except within a 'comment' or 'phrase'. + + The 'encoded-text' in an 'encoded-word' must be self-contained; + 'encoded-text' MUST NOT be continued from one 'encoded-word' to + another. This implies that the 'encoded-text' portion of a "B" + 'encoded-word' will be a multiple of 4 characters long; for a "Q" + 'encoded-word', any "=" character that appears in the 'encoded-text' + portion will be followed by two hexadecimal characters. + + Each 'encoded-word' MUST encode an integral number of octets. The + 'encoded-text' in each 'encoded-word' must be well-formed according + to the encoding specified; the 'encoded-text' may not be continued in + the next 'encoded-word'. (For example, "=?charset?Q?=?= + =?charset?Q?AB?=" would be illegal, because the two hex digits "AB" + must follow the "=" in the same 'encoded-word'.) + + Each 'encoded-word' MUST represent an integral number of characters. + A multi-octet character may not be split across adjacent 'encoded- + word's. + + Only printable and white space character data should be encoded using + this scheme. However, since these encoding schemes allow the + encoding of arbitrary octet values, mail readers that implement this + decoding should also ensure that display of the decoded data on the + recipient's terminal will not cause unwanted side-effects. + + Use of these methods to encode non-textual data (e.g., pictures or + sounds) is not defined by this memo. Use of 'encoded-word's to + represent strings of purely ASCII characters is allowed, but + discouraged. In rare cases it may be necessary to encode ordinary + text that looks like an 'encoded-word'. + + + + + + + + + +Moore Standards Track [Page 8] + +RFC 2047 Message Header Extensions November 1996 + + +6. Support of 'encoded-word's by mail readers + +6.1. Recognition of 'encoded-word's in message headers + + A mail reader must parse the message and body part headers according + to the rules in RFC 822 to correctly recognize 'encoded-word's. + + 'encoded-word's are to be recognized as follows: + + (1) Any message or body part header field defined as '*text', or any + user-defined header field, should be parsed as follows: Beginning + at the start of the field-body and immediately following each + occurrence of 'linear-white-space', each sequence of up to 75 + printable characters (not containing any 'linear-white-space') + should be examined to see if it is an 'encoded-word' according to + the syntax rules in section 2. Any other sequence of printable + characters should be treated as ordinary ASCII text. + + (2) Any header field not defined as '*text' should be parsed + according to the syntax rules for that header field. However, + any 'word' that appears within a 'phrase' should be treated as an + 'encoded-word' if it meets the syntax rules in section 2. + Otherwise it should be treated as an ordinary 'word'. + + (3) Within a 'comment', any sequence of up to 75 printable characters + (not containing 'linear-white-space'), that meets the syntax + rules in section 2, should be treated as an 'encoded-word'. + Otherwise it should be treated as normal comment text. + + (4) A MIME-Version header field is NOT required to be present for + 'encoded-word's to be interpreted according to this + specification. One reason for this is that the mail reader is + not expected to parse the entire message header before displaying + lines that may contain 'encoded-word's. + +6.2. Display of 'encoded-word's + + Any 'encoded-word's so recognized are decoded, and if possible, the + resulting unencoded text is displayed in the original character set. + + NOTE: Decoding and display of encoded-words occurs *after* a + structured field body is parsed into tokens. It is therefore + possible to hide 'special' characters in encoded-words which, when + displayed, will be indistinguishable from 'special' characters in the + surrounding text. For this and other reasons, it is NOT generally + possible to translate a message header containing 'encoded-word's to + an unencoded form which can be parsed by an RFC 822 mail reader. + + + + +Moore Standards Track [Page 9] + +RFC 2047 Message Header Extensions November 1996 + + + When displaying a particular header field that contains multiple + 'encoded-word's, any 'linear-white-space' that separates a pair of + adjacent 'encoded-word's is ignored. (This is to allow the use of + multiple 'encoded-word's to represent long strings of unencoded text, + without having to separate 'encoded-word's where spaces occur in the + unencoded text.) + + In the event other encodings are defined in the future, and the mail + reader does not support the encoding used, it may either (a) display + the 'encoded-word' as ordinary text, or (b) substitute an appropriate + message indicating that the text could not be decoded. + + If the mail reader does not support the character set used, it may + (a) display the 'encoded-word' as ordinary text (i.e., as it appears + in the header), (b) make a "best effort" to display using such + characters as are available, or (c) substitute an appropriate message + indicating that the decoded text could not be displayed. + + If the character set being used employs code-switching techniques, + display of the encoded text implicitly begins in "ASCII mode". In + addition, the mail reader must ensure that the output device is once + again in "ASCII mode" after the 'encoded-word' is displayed. + +6.3. Mail reader handling of incorrectly formed 'encoded-word's + + It is possible that an 'encoded-word' that is legal according to the + syntax defined in section 2, is incorrectly formed according to the + rules for the encoding being used. For example: + + (1) An 'encoded-word' which contains characters which are not legal + for a particular encoding (for example, a "-" in the "B" + encoding, or a SPACE or HTAB in either the "B" or "Q" encoding), + is incorrectly formed. + + (2) Any 'encoded-word' which encodes a non-integral number of + characters or octets is incorrectly formed. + + A mail reader need not attempt to display the text associated with an + 'encoded-word' that is incorrectly formed. However, a mail reader + MUST NOT prevent the display or handling of a message because an + 'encoded-word' is incorrectly formed. + +7. Conformance + + A mail composing program claiming compliance with this specification + MUST ensure that any string of non-white-space printable ASCII + characters within a '*text' or '*ctext' that begins with "=?" and + ends with "?=" be a valid 'encoded-word'. ("begins" means: at the + + + +Moore Standards Track [Page 10] + +RFC 2047 Message Header Extensions November 1996 + + + start of the field-body, immediately following 'linear-white-space', + or immediately following a "(" for an 'encoded-word' within '*ctext'; + "ends" means: at the end of the field-body, immediately preceding + 'linear-white-space', or immediately preceding a ")" for an + 'encoded-word' within '*ctext'.) In addition, any 'word' within a + 'phrase' that begins with "=?" and ends with "?=" must be a valid + 'encoded-word'. + + A mail reading program claiming compliance with this specification + must be able to distinguish 'encoded-word's from 'text', 'ctext', or + 'word's, according to the rules in section 6, anytime they appear in + appropriate places in message headers. It must support both the "B" + and "Q" encodings for any character set which it supports. The + program must be able to display the unencoded text if the character + set is "US-ASCII". For the ISO-8859-* character sets, the mail + reading program must at least be able to display the characters which + are also in the ASCII set. + +8. Examples + + The following are examples of message headers containing 'encoded- + word's: + + From: =?US-ASCII?Q?Keith_Moore?= + To: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= + CC: =?ISO-8859-1?Q?Andr=E9?= Pirard + Subject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?= + =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?= + + Note: In the first 'encoded-word' of the Subject field above, the + last "=" at the end of the 'encoded-text' is necessary because each + 'encoded-word' must be self-contained (the "=" character completes a + group of 4 base64 characters representing 2 octets). An additional + octet could have been encoded in the first 'encoded-word' (so that + the encoded-word would contain an exact multiple of 3 encoded + octets), except that the second 'encoded-word' uses a different + 'charset' than the first one. + + From: =?ISO-8859-1?Q?Olle_J=E4rnefors?= + To: ietf-822@dimacs.rutgers.edu, ojarnef@admin.kth.se + Subject: Time for ISO 10646? + + To: Dave Crocker + Cc: ietf-822@dimacs.rutgers.edu, paf@comsol.se + From: =?ISO-8859-1?Q?Patrik_F=E4ltstr=F6m?= + Subject: Re: RFC-HDR care and feeding + + + + + +Moore Standards Track [Page 11] + +RFC 2047 Message Header Extensions November 1996 + + + From: Nathaniel Borenstein + (=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?=) + To: Greg Vaudreuil , Ned Freed + , Keith Moore + Subject: Test of new header generator + MIME-Version: 1.0 + Content-type: text/plain; charset=ISO-8859-1 + + The following examples illustrate how text containing 'encoded-word's + which appear in a structured field body. The rules are slightly + different for fields defined as '*text' because "(" and ")" are not + recognized as 'comment' delimiters. [Section 5, paragraph (1)]. + + In each of the following examples, if the same sequence were to occur + in a '*text' field, the "displayed as" form would NOT be treated as + encoded words, but be identical to the "encoded form". This is + because each of the encoded-words in the following examples is + adjacent to a "(" or ")" character. + + encoded form displayed as + --------------------------------------------------------------------- + (=?ISO-8859-1?Q?a?=) (a) + + (=?ISO-8859-1?Q?a?= b) (a b) + + Within a 'comment', white space MUST appear between an + 'encoded-word' and surrounding text. [Section 5, + paragraph (2)]. However, white space is not needed between + the initial "(" that begins the 'comment', and the + 'encoded-word'. + + + (=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=) (ab) + + White space between adjacent 'encoded-word's is not + displayed. + + (=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=) (ab) + + Even multiple SPACEs between 'encoded-word's are ignored + for the purpose of display. + + (=?ISO-8859-1?Q?a?= (ab) + =?ISO-8859-1?Q?b?=) + + Any amount of linear-space-white between 'encoded-word's, + even if it includes a CRLF followed by one or more SPACEs, + is ignored for the purposes of display. + + + +Moore Standards Track [Page 12] + +RFC 2047 Message Header Extensions November 1996 + + + (=?ISO-8859-1?Q?a_b?=) (a b) + + In order to cause a SPACE to be displayed within a portion + of encoded text, the SPACE MUST be encoded as part of the + 'encoded-word'. + + (=?ISO-8859-1?Q?a?= =?ISO-8859-2?Q?_b?=) (a b) + + In order to cause a SPACE to be displayed between two strings + of encoded text, the SPACE MAY be encoded as part of one of + the 'encoded-word's. + +9. References + + [RFC 822] Crocker, D., "Standard for the Format of ARPA Internet Text + Messages", STD 11, RFC 822, UDEL, August 1982. + + [RFC 2049] Borenstein, N., and N. Freed, "Multipurpose Internet Mail + Extensions (MIME) Part Five: Conformance Criteria and Examples", + RFC 2049, November 1996. + + [RFC 2045] Borenstein, N., and N. Freed, "Multipurpose Internet Mail + Extensions (MIME) Part One: Format of Internet Message Bodies", + RFC 2045, November 1996. + + [RFC 2046] Borenstein N., and N. Freed, "Multipurpose Internet Mail + Extensions (MIME) Part Two: Media Types", RFC 2046, + November 1996. + + [RFC 2048] Freed, N., Klensin, J., and J. Postel, "Multipurpose + Internet Mail Extensions (MIME) Part Four: Registration + Procedures", RFC 2048, November 1996. + + + + + + + + + + + + + + + + + + + +Moore Standards Track [Page 13] + +RFC 2047 Message Header Extensions November 1996 + + +10. Security Considerations + + Security issues are not discussed in this memo. + +11. Acknowledgements + + The author wishes to thank Nathaniel Borenstein, Issac Chan, Lutz + Donnerhacke, Paul Eggert, Ned Freed, Andreas M. Kirchwitz, Olle + Jarnefors, Mike Rosin, Yutaka Sato, Bart Schaefer, and Kazuhiko + Yamamoto, for their helpful advice, insightful comments, and + illuminating questions in response to earlier versions of this + specification. + +12. Author's Address + + Keith Moore + University of Tennessee + 107 Ayres Hall + Knoxville TN 37996-1301 + + EMail: moore@cs.utk.edu + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Moore Standards Track [Page 14] + +RFC 2047 Message Header Extensions November 1996 + + +Appendix - changes since RFC 1522 (in no particular order) + + + explicitly state that the MIME-Version is not requried to use + 'encoded-word's. + + + add explicit note that SPACEs and TABs are not allowed within + 'encoded-word's, explaining that an 'encoded-word' must look like an + 'atom' to an RFC822 parser.values, to be precise). + + + add examples from Olle Jarnefors (thanks!) which illustrate how + encoded-words with adjacent linear-white-space are displayed. + + + explicitly list terms defined in RFC822 and referenced in this memo + + + fix transcription typos that caused one or two lines and a couple of + characters to disappear in the resulting text, due to nroff quirks. + + + clarify that encoded-words are allowed in '*text' fields in both + RFC822 headers and MIME body part headers, but NOT as parameter + values. + + + clarify the requirement to switch back to ASCII within the encoded + portion of an 'encoded-word', for any charset that uses code switching + sequences. + + + add a note about 'encoded-word's being delimited by "(" and ")" + within a comment, but not in a *text (how bizarre!). + + + fix the Andre Pirard example to get rid of the trailing "_" after + the =E9. (no longer needed post-1342). + + + clarification: an 'encoded-word' may appear immediately following + the initial "(" or immediately before the final ")" that delimits a + comment, not just adjacent to "(" and ")" *within* *ctext. + + + add a note to explain that a "B" 'encoded-word' will always have a + multiple of 4 characters in the 'encoded-text' portion. + + + add note about the "=" in the examples + + + note that processing of 'encoded-word's occurs *after* parsing, and + some of the implications thereof. + + + explicitly state that you can't expect to translate between + 1522 and either vanilla 822 or so-called "8-bit headers". + + + explicitly state that 'encoded-word's are not valid within a + 'quoted-string'. + + + +Moore Standards Track [Page 15] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2048.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2048.txt new file mode 100644 index 0000000..a3b18b3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2048.txt @@ -0,0 +1,1180 @@ + + + + + + +Network Working Group N. Freed +Request for Comments: 2048 Innosoft +BCP: 13 J. Klensin +Obsoletes: 1521, 1522, 1590 MCI +Category: Best Current Practice J. Postel + ISI + November 1996 + + + Multipurpose Internet Mail Extensions + (MIME) Part Four: + Registration Procedures + +Status of this Memo + + This document specifies an Internet Best Current Practices for the + Internet Community, and requests discussion and suggestions for + improvements. Distribution of this memo is unlimited. + +Abstract + + STD 11, RFC 822, defines a message representation protocol specifying + considerable detail about US-ASCII message headers, and leaves the + message content, or message body, as flat US-ASCII text. This set of + documents, collectively called the Multipurpose Internet Mail + Extensions, or MIME, redefines the format of messages to allow for + + (1) textual message bodies in character sets other than + US-ASCII, + + (2) an extensible set of different formats for non-textual + message bodies, + + (3) multi-part message bodies, and + + (4) textual header information in character sets other than + US-ASCII. + + These documents are based on earlier work documented in RFC 934, STD + 11, and RFC 1049, but extends and revises them. Because RFC 822 said + so little about message bodies, these documents are largely + orthogonal to (rather than a revision of) RFC 822. + + + + + + + + + +Freed, et. al. Best Current Practice [Page 1] + +RFC 2048 MIME Registration Procedures November 1996 + + + This fourth document, RFC 2048, specifies various IANA registration + procedures for the following MIME facilities: + + (1) media types, + + (2) external body access types, + + (3) content-transfer-encodings. + + Registration of character sets for use in MIME is covered elsewhere + and is no longer addressed by this document. + + These documents are revisions of RFCs 1521 and 1522, which themselves + were revisions of RFCs 1341 and 1342. An appendix in RFC 2049 + describes differences and changes from previous versions. + +Table of Contents + + 1. Introduction ......................................... 3 + 2. Media Type Registration .............................. 4 + 2.1 Registration Trees and Subtype Names ................ 4 + 2.1.1 IETF Tree ......................................... 4 + 2.1.2 Vendor Tree ....................................... 4 + 2.1.3 Personal or Vanity Tree ........................... 5 + 2.1.4 Special `x.' Tree ................................. 5 + 2.1.5 Additional Registration Trees ..................... 6 + 2.2 Registration Requirements ........................... 6 + 2.2.1 Functionality Requirement ......................... 6 + 2.2.2 Naming Requirements ............................... 6 + 2.2.3 Parameter Requirements ............................ 7 + 2.2.4 Canonicalization and Format Requirements .......... 7 + 2.2.5 Interchange Recommendations ....................... 8 + 2.2.6 Security Requirements ............................. 8 + 2.2.7 Usage and Implementation Non-requirements ......... 9 + 2.2.8 Publication Requirements .......................... 10 + 2.2.9 Additional Information ............................ 10 + 2.3 Registration Procedure .............................. 11 + 2.3.1 Present the Media Type to the Community for Review 11 + 2.3.2 IESG Approval ..................................... 12 + 2.3.3 IANA Registration ................................. 12 + 2.4 Comments on Media Type Registrations ................ 12 + 2.5 Location of Registered Media Type List .............. 12 + 2.6 IANA Procedures for Registering Media Types ......... 12 + 2.7 Change Control ...................................... 13 + 2.8 Registration Template ............................... 14 + 3. External Body Access Types ........................... 14 + 3.1 Registration Requirements ........................... 15 + 3.1.1 Naming Requirements ............................... 15 + + + +Freed, et. al. Best Current Practice [Page 2] + +RFC 2048 MIME Registration Procedures November 1996 + + + 3.1.2 Mechanism Specification Requirements .............. 15 + 3.1.3 Publication Requirements .......................... 15 + 3.1.4 Security Requirements ............................. 15 + 3.2 Registration Procedure .............................. 15 + 3.2.1 Present the Access Type to the Community .......... 16 + 3.2.2 Access Type Reviewer .............................. 16 + 3.2.3 IANA Registration ................................. 16 + 3.3 Location of Registered Access Type List ............. 16 + 3.4 IANA Procedures for Registering Access Types ........ 16 + 4. Transfer Encodings ................................... 17 + 4.1 Transfer Encoding Requirements ...................... 17 + 4.1.1 Naming Requirements ............................... 17 + 4.1.2 Algorithm Specification Requirements .............. 18 + 4.1.3 Input Domain Requirements ......................... 18 + 4.1.4 Output Range Requirements ......................... 18 + 4.1.5 Data Integrity and Generality Requirements ........ 18 + 4.1.6 New Functionality Requirements .................... 18 + 4.2 Transfer Encoding Definition Procedure .............. 19 + 4.3 IANA Procedures for Transfer Encoding Registration... 19 + 4.4 Location of Registered Transfer Encodings List ...... 19 + 5. Authors' Addresses ................................... 20 + A. Grandfathered Media Types ............................ 21 + +1. Introduction + + Recent Internet protocols have been carefully designed to be easily + extensible in certain areas. In particular, MIME [RFC 2045] is an + open-ended framework and can accommodate additional object types, + character sets, and access methods without any changes to the basic + protocol. A registration process is needed, however, to ensure that + the set of such values is developed in an orderly, well-specified, + and public manner. + + This document defines registration procedures which use the Internet + Assigned Numbers Authority (IANA) as a central registry for such + values. + + Historical Note: The registration process for media types was + initially defined in the context of the asynchronous Internet mail + environment. In this mail environment there is a need to limit the + number of possible media types to increase the likelihood of + interoperability when the capabilities of the remote mail system are + not known. As media types are used in new environments, where the + proliferation of media types is not a hindrance to interoperability, + the original procedure was excessively restrictive and had to be + generalized. + + + + + +Freed, et. al. Best Current Practice [Page 3] + +RFC 2048 MIME Registration Procedures November 1996 + + +2. Media Type Registration + + Registration of a new media type or types starts with the + construction of a registration proposal. Registration may occur in + several different registration trees, which have different + requirements as discussed below. In general, the new registration + proposal is circulated and reviewed in a fashion appropriate to the + tree involved. The media type is then registered if the proposal is + acceptable. The following sections describe the requirements and + procedures used for each of the different registration trees. + +2.1. Registration Trees and Subtype Names + + In order to increase the efficiency and flexibility of the + registration process, different structures of subtype names may be + registered to accomodate the different natural requirements for, + e.g., a subtype that will be recommended for wide support and + implementation by the Internet Community or a subtype that is used to + move files associated with proprietary software. The following + subsections define registration "trees", distinguished by the use of + faceted names (e.g., names of the form "tree.subtree...type"). Note + that some media types defined prior to this document do not conform + to the naming conventions described below. See Appendix A for a + discussion of them. + +2.1.1. IETF Tree + + The IETF tree is intended for types of general interest to the + Internet Community. Registration in the IETF tree requires approval + by the IESG and publication of the media type registration as some + form of RFC. + + Media types in the IETF tree are normally denoted by names that are + not explicitly faceted, i.e., do not contain period (".", full stop) + characters. + + The "owner" of a media type registration in the IETF tree is assumed + to be the IETF itself. Modification or alteration of the + specification requires the same level of processing (e.g. standards + track) required for the initial registration. + +2.1.2. Vendor Tree + + The vendor tree is used for media types associated with commercially + available products. "Vendor" or "producer" are construed as + equivalent and very broadly in this context. + + + + + +Freed, et. al. Best Current Practice [Page 4] + +RFC 2048 MIME Registration Procedures November 1996 + + + A registration may be placed in the vendor tree by anyone who has + need to interchange files associated with the particular product. + However, the registration formally belongs to the vendor or + organization producing the software or file format. Changes to the + specification will be made at their request, as discussed in + subsequent sections. + + Registrations in the vendor tree will be distinguished by the leading + facet "vnd.". That may be followed, at the discretion of the + registration, by either a media type name from a well-known producer + (e.g., "vnd.mudpie") or by an IANA-approved designation of the + producer's name which is then followed by a media type or product + designation (e.g., vnd.bigcompany.funnypictures). + + While public exposure and review of media types to be registered in + the vendor tree is not required, using the ietf-types list for review + is strongly encouraged to improve the quality of those + specifications. Registrations in the vendor tree may be submitted + directly to the IANA. + +2.1.3. Personal or Vanity Tree + + Registrations for media types created experimentally or as part of + products that are not distributed commercially may be registered in + the personal or vanity tree. The registrations are distinguished by + the leading facet "prs.". + + The owner of "personal" registrations and associated specifications + is the person or entity making the registration, or one to whom + responsibility has been transferred as described below. + + While public exposure and review of media types to be registered in + the personal tree is not required, using the ietf-types list for + review is strongly encouraged to improve the quality of those + specifications. Registrations in the personl tree may be submitted + directly to the IANA. + +2.1.4. Special `x.' Tree + + For convenience and symmetry with this registration scheme, media + type names with "x." as the first facet may be used for the same + purposes for which names starting in "x-" are normally used. These + types are unregistered, experimental, and should be used only with + the active agreement of the parties exchanging them. + + + + + + + +Freed, et. al. Best Current Practice [Page 5] + +RFC 2048 MIME Registration Procedures November 1996 + + + However, with the simplified registration procedures described above + for vendor and personal trees, it should rarely, if ever, be + necessary to use unregistered experimental types, and as such use of + both "x-" and "x." forms is discouraged. + +2.1.5. Additional Registration Trees + + From time to time and as required by the community, the IANA may, + with the advice and consent of the IESG, create new top-level + registration trees. It is explicitly assumed that these trees may be + created for external registration and management by well-known + permanent bodies, such as scientific societies for media types + specific to the sciences they cover. In general, the quality of + review of specifications for one of these additional registration + trees is expected to be equivalent to that which IETF would give to + registrations in its own tree. Establishment of these new trees will + be announced through RFC publication approved by the IESG. + +2.2. Registration Requirements + + Media type registration proposals are all expected to conform to + various requirements laid out in the following sections. Note that + requirement specifics sometimes vary depending on the registration + tree, again as detailed in the following sections. + +2.2.1. Functionality Requirement + + Media types must function as an actual media format: Registration of + things that are better thought of as a transfer encoding, as a + character set, or as a collection of separate entities of another + type, is not allowed. For example, although applications exist to + decode the base64 transfer encoding [RFC 2045], base64 cannot be + registered as a media type. + + This requirement applies regardless of the registration tree + involved. + +2.2.2. Naming Requirements + + All registered media types must be assigned MIME type and subtype + names. The combination of these names then serves to uniquely + identify the media type and the format of the subtype name identifies + the registration tree. + + The choice of top-level type name must take the nature of media type + involved into account. For example, media normally used for + representing still images should be a subtype of the image content + type, whereas media capable of representing audio information belongs + + + +Freed, et. al. Best Current Practice [Page 6] + +RFC 2048 MIME Registration Procedures November 1996 + + + under the audio content type. See RFC 2046 for additional information + on the basic set of top-level types and their characteristics. + + New subtypes of top-level types must conform to the restrictions of + the top-level type, if any. For example, all subtypes of the + multipart content type must use the same encapsulation syntax. + + In some cases a new media type may not "fit" under any currently + defined top-level content type. Such cases are expected to be quite + rare. However, if such a case arises a new top-level type can be + defined to accommodate it. Such a definition must be done via + standards-track RFC; no other mechanism can be used to define + additional top-level content types. + + These requirements apply regardless of the registration tree + involved. + +2.2.3. Parameter Requirements + + Media types may elect to use one or more MIME content type + parameters, or some parameters may be automatically made available to + the media type by virtue of being a subtype of a content type that + defines a set of parameters applicable to any of its subtypes. In + either case, the names, values, and meanings of any parameters must + be fully specified when a media type is registered in the IETF tree, + and should be specified as completely as possible when media types + are registered in the vendor or personal trees. + + New parameters must not be defined as a way to introduce new + functionality in types registered in the IETF tree, although new + parameters may be added to convey additional information that does + not otherwise change existing functionality. An example of this + would be a "revision" parameter to indicate a revision level of an + external specification such as JPEG. Similar behavior is encouraged + for media types registered in the vendor or personal trees but is not + required. + +2.2.4. Canonicalization and Format Requirements + + All registered media types must employ a single, canonical data + format, regardless of registration tree. + + A precise and openly available specification of the format of each + media type is required for all types registered in the IETF tree and + must at a minimum be referenced by, if it isn't actually included in, + the media type registration proposal itself. + + + + + +Freed, et. al. Best Current Practice [Page 7] + +RFC 2048 MIME Registration Procedures November 1996 + + + The specifications of format and processing particulars may or may + not be publically available for media types registered in the vendor + tree, and such registration proposals are explicitly permitted to + include only a specification of which software and version produce or + process such media types. References to or inclusion of format + specifications in registration proposals is encouraged but not + required. + + Format specifications are still required for registration in the + personal tree, but may be either published as RFCs or otherwise + deposited with IANA. The deposited specifications will meet the same + criteria as those required to register a well-known TCP port and, in + particular, need not be made public. + + Some media types involve the use of patented technology. The + registration of media types involving patented technology is + specifically permitted. However, the restrictions set forth in RFC + 1602 on the use of patented technology in standards-track protocols + must be respected when the specification of a media type is part of a + standards-track protocol. + +2.2.5. Interchange Recommendations + + Media types should, whenever possible, interoperate across as many + systems and applications as possible. However, some media types will + inevitably have problems interoperating across different platforms. + Problems with different versions, byte ordering, and specifics of + gateway handling can and will arise. + + Universal interoperability of media types is not required, but known + interoperability issues should be identified whenever possible. + Publication of a media type does not require an exhaustive review of + interoperability, and the interoperability considerations section is + subject to continuing evaluation. + + These recommendations apply regardless of the registration tree + involved. + +2.2.6. Security Requirements + + An analysis of security issues is required for for all types + registered in the IETF Tree. (This is in accordance with the basic + requirements for all IETF protocols.) A similar analysis for media + types registered in the vendor or personal trees is encouraged but + not required. However, regardless of what security analysis has or + has not been done, all descriptions of security issues must be as + accurate as possible regardless of registration tree. In particular, + a statement that there are "no security issues associated with this + + + +Freed, et. al. Best Current Practice [Page 8] + +RFC 2048 MIME Registration Procedures November 1996 + + + type" must not be confused with "the security issues associates with + this type have not been assessed". + + There is absolutely no requirement that media types registered in any + tree be secure or completely free from risks. Nevertheless, all + known security risks must be identified in the registration of a + media type, again regardless of registration tree. + + The security considerations section of all registrations is subject + to continuing evaluation and modification, and in particular may be + extended by use of the "comments on media types" mechanism described + in subsequent sections. + + Some of the issues that should be looked at in a security analysis of + a media type are: + + (1) Complex media types may include provisions for + directives that institute actions on a recipient's + files or other resources. In many cases provision is + made for originators to specify arbitrary actions in an + unrestricted fashion which may then have devastating + effects. See the registration of the + application/postscript media type in RFC 2046 for + an example of such directives and how to handle them. + + (2) Complex media types may include provisions for + directives that institute actions which, while not + directly harmful to the recipient, may result in + disclosure of information that either facilitates a + subsequent attack or else violates a recipient's + privacy in some way. Again, the registration of the + application/postscript media type illustrates how such + directives can be handled. + + (3) A media type might be targeted for applications that + require some sort of security assurance but not provide + the necessary security mechanisms themselves. For + example, a media type could be defined for storage of + confidential medical information which in turn requires + an external confidentiality service. + +2.2.7. Usage and Implementation Non-requirements + + In the asynchronous mail environment, where information on the + capabilities of the remote mail agent is frequently not available to + the sender, maximum interoperability is attained by restricting the + number of media types used to those "common" formats expected to be + widely implemented. This was asserted in the past as a reason to + + + +Freed, et. al. Best Current Practice [Page 9] + +RFC 2048 MIME Registration Procedures November 1996 + + + limit the number of possible media types and resulted in a + registration process with a significant hurdle and delay for those + registering media types. + + However, the need for "common" media types does not require limiting + the registration of new media types. If a limited set of media types + is recommended for a particular application, that should be asserted + by a separate applicability statement specific for the application + and/or environment. + + As such, universal support and implementation of a media type is NOT + a requirement for registration. If, however, a media type is + explicitly intended for limited use, this should be noted in its + registration. + +2.2.8. Publication Requirements + + Proposals for media types registered in the IETF tree must be + published as RFCs. RFC publication of vendor and personal media type + proposals is encouraged but not required. In all cases IANA will + retain copies of all media type proposals and "publish" them as part + of the media types registration tree itself. + + Other than in the IETF tree, the registration of a data type does not + imply endorsement, approval, or recommendation by IANA or IETF or + even certification that the specification is adequate. To become + Internet Standards, protocol, data objects, or whatever must go + through the IETF standards process. This is too difficult and too + lengthy a process for the convenient registration of media types. + + The IETF tree exists for media types that do require require a + substantive review and approval process with the vendor and personal + trees exist for those that do not. It is expected that applicability + statements for particular applications will be published from time to + time that recommend implementation of, and support for, media types + that have proven particularly useful in those contexts. + + As discussed above, registration of a top-level type requires + standards-track processing and, hence, RFC publication. + +2.2.9. Additional Information + + Various sorts of optional information may be included in the + specification of a media type if it is available: + + (1) Magic number(s) (length, octet values). Magic numbers + are byte sequences that are always present and thus can + be used to identify entities as being of a given media + + + +Freed, et. al. Best Current Practice [Page 10] + +RFC 2048 MIME Registration Procedures November 1996 + + + type. + + (2) File extension(s) commonly used on one or more + platforms to indicate that some file containing a given + type of media. + + (3) Macintosh File Type code(s) (4 octets) used to label + files containing a given type of media. + + Such information is often quite useful to implementors and if + available should be provided. + +2.3. Registration Procedure + + The following procedure has been implemented by the IANA for review + and approval of new media types. This is not a formal standards + process, but rather an administrative procedure intended to allow + community comment and sanity checking without excessive time delay. + For registration in the IETF tree, the normal IETF processes should + be followed, treating posting of an internet-draft and announcement + on the ietf-types list (as described in the next subsection) as a + first step. For registrations in the vendor or personal tree, the + initial review step described below may be omitted and the type + registered directly by submitting the template and an explanation + directly to IANA (at iana@iana.org). However, authors of vendor or + personal media type specifications are encouraged to seek community + review and comment whenever that is feasible. + +2.3.1. Present the Media Type to the Community for Review + + Send a proposed media type registration to the "ietf-types@iana.org" + mailing list for a two week review period. This mailing list has + been established for the purpose of reviewing proposed media and + access types. Proposed media types are not formally registered and + must not be used; the "x-" prefix specified in RFC 2045 can be used + until registration is complete. + + The intent of the public posting is to solicit comments and feedback + on the choice of type/subtype name, the unambiguity of the references + with respect to versions and external profiling information, and a + review of any interoperability or security considerations. The + submitter may submit a revised registration, or withdraw the + registration completely, at any time. + + + + + + + + +Freed, et. al. Best Current Practice [Page 11] + +RFC 2048 MIME Registration Procedures November 1996 + + +2.3.2. IESG Approval + + Media types registered in the IETF tree must be submitted to the IESG + for approval. + +2.3.3. IANA Registration + + Provided that the media type meets the requirements for media types + and has obtained approval that is necessary, the author may submit + the registration request to the IANA, which will register the media + type and make the media type registration available to the community. + +2.4. Comments on Media Type Registrations + + Comments on registered media types may be submitted by members of the + community to IANA. These comments will be passed on to the "owner" + of the media type if possible. Submitters of comments may request + that their comment be attached to the media type registration itself, + and if IANA approves of this the comment will be made accessible in + conjunction with the type registration itself. + +2.5. Location of Registered Media Type List + + Media type registrations will be posted in the anonymous FTP + directory "ftp://ftp.isi.edu/in-notes/iana/assignments/media-types/" + and all registered media types will be listed in the periodically + issued "Assigned Numbers" RFC [currently STD 2, RFC 1700]. The media + type description and other supporting material may also be published + as an Informational RFC by sending it to "rfc-editor@isi.edu" (please + follow the instructions to RFC authors [RFC-1543]). + +2.6. IANA Procedures for Registering Media Types + + The IANA will only register media types in the IETF tree in response + to a communication from the IESG stating that a given registration + has been approved. Vendor and personal types will be registered by + the IANA automatically and without any formal review as long as the + following minimal conditions are met: + + (1) Media types must function as an actual media format. + In particular, character sets and transfer encodings + may not be registered as media types. + + (2) All media types must have properly formed type and + subtype names. All type names must be defined by a + standards-track RFC. All subtype names must be unique, + must conform to the MIME grammar for such names, and + must contain the proper tree prefix. + + + +Freed, et. al. Best Current Practice [Page 12] + +RFC 2048 MIME Registration Procedures November 1996 + + + (3) Types registered in the personal tree must either + provide a format specification or a pointer to one. + + (4) Any security considerations given must not be obviously + bogus. (It is neither possible nor necessary for the + IANA to conduct a comprehensive security review of + media type registrations. Nevertheless, IANA has the + authority to identify obviously incompetent material + and exclude it.) + +2.7. Change Control + + Once a media type has been published by IANA, the author may request + a change to its definition. The descriptions of the different + registration trees above designate the "owners" of each type of + registration. The change request follows the same procedure as the + registration request: + + (1) Publish the revised template on the ietf-types list. + + (2) Leave at least two weeks for comments. + + (3) Publish using IANA after formal review if required. + + Changes should be requested only when there are serious omission or + errors in the published specification. When review is required, a + change request may be denied if it renders entities that were valid + under the previous definition invalid under the new definition. + + The owner of a content type may pass responsibility for the content + type to another person or agency by informing IANA and the ietf-types + list; this can be done without discussion or review. + + The IESG may reassign responsibility for a media type. The most + common case of this will be to enable changes to be made to types + where the author of the registration has died, moved out of contact + or is otherwise unable to make changes that are important to the + community. + + Media type registrations may not be deleted; media types which are no + longer believed appropriate for use can be declared OBSOLETE by a + change to their "intended use" field; such media types will be + clearly marked in the lists published by IANA. + + + + + + + + +Freed, et. al. Best Current Practice [Page 13] + +RFC 2048 MIME Registration Procedures November 1996 + + +2.8. Registration Template + + To: ietf-types@iana.org + Subject: Registration of MIME media type XXX/YYY + + MIME media type name: + + MIME subtype name: + + Required parameters: + + Optional parameters: + + Encoding considerations: + + Security considerations: + + Interoperability considerations: + + Published specification: + + Applications which use this media type: + + Additional information: + + Magic number(s): + File extension(s): + Macintosh File Type Code(s): + + Person & email address to contact for further information: + + Intended usage: + + (One of COMMON, LIMITED USE or OBSOLETE) + + Author/Change controller: + + (Any other information that the author deems interesting may be + added below this line.) + +3. External Body Access Types + + RFC 2046 defines the message/external-body media type, whereby a MIME + entity can act as pointer to the actual body data in lieu of + including the data directly in the entity body. Each + message/external-body reference specifies an access type, which + determines the mechanism used to retrieve the actual body data. RFC + 2046 defines an initial set of access types, but allows for the + + + +Freed, et. al. Best Current Practice [Page 14] + +RFC 2048 MIME Registration Procedures November 1996 + + + registration of additional access types to accommodate new retrieval + mechanisms. + +3.1. Registration Requirements + + New access type specifications must conform to a number of + requirements as described below. + +3.1.1. Naming Requirements + + Each access type must have a unique name. This name appears in the + access-type parameter in the message/external-body content-type + header field, and must conform to MIME content type parameter syntax. + +3.1.2. Mechanism Specification Requirements + + All of the protocols, transports, and procedures used by a given + access type must be described, either in the specification of the + access type itself or in some other publicly available specification, + in sufficient detail for the access type to be implemented by any + competent implementor. Use of secret and/or proprietary methods in + access types are expressly prohibited. The restrictions imposed by + RFC 1602 on the standardization of patented algorithms must be + respected as well. + +3.1.3. Publication Requirements + + All access types must be described by an RFC. The RFC may be + informational rather than standards-track, although standard-track + review and approval are encouraged for all access types. + +3.1.4. Security Requirements + + Any known security issues that arise from the use of the access type + must be completely and fully described. It is not required that the + access type be secure or that it be free from risks, but that the + known risks be identified. Publication of a new access type does not + require an exhaustive security review, and the security + considerations section is subject to continuing evaluation. + Additional security considerations should be addressed by publishing + revised versions of the access type specification. + +3.2. Registration Procedure + + Registration of a new access type starts with the construction of a + draft of an RFC. + + + + + +Freed, et. al. Best Current Practice [Page 15] + +RFC 2048 MIME Registration Procedures November 1996 + + +3.2.1. Present the Access Type to the Community + + Send a proposed access type specification to the "ietf- + types@iana.org" mailing list for a two week review period. This + mailing list has been established for the purpose of reviewing + proposed access and media types. Proposed access types are not + formally registered and must not be used. + + The intent of the public posting is to solicit comments and feedback + on the access type specification and a review of any security + considerations. + +3.2.2. Access Type Reviewer + + When the two week period has passed, the access type reviewer, who is + appointed by the IETF Applications Area Director, either forwards the + request to iana@isi.edu, or rejects it because of significant + objections raised on the list. + + Decisions made by the reviewer must be posted to the ietf-types + mailing list within 14 days. Decisions made by the reviewer may be + appealed to the IESG. + +3.2.3. IANA Registration + + Provided that the access type has either passed review or has been + successfully appealed to the IESG, the IANA will register the access + type and make the registration available to the community. The + specification of the access type must also be published as an RFC. + Informational RFCs are published by sending them to "rfc- + editor@isi.edu" (please follow the instructions to RFC authors [RFC- + 1543]). + +3.3. Location of Registered Access Type List + + Access type registrations will be posted in the anonymous FTP + directory "ftp://ftp.isi.edu/in-notes/iana/assignments/access-types/" + and all registered access types will be listed in the periodically + issued "Assigned Numbers" RFC [currently RFC-1700]. + +3.4. IANA Procedures for Registering Access Types + + The identity of the access type reviewer is communicated to the IANA + by the IESG. The IANA then only acts in response to access type + definitions that either are approved by the access type reviewer and + forwarded by the reviewer to the IANA for registration, or in + response to a communication from the IESG that an access type + definition appeal has overturned the access type reviewer's ruling. + + + +Freed, et. al. Best Current Practice [Page 16] + +RFC 2048 MIME Registration Procedures November 1996 + + +4. Transfer Encodings + + Transfer encodings are tranformations applied to MIME media types + after conversion to the media type's canonical form. Transfer + encodings are used for several purposes: + + (1) Many transports, especially message transports, can + only handle data consisting of relatively short lines + of text. There can also be severe restrictions on what + characters can be used in these lines of text -- some + transports are restricted to a small subset of US-ASCII + and others cannot handle certain character sequences. + Transfer encodings are used to transform binary data + into textual form that can survive such transports. + Examples of this sort of transfer encoding include the + base64 and quoted-printable transfer encodings defined + in RFC 2045. + + (2) Image, audio, video, and even application entities are + sometimes quite large. Compression algorithms are often + quite effective in reducing the size of large entities. + Transfer encodings can be used to apply general-purpose + non-lossy compression algorithms to MIME entities. + + (3) Transport encodings can be defined as a means of + representing existing encoding formats in a MIME + context. + + IMPORTANT: The standardization of a large numbers of different + transfer encodings is seen as a significant barrier to widespread + interoperability and is expressely discouraged. Nevertheless, the + following procedure has been defined to provide a means of defining + additional transfer encodings, should standardization actually be + justified. + +4.1. Transfer Encoding Requirements + + Transfer encoding specifications must conform to a number of + requirements as described below. + +4.1.1. Naming Requirements + + Each transfer encoding must have a unique name. This name appears in + the Content-Transfer-Encoding header field and must conform to the + syntax of that field. + + + + + + +Freed, et. al. Best Current Practice [Page 17] + +RFC 2048 MIME Registration Procedures November 1996 + + +4.1.2. Algorithm Specification Requirements + + All of the algorithms used in a transfer encoding (e.g. conversion + to printable form, compression) must be described in their entirety + in the transfer encoding specification. Use of secret and/or + proprietary algorithms in standardized transfer encodings are + expressly prohibited. The restrictions imposed by RFC 1602 on the + standardization of patented algorithms must be respected as well. + +4.1.3. Input Domain Requirements + + All transfer encodings must be applicable to an arbitrary sequence of + octets of any length. Dependence on particular input forms is not + allowed. + + It should be noted that the 7bit and 8bit encodings do not conform to + this requirement. Aside from the undesireability of having + specialized encodings, the intent here is to forbid the addition of + additional encodings along the lines of 7bit and 8bit. + +4.1.4. Output Range Requirements + + There is no requirement that a particular tranfer encoding produce a + particular form of encoded output. However, the output format for + each transfer encoding must be fully and completely documented. In + particular, each specification must clearly state whether the output + format always lies within the confines of 7bit data, 8bit data, or is + simply pure binary data. + +4.1.5. Data Integrity and Generality Requirements + + All transfer encodings must be fully invertible on any platform; it + must be possible for anyone to recover the original data by + performing the corresponding decoding operation. Note that this + requirement effectively excludes all forms of lossy compression as + well as all forms of encryption from use as a transfer encoding. + +4.1.6. New Functionality Requirements + + All transfer encodings must provide some sort of new functionality. + Some degree of functionality overlap with previously defined transfer + encodings is acceptable, but any new transfer encoding must also + offer something no other transfer encoding provides. + + + + + + + + +Freed, et. al. Best Current Practice [Page 18] + +RFC 2048 MIME Registration Procedures November 1996 + + +4.2. Transfer Encoding Definition Procedure + + Definition of a new transfer encoding starts with the construction of + a draft of a standards-track RFC. The RFC must define the transfer + encoding precisely and completely, and must also provide substantial + justification for defining and standardizing a new transfer encoding. + This specification must then be presented to the IESG for + consideration. The IESG can + + (1) reject the specification outright as being + inappropriate for standardization, + + (2) approve the formation of an IETF working group to work + on the specification in accordance with IETF + procedures, or, + + (3) accept the specification as-is and put it directly on + the standards track. + + Transfer encoding specifications on the standards track follow normal + IETF rules for standards track documents. A transfer encoding is + considered to be defined and available for use once it is on the + standards track. + +4.3. IANA Procedures for Transfer Encoding Registration + + There is no need for a special procedure for registering Transfer + Encodings with the IANA. All legitimate transfer encoding + registrations must appear as a standards-track RFC, so it is the + IESG's responsibility to notify the IANA when a new transfer encoding + has been approved. + +4.4. Location of Registered Transfer Encodings List + + Transfer encoding registrations will be posted in the anonymous FTP + directory "ftp://ftp.isi.edu/in-notes/iana/assignments/transfer- + encodings/" and all registered transfer encodings will be listed in + the periodically issued "Assigned Numbers" RFC [currently RFC-1700]. + + + + + + + + + + + + + +Freed, et. al. Best Current Practice [Page 19] + +RFC 2048 MIME Registration Procedures November 1996 + + +5. Authors' Addresses + + For more information, the authors of this document are best + contacted via Internet mail: + + Ned Freed + Innosoft International, Inc. + 1050 East Garvey Avenue South + West Covina, CA 91790 + USA + + Phone: +1 818 919 3600 + Fax: +1 818 919 3614 + EMail: ned@innosoft.com + + + John Klensin + MCI + 2100 Reston Parkway + Reston, VA 22091 + + Phone: +1 703 715-7361 + Fax: +1 703 715-7436 + EMail: klensin@mci.net + + + Jon Postel + USC/Information Sciences Institute + 4676 Admiralty Way + Marina del Rey, CA 90292 + USA + + + Phone: +1 310 822 1511 + Fax: +1 310 823 6714 + EMail: Postel@ISI.EDU + + + + + + + + + + + + + + + +Freed, et. al. Best Current Practice [Page 20] + +RFC 2048 MIME Registration Procedures November 1996 + + +Appendix A -- Grandfathered Media Types + + A number of media types, registered prior to 1996, would, if + registered under the guidelines in this document, be placed into + either the vendor or personal trees. Reregistration of those types + to reflect the appropriate trees is encouraged, but not required. + Ownership and change control principles outlined in this document + apply to those types as if they had been registered in the trees + described above. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Freed, et. al. Best Current Practice [Page 21] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2049.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2049.txt new file mode 100644 index 0000000..99f174b --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2049.txt @@ -0,0 +1,1347 @@ + + + + + + +Network Working Group N. Freed +Request for Comments: 2049 Innosoft +Obsoletes: 1521, 1522, 1590 N. Borenstein +Category: Standards Track First Virtual + November 1996 + + + Multipurpose Internet Mail Extensions + (MIME) Part Five: + Conformance Criteria and Examples + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + STD 11, RFC 822, defines a message representation protocol specifying + considerable detail about US-ASCII message headers, and leaves the + message content, or message body, as flat US-ASCII text. This set of + documents, collectively called the Multipurpose Internet Mail + Extensions, or MIME, redefines the format of messages to allow for + + (1) textual message bodies in character sets other than + US-ASCII, + + (2) an extensible set of different formats for non-textual + message bodies, + + (3) multi-part message bodies, and + + (4) textual header information in character sets other than + US-ASCII. + + These documents are based on earlier work documented in RFC 934, STD + 11, and RFC 1049, but extends and revises them. Because RFC 822 said + so little about message bodies, these documents are largely + orthogonal to (rather than a revision of) RFC 822. + + The initial document in this set, RFC 2045, specifies the various + headers used to describe the structure of MIME messages. The second + document defines the general structure of the MIME media typing + system and defines an initial set of media types. The third + document, RFC 2047, describes extensions to RFC 822 to allow non-US- + + + +Freed & Borenstein Standards Track [Page 1] + +RFC 2049 MIME Conformance November 1996 + + + ASCII text data in Internet mail header fields. The fourth document, + RFC 2048, specifies various IANA registration procedures for MIME- + related facilities. This fifth and final document describes MIME + conformance criteria as well as providing some illustrative examples + of MIME message formats, acknowledgements, and the bibliography. + + These documents are revisions of RFCs 1521, 1522, and 1590, which + themselves were revisions of RFCs 1341 and 1342. Appendix B of this + document describes differences and changes from previous versions. + +Table of Contents + + 1. Introduction .......................................... 2 + 2. MIME Conformance ...................................... 2 + 3. Guidelines for Sending Email Data ..................... 6 + 4. Canonical Encoding Model .............................. 9 + 5. Summary ............................................... 12 + 6. Security Considerations ............................... 12 + 7. Authors' Addresses .................................... 12 + 8. Acknowledgements ...................................... 13 + A. A Complex Multipart Example ........................... 15 + B. Changes from RFC 1521, 1522, and 1590 ................. 16 + C. References ............................................ 20 + +1. Introduction + + The first and second documents in this set define MIME header fields + and the initial set of MIME media types. The third document + describes extensions to RFC822 formats to allow for character sets + other than US-ASCII. This document describes what portions of MIME + must be supported by a conformant MIME implementation. It also + describes various pitfalls of contemporary messaging systems as well + as the canonical encoding model MIME is based on. + +2. MIME Conformance + + The mechanisms described in these documents are open-ended. It is + definitely not expected that all implementations will support all + available media types, nor that they will all share the same + extensions. In order to promote interoperability, however, it is + useful to define the concept of "MIME-conformance" to define a + certain level of implementation that allows the useful interworking + of messages with content that differs from US-ASCII text. In this + section, we specify the requirements for such conformance. + + + + + + + +Freed & Borenstein Standards Track [Page 2] + +RFC 2049 MIME Conformance November 1996 + + + A mail user agent that is MIME-conformant MUST: + + (1) Always generate a "MIME-Version: 1.0" header field in + any message it creates. + + (2) Recognize the Content-Transfer-Encoding header field + and decode all received data encoded by either quoted- + printable or base64 implementations. The identity + transformations 7bit, 8bit, and binary must also be + recognized. + + Any non-7bit data that is sent without encoding must be + properly labelled with a content-transfer-encoding of + 8bit or binary, as appropriate. If the underlying + transport does not support 8bit or binary (as SMTP + [RFC-821] does not), the sender is required to both + encode and label data using an appropriate Content- + Transfer-Encoding such as quoted-printable or base64. + + (3) Must treat any unrecognized Content-Transfer-Encoding + as if it had a Content-Type of "application/octet- + stream", regardless of whether or not the actual + Content-Type is recognized. + + (4) Recognize and interpret the Content-Type header field, + and avoid showing users raw data with a Content-Type + field other than text. Implementations must be able + to send at least text/plain messages, with the + character set specified with the charset parameter if + it is not US-ASCII. + + (5) Ignore any content type parameters whose names they do + not recognize. + + (6) Explicitly handle the following media type values, to + at least the following extents: + + Text: + + -- Recognize and display "text" mail with the + character set "US-ASCII." + + -- Recognize other character sets at least to the + extent of being able to inform the user about what + character set the message uses. + + + + + + +Freed & Borenstein Standards Track [Page 3] + +RFC 2049 MIME Conformance November 1996 + + + -- Recognize the "ISO-8859-*" character sets to the + extent of being able to display those characters that + are common to ISO-8859-* and US-ASCII, namely all + characters represented by octet values 1-127. + + -- For unrecognized subtypes in a known character + set, show or offer to show the user the "raw" version + of the data after conversion of the content from + canonical form to local form. + + -- Treat material in an unknown character set as if + it were "application/octet-stream". + + Image, audio, and video: + + -- At a minumum provide facilities to treat any + unrecognized subtypes as if they were + "application/octet-stream". + + Application: + + -- Offer the ability to remove either of the quoted- + printable or base64 encodings defined in this + document if they were used and put the resulting + information in a user file. + + Multipart: + + -- Recognize the mixed subtype. Display all relevant + information on the message level and the body part + header level and then display or offer to display + each of the body parts individually. + + -- Recognize the "alternative" subtype, and avoid + showing the user redundant parts of + multipart/alternative mail. + + -- Recognize the "multipart/digest" subtype, + specifically using "message/rfc822" rather than + "text/plain" as the default media type for body parts + inside "multipart/digest" entities. + + -- Treat any unrecognized subtypes as if they were + "mixed". + + + + + + + +Freed & Borenstein Standards Track [Page 4] + +RFC 2049 MIME Conformance November 1996 + + + Message: + + -- Recognize and display at least the RFC822 message + encapsulation (message/rfc822) in such a way as to + preserve any recursive structure, that is, displaying + or offering to display the encapsulated data in + accordance with its media type. + + -- Treat any unrecognized subtypes as if they were + "application/octet-stream". + + (7) Upon encountering any unrecognized Content-Type field, + an implementation must treat it as if it had a media + type of "application/octet-stream" with no parameter + sub-arguments. How such data are handled is up to an + implementation, but likely options for handling such + unrecognized data include offering the user to write it + into a file (decoded from its mail transport format) or + offering the user to name a program to which the + decoded data should be passed as input. + + (8) Conformant user agents are required, if they provide + non-standard support for non-MIME messages employing + character sets other than US-ASCII, to do so on + received messages only. Conforming user agents must not + send non-MIME messages containing anything other than + US-ASCII text. + + In particular, the use of non-US-ASCII text in mail + messages without a MIME-Version field is strongly + discouraged as it impedes interoperability when sending + messages between regions with different localization + conventions. Conforming user agents MUST include proper + MIME labelling when sending anything other than plain + text in the US-ASCII character set. + + In addition, non-MIME user agents should be upgraded if + at all possible to include appropriate MIME header + information in the messages they send even if nothing + else in MIME is supported. This upgrade will have + little, if any, effect on non-MIME recipients and will + aid MIME in correctly displaying such messages. It + also provides a smooth transition path to eventual + adoption of other MIME capabilities. + + (9) Conforming user agents must ensure that any string of + non-white-space printable US-ASCII characters within a + "*text" or "*ctext" that begins with "=?" and ends with + + + +Freed & Borenstein Standards Track [Page 5] + +RFC 2049 MIME Conformance November 1996 + + + "?=" be a valid encoded-word. ("begins" means: At the + start of the field-body or immediately following + linear-white-space; "ends" means: At the end of the + field-body or immediately preceding linear-white- + space.) In addition, any "word" within a "phrase" that + begins with "=?" and ends with "?=" must be a valid + encoded-word. + + (10) Conforming user agents must be able to distinguish + encoded-words from "text", "ctext", or "word"s, + according to the rules in section 4, anytime they + appear in appropriate places in message headers. It + must support both the "B" and "Q" encodings for any + character set which it supports. The program must be + able to display the unencoded text if the character set + is "US-ASCII". For the ISO-8859-* character sets, the + mail reading program must at least be able to display + the characters which are also in the US-ASCII set. + + A user agent that meets the above conditions is said to be MIME- + conformant. The meaning of this phrase is that it is assumed to be + "safe" to send virtually any kind of properly-marked data to users of + such mail systems, because such systems will at least be able to + treat the data as undifferentiated binary, and will not simply splash + it onto the screen of unsuspecting users. + + There is another sense in which it is always "safe" to send data in a + format that is MIME-conformant, which is that such data will not + break or be broken by any known systems that are conformant with RFC + 821 and RFC 822. User agents that are MIME-conformant have the + additional guarantee that the user will not be shown data that were + never intended to be viewed as text. + +3. Guidelines for Sending Email Data + + Internet email is not a perfect, homogeneous system. Mail may become + corrupted at several stages in its travel to a final destination. + Specifically, email sent throughout the Internet may travel across + many networking technologies. Many networking and mail technologies + do not support the full functionality possible in the SMTP transport + environment. Mail traversing these systems is likely to be modified + in order that it can be transported. + + There exist many widely-deployed non-conformant MTAs in the Internet. + These MTAs, speaking the SMTP protocol, alter messages on the fly to + take advantage of the internal data structure of the hosts they are + implemented on, or are just plain broken. + + + + +Freed & Borenstein Standards Track [Page 6] + +RFC 2049 MIME Conformance November 1996 + + + The following guidelines may be useful to anyone devising a data + format (media type) that is supposed to survive the widest range of + networking technologies and known broken MTAs unscathed. Note that + anything encoded in the base64 encoding will satisfy these rules, but + that some well-known mechanisms, notably the UNIX uuencode facility, + will not. Note also that anything encoded in the Quoted-Printable + encoding will survive most gateways intact, but possibly not some + gateways to systems that use the EBCDIC character set. + + (1) Under some circumstances the encoding used for data may + change as part of normal gateway or user agent + operation. In particular, conversion from base64 to + quoted-printable and vice versa may be necessary. This + may result in the confusion of CRLF sequences with line + breaks in text bodies. As such, the persistence of + CRLF as something other than a line break must not be + relied on. + + (2) Many systems may elect to represent and store text data + using local newline conventions. Local newline + conventions may not match the RFC822 CRLF convention -- + systems are known that use plain CR, plain LF, CRLF, or + counted records. The result is that isolated CR and LF + characters are not well tolerated in general; they may + be lost or converted to delimiters on some systems, and + hence must not be relied on. + + (3) The transmission of NULs (US-ASCII value 0) is + problematic in Internet mail. (This is largely the + result of NULs being used as a termination character by + many of the standard runtime library routines in the C + programming language.) The practice of using NULs as + termination characters is so entrenched now that + messages should not rely on them being preserved. + + (4) TAB (HT) characters may be misinterpreted or may be + automatically converted to variable numbers of spaces. + This is unavoidable in some environments, notably those + not based on the US-ASCII character set. Such + conversion is STRONGLY DISCOURAGED, but it may occur, + and mail formats must not rely on the persistence of + TAB (HT) characters. + + (5) Lines longer than 76 characters may be wrapped or + truncated in some environments. Line wrapping or line + truncation imposed by mail transports is STRONGLY + DISCOURAGED, but unavoidable in some cases. + Applications which require long lines must somehow + + + +Freed & Borenstein Standards Track [Page 7] + +RFC 2049 MIME Conformance November 1996 + + + differentiate between soft and hard line breaks. (A + simple way to do this is to use the quoted-printable + encoding.) + + (6) Trailing "white space" characters (SPACE, TAB (HT)) on + a line may be discarded by some transport agents, while + other transport agents may pad lines with these + characters so that all lines in a mail file are of + equal length. The persistence of trailing white space, + therefore, must not be relied on. + + (7) Many mail domains use variations on the US-ASCII + character set, or use character sets such as EBCDIC + which contain most but not all of the US-ASCII + characters. The correct translation of characters not + in the "invariant" set cannot be depended on across + character converting gateways. For example, this + situation is a problem when sending uuencoded + information across BITNET, an EBCDIC system. Similar + problems can occur without crossing a gateway, since + many Internet hosts use character sets other than US- + ASCII internally. The definition of Printable Strings + in X.400 adds further restrictions in certain special + cases. In particular, the only characters that are + known to be consistent across all gateways are the 73 + characters that correspond to the upper and lower case + letters A-Z and a-z, the 10 digits 0-9, and the + following eleven special characters: + + "'" (US-ASCII decimal value 39) + "(" (US-ASCII decimal value 40) + ")" (US-ASCII decimal value 41) + "+" (US-ASCII decimal value 43) + "," (US-ASCII decimal value 44) + "-" (US-ASCII decimal value 45) + "." (US-ASCII decimal value 46) + "/" (US-ASCII decimal value 47) + ":" (US-ASCII decimal value 58) + "=" (US-ASCII decimal value 61) + "?" (US-ASCII decimal value 63) + + A maximally portable mail representation will confine + itself to relatively short lines of text in which the + only meaningful characters are taken from this set of + 73 characters. The base64 encoding follows this rule. + + (8) Some mail transport agents will corrupt data that + includes certain literal strings. In particular, a + + + +Freed & Borenstein Standards Track [Page 8] + +RFC 2049 MIME Conformance November 1996 + + + period (".") alone on a line is known to be corrupted + by some (incorrect) SMTP implementations, and a line + that starts with the five characters "From " (the fifth + character is a SPACE) are commonly corrupted as well. + A careful composition agent can prevent these + corruptions by encoding the data (e.g., in the quoted- + printable encoding using "=46rom " in place of "From " + at the start of a line, and "=2E" in place of "." alone + on a line). + + Please note that the above list is NOT a list of recommended + practices for MTAs. RFC 821 MTAs are prohibited from altering the + character of white space or wrapping long lines. These BAD and + invalid practices are known to occur on established networks, and + implementations should be robust in dealing with the bad effects they + can cause. + +4. Canonical Encoding Model + + There was some confusion, in earlier versions of these documents, + regarding the model for when email data was to be converted to + canonical form and encoded, and in particular how this process would + affect the treatment of CRLFs, given that the representation of + newlines varies greatly from system to system. For this reason, a + canonical model for encoding is presented below. + + The process of composing a MIME entity can be modeled as being done + in a number of steps. Note that these steps are roughly similar to + those steps used in PEM [RFC-1421] and are performed for each + "innermost level" body: + + (1) Creation of local form. + + The body to be transmitted is created in the system's + native format. The native character set is used and, + where appropriate, local end of line conventions are + used as well. The body may be a UNIX-style text file, + or a Sun raster image, or a VMS indexed file, or audio + data in a system-dependent format stored only in + memory, or anything else that corresponds to the local + model for the representation of some form of + information. Fundamentally, the data is created in the + "native" form that corresponds to the type specified by + the media type. + + + + + + + +Freed & Borenstein Standards Track [Page 9] + +RFC 2049 MIME Conformance November 1996 + + + (2) Conversion to canonical form. + + The entire body, including "out-of-band" information + such as record lengths and possibly file attribute + information, is converted to a universal canonical + form. The specific media type of the body as well as + its associated attributes dictate the nature of the + canonical form that is used. Conversion to the proper + canonical form may involve character set conversion, + transformation of audio data, compression, or various + other operations specific to the various media types. + If character set conversion is involved, however, care + must be taken to understand the semantics of the media + type, which may have strong implications for any + character set conversion, e.g. with regard to + syntactically meaningful characters in a text subtype + other than "plain". + + For example, in the case of text/plain data, the text + must be converted to a supported character set and + lines must be delimited with CRLF delimiters in + accordance with RFC 822. Note that the restriction on + line lengths implied by RFC 822 is eliminated if the + next step employs either quoted-printable or base64 + encoding. + + (3) Apply transfer encoding. + + A Content-Transfer-Encoding appropriate for this body + is applied. Note that there is no fixed relationship + between the media type and the transfer encoding. In + particular, it may be appropriate to base the choice of + base64 or quoted-printable on character frequency + counts which are specific to a given instance of a + body. + + (4) Insertion into entity. + + The encoded body is inserted into a MIME entity with + appropriate headers. The entity is then inserted into + the body of a higher-level entity (message or + multipart) as needed. + + Conversion from entity form to local form is accomplished by + reversing these steps. Note that reversal of these steps may produce + differing results since there is no guarantee that the original and + final local forms are the same. + + + + +Freed & Borenstein Standards Track [Page 10] + +RFC 2049 MIME Conformance November 1996 + + + It is vital to note that these steps are only a model; they are + specifically NOT a blueprint for how an actual system would be built. + In particular, the model fails to account for two common designs: + + (1) In many cases the conversion to a canonical form prior + to encoding will be subsumed into the encoder itself, + which understands local formats directly. For example, + the local newline convention for text bodies might be + carried through to the encoder itself along with + knowledge of what that format is. + + (2) The output of the encoders may have to pass through one + or more additional steps prior to being transmitted as + a message. As such, the output of the encoder may not + be conformant with the formats specified by RFC 822. + In particular, once again it may be appropriate for the + converter's output to be expressed using local newline + conventions rather than using the standard RFC 822 CRLF + delimiters. + + Other implementation variations are conceivable as well. The vital + aspect of this discussion is that, in spite of any optimizations, + collapsings of required steps, or insertion of additional processing, + the resulting messages must be consistent with those produced by the + model described here. For example, a message with the following + header fields: + + Content-type: text/foo; charset=bar + Content-Transfer-Encoding: base64 + + must be first represented in the text/foo form, then (if necessary) + represented in the "bar" character set, and finally transformed via + the base64 algorithm into a mail-safe form. + + NOTE: Some confusion has been caused by systems that represent + messages in a format which uses local newline conventions which + differ from the RFC822 CRLF convention. It is important to note that + these formats are not canonical RFC822/MIME. These formats are + instead *encodings* of RFC822, where CRLF sequences in the canonical + representation of the message are encoded as the local newline + convention. Note that formats which encode CRLF sequences as, for + example, LF are not capable of representing MIME messages containing + binary data which contains LF octets not part of CRLF line separation + sequences. + + + + + + + +Freed & Borenstein Standards Track [Page 11] + +RFC 2049 MIME Conformance November 1996 + + +5. Summary + + This document defines what is meant by MIME Conformance. It also + details various problems known to exist in the Internet email system + and how to use MIME to overcome them. Finally, it describes MIME's + canonical encoding model. + +6. Security Considerations + + Security issues are discussed in the second document in this set, RFC + 2046. + +7. Authors' Addresses + + For more information, the authors of this document are best contacted + via Internet mail: + + Ned Freed + Innosoft International, Inc. + 1050 East Garvey Avenue South + West Covina, CA 91790 + USA + + Phone: +1 818 919 3600 + Fax: +1 818 919 3614 + EMail: ned@innosoft.com + + Nathaniel S. Borenstein + First Virtual Holdings + 25 Washington Avenue + Morristown, NJ 07960 + USA + + Phone: +1 201 540 8967 + Fax: +1 201 993 3032 + EMail: nsb@nsb.fv.com + + MIME is a result of the work of the Internet Engineering Task Force + Working Group on RFC 822 Extensions. The chairman of that group, + Greg Vaudreuil, may be reached at: + + Gregory M. Vaudreuil + Octel Network Services + 17080 Dallas Parkway + Dallas, TX 75248-1905 + USA + + EMail: Greg.Vaudreuil@Octel.Com + + + +Freed & Borenstein Standards Track [Page 12] + +RFC 2049 MIME Conformance November 1996 + + +8. Acknowledgements + + This document is the result of the collective effort of a large + number of people, at several IETF meetings, on the IETF-SMTP and + IETF-822 mailing lists, and elsewhere. Although any enumeration + seems doomed to suffer from egregious omissions, the following are + among the many contributors to this effort: + + Harald Tveit Alvestrand Marc Andreessen + Randall Atkinson Bob Braden + Philippe Brandon Brian Capouch + Kevin Carosso Uhhyung Choi + Peter Clitherow Dave Collier-Brown + Cristian Constantinof John Coonrod + Mark Crispin Dave Crocker + Stephen Crocker Terry Crowley + Walt Daniels Jim Davis + Frank Dawson Axel Deininger + Hitoshi Doi Kevin Donnelly + Steve Dorner Keith Edwards + Chris Eich Dana S. Emery + Johnny Eriksson Craig Everhart + Patrik Faltstrom Erik E. Fair + Roger Fajman Alain Fontaine + Martin Forssen James M. Galvin + Stephen Gildea Philip Gladstone + Thomas Gordon Keld Simonsen + Terry Gray Phill Gross + James Hamilton David Herron + Mark Horton Bruce Howard + Bill Janssen Olle Jarnefors + Risto Kankkunen Phil Karn + Alan Katz Tim Kehres + Neil Katin Steve Kille + Kyuho Kim Anders Klemets + John Klensin Valdis Kletniek + Jim Knowles Stev Knowles + Bob Kummerfeld Pekka Kytolaakso + Stellan Lagerstrom Vincent Lau + Timo Lehtinen Donald Lindsay + Warner Losh Carlyn Lowery + Laurence Lundblade Charles Lynn + John R. MacMillan Larry Masinter + Rick McGowan Michael J. McInerny + Leo Mclaughlin Goli Montaser-Kohsari + Tom Moore John Gardiner Myers + Erik Naggum Mark Needleman + Chris Newman John Noerenberg + + + +Freed & Borenstein Standards Track [Page 13] + +RFC 2049 MIME Conformance November 1996 + + + Mats Ohrman Julian Onions + Michael Patton David J. Pepper + Erik van der Poel Blake C. Ramsdell + Christer Romson Luc Rooijakkers + Marshall T. Rose Jonathan Rosenberg + Guido van Rossum Jan Rynning + Harri Salminen Michael Sanderson + Yutaka Sato Markku Savela + Richard Alan Schafer Masahiro Sekiguchi + Mark Sherman Bob Smart + Peter Speck Henry Spencer + Einar Stefferud Michael Stein + Klaus Steinberger Peter Svanberg + James Thompson Steve Uhler + Stuart Vance Peter Vanderbilt + Greg Vaudreuil Ed Vielmetti + Larry W. Virden Ryan Waldron + Rhys Weatherly Jay Weber + Dave Wecker Wally Wedel + Sven-Ove Westberg Brian Wideen + John Wobus Glenn Wright + Rayan Zachariassen David Zimmerman + + The authors apologize for any omissions from this list, which are + certainly unintentional. + + + + + + + + + + + + + + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 14] + +RFC 2049 MIME Conformance November 1996 + + +Appendix A -- A Complex Multipart Example + + What follows is the outline of a complex multipart message. This + message contains five parts that are to be displayed serially: two + introductory plain text objects, an embedded multipart message, a + text/enriched object, and a closing encapsulated text message in a + non-ASCII character set. The embedded multipart message itself + contains two objects to be displayed in parallel, a picture and an + audio fragment. + + MIME-Version: 1.0 + From: Nathaniel Borenstein + To: Ned Freed + Date: Fri, 07 Oct 1994 16:15:05 -0700 (PDT) + Subject: A multipart example + Content-Type: multipart/mixed; + boundary=unique-boundary-1 + + This is the preamble area of a multipart message. + Mail readers that understand multipart format + should ignore this preamble. + + If you are reading this text, you might want to + consider changing to a mail reader that understands + how to properly display multipart messages. + + --unique-boundary-1 + + ... Some text appears here ... + + [Note that the blank between the boundary and the start + of the text in this part means no header fields were + given and this is text in the US-ASCII character set. + It could have been done with explicit typing as in the + next part.] + + --unique-boundary-1 + Content-type: text/plain; charset=US-ASCII + + This could have been part of the previous part, but + illustrates explicit versus implicit typing of body + parts. + + --unique-boundary-1 + Content-Type: multipart/parallel; boundary=unique-boundary-2 + + --unique-boundary-2 + Content-Type: audio/basic + + + +Freed & Borenstein Standards Track [Page 15] + +RFC 2049 MIME Conformance November 1996 + + + Content-Transfer-Encoding: base64 + + ... base64-encoded 8000 Hz single-channel + mu-law-format audio data goes here ... + + --unique-boundary-2 + Content-Type: image/jpeg + Content-Transfer-Encoding: base64 + + ... base64-encoded image data goes here ... + + --unique-boundary-2-- + + --unique-boundary-1 + Content-type: text/enriched + + This is enriched. + as defined in RFC 1896 + + Isn't it + cool? + + --unique-boundary-1 + Content-Type: message/rfc822 + + From: (mailbox in US-ASCII) + To: (address in US-ASCII) + Subject: (subject in US-ASCII) + Content-Type: Text/plain; charset=ISO-8859-1 + Content-Transfer-Encoding: Quoted-printable + + ... Additional text in ISO-8859-1 goes here ... + + --unique-boundary-1-- + +Appendix B -- Changes from RFC 1521, 1522, and 1590 + + These documents are a revision of RFC 1521, 1522, and 1590. For the + convenience of those familiar with the earlier documents, the changes + from those documents are summarized in this appendix. For further + history, note that Appendix H in RFC 1521 specified how that document + differed from its predecessor, RFC 1341. + + (1) This document has been completely reformatted and split + into multiple documents. This was done to improve the + quality of the plain text version of this document, + which is required to be the reference copy. + + + + +Freed & Borenstein Standards Track [Page 16] + +RFC 2049 MIME Conformance November 1996 + + + (2) BNF describing the overall structure of MIME object + headers has been added. This is a documentation change + only -- the underlying syntax has not changed in any + way. + + (3) The specific BNF for the seven media types in MIME has + been removed. This BNF was incorrect, incomplete, amd + inconsistent with the type-indendependent BNF. And + since the type-independent BNF already fully specifies + the syntax of the various MIME headers, the type- + specific BNF was, in the final analysis, completely + unnecessary and caused more problems than it solved. + + (4) The more specific "US-ASCII" character set name has + replaced the use of the informal term ASCII in many + parts of these documents. + + (5) The informal concept of a primary subtype has been + removed. + + (6) The term "object" was being used inconsistently. The + definition of this term has been clarified, along with + the related terms "body", "body part", and "entity", + and usage has been corrected where appropriate. + + (7) The BNF for the multipart media type has been + rearranged to make it clear that the CRLF preceeding + the boundary marker is actually part of the marker + itself rather than the preceeding body part. + + (8) The prose and BNF describing the multipart media type + have been changed to make it clear that the body parts + within a multipart object MUST NOT contain any lines + beginning with the boundary parameter string. + + (9) In the rules on reassembling "message/partial" MIME + entities, "Subject" is added to the list of headers to + take from the inner message, and the example is + modified to clarify this point. + + (10) "Message/partial" fragmenters are restricted to + splitting MIME objects only at line boundaries. + + (11) In the discussion of the application/postscript type, + an additional paragraph has been added warning about + possible interoperability problems caused by embedding + of binary data inside a PostScript MIME entity. + + + + +Freed & Borenstein Standards Track [Page 17] + +RFC 2049 MIME Conformance November 1996 + + + (12) Added a clarifying note to the basic syntax rules for + the Content-Type header field to make it clear that the + following two forms: + + Content-type: text/plain; charset=us-ascii (comment) + + Content-type: text/plain; charset="us-ascii" + + are completely equivalent. + + (13) The following sentence has been removed from the + discussion of the MIME-Version header: "However, + conformant software is encouraged to check the version + number and at least warn the user if an unrecognized + MIME-version is encountered." + + (14) A typo was fixed that said "application/external-body" + instead of "message/external-body". + + (15) The definition of a character set has been reorganized + to make the requirements clearer. + + (16) The definition of the "image/gif" media type has been + moved to a separate document. This change was made + because of potential conflicts with IETF rules + governing the standardization of patented technology. + + (17) The definitions of "7bit" and "8bit" have been + tightened so that use of bare CR, LF can only be used + as end-of-line sequences. The document also no longer + requires that NUL characters be preserved, which brings + MIME into alignment with real-world implementations. + + (18) The definition of canonical text in MIME has been + tightened so that line breaks must be represented by a + CRLF sequence. CR and LF characters are not allowed + outside of this usage. The definition of quoted- + printable encoding has been altered accordingly. + + (19) The definition of the quoted-printable encoding now + includes a number of suggestions for how quoted- + printable encoders might best handle improperly encoded + material. + + (20) Prose was added to clarify the use of the "7bit", + "8bit", and "binary" transfer-encodings on multipart or + message entities encapsulating "8bit" or "binary" data. + + + + +Freed & Borenstein Standards Track [Page 18] + +RFC 2049 MIME Conformance November 1996 + + + (21) In the section on MIME Conformance, "multipart/digest" + support was added to the list of requirements for + minimal MIME conformance. Also, the requirement for + "message/rfc822" support were strengthened to clarify + the importance of recognizing recursive structure. + + (22) The various restrictions on subtypes of "message" are + now specified entirely on a subtype by subtype basis. + + (23) The definition of "message/rfc822" was changed to + indicate that at least one of the "From", "Subject", or + "Date" headers must be present. + + (24) The required handling of unrecognized subtypes as + "application/octet-stream" has been made more explicit + in both the type definitions sections and the + conformance guidelines. + + (25) Examples using text/richtext were changed to + text/enriched. + + (26) The BNF definition of subtype has been changed to make + it clear that either an IANA registered subtype or a + nonstandard "X-" subtype must be used in a Content-Type + header field. + + (27) MIME media types that are simply registered for use and + those that are standardized by the IETF are now + distinguished in the MIME BNF. + + (28) All of the various MIME registration procedures have + been extensively revised. IANA registration procedures + for character sets have been moved to a separate + document that is no included in this set of documents. + + (29) The use of escape and shift mechanisms in the US-ASCII + and ISO-8859-X character sets these documents define + have been clarified: Such mechanisms should never be + used in conjunction with these character sets and their + effect if they are used is undefined. + + (30) The definition of the AFS access-type for + message/external-body has been removed. + + (31) The handling of the combination of + multipart/alternative and message/external-body is now + specifically addressed. + + + + +Freed & Borenstein Standards Track [Page 19] + +RFC 2049 MIME Conformance November 1996 + + + (32) Security issues specific to message/external-body are + now discussed in some detail. + +Appendix C -- References + + [ATK] + Borenstein, Nathaniel S., Multimedia Applications + Development with the Andrew Toolkit, Prentice-Hall, 1990. + + [ISO-2022] + International Standard -- Information Processing -- + Character Code Structure and Extension Techniques, + ISO/IEC 2022:1994, 4th ed. + + [ISO-8859] + International Standard -- Information Processing -- 8-bit + Single-Byte Coded Graphic Character Sets + - Part 1: Latin Alphabet No. 1, ISO 8859-1:1987, 1st ed. + - Part 2: Latin Alphabet No. 2, ISO 8859-2:1987, 1st ed. + - Part 3: Latin Alphabet No. 3, ISO 8859-3:1988, 1st ed. + - Part 4: Latin Alphabet No. 4, ISO 8859-4:1988, 1st ed. + - Part 5: Latin/Cyrillic Alphabet, ISO 8859-5:1988, 1st + ed. + - Part 6: Latin/Arabic Alphabet, ISO 8859-6:1987, 1st ed. + - Part 7: Latin/Greek Alphabet, ISO 8859-7:1987, 1st ed. + - Part 8: Latin/Hebrew Alphabet, ISO 8859-8:1988, 1st ed. + - Part 9: Latin Alphabet No. 5, ISO/IEC 8859-9:1989, 1st + ed. + International Standard -- Information Technology -- 8-bit + Single-Byte Coded Graphic Character Sets + - Part 10: Latin Alphabet No. 6, ISO/IEC 8859-10:1992, + 1st ed. + + [ISO-646] + International Standard -- Information Technology -- ISO + 7-bit Coded Character Set for Information Interchange, + ISO 646:1991, 3rd ed.. + + [JPEG] + JPEG Draft Standard ISO 10918-1 CD. + + [MPEG] + Video Coding Draft Standard ISO 11172 CD, ISO + IEC/JTC1/SC2/WG11 (Motion Picture Experts Group), May, + 1991. + + + + + + +Freed & Borenstein Standards Track [Page 20] + +RFC 2049 MIME Conformance November 1996 + + + [PCM] + CCITT, Fascicle III.4 - Recommendation G.711, "Pulse Code + Modulation (PCM) of Voice Frequencies", Geneva, 1972. + + [POSTSCRIPT] + Adobe Systems, Inc., PostScript Language Reference + Manual, Addison-Wesley, 1985. + + [POSTSCRIPT2] + Adobe Systems, Inc., PostScript Language Reference + Manual, Addison-Wesley, Second Ed., 1990. + + [RFC-783] + Sollins, K.R., "TFTP Protocol (revision 2)", RFC-783, + MIT, June 1981. + + [RFC-821] + Postel, J.B., "Simple Mail Transfer Protocol", STD 10, + RFC 821, USC/Information Sciences Institute, August 1982. + + [RFC-822] + Crocker, D., "Standard for the Format of ARPA Internet + Text Messages", STD 11, RFC 822, UDEL, August 1982. + + [RFC-934] + Rose, M. and E. Stefferud, "Proposed Standard for Message + Encapsulation", RFC 934, Delaware and NMA, January 1985. + + [RFC-959] + Postel, J. and J. Reynolds, "File Transfer Protocol", STD + 9, RFC 959, USC/Information Sciences Institute, October + 1985. + + [RFC-1049] + Sirbu, M., "Content-Type Header Field for Internet + Messages", RFC 1049, CMU, March 1988. + + [RFC-1154] + Robinson, D., and R. Ullmann, "Encoding Header Field for + Internet Messages", RFC 1154, Prime Computer, Inc., April + 1990. + + [RFC-1341] + Borenstein, N., and N. Freed, "MIME (Multipurpose + Internet Mail Extensions): Mechanisms for Specifying and + Describing the Format of Internet Message Bodies", RFC + 1341, Bellcore, Innosoft, June 1992. + + + + +Freed & Borenstein Standards Track [Page 21] + +RFC 2049 MIME Conformance November 1996 + + + [RFC-1342] + Moore, K., "Representation of Non-Ascii Text in Internet + Message Headers", RFC 1342, University of Tennessee, June + 1992. + + [RFC-1344] + Borenstein, N., "Implications of MIME for Internet Mail + Gateways", RFC 1344, Bellcore, June 1992. + + [RFC-1345] + Simonsen, K., "Character Mnemonics & Character Sets", RFC + 1345, Rationel Almen Planlaegning, June 1992. + + [RFC-1421] + Linn, J., "Privacy Enhancement for Internet Electronic + Mail: Part I -- Message Encryption and Authentication + Procedures", RFC 1421, IAB IRTF PSRG, IETF PEM WG, + February 1993. + + [RFC-1422] + Kent, S., "Privacy Enhancement for Internet Electronic + Mail: Part II -- Certificate-Based Key Management", RFC + 1422, IAB IRTF PSRG, IETF PEM WG, February 1993. + + [RFC-1423] + Balenson, D., "Privacy Enhancement for Internet + Electronic Mail: Part III -- Algorithms, Modes, and + Identifiers", IAB IRTF PSRG, IETF PEM WG, February 1993. + + [RFC-1424] + Kaliski, B., "Privacy Enhancement for Internet Electronic + Mail: Part IV -- Key Certification and Related + Services", IAB IRTF PSRG, IETF PEM WG, February 1993. + + [RFC-1521] + Borenstein, N., and Freed, N., "MIME (Multipurpose + Internet Mail Extensions): Mechanisms for Specifying and + Describing the Format of Internet Message Bodies", RFC + 1521, Bellcore, Innosoft, September, 1993. + + [RFC-1522] + Moore, K., "Representation of Non-ASCII Text in Internet + Message Headers", RFC 1522, University of Tennessee, + September 1993. + + + + + + + +Freed & Borenstein Standards Track [Page 22] + +RFC 2049 MIME Conformance November 1996 + + + [RFC-1524] + Borenstein, N., "A User Agent Configuration Mechanism for + Multimedia Mail Format Information", RFC 1524, Bellcore, + September 1993. + + [RFC-1543] + Postel, J., "Instructions to RFC Authors", RFC 1543, + USC/Information Sciences Institute, October 1993. + + [RFC-1556] + Nussbacher, H., "Handling of Bi-directional Texts in + MIME", RFC 1556, Israeli Inter-University Computer + Center, December 1993. + + [RFC-1590] + Postel, J., "Media Type Registration Procedure", RFC + 1590, USC/Information Sciences Institute, March 1994. + + [RFC-1602] + Internet Architecture Board, Internet Engineering + Steering Group, Huitema, C., Gross, P., "The Internet + Standards Process -- Revision 2", March 1994. + + [RFC-1652] + Klensin, J., (WG Chair), Freed, N., (Editor), Rose, M., + Stefferud, E., and Crocker, D., "SMTP Service Extension + for 8bit-MIME transport", RFC 1652, United Nations + University, Innosoft, Dover Beach Consulting, Inc., + Network Management Associates, Inc., The Branch Office, + March 1994. + + [RFC-1700] + Reynolds, J. and J. Postel, "Assigned Numbers", STD 2, + RFC 1700, USC/Information Sciences Institute, October + 1994. + + [RFC-1741] + Faltstrom, P., Crocker, D., and Fair, E., "MIME Content + Type for BinHex Encoded Files", December 1994. + + [RFC-1896] + Resnick, P., and A. Walker, "The text/enriched MIME + Content-type", RFC 1896, February, 1996. + + + + + + + + +Freed & Borenstein Standards Track [Page 23] + +RFC 2049 MIME Conformance November 1996 + + + [RFC-2045] + Freed, N., and and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part One: Format of Internet Message + Bodies", RFC 2045, Innosoft, First Virtual Holdings, + November 1996. + + [RFC-2046] + Freed, N., and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part Two: Media Types", RFC 2046, + Innosoft, First Virtual Holdings, November 1996. + + [RFC-2047] + Moore, K., "Multipurpose Internet Mail Extensions (MIME) + Part Three: Representation of Non-ASCII Text in Internet + Message Headers", RFC 2047, University of + Tennessee, November 1996. + + [RFC-2048] + Freed, N., Klensin, J., and J. Postel, "Multipurpose + Internet Mail Extensions (MIME) Part Four: MIME + Registration Procedures", RFC 2048, Innosoft, MCI, + ISI, November 1996. + + [RFC-2049] + Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part Five: Conformance Criteria and + Examples", RFC 2049 (this document), Innosoft, First + Virtual Holdings, November 1996. + + [US-ASCII] + Coded Character Set -- 7-Bit American Standard Code for + Information Interchange, ANSI X3.4-1986. + + [X400] + Schicker, Pietro, "Message Handling Systems, X.400", + Message Handling Systems and Distributed Applications, E. + Stefferud, O-j. Jacobsen, and P. Schicker, eds., North- + Holland, 1989, pp. 3-41. + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 24] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2183.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2183.txt new file mode 100644 index 0000000..f16f127 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2183.txt @@ -0,0 +1,675 @@ + + + + + + +Network Working Group R. Troost +Request for Comments: 2183 New Century Systems +Updates: 1806 S. Dorner +Category: Standards Track QUALCOMM Incorporated + K. Moore, Editor + University of Tennessee + August 1997 + + + Communicating Presentation Information in + Internet Messages: + The Content-Disposition Header Field + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + This memo provides a mechanism whereby messages conforming to the + MIME specifications [RFC 2045, RFC 2046, RFC 2047, RFC 2048, RFC + 2049] can convey presentational information. It specifies the + "Content-Disposition" header field, which is optional and valid for + any MIME entity ("message" or "body part"). Two values for this + header field are described in this memo; one for the ordinary linear + presentation of the body part, and another to facilitate the use of + mail to transfer files. It is expected that more values will be + defined in the future, and procedures are defined for extending this + set of values. + + This document is intended as an extension to MIME. As such, the + reader is assumed to be familiar with the MIME specifications, and + [RFC 822]. The information presented herein supplements but does not + replace that found in those documents. + + This document is a revision to the Experimental protocol defined in + RFC 1806. As compared to RFC 1806, this document contains minor + editorial updates, adds new parameters needed to support the File + Transfer Body Part, and references a separate specification for the + handling of non-ASCII and/or very long parameter values. + + + + + + + +Troost, et. al. Standards Track [Page 1] + +RFC 2183 Content-Disposition August 1997 + + +1. Introduction + + MIME specifies a standard format for encapsulating multiple pieces of + data into a single Internet message. That document does not address + the issue of presentation styles; it provides a framework for the + interchange of message content, but leaves presentation issues solely + in the hands of mail user agent (MUA) implementors. + + Two common ways of presenting multipart electronic messages are as a + main document with a list of separate attachments, and as a single + document with the various parts expanded (displayed) inline. The + display of an attachment is generally construed to require positive + action on the part of the recipient, while inline message components + are displayed automatically when the message is viewed. A mechanism + is needed to allow the sender to transmit this sort of presentational + information to the recipient; the Content-Disposition header provides + this mechanism, allowing each component of a message to be tagged + with an indication of its desired presentation semantics. + + Tagging messages in this manner will often be sufficient for basic + message formatting. However, in many cases a more powerful and + flexible approach will be necessary. The definition of such + approaches is beyond the scope of this memo; however, such approaches + can benefit from additional Content-Disposition values and + parameters, to be defined at a later date. + + In addition to allowing the sender to specify the presentational + disposition of a message component, it is desirable to allow her to + indicate a default archival disposition; a filename. The optional + "filename" parameter provides for this. Further, the creation-date, + modification-date, and read-date parameters allow preservation of + those file attributes when the file is transmitted over MIME email. + + NB: The keywords MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, + SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL, when they appear in this + document, are to be interpreted as described in [RFC 2119]. + +2. The Content-Disposition Header Field + + Content-Disposition is an optional header field. In its absence, the + MUA may use whatever presentation method it deems suitable. + + It is desirable to keep the set of possible disposition types small + and well defined, to avoid needless complexity. Even so, evolving + usage will likely require the definition of additional disposition + types or parameters, so the set of disposition values is extensible; + see below. + + + + +Troost, et. al. Standards Track [Page 2] + +RFC 2183 Content-Disposition August 1997 + + + In the extended BNF notation of [RFC 822], the Content-Disposition + header field is defined as follows: + + disposition := "Content-Disposition" ":" + disposition-type + *(";" disposition-parm) + + disposition-type := "inline" + / "attachment" + / extension-token + ; values are not case-sensitive + + disposition-parm := filename-parm + / creation-date-parm + / modification-date-parm + / read-date-parm + / size-parm + / parameter + + filename-parm := "filename" "=" value + + creation-date-parm := "creation-date" "=" quoted-date-time + + modification-date-parm := "modification-date" "=" quoted-date-time + + read-date-parm := "read-date" "=" quoted-date-time + + size-parm := "size" "=" 1*DIGIT + + quoted-date-time := quoted-string + ; contents MUST be an RFC 822 `date-time' + ; numeric timezones (+HHMM or -HHMM) MUST be used + + + + NOTE ON PARAMETER VALUE LENGHTS: A short (length <= 78 characters) + parameter value containing only non-`tspecials' characters SHOULD be + represented as a single `token'. A short parameter value containing + only ASCII characters, but including `tspecials' characters, SHOULD + be represented as `quoted-string'. Parameter values longer than 78 + characters, or which contain non-ASCII characters, MUST be encoded as + specified in [RFC 2184]. + + `Extension-token', `parameter', `tspecials' and `value' are defined + according to [RFC 2045] (which references [RFC 822] in the definition + of some of these tokens). `quoted-string' and `DIGIT' are defined in + [RFC 822]. + + + + +Troost, et. al. Standards Track [Page 3] + +RFC 2183 Content-Disposition August 1997 + + +2.1 The Inline Disposition Type + + A bodypart should be marked `inline' if it is intended to be + displayed automatically upon display of the message. Inline + bodyparts should be presented in the order in which they occur, + subject to the normal semantics of multipart messages. + +2.2 The Attachment Disposition Type + + Bodyparts can be designated `attachment' to indicate that they are + separate from the main body of the mail message, and that their + display should not be automatic, but contingent upon some further + action of the user. The MUA might instead present the user of a + bitmap terminal with an iconic representation of the attachments, or, + on character terminals, with a list of attachments from which the + user could select for viewing or storage. + +2.3 The Filename Parameter + + The sender may want to suggest a filename to be used if the entity is + detached and stored in a separate file. If the receiving MUA writes + the entity to a file, the suggested filename should be used as a + basis for the actual filename, where possible. + + It is important that the receiving MUA not blindly use the suggested + filename. The suggested filename SHOULD be checked (and possibly + changed) to see that it conforms to local filesystem conventions, + does not overwrite an existing file, and does not present a security + problem (see Security Considerations below). + + The receiving MUA SHOULD NOT respect any directory path information + that may seem to be present in the filename parameter. The filename + should be treated as a terminal component only. Portable + specification of directory paths might possibly be done in the future + via a separate Content-Disposition parameter, but no provision is + made for it in this draft. + + Current [RFC 2045] grammar restricts parameter values (and hence + Content-Disposition filenames) to US-ASCII. We recognize the great + desirability of allowing arbitrary character sets in filenames, but + it is beyond the scope of this document to define the necessary + mechanisms. We expect that the basic [RFC 1521] `value' + specification will someday be amended to allow use of non-US-ASCII + characters, at which time the same mechanism should be used in the + Content-Disposition filename parameter. + + + + + + +Troost, et. al. Standards Track [Page 4] + +RFC 2183 Content-Disposition August 1997 + + + Beyond the limitation to US-ASCII, the sending MUA may wish to bear + in mind the limitations of common filesystems. Many have severe + length and character set restrictions. Short alphanumeric filenames + are least likely to require modification by the receiving system. + + The presence of the filename parameter does not force an + implementation to write the entity to a separate file. It is + perfectly acceptable for implementations to leave the entity as part + of the normal mail stream unless the user requests otherwise. As a + consequence, the parameter may be used on any MIME entity, even + `inline' ones. These will not normally be written to files, but the + parameter could be used to provide a filename if the receiving user + should choose to write the part to a file. + +2.4 The Creation-Date parameter + + The creation-date parameter MAY be used to indicate the date at which + the file was created. If this parameter is included, the paramter + value MUST be a quoted-string which contains a representation of the + creation date of the file in [RFC 822] `date-time' format. + + UNIX and POSIX implementors are cautioned that the `st_ctime' file + attribute of the `stat' structure is not the creation time of the + file; it is thus not appropriate as a source for the creation-date + parameter value. + +2.5 The Modification-Date parameter + + The modification-date parameter MAY be used to indicate the date at + which the file was last modified. If the modification-date parameter + is included, the paramter value MUST be a quoted-string which + contains a representation of the last modification date of the file + in [RFC 822] `date-time' format. + +2.6 The Read-Date parameter + + The read-date parameter MAY be used to indicate the date at which the + file was last read. If the read-date parameter is included, the + parameter value MUST be a quoted-string which contains a + representation of the last-read date of the file in [RFC 822] `date- + time' format. + +2.7 The Size parameter + + The size parameter indicates an approximate size of the file in + octets. It can be used, for example, to pre-allocate space before + attempting to store the file, or to determine whether enough space + exists. + + + +Troost, et. al. Standards Track [Page 5] + +RFC 2183 Content-Disposition August 1997 + + +2.8 Future Extensions and Unrecognized Disposition Types + + In the likely event that new parameters or disposition types are + needed, they should be registered with the Internet Assigned Numbers + Authority (IANA), in the manner specified in Section 9 of this memo. + + Once new disposition types and parameters are defined, there is of + course the likelihood that implementations will see disposition types + and parameters they do not understand. Furthermore, since x-tokens + are allowed, implementations may also see entirely unregistered + disposition types and parameters. + + Unrecognized parameters should be ignored. Unrecognized disposition + types should be treated as `attachment'. The choice of `attachment' + for unrecognized types is made because a sender who goes to the + trouble of producing a Content-Disposition header with a new + disposition type is more likely aiming for something more elaborate + than inline presentation. + + Unless noted otherwise in the definition of a parameter, Content- + Disposition parameters are valid for all dispositions. (In contrast + to MIME content-type parameters, which are defined on a per-content- + type basis.) Thus, for example, the `filename' parameter still means + the name of the file to which the part should be written, even if the + disposition itself is unrecognized. + +2.9 Content-Disposition and Multipart + + If a Content-Disposition header is used on a multipart body part, it + applies to the multipart as a whole, not the individual subparts. + The disposition types of the subparts do not need to be consulted + until the multipart itself is presented. When the multipart is + displayed, then the dispositions of the subparts should be respected. + + If the `inline' disposition is used, the multipart should be + displayed as normal; however, an `attachment' subpart should require + action from the user to display. + + If the `attachment' disposition is used, presentation of the + multipart should not proceed without explicit user action. Once the + user has chosen to display the multipart, the individual subpart + dispositions should be consulted to determine how to present the + subparts. + + + + + + + + +Troost, et. al. Standards Track [Page 6] + +RFC 2183 Content-Disposition August 1997 + + +2.10 Content-Disposition and the Main Message + + It is permissible to use Content-Disposition on the main body of an + [RFC 822] message. + +3. Examples + + Here is a an example of a body part containing a JPEG image that is + intended to be viewed by the user immediately: + + Content-Type: image/jpeg + Content-Disposition: inline + Content-Description: just a small picture of me + + + + The following body part contains a JPEG image that should be + displayed to the user only if the user requests it. If the JPEG is + written to a file, the file should be named "genome.jpg". The + recipient's user might also choose to set the last-modified date of + the stored file to date in the modification-date parameter: + + Content-Type: image/jpeg + Content-Disposition: attachment; filename=genome.jpeg; + modification-date="Wed, 12 Feb 1997 16:29:51 -0500"; + Content-Description: a complete map of the human genome + + + + The following is an example of the use of the `attachment' + disposition with a multipart body part. The user should see text- + part-1 immediately, then take some action to view multipart-2. After + taking action to view multipart-2, the user will see text-part-2 + right away, and be required to take action to view jpeg-1. Subparts + are indented for clarity; they would not be so indented in a real + message. + + + + + + + + + + + + + + + +Troost, et. al. Standards Track [Page 7] + +RFC 2183 Content-Disposition August 1997 + + + Content-Type: multipart/mixed; boundary=outer + Content-Description: multipart-1 + + --outer + Content-Type: text/plain + Content-Disposition: inline + Content-Description: text-part-1 + + Some text goes here + + --outer + Content-Type: multipart/mixed; boundary=inner + Content-Disposition: attachment + Content-Description: multipart-2 + + --inner + Content-Type: text/plain + Content-Disposition: inline + Content-Description: text-part-2 + + Some more text here. + + --inner + Content-Type: image/jpeg + Content-Disposition: attachment + Content-Description: jpeg-1 + + + --inner-- + --outer-- + +4. Summary + + Content-Disposition takes one of two values, `inline' and + `attachment'. `Inline' indicates that the entity should be + immediately displayed to the user, whereas `attachment' means that + the user should take additional action to view the entity. + + The `filename' parameter can be used to suggest a filename for + storing the bodypart, if the user wishes to store it in an external + file. + + + + + + + + + + +Troost, et. al. Standards Track [Page 8] + +RFC 2183 Content-Disposition August 1997 + + +5. Security Considerations + + There are security issues involved any time users exchange data. + While these are not to be minimized, neither does this memo change + the status quo in that regard, except in one instance. + + Since this memo provides a way for the sender to suggest a filename, + a receiving MUA must take care that the sender's suggested filename + does not represent a hazard. Using UNIX as an example, some hazards + would be: + + + Creating startup files (e.g., ".login"). + + + Creating or overwriting system files (e.g., "/etc/passwd"). + + + Overwriting any existing file. + + + Placing executable files into any command search path + (e.g., "~/bin/more"). + + + Sending the file to a pipe (e.g., "| sh"). + + In general, the receiving MUA should not name or place the file such + that it will get interpreted or executed without the user explicitly + initiating the action. + + It is very important to note that this is not an exhaustive list; it + is intended as a small set of examples only. Implementors must be + alert to the potential hazards on their target systems. + +6. References + + [RFC 2119] + Bradner, S., "Key words for use in RFCs to Indicate Requirement + Levels", RFC 2119, March 1997. + + [RFC 2184] + Freed, N. and K. Moore, "MIME Parameter value and Encoded Words: + Character Sets, Lanaguage, and Continuations", RFC 2184, August + 1997. + + [RFC 2045] + Freed, N. and N. Borenstein, "MIME (Multipurpose Internet Mail + Extensions) Part One: Format of Internet Message Bodies", RFC + 2045, December 1996. + + + + + + +Troost, et. al. Standards Track [Page 9] + +RFC 2183 Content-Disposition August 1997 + + + [RFC 2046] + Freed, N. and N. Borenstein, "MIME (Multipurpose Internet Mail + Extensions) Part Two: Media Types", RFC 2046, December 1996. + + [RFC 2047] + Moore, K., "MIME (Multipurpose Internet Mail Extensions) Part + Three: Message Header Extensions for non-ASCII Text", RFC 2047, + December 1996. + + [RFC 2048] + Freed, N., Klensin, J. and J. Postel, "MIME (Multipurpose + Internet Mail Extensions) Part Four: Registration Procedures", + RFC 2048, December 1996. + + [RFC 2049] + Freed, N. and N. Borenstein, "MIME (Multipurpose Internet Mail + Extensions) Part Five: Conformance Criteria and Examples", RFC + 2049, December 1996. + + [RFC 822] + Crocker, D., "Standard for the Format of ARPA Internet Text + Messages", STD 11, RFC 822, UDEL, August 1982. + +7. Acknowledgements + + We gratefully acknowledge the help these people provided during the + preparation of this draft: + + Nathaniel Borenstein + Ned Freed + Keith Moore + Dave Crocker + Dan Pritchett + + + + + + + + + + + + + + + + + + +Troost, et. al. Standards Track [Page 10] + +RFC 2183 Content-Disposition August 1997 + + +8. Authors' Addresses + + You should blame the editor of this version of the document for any + changes since RFC 1806: + + Keith Moore + Department of Computer Science + University of Tennessee, Knoxville + 107 Ayres Hall + Knoxville TN 37996-1301 + USA + + Phone: +1 (423) 974-5067 + Fax: +1 (423) 974-8296 + Email: moore@cs.utk.edu + + + The authors of RFC 1806 are: + + Rens Troost + New Century Systems + 324 East 41st Street #804 + New York, NY, 10017 USA + + Phone: +1 (212) 557-2050 + Fax: +1 (212) 557-2049 + EMail: rens@century.com + + + Steve Dorner + QUALCOMM Incorporated + 6455 Lusk Boulevard + San Diego, CA 92121 + USA + + EMail: sdorner@qualcomm.com + + +9. Registration of New Content-Disposition Values and Parameters + + New Content-Disposition values (besides "inline" and "attachment") + may be defined only by Internet standards-track documents, or in + Experimental documents approved by the Internet Engineering Steering + Group. + + + + + + + +Troost, et. al. Standards Track [Page 11] + +RFC 2183 Content-Disposition August 1997 + + + New content-disposition parameters may be registered by supplying the + information in the following template and sending it via electronic + mail to IANA@IANA.ORG: + + To: IANA@IANA.ORG + Subject: Registration of new Content-Disposition parameter + + Content-Disposition parameter name: + + Allowable values for this parameter: + (If the parameter can only assume a small number of values, + list each of those values. Otherwise, describe the values + that the parameter can assume.) + Description: + (What is the purpose of this parameter and how is it used?) + +10. Changes since RFC 1806 + + The following changes have been made since the earlier version of + this document, published in RFC 1806 as an Experimental protocol: + + + Updated references to MIME documents. In some cases this + involved substituting a reference to one of the current MIME + RFCs for a reference to RFC 1521; in other cases, a reference to + RFC 1521 was simply replaced with the word "MIME". + + + Added a section on registration procedures, since none of the + procedures in RFC 2048 seemed to be appropriate. + + + Added new parameter types: creation-date, modification-date, + read-date, and size. + + + + Incorporated a reference to draft-freed-pvcsc-* for encoding + long or non-ASCII parameter values. + + + Added reference to RFC 2119 to define MUST, SHOULD, etc. + keywords. + + + + + + + + + + + + + +Troost, et. al. Standards Track [Page 12] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2222.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2222.txt new file mode 100644 index 0000000..2b0a2ab --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2222.txt @@ -0,0 +1,899 @@ + + + + + + +Network Working Group J. Myers +Request for Comments: 2222 Netscape Communications +Category: Standards Track October 1997 + + + Simple Authentication and Security Layer (SASL) + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1997). All Rights Reserved. + +Table of Contents + + 1. Abstract .............................................. 2 + 2. Organization of this Document ......................... 2 + 2.1. How to Read This Document ............................. 2 + 2.2. Conventions Used in this Document ..................... 2 + 2.3. Examples .............................................. 3 + 3. Introduction and Overview ............................. 3 + 4. Profiling requirements ................................ 4 + 5. Specific issues ....................................... 5 + 5.1. Client sends data first ............................... 5 + 5.2. Server returns success with additional data ........... 5 + 5.3. Multiple authentications .............................. 5 + 6. Registration procedures ............................... 6 + 6.1. Comments on SASL mechanism registrations .............. 6 + 6.2. Location of Registered SASL Mechanism List ............ 6 + 6.3. Change Control ........................................ 7 + 6.4. Registration Template ................................. 7 + 7. Mechanism definitions ................................. 8 + 7.1. Kerberos version 4 mechanism .......................... 8 + 7.2. GSSAPI mechanism ...................................... 9 + 7.2.1 Client side of authentication protocol exchange ....... 9 + 7.2.2 Server side of authentication protocol exchange ....... 10 + 7.2.3 Security layer ........................................ 11 + 7.3. S/Key mechanism ....................................... 11 + 7.4. External mechanism .................................... 12 + 8. References ............................................ 13 + 9. Security Considerations ............................... 13 + 10. Author's Address ...................................... 14 + + + +Myers Standards Track [Page 1] + +RFC 2222 SASL October 1997 + + + Appendix A. Relation of SASL to Transport Security .......... 15 + Full Copyright Statement .................................... 16 + +1. Abstract + + This document describes a method for adding authentication support to + connection-based protocols. To use this specification, a protocol + includes a command for identifying and authenticating a user to a + server and for optionally negotiating protection of subsequent + protocol interactions. If its use is negotiated, a security layer is + inserted between the protocol and the connection. This document + describes how a protocol specifies such a command, defines several + mechanisms for use by the command, and defines the protocol used for + carrying a negotiated security layer over the connection. + +2. Organization of this Document + +2.1. How to Read This Document + + This document is written to serve two different audiences, protocol + designers using this specification to support authentication in their + protocol, and implementors of clients or servers for those protocols + using this specification. + + The sections "Introduction and Overview", "Profiling requirements", + and "Security Considerations" cover issues that protocol designers + need to understand and address in profiling this specification for + use in a specific protocol. + + Implementors of a protocol using this specification need the + protocol-specific profiling information in addition to the + information in this document. + +2.2. Conventions Used in this Document + + In examples, "C:" and "S:" indicate lines sent by the client and + server respectively. + + The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY" + in this document are to be interpreted as defined in "Key words for + use in RFCs to Indicate Requirement Levels" [RFC 2119]. + + + + + + + + + + +Myers Standards Track [Page 2] + +RFC 2222 SASL October 1997 + + +2.3. Examples + + Examples in this document are for the IMAP profile [RFC 2060] of this + specification. The base64 encoding of challenges and responses, as + well as the "+ " preceding the responses are part of the IMAP4 + profile, not part of the SASL specification itself. + +3. Introduction and Overview + + The Simple Authentication and Security Layer (SASL) is a method for + adding authentication support to connection-based protocols. To use + this specification, a protocol includes a command for identifying and + authenticating a user to a server and for optionally negotiating a + security layer for subsequent protocol interactions. + + The command has a required argument identifying a SASL mechanism. + SASL mechanisms are named by strings, from 1 to 20 characters in + length, consisting of upper-case letters, digits, hyphens, and/or + underscores. SASL mechanism names must be registered with the IANA. + Procedures for registering new SASL mechanisms are given in the + section "Registration procedures" + + If a server supports the requested mechanism, it initiates an + authentication protocol exchange. This consists of a series of + server challenges and client responses that are specific to the + requested mechanism. The challenges and responses are defined by the + mechanisms as binary tokens of arbitrary length. The protocol's + profile then specifies how these binary tokens are then encoded for + transfer over the connection. + + After receiving the authentication command or any client response, a + server may issue a challenge, indicate failure, or indicate + completion. The protocol's profile specifies how the server + indicates which of the above it is doing. + + After receiving a challenge, a client may issue a response or abort + the exchange. The protocol's profile specifies how the client + indicates which of the above it is doing. + + During the authentication protocol exchange, the mechanism performs + authentication, transmits an authorization identity (frequently known + as a userid) from the client to server, and negotiates the use of a + mechanism-specific security layer. If the use of a security layer is + agreed upon, then the mechanism must also define or negotiate the + maximum cipher-text buffer size that each side is able to receive. + + + + + + +Myers Standards Track [Page 3] + +RFC 2222 SASL October 1997 + + + The transmitted authorization identity may be different than the + identity in the client's authentication credentials. This permits + agents such as proxy servers to authenticate using their own + credentials, yet request the access privileges of the identity for + which they are proxying. With any mechanism, transmitting an + authorization identity of the empty string directs the server to + derive an authorization identity from the client's authentication + credentials. + + If use of a security layer is negotiated, it is applied to all + subsequent data sent over the connection. The security layer takes + effect immediately following the last response of the authentication + exchange for data sent by the client and the completion indication + for data sent by the server. Once the security layer is in effect, + the protocol stream is processed by the security layer into buffers + of cipher-text. Each buffer is transferred over the connection as a + stream of octets prepended with a four octet field in network byte + order that represents the length of the following buffer. The length + of the cipher-text buffer must be no larger than the maximum size + that was defined or negotiated by the other side. + +4. Profiling requirements + + In order to use this specification, a protocol definition must supply + the following information: + + 1. A service name, to be selected from the IANA registry of "service" + elements for the GSSAPI host-based service name form [RFC 2078]. + + 2. A definition of the command to initiate the authentication + protocol exchange. This command must have as a parameter the + mechanism name being selected by the client. + + The command SHOULD have an optional parameter giving an initial + response. This optional parameter allows the client to avoid a + round trip when using a mechanism which is defined to have the + client send data first. When this initial response is sent by the + client and the selected mechanism is defined to have the server + start with an initial challenge, the command fails. See section + 5.1 of this document for further information. + + 3. A definition of the method by which the authentication protocol + exchange is carried out, including how the challenges and + responses are encoded, how the server indicates completion or + failure of the exchange, how the client aborts an exchange, and + how the exchange method interacts with any line length limits in + the protocol. + + + + +Myers Standards Track [Page 4] + +RFC 2222 SASL October 1997 + + + 4. Identification of the octet where any negotiated security layer + starts to take effect, in both directions. + + 5. A specification of how the authorization identity passed from the + client to the server is to be interpreted. + +5. Specific issues + +5.1. Client sends data first + + Some mechanisms specify that the first data sent in the + authentication protocol exchange is from the client to the server. + + If a protocol's profile permits the command which initiates an + authentication protocol exchange to contain an initial client + response, this parameter SHOULD be used with such mechanisms. + + If the initial client response parameter is not given, or if a + protocol's profile does not permit the command which initiates an + authentication protocol exchange to contain an initial client + response, then the server issues a challenge with no data. The + client's response to this challenge is then used as the initial + client response. (The server then proceeds to send the next + challenge, indicates completion, or indicates failure.) + +5.2. Server returns success with additional data + + Some mechanisms may specify that server challenge data be sent to the + client along with an indication of successful completion of the + exchange. This data would, for example, authenticate the server to + the client. + + If a protocol's profile does not permit this server challenge to be + returned with a success indication, then the server issues the server + challenge without an indication of successful completion. The client + then responds with no data. After receiving this empty response, the + server then indicates successful completion. + +5.3. Multiple authentications + + Unless otherwise stated by the protocol's profile, only one + successful SASL negotiation may occur in a protocol session. In this + case, once an authentication protocol exchange has successfully + completed, further attempts to initiate an authentication protocol + exchange fail. + + + + + + +Myers Standards Track [Page 5] + +RFC 2222 SASL October 1997 + + + In the case that a profile explicitly permits multiple successful + SASL negotiations to occur, then in no case may multiple security + layers be simultaneously in effect. If a security layer is in effect + and a subsequent SASL negotiation selects no security layer, the + original security layer remains in effect. If a security layer is in + effect and a subsequent SASL negotiation selects a second security + layer, then the second security layer replaces the first. + +6. Registration procedures + + Registration of a SASL mechanism is done by filling in the template + in section 6.4 and sending it in to iana@isi.edu. IANA has the right + to reject obviously bogus registrations, but will perform no review + of clams made in the registration form. + + There is no naming convention for SASL mechanisms; any name that + conforms to the syntax of a SASL mechanism name can be registered. + + While the registration procedures do not require it, authors of SASL + mechanisms are encouraged to seek community review and comment + whenever that is feasible. Authors may seek community review by + posting a specification of their proposed mechanism as an internet- + draft. SASL mechanisms intended for widespread use should be + standardized through the normal IETF process, when appropriate. + +6.1. Comments on SASL mechanism registrations + + Comments on registered SASL mechanisms should first be sent to the + "owner" of the mechanism. Submitters of comments may, after a + reasonable attempt to contact the owner, request IANA to attach their + comment to the SASL mechanism registration itself. If IANA approves + of this the comment will be made accessible in conjunction with the + SASL mechanism registration itself. + +6.2. Location of Registered SASL Mechanism List + + SASL mechanism registrations will be posted in the anonymous FTP + directory "ftp://ftp.isi.edu/in-notes/iana/assignments/sasl- + mechanisms/" and all registered SASL mechanisms will be listed in the + periodically issued "Assigned Numbers" RFC [currently STD 2, RFC + 1700]. The SASL mechanism description and other supporting material + may also be published as an Informational RFC by sending it to "rfc- + editor@isi.edu" (please follow the instructions to RFC authors [RFC + 2223]). + + + + + + + +Myers Standards Track [Page 6] + +RFC 2222 SASL October 1997 + + +6.3. Change Control + + Once a SASL mechanism registration has been published by IANA, the + author may request a change to its definition. The change request + follows the same procedure as the registration request. + + The owner of a SASL mechanism may pass responsibility for the SASL + mechanism to another person or agency by informing IANA; this can be + done without discussion or review. + + The IESG may reassign responsibility for a SASL mechanism. The most + common case of this will be to enable changes to be made to + mechanisms where the author of the registration has died, moved out + of contact or is otherwise unable to make changes that are important + to the community. + + SASL mechanism registrations may not be deleted; mechanisms which are + no longer believed appropriate for use can be declared OBSOLETE by a + change to their "intended use" field; such SASL mechanisms will be + clearly marked in the lists published by IANA. + + The IESG is considered to be the owner of all SASL mechanisms which + are on the IETF standards track. + +6.4. Registration Template + + To: iana@iana.org + Subject: Registration of SASL mechanism X + + SASL mechanism name: + + Security considerations: + + Published specification (optional, recommended): + + Person & email address to contact for further information: + + Intended usage: + + (One of COMMON, LIMITED USE or OBSOLETE) + + Author/Change controller: + + (Any other information that the author deems interesting may be + added below this line.) + + + + + + +Myers Standards Track [Page 7] + +RFC 2222 SASL October 1997 + + +7. Mechanism definitions + + The following mechanisms are hereby defined. + +7.1. Kerberos version 4 mechanism + + The mechanism name associated with Kerberos version 4 is + "KERBEROS_V4". + + The first challenge consists of a random 32-bit number in network + byte order. The client responds with a Kerberos ticket and an + authenticator for the principal "service.hostname@realm", where + "service" is the service name specified in the protocol's profile, + "hostname" is the first component of the host name of the server with + all letters in lower case, and where "realm" is the Kerberos realm of + the server. The encrypted checksum field included within the + Kerberos authenticator contains the server provided challenge in + network byte order. + + Upon decrypting and verifying the ticket and authenticator, the + server verifies that the contained checksum field equals the original + server provided random 32-bit number. Should the verification be + successful, the server must add one to the checksum and construct 8 + octets of data, with the first four octets containing the incremented + checksum in network byte order, the fifth octet containing a bit-mask + specifying the security layers supported by the server, and the sixth + through eighth octets containing, in network byte order, the maximum + cipher-text buffer size the server is able to receive. The server + must encrypt using DES ECB mode the 8 octets of data in the session + key and issue that encrypted data in a second challenge. The client + considers the server authenticated if the first four octets of the + un-encrypted data is equal to one plus the checksum it previously + sent. + + The client must construct data with the first four octets containing + the original server-issued checksum in network byte order, the fifth + octet containing the bit-mask specifying the selected security layer, + the sixth through eighth octets containing in network byte order the + maximum cipher-text buffer size the client is able to receive, and + the following octets containing the authorization identity. The + client must then append from one to eight zero-valued octets so that + the length of the data is a multiple of eight octets. The client must + then encrypt using DES PCBC mode the data with the session key and + respond with the encrypted data. The server decrypts the data and + verifies the contained checksum. The server must verify that the + principal identified in the Kerberos ticket is authorized to connect + as that authorization identity. After this verification, the + authentication process is complete. + + + +Myers Standards Track [Page 8] + +RFC 2222 SASL October 1997 + + + The security layers and their corresponding bit-masks are as follows: + + 1 No security layer + 2 Integrity (krb_mk_safe) protection + 4 Privacy (krb_mk_priv) protection + + Other bit-masks may be defined in the future; bits which are not + understood must be negotiated off. + + EXAMPLE: The following are two Kerberos version 4 login scenarios to + the IMAP4 protocol (note that the line breaks in the sample + authenticators are for editorial clarity and are not in real + authenticators) + + S: * OK IMAP4 Server + C: A001 AUTHENTICATE KERBEROS_V4 + S: + AmFYig== + C: BAcAQU5EUkVXLkNNVS5FRFUAOCAsho84kLN3/IJmrMG+25a4DT + +nZImJjnTNHJUtxAA+o0KPKfHEcAFs9a3CL5Oebe/ydHJUwYFd + WwuQ1MWiy6IesKvjL5rL9WjXUb9MwT9bpObYLGOKi1Qh + S: + or//EoAADZI= + C: DiAF5A4gA+oOIALuBkAAmw== + S: A001 OK Kerberos V4 authentication successful + + + S: * OK IMAP4 Server + C: A001 AUTHENTICATE KERBEROS_V4 + S: + gcfgCA== + C: BAcAQU5EUkVXLkNNVS5FRFUAOCAsho84kLN3/IJmrMG+25a4DT + +nZImJjnTNHJUtxAA+o0KPKfHEcAFs9a3CL5Oebe/ydHJUwYFd + WwuQ1MWiy6IesKvjL5rL9WjXUb9MwT9bpObYLGOKi1Qh + S: A001 NO Kerberos V4 authentication failed + +7.2. GSSAPI mechanism + + The mechanism name associated with all mechanisms employing the + GSSAPI [RFC 2078] is "GSSAPI". + +7.2.1 Client side of authentication protocol exchange + + The client calls GSS_Init_sec_context, passing in 0 for + input_context_handle (initially) and a targ_name equal to output_name + from GSS_Import_Name called with input_name_type of + GSS_C_NT_HOSTBASED_SERVICE and input_name_string of + "service@hostname" where "service" is the service name specified in + the protocol's profile, and "hostname" is the fully qualified host + name of the server. The client then responds with the resulting + output_token. If GSS_Init_sec_context returns GSS_S_CONTINUE_NEEDED, + + + +Myers Standards Track [Page 9] + +RFC 2222 SASL October 1997 + + + then the client should expect the server to issue a token in a + subsequent challenge. The client must pass the token to another call + to GSS_Init_sec_context, repeating the actions in this paragraph. + + When GSS_Init_sec_context returns GSS_S_COMPLETE, the client takes + the following actions: If the last call to GSS_Init_sec_context + returned an output_token, then the client responds with the + output_token, otherwise the client responds with no data. The client + should then expect the server to issue a token in a subsequent + challenge. The client passes this token to GSS_Unwrap and interprets + the first octet of resulting cleartext as a bit-mask specifying the + security layers supported by the server and the second through fourth + octets as the maximum size output_message to send to the server. The + client then constructs data, with the first octet containing the + bit-mask specifying the selected security layer, the second through + fourth octets containing in network byte order the maximum size + output_message the client is able to receive, and the remaining + octets containing the authorization identity. The client passes the + data to GSS_Wrap with conf_flag set to FALSE, and responds with the + generated output_message. The client can then consider the server + authenticated. + +7.2.2 Server side of authentication protocol exchange + + The server passes the initial client response to + GSS_Accept_sec_context as input_token, setting input_context_handle + to 0 (initially). If GSS_Accept_sec_context returns + GSS_S_CONTINUE_NEEDED, the server returns the generated output_token + to the client in challenge and passes the resulting response to + another call to GSS_Accept_sec_context, repeating the actions in this + paragraph. + + When GSS_Accept_sec_context returns GSS_S_COMPLETE, the client takes + the following actions: If the last call to GSS_Accept_sec_context + returned an output_token, the server returns it to the client in a + challenge and expects a reply from the client with no data. Whether + or not an output_token was returned (and after receipt of any + response from the client to such an output_token), the server then + constructs 4 octets of data, with the first octet containing a bit- + mask specifying the security layers supported by the server and the + second through fourth octets containing in network byte order the + maximum size output_token the server is able to receive. The server + must then pass the plaintext to GSS_Wrap with conf_flag set to FALSE + and issue the generated output_message to the client in a challenge. + The server must then pass the resulting response to GSS_Unwrap and + interpret the first octet of resulting cleartext as the bit-mask for + the selected security layer, the second through fourth octets as the + maximum size output_message to send to the client, and the remaining + + + +Myers Standards Track [Page 10] + +RFC 2222 SASL October 1997 + + + octets as the authorization identity. The server must verify that + the src_name is authorized to authenticate as the authorization + identity. After these verifications, the authentication process is + complete. + +7.2.3 Security layer + + The security layers and their corresponding bit-masks are as follows: + + 1 No security layer + 2 Integrity protection. + Sender calls GSS_Wrap with conf_flag set to FALSE + 4 Privacy protection. + Sender calls GSS_Wrap with conf_flag set to TRUE + + Other bit-masks may be defined in the future; bits which are not + understood must be negotiated off. + +7.3. S/Key mechanism + + The mechanism name associated with S/Key [RFC 1760] using the MD4 + digest algorithm is "SKEY". + + The client sends an initial response with the authorization identity. + + The server then issues a challenge which contains the decimal + sequence number followed by a single space and the seed string for + the indicated authorization identity. The client responds with the + one-time-password, as either a 64-bit value in network byte order or + encoded in the "six English words" format. + + The server must verify the one-time-password. After this + verification, the authentication process is complete. + + S/Key authentication does not provide for any security layers. + + EXAMPLE: The following are two S/Key login scenarios in the IMAP4 + protocol. + + S: * OK IMAP4 Server + C: A001 AUTHENTICATE SKEY + S: + + C: bW9yZ2Fu + S: + OTUgUWE1ODMwOA== + C: Rk9VUiBNQU5OIFNPT04gRklSIFZBUlkgTUFTSA== + S: A001 OK S/Key authentication successful + + + + + +Myers Standards Track [Page 11] + +RFC 2222 SASL October 1997 + + + S: * OK IMAP4 Server + C: A001 AUTHENTICATE SKEY + S: + + C: c21pdGg= + S: + OTUgUWE1ODMwOA== + C: BsAY3g4gBNo= + S: A001 NO S/Key authentication failed + + The following is an S/Key login scenario in an IMAP4-like protocol + which has an optional "initial response" argument to the AUTHENTICATE + command. + + S: * OK IMAP4-Like Server + C: A001 AUTHENTICATE SKEY bW9yZ2Fu + S: + OTUgUWE1ODMwOA== + C: Rk9VUiBNQU5OIFNPT04gRklSIFZBUlkgTUFTSA== + S: A001 OK S/Key authentication successful + +7.4. External mechanism + + The mechanism name associated with external authentication is + "EXTERNAL". + + The client sends an initial response with the authorization identity. + + The server uses information, external to SASL, to determine whether + the client is authorized to authenticate as the authorization + identity. If the client is so authorized, the server indicates + successful completion of the authentication exchange; otherwise the + server indicates failure. + + The system providing this external information may be, for example, + IPsec or TLS. + + If the client sends the empty string as the authorization identity + (thus requesting the authorization identity be derived from the + client's authentication credentials), the authorization identity is + to be derived from authentication credentials which exist in the + system which is providing the external authentication. + + + + + + + + + + + + +Myers Standards Track [Page 12] + +RFC 2222 SASL October 1997 + + +8. References + + [RFC 2060] Crispin, M., "Internet Message Access Protocol - Version + 4rev1", RFC 2060, December 1996. + + [RFC 2078] Linn, J., "Generic Security Service Application Program + Interface, Version 2", RFC 2078, January 1997. + + [RFC 2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", RFC 2119, March 1997. + + [RFC 2223] Postel, J., and J. Reynolds, "Instructions to RFC + Authors", RFC 2223, October 1997. + + [RFC 1760] Haller, N., "The S/Key One-Time Password System", RFC + 1760, February 1995. + + [RFC 1700] Reynolds, J., and J. Postel, "Assigned Numbers", STD 2, + RFC 1700, October 1994. + +9. Security Considerations + + Security issues are discussed throughout this memo. + + The mechanisms that support integrity protection are designed such + that the negotiation of the security layer and authorization identity + is integrity protected. When the client selects a security layer + with at least integrity protection, this protects against an active + attacker hijacking the connection and modifying the authentication + exchange to negotiate a plaintext connection. + + When a server or client supports multiple authentication mechanisms, + each of which has a different security strength, it is possible for + an active attacker to cause a party to use the least secure mechanism + supported. To protect against this sort of attack, a client or + server which supports mechanisms of different strengths should have a + configurable minimum strength that it will use. It is not sufficient + for this minimum strength check to only be on the server, since an + active attacker can change which mechanisms the client sees as being + supported, causing the client to send authentication credentials for + its weakest supported mechanism. + + + + + + + + + + +Myers Standards Track [Page 13] + +RFC 2222 SASL October 1997 + + + The client's selection of a SASL mechanism is done in the clear and + may be modified by an active attacker. It is important for any new + SASL mechanisms to be designed such that an active attacker cannot + obtain an authentication with weaker security properties by modifying + the SASL mechanism name and/or the challenges and responses. + + Any protocol interactions prior to authentication are performed in + the clear and may be modified by an active attacker. In the case + where a client selects integrity protection, it is important that any + security-sensitive protocol negotiations be performed after + authentication is complete. Protocols should be designed such that + negotiations performed prior to authentication should be either + ignored or revalidated once authentication is complete. + +10. Author's Address + + John G. Myers + Netscape Communications + 501 E. Middlefield Road + Mail Stop MV-029 + Mountain View, CA 94043-4042 + + EMail: jgmyers@netscape.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Myers Standards Track [Page 14] + +RFC 2222 SASL October 1997 + + +Appendix A. Relation of SASL to Transport Security + + Questions have been raised about the relationship between SASL and + various services (such as IPsec and TLS) which provide a secured + connection. + + Two of the key features of SASL are: + + 1. The separation of the authorization identity from the identity in + the client's credentials. This permits agents such as proxy + servers to authenticate using their own credentials, yet request + the access privileges of the identity for which they are proxying. + + 2. Upon successful completion of an authentication exchange, the + server knows the authorization identity the client wishes to use. + This allows servers to move to a "user is authenticated" state in + the protocol. + + These features are extremely important to some application protocols, + yet Transport Security services do not always provide them. To + define SASL mechanisms based on these services would be a very messy + task, as the framing of these services would be redundant with the + framing of SASL and some method of providing these important SASL + features would have to be devised. + + Sometimes it is desired to enable within an existing connection the + use of a security service which does not fit the SASL model. (TLS is + an example of such a service.) This can be done by adding a command, + for example "STARTTLS", to the protocol. Such a command is outside + the scope of SASL, and should be different from the command which + starts a SASL authentication protocol exchange. + + In certain situations, it is reasonable to use SASL underneath one of + these Transport Security services. The transport service would + secure the connection, either service would authenticate the client, + and SASL would negotiate the authorization identity. The SASL + negotiation would be what moves the protocol from "unauthenticated" + to "authenticated" state. The "EXTERNAL" SASL mechanism is + explicitly intended to handle the case where the transport service + secures the connection and authenticates the client and SASL + negotiates the authorization identity. + + When using SASL underneath a sufficiently strong Transport Security + service, a SASL security layer would most likely be redundant. The + client and server would thus probably want to negotiate off the use + of a SASL security layer. + + + + + +Myers Standards Track [Page 15] + +RFC 2222 SASL October 1997 + + +Full Copyright Statement + + Copyright (C) The Internet Society (1997). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implmentation may be prepared, copied, published + andand distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Myers Standards Track [Page 16] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2231.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2231.txt new file mode 100644 index 0000000..120f98f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2231.txt @@ -0,0 +1,563 @@ + + + + + + +Network Working Group N. Freed +Request for Comments: 2231 Innosoft +Updates: 2045, 2047, 2183 K. Moore +Obsoletes: 2184 University of Tennessee +Category: Standards Track November 1997 + + + MIME Parameter Value and Encoded Word Extensions: + Character Sets, Languages, and Continuations + + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1997). All Rights Reserved. + +1. Abstract + + This memo defines extensions to the RFC 2045 media type and RFC 2183 + disposition parameter value mechanisms to provide + + (1) a means to specify parameter values in character sets + other than US-ASCII, + + (2) to specify the language to be used should the value be + displayed, and + + (3) a continuation mechanism for long parameter values to + avoid problems with header line wrapping. + + This memo also defines an extension to the encoded words defined in + RFC 2047 to allow the specification of the language to be used for + display as well as the character set. + +2. Introduction + + The Multipurpose Internet Mail Extensions, or MIME [RFC-2045, RFC- + 2046, RFC-2047, RFC-2048, RFC-2049], define a message format that + allows for: + + + + + +Freed & Moore Standards Track [Page 1] + +RFC 2231 MIME Value and Encoded Word Extensions November 1997 + + + (1) textual message bodies in character sets other than + US-ASCII, + + (2) non-textual message bodies, + + (3) multi-part message bodies, and + + (4) textual header information in character sets other than + US-ASCII. + + MIME is now widely deployed and is used by a variety of Internet + protocols, including, of course, Internet email. However, MIME's + success has resulted in the need for additional mechanisms that were + not provided in the original protocol specification. + + In particular, existing MIME mechanisms provide for named media type + (content-type field) parameters as well as named disposition + (content-disposition field). A MIME media type may specify any + number of parameters associated with all of its subtypes, and any + specific subtype may specify additional parameters for its own use. A + MIME disposition value may specify any number of associated + parameters, the most important of which is probably the attachment + disposition's filename parameter. + + These parameter names and values end up appearing in the content-type + and content-disposition header fields in Internet email. This + inherently imposes three crucial limitations: + + (1) Lines in Internet email header fields are folded + according to RFC 822 folding rules. This makes long + parameter values problematic. + + (2) MIME headers, like the RFC 822 headers they often + appear in, are limited to 7bit US-ASCII, and the + encoded-word mechanisms of RFC 2047 are not available + to parameter values. This makes it impossible to have + parameter values in character sets other than US-ASCII + without specifying some sort of private per-parameter + encoding. + + (3) It has recently become clear that character set + information is not sufficient to properly display some + sorts of information -- language information is also + needed [RFC-2130]. For example, support for + handicapped users may require reading text string + + + + + + +Freed & Moore Standards Track [Page 2] + +RFC 2231 MIME Value and Encoded Word Extensions November 1997 + + + aloud. The language the text is written in is needed + for this to be done correctly. Some parameter values + may need to be displayed, hence there is a need to + allow for the inclusion of language information. + + The last problem on this list is also an issue for the encoded words + defined by RFC 2047, as encoded words are intended primarily for + display purposes. + + This document defines extensions that address all of these + limitations. All of these extensions are implemented in a fashion + that is completely compatible at a syntactic level with existing MIME + implementations. In addition, the extensions are designed to have as + little impact as possible on existing uses of MIME. + + IMPORTANT NOTE: These mechanisms end up being somewhat gibbous when + they actually are used. As such, these mechanisms should not be used + lightly; they should be reserved for situations where a real need for + them exists. + +2.1. Requirements notation + + This document occasionally uses terms that appear in capital letters. + When the terms "MUST", "SHOULD", "MUST NOT", "SHOULD NOT", and "MAY" + appear capitalized, they are being used to indicate particular + requirements of this specification. A discussion of the meanings of + these terms appears in [RFC- 2119]. + +3. Parameter Value Continuations + + Long MIME media type or disposition parameter values do not interact + well with header line wrapping conventions. In particular, proper + header line wrapping depends on there being places where linear + whitespace (LWSP) is allowed, which may or may not be present in a + parameter value, and even if present may not be recognizable as such + since specific knowledge of parameter value syntax may not be + available to the agent doing the line wrapping. The result is that + long parameter values may end up getting truncated or otherwise + damaged by incorrect line wrapping implementations. + + A mechanism is therefore needed to break up parameter values into + smaller units that are amenable to line wrapping. Any such mechanism + MUST be compatible with existing MIME processors. This means that + + (1) the mechanism MUST NOT change the syntax of MIME media + type and disposition lines, and + + + + + +Freed & Moore Standards Track [Page 3] + +RFC 2231 MIME Value and Encoded Word Extensions November 1997 + + + (2) the mechanism MUST NOT depend on parameter ordering + since MIME states that parameters are not order + sensitive. Note that while MIME does prohibit + modification of MIME headers during transport, it is + still possible that parameters will be reordered when + user agent level processing is done. + + The obvious solution, then, is to use multiple parameters to contain + a single parameter value and to use some kind of distinguished name + to indicate when this is being done. And this obvious solution is + exactly what is specified here: The asterisk character ("*") followed + by a decimal count is employed to indicate that multiple parameters + are being used to encapsulate a single parameter value. The count + starts at 0 and increments by 1 for each subsequent section of the + parameter value. Decimal values are used and neither leading zeroes + nor gaps in the sequence are allowed. + + The original parameter value is recovered by concatenating the + various sections of the parameter, in order. For example, the + content-type field + + Content-Type: message/external-body; access-type=URL; + URL*0="ftp://"; + URL*1="cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar" + + is semantically identical to + + Content-Type: message/external-body; access-type=URL; + URL="ftp://cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar" + + Note that quotes around parameter values are part of the value + syntax; they are NOT part of the value itself. Furthermore, it is + explicitly permitted to have a mixture of quoted and unquoted + continuation fields. + +4. Parameter Value Character Set and Language Information + + Some parameter values may need to be qualified with character set or + language information. It is clear that a distinguished parameter + name is needed to identify when this information is present along + with a specific syntax for the information in the value itself. In + addition, a lightweight encoding mechanism is needed to accommodate 8 + bit information in parameter values. + + + + + + + + +Freed & Moore Standards Track [Page 4] + +RFC 2231 MIME Value and Encoded Word Extensions November 1997 + + + Asterisks ("*") are reused to provide the indicator that language and + character set information is present and encoding is being used. A + single quote ("'") is used to delimit the character set and language + information at the beginning of the parameter value. Percent signs + ("%") are used as the encoding flag, which agrees with RFC 2047. + + Specifically, an asterisk at the end of a parameter name acts as an + indicator that character set and language information may appear at + the beginning of the parameter value. A single quote is used to + separate the character set, language, and actual value information in + the parameter value string, and an percent sign is used to flag + octets encoded in hexadecimal. For example: + + Content-Type: application/x-stuff; + title*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A + + Note that it is perfectly permissible to leave either the character + set or language field blank. Note also that the single quote + delimiters MUST be present even when one of the field values is + omitted. This is done when either character set, language, or both + are not relevant to the parameter value at hand. This MUST NOT be + done in order to indicate a default character set or language -- + parameter field definitions MUST NOT assign a default character set + or language. + +4.1. Combining Character Set, Language, and Parameter Continuations + + Character set and language information may be combined with the + parameter continuation mechanism. For example: + + Content-Type: application/x-stuff + title*0*=us-ascii'en'This%20is%20even%20more%20 + title*1*=%2A%2A%2Afun%2A%2A%2A%20 + title*2="isn't it!" + + Note that: + + (1) Language and character set information only appear at + the beginning of a given parameter value. + + (2) Continuations do not provide a facility for using more + than one character set or language in the same + parameter value. + + (3) A value presented using multiple continuations may + contain a mixture of encoded and unencoded segments. + + + + + +Freed & Moore Standards Track [Page 5] + +RFC 2231 MIME Value and Encoded Word Extensions November 1997 + + + (4) The first segment of a continuation MUST be encoded if + language and character set information are given. + + (5) If the first segment of a continued parameter value is + encoded the language and character set field delimiters + MUST be present even when the fields are left blank. + +5. Language specification in Encoded Words + + RFC 2047 provides support for non-US-ASCII character sets in RFC 822 + message header comments, phrases, and any unstructured text field. + This is done by defining an encoded word construct which can appear + in any of these places. Given that these are fields intended for + display, it is sometimes necessary to associate language information + with encoded words as well as just the character set. This + specification extends the definition of an encoded word to allow the + inclusion of such information. This is simply done by suffixing the + character set specification with an asterisk followed by the language + tag. For example: + + From: =?US-ASCII*EN?Q?Keith_Moore?= + +6. IMAP4 Handling of Parameter Values + + IMAP4 [RFC-2060] servers SHOULD decode parameter value continuations + when generating the BODY and BODYSTRUCTURE fetch attributes. + +7. Modifications to MIME ABNF + + The ABNF for MIME parameter values given in RFC 2045 is: + + parameter := attribute "=" value + + attribute := token + ; Matching of attributes + ; is ALWAYS case-insensitive. + + This specification changes this ABNF to: + + parameter := regular-parameter / extended-parameter + + regular-parameter := regular-parameter-name "=" value + + regular-parameter-name := attribute [section] + + attribute := 1*attribute-char + + + + + +Freed & Moore Standards Track [Page 6] + +RFC 2231 MIME Value and Encoded Word Extensions November 1997 + + + attribute-char := + + section := initial-section / other-sections + + initial-section := "*0" + + other-sections := "*" ("1" / "2" / "3" / "4" / "5" / + "6" / "7" / "8" / "9") *DIGIT) + + extended-parameter := (extended-initial-name "=" + extended-value) / + (extended-other-names "=" + extended-other-values) + + extended-initial-name := attribute [initial-section] "*" + + extended-other-names := attribute other-sections "*" + + extended-initial-value := [charset] "'" [language] "'" + extended-other-values + + extended-other-values := *(ext-octet / attribute-char) + + ext-octet := "%" 2(DIGIT / "A" / "B" / "C" / "D" / "E" / "F") + + charset := + + language := + + The ABNF given in RFC 2047 for encoded-words is: + + encoded-word := "=?" charset "?" encoding "?" encoded-text "?=" + + This specification changes this ABNF to: + + encoded-word := "=?" charset ["*" language] "?" encoded-text "?=" + +8. Character sets which allow specification of language + + In the future it is likely that some character sets will provide + facilities for inline language labeling. Such facilities are + inherently more flexible than those defined here as they allow for + language switching in the middle of a string. + + + + + + + +Freed & Moore Standards Track [Page 7] + +RFC 2231 MIME Value and Encoded Word Extensions November 1997 + + + If and when such facilities are developed they SHOULD be used in + preference to the language labeling facilities specified here. Note + that all the mechanisms defined here allow for the omission of + language labels so as to be able to accommodate this possible future + usage. + +9. Security Considerations + + This RFC does not discuss security issues and is not believed to + raise any security issues not already endemic in electronic mail and + present in fully conforming implementations of MIME. + +10. References + + [RFC-822] + Crocker, D., "Standard for the Format of ARPA Internet + Text Messages", STD 11, RFC 822 August 1982. + + [RFC-1766] + Alvestrand, H., "Tags for the Identification of + Languages", RFC 1766, March 1995. + + [RFC-2045] + Freed, N., and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part One: Format of Internet Message + Bodies", RFC 2045, December 1996. + + [RFC-2046] + Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part Two: Media Types", RFC 2046, + December 1996. + + [RFC-2047] + Moore, K., "Multipurpose Internet Mail Extensions (MIME) + Part Three: Representation of Non-ASCII Text in Internet + Message Headers", RFC 2047, December 1996. + + [RFC-2048] + Freed, N., Klensin, J. and J. Postel, "Multipurpose + Internet Mail Extensions (MIME) Part Four: MIME + Registration Procedures", RFC 2048, December 1996. + + [RFC-2049] + Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part Five: Conformance Criteria and + Examples", RFC 2049, December 1996. + + + + + +Freed & Moore Standards Track [Page 8] + +RFC 2231 MIME Value and Encoded Word Extensions November 1997 + + + [RFC-2060] + Crispin, M., "Internet Message Access Protocol - Version + 4rev1", RFC 2060, December 1996. + + [RFC-2119] + Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", RFC 2119, March 1997. + + [RFC-2130] + Weider, C., Preston, C., Simonsen, K., Alvestrand, H., + Atkinson, R., Crispin, M., and P. Svanberg, "Report from the + IAB Character Set Workshop", RFC 2130, April 1997. + + [RFC-2183] + Troost, R., Dorner, S. and K. Moore, "Communicating + Presentation Information in Internet Messages: The + Content-Disposition Header", RFC 2183, August 1997. + +11. Authors' Addresses + + Ned Freed + Innosoft International, Inc. + 1050 Lakes Drive + West Covina, CA 91790 + USA + + Phone: +1 626 919 3600 + Fax: +1 626 919 3614 + EMail: ned.freed@innosoft.com + + + Keith Moore + Computer Science Dept. + University of Tennessee + 107 Ayres Hall + Knoxville, TN 37996-1301 + USA + + EMail: moore@cs.utk.edu + + + + + + + + + + + + +Freed & Moore Standards Track [Page 9] + +RFC 2231 MIME Value and Encoded Word Extensions November 1997 + + +12. Full Copyright Statement + + Copyright (C) The Internet Society (1997). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Freed & Moore Standards Track [Page 10] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2234.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2234.txt new file mode 100644 index 0000000..edea302 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2234.txt @@ -0,0 +1,787 @@ + + + + + + +Network Working Group D. Crocker, Ed. +Request for Comments: 2234 Internet Mail Consortium +Category: Standards Track P. Overell + Demon Internet Ltd. + November 1997 + + + Augmented BNF for Syntax Specifications: ABNF + + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1997). All Rights Reserved. + +TABLE OF CONTENTS + + 1. INTRODUCTION .................................................. 2 + + 2. RULE DEFINITION ............................................... 2 + 2.1 RULE NAMING .................................................. 2 + 2.2 RULE FORM .................................................... 3 + 2.3 TERMINAL VALUES .............................................. 3 + 2.4 EXTERNAL ENCODINGS ........................................... 5 + + 3. OPERATORS ..................................................... 5 + 3.1 CONCATENATION RULE1 RULE2 ............................. 5 + 3.2 ALTERNATIVES RULE1 / RULE2 ................................... 6 + 3.3 INCREMENTAL ALTERNATIVES RULE1 =/ RULE2 .................... 6 + 3.4 VALUE RANGE ALTERNATIVES %C##-## ........................... 7 + 3.5 SEQUENCE GROUP (RULE1 RULE2) ................................. 7 + 3.6 VARIABLE REPETITION *RULE .................................... 8 + 3.7 SPECIFIC REPETITION NRULE .................................... 8 + 3.8 OPTIONAL SEQUENCE [RULE] ..................................... 8 + 3.9 ; COMMENT .................................................... 8 + 3.10 OPERATOR PRECEDENCE ......................................... 9 + + 4. ABNF DEFINITION OF ABNF ....................................... 9 + + 5. SECURITY CONSIDERATIONS ....................................... 10 + + + + +Crocker & Overell Standards Track [Page 1] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + 6. APPENDIX A - CORE ............................................. 11 + 6.1 CORE RULES ................................................... 11 + 6.2 COMMON ENCODING .............................................. 12 + + 7. ACKNOWLEDGMENTS ............................................... 12 + + 8. REFERENCES .................................................... 13 + + 9. CONTACT ....................................................... 13 + + 10. FULL COPYRIGHT STATEMENT ..................................... 14 + +1. INTRODUCTION + + Internet technical specifications often need to define a format + syntax and are free to employ whatever notation their authors deem + useful. Over the years, a modified version of Backus-Naur Form + (BNF), called Augmented BNF (ABNF), has been popular among many + Internet specifications. It balances compactness and simplicity, + with reasonable representational power. In the early days of the + Arpanet, each specification contained its own definition of ABNF. + This included the email specifications, RFC733 and then RFC822 which + have come to be the common citations for defining ABNF. The current + document separates out that definition, to permit selective + reference. Predictably, it also provides some modifications and + enhancements. + + The differences between standard BNF and ABNF involve naming rules, + repetition, alternatives, order-independence, and value ranges. + Appendix A (Core) supplies rule definitions and encoding for a core + lexical analyzer of the type common to several Internet + specifications. It is provided as a convenience and is otherwise + separate from the meta language defined in the body of this document, + and separate from its formal status. + +2. RULE DEFINITION + +2.1 Rule Naming + + The name of a rule is simply the name itself; that is, a sequence of + characters, beginning with an alphabetic character, and followed by + a combination of alphabetics, digits and hyphens (dashes). + + NOTE: Rule names are case-insensitive + + The names , , and all refer + to the same rule. + + + + +Crocker & Overell Standards Track [Page 2] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + Unlike original BNF, angle brackets ("<", ">") are not required. + However, angle brackets may be used around a rule name whenever their + presence will facilitate discerning the use of a rule name. This is + typically restricted to rule name references in free-form prose, or + to distinguish partial rules that combine into a string not separated + by white space, such as shown in the discussion about repetition, + below. + +2.2 Rule Form + + A rule is defined by the following sequence: + + name = elements crlf + + where is the name of the rule, is one or more rule + names or terminal specifications and is the end-of- line + indicator, carriage return followed by line feed. The equal sign + separates the name from the definition of the rule. The elements + form a sequence of one or more rule names and/or value definitions, + combined according to the various operators, defined in this + document, such as alternative and repetition. + + For visual ease, rule definitions are left aligned. When a rule + requires multiple lines, the continuation lines are indented. The + left alignment and indentation are relative to the first lines of the + ABNF rules and need not match the left margin of the document. + +2.3 Terminal Values + + Rules resolve into a string of terminal values, sometimes called + characters. In ABNF a character is merely a non-negative integer. + In certain contexts a specific mapping (encoding) of values into a + character set (such as ASCII) will be specified. + + Terminals are specified by one or more numeric characters with the + base interpretation of those characters indicated explicitly. The + following bases are currently defined: + + b = binary + + d = decimal + + x = hexadecimal + + + + + + + + +Crocker & Overell Standards Track [Page 3] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + Hence: + + CR = %d13 + + CR = %x0D + + respectively specify the decimal and hexadecimal representation of + [US-ASCII] for carriage return. + + A concatenated string of such values is specified compactly, using a + period (".") to indicate separation of characters within that value. + Hence: + + CRLF = %d13.10 + + ABNF permits specifying literal text string directly, enclosed in + quotation-marks. Hence: + + command = "command string" + + Literal text strings are interpreted as a concatenated set of + printable characters. + + NOTE: ABNF strings are case-insensitive and + the character set for these strings is us-ascii. + + Hence: + + rulename = "abc" + + and: + + rulename = "aBc" + + will match "abc", "Abc", "aBc", "abC", "ABc", "aBC", "AbC" and "ABC". + + To specify a rule which IS case SENSITIVE, + specify the characters individually. + + For example: + + rulename = %d97 %d98 %d99 + + or + + rulename = %d97.98.99 + + + + + +Crocker & Overell Standards Track [Page 4] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + will match only the string which comprises only lowercased + characters, abc. + +2.4 External Encodings + + External representations of terminal value characters will vary + according to constraints in the storage or transmission environment. + Hence, the same ABNF-based grammar may have multiple external + encodings, such as one for a 7-bit US-ASCII environment, another for + a binary octet environment and still a different one when 16-bit + Unicode is used. Encoding details are beyond the scope of ABNF, + although Appendix A (Core) provides definitions for a 7-bit US-ASCII + environment as has been common to much of the Internet. + + By separating external encoding from the syntax, it is intended that + alternate encoding environments can be used for the same syntax. + +3. OPERATORS + +3.1 Concatenation Rule1 Rule2 + + A rule can define a simple, ordered string of values -- i.e., a + concatenation of contiguous characters -- by listing a sequence of + rule names. For example: + + foo = %x61 ; a + + bar = %x62 ; b + + mumble = foo bar foo + + So that the rule matches the lowercase string "aba". + + LINEAR WHITE SPACE: Concatenation is at the core of the ABNF + parsing model. A string of contiguous characters (values) is + parsed according to the rules defined in ABNF. For Internet + specifications, there is some history of permitting linear white + space (space and horizontal tab) to be freelyPand + implicitlyPinterspersed around major constructs, such as + delimiting special characters or atomic strings. + + NOTE: This specification for ABNF does not + provide for implicit specification of linear white + space. + + Any grammar which wishes to permit linear white space around + delimiters or string segments must specify it explicitly. It is + often useful to provide for such white space in "core" rules that are + + + +Crocker & Overell Standards Track [Page 5] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + then used variously among higher-level rules. The "core" rules might + be formed into a lexical analyzer or simply be part of the main + ruleset. + +3.2 Alternatives Rule1 / Rule2 + + Elements separated by forward slash ("/") are alternatives. + Therefore, + + foo / bar + + will accept or . + + NOTE: A quoted string containing alphabetic + characters is special form for specifying alternative + characters and is interpreted as a non-terminal + representing the set of combinatorial strings with the + contained characters, in the specified order but with + any mixture of upper and lower case.. + +3.3 Incremental Alternatives Rule1 =/ Rule2 + + It is sometimes convenient to specify a list of alternatives in + fragments. That is, an initial rule may match one or more + alternatives, with later rule definitions adding to the set of + alternatives. This is particularly useful for otherwise- independent + specifications which derive from the same parent rule set, such as + often occurs with parameter lists. ABNF permits this incremental + definition through the construct: + + oldrule =/ additional-alternatives + + So that the rule set + + ruleset = alt1 / alt2 + + ruleset =/ alt3 + + ruleset =/ alt4 / alt5 + + is the same as specifying + + ruleset = alt1 / alt2 / alt3 / alt4 / alt5 + + + + + + + + +Crocker & Overell Standards Track [Page 6] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + +3.4 Value Range Alternatives %c##-## + + A range of alternative numeric values can be specified compactly, + using dash ("-") to indicate the range of alternative values. Hence: + + DIGIT = %x30-39 + + is equivalent to: + + DIGIT = "0" / "1" / "2" / "3" / "4" / "5" / "6" / + + "7" / "8" / "9" + + Concatenated numeric values and numeric value ranges can not be + specified in the same string. A numeric value may use the dotted + notation for concatenation or it may use the dash notation to specify + one value range. Hence, to specify one printable character, between + end of line sequences, the specification could be: + + char-line = %x0D.0A %x20-7E %x0D.0A + +3.5 Sequence Group (Rule1 Rule2) + + Elements enclosed in parentheses are treated as a single element, + whose contents are STRICTLY ORDERED. Thus, + + elem (foo / bar) blat + + which matches (elem foo blat) or (elem bar blat). + + elem foo / bar blat + + matches (elem foo) or (bar blat). + + NOTE: It is strongly advised to use grouping + notation, rather than to rely on proper reading of + "bare" alternations, when alternatives consist of + multiple rule names or literals. + + Hence it is recommended that instead of the above form, the form: + + (elem foo) / (bar blat) + + be used. It will avoid misinterpretation by casual readers. + + The sequence group notation is also used within free text to set off + an element sequence from the prose. + + + + +Crocker & Overell Standards Track [Page 7] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + +3.6 Variable Repetition *Rule + + The operator "*" preceding an element indicates repetition. The full + form is: + + *element + + where and are optional decimal values, indicating at least + and at most occurrences of element. + + Default values are 0 and infinity so that * allows any + number, including zero; 1* requires at least one; + 3*3 allows exactly 3 and 1*2 allows one or two. + +3.7 Specific Repetition nRule + + A rule of the form: + + element + + is equivalent to + + *element + + That is, exactly occurrences of . Thus 2DIGIT is a + 2-digit number, and 3ALPHA is a string of three alphabetic + characters. + +3.8 Optional Sequence [RULE] + + Square brackets enclose an optional element sequence: + + [foo bar] + + is equivalent to + + *1(foo bar). + +3.9 ; Comment + + A semi-colon starts a comment that continues to the end of line. + This is a simple way of including useful notes in parallel with the + specifications. + + + + + + + + +Crocker & Overell Standards Track [Page 8] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + +3.10 Operator Precedence + + The various mechanisms described above have the following precedence, + from highest (binding tightest) at the top, to lowest and loosest at + the bottom: + + Strings, Names formation + Comment + Value range + Repetition + Grouping, Optional + Concatenation + Alternative + + Use of the alternative operator, freely mixed with concatenations can + be confusing. + + Again, it is recommended that the grouping operator be used to + make explicit concatenation groups. + +4. ABNF DEFINITION OF ABNF + + This syntax uses the rules provided in Appendix A (Core). + + rulelist = 1*( rule / (*c-wsp c-nl) ) + + rule = rulename defined-as elements c-nl + ; continues if next line starts + ; with white space + + rulename = ALPHA *(ALPHA / DIGIT / "-") + + defined-as = *c-wsp ("=" / "=/") *c-wsp + ; basic rules definition and + ; incremental alternatives + + elements = alternation *c-wsp + + c-wsp = WSP / (c-nl WSP) + + c-nl = comment / CRLF + ; comment or newline + + comment = ";" *(WSP / VCHAR) CRLF + + alternation = concatenation + *(*c-wsp "/" *c-wsp concatenation) + + + + +Crocker & Overell Standards Track [Page 9] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + concatenation = repetition *(1*c-wsp repetition) + + repetition = [repeat] element + + repeat = 1*DIGIT / (*DIGIT "*" *DIGIT) + + element = rulename / group / option / + char-val / num-val / prose-val + + group = "(" *c-wsp alternation *c-wsp ")" + + option = "[" *c-wsp alternation *c-wsp "]" + + char-val = DQUOTE *(%x20-21 / %x23-7E) DQUOTE + ; quoted string of SP and VCHAR + without DQUOTE + + num-val = "%" (bin-val / dec-val / hex-val) + + bin-val = "b" 1*BIT + [ 1*("." 1*BIT) / ("-" 1*BIT) ] + ; series of concatenated bit values + ; or single ONEOF range + + dec-val = "d" 1*DIGIT + [ 1*("." 1*DIGIT) / ("-" 1*DIGIT) ] + + hex-val = "x" 1*HEXDIG + [ 1*("." 1*HEXDIG) / ("-" 1*HEXDIG) ] + + prose-val = "<" *(%x20-3D / %x3F-7E) ">" + ; bracketed string of SP and VCHAR + without angles + ; prose description, to be used as + last resort + + +5. SECURITY CONSIDERATIONS + + Security is truly believed to be irrelevant to this document. + + + + + + + + + + + +Crocker & Overell Standards Track [Page 10] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + +6. APPENDIX A - CORE + + This Appendix is provided as a convenient core for specific grammars. + The definitions may be used as a core set of rules. + +6.1 Core Rules + + Certain basic rules are in uppercase, such as SP, HTAB, CRLF, + DIGIT, ALPHA, etc. + + ALPHA = %x41-5A / %x61-7A ; A-Z / a-z + + BIT = "0" / "1" + + CHAR = %x01-7F + ; any 7-bit US-ASCII character, + excluding NUL + + CR = %x0D + ; carriage return + + CRLF = CR LF + ; Internet standard newline + + CTL = %x00-1F / %x7F + ; controls + + DIGIT = %x30-39 + ; 0-9 + + DQUOTE = %x22 + ; " (Double Quote) + + HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" + + HTAB = %x09 + ; horizontal tab + + LF = %x0A + ; linefeed + + LWSP = *(WSP / CRLF WSP) + ; linear white space (past newline) + + OCTET = %x00-FF + ; 8 bits of data + + SP = %x20 + + + +Crocker & Overell Standards Track [Page 11] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + ; space + + VCHAR = %x21-7E + ; visible (printing) characters + + WSP = SP / HTAB + ; white space + +6.2 Common Encoding + + Externally, data are represented as "network virtual ASCII", namely + 7-bit US-ASCII in an 8-bit field, with the high (8th) bit set to + zero. A string of values is in "network byte order" with the + higher-valued bytes represented on the left-hand side and being sent + over the network first. + +7. ACKNOWLEDGMENTS + + The syntax for ABNF was originally specified in RFC 733. Ken L. + Harrenstien, of SRI International, was responsible for re-coding the + BNF into an augmented BNF that makes the representation smaller and + easier to understand. + + This recent project began as a simple effort to cull out the portion + of RFC 822 which has been repeatedly cited by non-email specification + writers, namely the description of augmented BNF. Rather than simply + and blindly converting the existing text into a separate document, + the working group chose to give careful consideration to the + deficiencies, as well as benefits, of the existing specification and + related specifications available over the last 15 years and therefore + to pursue enhancement. This turned the project into something rather + more ambitious than first intended. Interestingly the result is not + massively different from that original, although decisions such as + removing the list notation came as a surprise. + + The current round of specification was part of the DRUMS working + group, with significant contributions from Jerome Abela , Harald + Alvestrand, Robert Elz, Roger Fajman, Aviva Garrett, Tom Harsch, Dan + Kohn, Bill McQuillan, Keith Moore, Chris Newman , Pete Resnick and + Henning Schulzrinne. + + + + + + + + + + + +Crocker & Overell Standards Track [Page 12] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + +8. REFERENCES + + [US-ASCII] Coded Character Set--7-Bit American Standard Code for + Information Interchange, ANSI X3.4-1986. + + [RFC733] Crocker, D., Vittal, J., Pogran, K., and D. Henderson, + "Standard for the Format of ARPA Network Text Message," RFC 733, + November 1977. + + [RFC822] Crocker, D., "Standard for the Format of ARPA Internet Text + Messages", STD 11, RFC 822, August 1982. + +9. CONTACT + + David H. Crocker Paul Overell + + Internet Mail Consortium Demon Internet Ltd + 675 Spruce Dr. Dorking Business Park + Sunnyvale, CA 94086 USA Dorking + Surrey, RH4 1HN + UK + + Phone: +1 408 246 8253 + Fax: +1 408 249 6205 + EMail: dcrocker@imc.org paulo@turnpike.com + + + + + + + + + + + + + + + + + + + + + + + + + + +Crocker & Overell Standards Track [Page 13] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + +10. Full Copyright Statement + + Copyright (C) The Internet Society (1997). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Crocker & Overell Standards Track [Page 14] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2440.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2440.txt new file mode 100644 index 0000000..902ecee --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2440.txt @@ -0,0 +1,3642 @@ + + + + + +Network Working Group J. Callas +Request for Comments: 2440 Network Associates +Category: Standards Track L. Donnerhacke + IN-Root-CA Individual Network e.V. + H. Finney + Network Associates + R. Thayer + EIS Corporation + November 1998 + + + OpenPGP Message Format + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1998). All Rights Reserved. + +IESG Note + + This document defines many tag values, yet it doesn't describe a + mechanism for adding new tags (for new features). Traditionally the + Internet Assigned Numbers Authority (IANA) handles the allocation of + new values for future expansion and RFCs usually define the procedure + to be used by the IANA. However, there are subtle (and not so + subtle) interactions that may occur in this protocol between new + features and existing features which result in a significant + reduction in over all security. Therefore, this document does not + define an extension procedure. Instead requests to define new tag + values (say for new encryption algorithms for example) should be + forwarded to the IESG Security Area Directors for consideration or + forwarding to the appropriate IETF Working Group for consideration. + +Abstract + + This document is maintained in order to publish all necessary + information needed to develop interoperable applications based on the + OpenPGP format. It is not a step-by-step cookbook for writing an + application. It describes only the format and methods needed to read, + check, generate, and write conforming packets crossing any network. + It does not deal with storage and implementation questions. It does, + + + +Callas, et. al. Standards Track [Page 1] + +RFC 2440 OpenPGP Message Format November 1998 + + + however, discuss implementation issues necessary to avoid security + flaws. + + Open-PGP software uses a combination of strong public-key and + symmetric cryptography to provide security services for electronic + communications and data storage. These services include + confidentiality, key management, authentication, and digital + signatures. This document specifies the message formats used in + OpenPGP. + +Table of Contents + + Status of this Memo 1 + IESG Note 1 + Abstract 1 + Table of Contents 2 + 1. Introduction 4 + 1.1. Terms 5 + 2. General functions 5 + 2.1. Confidentiality via Encryption 5 + 2.2. Authentication via Digital signature 6 + 2.3. Compression 7 + 2.4. Conversion to Radix-64 7 + 2.5. Signature-Only Applications 7 + 3. Data Element Formats 7 + 3.1. Scalar numbers 8 + 3.2. Multi-Precision Integers 8 + 3.3. Key IDs 8 + 3.4. Text 8 + 3.5. Time fields 9 + 3.6. String-to-key (S2K) specifiers 9 + 3.6.1. String-to-key (S2k) specifier types 9 + 3.6.1.1. Simple S2K 9 + 3.6.1.2. Salted S2K 10 + 3.6.1.3. Iterated and Salted S2K 10 + 3.6.2. String-to-key usage 11 + 3.6.2.1. Secret key encryption 11 + 3.6.2.2. Symmetric-key message encryption 11 + 4. Packet Syntax 12 + 4.1. Overview 12 + 4.2. Packet Headers 12 + 4.2.1. Old-Format Packet Lengths 13 + 4.2.2. New-Format Packet Lengths 13 + 4.2.2.1. One-Octet Lengths 14 + 4.2.2.2. Two-Octet Lengths 14 + 4.2.2.3. Five-Octet Lengths 14 + 4.2.2.4. Partial Body Lengths 14 + 4.2.3. Packet Length Examples 14 + + + +Callas, et. al. Standards Track [Page 2] + +RFC 2440 OpenPGP Message Format November 1998 + + + 4.3. Packet Tags 15 + 5. Packet Types 16 + 5.1. Public-Key Encrypted Session Key Packets (Tag 1) 16 + 5.2. Signature Packet (Tag 2) 17 + 5.2.1. Signature Types 17 + 5.2.2. Version 3 Signature Packet Format 19 + 5.2.3. Version 4 Signature Packet Format 21 + 5.2.3.1. Signature Subpacket Specification 22 + 5.2.3.2. Signature Subpacket Types 24 + 5.2.3.3. Signature creation time 25 + 5.2.3.4. Issuer 25 + 5.2.3.5. Key expiration time 25 + 5.2.3.6. Preferred symmetric algorithms 25 + 5.2.3.7. Preferred hash algorithms 25 + 5.2.3.8. Preferred compression algorithms 26 + 5.2.3.9. Signature expiration time 26 + 5.2.3.10.Exportable Certification 26 + 5.2.3.11.Revocable 27 + 5.2.3.12.Trust signature 27 + 5.2.3.13.Regular expression 27 + 5.2.3.14.Revocation key 27 + 5.2.3.15.Notation Data 28 + 5.2.3.16.Key server preferences 28 + 5.2.3.17.Preferred key server 29 + 5.2.3.18.Primary user id 29 + 5.2.3.19.Policy URL 29 + 5.2.3.20.Key Flags 29 + 5.2.3.21.Signer's User ID 30 + 5.2.3.22.Reason for Revocation 30 + 5.2.4. Computing Signatures 31 + 5.2.4.1. Subpacket Hints 32 + 5.3. Symmetric-Key Encrypted Session-Key Packets (Tag 3) 32 + 5.4. One-Pass Signature Packets (Tag 4) 33 + 5.5. Key Material Packet 34 + 5.5.1. Key Packet Variants 34 + 5.5.1.1. Public Key Packet (Tag 6) 34 + 5.5.1.2. Public Subkey Packet (Tag 14) 34 + 5.5.1.3. Secret Key Packet (Tag 5) 35 + 5.5.1.4. Secret Subkey Packet (Tag 7) 35 + 5.5.2. Public Key Packet Formats 35 + 5.5.3. Secret Key Packet Formats 37 + 5.6. Compressed Data Packet (Tag 8) 38 + 5.7. Symmetrically Encrypted Data Packet (Tag 9) 39 + 5.8. Marker Packet (Obsolete Literal Packet) (Tag 10) 39 + 5.9. Literal Data Packet (Tag 11) 40 + 5.10. Trust Packet (Tag 12) 40 + 5.11. User ID Packet (Tag 13) 41 + 6. Radix-64 Conversions 41 + + + +Callas, et. al. Standards Track [Page 3] + +RFC 2440 OpenPGP Message Format November 1998 + + + 6.1. An Implementation of the CRC-24 in "C" 42 + 6.2. Forming ASCII Armor 42 + 6.3. Encoding Binary in Radix-64 44 + 6.4. Decoding Radix-64 46 + 6.5. Examples of Radix-64 46 + 6.6. Example of an ASCII Armored Message 47 + 7. Cleartext signature framework 47 + 7.1. Dash-Escaped Text 47 + 8. Regular Expressions 48 + 9. Constants 49 + 9.1. Public Key Algorithms 49 + 9.2. Symmetric Key Algorithms 49 + 9.3. Compression Algorithms 50 + 9.4. Hash Algorithms 50 + 10. Packet Composition 50 + 10.1. Transferable Public Keys 50 + 10.2. OpenPGP Messages 52 + 10.3. Detached Signatures 52 + 11. Enhanced Key Formats 52 + 11.1. Key Structures 52 + 11.2. Key IDs and Fingerprints 53 + 12. Notes on Algorithms 54 + 12.1. Symmetric Algorithm Preferences 54 + 12.2. Other Algorithm Preferences 55 + 12.2.1. Compression Preferences 56 + 12.2.2. Hash Algorithm Preferences 56 + 12.3. Plaintext 56 + 12.4. RSA 56 + 12.5. Elgamal 57 + 12.6. DSA 58 + 12.7. Reserved Algorithm Numbers 58 + 12.8. OpenPGP CFB mode 58 + 13. Security Considerations 59 + 14. Implementation Nits 60 + 15. Authors and Working Group Chair 62 + 16. References 63 + 17. Full Copyright Statement 65 + +1. Introduction + + This document provides information on the message-exchange packet + formats used by OpenPGP to provide encryption, decryption, signing, + and key management functions. It builds on the foundation provided in + RFC 1991 "PGP Message Exchange Formats." + + + + + + + +Callas, et. al. Standards Track [Page 4] + +RFC 2440 OpenPGP Message Format November 1998 + + +1.1. Terms + + * OpenPGP - This is a definition for security software that uses + PGP 5.x as a basis. + + * PGP - Pretty Good Privacy. PGP is a family of software systems + developed by Philip R. Zimmermann from which OpenPGP is based. + + * PGP 2.6.x - This version of PGP has many variants, hence the term + PGP 2.6.x. It used only RSA, MD5, and IDEA for its cryptographic + transforms. An informational RFC, RFC 1991, was written + describing this version of PGP. + + * PGP 5.x - This version of PGP is formerly known as "PGP 3" in the + community and also in the predecessor of this document, RFC 1991. + It has new formats and corrects a number of problems in the PGP + 2.6.x design. It is referred to here as PGP 5.x because that + software was the first release of the "PGP 3" code base. + + "PGP", "Pretty Good", and "Pretty Good Privacy" are trademarks of + Network Associates, Inc. and are used with permission. + + This document uses the terms "MUST", "SHOULD", and "MAY" as defined + in RFC 2119, along with the negated forms of those terms. + +2. General functions + + OpenPGP provides data integrity services for messages and data files + by using these core technologies: + + - digital signatures + + - encryption + + - compression + + - radix-64 conversion + + In addition, OpenPGP provides key management and certificate + services, but many of these are beyond the scope of this document. + +2.1. Confidentiality via Encryption + + OpenPGP uses two encryption methods to provide confidentiality: + symmetric-key encryption and public key encryption. With public-key + encryption, the object is encrypted using a symmetric encryption + algorithm. Each symmetric key is used only once. A new "session key" + is generated as a random number for each message. Since it is used + + + +Callas, et. al. Standards Track [Page 5] + +RFC 2440 OpenPGP Message Format November 1998 + + + only once, the session key is bound to the message and transmitted + with it. To protect the key, it is encrypted with the receiver's + public key. The sequence is as follows: + + 1. The sender creates a message. + + 2. The sending OpenPGP generates a random number to be used as a + session key for this message only. + + 3. The session key is encrypted using each recipient's public key. + These "encrypted session keys" start the message. + + 4. The sending OpenPGP encrypts the message using the session key, + which forms the remainder of the message. Note that the message + is also usually compressed. + + 5. The receiving OpenPGP decrypts the session key using the + recipient's private key. + + 6. The receiving OpenPGP decrypts the message using the session key. + If the message was compressed, it will be decompressed. + + With symmetric-key encryption, an object may be encrypted with a + symmetric key derived from a passphrase (or other shared secret), or + a two-stage mechanism similar to the public-key method described + above in which a session key is itself encrypted with a symmetric + algorithm keyed from a shared secret. + + Both digital signature and confidentiality services may be applied to + the same message. First, a signature is generated for the message and + attached to the message. Then, the message plus signature is + encrypted using a symmetric session key. Finally, the session key is + encrypted using public-key encryption and prefixed to the encrypted + block. + +2.2. Authentication via Digital signature + + The digital signature uses a hash code or message digest algorithm, + and a public-key signature algorithm. The sequence is as follows: + + 1. The sender creates a message. + + 2. The sending software generates a hash code of the message. + + 3. The sending software generates a signature from the hash code + using the sender's private key. + + 4. The binary signature is attached to the message. + + + +Callas, et. al. Standards Track [Page 6] + +RFC 2440 OpenPGP Message Format November 1998 + + + 5. The receiving software keeps a copy of the message signature. + + 6. The receiving software generates a new hash code for the + received message and verifies it using the message's signature. + If the verification is successful, the message is accepted as + authentic. + +2.3. Compression + + OpenPGP implementations MAY compress the message after applying the + signature but before encryption. + +2.4. Conversion to Radix-64 + + OpenPGP's underlying native representation for encrypted messages, + signature certificates, and keys is a stream of arbitrary octets. + Some systems only permit the use of blocks consisting of seven-bit, + printable text. For transporting OpenPGP's native raw binary octets + through channels that are not safe to raw binary data, a printable + encoding of these binary octets is needed. OpenPGP provides the + service of converting the raw 8-bit binary octet stream to a stream + of printable ASCII characters, called Radix-64 encoding or ASCII + Armor. + + Implementations SHOULD provide Radix-64 conversions. + + Note that many applications, particularly messaging applications, + will want more advanced features as described in the OpenPGP-MIME + document, RFC 2015. An application that implements OpenPGP for + messaging SHOULD implement OpenPGP-MIME. + +2.5. Signature-Only Applications + + OpenPGP is designed for applications that use both encryption and + signatures, but there are a number of problems that are solved by a + signature-only implementation. Although this specification requires + both encryption and signatures, it is reasonable for there to be + subset implementations that are non-comformant only in that they omit + encryption. + +3. Data Element Formats + + This section describes the data elements used by OpenPGP. + + + + + + + + +Callas, et. al. Standards Track [Page 7] + +RFC 2440 OpenPGP Message Format November 1998 + + +3.1. Scalar numbers + + Scalar numbers are unsigned, and are always stored in big-endian + format. Using n[k] to refer to the kth octet being interpreted, the + value of a two-octet scalar is ((n[0] << 8) + n[1]). The value of a + four-octet scalar is ((n[0] << 24) + (n[1] << 16) + (n[2] << 8) + + n[3]). + +3.2. Multi-Precision Integers + + Multi-Precision Integers (also called MPIs) are unsigned integers + used to hold large integers such as the ones used in cryptographic + calculations. + + An MPI consists of two pieces: a two-octet scalar that is the length + of the MPI in bits followed by a string of octets that contain the + actual integer. + + These octets form a big-endian number; a big-endian number can be + made into an MPI by prefixing it with the appropriate length. + + Examples: + + (all numbers are in hexadecimal) + + The string of octets [00 01 01] forms an MPI with the value 1. The + string [00 09 01 FF] forms an MPI with the value of 511. + + Additional rules: + + The size of an MPI is ((MPI.length + 7) / 8) + 2 octets. + + The length field of an MPI describes the length starting from its + most significant non-zero bit. Thus, the MPI [00 02 01] is not formed + correctly. It should be [00 01 01]. + +3.3. Key IDs + + A Key ID is an eight-octet scalar that identifies a key. + Implementations SHOULD NOT assume that Key IDs are unique. The + section, "Enhanced Key Formats" below describes how Key IDs are + formed. + +3.4. Text + + The default character set for text is the UTF-8 [RFC2279] encoding of + Unicode [ISO10646]. + + + + +Callas, et. al. Standards Track [Page 8] + +RFC 2440 OpenPGP Message Format November 1998 + + +3.5. Time fields + + A time field is an unsigned four-octet number containing the number + of seconds elapsed since midnight, 1 January 1970 UTC. + +3.6. String-to-key (S2K) specifiers + + String-to-key (S2K) specifiers are used to convert passphrase strings + into symmetric-key encryption/decryption keys. They are used in two + places, currently: to encrypt the secret part of private keys in the + private keyring, and to convert passphrases to encryption keys for + symmetrically encrypted messages. + +3.6.1. String-to-key (S2k) specifier types + + There are three types of S2K specifiers currently supported, as + follows: + +3.6.1.1. Simple S2K + + This directly hashes the string to produce the key data. See below + for how this hashing is done. + + Octet 0: 0x00 + Octet 1: hash algorithm + + Simple S2K hashes the passphrase to produce the session key. The + manner in which this is done depends on the size of the session key + (which will depend on the cipher used) and the size of the hash + algorithm's output. If the hash size is greater than or equal to the + session key size, the high-order (leftmost) octets of the hash are + used as the key. + + If the hash size is less than the key size, multiple instances of the + hash context are created -- enough to produce the required key data. + These instances are preloaded with 0, 1, 2, ... octets of zeros (that + is to say, the first instance has no preloading, the second gets + preloaded with 1 octet of zero, the third is preloaded with two + octets of zeros, and so forth). + + As the data is hashed, it is given independently to each hash + context. Since the contexts have been initialized differently, they + will each produce different hash output. Once the passphrase is + hashed, the output data from the multiple hashes is concatenated, + first hash leftmost, to produce the key data, with any excess octets + on the right discarded. + + + + + +Callas, et. al. Standards Track [Page 9] + +RFC 2440 OpenPGP Message Format November 1998 + + +3.6.1.2. Salted S2K + + This includes a "salt" value in the S2K specifier -- some arbitrary + data -- that gets hashed along with the passphrase string, to help + prevent dictionary attacks. + + Octet 0: 0x01 + Octet 1: hash algorithm + Octets 2-9: 8-octet salt value + + Salted S2K is exactly like Simple S2K, except that the input to the + hash function(s) consists of the 8 octets of salt from the S2K + specifier, followed by the passphrase. + +3.6.1.3. Iterated and Salted S2K + + This includes both a salt and an octet count. The salt is combined + with the passphrase and the resulting value is hashed repeatedly. + This further increases the amount of work an attacker must do to try + dictionary attacks. + + Octet 0: 0x03 + Octet 1: hash algorithm + Octets 2-9: 8-octet salt value + Octet 10: count, a one-octet, coded value + + The count is coded into a one-octet number using the following + formula: + + #define EXPBIAS 6 + count = ((Int32)16 + (c & 15)) << ((c >> 4) + EXPBIAS); + + The above formula is in C, where "Int32" is a type for a 32-bit + integer, and the variable "c" is the coded count, Octet 10. + + Iterated-Salted S2K hashes the passphrase and salt data multiple + times. The total number of octets to be hashed is specified in the + encoded count in the S2K specifier. Note that the resulting count + value is an octet count of how many octets will be hashed, not an + iteration count. + + Initially, one or more hash contexts are set up as with the other S2K + algorithms, depending on how many octets of key data are needed. + Then the salt, followed by the passphrase data is repeatedly hashed + until the number of octets specified by the octet count has been + hashed. The one exception is that if the octet count is less than + the size of the salt plus passphrase, the full salt plus passphrase + will be hashed even though that is greater than the octet count. + + + +Callas, et. al. Standards Track [Page 10] + +RFC 2440 OpenPGP Message Format November 1998 + + + After the hashing is done the data is unloaded from the hash + context(s) as with the other S2K algorithms. + +3.6.2. String-to-key usage + + Implementations SHOULD use salted or iterated-and-salted S2K + specifiers, as simple S2K specifiers are more vulnerable to + dictionary attacks. + +3.6.2.1. Secret key encryption + + An S2K specifier can be stored in the secret keyring to specify how + to convert the passphrase to a key that unlocks the secret data. + Older versions of PGP just stored a cipher algorithm octet preceding + the secret data or a zero to indicate that the secret data was + unencrypted. The MD5 hash function was always used to convert the + passphrase to a key for the specified cipher algorithm. + + For compatibility, when an S2K specifier is used, the special value + 255 is stored in the position where the hash algorithm octet would + have been in the old data structure. This is then followed + immediately by a one-octet algorithm identifier, and then by the S2K + specifier as encoded above. + + Therefore, preceding the secret data there will be one of these + possibilities: + + 0: secret data is unencrypted (no pass phrase) + 255: followed by algorithm octet and S2K specifier + Cipher alg: use Simple S2K algorithm using MD5 hash + + This last possibility, the cipher algorithm number with an implicit + use of MD5 and IDEA, is provided for backward compatibility; it MAY + be understood, but SHOULD NOT be generated, and is deprecated. + + These are followed by an 8-octet Initial Vector for the decryption of + the secret values, if they are encrypted, and then the secret key + values themselves. + +3.6.2.2. Symmetric-key message encryption + + OpenPGP can create a Symmetric-key Encrypted Session Key (ESK) packet + at the front of a message. This is used to allow S2K specifiers to + be used for the passphrase conversion or to create messages with a + mix of symmetric-key ESKs and public-key ESKs. This allows a message + to be decrypted either with a passphrase or a public key. + + + + + +Callas, et. al. Standards Track [Page 11] + +RFC 2440 OpenPGP Message Format November 1998 + + + PGP 2.X always used IDEA with Simple string-to-key conversion when + encrypting a message with a symmetric algorithm. This is deprecated, + but MAY be used for backward-compatibility. + +4. Packet Syntax + + This section describes the packets used by OpenPGP. + +4.1. Overview + + An OpenPGP message is constructed from a number of records that are + traditionally called packets. A packet is a chunk of data that has a + tag specifying its meaning. An OpenPGP message, keyring, certificate, + and so forth consists of a number of packets. Some of those packets + may contain other OpenPGP packets (for example, a compressed data + packet, when uncompressed, contains OpenPGP packets). + + Each packet consists of a packet header, followed by the packet body. + The packet header is of variable length. + +4.2. Packet Headers + + The first octet of the packet header is called the "Packet Tag." It + determines the format of the header and denotes the packet contents. + The remainder of the packet header is the length of the packet. + + Note that the most significant bit is the left-most bit, called bit + 7. A mask for this bit is 0x80 in hexadecimal. + + +---------------+ + PTag |7 6 5 4 3 2 1 0| + +---------------+ + Bit 7 -- Always one + Bit 6 -- New packet format if set + + PGP 2.6.x only uses old format packets. Thus, software that + interoperates with those versions of PGP must only use old format + packets. If interoperability is not an issue, either format may be + used. Note that old format packets have four bits of content tags, + and new format packets have six; some features cannot be used and + still be backward-compatible. + + Old format packets contain: + + Bits 5-2 -- content tag + Bits 1-0 - length-type + + + + + +Callas, et. al. Standards Track [Page 12] + +RFC 2440 OpenPGP Message Format November 1998 + + + New format packets contain: + + Bits 5-0 -- content tag + +4.2.1. Old-Format Packet Lengths + + The meaning of the length-type in old-format packets is: + + 0 - The packet has a one-octet length. The header is 2 octets long. + + 1 - The packet has a two-octet length. The header is 3 octets long. + + 2 - The packet has a four-octet length. The header is 5 octets long. + + 3 - The packet is of indeterminate length. The header is 1 octet + long, and the implementation must determine how long the packet + is. If the packet is in a file, this means that the packet + extends until the end of the file. In general, an implementation + SHOULD NOT use indeterminate length packets except where the end + of the data will be clear from the context, and even then it is + better to use a definite length, or a new-format header. The + new-format headers described below have a mechanism for precisely + encoding data of indeterminate length. + +4.2.2. New-Format Packet Lengths + + New format packets have four possible ways of encoding length: + + 1. A one-octet Body Length header encodes packet lengths of up to + 191 octets. + + 2. A two-octet Body Length header encodes packet lengths of 192 to + 8383 octets. + + 3. A five-octet Body Length header encodes packet lengths of up to + 4,294,967,295 (0xFFFFFFFF) octets in length. (This actually + encodes a four-octet scalar number.) + + 4. When the length of the packet body is not known in advance by the + issuer, Partial Body Length headers encode a packet of + indeterminate length, effectively making it a stream. + + + + + + + + + + +Callas, et. al. Standards Track [Page 13] + +RFC 2440 OpenPGP Message Format November 1998 + + +4.2.2.1. One-Octet Lengths + + A one-octet Body Length header encodes a length of from 0 to 191 + octets. This type of length header is recognized because the one + octet value is less than 192. The body length is equal to: + + bodyLen = 1st_octet; + +4.2.2.2. Two-Octet Lengths + + A two-octet Body Length header encodes a length of from 192 to 8383 + octets. It is recognized because its first octet is in the range 192 + to 223. The body length is equal to: + + bodyLen = ((1st_octet - 192) << 8) + (2nd_octet) + 192 + +4.2.2.3. Five-Octet Lengths + + A five-octet Body Length header consists of a single octet holding + the value 255, followed by a four-octet scalar. The body length is + equal to: + + bodyLen = (2nd_octet << 24) | (3rd_octet << 16) | + (4th_octet << 8) | 5th_octet + +4.2.2.4. Partial Body Lengths + + A Partial Body Length header is one octet long and encodes the length + of only part of the data packet. This length is a power of 2, from 1 + to 1,073,741,824 (2 to the 30th power). It is recognized by its one + octet value that is greater than or equal to 224, and less than 255. + The partial body length is equal to: + + partialBodyLen = 1 << (1st_octet & 0x1f); + + Each Partial Body Length header is followed by a portion of the + packet body data. The Partial Body Length header specifies this + portion's length. Another length header (of one of the three types -- + one octet, two-octet, or partial) follows that portion. The last + length header in the packet MUST NOT be a partial Body Length header. + Partial Body Length headers may only be used for the non-final parts + of the packet. + +4.2.3. Packet Length Examples + + These examples show ways that new-format packets might encode the + packet lengths. + + + + +Callas, et. al. Standards Track [Page 14] + +RFC 2440 OpenPGP Message Format November 1998 + + + A packet with length 100 may have its length encoded in one octet: + 0x64. This is followed by 100 octets of data. + + A packet with length 1723 may have its length coded in two octets: + 0xC5, 0xFB. This header is followed by the 1723 octets of data. + + A packet with length 100000 may have its length encoded in five + octets: 0xFF, 0x00, 0x01, 0x86, 0xA0. + + It might also be encoded in the following octet stream: 0xEF, first + 32768 octets of data; 0xE1, next two octets of data; 0xE0, next one + octet of data; 0xF0, next 65536 octets of data; 0xC5, 0xDD, last 1693 + octets of data. This is just one possible encoding, and many + variations are possible on the size of the Partial Body Length + headers, as long as a regular Body Length header encodes the last + portion of the data. Note also that the last Body Length header can + be a zero-length header. + + An implementation MAY use Partial Body Lengths for data packets, be + they literal, compressed, or encrypted. The first partial length MUST + be at least 512 octets long. Partial Body Lengths MUST NOT be used + for any other packet types. + + Please note that in all of these explanations, the total length of + the packet is the length of the header(s) plus the length of the + body. + +4.3. Packet Tags + + The packet tag denotes what type of packet the body holds. Note that + old format headers can only have tags less than 16, whereas new + format headers can have tags as great as 63. The defined tags (in + decimal) are: + + 0 -- Reserved - a packet tag must not have this value + 1 -- Public-Key Encrypted Session Key Packet + 2 -- Signature Packet + 3 -- Symmetric-Key Encrypted Session Key Packet + 4 -- One-Pass Signature Packet + 5 -- Secret Key Packet + 6 -- Public Key Packet + 7 -- Secret Subkey Packet + 8 -- Compressed Data Packet + 9 -- Symmetrically Encrypted Data Packet + 10 -- Marker Packet + 11 -- Literal Data Packet + 12 -- Trust Packet + + + + +Callas, et. al. Standards Track [Page 15] + +RFC 2440 OpenPGP Message Format November 1998 + + + 13 -- User ID Packet + 14 -- Public Subkey Packet + 60 to 63 -- Private or Experimental Values + +5. Packet Types + +5.1. Public-Key Encrypted Session Key Packets (Tag 1) + + A Public-Key Encrypted Session Key packet holds the session key used + to encrypt a message. Zero or more Encrypted Session Key packets + (either Public-Key or Symmetric-Key) may precede a Symmetrically + Encrypted Data Packet, which holds an encrypted message. The message + is encrypted with the session key, and the session key is itself + encrypted and stored in the Encrypted Session Key packet(s). The + Symmetrically Encrypted Data Packet is preceded by one Public-Key + Encrypted Session Key packet for each OpenPGP key to which the + message is encrypted. The recipient of the message finds a session + key that is encrypted to their public key, decrypts the session key, + and then uses the session key to decrypt the message. + + The body of this packet consists of: + + - A one-octet number giving the version number of the packet type. + The currently defined value for packet version is 3. An + implementation should accept, but not generate a version of 2, + which is equivalent to V3 in all other respects. + + - An eight-octet number that gives the key ID of the public key + that the session key is encrypted to. + + - A one-octet number giving the public key algorithm used. + + - A string of octets that is the encrypted session key. This string + takes up the remainder of the packet, and its contents are + dependent on the public key algorithm used. + + Algorithm Specific Fields for RSA encryption + + - multiprecision integer (MPI) of RSA encrypted value m**e mod n. + + Algorithm Specific Fields for Elgamal encryption: + + - MPI of Elgamal (Diffie-Hellman) value g**k mod p. + + - MPI of Elgamal (Diffie-Hellman) value m * y**k mod p. + + + + + + +Callas, et. al. Standards Track [Page 16] + +RFC 2440 OpenPGP Message Format November 1998 + + + The value "m" in the above formulas is derived from the session key + as follows. First the session key is prefixed with a one-octet + algorithm identifier that specifies the symmetric encryption + algorithm used to encrypt the following Symmetrically Encrypted Data + Packet. Then a two-octet checksum is appended which is equal to the + sum of the preceding session key octets, not including the algorithm + identifier, modulo 65536. This value is then padded as described in + PKCS-1 block type 02 [RFC2313] to form the "m" value used in the + formulas above. + + Note that when an implementation forms several PKESKs with one + session key, forming a message that can be decrypted by several keys, + the implementation MUST make new PKCS-1 padding for each key. + + An implementation MAY accept or use a Key ID of zero as a "wild card" + or "speculative" Key ID. In this case, the receiving implementation + would try all available private keys, checking for a valid decrypted + session key. This format helps reduce traffic analysis of messages. + +5.2. Signature Packet (Tag 2) + + A signature packet describes a binding between some public key and + some data. The most common signatures are a signature of a file or a + block of text, and a signature that is a certification of a user ID. + + Two versions of signature packets are defined. Version 3 provides + basic signature information, while version 4 provides an expandable + format with subpackets that can specify more information about the + signature. PGP 2.6.x only accepts version 3 signatures. + + Implementations MUST accept V3 signatures. Implementations SHOULD + generate V4 signatures. Implementations MAY generate a V3 signature + that can be verified by PGP 2.6.x. + + Note that if an implementation is creating an encrypted and signed + message that is encrypted to a V3 key, it is reasonable to create a + V3 signature. + +5.2.1. Signature Types + + There are a number of possible meanings for a signature, which are + specified in a signature type octet in any given signature. These + meanings are: + + 0x00: Signature of a binary document. + Typically, this means the signer owns it, created it, or + certifies that it has not been modified. + + + + +Callas, et. al. Standards Track [Page 17] + +RFC 2440 OpenPGP Message Format November 1998 + + + 0x01: Signature of a canonical text document. + Typically, this means the signer owns it, created it, or + certifies that it has not been modified. The signature is + calculated over the text data with its line endings converted + to and trailing blanks removed. + + 0x02: Standalone signature. + This signature is a signature of only its own subpacket + contents. It is calculated identically to a signature over a + zero-length binary document. Note that it doesn't make sense to + have a V3 standalone signature. + + 0x10: Generic certification of a User ID and Public Key packet. + The issuer of this certification does not make any particular + assertion as to how well the certifier has checked that the + owner of the key is in fact the person described by the user + ID. Note that all PGP "key signatures" are this type of + certification. + + 0x11: Persona certification of a User ID and Public Key packet. + The issuer of this certification has not done any verification + of the claim that the owner of this key is the user ID + specified. + + 0x12: Casual certification of a User ID and Public Key packet. + The issuer of this certification has done some casual + verification of the claim of identity. + + 0x13: Positive certification of a User ID and Public Key packet. + The issuer of this certification has done substantial + verification of the claim of identity. + + Please note that the vagueness of these certification claims is + not a flaw, but a feature of the system. Because PGP places + final authority for validity upon the receiver of a + certification, it may be that one authority's casual + certification might be more rigorous than some other + authority's positive certification. These classifications allow + a certification authority to issue fine-grained claims. + + 0x18: Subkey Binding Signature + This signature is a statement by the top-level signing key + indicates that it owns the subkey. This signature is calculated + directly on the subkey itself, not on any User ID or other + packets. + + + + + + +Callas, et. al. Standards Track [Page 18] + +RFC 2440 OpenPGP Message Format November 1998 + + + 0x1F: Signature directly on a key + This signature is calculated directly on a key. It binds the + information in the signature subpackets to the key, and is + appropriate to be used for subpackets that provide information + about the key, such as the revocation key subpacket. It is also + appropriate for statements that non-self certifiers want to + make about the key itself, rather than the binding between a + key and a name. + + 0x20: Key revocation signature + The signature is calculated directly on the key being revoked. + A revoked key is not to be used. Only revocation signatures by + the key being revoked, or by an authorized revocation key, + should be considered valid revocation signatures. + + 0x28: Subkey revocation signature + The signature is calculated directly on the subkey being + revoked. A revoked subkey is not to be used. Only revocation + signatures by the top-level signature key that is bound to this + subkey, or by an authorized revocation key, should be + considered valid revocation signatures. + + 0x30: Certification revocation signature + This signature revokes an earlier user ID certification + signature (signature class 0x10 through 0x13). It should be + issued by the same key that issued the revoked signature or an + authorized revocation key The signature should have a later + creation date than the signature it revokes. + + 0x40: Timestamp signature. + This signature is only meaningful for the timestamp contained + in it. + +5.2.2. Version 3 Signature Packet Format + + The body of a version 3 Signature Packet contains: + + - One-octet version number (3). + + - One-octet length of following hashed material. MUST be 5. + + - One-octet signature type. + + - Four-octet creation time. + + - Eight-octet key ID of signer. + + - One-octet public key algorithm. + + + +Callas, et. al. Standards Track [Page 19] + +RFC 2440 OpenPGP Message Format November 1998 + + + - One-octet hash algorithm. + + - Two-octet field holding left 16 bits of signed hash value. + + - One or more multi-precision integers comprising the signature. + This portion is algorithm specific, as described below. + + The data being signed is hashed, and then the signature type and + creation time from the signature packet are hashed (5 additional + octets). The resulting hash value is used in the signature + algorithm. The high 16 bits (first two octets) of the hash are + included in the signature packet to provide a quick test to reject + some invalid signatures. + + Algorithm Specific Fields for RSA signatures: + + - multiprecision integer (MPI) of RSA signature value m**d. + + Algorithm Specific Fields for DSA signatures: + + - MPI of DSA value r. + + - MPI of DSA value s. + + The signature calculation is based on a hash of the signed data, as + described above. The details of the calculation are different for + DSA signature than for RSA signatures. + + With RSA signatures, the hash value is encoded as described in PKCS-1 + section 10.1.2, "Data encoding", producing an ASN.1 value of type + DigestInfo, and then padded using PKCS-1 block type 01 [RFC2313]. + This requires inserting the hash value as an octet string into an + ASN.1 structure. The object identifier for the type of hash being + used is included in the structure. The hexadecimal representations + for the currently defined hash algorithms are: + + - MD2: 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x02 + + - MD5: 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05 + + - RIPEMD-160: 0x2B, 0x24, 0x03, 0x02, 0x01 + + - SHA-1: 0x2B, 0x0E, 0x03, 0x02, 0x1A + + + + + + + + +Callas, et. al. Standards Track [Page 20] + +RFC 2440 OpenPGP Message Format November 1998 + + + The ASN.1 OIDs are: + + - MD2: 1.2.840.113549.2.2 + + - MD5: 1.2.840.113549.2.5 + + - RIPEMD-160: 1.3.36.3.2.1 + + - SHA-1: 1.3.14.3.2.26 + + The full hash prefixes for these are: + + MD2: 0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, + 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x02, 0x05, 0x00, + 0x04, 0x10 + + MD5: 0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, + 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00, + 0x04, 0x10 + + RIPEMD-160: 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x24, + 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 + + SHA-1: 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0E, + 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14 + + DSA signatures MUST use hashes with a size of 160 bits, to match q, + the size of the group generated by the DSA key's generator value. + The hash function result is treated as a 160 bit number and used + directly in the DSA signature algorithm. + +5.2.3. Version 4 Signature Packet Format + + The body of a version 4 Signature Packet contains: + + - One-octet version number (4). + + - One-octet signature type. + + - One-octet public key algorithm. + + - One-octet hash algorithm. + + - Two-octet scalar octet count for following hashed subpacket + data. Note that this is the length in octets of all of the hashed + subpackets; a pointer incremented by this number will skip over + the hashed subpackets. + + + + +Callas, et. al. Standards Track [Page 21] + +RFC 2440 OpenPGP Message Format November 1998 + + + - Hashed subpacket data. (zero or more subpackets) + + - Two-octet scalar octet count for following unhashed subpacket + data. Note that this is the length in octets of all of the + unhashed subpackets; a pointer incremented by this number will + skip over the unhashed subpackets. + + - Unhashed subpacket data. (zero or more subpackets) + + - Two-octet field holding left 16 bits of signed hash value. + + - One or more multi-precision integers comprising the signature. + This portion is algorithm specific, as described above. + + The data being signed is hashed, and then the signature data from the + version number through the hashed subpacket data (inclusive) is + hashed. The resulting hash value is what is signed. The left 16 bits + of the hash are included in the signature packet to provide a quick + test to reject some invalid signatures. + + There are two fields consisting of signature subpackets. The first + field is hashed with the rest of the signature data, while the second + is unhashed. The second set of subpackets is not cryptographically + protected by the signature and should include only advisory + information. + + The algorithms for converting the hash function result to a signature + are described in a section below. + +5.2.3.1. Signature Subpacket Specification + + The subpacket fields consist of zero or more signature subpackets. + Each set of subpackets is preceded by a two-octet scalar count of the + length of the set of subpackets. + + Each subpacket consists of a subpacket header and a body. The header + consists of: + + - the subpacket length (1, 2, or 5 octets) + + - the subpacket type (1 octet) + + and is followed by the subpacket specific data. + + The length includes the type octet but not this length. Its format is + similar to the "new" format packet header lengths, but cannot have + partial body lengths. That is: + + + + +Callas, et. al. Standards Track [Page 22] + +RFC 2440 OpenPGP Message Format November 1998 + + + if the 1st octet < 192, then + lengthOfLength = 1 + subpacketLen = 1st_octet + + if the 1st octet >= 192 and < 255, then + lengthOfLength = 2 + subpacketLen = ((1st_octet - 192) << 8) + (2nd_octet) + 192 + + if the 1st octet = 255, then + lengthOfLength = 5 + subpacket length = [four-octet scalar starting at 2nd_octet] + + The value of the subpacket type octet may be: + + 2 = signature creation time + 3 = signature expiration time + 4 = exportable certification + 5 = trust signature + 6 = regular expression + 7 = revocable + 9 = key expiration time + 10 = placeholder for backward compatibility + 11 = preferred symmetric algorithms + 12 = revocation key + 16 = issuer key ID + 20 = notation data + 21 = preferred hash algorithms + 22 = preferred compression algorithms + 23 = key server preferences + 24 = preferred key server + 25 = primary user id + 26 = policy URL + 27 = key flags + 28 = signer's user id + 29 = reason for revocation + 100 to 110 = internal or user-defined + + An implementation SHOULD ignore any subpacket of a type that it does + not recognize. + + Bit 7 of the subpacket type is the "critical" bit. If set, it + denotes that the subpacket is one that is critical for the evaluator + of the signature to recognize. If a subpacket is encountered that is + marked critical but is unknown to the evaluating software, the + evaluator SHOULD consider the signature to be in error. + + + + + + +Callas, et. al. Standards Track [Page 23] + +RFC 2440 OpenPGP Message Format November 1998 + + + An evaluator may "recognize" a subpacket, but not implement it. The + purpose of the critical bit is to allow the signer to tell an + evaluator that it would prefer a new, unknown feature to generate an + error than be ignored. + + Implementations SHOULD implement "preferences". + +5.2.3.2. Signature Subpacket Types + + A number of subpackets are currently defined. Some subpackets apply + to the signature itself and some are attributes of the key. + Subpackets that are found on a self-signature are placed on a user id + certification made by the key itself. Note that a key may have more + than one user id, and thus may have more than one self-signature, and + differing subpackets. + + A self-signature is a binding signature made by the key the signature + refers to. There are three types of self-signatures, the + certification signatures (types 0x10-0x13), the direct-key signature + (type 0x1f), and the subkey binding signature (type 0x18). For + certification self-signatures, each user ID may have a self- + signature, and thus different subpackets in those self-signatures. + For subkey binding signatures, each subkey in fact has a self- + signature. Subpackets that appear in a certification self-signature + apply to the username, and subpackets that appear in the subkey + self-signature apply to the subkey. Lastly, subpackets on the direct + key signature apply to the entire key. + + Implementing software should interpret a self-signature's preference + subpackets as narrowly as possible. For example, suppose a key has + two usernames, Alice and Bob. Suppose that Alice prefers the + symmetric algorithm CAST5, and Bob prefers IDEA or Triple-DES. If the + software locates this key via Alice's name, then the preferred + algorithm is CAST5, if software locates the key via Bob's name, then + the preferred algorithm is IDEA. If the key is located by key id, + then algorithm of the default user id of the key provides the default + symmetric algorithm. + + A subpacket may be found either in the hashed or unhashed subpacket + sections of a signature. If a subpacket is not hashed, then the + information in it cannot be considered definitive because it is not + part of the signature proper. + + + + + + + + + +Callas, et. al. Standards Track [Page 24] + +RFC 2440 OpenPGP Message Format November 1998 + + +5.2.3.3. Signature creation time + + (4 octet time field) + + The time the signature was made. + + MUST be present in the hashed area. + +5.2.3.4. Issuer + + (8 octet key ID) + + The OpenPGP key ID of the key issuing the signature. + +5.2.3.5. Key expiration time + + (4 octet time field) + + The validity period of the key. This is the number of seconds after + the key creation time that the key expires. If this is not present + or has a value of zero, the key never expires. This is found only on + a self-signature. + +5.2.3.6. Preferred symmetric algorithms + + (sequence of one-octet values) + + Symmetric algorithm numbers that indicate which algorithms the key + holder prefers to use. The subpacket body is an ordered list of + octets with the most preferred listed first. It is assumed that only + algorithms listed are supported by the recipient's software. + Algorithm numbers in section 9. This is only found on a self- + signature. + +5.2.3.7. Preferred hash algorithms + + (array of one-octet values) + + Message digest algorithm numbers that indicate which algorithms the + key holder prefers to receive. Like the preferred symmetric + algorithms, the list is ordered. Algorithm numbers are in section 6. + This is only found on a self-signature. + + + + + + + + + +Callas, et. al. Standards Track [Page 25] + +RFC 2440 OpenPGP Message Format November 1998 + + +5.2.3.8. Preferred compression algorithms + + (array of one-octet values) + + Compression algorithm numbers that indicate which algorithms the key + holder prefers to use. Like the preferred symmetric algorithms, the + list is ordered. Algorithm numbers are in section 6. If this + subpacket is not included, ZIP is preferred. A zero denotes that + uncompressed data is preferred; the key holder's software might have + no compression software in that implementation. This is only found on + a self-signature. + +5.2.3.9. Signature expiration time + + (4 octet time field) + + The validity period of the signature. This is the number of seconds + after the signature creation time that the signature expires. If this + is not present or has a value of zero, it never expires. + +5.2.3.10. Exportable Certification + + (1 octet of exportability, 0 for not, 1 for exportable) + + This subpacket denotes whether a certification signature is + "exportable", to be used by other users than the signature's issuer. + The packet body contains a boolean flag indicating whether the + signature is exportable. If this packet is not present, the + certification is exportable; it is equivalent to a flag containing a + 1. + + Non-exportable, or "local", certifications are signatures made by a + user to mark a key as valid within that user's implementation only. + Thus, when an implementation prepares a user's copy of a key for + transport to another user (this is the process of "exporting" the + key), any local certification signatures are deleted from the key. + + The receiver of a transported key "imports" it, and likewise trims + any local certifications. In normal operation, there won't be any, + assuming the import is performed on an exported key. However, there + are instances where this can reasonably happen. For example, if an + implementation allows keys to be imported from a key database in + addition to an exported key, then this situation can arise. + + Some implementations do not represent the interest of a single user + (for example, a key server). Such implementations always trim local + certifications from any key they handle. + + + + +Callas, et. al. Standards Track [Page 26] + +RFC 2440 OpenPGP Message Format November 1998 + + +5.2.3.11. Revocable + + (1 octet of revocability, 0 for not, 1 for revocable) + + Signature's revocability status. Packet body contains a boolean flag + indicating whether the signature is revocable. Signatures that are + not revocable have any later revocation signatures ignored. They + represent a commitment by the signer that he cannot revoke his + signature for the life of his key. If this packet is not present, + the signature is revocable. + +5.2.3.12. Trust signature + + (1 octet "level" (depth), 1 octet of trust amount) + + Signer asserts that the key is not only valid, but also trustworthy, + at the specified level. Level 0 has the same meaning as an ordinary + validity signature. Level 1 means that the signed key is asserted to + be a valid trusted introducer, with the 2nd octet of the body + specifying the degree of trust. Level 2 means that the signed key is + asserted to be trusted to issue level 1 trust signatures, i.e. that + it is a "meta introducer". Generally, a level n trust signature + asserts that a key is trusted to issue level n-1 trust signatures. + The trust amount is in a range from 0-255, interpreted such that + values less than 120 indicate partial trust and values of 120 or + greater indicate complete trust. Implementations SHOULD emit values + of 60 for partial trust and 120 for complete trust. + +5.2.3.13. Regular expression + + (null-terminated regular expression) + + Used in conjunction with trust signature packets (of level > 0) to + limit the scope of trust that is extended. Only signatures by the + target key on user IDs that match the regular expression in the body + of this packet have trust extended by the trust signature subpacket. + The regular expression uses the same syntax as the Henry Spencer's + "almost public domain" regular expression package. A description of + the syntax is found in a section below. + +5.2.3.14. Revocation key + + (1 octet of class, 1 octet of algid, 20 octets of fingerprint) + + Authorizes the specified key to issue revocation signatures for this + key. Class octet must have bit 0x80 set. If the bit 0x40 is set, + then this means that the revocation information is sensitive. Other + bits are for future expansion to other kinds of authorizations. This + + + +Callas, et. al. Standards Track [Page 27] + +RFC 2440 OpenPGP Message Format November 1998 + + + is found on a self-signature. + + If the "sensitive" flag is set, the keyholder feels this subpacket + contains private trust information that describes a real-world + sensitive relationship. If this flag is set, implementations SHOULD + NOT export this signature to other users except in cases where the + data needs to be available: when the signature is being sent to the + designated revoker, or when it is accompanied by a revocation + signature from that revoker. Note that it may be appropriate to + isolate this subpacket within a separate signature so that it is not + combined with other subpackets that need to be exported. + +5.2.3.15. Notation Data + + (4 octets of flags, 2 octets of name length (M), + 2 octets of value length (N), + M octets of name data, + N octets of value data) + + This subpacket describes a "notation" on the signature that the + issuer wishes to make. The notation has a name and a value, each of + which are strings of octets. There may be more than one notation in a + signature. Notations can be used for any extension the issuer of the + signature cares to make. The "flags" field holds four octets of + flags. + + All undefined flags MUST be zero. Defined flags are: + + First octet: 0x80 = human-readable. This note is text, a note + from one person to another, and has no + meaning to software. + Other octets: none. + +5.2.3.16. Key server preferences + + (N octets of flags) + + This is a list of flags that indicate preferences that the key holder + has about how the key is handled on a key server. All undefined flags + MUST be zero. + + First octet: 0x80 = No-modify + the key holder requests that this key only be modified or updated + by the key holder or an administrator of the key server. + + This is found only on a self-signature. + + + + + +Callas, et. al. Standards Track [Page 28] + +RFC 2440 OpenPGP Message Format November 1998 + + +5.2.3.17. Preferred key server + + (String) + + This is a URL of a key server that the key holder prefers be used for + updates. Note that keys with multiple user ids can have a preferred + key server for each user id. Note also that since this is a URL, the + key server can actually be a copy of the key retrieved by ftp, http, + finger, etc. + +5.2.3.18. Primary user id + + (1 octet, boolean) + + This is a flag in a user id's self signature that states whether this + user id is the main user id for this key. It is reasonable for an + implementation to resolve ambiguities in preferences, etc. by + referring to the primary user id. If this flag is absent, its value + is zero. If more than one user id in a key is marked as primary, the + implementation may resolve the ambiguity in any way it sees fit. + +5.2.3.19. Policy URL + + (String) + + This subpacket contains a URL of a document that describes the policy + that the signature was issued under. + +5.2.3.20. Key Flags + + (Octet string) + + This subpacket contains a list of binary flags that hold information + about a key. It is a string of octets, and an implementation MUST NOT + assume a fixed size. This is so it can grow over time. If a list is + shorter than an implementation expects, the unstated flags are + considered to be zero. The defined flags are: + + First octet: + + 0x01 - This key may be used to certify other keys. + + 0x02 - This key may be used to sign data. + + 0x04 - This key may be used to encrypt communications. + + 0x08 - This key may be used to encrypt storage. + + + + +Callas, et. al. Standards Track [Page 29] + +RFC 2440 OpenPGP Message Format November 1998 + + + 0x10 - The private component of this key may have been split by a + secret-sharing mechanism. + + 0x80 - The private component of this key may be in the possession + of more than one person. + + Usage notes: + + The flags in this packet may appear in self-signatures or in + certification signatures. They mean different things depending on who + is making the statement -- for example, a certification signature + that has the "sign data" flag is stating that the certification is + for that use. On the other hand, the "communications encryption" flag + in a self-signature is stating a preference that a given key be used + for communications. Note however, that it is a thorny issue to + determine what is "communications" and what is "storage." This + decision is left wholly up to the implementation; the authors of this + document do not claim any special wisdom on the issue, and realize + that accepted opinion may change. + + The "split key" (0x10) and "group key" (0x80) flags are placed on a + self-signature only; they are meaningless on a certification + signature. They SHOULD be placed only on a direct-key signature (type + 0x1f) or a subkey signature (type 0x18), one that refers to the key + the flag applies to. + +5.2.3.21. Signer's User ID + + This subpacket allows a keyholder to state which user id is + responsible for the signing. Many keyholders use a single key for + different purposes, such as business communications as well as + personal communications. This subpacket allows such a keyholder to + state which of their roles is making a signature. + +5.2.3.22. Reason for Revocation + + (1 octet of revocation code, N octets of reason string) + + This subpacket is used only in key revocation and certification + revocation signatures. It describes the reason why the key or + certificate was revoked. + + The first octet contains a machine-readable code that denotes the + reason for the revocation: + + + + + + + +Callas, et. al. Standards Track [Page 30] + +RFC 2440 OpenPGP Message Format November 1998 + + + 0x00 - No reason specified (key revocations or cert revocations) + 0x01 - Key is superceded (key revocations) + 0x02 - Key material has been compromised (key revocations) + 0x03 - Key is no longer used (key revocations) + 0x20 - User id information is no longer valid (cert revocations) + + Following the revocation code is a string of octets which gives + information about the reason for revocation in human-readable form + (UTF-8). The string may be null, that is, of zero length. The length + of the subpacket is the length of the reason string plus one. + +5.2.4. Computing Signatures + + All signatures are formed by producing a hash over the signature + data, and then using the resulting hash in the signature algorithm. + + The signature data is simple to compute for document signatures + (types 0x00 and 0x01), for which the document itself is the data. + For standalone signatures, this is a null string. + + When a signature is made over a key, the hash data starts with the + octet 0x99, followed by a two-octet length of the key, and then body + of the key packet. (Note that this is an old-style packet header for + a key packet with two-octet length.) A subkey signature (type 0x18) + then hashes the subkey, using the same format as the main key. Key + revocation signatures (types 0x20 and 0x28) hash only the key being + revoked. + + A certification signature (type 0x10 through 0x13) hashes the user id + being bound to the key into the hash context after the above data. A + V3 certification hashes the contents of the name packet, without any + header. A V4 certification hashes the constant 0xb4 (which is an + old-style packet header with the length-of-length set to zero), a + four-octet number giving the length of the username, and then the + username data. + + Once the data body is hashed, then a trailer is hashed. A V3 + signature hashes five octets of the packet body, starting from the + signature type field. This data is the signature type, followed by + the four-octet signature time. A V4 signature hashes the packet body + starting from its first field, the version number, through the end of + the hashed subpacket data. Thus, the fields hashed are the signature + version, the signature type, the public key algorithm, the hash + algorithm, the hashed subpacket length, and the hashed subpacket + body. + + + + + + +Callas, et. al. Standards Track [Page 31] + +RFC 2440 OpenPGP Message Format November 1998 + + + V4 signatures also hash in a final trailer of six octets: the version + of the signature packet, i.e. 0x04; 0xFF; a four-octet, big-endian + number that is the length of the hashed data from the signature + packet (note that this number does not include these final six + octets. + + After all this has been hashed, the resulting hash field is used in + the signature algorithm, and placed at the end of the signature + packet. + +5.2.4.1. Subpacket Hints + + An implementation SHOULD put the two mandatory subpackets, creation + time and issuer, as the first subpackets in the subpacket list, + simply to make it easier for the implementer to find them. + + It is certainly possible for a signature to contain conflicting + information in subpackets. For example, a signature may contain + multiple copies of a preference or multiple expiration times. In most + cases, an implementation SHOULD use the last subpacket in the + signature, but MAY use any conflict resolution scheme that makes more + sense. Please note that we are intentionally leaving conflict + resolution to the implementer; most conflicts are simply syntax + errors, and the wishy-washy language here allows a receiver to be + generous in what they accept, while putting pressure on a creator to + be stingy in what they generate. + + Some apparent conflicts may actually make sense -- for example, + suppose a keyholder has an V3 key and a V4 key that share the same + RSA key material. Either of these keys can verify a signature created + by the other, and it may be reasonable for a signature to contain an + issuer subpacket for each key, as a way of explicitly tying those + keys to the signature. + +5.3. Symmetric-Key Encrypted Session-Key Packets (Tag 3) + + The Symmetric-Key Encrypted Session Key packet holds the symmetric- + key encryption of a session key used to encrypt a message. Zero or + more Encrypted Session Key packets and/or Symmetric-Key Encrypted + Session Key packets may precede a Symmetrically Encrypted Data Packet + that holds an encrypted message. The message is encrypted with a + session key, and the session key is itself encrypted and stored in + the Encrypted Session Key packet or the Symmetric-Key Encrypted + Session Key packet. + + If the Symmetrically Encrypted Data Packet is preceded by one or more + Symmetric-Key Encrypted Session Key packets, each specifies a + passphrase that may be used to decrypt the message. This allows a + + + +Callas, et. al. Standards Track [Page 32] + +RFC 2440 OpenPGP Message Format November 1998 + + + message to be encrypted to a number of public keys, and also to one + or more pass phrases. This packet type is new, and is not generated + by PGP 2.x or PGP 5.0. + + The body of this packet consists of: + + - A one-octet version number. The only currently defined version + is 4. + + - A one-octet number describing the symmetric algorithm used. + + - A string-to-key (S2K) specifier, length as defined above. + + - Optionally, the encrypted session key itself, which is decrypted + with the string-to-key object. + + If the encrypted session key is not present (which can be detected on + the basis of packet length and S2K specifier size), then the S2K + algorithm applied to the passphrase produces the session key for + decrypting the file, using the symmetric cipher algorithm from the + Symmetric-Key Encrypted Session Key packet. + + If the encrypted session key is present, the result of applying the + S2K algorithm to the passphrase is used to decrypt just that + encrypted session key field, using CFB mode with an IV of all zeros. + The decryption result consists of a one-octet algorithm identifier + that specifies the symmetric-key encryption algorithm used to encrypt + the following Symmetrically Encrypted Data Packet, followed by the + session key octets themselves. + + Note: because an all-zero IV is used for this decryption, the S2K + specifier MUST use a salt value, either a Salted S2K or an Iterated- + Salted S2K. The salt value will insure that the decryption key is + not repeated even if the passphrase is reused. + +5.4. One-Pass Signature Packets (Tag 4) + + The One-Pass Signature packet precedes the signed data and contains + enough information to allow the receiver to begin calculating any + hashes needed to verify the signature. It allows the Signature + Packet to be placed at the end of the message, so that the signer can + compute the entire signed message in one pass. + + A One-Pass Signature does not interoperate with PGP 2.6.x or earlier. + + The body of this packet consists of: + + + + + +Callas, et. al. Standards Track [Page 33] + +RFC 2440 OpenPGP Message Format November 1998 + + + - A one-octet version number. The current version is 3. + + - A one-octet signature type. Signature types are described in + section 5.2.1. + + - A one-octet number describing the hash algorithm used. + + - A one-octet number describing the public key algorithm used. + + - An eight-octet number holding the key ID of the signing key. + + - A one-octet number holding a flag showing whether the signature + is nested. A zero value indicates that the next packet is + another One-Pass Signature packet that describes another + signature to be applied to the same message data. + + Note that if a message contains more than one one-pass signature, + then the signature packets bracket the message; that is, the first + signature packet after the message corresponds to the last one-pass + packet and the final signature packet corresponds to the first one- + pass packet. + +5.5. Key Material Packet + + A key material packet contains all the information about a public or + private key. There are four variants of this packet type, and two + major versions. Consequently, this section is complex. + +5.5.1. Key Packet Variants + +5.5.1.1. Public Key Packet (Tag 6) + + A Public Key packet starts a series of packets that forms an OpenPGP + key (sometimes called an OpenPGP certificate). + +5.5.1.2. Public Subkey Packet (Tag 14) + + A Public Subkey packet (tag 14) has exactly the same format as a + Public Key packet, but denotes a subkey. One or more subkeys may be + associated with a top-level key. By convention, the top-level key + provides signature services, and the subkeys provide encryption + services. + + Note: in PGP 2.6.x, tag 14 was intended to indicate a comment packet. + This tag was selected for reuse because no previous version of PGP + ever emitted comment packets but they did properly ignore them. + Public Subkey packets are ignored by PGP 2.6.x and do not cause it to + fail, providing a limited degree of backward compatibility. + + + +Callas, et. al. Standards Track [Page 34] + +RFC 2440 OpenPGP Message Format November 1998 + + +5.5.1.3. Secret Key Packet (Tag 5) + + A Secret Key packet contains all the information that is found in a + Public Key packet, including the public key material, but also + includes the secret key material after all the public key fields. + +5.5.1.4. Secret Subkey Packet (Tag 7) + + A Secret Subkey packet (tag 7) is the subkey analog of the Secret Key + packet, and has exactly the same format. + +5.5.2. Public Key Packet Formats + + There are two versions of key-material packets. Version 3 packets + were first generated by PGP 2.6. Version 2 packets are identical in + format to Version 3 packets, but are generated by PGP 2.5 or before. + V2 packets are deprecated and they MUST NOT be generated. PGP 5.0 + introduced version 4 packets, with new fields and semantics. PGP + 2.6.x will not accept key-material packets with versions greater than + 3. + + OpenPGP implementations SHOULD create keys with version 4 format. An + implementation MAY generate a V3 key to ensure interoperability with + old software; note, however, that V4 keys correct some security + deficiencies in V3 keys. These deficiencies are described below. An + implementation MUST NOT create a V3 key with a public key algorithm + other than RSA. + + A version 3 public key or public subkey packet contains: + + - A one-octet version number (3). + + - A four-octet number denoting the time that the key was created. + + - A two-octet number denoting the time in days that this key is + valid. If this number is zero, then it does not expire. + + - A one-octet number denoting the public key algorithm of this key + + - A series of multi-precision integers comprising the key + material: + + - a multiprecision integer (MPI) of RSA public modulus n; + + - an MPI of RSA public encryption exponent e. + + + + + + +Callas, et. al. Standards Track [Page 35] + +RFC 2440 OpenPGP Message Format November 1998 + + + V3 keys SHOULD only be used for backward compatibility because of + three weaknesses in them. First, it is relatively easy to construct a + V3 key that has the same key ID as any other key because the key ID + is simply the low 64 bits of the public modulus. Secondly, because + the fingerprint of a V3 key hashes the key material, but not its + length, which increases the opportunity for fingerprint collisions. + Third, there are minor weaknesses in the MD5 hash algorithm that make + developers prefer other algorithms. See below for a fuller discussion + of key IDs and fingerprints. + + The version 4 format is similar to the version 3 format except for + the absence of a validity period. This has been moved to the + signature packet. In addition, fingerprints of version 4 keys are + calculated differently from version 3 keys, as described in section + "Enhanced Key Formats." + + A version 4 packet contains: + + - A one-octet version number (4). + + - A four-octet number denoting the time that the key was created. + + - A one-octet number denoting the public key algorithm of this key + + - A series of multi-precision integers comprising the key + material. This algorithm-specific portion is: + + Algorithm Specific Fields for RSA public keys: + + - multiprecision integer (MPI) of RSA public modulus n; + + - MPI of RSA public encryption exponent e. + + Algorithm Specific Fields for DSA public keys: + + - MPI of DSA prime p; + + - MPI of DSA group order q (q is a prime divisor of p-1); + + - MPI of DSA group generator g; + + - MPI of DSA public key value y (= g**x where x is secret). + + Algorithm Specific Fields for Elgamal public keys: + + - MPI of Elgamal prime p; + + - MPI of Elgamal group generator g; + + + +Callas, et. al. Standards Track [Page 36] + +RFC 2440 OpenPGP Message Format November 1998 + + + - MPI of Elgamal public key value y (= g**x where x is + secret). + +5.5.3. Secret Key Packet Formats + + The Secret Key and Secret Subkey packets contain all the data of the + Public Key and Public Subkey packets, with additional algorithm- + specific secret key data appended, in encrypted form. + + The packet contains: + + - A Public Key or Public Subkey packet, as described above + + - One octet indicating string-to-key usage conventions. 0 + indicates that the secret key data is not encrypted. 255 + indicates that a string-to-key specifier is being given. Any + other value is a symmetric-key encryption algorithm specifier. + + - [Optional] If string-to-key usage octet was 255, a one-octet + symmetric encryption algorithm. + + - [Optional] If string-to-key usage octet was 255, a string-to-key + specifier. The length of the string-to-key specifier is implied + by its type, as described above. + + - [Optional] If secret data is encrypted, eight-octet Initial + Vector (IV). + + - Encrypted multi-precision integers comprising the secret key + data. These algorithm-specific fields are as described below. + + - Two-octet checksum of the plaintext of the algorithm-specific + portion (sum of all octets, mod 65536). + + Algorithm Specific Fields for RSA secret keys: + + - multiprecision integer (MPI) of RSA secret exponent d. + + - MPI of RSA secret prime value p. + + - MPI of RSA secret prime value q (p < q). + + - MPI of u, the multiplicative inverse of p, mod q. + + Algorithm Specific Fields for DSA secret keys: + + - MPI of DSA secret exponent x. + + + + +Callas, et. al. Standards Track [Page 37] + +RFC 2440 OpenPGP Message Format November 1998 + + + Algorithm Specific Fields for Elgamal secret keys: + + - MPI of Elgamal secret exponent x. + + Secret MPI values can be encrypted using a passphrase. If a string- + to-key specifier is given, that describes the algorithm for + converting the passphrase to a key, else a simple MD5 hash of the + passphrase is used. Implementations SHOULD use a string-to-key + specifier; the simple hash is for backward compatibility. The cipher + for encrypting the MPIs is specified in the secret key packet. + + Encryption/decryption of the secret data is done in CFB mode using + the key created from the passphrase and the Initial Vector from the + packet. A different mode is used with V3 keys (which are only RSA) + than with other key formats. With V3 keys, the MPI bit count prefix + (i.e., the first two octets) is not encrypted. Only the MPI non- + prefix data is encrypted. Furthermore, the CFB state is + resynchronized at the beginning of each new MPI value, so that the + CFB block boundary is aligned with the start of the MPI data. + + With V4 keys, a simpler method is used. All secret MPI values are + encrypted in CFB mode, including the MPI bitcount prefix. + + The 16-bit checksum that follows the algorithm-specific portion is + the algebraic sum, mod 65536, of the plaintext of all the algorithm- + specific octets (including MPI prefix and data). With V3 keys, the + checksum is stored in the clear. With V4 keys, the checksum is + encrypted like the algorithm-specific data. This value is used to + check that the passphrase was correct. + +5.6. Compressed Data Packet (Tag 8) + + The Compressed Data packet contains compressed data. Typically, this + packet is found as the contents of an encrypted packet, or following + a Signature or One-Pass Signature packet, and contains literal data + packets. + + The body of this packet consists of: + + - One octet that gives the algorithm used to compress the packet. + + - The remainder of the packet is compressed data. + + A Compressed Data Packet's body contains an block that compresses + some set of packets. See section "Packet Composition" for details on + how messages are formed. + + + + + +Callas, et. al. Standards Track [Page 38] + +RFC 2440 OpenPGP Message Format November 1998 + + + ZIP-compressed packets are compressed with raw RFC 1951 DEFLATE + blocks. Note that PGP V2.6 uses 13 bits of compression. If an + implementation uses more bits of compression, PGP V2.6 cannot + decompress it. + + ZLIB-compressed packets are compressed with RFC 1950 ZLIB-style + blocks. + +5.7. Symmetrically Encrypted Data Packet (Tag 9) + + The Symmetrically Encrypted Data packet contains data encrypted with + a symmetric-key algorithm. When it has been decrypted, it will + typically contain other packets (often literal data packets or + compressed data packets). + + The body of this packet consists of: + + - Encrypted data, the output of the selected symmetric-key cipher + operating in PGP's variant of Cipher Feedback (CFB) mode. + + The symmetric cipher used may be specified in an Public-Key or + Symmetric-Key Encrypted Session Key packet that precedes the + Symmetrically Encrypted Data Packet. In that case, the cipher + algorithm octet is prefixed to the session key before it is + encrypted. If no packets of these types precede the encrypted data, + the IDEA algorithm is used with the session key calculated as the MD5 + hash of the passphrase. + + The data is encrypted in CFB mode, with a CFB shift size equal to the + cipher's block size. The Initial Vector (IV) is specified as all + zeros. Instead of using an IV, OpenPGP prefixes a 10-octet string to + the data before it is encrypted. The first eight octets are random, + and the 9th and 10th octets are copies of the 7th and 8th octets, + respectively. After encrypting the first 10 octets, the CFB state is + resynchronized if the cipher block size is 8 octets or less. The + last 8 octets of ciphertext are passed through the cipher and the + block boundary is reset. + + The repetition of 16 bits in the 80 bits of random data prefixed to + the message allows the receiver to immediately check whether the + session key is incorrect. + +5.8. Marker Packet (Obsolete Literal Packet) (Tag 10) + + An experimental version of PGP used this packet as the Literal + packet, but no released version of PGP generated Literal packets with + this tag. With PGP 5.x, this packet has been re-assigned and is + reserved for use as the Marker packet. + + + +Callas, et. al. Standards Track [Page 39] + +RFC 2440 OpenPGP Message Format November 1998 + + + The body of this packet consists of: + + - The three octets 0x50, 0x47, 0x50 (which spell "PGP" in UTF-8). + + Such a packet MUST be ignored when received. It may be placed at the + beginning of a message that uses features not available in PGP 2.6.x + in order to cause that version to report that newer software is + necessary to process the message. + +5.9. Literal Data Packet (Tag 11) + + A Literal Data packet contains the body of a message; data that is + not to be further interpreted. + + The body of this packet consists of: + + - A one-octet field that describes how the data is formatted. + + If it is a 'b' (0x62), then the literal packet contains binary data. + If it is a 't' (0x74), then it contains text data, and thus may need + line ends converted to local form, or other text-mode changes. RFC + 1991 also defined a value of 'l' as a 'local' mode for machine-local + conversions. This use is now deprecated. + + - File name as a string (one-octet length, followed by file name), + if the encrypted data should be saved as a file. + + If the special name "_CONSOLE" is used, the message is considered to + be "for your eyes only". This advises that the message data is + unusually sensitive, and the receiving program should process it more + carefully, perhaps avoiding storing the received data to disk, for + example. + + - A four-octet number that indicates the modification date of the + file, or the creation time of the packet, or a zero that + indicates the present time. + + - The remainder of the packet is literal data. + + Text data is stored with text endings (i.e. network-normal + line endings). These should be converted to native line endings by + the receiving software. + +5.10. Trust Packet (Tag 12) + + The Trust packet is used only within keyrings and is not normally + exported. Trust packets contain data that record the user's + specifications of which key holders are trustworthy introducers, + + + +Callas, et. al. Standards Track [Page 40] + +RFC 2440 OpenPGP Message Format November 1998 + + + along with other information that implementing software uses for + trust information. + + Trust packets SHOULD NOT be emitted to output streams that are + transferred to other users, and they SHOULD be ignored on any input + other than local keyring files. + +5.11. User ID Packet (Tag 13) + + A User ID packet consists of data that is intended to represent the + name and email address of the key holder. By convention, it includes + an RFC 822 mail name, but there are no restrictions on its content. + The packet length in the header specifies the length of the user id. + If it is text, it is encoded in UTF-8. + +6. Radix-64 Conversions + + As stated in the introduction, OpenPGP's underlying native + representation for objects is a stream of arbitrary octets, and some + systems desire these objects to be immune to damage caused by + character set translation, data conversions, etc. + + In principle, any printable encoding scheme that met the requirements + of the unsafe channel would suffice, since it would not change the + underlying binary bit streams of the native OpenPGP data structures. + The OpenPGP standard specifies one such printable encoding scheme to + ensure interoperability. + + OpenPGP's Radix-64 encoding is composed of two parts: a base64 + encoding of the binary data, and a checksum. The base64 encoding is + identical to the MIME base64 content-transfer-encoding [RFC2231, + Section 6.8]. An OpenPGP implementation MAY use ASCII Armor to + protect the raw binary data. + + The checksum is a 24-bit CRC converted to four characters of radix-64 + encoding by the same MIME base64 transformation, preceded by an + equals sign (=). The CRC is computed by using the generator 0x864CFB + and an initialization of 0xB704CE. The accumulation is done on the + data before it is converted to radix-64, rather than on the converted + data. A sample implementation of this algorithm is in the next + section. + + The checksum with its leading equal sign MAY appear on the first line + after the Base64 encoded data. + + Rationale for CRC-24: The size of 24 bits fits evenly into printable + base64. The nonzero initialization can detect more errors than a + zero initialization. + + + +Callas, et. al. Standards Track [Page 41] + +RFC 2440 OpenPGP Message Format November 1998 + + +6.1. An Implementation of the CRC-24 in "C" + + #define CRC24_INIT 0xb704ceL + #define CRC24_POLY 0x1864cfbL + + typedef long crc24; + crc24 crc_octets(unsigned char *octets, size_t len) + { + crc24 crc = CRC24_INIT; + int i; + + while (len--) { + crc ^= (*octets++) << 16; + for (i = 0; i < 8; i++) { + crc <<= 1; + if (crc & 0x1000000) + crc ^= CRC24_POLY; + } + } + return crc & 0xffffffL; + } + +6.2. Forming ASCII Armor + + When OpenPGP encodes data into ASCII Armor, it puts specific headers + around the data, so OpenPGP can reconstruct the data later. OpenPGP + informs the user what kind of data is encoded in the ASCII armor + through the use of the headers. + + Concatenating the following data creates ASCII Armor: + + - An Armor Header Line, appropriate for the type of data + + - Armor Headers + + - A blank (zero-length, or containing only whitespace) line + + - The ASCII-Armored data + + - An Armor Checksum + + - The Armor Tail, which depends on the Armor Header Line. + + An Armor Header Line consists of the appropriate header line text + surrounded by five (5) dashes ('-', 0x2D) on either side of the + header line text. The header line text is chosen based upon the type + of data that is being encoded in Armor, and how it is being encoded. + Header line texts include the following strings: + + + +Callas, et. al. Standards Track [Page 42] + +RFC 2440 OpenPGP Message Format November 1998 + + + BEGIN PGP MESSAGE + Used for signed, encrypted, or compressed files. + + BEGIN PGP PUBLIC KEY BLOCK + Used for armoring public keys + + BEGIN PGP PRIVATE KEY BLOCK + Used for armoring private keys + + BEGIN PGP MESSAGE, PART X/Y + Used for multi-part messages, where the armor is split amongst Y + parts, and this is the Xth part out of Y. + + BEGIN PGP MESSAGE, PART X + Used for multi-part messages, where this is the Xth part of an + unspecified number of parts. Requires the MESSAGE-ID Armor Header + to be used. + + BEGIN PGP SIGNATURE + Used for detached signatures, OpenPGP/MIME signatures, and + natures following clearsigned messages. Note that PGP 2.x s BEGIN + PGP MESSAGE for detached signatures. + + The Armor Headers are pairs of strings that can give the user or the + receiving OpenPGP implementation some information about how to decode + or use the message. The Armor Headers are a part of the armor, not a + part of the message, and hence are not protected by any signatures + applied to the message. + + The format of an Armor Header is that of a key-value pair. A colon + (':' 0x38) and a single space (0x20) separate the key and value. + OpenPGP should consider improperly formatted Armor Headers to be + corruption of the ASCII Armor. Unknown keys should be reported to + the user, but OpenPGP should continue to process the message. + + Currently defined Armor Header Keys are: + + - "Version", that states the OpenPGP Version used to encode the + message. + + - "Comment", a user-defined comment. + + - "MessageID", a 32-character string of printable characters. The + string must be the same for all parts of a multi-part message + that uses the "PART X" Armor Header. MessageID strings should be + + + + + + +Callas, et. al. Standards Track [Page 43] + +RFC 2440 OpenPGP Message Format November 1998 + + + unique enough that the recipient of the mail can associate all + the parts of a message with each other. A good checksum or + cryptographic hash function is sufficient. + + - "Hash", a comma-separated list of hash algorithms used in this + message. This is used only in clear-signed messages. + + - "Charset", a description of the character set that the plaintext + is in. Please note that OpenPGP defines text to be in UTF-8 by + default. An implementation will get best results by translating + into and out of UTF-8. However, there are many instances where + this is easier said than done. Also, there are communities of + users who have no need for UTF-8 because they are all happy with + a character set like ISO Latin-5 or a Japanese character set. In + such instances, an implementation MAY override the UTF-8 default + by using this header key. An implementation MAY implement this + key and any translations it cares to; an implementation MAY + ignore it and assume all text is UTF-8. + + The MessageID SHOULD NOT appear unless it is in a multi-part + message. If it appears at all, it MUST be computed from the + finished (encrypted, signed, etc.) message in a deterministic + fashion, rather than contain a purely random value. This is to + allow the legitimate recipient to determine that the MessageID + cannot serve as a covert means of leaking cryptographic key + information. + + The Armor Tail Line is composed in the same manner as the Armor + Header Line, except the string "BEGIN" is replaced by the string + "END." + +6.3. Encoding Binary in Radix-64 + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating three 8-bit input + groups. These 24 bits are then treated as four concatenated 6-bit + groups, each of which is translated into a single digit in the + Radix-64 alphabet. When encoding a bit stream with the Radix-64 + encoding, the bit stream must be presumed to be ordered with the + most-significant-bit first. That is, the first bit in the stream will + be the high-order bit in the first 8-bit octet, and the eighth bit + will be the low-order bit in the first 8-bit octet, and so on. + + + + + + + + +Callas, et. al. Standards Track [Page 44] + +RFC 2440 OpenPGP Message Format November 1998 + + + +--first octet--+-second octet--+--third octet--+ + |7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0| + +-----------+---+-------+-------+---+-----------+ + |5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0| + +--1.index--+--2.index--+--3.index--+--4.index--+ + + Each 6-bit group is used as an index into an array of 64 printable + characters from the table below. The character referenced by the + index is placed in the output string. + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + The encoded output stream must be represented in lines of no more + than 76 characters each. + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. There are three possibilities: + + 1. The last data group has 24 bits (3 octets). No special + processing is needed. + + 2. The last data group has 16 bits (2 octets). The first two 6-bit + groups are processed as above. The third (incomplete) data group + has two zero-value bits added to it, and is processed as above. + A pad character (=) is added to the output. + + 3. The last data group has 8 bits (1 octet). The first 6-bit group + is processed as above. The second (incomplete) data group has + four zero-value bits added to it, and is processed as above. Two + pad characters (=) are added to the output. + + + + +Callas, et. al. Standards Track [Page 45] + +RFC 2440 OpenPGP Message Format November 1998 + + +6.4. Decoding Radix-64 + + Any characters outside of the base64 alphabet are ignored in Radix-64 + data. Decoding software must ignore all line breaks or other + characters not found in the table above. + + In Radix-64 data, characters other than those in the table, line + breaks, and other white space probably indicate a transmission error, + about which a warning message or even a message rejection might be + appropriate under some circumstances. + + Because it is used only for padding at the end of the data, the + occurrence of any "=" characters may be taken as evidence that the + end of the data has been reached (without truncation in transit). No + such assurance is possible, however, when the number of octets + transmitted was a multiple of three and no "=" characters are + present. + +6.5. Examples of Radix-64 + + Input data: 0x14fb9c03d97e + Hex: 1 4 f b 9 c | 0 3 d 9 7 e + 8-bit: 00010100 11111011 10011100 | 00000011 11011001 + 11111110 + 6-bit: 000101 001111 101110 011100 | 000000 111101 100111 + 111110 + Decimal: 5 15 46 28 0 61 37 62 + Output: F P u c A 9 l + + + Input data: 0x14fb9c03d9 + Hex: 1 4 f b 9 c | 0 3 d 9 + 8-bit: 00010100 11111011 10011100 | 00000011 11011001 + pad with 00 + 6-bit: 000101 001111 101110 011100 | 000000 111101 100100 + Decimal: 5 15 46 28 0 61 36 + pad with = + Output: F P u c A 9 k = + + Input data: 0x14fb9c03 + Hex: 1 4 f b 9 c | 0 3 + 8-bit: 00010100 11111011 10011100 | 00000011 + pad with 0000 + 6-bit: 000101 001111 101110 011100 | 000000 110000 + Decimal: 5 15 46 28 0 48 + pad with = = + Output: F P u c A w = = + + + + + +Callas, et. al. Standards Track [Page 46] + +RFC 2440 OpenPGP Message Format November 1998 + + +6.6. Example of an ASCII Armored Message + + + -----BEGIN PGP MESSAGE----- + Version: OpenPrivacy 0.99 + + yDgBO22WxBHv7O8X7O/jygAEzol56iUKiXmV+XmpCtmpqQUKiQrFqclFqUDBovzS + vBSFjNSiVHsuAA== + =njUN + -----END PGP MESSAGE----- + + Note that this example is indented by two spaces. + +7. Cleartext signature framework + + It is desirable to sign a textual octet stream without ASCII armoring + the stream itself, so the signed text is still readable without + special software. In order to bind a signature to such a cleartext, + this framework is used. (Note that RFC 2015 defines another way to + clear sign messages for environments that support MIME.) + + The cleartext signed message consists of: + + - The cleartext header '-----BEGIN PGP SIGNED MESSAGE-----' on a + single line, + + - One or more "Hash" Armor Headers, + + - Exactly one empty line not included into the message digest, + + - The dash-escaped cleartext that is included into the message + digest, + + - The ASCII armored signature(s) including the '-----BEGIN PGP + SIGNATURE-----' Armor Header and Armor Tail Lines. + + If the "Hash" armor header is given, the specified message digest + algorithm is used for the signature. If there are no such headers, + MD5 is used, an implementation MAY omit them for V2.x compatibility. + If more than one message digest is used in the signature, the "Hash" + armor header contains a comma-delimited list of used message digests. + + Current message digest names are described below with the algorithm + IDs. + +7.1. Dash-Escaped Text + + The cleartext content of the message must also be dash-escaped. + + + +Callas, et. al. Standards Track [Page 47] + +RFC 2440 OpenPGP Message Format November 1998 + + + Dash escaped cleartext is the ordinary cleartext where every line + starting with a dash '-' (0x2D) is prefixed by the sequence dash '-' + (0x2D) and space ' ' (0x20). This prevents the parser from + recognizing armor headers of the cleartext itself. The message digest + is computed using the cleartext itself, not the dash escaped form. + + As with binary signatures on text documents, a cleartext signature is + calculated on the text using canonical line endings. The + line ending (i.e. the ) before the '-----BEGIN PGP + SIGNATURE-----' line that terminates the signed text is not + considered part of the signed text. + + Also, any trailing whitespace (spaces, and tabs, 0x09) at the end of + any line is ignored when the cleartext signature is calculated. + +8. Regular Expressions + + A regular expression is zero or more branches, separated by '|'. It + matches anything that matches one of the branches. + + A branch is zero or more pieces, concatenated. It matches a match for + the first, followed by a match for the second, etc. + + A piece is an atom possibly followed by '*', '+', or '?'. An atom + followed by '*' matches a sequence of 0 or more matches of the atom. + An atom followed by '+' matches a sequence of 1 or more matches of + the atom. An atom followed by '?' matches a match of the atom, or the + null string. + + An atom is a regular expression in parentheses (matching a match for + the regular expression), a range (see below), '.' (matching any + single character), '^' (matching the null string at the beginning of + the input string), '$' (matching the null string at the end of the + input string), a '\' followed by a single character (matching that + character), or a single character with no other significance + (matching that character). + + A range is a sequence of characters enclosed in '[]'. It normally + matches any single character from the sequence. If the sequence + begins with '^', it matches any single character not from the rest of + the sequence. If two characters in the sequence are separated by '-', + this is shorthand for the full list of ASCII characters between them + (e.g. '[0-9]' matches any decimal digit). To include a literal ']' in + the sequence, make it the first character (following a possible '^'). + To include a literal '-', make it the first or last character. + + + + + + +Callas, et. al. Standards Track [Page 48] + +RFC 2440 OpenPGP Message Format November 1998 + + +9. Constants + + This section describes the constants used in OpenPGP. + + Note that these tables are not exhaustive lists; an implementation + MAY implement an algorithm not on these lists. + + See the section "Notes on Algorithms" below for more discussion of + the algorithms. + +9.1. Public Key Algorithms + + ID Algorithm + -- --------- + 1 - RSA (Encrypt or Sign) + 2 - RSA Encrypt-Only + 3 - RSA Sign-Only + 16 - Elgamal (Encrypt-Only), see [ELGAMAL] + 17 - DSA (Digital Signature Standard) + 18 - Reserved for Elliptic Curve + 19 - Reserved for ECDSA + 20 - Elgamal (Encrypt or Sign) + + + + + + 21 - Reserved for Diffie-Hellman (X9.42, + as defined for IETF-S/MIME) + 100 to 110 - Private/Experimental algorithm. + + Implementations MUST implement DSA for signatures, and Elgamal for + encryption. Implementations SHOULD implement RSA keys. + Implementations MAY implement any other algorithm. + +9.2. Symmetric Key Algorithms + + ID Algorithm + -- --------- + 0 - Plaintext or unencrypted data + 1 - IDEA [IDEA] + 2 - Triple-DES (DES-EDE, as per spec - + 168 bit key derived from 192) + 3 - CAST5 (128 bit key, as per RFC 2144) + 4 - Blowfish (128 bit key, 16 rounds) [BLOWFISH] + 5 - SAFER-SK128 (13 rounds) [SAFER] + 6 - Reserved for DES/SK + 7 - Reserved for AES with 128-bit key + + + +Callas, et. al. Standards Track [Page 49] + +RFC 2440 OpenPGP Message Format November 1998 + + + 8 - Reserved for AES with 192-bit key + 9 - Reserved for AES with 256-bit key + 100 to 110 - Private/Experimental algorithm. + + Implementations MUST implement Triple-DES. Implementations SHOULD + implement IDEA and CAST5.Implementations MAY implement any other + algorithm. + +9.3. Compression Algorithms + + ID Algorithm + -- --------- + 0 - Uncompressed + 1 - ZIP (RFC 1951) + 2 - ZLIB (RFC 1950) + 100 to 110 - Private/Experimental algorithm. + + Implementations MUST implement uncompressed data. Implementations + SHOULD implement ZIP. Implementations MAY implement ZLIB. + +9.4. Hash Algorithms + + ID Algorithm Text Name + -- --------- ---- ---- + 1 - MD5 "MD5" + 2 - SHA-1 "SHA1" + 3 - RIPE-MD/160 "RIPEMD160" + 4 - Reserved for double-width SHA (experimental) + 5 - MD2 "MD2" + 6 - Reserved for TIGER/192 "TIGER192" + 7 - Reserved for HAVAL (5 pass, 160-bit) + "HAVAL-5-160" + 100 to 110 - Private/Experimental algorithm. + + Implementations MUST implement SHA-1. Implementations SHOULD + implement MD5. + +10. Packet Composition + + OpenPGP packets are assembled into sequences in order to create + messages and to transfer keys. Not all possible packet sequences are + meaningful and correct. This describes the rules for how packets + should be placed into sequences. + +10.1. Transferable Public Keys + + OpenPGP users may transfer public keys. The essential elements of a + transferable public key are: + + + +Callas, et. al. Standards Track [Page 50] + +RFC 2440 OpenPGP Message Format November 1998 + + + - One Public Key packet + + - Zero or more revocation signatures + + - One or more User ID packets + + - After each User ID packet, zero or more signature packets + (certifications) + + - Zero or more Subkey packets + + - After each Subkey packet, one signature packet, optionally a + revocation. + + The Public Key packet occurs first. Each of the following User ID + packets provides the identity of the owner of this public key. If + there are multiple User ID packets, this corresponds to multiple + means of identifying the same unique individual user; for example, a + user may have more than one email address, and construct a User ID + for each one. + + Immediately following each User ID packet, there are zero or more + signature packets. Each signature packet is calculated on the + immediately preceding User ID packet and the initial Public Key + packet. The signature serves to certify the corresponding public key + and user ID. In effect, the signer is testifying to his or her + belief that this public key belongs to the user identified by this + user ID. + + After the User ID packets there may be one or more Subkey packets. + In general, subkeys are provided in cases where the top-level public + key is a signature-only key. However, any V4 key may have subkeys, + and the subkeys may be encryption-only keys, signature-only keys, or + general-purpose keys. + + Each Subkey packet must be followed by one Signature packet, which + should be a subkey binding signature issued by the top level key. + + Subkey and Key packets may each be followed by a revocation Signature + packet to indicate that the key is revoked. Revocation signatures + are only accepted if they are issued by the key itself, or by a key + that is authorized to issue revocations via a revocation key + subpacket in a self-signature by the top level key. + + Transferable public key packet sequences may be concatenated to allow + transferring multiple public keys in one operation. + + + + + +Callas, et. al. Standards Track [Page 51] + +RFC 2440 OpenPGP Message Format November 1998 + + +10.2. OpenPGP Messages + + An OpenPGP message is a packet or sequence of packets that + corresponds to the following grammatical rules (comma represents + sequential composition, and vertical bar separates alternatives): + + OpenPGP Message :- Encrypted Message | Signed Message | + Compressed Message | Literal Message. + + Compressed Message :- Compressed Data Packet. + + Literal Message :- Literal Data Packet. + + ESK :- Public Key Encrypted Session Key Packet | + Symmetric-Key Encrypted Session Key Packet. + + ESK Sequence :- ESK | ESK Sequence, ESK. + + Encrypted Message :- Symmetrically Encrypted Data Packet | + ESK Sequence, Symmetrically Encrypted Data Packet. + + One-Pass Signed Message :- One-Pass Signature Packet, + OpenPGP Message, Corresponding Signature Packet. + + Signed Message :- Signature Packet, OpenPGP Message | + One-Pass Signed Message. + + In addition, decrypting a Symmetrically Encrypted Data packet and + + decompressing a Compressed Data packet must yield a valid OpenPGP + Message. + +10.3. Detached Signatures + + Some OpenPGP applications use so-called "detached signatures." For + example, a program bundle may contain a file, and with it a second + file that is a detached signature of the first file. These detached + signatures are simply a signature packet stored separately from the + data that they are a signature of. + +11. Enhanced Key Formats + +11.1. Key Structures + + The format of an OpenPGP V3 key is as follows. Entries in square + brackets are optional and ellipses indicate repetition. + + + + + +Callas, et. al. Standards Track [Page 52] + +RFC 2440 OpenPGP Message Format November 1998 + + + RSA Public Key + [Revocation Self Signature] + User ID [Signature ...] + [User ID [Signature ...] ...] + + Each signature certifies the RSA public key and the preceding user + ID. The RSA public key can have many user IDs and each user ID can + have many signatures. + + The format of an OpenPGP V4 key that uses two public keys is similar + except that the other keys are added to the end as 'subkeys' of the + primary key. + + Primary-Key + [Revocation Self Signature] + [Direct Key Self Signature...] + User ID [Signature ...] + [User ID [Signature ...] ...] + [[Subkey [Binding-Signature-Revocation] + Primary-Key-Binding-Signature] ...] + + A subkey always has a single signature after it that is issued using + the primary key to tie the two keys together. This binding signature + may be in either V3 or V4 format, but V4 is preferred, of course. + + In the above diagram, if the binding signature of a subkey has been + revoked, the revoked binding signature may be removed, leaving only + one signature. + + In a key that has a main key and subkeys, the primary key MUST be a + key capable of signing. The subkeys may be keys of any other type. + There may be other constructions of V4 keys, too. For example, there + may be a single-key RSA key in V4 format, a DSA primary key with an + RSA encryption key, or RSA primary key with an Elgamal subkey, etc. + + It is also possible to have a signature-only subkey. This permits a + primary key that collects certifications (key signatures) but is used + only used for certifying subkeys that are used for encryption and + signatures. + +11.2. Key IDs and Fingerprints + + For a V3 key, the eight-octet key ID consists of the low 64 bits of + the public modulus of the RSA key. + + The fingerprint of a V3 key is formed by hashing the body (but not + the two-octet length) of the MPIs that form the key material (public + modulus n, followed by exponent e) with MD5. + + + +Callas, et. al. Standards Track [Page 53] + +RFC 2440 OpenPGP Message Format November 1998 + + + A V4 fingerprint is the 160-bit SHA-1 hash of the one-octet Packet + Tag, followed by the two-octet packet length, followed by the entire + Public Key packet starting with the version field. The key ID is the + low order 64 bits of the fingerprint. Here are the fields of the + hash material, with the example of a DSA key: + + a.1) 0x99 (1 octet) + + a.2) high order length octet of (b)-(f) (1 octet) + + a.3) low order length octet of (b)-(f) (1 octet) + + b) version number = 4 (1 octet); + + c) time stamp of key creation (4 octets); + + d) algorithm (1 octet): 17 = DSA (example); + + e) Algorithm specific fields. + + Algorithm Specific Fields for DSA keys (example): + + e.1) MPI of DSA prime p; + + e.2) MPI of DSA group order q (q is a prime divisor of p-1); + + e.3) MPI of DSA group generator g; + + e.4) MPI of DSA public key value y (= g**x where x is secret). + + Note that it is possible for there to be collisions of key IDs -- two + different keys with the same key ID. Note that there is a much + smaller, but still non-zero probability that two different keys have + the same fingerprint. + + Also note that if V3 and V4 format keys share the same RSA key + material, they will have different key ids as well as different + fingerprints. + +12. Notes on Algorithms + +12.1. Symmetric Algorithm Preferences + + The symmetric algorithm preference is an ordered list of algorithms + that the keyholder accepts. Since it is found on a self-signature, it + is possible that a keyholder may have different preferences. For + example, Alice may have TripleDES only specified for "alice@work.com" + but CAST5, Blowfish, and TripleDES specified for "alice@home.org". + + + +Callas, et. al. Standards Track [Page 54] + +RFC 2440 OpenPGP Message Format November 1998 + + + Note that it is also possible for preferences to be in a subkey's + binding signature. + + Since TripleDES is the MUST-implement algorithm, if it is not + explicitly in the list, it is tacitly at the end. However, it is good + form to place it there explicitly. Note also that if an + implementation does not implement the preference, then it is + implicitly a TripleDES-only implementation. + + An implementation MUST not use a symmetric algorithm that is not in + the recipient's preference list. When encrypting to more than one + recipient, the implementation finds a suitable algorithm by taking + the intersection of the preferences of the recipients. Note that the + MUST-implement algorithm, TripleDES, ensures that the intersection is + not null. The implementation may use any mechanism to pick an + algorithm in the intersection. + + If an implementation can decrypt a message that a keyholder doesn't + have in their preferences, the implementation SHOULD decrypt the + message anyway, but MUST warn the keyholder than protocol has been + violated. (For example, suppose that Alice, above, has software that + implements all algorithms in this specification. Nonetheless, she + prefers subsets for work or home. If she is sent a message encrypted + with IDEA, which is not in her preferences, the software warns her + that someone sent her an IDEA-encrypted message, but it would ideally + decrypt it anyway.) + + An implementation that is striving for backward compatibility MAY + consider a V3 key with a V3 self-signature to be an implicit + preference for IDEA, and no ability to do TripleDES. This is + technically non-compliant, but an implementation MAY violate the + above rule in this case only and use IDEA to encrypt the message, + provided that the message creator is warned. Ideally, though, the + implementation would follow the rule by actually generating two + messages, because it is possible that the OpenPGP user's + implementation does not have IDEA, and thus could not read the + message. Consequently, an implementation MAY, but SHOULD NOT use IDEA + in an algorithm conflict with a V3 key. + +12.2. Other Algorithm Preferences + + Other algorithm preferences work similarly to the symmetric algorithm + preference, in that they specify which algorithms the keyholder + accepts. There are two interesting cases that other comments need to + be made about, though, the compression preferences and the hash + preferences. + + + + + +Callas, et. al. Standards Track [Page 55] + +RFC 2440 OpenPGP Message Format November 1998 + + +12.2.1. Compression Preferences + + Compression has been an integral part of PGP since its first days. + OpenPGP and all previous versions of PGP have offered compression. + And in this specification, the default is for messages to be + compressed, although an implementation is not required to do so. + Consequently, the compression preference gives a way for a keyholder + to request that messages not be compressed, presumably because they + are using a minimal implementation that does not include compression. + Additionally, this gives a keyholder a way to state that it can + support alternate algorithms. + + Like the algorithm preferences, an implementation MUST NOT use an + algorithm that is not in the preference vector. If the preferences + are not present, then they are assumed to be [ZIP(1), + UNCOMPRESSED(0)]. + +12.2.2. Hash Algorithm Preferences + + Typically, the choice of a hash algorithm is something the signer + does, rather than the verifier, because a signer does not typically + know who is going to be verifying the signature. This preference, + though, allows a protocol based upon digital signatures ease in + negotiation. + + Thus, if Alice is authenticating herself to Bob with a signature, it + makes sense for her to use a hash algorithm that Bob's software uses. + This preference allows Bob to state in his key which algorithms Alice + may use. + +12.3. Plaintext + + Algorithm 0, "plaintext", may only be used to denote secret keys that + are stored in the clear. Implementations must not use plaintext in + Symmetrically Encrypted Data Packets; they must use Literal Data + Packets to encode unencrypted or literal data. + +12.4. RSA + + There are algorithm types for RSA-signature-only, and RSA-encrypt- + only keys. These types are deprecated. The "key flags" subpacket in a + signature is a much better way to express the same idea, and + generalizes it to all algorithms. An implementation SHOULD NOT create + such a key, but MAY interpret it. + + An implementation SHOULD NOT implement RSA keys of size less than 768 + bits. + + + + +Callas, et. al. Standards Track [Page 56] + +RFC 2440 OpenPGP Message Format November 1998 + + + It is permissible for an implementation to support RSA merely for + backward compatibility; for example, such an implementation would + support V3 keys with IDEA symmetric cryptography. Note that this is + an exception to the other MUST-implement rules. An implementation + that supports RSA in V4 keys MUST implement the MUST-implement + features. + +12.5. Elgamal + + If an Elgamal key is to be used for both signing and encryption, + extra care must be taken in creating the key. + + An ElGamal key consists of a generator g, a prime modulus p, a secret + exponent x, and a public value y = g^x mod p. + + The generator and prime must be chosen so that solving the discrete + log problem is intractable. The group g should generate the + multiplicative group mod p-1 or a large subgroup of it, and the order + of g should have at least one large prime factor. A good choice is + to use a "strong" Sophie-Germain prime in choosing p, so that both p + and (p-1)/2 are primes. In fact, this choice is so good that + implementors SHOULD do it, as it avoids a small subgroup attack. + + In addition, a result of Bleichenbacher [BLEICHENBACHER] shows that + if the generator g has only small prime factors, and if g divides the + order of the group it generates, then signatures can be forged. In + particular, choosing g=2 is a bad choice if the group order may be + even. On the other hand, a generator of 2 is a fine choice for an + encryption-only key, as this will make the encryption faster. + + While verifying Elgamal signatures, note that it is important to test + that r and s are less than p. If this test is not done then + signatures can be trivially forged by using large r values of + approximately twice the length of p. This attack is also discussed + in the Bleichenbacher paper. + + Details on safe use of Elgamal signatures may be found in [MENEZES], + which discusses all the weaknesses described above. + + If an implementation allows Elgamal signatures, then it MUST use the + algorithm identifier 20 for an Elgamal public key that can sign. + + An implementation SHOULD NOT implement Elgamal keys of size less than + 768 bits. For long-term security, Elgamal keys should be 1024 bits or + longer. + + + + + + +Callas, et. al. Standards Track [Page 57] + +RFC 2440 OpenPGP Message Format November 1998 + + +12.6. DSA + + An implementation SHOULD NOT implement DSA keys of size less than 768 + bits. Note that present DSA is limited to a maximum of 1024 bit keys, + which are recommended for long-term use. + +12.7. Reserved Algorithm Numbers + + A number of algorithm IDs have been reserved for algorithms that + would be useful to use in an OpenPGP implementation, yet there are + issues that prevent an implementor from actually implementing the + algorithm. These are marked in the Public Algorithms section as + "(reserved for)". + + The reserved public key algorithms, Elliptic Curve (18), ECDSA (19), + and X9.42 (21) do not have the necessary parameters, parameter order, + or semantics defined. + + The reserved symmetric key algorithm, DES/SK (6), does not have + semantics defined. + + The reserved hash algorithms, TIGER192 (6), and HAVAL-5-160 (7), do + not have OIDs. The reserved algorithm number 4, reserved for a + double-width variant of SHA1, is not presently defined. + + We have reserver three algorithm IDs for the US NIST's Advanced + Encryption Standard. This algorithm will work with (at least) 128, + 192, and 256-bit keys. We expect that this algorithm will be selected + from the candidate algorithms in the year 2000. + +12.8. OpenPGP CFB mode + + OpenPGP does symmetric encryption using a variant of Cipher Feedback + Mode (CFB mode). This section describes the procedure it uses in + detail. This mode is what is used for Symmetrically Encrypted Data + Packets; the mechanism used for encrypting secret key material is + similar, but described in those sections above. + + OpenPGP CFB mode uses an initialization vector (IV) of all zeros, and + prefixes the plaintext with ten octets of random data, such that + octets 9 and 10 match octets 7 and 8. It does a CFB "resync" after + encrypting those ten octets. + + Note that for an algorithm that has a larger block size than 64 bits, + the equivalent function will be done with that entire block. For + example, a 16-octet block algorithm would operate on 16 octets, and + then produce two octets of check, and then work on 16-octet blocks. + + + + +Callas, et. al. Standards Track [Page 58] + +RFC 2440 OpenPGP Message Format November 1998 + + + Step by step, here is the procedure: + + 1. The feedback register (FR) is set to the IV, which is all zeros. + + 2. FR is encrypted to produce FRE (FR Encrypted). This is the + encryption of an all-zero value. + + 3. FRE is xored with the first 8 octets of random data prefixed to + the plaintext to produce C1-C8, the first 8 octets of ciphertext. + + 4. FR is loaded with C1-C8. + + 5. FR is encrypted to produce FRE, the encryption of the first 8 + octets of ciphertext. + + 6. The left two octets of FRE get xored with the next two octets of + data that were prefixed to the plaintext. This produces C9-C10, + the next two octets of ciphertext. + + 7. (The resync step) FR is loaded with C3-C10. + + 8. FR is encrypted to produce FRE. + + 9. FRE is xored with the first 8 octets of the given plaintext, now + that we have finished encrypting the 10 octets of prefixed data. + This produces C11-C18, the next 8 octets of ciphertext. + + 10. FR is loaded with C11-C18 + + 11. FR is encrypted to produce FRE. + + 12. FRE is xored with the next 8 octets of plaintext, to produce the + next 8 octets of ciphertext. These are loaded into FR and the + process is repeated until the plaintext is used up. + +13. Security Considerations + + As with any technology involving cryptography, you should check the + current literature to determine if any algorithms used here have been + found to be vulnerable to attack. + + This specification uses Public Key Cryptography technologies. + Possession of the private key portion of a public-private key pair is + assumed to be controlled by the proper party or parties. + + Certain operations in this specification involve the use of random + numbers. An appropriate entropy source should be used to generate + these numbers. See RFC 1750. + + + +Callas, et. al. Standards Track [Page 59] + +RFC 2440 OpenPGP Message Format November 1998 + + + The MD5 hash algorithm has been found to have weaknesses (pseudo- + collisions in the compress function) that make some people deprecate + its use. They consider the SHA-1 algorithm better. + + Many security protocol designers think that it is a bad idea to use a + single key for both privacy (encryption) and integrity (signatures). + In fact, this was one of the motivating forces behind the V4 key + format with separate signature and encryption keys. If you as an + implementor promote dual-use keys, you should at least be aware of + this controversy. + + The DSA algorithm will work with any 160-bit hash, but it is + sensitive to the quality of the hash algorithm, if the hash algorithm + is broken, it can leak the secret key. The Digital Signature Standard + (DSS) specifies that DSA be used with SHA-1. RIPEMD-160 is + considered by many cryptographers to be as strong. An implementation + should take care which hash algorithms are used with DSA, as a weak + hash can not only allow a signature to be forged, but could leak the + secret key. These same considerations about the quality of the hash + algorithm apply to Elgamal signatures. + + If you are building an authentication system, the recipient may + specify a preferred signing algorithm. However, the signer would be + foolish to use a weak algorithm simply because the recipient requests + it. + + Some of the encryption algorithms mentioned in this document have + been analyzed less than others. For example, although CAST5 is + presently considered strong, it has been analyzed less than Triple- + DES. Other algorithms may have other controversies surrounding them. + + Some technologies mentioned here may be subject to government control + in some countries. + +14. Implementation Nits + + This section is a collection of comments to help an implementer, + particularly with an eye to backward compatibility. Previous + implementations of PGP are not OpenPGP-compliant. Often the + differences are small, but small differences are frequently more + vexing than large differences. Thus, this list of potential problems + and gotchas for a developer who is trying to be backward-compatible. + + * PGP 5.x does not accept V4 signatures for anything other than + key material. + + * PGP 5.x does not recognize the "five-octet" lengths in new-format + headers or in signature subpacket lengths. + + + +Callas, et. al. Standards Track [Page 60] + +RFC 2440 OpenPGP Message Format November 1998 + + + * PGP 5.0 rejects an encrypted session key if the keylength differs + from the S2K symmetric algorithm. This is a bug in its validation + function. + + * PGP 5.0 does not handle multiple one-pass signature headers and + trailers. Signing one will compress the one-pass signed literal + and prefix a V3 signature instead of doing a nested one-pass + signature. + + * When exporting a private key, PGP 2.x generates the header "BEGIN + PGP SECRET KEY BLOCK" instead of "BEGIN PGP PRIVATE KEY BLOCK". + All previous versions ignore the implied data type, and look + directly at the packet data type. + + * In a clear-signed signature, PGP 5.0 will figure out the correct + hash algorithm if there is no "Hash:" header, but it will reject + a mismatch between the header and the actual algorithm used. The + "standard" (i.e. Zimmermann/Finney/et al.) version of PGP 2.x + rejects the "Hash:" header and assumes MD5. There are a number of + enhanced variants of PGP 2.6.x that have been modified for SHA-1 + signatures. + + * PGP 5.0 can read an RSA key in V4 format, but can only recognize + it with a V3 keyid, and can properly use only a V3 format RSA + key. + + * Neither PGP 5.x nor PGP 6.0 recognize Elgamal Encrypt and Sign + keys. They only handle Elgamal Encrypt-only keys. + + * There are many ways possible for two keys to have the same key + material, but different fingerprints (and thus key ids). Perhaps + the most interesting is an RSA key that has been "upgraded" to V4 + format, but since a V4 fingerprint is constructed by hashing the + key creation time along with other things, two V4 keys created at + different times, yet with the same key material will have + different fingerprints. + + * If an implementation is using zlib to interoperate with PGP 2.x, + then the "windowBits" parameter should be set to -13. + + + + + + + + + + + + +Callas, et. al. Standards Track [Page 61] + +RFC 2440 OpenPGP Message Format November 1998 + + +15. Authors and Working Group Chair + + The working group can be contacted via the current chair: + + John W. Noerenberg, II + Qualcomm, Inc + 6455 Lusk Blvd + San Diego, CA 92131 USA + + Phone: +1 619-658-3510 + EMail: jwn2@qualcomm.com + + + The principal authors of this memo are: + + Jon Callas + Network Associates, Inc. + 3965 Freedom Circle + Santa Clara, CA 95054, USA + + Phone: +1 408-346-5860 + EMail: jon@pgp.com, jcallas@nai.com + + + Lutz Donnerhacke + IKS GmbH + Wildenbruchstr. 15 + 07745 Jena, Germany + + Phone: +49-3641-675642 + EMail: lutz@iks-jena.de + + + Hal Finney + Network Associates, Inc. + 3965 Freedom Circle + Santa Clara, CA 95054, USA + + EMail: hal@pgp.com + + + Rodney Thayer + EIS Corporation + Clearwater, FL 33767, USA + + EMail: rodney@unitran.com + + + + + +Callas, et. al. Standards Track [Page 62] + +RFC 2440 OpenPGP Message Format November 1998 + + + This memo also draws on much previous work from a number of other + authors who include: Derek Atkins, Charles Breed, Dave Del Torto, + Marc Dyksterhouse, Gail Haspert, Gene Hoffman, Paul Hoffman, Raph + Levien, Colin Plumb, Will Price, William Stallings, Mark Weaver, and + Philip R. Zimmermann. + +16. References + + [BLEICHENBACHER] Bleichenbacher, Daniel, "Generating ElGamal + signatures without knowing the secret key," + Eurocrypt 96. Note that the version in the + proceedings has an error. A revised version is + available at the time of writing from + + + [BLOWFISH] Schneier, B. "Description of a New Variable-Length + Key, 64-Bit Block Cipher (Blowfish)" Fast Software + Encryption, Cambridge Security Workshop Proceedings + (December 1993), Springer-Verlag, 1994, pp191-204 + + + + [DONNERHACKE] Donnerhacke, L., et. al, "PGP263in - an improved + international version of PGP", ftp://ftp.iks- + jena.de/mitarb/lutz/crypt/software/pgp/ + + [ELGAMAL] T. ElGamal, "A Public-Key Cryptosystem and a + Signature Scheme Based on Discrete Logarithms," IEEE + Transactions on Information Theory, v. IT-31, n. 4, + 1985, pp. 469-472. + + [IDEA] Lai, X, "On the design and security of block + ciphers", ETH Series in Information Processing, J.L. + Massey (editor), Vol. 1, Hartung-Gorre Verlag + Knostanz, Technische Hochschule (Zurich), 1992 + + [ISO-10646] ISO/IEC 10646-1:1993. International Standard -- + Information technology -- Universal Multiple-Octet + Coded Character Set (UCS) -- Part 1: Architecture + and Basic Multilingual Plane. UTF-8 is described in + Annex R, adopted but not yet published. UTF-16 is + described in Annex Q, adopted but not yet published. + + [MENEZES] Alfred Menezes, Paul van Oorschot, and Scott + Vanstone, "Handbook of Applied Cryptography," CRC + Press, 1996. + + + + +Callas, et. al. Standards Track [Page 63] + +RFC 2440 OpenPGP Message Format November 1998 + + + [RFC822] Crocker, D., "Standard for the format of ARPA + Internet text messages", STD 11, RFC 822, August + 1982. + + [RFC1423] Balenson, D., "Privacy Enhancement for Internet + Electronic Mail: Part III: Algorithms, Modes, and + Identifiers", RFC 1423, October 1993. + + [RFC1641] Goldsmith, D. and M. Davis, "Using Unicode with + MIME", RFC 1641, July 1994. + + [RFC1750] Eastlake, D., Crocker, S. and J. Schiller, + "Randomness Recommendations for Security", RFC 1750, + December 1994. + + [RFC1951] Deutsch, P., "DEFLATE Compressed Data Format + Specification version 1.3.", RFC 1951, May 1996. + + [RFC1983] Malkin, G., "Internet Users' Glossary", FYI 18, RFC + 1983, August 1996. + + [RFC1991] Atkins, D., Stallings, W. and P. Zimmermann, "PGP + Message Exchange Formats", RFC 1991, August 1996. + + [RFC2015] Elkins, M., "MIME Security with Pretty Good Privacy + (PGP)", RFC 2015, October 1996. + + [RFC2231] Borenstein, N. and N. Freed, "Multipurpose Internet + Mail Extensions (MIME) Part One: Format of Internet + Message Bodies.", RFC 2231, November 1996. + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Level", BCP 14, RFC 2119, March 1997. + + [RFC2144] Adams, C., "The CAST-128 Encryption Algorithm", RFC + 2144, May 1997. + + [RFC2279] Yergeau., F., "UTF-8, a transformation format of + Unicode and ISO 10646", RFC 2279, January 1998. + + [RFC2313] Kaliski, B., "PKCS #1: RSA Encryption Standard + version 1.5", RFC 2313, March 1998. + + [SAFER] Massey, J.L. "SAFER K-64: One Year Later", B. + Preneel, editor, Fast Software Encryption, Second + International Workshop (LNCS 1008) pp212-241, + Springer-Verlag 1995 + + + + +Callas, et. al. Standards Track [Page 64] + +RFC 2440 OpenPGP Message Format November 1998 + + +17. Full Copyright Statement + + Copyright (C) The Internet Society (1998). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Callas, et. al. Standards Track [Page 65] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2487.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2487.txt new file mode 100644 index 0000000..fb1305f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2487.txt @@ -0,0 +1,451 @@ + + + + + + +Network Working Group P. Hoffman +Request for Comments: 2487 Internet Mail Consortium +Category: Standards Track January 1999 + + + SMTP Service Extension for Secure SMTP over TLS + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1999). All Rights Reserved. + +1. Abstract + + This document describes an extension to the SMTP service that allows + an SMTP server and client to use transport-layer security to provide + private, authenticated communication over the Internet. This gives + SMTP agents the ability to protect some or all of their + communications from eavesdroppers and attackers. + +2. Introduction + + SMTP [RFC-821] servers and clients normally communicate in the clear + over the Internet. In many cases, this communication goes through one + or more router that is not controlled or trusted by either entity. + Such an untrusted router might allow a third party to monitor or + alter the communications between the server and client. + + Further, there is often a desire for two SMTP agents to be able to + authenticate each others' identities. For example, a secure SMTP + server might only allow communications from other SMTP agents it + knows, or it might act differently for messages received from an + agent it knows than from one it doesn't know. + + TLS [TLS], more commonly known as SSL, is a popular mechanism for + enhancing TCP communications with privacy and authentication. TLS is + in wide use with the HTTP protocol, and is also being used for adding + security to many other common protocols that run over TCP. + + + + + + +Hoffman Standards Track [Page 1] + +RFC 2487 SMTP Service Extension January 1999 + + +2.1 Terminology + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [RFC-2119]. + +3. STARTTLS Extension + + The STARTTLS extension to SMTP is laid out as follows: + + (1) the name of the SMTP service defined here is STARTTLS; + + (2) the EHLO keyword value associated with the extension is STARTTLS; + + (3) the STARTTLS keyword has no parameters; + + (4) a new SMTP verb, "STARTTLS", is defined; + + (5) no additional parameters are added to any SMTP command. + +4. The STARTTLS Keyword + + The STARTTLS keyword is used to tell the SMTP client that the SMTP + server allows use of TLS. It takes no parameters. + +5. The STARTTLS Command + + The format for the STARTTLS command is: + + STARTTLS + + with no parameters. + + After the client gives the STARTTLS command, the server responds with + one of the following reply codes: + + 220 Ready to start TLS + 501 Syntax error (no parameters allowed) + 454 TLS not available due to temporary reason + + A publicly-referenced SMTP server MUST NOT require use of the + STARTTLS extension in order to deliver mail locally. This rule + prevents the STARTTLS extension from damaging the interoperability of + the Internet's SMTP infrastructure. A publicly-referenced SMTP server + is an SMTP server which runs on port 25 of an Internet host listed in + the MX record (or A record if an MX record is not present) for the + domain name on the right hand side of an Internet mail address. + + + + +Hoffman Standards Track [Page 2] + +RFC 2487 SMTP Service Extension January 1999 + + + Any SMTP server may refuse to accept messages for relay based on + authentication supplied during the TLS negotiation. An SMTP server + that is not publicly referenced may refuse to accept any messages for + relay or local delivery based on authentication supplied during the + TLS negotiation. + + A SMTP server that is not publicly referenced may choose to require + that the client perform a TLS negotiation before accepting any + commands. In this case, the server SHOULD return the reply code: + + 530 Must issue a STARTTLS command first + + to every command other than NOOP, EHLO, STARTTLS, or QUIT. If the + client and server are using the ENHANCEDSTATUSCODES ESMTP extension + [RFC-2034], the status code to be returned SHOULD be 5.7.0. + + After receiving a 220 response to a STARTTLS command, the client + SHOULD start the TLS negotiation before giving any other SMTP + commands. + + If the SMTP client is using pipelining as defined in RFC 1854, the + STARTTLS command must be the last command in a group. + +5.1 Processing After the STARTTLS Command + + After the TLS handshake has been completed, both parties MUST + immediately decide whether or not to continue based on the + authentication and privacy achieved. The SMTP client and server may + decide to move ahead even if the TLS negotiation ended with no + authentication and/or no privacy because most SMTP services are + performed with no authentication and no privacy, but some SMTP + clients or servers may want to continue only if a particular level of + authentication and/or privacy was achieved. + + If the SMTP client decides that the level of authentication or + privacy is not high enough for it to continue, it SHOULD issue an + SMTP QUIT command immediately after the TLS negotiation is complete. + If the SMTP server decides that the level of authentication or + privacy is not high enough for it to continue, it SHOULD reply to + every SMTP command from the client (other than a QUIT command) with + the 554 reply code (with a possible text string such as "Command + refused due to lack of security"). + + The decision of whether or not to believe the authenticity of the + other party in a TLS negotiation is a local matter. However, some + general rules for the decisions are: + + + + + +Hoffman Standards Track [Page 3] + +RFC 2487 SMTP Service Extension January 1999 + + + - A SMTP client would probably only want to authenticate an SMTP + server whose server certificate has a domain name that is the + domain name that the client thought it was connecting to. + - A publicly-referenced SMTP server would probably want to accept + any certificate from an SMTP client, and would possibly want to + put distinguishing information about the certificate in the + Received header of messages that were relayed or submitted from + the client. + +5.2 Result of the STARTTLS Command + + Upon completion of the TLS handshake, the SMTP protocol is reset to + the initial state (the state in SMTP after a server issues a 220 + service ready greeting). The server MUST discard any knowledge + obtained from the client, such as the argument to the EHLO command, + which was not obtained from the TLS negotiation itself. The client + MUST discard any knowledge obtained from the server, such as the list + of SMTP service extensions, which was not obtained from the TLS + negotiation itself. The client SHOULD send an EHLO command as the + first command after a successful TLS negotiation. + + The list of SMTP service extensions returned in response to an EHLO + command received after the TLS handshake MAY be different than the + list returned before the TLS handshake. For example, an SMTP server + might not want to advertise support for a particular SASL mechanism + [SASL] unless a client has sent an appropriate client certificate + during a TLS handshake. + + Both the client and the server MUST know if there is a TLS session + active. A client MUST NOT attempt to start a TLS session if a TLS + session is already active. A server MUST NOT return the TLS extension + in response to an EHLO command received after a TLS handshake has + completed. + +6. Usage Example + + The following dialog illustrates how a client and server can start a + TLS session: + + S: + C: + S: 220 mail.imc.org SMTP service ready + C: EHLO mail.ietf.org + S: 250-mail.imc.org offers a warm hug of welcome + S: 250 STARTTLS + C: STARTTLS + S: 220 Go ahead + C: + + + +Hoffman Standards Track [Page 4] + +RFC 2487 SMTP Service Extension January 1999 + + + C & S: + C & S: + C: + . . . + +7. Security Considerations + + It should be noted that SMTP is not an end-to-end mechanism. Thus, if + an SMTP client/server pair decide to add TLS privacy, they are not + securing the transport from the originating mail user agent to the + recipient. Further, because delivery of a single piece of mail may + go between more than two SMTP servers, adding TLS privacy to one pair + of servers does not mean that the entire SMTP chain has been made + private. Further, just because an SMTP server can authenticate an + SMTP client, it does not mean that the mail from the SMTP client was + authenticated by the SMTP client when the client received it. + + Both the STMP client and server must check the result of the TLS + negotiation to see whether acceptable authentication or privacy was + achieved. Ignoring this step completely invalidates using TLS for + security. The decision about whether acceptable authentication or + privacy was achieved is made locally, is implementation-dependant, + and is beyond the scope of this document. + + The SMTP client and server should note carefully the result of the + TLS negotiation. If the negotiation results in no privacy, or if it + results in privacy using algorithms or key lengths that are deemed + not strong enough, or if the authentication is not good enough for + either party, the client may choose to end the SMTP session with an + immediate QUIT command, or the server may choose to not accept any + more SMTP commands. + + A server announcing in an EHLO response that it uses a particular TLS + protocol should not pose any security issues, since any use of TLS + will be at least as secure as no use of TLS. + + A man-in-the-middle attack can be launched by deleting the "250 + STARTTLS" response from the server. This would cause the client not + to try to start a TLS session. An SMTP client can protect against + this attack by recording the fact that a particular SMTP server + offers TLS during one session and generating an alarm if it does not + appear in the EHLO response for a later session. The lack of TLS + during a session SHOULD NOT result in the bouncing of email, although + it could result in delayed processing. + + + + + + + +Hoffman Standards Track [Page 5] + +RFC 2487 SMTP Service Extension January 1999 + + + Before the TLS handshake has begun, any protocol interactions are + performed in the clear and may be modified by an active attacker. For + this reason, clients and servers MUST discard any knowledge obtained + prior to the start of the TLS handshake upon completion of the TLS + handshake. + + The STARTTLS extension is not suitable for authenticating the author + of an email message unless every hop in the delivery chain, including + the submission to the first SMTP server, is authenticated. Another + proposal [SMTP-AUTH] can be used to authenticate delivery and MIME + security multiparts [MIME-SEC] can be used to authenticate the author + of an email message. In addition, the [SMTP-AUTH] proposal offers + simpler and more flexible options to authenticate an SMTP client and + the SASL EXTERNAL mechanism [SASL] MAY be used in conjunction with + the STARTTLS command to provide an authorization identity. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Hoffman Standards Track [Page 6] + +RFC 2487 SMTP Service Extension January 1999 + + +A. References + + [RFC-821] Postel, J., "Simple Mail Transfer Protocol", RFC 821, + August 1982. + + [RFC-1869] Klensin, J., Freed, N, Rose, M, Stefferud, E. and D. + Crocker, "SMTP Service Extensions", STD 10, RFC 1869, + November 1995. + + [RFC-2034] Freed, N., "SMTP Service Extension for Returning Enhanced + Error Codes", RFC 2034, October 1996. + + [RFC-2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [SASL] Myers, J., "Simple Authentication and Security Layer + (SASL)", RFC 2222, October 1997. + + [SMTP-AUTH] "SMTP Service Extension for Authentication", Work in + Progress. + + [TLS] Dierks, T. and C. Allen, "The TLS Protocol Version 1.0", + RFC 2246, January 1999. + +B. Author's Address + + Paul Hoffman + Internet Mail Consortium + 127 Segre Place + Santa Cruz, CA 95060 + + Phone: (831) 426-9827 + EMail: phoffman@imc.org + + + + + + + + + + + + + + + + + + +Hoffman Standards Track [Page 7] + +RFC 2487 SMTP Service Extension January 1999 + + +C. Full Copyright Statement + + Copyright (C) The Internet Society (1999). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Hoffman Standards Track [Page 8] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2554.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2554.txt new file mode 100644 index 0000000..2922dea --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2554.txt @@ -0,0 +1,619 @@ + + + + + + +Network Working Group J. Myers +Request for Comments: 2554 Netscape Communications +Category: Standards Track March 1999 + + + SMTP Service Extension + for Authentication + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1999). All Rights Reserved. + + +1. Introduction + + This document defines an SMTP service extension [ESMTP] whereby an + SMTP client may indicate an authentication mechanism to the server, + perform an authentication protocol exchange, and optionally negotiate + a security layer for subsequent protocol interactions. This + extension is a profile of the Simple Authentication and Security + Layer [SASL]. + + +2. Conventions Used in this Document + + In examples, "C:" and "S:" indicate lines sent by the client and + server respectively. + + The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY" + in this document are to be interpreted as defined in "Key words for + use in RFCs to Indicate Requirement Levels" [KEYWORDS]. + + +3. The Authentication service extension + + + (1) the name of the SMTP service extension is "Authentication" + + (2) the EHLO keyword value associated with this extension is "AUTH" + + + + +Myers Standards Track [Page 1] + +RFC 2554 SMTP Authentication March 1999 + + + (3) The AUTH EHLO keyword contains as a parameter a space separated + list of the names of supported SASL mechanisms. + + (4) a new SMTP verb "AUTH" is defined + + (5) an optional parameter using the keyword "AUTH" is added to the + MAIL FROM command, and extends the maximum line length of the + MAIL FROM command by 500 characters. + + (6) this extension is appropriate for the submission protocol + [SUBMIT]. + + +4. The AUTH command + + AUTH mechanism [initial-response] + + Arguments: + a string identifying a SASL authentication mechanism. + an optional base64-encoded response + + Restrictions: + After an AUTH command has successfully completed, no more AUTH + commands may be issued in the same session. After a successful + AUTH command completes, a server MUST reject any further AUTH + commands with a 503 reply. + + The AUTH command is not permitted during a mail transaction. + + Discussion: + The AUTH command indicates an authentication mechanism to the + server. If the server supports the requested authentication + mechanism, it performs an authentication protocol exchange to + authenticate and identify the user. Optionally, it also + negotiates a security layer for subsequent protocol + interactions. If the requested authentication mechanism is not + supported, the server rejects the AUTH command with a 504 + reply. + + The authentication protocol exchange consists of a series of + server challenges and client answers that are specific to the + authentication mechanism. A server challenge, otherwise known + as a ready response, is a 334 reply with the text part + containing a BASE64 encoded string. The client answer consists + of a line containing a BASE64 encoded string. If the client + wishes to cancel an authentication exchange, it issues a line + with a single "*". If the server receives such an answer, it + MUST reject the AUTH command by sending a 501 reply. + + + +Myers Standards Track [Page 2] + +RFC 2554 SMTP Authentication March 1999 + + + The optional initial-response argument to the AUTH command is + used to save a round trip when using authentication mechanisms + that are defined to send no data in the initial challenge. + When the initial-response argument is used with such a + mechanism, the initial empty challenge is not sent to the + client and the server uses the data in the initial-response + argument as if it were sent in response to the empty challenge. + Unlike a zero-length client answer to a 334 reply, a zero- + length initial response is sent as a single equals sign ("="). + If the client uses an initial-response argument to the AUTH + command with a mechanism that sends data in the initial + challenge, the server rejects the AUTH command with a 535 + reply. + + If the server cannot BASE64 decode the argument, it rejects the + AUTH command with a 501 reply. If the server rejects the + authentication data, it SHOULD reject the AUTH command with a + 535 reply unless a more specific error code, such as one listed + in section 6, is appropriate. Should the client successfully + complete the authentication exchange, the SMTP server issues a + 235 reply. + + The service name specified by this protocol's profile of SASL + is "smtp". + + If a security layer is negotiated through the SASL + authentication exchange, it takes effect immediately following + the CRLF that concludes the authentication exchange for the + client, and the CRLF of the success reply for the server. Upon + a security layer's taking effect, the SMTP protocol is reset to + the initial state (the state in SMTP after a server issues a + 220 service ready greeting). The server MUST discard any + knowledge obtained from the client, such as the argument to the + EHLO command, which was not obtained from the SASL negotiation + itself. The client MUST discard any knowledge obtained from + the server, such as the list of SMTP service extensions, which + was not obtained from the SASL negotiation itself (with the + exception that a client MAY compare the list of advertised SASL + mechanisms before and after authentication in order to detect + an active down-negotiation attack). The client SHOULD send an + EHLO command as the first command after a successful SASL + negotiation which results in the enabling of a security layer. + + The server is not required to support any particular + authentication mechanism, nor are authentication mechanisms + required to support any security layers. If an AUTH command + fails, the client may try another authentication mechanism by + issuing another AUTH command. + + + +Myers Standards Track [Page 3] + +RFC 2554 SMTP Authentication March 1999 + + + If an AUTH command fails, the server MUST behave the same as if + the client had not issued the AUTH command. + + The BASE64 string may in general be arbitrarily long. Clients + and servers MUST be able to support challenges and responses + that are as long as are generated by the authentication + mechanisms they support, independent of any line length + limitations the client or server may have in other parts of its + protocol implementation. + + Examples: + S: 220 smtp.example.com ESMTP server ready + C: EHLO jgm.example.com + S: 250-smtp.example.com + S: 250 AUTH CRAM-MD5 DIGEST-MD5 + C: AUTH FOOBAR + S: 504 Unrecognized authentication type. + C: AUTH CRAM-MD5 + S: 334 + PENCeUxFREJoU0NnbmhNWitOMjNGNndAZWx3b29kLmlubm9zb2Z0LmNvbT4= + C: ZnJlZCA5ZTk1YWVlMDljNDBhZjJiODRhMGMyYjNiYmFlNzg2ZQ== + S: 235 Authentication successful. + + + +5. The AUTH parameter to the MAIL FROM command + + AUTH=addr-spec + + Arguments: + An addr-spec containing the identity which submitted the message + to the delivery system, or the two character sequence "<>" + indicating such an identity is unknown or insufficiently + authenticated. To comply with the restrictions imposed on ESMTP + parameters, the addr-spec is encoded inside an xtext. The syntax + of an xtext is described in section 5 of [ESMTP-DSN]. + + Discussion: + The optional AUTH parameter to the MAIL FROM command allows + cooperating agents in a trusted environment to communicate the + authentication of individual messages. + + If the server trusts the authenticated identity of the client to + assert that the message was originally submitted by the supplied + addr-spec, then the server SHOULD supply the same addr-spec in an + AUTH parameter when relaying the message to any server which + supports the AUTH extension. + + + + +Myers Standards Track [Page 4] + +RFC 2554 SMTP Authentication March 1999 + + + A MAIL FROM parameter of AUTH=<> indicates that the original + submitter of the message is not known. The server MUST NOT treat + the message as having been originally submitted by the client. + + If the AUTH parameter to the MAIL FROM is not supplied, the + client has authenticated, and the server believes the message is + an original submission by the client, the server MAY supply the + client's identity in the addr-spec in an AUTH parameter when + relaying the message to any server which supports the AUTH + extension. + + If the server does not sufficiently trust the authenticated + identity of the client, or if the client is not authenticated, + then the server MUST behave as if the AUTH=<> parameter was + supplied. The server MAY, however, write the value of the AUTH + parameter to a log file. + + If an AUTH=<> parameter was supplied, either explicitly or due to + the requirement in the previous paragraph, then the server MUST + supply the AUTH=<> parameter when relaying the message to any + server which it has authenticated to using the AUTH extension. + + A server MAY treat expansion of a mailing list as a new + submission, setting the AUTH parameter to the mailing list + address or mailing list administration address when relaying the + message to list subscribers. + + It is conforming for an implementation to be hard-coded to treat + all clients as being insufficiently trusted. In that case, the + implementation does nothing more than parse and discard + syntactically valid AUTH parameters to the MAIL FROM command and + supply AUTH=<> parameters to any servers to which it + authenticates using the AUTH extension. + + Examples: + C: MAIL FROM: AUTH=e+3Dmc2@example.com + S: 250 OK + + + + + + + + + + + + + + +Myers Standards Track [Page 5] + +RFC 2554 SMTP Authentication March 1999 + + +6. Error Codes + + The following error codes may be used to indicate various conditions + as described. + + 432 A password transition is needed + + This response to the AUTH command indicates that the user needs to + transition to the selected authentication mechanism. This typically + done by authenticating once using the PLAIN authentication mechanism. + + 534 Authentication mechanism is too weak + + This response to the AUTH command indicates that the selected + authentication mechanism is weaker than server policy permits for + that user. + + 538 Encryption required for requested authentication mechanism + + This response to the AUTH command indicates that the selected + authentication mechanism may only be used when the underlying SMTP + connection is encrypted. + + 454 Temporary authentication failure + + This response to the AUTH command indicates that the authentication + failed due to a temporary server failure. + + 530 Authentication required + + This response may be returned by any command other than AUTH, EHLO, + HELO, NOOP, RSET, or QUIT. It indicates that server policy requires + authentication in order to perform the requested action. + + + + + + + + + + + + + + + + + + +Myers Standards Track [Page 6] + +RFC 2554 SMTP Authentication March 1999 + + +7. Formal Syntax + + The following syntax specification uses the augmented Backus-Naur + Form (BNF) notation as specified in [ABNF]. + + Except as noted otherwise, all alphabetic characters are case- + insensitive. The use of upper or lower case characters to define + token strings is for editorial clarity only. Implementations MUST + accept these strings in a case-insensitive fashion. + + UPALPHA = %x41-5A ;; Uppercase: A-Z + + LOALPHA = %x61-7A ;; Lowercase: a-z + + ALPHA = UPALPHA / LOALPHA ;; case insensitive + + DIGIT = %x30-39 ;; Digits 0-9 + + HEXDIGIT = %x41-46 / DIGIT ;; hexidecimal digit (uppercase) + + hexchar = "+" HEXDIGIT HEXDIGIT + + xchar = %x21-2A / %x2C-3C / %x3E-7E + ;; US-ASCII except for "+", "=", SPACE and CTL + + xtext = *(xchar / hexchar) + + AUTH_CHAR = ALPHA / DIGIT / "-" / "_" + + auth_type = 1*20AUTH_CHAR + + auth_command = "AUTH" SPACE auth_type [SPACE (base64 / "=")] + *(CRLF [base64]) CRLF + + auth_param = "AUTH=" xtext + ;; The decoded form of the xtext MUST be either + ;; an addr-spec or the two characters "<>" + + base64 = base64_terminal / + ( 1*(4base64_CHAR) [base64_terminal] ) + + base64_char = UPALPHA / LOALPHA / DIGIT / "+" / "/" + ;; Case-sensitive + + base64_terminal = (2base64_char "==") / (3base64_char "=") + + continue_req = "334" SPACE [base64] CRLF + + + + +Myers Standards Track [Page 7] + +RFC 2554 SMTP Authentication March 1999 + + + CR = %x0C ;; ASCII CR, carriage return + + CRLF = CR LF + + CTL = %x00-1F / %x7F ;; any ASCII control character and DEL + + LF = %x0A ;; ASCII LF, line feed + + SPACE = %x20 ;; ASCII SP, space + + + + +8. References + + [ABNF] Crocker, D. and P. Overell, "Augmented BNF for Syntax + Specifications: ABNF", RFC 2234, November 1997. + + [CRAM-MD5] Klensin, J., Catoe, R. and P. Krumviede, "IMAP/POP + AUTHorize Extension for Simple Challenge/Response", RFC + 2195, September 1997. + + [ESMTP] Klensin, J., Freed, N., Rose, M., Stefferud, E. and D. + Crocker, "SMTP Service Extensions", RFC 1869, November + 1995. + + [ESMTP-DSN] Moore, K, "SMTP Service Extension for Delivery Status + Notifications", RFC 1891, January 1996. + + [KEYWORDS] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [SASL] Myers, J., "Simple Authentication and Security Layer + (SASL)", RFC 2222, October 1997. + + [SUBMIT] Gellens, R. and J. Klensin, "Message Submission", RFC + 2476, December 1998. + + [RFC821] Postel, J., "Simple Mail Transfer Protocol", STD 10, RFC + 821, August 1982. + + [RFC822] Crocker, D., "Standard for the Format of ARPA Internet + Text Messages", STD 11, RFC 822, August 1982. + + + + + + + + +Myers Standards Track [Page 8] + +RFC 2554 SMTP Authentication March 1999 + + +9. Security Considerations + + Security issues are discussed throughout this memo. + + If a client uses this extension to get an encrypted tunnel through an + insecure network to a cooperating server, it needs to be configured + to never send mail to that server when the connection is not mutually + authenticated and encrypted. Otherwise, an attacker could steal the + client's mail by hijacking the SMTP connection and either pretending + the server does not support the Authentication extension or causing + all AUTH commands to fail. + + Before the SASL negotiation has begun, any protocol interactions are + performed in the clear and may be modified by an active attacker. + For this reason, clients and servers MUST discard any knowledge + obtained prior to the start of the SASL negotiation upon completion + of a SASL negotiation which results in a security layer. + + This mechanism does not protect the TCP port, so an active attacker + may redirect a relay connection attempt to the submission port + [SUBMIT]. The AUTH=<> parameter prevents such an attack from causing + an relayed message without an envelope authentication to pick up the + authentication of the relay client. + + A message submission client may require the user to authenticate + whenever a suitable SASL mechanism is advertised. Therefore, it may + not be desirable for a submission server [SUBMIT] to advertise a SASL + mechanism when use of that mechanism grants the client no benefits + over anonymous submission. + + This extension is not intended to replace or be used instead of end- + to-end message signature and encryption systems such as S/MIME or + PGP. This extension addresses a different problem than end-to-end + systems; it has the following key differences: + + (1) it is generally useful only within a trusted enclave + + (2) it protects the entire envelope of a message, not just the + message's body. + + (3) it authenticates the message submission, not authorship of the + message content + + (4) it can give the sender some assurance the message was + delivered to the next hop in the case where the sender + mutually authenticates with the next hop and negotiates an + appropriate security layer. + + + + +Myers Standards Track [Page 9] + +RFC 2554 SMTP Authentication March 1999 + + + Additional security considerations are mentioned in the SASL + specification [SASL]. + + + +10. Author's Address + + John Gardiner Myers + Netscape Communications + 501 East Middlefield Road + Mail Stop MV-029 + Mountain View, CA 94043 + + EMail: jgmyers@netscape.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Myers Standards Track [Page 10] + +RFC 2554 SMTP Authentication March 1999 + + +11. Full Copyright Statement + + Copyright (C) The Internet Society (1999). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Myers Standards Track [Page 11] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2821.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2821.txt new file mode 100644 index 0000000..0eac911 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2821.txt @@ -0,0 +1,4427 @@ + + + + + + +Network Working Group J. Klensin, Editor +Request for Comments: 2821 AT&T Laboratories +Obsoletes: 821, 974, 1869 April 2001 +Updates: 1123 +Category: Standards Track + + + Simple Mail Transfer Protocol + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2001). All Rights Reserved. + +Abstract + + This document is a self-contained specification of the basic protocol + for the Internet electronic mail transport. It consolidates, updates + and clarifies, but doesn't add new or change existing functionality + of the following: + + - the original SMTP (Simple Mail Transfer Protocol) specification of + RFC 821 [30], + + - domain name system requirements and implications for mail + transport from RFC 1035 [22] and RFC 974 [27], + + - the clarifications and applicability statements in RFC 1123 [2], + and + + - material drawn from the SMTP Extension mechanisms [19]. + + It obsoletes RFC 821, RFC 974, and updates RFC 1123 (replaces the + mail transport materials of RFC 1123). However, RFC 821 specifies + some features that were not in significant use in the Internet by the + mid-1990s and (in appendices) some additional transport models. + Those sections are omitted here in the interest of clarity and + brevity; readers needing them should refer to RFC 821. + + + + + + +Klensin Standards Track [Page 1] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + It also includes some additional material from RFC 1123 that required + amplification. This material has been identified in multiple ways, + mostly by tracking flaming on various lists and newsgroups and + problems of unusual readings or interpretations that have appeared as + the SMTP extensions have been deployed. Where this specification + moves beyond consolidation and actually differs from earlier + documents, it supersedes them technically as well as textually. + + Although SMTP was designed as a mail transport and delivery protocol, + this specification also contains information that is important to its + use as a 'mail submission' protocol, as recommended for POP [3, 26] + and IMAP [6]. Additional submission issues are discussed in RFC 2476 + [15]. + + Section 2.3 provides definitions of terms specific to this document. + Except when the historical terminology is necessary for clarity, this + document uses the current 'client' and 'server' terminology to + identify the sending and receiving SMTP processes, respectively. + + A companion document [32] discusses message headers, message bodies + and formats and structures for them, and their relationship. + +Table of Contents + + 1. Introduction .................................................. 4 + 2. The SMTP Model ................................................ 5 + 2.1 Basic Structure .............................................. 5 + 2.2 The Extension Model .......................................... 7 + 2.2.1 Background ................................................. 7 + 2.2.2 Definition and Registration of Extensions .................. 8 + 2.3 Terminology .................................................. 9 + 2.3.1 Mail Objects ............................................... 10 + 2.3.2 Senders and Receivers ...................................... 10 + 2.3.3 Mail Agents and Message Stores ............................. 10 + 2.3.4 Host ....................................................... 11 + 2.3.5 Domain ..................................................... 11 + 2.3.6 Buffer and State Table ..................................... 11 + 2.3.7 Lines ...................................................... 12 + 2.3.8 Originator, Delivery, Relay, and Gateway Systems ........... 12 + 2.3.9 Message Content and Mail Data .............................. 13 + 2.3.10 Mailbox and Address ....................................... 13 + 2.3.11 Reply ..................................................... 13 + 2.4 General Syntax Principles and Transaction Model .............. 13 + 3. The SMTP Procedures: An Overview .............................. 15 + 3.1 Session Initiation ........................................... 15 + 3.2 Client Initiation ............................................ 16 + 3.3 Mail Transactions ............................................ 16 + 3.4 Forwarding for Address Correction or Updating ................ 19 + + + +Klensin Standards Track [Page 2] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + 3.5 Commands for Debugging Addresses ............................. 20 + 3.5.1 Overview ................................................... 20 + 3.5.2 VRFY Normal Response ....................................... 22 + 3.5.3 Meaning of VRFY or EXPN Success Response ................... 22 + 3.5.4 Semantics and Applications of EXPN ......................... 23 + 3.6 Domains ...................................................... 23 + 3.7 Relaying ..................................................... 24 + 3.8 Mail Gatewaying .............................................. 25 + 3.8.1 Header Fields in Gatewaying ................................ 26 + 3.8.2 Received Lines in Gatewaying ............................... 26 + 3.8.3 Addresses in Gatewaying .................................... 26 + 3.8.4 Other Header Fields in Gatewaying .......................... 27 + 3.8.5 Envelopes in Gatewaying .................................... 27 + 3.9 Terminating Sessions and Connections ......................... 27 + 3.10 Mailing Lists and Aliases ................................... 28 + 3.10.1 Alias ..................................................... 28 + 3.10.2 List ...................................................... 28 + 4. The SMTP Specifications ....................................... 29 + 4.1 SMTP Commands ................................................ 29 + 4.1.1 Command Semantics and Syntax ............................... 29 + 4.1.1.1 Extended HELLO (EHLO) or HELLO (HELO) ................... 29 + 4.1.1.2 MAIL (MAIL) .............................................. 31 + 4.1.1.3 RECIPIENT (RCPT) ......................................... 31 + 4.1.1.4 DATA (DATA) .............................................. 33 + 4.1.1.5 RESET (RSET) ............................................. 34 + 4.1.1.6 VERIFY (VRFY) ............................................ 35 + 4.1.1.7 EXPAND (EXPN) ............................................ 35 + 4.1.1.8 HELP (HELP) .............................................. 35 + 4.1.1.9 NOOP (NOOP) .............................................. 35 + 4.1.1.10 QUIT (QUIT) ............................................. 36 + 4.1.2 Command Argument Syntax .................................... 36 + 4.1.3 Address Literals ........................................... 38 + 4.1.4 Order of Commands .......................................... 39 + 4.1.5 Private-use Commands ....................................... 40 + 4.2 SMTP Replies ................................................ 40 + 4.2.1 Reply Code Severities and Theory ........................... 42 + 4.2.2 Reply Codes by Function Groups ............................. 44 + 4.2.3 Reply Codes in Numeric Order .............................. 45 + 4.2.4 Reply Code 502 ............................................. 46 + 4.2.5 Reply Codes After DATA and the Subsequent . .... 46 + 4.3 Sequencing of Commands and Replies ........................... 47 + 4.3.1 Sequencing Overview ........................................ 47 + 4.3.2 Command-Reply Sequences .................................... 48 + 4.4 Trace Information ............................................ 49 + 4.5 Additional Implementation Issues ............................. 53 + 4.5.1 Minimum Implementation ..................................... 53 + 4.5.2 Transparency ............................................... 53 + 4.5.3 Sizes and Timeouts ......................................... 54 + + + +Klensin Standards Track [Page 3] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + 4.5.3.1 Size limits and minimums ................................. 54 + 4.5.3.2 Timeouts ................................................. 56 + 4.5.4 Retry Strategies ........................................... 57 + 4.5.4.1 Sending Strategy ......................................... 58 + 4.5.4.2 Receiving Strategy ....................................... 59 + 4.5.5 Messages with a null reverse-path .......................... 59 + 5. Address Resolution and Mail Handling .......................... 60 + 6. Problem Detection and Handling ................................ 62 + 6.1 Reliable Delivery and Replies by Email ....................... 62 + 6.2 Loop Detection ............................................... 63 + 6.3 Compensating for Irregularities .............................. 63 + 7. Security Considerations ....................................... 64 + 7.1 Mail Security and Spoofing ................................... 64 + 7.2 "Blind" Copies ............................................... 65 + 7.3 VRFY, EXPN, and Security ..................................... 65 + 7.4 Information Disclosure in Announcements ...................... 66 + 7.5 Information Disclosure in Trace Fields ....................... 66 + 7.6 Information Disclosure in Message Forwarding ................. 67 + 7.7 Scope of Operation of SMTP Servers ........................... 67 + 8. IANA Considerations ........................................... 67 + 9. References .................................................... 68 + 10. Editor's Address ............................................. 70 + 11. Acknowledgments .............................................. 70 + Appendices ....................................................... 71 + A. TCP Transport Service ......................................... 71 + B. Generating SMTP Commands from RFC 822 Headers ................. 71 + C. Source Routes ................................................. 72 + D. Scenarios ..................................................... 73 + E. Other Gateway Issues .......................................... 76 + F. Deprecated Features of RFC 821 ................................ 76 + Full Copyright Statement ......................................... 79 + +1. Introduction + + The objective of the Simple Mail Transfer Protocol (SMTP) is to + transfer mail reliably and efficiently. + + SMTP is independent of the particular transmission subsystem and + requires only a reliable ordered data stream channel. While this + document specifically discusses transport over TCP, other transports + are possible. Appendices to RFC 821 describe some of them. + + An important feature of SMTP is its capability to transport mail + across networks, usually referred to as "SMTP mail relaying" (see + section 3.8). A network consists of the mutually-TCP-accessible + hosts on the public Internet, the mutually-TCP-accessible hosts on a + firewall-isolated TCP/IP Intranet, or hosts in some other LAN or WAN + environment utilizing a non-TCP transport-level protocol. Using + + + +Klensin Standards Track [Page 4] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + SMTP, a process can transfer mail to another process on the same + network or to some other network via a relay or gateway process + accessible to both networks. + + In this way, a mail message may pass through a number of intermediate + relay or gateway hosts on its path from sender to ultimate recipient. + The Mail eXchanger mechanisms of the domain name system [22, 27] (and + section 5 of this document) are used to identify the appropriate + next-hop destination for a message being transported. + +2. The SMTP Model + +2.1 Basic Structure + + The SMTP design can be pictured as: + + +----------+ +----------+ + +------+ | | | | + | User |<-->| | SMTP | | + +------+ | Client- |Commands/Replies| Server- | + +------+ | SMTP |<-------------->| SMTP | +------+ + | File |<-->| | and Mail | |<-->| File | + |System| | | | | |System| + +------+ +----------+ +----------+ +------+ + SMTP client SMTP server + + When an SMTP client has a message to transmit, it establishes a two- + way transmission channel to an SMTP server. The responsibility of an + SMTP client is to transfer mail messages to one or more SMTP servers, + or report its failure to do so. + + The means by which a mail message is presented to an SMTP client, and + how that client determines the domain name(s) to which mail messages + are to be transferred is a local matter, and is not addressed by this + document. In some cases, the domain name(s) transferred to, or + determined by, an SMTP client will identify the final destination(s) + of the mail message. In other cases, common with SMTP clients + associated with implementations of the POP [3, 26] or IMAP [6] + protocols, or when the SMTP client is inside an isolated transport + service environment, the domain name determined will identify an + intermediate destination through which all mail messages are to be + relayed. SMTP clients that transfer all traffic, regardless of the + target domain names associated with the individual messages, or that + do not maintain queues for retrying message transmissions that + initially cannot be completed, may otherwise conform to this + specification but are not considered fully-capable. Fully-capable + SMTP implementations, including the relays used by these less capable + + + + +Klensin Standards Track [Page 5] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + ones, and their destinations, are expected to support all of the + queuing, retrying, and alternate address functions discussed in this + specification. + + The means by which an SMTP client, once it has determined a target + domain name, determines the identity of an SMTP server to which a + copy of a message is to be transferred, and then performs that + transfer, is covered by this document. To effect a mail transfer to + an SMTP server, an SMTP client establishes a two-way transmission + channel to that SMTP server. An SMTP client determines the address + of an appropriate host running an SMTP server by resolving a + destination domain name to either an intermediate Mail eXchanger host + or a final target host. + + An SMTP server may be either the ultimate destination or an + intermediate "relay" (that is, it may assume the role of an SMTP + client after receiving the message) or "gateway" (that is, it may + transport the message further using some protocol other than SMTP). + SMTP commands are generated by the SMTP client and sent to the SMTP + server. SMTP replies are sent from the SMTP server to the SMTP + client in response to the commands. + + In other words, message transfer can occur in a single connection + between the original SMTP-sender and the final SMTP-recipient, or can + occur in a series of hops through intermediary systems. In either + case, a formal handoff of responsibility for the message occurs: the + protocol requires that a server accept responsibility for either + delivering a message or properly reporting the failure to do so. + + Once the transmission channel is established and initial handshaking + completed, the SMTP client normally initiates a mail transaction. + Such a transaction consists of a series of commands to specify the + originator and destination of the mail and transmission of the + message content (including any headers or other structure) itself. + When the same message is sent to multiple recipients, this protocol + encourages the transmission of only one copy of the data for all + recipients at the same destination (or intermediate relay) host. + + The server responds to each command with a reply; replies may + indicate that the command was accepted, that additional commands are + expected, or that a temporary or permanent error condition exists. + Commands specifying the sender or recipients may include server- + permitted SMTP service extension requests as discussed in section + 2.2. The dialog is purposely lock-step, one-at-a-time, although this + can be modified by mutually-agreed extension requests such as command + pipelining [13]. + + + + + +Klensin Standards Track [Page 6] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + Once a given mail message has been transmitted, the client may either + request that the connection be shut down or may initiate other mail + transactions. In addition, an SMTP client may use a connection to an + SMTP server for ancillary services such as verification of email + addresses or retrieval of mailing list subscriber addresses. + + As suggested above, this protocol provides mechanisms for the + transmission of mail. This transmission normally occurs directly + from the sending user's host to the receiving user's host when the + two hosts are connected to the same transport service. When they are + not connected to the same transport service, transmission occurs via + one or more relay SMTP servers. An intermediate host that acts as + either an SMTP relay or as a gateway into some other transmission + environment is usually selected through the use of the domain name + service (DNS) Mail eXchanger mechanism. + + Usually, intermediate hosts are determined via the DNS MX record, not + by explicit "source" routing (see section 5 and appendices C and + F.2). + +2.2 The Extension Model + +2.2.1 Background + + In an effort that started in 1990, approximately a decade after RFC + 821 was completed, the protocol was modified with a "service + extensions" model that permits the client and server to agree to + utilize shared functionality beyond the original SMTP requirements. + The SMTP extension mechanism defines a means whereby an extended SMTP + client and server may recognize each other, and the server can inform + the client as to the service extensions that it supports. + + Contemporary SMTP implementations MUST support the basic extension + mechanisms. For instance, servers MUST support the EHLO command even + if they do not implement any specific extensions and clients SHOULD + preferentially utilize EHLO rather than HELO. (However, for + compatibility with older conforming implementations, SMTP clients and + servers MUST support the original HELO mechanisms as a fallback.) + Unless the different characteristics of HELO must be identified for + interoperability purposes, this document discusses only EHLO. + + SMTP is widely deployed and high-quality implementations have proven + to be very robust. However, the Internet community now considers + some services to be important that were not anticipated when the + protocol was first designed. If support for those services is to be + added, it must be done in a way that permits older implementations to + continue working acceptably. The extension framework consists of: + + + + +Klensin Standards Track [Page 7] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + - The SMTP command EHLO, superseding the earlier HELO, + + - a registry of SMTP service extensions, + + - additional parameters to the SMTP MAIL and RCPT commands, and + + - optional replacements for commands defined in this protocol, such + as for DATA in non-ASCII transmissions [33]. + + SMTP's strength comes primarily from its simplicity. Experience with + many protocols has shown that protocols with few options tend towards + ubiquity, whereas protocols with many options tend towards obscurity. + + Each and every extension, regardless of its benefits, must be + carefully scrutinized with respect to its implementation, deployment, + and interoperability costs. In many cases, the cost of extending the + SMTP service will likely outweigh the benefit. + +2.2.2 Definition and Registration of Extensions + + The IANA maintains a registry of SMTP service extensions. A + corresponding EHLO keyword value is associated with each extension. + Each service extension registered with the IANA must be defined in a + formal standards-track or IESG-approved experimental protocol + document. The definition must include: + + - the textual name of the SMTP service extension; + + - the EHLO keyword value associated with the extension; + + - the syntax and possible values of parameters associated with the + EHLO keyword value; + + - any additional SMTP verbs associated with the extension + (additional verbs will usually be, but are not required to be, the + same as the EHLO keyword value); + + - any new parameters the extension associates with the MAIL or RCPT + verbs; + + - a description of how support for the extension affects the + behavior of a server and client SMTP; and, + + - the increment by which the extension is increasing the maximum + length of the commands MAIL and/or RCPT, over that specified in + this standard. + + + + + +Klensin Standards Track [Page 8] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + In addition, any EHLO keyword value starting with an upper or lower + case "X" refers to a local SMTP service extension used exclusively + through bilateral agreement. Keywords beginning with "X" MUST NOT be + used in a registered service extension. Conversely, keyword values + presented in the EHLO response that do not begin with "X" MUST + correspond to a standard, standards-track, or IESG-approved + experimental SMTP service extension registered with IANA. A + conforming server MUST NOT offer non-"X"-prefixed keyword values that + are not described in a registered extension. + + Additional verbs and parameter names are bound by the same rules as + EHLO keywords; specifically, verbs beginning with "X" are local + extensions that may not be registered or standardized. Conversely, + verbs not beginning with "X" must always be registered. + +2.3 Terminology + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described below. + + 1. MUST This word, or the terms "REQUIRED" or "SHALL", mean that + the definition is an absolute requirement of the specification. + + 2. MUST NOT This phrase, or the phrase "SHALL NOT", mean that the + definition is an absolute prohibition of the specification. + + 3. SHOULD This word, or the adjective "RECOMMENDED", mean that + there may exist valid reasons in particular circumstances to + ignore a particular item, but the full implications must be + understood and carefully weighed before choosing a different + course. + + 4. SHOULD NOT This phrase, or the phrase "NOT RECOMMENDED" mean + that there may exist valid reasons in particular circumstances + when the particular behavior is acceptable or even useful, but the + full implications should be understood and the case carefully + weighed before implementing any behavior described with this + label. + + 5. MAY This word, or the adjective "OPTIONAL", mean that an item is + truly optional. One vendor may choose to include the item because + a particular marketplace requires it or because the vendor feels + that it enhances the product while another vendor may omit the + same item. An implementation which does not include a particular + option MUST be prepared to interoperate with another + implementation which does include the option, though perhaps with + reduced functionality. In the same vein an implementation which + + + +Klensin Standards Track [Page 9] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + does include a particular option MUST be prepared to interoperate + with another implementation which does not include the option + (except, of course, for the feature the option provides.) + +2.3.1 Mail Objects + + SMTP transports a mail object. A mail object contains an envelope + and content. + + The SMTP envelope is sent as a series of SMTP protocol units + (described in section 3). It consists of an originator address (to + which error reports should be directed); one or more recipient + addresses; and optional protocol extension material. Historically, + variations on the recipient address specification command (RCPT TO) + could be used to specify alternate delivery modes, such as immediate + display; those variations have now been deprecated (see appendix F, + section F.6). + + The SMTP content is sent in the SMTP DATA protocol unit and has two + parts: the headers and the body. If the content conforms to other + contemporary standards, the headers form a collection of field/value + pairs structured as in the message format specification [32]; the + body, if structured, is defined according to MIME [12]. The content + is textual in nature, expressed using the US-ASCII repertoire [1]. + Although SMTP extensions (such as "8BITMIME" [20]) may relax this + restriction for the content body, the content headers are always + encoded using the US-ASCII repertoire. A MIME extension [23] defines + an algorithm for representing header values outside the US-ASCII + repertoire, while still encoding them using the US-ASCII repertoire. + +2.3.2 Senders and Receivers + + In RFC 821, the two hosts participating in an SMTP transaction were + described as the "SMTP-sender" and "SMTP-receiver". This document + has been changed to reflect current industry terminology and hence + refers to them as the "SMTP client" (or sometimes just "the client") + and "SMTP server" (or just "the server"), respectively. Since a + given host may act both as server and client in a relay situation, + "receiver" and "sender" terminology is still used where needed for + clarity. + +2.3.3 Mail Agents and Message Stores + + Additional mail system terminology became common after RFC 821 was + published and, where convenient, is used in this specification. In + particular, SMTP servers and clients provide a mail transport service + and therefore act as "Mail Transfer Agents" (MTAs). "Mail User + Agents" (MUAs or UAs) are normally thought of as the sources and + + + +Klensin Standards Track [Page 10] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + targets of mail. At the source, an MUA might collect mail to be + transmitted from a user and hand it off to an MTA; the final + ("delivery") MTA would be thought of as handing the mail off to an + MUA (or at least transferring responsibility to it, e.g., by + depositing the message in a "message store"). However, while these + terms are used with at least the appearance of great precision in + other environments, the implied boundaries between MUAs and MTAs + often do not accurately match common, and conforming, practices with + Internet mail. Hence, the reader should be cautious about inferring + the strong relationships and responsibilities that might be implied + if these terms were used elsewhere. + +2.3.4 Host + + For the purposes of this specification, a host is a computer system + attached to the Internet (or, in some cases, to a private TCP/IP + network) and supporting the SMTP protocol. Hosts are known by names + (see "domain"); identifying them by numerical address is discouraged. + +2.3.5 Domain + + A domain (or domain name) consists of one or more dot-separated + components. These components ("labels" in DNS terminology [22]) are + restricted for SMTP purposes to consist of a sequence of letters, + digits, and hyphens drawn from the ASCII character set [1]. Domain + names are used as names of hosts and of other entities in the domain + name hierarchy. For example, a domain may refer to an alias (label + of a CNAME RR) or the label of Mail eXchanger records to be used to + deliver mail instead of representing a host name. See [22] and + section 5 of this specification. + + The domain name, as described in this document and in [22], is the + entire, fully-qualified name (often referred to as an "FQDN"). A + domain name that is not in FQDN form is no more than a local alias. + Local aliases MUST NOT appear in any SMTP transaction. + +2.3.6 Buffer and State Table + + SMTP sessions are stateful, with both parties carefully maintaining a + common view of the current state. In this document we model this + state by a virtual "buffer" and a "state table" on the server which + may be used by the client to, for example, "clear the buffer" or + "reset the state table," causing the information in the buffer to be + discarded and the state to be returned to some previous state. + + + + + + + +Klensin Standards Track [Page 11] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +2.3.7 Lines + + SMTP commands and, unless altered by a service extension, message + data, are transmitted in "lines". Lines consist of zero or more data + characters terminated by the sequence ASCII character "CR" (hex value + 0D) followed immediately by ASCII character "LF" (hex value 0A). + This termination sequence is denoted as in this document. + Conforming implementations MUST NOT recognize or generate any other + character or character sequence as a line terminator. Limits MAY be + imposed on line lengths by servers (see section 4.5.3). + + In addition, the appearance of "bare" "CR" or "LF" characters in text + (i.e., either without the other) has a long history of causing + problems in mail implementations and applications that use the mail + system as a tool. SMTP client implementations MUST NOT transmit + these characters except when they are intended as line terminators + and then MUST, as indicated above, transmit them only as a + sequence. + +2.3.8 Originator, Delivery, Relay, and Gateway Systems + + This specification makes a distinction among four types of SMTP + systems, based on the role those systems play in transmitting + electronic mail. An "originating" system (sometimes called an SMTP + originator) introduces mail into the Internet or, more generally, + into a transport service environment. A "delivery" SMTP system is + one that receives mail from a transport service environment and + passes it to a mail user agent or deposits it in a message store + which a mail user agent is expected to subsequently access. A + "relay" SMTP system (usually referred to just as a "relay") receives + mail from an SMTP client and transmits it, without modification to + the message data other than adding trace information, to another SMTP + server for further relaying or for delivery. + + A "gateway" SMTP system (usually referred to just as a "gateway") + receives mail from a client system in one transport environment and + transmits it to a server system in another transport environment. + Differences in protocols or message semantics between the transport + environments on either side of a gateway may require that the gateway + system perform transformations to the message that are not permitted + to SMTP relay systems. For the purposes of this specification, + firewalls that rewrite addresses should be considered as gateways, + even if SMTP is used on both sides of them (see [11]). + + + + + + + + +Klensin Standards Track [Page 12] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +2.3.9 Message Content and Mail Data + + The terms "message content" and "mail data" are used interchangeably + in this document to describe the material transmitted after the DATA + command is accepted and before the end of data indication is + transmitted. Message content includes message headers and the + possibly-structured message body. The MIME specification [12] + provides the standard mechanisms for structured message bodies. + +2.3.10 Mailbox and Address + + As used in this specification, an "address" is a character string + that identifies a user to whom mail will be sent or a location into + which mail will be deposited. The term "mailbox" refers to that + depository. The two terms are typically used interchangeably unless + the distinction between the location in which mail is placed (the + mailbox) and a reference to it (the address) is important. An + address normally consists of user and domain specifications. The + standard mailbox naming convention is defined to be "local- + part@domain": contemporary usage permits a much broader set of + applications than simple "user names". Consequently, and due to a + long history of problems when intermediate hosts have attempted to + optimize transport by modifying them, the local-part MUST be + interpreted and assigned semantics only by the host specified in the + domain part of the address. + +2.3.11 Reply + + An SMTP reply is an acknowledgment (positive or negative) sent from + receiver to sender via the transmission channel in response to a + command. The general form of a reply is a numeric completion code + (indicating failure or success) usually followed by a text string. + The codes are for use by programs and the text is usually intended + for human users. Recent work [34] has specified further structuring + of the reply strings, including the use of supplemental and more + specific completion codes. + +2.4 General Syntax Principles and Transaction Model + + SMTP commands and replies have a rigid syntax. All commands begin + with a command verb. All Replies begin with a three digit numeric + code. In some commands and replies, arguments MUST follow the verb + or reply code. Some commands do not accept arguments (after the + verb), and some reply codes are followed, sometimes optionally, by + free form text. In both cases, where text appears, it is separated + from the verb or reply code by a space character. Complete + definitions of commands and replies appear in section 4. + + + + +Klensin Standards Track [Page 13] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + Verbs and argument values (e.g., "TO:" or "to:" in the RCPT command + and extension name keywords) are not case sensitive, with the sole + exception in this specification of a mailbox local-part (SMTP + Extensions may explicitly specify case-sensitive elements). That is, + a command verb, an argument value other than a mailbox local-part, + and free form text MAY be encoded in upper case, lower case, or any + mixture of upper and lower case with no impact on its meaning. This + is NOT true of a mailbox local-part. The local-part of a mailbox + MUST BE treated as case sensitive. Therefore, SMTP implementations + MUST take care to preserve the case of mailbox local-parts. Mailbox + domains are not case sensitive. In particular, for some hosts the + user "smith" is different from the user "Smith". However, exploiting + the case sensitivity of mailbox local-parts impedes interoperability + and is discouraged. + + A few SMTP servers, in violation of this specification (and RFC 821) + require that command verbs be encoded by clients in upper case. + Implementations MAY wish to employ this encoding to accommodate those + servers. + + The argument field consists of a variable length character string + ending with the end of the line, i.e., with the character sequence + . The receiver will take no action until this sequence is + received. + + The syntax for each command is shown with the discussion of that + command. Common elements and parameters are shown in section 4.1.2. + + Commands and replies are composed of characters from the ASCII + character set [1]. When the transport service provides an 8-bit byte + (octet) transmission channel, each 7-bit character is transmitted + right justified in an octet with the high order bit cleared to zero. + More specifically, the unextended SMTP service provides seven bit + transport only. An originating SMTP client which has not + successfully negotiated an appropriate extension with a particular + server MUST NOT transmit messages with information in the high-order + bit of octets. If such messages are transmitted in violation of this + rule, receiving SMTP servers MAY clear the high-order bit or reject + the message as invalid. In general, a relay SMTP SHOULD assume that + the message content it has received is valid and, assuming that the + envelope permits doing so, relay it without inspecting that content. + Of course, if the content is mislabeled and the data path cannot + accept the actual content, this may result in ultimate delivery of a + severely garbled message to the recipient. Delivery SMTP systems MAY + reject ("bounce") such messages rather than deliver them. No sending + SMTP system is permitted to send envelope commands in any character + + + + + +Klensin Standards Track [Page 14] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + set other than US-ASCII; receiving systems SHOULD reject such + commands, normally using "500 syntax error - invalid character" + replies. + + Eight-bit message content transmission MAY be requested of the server + by a client using extended SMTP facilities, notably the "8BITMIME" + extension [20]. 8BITMIME SHOULD be supported by SMTP servers. + However, it MUST not be construed as authorization to transmit + unrestricted eight bit material. 8BITMIME MUST NOT be requested by + senders for material with the high bit on that is not in MIME format + with an appropriate content-transfer encoding; servers MAY reject + such messages. + + The metalinguistic notation used in this document corresponds to the + "Augmented BNF" used in other Internet mail system documents. The + reader who is not familiar with that syntax should consult the ABNF + specification [8]. Metalanguage terms used in running text are + surrounded by pointed brackets (e.g., ) for clarity. + +3. The SMTP Procedures: An Overview + + This section contains descriptions of the procedures used in SMTP: + session initiation, the mail transaction, forwarding mail, verifying + mailbox names and expanding mailing lists, and the opening and + closing exchanges. Comments on relaying, a note on mail domains, and + a discussion of changing roles are included at the end of this + section. Several complete scenarios are presented in appendix D. + +3.1 Session Initiation + + An SMTP session is initiated when a client opens a connection to a + server and the server responds with an opening message. + + SMTP server implementations MAY include identification of their + software and version information in the connection greeting reply + after the 220 code, a practice that permits more efficient isolation + and repair of any problems. Implementations MAY make provision for + SMTP servers to disable the software and version announcement where + it causes security concerns. While some systems also identify their + contact point for mail problems, this is not a substitute for + maintaining the required "postmaster" address (see section 4.5.1). + + The SMTP protocol allows a server to formally reject a transaction + while still allowing the initial connection as follows: a 554 + response MAY be given in the initial connection opening message + instead of the 220. A server taking this approach MUST still wait + for the client to send a QUIT (see section 4.1.1.10) before closing + the connection and SHOULD respond to any intervening commands with + + + +Klensin Standards Track [Page 15] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + "503 bad sequence of commands". Since an attempt to make an SMTP + connection to such a system is probably in error, a server returning + a 554 response on connection opening SHOULD provide enough + information in the reply text to facilitate debugging of the sending + system. + +3.2 Client Initiation + + Once the server has sent the welcoming message and the client has + received it, the client normally sends the EHLO command to the + server, indicating the client's identity. In addition to opening the + session, use of EHLO indicates that the client is able to process + service extensions and requests that the server provide a list of the + extensions it supports. Older SMTP systems which are unable to + support service extensions and contemporary clients which do not + require service extensions in the mail session being initiated, MAY + use HELO instead of EHLO. Servers MUST NOT return the extended + EHLO-style response to a HELO command. For a particular connection + attempt, if the server returns a "command not recognized" response to + EHLO, the client SHOULD be able to fall back and send HELO. + + In the EHLO command the host sending the command identifies itself; + the command may be interpreted as saying "Hello, I am " (and, + in the case of EHLO, "and I support service extension requests"). + +3.3 Mail Transactions + + There are three steps to SMTP mail transactions. The transaction + starts with a MAIL command which gives the sender identification. + (In general, the MAIL command may be sent only when no mail + transaction is in progress; see section 4.1.4.) A series of one or + more RCPT commands follows giving the receiver information. Then a + DATA command initiates transfer of the mail data and is terminated by + the "end of mail" data indicator, which also confirms the + transaction. + + The first step in the procedure is the MAIL command. + + MAIL FROM: [SP ] + + This command tells the SMTP-receiver that a new mail transaction is + starting and to reset all its state tables and buffers, including any + recipients or mail data. The portion of the first or + only argument contains the source mailbox (between "<" and ">" + brackets), which can be used to report errors (see section 4.2 for a + discussion of error reporting). If accepted, the SMTP server returns + a 250 OK reply. If the mailbox specification is not acceptable for + some reason, the server MUST return a reply indicating whether the + + + +Klensin Standards Track [Page 16] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + failure is permanent (i.e., will occur again if the client tries to + send the same address again) or temporary (i.e., the address might be + accepted if the client tries again later). Despite the apparent + scope of this requirement, there are circumstances in which the + acceptability of the reverse-path may not be determined until one or + more forward-paths (in RCPT commands) can be examined. In those + cases, the server MAY reasonably accept the reverse-path (with a 250 + reply) and then report problems after the forward-paths are received + and examined. Normally, failures produce 550 or 553 replies. + + Historically, the can contain more than just a + mailbox, however, contemporary systems SHOULD NOT use source routing + (see appendix C). + + The optional are associated with negotiated SMTP + service extensions (see section 2.2). + + The second step in the procedure is the RCPT command. + + RCPT TO: [ SP ] + + The first or only argument to this command includes a forward-path + (normally a mailbox and domain, always surrounded by "<" and ">" + brackets) identifying one recipient. If accepted, the SMTP server + returns a 250 OK reply and stores the forward-path. If the recipient + is known not to be a deliverable address, the SMTP server returns a + 550 reply, typically with a string such as "no such user - " and the + mailbox name (other circumstances and reply codes are possible). + This step of the procedure can be repeated any number of times. + + The can contain more than just a mailbox. + Historically, the can be a source routing list of + hosts and the destination mailbox, however, contemporary SMTP clients + SHOULD NOT utilize source routes (see appendix C). Servers MUST be + prepared to encounter a list of source routes in the forward path, + but SHOULD ignore the routes or MAY decline to support the relaying + they imply. Similarly, servers MAY decline to accept mail that is + destined for other hosts or systems. These restrictions make a + server useless as a relay for clients that do not support full SMTP + functionality. Consequently, restricted-capability clients MUST NOT + assume that any SMTP server on the Internet can be used as their mail + processing (relaying) site. If a RCPT command appears without a + previous MAIL command, the server MUST return a 503 "Bad sequence of + commands" response. The optional are associated + with negotiated SMTP service extensions (see section 2.2). + + The third step in the procedure is the DATA command (or some + alternative specified in a service extension). + + + +Klensin Standards Track [Page 17] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + DATA + + If accepted, the SMTP server returns a 354 Intermediate reply and + considers all succeeding lines up to but not including the end of + mail data indicator to be the message text. When the end of text is + successfully received and stored the SMTP-receiver sends a 250 OK + reply. + + Since the mail data is sent on the transmission channel, the end of + mail data must be indicated so that the command and reply dialog can + be resumed. SMTP indicates the end of the mail data by sending a + line containing only a "." (period or full stop). A transparency + procedure is used to prevent this from interfering with the user's + text (see section 4.5.2). + + The end of mail data indicator also confirms the mail transaction and + tells the SMTP server to now process the stored recipients and mail + data. If accepted, the SMTP server returns a 250 OK reply. The DATA + command can fail at only two points in the protocol exchange: + + - If there was no MAIL, or no RCPT, command, or all such commands + were rejected, the server MAY return a "command out of sequence" + (503) or "no valid recipients" (554) reply in response to the DATA + command. If one of those replies (or any other 5yz reply) is + received, the client MUST NOT send the message data; more + generally, message data MUST NOT be sent unless a 354 reply is + received. + + - If the verb is initially accepted and the 354 reply issued, the + DATA command should fail only if the mail transaction was + incomplete (for example, no recipients), or if resources were + unavailable (including, of course, the server unexpectedly + becoming unavailable), or if the server determines that the + message should be rejected for policy or other reasons. + + However, in practice, some servers do not perform recipient + verification until after the message text is received. These servers + SHOULD treat a failure for one or more recipients as a "subsequent + failure" and return a mail message as discussed in section 6. Using + a "550 mailbox not found" (or equivalent) reply code after the data + are accepted makes it difficult or impossible for the client to + determine which recipients failed. + + When RFC 822 format [7, 32] is being used, the mail data include the + memo header items such as Date, Subject, To, Cc, From. Server SMTP + systems SHOULD NOT reject messages based on perceived defects in the + RFC 822 or MIME [12] message header or message body. In particular, + + + + +Klensin Standards Track [Page 18] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + they MUST NOT reject messages in which the numbers of Resent-fields + do not match or Resent-to appears without Resent-from and/or Resent- + date. + + Mail transaction commands MUST be used in the order discussed above. + +3.4 Forwarding for Address Correction or Updating + + Forwarding support is most often required to consolidate and simplify + addresses within, or relative to, some enterprise and less frequently + to establish addresses to link a person's prior address with current + one. Silent forwarding of messages (without server notification to + the sender), for security or non-disclosure purposes, is common in + the contemporary Internet. + + In both the enterprise and the "new address" cases, information + hiding (and sometimes security) considerations argue against exposure + of the "final" address through the SMTP protocol as a side-effect of + the forwarding activity. This may be especially important when the + final address may not even be reachable by the sender. Consequently, + the "forwarding" mechanisms described in section 3.2 of RFC 821, and + especially the 251 (corrected destination) and 551 reply codes from + RCPT must be evaluated carefully by implementers and, when they are + available, by those configuring systems. + + In particular: + + * Servers MAY forward messages when they are aware of an address + change. When they do so, they MAY either provide address-updating + information with a 251 code, or may forward "silently" and return + a 250 code. But, if a 251 code is used, they MUST NOT assume that + the client will actually update address information or even return + that information to the user. + + Alternately, + + * Servers MAY reject or bounce messages when they are not + deliverable when addressed. When they do so, they MAY either + provide address-updating information with a 551 code, or may + reject the message as undeliverable with a 550 code and no + address-specific information. But, if a 551 code is used, they + MUST NOT assume that the client will actually update address + information or even return that information to the user. + + SMTP server implementations that support the 251 and/or 551 reply + codes are strongly encouraged to provide configuration mechanisms so + that sites which conclude that they would undesirably disclose + information can disable or restrict their use. + + + +Klensin Standards Track [Page 19] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +3.5 Commands for Debugging Addresses + +3.5.1 Overview + + SMTP provides commands to verify a user name or obtain the content of + a mailing list. This is done with the VRFY and EXPN commands, which + have character string arguments. Implementations SHOULD support VRFY + and EXPN (however, see section 3.5.2 and 7.3). + + For the VRFY command, the string is a user name or a user name and + domain (see below). If a normal (i.e., 250) response is returned, + the response MAY include the full name of the user and MUST include + the mailbox of the user. It MUST be in either of the following + forms: + + User Name + local-part@domain + + When a name that is the argument to VRFY could identify more than one + mailbox, the server MAY either note the ambiguity or identify the + alternatives. In other words, any of the following are legitimate + response to VRFY: + + 553 User ambiguous + + or + + 553- Ambiguous; Possibilities are + 553-Joe Smith + 553-Harry Smith + 553 Melvin Smith + + or + + 553-Ambiguous; Possibilities + 553- + 553- + 553 + + Under normal circumstances, a client receiving a 553 reply would be + expected to expose the result to the user. Use of exactly the forms + given, and the "user ambiguous" or "ambiguous" keywords, possibly + supplemented by extended reply codes such as those described in [34], + will facilitate automated translation into other languages as needed. + Of course, a client that was highly automated or that was operating + in another language than English, might choose to try to translate + the response, to return some other indication to the user than the + + + + +Klensin Standards Track [Page 20] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + literal text of the reply, or to take some automated action such as + consulting a directory service for additional information before + reporting to the user. + + For the EXPN command, the string identifies a mailing list, and the + successful (i.e., 250) multiline response MAY include the full name + of the users and MUST give the mailboxes on the mailing list. + + In some hosts the distinction between a mailing list and an alias for + a single mailbox is a bit fuzzy, since a common data structure may + hold both types of entries, and it is possible to have mailing lists + containing only one mailbox. If a request is made to apply VRFY to a + mailing list, a positive response MAY be given if a message so + addressed would be delivered to everyone on the list, otherwise an + error SHOULD be reported (e.g., "550 That is a mailing list, not a + user" or "252 Unable to verify members of mailing list"). If a + request is made to expand a user name, the server MAY return a + positive response consisting of a list containing one name, or an + error MAY be reported (e.g., "550 That is a user name, not a mailing + list"). + + In the case of a successful multiline reply (normal for EXPN) exactly + one mailbox is to be specified on each line of the reply. The case + of an ambiguous request is discussed above. + + "User name" is a fuzzy term and has been used deliberately. An + implementation of the VRFY or EXPN commands MUST include at least + recognition of local mailboxes as "user names". However, since + current Internet practice often results in a single host handling + mail for multiple domains, hosts, especially hosts that provide this + functionality, SHOULD accept the "local-part@domain" form as a "user + name"; hosts MAY also choose to recognize other strings as "user + names". + + The case of expanding a mailbox list requires a multiline reply, such + as: + + C: EXPN Example-People + S: 250-Jon Postel + S: 250-Fred Fonebone + S: 250 Sam Q. Smith + + or + + C: EXPN Executive-Washroom-List + S: 550 Access Denied to You. + + + + + +Klensin Standards Track [Page 21] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + The character string arguments of the VRFY and EXPN commands cannot + be further restricted due to the variety of implementations of the + user name and mailbox list concepts. On some systems it may be + appropriate for the argument of the EXPN command to be a file name + for a file containing a mailing list, but again there are a variety + of file naming conventions in the Internet. Similarly, historical + variations in what is returned by these commands are such that the + response SHOULD be interpreted very carefully, if at all, and SHOULD + generally only be used for diagnostic purposes. + +3.5.2 VRFY Normal Response + + When normal (2yz or 551) responses are returned from a VRFY or EXPN + request, the reply normally includes the mailbox name, i.e., + "", where "domain" is a fully qualified domain + name, MUST appear in the syntax. In circumstances exceptional enough + to justify violating the intent of this specification, free-form text + MAY be returned. In order to facilitate parsing by both computers + and people, addresses SHOULD appear in pointed brackets. When + addresses, rather than free-form debugging information, are returned, + EXPN and VRFY MUST return only valid domain addresses that are usable + in SMTP RCPT commands. Consequently, if an address implies delivery + to a program or other system, the mailbox name used to reach that + target MUST be given. Paths (explicit source routes) MUST NOT be + returned by VRFY or EXPN. + + Server implementations SHOULD support both VRFY and EXPN. For + security reasons, implementations MAY provide local installations a + way to disable either or both of these commands through configuration + options or the equivalent. When these commands are supported, they + are not required to work across relays when relaying is supported. + Since they were both optional in RFC 821, they MUST be listed as + service extensions in an EHLO response, if they are supported. + +3.5.3 Meaning of VRFY or EXPN Success Response + + A server MUST NOT return a 250 code in response to a VRFY or EXPN + command unless it has actually verified the address. In particular, + a server MUST NOT return 250 if all it has done is to verify that the + syntax given is valid. In that case, 502 (Command not implemented) + or 500 (Syntax error, command unrecognized) SHOULD be returned. As + stated elsewhere, implementation (in the sense of actually validating + addresses and returning information) of VRFY and EXPN are strongly + recommended. Hence, implementations that return 500 or 502 for VRFY + are not in full compliance with this specification. + + + + + + +Klensin Standards Track [Page 22] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + There may be circumstances where an address appears to be valid but + cannot reasonably be verified in real time, particularly when a + server is acting as a mail exchanger for another server or domain. + "Apparent validity" in this case would normally involve at least + syntax checking and might involve verification that any domains + specified were ones to which the host expected to be able to relay + mail. In these situations, reply code 252 SHOULD be returned. These + cases parallel the discussion of RCPT verification discussed in + section 2.1. Similarly, the discussion in section 3.4 applies to the + use of reply codes 251 and 551 with VRFY (and EXPN) to indicate + addresses that are recognized but that would be forwarded or bounced + were mail received for them. Implementations generally SHOULD be + more aggressive about address verification in the case of VRFY than + in the case of RCPT, even if it takes a little longer to do so. + +3.5.4 Semantics and Applications of EXPN + + EXPN is often very useful in debugging and understanding problems + with mailing lists and multiple-target-address aliases. Some systems + have attempted to use source expansion of mailing lists as a means of + eliminating duplicates. The propagation of aliasing systems with + mail on the Internet, for hosts (typically with MX and CNAME DNS + records), for mailboxes (various types of local host aliases), and in + various proxying arrangements, has made it nearly impossible for + these strategies to work consistently, and mail systems SHOULD NOT + attempt them. + +3.6 Domains + + Only resolvable, fully-qualified, domain names (FQDNs) are permitted + when domain names are used in SMTP. In other words, names that can + be resolved to MX RRs or A RRs (as discussed in section 5) are + permitted, as are CNAME RRs whose targets can be resolved, in turn, + to MX or A RRs. Local nicknames or unqualified names MUST NOT be + used. There are two exceptions to the rule requiring FQDNs: + + - The domain name given in the EHLO command MUST BE either a primary + host name (a domain name that resolves to an A RR) or, if the host + has no name, an address literal as described in section 4.1.1.1. + + - The reserved mailbox name "postmaster" may be used in a RCPT + command without domain qualification (see section 4.1.1.3) and + MUST be accepted if so used. + + + + + + + + +Klensin Standards Track [Page 23] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +3.7 Relaying + + In general, the availability of Mail eXchanger records in the domain + name system [22, 27] makes the use of explicit source routes in the + Internet mail system unnecessary. Many historical problems with + their interpretation have made their use undesirable. SMTP clients + SHOULD NOT generate explicit source routes except under unusual + circumstances. SMTP servers MAY decline to act as mail relays or to + accept addresses that specify source routes. When route information + is encountered, SMTP servers are also permitted to ignore the route + information and simply send to the final destination specified as the + last element in the route and SHOULD do so. There has been an + invalid practice of using names that do not appear in the DNS as + destination names, with the senders counting on the intermediate + hosts specified in source routing to resolve any problems. If source + routes are stripped, this practice will cause failures. This is one + of several reasons why SMTP clients MUST NOT generate invalid source + routes or depend on serial resolution of names. + + When source routes are not used, the process described in RFC 821 for + constructing a reverse-path from the forward-path is not applicable + and the reverse-path at the time of delivery will simply be the + address that appeared in the MAIL command. + + A relay SMTP server is usually the target of a DNS MX record that + designates it, rather than the final delivery system. The relay + server may accept or reject the task of relaying the mail in the same + way it accepts or rejects mail for a local user. If it accepts the + task, it then becomes an SMTP client, establishes a transmission + channel to the next SMTP server specified in the DNS (according to + the rules in section 5), and sends it the mail. If it declines to + relay mail to a particular address for policy reasons, a 550 response + SHOULD be returned. + + Many mail-sending clients exist, especially in conjunction with + facilities that receive mail via POP3 or IMAP, that have limited + capability to support some of the requirements of this specification, + such as the ability to queue messages for subsequent delivery + attempts. For these clients, it is common practice to make private + arrangements to send all messages to a single server for processing + and subsequent distribution. SMTP, as specified here, is not ideally + suited for this role, and work is underway on standardized mail + submission protocols that might eventually supercede the current + practices. In any event, because these arrangements are private and + fall outside the scope of this specification, they are not described + here. + + + + + +Klensin Standards Track [Page 24] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + It is important to note that MX records can point to SMTP servers + which act as gateways into other environments, not just SMTP relays + and final delivery systems; see sections 3.8 and 5. + + If an SMTP server has accepted the task of relaying the mail and + later finds that the destination is incorrect or that the mail cannot + be delivered for some other reason, then it MUST construct an + "undeliverable mail" notification message and send it to the + originator of the undeliverable mail (as indicated by the reverse- + path). Formats specified for non-delivery reports by other standards + (see, for example, [24, 25]) SHOULD be used if possible. + + This notification message must be from the SMTP server at the relay + host or the host that first determines that delivery cannot be + accomplished. Of course, SMTP servers MUST NOT send notification + messages about problems transporting notification messages. One way + to prevent loops in error reporting is to specify a null reverse-path + in the MAIL command of a notification message. When such a message + is transmitted the reverse-path MUST be set to null (see section + 4.5.5 for additional discussion). A MAIL command with a null + reverse-path appears as follows: + + MAIL FROM:<> + + As discussed in section 2.4.1, a relay SMTP has no need to inspect or + act upon the headers or body of the message data and MUST NOT do so + except to add its own "Received:" header (section 4.4) and, + optionally, to attempt to detect looping in the mail system (see + section 6.2). + +3.8 Mail Gatewaying + + While the relay function discussed above operates within the Internet + SMTP transport service environment, MX records or various forms of + explicit routing may require that an intermediate SMTP server perform + a translation function between one transport service and another. As + discussed in section 2.3.8, when such a system is at the boundary + between two transport service environments, we refer to it as a + "gateway" or "gateway SMTP". + + Gatewaying mail between different mail environments, such as + different mail formats and protocols, is complex and does not easily + yield to standardization. However, some general requirements may be + given for a gateway between the Internet and another mail + environment. + + + + + + +Klensin Standards Track [Page 25] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +3.8.1 Header Fields in Gatewaying + + Header fields MAY be rewritten when necessary as messages are + gatewayed across mail environment boundaries. This may involve + inspecting the message body or interpreting the local-part of the + destination address in spite of the prohibitions in section 2.4.1. + + Other mail systems gatewayed to the Internet often use a subset of + RFC 822 headers or provide similar functionality with a different + syntax, but some of these mail systems do not have an equivalent to + the SMTP envelope. Therefore, when a message leaves the Internet + environment, it may be necessary to fold the SMTP envelope + information into the message header. A possible solution would be to + create new header fields to carry the envelope information (e.g., + "X-SMTP-MAIL:" and "X-SMTP-RCPT:"); however, this would require + changes in mail programs in foreign environments and might risk + disclosure of private information (see section 7.2). + +3.8.2 Received Lines in Gatewaying + + When forwarding a message into or out of the Internet environment, a + gateway MUST prepend a Received: line, but it MUST NOT alter in any + way a Received: line that is already in the header. + + "Received:" fields of messages originating from other environments + may not conform exactly to this specification. However, the most + important use of Received: lines is for debugging mail faults, and + this debugging can be severely hampered by well-meaning gateways that + try to "fix" a Received: line. As another consequence of trace + fields arising in non-SMTP environments, receiving systems MUST NOT + reject mail based on the format of a trace field and SHOULD be + extremely robust in the light of unexpected information or formats in + those fields. + + The gateway SHOULD indicate the environment and protocol in the "via" + clauses of Received field(s) that it supplies. + +3.8.3 Addresses in Gatewaying + + From the Internet side, the gateway SHOULD accept all valid address + formats in SMTP commands and in RFC 822 headers, and all valid RFC + 822 messages. Addresses and headers generated by gateways MUST + conform to applicable Internet standards (including this one and RFC + 822). Gateways are, of course, subject to the same rules for + handling source routes as those described for other SMTP systems in + section 3.3. + + + + + +Klensin Standards Track [Page 26] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +3.8.4 Other Header Fields in Gatewaying + + The gateway MUST ensure that all header fields of a message that it + forwards into the Internet mail environment meet the requirements for + Internet mail. In particular, all addresses in "From:", "To:", + "Cc:", etc., fields MUST be transformed (if necessary) to satisfy RFC + 822 syntax, MUST reference only fully-qualified domain names, and + MUST be effective and useful for sending replies. The translation + algorithm used to convert mail from the Internet protocols to another + environment's protocol SHOULD ensure that error messages from the + foreign mail environment are delivered to the return path from the + SMTP envelope, not to the sender listed in the "From:" field (or + other fields) of the RFC 822 message. + +3.8.5 Envelopes in Gatewaying + + Similarly, when forwarding a message from another environment into + the Internet, the gateway SHOULD set the envelope return path in + accordance with an error message return address, if supplied by the + foreign environment. If the foreign environment has no equivalent + concept, the gateway must select and use a best approximation, with + the message originator's address as the default of last resort. + +3.9 Terminating Sessions and Connections + + An SMTP connection is terminated when the client sends a QUIT + command. The server responds with a positive reply code, after which + it closes the connection. + + An SMTP server MUST NOT intentionally close the connection except: + + - After receiving a QUIT command and responding with a 221 reply. + + - After detecting the need to shut down the SMTP service and + returning a 421 response code. This response code can be issued + after the server receives any command or, if necessary, + asynchronously from command receipt (on the assumption that the + client will receive it after the next command is issued). + + In particular, a server that closes connections in response to + commands that are not understood is in violation of this + specification. Servers are expected to be tolerant of unknown + commands, issuing a 500 reply and awaiting further instructions from + the client. + + + + + + + +Klensin Standards Track [Page 27] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + An SMTP server which is forcibly shut down via external means SHOULD + attempt to send a line containing a 421 response code to the SMTP + client before exiting. The SMTP client will normally read the 421 + response code after sending its next command. + + SMTP clients that experience a connection close, reset, or other + communications failure due to circumstances not under their control + (in violation of the intent of this specification but sometimes + unavoidable) SHOULD, to maintain the robustness of the mail system, + treat the mail transaction as if a 451 response had been received and + act accordingly. + +3.10 Mailing Lists and Aliases + + An SMTP-capable host SHOULD support both the alias and the list + models of address expansion for multiple delivery. When a message is + delivered or forwarded to each address of an expanded list form, the + return address in the envelope ("MAIL FROM:") MUST be changed to be + the address of a person or other entity who administers the list. + However, in this case, the message header [32] MUST be left + unchanged; in particular, the "From" field of the message header is + unaffected. + + An important mail facility is a mechanism for multi-destination + delivery of a single message, by transforming (or "expanding" or + "exploding") a pseudo-mailbox address into a list of destination + mailbox addresses. When a message is sent to such a pseudo-mailbox + (sometimes called an "exploder"), copies are forwarded or + redistributed to each mailbox in the expanded list. Servers SHOULD + simply utilize the addresses on the list; application of heuristics + or other matching rules to eliminate some addresses, such as that of + the originator, is strongly discouraged. We classify such a pseudo- + mailbox as an "alias" or a "list", depending upon the expansion + rules. + +3.10.1 Alias + + To expand an alias, the recipient mailer simply replaces the pseudo- + mailbox address in the envelope with each of the expanded addresses + in turn; the rest of the envelope and the message body are left + unchanged. The message is then delivered or forwarded to each + expanded address. + +3.10.2 List + + A mailing list may be said to operate by "redistribution" rather than + by "forwarding". To expand a list, the recipient mailer replaces the + pseudo-mailbox address in the envelope with all of the expanded + + + +Klensin Standards Track [Page 28] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + addresses. The return address in the envelope is changed so that all + error messages generated by the final deliveries will be returned to + a list administrator, not to the message originator, who generally + has no control over the contents of the list and will typically find + error messages annoying. + +4. The SMTP Specifications + +4.1 SMTP Commands + +4.1.1 Command Semantics and Syntax + + The SMTP commands define the mail transfer or the mail system + function requested by the user. SMTP commands are character strings + terminated by . The commands themselves are alphabetic + characters terminated by if parameters follow and + otherwise. (In the interest of improved interoperability, SMTP + receivers are encouraged to tolerate trailing white space before the + terminating .) The syntax of the local part of a mailbox must + conform to receiver site conventions and the syntax specified in + section 4.1.2. The SMTP commands are discussed below. The SMTP + replies are discussed in section 4.2. + + A mail transaction involves several data objects which are + communicated as arguments to different commands. The reverse-path is + the argument of the MAIL command, the forward-path is the argument of + the RCPT command, and the mail data is the argument of the DATA + command. These arguments or data objects must be transmitted and + held pending the confirmation communicated by the end of mail data + indication which finalizes the transaction. The model for this is + that distinct buffers are provided to hold the types of data objects, + that is, there is a reverse-path buffer, a forward-path buffer, and a + mail data buffer. Specific commands cause information to be appended + to a specific buffer, or cause one or more buffers to be cleared. + + Several commands (RSET, DATA, QUIT) are specified as not permitting + parameters. In the absence of specific extensions offered by the + server and accepted by the client, clients MUST NOT send such + parameters and servers SHOULD reject commands containing them as + having invalid syntax. + +4.1.1.1 Extended HELLO (EHLO) or HELLO (HELO) + + These commands are used to identify the SMTP client to the SMTP + server. The argument field contains the fully-qualified domain name + of the SMTP client if one is available. In situations in which the + SMTP client system does not have a meaningful domain name (e.g., when + its address is dynamically allocated and no reverse mapping record is + + + +Klensin Standards Track [Page 29] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + available), the client SHOULD send an address literal (see section + 4.1.3), optionally followed by information that will help to identify + the client system. y The SMTP server identifies itself to the SMTP + client in the connection greeting reply and in the response to this + command. + + A client SMTP SHOULD start an SMTP session by issuing the EHLO + command. If the SMTP server supports the SMTP service extensions it + will give a successful response, a failure response, or an error + response. If the SMTP server, in violation of this specification, + does not support any SMTP service extensions it will generate an + error response. Older client SMTP systems MAY, as discussed above, + use HELO (as specified in RFC 821) instead of EHLO, and servers MUST + support the HELO command and reply properly to it. In any event, a + client MUST issue HELO or EHLO before starting a mail transaction. + + These commands, and a "250 OK" reply to one of them, confirm that + both the SMTP client and the SMTP server are in the initial state, + that is, there is no transaction in progress and all state tables and + buffers are cleared. + + Syntax: + + ehlo = "EHLO" SP Domain CRLF + helo = "HELO" SP Domain CRLF + + Normally, the response to EHLO will be a multiline reply. Each line + of the response contains a keyword and, optionally, one or more + parameters. Following the normal syntax for multiline replies, these + keyworks follow the code (250) and a hyphen for all but the last + line, and the code and a space for the last line. The syntax for a + positive response, using the ABNF notation and terminal symbols of + [8], is: + + ehlo-ok-rsp = ( "250" domain [ SP ehlo-greet ] CRLF ) + / ( "250-" domain [ SP ehlo-greet ] CRLF + *( "250-" ehlo-line CRLF ) + "250" SP ehlo-line CRLF ) + + ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127) + ; string of any characters other than CR or LF + + ehlo-line = ehlo-keyword *( SP ehlo-param ) + + ehlo-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-") + ; additional syntax of ehlo-params depends on + ; ehlo-keyword + + + + +Klensin Standards Track [Page 30] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + ehlo-param = 1*(%d33-127) + ; any CHAR excluding and all + ; control characters (US-ASCII 0-31 inclusive) + + Although EHLO keywords may be specified in upper, lower, or mixed + case, they MUST always be recognized and processed in a case- + insensitive manner. This is simply an extension of practices + specified in RFC 821 and section 2.4.1. + +4.1.1.2 MAIL (MAIL) + + This command is used to initiate a mail transaction in which the mail + data is delivered to an SMTP server which may, in turn, deliver it to + one or more mailboxes or pass it on to another system (possibly using + SMTP). The argument field contains a reverse-path and may contain + optional parameters. In general, the MAIL command may be sent only + when no mail transaction is in progress, see section 4.1.4. + + The reverse-path consists of the sender mailbox. Historically, that + mailbox might optionally have been preceded by a list of hosts, but + that behavior is now deprecated (see appendix C). In some types of + reporting messages for which a reply is likely to cause a mail loop + (for example, mail delivery and nondelivery notifications), the + reverse-path may be null (see section 3.7). + + This command clears the reverse-path buffer, the forward-path buffer, + and the mail data buffer; and inserts the reverse-path information + from this command into the reverse-path buffer. + + If service extensions were negotiated, the MAIL command may also + carry parameters associated with a particular service extension. + + Syntax: + + "MAIL FROM:" ("<>" / Reverse-Path) + [SP Mail-parameters] CRLF + +4.1.1.3 RECIPIENT (RCPT) + + This command is used to identify an individual recipient of the mail + data; multiple recipients are specified by multiple use of this + command. The argument field contains a forward-path and may contain + optional parameters. + + The forward-path normally consists of the required destination + mailbox. Sending systems SHOULD not generate the optional list of + hosts known as a source route. Receiving systems MUST recognize + + + + +Klensin Standards Track [Page 31] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + source route syntax but SHOULD strip off the source route + specification and utilize the domain name associated with the mailbox + as if the source route had not been provided. + + Similarly, relay hosts SHOULD strip or ignore source routes, and + names MUST NOT be copied into the reverse-path. When mail reaches + its ultimate destination (the forward-path contains only a + destination mailbox), the SMTP server inserts it into the destination + mailbox in accordance with its host mail conventions. + + For example, mail received at relay host xyz.com with envelope + commands + + MAIL FROM: + RCPT TO:<@hosta.int,@jkl.org:userc@d.bar.org> + + will normally be sent directly on to host d.bar.org with envelope + commands + + MAIL FROM: + RCPT TO: + + As provided in appendix C, xyz.com MAY also choose to relay the + message to hosta.int, using the envelope commands + + MAIL FROM: + RCPT TO:<@hosta.int,@jkl.org:userc@d.bar.org> + + or to jkl.org, using the envelope commands + + MAIL FROM: + RCPT TO:<@jkl.org:userc@d.bar.org> + + Of course, since hosts are not required to relay mail at all, xyz.com + may also reject the message entirely when the RCPT command is + received, using a 550 code (since this is a "policy reason"). + + If service extensions were negotiated, the RCPT command may also + carry parameters associated with a particular service extension + offered by the server. The client MUST NOT transmit parameters other + than those associated with a service extension offered by the server + in its EHLO response. + +Syntax: + "RCPT TO:" ("" / "" / Forward-Path) + [SP Rcpt-parameters] CRLF + + + + + +Klensin Standards Track [Page 32] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +4.1.1.4 DATA (DATA) + + The receiver normally sends a 354 response to DATA, and then treats + the lines (strings ending in sequences, as described in + section 2.3.7) following the command as mail data from the sender. + This command causes the mail data to be appended to the mail data + buffer. The mail data may contain any of the 128 ASCII character + codes, although experience has indicated that use of control + characters other than SP, HT, CR, and LF may cause problems and + SHOULD be avoided when possible. + + The mail data is terminated by a line containing only a period, that + is, the character sequence "." (see section 4.5.2). This + is the end of mail data indication. Note that the first of + this terminating sequence is also the that ends the final line + of the data (message text) or, if there was no data, ends the DATA + command itself. An extra MUST NOT be added, as that would + cause an empty line to be added to the message. The only exception + to this rule would arise if the message body were passed to the + originating SMTP-sender with a final "line" that did not end in + ; in that case, the originating SMTP system MUST either reject + the message as invalid or add in order to have the receiving + SMTP server recognize the "end of data" condition. + + The custom of accepting lines ending only in , as a concession to + non-conforming behavior on the part of some UNIX systems, has proven + to cause more interoperability problems than it solves, and SMTP + server systems MUST NOT do this, even in the name of improved + robustness. In particular, the sequence "." (bare line + feeds, without carriage returns) MUST NOT be treated as equivalent to + . as the end of mail data indication. + + Receipt of the end of mail data indication requires the server to + process the stored mail transaction information. This processing + consumes the information in the reverse-path buffer, the forward-path + buffer, and the mail data buffer, and on the completion of this + command these buffers are cleared. If the processing is successful, + the receiver MUST send an OK reply. If the processing fails the + receiver MUST send a failure reply. The SMTP model does not allow + for partial failures at this point: either the message is accepted by + the server for delivery and a positive response is returned or it is + not accepted and a failure reply is returned. In sending a positive + completion reply to the end of data indication, the receiver takes + full responsibility for the message (see section 6.1). Errors that + are diagnosed subsequently MUST be reported in a mail message, as + discussed in section 4.4. + + + + + +Klensin Standards Track [Page 33] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + When the SMTP server accepts a message either for relaying or for + final delivery, it inserts a trace record (also referred to + interchangeably as a "time stamp line" or "Received" line) at the top + of the mail data. This trace record indicates the identity of the + host that sent the message, the identity of the host that received + the message (and is inserting this time stamp), and the date and time + the message was received. Relayed messages will have multiple time + stamp lines. Details for formation of these lines, including their + syntax, is specified in section 4.4. + + Additional discussion about the operation of the DATA command appears + in section 3.3. + + Syntax: + "DATA" CRLF + +4.1.1.5 RESET (RSET) + + This command specifies that the current mail transaction will be + aborted. Any stored sender, recipients, and mail data MUST be + discarded, and all buffers and state tables cleared. The receiver + MUST send a "250 OK" reply to a RSET command with no arguments. A + reset command may be issued by the client at any time. It is + effectively equivalent to a NOOP (i.e., if has no effect) if issued + immediately after EHLO, before EHLO is issued in the session, after + an end-of-data indicator has been sent and acknowledged, or + immediately before a QUIT. An SMTP server MUST NOT close the + connection as the result of receiving a RSET; that action is reserved + for QUIT (see section 4.1.1.10). + + Since EHLO implies some additional processing and response by the + server, RSET will normally be more efficient than reissuing that + command, even though the formal semantics are the same. + + There are circumstances, contrary to the intent of this + specification, in which an SMTP server may receive an indication that + the underlying TCP connection has been closed or reset. To preserve + the robustness of the mail system, SMTP servers SHOULD be prepared + for this condition and SHOULD treat it as if a QUIT had been received + before the connection disappeared. + + Syntax: + "RSET" CRLF + + + + + + + + +Klensin Standards Track [Page 34] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +4.1.1.6 VERIFY (VRFY) + + This command asks the receiver to confirm that the argument + identifies a user or mailbox. If it is a user name, information is + returned as specified in section 3.5. + + This command has no effect on the reverse-path buffer, the forward- + path buffer, or the mail data buffer. + + Syntax: + "VRFY" SP String CRLF + +4.1.1.7 EXPAND (EXPN) + + This command asks the receiver to confirm that the argument + identifies a mailing list, and if so, to return the membership of + that list. If the command is successful, a reply is returned + containing information as described in section 3.5. This reply will + have multiple lines except in the trivial case of a one-member list. + + This command has no effect on the reverse-path buffer, the forward- + path buffer, or the mail data buffer and may be issued at any time. + + Syntax: + "EXPN" SP String CRLF + +4.1.1.8 HELP (HELP) + + This command causes the server to send helpful information to the + client. The command MAY take an argument (e.g., any command name) + and return more specific information as a response. + + This command has no effect on the reverse-path buffer, the forward- + path buffer, or the mail data buffer and may be issued at any time. + + SMTP servers SHOULD support HELP without arguments and MAY support it + with arguments. + + Syntax: + "HELP" [ SP String ] CRLF + +4.1.1.9 NOOP (NOOP) + + This command does not affect any parameters or previously entered + commands. It specifies no action other than that the receiver send + an OK reply. + + + + + +Klensin Standards Track [Page 35] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + This command has no effect on the reverse-path buffer, the forward- + path buffer, or the mail data buffer and may be issued at any time. + If a parameter string is specified, servers SHOULD ignore it. + + Syntax: + "NOOP" [ SP String ] CRLF + +4.1.1.10 QUIT (QUIT) + + This command specifies that the receiver MUST send an OK reply, and + then close the transmission channel. + + The receiver MUST NOT intentionally close the transmission channel + until it receives and replies to a QUIT command (even if there was an + error). The sender MUST NOT intentionally close the transmission + channel until it sends a QUIT command and SHOULD wait until it + receives the reply (even if there was an error response to a previous + command). If the connection is closed prematurely due to violations + of the above or system or network failure, the server MUST cancel any + pending transaction, but not undo any previously completed + transaction, and generally MUST act as if the command or transaction + in progress had received a temporary error (i.e., a 4yz response). + + The QUIT command may be issued at any time. + + Syntax: + "QUIT" CRLF + +4.1.2 Command Argument Syntax + + The syntax of the argument fields of the above commands (using the + syntax specified in [8] where applicable) is given below. Some of + the productions given below are used only in conjunction with source + routes as described in appendix C. Terminals not defined in this + document, such as ALPHA, DIGIT, SP, CR, LF, CRLF, are as defined in + the "core" syntax [8 (section 6)] or in the message format syntax + [32]. + + Reverse-path = Path + Forward-path = Path + Path = "<" [ A-d-l ":" ] Mailbox ">" + A-d-l = At-domain *( "," A-d-l ) + ; Note that this form, the so-called "source route", + ; MUST BE accepted, SHOULD NOT be generated, and SHOULD be + ; ignored. + At-domain = "@" domain + Mail-parameters = esmtp-param *(SP esmtp-param) + Rcpt-parameters = esmtp-param *(SP esmtp-param) + + + +Klensin Standards Track [Page 36] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + esmtp-param = esmtp-keyword ["=" esmtp-value] + esmtp-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-") + esmtp-value = 1*(%d33-60 / %d62-127) + ; any CHAR excluding "=", SP, and control characters + Keyword = Ldh-str + Argument = Atom + Domain = (sub-domain 1*("." sub-domain)) / address-literal + sub-domain = Let-dig [Ldh-str] + + address-literal = "[" IPv4-address-literal / + IPv6-address-literal / + General-address-literal "]" + ; See section 4.1.3 + + Mailbox = Local-part "@" Domain + + Local-part = Dot-string / Quoted-string + ; MAY be case-sensitive + + Dot-string = Atom *("." Atom) + + Atom = 1*atext + + Quoted-string = DQUOTE *qcontent DQUOTE + + String = Atom / Quoted-string + + While the above definition for Local-part is relatively permissive, + for maximum interoperability, a host that expects to receive mail + SHOULD avoid defining mailboxes where the Local-part requires (or + uses) the Quoted-string form or where the Local-part is case- + sensitive. For any purposes that require generating or comparing + Local-parts (e.g., to specific mailbox names), all quoted forms MUST + be treated as equivalent and the sending system SHOULD transmit the + form that uses the minimum quoting possible. + + Systems MUST NOT define mailboxes in such a way as to require the use + in SMTP of non-ASCII characters (octets with the high order bit set + to one) or ASCII "control characters" (decimal value 0-31 and 127). + These characters MUST NOT be used in MAIL or RCPT commands or other + commands that require mailbox names. + + Note that the backslash, "\", is a quote character, which is used to + indicate that the next character is to be used literally (instead of + its normal interpretation). For example, "Joe\,Smith" indicates a + single nine character user field with the comma being the fourth + character of the field. + + + + +Klensin Standards Track [Page 37] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + To promote interoperability and consistent with long-standing + guidance about conservative use of the DNS in naming and applications + (e.g., see section 2.3.1 of the base DNS document, RFC1035 [22]), + characters outside the set of alphas, digits, and hyphen MUST NOT + appear in domain name labels for SMTP clients or servers. In + particular, the underscore character is not permitted. SMTP servers + that receive a command in which invalid character codes have been + employed, and for which there are no other reasons for rejection, + MUST reject that command with a 501 response. + +4.1.3 Address Literals + + Sometimes a host is not known to the domain name system and + communication (and, in particular, communication to report and repair + the error) is blocked. To bypass this barrier a special literal form + of the address is allowed as an alternative to a domain name. For + IPv4 addresses, this form uses four small decimal integers separated + by dots and enclosed by brackets such as [123.255.37.2], which + indicates an (IPv4) Internet Address in sequence-of-octets form. For + IPv6 and other forms of addressing that might eventually be + standardized, the form consists of a standardized "tag" that + identifies the address syntax, a colon, and the address itself, in a + format specified as part of the IPv6 standards [17]. + + Specifically: + + IPv4-address-literal = Snum 3("." Snum) + IPv6-address-literal = "IPv6:" IPv6-addr + General-address-literal = Standardized-tag ":" 1*dcontent + Standardized-tag = Ldh-str + ; MUST be specified in a standards-track RFC + ; and registered with IANA + + Snum = 1*3DIGIT ; representing a decimal integer + ; value in the range 0 through 255 + Let-dig = ALPHA / DIGIT + Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig + + IPv6-addr = IPv6-full / IPv6-comp / IPv6v4-full / IPv6v4-comp + IPv6-hex = 1*4HEXDIG + IPv6-full = IPv6-hex 7(":" IPv6-hex) + IPv6-comp = [IPv6-hex *5(":" IPv6-hex)] "::" [IPv6-hex *5(":" + IPv6-hex)] + ; The "::" represents at least 2 16-bit groups of zeros + ; No more than 6 groups in addition to the "::" may be + ; present + IPv6v4-full = IPv6-hex 5(":" IPv6-hex) ":" IPv4-address-literal + IPv6v4-comp = [IPv6-hex *3(":" IPv6-hex)] "::" + + + +Klensin Standards Track [Page 38] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + [IPv6-hex *3(":" IPv6-hex) ":"] IPv4-address-literal + ; The "::" represents at least 2 16-bit groups of zeros + ; No more than 4 groups in addition to the "::" and + ; IPv4-address-literal may be present + +4.1.4 Order of Commands + + There are restrictions on the order in which these commands may be + used. + + A session that will contain mail transactions MUST first be + initialized by the use of the EHLO command. An SMTP server SHOULD + accept commands for non-mail transactions (e.g., VRFY or EXPN) + without this initialization. + + An EHLO command MAY be issued by a client later in the session. If + it is issued after the session begins, the SMTP server MUST clear all + buffers and reset the state exactly as if a RSET command had been + issued. In other words, the sequence of RSET followed immediately by + EHLO is redundant, but not harmful other than in the performance cost + of executing unnecessary commands. + + If the EHLO command is not acceptable to the SMTP server, 501, 500, + or 502 failure replies MUST be returned as appropriate. The SMTP + server MUST stay in the same state after transmitting these replies + that it was in before the EHLO was received. + + The SMTP client MUST, if possible, ensure that the domain parameter + to the EHLO command is a valid principal host name (not a CNAME or MX + name) for its host. If this is not possible (e.g., when the client's + address is dynamically assigned and the client does not have an + obvious name), an address literal SHOULD be substituted for the + domain name and supplemental information provided that will assist in + identifying the client. + + An SMTP server MAY verify that the domain name parameter in the EHLO + command actually corresponds to the IP address of the client. + However, the server MUST NOT refuse to accept a message for this + reason if the verification fails: the information about verification + failure is for logging and tracing only. + + The NOOP, HELP, EXPN, VRFY, and RSET commands can be used at any time + during a session, or without previously initializing a session. SMTP + servers SHOULD process these normally (that is, not return a 503 + code) even if no EHLO command has yet been received; clients SHOULD + open a session with EHLO before sending these commands. + + + + + +Klensin Standards Track [Page 39] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + If these rules are followed, the example in RFC 821 that shows "550 + access denied to you" in response to an EXPN command is incorrect + unless an EHLO command precedes the EXPN or the denial of access is + based on the client's IP address or other authentication or + authorization-determining mechanisms. + + The MAIL command (or the obsolete SEND, SOML, or SAML commands) + begins a mail transaction. Once started, a mail transaction consists + of a transaction beginning command, one or more RCPT commands, and a + DATA command, in that order. A mail transaction may be aborted by + the RSET (or a new EHLO) command. There may be zero or more + transactions in a session. MAIL (or SEND, SOML, or SAML) MUST NOT be + sent if a mail transaction is already open, i.e., it should be sent + only if no mail transaction had been started in the session, or it + the previous one successfully concluded with a successful DATA + command, or if the previous one was aborted with a RSET. + + If the transaction beginning command argument is not acceptable, a + 501 failure reply MUST be returned and the SMTP server MUST stay in + the same state. If the commands in a transaction are out of order to + the degree that they cannot be processed by the server, a 503 failure + reply MUST be returned and the SMTP server MUST stay in the same + state. + + The last command in a session MUST be the QUIT command. The QUIT + command cannot be used at any other time in a session, but SHOULD be + used by the client SMTP to request connection closure, even when no + session opening command was sent and accepted. + +4.1.5 Private-use Commands + + As specified in section 2.2.2, commands starting in "X" may be used + by bilateral agreement between the client (sending) and server + (receiving) SMTP agents. An SMTP server that does not recognize such + a command is expected to reply with "500 Command not recognized". An + extended SMTP server MAY list the feature names associated with these + private commands in the response to the EHLO command. + + Commands sent or accepted by SMTP systems that do not start with "X" + MUST conform to the requirements of section 2.2.2. + +4.2 SMTP Replies + + Replies to SMTP commands serve to ensure the synchronization of + requests and actions in the process of mail transfer and to guarantee + that the SMTP client always knows the state of the SMTP server. + Every command MUST generate exactly one reply. + + + + +Klensin Standards Track [Page 40] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + The details of the command-reply sequence are described in section + 4.3. + + An SMTP reply consists of a three digit number (transmitted as three + numeric characters) followed by some text unless specified otherwise + in this document. The number is for use by automata to determine + what state to enter next; the text is for the human user. The three + digits contain enough encoded information that the SMTP client need + not examine the text and may either discard it or pass it on to the + user, as appropriate. Exceptions are as noted elsewhere in this + document. In particular, the 220, 221, 251, 421, and 551 reply codes + are associated with message text that must be parsed and interpreted + by machines. In the general case, the text may be receiver dependent + and context dependent, so there are likely to be varying texts for + each reply code. A discussion of the theory of reply codes is given + in section 4.2.1. Formally, a reply is defined to be the sequence: a + three-digit code, , one line of text, and , or a multiline + reply (as defined in section 4.2.1). Since, in violation of this + specification, the text is sometimes not sent, clients which do not + receive it SHOULD be prepared to process the code alone (with or + without a trailing space character). Only the EHLO, EXPN, and HELP + commands are expected to result in multiline replies in normal + circumstances, however, multiline replies are allowed for any + command. + + In ABNF, server responses are: + + Greeting = "220 " Domain [ SP text ] CRLF + Reply-line = Reply-code [ SP text ] CRLF + + where "Greeting" appears only in the 220 response that announces that + the server is opening its part of the connection. + + An SMTP server SHOULD send only the reply codes listed in this + document. An SMTP server SHOULD use the text shown in the examples + whenever appropriate. + + An SMTP client MUST determine its actions only by the reply code, not + by the text (except for the "change of address" 251 and 551 and, if + necessary, 220, 221, and 421 replies); in the general case, any text, + including no text at all (although senders SHOULD NOT send bare + codes), MUST be acceptable. The space (blank) following the reply + code is considered part of the text. Whenever possible, a receiver- + SMTP SHOULD test the first digit (severity indication) of the reply + code. + + + + + + +Klensin Standards Track [Page 41] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + The list of codes that appears below MUST NOT be construed as + permanent. While the addition of new codes should be a rare and + significant activity, with supplemental information in the textual + part of the response being preferred, new codes may be added as the + result of new Standards or Standards-track specifications. + Consequently, a sender-SMTP MUST be prepared to handle codes not + specified in this document and MUST do so by interpreting the first + digit only. + +4.2.1 Reply Code Severities and Theory + + The three digits of the reply each have a special significance. The + first digit denotes whether the response is good, bad or incomplete. + An unsophisticated SMTP client, or one that receives an unexpected + code, will be able to determine its next action (proceed as planned, + redo, retrench, etc.) by examining this first digit. An SMTP client + that wants to know approximately what kind of error occurred (e.g., + mail system error, command syntax error) may examine the second + digit. The third digit and any supplemental information that may be + present is reserved for the finest gradation of information. + + There are five values for the first digit of the reply code: + + 1yz Positive Preliminary reply + The command has been accepted, but the requested action is being + held in abeyance, pending confirmation of the information in this + reply. The SMTP client should send another command specifying + whether to continue or abort the action. Note: unextended SMTP + does not have any commands that allow this type of reply, and so + does not have continue or abort commands. + + 2yz Positive Completion reply + The requested action has been successfully completed. A new + request may be initiated. + + 3yz Positive Intermediate reply + The command has been accepted, but the requested action is being + held in abeyance, pending receipt of further information. The + SMTP client should send another command specifying this + information. This reply is used in command sequence groups (i.e., + in DATA). + + 4yz Transient Negative Completion reply + The command was not accepted, and the requested action did not + occur. However, the error condition is temporary and the action + may be requested again. The sender should return to the beginning + of the command sequence (if any). It is difficult to assign a + meaning to "transient" when two different sites (receiver- and + + + +Klensin Standards Track [Page 42] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + sender-SMTP agents) must agree on the interpretation. Each reply + in this category might have a different time value, but the SMTP + client is encouraged to try again. A rule of thumb to determine + whether a reply fits into the 4yz or the 5yz category (see below) + is that replies are 4yz if they can be successful if repeated + without any change in command form or in properties of the sender + or receiver (that is, the command is repeated identically and the + receiver does not put up a new implementation.) + + 5yz Permanent Negative Completion reply + The command was not accepted and the requested action did not + occur. The SMTP client is discouraged from repeating the exact + request (in the same sequence). Even some "permanent" error + conditions can be corrected, so the human user may want to direct + the SMTP client to reinitiate the command sequence by direct + action at some point in the future (e.g., after the spelling has + been changed, or the user has altered the account status). + + The second digit encodes responses in specific categories: + + x0z Syntax: These replies refer to syntax errors, syntactically + correct commands that do not fit any functional category, and + unimplemented or superfluous commands. + + x1z Information: These are replies to requests for information, + such as status or help. + + x2z Connections: These are replies referring to the transmission + channel. + + x3z Unspecified. + + x4z Unspecified. + + x5z Mail system: These replies indicate the status of the receiver + mail system vis-a-vis the requested transfer or other mail system + action. + + The third digit gives a finer gradation of meaning in each category + specified by the second digit. The list of replies illustrates this. + Each reply text is recommended rather than mandatory, and may even + change according to the command with which it is associated. On the + other hand, the reply codes must strictly follow the specifications + in this section. Receiver implementations should not invent new + codes for slightly different situations from the ones described here, + but rather adapt codes already defined. + + + + + +Klensin Standards Track [Page 43] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + For example, a command such as NOOP, whose successful execution does + not offer the SMTP client any new information, will return a 250 + reply. The reply is 502 when the command requests an unimplemented + non-site-specific action. A refinement of that is the 504 reply for + a command that is implemented, but that requests an unimplemented + parameter. + + The reply text may be longer than a single line; in these cases the + complete text must be marked so the SMTP client knows when it can + stop reading the reply. This requires a special format to indicate a + multiple line reply. + + The format for multiline replies requires that every line, except the + last, begin with the reply code, followed immediately by a hyphen, + "-" (also known as minus), followed by text. The last line will + begin with the reply code, followed immediately by , optionally + some text, and . As noted above, servers SHOULD send the + if subsequent text is not sent, but clients MUST be prepared for it + to be omitted. + + For example: + + 123-First line + 123-Second line + 123-234 text beginning with numbers + 123 The last line + + In many cases the SMTP client then simply needs to search for a line + beginning with the reply code followed by or and ignore + all preceding lines. In a few cases, there is important data for the + client in the reply "text". The client will be able to identify + these cases from the current context. + +4.2.2 Reply Codes by Function Groups + + 500 Syntax error, command unrecognized + (This may include errors such as command line too long) + 501 Syntax error in parameters or arguments + 502 Command not implemented (see section 4.2.4) + 503 Bad sequence of commands + 504 Command parameter not implemented + + 211 System status, or system help reply + 214 Help message + (Information on how to use the receiver or the meaning of a + particular non-standard command; this reply is useful only + to the human user) + + + + +Klensin Standards Track [Page 44] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + 220 Service ready + 221 Service closing transmission channel + 421 Service not available, closing transmission channel + (This may be a reply to any command if the service knows it + must shut down) + + 250 Requested mail action okay, completed + 251 User not local; will forward to + (See section 3.4) + 252 Cannot VRFY user, but will accept message and attempt + delivery + (See section 3.5.3) + 450 Requested mail action not taken: mailbox unavailable + (e.g., mailbox busy) + 550 Requested action not taken: mailbox unavailable + (e.g., mailbox not found, no access, or command rejected + for policy reasons) + 451 Requested action aborted: error in processing + 551 User not local; please try + (See section 3.4) + 452 Requested action not taken: insufficient system storage + 552 Requested mail action aborted: exceeded storage allocation + 553 Requested action not taken: mailbox name not allowed + (e.g., mailbox syntax incorrect) + 354 Start mail input; end with . + 554 Transaction failed (Or, in the case of a connection-opening + response, "No SMTP service here") + +4.2.3 Reply Codes in Numeric Order + + 211 System status, or system help reply + 214 Help message + (Information on how to use the receiver or the meaning of a + particular non-standard command; this reply is useful only + to the human user) + 220 Service ready + 221 Service closing transmission channel + 250 Requested mail action okay, completed + 251 User not local; will forward to + (See section 3.4) + 252 Cannot VRFY user, but will accept message and attempt + delivery + (See section 3.5.3) + + 354 Start mail input; end with . + + + + + + +Klensin Standards Track [Page 45] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + 421 Service not available, closing transmission channel + (This may be a reply to any command if the service knows it + must shut down) + 450 Requested mail action not taken: mailbox unavailable + (e.g., mailbox busy) + 451 Requested action aborted: local error in processing + 452 Requested action not taken: insufficient system storage + 500 Syntax error, command unrecognized + (This may include errors such as command line too long) + 501 Syntax error in parameters or arguments + 502 Command not implemented (see section 4.2.4) + 503 Bad sequence of commands + 504 Command parameter not implemented + 550 Requested action not taken: mailbox unavailable + (e.g., mailbox not found, no access, or command rejected + for policy reasons) + 551 User not local; please try + (See section 3.4) + 552 Requested mail action aborted: exceeded storage allocation + 553 Requested action not taken: mailbox name not allowed + (e.g., mailbox syntax incorrect) + 554 Transaction failed (Or, in the case of a connection-opening + response, "No SMTP service here") + +4.2.4 Reply Code 502 + + Questions have been raised as to when reply code 502 (Command not + implemented) SHOULD be returned in preference to other codes. 502 + SHOULD be used when the command is actually recognized by the SMTP + server, but not implemented. If the command is not recognized, code + 500 SHOULD be returned. Extended SMTP systems MUST NOT list + capabilities in response to EHLO for which they will return 502 (or + 500) replies. + +4.2.5 Reply Codes After DATA and the Subsequent . + + When an SMTP server returns a positive completion status (2yz code) + after the DATA command is completed with ., it accepts + responsibility for: + + - delivering the message (if the recipient mailbox exists), or + + - if attempts to deliver the message fail due to transient + conditions, retrying delivery some reasonable number of times at + intervals as specified in section 4.5.4. + + + + + + +Klensin Standards Track [Page 46] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + - if attempts to deliver the message fail due to permanent + conditions, or if repeated attempts to deliver the message fail + due to transient conditions, returning appropriate notification to + the sender of the original message (using the address in the SMTP + MAIL command). + + When an SMTP server returns a permanent error status (5yz) code after + the DATA command is completed with ., it MUST NOT make + any subsequent attempt to deliver that message. The SMTP client + retains responsibility for delivery of that message and may either + return it to the user or requeue it for a subsequent attempt (see + section 4.5.4.1). + + The user who originated the message SHOULD be able to interpret the + return of a transient failure status (by mail message or otherwise) + as a non-delivery indication, just as a permanent failure would be + interpreted. I.e., if the client SMTP successfully handles these + conditions, the user will not receive such a reply. + + When an SMTP server returns a permanent error status (5yz) code after + the DATA command is completely with ., it MUST NOT make + any subsequent attempt to deliver the message. As with temporary + error status codes, the SMTP client retains responsibility for the + message, but SHOULD not again attempt delivery to the same server + without user review and intervention of the message. + +4.3 Sequencing of Commands and Replies + +4.3.1 Sequencing Overview + + The communication between the sender and receiver is an alternating + dialogue, controlled by the sender. As such, the sender issues a + command and the receiver responds with a reply. Unless other + arrangements are negotiated through service extensions, the sender + MUST wait for this response before sending further commands. + + One important reply is the connection greeting. Normally, a receiver + will send a 220 "Service ready" reply when the connection is + completed. The sender SHOULD wait for this greeting message before + sending any commands. + + Note: all the greeting-type replies have the official name (the + fully-qualified primary domain name) of the server host as the first + word following the reply code. Sometimes the host will have no + meaningful name. See 4.1.3 for a discussion of alternatives in these + situations. + + + + + +Klensin Standards Track [Page 47] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + For example, + + 220 ISIF.USC.EDU Service ready + or + 220 mail.foo.com SuperSMTP v 6.1.2 Service ready + or + 220 [10.0.0.1] Clueless host service ready + + The table below lists alternative success and failure replies for + each command. These SHOULD be strictly adhered to: a receiver may + substitute text in the replies, but the meaning and action implied by + the code numbers and by the specific command reply sequence cannot be + altered. + +4.3.2 Command-Reply Sequences + + Each command is listed with its usual possible replies. The prefixes + used before the possible replies are "I" for intermediate, "S" for + success, and "E" for error. Since some servers may generate other + replies under special circumstances, and to allow for future + extension, SMTP clients SHOULD, when possible, interpret only the + first digit of the reply and MUST be prepared to deal with + unrecognized reply codes by interpreting the first digit only. + Unless extended using the mechanisms described in section 2.2, SMTP + servers MUST NOT transmit reply codes to an SMTP client that are + other than three digits or that do not start in a digit between 2 and + 5 inclusive. + + These sequencing rules and, in principle, the codes themselves, can + be extended or modified by SMTP extensions offered by the server and + accepted (requested) by the client. + + In addition to the codes listed below, any SMTP command can return + any of the following codes if the corresponding unusual circumstances + are encountered: + + 500 For the "command line too long" case or if the command name was + not recognized. Note that producing a "command not recognized" + error in response to the required subset of these commands is a + violation of this specification. + + 501 Syntax error in command or arguments. In order to provide for + future extensions, commands that are specified in this document as + not accepting arguments (DATA, RSET, QUIT) SHOULD return a 501 + message if arguments are supplied in the absence of EHLO- + advertised extensions. + + 421 Service shutting down and closing transmission channel + + + +Klensin Standards Track [Page 48] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + Specific sequences are: + + CONNECTION ESTABLISHMENT + S: 220 + E: 554 + EHLO or HELO + S: 250 + E: 504, 550 + MAIL + S: 250 + E: 552, 451, 452, 550, 553, 503 + RCPT + S: 250, 251 (but see section 3.4 for discussion of 251 and 551) + E: 550, 551, 552, 553, 450, 451, 452, 503, 550 + DATA + I: 354 -> data -> S: 250 + E: 552, 554, 451, 452 + E: 451, 554, 503 + RSET + S: 250 + VRFY + S: 250, 251, 252 + E: 550, 551, 553, 502, 504 + EXPN + S: 250, 252 + E: 550, 500, 502, 504 + HELP + S: 211, 214 + E: 502, 504 + NOOP + S: 250 + QUIT + S: 221 + +4.4 Trace Information + + When an SMTP server receives a message for delivery or further + processing, it MUST insert trace ("time stamp" or "Received") + information at the beginning of the message content, as discussed in + section 4.1.1.4. + + This line MUST be structured as follows: + + - The FROM field, which MUST be supplied in an SMTP environment, + SHOULD contain both (1) the name of the source host as presented + in the EHLO command and (2) an address literal containing the IP + address of the source, determined from the TCP connection. + + + + +Klensin Standards Track [Page 49] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + - The ID field MAY contain an "@" as suggested in RFC 822, but this + is not required. + + - The FOR field MAY contain a list of entries when multiple + RCPT commands have been given. This may raise some security + issues and is usually not desirable; see section 7.2. + + An Internet mail program MUST NOT change a Received: line that was + previously added to the message header. SMTP servers MUST prepend + Received lines to messages; they MUST NOT change the order of + existing lines or insert Received lines in any other location. + + As the Internet grows, comparability of Received fields is important + for detecting problems, especially slow relays. SMTP servers that + create Received fields SHOULD use explicit offsets in the dates + (e.g., -0800), rather than time zone names of any type. Local time + (with an offset) is preferred to UT when feasible. This formulation + allows slightly more information about local circumstances to be + specified. If UT is needed, the receiver need merely do some simple + arithmetic to convert the values. Use of UT loses information about + the time zone-location of the server. If it is desired to supply a + time zone name, it SHOULD be included in a comment. + + When the delivery SMTP server makes the "final delivery" of a + message, it inserts a return-path line at the beginning of the mail + data. This use of return-path is required; mail systems MUST support + it. The return-path line preserves the information in the from the MAIL command. Here, final delivery means the message + has left the SMTP environment. Normally, this would mean it had been + delivered to the destination user or an associated mail drop, but in + some cases it may be further processed and transmitted by another + mail system. + + It is possible for the mailbox in the return path to be different + from the actual sender's mailbox, for example, if error responses are + to be delivered to a special error handling mailbox rather than to + the message sender. When mailing lists are involved, this + arrangement is common and useful as a means of directing errors to + the list maintainer rather than the message originator. + + The text above implies that the final mail data will begin with a + return path line, followed by one or more time stamp lines. These + lines will be followed by the mail data headers and body [32]. + + It is sometimes difficult for an SMTP server to determine whether or + not it is making final delivery since forwarding or other operations + may occur after the message is accepted for delivery. Consequently, + + + + +Klensin Standards Track [Page 50] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + any further (forwarding, gateway, or relay) systems MAY remove the + return path and rebuild the MAIL command as needed to ensure that + exactly one such line appears in a delivered message. + + A message-originating SMTP system SHOULD NOT send a message that + already contains a Return-path header. SMTP servers performing a + relay function MUST NOT inspect the message data, and especially not + to the extent needed to determine if Return-path headers are present. + SMTP servers making final delivery MAY remove Return-path headers + before adding their own. + + The primary purpose of the Return-path is to designate the address to + which messages indicating non-delivery or other mail system failures + are to be sent. For this to be unambiguous, exactly one return path + SHOULD be present when the message is delivered. Systems using RFC + 822 syntax with non-SMTP transports SHOULD designate an unambiguous + address, associated with the transport envelope, to which error + reports (e.g., non-delivery messages) should be sent. + + Historical note: Text in RFC 822 that appears to contradict the use + of the Return-path header (or the envelope reverse path address from + the MAIL command) as the destination for error messages is not + applicable on the Internet. The reverse path address (as copied into + the Return-path) MUST be used as the target of any mail containing + delivery error messages. + + In particular: + + - a gateway from SMTP->elsewhere SHOULD insert a return-path header, + unless it is known that the "elsewhere" transport also uses + Internet domain addresses and maintains the envelope sender + address separately. + + - a gateway from elsewhere->SMTP SHOULD delete any return-path + header present in the message, and either copy that information to + the SMTP envelope or combine it with information present in the + envelope of the other transport system to construct the reverse + path argument to the MAIL command in the SMTP envelope. + + The server must give special treatment to cases in which the + processing following the end of mail data indication is only + partially successful. This could happen if, after accepting several + recipients and the mail data, the SMTP server finds that the mail + data could be successfully delivered to some, but not all, of the + recipients. In such cases, the response to the DATA command MUST be + an OK reply. However, the SMTP server MUST compose and send an + "undeliverable mail" notification message to the originator of the + message. + + + +Klensin Standards Track [Page 51] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + A single notification listing all of the failed recipients or + separate notification messages MUST be sent for each failed + recipient. For economy of processing by the sender, the former is + preferred when possible. All undeliverable mail notification + messages are sent using the MAIL command (even if they result from + processing the obsolete SEND, SOML, or SAML commands) and use a null + return path as discussed in section 3.7. + + The time stamp line and the return path line are formally defined as + follows: + +Return-path-line = "Return-Path:" FWS Reverse-path + +Time-stamp-line = "Received:" FWS Stamp + +Stamp = From-domain By-domain Opt-info ";" FWS date-time + + ; where "date-time" is as defined in [32] + ; but the "obs-" forms, especially two-digit + ; years, are prohibited in SMTP and MUST NOT be used. + +From-domain = "FROM" FWS Extended-Domain CFWS + +By-domain = "BY" FWS Extended-Domain CFWS + +Extended-Domain = Domain / + ( Domain FWS "(" TCP-info ")" ) / + ( Address-literal FWS "(" TCP-info ")" ) + +TCP-info = Address-literal / ( Domain FWS Address-literal ) + ; Information derived by server from TCP connection + ; not client EHLO. + +Opt-info = [Via] [With] [ID] [For] + +Via = "VIA" FWS Link CFWS + +With = "WITH" FWS Protocol CFWS + +ID = "ID" FWS String / msg-id CFWS + +For = "FOR" FWS 1*( Path / Mailbox ) CFWS + +Link = "TCP" / Addtl-Link +Addtl-Link = Atom + ; Additional standard names for links are registered with the + ; Internet Assigned Numbers Authority (IANA). "Via" is + ; primarily of value with non-Internet transports. SMTP + + + +Klensin Standards Track [Page 52] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + ; servers SHOULD NOT use unregistered names. +Protocol = "ESMTP" / "SMTP" / Attdl-Protocol +Attdl-Protocol = Atom + ; Additional standard names for protocols are registered with the + ; Internet Assigned Numbers Authority (IANA). SMTP servers + ; SHOULD NOT use unregistered names. + +4.5 Additional Implementation Issues + +4.5.1 Minimum Implementation + + In order to make SMTP workable, the following minimum implementation + is required for all receivers. The following commands MUST be + supported to conform to this specification: + + EHLO + HELO + MAIL + RCPT + DATA + RSET + NOOP + QUIT + VRFY + + Any system that includes an SMTP server supporting mail relaying or + delivery MUST support the reserved mailbox "postmaster" as a case- + insensitive local name. This postmaster address is not strictly + necessary if the server always returns 554 on connection opening (as + described in section 3.1). The requirement to accept mail for + postmaster implies that RCPT commands which specify a mailbox for + postmaster at any of the domains for which the SMTP server provides + mail service, as well as the special case of "RCPT TO:" + (with no domain specification), MUST be supported. + + SMTP systems are expected to make every reasonable effort to accept + mail directed to Postmaster from any other system on the Internet. + In extreme cases --such as to contain a denial of service attack or + other breach of security-- an SMTP server may block mail directed to + Postmaster. However, such arrangements SHOULD be narrowly tailored + so as to avoid blocking messages which are not part of such attacks. + +4.5.2 Transparency + + Without some provision for data transparency, the character sequence + "." ends the mail text and cannot be sent by the user. + In general, users are not aware of such "forbidden" sequences. To + + + + +Klensin Standards Track [Page 53] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + allow all user composed text to be transmitted transparently, the + following procedures are used: + + - Before sending a line of mail text, the SMTP client checks the + first character of the line. If it is a period, one additional + period is inserted at the beginning of the line. + + - When a line of mail text is received by the SMTP server, it checks + the line. If the line is composed of a single period, it is + treated as the end of mail indicator. If the first character is a + period and there are other characters on the line, the first + character is deleted. + + The mail data may contain any of the 128 ASCII characters. All + characters are to be delivered to the recipient's mailbox, including + spaces, vertical and horizontal tabs, and other control characters. + If the transmission channel provides an 8-bit byte (octet) data + stream, the 7-bit ASCII codes are transmitted right justified in the + octets, with the high order bits cleared to zero. See 3.7 for + special treatment of these conditions in SMTP systems serving a relay + function. + + In some systems it may be necessary to transform the data as it is + received and stored. This may be necessary for hosts that use a + different character set than ASCII as their local character set, that + store data in records rather than strings, or which use special + character sequences as delimiters inside mailboxes. If such + transformations are necessary, they MUST be reversible, especially if + they are applied to mail being relayed. + +4.5.3 Sizes and Timeouts + +4.5.3.1 Size limits and minimums + + There are several objects that have required minimum/maximum sizes. + Every implementation MUST be able to receive objects of at least + these sizes. Objects larger than these sizes SHOULD be avoided when + possible. However, some Internet mail constructs such as encoded + X.400 addresses [16] will often require larger objects: clients MAY + attempt to transmit these, but MUST be prepared for a server to + reject them if they cannot be handled by it. To the maximum extent + possible, implementation techniques which impose no limits on the + length of these objects should be used. + + local-part + The maximum total length of a user name or other local-part is 64 + characters. + + + + +Klensin Standards Track [Page 54] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + domain + The maximum total length of a domain name or number is 255 + characters. + + path + The maximum total length of a reverse-path or forward-path is 256 + characters (including the punctuation and element separators). + + command line + The maximum total length of a command line including the command + word and the is 512 characters. SMTP extensions may be + used to increase this limit. + + reply line + The maximum total length of a reply line including the reply code + and the is 512 characters. More information may be + conveyed through multiple-line replies. + + text line + The maximum total length of a text line including the is + 1000 characters (not counting the leading dot duplicated for + transparency). This number may be increased by the use of SMTP + Service Extensions. + + message content + The maximum total length of a message content (including any + message headers as well as the message body) MUST BE at least 64K + octets. Since the introduction of Internet standards for + multimedia mail [12], message lengths on the Internet have grown + dramatically, and message size restrictions should be avoided if + at all possible. SMTP server systems that must impose + restrictions SHOULD implement the "SIZE" service extension [18], + and SMTP client systems that will send large messages SHOULD + utilize it when possible. + + recipients buffer + The minimum total number of recipients that must be buffered is + 100 recipients. Rejection of messages (for excessive recipients) + with fewer than 100 RCPT commands is a violation of this + specification. The general principle that relaying SMTP servers + MUST NOT, and delivery SMTP servers SHOULD NOT, perform validation + tests on message headers suggests that rejecting a message based + on the total number of recipients shown in header fields is to be + discouraged. A server which imposes a limit on the number of + recipients MUST behave in an orderly fashion, such as to reject + additional addresses over its limit rather than silently + discarding addresses previously accepted. A client that needs to + + + + +Klensin Standards Track [Page 55] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + deliver a message containing over 100 RCPT commands SHOULD be + prepared to transmit in 100-recipient "chunks" if the server + declines to accept more than 100 recipients in a single message. + + Errors due to exceeding these limits may be reported by using the + reply codes. Some examples of reply codes are: + + 500 Line too long. + or + 501 Path too long + or + 452 Too many recipients (see below) + or + 552 Too much mail data. + + RFC 821 [30] incorrectly listed the error where an SMTP server + exhausts its implementation limit on the number of RCPT commands + ("too many recipients") as having reply code 552. The correct reply + code for this condition is 452. Clients SHOULD treat a 552 code in + this case as a temporary, rather than permanent, failure so the logic + below works. + + When a conforming SMTP server encounters this condition, it has at + least 100 successful RCPT commands in its recipients buffer. If the + server is able to accept the message, then at least these 100 + addresses will be removed from the SMTP client's queue. When the + client attempts retransmission of those addresses which received 452 + responses, at least 100 of these will be able to fit in the SMTP + server's recipients buffer. Each retransmission attempt which is + able to deliver anything will be able to dispose of at least 100 of + these recipients. + + If an SMTP server has an implementation limit on the number of RCPT + commands and this limit is exhausted, it MUST use a response code of + 452 (but the client SHOULD also be prepared for a 552, as noted + above). If the server has a configured site-policy limitation on the + number of RCPT commands, it MAY instead use a 5XX response code. + This would be most appropriate if the policy limitation was intended + to apply if the total recipient count for a particular message body + were enforced even if that message body was sent in multiple mail + transactions. + +4.5.3.2 Timeouts + + An SMTP client MUST provide a timeout mechanism. It MUST use per- + command timeouts rather than somehow trying to time the entire mail + transaction. Timeouts SHOULD be easily reconfigurable, preferably + without recompiling the SMTP code. To implement this, a timer is set + + + +Klensin Standards Track [Page 56] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + for each SMTP command and for each buffer of the data transfer. The + latter means that the overall timeout is inherently proportional to + the size of the message. + + Based on extensive experience with busy mail-relay hosts, the minimum + per-command timeout values SHOULD be as follows: + + Initial 220 Message: 5 minutes + An SMTP client process needs to distinguish between a failed TCP + connection and a delay in receiving the initial 220 greeting + message. Many SMTP servers accept a TCP connection but delay + delivery of the 220 message until their system load permits more + mail to be processed. + + MAIL Command: 5 minutes + + RCPT Command: 5 minutes + A longer timeout is required if processing of mailing lists and + aliases is not deferred until after the message was accepted. + + DATA Initiation: 2 minutes + This is while awaiting the "354 Start Input" reply to a DATA + command. + + Data Block: 3 minutes + This is while awaiting the completion of each TCP SEND call + transmitting a chunk of data. + + DATA Termination: 10 minutes. + This is while awaiting the "250 OK" reply. When the receiver gets + the final period terminating the message data, it typically + performs processing to deliver the message to a user mailbox. A + spurious timeout at this point would be very wasteful and would + typically result in delivery of multiple copies of the message, + since it has been successfully sent and the server has accepted + responsibility for delivery. See section 6.1 for additional + discussion. + + An SMTP server SHOULD have a timeout of at least 5 minutes while it + is awaiting the next command from the sender. + +4.5.4 Retry Strategies + + The common structure of a host SMTP implementation includes user + mailboxes, one or more areas for queuing messages in transit, and one + or more daemon processes for sending and receiving mail. The exact + structure will vary depending on the needs of the users on the host + + + + +Klensin Standards Track [Page 57] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + and the number and size of mailing lists supported by the host. We + describe several optimizations that have proved helpful, particularly + for mailers supporting high traffic levels. + + Any queuing strategy MUST include timeouts on all activities on a + per-command basis. A queuing strategy MUST NOT send error messages + in response to error messages under any circumstances. + +4.5.4.1 Sending Strategy + + The general model for an SMTP client is one or more processes that + periodically attempt to transmit outgoing mail. In a typical system, + the program that composes a message has some method for requesting + immediate attention for a new piece of outgoing mail, while mail that + cannot be transmitted immediately MUST be queued and periodically + retried by the sender. A mail queue entry will include not only the + message itself but also the envelope information. + + The sender MUST delay retrying a particular destination after one + attempt has failed. In general, the retry interval SHOULD be at + least 30 minutes; however, more sophisticated and variable strategies + will be beneficial when the SMTP client can determine the reason for + non-delivery. + + Retries continue until the message is transmitted or the sender gives + up; the give-up time generally needs to be at least 4-5 days. The + parameters to the retry algorithm MUST be configurable. + + A client SHOULD keep a list of hosts it cannot reach and + corresponding connection timeouts, rather than just retrying queued + mail items. + + Experience suggests that failures are typically transient (the target + system or its connection has crashed), favoring a policy of two + connection attempts in the first hour the message is in the queue, + and then backing off to one every two or three hours. + + The SMTP client can shorten the queuing delay in cooperation with the + SMTP server. For example, if mail is received from a particular + address, it is likely that mail queued for that host can now be sent. + Application of this principle may, in many cases, eliminate the + requirement for an explicit "send queues now" function such as ETRN + [9]. + + The strategy may be further modified as a result of multiple + addresses per host (see below) to optimize delivery time vs. resource + usage. + + + + +Klensin Standards Track [Page 58] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + An SMTP client may have a large queue of messages for each + unavailable destination host. If all of these messages were retried + in every retry cycle, there would be excessive Internet overhead and + the sending system would be blocked for a long period. Note that an + SMTP client can generally determine that a delivery attempt has + failed only after a timeout of several minutes and even a one-minute + timeout per connection will result in a very large delay if retries + are repeated for dozens, or even hundreds, of queued messages to the + same host. + + At the same time, SMTP clients SHOULD use great care in caching + negative responses from servers. In an extreme case, if EHLO is + issued multiple times during the same SMTP connection, different + answers may be returned by the server. More significantly, 5yz + responses to the MAIL command MUST NOT be cached. + + When a mail message is to be delivered to multiple recipients, and + the SMTP server to which a copy of the message is to be sent is the + same for multiple recipients, then only one copy of the message + SHOULD be transmitted. That is, the SMTP client SHOULD use the + command sequence: MAIL, RCPT, RCPT,... RCPT, DATA instead of the + sequence: MAIL, RCPT, DATA, ..., MAIL, RCPT, DATA. However, if there + are very many addresses, a limit on the number of RCPT commands per + MAIL command MAY be imposed. Implementation of this efficiency + feature is strongly encouraged. + + Similarly, to achieve timely delivery, the SMTP client MAY support + multiple concurrent outgoing mail transactions. However, some limit + may be appropriate to protect the host from devoting all its + resources to mail. + +4.5.4.2 Receiving Strategy + + The SMTP server SHOULD attempt to keep a pending listen on the SMTP + port at all times. This requires the support of multiple incoming + TCP connections for SMTP. Some limit MAY be imposed but servers that + cannot handle more than one SMTP transaction at a time are not in + conformance with the intent of this specification. + + As discussed above, when the SMTP server receives mail from a + particular host address, it could activate its own SMTP queuing + mechanisms to retry any mail pending for that host address. + +4.5.5 Messages with a null reverse-path + + There are several types of notification messages which are required + by existing and proposed standards to be sent with a null reverse + path, namely non-delivery notifications as discussed in section 3.7, + + + +Klensin Standards Track [Page 59] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + other kinds of Delivery Status Notifications (DSNs) [24], and also + Message Disposition Notifications (MDNs) [10]. All of these kinds of + messages are notifications about a previous message, and they are + sent to the reverse-path of the previous mail message. (If the + delivery of such a notification message fails, that usually indicates + a problem with the mail system of the host to which the notification + message is addressed. For this reason, at some hosts the MTA is set + up to forward such failed notification messages to someone who is + able to fix problems with the mail system, e.g., via the postmaster + alias.) + + All other types of messages (i.e., any message which is not required + by a standards-track RFC to have a null reverse-path) SHOULD be sent + with with a valid, non-null reverse-path. + + Implementors of automated email processors should be careful to make + sure that the various kinds of messages with null reverse-path are + handled correctly, in particular such systems SHOULD NOT reply to + messages with null reverse-path. + +5. Address Resolution and Mail Handling + + Once an SMTP client lexically identifies a domain to which mail will + be delivered for processing (as described in sections 3.6 and 3.7), a + DNS lookup MUST be performed to resolve the domain name [22]. The + names are expected to be fully-qualified domain names (FQDNs): + mechanisms for inferring FQDNs from partial names or local aliases + are outside of this specification and, due to a history of problems, + are generally discouraged. The lookup first attempts to locate an MX + record associated with the name. If a CNAME record is found instead, + the resulting name is processed as if it were the initial name. If + no MX records are found, but an A RR is found, the A RR is treated as + if it was associated with an implicit MX RR, with a preference of 0, + pointing to that host. If one or more MX RRs are found for a given + name, SMTP systems MUST NOT utilize any A RRs associated with that + name unless they are located using the MX RRs; the "implicit MX" rule + above applies only if there are no MX records present. If MX records + are present, but none of them are usable, this situation MUST be + reported as an error. + + When the lookup succeeds, the mapping can result in a list of + alternative delivery addresses rather than a single address, because + of multiple MX records, multihoming, or both. To provide reliable + mail transmission, the SMTP client MUST be able to try (and retry) + each of the relevant addresses in this list in order, until a + delivery attempt succeeds. However, there MAY also be a configurable + limit on the number of alternate addresses that can be tried. In any + case, the SMTP client SHOULD try at least two addresses. + + + +Klensin Standards Track [Page 60] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + Two types of information is used to rank the host addresses: multiple + MX records, and multihomed hosts. + + Multiple MX records contain a preference indication that MUST be used + in sorting (see below). Lower numbers are more preferred than higher + ones. If there are multiple destinations with the same preference + and there is no clear reason to favor one (e.g., by recognition of an + easily-reached address), then the sender-SMTP MUST randomize them to + spread the load across multiple mail exchangers for a specific + organization. + + The destination host (perhaps taken from the preferred MX record) may + be multihomed, in which case the domain name resolver will return a + list of alternative IP addresses. It is the responsibility of the + domain name resolver interface to have ordered this list by + decreasing preference if necessary, and SMTP MUST try them in the + order presented. + + Although the capability to try multiple alternative addresses is + required, specific installations may want to limit or disable the use + of alternative addresses. The question of whether a sender should + attempt retries using the different addresses of a multihomed host + has been controversial. The main argument for using the multiple + addresses is that it maximizes the probability of timely delivery, + and indeed sometimes the probability of any delivery; the counter- + argument is that it may result in unnecessary resource use. Note + that resource use is also strongly determined by the sending strategy + discussed in section 4.5.4.1. + + If an SMTP server receives a message with a destination for which it + is a designated Mail eXchanger, it MAY relay the message (potentially + after having rewritten the MAIL FROM and/or RCPT TO addresses), make + final delivery of the message, or hand it off using some mechanism + outside the SMTP-provided transport environment. Of course, neither + of the latter require that the list of MX records be examined + further. + + If it determines that it should relay the message without rewriting + the address, it MUST sort the MX records to determine candidates for + delivery. The records are first ordered by preference, with the + lowest-numbered records being most preferred. The relay host MUST + then inspect the list for any of the names or addresses by which it + might be known in mail transactions. If a matching record is found, + all records at that preference level and higher-numbered ones MUST be + discarded from consideration. If there are no records left at that + point, it is an error condition, and the message MUST be returned as + undeliverable. If records do remain, they SHOULD be tried, best + preference first, as described above. + + + +Klensin Standards Track [Page 61] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +6. Problem Detection and Handling + +6.1 Reliable Delivery and Replies by Email + + When the receiver-SMTP accepts a piece of mail (by sending a "250 OK" + message in response to DATA), it is accepting responsibility for + delivering or relaying the message. It must take this responsibility + seriously. It MUST NOT lose the message for frivolous reasons, such + as because the host later crashes or because of a predictable + resource shortage. + + If there is a delivery failure after acceptance of a message, the + receiver-SMTP MUST formulate and mail a notification message. This + notification MUST be sent using a null ("<>") reverse path in the + envelope. The recipient of this notification MUST be the address + from the envelope return path (or the Return-Path: line). However, + if this address is null ("<>"), the receiver-SMTP MUST NOT send a + notification. Obviously, nothing in this section can or should + prohibit local decisions (i.e., as part of the same system + environment as the receiver-SMTP) to log or otherwise transmit + information about null address events locally if that is desired. If + the address is an explicit source route, it MUST be stripped down to + its final hop. + + For example, suppose that an error notification must be sent for a + message that arrived with: + + MAIL FROM:<@a,@b:user@d> + + The notification message MUST be sent using: + + RCPT TO: + + Some delivery failures after the message is accepted by SMTP will be + unavoidable. For example, it may be impossible for the receiving + SMTP server to validate all the delivery addresses in RCPT command(s) + due to a "soft" domain system error, because the target is a mailing + list (see earlier discussion of RCPT), or because the server is + acting as a relay and has no immediate access to the delivering + system. + + To avoid receiving duplicate messages as the result of timeouts, a + receiver-SMTP MUST seek to minimize the time required to respond to + the final . end of data indicator. See RFC 1047 [28] for + a discussion of this problem. + + + + + + +Klensin Standards Track [Page 62] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +6.2 Loop Detection + + Simple counting of the number of "Received:" headers in a message has + proven to be an effective, although rarely optimal, method of + detecting loops in mail systems. SMTP servers using this technique + SHOULD use a large rejection threshold, normally at least 100 + Received entries. Whatever mechanisms are used, servers MUST contain + provisions for detecting and stopping trivial loops. + +6.3 Compensating for Irregularities + + Unfortunately, variations, creative interpretations, and outright + violations of Internet mail protocols do occur; some would suggest + that they occur quite frequently. The debate as to whether a well- + behaved SMTP receiver or relay should reject a malformed message, + attempt to pass it on unchanged, or attempt to repair it to increase + the odds of successful delivery (or subsequent reply) began almost + with the dawn of structured network mail and shows no signs of + abating. Advocates of rejection claim that attempted repairs are + rarely completely adequate and that rejection of bad messages is the + only way to get the offending software repaired. Advocates of + "repair" or "deliver no matter what" argue that users prefer that + mail go through it if at all possible and that there are significant + market pressures in that direction. In practice, these market + pressures may be more important to particular vendors than strict + conformance to the standards, regardless of the preference of the + actual developers. + + The problems associated with ill-formed messages were exacerbated by + the introduction of the split-UA mail reading protocols [3, 26, 5, + 21]. These protocols have encouraged the use of SMTP as a posting + protocol, and SMTP servers as relay systems for these client hosts + (which are often only intermittently connected to the Internet). + Historically, many of those client machines lacked some of the + mechanisms and information assumed by SMTP (and indeed, by the mail + format protocol [7]). Some could not keep adequate track of time; + others had no concept of time zones; still others could not identify + their own names or addresses; and, of course, none could satisfy the + assumptions that underlay RFC 822's conception of authenticated + addresses. + + In response to these weak SMTP clients, many SMTP systems now + complete messages that are delivered to them in incomplete or + incorrect form. This strategy is generally considered appropriate + when the server can identify or authenticate the client, and there + are prior agreements between them. By contrast, there is at best + great concern about fixes applied by a relay or delivery SMTP server + that has little or no knowledge of the user or client machine. + + + +Klensin Standards Track [Page 63] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + The following changes to a message being processed MAY be applied + when necessary by an originating SMTP server, or one used as the + target of SMTP as an initial posting protocol: + + - Addition of a message-id field when none appears + + - Addition of a date, time or time zone when none appears + + - Correction of addresses to proper FQDN format + + The less information the server has about the client, the less likely + these changes are to be correct and the more caution and conservatism + should be applied when considering whether or not to perform fixes + and how. These changes MUST NOT be applied by an SMTP server that + provides an intermediate relay function. + + In all cases, properly-operating clients supplying correct + information are preferred to corrections by the SMTP server. In all + cases, documentation of actions performed by the servers (in trace + fields and/or header comments) is strongly encouraged. + +7. Security Considerations + +7.1 Mail Security and Spoofing + + SMTP mail is inherently insecure in that it is feasible for even + fairly casual users to negotiate directly with receiving and relaying + SMTP servers and create messages that will trick a naive recipient + into believing that they came from somewhere else. Constructing such + a message so that the "spoofed" behavior cannot be detected by an + expert is somewhat more difficult, but not sufficiently so as to be a + deterrent to someone who is determined and knowledgeable. + Consequently, as knowledge of Internet mail increases, so does the + knowledge that SMTP mail inherently cannot be authenticated, or + integrity checks provided, at the transport level. Real mail + security lies only in end-to-end methods involving the message + bodies, such as those which use digital signatures (see [14] and, + e.g., PGP [4] or S/MIME [31]). + + Various protocol extensions and configuration options that provide + authentication at the transport level (e.g., from an SMTP client to + an SMTP server) improve somewhat on the traditional situation + described above. However, unless they are accompanied by careful + handoffs of responsibility in a carefully-designed trust environment, + they remain inherently weaker than end-to-end mechanisms which use + digitally signed messages rather than depending on the integrity of + the transport system. + + + + +Klensin Standards Track [Page 64] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + Efforts to make it more difficult for users to set envelope return + path and header "From" fields to point to valid addresses other than + their own are largely misguided: they frustrate legitimate + applications in which mail is sent by one user on behalf of another + or in which error (or normal) replies should be directed to a special + address. (Systems that provide convenient ways for users to alter + these fields on a per-message basis should attempt to establish a + primary and permanent mailbox address for the user so that Sender + fields within the message data can be generated sensibly.) + + This specification does not further address the authentication issues + associated with SMTP other than to advocate that useful functionality + not be disabled in the hope of providing some small margin of + protection against an ignorant user who is trying to fake mail. + +7.2 "Blind" Copies + + Addresses that do not appear in the message headers may appear in the + RCPT commands to an SMTP server for a number of reasons. The two + most common involve the use of a mailing address as a "list exploder" + (a single address that resolves into multiple addresses) and the + appearance of "blind copies". Especially when more than one RCPT + command is present, and in order to avoid defeating some of the + purpose of these mechanisms, SMTP clients and servers SHOULD NOT copy + the full set of RCPT command arguments into the headers, either as + part of trace headers or as informational or private-extension + headers. Since this rule is often violated in practice, and cannot + be enforced, sending SMTP systems that are aware of "bcc" use MAY + find it helpful to send each blind copy as a separate message + transaction containing only a single RCPT command. + + There is no inherent relationship between either "reverse" (from + MAIL, SAML, etc., commands) or "forward" (RCPT) addresses in the SMTP + transaction ("envelope") and the addresses in the headers. Receiving + systems SHOULD NOT attempt to deduce such relationships and use them + to alter the headers of the message for delivery. The popular + "Apparently-to" header is a violation of this principle as well as a + common source of unintended information disclosure and SHOULD NOT be + used. + +7.3 VRFY, EXPN, and Security + + As discussed in section 3.5, individual sites may want to disable + either or both of VRFY or EXPN for security reasons. As a corollary + to the above, implementations that permit this MUST NOT appear to + have verified addresses that are not, in fact, verified. If a site + + + + + +Klensin Standards Track [Page 65] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + disables these commands for security reasons, the SMTP server MUST + return a 252 response, rather than a code that could be confused with + successful or unsuccessful verification. + + Returning a 250 reply code with the address listed in the VRFY + command after having checked it only for syntax violates this rule. + Of course, an implementation that "supports" VRFY by always returning + 550 whether or not the address is valid is equally not in + conformance. + + Within the last few years, the contents of mailing lists have become + popular as an address information source for so-called "spammers." + The use of EXPN to "harvest" addresses has increased as list + administrators have installed protections against inappropriate uses + of the lists themselves. Implementations SHOULD still provide + support for EXPN, but sites SHOULD carefully evaluate the tradeoffs. + As authentication mechanisms are introduced into SMTP, some sites may + choose to make EXPN available only to authenticated requestors. + +7.4 Information Disclosure in Announcements + + There has been an ongoing debate about the tradeoffs between the + debugging advantages of announcing server type and version (and, + sometimes, even server domain name) in the greeting response or in + response to the HELP command and the disadvantages of exposing + information that might be useful in a potential hostile attack. The + utility of the debugging information is beyond doubt. Those who + argue for making it available point out that it is far better to + actually secure an SMTP server rather than hope that trying to + conceal known vulnerabilities by hiding the server's precise identity + will provide more protection. Sites are encouraged to evaluate the + tradeoff with that issue in mind; implementations are strongly + encouraged to minimally provide for making type and version + information available in some way to other network hosts. + +7.5 Information Disclosure in Trace Fields + + In some circumstances, such as when mail originates from within a LAN + whose hosts are not directly on the public Internet, trace + ("Received") fields produced in conformance with this specification + may disclose host names and similar information that would not + normally be available. This ordinarily does not pose a problem, but + sites with special concerns about name disclosure should be aware of + it. Also, the optional FOR clause should be supplied with caution or + not at all when multiple recipients are involved lest it + inadvertently disclose the identities of "blind copy" recipients to + others. + + + + +Klensin Standards Track [Page 66] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +7.6 Information Disclosure in Message Forwarding + + As discussed in section 3.4, use of the 251 or 551 reply codes to + identify the replacement address associated with a mailbox may + inadvertently disclose sensitive information. Sites that are + concerned about those issues should ensure that they select and + configure servers appropriately. + +7.7 Scope of Operation of SMTP Servers + + It is a well-established principle that an SMTP server may refuse to + accept mail for any operational or technical reason that makes sense + to the site providing the server. However, cooperation among sites + and installations makes the Internet possible. If sites take + excessive advantage of the right to reject traffic, the ubiquity of + email availability (one of the strengths of the Internet) will be + threatened; considerable care should be taken and balance maintained + if a site decides to be selective about the traffic it will accept + and process. + + In recent years, use of the relay function through arbitrary sites + has been used as part of hostile efforts to hide the actual origins + of mail. Some sites have decided to limit the use of the relay + function to known or identifiable sources, and implementations SHOULD + provide the capability to perform this type of filtering. When mail + is rejected for these or other policy reasons, a 550 code SHOULD be + used in response to EHLO, MAIL, or RCPT as appropriate. + +8. IANA Considerations + + IANA will maintain three registries in support of this specification. + The first consists of SMTP service extensions with the associated + keywords, and, as needed, parameters and verbs. As specified in + section 2.2.2, no entry may be made in this registry that starts in + an "X". Entries may be made only for service extensions (and + associated keywords, parameters, or verbs) that are defined in + standards-track or experimental RFCs specifically approved by the + IESG for this purpose. + + The second registry consists of "tags" that identify forms of domain + literals other than those for IPv4 addresses (specified in RFC 821 + and in this document) and IPv6 addresses (specified in this + document). Additional literal types require standardization before + being used; none are anticipated at this time. + + The third, established by RFC 821 and renewed by this specification, + is a registry of link and protocol identifiers to be used with the + "via" and "with" subclauses of the time stamp ("Received: header") + + + +Klensin Standards Track [Page 67] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + described in section 4.4. Link and protocol identifiers in addition + to those specified in this document may be registered only by + standardization or by way of an RFC-documented, IESG-approved, + Experimental protocol extension. + +9. References + + [1] American National Standards Institute (formerly United States of + America Standards Institute), X3.4, 1968, "USA Code for + Information Interchange". ANSI X3.4-1968 has been replaced by + newer versions with slight modifications, but the 1968 version + remains definitive for the Internet. + + [2] Braden, R., "Requirements for Internet hosts - application and + support", STD 3, RFC 1123, October 1989. + + [3] Butler, M., Chase, D., Goldberger, J., Postel, J. and J. + Reynolds, "Post Office Protocol - version 2", RFC 937, February + 1985. + + [4] Callas, J., Donnerhacke, L., Finney, H. and R. Thayer, "OpenPGP + Message Format", RFC 2440, November 1998. + + [5] Crispin, M., "Interactive Mail Access Protocol - Version 2", RFC + 1176, August 1990. + + [6] Crispin, M., "Internet Message Access Protocol - Version 4", RFC + 2060, December 1996. + + [7] Crocker, D., "Standard for the Format of ARPA Internet Text + Messages", RFC 822, August 1982. + + [8] Crocker, D. and P. Overell, Eds., "Augmented BNF for Syntax + Specifications: ABNF", RFC 2234, November 1997. + + [9] De Winter, J., "SMTP Service Extension for Remote Message Queue + Starting", RFC 1985, August 1996. + + [10] Fajman, R., "An Extensible Message Format for Message + Disposition Notifications", RFC 2298, March 1998. + + [11] Freed, N, "Behavior of and Requirements for Internet Firewalls", + RFC 2979, October 2000. + + [12] Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part One: Format of Internet Message Bodies", + RFC 2045, December 1996. + + + + +Klensin Standards Track [Page 68] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + [13] Freed, N., "SMTP Service Extension for Command Pipelining", RFC + 2920, September 2000. + + [14] Galvin, J., Murphy, S., Crocker, S. and N. Freed, "Security + Multiparts for MIME: Multipart/Signed and Multipart/Encrypted", + RFC 1847, October 1995. + + [15] Gellens, R. and J. Klensin, "Message Submission", RFC 2476, + December 1998. + + [16] Kille, S., "Mapping between X.400 and RFC822/MIME", RFC 2156, + January 1998. + + [17] Hinden, R and S. Deering, Eds. "IP Version 6 Addressing + Architecture", RFC 2373, July 1998. + + [18] Klensin, J., Freed, N. and K. Moore, "SMTP Service Extension for + Message Size Declaration", STD 10, RFC 1870, November 1995. + + [19] Klensin, J., Freed, N., Rose, M., Stefferud, E. and D. Crocker, + "SMTP Service Extensions", STD 10, RFC 1869, November 1995. + + [20] Klensin, J., Freed, N., Rose, M., Stefferud, E. and D. Crocker, + "SMTP Service Extension for 8bit-MIMEtransport", RFC 1652, July + 1994. + + [21] Lambert, M., "PCMAIL: A distributed mail system for personal + computers", RFC 1056, July 1988. + + [22] Mockapetris, P., "Domain names - implementation and + specification", STD 13, RFC 1035, November 1987. + + Mockapetris, P., "Domain names - concepts and facilities", STD + 13, RFC 1034, November 1987. + + [23] Moore, K., "MIME (Multipurpose Internet Mail Extensions) Part + Three: Message Header Extensions for Non-ASCII Text", RFC 2047, + December 1996. + + [24] Moore, K., "SMTP Service Extension for Delivery Status + Notifications", RFC 1891, January 1996. + + [25] Moore, K., and G. Vaudreuil, "An Extensible Message Format for + Delivery Status Notifications", RFC 1894, January 1996. + + [26] Myers, J. and M. Rose, "Post Office Protocol - Version 3", STD + 53, RFC 1939, May 1996. + + + + +Klensin Standards Track [Page 69] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + [27] Partridge, C., "Mail routing and the domain system", RFC 974, + January 1986. + + [28] Partridge, C., "Duplicate messages and SMTP", RFC 1047, February + 1988. + + [29] Postel, J., ed., "Transmission Control Protocol - DARPA Internet + Program Protocol Specification", STD 7, RFC 793, September 1981. + + [30] Postel, J., "Simple Mail Transfer Protocol", RFC 821, August + 1982. + + [31] Ramsdell, B., Ed., "S/MIME Version 3 Message Specification", RFC + 2633, June 1999. + + [32] Resnick, P., Ed., "Internet Message Format", RFC 2822, April + 2001. + + [33] Vaudreuil, G., "SMTP Service Extensions for Transmission of + Large and Binary MIME Messages", RFC 1830, August 1995. + + [34] Vaudreuil, G., "Enhanced Mail System Status Codes", RFC 1893, + January 1996. + +10. Editor's Address + + John C. Klensin + AT&T Laboratories + 99 Bedford St + Boston, MA 02111 USA + + Phone: 617-574-3076 + EMail: klensin@research.att.com + +11. Acknowledgments + + Many people worked long and hard on the many iterations of this + document. There was wide-ranging debate in the IETF DRUMS Working + Group, both on its mailing list and in face to face discussions, + about many technical issues and the role of a revised standard for + Internet mail transport, and many contributors helped form the + wording in this specification. The hundreds of participants in the + many discussions since RFC 821 was produced are too numerous to + mention, but they all helped this document become what it is. + + + + + + + +Klensin Standards Track [Page 70] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +APPENDICES + +A. TCP Transport Service + + The TCP connection supports the transmission of 8-bit bytes. The + SMTP data is 7-bit ASCII characters. Each character is transmitted + as an 8-bit byte with the high-order bit cleared to zero. Service + extensions may modify this rule to permit transmission of full 8-bit + data bytes as part of the message body, but not in SMTP commands or + responses. + +B. Generating SMTP Commands from RFC 822 Headers + + Some systems use RFC 822 headers (only) in a mail submission + protocol, or otherwise generate SMTP commands from RFC 822 headers + when such a message is handed to an MTA from a UA. While the MTA-UA + protocol is a private matter, not covered by any Internet Standard, + there are problems with this approach. For example, there have been + repeated problems with proper handling of "bcc" copies and + redistribution lists when information that conceptually belongs to a + mail envelopes is not separated early in processing from header + information (and kept separate). + + It is recommended that the UA provide its initial ("submission + client") MTA with an envelope separate from the message itself. + However, if the envelope is not supplied, SMTP commands SHOULD be + generated as follows: + + 1. Each recipient address from a TO, CC, or BCC header field SHOULD + be copied to a RCPT command (generating multiple message copies if + that is required for queuing or delivery). This includes any + addresses listed in a RFC 822 "group". Any BCC fields SHOULD then + be removed from the headers. Once this process is completed, the + remaining headers SHOULD be checked to verify that at least one + To:, Cc:, or Bcc: header remains. If none do, then a bcc: header + with no additional information SHOULD be inserted as specified in + [32]. + + 2. The return address in the MAIL command SHOULD, if possible, be + derived from the system's identity for the submitting (local) + user, and the "From:" header field otherwise. If there is a + system identity available, it SHOULD also be copied to the Sender + header field if it is different from the address in the From + header field. (Any Sender field that was already there SHOULD be + removed.) Systems may provide a way for submitters to override + the envelope return address, but may want to restrict its use to + privileged users. This will not prevent mail forgery, but may + lessen its incidence; see section 7.1. + + + +Klensin Standards Track [Page 71] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + When an MTA is being used in this way, it bears responsibility for + ensuring that the message being transmitted is valid. The mechanisms + for checking that validity, and for handling (or returning) messages + that are not valid at the time of arrival, are part of the MUA-MTA + interface and not covered by this specification. + + A submission protocol based on Standard RFC 822 information alone + MUST NOT be used to gateway a message from a foreign (non-SMTP) mail + system into an SMTP environment. Additional information to construct + an envelope must come from some source in the other environment, + whether supplemental headers or the foreign system's envelope. + + Attempts to gateway messages using only their header "to" and "cc" + fields have repeatedly caused mail loops and other behavior adverse + to the proper functioning of the Internet mail environment. These + problems have been especially common when the message originates from + an Internet mailing list and is distributed into the foreign + environment using envelope information. When these messages are then + processed by a header-only remailer, loops back to the Internet + environment (and the mailing list) are almost inevitable. + +C. Source Routes + + Historically, the was a reverse source routing list of + hosts and a source mailbox. The first host in the + SHOULD be the host sending the MAIL command. Similarly, the + may be a source routing lists of hosts and a + destination mailbox. However, in general, the SHOULD + contain only a mailbox and domain name, relying on the domain name + system to supply routing information if required. The use of source + routes is deprecated; while servers MUST be prepared to receive and + handle them as discussed in section 3.3 and F.2, clients SHOULD NOT + transmit them and this section was included only to provide context. + + For relay purposes, the forward-path may be a source route of the + form "@ONE,@TWO:JOE@THREE", where ONE, TWO, and THREE MUST BE fully- + qualified domain names. This form is used to emphasize the + distinction between an address and a route. The mailbox is an + absolute address, and the route is information about how to get + there. The two concepts should not be confused. + + If source routes are used, RFC 821 and the text below should be + consulted for the mechanisms for constructing and updating the + forward- and reverse-paths. + + + + + + + +Klensin Standards Track [Page 72] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + The SMTP server transforms the command arguments by moving its own + identifier (its domain name or that of any domain for which it is + acting as a mail exchanger), if it appears, from the forward-path to + the beginning of the reverse-path. + + Notice that the forward-path and reverse-path appear in the SMTP + commands and replies, but not necessarily in the message. That is, + there is no need for these paths and especially this syntax to appear + in the "To:" , "From:", "CC:", etc. fields of the message header. + Conversely, SMTP servers MUST NOT derive final message delivery + information from message header fields. + + When the list of hosts is present, it is a "reverse" source route and + indicates that the mail was relayed through each host on the list + (the first host in the list was the most recent relay). This list is + used as a source route to return non-delivery notices to the sender. + As each relay host adds itself to the beginning of the list, it MUST + use its name as known in the transport environment to which it is + relaying the mail rather than that of the transport environment from + which the mail came (if they are different). + +D. Scenarios + + This section presents complete scenarios of several types of SMTP + sessions. In the examples, "C:" indicates what is said by the SMTP + client, and "S:" indicates what is said by the SMTP server. + +D.1 A Typical SMTP Transaction Scenario + + This SMTP example shows mail sent by Smith at host bar.com, to Jones, + Green, and Brown at host foo.com. Here we assume that host bar.com + contacts host foo.com directly. The mail is accepted for Jones and + Brown. Green does not have a mailbox at host foo.com. + + S: 220 foo.com Simple Mail Transfer Service Ready + C: EHLO bar.com + S: 250-foo.com greets bar.com + S: 250-8BITMIME + S: 250-SIZE + S: 250-DSN + S: 250 HELP + C: MAIL FROM: + S: 250 OK + C: RCPT TO: + S: 250 OK + C: RCPT TO: + S: 550 No such user here + C: RCPT TO: + + + +Klensin Standards Track [Page 73] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + S: 250 OK + C: DATA + S: 354 Start mail input; end with . + C: Blah blah blah... + C: ...etc. etc. etc. + C: . + S: 250 OK + C: QUIT + S: 221 foo.com Service closing transmission channel + +D.2 Aborted SMTP Transaction Scenario + + S: 220 foo.com Simple Mail Transfer Service Ready + C: EHLO bar.com + S: 250-foo.com greets bar.com + S: 250-8BITMIME + S: 250-SIZE + S: 250-DSN + S: 250 HELP + C: MAIL FROM: + S: 250 OK + C: RCPT TO: + S: 250 OK + C: RCPT TO: + S: 550 No such user here + C: RSET + S: 250 OK + C: QUIT + S: 221 foo.com Service closing transmission channel + +D.3 Relayed Mail Scenario + + Step 1 -- Source Host to Relay Host + + S: 220 foo.com Simple Mail Transfer Service Ready + C: EHLO bar.com + S: 250-foo.com greets bar.com + S: 250-8BITMIME + S: 250-SIZE + S: 250-DSN + S: 250 HELP + C: MAIL FROM: + S: 250 OK + C: RCPT TO:<@foo.com:Jones@XYZ.COM> + S: 250 OK + C: DATA + S: 354 Start mail input; end with . + C: Date: Thu, 21 May 1998 05:33:29 -0700 + + + +Klensin Standards Track [Page 74] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + C: From: John Q. Public + C: Subject: The Next Meeting of the Board + C: To: Jones@xyz.com + C: + C: Bill: + C: The next meeting of the board of directors will be + C: on Tuesday. + C: John. + C: . + S: 250 OK + C: QUIT + S: 221 foo.com Service closing transmission channel + + Step 2 -- Relay Host to Destination Host + + S: 220 xyz.com Simple Mail Transfer Service Ready + C: EHLO foo.com + S: 250 xyz.com is on the air + C: MAIL FROM:<@foo.com:JQP@bar.com> + S: 250 OK + C: RCPT TO: + S: 250 OK + C: DATA + S: 354 Start mail input; end with . + C: Received: from bar.com by foo.com ; Thu, 21 May 1998 + C: 05:33:29 -0700 + C: Date: Thu, 21 May 1998 05:33:22 -0700 + C: From: John Q. Public + C: Subject: The Next Meeting of the Board + C: To: Jones@xyz.com + C: + C: Bill: + C: The next meeting of the board of directors will be + C: on Tuesday. + C: John. + C: . + S: 250 OK + C: QUIT + S: 221 foo.com Service closing transmission channel + +D.4 Verifying and Sending Scenario + + S: 220 foo.com Simple Mail Transfer Service Ready + C: EHLO bar.com + S: 250-foo.com greets bar.com + S: 250-8BITMIME + S: 250-SIZE + S: 250-DSN + + + +Klensin Standards Track [Page 75] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + S: 250-VRFY + S: 250 HELP + C: VRFY Crispin + S: 250 Mark Crispin + C: SEND FROM: + S: 250 OK + C: RCPT TO: + S: 250 OK + C: DATA + S: 354 Start mail input; end with . + C: Blah blah blah... + C: ...etc. etc. etc. + C: . + S: 250 OK + C: QUIT + S: 221 foo.com Service closing transmission channel + +E. Other Gateway Issues + + In general, gateways between the Internet and other mail systems + SHOULD attempt to preserve any layering semantics across the + boundaries between the two mail systems involved. Gateway- + translation approaches that attempt to take shortcuts by mapping, + (such as envelope information from one system to the message headers + or body of another) have generally proven to be inadequate in + important ways. Systems translating between environments that do not + support both envelopes and headers and Internet mail must be written + with the understanding that some information loss is almost + inevitable. + +F. Deprecated Features of RFC 821 + + A few features of RFC 821 have proven to be problematic and SHOULD + NOT be used in Internet mail. + +F.1 TURN + + This command, described in RFC 821, raises important security issues + since, in the absence of strong authentication of the host requesting + that the client and server switch roles, it can easily be used to + divert mail from its correct destination. Its use is deprecated; + SMTP systems SHOULD NOT use it unless the server can authenticate the + client. + + + + + + + + +Klensin Standards Track [Page 76] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +F.2 Source Routing + + RFC 821 utilized the concept of explicit source routing to get mail + from one host to another via a series of relays. The requirement to + utilize source routes in regular mail traffic was eliminated by the + introduction of the domain name system "MX" record and the last + significant justification for them was eliminated by the + introduction, in RFC 1123, of a clear requirement that addresses + following an "@" must all be fully-qualified domain names. + Consequently, the only remaining justifications for the use of source + routes are support for very old SMTP clients or MUAs and in mail + system debugging. They can, however, still be useful in the latter + circumstance and for routing mail around serious, but temporary, + problems such as problems with the relevant DNS records. + + SMTP servers MUST continue to accept source route syntax as specified + in the main body of this document and in RFC 1123. They MAY, if + necessary, ignore the routes and utilize only the target domain in + the address. If they do utilize the source route, the message MUST + be sent to the first domain shown in the address. In particular, a + server MUST NOT guess at shortcuts within the source route. + + Clients SHOULD NOT utilize explicit source routing except under + unusual circumstances, such as debugging or potentially relaying + around firewall or mail system configuration errors. + +F.3 HELO + + As discussed in sections 3.1 and 4.1.1, EHLO is strongly preferred to + HELO when the server will accept the former. Servers must continue + to accept and process HELO in order to support older clients. + +F.4 #-literals + + RFC 821 provided for specifying an Internet address as a decimal + integer host number prefixed by a pound sign, "#". In practice, that + form has been obsolete since the introduction of TCP/IP. It is + deprecated and MUST NOT be used. + +F.5 Dates and Years + + When dates are inserted into messages by SMTP clients or servers + (e.g., in trace fields), four-digit years MUST BE used. Two-digit + years are deprecated; three-digit years were never permitted in the + Internet mail system. + + + + + + +Klensin Standards Track [Page 77] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +F.6 Sending versus Mailing + + In addition to specifying a mechanism for delivering messages to + user's mailboxes, RFC 821 provided additional, optional, commands to + deliver messages directly to the user's terminal screen. These + commands (SEND, SAML, SOML) were rarely implemented, and changes in + workstation technology and the introduction of other protocols may + have rendered them obsolete even where they are implemented. + + Clients SHOULD NOT provide SEND, SAML, or SOML as services. Servers + MAY implement them. If they are implemented by servers, the + implementation model specified in RFC 821 MUST be used and the + command names MUST be published in the response to the EHLO command. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Klensin Standards Track [Page 78] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +Full Copyright Statement + + Copyright (C) The Internet Society (2001). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + + + + + + + + + + + + + +Klensin Standards Track [Page 79] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2822.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2822.txt new file mode 100644 index 0000000..9f698f7 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2822.txt @@ -0,0 +1,2859 @@ + + + + + + +Network Working Group P. Resnick, Editor +Request for Comments: 2822 QUALCOMM Incorporated +Obsoletes: 822 April 2001 +Category: Standards Track + + + Internet Message Format + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2001). All Rights Reserved. + +Abstract + + This standard specifies a syntax for text messages that are sent + between computer users, within the framework of "electronic mail" + messages. This standard supersedes the one specified in Request For + Comments (RFC) 822, "Standard for the Format of ARPA Internet Text + Messages", updating it to reflect current practice and incorporating + incremental changes that were specified in other RFCs. + +Table of Contents + + 1. Introduction ............................................... 3 + 1.1. Scope .................................................... 3 + 1.2. Notational conventions ................................... 4 + 1.2.1. Requirements notation .................................. 4 + 1.2.2. Syntactic notation ..................................... 4 + 1.3. Structure of this document ............................... 4 + 2. Lexical Analysis of Messages ............................... 5 + 2.1. General Description ...................................... 5 + 2.1.1. Line Length Limits ..................................... 6 + 2.2. Header Fields ............................................ 7 + 2.2.1. Unstructured Header Field Bodies ....................... 7 + 2.2.2. Structured Header Field Bodies ......................... 7 + 2.2.3. Long Header Fields ..................................... 7 + 2.3. Body ..................................................... 8 + 3. Syntax ..................................................... 9 + 3.1. Introduction ............................................. 9 + 3.2. Lexical Tokens ........................................... 9 + + + +Resnick Standards Track [Page 1] + +RFC 2822 Internet Message Format April 2001 + + + 3.2.1. Primitive Tokens ....................................... 9 + 3.2.2. Quoted characters ......................................10 + 3.2.3. Folding white space and comments .......................11 + 3.2.4. Atom ...................................................12 + 3.2.5. Quoted strings .........................................13 + 3.2.6. Miscellaneous tokens ...................................13 + 3.3. Date and Time Specification ..............................14 + 3.4. Address Specification ....................................15 + 3.4.1. Addr-spec specification ................................16 + 3.5 Overall message syntax ....................................17 + 3.6. Field definitions ........................................18 + 3.6.1. The origination date field .............................20 + 3.6.2. Originator fields ......................................21 + 3.6.3. Destination address fields .............................22 + 3.6.4. Identification fields ..................................23 + 3.6.5. Informational fields ...................................26 + 3.6.6. Resent fields ..........................................26 + 3.6.7. Trace fields ...........................................28 + 3.6.8. Optional fields ........................................29 + 4. Obsolete Syntax ............................................29 + 4.1. Miscellaneous obsolete tokens ............................30 + 4.2. Obsolete folding white space .............................31 + 4.3. Obsolete Date and Time ...................................31 + 4.4. Obsolete Addressing ......................................33 + 4.5. Obsolete header fields ...................................33 + 4.5.1. Obsolete origination date field ........................34 + 4.5.2. Obsolete originator fields .............................34 + 4.5.3. Obsolete destination address fields ....................34 + 4.5.4. Obsolete identification fields .........................35 + 4.5.5. Obsolete informational fields ..........................35 + 4.5.6. Obsolete resent fields .................................35 + 4.5.7. Obsolete trace fields ..................................36 + 4.5.8. Obsolete optional fields ...............................36 + 5. Security Considerations ....................................36 + 6. Bibliography ...............................................37 + 7. Editor's Address ...........................................38 + 8. Acknowledgements ...........................................39 + Appendix A. Example messages ..................................41 + A.1. Addressing examples ......................................41 + A.1.1. A message from one person to another with simple + addressing .............................................41 + A.1.2. Different types of mailboxes ...........................42 + A.1.3. Group addresses ........................................43 + A.2. Reply messages ...........................................43 + A.3. Resent messages ..........................................44 + A.4. Messages with trace fields ...............................46 + A.5. White space, comments, and other oddities ................47 + A.6. Obsoleted forms ..........................................47 + + + +Resnick Standards Track [Page 2] + +RFC 2822 Internet Message Format April 2001 + + + A.6.1. Obsolete addressing ....................................48 + A.6.2. Obsolete dates .........................................48 + A.6.3. Obsolete white space and comments ......................48 + Appendix B. Differences from earlier standards ................49 + Appendix C. Notices ...........................................50 + Full Copyright Statement ......................................51 + +1. Introduction + +1.1. Scope + + This standard specifies a syntax for text messages that are sent + between computer users, within the framework of "electronic mail" + messages. This standard supersedes the one specified in Request For + Comments (RFC) 822, "Standard for the Format of ARPA Internet Text + Messages" [RFC822], updating it to reflect current practice and + incorporating incremental changes that were specified in other RFCs + [STD3]. + + This standard specifies a syntax only for text messages. In + particular, it makes no provision for the transmission of images, + audio, or other sorts of structured data in electronic mail messages. + There are several extensions published, such as the MIME document + series [RFC2045, RFC2046, RFC2049], which describe mechanisms for the + transmission of such data through electronic mail, either by + extending the syntax provided here or by structuring such messages to + conform to this syntax. Those mechanisms are outside of the scope of + this standard. + + In the context of electronic mail, messages are viewed as having an + envelope and contents. The envelope contains whatever information is + needed to accomplish transmission and delivery. (See [RFC2821] for a + discussion of the envelope.) The contents comprise the object to be + delivered to the recipient. This standard applies only to the format + and some of the semantics of message contents. It contains no + specification of the information in the envelope. + + However, some message systems may use information from the contents + to create the envelope. It is intended that this standard facilitate + the acquisition of such information by programs. + + This specification is intended as a definition of what message + content format is to be passed between systems. Though some message + systems locally store messages in this format (which eliminates the + need for translation between formats) and others use formats that + differ from the one specified in this standard, local storage is + outside of the scope of this standard. + + + + +Resnick Standards Track [Page 3] + +RFC 2822 Internet Message Format April 2001 + + + Note: This standard is not intended to dictate the internal formats + used by sites, the specific message system features that they are + expected to support, or any of the characteristics of user interface + programs that create or read messages. In addition, this standard + does not specify an encoding of the characters for either transport + or storage; that is, it does not specify the number of bits used or + how those bits are specifically transferred over the wire or stored + on disk. + +1.2. Notational conventions + +1.2.1. Requirements notation + + This document occasionally uses terms that appear in capital letters. + When the terms "MUST", "SHOULD", "RECOMMENDED", "MUST NOT", "SHOULD + NOT", and "MAY" appear capitalized, they are being used to indicate + particular requirements of this specification. A discussion of the + meanings of these terms appears in [RFC2119]. + +1.2.2. Syntactic notation + + This standard uses the Augmented Backus-Naur Form (ABNF) notation + specified in [RFC2234] for the formal definitions of the syntax of + messages. Characters will be specified either by a decimal value + (e.g., the value %d65 for uppercase A and %d97 for lowercase A) or by + a case-insensitive literal value enclosed in quotation marks (e.g., + "A" for either uppercase or lowercase A). See [RFC2234] for the full + description of the notation. + +1.3. Structure of this document + + This document is divided into several sections. + + This section, section 1, is a short introduction to the document. + + Section 2 lays out the general description of a message and its + constituent parts. This is an overview to help the reader understand + some of the general principles used in the later portions of this + document. Any examples in this section MUST NOT be taken as + specification of the formal syntax of any part of a message. + + Section 3 specifies formal ABNF rules for the structure of each part + of a message (the syntax) and describes the relationship between + those parts and their meaning in the context of a message (the + semantics). That is, it describes the actual rules for the structure + of each part of a message (the syntax) as well as a description of + the parts and instructions on how they ought to be interpreted (the + semantics). This includes analysis of the syntax and semantics of + + + +Resnick Standards Track [Page 4] + +RFC 2822 Internet Message Format April 2001 + + + subparts of messages that have specific structure. The syntax + included in section 3 represents messages as they MUST be created. + There are also notes in section 3 to indicate if any of the options + specified in the syntax SHOULD be used over any of the others. + + Both sections 2 and 3 describe messages that are legal to generate + for purposes of this standard. + + Section 4 of this document specifies an "obsolete" syntax. There are + references in section 3 to these obsolete syntactic elements. The + rules of the obsolete syntax are elements that have appeared in + earlier revisions of this standard or have previously been widely + used in Internet messages. As such, these elements MUST be + interpreted by parsers of messages in order to be conformant to this + standard. However, since items in this syntax have been determined + to be non-interoperable or to cause significant problems for + recipients of messages, they MUST NOT be generated by creators of + conformant messages. + + Section 5 details security considerations to take into account when + implementing this standard. + + Section 6 is a bibliography of references in this document. + + Section 7 contains the editor's address. + + Section 8 contains acknowledgements. + + Appendix A lists examples of different sorts of messages. These + examples are not exhaustive of the types of messages that appear on + the Internet, but give a broad overview of certain syntactic forms. + + Appendix B lists the differences between this standard and earlier + standards for Internet messages. + + Appendix C has copyright and intellectual property notices. + +2. Lexical Analysis of Messages + +2.1. General Description + + At the most basic level, a message is a series of characters. A + message that is conformant with this standard is comprised of + characters with values in the range 1 through 127 and interpreted as + US-ASCII characters [ASCII]. For brevity, this document sometimes + refers to this range of characters as simply "US-ASCII characters". + + + + + +Resnick Standards Track [Page 5] + +RFC 2822 Internet Message Format April 2001 + + + Note: This standard specifies that messages are made up of characters + in the US-ASCII range of 1 through 127. There are other documents, + specifically the MIME document series [RFC2045, RFC2046, RFC2047, + RFC2048, RFC2049], that extend this standard to allow for values + outside of that range. Discussion of those mechanisms is not within + the scope of this standard. + + Messages are divided into lines of characters. A line is a series of + characters that is delimited with the two characters carriage-return + and line-feed; that is, the carriage return (CR) character (ASCII + value 13) followed immediately by the line feed (LF) character (ASCII + value 10). (The carriage-return/line-feed pair is usually written in + this document as "CRLF".) + + A message consists of header fields (collectively called "the header + of the message") followed, optionally, by a body. The header is a + sequence of lines of characters with special syntax as defined in + this standard. The body is simply a sequence of characters that + follows the header and is separated from the header by an empty line + (i.e., a line with nothing preceding the CRLF). + +2.1.1. Line Length Limits + + There are two limits that this standard places on the number of + characters in a line. Each line of characters MUST be no more than + 998 characters, and SHOULD be no more than 78 characters, excluding + the CRLF. + + The 998 character limit is due to limitations in many implementations + which send, receive, or store Internet Message Format messages that + simply cannot handle more than 998 characters on a line. Receiving + implementations would do well to handle an arbitrarily large number + of characters in a line for robustness sake. However, there are so + many implementations which (in compliance with the transport + requirements of [RFC2821]) do not accept messages containing more + than 1000 character including the CR and LF per line, it is important + for implementations not to create such messages. + + The more conservative 78 character recommendation is to accommodate + the many implementations of user interfaces that display these + messages which may truncate, or disastrously wrap, the display of + more than 78 characters per line, in spite of the fact that such + implementations are non-conformant to the intent of this + specification (and that of [RFC2821] if they actually cause + information to be lost). Again, even though this limitation is put on + messages, it is encumbant upon implementations which display messages + + + + + +Resnick Standards Track [Page 6] + +RFC 2822 Internet Message Format April 2001 + + + to handle an arbitrarily large number of characters in a line + (certainly at least up to the 998 character limit) for the sake of + robustness. + +2.2. Header Fields + + Header fields are lines composed of a field name, followed by a colon + (":"), followed by a field body, and terminated by CRLF. A field + name MUST be composed of printable US-ASCII characters (i.e., + characters that have values between 33 and 126, inclusive), except + colon. A field body may be composed of any US-ASCII characters, + except for CR and LF. However, a field body may contain CRLF when + used in header "folding" and "unfolding" as described in section + 2.2.3. All field bodies MUST conform to the syntax described in + sections 3 and 4 of this standard. + +2.2.1. Unstructured Header Field Bodies + + Some field bodies in this standard are defined simply as + "unstructured" (which is specified below as any US-ASCII characters, + except for CR and LF) with no further restrictions. These are + referred to as unstructured field bodies. Semantically, unstructured + field bodies are simply to be treated as a single line of characters + with no further processing (except for header "folding" and + "unfolding" as described in section 2.2.3). + +2.2.2. Structured Header Field Bodies + + Some field bodies in this standard have specific syntactical + structure more restrictive than the unstructured field bodies + described above. These are referred to as "structured" field bodies. + Structured field bodies are sequences of specific lexical tokens as + described in sections 3 and 4 of this standard. Many of these tokens + are allowed (according to their syntax) to be introduced or end with + comments (as described in section 3.2.3) as well as the space (SP, + ASCII value 32) and horizontal tab (HTAB, ASCII value 9) characters + (together known as the white space characters, WSP), and those WSP + characters are subject to header "folding" and "unfolding" as + described in section 2.2.3. Semantic analysis of structured field + bodies is given along with their syntax. + +2.2.3. Long Header Fields + + Each header field is logically a single line of characters comprising + the field name, the colon, and the field body. For convenience + however, and to deal with the 998/78 character limitations per line, + the field body portion of a header field can be split into a multiple + line representation; this is called "folding". The general rule is + + + +Resnick Standards Track [Page 7] + +RFC 2822 Internet Message Format April 2001 + + + that wherever this standard allows for folding white space (not + simply WSP characters), a CRLF may be inserted before any WSP. For + example, the header field: + + Subject: This is a test + + can be represented as: + + Subject: This + is a test + + Note: Though structured field bodies are defined in such a way that + folding can take place between many of the lexical tokens (and even + within some of the lexical tokens), folding SHOULD be limited to + placing the CRLF at higher-level syntactic breaks. For instance, if + a field body is defined as comma-separated values, it is recommended + that folding occur after the comma separating the structured items in + preference to other places where the field could be folded, even if + it is allowed elsewhere. + + The process of moving from this folded multiple-line representation + of a header field to its single line representation is called + "unfolding". Unfolding is accomplished by simply removing any CRLF + that is immediately followed by WSP. Each header field should be + treated in its unfolded form for further syntactic and semantic + evaluation. + +2.3. Body + + The body of a message is simply lines of US-ASCII characters. The + only two limitations on the body are as follows: + + - CR and LF MUST only occur together as CRLF; they MUST NOT appear + independently in the body. + + - Lines of characters in the body MUST be limited to 998 characters, + and SHOULD be limited to 78 characters, excluding the CRLF. + + Note: As was stated earlier, there are other standards documents, + specifically the MIME documents [RFC2045, RFC2046, RFC2048, RFC2049] + that extend this standard to allow for different sorts of message + bodies. Again, these mechanisms are beyond the scope of this + document. + + + + + + + + +Resnick Standards Track [Page 8] + +RFC 2822 Internet Message Format April 2001 + + +3. Syntax + +3.1. Introduction + + The syntax as given in this section defines the legal syntax of + Internet messages. Messages that are conformant to this standard + MUST conform to the syntax in this section. If there are options in + this section where one option SHOULD be generated, that is indicated + either in the prose or in a comment next to the syntax. + + For the defined expressions, a short description of the syntax and + use is given, followed by the syntax in ABNF, followed by a semantic + analysis. Primitive tokens that are used but otherwise unspecified + come from [RFC2234]. + + In some of the definitions, there will be nonterminals whose names + start with "obs-". These "obs-" elements refer to tokens defined in + the obsolete syntax in section 4. In all cases, these productions + are to be ignored for the purposes of generating legal Internet + messages and MUST NOT be used as part of such a message. However, + when interpreting messages, these tokens MUST be honored as part of + the legal syntax. In this sense, section 3 defines a grammar for + generation of messages, with "obs-" elements that are to be ignored, + while section 4 adds grammar for interpretation of messages. + +3.2. Lexical Tokens + + The following rules are used to define an underlying lexical + analyzer, which feeds tokens to the higher-level parsers. This + section defines the tokens used in structured header field bodies. + + Note: Readers of this standard need to pay special attention to how + these lexical tokens are used in both the lower-level and + higher-level syntax later in the document. Particularly, the white + space tokens and the comment tokens defined in section 3.2.3 get used + in the lower-level tokens defined here, and those lower-level tokens + are in turn used as parts of the higher-level tokens defined later. + Therefore, the white space and comments may be allowed in the + higher-level tokens even though they may not explicitly appear in a + particular definition. + +3.2.1. Primitive Tokens + + The following are primitive tokens referred to elsewhere in this + standard, but not otherwise defined in [RFC2234]. Some of them will + not appear anywhere else in the syntax, but they are convenient to + refer to in other parts of this document. + + + + +Resnick Standards Track [Page 9] + +RFC 2822 Internet Message Format April 2001 + + + Note: The "specials" below are just such an example. Though the + specials token does not appear anywhere else in this standard, it is + useful for implementers who use tools that lexically analyze + messages. Each of the characters in specials can be used to indicate + a tokenization point in lexical analysis. + +NO-WS-CTL = %d1-8 / ; US-ASCII control characters + %d11 / ; that do not include the + %d12 / ; carriage return, line feed, + %d14-31 / ; and white space characters + %d127 + +text = %d1-9 / ; Characters excluding CR and LF + %d11 / + %d12 / + %d14-127 / + obs-text + +specials = "(" / ")" / ; Special characters used in + "<" / ">" / ; other parts of the syntax + "[" / "]" / + ":" / ";" / + "@" / "\" / + "," / "." / + DQUOTE + + No special semantics are attached to these tokens. They are simply + single characters. + +3.2.2. Quoted characters + + Some characters are reserved for special interpretation, such as + delimiting lexical tokens. To permit use of these characters as + uninterpreted data, a quoting mechanism is provided. + +quoted-pair = ("\" text) / obs-qp + + Where any quoted-pair appears, it is to be interpreted as the text + character alone. That is to say, the "\" character that appears as + part of a quoted-pair is semantically "invisible". + + Note: The "\" character may appear in a message where it is not part + of a quoted-pair. A "\" character that does not appear in a + quoted-pair is not semantically invisible. The only places in this + standard where quoted-pair currently appears are ccontent, qcontent, + dcontent, no-fold-quote, and no-fold-literal. + + + + + +Resnick Standards Track [Page 10] + +RFC 2822 Internet Message Format April 2001 + + +3.2.3. Folding white space and comments + + White space characters, including white space used in folding + (described in section 2.2.3), may appear between many elements in + header field bodies. Also, strings of characters that are treated as + comments may be included in structured field bodies as characters + enclosed in parentheses. The following defines the folding white + space (FWS) and comment constructs. + + Strings of characters enclosed in parentheses are considered comments + so long as they do not appear within a "quoted-string", as defined in + section 3.2.5. Comments may nest. + + There are several places in this standard where comments and FWS may + be freely inserted. To accommodate that syntax, an additional token + for "CFWS" is defined for places where comments and/or FWS can occur. + However, where CFWS occurs in this standard, it MUST NOT be inserted + in such a way that any line of a folded header field is made up + entirely of WSP characters and nothing else. + +FWS = ([*WSP CRLF] 1*WSP) / ; Folding white space + obs-FWS + +ctext = NO-WS-CTL / ; Non white space controls + + %d33-39 / ; The rest of the US-ASCII + %d42-91 / ; characters not including "(", + %d93-126 ; ")", or "\" + +ccontent = ctext / quoted-pair / comment + +comment = "(" *([FWS] ccontent) [FWS] ")" + +CFWS = *([FWS] comment) (([FWS] comment) / FWS) + + Throughout this standard, where FWS (the folding white space token) + appears, it indicates a place where header folding, as discussed in + section 2.2.3, may take place. Wherever header folding appears in a + message (that is, a header field body containing a CRLF followed by + any WSP), header unfolding (removal of the CRLF) is performed before + any further lexical analysis is performed on that header field + according to this standard. That is to say, any CRLF that appears in + FWS is semantically "invisible." + + A comment is normally used in a structured field body to provide some + human readable informational text. Since a comment is allowed to + contain FWS, folding is permitted within the comment. Also note that + since quoted-pair is allowed in a comment, the parentheses and + + + +Resnick Standards Track [Page 11] + +RFC 2822 Internet Message Format April 2001 + + + backslash characters may appear in a comment so long as they appear + as a quoted-pair. Semantically, the enclosing parentheses are not + part of the comment; the comment is what is contained between the two + parentheses. As stated earlier, the "\" in any quoted-pair and the + CRLF in any FWS that appears within the comment are semantically + "invisible" and therefore not part of the comment either. + + Runs of FWS, comment or CFWS that occur between lexical tokens in a + structured field header are semantically interpreted as a single + space character. + +3.2.4. Atom + + Several productions in structured header field bodies are simply + strings of certain basic characters. Such productions are called + atoms. + + Some of the structured header field bodies also allow the period + character (".", ASCII value 46) within runs of atext. An additional + "dot-atom" token is defined for those purposes. + +atext = ALPHA / DIGIT / ; Any character except controls, + "!" / "#" / ; SP, and specials. + "$" / "%" / ; Used for atoms + "&" / "'" / + "*" / "+" / + "-" / "/" / + "=" / "?" / + "^" / "_" / + "`" / "{" / + "|" / "}" / + "~" + +atom = [CFWS] 1*atext [CFWS] + +dot-atom = [CFWS] dot-atom-text [CFWS] + +dot-atom-text = 1*atext *("." 1*atext) + + Both atom and dot-atom are interpreted as a single unit, comprised of + the string of characters that make it up. Semantically, the optional + comments and FWS surrounding the rest of the characters are not part + of the atom; the atom is only the run of atext characters in an atom, + or the atext and "." characters in a dot-atom. + + + + + + + +Resnick Standards Track [Page 12] + +RFC 2822 Internet Message Format April 2001 + + +3.2.5. Quoted strings + + Strings of characters that include characters other than those + allowed in atoms may be represented in a quoted string format, where + the characters are surrounded by quote (DQUOTE, ASCII value 34) + characters. + +qtext = NO-WS-CTL / ; Non white space controls + + %d33 / ; The rest of the US-ASCII + %d35-91 / ; characters not including "\" + %d93-126 ; or the quote character + +qcontent = qtext / quoted-pair + +quoted-string = [CFWS] + DQUOTE *([FWS] qcontent) [FWS] DQUOTE + [CFWS] + + A quoted-string is treated as a unit. That is, quoted-string is + identical to atom, semantically. Since a quoted-string is allowed to + contain FWS, folding is permitted. Also note that since quoted-pair + is allowed in a quoted-string, the quote and backslash characters may + appear in a quoted-string so long as they appear as a quoted-pair. + + Semantically, neither the optional CFWS outside of the quote + characters nor the quote characters themselves are part of the + quoted-string; the quoted-string is what is contained between the two + quote characters. As stated earlier, the "\" in any quoted-pair and + the CRLF in any FWS/CFWS that appears within the quoted-string are + semantically "invisible" and therefore not part of the quoted-string + either. + +3.2.6. Miscellaneous tokens + + Three additional tokens are defined, word and phrase for combinations + of atoms and/or quoted-strings, and unstructured for use in + unstructured header fields and in some places within structured + header fields. + +word = atom / quoted-string + +phrase = 1*word / obs-phrase + + + + + + + + +Resnick Standards Track [Page 13] + +RFC 2822 Internet Message Format April 2001 + + +utext = NO-WS-CTL / ; Non white space controls + %d33-126 / ; The rest of US-ASCII + obs-utext + +unstructured = *([FWS] utext) [FWS] + +3.3. Date and Time Specification + + Date and time occur in several header fields. This section specifies + the syntax for a full date and time specification. Though folding + white space is permitted throughout the date-time specification, it + is RECOMMENDED that a single space be used in each place that FWS + appears (whether it is required or optional); some older + implementations may not interpret other occurrences of folding white + space correctly. + +date-time = [ day-of-week "," ] date FWS time [CFWS] + +day-of-week = ([FWS] day-name) / obs-day-of-week + +day-name = "Mon" / "Tue" / "Wed" / "Thu" / + "Fri" / "Sat" / "Sun" + +date = day month year + +year = 4*DIGIT / obs-year + +month = (FWS month-name FWS) / obs-month + +month-name = "Jan" / "Feb" / "Mar" / "Apr" / + "May" / "Jun" / "Jul" / "Aug" / + "Sep" / "Oct" / "Nov" / "Dec" + +day = ([FWS] 1*2DIGIT) / obs-day + +time = time-of-day FWS zone + +time-of-day = hour ":" minute [ ":" second ] + +hour = 2DIGIT / obs-hour + +minute = 2DIGIT / obs-minute + +second = 2DIGIT / obs-second + +zone = (( "+" / "-" ) 4DIGIT) / obs-zone + + + + + +Resnick Standards Track [Page 14] + +RFC 2822 Internet Message Format April 2001 + + + The day is the numeric day of the month. The year is any numeric + year 1900 or later. + + The time-of-day specifies the number of hours, minutes, and + optionally seconds since midnight of the date indicated. + + The date and time-of-day SHOULD express local time. + + The zone specifies the offset from Coordinated Universal Time (UTC, + formerly referred to as "Greenwich Mean Time") that the date and + time-of-day represent. The "+" or "-" indicates whether the + time-of-day is ahead of (i.e., east of) or behind (i.e., west of) + Universal Time. The first two digits indicate the number of hours + difference from Universal Time, and the last two digits indicate the + number of minutes difference from Universal Time. (Hence, +hhmm + means +(hh * 60 + mm) minutes, and -hhmm means -(hh * 60 + mm) + minutes). The form "+0000" SHOULD be used to indicate a time zone at + Universal Time. Though "-0000" also indicates Universal Time, it is + used to indicate that the time was generated on a system that may be + in a local time zone other than Universal Time and therefore + indicates that the date-time contains no information about the local + time zone. + + A date-time specification MUST be semantically valid. That is, the + day-of-the-week (if included) MUST be the day implied by the date, + the numeric day-of-month MUST be between 1 and the number of days + allowed for the specified month (in the specified year), the + time-of-day MUST be in the range 00:00:00 through 23:59:60 (the + number of seconds allowing for a leap second; see [STD12]), and the + zone MUST be within the range -9959 through +9959. + +3.4. Address Specification + + Addresses occur in several message header fields to indicate senders + and recipients of messages. An address may either be an individual + mailbox, or a group of mailboxes. + +address = mailbox / group + +mailbox = name-addr / addr-spec + +name-addr = [display-name] angle-addr + +angle-addr = [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr + +group = display-name ":" [mailbox-list / CFWS] ";" + [CFWS] + + + + +Resnick Standards Track [Page 15] + +RFC 2822 Internet Message Format April 2001 + + +display-name = phrase + +mailbox-list = (mailbox *("," mailbox)) / obs-mbox-list + +address-list = (address *("," address)) / obs-addr-list + + A mailbox receives mail. It is a conceptual entity which does not + necessarily pertain to file storage. For example, some sites may + choose to print mail on a printer and deliver the output to the + addressee's desk. Normally, a mailbox is comprised of two parts: (1) + an optional display name that indicates the name of the recipient + (which could be a person or a system) that could be displayed to the + user of a mail application, and (2) an addr-spec address enclosed in + angle brackets ("<" and ">"). There is also an alternate simple form + of a mailbox where the addr-spec address appears alone, without the + recipient's name or the angle brackets. The Internet addr-spec + address is described in section 3.4.1. + + Note: Some legacy implementations used the simple form where the + addr-spec appears without the angle brackets, but included the name + of the recipient in parentheses as a comment following the addr-spec. + Since the meaning of the information in a comment is unspecified, + implementations SHOULD use the full name-addr form of the mailbox, + instead of the legacy form, to specify the display name associated + with a mailbox. Also, because some legacy implementations interpret + the comment, comments generally SHOULD NOT be used in address fields + to avoid confusing such implementations. + + When it is desirable to treat several mailboxes as a single unit + (i.e., in a distribution list), the group construct can be used. The + group construct allows the sender to indicate a named group of + recipients. This is done by giving a display name for the group, + followed by a colon, followed by a comma separated list of any number + of mailboxes (including zero and one), and ending with a semicolon. + Because the list of mailboxes can be empty, using the group construct + is also a simple way to communicate to recipients that the message + was sent to one or more named sets of recipients, without actually + providing the individual mailbox address for each of those + recipients. + +3.4.1. Addr-spec specification + + An addr-spec is a specific Internet identifier that contains a + locally interpreted string followed by the at-sign character ("@", + ASCII value 64) followed by an Internet domain. The locally + interpreted string is either a quoted-string or a dot-atom. If the + string can be represented as a dot-atom (that is, it contains no + characters other than atext characters or "." surrounded by atext + + + +Resnick Standards Track [Page 16] + +RFC 2822 Internet Message Format April 2001 + + + characters), then the dot-atom form SHOULD be used and the + quoted-string form SHOULD NOT be used. Comments and folding white + space SHOULD NOT be used around the "@" in the addr-spec. + +addr-spec = local-part "@" domain + +local-part = dot-atom / quoted-string / obs-local-part + +domain = dot-atom / domain-literal / obs-domain + +domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS] + +dcontent = dtext / quoted-pair + +dtext = NO-WS-CTL / ; Non white space controls + + %d33-90 / ; The rest of the US-ASCII + %d94-126 ; characters not including "[", + ; "]", or "\" + + The domain portion identifies the point to which the mail is + delivered. In the dot-atom form, this is interpreted as an Internet + domain name (either a host name or a mail exchanger name) as + described in [STD3, STD13, STD14]. In the domain-literal form, the + domain is interpreted as the literal Internet address of the + particular host. In both cases, how addressing is used and how + messages are transported to a particular host is covered in the mail + transport document [RFC2821]. These mechanisms are outside of the + scope of this document. + + The local-part portion is a domain dependent string. In addresses, + it is simply interpreted on the particular host as a name of a + particular mailbox. + +3.5 Overall message syntax + + A message consists of header fields, optionally followed by a message + body. Lines in a message MUST be a maximum of 998 characters + excluding the CRLF, but it is RECOMMENDED that lines be limited to 78 + characters excluding the CRLF. (See section 2.1.1 for explanation.) + In a message body, though all of the characters listed in the text + rule MAY be used, the use of US-ASCII control characters (values 1 + through 8, 11, 12, and 14 through 31) is discouraged since their + interpretation by receivers for display is not guaranteed. + + + + + + + +Resnick Standards Track [Page 17] + +RFC 2822 Internet Message Format April 2001 + + +message = (fields / obs-fields) + [CRLF body] + +body = *(*998text CRLF) *998text + + The header fields carry most of the semantic information and are + defined in section 3.6. The body is simply a series of lines of text + which are uninterpreted for the purposes of this standard. + +3.6. Field definitions + + The header fields of a message are defined here. All header fields + have the same general syntactic structure: A field name, followed by + a colon, followed by the field body. The specific syntax for each + header field is defined in the subsequent sections. + + Note: In the ABNF syntax for each field in subsequent sections, each + field name is followed by the required colon. However, for brevity + sometimes the colon is not referred to in the textual description of + the syntax. It is, nonetheless, required. + + It is important to note that the header fields are not guaranteed to + be in a particular order. They may appear in any order, and they + have been known to be reordered occasionally when transported over + the Internet. However, for the purposes of this standard, header + fields SHOULD NOT be reordered when a message is transported or + transformed. More importantly, the trace header fields and resent + header fields MUST NOT be reordered, and SHOULD be kept in blocks + prepended to the message. See sections 3.6.6 and 3.6.7 for more + information. + + The only required header fields are the origination date field and + the originator address field(s). All other header fields are + syntactically optional. More information is contained in the table + following this definition. + +fields = *(trace + *(resent-date / + resent-from / + resent-sender / + resent-to / + resent-cc / + resent-bcc / + resent-msg-id)) + *(orig-date / + from / + sender / + reply-to / + + + +Resnick Standards Track [Page 18] + +RFC 2822 Internet Message Format April 2001 + + + to / + cc / + bcc / + message-id / + in-reply-to / + references / + subject / + comments / + keywords / + optional-field) + + The following table indicates limits on the number of times each + field may occur in a message header as well as any special + limitations on the use of those fields. An asterisk next to a value + in the minimum or maximum column indicates that a special restriction + appears in the Notes column. + +Field Min number Max number Notes + +trace 0 unlimited Block prepended - see + 3.6.7 + +resent-date 0* unlimited* One per block, required + if other resent fields + present - see 3.6.6 + +resent-from 0 unlimited* One per block - see + 3.6.6 + +resent-sender 0* unlimited* One per block, MUST + occur with multi-address + resent-from - see 3.6.6 + +resent-to 0 unlimited* One per block - see + 3.6.6 + +resent-cc 0 unlimited* One per block - see + 3.6.6 + +resent-bcc 0 unlimited* One per block - see + 3.6.6 + +resent-msg-id 0 unlimited* One per block - see + 3.6.6 + +orig-date 1 1 + +from 1 1 See sender and 3.6.2 + + + +Resnick Standards Track [Page 19] + +RFC 2822 Internet Message Format April 2001 + + +sender 0* 1 MUST occur with multi- + address from - see 3.6.2 + +reply-to 0 1 + +to 0 1 + +cc 0 1 + +bcc 0 1 + +message-id 0* 1 SHOULD be present - see + 3.6.4 + +in-reply-to 0* 1 SHOULD occur in some + replies - see 3.6.4 + +references 0* 1 SHOULD occur in some + replies - see 3.6.4 + +subject 0 1 + +comments 0 unlimited + +keywords 0 unlimited + +optional-field 0 unlimited + + The exact interpretation of each field is described in subsequent + sections. + +3.6.1. The origination date field + + The origination date field consists of the field name "Date" followed + by a date-time specification. + +orig-date = "Date:" date-time CRLF + + The origination date specifies the date and time at which the creator + of the message indicated that the message was complete and ready to + enter the mail delivery system. For instance, this might be the time + that a user pushes the "send" or "submit" button in an application + program. In any case, it is specifically not intended to convey the + time that the message is actually transported, but rather the time at + which the human or other creator of the message has put the message + into its final form, ready for transport. (For example, a portable + computer user who is not connected to a network might queue a message + + + + +Resnick Standards Track [Page 20] + +RFC 2822 Internet Message Format April 2001 + + + for delivery. The origination date is intended to contain the date + and time that the user queued the message, not the time when the user + connected to the network to send the message.) + +3.6.2. Originator fields + + The originator fields of a message consist of the from field, the + sender field (when applicable), and optionally the reply-to field. + The from field consists of the field name "From" and a + comma-separated list of one or more mailbox specifications. If the + from field contains more than one mailbox specification in the + mailbox-list, then the sender field, containing the field name + "Sender" and a single mailbox specification, MUST appear in the + message. In either case, an optional reply-to field MAY also be + included, which contains the field name "Reply-To" and a + comma-separated list of one or more addresses. + +from = "From:" mailbox-list CRLF + +sender = "Sender:" mailbox CRLF + +reply-to = "Reply-To:" address-list CRLF + + The originator fields indicate the mailbox(es) of the source of the + message. The "From:" field specifies the author(s) of the message, + that is, the mailbox(es) of the person(s) or system(s) responsible + for the writing of the message. The "Sender:" field specifies the + mailbox of the agent responsible for the actual transmission of the + message. For example, if a secretary were to send a message for + another person, the mailbox of the secretary would appear in the + "Sender:" field and the mailbox of the actual author would appear in + the "From:" field. If the originator of the message can be indicated + by a single mailbox and the author and transmitter are identical, the + "Sender:" field SHOULD NOT be used. Otherwise, both fields SHOULD + appear. + + The originator fields also provide the information required when + replying to a message. When the "Reply-To:" field is present, it + indicates the mailbox(es) to which the author of the message suggests + that replies be sent. In the absence of the "Reply-To:" field, + replies SHOULD by default be sent to the mailbox(es) specified in the + "From:" field unless otherwise specified by the person composing the + reply. + + In all cases, the "From:" field SHOULD NOT contain any mailbox that + does not belong to the author(s) of the message. See also section + 3.6.3 for more information on forming the destination addresses for a + reply. + + + +Resnick Standards Track [Page 21] + +RFC 2822 Internet Message Format April 2001 + + +3.6.3. Destination address fields + + The destination fields of a message consist of three possible fields, + each of the same form: The field name, which is either "To", "Cc", or + "Bcc", followed by a comma-separated list of one or more addresses + (either mailbox or group syntax). + +to = "To:" address-list CRLF + +cc = "Cc:" address-list CRLF + +bcc = "Bcc:" (address-list / [CFWS]) CRLF + + The destination fields specify the recipients of the message. Each + destination field may have one or more addresses, and each of the + addresses indicate the intended recipients of the message. The only + difference between the three fields is how each is used. + + The "To:" field contains the address(es) of the primary recipient(s) + of the message. + + The "Cc:" field (where the "Cc" means "Carbon Copy" in the sense of + making a copy on a typewriter using carbon paper) contains the + addresses of others who are to receive the message, though the + content of the message may not be directed at them. + + The "Bcc:" field (where the "Bcc" means "Blind Carbon Copy") contains + addresses of recipients of the message whose addresses are not to be + revealed to other recipients of the message. There are three ways in + which the "Bcc:" field is used. In the first case, when a message + containing a "Bcc:" field is prepared to be sent, the "Bcc:" line is + removed even though all of the recipients (including those specified + in the "Bcc:" field) are sent a copy of the message. In the second + case, recipients specified in the "To:" and "Cc:" lines each are sent + a copy of the message with the "Bcc:" line removed as above, but the + recipients on the "Bcc:" line get a separate copy of the message + containing a "Bcc:" line. (When there are multiple recipient + addresses in the "Bcc:" field, some implementations actually send a + separate copy of the message to each recipient with a "Bcc:" + containing only the address of that particular recipient.) Finally, + since a "Bcc:" field may contain no addresses, a "Bcc:" field can be + sent without any addresses indicating to the recipients that blind + copies were sent to someone. Which method to use with "Bcc:" fields + is implementation dependent, but refer to the "Security + Considerations" section of this document for a discussion of each. + + + + + + +Resnick Standards Track [Page 22] + +RFC 2822 Internet Message Format April 2001 + + + When a message is a reply to another message, the mailboxes of the + authors of the original message (the mailboxes in the "From:" field) + or mailboxes specified in the "Reply-To:" field (if it exists) MAY + appear in the "To:" field of the reply since these would normally be + the primary recipients of the reply. If a reply is sent to a message + that has destination fields, it is often desirable to send a copy of + the reply to all of the recipients of the message, in addition to the + author. When such a reply is formed, addresses in the "To:" and + "Cc:" fields of the original message MAY appear in the "Cc:" field of + the reply, since these are normally secondary recipients of the + reply. If a "Bcc:" field is present in the original message, + addresses in that field MAY appear in the "Bcc:" field of the reply, + but SHOULD NOT appear in the "To:" or "Cc:" fields. + + Note: Some mail applications have automatic reply commands that + include the destination addresses of the original message in the + destination addresses of the reply. How those reply commands behave + is implementation dependent and is beyond the scope of this document. + In particular, whether or not to include the original destination + addresses when the original message had a "Reply-To:" field is not + addressed here. + +3.6.4. Identification fields + + Though optional, every message SHOULD have a "Message-ID:" field. + Furthermore, reply messages SHOULD have "In-Reply-To:" and + "References:" fields as appropriate, as described below. + + The "Message-ID:" field contains a single unique message identifier. + The "References:" and "In-Reply-To:" field each contain one or more + unique message identifiers, optionally separated by CFWS. + + The message identifier (msg-id) is similar in syntax to an angle-addr + construct without the internal CFWS. + +message-id = "Message-ID:" msg-id CRLF + +in-reply-to = "In-Reply-To:" 1*msg-id CRLF + +references = "References:" 1*msg-id CRLF + +msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS] + +id-left = dot-atom-text / no-fold-quote / obs-id-left + +id-right = dot-atom-text / no-fold-literal / obs-id-right + +no-fold-quote = DQUOTE *(qtext / quoted-pair) DQUOTE + + + +Resnick Standards Track [Page 23] + +RFC 2822 Internet Message Format April 2001 + + +no-fold-literal = "[" *(dtext / quoted-pair) "]" + + The "Message-ID:" field provides a unique message identifier that + refers to a particular version of a particular message. The + uniqueness of the message identifier is guaranteed by the host that + generates it (see below). This message identifier is intended to be + machine readable and not necessarily meaningful to humans. A message + identifier pertains to exactly one instantiation of a particular + message; subsequent revisions to the message each receive new message + identifiers. + + Note: There are many instances when messages are "changed", but those + changes do not constitute a new instantiation of that message, and + therefore the message would not get a new message identifier. For + example, when messages are introduced into the transport system, they + are often prepended with additional header fields such as trace + fields (described in section 3.6.7) and resent fields (described in + section 3.6.6). The addition of such header fields does not change + the identity of the message and therefore the original "Message-ID:" + field is retained. In all cases, it is the meaning that the sender + of the message wishes to convey (i.e., whether this is the same + message or a different message) that determines whether or not the + "Message-ID:" field changes, not any particular syntactic difference + that appears (or does not appear) in the message. + + The "In-Reply-To:" and "References:" fields are used when creating a + reply to a message. They hold the message identifier of the original + message and the message identifiers of other messages (for example, + in the case of a reply to a message which was itself a reply). The + "In-Reply-To:" field may be used to identify the message (or + messages) to which the new message is a reply, while the + "References:" field may be used to identify a "thread" of + conversation. + + When creating a reply to a message, the "In-Reply-To:" and + "References:" fields of the resultant message are constructed as + follows: + + The "In-Reply-To:" field will contain the contents of the "Message- + ID:" field of the message to which this one is a reply (the "parent + message"). If there is more than one parent message, then the "In- + Reply-To:" field will contain the contents of all of the parents' + "Message-ID:" fields. If there is no "Message-ID:" field in any of + the parent messages, then the new message will have no "In-Reply-To:" + field. + + + + + + +Resnick Standards Track [Page 24] + +RFC 2822 Internet Message Format April 2001 + + + The "References:" field will contain the contents of the parent's + "References:" field (if any) followed by the contents of the parent's + "Message-ID:" field (if any). If the parent message does not contain + a "References:" field but does have an "In-Reply-To:" field + containing a single message identifier, then the "References:" field + will contain the contents of the parent's "In-Reply-To:" field + followed by the contents of the parent's "Message-ID:" field (if + any). If the parent has none of the "References:", "In-Reply-To:", + or "Message-ID:" fields, then the new message will have no + "References:" field. + + Note: Some implementations parse the "References:" field to display + the "thread of the discussion". These implementations assume that + each new message is a reply to a single parent and hence that they + can walk backwards through the "References:" field to find the parent + of each message listed there. Therefore, trying to form a + "References:" field for a reply that has multiple parents is + discouraged and how to do so is not defined in this document. + + The message identifier (msg-id) itself MUST be a globally unique + identifier for a message. The generator of the message identifier + MUST guarantee that the msg-id is unique. There are several + algorithms that can be used to accomplish this. Since the msg-id has + a similar syntax to angle-addr (identical except that comments and + folding white space are not allowed), a good method is to put the + domain name (or a domain literal IP address) of the host on which the + message identifier was created on the right hand side of the "@", and + put a combination of the current absolute date and time along with + some other currently unique (perhaps sequential) identifier available + on the system (for example, a process id number) on the left hand + side. Using a date on the left hand side and a domain name or domain + literal on the right hand side makes it possible to guarantee + uniqueness since no two hosts use the same domain name or IP address + at the same time. Though other algorithms will work, it is + RECOMMENDED that the right hand side contain some domain identifier + (either of the host itself or otherwise) such that the generator of + the message identifier can guarantee the uniqueness of the left hand + side within the scope of that domain. + + Semantically, the angle bracket characters are not part of the + msg-id; the msg-id is what is contained between the two angle bracket + characters. + + + + + + + + + +Resnick Standards Track [Page 25] + +RFC 2822 Internet Message Format April 2001 + + +3.6.5. Informational fields + + The informational fields are all optional. The "Keywords:" field + contains a comma-separated list of one or more words or + quoted-strings. The "Subject:" and "Comments:" fields are + unstructured fields as defined in section 2.2.1, and therefore may + contain text or folding white space. + +subject = "Subject:" unstructured CRLF + +comments = "Comments:" unstructured CRLF + +keywords = "Keywords:" phrase *("," phrase) CRLF + + These three fields are intended to have only human-readable content + with information about the message. The "Subject:" field is the most + common and contains a short string identifying the topic of the + message. When used in a reply, the field body MAY start with the + string "Re: " (from the Latin "res", in the matter of) followed by + the contents of the "Subject:" field body of the original message. + If this is done, only one instance of the literal string "Re: " ought + to be used since use of other strings or more than one instance can + lead to undesirable consequences. The "Comments:" field contains any + additional comments on the text of the body of the message. The + "Keywords:" field contains a comma-separated list of important words + and phrases that might be useful for the recipient. + +3.6.6. Resent fields + + Resent fields SHOULD be added to any message that is reintroduced by + a user into the transport system. A separate set of resent fields + SHOULD be added each time this is done. All of the resent fields + corresponding to a particular resending of the message SHOULD be + together. Each new set of resent fields is prepended to the message; + that is, the most recent set of resent fields appear earlier in the + message. No other fields in the message are changed when resent + fields are added. + + Each of the resent fields corresponds to a particular field elsewhere + in the syntax. For instance, the "Resent-Date:" field corresponds to + the "Date:" field and the "Resent-To:" field corresponds to the "To:" + field. In each case, the syntax for the field body is identical to + the syntax given previously for the corresponding field. + + When resent fields are used, the "Resent-From:" and "Resent-Date:" + fields MUST be sent. The "Resent-Message-ID:" field SHOULD be sent. + "Resent-Sender:" SHOULD NOT be used if "Resent-Sender:" would be + identical to "Resent-From:". + + + +Resnick Standards Track [Page 26] + +RFC 2822 Internet Message Format April 2001 + + +resent-date = "Resent-Date:" date-time CRLF + +resent-from = "Resent-From:" mailbox-list CRLF + +resent-sender = "Resent-Sender:" mailbox CRLF + +resent-to = "Resent-To:" address-list CRLF + +resent-cc = "Resent-Cc:" address-list CRLF + +resent-bcc = "Resent-Bcc:" (address-list / [CFWS]) CRLF + +resent-msg-id = "Resent-Message-ID:" msg-id CRLF + + Resent fields are used to identify a message as having been + reintroduced into the transport system by a user. The purpose of + using resent fields is to have the message appear to the final + recipient as if it were sent directly by the original sender, with + all of the original fields remaining the same. Each set of resent + fields correspond to a particular resending event. That is, if a + message is resent multiple times, each set of resent fields gives + identifying information for each individual time. Resent fields are + strictly informational. They MUST NOT be used in the normal + processing of replies or other such automatic actions on messages. + + Note: Reintroducing a message into the transport system and using + resent fields is a different operation from "forwarding". + "Forwarding" has two meanings: One sense of forwarding is that a mail + reading program can be told by a user to forward a copy of a message + to another person, making the forwarded message the body of the new + message. A forwarded message in this sense does not appear to have + come from the original sender, but is an entirely new message from + the forwarder of the message. On the other hand, forwarding is also + used to mean when a mail transport program gets a message and + forwards it on to a different destination for final delivery. Resent + header fields are not intended for use with either type of + forwarding. + + The resent originator fields indicate the mailbox of the person(s) or + system(s) that resent the message. As with the regular originator + fields, there are two forms: a simple "Resent-From:" form which + contains the mailbox of the individual doing the resending, and the + more complex form, when one individual (identified in the + "Resent-Sender:" field) resends a message on behalf of one or more + others (identified in the "Resent-From:" field). + + Note: When replying to a resent message, replies behave just as they + would with any other message, using the original "From:", + + + +Resnick Standards Track [Page 27] + +RFC 2822 Internet Message Format April 2001 + + + "Reply-To:", "Message-ID:", and other fields. The resent fields are + only informational and MUST NOT be used in the normal processing of + replies. + + The "Resent-Date:" indicates the date and time at which the resent + message is dispatched by the resender of the message. Like the + "Date:" field, it is not the date and time that the message was + actually transported. + + The "Resent-To:", "Resent-Cc:", and "Resent-Bcc:" fields function + identically to the "To:", "Cc:", and "Bcc:" fields respectively, + except that they indicate the recipients of the resent message, not + the recipients of the original message. + + The "Resent-Message-ID:" field provides a unique identifier for the + resent message. + +3.6.7. Trace fields + + The trace fields are a group of header fields consisting of an + optional "Return-Path:" field, and one or more "Received:" fields. + The "Return-Path:" header field contains a pair of angle brackets + that enclose an optional addr-spec. The "Received:" field contains a + (possibly empty) list of name/value pairs followed by a semicolon and + a date-time specification. The first item of the name/value pair is + defined by item-name, and the second item is either an addr-spec, an + atom, a domain, or a msg-id. Further restrictions may be applied to + the syntax of the trace fields by standards that provide for their + use, such as [RFC2821]. + +trace = [return] + 1*received + +return = "Return-Path:" path CRLF + +path = ([CFWS] "<" ([CFWS] / addr-spec) ">" [CFWS]) / + obs-path + +received = "Received:" name-val-list ";" date-time CRLF + +name-val-list = [CFWS] [name-val-pair *(CFWS name-val-pair)] + +name-val-pair = item-name CFWS item-value + +item-name = ALPHA *(["-"] (ALPHA / DIGIT)) + +item-value = 1*angle-addr / addr-spec / + atom / domain / msg-id + + + +Resnick Standards Track [Page 28] + +RFC 2822 Internet Message Format April 2001 + + + A full discussion of the Internet mail use of trace fields is + contained in [RFC2821]. For the purposes of this standard, the trace + fields are strictly informational, and any formal interpretation of + them is outside of the scope of this document. + +3.6.8. Optional fields + + Fields may appear in messages that are otherwise unspecified in this + standard. They MUST conform to the syntax of an optional-field. + This is a field name, made up of the printable US-ASCII characters + except SP and colon, followed by a colon, followed by any text which + conforms to unstructured. + + The field names of any optional-field MUST NOT be identical to any + field name specified elsewhere in this standard. + +optional-field = field-name ":" unstructured CRLF + +field-name = 1*ftext + +ftext = %d33-57 / ; Any character except + %d59-126 ; controls, SP, and + ; ":". + + For the purposes of this standard, any optional field is + uninterpreted. + +4. Obsolete Syntax + + Earlier versions of this standard allowed for different (usually more + liberal) syntax than is allowed in this version. Also, there have + been syntactic elements used in messages on the Internet whose + interpretation have never been documented. Though some of these + syntactic forms MUST NOT be generated according to the grammar in + section 3, they MUST be accepted and parsed by a conformant receiver. + This section documents many of these syntactic elements. Taking the + grammar in section 3 and adding the definitions presented in this + section will result in the grammar to use for interpretation of + messages. + + Note: This section identifies syntactic forms that any implementation + MUST reasonably interpret. However, there are certainly Internet + messages which do not conform to even the additional syntax given in + this section. The fact that a particular form does not appear in any + section of this document is not justification for computer programs + to crash or for malformed data to be irretrievably lost by any + implementation. To repeat an example, though this document requires + lines in messages to be no longer than 998 characters, silently + + + +Resnick Standards Track [Page 29] + +RFC 2822 Internet Message Format April 2001 + + + discarding the 999th and subsequent characters in a line without + warning would still be bad behavior for an implementation. It is up + to the implementation to deal with messages robustly. + + One important difference between the obsolete (interpreting) and the + current (generating) syntax is that in structured header field bodies + (i.e., between the colon and the CRLF of any structured header + field), white space characters, including folding white space, and + comments can be freely inserted between any syntactic tokens. This + allows many complex forms that have proven difficult for some + implementations to parse. + + Another key difference between the obsolete and the current syntax is + that the rule in section 3.2.3 regarding lines composed entirely of + white space in comments and folding white space does not apply. See + the discussion of folding white space in section 4.2 below. + + Finally, certain characters that were formerly allowed in messages + appear in this section. The NUL character (ASCII value 0) was once + allowed, but is no longer for compatibility reasons. CR and LF were + allowed to appear in messages other than as CRLF; this use is also + shown here. + + Other differences in syntax and semantics are noted in the following + sections. + +4.1. Miscellaneous obsolete tokens + + These syntactic elements are used elsewhere in the obsolete syntax or + in the main syntax. The obs-char and obs-qp elements each add ASCII + value 0. Bare CR and bare LF are added to obs-text and obs-utext. + The period character is added to obs-phrase. The obs-phrase-list + provides for "empty" elements in a comma-separated list of phrases. + + Note: The "period" (or "full stop") character (".") in obs-phrase is + not a form that was allowed in earlier versions of this or any other + standard. Period (nor any other character from specials) was not + allowed in phrase because it introduced a parsing difficulty + distinguishing between phrases and portions of an addr-spec (see + section 4.4). It appears here because the period character is + currently used in many messages in the display-name portion of + addresses, especially for initials in names, and therefore must be + interpreted properly. In the future, period may appear in the + regular syntax of phrase. + +obs-qp = "\" (%d0-127) + +obs-text = *LF *CR *(obs-char *LF *CR) + + + +Resnick Standards Track [Page 30] + +RFC 2822 Internet Message Format April 2001 + + +obs-char = %d0-9 / %d11 / ; %d0-127 except CR and + %d12 / %d14-127 ; LF + +obs-utext = obs-text + +obs-phrase = word *(word / "." / CFWS) + +obs-phrase-list = phrase / 1*([phrase] [CFWS] "," [CFWS]) [phrase] + + Bare CR and bare LF appear in messages with two different meanings. + In many cases, bare CR or bare LF are used improperly instead of CRLF + to indicate line separators. In other cases, bare CR and bare LF are + used simply as ASCII control characters with their traditional ASCII + meanings. + +4.2. Obsolete folding white space + + In the obsolete syntax, any amount of folding white space MAY be + inserted where the obs-FWS rule is allowed. This creates the + possibility of having two consecutive "folds" in a line, and + therefore the possibility that a line which makes up a folded header + field could be composed entirely of white space. + + obs-FWS = 1*WSP *(CRLF 1*WSP) + +4.3. Obsolete Date and Time + + The syntax for the obsolete date format allows a 2 digit year in the + date field and allows for a list of alphabetic time zone + specifications that were used in earlier versions of this standard. + It also permits comments and folding white space between many of the + tokens. + +obs-day-of-week = [CFWS] day-name [CFWS] + +obs-year = [CFWS] 2*DIGIT [CFWS] + +obs-month = CFWS month-name CFWS + +obs-day = [CFWS] 1*2DIGIT [CFWS] + +obs-hour = [CFWS] 2DIGIT [CFWS] + +obs-minute = [CFWS] 2DIGIT [CFWS] + +obs-second = [CFWS] 2DIGIT [CFWS] + +obs-zone = "UT" / "GMT" / ; Universal Time + + + +Resnick Standards Track [Page 31] + +RFC 2822 Internet Message Format April 2001 + + + ; North American UT + ; offsets + "EST" / "EDT" / ; Eastern: - 5/ - 4 + "CST" / "CDT" / ; Central: - 6/ - 5 + "MST" / "MDT" / ; Mountain: - 7/ - 6 + "PST" / "PDT" / ; Pacific: - 8/ - 7 + + %d65-73 / ; Military zones - "A" + %d75-90 / ; through "I" and "K" + %d97-105 / ; through "Z", both + %d107-122 ; upper and lower case + + Where a two or three digit year occurs in a date, the year is to be + interpreted as follows: If a two digit year is encountered whose + value is between 00 and 49, the year is interpreted by adding 2000, + ending up with a value between 2000 and 2049. If a two digit year is + encountered with a value between 50 and 99, or any three digit year + is encountered, the year is interpreted by adding 1900. + + In the obsolete time zone, "UT" and "GMT" are indications of + "Universal Time" and "Greenwich Mean Time" respectively and are both + semantically identical to "+0000". + + The remaining three character zones are the US time zones. The first + letter, "E", "C", "M", or "P" stands for "Eastern", "Central", + "Mountain" and "Pacific". The second letter is either "S" for + "Standard" time, or "D" for "Daylight" (or summer) time. Their + interpretations are as follows: + + EDT is semantically equivalent to -0400 + EST is semantically equivalent to -0500 + CDT is semantically equivalent to -0500 + CST is semantically equivalent to -0600 + MDT is semantically equivalent to -0600 + MST is semantically equivalent to -0700 + PDT is semantically equivalent to -0700 + PST is semantically equivalent to -0800 + + The 1 character military time zones were defined in a non-standard + way in [RFC822] and are therefore unpredictable in their meaning. + The original definitions of the military zones "A" through "I" are + equivalent to "+0100" through "+0900" respectively; "K", "L", and "M" + are equivalent to "+1000", "+1100", and "+1200" respectively; "N" + through "Y" are equivalent to "-0100" through "-1200" respectively; + and "Z" is equivalent to "+0000". However, because of the error in + [RFC822], they SHOULD all be considered equivalent to "-0000" unless + there is out-of-band information confirming their meaning. + + + + +Resnick Standards Track [Page 32] + +RFC 2822 Internet Message Format April 2001 + + + Other multi-character (usually between 3 and 5) alphabetic time zones + have been used in Internet messages. Any such time zone whose + meaning is not known SHOULD be considered equivalent to "-0000" + unless there is out-of-band information confirming their meaning. + +4.4. Obsolete Addressing + + There are three primary differences in addressing. First, mailbox + addresses were allowed to have a route portion before the addr-spec + when enclosed in "<" and ">". The route is simply a comma-separated + list of domain names, each preceded by "@", and the list terminated + by a colon. Second, CFWS were allowed between the period-separated + elements of local-part and domain (i.e., dot-atom was not used). In + addition, local-part is allowed to contain quoted-string in addition + to just atom. Finally, mailbox-list and address-list were allowed to + have "null" members. That is, there could be two or more commas in + such a list with nothing in between them. + +obs-angle-addr = [CFWS] "<" [obs-route] addr-spec ">" [CFWS] + +obs-route = [CFWS] obs-domain-list ":" [CFWS] + +obs-domain-list = "@" domain *(*(CFWS / "," ) [CFWS] "@" domain) + +obs-local-part = word *("." word) + +obs-domain = atom *("." atom) + +obs-mbox-list = 1*([mailbox] [CFWS] "," [CFWS]) [mailbox] + +obs-addr-list = 1*([address] [CFWS] "," [CFWS]) [address] + + When interpreting addresses, the route portion SHOULD be ignored. + +4.5. Obsolete header fields + + Syntactically, the primary difference in the obsolete field syntax is + that it allows multiple occurrences of any of the fields and they may + occur in any order. Also, any amount of white space is allowed + before the ":" at the end of the field name. + +obs-fields = *(obs-return / + obs-received / + obs-orig-date / + obs-from / + obs-sender / + obs-reply-to / + obs-to / + + + +Resnick Standards Track [Page 33] + +RFC 2822 Internet Message Format April 2001 + + + obs-cc / + obs-bcc / + obs-message-id / + obs-in-reply-to / + obs-references / + obs-subject / + obs-comments / + obs-keywords / + obs-resent-date / + obs-resent-from / + obs-resent-send / + obs-resent-rply / + obs-resent-to / + obs-resent-cc / + obs-resent-bcc / + obs-resent-mid / + obs-optional) + + Except for destination address fields (described in section 4.5.3), + the interpretation of multiple occurrences of fields is unspecified. + Also, the interpretation of trace fields and resent fields which do + not occur in blocks prepended to the message is unspecified as well. + Unless otherwise noted in the following sections, interpretation of + other fields is identical to the interpretation of their non-obsolete + counterparts in section 3. + +4.5.1. Obsolete origination date field + +obs-orig-date = "Date" *WSP ":" date-time CRLF + +4.5.2. Obsolete originator fields + +obs-from = "From" *WSP ":" mailbox-list CRLF + +obs-sender = "Sender" *WSP ":" mailbox CRLF + +obs-reply-to = "Reply-To" *WSP ":" mailbox-list CRLF + +4.5.3. Obsolete destination address fields + +obs-to = "To" *WSP ":" address-list CRLF + +obs-cc = "Cc" *WSP ":" address-list CRLF + +obs-bcc = "Bcc" *WSP ":" (address-list / [CFWS]) CRLF + + + + + + +Resnick Standards Track [Page 34] + +RFC 2822 Internet Message Format April 2001 + + + When multiple occurrences of destination address fields occur in a + message, they SHOULD be treated as if the address-list in the first + occurrence of the field is combined with the address lists of the + subsequent occurrences by adding a comma and concatenating. + +4.5.4. Obsolete identification fields + + The obsolete "In-Reply-To:" and "References:" fields differ from the + current syntax in that they allow phrase (words or quoted strings) to + appear. The obsolete forms of the left and right sides of msg-id + allow interspersed CFWS, making them syntactically identical to + local-part and domain respectively. + +obs-message-id = "Message-ID" *WSP ":" msg-id CRLF + +obs-in-reply-to = "In-Reply-To" *WSP ":" *(phrase / msg-id) CRLF + +obs-references = "References" *WSP ":" *(phrase / msg-id) CRLF + +obs-id-left = local-part + +obs-id-right = domain + + For purposes of interpretation, the phrases in the "In-Reply-To:" and + "References:" fields are ignored. + + Semantically, none of the optional CFWS surrounding the local-part + and the domain are part of the obs-id-left and obs-id-right + respectively. + +4.5.5. Obsolete informational fields + +obs-subject = "Subject" *WSP ":" unstructured CRLF + +obs-comments = "Comments" *WSP ":" unstructured CRLF + +obs-keywords = "Keywords" *WSP ":" obs-phrase-list CRLF + +4.5.6. Obsolete resent fields + + The obsolete syntax adds a "Resent-Reply-To:" field, which consists + of the field name, the optional comments and folding white space, the + colon, and a comma separated list of addresses. + +obs-resent-from = "Resent-From" *WSP ":" mailbox-list CRLF + +obs-resent-send = "Resent-Sender" *WSP ":" mailbox CRLF + + + + +Resnick Standards Track [Page 35] + +RFC 2822 Internet Message Format April 2001 + + +obs-resent-date = "Resent-Date" *WSP ":" date-time CRLF + +obs-resent-to = "Resent-To" *WSP ":" address-list CRLF + +obs-resent-cc = "Resent-Cc" *WSP ":" address-list CRLF + +obs-resent-bcc = "Resent-Bcc" *WSP ":" + (address-list / [CFWS]) CRLF + +obs-resent-mid = "Resent-Message-ID" *WSP ":" msg-id CRLF + +obs-resent-rply = "Resent-Reply-To" *WSP ":" address-list CRLF + + As with other resent fields, the "Resent-Reply-To:" field is to be + treated as trace information only. + +4.5.7. Obsolete trace fields + + The obs-return and obs-received are again given here as template + definitions, just as return and received are in section 3. Their + full syntax is given in [RFC2821]. + +obs-return = "Return-Path" *WSP ":" path CRLF + +obs-received = "Received" *WSP ":" name-val-list CRLF + +obs-path = obs-angle-addr + +4.5.8. Obsolete optional fields + +obs-optional = field-name *WSP ":" unstructured CRLF + +5. Security Considerations + + Care needs to be taken when displaying messages on a terminal or + terminal emulator. Powerful terminals may act on escape sequences + and other combinations of ASCII control characters with a variety of + consequences. They can remap the keyboard or permit other + modifications to the terminal which could lead to denial of service + or even damaged data. They can trigger (sometimes programmable) + answerback messages which can allow a message to cause commands to be + issued on the recipient's behalf. They can also effect the operation + of terminal attached devices such as printers. Message viewers may + wish to strip potentially dangerous terminal escape sequences from + the message prior to display. However, other escape sequences appear + in messages for useful purposes (cf. [RFC2045, RFC2046, RFC2047, + RFC2048, RFC2049, ISO2022]) and therefore should not be stripped + indiscriminately. + + + +Resnick Standards Track [Page 36] + +RFC 2822 Internet Message Format April 2001 + + + Transmission of non-text objects in messages raises additional + security issues. These issues are discussed in [RFC2045, RFC2046, + RFC2047, RFC2048, RFC2049]. + + Many implementations use the "Bcc:" (blind carbon copy) field + described in section 3.6.3 to facilitate sending messages to + recipients without revealing the addresses of one or more of the + addressees to the other recipients. Mishandling this use of "Bcc:" + has implications for confidential information that might be revealed, + which could eventually lead to security problems through knowledge of + even the existence of a particular mail address. For example, if + using the first method described in section 3.6.3, where the "Bcc:" + line is removed from the message, blind recipients have no explicit + indication that they have been sent a blind copy, except insofar as + their address does not appear in the message header. Because of + this, one of the blind addressees could potentially send a reply to + all of the shown recipients and accidentally reveal that the message + went to the blind recipient. When the second method from section + 3.6.3 is used, the blind recipient's address appears in the "Bcc:" + field of a separate copy of the message. If the "Bcc:" field sent + contains all of the blind addressees, all of the "Bcc:" recipients + will be seen by each "Bcc:" recipient. Even if a separate message is + sent to each "Bcc:" recipient with only the individual's address, + implementations still need to be careful to process replies to the + message as per section 3.6.3 so as not to accidentally reveal the + blind recipient to other recipients. + +6. Bibliography + + [ASCII] American National Standards Institute (ANSI), Coded + Character Set - 7-Bit American National Standard Code for + Information Interchange, ANSI X3.4, 1986. + + [ISO2022] International Organization for Standardization (ISO), + Information processing - ISO 7-bit and 8-bit coded + character sets - Code extension techniques, Third edition + - 1986-05-01, ISO 2022, 1986. + + [RFC822] Crocker, D., "Standard for the Format of ARPA Internet + Text Messages", RFC 822, August 1982. + + [RFC2045] Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part One: Format of Internet Message + Bodies", RFC 2045, November 1996. + + [RFC2046] Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part Two: Media Types", RFC 2046, + November 1996. + + + +Resnick Standards Track [Page 37] + +RFC 2822 Internet Message Format April 2001 + + + [RFC2047] Moore, K., "Multipurpose Internet Mail Extensions (MIME) + Part Three: Message Header Extensions for Non-ASCII Text", + RFC 2047, November 1996. + + [RFC2048] Freed, N., Klensin, J. and J. Postel, "Multipurpose + Internet Mail Extensions (MIME) Part Four: Format of + Internet Message Bodies", RFC 2048, November 1996. + + [RFC2049] Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part Five: Conformance Criteria and + Examples", RFC 2049, November 1996. + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RFC2234] Crocker, D., Editor, and P. Overell, "Augmented BNF for + Syntax Specifications: ABNF", RFC 2234, November 1997. + + [RFC2821] Klensin, J., Editor, "Simple Mail Transfer Protocol", RFC + 2821, March 2001. + + [STD3] Braden, R., "Host Requirements", STD 3, RFC 1122 and RFC + 1123, October 1989. + + [STD12] Mills, D., "Network Time Protocol", STD 12, RFC 1119, + September 1989. + + [STD13] Mockapetris, P., "Domain Name System", STD 13, RFC 1034 + and RFC 1035, November 1987. + + [STD14] Partridge, C., "Mail Routing and the Domain System", STD + 14, RFC 974, January 1986. + +7. Editor's Address + + Peter W. Resnick + QUALCOMM Incorporated + 5775 Morehouse Drive + San Diego, CA 92121-1714 + USA + + Phone: +1 858 651 4478 + Fax: +1 858 651 1102 + EMail: presnick@qualcomm.com + + + + + + + +Resnick Standards Track [Page 38] + +RFC 2822 Internet Message Format April 2001 + + +8. Acknowledgements + + Many people contributed to this document. They included folks who + participated in the Detailed Revision and Update of Messaging + Standards (DRUMS) Working Group of the Internet Engineering Task + Force (IETF), the chair of DRUMS, the Area Directors of the IETF, and + people who simply sent their comments in via e-mail. The editor is + deeply indebted to them all and thanks them sincerely. The below + list includes everyone who sent e-mail concerning this document. + Hopefully, everyone who contributed is named here: + + Matti Aarnio Barry Finkel Larry Masinter + Tanaka Akira Erik Forsberg Denis McKeon + Russ Allbery Chuck Foster William P McQuillan + Eric Allman Paul Fox Alexey Melnikov + Harald Tveit Alvestrand Klaus M. Frank Perry E. Metzger + Ran Atkinson Ned Freed Steven Miller + Jos Backus Jochen Friedrich Keith Moore + Bruce Balden Randall C. Gellens John Gardiner Myers + Dave Barr Sukvinder Singh Gill Chris Newman + Alan Barrett Tim Goodwin John W. Noerenberg + John Beck Philip Guenther Eric Norman + J. Robert von Behren Tony Hansen Mike O'Dell + Jos den Bekker John Hawkinson Larry Osterman + D. J. Bernstein Philip Hazel Paul Overell + James Berriman Kai Henningsen Jacob Palme + Norbert Bollow Robert Herriot Michael A. Patton + Raj Bose Paul Hethmon Uzi Paz + Antony Bowesman Jim Hill Michael A. Quinlan + Scott Bradner Paul E. Hoffman Eric S. Raymond + Randy Bush Steve Hole Sam Roberts + Tom Byrer Kari Hurtta Hugh Sasse + Bruce Campbell Marco S. Hyman Bart Schaefer + Larry Campbell Ofer Inbar Tom Scola + W. J. Carpenter Olle Jarnefors Wolfgang Segmuller + Michael Chapman Kevin Johnson Nick Shelness + Richard Clayton Sudish Joseph John Stanley + Maurizio Codogno Maynard Kang Einar Stefferud + Jim Conklin Prabhat Keni Jeff Stephenson + R. Kelley Cook John C. Klensin Bernard Stern + Steve Coya Graham Klyne Peter Sylvester + Mark Crispin Brad Knowles Mark Symons + Dave Crocker Shuhei Kobayashi Eric Thomas + Matt Curtin Peter Koch Lee Thompson + Michael D'Errico Dan Kohn Karel De Vriendt + Cyrus Daboo Christian Kuhtz Matthew Wall + Jutta Degener Anand Kumria Rolf Weber + Mark Delany Steen Larsen Brent B. Welch + + + +Resnick Standards Track [Page 39] + +RFC 2822 Internet Message Format April 2001 + + + Steve Dorner Eliot Lear Dan Wing + Harold A. Driscoll Barry Leiba Jack De Winter + Michael Elkins Jay Levitt Gregory J. Woodhouse + Robert Elz Lars-Johan Liman Greg A. Woods + Johnny Eriksson Charles Lindsey Kazu Yamamoto + Erik E. Fair Pete Loshin Alain Zahm + Roger Fajman Simon Lyall Jamie Zawinski + Patrik Faltstrom Bill Manning Timothy S. Zurcher + Claus Andre Farber John Martin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Resnick Standards Track [Page 40] + +RFC 2822 Internet Message Format April 2001 + + +Appendix A. Example messages + + This section presents a selection of messages. These are intended to + assist in the implementation of this standard, but should not be + taken as normative; that is to say, although the examples in this + section were carefully reviewed, if there happens to be a conflict + between these examples and the syntax described in sections 3 and 4 + of this document, the syntax in those sections is to be taken as + correct. + + Messages are delimited in this section between lines of "----". The + "----" lines are not part of the message itself. + +A.1. Addressing examples + + The following are examples of messages that might be sent between two + individuals. + +A.1.1. A message from one person to another with simple addressing + + This could be called a canonical message. It has a single author, + John Doe, a single recipient, Mary Smith, a subject, the date, a + message identifier, and a textual message in the body. + +---- +From: John Doe +To: Mary Smith +Subject: Saying Hello +Date: Fri, 21 Nov 1997 09:55:06 -0600 +Message-ID: <1234@local.machine.example> + +This is a message just to say hello. +So, "Hello". +---- + + + + + + + + + + + + + + + + + +Resnick Standards Track [Page 41] + +RFC 2822 Internet Message Format April 2001 + + + If John's secretary Michael actually sent the message, though John + was the author and replies to this message should go back to him, the + sender field would be used: + +---- +From: John Doe +Sender: Michael Jones +To: Mary Smith +Subject: Saying Hello +Date: Fri, 21 Nov 1997 09:55:06 -0600 +Message-ID: <1234@local.machine.example> + +This is a message just to say hello. +So, "Hello". +---- + +A.1.2. Different types of mailboxes + + This message includes multiple addresses in the destination fields + and also uses several different forms of addresses. + +---- +From: "Joe Q. Public" +To: Mary Smith , jdoe@example.org, Who? +Cc: , "Giant; \"Big\" Box" +Date: Tue, 1 Jul 2003 10:52:37 +0200 +Message-ID: <5678.21-Nov-1997@example.com> + +Hi everyone. +---- + + Note that the display names for Joe Q. Public and Giant; "Big" Box + needed to be enclosed in double-quotes because the former contains + the period and the latter contains both semicolon and double-quote + characters (the double-quote characters appearing as quoted-pair + construct). Conversely, the display name for Who? could appear + without them because the question mark is legal in an atom. Notice + also that jdoe@example.org and boss@nil.test have no display names + associated with them at all, and jdoe@example.org uses the simpler + address form without the angle brackets. + + + + + + + + + + + +Resnick Standards Track [Page 42] + +RFC 2822 Internet Message Format April 2001 + + +A.1.3. Group addresses + +---- +From: Pete +To: A Group:Chris Jones ,joe@where.test,John ; +Cc: Undisclosed recipients:; +Date: Thu, 13 Feb 1969 23:32:54 -0330 +Message-ID: + +Testing. +---- + + In this message, the "To:" field has a single group recipient named A + Group which contains 3 addresses, and a "Cc:" field with an empty + group recipient named Undisclosed recipients. + +A.2. Reply messages + + The following is a series of three messages that make up a + conversation thread between John and Mary. John firsts sends a + message to Mary, Mary then replies to John's message, and then John + replies to Mary's reply message. + + Note especially the "Message-ID:", "References:", and "In-Reply-To:" + fields in each message. + +---- +From: John Doe +To: Mary Smith +Subject: Saying Hello +Date: Fri, 21 Nov 1997 09:55:06 -0600 +Message-ID: <1234@local.machine.example> + +This is a message just to say hello. +So, "Hello". +---- + + + + + + + + + + + + + + + +Resnick Standards Track [Page 43] + +RFC 2822 Internet Message Format April 2001 + + + When sending replies, the Subject field is often retained, though + prepended with "Re: " as described in section 3.6.5. + +---- +From: Mary Smith +To: John Doe +Reply-To: "Mary Smith: Personal Account" +Subject: Re: Saying Hello +Date: Fri, 21 Nov 1997 10:01:10 -0600 +Message-ID: <3456@example.net> +In-Reply-To: <1234@local.machine.example> +References: <1234@local.machine.example> + +This is a reply to your hello. +---- + + Note the "Reply-To:" field in the above message. When John replies + to Mary's message above, the reply should go to the address in the + "Reply-To:" field instead of the address in the "From:" field. + +---- +To: "Mary Smith: Personal Account" +From: John Doe +Subject: Re: Saying Hello +Date: Fri, 21 Nov 1997 11:00:00 -0600 +Message-ID: +In-Reply-To: <3456@example.net> +References: <1234@local.machine.example> <3456@example.net> + +This is a reply to your reply. +---- + +A.3. Resent messages + + Start with the message that has been used as an example several + times: + +---- +From: John Doe +To: Mary Smith +Subject: Saying Hello +Date: Fri, 21 Nov 1997 09:55:06 -0600 +Message-ID: <1234@local.machine.example> + +This is a message just to say hello. +So, "Hello". +---- + + + + +Resnick Standards Track [Page 44] + +RFC 2822 Internet Message Format April 2001 + + + Say that Mary, upon receiving this message, wishes to send a copy of + the message to Jane such that (a) the message would appear to have + come straight from John; (b) if Jane replies to the message, the + reply should go back to John; and (c) all of the original + information, like the date the message was originally sent to Mary, + the message identifier, and the original addressee, is preserved. In + this case, resent fields are prepended to the message: + +---- +Resent-From: Mary Smith +Resent-To: Jane Brown +Resent-Date: Mon, 24 Nov 1997 14:22:01 -0800 +Resent-Message-ID: <78910@example.net> +From: John Doe +To: Mary Smith +Subject: Saying Hello +Date: Fri, 21 Nov 1997 09:55:06 -0600 +Message-ID: <1234@local.machine.example> + +This is a message just to say hello. +So, "Hello". +---- + + If Jane, in turn, wished to resend this message to another person, + she would prepend her own set of resent header fields to the above + and send that. + + + + + + + + + + + + + + + + + + + + + + + + + +Resnick Standards Track [Page 45] + +RFC 2822 Internet Message Format April 2001 + + +A.4. Messages with trace fields + + As messages are sent through the transport system as described in + [RFC2821], trace fields are prepended to the message. The following + is an example of what those trace fields might look like. Note that + there is some folding white space in the first one since these lines + can be long. + +---- +Received: from x.y.test + by example.net + via TCP + with ESMTP + id ABC12345 + for ; 21 Nov 1997 10:05:43 -0600 +Received: from machine.example by x.y.test; 21 Nov 1997 10:01:22 -0600 +From: John Doe +To: Mary Smith +Subject: Saying Hello +Date: Fri, 21 Nov 1997 09:55:06 -0600 +Message-ID: <1234@local.machine.example> + +This is a message just to say hello. +So, "Hello". +---- + + + + + + + + + + + + + + + + + + + + + + + + + + +Resnick Standards Track [Page 46] + +RFC 2822 Internet Message Format April 2001 + + +A.5. White space, comments, and other oddities + + White space, including folding white space, and comments can be + inserted between many of the tokens of fields. Taking the example + from A.1.3, white space and comments can be inserted into all of the + fields. + +---- +From: Pete(A wonderful \) chap) +To:A Group(Some people) + :Chris Jones , + joe@example.org, + John (my dear friend); (the end of the group) +Cc:(Empty list)(start)Undisclosed recipients :(nobody(that I know)) ; +Date: Thu, + 13 + Feb + 1969 + 23:32 + -0330 (Newfoundland Time) +Message-ID: + +Testing. +---- + + The above example is aesthetically displeasing, but perfectly legal. + Note particularly (1) the comments in the "From:" field (including + one that has a ")" character appearing as part of a quoted-pair); (2) + the white space absent after the ":" in the "To:" field as well as + the comment and folding white space after the group name, the special + character (".") in the comment in Chris Jones's address, and the + folding white space before and after "joe@example.org,"; (3) the + multiple and nested comments in the "Cc:" field as well as the + comment immediately following the ":" after "Cc"; (4) the folding + white space (but no comments except at the end) and the missing + seconds in the time of the date field; and (5) the white space before + (but not within) the identifier in the "Message-ID:" field. + +A.6. Obsoleted forms + + The following are examples of obsolete (that is, the "MUST NOT + generate") syntactic elements described in section 4 of this + document. + + + + + + + + +Resnick Standards Track [Page 47] + +RFC 2822 Internet Message Format April 2001 + + +A.6.1. Obsolete addressing + + Note in the below example the lack of quotes around Joe Q. Public, + the route that appears in the address for Mary Smith, the two commas + that appear in the "To:" field, and the spaces that appear around the + "." in the jdoe address. + +---- +From: Joe Q. Public +To: Mary Smith <@machine.tld:mary@example.net>, , jdoe@test . example +Date: Tue, 1 Jul 2003 10:52:37 +0200 +Message-ID: <5678.21-Nov-1997@example.com> + +Hi everyone. +---- + +A.6.2. Obsolete dates + + The following message uses an obsolete date format, including a non- + numeric time zone and a two digit year. Note that although the + day-of-week is missing, that is not specific to the obsolete syntax; + it is optional in the current syntax as well. + +---- +From: John Doe +To: Mary Smith +Subject: Saying Hello +Date: 21 Nov 97 09:55:06 GMT +Message-ID: <1234@local.machine.example> + +This is a message just to say hello. +So, "Hello". +---- + +A.6.3. Obsolete white space and comments + + White space and comments can appear between many more elements than + in the current syntax. Also, folding lines that are made up entirely + of white space are legal. + + + + + + + + + + + + +Resnick Standards Track [Page 48] + +RFC 2822 Internet Message Format April 2001 + + +---- +From : John Doe +To : Mary Smith +__ + +Subject : Saying Hello +Date : Fri, 21 Nov 1997 09(comment): 55 : 06 -0600 +Message-ID : <1234 @ local(blah) .machine .example> + +This is a message just to say hello. +So, "Hello". +---- + + Note especially the second line of the "To:" field. It starts with + two space characters. (Note that "__" represent blank spaces.) + Therefore, it is considered part of the folding as described in + section 4.2. Also, the comments and white space throughout + addresses, dates, and message identifiers are all part of the + obsolete syntax. + +Appendix B. Differences from earlier standards + + This appendix contains a list of changes that have been made in the + Internet Message Format from earlier standards, specifically [RFC822] + and [STD3]. Items marked with an asterisk (*) below are items which + appear in section 4 of this document and therefore can no longer be + generated. + + 1. Period allowed in obsolete form of phrase. + 2. ABNF moved out of document to [RFC2234]. + 3. Four or more digits allowed for year. + 4. Header field ordering (and lack thereof) made explicit. + 5. Encrypted header field removed. + 6. Received syntax loosened to allow any token/value pair. + 7. Specifically allow and give meaning to "-0000" time zone. + 8. Folding white space is not allowed between every token. + 9. Requirement for destinations removed. + 10. Forwarding and resending redefined. + 11. Extension header fields no longer specifically called out. + 12. ASCII 0 (null) removed.* + 13. Folding continuation lines cannot contain only white space.* + 14. Free insertion of comments not allowed in date.* + 15. Non-numeric time zones not allowed.* + 16. Two digit years not allowed.* + 17. Three digit years interpreted, but not allowed for generation. + 18. Routes in addresses not allowed.* + 19. CFWS within local-parts and domains not allowed.* + 20. Empty members of address lists not allowed.* + + + +Resnick Standards Track [Page 49] + +RFC 2822 Internet Message Format April 2001 + + + 21. Folding white space between field name and colon not allowed.* + 22. Comments between field name and colon not allowed. + 23. Tightened syntax of in-reply-to and references.* + 24. CFWS within msg-id not allowed.* + 25. Tightened semantics of resent fields as informational only. + 26. Resent-Reply-To not allowed.* + 27. No multiple occurrences of fields (except resent and received).* + 28. Free CR and LF not allowed.* + 29. Routes in return path not allowed.* + 30. Line length limits specified. + 31. Bcc more clearly specified. + +Appendix C. Notices + + Intellectual Property + + The IETF takes no position regarding the validity or scope of any + intellectual property or other rights that might be claimed to + pertain to the implementation or use of the technology described in + this document or the extent to which any license under such rights + might or might not be available; neither does it represent that it + has made any effort to identify any such rights. Information on the + IETF's procedures with respect to rights in standards-track and + standards-related documentation can be found in BCP-11. Copies of + claims of rights made available for publication and any assurances of + licenses to be made available, or the result of an attempt made to + obtain a general license or permission for the use of such + proprietary rights by implementors or users of this specification can + be obtained from the IETF Secretariat. + + + + + + + + + + + + + + + + + + + + + + +Resnick Standards Track [Page 50] + +RFC 2822 Internet Message Format April 2001 + + +Full Copyright Statement + + Copyright (C) The Internet Society (2001). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + + + + + + + + + + + + + +Resnick Standards Track [Page 51] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc3156.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc3156.txt new file mode 100644 index 0000000..c8ca1d5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc3156.txt @@ -0,0 +1,842 @@ + + + + + +Network Working Group M. Elkins +Request for Comments: 3156 Network Associates, Inc. +Updates: 2015 D. Del Torto +Category: Standards Track CryptoRights Foundation + R. Levien + University of California at Berkeley + T. Roessler + August 2001 + + + MIME Security with OpenPGP + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2001). All Rights Reserved. + +Abstract + + This document describes how the OpenPGP Message Format can be used to + provide privacy and authentication using the Multipurpose Internet + Mail Extensions (MIME) security content types described in RFC 1847. + +1. Introduction + + Work on integrating PGP (Pretty Good Privacy) with MIME [3] + (including the since withdrawn "application/pgp" content type) prior + to RFC 2015 suffered from a number of problems, the most significant + of which is the inability to recover signed message bodies without + parsing data structures specific to PGP. RFC 2015 makes use of the + elegant solution proposed in RFC 1847, which defines security + multipart formats for MIME. The security multiparts clearly separate + the signed message body from the signature, and have a number of + other desirable properties. This document revises RFC 2015 to adopt + the integration of PGP and MIME to the needs which emerged during the + work on the OpenPGP specification. + + This document defines three content types for implementing security + and privacy with OpenPGP: "application/pgp-encrypted", + "application/pgp-signature" and "application/pgp-keys". + + + + +Elkins, et al. Standards Track [Page 1] + +RFC 3156 MIME Security with OpenPGP August 2001 + + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in RFC 2119. + +2. OpenPGP data formats + + OpenPGP implementations can generate either ASCII armor (described in + [1]) or 8-bit binary output when encrypting data, generating a + digital signature, or extracting public key data. The ASCII armor + output is the REQUIRED method for data transfer. This allows those + users who do not have the means to interpret the formats described in + this document to be able to extract and use the OpenPGP information + in the message. + + When the amount of data to be transmitted requires that it be sent in + many parts, the MIME message/partial mechanism SHOULD be used rather + than the multi-part ASCII armor OpenPGP format. + +3. Content-Transfer-Encoding restrictions + + Multipart/signed and multipart/encrypted are to be treated by agents + as opaque, meaning that the data is not to be altered in any way [2], + [7]. However, many existing mail gateways will detect if the next + hop does not support MIME or 8-bit data and perform conversion to + either Quoted-Printable or Base64. This presents serious problems + for multipart/signed, in particular, where the signature is + invalidated when such an operation occurs. For this reason all data + signed according to this protocol MUST be constrained to 7 bits (8- + bit data MUST be encoded using either Quoted-Printable or Base64). + Note that this also includes the case where a signed object is also + encrypted (see section 6). This restriction will increase the + likelihood that the signature will be valid upon receipt. + + Additionally, implementations MUST make sure that no trailing + whitespace is present after the MIME encoding has been applied. + + Note: In most cases, trailing whitespace can either be removed, or + protected by applying an appropriate content-transfer-encoding. + However, special care must be taken when any header lines - either + in MIME entity headers, or in embedded RFC 822 headers - are + present which only consist of whitespace: Such lines must be + removed entirely, since replacing them by empty lines would turn + them into header delimiters, and change the semantics of the + message. The restrictions on whitespace are necessary in order to + make the hash calculated invariant under the text and binary mode + signature mechanisms provided by OpenPGP [1]. Also, they help to + avoid compatibility problems with PGP implementations which + predate the OpenPGP specification. + + + +Elkins, et al. Standards Track [Page 2] + +RFC 3156 MIME Security with OpenPGP August 2001 + + + Note: If any line begins with the string "From ", it is strongly + suggested that either the Quoted-Printable or Base64 MIME encoding + be applied. If Quoted-Printable is used, at least one of the + characters in the string should be encoded using the hexadecimal + coding rule. This is because many mail transfer and delivery + agents treat "From " (the word "from" followed immediately by a + space character) as the start of a new message and thus insert a + right angle-bracket (>) in front of any line beginning with + "From " to distinguish this case, invalidating the signature. + + Data that is ONLY to be encrypted is allowed to contain 8-bit + characters and trailing whitespace and therefore need not undergo the + conversion to a 7bit format, and the stripping of whitespace. + + Implementor's note: It cannot be stressed enough that applications + using this standard follow MIME's suggestion that you "be + conservative in what you generate, and liberal in what you + accept." In this particular case it means it would be wise for an + implementation to accept messages with any content-transfer- + encoding, but restrict generation to the 7-bit format required by + this memo. This will allow future compatibility in the event the + Internet SMTP framework becomes 8-bit friendly. + +4. OpenPGP encrypted data + + Before OpenPGP encryption, the data is written in MIME canonical + format (body and headers). + + OpenPGP encrypted data is denoted by the "multipart/encrypted" + content type, described in [2], and MUST have a "protocol" parameter + value of "application/pgp-encrypted". Note that the value of the + parameter MUST be enclosed in quotes. + + The multipart/encrypted MIME body MUST consist of exactly two body + parts, the first with content type "application/pgp-encrypted". This + body contains the control information. A message complying with this + standard MUST contain a "Version: 1" field in this body. Since the + OpenPGP packet format contains all other information necessary for + decrypting, no other information is required here. + + The second MIME body part MUST contain the actual encrypted data. It + MUST be labeled with a content type of "application/octet-stream". + + Example message: + + From: Michael Elkins + To: Michael Elkins + Mime-Version: 1.0 + + + +Elkins, et al. Standards Track [Page 3] + +RFC 3156 MIME Security with OpenPGP August 2001 + + + Content-Type: multipart/encrypted; boundary=foo; + protocol="application/pgp-encrypted" + + --foo + Content-Type: application/pgp-encrypted + + Version: 1 + + --foo + Content-Type: application/octet-stream + + -----BEGIN PGP MESSAGE----- + Version: 2.6.2 + + hIwDY32hYGCE8MkBA/wOu7d45aUxF4Q0RKJprD3v5Z9K1YcRJ2fve87lMlDlx4Oj + eW4GDdBfLbJE7VUpp13N19GL8e/AqbyyjHH4aS0YoTk10QQ9nnRvjY8nZL3MPXSZ + g9VGQxFeGqzykzmykU6A26MSMexR4ApeeON6xzZWfo+0yOqAq6lb46wsvldZ96YA + AABH78hyX7YX4uT1tNCWEIIBoqqvCeIMpp7UQ2IzBrXg6GtukS8NxbukLeamqVW3 + 1yt21DYOjuLzcMNe/JNsD9vDVCvOOG3OCi8= + =zzaA + -----END PGP MESSAGE----- + + --foo-- + +5. OpenPGP signed data + + OpenPGP signed messages are denoted by the "multipart/signed" content + type, described in [2], with a "protocol" parameter which MUST have a + value of "application/pgp-signature" (MUST be quoted). + + The "micalg" parameter for the "application/pgp-signature" protocol + MUST contain exactly one hash-symbol of the format "pgp-", where identifies the Message + Integrity Check (MIC) algorithm used to generate the signature. + Hash-symbols are constructed from the text names registered in [1] or + according to the mechanism defined in that document by converting the + text name to lower case and prefixing it with the four characters + "pgp-". + + Currently defined values are "pgp-md5", "pgp-sha1", "pgp-ripemd160", + "pgp-md2", "pgp-tiger192", and "pgp-haval-5-160". + + The multipart/signed body MUST consist of exactly two parts. The + first part contains the signed data in MIME canonical format, + including a set of appropriate content headers describing the data. + + The second body MUST contain the OpenPGP digital signature. It MUST + be labeled with a content type of "application/pgp-signature". + + + +Elkins, et al. Standards Track [Page 4] + +RFC 3156 MIME Security with OpenPGP August 2001 + + + Note: Implementations can either generate "signatures of a + canonical text document" or "signatures of a binary document", as + defined in [1]. The restrictions on the signed material put forth + in section 3 and in this section will make sure that the various + MIC algorithm variants specified in [1] and [5] will all produce + the same result. + + When the OpenPGP digital signature is generated: + + (1) The data to be signed MUST first be converted to its content- + type specific canonical form. For text/plain, this means + conversion to an appropriate character set and conversion of + line endings to the canonical sequence. + + (2) An appropriate Content-Transfer-Encoding is then applied; see + section 3. In particular, line endings in the encoded data + MUST use the canonical sequence where appropriate + (note that the canonical line ending may or may not be present + on the last line of encoded data and MUST NOT be included in + the signature if absent). + + (3) MIME content headers are then added to the body, each ending + with the canonical sequence. + + (4) As described in section 3 of this document, any trailing + whitespace MUST then be removed from the signed material. + + (5) As described in [2], the digital signature MUST be calculated + over both the data to be signed and its set of content headers. + + (6) The signature MUST be generated detached from the signed data + so that the process does not alter the signed data in any way. + + Note: The accepted OpenPGP convention is for signed data to end + with a sequence. Note that the sequence + immediately preceding a MIME boundary delimiter line is considered + to be part of the delimiter in [3], 5.1. Thus, it is not part of + the signed data preceding the delimiter line. An implementation + which elects to adhere to the OpenPGP convention has to make sure + it inserts a pair on the last line of the data to be + signed and transmitted (signed message and transmitted message + MUST be identical). + + Example message: + + From: Michael Elkins + To: Michael Elkins + Mime-Version: 1.0 + + + +Elkins, et al. Standards Track [Page 5] + +RFC 3156 MIME Security with OpenPGP August 2001 + + + Content-Type: multipart/signed; boundary=bar; micalg=pgp-md5; + protocol="application/pgp-signature" + + --bar + & Content-Type: text/plain; charset=iso-8859-1 + & Content-Transfer-Encoding: quoted-printable + & + & =A1Hola! + & + & Did you know that talking to yourself is a sign of senility? + & + & It's generally a good idea to encode lines that begin with + & From=20because some mail transport agents will insert a greater- + & than (>) sign, thus invalidating the signature. + & + & Also, in some cases it might be desirable to encode any =20 + & trailing whitespace that occurs on lines in order to ensure =20 + & that the message signature is not invalidated when passing =20 + & a gateway that modifies such whitespace (like BITNET). =20 + & + & me + + --bar + + Content-Type: application/pgp-signature + + -----BEGIN PGP MESSAGE----- + Version: 2.6.2 + + iQCVAwUBMJrRF2N9oWBghPDJAQE9UQQAtl7LuRVndBjrk4EqYBIb3h5QXIX/LC// + jJV5bNvkZIGPIcEmI5iFd9boEgvpirHtIREEqLQRkYNoBActFBZmh9GC3C041WGq + uMbrbxc+nIs1TIKlA08rVi9ig/2Yh7LFrK5Ein57U/W72vgSxLhe/zhdfolT9Brn + HOxEa44b+EI= + =ndaj + -----END PGP MESSAGE----- + + --bar-- + + The "&"s in the previous example indicate the portion of the data + over which the signature was calculated. + + Upon receipt of a signed message, an application MUST: + + (1) Convert line endings to the canonical sequence before + the signature can be verified. This is necessary since the + local MTA may have converted to a local end of line convention. + + + + + +Elkins, et al. Standards Track [Page 6] + +RFC 3156 MIME Security with OpenPGP August 2001 + + + (2) Pass both the signed data and its associated content headers + along with the OpenPGP signature to the signature verification + service. + +6. Encrypted and Signed Data + + Sometimes it is desirable to both digitally sign and then encrypt a + message to be sent. This protocol allows for two methods of + accomplishing this task. + +6.1. RFC 1847 Encapsulation + + In [2], it is stated that the data is first signed as a + multipart/signature body, and then encrypted to form the final + multipart/encrypted body. This is most useful for standard MIME- + compliant message forwarding. + + Example: + + Content-Type: multipart/encrypted; + protocol="application/pgp-encrypted"; boundary=foo + + --foo + Content-Type: application/pgp-encrypted + + Version: 1 + + --foo + Content-Type: application/octet-stream + + -----BEGIN PGP MESSAGE----- + & Content-Type: multipart/signed; micalg=pgp-md5 + & protocol="application/pgp-signature"; boundary=bar + & + & --bar + & Content-Type: text/plain; charset=us-ascii + & + & This message was first signed, and then encrypted. + & + & --bar + & Content-Type: application/pgp-signature + & + & -----BEGIN PGP MESSAGE----- + & Version: 2.6.2 + & + & iQCVAwUBMJrRF2N9oWBghPDJAQE9UQQAtl7LuRVndBjrk4EqYBIb3h5QXIX/LC// + & jJV5bNvkZIGPIcEmI5iFd9boEgvpirHtIREEqLQRkYNoBActFBZmh9GC3C041WGq + & uMbrbxc+nIs1TIKlA08rVi9ig/2Yh7LFrK5Ein57U/W72vgSxLhe/zhdfolT9Brn + + + +Elkins, et al. Standards Track [Page 7] + +RFC 3156 MIME Security with OpenPGP August 2001 + + + & HOxEa44b+EI= + & =ndaj + & -----END PGP MESSAGE----- + & + & --bar-- + -----END PGP MESSAGE----- + + --foo-- + + (The text preceded by '&' indicates that it is really encrypted, but + presented as text for clarity.) + +6.2. Combined method + + The OpenPGP packet format [1] describes a method for signing and + encrypting data in a single OpenPGP message. This method is allowed + in order to reduce processing overhead and increase compatibility + with non-MIME implementations of OpenPGP. The resulting data is + formatted as a "multipart/encrypted" object as described in Section + 4. + + Messages which are encrypted and signed in this combined fashion are + REQUIRED to follow the same canonicalization rules as + multipart/signed objects. + + It is explicitly allowed for an agent to decrypt a combined message + and rewrite it as a multipart/signed object using the signature data + embedded in the encrypted version. + +7. Distribution of OpenPGP public keys + + Content-Type: application/pgp-keys + Required parameters: none + Optional parameters: none + + A MIME body part of the content type "application/pgp-keys" contains + ASCII-armored transferable Public Key Packets as defined in [1], + section 10.1. + +8. Security Considerations + + Signatures of a canonical text document as defined in [1] ignore + trailing white space in signed material. Implementations which + choose to use signatures of canonical text documents will not be able + to detect the addition of whitespace in transit. + + See [3], [4] for more information on the security considerations + concerning the underlying protocols. + + + +Elkins, et al. Standards Track [Page 8] + +RFC 3156 MIME Security with OpenPGP August 2001 + + +9. IANA Considerations + + This document defines three media types: "application/pgp-encrypted", + "application/pgp-signature" and "application/pgp-keys". The + following sections specify the IANA registrations for these types. + +9.1. Registration of the application/pgp-encrypted media type + + MIME media type name: application + MIME subtype name: pgp-encrypted + Required parameters: none + Optional parameters: none + + Encoding considerations: + + Currently this media type always consists of a single 7bit text + string. + + Security considerations: + + See Section 8 and RFC 2440 Section 13. + + Interoperability considerations: none + + Published specification: + + This document. + + Additional information: + + Magic number(s): none + File extension(s): none + Macintosh File Type Code(s): none + + Person & email address to contact for further information: + + Michael Elkins + Email: me@cs.hmc.edu + + Intended usage: common + + Author/Change controller: + + Michael Elkins + Email: me@cs.hmc.edu + + + + + + +Elkins, et al. Standards Track [Page 9] + +RFC 3156 MIME Security with OpenPGP August 2001 + + +9.2. Registration of the application/pgp-signature media type + + MIME media type name: application + MIME subtype name: pgp-signature + Required parameters: none + Optional parameters: none + + Encoding considerations: + + The content of this media type always consists of 7bit text. + + Security considerations: + + See Section 8 and RFC 2440 Section 13. + + Interoperability considerations: none + + Published specification: + + RFC 2440 and this document. + + Additional information: + + Magic number(s): none + File extension(s): asc, sig + Macintosh File Type Code(s): pgDS + + Person & email address to contact for further information: + + Michael Elkins + Email: me@cs.hmc.edu + + Intended usage: common + + Author/Change controller: + + Michael Elkins + Email: me@cs.hmc.edu + +9.3. Registration of the application/pgp-keys media type + + MIME media type name: application + MIME subtype name: pgp-keys + Required parameters: none + Optional parameters: none + + + + + + +Elkins, et al. Standards Track [Page 10] + +RFC 3156 MIME Security with OpenPGP August 2001 + + + Encoding considerations: + + The content of this media type always consists of 7bit text. + + Security considerations: + + See Section 8 and RFC 2440 Section 13. + + Interoperability considerations: none + + Published specification: + + RFC 2440 and this document. + + Additional information: + + Magic number(s): none + File extension(s): asc + Macintosh File Type Code(s): none + + Person & email address to contact for further information: + + Michael Elkins + Email: me@cs.hmc.edu + + Intended usage: common + + Author/Change controller: + + Michael Elkins + Email: me@cs.hmc.edu + + + + + + + + + + + + + + + + + + + + +Elkins, et al. Standards Track [Page 11] + +RFC 3156 MIME Security with OpenPGP August 2001 + + +10. Notes + + "PGP" and "Pretty Good Privacy" are registered trademarks of Network + Associates, Inc. + +11. Acknowledgements + + This document relies on the work of the IETF's OpenPGP Working + Group's definitions of the OpenPGP Message Format. The OpenPGP + message format is currently described in RFC 2440 [1]. + + Special thanks are due: to Philip Zimmermann for his original and + ongoing work on PGP; to Charles Breed, Jon Callas and Dave Del Torto + for originally proposing the formation of the OpenPGP Working Group; + and to Steve Schoenfeld for helpful feedback during the draft + process. The authors would also like to thank the engineers at + Pretty Good Privacy, Inc (now Network Associates, Inc), including + Colin Plumb, Hal Finney, Jon Callas, Mark Elrod, Mark Weaver and + Lloyd Chambers, for their technical commentary. + + Additional thanks are due to Jeff Schiller and Derek Atkins for their + continuing support of strong cryptography and PGP freeware at MIT; to + Rodney Thayer of Sable Technology; to John Noerenberg, Steve Dorner + and Laurence Lundblade of the Eudora team at QUALCOMM, Inc; to Bodo + Moeller for proposing the approach followed with respect to trailing + whitespace; to John Gilmore, Hugh Daniel and Fred Ringel (at + Rivertown) and Ian Bell (at Turnpike) for their timely critical + commentary; and to the international members of the IETF's OpenPGP + mailing list, including William Geiger, Lutz Donnerhacke and Kazu + Yamamoto. The idea to use multipart/mixed with multipart/signed has + been attributed to James Galvin. Finally, our gratitude is due to + the many members of the "Cypherpunks," "Coderpunks" and "pgp-users" + mailing lists and the many users + of PGP worldwide for helping keep the path to privacy open. + + + + + + + + + + + + + + + + + +Elkins, et al. Standards Track [Page 12] + +RFC 3156 MIME Security with OpenPGP August 2001 + + +12. Addresses of the Authors and OpenPGP Working Group Chair + + The OpenPGP working group can be contacted via the current chair: + + John W. Noerenberg II + Qualcomm, Inc. + 5775 Morehouse Dr. + San Diego, CA 92121 USA + + Phone: +1 619 658 3510 + EMail: jwn2@qualcomm.com + + The principal authors of this document are: + + Dave Del Torto + CryptoRights Foundation + 80 Alviso Street, Mailstop: CRF + San Francisco, CA 94127 USA + + Phone: +1.415.334.5533, vm: #2 + EMail: ddt@cryptorights.org, ddt@openpgp.net + + + Michael Elkins + Network Associates, Inc. + 3415 S. Sepulveda Blvd Suite 700 + Los Angeles, CA 90034 USA + + Phone: +1.310.737.1663 + Fax: +1.310.737.1755 + Email: me@cs.hmc.edu, Michael_Elkins@NAI.com + + + Raph Levien + University of California at Berkeley + 579 Soda Hall + Berkeley, CA 94720 USA + + Phone: +1.510.642.6509 + EMail: raph@acm.org + + + Thomas Roessler + Nordstrasse 99 + D-53111 Bonn, Germany + + Phone: +49-228-638007 + EMail: roessler@does-not-exist.org + + + +Elkins, et al. Standards Track [Page 13] + +RFC 3156 MIME Security with OpenPGP August 2001 + + +References + + [1] Callas, J., Donnerhacke, L., Finney, H. and R. Thayer, "OpenPGP + Message Format", RFC 2440, November 1998. + + [2] Galvin, J., Murphy, G., Crocker, S. and N. Freed, "Security + Multiparts for MIME: Multipart/Signed and Multipart/Encrypted", + RFC 1847, October 1995. + + [3] Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part Two: Media Types", RFC 2046, November + 1996. + + [4] Galvin, J., Murphy, G., Crocker, S. and N. Freed, "MIME Object + Security Services", RFC 1848, October 1995. + + [5] Atkins, D., Stallings, W. and P. Zimmermann, "PGP Message + Exchange Formats", RFC 1991, August 1996. + + [6] Elkins, M., "MIME Security with Pretty Good Privacy (PGP)", RFC + 2015, October 1996. + + [7] Freed, N., "Gateways and MIME Security Multiparts", RFC 2480, + January 1999. + + + + + + + + + + + + + + + + + + + + + + + + + + + +Elkins, et al. Standards Track [Page 14] + +RFC 3156 MIME Security with OpenPGP August 2001 + + +Full Copyright Statement + + Copyright (C) The Internet Society (2001). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + + + + + + + + + + + + + +Elkins, et al. Standards Track [Page 15] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc3676.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc3676.txt new file mode 100644 index 0000000..bc08fba --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc3676.txt @@ -0,0 +1,1123 @@ + + + + + + +Network Working Group R. Gellens +Request for Comments: 3676 Qualcomm +Obsoletes: 2646 February 2004 +Category: Standards Track + + + The Text/Plain Format and DelSp Parameters + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2004). All Rights Reserved. + +Abstract + + This specification establishes two parameters (Format and DelSP) to + be used with the Text/Plain media type. In the presence of these + parameters, trailing whitespace is used to indicate flowed lines and + a canonical quote indicator is used to indicate quoted lines. This + results in an encoding which appears as normal Text/Plain in older + implementations, since it is in fact normal Text/Plain, yet provides + for superior wrapping/flowing, and quoting. + + This document supersedes the one specified in RFC 2646, "The + Text/Plain Format Parameter", and adds the DelSp parameter to + accommodate languages/coded character sets in which ASCII spaces are + not used or appear rarely. + +Table of Contents + + 1. Introduction. . . . . . . . . . . . . . . . . . . . . . . . . 2 + 2. Conventions Used in this Document . . . . . . . . . . . . . . 2 + 3. The Problem . . . . . . . . . . . . . . . . . . . . . . . . . 3 + 3.1. Paragraph Text. . . . . . . . . . . . . . . . . . . . . 3 + 3.2. Embarrassing Line Wrap . . . . . . . . . . . . . . . . 3 + 3.3. New Media Types . . . . . . . . . . . . . . . . . . . . 4 + 4. The Format and DelSp Parameters . . . . . . . . . . . . . . . 5 + 4.1. Interpreting Format=Flowed. . . . . . . . . . . . . . . 6 + 4.2. Generating Format=Flowed . . . . . . . . . . . . . . . 7 + 4.3. Usenet Signature Convention . . . . . . . . . . . . . . 9 + 4.4. Space-Stuffing . . . . . . . . . . . . . . . . . . . . 9 + + + +Gellens Standards Track [Page 1] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + 4.5. Quoting . . . . . . . . . . . . . . . . . . . . . . . . 9 + 4.6. Digital Signatures and Encryption . . . . . . . . . . . 11 + 4.7. Examples. . . . . . . . . . . . . . . . . . . . . . . . 12 + 5. Interoperability. . . . . . . . . . . . . . . . . . . . . . . 12 + 6. ABNF. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 + 7. Failure Modes . . . . . . . . . . . . . . . . . . . . . . . . 14 + 7.1. Trailing White Space Corruption . . . . . . . . . . . . 14 + 8. Security Considerations . . . . . . . . . . . . . . . . . . . 15 + 9. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 15 + 10. Internationalization Considerations . . . . . . . . . . . . . 15 + 11. Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . 15 + 12. Normative References. . . . . . . . . . . . . . . . . . . . . 16 + 13. Informative References. . . . . . . . . . . . . . . . . . . . 16 + Appendix A: Changes from RFC 2646 . . . . . . . . . . . . . . . . 18 + Author's Address. . . . . . . . . . . . . . . . . . . . . . . . . 19 + Full Copyright Statement. . . . . . . . . . . . . . . . . . . . . 20 + +1. Introduction + + Interoperability problems have been observed with erroneous labelling + of paragraph text as Text/Plain, and with various forms of + "embarrassing line wrap". (See Section 3.) + + Attempts to deploy new media types, such as Text/Enriched [Rich] and + Text/HTML [HTML] have suffered from a lack of backwards compatibility + and an often hostile user reaction at the receiving end. + + What is required is a format which is in all significant ways + Text/Plain, and therefore is quite suitable for display as + Text/Plain, and yet allows the sender to express to the receiver + which lines are quoted and which lines are considered a logical + paragraph, and thus eligible to be flowed (wrapped and joined) as + appropriate. + +2. Conventions Used in this Document + + The key words "REQUIRED", "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", + and "MAY" in this document are to be interpreted as described in "Key + words for use in RFCs to Indicate Requirement Levels" [KEYWORDS]. + + The term "paragraph" is used here to mean a series of lines which are + logically to be treated as a unit for display purposes and eligible + to be flowed (wrapped and joined) as appropriate to fit in the + display window and when creating text for replies, forwarding, etc. + + + + + + + +Gellens Standards Track [Page 2] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + +3. The Problem + + The Text/Plain media type is the lowest common denominator of + Internet email, with lines of no more than 998 characters (by + convention usually no more than 78), and where the carriage-return + and line-feed (CRLF) sequence represents a line break (see [MIME-IMT] + and [MSG-FMT]). + + Text/Plain is usually displayed as preformatted text, often in a + fixed font. That is, the characters start at the left margin of the + display window, and advance to the right until a CRLF sequence is + seen, at which point a new line is started, again at the left margin. + When a line length exceeds the display window, some clients will wrap + the line, while others invoke a horizontal scroll bar. + + Text which meets this description is defined by this memo as "fixed". + + Some interoperability problems have been observed with this format: + +3.1. Paragraph Text + + Many modern programs use a proportional-spaced font, and use CRLF to + represent paragraph breaks. Line breaks are "soft", occurring as + needed on display. That is, characters are grouped into a paragraph + until a CRLF sequence is seen, at which point a new paragraph is + started. Each paragraph is displayed, starting at the left margin + (or paragraph indent), and continuing to the right until a word is + encountered which does not fit in the remaining display width. This + word is displayed at the left margin of the next line. This + continues until the paragraph ends (a CRLF is seen). Extra vertical + space is left between paragraphs. + + Text which meets this description is defined by this memo as + "flowed". + + Numerous software products erroneously label this format as + Text/Plain, resulting in much user discomfort. + +3.2. Embarrassing Line Wrap + + As Text/Plain messages are quoted in replies or forwarded messages, + each line gradually increases in length, eventually being arbitrarily + hard wrapped, resulting in "embarrassing line wrap". This produces + text which is, at best, hard to read, and often confuses + attributions. + + + + + + +Gellens Standards Track [Page 3] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + Example: + + >>>>>>This is a comment from the first message to show a + >quoting example. + >>>>>This is a comment from the second message to show a + >quoting example. + >>>>This is a comment from the third message. + >>>This is a comment from the fourth message. + + It can be confusing to assign attribution to lines 2 and 4 above. + + In addition, as devices with display widths smaller than 79 or 80 + characters become more popular, embarrassing line wrap has become + even more prevalent, even with unquoted text. + + Example: + + This is paragraph text that is + meant to be flowed across + several lines. + However, the sending mailer is + converting it to fixed text at + a width of 72 + characters, which causes it to + look like this when shown on a + PDA with only + 30 character lines. + +3.3. New Media Types + + Attempts to deploy new media types, such as Text/Enriched [Rich] and + Text/HTML [HTML] have suffered from a lack of backwards compatibility + and an often hostile user reaction at the receiving end. + + In particular, Text/Enriched requires that open angle brackets ("<") + and hard line breaks be doubled, with resulting user unhappiness when + viewed as Text/Plain. Text/HTML requires even more alteration of + text, with a corresponding increase in user complaints. + + A proposal to define a new media type to explicitly represent the + paragraph form suffered from a lack of interoperability with + currently deployed software. Some programs treat unknown subtypes of + TEXT as an attachment. + + + + + + + + +Gellens Standards Track [Page 4] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + What is desired is a format which is in all significant ways + Text/Plain, and therefore is quite suitable for display as + Text/Plain, and yet allows the sender to express to the receiver + which lines can be considered a logical paragraph, and thus flowed + (wrapped and joined) as appropriate. + +4. The Format and DelSp Parameters + + This specification defines two MIME parameters for use with + Text/Plain: + + Name: Format + Value: Fixed, Flowed + + Name: DelSp + Value: Yes, No + + (Neither the parameter names nor values are case sensitive.) + + If Format is not specified, or if the value is not recognized, a + value of Fixed is assumed. The semantics of the Fixed value are the + usual associated with Text/Plain [MIME-IMT]. + + A Format value of Flowed indicates that the definition of flowed text + (as specified in this memo) was used on generation, and MAY be used + on reception. + + Note that because Format is a parameter of the Text/Plain content- + type, any content-transfer-encoding used is irrelevant to the + processing of flowed text. + + If DelSp is not specified, or if its value is not recognized, a value + of No is assumed. The use of DelSp without a Format value of Flowed + is undefined. When creating messages, DelSp SHOULD NOT be specified + in Text content types other than Text/Plain with Format = Flowed. + When receiving messages, DelSp SHOULD be ignored if used in a Text + content type other than Text/Plain with Format = Flowed. + + This section discusses flowed text; section 6 provides a formal + definition. + + Section 5 discusses interoperability. + + Note that this memo describes an on-the-wire format. It does not + address formats for local file storage. + + + + + + +Gellens Standards Track [Page 5] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + +4.1. Interpreting Format=Flowed + + If the first character of a line is a quote mark (">"), the line is + considered to be quoted (see Section 4.5). Logically, all quote + marks are counted and deleted, resulting in a line with a non-zero + quote depth, and content. (The agent is of course free to display + the content with quote marks or excerpt bars or anything else.) + Logically, this test for quoted lines is done before any other tests + (that is, before checking for space-stuffed and flowed). + + If the first character of a line is a space, the line has been + space-stuffed (see Section 4.4). Logically, this leading space is + deleted before examining the line further (that is, before checking + for flowed). + + If the line ends in a space, the line is flowed. Otherwise it is + fixed. The exception to this rule is a signature separator line, + described in Section 4.3. Such lines end in a space but are neither + flowed nor fixed. + + If the line is flowed and DelSp is "yes", the trailing space + immediately prior to the line's CRLF is logically deleted. If the + DelSp parameter is "no" (or not specified, or set to an unrecognized + value), the trailing space is not deleted. + + Any remaining trailing spaces are part of the line's content, but the + CRLF of a soft line break is not. + + A series of one or more flowed lines followed by one fixed line is + considered a paragraph, and MAY be flowed (wrapped and unwrapped) as + appropriate on display and in the construction of new messages (see + Section 4.5). + + An interpreting agent SHOULD allow for three exceptions to the rule + that paragraphs end with a fixed line. These exceptions are + improperly constructed messages: a flowed line SHOULD be considered + to end the paragraph if it is followed by a line of a different quote + depth (see 4.5) or by a signature separator (see 4.3); the end of the + body also ends the paragraph. + + A line consisting of one or more spaces (after deleting a space + acting as stuffing) is considered a flowed line. + + An empty line (just a CRLF) is a fixed line. + + Note that, for Unicode text, [Annex-14] provides guidance for + choosing at which characters to wrap a line. + + + + +Gellens Standards Track [Page 6] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + +4.2. Generating Format=Flowed + + When generating Format=Flowed text, lines SHOULD be 78 characters or + shorter, including any trailing white space and also including any + space added as part of stuffing (see Section 4.4). As suggested + values, any paragraph longer than 78 characters in total length could + be wrapped using lines of 72 or fewer characters. While the specific + line length used is a matter of aesthetics and preference, longer + lines are more likely to require rewrapping and to encounter + difficulties with older mailers. (It has been suggested that 66 + character lines are the most readable.) + + The restriction to 78 or fewer characters between CRLFs on the wire + is to conform to [MSG-FMT]. + + (In addition to conformance to [MSG-FMT], there is a historical need + that all lines, even when displayed by a non-flowed-aware program, + will fit in a standard 79- or 80-column screen without having to be + wrapped. The limit is 78, not 79 or 80, because while 79 or 80 fit + on a line, the last column is often reserved for a line-wrap + indicator.) + + When creating flowed text, the generating agent wraps, that is, + inserts 'soft' line breaks as needed. Soft line breaks are added at + natural wrapping points, such as between words. A soft line break is + a SP CRLF sequence. + + There are two techniques for inserting soft line breaks. The older + technique, established by RFC 2646, creates a soft line break by + inserting a CRLF after the occurrence of a space. With this + technique, soft line breaks are only possible where spaces already + occur. When this technique is used, the DelSp parameter SHOULD be + used; if used it MUST be set to "no". + + The newer technique, suitable for use even with languages/coded + character sets in which the ASCII space character is rare or not + used, creates a soft line break by inserting a SP CRLF sequence. + When this technique is used, the DelSp parameter MUST be used and + MUST be set to "yes". Note that because of space-stuffing (see + Section 4.4), when this technique is used and a soft line break is + inserted at a point where a SP already exists (such as between + words), if the SP CRLF sequence is added immediately before the SP, + the pre-existing SP becomes leading and thus requires stuffing. It + is RECOMMENDED that agents avoid this by inserting the SP CRLF + sequence following the existing SP. + + Generating agents MAY use either method within each Text/Plain body + part. + + + +Gellens Standards Track [Page 7] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + Regardless of which technique is used, a generating agent SHOULD NOT + insert a space in an unnatural location, such as into a word (a + sequence of printable characters, not containing spaces, in a + language/coded character set in which spaces are common). If faced + with such a word which exceeds 78 characters (but less than 998 + characters, the [SMTP] limit on line length), the agent SHOULD send + the word as is and exceed the 78-character limit on line length. + + A generating agent SHOULD: + + o Ensure all lines (fixed and flowed) are 78 characters or fewer in + length, counting any trailing space as well as a space added as + stuffing, but not counting the CRLF, unless a word by itself + exceeds 78 characters. + + o Trim spaces before user-inserted hard line breaks. + + A generating agent MUST: + + o Space-stuff lines which start with a space, "From ", or ">". + + In order to create messages which do not require space-stuffing, and + are thus more aesthetically pleasing when viewed as Format=Fixed, a + generating agent MAY avoid wrapping immediately before ">", "From ", + or space. + + (See Sections 4.4 and 4.5 for more information on space-stuffing and + quoting, respectively.) + + A Format=Flowed message consists of zero or more paragraphs, each + containing one or more flowed lines followed by one fixed line. The + usual case is a series of flowed text lines with blank (empty) fixed + lines between them. + + Any number of fixed lines can appear between paragraphs. + + When placing soft line breaks in a paragraph, generating agents MUST + NOT place them in a way that causes any line of the paragraph to be a + signature separator line, because paragraphs cannot contain signature + separator lines (see Sections 4.3 and 6). + + [Quoted-Printable] encoding SHOULD NOT be used with Format=Flowed + unless absolutely necessary (for example, non-US-ASCII (8-bit) + characters over a strictly 7-bit transport such as unextended + [SMTP]). In particular, a message SHOULD NOT be encoded in Quoted- + Printable for the sole purpose of protecting the trailing space on + flowed lines unless the body part is cryptographically signed or + encrypted (see Section 4.6). + + + +Gellens Standards Track [Page 8] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + The intent of Format=Flowed is to allow user agents to generate + flowed text which is non-obnoxious when viewed as pure, raw + Text/Plain (without any decoding); use of Quoted-Printable hinders + this and may cause Format=Flowed to be rejected by end users. + +4.3. Usenet Signature Convention + + There is a long-standing convention in Usenet news which also + commonly appears in Internet mail of using "-- " as the separator + line between the body and the signature of a message. When + generating a Format=Flowed message containing a Usenet-style + separator before the signature, the separator line is sent as-is. + This is a special case; an (optionally quoted or quoted and stuffed) + line consisting of DASH DASH SP is neither fixed nor flowed. + + Generating agents MUST NOT end a paragraph with such a signature + line. + + A receiving agent needs to test for a signature line both before the + test for a quoted line (see Section 4.5) and also after logically + counting and deleting quote marks and stuffing (see Section 4.4) from + a quoted line. + +4.4. Space-Stuffing + + In order to allow for unquoted lines which start with ">", and to + protect against systems which "From-munge" in-transit messages + (modifying any line which starts with "From " to ">From "), + Format=Flowed provides for space-stuffing. + + Space-stuffing adds a single space to the start of any line which + needs protection when the message is generated. On reception, if the + first character of a line is a space, it is logically deleted. This + occurs after the test for a quoted line (which logically counts and + deletes any quote marks), and before the test for a flowed line. + + On generation, any unquoted lines which start with ">", and any lines + which start with a space or "From " MUST be space-stuffed. Other + lines MAY be space-stuffed as desired. + + (Note that space-stuffing is conceptually similar to dot-stuffing as + specified in [SMTP].) + +4.5. Quoting + + In Format=Flowed, the canonical quote indicator (or quote mark) is + one or more close angle bracket (">") characters. Lines which start + with the quote indicator are considered quoted. The number of ">" + + + +Gellens Standards Track [Page 9] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + characters at the start of the line specifies the quote depth. + Flowed lines which are also quoted may require special handling on + display and when copied to new messages. + + When creating quoted flowed lines, each such line starts with the + quote indicator. + + Note that because of space-stuffing, the lines + >> Exit, Stage Left + and + >>Exit, Stage Left + are semantically identical; both have a quote-depth of two, and a + content of "Exit, Stage Left". + + However, the line + > > Exit, Stage Left + is different. It has a quote-depth of one, and a content of + "> Exit, Stage Left". + + When generating quoted flowed lines, an agent needs to pay attention + to changes in quote depth. All lines of a paragraph MUST be + unquoted, or else they MUST all be quoted and have the same quote + depth. Therefore, whenever there is a change in quote depth, or a + change from quoted to unquoted, or change from unquoted to quoted, + the line immediately preceding the change MUST NOT be a flowed line. + + If a receiving agent wishes to reformat flowed quoted lines (joining + and/or wrapping them) on display or when generating new messages, the + lines SHOULD be de-quoted, reformatted, and then re-quoted. To de- + quote, the number of close angle brackets in the quote indicator at + the start of each line is counted. To re-quote after reformatting, a + quote indicator containing the same number of close angle brackets + originally present are prefixed to each line. + + On reception, if a change in quote depth occurs on a flowed line, + this is an improperly formatted message. The receiver SHOULD handle + this error by using the 'quote-depth-wins' rule, which is to consider + the paragraph to end with the flowed line immediately preceding the + change in quote depth. + + In other words, whenever two adjacent lines have different quote + depths, senders MUST ensure that the earlier line is not flowed (does + not end in a space), and receivers finding a flowed line there SHOULD + treat it as the last line of a paragraph. + + For example, consider the following sequence of lines (using '*' to + indicate a soft line break, i.e., SP CRLF, and '#' to indicate a hard + line break, i.e., CRLF): + + + +Gellens Standards Track [Page 10] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + > Thou villainous ill-breeding spongy dizzy-eyed* + > reeky elf-skinned pigeon-egg!* <--- problem ---< + >> Thou artless swag-bellied milk-livered* + >> dismal-dreaming idle-headed scut!# + >>> Thou errant folly-fallen spleeny reeling-ripe* + >>> unmuzzled ratsbane!# + >>>> Henceforth, the coding style is to be strictly* + >>>> enforced, including the use of only upper case.# + >>>>> I've noticed a lack of adherence to the coding* + >>>>> styles, of late.# + >>>>>> Any complaints?# + + The second line ends in a soft line break, even though it is the last + line of the one-deep quote block. The question then arises as to how + this line is to be interpreted, considering that the next line is the + first line of the two-deep quote block. + + The example text above, when processed according to quote-depth wins, + results in the first two lines being considered as one quoted, flowed + section, with a quote depth of 1; the third and fourth lines become a + quoted, flowed section, with a quote depth of 2. + + A generating agent MUST NOT create this situation; a receiving agent + SHOULD handle it by giving preference to the quote depth. + +4.6. Digital Signatures and Encryption + + If a message is digitally signed or encrypted it is important that + cryptographic processing use the same text for signature verification + and/or decryption as was used for signature generation and/or + encryption. Since the use of format=flowed allows text to be altered + (by adding or removing line breaks and trailing spaces) between + composition and transmission, and between reception and display, + interoperability problems or security vulnerabilities may arise if + originator and recipient do not both use the on-the-wire format for + cryptographic processing. + + The implications of the interaction between format=flowed and any + specific cryptographic process depend on the details of the + cryptographic processing and should be understood before using + format=flowed in conjunction with signed and/or encrypted messages. + + Note that [OpenPGP] specifies (in Section 7.1) that "any trailing + whitespace (spaces, and tabs, 0x09) at the end of any line is ignored + when the cleartext signature is calculated." + + + + + + +Gellens Standards Track [Page 11] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + Thus it would be possible to add, in transit, a format=flowed header + to a regular, format=fixed vanilla PGP (not [OpenPGP-MIME]) signed + message and add arbitrary trailing space characters without this + addition being detected. This would change the rendering of the + article by a client which supported format=flowed. + + Therefore, the use of [OpenPGP] with format=flowed messages is + strongly discouraged. [OpenPGP-MIME] is recommended instead. + +4.7. Examples + + The following example contains three paragraphs: + + `Take some more tea,' the March Hare said to Alice, very + earnestly. + + `I've had nothing yet,' Alice replied in an offended tone, `so I + can't take more.' + + `You mean you can't take LESS,' said the Hatter: `it's very easy + to take MORE than nothing.' + + This could be encoded as follows (using '*' to indicate a soft line + break, that is, SP CRLF sequence, and '#' to indicate a hard line + break, that is, CRLF): + + `Take some more tea,' the March Hare said to Alice, very* + earnestly.# + # + `I've had nothing yet,' Alice replied in an offended tone, `so* + I can't take more.'# + # + `You mean you can't take LESS,' said the Hatter: `it's very* + easy to take MORE than nothing.'# + + To show an example of quoting, here we have the same exchange, + presented as a series of direct quotes: + + >>>Take some more tea.# + >>I've had nothing yet, so I can't take more.# + >You mean you can't take LESS, it's very easy to take* + >MORE than nothing.# + +5. Interoperability + + Because flowed lines are all-but-indistinguishable from fixed lines, + software which does not recognize Format=Flowed treats flowed lines + as normal Text/Plain (which is what they are). Thus, Format=Flowed + + + +Gellens Standards Track [Page 12] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + interoperates with older clients, although flowed lines will have + trailing white space inserted. + + If a space-stuffed message is received by an agent which handles + Format=Flowed, the space-stuffing is reversed and thus the message + appears unchanged. An agent which is not aware of Format=Flowed will + of course not undo any space-stuffing; thus Format=Flowed messages + may appear with a leading space on some lines (those which start with + a space, ">" which is not a quote indicator, or "From "). Since + lines which require space-stuffing rarely occur, and the aesthetic + consequences of unreversed space-stuffing are minimal, this is not + expected to be a significant problem. + + If some lines begin with one or more spaces, the generating agent MAY + space-stuff all lines, to maintain the relative indentation of the + lines when viewed by clients which are not aware of Format=Flowed. + + Messages generated with DelSp=yes and received by clients which are + aware of Format=Flowed but are not aware of the DelSp parameter will + have an extra space remaining after removal of soft line breaks. + Thus, when generating text in languages/coded character sets in which + spaces are common, the generating agent MAY always use the DelSp=no + method. + + Hand-aligned text, such as ASCII tables or art, source code, etc., + SHOULD be sent as fixed, not flowed lines. + +6. ABNF + + The constructs used in Text/Plain; Format=Flowed body parts are + described using Augmented Backus-Naur Form [ABNF], including the core + rules defined in Appendix A. + + Note that the SP (space) and ">" characters are encoded according to + the charset parameter. + +flowed-body = *( paragraph / fixed-line / sig-sep ) +paragraph = 1*flowed-line fixed-line + ; all lines in paragraph MUST be unquoted or + ; have same quote depth +flowed-line = ( flowed-line-qt / flowed-line-unqt ) flow CRLF +flowed-line-qt = quote ( ( stuffing stuffed-flowed ) / + unstuffed-flowed ) +flowed-line-unqt = ( stuffing stuffed-flowed ) / unstuffed-flowed +stuffed-flowed = *text-char +unstuffed-flowed = non-sp-quote *text-char +fixed-line = fixed-line-qt / fixed-line-unqt +fixed-line-qt = quote ( ( stuffing stuffed-fixed ) / + + + +Gellens Standards Track [Page 13] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + unstuffed-fixed ) CRLF +fixed-line-unqt = ( stuffed-fixed / unstuffed-fixed ) CRLF +stuffed-fixed = *text-char non-sp +unstuffed-fixed = non-sp-quote [ *text-char non-sp ] +sig-sep = [ quote [stuffing] ] "--" SP CRLF +quote-mark = ">" +quote = 1*quote-mark +stuffing = SP ; space-stuffed, added on generation if + ; needed, deleted on reception +flow = SP ; space before CRLF indicates flowed line, + ; if DelSp=yes, space was added on generation + ; and is deleted on reception +non-sp-quote = < any character except NUL, CR, LF, SP, quote-mark > +non-sp = non-sp-quote / quote-mark +text-char = non-sp / SP + + That is, a Format=Flowed message body consists of any number of + paragraphs and/or fixed lines and/or signature separator lines; + paragraphs need at least one flowed line and are terminated by a + fixed line; the fixed line terminating the paragraph is part of the + paragraph. (There are some exceptions to this described in the + text.) + + Without at least one flowed line, there is a series of fixed lines, + each independent. There is no paragraph. + + With at least one flowed line, there is a paragraph, and the received + lines can be reformed and flowed to fit the display window size. + This can only be done if the lines are part of a logical grouping, + the paragraph. + + Note that the definitions of flowed-line and sig-sep are potentially + ambiguous: a signature separator line matches both, but is treated as + a signature separator line and not a flowed line. + +7. Failure Modes + +7.1. Trailing White Space Corruption + + There are systems in existence which alter trailing whitespace on + messages which pass through them. Such systems may strip, or in + rarer cases, add trailing whitespace, in violation of RFC 2821 [SMTP] + Section 4.5.2. + + Stripping trailing whitespace has the effect of converting flowed + lines to fixed lines, which results in a message no worse than if + Format=Flowed had not been used. + + + + +Gellens Standards Track [Page 14] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + Adding trailing whitespace to a Format=Flowed message may result in a + malformed display or reply. + + Since most systems which add trailing white space do so to create a + line which fills an internal record format, the result is almost + always a line which contains an even number of characters (counting + the added trailing white space). + + One possible avoidance, therefore, would be to define Format=Flowed + lines to use either one or two trailing space characters to indicate + a flowed line, such that the total line length is odd. However, + considering the scarcity of such systems today, it is not worth the + added complexity. + +8. Security Considerations + + Any security considerations which apply to Text/Plain also apply to + Text/Plain with Format=Flowed. + + Section 4.6 discusses the interaction between Format=Flowed and + digital signatures or encryption. + +9. IANA Considerations + + IANA has added a reference to this specification in the Text/Plain + Media Type registration. + +10. Internationalization Considerations + + The line wrap and quoting specifications of Format=Flowed may not be + suitable for certain charsets, such as for Arabic and Hebrew + characters that read from right to left. Care needs to be taken in + applying format=flowed in these cases, as format=fixed combined with + [quoted-printable] encoding may be more suitable. + + The DelSp parameter was added specifically to permit Format=Flowed to + be used with languages/coded character sets in which the ASCII space + character is rarely used, or not used at all. + +11. Acknowledgments + + The DelSp parameter was developed during a series of discussions + among a number of people, including Harald Alvestrand, Grant Baillie, + Ian Bell, Steve Dorner, Patrik Faltstrom, Eric Fischer, Ned Freed, + Alexey Melnikov, John Myers, and Pete Resnick. + + + + + + +Gellens Standards Track [Page 15] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + Corrections and clarifications to RFC 2646 and early versions of this + document were pointed out by several people, including Adam Costello, + Jutta Degener, Tony Hansen, Simon Josefsson, Dan Kohn, Ragho + Mahalingam, Keith Moore, Greg Troxel, and Dan Wing. + + I'm told that NeXT's mail application used a very similar mechanism + (without support for non-Western languages) in 1992. + +12. Normative References + + [ABNF] Crocker, D., Ed. and P. Overell, "Augmented BNF + for Syntax Specifications: ABNF", RFC 2234, + November 1997. + + [KEYWORDS] Bradner, S., "Key words for use in RFCs to + Indicate Requirement Levels", BCP 14, RFC 2119, + March 1997. + + [MIME-IMT] Freed, N. and N. Borenstein, "Multipurpose + Internet Mail Extensions (MIME) Part Two: Media + Types", RFC 2046, November 1996. + + [Quoted-Printable] Freed, N. and N. Borenstein, "Multipurpose + Internet Mail Extensions (MIME) Part One: Format + of Internet Message Bodies", RFC 2045, November + 1996. + +13. Informative References + + [Annex-14] Unicode Standard Annex #14, "Line Breaking + Properties" + + + [MSG-FMT] Resnick, P., Ed., "Internet Message Format", RFC + 2822, April 2001. + + [OpenPGP] Callas, J., Donnerhacke, L., Finney, H. and R. + Thayer, "OpenPGP Message Format", RFC 2440, + November 1998. + + [OpenPGP-MIME] Elkins, M., "MIME Security with Pretty Good + Privacy (PGP)", RFC 2015, October 1996. + + Elkins, M., Del Torto, D., Levien, R. and J. + Roessler, "MIME Security with OpenPGP", RFC 3156, + August 2001. + + + + + +Gellens Standards Track [Page 16] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + [Rich] Resnick, P. and A. Walker, "The text/enriched MIME + Content-type", RFC 1896, February 1996. + + [SMTP] Klensin, J., Ed., "Simple Mail Transfer Protocol", + RFC 2821, April 2001. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Gellens Standards Track [Page 17] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + +Appendix A: Changes from RFC 2646 + + Substantive: + + o Added DelSp parameter to handle languages and coded character sets + in which space is less common or not used. + o Updated text on generating and interpreting to accommodate the + DelSp parameter. + o Changed the limits of 79 or 80 to be 78 in conformance with RFC + 2822. + o Added text on generating to clarify that the 78-character limit + includes trailing white space and stuffing. + o Changed sig-sep in ABNF to allow stuffing. + o Changed fixed-line to allow empty lines in ABNF. + o Added explanatory text following ABNF. + o Moved text from Abstract to new Introduction; rewrote Abstract. + o Moved interoperability text to new section, and updated. + o Clarified Security Considerations. + o Text on digital signatures now discusses that OpenPGP ignores + trailing white space. + o Mention Unicode Annex 14. + o Added mention of quoting to Abstract and Introduction. + o Deleted line analysis table. + o Added recommendations for OpenPGP and OpenPGP-MIME. + o Rewrote ABNF rules to remove most ambiguity and note remaining + case. + o Added note that c-t-e is irrelevant to flowed text processing. + o Added text indicating that end of data terminates a paragraph. + o Moved sig-sep out of fixed-line ABNF. + o Changed some SHOULDs to MUSTs (space-stuffing, quoted paragraphs). + o Added note to ABNF that space and ">" are encoded according to + charset. + o Mentioned exceptions in section on interpreting. + o Clarified and made consistent treatment of signature separator + lines. + + Editorial: + + o Added mention of NeXT's mail application to Acknowledgments. + o Updated Acknowledgments. + o Updated [SMTP] reference to 2821. + o Added Notices. + o Split References into Normative and Informative. + o Improved text wording in some areas. + o Standardize on "quote depth", not "quoting depth". + o Moved section on interpreting before section on generating. + o Reworded non-normative "should"s. + o Noted meaning of "paragraph". + + + +Gellens Standards Track [Page 18] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + The DelSp parameter was added specifically to permit Format=Flowed to + be used with languages/coded character sets in which the ASCII space + character is rarely used, or not used at all. The DelSp mechanism + was selected despite having been initially rejected as too much of a + kludge, because among the many different techniques proposed, it + allows for maximum interoperability among clients which support + neither this specification nor RFC 2646, those which do support RFC + 2646 but not this specification, and those that do support this + specification; this set is multiplied by those that handle + languages/coded character sets in which spaces are common, and in + which they are uncommon or not used. + +Author's Address + + Randall Gellens + QUALCOMM Incorporated + 5775 Morehouse Drive + San Diego, CA 92121 + USA + + Phone: +1 858 651 5115 + EMail: randy@qualcomm.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Gellens Standards Track [Page 19] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + +Full Copyright Statement + + Copyright (C) The Internet Society (2004). This document is subject + to the rights, licenses and restrictions contained in BCP 78 and + except as set forth therein, the authors retain all their rights. + + This document and the information contained herein are provided on an + "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE + REPRESENTS OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE + INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF + THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED + WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Intellectual Property + + The IETF takes no position regarding the validity or scope of any + Intellectual Property Rights or other rights that might be claimed + to pertain to the implementation or use of the technology + described in this document or the extent to which any license + under such rights might or might not be available; nor does it + represent that it has made any independent effort to identify any + such rights. Information on the procedures with respect to + rights in RFC documents can be found in BCP 78 and BCP 79. + + Copies of IPR disclosures made to the IETF Secretariat and any + assurances of licenses to be made available, or the result of an + attempt made to obtain a general license or permission for the use + of such proprietary rights by implementers or users of this + specification can be obtained from the IETF on-line IPR repository + at http://www.ietf.org/ipr. + + The IETF invites any interested party to bring to its attention + any copyrights, patents or patent applications, or other + proprietary rights that may cover technology that may be required + to implement this standard. Please address the information to the + IETF at ietf-ipr@ietf.org. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + + + +Gellens Standards Track [Page 20] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4505.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4505.txt new file mode 100644 index 0000000..6b8a4a1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4505.txt @@ -0,0 +1,507 @@ + + + + + + +Network Working Group K. Zeilenga, Ed. +Request for Comments: 4505 OpenLDAP Foundation +Obsoletes: 2245 June 2006 +Category: Standards Track + + + Anonymous Simple Authentication and Security Layer (SASL) Mechanism + +Status of This Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2006). + +Abstract + + On the Internet, it is common practice to permit anonymous access to + various services. Traditionally, this has been done with a plain- + text password mechanism using "anonymous" as the user name and using + optional trace information, such as an email address, as the + password. As plain-text login commands are not permitted in new IETF + protocols, a new way to provide anonymous login is needed within the + context of the Simple Authentication and Security Layer (SASL) + framework. + +1. Introduction + + This document defines an anonymous mechanism for the Simple + Authentication and Security Layer ([SASL]) framework. The name + associated with this mechanism is "ANONYMOUS". + + Unlike many other SASL mechanisms, whose purpose is to authenticate + and identify the user to a server, the purpose of this SASL mechanism + is to allow the user to gain access to services or resources without + requiring the user to establish or otherwise disclose their identity + to the server. That is, this mechanism provides an anonymous login + method. + + This mechanism does not provide a security layer. + + This document replaces RFC 2245. Changes since RFC 2245 are detailed + in Appendix A. + + + +Zeilenga Standards Track [Page 1] + +RFC 4505 Anonymous SASL Mechanism June 2006 + + +2. The Anonymous Mechanism + + The mechanism consists of a single message from the client to the + server. The client may include in this message trace information in + the form of a string of [UTF-8]-encoded [Unicode] characters prepared + in accordance with [StringPrep] and the "trace" stringprep profile + defined in Section 3 of this document. The trace information, which + has no semantical value, should take one of two forms: an Internet + email address, or an opaque string that does not contain the '@' + (U+0040) character and that can be interpreted by the system + administrator of the client's domain. For privacy reasons, an + Internet email address or other information identifying the user + should only be used with permission from the user. + + A server that permits anonymous access will announce support for the + ANONYMOUS mechanism and allow anyone to log in using that mechanism, + usually with restricted access. + + A formal grammar for the client message using Augmented BNF [ABNF] is + provided below as a tool for understanding this technical + specification. + + message = [ email / token ] + ;; to be prepared in accordance with Section 3 + + UTF1 = %x00-3F / %x41-7F ;; less '@' (U+0040) + UTF2 = %xC2-DF UTF0 + UTF3 = %xE0 %xA0-BF UTF0 / %xE1-EC 2(UTF0) / + %xED %x80-9F UTF0 / %xEE-EF 2(UTF0) + UTF4 = %xF0 %x90-BF 2(UTF0) / %xF1-F3 3(UTF0) / + %xF4 %x80-8F 2(UTF0) + UTF0 = %x80-BF + + TCHAR = UTF1 / UTF2 / UTF3 / UTF4 + ;; any UTF-8 encoded Unicode character + ;; except '@' (U+0040) + + email = addr-spec + ;; as defined in [IMAIL] + + token = 1*255TCHAR + + Note to implementors: + The production is restricted to 255 UTF-8-encoded Unicode + characters. As the encoding of a characters uses a sequence of 1 + to 4 octets, a token may be as long as 1020 octets. + + + + + +Zeilenga Standards Track [Page 2] + +RFC 4505 Anonymous SASL Mechanism June 2006 + + +3. The "trace" Profile of "Stringprep" + + This section defines the "trace" profile of [StringPrep]. This + profile is designed for use with the SASL ANONYMOUS Mechanism. + Specifically, the client is to prepare the production in + accordance with this profile. + + The character repertoire of this profile is Unicode 3.2 [Unicode]. + + No mapping is required by this profile. + + No Unicode normalization is required by this profile. + + The list of unassigned code points for this profile is that provided + in Appendix A of [StringPrep]. Unassigned code points are not + prohibited. + + Characters from the following tables of [StringPrep] are prohibited: + + - C.2.1 (ASCII control characters) + - C.2.2 (Non-ASCII control characters) + - C.3 (Private use characters) + - C.4 (Non-character code points) + - C.5 (Surrogate codes) + - C.6 (Inappropriate for plain text) + - C.8 (Change display properties are deprecated) + - C.9 (Tagging characters) + + No additional characters are prohibited. + + This profile requires bidirectional character checking per Section 6 + of [StringPrep]. + +4. Example + + Here is a sample ANONYMOUS login between an IMAP client and server. + In this example, "C:" and "S:" indicate lines sent by the client and + server, respectively. If such lines are wrapped without a new "C:" + or "S:" label, then the wrapping is for editorial clarity and is not + part of the command. + + Note that this example uses the IMAP profile [IMAP4] of SASL. The + base64 encoding of challenges and responses as well as the "+ " + preceding the responses are part of the IMAP4 profile, not part of + SASL itself. Additionally, protocols with SASL profiles permitting + an initial client response will be able to avoid the extra round trip + below (the server response with an empty "+ "). + + + + +Zeilenga Standards Track [Page 3] + +RFC 4505 Anonymous SASL Mechanism June 2006 + + + In this example, the trace information is "sirhc". + + S: * OK IMAP4 server ready + C: A001 CAPABILITY + S: * CAPABILITY IMAP4 IMAP4rev1 AUTH=DIGEST-MD5 AUTH=ANONYMOUS + S: A001 OK done + C: A002 AUTHENTICATE ANONYMOUS + S: + + C: c2lyaGM= + S: A003 OK Welcome, trace information has been logged. + +5. Security Considerations + + The ANONYMOUS mechanism grants access to services and/or resources by + anyone. For this reason, it should be disabled by default so that + the administrator can make an explicit decision to enable it. + + If the anonymous user has any write privileges, a denial-of-service + attack is possible by filling up all available space. This can be + prevented by disabling all write access by anonymous users. + + If anonymous users have read and write access to the same area, the + server can be used as a communication mechanism to exchange + information anonymously. Servers that accept anonymous submissions + should implement the common "drop box" model, which forbids anonymous + read access to the area where anonymous submissions are accepted. + + If the anonymous user can run many expensive operations (e.g., an + IMAP SEARCH BODY command), this could enable a denial-of-service + attack. Servers are encouraged to reduce the priority of anonymous + users or limit their resource usage. + + While servers may impose a limit on the number of anonymous users, + note that such limits enable denial-of-service attacks and should be + used with caution. + + The trace information is not authenticated, so it can be falsified. + This can be used as an attempt to get someone else in trouble for + access to questionable information. Administrators investigating + abuse need to realize that this trace information may be falsified. + + A client that uses the user's correct email address as trace + information without explicit permission may violate that user's + privacy. Anyone who accesses an anonymous archive on a sensitive + subject (e.g., sexual abuse) likely has strong privacy needs. + Clients should not send the email address without the explicit + permission of the user and should offer the option of supplying no + trace information, thus only exposing the source IP address and time. + + + +Zeilenga Standards Track [Page 4] + +RFC 4505 Anonymous SASL Mechanism June 2006 + + + Anonymous proxy servers could enhance this privacy but would have to + consider the resulting potential denial-of-service attacks. + + Anonymous connections are susceptible to man-in-the-middle attacks + that view or alter the data transferred. Clients and servers are + encouraged to support external data security services. + + Protocols that fail to require an explicit anonymous login are more + susceptible to break-ins given certain common implementation + techniques. Specifically, Unix servers that offer user login may + initially start up as root and switch to the appropriate user id + after an explicit login command. Normally, such servers refuse all + data access commands prior to explicit login and may enter a + restricted security environment (e.g., the Unix chroot(2) function) + for anonymous users. If anonymous access is not explicitly + requested, the entire data access machinery is exposed to external + security attacks without the chance for explicit protective measures. + Protocols that offer restricted data access should not allow + anonymous data access without an explicit login step. + + General [SASL] security considerations apply to this mechanism. + + [StringPrep] security considerations and [Unicode] security + considerations discussed in [StringPrep] apply to this mechanism. + [UTF-8] security considerations also apply. + +6. IANA Considerations + + The SASL Mechanism registry [IANA-SASL] entry for the ANONYMOUS + mechanism has been updated by the IANA to reflect that this document + now provides its technical specification. + + To: iana@iana.org + Subject: Updated Registration of SASL mechanism ANONYMOUS + + SASL mechanism name: ANONYMOUS + Security considerations: See RFC 4505. + Published specification (optional, recommended): RFC 4505 + Person & email address to contact for further information: + Kurt Zeilenga + Chris Newman + Intended usage: COMMON + Author/Change controller: IESG + Note: Updates existing entry for ANONYMOUS + + + + + + + +Zeilenga Standards Track [Page 5] + +RFC 4505 Anonymous SASL Mechanism June 2006 + + + The [StringPrep] profile "trace", first defined in this RFC, has been + registered: + + To: iana@iana.org + Subject: Initial Registration of Stringprep "trace" profile + + Stringprep profile: trace + Published specification: RFC 4505 + Person & email address to contact for further information: + Kurt Zeilenga + +7. Acknowledgement + + This document is a revision of RFC 2245 by Chris Newman. Portions of + the grammar defined in Section 1 were borrowed from RFC 3629 by + Francois Yergeau. + + This document is a product of the IETF SASL WG. + +8. Normative References + + [ABNF] Crocker, D. and P. Overell, "Augmented BNF for Syntax + Specifications: ABNF", RFC 4234, October 2005. + + [IMAIL] Resnick, P., "Internet Message Format", RFC 2822, April + 2001. + + [SASL] Melnikov, A., Ed. and K. Zeilenga, Ed., "Simple + Authentication and Security Layer (SASL)", RFC 4422, + June 2006. + + [StringPrep] Hoffman, P. and M. Blanchet, "Preparation of + Internationalized Strings ('stringprep')", RFC 3454, + December 2002. + + [Unicode] The Unicode Consortium, "The Unicode Standard, Version + 3.2.0" is defined by "The Unicode Standard, Version 3.0" + (Reading, MA, Addison-Wesley, 2000. ISBN 0-201-61633-5), + as amended by the "Unicode Standard Annex #27: Unicode + 3.1" (http://www.unicode.org/reports/tr27/) and by the + "Unicode Standard Annex #28: Unicode 3.2" + (http://www.unicode.org/reports/tr28/). + + [UTF-8] Yergeau, F., "UTF-8, a transformation format of ISO + 10646", RFC 3629 (also STD 63), November 2003. + + + + + + +Zeilenga Standards Track [Page 6] + +RFC 4505 Anonymous SASL Mechanism June 2006 + + +9. Informative References + + [IMAP4] Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - VERSION + 4rev1", RFC 3501, March 2003. + + [IANA-SASL] IANA, "SIMPLE AUTHENTICATION AND SECURITY LAYER (SASL) + MECHANISMS", . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Zeilenga Standards Track [Page 7] + +RFC 4505 Anonymous SASL Mechanism June 2006 + + +Appendix A. Changes since RFC 2245 + + This appendix is non-normative. + + RFC 2245 allows the client to include optional trace information in + the form of a human readable string. RFC 2245 restricted this string + to US-ASCII. As the Internet is international, this document uses a + string restricted to UTF-8 encoded Unicode characters. A + "stringprep" profile is defined to precisely define which Unicode + characters are allowed in this string. While the string remains + restricted to 255 characters, the encoded length of each character + may now range from 1 to 4 octets. + + Additionally, a number of editorial changes were made. + +Editor's Address + + Kurt D. Zeilenga + OpenLDAP Foundation + + EMail: Kurt@OpenLDAP.org + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Zeilenga Standards Track [Page 8] + +RFC 4505 Anonymous SASL Mechanism June 2006 + + +Full Copyright Statement + + Copyright (C) The Internet Society (2006). + + This document is subject to the rights, licenses and restrictions + contained in BCP 78, and except as set forth therein, the authors + retain all their rights. + + This document and the information contained herein are provided on an + "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS + OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET + ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE + INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED + WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Intellectual Property + + The IETF takes no position regarding the validity or scope of any + Intellectual Property Rights or other rights that might be claimed to + pertain to the implementation or use of the technology described in + this document or the extent to which any license under such rights + might or might not be available; nor does it represent that it has + made any independent effort to identify any such rights. Information + on the procedures with respect to rights in RFC documents can be + found in BCP 78 and BCP 79. + + Copies of IPR disclosures made to the IETF Secretariat and any + assurances of licenses to be made available, or the result of an + attempt made to obtain a general license or permission for the use of + such proprietary rights by implementers or users of this + specification can be obtained from the IETF on-line IPR repository at + http://www.ietf.org/ipr. + + The IETF invites any interested party to bring to its attention any + copyrights, patents or patent applications, or other proprietary + rights that may cover technology that may be required to implement + this standard. Please address the information to the IETF at + ietf-ipr@ietf.org. + +Acknowledgement + + Funding for the RFC Editor function is provided by the IETF + Administrative Support Activity (IASA). + + + + + + + +Zeilenga Standards Track [Page 9] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4616.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4616.txt new file mode 100644 index 0000000..991189d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4616.txt @@ -0,0 +1,619 @@ + + + + + + +Network Working Group K. Zeilenga, Ed. +Request for Comments: 4616 OpenLDAP Foundation +Updates: 2595 August 2006 +Category: Standards Track + + + The PLAIN Simple Authentication and Security Layer (SASL) Mechanism + +Status of This Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2006). + +Abstract + + This document defines a simple clear-text user/password Simple + Authentication and Security Layer (SASL) mechanism called the PLAIN + mechanism. The PLAIN mechanism is intended to be used, in + combination with data confidentiality services provided by a lower + layer, in protocols that lack a simple password authentication + command. + + + + + + + + + + + + + + + + + + + + + + + +Zeilenga Standards Track [Page 1] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + +1. Introduction + + Clear-text, multiple-use passwords are simple, interoperate with + almost all existing operating system authentication databases, and + are useful for a smooth transition to a more secure password-based + authentication mechanism. The drawback is that they are unacceptable + for use over network connections where data confidentiality is not + ensured. + + This document defines the PLAIN Simple Authentication and Security + Layer ([SASL]) mechanism for use in protocols with no clear-text + login command (e.g., [ACAP] or [SMTP-AUTH]). This document updates + RFC 2595, replacing Section 6. Changes since RFC 2595 are detailed + in Appendix A. + + The name associated with this mechanism is "PLAIN". + + The PLAIN SASL mechanism does not provide a security layer. + + The PLAIN mechanism should not be used without adequate data security + protection as this mechanism affords no integrity or confidentiality + protections itself. The mechanism is intended to be used with data + security protections provided by application-layer protocol, + generally through its use of Transport Layer Security ([TLS]) + services. + + By default, implementations SHOULD advertise and make use of the + PLAIN mechanism only when adequate data security services are in + place. Specifications for IETF protocols that indicate that this + mechanism is an applicable authentication mechanism MUST mandate that + implementations support an strong data security service, such as TLS. + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [Keywords]. + +2. PLAIN SASL Mechanism + + The mechanism consists of a single message, a string of [UTF-8] + encoded [Unicode] characters, from the client to the server. The + client presents the authorization identity (identity to act as), + followed by a NUL (U+0000) character, followed by the authentication + identity (identity whose password will be used), followed by a NUL + (U+0000) character, followed by the clear-text password. As with + other SASL mechanisms, the client does not provide an authorization + identity when it wishes the server to derive an identity from the + credentials and use that as the authorization identity. + + + + +Zeilenga Standards Track [Page 2] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + + The formal grammar for the client message using Augmented BNF [ABNF] + follows. + + message = [authzid] UTF8NUL authcid UTF8NUL passwd + authcid = 1*SAFE ; MUST accept up to 255 octets + authzid = 1*SAFE ; MUST accept up to 255 octets + passwd = 1*SAFE ; MUST accept up to 255 octets + UTF8NUL = %x00 ; UTF-8 encoded NUL character + + SAFE = UTF1 / UTF2 / UTF3 / UTF4 + ;; any UTF-8 encoded Unicode character except NUL + + UTF1 = %x01-7F ;; except NUL + UTF2 = %xC2-DF UTF0 + UTF3 = %xE0 %xA0-BF UTF0 / %xE1-EC 2(UTF0) / + %xED %x80-9F UTF0 / %xEE-EF 2(UTF0) + UTF4 = %xF0 %x90-BF 2(UTF0) / %xF1-F3 3(UTF0) / + %xF4 %x80-8F 2(UTF0) + UTF0 = %x80-BF + + The authorization identity (authzid), authentication identity + (authcid), password (passwd), and NUL character deliminators SHALL be + transferred as [UTF-8] encoded strings of [Unicode] characters. As + the NUL (U+0000) character is used as a deliminator, the NUL (U+0000) + character MUST NOT appear in authzid, authcid, or passwd productions. + + The form of the authzid production is specific to the application- + level protocol's SASL profile [SASL]. The authcid and passwd + productions are form-free. Use of non-visible characters or + characters that a user may be unable to enter on some keyboards is + discouraged. + + Servers MUST be capable of accepting authzid, authcid, and passwd + productions up to and including 255 octets. It is noted that the + UTF-8 encoding of a Unicode character may be as long as 4 octets. + + Upon receipt of the message, the server will verify the presented (in + the message) authentication identity (authcid) and password (passwd) + with the system authentication database, and it will verify that the + authentication credentials permit the client to act as the (presented + or derived) authorization identity (authzid). If both steps succeed, + the user is authenticated. + + The presented authentication identity and password strings, as well + as the database authentication identity and password strings, are to + be prepared before being used in the verification process. The + [SASLPrep] profile of the [StringPrep] algorithm is the RECOMMENDED + preparation algorithm. The SASLprep preparation algorithm is + + + +Zeilenga Standards Track [Page 3] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + + recommended to improve the likelihood that comparisons behave in an + expected manner. The SASLprep preparation algorithm is not mandatory + so as to allow the server to employ other preparation algorithms + (including none) when appropriate. For instance, use of a different + preparation algorithm may be necessary for the server to interoperate + with an external system. + + When preparing the presented strings using [SASLPrep], the presented + strings are to be treated as "query" strings (Section 7 of + [StringPrep]) and hence unassigned code points are allowed to appear + in their prepared output. When preparing the database strings using + [SASLPrep], the database strings are to be treated as "stored" + strings (Section 7 of [StringPrep]) and hence unassigned code points + are prohibited from appearing in their prepared output. + + Regardless of the preparation algorithm used, if the output of a + non-invertible function (e.g., hash) of the expected string is + stored, the string MUST be prepared before input to that function. + + Regardless of the preparation algorithm used, if preparation fails or + results in an empty string, verification SHALL fail. + + When no authorization identity is provided, the server derives an + authorization identity from the prepared representation of the + provided authentication identity string. This ensures that the + derivation of different representations of the authentication + identity produces the same authorization identity. + + The server MAY use the credentials to initialize any new + authentication database, such as one suitable for [CRAM-MD5] or + [DIGEST-MD5]. + +3. Pseudo-Code + + This section provides pseudo-code illustrating the verification + process (using hashed passwords and the SASLprep preparation + function) discussed above. This section is not definitive. + + boolean Verify(string authzid, string authcid, string passwd) { + string pAuthcid = SASLprep(authcid, true); # prepare authcid + string pPasswd = SASLprep(passwd, true); # prepare passwd + if (pAuthcid == NULL || pPasswd == NULL) { + return false; # preparation failed + } + if (pAuthcid == "" || pPasswd == "") { + return false; # empty prepared string + } + + + + +Zeilenga Standards Track [Page 4] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + + storedHash = FetchPasswordHash(pAuthcid); + if (storedHash == NULL || storedHash == "") { + return false; # error or unknown authcid + } + + if (!Compare(storedHash, Hash(pPasswd))) { + return false; # incorrect password + } + + if (authzid == NULL ) { + authzid = DeriveAuthzid(pAuthcid); + if (authzid == NULL || authzid == "") { + return false; # could not derive authzid + } + } + + if (!Authorize(pAuthcid, authzid)) { + return false; # not authorized + } + + return true; + } + + The second parameter of the SASLprep function, when true, indicates + that unassigned code points are allowed in the input. When the + SASLprep function is called to prepare the password prior to + computing the stored hash, the second parameter would be false. + + The second parameter provided to the Authorize function is not + prepared by this code. The application-level SASL profile should be + consulted to determine what, if any, preparation is necessary. + + Note that the DeriveAuthzid and Authorize functions (whether + implemented as one function or two, whether designed in a manner in + which these functions or whether the mechanism implementation can be + reused elsewhere) require knowledge and understanding of mechanism + and the application-level protocol specification and/or + implementation details to implement. + + Note that the Authorize function outcome is clearly dependent on + details of the local authorization model and policy. Both functions + may be dependent on other factors as well. + + + + + + + + + +Zeilenga Standards Track [Page 5] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + +4. Examples + + This section provides examples of PLAIN authentication exchanges. + The examples are intended to help the readers understand the above + text. The examples are not definitive. + + "C:" and "S:" indicate lines sent by the client and server, + respectively. "" represents a single NUL (U+0000) character. + The Application Configuration Access Protocol ([ACAP]) is used in the + examples. + + The first example shows how the PLAIN mechanism might be used for + user authentication. + + S: * ACAP (SASL "CRAM-MD5") (STARTTLS) + C: a001 STARTTLS + S: a001 OK "Begin TLS negotiation now" + + S: * ACAP (SASL "CRAM-MD5" "PLAIN") + C: a002 AUTHENTICATE "PLAIN" + S: + "" + C: {21} + C: timtanstaaftanstaaf + S: a002 OK "Authenticated" + + The second example shows how the PLAIN mechanism might be used to + attempt to assume the identity of another user. In this example, the + server rejects the request. Also, this example makes use of the + protocol optional initial response capability to eliminate a round- + trip. + + S: * ACAP (SASL "CRAM-MD5") (STARTTLS) + C: a001 STARTTLS + S: a001 OK "Begin TLS negotiation now" + + S: * ACAP (SASL "CRAM-MD5" "PLAIN") + C: a002 AUTHENTICATE "PLAIN" {20+} + C: UrselKurtxipj3plmq + S: a002 NO "Not authorized to requested authorization identity" + +5. Security Considerations + + As the PLAIN mechanism itself provided no integrity or + confidentiality protections, it should not be used without adequate + external data security protection, such as TLS services provided by + many application-layer protocols. By default, implementations SHOULD + NOT advertise and SHOULD NOT make use of the PLAIN mechanism unless + adequate data security services are in place. + + + +Zeilenga Standards Track [Page 6] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + + When the PLAIN mechanism is used, the server gains the ability to + impersonate the user to all services with the same password + regardless of any encryption provided by TLS or other confidentiality + protection mechanisms. Whereas many other authentication mechanisms + have similar weaknesses, stronger SASL mechanisms address this issue. + Clients are encouraged to have an operational mode where all + mechanisms that are likely to reveal the user's password to the + server are disabled. + + General [SASL] security considerations apply to this mechanism. + + Unicode, [UTF-8], and [StringPrep] security considerations also + apply. + +6. IANA Considerations + + The SASL Mechanism registry [IANA-SASL] entry for the PLAIN mechanism + has been updated by the IANA to reflect that this document now + provides its technical specification. + + To: iana@iana.org + Subject: Updated Registration of SASL mechanism PLAIN + + SASL mechanism name: PLAIN + Security considerations: See RFC 4616. + Published specification (optional, recommended): RFC 4616 + Person & email address to contact for further information: + Kurt Zeilenga + IETF SASL WG + Intended usage: COMMON + Author/Change controller: IESG + Note: Updates existing entry for PLAIN + +7. Acknowledgements + + This document is a revision of RFC 2595 by Chris Newman. Portions of + the grammar defined in Section 2 were borrowed from [UTF-8] by + Francois Yergeau. + + This document is a product of the IETF Simple Authentication and + Security Layer (SASL) Working Group. + + + + + + + + + + +Zeilenga Standards Track [Page 7] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + +8. Normative References + + [ABNF] Crocker, D., Ed. and P. Overell, "Augmented BNF for + Syntax Specifications: ABNF", RFC 4234, October 2005. + + [Keywords] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [SASL] Melnikov, A., Ed., and K. Zeilenga, Ed., "Simple + Authentication and Security Layer (SASL)", RFC 4422, + June 2006. + + [SASLPrep] Zeilenga, K., "SASLprep: Stringprep Profile for User + Names and Passwords", RFC 4013, February 2005. + + [StringPrep] Hoffman, P. and M. Blanchet, "Preparation of + Internationalized Strings ("stringprep")", RFC 3454, + December 2002. + + [Unicode] The Unicode Consortium, "The Unicode Standard, Version + 3.2.0" is defined by "The Unicode Standard, Version + 3.0" (Reading, MA, Addison-Wesley, 2000. ISBN 0-201- + 61633-5), as amended by the "Unicode Standard Annex + #27: Unicode 3.1" + (http://www.unicode.org/reports/tr27/) and by the + "Unicode Standard Annex #28: Unicode 3.2" + (http://www.unicode.org/reports/tr28/). + + [UTF-8] Yergeau, F., "UTF-8, a transformation format of ISO + 10646", STD 63, RFC 3629, November 2003. + + [TLS] Dierks, T. and E. Rescorla, "The Transport Layer + Security (TLS) Protocol Version 1.1", RFC 4346, April + 2006. + +9. Informative References + + [ACAP] Newman, C. and J. Myers, "ACAP -- Application + Configuration Access Protocol", RFC 2244, November + 1997. + + [CRAM-MD5] Nerenberg, L., Ed., "The CRAM-MD5 SASL Mechanism", Work + in Progress, June 2006. + + [DIGEST-MD5] Melnikov, A., Ed., "Using Digest Authentication as a + SASL Mechanism", Work in Progress, June 2006. + + + + + +Zeilenga Standards Track [Page 8] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + + [IANA-SASL] IANA, "SIMPLE AUTHENTICATION AND SECURITY LAYER (SASL) + MECHANISMS", + . + + [SMTP-AUTH] Myers, J., "SMTP Service Extension for Authentication", + RFC 2554, March 1999. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Zeilenga Standards Track [Page 9] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + +Appendix A. Changes since RFC 2595 + + This appendix is non-normative. + + This document replaces Section 6 of RFC 2595. + + The specification details how the server is to compare client- + provided character strings with stored character strings. + + The ABNF grammar was updated. In particular, the grammar now allows + LINE FEED (U+000A) and CARRIAGE RETURN (U+000D) characters in the + authzid, authcid, passwd productions. However, whether these control + characters may be used depends on the string preparation rules + applicable to the production. For passwd and authcid productions, + control characters are prohibited. For authzid, one must consult the + application-level SASL profile. This change allows PLAIN to carry + all possible authorization identity strings allowed in SASL. + + Pseudo-code was added. + + The example section was expanded to illustrate more features of the + PLAIN mechanism. + +Editor's Address + + Kurt D. Zeilenga + OpenLDAP Foundation + + EMail: Kurt@OpenLDAP.org + + + + + + + + + + + + + + + + + + + + + + +Zeilenga Standards Track [Page 10] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + +Full Copyright Statement + + Copyright (C) The Internet Society (2006). + + This document is subject to the rights, licenses and restrictions + contained in BCP 78, and except as set forth therein, the authors + retain all their rights. + + This document and the information contained herein are provided on an + "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS + OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET + ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE + INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED + WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Intellectual Property + + The IETF takes no position regarding the validity or scope of any + Intellectual Property Rights or other rights that might be claimed to + pertain to the implementation or use of the technology described in + this document or the extent to which any license under such rights + might or might not be available; nor does it represent that it has + made any independent effort to identify any such rights. Information + on the procedures with respect to rights in RFC documents can be + found in BCP 78 and BCP 79. + + Copies of IPR disclosures made to the IETF Secretariat and any + assurances of licenses to be made available, or the result of an + attempt made to obtain a general license or permission for the use of + such proprietary rights by implementers or users of this + specification can be obtained from the IETF on-line IPR repository at + http://www.ietf.org/ipr. + + The IETF invites any interested party to bring to its attention any + copyrights, patents or patent applications, or other proprietary + rights that may cover technology that may be required to implement + this standard. Please address the information to the IETF at + ietf-ipr@ietf.org. + +Acknowledgement + + Funding for the RFC Editor function is provided by the IETF + Administrative Support Activity (IASA). + + + + + + + +Zeilenga Standards Track [Page 11] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4870.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4870.txt new file mode 100644 index 0000000..55ba5e2 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4870.txt @@ -0,0 +1,2298 @@ + + + + + +Network Working Group M. Delany +Request for Comments: 4870 Yahoo! Inc +Obsoleted By: 4871 May 2007 +Category: Historic + + + Domain-Based Email Authentication Using Public Keys + Advertised in the DNS (DomainKeys) + +Status of This Memo + + This memo defines a Historic Document for the Internet community. It + does not specify an Internet standard of any kind. Distribution of + this memo is unlimited. + +Copyright Notice + + Copyright (C) The IETF Trust (2007). + +Abstract + + "DomainKeys" creates a domain-level authentication framework for + email by using public key technology and the DNS to prove the + provenance and contents of an email. + + This document defines a framework for digitally signing email on a + per-domain basis. The ultimate goal of this framework is to + unequivocally prove and protect identity while retaining the + semantics of Internet email as it is known today. + + Proof and protection of email identity may assist in the global + control of "spam" and "phishing". + + + + + + + + + + + + + + + + + + + +Delany Historic [Page 1] + +RFC 4870 DomainKeys May 2007 + + +Table of Contents + + 1. Introduction ....................................................3 + 1.1. Lack of Authentication Is Damaging Internet Email ..........3 + 1.2. Digitally Signing Email Creates Credible Domain + Authentication .............................................4 + 1.3. Public Keys in the DNS .....................................4 + 1.4. Initial Deployment Is Likely at the Border MTA .............5 + 1.5. Conveying Verification Results to MUAs .....................5 + 1.6. Technical Minutiae Are Not Completely Covered ..............5 + 1.7. Motivation .................................................6 + 1.8. Benefits of DomainKeys .....................................6 + 1.9. Definitions ................................................7 + 1.10. Requirements Notation .....................................8 + 2. DomainKeys Overview .............................................8 + 3. DomainKeys Detailed View ........................................8 + 3.1. Determining the Sending Address of an Email ................9 + 3.2. Retrieving the Public Key Given the Sending Domain ........10 + 3.2.1. Introducing "selectors" ............................10 + 3.2.2. Public Key Signing and Verification Algorithm ......11 + 3.2.3. Public key Representation in the DNS ...............13 + 3.2.4. Key Sizes ..........................................14 + 3.3. Storing the Signature in the Email Header .................15 + 3.4. Preparation of Email for Transit and Signing ..............17 + 3.4.1. Preparation for Transit ............................18 + 3.4.2. Canonicalization for Signing .......................18 + 3.4.2.1. The "simple" Canonicalization Algorithm ...19 + 3.4.2.2. The "nofws" Canonicalization Algorithm ....19 + 3.5. The Signing Process .......................................20 + 3.5.1. Identifying the Sending Domain .....................20 + 3.5.2. Determining Whether an Email Should Be Signed ......21 + 3.5.3. Selecting a Private Key and Corresponding + Selector Information ...............................21 + 3.5.4. Calculating the Signature Value ....................21 + 3.5.5. Prepending the "DomainKey-Signature:" Header .......21 + 3.6. Policy Statement of Sending Domain ........................22 + 3.7. The Verification Process ..................................23 + 3.7.1. Presumption that Headers Are Not Reordered .........24 + 3.7.2. Verification Should Render a Binary Result .........24 + 3.7.3. Selecting the Most Appropriate + "DomainKey-Signature:" Header ......................24 + 3.7.4. Retrieve the Public Key Based on the + Signature Information ..............................26 + 3.7.5. Verify the Signature ...............................27 + 3.7.6. Retrieving Sending Domain Policy ...................27 + 3.7.7. Applying Local Policy ..............................27 + 3.8. Conveying Verification Results to MUAs ....................27 + + + + +Delany Historic [Page 2] + +RFC 4870 DomainKeys May 2007 + + + 4. Example of Use .................................................29 + 4.1. The User Composes an Email ................................29 + 4.2. The Email Is Signed .......................................29 + 4.3. The Email Signature Is Verified ...........................30 + 5. Association with a Certificate Authority .......................31 + 5.1. The "DomainKey-X509:" Header ..............................31 + 6. Topics for Discussion ..........................................32 + 6.1. The Benefits of Selectors .................................32 + 6.2. Canonicalization of Email .................................33 + 6.3. Mailing Lists .............................................33 + 6.4. Roving Users ..............................................33 + 7. Security Considerations ........................................34 + 7.1. DNS .......................................................34 + 7.1.1. The DNS Is Not Currently Secure ....................34 + 7.1.2. DomainKeys Creates Additional DNS Load .............35 + 7.2. Key Management ............................................35 + 7.3. Implementation Risks ......................................35 + 7.4. Privacy Assumptions with Forwarding Addresses .............35 + 7.5. Cryptographic Processing Is Computationally Intensive .....36 + 8. The Trial ......................................................36 + 8.1. Goals .....................................................36 + 8.2. Results of Trial ..........................................37 + 9. Note to Implementors Regarding TXT Records .....................37 + 10. References ....................................................37 + 10.1. Normative References .....................................37 + 10.2. Informative References ...................................38 + Appendix A - Syntax Rules for the Tag=Value Format .............39 + Acknowledgments ................................................40 + +1. Introduction + + This document proposes an authentication framework for email that + stores public keys in the DNS and digitally signs email on a domain + basis. Separate documents discuss how this framework can be extended + to validate the delivery path of email as well as facilitate per-user + authentication. + + The DomainKeys specification was a primary source from which the + DomainKeys Identified Mail [DKIM] specification has been derived. + The purpose in submitting this document is as an historical reference + for deployed implementations written prior to the DKIM specification. + +1.1. Lack of Authentication Is Damaging Internet Email + + Authentication of email is not currently widespread. Not only is it + difficult to prove your own identity, it is impossible to prevent + others from abusing your identity. + + + + +Delany Historic [Page 3] + +RFC 4870 DomainKeys May 2007 + + + While most email exchanges do not intrinsically need authentication + beyond context, it is the rampant abuse of identity by "spammers", + "phishers", and their criminal ilk that makes proof necessary. In + other words, authentication is as much about protection as proof. + + Importantly, the inability to authenticate email effectively + delegates much of the control of the disposition of inbound email to + the sender, since senders can trivially assume any email address. + Creating email authentication is the first step to returning + dispositional control of email to the recipient. + + For the purposes of this document, authentication is seen from a user + perspective, and is intended to answer the question "who sent this + email?" where "who" is the email address the recipient sees and "this + email" is the content that the recipient sees. + +1.2. Digitally Signing Email Creates Credible Domain Authentication + + DomainKeys combines public key cryptography and the DNS to provide + credible domain-level authentication for email. + + When an email claims to originate from a certain domain, DomainKeys + provides a mechanism by which the recipient system can credibly + determine that the email did in fact originate from a person or + system authorized to send email for that domain. + + The authentication provided by DomainKeys works in a number of + scenarios in which other authentication systems fail or create + complex operational requirements. These include the following: + + o forwarded email + + o distributed sending systems + + o authorized third-party sending + + This base definition of DomainKeys is intended to primarily enable + domain-level authenticity. Whether a given message is really sent by + the purported user within the domain is outside the scope of the base + definition. Having said that, this specification includes the + possibility that some domains may wish to delegate fine-grained + authentication to individual users. + +1.3. Public Keys in the DNS + + DomainKeys differs from traditional hierarchical public key systems + in that it leverages the DNS for public key management, placing + complete and direct control of key generation and management with the + + + +Delany Historic [Page 4] + +RFC 4870 DomainKeys May 2007 + + + owner of the domain. That is, if you have control over the DNS for a + given domain, you have control over your DomainKeys for that domain. + + The DNS is proposed as the initial mechanism for publishing public + keys. DomainKeys is specifically designed to be extensible to other + key-fetching services as they become available. + +1.4. Initial Deployment Is Likely at the Border MTA + + For practical reasons, it is expected that initial implementations of + DomainKeys will be deployed on Mail Transfer Agents (MTAs) that + accept or relay email across administrative or organizational + boundaries. There are numerous advantages to deployment at the + border MTA, including: + + o a reduction in the number of MTAs that have to be changed to + support an implementation of DomainKeys + + o a reduction in the number of MTAs involved in transmitting the + email between a signing system and a verifying system, thus + reducing the number of places that can make accidental changes + to the contents + + o removing the need to implement DomainKeys within an internal + email network. + + However, there is no necessity to deploy DomainKeys at the border as + signing and verifying can effectively occur anywhere from the border + MTA right back to the Mail User Agent (MUA). In particular, the best + place to sign an email for many domains is likely to be at the point + of SUBMISSION where the sender is often authenticated through SMTP + AUTH or other identifying mechanisms. + +1.5. Conveying Verification Results to MUAs + + It follows that testing the authenticity of an email results in some + action based on the results of the test. Oftentimes, the action is + to notify the MUA in some way -- typically via a header line. + + The "Domainkey-Status:" header is defined in this specification for + recording authentication results in the email. + +1.6. Technical Minutiae Are Not Completely Covered + + The intent of this specification is to communicate the fundamental + characteristics of DomainKeys for an implementor. However, some + aspects are derived from the functionality of the openssl command + [OPENSSL] and, rather than duplicate that documentation, implementors + + + +Delany Historic [Page 5] + +RFC 4870 DomainKeys May 2007 + + + are expected to understand the mechanics of the openssl command, + sufficient to complete the implementation. + +1.7. Motivation + + The motivation for DomainKeys is to define a simple, cheap, and + "sufficiently effective" mechanism by which domain owners can control + who has authority to send email using their domain. To this end, the + designers of DomainKeys set out to build a framework that: + + o is transparent and compatible with the existing email + infrastructure + + o requires no new infrastructure + + o can be implemented independently of clients in order to reduce + deployment time + + o does not require the use of a central certificate authority + that might impose fees for certificates or introduce delays to + deployment + + o can be deployed incrementally + + While we believe that DomainKeys meets these criteria, it is by no + means a perfect solution. The current Internet imposes considerable + compromises on any similar scheme, and readers should be careful not + to misinterpret the information provided in this document to imply + that DomainKeys makes stronger credibility statements than it is able + to do. + +1.8. Benefits of DomainKeys + + As the reader will discover, DomainKeys is solely an authentication + system. It is not a magic bullet for spam, nor is it an + authorization system, a reputation system, a certification system, or + a trust system. + + However, a strong authentication system such as DomainKeys creates an + unimpeachable framework within which comprehensive authorization + systems, reputations systems, and their ilk can be developed. + + + + + + + + + + +Delany Historic [Page 6] + +RFC 4870 DomainKeys May 2007 + + +1.9. Definitions + + With reference to the following sample email: + + Line Data + Number Bytes Content + ---- --- -------------------------------------------- + 01 46 From: "Joe SixPack" + 02 40 To: "Suzie Q" + 03 25 Subject: Is dinner ready? + 04 43 Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) + 05 40 Comment: This comment has a continuation + 06 51 because this line begins with folding white space + 07 60 Message-ID: <20030712040037.46341@football.example.com> + 08 00 + 09 03 Hi. + 10 00 + 11 37 We lost the game. Are you hungry yet? + 12 00 + 13 04 Joe. + 14 00 + 15 00 + + Line 01 is the first line of the email and the first line of the + headers. + + Lines 05 and 06 constitute the "Comment:" header. + + Line 06 is a continuation header line. + + Line 07 is the last line of the headers. + + Line 08 is the empty line that separates the header from the body. + + Line 09 is the first line of the body. + + Lines 10, 12, 14, and 15 are empty lines. + + Line 13 is the last non-empty line of the email. + + Line 15 is the last line of the body and the last line of the email. + + Lines 01 to 15 constitute the complete email. + + Line 01 is earlier than line 02, and line 02 is later than line 01. + + + + + + +Delany Historic [Page 7] + +RFC 4870 DomainKeys May 2007 + + +1.10. Requirements Notation + + This document occasionally uses terms that appear in capital letters. + When the terms "MUST", "SHOULD", "RECOMMENDED", "MUST NOT", "SHOULD + NOT", and "MAY" appear capitalized, they are being used to indicate + particular requirements of this specification. A discussion of the + meanings of these terms appears in [RFC2119]. + +2. DomainKeys Overview + + Under DomainKeys, a domain owner generates one or more private/public + key pairs that will be used to sign messages originating from that + domain. The domain owner places the public key in his domain + namespace (i.e., in a DNS record associated with that domain), and + makes the private key available to the outbound email system. When + an email is submitted by an authorized user of that domain, the email + system uses the private key to digitally sign the email associated + with the sending domain. The signature is added as a header to the + email, and the message is transferred to its recipients in the usual + way. + + When a message is received with a DomainKey signature header, the + receiving system can verify the signature as follows: + + 1. Extract the signature and claimed sending domain from the + email. + + 2. Fetch the public key from the claimed sending domain namespace. + + 3. Use public key to determine whether the signature of the email + has been generated with the corresponding private key, and thus + whether the email was sent with the authority of the claimed + sending domain. + + In the event that an email arrives without a signature or when the + signature verification fails, the receiving system retrieves the + policy of the claimed sending domain to ascertain the preferred + disposition of such email. + + Armed with this information, the recipient system can apply local + policy based on the results of the signature test. + +3. DomainKeys Detailed View + + This section discusses the specifics of DomainKeys that are needed to + create interoperable implementations. This section answers the + following questions: + + + + +Delany Historic [Page 8] + +RFC 4870 DomainKeys May 2007 + + + Given an email, how is the sending domain determined? + + How is the public key retrieved for a sending domain? + + As email transits the email system, it can potentially go through + a number of changes. Which parts of the email are included in the + signature and how are they protected from such transformations? + + How is the signature represented in the email? + + If a signature is not present, or a verification fails, how does + the recipient determine the policy intent of the sending domain? + + Finally, on verifying the authenticity of an email, how is that + result conveyed to participating MUAs? + + While there are many alternative design choices, most lead to + comparable functionality. The overriding selection criteria used to + choose among the alternatives are as follows: + + o use deployed technology whenever possible + + o prefer ease of implementation + + o avoid trading risk for excessive flexibility or + interoperability + + o include basic flexibility + + Adherence to these criteria implies that some existing email + implementations will require changes to participate in DomainKeys. + Ultimately, some hard choices need to be made regarding which + requirements are more important. + +3.1. Determining the Sending Address of an Email + + The goal of DomainKeys is to give the recipient confidence that the + email originated from the claimed sender. As with much of Internet + email, agreement over what constitutes the "sender" is no easy + matter. Forwarding systems and mailing lists add serious + complications to an overtly simple question. From the point of view + of the recipient, the authenticity claim should be directed at the + domain most visible to the recipient. + + In the first instance, the most visible address is clearly the RFC + 2822 "From:" address [RFC2822]. Therefore, a conforming email MUST + contain a single "From:" header from which an email address with a + domain name can be extracted. + + + +Delany Historic [Page 9] + +RFC 4870 DomainKeys May 2007 + + + A conforming email MAY contain a single RFC 2822 "Sender:" header + from which an email address with a domain name can be extracted. + + If the email has a valid "From:" and a valid "Sender:" header, then + the signer MUST use the sending address in the "Sender:" header. + + If the email has a valid "From:" and no "Sender:" header, then the + signer MUST use the first sending address in the "From:" header. + + In all other cases, a signer MUST NOT sign the email. Implementors + should note that an email with a "Sender:" header and no "From:" + header MUST NOT be signed. + + The domain name in the sending address constitutes the "sending + domain". + +3.2. Retrieving the Public Key Given the Sending Domain + + To avoid namespace conflicts, it is proposed that the DNS namespace + "_domainkey." be reserved within the sending domain for storing + public keys, e.g., if the sending domain is example.net, then the + public keys for that domain are stored in the _domainkey.example.net + namespace. + +3.2.1. Introducing "selectors" + + To support multiple concurrent public keys per sending domain, the + DNS namespace is further subdivided with "selectors". Selectors are + arbitrary names below the "_domainkey." namespace. A selector value + and length MUST be legal in the DNS namespace and in email headers + with the additional provision that they cannot contain a semicolon. + + Examples of namespaces using selectors are as follows: + + "coolumbeach._domainkey.example.net" + "sebastopol._domainkey.example.net" + "reykjavik._domainkey.example.net" + "default._domainkey.example.net" + + and + + "2005.pao._domainkey.example.net" + "2005.sql._domainkey.example.net" + "2005.rhv._domainkey.example.net" + + Periods are allowed in selectors and are to be treated as component + separators. In the case of DNS queries, that means the period + defines subdomain boundaries. + + + +Delany Historic [Page 10] + +RFC 4870 DomainKeys May 2007 + + + The number of public keys and corresponding selectors for each domain + is determined by the domain owner. Many domain owners will be + satisfied with just one selector, whereas administratively + distributed organizations may choose to manage disparate selectors + and key pairs in different regions, or on different email servers. + + Beyond administrative convenience, selectors make it possible to + seamlessly replace public keys on a routine basis. If a domain + wishes to change from using a public key associated with selector + "2005" to a public key associated with selector "2006", it merely + makes sure that both public keys are advertised in the DNS + concurrently for the transition period during which email may be in + transit prior to verification. At the start of the transition + period, the outbound email servers are configured to sign with the + "2006" private key. At the end of the transition period, the "2005" + public key is removed from the DNS. + + While some domains may wish to make selector values well known, + others will want to take care not to allocate selector names in a way + that allows harvesting of data by outside parties. For example, if + per-user keys are issued, the domain owner will need to make the + decision as to whether to make this selector associated directly with + the user name or make it some unassociated random value, such as the + fingerprint of the public key. + +3.2.2. Public Key Signing and Verification Algorithm + + The default signature is an RSA signed SHA1 digest of the complete + email. + + For ease of explanation, the openssl command is used throughout this + document to describe the mechanism by which keys and signatures are + managed. + + One way to generate a 768-bit private key suitable for DomainKeys is + to use openssl like this: + + $ openssl genrsa -out rsa.private 768 + + + + + + + + + + + + + +Delany Historic [Page 11] + +RFC 4870 DomainKeys May 2007 + + + which results in the file rsa.private containing the key information + similar to this: + + -----BEGIN RSA PRIVATE KEY----- + MIIByQIBAAJhAKJ2lzDLZ8XlVambQfMXn3LRGKOD5o6lMIgulclWjZwP56LRqdg5 + ZX15bhc/GsvW8xW/R5Sh1NnkJNyL/cqY1a+GzzL47t7EXzVc+nRLWT1kwTvFNGIo + AUsFUq+J6+OprwIDAQABAmBOX0UaLdWWusYzNol++nNZ0RLAtr1/LKMX3tk1MkLH + +Ug13EzB2RZjjDOWlUOY98yxW9/hX05Uc9V5MPo+q2Lzg8wBtyRLqlORd7pfxYCn + Kapi2RPMcR1CxEJdXOkLCFECMQDTO0fzuShRvL8q0m5sitIHlLA/L+0+r9KaSRM/ + 3WQrmUpV+fAC3C31XGjhHv2EuAkCMQDE5U2nP2ZWVlSbxOKBqX724amoL7rrkUew + ti9TEjfaBndGKF2yYF7/+g53ZowRkfcCME/xOJr58VN17pejSl1T8Icj88wGNHCs + FDWGAH4EKNwDSMnfLMG4WMBqd9rzYpkvGQIwLhAHDq2CX4hq2tZAt1zT2yYH7tTb + weiHAQxeHe0RK+x/UuZ2pRhuoSv63mwbMLEZAjAP2vy6Yn+f9SKw2mKuj1zLjEhG + 6ppw+nKD50ncnPoP322UMxVNG4Eah0GYJ4DLP0U= + -----END RSA PRIVATE KEY----- + + Once a private key has been generated, the openssl command can be + used to sign an appropriately prepared email, like this: + + $ openssl dgst -sign rsa.private -sha1 + To: "Suzie Q" + Subject: Is dinner ready? + Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) + Message-ID: <20030712040037.46341.5F8J@football.example.com> + + Hi. + + We lost the game. Are you hungry yet? + + Joe. + +4.2. The Email Is Signed + + This email is signed by the football.example.com outbound email + server and now looks like this: + + DomainKey-Signature: a=rsa-sha1; s=brisbane; d=football.example.com; + c=simple; q=dns; + b=dzdVyOfAKCdLXdJOc9G2q8LoXSlEniSbav+yuU4zGeeruD00lszZ + VoG4ZHRNiYzR; + Received: from dsl-10.2.3.4.football.example.com [10.2.3.4] + by submitserver.football.example.com with SUBMISSION; + Fri, 11 Jul 2003 21:01:54 -0700 (PDT) + From: "Joe SixPack" + To: "Suzie Q" + Subject: Is dinner ready? + Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) + Message-ID: <20030712040037.46341.5F8J@football.example.com> + + Hi. + + We lost the game. Are you hungry yet? + + Joe. + + The signing email server requires access to the private key + associated with the "brisbane" selector to generate this signature. + Distribution and management of private keys are outside the scope of + this document. + + + +Delany Historic [Page 29] + +RFC 4870 DomainKeys May 2007 + + +4.3. The Email Signature Is Verified + + The signature is normally verified by an inbound SMTP server or + possibly the final delivery agent. However, intervening MTAs can + also perform this verification if they choose to do so. + + The verification process uses the domain "football.example.com" + extracted from the "From:" header and the selector "brisbane" from + the "DomainKey-Signature:" header to form the DNS TXT query for: + + brisbane._domainkey.football.example.com + + Since there is no "h" tag in the "DomainKey-Signature:" header, + signature verification starts with the line following the + "DomainKey-Signature:" line. The email is canonically prepared for + verifying with the "simple" method. + + The result of the query and subsequent verification of the signature + is stored in the "DomainKey-Status:" header line. After successful + verification, the email looks like this: + + DomainKey-Status: good + from=joe@football.example.com; domainkeys=pass + Received: from mout23.brisbane.football.example.com (192.168.1.1) + by shopping.example.net with SMTP; + Fri, 11 Jul 2003 21:01:59 -0700 (PDT) + DomainKey-Signature: a=rsa-sha1; s=brisbane; d=football.example.com; + c=simple; q=dns; + b=dzdVyOfAKCdLXdJOc9G2q8LoXSlEniSbav+yuU4zGeeruD00lszZ + VoG4ZHRNiYzR; + Received: from dsl-10.2.3.4.network.example.com [10.2.3.4] + by submitserver.example.com with SUBMISSION; + Fri, 11 Jul 2003 21:01:54 -0700 (PDT) + From: "Joe SixPack" + To: "Suzie Q" + Subject: Is dinner ready? + Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) + Message-ID: <20030712040037.46341.5F8J@football.example.com> + + Hi. + + We lost the game. Are you hungry yet? + + Joe. + + + + + + + +Delany Historic [Page 30] + +RFC 4870 DomainKeys May 2007 + + +5. Association with a Certificate Authority + + A fundamental aspect of DomainKeys is that public keys are generated + and advertised by each domain at no additional cost. This + accessibility markedly differs from traditional Public Key + Infrastructures where there is typically a Certificate Authority (CA) + who validates an applicant and issues a signed certificate -- + containing their public key -- often for a recurring fee. + + While CAs do impose costs, they also have the potential to provide + additional value as part of their certification process. Consider + financial institutions, public utilities, law enforcement agencies, + and the like. In many cases, such entities justifiably need to + discriminate themselves above and beyond the authentication that + DomainKeys offers. + + Creating a link between DomainKeys and CA-issued certificates has the + potential to access additional authentication mechanisms that are + more authoritative than domain-owner-issued authentication. It is + well beyond the scope of this specification to describe such + authorities apart from defining how the linkage could be achieved + with the "DomainKey-X509:" header. + +5.1. The "DomainKey-X509:" Header + + The "DomainKey-X509:" header provides a link between the public key + used to sign the email and the certificate issued by a CA. + + The exact content, syntax, and semantics of this header are yet to be + resolved. One possibility is that this header contains an encoding + of the certificate issued by a CA. Another possibility is that this + header contains a URL that points to a certificate issued by a CA. + + In either case, this header can only be consulted if the signature + verifies and MUST be part of the content signed by the corresponding + "DomainKey-Signature:" header. Furthermore, it is likely that MUAs + rather than MTAs will confirm that the link to the CA-issued + certificate is valid. In part, this is because many MUAs already + have built-in capabilities as a consequence of Secure/Multipurpose + Internet Mail Extensions (S/MIME) [SMIME] and Secure Socket Layer + (SSL) [SSL] support. + + The proof of linkage is made by testing that the public key in the + certificate matches the public key used to sign the email. + + + + + + + +Delany Historic [Page 31] + +RFC 4870 DomainKeys May 2007 + + + An example of an email containing the "DomainKey-X509:" header is: + + DomainKey-Signature: a=rsa-sha1; s=statements; + d=largebank.example.com; c=simple; q=dns; + b=dzdVyOfAKCdLXdJOc9G2q8LoXSlEniSbav+yuU4zGeeruD00lszZ + VoG4ZHRNiYzR; + DomainKey-X509: https://ca.example.net/largebank.example.com + From: "Large Bank" + To: "Suzie Q" + Subject: Statement for Account: 1234-5678 + ... + + The format of the retrieved value from the URL is not yet defined, + nor is the determination of valid CAs. + + The whole matter of linkage to CA-issued certificates is one aspect + of DomainKeys that needs to be resolved with relevant CA's and + certificate-issuing entities. The primary point is that a link is + possible to a higher authority. + +6. Topics for Discussion + +6.1. The Benefits of Selectors + + Selectors are at the heart of the flexibility of DomainKeys. A + domain administrator is free to use a single DomainKey for all + outbound mail. Alternatively, the domain administrator may use many + DomainKeys differentiated by selector and assign each key to + different servers. + + For example, a large outbound email farm might have a unique + DomainKey for each server, and thus their DNS will advertise + potentially hundreds of keys via their unique selectors. + + Another example is a corporate email administrator who might generate + a separate DomainKey for each regional office email server. + + In essence, selectors allow a domain owner to distribute authority to + send on behalf of that domain. Combined with the ability to revoke + by removal or Time to Live (TTL) expiration, a domain owner has + coarse-grained control over the duration of the distributed + authority. + + Selectors are particularly useful for domain owners who want to + contract a third-party mailing system to send a particular set of + mail. The domain owner can generate a special key pair and selector + just for this mail-out. The domain owner has to provide the private + key and selector to the third party for the life of the mail-out. + + + +Delany Historic [Page 32] + +RFC 4870 DomainKeys May 2007 + + + However, as soon as the mail-out is completely delivered, the domain + owner can revoke the public key by the simple expedient of removing + the entry from the DNS. + +6.2. Canonicalization of Email + + It is an unfortunate fact that some email software routinely (and + often unnecessarily) transforms email as it transits through the + network. Such transformations conflict with the fundamental purpose + of cryptographic signatures - to detect modifications. + + While two canonicalization algorithms are defined in this + specification, the primary goal of "nofws" is to provide a transition + path to "simple". With a mixture of "simple" and "nofws" email, a + receiver can determine which systems are modifying email in ways that + cause the signature to fail and thus provide feedback to the + modifying system. + +6.3. Mailing Lists + + Integrating existing Mailing List Managers (MLMs) into the DomainKeys + authentication system is a complicated area, as the behavior of MLMs + is highly variable. Essentially, there are two types of MLMs under + consideration: those that modify email to such an extent that + verification of the original content is not possible, and those that + make minimal or no modifications to an email. + + MLMs that modify email in a way that causes verification to fail MUST + prepend a "Sender:" header and SHOULD prepend a "List-ID:" header + prior to signing for distribution to list recipients. + + A participating SUBMISSION server can deduce the need to re-sign such + an email by the presence of a "Sender:" or "List-ID:" header from an + authorized submission. + + MLMs that do not modify email in a way that causes verification to + fail MAY perform the same actions as a modifying MLM. + +6.4. Roving Users + + One scenario that presents a particular problem with any form of + email authentication, including DomainKeys, is the roving user: a + user who is obliged to use a third-party SUBMISSION service when + unable to connect to the user's own SUBMISSION service. The classic + example cited is a traveling salesperson being redirected to a hotel + email server to send email. + + + + + +Delany Historic [Page 33] + +RFC 4870 DomainKeys May 2007 + + + As far as DomainKeys is concerned, email of this nature clearly + originates from an email server that does not have authority to send + on behalf of the domain of the salesperson and is therefore + indistinguishable from a forgery. While DomainKeys does not + prescribe any specific action for such email, it is likely that over + time, such email will be treated as second-class email. + + The typical solution offered to roving users is to submit email via + an authorized server for their domain -- perhaps via a Virtual + Private Network (VPN) or a web interface or even SMTP AUTH back to a + SUBMISSION server. + + While these are perfectly acceptable solutions for many, they are not + necessarily solutions that are available or possible for all such + users. + + One possible way to address the needs of this contingent of + potentially disenfranchised users is for the domain to issue per-user + DomainKeys. Per-user DomainKeys are identified by a non-empty "g" + tag value in the corresponding DNS record. + +7. Security Considerations + +7.1. DNS + + DomainKeys is primarily a security mechanism. Its core purpose is to + make claims about email authentication in a credible way. However, + DomainKeys, like virtually all Internet applications, relies on the + DNS, which has well-known security flaws [RFC3833]. + +7.1.1. The DNS Is Not Currently Secure + + While the DNS is currently insecure, it is expected that the security + problems should and will be solved by DNS Security (DNSSEC) [DNSSEC], + and all users of the DNS will reap the benefit of that work. + + Secondly, the types of DNS attacks relevant to DomainKeys are very + costly and are far less rewarding than DNS attacks on other Internet + applications. + + To systematically thwart the intent of DomainKeys, an attacker must + conduct a very costly and very extensive attack on many parts of the + DNS over an extended period. No one knows for sure how attackers + will respond; however, the cost/benefit of conducting prolonged DNS + attacks of this nature is expected to be uneconomical. + + Finally, DomainKeys is only intended as a "sufficient" method of + proving authenticity. It is not intended to provide strong + + + +Delany Historic [Page 34] + +RFC 4870 DomainKeys May 2007 + + + cryptographic proof about authorship or contents. Other technologies + such as GnuPG and S/MIME address those requirements. + +7.1.2. DomainKeys Creates Additional DNS Load + + A second security issue related to the DNS revolves around the + increased DNS traffic as a consequence of fetching selector-based + data, as well as fetching sending domain policy. Widespread + deployment of DomainKeys will result in a significant increase in DNS + queries to the claimed sending domain. In the case of forgeries on a + large scale, DNS servers could see a substantial increase in queries. + +7.2. Key Management + + All public key systems require management of key pairs. Private keys + in particular need to be securely distributed to each signing mail + server and protected on those servers. For those familiar with SSL, + the key management issues are similar to those of managing SSL + certificates. Poor key management may result in unauthorized access + to private keys, which in essence gives unauthorized access to your + identity. + +7.3. Implementation Risks + + It is well recognized in cryptographic circles that many security + failures are caused by poor implementations rather than poor + algorithms. For example, early SSL implementations were vulnerable + because the implementors used predictable "random numbers". + + While some MTA software already supports various cryptographic + techniques, such as TLS, many do not. This proposal introduces + cryptographic requirements into MTA software that implies a much + higher duty of care to manage the increased risk. + + There are numerous articles, books, courses, and consultants that + help programming security applications. Potential implementors are + strongly encouraged to avail themselves of all possible resources to + ensure secure implementations. + +7.4. Privacy Assumptions with Forwarding Addresses + + Some people believe that they can achieve anonymity by using an email + forwarding service. While this has never been particularly true, as + bounces, over-quota messages, vacation messages, and web bugs all + conspire to expose IP addresses and domain names associated with the + delivery path, the DNS queries that are required to verify DomainKeys + signature can provide additional information to the sender. + + + + +Delany Historic [Page 35] + +RFC 4870 DomainKeys May 2007 + + + In particular, as mail is forwarded through the mail network, the DNS + queries for the selector will typically identify the DNS cache used + by the forwarding and delivery MTAs. + +7.5. Cryptographic Processing Is Computationally Intensive + + Verifying a signature is computationally significant. Early + indications are that a typical mail server can expect to increase CPU + demands by 8-15 percent. While this increased demand is modest + compared to other common mail processing costs -- such as Bayesian + filtering -- any increase in resource requirements can make a + denial-of-service attack more effective against a mail system. + + A constraining factor of such attacks is that the net computational + cost of verifying is bounded by the maximum key size allowed by this + specification and is essentially linear to the rate at which mail is + accepted by the verifying system. Consequently, the additional + computational cost may augment a denial-of-service attack, but it + does not add a non-linear component to such attacks. + +8. The Trial + + The DomainKeys protocol was deployed as a trial to better understand + the implications of deploying wide-scale cryptographic email + authentication. + + Open Source implementations were made available at various places, + particularly Source Forge [SOURCEFORGE], which includes links to + numerous implementations, both Open Source and commercial. + +8.1. Goals + + The primary goals of the trial were to: + + o understand the operational implications of running a DNS-based + public key system for email + + o measure the effectiveness of the canonicalization algorithms + + o experiment with possible per-user key deployment models + + o fully define the semantics of the "DomainKey-X509:" header + + + + + + + + + +Delany Historic [Page 36] + +RFC 4870 DomainKeys May 2007 + + +8.2. Results of Trial + + The DomainKeys trial ran for approximately 2 years, in which time + numerous large ISPs and many thousands of smaller domains + participated in signing or verifying with DomainKeys. The low order + numbers are that at least one billion DomainKey signed emails transit + the Internet each day between some 12,000 participating domains. + + The operational and development experience of that trial was applied + to DKIM. + +9. Note to Implementors Regarding TXT Records + + The DNS is very flexible in that it is possible to have multiple TXT + records for a single name and for those TXT records to contain + multiple strings. + + In all cases, implementors of DomainKeys should expect a single TXT + record for any particular name. If multiple TXT records are + returned, the implementation is free to pick any single TXT record as + the authoritative data. In other words, if a name server returns + different TXT records for the same name, it can expect unpredictable + results. + + Within a single TXT record, implementors should concatenate multiple + strings in the order presented and ignore string boundaries. Note + that a number of popular DNS command-line tools render multiple + strings as separately quoted strings, which can be misleading to a + novice implementor. + +10. References + +10.1. Normative References + + [BASE64] Josefsson, S., "The Base16, Base32, and Base64 Data + Encodings", RFC 4648, October 2006. + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [PEM] Linn, J., "Privacy Enhancement for Internet Electronic + Mail: Part I: Message Encryption and Authentication + Procedures", RFC 1421 February, 1993. + + + + + + + + +Delany Historic [Page 37] + +RFC 4870 DomainKeys May 2007 + + +10.2. Informative References + + [DKIM] Allman, E., Callas, J., Delany, M., Libbey, M., Fenton, + J., and M. Thomas, "DomainKeys Identified Mail (DKIM) + Signatures", RFC 4871, May 2007. + + [DNSSEC] http://www.ietf.org/html.charters/dnsext-charter.html + + [OPENSSL] http://www.openssl.org + + [RFC2822] Resnick, P., Editor, "Internet Message Format", RFC + 2822, April 2001. + + [RFC3833] Atkins, D. and R. Austein, "Threat Analysis of the + Domain Name System (DNS)", RFC 3833, August 2004. + + [SMIME] Ramsdell, B., Ed., "Secure/Multipurpose Internet Mail + Extensions (S/MIME) Version 3.1 Message Specification", + RFC 3851, July 2004. + + [SOURCEFORGE] http://domainkeys.sourceforge.net + + [SSL] http://wp.netscape.com/security/techbriefs/ssl.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Delany Historic [Page 38] + +RFC 4870 DomainKeys May 2007 + + +Appendix A - Syntax Rules for the Tag=Value Format + + A simple tag=value syntax is used to encode data in the response + values for DNS queries as well as headers embedded in emails. This + section summarized the syntactic rules for this encoding: + + o A tag=value pair consists of three tokens, a "tag", the "=" + character, and the "value" + + o A tag MUST be one character long and MUST be a lowercase + alphabetic character + + o Duplicate tags are not allowed + + o A value MUST only consist of characters that are valid in RFC + 2822 headers and DNS TXT records and are within the ASCII range + of characters from SPACE (0x20) to TILDE (0x7E) inclusive. + Values MUST NOT contain a semicolon but they may contain "=" + characters. + + o A tag=value pair MUST be terminated by a semicolon or the end + of the data + + o Values MUST be processed as case sensitive unless the specific + tag description of semantics imply case insensitivity. + + o Values MAY be zero bytes long + + o Whitespace MAY surround any of the tokens; however, whitespace + within a value MUST be retained unless explicitly excluded by + the specific tag description. Currently, the only tags that + specifically ignore embedded whitespace are the "b" and "h" + tags in the "DomainKey-Signature:" header. + + o Tag=value pairs that represent the default value MAY be + included to aid legibility. + + o Unrecognized tags MUST be ignored + + + + + + + + + + + + + +Delany Historic [Page 39] + +RFC 4870 DomainKeys May 2007 + + +Acknowledgments + + The editor wishes to thank Russ Allbery, Eric Allman, Edwin Aoki, + Claus Asmann, Steve Atkins, Jon Callas, Dave Crocker, Michael Cudahy, + Jutta Degener, Timothy Der, Jim Fenton, Duncan Findlay, Phillip + Hallam-Baker, Murray S. Kucherawy, John Levine, Miles Libbey, David + Margrave, Justin Mason, David Mayne, Russell Nelson, Juan Altmayer + Pizzorno, Blake Ramsdell, Scott Renfro, the Spamhaus.org team, Malte + S. Stretz, Robert Sanders, Bradley Taylor, and Rand Wacker for their + valuable suggestions and constructive criticism. + +Author's Address + + Mark Delany + Yahoo! Inc + 701 First Avenue + Sunnyvale, CA 95087 + USA + + EMail: markd+domainkeys@yahoo-inc.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Delany Historic [Page 40] + +RFC 4870 DomainKeys May 2007 + + +Full Copyright Statement + + Copyright (C) The IETF Trust (2007). + + This document is subject to the rights, licenses and restrictions + contained in BCP 78, and except as set forth therein, the authors + retain all their rights. + + This document and the information contained herein are provided on an + "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS + OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE IETF TRUST AND + THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF + THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED + WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Intellectual Property + + The IETF takes no position regarding the validity or scope of any + Intellectual Property Rights or other rights that might be claimed to + pertain to the implementation or use of the technology described in + this document or the extent to which any license under such rights + might or might not be available; nor does it represent that it has + made any independent effort to identify any such rights. Information + on the procedures with respect to rights in RFC documents can be + found in BCP 78 and BCP 79. + + Copies of IPR disclosures made to the IETF Secretariat and any + assurances of licenses to be made available, or the result of an + attempt made to obtain a general license or permission for the use of + such proprietary rights by implementers or users of this + specification can be obtained from the IETF on-line IPR repository at + http://www.ietf.org/ipr. + + The IETF invites any interested party to bring to its attention any + copyrights, patents or patent applications, or other proprietary + rights that may cover technology that may be required to implement + this standard. Please address the information to the IETF at + ietf-ipr@ietf.org. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + +Delany Historic [Page 41] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4871.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4871.txt new file mode 100644 index 0000000..80410e6 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4871.txt @@ -0,0 +1,3978 @@ + + + + + +Network Working Group E. Allman +Request for Comments: 4871 Sendmail, Inc. +Obsoletes: 4870 J. Callas +Category: Standards Track PGP Corporation + M. Delany + M. Libbey + Yahoo! Inc + J. Fenton + M. Thomas + Cisco Systems, Inc. + May 2007 + + + DomainKeys Identified Mail (DKIM) Signatures + +Status of This Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The IETF Trust (2007). + +Abstract + + DomainKeys Identified Mail (DKIM) defines a domain-level + authentication framework for email using public-key cryptography and + key server technology to permit verification of the source and + contents of messages by either Mail Transfer Agents (MTAs) or Mail + User Agents (MUAs). The ultimate goal of this framework is to permit + a signing domain to assert responsibility for a message, thus + protecting message signer identity and the integrity of the messages + they convey while retaining the functionality of Internet email as it + is known today. Protection of email identity may assist in the + global control of "spam" and "phishing". + + + + + + + + + + + + +Allman, et al. Standards Track [Page 1] + +RFC 4871 DKIM Signatures May 2007 + + +Table of Contents + + 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 4 + 1.1. Signing Identity . . . . . . . . . . . . . . . . . . . . . 5 + 1.2. Scalability . . . . . . . . . . . . . . . . . . . . . . . 5 + 1.3. Simple Key Management . . . . . . . . . . . . . . . . . . 5 + 2. Terminology and Definitions . . . . . . . . . . . . . . . . . 5 + 2.1. Signers . . . . . . . . . . . . . . . . . . . . . . . . . 6 + 2.2. Verifiers . . . . . . . . . . . . . . . . . . . . . . . . 6 + 2.3. Whitespace . . . . . . . . . . . . . . . . . . . . . . . . 6 + 2.4. Common ABNF Tokens . . . . . . . . . . . . . . . . . . . . 6 + 2.5. Imported ABNF Tokens . . . . . . . . . . . . . . . . . . . 7 + 2.6. DKIM-Quoted-Printable . . . . . . . . . . . . . . . . . . 7 + 3. Protocol Elements . . . . . . . . . . . . . . . . . . . . . . 8 + 3.1. Selectors . . . . . . . . . . . . . . . . . . . . . . . . 8 + 3.2. Tag=Value Lists . . . . . . . . . . . . . . . . . . . . . 10 + 3.3. Signing and Verification Algorithms . . . . . . . . . . . 11 + 3.4. Canonicalization . . . . . . . . . . . . . . . . . . . . . 13 + 3.5. The DKIM-Signature Header Field . . . . . . . . . . . . . 17 + 3.6. Key Management and Representation . . . . . . . . . . . . 25 + 3.7. Computing the Message Hashes . . . . . . . . . . . . . . . 29 + 3.8. Signing by Parent Domains . . . . . . . . . . . . . . . . 31 + 4. Semantics of Multiple Signatures . . . . . . . . . . . . . . . 32 + 4.1. Example Scenarios . . . . . . . . . . . . . . . . . . . . 32 + 4.2. Interpretation . . . . . . . . . . . . . . . . . . . . . . 33 + 5. Signer Actions . . . . . . . . . . . . . . . . . . . . . . . . 34 + 5.1. Determine Whether the Email Should Be Signed and by + Whom . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 + 5.2. Select a Private Key and Corresponding Selector + Information . . . . . . . . . . . . . . . . . . . . . . . 35 + 5.3. Normalize the Message to Prevent Transport Conversions . . 35 + 5.4. Determine the Header Fields to Sign . . . . . . . . . . . 36 + 5.5. Recommended Signature Content . . . . . . . . . . . . . . 38 + 5.6. Compute the Message Hash and Signature . . . . . . . . . . 39 + 5.7. Insert the DKIM-Signature Header Field . . . . . . . . . . 40 + 6. Verifier Actions . . . . . . . . . . . . . . . . . . . . . . . 40 + 6.1. Extract Signatures from the Message . . . . . . . . . . . 41 + 6.2. Communicate Verification Results . . . . . . . . . . . . . 46 + 6.3. Interpret Results/Apply Local Policy . . . . . . . . . . . 47 + 7. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 48 + 7.1. DKIM-Signature Tag Specifications . . . . . . . . . . . . 48 + 7.2. DKIM-Signature Query Method Registry . . . . . . . . . . . 49 + 7.3. DKIM-Signature Canonicalization Registry . . . . . . . . . 49 + 7.4. _domainkey DNS TXT Record Tag Specifications . . . . . . . 50 + 7.5. DKIM Key Type Registry . . . . . . . . . . . . . . . . . . 50 + 7.6. DKIM Hash Algorithms Registry . . . . . . . . . . . . . . 51 + 7.7. DKIM Service Types Registry . . . . . . . . . . . . . . . 51 + 7.8. DKIM Selector Flags Registry . . . . . . . . . . . . . . . 52 + + + +Allman, et al. Standards Track [Page 2] + +RFC 4871 DKIM Signatures May 2007 + + + 7.9. DKIM-Signature Header Field . . . . . . . . . . . . . . . 52 + 8. Security Considerations . . . . . . . . . . . . . . . . . . . 52 + 8.1. Misuse of Body Length Limits ("l=" Tag) . . . . . . . . . 52 + 8.2. Misappropriated Private Key . . . . . . . . . . . . . . . 53 + 8.3. Key Server Denial-of-Service Attacks . . . . . . . . . . . 54 + 8.4. Attacks Against the DNS . . . . . . . . . . . . . . . . . 54 + 8.5. Replay Attacks . . . . . . . . . . . . . . . . . . . . . . 55 + 8.6. Limits on Revoking Keys . . . . . . . . . . . . . . . . . 55 + 8.7. Intentionally Malformed Key Records . . . . . . . . . . . 56 + 8.8. Intentionally Malformed DKIM-Signature Header Fields . . . 56 + 8.9. Information Leakage . . . . . . . . . . . . . . . . . . . 56 + 8.10. Remote Timing Attacks . . . . . . . . . . . . . . . . . . 56 + 8.11. Reordered Header Fields . . . . . . . . . . . . . . . . . 56 + 8.12. RSA Attacks . . . . . . . . . . . . . . . . . . . . . . . 56 + 8.13. Inappropriate Signing by Parent Domains . . . . . . . . . 57 + 9. References . . . . . . . . . . . . . . . . . . . . . . . . . . 57 + 9.1. Normative References . . . . . . . . . . . . . . . . . . . 57 + 9.2. Informative References . . . . . . . . . . . . . . . . . . 58 + Appendix A. Example of Use (INFORMATIVE) . . . . . . . . . . . . 60 + A.1. The user composes an email . . . . . . . . . . . . . . . . 60 + A.2. The email is signed . . . . . . . . . . . . . . . . . . . 61 + A.3. The email signature is verified . . . . . . . . . . . . . 61 + Appendix B. Usage Examples (INFORMATIVE) . . . . . . . . . . . . 62 + B.1. Alternate Submission Scenarios . . . . . . . . . . . . . . 63 + B.2. Alternate Delivery Scenarios . . . . . . . . . . . . . . . 65 + Appendix C. Creating a Public Key (INFORMATIVE) . . . . . . . . . 67 + Appendix D. MUA Considerations . . . . . . . . . . . . . . . . . 68 + Appendix E. Acknowledgements . . . . . . . . . . . . . . . . . . 69 + + + + + + + + + + + + + + + + + + + + + + + +Allman, et al. Standards Track [Page 3] + +RFC 4871 DKIM Signatures May 2007 + + +1. Introduction + + DomainKeys Identified Mail (DKIM) defines a mechanism by which email + messages can be cryptographically signed, permitting a signing domain + to claim responsibility for the introduction of a message into the + mail stream. Message recipients can verify the signature by querying + the signer's domain directly to retrieve the appropriate public key, + and thereby confirm that the message was attested to by a party in + possession of the private key for the signing domain. + + The approach taken by DKIM differs from previous approaches to + message signing (e.g., Secure/Multipurpose Internet Mail Extensions + (S/MIME) [RFC1847], OpenPGP [RFC2440]) in that: + + o the message signature is written as a message header field so that + neither human recipients nor existing MUA (Mail User Agent) + software is confused by signature-related content appearing in the + message body; + + o there is no dependency on public and private key pairs being + issued by well-known, trusted certificate authorities; + + o there is no dependency on the deployment of any new Internet + protocols or services for public key distribution or revocation; + + o signature verification failure does not force rejection of the + message; + + o no attempt is made to include encryption as part of the mechanism; + + o message archiving is not a design goal. + + DKIM: + + o is compatible with the existing email infrastructure and + transparent to the fullest extent possible; + + o requires minimal new infrastructure; + + o can be implemented independently of clients in order to reduce + deployment time; + + o can be deployed incrementally; + + o allows delegation of signing to third parties. + + + + + + +Allman, et al. Standards Track [Page 4] + +RFC 4871 DKIM Signatures May 2007 + + +1.1. Signing Identity + + DKIM separates the question of the identity of the signer of the + message from the purported author of the message. In particular, a + signature includes the identity of the signer. Verifiers can use the + signing information to decide how they want to process the message. + The signing identity is included as part of the signature header + field. + + INFORMATIVE RATIONALE: The signing identity specified by a DKIM + signature is not required to match an address in any particular + header field because of the broad methods of interpretation by + recipient mail systems, including MUAs. + +1.2. Scalability + + DKIM is designed to support the extreme scalability requirements that + characterize the email identification problem. There are currently + over 70 million domains and a much larger number of individual + addresses. DKIM seeks to preserve the positive aspects of the + current email infrastructure, such as the ability for anyone to + communicate with anyone else without introduction. + +1.3. Simple Key Management + + DKIM differs from traditional hierarchical public-key systems in that + no Certificate Authority infrastructure is required; the verifier + requests the public key from a repository in the domain of the + claimed signer directly rather than from a third party. + + The DNS is proposed as the initial mechanism for the public keys. + Thus, DKIM currently depends on DNS administration and the security + of the DNS system. DKIM is designed to be extensible to other key + fetching services as they become available. + +2. Terminology and Definitions + + This section defines terms used in the rest of the document. Syntax + descriptions use the form described in Augmented BNF for Syntax + Specifications [RFC4234]. + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [RFC2119]. + + + + + + + +Allman, et al. Standards Track [Page 5] + +RFC 4871 DKIM Signatures May 2007 + + +2.1. Signers + + Elements in the mail system that sign messages on behalf of a domain + are referred to as signers. These may be MUAs (Mail User Agents), + MSAs (Mail Submission Agents), MTAs (Mail Transfer Agents), or other + agents such as mailing list exploders. In general, any signer will + be involved in the injection of a message into the message system in + some way. The key issue is that a message must be signed before it + leaves the administrative domain of the signer. + +2.2. Verifiers + + Elements in the mail system that verify signatures are referred to as + verifiers. These may be MTAs, Mail Delivery Agents (MDAs), or MUAs. + In most cases it is expected that verifiers will be close to an end + user (reader) of the message or some consuming agent such as a + mailing list exploder. + +2.3. Whitespace + + There are three forms of whitespace: + + o WSP represents simple whitespace, i.e., a space or a tab character + (formal definition in [RFC4234]). + + o LWSP is linear whitespace, defined as WSP plus CRLF (formal + definition in [RFC4234]). + + o FWS is folding whitespace. It allows multiple lines separated by + CRLF followed by at least one whitespace, to be joined. + + The formal ABNF for these are (WSP and LWSP are given for information + only): + + WSP = SP / HTAB + LWSP = *(WSP / CRLF WSP) + FWS = [*WSP CRLF] 1*WSP + + The definition of FWS is identical to that in [RFC2822] except for + the exclusion of obs-FWS. + +2.4. Common ABNF Tokens + + The following ABNF tokens are used elsewhere in this document: + hyphenated-word = ALPHA [ *(ALPHA / DIGIT / "-") (ALPHA / DIGIT) ] + base64string = 1*(ALPHA / DIGIT / "+" / "/" / [FWS]) + [ "=" [FWS] [ "=" [FWS] ] ] + + + + +Allman, et al. Standards Track [Page 6] + +RFC 4871 DKIM Signatures May 2007 + + +2.5. Imported ABNF Tokens + + The following tokens are imported from other RFCs as noted. Those + RFCs should be considered definitive. + + The following tokens are imported from [RFC2821]: + + o "Local-part" (implementation warning: this permits quoted strings) + + o "sub-domain" + + The following tokens are imported from [RFC2822]: + + o "field-name" (name of a header field) + + o "dot-atom-text" (in the Local-part of an email address) + + The following tokens are imported from [RFC2045]: + + o "qp-section" (a single line of quoted-printable-encoded text) + + o "hex-octet" (a quoted-printable encoded octet) + + INFORMATIVE NOTE: Be aware that the ABNF in RFC 2045 does not obey + the rules of RFC 4234 and must be interpreted accordingly, + particularly as regards case folding. + + Other tokens not defined herein are imported from [RFC4234]. These + are intuitive primitives such as SP, HTAB, WSP, ALPHA, DIGIT, CRLF, + etc. + +2.6. DKIM-Quoted-Printable + + The DKIM-Quoted-Printable encoding syntax resembles that described in + Quoted-Printable [RFC2045], Section 6.7: any character MAY be encoded + as an "=" followed by two hexadecimal digits from the alphabet + "0123456789ABCDEF" (no lowercase characters permitted) representing + the hexadecimal-encoded integer value of that character. All control + characters (those with values < %x20), 8-bit characters (values > + %x7F), and the characters DEL (%x7F), SPACE (%x20), and semicolon + (";", %x3B) MUST be encoded. Note that all whitespace, including + SPACE, CR, and LF characters, MUST be encoded. After encoding, FWS + MAY be added at arbitrary locations in order to avoid excessively + long lines; such whitespace is NOT part of the value, and MUST be + removed before decoding. + + + + + + +Allman, et al. Standards Track [Page 7] + +RFC 4871 DKIM Signatures May 2007 + + + ABNF: + + dkim-quoted-printable = + *(FWS / hex-octet / dkim-safe-char) + ; hex-octet is from RFC 2045 + dkim-safe-char = %x21-3A / %x3C / %x3E-7E + ; '!' - ':', '<', '>' - '~' + ; Characters not listed as "mail-safe" in + ; RFC 2049 are also not recommended. + + INFORMATIVE NOTE: DKIM-Quoted-Printable differs from Quoted- + Printable as defined in RFC 2045 in several important ways: + + 1. Whitespace in the input text, including CR and LF, must be + encoded. RFC 2045 does not require such encoding, and does + not permit encoding of CR or LF characters that are part of a + CRLF line break. + + 2. Whitespace in the encoded text is ignored. This is to allow + tags encoded using DKIM-Quoted-Printable to be wrapped as + needed. In particular, RFC 2045 requires that line breaks in + the input be represented as physical line breaks; that is not + the case here. + + 3. The "soft line break" syntax ("=" as the last non-whitespace + character on the line) does not apply. + + 4. DKIM-Quoted-Printable does not require that encoded lines be + no more than 76 characters long (although there may be other + requirements depending on the context in which the encoded + text is being used). + +3. Protocol Elements + + Protocol Elements are conceptual parts of the protocol that are not + specific to either signers or verifiers. The protocol descriptions + for signers and verifiers are described in later sections (Signer + Actions (Section 5) and Verifier Actions (Section 6)). NOTE: This + section must be read in the context of those sections. + +3.1. Selectors + + To support multiple concurrent public keys per signing domain, the + key namespace is subdivided using "selectors". For example, + selectors might indicate the names of office locations (e.g., + "sanfrancisco", "coolumbeach", and "reykjavik"), the signing date + (e.g., "january2005", "february2005", etc.), or even the individual + user. + + + +Allman, et al. Standards Track [Page 8] + +RFC 4871 DKIM Signatures May 2007 + + + Selectors are needed to support some important use cases. For + example: + + o Domains that want to delegate signing capability for a specific + address for a given duration to a partner, such as an advertising + provider or other outsourced function. + + o Domains that want to allow frequent travelers to send messages + locally without the need to connect with a particular MSA. + + o "Affinity" domains (e.g., college alumni associations) that + provide forwarding of incoming mail, but that do not operate a + mail submission agent for outgoing mail. + + Periods are allowed in selectors and are component separators. When + keys are retrieved from the DNS, periods in selectors define DNS + label boundaries in a manner similar to the conventional use in + domain names. Selector components might be used to combine dates + with locations, for example, "march2005.reykjavik". In a DNS + implementation, this can be used to allow delegation of a portion of + the selector namespace. + + ABNF: + + selector = sub-domain *( "." sub-domain ) + + The number of public keys and corresponding selectors for each domain + is determined by the domain owner. Many domain owners will be + satisfied with just one selector, whereas administratively + distributed organizations may choose to manage disparate selectors + and key pairs in different regions or on different email servers. + + Beyond administrative convenience, selectors make it possible to + seamlessly replace public keys on a routine basis. If a domain + wishes to change from using a public key associated with selector + "january2005" to a public key associated with selector + "february2005", it merely makes sure that both public keys are + advertised in the public-key repository concurrently for the + transition period during which email may be in transit prior to + verification. At the start of the transition period, the outbound + email servers are configured to sign with the "february2005" private + key. At the end of the transition period, the "january2005" public + key is removed from the public-key repository. + + INFORMATIVE NOTE: A key may also be revoked as described below. + The distinction between revoking and removing a key selector + record is subtle. When phasing out keys as described above, a + signing domain would probably simply remove the key record after + + + +Allman, et al. Standards Track [Page 9] + +RFC 4871 DKIM Signatures May 2007 + + + the transition period. However, a signing domain could elect to + revoke the key (but maintain the key record) for a further period. + There is no defined semantic difference between a revoked key and + a removed key. + + While some domains may wish to make selector values well known, + others will want to take care not to allocate selector names in a way + that allows harvesting of data by outside parties. For example, if + per-user keys are issued, the domain owner will need to make the + decision as to whether to associate this selector directly with the + user name, or make it some unassociated random value, such as a + fingerprint of the public key. + + INFORMATIVE OPERATIONS NOTE: Reusing a selector with a new key + (for example, changing the key associated with a user's name) + makes it impossible to tell the difference between a message that + didn't verify because the key is no longer valid versus a message + that is actually forged. For this reason, signers are ill-advised + to reuse selectors for new keys. A better strategy is to assign + new keys to new selectors. + +3.2. Tag=Value Lists + + DKIM uses a simple "tag=value" syntax in several contexts, including + in messages and domain signature records. + + Values are a series of strings containing either plain text, "base64" + text (as defined in [RFC2045], Section 6.8), "qp-section" (ibid, + Section 6.7), or "dkim-quoted-printable" (as defined in Section 2.6). + The name of the tag will determine the encoding of each value. + Unencoded semicolon (";") characters MUST NOT occur in the tag value, + since that separates tag-specs. + + INFORMATIVE IMPLEMENTATION NOTE: Although the "plain text" defined + below (as "tag-value") only includes 7-bit characters, an + implementation that wished to anticipate future standards would be + advised not to preclude the use of UTF8-encoded text in tag=value + lists. + + + + + + + + + + + + + +Allman, et al. Standards Track [Page 10] + +RFC 4871 DKIM Signatures May 2007 + + + Formally, the syntax rules are as follows: + + tag-list = tag-spec 0*( ";" tag-spec ) [ ";" ] + tag-spec = [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS] + tag-name = ALPHA 0*ALNUMPUNC + tag-value = [ tval 0*( 1*(WSP / FWS) tval ) ] + ; WSP and FWS prohibited at beginning and end + tval = 1*VALCHAR + VALCHAR = %x21-3A / %x3C-7E + ; EXCLAMATION to TILDE except SEMICOLON + ALNUMPUNC = ALPHA / DIGIT / "_" + + Note that WSP is allowed anywhere around tags. In particular, any + WSP after the "=" and any WSP before the terminating ";" is not part + of the value; however, WSP inside the value is significant. + + Tags MUST be interpreted in a case-sensitive manner. Values MUST be + processed as case sensitive unless the specific tag description of + semantics specifies case insensitivity. + + Tags with duplicate names MUST NOT occur within a single tag-list; if + a tag name does occur more than once, the entire tag-list is invalid. + + Whitespace within a value MUST be retained unless explicitly excluded + by the specific tag description. + + Tag=value pairs that represent the default value MAY be included to + aid legibility. + + Unrecognized tags MUST be ignored. + + Tags that have an empty value are not the same as omitted tags. An + omitted tag is treated as having the default value; a tag with an + empty value explicitly designates the empty string as the value. For + example, "g=" does not mean "g=*", even though "g=*" is the default + for that tag. + +3.3. Signing and Verification Algorithms + + DKIM supports multiple digital signature algorithms. Two algorithms + are defined by this specification at this time: rsa-sha1 and rsa- + sha256. The rsa-sha256 algorithm is the default if no algorithm is + specified. Verifiers MUST implement both rsa-sha1 and rsa-sha256. + Signers MUST implement and SHOULD sign using rsa-sha256. + + + + + + + +Allman, et al. Standards Track [Page 11] + +RFC 4871 DKIM Signatures May 2007 + + + INFORMATIVE NOTE: Although sha256 is strongly encouraged, some + senders of low-security messages (such as routine newsletters) may + prefer to use sha1 because of reduced CPU requirements to compute + a sha1 hash. In general, sha256 should always be used whenever + possible. + +3.3.1. The rsa-sha1 Signing Algorithm + + The rsa-sha1 Signing Algorithm computes a message hash as described + in Section 3.7 below using SHA-1 [FIPS.180-2.2002] as the hash-alg. + That hash is then signed by the signer using the RSA algorithm + (defined in PKCS#1 version 1.5 [RFC3447]) as the crypt-alg and the + signer's private key. The hash MUST NOT be truncated or converted + into any form other than the native binary form before being signed. + The signing algorithm SHOULD use a public exponent of 65537. + +3.3.2. The rsa-sha256 Signing Algorithm + + The rsa-sha256 Signing Algorithm computes a message hash as described + in Section 3.7 below using SHA-256 [FIPS.180-2.2002] as the hash-alg. + That hash is then signed by the signer using the RSA algorithm + (defined in PKCS#1 version 1.5 [RFC3447]) as the crypt-alg and the + signer's private key. The hash MUST NOT be truncated or converted + into any form other than the native binary form before being signed. + +3.3.3. Key Sizes + + Selecting appropriate key sizes is a trade-off between cost, + performance, and risk. Since short RSA keys more easily succumb to + off-line attacks, signers MUST use RSA keys of at least 1024 bits for + long-lived keys. Verifiers MUST be able to validate signatures with + keys ranging from 512 bits to 2048 bits, and they MAY be able to + validate signatures with larger keys. Verifier policies may use the + length of the signing key as one metric for determining whether a + signature is acceptable. + + Factors that should influence the key size choice include the + following: + + o The practical constraint that large (e.g., 4096 bit) keys may not + fit within a 512-byte DNS UDP response packet + + o The security constraint that keys smaller than 1024 bits are + subject to off-line attacks + + o Larger keys impose higher CPU costs to verify and sign email + + + + + +Allman, et al. Standards Track [Page 12] + +RFC 4871 DKIM Signatures May 2007 + + + o Keys can be replaced on a regular basis, thus their lifetime can + be relatively short + + o The security goals of this specification are modest compared to + typical goals of other systems that employ digital signatures + + See [RFC3766] for further discussion on selecting key sizes. + +3.3.4. Other Algorithms + + Other algorithms MAY be defined in the future. Verifiers MUST ignore + any signatures using algorithms that they do not implement. + +3.4. Canonicalization + + Empirical evidence demonstrates that some mail servers and relay + systems modify email in transit, potentially invalidating a + signature. There are two competing perspectives on such + modifications. For most signers, mild modification of email is + immaterial to the authentication status of the email. For such + signers, a canonicalization algorithm that survives modest in-transit + modification is preferred. + + Other signers demand that any modification of the email, however + minor, result in a signature verification failure. These signers + prefer a canonicalization algorithm that does not tolerate in-transit + modification of the signed email. + + Some signers may be willing to accept modifications to header fields + that are within the bounds of email standards such as [RFC2822], but + are unwilling to accept any modification to the body of messages. + + To satisfy all requirements, two canonicalization algorithms are + defined for each of the header and the body: a "simple" algorithm + that tolerates almost no modification and a "relaxed" algorithm that + tolerates common modifications such as whitespace replacement and + header field line rewrapping. A signer MAY specify either algorithm + for header or body when signing an email. If no canonicalization + algorithm is specified by the signer, the "simple" algorithm defaults + for both header and body. Verifiers MUST implement both + canonicalization algorithms. Note that the header and body may use + different canonicalization algorithms. Further canonicalization + algorithms MAY be defined in the future; verifiers MUST ignore any + signatures that use unrecognized canonicalization algorithms. + + Canonicalization simply prepares the email for presentation to the + signing or verification algorithm. It MUST NOT change the + + + + +Allman, et al. Standards Track [Page 13] + +RFC 4871 DKIM Signatures May 2007 + + + transmitted data in any way. Canonicalization of header fields and + body are described below. + + NOTE: This section assumes that the message is already in "network + normal" format (text is ASCII encoded, lines are separated with CRLF + characters, etc.). See also Section 5.3 for information about + normalizing the message. + +3.4.1. The "simple" Header Canonicalization Algorithm + + The "simple" header canonicalization algorithm does not change header + fields in any way. Header fields MUST be presented to the signing or + verification algorithm exactly as they are in the message being + signed or verified. In particular, header field names MUST NOT be + case folded and whitespace MUST NOT be changed. + +3.4.2. The "relaxed" Header Canonicalization Algorithm + + The "relaxed" header canonicalization algorithm MUST apply the + following steps in order: + + o Convert all header field names (not the header field values) to + lowercase. For example, convert "SUBJect: AbC" to "subject: AbC". + + o Unfold all header field continuation lines as described in + [RFC2822]; in particular, lines with terminators embedded in + continued header field values (that is, CRLF sequences followed by + WSP) MUST be interpreted without the CRLF. Implementations MUST + NOT remove the CRLF at the end of the header field value. + + o Convert all sequences of one or more WSP characters to a single SP + character. WSP characters here include those before and after a + line folding boundary. + + o Delete all WSP characters at the end of each unfolded header field + value. + + o Delete any WSP characters remaining before and after the colon + separating the header field name from the header field value. The + colon separator MUST be retained. + +3.4.3. The "simple" Body Canonicalization Algorithm + + The "simple" body canonicalization algorithm ignores all empty lines + at the end of the message body. An empty line is a line of zero + length after removal of the line terminator. If there is no body or + no trailing CRLF on the message body, a CRLF is added. It makes no + + + + +Allman, et al. Standards Track [Page 14] + +RFC 4871 DKIM Signatures May 2007 + + + other changes to the message body. In more formal terms, the + "simple" body canonicalization algorithm converts "0*CRLF" at the end + of the body to a single "CRLF". + + Note that a completely empty or missing body is canonicalized as a + single "CRLF"; that is, the canonicalized length will be 2 octets. + +3.4.4. The "relaxed" Body Canonicalization Algorithm + + The "relaxed" body canonicalization algorithm: + + o Ignores all whitespace at the end of lines. Implementations MUST + NOT remove the CRLF at the end of the line. + + o Reduces all sequences of WSP within a line to a single SP + character. + + o Ignores all empty lines at the end of the message body. "Empty + line" is defined in Section 3.4.3. + + INFORMATIVE NOTE: It should be noted that the relaxed body + canonicalization algorithm may enable certain types of extremely + crude "ASCII Art" attacks where a message may be conveyed by + adjusting the spacing between words. If this is a concern, the + "simple" body canonicalization algorithm should be used instead. + +3.4.5. Body Length Limits + + A body length count MAY be specified to limit the signature + calculation to an initial prefix of the body text, measured in + octets. If the body length count is not specified, the entire + message body is signed. + + INFORMATIVE RATIONALE: This capability is provided because it is + very common for mailing lists to add trailers to messages (e.g., + instructions how to get off the list). Until those messages are + also signed, the body length count is a useful tool for the + verifier since it may as a matter of policy accept messages having + valid signatures with extraneous data. + + INFORMATIVE IMPLEMENTATION NOTE: Using body length limits enables + an attack in which an attacker modifies a message to include + content that solely benefits the attacker. It is possible for the + appended content to completely replace the original content in the + end recipient's eyes and to defeat duplicate message detection + algorithms. To avoid this attack, signers should be wary of using + + + + + +Allman, et al. Standards Track [Page 15] + +RFC 4871 DKIM Signatures May 2007 + + + this tag, and verifiers might wish to ignore the tag or remove + text that appears after the specified content length, perhaps + based on other criteria. + + The body length count allows the signer of a message to permit data + to be appended to the end of the body of a signed message. The body + length count MUST be calculated following the canonicalization + algorithm; for example, any whitespace ignored by a canonicalization + algorithm is not included as part of the body length count. Signers + of MIME messages that include a body length count SHOULD be sure that + the length extends to the closing MIME boundary string. + + INFORMATIVE IMPLEMENTATION NOTE: A signer wishing to ensure that + the only acceptable modifications are to add to the MIME postlude + would use a body length count encompassing the entire final MIME + boundary string, including the final "--CRLF". A signer wishing + to allow additional MIME parts but not modification of existing + parts would use a body length count extending through the final + MIME boundary string, omitting the final "--CRLF". Note that this + only works for some MIME types, e.g., multipart/mixed but not + multipart/signed. + + A body length count of zero means that the body is completely + unsigned. + + Signers wishing to ensure that no modification of any sort can occur + should specify the "simple" canonicalization algorithm for both + header and body and omit the body length count. + +3.4.6. Canonicalization Examples (INFORMATIVE) + + In the following examples, actual whitespace is used only for + clarity. The actual input and output text is designated using + bracketed descriptors: "" for a space character, "" for a + tab character, and "" for a carriage-return/line-feed sequence. + For example, "X Y" and "XY" represent the same three + characters. + + Example 1: A message reading: + + A: X + B : Y + Z + + C + D E + + + + + +Allman, et al. Standards Track [Page 16] + +RFC 4871 DKIM Signatures May 2007 + + + when canonicalized using relaxed canonicalization for both header and + body results in a header reading: + + a:X + b:Y Z + + and a body reading: + + C + D E + + Example 2: The same message canonicalized using simple + canonicalization for both header and body results in a header + reading: + + A: X + B : Y + Z + + and a body reading: + + C + D E + + Example 3: When processed using relaxed header canonicalization and + simple body canonicalization, the canonicalized version has a header + of: + + a:X + b:Y Z + + and a body reading: + + C + D E + +3.5. The DKIM-Signature Header Field + + The signature of the email is stored in the DKIM-Signature header + field. This header field contains all of the signature and key- + fetching data. The DKIM-Signature value is a tag-list as described + in Section 3.2. + + The DKIM-Signature header field SHOULD be treated as though it were a + trace header field as defined in Section 3.6 of [RFC2822], and hence + SHOULD NOT be reordered and SHOULD be prepended to the message. + + + + + +Allman, et al. Standards Track [Page 17] + +RFC 4871 DKIM Signatures May 2007 + + + The DKIM-Signature header field being created or verified is always + included in the signature calculation, after the rest of the header + fields being signed; however, when calculating or verifying the + signature, the value of the "b=" tag (signature value) of that DKIM- + Signature header field MUST be treated as though it were an empty + string. Unknown tags in the DKIM-Signature header field MUST be + included in the signature calculation but MUST be otherwise ignored + by verifiers. Other DKIM-Signature header fields that are included + in the signature should be treated as normal header fields; in + particular, the "b=" tag is not treated specially. + + The encodings for each field type are listed below. Tags described + as qp-section are encoded as described in Section 6.7 of MIME Part + One [RFC2045], with the additional conversion of semicolon characters + to "=3B"; intuitively, this is one line of quoted-printable encoded + text. The dkim-quoted-printable syntax is defined in Section 2.6. + + Tags on the DKIM-Signature header field along with their type and + requirement status are shown below. Unrecognized tags MUST be + ignored. + + v= Version (MUST be included). This tag defines the version of this + specification that applies to the signature record. It MUST have + the value "1". Note that verifiers must do a string comparison + on this value; for example, "1" is not the same as "1.0". + + ABNF: + + sig-v-tag = %x76 [FWS] "=" [FWS] "1" + + INFORMATIVE NOTE: DKIM-Signature version numbers are expected + to increase arithmetically as new versions of this + specification are released. + + a= The algorithm used to generate the signature (plain-text; + REQUIRED). Verifiers MUST support "rsa-sha1" and "rsa-sha256"; + signers SHOULD sign using "rsa-sha256". See Section 3.3 for a + description of algorithms. + + ABNF: + + sig-a-tag = %x61 [FWS] "=" [FWS] sig-a-tag-alg + sig-a-tag-alg = sig-a-tag-k "-" sig-a-tag-h + sig-a-tag-k = "rsa" / x-sig-a-tag-k + sig-a-tag-h = "sha1" / "sha256" / x-sig-a-tag-h + x-sig-a-tag-k = ALPHA *(ALPHA / DIGIT) ; for later extension + x-sig-a-tag-h = ALPHA *(ALPHA / DIGIT) ; for later extension + + + + +Allman, et al. Standards Track [Page 18] + +RFC 4871 DKIM Signatures May 2007 + + + b= The signature data (base64; REQUIRED). Whitespace is ignored in + this value and MUST be ignored when reassembling the original + signature. In particular, the signing process can safely insert + FWS in this value in arbitrary places to conform to line-length + limits. See Signer Actions (Section 5) for how the signature is + computed. + + ABNF: + + sig-b-tag = %x62 [FWS] "=" [FWS] sig-b-tag-data + sig-b-tag-data = base64string + + bh= The hash of the canonicalized body part of the message as limited + by the "l=" tag (base64; REQUIRED). Whitespace is ignored in + this value and MUST be ignored when reassembling the original + signature. In particular, the signing process can safely insert + FWS in this value in arbitrary places to conform to line-length + limits. See Section 3.7 for how the body hash is computed. + + ABNF: + + sig-bh-tag = %x62 %x68 [FWS] "=" [FWS] sig-bh-tag-data + sig-bh-tag-data = base64string + + c= Message canonicalization (plain-text; OPTIONAL, default is + "simple/simple"). This tag informs the verifier of the type of + canonicalization used to prepare the message for signing. It + consists of two names separated by a "slash" (%d47) character, + corresponding to the header and body canonicalization algorithms + respectively. These algorithms are described in Section 3.4. If + only one algorithm is named, that algorithm is used for the + header and "simple" is used for the body. For example, + "c=relaxed" is treated the same as "c=relaxed/simple". + + ABNF: + + sig-c-tag = %x63 [FWS] "=" [FWS] sig-c-tag-alg + ["/" sig-c-tag-alg] + sig-c-tag-alg = "simple" / "relaxed" / x-sig-c-tag-alg + x-sig-c-tag-alg = hyphenated-word ; for later extension + + d= The domain of the signing entity (plain-text; REQUIRED). This is + the domain that will be queried for the public key. This domain + MUST be the same as or a parent domain of the "i=" tag (the + signing identity, as described below), or it MUST meet the + requirements for parent domain signing described in Section 3.8. + When presented with a signature that does not meet these + requirement, verifiers MUST consider the signature invalid. + + + +Allman, et al. Standards Track [Page 19] + +RFC 4871 DKIM Signatures May 2007 + + + Internationalized domain names MUST be encoded as described in + [RFC3490]. + + ABNF: + + sig-d-tag = %x64 [FWS] "=" [FWS] domain-name + domain-name = sub-domain 1*("." sub-domain) + ; from RFC 2821 Domain, but excluding address-literal + + h= Signed header fields (plain-text, but see description; REQUIRED). + A colon-separated list of header field names that identify the + header fields presented to the signing algorithm. The field MUST + contain the complete list of header fields in the order presented + to the signing algorithm. The field MAY contain names of header + fields that do not exist when signed; nonexistent header fields + do not contribute to the signature computation (that is, they are + treated as the null input, including the header field name, the + separating colon, the header field value, and any CRLF + terminator). The field MUST NOT include the DKIM-Signature + header field that is being created or verified, but may include + others. Folding whitespace (FWS) MAY be included on either side + of the colon separator. Header field names MUST be compared + against actual header field names in a case-insensitive manner. + This list MUST NOT be empty. See Section 5.4 for a discussion of + choosing header fields to sign. + + ABNF: + + sig-h-tag = %x68 [FWS] "=" [FWS] hdr-name + 0*( *FWS ":" *FWS hdr-name ) + hdr-name = field-name + + INFORMATIVE EXPLANATION: By "signing" header fields that do not + actually exist, a signer can prevent insertion of those + header fields before verification. However, since a signer + cannot possibly know what header fields might be created in + the future, and that some MUAs might present header fields + that are embedded inside a message (e.g., as a message/rfc822 + content type), the security of this solution is not total. + + INFORMATIVE EXPLANATION: The exclusion of the header field name + and colon as well as the header field value for non-existent + header fields prevents an attacker from inserting an actual + header field with a null value. + + + + + + + +Allman, et al. Standards Track [Page 20] + +RFC 4871 DKIM Signatures May 2007 + + + i= Identity of the user or agent (e.g., a mailing list manager) on + behalf of which this message is signed (dkim-quoted-printable; + OPTIONAL, default is an empty Local-part followed by an "@" + followed by the domain from the "d=" tag). The syntax is a + standard email address where the Local-part MAY be omitted. The + domain part of the address MUST be the same as or a subdomain of + the value of the "d=" tag. + + Internationalized domain names MUST be converted using the steps + listed in Section 4 of [RFC3490] using the "ToASCII" function. + + ABNF: + + sig-i-tag = %x69 [FWS] "=" [FWS] [ Local-part ] "@" domain-name + + INFORMATIVE NOTE: The Local-part of the "i=" tag is optional + because in some cases a signer may not be able to establish a + verified individual identity. In such cases, the signer may + wish to assert that although it is willing to go as far as + signing for the domain, it is unable or unwilling to commit + to an individual user name within their domain. It can do so + by including the domain part but not the Local-part of the + identity. + + INFORMATIVE DISCUSSION: This document does not require the value + of the "i=" tag to match the identity in any message header + fields. This is considered to be a verifier policy issue. + Constraints between the value of the "i=" tag and other + identities in other header fields seek to apply basic + authentication into the semantics of trust associated with a + role such as content author. Trust is a broad and complex + topic and trust mechanisms are subject to highly creative + attacks. The real-world efficacy of any but the most basic + bindings between the "i=" value and other identities is not + well established, nor is its vulnerability to subversion by + an attacker. Hence reliance on the use of these options + should be strictly limited. In particular, it is not at all + clear to what extent a typical end-user recipient can rely on + any assurances that might be made by successful use of the + "i=" options. + + l= Body length count (plain-text unsigned decimal integer; OPTIONAL, + default is entire body). This tag informs the verifier of the + number of octets in the body of the email after canonicalization + included in the cryptographic hash, starting from 0 immediately + following the CRLF preceding the body. This value MUST NOT be + larger than the actual number of octets in the canonicalized + message body. + + + +Allman, et al. Standards Track [Page 21] + +RFC 4871 DKIM Signatures May 2007 + + + INFORMATIVE IMPLEMENTATION WARNING: Use of the "l=" tag might + allow display of fraudulent content without appropriate + warning to end users. The "l=" tag is intended for + increasing signature robustness when sending to mailing lists + that both modify their content and do not sign their + messages. However, using the "l=" tag enables attacks in + which an intermediary with malicious intent modifies a + message to include content that solely benefits the attacker. + It is possible for the appended content to completely replace + the original content in the end recipient's eyes and to + defeat duplicate message detection algorithms. Examples are + described in Security Considerations (Section 8). To avoid + this attack, signers should be extremely wary of using this + tag, and verifiers might wish to ignore the tag or remove + text that appears after the specified content length. + + INFORMATIVE NOTE: The value of the "l=" tag is constrained to 76 + decimal digits. This constraint is not intended to predict + the size of future messages or to require implementations to + use an integer representation large enough to represent the + maximum possible value, but is intended to remind the + implementer to check the length of this and all other tags + during verification and to test for integer overflow when + decoding the value. Implementers may need to limit the + actual value expressed to a value smaller than 10^76, e.g., + to allow a message to fit within the available storage space. + + ABNF: + + sig-l-tag = %x6c [FWS] "=" [FWS] 1*76DIGIT + + q= A colon-separated list of query methods used to retrieve the + public key (plain-text; OPTIONAL, default is "dns/txt"). Each + query method is of the form "type[/options]", where the syntax + and semantics of the options depend on the type and specified + options. If there are multiple query mechanisms listed, the + choice of query mechanism MUST NOT change the interpretation of + the signature. Implementations MUST use the recognized query + mechanisms in the order presented. + + Currently, the only valid value is "dns/txt", which defines the DNS + TXT record lookup algorithm described elsewhere in this document. + The only option defined for the "dns" query type is "txt", which + MUST be included. Verifiers and signers MUST support "dns/txt". + + + + + + + +Allman, et al. Standards Track [Page 22] + +RFC 4871 DKIM Signatures May 2007 + + + ABNF: + + sig-q-tag = %x71 [FWS] "=" [FWS] sig-q-tag-method + *([FWS] ":" [FWS] sig-q-tag-method) + sig-q-tag-method = "dns/txt" / x-sig-q-tag-type + ["/" x-sig-q-tag-args] + x-sig-q-tag-type = hyphenated-word ; for future extension + x-sig-q-tag-args = qp-hdr-value + + s= The selector subdividing the namespace for the "d=" (domain) tag + (plain-text; REQUIRED). + + ABNF: + + sig-s-tag = %x73 [FWS] "=" [FWS] selector + + t= Signature Timestamp (plain-text unsigned decimal integer; + RECOMMENDED, default is an unknown creation time). The time that + this signature was created. The format is the number of seconds + since 00:00:00 on January 1, 1970 in the UTC time zone. The + value is expressed as an unsigned integer in decimal ASCII. This + value is not constrained to fit into a 31- or 32-bit integer. + Implementations SHOULD be prepared to handle values up to at + least 10^12 (until approximately AD 200,000; this fits into 40 + bits). To avoid denial-of-service attacks, implementations MAY + consider any value longer than 12 digits to be infinite. Leap + seconds are not counted. Implementations MAY ignore signatures + that have a timestamp in the future. + + ABNF: + + sig-t-tag = %x74 [FWS] "=" [FWS] 1*12DIGIT + + x= Signature Expiration (plain-text unsigned decimal integer; + RECOMMENDED, default is no expiration). The format is the same + as in the "t=" tag, represented as an absolute date, not as a + time delta from the signing timestamp. The value is expressed as + an unsigned integer in decimal ASCII, with the same constraints + on the value in the "t=" tag. Signatures MAY be considered + invalid if the verification time at the verifier is past the + expiration date. The verification time should be the time that + the message was first received at the administrative domain of + the verifier if that time is reliably available; otherwise the + current time should be used. The value of the "x=" tag MUST be + greater than the value of the "t=" tag if both are present. + + + + + + +Allman, et al. Standards Track [Page 23] + +RFC 4871 DKIM Signatures May 2007 + + + INFORMATIVE NOTE: The "x=" tag is not intended as an anti-replay + defense. + + ABNF: + + sig-x-tag = %x78 [FWS] "=" [FWS] 1*12DIGIT + + z= Copied header fields (dkim-quoted-printable, but see description; + OPTIONAL, default is null). A vertical-bar-separated list of + selected header fields present when the message was signed, + including both the field name and value. It is not required to + include all header fields present at the time of signing. This + field need not contain the same header fields listed in the "h=" + tag. The header field text itself must encode the vertical bar + ("|", %x7C) character (i.e., vertical bars in the "z=" text are + metacharacters, and any actual vertical bar characters in a + copied header field must be encoded). Note that all whitespace + must be encoded, including whitespace between the colon and the + header field value. After encoding, FWS MAY be added at + arbitrary locations in order to avoid excessively long lines; + such whitespace is NOT part of the value of the header field, and + MUST be removed before decoding. + + The header fields referenced by the "h=" tag refer to the fields in + the RFC 2822 header of the message, not to any copied fields in + the "z=" tag. Copied header field values are for diagnostic use. + + Header fields with characters requiring conversion (perhaps from + legacy MTAs that are not [RFC2822] compliant) SHOULD be converted + as described in MIME Part Three [RFC2047]. + + ABNF: + sig-z-tag = %x7A [FWS] "=" [FWS] sig-z-tag-copy + *( [FWS] "|" sig-z-tag-copy ) + sig-z-tag-copy = hdr-name ":" qp-hdr-value + qp-hdr-value = dkim-quoted-printable ; with "|" encoded + + INFORMATIVE EXAMPLE of a signature header field spread across + multiple continuation lines: + + + + + + + + + + + + +Allman, et al. Standards Track [Page 24] + +RFC 4871 DKIM Signatures May 2007 + + + DKIM-Signature: a=rsa-sha256; d=example.net; s=brisbane; + c=simple; q=dns/txt; i=@eng.example.net; + t=1117574938; x=1118006938; + h=from:to:subject:date; + z=From:foo@eng.example.net|To:joe@example.com| + Subject:demo=20run|Date:July=205,=202005=203:44:08=20PM=20-0700; + bh=MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=; + b=dzdVyOfAKCdLXdJOc9G2q8LoXSlEniSbav+yuU4zGeeruD00lszZ + VoG4ZHRNiYzR + +3.6. Key Management and Representation + + Signature applications require some level of assurance that the + verification public key is associated with the claimed signer. Many + applications achieve this by using public key certificates issued by + a trusted third party. However, DKIM can achieve a sufficient level + of security, with significantly enhanced scalability, by simply + having the verifier query the purported signer's DNS entry (or some + security-equivalent) in order to retrieve the public key. + + DKIM keys can potentially be stored in multiple types of key servers + and in multiple formats. The storage and format of keys are + irrelevant to the remainder of the DKIM algorithm. + + Parameters to the key lookup algorithm are the type of the lookup + (the "q=" tag), the domain of the signer (the "d=" tag of the DKIM- + Signature header field), and the selector (the "s=" tag). + + public_key = dkim_find_key(q_val, d_val, s_val) + + This document defines a single binding, using DNS TXT records to + distribute the keys. Other bindings may be defined in the future. + +3.6.1. Textual Representation + + It is expected that many key servers will choose to present the keys + in an otherwise unstructured text format (for example, an XML form + would not be considered to be unstructured text for this purpose). + The following definition MUST be used for any DKIM key represented in + an otherwise unstructured textual form. + + The overall syntax is a tag-list as described in Section 3.2. The + current valid tags are described below. Other tags MAY be present + and MUST be ignored by any implementation that does not understand + them. + + + + + + +Allman, et al. Standards Track [Page 25] + +RFC 4871 DKIM Signatures May 2007 + + + v= Version of the DKIM key record (plain-text; RECOMMENDED, default + is "DKIM1"). If specified, this tag MUST be set to "DKIM1" + (without the quotes). This tag MUST be the first tag in the + record. Records beginning with a "v=" tag with any other value + MUST be discarded. Note that verifiers must do a string + comparison on this value; for example, "DKIM1" is not the same as + "DKIM1.0". + + ABNF: + + key-v-tag = %x76 [FWS] "=" [FWS] "DKIM1" + + g= Granularity of the key (plain-text; OPTIONAL, default is "*"). + This value MUST match the Local-part of the "i=" tag of the DKIM- + Signature header field (or its default value of the empty string + if "i=" is not specified), with a single, optional "*" character + matching a sequence of zero or more arbitrary characters + ("wildcarding"). An email with a signing address that does not + match the value of this tag constitutes a failed verification. + The intent of this tag is to constrain which signing address can + legitimately use this selector, for example, when delegating a + key to a third party that should only be used for special + purposes. Wildcarding allows matching for addresses such as + "user+*" or "*-offer". An empty "g=" value never matches any + addresses. + + ABNF: + + key-g-tag = %x67 [FWS] "=" [FWS] key-g-tag-lpart + key-g-tag-lpart = [dot-atom-text] ["*" [dot-atom-text] ] + + h= Acceptable hash algorithms (plain-text; OPTIONAL, defaults to + allowing all algorithms). A colon-separated list of hash + algorithms that might be used. Signers and Verifiers MUST + support the "sha256" hash algorithm. Verifiers MUST also support + the "sha1" hash algorithm. + + ABNF: + + key-h-tag = %x68 [FWS] "=" [FWS] key-h-tag-alg + 0*( [FWS] ":" [FWS] key-h-tag-alg ) + key-h-tag-alg = "sha1" / "sha256" / x-key-h-tag-alg + x-key-h-tag-alg = hyphenated-word ; for future extension + + + + + + + + +Allman, et al. Standards Track [Page 26] + +RFC 4871 DKIM Signatures May 2007 + + + k= Key type (plain-text; OPTIONAL, default is "rsa"). Signers and + verifiers MUST support the "rsa" key type. The "rsa" key type + indicates that an ASN.1 DER-encoded [ITU.X660.1997] RSAPublicKey + [RFC3447] (see Sections 3.1 and A.1.1) is being used in the "p=" + tag. (Note: the "p=" tag further encodes the value using the + base64 algorithm.) + + ABNF: + + key-k-tag = %x76 [FWS] "=" [FWS] key-k-tag-type + key-k-tag-type = "rsa" / x-key-k-tag-type + x-key-k-tag-type = hyphenated-word ; for future extension + + n= Notes that might be of interest to a human (qp-section; OPTIONAL, + default is empty). No interpretation is made by any program. + This tag should be used sparingly in any key server mechanism + that has space limitations (notably DNS). This is intended for + use by administrators, not end users. + + ABNF: + + key-n-tag = %x6e [FWS] "=" [FWS] qp-section + + p= Public-key data (base64; REQUIRED). An empty value means that + this public key has been revoked. The syntax and semantics of + this tag value before being encoded in base64 are defined by the + "k=" tag. + + INFORMATIVE RATIONALE: If a private key has been compromised + or otherwise disabled (e.g., an outsourcing contract has been + terminated), a signer might want to explicitly state that it + knows about the selector, but all messages using that + selector should fail verification. Verifiers should ignore + any DKIM-Signature header fields with a selector referencing + a revoked key. + + ABNF: + + key-p-tag = %x70 [FWS] "=" [ [FWS] base64string ] + + INFORMATIVE NOTE: A base64string is permitted to include white + space (FWS) at arbitrary places; however, any CRLFs must be + followed by at least one WSP character. Implementors and + administrators are cautioned to ensure that selector TXT + records conform to this specification. + + + + + + +Allman, et al. Standards Track [Page 27] + +RFC 4871 DKIM Signatures May 2007 + + + s= Service Type (plain-text; OPTIONAL; default is "*"). A colon- + separated list of service types to which this record applies. + Verifiers for a given service type MUST ignore this record if the + appropriate type is not listed. Currently defined service types + are as follows: + + * matches all service types + + email electronic mail (not necessarily limited to SMTP) + + This tag is intended to constrain the use of keys for other + purposes, should use of DKIM be defined by other services in the + future. + + ABNF: + + key-s-tag = %x73 [FWS] "=" [FWS] key-s-tag-type + 0*( [FWS] ":" [FWS] key-s-tag-type ) + key-s-tag-type = "email" / "*" / x-key-s-tag-type + x-key-s-tag-type = hyphenated-word ; for future extension + + t= Flags, represented as a colon-separated list of names (plain- + text; OPTIONAL, default is no flags set). The defined flags are + as follows: + + y This domain is testing DKIM. Verifiers MUST NOT treat + messages from signers in testing mode differently from + unsigned email, even should the signature fail to verify. + Verifiers MAY wish to track testing mode results to assist + the signer. + + s Any DKIM-Signature header fields using the "i=" tag MUST have + the same domain value on the right-hand side of the "@" in + the "i=" tag and the value of the "d=" tag. That is, the + "i=" domain MUST NOT be a subdomain of "d=". Use of this + flag is RECOMMENDED unless subdomaining is required. + + ABNF: + + key-t-tag = %x74 [FWS] "=" [FWS] key-t-tag-flag + 0*( [FWS] ":" [FWS] key-t-tag-flag ) + key-t-tag-flag = "y" / "s" / x-key-t-tag-flag + x-key-t-tag-flag = hyphenated-word ; for future extension + + Unrecognized flags MUST be ignored. + + + + + + +Allman, et al. Standards Track [Page 28] + +RFC 4871 DKIM Signatures May 2007 + + +3.6.2. DNS Binding + + A binding using DNS TXT records as a key service is hereby defined. + All implementations MUST support this binding. + +3.6.2.1. Namespace + + All DKIM keys are stored in a subdomain named "_domainkey". Given a + DKIM-Signature field with a "d=" tag of "example.com" and an "s=" tag + of "foo.bar", the DNS query will be for + "foo.bar._domainkey.example.com". + + INFORMATIVE OPERATIONAL NOTE: Wildcard DNS records (e.g., + *.bar._domainkey.example.com) do not make sense in this context + and should not be used. Note also that wildcards within domains + (e.g., s._domainkey.*.example.com) are not supported by the DNS. + +3.6.2.2. Resource Record Types for Key Storage + + The DNS Resource Record type used is specified by an option to the + query-type ("q=") tag. The only option defined in this base + specification is "txt", indicating the use of a TXT Resource Record + (RR). A later extension of this standard may define another RR type. + + Strings in a TXT RR MUST be concatenated together before use with no + intervening whitespace. TXT RRs MUST be unique for a particular + selector name; that is, if there are multiple records in an RRset, + the results are undefined. + + TXT RRs are encoded as described in Section 3.6.1. + +3.7. Computing the Message Hashes + + Both signing and verifying message signatures start with a step of + computing two cryptographic hashes over the message. Signers will + choose the parameters of the signature as described in Signer Actions + (Section 5); verifiers will use the parameters specified in the DKIM- + Signature header field being verified. In the following discussion, + the names of the tags in the DKIM-Signature header field that either + exists (when verifying) or will be created (when signing) are used. + Note that canonicalization (Section 3.4) is only used to prepare the + email for signing or verifying; it does not affect the transmitted + email in any way. + + The signer/verifier MUST compute two hashes, one over the body of the + message and one over the selected header fields of the message. + + + + + +Allman, et al. Standards Track [Page 29] + +RFC 4871 DKIM Signatures May 2007 + + + Signers MUST compute them in the order shown. Verifiers MAY compute + them in any order convenient to the verifier, provided that the + result is semantically identical to the semantics that would be the + case had they been computed in this order. + + In hash step 1, the signer/verifier MUST hash the message body, + canonicalized using the body canonicalization algorithm specified in + the "c=" tag and then truncated to the length specified in the "l=" + tag. That hash value is then converted to base64 form and inserted + into (signers) or compared to (verifiers) the "bh=" tag of the DKIM- + Signature header field. + + In hash step 2, the signer/verifier MUST pass the following to the + hash algorithm in the indicated order. + + 1. The header fields specified by the "h=" tag, in the order + specified in that tag, and canonicalized using the header + canonicalization algorithm specified in the "c=" tag. Each + header field MUST be terminated with a single CRLF. + + 2. The DKIM-Signature header field that exists (verifying) or will + be inserted (signing) in the message, with the value of the "b=" + tag deleted (i.e., treated as the empty string), canonicalized + using the header canonicalization algorithm specified in the "c=" + tag, and without a trailing CRLF. + + All tags and their values in the DKIM-Signature header field are + included in the cryptographic hash with the sole exception of the + value portion of the "b=" (signature) tag, which MUST be treated as + the null string. All tags MUST be included even if they might not be + understood by the verifier. The header field MUST be presented to + the hash algorithm after the body of the message rather than with the + rest of the header fields and MUST be canonicalized as specified in + the "c=" (canonicalization) tag. The DKIM-Signature header field + MUST NOT be included in its own h= tag, although other DKIM-Signature + header fields MAY be signed (see Section 4). + + When calculating the hash on messages that will be transmitted using + base64 or quoted-printable encoding, signers MUST compute the hash + after the encoding. Likewise, the verifier MUST incorporate the + values into the hash before decoding the base64 or quoted-printable + text. However, the hash MUST be computed before transport level + encodings such as SMTP "dot-stuffing" (the modification of lines + beginning with a "." to avoid confusion with the SMTP end-of-message + marker, as specified in [RFC2821]). + + With the exception of the canonicalization procedure described in + Section 3.4, the DKIM signing process treats the body of messages as + + + +Allman, et al. Standards Track [Page 30] + +RFC 4871 DKIM Signatures May 2007 + + + simply a string of octets. DKIM messages MAY be either in plain-text + or in MIME format; no special treatment is afforded to MIME content. + Message attachments in MIME format MUST be included in the content + that is signed. + + More formally, the algorithm for the signature is as follows: + body-hash = hash-alg(canon_body) + header-hash = hash-alg(canon_header || DKIM-SIG) + signature = sig-alg(header-hash, key) + + where "sig-alg" is the signature algorithm specified by the "a=" tag, + "hash-alg" is the hash algorithm specified by the "a=" tag, + "canon_header" and "canon_body" are the canonicalized message header + and body (respectively) as defined in Section 3.4 (excluding the + DKIM-Signature header field), and "DKIM-SIG" is the canonicalized + DKIM-Signature header field sans the signature value itself, but with + "body-hash" included as the "bh=" tag. + + INFORMATIVE IMPLEMENTERS' NOTE: Many digital signature APIs + provide both hashing and application of the RSA private key using + a single "sign()" primitive. When using such an API, the last two + steps in the algorithm would probably be combined into a single + call that would perform both the "hash-alg" and the "sig-alg". + +3.8. Signing by Parent Domains + + In some circumstances, it is desirable for a domain to apply a + signature on behalf of any of its subdomains without the need to + maintain separate selectors (key records) in each subdomain. By + default, private keys corresponding to key records can be used to + sign messages for any subdomain of the domain in which they reside; + e.g., a key record for the domain example.com can be used to verify + messages where the signing identity ("i=" tag of the signature) is + sub.example.com, or even sub1.sub2.example.com. In order to limit + the capability of such keys when this is not intended, the "s" flag + may be set in the "t=" tag of the key record to constrain the + validity of the record to exactly the domain of the signing identity. + If the referenced key record contains the "s" flag as part of the + "t=" tag, the domain of the signing identity ("i=" flag) MUST be the + same as that of the d= domain. If this flag is absent, the domain of + the signing identity MUST be the same as, or a subdomain of, the d= + domain. Key records that are not intended for use with subdomains + SHOULD specify the "s" flag in the "t=" tag. + + + + + + + + +Allman, et al. Standards Track [Page 31] + +RFC 4871 DKIM Signatures May 2007 + + +4. Semantics of Multiple Signatures + +4.1. Example Scenarios + + There are many reasons why a message might have multiple signatures. + For example, a given signer might sign multiple times, perhaps with + different hashing or signing algorithms during a transition phase. + + INFORMATIVE EXAMPLE: Suppose SHA-256 is in the future found to be + insufficiently strong, and DKIM usage transitions to SHA-1024. A + signer might immediately sign using the newer algorithm, but + continue to sign using the older algorithm for interoperability + with verifiers that had not yet upgraded. The signer would do + this by adding two DKIM-Signature header fields, one using each + algorithm. Older verifiers that did not recognize SHA-1024 as an + acceptable algorithm would skip that signature and use the older + algorithm; newer verifiers could use either signature at their + option, and all other things being equal might not even attempt to + verify the other signature. + + Similarly, a signer might sign a message including all headers and no + "l=" tag (to satisfy strict verifiers) and a second time with a + limited set of headers and an "l=" tag (in anticipation of possible + message modifications in route to other verifiers). Verifiers could + then choose which signature they preferred. + + INFORMATIVE EXAMPLE: A verifier might receive a message with two + signatures, one covering more of the message than the other. If + the signature covering more of the message verified, then the + verifier could make one set of policy decisions; if that signature + failed but the signature covering less of the message verified, + the verifier might make a different set of policy decisions. + + Of course, a message might also have multiple signatures because it + passed through multiple signers. A common case is expected to be + that of a signed message that passes through a mailing list that also + signs all messages. Assuming both of those signatures verify, a + recipient might choose to accept the message if either of those + signatures were known to come from trusted sources. + + INFORMATIVE EXAMPLE: Recipients might choose to whitelist mailing + lists to which they have subscribed and that have acceptable anti- + abuse policies so as to accept messages sent to that list even + from unknown authors. They might also subscribe to less trusted + mailing lists (e.g., those without anti-abuse protection) and be + willing to accept all messages from specific authors, but insist + on doing additional abuse scanning for other messages. + + + + +Allman, et al. Standards Track [Page 32] + +RFC 4871 DKIM Signatures May 2007 + + + Another related example of multiple signers might be forwarding + services, such as those commonly associated with academic alumni + sites. + + INFORMATIVE EXAMPLE: A recipient might have an address at + members.example.org, a site that has anti-abuse protection that is + somewhat less effective than the recipient would prefer. Such a + recipient might have specific authors whose messages would be + trusted absolutely, but messages from unknown authors that had + passed the forwarder's scrutiny would have only medium trust. + +4.2. Interpretation + + A signer that is adding a signature to a message merely creates a new + DKIM-Signature header, using the usual semantics of the h= option. A + signer MAY sign previously existing DKIM-Signature header fields + using the method described in Section 5.4 to sign trace header + fields. + + INFORMATIVE NOTE: Signers should be cognizant that signing DKIM- + Signature header fields may result in signature failures with + intermediaries that do not recognize that DKIM-Signature header + fields are trace header fields and unwittingly reorder them, thus + breaking such signatures. For this reason, signing existing DKIM- + Signature header fields is unadvised, albeit legal. + + INFORMATIVE NOTE: If a header field with multiple instances is + signed, those header fields are always signed from the bottom up. + Thus, it is not possible to sign only specific DKIM-Signature + header fields. For example, if the message being signed already + contains three DKIM-Signature header fields A, B, and C, it is + possible to sign all of them, B and C only, or C only, but not A + only, B only, A and B only, or A and C only. + + A signer MAY add more than one DKIM-Signature header field using + different parameters. For example, during a transition period a + signer might want to produce signatures using two different hash + algorithms. + + Signers SHOULD NOT remove any DKIM-Signature header fields from + messages they are signing, even if they know that the signatures + cannot be verified. + + When evaluating a message with multiple signatures, a verifier SHOULD + evaluate signatures independently and on their own merits. For + example, a verifier that by policy chooses not to accept signatures + with deprecated cryptographic algorithms would consider such + signatures invalid. Verifiers MAY process signatures in any order of + + + +Allman, et al. Standards Track [Page 33] + +RFC 4871 DKIM Signatures May 2007 + + + their choice; for example, some verifiers might choose to process + signatures corresponding to the From field in the message header + before other signatures. See Section 6.1 for more information about + signature choices. + + INFORMATIVE IMPLEMENTATION NOTE: Verifier attempts to correlate + valid signatures with invalid signatures in an attempt to guess + why a signature failed are ill-advised. In particular, there is + no general way that a verifier can determine that an invalid + signature was ever valid. + + Verifiers SHOULD ignore failed signatures as though they were not + present in the message. Verifiers SHOULD continue to check + signatures until a signature successfully verifies to the + satisfaction of the verifier. To limit potential denial-of-service + attacks, verifiers MAY limit the total number of signatures they will + attempt to verify. + +5. Signer Actions + + The following steps are performed in order by signers. + +5.1. Determine Whether the Email Should Be Signed and by Whom + + A signer can obviously only sign email for domains for which it has a + private key and the necessary knowledge of the corresponding public + key and selector information. However, there are a number of other + reasons beyond the lack of a private key why a signer could choose + not to sign an email. + + INFORMATIVE NOTE: Signing modules may be incorporated into any + portion of the mail system as deemed appropriate, including an + MUA, a SUBMISSION server, or an MTA. Wherever implemented, + signers should beware of signing (and thereby asserting + responsibility for) messages that may be problematic. In + particular, within a trusted enclave the signing address might be + derived from the header according to local policy; SUBMISSION + servers might only sign messages from users that are properly + authenticated and authorized. + + INFORMATIVE IMPLEMENTER ADVICE: SUBMISSION servers should not sign + Received header fields if the outgoing gateway MTA obfuscates + Received header fields, for example, to hide the details of + internal topology. + + If an email cannot be signed for some reason, it is a local policy + decision as to what to do with that email. + + + + +Allman, et al. Standards Track [Page 34] + +RFC 4871 DKIM Signatures May 2007 + + +5.2. Select a Private Key and Corresponding Selector Information + + This specification does not define the basis by which a signer should + choose which private key and selector information to use. Currently, + all selectors are equal as far as this specification is concerned, so + the decision should largely be a matter of administrative + convenience. Distribution and management of private keys is also + outside the scope of this document. + + INFORMATIVE OPERATIONS ADVICE: A signer should not sign with a + private key when the selector containing the corresponding public + key is expected to be revoked or removed before the verifier has + an opportunity to validate the signature. The signer should + anticipate that verifiers may choose to defer validation, perhaps + until the message is actually read by the final recipient. In + particular, when rotating to a new key pair, signing should + immediately commence with the new private key and the old public + key should be retained for a reasonable validation interval before + being removed from the key server. + +5.3. Normalize the Message to Prevent Transport Conversions + + Some messages, particularly those using 8-bit characters, are subject + to modification during transit, notably conversion to 7-bit form. + Such conversions will break DKIM signatures. In order to minimize + the chances of such breakage, signers SHOULD convert the message to a + suitable MIME content transfer encoding such as quoted-printable or + base64 as described in MIME Part One [RFC2045] before signing. Such + conversion is outside the scope of DKIM; the actual message SHOULD be + converted to 7-bit MIME by an MUA or MSA prior to presentation to the + DKIM algorithm. + + If the message is submitted to the signer with any local encoding + that will be modified before transmission, that modification to + canonical [RFC2822] form MUST be done before signing. In particular, + bare CR or LF characters (used by some systems as a local line + separator convention) MUST be converted to the SMTP-standard CRLF + sequence before the message is signed. Any conversion of this sort + SHOULD be applied to the message actually sent to the recipient(s), + not just to the version presented to the signing algorithm. + + More generally, the signer MUST sign the message as it is expected to + be received by the verifier rather than in some local or internal + form. + + + + + + + +Allman, et al. Standards Track [Page 35] + +RFC 4871 DKIM Signatures May 2007 + + +5.4. Determine the Header Fields to Sign + + The From header field MUST be signed (that is, included in the "h=" + tag of the resulting DKIM-Signature header field). Signers SHOULD + NOT sign an existing header field likely to be legitimately modified + or removed in transit. In particular, [RFC2821] explicitly permits + modification or removal of the Return-Path header field in transit. + Signers MAY include any other header fields present at the time of + signing at the discretion of the signer. + + INFORMATIVE OPERATIONS NOTE: The choice of which header fields to + sign is non-obvious. One strategy is to sign all existing, non- + repeatable header fields. An alternative strategy is to sign only + header fields that are likely to be displayed to or otherwise be + likely to affect the processing of the message at the receiver. A + third strategy is to sign only "well known" headers. Note that + verifiers may treat unsigned header fields with extreme + skepticism, including refusing to display them to the end user or + even ignoring the signature if it does not cover certain header + fields. For this reason, signing fields present in the message + such as Date, Subject, Reply-To, Sender, and all MIME header + fields are highly advised. + + The DKIM-Signature header field is always implicitly signed and MUST + NOT be included in the "h=" tag except to indicate that other + preexisting signatures are also signed. + + Signers MAY claim to have signed header fields that do not exist + (that is, signers MAY include the header field name in the "h=" tag + even if that header field does not exist in the message). When + computing the signature, the non-existing header field MUST be + treated as the null string (including the header field name, header + field value, all punctuation, and the trailing CRLF). + + INFORMATIVE RATIONALE: This allows signers to explicitly assert + the absence of a header field; if that header field is added later + the signature will fail. + + INFORMATIVE NOTE: A header field name need only be listed once + more than the actual number of that header field in a message at + the time of signing in order to prevent any further additions. + For example, if there is a single Comments header field at the + time of signing, listing Comments twice in the "h=" tag is + sufficient to prevent any number of Comments header fields from + being appended; it is not necessary (but is legal) to list + Comments three or more times in the "h=" tag. + + + + + +Allman, et al. Standards Track [Page 36] + +RFC 4871 DKIM Signatures May 2007 + + + Signers choosing to sign an existing header field that occurs more + than once in the message (such as Received) MUST sign the physically + last instance of that header field in the header block. Signers + wishing to sign multiple instances of such a header field MUST + include the header field name multiple times in the h= tag of the + DKIM-Signature header field, and MUST sign such header fields in + order from the bottom of the header field block to the top. The + signer MAY include more instances of a header field name in h= than + there are actual corresponding header fields to indicate that + additional header fields of that name SHOULD NOT be added. + + INFORMATIVE EXAMPLE: + + If the signer wishes to sign two existing Received header fields, + and the existing header contains: + + Received: + Received: + Received: + + then the resulting DKIM-Signature header field should read: + + DKIM-Signature: ... h=Received : Received : ... + + and Received header fields and will be signed in that + order. + + Signers should be careful of signing header fields that might have + additional instances added later in the delivery process, since such + header fields might be inserted after the signed instance or + otherwise reordered. Trace header fields (such as Received) and + Resent-* blocks are the only fields prohibited by [RFC2822] from + being reordered. In particular, since DKIM-Signature header fields + may be reordered by some intermediate MTAs, signing existing DKIM- + Signature header fields is error-prone. + + INFORMATIVE ADMONITION: Despite the fact that [RFC2822] permits + header fields to be reordered (with the exception of Received + header fields), reordering of signed header fields with multiple + instances by intermediate MTAs will cause DKIM signatures to be + broken; such anti-social behavior should be avoided. + + INFORMATIVE IMPLEMENTER'S NOTE: Although not required by this + specification, all end-user visible header fields should be signed + to avoid possible "indirect spamming". For example, if the + Subject header field is not signed, a spammer can resend a + previously signed mail, replacing the legitimate subject with a + one-line spam. + + + +Allman, et al. Standards Track [Page 37] + +RFC 4871 DKIM Signatures May 2007 + + +5.5. Recommended Signature Content + + In order to maximize compatibility with a variety of verifiers, it is + recommended that signers follow the practices outlined in this + section when signing a message. However, these are generic + recommendations applying to the general case; specific senders may + wish to modify these guidelines as required by their unique + situations. Verifiers MUST be capable of verifying signatures even + if one or more of the recommended header fields is not signed (with + the exception of From, which must always be signed) or if one or more + of the disrecommended header fields is signed. Note that verifiers + do have the option of ignoring signatures that do not cover a + sufficient portion of the header or body, just as they may ignore + signatures from an identity they do not trust. + + The following header fields SHOULD be included in the signature, if + they are present in the message being signed: + + o From (REQUIRED in all signatures) + + o Sender, Reply-To + + o Subject + + o Date, Message-ID + + o To, Cc + + o MIME-Version + + o Content-Type, Content-Transfer-Encoding, Content-ID, Content- + Description + + o Resent-Date, Resent-From, Resent-Sender, Resent-To, Resent-Cc, + Resent-Message-ID + + o In-Reply-To, References + + o List-Id, List-Help, List-Unsubscribe, List-Subscribe, List-Post, + List-Owner, List-Archive + + The following header fields SHOULD NOT be included in the signature: + + o Return-Path + + o Received + + o Comments, Keywords + + + +Allman, et al. Standards Track [Page 38] + +RFC 4871 DKIM Signatures May 2007 + + + o Bcc, Resent-Bcc + + o DKIM-Signature + + Optional header fields (those not mentioned above) normally SHOULD + NOT be included in the signature, because of the potential for + additional header fields of the same name to be legitimately added or + reordered prior to verification. There are likely to be legitimate + exceptions to this rule, because of the wide variety of application- + specific header fields that may be applied to a message, some of + which are unlikely to be duplicated, modified, or reordered. + + Signers SHOULD choose canonicalization algorithms based on the types + of messages they process and their aversion to risk. For example, + e-commerce sites sending primarily purchase receipts, which are not + expected to be processed by mailing lists or other software likely to + modify messages, will generally prefer "simple" canonicalization. + Sites sending primarily person-to-person email will likely prefer to + be more resilient to modification during transport by using "relaxed" + canonicalization. + + Signers SHOULD NOT use "l=" unless they intend to accommodate + intermediate mail processors that append text to a message. For + example, many mailing list processors append "unsubscribe" + information to message bodies. If signers use "l=", they SHOULD + include the entire message body existing at the time of signing in + computing the count. In particular, signers SHOULD NOT specify a + body length of 0 since this may be interpreted as a meaningless + signature by some verifiers. + +5.6. Compute the Message Hash and Signature + + The signer MUST compute the message hash as described in Section 3.7 + and then sign it using the selected public-key algorithm. This will + result in a DKIM-Signature header field that will include the body + hash and a signature of the header hash, where that header includes + the DKIM-Signature header field itself. + + Entities such as mailing list managers that implement DKIM and that + modify the message or a header field (for example, inserting + unsubscribe information) before retransmitting the message SHOULD + check any existing signature on input and MUST make such + modifications before re-signing the message. + + The signer MAY elect to limit the number of bytes of the body that + will be included in the hash and hence signed. The length actually + hashed should be inserted in the "l=" tag of the DKIM-Signature + header field. + + + +Allman, et al. Standards Track [Page 39] + +RFC 4871 DKIM Signatures May 2007 + + +5.7. Insert the DKIM-Signature Header Field + + Finally, the signer MUST insert the DKIM-Signature header field + created in the previous step prior to transmitting the email. The + DKIM-Signature header field MUST be the same as used to compute the + hash as described above, except that the value of the "b=" tag MUST + be the appropriately signed hash computed in the previous step, + signed using the algorithm specified in the "a=" tag of the DKIM- + Signature header field and using the private key corresponding to the + selector given in the "s=" tag of the DKIM-Signature header field, as + chosen above in Section 5.2 + + The DKIM-Signature header field MUST be inserted before any other + DKIM-Signature fields in the header block. + + INFORMATIVE IMPLEMENTATION NOTE: The easiest way to achieve this + is to insert the DKIM-Signature header field at the beginning of + the header block. In particular, it may be placed before any + existing Received header fields. This is consistent with treating + DKIM-Signature as a trace header field. + +6. Verifier Actions + + Since a signer MAY remove or revoke a public key at any time, it is + recommended that verification occur in a timely manner. In many + configurations, the most timely place is during acceptance by the + border MTA or shortly thereafter. In particular, deferring + verification until the message is accessed by the end user is + discouraged. + + A border or intermediate MTA MAY verify the message signature(s). An + MTA who has performed verification MAY communicate the result of that + verification by adding a verification header field to incoming + messages. This considerably simplifies things for the user, who can + now use an existing mail user agent. Most MUAs have the ability to + filter messages based on message header fields or content; these + filters would be used to implement whatever policy the user wishes + with respect to unsigned mail. + + A verifying MTA MAY implement a policy with respect to unverifiable + mail, regardless of whether or not it applies the verification header + field to signed messages. + + Verifiers MUST produce a result that is semantically equivalent to + applying the following steps in the order listed. In practice, + several of these steps can be performed in parallel in order to + improve performance. + + + + +Allman, et al. Standards Track [Page 40] + +RFC 4871 DKIM Signatures May 2007 + + +6.1. Extract Signatures from the Message + + The order in which verifiers try DKIM-Signature header fields is not + defined; verifiers MAY try signatures in any order they like. For + example, one implementation might try the signatures in textual + order, whereas another might try signatures by identities that match + the contents of the From header field before trying other signatures. + Verifiers MUST NOT attribute ultimate meaning to the order of + multiple DKIM-Signature header fields. In particular, there is + reason to believe that some relays will reorder the header fields in + potentially arbitrary ways. + + INFORMATIVE IMPLEMENTATION NOTE: Verifiers might use the order as + a clue to signing order in the absence of any other information. + However, other clues as to the semantics of multiple signatures + (such as correlating the signing host with Received header fields) + may also be considered. + + A verifier SHOULD NOT treat a message that has one or more bad + signatures and no good signatures differently from a message with no + signature at all; such treatment is a matter of local policy and is + beyond the scope of this document. + + When a signature successfully verifies, a verifier will either stop + processing or attempt to verify any other signatures, at the + discretion of the implementation. A verifier MAY limit the number of + signatures it tries to avoid denial-of-service attacks. + + INFORMATIVE NOTE: An attacker could send messages with large + numbers of faulty signatures, each of which would require a DNS + lookup and corresponding CPU time to verify the message. This + could be an attack on the domain that receives the message, by + slowing down the verifier by requiring it to do a large number of + DNS lookups and/or signature verifications. It could also be an + attack against the domains listed in the signatures, essentially + by enlisting innocent verifiers in launching an attack against the + DNS servers of the actual victim. + + In the following description, text reading "return status + (explanation)" (where "status" is one of "PERMFAIL" or "TEMPFAIL") + means that the verifier MUST immediately cease processing that + signature. The verifier SHOULD proceed to the next signature, if any + is present, and completely ignore the bad signature. If the status + is "PERMFAIL", the signature failed and should not be reconsidered. + If the status is "TEMPFAIL", the signature could not be verified at + this time but may be tried again later. A verifier MAY either defer + the message for later processing, perhaps by queueing it locally or + issuing a 451/4.7.5 SMTP reply, or try another signature; if no good + + + +Allman, et al. Standards Track [Page 41] + +RFC 4871 DKIM Signatures May 2007 + + + signature is found and any of the signatures resulted in a TEMPFAIL + status, the verifier MAY save the message for later processing. The + "(explanation)" is not normative text; it is provided solely for + clarification. + + Verifiers SHOULD ignore any DKIM-Signature header fields where the + signature does not validate. Verifiers that are prepared to validate + multiple signature header fields SHOULD proceed to the next signature + header field, should it exist. However, verifiers MAY make note of + the fact that an invalid signature was present for consideration at a + later step. + + INFORMATIVE NOTE: The rationale of this requirement is to permit + messages that have invalid signatures but also a valid signature + to work. For example, a mailing list exploder might opt to leave + the original submitter signature in place even though the exploder + knows that it is modifying the message in some way that will break + that signature, and the exploder inserts its own signature. In + this case, the message should succeed even in the presence of the + known-broken signature. + + For each signature to be validated, the following steps should be + performed in such a manner as to produce a result that is + semantically equivalent to performing them in the indicated order. + +6.1.1. Validate the Signature Header Field + + Implementers MUST meticulously validate the format and values in the + DKIM-Signature header field; any inconsistency or unexpected values + MUST cause the header field to be completely ignored and the verifier + to return PERMFAIL (signature syntax error). Being "liberal in what + you accept" is definitely a bad strategy in this security context. + Note however that this does not include the existence of unknown tags + in a DKIM-Signature header field, which are explicitly permitted. + + Verifiers MUST ignore DKIM-Signature header fields with a "v=" tag + that is inconsistent with this specification and return PERMFAIL + (incompatible version). + + INFORMATIVE IMPLEMENTATION NOTE: An implementation may, of course, + choose to also verify signatures generated by older versions of + this specification. + + If any tag listed as "required" in Section 3.5 is omitted from the + DKIM-Signature header field, the verifier MUST ignore the DKIM- + Signature header field and return PERMFAIL (signature missing + required tag). + + + + +Allman, et al. Standards Track [Page 42] + +RFC 4871 DKIM Signatures May 2007 + + + INFORMATIONAL NOTE: The tags listed as required in Section 3.5 are + "v=", "a=", "b=", "bh=", "d=", "h=", and "s=". Should there be a + conflict between this note and Section 3.5, Section 3.5 is + normative. + + If the DKIM-Signature header field does not contain the "i=" tag, the + verifier MUST behave as though the value of that tag were "@d", where + "d" is the value from the "d=" tag. + + Verifiers MUST confirm that the domain specified in the "d=" tag is + the same as or a parent domain of the domain part of the "i=" tag. + If not, the DKIM-Signature header field MUST be ignored and the + verifier should return PERMFAIL (domain mismatch). + + If the "h=" tag does not include the From header field, the verifier + MUST ignore the DKIM-Signature header field and return PERMFAIL (From + field not signed). + + Verifiers MAY ignore the DKIM-Signature header field and return + PERMFAIL (signature expired) if it contains an "x=" tag and the + signature has expired. + + Verifiers MAY ignore the DKIM-Signature header field if the domain + used by the signer in the "d=" tag is not associated with a valid + signing entity. For example, signatures with "d=" values such as + "com" and "co.uk" may be ignored. The list of unacceptable domains + SHOULD be configurable. + + Verifiers MAY ignore the DKIM-Signature header field and return + PERMFAIL (unacceptable signature header) for any other reason, for + example, if the signature does not sign header fields that the + verifier views to be essential. As a case in point, if MIME header + fields are not signed, certain attacks may be possible that the + verifier would prefer to avoid. + +6.1.2. Get the Public Key + + The public key for a signature is needed to complete the verification + process. The process of retrieving the public key depends on the + query type as defined by the "q=" tag in the DKIM-Signature header + field. Obviously, a public key need only be retrieved if the process + of extracting the signature information is completely successful. + Details of key management and representation are described in + Section 3.6. The verifier MUST validate the key record and MUST + ignore any public key records that are malformed. + + When validating a message, a verifier MUST perform the following + steps in a manner that is semantically the same as performing them in + + + +Allman, et al. Standards Track [Page 43] + +RFC 4871 DKIM Signatures May 2007 + + + the order indicated (in some cases, the implementation may + parallelize or reorder these steps, as long as the semantics remain + unchanged): + + 1. Retrieve the public key as described in Section 3.6 using the + algorithm in the "q=" tag, the domain from the "d=" tag, and the + selector from the "s=" tag. + + 2. If the query for the public key fails to respond, the verifier + MAY defer acceptance of this email and return TEMPFAIL (key + unavailable). If verification is occurring during the incoming + SMTP session, this MAY be achieved with a 451/4.7.5 SMTP reply + code. Alternatively, the verifier MAY store the message in the + local queue for later trial or ignore the signature. Note that + storing a message in the local queue is subject to denial-of- + service attacks. + + 3. If the query for the public key fails because the corresponding + key record does not exist, the verifier MUST immediately return + PERMFAIL (no key for signature). + + 4. If the query for the public key returns multiple key records, the + verifier may choose one of the key records or may cycle through + the key records performing the remainder of these steps on each + record at the discretion of the implementer. The order of the + key records is unspecified. If the verifier chooses to cycle + through the key records, then the "return ..." wording in the + remainder of this section means "try the next key record, if any; + if none, return to try another signature in the usual way". + + 5. If the result returned from the query does not adhere to the + format defined in this specification, the verifier MUST ignore + the key record and return PERMFAIL (key syntax error). Verifiers + are urged to validate the syntax of key records carefully to + avoid attempted attacks. In particular, the verifier MUST ignore + keys with a version code ("v=" tag) that they do not implement. + + 6. If the "g=" tag in the public key does not match the Local-part + of the "i=" tag in the message signature header field, the + verifier MUST ignore the key record and return PERMFAIL + (inapplicable key). If the Local-part of the "i=" tag on the + message signature is not present, the "g=" tag must be "*" (valid + for all addresses in the domain) or the entire g= tag must be + omitted (which defaults to "g=*"), otherwise the verifier MUST + ignore the key record and return PERMFAIL (inapplicable key). + Other than this test, verifiers SHOULD NOT treat a message signed + with a key record having a "g=" tag any differently than one + without; in particular, verifiers SHOULD NOT prefer messages that + + + +Allman, et al. Standards Track [Page 44] + +RFC 4871 DKIM Signatures May 2007 + + + seem to have an individual signature by virtue of a "g=" tag + versus a domain signature. + + 7. If the "h=" tag exists in the public key record and the hash + algorithm implied by the a= tag in the DKIM-Signature header + field is not included in the contents of the "h=" tag, the + verifier MUST ignore the key record and return PERMFAIL + (inappropriate hash algorithm). + + 8. If the public key data (the "p=" tag) is empty, then this key has + been revoked and the verifier MUST treat this as a failed + signature check and return PERMFAIL (key revoked). There is no + defined semantic difference between a key that has been revoked + and a key record that has been removed. + + 9. If the public key data is not suitable for use with the algorithm + and key types defined by the "a=" and "k=" tags in the DKIM- + Signature header field, the verifier MUST immediately return + PERMFAIL (inappropriate key algorithm). + +6.1.3. Compute the Verification + + Given a signer and a public key, verifying a signature consists of + actions semantically equivalent to the following steps. + + 1. Based on the algorithm defined in the "c=" tag, the body length + specified in the "l=" tag, and the header field names in the "h=" + tag, prepare a canonicalized version of the message as is + described in Section 3.7 (note that this version does not + actually need to be instantiated). When matching header field + names in the "h=" tag against the actual message header field, + comparisons MUST be case-insensitive. + + 2. Based on the algorithm indicated in the "a=" tag, compute the + message hashes from the canonical copy as described in + Section 3.7. + + 3. Verify that the hash of the canonicalized message body computed + in the previous step matches the hash value conveyed in the "bh=" + tag. If the hash does not match, the verifier SHOULD ignore the + signature and return PERMFAIL (body hash did not verify). + + 4. Using the signature conveyed in the "b=" tag, verify the + signature against the header hash using the mechanism appropriate + for the public key algorithm described in the "a=" tag. If the + signature does not validate, the verifier SHOULD ignore the + signature and return PERMFAIL (signature did not verify). + + + + +Allman, et al. Standards Track [Page 45] + +RFC 4871 DKIM Signatures May 2007 + + + 5. Otherwise, the signature has correctly verified. + + INFORMATIVE IMPLEMENTER'S NOTE: Implementations might wish to + initiate the public-key query in parallel with calculating the + hash as the public key is not needed until the final decryption is + calculated. Implementations may also verify the signature on the + message header before validating that the message hash listed in + the "bh=" tag in the DKIM-Signature header field matches that of + the actual message body; however, if the body hash does not match, + the entire signature must be considered to have failed. + + A body length specified in the "l=" tag of the signature limits the + number of bytes of the body passed to the verification algorithm. + All data beyond that limit is not validated by DKIM. Hence, + verifiers might treat a message that contains bytes beyond the + indicated body length with suspicion, such as by truncating the + message at the indicated body length, declaring the signature invalid + (e.g., by returning PERMFAIL (unsigned content)), or conveying the + partial verification to the policy module. + + INFORMATIVE IMPLEMENTATION NOTE: Verifiers that truncate the body + at the indicated body length might pass on a malformed MIME + message if the signer used the "N-4" trick (omitting the final + "--CRLF") described in the informative note in Section 3.4.5. + Such verifiers may wish to check for this case and include a + trailing "--CRLF" to avoid breaking the MIME structure. A simple + way to achieve this might be to append "--CRLF" to any "multipart" + message with a body length; if the MIME structure is already + correctly formed, this will appear in the postlude and will not be + displayed to the end user. + +6.2. Communicate Verification Results + + Verifiers wishing to communicate the results of verification to other + parts of the mail system may do so in whatever manner they see fit. + For example, implementations might choose to add an email header + field to the message before passing it on. Any such header field + SHOULD be inserted before any existing DKIM-Signature or preexisting + authentication status header fields in the header field block. + + INFORMATIVE ADVICE to MUA filter writers: Patterns intended to + search for results header fields to visibly mark authenticated + mail for end users should verify that such header field was added + by the appropriate verifying domain and that the verified identity + matches the author identity that will be displayed by the MUA. In + particular, MUA filters should not be influenced by bogus results + + + + + +Allman, et al. Standards Track [Page 46] + +RFC 4871 DKIM Signatures May 2007 + + + header fields added by attackers. To circumvent this attack, + verifiers may wish to delete existing results header fields after + verification and before adding a new header field. + +6.3. Interpret Results/Apply Local Policy + + It is beyond the scope of this specification to describe what actions + a verifier system should make, but an authenticated email presents an + opportunity to a receiving system that unauthenticated email cannot. + Specifically, an authenticated email creates a predictable identifier + by which other decisions can reliably be managed, such as trust and + reputation. Conversely, unauthenticated email lacks a reliable + identifier that can be used to assign trust and reputation. It is + reasonable to treat unauthenticated email as lacking any trust and + having no positive reputation. + + In general, verifiers SHOULD NOT reject messages solely on the basis + of a lack of signature or an unverifiable signature; such rejection + would cause severe interoperability problems. However, if the + verifier does opt to reject such messages (for example, when + communicating with a peer who, by prior agreement, agrees to only + send signed messages), and the verifier runs synchronously with the + SMTP session and a signature is missing or does not verify, the MTA + SHOULD use a 550/5.7.x reply code. + + If it is not possible to fetch the public key, perhaps because the + key server is not available, a temporary failure message MAY be + generated using a 451/4.7.5 reply code, such as: + + 451 4.7.5 Unable to verify signature - key server unavailable + + Temporary failures such as inability to access the key server or + other external service are the only conditions that SHOULD use a 4xx + SMTP reply code. In particular, cryptographic signature verification + failures MUST NOT return 4xx SMTP replies. + + Once the signature has been verified, that information MUST be + conveyed to higher-level systems (such as explicit allow/whitelists + and reputation systems) and/or to the end user. If the message is + signed on behalf of any address other than that in the From: header + field, the mail system SHOULD take pains to ensure that the actual + signing identity is clear to the reader. + + The verifier MAY treat unsigned header fields with extreme + skepticism, including marking them as untrusted or even deleting them + before display to the end user. + + + + + +Allman, et al. Standards Track [Page 47] + +RFC 4871 DKIM Signatures May 2007 + + + While the symptoms of a failed verification are obvious -- the + signature doesn't verify -- establishing the exact cause can be more + difficult. If a selector cannot be found, is that because the + selector has been removed, or was the value changed somehow in + transit? If the signature line is missing, is that because it was + never there, or was it removed by an overzealous filter? For + diagnostic purposes, the exact reason why the verification fails + SHOULD be made available to the policy module and possibly recorded + in the system logs. If the email cannot be verified, then it SHOULD + be rendered the same as all unverified email regardless of whether or + not it looks like it was signed. + +7. IANA Considerations + + DKIM introduces some new namespaces that have been registered with + IANA. In all cases, new values are assigned only for values that + have been documented in a published RFC that has IETF Consensus + [RFC2434]. + +7.1. DKIM-Signature Tag Specifications + + A DKIM-Signature provides for a list of tag specifications. IANA has + established the DKIM-Signature Tag Specification Registry for tag + specifications that can be used in DKIM-Signature fields. + + The initial entries in the registry comprise: + + +------+-----------------+ + | TYPE | REFERENCE | + +------+-----------------+ + | v | (this document) | + | a | (this document) | + | b | (this document) | + | bh | (this document) | + | c | (this document) | + | d | (this document) | + | h | (this document) | + | i | (this document) | + | l | (this document) | + | q | (this document) | + | s | (this document) | + | t | (this document) | + | x | (this document) | + | z | (this document) | + +------+-----------------+ + + DKIM-Signature Tag Specification Registry Initial Values + + + + +Allman, et al. Standards Track [Page 48] + +RFC 4871 DKIM Signatures May 2007 + + +7.2. DKIM-Signature Query Method Registry + + The "q=" tag-spec (specified in Section 3.5) provides for a list of + query methods. + + IANA has established the DKIM-Signature Query Method Registry for + mechanisms that can be used to retrieve the key that will permit + validation processing of a message signed using DKIM. + + The initial entry in the registry comprises: + + +------+--------+-----------------+ + | TYPE | OPTION | REFERENCE | + +------+--------+-----------------+ + | dns | txt | (this document) | + +------+--------+-----------------+ + + DKIM-Signature Query Method Registry Initial Values + +7.3. DKIM-Signature Canonicalization Registry + + The "c=" tag-spec (specified in Section 3.5) provides for a specifier + for canonicalization algorithms for the header and body of the + message. + + IANA has established the DKIM-Signature Canonicalization Algorithm + Registry for algorithms for converting a message into a canonical + form before signing or verifying using DKIM. + + The initial entries in the header registry comprise: + + +---------+-----------------+ + | TYPE | REFERENCE | + +---------+-----------------+ + | simple | (this document) | + | relaxed | (this document) | + +---------+-----------------+ + + DKIM-Signature Header Canonicalization Algorithm Registry + Initial Values + + + + + + + + + + + +Allman, et al. Standards Track [Page 49] + +RFC 4871 DKIM Signatures May 2007 + + + The initial entries in the body registry comprise: + + +---------+-----------------+ + | TYPE | REFERENCE | + +---------+-----------------+ + | simple | (this document) | + | relaxed | (this document) | + +---------+-----------------+ + + DKIM-Signature Body Canonicalization Algorithm Registry + Initial Values + +7.4. _domainkey DNS TXT Record Tag Specifications + + A _domainkey DNS TXT record provides for a list of tag + specifications. IANA has established the DKIM _domainkey DNS TXT Tag + Specification Registry for tag specifications that can be used in DNS + TXT Records. + + The initial entries in the registry comprise: + + +------+-----------------+ + | TYPE | REFERENCE | + +------+-----------------+ + | v | (this document) | + | g | (this document) | + | h | (this document) | + | k | (this document) | + | n | (this document) | + | p | (this document) | + | s | (this document) | + | t | (this document) | + +------+-----------------+ + + DKIM _domainkey DNS TXT Record Tag Specification Registry + Initial Values + +7.5. DKIM Key Type Registry + + The "k=" (specified in Section 3.6.1) and the "a=" (specified in Section 3.5) tags provide for a list of + mechanisms that can be used to decode a DKIM signature. + + IANA has established the DKIM Key Type Registry for such mechanisms. + + + + + + + +Allman, et al. Standards Track [Page 50] + +RFC 4871 DKIM Signatures May 2007 + + + The initial entry in the registry comprises: + + +------+-----------+ + | TYPE | REFERENCE | + +------+-----------+ + | rsa | [RFC3447] | + +------+-----------+ + + DKIM Key Type Initial Values + +7.6. DKIM Hash Algorithms Registry + + The "h=" (specified in Section 3.6.1) and the "a=" (specified in Section 3.5) tags provide for a list of + mechanisms that can be used to produce a digest of message data. + + IANA has established the DKIM Hash Algorithms Registry for such + mechanisms. + + The initial entries in the registry comprise: + + +--------+-------------------+ + | TYPE | REFERENCE | + +--------+-------------------+ + | sha1 | [FIPS.180-2.2002] | + | sha256 | [FIPS.180-2.2002] | + +--------+-------------------+ + + DKIM Hash Algorithms Initial Values + +7.7. DKIM Service Types Registry + + The "s=" tag (specified in Section 3.6.1) provides for a + list of service types to which this selector may apply. + + IANA has established the DKIM Service Types Registry for service + types. + + The initial entries in the registry comprise: + + +-------+-----------------+ + | TYPE | REFERENCE | + +-------+-----------------+ + | email | (this document) | + | * | (this document) | + +-------+-----------------+ + + DKIM Service Types Registry Initial Values + + + +Allman, et al. Standards Track [Page 51] + +RFC 4871 DKIM Signatures May 2007 + + +7.8. DKIM Selector Flags Registry + + The "t=" tag (specified in Section 3.6.1) provides for a + list of flags to modify interpretation of the selector. + + IANA has established the DKIM Selector Flags Registry for additional + flags. + + The initial entries in the registry comprise: + + +------+-----------------+ + | TYPE | REFERENCE | + +------+-----------------+ + | y | (this document) | + | s | (this document) | + +------+-----------------+ + + DKIM Selector Flags Registry Initial Values + +7.9. DKIM-Signature Header Field + + IANA has added DKIM-Signature to the "Permanent Message Header + Fields" registry (see [RFC3864]) for the "mail" protocol, using this + document as the reference. + +8. Security Considerations + + It has been observed that any mechanism that is introduced that + attempts to stem the flow of spam is subject to intensive attack. + DKIM needs to be carefully scrutinized to identify potential attack + vectors and the vulnerability to each. See also [RFC4686]. + +8.1. Misuse of Body Length Limits ("l=" Tag) + + Body length limits (in the form of the "l=" tag) are subject to + several potential attacks. + +8.1.1. Addition of New MIME Parts to Multipart/* + + If the body length limit does not cover a closing MIME multipart + section (including the trailing "--CRLF" portion), then it is + possible for an attacker to intercept a properly signed multipart + message and add a new body part. Depending on the details of the + MIME type and the implementation of the verifying MTA and the + receiving MUA, this could allow an attacker to change the information + displayed to an end user from an apparently trusted source. + + + + + +Allman, et al. Standards Track [Page 52] + +RFC 4871 DKIM Signatures May 2007 + + + For example, if attackers can append information to a "text/html" + body part, they may be able to exploit a bug in some MUAs that + continue to read after a "" marker, and thus display HTML text + on top of already displayed text. If a message has a + "multipart/alternative" body part, they might be able to add a new + body part that is preferred by the displaying MUA. + +8.1.2. Addition of new HTML content to existing content + + Several receiving MUA implementations do not cease display after a + """" tag. In particular, this allows attacks involving + overlaying images on top of existing text. + + INFORMATIVE EXAMPLE: Appending the following text to an existing, + properly closed message will in many MUAs result in inappropriate + data being rendered on top of existing, correct data: +
    + +
    + +8.2. Misappropriated Private Key + + If the private key for a user is resident on their computer and is + not protected by an appropriately secure mechanism, it is possible + for malware to send mail as that user and any other user sharing the + same private key. The malware would not, however, be able to + generate signed spoofs of other signers' addresses, which would aid + in identification of the infected user and would limit the + possibilities for certain types of attacks involving socially + engineered messages. This threat applies mainly to MUA-based + implementations; protection of private keys on servers can be easily + achieved through the use of specialized cryptographic hardware. + + A larger problem occurs if malware on many users' computers obtains + the private keys for those users and transmits them via a covert + channel to a site where they can be shared. The compromised users + would likely not know of the misappropriation until they receive + "bounce" messages from messages they are purported to have sent. + Many users might not understand the significance of these bounce + messages and would not take action. + + One countermeasure is to use a user-entered passphrase to encrypt the + private key, although users tend to choose weak passphrases and often + reuse them for different purposes, possibly allowing an attack + against DKIM to be extended into other domains. Nevertheless, the + decoded private key might be briefly available to compromise by + malware when it is entered, or might be discovered via keystroke + + + +Allman, et al. Standards Track [Page 53] + +RFC 4871 DKIM Signatures May 2007 + + + logging. The added complexity of entering a passphrase each time one + sends a message would also tend to discourage the use of a secure + passphrase. + + A somewhat more effective countermeasure is to send messages through + an outgoing MTA that can authenticate the submitter using existing + techniques (e.g., SMTP Authentication), possibly validate the message + itself (e.g., verify that the header is legitimate and that the + content passes a spam content check), and sign the message using a + key appropriate for the submitter address. Such an MTA can also + apply controls on the volume of outgoing mail each user is permitted + to originate in order to further limit the ability of malware to + generate bulk email. + +8.3. Key Server Denial-of-Service Attacks + + Since the key servers are distributed (potentially separate for each + domain), the number of servers that would need to be attacked to + defeat this mechanism on an Internet-wide basis is very large. + Nevertheless, key servers for individual domains could be attacked, + impeding the verification of messages from that domain. This is not + significantly different from the ability of an attacker to deny + service to the mail exchangers for a given domain, although it + affects outgoing, not incoming, mail. + + A variation on this attack is that if a very large amount of mail + were to be sent using spoofed addresses from a given domain, the key + servers for that domain could be overwhelmed with requests. However, + given the low overhead of verification compared with handling of the + email message itself, such an attack would be difficult to mount. + +8.4. Attacks Against the DNS + + Since the DNS is a required binding for key services, specific + attacks against the DNS must be considered. + + While the DNS is currently insecure [RFC3833], these security + problems are the motivation behind DNS Security (DNSSEC) [RFC4033], + and all users of the DNS will reap the benefit of that work. + + DKIM is only intended as a "sufficient" method of proving + authenticity. It is not intended to provide strong cryptographic + proof about authorship or contents. Other technologies such as + OpenPGP [RFC2440] and S/MIME [RFC3851] address those requirements. + + A second security issue related to the DNS revolves around the + increased DNS traffic as a consequence of fetching selector-based + data as well as fetching signing domain policy. Widespread + + + +Allman, et al. Standards Track [Page 54] + +RFC 4871 DKIM Signatures May 2007 + + + deployment of DKIM will result in a significant increase in DNS + queries to the claimed signing domain. In the case of forgeries on a + large scale, DNS servers could see a substantial increase in queries. + + A specific DNS security issue that should be considered by DKIM + verifiers is the name chaining attack described in Section 2.3 of the + DNS Threat Analysis [RFC3833]. A DKIM verifier, while verifying a + DKIM-Signature header field, could be prompted to retrieve a key + record of an attacker's choosing. This threat can be minimized by + ensuring that name servers, including recursive name servers, used by + the verifier enforce strict checking of "glue" and other additional + information in DNS responses and are therefore not vulnerable to this + attack. + +8.5. Replay Attacks + + In this attack, a spammer sends a message to be spammed to an + accomplice, which results in the message being signed by the + originating MTA. The accomplice resends the message, including the + original signature, to a large number of recipients, possibly by + sending the message to many compromised machines that act as MTAs. + The messages, not having been modified by the accomplice, have valid + signatures. + + Partial solutions to this problem involve the use of reputation + services to convey the fact that the specific email address is being + used for spam and that messages from that signer are likely to be + spam. This requires a real-time detection mechanism in order to + react quickly enough. However, such measures might be prone to + abuse, if for example an attacker resent a large number of messages + received from a victim in order to make them appear to be a spammer. + + Large verifiers might be able to detect unusually large volumes of + mails with the same signature in a short time period. Smaller + verifiers can get substantially the same volume of information via + existing collaborative systems. + +8.6. Limits on Revoking Keys + + When a large domain detects undesirable behavior on the part of one + of its users, it might wish to revoke the key used to sign that + user's messages in order to disavow responsibility for messages that + have not yet been verified or that are the subject of a replay + attack. However, the ability of the domain to do so can be limited + if the same key, for scalability reasons, is used to sign messages + for many other users. Mechanisms for explicitly revoking keys on a + per-address basis have been proposed but require further study as to + their utility and the DNS load they represent. + + + +Allman, et al. Standards Track [Page 55] + +RFC 4871 DKIM Signatures May 2007 + + +8.7. Intentionally Malformed Key Records + + It is possible for an attacker to publish key records in DNS that are + intentionally malformed, with the intent of causing a denial-of- + service attack on a non-robust verifier implementation. The attacker + could then cause a verifier to read the malformed key record by + sending a message to one of its users referencing the malformed + record in a (not necessarily valid) signature. Verifiers MUST + thoroughly verify all key records retrieved from the DNS and be + robust against intentionally as well as unintentionally malformed key + records. + +8.8. Intentionally Malformed DKIM-Signature Header Fields + + Verifiers MUST be prepared to receive messages with malformed DKIM- + Signature header fields, and thoroughly verify the header field + before depending on any of its contents. + +8.9. Information Leakage + + An attacker could determine when a particular signature was verified + by using a per-message selector and then monitoring their DNS traffic + for the key lookup. This would act as the equivalent of a "web bug" + for verification time rather than when the message was read. + +8.10. Remote Timing Attacks + + In some cases, it may be possible to extract private keys using a + remote timing attack [BONEH03]. Implementations should consider + obfuscating the timing to prevent such attacks. + +8.11. Reordered Header Fields + + Existing standards allow intermediate MTAs to reorder header fields. + If a signer signs two or more header fields of the same name, this + can cause spurious verification errors on otherwise legitimate + messages. In particular, signers that sign any existing DKIM- + Signature fields run the risk of having messages incorrectly fail to + verify. + +8.12. RSA Attacks + + An attacker could create a large RSA signing key with a small + exponent, thus requiring that the verification key have a large + exponent. This will force verifiers to use considerable computing + resources to verify the signature. Verifiers might avoid this attack + by refusing to verify signatures that reference selectors with public + keys having unreasonable exponents. + + + +Allman, et al. Standards Track [Page 56] + +RFC 4871 DKIM Signatures May 2007 + + + In general, an attacker might try to overwhelm a verifier by flooding + it with messages requiring verification. This is similar to other + MTA denial-of-service attacks and should be dealt with in a similar + fashion. + +8.13. Inappropriate Signing by Parent Domains + + The trust relationship described in Section 3.8 could conceivably be + used by a parent domain to sign messages with identities in a + subdomain not administratively related to the parent. For example, + the ".com" registry could create messages with signatures using an + "i=" value in the example.com domain. There is no general solution + to this problem, since the administrative cut could occur anywhere in + the domain name. For example, in the domain "example.podunk.ca.us" + there are three administrative cuts (podunk.ca.us, ca.us, and us), + any of which could create messages with an identity in the full + domain. + + INFORMATIVE NOTE: This is considered an acceptable risk for the + same reason that it is acceptable for domain delegation. For + example, in the example above any of the domains could potentially + simply delegate "example.podunk.ca.us" to a server of their choice + and completely replace all DNS-served information. Note that a + verifier MAY ignore signatures that come from an unlikely domain + such as ".com", as discussed in Section 6.1.1. + +9. References + +9.1. Normative References + + [FIPS.180-2.2002] U.S. Department of Commerce, "Secure Hash + Standard", FIPS PUB 180-2, August 2002. + + [ITU.X660.1997] "Information Technology - ASN.1 encoding rules: + Specification of Basic Encoding Rules (BER), + Canonical Encoding Rules (CER) and Distinguished + Encoding Rules (DER)", ITU-T Recommendation X.660, + 1997. + + [RFC2045] Freed, N. and N. Borenstein, "Multipurpose + Internet Mail Extensions (MIME) Part One: Format + of Internet Message Bodies", RFC 2045, + November 1996. + + [RFC2047] Moore, K., "MIME (Multipurpose Internet Mail + Extensions) Part Three: Message header field + Extensions for Non-ASCII Text", RFC 2047, + November 1996. + + + +Allman, et al. Standards Track [Page 57] + +RFC 4871 DKIM Signatures May 2007 + + + [RFC2119] Bradner, S., "Key words for use in RFCs to + Indicate Requirement Levels", BCP 14, RFC 2119, + March 1997. + + [RFC2821] Klensin, J., "Simple Mail Transfer Protocol", + RFC 2821, April 2001. + + [RFC2822] Resnick, P., "Internet Message Format", RFC 2822, + April 2001. + + [RFC3447] Jonsson, J. and B. Kaliski, "Public-Key + Cryptography Standards (PKCS) #1: RSA Cryptography + Specifications Version 2.1", RFC 3447, + February 2003. + + [RFC3490] Faltstrom, P., Hoffman, P., and A. Costello, + "Internationalizing Domain Names in Applications + (IDNA)", RFC 3490, March 2003. + + [RFC4234] Crocker, D., Ed. and P. Overell, "Augmented BNF + for Syntax Specifications: ABNF", RFC 4234, + October 2005. + +9.2. Informative References + + [BONEH03] Proc. 12th USENIX Security Symposium, "Remote + Timing Attacks are Practical", 2003. + + [RFC1847] Galvin, J., Murphy, S., Crocker, S., and N. Freed, + "Security Multiparts for MIME: Multipart/Signed + and Multipart/Encrypted", RFC 1847, October 1995. + + [RFC2434] Narten, T. and H. Alvestrand, "Guidelines for + Writing an IANA Considerations Section in RFCs", + BCP 26, RFC 2434, October 1998. + + [RFC2440] Callas, J., Donnerhacke, L., Finney, H., and R. + Thayer, "OpenPGP Message Format", RFC 2440, + November 1998. + + [RFC3766] Orman, H. and P. Hoffman, "Determining Strengths + for Public Keys Used For Exchanging Symmetric + Keys", RFC 3766, April 2004. + + [RFC3833] Atkins, D. and R. Austein, "Threat Analysis of the + Domain Name System (DNS)", RFC 3833, August 2004. + + + + + +Allman, et al. Standards Track [Page 58] + +RFC 4871 DKIM Signatures May 2007 + + + [RFC3851] Ramsdell, B., "S/MIME Version 3 Message + Specification", RFC 3851, June 1999. + + [RFC3864] Klyne, G., Nottingham, M., and J. Mogul, + "Registration Procedures for Message Header + Fields", BCP 90, September 2004. + + [RFC4033] Arends, R., Austein, R., Larson, M., Massey, D., + and S. Rose, "DNS Security Introduction and + Requirements", RFC 4033, March 2005. + + [RFC4686] Fenton, J., "Analysis of Threats Motivating + DomainKeys Identified Mail (DKIM)", RFC 4686, + September 2006. + + [RFC4870] Delany, M., "Domain-Based Email Authentication + Using Public Keys Advertised in the DNS + (DomainKeys)", RFC 4870, May 2007. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Allman, et al. Standards Track [Page 59] + +RFC 4871 DKIM Signatures May 2007 + + +Appendix A. Example of Use (INFORMATIVE) + + This section shows the complete flow of an email from submission to + final delivery, demonstrating how the various components fit + together. The key used in this example is shown in Appendix C. + +A.1. The User Composes an Email + + + From: Joe SixPack + To: Suzie Q + Subject: Is dinner ready? + Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) + Message-ID: <20030712040037.46341.5F8J@football.example.com> + + Hi. + + We lost the game. Are you hungry yet? + + Joe. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Allman, et al. Standards Track [Page 60] + +RFC 4871 DKIM Signatures May 2007 + + +A.2. The Email Is Signed + + This email is signed by the example.com outbound email server and now + looks like this: + + DKIM-Signature: v=1; a=rsa-sha256; s=brisbane; d=example.com; + c=simple/simple; q=dns/txt; i=joe@football.example.com; + h=Received : From : To : Subject : Date : Message-ID; + bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; + b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB + 4nujc7YopdG5dWLSdNg6xNAZpOPr+kHxt1IrE+NahM6L/LbvaHut + KVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrIx0orEtZV + 4bmp/YzhwvcubU4=; + Received: from client1.football.example.com [192.0.2.1] + by submitserver.example.com with SUBMISSION; + Fri, 11 Jul 2003 21:01:54 -0700 (PDT) + From: Joe SixPack + To: Suzie Q + Subject: Is dinner ready? + Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) + Message-ID: <20030712040037.46341.5F8J@football.example.com> + + Hi. + + We lost the game. Are you hungry yet? + + Joe. + + The signing email server requires access to the private key + associated with the "brisbane" selector to generate this signature. + +A.3. The Email Signature Is Verified + + The signature is normally verified by an inbound SMTP server or + possibly the final delivery agent. However, intervening MTAs can + also perform this verification if they choose to do so. The + verification process uses the domain "example.com" extracted from the + "d=" tag and the selector "brisbane" from the "s=" tag in the DKIM- + Signature header field to form the DNS DKIM query for: + + brisbane._domainkey.example.com + + Signature verification starts with the physically last Received + header field, the From header field, and so forth, in the order + listed in the "h=" tag. Verification follows with a single CRLF + followed by the body (starting with "Hi."). The email is canonically + prepared for verifying with the "simple" method. The result of the + query and subsequent verification of the signature is stored (in this + + + +Allman, et al. Standards Track [Page 61] + +RFC 4871 DKIM Signatures May 2007 + + + example) in the X-Authentication-Results header field line. After + successful verification, the email looks like this: + + X-Authentication-Results: shopping.example.net + header.from=joe@football.example.com; dkim=pass + Received: from mout23.football.example.com (192.168.1.1) + by shopping.example.net with SMTP; + Fri, 11 Jul 2003 21:01:59 -0700 (PDT) + DKIM-Signature: v=1; a=rsa-sha256; s=brisbane; d=example.com; + c=simple/simple; q=dns/txt; i=joe@football.example.com; + h=Received : From : To : Subject : Date : Message-ID; + bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; + b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB + 4nujc7YopdG5dWLSdNg6xNAZpOPr+kHxt1IrE+NahM6L/LbvaHut + KVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrIx0orEtZV + 4bmp/YzhwvcubU4=; + Received: from client1.football.example.com [192.0.2.1] + by submitserver.example.com with SUBMISSION; + Fri, 11 Jul 2003 21:01:54 -0700 (PDT) + From: Joe SixPack + To: Suzie Q + Subject: Is dinner ready? + Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) + Message-ID: <20030712040037.46341.5F8J@football.example.com> + + Hi. + + We lost the game. Are you hungry yet? + + Joe. + +Appendix B. Usage Examples (INFORMATIVE) + + DKIM signing and validating can be used in different ways, for + different operational scenarios. This Appendix discusses some common + examples. + + NOTE: Descriptions in this Appendix are for informational purposes + only. They describe various ways that DKIM can be used, given + particular constraints and needs. In no case are these examples + intended to be taken as providing explanation or guidance + concerning DKIM specification details, when creating an + implementation. + + + + + + + + +Allman, et al. Standards Track [Page 62] + +RFC 4871 DKIM Signatures May 2007 + + +B.1. Alternate Submission Scenarios + + In the most simple scenario, a user's MUA, MSA, and Internet + (boundary) MTA are all within the same administrative environment, + using the same domain name. Therefore, all of the components + involved in submission and initial transfer are related. However, it + is common for two or more of the components to be under independent + administrative control. This creates challenges for choosing and + administering the domain name to use for signing, and for its + relationship to common email identity header fields. + +B.1.1. Delegated Business Functions + + Some organizations assign specific business functions to discrete + groups, inside or outside the organization. The goal, then, is to + authorize that group to sign some mail, but to constrain what + signatures they can generate. DKIM selectors (the "s=" signature + tag) and granularity (the "g=" key tag) facilitate this kind of + restricted authorization. Examples of these outsourced business + functions are legitimate email marketing providers and corporate + benefits providers. + + Here, the delegated group needs to be able to send messages that are + signed, using the email domain of the client company. At the same + time, the client often is reluctant to register a key for the + provider that grants the ability to send messages for arbitrary + addresses in the domain. + + There are multiple ways to administer these usage scenarios. In one + case, the client organization provides all of the public query + service (for example, DNS) administration, and in another it uses DNS + delegation to enable all ongoing administration of the DKIM key + record by the delegated group. + + If the client organization retains responsibility for all of the DNS + administration, the outsourcing company can generate a key pair, + supplying the public key to the client company, which then registers + it in the query service, using a unique selector that authorizes a + specific From header field Local-part. For example, a client with + the domain "example.com" could have the selector record specify + "g=winter-promotions" so that this signature is only valid for mail + with a From address of "winter-promotions@example.com". This would + enable the provider to send messages using that specific address and + have them verify properly. The client company retains control over + the email address because it retains the ability to revoke the key at + any time. + + + + + +Allman, et al. Standards Track [Page 63] + +RFC 4871 DKIM Signatures May 2007 + + + If the client wants the delegated group to do the DNS administration, + it can have the domain name that is specified with the selector point + to the provider's DNS server. The provider then creates and + maintains all of the DKIM signature information for that selector. + Hence, the client cannot provide constraints on the Local-part of + addresses that get signed, but it can revoke the provider's signing + rights by removing the DNS delegation record. + +B.1.2. PDAs and Similar Devices + + PDAs demonstrate the need for using multiple keys per domain. + Suppose that John Doe wanted to be able to send messages using his + corporate email address, jdoe@example.com, and his email device did + not have the ability to make a Virtual Private Network (VPN) + connection to the corporate network, either because the device is + limited or because there are restrictions enforced by his Internet + access provider. If the device was equipped with a private key + registered for jdoe@example.com by the administrator of the + example.com domain, and appropriate software to sign messages, John + could sign the message on the device itself before transmission + through the outgoing network of the access service provider. + +B.1.3. Roaming Users + + Roaming users often find themselves in circumstances where it is + convenient or necessary to use an SMTP server other than their home + server; examples are conferences and many hotels. In such + circumstances, a signature that is added by the submission service + will use an identity that is different from the user's home system. + + Ideally, roaming users would connect back to their home server using + either a VPN or a SUBMISSION server running with SMTP AUTHentication + on port 587. If the signing can be performed on the roaming user's + laptop, then they can sign before submission, although the risk of + further modification is high. If neither of these are possible, + these roaming users will not be able to send mail signed using their + own domain key. + +B.1.4. Independent (Kiosk) Message Submission + + Stand-alone services, such as walk-up kiosks and web-based + information services, have no enduring email service relationship + with the user, but users occasionally request that mail be sent on + their behalf. For example, a website providing news often allows the + reader to forward a copy of the article to a friend. This is + typically done using the reader's own email address, to indicate who + the author is. This is sometimes referred to as the "Evite problem", + + + + +Allman, et al. Standards Track [Page 64] + +RFC 4871 DKIM Signatures May 2007 + + + named after the website of the same name that allows a user to send + invitations to friends. + + A common way this is handled is to continue to put the reader's email + address in the From header field of the message, but put an address + owned by the email posting site into the Sender header field. The + posting site can then sign the message, using the domain that is in + the Sender field. This provides useful information to the receiving + email site, which is able to correlate the signing domain with the + initial submission email role. + + Receiving sites often wish to provide their end users with + information about mail that is mediated in this fashion. Although + the real efficacy of different approaches is a subject for human + factors usability research, one technique that is used is for the + verifying system to rewrite the From header field, to indicate the + address that was verified. For example: From: John Doe via + news@news-site.com . (Note that such rewriting + will break a signature, unless it is done after the verification pass + is complete.) + +B.2. Alternate Delivery Scenarios + + Email is often received at a mailbox that has an address different + from the one used during initial submission. In these cases, an + intermediary mechanism operates at the address originally used and it + then passes the message on to the final destination. This mediation + process presents some challenges for DKIM signatures. + +B.2.1. Affinity Addresses + + "Affinity addresses" allow a user to have an email address that + remains stable, even as the user moves among different email + providers. They are typically associated with college alumni + associations, professional organizations, and recreational + organizations with which they expect to have a long-term + relationship. These domains usually provide forwarding of incoming + email, and they often have an associated Web application that + authenticates the user and allows the forwarding address to be + changed. However, these services usually depend on users sending + outgoing messages through their own service providers' MTAs. Hence, + mail that is signed with the domain of the affinity address is not + signed by an entity that is administered by the organization owning + that domain. + + With DKIM, affinity domains could use the Web application to allow + users to register per-user keys to be used to sign messages on behalf + of their affinity address. The user would take away the secret half + + + +Allman, et al. Standards Track [Page 65] + +RFC 4871 DKIM Signatures May 2007 + + + of the key pair for signing, and the affinity domain would publish + the public half in DNS for access by verifiers. + + This is another application that takes advantage of user-level + keying, and domains used for affinity addresses would typically have + a very large number of user-level keys. Alternatively, the affinity + domain could handle outgoing mail, operating a mail submission agent + that authenticates users before accepting and signing messages for + them. This is of course dependent on the user's service provider not + blocking the relevant TCP ports used for mail submission. + +B.2.2. Simple Address Aliasing (.forward) + + In some cases, a recipient is allowed to configure an email address + to cause automatic redirection of email messages from the original + address to another, such as through the use of a Unix .forward file. + In this case, messages are typically redirected by the mail handling + service of the recipient's domain, without modification, except for + the addition of a Received header field to the message and a change + in the envelope recipient address. In this case, the recipient at + the final address' mailbox is likely to be able to verify the + original signature since the signed content has not changed, and DKIM + is able to validate the message signature. + +B.2.3. Mailing Lists and Re-Posters + + There is a wide range of behaviors in services that take delivery of + a message and then resubmit it. A primary example is with mailing + lists (collectively called "forwarders" below), ranging from those + that make no modification to the message itself, other than to add a + Received header field and change the envelope information, to those + that add header fields, change the Subject header field, add content + to the body (typically at the end), or reformat the body in some + manner. The simple ones produce messages that are quite similar to + the automated alias services. More elaborate systems essentially + create a new message. + + A Forwarder that does not modify the body or signed header fields of + a message is likely to maintain the validity of the existing + signature. It also could choose to add its own signature to the + message. + + Forwarders which modify a message in a way that could make an + existing signature invalid are particularly good candidates for + adding their own signatures (e.g., mailing-list-name@example.net). + Since (re-)signing is taking responsibility for the content of the + message, these signing forwarders are likely to be selective, and + forward or re-sign a message only if it is received with a valid + + + +Allman, et al. Standards Track [Page 66] + +RFC 4871 DKIM Signatures May 2007 + + + signature or if they have some other basis for knowing that the + message is not spoofed. + + A common practice among systems that are primarily redistributors of + mail is to add a Sender header field to the message, to identify the + address being used to sign the message. This practice will remove + any preexisting Sender header field as required by [RFC2822]. The + forwarder applies a new DKIM-Signature header field with the + signature, public key, and related information of the forwarder. + +Appendix C. Creating a Public Key (INFORMATIVE) + + The default signature is an RSA signed SHA256 digest of the complete + email. For ease of explanation, the openssl command is used to + describe the mechanism by which keys and signatures are managed. One + way to generate a 1024-bit, unencrypted private key suitable for DKIM + is to use openssl like this: + + $ openssl genrsa -out rsa.private 1024 + + For increased security, the "-passin" parameter can also be added to + encrypt the private key. Use of this parameter will require entering + a password for several of the following steps. Servers may prefer to + use hardware cryptographic support. + + The "genrsa" step results in the file rsa.private containing the key + information similar to this: + + -----BEGIN RSA PRIVATE KEY----- + MIICXwIBAAKBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYtIxN2SnFC + jxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v/RtdC2UzJ1lWT947qR+Rcac2gb + to/NMqJ0fzfVjH4OuKhitdY9tf6mcwGjaNBcWToIMmPSPDdQPNUYckcQ2QIDAQAB + AoGBALmn+XwWk7akvkUlqb+dOxyLB9i5VBVfje89Teolwc9YJT36BGN/l4e0l6QX + /1//6DWUTB3KI6wFcm7TWJcxbS0tcKZX7FsJvUz1SbQnkS54DJck1EZO/BLa5ckJ + gAYIaqlA9C0ZwM6i58lLlPadX/rtHb7pWzeNcZHjKrjM461ZAkEA+itss2nRlmyO + n1/5yDyCluST4dQfO8kAB3toSEVc7DeFeDhnC1mZdjASZNvdHS4gbLIA1hUGEF9m + 3hKsGUMMPwJBAPW5v/U+AWTADFCS22t72NUurgzeAbzb1HWMqO4y4+9Hpjk5wvL/ + eVYizyuce3/fGke7aRYw/ADKygMJdW8H/OcCQQDz5OQb4j2QDpPZc0Nc4QlbvMsj + 7p7otWRO5xRa6SzXqqV3+F0VpqvDmshEBkoCydaYwc2o6WQ5EBmExeV8124XAkEA + qZzGsIxVP+sEVRWZmW6KNFSdVUpk3qzK0Tz/WjQMe5z0UunY9Ax9/4PVhp/j61bf + eAYXunajbBSOLlx4D+TunwJBANkPI5S9iylsbLs6NkaMHV6k5ioHBBmgCak95JGX + GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= + -----END RSA PRIVATE KEY----- + + To extract the public-key component from the private key, use openssl + like this: + + $ openssl rsa -in rsa.private -out rsa.public -pubout -outform PEM + + + +Allman, et al. Standards Track [Page 67] + +RFC 4871 DKIM Signatures May 2007 + + + This results in the file rsa.public containing the key information + similar to this: + + -----BEGIN PUBLIC KEY----- + MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkM + oGeLnQg1fWn7/zYtIxN2SnFCjxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v/R + tdC2UzJ1lWT947qR+Rcac2gbto/NMqJ0fzfVjH4OuKhitdY9tf6mcwGjaNBcWToI + MmPSPDdQPNUYckcQ2QIDAQAB + -----END PUBLIC KEY----- + + This public-key data (without the BEGIN and END tags) is placed in + the DNS: + + brisbane IN TXT ("v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ" + "KBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYt" + "IxN2SnFCjxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v" + "/RtdC2UzJ1lWT947qR+Rcac2gbto/NMqJ0fzfVjH4OuKhi" + "tdY9tf6mcwGjaNBcWToIMmPSPDdQPNUYckcQ2QIDAQAB") + +Appendix D. MUA Considerations + + When a DKIM signature is verified, the processing system sometimes + makes the result available to the recipient user's MUA. How to + present this information to the user in a way that helps them is a + matter of continuing human factors usability research. The tendency + is to have the MUA highlight the address associated with this signing + identity in some way, in an attempt to show the user the address from + which the mail was sent. An MUA might do this with visual cues such + as graphics, or it might include the address in an alternate view, or + it might even rewrite the original From address using the verified + information. Some MUAs might indicate which header fields were + protected by the validated DKIM signature. This could be done with a + positive indication on the signed header fields, with a negative + indication on the unsigned header fields, by visually hiding the + unsigned header fields, or some combination of these. If an MUA uses + visual indications for signed header fields, the MUA probably needs + to be careful not to display unsigned header fields in a way that + might be construed by the end user as having been signed. If the + message has an l= tag whose value does not extend to the end of the + message, the MUA might also hide or mark the portion of the message + body that was not signed. + + The aforementioned information is not intended to be exhaustive. The + MUA may choose to highlight, accentuate, hide, or otherwise display + any other information that may, in the opinion of the MUA author, be + deemed important to the end user. + + + + + +Allman, et al. Standards Track [Page 68] + +RFC 4871 DKIM Signatures May 2007 + + +Appendix E. Acknowledgements + + The authors wish to thank Russ Allbery, Edwin Aoki, Claus Assmann, + Steve Atkins, Rob Austein, Fred Baker, Mark Baugher, Steve Bellovin, + Nathaniel Borenstein, Dave Crocker, Michael Cudahy, Dennis Dayman, + Jutta Degener, Frank Ellermann, Patrik Faeltstroem, Mark Fanto, + Stephen Farrell, Duncan Findlay, Elliot Gillum, Olafur + Gu[eth]mundsson, Phillip Hallam-Baker, Tony Hansen, Sam Hartman, + Arvel Hathcock, Amir Herzberg, Paul Hoffman, Russ Housley, Craig + Hughes, Cullen Jennings, Don Johnsen, Harry Katz, Murray S. + Kucherawy, Barry Leiba, John Levine, Charles Lindsey, Simon + Longsdale, David Margrave, Justin Mason, David Mayne, Thierry Moreau, + Steve Murphy, Russell Nelson, Dave Oran, Doug Otis, Shamim Pirzada, + Juan Altmayer Pizzorno, Sanjay Pol, Blake Ramsdell, Christian Renaud, + Scott Renfro, Neil Rerup, Eric Rescorla, Dave Rossetti, Hector + Santos, Jim Schaad, the Spamhaus.org team, Malte S. Stretz, Robert + Sanders, Rand Wacker, Sam Weiler, and Dan Wing for their valuable + suggestions and constructive criticism. + + The DomainKeys specification was a primary source from which this + specification has been derived. Further information about DomainKeys + is at [RFC4870]. + +Authors' Addresses + + Eric Allman + Sendmail, Inc. + 6425 Christie Ave, Suite 400 + Emeryville, CA 94608 + USA + + Phone: +1 510 594 5501 + EMail: eric+dkim@sendmail.org + URI: + + + Jon Callas + PGP Corporation + 3460 West Bayshore + Palo Alto, CA 94303 + USA + + Phone: +1 650 319 9016 + EMail: jon@pgp.com + + + + + + + +Allman, et al. Standards Track [Page 69] + +RFC 4871 DKIM Signatures May 2007 + + + Mark Delany + Yahoo! Inc + 701 First Avenue + Sunnyvale, CA 95087 + USA + + Phone: +1 408 349 6831 + EMail: markd+dkim@yahoo-inc.com + URI: + + + Miles Libbey + Yahoo! Inc + 701 First Avenue + Sunnyvale, CA 95087 + USA + + EMail: mlibbeymail-mailsig@yahoo.com + URI: + + + Jim Fenton + Cisco Systems, Inc. + MS SJ-9/2 + 170 W. Tasman Drive + San Jose, CA 95134-1706 + USA + + Phone: +1 408 526 5914 + EMail: fenton@cisco.com + URI: + + + Michael Thomas + Cisco Systems, Inc. + MS SJ-9/2 + 170 W. Tasman Drive + San Jose, CA 95134-1706 + + Phone: +1 408 525 5386 + EMail: mat@cisco.com + + + + + + + + + + +Allman, et al. Standards Track [Page 70] + +RFC 4871 DKIM Signatures May 2007 + + +Full Copyright Statement + + Copyright (C) The IETF Trust (2007). + + This document is subject to the rights, licenses and restrictions + contained in BCP 78, and except as set forth therein, the authors + retain all their rights. + + This document and the information contained herein are provided on an + "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS + OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE IETF TRUST AND + THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF + THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED + WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Intellectual Property + + The IETF takes no position regarding the validity or scope of any + Intellectual Property Rights or other rights that might be claimed to + pertain to the implementation or use of the technology described in + this document or the extent to which any license under such rights + might or might not be available; nor does it represent that it has + made any independent effort to identify any such rights. Information + on the procedures with respect to rights in RFC documents can be + found in BCP 78 and BCP 79. + + Copies of IPR disclosures made to the IETF Secretariat and any + assurances of licenses to be made available, or the result of an + attempt made to obtain a general license or permission for the use of + such proprietary rights by implementers or users of this + specification can be obtained from the IETF on-line IPR repository at + http://www.ietf.org/ipr. + + The IETF invites any interested party to bring to its attention any + copyrights, patents or patent applications, or other proprietary + rights that may cover technology that may be required to implement + this standard. Please address the information to the IETF at + ietf-ipr@ietf.org. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + +Allman, et al. Standards Track [Page 71] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4880.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4880.txt new file mode 100644 index 0000000..0db9453 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4880.txt @@ -0,0 +1,5042 @@ + + + + + +Network Working Group J. Callas +Request for Comments: 4880 PGP Corporation +Obsoletes: 1991, 2440 L. Donnerhacke +Category: Standards Track IKS GmbH + H. Finney + PGP Corporation + D. Shaw + R. Thayer + November 2007 + + + OpenPGP Message Format + +Status of This Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + This document is maintained in order to publish all necessary + information needed to develop interoperable applications based on the + OpenPGP format. It is not a step-by-step cookbook for writing an + application. It describes only the format and methods needed to + read, check, generate, and write conforming packets crossing any + network. It does not deal with storage and implementation questions. + It does, however, discuss implementation issues necessary to avoid + security flaws. + + OpenPGP software uses a combination of strong public-key and + symmetric cryptography to provide security services for electronic + communications and data storage. These services include + confidentiality, key management, authentication, and digital + signatures. This document specifies the message formats used in + OpenPGP. + + + + + + + + + + + + + +Callas, et al Standards Track [Page 1] + +RFC 4880 OpenPGP Message Format November 2007 + + +Table of Contents + + 1. Introduction ....................................................5 + 1.1. Terms ......................................................5 + 2. General functions ...............................................6 + 2.1. Confidentiality via Encryption .............................6 + 2.2. Authentication via Digital Signature .......................7 + 2.3. Compression ................................................7 + 2.4. Conversion to Radix-64 .....................................8 + 2.5. Signature-Only Applications ................................8 + 3. Data Element Formats ............................................8 + 3.1. Scalar Numbers .............................................8 + 3.2. Multiprecision Integers ....................................9 + 3.3. Key IDs ....................................................9 + 3.4. Text .......................................................9 + 3.5. Time Fields ...............................................10 + 3.6. Keyrings ..................................................10 + 3.7. String-to-Key (S2K) Specifiers ............................10 + 3.7.1. String-to-Key (S2K) Specifier Types ................10 + 3.7.1.1. Simple S2K ................................10 + 3.7.1.2. Salted S2K ................................11 + 3.7.1.3. Iterated and Salted S2K ...................11 + 3.7.2. String-to-Key Usage ................................12 + 3.7.2.1. Secret-Key Encryption .....................12 + 3.7.2.2. Symmetric-Key Message Encryption ..........13 + 4. Packet Syntax ..................................................13 + 4.1. Overview ..................................................13 + 4.2. Packet Headers ............................................13 + 4.2.1. Old Format Packet Lengths ..........................14 + 4.2.2. New Format Packet Lengths ..........................15 + 4.2.2.1. One-Octet Lengths .........................15 + 4.2.2.2. Two-Octet Lengths .........................15 + 4.2.2.3. Five-Octet Lengths ........................15 + 4.2.2.4. Partial Body Lengths ......................16 + 4.2.3. Packet Length Examples .............................16 + 4.3. Packet Tags ...............................................17 + 5. Packet Types ...................................................17 + 5.1. Public-Key Encrypted Session Key Packets (Tag 1) ..........17 + 5.2. Signature Packet (Tag 2) ..................................19 + 5.2.1. Signature Types ....................................19 + 5.2.2. Version 3 Signature Packet Format ..................21 + 5.2.3. Version 4 Signature Packet Format ..................24 + 5.2.3.1. Signature Subpacket Specification .........25 + 5.2.3.2. Signature Subpacket Types .................27 + 5.2.3.3. Notes on Self-Signatures ..................27 + 5.2.3.4. Signature Creation Time ...................28 + 5.2.3.5. Issuer ....................................28 + 5.2.3.6. Key Expiration Time .......................28 + + + +Callas, et al Standards Track [Page 2] + +RFC 4880 OpenPGP Message Format November 2007 + + + 5.2.3.7. Preferred Symmetric Algorithms ............28 + 5.2.3.8. Preferred Hash Algorithms .................29 + 5.2.3.9. Preferred Compression Algorithms ..........29 + 5.2.3.10. Signature Expiration Time ................29 + 5.2.3.11. Exportable Certification .................29 + 5.2.3.12. Revocable ................................30 + 5.2.3.13. Trust Signature ..........................30 + 5.2.3.14. Regular Expression .......................31 + 5.2.3.15. Revocation Key ...........................31 + 5.2.3.16. Notation Data ............................31 + 5.2.3.17. Key Server Preferences ...................32 + 5.2.3.18. Preferred Key Server .....................33 + 5.2.3.19. Primary User ID ..........................33 + 5.2.3.20. Policy URI ...............................33 + 5.2.3.21. Key Flags ................................33 + 5.2.3.22. Signer's User ID .........................34 + 5.2.3.23. Reason for Revocation ....................35 + 5.2.3.24. Features .................................36 + 5.2.3.25. Signature Target .........................36 + 5.2.3.26. Embedded Signature .......................37 + 5.2.4. Computing Signatures ...............................37 + 5.2.4.1. Subpacket Hints ...........................38 + 5.3. Symmetric-Key Encrypted Session Key Packets (Tag 3) .......38 + 5.4. One-Pass Signature Packets (Tag 4) ........................39 + 5.5. Key Material Packet .......................................40 + 5.5.1. Key Packet Variants ................................40 + 5.5.1.1. Public-Key Packet (Tag 6) .................40 + 5.5.1.2. Public-Subkey Packet (Tag 14) .............40 + 5.5.1.3. Secret-Key Packet (Tag 5) .................41 + 5.5.1.4. Secret-Subkey Packet (Tag 7) ..............41 + 5.5.2. Public-Key Packet Formats ..........................41 + 5.5.3. Secret-Key Packet Formats ..........................43 + 5.6. Compressed Data Packet (Tag 8) ............................45 + 5.7. Symmetrically Encrypted Data Packet (Tag 9) ...............45 + 5.8. Marker Packet (Obsolete Literal Packet) (Tag 10) ..........46 + 5.9. Literal Data Packet (Tag 11) ..............................46 + 5.10. Trust Packet (Tag 12) ....................................47 + 5.11. User ID Packet (Tag 13) ..................................48 + 5.12. User Attribute Packet (Tag 17) ...........................48 + 5.12.1. The Image Attribute Subpacket .....................48 + 5.13. Sym. Encrypted Integrity Protected Data Packet (Tag 18) ..49 + 5.14. Modification Detection Code Packet (Tag 19) ..............52 + 6. Radix-64 Conversions ...........................................53 + 6.1. An Implementation of the CRC-24 in "C" ....................54 + 6.2. Forming ASCII Armor .......................................54 + 6.3. Encoding Binary in Radix-64 ...............................57 + 6.4. Decoding Radix-64 .........................................58 + 6.5. Examples of Radix-64 ......................................59 + + + +Callas, et al Standards Track [Page 3] + +RFC 4880 OpenPGP Message Format November 2007 + + + 6.6. Example of an ASCII Armored Message .......................59 + 7. Cleartext Signature Framework ..................................59 + 7.1. Dash-Escaped Text .........................................60 + 8. Regular Expressions ............................................61 + 9. Constants ......................................................61 + 9.1. Public-Key Algorithms .....................................62 + 9.2. Symmetric-Key Algorithms ..................................62 + 9.3. Compression Algorithms ....................................63 + 9.4. Hash Algorithms ...........................................63 + 10. IANA Considerations ...........................................63 + 10.1. New String-to-Key Specifier Types ........................64 + 10.2. New Packets ..............................................64 + 10.2.1. User Attribute Types ..............................64 + 10.2.1.1. Image Format Subpacket Types .............64 + 10.2.2. New Signature Subpackets ..........................64 + 10.2.2.1. Signature Notation Data Subpackets .......65 + 10.2.2.2. Key Server Preference Extensions .........65 + 10.2.2.3. Key Flags Extensions .....................65 + 10.2.2.4. Reason For Revocation Extensions .........65 + 10.2.2.5. Implementation Features ..................66 + 10.2.3. New Packet Versions ...............................66 + 10.3. New Algorithms ...........................................66 + 10.3.1. Public-Key Algorithms .............................66 + 10.3.2. Symmetric-Key Algorithms ..........................67 + 10.3.3. Hash Algorithms ...................................67 + 10.3.4. Compression Algorithms ............................67 + 11. Packet Composition ............................................67 + 11.1. Transferable Public Keys .................................67 + 11.2. Transferable Secret Keys .................................69 + 11.3. OpenPGP Messages .........................................69 + 11.4. Detached Signatures ......................................70 + 12. Enhanced Key Formats ..........................................70 + 12.1. Key Structures ...........................................70 + 12.2. Key IDs and Fingerprints .................................71 + 13. Notes on Algorithms ...........................................72 + 13.1. PKCS#1 Encoding in OpenPGP ...............................72 + 13.1.1. EME-PKCS1-v1_5-ENCODE .............................73 + 13.1.2. EME-PKCS1-v1_5-DECODE .............................73 + 13.1.3. EMSA-PKCS1-v1_5 ...................................74 + 13.2. Symmetric Algorithm Preferences ..........................75 + 13.3. Other Algorithm Preferences ..............................76 + 13.3.1. Compression Preferences ...........................76 + 13.3.2. Hash Algorithm Preferences ........................76 + 13.4. Plaintext ................................................77 + 13.5. RSA ......................................................77 + 13.6. DSA ......................................................77 + 13.7. Elgamal ..................................................78 + 13.8. Reserved Algorithm Numbers ...............................78 + + + +Callas, et al Standards Track [Page 4] + +RFC 4880 OpenPGP Message Format November 2007 + + + 13.9. OpenPGP CFB Mode .........................................78 + 13.10. Private or Experimental Parameters ......................79 + 13.11. Extension of the MDC System .............................80 + 13.12. Meta-Considerations for Expansion .......................80 + 14. Security Considerations .......................................81 + 15. Implementation Nits ...........................................84 + 16. References ....................................................86 + 16.1. Normative References .....................................86 + 16.2. Informative References ...................................88 + +1. Introduction + + This document provides information on the message-exchange packet + formats used by OpenPGP to provide encryption, decryption, signing, + and key management functions. It is a revision of RFC 2440, "OpenPGP + Message Format", which itself replaces RFC 1991, "PGP Message + Exchange Formats" [RFC1991] [RFC2440]. + +1.1. Terms + + * OpenPGP - This is a term for security software that uses PGP 5.x + as a basis, formalized in RFC 2440 and this document. + + * PGP - Pretty Good Privacy. PGP is a family of software systems + developed by Philip R. Zimmermann from which OpenPGP is based. + + * PGP 2.6.x - This version of PGP has many variants, hence the term + PGP 2.6.x. It used only RSA, MD5, and IDEA for its cryptographic + transforms. An informational RFC, RFC 1991, was written + describing this version of PGP. + + * PGP 5.x - This version of PGP is formerly known as "PGP 3" in the + community and also in the predecessor of this document, RFC 1991. + It has new formats and corrects a number of problems in the PGP + 2.6.x design. It is referred to here as PGP 5.x because that + software was the first release of the "PGP 3" code base. + + * GnuPG - GNU Privacy Guard, also called GPG. GnuPG is an OpenPGP + implementation that avoids all encumbered algorithms. + Consequently, early versions of GnuPG did not include RSA public + keys. GnuPG may or may not have (depending on version) support + for IDEA or other encumbered algorithms. + + "PGP", "Pretty Good", and "Pretty Good Privacy" are trademarks of PGP + Corporation and are used with permission. The term "OpenPGP" refers + to the protocol described in this and related documents. + + + + + +Callas, et al Standards Track [Page 5] + +RFC 4880 OpenPGP Message Format November 2007 + + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [RFC2119]. + + The key words "PRIVATE USE", "HIERARCHICAL ALLOCATION", "FIRST COME + FIRST SERVED", "EXPERT REVIEW", "SPECIFICATION REQUIRED", "IESG + APPROVAL", "IETF CONSENSUS", and "STANDARDS ACTION" that appear in + this document when used to describe namespace allocation are to be + interpreted as described in [RFC2434]. + +2. General functions + + OpenPGP provides data integrity services for messages and data files + by using these core technologies: + + - digital signatures + + - encryption + + - compression + + - Radix-64 conversion + + In addition, OpenPGP provides key management and certificate + services, but many of these are beyond the scope of this document. + +2.1. Confidentiality via Encryption + + OpenPGP combines symmetric-key encryption and public-key encryption + to provide confidentiality. When made confidential, first the object + is encrypted using a symmetric encryption algorithm. Each symmetric + key is used only once, for a single object. A new "session key" is + generated as a random number for each object (sometimes referred to + as a session). Since it is used only once, the session key is bound + to the message and transmitted with it. To protect the key, it is + encrypted with the receiver's public key. The sequence is as + follows: + + 1. The sender creates a message. + + 2. The sending OpenPGP generates a random number to be used as a + session key for this message only. + + 3. The session key is encrypted using each recipient's public key. + These "encrypted session keys" start the message. + + + + + + +Callas, et al Standards Track [Page 6] + +RFC 4880 OpenPGP Message Format November 2007 + + + 4. The sending OpenPGP encrypts the message using the session key, + which forms the remainder of the message. Note that the message + is also usually compressed. + + 5. The receiving OpenPGP decrypts the session key using the + recipient's private key. + + 6. The receiving OpenPGP decrypts the message using the session key. + If the message was compressed, it will be decompressed. + + With symmetric-key encryption, an object may be encrypted with a + symmetric key derived from a passphrase (or other shared secret), or + a two-stage mechanism similar to the public-key method described + above in which a session key is itself encrypted with a symmetric + algorithm keyed from a shared secret. + + Both digital signature and confidentiality services may be applied to + the same message. First, a signature is generated for the message + and attached to the message. Then the message plus signature is + encrypted using a symmetric session key. Finally, the session key is + encrypted using public-key encryption and prefixed to the encrypted + block. + +2.2. Authentication via Digital Signature + + The digital signature uses a hash code or message digest algorithm, + and a public-key signature algorithm. The sequence is as follows: + + 1. The sender creates a message. + + 2. The sending software generates a hash code of the message. + + 3. The sending software generates a signature from the hash code + using the sender's private key. + + 4. The binary signature is attached to the message. + + 5. The receiving software keeps a copy of the message signature. + + 6. The receiving software generates a new hash code for the received + message and verifies it using the message's signature. If the + verification is successful, the message is accepted as authentic. + +2.3. Compression + + OpenPGP implementations SHOULD compress the message after applying + the signature but before encryption. + + + + +Callas, et al Standards Track [Page 7] + +RFC 4880 OpenPGP Message Format November 2007 + + + If an implementation does not implement compression, its authors + should be aware that most OpenPGP messages in the world are + compressed. Thus, it may even be wise for a space-constrained + implementation to implement decompression, but not compression. + + Furthermore, compression has the added side effect that some types of + attacks can be thwarted by the fact that slightly altered, compressed + data rarely uncompresses without severe errors. This is hardly + rigorous, but it is operationally useful. These attacks can be + rigorously prevented by implementing and using Modification Detection + Codes as described in sections following. + +2.4. Conversion to Radix-64 + + OpenPGP's underlying native representation for encrypted messages, + signature certificates, and keys is a stream of arbitrary octets. + Some systems only permit the use of blocks consisting of seven-bit, + printable text. For transporting OpenPGP's native raw binary octets + through channels that are not safe to raw binary data, a printable + encoding of these binary octets is needed. OpenPGP provides the + service of converting the raw 8-bit binary octet stream to a stream + of printable ASCII characters, called Radix-64 encoding or ASCII + Armor. + + Implementations SHOULD provide Radix-64 conversions. + +2.5. Signature-Only Applications + + OpenPGP is designed for applications that use both encryption and + signatures, but there are a number of problems that are solved by a + signature-only implementation. Although this specification requires + both encryption and signatures, it is reasonable for there to be + subset implementations that are non-conformant only in that they omit + encryption. + +3. Data Element Formats + + This section describes the data elements used by OpenPGP. + +3.1. Scalar Numbers + + Scalar numbers are unsigned and are always stored in big-endian + format. Using n[k] to refer to the kth octet being interpreted, the + value of a two-octet scalar is ((n[0] << 8) + n[1]). The value of a + four-octet scalar is ((n[0] << 24) + (n[1] << 16) + (n[2] << 8) + + n[3]). + + + + + +Callas, et al Standards Track [Page 8] + +RFC 4880 OpenPGP Message Format November 2007 + + +3.2. Multiprecision Integers + + Multiprecision integers (also called MPIs) are unsigned integers used + to hold large integers such as the ones used in cryptographic + calculations. + + An MPI consists of two pieces: a two-octet scalar that is the length + of the MPI in bits followed by a string of octets that contain the + actual integer. + + These octets form a big-endian number; a big-endian number can be + made into an MPI by prefixing it with the appropriate length. + + Examples: + + (all numbers are in hexadecimal) + + The string of octets [00 01 01] forms an MPI with the value 1. The + string [00 09 01 FF] forms an MPI with the value of 511. + + Additional rules: + + The size of an MPI is ((MPI.length + 7) / 8) + 2 octets. + + The length field of an MPI describes the length starting from its + most significant non-zero bit. Thus, the MPI [00 02 01] is not + formed correctly. It should be [00 01 01]. + + Unused bits of an MPI MUST be zero. + + Also note that when an MPI is encrypted, the length refers to the + plaintext MPI. It may be ill-formed in its ciphertext. + +3.3. Key IDs + + A Key ID is an eight-octet scalar that identifies a key. + Implementations SHOULD NOT assume that Key IDs are unique. The + section "Enhanced Key Formats" below describes how Key IDs are + formed. + +3.4. Text + + Unless otherwise specified, the character set for text is the UTF-8 + [RFC3629] encoding of Unicode [ISO10646]. + + + + + + + +Callas, et al Standards Track [Page 9] + +RFC 4880 OpenPGP Message Format November 2007 + + +3.5. Time Fields + + A time field is an unsigned four-octet number containing the number + of seconds elapsed since midnight, 1 January 1970 UTC. + +3.6. Keyrings + + A keyring is a collection of one or more keys in a file or database. + Traditionally, a keyring is simply a sequential list of keys, but may + be any suitable database. It is beyond the scope of this standard to + discuss the details of keyrings or other databases. + +3.7. String-to-Key (S2K) Specifiers + + String-to-key (S2K) specifiers are used to convert passphrase strings + into symmetric-key encryption/decryption keys. They are used in two + places, currently: to encrypt the secret part of private keys in the + private keyring, and to convert passphrases to encryption keys for + symmetrically encrypted messages. + +3.7.1. String-to-Key (S2K) Specifier Types + + There are three types of S2K specifiers currently supported, and + some reserved values: + + ID S2K Type + -- -------- + 0 Simple S2K + 1 Salted S2K + 2 Reserved value + 3 Iterated and Salted S2K + 100 to 110 Private/Experimental S2K + + These are described in Sections 3.7.1.1 - 3.7.1.3. + +3.7.1.1. Simple S2K + + This directly hashes the string to produce the key data. See below + for how this hashing is done. + + Octet 0: 0x00 + Octet 1: hash algorithm + + Simple S2K hashes the passphrase to produce the session key. The + manner in which this is done depends on the size of the session key + (which will depend on the cipher used) and the size of the hash + + + + + +Callas, et al Standards Track [Page 10] + +RFC 4880 OpenPGP Message Format November 2007 + + + algorithm's output. If the hash size is greater than the session key + size, the high-order (leftmost) octets of the hash are used as the + key. + + If the hash size is less than the key size, multiple instances of the + hash context are created -- enough to produce the required key data. + These instances are preloaded with 0, 1, 2, ... octets of zeros (that + is to say, the first instance has no preloading, the second gets + preloaded with 1 octet of zero, the third is preloaded with two + octets of zeros, and so forth). + + As the data is hashed, it is given independently to each hash + context. Since the contexts have been initialized differently, they + will each produce different hash output. Once the passphrase is + hashed, the output data from the multiple hashes is concatenated, + first hash leftmost, to produce the key data, with any excess octets + on the right discarded. + +3.7.1.2. Salted S2K + + This includes a "salt" value in the S2K specifier -- some arbitrary + data -- that gets hashed along with the passphrase string, to help + prevent dictionary attacks. + + Octet 0: 0x01 + Octet 1: hash algorithm + Octets 2-9: 8-octet salt value + + Salted S2K is exactly like Simple S2K, except that the input to the + hash function(s) consists of the 8 octets of salt from the S2K + specifier, followed by the passphrase. + +3.7.1.3. Iterated and Salted S2K + + This includes both a salt and an octet count. The salt is combined + with the passphrase and the resulting value is hashed repeatedly. + This further increases the amount of work an attacker must do to try + dictionary attacks. + + Octet 0: 0x03 + Octet 1: hash algorithm + Octets 2-9: 8-octet salt value + Octet 10: count, a one-octet, coded value + + + + + + + + +Callas, et al Standards Track [Page 11] + +RFC 4880 OpenPGP Message Format November 2007 + + + The count is coded into a one-octet number using the following + formula: + + #define EXPBIAS 6 + count = ((Int32)16 + (c & 15)) << ((c >> 4) + EXPBIAS); + + The above formula is in C, where "Int32" is a type for a 32-bit + integer, and the variable "c" is the coded count, Octet 10. + + Iterated-Salted S2K hashes the passphrase and salt data multiple + times. The total number of octets to be hashed is specified in the + encoded count in the S2K specifier. Note that the resulting count + value is an octet count of how many octets will be hashed, not an + iteration count. + + Initially, one or more hash contexts are set up as with the other S2K + algorithms, depending on how many octets of key data are needed. + Then the salt, followed by the passphrase data, is repeatedly hashed + until the number of octets specified by the octet count has been + hashed. The one exception is that if the octet count is less than + the size of the salt plus passphrase, the full salt plus passphrase + will be hashed even though that is greater than the octet count. + After the hashing is done, the data is unloaded from the hash + context(s) as with the other S2K algorithms. + +3.7.2. String-to-Key Usage + + Implementations SHOULD use salted or iterated-and-salted S2K + specifiers, as simple S2K specifiers are more vulnerable to + dictionary attacks. + +3.7.2.1. Secret-Key Encryption + + An S2K specifier can be stored in the secret keyring to specify how + to convert the passphrase to a key that unlocks the secret data. + Older versions of PGP just stored a cipher algorithm octet preceding + the secret data or a zero to indicate that the secret data was + unencrypted. The MD5 hash function was always used to convert the + passphrase to a key for the specified cipher algorithm. + + For compatibility, when an S2K specifier is used, the special value + 254 or 255 is stored in the position where the hash algorithm octet + would have been in the old data structure. This is then followed + immediately by a one-octet algorithm identifier, and then by the S2K + specifier as encoded above. + + + + + + +Callas, et al Standards Track [Page 12] + +RFC 4880 OpenPGP Message Format November 2007 + + + Therefore, preceding the secret data there will be one of these + possibilities: + + 0: secret data is unencrypted (no passphrase) + 255 or 254: followed by algorithm octet and S2K specifier + Cipher alg: use Simple S2K algorithm using MD5 hash + + This last possibility, the cipher algorithm number with an implicit + use of MD5 and IDEA, is provided for backward compatibility; it MAY + be understood, but SHOULD NOT be generated, and is deprecated. + + These are followed by an Initial Vector of the same length as the + block size of the cipher for the decryption of the secret values, if + they are encrypted, and then the secret-key values themselves. + +3.7.2.2. Symmetric-Key Message Encryption + + OpenPGP can create a Symmetric-key Encrypted Session Key (ESK) packet + at the front of a message. This is used to allow S2K specifiers to + be used for the passphrase conversion or to create messages with a + mix of symmetric-key ESKs and public-key ESKs. This allows a message + to be decrypted either with a passphrase or a public-key pair. + + PGP 2.X always used IDEA with Simple string-to-key conversion when + encrypting a message with a symmetric algorithm. This is deprecated, + but MAY be used for backward-compatibility. + +4. Packet Syntax + + This section describes the packets used by OpenPGP. + +4.1. Overview + + An OpenPGP message is constructed from a number of records that are + traditionally called packets. A packet is a chunk of data that has a + tag specifying its meaning. An OpenPGP message, keyring, + certificate, and so forth consists of a number of packets. Some of + those packets may contain other OpenPGP packets (for example, a + compressed data packet, when uncompressed, contains OpenPGP packets). + + Each packet consists of a packet header, followed by the packet body. + The packet header is of variable length. + +4.2. Packet Headers + + The first octet of the packet header is called the "Packet Tag". It + determines the format of the header and denotes the packet contents. + The remainder of the packet header is the length of the packet. + + + +Callas, et al Standards Track [Page 13] + +RFC 4880 OpenPGP Message Format November 2007 + + + Note that the most significant bit is the leftmost bit, called bit 7. + A mask for this bit is 0x80 in hexadecimal. + + +---------------+ + PTag |7 6 5 4 3 2 1 0| + +---------------+ + Bit 7 -- Always one + Bit 6 -- New packet format if set + + PGP 2.6.x only uses old format packets. Thus, software that + interoperates with those versions of PGP must only use old format + packets. If interoperability is not an issue, the new packet format + is RECOMMENDED. Note that old format packets have four bits of + packet tags, and new format packets have six; some features cannot be + used and still be backward-compatible. + + Also note that packets with a tag greater than or equal to 16 MUST + use new format packets. The old format packets can only express tags + less than or equal to 15. + + Old format packets contain: + + Bits 5-2 -- packet tag + Bits 1-0 -- length-type + + New format packets contain: + + Bits 5-0 -- packet tag + +4.2.1. Old Format Packet Lengths + + The meaning of the length-type in old format packets is: + + 0 - The packet has a one-octet length. The header is 2 octets long. + + 1 - The packet has a two-octet length. The header is 3 octets long. + + 2 - The packet has a four-octet length. The header is 5 octets long. + + 3 - The packet is of indeterminate length. The header is 1 octet + long, and the implementation must determine how long the packet + is. If the packet is in a file, this means that the packet + extends until the end of the file. In general, an implementation + SHOULD NOT use indeterminate-length packets except where the end + of the data will be clear from the context, and even then it is + better to use a definite length, or a new format header. The new + format headers described below have a mechanism for precisely + encoding data of indeterminate length. + + + +Callas, et al Standards Track [Page 14] + +RFC 4880 OpenPGP Message Format November 2007 + + +4.2.2. New Format Packet Lengths + + New format packets have four possible ways of encoding length: + + 1. A one-octet Body Length header encodes packet lengths of up to 191 + octets. + + 2. A two-octet Body Length header encodes packet lengths of 192 to + 8383 octets. + + 3. A five-octet Body Length header encodes packet lengths of up to + 4,294,967,295 (0xFFFFFFFF) octets in length. (This actually + encodes a four-octet scalar number.) + + 4. When the length of the packet body is not known in advance by the + issuer, Partial Body Length headers encode a packet of + indeterminate length, effectively making it a stream. + +4.2.2.1. One-Octet Lengths + + A one-octet Body Length header encodes a length of 0 to 191 octets. + This type of length header is recognized because the one octet value + is less than 192. The body length is equal to: + + bodyLen = 1st_octet; + +4.2.2.2. Two-Octet Lengths + + A two-octet Body Length header encodes a length of 192 to 8383 + octets. It is recognized because its first octet is in the range 192 + to 223. The body length is equal to: + + bodyLen = ((1st_octet - 192) << 8) + (2nd_octet) + 192 + +4.2.2.3. Five-Octet Lengths + + A five-octet Body Length header consists of a single octet holding + the value 255, followed by a four-octet scalar. The body length is + equal to: + + bodyLen = (2nd_octet << 24) | (3rd_octet << 16) | + (4th_octet << 8) | 5th_octet + + This basic set of one, two, and five-octet lengths is also used + internally to some packets. + + + + + + +Callas, et al Standards Track [Page 15] + +RFC 4880 OpenPGP Message Format November 2007 + + +4.2.2.4. Partial Body Lengths + + A Partial Body Length header is one octet long and encodes the length + of only part of the data packet. This length is a power of 2, from 1 + to 1,073,741,824 (2 to the 30th power). It is recognized by its one + octet value that is greater than or equal to 224, and less than 255. + The Partial Body Length is equal to: + + partialBodyLen = 1 << (1st_octet & 0x1F); + + Each Partial Body Length header is followed by a portion of the + packet body data. The Partial Body Length header specifies this + portion's length. Another length header (one octet, two-octet, + five-octet, or partial) follows that portion. The last length header + in the packet MUST NOT be a Partial Body Length header. Partial Body + Length headers may only be used for the non-final parts of the + packet. + + Note also that the last Body Length header can be a zero-length + header. + + An implementation MAY use Partial Body Lengths for data packets, be + they literal, compressed, or encrypted. The first partial length + MUST be at least 512 octets long. Partial Body Lengths MUST NOT be + used for any other packet types. + +4.2.3. Packet Length Examples + + These examples show ways that new format packets might encode the + packet lengths. + + A packet with length 100 may have its length encoded in one octet: + 0x64. This is followed by 100 octets of data. + + A packet with length 1723 may have its length encoded in two octets: + 0xC5, 0xFB. This header is followed by the 1723 octets of data. + + A packet with length 100000 may have its length encoded in five + octets: 0xFF, 0x00, 0x01, 0x86, 0xA0. + + It might also be encoded in the following octet stream: 0xEF, first + 32768 octets of data; 0xE1, next two octets of data; 0xE0, next one + octet of data; 0xF0, next 65536 octets of data; 0xC5, 0xDD, last 1693 + octets of data. This is just one possible encoding, and many + variations are possible on the size of the Partial Body Length + headers, as long as a regular Body Length header encodes the last + portion of the data. + + + + +Callas, et al Standards Track [Page 16] + +RFC 4880 OpenPGP Message Format November 2007 + + + Please note that in all of these explanations, the total length of + the packet is the length of the header(s) plus the length of the + body. + +4.3. Packet Tags + + The packet tag denotes what type of packet the body holds. Note that + old format headers can only have tags less than 16, whereas new + format headers can have tags as great as 63. The defined tags (in + decimal) are as follows: + + 0 -- Reserved - a packet tag MUST NOT have this value + 1 -- Public-Key Encrypted Session Key Packet + 2 -- Signature Packet + 3 -- Symmetric-Key Encrypted Session Key Packet + 4 -- One-Pass Signature Packet + 5 -- Secret-Key Packet + 6 -- Public-Key Packet + 7 -- Secret-Subkey Packet + 8 -- Compressed Data Packet + 9 -- Symmetrically Encrypted Data Packet + 10 -- Marker Packet + 11 -- Literal Data Packet + 12 -- Trust Packet + 13 -- User ID Packet + 14 -- Public-Subkey Packet + 17 -- User Attribute Packet + 18 -- Sym. Encrypted and Integrity Protected Data Packet + 19 -- Modification Detection Code Packet + 60 to 63 -- Private or Experimental Values + +5. Packet Types + +5.1. Public-Key Encrypted Session Key Packets (Tag 1) + + A Public-Key Encrypted Session Key packet holds the session key used + to encrypt a message. Zero or more Public-Key Encrypted Session Key + packets and/or Symmetric-Key Encrypted Session Key packets may + precede a Symmetrically Encrypted Data Packet, which holds an + encrypted message. The message is encrypted with the session key, + and the session key is itself encrypted and stored in the Encrypted + Session Key packet(s). The Symmetrically Encrypted Data Packet is + preceded by one Public-Key Encrypted Session Key packet for each + OpenPGP key to which the message is encrypted. The recipient of the + message finds a session key that is encrypted to their public key, + decrypts the session key, and then uses the session key to decrypt + the message. + + + + +Callas, et al Standards Track [Page 17] + +RFC 4880 OpenPGP Message Format November 2007 + + + The body of this packet consists of: + + - A one-octet number giving the version number of the packet type. + The currently defined value for packet version is 3. + + - An eight-octet number that gives the Key ID of the public key to + which the session key is encrypted. If the session key is + encrypted to a subkey, then the Key ID of this subkey is used + here instead of the Key ID of the primary key. + + - A one-octet number giving the public-key algorithm used. + + - A string of octets that is the encrypted session key. This + string takes up the remainder of the packet, and its contents are + dependent on the public-key algorithm used. + + Algorithm Specific Fields for RSA encryption + + - multiprecision integer (MPI) of RSA encrypted value m**e mod n. + + Algorithm Specific Fields for Elgamal encryption: + + - MPI of Elgamal (Diffie-Hellman) value g**k mod p. + + - MPI of Elgamal (Diffie-Hellman) value m * y**k mod p. + + The value "m" in the above formulas is derived from the session key + as follows. First, the session key is prefixed with a one-octet + algorithm identifier that specifies the symmetric encryption + algorithm used to encrypt the following Symmetrically Encrypted Data + Packet. Then a two-octet checksum is appended, which is equal to the + sum of the preceding session key octets, not including the algorithm + identifier, modulo 65536. This value is then encoded as described in + PKCS#1 block encoding EME-PKCS1-v1_5 in Section 7.2.1 of [RFC3447] to + form the "m" value used in the formulas above. See Section 13.1 of + this document for notes on OpenPGP's use of PKCS#1. + + Note that when an implementation forms several PKESKs with one + session key, forming a message that can be decrypted by several keys, + the implementation MUST make a new PKCS#1 encoding for each key. + + An implementation MAY accept or use a Key ID of zero as a "wild card" + or "speculative" Key ID. In this case, the receiving implementation + would try all available private keys, checking for a valid decrypted + session key. This format helps reduce traffic analysis of messages. + + + + + + +Callas, et al Standards Track [Page 18] + +RFC 4880 OpenPGP Message Format November 2007 + + +5.2. Signature Packet (Tag 2) + + A Signature packet describes a binding between some public key and + some data. The most common signatures are a signature of a file or a + block of text, and a signature that is a certification of a User ID. + + Two versions of Signature packets are defined. Version 3 provides + basic signature information, while version 4 provides an expandable + format with subpackets that can specify more information about the + signature. PGP 2.6.x only accepts version 3 signatures. + + Implementations SHOULD accept V3 signatures. Implementations SHOULD + generate V4 signatures. + + Note that if an implementation is creating an encrypted and signed + message that is encrypted to a V3 key, it is reasonable to create a + V3 signature. + +5.2.1. Signature Types + + There are a number of possible meanings for a signature, which are + indicated in a signature type octet in any given signature. Please + note that the vagueness of these meanings is not a flaw, but a + feature of the system. Because OpenPGP places final authority for + validity upon the receiver of a signature, it may be that one + signer's casual act might be more rigorous than some other + authority's positive act. See Section 5.2.4, "Computing Signatures", + for detailed information on how to compute and verify signatures of + each type. + + These meanings are as follows: + + 0x00: Signature of a binary document. + This means the signer owns it, created it, or certifies that it + has not been modified. + + 0x01: Signature of a canonical text document. + This means the signer owns it, created it, or certifies that it + has not been modified. The signature is calculated over the text + data with its line endings converted to . + + 0x02: Standalone signature. + This signature is a signature of only its own subpacket contents. + It is calculated identically to a signature over a zero-length + binary document. Note that it doesn't make sense to have a V3 + standalone signature. + + + + + +Callas, et al Standards Track [Page 19] + +RFC 4880 OpenPGP Message Format November 2007 + + + 0x10: Generic certification of a User ID and Public-Key packet. + The issuer of this certification does not make any particular + assertion as to how well the certifier has checked that the owner + of the key is in fact the person described by the User ID. + + 0x11: Persona certification of a User ID and Public-Key packet. + The issuer of this certification has not done any verification of + the claim that the owner of this key is the User ID specified. + + 0x12: Casual certification of a User ID and Public-Key packet. + The issuer of this certification has done some casual + verification of the claim of identity. + + 0x13: Positive certification of a User ID and Public-Key packet. + The issuer of this certification has done substantial + verification of the claim of identity. + + Most OpenPGP implementations make their "key signatures" as 0x10 + certifications. Some implementations can issue 0x11-0x13 + certifications, but few differentiate between the types. + + 0x18: Subkey Binding Signature + This signature is a statement by the top-level signing key that + indicates that it owns the subkey. This signature is calculated + directly on the primary key and subkey, and not on any User ID or + other packets. A signature that binds a signing subkey MUST have + an Embedded Signature subpacket in this binding signature that + contains a 0x19 signature made by the signing subkey on the + primary key and subkey. + + 0x19: Primary Key Binding Signature + This signature is a statement by a signing subkey, indicating + that it is owned by the primary key and subkey. This signature + is calculated the same way as a 0x18 signature: directly on the + primary key and subkey, and not on any User ID or other packets. + + 0x1F: Signature directly on a key + This signature is calculated directly on a key. It binds the + information in the Signature subpackets to the key, and is + appropriate to be used for subpackets that provide information + about the key, such as the Revocation Key subpacket. It is also + appropriate for statements that non-self certifiers want to make + about the key itself, rather than the binding between a key and a + name. + + + + + + + +Callas, et al Standards Track [Page 20] + +RFC 4880 OpenPGP Message Format November 2007 + + + 0x20: Key revocation signature + The signature is calculated directly on the key being revoked. A + revoked key is not to be used. Only revocation signatures by the + key being revoked, or by an authorized revocation key, should be + considered valid revocation signatures. + + 0x28: Subkey revocation signature + The signature is calculated directly on the subkey being revoked. + A revoked subkey is not to be used. Only revocation signatures + by the top-level signature key that is bound to this subkey, or + by an authorized revocation key, should be considered valid + revocation signatures. + + 0x30: Certification revocation signature + This signature revokes an earlier User ID certification signature + (signature class 0x10 through 0x13) or direct-key signature + (0x1F). It should be issued by the same key that issued the + revoked signature or an authorized revocation key. The signature + is computed over the same data as the certificate that it + revokes, and should have a later creation date than that + certificate. + + 0x40: Timestamp signature. + This signature is only meaningful for the timestamp contained in + it. + + 0x50: Third-Party Confirmation signature. + This signature is a signature over some other OpenPGP Signature + packet(s). It is analogous to a notary seal on the signed data. + A third-party signature SHOULD include Signature Target + subpacket(s) to give easy identification. Note that we really do + mean SHOULD. There are plausible uses for this (such as a blind + party that only sees the signature, not the key or source + document) that cannot include a target subpacket. + +5.2.2. Version 3 Signature Packet Format + + The body of a version 3 Signature Packet contains: + + - One-octet version number (3). + + - One-octet length of following hashed material. MUST be 5. + + - One-octet signature type. + + - Four-octet creation time. + + - Eight-octet Key ID of signer. + + + +Callas, et al Standards Track [Page 21] + +RFC 4880 OpenPGP Message Format November 2007 + + + - One-octet public-key algorithm. + + - One-octet hash algorithm. + + - Two-octet field holding left 16 bits of signed hash value. + + - One or more multiprecision integers comprising the signature. + This portion is algorithm specific, as described below. + + The concatenation of the data to be signed, the signature type, and + creation time from the Signature packet (5 additional octets) is + hashed. The resulting hash value is used in the signature algorithm. + The high 16 bits (first two octets) of the hash are included in the + Signature packet to provide a quick test to reject some invalid + signatures. + + Algorithm-Specific Fields for RSA signatures: + + - multiprecision integer (MPI) of RSA signature value m**d mod n. + + Algorithm-Specific Fields for DSA signatures: + + - MPI of DSA value r. + + - MPI of DSA value s. + + The signature calculation is based on a hash of the signed data, as + described above. The details of the calculation are different for + DSA signatures than for RSA signatures. + + With RSA signatures, the hash value is encoded using PKCS#1 encoding + type EMSA-PKCS1-v1_5 as described in Section 9.2 of RFC 3447. This + requires inserting the hash value as an octet string into an ASN.1 + structure. The object identifier for the type of hash being used is + included in the structure. The hexadecimal representations for the + currently defined hash algorithms are as follows: + + - MD5: 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05 + + - RIPEMD-160: 0x2B, 0x24, 0x03, 0x02, 0x01 + + - SHA-1: 0x2B, 0x0E, 0x03, 0x02, 0x1A + + - SHA224: 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04 + + - SHA256: 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 + + - SHA384: 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 + + + +Callas, et al Standards Track [Page 22] + +RFC 4880 OpenPGP Message Format November 2007 + + + - SHA512: 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 + + The ASN.1 Object Identifiers (OIDs) are as follows: + + - MD5: 1.2.840.113549.2.5 + + - RIPEMD-160: 1.3.36.3.2.1 + + - SHA-1: 1.3.14.3.2.26 + + - SHA224: 2.16.840.1.101.3.4.2.4 + + - SHA256: 2.16.840.1.101.3.4.2.1 + + - SHA384: 2.16.840.1.101.3.4.2.2 + + - SHA512: 2.16.840.1.101.3.4.2.3 + + The full hash prefixes for these are as follows: + + MD5: 0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, + 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00, + 0x04, 0x10 + + RIPEMD-160: 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x24, + 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 + + SHA-1: 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0E, + 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14 + + SHA224: 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, + 0x00, 0x04, 0x1C + + SHA256: 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, + 0x00, 0x04, 0x20 + + SHA384: 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, + 0x00, 0x04, 0x30 + + SHA512: 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, + 0x00, 0x04, 0x40 + + DSA signatures MUST use hashes that are equal in size to the number + of bits of q, the group generated by the DSA key's generator value. + + + +Callas, et al Standards Track [Page 23] + +RFC 4880 OpenPGP Message Format November 2007 + + + If the output size of the chosen hash is larger than the number of + bits of q, the hash result is truncated to fit by taking the number + of leftmost bits equal to the number of bits of q. This (possibly + truncated) hash function result is treated as a number and used + directly in the DSA signature algorithm. + +5.2.3. Version 4 Signature Packet Format + + The body of a version 4 Signature packet contains: + + - One-octet version number (4). + + - One-octet signature type. + + - One-octet public-key algorithm. + + - One-octet hash algorithm. + + - Two-octet scalar octet count for following hashed subpacket data. + Note that this is the length in octets of all of the hashed + subpackets; a pointer incremented by this number will skip over + the hashed subpackets. + + - Hashed subpacket data set (zero or more subpackets). + + - Two-octet scalar octet count for the following unhashed subpacket + data. Note that this is the length in octets of all of the + unhashed subpackets; a pointer incremented by this number will + skip over the unhashed subpackets. + + - Unhashed subpacket data set (zero or more subpackets). + + - Two-octet field holding the left 16 bits of the signed hash + value. + + - One or more multiprecision integers comprising the signature. + This portion is algorithm specific, as described above. + + The concatenation of the data being signed and the signature data + from the version number through the hashed subpacket data (inclusive) + is hashed. The resulting hash value is what is signed. The left 16 + bits of the hash are included in the Signature packet to provide a + quick test to reject some invalid signatures. + + There are two fields consisting of Signature subpackets. The first + field is hashed with the rest of the signature data, while the second + is unhashed. The second set of subpackets is not cryptographically + + + + +Callas, et al Standards Track [Page 24] + +RFC 4880 OpenPGP Message Format November 2007 + + + protected by the signature and should include only advisory + information. + + The algorithms for converting the hash function result to a signature + are described in a section below. + +5.2.3.1. Signature Subpacket Specification + + A subpacket data set consists of zero or more Signature subpackets. + In Signature packets, the subpacket data set is preceded by a two- + octet scalar count of the length in octets of all the subpackets. A + pointer incremented by this number will skip over the subpacket data + set. + + Each subpacket consists of a subpacket header and a body. The header + consists of: + + - the subpacket length (1, 2, or 5 octets), + + - the subpacket type (1 octet), + + and is followed by the subpacket-specific data. + + The length includes the type octet but not this length. Its format + is similar to the "new" format packet header lengths, but cannot have + Partial Body Lengths. That is: + + if the 1st octet < 192, then + lengthOfLength = 1 + subpacketLen = 1st_octet + + if the 1st octet >= 192 and < 255, then + lengthOfLength = 2 + subpacketLen = ((1st_octet - 192) << 8) + (2nd_octet) + 192 + + if the 1st octet = 255, then + lengthOfLength = 5 + subpacket length = [four-octet scalar starting at 2nd_octet] + + The value of the subpacket type octet may be: + + 0 = Reserved + 1 = Reserved + 2 = Signature Creation Time + 3 = Signature Expiration Time + 4 = Exportable Certification + 5 = Trust Signature + 6 = Regular Expression + + + +Callas, et al Standards Track [Page 25] + +RFC 4880 OpenPGP Message Format November 2007 + + + 7 = Revocable + 8 = Reserved + 9 = Key Expiration Time + 10 = Placeholder for backward compatibility + 11 = Preferred Symmetric Algorithms + 12 = Revocation Key + 13 = Reserved + 14 = Reserved + 15 = Reserved + 16 = Issuer + 17 = Reserved + 18 = Reserved + 19 = Reserved + 20 = Notation Data + 21 = Preferred Hash Algorithms + 22 = Preferred Compression Algorithms + 23 = Key Server Preferences + 24 = Preferred Key Server + 25 = Primary User ID + 26 = Policy URI + 27 = Key Flags + 28 = Signer's User ID + 29 = Reason for Revocation + 30 = Features + 31 = Signature Target + 32 = Embedded Signature + 100 To 110 = Private or experimental + + An implementation SHOULD ignore any subpacket of a type that it does + not recognize. + + Bit 7 of the subpacket type is the "critical" bit. If set, it + denotes that the subpacket is one that is critical for the evaluator + of the signature to recognize. If a subpacket is encountered that is + marked critical but is unknown to the evaluating software, the + evaluator SHOULD consider the signature to be in error. + + An evaluator may "recognize" a subpacket, but not implement it. The + purpose of the critical bit is to allow the signer to tell an + evaluator that it would prefer a new, unknown feature to generate an + error than be ignored. + + Implementations SHOULD implement the three preferred algorithm + subpackets (11, 21, and 22), as well as the "Reason for Revocation" + subpacket. Note, however, that if an implementation chooses not to + implement some of the preferences, it is required to behave in a + polite manner to respect the wishes of those users who do implement + these preferences. + + + +Callas, et al Standards Track [Page 26] + +RFC 4880 OpenPGP Message Format November 2007 + + +5.2.3.2. Signature Subpacket Types + + A number of subpackets are currently defined. Some subpackets apply + to the signature itself and some are attributes of the key. + Subpackets that are found on a self-signature are placed on a + certification made by the key itself. Note that a key may have more + than one User ID, and thus may have more than one self-signature, and + differing subpackets. + + A subpacket may be found either in the hashed or unhashed subpacket + sections of a signature. If a subpacket is not hashed, then the + information in it cannot be considered definitive because it is not + part of the signature proper. + +5.2.3.3. Notes on Self-Signatures + + A self-signature is a binding signature made by the key to which the + signature refers. There are three types of self-signatures, the + certification signatures (types 0x10-0x13), the direct-key signature + (type 0x1F), and the subkey binding signature (type 0x18). For + certification self-signatures, each User ID may have a self- + signature, and thus different subpackets in those self-signatures. + For subkey binding signatures, each subkey in fact has a self- + signature. Subpackets that appear in a certification self-signature + apply to the user name, and subpackets that appear in the subkey + self-signature apply to the subkey. Lastly, subpackets on the + direct-key signature apply to the entire key. + + Implementing software should interpret a self-signature's preference + subpackets as narrowly as possible. For example, suppose a key has + two user names, Alice and Bob. Suppose that Alice prefers the + symmetric algorithm CAST5, and Bob prefers IDEA or TripleDES. If the + software locates this key via Alice's name, then the preferred + algorithm is CAST5; if software locates the key via Bob's name, then + the preferred algorithm is IDEA. If the key is located by Key ID, + the algorithm of the primary User ID of the key provides the + preferred symmetric algorithm. + + Revoking a self-signature or allowing it to expire has a semantic + meaning that varies with the signature type. Revoking the self- + signature on a User ID effectively retires that user name. The + self-signature is a statement, "My name X is tied to my signing key + K" and is corroborated by other users' certifications. If another + user revokes their certification, they are effectively saying that + they no longer believe that name and that key are tied together. + Similarly, if the users themselves revoke their self-signature, then + the users no longer go by that name, no longer have that email + address, etc. Revoking a binding signature effectively retires that + + + +Callas, et al Standards Track [Page 27] + +RFC 4880 OpenPGP Message Format November 2007 + + + subkey. Revoking a direct-key signature cancels that signature. + Please see the "Reason for Revocation" subpacket (Section 5.2.3.23) + for more relevant detail. + + Since a self-signature contains important information about the key's + use, an implementation SHOULD allow the user to rewrite the self- + signature, and important information in it, such as preferences and + key expiration. + + It is good practice to verify that a self-signature imported into an + implementation doesn't advertise features that the implementation + doesn't support, rewriting the signature as appropriate. + + An implementation that encounters multiple self-signatures on the + same object may resolve the ambiguity in any way it sees fit, but it + is RECOMMENDED that priority be given to the most recent self- + signature. + +5.2.3.4. Signature Creation Time + + (4-octet time field) + + The time the signature was made. + + MUST be present in the hashed area. + +5.2.3.5. Issuer + + (8-octet Key ID) + + The OpenPGP Key ID of the key issuing the signature. + +5.2.3.6. Key Expiration Time + + (4-octet time field) + + The validity period of the key. This is the number of seconds after + the key creation time that the key expires. If this is not present + or has a value of zero, the key never expires. This is found only on + a self-signature. + +5.2.3.7. Preferred Symmetric Algorithms + + (array of one-octet values) + + Symmetric algorithm numbers that indicate which algorithms the key + holder prefers to use. The subpacket body is an ordered list of + octets with the most preferred listed first. It is assumed that only + + + +Callas, et al Standards Track [Page 28] + +RFC 4880 OpenPGP Message Format November 2007 + + + algorithms listed are supported by the recipient's software. + Algorithm numbers are in Section 9. This is only found on a self- + signature. + +5.2.3.8. Preferred Hash Algorithms + + (array of one-octet values) + + Message digest algorithm numbers that indicate which algorithms the + key holder prefers to receive. Like the preferred symmetric + algorithms, the list is ordered. Algorithm numbers are in Section 9. + This is only found on a self-signature. + +5.2.3.9. Preferred Compression Algorithms + + (array of one-octet values) + + Compression algorithm numbers that indicate which algorithms the key + holder prefers to use. Like the preferred symmetric algorithms, the + list is ordered. Algorithm numbers are in Section 9. If this + subpacket is not included, ZIP is preferred. A zero denotes that + uncompressed data is preferred; the key holder's software might have + no compression software in that implementation. This is only found + on a self-signature. + +5.2.3.10. Signature Expiration Time + + (4-octet time field) + + The validity period of the signature. This is the number of seconds + after the signature creation time that the signature expires. If + this is not present or has a value of zero, it never expires. + +5.2.3.11. Exportable Certification + + (1 octet of exportability, 0 for not, 1 for exportable) + + This subpacket denotes whether a certification signature is + "exportable", to be used by other users than the signature's issuer. + The packet body contains a Boolean flag indicating whether the + signature is exportable. If this packet is not present, the + certification is exportable; it is equivalent to a flag containing a + 1. + + Non-exportable, or "local", certifications are signatures made by a + user to mark a key as valid within that user's implementation only. + + + + + +Callas, et al Standards Track [Page 29] + +RFC 4880 OpenPGP Message Format November 2007 + + + Thus, when an implementation prepares a user's copy of a key for + transport to another user (this is the process of "exporting" the + key), any local certification signatures are deleted from the key. + + The receiver of a transported key "imports" it, and likewise trims + any local certifications. In normal operation, there won't be any, + assuming the import is performed on an exported key. However, there + are instances where this can reasonably happen. For example, if an + implementation allows keys to be imported from a key database in + addition to an exported key, then this situation can arise. + + Some implementations do not represent the interest of a single user + (for example, a key server). Such implementations always trim local + certifications from any key they handle. + +5.2.3.12. Revocable + + (1 octet of revocability, 0 for not, 1 for revocable) + + Signature's revocability status. The packet body contains a Boolean + flag indicating whether the signature is revocable. Signatures that + are not revocable have any later revocation signatures ignored. They + represent a commitment by the signer that he cannot revoke his + signature for the life of his key. If this packet is not present, + the signature is revocable. + +5.2.3.13. Trust Signature + + (1 octet "level" (depth), 1 octet of trust amount) + + Signer asserts that the key is not only valid but also trustworthy at + the specified level. Level 0 has the same meaning as an ordinary + validity signature. Level 1 means that the signed key is asserted to + be a valid trusted introducer, with the 2nd octet of the body + specifying the degree of trust. Level 2 means that the signed key is + asserted to be trusted to issue level 1 trust signatures, i.e., that + it is a "meta introducer". Generally, a level n trust signature + asserts that a key is trusted to issue level n-1 trust signatures. + The trust amount is in a range from 0-255, interpreted such that + values less than 120 indicate partial trust and values of 120 or + greater indicate complete trust. Implementations SHOULD emit values + of 60 for partial trust and 120 for complete trust. + + + + + + + + + +Callas, et al Standards Track [Page 30] + +RFC 4880 OpenPGP Message Format November 2007 + + +5.2.3.14. Regular Expression + + (null-terminated regular expression) + + Used in conjunction with trust Signature packets (of level > 0) to + limit the scope of trust that is extended. Only signatures by the + target key on User IDs that match the regular expression in the body + of this packet have trust extended by the trust Signature subpacket. + The regular expression uses the same syntax as the Henry Spencer's + "almost public domain" regular expression [REGEX] package. A + description of the syntax is found in Section 8 below. + +5.2.3.15. Revocation Key + + (1 octet of class, 1 octet of public-key algorithm ID, 20 octets of + fingerprint) + + Authorizes the specified key to issue revocation signatures for this + key. Class octet must have bit 0x80 set. If the bit 0x40 is set, + then this means that the revocation information is sensitive. Other + bits are for future expansion to other kinds of authorizations. This + is found on a self-signature. + + If the "sensitive" flag is set, the keyholder feels this subpacket + contains private trust information that describes a real-world + sensitive relationship. If this flag is set, implementations SHOULD + NOT export this signature to other users except in cases where the + data needs to be available: when the signature is being sent to the + designated revoker, or when it is accompanied by a revocation + signature from that revoker. Note that it may be appropriate to + isolate this subpacket within a separate signature so that it is not + combined with other subpackets that need to be exported. + +5.2.3.16. Notation Data + + (4 octets of flags, 2 octets of name length (M), + 2 octets of value length (N), + M octets of name data, + N octets of value data) + + This subpacket describes a "notation" on the signature that the + issuer wishes to make. The notation has a name and a value, each of + which are strings of octets. There may be more than one notation in + a signature. Notations can be used for any extension the issuer of + the signature cares to make. The "flags" field holds four octets of + flags. + + + + + +Callas, et al Standards Track [Page 31] + +RFC 4880 OpenPGP Message Format November 2007 + + + All undefined flags MUST be zero. Defined flags are as follows: + + First octet: 0x80 = human-readable. This note value is text. + Other octets: none. + + Notation names are arbitrary strings encoded in UTF-8. They reside + in two namespaces: The IETF namespace and the user namespace. + + The IETF namespace is registered with IANA. These names MUST NOT + contain the "@" character (0x40). This is a tag for the user + namespace. + + Names in the user namespace consist of a UTF-8 string tag followed by + "@" followed by a DNS domain name. Note that the tag MUST NOT + contain an "@" character. For example, the "sample" tag used by + Example Corporation could be "sample@example.com". + + Names in a user space are owned and controlled by the owners of that + domain. Obviously, it's bad form to create a new name in a DNS space + that you don't own. + + Since the user namespace is in the form of an email address, + implementers MAY wish to arrange for that address to reach a person + who can be consulted about the use of the named tag. Note that due + to UTF-8 encoding, not all valid user space name tags are valid email + addresses. + + If there is a critical notation, the criticality applies to that + specific notation and not to notations in general. + +5.2.3.17. Key Server Preferences + + (N octets of flags) + + This is a list of one-bit flags that indicate preferences that the + key holder has about how the key is handled on a key server. All + undefined flags MUST be zero. + + First octet: 0x80 = No-modify + the key holder requests that this key only be modified or updated + by the key holder or an administrator of the key server. + + This is found only on a self-signature. + + + + + + + + +Callas, et al Standards Track [Page 32] + +RFC 4880 OpenPGP Message Format November 2007 + + +5.2.3.18. Preferred Key Server + + (String) + + This is a URI of a key server that the key holder prefers be used for + updates. Note that keys with multiple User IDs can have a preferred + key server for each User ID. Note also that since this is a URI, the + key server can actually be a copy of the key retrieved by ftp, http, + finger, etc. + +5.2.3.19. Primary User ID + + (1 octet, Boolean) + + This is a flag in a User ID's self-signature that states whether this + User ID is the main User ID for this key. It is reasonable for an + implementation to resolve ambiguities in preferences, etc. by + referring to the primary User ID. If this flag is absent, its value + is zero. If more than one User ID in a key is marked as primary, the + implementation may resolve the ambiguity in any way it sees fit, but + it is RECOMMENDED that priority be given to the User ID with the most + recent self-signature. + + When appearing on a self-signature on a User ID packet, this + subpacket applies only to User ID packets. When appearing on a + self-signature on a User Attribute packet, this subpacket applies + only to User Attribute packets. That is to say, there are two + different and independent "primaries" -- one for User IDs, and one + for User Attributes. + +5.2.3.20. Policy URI + + (String) + + This subpacket contains a URI of a document that describes the policy + under which the signature was issued. + +5.2.3.21. Key Flags + + (N octets of flags) + + This subpacket contains a list of binary flags that hold information + about a key. It is a string of octets, and an implementation MUST + NOT assume a fixed size. This is so it can grow over time. If a + list is shorter than an implementation expects, the unstated flags + are considered to be zero. The defined flags are as follows: + + + + + +Callas, et al Standards Track [Page 33] + +RFC 4880 OpenPGP Message Format November 2007 + + + First octet: + + 0x01 - This key may be used to certify other keys. + + 0x02 - This key may be used to sign data. + + 0x04 - This key may be used to encrypt communications. + + 0x08 - This key may be used to encrypt storage. + + 0x10 - The private component of this key may have been split + by a secret-sharing mechanism. + + 0x20 - This key may be used for authentication. + + 0x80 - The private component of this key may be in the + possession of more than one person. + + Usage notes: + + The flags in this packet may appear in self-signatures or in + certification signatures. They mean different things depending on + who is making the statement -- for example, a certification signature + that has the "sign data" flag is stating that the certification is + for that use. On the other hand, the "communications encryption" + flag in a self-signature is stating a preference that a given key be + used for communications. Note however, that it is a thorny issue to + determine what is "communications" and what is "storage". This + decision is left wholly up to the implementation; the authors of this + document do not claim any special wisdom on the issue and realize + that accepted opinion may change. + + The "split key" (0x10) and "group key" (0x80) flags are placed on a + self-signature only; they are meaningless on a certification + signature. They SHOULD be placed only on a direct-key signature + (type 0x1F) or a subkey signature (type 0x18), one that refers to the + key the flag applies to. + +5.2.3.22. Signer's User ID + + (String) + + This subpacket allows a keyholder to state which User ID is + responsible for the signing. Many keyholders use a single key for + different purposes, such as business communications as well as + personal communications. This subpacket allows such a keyholder to + state which of their roles is making a signature. + + + + +Callas, et al Standards Track [Page 34] + +RFC 4880 OpenPGP Message Format November 2007 + + + This subpacket is not appropriate to use to refer to a User Attribute + packet. + +5.2.3.23. Reason for Revocation + + (1 octet of revocation code, N octets of reason string) + + This subpacket is used only in key revocation and certification + revocation signatures. It describes the reason why the key or + certificate was revoked. + + The first octet contains a machine-readable code that denotes the + reason for the revocation: + + 0 - No reason specified (key revocations or cert revocations) + 1 - Key is superseded (key revocations) + 2 - Key material has been compromised (key revocations) + 3 - Key is retired and no longer used (key revocations) + 32 - User ID information is no longer valid (cert revocations) + 100-110 - Private Use + + Following the revocation code is a string of octets that gives + information about the Reason for Revocation in human-readable form + (UTF-8). The string may be null, that is, of zero length. The + length of the subpacket is the length of the reason string plus one. + An implementation SHOULD implement this subpacket, include it in all + revocation signatures, and interpret revocations appropriately. + There are important semantic differences between the reasons, and + there are thus important reasons for revoking signatures. + + If a key has been revoked because of a compromise, all signatures + created by that key are suspect. However, if it was merely + superseded or retired, old signatures are still valid. If the + revoked signature is the self-signature for certifying a User ID, a + revocation denotes that that user name is no longer in use. Such a + revocation SHOULD include a 0x20 code. + + Note that any signature may be revoked, including a certification on + some other person's key. There are many good reasons for revoking a + certification signature, such as the case where the keyholder leaves + the employ of a business with an email address. A revoked + certification is no longer a part of validity calculations. + + + + + + + + + +Callas, et al Standards Track [Page 35] + +RFC 4880 OpenPGP Message Format November 2007 + + +5.2.3.24. Features + + (N octets of flags) + + The Features subpacket denotes which advanced OpenPGP features a + user's implementation supports. This is so that as features are + added to OpenPGP that cannot be backwards-compatible, a user can + state that they can use that feature. The flags are single bits that + indicate that a given feature is supported. + + This subpacket is similar to a preferences subpacket, and only + appears in a self-signature. + + An implementation SHOULD NOT use a feature listed when sending to a + user who does not state that they can use it. + + Defined features are as follows: + + First octet: + + 0x01 - Modification Detection (packets 18 and 19) + + If an implementation implements any of the defined features, it + SHOULD implement the Features subpacket, too. + + An implementation may freely infer features from other suitable + implementation-dependent mechanisms. + +5.2.3.25. Signature Target + + (1 octet public-key algorithm, 1 octet hash algorithm, N octets hash) + + This subpacket identifies a specific target signature to which a + signature refers. For revocation signatures, this subpacket + provides explicit designation of which signature is being revoked. + For a third-party or timestamp signature, this designates what + signature is signed. All arguments are an identifier of that target + signature. + + The N octets of hash data MUST be the size of the hash of the + signature. For example, a target signature with a SHA-1 hash MUST + have 20 octets of hash data. + + + + + + + + + +Callas, et al Standards Track [Page 36] + +RFC 4880 OpenPGP Message Format November 2007 + + +5.2.3.26. Embedded Signature + + (1 signature packet body) + + This subpacket contains a complete Signature packet body as + specified in Section 5.2 above. It is useful when one signature + needs to refer to, or be incorporated in, another signature. + +5.2.4. Computing Signatures + + All signatures are formed by producing a hash over the signature + data, and then using the resulting hash in the signature algorithm. + + For binary document signatures (type 0x00), the document data is + hashed directly. For text document signatures (type 0x01), the + document is canonicalized by converting line endings to , + and the resulting data is hashed. + + When a signature is made over a key, the hash data starts with the + octet 0x99, followed by a two-octet length of the key, and then body + of the key packet. (Note that this is an old-style packet header for + a key packet with two-octet length.) A subkey binding signature + (type 0x18) or primary key binding signature (type 0x19) then hashes + the subkey using the same format as the main key (also using 0x99 as + the first octet). Key revocation signatures (types 0x20 and 0x28) + hash only the key being revoked. + + A certification signature (type 0x10 through 0x13) hashes the User + ID being bound to the key into the hash context after the above + data. A V3 certification hashes the contents of the User ID or + attribute packet packet, without any header. A V4 certification + hashes the constant 0xB4 for User ID certifications or the constant + 0xD1 for User Attribute certifications, followed by a four-octet + number giving the length of the User ID or User Attribute data, and + then the User ID or User Attribute data. + + When a signature is made over a Signature packet (type 0x50), the + hash data starts with the octet 0x88, followed by the four-octet + length of the signature, and then the body of the Signature packet. + (Note that this is an old-style packet header for a Signature packet + with the length-of-length set to zero.) The unhashed subpacket data + of the Signature packet being hashed is not included in the hash, and + the unhashed subpacket data length value is set to zero. + + Once the data body is hashed, then a trailer is hashed. A V3 + signature hashes five octets of the packet body, starting from the + signature type field. This data is the signature type, followed by + the four-octet signature time. A V4 signature hashes the packet body + + + +Callas, et al Standards Track [Page 37] + +RFC 4880 OpenPGP Message Format November 2007 + + + starting from its first field, the version number, through the end + of the hashed subpacket data. Thus, the fields hashed are the + signature version, the signature type, the public-key algorithm, the + hash algorithm, the hashed subpacket length, and the hashed + subpacket body. + + V4 signatures also hash in a final trailer of six octets: the + version of the Signature packet, i.e., 0x04; 0xFF; and a four-octet, + big-endian number that is the length of the hashed data from the + Signature packet (note that this number does not include these final + six octets). + + After all this has been hashed in a single hash context, the + resulting hash field is used in the signature algorithm and placed + at the end of the Signature packet. + +5.2.4.1. Subpacket Hints + + It is certainly possible for a signature to contain conflicting + information in subpackets. For example, a signature may contain + multiple copies of a preference or multiple expiration times. In + most cases, an implementation SHOULD use the last subpacket in the + signature, but MAY use any conflict resolution scheme that makes + more sense. Please note that we are intentionally leaving conflict + resolution to the implementer; most conflicts are simply syntax + errors, and the wishy-washy language here allows a receiver to be + generous in what they accept, while putting pressure on a creator to + be stingy in what they generate. + + Some apparent conflicts may actually make sense -- for example, + suppose a keyholder has a V3 key and a V4 key that share the same + RSA key material. Either of these keys can verify a signature + created by the other, and it may be reasonable for a signature to + contain an issuer subpacket for each key, as a way of explicitly + tying those keys to the signature. + +5.3. Symmetric-Key Encrypted Session Key Packets (Tag 3) + + The Symmetric-Key Encrypted Session Key packet holds the + symmetric-key encryption of a session key used to encrypt a message. + Zero or more Public-Key Encrypted Session Key packets and/or + Symmetric-Key Encrypted Session Key packets may precede a + Symmetrically Encrypted Data packet that holds an encrypted message. + The message is encrypted with a session key, and the session key is + itself encrypted and stored in the Encrypted Session Key packet or + the Symmetric-Key Encrypted Session Key packet. + + + + + +Callas, et al Standards Track [Page 38] + +RFC 4880 OpenPGP Message Format November 2007 + + + If the Symmetrically Encrypted Data packet is preceded by one or + more Symmetric-Key Encrypted Session Key packets, each specifies a + passphrase that may be used to decrypt the message. This allows a + message to be encrypted to a number of public keys, and also to one + or more passphrases. This packet type is new and is not generated + by PGP 2.x or PGP 5.0. + + The body of this packet consists of: + + - A one-octet version number. The only currently defined version + is 4. + + - A one-octet number describing the symmetric algorithm used. + + - A string-to-key (S2K) specifier, length as defined above. + + - Optionally, the encrypted session key itself, which is decrypted + with the string-to-key object. + + If the encrypted session key is not present (which can be detected + on the basis of packet length and S2K specifier size), then the S2K + algorithm applied to the passphrase produces the session key for + decrypting the file, using the symmetric cipher algorithm from the + Symmetric-Key Encrypted Session Key packet. + + If the encrypted session key is present, the result of applying the + S2K algorithm to the passphrase is used to decrypt just that + encrypted session key field, using CFB mode with an IV of all zeros. + The decryption result consists of a one-octet algorithm identifier + that specifies the symmetric-key encryption algorithm used to + encrypt the following Symmetrically Encrypted Data packet, followed + by the session key octets themselves. + + Note: because an all-zero IV is used for this decryption, the S2K + specifier MUST use a salt value, either a Salted S2K or an + Iterated-Salted S2K. The salt value will ensure that the decryption + key is not repeated even if the passphrase is reused. + +5.4. One-Pass Signature Packets (Tag 4) + + The One-Pass Signature packet precedes the signed data and contains + enough information to allow the receiver to begin calculating any + hashes needed to verify the signature. It allows the Signature + packet to be placed at the end of the message, so that the signer + can compute the entire signed message in one pass. + + A One-Pass Signature does not interoperate with PGP 2.6.x or + earlier. + + + +Callas, et al Standards Track [Page 39] + +RFC 4880 OpenPGP Message Format November 2007 + + + The body of this packet consists of: + + - A one-octet version number. The current version is 3. + + - A one-octet signature type. Signature types are described in + Section 5.2.1. + + - A one-octet number describing the hash algorithm used. + + - A one-octet number describing the public-key algorithm used. + + - An eight-octet number holding the Key ID of the signing key. + + - A one-octet number holding a flag showing whether the signature + is nested. A zero value indicates that the next packet is + another One-Pass Signature packet that describes another + signature to be applied to the same message data. + + Note that if a message contains more than one one-pass signature, + then the Signature packets bracket the message; that is, the first + Signature packet after the message corresponds to the last one-pass + packet and the final Signature packet corresponds to the first + one-pass packet. + +5.5. Key Material Packet + + A key material packet contains all the information about a public or + private key. There are four variants of this packet type, and two + major versions. Consequently, this section is complex. + +5.5.1. Key Packet Variants + +5.5.1.1. Public-Key Packet (Tag 6) + + A Public-Key packet starts a series of packets that forms an OpenPGP + key (sometimes called an OpenPGP certificate). + +5.5.1.2. Public-Subkey Packet (Tag 14) + + A Public-Subkey packet (tag 14) has exactly the same format as a + Public-Key packet, but denotes a subkey. One or more subkeys may be + associated with a top-level key. By convention, the top-level key + provides signature services, and the subkeys provide encryption + services. + + Note: in PGP 2.6.x, tag 14 was intended to indicate a comment + packet. This tag was selected for reuse because no previous version + of PGP ever emitted comment packets but they did properly ignore + + + +Callas, et al Standards Track [Page 40] + +RFC 4880 OpenPGP Message Format November 2007 + + + them. Public-Subkey packets are ignored by PGP 2.6.x and do not + cause it to fail, providing a limited degree of backward + compatibility. + +5.5.1.3. Secret-Key Packet (Tag 5) + + A Secret-Key packet contains all the information that is found in a + Public-Key packet, including the public-key material, but also + includes the secret-key material after all the public-key fields. + +5.5.1.4. Secret-Subkey Packet (Tag 7) + + A Secret-Subkey packet (tag 7) is the subkey analog of the Secret + Key packet and has exactly the same format. + +5.5.2. Public-Key Packet Formats + + There are two versions of key-material packets. Version 3 packets + were first generated by PGP 2.6. Version 4 keys first appeared in + PGP 5.0 and are the preferred key version for OpenPGP. + + OpenPGP implementations MUST create keys with version 4 format. V3 + keys are deprecated; an implementation MUST NOT generate a V3 key, + but MAY accept it. + + A version 3 public key or public-subkey packet contains: + + - A one-octet version number (3). + + - A four-octet number denoting the time that the key was created. + + - A two-octet number denoting the time in days that this key is + valid. If this number is zero, then it does not expire. + + - A one-octet number denoting the public-key algorithm of this key. + + - A series of multiprecision integers comprising the key material: + + - a multiprecision integer (MPI) of RSA public modulus n; + + - an MPI of RSA public encryption exponent e. + + V3 keys are deprecated. They contain three weaknesses. First, it is + relatively easy to construct a V3 key that has the same Key ID as any + other key because the Key ID is simply the low 64 bits of the public + modulus. Secondly, because the fingerprint of a V3 key hashes the + key material, but not its length, there is an increased opportunity + for fingerprint collisions. Third, there are weaknesses in the MD5 + + + +Callas, et al Standards Track [Page 41] + +RFC 4880 OpenPGP Message Format November 2007 + + + hash algorithm that make developers prefer other algorithms. See + below for a fuller discussion of Key IDs and fingerprints. + + V2 keys are identical to the deprecated V3 keys except for the + version number. An implementation MUST NOT generate them and MAY + accept or reject them as it sees fit. + + The version 4 format is similar to the version 3 format except for + the absence of a validity period. This has been moved to the + Signature packet. In addition, fingerprints of version 4 keys are + calculated differently from version 3 keys, as described in the + section "Enhanced Key Formats". + + A version 4 packet contains: + + - A one-octet version number (4). + + - A four-octet number denoting the time that the key was created. + + - A one-octet number denoting the public-key algorithm of this key. + + - A series of multiprecision integers comprising the key material. + This algorithm-specific portion is: + + Algorithm-Specific Fields for RSA public keys: + + - multiprecision integer (MPI) of RSA public modulus n; + + - MPI of RSA public encryption exponent e. + + Algorithm-Specific Fields for DSA public keys: + + - MPI of DSA prime p; + + - MPI of DSA group order q (q is a prime divisor of p-1); + + - MPI of DSA group generator g; + + - MPI of DSA public-key value y (= g**x mod p where x + is secret). + + Algorithm-Specific Fields for Elgamal public keys: + + - MPI of Elgamal prime p; + + - MPI of Elgamal group generator g; + + + + + +Callas, et al Standards Track [Page 42] + +RFC 4880 OpenPGP Message Format November 2007 + + + - MPI of Elgamal public key value y (= g**x mod p where x + is secret). + +5.5.3. Secret-Key Packet Formats + + The Secret-Key and Secret-Subkey packets contain all the data of the + Public-Key and Public-Subkey packets, with additional algorithm- + specific secret-key data appended, usually in encrypted form. + + The packet contains: + + - A Public-Key or Public-Subkey packet, as described above. + + - One octet indicating string-to-key usage conventions. Zero + indicates that the secret-key data is not encrypted. 255 or 254 + indicates that a string-to-key specifier is being given. Any + other value is a symmetric-key encryption algorithm identifier. + + - [Optional] If string-to-key usage octet was 255 or 254, a one- + octet symmetric encryption algorithm. + + - [Optional] If string-to-key usage octet was 255 or 254, a + string-to-key specifier. The length of the string-to-key + specifier is implied by its type, as described above. + + - [Optional] If secret data is encrypted (string-to-key usage octet + not zero), an Initial Vector (IV) of the same length as the + cipher's block size. + + - Plain or encrypted multiprecision integers comprising the secret + key data. These algorithm-specific fields are as described + below. + + - If the string-to-key usage octet is zero or 255, then a two-octet + checksum of the plaintext of the algorithm-specific portion (sum + of all octets, mod 65536). If the string-to-key usage octet was + 254, then a 20-octet SHA-1 hash of the plaintext of the + algorithm-specific portion. This checksum or hash is encrypted + together with the algorithm-specific fields (if string-to-key + usage octet is not zero). Note that for all other values, a + two-octet checksum is required. + + Algorithm-Specific Fields for RSA secret keys: + + - multiprecision integer (MPI) of RSA secret exponent d. + + - MPI of RSA secret prime value p. + + + + +Callas, et al Standards Track [Page 43] + +RFC 4880 OpenPGP Message Format November 2007 + + + - MPI of RSA secret prime value q (p < q). + + - MPI of u, the multiplicative inverse of p, mod q. + + Algorithm-Specific Fields for DSA secret keys: + + - MPI of DSA secret exponent x. + + Algorithm-Specific Fields for Elgamal secret keys: + + - MPI of Elgamal secret exponent x. + + Secret MPI values can be encrypted using a passphrase. If a string- + to-key specifier is given, that describes the algorithm for + converting the passphrase to a key, else a simple MD5 hash of the + passphrase is used. Implementations MUST use a string-to-key + specifier; the simple hash is for backward compatibility and is + deprecated, though implementations MAY continue to use existing + private keys in the old format. The cipher for encrypting the MPIs + is specified in the Secret-Key packet. + + Encryption/decryption of the secret data is done in CFB mode using + the key created from the passphrase and the Initial Vector from the + packet. A different mode is used with V3 keys (which are only RSA) + than with other key formats. With V3 keys, the MPI bit count prefix + (i.e., the first two octets) is not encrypted. Only the MPI non- + prefix data is encrypted. Furthermore, the CFB state is + resynchronized at the beginning of each new MPI value, so that the + CFB block boundary is aligned with the start of the MPI data. + + With V4 keys, a simpler method is used. All secret MPI values are + encrypted in CFB mode, including the MPI bitcount prefix. + + The two-octet checksum that follows the algorithm-specific portion is + the algebraic sum, mod 65536, of the plaintext of all the algorithm- + specific octets (including MPI prefix and data). With V3 keys, the + checksum is stored in the clear. With V4 keys, the checksum is + encrypted like the algorithm-specific data. This value is used to + check that the passphrase was correct. However, this checksum is + deprecated; an implementation SHOULD NOT use it, but should rather + use the SHA-1 hash denoted with a usage octet of 254. The reason for + this is that there are some attacks that involve undetectably + modifying the secret key. + + + + + + + + +Callas, et al Standards Track [Page 44] + +RFC 4880 OpenPGP Message Format November 2007 + + +5.6. Compressed Data Packet (Tag 8) + + The Compressed Data packet contains compressed data. Typically, this + packet is found as the contents of an encrypted packet, or following + a Signature or One-Pass Signature packet, and contains a literal data + packet. + + The body of this packet consists of: + + - One octet that gives the algorithm used to compress the packet. + + - Compressed data, which makes up the remainder of the packet. + + A Compressed Data Packet's body contains an block that compresses + some set of packets. See section "Packet Composition" for details on + how messages are formed. + + ZIP-compressed packets are compressed with raw RFC 1951 [RFC1951] + DEFLATE blocks. Note that PGP V2.6 uses 13 bits of compression. If + an implementation uses more bits of compression, PGP V2.6 cannot + decompress it. + + ZLIB-compressed packets are compressed with RFC 1950 [RFC1950] ZLIB- + style blocks. + + BZip2-compressed packets are compressed using the BZip2 [BZ2] + algorithm. + +5.7. Symmetrically Encrypted Data Packet (Tag 9) + + The Symmetrically Encrypted Data packet contains data encrypted with + a symmetric-key algorithm. When it has been decrypted, it contains + other packets (usually a literal data packet or compressed data + packet, but in theory other Symmetrically Encrypted Data packets or + sequences of packets that form whole OpenPGP messages). + + The body of this packet consists of: + + - Encrypted data, the output of the selected symmetric-key cipher + operating in OpenPGP's variant of Cipher Feedback (CFB) mode. + + The symmetric cipher used may be specified in a Public-Key or + Symmetric-Key Encrypted Session Key packet that precedes the + Symmetrically Encrypted Data packet. In that case, the cipher + algorithm octet is prefixed to the session key before it is + encrypted. If no packets of these types precede the encrypted data, + the IDEA algorithm is used with the session key calculated as the MD5 + hash of the passphrase, though this use is deprecated. + + + +Callas, et al Standards Track [Page 45] + +RFC 4880 OpenPGP Message Format November 2007 + + + The data is encrypted in CFB mode, with a CFB shift size equal to the + cipher's block size. The Initial Vector (IV) is specified as all + zeros. Instead of using an IV, OpenPGP prefixes a string of length + equal to the block size of the cipher plus two to the data before it + is encrypted. The first block-size octets (for example, 8 octets for + a 64-bit block length) are random, and the following two octets are + copies of the last two octets of the IV. For example, in an 8-octet + block, octet 9 is a repeat of octet 7, and octet 10 is a repeat of + octet 8. In a cipher of length 16, octet 17 is a repeat of octet 15 + and octet 18 is a repeat of octet 16. As a pedantic clarification, + in both these examples, we consider the first octet to be numbered 1. + + After encrypting the first block-size-plus-two octets, the CFB state + is resynchronized. The last block-size octets of ciphertext are + passed through the cipher and the block boundary is reset. + + The repetition of 16 bits in the random data prefixed to the message + allows the receiver to immediately check whether the session key is + incorrect. See the "Security Considerations" section for hints on + the proper use of this "quick check". + +5.8. Marker Packet (Obsolete Literal Packet) (Tag 10) + + An experimental version of PGP used this packet as the Literal + packet, but no released version of PGP generated Literal packets with + this tag. With PGP 5.x, this packet has been reassigned and is + reserved for use as the Marker packet. + + The body of this packet consists of: + + - The three octets 0x50, 0x47, 0x50 (which spell "PGP" in UTF-8). + + Such a packet MUST be ignored when received. It may be placed at the + beginning of a message that uses features not available in PGP 2.6.x + in order to cause that version to report that newer software is + necessary to process the message. + +5.9. Literal Data Packet (Tag 11) + + A Literal Data packet contains the body of a message; data that is + not to be further interpreted. + + The body of this packet consists of: + + - A one-octet field that describes how the data is formatted. + + + + + + +Callas, et al Standards Track [Page 46] + +RFC 4880 OpenPGP Message Format November 2007 + + + If it is a 'b' (0x62), then the Literal packet contains binary data. + If it is a 't' (0x74), then it contains text data, and thus may need + line ends converted to local form, or other text-mode changes. The + tag 'u' (0x75) means the same as 't', but also indicates that + implementation believes that the literal data contains UTF-8 text. + + Early versions of PGP also defined a value of 'l' as a 'local' mode + for machine-local conversions. RFC 1991 [RFC1991] incorrectly stated + this local mode flag as '1' (ASCII numeral one). Both of these local + modes are deprecated. + + - File name as a string (one-octet length, followed by a file + name). This may be a zero-length string. Commonly, if the + source of the encrypted data is a file, this will be the name of + the encrypted file. An implementation MAY consider the file name + in the Literal packet to be a more authoritative name than the + actual file name. + + If the special name "_CONSOLE" is used, the message is considered to + be "for your eyes only". This advises that the message data is + unusually sensitive, and the receiving program should process it more + carefully, perhaps avoiding storing the received data to disk, for + example. + + - A four-octet number that indicates a date associated with the + literal data. Commonly, the date might be the modification date + of a file, or the time the packet was created, or a zero that + indicates no specific time. + + - The remainder of the packet is literal data. + + Text data is stored with text endings (i.e., network- + normal line endings). These should be converted to native line + endings by the receiving software. + +5.10. Trust Packet (Tag 12) + + The Trust packet is used only within keyrings and is not normally + exported. Trust packets contain data that record the user's + specifications of which key holders are trustworthy introducers, + along with other information that implementing software uses for + trust information. The format of Trust packets is defined by a given + implementation. + + Trust packets SHOULD NOT be emitted to output streams that are + transferred to other users, and they SHOULD be ignored on any input + other than local keyring files. + + + + +Callas, et al Standards Track [Page 47] + +RFC 4880 OpenPGP Message Format November 2007 + + +5.11. User ID Packet (Tag 13) + + A User ID packet consists of UTF-8 text that is intended to represent + the name and email address of the key holder. By convention, it + includes an RFC 2822 [RFC2822] mail name-addr, but there are no + restrictions on its content. The packet length in the header + specifies the length of the User ID. + +5.12. User Attribute Packet (Tag 17) + + The User Attribute packet is a variation of the User ID packet. It + is capable of storing more types of data than the User ID packet, + which is limited to text. Like the User ID packet, a User Attribute + packet may be certified by the key owner ("self-signed") or any other + key owner who cares to certify it. Except as noted, a User Attribute + packet may be used anywhere that a User ID packet may be used. + + While User Attribute packets are not a required part of the OpenPGP + standard, implementations SHOULD provide at least enough + compatibility to properly handle a certification signature on the + User Attribute packet. A simple way to do this is by treating the + User Attribute packet as a User ID packet with opaque contents, but + an implementation may use any method desired. + + The User Attribute packet is made up of one or more attribute + subpackets. Each subpacket consists of a subpacket header and a + body. The header consists of: + + - the subpacket length (1, 2, or 5 octets) + + - the subpacket type (1 octet) + + and is followed by the subpacket specific data. + + The only currently defined subpacket type is 1, signifying an image. + An implementation SHOULD ignore any subpacket of a type that it does + not recognize. Subpacket types 100 through 110 are reserved for + private or experimental use. + +5.12.1. The Image Attribute Subpacket + + The Image Attribute subpacket is used to encode an image, presumably + (but not required to be) that of the key owner. + + The Image Attribute subpacket begins with an image header. The first + two octets of the image header contain the length of the image + header. Note that unlike other multi-octet numerical values in this + document, due to a historical accident this value is encoded as a + + + +Callas, et al Standards Track [Page 48] + +RFC 4880 OpenPGP Message Format November 2007 + + + little-endian number. The image header length is followed by a + single octet for the image header version. The only currently + defined version of the image header is 1, which is a 16-octet image + header. The first three octets of a version 1 image header are thus + 0x10, 0x00, 0x01. + + The fourth octet of a version 1 image header designates the encoding + format of the image. The only currently defined encoding format is + the value 1 to indicate JPEG. Image format types 100 through 110 are + reserved for private or experimental use. The rest of the version 1 + image header is made up of 12 reserved octets, all of which MUST be + set to 0. + + The rest of the image subpacket contains the image itself. As the + only currently defined image type is JPEG, the image is encoded in + the JPEG File Interchange Format (JFIF), a standard file format for + JPEG images [JFIF]. + + An implementation MAY try to determine the type of an image by + examination of the image data if it is unable to handle a particular + version of the image header or if a specified encoding format value + is not recognized. + +5.13. Sym. Encrypted Integrity Protected Data Packet (Tag 18) + + The Symmetrically Encrypted Integrity Protected Data packet is a + variant of the Symmetrically Encrypted Data packet. It is a new + feature created for OpenPGP that addresses the problem of detecting a + modification to encrypted data. It is used in combination with a + Modification Detection Code packet. + + There is a corresponding feature in the features Signature subpacket + that denotes that an implementation can properly use this packet + type. An implementation MUST support decrypting these packets and + SHOULD prefer generating them to the older Symmetrically Encrypted + Data packet when possible. Since this data packet protects against + modification attacks, this standard encourages its proliferation. + While blanket adoption of this data packet would create + interoperability problems, rapid adoption is nevertheless important. + An implementation SHOULD specifically denote support for this packet, + but it MAY infer it from other mechanisms. + + For example, an implementation might infer from the use of a cipher + such as Advanced Encryption Standard (AES) or Twofish that a user + supports this feature. It might place in the unhashed portion of + another user's key signature a Features subpacket. It might also + present a user with an opportunity to regenerate their own self- + signature with a Features subpacket. + + + +Callas, et al Standards Track [Page 49] + +RFC 4880 OpenPGP Message Format November 2007 + + + This packet contains data encrypted with a symmetric-key algorithm + and protected against modification by the SHA-1 hash algorithm. When + it has been decrypted, it will typically contain other packets (often + a Literal Data packet or Compressed Data packet). The last decrypted + packet in this packet's payload MUST be a Modification Detection Code + packet. + + The body of this packet consists of: + + - A one-octet version number. The only currently defined value is + 1. + + - Encrypted data, the output of the selected symmetric-key cipher + operating in Cipher Feedback mode with shift amount equal to the + block size of the cipher (CFB-n where n is the block size). + + The symmetric cipher used MUST be specified in a Public-Key or + Symmetric-Key Encrypted Session Key packet that precedes the + Symmetrically Encrypted Data packet. In either case, the cipher + algorithm octet is prefixed to the session key before it is + encrypted. + + The data is encrypted in CFB mode, with a CFB shift size equal to the + cipher's block size. The Initial Vector (IV) is specified as all + zeros. Instead of using an IV, OpenPGP prefixes an octet string to + the data before it is encrypted. The length of the octet string + equals the block size of the cipher in octets, plus two. The first + octets in the group, of length equal to the block size of the cipher, + are random; the last two octets are each copies of their 2nd + preceding octet. For example, with a cipher whose block size is 128 + bits or 16 octets, the prefix data will contain 16 random octets, + then two more octets, which are copies of the 15th and 16th octets, + respectively. Unlike the Symmetrically Encrypted Data Packet, no + special CFB resynchronization is done after encrypting this prefix + data. See "OpenPGP CFB Mode" below for more details. + + The repetition of 16 bits in the random data prefixed to the message + allows the receiver to immediately check whether the session key is + incorrect. + + The plaintext of the data to be encrypted is passed through the SHA-1 + hash function, and the result of the hash is appended to the + plaintext in a Modification Detection Code packet. The input to the + hash function includes the prefix data described above; it includes + all of the plaintext, and then also includes two octets of values + 0xD3, 0x14. These represent the encoding of a Modification Detection + Code packet tag and length field of 20 octets. + + + + +Callas, et al Standards Track [Page 50] + +RFC 4880 OpenPGP Message Format November 2007 + + + The resulting hash value is stored in a Modification Detection Code + (MDC) packet, which MUST use the two octet encoding just given to + represent its tag and length field. The body of the MDC packet is + the 20-octet output of the SHA-1 hash. + + The Modification Detection Code packet is appended to the plaintext + and encrypted along with the plaintext using the same CFB context. + + During decryption, the plaintext data should be hashed with SHA-1, + including the prefix data as well as the packet tag and length field + of the Modification Detection Code packet. The body of the MDC + packet, upon decryption, is compared with the result of the SHA-1 + hash. + + Any failure of the MDC indicates that the message has been modified + and MUST be treated as a security problem. Failures include a + difference in the hash values, but also the absence of an MDC packet, + or an MDC packet in any position other than the end of the plaintext. + Any failure SHOULD be reported to the user. + + Note: future designs of new versions of this packet should consider + rollback attacks since it will be possible for an attacker to change + the version back to 1. + + NON-NORMATIVE EXPLANATION + + The MDC system, as packets 18 and 19 are called, were created to + provide an integrity mechanism that is less strong than a + signature, yet stronger than bare CFB encryption. + + It is a limitation of CFB encryption that damage to the ciphertext + will corrupt the affected cipher blocks and the block following. + Additionally, if data is removed from the end of a CFB-encrypted + block, that removal is undetectable. (Note also that CBC mode has + a similar limitation, but data removed from the front of the block + is undetectable.) + + The obvious way to protect or authenticate an encrypted block is + to digitally sign it. However, many people do not wish to + habitually sign data, for a large number of reasons beyond the + scope of this document. Suffice it to say that many people + consider properties such as deniability to be as valuable as + integrity. + + OpenPGP addresses this desire to have more security than raw + encryption and yet preserve deniability with the MDC system. An + MDC is intentionally not a MAC. Its name was not selected by + accident. It is analogous to a checksum. + + + +Callas, et al Standards Track [Page 51] + +RFC 4880 OpenPGP Message Format November 2007 + + + Despite the fact that it is a relatively modest system, it has + proved itself in the real world. It is an effective defense to + several attacks that have surfaced since it has been created. It + has met its modest goals admirably. + + Consequently, because it is a modest security system, it has + modest requirements on the hash function(s) it employs. It does + not rely on a hash function being collision-free, it relies on a + hash function being one-way. If a forger, Frank, wishes to send + Alice a (digitally) unsigned message that says, "I've always + secretly loved you, signed Bob", it is far easier for him to + construct a new message than it is to modify anything intercepted + from Bob. (Note also that if Bob wishes to communicate secretly + with Alice, but without authentication or identification and with + a threat model that includes forgers, he has a problem that + transcends mere cryptography.) + + Note also that unlike nearly every other OpenPGP subsystem, there + are no parameters in the MDC system. It hard-defines SHA-1 as its + hash function. This is not an accident. It is an intentional + choice to avoid downgrade and cross-grade attacks while making a + simple, fast system. (A downgrade attack would be an attack that + replaced SHA-256 with SHA-1, for example. A cross-grade attack + would replace SHA-1 with another 160-bit hash, such as RIPE- + MD/160, for example.) + + However, given the present state of hash function cryptanalysis + and cryptography, it may be desirable to upgrade the MDC system to + a new hash function. See Section 13.11 in the "IANA + Considerations" for guidance. + +5.14. Modification Detection Code Packet (Tag 19) + + The Modification Detection Code packet contains a SHA-1 hash of + plaintext data, which is used to detect message modification. It is + only used with a Symmetrically Encrypted Integrity Protected Data + packet. The Modification Detection Code packet MUST be the last + packet in the plaintext data that is encrypted in the Symmetrically + Encrypted Integrity Protected Data packet, and MUST appear in no + other place. + + A Modification Detection Code packet MUST have a length of 20 octets. + + + + + + + + + +Callas, et al Standards Track [Page 52] + +RFC 4880 OpenPGP Message Format November 2007 + + + The body of this packet consists of: + + - A 20-octet SHA-1 hash of the preceding plaintext data of the + Symmetrically Encrypted Integrity Protected Data packet, + including prefix data, the tag octet, and length octet of the + Modification Detection Code packet. + + Note that the Modification Detection Code packet MUST always use a + new format encoding of the packet tag, and a one-octet encoding of + the packet length. The reason for this is that the hashing rules for + modification detection include a one-octet tag and one-octet length + in the data hash. While this is a bit restrictive, it reduces + complexity. + +6. Radix-64 Conversions + + As stated in the introduction, OpenPGP's underlying native + representation for objects is a stream of arbitrary octets, and some + systems desire these objects to be immune to damage caused by + character set translation, data conversions, etc. + + In principle, any printable encoding scheme that met the requirements + of the unsafe channel would suffice, since it would not change the + underlying binary bit streams of the native OpenPGP data structures. + The OpenPGP standard specifies one such printable encoding scheme to + ensure interoperability. + + OpenPGP's Radix-64 encoding is composed of two parts: a base64 + encoding of the binary data and a checksum. The base64 encoding is + identical to the MIME base64 content-transfer-encoding [RFC2045]. + + The checksum is a 24-bit Cyclic Redundancy Check (CRC) converted to + four characters of radix-64 encoding by the same MIME base64 + transformation, preceded by an equal sign (=). The CRC is computed + by using the generator 0x864CFB and an initialization of 0xB704CE. + The accumulation is done on the data before it is converted to + radix-64, rather than on the converted data. A sample implementation + of this algorithm is in the next section. + + The checksum with its leading equal sign MAY appear on the first line + after the base64 encoded data. + + Rationale for CRC-24: The size of 24 bits fits evenly into printable + base64. The nonzero initialization can detect more errors than a + zero initialization. + + + + + + +Callas, et al Standards Track [Page 53] + +RFC 4880 OpenPGP Message Format November 2007 + + +6.1. An Implementation of the CRC-24 in "C" + + #define CRC24_INIT 0xB704CEL + #define CRC24_POLY 0x1864CFBL + + typedef long crc24; + crc24 crc_octets(unsigned char *octets, size_t len) + { + crc24 crc = CRC24_INIT; + int i; + while (len--) { + crc ^= (*octets++) << 16; + for (i = 0; i < 8; i++) { + crc <<= 1; + if (crc & 0x1000000) + crc ^= CRC24_POLY; + } + } + return crc & 0xFFFFFFL; + } + +6.2. Forming ASCII Armor + + When OpenPGP encodes data into ASCII Armor, it puts specific headers + around the Radix-64 encoded data, so OpenPGP can reconstruct the data + later. An OpenPGP implementation MAY use ASCII armor to protect raw + binary data. OpenPGP informs the user what kind of data is encoded + in the ASCII armor through the use of the headers. + + Concatenating the following data creates ASCII Armor: + + - An Armor Header Line, appropriate for the type of data + + - Armor Headers + + - A blank (zero-length, or containing only whitespace) line + + - The ASCII-Armored data + + - An Armor Checksum + + - The Armor Tail, which depends on the Armor Header Line + + An Armor Header Line consists of the appropriate header line text + surrounded by five (5) dashes ('-', 0x2D) on either side of the + header line text. The header line text is chosen based upon the type + of data that is being encoded in Armor, and how it is being encoded. + Header line texts include the following strings: + + + +Callas, et al Standards Track [Page 54] + +RFC 4880 OpenPGP Message Format November 2007 + + + BEGIN PGP MESSAGE + Used for signed, encrypted, or compressed files. + + BEGIN PGP PUBLIC KEY BLOCK + Used for armoring public keys. + + BEGIN PGP PRIVATE KEY BLOCK + Used for armoring private keys. + + BEGIN PGP MESSAGE, PART X/Y + Used for multi-part messages, where the armor is split amongst Y + parts, and this is the Xth part out of Y. + + BEGIN PGP MESSAGE, PART X + Used for multi-part messages, where this is the Xth part of an + unspecified number of parts. Requires the MESSAGE-ID Armor + Header to be used. + + BEGIN PGP SIGNATURE + Used for detached signatures, OpenPGP/MIME signatures, and + cleartext signatures. Note that PGP 2.x uses BEGIN PGP MESSAGE + for detached signatures. + + Note that all these Armor Header Lines are to consist of a complete + line. That is to say, there is always a line ending preceding the + starting five dashes, and following the ending five dashes. The + header lines, therefore, MUST start at the beginning of a line, and + MUST NOT have text other than whitespace following them on the same + line. These line endings are considered a part of the Armor Header + Line for the purposes of determining the content they delimit. This + is particularly important when computing a cleartext signature (see + below). + + The Armor Headers are pairs of strings that can give the user or the + receiving OpenPGP implementation some information about how to decode + or use the message. The Armor Headers are a part of the armor, not a + part of the message, and hence are not protected by any signatures + applied to the message. + + The format of an Armor Header is that of a key-value pair. A colon + (':' 0x38) and a single space (0x20) separate the key and value. + OpenPGP should consider improperly formatted Armor Headers to be + corruption of the ASCII Armor. Unknown keys should be reported to + the user, but OpenPGP should continue to process the message. + + Note that some transport methods are sensitive to line length. While + there is a limit of 76 characters for the Radix-64 data (Section + 6.3), there is no limit to the length of Armor Headers. Care should + + + +Callas, et al Standards Track [Page 55] + +RFC 4880 OpenPGP Message Format November 2007 + + + be taken that the Armor Headers are short enough to survive + transport. One way to do this is to repeat an Armor Header key + multiple times with different values for each so that no one line is + overly long. + + Currently defined Armor Header Keys are as follows: + + - "Version", which states the OpenPGP implementation and version + used to encode the message. + + - "Comment", a user-defined comment. OpenPGP defines all text to + be in UTF-8. A comment may be any UTF-8 string. However, the + whole point of armoring is to provide seven-bit-clean data. + Consequently, if a comment has characters that are outside the + US-ASCII range of UTF, they may very well not survive transport. + + - "MessageID", a 32-character string of printable characters. The + string must be the same for all parts of a multi-part message + that uses the "PART X" Armor Header. MessageID strings should be + unique enough that the recipient of the mail can associate all + the parts of a message with each other. A good checksum or + cryptographic hash function is sufficient. + + The MessageID SHOULD NOT appear unless it is in a multi-part + message. If it appears at all, it MUST be computed from the + finished (encrypted, signed, etc.) message in a deterministic + fashion, rather than contain a purely random value. This is to + allow the legitimate recipient to determine that the MessageID + cannot serve as a covert means of leaking cryptographic key + information. + + - "Hash", a comma-separated list of hash algorithms used in this + message. This is used only in cleartext signed messages. + + - "Charset", a description of the character set that the plaintext + is in. Please note that OpenPGP defines text to be in UTF-8. An + implementation will get best results by translating into and out + of UTF-8. However, there are many instances where this is easier + said than done. Also, there are communities of users who have no + need for UTF-8 because they are all happy with a character set + like ISO Latin-5 or a Japanese character set. In such instances, + an implementation MAY override the UTF-8 default by using this + header key. An implementation MAY implement this key and any + translations it cares to; an implementation MAY ignore it and + assume all text is UTF-8. + + + + + + +Callas, et al Standards Track [Page 56] + +RFC 4880 OpenPGP Message Format November 2007 + + + The Armor Tail Line is composed in the same manner as the Armor + Header Line, except the string "BEGIN" is replaced by the string + "END". + +6.3. Encoding Binary in Radix-64 + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating three 8-bit input + groups. These 24 bits are then treated as four concatenated 6-bit + groups, each of which is translated into a single digit in the + Radix-64 alphabet. When encoding a bit stream with the Radix-64 + encoding, the bit stream must be presumed to be ordered with the most + significant bit first. That is, the first bit in the stream will be + the high-order bit in the first 8-bit octet, and the eighth bit will + be the low-order bit in the first 8-bit octet, and so on. + + +--first octet--+-second octet--+--third octet--+ + |7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0| + +-----------+---+-------+-------+---+-----------+ + |5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0| + +--1.index--+--2.index--+--3.index--+--4.index--+ + + Each 6-bit group is used as an index into an array of 64 printable + characters from the table below. The character referenced by the + index is placed in the output string. + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + The encoded output stream must be represented in lines of no more + than 76 characters each. + + + +Callas, et al Standards Track [Page 57] + +RFC 4880 OpenPGP Message Format November 2007 + + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. There are three possibilities: + + 1. The last data group has 24 bits (3 octets). No special processing + is needed. + + 2. The last data group has 16 bits (2 octets). The first two 6-bit + groups are processed as above. The third (incomplete) data group + has two zero-value bits added to it, and is processed as above. A + pad character (=) is added to the output. + + 3. The last data group has 8 bits (1 octet). The first 6-bit group + is processed as above. The second (incomplete) data group has + four zero-value bits added to it, and is processed as above. Two + pad characters (=) are added to the output. + +6.4. Decoding Radix-64 + + In Radix-64 data, characters other than those in the table, line + breaks, and other white space probably indicate a transmission error, + about which a warning message or even a message rejection might be + appropriate under some circumstances. Decoding software must ignore + all white space. + + Because it is used only for padding at the end of the data, the + occurrence of any "=" characters may be taken as evidence that the + end of the data has been reached (without truncation in transit). No + such assurance is possible, however, when the number of octets + transmitted was a multiple of three and no "=" characters are + present. + + + + + + + + + + + + + + + + + + + + + +Callas, et al Standards Track [Page 58] + +RFC 4880 OpenPGP Message Format November 2007 + + +6.5. Examples of Radix-64 + + Input data: 0x14FB9C03D97E + Hex: 1 4 F B 9 C | 0 3 D 9 7 E + 8-bit: 00010100 11111011 10011100 | 00000011 11011001 11111110 + 6-bit: 000101 001111 101110 011100 | 000000 111101 100111 111110 + Decimal: 5 15 46 28 0 61 37 62 + Output: F P u c A 9 l + + Input data: 0x14FB9C03D9 + Hex: 1 4 F B 9 C | 0 3 D 9 + 8-bit: 00010100 11111011 10011100 | 00000011 11011001 + pad with 00 + 6-bit: 000101 001111 101110 011100 | 000000 111101 100100 + Decimal: 5 15 46 28 0 61 36 + pad with = + Output: F P u c A 9 k = + Input data: 0x14FB9C03 + Hex: 1 4 F B 9 C | 0 3 + 8-bit: 00010100 11111011 10011100 | 00000011 + pad with 0000 + 6-bit: 000101 001111 101110 011100 | 000000 110000 + Decimal: 5 15 46 28 0 48 + pad with = = + Output: F P u c A w = = + +6.6. Example of an ASCII Armored Message + + -----BEGIN PGP MESSAGE----- + Version: OpenPrivacy 0.99 + + yDgBO22WxBHv7O8X7O/jygAEzol56iUKiXmV+XmpCtmpqQUKiQrFqclFqUDBovzS + vBSFjNSiVHsuAA== + =njUN + -----END PGP MESSAGE----- + + Note that this example has extra indenting; an actual armored message + would have no leading whitespace. + +7. Cleartext Signature Framework + + It is desirable to be able to sign a textual octet stream without + ASCII armoring the stream itself, so the signed text is still + readable without special software. In order to bind a signature to + such a cleartext, this framework is used. (Note that this framework + is not intended to be reversible. RFC 3156 [RFC3156] defines another + way to sign cleartext messages for environments that support MIME.) + + + + + +Callas, et al Standards Track [Page 59] + +RFC 4880 OpenPGP Message Format November 2007 + + + The cleartext signed message consists of: + + - The cleartext header '-----BEGIN PGP SIGNED MESSAGE-----' on a + single line, + + - One or more "Hash" Armor Headers, + + - Exactly one empty line not included into the message digest, + + - The dash-escaped cleartext that is included into the message + digest, + + - The ASCII armored signature(s) including the '-----BEGIN PGP + SIGNATURE-----' Armor Header and Armor Tail Lines. + + If the "Hash" Armor Header is given, the specified message digest + algorithm(s) are used for the signature. If there are no such + headers, MD5 is used. If MD5 is the only hash used, then an + implementation MAY omit this header for improved V2.x compatibility. + If more than one message digest is used in the signature, the "Hash" + armor header contains a comma-delimited list of used message digests. + + Current message digest names are described below with the algorithm + IDs. + + An implementation SHOULD add a line break after the cleartext, but + MAY omit it if the cleartext ends with a line break. This is for + visual clarity. + +7.1. Dash-Escaped Text + + The cleartext content of the message must also be dash-escaped. + + Dash-escaped cleartext is the ordinary cleartext where every line + starting with a dash '-' (0x2D) is prefixed by the sequence dash '-' + (0x2D) and space ' ' (0x20). This prevents the parser from + recognizing armor headers of the cleartext itself. An implementation + MAY dash-escape any line, SHOULD dash-escape lines commencing "From" + followed by a space, and MUST dash-escape any line commencing in a + dash. The message digest is computed using the cleartext itself, not + the dash-escaped form. + + As with binary signatures on text documents, a cleartext signature is + calculated on the text using canonical line endings. The + line ending (i.e., the ) before the '-----BEGIN PGP + SIGNATURE-----' line that terminates the signed text is not + considered part of the signed text. + + + + +Callas, et al Standards Track [Page 60] + +RFC 4880 OpenPGP Message Format November 2007 + + + When reversing dash-escaping, an implementation MUST strip the string + "- " if it occurs at the beginning of a line, and SHOULD warn on "-" + and any character other than a space at the beginning of a line. + + Also, any trailing whitespace -- spaces (0x20) and tabs (0x09) -- at + the end of any line is removed when the cleartext signature is + generated. + +8. Regular Expressions + + A regular expression is zero or more branches, separated by '|'. It + matches anything that matches one of the branches. + + A branch is zero or more pieces, concatenated. It matches a match + for the first, followed by a match for the second, etc. + + A piece is an atom possibly followed by '*', '+', or '?'. An atom + followed by '*' matches a sequence of 0 or more matches of the atom. + An atom followed by '+' matches a sequence of 1 or more matches of + the atom. An atom followed by '?' matches a match of the atom, or + the null string. + + An atom is a regular expression in parentheses (matching a match for + the regular expression), a range (see below), '.' (matching any + single character), '^' (matching the null string at the beginning of + the input string), '$' (matching the null string at the end of the + input string), a '\' followed by a single character (matching that + character), or a single character with no other significance + (matching that character). + + A range is a sequence of characters enclosed in '[]'. It normally + matches any single character from the sequence. If the sequence + begins with '^', it matches any single character not from the rest of + the sequence. If two characters in the sequence are separated + by '-', this is shorthand for the full list of ASCII characters + between them (e.g., '[0-9]' matches any decimal digit). To include a + literal ']' in the sequence, make it the first character (following a + possible '^'). To include a literal '-', make it the first or last + character. + +9. Constants + + This section describes the constants used in OpenPGP. + + Note that these tables are not exhaustive lists; an implementation + MAY implement an algorithm not on these lists, so long as the + algorithm numbers are chosen from the private or experimental + algorithm range. + + + +Callas, et al Standards Track [Page 61] + +RFC 4880 OpenPGP Message Format November 2007 + + + See the section "Notes on Algorithms" below for more discussion of + the algorithms. + +9.1. Public-Key Algorithms + + ID Algorithm + -- --------- + 1 - RSA (Encrypt or Sign) [HAC] + 2 - RSA Encrypt-Only [HAC] + 3 - RSA Sign-Only [HAC] + 16 - Elgamal (Encrypt-Only) [ELGAMAL] [HAC] + 17 - DSA (Digital Signature Algorithm) [FIPS186] [HAC] + 18 - Reserved for Elliptic Curve + 19 - Reserved for ECDSA + 20 - Reserved (formerly Elgamal Encrypt or Sign) + 21 - Reserved for Diffie-Hellman (X9.42, + as defined for IETF-S/MIME) + 100 to 110 - Private/Experimental algorithm + + Implementations MUST implement DSA for signatures, and Elgamal for + encryption. Implementations SHOULD implement RSA keys (1). RSA + Encrypt-Only (2) and RSA Sign-Only are deprecated and SHOULD NOT be + generated, but may be interpreted. See Section 13.5. See Section + 13.8 for notes on Elliptic Curve (18), ECDSA (19), Elgamal Encrypt or + Sign (20), and X9.42 (21). Implementations MAY implement any other + algorithm. + +9.2. Symmetric-Key Algorithms + + ID Algorithm + -- --------- + 0 - Plaintext or unencrypted data + 1 - IDEA [IDEA] + 2 - TripleDES (DES-EDE, [SCHNEIER] [HAC] - + 168 bit key derived from 192) + 3 - CAST5 (128 bit key, as per [RFC2144]) + 4 - Blowfish (128 bit key, 16 rounds) [BLOWFISH] + 5 - Reserved + 6 - Reserved + 7 - AES with 128-bit key [AES] + 8 - AES with 192-bit key + 9 - AES with 256-bit key + 10 - Twofish with 256-bit key [TWOFISH] + 100 to 110 - Private/Experimental algorithm + + Implementations MUST implement TripleDES. Implementations SHOULD + implement AES-128 and CAST5. Implementations that interoperate with + + + + +Callas, et al Standards Track [Page 62] + +RFC 4880 OpenPGP Message Format November 2007 + + + PGP 2.6 or earlier need to support IDEA, as that is the only + symmetric cipher those versions use. Implementations MAY implement + any other algorithm. + +9.3. Compression Algorithms + + ID Algorithm + -- --------- + 0 - Uncompressed + 1 - ZIP [RFC1951] + 2 - ZLIB [RFC1950] + 3 - BZip2 [BZ2] + 100 to 110 - Private/Experimental algorithm + + Implementations MUST implement uncompressed data. Implementations + SHOULD implement ZIP. Implementations MAY implement any other + algorithm. + +9.4. Hash Algorithms + + ID Algorithm Text Name + -- --------- --------- + 1 - MD5 [HAC] "MD5" + 2 - SHA-1 [FIPS180] "SHA1" + 3 - RIPE-MD/160 [HAC] "RIPEMD160" + 4 - Reserved + 5 - Reserved + 6 - Reserved + 7 - Reserved + 8 - SHA256 [FIPS180] "SHA256" + 9 - SHA384 [FIPS180] "SHA384" + 10 - SHA512 [FIPS180] "SHA512" + 11 - SHA224 [FIPS180] "SHA224" + 100 to 110 - Private/Experimental algorithm + + Implementations MUST implement SHA-1. Implementations MAY implement + other algorithms. MD5 is deprecated. + +10. IANA Considerations + + OpenPGP is highly parameterized, and consequently there are a number + of considerations for allocating parameters for extensions. This + section describes how IANA should look at extensions to the protocol + as described in this document. + + + + + + + +Callas, et al Standards Track [Page 63] + +RFC 4880 OpenPGP Message Format November 2007 + + +10.1. New String-to-Key Specifier Types + + OpenPGP S2K specifiers contain a mechanism for new algorithms to turn + a string into a key. This specification creates a registry of S2K + specifier types. The registry includes the S2K type, the name of the + S2K, and a reference to the defining specification. The initial + values for this registry can be found in Section 3.7.1. Adding a new + S2K specifier MUST be done through the IETF CONSENSUS method, as + described in [RFC2434]. + +10.2. New Packets + + Major new features of OpenPGP are defined through new packet types. + This specification creates a registry of packet types. The registry + includes the packet type, the name of the packet, and a reference to + the defining specification. The initial values for this registry can + be found in Section 4.3. Adding a new packet type MUST be done + through the IETF CONSENSUS method, as described in [RFC2434]. + +10.2.1. User Attribute Types + + The User Attribute packet permits an extensible mechanism for other + types of certificate identification. This specification creates a + registry of User Attribute types. The registry includes the User + Attribute type, the name of the User Attribute, and a reference to + the defining specification. The initial values for this registry can + be found in Section 5.12. Adding a new User Attribute type MUST be + done through the IETF CONSENSUS method, as described in [RFC2434]. + +10.2.1.1. Image Format Subpacket Types + + Within User Attribute packets, there is an extensible mechanism for + other types of image-based user attributes. This specification + creates a registry of Image Attribute subpacket types. The registry + includes the Image Attribute subpacket type, the name of the Image + Attribute subpacket, and a reference to the defining specification. + The initial values for this registry can be found in Section 5.12.1. + Adding a new Image Attribute subpacket type MUST be done through the + IETF CONSENSUS method, as described in [RFC2434]. + +10.2.2. New Signature Subpackets + + OpenPGP signatures contain a mechanism for signed (or unsigned) data + to be added to them for a variety of purposes in the Signature + subpackets as discussed in Section 5.2.3.1. This specification + creates a registry of Signature subpacket types. The registry + includes the Signature subpacket type, the name of the subpacket, and + a reference to the defining specification. The initial values for + + + +Callas, et al Standards Track [Page 64] + +RFC 4880 OpenPGP Message Format November 2007 + + + this registry can be found in Section 5.2.3.1. Adding a new + Signature subpacket MUST be done through the IETF CONSENSUS method, + as described in [RFC2434]. + +10.2.2.1. Signature Notation Data Subpackets + + OpenPGP signatures further contain a mechanism for extensions in + signatures. These are the Notation Data subpackets, which contain a + key/value pair. Notations contain a user space that is completely + unmanaged and an IETF space. + + This specification creates a registry of Signature Notation Data + types. The registry includes the Signature Notation Data type, the + name of the Signature Notation Data, its allowed values, and a + reference to the defining specification. The initial values for this + registry can be found in Section 5.2.3.16. Adding a new Signature + Notation Data subpacket MUST be done through the EXPERT REVIEW + method, as described in [RFC2434]. + +10.2.2.2. Key Server Preference Extensions + + OpenPGP signatures contain a mechanism for preferences to be + specified about key servers. This specification creates a registry + of key server preferences. The registry includes the key server + preference, the name of the preference, and a reference to the + defining specification. The initial values for this registry can be + found in Section 5.2.3.17. Adding a new key server preference MUST + be done through the IETF CONSENSUS method, as described in [RFC2434]. + +10.2.2.3. Key Flags Extensions + + OpenPGP signatures contain a mechanism for flags to be specified + about key usage. This specification creates a registry of key usage + flags. The registry includes the key flags value, the name of the + flag, and a reference to the defining specification. The initial + values for this registry can be found in Section 5.2.3.21. Adding a + new key usage flag MUST be done through the IETF CONSENSUS method, as + described in [RFC2434]. + +10.2.2.4. Reason for Revocation Extensions + + OpenPGP signatures contain a mechanism for flags to be specified + about why a key was revoked. This specification creates a registry + of "Reason for Revocation" flags. The registry includes the "Reason + for Revocation" flags value, the name of the flag, and a reference to + the defining specification. The initial values for this registry can + be found in Section 5.2.3.23. Adding a new feature flag MUST be done + through the IETF CONSENSUS method, as described in [RFC2434]. + + + +Callas, et al Standards Track [Page 65] + +RFC 4880 OpenPGP Message Format November 2007 + + +10.2.2.5. Implementation Features + + OpenPGP signatures contain a mechanism for flags to be specified + stating which optional features an implementation supports. This + specification creates a registry of feature-implementation flags. + The registry includes the feature-implementation flags value, the + name of the flag, and a reference to the defining specification. The + initial values for this registry can be found in Section 5.2.3.24. + Adding a new feature-implementation flag MUST be done through the + IETF CONSENSUS method, as described in [RFC2434]. + + Also see Section 13.12 for more information about when feature flags + are needed. + +10.2.3. New Packet Versions + + The core OpenPGP packets all have version numbers, and can be revised + by introducing a new version of an existing packet. This + specification creates a registry of packet types. The registry + includes the packet type, the number of the version, and a reference + to the defining specification. The initial values for this registry + can be found in Section 5. Adding a new packet version MUST be done + through the IETF CONSENSUS method, as described in [RFC2434]. + +10.3. New Algorithms + + Section 9 lists the core algorithms that OpenPGP uses. Adding in a + new algorithm is usually simple. For example, adding in a new + symmetric cipher usually would not need anything more than allocating + a constant for that cipher. If that cipher had other than a 64-bit + or 128-bit block size, there might need to be additional + documentation describing how OpenPGP-CFB mode would be adjusted. + Similarly, when DSA was expanded from a maximum of 1024-bit public + keys to 3072-bit public keys, the revision of FIPS 186 contained + enough information itself to allow implementation. Changes to this + document were made mainly for emphasis. + +10.3.1. Public-Key Algorithms + + OpenPGP specifies a number of public-key algorithms. This + specification creates a registry of public-key algorithm identifiers. + The registry includes the algorithm name, its key sizes and + parameters, and a reference to the defining specification. The + initial values for this registry can be found in Section 9. Adding a + new public-key algorithm MUST be done through the IETF CONSENSUS + method, as described in [RFC2434]. + + + + + +Callas, et al Standards Track [Page 66] + +RFC 4880 OpenPGP Message Format November 2007 + + +10.3.2. Symmetric-Key Algorithms + + OpenPGP specifies a number of symmetric-key algorithms. This + specification creates a registry of symmetric-key algorithm + identifiers. The registry includes the algorithm name, its key sizes + and block size, and a reference to the defining specification. The + initial values for this registry can be found in Section 9. Adding a + new symmetric-key algorithm MUST be done through the IETF CONSENSUS + method, as described in [RFC2434]. + +10.3.3. Hash Algorithms + + OpenPGP specifies a number of hash algorithms. This specification + creates a registry of hash algorithm identifiers. The registry + includes the algorithm name, a text representation of that name, its + block size, an OID hash prefix, and a reference to the defining + specification. The initial values for this registry can be found in + Section 9 for the algorithm identifiers and text names, and Section + 5.2.2 for the OIDs and expanded signature prefixes. Adding a new + hash algorithm MUST be done through the IETF CONSENSUS method, as + described in [RFC2434]. + +10.3.4. Compression Algorithms + + OpenPGP specifies a number of compression algorithms. This + specification creates a registry of compression algorithm + identifiers. The registry includes the algorithm name and a + reference to the defining specification. The initial values for this + registry can be found in Section 9.3. Adding a new compression key + algorithm MUST be done through the IETF CONSENSUS method, as + described in [RFC2434]. + +11. Packet Composition + + OpenPGP packets are assembled into sequences in order to create + messages and to transfer keys. Not all possible packet sequences are + meaningful and correct. This section describes the rules for how + packets should be placed into sequences. + +11.1. Transferable Public Keys + + OpenPGP users may transfer public keys. The essential elements of a + transferable public key are as follows: + + - One Public-Key packet + + - Zero or more revocation signatures + + + + +Callas, et al Standards Track [Page 67] + +RFC 4880 OpenPGP Message Format November 2007 + + + - One or more User ID packets + + - After each User ID packet, zero or more Signature packets + (certifications) + + - Zero or more User Attribute packets + + - After each User Attribute packet, zero or more Signature packets + (certifications) + + - Zero or more Subkey packets + + - After each Subkey packet, one Signature packet, plus optionally a + revocation + + The Public-Key packet occurs first. Each of the following User ID + packets provides the identity of the owner of this public key. If + there are multiple User ID packets, this corresponds to multiple + means of identifying the same unique individual user; for example, a + user may have more than one email address, and construct a User ID + for each one. + + Immediately following each User ID packet, there are zero or more + Signature packets. Each Signature packet is calculated on the + immediately preceding User ID packet and the initial Public-Key + packet. The signature serves to certify the corresponding public key + and User ID. In effect, the signer is testifying to his or her + belief that this public key belongs to the user identified by this + User ID. + + Within the same section as the User ID packets, there are zero or + more User Attribute packets. Like the User ID packets, a User + Attribute packet is followed by zero or more Signature packets + calculated on the immediately preceding User Attribute packet and the + initial Public-Key packet. + + User Attribute packets and User ID packets may be freely intermixed + in this section, so long as the signatures that follow them are + maintained on the proper User Attribute or User ID packet. + + After the User ID packet or Attribute packet, there may be zero or + more Subkey packets. In general, subkeys are provided in cases where + the top-level public key is a signature-only key. However, any V4 + key may have subkeys, and the subkeys may be encryption-only keys, + signature-only keys, or general-purpose keys. V3 keys MUST NOT have + subkeys. + + + + + +Callas, et al Standards Track [Page 68] + +RFC 4880 OpenPGP Message Format November 2007 + + + Each Subkey packet MUST be followed by one Signature packet, which + should be a subkey binding signature issued by the top-level key. + For subkeys that can issue signatures, the subkey binding signature + MUST contain an Embedded Signature subpacket with a primary key + binding signature (0x19) issued by the subkey on the top-level key. + + Subkey and Key packets may each be followed by a revocation Signature + packet to indicate that the key is revoked. Revocation signatures + are only accepted if they are issued by the key itself, or by a key + that is authorized to issue revocations via a Revocation Key + subpacket in a self-signature by the top-level key. + + Transferable public-key packet sequences may be concatenated to allow + transferring multiple public keys in one operation. + +11.2. Transferable Secret Keys + + OpenPGP users may transfer secret keys. The format of a transferable + secret key is the same as a transferable public key except that + secret-key and secret-subkey packets are used instead of the public + key and public-subkey packets. Implementations SHOULD include self- + signatures on any user IDs and subkeys, as this allows for a complete + public key to be automatically extracted from the transferable secret + key. Implementations MAY choose to omit the self-signatures, + especially if a transferable public key accompanies the transferable + secret key. + +11.3. OpenPGP Messages + + An OpenPGP message is a packet or sequence of packets that + corresponds to the following grammatical rules (comma represents + sequential composition, and vertical bar separates alternatives): + + OpenPGP Message :- Encrypted Message | Signed Message | + Compressed Message | Literal Message. + + Compressed Message :- Compressed Data Packet. + + Literal Message :- Literal Data Packet. + + ESK :- Public-Key Encrypted Session Key Packet | + Symmetric-Key Encrypted Session Key Packet. + + ESK Sequence :- ESK | ESK Sequence, ESK. + + Encrypted Data :- Symmetrically Encrypted Data Packet | + Symmetrically Encrypted Integrity Protected Data Packet + + + + +Callas, et al Standards Track [Page 69] + +RFC 4880 OpenPGP Message Format November 2007 + + + Encrypted Message :- Encrypted Data | ESK Sequence, Encrypted Data. + + One-Pass Signed Message :- One-Pass Signature Packet, + OpenPGP Message, Corresponding Signature Packet. + + Signed Message :- Signature Packet, OpenPGP Message | + One-Pass Signed Message. + + In addition, decrypting a Symmetrically Encrypted Data packet or a + Symmetrically Encrypted Integrity Protected Data packet as well as + decompressing a Compressed Data packet must yield a valid OpenPGP + Message. + +11.4. Detached Signatures + + Some OpenPGP applications use so-called "detached signatures". For + example, a program bundle may contain a file, and with it a second + file that is a detached signature of the first file. These detached + signatures are simply a Signature packet stored separately from the + data for which they are a signature. + +12. Enhanced Key Formats + +12.1. Key Structures + + The format of an OpenPGP V3 key is as follows. Entries in square + brackets are optional and ellipses indicate repetition. + + RSA Public Key + [Revocation Self Signature] + User ID [Signature ...] + [User ID [Signature ...] ...] + + Each signature certifies the RSA public key and the preceding User + ID. The RSA public key can have many User IDs and each User ID can + have many signatures. V3 keys are deprecated. Implementations MUST + NOT generate new V3 keys, but MAY continue to use existing ones. + + The format of an OpenPGP V4 key that uses multiple public keys is + similar except that the other keys are added to the end as "subkeys" + of the primary key. + + + + + + + + + + +Callas, et al Standards Track [Page 70] + +RFC 4880 OpenPGP Message Format November 2007 + + + Primary-Key + [Revocation Self Signature] + [Direct Key Signature...] + User ID [Signature ...] + [User ID [Signature ...] ...] + [User Attribute [Signature ...] ...] + [[Subkey [Binding-Signature-Revocation] + Primary-Key-Binding-Signature] ...] + + A subkey always has a single signature after it that is issued using + the primary key to tie the two keys together. This binding signature + may be in either V3 or V4 format, but SHOULD be V4. Subkeys that can + issue signatures MUST have a V4 binding signature due to the REQUIRED + embedded primary key binding signature. + + In the above diagram, if the binding signature of a subkey has been + revoked, the revoked key may be removed, leaving only one key. + + In a V4 key, the primary key MUST be a key capable of certification. + The subkeys may be keys of any other type. There may be other + constructions of V4 keys, too. For example, there may be a single- + key RSA key in V4 format, a DSA primary key with an RSA encryption + key, or RSA primary key with an Elgamal subkey, etc. + + It is also possible to have a signature-only subkey. This permits a + primary key that collects certifications (key signatures), but is + used only for certifying subkeys that are used for encryption and + signatures. + +12.2. Key IDs and Fingerprints + + For a V3 key, the eight-octet Key ID consists of the low 64 bits of + the public modulus of the RSA key. + + The fingerprint of a V3 key is formed by hashing the body (but not + the two-octet length) of the MPIs that form the key material (public + modulus n, followed by exponent e) with MD5. Note that both V3 keys + and MD5 are deprecated. + + A V4 fingerprint is the 160-bit SHA-1 hash of the octet 0x99, + followed by the two-octet packet length, followed by the entire + Public-Key packet starting with the version field. The Key ID is the + low-order 64 bits of the fingerprint. Here are the fields of the + hash material, with the example of a DSA key: + + a.1) 0x99 (1 octet) + + a.2) high-order length octet of (b)-(e) (1 octet) + + + +Callas, et al Standards Track [Page 71] + +RFC 4880 OpenPGP Message Format November 2007 + + + a.3) low-order length octet of (b)-(e) (1 octet) + + b) version number = 4 (1 octet); + + c) timestamp of key creation (4 octets); + + d) algorithm (1 octet): 17 = DSA (example); + + e) Algorithm-specific fields. + + Algorithm-Specific Fields for DSA keys (example): + + e.1) MPI of DSA prime p; + + e.2) MPI of DSA group order q (q is a prime divisor of p-1); + + e.3) MPI of DSA group generator g; + + e.4) MPI of DSA public-key value y (= g**x mod p where x is secret). + + Note that it is possible for there to be collisions of Key IDs -- two + different keys with the same Key ID. Note that there is a much + smaller, but still non-zero, probability that two different keys have + the same fingerprint. + + Also note that if V3 and V4 format keys share the same RSA key + material, they will have different Key IDs as well as different + fingerprints. + + Finally, the Key ID and fingerprint of a subkey are calculated in the + same way as for a primary key, including the 0x99 as the first octet + (even though this is not a valid packet ID for a public subkey). + +13. Notes on Algorithms + +13.1. PKCS#1 Encoding in OpenPGP + + This standard makes use of the PKCS#1 functions EME-PKCS1-v1_5 and + EMSA-PKCS1-v1_5. However, the calling conventions of these functions + has changed in the past. To avoid potential confusion and + interoperability problems, we are including local copies in this + document, adapted from those in PKCS#1 v2.1 [RFC3447]. RFC 3447 + should be treated as the ultimate authority on PKCS#1 for OpenPGP. + Nonetheless, we believe that there is value in having a self- + contained document that avoids problems in the future with needed + changes in the conventions. + + + + + +Callas, et al Standards Track [Page 72] + +RFC 4880 OpenPGP Message Format November 2007 + + +13.1.1. EME-PKCS1-v1_5-ENCODE + + Input: + + k = the length in octets of the key modulus + + M = message to be encoded, an octet string of length mLen, where + mLen <= k - 11 + + Output: + + EM = encoded message, an octet string of length k + + Error: "message too long" + + 1. Length checking: If mLen > k - 11, output "message too long" and + stop. + + 2. Generate an octet string PS of length k - mLen - 3 consisting of + pseudo-randomly generated nonzero octets. The length of PS will + be at least eight octets. + + 3. Concatenate PS, the message M, and other padding to form an + encoded message EM of length k octets as + + EM = 0x00 || 0x02 || PS || 0x00 || M. + + 4. Output EM. + +13.1.2. EME-PKCS1-v1_5-DECODE + + Input: + + EM = encoded message, an octet string + + Output: + + M = message, an octet string + + Error: "decryption error" + + To decode an EME-PKCS1_v1_5 message, separate the encoded message EM + into an octet string PS consisting of nonzero octets and a message M + as follows + + EM = 0x00 || 0x02 || PS || 0x00 || M. + + + + + +Callas, et al Standards Track [Page 73] + +RFC 4880 OpenPGP Message Format November 2007 + + + If the first octet of EM does not have hexadecimal value 0x00, if the + second octet of EM does not have hexadecimal value 0x02, if there is + no octet with hexadecimal value 0x00 to separate PS from M, or if the + length of PS is less than 8 octets, output "decryption error" and + stop. See also the security note in Section 14 regarding differences + in reporting between a decryption error and a padding error. + +13.1.3. EMSA-PKCS1-v1_5 + + This encoding method is deterministic and only has an encoding + operation. + + Option: + + Hash - a hash function in which hLen denotes the length in octets of + the hash function output + + Input: + + M = message to be encoded + + mL = intended length in octets of the encoded message, at least tLen + + 11, where tLen is the octet length of the DER encoding T of a + certain value computed during the encoding operation + + Output: + + EM = encoded message, an octet string of length emLen + + Errors: "message too long"; "intended encoded message length too + short" + + Steps: + + 1. Apply the hash function to the message M to produce a hash value + H: + + H = Hash(M). + + If the hash function outputs "message too long," output "message + too long" and stop. + + 2. Using the list in Section 5.2.2, produce an ASN.1 DER value for + the hash function used. Let T be the full hash prefix from + Section 5.2.2, and let tLen be the length in octets of T. + + 3. If emLen < tLen + 11, output "intended encoded message length + too short" and stop. + + + +Callas, et al Standards Track [Page 74] + +RFC 4880 OpenPGP Message Format November 2007 + + + 4. Generate an octet string PS consisting of emLen - tLen - 3 + octets with hexadecimal value 0xFF. The length of PS will be at + least 8 octets. + + 5. Concatenate PS, the hash prefix T, and other padding to form the + encoded message EM as + + EM = 0x00 || 0x01 || PS || 0x00 || T. + + 6. Output EM. + +13.2. Symmetric Algorithm Preferences + + The symmetric algorithm preference is an ordered list of algorithms + that the keyholder accepts. Since it is found on a self-signature, + it is possible that a keyholder may have multiple, different + preferences. For example, Alice may have TripleDES only specified + for "alice@work.com" but CAST5, Blowfish, and TripleDES specified for + "alice@home.org". Note that it is also possible for preferences to + be in a subkey's binding signature. + + Since TripleDES is the MUST-implement algorithm, if it is not + explicitly in the list, it is tacitly at the end. However, it is + good form to place it there explicitly. Note also that if an + implementation does not implement the preference, then it is + implicitly a TripleDES-only implementation. + + An implementation MUST NOT use a symmetric algorithm that is not in + the recipient's preference list. When encrypting to more than one + recipient, the implementation finds a suitable algorithm by taking + the intersection of the preferences of the recipients. Note that the + MUST-implement algorithm, TripleDES, ensures that the intersection is + not null. The implementation may use any mechanism to pick an + algorithm in the intersection. + + If an implementation can decrypt a message that a keyholder doesn't + have in their preferences, the implementation SHOULD decrypt the + message anyway, but MUST warn the keyholder that the protocol has + been violated. For example, suppose that Alice, above, has software + that implements all algorithms in this specification. Nonetheless, + she prefers subsets for work or home. If she is sent a message + encrypted with IDEA, which is not in her preferences, the software + warns her that someone sent her an IDEA-encrypted message, but it + would ideally decrypt it anyway. + + + + + + + +Callas, et al Standards Track [Page 75] + +RFC 4880 OpenPGP Message Format November 2007 + + +13.3. Other Algorithm Preferences + + Other algorithm preferences work similarly to the symmetric algorithm + preference, in that they specify which algorithms the keyholder + accepts. There are two interesting cases that other comments need to + be made about, though, the compression preferences and the hash + preferences. + +13.3.1. Compression Preferences + + Compression has been an integral part of PGP since its first days. + OpenPGP and all previous versions of PGP have offered compression. + In this specification, the default is for messages to be compressed, + although an implementation is not required to do so. Consequently, + the compression preference gives a way for a keyholder to request + that messages not be compressed, presumably because they are using a + minimal implementation that does not include compression. + Additionally, this gives a keyholder a way to state that it can + support alternate algorithms. + + Like the algorithm preferences, an implementation MUST NOT use an + algorithm that is not in the preference vector. If the preferences + are not present, then they are assumed to be [ZIP(1), + Uncompressed(0)]. + + Additionally, an implementation MUST implement this preference to the + degree of recognizing when to send an uncompressed message. A robust + implementation would satisfy this requirement by looking at the + recipient's preference and acting accordingly. A minimal + implementation can satisfy this requirement by never generating a + compressed message, since all implementations can handle messages + that have not been compressed. + +13.3.2. Hash Algorithm Preferences + + Typically, the choice of a hash algorithm is something the signer + does, rather than the verifier, because a signer rarely knows who is + going to be verifying the signature. This preference, though, allows + a protocol based upon digital signatures ease in negotiation. + + Thus, if Alice is authenticating herself to Bob with a signature, it + makes sense for her to use a hash algorithm that Bob's software uses. + This preference allows Bob to state in his key which algorithms Alice + may use. + + Since SHA1 is the MUST-implement hash algorithm, if it is not + explicitly in the list, it is tacitly at the end. However, it is + good form to place it there explicitly. + + + +Callas, et al Standards Track [Page 76] + +RFC 4880 OpenPGP Message Format November 2007 + + +13.4. Plaintext + + Algorithm 0, "plaintext", may only be used to denote secret keys that + are stored in the clear. Implementations MUST NOT use plaintext in + Symmetrically Encrypted Data packets; they must use Literal Data + packets to encode unencrypted or literal data. + +13.5. RSA + + There are algorithm types for RSA Sign-Only, and RSA Encrypt-Only + keys. These types are deprecated. The "key flags" subpacket in a + signature is a much better way to express the same idea, and + generalizes it to all algorithms. An implementation SHOULD NOT + create such a key, but MAY interpret it. + + An implementation SHOULD NOT implement RSA keys of size less than + 1024 bits. + +13.6. DSA + + An implementation SHOULD NOT implement DSA keys of size less than + 1024 bits. It MUST NOT implement a DSA key with a q size of less + than 160 bits. DSA keys MUST also be a multiple of 64 bits, and the + q size MUST be a multiple of 8 bits. The Digital Signature Standard + (DSS) [FIPS186] specifies that DSA be used in one of the following + ways: + + * 1024-bit key, 160-bit q, SHA-1, SHA-224, SHA-256, SHA-384, or + SHA-512 hash + + * 2048-bit key, 224-bit q, SHA-224, SHA-256, SHA-384, or SHA-512 + hash + + * 2048-bit key, 256-bit q, SHA-256, SHA-384, or SHA-512 hash + + * 3072-bit key, 256-bit q, SHA-256, SHA-384, or SHA-512 hash + + The above key and q size pairs were chosen to best balance the + strength of the key with the strength of the hash. Implementations + SHOULD use one of the above key and q size pairs when generating DSA + keys. If DSS compliance is desired, one of the specified SHA hashes + must be used as well. [FIPS186] is the ultimate authority on DSS, + and should be consulted for all questions of DSS compliance. + + Note that earlier versions of this standard only allowed a 160-bit q + with no truncation allowed, so earlier implementations may not be + able to handle signatures with a different q size or a truncated + hash. + + + +Callas, et al Standards Track [Page 77] + +RFC 4880 OpenPGP Message Format November 2007 + + +13.7. Elgamal + + An implementation SHOULD NOT implement Elgamal keys of size less than + 1024 bits. + +13.8. Reserved Algorithm Numbers + + A number of algorithm IDs have been reserved for algorithms that + would be useful to use in an OpenPGP implementation, yet there are + issues that prevent an implementer from actually implementing the + algorithm. These are marked in Section 9.1, "Public-Key Algorithms", + as "reserved for". + + The reserved public-key algorithms, Elliptic Curve (18), ECDSA (19), + and X9.42 (21), do not have the necessary parameters, parameter + order, or semantics defined. + + Previous versions of OpenPGP permitted Elgamal [ELGAMAL] signatures + with a public-key identifier of 20. These are no longer permitted. + An implementation MUST NOT generate such keys. An implementation + MUST NOT generate Elgamal signatures. See [BLEICHENBACHER]. + +13.9. OpenPGP CFB Mode + + OpenPGP does symmetric encryption using a variant of Cipher Feedback + mode (CFB mode). This section describes the procedure it uses in + detail. This mode is what is used for Symmetrically Encrypted Data + Packets; the mechanism used for encrypting secret-key material is + similar, and is described in the sections above. + + In the description below, the value BS is the block size in octets of + the cipher. Most ciphers have a block size of 8 octets. The AES and + Twofish have a block size of 16 octets. Also note that the + description below assumes that the IV and CFB arrays start with an + index of 1 (unlike the C language, which assumes arrays start with a + zero index). + + OpenPGP CFB mode uses an initialization vector (IV) of all zeros, and + prefixes the plaintext with BS+2 octets of random data, such that + octets BS+1 and BS+2 match octets BS-1 and BS. It does a CFB + resynchronization after encrypting those BS+2 octets. + + Thus, for an algorithm that has a block size of 8 octets (64 bits), + the IV is 10 octets long and octets 7 and 8 of the IV are the same as + octets 9 and 10. For an algorithm with a block size of 16 octets + (128 bits), the IV is 18 octets long, and octets 17 and 18 replicate + octets 15 and 16. Those extra two octets are an easy check for a + correct key. + + + +Callas, et al Standards Track [Page 78] + +RFC 4880 OpenPGP Message Format November 2007 + + + Step by step, here is the procedure: + + 1. The feedback register (FR) is set to the IV, which is all zeros. + + 2. FR is encrypted to produce FRE (FR Encrypted). This is the + encryption of an all-zero value. + + 3. FRE is xored with the first BS octets of random data prefixed to + the plaintext to produce C[1] through C[BS], the first BS octets + of ciphertext. + + 4. FR is loaded with C[1] through C[BS]. + + 5. FR is encrypted to produce FRE, the encryption of the first BS + octets of ciphertext. + + 6. The left two octets of FRE get xored with the next two octets of + data that were prefixed to the plaintext. This produces C[BS+1] + and C[BS+2], the next two octets of ciphertext. + + 7. (The resynchronization step) FR is loaded with C[3] through + C[BS+2]. + + 8. FR is encrypted to produce FRE. + + 9. FRE is xored with the first BS octets of the given plaintext, now + that we have finished encrypting the BS+2 octets of prefixed + data. This produces C[BS+3] through C[BS+(BS+2)], the next BS + octets of ciphertext. + + 10. FR is loaded with C[BS+3] to C[BS + (BS+2)] (which is C11-C18 for + an 8-octet block). + + 11. FR is encrypted to produce FRE. + + 12. FRE is xored with the next BS octets of plaintext, to produce + the next BS octets of ciphertext. These are loaded into FR, and + the process is repeated until the plaintext is used up. + +13.10. Private or Experimental Parameters + + S2K specifiers, Signature subpacket types, user attribute types, + image format types, and algorithms described in Section 9 all reserve + the range 100 to 110 for private and experimental use. Packet types + reserve the range 60 to 63 for private and experimental use. These + are intentionally managed with the PRIVATE USE method, as described + in [RFC2434]. + + + + +Callas, et al Standards Track [Page 79] + +RFC 4880 OpenPGP Message Format November 2007 + + + However, implementations need to be careful with these and promote + them to full IANA-managed parameters when they grow beyond the + original, limited system. + +13.11. Extension of the MDC System + + As described in the non-normative explanation in Section 5.13, the + MDC system is uniquely unparameterized in OpenPGP. This was an + intentional decision to avoid cross-grade attacks. If the MDC system + is extended to a stronger hash function, care must be taken to avoid + downgrade and cross-grade attacks. + + One simple way to do this is to create new packets for a new MDC. + For example, instead of the MDC system using packets 18 and 19, a new + MDC could use 20 and 21. This has obvious drawbacks (it uses two + packet numbers for each new hash function in a space that is limited + to a maximum of 60). + + Another simple way to extend the MDC system is to create new versions + of packet 18, and reflect this in packet 19. For example, suppose + that V2 of packet 18 implicitly used SHA-256. This would require + packet 19 to have a length of 32 octets. The change in the version + in packet 18 and the size of packet 19 prevent a downgrade attack. + + There are two drawbacks to this latter approach. The first is that + using the version number of a packet to carry algorithm information + is not tidy from a protocol-design standpoint. It is possible that + there might be several versions of the MDC system in common use, but + this untidiness would reflect untidiness in cryptographic consensus + about hash function security. The second is that different versions + of packet 19 would have to have unique sizes. If there were two + versions each with 256-bit hashes, they could not both have 32-octet + packet 19s without admitting the chance of a cross-grade attack. + + Yet another, complex approach to extend the MDC system would be a + hybrid of the two above -- create a new pair of MDC packets that are + fully parameterized, and yet protected from downgrade and cross- + grade. + + Any change to the MDC system MUST be done through the IETF CONSENSUS + method, as described in [RFC2434]. + +13.12. Meta-Considerations for Expansion + + If OpenPGP is extended in a way that is not backwards-compatible, + meaning that old implementations will not gracefully handle their + + + + + +Callas, et al Standards Track [Page 80] + +RFC 4880 OpenPGP Message Format November 2007 + + + absence of a new feature, the extension proposal can be declared in + the key holder's self-signature as part of the Features signature + subpacket. + + We cannot state definitively what extensions will not be upwards- + compatible, but typically new algorithms are upwards-compatible, + whereas new packets are not. + + If an extension proposal does not update the Features system, it + SHOULD include an explanation of why this is unnecessary. If the + proposal contains neither an extension to the Features system nor an + explanation of why such an extension is unnecessary, the proposal + SHOULD be rejected. + +14. Security Considerations + + * As with any technology involving cryptography, you should check the + current literature to determine if any algorithms used here have + been found to be vulnerable to attack. + + * This specification uses Public-Key Cryptography technologies. It + is assumed that the private key portion of a public-private key + pair is controlled and secured by the proper party or parties. + + * Certain operations in this specification involve the use of random + numbers. An appropriate entropy source should be used to generate + these numbers (see [RFC4086]). + + * The MD5 hash algorithm has been found to have weaknesses, with + collisions found in a number of cases. MD5 is deprecated for use + in OpenPGP. Implementations MUST NOT generate new signatures using + MD5 as a hash function. They MAY continue to consider old + signatures that used MD5 as valid. + + * SHA-224 and SHA-384 require the same work as SHA-256 and SHA-512, + respectively. In general, there are few reasons to use them + outside of DSS compatibility. You need a situation where one needs + more security than smaller hashes, but does not want to have the + full 256-bit or 512-bit data length. + + * Many security protocol designers think that it is a bad idea to use + a single key for both privacy (encryption) and integrity + (signatures). In fact, this was one of the motivating forces + behind the V4 key format with separate signature and encryption + keys. If you as an implementer promote dual-use keys, you should + at least be aware of this controversy. + + + + + +Callas, et al Standards Track [Page 81] + +RFC 4880 OpenPGP Message Format November 2007 + + + * The DSA algorithm will work with any hash, but is sensitive to the + quality of the hash algorithm. Verifiers should be aware that even + if the signer used a strong hash, an attacker could have modified + the signature to use a weak one. Only signatures using acceptably + strong hash algorithms should be accepted as valid. + + * As OpenPGP combines many different asymmetric, symmetric, and hash + algorithms, each with different measures of strength, care should + be taken that the weakest element of an OpenPGP message is still + sufficiently strong for the purpose at hand. While consensus about + the strength of a given algorithm may evolve, NIST Special + Publication 800-57 [SP800-57] recommends the following list of + equivalent strengths: + + Asymmetric | Hash | Symmetric + key size | size | key size + ------------+--------+----------- + 1024 160 80 + 2048 224 112 + 3072 256 128 + 7680 384 192 + 15360 512 256 + + * There is a somewhat-related potential security problem in + signatures. If an attacker can find a message that hashes to the + same hash with a different algorithm, a bogus signature structure + can be constructed that evaluates correctly. + + For example, suppose Alice DSA signs message M using hash algorithm + H. Suppose that Mallet finds a message M' that has the same hash + value as M with H'. Mallet can then construct a signature block + that verifies as Alice's signature of M' with H'. However, this + would also constitute a weakness in either H or H' or both. Should + this ever occur, a revision will have to be made to this document + to revise the allowed hash algorithms. + + * If you are building an authentication system, the recipient may + specify a preferred signing algorithm. However, the signer would + be foolish to use a weak algorithm simply because the recipient + requests it. + + * Some of the encryption algorithms mentioned in this document have + been analyzed less than others. For example, although CAST5 is + presently considered strong, it has been analyzed less than + TripleDES. Other algorithms may have other controversies + surrounding them. + + + + + +Callas, et al Standards Track [Page 82] + +RFC 4880 OpenPGP Message Format November 2007 + + + * In late summer 2002, Jallad, Katz, and Schneier published an + interesting attack on the OpenPGP protocol and some of its + implementations [JKS02]. In this attack, the attacker modifies a + message and sends it to a user who then returns the erroneously + decrypted message to the attacker. The attacker is thus using the + user as a random oracle, and can often decrypt the message. + + Compressing data can ameliorate this attack. The incorrectly + decrypted data nearly always decompresses in ways that defeat the + attack. However, this is not a rigorous fix, and leaves open some + small vulnerabilities. For example, if an implementation does not + compress a message before encryption (perhaps because it knows it + was already compressed), then that message is vulnerable. Because + of this happenstance -- that modification attacks can be thwarted + by decompression errors -- an implementation SHOULD treat a + decompression error as a security problem, not merely a data + problem. + + This attack can be defeated by the use of Modification Detection, + provided that the implementation does not let the user naively + return the data to the attacker. An implementation MUST treat an + MDC failure as a security problem, not merely a data problem. + + In either case, the implementation MAY allow the user access to the + erroneous data, but MUST warn the user as to potential security + problems should that data be returned to the sender. + + While this attack is somewhat obscure, requiring a special set of + circumstances to create it, it is nonetheless quite serious as it + permits someone to trick a user to decrypt a message. + Consequently, it is important that: + + 1. Implementers treat MDC errors and decompression failures as + security problems. + + 2. Implementers implement Modification Detection with all due + speed and encourage its spread. + + 3. Users migrate to implementations that support Modification + Detection with all due speed. + + * PKCS#1 has been found to be vulnerable to attacks in which a system + that reports errors in padding differently from errors in + decryption becomes a random oracle that can leak the private key in + mere millions of queries. Implementations must be aware of this + attack and prevent it from happening. The simplest solution is to + report a single error code for all variants of decryption errors so + as not to leak information to an attacker. + + + +Callas, et al Standards Track [Page 83] + +RFC 4880 OpenPGP Message Format November 2007 + + + * Some technologies mentioned here may be subject to government + control in some countries. + + * In winter 2005, Serge Mister and Robert Zuccherato from Entrust + released a paper describing a way that the "quick check" in OpenPGP + CFB mode can be used with a random oracle to decrypt two octets of + every cipher block [MZ05]. They recommend as prevention not using + the quick check at all. + + Many implementers have taken this advice to heart for any data that + is symmetrically encrypted and for which the session key is + public-key encrypted. In this case, the quick check is not needed + as the public-key encryption of the session key should guarantee + that it is the right session key. In other cases, the + implementation should use the quick check with care. + + On the one hand, there is a danger to using it if there is a random + oracle that can leak information to an attacker. In plainer + language, there is a danger to using the quick check if timing + information about the check can be exposed to an attacker, + particularly via an automated service that allows rapidly repeated + queries. + + On the other hand, it is inconvenient to the user to be informed + that they typed in the wrong passphrase only after a petabyte of + data is decrypted. There are many cases in cryptographic + engineering where the implementer must use care and wisdom, and + this is one. + +15. Implementation Nits + + This section is a collection of comments to help an implementer, + particularly with an eye to backward compatibility. Previous + implementations of PGP are not OpenPGP compliant. Often the + differences are small, but small differences are frequently more + vexing than large differences. Thus, this is a non-comprehensive + list of potential problems and gotchas for a developer who is trying + to be backward-compatible. + + * The IDEA algorithm is patented, and yet it is required for PGP + 2.x interoperability. It is also the de-facto preferred + algorithm for a V3 key with a V3 self-signature (or no self- + signature). + + * When exporting a private key, PGP 2.x generates the header "BEGIN + PGP SECRET KEY BLOCK" instead of "BEGIN PGP PRIVATE KEY BLOCK". + All previous versions ignore the implied data type, and look + directly at the packet data type. + + + +Callas, et al Standards Track [Page 84] + +RFC 4880 OpenPGP Message Format November 2007 + + + * PGP 2.0 through 2.5 generated V2 Public-Key packets. These are + identical to the deprecated V3 keys except for the version + number. An implementation MUST NOT generate them and may accept + or reject them as it sees fit. Some older PGP versions generated + V2 PKESK packets (Tag 1) as well. An implementation may accept + or reject V2 PKESK packets as it sees fit, and MUST NOT generate + them. + + * PGP 2.6.x will not accept key-material packets with versions + greater than 3. + + * There are many ways possible for two keys to have the same key + material, but different fingerprints (and thus Key IDs). Perhaps + the most interesting is an RSA key that has been "upgraded" to V4 + format, but since a V4 fingerprint is constructed by hashing the + key creation time along with other things, two V4 keys created at + different times, yet with the same key material will have + different fingerprints. + + * If an implementation is using zlib to interoperate with PGP 2.x, + then the "windowBits" parameter should be set to -13. + + * The 0x19 back signatures were not required for signing subkeys + until relatively recently. Consequently, there may be keys in + the wild that do not have these back signatures. Implementing + software may handle these keys as it sees fit. + + * OpenPGP does not put limits on the size of public keys. However, + larger keys are not necessarily better keys. Larger keys take + more computation time to use, and this can quickly become + impractical. Different OpenPGP implementations may also use + different upper bounds for public key sizes, and so care should + be taken when choosing sizes to maintain interoperability. As of + 2007 most implementations have an upper bound of 4096 bits. + + * ASCII armor is an optional feature of OpenPGP. The OpenPGP + working group strives for a minimal set of mandatory-to-implement + features, and since there could be useful implementations that + only use binary object formats, this is not a "MUST" feature for + an implementation. For example, an implementation that is using + OpenPGP as a mechanism for file signatures may find ASCII armor + unnecessary. OpenPGP permits an implementation to declare what + features it does and does not support, but ASCII armor is not one + of these. Since most implementations allow binary and armored + objects to be used indiscriminately, an implementation that does + not implement ASCII armor may find itself with compatibility + issues with general-purpose implementations. Moreover, + implementations of OpenPGP-MIME [RFC3156] already have a + + + +Callas, et al Standards Track [Page 85] + +RFC 4880 OpenPGP Message Format November 2007 + + + requirement for ASCII armor so those implementations will + necessarily have support. + +16. References + +16.1. Normative References + + [AES] NIST, FIPS PUB 197, "Advanced Encryption Standard + (AES)," November 2001. + http://csrc.nist.gov/publications/fips/fips197/fips- + 197.{ps,pdf} + + [BLOWFISH] Schneier, B. "Description of a New Variable-Length + Key, 64-Bit Block Cipher (Blowfish)" Fast Software + Encryption, Cambridge Security Workshop Proceedings + (December 1993), Springer-Verlag, 1994, pp191-204 + + + [BZ2] J. Seward, jseward@acm.org, "The Bzip2 and libbzip2 + home page" + + [ELGAMAL] T. Elgamal, "A Public-Key Cryptosystem and a + Signature Scheme Based on Discrete Logarithms," IEEE + Transactions on Information Theory, v. IT-31, n. 4, + 1985, pp. 469-472. + + [FIPS180] Secure Hash Signature Standard (SHS) (FIPS PUB 180- + 2). + + + [FIPS186] Digital Signature Standard (DSS) (FIPS PUB 186-2). + FIPS 186-3 describes keys + greater than 1024 bits. The latest draft is at: + + + [HAC] Alfred Menezes, Paul van Oorschot, and Scott + Vanstone, "Handbook of Applied Cryptography," CRC + Press, 1996. + + + [IDEA] Lai, X, "On the design and security of block + ciphers", ETH Series in Information Processing, J.L. + Massey (editor), Vol. 1, Hartung-Gorre Verlag + Knostanz, Technische Hochschule (Zurich), 1992 + + + + +Callas, et al Standards Track [Page 86] + +RFC 4880 OpenPGP Message Format November 2007 + + + [ISO10646] ISO/IEC 10646-1:1993. International Standard -- + Information technology -- Universal Multiple-Octet + Coded Character Set (UCS) -- Part 1: Architecture + and Basic Multilingual Plane. + + [JFIF] JPEG File Interchange Format (Version 1.02). Eric + Hamilton, C-Cube Microsystems, Milpitas, CA, + September 1, 1992. + + [RFC1950] Deutsch, P. and J-L. Gailly, "ZLIB Compressed Data + Format Specification version 3.3", RFC 1950, May + 1996. + + [RFC1951] Deutsch, P., "DEFLATE Compressed Data Format + Specification version 1.3", RFC 1951, May 1996. + + [RFC2045] Freed, N. and N. Borenstein, "Multipurpose Internet + Mail Extensions (MIME) Part One: Format of Internet + Message Bodies", RFC 2045, November 1996 + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RFC2144] Adams, C., "The CAST-128 Encryption Algorithm", RFC + 2144, May 1997. + + [RFC2434] Narten, T. and H. Alvestrand, "Guidelines for + Writing an IANA Considerations Section in RFCs", BCP + 26, RFC 2434, October 1998. + + [RFC2822] Resnick, P., "Internet Message Format", RFC 2822, + April 2001. + + [RFC3156] Elkins, M., Del Torto, D., Levien, R., and T. + Roessler, "MIME Security with OpenPGP", RFC 3156, + August 2001. + + [RFC3447] Jonsson, J. and B. Kaliski, "Public-Key Cryptography + Standards (PKCS) #1: RSA Cryptography Specifications + Version 2.1", RFC 3447, February 2003. + + [RFC3629] Yergeau, F., "UTF-8, a transformation format of ISO + 10646", STD 63, RFC 3629, November 2003. + + [RFC4086] Eastlake, D., 3rd, Schiller, J., and S. Crocker, + "Randomness Requirements for Security", BCP 106, RFC + 4086, June 2005. + + + + +Callas, et al Standards Track [Page 87] + +RFC 4880 OpenPGP Message Format November 2007 + + + [SCHNEIER] Schneier, B., "Applied Cryptography Second Edition: + protocols, algorithms, and source code in C", 1996. + + [TWOFISH] B. Schneier, J. Kelsey, D. Whiting, D. Wagner, C. + Hall, and N. Ferguson, "The Twofish Encryption + Algorithm", John Wiley & Sons, 1999. + +16.2. Informative References + + [BLEICHENBACHER] Bleichenbacher, Daniel, "Generating Elgamal + signatures without knowing the secret key," + Eurocrypt 96. Note that the version in the + proceedings has an error. A revised version is + available at the time of writing from + + + [JKS02] Kahil Jallad, Jonathan Katz, Bruce Schneier + "Implementation of Chosen-Ciphertext Attacks against + PGP and GnuPG" http://www.counterpane.com/pgp- + attack.html + + [MAURER] Ueli Maurer, "Modelling a Public-Key + Infrastructure", Proc. 1996 European Symposium on + Research in Computer Security (ESORICS' 96), Lecture + Notes in Computer Science, Springer-Verlag, vol. + 1146, pp. 325-350, Sep 1996. + + [MZ05] Serge Mister, Robert Zuccherato, "An Attack on CFB + Mode Encryption As Used By OpenPGP," IACR ePrint + Archive: Report 2005/033, 8 Feb 2005 + http://eprint.iacr.org/2005/033 + + [REGEX] Jeffrey Friedl, "Mastering Regular Expressions," + O'Reilly, ISBN 0-596-00289-0. + + [RFC1423] Balenson, D., "Privacy Enhancement for Internet + Electronic Mail: Part III: Algorithms, Modes, and + Identifiers", RFC 1423, February 1993. + + [RFC1991] Atkins, D., Stallings, W., and P. Zimmermann, "PGP + Message Exchange Formats", RFC 1991, August 1996. + + [RFC2440] Callas, J., Donnerhacke, L., Finney, H., and R. + Thayer, "OpenPGP Message Format", RFC 2440, November + 1998. + + + + + +Callas, et al Standards Track [Page 88] + +RFC 4880 OpenPGP Message Format November 2007 + + + [SP800-57] NIST Special Publication 800-57, Recommendation on + Key Management + + + +Acknowledgements + + This memo also draws on much previous work from a number of other + authors, including: Derek Atkins, Charles Breed, Dave Del Torto, Marc + Dyksterhouse, Gail Haspert, Gene Hoffman, Paul Hoffman, Ben Laurie, + Raph Levien, Colin Plumb, Will Price, David Shaw, William Stallings, + Mark Weaver, and Philip R. Zimmermann. + +Authors' Addresses + + The working group can be contacted via the current chair: + + Derek Atkins + IHTFP Consulting, Inc. + 4 Farragut Ave + Somerville, MA 02144 USA + + EMail: derek@ihtfp.com + Tel: +1 617 623 3745 + + The principal authors of this document are as follows: + + Jon Callas + EMail: jon@callas.org + + Lutz Donnerhacke + IKS GmbH + Wildenbruchstr. 15 + 07745 Jena, Germany + EMail: lutz@iks-jena.de + + Hal Finney + EMail: hal@finney.org + + David Shaw + EMail: dshaw@jabberwocky.com + + Rodney Thayer + EMail: rodney@canola-jones.com + + + + + +Callas, et al Standards Track [Page 89] + +RFC 4880 OpenPGP Message Format November 2007 + + +Full Copyright Statement + + Copyright (C) The IETF Trust (2007). + + This document is subject to the rights, licenses and restrictions + contained in BCP 78, and except as set forth therein, the authors + retain all their rights. + + This document and the information contained herein are provided on an + "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS + OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE IETF TRUST AND + THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF + THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED + WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Intellectual Property + + The IETF takes no position regarding the validity or scope of any + Intellectual Property Rights or other rights that might be claimed to + pertain to the implementation or use of the technology described in + this document or the extent to which any license under such rights + might or might not be available; nor does it represent that it has + made any independent effort to identify any such rights. Information + on the procedures with respect to rights in RFC documents can be + found in BCP 78 and BCP 79. + + Copies of IPR disclosures made to the IETF Secretariat and any + assurances of licenses to be made available, or the result of an + attempt made to obtain a general license or permission for the use of + such proprietary rights by implementers or users of this + specification can be obtained from the IETF on-line IPR repository at + http://www.ietf.org/ipr. + + The IETF invites any interested party to bring to its attention any + copyrights, patents or patent applications, or other proprietary + rights that may cover technology that may be required to implement + this standard. Please address the information to the IETF at + ietf-ipr@ietf.org. + + + + + + + + + + + + +Callas, et al Standards Track [Page 90] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4954.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4954.txt new file mode 100644 index 0000000..668d738 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4954.txt @@ -0,0 +1,1123 @@ + + + + + + +Network Working Group R. Siemborski, Ed. +Request for Comments: 4954 Google, Inc. +Obsoletes: 2554 A. Melnikov, Ed. +Updates: 3463 Isode Limited +Category: Standards Track July 2007 + + + SMTP Service Extension for Authentication + +Status of This Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The IETF Trust (2007). + +Abstract + + This document defines a Simple Mail Transport Protocol (SMTP) + extension whereby an SMTP client may indicate an authentication + mechanism to the server, perform an authentication protocol exchange, + and optionally negotiate a security layer for subsequent protocol + interactions during this session. This extension includes a profile + of the Simple Authentication and Security Layer (SASL) for SMTP. + + This document obsoletes RFC 2554. + + + + + + + + + + + + + + + + + + + + +Siemborski & Melnikov Standards Track [Page 1] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + +Table of Contents + + 1. Introduction ....................................................2 + 2. How to Read This Document .......................................2 + 3. The Authentication Service Extension ............................3 + 4. The AUTH Command ................................................3 + 4.1. Examples ...................................................7 + 5. The AUTH Parameter to the MAIL FROM command .....................9 + 5.1. Examples ..................................................10 + 6. Status Codes ...................................................11 + 7. Additional requirements on servers .............................12 + 8. Formal Syntax ..................................................13 + 9. Security Considerations ........................................14 + 10. IANA Considerations ...........................................15 + 11. Normative References ..........................................15 + 12. Informative References ........................................16 + 13. Acknowledgments ...............................................17 + 14. Additional Requirements When Using SASL PLAIN over TLS ........17 + 15. Changes since RFC 2554 ........................................18 + +1. Introduction + + This document defines a Simple Mail Transport Protocol (SMTP) + extension whereby an SMTP client may indicate an authentication + mechanism to the server, perform an authentication protocol exchange, + optionally negotiate a security layer for subsequent protocol + interactions during this session and, during a mail transaction, + optionally specify a mailbox associated with the identity that + submitted the message to the mail delivery system. + + This extension includes a profile of the Simple Authentication and + Security Layer (SASL) for SMTP. + + When compared to RFC 2554, this document deprecates use of the 538 + response code, adds a new Enhanced Status Code, adds a requirement to + support SASLprep profile for preparing authorization identities, + recommends use of RFC 3848 transmission types in the Received trace + header field, and clarifies interaction with SMTP PIPELINING + [PIPELINING] extension. + +2. How to Read This Document + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [KEYWORDS]. + + In examples, "C:" and "S:" indicate lines sent by the client and + server, respectively. + + + +Siemborski & Melnikov Standards Track [Page 2] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + +3. The Authentication Service Extension + + 1. The name of this [SMTP] service extension is "Authentication". + + 2. The EHLO keyword value associated with this extension is "AUTH". + + 3. The AUTH EHLO keyword contains as a parameter a space-separated + list of the names of available [SASL] mechanisms. The list of + available mechanisms MAY change after a successful STARTTLS + command [SMTP-TLS]. + + 4. A new [SMTP] verb "AUTH" is defined. + + 5. An optional parameter using the keyword "AUTH" is added to the + MAIL FROM command, and extends the maximum line length of the + MAIL FROM command by 500 characters. + + 6. This extension is appropriate for the submission protocol + [SUBMIT]. + +4. The AUTH Command + + AUTH mechanism [initial-response] + + Arguments: + mechanism: A string identifying a [SASL] authentication + mechanism. + + initial-response: An optional initial client response. If + present, this response MUST be encoded as described in Section + 4 of [BASE64] or contain a single character "=". + + Restrictions: + After an AUTH command has been successfully completed, no more + AUTH commands may be issued in the same session. After a + successful AUTH command completes, a server MUST reject any + further AUTH commands with a 503 reply. + + The AUTH command is not permitted during a mail transaction. + An AUTH command issued during a mail transaction MUST be + rejected with a 503 reply. + + Discussion: + The AUTH command initiates a [SASL] authentication exchange + between the client and the server. The client identifies the + SASL mechanism to use with the first parameter of the AUTH + command. If the server supports the requested authentication + mechanism, it performs the SASL exchange to authenticate the + + + +Siemborski & Melnikov Standards Track [Page 3] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + user. Optionally, it also negotiates a security layer for + subsequent protocol interactions during this session. If the + requested authentication mechanism is invalid (e.g., is not + supported or requires an encryption layer), the server rejects + the AUTH command with a 504 reply. If the server supports the + [ESMTP-CODES] extension, it SHOULD return a 5.5.4 enhanced + response code. + + The SASL authentication exchange consists of a series of + server challenges and client responses that are specific to + the chosen [SASL] mechanism. + + A server challenge is sent as a 334 reply with the text part + containing the [BASE64] encoded string supplied by the SASL + mechanism. This challenge MUST NOT contain any text other + than the BASE64 encoded challenge. + + A client response consists of a line containing a [BASE64] + encoded string. If the client wishes to cancel the + authentication exchange, it issues a line with a single "*". + If the server receives such a response, it MUST reject the + AUTH command by sending a 501 reply. + + The optional initial response argument to the AUTH command is + used to save a round-trip when using authentication mechanisms + that support an initial client response. If the initial + response argument is omitted and the chosen mechanism requires + an initial client response, the server MUST proceed as defined + in Section 5.1 of [SASL]. In SMTP, a server challenge that + contains no data is defined as a 334 reply with no text part. + Note that there is still a space following the reply code, so + the complete response line is "334 ". + + Note that the AUTH command is still subject to the line length + limitations defined in [SMTP]. If use of the initial response + argument would cause the AUTH command to exceed this length, + the client MUST NOT use the initial response parameter (and + instead proceed as defined in Section 5.1 of [SASL]). + + If the client is transmitting an initial response of zero + length, it MUST instead transmit the response as a single + equals sign ("="). This indicates that the response is + present, but contains no data. + + If the client uses an initial-response argument to the AUTH + command with a SASL mechanism in which the client does not + begin the authentication exchange, the server MUST reject the + + + + +Siemborski & Melnikov Standards Track [Page 4] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + AUTH command with a 501 reply. Servers using the enhanced + status codes extension [ESMTP-CODES] SHOULD return an enhanced + status code of 5.7.0 in this case. + + If the server cannot [BASE64] decode any client response, it + MUST reject the AUTH command with a 501 reply (and an enhanced + status code of 5.5.2). If the client cannot BASE64 decode any + of the server's challenges, it MUST cancel the authentication + using the "*" response. In particular, servers and clients + MUST reject (and not ignore) any character not explicitly + allowed by the BASE64 alphabet, and MUST reject any sequence + of BASE64 characters that contains the pad character ('=') + anywhere other than the end of the string (e.g., "=AAA" and + "AAA=BBB" are not allowed). + + Note that these [BASE64] strings can be much longer than + normal SMTP commands. Clients and servers MUST be able to + handle the maximum encoded size of challenges and responses + generated by their supported authentication mechanisms. This + requirement is independent of any line length limitations the + client or server may have in other parts of its protocol + implementation. (At the time of writing of this document, + 12288 octets is considered to be a sufficient line length + limit for handling of deployed authentication mechanisms.) + If, during an authentication exchange, the server receives a + line that is longer than the server's authentication buffer, + the server fails the AUTH command with the 500 reply. Servers + using the enhanced status codes extension [ESMTP-CODES] SHOULD + return an enhanced status code of 5.5.6 in this case. + + The authorization identity generated by this [SASL] exchange + is a "simple username" (in the sense defined in [SASLprep]), + and both client and server SHOULD (*) use the [SASLprep] + profile of the [StringPrep] algorithm to prepare these names + for transmission or comparison. If preparation of the + authorization identity fails or results in an empty string + (unless it was transmitted as the empty string), the server + MUST fail the authentication. + + (*) Note: Future revision of this specification may change this + requirement to MUST. Currently, the SHOULD is used in order to + avoid breaking the majority of existing implementations. + + If the server is unable to authenticate the client, it SHOULD reject + the AUTH command with a 535 reply unless a more specific error code + is appropriate. Should the client successfully complete the + exchange, the SMTP server issues a 235 reply. (Note that the SMTP + protocol doesn't support the SASL feature of returning additional + + + +Siemborski & Melnikov Standards Track [Page 5] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + data with a successful outcome.) These status codes, along with + others defined by this extension, are discussed in Section 6 of this + document. + + If a security layer is negotiated during the SASL exchange, it takes + effect for the client on the octet immediately following the CRLF + that concludes the last response generated by the client. For the + server, it takes effect immediately following the CRLF of its success + reply. + + When a security layer takes effect, the SMTP protocol is reset to the + initial state (the state in SMTP after a server issues a 220 service + ready greeting). The server MUST discard any knowledge obtained from + the client, such as the EHLO argument, which was not obtained from + the SASL negotiation itself. Likewise, the client MUST discard any + knowledge obtained from the server, such as the list of SMTP service + extensions, which was not obtained from the SASL negotiation itself. + (Note that a client MAY compare the advertised SASL mechanisms before + and after authentication in order to detect an active down- + negotiation attack). + + The client SHOULD send an EHLO command as the first command after a + successful SASL negotiation that results in the enabling of a + security layer. + + When an entity (whether it is the client or the server end) is + sending data, and both [TLS] and SASL security layers are in effect, + the TLS encoding MUST be applied after the SASL encoding, regardless + of the order in which the layers were negotiated. + + The service name specified by this protocol's profile of SASL is + "smtp". This service name is also to be used for the [SUBMIT] + protocol. + + If an AUTH command fails, the client MAY proceed without + authentication. Alternatively, the client MAY try another + authentication mechanism or present different credentials by issuing + another AUTH + + Note: A server implementation MUST implement a configuration in which + it does NOT permit any plaintext password mechanisms, unless either + the STARTTLS [SMTP-TLS] command has been negotiated or some other + mechanism that protects the session from password snooping has been + provided. Server sites SHOULD NOT use any configuration which + permits a plaintext password mechanism without such a protection + mechanism against password snooping. + + + + + +Siemborski & Melnikov Standards Track [Page 6] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + To ensure interoperability, client and server implementations of this + extension MUST implement the [PLAIN] SASL mechanism running over TLS + [TLS] [SMTP-TLS]. See also Section 15 for additional requirements on + implementations of [PLAIN] over [TLS]. + + Note that many existing client and server implementations implement + CRAM-MD5 [CRAM-MD5] SASL mechanism. In order to ensure + interoperability with deployed software, new implementations MAY + implement it; however, implementations should be aware that this SASL + mechanism doesn't provide any server authentication. Note that at + the time of writing of this document the SASL Working Group is + working on several replacement SASL mechanisms that provide server + authentication and other features. + + When the AUTH command is used together with the [PIPELINING] + extension, it MUST be the last command in a pipelined group of + commands. The only exception to this rule is when the AUTH command + contains an initial response for a SASL mechanism that allows the + client to send data first, the SASL mechanism is known to complete in + one round-trip, and a security layer is not negotiated by the client. + Two examples of such SASL mechanisms are PLAIN [PLAIN] and EXTERNAL + [SASL]. + +4.1. Examples + + Here is an example of a client attempting AUTH using the [PLAIN] SASL + mechanism under a TLS layer, and making use of the initial client + response: + + S: 220-smtp.example.com ESMTP Server + C: EHLO client.example.com + S: 250-smtp.example.com Hello client.example.com + S: 250-AUTH GSSAPI DIGEST-MD5 + S: 250-ENHANCEDSTATUSCODES + S: 250 STARTTLS + C: STARTTLS + S: 220 Ready to start TLS + ... TLS negotiation proceeds, further commands + protected by TLS layer ... + C: EHLO client.example.com + S: 250-smtp.example.com Hello client.example.com + S: 250 AUTH GSSAPI DIGEST-MD5 PLAIN + C: AUTH PLAIN dGVzdAB0ZXN0ADEyMzQ= + S: 235 2.7.0 Authentication successful + + Here is another client that is attempting AUTH PLAIN under a TLS + layer, this time without the initial response. Parts of the + negotiation before the TLS layer was established have been omitted: + + + +Siemborski & Melnikov Standards Track [Page 7] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + ... TLS negotiation proceeds, further commands + protected by TLS layer ... + C: EHLO client.example.com + S: 250-smtp.example.com Hello client.example.com + S: 250 AUTH GSSAPI DIGEST-MD5 PLAIN + C: AUTH PLAIN + (note: there is a single space following the 334 + on the following line) + S: 334 + C: dGVzdAB0ZXN0ADEyMzQ= + S: 235 2.7.0 Authentication successful + + Here is an example using CRAM-MD5 [CRAM-MD5], a mechanism in which + the client does not begin the authentication exchange, and includes a + server challenge: + + S: 220-smtp.example.com ESMTP Server + C: EHLO client.example.com + S: 250-smtp.example.com Hello client.example.com + S: 250-AUTH DIGEST-MD5 CRAM-MD5 + S: 250-ENHANCEDSTATUSCODES + S: 250 STARTTLS + C: AUTH CRAM-MD5 + S: 334 PDQxOTI5NDIzNDEuMTI4Mjg0NzJAc291cmNlZm91ci5hbmRyZXcuY211LmVk + dT4= + C: cmpzMyBlYzNhNTlmZWQzOTVhYmExZWM2MzY3YzRmNGI0MWFjMA== + S: 235 2.7.0 Authentication successful + + Here is an example of a client attempting AUTH EXTERNAL under TLS, + using the derived authorization ID (and thus a zero-length initial + client response). + + S: 220-smtp.example.com ESMTP Server + C: EHLO client.example.com + S: 250-smtp.example.com Hello client.example.com + S: 250-AUTH GSSAPI DIGEST-MD5 + S: 250-ENHANCEDSTATUSCODES + S: 250 STARTTLS + C: STARTTLS + S: 220 Ready to start TLS + ... TLS negotiation proceeds, further commands + protected by TLS layer ... + C: EHLO client.example.com + S: 250-smtp.example.com Hello client.example.com + S: 250 AUTH EXTERNAL GSSAPI DIGEST-MD5 PLAIN + C: AUTH EXTERNAL = + S: 235 2.7.0 Authentication successful + + + + +Siemborski & Melnikov Standards Track [Page 8] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + +5. The AUTH Parameter to the MAIL FROM command + + AUTH=mailbox + + Arguments: + A (see Section 4.1.2 of [SMTP]) that is associated + with the identity that submitted the message to the delivery + system, or the two character sequence "<>" indicating such an + identity is unknown or insufficiently authenticated. To comply + with restrictions imposed on ESMTP parameters, the is + encoded inside an xtext. The syntax of an xtext is described in + Section 4 of [ESMTP-DSN]. + + Note: + For the purposes of this discussion, "authenticated identity" + refers to the identity (if any) derived from the authorization + identity of previous AUTH command, while the terms "authorized + identity" and "supplied " refer to the sender identity + that is being associated with a particular message. Note that + one authenticated identity may be able to identify messages as + being sent by any number of authorized identities within a + single session. For example, this may be the case when an SMTP + server (one authenticated identity) is processing its queue + (many messages with distinct authorized identities). + + Discussion: + The optional AUTH parameter to the MAIL FROM command allows + cooperating agents in a trusted environment to communicate the + authorization identity associated with individual messages. + + If the server trusts the authenticated identity of the client to + assert that the message was originally submitted by the supplied + , then the server SHOULD supply the same in + an AUTH parameter when relaying the message to any other server + which supports the AUTH extension. + + For this reason, servers that advertise support for this + extension MUST support the AUTH parameter to the MAIL FROM + command even when the client has not authenticated itself to the + server. + + A MAIL FROM parameter of AUTH=<> indicates that the original + submitter of the message is not known. The server MUST NOT + treat the message as having been originally submitted by the + authenticated identity that resulted from the AUTH command. + + + + + + +Siemborski & Melnikov Standards Track [Page 9] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + If the AUTH parameter to the MAIL FROM command is not supplied, + the client has authenticated, and the server believes the + message is an original submission, the server MAY generate a + from the user's authenticated identity for use in an + AUTH parameter when relaying the message to any server which + supports the AUTH extension. The generated is + implementation specific, but it MUST conform to the syntax of + [SMTP]. If the implementation cannot generate a valid + , it MUST transmit AUTH=<> when relaying this message. + + If the server does not sufficiently trust the authenticated + identity of the client, or if the client is not authenticated, + then the server MUST behave as if the AUTH=<> parameter was + supplied. The server MAY, however, write the value of any + supplied AUTH parameter to a log file. + + If an AUTH=<> parameter was supplied, either explicitly or due + to the requirement in the previous paragraph, then the server + MUST supply the AUTH=<> parameter when relaying the message to + any server which it has authenticated to using the AUTH + extension. + + A server MAY treat expansion of a mailing list as a new + submission, setting the AUTH parameter to the mailing list + address or mailing list administration address when relaying the + message to list subscribers. + + Note that an implementation which is hard-coded to treat all + clients as being insufficiently trusted is compliant with this + specification. In that case, the implementation does nothing + more than parse and discard syntactically valid AUTH parameters + to the MAIL FROM command, and supply AUTH=<> parameters to any + servers that it authenticates to. + +5.1. Examples + + An example where the original identity of the sender is trusted and + known: + + C: MAIL FROM: AUTH=e+3Dmc2@example.com + S: 250 OK + + One example where the identity of the sender is not trusted or is + otherwise being suppressed by the client: + + C: MAIL FROM: AUTH=<> + S: 250 OK + + + + +Siemborski & Melnikov Standards Track [Page 10] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + +6. Status Codes + + The following error codes may be used to indicate various success or + failure conditions. Servers that return enhanced status codes + [ESMTP-CODES] SHOULD use the enhanced codes suggested here. + + 235 2.7.0 Authentication Succeeded + + This response to the AUTH command indicates that the authentication + was successful. + + 432 4.7.12 A password transition is needed + + This response to the AUTH command indicates that the user needs to + transition to the selected authentication mechanism. This is + typically done by authenticating once using the [PLAIN] + authentication mechanism. The selected mechanism SHOULD then work + for authentications in subsequent sessions. + + 454 4.7.0 Temporary authentication failure + + This response to the AUTH command indicates that the authentication + failed due to a temporary server failure. The client SHOULD NOT + prompt the user for another password in this case, and should instead + notify the user of server failure. + + 534 5.7.9 Authentication mechanism is too weak + + This response to the AUTH command indicates that the selected + authentication mechanism is weaker than server policy permits for + that user. The client SHOULD retry with a new authentication + mechanism. + + 535 5.7.8 Authentication credentials invalid + + This response to the AUTH command indicates that the authentication + failed due to invalid or insufficient authentication credentials. In + this case, the client SHOULD ask the user to supply new credentials + (such as by presenting a password dialog box). + + 500 5.5.6 Authentication Exchange line is too long + + This response to the AUTH command indicates that the authentication + failed due to the client sending a [BASE64] response that is longer + than the maximum buffer size available for the currently selected + SASL mechanism. + + + + + +Siemborski & Melnikov Standards Track [Page 11] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + 530 5.7.0 Authentication required + + This response SHOULD be returned by any command other than AUTH, + EHLO, HELO, NOOP, RSET, or QUIT when server policy requires + authentication in order to perform the requested action and + authentication is not currently in force. + + 538 5.7.11 Encryption required for requested authentication + mechanism + + This response to the AUTH command indicates that the selected + authentication mechanism may only be used when the underlying SMTP + connection is encrypted. Note that this response code is documented + here for historical purposes only. Modern implementations SHOULD NOT + advertise mechanisms that are not permitted due to lack of + encryption, unless an encryption layer of sufficient strength is + currently being employed. + + This document adds several new enhanced status codes to the list + defined in [ENHANCED]: + + The following 3 Enhanced Status Codes were defined above: + + 5.7.8 Authentication credentials invalid + 5.7.9 Authentication mechanism is too weak + 5.7.11 Encryption required for requested authentication mechanism + + X.5.6 Authentication Exchange line is too long + + This enhanced status code SHOULD be returned when the server fails + the AUTH command due to the client sending a [BASE64] response which + is longer than the maximum buffer size available for the currently + selected SASL mechanism. This is useful for both permanent and + persistent transient errors. + +7. Additional Requirements on Servers + + As described in Section 4.4 of [SMTP], an SMTP server that receives a + message for delivery or further processing MUST insert the + "Received:" header field at the beginning of the message content. + This document places additional requirements on the content of a + generated "Received:" header field. Upon successful authentication, + a server SHOULD use the "ESMTPA" or the "ESMTPSA" [SMTP-TT] (when + appropriate) keyword in the "with" clause of the Received header + field. + + + + + + +Siemborski & Melnikov Standards Track [Page 12] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + +8. Formal Syntax + + The following syntax specification uses the Augmented Backus-Naur + Form notation as specified in [ABNF]. Non-terminals referenced but + not defined below are as defined by [ABNF] or [SASL]. The non- + terminal is defined in [SMTP]. + + Except as noted otherwise, all alphabetic characters are case- + insensitive. The use of upper or lower case characters to define + token strings is for editorial clarity only. Implementations MUST + accept these strings in a case-insensitive fashion. + + hexchar = "+" HEXDIG HEXDIG + + xchar = %x21-2A / %x2C-3C / %x3E-7E + ;; US-ASCII except for "+", "=", SP, and CTL + + xtext = *(xchar / hexchar) + ;; non-US-ASCII is only allowed as hexchar + + auth-command = "AUTH" SP sasl-mech [SP initial-response] + *(CRLF [base64]) [CRLF cancel-response] + CRLF + ;; is defined in [SASL] + + auth-param = "AUTH=" xtext + ;; Parameter to the MAIL FROM command. + ;; This non-terminal complies with + ;; syntax defined by esmtp-param [SMTP]. + ;; + ;; The decoded form of the xtext MUST be + ;; either a or the two + ;; characters "<>" + + base64 = base64-terminal / + ( 1*(4base64-char) [base64-terminal] ) + + base64-char = ALPHA / DIGIT / "+" / "/" + ;; Case-sensitive + + base64-terminal = (2base64-char "==") / (3base64-char "=") + + continue-req = "334" SP [base64] CRLF + ;; Intermediate response to the AUTH + ;; command. + ;; This non-terminal complies with + ;; syntax defined by Reply-line [SMTP]. + + + + +Siemborski & Melnikov Standards Track [Page 13] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + initial-response= base64 / "=" + + cancel-response = "*" + +9. Security Considerations + + Security issues are discussed throughout this memo. + + If a client uses this extension to get an encrypted tunnel through an + insecure network to a cooperating server, it needs to be configured + to never send mail to that server when the connection is not mutually + authenticated and encrypted. Otherwise, an attacker could steal the + client's mail by hijacking the [SMTP] connection and either + pretending the server does not support the Authentication extension + or causing all AUTH commands to fail. + + Before the [SASL] negotiation has begun, any protocol interactions + are performed in the clear and may be modified by an active attacker. + For this reason, clients and servers MUST discard any knowledge + obtained prior to the start of the SASL negotiation upon the + establishment of a security layer. + + This mechanism does not protect the TCP port, so an active attacker + may redirect a relay connection attempt (i.e., a connection between + two Mail Transfer Agents (MTAs)) to the submission port [SUBMIT]. + The AUTH=<> parameter prevents such an attack from causing a relayed + message and, in the absence of other envelope authentication, from + picking up the authentication of the relay client. + + A message submission client may require the user to authenticate + whenever a suitable [SASL] mechanism is advertised. Therefore, it + may not be desirable for a submission server [SUBMIT] to advertise a + SASL mechanism when use of that mechanism grants the clients no + benefits over anonymous submission. + + Servers MAY implement a policy whereby the connection is dropped + after a number of failed authentication attempts. If they do so, + they SHOULD NOT drop the connection until at least 3 attempts to + authenticate have failed. + + If an implementation supports SASL mechanisms that are vulnerable to + passive eavesdropping attacks (such as [PLAIN]), then the + implementation MUST support at least one configuration where these + SASL mechanisms are not advertised or used without the presence of an + external security layer such as [TLS]. + + + + + + +Siemborski & Melnikov Standards Track [Page 14] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + This extension is not intended to replace or be used instead of end- + to-end message signature and encryption systems such as [S/MIME] or + [PGP]. This extension addresses a different problem than end-to-end + systems; it has the following key differences: + + 1. It is generally useful only within a trusted enclave. + + 2. It protects the entire envelope of a message, not just the + message's body. + + 3. It authenticates the message submission, not authorship of the + message content. + + 4. When mutual authentication is used along with a security layer, + it can give the sender some assurance that the message was + successfully delivered to the next hop. + + Additional security considerations are mentioned in the [SASL] + specification. Additional security considerations specific to a + particular SASL mechanism are described in the relevant + specification. Additional security considerations for [PLAIN] over + [TLS] are mentioned in Section 15 of this document. + +10. IANA Considerations + + IANA updated the entry for the "smtp" SASL protocol name to point at + this document. + + IANA updated the registration of the Authentication SMTP service + extension as defined in Section 3 of this document. This registry is + currently located at . + +11. Normative References + + [ABNF] Crocker, D. and P. Overell, "Augmented BNF for Syntax + Specifications: ABNF", RFC 4234, October 2005. + + [BASE64] Josefsson, S., "The Base16, Base32, and Base64 Data + Encodings", RFC 4648, October 2006. + + [ESMTP-CODES] Freed, N., "SMTP Service Extension for Returning + Enhanced Error Codes", RFC 2034, October 1996. + + [ENHANCED] Vaudreuil, G., "Enhanced Mail System Status Codes", RFC + 3463, January 2003. + + + + + +Siemborski & Melnikov Standards Track [Page 15] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + [ESMTP-DSN] Moore, K., "Simple Mail Transfer Protocol (SMTP) + Service Extension Delivery Status Notifications + (DSNs)", RFC 3461, January 2003. + + [KEYWORDS] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [SASL] Melnikov, A. and K. Zeilenga, "Simple Authentication + and Security Layer (SASL)", RFC 4422, June 2006. + + [SASLprep] Zeilenga, K., "SASLprep: Stringprep Profile for User + Names and Passwords", RFC 4013, February 2005. + + [SMTP] Klensin, J., "Simple Mail Transfer Protocol", RFC 2821, + April 2001. + + [SMTP-TLS] Hoffman, P., "SMTP Service Extension for Secure SMTP + over Transport Layer Security", RFC 3207, February + 2002. + + [StringPrep] Hoffman, P. and M. Blanchet, "Preparation of + Internationalized Strings ("stringprep")", RFC 3454, + December 2002. + + [SUBMIT] Gellens, R. and J. Klensin, "Message Submission for + Mail", RFC 4409, April 2006. + + [SMTP-TT] Newman, C., "ESMTP and LMTP Transmission Types + Registration", RFC 3848, July 2004. + + [PLAIN] Zeilenga, K., Ed., "The PLAIN Simple Authentication and + Security Layer (SASL) Mechanism", RFC 4616, August + 2006. + + [X509] Housley, R., Polk, W., Ford, W., and D. Solo, "Internet + X.509 Public Key Infrastructure Certificate and + Certificate Revocation List (CRL) Profile", RFC 3280, + April 2002. + +12. Informative References + + [PGP] Elkins, M., "MIME Security with Pretty Good Privacy + (PGP)", RFC 2015, October 1996. + + [S/MIME] Ramsdell, B., Ed., "Secure/Multipurpose Internet Mail + Extensions (S/MIME) Version 3.1 Message Specification", + RFC 3851, July 2004. + + + + +Siemborski & Melnikov Standards Track [Page 16] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + [TLS] Dierks, T. and E. Rescorla, "The Transport Layer + Security (TLS) Protocol Version 1.1", RFC 4346, April + 2006. + + [PIPELINING] Freed, N., "SMTP Service Extension for Command + Pipelining", STD 60, RFC 2920, September 2000. + + [CRAM-MD5] Klensin, J., Catoe, R., and P. Krumviede, "IMAP/POP + AUTHorize Extension for Simple Challenge/Response", RFC + 2195, September 1997. + +13. Acknowledgments + + The editors would like to acknowledge the contributions of John Myers + and other contributors to RFC 2554, on which this document draws from + heavily. + + The editors would also like to thank Ken Murchison, Mark Crispin, + Chris Newman, David Wilson, Dave Cridland, Frank Ellermann, Ned + Freed, John Klensin, Tony Finch, Abhijit Menon-Sen, Philip Guenther, + Sam Hartman, Russ Housley, Cullen Jennings, and Lisa Dusseault for + the time they devoted to reviewing of this document and/or for the + comments received. + +14. Additional Requirements When Using SASL PLAIN over TLS + + This section is normative for SMTP implementations that support SASL + [PLAIN] over [TLS]. + + If an SMTP client is willing to use SASL PLAIN over TLS to + authenticate to the SMTP server, the client verifies the server + certificate according to the rules of [X509]. If the server has not + provided any certificate, or if the certificate verification fails, + the client MUST NOT attempt to authenticate using the SASL PLAIN + mechanism. + + After a successful [TLS] negotiation, the client MUST check its + understanding of the server hostname against the server's identity as + presented in the server Certificate message, in order to prevent + man-in-the-middle attacks. If the match fails, the client MUST NOT + attempt to authenticate using the SASL PLAIN mechanism. Matching is + performed according to the following rules: + + The client MUST use the server hostname it used to open the + connection as the value to compare against the server name as + expressed in the server certificate. The client MUST NOT use + + + + + +Siemborski & Melnikov Standards Track [Page 17] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + any form of the server hostname derived from an insecure remote + source (e.g., insecure DNS lookup). CNAME canonicalization is + not done. + + If a subjectAltName extension of type dNSName is present in the + certificate, it SHOULD be used as the source of the server's + identity. + + Matching is case-insensitive. + + A "*" wildcard character MAY be used as the leftmost name + component in the certificate. For example, *.example.com would + match a.example.com, foo.example.com, etc., but would not match + example.com. + + If the certificate contains multiple names (e.g., more than one + dNSName field), then a match with any one of the fields is + considered acceptable. + +15. Changes since RFC 2554 + + 1. Clarified that servers MUST support the use of the AUTH=mailbox + parameter to MAIL FROM, even when the client is not + authenticated. + + 2. Clarified the initial-client-send requirements, and give + additional examples. + + 3. Updated references to newer versions of various specifications. + + 4. Required SASL PLAIN (over TLS) as mandatory-to-implement. + + 5. Clarified that the mechanism list can change. + + 6. Deprecated the use of the 538 response code. + + 7. Added the use of the SASLprep profile for preparing authorization + identities. + + 8. Substantial cleanup of response codes and indicated suggested + enhanced response codes. Also indicated what response codes + should result in a client prompting the user for new credentials. + + 9. Updated ABNF section to use RFC 4234. + + 10. Clarified interaction with SMTP PIPELINING extension. + + 11. Added a reference to RFC 3848. + + + +Siemborski & Melnikov Standards Track [Page 18] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + 12. Added a new Enhanced Status Code for "authentication line too + long" case. + + 13. Other general editorial clarifications. + +Editors' Addresses + + Robert Siemborski + Google, Inc. + 1600 Ampitheatre Parkway + Mountain View, CA 94043, USA + + Phone: +1 650 623 6925 + EMail: robsiemb@google.com + + + Alexey Melnikov + Isode Limited + 5 Castle Business Village, 36 Station Road, + Hampton, Middlesex, TW12 2BX, UK + + EMail: Alexey.Melnikov@isode.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Siemborski & Melnikov Standards Track [Page 19] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + +Full Copyright Statement + + Copyright (C) The IETF Trust (2007). + + This document is subject to the rights, licenses and restrictions + contained in BCP 78, and except as set forth therein, the authors + retain all their rights. + + This document and the information contained herein are provided on an + "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS + OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE IETF TRUST AND + THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF + THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED + WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Intellectual Property + + The IETF takes no position regarding the validity or scope of any + Intellectual Property Rights or other rights that might be claimed to + pertain to the implementation or use of the technology described in + this document or the extent to which any license under such rights + might or might not be available; nor does it represent that it has + made any independent effort to identify any such rights. Information + on the procedures with respect to rights in RFC documents can be + found in BCP 78 and BCP 79. + + Copies of IPR disclosures made to the IETF Secretariat and any + assurances of licenses to be made available, or the result of an + attempt made to obtain a general license or permission for the use of + such proprietary rights by implementers or users of this + specification can be obtained from the IETF on-line IPR repository at + http://www.ietf.org/ipr. + + The IETF invites any interested party to bring to its attention any + copyrights, patents or patent applications, or other proprietary + rights that may cover technology that may be required to implement + this standard. Please address the information to the IETF at + ietf-ipr@ietf.org. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + +Siemborski & Melnikov Standards Track [Page 20] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/rfc5751.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc5751.txt new file mode 100644 index 0000000..8d1dd21 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/rfc5751.txt @@ -0,0 +1,2523 @@ + + + + + + +Internet Engineering Task Force (IETF) B. Ramsdell +Request for Comments: 5751 Brute Squad Labs +Obsoletes: 3851 S. Turner +Category: Standards Track IECA +ISSN: 2070-1721 January 2010 + + + Secure/Multipurpose Internet Mail Extensions (S/MIME) Version 3.2 + Message Specification + +Abstract + + This document defines Secure/Multipurpose Internet Mail Extensions + (S/MIME) version 3.2. S/MIME provides a consistent way to send and + receive secure MIME data. Digital signatures provide authentication, + message integrity, and non-repudiation with proof of origin. + Encryption provides data confidentiality. Compression can be used to + reduce data size. This document obsoletes RFC 3851. + +Status of This Memo + + This is an Internet Standards Track document. + + This document is a product of the Internet Engineering Task Force + (IETF). It represents the consensus of the IETF community. It has + received public review and has been approved for publication by + the Internet Engineering Steering Group (IESG). Further + information on Internet Standards is available in Section 2 of + RFC 5741. + + Information about the current status of this document, any + errata, and how to provide feedback on it may be obtained at + http://www.rfc-editor.org/info/rfc5751. + + + + + + + + + + + + + + + + + + +Ramsdell & Turner Standards Track [Page 1] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +Copyright Notice + + Copyright (c) 2010 IETF Trust and the persons identified as the + document authors. All rights reserved. + + This document is subject to BCP 78 and the IETF Trust's Legal + Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info) in effect on the date of + publication of this document. Please review these documents + carefully, as they describe your rights and restrictions with respect + to this document. Code Components extracted from this document must + include Simplified BSD License text as described in Section 4.e of + the Trust Legal Provisions and are provided without warranty as + described in the Simplified BSD License. + + This document may contain material from IETF Documents or IETF + Contributions published or made publicly available before November + 10, 2008. The person(s) controlling the copyright in some of this + material may not have granted the IETF Trust the right to allow + modifications of such material outside the IETF Standards Process. + Without obtaining an adequate license from the person(s) controlling + the copyright in such materials, this document may not be modified + outside the IETF Standards Process, and derivative works of it may + not be created outside the IETF Standards Process, except to format + it for publication as an RFC or to translate it into languages other + than English. + + + + + + + + + + + + + + + + + + + + + + + + + +Ramsdell & Turner Standards Track [Page 2] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +Table of Contents + + 1. Introduction ....................................................4 + 1.1. Specification Overview .....................................4 + 1.2. Definitions ................................................5 + 1.3. Conventions Used in This Document ..........................6 + 1.4. Compatibility with Prior Practice of S/MIME ................7 + 1.5. Changes from S/MIME v3 to S/MIME v3.1 ......................7 + 1.6. Changes since S/MIME v3.1 ..................................7 + 2. CMS Options .....................................................9 + 2.1. DigestAlgorithmIdentifier ..................................9 + 2.2. SignatureAlgorithmIdentifier ...............................9 + 2.3. KeyEncryptionAlgorithmIdentifier ..........................10 + 2.4. General Syntax ............................................11 + 2.5. Attributes and the SignerInfo Type ........................12 + 2.6. SignerIdentifier SignerInfo Type ..........................16 + 2.7. ContentEncryptionAlgorithmIdentifier ......................16 + 3. Creating S/MIME Messages .......................................18 + 3.1. Preparing the MIME Entity for Signing, Enveloping, + or Compressing ............................................19 + 3.2. The application/pkcs7-mime Media Type .....................23 + 3.3. Creating an Enveloped-Only Message ........................25 + 3.4. Creating a Signed-Only Message ............................26 + 3.5. Creating a Compressed-Only Message ........................30 + 3.6. Multiple Operations .......................................30 + 3.7. Creating a Certificate Management Message .................31 + 3.8. Registration Requests .....................................32 + 3.9. Identifying an S/MIME Message .............................32 + 4. Certificate Processing .........................................32 + 4.1. Key Pair Generation .......................................33 + 4.2. Signature Generation ......................................33 + 4.3. Signature Verification ....................................34 + 4.4. Encryption ................................................34 + 4.5. Decryption ................................................34 + 5. IANA Considerations ............................................34 + 5.1. Media Type for application/pkcs7-mime .....................34 + 5.2. Media Type for application/pkcs7-signature ................35 + 6. Security Considerations ........................................36 + 7. References .....................................................38 + 7.1. Reference Conventions .....................................38 + 7.2. Normative References ......................................39 + 7.3. Informative References ....................................41 + Appendix A. ASN.1 Module ..........................................43 + Appendix B. Moving S/MIME v2 Message Specification to Historic + Status ................................................45 + Appendix C. Acknowledgments .......................................45 + + + + + +Ramsdell & Turner Standards Track [Page 3] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +1. Introduction + + S/MIME (Secure/Multipurpose Internet Mail Extensions) provides a + consistent way to send and receive secure MIME data. Based on the + popular Internet MIME standard, S/MIME provides the following + cryptographic security services for electronic messaging + applications: authentication, message integrity and non-repudiation + of origin (using digital signatures), and data confidentiality (using + encryption). As a supplementary service, S/MIME provides for message + compression. + + S/MIME can be used by traditional mail user agents (MUAs) to add + cryptographic security services to mail that is sent, and to + interpret cryptographic security services in mail that is received. + However, S/MIME is not restricted to mail; it can be used with any + transport mechanism that transports MIME data, such as HTTP or SIP. + As such, S/MIME takes advantage of the object-based features of MIME + and allows secure messages to be exchanged in mixed-transport + systems. + + Further, S/MIME can be used in automated message transfer agents that + use cryptographic security services that do not require any human + intervention, such as the signing of software-generated documents and + the encryption of FAX messages sent over the Internet. + +1.1. Specification Overview + + This document describes a protocol for adding cryptographic signature + and encryption services to MIME data. The MIME standard [MIME-SPEC] + provides a general structure for the content of Internet messages and + allows extensions for new content-type-based applications. + + This specification defines how to create a MIME body part that has + been cryptographically enhanced according to the Cryptographic + Message Syntax (CMS) RFC 5652 [CMS], which is derived from PKCS #7 + [PKCS-7]. This specification also defines the application/pkcs7-mime + media type that can be used to transport those body parts. + + This document also discusses how to use the multipart/signed media + type defined in [MIME-SECURE] to transport S/MIME signed messages. + multipart/signed is used in conjunction with the application/pkcs7- + signature media type, which is used to transport a detached S/MIME + signature. + + + + + + + + +Ramsdell & Turner Standards Track [Page 4] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + In order to create S/MIME messages, an S/MIME agent MUST follow the + specifications in this document, as well as the specifications listed + in the Cryptographic Message Syntax document [CMS], [CMSALG], + [RSAPSS], [RSAOAEP], and [CMS-SHA2]. + + Throughout this specification, there are requirements and + recommendations made for how receiving agents handle incoming + messages. There are separate requirements and recommendations for + how sending agents create outgoing messages. In general, the best + strategy is to "be liberal in what you receive and conservative in + what you send". Most of the requirements are placed on the handling + of incoming messages, while the recommendations are mostly on the + creation of outgoing messages. + + The separation for requirements on receiving agents and sending + agents also derives from the likelihood that there will be S/MIME + systems that involve software other than traditional Internet mail + clients. S/MIME can be used with any system that transports MIME + data. An automated process that sends an encrypted message might not + be able to receive an encrypted message at all, for example. Thus, + the requirements and recommendations for the two types of agents are + listed separately when appropriate. + +1.2. Definitions + + For the purposes of this specification, the following definitions + apply. + + ASN.1: Abstract Syntax Notation One, as defined in ITU-T + Recommendation X.680 [X.680]. + + BER: Basic Encoding Rules for ASN.1, as defined in ITU- + T Recommendation X.690 [X.690]. + + Certificate: A type that binds an entity's name to a public key + with a digital signature. + + DER: Distinguished Encoding Rules for ASN.1, as defined + in ITU-T Recommendation X.690 [X.690]. + + 7-bit data: Text data with lines less than 998 characters + long, where none of the characters have the 8th + bit set, and there are no NULL characters. + and occur only as part of a end-of- + line delimiter. + + + + + + +Ramsdell & Turner Standards Track [Page 5] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + 8-bit data: Text data with lines less than 998 characters, and + where none of the characters are NULL characters. + and occur only as part of a + end-of-line delimiter. + + Binary data: Arbitrary data. + + Transfer encoding: A reversible transformation made on data so 8-bit + or binary data can be sent via a channel that only + transmits 7-bit data. + + Receiving agent: Software that interprets and processes S/MIME CMS + objects, MIME body parts that contain CMS content + types, or both. + + Sending agent: Software that creates S/MIME CMS content types, + MIME body parts that contain CMS content types, or + both. + + S/MIME agent: User software that is a receiving agent, a sending + agent, or both. + +1.3. Conventions Used in This Document + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [MUSTSHOULD]. + + We define some additional terms here: + + SHOULD+ This term means the same as SHOULD. However, the authors + expect that a requirement marked as SHOULD+ will be + promoted at some future time to be a MUST. + + SHOULD- This term means the same as SHOULD. However, the authors + expect that a requirement marked as SHOULD- will be demoted + to a MAY in a future version of this document. + + MUST- This term means the same as MUST. However, the authors + expect that this requirement will no longer be a MUST in a + future document. Although its status will be determined at + a later time, it is reasonable to expect that if a future + revision of a document alters the status of a MUST- + requirement, it will remain at least a SHOULD or a SHOULD-. + + + + + + + +Ramsdell & Turner Standards Track [Page 6] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +1.4. Compatibility with Prior Practice of S/MIME + + S/MIME version 3.2 agents ought to attempt to have the greatest + interoperability possible with agents for prior versions of S/MIME. + S/MIME version 2 is described in RFC 2311 through RFC 2315 inclusive + [SMIMEv2], S/MIME version 3 is described in RFC 2630 through RFC 2634 + inclusive and RFC 5035 [SMIMEv3], and S/MIME version 3.1 is described + in RFC 3850, RFC 3851, RFC 3852, RFC 2634, and RFC 5035 [SMIMEv3.1]. + RFC 2311 also has historical information about the development of + S/MIME. + +1.5. Changes from S/MIME v3 to S/MIME v3.1 + + The RSA public key algorithm was changed to a MUST implement key + wrapping algorithm, and the Diffie-Hellman (DH) algorithm changed to + a SHOULD implement. + + The AES symmetric encryption algorithm has been included as a SHOULD + implement. + + The RSA public key algorithm was changed to a MUST implement + signature algorithm. + + Ambiguous language about the use of "empty" SignedData messages to + transmit certificates was clarified to reflect that transmission of + Certificate Revocation Lists is also allowed. + + The use of binary encoding for some MIME entities is now explicitly + discussed. + + Header protection through the use of the message/rfc822 media type + has been added. + + Use of the CompressedData CMS type is allowed, along with required + media type and file extension additions. + +1.6. Changes since S/MIME v3.1 + + Editorial changes, e.g., replaced "MIME type" with "media type", + content-type with Content-Type. + + Moved "Conventions Used in This Document" to Section 1.3. Added + definitions for SHOULD+, SHOULD-, and MUST-. + + Section 1.1 and Appendix A: Added references to RFCs for RSASSA-PSS, + RSAES-OAEP, and SHA2 CMS algorithms. Added CMS Multiple Signers + Clarification to CMS reference. + + + + +Ramsdell & Turner Standards Track [Page 7] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Section 1.2: Updated references to ASN.1 to X.680 and BER and DER to + X.690. + + Section 1.4: Added references to S/MIME MSG 3.1 RFCs. + + Section 2.1 (digest algorithm): SHA-256 added as MUST, SHA-1 and MD5 + made SHOULD-. + + Section 2.2 (signature algorithms): RSA with SHA-256 added as MUST, + and DSA with SHA-256 added as SHOULD+, RSA with SHA-1, DSA with + SHA-1, and RSA with MD5 changed to SHOULD-, and RSASSA-PSS with + SHA-256 added as SHOULD+. Also added note about what S/MIME v3.1 + clients support. + + Section 2.3 (key encryption): DH changed to SHOULD-, and RSAES-OAEP + added as SHOULD+. Elaborated requirements for key wrap algorithm. + + Section 2.5.1: Added requirement that receiving agents MUST support + both GeneralizedTime and UTCTime. + + Section 2.5.2: Replaced reference "sha1WithRSAEncryption" with + "sha256WithRSAEncryption", "DES-3EDE-CBC" with "AES-128 CBC", and + deleted the RC5 example. + + Section 2.5.2.1: Deleted entire section (discussed deprecated RC2). + + Section 2.7, 2.7.1, Appendix A: references to RC2/40 removed. + + Section 2.7 (content encryption): AES-128 CBC added as MUST, AES-192 + and AES-256 CBC SHOULD+, tripleDES now SHOULD-. + + Section 2.7.1: Updated pointers from 2.7.2.1 through 2.7.2.4 to + 2.7.1.1 to 2.7.1.2. + + Section 3.1.1: Removed text about MIME character sets. + + Section 3.2.2 and 3.6: Replaced "encrypted" with "enveloped". Update + OID example to use AES-128 CBC oid. + + Section 3.4.3.2: Replace micalg parameter for SHA-1 with sha-1. + + Section 4: Updated reference to CERT v3.2. + + Section 4.1: Updated RSA and DSA key size discussion. Moved last + four sentences to security considerations. Updated reference to + randomness requirements for security. + + + + + +Ramsdell & Turner Standards Track [Page 8] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Section 5: Added IANA registration templates to update media type + registry to point to this document as opposed to RFC 2311. + + Section 6: Updated security considerations. + + Section 7: Moved references from Appendix B to this section. Updated + references. Added informational references to SMIMEv2, SMIMEv3, and + SMIMEv3.1. + + Appendix B: Added Appendix B to move S/MIME v2 to Historic status. + +2. CMS Options + + CMS allows for a wide variety of options in content, attributes, and + algorithm support. This section puts forth a number of support + requirements and recommendations in order to achieve a base level of + interoperability among all S/MIME implementations. [CMSALG] and + [CMS-SHA2] provides additional details regarding the use of the + cryptographic algorithms. [ESS] provides additional details + regarding the use of additional attributes. + +2.1. DigestAlgorithmIdentifier + + Sending and receiving agents MUST support SHA-256 [CMS-SHA2] and + SHOULD- support SHA-1 [CMSALG]. Receiving agents SHOULD- support MD5 + [CMSALG] for the purpose of providing backward compatibility with + MD5-digested S/MIME v2 SignedData objects. + +2.2. SignatureAlgorithmIdentifier + + Receiving agents: + + - MUST support RSA with SHA-256. + + - SHOULD+ support DSA with SHA-256. + + - SHOULD+ support RSASSA-PSS with SHA-256. + + - SHOULD- support RSA with SHA-1. + + - SHOULD- support DSA with SHA-1. + + - SHOULD- support RSA with MD5. + + + + + + + + +Ramsdell & Turner Standards Track [Page 9] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Sending agents: + + - MUST support RSA with SHA-256. + + - SHOULD+ support DSA with SHA-256. + + - SHOULD+ support RSASSA-PSS with SHA-256. + + - SHOULD- support RSA with SHA-1 or DSA with SHA-1. + + - SHOULD- support RSA with MD5. + + See Section 4.1 for information on key size and algorithm references. + + Note that S/MIME v3.1 clients support verifying id-dsa-with-sha1 and + rsaEncryption and might not implement sha256withRSAEncryption. Note + that S/MIME v3 clients might only implement signing or signature + verification using id-dsa-with-sha1, and might also use id-dsa as an + AlgorithmIdentifier in this field. Receiving clients SHOULD + recognize id-dsa as equivalent to id-dsa-with-sha1, and sending + clients MUST use id-dsa-with-sha1 if using that algorithm. Also note + that S/MIME v2 clients are only required to verify digital signatures + using the rsaEncryption algorithm with SHA-1 or MD5, and might not + implement id-dsa-with-sha1 or id-dsa at all. + +2.3. KeyEncryptionAlgorithmIdentifier + + Receiving and sending agents: + + - MUST support RSA Encryption, as specified in [CMSALG]. + + - SHOULD+ support RSAES-OAEP, as specified in [RSAOAEP]. + + - SHOULD- support DH ephemeral-static mode, as specified in + [CMSALG] and [SP800-57]. + + When DH ephemeral-static is used, a key wrap algorithm is also + specified in the KeyEncryptionAlgorithmIdentifier [CMS]. The + underlying encryption functions for the key wrap and content + encryption algorithm ([CMSALG] and [CMSAES]) and the key sizes for + the two algorithms MUST be the same (e.g., AES-128 key wrap algorithm + with AES-128 content encryption algorithm). As AES-128 CBC is the + mandatory-to-implement content encryption algorithm, the AES-128 key + wrap algorithm MUST also be supported when DH ephemeral-static is + used. + + + + + + +Ramsdell & Turner Standards Track [Page 10] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Note that S/MIME v3.1 clients might only implement key encryption and + decryption using the rsaEncryption algorithm. Note that S/MIME v3 + clients might only implement key encryption and decryption using the + Diffie-Hellman algorithm. Also note that S/MIME v2 clients are only + capable of decrypting content-encryption keys using the rsaEncryption + algorithm. + +2.4. General Syntax + + There are several CMS content types. Of these, only the Data, + SignedData, EnvelopedData, and CompressedData content types are + currently used for S/MIME. + +2.4.1. Data Content Type + + Sending agents MUST use the id-data content type identifier to + identify the "inner" MIME message content. For example, when + applying a digital signature to MIME data, the CMS SignedData + encapContentInfo eContentType MUST include the id-data object + identifier and the media type MUST be stored in the SignedData + encapContentInfo eContent OCTET STRING (unless the sending agent is + using multipart/signed, in which case the eContent is absent, per + Section 3.4.3 of this document). As another example, when applying + encryption to MIME data, the CMS EnvelopedData encryptedContentInfo + contentType MUST include the id-data object identifier and the + encrypted MIME content MUST be stored in the EnvelopedData + encryptedContentInfo encryptedContent OCTET STRING. + +2.4.2. SignedData Content Type + + Sending agents MUST use the SignedData content type to apply a + digital signature to a message or, in a degenerate case where there + is no signature information, to convey certificates. Applying a + signature to a message provides authentication, message integrity, + and non-repudiation of origin. + +2.4.3. EnvelopedData Content Type + + This content type is used to apply data confidentiality to a message. + A sender needs to have access to a public key for each intended + message recipient to use this service. + +2.4.4. CompressedData Content Type + + This content type is used to apply data compression to a message. + This content type does not provide authentication, message integrity, + non-repudiation, or data confidentiality, and is only used to reduce + the message's size. + + + +Ramsdell & Turner Standards Track [Page 11] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + See Section 3.6 for further guidance on the use of this type in + conjunction with other CMS types. + +2.5. Attributes and the SignerInfo Type + + The SignerInfo type allows the inclusion of unsigned and signed + attributes along with a signature. + + Receiving agents MUST be able to handle zero or one instance of each + of the signed attributes listed here. Sending agents SHOULD generate + one instance of each of the following signed attributes in each + S/MIME message: + + - Signing Time (section (Section 2.5.1 in this document) + + - SMIME Capabilities (section (Section 2.5.2 in this document) + + - Encryption Key Preference (section (Section 2.5.3 in this + document) + + - Message Digest (section (Section 11.2 in [CMS]) + + - Content Type (section (Section 11.1 in [CMS]) + + Further, receiving agents SHOULD be able to handle zero or one + instance of the signingCertificate and signingCertificatev2 signed + attributes, as defined in Section 5 of RFC 2634 [ESS] and Section 3 + of RFC 5035 [ESS]. + + Sending agents SHOULD generate one instance of the signingCertificate + or signingCertificatev2 signed attribute in each SignerInfo + structure. + + Additional attributes and values for these attributes might be + defined in the future. Receiving agents SHOULD handle attributes or + values that they do not recognize in a graceful manner. + + Interactive sending agents that include signed attributes that are + not listed here SHOULD display those attributes to the user, so that + the user is aware of all of the data being signed. + +2.5.1. Signing Time Attribute + + The signing-time attribute is used to convey the time that a message + was signed. The time of signing will most likely be created by a + message originator and therefore is only as trustworthy as the + originator. + + + + +Ramsdell & Turner Standards Track [Page 12] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Sending agents MUST encode signing time through the year 2049 as + UTCTime; signing times in 2050 or later MUST be encoded as + GeneralizedTime. When the UTCTime CHOICE is used, S/MIME agents MUST + interpret the year field (YY) as follows: + + If YY is greater than or equal to 50, the year is interpreted as + 19YY; if YY is less than 50, the year is interpreted as 20YY. + + Receiving agents MUST be able to process signing-time attributes that + are encoded in either UTCTime or GeneralizedTime. + +2.5.2. SMIME Capabilities Attribute + + The SMIMECapabilities attribute includes signature algorithms (such + as "sha256WithRSAEncryption"), symmetric algorithms (such as "AES-128 + CBC"), and key encipherment algorithms (such as "rsaEncryption"). + There are also several identifiers that indicate support for other + optional features such as binary encoding and compression. The + SMIMECapabilities were designed to be flexible and extensible so + that, in the future, a means of identifying other capabilities and + preferences such as certificates can be added in a way that will not + cause current clients to break. + + If present, the SMIMECapabilities attribute MUST be a + SignedAttribute; it MUST NOT be an UnsignedAttribute. CMS defines + SignedAttributes as a SET OF Attribute. The SignedAttributes in a + signerInfo MUST NOT include multiple instances of the + SMIMECapabilities attribute. CMS defines the ASN.1 syntax for + Attribute to include attrValues SET OF AttributeValue. A + SMIMECapabilities attribute MUST only include a single instance of + AttributeValue. There MUST NOT be zero or multiple instances of + AttributeValue present in the attrValues SET OF AttributeValue. + + The semantics of the SMIMECapabilities attribute specify a partial + list as to what the client announcing the SMIMECapabilities can + support. A client does not have to list every capability it + supports, and need not list all its capabilities so that the + capabilities list doesn't get too long. In an SMIMECapabilities + attribute, the object identifiers (OIDs) are listed in order of their + preference, but SHOULD be separated logically along the lines of + their categories (signature algorithms, symmetric algorithms, key + encipherment algorithms, etc.). + + The structure of the SMIMECapabilities attribute is to facilitate + simple table lookups and binary comparisons in order to determine + matches. For instance, the DER-encoding for the SMIMECapability for + AES-128 CBC MUST be identically encoded regardless of the + implementation. Because of the requirement for identical encoding, + + + +Ramsdell & Turner Standards Track [Page 13] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + individuals documenting algorithms to be used in the + SMIMECapabilities attribute SHOULD explicitly document the correct + byte sequence for the common cases. + + For any capability, the associated parameters for the OID MUST + specify all of the parameters necessary to differentiate between two + instances of the same algorithm. + + The OIDs that correspond to algorithms SHOULD use the same OID as the + actual algorithm, except in the case where the algorithm usage is + ambiguous from the OID. For instance, in an earlier specification, + rsaEncryption was ambiguous because it could refer to either a + signature algorithm or a key encipherment algorithm. In the event + that an OID is ambiguous, it needs to be arbitrated by the maintainer + of the registered SMIMECapabilities list as to which type of + algorithm will use the OID, and a new OID MUST be allocated under the + smimeCapabilities OID to satisfy the other use of the OID. + + The registered SMIMECapabilities list specifies the parameters for + OIDs that need them, most notably key lengths in the case of + variable-length symmetric ciphers. In the event that there are no + differentiating parameters for a particular OID, the parameters MUST + be omitted, and MUST NOT be encoded as NULL. Additional values for + the SMIMECapabilities attribute might be defined in the future. + Receiving agents MUST handle a SMIMECapabilities object that has + values that it does not recognize in a graceful manner. + + Section 2.7.1 explains a strategy for caching capabilities. + +2.5.3. Encryption Key Preference Attribute + + The encryption key preference attribute allows the signer to + unambiguously describe which of the signer's certificates has the + signer's preferred encryption key. This attribute is designed to + enhance behavior for interoperating with those clients that use + separate keys for encryption and signing. This attribute is used to + convey to anyone viewing the attribute which of the listed + certificates is appropriate for encrypting a session key for future + encrypted messages. + + If present, the SMIMEEncryptionKeyPreference attribute MUST be a + SignedAttribute; it MUST NOT be an UnsignedAttribute. CMS defines + SignedAttributes as a SET OF Attribute. The SignedAttributes in a + signerInfo MUST NOT include multiple instances of the + SMIMEEncryptionKeyPreference attribute. CMS defines the ASN.1 syntax + for Attribute to include attrValues SET OF AttributeValue. A + SMIMEEncryptionKeyPreference attribute MUST only include a single + + + + +Ramsdell & Turner Standards Track [Page 14] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + instance of AttributeValue. There MUST NOT be zero or multiple + instances of AttributeValue present in the attrValues SET OF + AttributeValue. + + The sending agent SHOULD include the referenced certificate in the + set of certificates included in the signed message if this attribute + is used. The certificate MAY be omitted if it has been previously + made available to the receiving agent. Sending agents SHOULD use + this attribute if the commonly used or preferred encryption + certificate is not the same as the certificate used to sign the + message. + + Receiving agents SHOULD store the preference data if the signature on + the message is valid and the signing time is greater than the + currently stored value. (As with the SMIMECapabilities, the clock + skew SHOULD be checked and the data not used if the skew is too + great.) Receiving agents SHOULD respect the sender's encryption key + preference attribute if possible. This, however, represents only a + preference and the receiving agent can use any certificate in + replying to the sender that is valid. + + Section 2.7.1 explains a strategy for caching preference data. + +2.5.3.1. Selection of Recipient Key Management Certificate + + In order to determine the key management certificate to be used when + sending a future CMS EnvelopedData message for a particular + recipient, the following steps SHOULD be followed: + + - If an SMIMEEncryptionKeyPreference attribute is found in a + SignedData object received from the desired recipient, this + identifies the X.509 certificate that SHOULD be used as the X.509 + key management certificate for the recipient. + + - If an SMIMEEncryptionKeyPreference attribute is not found in a + SignedData object received from the desired recipient, the set of + X.509 certificates SHOULD be searched for a X.509 certificate with + the same subject name as the signer of a X.509 certificate that can + be used for key management. + + - Or use some other method of determining the user's key management + key. If a X.509 key management certificate is not found, then + encryption cannot be done with the signer of the message. If + multiple X.509 key management certificates are found, the S/MIME + agent can make an arbitrary choice between them. + + + + + + +Ramsdell & Turner Standards Track [Page 15] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +2.6. SignerIdentifier SignerInfo Type + + S/MIME v3.2 implementations MUST support both issuerAndSerialNumber + and subjectKeyIdentifier. Messages that use the subjectKeyIdentifier + choice cannot be read by S/MIME v2 clients. + + It is important to understand that some certificates use a value for + subjectKeyIdentifier that is not suitable for uniquely identifying a + certificate. Implementations MUST be prepared for multiple + certificates for potentially different entities to have the same + value for subjectKeyIdentifier, and MUST be prepared to try each + matching certificate during signature verification before indicating + an error condition. + +2.7. ContentEncryptionAlgorithmIdentifier + + Sending and receiving agents: + + - MUST support encryption and decryption with AES-128 CBC + [CMSAES]. + + - SHOULD+ support encryption and decryption with AES-192 CBC and + AES-256 CBC [CMSAES]. + + - SHOULD- support encryption and decryption with DES EDE3 CBC, + hereinafter called "tripleDES" [CMSALG]. + +2.7.1. Deciding Which Encryption Method to Use + + When a sending agent creates an encrypted message, it has to decide + which type of encryption to use. The decision process involves using + information garnered from the capabilities lists included in messages + received from the recipient, as well as out-of-band information such + as private agreements, user preferences, legal restrictions, and so + on. + + Section 2.5.2 defines a method by which a sending agent can + optionally announce, among other things, its decrypting capabilities + in its order of preference. The following method for processing and + remembering the encryption capabilities attribute in incoming signed + messages SHOULD be used. + + - If the receiving agent has not yet created a list of + capabilities for the sender's public key, then, after verifying + the signature on the incoming message and checking the + timestamp, the receiving agent SHOULD create a new list + containing at least the signing time and the symmetric + capabilities. + + + +Ramsdell & Turner Standards Track [Page 16] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + - If such a list already exists, the receiving agent SHOULD verify + that the signing time in the incoming message is greater than + the signing time stored in the list and that the signature is + valid. If so, the receiving agent SHOULD update both the + signing time and capabilities in the list. Values of the + signing time that lie far in the future (that is, a greater + discrepancy than any reasonable clock skew), or a capabilities + list in messages whose signature could not be verified, MUST NOT + be accepted. + + The list of capabilities SHOULD be stored for future use in creating + messages. + + Before sending a message, the sending agent MUST decide whether it is + willing to use weak encryption for the particular data in the + message. If the sending agent decides that weak encryption is + unacceptable for this data, then the sending agent MUST NOT use a + weak algorithm. The decision to use or not use weak encryption + overrides any other decision in this section about which encryption + algorithm to use. + + Sections 2.7.1.1 through 2.7.1.2 describe the decisions a sending + agent SHOULD use in deciding which type of encryption will be applied + to a message. These rules are ordered, so the sending agent SHOULD + make its decision in the order given. + +2.7.1.1. Rule 1: Known Capabilities + + If the sending agent has received a set of capabilities from the + recipient for the message the agent is about to encrypt, then the + sending agent SHOULD use that information by selecting the first + capability in the list (that is, the capability most preferred by the + intended recipient) that the sending agent knows how to encrypt. The + sending agent SHOULD use one of the capabilities in the list if the + agent reasonably expects the recipient to be able to decrypt the + message. + +2.7.1.2. Rule 2: Unknown Capabilities, Unknown Version of S/MIME + + If the following two conditions are met: + + - the sending agent has no knowledge of the encryption + capabilities of the recipient, and + + - the sending agent has no knowledge of the version of S/MIME of + the recipient, + + + + + +Ramsdell & Turner Standards Track [Page 17] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + then the sending agent SHOULD use AES-128 because it is a stronger + algorithm and is required by S/MIME v3.2. If the sending agent + chooses not to use AES-128 in this step, it SHOULD use tripleDES. + +2.7.2. Choosing Weak Encryption + + All algorithms that use 40-bit keys are considered by many to be weak + encryption. A sending agent that is controlled by a human SHOULD + allow a human sender to determine the risks of sending data using a + weak encryption algorithm before sending the data, and possibly allow + the human to use a stronger encryption method such as tripleDES or + AES. + +2.7.3. Multiple Recipients + + If a sending agent is composing an encrypted message to a group of + recipients where the encryption capabilities of some of the + recipients do not overlap, the sending agent is forced to send more + than one message. Please note that if the sending agent chooses to + send a message encrypted with a strong algorithm, and then send the + same message encrypted with a weak algorithm, someone watching the + communications channel could learn the contents of the strongly + encrypted message simply by decrypting the weakly encrypted message. + +3. Creating S/MIME Messages + + This section describes the S/MIME message formats and how they are + created. S/MIME messages are a combination of MIME bodies and CMS + content types. Several media types as well as several CMS content + types are used. The data to be secured is always a canonical MIME + entity. The MIME entity and other data, such as certificates and + algorithm identifiers, are given to CMS processing facilities that + produce a CMS object. Finally, the CMS object is wrapped in MIME. + The Enhanced Security Services for S/MIME [ESS] document provides + descriptions of how nested, secured S/MIME messages are formatted. + ESS provides a description of how a triple-wrapped S/MIME message is + formatted using multipart/signed and application/pkcs7-mime for the + signatures. + + S/MIME provides one format for enveloped-only data, several formats + for signed-only data, and several formats for signed and enveloped + data. Several formats are required to accommodate several + environments, in particular for signed messages. The criteria for + choosing among these formats are also described. + + The reader of this section is expected to understand MIME as + described in [MIME-SPEC] and [MIME-SECURE]. + + + + +Ramsdell & Turner Standards Track [Page 18] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +3.1. Preparing the MIME Entity for Signing, Enveloping, or Compressing + + S/MIME is used to secure MIME entities. A MIME entity can be a sub- + part, sub-parts of a message, or the whole message with all its sub- + parts. A MIME entity that is the whole message includes only the + MIME message headers and MIME body, and does not include the RFC-822 + header. Note that S/MIME can also be used to secure MIME entities + used in applications other than Internet mail. If protection of the + RFC-822 header is required, the use of the message/rfc822 media type + is explained later in this section. + + The MIME entity that is secured and described in this section can be + thought of as the "inside" MIME entity. That is, it is the + "innermost" object in what is possibly a larger MIME message. + Processing "outside" MIME entities into CMS content types is + described in Sections 3.2, 3.4, and elsewhere. + + The procedure for preparing a MIME entity is given in [MIME-SPEC]. + The same procedure is used here with some additional restrictions + when signing. The description of the procedures from [MIME-SPEC] is + repeated here, but it is suggested that the reader refer to that + document for the exact procedure. This section also describes + additional requirements. + + A single procedure is used for creating MIME entities that are to + have any combination of signing, enveloping, and compressing applied. + Some additional steps are recommended to defend against known + corruptions that can occur during mail transport that are of + particular importance for clear-signing using the multipart/signed + format. It is recommended that these additional steps be performed + on enveloped messages, or signed and enveloped messages, so that the + message can be forwarded to any environment without modification. + + These steps are descriptive rather than prescriptive. The + implementer is free to use any procedure as long as the result is the + same. + + Step 1. The MIME entity is prepared according to the local + conventions. + + Step 2. The leaf parts of the MIME entity are converted to canonical + form. + + Step 3. Appropriate transfer encoding is applied to the leaves of + the MIME entity. + + + + + + +Ramsdell & Turner Standards Track [Page 19] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + When an S/MIME message is received, the security services on the + message are processed, and the result is the MIME entity. That MIME + entity is typically passed to a MIME-capable user agent where it is + further decoded and presented to the user or receiving application. + + In order to protect outer, non-content-related message header fields + (for instance, the "Subject", "To", "From", and "Cc" fields), the + sending client MAY wrap a full MIME message in a message/rfc822 + wrapper in order to apply S/MIME security services to these header + fields. It is up to the receiving client to decide how to present + this "inner" header along with the unprotected "outer" header. + + When an S/MIME message is received, if the top-level protected MIME + entity has a Content-Type of message/rfc822, it can be assumed that + the intent was to provide header protection. This entity SHOULD be + presented as the top-level message, taking into account header + merging issues as previously discussed. + +3.1.1. Canonicalization + + Each MIME entity MUST be converted to a canonical form that is + uniquely and unambiguously representable in the environment where the + signature is created and the environment where the signature will be + verified. MIME entities MUST be canonicalized for enveloping and + compressing as well as signing. + + The exact details of canonicalization depend on the actual media type + and subtype of an entity, and are not described here. Instead, the + standard for the particular media type SHOULD be consulted. For + example, canonicalization of type text/plain is different from + canonicalization of audio/basic. Other than text types, most types + have only one representation regardless of computing platform or + environment that can be considered their canonical representation. + In general, canonicalization will be performed by the non-security + part of the sending agent rather than the S/MIME implementation. + + The most common and important canonicalization is for text, which is + often represented differently in different environments. MIME + entities of major type "text" MUST have both their line endings and + character set canonicalized. The line ending MUST be the pair of + characters , and the charset SHOULD be a registered charset + [CHARSETS]. The details of the canonicalization are specified in + [MIME-SPEC]. + + Note that some charsets such as ISO-2022 have multiple + representations for the same characters. When preparing such text + for signing, the canonical representation specified for the charset + MUST be used. + + + +Ramsdell & Turner Standards Track [Page 20] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +3.1.2. Transfer Encoding + + When generating any of the secured MIME entities below, except the + signing using the multipart/signed format, no transfer encoding is + required at all. S/MIME implementations MUST be able to deal with + binary MIME objects. If no Content-Transfer-Encoding header field is + present, the transfer encoding is presumed to be 7BIT. + + S/MIME implementations SHOULD however use transfer encoding described + in Section 3.1.3 for all MIME entities they secure. The reason for + securing only 7-bit MIME entities, even for enveloped data that are + not exposed to the transport, is that it allows the MIME entity to be + handled in any environment without changing it. For example, a + trusted gateway might remove the envelope, but not the signature, of + a message, and then forward the signed message on to the end + recipient so that they can verify the signatures directly. If the + transport internal to the site is not 8-bit clean, such as on a wide- + area network with a single mail gateway, verifying the signature will + not be possible unless the original MIME entity was only 7-bit data. + + S/MIME implementations that "know" that all intended recipients are + capable of handling inner (all but the outermost) binary MIME objects + SHOULD use binary encoding as opposed to a 7-bit-safe transfer + encoding for the inner entities. The use of a 7-bit-safe encoding + (such as base64) would unnecessarily expand the message size. + Implementations MAY "know" that recipient implementations are capable + of handling inner binary MIME entities either by interpreting the id- + cap-preferBinaryInside SMIMECapabilities attribute, by prior + agreement, or by other means. + + If one or more intended recipients are unable to handle inner binary + MIME objects, or if this capability is unknown for any of the + intended recipients, S/MIME implementations SHOULD use transfer + encoding described in Section 3.1.3 for all MIME entities they + secure. + +3.1.3. Transfer Encoding for Signing Using multipart/signed + + If a multipart/signed entity is ever to be transmitted over the + standard Internet SMTP infrastructure or other transport that is + constrained to 7-bit text, it MUST have transfer encoding applied so + that it is represented as 7-bit text. MIME entities that are 7-bit + data already need no transfer encoding. Entities such as 8-bit text + and binary data can be encoded with quoted-printable or base-64 + transfer encoding. + + + + + + +Ramsdell & Turner Standards Track [Page 21] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + The primary reason for the 7-bit requirement is that the Internet + mail transport infrastructure cannot guarantee transport of 8-bit or + binary data. Even though many segments of the transport + infrastructure now handle 8-bit and even binary data, it is sometimes + not possible to know whether the transport path is 8-bit clean. If a + mail message with 8-bit data were to encounter a message transfer + agent that cannot transmit 8-bit or binary data, the agent has three + options, none of which are acceptable for a clear-signed message: + + - The agent could change the transfer encoding; this would + invalidate the signature. + + - The agent could transmit the data anyway, which would most likely + result in the 8th bit being corrupted; this too would invalidate + the signature. + + - The agent could return the message to the sender. + + [MIME-SECURE] prohibits an agent from changing the transfer encoding + of the first part of a multipart/signed message. If a compliant + agent that cannot transmit 8-bit or binary data encounters a + multipart/signed message with 8-bit or binary data in the first part, + it would have to return the message to the sender as undeliverable. + +3.1.4. Sample Canonical MIME Entity + + This example shows a multipart/mixed message with full transfer + encoding. This message contains a text part and an attachment. The + sample message text includes characters that are not US-ASCII and + thus need to be transfer encoded. Though not shown here, the end of + each line is . The line ending of the MIME headers, the + text, and the transfer encoded parts, all MUST be . + + Note that this example is not of an S/MIME message. + + Content-Type: multipart/mixed; boundary=bar + + --bar + Content-Type: text/plain; charset=iso-8859-1 + Content-Transfer-Encoding: quoted-printable + + =A1Hola Michael! + + How do you like the new S/MIME specification? + + It's generally a good idea to encode lines that begin with + From=20because some mail transport agents will insert a greater- + than (>) sign, thus invalidating the signature. + + + +Ramsdell & Turner Standards Track [Page 22] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Also, in some cases it might be desirable to encode any =20 + trailing whitespace that occurs on lines in order to ensure =20 + that the message signature is not invalidated when passing =20 + a gateway that modifies such whitespace (like BITNET). =20 + + --bar + Content-Type: image/jpeg + Content-Transfer-Encoding: base64 + + iQCVAwUBMJrRF2N9oWBghPDJAQE9UQQAtl7LuRVndBjrk4EqYBIb3h5QXIX/LC// + jJV5bNvkZIGPIcEmI5iFd9boEgvpirHtIREEqLQRkYNoBActFBZmh9GC3C041WGq + uMbrbxc+nIs1TIKlA08rVi9ig/2Yh7LFrK5Ein57U/W72vgSxLhe/zhdfolT9Brn + HOxEa44b+EI= + + --bar-- + +3.2. The application/pkcs7-mime Media Type + + The application/pkcs7-mime media type is used to carry CMS content + types including EnvelopedData, SignedData, and CompressedData. The + details of constructing these entities are described in subsequent + sections. This section describes the general characteristics of the + application/pkcs7-mime media type. + + The carried CMS object always contains a MIME entity that is prepared + as described in Section 3.1 if the eContentType is id-data. Other + contents MAY be carried when the eContentType contains different + values. See [ESS] for an example of this with signed receipts. + + Since CMS content types are binary data, in most cases base-64 + transfer encoding is appropriate, in particular, when used with SMTP + transport. The transfer encoding used depends on the transport + through which the object is to be sent, and is not a characteristic + of the media type. + + Note that this discussion refers to the transfer encoding of the CMS + object or "outside" MIME entity. It is completely distinct from, and + unrelated to, the transfer encoding of the MIME entity secured by the + CMS object, the "inside" object, which is described in Section 3.1. + + Because there are several types of application/pkcs7-mime objects, a + sending agent SHOULD do as much as possible to help a receiving agent + know about the contents of the object without forcing the receiving + agent to decode the ASN.1 for the object. The Content-Type header + field of all application/pkcs7-mime objects SHOULD include the + optional "smime-type" parameter, as described in the following + sections. + + + + +Ramsdell & Turner Standards Track [Page 23] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +3.2.1. The name and filename Parameters + + For the application/pkcs7-mime, sending agents SHOULD emit the + optional "name" parameter to the Content-Type field for compatibility + with older systems. Sending agents SHOULD also emit the optional + Content-Disposition field [CONTDISP] with the "filename" parameter. + If a sending agent emits the above parameters, the value of the + parameters SHOULD be a file name with the appropriate extension: + + Media Type File Extension + application/pkcs7-mime (SignedData, EnvelopedData) .p7m + application/pkcs7-mime (degenerate SignedData .p7c + certificate management message) + application/pkcs7-mime (CompressedData) .p7z + application/pkcs7-signature (SignedData) .p7s + + In addition, the file name SHOULD be limited to eight characters + followed by a three-letter extension. The eight-character filename + base can be any distinct name; the use of the filename base "smime" + SHOULD be used to indicate that the MIME entity is associated with + S/MIME. + + Including a file name serves two purposes. It facilitates easier use + of S/MIME objects as files on disk. It also can convey type + information across gateways. When a MIME entity of type + application/pkcs7-mime (for example) arrives at a gateway that has no + special knowledge of S/MIME, it will default the entity's media type + to application/octet-stream and treat it as a generic attachment, + thus losing the type information. However, the suggested filename + for an attachment is often carried across a gateway. This often + allows the receiving systems to determine the appropriate application + to hand the attachment off to, in this case, a stand-alone S/MIME + processing application. Note that this mechanism is provided as a + convenience for implementations in certain environments. A proper + S/MIME implementation MUST use the media types and MUST NOT rely on + the file extensions. + +3.2.2. The smime-type Parameter + + The application/pkcs7-mime content type defines the optional "smime- + type" parameter. The intent of this parameter is to convey details + about the security applied (signed or enveloped) along with + information about the contained content. This specification defines + the following smime-types. + + + + + + + +Ramsdell & Turner Standards Track [Page 24] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Name CMS Type Inner Content + enveloped-data EnvelopedData id-data + signed-data SignedData id-data + certs-only SignedData none + compressed-data CompressedData id-data + + In order for consistency to be obtained with future specifications, + the following guidelines SHOULD be followed when assigning a new + smime-type parameter. + + 1. If both signing and encryption can be applied to the content, + then two values for smime-type SHOULD be assigned "signed-*" + and "enveloped-*". If one operation can be assigned, then this + can be omitted. Thus, since "certs-only" can only be signed, + "signed-" is omitted. + + 2. A common string for a content OID SHOULD be assigned. We use + "data" for the id-data content OID when MIME is the inner + content. + + 3. If no common string is assigned, then the common string of + "OID." is recommended (for example, + "OID.2.16.840.1.101.3.4.1.2" would be AES-128 CBC). + + It is explicitly intended that this field be a suitable hint for mail + client applications to indicate whether a message is "signed" or + "enveloped" without having to tunnel into the CMS payload. + +3.3. Creating an Enveloped-Only Message + + This section describes the format for enveloping a MIME entity + without signing it. It is important to note that sending enveloped + but not signed messages does not provide for data integrity. It is + possible to replace ciphertext in such a way that the processed + message will still be valid, but the meaning can be altered. + + Step 1. The MIME entity to be enveloped is prepared according to + Section 3.1. + + Step 2. The MIME entity and other required data is processed into a + CMS object of type EnvelopedData. In addition to encrypting + a copy of the content-encryption key for each recipient, a + copy of the content-encryption key SHOULD be encrypted for + the originator and included in the EnvelopedData (see [CMS], + Section 6). + + Step 3. The EnvelopedData object is wrapped in a CMS ContentInfo + object. + + + +Ramsdell & Turner Standards Track [Page 25] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Step 4. The ContentInfo object is inserted into an + application/pkcs7-mime MIME entity. + + The smime-type parameter for enveloped-only messages is "enveloped- + data". The file extension for this type of message is ".p7m". + + A sample message would be: + + Content-Type: application/pkcs7-mime; smime-type=enveloped-data; + name=smime.p7m + Content-Transfer-Encoding: base64 + Content-Disposition: attachment; filename=smime.p7m + + rfvbnj756tbBghyHhHUujhJhjH77n8HHGT9HG4VQpfyF467GhIGfHfYT6 + 7n8HHGghyHhHUujhJh4VQpfyF467GhIGfHfYGTrfvbnjT6jH7756tbB9H + f8HHGTrfvhJhjH776tbB9HG4VQbnj7567GhIGfHfYT6ghyHhHUujpfyF4 + 0GhIGfHfQbnj756YT64V + +3.4. Creating a Signed-Only Message + + There are two formats for signed messages defined for S/MIME: + + - application/pkcs7-mime with SignedData. + + - multipart/signed. + + In general, the multipart/signed form is preferred for sending, and + receiving agents MUST be able to handle both. + +3.4.1. Choosing a Format for Signed-Only Messages + + There are no hard-and-fast rules as to when a particular signed-only + format is chosen. It depends on the capabilities of all the + receivers and the relative importance of receivers with S/MIME + facilities being able to verify the signature versus the importance + of receivers without S/MIME software being able to view the message. + + Messages signed using the multipart/signed format can always be + viewed by the receiver whether or not they have S/MIME software. + They can also be viewed whether they are using a MIME-native user + agent or they have messages translated by a gateway. In this + context, "be viewed" means the ability to process the message + essentially as if it were not a signed message, including any other + MIME structure the message might have. + + + + + + + +Ramsdell & Turner Standards Track [Page 26] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Messages signed using the SignedData format cannot be viewed by a + recipient unless they have S/MIME facilities. However, the + SignedData format protects the message content from being changed by + benign intermediate agents. Such agents might do line wrapping or + content-transfer encoding changes that would break the signature. + +3.4.2. Signing Using application/pkcs7-mime with SignedData + + This signing format uses the application/pkcs7-mime media type. The + steps to create this format are: + + Step 1. The MIME entity is prepared according to Section 3.1. + + Step 2. The MIME entity and other required data are processed into a + CMS object of type SignedData. + + Step 3. The SignedData object is wrapped in a CMS ContentInfo + object. + + Step 4. The ContentInfo object is inserted into an + application/pkcs7-mime MIME entity. + + The smime-type parameter for messages using application/pkcs7-mime + with SignedData is "signed-data". The file extension for this type + of message is ".p7m". + + A sample message would be: + + Content-Type: application/pkcs7-mime; smime-type=signed-data; + name=smime.p7m + Content-Transfer-Encoding: base64 + Content-Disposition: attachment; filename=smime.p7m + + 567GhIGfHfYT6ghyHhHUujpfyF4f8HHGTrfvhJhjH776tbB9HG4VQbnj7 + 77n8HHGT9HG4VQpfyF467GhIGfHfYT6rfvbnj756tbBghyHhHUujhJhjH + HUujhJh4VQpfyF467GhIGfHfYGTrfvbnjT6jH7756tbB9H7n8HHGghyHh + 6YT64V0GhIGfHfQbnj75 + +3.4.3. Signing Using the multipart/signed Format + + This format is a clear-signing format. Recipients without any S/MIME + or CMS processing facilities are able to view the message. It makes + use of the multipart/signed media type described in [MIME-SECURE]. + The multipart/signed media type has two parts. The first part + contains the MIME entity that is signed; the second part contains the + "detached signature" CMS SignedData object in which the + encapContentInfo eContent field is absent. + + + + +Ramsdell & Turner Standards Track [Page 27] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +3.4.3.1. The application/pkcs7-signature Media Type + + This media type always contains a CMS ContentInfo containing a single + CMS object of type SignedData. The SignedData encapContentInfo + eContent field MUST be absent. The signerInfos field contains the + signatures for the MIME entity. + + The file extension for signed-only messages using application/pkcs7- + signature is ".p7s". + +3.4.3.2. Creating a multipart/signed Message + + Step 1. The MIME entity to be signed is prepared according to + Section 3.1, taking special care for clear-signing. + + Step 2. The MIME entity is presented to CMS processing in order to + obtain an object of type SignedData in which the + encapContentInfo eContent field is absent. + + Step 3. The MIME entity is inserted into the first part of a + multipart/signed message with no processing other than that + described in Section 3.1. + + Step 4. Transfer encoding is applied to the "detached signature" CMS + SignedData object, and it is inserted into a MIME entity of + type application/pkcs7-signature. + + Step 5. The MIME entity of the application/pkcs7-signature is + inserted into the second part of the multipart/signed + entity. + + The multipart/signed Content-Type has two required parameters: the + protocol parameter and the micalg parameter. + + The protocol parameter MUST be "application/pkcs7-signature". Note + that quotation marks are required around the protocol parameter + because MIME requires that the "/" character in the parameter value + MUST be quoted. + + The micalg parameter allows for one-pass processing when the + signature is being verified. The value of the micalg parameter is + dependent on the message digest algorithm(s) used in the calculation + of the Message Integrity Check. If multiple message digest + algorithms are used, they MUST be separated by commas per [MIME- + SECURE]. The values to be placed in the micalg parameter SHOULD be + from the following: + + + + + +Ramsdell & Turner Standards Track [Page 28] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Algorithm Value Used + + MD5 md5 + SHA-1 sha-1 + SHA-224 sha-224 + SHA-256 sha-256 + SHA-384 sha-384 + SHA-512 sha-512 + Any other (defined separately in algorithm profile or "unknown" + if not defined) + + (Historical note: some early implementations of S/MIME emitted and + expected "rsa-md5", "rsa-sha1", and "sha1" for the micalg parameter.) + Receiving agents SHOULD be able to recover gracefully from a micalg + parameter value that they do not recognize. Future names for this + parameter will be consistent with the IANA "Hash Function Textual + Names" registry. + +3.4.3.3. Sample multipart/signed Message + + Content-Type: multipart/signed; + protocol="application/pkcs7-signature"; + micalg=sha1; boundary=boundary42 + + --boundary42 + Content-Type: text/plain + + This is a clear-signed message. + + --boundary42 + Content-Type: application/pkcs7-signature; name=smime.p7s + Content-Transfer-Encoding: base64 + Content-Disposition: attachment; filename=smime.p7s + + ghyHhHUujhJhjH77n8HHGTrfvbnj756tbB9HG4VQpfyF467GhIGfHfYT6 + 4VQpfyF467GhIGfHfYT6jH77n8HHGghyHhHUujhJh756tbB9HGTrfvbnj + n8HHGTrfvhJhjH776tbB9HG4VQbnj7567GhIGfHfYT6ghyHhHUujpfyF4 + 7GhIGfHfYT64VQbnj756 + + --boundary42-- + + The content that is digested (the first part of the multipart/signed) + consists of the bytes: + + 43 6f 6e 74 65 6e 74 2d 54 79 70 65 3a 20 74 65 78 74 2f 70 6c 61 69 + 6e 0d 0a 0d 0a 54 68 69 73 20 69 73 20 61 20 63 6c 65 61 72 2d 73 69 + 67 6e 65 64 20 6d 65 73 73 61 67 65 2e 0d 0a + + + + +Ramsdell & Turner Standards Track [Page 29] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +3.5. Creating a Compressed-Only Message + + This section describes the format for compressing a MIME entity. + Please note that versions of S/MIME prior to version 3.1 did not + specify any use of CompressedData, and will not recognize it. The + use of a capability to indicate the ability to receive CompressedData + is described in [CMSCOMPR] and is the preferred method for + compatibility. + + Step 1. The MIME entity to be compressed is prepared according to + Section 3.1. + + Step 2. The MIME entity and other required data are processed into a + CMS object of type CompressedData. + + Step 3. The CompressedData object is wrapped in a CMS ContentInfo + object. + + Step 4. The ContentInfo object is inserted into an + application/pkcs7-mime MIME entity. + + The smime-type parameter for compressed-only messages is "compressed- + data". The file extension for this type of message is ".p7z". + + A sample message would be: + + Content-Type: application/pkcs7-mime; smime-type=compressed-data; + name=smime.p7z + Content-Transfer-Encoding: base64 + Content-Disposition: attachment; filename=smime.p7z + + rfvbnj756tbBghyHhHUujhJhjH77n8HHGT9HG4VQpfyF467GhIGfHfYT6 + 7n8HHGghyHhHUujhJh4VQpfyF467GhIGfHfYGTrfvbnjT6jH7756tbB9H + f8HHGTrfvhJhjH776tbB9HG4VQbnj7567GhIGfHfYT6ghyHhHUujpfyF4 + 0GhIGfHfQbnj756YT64V + +3.6. Multiple Operations + + The signed-only, enveloped-only, and compressed-only MIME formats can + be nested. This works because these formats are all MIME entities + that encapsulate other MIME entities. + + An S/MIME implementation MUST be able to receive and process + arbitrarily nested S/MIME within reasonable resource limits of the + recipient computer. + + + + + + +Ramsdell & Turner Standards Track [Page 30] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + It is possible to apply any of the signing, encrypting, and + compressing operations in any order. It is up to the implementer and + the user to choose. When signing first, the signatories are then + securely obscured by the enveloping. When enveloping first the + signatories are exposed, but it is possible to verify signatures + without removing the enveloping. This can be useful in an + environment where automatic signature verification is desired, as no + private key material is required to verify a signature. + + There are security ramifications to choosing whether to sign first or + encrypt first. A recipient of a message that is encrypted and then + signed can validate that the encrypted block was unaltered, but + cannot determine any relationship between the signer and the + unencrypted contents of the message. A recipient of a message that + is signed then encrypted can assume that the signed message itself + has not been altered, but that a careful attacker could have changed + the unauthenticated portions of the encrypted message. + + When using compression, keep the following guidelines in mind: + + - Compression of binary encoded encrypted data is discouraged, + since it will not yield significant compression. Base64 + encrypted data could very well benefit, however. + + - If a lossy compression algorithm is used with signing, you will + need to compress first, then sign. + +3.7. Creating a Certificate Management Message + + The certificate management message or MIME entity is used to + transport certificates and/or Certificate Revocation Lists, such as + in response to a registration request. + + Step 1. The certificates and/or Certificate Revocation Lists are + made available to the CMS generating process that creates a + CMS object of type SignedData. The SignedData + encapContentInfo eContent field MUST be absent and + signerInfos field MUST be empty. + + Step 2. The SignedData object is wrapped in a CMS ContentInfo + object. + + Step 3. The ContentInfo object is enclosed in an + application/pkcs7-mime MIME entity. + + The smime-type parameter for a certificate management message is + "certs-only". The file extension for this type of message is ".p7c". + + + + +Ramsdell & Turner Standards Track [Page 31] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +3.8. Registration Requests + + A sending agent that signs messages MUST have a certificate for the + signature so that a receiving agent can verify the signature. There + are many ways of getting certificates, such as through an exchange + with a certification authority, through a hardware token or diskette, + and so on. + + S/MIME v2 [SMIMEv2] specified a method for "registering" public keys + with certificate authorities using an application/pkcs10 body part. + Since that time, the IETF PKIX Working Group has developed other + methods for requesting certificates. However, S/MIME v3.2 does not + require a particular certificate request mechanism. + +3.9. Identifying an S/MIME Message + + Because S/MIME takes into account interoperation in non-MIME + environments, several different mechanisms are employed to carry the + type information, and it becomes a bit difficult to identify S/MIME + messages. The following table lists criteria for determining whether + or not a message is an S/MIME message. A message is considered an + S/MIME message if it matches any of the criteria listed below. + + The file suffix in the table below comes from the "name" parameter in + the Content-Type header field, or the "filename" parameter on the + Content-Disposition header field. These parameters that give the + file suffix are not listed below as part of the parameter section. + + Media type: application/pkcs7-mime + parameters: any + file suffix: any + + Media type: multipart/signed + parameters: protocol="application/pkcs7-signature" + file suffix: any + + Media type: application/octet-stream + parameters: any + file suffix: p7m, p7s, p7c, p7z + +4. Certificate Processing + + A receiving agent MUST provide some certificate retrieval mechanism + in order to gain access to certificates for recipients of digital + envelopes. This specification does not cover how S/MIME agents + handle certificates, only what they do after a certificate has been + validated or rejected. S/MIME certificate issues are covered in + [CERT32]. + + + +Ramsdell & Turner Standards Track [Page 32] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + At a minimum, for initial S/MIME deployment, a user agent could + automatically generate a message to an intended recipient requesting + that recipient's certificate in a signed return message. Receiving + and sending agents SHOULD also provide a mechanism to allow a user to + "store and protect" certificates for correspondents in such a way so + as to guarantee their later retrieval. + +4.1. Key Pair Generation + + All generated key pairs MUST be generated from a good source of non- + deterministic random input [RANDOM] and the private key MUST be + protected in a secure fashion. + + An S/MIME user agent MUST NOT generate asymmetric keys less than 512 + bits for use with the RSA or DSA signature algorithms. + + For 512-bit RSA with SHA-1 see [CMSALG] and [FIPS186-2] without + Change Notice 1, for 512-bit RSA with SHA-256 see [CMS-SHA2] and + [FIPS186-2] without Change Notice 1, and for 1024-bit through + 2048-bit RSA with SHA-256 see [CMS-SHA2] and [FIPS186-2] with Change + Notice 1. The first reference provides the signature algorithm's + object identifier, and the second provides the signature algorithm's + definition. + + For 512-bit DSA with SHA-1 see [CMSALG] and [FIPS186-2] without + Change Notice 1, for 512-bit DSA with SHA-256 see [CMS-SHA2] and + [FIPS186-2] without Change Notice 1, for 1024-bit DSA with SHA-1 see + [CMSALG] and [FIPS186-2] with Change Notice 1, for 1024-bit and above + DSA with SHA-256 see [CMS-SHA2] and [FIPS186-3]. The first reference + provides the signature algorithm's object identifier and the second + provides the signature algorithm's definition. + + For RSASSA-PSS with SHA-256, see [RSAPSS]. For 1024-bit DH, see + [CMSALG]. For 1024-bit and larger DH, see [SP800-56A]; regardless, + use the KDF, which is from X9.42, specified in [CMSALG]. For RSAES- + OAEP, see [RSAOAEP]. + +4.2. Signature Generation + + The following are the requirements for an S/MIME agent generated RSA, + RSASSA-PSS, and DSA signatures: + + key size <= 1023 : SHOULD NOT (see Security Considerations) + 1024 <= key size <= 2048 : SHOULD (see Security Considerations) + 2048 < key size : MAY (see Security Considerations) + + + + + + +Ramsdell & Turner Standards Track [Page 33] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +4.3. Signature Verification + + The following are the requirements for S/MIME receiving agents during + signature verification of RSA, RSASSA-PSS, and DSA signatures: + + key size <= 1023 : MAY (see Security Considerations) + 1024 <= key size <= 2048 : MUST (see Security Considerations) + 2048 < key size : MAY (see Security Considerations) + +4.4. Encryption + + The following are the requirements for an S/MIME agent when + establishing keys for content encryption using the RSA, RSA-OAEP, and + DH algorithms: + + key size <= 1023 : SHOULD NOT (see Security Considerations) + 1024 <= key size <= 2048 : SHOULD (see Security Considerations) + 2048 < key size : MAY (see Security Considerations) + +4.5. Decryption + + The following are the requirements for an S/MIME agent when + establishing keys for content decryption using the RSA, RSAES-OAEP, + and DH algorithms: + + key size <= 1023 : MAY (see Security Considerations) + 1024 <= key size <= 2048 : MUST (see Security Considerations) + 2048 < key size : MAY (see Security Considerations) + +5. IANA Considerations + + The following information updates the media type registration for + application/pkcs7-mime and application/pkcs7-signature to refer to + this document as opposed to RFC 2311. + + Note that other documents can define additional MIME media types for + S/MIME. + +5.1. Media Type for application/pkcs7-mime + + Type name: application + + Subtype Name: pkcs7-mime + + Required Parameters: NONE + + + + + + +Ramsdell & Turner Standards Track [Page 34] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Optional Parameters: smime-type/signed-data + smime-type/enveloped-data + smime-type/compressed-data + smime-type/certs-only + name + + Encoding Considerations: See Section 3 of this document + + Security Considerations: See Section 6 of this document + + Interoperability Considerations: See Sections 1-6 of this document + + Published Specification: RFC 2311, RFC 2633, and this document + + Applications that use this media type: Security applications + + Additional information: NONE + + Person & email to contact for further information: + S/MIME working group chairs smime-chairs@tools.ietf.org + + Intended usage: COMMON + + Restrictions on usage: NONE + + Author: Sean Turner + + Change Controller: S/MIME working group delegated from the IESG + +5.2. Media Type for application/pkcs7-signature + + Type name: application + + Subtype Name: pkcs7-signature + + Required Parameters: NONE + + Optional Parameters: NONE + + Encoding Considerations: See Section 3 of this document + + Security Considerations: See Section 6 of this document + + Interoperability Considerations: See Sections 1-6 of this document + + Published Specification: RFC 2311, RFC 2633, and this document + + Applications that use this media type: Security applications + + + +Ramsdell & Turner Standards Track [Page 35] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Additional information: NONE + + Person & email to contact for further information: + S/MIME working group chairs smime-chairs@tools.ietf.org + + Intended usage: COMMON + + Restrictions on usage: NONE + + Author: Sean Turner + + Change Controller: S/MIME working group delegated from the IESG + +6. Security Considerations + + Cryptographic algorithms will be broken or weakened over time. + Implementers and users need to check that the cryptographic + algorithms listed in this document continue to provide the expected + level of security. The IETF from time to time may issue documents + dealing with the current state of the art. For example: + + - The Million Message Attack described in RFC 3218 [MMA]. + + - The Diffie-Hellman "small-subgroup" attacks described in RFC + 2785 [DHSUB]. + + - The attacks against hash algorithms described in RFC 4270 [HASH- + ATTACK]. + + This specification uses Public-Key Cryptography technologies. It is + assumed that the private key is protected to ensure that it is not + accessed or altered by unauthorized parties. + + It is impossible for most people or software to estimate the value of + a message's content. Further, it is impossible for most people or + software to estimate the actual cost of recovering an encrypted + message content that is encrypted with a key of a particular size. + Further, it is quite difficult to determine the cost of a failed + decryption if a recipient cannot process a message's content. Thus, + choosing between different key sizes (or choosing whether to just use + plaintext) is also impossible for most people or software. However, + decisions based on these criteria are made all the time, and + therefore this specification gives a framework for using those + estimates in choosing algorithms. + + The choice of 2048 bits as the RSA asymmetric key size in this + specification is based on the desire to provide at least 100 bits of + security. The key sizes that must be supported to conform to this + + + +Ramsdell & Turner Standards Track [Page 36] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + specification seem appropriate for the Internet based on [STRENGTH]. + Of course, there are environments, such as financial and medical + systems, that may select different key sizes. For this reason, an + implementation MAY support key sizes beyond those recommended in this + specification. + + Receiving agents that validate signatures and sending agents that + encrypt messages need to be cautious of cryptographic processing + usage when validating signatures and encrypting messages using keys + larger than those mandated in this specification. An attacker could + send certificates with keys that would result in excessive + cryptographic processing, for example, keys larger than those + mandated in this specification, which could swamp the processing + element. Agents that use such keys without first validating the + certificate to a trust anchor are advised to have some sort of + cryptographic resource management system to prevent such attacks. + + Using weak cryptography in S/MIME offers little actual security over + sending plaintext. However, other features of S/MIME, such as the + specification of AES and the ability to announce stronger + cryptographic capabilities to parties with whom you communicate, + allow senders to create messages that use strong encryption. Using + weak cryptography is never recommended unless the only alternative is + no cryptography. + + RSA and DSA keys of less than 1024 bits are now considered by many + experts to be cryptographically insecure (due to advances in + computing power), and should no longer be used to protect messages. + Such keys were previously considered secure, so processing previously + received signed and encrypted mail will often result in the use of + weak keys. Implementations that wish to support previous versions of + S/MIME or process old messages need to consider the security risks + that result from smaller key sizes (e.g., spoofed messages) versus + the costs of denial of service. If an implementation supports + verification of digital signatures generated with RSA and DSA keys of + less than 1024 bits, it MUST warn the user. Implementers should + consider providing different warnings for newly received messages and + previously stored messages. Server implementations (e.g., secure + mail list servers) where user warnings are not appropriate SHOULD + reject messages with weak signatures. + + Implementers SHOULD be aware that multiple active key pairs can be + associated with a single individual. For example, one key pair can + be used to support confidentiality, while a different key pair can be + used for digital signatures. + + + + + + +Ramsdell & Turner Standards Track [Page 37] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + If a sending agent is sending the same message using different + strengths of cryptography, an attacker watching the communications + channel might be able to determine the contents of the strongly + encrypted message by decrypting the weakly encrypted version. In + other words, a sender SHOULD NOT send a copy of a message using + weaker cryptography than they would use for the original of the + message. + + Modification of the ciphertext can go undetected if authentication is + not also used, which is the case when sending EnvelopedData without + wrapping it in SignedData or enclosing SignedData within it. + + If an implementation is concerned about compliance with National + Institute of Standards and Technology (NIST) key size + recommendations, then see [SP800-57]. + + If messaging environments make use of the fact that a message is + signed to change the behavior of message processing (examples would + be running rules or UI display hints), without first verifying that + the message is actually signed and knowing the state of the + signature, this can lead to incorrect handling of the message. + Visual indicators on messages may need to have the signature + validation code checked periodically if the indicator is supposed to + give information on the current status of a message. + +7. References + +7.1. Reference Conventions + + [CMS] refers to [RFC5652]. + + [ESS] refers to [RFC2634] and [RFC5035]. + + [MIME] refers to [RFC2045], [RFC2046], [RFC2047], [RFC2049], + [RFC4288], and [RFC4289]. + + [SMIMEv2] refers to [RFC2311], [RFC2312], [RFC2313], [RFC2314], and + [RFC2315]. + + [SMIMEv3] refers to [RFC2630], [RFC2631], [RFC2632], [RFC2633], + [RFC2634], and [RFC5035]. + + [SMIMv3.1] refers to [RFC2634], [RFC3850], [RFC3851], [RFC3852], and + [RFC5035]. + + + + + + + +Ramsdell & Turner Standards Track [Page 38] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +7.2. Normative References + + [CERT32] Ramsdell, B. and S. Turner, "Secure/Multipurpose + Internet Mail Extensions (S/MIME) Version 3.2 + Certificate Handling", RFC 5750, January 2010. + + [CHARSETS] Character sets assigned by IANA. See + http://www.iana.org/assignments/character-sets. + + [CMSAES] Schaad, J., "Use of the Advanced Encryption Standard + (AES) Encryption Algorithm in Cryptographic Message + Syntax (CMS)", RFC 3565, July 2003. + + [CMSALG] Housley, R., "Cryptographic Message Syntax (CMS) + Algorithms", RFC 3370, August 2002. + + [CMSCOMPR] Gutmann, P., "Compressed Data Content Type for + Cryptographic Message Syntax (CMS)", RFC 3274, June + 2002. + + [CMS-SHA2] Turner, S., "Using SHA2 Algorithms with Cryptographic + Message Syntax", RFC 5754, January 2010. + + [CONTDISP] Troost, R., Dorner, S., and K. Moore, Ed., + "Communicating Presentation Information in Internet + Messages: The Content-Disposition Header Field", RFC + 2183, August 1997. + + [FIPS186-2] National Institute of Standards and Technology (NIST), + "Digital Signature Standard (DSS)", FIPS Publication + 186-2, January 2000. [With Change Notice 1]. + + [FIPS186-3] National Institute of Standards and Technology (NIST), + FIPS Publication 186-3: Digital Signature Standard, + June 2009. + + [MIME-SECURE] Galvin, J., Murphy, S., Crocker, S., and N. Freed, + "Security Multiparts for MIME: Multipart/Signed and + Multipart/Encrypted", RFC 1847, October 1995. + + [MUSTSHOULD] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RANDOM] Eastlake, D., 3rd, Schiller, J., and S. Crocker, + "Randomness Requirements for Security", BCP 106, RFC + 4086, June 2005. + + + + + +Ramsdell & Turner Standards Track [Page 39] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + [RFC2045] Freed, N. and N. Borenstein, "Multipurpose Internet + Mail Extensions (MIME) Part One: Format of Internet + Message Bodies", RFC 2045, November 1996. + + [RFC2046] Freed, N. and N. Borenstein, "Multipurpose Internet + Mail Extensions (MIME) Part Two: Media Types", RFC + 2046, November 1996. + + [RFC2047] Moore, K., "MIME (Multipurpose Internet Mail + Extensions) Part Three: Message Header Extensions for + Non-ASCII Text", RFC 2047, November 1996. + + [RFC2049] Freed, N. and N. Borenstein, "Multipurpose Internet + Mail Extensions (MIME) Part Five: Conformance Criteria + and Examples", RFC 2049, November 1996. + + [RFC2634] Hoffman, P. Ed., "Enhanced Security Services for + S/MIME", RFC 2634, June 1999. + + [RFC4288] Freed, N. and J. Klensin, "Media Type Specifications + and Registration Procedures", BCP 13, RFC 4288, + December 2005. + + [RFC4289] Freed, N. and J. Klensin, "Multipurpose Internet Mail + Extensions (MIME) Part Four: Registration Procedures", + BCP 13, RFC 4289, December 2005. + + [RFC5035] Schaad, J., "Enhanced Security Services (ESS) Update: + Adding CertID Algorithm Agility", RFC 5035, August + 2007. + + [RFC5652] Housley, R., "Cryptographic Message Syntax (CMS)", RFC + 5652, September 2009. + + [RSAOAEP] Housley, R. "Use of the RSAES-OAEP Key Transport + Algorithm in the Cryptographic Message Syntax (CMS)", + RFC 3560, July 2003. + + [RSAPSS] Schaad, J., "Use of the RSASSA-PSS Signature Algorithm + in Cryptographic Message Syntax (CMS)", RFC 4056, June + 2005. + + [SP800-56A] National Institute of Standards and Technology (NIST), + Special Publication 800-56A: Recommendation Pair-Wise + Key Establishment Schemes Using Discrete Logarithm + Cryptography (Revised), March 2007. + + + + + +Ramsdell & Turner Standards Track [Page 40] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + [X.680] ITU-T Recommendation X.680 (2002) | ISO/IEC + 8824-1:2002. Information Technology - Abstract Syntax + Notation One (ASN.1): Specification of basic notation. + + [X.690] ITU-T Recommendation X.690 (2002) | ISO/IEC + 8825-1:2002. Information Technology - ASN.1 encoding + rules: Specification of Basic Encoding Rules (BER), + Canonical Encoding Rules (CER) and Distinguished + Encoding Rules (DER). + +7.3. Informative References + + [DHSUB] Zuccherato, R., "Methods for Avoiding the "Small- + Subgroup" Attacks on the Diffie-Hellman Key Agreement + Method for S/MIME", RFC 2785, March 2000. + + [HASH-ATTACK] Hoffman, P. and B. Schneier, "Attacks on Cryptographic + Hashes in Internet Protocols", RFC 4270, November 2005. + + [MMA] Rescorla, E., "Preventing the Million Message Attack on + Cryptographic Message Syntax", RFC 3218, January 2002. + + [PKCS-7] Kaliski, B., "PKCS #7: Cryptographic Message Syntax + Version 1.5", RFC 2315, March 1998. + + [RFC2311] Dusse, S., Hoffman, P., Ramsdell, B., Lundblade, L., + and L. Repka, "S/MIME Version 2 Message Specification", + RFC 2311, March 1998. + + [RFC2312] Dusse, S., Hoffman, P., Ramsdell, B., and J. + Weinstein, "S/MIME Version 2 Certificate Handling", RFC + 2312, March 1998. + + [RFC2313] Kaliski, B., "PKCS #1: RSA Encryption Version 1.5", RFC + 2313, March 1998. + + [RFC2314] Kaliski, B., "PKCS #10: Certification Request Syntax + Version 1.5", RFC 2314, March 1998. + + [RFC2315] Kaliski, B., "PKCS #7: Certification Message Syntax + Version 1.5", RFC 2315, March 1998. + + [RFC2630] Housley, R., "Cryptographic Message Syntax", RFC 2630, + June 1999. + + [RFC2631] Rescorla, E., "Diffie-Hellman Key Agreement Method", + RFC 2631, June 1999. + + + + +Ramsdell & Turner Standards Track [Page 41] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + [RFC2632] Ramsdell, B., Ed., "S/MIME Version 3 Certificate + Handling", RFC 2632, June 1999. + + [RFC2633] Ramsdell, B., Ed., "S/MIME Version 3 Message + Specification", RFC 2633, June 1999. + + [RFC3850] Ramsdell, B., Ed., "Secure/Multipurpose Internet Mail + Extensions (S/MIME) Version 3.1 Certificate Handling", + RFC 3850, July 2004. + + [RFC3851] Ramsdell, B., Ed., "Secure/Multipurpose Internet Mail + Extensions (S/MIME) Version 3.1 Message Specification", + RFC 3851, July 2004. + + [RFC3852] Housley, R., "Cryptographic Message Syntax (CMS)", RFC + 3852, July 2004. + + [SP800-57] National Institute of Standards and Technology (NIST), + Special Publication 800-57: Recommendation for Key + Management, August 2005. + + [STRENGTH] Orman, H., and P. Hoffman, "Determining Strengths For + Public Keys Used For Exchanging Symmetric Keys", BCP + 86, RFC 3766, April 2004. + + + + + + + + + + + + + + + + + + + + + + + + + + + +Ramsdell & Turner Standards Track [Page 42] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +Appendix A. ASN.1 Module + + Note: The ASN.1 module contained herein is unchanged from RFC 3851 + [SMIMEv3.1] with the exception of a change to the prefersBinaryInside + ASN.1 comment. This module uses the 1988 version of ASN.1. + + SecureMimeMessageV3dot1 + + { iso(1) member-body(2) us(840) rsadsi(113549) + pkcs(1) pkcs-9(9) smime(16) modules(0) msg-v3dot1(21) } + + DEFINITIONS IMPLICIT TAGS ::= + + BEGIN + + IMPORTS + + -- Cryptographic Message Syntax [CMS] + SubjectKeyIdentifier, IssuerAndSerialNumber, + RecipientKeyIdentifier + FROM CryptographicMessageSyntax + { iso(1) member-body(2) us(840) rsadsi(113549) + pkcs(1) pkcs-9(9) smime(16) modules(0) cms-2001(14) }; + + -- id-aa is the arc with all new authenticated and unauthenticated + -- attributes produced by the S/MIME Working Group + + id-aa OBJECT IDENTIFIER ::= {iso(1) member-body(2) usa(840) + rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) attributes(2)} + + -- S/MIME Capabilities provides a method of broadcasting the + -- symmetric capabilities understood. Algorithms SHOULD be ordered + -- by preference and grouped by type + + smimeCapabilities OBJECT IDENTIFIER ::= {iso(1) member-body(2) + us(840) rsadsi(113549) pkcs(1) pkcs-9(9) 15} + + SMIMECapability ::= SEQUENCE { + capabilityID OBJECT IDENTIFIER, + parameters ANY DEFINED BY capabilityID OPTIONAL } + + SMIMECapabilities ::= SEQUENCE OF SMIMECapability + + -- Encryption Key Preference provides a method of broadcasting the + -- preferred encryption certificate. + + id-aa-encrypKeyPref OBJECT IDENTIFIER ::= {id-aa 11} + + + + +Ramsdell & Turner Standards Track [Page 43] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + SMIMEEncryptionKeyPreference ::= CHOICE { + issuerAndSerialNumber [0] IssuerAndSerialNumber, + receipentKeyId [1] RecipientKeyIdentifier, + subjectAltKeyIdentifier [2] SubjectKeyIdentifier + } + + -- receipentKeyId is spelt incorrectly, but kept for historical + -- reasons. + + id-smime OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) + rsadsi(113549) pkcs(1) pkcs9(9) 16 } + + id-cap OBJECT IDENTIFIER ::= { id-smime 11 } + + -- The preferBinaryInside OID indicates an ability to receive + -- messages with binary encoding inside the CMS wrapper. + -- The preferBinaryInside attribute's value field is ABSENT. + + id-cap-preferBinaryInside OBJECT IDENTIFIER ::= { id-cap 1 } + + -- The following list OIDs to be used with S/MIME V3 + + -- Signature Algorithms Not Found in [CMSALG], [CMS-SHA2], [RSAPSS], + -- and [RSAOAEP] + + -- + -- md2WithRSAEncryption OBJECT IDENTIFIER ::= + -- {iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) + -- 2} + + -- + -- Other Signed Attributes + -- + -- signingTime OBJECT IDENTIFIER ::= + -- {iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-9(9) + -- 5} + -- See [CMS] for a description of how to encode the attribute + -- value. + + SMIMECapabilitiesParametersForRC2CBC ::= INTEGER + -- (RC2 Key Length (number of bits)) + + END + + + + + + + + +Ramsdell & Turner Standards Track [Page 44] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +Appendix B. Moving S/MIME v2 Message Specification to Historic Status + + The S/MIME v3 [SMIMEv3], v3.1 [SMIMEv3.1], and v3.2 (this document) + are backwards compatible with the S/MIME v2 Message Specification + [SMIMEv2], with the exception of the algorithms (dropped RC2/40 + requirement and added DSA and RSASSA-PSS requirements). Therefore, + it is recommended that RFC 2311 [SMIMEv2] be moved to Historic + status. + +Appendix C. Acknowledgments + + Many thanks go out to the other authors of the S/MIME version 2 + Message Specification RFC: Steve Dusse, Paul Hoffman, Laurence + Lundblade, and Lisa Repka. Without v2, there wouldn't be a v3, v3.1, + or v3.2. + + A number of the members of the S/MIME Working Group have also worked + very hard and contributed to this document. Any list of people is + doomed to omission, and for that I apologize. In alphabetical order, + the following people stand out in my mind because they made direct + contributions to this document: + + Tony Capel, Piers Chivers, Dave Crocker, Bill Flanigan, Peter + Gutmann, Alfred Hoenes, Paul Hoffman, Russ Housley, William Ottaway, + John Pawling, and Jim Schaad. + +Authors' Addresses + + Blake Ramsdell + Brute Squad Labs, Inc. + + EMail: blaker@gmail.com + + + Sean Turner + IECA, Inc. + 3057 Nutley Street, Suite 106 + Fairfax, VA 22031 + USA + + EMail: turners@ieca.com + + + + + + + + + + +Ramsdell & Turner Standards Track [Page 45] + diff --git a/vendor/swiftmailer/swiftmailer/notes/rfc/whats_where.txt b/vendor/swiftmailer/swiftmailer/notes/rfc/whats_where.txt new file mode 100644 index 0000000..201a5f7 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/rfc/whats_where.txt @@ -0,0 +1,90 @@ +RFC 1854: +--------- +PIPELINING extension + +RFC 2222: +--------- +SASL + +RFC 4505: +--------- +ANYNONYMOUS SASL + +RFC 4616: +--------- +PLAIN SASL + +RFC 2487: +--------- +STARTTLS extension + +RFC 2554 & 4954: +---------------- +AUTH extension + +RFC 2821: +--------- +SMTP protocol + +RFC 2822: +--------- +General message structure (focusing on important headers) + +RFC 2045: +--------- +Quoted Printable Encoding +Base 64 Encoding +Detailed message structure + +RFC 2046: +--------- +Media types (for subparts) + +RFC 2047: +--------- +Header Encoding + +RFC 2183: +--------- +The Content-Disposition header + +RFC 2231: +--------- +Encoded Text header/attribute extensions + +RFC 2234: +--------- +ABNF definitions + +RFC 3676: +--------- +Flowed formatting/delsp parameters + +RFC 2015: +--------- +Original Mime Security with OpenPGP + +RFC 2440: +--------- +Original OpenPGP message format + +RFC 3156: +--------- +Current (30/01/2009) Mime Security with OpenPGP + +RFC 4880: +--------- +Current (30/01/2009) OpenPGP Message format revision (fun, it's exactly original *2 --sorry) + +RFC 5751: +-------- +Secure/Multipurpose Internet Mail Extensions (S/MIME) Version 3.2 + +RFC 4870: +--------- +DomainKeys (historic), but still used by a few people. + +RFX 4871: +--------- +DKIM + diff --git a/vendor/swiftmailer/swiftmailer/notes/smtp.txt b/vendor/swiftmailer/swiftmailer/notes/smtp.txt new file mode 100644 index 0000000..db73744 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/notes/smtp.txt @@ -0,0 +1,48 @@ +General Notes +-------------- + * MX is NOT required, but an A record, or CNAME to a MX MUST be present at the least. + * EHLO should be tried, then fall back to HELO + * The 250 return code from RCPT TO is not actually clear-cut. A 251 may be + returned if the message was forwarded to another address. This could be a + useful indicator to end-users that an address should be updated. + * RCPT TO can accpet just "postmaster" without a domain name + * Server MUST not close connection before: + - QUIT and returning 221 response + - Forced requirement, and only after returning a 421 response + - Clients expriencing a forced connection closure, without prior warning should + just treat it like a 451 closure regardless + * ALWAYS use blocking sockets for the initial connection (this should prevent + Exim4's whining about sync). + +Response codes +-------------- + * From RFC2821, 4.2. + - In particular, the 220, 221, 251, 421, and 551 reply codes + are associated with message text that must be parsed and interpreted + by machines. + +Error Codes +------------ + * Numeric 5yz = Error + - 550/RCPT TO = Relay denied + - 500 = Unknown command + * Numeric 2yz = Normal + * Numeric 4yz = Temporary failure?? + + + + Swift + pear.swiftmailer.org + Free Feature-rich PHP Mailer. + + Swift Mailer integrates into any web app written in PHP 5, offering a flexible and elegant object-oriented approach to sending emails with a multitude of features. + + + Fabien Potencier + fabpot + fabien.potencier@symfony-project.org + yes + + + Chris Corbyn + d11wtq + + no + + {{ date }} + + + {{ version }} + {{ api_version }} + + + {{ stability }} + {{ stability }} + + MIT + - + + + + + + + + + + + + + + + + + + + + +{{ files }} + + + + + + + + + 5.2.4 + + + 1.4.0 + + + + + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/CHANGES b/vendor/swiftmailer/swiftmailer/test-suite/CHANGES new file mode 100644 index 0000000..6229a4c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/CHANGES @@ -0,0 +1,7 @@ +Sweety SimpleTest Front-end, Version 0.1 - beta +----------------------------------------------- + +17th November 2007, 0.1b: + Finished initial draft, giving up on dealing with invalid XML for now since + $test->dump() should be used for output where needed. Will address this again + later. diff --git a/vendor/swiftmailer/swiftmailer/test-suite/LICENSE b/vendor/swiftmailer/swiftmailer/test-suite/LICENSE new file mode 100644 index 0000000..fc8a5de --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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. diff --git a/vendor/swiftmailer/swiftmailer/test-suite/README b/vendor/swiftmailer/swiftmailer/test-suite/README new file mode 100644 index 0000000..6348255 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/README @@ -0,0 +1,159 @@ +Sweety SimpleTest Suite +----------------------- + +Sweety is a wrapper around SimpleTest's XML reporting capabilities which +makes unit tests easier to manage and friendlier to run. + +Tests are run in a grouped fashion, but each individual test runs in its own +environment and own memory space either via forking new PHP processes, or by +making new HTTP requests. + +Sweety works with any vanilla version of SimpleTest since the XmlReporter was +added. + +Tests can be run on command line, in an AJAX equipped web browser*, or in a +web browser with javascript turned off. + + * Sweety has been tested with success in the following browsers: + + - Mozilla Firefox 2.0 + - Safari 3-beta + - Internet Explorer 7 + - Opera 9 + + +Configuring Sweety: +-------------------- + +All Sweety configuration is contained inside the config.php file, defined as +constants for the mostpart. + +Make sure you at least indicate a path to a directory containing SimpleTest, +and also change the SWEETY_INCLUDE_PATH and SWEETY_TEST_PATH to fit your needs. + +Paths are provided using the directory separator for your OS. Use the PHP +constant PATH_SEPARATOR if you need to run in different environments. + +If you have test cases in directories /webdev/tests/unit and +/webdev/tests/integration your SWEETY_TEST_PATH should look like: + +define('SWEETY_TEST_PATH', '/webdev/tests/unit' . PATH_SEPARATOR . + '/webdev/tests/integration'); + +If you want to run Sweety on the command line you'll need to specify the path +to your PHP executable (typically /usr/bin/php). Sweety needs to be able to +fork new processes using this executable. + + +What to do if your naming scheme doesn't use PEAR conventions: +-------------------------------------------------------------- + +By default Sweety looks for classes using PEAR naming conventions. If you use +some other naming convention you need to tell Sweety how to find your test cases. + +This is a two step process: + +1) Write a new Sweety_TestLocator -- don't worry, it's easy! + + Refer to the interface in lib/Sweety/TestLocator.php for guidance on what + your TestLocator needs to include (just two methods for searching and including). + +2) Add it to your config.php. + + Once you've written a new TestLocator which works for your naming scheme, + change the config value SWEETY_TEST_LOCATOR to the name of your new class, then + include the class file somewhere inside the config.php. If you use multiple class + naming conventions, list your TestLocators as a comma separated string. + + +Making tests appear in Sweety's interface: +------------------------------------------- + +No really, you just edit the configuration and they'll show up if a TestLocator +can find them ;) + + +Running sweety on the command line: +----------------------------------- + +Interacting with Sweety on the command line you'll get almost as much detail +as you do in a web browser, although the formatting obviously isn't so pretty! + +All operations are handled by the file named run.php in the sweety installation +directory. + + -bash$ php run.php #runs all tests + -bash$ php run.php Name_Of_TestClass #runs a single test case + -bash$ php run.php Name_Of_TestClass xml #runs a single test case in XML + + + +Runing Sweety with AJAX: +------------------------- + +Open up an AJAX equipped web browsers (preferably supporting DOM 3 XPath, but at +least support basic DOM). Navigate to the index.php file at the installation +directory of Sweety. You'll see the screen is divided into two sections, left +and right. On the left there's a list of test cases which you can click to run. +On the right you get all the verbose output from running the tests. + +Clicking the "Run Tests" button will run all tests you can currently see in +the list. As each test runs, a request is sent to the web server to get +SimpleTest to run your test case. If the test passes the test case will turn +green, if it fails it will turn red. Tests go yellow until a final conclusion +is drawn. + +If you need to stop the tests at any time just click the button again (it +should say "Stop Tests" whilst the tests run). + +Whilst the tests run, the large bar to the right of the screen will tally +up aggregated results and will eventually go either green or red indicating +a pass or failure. Failed assertion messages will appear in the page just like +they do with the HtmlReporter of SimpleTest. + +Clicking a single test case will run just that test in isolation. If you want +(or need?) to run the test with SimpleTest's HtmlReporter just click the +HTML Icon (little world image) next to the test case. Tests can also be run in +XML if needed. + +Above the list of tests there's a filter box which can be directly typed into. +Typing in here narrows down the list of testcases to show only the ones which +match the search query. + +Refreshing the page with your browser's refresh button will reset the test suite. + +When you change your code, you DO NOT need to refesh your browser window. Just +click the "Run Tests" button, or click the individual test to refresh the results. + +If you add a new test case you will need to refresh your browser window however. + + +Running Sweety without JavaScript: +---------------------------------- + +If your web browser has JavaScript disabled you can still use the HTML version +of the test suite. Open up the index.php file in your web browser. + +You'll see the screen is divided into two sections, left +and right. On the left there's a list of test cases with checkboxes next to them +which you can check to run. On the right you get all the verbose output from +running the tests. + +Clicking the "Run Tests" button will run all the currently selected test cases. +This could take a long time depending upon how many tests you have to run, +but once complete you'll see the page again where the tests you selected will +either be red or green indicating a pass or failure. The bar at the right of +the screen will contain aggregate results for all the tests and will be either +red or green to indicate an overall pass or failure. + +Assertion messages appear in the page just like with SimpleTest's HtmlReporter. + +If you want to run just a single test case, click the "Run" icon at the right +of the test (little running man image). + +You can run tests with SimpleTest's HtmlReporter by clicking the HTML icon +(little world image) next to the test case. Tests can be run in XML if needed +too. + +Enjoy! + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/config.php b/vendor/swiftmailer/swiftmailer/test-suite/config.php new file mode 100644 index 0000000..c363b03 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/config.php @@ -0,0 +1,68 @@ +setReporter(new Sweety_Reporter_HtmlReporter()); + +$runner->setIgnoredClassRegex(SWEETY_IGNORED_CLASSES); + +$locators = preg_split('/\s*,\s*/', SWEETY_TEST_LOCATOR); +foreach ($locators as $locator) +{ + $runner->registerTestLocator(new $locator()); +} + +if (isset($_GET['test'])) +{ + $testName = $_GET['test']; + $format = isset($_GET['format']) ? $_GET['format'] : Sweety_Runner::REPORT_HTML; + + $runner->runTestCase($testName, $format); +} +else +{ + $runner->runAllTests(); +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Reporter.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Reporter.php new file mode 100644 index 0000000..345bdc0 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Reporter.php @@ -0,0 +1,69 @@ +_name = $name; + $this->_aggregates = array( + 'cases' => 0, + 'run' => 0, + 'passes' => 0, + 'fails' => 0, + 'exceptions' => 0 + ); + } + + /** + * Used so test case reporters can notify this reporter when they've completed. + * @param string $testCase + */ + public function notifyEnded($testCase) + { + $this->_aggregates['run']++; + } + + /** + * Get the reporter used to report on this specific test case. + * @param string $testCase + * @return Sweety_Reporter + */ + public function getReporterFor($testCase) + { + $this->_aggregates['cases']++; + + $reporter = new Sweety_Reporter_CliTestCaseReporter($testCase, $this); + return $reporter; + } + + /** + * Returns true if start() has been invoked. + * @return boolean + */ + public function isStarted() + { + return $this->_started; + } + + /** + * Start reporting. + */ + public function start() + { + $this->_started = true; + echo $this->_name . PHP_EOL; + } + + /** + * Report a skipped test case. + * @param string $message + * @param string $path + */ + public function reportSkip($message, $path) + { + echo " \033[34m\033[1m\033[4mSkip\033[0m:"; + $messageLines = explode(PHP_EOL, wordwrap($message, 74, PHP_EOL)); + foreach ($messageLines as $line) + { + echo ' ' . $line . PHP_EOL; + } + echo ' in: ' . $path . PHP_EOL; + } + + /** + * Report a passing assertion. + * @param string $message + * @param string $path + */ + public function reportPass($message, $path) + { + $this->_aggregates['passes']++; + } + + /** + * Report a failing assertion. + * @param string $message + * @param string $path + */ + public function reportFail($message, $path) + { + $this->_aggregates['fails']++; + + echo "\033[31m" . $this->_aggregates['fails'] . ') '; + echo $message . "\033[0m" . PHP_EOL; + echo ' in: ' . $path . PHP_EOL; + } + + /** + * Report an unexpected exception. + * @param string $message + * @param string $path + */ + public function reportException($message, $path) + { + $this->_aggregates['exceptions']++; + + echo "\033[31m\033[1mException" . $this->_aggregates['exceptions'] . "\033[0m!" . PHP_EOL; + echo "\033[1m" . $message . "\033[0m" . PHP_EOL; + echo ' in ' . $path . PHP_EOL; + } + + /** + * Report output from something like a dump(). + * @param string $output + * @param string $path + */ + public function reportOutput($output, $path) + { + if (preg_match('/^\{image @ (.*?)\}$/D', $output, $matches)) + { + echo " \033[33mSmoke Test\033[0m" . PHP_EOL; + echo ' Compare email sent with image @ ' . $matches[1] . PHP_EOL; + } + else + { + echo '--------------------' . PHP_EOL; + echo $output . PHP_EOL; + echo '--------------------' . PHP_EOL; + } + } + + /** + * End reporting. + */ + public function finish() + { + $this->_started = false; + + $incomplete = $this->_aggregates['cases'] - $this->_aggregates['run']; + + if ($incomplete) + { + echo '**********************' . PHP_EOL; + echo $incomplete . ' test case(s) did not complete.' . PHP_EOL . + 'This may be because invalid XML was output during the test run' . PHP_EOL . + 'and/or because an error occured.' . PHP_EOL . + 'Try running the tests separately for more detail.' . PHP_EOL; + echo '**********************' . PHP_EOL; + } + + $success = (!$this->_aggregates['fails'] && !$this->_aggregates['exceptions'] + && $this->_aggregates['cases'] == $this->_aggregates['run']); + + if ($success) + { + echo "\033[32m\033[1mOK\033[0m" . PHP_EOL; + } + else + { + echo "\033[31m\033[1mFAILURES!!!\033[0m" . PHP_EOL; + } + + echo 'Test cases run: '; + echo $this->_aggregates['run'] . '/' . $this->_aggregates['cases'] . ', '; + echo 'Passes: ' . $this->_aggregates['passes'] . ', '; + echo 'Failures: ' . $this->_aggregates['fails'] . ', '; + echo 'Exceptions: '. $this->_aggregates['exceptions'] . PHP_EOL; + + exit((int) !$success); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Reporter/CliTestCaseReporter.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Reporter/CliTestCaseReporter.php new file mode 100644 index 0000000..ba23834 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Reporter/CliTestCaseReporter.php @@ -0,0 +1,160 @@ +_parent = $parent; + $this->_testCase = $testCase; + $this->_aggregates = array( + 'passes' => 0, + 'fails' => 0, + 'exceptions' => 0 + ); + } + + /** + * Get the reporter used to report on this specific test case. + * This method is stubbed only to return itself. + * @param string $testCase + * @return Sweety_Reporter + */ + public function getReporterFor($testCase) + { + return $this; + } + + /** + * Returns true if start() has been invoked. + * @return boolean + */ + public function isStarted() + { + return $this->_started; + } + + /** + * Start reporting. + */ + public function start() + { + $this->_started = true; + } + + /** + * Report a skipped test case. + * @param string $message + * @param string $path + */ + public function reportSkip($message, $path) + { + $this->_parent->reportSkip($message, $path); + } + + /** + * Report a passing assertion. + * @param string $message + * @param string $path + */ + public function reportPass($message, $path) + { + $this->_aggregates['passes']++; + $this->_parent->reportPass($message, $path); + } + + /** + * Report a failing assertion. + * @param string $message + * @param string $path + */ + public function reportFail($message, $path) + { + $this->_aggregates['fails']++; + $this->_parent->reportFail($message, $path); + } + + /** + * Report an unexpected exception. + * @param string $message + * @param string $path + */ + public function reportException($message, $path) + { + $this->_aggregates['exceptions']++; + $this->_parent->reportException($message, $path); + } + + /** + * Report output from something like a dump(). + * @param string $output + * @param string $path + */ + public function reportOutput($output, $path) + { + $this->_parent->reportOutput($output, $path); + } + + /** + * End reporting. + */ + public function finish() + { + $this->_started = false; + + if (!$this->_aggregates['fails'] && !$this->_aggregates['exceptions']) + { + echo ' >> ' . $this->_testCase . ' '; + echo "\033[32mOK\033[0m" . PHP_EOL; + } + else + { + echo ' !! ' . $this->_testCase . ' '; + echo "\033[31mFAILED\033[0m" . PHP_EOL; + } + + $this->_parent->notifyEnded($this->_testCase); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Reporter/HtmlReporter.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Reporter/HtmlReporter.php new file mode 100644 index 0000000..6a15339 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Reporter/HtmlReporter.php @@ -0,0 +1,174 @@ +_tplVars =& $vars; + } + + /** + * Used so test case reporters can notify this reporter when they've completed. + * @param string $testCase + */ + public function notifyEnded($testCase) + { + $this->_tplVars['runTests'][] = $testCase; + $this->_tplVars['runCount']++; + } + + /** + * Get the reporter used to report on this specific test case. + * @param string $testCase + * @return Sweety_Reporter + */ + public function getReporterFor($testCase) + { + $this->_tplVars['caseCount']++; + + $reporter = new Sweety_Reporter_HtmlTestCaseReporter($testCase, $this); + $reporter->setTemplateVars($this->_tplVars); + return $reporter; + } + + /** + * Returns true if start() has been invoked. + * @return boolean + */ + public function isStarted() + { + return $this->_started; + } + + /** + * Start reporting. + */ + public function start() + { + $this->_started = true; + } + + /** + * Report a skipped test case. + * @param string $message + * @param string $path + */ + public function reportSkip($message, $path) + { + $this->_tplVars['messages'][] = array( + 'type' => 'skip', + 'path' => $path, + 'text' => $message); + } + + /** + * Report a passing assertion. + * @param string $message + * @param string $path + */ + public function reportPass($message, $path) + { + $this->_tplVars['passCount']++; + } + + /** + * Report a failing assertion. + * @param string $message + * @param string $path + */ + public function reportFail($message, $path) + { + $this->_tplVars['failCount']++; + $this->_tplVars['messages'][] = array( + 'type' => 'fail', + 'path' => $path, + 'text' => $message); + } + + /** + * Report an unexpected exception. + * @param string $message + * @param string $path + */ + public function reportException($message, $path) + { + $this->_tplVars['exceptionCount']++; + $this->_tplVars['messages'][] = array( + 'type' => 'exception', + 'path' => $path, + 'text' => $message); + } + + /** + * Report output from something like a dump(). + * @param string $output + * @param string $path + */ + public function reportOutput($output, $path) + { + $this->_tplVars['messages'][] = array( + 'type' => 'output', + 'path' => $path, + 'text' => $output); + } + + /** + * End reporting. + */ + public function finish() + { + $this->_started = false; + + if (!$this->_tplVars['failCount'] && !$this->_tplVars['exceptionCount'] + && $this->_tplVars['caseCount'] == $this->_tplVars['runCount']) + { + $this->_tplVars['result'] = 'pass'; + } + else + { + $this->_tplVars['result'] = 'fail'; + } + + $incomplete = $this->_tplVars['caseCount'] - $this->_tplVars['runCount']; + + if (0 < $incomplete) + { + $this->_tplVars['messages'][] = array( + 'type' => 'internal', + 'path' => '', + 'text' => $incomplete . ' test case(s) did not complete.' . + ' This may be because invalid XML was output during the test run' . + ' and/or because an error occured.' . + ' Incomplete test cases are shown in yellow. Click the HTML link ' . + 'next to the test for more detail.' + ); + } + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Reporter/HtmlTestCaseReporter.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Reporter/HtmlTestCaseReporter.php new file mode 100644 index 0000000..8d4474d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Reporter/HtmlTestCaseReporter.php @@ -0,0 +1,174 @@ +_parent = $parent; + $this->_testCase = $testCase; + $this->_aggregates = array( + 'passes' => 0, + 'fails' => 0, + 'exceptions' => 0 + ); + } + + /** + * Set template data. + * @param mixed[] + */ + public function setTemplateVars(&$vars) + { + $this->_tplVars =& $vars; + } + + /** + * Get the reporter used to report on this specific test case. + * This method is stubbed only to return itself. + * @param string $testCase + * @return Sweety_Reporter + */ + public function getReporterFor($testCase) + { + return $this; + } + + /** + * Returns true if start() has been invoked. + * @return boolean + */ + public function isStarted() + { + return $this->_started; + } + + /** + * Start reporting. + */ + public function start() + { + $this->_started = true; + $this->_tplVars['runTests'][$this->_testCase] = 'running'; + } + + /** + * Report a skipped test case. + * @param string $message + * @param string $path + */ + public function reportSkip($message, $path) + { + $this->_parent->reportSkip($message, $path); + } + + /** + * Report a passing assertion. + * @param string $message + * @param string $path + */ + public function reportPass($message, $path) + { + $this->_aggregates['passes']++; + $this->_parent->reportPass($message, $path); + } + + /** + * Report a failing assertion. + * @param string $message + * @param string $path + */ + public function reportFail($message, $path) + { + $this->_aggregates['fails']++; + $this->_parent->reportFail($message, $path); + } + + /** + * Report an unexpected exception. + * @param string $message + * @param string $path + */ + public function reportException($message, $path) + { + $this->_aggregates['exceptions']++; + $this->_parent->reportException($message, $path); + } + + /** + * Report output from something like a dump(). + * @param string $output + * @param string $path + */ + public function reportOutput($output, $path) + { + $this->_parent->reportOutput($output, $path); + } + + /** + * End reporting. + */ + public function finish() + { + $this->_started = false; + + if (!$this->_aggregates['fails'] && !$this->_aggregates['exceptions']) + { + $this->_tplVars['runTests'][$this->_testCase] = 'pass'; + } + else + { + $this->_tplVars['runTests'][$this->_testCase] = 'fail'; + } + + $this->_parent->notifyEnded($this->_testCase); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Runner.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Runner.php new file mode 100644 index 0000000..b07ddb4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Runner.php @@ -0,0 +1,56 @@ +_reporter = $reporter; + } + + /** + * Get the reporter used for showing results. + * @return Sweety_Reporter + */ + public function getReporter() + { + return $this->_reporter; + } + + /** + * Register a test locator instance. + * @param Sweety_TestLocator $locator + */ + public function registerTestLocator(Sweety_TestLocator $locator) + { + $this->_testLocators[] = $locator; + } + + /** + * Set the regular expression used to filter out certain class names. + * @param string $ignoredClassRegex + */ + public function setIgnoredClassRegex($ignoredClassRegex) + { + $this->_ignoredClassRegex = $ignoredClassRegex; + } + + /** + * Get the filtering regular expression for ignoring certain classes. + * @return string + */ + public function getIgnoredClassRegex() + { + return $this->_ignoredClassRegex; + } + + /** + * Run a single test case with the given name, using the provided output format. + * @param string $testName + * @param string $format (xml, text or html) + * @return int + */ + public function runTestCase($testName, $format = Sweety_Runner::REPORT_TEXT) + { + foreach ($this->_testLocators as $locator) + { + if ($locator->includeTest($testName)) + { + break; + } + } + + $testClass = new ReflectionClass($testName); + if ($testClass->getConstructor()) + { + //We don't want test output to be cached + if (!SimpleReporter::inCli()) + { + header("Cache-Control: no-cache, must-revalidate"); + header("Pragma: no-cache"); + header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); + } + + switch ($format) + { + case Sweety_Runner::REPORT_HTML: + $reporter = new HtmlReporter(); + break; + case Sweety_Runner::REPORT_XML: + if (!SimpleReporter::inCli()) + { + header("Content-Type: text/xml"); //Sigh! SimpleTest (skip() issues). + } + $reporter = new XmlReporter(); + break; + case Sweety_Runner::REPORT_TEXT: + default: + $reporter = new TextReporter(); + break; + } + $test = $testClass->newInstance(); + return $test->run($reporter) ? 0 : 1; + } + + return 1; + } + + /** + * Use strategies to find tests which are runnable. + * @param string[] $dirs + * @return string[] + */ + protected function findTests($dirs = array()) + { + $tests = array(); + foreach ($this->_testLocators as $locator) + { + $tests += $locator->getTests($dirs); + } + return $tests; + } + + /** + * Parse an XML response from a test case an report to the reporter. + * @param string $xml + * @param string $testCase + */ + protected function parseXml($xml, $testCase) + { + $reporter = $this->_reporter->getReporterFor($testCase); + if (!$reporter->isStarted()) + { + $reporter->start(); + } + + $xml = str_replace("\0", '?', trim($xml)); + $xml = preg_replace('/[^\x01-\x7F]/e', 'sprintf("&#%d;", ord("$0"));', $xml); //Do something better? + if (!empty($xml)) + { + $document = @simplexml_load_string($xml); + if ($document) + { + $this->_parseDocument($document, $testCase, $reporter); + $reporter->finish(); + return; + } + } + + $reporter->reportException( + 'Invalid XML response: ' . + trim(strip_tags( + preg_replace('/^\s*<\?xml.+<\/(?:name|pass|fail|exception)>/s', '', $xml) + )), + $testCase + ); + } + + /** + * Parse formatted test output. + * @param SimpleXMLElement The node containing the output + * @param string $path to this test method + * @access private + */ + private function _parseFormatted(SimpleXMLElement $formatted, $path = '', + Sweety_Reporter $reporter) + { + $reporter->reportOutput((string)$formatted, $path); + } + + /** + * Parse test output. + * @param SimpleXMLElement The node containing the output + * @param string $path to this test method + * @access private + */ + private function _parseMessage(SimpleXMLElement $message, $path = '', + Sweety_Reporter $reporter) + { + $reporter->reportOutput((string)$message, $path); + } + + /** + * Parse a test failure. + * @param SimpleXMLElement The node containing the fail + * @param string $path to this test method + * @access private + */ + private function _parseFailure(SimpleXMLElement $failure, $path = '', + Sweety_Reporter $reporter) + { + $reporter->reportFail((string)$failure, $path); + } + + /** + * Parse an exception. + * @param SimpleXMLElement The node containing the exception + * @param string $path to this test method + * @access private + */ + private function _parseException(SimpleXMLElement $exception, $path = '', + Sweety_Reporter $reporter) + { + $reporter->reportException((string)$exception, $path); + } + + /** + * Parse a pass. + * @param SimpleXMLElement The node containing this pass. + * @param string $path to this test method + * @access private + */ + private function _parsePass(SimpleXMLElement $pass, $path = '', + Sweety_Reporter $reporter) + { + $reporter->reportPass((string)$pass, $path); + } + + /** + * Parse a single test case. + * @param SimpleXMLElement The node containing the test case + * @param string $path to this test case + * @access private + */ + private function _parseTestCase(SimpleXMLElement $testCase, $path = '', + Sweety_Reporter $reporter) + { + foreach ($testCase->xpath('./test') as $testMethod) + { + $testMethodName = (string) $this->_firstNodeValue($testMethod->xpath('./name')); + + foreach ($testMethod->xpath('./formatted') as $formatted) + { + $this->_parseFormatted( + $formatted, $path . ' -> ' . $testMethodName, $reporter); + } + + foreach ($testMethod->xpath('./message') as $message) + { + $this->_parseMessage( + $message, $path . ' -> ' . $testMethodName, $reporter); + } + + foreach ($testMethod->xpath('./fail') as $failure) + { + $this->_parseFailure( + $failure, $path . ' -> ' . $testMethodName, $reporter); + } + + foreach ($testMethod->xpath('./exception') as $exception) + { + $this->_parseException( + $exception, $path . ' -> ' . $testMethodName, $reporter); + } + + foreach ($testMethod->xpath('./pass') as $pass) + { + $this->_parsePass($pass, $path . ' -> ' . $testMethodName, $reporter); + } + + $stdout = trim((string) $testMethod); + if ($stdout) + { + $reporter->reportOutput($stdout, $path . ' -> ' . $testMethodName); + } + } + } + + /** + * Parse the results of all tests. + * @param SimpleXMLElement The node containing the tests + * @param string $path to the tests + * @access private + */ + private function _parseResults(SimpleXMLElement $document, $path = '', + Sweety_Reporter $reporter) + { + $groups = $document->xpath('./group'); + if (!empty($groups)) + { + foreach ($groups as $group) + { + $groupName = (string) $this->_firstNodeValue($group->xpath('./name')); + $this->_parseResults($group, $path . ' -> ' . $groupName, $reporter); + } + } + else + { + foreach ($document->xpath('./case') as $testCase) + { + $this->_parseTestCase($testCase, $path, $reporter); + } + } + } + + /** + * Parse the entire SimpleTest XML document from a test case. + * @param SimpleXMLElement $document to parse + * @param string $path to the test + * @access private + */ + private function _parseDocument(SimpleXMLElement $document, $path = '', + Sweety_Reporter $reporter) + { + if ($everything = $this->_firstNodeValue($document->xpath('/run'))) + { + $this->_parseResults($everything, $path, $reporter); + } + elseif ($skip = $this->_firstNodeValue($document->xpath('/skip'))) + { + $reporter->reportSkip((string) $skip, $path); + } + } + + protected function _sort($a, $b) + { + $apkg = preg_replace('/_[^_]+$/D', '', $a); + $bpkg = preg_replace('/_[^_]+$/D', '', $b); + if ($apkg == $bpkg) + { + if ($a == $b) + { + return 0; + } + else + { + return ($a > $b) ? 1 : -1; + } + } + else + { + return ($apkg > $bpkg) ? 1 : -1; + } + } + + private function _firstNodeValue($nodeSet) + { + $first = array_shift($nodeSet); + return $first; + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Runner/CliRunner.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Runner/CliRunner.php new file mode 100644 index 0000000..c3e4026 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Runner/CliRunner.php @@ -0,0 +1,128 @@ +_dirs = $dirs; + $this->_command = $command; + } + + /** + * Runs all test cases found under the given directories. + * @param string[] $directories to scan for test cases + * @param string To be prepended to class names + * @return int + */ + public function runAllTests($dirs = array()) + { + if (empty($dirs)) + { + $dirs = $this->_dirs; + } + + $reporter = $this->getReporter(); + + if (!$reporter->isStarted()) + { + $reporter->start(); + } + + $tests = $this->findTests($dirs); + usort($tests, array($this, '_sort')); + + global $argv; + + if (!empty($argv[1])) + { + if (substr($argv[1], 0, 1) == '!') + { + $argv[1] = substr($argv[1], 1); + foreach ($tests as $index => $name) + { + if (@preg_match($argv[1] . 'i', $name)) + { + unset($tests[$index]); + } + } + } + else + { + foreach ($tests as $index => $name) + { + if (!@preg_match($argv[1] . 'i', $name)) + { + unset($tests[$index]); + } + } + } + } + + $ret = $this->_runTestList($tests); + + $reporter->finish(); + + return $ret; + } + + /** + * Run all possible tests from the given list. + * @param string[] $tests + * @return int + */ + protected function _runTestList(array $tests) + { + foreach ($tests as $testCase) + { + if (preg_match($this->getIgnoredClassRegex(), $testCase)) + { + continue; + } + + $command = $this->_command; + $command .= ' ' . $testCase; + $command .= ' ' . Sweety_Runner::REPORT_XML; + + exec($command, $output, $status); + + $xml = implode(PHP_EOL, $output); + + $this->parseXml($xml, $testCase); + + unset($status); + unset($output); + } + + return 0; + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Runner/HtmlRunner.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Runner/HtmlRunner.php new file mode 100644 index 0000000..e115692 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/Runner/HtmlRunner.php @@ -0,0 +1,160 @@ +_dirs = $dirs; + $this->_template = $template; + $this->_name = $name; + } + + /** + * Runs all test cases found under the given directories. + * @param string[] $directories to scan for test cases + * @param string To be prepended to class names + * @return int + */ + public function runAllTests($dirs = array()) + { + //We don't want test output to be cached + header("Cache-Control: no-cache, must-revalidate"); + header("Pragma: no-cache"); + header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); + + if (empty($dirs)) + { + $dirs = $this->_dirs; + } + + $testCases = $this->findTests($dirs); + foreach ($testCases as $k => $testCase) + { + if (preg_match($this->getIgnoredClassRegex(), $testCase)) + { + unset($testCases[$k]); + } + } + + usort($testCases, array($this, '_sort')); + + $vars = array( + //String + 'testCases' => $testCases, + //String + 'suiteName' => $this->_name, + // testCase => pass | fail | running + 'runTests' => array(), + //Integer + 'caseCount' => 0, + //Integer + 'runCount' => 0, + //Integer + 'passCount' => 0, + //Integer + 'failCount' => 0, + //Integer + 'exceptionCount' => 0, + // type => pass | fail | exception | output | internal, path => testCase, text => ... + 'messages' => array(), + // pass | fail + 'result' => 'idle' + ); + + if (isset($_REQUEST['runtests'])) + { + $reporter = $this->getReporter(); + $reporter->setTemplateVars($vars); + + if (!$reporter->isStarted()) + { + $reporter->start(); + } + + $this->_runTestList((array)$_REQUEST['runtests'], $reporter); + + $reporter->finish(); + } + else + { + foreach ($testCases as $testCase) + { + $vars['runTests'][$testCase] = 'idle'; //Show all checked by default + } + } + + $this->_render($vars); + } + + /** + * Run tests in the given array using the REST API (kind of). + * @param string[] $tests + * @param Sweety_Reporter $reporter + * @return int + */ + protected function _runTestList(array $tests, Sweety_Reporter $reporter) + { + $protocol = !empty($_SERVER['HTTPS']) ? 'https://' : 'http://'; + + //Most likely a HTTP/1.0 server not supporting HOST header + $server = !empty($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '127.0.0.1'; + + $path = '/'; + if (!empty($_SERVER['REQUEST_URI'])) + { + $path = preg_replace('/\?.*$/sD', '', $_SERVER['REQUEST_URI']); + } + + $baseUrl = $protocol . $server . $path; + + foreach ($tests as $testCase) + { + $url = $baseUrl . '?test=' . $testCase . '&format=xml'; + $xml = file_get_contents($url); + $this->parseXml($xml, $testCase); + } + + return 0; + } + + /** + * Renders the view for the suite. + * @param string[] $templateVars + */ + protected function _render($vars = array()) + { + foreach ($vars as $k => $v) + { + $$k = $v; + } + + require_once $this->_template; + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/TestLocator.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/TestLocator.php new file mode 100644 index 0000000..96e4649 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/Sweety/TestLocator.php @@ -0,0 +1,25 @@ +_findTestCases($dirs); + } + + public function includeTest($testCase) + { + $file = str_replace('_', '/', $testCase) . '.php'; + foreach (explode(PATH_SEPARATOR, get_include_path()) as $dir) + { + if (is_file($dir . '/' . $file)) + { + require_once $dir . '/' . $file; + return true; + } + } + + return false; + } + + protected function _findTestCases($dirs = array(), $prepend = '') + { + $ret = array(); + + foreach ($dirs as $dir) + { + if (array_key_exists($dir, $this->_testCache)) + { + $ret += $this->_testCache[$dir]; + continue; + } + + $this->_testCache[$dir] = array(); + + $handle = opendir($dir); + while (false !== $file = readdir($handle)) + { + if (substr($file, 0, 1) != '.' && is_dir($dir . '/' . $file)) + { + foreach ($this->_findTestCases( + array($dir . '/' . $file), $prepend . $file . '_') as $add) + { + $this->_testCache[$dir][] = $add; + $ret[] = $add; + } + } + elseif (substr($file, -4) == '.php') + { + $className = $prepend . basename($file, '.php'); + $this->_testCache[$dir][] = $className; + $ret[] = $className; + } + } + closedir($handle); + } + + sort($ret); + + return $ret; + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/HELP_MY_TESTS_DONT_WORK_ANYMORE b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/HELP_MY_TESTS_DONT_WORK_ANYMORE new file mode 100644 index 0000000..d99acd8 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/HELP_MY_TESTS_DONT_WORK_ANYMORE @@ -0,0 +1,383 @@ +Simple Test interface changes +============================= +Because the SimpleTest tool set is still evolving it is likely that tests +written with earlier versions will fail with the newest ones. The most +dramatic changes are in the alpha releases. Here is a list of possible +problems and their fixes... + +Fatal error: Call to undefined method Classname::classname() +------------------------------------------------------------ +SimpleTest renamed all of its constructors from +Classname to __construct; derived classes invoking +their parent constructors should replace parent::Classname() +with parent::__construct(). + +Custom CSS in HtmlReporter not being applied +-------------------------------------------- +Batch rename of protected and private methods +means that _getCss() was renamed to getCss(); +replace your function definition accordingly. + +setReturnReference() throws errors in E_STRICT +---------------------------------------------- +Happens when an object is passed by reference. +This also happens with setReturnReferenceAt(). +If you want to return objects then replace these +with calls to returns() and returnsAt() with the +same arguments. This change was forced in the 1.1 +version for E_STRICT compatibility. + +assertReference() throws errors in E_STRICT +------------------------------------------- +Due to language restrictions you cannot compare +both variables and objects in E_STRICT mode. Use +assertSame() in this mode with objects. This change +was forced the 1.1 version. + +Cannot create GroupTest +----------------------- +The GroupTest has been renamed TestSuite (see below). +It was removed completely in 1.1 in favour of this +name. + +No method getRelativeUrls() or getAbsoluteUrls() +------------------------------------------------ +These methods were always a bit weird anyway, and +the new parsing of the base tag makes them more so. +They have been replaced with getUrls() instead. If +you want the old functionality then simply chop +off the current domain from getUrl(). + +Method setWildcard() removed in mocks +------------------------------------- +Even setWildcard() has been removed in 1.0.1beta now. +If you want to test explicitely for a '*' string, then +simply pass in new IdenticalExpectation('*') instead. + +No method _getTest() on mocks +----------------------------- +This has finally been removed. It was a pretty esoteric +flex point anyway. It was there to allow the mocks to +work with other test tools, but no one does this. + +No method assertError(), assertNoErrors(), swallowErrors() +---------------------------------------------------------- +These have been deprecated in 1.0.1beta in favour of +expectError() and expectException(). assertNoErrors() is +redundant if you use expectError() as failures are now reported +immediately. + +No method TestCase::signal() +---------------------------- +This has been deprecated in favour of triggering an error or +throwing an exception. Deprecated as of 1.0.1beta. + +No method TestCase::sendMessage() +--------------------------------- +This has been deprecated as of 1.0.1beta. + +Failure to connect now emits failures +------------------------------------- +It used to be that you would have to use the +getTransferError() call on the web tester to see if +there was a socket level error in a fetch. This check +is now always carried out by the WebTestCase unless +the fetch is prefaced with WebTestCase::ignoreErrors(). +The ignore directive only lasts for test case fetching +action such as get() and click(). + +No method SimpleTestOptions::ignore() +------------------------------------- +This is deprecated in version 1.0.1beta and has been moved +to SimpleTest::ignore() as that is more readable. In +addition, parent classes are also ignored automatically. +If you are using PHP5 you can skip this directive simply +by marking your test case as abstract. + +No method assertCopy() +---------------------- +This is deprecated in 1.0.1 in favour of assertClone(). +The assertClone() method is slightly different in that +the objects must be identical, but without being a +reference. It is thus not a strict inversion of +assertReference(). + +Constructor wildcard override has no effect in mocks +---------------------------------------------------- +As of 1.0.1beta this is now set with setWildcard() instead +of in the constructor. + +No methods setStubBaseClass()/getStubBaseClass() +------------------------------------------------ +As mocks are now used instead of stubs, these methods +stopped working and are now removed as of the 1.0.1beta +release. The mock objects may be freely used instead. + +No method addPartialMockCode() +------------------------------ +The ability to insert arbitrary partial mock code +has been removed. This was a low value feature +causing needless complications. It was removed +in the 1.0.1beta release. + +No method setMockBaseClass() +---------------------------- +The ability to change the mock base class has been +scheduled for removal and is deprecated since the +1.0.1beta version. This was a rarely used feature +except as a workaround for PHP5 limitations. As +these limitations are being resolved it's hoped +that the bundled mocks can be used directly. + +No class Stub +------------- +Server stubs are deprecated from 1.0.1 as the mocks now +have exactly the same interface. Just use mock objects +instead. + +No class SimpleTestOptions +-------------------------- +This was replced by the shorter SimpleTest in 1.0.1beta1 +and is since deprecated. + +No file simple_test.php +----------------------- +This was renamed test_case.php in 1.0.1beta to more accurately +reflect it's purpose. This file should never be directly +included in test suites though, as it's part of the +underlying mechanics and has a tendency to be refactored. + +No class WantedPatternExpectation +--------------------------------- +This was deprecated in 1.0.1alpha in favour of the simpler +name PatternExpectation. + +No class NoUnwantedPatternExpectation +------------------------------------- +This was deprecated in 1.0.1alpha in favour of the simpler +name NoPatternExpectation. + +No method assertNoUnwantedPattern() +----------------------------------- +This has been renamed to assertNoPattern() in 1.0.1alpha and +the old form is deprecated. + +No method assertWantedPattern() +------------------------------- +This has been renamed to assertPattern() in 1.0.1alpha and +the old form is deprecated. + +No method assertExpectation() +----------------------------- +This was renamed as assert() in 1.0.1alpha and the old form +has been deprecated. + +No class WildcardExpectation +---------------------------- +This was a mostly internal class for the mock objects. It was +renamed AnythingExpectation to bring it closer to JMock and +NMock in version 1.0.1alpha. + +Missing UnitTestCase::assertErrorPattern() +------------------------------------------ +This method is deprecated for version 1.0.1 onwards. +This method has been subsumed by assertError() that can now +take an expectation. Simply pass a PatternExpectation +into assertError() to simulate the old behaviour. + +No HTML when matching page elements +----------------------------------- +This behaviour has been switched to using plain text as if it +were seen by the user of the browser. This means that HTML tags +are suppressed, entities are converted and whitespace is +normalised. This should make it easier to match items in forms. +Also images are replaced with their "alt" text so that they +can be matched as well. + +No method SimpleRunner::_getTestCase() +-------------------------------------- +This was made public as getTestCase() in 1.0RC2. + +No method restartSession() +-------------------------- +This was renamed to restart() in the WebTestCase, SimpleBrowser +and the underlying SimpleUserAgent in 1.0RC2. Because it was +undocumented anyway, no attempt was made at backward +compatibility. + +My custom test case ignored by tally() +-------------------------------------- +The _assertTrue method has had it's signature changed due to a bug +in the PHP 5.0.1 release. You must now use getTest() from within +that method to get the test case. Mock compatibility with other +unit testers is now deprecated as of 1.0.1alpha as PEAR::PHPUnit2 +should soon have mock support of it's own. + +Broken code extending SimpleRunner +---------------------------------- +This was replaced with SimpleScorer so that I could use the runner +name in another class. This happened in RC1 development and there +is no easy backward compatibility fix. The solution is simply to +extend SimpleScorer instead. + +Missing method getBaseCookieValue() +----------------------------------- +This was renamed getCurrentCookieValue() in RC1. + +Missing files from the SimpleTest suite +--------------------------------------- +Versions of SimpleTest prior to Beta6 required a SIMPLE_TEST constant +to point at the SimpleTest folder location before any of the toolset +was loaded. This is no longer documented as it is now unnecessary +for later versions. If you are using an earlier version you may +need this constant. Consult the documentation that was bundled with +the release that you are using or upgrade to Beta6 or later. + +No method SimpleBrowser::getCurrentUrl() +-------------------------------------- +This is replaced with the more versatile showRequest() for +debugging. It only existed in this context for version Beta5. +Later versions will have SimpleBrowser::getHistory() for tracking +paths through pages. It is renamed as getUrl() since 1.0RC1. + +No method Stub::setStubBaseClass() +---------------------------------- +This method has finally been removed in 1.0RC1. Use +SimpleTestOptions::setStubBaseClass() instead. + +No class CommandLineReporter +---------------------------- +This was renamed to TextReporter in Beta3 and the deprecated version +was removed in 1.0RC1. + +No method requireReturn() +------------------------- +This was deprecated in Beta3 and is now removed. + +No method expectCookie() +------------------------ +This method was abruptly removed in Beta4 so as to simplify the internals +until another mechanism can replace it. As a workaround it is necessary +to assert that the cookie has changed by setting it before the page +fetch and then assert the desired value. + +No method clickSubmitByFormId() +------------------------------- +This method had an incorrect name as no button was involved. It was +renamed to submitByFormId() in Beta4 and the old version deprecated. +Now removed. + +No method paintStart() or paintEnd() +------------------------------------ +You should only get this error if you have subclassed the lower level +reporting and test runner machinery. These methods have been broken +down into events for test methods, events for test cases and events +for group tests. The new methods are... + +paintStart() --> paintMethodStart(), paintCaseStart(), paintGroupStart() +paintEnd() --> paintMethodEnd(), paintCaseEnd(), paintGroupEnd() + +This change was made in Beta3, ironically to make it easier to subclass +the inner machinery. Simply duplicating the code you had in the previous +methods should provide a temporary fix. + +No class TestDisplay +-------------------- +This has been folded into SimpleReporter in Beta3 and is now deprecated. +It was removed in RC1. + +No method WebTestCase::fetch() +------------------------------ +This was renamed get() in Alpha8. It is removed in Beta3. + +No method submit() +------------------ +This has been renamed clickSubmit() in Beta1. The old method was +removed in Beta2. + +No method clearHistory() +------------------------ +This method is deprecated in Beta2 and removed in RC1. + +No method getCallCount() +------------------------ +This method has been deprecated since Beta1 and has now been +removed. There are now more ways to set expectations on counts +and so this method should be unecessery. Removed in RC1. + +Cannot find file * +------------------ +The following public name changes have occoured... + +simple_html_test.php --> reporter.php +simple_mock.php --> mock_objects.php +simple_unit.php --> unit_tester.php +simple_web.php --> web_tester.php + +The old names were deprecated in Alpha8 and removed in Beta1. + +No method attachObserver() +-------------------------- +Prior to the Alpha8 release the old internal observer pattern was +gutted and replaced with a visitor. This is to trade flexibility of +test case expansion against the ease of writing user interfaces. + +Code such as... + +$test = &new MyTestCase(); +$test->attachObserver(new TestHtmlDisplay()); +$test->run(); + +...should be rewritten as... + +$test = &new MyTestCase(); +$test->run(new HtmlReporter()); + +If you previously attached multiple observers then the workaround +is to run the tests twice, once with each, until they can be combined. +For one observer the old method is simulated in Alpha 8, but is +removed in Beta1. + +No class TestHtmlDisplay +------------------------ +This class has been renamed to HtmlReporter in Alpha8. It is supported, +but deprecated in Beta1 and removed in Beta2. If you have subclassed +the display for your own design, then you will have to extend this +class (HtmlReporter) instead. + +If you have accessed the event queue by overriding the notify() method +then I am afraid you are in big trouble :(. The reporter is now +carried around the test suite by the runner classes and the methods +called directly. In the unlikely event that this is a problem and +you don't want to upgrade the test tool then simplest is to write your +own runner class and invoke the tests with... + +$test->accept(new MyRunner(new MyReporter())); + +...rather than the run method. This should be easier to extend +anyway and gives much more control. Even this method is overhauled +in Beta3 where the runner class can be set within the test case. Really +the best thing to do is to upgrade to this version as whatever you were +trying to achieve before should now be very much easier. + +Missing set options method +-------------------------- +All test suite options are now in one class called SimpleTestOptions. +This means that options are set differently... + +GroupTest::ignore() --> SimpleTestOptions::ignore() +Mock::setMockBaseClass() --> SimpleTestOptions::setMockBaseClass() + +These changed in Alpha8 and the old versions are now removed in RC1. + +No method setExpected*() +------------------------ +The mock expectations changed their names in Alpha4 and the old names +ceased to be supported in Alpha8. The changes are... + +setExpectedArguments() --> expectArguments() +setExpectedArgumentsSequence() --> expectArgumentsAt() +setExpectedCallCount() --> expectCallCount() +setMaximumCallCount() --> expectMaximumCallCount() + +The parameters remained the same. diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/LICENSE b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/LICENSE new file mode 100644 index 0000000..09f465a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/LICENSE @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. 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 not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the 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 +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/README b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/README new file mode 100644 index 0000000..f0912b2 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/README @@ -0,0 +1,108 @@ +SimpleTest +========== +You probably got this package from... +http://simpletest.sourceforge.net/projects/simpletest/ + +If there is no licence agreement with this package please download +a version from the location above. You must read and accept that +licence to use this software. The file is titled simply LICENSE. + +What is it? It's a framework for unit testing, web site testing and +mock objects for PHP 5.0.5+. + +If you have used JUnit, you will find this PHP unit testing version very +similar. Also included is a mock objects and server stubs generator. +The stubs can have return values set for different arguments, can have +sequences set also by arguments and can return items by reference. +The mocks inherit all of this functionality and can also have +expectations set, again in sequences and for different arguments. + +A web tester similar in concept to JWebUnit is also included. There is no +JavaScript or tables support, but forms, authentication, cookies and +frames are handled. + +You can see a release schedule at http://simpletest.org/en/overview.html +which is also copied to the documentation folder with this release. +A full PHPDocumenter API documentation exists at +http://simpletest.org/api/. + +The user interface is minimal +in the extreme, but a lot of information flows from the test suite. +After version 1.0 we will release a better web UI, but we are leaving XUL +and GTk versions to volunteers as everybody has their own opinion +on a good GUI, and we don't want to discourage development by shipping +one with the toolkit. You can download an Eclipse plug-in separately. + +You are looking at a second full release. The unit tests for SimpleTest +itself can be run here... + +simpletest/test/unit_tests.php + +And tests involving live network connections as well are here... + +simpletest/test/all_tests.php + +The full tests will typically overrun the 8Mb limit often allowed +to a PHP process. A workaround is to run the tests on the command +with a custom php.ini file if you do not have access to your server +version. + +You will have to edit the all_tests.php file if you are accesssing +the internet through a proxy server. See the comments in all_tests.php +for instructions. + +The full tests read some test data from the LastCraft site. If the site +is down or has been modified for a later version then you will get +spurious errors. A unit_tests.php failure on the other hand would be +very serious. As far as we know we haven't yet managed to check in any +unit test failures, so please correct us if you find one. + +Even if all of the tests run please verify that your existing test suites +also function as expected. If they don't see the file... + +HELP_MY_TESTS_DONT_WORK_ANYMORE + +This contains information on interface changes. It also points out +deprecated interfaces, so you should read this even if all of +your current tests appear to run. + +There is a documentation folder which contains the core reference information +in English and French, although this information is fairly basic. +You can find a tutorial on... + +http://simpletest.org/en/first_test_tutorial.html + +...to get you started and this material will eventually become included +with the project documentation. A French translation exists at... + +http://simpletest.org/fr/first_test_tutorial.html + +If you download and use, and possibly even extend this tool, please let us +know. Any feedback, even bad, is always welcome and we will work to get +your suggestions into the next release. Ideally please send your +comments to... + +simpletest-support@lists.sourceforge.net + +...so that others can read them too. We usually try to respond within 48 +hours. + +There is no change log except at Sourceforge. You can visit the +release notes to see the completed TODO list after each cycle and also the +status of any bugs, but if the bug is recent then it will be fixed in SVN only. +The SVN check-ins always have all the tests passing and so SVN snapshots should +be pretty usable, although the code may not look so good internally. + +Oh, yes. It is called "Simple" because it should be simple to +use. We intend to add a complete set of tools for a test first +and "test as you code" type of development. "Simple" does not +mean "Lite" in this context. + +Thanks to everyone who has sent comments and offered suggestions. They +really are invaluable, but sadly you are too many to mention in full. +Thanks to all on the advanced PHP forum on SitePoint, especially Harry +Fuecks. Early adopters are always an inspiration. + +Marcus Baker, Jason Sweat, Travis Swicegood, Perrick Penet and Edward Z. Yang. +-- +marcus@lastcraft.com diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/TODO.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/TODO.xml new file mode 100644 index 0000000..3fa84e9 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/TODO.xml @@ -0,0 +1,176 @@ + + + TODO tasks for the current iteration + +
    +

    + The following is the approximate plan for the next full point release. +

    +

    + Before each release we hope to have the following done. + More may get done, depending on the interest of the volunteers, + but this is the current minimum. +

    +

    + The aim of this release cycle is to produce a functionally + identical version to the 1.0.1 release, but bug-fixed and + fully compatible with PHP 5.0.5+ under E_STRICT. + We are also hoping to flush out issues and use cases + caused by people hacking against unpublished flex points + in SimpleTest. + We want to break people's code now, not while we are developing + features down the line. +

    +

    + With the website move to a new server, and more developers, + we are able and need to improve the test automation and developer + cooperation. + This release is a deep drawing of breath before going forward. +

    +
    +
    + + + Undefined property $_reporter + fatal error + + + + + + + + + The HELP_MY_TESTS_DONT_WORK_ANYMORE needs to be updated. + + README needs to be updated. + + Write XSLT code for this file so Perrick doesn't strangle me + + + + Ensure extensions are compatible with PHP5 constructor renaming in the current trunk. + + + Update PEAR package task to be compatible with latest PEAR installer. + PHP 5.3 compatible under E_STRICT + PHP 5.2.0-5 compatible under E_STRICT + PHP 5.1.0-6 compatible under E_STRICT + continuous integration + error_reporting(E_ALL|E_STRICT)gives lots of warning + Remove all deprecated methods + + Drop underscores from protected methods and + private variables. + Make all variables private and add protected + accessors where we use them internally. + + That way people will start complaining. + Upon each complaint we'll add an accessor and + capture the use case from them. + + We'll stick the use cases in the feature request tracker for now + + Move web site to new server + + + + + + + Deprecate all mentions of GroupTest without breaking + existing code. + + Need to swap the terminology for TestSuite + in method names, etc. + + + XmlReporter generating invalid XML + + + Remove reflection facade for PHP4 + + + + label not assigned to radio and checkbox + incorrect proxy requests + + + + Docblocks need to be cut back to a minimum + + + + PHP 5.0.5 compatible under E_STRICT + Move acceptance tests sample pages to new server + + + + + + + Remove reflection facade for PHP4 + + + + + + Throw away old tutorial + + + + PHP 6 compatible under E_STRICT + + Automated nightly test script that runs tests on all + targeted PHP versions. + + + +
    +
    + + +
    Current iteration is 1.1beta. + + + Upcoming tasks for + Unit tester, + Reporter, + Mock objects, + Parser, + Browser, + Web tester, + Documentation, + Extensions and + Build. + + + + + Trackers for : + feature requests, + bugs and + patches. + + + + + software development, + computer programmer, + php programming, + programming php, + software development company, + software development uk, + php tutorial, + bespoke software development uk, + corporate web development, + architecture, + freelancer, + php resources, + wordtracker, + web marketing, + serach engines, + web positioning, + internet marketing + + + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/VERSION b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/VERSION new file mode 100644 index 0000000..ab8bad0 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/VERSION @@ -0,0 +1 @@ +1.1beta \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/authentication.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/authentication.php new file mode 100644 index 0000000..5af66ec --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/authentication.php @@ -0,0 +1,237 @@ +type = $type; + $this->root = $url->getBasePath(); + $this->username = false; + $this->password = false; + } + + /** + * Adds another location to the realm. + * @param SimpleUrl $url Somewhere in realm. + * @access public + */ + function stretch($url) { + $this->root = $this->getCommonPath($this->root, $url->getPath()); + } + + /** + * Finds the common starting path. + * @param string $first Path to compare. + * @param string $second Path to compare. + * @return string Common directories. + * @access private + */ + protected function getCommonPath($first, $second) { + $first = explode('/', $first); + $second = explode('/', $second); + for ($i = 0; $i < min(count($first), count($second)); $i++) { + if ($first[$i] != $second[$i]) { + return implode('/', array_slice($first, 0, $i)) . '/'; + } + } + return implode('/', $first) . '/'; + } + + /** + * Sets the identity to try within this realm. + * @param string $username Username in authentication dialog. + * @param string $username Password in authentication dialog. + * @access public + */ + function setIdentity($username, $password) { + $this->username = $username; + $this->password = $password; + } + + /** + * Accessor for current identity. + * @return string Last succesful username. + * @access public + */ + function getUsername() { + return $this->username; + } + + /** + * Accessor for current identity. + * @return string Last succesful password. + * @access public + */ + function getPassword() { + return $this->password; + } + + /** + * Test to see if the URL is within the directory + * tree of the realm. + * @param SimpleUrl $url URL to test. + * @return boolean True if subpath. + * @access public + */ + function isWithin($url) { + if ($this->isIn($this->root, $url->getBasePath())) { + return true; + } + if ($this->isIn($this->root, $url->getBasePath() . $url->getPage() . '/')) { + return true; + } + return false; + } + + /** + * Tests to see if one string is a substring of + * another. + * @param string $part Small bit. + * @param string $whole Big bit. + * @return boolean True if the small bit is + * in the big bit. + * @access private + */ + protected function isIn($part, $whole) { + return strpos($whole, $part) === 0; + } +} + +/** + * Manages security realms. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleAuthenticator { + private $realms; + + /** + * Clears the realms. + * @access public + */ + function SimpleAuthenticator() { + $this->restartSession(); + } + + /** + * Starts with no realms set up. + * @access public + */ + function restartSession() { + $this->realms = array(); + } + + /** + * Adds a new realm centered the current URL. + * Browsers privatey wildly on their behaviour in this + * regard. Mozilla ignores the realm and presents + * only when challenged, wasting bandwidth. IE + * just carries on presenting until a new challenge + * occours. SimpleTest tries to follow the spirit of + * the original standards committee and treats the + * base URL as the root of a file tree shaped realm. + * @param SimpleUrl $url Base of realm. + * @param string $type Authentication type for this + * realm. Only Basic authentication + * is currently supported. + * @param string $realm Name of realm. + * @access public + */ + function addRealm($url, $type, $realm) { + $this->realms[$url->getHost()][$realm] = new SimpleRealm($type, $url); + } + + /** + * Sets the current identity to be presented + * against that realm. + * @param string $host Server hosting realm. + * @param string $realm Name of realm. + * @param string $username Username for realm. + * @param string $password Password for realm. + * @access public + */ + function setIdentityForRealm($host, $realm, $username, $password) { + if (isset($this->realms[$host][$realm])) { + $this->realms[$host][$realm]->setIdentity($username, $password); + } + } + + /** + * Finds the name of the realm by comparing URLs. + * @param SimpleUrl $url URL to test. + * @return SimpleRealm Name of realm. + * @access private + */ + protected function findRealmFromUrl($url) { + if (! isset($this->realms[$url->getHost()])) { + return false; + } + foreach ($this->realms[$url->getHost()] as $name => $realm) { + if ($realm->isWithin($url)) { + return $realm; + } + } + return false; + } + + /** + * Presents the appropriate headers for this location. + * @param SimpleHttpRequest $request Request to modify. + * @param SimpleUrl $url Base of realm. + * @access public + */ + function addHeaders(&$request, $url) { + if ($url->getUsername() && $url->getPassword()) { + $username = $url->getUsername(); + $password = $url->getPassword(); + } elseif ($realm = $this->findRealmFromUrl($url)) { + $username = $realm->getUsername(); + $password = $realm->getPassword(); + } else { + return; + } + $this->addBasicHeaders($request, $username, $password); + } + + /** + * Presents the appropriate headers for this + * location for basic authentication. + * @param SimpleHttpRequest $request Request to modify. + * @param string $username Username for realm. + * @param string $password Password for realm. + * @access public + */ + static function addBasicHeaders(&$request, $username, $password) { + if ($username && $password) { + $request->addHeaderLine( + 'Authorization: Basic ' . base64_encode("$username:$password")); + } + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/autorun.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/autorun.php new file mode 100644 index 0000000..03729bf --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/autorun.php @@ -0,0 +1,97 @@ +createSuiteFromClasses( + basename(initial_file()), + $loader->selectRunnableTests($candidates)); + $result = $suite->run(new DefaultReporter()); + } catch (Exception $stack_frame_fix) { + print $stack_frame_fix->getMessage(); + $result = false; + } + if (SimpleReporter::inCli()) { + exit($result ? 0 : 1); + } +} + +/** + * Checks the current test context to see if a test has + * ever been run. + * @return boolean True if tests have run. + */ +function tests_have_run() { + if ($context = SimpleTest::getContext()) { + return (boolean)$context->getTest(); + } + return false; +} + +/** + * The first autorun file. + * @return string Filename of first autorun script. + */ +function initial_file() { + static $file = false; + if (! $file) { + if (isset($_SERVER, $_SERVER['SCRIPT_FILENAME'])) { + $file = $_SERVER['SCRIPT_FILENAME']; + } else { + $included_files = get_included_files(); + $file = reset($included_files); + } + } + return $file; +} + +/** + * Just the classes from the first autorun script. May + * get a few false positives, as it just does a regex based + * on following the word "class". + * @return array List of all possible classes in first + * autorun script. + */ +function classes_defined_in_initial_file() { + if (preg_match_all('/\bclass\s+(\w+)/i', file_get_contents(initial_file()), $matches)) { + return array_map('strtolower', $matches[1]); + } + return array(); +} + +/** + * Every class since the first autorun include. This + * is safe enough if require_once() is alwyas used. + * @return array Class names. + */ +function capture_new_classes() { + global $SIMPLETEST_AUTORUNNER_INITIAL_CLASSES; + return array_map('strtolower', array_diff(get_declared_classes(), + $SIMPLETEST_AUTORUNNER_INITIAL_CLASSES ? + $SIMPLETEST_AUTORUNNER_INITIAL_CLASSES : array())); +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/browser.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/browser.php new file mode 100644 index 0000000..da0379d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/browser.php @@ -0,0 +1,1094 @@ +sequence = array(); + $this->position = -1; + } + + /** + * Test for no entries yet. + * @return boolean True if empty. + * @access private + */ + protected function isEmpty() { + return ($this->position == -1); + } + + /** + * Test for being at the beginning. + * @return boolean True if first. + * @access private + */ + protected function atBeginning() { + return ($this->position == 0) && ! $this->isEmpty(); + } + + /** + * Test for being at the last entry. + * @return boolean True if last. + * @access private + */ + protected function atEnd() { + return ($this->position + 1 >= count($this->sequence)) && ! $this->isEmpty(); + } + + /** + * Adds a successfully fetched page to the history. + * @param SimpleUrl $url URL of fetch. + * @param SimpleEncoding $parameters Any post data with the fetch. + * @access public + */ + function recordEntry($url, $parameters) { + $this->dropFuture(); + array_push( + $this->sequence, + array('url' => $url, 'parameters' => $parameters)); + $this->position++; + } + + /** + * Last fully qualified URL for current history + * position. + * @return SimpleUrl URL for this position. + * @access public + */ + function getUrl() { + if ($this->isEmpty()) { + return false; + } + return $this->sequence[$this->position]['url']; + } + + /** + * Parameters of last fetch from current history + * position. + * @return SimpleFormEncoding Post parameters. + * @access public + */ + function getParameters() { + if ($this->isEmpty()) { + return false; + } + return $this->sequence[$this->position]['parameters']; + } + + /** + * Step back one place in the history. Stops at + * the first page. + * @return boolean True if any previous entries. + * @access public + */ + function back() { + if ($this->isEmpty() || $this->atBeginning()) { + return false; + } + $this->position--; + return true; + } + + /** + * Step forward one place. If already at the + * latest entry then nothing will happen. + * @return boolean True if any future entries. + * @access public + */ + function forward() { + if ($this->isEmpty() || $this->atEnd()) { + return false; + } + $this->position++; + return true; + } + + /** + * Ditches all future entries beyond the current + * point. + * @access private + */ + protected function dropFuture() { + if ($this->isEmpty()) { + return; + } + while (! $this->atEnd()) { + array_pop($this->sequence); + } + } +} + +/** + * Simulated web browser. This is an aggregate of + * the user agent, the HTML parsing, request history + * and the last header set. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleBrowser { + private $user_agent; + private $page; + private $history; + private $ignore_frames; + private $maximum_nested_frames; + + /** + * Starts with a fresh browser with no + * cookie or any other state information. The + * exception is that a default proxy will be + * set up if specified in the options. + * @access public + */ + function __construct() { + $this->user_agent = $this->createUserAgent(); + $this->user_agent->useProxy( + SimpleTest::getDefaultProxy(), + SimpleTest::getDefaultProxyUsername(), + SimpleTest::getDefaultProxyPassword()); + $this->page = new SimplePage(); + $this->history = $this->createHistory(); + $this->ignore_frames = false; + $this->maximum_nested_frames = DEFAULT_MAX_NESTED_FRAMES; + } + + /** + * Creates the underlying user agent. + * @return SimpleFetcher Content fetcher. + * @access protected + */ + protected function createUserAgent() { + return new SimpleUserAgent(); + } + + /** + * Creates a new empty history list. + * @return SimpleBrowserHistory New list. + * @access protected + */ + protected function createHistory() { + return new SimpleBrowserHistory(); + } + + /** + * Disables frames support. Frames will not be fetched + * and the frameset page will be used instead. + * @access public + */ + function ignoreFrames() { + $this->ignore_frames = true; + } + + /** + * Enables frames support. Frames will be fetched from + * now on. + * @access public + */ + function useFrames() { + $this->ignore_frames = false; + } + + /** + * Switches off cookie sending and recieving. + * @access public + */ + function ignoreCookies() { + $this->user_agent->ignoreCookies(); + } + + /** + * Switches back on the cookie sending and recieving. + * @access public + */ + function useCookies() { + $this->user_agent->useCookies(); + } + + /** + * Parses the raw content into a page. Will load further + * frame pages unless frames are disabled. + * @param SimpleHttpResponse $response Response from fetch. + * @param integer $depth Nested frameset depth. + * @return SimplePage Parsed HTML. + * @access private + */ + protected function parse($response, $depth = 0) { + $page = $this->buildPage($response); + if ($this->ignore_frames || ! $page->hasFrames() || ($depth > $this->maximum_nested_frames)) { + return $page; + } + $frameset = new SimpleFrameset($page); + foreach ($page->getFrameset() as $key => $url) { + $frame = $this->fetch($url, new SimpleGetEncoding(), $depth + 1); + $frameset->addFrame($frame, $key); + } + return $frameset; + } + + /** + * Assembles the parsing machinery and actually parses + * a single page. Frees all of the builder memory and so + * unjams the PHP memory management. + * @param SimpleHttpResponse $response Response from fetch. + * @return SimplePage Parsed top level page. + * @access protected + */ + protected function buildPage($response) { + $builder = new SimplePageBuilder(); + $page = $builder->parse($response); + $builder->free(); + unset($builder); + return $page; + } + + /** + * Fetches a page. Jointly recursive with the parse() + * method as it descends a frameset. + * @param string/SimpleUrl $url Target to fetch. + * @param SimpleEncoding $encoding GET/POST parameters. + * @param integer $depth Nested frameset depth protection. + * @return SimplePage Parsed page. + * @access private + */ + protected function fetch($url, $encoding, $depth = 0) { + $response = $this->user_agent->fetchResponse($url, $encoding); + if ($response->isError()) { + return new SimplePage($response); + } + return $this->parse($response, $depth); + } + + /** + * Fetches a page or a single frame if that is the current + * focus. + * @param SimpleUrl $url Target to fetch. + * @param SimpleEncoding $parameters GET/POST parameters. + * @return string Raw content of page. + * @access private + */ + protected function load($url, $parameters) { + $frame = $url->getTarget(); + if (! $frame || ! $this->page->hasFrames() || (strtolower($frame) == '_top')) { + return $this->loadPage($url, $parameters); + } + return $this->loadFrame(array($frame), $url, $parameters); + } + + /** + * Fetches a page and makes it the current page/frame. + * @param string/SimpleUrl $url Target to fetch as string. + * @param SimplePostEncoding $parameters POST parameters. + * @return string Raw content of page. + * @access private + */ + protected function loadPage($url, $parameters) { + $this->page = $this->fetch($url, $parameters); + $this->history->recordEntry( + $this->page->getUrl(), + $this->page->getRequestData()); + return $this->page->getRaw(); + } + + /** + * Fetches a frame into the existing frameset replacing the + * original. + * @param array $frames List of names to drill down. + * @param string/SimpleUrl $url Target to fetch as string. + * @param SimpleFormEncoding $parameters POST parameters. + * @return string Raw content of page. + * @access private + */ + protected function loadFrame($frames, $url, $parameters) { + $page = $this->fetch($url, $parameters); + $this->page->setFrame($frames, $page); + return $page->getRaw(); + } + + /** + * Removes expired and temporary cookies as if + * the browser was closed and re-opened. + * @param string/integer $date Time when session restarted. + * If omitted then all persistent + * cookies are kept. + * @access public + */ + function restart($date = false) { + $this->user_agent->restart($date); + } + + /** + * Adds a header to every fetch. + * @param string $header Header line to add to every + * request until cleared. + * @access public + */ + function addHeader($header) { + $this->user_agent->addHeader($header); + } + + /** + * Ages the cookies by the specified time. + * @param integer $interval Amount in seconds. + * @access public + */ + function ageCookies($interval) { + $this->user_agent->ageCookies($interval); + } + + /** + * Sets an additional cookie. If a cookie has + * the same name and path it is replaced. + * @param string $name Cookie key. + * @param string $value Value of cookie. + * @param string $host Host upon which the cookie is valid. + * @param string $path Cookie path if not host wide. + * @param string $expiry Expiry date. + * @access public + */ + function setCookie($name, $value, $host = false, $path = '/', $expiry = false) { + $this->user_agent->setCookie($name, $value, $host, $path, $expiry); + } + + /** + * Reads the most specific cookie value from the + * browser cookies. + * @param string $host Host to search. + * @param string $path Applicable path. + * @param string $name Name of cookie to read. + * @return string False if not present, else the + * value as a string. + * @access public + */ + function getCookieValue($host, $path, $name) { + return $this->user_agent->getCookieValue($host, $path, $name); + } + + /** + * Reads the current cookies for the current URL. + * @param string $name Key of cookie to find. + * @return string Null if there is no current URL, false + * if the cookie is not set. + * @access public + */ + function getCurrentCookieValue($name) { + return $this->user_agent->getBaseCookieValue($name, $this->page->getUrl()); + } + + /** + * Sets the maximum number of redirects before + * a page will be loaded anyway. + * @param integer $max Most hops allowed. + * @access public + */ + function setMaximumRedirects($max) { + $this->user_agent->setMaximumRedirects($max); + } + + /** + * Sets the maximum number of nesting of framed pages + * within a framed page to prevent loops. + * @param integer $max Highest depth allowed. + * @access public + */ + function setMaximumNestedFrames($max) { + $this->maximum_nested_frames = $max; + } + + /** + * Sets the socket timeout for opening a connection. + * @param integer $timeout Maximum time in seconds. + * @access public + */ + function setConnectionTimeout($timeout) { + $this->user_agent->setConnectionTimeout($timeout); + } + + /** + * Sets proxy to use on all requests for when + * testing from behind a firewall. Set URL + * to false to disable. + * @param string $proxy Proxy URL. + * @param string $username Proxy username for authentication. + * @param string $password Proxy password for authentication. + * @access public + */ + function useProxy($proxy, $username = false, $password = false) { + $this->user_agent->useProxy($proxy, $username, $password); + } + + /** + * Fetches the page content with a HEAD request. + * Will affect cookies, but will not change the base URL. + * @param string/SimpleUrl $url Target to fetch as string. + * @param hash/SimpleHeadEncoding $parameters Additional parameters for + * HEAD request. + * @return boolean True if successful. + * @access public + */ + function head($url, $parameters = false) { + if (! is_object($url)) { + $url = new SimpleUrl($url); + } + if ($this->getUrl()) { + $url = $url->makeAbsolute($this->getUrl()); + } + $response = &$this->user_agent->fetchResponse($url, new SimpleHeadEncoding($parameters)); + return ! $response->isError(); + } + + /** + * Fetches the page content with a simple GET request. + * @param string/SimpleUrl $url Target to fetch. + * @param hash/SimpleFormEncoding $parameters Additional parameters for + * GET request. + * @return string Content of page or false. + * @access public + */ + function get($url, $parameters = false) { + if (! is_object($url)) { + $url = new SimpleUrl($url); + } + if ($this->getUrl()) { + $url = $url->makeAbsolute($this->getUrl()); + } + return $this->load($url, new SimpleGetEncoding($parameters)); + } + + /** + * Fetches the page content with a POST request. + * @param string/SimpleUrl $url Target to fetch as string. + * @param hash/SimpleFormEncoding $parameters POST parameters. + * @return string Content of page. + * @access public + */ + function post($url, $parameters = false) { + if (! is_object($url)) { + $url = new SimpleUrl($url); + } + if ($this->getUrl()) { + $url = $url->makeAbsolute($this->getUrl()); + } + return $this->load($url, new SimplePostEncoding($parameters)); + } + + /** + * Equivalent to hitting the retry button on the + * browser. Will attempt to repeat the page fetch. If + * there is no history to repeat it will give false. + * @return string/boolean Content if fetch succeeded + * else false. + * @access public + */ + function retry() { + $frames = $this->page->getFrameFocus(); + if (count($frames) > 0) { + $this->loadFrame( + $frames, + $this->page->getUrl(), + $this->page->getRequestData()); + return $this->page->getRaw(); + } + if ($url = $this->history->getUrl()) { + $this->page = $this->fetch($url, $this->history->getParameters()); + return $this->page->getRaw(); + } + return false; + } + + /** + * Equivalent to hitting the back button on the + * browser. The browser history is unchanged on + * failure. The page content is refetched as there + * is no concept of content caching in SimpleTest. + * @return boolean True if history entry and + * fetch succeeded + * @access public + */ + function back() { + if (! $this->history->back()) { + return false; + } + $content = $this->retry(); + if (! $content) { + $this->history->forward(); + } + return $content; + } + + /** + * Equivalent to hitting the forward button on the + * browser. The browser history is unchanged on + * failure. The page content is refetched as there + * is no concept of content caching in SimpleTest. + * @return boolean True if history entry and + * fetch succeeded + * @access public + */ + function forward() { + if (! $this->history->forward()) { + return false; + } + $content = $this->retry(); + if (! $content) { + $this->history->back(); + } + return $content; + } + + /** + * Retries a request after setting the authentication + * for the current realm. + * @param string $username Username for realm. + * @param string $password Password for realm. + * @return boolean True if successful fetch. Note + * that authentication may still have + * failed. + * @access public + */ + function authenticate($username, $password) { + if (! $this->page->getRealm()) { + return false; + } + $url = $this->page->getUrl(); + if (! $url) { + return false; + } + $this->user_agent->setIdentity( + $url->getHost(), + $this->page->getRealm(), + $username, + $password); + return $this->retry(); + } + + /** + * Accessor for a breakdown of the frameset. + * @return array Hash tree of frames by name + * or index if no name. + * @access public + */ + function getFrames() { + return $this->page->getFrames(); + } + + /** + * Accessor for current frame focus. Will be + * false if no frame has focus. + * @return integer/string/boolean Label if any, otherwise + * the position in the frameset + * or false if none. + * @access public + */ + function getFrameFocus() { + return $this->page->getFrameFocus(); + } + + /** + * Sets the focus by index. The integer index starts from 1. + * @param integer $choice Chosen frame. + * @return boolean True if frame exists. + * @access public + */ + function setFrameFocusByIndex($choice) { + return $this->page->setFrameFocusByIndex($choice); + } + + /** + * Sets the focus by name. + * @param string $name Chosen frame. + * @return boolean True if frame exists. + * @access public + */ + function setFrameFocus($name) { + return $this->page->setFrameFocus($name); + } + + /** + * Clears the frame focus. All frames will be searched + * for content. + * @access public + */ + function clearFrameFocus() { + return $this->page->clearFrameFocus(); + } + + /** + * Accessor for last error. + * @return string Error from last response. + * @access public + */ + function getTransportError() { + return $this->page->getTransportError(); + } + + /** + * Accessor for current MIME type. + * @return string MIME type as string; e.g. 'text/html' + * @access public + */ + function getMimeType() { + return $this->page->getMimeType(); + } + + /** + * Accessor for last response code. + * @return integer Last HTTP response code received. + * @access public + */ + function getResponseCode() { + return $this->page->getResponseCode(); + } + + /** + * Accessor for last Authentication type. Only valid + * straight after a challenge (401). + * @return string Description of challenge type. + * @access public + */ + function getAuthentication() { + return $this->page->getAuthentication(); + } + + /** + * Accessor for last Authentication realm. Only valid + * straight after a challenge (401). + * @return string Name of security realm. + * @access public + */ + function getRealm() { + return $this->page->getRealm(); + } + + /** + * Accessor for current URL of page or frame if + * focused. + * @return string Location of current page or frame as + * a string. + */ + function getUrl() { + $url = $this->page->getUrl(); + return $url ? $url->asString() : false; + } + + /** + * Accessor for base URL of page if set via BASE tag + * @return string base URL + */ + function getBaseUrl() { + $url = $this->page->getBaseUrl(); + return $url ? $url->asString() : false; + } + + /** + * Accessor for raw bytes sent down the wire. + * @return string Original text sent. + * @access public + */ + function getRequest() { + return $this->page->getRequest(); + } + + /** + * Accessor for raw header information. + * @return string Header block. + * @access public + */ + function getHeaders() { + return $this->page->getHeaders(); + } + + /** + * Accessor for raw page information. + * @return string Original text content of web page. + * @access public + */ + function getContent() { + return $this->page->getRaw(); + } + + /** + * Accessor for plain text version of the page. + * @return string Normalised text representation. + * @access public + */ + function getContentAsText() { + return $this->page->getText(); + } + + /** + * Accessor for parsed title. + * @return string Title or false if no title is present. + * @access public + */ + function getTitle() { + return $this->page->getTitle(); + } + + /** + * Accessor for a list of all links in current page. + * @return array List of urls with scheme of + * http or https and hostname. + * @access public + */ + function getUrls() { + return $this->page->getUrls(); + } + + /** + * Sets all form fields with that name. + * @param string $label Name or label of field in forms. + * @param string $value New value of field. + * @return boolean True if field exists, otherwise false. + * @access public + */ + function setField($label, $value, $position=false) { + return $this->page->setField(new SimpleByLabelOrName($label), $value, $position); + } + + /** + * Sets all form fields with that name. Will use label if + * one is available (not yet implemented). + * @param string $name Name of field in forms. + * @param string $value New value of field. + * @return boolean True if field exists, otherwise false. + * @access public + */ + function setFieldByName($name, $value, $position=false) { + return $this->page->setField(new SimpleByName($name), $value, $position); + } + + /** + * Sets all form fields with that id attribute. + * @param string/integer $id Id of field in forms. + * @param string $value New value of field. + * @return boolean True if field exists, otherwise false. + * @access public + */ + function setFieldById($id, $value) { + return $this->page->setField(new SimpleById($id), $value); + } + + /** + * Accessor for a form element value within the page. + * Finds the first match. + * @param string $label Field label. + * @return string/boolean A value if the field is + * present, false if unchecked + * and null if missing. + * @access public + */ + function getField($label) { + return $this->page->getField(new SimpleByLabelOrName($label)); + } + + /** + * Accessor for a form element value within the page. + * Finds the first match. + * @param string $name Field name. + * @return string/boolean A string if the field is + * present, false if unchecked + * and null if missing. + * @access public + */ + function getFieldByName($name) { + return $this->page->getField(new SimpleByName($name)); + } + + /** + * Accessor for a form element value within the page. + * @param string/integer $id Id of field in forms. + * @return string/boolean A string if the field is + * present, false if unchecked + * and null if missing. + * @access public + */ + function getFieldById($id) { + return $this->page->getField(new SimpleById($id)); + } + + /** + * Clicks the submit button by label. The owning + * form will be submitted by this. + * @param string $label Button label. An unlabeled + * button can be triggered by 'Submit'. + * @param hash $additional Additional form data. + * @return string/boolean Page on success. + * @access public + */ + function clickSubmit($label = 'Submit', $additional = false) { + if (! ($form = $this->page->getFormBySubmit(new SimpleByLabel($label)))) { + return false; + } + $success = $this->load( + $form->getAction(), + $form->submitButton(new SimpleByLabel($label), $additional)); + return ($success ? $this->getContent() : $success); + } + + /** + * Clicks the submit button by name attribute. The owning + * form will be submitted by this. + * @param string $name Button name. + * @param hash $additional Additional form data. + * @return string/boolean Page on success. + * @access public + */ + function clickSubmitByName($name, $additional = false) { + if (! ($form = $this->page->getFormBySubmit(new SimpleByName($name)))) { + return false; + } + $success = $this->load( + $form->getAction(), + $form->submitButton(new SimpleByName($name), $additional)); + return ($success ? $this->getContent() : $success); + } + + /** + * Clicks the submit button by ID attribute of the button + * itself. The owning form will be submitted by this. + * @param string $id Button ID. + * @param hash $additional Additional form data. + * @return string/boolean Page on success. + * @access public + */ + function clickSubmitById($id, $additional = false) { + if (! ($form = $this->page->getFormBySubmit(new SimpleById($id)))) { + return false; + } + $success = $this->load( + $form->getAction(), + $form->submitButton(new SimpleById($id), $additional)); + return ($success ? $this->getContent() : $success); + } + + /** + * Tests to see if a submit button exists with this + * label. + * @param string $label Button label. + * @return boolean True if present. + * @access public + */ + function isSubmit($label) { + return (boolean)$this->page->getFormBySubmit(new SimpleByLabel($label)); + } + + /** + * Clicks the submit image by some kind of label. Usually + * the alt tag or the nearest equivalent. The owning + * form will be submitted by this. Clicking outside of + * the boundary of the coordinates will result in + * a failure. + * @param string $label ID attribute of button. + * @param integer $x X-coordinate of imaginary click. + * @param integer $y Y-coordinate of imaginary click. + * @param hash $additional Additional form data. + * @return string/boolean Page on success. + * @access public + */ + function clickImage($label, $x = 1, $y = 1, $additional = false) { + if (! ($form = $this->page->getFormByImage(new SimpleByLabel($label)))) { + return false; + } + $success = $this->load( + $form->getAction(), + $form->submitImage(new SimpleByLabel($label), $x, $y, $additional)); + return ($success ? $this->getContent() : $success); + } + + /** + * Clicks the submit image by the name. Usually + * the alt tag or the nearest equivalent. The owning + * form will be submitted by this. Clicking outside of + * the boundary of the coordinates will result in + * a failure. + * @param string $name Name attribute of button. + * @param integer $x X-coordinate of imaginary click. + * @param integer $y Y-coordinate of imaginary click. + * @param hash $additional Additional form data. + * @return string/boolean Page on success. + * @access public + */ + function clickImageByName($name, $x = 1, $y = 1, $additional = false) { + if (! ($form = $this->page->getFormByImage(new SimpleByName($name)))) { + return false; + } + $success = $this->load( + $form->getAction(), + $form->submitImage(new SimpleByName($name), $x, $y, $additional)); + return ($success ? $this->getContent() : $success); + } + + /** + * Clicks the submit image by ID attribute. The owning + * form will be submitted by this. Clicking outside of + * the boundary of the coordinates will result in + * a failure. + * @param integer/string $id ID attribute of button. + * @param integer $x X-coordinate of imaginary click. + * @param integer $y Y-coordinate of imaginary click. + * @param hash $additional Additional form data. + * @return string/boolean Page on success. + * @access public + */ + function clickImageById($id, $x = 1, $y = 1, $additional = false) { + if (! ($form = $this->page->getFormByImage(new SimpleById($id)))) { + return false; + } + $success = $this->load( + $form->getAction(), + $form->submitImage(new SimpleById($id), $x, $y, $additional)); + return ($success ? $this->getContent() : $success); + } + + /** + * Tests to see if an image exists with this + * title or alt text. + * @param string $label Image text. + * @return boolean True if present. + * @access public + */ + function isImage($label) { + return (boolean)$this->page->getFormByImage(new SimpleByLabel($label)); + } + + /** + * Submits a form by the ID. + * @param string $id The form ID. No submit button value + * will be sent. + * @return string/boolean Page on success. + * @access public + */ + function submitFormById($id) { + if (! ($form = $this->page->getFormById($id))) { + return false; + } + $success = $this->load( + $form->getAction(), + $form->submit()); + return ($success ? $this->getContent() : $success); + } + + /** + * Finds a URL by label. Will find the first link + * found with this link text by default, or a later + * one if an index is given. The match ignores case and + * white space issues. + * @param string $label Text between the anchor tags. + * @param integer $index Link position counting from zero. + * @return string/boolean URL on success. + * @access public + */ + function getLink($label, $index = 0) { + $urls = $this->page->getUrlsByLabel($label); + if (count($urls) == 0) { + return false; + } + if (count($urls) < $index + 1) { + return false; + } + return $urls[$index]; + } + + /** + * Follows a link by label. Will click the first link + * found with this link text by default, or a later + * one if an index is given. The match ignores case and + * white space issues. + * @param string $label Text between the anchor tags. + * @param integer $index Link position counting from zero. + * @return string/boolean Page on success. + * @access public + */ + function clickLink($label, $index = 0) { + $url = $this->getLink($label, $index); + if ($url === false) { + return false; + } + $this->load($url, new SimpleGetEncoding()); + return $this->getContent(); + } + + /** + * Finds a link by id attribute. + * @param string $id ID attribute value. + * @return string/boolean URL on success. + * @access public + */ + function getLinkById($id) { + return $this->page->getUrlById($id); + } + + /** + * Follows a link by id attribute. + * @param string $id ID attribute value. + * @return string/boolean Page on success. + * @access public + */ + function clickLinkById($id) { + if (! ($url = $this->getLinkById($id))) { + return false; + } + $this->load($url, new SimpleGetEncoding()); + return $this->getContent(); + } + + /** + * Clicks a visible text item. Will first try buttons, + * then links and then images. + * @param string $label Visible text or alt text. + * @return string/boolean Raw page or false. + * @access public + */ + function click($label) { + $raw = $this->clickSubmit($label); + if (! $raw) { + $raw = $this->clickLink($label); + } + if (! $raw) { + $raw = $this->clickImage($label); + } + return $raw; + } + + /** + * Tests to see if a click target exists. + * @param string $label Visible text or alt text. + * @return boolean True if target present. + * @access public + */ + function isClickable($label) { + return $this->isSubmit($label) || ($this->getLink($label) !== false) || $this->isImage($label); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/collector.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/collector.php new file mode 100644 index 0000000..0f8e91f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/collector.php @@ -0,0 +1,122 @@ + + * @package SimpleTest + * @subpackage UnitTester + * @version $Id: collector.php 1784 2008-04-26 13:07:14Z pp11 $ + */ + +/** + * The basic collector for {@link GroupTest} + * + * @see collect(), GroupTest::collect() + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleCollector { + + /** + * Strips off any kind of slash at the end so as to normalise the path. + * @param string $path Path to normalise. + * @return string Path without trailing slash. + */ + protected function removeTrailingSlash($path) { + if (substr($path, -1) == DIRECTORY_SEPARATOR) { + return substr($path, 0, -1); + } elseif (substr($path, -1) == '/') { + return substr($path, 0, -1); + } else { + return $path; + } + } + + /** + * Scans the directory and adds what it can. + * @param object $test Group test with {@link GroupTest::addTestFile()} method. + * @param string $path Directory to scan. + * @see _attemptToAdd() + */ + function collect(&$test, $path) { + $path = $this->removeTrailingSlash($path); + if ($handle = opendir($path)) { + while (($entry = readdir($handle)) !== false) { + if ($this->isHidden($entry)) { + continue; + } + $this->handle($test, $path . DIRECTORY_SEPARATOR . $entry); + } + closedir($handle); + } + } + + /** + * This method determines what should be done with a given file and adds + * it via {@link GroupTest::addTestFile()} if necessary. + * + * This method should be overriden to provide custom matching criteria, + * such as pattern matching, recursive matching, etc. For an example, see + * {@link SimplePatternCollector::_handle()}. + * + * @param object $test Group test with {@link GroupTest::addTestFile()} method. + * @param string $filename A filename as generated by {@link collect()} + * @see collect() + * @access protected + */ + protected function handle(&$test, $file) { + if (is_dir($file)) { + return; + } + $test->addFile($file); + } + + /** + * Tests for hidden files so as to skip them. Currently + * only tests for Unix hidden files. + * @param string $filename Plain filename. + * @return boolean True if hidden file. + * @access private + */ + protected function isHidden($filename) { + return strncmp($filename, '.', 1) == 0; + } +} + +/** + * An extension to {@link SimpleCollector} that only adds files matching a + * given pattern. + * + * @package SimpleTest + * @subpackage UnitTester + * @see SimpleCollector + */ +class SimplePatternCollector extends SimpleCollector { + private $pattern; + + /** + * + * @param string $pattern Perl compatible regex to test name against + * See {@link http://us4.php.net/manual/en/reference.pcre.pattern.syntax.php PHP's PCRE} + * for full documentation of valid pattern.s + */ + function __construct($pattern = '/php$/i') { + $this->pattern = $pattern; + } + + /** + * Attempts to add files that match a given pattern. + * + * @see SimpleCollector::_handle() + * @param object $test Group test with {@link GroupTest::addTestFile()} method. + * @param string $path Directory to scan. + * @access protected + */ + protected function handle(&$test, $filename) { + if (preg_match($this->pattern, $filename)) { + parent::handle($test, $filename); + } + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/compatibility.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/compatibility.php new file mode 100644 index 0000000..6e90fe4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/compatibility.php @@ -0,0 +1,166 @@ += 0) { + eval('$copy = clone $object;'); + return $copy; + } + return $object; + } + + /** + * Identity test. Drops back to equality + types for PHP5 + * objects as the === operator counts as the + * stronger reference constraint. + * @param mixed $first Test subject. + * @param mixed $second Comparison object. + * @return boolean True if identical. + * @access public + */ + static function isIdentical($first, $second) { + if (version_compare(phpversion(), '5') >= 0) { + return SimpleTestCompatibility::isIdenticalType($first, $second); + } + if ($first != $second) { + return false; + } + return ($first === $second); + } + + /** + * Recursive type test. + * @param mixed $first Test subject. + * @param mixed $second Comparison object. + * @return boolean True if same type. + * @access private + */ + protected static function isIdenticalType($first, $second) { + if (gettype($first) != gettype($second)) { + return false; + } + if (is_object($first) && is_object($second)) { + if (get_class($first) != get_class($second)) { + return false; + } + return SimpleTestCompatibility::isArrayOfIdenticalTypes( + get_object_vars($first), + get_object_vars($second)); + } + if (is_array($first) && is_array($second)) { + return SimpleTestCompatibility::isArrayOfIdenticalTypes($first, $second); + } + if ($first !== $second) { + return false; + } + return true; + } + + /** + * Recursive type test for each element of an array. + * @param mixed $first Test subject. + * @param mixed $second Comparison object. + * @return boolean True if identical. + * @access private + */ + protected static function isArrayOfIdenticalTypes($first, $second) { + if (array_keys($first) != array_keys($second)) { + return false; + } + foreach (array_keys($first) as $key) { + $is_identical = SimpleTestCompatibility::isIdenticalType( + $first[$key], + $second[$key]); + if (! $is_identical) { + return false; + } + } + return true; + } + + /** + * Test for two variables being aliases. + * @param mixed $first Test subject. + * @param mixed $second Comparison object. + * @return boolean True if same. + * @access public + */ + static function isReference(&$first, &$second) { + if (version_compare(phpversion(), '5', '>=') && is_object($first)) { + return ($first === $second); + } + if (is_object($first) && is_object($second)) { + $id = uniqid("test"); + $first->$id = true; + $is_ref = isset($second->$id); + unset($first->$id); + return $is_ref; + } + $temp = $first; + $first = uniqid("test"); + $is_ref = ($first === $second); + $first = $temp; + return $is_ref; + } + + /** + * Test to see if an object is a member of a + * class hiearchy. + * @param object $object Object to test. + * @param string $class Root name of hiearchy. + * @return boolean True if class in hiearchy. + * @access public + */ + static function isA($object, $class) { + if (version_compare(phpversion(), '5') >= 0) { + if (! class_exists($class, false)) { + if (function_exists('interface_exists')) { + if (! interface_exists($class, false)) { + return false; + } + } + } + eval("\$is_a = \$object instanceof $class;"); + return $is_a; + } + if (function_exists('is_a')) { + return is_a($object, $class); + } + return ((strtolower($class) == get_class($object)) + or (is_subclass_of($object, $class))); + } + + /** + * Sets a socket timeout for each chunk. + * @param resource $handle Socket handle. + * @param integer $timeout Limit in seconds. + * @access public + */ + static function setTimeout($handle, $timeout) { + if (function_exists('stream_set_timeout')) { + stream_set_timeout($handle, $timeout, 0); + } elseif (function_exists('socket_set_timeout')) { + socket_set_timeout($handle, $timeout, 0); + } elseif (function_exists('set_socket_timeout')) { + set_socket_timeout($handle, $timeout, 0); + } + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/cookies.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/cookies.php new file mode 100644 index 0000000..675bd20 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/cookies.php @@ -0,0 +1,380 @@ +host = false; + $this->name = $name; + $this->value = $value; + $this->path = ($path ? $this->fixPath($path) : "/"); + $this->expiry = false; + if (is_string($expiry)) { + $this->expiry = strtotime($expiry); + } elseif (is_integer($expiry)) { + $this->expiry = $expiry; + } + $this->is_secure = $is_secure; + } + + /** + * Sets the host. The cookie rules determine + * that the first two parts are taken for + * certain TLDs and three for others. If the + * new host does not match these rules then the + * call will fail. + * @param string $host New hostname. + * @return boolean True if hostname is valid. + * @access public + */ + function setHost($host) { + if ($host = $this->truncateHost($host)) { + $this->host = $host; + return true; + } + return false; + } + + /** + * Accessor for the truncated host to which this + * cookie applies. + * @return string Truncated hostname. + * @access public + */ + function getHost() { + return $this->host; + } + + /** + * Test for a cookie being valid for a host name. + * @param string $host Host to test against. + * @return boolean True if the cookie would be valid + * here. + */ + function isValidHost($host) { + return ($this->truncateHost($host) === $this->getHost()); + } + + /** + * Extracts just the domain part that determines a + * cookie's host validity. + * @param string $host Host name to truncate. + * @return string Domain or false on a bad host. + * @access private + */ + protected function truncateHost($host) { + $tlds = SimpleUrl::getAllTopLevelDomains(); + if (preg_match('/[a-z\-]+\.(' . $tlds . ')$/i', $host, $matches)) { + return $matches[0]; + } elseif (preg_match('/[a-z\-]+\.[a-z\-]+\.[a-z\-]+$/i', $host, $matches)) { + return $matches[0]; + } + return false; + } + + /** + * Accessor for name. + * @return string Cookie key. + * @access public + */ + function getName() { + return $this->name; + } + + /** + * Accessor for value. A deleted cookie will + * have an empty string for this. + * @return string Cookie value. + * @access public + */ + function getValue() { + return $this->value; + } + + /** + * Accessor for path. + * @return string Valid cookie path. + * @access public + */ + function getPath() { + return $this->path; + } + + /** + * Tests a path to see if the cookie applies + * there. The test path must be longer or + * equal to the cookie path. + * @param string $path Path to test against. + * @return boolean True if cookie valid here. + * @access public + */ + function isValidPath($path) { + return (strncmp( + $this->fixPath($path), + $this->getPath(), + strlen($this->getPath())) == 0); + } + + /** + * Accessor for expiry. + * @return string Expiry string. + * @access public + */ + function getExpiry() { + if (! $this->expiry) { + return false; + } + return gmdate("D, d M Y H:i:s", $this->expiry) . " GMT"; + } + + /** + * Test to see if cookie is expired against + * the cookie format time or timestamp. + * Will give true for a session cookie. + * @param integer/string $now Time to test against. Result + * will be false if this time + * is later than the cookie expiry. + * Can be either a timestamp integer + * or a cookie format date. + * @access public + */ + function isExpired($now) { + if (! $this->expiry) { + return true; + } + if (is_string($now)) { + $now = strtotime($now); + } + return ($this->expiry < $now); + } + + /** + * Ages the cookie by the specified number of + * seconds. + * @param integer $interval In seconds. + * @public + */ + function agePrematurely($interval) { + if ($this->expiry) { + $this->expiry -= $interval; + } + } + + /** + * Accessor for the secure flag. + * @return boolean True if cookie needs SSL. + * @access public + */ + function isSecure() { + return $this->is_secure; + } + + /** + * Adds a trailing and leading slash to the path + * if missing. + * @param string $path Path to fix. + * @access private + */ + protected function fixPath($path) { + if (substr($path, 0, 1) != '/') { + $path = '/' . $path; + } + if (substr($path, -1, 1) != '/') { + $path .= '/'; + } + return $path; + } +} + +/** + * Repository for cookies. This stuff is a + * tiny bit browser dependent. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleCookieJar { + private $cookies; + + /** + * Constructor. Jar starts empty. + * @access public + */ + function __construct() { + $this->cookies = array(); + } + + /** + * Removes expired and temporary cookies as if + * the browser was closed and re-opened. + * @param string/integer $now Time to test expiry against. + * @access public + */ + function restartSession($date = false) { + $surviving_cookies = array(); + for ($i = 0; $i < count($this->cookies); $i++) { + if (! $this->cookies[$i]->getValue()) { + continue; + } + if (! $this->cookies[$i]->getExpiry()) { + continue; + } + if ($date && $this->cookies[$i]->isExpired($date)) { + continue; + } + $surviving_cookies[] = $this->cookies[$i]; + } + $this->cookies = $surviving_cookies; + } + + /** + * Ages all cookies in the cookie jar. + * @param integer $interval The old session is moved + * into the past by this number + * of seconds. Cookies now over + * age will be removed. + * @access public + */ + function agePrematurely($interval) { + for ($i = 0; $i < count($this->cookies); $i++) { + $this->cookies[$i]->agePrematurely($interval); + } + } + + /** + * Sets an additional cookie. If a cookie has + * the same name and path it is replaced. + * @param string $name Cookie key. + * @param string $value Value of cookie. + * @param string $host Host upon which the cookie is valid. + * @param string $path Cookie path if not host wide. + * @param string $expiry Expiry date. + * @access public + */ + function setCookie($name, $value, $host = false, $path = '/', $expiry = false) { + $cookie = new SimpleCookie($name, $value, $path, $expiry); + if ($host) { + $cookie->setHost($host); + } + $this->cookies[$this->findFirstMatch($cookie)] = $cookie; + } + + /** + * Finds a matching cookie to write over or the + * first empty slot if none. + * @param SimpleCookie $cookie Cookie to write into jar. + * @return integer Available slot. + * @access private + */ + protected function findFirstMatch($cookie) { + for ($i = 0; $i < count($this->cookies); $i++) { + $is_match = $this->isMatch( + $cookie, + $this->cookies[$i]->getHost(), + $this->cookies[$i]->getPath(), + $this->cookies[$i]->getName()); + if ($is_match) { + return $i; + } + } + return count($this->cookies); + } + + /** + * Reads the most specific cookie value from the + * browser cookies. Looks for the longest path that + * matches. + * @param string $host Host to search. + * @param string $path Applicable path. + * @param string $name Name of cookie to read. + * @return string False if not present, else the + * value as a string. + * @access public + */ + function getCookieValue($host, $path, $name) { + $longest_path = ''; + foreach ($this->cookies as $cookie) { + if ($this->isMatch($cookie, $host, $path, $name)) { + if (strlen($cookie->getPath()) > strlen($longest_path)) { + $value = $cookie->getValue(); + $longest_path = $cookie->getPath(); + } + } + } + return (isset($value) ? $value : false); + } + + /** + * Tests cookie for matching against search + * criteria. + * @param SimpleTest $cookie Cookie to test. + * @param string $host Host must match. + * @param string $path Cookie path must be shorter than + * this path. + * @param string $name Name must match. + * @return boolean True if matched. + * @access private + */ + protected function isMatch($cookie, $host, $path, $name) { + if ($cookie->getName() != $name) { + return false; + } + if ($host && $cookie->getHost() && ! $cookie->isValidHost($host)) { + return false; + } + if (! $cookie->isValidPath($path)) { + return false; + } + return true; + } + + /** + * Uses a URL to sift relevant cookies by host and + * path. Results are list of strings of form "name=value". + * @param SimpleUrl $url Url to select by. + * @return array Valid name and value pairs. + * @access public + */ + function selectAsPairs($url) { + $pairs = array(); + foreach ($this->cookies as $cookie) { + if ($this->isMatch($cookie, $url->getHost(), $url->getPath(), $cookie->getName())) { + $pairs[] = $cookie->getName() . '=' . $cookie->getValue(); + } + } + return $pairs; + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/default_reporter.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/default_reporter.php new file mode 100644 index 0000000..9ec708d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/default_reporter.php @@ -0,0 +1,163 @@ + 'case', 'c' => 'case', + 'test' => 'test', 't' => 'test', + ); + private $case = ''; + private $test = ''; + private $xml = false; + private $help = false; + private $no_skips = false; + + /** + * Parses raw command line arguments into object properties. + * @param string $arguments Raw commend line arguments. + */ + function __construct($arguments) { + if (! is_array($arguments)) { + return; + } + foreach ($arguments as $i => $argument) { + if (preg_match('/^--?(test|case|t|c)=(.+)$/', $argument, $matches)) { + $property = $this->to_property[$matches[1]]; + $this->$property = $matches[2]; + } elseif (preg_match('/^--?(test|case|t|c)$/', $argument, $matches)) { + $property = $this->to_property[$matches[1]]; + if (isset($arguments[$i + 1])) { + $this->$property = $arguments[$i + 1]; + } + } elseif (preg_match('/^--?(xml|x)$/', $argument)) { + $this->xml = true; + } elseif (preg_match('/^--?(no-skip|no-skips|s)$/', $argument)) { + $this->no_skips = true; + } elseif (preg_match('/^--?(help|h)$/', $argument)) { + $this->help = true; + } + } + } + + /** + * Run only this test. + * @return string Test name to run. + */ + function getTest() { + return $this->test; + } + + /** + * Run only this test suite. + * @return string Test class name to run. + */ + function getTestCase() { + return $this->case; + } + + /** + * Output should be XML or not. + * @return boolean True if XML desired. + */ + function isXml() { + return $this->xml; + } + + /** + * Output should suppress skip messages. + * @return boolean True for no skips. + */ + function noSkips() { + return $this->no_skips; + } + + /** + * Output should be a help message. Disabled during XML mode. + * @return boolean True if help message desired. + */ + function help() { + return $this->help && !$this->xml; + } + + /** + * Returns plain-text help message for command line runner. + * @return string String help message + */ + function getHelpText() { + return << [args...] + + -c Run only the test-case + -t Run only the test method + -s Suppress skip messages + -x Return test results in XML + -h Display this help message + +HELP; + } + +} + +/** + * The default reporter used by SimpleTest's autorun + * feature. The actual reporters used are dependency + * injected and can be overridden. + * @package SimpleTest + * @subpackage UnitTester + */ +class DefaultReporter extends SimpleReporterDecorator { + + /** + * Assembles the appopriate reporter for the environment. + */ + function __construct() { + if (SimpleReporter::inCli()) { + $parser = new SimpleCommandLineParser($_SERVER['argv']); + $interfaces = $parser->isXml() ? array('XmlReporter') : array('TextReporter'); + if ($parser->help()) { + // I'm not sure if we should do the echo'ing here -- ezyang + echo $parser->getHelpText(); + exit(1); + } + $reporter = new SelectiveReporter( + SimpleTest::preferred($interfaces), + $parser->getTestCase(), + $parser->getTest()); + if ($parser->noSkips()) { + $reporter = new NoSkipsReporter($reporter); + } + } else { + $reporter = new SelectiveReporter( + SimpleTest::preferred('HtmlReporter'), + @$_GET['c'], + @$_GET['t']); + if (@$_GET['skips'] == 'no' || @$_GET['show-skips'] == 'no') { + $reporter = new NoSkipsReporter($reporter); + } + } + parent::__construct($reporter); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/detached.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/detached.php new file mode 100644 index 0000000..a325e14 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/detached.php @@ -0,0 +1,96 @@ +command = $command; + $this->dry_command = $dry_command ? $dry_command : $command; + $this->size = false; + } + + /** + * Accessor for the test name for subclasses. + * @return string Name of the test. + * @access public + */ + function getLabel() { + return $this->command; + } + + /** + * Runs the top level test for this class. Currently + * reads the data as a single chunk. I'll fix this + * once I have added iteration to the browser. + * @param SimpleReporter $reporter Target of test results. + * @returns boolean True if no failures. + * @access public + */ + function run(&$reporter) { + $shell = &new SimpleShell(); + $shell->execute($this->command); + $parser = &$this->createParser($reporter); + if (! $parser->parse($shell->getOutput())) { + trigger_error('Cannot parse incoming XML from [' . $this->command . ']'); + return false; + } + return true; + } + + /** + * Accessor for the number of subtests. + * @return integer Number of test cases. + * @access public + */ + function getSize() { + if ($this->size === false) { + $shell = &new SimpleShell(); + $shell->execute($this->dry_command); + $reporter = &new SimpleReporter(); + $parser = &$this->createParser($reporter); + if (! $parser->parse($shell->getOutput())) { + trigger_error('Cannot parse incoming XML from [' . $this->dry_command . ']'); + return false; + } + $this->size = $reporter->getTestCaseCount(); + } + return $this->size; + } + + /** + * Creates the XML parser. + * @param SimpleReporter $reporter Target of test results. + * @return SimpleTestXmlListener XML reader. + * @access protected + */ + protected function &createParser(&$reporter) { + return new SimpleTestXmlParser($reporter); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/en/docs.css b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/en/docs.css new file mode 100644 index 0000000..18368a0 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/en/docs.css @@ -0,0 +1,121 @@ +body { + padding-left: 3%; + padding-right: 3%; +} +h1, h2, h3 { + font-family: sans-serif; +} +h1 { + text-align: center; +} +pre { + font-family: "courier new", courier, typewriter, monospace; + font-size: 90%; + border: 1px solid; + border-color: #999966; + background-color: #ffffcc; + padding: 5px; + margin-left: 20px; + margin-right: 40px; +} +.code, .new_code, pre.new_code { + font-family: "courier new", courier, typewriter, monospace; + font-weight: bold; +} +div.copyright { + font-size: 80%; + color: gray; +} +div.copyright a { + margin-top: 1em; + color: gray; +} +ul.api { + border: 2px outset; + border-color: gray; + background-color: white; + margin: 5px; + margin-left: 5%; + margin-right: 5%; +} +ul.api li { + margin-top: 0.2em; + margin-bottom: 0.2em; + list-style: none; + text-indent: -3em; + padding-left: 1em; +} +div.demo { + border: 4px ridge; + border-color: gray; + padding: 10px; + margin: 5px; + margin-left: 20px; + margin-right: 40px; + background-color: white; +} +div.demo span.fail { + color: red; +} +div.demo span.pass { + color: green; +} +div.demo h1 { + font-size: 12pt; + text-align: left; + font-weight: bold; +} +div.menu { + text-align: center; +} +table { + border: 2px outset; + border-color: gray; + background-color: white; + margin: 5px; + margin-left: 5%; + margin-right: 5%; +} +td { + font-size: 90%; +} +.shell { + color: white; +} +pre.shell { + border: 4px ridge; + border-color: gray; + padding: 10px; + margin: 5px; + margin-left: 20px; + margin-right: 40px; + background-color: #000100; + color: #99ff99; + font-size: 90%; +} +pre.file { + color: black; + border: 1px solid; + border-color: black; + padding: 10px; + margin: 5px; + margin-left: 20px; + margin-right: 40px; + background-color: white; + font-size: 90%; +} +form.demo { + background-color: lightgray; + border: 4px outset; + border-color: lightgray; + padding: 10px; + margin-right: 40%; +} +dl, dd { + margin: 10px; + margin-left: 30px; +} +em { + font-weight: bold; + font-family: "courier new", courier, typewriter, monospace; +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/fr/docs.css b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/fr/docs.css new file mode 100644 index 0000000..4917048 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/fr/docs.css @@ -0,0 +1,84 @@ +body { + padding-left: 3%; + padding-right: 3%; +} +pre { + font-family: "courier new", courier; + font-size: 80%; + border: 1px solid; + background-color: #cccccc; + padding: 5px; + margin-left: 5%; + margin-right: 8%; +} +.code, .new_code, pre.new_code { + font-weight: bold; +} +div.copyright { + font-size: 80%; + color: gray; +} +div.copyright a { + color: gray; +} +ul.api { + padding-left: 0em; + padding-right: 25%; +} +ul.api li { + margin-top: 0.2em; + margin-bottom: 0.2em; + list-style: none; + text-indent: -3em; + padding-left: 3em; +} +div.demo { + border: 4px ridge; + border-color: gray; + padding: 10px; + margin: 5px; + margin-left: 20px; + margin-right: 40px; + background-color: white; +} +div.demo span.fail { + color: red; +} +div.demo span.pass { + color: green; +} +div.demo h1 { + font-size: 12pt; + text-align: left; + font-weight: bold; +} +table { + border: 2px outset; + border-color: gray; + background-color: white; + margin: 5px; + margin-left: 5%; + margin-right: 5%; +} +td { + font-size: 80%; +} +.shell { + color: white; +} +pre.shell { + border: 4px ridge; + border-color: gray; + padding: 10px; + margin: 5px; + margin-left: 20px; + margin-right: 40px; + background-color: black; +} +form.demo { + background-color: lightgray; + border: 4px outset; + border-color: lightgray; + padding: 10px; + margin-right: 40%; +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/lastcraft/README b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/lastcraft/README new file mode 100644 index 0000000..488198a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/lastcraft/README @@ -0,0 +1 @@ +Output folder for Lastcraft site versions of documentation. \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/onpk/README b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/onpk/README new file mode 100644 index 0000000..fc1dd62 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/onpk/README @@ -0,0 +1 @@ +Répertoire utilisé pour l'extraction de la documentation au format du site onpk.net \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/pkg/README b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/pkg/README new file mode 100644 index 0000000..821b4f6 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/pkg/README @@ -0,0 +1 @@ +Files here are generated from make_phpdoc_docs.sh \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/README b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/README new file mode 100644 index 0000000..4126955 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/README @@ -0,0 +1 @@ +Output folder for SimpleTest.org site versions of content, documentation & tutorial. \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/favicon.ico b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/favicon.ico new file mode 100644 index 0000000..d4c1cc1 Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/favicon.ico differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/book-domain-driven-design.jpg b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/book-domain-driven-design.jpg new file mode 100644 index 0000000..3bebb8e Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/book-domain-driven-design.jpg differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/book-guide-to-php-design-patterns.jpg b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/book-guide-to-php-design-patterns.jpg new file mode 100644 index 0000000..ffb536d Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/book-guide-to-php-design-patterns.jpg differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/book-the-php-anthology-object-oriented-php-solutions.jpg b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/book-the-php-anthology-object-oriented-php-solutions.jpg new file mode 100644 index 0000000..d0e502b Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/book-the-php-anthology-object-oriented-php-solutions.jpg differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/quote.png b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/quote.png new file mode 100644 index 0000000..a83cf71 Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/quote.png differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-contribute.png b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-contribute.png new file mode 100755 index 0000000..734463e Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-contribute.png differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-download.png b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-download.png new file mode 100644 index 0000000..22990ce Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-download.png differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-external-bottom.png b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-external-bottom.png new file mode 100644 index 0000000..d976212 Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-external-bottom.png differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-external-middle.png b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-external-middle.png new file mode 100644 index 0000000..fb37e19 Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-external-middle.png differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-external-top.png b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-external-top.png new file mode 100644 index 0000000..3f1ff2d Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-external-top.png differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-internal-bottom.png b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-internal-bottom.png new file mode 100644 index 0000000..eec1b2a Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-internal-bottom.png differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-internal-middle.png b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-internal-middle.png new file mode 100644 index 0000000..0444741 Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-internal-middle.png differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-internal-top.png b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-internal-top.png new file mode 100644 index 0000000..8e27bc9 Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-internal-top.png differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-logo.png b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-logo.png new file mode 100644 index 0000000..76c497c Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-logo.png differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-start-testing.png b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-start-testing.png new file mode 100644 index 0000000..f36a58e Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-start-testing.png differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-support.png b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-support.png new file mode 100644 index 0000000..aedd5a0 Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/simpletest-support.png differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/test-in-cli.png b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/test-in-cli.png new file mode 100644 index 0000000..abb6159 Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/test-in-cli.png differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/test-with-1-fail.png b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/test-with-1-fail.png new file mode 100644 index 0000000..74834de Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/test-with-1-fail.png differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/test-with-1-pass.png b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/test-with-1-pass.png new file mode 100644 index 0000000..dbbce1a Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/images/test-with-1-pass.png differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/index.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/index.html new file mode 100644 index 0000000..4a7d4b5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/index.html @@ -0,0 +1,192 @@ + + + +SimpleTest - Unit Testing for PHP + + + + +
    +
    + +
    +
    + +

    + SimpleTest 1.0.1 has been + + released. + This is the last PHP 4 compatible release. + From now on, compatibility extends from PHP 5.0.5+. +

    +
    +
    + +

    + Familiar with unit testing ? Just dive directly into SimpleTest with + the one-page starter and + the complete API. +
    + Otherwise see the ongoing + documentation. +
    + And for example test cases check out the + tutorial. +

    +
    +
    + +

    + Need help on your testing strategy ? + Feel free to join the + SimpleTest support mailing-list. +

    +

    + If you need some new functionnality in SimpleTest, you may want to look at + the features tracker. + Also the bug and + patches trackers can be useful. +

    +
    +
    + +

    + And if you feel like giving a hand, feel free to look around the + current TODO list... +

    +
    +
    + + SourceForge.net Logo + +
    +
    +
    +
    +

    + [2008/04/08] + + SimpleTest 1.0.1 is released. + The last release that supports PHP4.2 up to PHP5.3 inclusive. + Compared to the last stable release, the main significant change + is the switch to autorun. +

    +

    + The SimpleTest PHP unit tester + is available for download from your nearest + SourceForge. + It is a PHP unit test and web test framework. + Users of JUnit will be + familiar with most of the interface. + The JWebUnit + style functionality is more complete now. + It has support for SSL, forms, frames, proxies and basic authentication. + The idea is that common but fiddly PHP tasks, such as logging into a site, + can be tested easily. +

    +

    Screenshots

    +

    + Here's what the result of your first test would look like : +

    +

    + test with 1 pass +

    +

    + Well not quite. In true TDD fashion, you should see a failing test case : +

    +

    + test with 1 fail +

    +

    + You may also prefer doing your testing with the command-line : +

    +

    + test in cli +

    +

    Documentation

    +

    + While (still) very scattered around different sites, + the SimpleTest documentation is quite dense and thorough. +

    + +

    + Other type of interesting stuff while starting out + with Test Driven Development and SimpleTest include : +

    + +

    + A couple of books do use SimpleTest quite extensively : +

    + +

    Contributing

    +

    + For translators the documentation is available in XML format : + we're always please to add new languages to our code base. +

    +

    + And while we do try our best keeping this tool bug-free, detecting defects and + submitting failing test cases and/or patches can come very handy ! Interested ? + Drop by the mailing-list, + most things tend to happen there... +

    +
    +
    + + + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/js/jquery-1.2.1.pack.js b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/js/jquery-1.2.1.pack.js new file mode 100644 index 0000000..d646859 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/js/jquery-1.2.1.pack.js @@ -0,0 +1,11 @@ +/* + * jQuery 1.2.1 - New Wave Javascript + * + * Copyright (c) 2007 John Resig (jquery.com) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * $Date: 2007-09-16 23:42:06 -0400 (Sun, 16 Sep 2007) $ + * $Rev: 3353 $ + */ +eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(G(){9(1m E!="W")H w=E;H E=18.15=G(a,b){I 6 7u E?6.5N(a,b):1u E(a,b)};9(1m $!="W")H D=$;18.$=E;H u=/^[^<]*(<(.|\\s)+>)[^>]*$|^#(\\w+)$/;E.1b=E.3A={5N:G(c,a){c=c||U;9(1m c=="1M"){H m=u.2S(c);9(m&&(m[1]||!a)){9(m[1])c=E.4D([m[1]],a);J{H b=U.3S(m[3]);9(b)9(b.22!=m[3])I E().1Y(c);J{6[0]=b;6.K=1;I 6}J c=[]}}J I 1u E(a).1Y(c)}J 9(E.1n(c))I 1u E(U)[E.1b.2d?"2d":"39"](c);I 6.6v(c.1c==1B&&c||(c.4c||c.K&&c!=18&&!c.1y&&c[0]!=W&&c[0].1y)&&E.2h(c)||[c])},4c:"1.2.1",7Y:G(){I 6.K},K:0,21:G(a){I a==W?E.2h(6):6[a]},2o:G(a){H b=E(a);b.4Y=6;I b},6v:G(a){6.K=0;1B.3A.1a.16(6,a);I 6},N:G(a,b){I E.N(6,a,b)},4I:G(a){H b=-1;6.N(G(i){9(6==a)b=i});I b},1x:G(f,d,e){H c=f;9(f.1c==3X)9(d==W)I 6.K&&E[e||"1x"](6[0],f)||W;J{c={};c[f]=d}I 6.N(G(a){L(H b 1i c)E.1x(e?6.R:6,b,E.1e(6,c[b],e,a,b))})},17:G(b,a){I 6.1x(b,a,"3C")},2g:G(e){9(1m e!="5i"&&e!=S)I 6.4n().3g(U.6F(e));H t="";E.N(e||6,G(){E.N(6.3j,G(){9(6.1y!=8)t+=6.1y!=1?6.6x:E.1b.2g([6])})});I t},5m:G(b){9(6[0])E(b,6[0].3H).6u().3d(6[0]).1X(G(){H a=6;1W(a.1w)a=a.1w;I a}).3g(6);I 6},8m:G(a){I 6.N(G(){E(6).6q().5m(a)})},8d:G(a){I 6.N(G(){E(6).5m(a)})},3g:G(){I 6.3z(1q,Q,1,G(a){6.58(a)})},6j:G(){I 6.3z(1q,Q,-1,G(a){6.3d(a,6.1w)})},6g:G(){I 6.3z(1q,P,1,G(a){6.12.3d(a,6)})},50:G(){I 6.3z(1q,P,-1,G(a){6.12.3d(a,6.2q)})},2D:G(){I 6.4Y||E([])},1Y:G(t){H b=E.1X(6,G(a){I E.1Y(t,a)});I 6.2o(/[^+>] [^+>]/.14(t)||t.1g("..")>-1?E.4V(b):b)},6u:G(e){H f=6.1X(G(){I 6.67?E(6.67)[0]:6.4R(Q)});H d=f.1Y("*").4O().N(G(){9(6[F]!=W)6[F]=S});9(e===Q)6.1Y("*").4O().N(G(i){H c=E.M(6,"2P");L(H a 1i c)L(H b 1i c[a])E.1j.1f(d[i],a,c[a][b],c[a][b].M)});I f},1E:G(t){I 6.2o(E.1n(t)&&E.2W(6,G(b,a){I t.16(b,[a])})||E.3m(t,6))},5V:G(t){I 6.2o(t.1c==3X&&E.3m(t,6,Q)||E.2W(6,G(a){I(t.1c==1B||t.4c)?E.2A(a,t)<0:a!=t}))},1f:G(t){I 6.2o(E.1R(6.21(),t.1c==3X?E(t).21():t.K!=W&&(!t.11||E.11(t,"2Y"))?t:[t]))},3t:G(a){I a?E.3m(a,6).K>0:P},7c:G(a){I 6.3t("."+a)},3i:G(b){9(b==W){9(6.K){H c=6[0];9(E.11(c,"24")){H e=c.4Z,a=[],Y=c.Y,2G=c.O=="24-2G";9(e<0)I S;L(H i=2G?e:0,33=2G?e+1:Y.K;i<33;i++){H d=Y[i];9(d.26){H b=E.V.1h&&!d.9V["1Q"].9L?d.2g:d.1Q;9(2G)I b;a.1a(b)}}I a}J I 6[0].1Q.1p(/\\r/g,"")}}J I 6.N(G(){9(b.1c==1B&&/4k|5j/.14(6.O))6.2Q=(E.2A(6.1Q,b)>=0||E.2A(6.2H,b)>=0);J 9(E.11(6,"24")){H a=b.1c==1B?b:[b];E("9h",6).N(G(){6.26=(E.2A(6.1Q,a)>=0||E.2A(6.2g,a)>=0)});9(!a.K)6.4Z=-1}J 6.1Q=b})},4o:G(a){I a==W?(6.K?6[0].3O:S):6.4n().3g(a)},6H:G(a){I 6.50(a).28()},6E:G(i){I 6.2J(i,i+1)},2J:G(){I 6.2o(1B.3A.2J.16(6,1q))},1X:G(b){I 6.2o(E.1X(6,G(a,i){I b.2O(a,i,a)}))},4O:G(){I 6.1f(6.4Y)},3z:G(f,d,g,e){H c=6.K>1,a;I 6.N(G(){9(!a){a=E.4D(f,6.3H);9(g<0)a.8U()}H b=6;9(d&&E.11(6,"1I")&&E.11(a[0],"4m"))b=6.4l("1K")[0]||6.58(U.5B("1K"));E.N(a,G(){H a=c?6.4R(Q):6;9(!5A(0,a))e.2O(b,a)})})}};G 5A(i,b){H a=E.11(b,"1J");9(a){9(b.3k)E.3G({1d:b.3k,3e:P,1V:"1J"});J E.5f(b.2g||b.6s||b.3O||"");9(b.12)b.12.3b(b)}J 9(b.1y==1)E("1J",b).N(5A);I a}E.1k=E.1b.1k=G(){H c=1q[0]||{},a=1,2c=1q.K,5e=P;9(c.1c==8o){5e=c;c=1q[1]||{}}9(2c==1){c=6;a=0}H b;L(;a<2c;a++)9((b=1q[a])!=S)L(H i 1i b){9(c==b[i])6r;9(5e&&1m b[i]==\'5i\'&&c[i])E.1k(c[i],b[i]);J 9(b[i]!=W)c[i]=b[i]}I c};H F="15"+(1u 3D()).3B(),6p=0,5c={};E.1k({8a:G(a){18.$=D;9(a)18.15=w;I E},1n:G(a){I!!a&&1m a!="1M"&&!a.11&&a.1c!=1B&&/G/i.14(a+"")},4a:G(a){I a.2V&&!a.1G||a.37&&a.3H&&!a.3H.1G},5f:G(a){a=E.36(a);9(a){9(18.6l)18.6l(a);J 9(E.V.1N)18.56(a,0);J 3w.2O(18,a)}},11:G(b,a){I b.11&&b.11.27()==a.27()},1L:{},M:G(c,d,b){c=c==18?5c:c;H a=c[F];9(!a)a=c[F]=++6p;9(d&&!E.1L[a])E.1L[a]={};9(b!=W)E.1L[a][d]=b;I d?E.1L[a][d]:a},30:G(c,b){c=c==18?5c:c;H a=c[F];9(b){9(E.1L[a]){2E E.1L[a][b];b="";L(b 1i E.1L[a])1T;9(!b)E.30(c)}}J{2a{2E c[F]}29(e){9(c.53)c.53(F)}2E E.1L[a]}},N:G(a,b,c){9(c){9(a.K==W)L(H i 1i a)b.16(a[i],c);J L(H i=0,48=a.K;i<48;i++)9(b.16(a[i],c)===P)1T}J{9(a.K==W)L(H i 1i a)b.2O(a[i],i,a[i]);J L(H i=0,48=a.K,3i=a[0];i<48&&b.2O(3i,i,3i)!==P;3i=a[++i]){}}I a},1e:G(c,b,d,e,a){9(E.1n(b))b=b.2O(c,[e]);H f=/z-?4I|7T-?7Q|1r|69|7P-?1H/i;I b&&b.1c==4W&&d=="3C"&&!f.14(a)?b+"2T":b},1o:{1f:G(b,c){E.N((c||"").2l(/\\s+/),G(i,a){9(!E.1o.3K(b.1o,a))b.1o+=(b.1o?" ":"")+a})},28:G(b,c){b.1o=c!=W?E.2W(b.1o.2l(/\\s+/),G(a){I!E.1o.3K(c,a)}).66(" "):""},3K:G(t,c){I E.2A(c,(t.1o||t).3s().2l(/\\s+/))>-1}},2k:G(e,o,f){L(H i 1i o){e.R["3r"+i]=e.R[i];e.R[i]=o[i]}f.16(e,[]);L(H i 1i o)e.R[i]=e.R["3r"+i]},17:G(e,p){9(p=="1H"||p=="2N"){H b={},42,41,d=["7J","7I","7G","7F"];E.N(d,G(){b["7C"+6]=0;b["7B"+6+"5Z"]=0});E.2k(e,b,G(){9(E(e).3t(\':3R\')){42=e.7A;41=e.7w}J{e=E(e.4R(Q)).1Y(":4k").5W("2Q").2D().17({4C:"1P",2X:"4F",19:"2Z",7o:"0",1S:"0"}).5R(e.12)[0];H a=E.17(e.12,"2X")||"3V";9(a=="3V")e.12.R.2X="7g";42=e.7e;41=e.7b;9(a=="3V")e.12.R.2X="3V";e.12.3b(e)}});I p=="1H"?42:41}I E.3C(e,p)},3C:G(h,j,i){H g,2w=[],2k=[];G 3n(a){9(!E.V.1N)I P;H b=U.3o.3Z(a,S);I!b||b.4y("3n")==""}9(j=="1r"&&E.V.1h){g=E.1x(h.R,"1r");I g==""?"1":g}9(j.1t(/4u/i))j=y;9(!i&&h.R[j])g=h.R[j];J 9(U.3o&&U.3o.3Z){9(j.1t(/4u/i))j="4u";j=j.1p(/([A-Z])/g,"-$1").2p();H d=U.3o.3Z(h,S);9(d&&!3n(h))g=d.4y(j);J{L(H a=h;a&&3n(a);a=a.12)2w.4w(a);L(a=0;a<2w.K;a++)9(3n(2w[a])){2k[a]=2w[a].R.19;2w[a].R.19="2Z"}g=j=="19"&&2k[2w.K-1]!=S?"2s":U.3o.3Z(h,S).4y(j)||"";L(a=0;a<2k.K;a++)9(2k[a]!=S)2w[a].R.19=2k[a]}9(j=="1r"&&g=="")g="1"}J 9(h.3Q){H f=j.1p(/\\-(\\w)/g,G(m,c){I c.27()});g=h.3Q[j]||h.3Q[f];9(!/^\\d+(2T)?$/i.14(g)&&/^\\d/.14(g)){H k=h.R.1S;H e=h.4v.1S;h.4v.1S=h.3Q.1S;h.R.1S=g||0;g=h.R.71+"2T";h.R.1S=k;h.4v.1S=e}}I g},4D:G(a,e){H r=[];e=e||U;E.N(a,G(i,d){9(!d)I;9(d.1c==4W)d=d.3s();9(1m d=="1M"){d=d.1p(/(<(\\w+)[^>]*?)\\/>/g,G(m,a,b){I b.1t(/^(70|6Z|6Y|9Q|4t|9N|9K|3a|9G|9E)$/i)?m:a+">"});H s=E.36(d).2p(),1s=e.5B("1s"),2x=[];H c=!s.1g("<9y")&&[1,"<24>",""]||!s.1g("<9w")&&[1,"<6T>",""]||s.1t(/^<(9u|1K|9t|9r|9p)/)&&[1,"<1I>",""]||!s.1g("<4m")&&[2,"<1I><1K>",""]||(!s.1g("<9m")||!s.1g("<9k"))&&[3,"<1I><1K><4m>",""]||!s.1g("<6Y")&&[2,"<1I><1K><6L>",""]||E.V.1h&&[1,"1s<1s>",""]||[0,"",""];1s.3O=c[1]+d+c[2];1W(c[0]--)1s=1s.5p;9(E.V.1h){9(!s.1g("<1I")&&s.1g("<1K")<0)2x=1s.1w&&1s.1w.3j;J 9(c[1]=="<1I>"&&s.1g("<1K")<0)2x=1s.3j;L(H n=2x.K-1;n>=0;--n)9(E.11(2x[n],"1K")&&!2x[n].3j.K)2x[n].12.3b(2x[n]);9(/^\\s/.14(d))1s.3d(e.6F(d.1t(/^\\s*/)[0]),1s.1w)}d=E.2h(1s.3j)}9(0===d.K&&(!E.11(d,"2Y")&&!E.11(d,"24")))I;9(d[0]==W||E.11(d,"2Y")||d.Y)r.1a(d);J r=E.1R(r,d)});I r},1x:G(c,d,a){H e=E.4a(c)?{}:E.5o;9(d=="26"&&E.V.1N)c.12.4Z;9(e[d]){9(a!=W)c[e[d]]=a;I c[e[d]]}J 9(E.V.1h&&d=="R")I E.1x(c.R,"9e",a);J 9(a==W&&E.V.1h&&E.11(c,"2Y")&&(d=="9d"||d=="9a"))I c.97(d).6x;J 9(c.37){9(a!=W){9(d=="O"&&E.11(c,"4t")&&c.12)6G"O 94 93\'t 92 91";c.90(d,a)}9(E.V.1h&&/6C|3k/.14(d)&&!E.4a(c))I c.4p(d,2);I c.4p(d)}J{9(d=="1r"&&E.V.1h){9(a!=W){c.69=1;c.1E=(c.1E||"").1p(/6O\\([^)]*\\)/,"")+(3I(a).3s()=="8S"?"":"6O(1r="+a*6A+")")}I c.1E?(3I(c.1E.1t(/1r=([^)]*)/)[1])/6A).3s():""}d=d.1p(/-([a-z])/8Q,G(z,b){I b.27()});9(a!=W)c[d]=a;I c[d]}},36:G(t){I(t||"").1p(/^\\s+|\\s+$/g,"")},2h:G(a){H r=[];9(1m a!="8P")L(H i=0,2c=a.K;i<2c;i++)r.1a(a[i]);J r=a.2J(0);I r},2A:G(b,a){L(H i=0,2c=a.K;i<2c;i++)9(a[i]==b)I i;I-1},1R:G(a,b){9(E.V.1h){L(H i=0;b[i];i++)9(b[i].1y!=8)a.1a(b[i])}J L(H i=0;b[i];i++)a.1a(b[i]);I a},4V:G(b){H r=[],2f={};2a{L(H i=0,6y=b.K;i<6y;i++){H a=E.M(b[i]);9(!2f[a]){2f[a]=Q;r.1a(b[i])}}}29(e){r=b}I r},2W:G(b,a,c){9(1m a=="1M")a=3w("P||G(a,i){I "+a+"}");H d=[];L(H i=0,4g=b.K;i<4g;i++)9(!c&&a(b[i],i)||c&&!a(b[i],i))d.1a(b[i]);I d},1X:G(c,b){9(1m b=="1M")b=3w("P||G(a){I "+b+"}");H d=[];L(H i=0,4g=c.K;i<4g;i++){H a=b(c[i],i);9(a!==S&&a!=W){9(a.1c!=1B)a=[a];d=d.8M(a)}}I d}});H v=8K.8I.2p();E.V={4s:(v.1t(/.+(?:8F|8E|8C|8B)[\\/: ]([\\d.]+)/)||[])[1],1N:/6w/.14(v),34:/34/.14(v),1h:/1h/.14(v)&&!/34/.14(v),35:/35/.14(v)&&!/(8z|6w)/.14(v)};H y=E.V.1h?"4h":"5h";E.1k({5g:!E.V.1h||U.8y=="8x",4h:E.V.1h?"4h":"5h",5o:{"L":"8w","8v":"1o","4u":y,5h:y,4h:y,3O:"3O",1o:"1o",1Q:"1Q",3c:"3c",2Q:"2Q",8u:"8t",26:"26",8s:"8r"}});E.N({1D:"a.12",8q:"15.4e(a,\'12\')",8p:"15.2I(a,2,\'2q\')",8n:"15.2I(a,2,\'4d\')",8l:"15.4e(a,\'2q\')",8k:"15.4e(a,\'4d\')",8j:"15.5d(a.12.1w,a)",8i:"15.5d(a.1w)",6q:"15.11(a,\'8h\')?a.8f||a.8e.U:15.2h(a.3j)"},G(i,n){E.1b[i]=G(a){H b=E.1X(6,n);9(a&&1m a=="1M")b=E.3m(a,b);I 6.2o(E.4V(b))}});E.N({5R:"3g",8c:"6j",3d:"6g",8b:"50",89:"6H"},G(i,n){E.1b[i]=G(){H a=1q;I 6.N(G(){L(H j=0,2c=a.K;j<2c;j++)E(a[j])[n](6)})}});E.N({5W:G(a){E.1x(6,a,"");6.53(a)},88:G(c){E.1o.1f(6,c)},87:G(c){E.1o.28(6,c)},86:G(c){E.1o[E.1o.3K(6,c)?"28":"1f"](6,c)},28:G(a){9(!a||E.1E(a,[6]).r.K){E.30(6);6.12.3b(6)}},4n:G(){E("*",6).N(G(){E.30(6)});1W(6.1w)6.3b(6.1w)}},G(i,n){E.1b[i]=G(){I 6.N(n,1q)}});E.N(["85","5Z"],G(i,a){H n=a.2p();E.1b[n]=G(h){I 6[0]==18?E.V.1N&&3y["84"+a]||E.5g&&38.33(U.2V["5a"+a],U.1G["5a"+a])||U.1G["5a"+a]:6[0]==U?38.33(U.1G["6n"+a],U.1G["6m"+a]):h==W?(6.K?E.17(6[0],n):S):6.17(n,h.1c==3X?h:h+"2T")}});H C=E.V.1N&&3x(E.V.4s)<83?"(?:[\\\\w*57-]|\\\\\\\\.)":"(?:[\\\\w\\82-\\81*57-]|\\\\\\\\.)",6k=1u 47("^>\\\\s*("+C+"+)"),6i=1u 47("^("+C+"+)(#)("+C+"+)"),6h=1u 47("^([#.]?)("+C+"*)");E.1k({55:{"":"m[2]==\'*\'||15.11(a,m[2])","#":"a.4p(\'22\')==m[2]",":":{80:"im[3]-0",2I:"m[3]-0==i",6E:"m[3]-0==i",3v:"i==0",3u:"i==r.K-1",6f:"i%2==0",6e:"i%2","3v-46":"a.12.4l(\'*\')[0]==a","3u-46":"15.2I(a.12.5p,1,\'4d\')==a","7X-46":"!15.2I(a.12.5p,2,\'4d\')",1D:"a.1w",4n:"!a.1w",7W:"(a.6s||a.7V||15(a).2g()||\'\').1g(m[3])>=0",3R:\'"1P"!=a.O&&15.17(a,"19")!="2s"&&15.17(a,"4C")!="1P"\',1P:\'"1P"==a.O||15.17(a,"19")=="2s"||15.17(a,"4C")=="1P"\',7U:"!a.3c",3c:"a.3c",2Q:"a.2Q",26:"a.26||15.1x(a,\'26\')",2g:"\'2g\'==a.O",4k:"\'4k\'==a.O",5j:"\'5j\'==a.O",54:"\'54\'==a.O",52:"\'52\'==a.O",51:"\'51\'==a.O",6d:"\'6d\'==a.O",6c:"\'6c\'==a.O",2r:\'"2r"==a.O||15.11(a,"2r")\',4t:"/4t|24|6b|2r/i.14(a.11)",3K:"15.1Y(m[3],a).K",7S:"/h\\\\d/i.14(a.11)",7R:"15.2W(15.32,G(1b){I a==1b.T;}).K"}},6a:[/^(\\[) *@?([\\w-]+) *([!*$^~=]*) *(\'?"?)(.*?)\\4 *\\]/,/^(:)([\\w-]+)\\("?\'?(.*?(\\(.*?\\))?[^(]*?)"?\'?\\)/,1u 47("^([:.#]*)("+C+"+)")],3m:G(a,c,b){H d,2b=[];1W(a&&a!=d){d=a;H f=E.1E(a,c,b);a=f.t.1p(/^\\s*,\\s*/,"");2b=b?c=f.r:E.1R(2b,f.r)}I 2b},1Y:G(t,o){9(1m t!="1M")I[t];9(o&&!o.1y)o=S;o=o||U;H d=[o],2f=[],3u;1W(t&&3u!=t){H r=[];3u=t;t=E.36(t);H l=P;H g=6k;H m=g.2S(t);9(m){H p=m[1].27();L(H i=0;d[i];i++)L(H c=d[i].1w;c;c=c.2q)9(c.1y==1&&(p=="*"||c.11.27()==p.27()))r.1a(c);d=r;t=t.1p(g,"");9(t.1g(" ")==0)6r;l=Q}J{g=/^([>+~])\\s*(\\w*)/i;9((m=g.2S(t))!=S){r=[];H p=m[2],1R={};m=m[1];L(H j=0,31=d.K;j<31;j++){H n=m=="~"||m=="+"?d[j].2q:d[j].1w;L(;n;n=n.2q)9(n.1y==1){H h=E.M(n);9(m=="~"&&1R[h])1T;9(!p||n.11.27()==p.27()){9(m=="~")1R[h]=Q;r.1a(n)}9(m=="+")1T}}d=r;t=E.36(t.1p(g,""));l=Q}}9(t&&!l){9(!t.1g(",")){9(o==d[0])d.44();2f=E.1R(2f,d);r=d=[o];t=" "+t.68(1,t.K)}J{H k=6i;H m=k.2S(t);9(m){m=[0,m[2],m[3],m[1]]}J{k=6h;m=k.2S(t)}m[2]=m[2].1p(/\\\\/g,"");H f=d[d.K-1];9(m[1]=="#"&&f&&f.3S&&!E.4a(f)){H q=f.3S(m[2]);9((E.V.1h||E.V.34)&&q&&1m q.22=="1M"&&q.22!=m[2])q=E(\'[@22="\'+m[2]+\'"]\',f)[0];d=r=q&&(!m[3]||E.11(q,m[3]))?[q]:[]}J{L(H i=0;d[i];i++){H a=m[1]=="#"&&m[3]?m[3]:m[1]!=""||m[0]==""?"*":m[2];9(a=="*"&&d[i].11.2p()=="5i")a="3a";r=E.1R(r,d[i].4l(a))}9(m[1]==".")r=E.4X(r,m[2]);9(m[1]=="#"){H e=[];L(H i=0;r[i];i++)9(r[i].4p("22")==m[2]){e=[r[i]];1T}r=e}d=r}t=t.1p(k,"")}}9(t){H b=E.1E(t,r);d=r=b.r;t=E.36(b.t)}}9(t)d=[];9(d&&o==d[0])d.44();2f=E.1R(2f,d);I 2f},4X:G(r,m,a){m=" "+m+" ";H c=[];L(H i=0;r[i];i++){H b=(" "+r[i].1o+" ").1g(m)>=0;9(!a&&b||a&&!b)c.1a(r[i])}I c},1E:G(t,r,h){H d;1W(t&&t!=d){d=t;H p=E.6a,m;L(H i=0;p[i];i++){m=p[i].2S(t);9(m){t=t.7O(m[0].K);m[2]=m[2].1p(/\\\\/g,"");1T}}9(!m)1T;9(m[1]==":"&&m[2]=="5V")r=E.1E(m[3],r,Q).r;J 9(m[1]==".")r=E.4X(r,m[2],h);J 9(m[1]=="["){H g=[],O=m[3];L(H i=0,31=r.K;i<31;i++){H a=r[i],z=a[E.5o[m[2]]||m[2]];9(z==S||/6C|3k|26/.14(m[2]))z=E.1x(a,m[2])||\'\';9((O==""&&!!z||O=="="&&z==m[5]||O=="!="&&z!=m[5]||O=="^="&&z&&!z.1g(m[5])||O=="$="&&z.68(z.K-m[5].K)==m[5]||(O=="*="||O=="~=")&&z.1g(m[5])>=0)^h)g.1a(a)}r=g}J 9(m[1]==":"&&m[2]=="2I-46"){H e={},g=[],14=/(\\d*)n\\+?(\\d*)/.2S(m[3]=="6f"&&"2n"||m[3]=="6e"&&"2n+1"||!/\\D/.14(m[3])&&"n+"+m[3]||m[3]),3v=(14[1]||1)-0,d=14[2]-0;L(H i=0,31=r.K;i<31;i++){H j=r[i],12=j.12,22=E.M(12);9(!e[22]){H c=1;L(H n=12.1w;n;n=n.2q)9(n.1y==1)n.4U=c++;e[22]=Q}H b=P;9(3v==1){9(d==0||j.4U==d)b=Q}J 9((j.4U+d)%3v==0)b=Q;9(b^h)g.1a(j)}r=g}J{H f=E.55[m[1]];9(1m f!="1M")f=E.55[m[1]][m[2]];f=3w("P||G(a,i){I "+f+"}");r=E.2W(r,f,h)}}I{r:r,t:t}},4e:G(b,c){H d=[];H a=b[c];1W(a&&a!=U){9(a.1y==1)d.1a(a);a=a[c]}I d},2I:G(a,e,c,b){e=e||1;H d=0;L(;a;a=a[c])9(a.1y==1&&++d==e)1T;I a},5d:G(n,a){H r=[];L(;n;n=n.2q){9(n.1y==1&&(!a||n!=a))r.1a(n)}I r}});E.1j={1f:G(g,e,c,h){9(E.V.1h&&g.4j!=W)g=18;9(!c.2u)c.2u=6.2u++;9(h!=W){H d=c;c=G(){I d.16(6,1q)};c.M=h;c.2u=d.2u}H i=e.2l(".");e=i[0];c.O=i[1];H b=E.M(g,"2P")||E.M(g,"2P",{});H f=E.M(g,"2t",G(){H a;9(1m E=="W"||E.1j.4T)I a;a=E.1j.2t.16(g,1q);I a});H j=b[e];9(!j){j=b[e]={};9(g.4S)g.4S(e,f,P);J g.7N("43"+e,f)}j[c.2u]=c;6.1Z[e]=Q},2u:1,1Z:{},28:G(d,c,b){H e=E.M(d,"2P"),2L,4I;9(1m c=="1M"){H a=c.2l(".");c=a[0]}9(e){9(c&&c.O){b=c.4Q;c=c.O}9(!c){L(c 1i e)6.28(d,c)}J 9(e[c]){9(b)2E e[c][b.2u];J L(b 1i e[c])9(!a[1]||e[c][b].O==a[1])2E e[c][b];L(2L 1i e[c])1T;9(!2L){9(d.4P)d.4P(c,E.M(d,"2t"),P);J d.7M("43"+c,E.M(d,"2t"));2L=S;2E e[c]}}L(2L 1i e)1T;9(!2L){E.30(d,"2P");E.30(d,"2t")}}},1F:G(d,b,e,c,f){b=E.2h(b||[]);9(!e){9(6.1Z[d])E("*").1f([18,U]).1F(d,b)}J{H a,2L,1b=E.1n(e[d]||S),4N=!b[0]||!b[0].2M;9(4N)b.4w(6.4M({O:d,2m:e}));b[0].O=d;9(E.1n(E.M(e,"2t")))a=E.M(e,"2t").16(e,b);9(!1b&&e["43"+d]&&e["43"+d].16(e,b)===P)a=P;9(4N)b.44();9(f&&f.16(e,b)===P)a=P;9(1b&&c!==P&&a!==P&&!(E.11(e,\'a\')&&d=="4L")){6.4T=Q;e[d]()}6.4T=P}I a},2t:G(d){H a;d=E.1j.4M(d||18.1j||{});H b=d.O.2l(".");d.O=b[0];H c=E.M(6,"2P")&&E.M(6,"2P")[d.O],3q=1B.3A.2J.2O(1q,1);3q.4w(d);L(H j 1i c){3q[0].4Q=c[j];3q[0].M=c[j].M;9(!b[1]||c[j].O==b[1]){H e=c[j].16(6,3q);9(a!==P)a=e;9(e===P){d.2M();d.3p()}}}9(E.V.1h)d.2m=d.2M=d.3p=d.4Q=d.M=S;I a},4M:G(c){H a=c;c=E.1k({},a);c.2M=G(){9(a.2M)a.2M();a.7L=P};c.3p=G(){9(a.3p)a.3p();a.7K=Q};9(!c.2m&&c.65)c.2m=c.65;9(E.V.1N&&c.2m.1y==3)c.2m=a.2m.12;9(!c.4K&&c.4J)c.4K=c.4J==c.2m?c.7H:c.4J;9(c.64==S&&c.63!=S){H e=U.2V,b=U.1G;c.64=c.63+(e&&e.2R||b.2R||0);c.7E=c.7D+(e&&e.2B||b.2B||0)}9(!c.3Y&&(c.61||c.60))c.3Y=c.61||c.60;9(!c.5F&&c.5D)c.5F=c.5D;9(!c.3Y&&c.2r)c.3Y=(c.2r&1?1:(c.2r&2?3:(c.2r&4?2:0)));I c}};E.1b.1k({3W:G(c,a,b){I c=="5Y"?6.2G(c,a,b):6.N(G(){E.1j.1f(6,c,b||a,b&&a)})},2G:G(d,b,c){I 6.N(G(){E.1j.1f(6,d,G(a){E(6).5X(a);I(c||b).16(6,1q)},c&&b)})},5X:G(a,b){I 6.N(G(){E.1j.28(6,a,b)})},1F:G(c,a,b){I 6.N(G(){E.1j.1F(c,a,6,Q,b)})},7x:G(c,a,b){9(6[0])I E.1j.1F(c,a,6[0],P,b)},25:G(){H a=1q;I 6.4L(G(e){6.4H=0==6.4H?1:0;e.2M();I a[6.4H].16(6,[e])||P})},7v:G(f,g){G 4G(e){H p=e.4K;1W(p&&p!=6)2a{p=p.12}29(e){p=6};9(p==6)I P;I(e.O=="4x"?f:g).16(6,[e])}I 6.4x(4G).5U(4G)},2d:G(f){5T();9(E.3T)f.16(U,[E]);J E.3l.1a(G(){I f.16(6,[E])});I 6}});E.1k({3T:P,3l:[],2d:G(){9(!E.3T){E.3T=Q;9(E.3l){E.N(E.3l,G(){6.16(U)});E.3l=S}9(E.V.35||E.V.34)U.4P("5S",E.2d,P);9(!18.7t.K)E(18).39(G(){E("#4E").28()})}}});E.N(("7s,7r,39,7q,6n,5Y,4L,7p,"+"7n,7m,7l,4x,5U,7k,24,"+"51,7j,7i,7h,3U").2l(","),G(i,o){E.1b[o]=G(f){I f?6.3W(o,f):6.1F(o)}});H x=P;G 5T(){9(x)I;x=Q;9(E.V.35||E.V.34)U.4S("5S",E.2d,P);J 9(E.V.1h){U.7f("<7d"+"7y 22=4E 7z=Q "+"3k=//:><\\/1J>");H a=U.3S("4E");9(a)a.62=G(){9(6.2C!="1l")I;E.2d()};a=S}J 9(E.V.1N)E.4B=4j(G(){9(U.2C=="5Q"||U.2C=="1l"){4A(E.4B);E.4B=S;E.2d()}},10);E.1j.1f(18,"39",E.2d)}E.1b.1k({39:G(g,d,c){9(E.1n(g))I 6.3W("39",g);H e=g.1g(" ");9(e>=0){H i=g.2J(e,g.K);g=g.2J(0,e)}c=c||G(){};H f="4z";9(d)9(E.1n(d)){c=d;d=S}J{d=E.3a(d);f="5P"}H h=6;E.3G({1d:g,O:f,M:d,1l:G(a,b){9(b=="1C"||b=="5O")h.4o(i?E("<1s/>").3g(a.40.1p(/<1J(.|\\s)*?\\/1J>/g,"")).1Y(i):a.40);56(G(){h.N(c,[a.40,b,a])},13)}});I 6},7a:G(){I E.3a(6.5M())},5M:G(){I 6.1X(G(){I E.11(6,"2Y")?E.2h(6.79):6}).1E(G(){I 6.2H&&!6.3c&&(6.2Q||/24|6b/i.14(6.11)||/2g|1P|52/i.14(6.O))}).1X(G(i,c){H b=E(6).3i();I b==S?S:b.1c==1B?E.1X(b,G(a,i){I{2H:c.2H,1Q:a}}):{2H:c.2H,1Q:b}}).21()}});E.N("5L,5K,6t,5J,5I,5H".2l(","),G(i,o){E.1b[o]=G(f){I 6.3W(o,f)}});H B=(1u 3D).3B();E.1k({21:G(d,b,a,c){9(E.1n(b)){a=b;b=S}I E.3G({O:"4z",1d:d,M:b,1C:a,1V:c})},78:G(b,a){I E.21(b,S,a,"1J")},77:G(c,b,a){I E.21(c,b,a,"45")},76:G(d,b,a,c){9(E.1n(b)){a=b;b={}}I E.3G({O:"5P",1d:d,M:b,1C:a,1V:c})},75:G(a){E.1k(E.59,a)},59:{1Z:Q,O:"4z",2z:0,5G:"74/x-73-2Y-72",6o:Q,3e:Q,M:S},49:{},3G:G(s){H f,2y=/=(\\?|%3F)/g,1v,M;s=E.1k(Q,s,E.1k(Q,{},E.59,s));9(s.M&&s.6o&&1m s.M!="1M")s.M=E.3a(s.M);9(s.1V=="4b"){9(s.O.2p()=="21"){9(!s.1d.1t(2y))s.1d+=(s.1d.1t(/\\?/)?"&":"?")+(s.4b||"5E")+"=?"}J 9(!s.M||!s.M.1t(2y))s.M=(s.M?s.M+"&":"")+(s.4b||"5E")+"=?";s.1V="45"}9(s.1V=="45"&&(s.M&&s.M.1t(2y)||s.1d.1t(2y))){f="4b"+B++;9(s.M)s.M=s.M.1p(2y,"="+f);s.1d=s.1d.1p(2y,"="+f);s.1V="1J";18[f]=G(a){M=a;1C();1l();18[f]=W;2a{2E 18[f]}29(e){}}}9(s.1V=="1J"&&s.1L==S)s.1L=P;9(s.1L===P&&s.O.2p()=="21")s.1d+=(s.1d.1t(/\\?/)?"&":"?")+"57="+(1u 3D()).3B();9(s.M&&s.O.2p()=="21"){s.1d+=(s.1d.1t(/\\?/)?"&":"?")+s.M;s.M=S}9(s.1Z&&!E.5b++)E.1j.1F("5L");9(!s.1d.1g("8g")&&s.1V=="1J"){H h=U.4l("9U")[0];H g=U.5B("1J");g.3k=s.1d;9(!f&&(s.1C||s.1l)){H j=P;g.9R=g.62=G(){9(!j&&(!6.2C||6.2C=="5Q"||6.2C=="1l")){j=Q;1C();1l();h.3b(g)}}}h.58(g);I}H k=P;H i=18.6X?1u 6X("9P.9O"):1u 6W();i.9M(s.O,s.1d,s.3e);9(s.M)i.5C("9J-9I",s.5G);9(s.5y)i.5C("9H-5x-9F",E.49[s.1d]||"9D, 9C 9B 9A 5v:5v:5v 9z");i.5C("X-9x-9v","6W");9(s.6U)s.6U(i);9(s.1Z)E.1j.1F("5H",[i,s]);H c=G(a){9(!k&&i&&(i.2C==4||a=="2z")){k=Q;9(d){4A(d);d=S}1v=a=="2z"&&"2z"||!E.6S(i)&&"3U"||s.5y&&E.6R(i,s.1d)&&"5O"||"1C";9(1v=="1C"){2a{M=E.6Q(i,s.1V)}29(e){1v="5k"}}9(1v=="1C"){H b;2a{b=i.5s("6P-5x")}29(e){}9(s.5y&&b)E.49[s.1d]=b;9(!f)1C()}J E.5r(s,i,1v);1l();9(s.3e)i=S}};9(s.3e){H d=4j(c,13);9(s.2z>0)56(G(){9(i){i.9q();9(!k)c("2z")}},s.2z)}2a{i.9o(s.M)}29(e){E.5r(s,i,S,e)}9(!s.3e)c();I i;G 1C(){9(s.1C)s.1C(M,1v);9(s.1Z)E.1j.1F("5I",[i,s])}G 1l(){9(s.1l)s.1l(i,1v);9(s.1Z)E.1j.1F("6t",[i,s]);9(s.1Z&&!--E.5b)E.1j.1F("5K")}},5r:G(s,a,b,e){9(s.3U)s.3U(a,b,e);9(s.1Z)E.1j.1F("5J",[a,s,e])},5b:0,6S:G(r){2a{I!r.1v&&9n.9l=="54:"||(r.1v>=6N&&r.1v<9j)||r.1v==6M||E.V.1N&&r.1v==W}29(e){}I P},6R:G(a,c){2a{H b=a.5s("6P-5x");I a.1v==6M||b==E.49[c]||E.V.1N&&a.1v==W}29(e){}I P},6Q:G(r,b){H c=r.5s("9i-O");H d=b=="6K"||!b&&c&&c.1g("6K")>=0;H a=d?r.9g:r.40;9(d&&a.2V.37=="5k")6G"5k";9(b=="1J")E.5f(a);9(b=="45")a=3w("("+a+")");I a},3a:G(a){H s=[];9(a.1c==1B||a.4c)E.N(a,G(){s.1a(3f(6.2H)+"="+3f(6.1Q))});J L(H j 1i a)9(a[j]&&a[j].1c==1B)E.N(a[j],G(){s.1a(3f(j)+"="+3f(6))});J s.1a(3f(j)+"="+3f(a[j]));I s.66("&").1p(/%20/g,"+")}});E.1b.1k({1A:G(b,a){I b?6.1U({1H:"1A",2N:"1A",1r:"1A"},b,a):6.1E(":1P").N(G(){6.R.19=6.3h?6.3h:"";9(E.17(6,"19")=="2s")6.R.19="2Z"}).2D()},1z:G(b,a){I b?6.1U({1H:"1z",2N:"1z",1r:"1z"},b,a):6.1E(":3R").N(G(){6.3h=6.3h||E.17(6,"19");9(6.3h=="2s")6.3h="2Z";6.R.19="2s"}).2D()},6J:E.1b.25,25:G(a,b){I E.1n(a)&&E.1n(b)?6.6J(a,b):a?6.1U({1H:"25",2N:"25",1r:"25"},a,b):6.N(G(){E(6)[E(6).3t(":1P")?"1A":"1z"]()})},9c:G(b,a){I 6.1U({1H:"1A"},b,a)},9b:G(b,a){I 6.1U({1H:"1z"},b,a)},99:G(b,a){I 6.1U({1H:"25"},b,a)},98:G(b,a){I 6.1U({1r:"1A"},b,a)},96:G(b,a){I 6.1U({1r:"1z"},b,a)},95:G(c,a,b){I 6.1U({1r:a},c,b)},1U:G(k,i,h,g){H j=E.6D(i,h,g);I 6[j.3L===P?"N":"3L"](G(){j=E.1k({},j);H f=E(6).3t(":1P"),3y=6;L(H p 1i k){9(k[p]=="1z"&&f||k[p]=="1A"&&!f)I E.1n(j.1l)&&j.1l.16(6);9(p=="1H"||p=="2N"){j.19=E.17(6,"19");j.2U=6.R.2U}}9(j.2U!=S)6.R.2U="1P";j.3M=E.1k({},k);E.N(k,G(c,a){H e=1u E.2j(3y,j,c);9(/25|1A|1z/.14(a))e[a=="25"?f?"1A":"1z":a](k);J{H b=a.3s().1t(/^([+-]=)?([\\d+-.]+)(.*)$/),1O=e.2b(Q)||0;9(b){H d=3I(b[2]),2i=b[3]||"2T";9(2i!="2T"){3y.R[c]=(d||1)+2i;1O=((d||1)/e.2b(Q))*1O;3y.R[c]=1O+2i}9(b[1])d=((b[1]=="-="?-1:1)*d)+1O;e.3N(1O,d,2i)}J e.3N(1O,a,"")}});I Q})},3L:G(a,b){9(E.1n(a)){b=a;a="2j"}9(!a||(1m a=="1M"&&!b))I A(6[0],a);I 6.N(G(){9(b.1c==1B)A(6,a,b);J{A(6,a).1a(b);9(A(6,a).K==1)b.16(6)}})},9f:G(){H a=E.32;I 6.N(G(){L(H i=0;i-8O?r:3I(E.17(6.T,6.1e))||0},3N:G(c,b,e){6.5u=(1u 3D()).3B();6.1O=c;6.2D=b;6.2i=e||6.2i||"2T";6.2v=6.1O;6.4q=6.4i=0;6.4r();H f=6;G t(){I f.2F()}t.T=6.T;E.32.1a(t);9(E.32.K==1){H d=4j(G(){H a=E.32;L(H i=0;i6.Y.2e+6.5u){6.2v=6.2D;6.4q=6.4i=1;6.4r();6.Y.3M[6.1e]=Q;H a=Q;L(H i 1i 6.Y.3M)9(6.Y.3M[i]!==Q)a=P;9(a){9(6.Y.19!=S){6.T.R.2U=6.Y.2U;6.T.R.19=6.Y.19;9(E.17(6.T,"19")=="2s")6.T.R.19="2Z"}9(6.Y.1z)6.T.R.19="2s";9(6.Y.1z||6.Y.1A)L(H p 1i 6.Y.3M)E.1x(6.T.R,p,6.Y.3P[p])}9(a&&E.1n(6.Y.1l))6.Y.1l.16(6.T);I P}J{H n=t-6.5u;6.4i=n/6.Y.2e;6.4q=E.3J[6.Y.3J||(E.3J.5q?"5q":"6B")](6.4i,n,0,1,6.Y.2e);6.2v=6.1O+((6.2D-6.1O)*6.4q);6.4r()}I Q}};E.2j.2F={2R:G(a){a.T.2R=a.2v},2B:G(a){a.T.2B=a.2v},1r:G(a){E.1x(a.T.R,"1r",a.2v)},6z:G(a){a.T.R[a.1e]=a.2v+a.2i}};E.1b.6m=G(){H c=0,3E=0,T=6[0],5t;9(T)8L(E.V){H b=E.17(T,"2X")=="4F",1D=T.12,23=T.23,2K=T.3H,4f=1N&&3x(4s)<8J;9(T.6V){5w=T.6V();1f(5w.1S+38.33(2K.2V.2R,2K.1G.2R),5w.3E+38.33(2K.2V.2B,2K.1G.2B));9(1h){H d=E("4o").17("8H");d=(d=="8G"||E.5g&&3x(4s)>=7)&&2||d;1f(-d,-d)}}J{1f(T.5l,T.5z);1W(23){1f(23.5l,23.5z);9(35&&/^t[d|h]$/i.14(1D.37)||!4f)d(23);9(4f&&!b&&E.17(23,"2X")=="4F")b=Q;23=23.23}1W(1D.37&&!/^1G|4o$/i.14(1D.37)){9(!/^8D|1I-9S.*$/i.14(E.17(1D,"19")))1f(-1D.2R,-1D.2B);9(35&&E.17(1D,"2U")!="3R")d(1D);1D=1D.12}9(4f&&b)1f(-2K.1G.5l,-2K.1G.5z)}5t={3E:3E,1S:c}}I 5t;G d(a){1f(E.17(a,"9T"),E.17(a,"8A"))}G 1f(l,t){c+=3x(l)||0;3E+=3x(t)||0}}})();',62,616,'||||||this|||if|||||||||||||||||||||||||||||||||function|var|return|else|length|for|data|each|type|false|true|style|null|elem|document|browser|undefined||options|||nodeName|parentNode||test|jQuery|apply|css|window|display|push|fn|constructor|url|prop|add|indexOf|msie|in|event|extend|complete|typeof|isFunction|className|replace|arguments|opacity|div|match|new|status|firstChild|attr|nodeType|hide|show|Array|success|parent|filter|trigger|body|height|table|script|tbody|cache|string|safari|start|hidden|value|merge|left|break|animate|dataType|while|map|find|global||get|id|offsetParent|select|toggle|selected|toUpperCase|remove|catch|try|cur|al|ready|duration|done|text|makeArray|unit|fx|swap|split|target||pushStack|toLowerCase|nextSibling|button|none|handle|guid|now|stack|tb|jsre|timeout|inArray|scrollTop|readyState|end|delete|step|one|name|nth|slice|doc|ret|preventDefault|width|call|events|checked|scrollLeft|exec|px|overflow|documentElement|grep|position|form|block|removeData|rl|timers|max|opera|mozilla|trim|tagName|Math|load|param|removeChild|disabled|insertBefore|async|encodeURIComponent|append|oldblock|val|childNodes|src|readyList|multiFilter|color|defaultView|stopPropagation|args|old|toString|is|last|first|eval|parseInt|self|domManip|prototype|getTime|curCSS|Date|top||ajax|ownerDocument|parseFloat|easing|has|queue|curAnim|custom|innerHTML|orig|currentStyle|visible|getElementById|isReady|error|static|bind|String|which|getComputedStyle|responseText|oWidth|oHeight|on|shift|json|child|RegExp|ol|lastModified|isXMLDoc|jsonp|jquery|previousSibling|dir|safari2|el|styleFloat|state|setInterval|radio|getElementsByTagName|tr|empty|html|getAttribute|pos|update|version|input|float|runtimeStyle|unshift|mouseover|getPropertyValue|GET|clearInterval|safariTimer|visibility|clean|__ie_init|absolute|handleHover|lastToggle|index|fromElement|relatedTarget|click|fix|evt|andSelf|removeEventListener|handler|cloneNode|addEventListener|triggered|nodeIndex|unique|Number|classFilter|prevObject|selectedIndex|after|submit|password|removeAttribute|file|expr|setTimeout|_|appendChild|ajaxSettings|client|active|win|sibling|deep|globalEval|boxModel|cssFloat|object|checkbox|parsererror|offsetLeft|wrapAll|dequeue|props|lastChild|swing|handleError|getResponseHeader|results|startTime|00|box|Modified|ifModified|offsetTop|evalScript|createElement|setRequestHeader|ctrlKey|callback|metaKey|contentType|ajaxSend|ajaxSuccess|ajaxError|ajaxStop|ajaxStart|serializeArray|init|notmodified|POST|loaded|appendTo|DOMContentLoaded|bindReady|mouseout|not|removeAttr|unbind|unload|Width|keyCode|charCode|onreadystatechange|clientX|pageX|srcElement|join|outerHTML|substr|zoom|parse|textarea|reset|image|odd|even|before|quickClass|quickID|prepend|quickChild|execScript|offset|scroll|processData|uuid|contents|continue|textContent|ajaxComplete|clone|setArray|webkit|nodeValue|fl|_default|100|linear|href|speed|eq|createTextNode|throw|replaceWith|splice|_toggle|xml|colgroup|304|200|alpha|Last|httpData|httpNotModified|httpSuccess|fieldset|beforeSend|getBoundingClientRect|XMLHttpRequest|ActiveXObject|col|br|abbr|pixelLeft|urlencoded|www|application|ajaxSetup|post|getJSON|getScript|elements|serialize|clientWidth|hasClass|scr|clientHeight|write|relative|keyup|keypress|keydown|change|mousemove|mouseup|mousedown|right|dblclick|resize|focus|blur|frames|instanceof|hover|offsetWidth|triggerHandler|ipt|defer|offsetHeight|border|padding|clientY|pageY|Left|Right|toElement|Bottom|Top|cancelBubble|returnValue|detachEvent|attachEvent|substring|line|weight|animated|header|font|enabled|innerText|contains|only|size|gt|lt|uFFFF|u0128|417|inner|Height|toggleClass|removeClass|addClass|replaceAll|noConflict|insertAfter|prependTo|wrap|contentWindow|contentDocument|http|iframe|children|siblings|prevAll|nextAll|wrapInner|prev|Boolean|next|parents|maxLength|maxlength|readOnly|readonly|class|htmlFor|CSS1Compat|compatMode|compatible|borderTopWidth|ie|ra|inline|it|rv|medium|borderWidth|userAgent|522|navigator|with|concat|1px|10000|array|ig|PI|NaN|400|reverse|fast|600|slow|Function|Object|setAttribute|changed|be|can|property|fadeTo|fadeOut|getAttributeNode|fadeIn|slideToggle|method|slideUp|slideDown|action|cssText|stop|responseXML|option|content|300|th|protocol|td|location|send|cap|abort|colg|cos|tfoot|thead|With|leg|Requested|opt|GMT|1970|Jan|01|Thu|area|Since|hr|If|Type|Content|meta|specified|open|link|XMLHTTP|Microsoft|img|onload|row|borderLeftWidth|head|attributes'.split('|'),0,{})) \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/js/jquery-speakers_coaches_consultants.js b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/js/jquery-speakers_coaches_consultants.js new file mode 100644 index 0000000..6cf741a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/js/jquery-speakers_coaches_consultants.js @@ -0,0 +1,30 @@ +jQuery(function($) { + var themap = $('
    ').css({ + 'width': '90%', + 'height': '500px' + }).insertBefore('ul.people'); + + var mapstraction = new Mapstraction('themap','google'); + mapstraction.addControls({ + zoom: 'large', + map_type: true + }); + + mapstraction.setCenterAndZoom( + new LatLonPoint(30, -0.126236), + 2 // Zoom level appropriate for Brighton city centre + ); + + $('.vcard').each(function() { + var hcard = $(this); + + var latitude = hcard.find('.geo .latitude').text(); + var longitude = hcard.find('.geo .longitude').text(); + + var marker = new Marker(new LatLonPoint(latitude, longitude)); + marker.setInfoBubble( + '
    ' + hcard.html() + '
    ' + ); + mapstraction.addMarker(marker); + }); +}); \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/js/jquery.heartbeat.js b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/js/jquery.heartbeat.js new file mode 100755 index 0000000..0011793 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/js/jquery.heartbeat.js @@ -0,0 +1,9 @@ +$(document).ready(function() { + $('#tests-results').load('../views/heartbeat.php?tests-results'); + $('#commits').load('../views/heartbeat.php', '', function() { + $(this).find(".sparkline").each(function() { + sparklinequery($(this)); + }); + }); + $('#last-commits').load('../views/heartbeat.php?last-commits'); +}); diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/js/jquery.sparkline.js b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/js/jquery.sparkline.js new file mode 100755 index 0000000..28c55d7 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/js/jquery.sparkline.js @@ -0,0 +1,76 @@ +/** + * Javascript Sparklines Library + * Based on a work by John Resig + * http://ejohn.org/projects/jspark/ + * + * This work is licensed under a Creative Commons Attribution 2.5 License + * More Info: http://creativecommons.org/licenses/by/2.5/ + * + * To use, place your data points within your HTML, like so: + * 10,8,20,5... + * + * in your CSS you might want to have the rule: + * .sparkline { display: none } + * so that non-compatible browsers don't see a huge pile of numbers. + * + */ + +function sparklinequery(o) { + var p = o.text().split(','); + o.empty(); + + var nw = "auto"; + var nh = "auto"; + + var f = 2; + var w = ( nw == "auto" || nw == 0 ? p.length * f : nw - 0 ); + var h = ( nh == "auto" || nh == 0 ? "1em" : nh ); + + var co = document.createElement("canvas"); + + if ( co.getContext ) { + o.css({ display: "inline" }); + } else { + return false; + } + + + co.style.height = h; + co.style.width = w; + co.width = w; + o.append( co ); + + var h = co.offsetHeight; + co.height = h; + + var min = 9999; + var max = -1; + + for ( var i = 0; i < p.length; i++ ) { + p[i] = p[i] - 0; + if ( p[i] < min ) min = p[i]; + if ( p[i] > max ) max = p[i]; + } + + if ( co.getContext ) { + var c = co.getContext("2d"); + c.strokeStyle = o.css("color"); + c.lineWidth = 1.0; + c.beginPath(); + + for ( var i = 0; i < p.length; i++ ) { + x = (w / p.length) * i; + if (max != min) { + y = h - (((p[i] - min) / (max - min)) * h) ; + } else { + y = 0; + } + y = 3 * i; + c.lineTo(x, y); + } + + c.stroke(); +alert(c); + o.css({ display:"inline" }); + } +} \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/simpletest.css b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/simpletest.css new file mode 100644 index 0000000..13fa388 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/simpletest.css @@ -0,0 +1,43 @@ +/* SimpleTest - Unit Testing for PHP */ +body { font-family : georgia, serif; } +img { border : 0; } +h1 { color : #009933; } +h3 { width : 100%; border-bottom : 1px solid #cccccc; } +blockquote { background: transparent url('images/quote.png') left top no-repeat; font-style : italic; padding-left : 40px; margin-left : 10px; } +td { vertical-align : top; } + +#logo { margin-bottom : 20px; } +#credits { margin-top : 40px; } +#actions { position : absolute; top : 20px; left : 40px; width : 335px; } +#actions div { width : 305px; } +#content { position : absolute; top : 20px; left : 400px; padding-bottom : 50px; width : 491px; } +#news { border : 2px solid #009933; padding : 20px; font-weight : bold; font-size : 18pt; width : 451px; } +#news a { color : #009933; } +#internal { background: url('images/simpletest-internal-middle.png') repeat-y; margin-top : 2em; width : 306px; } +#external { background: url('images/simpletest-external-middle.png') repeat-y; width : 306px; } +#internal > div , #external > div { padding : 0 0.5em 0 0.5em; } + +div.console { background-color: black; border: 4px ridge; border-color: gray; color : white; margin: 5px 40px 20px 20px; padding: 10px; } +div.demo { background-color: white; border: 4px ridge; border-color: gray; margin: 5px; margin-left: 20px; margin-right: 40px; padding: 10px; } +div.demo span.fail { color: red; } +div.demo span.pass { color: green; } +div.demo h1 { color: black; font-size: 12pt; font-weight: bold; text-align: left; } +pre { font-family: monospaced; border-left: 1px solid #999999; background-color: white; padding: 5px; margin-left: 5px; font-size : 10pt; } + +.screenshot { border : 1px solid #cccccc;} +.experimental { background-color : yellow; border : 4px dashed black; padding : 10px; } +.experimental pre { border-left : none; background-color : yellow; } +.bubble, .bubble h3 { font-size: 9pt; } +.people { margin : 0; list-style : none; padding : 0; } +.geo { color : #777777; } +.photo { margin-bottom : 2em; } +.photo span { display : block; } +.photo span.title { font-weight : bold; margin-top : 1em; } +.photo img { border : 1px solid #777777; padding : 1em; margin : 1em; } + +dd { font-size : 10.5pt; } +.done, .done span, .done + dd { color : #AAAAAA; text-decoration : line-through; } +.postponed, .postponed span, .postponed + dd { color : #AAAAAA; } + +dt div.fail { background-color : red; color : white; padding : 0.2em; } +dt div.pass { background-color : green; color : white; padding : 0.2em; } \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/views/heartbeat.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/views/heartbeat.php new file mode 100755 index 0000000..9b48f1c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/views/heartbeat.php @@ -0,0 +1,174 @@ +view($_SERVER['QUERY_STRING']); + +class SimpleHeartBeat { + public $log_directory; + public $tests_directory; + + function __construct($log_directory) { + $this->log_directory = $log_directory; + } + + function view($querystring) { + switch ($querystring) { + case "last-commits": + return $this->displayLastCommits(); + case "tests-results": + return $this->displayTestsResults(); + case "commits": + default: + return $this->displaySparkline("commits last week"); + } + } + + function displayTestsResults() { + foreach(new DirectoryIterator($this->log_directory) as $node) { + if (preg_match("/simpletest\..*\.log/", $node->getFilename())) { + $log = new SimpleHeartBeatLog($node); + if (!isset($html)) { + $html = "
    "; + } + $html .= "
    ".$log->details()."
    "; + $html .= "
    ".$log->info()."
    "; + } + } + if (isset($html)) { + $html .= "
    "; + } else { + $html = $this->dataUnavailable(); + } + + return $html; + } + + function displayLastCommits($number=5) { + $entries = array(); + $xml = simplexml_load_file($this->log_directory."/svn.xml"); + foreach ($xml->logentry as $logentry) { + $dt = $logentry->msg; + $dd = $logentry['revision']." - ".$logentry->author. " - ".$logentry->date; + $entries[] = array('dt' => $dt, 'dd' => $dd); + } + + if (count($entries) > 0) { + $html = "
    "; + krsort($entries); + $i = 0; + foreach($entries as $entry) { + if ($i < $number) { + $i++; + $html .= "
    ".$entry['dt']."
    "; + $html .= "
    ".$entry['dd']."
    "; + } else { + break; + } + } + + $html .= "
    "; + } else { + $html = $this->dataUnavailable(); + } + + return $html; + } + + function displaySparkline($name="commits last week") { + $method = $this->findMethod($name); + $data = $this->$method(); + + if (is_array($data)) { + $html = "
    "; + $html .= ""; + $html .= join(",", $data); + $html .= ""; + $html .= " ".array_pop($data)." ".$name; + $html .= "
    "; + } else { + $html = $this->dataUnavailable(); + } + + return $html; + } + + function dataUnavailable() { + return "
    data unavailable
    "; + } + function findMethod($name) { + switch ($name) { + default: + return "commitsPerWeek"; + } + } + + function commitsPerWeek() { + $data = array(); + $xml = simplexml_load_file($this->log_directory."/svn.xml"); + foreach ($xml->logentry as $logentry) { + $timestamp = strtotime($logentry->date); + $weekly = strtotime("last monday", $timestamp); + if (!isset($data[$weekly])) { + $data[$weekly] = 0; + } + $data[$weekly]++; + } + + $data = $this->normalizeData($data, "week"); + + return $data; + } + + function normalizeData($data, $period="week") { + $min = min(array_keys($data)); + $max = max(array_keys($data)); + + $normalized = array(); + $current = $min; + while ($current <= $max) { + $normalized[$current] = 0; + $current = strtotime("+1 ".$period, $current); + } + + foreach ($data as $timestamp => $value) { + $normalized[$timestamp] = $value; + } + + return $normalized; + } +} + +class SimpleHeartBeatLog { + public $node; + public $content = ""; + + function __construct($node) { + $this->node = $node; + $this->content = file_get_contents($this->node->getPathname()); + } + + function result() { + if (preg_match("/OK/", $this->content)) { + return "pass"; + } else { + return "fail"; + } + } + + function info() { + return nl2br($this->content); + } + + function details() { + $details = substr($this->node->getFilename(), 11); + $details = $this->result(). " with ".substr($details, 0, -4); + $details .= " - ".date("c", $this->node->getCTime()); + + return "
    result()."\">".$details."
    "; + } +} + +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/views/photos_stream.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/views/photos_stream.php new file mode 100644 index 0000000..c85bcf5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/simpletest.org/views/photos_stream.php @@ -0,0 +1,37 @@ +showLastPhotos(); + +class SimpleFlickrStreamXMLElement extends SimpleXMLElement { + function showLastPhotos() { + $html = ""; + foreach ($this->entry as $entry) { + $html .= "
    "; + $html .= "".$entry->title.""; + $html .= "".$this->extractFirstImage($entry).""; + $html .= "".$this->extractAuthor($entry).""; + $html .= "
    "; + } + return $html; + } + + function extractAuthor($entry) { + return "author->uri."\">".$entry->author->name.""; + } + + function extractFirstImage($entry) { + $content = $entry->content; + $content = substr($content, strpos($content, "") + 3); + + return "link[0]['href']."\">".$image.""; + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/about.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/about.xml new file mode 100644 index 0000000..294a197 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/about.xml @@ -0,0 +1,112 @@ + + + + About Last Craft + +
    +

    + + At the moment this site is maintained by myself, + Marcus Baker + and I am an Object Oriented web developer + and father of two. + I am currently a freelancer working as a senior developer for + Wordtracker + and as a consultant to various smaller companies. + Wordtracker is an internet statistics company with a huge database + of search engine keyword popularity. + The company software development is eXtreme Programming based. +

    +

    + My wife Aviva Racher is a + microbiologist and Mother of the same two. +

    +
    +
    +

    + Read the interview + Who's really being protected? on the + O'Reilly network if you have an interest in software patents. +

    +
    +
    +

    + Besides working for Wordtracker, I also act as an independent + consultant. + Usually in relation to bringing agile development practices into an + organisation. + Clients include Waterscape and Amnesty International. +

    +

    + I did a quick site for a graphic designer and illustrator friend of mine, + Dylan Beck. + He has a very unique style. +

    +

    + The Ambassadors of Om are a Jazz-Funk + fusion (?) band with an internet presence going back over five + years. Site includes samples, videos and links. The band is + managed by guitarist and lead singer Paul Grimes who is a friend of ours. +

    +

    + + Mark Eichner is Aviva's uncle and run's the + + Leisure Pursuits + off-road driving school. Lots of wheels and mud! +

    +
    +
    + + + Last Craft people + Myself and friends and relations. + + + Issues that I care about. + + + Associated companies + and people. + + + + + + Extreme Tuesday + Club is an informal meeting in London to promote + Extreme programming. + + + PHPLondon + is a user group that meets the first thursday of every month. + + + Wordtracker + crunches internet search data for web marketing. + + + + + software development, + computer programmer, + php programming, + programming php, + software development company, + software development uk, + php tutorial, + bespoke software development uk, + corporate web development, + architecture, + freelancer, + php resources, + wordtracker, + web marketing, + serach engines, + web positioning, + internet marketing + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/authentication_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/authentication_documentation.xml new file mode 100644 index 0000000..7fc01fc --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/authentication_documentation.xml @@ -0,0 +1,337 @@ + + + + SimpleTest documentation for testing log-in and authentication + + +

    + One of the trickiest, and yet most important, areas + of testing web sites is the security. + Testing these schemes is one of the core goals of + the SimpleTest web tester. +

    +
    +
    +

    + If you fetch a page protected by basic authentication then + rather than receiving content, you will instead get a 401 + header. + We can illustrate this with this test... + + function test401Header() { + $this->get('http://www.lastcraft.com/protected/'); + $this->showHeaders(); + } +} +]]> + This allows us to see the challenge header... +

    +

    File test

    +
    +
    1/1 test cases complete. + 0 passes, 0 fails and 0 exceptions.
    +
    + We are trying to get away from visual inspection though, and so SimpleTest + allows to make automated assertions against the challenge. + Here is a thorough test of our header... +get('http://www.lastcraft.com/protected/'); + $this->assertAuthentication('Basic'); + $this->assertResponse(401); + $this->assertRealm('SimpleTest basic authentication'); + } +} +]]> + Any one of these tests would normally do on it's own depending + on the amount of detail you want to see. +

    +

    + One theme that runs through SimpleTest is the ability to use + SimpleExpectation objects wherever a simple + match is not enough. + If you want only an approximate match to the realm for + example, you can do this... +get('http://www.lastcraft.com/protected/'); + $this->assertRealm(new PatternExpectation('/simpletest/i')); + } +} +]]> + This type of test, testing HTTP responses, is not typical. +

    +

    + Most of the time we are not interested in testing the + authentication itself, but want to get past it to test + the pages underneath. + As soon as the challenge has been issued we can reply with + an authentication response... +get('http://www.lastcraft.com/protected/'); + $this->authenticate('Me', 'Secret'); + $this->assertTitle(...); + } +} +]]> + The username and password will now be sent with every + subsequent request to that directory and subdirectories. + You will have to authenticate again if you step outside + the authenticated directory, but SimpleTest is smart enough + to merge subdirectories into a common realm. +

    +

    + If you want, you can shortcut this step further by encoding + the log in details straight into the URL... +get('http://Me:Secret@www.lastcraft.com/protected/'); + $this->assertTitle(...); + } +} +]]> + If your username or password has special characters, then you + will have to URL encode them or the request will not be parsed + correctly. + I'm afraid we leave this up to you. +

    +

    + A problem with encoding the login details directly in the URL is + the authentication header will not be sent on subsequent requests. + If you navigate with relative URLs though, the authentication + information will be preserved along with the domain name. +

    +

    + Normally though, you use the authenticate() call. + SimpleTest will then remember your login information on each request. +

    +

    + Only testing with basic authentication is currently supported, and + this is only really secure in tandem with HTTPS connections. + This is usually good enough to protect test server from prying eyes, + however. + Digest authentication and NTLM authentication may be added + in the future if enough people request this feature. +

    +
    +
    +

    + Basic authentication doesn't give enough control over the + user interface for web developers. + More likely this functionality will be coded directly into + the web architecture using cookies with complicated timeouts. + We need to be able to test this too. +

    +

    + Starting with a simple log-in form... +

    
    +    Username:
    +    
    + Password: +
    + + +]]>
    + Which looks like... +

    +

    +

    + Username: +
    + Password: +
    + +
    +

    +

    + Let's suppose that in fetching this page a cookie has been + set with a session ID. + We are not going to fill the form in yet, just test that + we are tracking the user. + Here is the test... +get('http://www.my-site.com/login.php'); + $this->assertCookie('SID'); + } +} +]]> + All we are doing is confirming that the cookie is set. + As the value is likely to be rather cryptic it's not + really worth testing this with... +get('http://www.my-site.com/login.php'); + $this->assertCookie('SID', new PatternExpectation('/[a-f0-9]{32}/i')); + } +} +]]> + If you are using PHP to handle sessions for you then + this test is even more useless, as we are just testing PHP itself. +

    +

    + The simplest test of logging in is to visually inspect the + next page to see if you are really logged in. + Just test the next page with WebTestCase::assertText(). +

    +

    + The test is similar to any other form test, + but we might want to confirm that we still have the same + cookie after log-in as before we entered. + We wouldn't want to lose track of this after all. + Here is a possible test for this... +get('http://www.my-site.com/login.php'); + $session = $this->getCookie('SID'); + $this->setField('u', 'Me'); + $this->setField('p', 'Secret'); + $this->click('Log in'); + $this->assertText('Welcome Me'); + $this->assertCookie('SID', $session); + } +} +]]> + This confirms that the session identifier is maintained + afer log-in and we haven't accidently reset it. +

    +

    + We could even attempt to hack our own system by setting + arbitrary cookies to gain access... +get('http://www.my-site.com/login.php'); + $this->setCookie('SID', 'Some other session'); + $this->get('http://www.my-site.com/restricted.php'); + $this->assertText('Access denied'); + } +} +]]> + Is your site protected from this attack? +

    +
    +
    +

    + If you are testing an authentication system a critical piece + of behaviour is what happens when a user logs back in. + We would like to simulate closing and reopening a browser... +get('http://www.my-site.com/login.php'); + $this->setField('u', 'Me'); + $this->setField('p', 'Secret'); + $this->click('Log in'); + $this->assertText('Welcome Me'); + + $this->restart(); + $this->get('http://www.my-site.com/restricted.php'); + $this->assertText('Access denied'); + } +} +]]> + The WebTestCase::restart() method will + preserve cookies that have unexpired timeouts, but throw away + those that are temporary or expired. + You can optionally specify the time and date that the restart + happened. +

    +

    + Expiring cookies can be a problem. + After all, if you have a cookie that expires after an hour, + you don't want to stall the test for an hour while waiting + for the cookie to pass it's timeout. +

    +

    + To push the cookies over the hour limit you can age them + before you restart the session... +get('http://www.my-site.com/login.php'); + $this->setField('u', 'Me'); + $this->setField('p', 'Secret'); + $this->click('Log in'); + $this->assertText('Welcome Me'); + + $this->ageCookies(3600); + $this->restart(); + $this->get('http://www.my-site.com/restricted.php'); + $this->assertText('Access denied'); + } +} +]]> + After the restart it will appear that cookies are an + hour older, and any that pass their expiry will have + disappeared. +

    +
    +
    + + + Getting through Basic HTTP authentication + + + Testing cookie based authentication + + + Managing browser sessions and timeouts + + + + + SimpleTest project page on SourceForge. + + + SimpleTest download page on LastCraft. + + + The developer's API for SimpleTest + gives full detail on the classes and assertions available. + + + + + software development, + php programming for clients, + customer focused php, + software development tools, + acceptance testing framework, + free php scripts, + log in boxes, + unit testing authentication systems, + php resources, + HTMLUnit, + JWebUnit, + php testing, + unit test resource, + web testing, + HTTP authentication, + testing log in, + authentication testing, + security tests + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/books_website.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/books_website.xml new file mode 100644 index 0000000..0877eff --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/books_website.xml @@ -0,0 +1,73 @@ + + + + Books in and around SimpleTest + +
    +

    + From time to time a book is recommanded on the mailing-list, you can find it here + with the original comments ! +

    +

    + Domain-Driven Design: Tackling Complexity in the Heart of Software +

    +
    + TDD is a lot about the mechanics of coding, but it says you are done + when there is no duplication. DDD considers the code in constant churn, + always renaming as the domain becomes clear and also feeding terms back + into the domain as the code evolves.
    +
    + Anyways, it's a great book :) [source] +
    +
    +
    +

    + Two of SimpleTest contributors have written books about PHP. Both have a lot + of examples with the SimpleTest Unit testing framework. +

    +

    + PHP|Architect's Guide to PHP Design Patterns +
    + PHP|Architect's Guide to PHP Design Patterns
    + by Jason E. Sweat
    + (get it from : PHP|Architect | + Amazon ) +

    +

    + The PHP Anthology: Object Oriented PHP Solutions +
    + The PHP Anthology: Object Oriented PHP Solutions
    + by Harry Fuecks
    + (get it from : SitePoint | + Amazon | + review on Slashdot ) +

    +
    +
    +

    + One way to help the team of contributors is to buy books from this page through + Amazon (with the simpletest-21 tag). We all love reading stuff that's + both interesting and challenging. +

    +
    +
    + + + Latest recommandation + + + Books by contributors + + + Buying books + + + + + + + + + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/boundary_classes_tutorial.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/boundary_classes_tutorial.xml new file mode 100644 index 0000000..eb2acbf --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/boundary_classes_tutorial.xml @@ -0,0 +1,405 @@ + + + + + PHP unit testing tutorial - Organising unit tests and "setup tests" + + + +

    + You are probably thinking that we have well and truly exhausted + the Log class by now and that there is + really nothing more to add. + Things are never that simple with object oriented programming, though. + You think you understand a problem and then something comes a long + that challenges your perspective and leads to an even deeper appreciation. + I thought I understood the logging class and that only the first page + of the tutorial would use it. + After that I would move on to something more complicated. + No one is more surprised than me that I still haven't got to + the bottom of it. + In fact I think I have only just figured out what a logger does. +

    +
    +
    +

    + Supposing that we do not want to log to a file at all. + Perhaps we want to print to the screen, write the messages to a + socket or send them to the Unix(tm) syslog daemon for + dispatching across the network. + How do we incorporate this variation? +

    +

    + Simplest is to subclass the Log + overriding the message() method + with new versions. + This will work in the short term, but there is actually something + subtle, but deeply wrong with this. + Suppose we do subclass and have loggers that write to files, + the screen and the network. + Three classes , but that is OK. + Now suppose that we want a new logging class that adds message + filtering by priority, letting only certain types of messages + through according to some configuration file. +

    +

    + We are stuck. If we subclass again, we have to do it for all + three classes, giving us six classes. + The amount of duplication is horrible. +

    +

    + So are you now wishing that PHP had multiple inheritence? + Well, here that would reduce the short term workload, but + complicate what should be a very simple class. + Multiple inheritance, even when supported, should be used + extremely carefully as all sorts of complicated entanglements + can result. + Treat it as a loaded gun. + In fact, our sudden need for it is telling us something else - perhaps + that we have gone wrong on the conceptual level. +

    +

    + What does a logger do? + Does it send messages to a file? + Does it send messages to a network? + Does it send messages to a screen? + Nope. + It just sends messages (full stop). + The target of those messages can be chosen when setting + up the log, but after that the + logger should be left to combine and format the message + elements as that is its real job. + We restricted ourselves by assuming that target was a filename. +

    +
    +
    +

    + The solution to this plight is a real classic. + First we encapsulate the variation in a class as this will + add a level of indirection. + Instead of passing in the file name as a string we + will pass the "thing that we will write to" + which we will call a Writer. + Back to the tests... + +require_once('../classes/writer.php'); +Mock::generate('Clock'); + +class TestOfLogging extends UnitTestCase { + function setUp() { + @unlink('../temp/test.log'); + } + function tearDown() { + @unlink('../temp/test.log'); + } + + function testCreatingNewFile() { + $log = new Log(new FileWriter('../temp/test.log')); + $this->assertFalse(file_exists('../temp/test.log'), 'Created before message'); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists('../temp/test.log'), 'File created'); + } + + function testAppendingToFile() { + $log = new Log(new FileWriter('../temp/test.log')); + $log->message('Test line 1'); + $this->assertWantedPattern( + '/Test line 1/', + $this->getFileLine('../temp/test.log', 0)); + $log->message('Test line 2'); + $this->assertWantedPattern( + '/Test line 2/', + $this->getFileLine('../temp/test.log', 1)); + } + + function testTimestamps() { + $clock = new MockClock($this); + $clock->setReturnValue('now', 'Timestamp'); + $log = new Log(new FileWriter('../temp/test.log')); + $log->message('Test line', $clock); + $this->assertWantedPattern( + '/Timestamp/', + $this->getFileLine('../temp/test.log', 0), + 'Found timestamp'); + } + + function getFileLine($filename, $index) { + $messages = file($filename); + return $messages[$index]; + } +} +?> +]]> + I am going to do this one step at a time so as not to get + confused. + I have replaced the file names with an imaginary + FileWriter class from + an imaginary file classes/writer.php. + This will cause the tests to crash as we have not written + the writer yet. + Should we do that now? +

    +

    + We could, but we don't have to. + We do need to create the interface, though, or we won't be + able to mock it. + This makes classes/writer.php looks like... + +]]> + We need to modify the Log class + as well... + +require_once('../classes/writer.php'); + +class Log { + private $writer; + + function Log($writer) { + $this->writer = $writer; + } + + function message($message, $clock = false) { + if (! is_object($clock)) { + $clock = new Clock(); + } + $this->writer->write("[" . $clock->now() . "] $message"); + } +} +?> +]]> + There is not much that hasn't changed in our now even smaller + class. + The tests run, but fail at this point unless we add code to + the writer. + What do we do now? +

    +

    + We could start writing tests and code the + FileWriter class alongside, but + while we were doing this our Log + tests would be failing and disturbing our focus. + In fact we do not have to. +

    +

    + Part of our plan is to free the logging class from the file + system and there is a way to do this. + First we add a tests/writer_test.php so that + we have somewhere to place our test code from log_test.php + that we are going to shuffle around. + I won't yet add it to the all_tests.php file, + as it is the logging aspect we are tackling right now. +

    +

    + Now I have done that (honest!) we remove any + tests from log_test.php that are not strictly logging + related and move them to writer_test.php for later. + We will also mock the writer so that it does not write + out to real files... + +Mock::generate('FileWriter'); + +class TestOfLogging extends UnitTestCase { + + function testWriting() { + $clock = new MockClock(); + $clock->setReturnValue('now', 'Timestamp'); + $writer = new MockFileWriter($this); + $writer->expectOnce('write', array('[Timestamp] Test line')); + $log = new Log(\$writer); + $log->message('Test line', $clock); + } +} +?> +]]> + Yes that really is the whole test case and it really is that short. + A lot has happened here... +

      +
    1. + The requirement to create the file only when needed has + moved to the FileWriter. +
    2. +
    3. + As we are dealing with mocks, no files are actually + created and so I moved the + setUp() and + tearDown() off into the + FileWriter tests. +
    4. +
    5. + The test now consists of sending a sample message and + testing the format. +
    6. +
    + Hang on a minute, where are the assertions? +

    +

    + The mock objects do much more than simply behave like other + objects, they also run tests. + The expectOnce() + call told the mock to expect a single parameter of + the string "[Timestamp] Test line" when + the mock write() method is + called. + When that method is called the expected parameters are + compared with this and either a pass or a fail is sent + to the unit test as a result. +

    +

    + The other expectation is that write() + will be called and called only once. + If the method is not called before the end of the test, + a failure is generated. + We can see all this in action by running the tests... +

    +

    All tests

    + Pass: log_test.php->Log class test->testwriting->Arguments for [write] were [String: [Timestamp] Test line]
    + Pass: log_test.php->Log class test->testwriting->Expected call count for [write] was [1], but got [1]
    + + Pass: clock_test.php->Clock class test->testclockadvance->Advancement
    + Pass: clock_test.php->Clock class test->testclocktellstime->Now is the right time
    +
    3/3 test cases complete. + 4 passes and 0 fails.
    +
    +

    +
    +
    +

    + Something very nice has happened to the logger besides merely + getting smaller. +

    +

    + The only things it depends on now are classes that we have written + ourselves, and + in the tests these are mocked and so there are no dependencies + on anything other than our own PHP code. + No writing to files or waiting for clocks to tick over. + This means that the log_test.php test case will + run as fast as the processor will carry it. + By contrast the FileWriter + and Clock classes are very + close to the system. + This makes them harder to test as real data must be moved + around and painstakingly confirmed, often by ad hoc tricks. +

    +

    + Our last refactoring has helped a bit. + The hard to test classes on the boundary of the application + and the system are now smaller, as the I/O code has + been further separated from the domain logic. + They are direct mappings to PHP operations: + FileWriter::write() maps + to PHP fwrite() with the + file opened for appending and + Clock::now() maps to + PHP time(). + This makes debugging easier. + It also means that these classes will change less often. +

    +

    + If they don't change a lot then there is no reason to + keep running the tests for them. + This means that tests for the boundary classes can be moved + off into there own test suite leaving the other unit tests + to run at full speed. + In fact this is what I tend to do and the test cases + in SimpleTest itself are + divided this way. +

    +

    + That may not sound like much with one unit test and two + boundary tests, but typical applications can have + twenty boundary classes and two hundred application + classes. + To keep the unit tests running at full speed you will want + to keep them separate. +

    +

    + Another benefit of this separation is that you are left + with the smaller test suite for all your dependencies. + Suppose you want to set up a server for your application + and you want to make sure that all underlying components, + directories and configuration are correct. + You no longer need to run the entire test suite to find out, + just run the tests for your boundary classes. + For this reason, this test suite is often called the + "setup tests". + Hand them to your system administrator, and they can set up + your server for you. +

    +

    + Besides, separating off decisions of which system components + to use is good development. + Being able to change the underlying dependencies easily is a + good thing. + Perhaps all this mocking is + improving our design? +

    +
    +
    + + + Handling variation in our logger. + + + Abstracting further with a mock Writer class. + + + Separating outsetup tests cleans things up. + + + + + This tutorial follows the Mock objects introduction. + + + Next is test driven design. + + + You will need the SimpleTest testing framework + to try these examples. + + + + + software development, + php programming, + programming php, + software development tools, + php tutorial, + free php scripts, + organizing unit tests, + testing tips, + development tricks, + software architecture for testing, + php example code, + mock objects, + junit port, + test case examples, + php testing, + unit test tool, + php test suite + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/browser_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/browser_documentation.xml new file mode 100644 index 0000000..fb1ba68 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/browser_documentation.xml @@ -0,0 +1,281 @@ + + + + SimpleTest documentation for the scriptable web browser component + + +

    + SimpleTest's web browser component can be used not just + outside of the WebTestCase class, but also + independently of the SimpleTest framework itself. +

    +
    +
    +

    + You can use the web browser in PHP scripts to confirm + services are up and running, or to extract information + from them at a regular basis. + For example, here is a small script to extract the current number of + open PHP 5 bugs from the PHP web site... +get('http://php.net/'); +$browser->click('reporting bugs'); +$browser->click('statistics'); +$page = $browser->click('PHP 5 bugs only'); +preg_match('/status=Open.*?by=Any.*?(\d+)<\/a>/', $page, $matches); +print $matches[1]; +?> +]]> + There are simpler methods to do this particular example in PHP + of course. + For example you can just use the PHP file() + command against what here is a pretty fixed page. + However, using the web browser for scripts allows authentication, + correct handling of cookies, automatic loading of frames, redirects, + form submission and the ability to examine the page headers. +

    +

    + Methods such as periodic scraping are fragile against a site that is constantly + evolving and you would want a more direct way of accessing + data in a permanent set up, but for simple tasks this can provide + a very rapid solution. +

    +

    + All of the navigation methods used in the + WebTestCase + are present in the SimpleBrowser class, but + the assertions are replaced with simpler accessors. + Here is a full list of the page navigation methods... + + + + + + + + + + + + + + + + + + + + + + +
    addHeader($header)Adds a header to every fetch
    useProxy($proxy, $username, $password)Use this proxy from now on
    head($url, $parameters)Perform a HEAD request
    get($url, $parameters)Fetch a page with GET
    post($url, $parameters)Fetch a page with POST
    click($label)Clicks visible link or button text
    clickLink($label)Follows a link by label
    clickLinkById($id)Follows a link by attribute
    getUrl()Current URL of page or frame
    getTitle()Page title
    getContent()Raw page or frame
    getContentAsText()HTML removed except for alt text
    retry()Repeat the last request
    back()Use the browser back button
    forward()Use the browser forward button
    authenticate($username, $password)Retry page or frame after a 401 response
    restart($date)Restarts the browser for a new session
    ageCookies($interval)Ages the cookies by the specified time
    setCookie($name, $value)Sets an additional cookie
    getCookieValue($host, $path, $name)Reads the most specific cookie
    getCurrentCookieValue($name)Reads cookie for the current context
    + The methods SimpleBrowser::useProxy() and + SimpleBrowser::addHeader() are special. + Once called they continue to apply to all subsequent fetches. +

    +

    + Navigating forms is similar to the + WebTestCase form navigation... + + + + + + + + + + + + + + +
    setField($label, $value)Sets all form fields with that label or name
    setFieldByName($name, $value)Sets all form fields with that name
    setFieldById($id, $value)Sets all form fields with that id
    getField($label)Accessor for a form element value by label tag and then name
    getFieldByName($name)Accessor for a form element value using name attribute
    getFieldById($id)Accessor for a form element value
    clickSubmit($label)Submits form by button label
    clickSubmitByName($name)Submits form by button attribute
    clickSubmitById($id)Submits form by button attribute
    clickImage($label, $x, $y)Clicks an input tag of type image by title or alt text
    clickImageByName($name, $x, $y)Clicks an input tag of type image by name
    clickImageById($id, $x, $y)Clicks an input tag of type image by ID attribute
    submitFormById($id)Submits by the form tag attribute
    + At the moment there aren't many methods to list available links and fields. + + + + + + + +
    isClickable($label)Test to see if a click target exists by label or name
    isSubmit($label)Test for the existence of a button with that label or name
    isImage($label)Test for the existence of an image button with that label or name
    getLink($label)Finds a URL from it's label
    getLinkById($label)Finds a URL from it's ID attribute
    getUrls()Lists available links in the current page
    + This will be expanded in later versions of SimpleTest. +

    +

    + Frames are a rather esoteric feature these days, but SimpleTest has + retained support for them. +

    +

    + Within a page, individual frames can be selected. + If no selection is made then all the frames are merged together + in one large conceptual page. + The content of the current page will be a concatenation of all of the + frames in the order that they were specified in the "frameset" + tags. + + + + + + +
    getFrames()A dump of the current frame structure
    getFrameFocus()Current frame label or index
    setFrameFocusByIndex($choice)Select a frame numbered from 1
    setFrameFocus($name)Select frame by label
    clearFrameFocus()Treat all the frames as a single page
    + When focused on a single frame, the content will come from + that frame only. + This includes links to click and forms to submit. +

    +
    +
    +

    + All of this functionality is great when we actually manage to fetch pages, + but that doesn't always happen. + To help figure out what went wrong, the browser has some methods to + aid in debugging... + + + + + + + + + + + + + + + + + +
    setConnectionTimeout($timeout)Close the socket on overrun
    getUrl()Url of most recent page fetched
    getRequest()Raw request header of page or frame
    getHeaders()Raw response header of page or frame
    getTransportError()Any socket level errors in the last fetch
    getResponseCode()HTTP response of page or frame
    getMimeType()Mime type of page or frame
    getAuthentication()Authentication type in 401 challenge header
    getRealm()Authentication realm in 401 challenge header
    getBaseUrl()Base url only of most recent page fetched
    setMaximumRedirects($max)Number of redirects before page is loaded anyway
    setMaximumNestedFrames($max)Protection against recursive framesets
    ignoreFrames()Disables frames support
    useFrames()Enables frames support
    ignoreCookies()Disables sending and receiving of cookies
    useCookies()Enables cookie support
    + The methods SimpleBrowser::setConnectionTimeout() + SimpleBrowser::setMaximumRedirects(), + SimpleBrowser::setMaximumNestedFrames(), + SimpleBrowser::ignoreFrames(), + SimpleBrowser::useFrames(), + SimpleBrowser::ignoreCookies() and + SimpleBrowser::useCokies() continue to apply + to every subsequent request. + The other methods are frames aware. + This means that if you have an individual frame that is not + loading, navigate to it using SimpleBrowser::setFrameFocus() + and you can then use SimpleBrowser::getRequest(), etc to + see what happened. +

    +
    +
    +

    + Anything that could be done in a + WebTestCase can + now be done in a UnitTestCase. + This means that we could freely mix domain object testing with the + web interface... +class TestOfRegistration extends UnitTestCase { + function testNewUserAddedToAuthenticator() { + $browser = new SimpleBrowser(); + $browser->get('http://my-site.com/register.php'); + $browser->setField('email', 'me@here'); + $browser->setField('password', 'Secret'); + $browser->click('Register'); + + $authenticator = new Authenticator(); + $member = $authenticator->findByEmail('me@here'); + $this->assertEqual($member->getPassword(), 'Secret'); + } +} +]]> + While this may be a useful temporary expediency, I am not a fan + of this type of testing. + The testing has cut across application layers, make it twice as + likely it will need refactoring when the code changes. +

    +

    + A more useful case of where using the browser directly can be helpful + is where the WebTestCase cannot cope. + An example is where two browsers are needed at the same time. +

    +

    + For example, say we want to disallow multiple simultaneous + usage of a site with the same username. + This test case will do the job... + + $first_attempt = new SimpleBrowser(); + $first_attempt->get('http://my-site.com/login.php'); + $first_attempt->setField('name', 'Me'); + $first_attempt->setField('password', 'Secret'); + $first_attempt->click('Enter'); + $this->assertEqual($first_attempt->getTitle(), 'Welcome'); + + $second_attempt = new SimpleBrowser(); + $second_attempt->get('http://my-site.com/login.php'); + $second_attempt->setField('name', 'Me'); + $second_attempt->setField('password', 'Secret'); + $second_attempt->click('Enter'); + $this->assertEqual($second_attempt->getTitle(), 'Access Denied'); + } +} +]]> + You can also use the SimpleBrowser class + directly when you want to write test cases using a different + test tool than SimpleTest, such as PHPUnit. +

    +
    +
    + + + Using the bundled web browser in scripts + + + Debugging failed pages + + + Complex tests with multiple web browsers + + + + + SimpleTest project page on SourceForge. + + + SimpleTest download page on LastCraft. + + + The developer's API for SimpleTest + gives full detail on the classes and assertions available. + + + + + user agent, + web browser php, + fetching pages, + spider scripts, + software development, + php programming for clients, + customer focused php, + software development tools, + acceptance testing framework, + free php scripts, + log in boxes, + unit testing authentication systems, + php resources, + HTMLUnit, + JWebUnit, + php testing, + unit test resource, + web testing, + HTTP authentication, + testing log in, + authentication testing, + security tests + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/changelog.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/changelog.xml new file mode 100755 index 0000000..ab3a6fd --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/changelog.xml @@ -0,0 +1,188 @@ + + + + SimpleTest's Changelog + +
    + + + Whitespace clean up + + + Some in line documentation fixes + + + Adding the JUnitReporter as an extension to SimpleTest (work by Patrice Neff) + + + New support page for Screencasts + + + + + Synchronizing the french translation. + + + Unit tests working for PHP 5.3 + + + Fix segfault with Zend Optimizer v3.2.2 (probably) + + + Adding some tags to help synchronise the docs + + + Add support for E_DEPRECATED + + + SimpleFileLoader::load: fix for $test_file already included, by daniel hahler - blueyed + + + New tests for UTF8 inside the browser. + + + "Update FormTesting tutorial page for hidden fields" submitted by David Heath - dgheath + + + Moving around the extensions : /ui is now deprecated, /extensions is holding all extensions, + /test/extensions is holding all extensions' test suites + + + Fixing one of the incompatible interface errors + + + Let choose which field of the same name to set + + + Clearing fatal error when throwing in a tearDown + + + Avoid a fatal error in assertLink + (when the link with a label does not exists in a page) submitted by German Rumm - german.rumm AT gmail.com + + + CssSelector chokes on single-quotes + + + clickImageByID not working : just updating the documentation + + + html special chars in links + + + decodeHtml does not decode some entities + (based on patch provided by Quandary - ai2097) + + + Radio buttons not working when set as integer + + + Missing return value + + + Inner links inside documentation for "simpletest.org" now work with *.html + + + Hostname extracted incorrectly from URIs containing @ sign + + + assertWantedText matches javascript source code + + + SimpleUrl doesn't appear to handle path after filename + + + remove call-time reference - its declared in the constructor, so that's enough + + + Adding error throwing to mocks + + + Added PHP4 patches for new mock code + + + Added filter that rewrites paths to included files in tests... + now just need to clean up all the hardcoded path references + in the existing tests and we should be able to make a start on building an extension layout + that's compatible with PEAR installer *and* manual tar/zip extraction + + + Add in default wrap to catch all 'verify' methods and wrap them in assertTrue + + + Recursive forms fails + + + SimpleFileLoader::selectRunnableTests(..) not only marks abstract classes as ignored + but filters them as well + + + renaming SimpleReflection::_isAbstractMethodInParent() into _isAbstractMethodInParents() + and making it check upwards if method was declared somewhere abstract not just in the immediate parent, + this allows to avoid ugly 'must be compatible' error in PHP5 + + + switch to Subversion (SVN) + + + + + autorun + + + browser base tag support + + + + + expectException() + + + proper skip facility + + + greater formatting control in the reporters + + + various mock object compatibility fixes + + +
    +
    + + + Changelog for version 1.0.1 + + + + + SimpleTest project page on SourceForge. + + + The developer's API for SimpleTest + gives full detail on the classes and assertions available. + + + + + software development, + php programming for clients, + customer focused php, + software development tools, + acceptance testing framework, + free php scripts, + log in boxes, + unit testing authentication systems, + php resources, + HTMLUnit, + JWebUnit, + php testing, + unit test resource, + web testing, + HTTP authentication, + testing log in, + authentication testing, + security tests + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/coding_standards.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/coding_standards.xml new file mode 100644 index 0000000..f08b774 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/coding_standards.xml @@ -0,0 +1,135 @@ + + + + Coding Standards within SimpleTest + +
    +

    + Here's the list we try to follow : +

      +
    1. + Short method bodies, hopefully five lines or less. +
    2. +
    3. + No final methods. + People hack their test tools in wierd and wonderful ways. +
    4. +
    5. + No type hinting. + So much of SimpeTest is unit tested, that type hints don't + currently pay their way. +
    6. +
    7. + All object variables are private. + Those currently in the code base that aren't, should be. + Exceptions are fluent interfaces. +
    8. +
    9. + ClassLikeThis, methodLikeThis(), $variables_like_this. +
    10. +
    11. + No abbreviations in names, + so $parameter rather than $param. + Acronyms are allowed if they are industry standard. + We tend to camel case them, e.g. parseXml(). +
    12. +
    13. + We prefer "get"/"set" prefix for accessors, + e.g. getMyAttribute(). + The exception is for fluent interfaces. + This preference is mainly historical though, and could be dropped. + Ruby coding standards are superceding Java ones these days, and + "get" isn't used in that environment. +
    14. +
    15. + Accessors even when a subclass calls a superclass. +
    16. +
    17. + Don't be afraid of long class and method names. + The exception is the use of $i as a loop variable. + A lot of people viewing the code are casual passers by and won't know + any secret conventions. +
    18. +
    19. + "Simple" as a class name prefix + unless it really pollutes the visible domain language. + No other namespace prefixing unless there is known library + clash in the wild. +
    20. +
    21. + Eric Evans domain driven design style + rather than pure XP/TDD. That is, slight over design + in order to make concepts clear. +
    22. +
    +

    +

    + Obviously these reflect Marcus Bakers' average coding style + over several projects and programming languages. +

    +

    + Is should also be obvious that + the code is riddled with exceptions to these rules :). +

    +
    +
    +

    + Please no dangling brace style. + No blank lines in methods. + So not... + + It wastes vertical space and is a hangover from long declarations in C++. + Rather this... + + White space to me should separate something, + such as a method or class. + If you feel the need to add space within a method body, just break + the code up into separate methods. + The exception is test code, which can contain a lot of mock set up. + It's still discouraged though. +

    +

    + By popular demand, all functions and methods are docblocked. + Private variables are not (the extra clutter is not worth it). + The only docblock attributes we require are @return + and @param as some IDE's use them. + Please take the time to write a human friendly comment. + Imagine you are chatting to a fellow SimpleTest user and they + ask the most obvious question about that variable. +

    +
    +
    + + + Coding standards + + + And other Code formatting stuff. + + + + + + + + + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/display_subclass_tutorial.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/display_subclass_tutorial.xml new file mode 100644 index 0000000..0e5243b --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/display_subclass_tutorial.xml @@ -0,0 +1,267 @@ + + + + PHP unit testing tutorial - Subclassing the test display + + +

    + SimpleTest ships with the simplest possible interface by default. + Mostly you just want to know if your test passed, or your test failed. + If it failed, you want the reason. + Nothing more. +

    +

    + Sometimes you want a more sophisticated output for stakeholders + and for acceptance testing. + If a minimal display is not enough, here + is how to roll your own. +

    +
    +
    +

    + Do you really need to see the passes? + Oh all right then, here's how. +

    +

    + We have to subclass the attached display, which in our case + is currently HtmlReporter. + The HtmlReporter class is in + the file simpletest/reporter.php and currently has + the following interface... + + Here is what the relevant methods mean. + You can see the + whole list here + if you are interested. +

      +
    • + __construct()
      + is the constructor. + Note that the unit test sets up the link to the display + rather than the other way around. + The display is a passive receiver of test events. + This allows easy adaption of the display for other test + systems beside unit tests, such as monitoring servers. + It also means that the unit test can write to more than + one display at a time. +
    • +
    • + void paintFail(string $message)
      + paints a failure. + See below. +
    • +
    • + void paintPass(string $message)
      + by default does nothing. + This is the method we will modify. +
    • +
    • + string getCss()
      + returns the CSS styles as a string for the page header + method. + Additional styles have to be appended here. +
    • +
    • + array getTestList()
      + is a convenience method for subclasses. + Lists the current nesting of the tests as a list + of test names. + The first, most deeply nested test, is first in the + list and the current test method will be last. +
    • +
    +

    +

    + To show the passes we just need the + paintPass() method to behave + just like paintFail(). + Of course we won't modify the original, we'll subclass. +

    +
    +
    +

    + Firstly we'll create a tests/show_passes.php file + in our logging project and then place in it this empty class... + +]]> + A quick peruse of the + SimpleTest code base + shows the paintFail() implementation + at the time of writing to look like this... +Fail: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode("->", $breadcrumb); + print "->$message
    \n"; +} +]]>
    + Essentially it chains to the parent's version, which we + have to do also to preserve house keeping, and then + prints a breadcrumbs trail calculated from the current test + list. + It drops the top level tests name, though. + As it is the same on every test that would be a little bit too + much information. + Transposing this to our new class... + + function paintPass($message) { + parent::paintPass($message); + print "Pass: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode("->", $breadcrumb); + print "->$message
    \n"; + } +} +]]>
    + So far so good. + Now to make use of our new class we have to modify our + tests/all_tests.php file. + +require_once('show_passes.php'); +require_once('simpletest/simpletest.php'); +SimpleTest::prefer('ShowPasses'); +require_once('simpletest/autorun.php'); + +class AllTests { + function __construct() { + parent::__construct('All tests'); + $this->addTestFile('log_test.php'); + $this->addTestFile('clock_test.php'); + } +} +?> +]]> + We can run this to see the results of our handywork... +

    +

    All tests

    + Pass: log_test.php->Log class test->testappendingtofile->Expecting [/Test line 1/] in [Test line 1]
    + Pass: log_test.php->Log class test->testappendingtofile->Expecting [/Test line 2/] in [Test line 2]
    + Pass: log_test.php->Log class test->testcreatingnewfile->Created before message
    + Pass: log_test.php->Log class test->testcreatingnewfile->File created
    + Pass: clock_test.php->Clock class test->testclockadvance->Advancement
    + Pass: clock_test.php->Clock class test->testclocktellstime->Now is the right time
    +
    3/3 test cases complete. + 6 passes and 0 fails.
    +
    + Nice, but no gold star. + We have lost a little formatting here. + The display does not have a CSS style for + span.pass, but we can add this + easily by overriding one more method... +Pass: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode("->", $breadcrumb); + print "->$message
    \n"; + } + + protected function getCss() { + return parent::getCss() . ' .pass { color: green; }'; + } +} +]]>
    + If you are adding the code as you go, you will see the style + appended when you do view source on the test results page in your browser. + To the eye the display itself should now look like this... +
    +

    All tests

    + Pass: log_test.php->Log class test->testappendingtofile->Expecting [/Test line 1/] in [Test line 1]
    + Pass: log_test.php->Log class test->testappendingtofile->Expecting [/Test line 2/] in [Test line 2]
    + Pass: log_test.php->Log class test->testcreatingnewfile->Created before message
    + Pass: log_test.php->Log class test->testcreatingnewfile->File created
    + Pass: clock_test.php->Clock class test->testclockadvance->Advancement
    + Pass: clock_test.php->Clock class test->testclocktellstime->Now is the right time
    +
    3/3 test cases complete. + 6 passes and 0 fails.
    +
    + Some people definitely prefer to see the passes being added + as they are working on code; the feeling that you are getting + work done is nice after all. + Once you have to scroll up and down the page to find failures + though, you soon come to realise its dark side. +

    +

    + Try it both ways and see which you prefer. + We'll leave it in for a bit anyhow when looking at the + mock objects coming up. + This is the first test tool that generates additional tests + and it will be useful to see what is happening behind the scenes. +

    +
    +
    + + + How to Change the display to show test passes. + + + Subclassing the HtmlReporter class. + + + + + The previous tutorial section was + subclassing the test case. + + + This section is very specific to + SimpleTest. + If you use another tool you will want to skip this. + + + + + software development test first, + php general programming advice, + programming php, + software development tools, + php tutorial, + free php sample code, + architecture, + sample test cases, + php simple test framework, + php resources, + php test case examples, + phpunit, + simpletest, + unit test, + php testing + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/download_website.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/download_website.xml new file mode 100644 index 0000000..542c057 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/download_website.xml @@ -0,0 +1,106 @@ + + + + Downloading SimpleTest + +
    +

    + The current release is : + + SimpleTest v1.0.1. +

    +

    + You can download this version from + + SourceForget.net + and your local mirror. You may also want to look at + the current changelog. +

    + +
    +
    +

    + If eclipse is your IDE / editor of choice, you might consider + the + Eclipse plugin. +

    +

    + For automatic "find and install", the URL is : +

    http://simpletest.org/eclipse/
    +

    +
    +
    +

    + SimpleTest has been packaged by the community to different flavours. +

    + +

    + Note : careful, some versions are not always up-to-date. +

    +
    +
    +

    + The source code is hosted on Sourceforge as well : you can browse the + SVN repository + there. +

    +
    +
    + +
    +
    + + + Current release + + + Eclipse release + + + Packages + + + Source + + + Older stable releases + + + + + + + + + SimpleTest, + download, + source code, + stable release, + eclipse release, + eclipse plugin, + debian package, + drupal module, + pear channel, + pearified package + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/expectation_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/expectation_documentation.xml new file mode 100644 index 0000000..74a0f02 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/expectation_documentation.xml @@ -0,0 +1,383 @@ + + + + + Extending the SimpleTest unit tester with additional expectation classes + + +
    +

    + The default behaviour of the + mock objects + in + SimpleTest + is either an identical match on the argument or to allow any argument at all. + For almost all tests this is sufficient. + Sometimes, though, you want to weaken a test case. +

    +

    + One place where a test can be too tightly coupled is with + text matching. + Suppose we have a component that outputs a helpful error + message when something goes wrong. + You want to test that the correct error was sent, but the actual + text may be rather long. + If you test for the text exactly, then every time the exact wording + of the message changes, you will have to go back and edit the test suite. +

    +

    + For example, suppose we have a news service that has failed + to connect to its remote source. +class NewsService { + ... + function publish($writer) { + if (! $this->isConnected()) { + $writer->write('Cannot connect to news service "' . + $this->_name . '" at this time. ' . + 'Please try again later.'); + } + ... + } +} +]]> + Here it is sending its content to a + Writer class. + We could test this behaviour with a + MockWriter like so... + + $writer = new MockWriter(); + $writer->expectOnce('write', array( + 'Cannot connect to news service ' . + '"BBC News" at this time. ' . + 'Please try again later.')); + + $service = new NewsService('BBC News'); + $service->publish($writer); + } +} +]]> + This is a good example of a brittle test. + If we decide to add additional instructions, such as + suggesting an alternative news source, we will break + our tests even though no underlying functionality + has been altered. +

    +

    + To get around this, we would like to do a regular expression + test rather than an exact match. + We can actually do this with... + + $writer->expectOnce( + 'write', + array(new PatternExpectation('/cannot connect/i'))); + + $service = new NewsService('BBC News'); + $service->publish($writer); + } +} +]]> + Instead of passing in the expected parameter to the + MockWriter we pass an + expectation class called + PatternExpectation. + The mock object is smart enough to recognise this as special + and to treat it differently. + Rather than simply comparing the incoming argument to this + object, it uses the expectation object itself to + perform the test. +

    +

    + The PatternExpectation takes + the regular expression to match in its constructor. + Whenever a comparison is made by the MockWriter + against this expectation class, it will do a + preg_match() with this pattern. + With our test case above, as long as "cannot connect" + appears in the text of the string, the mock will issue a pass + to the unit tester. + The rest of the text does not matter. +

    +

    + The possible expectation classes are... + + + + + + + + + + + + + + + +
    AnythingExpectationWill always match
    EqualExpectationAn equality, rather than the stronger identity comparison
    NotEqualExpectationAn inequality comparison
    IndenticalExpectationThe default mock object check which must match exactly
    NotIndenticalExpectationInverts the mock object logic
    WithinMarginExpectationCompares a value to within a margin
    OutsideMarginExpectationChecks that a value is out side the margin
    PatternExpectationUses a Perl Regex to match a string
    NoPatternExpectationPasses only if failing a Perl Regex
    IsAExpectationChecks the type or class name only
    NotAExpectationOpposite of the IsAExpectation
    MethodExistsExpectationChecks a method is available on an object
    TrueExpectationAccepts any PHP variable that evaluates to true
    FalseExpectationAccepts any PHP variable that evaluates to false
    + Most take the expected value in the constructor. + The exceptions are the pattern matchers, which take a regular expression, + and the IsAExpectation and NotAExpectation which takes a type + or class name as a string. +

    +

    + Some examples... +

    +

    +expectOnce('method', array(new IdenticalExpectation(14))); +]]> + This is the same as $mock->expectOnce('method', array(14)). +expectOnce('method', array(new EqualExpectation(14))); +]]> + This is different from the previous version in that the string + "14" as a parameter will also pass. + Sometimes the additional type checks of SimpleTest are too restrictive. +expectOnce('method', array(new AnythingExpectation(14))); +]]> + This is the same as $mock->expectOnce('method', array('*')). +expectOnce('method', array(new IdenticalExpectation('*'))); +]]> + This is handy if you want to assert a literal "*". + + This matches on anything other than integer 14. + Even the string "14" would pass. + + This will accept any value from 13.999 to 14.001 inclusive. +

    +
    +
    +

    + The expectation classes can be used not just for sending assertions + from mock objects, but also for selecting behaviour for the + mock objects. + Anywhere a list of arguments is given, a list of expectation objects + can be inserted instead. +

    +

    + Suppose we want a mock authorisation server to simulate a successful login, + but only if it receives a valid session object. + We can do this as follows... + +$authorisation = new MockAuthorisation(); +$authorisation->setReturnValue( + 'isAllowed', + true, + array(new IsAExpectation('Session', 'Must be a session'))); +$authorisation->setReturnValue('isAllowed', false); +]]> + We have set the default mock behaviour to return false when + isAllowed is called. + When we call the method with a single parameter that + is a Session object, it will return true. + We have also added a second parameter as a message. + This will be displayed as part of the mock object + failure message if this expectation is the cause of + a failure. +

    +

    + This kind of sophistication is rarely useful, but is included for + completeness. +

    +
    +
    +

    + The expectation classes have a very simple structure. + So simple that it is easy to create your own versions for + commonly used test logic. +

    +

    + As an example here is the creation of a class to test for + valid IP addresses. + In order to work correctly with the stubs and mocks the new + expectation class should extend + SimpleExpectation or forther extend a subclass... +class ValidIp extends SimpleExpectation { + + function test($ip) { + return (ip2long($ip) != -1); + } + + function testMessage($ip) { + return "Address [$ip] should be a valid IP address"; + } +} +]]> + There are only two methods to implement. + The test() method should + evaluate to true if the expectation is to pass, and + false otherwise. + The testMessage() method + should simply return some helpful text explaining the test + that was carried out. +

    +

    + This class can now be used in place of the earlier expectation + classes. +

    +

    + Here is a more typical example, matching part of a hash... +class JustField extends EqualExpectation { + private $key; + + function __construct($key, $expected) { + parent::__construct($expected); + $this->key = $key; + } + + function test($compare) { + if (! isset($compare[$this->key])) { + return false; + } + return parent::test($compare[$this->key]); + } + + function testMessage($compare) { + if (! isset($compare[$this->key])) { + return 'Key [' . $this->key . '] does not exist'; + } + return 'Key [' . $this->key . '] -> ' . + parent::testMessage($compare[$this->key]); + } +} +]]> + We tend to seperate message clauses with + " ]]>". + This allows derivative tools to reformat the output. +

    +

    + Suppose some authenticator is expecting to be given + a database row corresponding to the user, and we + only need to confirm the username is correct. + We can assert just their username with... +expectOnce('authenticate', + array(new JustKey('username', 'marcus'))); +]]> +

    +
    +
    +

    + The SimpleTest unit testing framework + also uses the expectation classes internally for the + UnitTestCase class. + We can also take advantage of these mechanisms to reuse our + homebrew expectation classes within the test suites directly. +

    +

    + The most crude way of doing this is to use the generic + SimpleTest::assert() method to + test against it directly... +class TestOfNetworking extends UnitTestCase { + ... + function testGetValidIp() { + $server = &new Server(); + $this->assert( + new ValidIp(), + $server->getIp(), + 'Server IP address->%s'); + } +} +]]> + assert() will test any expectation class directly. +

    +

    + This is a little untidy compared with our usual + assert...() syntax. +

    +

    + For such a simple case we would normally create a + separate assertion method on our test case rather + than bother using the expectation class. + If we pretend that our expectation is a little more + complicated for a moment, so that we want to reuse it, + we get... + + function assertValidIp($ip, $message = '%s') { + $this->assert(new ValidIp(), $ip, $message); + } + + function testGetValidIp() { + $server = &new Server(); + $this->assertValidIp( + $server->getIp(), + 'Server IP address->%s'); + } +} +]]> + It is rare to need the expectations for more than pattern + matching, but these facilities do allow testers to build + some sort of domain language for testing their application. + Also, complex expectation classes could make the tests + harder to read and debug. + In effect extending the test framework to create their own tool set. +

    +
    +
    + + + Using expectations for + more precise testing with mock objects + + + Changing mock object behaviour with expectations + + + Extending the expectations + + + Underneath SimpleTest uses expectation classes + + + + + SimpleTest project page on SourceForge. + + + SimpleTest download page on LastCraft. + + + The expectations mimic the constraints in JMock. + + + Full API for SimpleTest + from the PHPDoc. + + + + + mock objects, + test driven development, + inheritance of expectations, + mock object constraints, + advanced PHP unit testing, + test first, + test framework architecture + expectations in simpletest + test cases + server stubs + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/experimental_dom_tester.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/experimental_dom_tester.xml new file mode 100644 index 0000000..fda46a0 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/experimental_dom_tester.xml @@ -0,0 +1,231 @@ + + + + [experimental] Testing elements by CSS selector + + +
    + Careful : this documentation refers to un-released code. It's only available in SVN. +
    +

    + Before SimpleTest2 gets a new browser (PHP5 only and DOM based), a new + extension made its way to the SimpleTest trunk. +

    +

    + Note : it's already PHP5 only. +

    +
    +
    +

    + 1998 seems a long time ago now : it's the year + the the Web Standards Project (WaSP) + started fighting for standards. +

    +

    + Nearly ten years later, every web developper is now familiar with CSS : + id & class are the new tools of the trade. + The tricks you've learned while styling, you can re-use them when testing your web site. +

    +

    + I was about to start with the Flickr home page + but then again, this is what the HTML source looks like : + +

    +
    + + + +
    +[...] +]]> + Who said too many tables ? So I picked something else... + the SimpleTest home page ! + +
    +
    + +
    +
    + +[...] +]]> +

    + +
    +

    + As usual we start with the necessary require_once + +]]> + The DomTestCase extends the WebTestCase, + we can just use it the same way. +

    +

    +assertTrue($this->get($url)); + $this->assertEqual($this->getUrl(), $url); + $this->assertElementsBySelector( + 'h2', + array('Screenshots', 'Documentation', 'Contributing') + ); + } +} +?> +]]> +

    +

    + I was expecting to get a nice green bar straight away : + I did wrote the HTML template after all. + Unfortunately it didn't happen, PHP gave me some nice exceptions : +

    + home: /$ php dom_tester_doc_test.php
    + dom_tester_doc_test.php
    + Exception 1!
    + Unexpected PHP error [DOMDocument::loadHTML() [function.DOMDocument-loadHTML]: ID simpletestlogo already defined in Entity, line: 12] severity [E_WARNING] in [/Users/perrick/Sites/simpletest/extensions/dom_tester.php line 103]
    + in testGet
    + in TestOfLiveCssSelectors
    + Exception 2!
    + Unexpected PHP error [DOMDocument::loadHTML() [function.DOMDocument-loadHTML]: ID simpletestdownload already defined in Entity, line: 16] severity [E_WARNING] in [/Users/perrick/Sites/simpletest/extensions/dom_tester.php line 103]
    + in testGet
    + in TestOfLiveCssSelectors
    + Exception 3!
    + Unexpected PHP error [DOMDocument::loadHTML() [function.DOMDocument-loadHTML]: ID simpleteststarttesting already defined in Entity, line: 24] severity [E_WARNING] in [/Users/perrick/Sites/simpletest/extensions/dom_tester.php line 103]
    + in testGet
    + in TestOfLiveCssSelectors
    + Exception 4!
    + Unexpected PHP error [DOMDocument::loadHTML() [function.DOMDocument-loadHTML]: ID simpletestsupport already defined in Entity, line: 38] severity [E_WARNING] in [/Users/perrick/Sites/simpletest/extensions/dom_tester.php line 103]
    + in testGet
    + in TestOfLiveCssSelectors
    + FAILURES!!!
    + Test cases run: 1/1, Passes: 3, Failures: 0, Exceptions: 4
    +
    + That's what validation is all about I guess. +

    +

    + Back to the drawing board... A simple fix later, everything is fine. +

    + home: /$ php dom_tester_doc_test.php
    + dom_tester_doc_test.php
    + OK
    + Test cases run: 1/1, Passes: 3, Failures: 0, Exceptions: 0
    +
    +

    +
    +
    +

    + If you thought it was easy, we can make things a little bit more difficult - + or precise - with attribute selectors : +assertTrue($this->get($url)); + $this->assertEqual($this->getUrl(), $url); + $this->assertElementsBySelector( + 'h2', + array('Screenshots', 'Documentation', 'Contributing') + ); + $this->assertElementsBySelector( + 'a[href="http://simpletest.org/api/"]', + array('the complete API', 'documented API') + ); + } +} +[...] +]]> +

    + home: /$ php dom_tester_doc_test.php
    + dom_tester_doc_test.php
    + OK
    + Test cases run: 1/1, Passes: 4, Failures: 0, Exceptions: 0
    +
    +

    +

    + Oh and by the way, combinators do work as well : +assertTrue($this->get($url)); + $this->assertEqual($this->getUrl(), $url); + $this->assertElementsBySelector( + 'h2', + array('Screenshots', 'Documentation', 'Contributing') + ); + $this->assertElementsBySelector( + 'a[href="http://simpletest.org/api/"]', + array('the complete API', 'documented API') + ); + $this->assertElementsBySelector( + 'div#content > p > strong', + array('SimpleTest PHP unit tester') + ); + } +} +[...] +]]> +

    + home: /$ php dom_tester_doc_test.php
    + dom_tester_doc_test.php
    + OK
    + Test cases run: 1/1, Passes: 5, Failures: 0, Exceptions: 0
    +
    +

    +
    + + + A Recorder for non-developpers. + Exploring the Recorder results. + + + + The JUnit FAQ + has plenty of useful testing advice. + + + Next is grouping test + cases together. + + + You will need the SimpleTest testing framework + for these examples. + + + + + software development, + php programming, + programming php, + software development tools, + php tutorial, + free php scripts, + architecture, + php resources, + mock objects, + junit, + php testing, + unit test, + automated php testing, + test cases tutorial, + explain unit test case, + unit test example, + unit test + + + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/experimental_intro.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/experimental_intro.xml new file mode 100644 index 0000000..430e250 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/experimental_intro.xml @@ -0,0 +1,54 @@ + + + + Experimental stuff, found in SVN only + +
    +

    + If a bug is found in SimpleTest, chances are it'll get corrected + inside the Subversion (SVN) tree with the corresponding test case : just to make + sure the bug doesn't make a come back later on. +

    +

    + Also the SimpleTest SVN trunk is usually full of little known + experiments with code : new ideas are usually tried out there + before a general release. Before it happens, the documentation needs + to be completed : that's where this section comes into place. You'll + find here new features the dev team is happy with : we're looking + for comments... +

    +

    + The mailing-list is usually the best place to give feedback. Other options + includes submitting a bug or a patch directly on the Sourceforge trackers... +

    +

    + Note : some of these features may never make it to a release or not for + a long time! +

    +
    +
    + + + SimpleTest mailing-lists' page on SourceForge. + + + SimpleTest trackers' page on Sourceforge as well. + + + + + php unit testing, + test integration, + documentation, + marcus baker, + simple test, + simpletest documentation, + phpunit, + pear, + experimental, + cvs, + code experiment, + feedback + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/experimental_recorder.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/experimental_recorder.xml new file mode 100644 index 0000000..e4991cc --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/experimental_recorder.xml @@ -0,0 +1,170 @@ + + + + [experimental] Using the test results for other tools with the Recorder + + +
    + Careful : this documentation refers to un-released code. It's only available in SVN. +
    +

    + What the developer wants from his tests is immediate feedback. The TextReporter + and the HtmlReporter are there to show this feedback. As quickly as possible + and as close to your working tools as possible : the console, the browser. +

    +
    +
    +

    + But often developers don't work alone : you may have a team contributing to + the same code base next door, a manager a little bit further away and even + a team of testers or power users on the other side of the planet. +

    +

    + For them as well tests' results are valuable pieces of information ! They just need + to be formatted and archived somewhere. The Recorder allows you to grab all + output to a simple PHP array. + +require_once('simpletest/ui/recorder.php'); + +$group = new TestSuite(); +$group->addTestFile('test_of_module_ship.php'); +$group->addTestFile('test_of_module_pay.php'); + +$recorder = new Recorder(); +$group->run($recorder); +?> +]]> +

    +

    + Within this $recorder object lies the test suite information. + Let's dump the $recorder->results variable and check what's + in it : + + array(4) { + ["time"]=> + int(1173951256) + ["status"]=> + string(6) "Passed" + ["test"]=> + string(129) "examples/test_of_module_ship.php->TestUsingParcelForce->testParcelForceIsFine" + ["message"]=> + string(97) " at [examples/test_of_module_ship.php line 7]" + } + [1]=> + array(4) { + ["time"]=> + int(1173951256) + ["status"]=> + string(6) "Passed" + ["test"]=> + string(154) "examples/test_of_module_ship.php->TestUsingMyOwnAirplane->testUsingMyOwnAirplaneOnlyWorksForSmallGifts" + ["message"]=> + string(134) "Expected false, got [Boolean: false] at [examples/test_of_module_ship.php line 13]" + } + [2]=> + array(4) { + ["time"]=> + int(1173951256) + ["status"]=> + string(6) "Passed" + ["test"]=> + string(135) "examples/test_of_module_pay.php->TestPayForParcelForce->testPayForParcelForceIsFine" + ["message"]=> + string(96) " at [examples/test_of_module_pay.php line 7]" + } + [3]=> + array(4) { + ["time"]=> + int(1173951256) + ["status"]=> + string(6) "Passed" + ["test"]=> + string(155) "examples/test_of_module_pay.php->TestPayForMyOwnAirplane->testPayForMyOwnAirplaneOnlyWorksForSmallGifts" + ["message"]=> + string(133) "Expected false, got [Boolean: false] at [examples/test_of_module_pay.php line 13]" + } +} +?> +]]> +

    +
    +
    +

    + Each element of the $recoder->results array contains one assertion's + information : +

      +
    • time : when the assertion was executed, in a timestamp format.
    • +
    • status : either Passed or Failed
    • +
    • test : the name of the test
    • +
    • message : the actual message, useful for understanding what went wrong
    • +
    +

    +

    + Now iterating throught the results becomes really easy... +addTestFile('test_of_module_ship.php'); +$group->addTestFile('test_of_module_pay.php'); +$recorder = new Recorder(); +$group->run($recorder); + +foreach (recorder->results as $result) { + if ($result->status == "Failed") { + do_something_while_it_is_time(result); + } +} + +?> +]]> +

    +
    +
    + + A Recorder for non-developers. + Exploring the Recorder results. + + + + The JUnit FAQ + has plenty of useful testing advice. + + + Next is grouping test + cases together. + + + You will need the SimpleTest testing framework + for these examples. + + + + + software development, + php programming, + programming php, + software development tools, + php tutorial, + free php scripts, + architecture, + php resources, + mock objects, + junit, + php testing, + unit test, + automated php testing, + test cases tutorial, + explain unit test case, + unit test example, + unit test + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/extension_eclipse.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/extension_eclipse.xml new file mode 100644 index 0000000..b86278b --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/extension_eclipse.xml @@ -0,0 +1,279 @@ + + + + Simpletest Eclipse Plugin Documentation + +
    +
      +
    • + Operating System.
      + This plugin should work on any platform on which Eclipse works. Specifically + the plugin has been tested to work on Linux, OS X, and Windows. If the + plugin does not appear to work on one of these platforms a new incompatibility + may have emerged, but this is relatively unlikely. +
    • +
    • + Eclipse
      + The plugin has been designed to work with Eclipse versions 3.1.X+ and 3.2.X+. + Eclipse in general requires a JVM version of 1.4.x; there are no additional requirements for the plugin. +
    • +
    • + PHP
      + The plugin has been tested to work with PHP 4.3.x+ (including PHP 5.1.x.). +
    • +
    • + Xdebug
      + The plugin can be configured to work with Xdebug (version 2.0.0RC1 or later). + The php.ini file which is used by the plugin (see below for specifying the php.ini file) must load the Xdebug extension. + This is usually accomplished by adding a zend_extension_ts="Some Windows Path" or zend_extension="Some Linux Path" to your php.ini file. + Specific instructions can be found at: http://xdebug.org/install.php + Do not worry about specifying any other parameters, the plugin will handle setting the necessary parameters, as needed. +
    • +
    • + Simpletest
      + The plugin has been designed to work with a specific version of Simpletest + and in fact includes the associated version of Simpletest in the plugin for compatibility. + A plugin with a version number of 1.0.0_0.0.4 would indicate that the plugin was compatible + with Simpletest version 1.0.0 and was version 0.0.4 of the plugin. + Some slight modifications to the Simpletest code are required to make the plugin + work with Simpletest; these modifications may or may not be in the released version of Simpletest. +
    • +
    • + PHPUnit2
      + Starting with plugin version 0.1.6 experimental support for the CVS version of PHPUnit2 + is available. To use PHPUnit2 select a path to PHPUnit2 and select PHPUnit2 test instead of + SimpleTest. Note: this is the path to the folder that contains the PHPUnit2 + folder. PHPUnit3 is not currently supported but will be supported in the near future. +
    • +
    +
    +
    +
      +
    1. Download and Install Eclipse (www.eclipse.org) – if you are not sure what to download, download the Eclipse 3.1.0 SDK.
    2. +
    3. Download the latest Simpletest Eclipse plug-in zip file (Simpletest Sourceforge)
    4. +
    5. Extract the zip file to a temporary location {this will be referred to later in this documentation as $unzip}
    6. +
    7. Start Eclipse
    8. +
    9. Open the Install Wizard by clicking "Help" > "Software Updates" > "Find and Install" from the menu bar.
    10. +
    11. Select the second button, "Search for new features to install" and click "Next".
    12. +
    13. Click the button on the right hand side, "New Local Site".
    14. +
    15. Select the folder $unzip {the folder extracted previously}.
    16. +
    17. Click the "OK" button.
    18. +
    19. In the "Edit Local Site" window, Click the "OK" button
    20. +
    21. Click the "Finish" button
    22. +
    23. In the "Search Results" window, drill down and select "Simpletest plug-in 0.0.x"
    24. +
    25. Click the "Next" button.
    26. +
    27. Read the license and accept the license by clicking the "I accept the terms in the license agreement" radio button and then clicking the "Next" button
    28. +
    29. In the "Installation" window you can choose to change the location -- most users should just click the "Finish" button
    30. +
    31. In the "Feature Verification" window, click the "Install" button.
    32. +
    33. When prompted, restart Eclipse
    34. +
    35. +

      After starting Eclipse for the first time after installation + you will need to perform a quick configuration of the Simpletest Plug-in. + To perform this configuration: +

      +
        +
      1. + Select "Window" > "Preferences" from the menu bar +
      2. +
      3. + Select "Simpletest" from the categories on the left hand side of the popup box. +
      4. +
      5. + Enter or Browse for the location of a PHP executable to use. +
      6. +
      7. + Leave the include file blank +
      8. +
      9. + Enter .php as the Test File Suffix. + Alternately if you name your test PHP files with a sufficiently different suffix + (e.g sometest.tst.php) you could enter in a more differentiating suffix (e.g. .tst.php). + This helps when the plug-in is looking for tests to execute. +
      10. +
      11. + Hit the "Ok" button to close the preferences window. +
      12. +
      +
    36. +
    +
    +
    + Note: this will only work if you have previously installed the plugin using the + Eclipse installation wizard. If you previously copied directories for installation, + it is recommended that you shutdown Eclipse, delete the previous version directories, and + follow the Installation instructions above (you should not have to perform the initial configuration + portion). +
      +
    1. Select "Help" > "Software Updates" > "Manage Configuration" from the menu
    2. +
    3. Drill down to find the Simpletest plugin and select it
    4. +
    5. In the right hand pane click the link for "Scan for Updates"
    6. +
    7. IF no updates are found, navigate to Window > Preferences > Install/Update > Valid Updates in the Eclipse preferences and select 'Compatible', not 'equivalent'. Then repeat the steps above.
    8. +
    9. Select the feature versions that you wish to upgrade, and click "Next".
    10. +
    11. Review the license agreements for the upgraded features. If the terms of all these licenses are acceptable, check "I accept the terms in the license agreements." Do not proceed to download the features if the license terms are not acceptable.
    12. +
    13. Click "Install" to allow the downloading and installing to proceed.
    14. +
    15. Once all the features and plug-ins have been downloaded successfully and their files installed into the product on the local computer, a new configuration that incorporates these features and plug-ins will be formulated. Click Yes when asked to exit and restart the Workbench for the changes to take effect.
    16. +
    +
    +
    +

    + Note: this will only work if the plugin was installed via the Feature Update method. If installed via alternate methods, then the plugin can be uninstalled by deleting the directories which were previously added. +

    +
      +
    1. Select "Help" > "Software Updates" > "Manage Configuration"
    2. +
    3. Drill down to find the Simpletest plugin
    4. +
    5. Right-click on the Simpletest plugin and select the option "Disable"
    6. +
    7. when prompted, restart Eclipse
    8. +
    9. After Eclipse has restarted, select "Help" > "Software Updates" > "Manage Configuration" from the menu bar
    10. +
    11. Select the option from the toolbar "Show Disabled Features"
    12. +
    13. Drill down to find the Simpletest plugin
    14. +
    15. Right-click on the Simpletest plugin and select the option "Uninstall"
    16. +
    17. When prompted, restart Eclipse
    18. +
    +
    +
    +

    + The following details some sample usages of the plugin. +

    +
      +
    1. Create a new test project (a holding place for related files) +
        +
      1. Select "File" > "New" > "Project.." from the menu
      2. +
      3. Expand the folder "General" and select "Project"
      4. +
      5. Click the "Next" button
      6. +
      7. On the next tab enter a project name: "Test"
      8. +
      9. Use the default Project Contents
      10. +
      11. Click the "Finish" button
      12. +
      +
    2. +
    3. Create a Single Passing Test +
        +
      1. In the Package Explorer View right-click on the "Test" project and select "New" > "File".
      2. +
      3. For the filename enter: test1.php and click "Finish"
      4. +
      5. Double-click on the test1.php entry in the Package Explorer which should open a new view to edit the file.
      6. +
      7. Enter the following for the contents of the file: +assertEqual(3,$total, "This should pass"); + } +} +?> +]]> +
      8. +
      9. Select "File" > "Save" from the menu.
      10. +
      11. Right click on the test1.php entry and select "Run" > "Run Simpletest".
      12. +
      13. The "Result View" should populate with information about the test run and the Simpletest console should fill with some information as well.
      14. +
      +
    4. +
    5. Single Test class multiple tests +
        +
      1. In the Package Explorer View right-click on the "Test" project and select "New" > "File".
      2. +
      3. For the filename enter: test2.php and click "Finish".
      4. +
      5. Double-click on the test2.php entry in the Package Explorer which should open a new view to edit the file.
      6. +
      7. Enter the following for the contents of the file: +assertEqual(3,$total, "This should pass"); + } + function test_fail(){ + $x = 1; + $y = 2; + $total = $x + $y; + $this->assertEqual(4,$total,"This should fail"); + } +} +?> +]]> +
      8. +
      9. Select "File" > "save" from the menu bar.
      10. +
      11. Right click on the test2.php entry and select "Run" > "Run Simpletest".
      12. +
      13. The Result View should populate with information about the test run and the Simpletest console should fill with some information as well
      14. +
      +
    6. +
    7. Group Tests (test multiple files at once) +
        +
      1. In the Package Explorer View right-click on the "Test" project and select "New" > "File"
      2. +
      3. For the filename enter: grouptest.php and click "Finish".
      4. +
      5. Double-click on the grouptest.php entry in the Package Explorer which should open a new view to edit the file.
      6. +
      7. Enter the following for the contents of the file: +addTestFile(dirname(__FILE__).'/test1.php'); + $this->addTestFile(dirname(__FILE__).'/test2.php'); + } +} +?> +]]> +
      8. +
      9. Select "File" > "save" from the menu
      10. +
      11. Right click on the grouptest.php entry and select "Run" > "Run Simpletest"
      12. +
      13. The Result View should populate with information about the test run and the Simpletest console should fill with some information as well
      14. +
      +
    8. +
    +
    +
    +
      +
    • + Make sure that if a constructor is used in the test case + that the last line of the constructor calls the parent + constructor (e.g. parent::UnitTestCase) +
    • +
    • + Do not put any assertions into the test class constructor +
    • +
    • + If you get an error indicating that a class could not load; then restart Eclipse. Once + Eclipse restarts, open the Result View manually by selecting "Window"->"Show View"->"Other..." + Then select the SimpleTest Category and select "Result View" and click "OK". +
    • +
    +
    +
    +

    These are features that should eventually make it into this plugin

    +
      +
    • + Allow different "include" file on each runner (override the "master" include) +
    • +
    • + Handle fatal errors better +
    • +
    +
    +
    + + + + software development, + eclipse plugin, + php programming, + programming php, + software development tools, + php tutorial, + free php scripts, + architecture, + php resources, + mock objects, + junit, + php testing, + unit test, + automated php testing, + test cases tutorial, + explain unit test case, + unit test example, + unit test + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/first_test_tutorial.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/first_test_tutorial.xml new file mode 100644 index 0000000..962ec80 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/first_test_tutorial.xml @@ -0,0 +1,436 @@ + + + + PHP unit testing tutorial - Creating an example test case in PHP + + +

    + If you are new to unit testing it is recommended that you + actually try the code out as we go. + There is actually not very much to type and you will get + a feel for the rhythm of test first programming. +

    +

    + To run the examples as is, you need an empty directory with the folders + classes, tests and temp. + Unpack the SimpleTest framework + into the tests folder and make sure your web server + can reach these locations. +

    +
    +
    +

    + The quick introductory example + featured the unit testing of a simple log class. + In this tutorial on SimpleTest I am going to try to + tell the whole story of developing this class. + This PHP class is small and simple and in the course of + this introduction will receive far more attention than + it probably would in production. + Yet even this tiny class contains some surprisingly difficult + design decisions. +

    +

    + Maybe they are too difficult? + Rather than trying to design the whole thing up front + I'll start with a known requirement, namely + we want to write messages to a file. + These messages must be appended to the file if it exists. + Later we will want priorities and filters and things, but + for now we will place the file writing requirement in + the forefront of our thoughts. + We will think of nothing else for fear of getting confused. + OK, let's make a test... + +]]> + Piece by piece here is what it all means. +

    +

    + The dirname(__FILE__) construct just ensures + that the path is taken relative current file. +

    +

    + What is this autorun.php file? + This file does the expected work of pulling in the definitions + of UnitTestCase. + It collects all test classes in the current file and runs + them automagically. + It does this by setting up an exit handler. + More on this later when we look at modifying the output. +

    +

    + The tests themselves are gathered in test case classes. + This one, the TestOfLoggingclass , is typical in extending + UnitTestCase. + When the test case is invoked by the autorunner it will + search for any method within that starts with the name "test". + Each of these methods will be executed in the order they are + defined in the class. +

    +

    + Our only test method at present is called + testFirstLogMessagesCreatesFileIfNonexistent(). + There is nothing in it yet. +

    +

    + Now the empty method definition on its own does not do anything. + We need to actually place some code inside it. + The UnitTestCase class + will typically generate test events when run and these events are + sent to an observing reporter using methods inherited from + UnitTestCase. +

    +

    + Now to add test code... + + +class TestOfLogging extends UnitTestCase { + function testFirstLogMessagesCreatesFileIfNonexistent() { + @unlink(dirname(__FILE__) . '/../temp/test.log'); + $log = new Log(dirname(__FILE__) . '/../temp/test.log'); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists(dirname(__FILE__) . '../temp/test.log')); + } +} +?> +]]> +

    +

    + You are probably thinking that that is a lot of test code for + just one test and I would agree. + Don't worry. + This is a fixed cost and from now on we can add tests + pretty much as one liners. + Even less when using some of the test artifacts that we + will use later. +

    +

    + You might also have been thinking that + testFirstLogMessagesCreatesFileIfNonexistent + is an awfully long method name. + Normally that would be true, but here it's a good thing. + We will never type this name again, and we save ourselves having + to write comments or specifications. +

    +

    + Now comes the first of our decisions. + Our test file is called log_test.php (any name + is fine) and is in a folder called tests (anywhere is fine). + We have called our code file log.php and this is + the code we are going to test. + I have placed it into a folder called classes, so that means + we are building a class, yes? +

    +

    + For this example I am, but the unit tester is not restricted + to testing classes. + It is just that object oriented code is easier to break + down and redesign for testing. + It is no accident that the fine grain testing style of unit + tests has arisen from the object community. +

    +

    + The test itself is minimal. + It first deletes any previous test file that may have + been left lying around. + Design decisions now come in thick and fast. + Our class is called Log + and takes the file path in the constructor. + We create a log and immediately send a message to + it using a method named + message(). + Sadly, original naming is not a desirable characteristic + of a software developer. +

    +

    + The smallest unit of a...er...unit test is the assertion. + Here we want to assert that the log file we just sent + a message to was indeed created. + UnitTestCase::assertTrue() + will send a pass event if the condition evaluates to + true and a fail event otherwise. + We can have a variety of different assertions and even more + if we extend our base test cases. +

    +

    + Here is the base list... + + + + + + + + + + + + + + + + + + + + + +
    assertTrue($x)Fail unless $x evaluates true
    assertFalse($x)Fail unless $x evaluates false
    assertNull($x)Fail unless $x is not set
    assertNotNull($x)Fail unless $x is set to something
    assertIsA($x, $t)Fail unless $x is the class or type $t
    assertNotA($x, $t)Fail unless $x is not the class or type $t
    assertEqual($x, $y)Fail unless $x == $y is true
    assertNotEqual($x, $y)Fail unless $x == $y is false
    assertWithinMargin($x, $y, $margin)Fail unless $x and $y are separated less than $margin
    assertOutsideMargin($x, $y, $margin)Fail unless $x and $y are sufficiently different
    assertIdentical($x, $y)Fail unless $x === $y for variables, $x == $y for objects of the same type
    assertNotIdentical($x, $y)Fail unless $x === $y is false, or two objects are unequal or different types
    assertReference($x, $y)Fail unless $x and $y are the same variable
    assertCopy($x, $y)Fail unless $x and $y are the different in any way
    assertSame($x, $y)Fail unless $x and $y are the same objects
    assertClone($x, $y)Fail unless $x and $y are identical, but separate objects
    assertPattern($p, $x)Fail unless the regex $p matches $x
    assertNoPattern($p, $x)Fail if the regex $p matches $x
    expectError($e)Triggers a fail if this error does not happen before the end of the test
    expectException($e)Triggers a fail if this exception is not thrown before the end of the test
    +

    +

    + We are now ready to execute our test script by pointing the + browser at it. + What happens? + It should crash... +

    + Fatal error: Failed opening required '../classes/log.php' (include_path='') in /home/marcus/projects/lastcraft/tutorial_tests/Log/tests/log_test.php on line 7 +
    + The reason is that we have not yet created log.php. +

    +

    + Hang on, that's silly! + You aren't going to build a test without creating any of the + code you are testing, surely...? +

    +
    +
    +

    + Co-inventor of + Extreme Programming, + Kent Beck, has come up with another manifesto. + The book is called + Test driven development + or TDD and raises unit testing to a senior position in design. + In a nutshell you write a small test first and + then only get this passing by writing code. + Any code. + Just get it working. +

    +

    + You write another test and get that passing. + What you will now have is some duplication and generally lousy + code. + You re-arrange, or "refactor", that code while the tests are passing. + This stops you breaking anything. + Once the code is as clean as possible you are ready to add more + functionality, which you don't do. + Instead you add another test for your feature and start the + cycle again. + Your functionality gets created by trying to pass the tests that + define it. +

    +

    + Think of it as an executable specification that you create just in time. +

    +

    + This is a radical approach and one that I feel is incomplete, + but it makes for a great way to explain a unit tester! + We happen to have a failing, not to say crashing, test right now so + let's write some code into log.php... + +]]> + This is the minimum to avoid a PHP fatal error. + We now get the response... +

    +

    TestOfLogging

    + Fail: testFirstLogMessagesCreatesFileIfNonexistent->True assertion failed.
    +
    1/1 test cases complete. + 0 passes, 1 fails and 0 exceptions.
    +
    + And "TestOfLogging" has failed. + SimpleTest uses class names by default to describe the tests, but + we can replace the name with our own... +function __construct() { + parent::__construct('Log test'); + } + + function testFirstLogMessagesCreatesFileIfNonexistent() { + @unlink(dirname(__FILE__) . '/../temp/test.log'); + $log = new Log(dirname(__FILE__) . '/../temp/test.log'); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists(dirname(__FILE__) . '/../temp/test.log')); + } +} +]]> + Giving... +
    +

    Log test

    + Fail: testFirstLogMessagesCreatesFileIfNonexistent->File created.
    +
    1/1 test cases complete. + 0 passes, 1 fails and 0 exceptions.
    +
    + If you want to change the test name, then you'd have to do it by + changing the reporter output, which we look at later. +

    +

    + To get the test passing we could just create the file in the + Log constructor. + This "faking it" technique is very useful for checking + that your tests work when the going gets tough. + This is especially so if you have had a run of test failures + and just want to confirm that you haven't just missed something + stupid. + We are not going that slow, so... + + var $path; + + function __construct($path) { + $this->path = $path; + } + + function message($message) { + $file = fopen($this->path, 'a'); + fwrite($file, $message . "\n"); + fclose($file); + } +} +?> +]]> + It took me no less than four failures to get to the next step. + I had not created the temporary directory, I had not made it + publicly writeable, I had one typo and I did not check in the + new directory to CVS (I found out later). + Any one of these could have kept me busy for several hours if + they had come to light later, but then that is what testing is for. +

    +

    + With the necessary fixes we get... +

    +

    Log test

    +
    1/1 test cases complete. + 1 passes, 0 fails and 0 exceptions.
    +
    + Success! +

    +

    + You may not like the rather minimal style of the display. + Passes are not shown by default because generally you do + not need more information when you actually understand what is + going on. + If you do not know what is going on then you should write another test. +

    +

    + OK, this is a little strict. + If you want to see the passes as well then you + can subclass the + HtmlReporter class + and attach that to the test instead. + Even I like the comfort factor sometimes. +

    +
    +
    +

    + There is a subtlety here. + We don't want the file created until we actually send + a message. + Rather than think about this too deeply we will just + assert it... + + $this->assertFalse(file_exists(dirname(__FILE__) . '/../temp/test.log')); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists(dirname(__FILE__) . '/../temp/test.log')); + + } +} +]]> + ...and find it already works... +

    +

    TestOfLogging

    +
    1/1 test cases complete. + 2 passes, 0 fails and 0 exceptions.
    +
    + Actually I knew it would. + I am putting this test in to confirm this partly for peace of mind, but + also to document the behaviour. + That little extra test line says more in this context than + a dozen lines of use case or a whole UML activity diagram. + That the test suite acts as a source of documentation is a pleasant + side effect of all these tests. +

    +

    + Should we clean up the temporary file at the end of the test? + I usually do this once I am finished with a test method + and it is working. + I don't want to check in code that leaves remnants of + test files lying around after a test. + I don't do it while I am writing the code, though. + I probably should, but sometimes I need to see what is + going on and there is that comfort thing again. +

    +

    + In a real life project we usually have more than one test case, + so we next have to look at + grouping tests into test suites. +

    +
    +
    + + Creating a new test case. + Test driven development in PHP. + Tests as documentation is one of many side effects. + + + + The JUnit FAQ + has plenty of useful testing advice. + + + Next is grouping test + cases together. + + + You will need the SimpleTest testing framework + for these examples. + + + + + software development, + php programming, + programming php, + software development tools, + php tutorial, + free php scripts, + architecture, + php resources, + mock objects, + junit, + php testing, + unit test, + automated php testing, + test cases tutorial, + explain unit test case, + unit test example, + unit test + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/form_testing_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/form_testing_documentation.xml new file mode 100644 index 0000000..d7ccceb --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/form_testing_documentation.xml @@ -0,0 +1,304 @@ + + + + SimpleTest documentation for testing HTML forms + +
    +

    + When a page is fetched by the WebTestCase + using get() or + post() the page content is + automatically parsed. + This results in any form controls that are inside <form> tags + being available from within the test case. + For example, if we have this snippet of HTML... +

    
    +    
    +    
    +
    +]]>
    + Which looks like this... +

    +

    +

    + + +
    +

    +

    + We can navigate to this code, via the + LastCraft + site, with the following test... + + function testDefaultValue() { + $this->get('http://www.lastcraft.com/form_testing_documentation.php'); + $this->assertField('a', 'A default'); + } +} +]]> + Immediately after loading the page all of the HTML controls are set at + their default values just as they would appear in the web browser. + The assertion tests that a HTML widget exists in the page with the + name "a" and that it is currently set to the value + "A default". + As usual, we could use a pattern expectation instead of a fixed + string. +get('http://www.lastcraft.com/form_testing_documentation.php'); + $this->assertField('a', new PatternExpectation('/default/')); + } +} +]]> + We could submit the form straight away, but first we'll change + the value of the text field and only then submit it... +get('http://www.my-site.com/'); + $this->assertField('a', 'A default'); + $this->setField('a', 'New value'); + $this->click('Go'); + } +} +]]> + Because we didn't specify a method attribute on the form tag, and + didn't specify an action either, the test case will follow + the usual browser behaviour of submitting the form data as a GET + request back to the same location. + In general SimpleTest tries to emulate typical browser behaviour as much as possible, + rather than attempting to catch any form of HTML omission. + This is because the target of the testing framework is the PHP application + logic, not syntax or other errors in the HTML code. + For HTML errors, other tools such as + HTMLTidy should be used, + or any of the HTML and CSS validators already out there. +

    +

    + If a field is not present in any form, or if an option is unavailable, + then WebTestCase::setField() will return + false. + For example, suppose we wish to verify that a "Superuser" + option is not present in this form... +

    Select type of user to add:
    +
    +]]>
    + Which looks like... +

    +

    +

    + Select type of user to add: + +
    +

    +

    + The following test will confirm it... + + $this->get('http://www.lastcraft.com/form_testing_documentation.php'); + $this->assertFalse($this->setField('type', 'Superuser')); + } +} +]]> + The current selection will not be changed if the new value is not an option. +

    +

    + Here is the full list of widgets currently supported... +

      +
    • Text fields, including hidden and password fields.
    • +
    • Submit buttons including the button tag, although not yet reset buttons
    • +
    • Text area. This includes text wrapping behaviour.
    • +
    • Checkboxes, including multiple checkboxes in the same form.
    • +
    • Drop down selections, including multiple selects.
    • +
    • Radio buttons.
    • +
    • Images.
    • +
    +

    +

    + The browser emulation offered by SimpleTest mimics + the actions which can be perform by a user on a + standard HTML page. Javascript is not supported, and + it's unlikely that support will be added any time + soon. +

    +

    + Of particular note is that the Javascript idiom of + passing form results by setting a hidden field cannot + be performed using the normal SimpleTest + commands. See below for a way to test such forms. +

    +
    +
    +

    + SimpleTest can cope with two types of multivalue controls: Multiple + selection drop downs, and multiple checkboxes with the same name + within a form. + The multivalue nature of these means that setting and testing + are slightly different. + Using checkboxes as an example... +

    
    +    Create privileges allowed:
    +    
    + Retrieve privileges allowed: +
    + Update privileges allowed: +
    + Destroy privileges allowed: +
    + + +]]>
    + Which renders as... +

    +

    +

    + Create privileges allowed: +
    + Retrieve privileges allowed: +
    + Update privileges allowed: +
    + Destroy privileges allowed: +
    + +
    +

    +

    + If we wish to disable all but the retrieval privileges and + submit this information we can do it like this... + + function testDisableNastyPrivileges() { + $this->get('http://www.lastcraft.com/form_testing_documentation.php'); + $this->assertField('crud', array('c', 'r', 'u', 'd')); + $this->setField('crud', array('r')); + $this->click('Enable Privileges'); + } +} +]]> + Instead of setting the field to a single value, we give it a list + of values. + We do the same when testing expected values. + We can then write other test code to confirm the effect of this, perhaps + by logging in as that user and attempting an update. +

    +
    +
    +

    + If you want to test a form which relies on javascript to set a hidden + field, you can't just call setField(). + The following code will not work: +// This does *not* work + $this->setField('a_hidden_field', '123'); + $this->clickSubmit('OK'); + } +} +]]> + Instead, you need to pass the additional form parameters to the + clickSubmit() method: +$this->clickSubmit('OK', array('a_hidden_field'=>'123')); + } + +} +]]> + Bear in mind that in doing this you're effectively stubbing out a + part of your software (the javascript code in the form), and + perhaps you might be better off using something like + Selenium to ensure a complete + test. +

    +
    +
    +

    + If you want to test a form handler, but have not yet written + or do not have access to the form itself, you can create a + form submission by hand. + + function testAttemptedHack() { + $this->post( + 'http://www.my-site.com/add_user.php', + array('type' => 'superuser')); + $this->assertNoText('user created'); + } +} +]]> + By adding data to the WebTestCase::post() + method, we are emulating a form submission. + You would normally only do this as a temporary expedient, or where + you are expecting a 3rd party to submit to a form. + The exception is when you want tests to protect you from + attempts to spoof your pages. +

    +
    +
    + + + Changing form values and successfully + Submitting a simple form + + + Handling widgets with multiple values + by setting lists. + + + Bypassing javascript to set a hidden field. + + + Raw posting when you don't have a button + to click. + + + + + SimpleTest project page on SourceForge. + + + SimpleTest download page on LastCraft. + + + The developer's API for SimpleTest + gives full detail on the classes and assertions available. + + + + + software development, + php programming for clients, + customer focused php, + software development tools, + acceptance testing framework, + free php scripts, + architecture, + php resources, + HTMLUnit, + JWebUnit, + php testing, + unit test resource, + web testing + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/gain_control_tutorial.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/gain_control_tutorial.xml new file mode 100644 index 0000000..f0bbc2c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/gain_control_tutorial.xml @@ -0,0 +1,290 @@ + + + + PHP unit testing tutorial - Isolating variables when testing + + +

    + In order to test a code module you need very tight control + of its environment. + If anything can vary behind the scenes, for example a + configuration file, then this could cause the tests + to fail unexpectedly. + This would not be a fair test of the code and could + cause you to spend fruitless hours examining code that + is actually working, rather than dealing with the configuration issue + that actually failed the test. + At the very least your test cases get more complicated in + taking account the possible variations. +

    +
    +
    +

    + There are often a lot of obvious variables that could affect + a unit test case, especially in the web development + environment in which PHP usually operates. + These include database set up, file permissions, network + resources and configuration amongst others. + The failure or misinstall of one of these components will + break the test suite. +

    +

    + Do we add tests to confirm these components are installed? + This is a good idea, but if you place them into code module + tests you will start to clutter you test code with detail + that is irrelavent to the immediate task. + They should be placed in their own test suite. +

    +

    + Another problem, though, is that our development machines + must have every system component installed to be able + to run the test suite. + Your tests run slower too. +

    +

    + When faced with this while coding we will often create wrapper + versions of classes that deal with these resources. + Ugly details of these resources are then coded once only. + I like to call these classes "gateway classes", + as they exist at the edges of the application, + the interface of your application with the rest of the + system. + These gateway classes are best simulated during testing + by fake versions. + These run faster and are often called + "Server Stubs", or in more generic form + "Mock Objects". + It is a great time saver to wrap and stub out such resources. +

    +

    + One often neglected external resource is time. +

    +

    + For example, to test a session time-out coders will often + temporarily set the session time limit to a small value, say two seconds, + and then do a sleep(3) + and assert that the session is now invalid. + That adds three seconds to your test suite and is usually + a lot of extra code making your session classes that maleable. + Far simpler is to have a way to suddenly advance the clock. + To control time. +

    +
    +
    +

    + We will again design our clock wrapper by first writing tests. + We add a clock test case to our tests/all_tests.php + test suite... +addTest(new TestOfLogging()); + $this->addTest(new TestOfClock()); + } +} +?> +]]> + Then we create the test case in the new file + tests/clock_test.php... + +class TestOfClock extends UnitTestCase { + function testClockTellsTime() { + $clock = new Clock(); + $this->assertEqual($clock->now(), time()); + } +} +?> +]]> + Our only test at the moment is that our new + Clock class acts + as a simple PHP time() + function substitute. + We will write the time shift functionality once we are green. + At the moment we are obviously not green... +

    +
    + Fatal error: Failed opening required '../classes/clock.php' (include_path='') in + /home/marcus/projects/lastcraft/tutorial_tests/tests/clock_test.php on line 2 +
    +
    + We create a classes/clock.php file... + +]]> + This regains our flow ready for coding. +
    +

    AllTests

    + Fail: TestOfClock -> testClockTellsTime -> [NULL: ] should be equal to [integer: 1050257362]
    +
    3/3 test cases complete. + 4 passes, 1 fails and 0 exceptions.
    +
    + This is now easy to fix... + + return time(); + } +} +]]> + And we are green... +
    +

    AllTests

    +
    3/3 test cases complete. + 5 passes, 0 fails and 0 exceptions.
    +
    + There is still a problem. +

    +

    + The clock could roll over during the assertion causing the + result to be out by one second. + The chances are small, but if there were a lot of timing tests + you would end up with a test suite that is erratic, severely + limiting its usefulness. + We will tackle this shortly and for now just jot it onto our + "to do" list. +

    +

    + The advancement test looks like this... +assertEqual($clock->now(), time()); + } + + function testClockAdvance() { + $clock = new Clock(); + $clock->advance(10); + $this->assertEqual($clock->now(), time() + 10); + } +} +]]> + The code to get to green is straight forward and just involves adding + a time offset. + + private $offset = 0; + + function now() { + return time() + $this->offset; + } + + function advance($offset) { + $this->offset += $offset; + } +} +]]> +

    +
    +
    +

    + Our all_tests.php file has some repetition we could + do without. + We have to manually add our test cases from each included + file. + It is possible to remove it, but use of the following requires care. + The TestSuite class has a + convenience method called addFile() + that takes a PHP file as a parameter. + This mechanism makes a note of all the classes, requires in the + file and then has a look at any newly created classes. + If they are descendents of SimpleTestCase + they are added as a new TestSuite. +

    +

    + Here is our refactored test suite using this method... + +require_once(dirname(__FILE__) . '/simpletest/autorun.php'); + +class AllTests extends TestSuite { + function AllTests() { + parent::__construct(); + $this->addFile('log_test.php'); + $this->addFile('clock_test.php'); + } +} +?> +]]> + The pitfalls of this are... +

      +
    1. + If the test file has already been included, + no new classes will be added to this group +
    2. +
    3. + If the test file has other classes that are + related to SimpleTestCase + then these will be added to the group test as well. +
    4. +
    + In practice neither of these turn out to be problems. + Test suites are usually a tree structure. + It's rare to need a test case in two places. + As for extra classes being included, they have to be + descendents of SimpleTestCase. + These are likely to be superclasses of helper code + for several other tests. + Simply marking them as abstract is + enough to stop them being run. +

    +

    + We should really fix the glitch with the possible + clock rollover so we'll + do this next. +

    +
    +
    + + Time is an often neglected variable in tests. + A clock class allows us to alter time. + Tidying the group test. + + + + The previous section is + grouping unit tests. + + + The next section is + subclassing test cases. + + + You will need + the SimpleTest unit tester + for the examples. + + + + + software development, + php programming, + programming php, + software development tools, + php tutorial, + free php scripts, + architecture, + php resources, + mock objects, + junit, + php testing, + unit test, + php testing + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/group_test_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/group_test_documentation.xml new file mode 100644 index 0000000..96c140a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/group_test_documentation.xml @@ -0,0 +1,332 @@ + + + + SimpleTest for PHP test suites + +
    +

    + To run test cases as part of a group, the test cases should really + be placed in files without the runner code... + +]]> + As many cases as needed can appear in a single file. + They should include any code they need, such as the library + being tested, but none of the simple test libraries. +

    +

    + If you have extended any test cases, you can include them + as well. In PHP 4... + + class MyFileTestCase extends UnitTestCase { + ... + } + SimpleTest::ignore('MyFileTestCase'); + + class FileTester extends MyFileTestCase { ... } + + class SocketTester extends UnitTestCase { ... } +?> +]]> + The FileTester class does + not contain any actual tests, but is a base class for other + test cases. + For this reason we use the + SimpleTestOptions::ignore() directive + to tell the upcoming group test to ignore it. + This directive can appear anywhere in the file and works + when a whole file of test cases is loaded (see below). +

    +

    + If you are using PHP 5, you do not need this special directive at all. + Simply mark any test cases that should not be run as abstract... +abstract class MyFileTestCase extends UnitTestCase { + ... +} + +class FileTester extends MyFileTestCase { ... } + +class SocketTester extends UnitTestCase { ... } +]]> +

    +

    + We will call this sample file_test.php. + Next we create a group test file, called say my_group_test.php. + You will think of a better name I am sure. +

    +

    + We will add the test file using a safe method... + + require_once('file_test.php'); + + $test = &new TestSuite('All file tests'); + $test->addTestCase(new FileTestCase()); + $test->run(new HtmlReporter()); +?> +]]> + This instantiates the test case before the test suite is + run. + This could get a little expensive with a large number of test + cases, and can be surprising behaviour. +

    +

    + The main problem is that for every test case + that we add we will have + to require_once() the test code + file and manually instantiate each and every test case. +

    +

    + We can save a lot of typing with... + + $test->addTestFile('file_test.php'); + $test->run(new HtmlReporter()); +?> +]]> + What happens here is that the TestSuite + class has done the require_once() + for us. + It then checks to see if any new test case classes + have been created by the new file and automatically adds + them to the group test. + Now all we have to do is add each new file. +

    +

    + No only that, but you can guarantee that the constructor is run + just before the first test method and, in PHP 5, the destructor + is run just after the last test method. +

    +

    + There are two things that could go wrong and which require care... +

      +
    1. + The file could already have been parsed by PHP, and so no + new classes will have been added. You should make + sure that the test cases are only included in this file + and no others (Note : with the new autorun + functionnality, this problem has now been solved). +
    2. +
    3. + New test case extension classes that get included will be + placed in the group test and run also. + You will need to add a SimpleTestOptions::ignore() + directive for these classes, or make sure that they are included + before the TestSuite::addTestFile() + line, or make sure that they are abstract classes. +
    4. +
    +

    +
    +
    +

    + The above method places all of the test cases into one large group. + For larger projects though this may not be flexible enough; you + may want to group the tests in all sorts of ways. +

    +

    + To get a more flexible group test we can subclass + TestSuite and then instantiate it as needed... + + class FileTestSuite extends TestSuite { + function FileTestSuite() { + $this->TestSuite('All file tests'); + $this->addTestFile('file_test.php'); + } + } +?> +]]> + This effectively names the test in the constructor and then + adds our test cases and a single group below. + Of course we can add more than one group at this point. + We can now invoke the tests from a separate runner file... + + $test = &new FileTestSuite(); + $test->run(new HtmlReporter()); +?> +]]> + ...or we can group them into even larger group tests. + We can even mix groups and test cases freely as long as + we are careful about double includes... + + $test = &new BigTestSuite('Big group'); + $test->addTestFile('file_test_suite.php'); + $test->addTestFile('some_test_case.php'); + $test->run(new HtmlReporter()); +?> +]]> + In the event of a double include, ony the first instance + of the test case will be run. +

    +

    + If we still wish to run the original group test, and we + don't want all of these little runner files, we can + put the test runner code around guard bars when we create + each group. +TestSuite('All file tests'); + $test->addTestFile('file_test.php'); + } + } + + if (! defined('RUNNER')) { + define('RUNNER', true); + $test = &new FileTestSuite(); + $test->run(new HtmlReporter()); + } +?> +]]> + This approach requires the guard to be set when including + the group test file, but this is still less hassle than + lots of separate runner files. + You include the same guard on the top level tests to make sure + that run() will run once only + from the top level script that has been invoked. + + define('RUNNER', true); + require_once('file_test_suite.php'); + + $test = &new BigTestSuite('Big group'); + $test->addTestCase(new FileTestSuite()); + $test->addTestCase(...); + $test->run(new HtmlReporter()); +?> +]]> + As with the normal test cases, a TestSuite can + be loaded with the TestSuite::addTestFile() method. + + $test->addTestFile('file_test_suite.php'); + $test->addTestFile(...); + $test->run(new HtmlReporter()); +?> +]]> +

    +
    +
    +

    + If you already have unit tests for your code or are extending external + classes that have tests, it is unlikely that all of the test cases + are in SimpleTest format. + Fortunately it is possible to incorporate test cases from other + unit testers directly into SimpleTest group tests. +

    +

    + Say we have the following + PhpUnit + test case in the file config_test.php... +class ConfigFileTest extends TestCase { + function ConfigFileTest() { + $this->TestCase('Config file test'); + } + + function testContents() { + $config = new ConfigFile('test.conf'); + $this->assertRegexp('/me/', $config->getValue('username')); + } +} +]]> + The group test can recognise this as long as we include + the appropriate adapter class before we add the test + file... + + require_once('simpletest/adapters/phpunit_test_case.php'); + + $test = &new TestSuite('All file tests'); + $test->addTestFile('config_test.php'); + $test->run(new HtmlReporter()); +?> +]]> + There are only two adapters, the other is for the + PEAR + 1.0 unit tester... + + require_once('simpletest/adapters/pear_test_case.php'); + + $test = &new TestSuite('All file tests'); + $test->addTestFile('some_pear_test_cases.php'); + $test->run(new HtmlReporter()); +?> +]]> + The PEAR test cases can be freely mixed with SimpleTest + ones even in the same test file, + but you cannot use SimpleTest assertions in the legacy + test case versions. + This is done as a check that you are not accidently making + your test cases completely dependent on SimpleTest. + You may want to do a PEAR release of your library for example, + which would mean shipping it with valid PEAR::PhpUnit test + cases. +

    +
    +
    + + + Different ways to group tests together. + + + Combining group tests into larger groups. + + + Integrating legacy test cases from other + types of PHPUnit. + + + + + SimpleTest project page on SourceForge. + + + SimpleTest download page on LastCraft. + + + + + php unit testing, test integration, documentation, marcus baker, simple test, + simpletest documentation, phpunit, pear + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/group_test_tutorial.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/group_test_tutorial.xml new file mode 100644 index 0000000..3dfd8ef --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/group_test_tutorial.xml @@ -0,0 +1,239 @@ + + + + + PHP unit testing tutorial - Grouping together unit + tests and examples of writing test cases + + + +

    + Next up we will fill in some blanks and create a test suite. +

    +
    +
    +

    + Adding another test can be as simple as adding another method + to a test case... +UnitTestCase('Log class test'); + } + function testCreatingNewFile() { + @unlink('../temp/test.log'); + $log = new Log('../temp/test.log'); + $this->assertFalse(file_exists('../temp/test.log'), 'Created before message'); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists('../temp/test.log'), 'File created'); + @unlink('../temp/test.log'); + } + function testAppendingToFile() { + @unlink('../temp/test.log'); + $log = new Log('../temp/test.log'); + $log->message('Test line 1'); + $messages = file('../temp/test.log'); + $this->assertWantedPattern('/Test line 1/', $messages[0]); + $log->message('Test line 2'); + $messages = file('../temp/test.log'); + $this->assertWantedPattern('/Test line 2/', $messages[1]); + @unlink('../temp/test.log'); + } +} +]]> + The assertWantedPattern() + test case method uses Perl style regular expressions for + matching. +

    +

    + All we are doing in this new test method is writing a line to a file and + reading it back twice over. + We simply want to confirm that the logger appends the + text rather than writing over the old file. + A little pedantic, but hey, it's a tutorial! +

    +

    + In fact this unit test actually passes straight away... +

    +

    Log class test

    +
    1/1 test cases complete. + 4 passes and 0 fails.
    +
    + The trouble is there is already a lot of repetition here, + we have to delete the test file before and after every test. + With outrageous plagarism from JUnit, + SimpleTest has setUp() and + tearDown() methods + which are run before and after every test respectively. + File deletion is common to all the test methods so we + should move that operation there. +

    +

    + Our tests are green so we can refactor... +UnitTestCase('Log class test'); + } + function setUp() { + @unlink('../temp/test.log'); + } + function tearDown() { + @unlink('../temp/test.log'); + } + function testCreatingNewFile() { + $log = new Log('../temp/test.log'); + $this->assertFalse(file_exists('../temp/test.log'), 'Created before message'); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists('../temp/test.log'), 'File created'); + } + function testAppendingToFile() { + $log = new Log('../temp/test.log'); + $log->message('Test line 1'); + $messages = file('../temp/test.log'); + $this->assertWantedPattern('/Test line 1/', $messages[0]); + $log->message('Test line 2'); + $messages = file('../temp/test.log'); + $this->assertWantedPattern('/Test line 2/', $messages[1]); + } +} +]]> + The test stays green. + We can add non-test methods to the test case as long as the method + name does not start with the string "test". + Only the methods that start "test" are run. + This allows further optional refactoring... +UnitTestCase('Log class test'); + } + function setUp() { + @unlink('../temp/test.log'); + } + function tearDown() { + @unlink('../temp/test.log'); + } + function getFileLine($filename, $index) { + $messages = file($filename); + return $messages[$index]; + } + function testCreatingNewFile() { + $log = new Log('../temp/test.log'); + $this->assertFalse(file_exists('../temp/test.log'), 'Created before message'); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists('../temp/test.log'), 'File created'); + } + function testAppendingToFile() { + $log = new Log('../temp/test.log'); + $log->message('Test line 1'); + $this->assertWantedPattern('/Test line 1/', $this->getFileLine('../temp/test.log', 0)); + $log->message('Test line 2'); + $this->assertWantedPattern('/Test line 2/', $this->getFileLine('../temp/test.log', 1)); + } +} +]]> + It is a matter of taste whether you prefer this version + to the previous one. There is a little more code, but + the logic of the test is clearer. +

    +
    +
    +

    + A test case does not function alone for very long. + When coding for real we usually want to run as many tests as + quickly and as often as we can. + This means grouping them together into test suites that + could easily include every test in the application. +

    +

    + Firstly we have to clean out the test running code from + our existing test case... + +require_once('../classes/log.php'); + +class TestOfLogging extends UnitTestCase { + ... +} +?> +]]> + We no longer need the SIMPLE_TEST + constant. + Next we create a group test called all_tests.php + in the tests folder... +addTestCase(new TestOfLogging()); +$test->run(new HtmlReporter()); +?> +]]> + We hardly notice the difference when things work... +

    +

    All tests

    +
    1/1 test cases complete. + 4 passes and 0 fails.
    +
    + Group tests add to the test case count. + Adding new test cases is very straight forward. + Simply include the test cases file and add any contained test + cases individually. + You can also nest group tests within other group tests + (although you should avoid loops). +

    +

    + In the next page + we will add these more quickly. +

    +
    +
    + + + Adding another test to the test case + and refactoring. + + + The crude way to group unit tests. + + + + + Next is controlling + how the class under test interacts with the rest + of the system. + + + Previous is the creation + of a first test. + + + You need SimpleTest to run these examples. + + + + + software development, + php programming, + programming in php, + test first, + software development tools, + php tutorial, + free php scripts, + architecture, + php resources, + mock objects, + junit, + php testing, + unit test, + phpunit, + PHP unit testing + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/heartbeat.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/heartbeat.xml new file mode 100755 index 0000000..fc702c6 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/heartbeat.xml @@ -0,0 +1,57 @@ + + + + Heartbeat : how is SimpleTest doing ? + + + + + +

    + Want to know check how healthy SimpleTest's codebase is? + Well, do enjoy the tests results, commit history and last commits... +

    +
    +
    +

    +
    +
    +

    +
    +
    +

    +
    +
    + + + Current tests results + + + Commits per week + + + Last commits + + + + + software development, + computer programmer, + php programming, + programming php, + software development company, + software development uk, + php tutorial, + bespoke software development uk, + corporate web development, + architecture, + freelancer, + php resources, + wordtracker, + web marketing, + serach engines, + web positioning, + internet marketing + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/ideas.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/ideas.xml new file mode 100644 index 0000000..2eabee2 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/ideas.xml @@ -0,0 +1,188 @@ + + + + Ideas for Google Summer of Code Projects + + +

    + Most of the stuff on the SimpleTest2 road map is incremental. + Ideas for Google Summer of Code projects are of another kind. + There're here to unleash and promote new crazy ideas to the project + and to PHP's testing universe! +

    +

    + Of course this list is not exhaustive: + if you have another itch to scratch, feel free + to add your wishes on the mailing-list. +

    +
    + Careful though : currently the SimpleTest project is taking part under the + PHP umbrella. + So handle with care! +
    +
    +
    +
    +
    + Enable JavaScript inside SimpleTest browser. +
    +
    + This would be a MASSIVE feature to have. + We're probably talking about integrating + a C-based or Java-based JavaScript engine. + The Java folks have + HttpUnit + already : some stuff could be derived from there. + Demonstrating the feasibility alone would be a real + step forward. The hard work is the DOM emulation. +
    +
    + Add layer testing. +
    +
    + When using an MVC framework is to support + bypassing the webserver, and interacting with the framework at + the level of Request and Response objects. + It would be nice to see if we could develop an interface standard for + the HTTP layer which would allow SimpleTest to work cleanly with + different frameworks. On the other hand, maybe there already is enough + of an interface in the standard PHP superglobals. + Symfony with its lime test framework works in exactly this manner. +
    +
    + Make parsers switchable and change the HTML Parser. +
    +
    + Replace the html parser with + tidylib. + It's super-fast compared to parsing in PHP. + If there are worries about adding a dependency, + then maybe make it an optional plug-in instead. +
    +
    + Web Form Fuzzer. +
    +
    + Fuzz testing is a software testing technique that provides random data + ("fuzz") to the inputs of a program. If the program fails (for + example, by crashing, or by failing built-in code assertions), the + defects can be noted. +
    + A Web Form Fuzzer would be built on top of the basic WebTestCase api, + enabling developers to point a test case at a target URL and have it + introspect form inputs and post actions. The fuzzer would generate + random data strings, populate the input fields, and repeatedly post + the forms, looking for errors, validation responses, and HTTP status + codes that reflect how the site handled a combination of well-formed + and malformed input. This would have the potential to completely wreck + some websites, but would be great for testing the robustness and edge + case behavior of CMS's and other online administration tools. +
    +
    +
    +
    +
    +
    + Mutation test coverage. +
    +
    +
    +
    +
    +
    + Standalone notifying tool +
    +
    + Based on rules, this notifying tool detects a file change + within a particular directory (maybe with + php-inotifytools : + such a monitor could learn how your code works by observing it being tested. + For example, call graphing via static analysis might work, as would + running Xdebug code coverage while running the tests and monitoring + the code executed after each test method, as would some sort of + DBG-derived client that received notifications from Xdebug while + the code was being executed like a debugging client. + We'd have "rings" of functionality. The first + ring is the tests that exercises the particular method/function + you're working with by a direct call. + Then start working rings out based on how far away the test method + is when that code is called. + These rings would then be executed on a save, starting with the inner + most. Any failure would stop further tests cause a system alert to + something like Growl or Snarl or any Reporter that you drop in. +
    +
    + PDT (Eclipse) : implement PHPUnit hooks +
    +
    + There are somme hooks inside PDT implemented for PHPUnit. + But it seems there're unused since PHPUnit changed too much + recently. SimpleTest could use them as well : + the SimpleTest Eclipse plugin could then become + the PDT Test plugin while fixing those hooks. +
    +
    + PDT (Eclipse) : debug while unit testing +
    +
    + The GUI for Eclipse recently caught up with PDT : debugging while + running a SimpleTest test case is finally doable. +
    +
    + Creative IDE integration +
    +
    + For example, running a subset of tests after each edit. +
    +
    + Firefox plugin to create SimpleTest file on the fly. +
    +
    + The same way Selenium IDE currently works. +
    +
    + Komodo integration. +
    +
    + Integration with monitoring tools such as Nagios. +
    +
    +
    +
    + + + Browser-based + + + ... and Unit testing-related ideas for GSOC2008. + + + Also of interest, + integration with third-party tools. + + + + + Google Summer of Code 2008. + + + + + software development, + computer programmer, + php programming, + programming php, + software development company, + software development uk, + php tutorial, + bespoke software development uk, + corporate web development, + architecture, + integration, + ide integration, + google summer of code, + mutation test coverage, + javascript + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/improving_design_tutorial.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/improving_design_tutorial.xml new file mode 100644 index 0000000..fc59981 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/improving_design_tutorial.xml @@ -0,0 +1,195 @@ + + + + + PHP unit testing tutorial - Top down design + test first with mock objects + + +
    +

    + I lied. +

    +

    + I haven't created a writer test at all, only the + FileWriter interface that I showed + earlier. + In fact I'll go one step further away from a finished article and assume + only an abstract writer in classes/writer.php... +Writer { + + function Writer() { + } + + function write($message) { + } +} +?> +]]> + The corresponding test changes are... + +Mock::generate('Writer'); + +class TestOfLogging extends UnitTestCase { + function TestOfLogging() { + $this->UnitTestCase('Log class test'); + } + function testWriting() { + $clock = &new MockClock($this); + $clock->setReturnValue('now', 'Timestamp'); + $writer = &new MockWriter($this); + $writer->expectOnce('write', array('[Timestamp] Test line')); + $log = &new Log($writer); + $log->message('Test line', &$clock); + $writer->tally(); + } +} +?> +]]> + In order to use the logging class we would need to code a file + writer or other sort of writer, but at the moment we are only + testing and so we do not yet need it. + So, in other words, using mocks we can defer the creation of + lower level objects until we feel like it. + Not only can we design top down, but we can test top down too. +

    +
    +
    +

    + Imagine for the moment that we had started the logging class + from another direction. + Pretend that we had coded just enough of the Log + to realise we needed a Writer somehow. + How would we have included it? +

    +

    + Well, inheriting from the writer would not have allowed us to mock it + from the testing point of view. + From the design point of view we would have been restricted to + just one writer without multiple inheritence. +

    +

    + Creating the writer internally, rather than passing it in the constructor, + by choosing a class name is possible, but we would have less control + of the mock object set up. + From the design point of view it would have been nearly impossible + to pass parameters to the writer in all the different formats ever needed. + You would have to have the writer restricted to say a hash or complicated + string describing the target details. + Needlessly complicated at best. +

    +

    + Using a factory method to create the writer internally would be + possible, but would mean subclassing it for testing so that the factory + method could be replaced with one returning a mock. + More work from the test point of view, although still possible. + From the design point of view it would mean a new logger subclass + for every type of writer that we want to use. + This is called a parallel class hiearchy and obviously involves + duplication. + Yuk. +

    +

    + At the other extreme, passing or creating the writer on every + message would have been repetitive and reduced the + Log class code to a single + method, a sure sign that the whole class has become redundant. +

    +

    + This tension between ease of testing and avoiding repetition + has allowed us to find a flexible and clean design. + Remember our brief yearning for multiple inheritence? + We have replaced it with polymorphism (lots of writers) and separated the + logging hierachy from the writing hierarchy. + We connect the two through this simpler Log + by aggregation. + This trick is actually a design pattern called a "Bridge". +

    +

    + So we have been pushed by test code (we haven't written much else yet) + into a design pattern. + Think about this for a second. + Tests improve code quality, certainly in my case, but this is + something far more profound and far more powerful. +

    +

    + Testing has improved the design. +

    +
    +
    +

    + Creating a mock object is as easy as creating the interface in text + form. + If you have UML or other tools that create these interfaces for you, + then you have an even more flexible route to quickly generate + test objects. + Even if you do not, you can switch from white board drawing, to + writing a test case, to writing a mock, to generating an interface + which takes us back to the whiteboard drawing again, fairly easily. + Like refactoring, design, code and test become unified. +

    +

    + Because mock objects work top down they can be bought into the design + more quickly than normal refactoring, which requires at least + partially working code before it kicks in. + This means that the testing code interacts with the design faster + and this means that the design quality goes up sooner. +

    +

    + A unit tester is a coding tool. + A unit tester with mock objects is a design tool. +

    +
    +
    + + + Mock now, code later. + + + We derive the bridge pattern. + + + Designing and testing hand in hand. + + + + + This tutorial follows Boundary classes. + + + You will need the SimpleTest testing framework + to try these examples. + + + For more mock object discussion see the + Extreme Tuesday Wiki + or the + C2 Wiki + + + + + software development, + php programming tutorial, + programming php test cases, + software development tools, + php tutorial, + free php code, + architecture, + php examples, + mock object examples, + junit style testing, + php testing frameworks, + unit test, + mock objects in PHP, + php testing + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/index.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/index.xml new file mode 100644 index 0000000..730cafb --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/index.xml @@ -0,0 +1,220 @@ + + + + + The Last Craft? Web developer tutorials on PHP, Extreme programming + and Object Oriented development + + + +

    + This site focuses mostly on developing with lightweight web technologies such as + PHP, + especially when applied with agile methodologies such as + XP. + No guarantee of quality is given or even intended. + It is hoped only that what you find gives you ideas and enthusiasm + from a fellow computer programmer. +

    +

    + I've been a little busy of late with children (two versions). + They take quite a chunk of your professional life and 100% of + what's left! + Hopefully the projects below should start to get back on track. +

    +
    +
    +

    + My latest project is Cgreen. + It's a C unit tester. + There are a couple of C unit testing tools out there already of course. + What makes Cgreen different is that it is pure C99, includes + a tutorial right here and has facilities for creating mock functions. + Mock functions should lead to more decoupled C code if Mock objects are + anything to go by. + It's alpha status right now until I get feedback from other users. + So if you want to be influential, try it out right now. + The project has been mostly funded by Wordtracker, + for which I am very grateful. +

    +

    + Along with Jon Ramsey, I am a founder of + PHP London, a PHP user + group not surprisingly based in London. + It's going well. + The networking meetings take place on the first Thursday of every month + at a pub. +

    +

    + In addition the group organises other events that include the 2nd + London UK PHP Conference. + This is a one day event on Friday the 23rd of February 2007 and costs only + fifty quid. +

    +

    + The SimpleTest PHP unit tester + is available for download from your nearest + SourceForge. + It is a PHP unit test and web test framework. + Users of JUnit will be + familiar with most of the interface. + The JWebUnit + style functionality is more complete now. + It has support for SSL, forms, frames, proxies and basic authentication. + The current CVS code should become the 1.0.1 release real soon now and + includes file upload and many small improvements. + The idea is that common but fiddly PHP tasks, such as logging into a site, + can be tested easily. +

    +

    + My most neglected project right now is a requirents testing + and sign-off tool called + Arbiter. + It's actually best described in this + Sitepoint thread, + but basically think of it as a document driven FIT for small + web projects only. + The project is currently stalled due the birth of children and + writing projects. +

    +

    + Also on the subject of open source, Wordtracker + have kindly agreed to publish some of their in house tools. + A Wordtracker spin off is fakeMail. + Testing applications that send e-mails can be a right royal pain because + of all of the infrastructure involved. + You will likely need an SMPT gateway that talks to a POP client that + you can read the queue from. + That's a lot of set up. + Fakemail acts as an SMTP gateway on any port + you define. + When you send it a mail it simply copies that mail to the local file + system in whatever directory you want. + You then just have to look at the local file. + It means that you must be able to configure your application to use + a port other than 25, but that's not usualy difficult. +

    +
    +
    +

    + A craft is defined as... +

    + Art or skill; dexterity in particular manual employment; + hence, the occupation or employment itself; manual art; a + trade. +
    +

    +

    + If you lose a screw or clasp from a hand made jewellery box it is hopeless to + try to find a replacement. Nothing else is quite the same and the mechanism will + fail to work. It may even cause more damage when applied. You need to find the original maker + or someone of the same skill to make you another. + Sound like software? + Yet mechanical parts today are interchangeable. +

    +

    + Writing software has resisted mass production. + As soon as a part of it becomes + routine it can be automated. + Once it is you don't need a programmer any more. + Routine programming jobs no longer exist. + All that is left is the unsolved problems that require + a higher level of skill in constructing their solutions. +

    +

    + This dependency on the ability of the artisan, combined with nothing quite fitting + together properly, is what gives software the pre-industrial feel. +

    +
    +
    +

    + The panel at the top is supposed to resemble a standard office index card. + The way it is marked out is called a + CRC card. + It stands for Classes, Responsibilities and + Collaborations and is the cheapest software development tool you + can find. + You really do buy a pack of cards. +

    +

    + The role is written at the top and would often be just a class + name. + The left side is the object's responsibilities and on the + right collaborations (within the page I have treated these as + internal links and external links repectively). + A group of developers can point at, discuss and discard cards + in the heat of design session. + It makes it especially difficult for only one person to take charge + of a discussion in the way you can with a UML tool or notepad. + Try scribbling out five cards before someone gets a look in. +

    +

    + Now nothing beats a whiteboard for collaboration, but if the level + of detail is getting too high and you want a temporary record, + give the CRC cards a try. +

    +
    +
    + + + Projects under development. + All free and open source software. + + + Why Last Craft? + Odd name isn't it? + + + Why this index card at the top? + + + + + SimpleTest is a PHP unit test framework. + + + My article on TDD + + + My article on the + Registry Pattern. + + + Site E-mail is + SpamCop + filtered which I cannot recommend enough. + + + + + software development, + computer programmer, + php programming, + programming php, + software development tools, + software development company, + php tutorial, + free php scripts, + bespoke software development uk, + corporate web development, + architecture, + php resources, + wordtracker, + xslt, + java, + bug tracker, + bug reporting, + unit test, + php testing, + test cases tutorial, + explain unit test case, + unit test example, + xml + + + A web site of software development tutorials and examples with an + emphasis on web programming, testing, agile methodology and PHP + development + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/intro.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/intro.xml new file mode 100644 index 0000000..87882c4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/intro.xml @@ -0,0 +1,54 @@ + + + + + Summary of SimpleTest including it's Java tool roots. + + + +

    + The SimpleTest PHP unit tester is in + the 1.0.1 release cycle and is available for download from your nearest SourceForge. +

    +

    + It is a PHP unit test, mock objects and web test framework. + Users of JUnit will be + familiar with most of the interface. + For web testing a small web browser is included, similar to + JWebUnit. + It has support for SSL, frames, proxies, basic authentication, redirects, + and the standard HTML form controls. + The idea is that common but fiddly PHP tasks, such as logging into a site, + can be tested easily as calling click(). +

    +

    + There is currently no support for JavaScript in the web tester. + For testing these elements we recommend + Selenium. +

    +
    +
    + + + software development, + computer programmer, + php programming, + programming php, + software development tools, + software development company, + php tutorial, + free php scripts, + bespoke software development uk, + corporate web development, + architecture, + php resources, + unit test, + php testing, + test cases tutorial, + explain unit test case, + unit test example, + xml + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/mock_objects_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/mock_objects_documentation.xml new file mode 100644 index 0000000..96f39a5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/mock_objects_documentation.xml @@ -0,0 +1,708 @@ + + + + SimpleTest for PHP mock objects documentation + +
    +

    + Mock objects have two roles during a test case: actor and critic. +

    +

    + The actor behaviour is to simulate objects that are difficult to + set up or time consuming to set up for a test. + The classic example is a database connection. + Setting up a test database at the start of each test would slow + testing to a crawl and would require the installation of the + database engine and test data on the test machine. + If we can simulate the connection and return data of our + choosing we not only win on the pragmatics of testing, but can + also feed our code spurious data to see how it responds. + We can simulate databases being down or other extremes + without having to create a broken database for real. + In other words, we get greater control of the test environment. +

    +

    + If mock objects only behaved as actors they would simply be + known as server stubs. + This was originally a pattern named by Robert Binder (Testing + object-oriented systems: models, patterns, and tools, + Addison-Wesley) in 1999. +

    +

    + A server stub is a simulation of an object or component. + It should exactly replace a component in a system for test + or prototyping purposes, but remain lightweight. + This allows tests to run more quickly, or if the simulated + class has not been written, to run at all. +

    +

    + However, the mock objects not only play a part (by supplying chosen + return values on demand) they are also sensitive to the + messages sent to them (via expectations). + By setting expected parameters for a method call they act + as a guard that the calls upon them are made correctly. + If expectations are not met they save us the effort of + writing a failed test assertion by performing that duty on our + behalf. +

    +

    + In the case of an imaginary database connection they can + test that the query, say SQL, was correctly formed by + the object that is using the connection. + Set them up with fairly tight expectations and you will + hardly need manual assertions at all. +

    +
    +
    +

    + In the same way that we create server stubs, all we need is an + existing class, say a database connection that looks like this... +class DatabaseConnection { + function DatabaseConnection() { + } + + function query() { + } + + function selectQuery() { + } +} +]]> + The class does not need to have been implemented yet. + To create a mock version of the class we need to include the + mock object library and run the generator... +require_once('simpletest/unit_tester.php'); +require_once('simpletest/mock_objects.php'); +require_once('database_connection.php'); + +Mock::generate('DatabaseConnection'); +]]> + This generates a clone class called + MockDatabaseConnection. + We can now create instances of the new class within + our test case... + +class MyTestCase extends UnitTestCase { + + function testSomething() { + $connection = &new MockDatabaseConnection(); + } +} +]]> + Unlike the generated stubs the mock constructor needs a reference + to the test case so that it can dispatch passes and failures while + checking its expectations. + This means that mock objects can only be used within test cases. + Despite this their extra power means that stubs are hardly ever used + if mocks are available. +

    +

    +

    Mocks as actors

    +

    +

    + The mock version of a class has all the methods of the original, + so that operations like + query()]]> are still + legal. + The return value will be null, + but we can change that with... +$connection->setReturnValue('query', 37) +]]> + Now every time we call + query()]]> we get + the result of 37. + We can set the return value to anything, say a hash of + imaginary database results or a list of persistent objects. + Parameters are irrelevant here, we always get the same + values back each time once they have been set up this way. + That may not sound like a convincing replica of a + database connection, but for the half a dozen lines of + a test method it is usually all you need. +

    +

    + We can also add extra methods to the mock when generating it + and choose our own class name... +Mock::generate('DatabaseConnection', 'MyMockDatabaseConnection', array('setOptions')); +]]> + Here the mock will behave as if the setOptions() + existed in the original class. + This is handy if a class has used the PHP overload() + mechanism to add dynamic methods. + You can create a special mock to simulate this situation. +

    +

    + Things aren't always that simple though. + One common problem is iterators, where constantly returning + the same value could cause an endless loop in the object + being tested. + For these we need to set up sequences of values. + Let's say we have a simple iterator that looks like this... + + This is about the simplest iterator you could have. + Assuming that this iterator only returns text until it + reaches the end, when it returns false, we can simulate it + with... + + $iterator = &new MockIterator(); + $iterator->setReturnValue('next', false); + $iterator->setReturnValueAt(0, 'next', 'First string'); + $iterator->setReturnValueAt(1, 'next', 'Second string'); + ... + } +} +]]> + When next() is called on the + mock iterator it will first return "First string", + on the second call "Second string" will be returned + and on any other call false will + be returned. + The sequenced return values take precedence over the constant + return value. + The constant one is a kind of default if you like. +

    +

    + Another tricky situation is an overloaded + get() operation. + An example of this is an information holder with name/value pairs. + Say we have a configuration class like... + + This is a classic situation for using mock objects as + actual configuration will vary from machine to machine, + hardly helping the reliability of our tests if we use it + directly. + The problem though is that all the data comes through the + getValue() method and yet + we want different results for different keys. + Luckily the mocks have a filter system... +$config = &new MockConfiguration(); +$config->setReturnValue('getValue', 'primary', array('db_host')); +$config->setReturnValue('getValue', 'admin', array('db_user')); +$config->setReturnValue('getValue', 'secret', array('db_password')); +]]> + The extra parameter is a list of arguments to attempt + to match. + In this case we are trying to match only one argument which + is the look up key. + Now when the mock object has the + getValue() method invoked + like this... +getValue('db_user') +]]> + ...it will return "admin". + It finds this by attempting to match the calling arguments + to its list of returns one after another until + a complete match is found. +

    +

    + You can set a default argument argument like so... + +$config->setReturnValue('getValue', false, array('*')); +]]> + This is not the same as setting the return value without + any argument requirements like this... + +$config->setReturnValue('getValue', false); +]]> + In the first case it will accept any single argument, + but exactly one is required. + In the second case any number of arguments will do and + it acts as a catchall after all other matches. + Note that if we add further single parameter options after + the wildcard in the first case, they will be ignored as the wildcard + will match first. + With complex parameter lists the ordering could be important + or else desired matches could be masked by earlier wildcard + ones. + Declare the most specific matches first if you are not sure. +

    +

    + There are times when you want a specific object to be + dished out by the mock rather than a copy. + The PHP4 copy semantics force us to use a different method + for this. + You might be simulating a container for example... + + In this case you can set a reference into the mock's + return list... + +$vector = &new MockVector(); +$vector->setReturnReference('get', $thing, array(12)); +]]> + With this arrangement you know that every time + get(12)]]> is + called it will return the same + $thing each time. + This is compatible with PHP5 as well. +

    +

    + These three factors, timing, parameters and whether to copy, + can be combined orthogonally. + For example... + +$complex->setReturnReferenceAt(3, 'get', $stuff, array('*', 1)); +]]> + This will return the $stuff only on the third + call and only if two parameters were set the second of + which must be the integer 1. + That should cover most simple prototyping situations. +

    +

    + A final tricky case is one object creating another, known + as a factory pattern. + Suppose that on a successful query to our imaginary + database, a result set is returned as an iterator with + each call to next() giving + one row until false. + This sounds like a simulation nightmare, but in fact it can all + be mocked using the mechanics above. +

    +

    + Here's how... + + $result = &new MockResultIterator(); + $result->setReturnValue('next', false); + $result->setReturnValueAt(0, 'next', array(1, 'tom')); + $result->setReturnValueAt(1, 'next', array(3, 'dick')); + $result->setReturnValueAt(2, 'next', array(6, 'harry')); + + $connection = &new MockDatabaseConnection(); + $connection->setReturnValue('query', false); + $connection->setReturnReference( + 'query', + $result, + array('select id, name from users')); + + $finder = &new UserFinder($connection); + $this->assertIdentical( + $finder->findNames(), + array('tom', 'dick', 'harry')); + } +} +]]> + Now only if our + $connection is called with the correct + query() will the + $result be returned that is + itself exhausted after the third call to next(). + This should be enough + information for our UserFinder class, + the class actually + being tested here, to come up with goods. + A very precise test and not a real database in sight. +

    +
    +
    +

    + Although the server stubs approach insulates your tests from + real world disruption, it is only half the benefit. + You can have the class under test receiving the required + messages, but is your new class sending correct ones? + Testing this can get messy without a mock objects library. +

    +

    + By way of example, suppose we have a + SessionPool class that we + want to add logging to. + Rather than grow the original class into something more + complicated, we want to add this behaviour with a decorator (GOF). + The SessionPool code currently looks + like this... +class SessionPool { + function SessionPool() { + ... + } + + function &findSession($cookie) { + ... + } + ... +} + +class Session { + ... +} +]]> + While our logging code looks like this... + +class Log { + function Log() { + ... + } + + function message() { + ... + } +} + +class LoggingSessionPool { + function LoggingSessionPool(&$session_pool, &$log) { + ... + } + + function &findSession($cookie) { + ... + } + ... +} +]]> + Out of all of this, the only class we want to test here + is the LoggingSessionPool. + In particular we would like to check that the + findSession() method is + called with the correct session ID in the cookie and that + it sent the message "Starting session $cookie" + to the logger. +

    +

    + Despite the fact that we are testing only a few lines of + production code, here is what we would have to do in a + conventional test case: +

      +
    1. Create a log object.
    2. +
    3. Set a directory to place the log file.
    4. +
    5. Set the directory permissions so we can write the log.
    6. +
    7. Create a SessionPool object.
    8. +
    9. Hand start a session, which probably does lot's of things.
    10. +
    11. Invoke findSession().
    12. +
    13. Read the new Session ID (hope there is an accessor!).
    14. +
    15. Raise a test assertion to confirm that the ID matches the cookie.
    16. +
    17. Read the last line of the log file.
    18. +
    19. Pattern match out the extra logging timestamps, etc.
    20. +
    21. Assert that the session message is contained in the text.
    22. +
    + It is hardly surprising that developers hate writing tests + when they are this much drudgery. + To make things worse, every time the logging format changes or + the method of creating new sessions changes, we have to rewrite + parts of this test even though this test does not officially + test those parts of the system. + We are creating headaches for the writers of these other classes. +

    +

    + Instead, here is the complete test method using mock object magic... + + $session = &new MockSession(); + $pool = &new MockSessionPool(); + $pool->setReturnReference('findSession', $session); + $pool->expectOnce('findSession', array('abc')); + + $log = &new MockLog(); + $log->expectOnce('message', array('Starting session abc')); + + $logging_pool = &new LoggingSessionPool($pool, $log); + $this->assertReference($logging_pool->findSession('abc'), $session); + } +} +]]> + We start by creating a dummy session. + We don't have to be too fussy about this as the check + for which session we want is done elsewhere. + We only need to check that it was the same one that came + from the session pool. +

    +

    + findSession() is a factory + method the simulation of which is described above. + The point of departure comes with the first + expectOnce() call. + This line states that whenever + findSession() is invoked on the + mock, it will test the incoming arguments. + If it receives the single argument of a string "abc" + then a test pass is sent to the unit tester, otherwise a fail is + generated. + This was the part where we checked that the right session was asked for. + The argument list follows the same format as the one for setting + return values. + You can have wildcards and sequences and the order of + evaluation is the same. +

    +

    + We use the same pattern to set up the mock logger. + We tell it that it should have + message() invoked + once only with the argument "Starting session abc". + By testing the calling arguments, rather than the logger output, + we insulate the test from any display changes in the logger. +

    +

    + We start to run our tests when we create the new + LoggingSessionPool and feed + it our preset mock objects. + Everything is now under our control. +

    +

    + This is still quite a bit of test code, but the code is very + strict. + If it still seems rather daunting there is a lot less of it + than if we tried this without mocks and this particular test, + interactions rather than output, is always more work to set + up. + More often you will be testing more complex situations without + needing this level or precision. + Also some of this can be refactored into a test case + setUp() method. +

    +

    + Here is the full list of expectations you can set on a mock object + in SimpleTest... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ExpectationNeeds tally()
    expect($method, $args)No
    expectAt($timing, $method, $args)No
    expectCallCount($method, $count)Yes
    expectMaximumCallCount($method, $count)No
    expectMinimumCallCount($method, $count)Yes
    expectNever($method)No
    expectOnce($method, $args)Yes
    expectAtLeastOnce($method, $args)Yes
    + Where the parameters are... +

    +
    $method
    +
    The method name, as a string, to apply the condition to.
    +
    $args
    +
    + The arguments as a list. Wildcards can be included in the same + manner as for setReturn(). + This argument is optional for expectOnce() + and expectAtLeastOnce(). +
    +
    $timing
    +
    + The only point in time to test the condition. + The first call starts at zero. +
    +
    $count
    +
    The number of calls expected.
    +
    + The method expectMaximumCallCount() + is slightly different in that it will only ever generate a failure. + It is silent if the limit is never reached. +

    +

    + Also if you have juste one call in your test, make sure you're using + expectOnce.
    + Using $mocked->expectAt(0, 'method', 'args); + on its own will not be catched : + checking the arguments and the overall call count + are currently independant. +

    +

    + Like the assertions within test cases, all of the expectations + can take a message override as an extra parameter. + Also the original failure message can be embedded in the output + as "%s". +

    +
    +
    +

    + There are three approaches to creating mocks including the one + that SimpleTest employs. + Coding them by hand using a base class, generating them to + a file and dynamically generating them on the fly. +

    +

    + Mock objects generated with SimpleTest + are dynamic. + They are created at run time in memory, using + eval(), rather than written + out to a file. + This makes the mocks easy to create, a one liner, + especially compared with hand + crafting them in a parallel class hierarchy. + The problem is that the behaviour is usually set up in the tests + themselves. + If the original objects change the mock versions + that the tests rely on can get out of sync. + This can happen with the parallel hierarchy approach as well, + but is far more quickly detected. +

    +

    + The solution, of course, is to add some real integration + tests. + You don't need very many and the convenience gained + from the mocks more than outweighs the small amount of + extra testing. + You cannot trust code that was only tested with mocks. +

    +

    + If you are still determined to build static libraries of mocks + because you want to simulate very specific behaviour, you can + achieve the same effect using the SimpleTest class generator. + In your library file, say mocks/connection.php for a + database connection, create a mock and inherit to override + special methods or add presets... + + Mock::generate('Connection', 'BasicMockConnection'); + class MockConnection extends BasicMockConnection { + function MockConnection() { + $this->BasicMockConnection(); + $this->setReturn('query', false); + } + } +?> +]]> + The generate call tells the class generator to create + a class called BasicMockConnection + rather than the usual MockConnection. + We then inherit from this to get our version of + MockConnection. + By intercepting in this way we can add behaviour, here setting + the default value of query() to be false. + By using the default name we make sure that the mock class + generator will not recreate a different one when invoked elsewhere in the + tests. + It never creates a class if it already exists. + As long as the above file is included first then all tests + that generated MockConnection should + now be using our one instead. + If we don't get the order right and the mock library + creates one first then the class creation will simply fail. +

    +

    + Use this trick if you find you have a lot of common mock behaviour + or you are getting frequent integration problems at later + stages of testing. +

    +
    +
    + + + What are mock objects? + + + Creating mock objects. + + + Mocks as actors or stubs. + + + Mocks as critics with expectations. + + + Other approaches including mock libraries. + + + + + The original + Mock objects paper. + + + SimpleTest project page on SourceForge. + + + SimpleTest home page on LastCraft. + + + + + software development, + php programming, + programming php, + software development tools, + php tutorial, + free php scripts, + architecture, + php resources, + mock objects, + junit, + php testing, + unit test, + php testing, + jmock, + nmock + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/mock_objects_tutorial.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/mock_objects_tutorial.xml new file mode 100644 index 0000000..875adfd --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/mock_objects_tutorial.xml @@ -0,0 +1,359 @@ + + + + PHP unit testing tutorial - Using mock objects in PHP + +
    +

    + Before more functionality is added there is some refactoring + to do. + We are going to do some timing tests and so the + TimeTestCase class definitely needs + its own file. + Let's say tests/time_test_case.php... +UnitTestCase($test_name); + } + function assertSameTime($time1, $time2, $message = '') { + if (! $message) { + $message = "Time [$time1] should match time [$time2]"; + } + $this->assertTrue( + ($time1 == $time2) || ($time1 + 1 == $time2), + $message); + } +} +?> +]]> + We can then require() this file into + the all_tests.php script. +

    +
    +
    +

    + I don't know quite what the format of the log message should + be for the test, so to check for a timestamp we could do the + simplest possible thing, which is to look for a sequence of digits. + +require_once('../classes/clock.php'); + +class TestOfLogging extends TimeTestCase { + function TestOfLogging() { + $this->TimeTestCase('Log class test'); + } + function setUp() { + @unlink('../temp/test.log'); + } + function tearDown() { + @unlink('../temp/test.log'); + } + function getFileLine($filename, $index) { + $messages = file($filename); + return $messages[$index]; + } + function testCreatingNewFile() { + ... + } + function testAppendingToFile() { + ... + } + function testTimestamps() { + $log = new Log('../temp/test.log'); + $log->message('Test line'); + $this->assertTrue( + preg_match('/(\d+)/', $this->getFileLine('../temp/test.log', 0), $matches), + 'Found timestamp'); + $clock = new clock(); + $this->assertSameTime((integer)$matches[1], $clock->now(), 'Correct time'); + } +} +?> +]]> + The test case creates a new Log + object and writes a message. + We look for a digit sequence and then test it against the current + time using our Clock object. + Of course it doesn't work until we write the code. +

    +

    All tests

    + Pass: log_test.php->Log class test->testappendingtofile->Expecting [/Test line 1/] in [Test line 1]
    + Pass: log_test.php->Log class test->testappendingtofile->Expecting [/Test line 2/] in [Test line 2]
    + Pass: log_test.php->Log class test->testcreatingnewfile->Created before message
    + Pass: log_test.php->Log class test->testcreatingnewfile->File created
    + Fail: log_test.php->Log class test->testtimestamps->Found timestamp
    +
    + Notice: Undefined offset: 1 in /home/marcus/projects/lastcraft/tutorial_tests/tests/log_test.php on line 44
    + Fail: log_test.php->Log class test->testtimestamps->Correct time
    + Pass: clock_test.php->Clock class test->testclockadvance->Advancement
    + Pass: clock_test.php->Clock class test->testclocktellstime->Now is the right time
    +
    3/3 test cases complete. + 6 passes and 2 fails.
    +
    + The test suite is still showing the passes from our earlier + modification. +

    +

    + We can get the tests to pass simply by adding a timestamp + when writing out to the file. + Yes, of course all of this is trivial and + I would not normally test this fanatically, but it is going + to illustrate a more general problem. + The log.php file becomes... + +require_once('../classes/clock.php'); + +class Log { + var $_file_path; + + function Log($file_path) { + $this->_file_path = $file_path; + } + + function message($message) { + $clock = new Clock(); + $file = fopen($this->_file_path, 'a'); + fwrite($file, "[" . $clock->now() . "] $message\n"); + fclose($file); + } +} +?> +]]> + The tests should now pass. +

    +

    + Our new test is full of problems, though. + What if our time format changes to something else? + Things are going to be a lot more complicated to test if this + happens. + It also means that any changes to the clock class time + format will cause our logging tests to fail also. + This means that our log tests are tangled up with the clock tests + and extremely fragile. + It lacks cohesion, which is the same as saying it is not + tightly focused, testing facets of the clock as well as the log. + Our problems are caused in part because the clock output + is unpredictable when + all we really want to test is that the logging message + contains the output of + Clock::now(). + We don't + really care about the contents of that method call. +

    +

    + Can we make that call predictable? + We could if we could get the log to use a dummy version + of the clock for the duration of the test. + The dummy clock class would have to behave the same way + as the Clock class + except for the fixed output from the + now() method. + Hey, that would even free us from using the + TimeTestCase class! +

    +

    + We could write such a class pretty easily although it is + rather tedious work. + We just create another clock class with same interface + except that the now() method + returns a value that we can change with some other setter method. + That is quite a lot of work for a pretty minor test. +

    +

    + Except that it is really no work at all. +

    +
    +
    +

    + To reach instant testing clock nirvana we need + only three extra lines of code... + + This includes the mock generator code. + It is simplest to place this in the all_tests.php + script as it gets used rather a lot. + + This is the line that does the work. + The code generator scans the class for all of its + methods, creates code to generate an identically + interfaced class, but with the name "Mock" added, + and then eval()s the new code to + create the new class. + + This line can be added to any test method we are interested in. + It creates the dummy clock ready to receive our instructions. +

    +

    + Our test case is on the first steps of a radical clean up... + +Mock::generate('Clock'); + +class TestOfLogging extends UnitTestCase { + function TestOfLogging() { + $this->UnitTestCase('Log class test'); + } + function setUp() { + @unlink('../temp/test.log'); + } + function tearDown() { + @unlink('../temp/test.log'); + } + function getFileLine($filename, $index) { + $messages = file($filename); + return $messages[$index]; + } + function testCreatingNewFile() { + ... + } + function testAppendingToFile() { + ... + } + function testTimestamps() { + $clock = &new MockClock($this); + $clock->setReturnValue('now', 'Timestamp'); + $log = new Log('../temp/test.log'); + $log->message('Test line', &$clock); + $this->assertWantedPattern( + '/Timestamp/', + $this->getFileLine('../temp/test.log', 0), + 'Found timestamp'); + } +} +?> +]]> + This test method creates a MockClock + object and then sets the return value of the + now() method to be the string + "Timestamp". + Every time we call $clock->now() + it will return this string. + This should be easy to spot. +

    +

    + Next we create our log and send a message. + We pass into the message() + call the clock we would like to use. + This means that we will have to add an optional parameter to + the logging class to make testing possible... +_file_path = $file_path; + } + + function message($message, $clock = false) { + if (!is_object($clock)) { + $clock = new Clock(); + } + $file = fopen($this->_file_path, 'a'); + fwrite($file, "[" . $clock->now() . "] $message\n"); + fclose($file); + } +} +]]> + All of the tests now pass and they test only the logging code. + We can breathe easy again. +

    +

    + Does that extra parameter in the Log + class bother you? + We have changed the interface just to facilitate testing after + all. + Are not interfaces the most important thing? + Have we sullied our class with test code? +

    +

    + Possibly, but consider this. + Next chance you get, look at a circuit board, perhaps the motherboard + of the computer you are looking at right now. + On most boards you will find the odd empty hole, or solder + joint with nothing attached or perhaps a pin or socket + that has no obvious function. + Chances are that some of these are for expansion and + variations, but most of the remainder will be for testing. +

    +

    + Think about that. + The factories making the boards many times over wasting material + on parts that do not add to the final function. + If hardware engineers can make this sacrifice of elegance I am + sure we can too. + Our sacrifice wastes no materials after all. +

    +

    + Still bother you? + Actually it bothers me too, but not so much here. + The number one priority is code that works, not prizes + for minimalism. + If it really bothers you, then move the creation of the clock + into another protected factory method. + Then subclass the clock for testing and override the + factory method with one that returns the mock. + Your tests are clumsier, but your interface is intact. +

    +

    + Again I leave the decision to you. +

    +
    +
    + + + Refactoring the tests so we can reuse + our new time test. + + Adding Log timestamps. + Mocking the clock to make the test cohesive. + + + + This follows the unit test tutorial. + + + Next is distilling boundary classes. + + + You will need the SimpleTest + tool to run the examples. + + + Mock objects papers. + + + + + software development, + php programming, + programming php, + software development tools, + php tutorial, + free php scripts, + architecture, + php resources, + mock objects, + junit, + php testing, + unit test, + php testing + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/overview.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/overview.xml new file mode 100644 index 0000000..f01b3cb --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/overview.xml @@ -0,0 +1,460 @@ + + + + + Overview and feature list for the SimpleTest PHP unit tester and web tester + + +
    +

    + The heart of SimpleTest is a testing framework built around + test case classes. + These are written as extensions of base test case classes, + each extended with methods that actually contain test code. + Top level test scripts then invoke the run() + methods on every one of these test cases in order. + Each test method is written to invoke various assertions that + the developer expects to be true such as + assertEqual(). + If the expectation is correct, then a successful result is dispatched to the + observing test reporter, but any failure triggers an alert + and a description of the mismatch. +

    +

    + A test case looks like this... +MyTestCase extends UnitTestCase { + + function testCreatedLogFile() { + $log = &new Log('my.log'); + $log->message('Hello'); + $this->assertTrue(file_exists('my.log')); + } +} +?> +]]> +

    +

    + These tools are designed for the developer. + Tests are written in the PHP language itself more or less + as the application itself is built. + The advantage of using PHP itself as the testing language is that + there are no new languages to learn, testing can start straight away, + and the developer can test any part of the code. + Basically, all parts that can be accessed by the application code can also be + accessed by the test code, if they are in the same programming language. +

    +

    + The simplest type of test case is the + UnitTestCase. + This class of test case includes standard tests for equality, + references and pattern matching. + All these test the typical expectations of what you would + expect the result of a function or method to be. + This is by far the most common type of test in the daily + routine of development, making up about 95% of test cases. +

    +

    + The top level task of a web application though is not to + produce correct output from its methods and objects, but + to generate web pages. + The WebTestCase class tests web + pages. + It simulates a web browser requesting a page, complete with + cookies, proxies, secure connections, authentication, forms, frames and most + navigation elements. + With this type of test case, the developer can assert that + information is present in the page and that forms and + sessions are handled correctly. +

    +

    + A WebTestCase looks like this... +MySiteTest extends WebTestCase { + + function testHomePage() { + $this->get('http://www.my-site.com/index.php'); + $this->assertTitle('My Home Page'); + $this->clickLink('Contact'); + $this->assertTitle('Contact me'); + $this->assertPattern('/Email me at/'); + } +} +?> +]]> +

    +
    +
    +

    + The following is a very rough outline of past and future features + and their expected point of release. + I am afraid it is liable to change without warning, as meeting the + milestones rather depends on time available. + Green stuff has been coded, but not necessarily released yet. + If you have a pressing need for a green but unreleased feature + then you should check-out the code from Sourceforge SVN directly. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FeatureDescriptionRelease
    Unit test caseCore test case class and assertions1.0
    Html displaySimplest possible display1.0
    Autoloading of test cases + Reading a file with test cases and loading them into a + group test automatically + 1.0
    Mock objects + Objects capable of simulating other objects removing + test dependencies + 1.0
    Web test caseAllows link following and title tag matching1.0
    Partial mocks + Mocking parts of a class for testing less than a class + or for complex simulations + 1.0
    Web cookie handlingCorrect handling of cookies when fetching pages1.0
    Following redirectsPage fetching automatically follows 300 redirects1.0
    Form parsingAbility to submit simple forms and read default form values1.0
    Command line interfaceTest display without the need of a web browser1.0
    Exposure of expectation classesCan create precise tests with mocks as well as test cases1.0
    XML output and parsing + Allows multi host testing and the integration of acceptance + testing extensions + 1.0
    Browser component + Exposure of lower level web browser interface for more + detailed test cases + 1.0
    HTTP authentication + Fetching protected web pages with basic authentication + only + 1.0
    SSL supportCan connect to https: pages1.0
    Proxy supportCan connect via. common proxies1.0
    Frames supportHandling of frames in web test cases1.0
    File upload testingCan simulate the input type file tag1.0.1
    Mocking interfaces + Can generate mock objects to interfaces as well as classes + and class interfaces are carried for type hints + 1.0.1
    Testing exceptionsSimilar to testing PHP errors1.0.1
    HTML label supportCan access all controls using the visual label1.0.1
    Base tag supportRespects page base tag when clicking1.0.1
    PHP 5 E_STRICT compliantPHP 5 only version that works with the E_STRICT error level1.1
    BDD style fixturesCan import fixtures using a mixin like given() method1.5
    Reporting machinery enhancementsImproved message passing for better cooperation with IDEs1.5
    Fluent mock interfaceMore flexible and concise mock objects1.6
    LocalisationMessages abstracted and code generated1.6
    CSS selectorsHTML content can be examined using CSS selectors1.7
    HTML table assertionsCan match HTML or table elements to expectations1.7
    Unified acceptance testing modelContent searchable through selectors combined with expectations1.7
    DatabaseTestCaseSQL selectors and DB drivers1.7
    IFrame supportReads IFrame content that can be refreshed1.8
    Alternate HTML parsersCan detect compiled parsers for performance improvements1.8
    Integrated Selenium supportEasy to use built in Selenium driver and tutorial1.9
    Code coverageReports using the bundled tool when using XDebug1.9
    Deprecation of old methodsSimpler interface for SimpleTest22.0
    Javascript suportUse of PECL module to add Javascript to the native browser3.0
    + PHP5 migraton will start straight after the version 1.0.1 series, + whereupon only PHP 5.1+ will be supported. + SimpleTest is currently compatible with PHP 5, but will not + make use of all of the new features until version 1.1. +

    +
    +
    +

    + Process is at least as important as tools. + The type of process that makes the heaviest use of a developer's + testing tool is of course + Extreme Programming. + This is one of the + Agile Methodologies + which combine various practices to "flatten the cost curve" of software development. + More extreme still is Test Driven Development, + where you very strictly adhere to the rule of no coding until you have a test. + If you're more of a planner, or believe that experience trumps evolution, + you may prefer the + RUP approach. + I haven't tried it, but even I can see that you will need test tools (see figure 9). +

    +

    + Most unit testers clone JUnit to some degree, + as far as the interface at least. There is a wealth of information on the + JUnit site including the + FAQ + which contains plenty of general advice on testing. + Once you get bitten by the bug you will certainly appreciate the phrase + test infected + coined by Eric Gamma. + If you are still reviewing which unit tester to use you can find pretty complete + lists from + Wikipedia, + Software testing FAQ, + and Open source testing. +

    +

    + There is still very little material on using mock objects, which is a shame + as unit testing without them is a lot more work. + The original mock objects paper + is very Java focused, but still worth a read. + The most authoritive sources are probably + the original mock objects site and + JMock. + Java centric, but tucked away in PDFs they contain some deep knowledge on using mocks from the + extended experience of the concept inventors. + As a new technology there are plenty of discussions and debate on how to use mocks, + often on Wikis such as + Extreme Tuesday + or www.mockobjects.com + or the original C2 Wiki. + Injecting mocks into a class is the main area of debate for which this + paper on IBM + makes a good starting point. +

    +

    + There are plenty of web testing tools, but the scriptable ones + are mostly are written in Java and + tutorials and advice are rather thin on the ground. + The only hope is to look at the documentation for + HTTPUnit, + HTMLUnit + or JWebUnit and hope for clues. + There are some XML driven test frameworks, but again most + require Java to run. +

    +

    + Most significant is a new generation of tools that run directly in the web browser + are now available. + These include + Selenium and + Watir. + They are non-trivial to set up and slow to run, but can essentially test anything. + As SimpleTest does not support JavaScript you would probably + have to look at these tools anyway if you have highly dynamic + pages. +

    +
    +
    + + + Quick summary + of the SimpleTest tool for PHP. + + + List of features, + both current ones and those planned. + + + There are plenty of unit testing resources + on the web. + + + + + Documentation for SimpleTest. + + + How to write PHP test cases + is a fairly advanced tutorial. + + + SimpleTest API from phpdoc. + + + + + software development tools, + php programming, + programming php, + software development tools, + Tools for extreme programming, + free php scripts, + links of testing tools, + php testing resources, + mock objects, + junit, + jwebunit, + htmlunit, + itc, + php testing links, + unit test advice and documentation, + extreme programming in php + + + + + + Marcus Baker + + Primary Developer{@link mailto:marcus@lastcraft.com marcus@lastcraft.com} + + + + Perrick Pennet + + General manager{@link mailto:perrick@noparking.net perrick@noparking.net} + + + + Jason Sweat + + Documentation{@link mailto:jsweat_php@yahoo.com jsweat_php@yahoo.com} + + + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/partial_mocks_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/partial_mocks_documentation.xml new file mode 100644 index 0000000..ffc013d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/partial_mocks_documentation.xml @@ -0,0 +1,402 @@ + + + + SimpleTest for PHP partial mocks documentation + + +

    + A partial mock is simply a pattern to alleviate a specific problem + in testing with mock objects, + that of getting mock objects into tight corners. + It's quite a limited tool and possibly not even a good idea. + It is included with SimpleTest because I have found it useful + on more than one occasion and has saved a lot of work at that point. +

    +
    +
    +

    + When one object uses another it is very simple to just pass a mock + version in already set up with its expectations. + Things are rather tricker if one object creates another and the + creator is the one you want to test. + This means that the created object should be mocked, but we can + hardly tell our class under test to create a mock instead. + The tested class doesn't even know it is running inside a test + after all. +

    +

    + For example, suppose we are building a telnet client and it + needs to create a network socket to pass its messages. + The connection method might look something like... +read( ... ); + ... + } +} +?> +]]> + We would really like to have a mock object version of the socket + here, what can we do? +

    +

    + The first solution is to pass the socket in as a parameter, + forcing the creation up a level. + Having the client handle this is actually a very good approach + if you can manage it and should lead to factoring the creation from + the doing. + In fact, this is one way in which testing with mock objects actually + forces you to code more tightly focused solutions. + They improve your programming. +

    +

    + Here this would be... +function &connect(&$socket, $username, $password) { + $socket->read( ... ); + ... + } +} +?> +]]> + This means that the test code is typical for a test involving + mock objects. + + $socket = &new MockSocket($this); + ... + $telnet = &new Telnet(); + $telnet->connect($socket, 'Me', 'Secret'); + ... + } +} +]]> + It is pretty obvious though that one level is all you can go. + You would hardly want your top level application creating + every low level file, socket and database connection ever + needed. + It wouldn't know the constructor parameters anyway. +

    +

    + The next simplest compromise is to have the created object passed + in as an optional parameter... + + function &connect($ip, $port, $username, $password, $socket = false) { + if (!$socket) { + $socket = &new Socket($ip, $port); + } + $socket->read( ... ); + ... + return $socket; + } +} +?> +]]> + For a quick solution this is usually good enough. + The test now looks almost the same as if the parameter + was formally passed... + + $socket = &new MockSocket($this); + ... + $telnet = &new Telnet(); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret', &$socket); + ... + } +} +]]> + The problem with this approach is its untidiness. + There is test code in the main class and parameters passed + in the test case that are never used. + This is a quick and dirty approach, but nevertheless effective + in most situations. +

    +

    + The next method is to pass in a factory object to do the creation... + + function Telnet(&$network) { + $this->_network = &$network; + } + ... + function &connect($ip, $port, $username, $password) { + $socket = &$this->_network->createSocket($ip, $port); + $socket->read( ... ); + ... + return $socket; + } +} +?> +]]> + This is probably the most highly factored answer as creation + is now moved into a small specialist class. + The networking factory can now be tested separately, but mocked + easily when we are testing the telnet class... + + $socket = &new MockSocket($this); + ... + $network = &new MockNetwork($this); + $network->setReturnReference('createSocket', $socket); + $telnet = &new Telnet($network); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ... + } +} +]]> + The downside is that we are adding a lot more classes to the + library. + Also we are passing a lot of factories around which will + make the code a little less intuitive. + The most flexible solution, but the most complex. +

    +

    + Is there a middle ground? +

    +
    +
    +

    + There is a way we can circumvent the problem without creating + any new application classes, but it involves creating a subclass + when we do the actual testing. + Firstly we move the socket creation into its own method... + + $socket = &$this->_createSocket($ip, $port); + $socket->read( ... ); + ... + } + + function &_createSocket($ip, $port) { + return new Socket($ip, $port); + } +} +?> +]]> + This is the only change we make to the application code. +

    +

    + For the test case we have to create a subclass so that + we can intercept the socket creation... +class TelnetTestVersion extends Telnet { + var $_mock; + + function TelnetTestVersion(&$mock) { + $this->_mock = &$mock; + $this->Telnet(); + } + + function &_createSocket() { + return $this->_mock; + } +} +]]> + Here I have passed the mock in the constructor, but a + setter would have done just as well. + Note that the mock was set into the object variable + before the constructor was chained. + This is necessary in case the constructor calls + connect(). + Otherwise it could get a null value from + _createSocket(). +

    +

    + After the completion of all of this extra work the + actual test case is fairly easy. + We just test our new class instead... + + $socket = &new MockSocket($this); + ... + $telnet = &new TelnetTestVersion($socket); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ... + } +} +]]> + The new class is very simple of course. + It just sets up a return value, rather like a mock. + It would be nice if it also checked the incoming parameters + as well. + Just like a mock. + It seems we are likely to do this often, can + we automate the subclass creation? +

    +
    +
    +

    + Of course the answer is "yes" or I would have stopped writing + this by now! + The previous test case was a lot of work, but we can + generate the subclass using a similar approach to the mock objects. +

    +

    + Here is the partial mock version of the test... +Mock::generatePartial( + 'Telnet', + 'TelnetTestVersion', + array('_createSocket')); + +class TelnetTest extends UnitTestCase { + ... + function testConnection() { + $socket = &new MockSocket($this); + ... + $telnet = &new TelnetTestVersion($this); + $telnet->setReturnReference('_createSocket', $socket); + $telnet->Telnet(); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ... + } +} +]]> + The partial mock is a subclass of the original with + selected methods "knocked out" with test + versions. + The generatePartial() call + takes three parameters: the class to be subclassed, + the new test class name and a list of methods to mock. +

    +

    + Instantiating the resulting objects is slightly tricky. + The only constructor parameter of a partial mock is + the unit tester reference. + As with the normal mock objects this is needed for sending + test results in response to checked expectations. +

    +

    + The original constructor is not run yet. + This is necessary in case the constructor is going to + make use of the as yet unset mocked methods. + We set any return values at this point and then run the + constructor with its normal parameters. + This three step construction of "new", followed + by setting up the methods, followed by running the constructor + proper is what distinguishes the partial mock code. +

    +

    + Apart from construction, all of the mocked methods have + the same features as mock objects and all of the unmocked + methods behave as before. + We can set expectations very easily... +setReturnReference('_createSocket', $socket); + $telnet->expectOnce('_createSocket', array('127.0.0.1', 21)); + $telnet->Telnet(); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ... + $telnet->tally(); + } +} +]]> +

    +
    +
    +

    + The mocked out methods don't have to be factory methods, + they could be any sort of method. + In this way partial mocks allow us to take control of any part of + a class except the constructor. + We could even go as far as to mock every method + except one we actually want to test. +

    +

    + This last situation is all rather hypothetical, as I haven't + tried it. + I am open to the possibility, but a little worried that + forcing object granularity may be better for the code quality. + I personally use partial mocks as a way of overriding creation + or for occasional testing of the TemplateMethod pattern. +

    +

    + It's all going to come down to the coding standards of your + project to decide which mechanism you use. +

    +
    +
    + + + The mock injection problem. + + + Moving creation to a protected factory method. + + + Partial mocks generate subclasses. + + + Partial mocks test less than a class. + + + + + SimpleTest project page on SourceForge. + + + Full API for SimpleTest + from the PHPDoc. + + + The protected factory is described in + this paper from IBM. + This is the only formal comment I have seen on this problem. + + + + + php software development, + php test case development, + database programming php, + software development tools, + php advanced tutorial, + phpunit style scripts, + architecture, + php resources, + mock objects, + junit, + php test framework, + unit test, + php testing + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/photos_stream.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/photos_stream.xml new file mode 100644 index 0000000..ed90b7d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/photos_stream.xml @@ -0,0 +1,51 @@ + + + + Photos & pictures tagged SimpleTest + + +

    + A set of photos and pictures tagged with SimpleTest. +

    +
    +
    + + + +
    +
    + + + + Pictures tagged + SimpleTest on Flickr + + + + + + software development, + computer programmer, + php programming, + programming php, + software development company, + software development uk, + php tutorial, + bespoke software development uk, + corporate web development, + architecture, + freelancer, + php resources, + wordtracker, + web marketing, + serach engines, + web positioning, + internet marketing + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/reporter_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/reporter_documentation.xml new file mode 100644 index 0000000..efc4ce2 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/reporter_documentation.xml @@ -0,0 +1,471 @@ + + + + SimpleTest for PHP test runner and display documentation + + +

    + SimpleTest pretty much follows the MVC pattern + (Model-View-Controller). + The reporter classes are the view and the model is your + test cases and their hiearchy. + The controller is mostly hidden from the user of + SimpleTest unless you want to change how the test cases + are actually run, in which case it is possible to + override the runner objects from within the test case. + As usual with MVC, the controller is mostly undefined + and there are other places to control the test run. +

    +
    +
    +

    + The default test display is minimal in the extreme. + It reports success and failure with the conventional red and + green bars and shows a breadcrumb trail of test groups + for every failed assertion. + Here's a fail... +

    +

    File test

    + Fail: createnewfile->True assertion failed.
    +
    1/1 test cases complete. + 0 passes, 1 fails and 0 exceptions.
    +
    + And here all tests passed... +
    +

    File test

    +
    1/1 test cases complete. + 1 passes, 0 fails and 0 exceptions.
    +
    + The good news is that there are several points in the display + hiearchy for subclassing. +

    +

    + For web page based displays there is the + HtmlReporter class with the following + signature... + + Here is what some of these methods mean. First the display methods + that you will probably want to override... +

      +
    • + HtmlReporter(string $encoding)
      + is the constructor. + Note that the unit test sets up the link to the display + rather than the other way around. + The display is a mostly passive receiver of test events. + This allows easy adaption of the display for other test + systems beside unit tests, such as monitoring servers. + The encoding is the character encoding you wish to + display the test output in. + In order to correctly render debug output when + using the web tester, this should match the encoding + of the site you are trying to test. + The available character set strings are described in + the PHP html_entities() + function. +
    • +
    • + void paintHeader(string $test_name)
      + is called once at the very start of the test when the first + start event arrives. + The first start event is usually delivered by the top level group + test and so this is where $test_name + comes from. + It paints the page titles, CSS, body tag, etc. + It returns nothing (void). +
    • +
    • + void paintFooter(string $test_name)
      + Called at the very end of the test to close any tags opened + by the page header. + By default it also displays the red/green bar and the final + count of results. + Actually the end of the test happens when a test end event + comes in with the same name as the one that started it all + at the same level. + The tests nest you see. + Closing the last test finishes the display. +
    • +
    • + void paintMethodStart(string $test_name)
      + is called at the start of each test method. + The name normally comes from method name. + The other test start events behave the same way except + that the group test one tells the reporter how large + it is in number of held test cases. + This is so that the reporter can display a progress bar + as the runner churns through the test cases. +
    • +
    • + void paintMethodEnd(string $test_name)
      + backs out of the test started with the same name. +
    • +
    • + void paintFail(string $message)
      + paints a failure. + By default it just displays the word fail, a breadcrumbs trail + showing the current test nesting and the message issued by + the assertion. +
    • +
    • + void paintPass(string $message)
      + by default does nothing. +
    • +
    • + string getCss()
      + Returns the CSS styles as a string for the page header + method. + Additional styles have to be appended here if you are + not overriding the page header. + You will want to use this method in an overriden page header + if you want to include the original CSS. +
    • +
    + There are also some accessors to get information on the current + state of the test suite. + Use these to enrich the display... +
      +
    • + array getTestList()
      + is the first convenience method for subclasses. + Lists the current nesting of the tests as a list + of test names. + The first, most deeply nested test, is first in the + list and the current test method will be last. +
    • +
    • + integer getPassCount()
      + returns the number of passes chalked up so far. + Needed for the display at the end. +
    • +
    • + integer getFailCount()
      + is likewise the number of fails so far. +
    • +
    • + integer getExceptionCount()
      + is likewise the number of errors so far. +
    • +
    • + integer getTestCaseCount()
      + is the total number of test cases in the test run. + This includes the grouping tests themselves. +
    • +
    • + integer getTestCaseProgress()
      + is the number of test cases completed so far. +
    • +
    + One simple modification is to get the HtmlReporter to display + the passes as well as the failures and errors... +class ShowPasses extends HtmlReporter { + + function paintPass($message) { + parent::paintPass($message); + print "&Pass: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode("->", $breadcrumb); + print "->$message
    \n"; + } + + protected function getCss() { + return parent::getCss() . ' .pass { color: green; }'; + } +} +]]>
    +

    +

    + One method that was glossed over was the makeDry() + method. + If you run this method, with no parameters, on the reporter + before the test suite is run no actual test methods + will be called. + You will still get the events of entering and leaving the + test methods and test cases, but no passes or failures etc, + because the test code will not actually be executed. +

    +

    + The reason for this is to allow for more sophistcated + GUI displays that allow the selection of individual test + cases. + In order to build a list of possible tests they need a + report on the test structure for drawing, say a tree view + of the test suite. + With a reporter set to dry run that just sends drawing events + this is easily accomplished. +

    +
    +
    +

    + Rather than simply modifying the existing display, you might want to + produce a whole new HTML look, or even generate text or XML. + Rather than override every method in + HtmlReporter we can take one + step up the class hiearchy to SimpleReporter + in the simple_test.php source file. +

    +

    + A do nothing display, a blank canvas for your own creation, would + be... +require_once('simpletest/simple_test.php'); + +class MyDisplay extends SimpleReporter { + + function paintHeader($test_name) { + } + + function paintFooter($test_name) { + } + + function paintStart($test_name, $size) { + parent::paintStart($test_name, $size); + } + + function paintEnd($test_name, $size) { + parent::paintEnd($test_name, $size); + } + + function paintPass($message) { + parent::paintPass($message); + } + + function paintFail($message) { + parent::paintFail($message); + } +} +]]> + No output would come from this class until you add it. +

    +
    +
    +

    + SimpleTest also ships with a minimal command line reporter. + The interface mimics JUnit to some extent, but paints the + failure messages as they arrive. + To use the command line reporter simply substitute it + for the HTML version... +addTestFile('tests/file_test.php'); +$test->run(new TextReporter()); +?> +]]> + Then invoke the test suite from the command line... +

    +php file_test.php
    +
    + You will need the command line version of PHP installed + of course. + A passing test suite looks like this... +
    +File test
    +OK
    +Test cases run: 1/1, Failures: 0, Exceptions: 0
    +
    + A failure triggers a display like this... +
    +File test
    +1) True assertion failed.
    +    in createnewfile
    +FAILURES!!!
    +Test cases run: 1/1, Failures: 1, Exceptions: 0
    +
    +

    +

    + One of the main reasons for using a command line driven + test suite is of using the tester as part of some automated + process. + To function properly in shell scripts the test script should + return a non-zero exit code on failure. + If a test suite fails the value false + is returned from the SimpleTest::run() + method. + We can use that result to exit the script with the desired return + code... +addTestFile('tests/file_test.php'); +exit ($test->run(new TextReporter()) ? 0 : 1); +?> +]]> + Of course we don't really want to create two test scripts, + a command line one and a web browser one, for each test suite. + The command line reporter includes a method to sniff out the + run time environment... +addTestFile('tests/file_test.php'); +if (TextReporter::inCli()) { + exit ($test->run(new TextReporter()) ? 0 : 1); +} +$test->run(new HtmlReporter()); +?> +]]> + This is the form used within SimpleTest itself. +

    +
    +
    +

    + SimpleTest ships with an XmlReporter class + used for internal communication. + When run the output looks like... +

    
    +
    +  
    +    Remote tests
    +    
    +      Visual test with 48 passes, 48 fails and 4 exceptions
    +      
    +        testofunittestcaseoutput
    +        
    +          testofresults
    +          This assertion passed
    +          This assertion failed
    +        
    +        
    +          ...
    +        
    +      
    +    
    +  
    +
    +]]>
    + You can make use of this format with the parser + supplied as part of SimpleTest itself. + This is called SimpleTestXmlParser and + resides in xml.php within the SimpleTest package... +parse($test_output); +?> +]]> + The $test_output should be the XML format + from the XML reporter, and could come from say a command + line run of a test case. + The parser sends events to the reporter just like any + other test run. + There are some odd occasions where this is actually useful. +

    +

    + A problem with large test suites is thet they can exhaust + the default 8Mb memory limit on a PHP process. + By having the test groups output in XML and run in + separate processes, the output can be reparsed to + aggregate the results into a much smaller footprint top level + test. +

    +

    + Because the XML output can come from anywhere, this opens + up the possibility of aggregating test runs from remote + servers. + A test case already exists to do this within the SimpleTest + framework, but it is currently experimental... +require_once('../remote.php'); +require_once('../reporter.php'); + +$test_url = ...; +$dry_url = ...; + +$test = &new TestSuite('Remote tests'); +$test->addTestCase(new RemoteTestCase($test_url, $dry_url)); +$test->run(new HtmlReporter()); +?> +]]> + The RemoteTestCase takes the actual location + of the test runner, basically a web page in XML format. + It also takes the URL of a reporter set to do a dry run. + This is so that progress can be reported upward correctly. + The RemoteTestCase can be added to test suites + just like any other group test. +

    +
    +
    + + + Displaying results in HTML + + + Displaying and reporting results + in other formats + + + Using SimpleTest from the command line + + + Using Using XML for remote testing + + + + + SimpleTest project page on SourceForge. + + + SimpleTest download page on LastCraft. + + + The developer's API for SimpleTest + gives full detail on the classes and assertions available. + + + + + php unit testing, + documentation, + marcus baker, + simple test, + simpletest, + remote testing, + xml tests, + automated testing + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/screencasts.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/screencasts.xml new file mode 100644 index 0000000..b003e0a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/screencasts.xml @@ -0,0 +1,76 @@ + + + + Screencasts : Basic and Advanced TDD using SimpleTest + +
    +

    + Want to see a basic TDD session in PHP ? + Here's a screencast from a session presented at + the PHP Everywhere Conference + by Andre John Cruz + (Application Developer, Barclays Singapore). +

    +

    + Basic TDD in PHP Demo, 1 of 3
    + +
    +
    + Basic TDD in PHP Demo, 2 of 3
    + +
    +
    + Basic TDD in PHP Demo, 3 of 3
    + +

    +

    + Advanced TDD in PHP Demo, 1 of 4
    + +
    +
    + Advanced TDD in PHP Demo, 2 of 4
    + +
    +
    + Advanced TDD in PHP Demo, 3 of 4
    + +
    +
    + Advanced TDD in PHP Demo, 4 of 4
    + +

    +
    +
    + + + A screencast + covering "Basic and Advanced TDD in PHP". + + + + + The PHP User Group + Philippines organized the event. + + + + + software development, + test case example, + programming php, + software development tools, + php tutorial, + creating subclass, + free php scripts, + architecture, + php resources, + junit, + phpunit style testing, + unit test, + php testing, + screencast, + basic TDD, + advanced TDD + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/simple_test.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/simple_test.xml new file mode 100644 index 0000000..10aa862 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/simple_test.xml @@ -0,0 +1,524 @@ + + + + + Download the Simple Test testing framework - + Unit tests and mock objects for PHP + + + + 1.0.1 release cycle started. + Features include include file upload, better PHP5 support for + mock objects, and HTML label support. + There is also a big clean up of method names and some internals + refactoring. + + +

    + The following assumes that you are familiar with the concept + of unit testing as well as the PHP web development language. + It is a guide for the impatient new user of + SimpleTest. + For fuller documentation, especially if you are new + to unit testing see the ongoing + documentation, and for + example test cases see the + unit testing tutorial. +

    +
    +
    +

    + Amongst software testing tools, a unit tester is the one + closest to the developer. + In the context of agile development the test code sits right + next to the source code as both are written simultaneously. + In this context SimpleTest aims to be a complete PHP developer + test solution and is called "Simple" because it + should be easy to use and extend. + It wasn't a good choice of name really. + It includes all of the typical functions you would expect from + JUnit and the + PHPUnit + ports, and includes + mock objects. +

    +

    + What makes this tool immediately useful to the PHP developer is the internal + web browser. + This allows tests that navigate web sites, fill in forms and test pages. + Being able to write these test in PHP means that it is easy to write + integrated tests. + An example might be confirming that a user was written to a database + after a signing up through the web site. +

    +

    + The quickest way to demonstrate SimpleTest is with an example. +

    +

    + Let us suppose we are testing a simple file logging class called + Log in classes/log.php. + We start by creating a test script which we will call + tests/log_test.php and populate it as follows... +require_once('simpletest/autorun.php'); +require_once('../classes/log.php'); + +class TestOfLogging extends UnitTestCase { +} +?> +]]> + Here the simpletest folder is either local or in the path. + You would have to edit these locations depending on where you + unpacked the toolset. + The "autorun.php" file does more than just include the + SimpleTest files, it also runs our test for us. +

    +

    + The TestOfLogging is our first test case and it's + currently empty. + Each test case is a class that extends one of the SimpleTet base classes + and we can have as many of these in the file as we want. +

    +

    + With three lines of scaffolding, and our Log class + include, we have a test suite. + No tests though. +

    +

    + For our first test, we'll assume that the Log class + takes the file name to write to in the constructor, and we have + a temporary folder in which to place this file... +testLogCreatesNewFileOnFirstMessage() { + @unlink('/temp/test.log'); + $log = new Log('/temp/test.log'); + $this->assertFalse(file_exists('/temp/test.log')); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists('/temp/test.log')); + } +} +?> +]]> + When a test case runs, it will search for any method that + starts with the string "test" + and execute that method. + If the method starts "test", it's a test. + Note the very long name testLogCreatesNewFileOnFirstMessage(). + This is considered good style and makes the test output more readable. +

    +

    + We would normally have more than one test method in a test case, + but that's for later. +

    +

    + Assertions within the test methods trigger messages to the + test framework which displays the result immediately. + This immediate response is important, not just in the event + of the code causing a crash, but also so that + print statements can display + their debugging content right next to the assertion concerned. +

    +

    + To see these results we have to actually run the tests. + No other code is necessary - we can just open the page + with our browser. +

    +

    + On failure the display looks like this... +

    +

    TestOfLogging

    + Fail: testLogCreatesNewFileOnFirstMessage->True assertion failed.
    +
    1/1 test cases complete. + 1 passes and 1 fails.
    +
    + ...and if it passes like this... +
    +

    TestOfLogging

    +
    1/1 test cases complete. + 2 passes and 0 fails.
    +
    + And if you get this... +
    + Fatal error: Failed opening required '../classes/log.php' (include_path='') in /home/marcus/projects/lastcraft/tutorial_tests/Log/tests/log_test.php on line 7 +
    + it means you're missing the classes/Log.php file that could look like... + +class Log { + function Log($file_path) { + } + + function message() { + } +} +?> +]]> + It's fun to write the code after the test. + More than fun even - + this system is called "Test Driven Development". +

    +

    + For more information about UnitTestCase, see + the unit test documentation. +

    +
    +
    +

    + It is unlikely in a real application that we will only ever run + one test case. + This means that we need a way of grouping cases into a test + script that can, if need be, run every test for the application. +

    +

    + Our first step is to create a new file called tests/all_tests.php + and insert the following code... +require_once('simpletest/autorun.php'); + +class AllTests extends TestSuite { + function AllTests() { + $this->TestSuite('All tests'); + $this->addFile('log_test.php'); + } +} +?> +]]> + The "autorun" include allows our upcoming test suite + to be run just by invoking this script. +

    +

    + The TestSuite subclass must chain it's constructor. + This limitation will be removed in future versions. +

    +

    + The method TestSuite::addFile() + will include the test case file and read any new classes + that are descended from SimpleTestCase. + UnitTestCase is just one example of a class derived from + SimpleTestCase, and you can create your own. + TestSuite::addFile() can include other test suites. +

    +

    + The class will not be instantiated yet. + When the test suite runs it will construct each instance once + it reaches that test, then destroy it straight after. + This means that the constructor is run just before each run + of that test case, and the destructor is run before the next test case starts. +

    +

    + It is common to group test case code into superclasses which are not + supposed to run, but become the base classes of other tests. + For "autorun" to work properly the test case file should not blindly run + any other test case extensions that do not actually run tests. + This could result in extra test cases being counted during the test + run. + Hardly a major problem, but to avoid this inconvenience simply mark your + base class as abstract. + SimpleTest won't run abstract classes. + If you are still using PHP4, then + a SimpleTestOptions::ignore() directive + somewhere in the test case file will have the same effect. +

    +

    + Also, the test case file should not have been included + elsewhere or no cases will be added to this group test. + This would be a more serious error as if the test case classes are + already loaded by PHP the TestSuite::addFile() + method will not detect them. +

    +

    + To display the results it is necessary only to invoke + tests/all_tests.php from the web server or the command line. +

    +

    + For more information about building test suites, + see the test suite documentation. +

    +
    +
    +

    + Let's move further into the future and do something really complicated. +

    +

    + Assume that our logging class is tested and completed. + Assume also that we are testing another class that is + required to write log messages, say a + SessionPool. + We want to test a method that will probably end up looking + like this... + +class SessionPool { + ... + function logIn($username) { + ... + $this->_log->message("User $username logged in."); + ... + } + ... +} + +]]> + In the spirit of reuse, we are using our + Log class. + A conventional test case might look like this... +require_once('../classes/session_pool.php'); + +class TestOfSessionLogging extends UnitTestCase { + + function setUp() { + @unlink('/temp/test.log'); + } + + function tearDown() { + @unlink('/temp/test.log'); + } + + function testLoggingInIsLogged() { + $log = new Log('/temp/test.log'); + $session_pool = &new SessionPool($log); + $session_pool->logIn('fred'); + $messages = file('/temp/test.log'); + $this->assertEqual($messages[0], "User fred logged in.\n"); + } +} +?> +]]> + We'll explain the setUp() and tearDown() + methods later. +

    +

    + This test case design is not all bad, but it could be improved. + We are spending time fiddling with log files which are + not part of our test. + We have created close ties with the Log class and + this test. + What if we don't use files any more, but use ths + syslog library instead? + It means that our TestOfSessionLogging test will + fail, even thouh it's not testing Logging. +

    +

    + It's fragile in smaller ways too. + Did you notice the extra carriage return in the message? + Was that added by the logger? + What if it also added a time stamp or other data? +

    +

    + The only part that we really want to test is that a particular + message was sent to the logger. + We can reduce coupling if we pass in a fake logging class + that simply records the message calls for testing, but + takes no action. + It would have to look exactly like our original though. +

    +

    + If the fake object doesn't write to a file then we save on deleting + the file before and after each test. We could save even more + test code if the fake object would kindly run the assertion for us. +

    +

    + Too good to be true? + We can create such an object easily... +Mock::generate('Log'); + +class TestOfSessionLogging extends UnitTestCase { + + function testLoggingInIsLogged() { + $log = &new MockLog(); + $log->expectOnce('message', array('User fred logged in.')); + $session_pool = &new SessionPool($log); + $session_pool->logIn('fred'); + } +} +?> +]]> + The Mock::generate() call code generated a new class + called MockLog. + This looks like an identical clone, except that we can wire test code + to it. + That's what expectOnce() does. + It says that if message() is ever called on me, it had + better be with the parameter "User fred logged in.". +

    +

    + The test will be triggered when the call to + message() is invoked on the + MockLog object by SessionPool::logIn() code. + The mock call will trigger a parameter comparison and then send the + resulting pass or fail event to the test display. + Wildcards can be included here too, so you don't have to test every parameter of + a call when you only want to test one. +

    +

    + If the mock reaches the end of the test case without the + method being called, the expectOnce() + expectation will trigger a test failure. + In other words the mocks can detect the absence of + behaviour as well as the presence. +

    +

    + The mock objects in the SimpleTest suite can have arbitrary + return values set, sequences of returns, return values + selected according to the incoming arguments, sequences of + parameter expectations and limits on the number of times + a method is to be invoked. +

    +

    + For more information about mocking and stubbing, see the + mock objects documentation. +

    +
    +
    +

    + One of the requirements of web sites is that they produce web + pages. + If you are building a project top-down and you want to fully + integrate testing along the way then you will want a way of + automatically navigating a site and examining output for + correctness. + This is the job of a web tester. +

    +

    + The web testing in SimpleTest is fairly primitive, as there is + no JavaScript. + Most other browser operations are simulated. +

    +

    + To give an idea here is a trivial example where a home + page is fetched, from which we navigate to an "about" + page and then test some client determined content. +require_once('simpletest/web_tester.php'); + +class TestOfAbout extends WebTestCase { + function testOurAboutPageGivesFreeReignToOurEgo() { + $this->get('http://test-server/index.php'); + $this->click('About'); + $this->assertTitle('About why we are so great'); + $this->assertText('We are really great'); + } +} +?> +]]> + With this code as an acceptance test, you can ensure that + the content always meets the specifications of both the + developers, and the other project stakeholders. +

    +

    + You can navigate forms too... +get('http://google.com/'); + $this->setField('q', 'simpletest'); + $this->click("I'm Feeling Lucky"); + $this->assertTitle('SimpleTest - Unit Testing for PHP'); + } +} +?> +]]> + ...although this could violate Google's(tm) terms and conditions. +

    +

    + For more information about web testing, see the + scriptable + browser documentation and the + WebTestCase. +

    +

    + SourceForge.net Logo +

    +
    +
    + + + Using unit tester + with an example. + + + Grouping tests + for testing with one click. + + + Using mock objects + to ease testing and gain tighter control. + + + Testing web pages + at the browser level. + + + + + Download PHP Simple Test + from SourceForge. + + + The developer's API for SimpleTest + gives full detail on the classes and assertions available. + + + + + software development, + php programming, + programming php, + software development tools, + php tutorial, + free php scripts, + architecture, + php resources, + mock objects, + junit, + php testing, + php unit, + methodology, + test first, + sourceforge, + open source, + unit test, + web tester, + web testing, + html testing tools, + testing web pages, + php mock objects, + navigating websites automatically, + automated testing, + web scripting, + wget, + curl testing, + jmock for php, + jwebunit, + phpunit, + php unit testing, + php web testing, + jason sweat, + marcus baker, + topstyle plug in, + phpedit plug in + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/softwares_using_simpletest.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/softwares_using_simpletest.xml new file mode 100644 index 0000000..368a501 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/softwares_using_simpletest.xml @@ -0,0 +1,164 @@ + + + + Softwares using SimpleTest + +
    +

    + Some projects are able to extend SimpleTest's functionnality : +

    +
    +
    + + Limb Unit + +
    +
    + limb_unit is an advanced SimpleTest based tests runner + utility for PHP5. In a nutshell, limb_unit finds and executes tests within + the file system. It's similar to phpunit utility from PHPUnit, + yet more powerful. +
    +
    + + SimpleTestXUL + +
    +
    + A XUL interface to SimpleTest framework. +
    +
    + + Stagehand_TestRunner + +
    +
    + Automated test runners for PHPUnit and SimpleTest. +
    +
    + + Suite Tester + +
    +
    + Suite Tester is an AJAX-driven add-on for the + PHP SimpleTest unit-testing tool. The main benefits are conveniently testing + an entire application or a single unit in one place, and quickly viewing + a suite's results one unit at a time. +
    + +
    +
    +
    +

    + Others are using SimpleTest to build stuff : +

    +
    +
    + + CakePHP + +
    +
    + CakePHP is a rapid development framework for PHP + which uses commonly known design patterns like ActiveRecord, + Association Data Mapping, Front Controller and MVC. +
    +
    + + Web Application Component Toolkit + +
    +
    + The Web Application Component Toolkit is + a framework for creating web applications. + WACT facilitates a modular approach where individual, + independent or reusable components may be integrated into a larger web + application. WACT assists in implementing the Model View Controller + pattern and the related Domain Model, Template View, Front Controller + and Application Controller patterns. +
    +
    + + Limb3 + +
    +
    + Limb is an OpenSource(LGPL) PHP framework aimed for + rapid web application development. + It is a committed proponents of the beautiful and + easy-to-maintain code that simply works. + That is why the Limb code is developed and constantly refactored in a + test driven manner using the best agile development practices. +
    +
    + + Drupal | SimpleTest + +
    +
    + A framework for running automated unit tests in Drupal. + This module comes with a set of core tests. These can be helpful when + making patches for the drupal core or when building your own set of + modules. +
    +
    + + WideImage + +
    +
    + WideImage is an object-oriented PHP image library, written + in/for PHP5. Uses GD2 and is unit-tested with Simpletest. Promotes ease of + use and extensibility. +
    +
    + + Aperiplus + +
    +
    + Aperiplus is a general purpose, unit-tested, OOP library + for PHP5. It contains extensions for SimpleTest. +
    +
    + + Flux CMS + +
    +
    + Flux CMS is a XML/XSLT CMS based on PHP 5 and Popoon. + It's easy to use for the enduser and has WYSIWYG editing capabilites, + but it's also very extensible and powerful for implementors to suit + your needs. +
    +
    + + Moodle + +
    +
    + Moodle is a course management system (CMS) - + a free, Open Source software package designed using sound pedagogical + principles, to help educators create effective online learning communities. +
    +
    +
    +
    + + + Projects extending SimpleTest + + + And some using SimpleTest + + + + + + + + + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/speakers_coaches_consultancy.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/speakers_coaches_consultancy.xml new file mode 100644 index 0000000..ed0a67e --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/speakers_coaches_consultancy.xml @@ -0,0 +1,94 @@ + + + + Speakers, coaches and consultants + +
    +

    + Providing a tool -- SimpleTest -- to enable developpers + get started with Unit Testing and TDD is sometimes not enough. +

    +

    + You need the "whole solution" : enjoying the knowledge of an + experienced coach or consultant can boost your strategy. + The team developping SimpleTest can offer in-depth expertise + to you projects. +

    +
    +
    + + + + + + +
    +
    + + + + Providing the whole solution + + + Speakers, coaches and consultants close to you + + + + + + software development, + computer programmer, + php programming, + programming php, + software development company, + software development uk, + php tutorial, + bespoke software development uk, + corporate web development, + architecture, + freelancer, + php resources, + wordtracker, + web marketing, + serach engines, + web positioning, + internet marketing + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/subclass_tutorial.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/subclass_tutorial.xml new file mode 100644 index 0000000..17c5ce9 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/subclass_tutorial.xml @@ -0,0 +1,255 @@ + + + + PHP unit testing tutorial - Subclassing a test case + +
    +

    + We left our clock test with a hole. + If the PHP time() + function rolled over during this comparison... +assertEqual($clock->now(), time(), 'Now is the right time'); +} +]]> + ...our test would be out by one second and would cause + a false failure. + Erratic behaviour of our test suite is not what we want when + we could be running it a hundred times a day. +

    +

    + We could rewrite the test as... + + $time1 = $clock->now(); + $time2 = time(); + $this->assertTrue($time1 == $time2) || ($time1 + 1 == $time2), 'Now is the right time'); +} +]]> + This is hardly a clear design though and we will have to repeat + this for every timing test that we do. + Repetition is public enemy number one and so we'll + use this as incentive to factor out the new test code. +UnitTestCase('Clock class test'); + } + function assertSameTime($time1, $time2, $message) { + $this->assertTrue( + ($time1 == $time2) || ($time1 + 1 == $time2), + $message); + } + function testClockTellsTime() { + $clock = new Clock(); + $this->assertSameTime($clock->now(), time(), 'Now is the right time'); + } + function testClockAdvance() { + $clock = new Clock(); + $clock->advance(10); + $this->assertSameTime($clock->now(), time() + 10, 'Advancement'); + } +} +]]> + Of course each time I make one of these changes I rerun the + tests to make sure we are still OK. + Refactor on green. + It's a lot safer. +

    +
    +
    +

    + It may be that we want more than one test case that is + timing sensitive. + Perhaps we are reading timestamps from database rows + or other places that could allow an extra second to + tick over. + For these new test classes to take advantage of our new assertion + we need to place it into a superclass. +

    +

    + Here is the complete clock_test.php file after + promoting our assertSameTime() + method to its own superclass... + +class TimeTestCase extends UnitTestCase { + function TimeTestCase($test_name) { + $this->UnitTestCase($test_name); + } + function assertSameTime($time1, $time2, $message) { + $this->assertTrue( + ($time1 == $time2) || ($time1 + 1 == $time2), + $message); + } +} + +class TestOfClock extends TimeTestCase { + function TestOfClock() { + $this->TimeTestCase('Clock class test'); + } + function testClockTellsTime() { + $clock = new Clock(); + $this->assertSameTime($clock->now(), time(), 'Now is the right time'); + } + function testClockAdvance() { + $clock = new Clock(); + $clock->advance(10); + $this->assertSameTime($clock->now(), time() + 10, 'Advancement'); + } +} +?> +]]> + Now we get the benefit of our new assertion every + time we inherit from our own + TimeTestCase class + rather than the default + UnitTestCase. + This is very much how the JUnit tool was designed + to be used and SimpleTest is a port of that interface. + It is a testing framework from which your own test + system can be grown. +

    +

    + If we run the tests now we get a slight niggle... +

    + Warning: Missing argument 1 for timetestcase() + in /home/marcus/projects/lastcraft/tutorial_tests/tests/clock_test.php on line 5
    +

    All tests

    +
    3/3 test cases complete. + 6 passes and 0 fails.
    +
    + The reasons for this are quite tricky. +

    +

    + Our subclass requires a constructor parameter that has + not been supplied and yet it appears that we did + supply it. + When we inherited our new class we passed it in our own + constructor. + It's right here... +TimeTestCase('Clock class test'); +} +]]> + In fact we are right, that is not the problem. +

    +

    + Remember when we built our all_tests.php + group test by using the + addTestFile() method. + This method looks for test case classes, instantiates + them if they are new and then runs all of their tests. + What's happened is that it has found our + test case extension as well. + This is harmless as there are no test methods within it, + that is, method names that start with the string + "test". + No extra tests are run. +

    +

    + The trouble is that it instantiates the class and does this without + the $test_name parameter + which is what causes our warning. + This parameter is not normally required of a test + case and not normally of its assertions either. + To make our extended test case match the + UnitTestCase interface + we must make these optional... +$test_name = false) { + $this->UnitTestCase($test_name); + } + function assertSameTime($time1, $time2, $message = false) { + if (! $message) { + $message = "Time [$time1] should match time [$time2]"; + } + $this->assertTrue( + ($time1 == $time2) || ($time1 + 1 == $time2), + $message); + } +} +]]> + Of course it should still bother you that this class is + instantiated by the test suite unnecessarily. + Here is a modification to prevent it running... +SimpleTestOptions::ignore('TimeTestCase'); +class TimeTestCase extends UnitTestCase { + function TimeTestCase($test_name = false) { + $this->UnitTestCase($test_name); + } + function assertSameTime($time1, $time2, $message = '') { + if (!$message) { + $message = "Time [$time1] should match time [$time2]"; + } + $this->assertTrue( + ($time1 == $time2) || ($time1 + 1 == $time2), + $message); + } +} +]]> + This just tells SimpleTest to always ignore this class when + building test suites. + It can be included anywhere in the test case file. +

    +

    + Six passes looks good, but does not tell the casual + observer what has been tested. + For that you have to look at the code. + If that sounds like drudge to you and you would like this + information displayed before you then we should go on + to show the passes next. +

    +
    +
    + + + A timing insensitive assertion + that allows a one second gain. + + + Subclassing the test case + so as to reuse the test method. + + + + + The previous section was + controlling test variables. + + + The next tutorial section was + changing the test display. + + + You will need the + SimpleTest test tool to run the + sample code. + + + + + software development, + test case example, + programming php, + software development tools, + php tutorial, + creating subclass, + free php scripts, + architecture, + php resources, + junit, + phpunit style testing, + unit test, + php testing + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/support_website.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/support_website.xml new file mode 100644 index 0000000..15b179d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/support_website.xml @@ -0,0 +1,59 @@ + + + + Support mailing list + + +

    + The simpletest-support mailing-list is probably + the most active area around SimpleTest : + help, advice, bugs and workarounds tend to happen most of the time. +

    +
    +
    +

    + It's really + + easy to subscribe + and it's + + fully searchable too. +

    +

    + At the last count, there were about 114 subscribers and 1908 message sent. + That's anything between 1 and 4 messages per day on average. +

    +
    +
    + + + To subscribe. + + + + + Subscribing to the + + Support mailing list. + + + Reading the + + archive. + + + + + SimpleTest, + download, + source code, + stable release, + eclipse release, + eclipse plugin, + debian package, + drupal module, + pear channel, + pearified package + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/unit_test_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/unit_test_documentation.xml new file mode 100644 index 0000000..adc9236 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/unit_test_documentation.xml @@ -0,0 +1,313 @@ + + + + SimpleTest for PHP regression test documentation + +
    +

    + The core system is a regression testing framework built around + test cases. + A sample test case looks like this... +class FileTestCase extends UnitTestCase { +} +]]> + Actual tests are added as methods in the test case whose names + by default start with the string "test" and + when the test case is invoked all such methods are run in + the order that PHP introspection finds them. + As many test methods can be added as needed. +

    +

    + For example... +UnitTestCase('File test'); + } + + function setUp() { + @unlink('../temp/test.txt'); + } + + function tearDown() { + @unlink('../temp/test.txt'); + } + + function testCreation() { + $writer = &new FileWriter('../temp/test.txt'); + $writer->write('Hello'); + $this->assertTrue(file_exists('../temp/test.txt'), 'File created'); + } +} +]]> + The constructor is optional and usually omitted. + Without a name, the class name is taken as the name of the test case. +

    +

    + Our only test method at the moment is testCreation() + where we check that a file has been created by our + Writer object. + We could have put the unlink() + code into this method as well, but by placing it in + setUp() and + tearDown() we can use it with + other test methods that we add. +

    +

    + The setUp() method is run + just before each and every test method. + tearDown() is run just after + each and every test method. +

    +

    + You can place some test case set up into the constructor to + be run once for all the methods in the test case, but + you risk test inteference that way. + This way is slightly slower, but it is safer. + Note that if you come from a JUnit background this will not + be the behaviour you are used to. + JUnit surprisingly reinstantiates the test case for each test + method to prevent such interference. + SimpleTest requires the end user to use setUp(), but + supplies additional hooks for library writers. +

    +

    + The means of reporting test results (see below) are by a + visiting display class + that is notified by various assert...() + methods. + Here is the full list for the UnitTestCase + class, the default for SimpleTest... + + + + + + + + + + + + + + + + + + + +
    assertTrue($x)Fail if $x is false
    assertFalse($x)Fail if $x is true
    assertNull($x)Fail if $x is set
    assertNotNull($x)Fail if $x not set
    assertIsA($x, $t)Fail if $x is not the class or type $t
    assertNotA($x, $t)Fail if $x is of the class or type $t
    assertEqual($x, $y)Fail if $x == $y is false
    assertNotEqual($x, $y)Fail if $x == $y is true
    assertWithinMargin($x, $y, $m)Fail if abs($x - $y) < $m is false
    assertOutsideMargin($x, $y, $m)Fail if abs($x - $y) < $m is true
    assertIdentical($x, $y)Fail if $x == $y is false or a type mismatch
    assertNotIdentical($x, $y)Fail if $x == $y is true and types match
    assertReference($x, $y)Fail unless $x and $y are the same variable
    assertClone($x, $y)Fail unless $x and $y are identical copies
    assertPattern($p, $x)Fail unless the regex $p matches $x
    assertNoPattern($p, $x)Fail if the regex $p matches $x
    expectError($x)Swallows any upcoming matching error
    assert($e)Fail on failed expectation object $e
    + All assertion methods can take an optional description as a + last parameter. + This is to label the displayed result with. + If omitted a default message is sent instead, which is usually + sufficient. + This default message can still be embedded in your own message + if you include "%s" within the string. + All the assertions return true on a pass or false on failure. +

    +

    + Some examples... +$this->assertNull($variable, 'Should be cleared'); +]]> + ...will pass and normally show no message. + If you have + set up the tester to display passes + as well then the message will be displayed as is. +$this->assertIdentical(0, false, 'Zero is not false [%s]'); +]]> + This will fail as it performs a type + check, as well as a comparison, between the two values. + The "%s" part is replaced by the default + error message that would have been shown if we had not + supplied our own. +$this->assertReference($a, $b); +]]> + Will fail as the variable $a is a copy of $b. +$this->assertPattern('/hello/i', 'Hello world'); +]]> + This will pass as using a case insensitive match the string + hello is contained in Hello world. +$this->expectError(); +trigger_error('Catastrophe'); +]]> + Here the check catches the "Catastrophe" + message without checking the text and passes. + This removes the error from the queue. +$this->expectError('Catastrophe'); +trigger_error('Catastrophe'); +]]> + The next error check tests not only the existence of the error, + but also the text which, here matches so another pass. + If any unchecked errors are left at the end of a test method then + an exception will be reported in the test. +

    +

    + Note that SimpleTest cannot catch compile time PHP errors. +

    +

    + The test cases also have some convenience methods for debugging + code or extending the suite... + + + + + + + + +
    setUp()Runs this before each test method
    tearDown()Runs this after each test method
    pass()Sends a test pass
    fail()Sends a test failure
    error()Sends an exception event
    signal($type, $payload)Sends a user defined message to the test reporter
    dump($var)Does a formatted print_r() for quick and dirty debugging
    +

    +
    +
    +

    + Of course additional test methods can be added to create + specific types of test case, so as to extend framework... + +class FileTester extends UnitTestCase { + function FileTester($name = false) { + $this->UnitTestCase($name); + } + + function assertFileExists($filename, $message = '%s') { + $this->assertTrue( + file_exists($filename), + sprintf($message, 'File [$filename] existence check')); + } +} +]]> + Here the SimpleTest library is held in a folder called + simpletest that is local. + Substitute your own path for this. +

    +

    + To prevent this test case being run accidently, it is + advisable to mark it as abstract. +

    +

    + Alternatively you could add a + SimpleTestOptions::ignore('FileTester'); + directive in your code. +

    +

    + This new case can be now be inherited just like + a normal test case... +FileTester { + + function setUp() { + @unlink('../temp/test.txt'); + } + + function tearDown() { + @unlink('../temp/test.txt'); + } + + function testCreation() { + $writer = &new FileWriter('../temp/test.txt'); + $writer->write('Hello'); + $this->assertFileExists('../temp/test.txt'); + } +} +]]> +

    +

    + If you want a test case that does not have all of the + UnitTestCase assertions, + only your own and a few basics, + you need to extend the SimpleTestCase + class instead. + It is found in simple_test.php rather than + unit_tester.php. + See later if you + want to incorporate other unit tester's + test cases in your test suites. +

    +
    +
    +

    + You won't often run single test cases except when bashing + away at a module that is having difficulty, and you don't + want to upset the main test suite. + With autorun no particular scaffolding is needed, + just launch your particular test file and you're ready to go. +

    +

    + You can even decide which reporter (for example, + TextReporter or HtmlReporter) + you prefer for a specific file when launched on its own... + +SimpleTest :: prefer(new TextReporter()); +require_once('../classes/writer.php'); + +class FileTestCase extends UnitTestCase { + ... +} +?> +]]> + This script will run as is, but of course will output zero passes + and zero failures until test methods are added. +

    +
    +
    + + + Unit test cases and basic assertions. + + + Extending test cases to + customise them for your own project. + + + Running a single case as + a single script. + + + + + SimpleTest project page on SourceForge. + + + SimpleTest download page on LastCraft. + + + Full API for SimpleTest + from the PHPDoc. + + + + + php unit testing, + test integration, + documentation, + marcus baker, + simple test, + simpletest documentation, + phpunit, + junit, + xunit, + agile web development, + eXtreme Programming, + Test Driven, + TDD + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/web_tester_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/web_tester_documentation.xml new file mode 100644 index 0000000..e2c9699 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/web_tester_documentation.xml @@ -0,0 +1,379 @@ + + + + Simple Test for PHP web script testing documentation + +
    +

    + Testing classes is all very well, but PHP is predominately + a language for creating functionality within web pages. + How do we test the front end presentation role of our PHP + applications? + Well the web pages are just text, so we should be able to + examine them just like any other test data. +

    +

    + This leads to a tricky issue. + If we test at too low a level, testing for matching tags + in the page with pattern matching for example, our tests will + be brittle. + The slightest change in layout could break a large number of + tests. + If we test at too high a level, say using mock versions of a + template engine, then we lose the ability to automate some classes + of test. + For example, the interaction of forms and navigation will + have to be tested manually. + These types of test are extremely repetitive and error prone. +

    +

    + SimpleTest includes a special form of test case for the testing + of web page actions. + The WebTestCase includes facilities + for navigation, content and cookie checks and form handling. + Usage of these test cases is similar to the + UnitTestCase... +class TestOfLastcraft extends WebTestCase { +} +]]> + Here we are about to test the + Last Craft site itself. + If this test case is in a file called lastcraft_test.php + then it can be loaded in a runner script just like unit tests... + +require_once('simpletest/web_tester.php'); +SimpleTest::prefer(new TextReporter()); + +class WebTests extends TestSuite { + function WebTests() { + $this->TestSuite('Web site tests'); + $this->addFile('lastcraft_test.php'); + } +} +?> +]]> + I am using the text reporter here to more clearly + distinguish the web content from the test output. +

    +

    + Nothing is being tested yet. + We can fetch the home page by using the + get() method... + + function testHomepage() { + $this->assertTrue($this->get('http://www.lastcraft.com/')); + } +} +]]> + The get() method will + return true only if page content was successfully + loaded. + It is a simple, but crude way to check that a web page + was actually delivered by the web server. + However that content may be a 404 response and yet + our get() method will still return true. +

    +

    + Assuming that the web server for the Last Craft site is up + (sadly not always the case), we should see... +

    +Web site tests
    +OK
    +Test cases run: 1/1, Failures: 0, Exceptions: 0
    +
    + All we have really checked is that any kind of page was + returned. + We don't yet know if it was the right one. +

    +
    +
    +

    + To confirm that the page we think we are on is actually the + page we are on, we need to verify the page content. + + $this->get('http://www.lastcraft.com/'); + $this->assertText('Why the last craft'); + } +} +]]> + The page from the last fetch is held in a buffer in + the test case, so there is no need to refer to it directly. + The pattern match is always made against the buffer. +

    +

    + Here is the list of possible content assertions... + + + + + + + + + + + + + + + + + + + + + +
    assertTitle($title)Pass if title is an exact match
    assertText($text)Pass if matches visible and "alt" text
    assertNoText($text)Pass if doesn't match visible and "alt" text
    assertPattern($pattern)A Perl pattern match against the page content
    assertNoPattern($pattern)A Perl pattern match to not find content
    assertLink($label)Pass if a link with this text is present
    assertNoLink($label)Pass if no link with this text is present
    assertLinkById($id)Pass if a link with this id attribute is present
    assertNoLinkById($id)Pass if no link with this id attribute is present
    assertField($name, $value)Pass if an input tag with this name has this value
    assertFieldById($id, $value)Pass if an input tag with this id has this value
    assertResponse($codes)Pass if HTTP response matches this list
    assertMime($types)Pass if MIME type is in this list
    assertAuthentication($protocol)Pass if the current challenge is this protocol
    assertNoAuthentication()Pass if there is no current challenge
    assertRealm($name)Pass if the current challenge realm matches
    assertHeader($header, $content)Pass if a header was fetched matching this value
    assertNoHeader($header)Pass if a header was not fetched
    assertCookie($name, $value)Pass if there is currently a matching cookie
    assertNoCookie($name)Pass if there is currently no cookie of this name
    + As usual with the SimpleTest assertions, they all return + false on failure and true on pass. + They also allow an optional test message and you can embed + the original test message inside using "%s" inside + your custom message. +

    +

    + So now we could instead test against the title tag with... +$this->assertTitle('The Last Craft? Web developer tutorials on PHP, Extreme programming and Object Oriented development'); +]]> + ...or, if that is too long and fragile... +$this->assertTitle(new PatternExpectation('/The Last Craft/')); +]]> + As well as the simple HTML content checks we can check + that the MIME type is in a list of allowed types with... +$this->assertMime(array('text/plain', 'text/html')); +]]> + More interesting is checking the HTTP response code. + Like the MIME type, we can assert that the response code + is in a list of allowed values... +get('http://www.lastcraft.com/test/redirect.php'); + $this->assertResponse(200); + } +} +]]> + Here we are checking that the fetch is successful by + allowing only a 200 HTTP response. + This test will pass, but it is not actually correct to do so. + There is no page, instead the server issues a redirect. + The WebTestCase will + automatically follow up to three such redirects. + The tests are more robust this way and we are usually + interested in the interaction with the pages rather + than their delivery. + If the redirects are of interest then this ability must + be disabled... + + $this->setMaximumRedirects(0); + $this->get('http://www.lastcraft.com/test/redirect.php'); + $this->assertResponse(200); + } +} +]]> + The assertion now fails as expected... +

    +Web site tests
    +1) Expecting response in [200] got [302]
    +    in testhomepage
    +    in testoflastcraft
    +    in lastcraft_test.php
    +FAILURES!!!
    +Test cases run: 1/1, Failures: 1, Exceptions: 0
    +
    + We can modify the test to correctly assert redirects with... +setMaximumRedirects(0); + $this->get('http://www.lastcraft.com/test/redirect.php'); + $this->assertResponse(array(301, 302, 303, 307)); + } +} +]]> + This now passes. +

    +
    +
    +

    + Users don't often navigate sites by typing in URLs, but by + clicking links and buttons. + Here we confirm that the contact details can be reached + from the home page... +get('http://www.lastcraft.com/'); + $this->clickLink('About'); + $this->assertTitle(new PatternExpectation('/About Last Craft/')); + } +} +]]> + The parameter is the text of the link. +

    +

    + If the target is a button rather than an anchor tag, then + clickSubmit() can be used + with the button title... +$this->clickSubmit('Go!'); +]]> + If you are not sure or don't care, the usual case, then just + use the click() method... +$this->click('Go!'); +]]> +

    +

    + The list of navigation methods is... + + + + + + + + + + + + + + + + + + + + + + + + + +
    getUrl()The current location
    get($url, $parameters)Send a GET request with these parameters
    post($url, $parameters)Send a POST request with these parameters
    head($url, $parameters)Send a HEAD request without replacing the page content
    retry()Reload the last request
    back()Like the browser back button
    forward()Like the browser forward button
    authenticate($name, $password)Retry after a challenge
    restart()Restarts the browser as if a new session
    getCookie($name)Gets the cookie value for the current context
    ageCookies($interval)Ages current cookies prior to a restart
    clearFrameFocus()Go back to treating all frames as one page
    clickSubmit($label)Click the first button with this label
    clickSubmitByName($name)Click the button with this name attribute
    clickSubmitById($id)Click the button with this ID attribute
    clickImage($label, $x, $y)Click an input tag of type image by title or alt text
    clickImageByName($name, $x, $y)Click an input tag of type image by name
    clickImageById($id, $x, $y)Click an input tag of type image by ID attribute
    submitFormById($id)Submit a form without the submit value
    clickLink($label, $index)Click an anchor by the visible label text
    clickLinkById($id)Click an anchor by the ID attribute
    getFrameFocus()The name of the currently selected frame
    setFrameFocusByIndex($choice)Focus on a frame counting from 1
    setFrameFocus($name)Focus on a frame by name
    +

    +

    + The parameters in the get(), post() or + head() methods are optional. + The HTTP HEAD fetch does not change the browser context, only loads + cookies. + This can be useful for when an image or stylesheet sets a cookie + for crafty robot blocking. +

    +

    + The retry(), back() and + forward() commands work as they would on + your web browser. + They use the history to retry pages. + This can be handy for checking the effect of hitting the + back button on your forms. +

    +

    + The frame methods need a little explanation. + By default a framed page is treated just like any other. + Content will be searced for throughout the entire frameset, + so clicking a link will work no matter which frame + the anchor tag is in. + You can override this behaviour by focusing on a single + frame. + If you do that, all searches and actions will apply to that + frame alone, such as authentication and retries. + If a link or button is not in a focused frame then it cannot + be clicked. +

    +

    + Testing navigation on fixed pages only tells you when you + have broken an entire script. + For highly dynamic pages, such as for bulletin boards, this can + be crucial for verifying the correctness of the application. + For most applications though, the really tricky logic is usually in + the handling of forms and sessions. + Fortunately SimpleTest includes + tools for testing web forms + as well. +

    +
    +
    +

    + Although SimpleTest does not have the goal of testing networking + problems, it does include some methods to modify and debug + the requests it makes. + Here is another method list... + + + + + + + + + + + +
    getTransportError()The last socket error
    showRequest()Dump the outgoing request
    showHeaders()Dump the incoming headers
    showSource()Dump the raw HTML page content
    ignoreFrames()Do not load framesets
    setCookie($name, $value)Set a cookie from now on
    addHeader($header)Always add this header to the request
    setMaximumRedirects($max)Stop after this many redirects
    setConnectionTimeout($timeout)Kill the connection after this time between bytes
    useProxy($proxy, $name, $password)Make requests via this proxy URL
    + These methods are principally for debugging. +

    +
    +
    + + + Successfully fetching a web page + + + Testing the page content + + + Navigating a web site + while testing + + + Raw request modifications and debugging methods + + + + + SimpleTest project page on SourceForge. + + + SimpleTest download page on LastCraft. + + + The developer's API for SimpleTest + gives full detail on the classes and assertions available. + + + + + software development, + php programming for clients, + customer focused php, + software development tools, + acceptance testing framework, + free php scripts, + architecture, + php resources, + HTMLUnit, + JWebUnit, + php testing, + unit test resource, + web testing + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/writing_extensions.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/writing_extensions.xml new file mode 100644 index 0000000..6e34afc --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/en/writing_extensions.xml @@ -0,0 +1,128 @@ + + + + Writing an extension for SimpleTest + + +

    + SimpleTest is coded to be flexible and extensible. + It means you can add your own assertions. + You can also add your own reporter or any other tool worth your fancy. + In fact there's a special directory inside SimpleTest SVN trunk devoted + to such kind of experimentations. +

    +
    +
    +

    + There's a growing list of available extensions: +

    +
    +
    colortext_reporter
    +
    + provides an ANSI-colored {@link TextReporter} for viewing test results. +
    +
    dom_tester
    +
    + create a CSS Selector expectation. +
    +
    pear_test_case
    +
    + provides an adapter for PEAR PHPUnit test case to allow + legacy PEAR test cases to be used with SimpleTest. +
    +
    phpunit_test_case
    +
    + provides an adapter for sourceforge PHPUnit test case to allow + legacy test cases to be used with SimpleTest. +
    +
    recorder
    +
    + returns an array with timestamp, status, test name + and message for each pass and failure. +
    +
    selenese_tester
    +
    + integrates selenese html test suite support + (can be generated by selenium-IDE). +
    +
    selenium
    +
    + provides a bridge to a Selenium server. +
    +
    testdox
    +
    + ... +
    +
    treemap_reporter
    +
    + constructs and renders a treemap visualization of a test run. +
    +
    webunit_reporter
    +
    + ... +
    +
    +
    +
    +

    + If you want to have your own extension accepted inside the SVN trunk, + juste make sure you follow these simple rules: +

      +
    1. + Main code goes into a file located at
      + /extensions/my_new_extension.php +
    2. +
    3. + If you need extra stuff (classes, sub-routines, js or css files), + make sure everything is located your own
      + /extensions/my_new_extension/* +
    4. +
    5. + And since your package is unit-tested, + your tests go into
      + /extensions/my_new_extension/test/*
      + or
      + /extensions/my_new_extension/* +
    6. +
    7. + Bonus : you can get your own test suite caught directly + from SimpleTest. There's dedicated test scanning recursively + the extensions' directories and running the test file matching + /test.php$/. + An easy way for us to check if everyone's extension carries + on working with the current code base... +
    8. +
    + Also for general coding practices, you can have a look at the + coding standards and formatting. +

    +
    +
    + + Existing extensions + Committing a new extension + + + + + + software development, + php programming, + programming php, + software development tools, + php tutorial, + free php scripts, + architecture, + php resources, + mock objects, + plugins, + extensions, + extension, + selenium, + selenese, + testdox, + treemap, + reporter + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/authentication_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/authentication_documentation.xml new file mode 100644 index 0000000..b3ef8d1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/authentication_documentation.xml @@ -0,0 +1,307 @@ + + + + + Documentation Simple Test : tester l'authentification + + +

    + Un des secteurs à la fois délicat et important lors d'un test + de site web reste la sécurité. Tester ces schémas est au coeur + des objectifs du testeur web de SimpleTest. +

    +
    +
    +

    + Si vous allez chercher une page web protégée + par une authentification basique, vous hériterez d'une entête 401. + Nous pouvons représenter ceci par ce test... + + function test401Header() { + $this->get('http://www.lastcraft.com/protected/'); + $this->showHeaders(); + } +} +]]> + Ce qui nous permet de voir les entêtes reçues... +

    +

    File test

    +
    +
    1/1 test cases complete. + 0 passes, 0 fails and 0 exceptions.
    +
    + Sauf que nous voulons éviter l'inspection visuelle, + on souhaite que SimpleTest puisse nous dire si oui ou non + la page est protégée. Voici un test en profondeur sur nos entêtes... +get('http://www.lastcraft.com/protected/'); + $this->assertAuthentication('Basic'); + $this->assertResponse(401); + $this->assertRealm('SimpleTest basic authentication'); + } +} +]]> + N'importe laquelle de ces assertions suffirait, + tout dépend de la masse de détails que vous souhaitez voir. +

    +

    + La plupart du temps, nous ne souhaitons pas tester + l'authentification en elle-même, mais plutôt + les pages protégées par cette authentification. + Dès que la tentative d'authentification est reçue, + nous pouvons y répondre à l'aide d'une réponse d'authentification : +get('http://www.lastcraft.com/protected/'); + $this->authenticate('Me', 'Secret'); + $this->assertTitle(...); + } +} +]]> + Le nom d'utilisateur et le mot de passe seront désormais + envoyés à chaque requête vers ce répertoire + et ses sous-répertoires. + En revanche vous devrez vous authentifier à nouveau + si vous sortez de ce répertoire mais SimpleTest est assez + intelligent pour fusionner les sous-répertoires dans un même domaine. +

    +

    + Vous pouvez gagner une ligne en définissant + l'authentification au niveau de l'URL... +get('http://Me:Secret@www.lastcraft.com/protected/'); + $this->assertTitle(...); + } +} +]]> + Si votre nom d'utilisateur ou mot de passe comporte + des caractères spéciaux, alors n'oubliez pas de les encoder, + sinon la requête ne sera pas analysée correctement. + De plus cette entête ne sera pas envoyée aux + sous requêtes si vous la définissez avec une URL absolue. + Par contre si vous naviguez avec des URL relatives, + l'information d'authentification sera préservée. +

    +

    + Pour l'instant, seule l'authentification de base est implémentée + et elle n'est réellement fiable qu'en tandem avec une connexion HTTPS. + C'est généralement suffisant pour protéger + le serveur testé des regards malveillants. + Les authentifications Digest et NTLM pourraient être ajoutées prochainement. +

    +
    +
    +

    + L'authentification de base ne donne pas assez de contrôle + au développeur Web sur l'interface utilisateur. + Il y a de forte chance pour que cette fonctionnalité + soit codée directement dans l'architecture web + à grand renfort de cookies et de timeouts compliqués. +

    +

    + Commençons par un simple formulaire de connexion... +

    
    +    Username:
    +    
    + Password: +
    + + +]]>
    + Lequel doit ressembler à... +

    +

    +

    + Username: +
    + Password: +
    + +
    +

    +

    + Supposons que, durant le chargement de la page, + un cookie ait été inscrit avec un numéro d'identifiant de session. + Nous n'allons pas encore remplir le formulaire, + juste tester que nous pistons bien l'utilisateur. + Voici le test... +get('http://www.my-site.com/login.php'); + $this->assertCookie('SID'); + } +} +]]> + Nous nous contentons ici de vérifier que le cookie a bien été défini. + Etant donné que sa valeur est plutôt énigmatique, + elle ne vaut pas la peine d'être testée. +

    +

    + Le reste du test est le même que dans n'importe quel autre formulaire, + mais nous pourrions souhaiter nous assurer + que le cookie n'a pas été modifié depuis la phase de connexion. + Voici comment cela pourrait être testé : +get('http://www.my-site.com/login.php'); + $session = $this->getCookie('SID'); + $this->setField('u', 'Me'); + $this->setField('p', 'Secret'); + $this->clickSubmit('Log in'); + $this->assertWantedPattern('/Welcome Me/'); + $this->assertCookie('SID', $session); + } +} +]]> + Ceci confirme que l'identifiant de session + est identique avant et après la connexion. +

    +

    + Nous pouvons même essayer de duper notre propre système + en créant un cookie arbitraire pour se connecter... +get('http://www.my-site.com/login.php'); + $this->setCookie('SID', 'Some other session'); + $this->get('http://www.my-site.com/restricted.php'); + $this->assertWantedPattern('/Access denied/'); + } +} +]]> + Votre site est-il protégé contre ce type d'attaque ? +

    +
    +
    +

    + Si vous testez un système d'authentification, + la reconnexion par un utilisateur est un point sensible. + Essayons de simuler ce qui se passe dans ce cas : +get('http://www.my-site.com/login.php'); + $this->setField('u', 'Me'); + $this->setField('p', 'Secret'); + $this->clickSubmit('Log in'); + $this->assertWantedPattern('/Welcome Me/'); + + $this->restart(); + $this->get('http://www.my-site.com/restricted.php'); + $this->assertWantedPattern('/Access denied/'); + } +} +]]> + La méthode WebTestCase::restart() préserve les cookies + dont le timeout a expiré, mais conserve les cookies temporaires ou expirés. + Vous pouvez spécifier l'heure et la date de leur réactivation. +

    +

    + L'expiration des cookies peut être un problème. + Si vous avez un cookie qui doit expirer au bout d'une heure, + nous n'allons pas mettre le test en veille en attendant + que le cookie expire... +

    +

    + Afin de provoquer leur expiration, + vous pouvez dater manuellement les cookies, + avant le début de la session. +get('http://www.my-site.com/login.php'); + $this->setField('u', 'Me'); + $this->setField('p', 'Secret'); + $this->clickSubmit('Log in'); + $this->assertWantedPattern('/Welcome Me/'); + + $this->ageCookies(3600); + $this->restart(); + $this->get('http://www.my-site.com/restricted.php'); + $this->assertWantedPattern('/Access denied/'); + } +} +]]> + Après le redémarrage, les cookies seront plus vieux + d'une heure et que tous ceux dont la date d'expiration + sera passée auront disparus. +

    +
    +
    + + + Passer au travers d'une authentification HTTP basique + + + Tester l'authentification basée sur des cookies + + + Gérer les sessions du navigateur et les timeouts + + + + + La page du projet SimpleTest sur SourceForge. + + + La page de téléchargement de SimpleTest sur LastCraft. + + + L'API du développeur pour SimpleTest donne tous les détails sur les classes et les assertions disponibles. + + + + + développement logiciel, + programmation php, + php orienté client, + outils de développement logiciel, + tutorial php, + scripts php gratuits, + architecture, + ressources php, + objets fantaise, + php testing, + php unit, + méthodologie, + développement piloté par les tests, + outils tests html, + tester des web pages, + php objets fantaise, + naviguer automatiquement sur des sites web, + test automatisé, + scripting web, + HTMLUnit, + JWebUnit, + phpunit, + php unit testing, + php web testing, + test unitaire de système d'authentification, + authentification HTTP, + test de connexion, + test d'authentification, + test de sécurité + + +
    + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/books_website.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/books_website.xml new file mode 100644 index 0000000..5e198fd --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/books_website.xml @@ -0,0 +1,77 @@ + + + + + Livres avec / à propos / pas loin de SimpleTest + +
    +

    + De temps en temsp, un livre est recommandé sur la mailing-list, + vous le trouverez ci-dessous avec les commentaires originaux ! +

    +

    + Domain-Driven Design: Tackling Complexity in the Heart of Software +

    +
    + Le Développement Piloté par les Tests est principalement intéressé par + les mécanismes pour coder, mais il dit que c'est fini quand il n'y plus + de duplication. Le Développement Piloté par la Conception (DDD) considère + que le code est constamment in churn, changeant de terminologie au + fur et à mesure que le domaine devient plus clair, et aussi nourrisant le domaine + avec des nouveaux termes quand le code évolue..
    +
    + Et puis, c'est un super bouquin :) + [source] +
    +
    +
    +

    + Deux contributeurs de Simpletest ont écrit des livres sur PHP. + Tous les deux ont de nombreux exemples avec le framework SimpleTest. +

    +

    + PHP|Architect's Guide to PHP Design Patterns +
    + PHP|Architect's Guide to PHP Design Patterns
    + par Jason E. Sweat
    + (l'obtenir depuis : PHP|Architect | + Amazon ) +

    +

    + The PHP Anthology: Object Oriented PHP Solutions +
    + The PHP Anthology: Object Oriented PHP Solutions
    + par Harry Fuecks
    + (l'obtenir depuis : SitePoint | + Amazon | + critique sur Slashdot ) +

    +
    +
    +

    + Une manière d'aider l'équipe de contributeurs, c'est d'acheter des livres via + cete page avec Amazon (avec le mot-clé simpletest-21). + Nous adorons tous lire des trucs à la fois intéressants et stimulants. +

    +
    +
    + + + Latest recommandation + + + Books by contributors + + + Buying books + + + + + + + + + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/boundary_classes_tutorial.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/boundary_classes_tutorial.xml new file mode 100644 index 0000000..bf26c71 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/boundary_classes_tutorial.xml @@ -0,0 +1,410 @@ + + + + + + Tutorial de tests unitaires PHP - Organiser les tests unitaires et les scénarios de test de classe frontière + + + +

    + Vous pensez probablement que nous avons désormais épuisé + les modifications sur la classe Log + et qu'il n'y a plus rien à ajouter. + Sauf que les choses ne sont jamais simples avec la Programmation Orienté Objet. + Vous pensez comprendre un problème et un nouveau cas arrive : + il défie votre point de vue et vous conduit + vers une analyse encore plus profonde. + Je pensais comprendre la classe de log et + que seule la première page du tutorial l'utiliserait. + Après ça, je serais passé à quelque chose de plus compliqué. + Personne n'est plus surpris que moi de ne pas l'avoir bouclée. + En fait je pense que je viens à peine + de me rendre compte de ce qu'un loggueur fait. +

    +
    +
    +

    + Supposons que nous ne voulons plus seulement enregistrer + les logs vers un fichier. Nous pourrions vouloir les afficher à l'écran, + les envoyer au daemon syslog d'Unix(tm) via un socket. + Comment s'accommoder de tels changements ? +

    +

    + Le plus simple est de créer des sous-classes de Log + qui écrasent la méthode message() avec les nouvelles versions. + Ce système fonctionne bien à court terme, sauf qu'il a quelque chose + de subtilement mais foncièrement erroné. Supposons que nous créions + ces sous-classes et que nous ayons des loggueurs écrivant vers un fichier, + sur l'écran et via le réseau. Trois classes en tout : ça fonctionne. + Maintenant supposons que nous voulons ajouter une nouvelle classe de log + qui ajoute un filtrage par priorité des messages, ne laissant passer + que les messages d'un certain type, le tout suivant un fichier de configuration. +

    +

    + Nous sommes coincés. Si nous créons de nouvelles sous-classes, + nous devons le faire pour l'ensemble des trois classes, + ce qui nous donnerait six classes. L'envergure de la duplication est horrible. +

    +

    + Alors, est-ce que vous êtes en train de souhaiter que PHP ait + l'héritage multiple ? Effectivement, cela réduirait l'ampleur + de la tâche à court terme, mais aussi compliquerait quelque + chose qui devrait être une classe très simple. L'héritage multiple, + même supporté, devrait être utilisé avec le plus grand soin car + toutes sortes d'enchevêtrements peuvent en découler. + En fait ce soudain besoin nous dit quelque chose d'autre + - peut-être que notre erreur si situe au niveau de la conception. +

    +

    + Qu'est-ce que doit faire un loggueur ? Est-ce qu'il envoie + un message vers un fichier ? A l'écran ? Via le réseau ? Non. + Il envoie un message, point final. La cible de ses messages + peut être sélectionnée à l'initialisation du log, + mais après ça pas touche : le loggueur doit pouvoir combiner + et formater les éléments du message puisque tel est son véritable boulot. + Présumer que la cible fut un nom de fichier était une belle paire d'oeillères. +

    +
    +
    +

    + La solution de cette mauvaise passe est un classique. + Tout d'abord nous encapsulons la variation de la classe : + cela ajoute un niveau d'indirection. Au lieu d'introduire + le nom du fichier comme une chaîne, nous l'introduisons comme + "cette chose vers laquelle on écrit" et que + nous appelons un Writer. Retour aux tests... + + require_once('../classes/writer.php'); + Mock::generate('Clock'); + + class TestOfLogging extends UnitTestCase { + function TestOfLogging() { + $this->UnitTestCase('Log class test'); + } + function setUp() { + @unlink('../temp/test.log'); + } + function tearDown() { + @unlink('../temp/test.log'); + } + function getFileLine($filename, $index) { + $messages = file($filename); + return $messages[$index]; + } + function testCreatingNewFile() { + $log = new Log(new FileWriter('../temp/test.log')); + $this->assertFalse(file_exists('../temp/test.log'), 'Created before message'); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists('../temp/test.log'), 'File created'); + } + function testAppendingToFile() { + $log = new Log(new FileWriter('../temp/test.log')); + $log->message('Test line 1'); + $this->assertWantedPattern( + '/Test line 1/', + $this->getFileLine('../temp/test.log', 0)); + $log->message('Test line 2'); + $this->assertWantedPattern( + '/Test line 2/', + $this->getFileLine('../temp/test.log', 1)); + } + function testTimestamps() { + $clock = &new MockClock($this); + $clock->setReturnValue('now', 'Timestamp'); + $log = new Log(new FileWriter('../temp/test.log')); + $log->message('Test line', &$clock); + $this->assertWantedPattern( + '/Timestamp/', + $this->getFileLine('../temp/test.log', 0), + 'Found timestamp'); + } + } +?> +]]> + Je vais parcourir ces tests pas à pas pour ne pas ajouter + trop de confusion. J'ai remplacé les noms de fichier par + une classe imaginaire FileWriter en provenance + d'un fichier classes/writer.php. + Par conséquent les tests devraient planter + puisque nous n'avons pas encore écrit ce scripteur. + Doit-on le faire maintenant ? +

    +

    + Nous pourrions, mais ce n'est pas obligé. + Par contre nous avons besoin de créer l'interface, + ou alors il ne sera pas possible de la simuler. + Au final classes/writer.php ressemble à... + +]]> + Nous avons aussi besoin de modifier la classe Log... + + require_once('../classes/writer.php'); + + class Log { + var $_writer; + + function Log(&$writer) { + $this->_writer = &$writer; + } + + function message($message, $clock = false) { + if (! is_object($clock)) { + $clock = new Clock(); + } + $this->_writer->write("[" . $clock->now() . "] $message"); + } + } +?> +]]> + Il n'y a pas grand chose qui n'ait pas changé y compris + dans la plus petite de nos classes. Désormais les tests + s'exécutent mais ne passent pas, à moins que nous ajoutions + du code dans le scripteur. Alors que faisons nous ? +

    +

    + Nous pourrions commencer par écrire des tests et + développer la classe FileWriter parallèlement, + mais lors de cette étape nos tests de Log + continueraient d'échouer et de nous distraire. + En fait nous n'en avons pas besoin. +

    +

    + Une partie de notre objectif est de libérer la classe + du loggueur de l'emprise du système de fichiers + et il existe un moyen d'y arriver. + Tout d'abord nous créons le fichier tests/writer_test.php + de manière à avoir un endroit pour placer + notre code test en provenance de log_test.php + et que nous allons brasser. Sauf que je ne vais pas l'ajouter + dans le fichier all_tests.php pour l'instant + puisque qu'il s'agit de la partie de log que nous sommes en train d'aborder. +

    +

    + Nous enlevons tous les test de log_test.php + qui ne sont pas strictement en lien avec le journal + et nous les gardons bien précieusement dans + writer_test.php pour plus tard. + Nous allons aussi simuler le scripteur pour qu'il n'écrive pas + réellement dans un fichier... + + Mock::generate('FileWriter'); + + class TestOfLogging extends UnitTestCase { + function TestOfLogging() { + $this->UnitTestCase('Log class test'); + } + function testWriting() { + $clock = &new MockClock($this); + $clock->setReturnValue('now', 'Timestamp'); + $writer = &new MockFileWriter($this); + $writer->expectArguments('write', array('[Timestamp] Test line')); + $writer->expectCallCount('write', 1); + $log = &new Log($writer); + $log->message('Test line', &$clock); + $writer->tally(); + } + } +?> +]]> + Eh oui c'est tout : il s'agit bien de l'ensemble du scénario de test + et c'est normal qu'il soit aussi court. Pas mal de choses se sont passées... +

      +
    1. + La nécessité de créer le fichier uniquement + si nécessaire a été déplacée vers le FileWriter. +
    2. +
    3. + Étant donné que nous travaillons avec des objets fantaisie, + aucun fichier n'a été créé et donc setUp() + et tearDown() passent dans les tests du scripteur. +
    4. +
    5. + Désormais le test consiste simplement dans l'envoi + d'un message type et du test de son format. +
    6. +
    + Attendez un instant, où sont les assertions ? +

    +

    + Les objets fantaisie font beaucoup plus que se comporter + comme des objets, ils exécutent aussi des test. + L'appel expectArguments() dit à l'objet fantaisie + d'attendre un seul paramètre de la chaîne "[Timestamp] Test" + quand la méthode fantaise write() est appelée. + Lorsque cette méthode est appelée les paramètres attendus + sont comparés avec ceci et un succès ou un échec est renvoyé + comme résultat au test unitaire. + C'est pourquoi un nouvel objet fantaisie a une référence + vers $this dans son constructeur, + il a besoin de ce $this pour l'envoi de son propre résultat. +

    +

    + L'autre attente, c'est que le write ne soit appelé + qu'une seule et unique fois. Juste l'initialiser ne serait pas suffisant. + L'objet fantaisie attendrait une éternité + si la méthode n'était jamais appelée + et par conséquent n'enverrait jamais + le message d'erreur à la fin du test. + Pour y faire face, l'appel tally() lui dit de vérifier + le nombre d'appel à ce moment là. + Nous pouvons voir tout ça en lançant les tests... +

    +

    All tests

    + Pass: log_test.php->Log class test->testwriting->Arguments for [write] were [String: [Timestamp] Test line]
    + Pass: log_test.php->Log class test->testwriting->Expected call count for [write] was [1], but got [1]
    + + Pass: clock_test.php->Clock class test->testclockadvance->Advancement
    + Pass: clock_test.php->Clock class test->testclocktellstime->Now is the right time
    +
    3/3 test cases complete. + 4 passes and 0 fails.
    +
    +

    +

    + En fait nous pouvons encore raccourcir nos tests. + L'attente de l'objet fantaisie expectOnce() + peut combiner les deux attentes séparées. +setReturnValue('now', 'Timestamp'); + $writer = &new MockFileWriter($this); + $writer->expectOnce('write', array('[Timestamp] Test line')); + $log = &new Log($writer); + $log->message('Test line', &$clock); + $writer->tally(); +} +]]> + Cela peut être une abréviation utile. +

    +
    +
    +

    + Quelque chose de très agréable est arrivée au loggueur + en plus de devenir purement et simplement plus court. +

    +

    + Les seules choses dont il dépend sont maintenant + des classes que nous avons écrites nous-même + et qui dans les tests sont simulées : + donc aucune dépendance hormis notre propre code PHP. + Pas de fichier à écrire ni de déclenchement + via une horloge à attendre. Cela veut dire que le scénario + de test log_test.php va s'exécuter aussi vite + que le processeur le permet. + Par contraste les classes FileWriter et Clock + sont très proches du système. + Plus difficile à tester puisque de vraies données + doivent être déplacées et validées avec soin, + souvent par des astuces ad hoc. +

    +

    + Notre dernière factorisation a beaucoup aidé. + Les classes aux frontières de l'application et du système, + celles qui sont difficiles à tester, sont désormais plus courtes + étant donné que le code d'I/O a été éloigné + encore plus de la logique applicative. + Il existe des liens directs vers des opérations PHP : + FileWriter::write() s'apparente à l'équivalent + PHP fwrite() avec le fichier ouvert pour l'ajout + et Clock::now() s'apparente lui aussi + à un équivalent PHP time(). + Primo le débogage devient plus simple. + Secundo ces classes devraient bouger moins souvent. +

    +

    + Si elles ne changent pas beaucoup alors il n'y a aucune raison + pour continuer à en exécuter les tests. + Cela veut dire que les tests pour les classes frontières + peuvent être déplacées vers leur propre suite de tests, + laissant les autres tourner à plein régime. + En fait c'est comme ça que j'ai tendance à travailler + et les scénarios de test de SimpleTest + lui-même sont divisés de cette manière. +

    +

    + Peut-être que ça ne vous paraît pas beaucoup + avec un test unitaire et deux tests aux frontières, + mais une application typique peut contenir + vingt classes de frontière et deux cent classes d'application. + Pour continuer leur exécution à toute vitesse, + vous voudrez les tenir séparées. +

    +

    + De plus, un bon développement passe par des décisions + de tri entre les composants à utiliser. + Peut-être, qui sait, tous ces simulacres pourront + améliorer votre conception. +

    +
    +
    + + + + Variations sur un log + + + Abstraire un niveau supplémentaire via une classe + fantaisie d'un scripteur + + + Séparer les tests des classes frontières + pour un petit nettoyage + + + + + Ce tutorial suit l'introduction aux + objets fantaisies. + + + Ensuite vient la + conception pilotée par les tests. + + + Vous aurez besoin du + framework de test SimpleTest pour essayer ces exemples. + + + + + développement logiciel, + programmation php, + outils de développement logiciel, + tutorial php, + scripts php gratuits, + organisation de tests unitaires, + conseil de test, + astuce de développement, + architecture logicielle pour des tests, + exemple de code php, + objets fantaisie, + port de junit, + exemples de scénarios de test, + test php, + outil de test unitaire, + suite de test php + + +
    + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/browser_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/browser_documentation.xml new file mode 100644 index 0000000..db373eb --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/browser_documentation.xml @@ -0,0 +1,259 @@ + + + + + Documentation SimpleTest : le composant de navigation web scriptable + + +

    + Le composant de navigation web de SimpleTest peut être utilisé + non seulement à l'extérieur de la classe WebTestCase, + mais aussi indépendamment du framework SimpleTest lui-même. +

    +
    +
    +

    + Vous pouvez utiliser le navigateur web dans des scripts PHP + pour confirmer que des services marchent bien comme il faut + ou pour extraire des informations à partir de ceux-ci de façon régulière. + Par exemple, voici un petit script pour extraire + le nombre de bogues ouverts dans PHP 5 à partir + du site web PHP... +get('http://php.net/'); + $browser->clickLink('reporting bugs'); + $browser->clickLink('statistics'); + $browser->clickLink('PHP 5 bugs only'); + $page = $browser->getContent(); + preg_match('/status=Open.*?by=Any.*?(\d+)<\/a>/', $page, $matches); + print $matches[1]; +?> +]]> + Bien sûr Il y a des méthodes plus simple pour réaliser + cet exemple en PHP. Par exemple, vous pourriez juste + utiliser la commande PHP file() sur ce qui est + ici une page fixe. Cependant, en utilisant des scripts + avec le navigateur web vous vous autorisez l'authentification, + la gestion des cookies, le chargement automatique des fenêtres, + les redirections, la transmission de formulaires et la capacité + d'examiner les entêtes. De telles méthodes sont fragiles dans + un site en constante évolution et vous voudrez employer une méthode + plus directe pour accéder aux données de façon permanente, + mais pour des tâches simples cette technique peut s'avérer + une solution très rapide. +

    +

    + Toutes les méthode de navigation utilisées dans WebTestCase sont présente dans la classe SimpleBrowser, mais les assertions sont remplacées par de simples accesseurs. Voici une liste complète des méthodes de navigation de page à page... + + + + + + + + + + + + + + + + + + + + + + + +
    addHeader($header)Ajouter une entête à chaque téléchargement
    useProxy($proxy, $username, $password)Utilise ce proxy à partir de maintenant
    head($url, $parameters)Effectue une requête HEAD
    get($url, $parameters)Télécharge une page avec un GET
    post($url, $parameters)Télécharge une page avec un POST
    clickLink($label)Suit un lien par son étiquette
    isLink($label)Vérifie l'existance d'un lien par son étiquette
    clickLinkById($id)Suit un lien par son attribut d'identification
    isLinkById($id)Vérifie l'existance d'un lien par son attribut d'identification
    getUrl()La page ou la fenêtre URL en cours
    getTitle()Le titre de la page
    getContent()Le page ou la fenêtre brute
    getContentAsText()Sans code HTML à l'exception du text "alt"
    retry()Répète la dernière requête
    back()Utilise le bouton "précédent" du navigateur
    forward()Utilise le bouton "suivant" du navigateur
    authenticate($username, $password)Retente la page ou la fenêtre après une réponse 401
    restart($date)Relance le navigateur pour une nouvelle session
    ageCookies($interval)Change la date des cookies
    setCookie($name, $value)Lance un nouveau cookie
    getCookieValue($host, $path, $name)Lit le cookie le plus spécifique
    getCurrentCookieValue($name)Lit le contenue du cookie en cours
    + Les méthode SimpleBrowser::useProxy() et + SimpleBrowser::addHeader() sont spéciales. + Une fois appelées, elles continuent à s'appliquer sur les téléchargements suivants. +

    +

    + Naviguer dans les formulaires est similaire à la navigation des formulaires via WebTestCase... + + + + + + + + + + + + +
    setField($name, $value)Modifie tous les champs avec ce nom
    setFieldById($id, $value)Modifie tous les champs avec cet identifiant
    getField($name)Accesseur de la valeur d'un élément de formulaire
    getFieldById($id)Accesseur de la valeur de l'élément de formulaire avec cet identifiant
    clickSubmit($label)Transmet le formulaire avec l'étiquette de son bouton
    clickSubmitByName($name)Transmet le formulaire avec l'attribut de son bouton
    clickSubmitById($id)Transmet le formulaire avec l'identifiant de son bouton
    clickImage($label, $x, $y)Clique sur une balise input de type image par son titre (title="*") our son texte alternatif (alt="*")
    clickImageByName($name, $x, $y)Clique sur une balise input de type image par son attribut (name="*")
    clickImageById($id, $x, $y)Clique sur une balise input de type image par son identifiant (id="*")
    submitFormById($id)Transmet le formulaire par son identifiant propre
    + Au jourd d'aujourd'hui il n'existe aucune méthode pour lister + les formulaires et les champs disponibles : ce sera probablement + ajouté dans des versions successives de SimpleTest. +

    +

    + A l'intérieur d'une page, les fenêtres individuelles peuvent être + sélectionnées. Si aucune sélection n'est réalisée alors + toutes les fenêtres sont fusionnées ensemble dans + une unique et grande page. + Le contenu de la page en cours sera une concaténation des + toutes les fenêtres dans l'ordre spécifié par les balises "frameset". + + + + + + +
    getFrames()Un déchargement de la structure de la fenêtre courante
    getFrameFocus()L'index ou l'étiquette de la fenêtre en courante
    setFrameFocusByIndex($choice)Sélectionne la fenêtre numérotée à partir de 1
    setFrameFocus($name)Sélectionne une fenêtre par son étiquette
    clearFrameFocus()Traite toutes les fenêtres comme une seule page
    + Lorsqu'on est focalisé sur une fenêtre unique, + le contenu viendra de celle-ci uniquement. + Cela comprend les liens à cliquer et les formulaires à transmettre. +

    +
    +
    +

    + Toute cette masse de fonctionnalités est géniale + lorsqu'on arrive à bien télécharger les pages, + mais ce n'est pas toujours évident. + Pour aider à découvrir les erreurs, le navigateur a aussi + des méthodes pour aider au débogage. + + + + + + + + + + + + + +
    setConnectionTimeout($timeout)Ferme la socket avec un délai trop long
    getRequest()L'entête de la requête brute de la page ou de la fenêtre
    getHeaders()L'entête de réponse de la page ou de la fenêtre
    getTransportError()N'importe quel erreur au niveau de la socket dans le dernier téléchargement
    getResponseCode()La réponse HTTP de la page ou de la fenêtre
    getMimeType()Le type Mime de la page our de la fenêtre
    getAuthentication()Le type d'authentification dans l'entête d'une provocation 401
    getRealm()Le realm d'authentification dans l'entête d'une provocation 401
    setMaximumRedirects($max)Nombre de redirections avant que la page ne soit chargée automatiquement
    setMaximumNestedFrames($max)Protection contre des framesets récursifs
    ignoreFrames()Neutralise le support des fenêtres
    useFrames()Autorise le support des fenêtres
    + Les méthodes SimpleBrowser::setConnectionTimeout(), + SimpleBrowser::setMaximumRedirects(), + SimpleBrowser::setMaximumNestedFrames(), + SimpleBrowser::ignoreFrames() + et SimpleBrowser::useFrames() continuent à s'appliquer + sur toutes les requêtes suivantes. + Les autres méthodes tiennent compte des fenêtres. + Cela veut dire que si une fenêtre individuelle ne se charge pas, + il suffit de se diriger vers elle avec + SimpleBrowser::setFrameFocus() : ensuite on utilisera + SimpleBrowser::getRequest(), etc. pour voir ce qui se passe. +

    +
    +
    +

    + Tout ce qui peut être fait dans + WebTestCase peut maintenant + être fait dans un UnitTestCase. + Ce qui revient à dire que nous pouvons librement mélanger + des tests sur des objets de domaine avec l'interface web... + +class TestOfRegistration extends UnitTestCase { + function testNewUserAddedToAuthenticator() { + $browser = &new SimpleBrowser(); + $browser->get('http://my-site.com/register.php'); + $browser->setField('email', 'me@here'); + $browser->setField('password', 'Secret'); + $browser->clickSubmit('Register'); + + $authenticator = &new Authenticator(); + $member = &$authenticator->findByEmail('me@here'); + $this->assertEqual($member->getPassword(), 'Secret'); + } +} +]]> + Bien que ça puisse être utile par convenance temporaire, + je ne suis pas fan de ce genre de test. Ce test s'applique + à plusieurs couches de l'application, ça implique qu'il est + plus que probable qu'il faudra le remanier lorsque le code changera. +

    +

    + Un cas plus utile d'utilisation directe du navigateur est + le moment où le WebTestCase ne peut plus suivre. + Un exemple ? Quand deux navigateurs doivent être utilisés en même temps. +

    +

    + Par exemple, supposons que nous voulions interdire + des usages simultanés d'un site avec le même login d'identification. + Ce scénario de test le vérifie... +get('http://my-site.com/login.php'); + $first->setField('name', 'Me'); + $first->setField('password', 'Secret'); + $first->clickSubmit('Enter'); + $this->assertEqual($first->getTitle(), 'Welcome'); + + $second = &new SimpleBrowser(); + $second->get('http://my-site.com/login.php'); + $second->setField('name', 'Me'); + $second->setField('password', 'Secret'); + $second->clickSubmit('Enter'); + $this->assertEqual($second->getTitle(), 'Access Denied'); + } +} +]]> + Vous pouvez aussi utiliser la classe SimpleBrowser + quand vous souhaitez écrire des scénarios de test en utilisant + un autre outil que SimpleTest. +

    +
    +
    + + + Utiliser le navigateur web dans des scripts + + + Déboguer les erreurs sur les pages + + + Tests complexes avec des navigateurs web multiples + + + + + La page du projet SimpleTest sur + SourceForge. + + + La page de téléchargement de SimpleTest sur + LastCraft. + + + L'API de développeur pour SimpleTest + donne tous les détails sur les classes et les assertions disponibles. + + + + + développement logiciel, + programmation php pour des clients, + php centré autour du client, + outils de développement logiciel, + framework de test de recette, + scripts php gratuits, + test unitaire de systèmes d'authentification, + ressources php, + HTMLUnit, + JWebUnit, + test php, + ressource de test unitaire, + test web, + authentification HTTP, + tester la connection, + tester l'authentification, + tests de sécurité + + +
    + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/display_subclass_tutorial.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/display_subclass_tutorial.xml new file mode 100644 index 0000000..7157637 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/display_subclass_tutorial.xml @@ -0,0 +1,282 @@ + + + + + Tutorial de test unitaire en PHP - Sous-classer l'affichage du test + + +

    + Le composant affichage de SimpleTest est en fait + la dernière partie à développer. + Des morceaux de la section suivante changeront prochainement + et -- avec optimisme -- des composants d'affichage + plus sophistiqués seront écrits, mais pour l'instant + si un affichage minime n'est pas suffisant, + voici comment réaliser le votre. +

    +
    +
    +

    + Bon d'accord, voici comment. +

    +

    + Nous devons créer une sous-classe de l'affichage utilisée, + dans notre cas il s'agit de HtmlReporter. + La classe HtmlReporter est situé dans le fichier + simpletest/reporter.php : + pour l'instant elle a l'interface suivante... + + Voici ce que les méthodes pertinentes veulent dire. + Vous pouvez consulter la + liste complète ici + si cela vous intéresse. +

      +
    • + HtmlReporter()
      + est le constructeur. Notez qu'un test unitaire initie + le lien vers l'affichage plutôt que l'inverse. + L'affichage est un réceptacle passif des évènements de test. + Cela permet une adaptation facile de l'affichage + pour d'autres systèmes de test en dehors + des tests unitaires comme la surveillance + de serveurs par exemple. + Autre avantage, un test unitaire peut écrire + vers plus d'un affichage à la fois. +
    • +
    • + void paintFail(string $message)
      + peint un échec. Voir ci-dessous. +
    • +
    • + void paintPass(string \$message)
      + ne fait rien par défaut. C'est cette méthode + que nous allons modifier. +
    • +
    • + string getCss()
      + renvoie le style CSS - via une chaîne - pour + la méthode d'entête de la page. + Des styles complémentaires peuvent être ajoutés ici. +
    • +
    • + array getTestList()
      + est une méthode commode pour des sous-classes. + Elle liste l'emboîtement courant des tests + via une liste de noms de test. + Le premier, le test emboîté le plus profondément, + est le premier dans la liste et la méthode + du test courant sera la dernière. +
    • +
    +

    +

    + Pour afficher les succès nous avons juste + besoin que la méthode paintPass() + se comporte comme paintFail(). + Bien sûr nous n'allons pas modifier l'original. + Nous allons juste créer une sous-classe. +

    +
    +
    +

    + Premièrement nous allons créer un fichier + tests/show_passes.php dans notre projet de log + et y placer cette classe vide... +HtmlReporter(); + } + } +?> +]]> + Une rapide mais attentive lecture du + code de SimpleTest + indique que l'implémentation de paintFail() ressemble à... +Fail: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode("->", $breadcrumb); + print "->$message
    \n"; +} +]]>
    + Essentiellement elle s'enchaîne à la version du parent, + que nous devons aussi réaliser pour garantir le ménage, + et ensuite imprime une trace calculée à partir de la liste + des tests courants. Par contre elle perd au passage + le nom du test du premier niveau. + Etant donné qu'il est identique pour chaque test, + ce serait un peu trop d'informations. + En la transposant dans notre nouvelle classe... +HtmlReporter(); + } + + function paintPass($message) { + parent::paintPass($message); + print "Pass: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode("->", $breadcrumb); + print "->$message
    \n"; + }
    +} +]]>
    + Pour l'instant tout roule. + Maintenant pour utiliser notre nouvelle classe, + nous allons modifier notre fichier tests/all_tests.php... + + require_once('show_passes.php'); + + $test = &new GroupTest('All tests'); + $test->addTestFile('log_test.php'); + $test->addTestFile('clock_test.php'); + $test->run(new ShowPasses()); +?> +]]> + Nous pouvons le lancer pour voir le résultat de notre bricolage... +

    +

    All tests

    + Pass: log_test.php->Log class test->testappendingtofile->Expecting [/Test line 1/] in [Test line 1]
    + Pass: log_test.php->Log class test->testappendingtofile->Expecting [/Test line 2/] in [Test line 2]
    + Pass: log_test.php->Log class test->testcreatingnewfile->Created before message
    + Pass: log_test.php->Log class test->testcreatingnewfile->File created
    + Pass: clock_test.php->Clock class test->testclockadvance->Advancement
    + Pass: clock_test.php->Clock class test->testclocktellstime->Now is the right time
    +
    3/3 test cases complete. + 6 passes and 0 fails.
    +
    + Joli, mais pas encore digne d'une médaille d'or. + Nous avons perdu un peu d'information au passage. + L'affichage du span.pass n'est pas stylé en CSS, + mais nous pouvons l'ajouter en modifiant une autre méthode... +HtmlReporter(); + } + + function paintPass($message) { + parent::paintPass($message); + print "Pass: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode("->", $breadcrumb); + print "->$message
    \n"; + } + + protected function getCss() { + return parent::getCss() . ' .pass { color: green; }'; + } +} +]]>
    + Si vous ajoutez le code au fur et à mesure, + vous verrez l'ajout du style dans le code source + du résultat via le navigateur. A l'oeil, + l'affichage devrait ressembler à... +
    +

    All tests

    + Pass: log_test.php->Log class test->testappendingtofile->Expecting [/Test line 1/] in [Test line 1]
    + Pass: log_test.php->Log class test->testappendingtofile->Expecting [/Test line 2/] in [Test line 2]
    + Pass: log_test.php->Log class test->testcreatingnewfile->Created before message
    + Pass: log_test.php->Log class test->testcreatingnewfile->File created
    + Pass: clock_test.php->Clock class test->testclockadvance->Advancement
    + Pass: clock_test.php->Clock class test->testclocktellstime->Now is the right time
    +
    3/3 test cases complete. + 6 passes and 0 fails.
    +
    + Certains préfèrent voir les succès quand ils travaillent sur le code; + le sentiment de travail achevé est sympathique après tout. + Une fois que vous commencez à naviguer + de haut en bas pour trouver les erreurs, assez vite + vous en comprendrez le côté obscur. +

    +

    + Essayez les deux méthodes pour déterminer votre préférence. + Nous allons le laisser tel que pour l'étape qui approche : + les objets fantaisie. + Il s'agit du premier outil de test qui ajoute des tests additionnels : + il sera utile de voir ce qui se passe dans les coulisses. +

    +
    +
    + + + Comment changer l'affichage pour afficher + les passages avec succès. + + + Sous classer + la classe HtmlReporter. + + + + + La section précédente : + sous-classer les scénarios de test + + + Cette section est très spécifique à + SimpleTest. + Si vous utilisez un autre outil, + n'hésitez pas à sauter pardessus. + + + + + développement logiciel piloté par les tests, + conseil pour programmer en php, + programmation php, + outils de développement logiciel, + tutorial php, + scripts php gratuits, + architecture, + exemple de scénario de test, + framework de tests unitaires, + ressources php, + exemple de code php, + junit, + phpunit, + simpletest, + test php, + outil de test unitaire, + suite de test php + + +
    + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/download_website.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/download_website.xml new file mode 100644 index 0000000..b64af48 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/download_website.xml @@ -0,0 +1,110 @@ + + + + + Télécharger SimpleTest + +
    +

    + La version courante est : + + SimpleTest v1.0.1. +

    +

    + Vous pouvez télécharger cette version depuis + + SourceForget.net + et votre miroir le plus proche. Vous pouvez aussi jeter un oeil du côté + du changelog actuel. +

    +

    + Au fait, ne soyez pas effrayé par le mot beta : + pas mal d'utilisateurs vont même jusqu'à utiliser la version SVN. +

    +
    +
    +

    + Si Eclipse est votre IDE / éditeur de prédilection, vous aurez + peut-être envie d'utiliser le + + plugin Eclipse. +

    +

    + Pour utilser les procédures automatiques, l'URL est : +

    http://simpletest.org/eclipse/
    +

    +
    +
    +

    + SimpleTest a été packagé par la communauté avec d'autres parfums encore. +

    + +

    + Attention : certains paquets ne sont pas toujours très à jour. +

    +
    +
    +

    + Le code source est hébergé par SourceForge : vous pouvez l'étudier / le butiner + via le + dépôt SVN. +

    +
    +
    + +
    +
    + + + Version courante + + + Paquet Eclipse + + + Autres paquets + + + Source + + + Autres versions stables + + + + + + + + + SimpleTest, + download, + source code, + stable release, + eclipse release, + eclipse plugin, + debian package, + drupal module, + pear channel, + pearified package + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/expectation_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/expectation_documentation.xml new file mode 100644 index 0000000..c9acdc6 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/expectation_documentation.xml @@ -0,0 +1,308 @@ + + + + + Documentation SimpleTest : étendre le testeur unitaire avec des classes d'attentes supplémentaires + +
    +

    + Le comportement par défaut des + objets fantaisie dans + SimpleTest + est soit une correspondance identique sur l'argument, + soit l'acceptation de n'importe quel argument. + Pour la plupart des tests, c'est suffisant. + Cependant il est parfois nécessaire de ramollir un scénario de test. +

    +

    + Un des endroits où un test peut être trop serré + est la reconnaissance textuelle. Prenons l'exemple + d'un composant qui produirait un message d'erreur + utile lorsque quelque chose plante. Il serait utile de tester + que l'erreur correcte est renvoyée, + mais le texte proprement dit risque d'être plutôt long. + Si vous testez le texte dans son ensemble alors + à chaque modification de ce même message + -- même un point ou une virgule -- vous aurez + à revenir sur la suite de test pour la modifier. +

    +

    + Voici un cas concret, nous avons un service d'actualités + qui a échoué dans sa tentative de connexion à sa source distante. +class NewsService { + ... + function publish(&$writer) { + if (! $this->isConnected()) { + $writer->write('Cannot connect to news service "' . + $this->_name . '" at this time. ' . + 'Please try again later.'); + } + ... + } +} +]]> + Là il envoie son contenu vers un classe Writer. + Nous pourrions tester ce comportement avec un MockWriter... + + $writer = &new MockWriter($this); + $writer->expectOnce('write', array( + 'Cannot connect to news service ' . + '"BBC News" at this time. ' . + 'Please try again later.')); + + $service = &new NewsService('BBC News'); + $service->publish($writer); + + $writer->tally(); + } +} +]]> + C'est un bon exemple d'un test fragile. + Si nous décidons d'ajouter des instructions complémentaires, + par exemple proposer une source d'actualités alternative, + nous casserons nos tests par la même occasion sans pourtant + avoir modifié une seule fonctionnalité. +

    +

    + Pour contourner ce problème, nous voudrions utiliser + un test avec une expression rationnelle plutôt + qu'une correspondance exacte. Nous pouvons y parvenir avec... + + $writer->expectOnce( + 'write', + array(new WantedPatternExpectation('/cannot connect/i'))); + + $service = &new NewsService('BBC News'); + $service->publish($writer); + + $writer->tally(); + } +} +]]> + Plutôt que de transmettre le paramètre attendu au MockWriter, + nous envoyons une classe d'attente appelée WantedPatternExpectation. + L'objet fantaisie est suffisamment élégant pour reconnaître + qu'il s'agit d'un truc spécial et pour le traiter différemment. + Plutôt que de comparer l'argument entrant à cet objet, + il utilise l'objet attente lui-même pour exécuter le test. +

    +

    + WantedPatternExpectation utilise + l'expression rationnelle pour la comparaison avec son constructeur. + A chaque fois qu'une comparaison est fait à travers + MockWriter par rapport à cette classe attente, + elle fera un preg_match() avec ce motif. + Dans notre scénario de test ci-dessus, aussi longtemps + que la chaîne "cannot connect" apparaît dans le texte, + la fantaisie transmettra un succès au testeur unitaire. + Peu importe le reste du texte. +

    +

    + Les classes attente possibles sont... + + + + + + + + + + +
    EqualExpectationUne égalité, plutôt que la plus forte comparaison à l'identique
    NotEqualExpectationUne comparaison sur la non-égalité
    IndenticalExpectationLa vérification par défaut de l'objet fantaisie qui doit correspondre exactement
    NotIndenticalExpectationInverse la logique de l'objet fantaisie
    WantedPatternExpectationUtilise une expression rationnelle Perl pour comparer une chaîne
    NoUnwantedPatternExpectationPasse seulement si l'expression rationnelle Perl échoue
    IsAExpectationVérifie le type ou le nom de la classe uniquement
    NotAExpectationL'opposé de IsAExpectation
    MethodExistsExpectationVérifie si la méthode est disponible sur un objet
    + La plupart utilisent la valeur attendue dans le constructeur. + Les exceptions sont les vérifications sur motif, + qui utilisent une expression rationnelle, ainsi que + IsAExpectation et NotAExpectation, + qui prennent un type ou un nom de classe comme chaîne. +

    +
    +
    +

    + Les classes attente peuvent servir à autre chose + que l'envoi d'assertions depuis les objets fantaisie, + afin de choisir le comportement d'un + objet fantaisie + ou celui d'un bouchon serveur. + A chaque fois qu'une liste d'arguments est donnée, + une liste d'objets d'attente peut être insérée à la place. +

    +

    + Mettons que nous voulons qu'un bouchon serveur + d'autorisation simule une connexion réussie seulement + si il reçoit un objet de session valide. + Nous pouvons y arriver avec ce qui suit... + +$authorisation = new StubAuthorisation(); +$authorisation->setReturnValue( + 'isAllowed', + true, + array(new IsAExpectation('Session', 'Must be a session'))); +$authorisation->setReturnValue('isAllowed', false); +]]> + Le comportement par défaut du bouchon serveur + est défini pour renvoyer false + quand isAllowed est appelé. + Lorsque nous appelons cette méthode avec un unique paramètre + qui est un objet Session, il renverra true. + Nous avons aussi ajouté un deuxième paramètre comme message. + Il sera affiché dans le message d'erreur de l'objet fantaisie + si l'attente est la cause de l'échec. +

    +

    + Ce niveau de sophistication est rarement utile : + il n'est inclut que pour être complet. +

    +
    +
    +

    + Les classes d'attentes ont une structure très simple. + Tellement simple qu'il devient très simple de créer + vos propres version de logique pour des tests utilisés couramment. +

    +

    + Par exemple voici la création d'une classe pour tester + la validité d'adresses IP. Pour fonctionner correctement + avec les bouchons serveurs et les objets fantaisie, + cette nouvelle classe d'attente devrait étendre + SimpleExpectation... +class ValidIp extends SimpleExpectation { + + function test($ip) { + return (ip2long($ip) != -1); + } + + function testMessage($ip) { + return "Address [$ip] should be a valid IP address"; + } +} +]]> + Il n'y a véritablement que deux méthodes à mettre en place. + La méthode test() devrait renvoyer un true + si l'attente doit passer, et une erreur false + dans le cas contraire. La méthode testMessage() + ne devrait renvoyer que du texte utile à la compréhension du test en lui-même. +

    +

    + Cette classe peut désormais être employée à la place + des classes d'attente précédentes. +

    +
    +
    +

    + Le framework + de test unitaire SimpleTest utilise aussi dans son coeur + des classes d'attente pour + la classe UnitTestCase. + Nous pouvons aussi tirer parti de ces mécanismes pour réutiliser + nos propres classes attente à l'intérieur même des suites de test. +

    +

    + La méthode la plus directe est d'utiliser la méthode + SimpleTest::assertExpectation() pour effectuer le test... +class TestOfNetworking extends UnitTestCase { + ... + function testGetValidIp() { + $server = &new Server(); + $this->assertExpectation( + new ValidIp(), + $server->getIp(), + 'Server IP address->%s'); + } +} +]]> + C'est plutôt sale par rapport à notre syntaxe habituelle + du type assert...(). +

    +

    + Pour un cas aussi simple, nous créons d'ordinaire une méthode + d'assertion distincte en utilisant la classe d'attente. + Supposons un instant que notre attente soit un peu plus + compliquée et que par conséquent nous souhaitions la réutiliser, + nous obtenons... + + function assertValidIp($ip, $message = '%s') { + $this->assertExpectation(new ValidIp(), $ip, $message); + } + + function testGetValidIp() { + $server = &new Server(); + $this->assertValidIp( + $server->getIp(), + 'Server IP address->%s'); + } +} +]]> + Il est peu probable que nous ayons besoin + de ce niveau de contrôle sur la machinerie de test. + Il est assez rare que le besoin d'une attente dépasse + le stade de la reconnaissance d'un motif. + De plus, les classes d'attente complexes peuvent rendre + les tests difficiles à lire et à déboguer. + Ces mécanismes sont véritablement là pour les auteurs + de système qui étendront le framework de test + pour leurs propres outils de test. +

    +
    +
    + + + Utiliser les attentes pour des tests + plus précis avec des objets fantaisie + + + Changer le comportement d'un objet fantaisie + avec des attentes + + + Créer des attentes + + + Par dessous SimpleTest utilise des classes d'attente + + + + + La page du projet SimpleTest sur + SourceForge. + + + La page de téléchargement de SimpleTest sur + LastCraft. + + + Les attentes imitent les contraintes dans + JMock. + + + L'API complète pour SimpleTest + réalisé avec PHPDoc. + + + + + objets fantaisie, + développement piloté par les tests, + héritage des attentes, + contraintes d'objet fantaisie, + test unitaire avancé en PHP, + test en premier, + architecture de framework de test + + +
    + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/extension_eclipse.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/extension_eclipse.xml new file mode 100644 index 0000000..ab9a5d0 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/extension_eclipse.xml @@ -0,0 +1,292 @@ + + + + + Documentation du plugin Eclipse pour Simpletest + +
    +
      +
    • + Système d'exploitation - OS
      + Ce plugin devrait fonctionner sur toutes les plateformes qui font tourner Eclipse. + Il a été testé spécifiquement sur Linux, OS X et Windows. + Si le plugin ne marche pas sur l'une des ces plateformes, c'est qu'une nouvelle + incompatiblité est apparue : chose rare à priori. +
    • +
    • + Eclipse
      + Le plugin a été pensé et développé pour fonctionner avec les versions 3.1.X+ and 3.2.X+ d'Eclipse. + Eclipse en général nécessite une version 1.4.x ou supérieur de la JVM : + il n'y a pas d'autres pré-requis pour le plugin. +
    • +
    • + PHP
      + Le plugin a été testé pour fonctionner avec PHP 4.3.x+ (y compris PHP 5.1.x.). +
    • +
    • + Xdebug
      + Le plugin peut-être configuré pour fonctionnver avec Xdebug (version 2.0.0RC1 ou suivante). + Le fichier php.ini utilisé par le plugin (voir ci-dessous pour définir ce fichier) doit charger l'extension Xdebug. + Généralement on y arrive en ajoutant zend_extension_ts="Some Windows Path" ou zend_extension="Some Linux Path" + au fichier php.ini. + Des instructions spécifiques peuvent être trouvées à l'adresse : + http://xdebug.org/install.php + Pas la peine de se tracasser à propos des autres paramètres, le plugin se chargera d'initialiser + les paramètres nécessaires au besoin. +
    • +
    • + Simpletest
      + Le plugin est prévu pour tourner avec une version spécifique de SimpleTest. + Et même plus : il inclut la version associée de SimpleTest compatible. + Un plugin avec comme numéro de version 1.0.0_0.0.4 indiquerait que les plugin est compatible + avec la version 1.0.0 de SimpleTest and qu'il s'agit de la version 0.0.4 du plugin. + De légères modifications sont nécessaires sur le code de SimpleTest même pour + que le plugin fonctionne bien; ces modifications peuvent (ou pas) être comprise dans + la version de SimpleTest sans plugin. +
    • +
    • + PHPUnit2
      + A partir du plugin version 0.1.6 un support expérimental de la version CVS de PHPUnit2 + est disponible. Pour utiliser PHPUnit2, Choisisser un chemin vers PHPUnit2 et sélectionnez + les tests PHPUnit2 en lieu et place de SimpleTest. + Note: il s'agit du chemin vers le dossier qui contient le dosser PHPUnit2. PHPUnit 3 + n'est pas supporté mais devrait l'être plus tard. +
    • +
    +
    +
    +
      +
    1. Télécharger et installer Eclipse (www.eclipse.org) – si vous n'êtes pas sûr quoi télécharger, prenez le SDK 3.1.0 d'Eclipse.
    2. +
    3. Télécharger le dernier fichier ZIP du plugin Eclipse pour SimpleTest (Simpletest Sourceforge)
    4. +
    5. Extgraire le contenu du fichier ZIP vers un répertoire temporaire {cette documentation y fera référence par $unzip}
    6. +
    7. Lancer Eclipse
    8. +
    9. Ouvrir "Install Wizard" en cliquant sur "Help > Software Updates > Find and Install" depuis la barre du menu.
    10. +
    11. Choisisser le deuxième bouton, "Search for new features to install" et cliquer sur "Next".
    12. +
    13. Cliquer sur le bouton du côté droit, "New Local Site".
    14. +
    15. Sélectionner le répertoire $unzip {le répertoire qui a servi pour l'extraction}.
    16. +
    17. Cliquer sur le bouton "OK".
    18. +
    19. Dans la fenêtre "Edit Local Site", cliquer sulick the "OK" button
    20. +
    21. Cliquer sur le bouton "Finish"
    22. +
    23. Dans la fenêtre "Search Results", chercher puis sélectionner "Simpletest plug-in 0.0.x"
    24. +
    25. Cliquer sur le bouton "Next"
    26. +
    27. Lire la licence et l'accepter en cliquant le bouton radio "I accept the terms in the license agreement" puis cliquer sur le bouton "Next"
    28. +
    29. Dans la fenêtre "Installation", vous pouvez changer le lieu d'installation -- la plupart des utilisateurs appuyeront juste sur le bouton "Finish"
    30. +
    31. Dans la fenêtre "Feature Verification", cliquer sur le bouton "Install"
    32. +
    33. Relancer Eclipse comme demandé
    34. +
    35. +

      Après avoir lancé Eclipse pour la première fois après l'installation, + vous aurez besoin de configurer le plugin SimpleTest. Pour y arriver : +

      +
        +
      1. + Sélectionner "Window > Preferences" dans la barre de menu +
      2. +
      3. + Sélectionner "Simpletest" dans les catégories sur la gauche de la boîte de dialogue. +
      4. +
      5. + Remplir ou choisir le répertoire contenant l'éxécutable PHP à utliser. +
      6. +
      7. + Laisser vierge le champ avec le fichier à inclure. +
      8. +
      9. + Remplir .php comme "Test File Suffix". + Bien sûr si vous utilisez ue autre extension pour vos fichiers de tests en PHP + (par exemple mon-test.tst.php) n'hésitez pas à remplir le champ avec ce qui va bien (dans notre exemple .tst.php). + Le plugin s'en sert pour trouver des tests à éxécuter. +
      10. +
      11. + Appuer sur le bouton "Ok" pour fermer la fenêtre des préférences. +
      12. +
      +
    36. +
    +
    +
    + Note: cette procédure ne fonctionnera que si vous avez installé le plugin via + le "installation wizard". Si vous n'aviez fait que copier des répertoires pour l'installation, + il est recommandé de fermer Eclipse, de supprimer les anciennes version et de suivre les instructions + d'installation ci-dessus (vous ne devriez pas avoir à refaire la configuration initiale). +
      +
    1. Sélectionner "Help > Software Updates > Manage Configuration" dans le menu
    2. +
    3. Trouver le plugin Simpletest dans la liste et le choisir
    4. +
    5. Dans la section de droite, cliquer sur le lien "Scan for Updates"
    6. +
    7. Si aucune mise à jour n'est détectée, naviguer vers "Window > Preferences > Install/Update > Valid Updates" dans les préférences d'Eclipse + et sélectionner "compatible", en lieu et place de "equivalent". Puis recommencer les étapes ci-dessus.
    8. +
    9. Sélectionner les versions à mettre à jour et cliquer sur "Next".
    10. +
    11. Relire les licences pour ces mises à jour : si elles sont acceptables, cocher "I accept the terms in the license agreements." + Si les termes de la licence ne vous paraissent pas acceptables, il est encore temps d'arrêter le téléchargement.
    12. +
    13. Cliquer sur "Install" pour permettre le téléchargement est l'installation.
    14. +
    15. Une fois que toutes les fonctionnalités et plugins ont été téléchargés avec succès + et que leurs fihciers ont été installés sur l'ordinateur local, + une nouvelle configuration qui incorpore les nouvelles fonctionnalités et nouveaux plugins sera créée. + Cliquer sur "Yes" quand on vous demander de quitter et relancher le "Workbench" pour les changements prennent effet.
    16. +
    +
    +
    + Note: ceci ne fonctionnera que si le plugin a été installé via la méthode "Feature Update". + Si l'installation a été effectué par une autre méthode alors le plugin peut être supprimé en effaçant + les répertoires qui avaient été ajoutés. +
      +
    1. Sélectionner "Help > Software Updates > Manage Configuration"
    2. +
    3. Choisir le plugin Simpletest dans la liste
    4. +
    5. Cliquer-droit sur le plugin Simpletest et sélectionner l'option "Disable"
    6. +
    7. Accepter le redémarrage d'Eclipse
    8. +
    9. Une fois Eclipse redémarré, sélectionner "Help > Software Updates > Manage Configuration" depuis le menu
    10. +
    11. Sélectionner l'option "Show Disabled Features" dans la barre d'outils
    12. +
    13. Choisir le plugin SimpleTest dans la liste
    14. +
    15. Cliquer-droit sur le plugin SimpleTest et sélectionner l'option "Uninstall"
    16. +
    17. Accepter le redémarrage d'Eclipse
    18. +
    +
    +
    +

    + La suite présente quelques exemples d'utilsation du plugin. +

    +
      +
    1. Créer une nouveau projet de test (un lieu pour grouper des fichiers similaires) +
        +
      1. Sélectionner "File > New > Project.." dans le menu
      2. +
      3. Agrandir le répertoire "General" et choisir "Project"
      4. +
      5. Cliquer sur le bouton "Next"
      6. +
      7. Sur le tab suivant, ajouter un nom de projet : "Test"
      8. +
      9. Utiliser le contenu de projet par défaut
      10. +
      11. Cliquer sur le bouton "Finish"
      12. +
      +
    2. +
    3. Créer un test unique avec un seul succès +
        +
      1. Dans l'explorateur de paquet cliquer-droit sur le projet "Test" et sélectionner "New > File".
      2. +
      3. Comme nom de fichier remplir : test1.php et cliquer sur "Finish"
      4. +
      5. Double-cliquer sur l'entrée test1.php dans l'explorateur de paquet : cela devrait ouvrir une nouvelle vue pour modifier ce fichier.
      6. +
      7. Remplir le fichier avec ces lignes : +assertEqual(3,$total, "This should pass"); + } +} +?> +]]> +
      8. +
      9. Sélectionner "File > Save" depuis le menu.
      10. +
      11. Cliquer-droit sur test1.php et sélectionner "Run > Run Simpletest".
      12. +
      13. La "Result View" devrait se remplir avec les informations sur le résultat du test + et la console SimpleTest devrait se remplir avec d'autres informations aussi.
      14. +
      +
    4. +
    5. Une seule classe de test, plusieurs tests +
        +
      1. dans l'explorateur de paquet, cliquer droit sur le projet "Test" et sélectionner "New > File".
      2. +
      3. En tant que nom de fichier, utiliser : test2.php et cliquer sur "Finish".
      4. +
      5. Double-cliquer sur l'entrée test2.php dans l'explorateur de paquet : une nouvelle vue s'ouvre, prête pour l'édition.
      6. +
      7. Remplir le contenu avec : +assertEqual(3,$total, "This should pass"); + } + function test_fail(){ + $x = 1; + $y = 2; + $total = $x + $y; + $this->assertEqual(4,$total,"This should fail"); + } +} +?> +]]> +
      8. +
      9. Sélectionner "File > save" dans la barre de menu.
      10. +
      11. Cliquer-droit sur grouptest.php et sélectionner "Run > Run Simpletest".
      12. +
      13. La "Result View" devrait se remplir avec les informations issus du test lancé, + idem pour pour la console SimpleTest.
      14. +
      +
    6. +
    7. Tests groupés (plusieurs fichiers de test en même temps) +
        +
      1. dans l'explorateur de paquet, cliquer droit sur le projet "Test" et sélectionner "New > File".
      2. +
      3. En tant que nom de fichier, utiliser : test2.php et cliquer sur "Finish".
      4. +
      5. Double-cliquer sur l'entrée test2.php dans l'explorateur de paquet : une nouvelle vue s'ouvre, prête pour l'édition.
      6. +
      7. Remplir le contenu avec : +addTestFile(dirname(__FILE__).'/test1.php'); + $this->addTestFile(dirname(__FILE__).'/test2.php'); + } +} +?> +]]> +
      8. +
      9. Sélectionner "File > Save" depuis le menu.
      10. +
      11. Cliquer-droit sur test1.php et sélectionner "Run > Run Simpletest".
      12. +
      13. La "Result View" devrait se remplir avec les informations sur le résultat du test + et la console SimpleTest devrait se remplir avec d'autres informations aussi.
      14. +
      +
    8. +
    +
    +
    +
      +
    • + Si un constructeur est utilisé dans le scénario de test, + bien faire attention à ce que la dernière ligne de ce constructeur appelle + le constructeur parent (par exemple parent::UnitTestCase) +
    • +
    • + Ne pas mettre d'assertions dans le constructeur de la classe de test +
    • +
    • + Si vous obtenez une erreur indiquant qu'une classe ne pouvait pas être chargé, alors il faut + relancer Eclipse. Une fois qu'Eclipse a redémarré, ouvrir la "Result View" manuellement + en sélectionnant "Window > Show View > Other..." + Puis sélectionner la catégorie SimpleTest, puis la "Result View" et enfin cliquer sur "OK". +
    • +
    +
    +
    +

    Ci-dessous des fonctionnalités qui devrait arriver à un moment ou un autre dans ce plugin

    +
      +
    • + Permettre différents fichiers d'inclustion pour chaque lanceur + (écraser l'include "master") +
    • +
    • + Mieux gérer les erreurs fatales +
    • +
    +
    +
    + + + + dévloppement logiciel, + plugin eclipse, + programmation en binôme, + programmation php, + outils de développement logiciel, + tutorial php, + scripts php gratuits, + architecture, + ressources php, + objets fantaisie, + junit, + php testing, + test unitaire, + test php automatisé, + explication test unitaire, + exemple test unitaire + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/first_test_tutorial.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/first_test_tutorial.xml new file mode 100644 index 0000000..fc94905 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/first_test_tutorial.xml @@ -0,0 +1,462 @@ + + + + + Tutorial sur les tests unitaires en PHP - Créer un exemple de scénario de test en PHP + + +

    + Si vous débutez avec les tests unitaires, + il est recommandé d'essayer le code au fur et à mesure. + Il n'y pas grand chose à taper et vous sentirez + le rythme de la programmation pilotée par les tests. +

    +

    + Pour exécuter les exemples tels quels, + vous aurez besoin de créer un nouveau répertoire + et d'y installer trois dossiers : + classes, tests et temp. + Dézippez le framework SimpleTest + dans le dossier tests + et assurez vous que votre serveur web puisse atteindre ces endroits. +

    +
    +
    +

    + L'exemple dans l'introduction rapide + comprenait les tests unitaires d'une simple classe de log. + Dans ce tutorial à propos de Simple Test, je vais essayer + de raconter toute l'histoire du développement de cette classe. + Cette classe PHP est courte et simple : + au cours de cette introduction, elle recevra beaucoup + plus d'attention que dans le cadre d'un développement de production. + Nous verrons que derrière son apparente simplicité + se cachent des choix de conception étonnamment difficiles. +

    +

    + Peut-être que ces choix sont trop difficiles ? + Plutôt que d'essayer de penser à tout en amont, + je vais commencer par poser une exigence : + nous voulons écrire des messages dans un fichier. + Ces messages doivent être ajoutés en fin de fichier s'il existe. + Plus tard nous aurons besoin de priorités, + de filtres et d'autres choses encore, + mais nous plaçons l'écriture dans un fichier + au coeur de nos préoccupations. + Nous ne penserons à rien d'autres par peur de confusion. + OK, commençons par écrire un test... +run(new HtmlReporter()); +?> +]]> + Pas à pas, voici ce qu'il veut dire. +

    +

    + La constante SIMPLE_TEST contient + le chemin vers les classes de Simple Test à partir de ce fichier. + Les classes pourraient être placées dans le path + du fichier php.ini mais si vous êtes sur un serveur mutualisé, + vous n'y aurez probablement pas accès. + Pour que tout le monde soit content, + le chemin est déclaré explicitement dans le script de test. + Plus tard nous verrons comment tout finira au même endroit. +

    +

    + Qu'est-ce donc que ce fichier autorun.php ? + Les bibliothèques de SimpleTest sont une boîte à outil + pour créer votre propre suite de tests standardisés. + Elles peuvent être utilisées "telles que" sans problème, + mais sont constituées par des composants qui doivent être assemblés. + autorun.php est un composant spécial : + il fournit les parties "testeur unitaire" et "affichage". + Il attrape les classes de test et les lance automagiquement. +

    +

    + Il est probable que vous en viendrez à écrire votre propre affichage + et ajouter cette version par défaut est optionnel. + SimpleTest inclut une classe d'affichage utilisable - et basique - + appelée HtmlReporter. + Sur des tests, elle peut enregistrer débuts, fins, erreurs, succès et échec. + Elle affiche ces informations au plus vite, au cas où le code + du test fait planter le script et masque la source d'échec. +

    +

    + Les tests eux-mêmes sont rassemblés dans une classe de scénario de test. + Cette dernière est typiquement une extension de + la classe UnitTestCase. + Quand le test est exécuté, elle cherche les méthodes + commençant par "test" et les lancent. + Notre seule méthode de test pour l'instant est appellée + testCreatingNewFile() mais elle est encore vide. +

    +

    + Une méthode vide ne fait rien. Nous avons besoin d'y placer du code. + La classe UnitTestCase génère des évènements + de test à son exécution : + ces évènements sont envoyés vers un observateur. +

    +

    + Et pour ajouter du code de test... + + + class TestOfLogging extends UnitTestCase { + function testCreatingNewFile() { + @unlink('../temp/test.log'); + $log = new Log('../temp/test.log'); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists('../temp/test.log')); + } + } + + $test = &new TestOfLogging(); + $test->run(new HtmlReporter()); +?> +]]> +

    +

    + Vous pensez probablement que ça représente beaucoup + de code pour un unique test et je suis d'accord avec vous. + Ne vous inquiétez pas. Il s'agit d'un coût fixe et à partir + de maintenant nous pouvons ajouter des tests : + une ligne ou presque à chaque fois. + Parfois moins en utilisant des artefacts + de test que nous découvrirons plus tard. +

    +

    + Nous devons maintenant prendre nos premières décisions. + Notre fichier de test s'appelle log_test.php + (n'importe quel nom ferait l'affaire) : + nous le plaçons dans un dossier appelé tests + (partout ailleurs serait aussi bien). + Notre fichier de code s'appelle log.php : + c'est son contenu que nous allons tester. + Je l'ai placé dans notre dossier classes : + cela veut-il dire que nous construisons une classe ? +

    +

    + Pour cet exemple, la réponse est oui, + mais le testeur unitaire n'est pas restreint aux tests de classe. + C'est juste que le code orienté objet est plus facile + à dépecer et à remodeler. Ce n'est pas par hasard + si la conduite de tests fins via les tests unitaires + est apparue au sein de la communauté OO. +

    +

    + Le test en lui-même est minimal. Tout d'abord il élimine + tout autre fichier de test qui serait encore présent. + Les décisions de conception arrivent ensuite en rafale. + Notre classe s'appelle Log : + elle passe le chemin du fichier au constructeur. + Nous créons le log et nous lui envoyons aussitôt + un message en utilisant la méthode message(). + L'originalité dans le nommage n'est pas + une caractéristique désirable chez un développeur informatique : + c'est triste mais c'est comme ça. +

    +

    + La plus petite unité d'un test mmm... heu... unitaire est l'assertion. + Ici nous voulons nous assurer que le fichier log + auquel nous venons d'envoyer un message a bel et bien été créé. + UnitTestCase::assertTrue() enverra + un évènement réussite si la condition évaluée est vraie + ou un échec dans le cas contraire. + Nous pouvons avoir un ensemble d'assertions différentes + et encore plus si nous étendons + nos scénarios de test classique. Voici la liste... + + + + + + + + + + + + + + + + +
    assertTrue($x)Echoue si $x est faux
    assertFalse($x)Echoue si $x est vrai
    assertNull($x)Echoue si $x est initialisé
    assertNotNull($x)Echoue si $x n'est pas initialisé
    assertIsA($x, $t)Echoue si $x n'est pas de la classe ou du type $t
    assertEqual($x, $y)Echoue si $x == $y est faux
    assertNotEqual($x, $y)Echoue si $x == $y est vrai
    assertIdentical($x, $y)Echoue si $x === $y est faux
    assertNotIdentical($x, $y)Echoue si $x === $y est vrai
    assertReference($x, $y)Echoue sauf si $x et $y sont la même variable
    assertCopy($x, $y)Echoue si $x et $y sont la même variable
    assertWantedPattern($p, $x)Echoue sauf si l'expression rationnelle $p capture $x
    assertNoUnwantedPattern($p, $x)Echoue si l'expression rationnelle $p capture $x
    assertNoErrors()Echoue si une erreur PHP arrive
    assertError($x)Echoue si aucune erreur ou message incorrect de PHP n'arrive
    +

    +

    + Nous sommes désormais prêt à lancer notre script de test + en le passant dans le navigateur. + Qu'est-ce qui devrait arriver ? + Il devrait planter... +

    + Fatal error: Failed opening required '../classes/log.php' (include_path='') in /home/marcus/projects/lastcraft/tutorial_tests/Log/tests/log_test.php on line 7 +
    + La raison ? Nous n'avons pas encore créé log.php. +

    +

    + Mais attendez une minute, c'est idiot ! + Ne me dites pas qu'il faut créer + un test sans écrire le code à tester auparavant... +

    +
    +
    +

    + Co-inventeur de + l'Extreme Programming, + Kent Beck a lancé un autre manifeste. + Le livre est appelé + Test Driven Development + (Développement Piloté par les Tests) + ou TDD et élève les tests unitaires à une position élevée de la conception. + En quelques mots, vous écrivez d'abord un petit test + et seulement ensuite le code qui passe ce test. + N'importe quel bout de code. Juste pour qu'il passe. +

    +

    + Vous écrivez un autre test et puis de nouveau du code qui passe. + Vous aurez alors un peu de duplication et généralement + du code pas très propre. Vous remaniez (factorisez) + ce code-là en vous assurant que les tests continuent à passer : + vous ne pouvez rien casser. + Une fois que le code est le plus propre possible + vous êtes prêt à ajouter des nouvelles fonctionnalités. + Il suffit juste de rajouter des nouveaux tests et de recommencer + le cycle une nouvelle fois. + +

    +

    + Il s'agit d'une approche assez radicale et + j'ai parfois l'impression qu'elle est incomplète. + Mais il s'agit d'un moyen efficace pour expliquer + un testeur unitaire ! + Il se trouve que nous avons un test qui échoue, + pour ne pas dire qu'il plante : + l'heure est venue d'écrire du code dans log.php... + +]]> + Il s'agit là du minimum que nous puissions + faire pour éviter une erreur fatale de PHP. + Et maintenant la réponse devient... +

    +

    testoflogging

    + Fail: testcreatingnewfile->True assertion failed.
    +
    1/1 test cases complete. + 0 passes and 1 fails.
    +
    + "testoflogging" a échoué. + Parmi les défauts de PHP on trouve cette fâcheuse tendance + à transformer intérieurement les noms de classes + et de méthodes en minuscules. + SimpleTest utilise ces noms par défaut pour décrire + les tests mais nous pouvons les remplacer par nos propres noms. +function TestOfLogging() { + $this->UnitTestCase('Log class test'); + } + function testCreatingNewFile() { + @unlink('../temp/test.log'); + $log = new Log('../temp/test.log'); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists('../temp/test.log'), 'File created'); + } +} +]]> + Ce qui donne... +
    +

    Log class test

    + Fail: testcreatingnewfile->File created.
    +
    1/1 test cases complete. + 0 passes and 1 fails.
    +
    + Par contre pour le nom des méthodes il n'y a rien à faire, désolé. +

    +

    + Les messages d'un test comme ceux-ci ressemblent + à bien des égards à des commentaires de code. + Certains ne jurent que par eux, d'autres au contraire + les bannissent purement et simplement en les considérant + aussi encombrants qu'inutiles. + Pour ma part, je me situe quelque part au milieu. +

    +

    + Pour que le test passe, nous pourrions nous contenter + de créer le fichier dans le constructeur de Log. + Cette technique "en faisant semblant" est très utile + pour vérifier que le test fonctionne pendant les passages difficiles. + Elle le devient encore plus si vous sortez d'un passage + avec des tests ayant échoués et que vous voulez juste vérifier + de ne pas avoir oublié un truc bête. + Nous n'allons pas aussi lentement donc... + + var $_file_path; + + function Log($file_path) { + $this->_file_path = $file_path; + } + + function message($message) { + $file = fopen($this->_file_path, 'a'); + fwrite($file, $message . "\n"); + fclose($file); + } + } +?> +]]> + Au total, pas moins de 4 échecs ont été nécessaire + pour passer à l'étape suivante. Je n'avais pas créé + le répertoire temporaire, je ne lui avais pas donné + les droits d'écriture, j'avais une coquille et + je n'avais pas non plus ajouté ce nouveau répertoire dans CVS. + N'importe laquelle de ces erreurs aurait pu m'occuper + pendant plusieurs heures si elle était apparue plus tard + mais c'est bien pour ces cas là qu'on teste. + Avec les corrections adéquates, ça donne... +

    +

    Log class test

    +
    1/1 test cases complete. + 1 passes and 0 fails.
    +
    + Ça marche! +

    +

    + Peut-être n'aimez-vous pas le style plutôt minimal de l'affichage. + Les succès ne sont pas montrés par défaut puisque + généralement vous n'avez pas besoin de plus d'information + quand vous comprenez effectivement ce qui se passe. + Dans le cas contraire, pensez à écrire d'autres tests. +

    +

    + D'accord, c'est assez strict. Si vous voulez aussi voir + les succès alors vous pouvez + créer une sous-classe + de HtmlReporter et l'utiliser pour les tests. + Même moi j'aime bien ce confort parfois. +

    +
    +
    +

    + Il y a une nuance ici. Nous ne voulons pas créer de fichier + avant d'avoir effectivement envoyé de message. + Plutôt que d'y réfléchir trop longtemps, + nous allons juste ajouter un test pour ça. +UnitTestCase('Log class test'); + } + function testCreatingNewFile() { + @unlink('../temp/test.log'); + $log = new Log('../temp/test.log'); + $this->assertFalse(file_exists('../temp/test.log'), 'No file created before first message'); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists('../temp/test.log'), 'File created'); + } +} +]]> + ...et découvrir que ça marche déjà... +

    +

    Log class test

    +
    1/1 test cases complete. + 2 passes and 0 fails.
    +
    + En fait je savais que ça allait être le cas. + J'ajoute ce test de confirmation tout d'abord pour + garder l'esprit tranquille, mais aussi pour documenter ce comportement. + Ce petit test supplémentaire dans son contexte + en dit plus long qu'un scénario utilisateur + d'une douzaine de lignes ou qu'un diagramme UML complet. + Que la suite de tests devienne une source de documentation + est un effet secondaire assez agréable. +

    +

    + Devrions-nous supprimer le fichier temporaire à la fin du test ? + Par habitude, je le fais une fois que j'en ai terminé + avec la méthode de test et qu'elle marche. + Je n'ai pas envie de valider du code qui laisse + des restes de fichiers de test traîner après un test. + Mais je ne le fais pas non plus pendant que j'écris le code. + Peut-être devrais-je, mais parfois j'ai besoin de voir ce qui se passe : + on retrouve cet aspect confort évoqué plus haut. +

    +

    + Dans un véritable projet, + nous avons habituellement plus qu'un unique scénario de test : + c'est pourquoi nous allons regarder comment + grouper des tests dans des suites de tests. +

    +
    +
    + + + Créer un nouveau scénario de test. + + + Le Développement Piloté par les Tests en PHP. + + + Les tests comme documentation + est un des nombreux effets secondaires. + + + + + La FAQ de JUnit contient plein de conseils judicieux sur les tests. + + + Ensuite vient "comment grouper des scénarios de tests ensemble". + + + Vous aurez besoin du framework de test SimpleTest pour ces exemples. + + + + + développement logiciel, + programmation php, + outils de développement logiciel, + tutorial php, + scripts php gratuits, + architecture, + ressouces php, + objets fantaisie, + junit, + test php, + test unitaire, + test php automatisé, + tutorial de scénarios de test, + explication d'un scénario de test unitaire, + exemple de test unitaire + + +
    + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/form_testing_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/form_testing_documentation.xml new file mode 100644 index 0000000..89e4933 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/form_testing_documentation.xml @@ -0,0 +1,308 @@ + + + + + Documentation SimpleTest : tester des formulaires HTML + +
    +

    + Lorsqu'une page est téléchargée par WebTestCase + en utilisant get() ou post() + le contenu de la page est automatiquement analysé. + De cette analyse découle le fait que toutes les commandes + à l'intérieur de la balise <form> sont disponibles + depuis l'intérieur du scénario de test. + Prenons par exemple cet extrait de code HTML... +

    
    +    
    +    
    +
    +]]>
    + Il ressemble à... +

    +

    +

    + + +
    +

    +

    + Nous pouvons naviguer vers ce code, via le site + LastCraft, + avec le test suivant... + + function testDefaultValue() { + $this->get('http://www.lastcraft.com/form_testing_documentation.php'); + $this->assertField('a', 'A default'); + } +} +]]> + Directement après le chargement de la page toutes les commandes HTML + sont initiées avec leur valeur par défaut, comme elles apparaîtraient + dans un navigateur web. L'assertion teste qu'un objet HTML + avec le nom "a" existe dans la page + et qu'il contient la valeur "A default". +

    +

    + Nous pourrions retourner le formulaire tout de suite, + mais d'abord nous allons changer la valeur du champ texte. + Ce n'est qu'après que nous le transmettrons... +get('http://www.my-site.com/'); + $this->assertField('a', 'A default'); + $this->setField('a', 'New value'); + $this->clickSubmit('Go'); + } +} +]]> + Parce que nous n'avons spécifié ni attribut "method" + sur la balise form, ni attribut "action", + le scénario de test suivra le comportement classique d'un navigateur : + transmission des données avec une requête GET + vers la même page. SimpleTest essaie d'émuler + le comportement typique d'un navigateur autant que possible, + plutôt que d'essayer d'attraper des attributs manquants sur les balises. + La raison est simple : la cible d'un framework de test est + la logique d'une application PHP, pas les erreurs + -- de syntaxe ou autres -- du code HTML. + Pour les erreurs HTML, d'autres outils tel + HTMLTidy + devraient être employés. +

    +

    + Si un champ manque dans n'importe quel formulaire ou si + une option est indisponible alors WebTestCase::setField() + renverra false. Par exemple, supposons que + nous souhaitons vérifier qu'une option "Superuser" + n'est pas présente dans ce formulaire... +

    Select type of user to add:
    +
    +]]>
    + Qui ressemble à... +

    +

    +

    + Select type of user to add: + +
    +

    +

    + Le test suivant le confirmera... + + $this->get('http://www.lastcraft.com/form_testing_documentation.php'); + $this->assertFalse($this->setField('type', 'Superuser')); + } +} +]]> + La sélection ne sera pas changée suite à un échec d'initialisation + d'une valeur sur un objet. +

    +

    + Voici la liste complète des objets supportés à aujourd'hui... +

      +
    • Champs texte, y compris les champs masqués (hidden) ou cryptés (password).
    • +
    • Boutons submit, en incluant aussi la balise button, mais pas encore les boutons reset
    • +
    • Aires texte (textarea) avec leur gestion des retours à la ligne (wrap).
    • +
    • Cases à cocher, y compris les cases à cocher multiples dans un même formulaire.
    • +
    • Listes à menu déroulant, y compris celles à sélections multiples.
    • +
    • Boutons radio.
    • +
    • Images.
    • +
    +

    +

    + Le navigateur proposé par SimpleTest émule les actions + qui peuvent être réalisées par un utilisateur sur + une page HTML standard. Javascript n'est pas supporté et + il y a peu de chance pour qu'il le soit prochainement. +

    +

    + Une attention particulière doit être porté aux techniques Javascript + qui changent la valeur d'un champ caché : elles ne peuvent pas être + réalisées avec les commandes classiques de SimpleTest. + Une méthode alternative est proposée plus loin. +

    +
    +
    +

    + SimpleTest peut gérer deux types de commandes à valeur multiple : + les menus déroulants à sélection multiple et les cases à cocher + avec le même nom à l'intérieur même d'un formulaire. + La nature de ceux-ci implique que leur initialisation + et leur test sont légèrement différents. + Voici un exemple avec des cases à cocher... +

    
    +    Create privileges allowed:
    +    
    + Retrieve privileges allowed: +
    + Update privileges allowed: +
    + Destroy privileges allowed: +
    + + +]]>
    + Qui se traduit par... +

    +

    +

    + Create privileges allowed: +
    + Retrieve privileges allowed: +
    + Update privileges allowed: +
    + Destroy privileges allowed: +
    + +
    +

    +

    + Si nous souhaitons désactiver tous les privilèges sauf + ceux de téléchargement (Retrieve) et transmettre cette information, + nous pouvons y arriver par... + + function testDisableNastyPrivileges() { + $this->get('http://www.lastcraft.com/form_testing_documentation.php'); + $this->assertField('crud', array('c', 'r', 'u', 'd')); + $this->setField('crud', array('r')); + $this->clickSubmit('Enable Privileges'); + } +} +]]> + Plutôt que d'initier le champ à une valeur unique, + nous lui donnons une liste de valeurs. + Nous faisons la même chose pour tester les valeurs attendues. + Nous pouvons écrire d'autres bouts de code de test + pour confirmer cet effet, peut-être en nous connectant + comme utilisateur et en essayant d'effectuer une mise à jour. +

    +
    +
    +

    + Si vous souhaitez tester un formulaire dépendant de Javascript + pour la modification d'un champ caché, vous ne pouvez pas + simplement utiliser setField(). + Le code suivant ne fonctionnera pas : +// Ne fonctionne *pas* + $this->setField('un_champ_caché', '123'); + $this->clickSubmit('OK'); + } +} +]]> + A la place, vous aurez besoin d'ajouter le paramètre supplémentaire + du formulaire à la méthode clickSubmit() : +$this->clickSubmit('OK', array('un_champ_caché'=>'123')); + } + +} +]]> +

    +

    + N'oubliez pas que de la sorte, vous êtes effectivement en train + de court-circuitez une partie de votre application (le code Javascript + dans le formulaire) et que peut-être serait-il plus prudent + d'utiliser un outil comme + Selenium pour mettre sur pied + un test de recette complet. +

    +
    +
    +

    + Si vous souhaitez tester un gestionnaire de formulaire + mais que vous ne l'avez pas écrit ou que vous n'y avez + pas encore accès, vous pouvez créer un envoi de formulaire à la main. + + function testAttemptedHack() { + $this->post( + 'http://www.my-site.com/add_user.php', + array('type' => 'superuser')); + $this->assertNoUnwantedPattern('/user created/i'); + } +} +]]> + En ajoutant des données à la méthode WebTestCase::post(), + nous essayons de télécharger la page via la transmission d'un formulaire. +

    +
    +
    + + + Modifier les valeurs d'un formulaire et + réussir à transmettre un simple formulaire + + + Gérer des objets à valeurs multiples + en initialisant des listes. + + + Le cas des formulaires utilisant Javascript pour + modifier un champ caché + + + Envoi brut quand il n'existe pas de bouton à cliquer. + + + + + La page du projet SimpleTest sur + SourceForge. + + + La page de téléchargement de SimpleTest sur + LastCraft. + + + L'API du développeur pour SimpleTest + donne tous les détails sur les classes et les assertions disponibles. + + + + + développement logiciel, + programmation php pour des clients, + php centré sur le client, + outils de développement logiciel, + frameword de test de recette, + scripts php gratuits, + architecture, + ressources php, + HTMLUnit, + JWebUnit, + test php, + ressources de test unitaire, + test web + + +
    + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/gain_control_tutorial.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/gain_control_tutorial.xml new file mode 100644 index 0000000..5f13ea0 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/gain_control_tutorial.xml @@ -0,0 +1,324 @@ + + + + + Tutorial de test unitaire en PHP - Isoler les variables pendant le test + + +

    + Pour tester un module de code vous avez besoin + d'avoir un contrôle très précis sur son environnement. + Si quelque chose change dans les coulisses, + par exemple dans un fichier de configuration, + alors les tests peuvent échouer de façon inattendue. + Il ne s'agirait plus d'un test de code sans équivoque + et pourrait vous faire perdre des heures précieuses + à la recherche d'erreurs dans un code qui fonctionne. + Alors qu'il s'agit d'un problème de configuration + qui plante le test en question. + Au mieux vos scénarios de test deviennent de plus en plus + compliqués afin de prendre en compte toutes les variations possibles. +

    +
    +
    +

    + Il y a souvent beaucoup de variables évidentes qui peuvent affecter + un scénario de test unitaire, d'autant plus dans un environnement + de développement web dans lequel PHP a ses aises. + Parmi celles-ci, on trouve les paramètres de connexion + à la base de données et ceux de configuration, + les droits de fichier et les ressources réseau, etc. + L'échec ou la mauvaise installation de l'un ou l'autre + de ces composants cassera la suite de test. + Est-ce que nous devons ajouter des tests pour valider + l'installation de ces composants ? + C'est une bonne idée mais si vous les placez + dans les tests du module de code vous aller commencer + à encombrer votre code de test avec des détails + hors de propos avec la tâche en cours. + Ils doivent être placés dans leur propre groupe de tests. +

    +

    + Par contre un autre problème reste : + nos machines de développement doivent aussi avoir + tous les composants système d'installés avant l'exécution + de la suite de test. Et vos tests s'exécuteront plus lentement. +

    +

    + Devant un tel dilemme, nous créerons souvent + des versions enveloppantes des classes qui gèrent ces ressources. + Les vilains détails de ces ressources sont ensuite codés une seule fois. + J'aime bien appeler ces classes des "classes frontière" + étant donné qu'elles existent en bordure de l'application, + l'interface entre votre application et le reste du système. + Ces classes frontière sont - dans le meilleur des cas - simulées + pendant les tests par des versions de simulacre. + Elles s'exécutent plus rapidement et sont souvent appelées + "bouchon serveur [Ndt : Server Stubs]" + ou dans leur forme plus générique "objet fantaisie + [Ndt : Mock Objects]". + Envelopper et bouchonner chacune de ces ressources + permet d'économiser pas mal de temps. +

    +

    + Un des facteurs souvent négligés reste le temps. + Par exemple, pour tester l'expiration d'une session des codeurs + vont souvent temporairement en caler la durée + à une valeur très courte, disons 2 secondes, + et ensuite effectuer un sleep(3) : + ils estiment alors que la session a expirée. + Sauf que cette opération ajoute 3 secondes à la suite de test : + il s'agit souvent de beaucoup de code en plus + pour rendre la classe de session aussi malléable. + Plus simple serait d'avoir un moyen d'avancer l'horloge arbitrairement. + De contrôler le temps. +

    +
    +
    +

    + Une nouvelle fois, nous allons effectuer notre conception + d'une enveloppe d'horloge via l'écriture de tests. + Premièrement nous ajoutons un scénario de test d'horloge + dans notre suite de test tests/all_tests.php... + +require_once('clock_test.php'); + +$test = &new TestSuite('All tests'); +$test->addTestCase(new TestOfLogging()); +$test->addTestCase(new TestOfClock()); +$test->run(new HtmlReporter()); +?> +]]> + Ensuite nous créons le scénario de test + dans un nouveau fichier tests/clock_test.php... +UnitTestCase('Clock class test'); + } + function testClockTellsTime() { + $clock = new Clock(); + $this->assertEqual($clock->now(), time(), 'Now is the right time'); + } + function testClockAdvance() { + } + } +?> +]]> + Notre unique test pour le moment, c'est que + notre nouvelle class Clock se comporte + comme un simple substitut de la fonction time() en PHP. + L'autre méthode tient lieu d'emploi. + C'est notre chose à faire en quelque sorte. + Nous ne lui avons pas donnée de test parce que ça casserait notre rythme. + Nous écrirons cette fonctionnalité de décalage + dans le temps une fois que nous serons au vert. + Pour le moment nous ne sommes évidemment pas dans le vert... +

    +
    + Fatal error: Failed opening required '../classes/clock.php' (include_path='') in + /home/marcus/projects/lastcraft/tutorial_tests/tests/clock_test.php on line 2 +
    +
    + Nous créons un fichier classes/clock.php comme ceci... + +]]> + De la sorte nous reprenons le cours du code. +
    +

    All tests

    + Fail: Clock class test->testclocktellstime->[NULL: ] should be equal to [integer: 1050257362]
    +
    3/3 test cases complete. + 4 passes and 1 fails.
    +
    + Facile à corriger... + + return time(); + } +} +]]> + Et nous revoici dans le vert... +
    +

    All tests

    +
    3/3 test cases complete. + 5 passes and 0 fails.
    +
    + Il y a juste un petit problème. + L'horloge pourrait basculer pendant l'assertion + et créer un écart d'une seconde. + Les probabilités sont assez faibles mais s'il devait + y avoir beaucoup de tests de chronométrage + nous finirions avec une suite de test qui serait erratique + et forcément presque inutile. + Nous nous y attaquerons bientôt + et pour l'instant nous l'ajoutons dans la liste des "choses à faire". +

    +

    + Le test d'avancement ressemble à... +UnitTestCase('Clock class test'); + } + function testClockTellsTime() { + $clock = new Clock(); + $this->assertEqual($clock->now(), time(), 'Now is the right time'); + } + function testClockAdvance() { + $clock = new Clock(); + $clock->advance(10); + $this->assertEqual($clock->now(), time() + 10, 'Advancement'); + } +} +]]> + Le code pour arriver au vert est direct : + il suffit d'ajouter un décalage de temps. + + var $_offset; + + function Clock() { + $this->_offset = 0; + } + + function now() { + return time() + $this->_offset; + } + + function advance($offset) { + $this->_offset += $offset; + } +} +]]> +

    +
    +
    +

    + Notre fichier all_tests.php contient des répétitions + dont nous pourrions nous débarrasser. + Nous devons ajouter manuellement tous nos scénarios de test + depuis chaque fichier inclus. + C'est possible de les enlever mais avec les précautions suivantes. + La classe GroupTest inclue une méthode bien pratique + appelée addTestFile() qui prend un fichier PHP comme paramètre. + Ce mécanisme prend note de toutes les classes : + elle inclut le fichier et ensuite regarde toutes les classes + nouvellement créées. S'il y a des filles de TestCase + elles sont ajoutées au nouveau test de groupe. +

    +

    + En outre, la bibliothèque autorun lancera tous les scénarios + de test collectés automagiquement après les avoir chargés. +

    +

    + Voici notre suite de test remaniée en appliquant cette méthode... + +require_once(SIMPLE_TEST . 'autorun.php'); + +class AllTests extends TestSuite { + function AllTests() { + $this->TestSuite('All tests'); + $this->addFile('log_test.php'); + $this->addFile('clock_test.php'); + } +} +?> +]]> + Les inconvéniants sont les suivants... +

      +
    1. + Si le fichier de test a déjà été inclus, + aucune nouvelle classe ne sera ajoutée au groupe. +
    2. +
    3. + Si le fichier de test contient d'autres classes + reliées à TestCase alors celles-ci + aussi seront ajouté au test de groupe. +
    4. +
    + Dans nos test nous n'avons que des scénarios dans les + fichiers de test et en plus nous avons supprimé + leur inclusion du script all_tests.php : + nous sommes donc en règle. C'est la situation la plus commune. +

    +

    + Nous devrions corriger au plus vite le petit problème + de décalage possible sur l'horloge : + c'est ce que nous faisons ensuite. +

    +
    +
    + + + Le temps est souvent une variable négligée dans les tests. + + + Une classe horloge + nous permet de modifier le temps. + + + Nettoyer le test de groupe. + + + + + La section précédente : + grouper des tests unitaires. + + + La section suivante : + sous classer les scénarios de test. + + + Vous aurez besoin du + testeur unitaire SimpleTest pour les exemples. + + + + + développement logiciel, + programmation php, + outils de développement logiciel, + tutorial php, + scripts php gratuits, + organisation de tests unitaires, + conseil de test, + astuce de développement, + architecture logicielle pour des tests, + exemple de code php, + objets fantaisie, + junit, + test php, + outil de test unitaire, + suite de test php + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/group_test_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/group_test_documentation.xml new file mode 100644 index 0000000..80d9178 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/group_test_documentation.xml @@ -0,0 +1,353 @@ + + + + + Documentation SimpleTest : Grouper des tests + +
    +

    + Pour lancer les scénarios de tests en tant que groupe, + ils devraient être placés dans des fichiers sans le code du lanceur... + +]]> + Autant de scénarios que nécessaires peuvent être + mis dans un fichier unique. Ils doivent contenir + tout le code nécessaire, entre autres la bibliothèque testée, + mais aucune des bibliothèques de SimpleTest. +

    +

    + Si vous avez étendu l'un ou l'autre des scénarios de test, + vous pouvez aussi les inclure. + + class MyFileTestCase extends UnitTestCase { + ... + } + SimpleTestOptions::ignore('MyFileTestCase'); + + class FileTester extends MyFileTestCase { + ... + } + + class SocketTester extends UnitTestCase { + ... + } +?> +]]> + La classe FileTester ne contient aucun test véritable, + il s'agit d'une classe de base pour d'autres scénarios de test. + Pour cette raison nous utilisons la directive + SimpleTestOptions::ignore() pour indiquer + au prochain groupe de tests de l'ignorer. + Cette directive peut se placer n'importe où dans le fichier + et fonctionne quand un fichier complet des scénarios de test + est chargé (cf. ci-dessous). + Nous l'appelons file_test.php. +

    +

    + Ensuite nous créons un fichier de groupe de tests, + disons group_test.php. + Vous penserez à un nom plus convaincant, j'en suis sûr. + Nous lui ajoutons le fichier de test avec une méthode sans risque... + + require_once('file_test.php'); + + $test = &new GroupTest('All file tests'); + $test->addTestCase(new FileTestCase()); + $test->run(new HtmlReporter()); +?> +]]> + Ceci instancie le scénario de test avant que + la suite de test ne soit lancée. + Ça pourrait devenir assez onéreux avec + un grand nombre de scénarios de test : + il existe donc une autre méthode qui instancie + la classe uniquement quand elle devient nécessaire... + + $test->addTestClass('FileTestCase'); + $test->run(new HtmlReporter()); +?> +]]> + Le problème de cette technique est que pour + chaque scénario de test supplémentaire nous aurons à importer + (via require_once()) le fichier de code de test + et à instancier manuellement chaque scénario de test. + Nous pouvons nous épargner beaucoup de dactylographie avec... + + $test->addTestFile('file_test.php'); + $test->run(new HtmlReporter()); +?> +]]> + Voici ce qui vient de se passer : + la classe GroupTest a réalisé le + require_once() pour nous. + Ensuite elle vérifie si de nouvelles classes de scénario + de test ont été créées par ce nouveau fichier + et les ajoute automatiquement au groupe de tests. + Désormais tout ce qu'il nous reste à faire, + c'est d'ajouter chaque nouveau fichier. +

    +

    + Il y a deux choses qui peuvent planter + et qui demandent un minimum d'attention... +

      +
    1. + Le fichier peut déjà avoir été analysé par PHP + et dans ce cas aucune classe ne sera ajoutée. + Pensez à bien vérifier que les scénarios de test + ne sont inclus que dans ce fichier et dans aucun autre + (Note : avec la nouvelle fonctionnalité autorun, + ce problème a maintenant été résolu). +
    2. +
    3. + Les nouvelles classes d'extension de scénario + de test qui sont incluses seront placées + dans le groupe de tests et exécutées par la même occasion. + Vous aurez à ajouter une directive + SimpleTestOptions::ignore() pour ces classes + ou alors pensez à les ajouter avant la ligne + GroupTest::addTestFile(). +
    4. +
    +

    +
    +
    +

    + La technique ci-dessus place tous les scénarios de test + dans un unique et grand groupe. + Sauf que pour des projets plus conséquents, + ce n'est probablement pas assez souple; + vous voudriez peut-être grouper les tests tout à fait différemment. +

    +

    + Pour obtenir un groupe de tests plus souple + nous pouvons sous classer GroupTest + et ensuite l'instancier au cas par cas... + + class FileGroupTest extends GroupTest { + function FileGroupTest() { + $this->GroupTest('All file tests'); + $this->addTestFile('file_test.php'); + } + } +?> +]]> + Ceci nomme le test dans le constructeur + et ensuite ajoute à la fois nos scénarios + de test et un unique groupe en dessous. + Bien sûr nous pouvons ajouter plus d'un groupe à cet instant. + Nous pouvons maintenant invoquer les tests + à partir d'un autre fichier d'exécution... + + $test = &new FileGroupTest(); + $test->run(new HtmlReporter()); +?> +]]> + ...ou alors nous pouvons les grouper + dans un groupe de tests encore plus grand... + + $test = &new BigGroupTest('Big group'); + $test->addTestCase(new FileGroupTest()); + $test->addTestCase(...); + $test->run(new HtmlReporter()); +?> +]]> + Si nous souhaitons lancer le groupe de tests original + sans utiliser ses petits fichiers d'exécution, + nous pouvons mettre le code du lanceur de test + derrière des barreaux quand nous créons chaque groupe. +GroupTest('All file tests'); + $test->addTestFile('file_test.php'); + } + } + + if (! defined('RUNNER')) { + define('RUNNER', true); + $test = &new FileGroupTest(); + $test->run(new HtmlReporter()); + } +?> +]]> + Cette approche exige aux barrières d'être activées + à l'inclusion du fichier de groupe de tests, + mais c'est quand même moins de tracas que beaucoup + de fichiers de lancement éparpillés. + Reste à inclure des barreaux identiques + au niveau supérieur afin de s'assurer que + le run() ne sera lancé qu'une seule fois + à partir du script de haut niveau qui l'a invoqué. +addTestCase(new FileGroupTest()); + $test->addTestCase(...); + $test->run(new HtmlReporter()); +?> +]]> + Comme les scénarios de test normaux, + un GroupTest peut être chargé avec la méthode + GroupTest::addTestFile(). + + $test->addTestFile('file_group_test.php'); + $test->addTestFile(...); + $test->run(new HtmlReporter()); +?> +]]> +

    +
    +
    +

    + Si vous avez déjà des tests unitaires pour votre code + ou alors si vous étendez des classes externes + qui ont déjà leurs propres tests, il y a peu de chances + pour que ceux-ci soient déjà au format SimpleTest. + Heureusement il est possible d'incorporer ces scénarios + de test en provenance d'autres testeurs unitaires + directement dans des groupes de test SimpleTest. +

    +

    + Par exemple, supposons que nous ayons + ce scénario de test prévu pour + PhpUnit + dans le fichier config_test.php... +class ConfigFileTest extends TestCase { + function ConfigFileTest() { + $this->TestCase('Config file test'); + } + + function testContents() { + $config = new ConfigFile('test.conf'); + $this->assertRegexp('/me/', $config->getValue('username')); + } +} +]]> + Le groupe de tests peut le reconnaître à partir + du moment où nous mettons l'adaptateur approprié + avant d'ajouter le fichier de test... + + require_once('simpletest/adapters/phpunit_test_case.php'); + + $test = &new GroupTest('All file tests'); + $test->addTestFile('config_test.php'); + $test->run(new HtmlReporter()); +?> +]]> + Il n'y a que deux adaptateurs, + l'autre est pour le paquet testeur unitaire de + PEAR... + + require_once('simpletest/adapters/pear_test_case.php'); + + $test = &new GroupTest('All file tests'); + $test->addTestFile('some_pear_test_cases.php'); + $test->run(new HtmlReporter()); +?> +]]> + Les scénarios de test de PEAR peuvent être + librement mélangés avec ceux de SimpleTest + mais vous ne pouvez pas utiliser les assertions + de SimpleTest au sein des versions héritées + des scénarios de test. La raison ? + Une simple vérification que vous ne rendez pas + par accident vos scénarios de test complètement + dépendants de SimpleTest. + Peut-être que vous souhaitez publier + votre bibliothèque sur PEAR par exemple : + ça voudrait dire la livrer avec des scénarios de + test compatibles avec PEAR::PhpUnit. +

    +
    +
    + + + Plusieurs approches pour grouper des tests ensemble. + + + Combiner des groupes des tests dans des + groupes plus grands. + + + Intégrer des scénarios de test hérités + d'un autre type de PHPUnit. + + + + + La page du projet SimpleTest sur + SourceForge. + + + La page de téléchargement de SimpleTest sur + LastCraft. + + + + + test unitaire en php, + intégration de test, + documentation, + marcus baker, + perrick penet, + test simple, + documentation simpletest, + phpunit, + pear + + +
    + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/group_test_tutorial.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/group_test_tutorial.xml new file mode 100644 index 0000000..84f7016 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/group_test_tutorial.xml @@ -0,0 +1,247 @@ + + + + + + Tutorial de test unitaire PHP - Grouper des tests unitaires et exemples d'écriture de scénarios de tests + + + +

    + Pour enchaîner nous allons remplir des blancs et créer une suite de tests. +

    +
    +
    +

    + Ajouter un autre test peut être aussi simple + qu'ajouter une nouvelle méthode à un scénario de test... +UnitTestCase('Log class test'); + } + function testCreatingNewFile() { + @unlink('../temp/test.log'); + $log = new Log('../temp/test.log'); + $this->assertFalse(file_exists('../temp/test.log'), 'Created before message'); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists('../temp/test.log'), 'File created'); + @unlink('../temp/test.log'); + } + function testAppendingToFile() { + @unlink('../temp/test.log'); + $log = new Log('../temp/test.log'); + $log->message('Test line 1'); + $messages = file('../temp/test.log'); + $this->assertWantedPattern('/Test line 1/', $messages[0]); + $log->message('Test line 2'); + $messages = file('../temp/test.log'); + $this->assertWantedPattern('/Test line 2/', $messages[1]); + @unlink('../temp/test.log'); + } +} +]]> + La méthode du scénario de test assertWantedPattern() + utilise les expressions rationnelles Perl pour vérifier + qu'une chaîne respecte un certain motif. +

    +

    + Tout ce que nous faisons dans ce nouveau test, + c'est écrire une ligne dans un fichier, puis la lire, + le tout deux fois de suite. + Nous souhaitons simplement vérifier que le loggueur + ajoute le texte à la fin plutôt qu'écraser + les données déjà existantes. Quelque peu pédant, + mais après tout il s'agit d'un tutorial ! +

    +

    + De toute façon ce test passe directement... +

    +

    Log class test

    +
    1/1 test cases complete. + 4 passes and 0 fails.
    +
    + Notre code contient actuellement beaucoup de répétitions, + nous devons effacer le fichier de test avant et après chaque test. + De même que JUnit, + SimpleTest utilise les méthodes + setUp() et tearDown() + qui sont exécutées respectivement avant et après chaque test. + La suppression du fichier est commune à tous les tests : + nous devrions donc y mettre cette opération. +

    +

    + Nos tests sont verts donc nous pouvons faire un peu de remaniement... +UnitTestCase('Log class test'); + } + function setUp() { + @unlink('../temp/test.log'); + } + function tearDown() { + @unlink('../temp/test.log'); + } + function testCreatingNewFile() { + $log = new Log('../temp/test.log'); + $this->assertFalse(file_exists('../temp/test.log'), 'Created before message'); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists('../temp/test.log'), 'File created'); + } + function testAppendingToFile() { + $log = new Log('../temp/test.log'); + $log->message('Test line 1'); + $messages = file('../temp/test.log'); + $this->assertWantedPattern('/Test line 1/', $messages[0]); + $log->message('Test line 2'); + $messages = file('../temp/test.log'); + $this->assertWantedPattern('/Test line 2/', $messages[1]); + } +} +]]> + Le test reste vert. Nous pouvons continuer + à ajouter des méthodes sans test au scénario, + il suffit que leur nom ne commence pas par + la chaîne "test". + Seules les méthodes commençant par "test" sont exécutées. + Nous pouvons donc continuer le remaniement... +UnitTestCase('Log class test'); + } + function setUp() { + @unlink('../temp/test.log'); + } + function tearDown() { + @unlink('../temp/test.log'); + } + function getFileLine($filename, $index) { + $messages = file($filename); + return $messages[$index]; + } + function testCreatingNewFile() { + $log = new Log('../temp/test.log'); + $this->assertFalse(file_exists('../temp/test.log'), 'Created before message'); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists('../temp/test.log'), 'File created'); + } + function testAppendingToFile() { + $log = new Log('../temp/test.log'); + $log->message('Test line 1'); + $this->assertWantedPattern('/Test line 1/', $this->getFileLine('../temp/test.log', 0)); + $log->message('Test line 2'); + $this->assertWantedPattern('/Test line 2/', $this->getFileLine('../temp/test.log', 1)); + } +} +]]> + Que vous préfériez cette version ou la précédente ne dépend + que de votre goût personnel. + Il y a un peu plus de code dans cette dernière + mais la logique du test est plus claire. +

    +
    +
    +

    + Un scénario de test ne fonctionne pas tout seul + pendant très longtemps. + Quand on code pour de vrai nous souhaitons exécuter + un maximum de tests aussi souvent + et aussi rapidement que possible. + Ça veut dire les grouper dans des suites de tests + qui incluent l'ensemble des tests de l'application. +

    +

    + Premièrement nous devons supprimer le code d'exécution + des tests se trouvant dans notre scénario de test. + +require_once('../classes/log.php'); + +class TestOfLogging extends UnitTestCase { + ... +} +?> +]]> + Nous n'avons plus besoin de la constante + SIMPLE_TEST. + Ensuite nous créons un groupe de tests appelé + all_tests.php dans le répertoire tests... +addTestCase(new TestOfLogging()); + $test->run(new HtmlReporter()); +?> +]]> + Il n'y a presque de pas de différence tant que les choses marchent... +

    +

    All tests

    +
    1/1 test cases complete. + 4 passes and 0 fails.
    +
    + Les tests du groupe s'ajoutent au compteur + des scénarios de test. Ajouter des nouveaux scénarios + de test est très simple. + Il suffit d'inclure le fichier d'un scénario + et d'ajouter individuellement tous les scénarios autonomes. + Vous pouvez aussi emboîter les groupes de test + les uns dans les autres (tout en faisant bien attention d'éviter les boucles). +

    +

    + Dans la page suivante + nous les ajouterons encore plus rapidement. +

    +
    +
    + + + Ajouter un autres test au scénario existant et remanier. + + + La technique brute pour grouper des tests unitaires. + + + + + Ensuite + vient le contrôle de comment la classe sous le test interagit + avec le reste du système. + + + Avant + il y a la création du premier test. + + + Vous aurez besoin de SimpleTest + pour exécuter ces exemples. + + + + + développement logiciel, + programmation php, + outils de développement logiciel, + tutorial php, + scripts php gratuits, + pilotage par les tests, + architecture, + ressouces php, + objets fantaisie, + junit, + test php, + test unitaire, + phpunit, + test unitaire php + + +
    + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/improving_design_tutorial.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/improving_design_tutorial.xml new file mode 100644 index 0000000..b2e3686 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/improving_design_tutorial.xml @@ -0,0 +1,218 @@ + + + + + + tutoriel de test unitaire en PHP - Conception du haut vers le bas, tester d'abord avec des objets fantaisie + + +
    +

    + J'ai menti. +

    +

    + Je n'ai pas créé de test pour le scripteur, + uniquement l'interface FileWriter + que j'ai affiché plus tôt. + En fait je vais encore m'éloigner d'un article fini + et présumer l'existence un scripteur abstrait + dans classes/writer.php... +Writer { + + function Writer() { + } + + function write($message) { + } +} +?> +]]> + Les changements correspondant au niveau du test sont... + +Mock::generate('Writer'); + +class TestOfLogging extends UnitTestCase { + function TestOfLogging() { + $this->UnitTestCase('Log class test'); + } + function testWriting() { + $clock = &new MockClock($this); + $clock->setReturnValue('now', 'Timestamp'); + $writer = &new MockWriter($this); + $writer->expectOnce('write', array('[Timestamp] Test line')); + $log = &new Log($writer); + $log->message('Test line', &$clock); + $writer->tally(); + } +} +?> +]]> + Afin d'utiliser la classe de log, nous aurions besoin + de coder un scripteur de fichier - ou un autre type de scripteur - + mais pour le moment nous ne faisons que des tests + et nous n'en avons pas encore besoin. + En d'autres termes, en utilisant des objets fantaisie + nous pouvons décaler la création d'un objet + de niveau plus bas jusqu'au moment opportun. + Non seulement nous pouvons faire la conception du haut vers le bas, + mais en plus nous pouvons aussi tester du haut vers le bas. +

    +
    +
    +

    + Imaginez pour un moment que nous ayons commencé la classe de log + à partir d'une autre direction. + Simulez avoir écrit juste assez du Log + pour avoir réaliser le besoin d'un Writer. + Comme l'aurions-nous inclus ? +

    +

    + Bon, l'héritage du scripteur ne nous aurait pas + permis de le simuler du point de vue des tests. + De celui de la conception nous aurions été restreint + à un unique scripteur sans héritage multiple. +

    +

    + Créer un scripteur interne, plutôt qu'en le passant + au constructeur, en choisissant un nom de classe, + est possible, mais nous aurions moins de contrôle + sur l'initialisation de l'objet fantaisie. + Du point de vue de la conception il aurait été + presque impossible de passer des paramètres + au scripteur dans tous les formats possibles + et envisageables. Vous auriez dû restreindre + le scripteur à un hash ou à une chaîne compliquée + décrivant tous les détails de la cible. + Au mieux compliqué sans raison. +

    +

    + Utiliser une méthode fabrique pour créer le scripteur + intérieurement serait possible, mais ça voudrait + dire le sous classer pour les tests de manière + à remplacer la méthode fabrique par une autre méthode + renvoyant un leurre. Plus de boulot du point de vue des tests, + quoique toujours possible. Du point de vue de la conception, + ça voudrait dire créer une nouvelle sous-classe de + log pour chaque type de scripteur. + Cela s'appelle une hiérarchie de classe parallèle + et fait bien entendu à de la duplication. + Beurk. +

    +

    + A l'autre extrême, passer ou créer le scripteur + à chaque message aurait été répétitif + et aurait réduit le code de la classe Log + à une unique méthode, un signe certain + que toute la classe est devenue redondante. +

    +

    + Cette tension entre la facilité du test + et le refus de la répétition nous a permis + de trouver une conception à la fois flexible et propre. + Vous vous souvenez de notre bref envie de l'héritage multiple ? + Nous l'avons remplacé par du polymorphisme + (plein de scripteurs) et séparé la hiérarchie du journal + de celle de l'écriture. + Nous relions les deux par agrégation à travers + le plus simple Log. + Cette astuce est en fait un design pattern + (modèle de conception) appelé "Pont" ou "Bridge". +

    +

    + Donc nous avons été poussé par le code de test + (nous n'avons presque rien écrit d'autre) vers un design pattern. + Pensez-y une seconde. Les tests améliorent + la qualité du code, à coup sûr dans mon cas, + mais il y a quelque chose de bien plus profond et plus puissant. +

    +

    + Les tests ont amélioré la conception. +

    +
    +
    +

    + Créer un objet fantaisie est aussi simple + que de créer l'interface à l'écrit. + Si vous utilisez de l'UML ou d'autres outils + pour générer ces interfaces alors vous avez un chemin encore + plus flexible pour générer rapidement vos objets de test. + Même sans, vous pouvez passer du dessin sur tableau blanc, + à l'écriture de l'objet fantaisie, + puis à la génération de l'interface qui nous renvoie + de nouveau au tableau blanc, le tout très simplement. + Comme le remaniement, la conception, le code et les tests s'unifient. +

    +

    + Parce que les objets fantaisie travaillent du haut vers le bas, + ils peuvent être amenés dans la conception plus rapidement + qu'un remaniement classique qui demande + quant à lui du code fonctionnel avant de pourvoir s'installer. + Ça veut dire que le code de test interagit plus vite + avec la conception : par conséquent la qualité + de la conception augmente elle aussi plus vite. +

    +

    + Un testeur unitaire est un outil de code. + Un testeur unitaire avec objet fantaisie est un outil de conception. +

    +
    +
    + + + Simuler maintenant, coder plus tard. + + + Nous dérivons vers le design pattern bridge. + + + Conception et test main dans la main. + + + + + Ce tutorial suit + les classes frontière. + + + Vous aurez besoin du + framework de test SimpleTest + pour essayer ces exemples. + + + Pour des discussions plus fournis sur + les objets fantaisie, voyez le + + Wiki des Extreme Tuesday + ou le Wiki C2 + (en anglais tous les deux). + + + + + développement logiciel, + tutoriel de programmation php, + scénarios de test en php, + outils de développement logiciel, + tutorial php, + scripts php gratuits, + architecture, + exemple php, + exemple d'objet fantaisie, + test style junit, + architecture logicielle pour des tests, + framework de test en php, + test unitaire, + objet fantaisie en php + test php, + suite de test php + + +
    + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/index.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/index.xml new file mode 100644 index 0000000..501897e --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/index.xml @@ -0,0 +1,69 @@ + + + + + Les articles de :: onpk :: sur php / simpletest : traductions en français des articles publiés sur Lastcraft.com + + + +

    + Cette section rassemble l'ensemble des articles et documentations pour SimpleTest traduits en français. +

    +
    +
    +

    + J'ai commencé par découvrir l'Extreme Programming via le web. Ensuite j'ai gagné un livre en français sur XP. Et découvrant la confiance supplémentaire gagnée via les tests unitaires et de recette, je suis devenu "test-infected". +

    +

    + Après avoir goûté aux joies de SimpleTest, j'ai contacté Marcus Baker -- responsable du projet -- pour lui proposer de traduire en français la page d'introduction, puis les tutoriaux et finalement toute la documentation. Un travail qui n'aurait pas été possible sans l'aide de quelques relecteurs bienveillants : Jérémie C., David B., Emmanuel G., Olivier L. et Cédric G. (dans le désordre). +

    +

    + Si vous lisez attentivement ces quelques pages et que vous y trouvez encore des fautes d'orthographe, pensez à me les renvoyer : comme ça le prochain ne pourra pas se dire la même chose. +

    +
    +
    +

    + J'ai un blog -- :: onpk ::, une entreprise -- No Parking dont le produit phare est openTIME. Dans la communauté je me ballade du côté de l'AFUP, des apéros PHP et des praticiens XP. +

    +
    +
    +

    +

    +
    +
    + + + Comment en arrive-t-on au développement piloté par les tests ? + + + Et en dehors des tests ? + + + Tous les articles + + + + + SimpleTest -- l'original en anglais. + + + + + développement logiciel, + programmation php, + outils de développement logiciel, + tutorial php, + scripts php gratuits, + conseil de test, + architecture logicielle pour des tests, + exemple de code php, + junit, + test php, + outil de test unitaire, + suite de test php + + + Des articles (documentations et tutoriels) pour découvrir le développement agile en PHP avec le framework de tests unitaires SimpleTest. + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/intro.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/intro.xml new file mode 100644 index 0000000..ceacc63 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/intro.xml @@ -0,0 +1,39 @@ + + + + + Les articles de :: onpk :: sur php / simpletest : traductions en français des articles publiés sur Lastcraft.com + + + +

    + Le testeur unitaire SimpleTest PHP a enfin atteint la délicate version 1.0 : il est disponible au téléchargement chez votre SourceForge le plus proche. +

    +

    + Il s'agit d'un testeur unitaire PHP et aussi d'un testeur web. Il est construit pour être extensible de plusieurs manières. Les utilisateurs de JUnit seront familiers avec la plupart des interfaces. L'affichage des tests est largement modifiable tout comme le scénario de test de base. Par ailleurs les objets fantaisie peuvent être utilisés avec d'autres testeurs unitaires. +

    +

    + Les fonctionnalités à-la-JWebUnit sont plus complètes aussi. Il y a le support pour SSL, les fenêtres, les proxies, l'authentification simple et un large choix de contrôles HTML. L'idée est que les tâches fastidieuses en PHP, se connecter à un site par exemple, puissent être testées facilement. +

    +
    +
    + + + développement logiciel, + programmation php, + outils de développement logiciel, + tutorial php, + scripts php gratuits, + conseil de test, + architecture logicielle pour des tests, + exemple de code php, + junit, + test php, + outil de test unitaire, + suite de test php + + + Des articles (documentations et tutoriels) pour découvrir le développement agile en PHP avec le framework de tests unitaires SimpleTest. + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/logiciels_utilisant_simpletest.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/logiciels_utilisant_simpletest.xml new file mode 100644 index 0000000..de0f9c1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/logiciels_utilisant_simpletest.xml @@ -0,0 +1,121 @@ + + + + Logiciels utilisant SimpleTest + +
    +

    + Il y a un certain nombre de projets qui étendent les fonctionnalités + de SimpleTest : +

    +
    +
    + + SimpleTestXUL + +
    +
    + + Limb Unit + +
    +
    + + Stagehand_TestRunner + +
    +
    + + Suite Tester + +
    + +
    +
    +
    +

    + D'autres se servent de SimpleTest pour construire leurs projets : +

    +
    +
    + + Jelix - framework PHP5 + +
    +
    + Jelix est un framework pour PHP5 qui permet de développer tout type + d'application. Performant, il est conçu pour les sites à forte charge; + entièrement objet, il est fortement modulaire et extensible. + En outre ils basé sur des modèles de conception connus dont MVC, DAO.. + et prend en charge de nombreux formats de sortie : + XHTML, XUL, RSS, ATOM, RDF, ZIP, XML, PDF, etc. +
    +
    + + Copix, Framework PHP + +
    +
    + Copix est un framework pour PHP qui existe depuis maintenant plus de 6 ans. + Copix est un projet libre (LGPL) mature utilisé sur de très nombreux sites. +
    +
    + + CakePHP + +
    +
    + + Web Application Component Toolkit + +
    +
    + + Limb3 + +
    +
    + + Drupal | SimpleTest + +
    +
    + + WideImage + +
    +
    + + Aperiplus + +
    +
    + + Flux CMS + +
    +
    + + Moodle + +
    +
    +
    +
    + + + Projects extending SimpleTest + + + "Projects using SimpleTest + + + + + + + + + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/mock_objects_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/mock_objects_documentation.xml new file mode 100644 index 0000000..d4c5ee9 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/mock_objects_documentation.xml @@ -0,0 +1,729 @@ + + + + + Documentation SimpleTest : les objets fantaise + +
    +

    + Les objets fantaisie - ou "mock objects" en anglais - + ont deux rôles pendant un scénario de test : acteur et critique. +

    +

    + Le comportement d'acteur est celui de simuler + des objets difficiles à initialiser ou trop consommateurs + en temps pendant un test. + Le cas classique est celui de la connexion à une base de données. + Mettre sur pied une base de données de test au lancement + de chaque test ralentirait considérablement les tests + et en plus exigerait l'installation d'un moteur + de base de données ainsi que des données sur la machine de test. + Si nous pouvons simuler la connexion + et renvoyer des données à notre guise + alors non seulement nous gagnons en pragmatisme + sur les tests mais en sus nous pouvons nourrir + notre base avec des données falsifiées + et voir comment il répond. Nous pouvons + simuler une base de données en suspens ou + d'autres cas extrêmes sans avoir à créer + une véritable panne de base de données. + En d'autres termes nous pouvons gagner + en contrôle sur l'environnement de test. +

    +

    + Si les objets fantaisie ne se comportaient que comme + des acteurs alors on les connaîtrait sous le nom de + bouchons serveur. +

    +

    + Cependant non seulement les objets fantaisie jouent + un rôle (en fournissant à la demande les valeurs requises) + mais en plus ils sont aussi sensibles aux messages qui + leur sont envoyés (par le biais d'attentes). + En posant les paramètres attendus d'une méthode + ils agissent comme des gardiens : + un appel sur eux doit être réalisé correctement. + Si les attentes ne sont pas atteintes ils nous épargnent + l'effort de l'écriture d'une assertion de test avec + échec en réalisant cette tâche à notre place. + Dans le cas d'une connexion à une base de données + imaginaire ils peuvent tester si la requête, disons SQL, + a bien été formé par l'objet qui utilise cette connexion. + Mettez-les sur pied avec des attentes assez précises + et vous verrez que vous n'aurez presque plus d'assertion à écrire manuellement. +

    +
    +
    +

    + Comme pour la création des bouchons serveur, tout ce dont + nous avons besoin c'est d'un classe existante. + La fameuse connexion à une base de données qui ressemblerait à... +class DatabaseConnection { + function DatabaseConnection() { + } + + function query() { + } + + function selectQuery() { + } +} +]]> + Cette classe n'a pas encore besoin d'être implémentée. + Pour en créer sa version fantaisie nous devons juste + inclure la librairie d'objet fantaisie puis lancer le générateur... +require_once('simpletest/unit_tester.php'); +require_once('simpletest/mock_objects.php'); +require_once('database_connection.php'); + +Mock::generate('DatabaseConnection'); +]]> + Ceci génère une classe clone appelée MockDatabaseConnection. + Nous pouvons désormais créer des instances de + cette nouvelle classe à l'intérieur même de notre scénario de test... + +class MyTestCase extends UnitTestCase { + + function testSomething() { + $connection = &new MockDatabaseConnection($this); + } +} +]]> + Contrairement aux bouchons, le constructeur + d'une classe fantaisie a besoin d'une référence au scénario + de test pour pouvoir transmettre les succès + et les échecs pendant qu'il vérifie les attentes. + Concrètement ça veut dire que les objets fantaisie + ne peuvent être utilisés qu'au sein d'un scénario de test. + Malgré tout, cette puissance supplémentaire implique + que les bouchons ne sont que rarement utilisés + si des objets fantaisie sont disponibles. +

    +
    +
    +

    + La version fantaisie d'une classe contient + toutes les méthodes de l'originale. + De la sorte une opération comme + query()]]> + est encore possible. + Tout comme avec les bouchons, nous pouvons remplacer + la valeur nulle renvoyée par défaut... +$connection->setReturnValue('query', 37); +]]> + Désormais à chaque appel de + query()]]> + nous recevons comme résultat 37. + Tout comme avec les bouchons nous pouvons utiliser + des jokers et surcharger le paramètre joker. + Nous pouvons aussi ajouter des méthodes supplémentaires + à l'objet fantaisie lors de sa génération + et lui choisir un nom de classe qui lui soit propre... +Mock::generate('DatabaseConnection', 'MyMockDatabaseConnection', array('setOptions')); +]]> + Ici l'objet fantaisie se comportera comme + si setOptions() existait dans la classe originale. + C'est pratique si une classe a utilisé le mécanisme + overload() de PHP pour ajouter des méthodes dynamiques. + Vous pouvez créer des fantaisies spéciales pour simuler cette situation. +

    +

    + Tous les modèles disponibles avec les bouchons serveur + le sont également avec les objets fantaisie... + + Une nouvelle fois, supposons que cet itérateur + ne retourne que du texte jusqu'au moment où il atteint + son terme, quand il renvoie false. + Nous pouvons le simuler avec... + + $iterator = &new MockIterator($this); + $iterator->setReturnValue('next', false); + $iterator->setReturnValueAt(0, 'next', 'First string'); + $iterator->setReturnValueAt(1, 'next', 'Second string'); + ... + } +} +]]> + Au moment du premier appel à next() + sur l'itérateur fantaisie il renverra tout d'abord + "First string", puis ce sera au tour de + "Second string" au deuxième appel + et ensuite pour tout appel suivant false + sera renvoyé. + Ces valeurs renvoyées successivement sont prioritaires + sur la valeur constante retournée. + Cette dernière est un genre de valeur par défaut si vous voulez. +

    +

    + Reprenons aussi le conteneur d'information bouchonné + avec des pairs clef / valeur... + + Il s'agit là d'une situation classique + d'utilisation d'objets fantaisie étant donné + que la configuration peut varier grandement de machine à machine : + ça contraint fortement la fiabilité de nos tests + si nous l'utilisons directement. + Le problème est que toutes les données nous parviennent + à travers la méthode getValue() + et que nous voulons des résultats différents pour des clefs différentes. + Heureusement les objets fantaisie ont un système de filtrage... +$config = &new MockConfiguration($this); +$config->setReturnValue('getValue', 'primary', array('db_host')); +$config->setReturnValue('getValue', 'admin', array('db_user')); +$config->setReturnValue('getValue', 'secret', array('db_password')); +]]> + Le paramètre en plus est une liste d'arguments + à faire correspondre. Dans ce cas nous essayons + de faire correspondre un unique argument : + en l'occurrence la clef recherchée. + Maintenant que la méthode getValue() + est invoquée sur l'objet fantaisie... +getValue('db_user') +]]> + ...elle renverra "admin". + Elle le trouve en essayant de faire correspondre + les arguments entrants dans sa liste + d'arguments sortants les uns après les autres + jusqu'au moment où une correspondance exacte est atteinte. +

    +

    + Il y a des fois où vous souhaitez + qu'un objet spécifique soit servi par la fantaisie + plutôt qu'une copie. + De nouveau c'est identique au mécanisme des bouchons serveur... + + Dans ce cas vous pouvez placer une référence + dans la liste renvoyée par l'objet fantaisie... + +$vector = &new MockVector($this); +$vector->setReturnReference('get', $thing, array(12)); +]]> + Avec cet arrangement vous savez qu'à chaque appel + de get(12)]]> + le même $thing sera renvoyé. +

    +
    +
    +

    + Même si les bouchons serveur vous isolent + du désordre du monde réel, il ne s'agit là que + de la moitié du bénéfice potentiel. + Vous pouvez avoir une classe de test recevant + les messages ad hoc, mais est-ce que votre nouvelle classe + renvoie bien les bons ? + Le tester peut devenir cafouillis sans une librairie d'objets fantaisie. +

    +

    + Pour l'exemple, prenons une classe SessionPool + à laquelle nous allons ajouter une fonction de log. + Plutôt que de complexifier la classe originale, + nous souhaitons ajouter ce comportement avec un décorateur (GOF). + Pour l'instant le code de SessionPool ressemble à... +class SessionPool { + function SessionPool() { + ... + } + + function &findSession($cookie) { + ... + } + ... +} + +class Session { + ... +} +]]> + + Alors que pour notre code de log, nous avons... + +class Log { + function Log() { + ... + } + + function message() { + ... + } +} + +class LoggingSessionPool { + function LoggingSessionPool(&$session_pool, &$log) { + ... + } + + function &findSession($cookie) { + ... + } + ... +} +]]> + Dans tout ceci, la seule classe à tester est + LoggingSessionPool. En particulier, + nous voulons vérifier que la méthode findSession() + est appelée avec le bon identifiant de session au sein du cookie + et qu'elle renvoie bien le message "Starting session $cookie" + au loggueur. +

    +

    + Bien que nous ne testions que quelques lignes + de code de production, voici la liste des choses + à faire dans un scénario de test conventionnel : +

      +
    1. Créer un objet de log.
    2. +
    3. Indiquer le répertoire d'écriture du fichier de log.
    4. +
    5. Modifier les droits sur le répertoire pour pouvoir y écrire le fichier.
    6. +
    7. Créer un objet SessionPool.
    8. +
    9. Lancer une session, ce qui demande probablement pas mal de choses.
    10. +
    11. Invoquer findSession().
    12. +
    13. Lire le nouvel identifiant de session (en espérant qu'il existe un accesseur !).
    14. +
    15. Lever une assertion de test pour vérifier que cet identifiant correspond bien au cookie.
    16. +
    17. Lire la dernière ligne du fichier de log.
    18. +
    19. Supprimer avec une (ou plusieurs) expression rationnelle les timestamps de log en trop, etc.
    20. +
    21. Vérifier que le message de session est bien dans le texte.
    22. +
    + Pas étonnant que les développeurs détestent + écrire des tests quand ils sont aussi ingrats. + Pour rendre les choses encore pire, à chaque fois que + le format de log change ou bien que la méthode de création + des sessions change, nous devons réécrire une partie + des tests alors même qu'ils ne testent pas ces parties + du système. Nous sommes en train de préparer + le cauchemar pour les développeurs de ces autres classes. +

    +

    + A la place, voici la méthode complète pour le test + avec un peu de magie via les objets fantaisie... + + $session = &new MockSession($this); + $pool = &new MockSessionPool($this); + $pool->setReturnReference('findSession', $session); + $pool->expectOnce('findSession', array('abc')); + + $log = &new MockLog($this); + $log->expectOnce('message', array('Starting session abc')); + + $logging_pool = &new LoggingSessionPool($pool, $log); + $this->assertReference($logging_pool->findSession('abc'), $session); + $pool->tally(); + $log->tally(); + } +} +]]> + Commençons par écrire une session simulacre. + Pas la peine d'être trop pointilleux avec + celle-ci puisque la vérification de la session + désirée est effectuée ailleurs. Nous avons + juste besoin de vérifier qu'il s'agit de + la même que celle qui vient du groupe commun des sessions. +

    +

    + findSession() est un méthode fabrique + dont la simulation est décrite plus haut. + Le point de départ vient avec le premier appel + expectOnce(). Cette ligne indique + qu'à chaque fois que findSession() + est invoqué sur l'objet fantaisie, il vérifiera + les arguments entrant. S'il ne reçoit + que la chaîne "abc" en tant qu'argument + alors un succès est envoyé au testeur unitaire, + sinon c'est un échec qui est généré. + Il s'agit là de la partie qui teste si nous avons bien + la bonne session. La liste des arguments suit + une format identique à celui qui précise les valeurs renvoyées. + Vous pouvez avoir des jokers et des séquences + et l'ordre de l'évaluation restera le même. +

    +

    + Si l'appel n'est jamais effectué alors n'est généré + ni le succès, ni l'échec. Pour contourner cette limitation, + nous devons dire à l'objet fantaisie que le test est terminé : + il pourra alors décider si les attentes ont été répondues. + L'assertion du testeur unitaire de ceci est déclenchée + par l'appel tally() à la fin du test. +

    +

    + Nous utilisons le même modèle pour mettre sur pied + le loggueur fantaisie. Nous lui indiquons que message() + devrait être invoqué une fois et une fois seulement + avec l'argument "Starting session abc". + En testant les arguments d'appel, plutôt que ceux de sortie du loggueur, + nous isolons le test de tout modification dans le loggueur. +

    +

    + Nous commençons le lancement nos tests à la création + du nouveau LoggingSessionPool + et nous l'alimentons avec nos objets fantaisie juste créés. + Désormais tout est sous contrôle. Au final nous confirmons + que le $session donné au décorateur est bien + celui reçu et prions les objets fantaisie de lancer leurs + tests de comptage d'appel interne avec les appels tally(). +

    +

    + Il y a encore pas mal de code de test, mais ce code est très strict. + S'il vous semble encore terrifiant il l'est bien moins + que si nous avions essayé sans les objets fantaisie + et ce test en particulier, interactions plutôt que résultat, + est toujours plus difficile à mettre en place. + Le plus souvent vous aurez besoin de tester des situations + plus complexes sans ce niveau ni cette précision. + En outre une partie peut être remaniée avec la méthode + de scénario de test setUp(). +

    +

    + Voici la liste complète des attentes que vous pouvez + placer sur un objet fantaisie avec + SimpleTest... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    AttenteNécessite tally()
    expectArguments($method, $args)Non
    expectArgumentsAt($timing, $method, $args)Non
    expectCallCount($method, $count)Oui
    expectMaximumCallCount($method, $count)Non
    expectMinimumCallCount($method, $count)Oui
    expectNever($method)Non
    expectOnce($method, $args)Oui
    expectAtLeastOnce($method, $args)Oui
    + Où les paramètres sont... +

    +
    $method
    +
    Le nom de la méthode, sous la forme d'une chaîne, + à laquelle la condition doit être appliquée.
    +
    $args
    +
    + Les arguments sous la forme d'une liste. + Les jokers peuvent être inclus de la même manière + qu'avec setReturn(). + Cet argument est optionnel pour expectOnce() + et expectAtLeastOnce(). +
    +
    $timing
    +
    + Le seul point dans le temps pour tester + la condition. Le premier appel commence à zéro. +
    +
    $count
    +
    Le nombre d'appels attendu.
    +
    + La méthode expectMaximumCallCount() + est légèrement différente dans le sens où elle ne pourra + générer qu'un échec. Elle reste silencieuse + si la limite n'est jamais atteinte. +

    +

    + Par ailleurs si vous avez just un appel dans votre test, + vérifiez bien que vous utiliser + expectOnce.
    + Utiliser $mocked->expectAt(0, 'method', 'args); + tout seul ne sera pas pris en compte : + la vérification des arguments et le comptage total + sont pour l'instant encore indépendant. +

    +

    + Comme avec les assertions dans les scénarios de test, + toutes ces attentes peuvent accepter une surcharge de + message sous la forme d'un paramètre supplémentaire. + Par ailleurs le message d'échec original peut être inclus + dans le résultat avec "%s". +

    +
    +
    +

    + Il existe trois approches pour créer des objets + fantaisie en comprenant celle utilisée par SimpleTest. + Les coder à la main en utilisant une classe de base, + les générer dans un fichier ou les générer dynamiquement à la volée. +

    +

    + Les objets fantaisie générés avec + SimpleTest sont dynamiques. + Ils sont créés à l'exécution dans la mémoire, + grâce à eval(), plutôt qu'écrits dans un fichier. + Cette opération les rend facile à créer, + en une seule ligne, surtout par rapport à leur création + à la main dans une hiérarchie de classe parallèle. + Le problème avec ce comportement tient généralement + dans la mise en place des tests proprement dits. + Si les objets originaux changent les versions fantaisie + sur lesquels reposent les tests, une désynchronisation peut subvenir. + Cela peut aussi arriver avec l'approche en hiérarchie parallèle, + mais c'est détecté beaucoup plus vite. +

    +

    + Bien sûr, la solution est d'ajouter de véritables tests d'intégration. + Vous n'en avez pas besoin de beaucoup + et le côté pratique des objets fantaisie fait plus + que compenser la petite dose de test supplémentaire. + Vous ne pouvez pas avoir confiance dans du code qui + ne serait testé que par des objets fantaisie. +

    +

    + Si vous restez déterminé de construire des librairies + statiques d'objets fantaisie parce que vous souhaitez + émuler un comportement très spécifique, + vous pouvez y parvenir grâce au générateur de classe de SimpleTest. + Dans votre fichier librairie, par exemple + mocks/connection.php pour une connexion à une base de données, + créer un objet fantaisie et provoquer l'héritage + pour hériter pour surcharger des méthodes spéciales + ou ajouter des préréglages... + + Mock::generate('Connection', 'BasicMockConnection'); + class MockConnection extends BasicMockConnection { + function MockConnection(&$test, $wildcard = '*') { + $this->BasicMockConnection($test, $wildcard); + $this->setReturn('query', false); + } + } +?> +]]> + L'appel generate dit au générateur de classe + d'en créer une appelée BasicMockConnection + plutôt que la plus courante MockConnection. + Ensuite nous héritons à partir de celle-ci pour obtenir + notre version de MockConnection. + En interceptant de cette manière nous pouvons ajouter + un comportement, ici transformer la valeur par défaut de + query() en "false". + En utilisant le nom par défaut nous garantissons + que le générateur de classe fantaisie n'en recréera + pas une autre différente si il est invoqué ailleurs + dans les tests. Il ne créera jamais de classe + si elle existe déjà. Aussi longtemps que le fichier + ci-dessus est inclus avant alors tous les tests qui + généraient MockConnection devraient + utiliser notre version à présent. Par contre si + nous avons une erreur dans l'ordre et que la librairie + de fantaisie en crée une d'abord alors la création + de la classe échouera tout simplement. +

    +

    + Utiliser cette astuce si vous vous trouvez avec beaucoup + de comportement en commun sur les objets fantaisie + ou si vous avez de fréquents problèmes d'intégration + plus tard dans les étapes de test. +

    +
    +
    +

    + Mais au moment d'écrire ces lignes c'est le seul + à gérer les objets fantaisie, donc vous êtes bloqué avec lui ? +

    +

    + Non, pas du tout. + SimpleTest est une boîte à outils + et parmi ceux-ci on trouve les objets fantaisie + qui peuvent être utilisés indépendamment. + Supposons que vous avez votre propre testeur unitaire favori + et que tous vos tests actuels l'utilisent. + Prétendez que vous avez appelé votre tester unitaire PHPUnit + (c'est ce que tout le monde a fait) et que la classe principale + de test ressemble à... + + La seule chose que la méthode assertion() réalise, + c'est de préparer une sortie embellie alors le paramètre boolien + de l'assertion sert à déterminer s'il s'agit d'une erreur ou d'un succès. + Supposons qu'elle est utilisée de la manière suivante... +assertion('I hope this file exists', file_exists('my_file')); +]]> + Comment utiliser les objets fantaisie avec ceci ? +

    +

    + Il y a une méthode protégée sur la classe de base + des objets fantaisie : elle s'appelle _assertTrue(). + En surchargeant cette méthode nous pouvons utiliser + notre propre format d'assertion. + Nous commençons avec une sous-classe, dans my_mock.php... +SimpleMock($test, $wildcard); + } + + function _assertTrue($assertion, $message) { + $test = &$this->getTest(); + $test->assertion($message, $assertion); + } + } +?> +]]> + Maintenant une instance de MyMock + créera un objet qui parle le même langage que votre testeur. + Bien sûr le truc c'est que nous créons jamais un tel objet : + le générateur s'en chargera. Nous avons juste besoin + d'une ligne de code supplémentaire pour dire au générateur + d'utiliser vos nouveaux objets fantaisie... +SimpleMock(&$test, $wildcard); + } + + function _assertTrue($assertion, $message , &$test) { + $test->assertion($message, $assertion); + } + } + SimpleTestOptions::setMockBaseClass('MyMock'); +?> +]]> + A partir de maintenant vous avez juste à inclure + my_mock.php à la place de la version par défaut + simple_mock.php et vous pouvez introduire + des objets fantaisie dans votre suite de tests existants. +

    +
    +
    + + + Que sont les objets fantaisie ? + + + Créer des objets fantaisie. + + + L'objet fantaisie - acteur ou bouchon. + + + L'objet fantaisie - critique avec des attentes. + + + D'autres approches + y compris des librairies d'objets fantaisie. + + + Utiliser les objets fantaisie avec + d'autres testeurs unitaires. + + + + + L'article originel sur + les objets fantaisie. + + + La page du projet SimpleTest sur + SourceForge. + + + La page d'accueil de SimpleTest sur + LastCraft. + + + + + développement logiciel, + programmation en php, + outils de développement logiciel, + tutoriel php, + scripts php gratuits, + architecture, + ressources php, + mock objects, + objets fantaisie, + junit, + test php, + test unitaire, + tester en php + + +
    + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/mock_objects_tutorial.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/mock_objects_tutorial.xml new file mode 100644 index 0000000..b168d41 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/mock_objects_tutorial.xml @@ -0,0 +1,374 @@ + + + + + tutorial sur les tests unitaires en PHP - Utiliser les objets fantaisie en PHP + +
    +

    + Avant d'ajouter de nouvelles fonctionnalités + il y a du remaniement à faire. + Nous allons effectuer des tests chronométrés + et la classe TimeTestCase a définitivement + besoin d'un fichier propre. + Appelons le tests/time_test_case.php... +UnitTestCase($test_name); + } + function assertSameTime($time1, $time2, $message = '') { + if (! $message) { + $message = "Time [$time1] should match time [$time2]"; + } + $this->assertTrue( + ($time1 == $time2) || ($time1 + 1 == $time2), + $message); + } +} +?> +]]> + Nous pouvons lors utiliser require() + pour incorporer ce fichier dans le script all_tests.php. +

    +
    +
    +

    + Je ne sais pas trop quel devrait être + le format du message de log pour le test alors + pour vérifier le timestamp nous pourrions juste + faire la plus simple des choses possibles, + c'est à dire rechercher une suite de chiffres. + +require_once('../classes/clock.php'); + +class TestOfLogging extends TimeTestCase { + function TestOfLogging() { + $this->TimeTestCase('Log class test'); + } + function setUp() { + @unlink('../temp/test.log'); + } + function tearDown() { + @unlink('../temp/test.log'); + } + function getFileLine($filename, $index) { + $messages = file($filename); + return $messages[$index]; + } + function testCreatingNewFile() { + ... + } + function testAppendingToFile() { + ... + } + function testTimestamps() { + $log = new Log('../temp/test.log'); + $log->message('Test line'); + $this->assertTrue( + preg_match('/(\d+)/', $this->getFileLine('../temp/test.log', 0), $matches), + 'Found timestamp'); + $clock = new clock(); + $this->assertSameTime((integer)$matches[1], $clock->now(), 'Correct time'); + } +} +?> +]]> + Ce scénario de test crée un nouvel objet Log + et écrit un message. Nous recherchons une suite de chiffres + et nous la comparons à l'horloge présente en utilisant + notre objet Clock. Bien sûr ça ne marche pas + avant d'avoir écrit le code. +

    +

    All tests

    + Pass: log_test.php->Log class test->testappendingtofile->Expecting [/Test line 1/] in [Test line 1]
    + Pass: log_test.php->Log class test->testappendingtofile->Expecting [/Test line 2/] in [Test line 2]
    + Pass: log_test.php->Log class test->testcreatingnewfile->Created before message
    + Pass: log_test.php->Log class test->testcreatingnewfile->File created
    + Fail: log_test.php->Log class test->testtimestamps->Found timestamp
    +
    + Notice: Undefined offset: 1 in /home/marcus/projects/lastcraft/tutorial_tests/tests/log_test.php on line 44
    + Fail: log_test.php->Log class test->testtimestamps->Correct time
    + Pass: clock_test.php->Clock class test->testclockadvance->Advancement
    + Pass: clock_test.php->Clock class test->testclocktellstime->Now is the right time
    +
    3/3 test cases complete. + 6 passes and 2 fails.
    +
    + Cette suite de tests montre encore les succès + de notre modification précédente. +

    +

    + Nous pouvons faire passer les tests en ajoutant + simplement un timestamp à l'écriture dans le fichier. + Oui, bien sûr, tout ceci est assez trivial et d'habitude + je ne le testerais pas aussi fanatiquement, + mais ça va illustrer un problème plus général... + Le fichier log.php devient... + +require_once('../classes/clock.php'); + +class Log { + var $_file_path; + + function Log($file_path) { + $this->_file_path = $file_path; + } + + function message($message) { + $clock = new Clock(); + $file = fopen($this->_file_path, 'a'); + fwrite($file, "[" . $clock->now() . "] $message\n"); + fclose($file); + } +} +?> +]]> + Les tests devraient passer. +

    +

    + Par contre notre nouveau test est plein de problèmes. + Qu'est-ce qui se passe si notre format de temps change ? + Les choses vont devenir largement plus compliquées + si ça venait à se produire. + Cela veut aussi dire que n'importe quel changement + du format de notre classe horloge causera aussi + un échec dans les tests de log. + Bilan : nos tests de log sont tout mélangés + avec les test d'horloge et par la même très fragiles. + Tester à la fois des facettes de l'horloge + et d'autres du log manque de cohésion, + ou de focalisation étanche si vous préférez. + Nos problèmes sont causés en partie parce que + le résultat de l'horloge est imprévisible alors que + l'unique chose à tester est la présence + du résultat de Clock::now(). + Peu importe le contenu de l'appel de cette méthode. +

    +

    + Pouvons-nous rendre cet appel prévisible ? + Oui si nous pouvons forcer le loggueur à utiliser + une version factice de l'horloge lors du test. + Cette classe d'horloge factice devrait se comporter + exactement comme la classe Clock + à part une sortie fixée dans la méthode now(). + Et au passage, ça nous affranchirait même + de la classe TimeTestCase ! +

    +

    + Nous pourrions écrire une telle classe assez + facilement même s'il s'agit d'un boulot plutôt fastidieux. + Nous devons juste créer une autre classe + d'horloge avec la même interface sauf que + la méthode now() retourne une valeur modifiable + via une autre méthode d'initialisation. + C'est plutôt pas mal de travail pour un test plutôt mineur. +

    +

    + Sauf que ça se fait sans aucun effort. +

    +
    +
    +

    + Pour atteindre le nirvana de l'horloge instantané + pour test nous n'avons besoin que de trois lignes de code supplémentaires... + + Cette instruction inclut le code de générateur + d'objet fantaisie. Le plus simple reste de le mettre + dans le script all_tests.php étant donné + qu'il est utilisé assez fréquemment. + + C'est la ligne qui fait le travail. + Le générateur de code scanne la classe, + en extrait toutes ses méthodes, crée le code + pour générer une classe avec une interface identique, + mais en ajoutant le nom "Mock" + et ensuite eval() le nouveau code pour créer la nouvelle classe. + + Cette ligne peut être ajoutée dans n'importe + quelle méthode de test qui nous intéresserait. + Elle crée l'horloge fantaisie prête à recevoir nos instructions. +

    +

    + Notre scénario de test en est à ses premiers pas + vers un nettoyage radical... + +Mock::generate('Clock'); + +class TestOfLogging extends UnitTestCase { + function TestOfLogging() { + $this->UnitTestCase('Log class test'); + } + function setUp() { + @unlink('../temp/test.log'); + } + function tearDown() { + @unlink('../temp/test.log'); + } + function getFileLine($filename, $index) { + $messages = file($filename); + return $messages[$index]; + } + function testCreatingNewFile() { + ... + } + function testAppendingToFile() { + ... + } + function testTimestamps() { + $clock = &new MockClock($this); + $clock->setReturnValue('now', 'Timestamp'); + $log = new Log('../temp/test.log'); + $log->message('Test line', &$clock); + $this->assertWantedPattern( + '/Timestamp/', + $this->getFileLine('../temp/test.log', 0), + 'Found timestamp'); + } +} +?> +]]> + Cette méthode de test crée un objet MockClock + puis définit la valeur retourné par la méthode + now() par la chaîne "Timestamp". + A chaque fois que nous appelons $clock->now(), + elle retournera cette même chaîne. + Ça devrait être quelque chose de facilement repérable. +

    +

    + Ensuite nous créons notre loggueur et envoyons un message. + Nous incluons dans l'appel message() + l'horloge que nous souhaitons utiliser. + Ça veut dire que nous aurons à ajouter un paramètre + optionnel à la classe de log pour rendre ce test possible... +_file_path = $file_path; + } + + function message($message, $clock = false) { + if (!is_object($clock)) { + $clock = new Clock(); + } + $file = fopen($this->_file_path, 'a'); + fwrite($file, "[" . $clock->now() . "] $message\n"); + fclose($file); + } +} +]]> + Maintenant tous les tests passent et ils ne testent + que le code du loggueur. Nous pouvons à nouveau respirer. +

    +

    + Est-ce que ce paramètre supplémentaire dans la classe Log + vous gêne ? Nous n'avons changé l'interface que + pour faciliter les tests après tout. + Les interfaces ne sont-elles pas la chose la plus importante ? + Avons nous souillé notre classe avec du code de test ? +

    +

    + Peut-être, mais réfléchissez à ce qui suit. + A la prochaine occasion, + regardez une carte avec des circuits imprimés, + peut-être la carte mère de l'ordinateur que + ous regardez actuellement. Sur la plupart d'entre elles + vous trouverez un trou bizarre et vide + ou alors un point de soudure sans rien de fixé + ou même une épingle ou une prise sans aucune fonction évidente. + Peut-être certains sont là en prévision d'une expansion + ou d'une variation future, mais la plupart n'y sont que pour les tests. +

    +

    + Pensez-y. Les usines qui fabriquent ces cartes imprimées + par centaine de milliers gaspillent des matières premières + sur des pièces qui n'ajoutent rien à la fonction finale. + Si les ingénieurs matériel peuvent faire quelques sacrifices + à l'élégance, je suis sûr que nous pouvons aussi le faire. + Notre sacrifice ne gaspille pas de matériel après tout. +

    +

    + Ça vous gêne encore ? En fait moi aussi, mais pas tellement ici. + La priorité numéro 1 reste du code qui marche, + pas un prix pour minimalisme. + Si ça vous gêne vraiment alors déplacez la création + de l'horloge dans une autre méthode mère protégée. + Ensuite sous classez l'horloge pour le test + et écrasez la méthode mère avec une qui renvoie le leurre. + Vos tests sont bancals mais votre interface est intacte. +

    +

    + Une nouvelle fois je vous laisse la décision finale. +

    +
    +
    + + + Remanier les tests + dans le but de réutiliser notre nouveau test de temps. + + + Ajouter des timestamps de Log. + + + Créer une horloge fantaisie + pour rendre les tests cohésifs. + + + + + La section précédente : + tutorial de test unitaire. + + + La section suivante : + les frontières de l'application. + + + Vous aurez besoin du + framework de test SimpleTest + pour essayer ces exemples. + + + Documents sur les objets fantaisie. + + + + + développement logiciel, + programmation php, + outils de développement logiciel, + tutoriel php, + scripts php gratuits, + architecture, + ressources php, + objet fantaisie, + junit, + phpunit, + simpletest, + test php, + outil de test unitaire, + suite de test php + + +
    + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/overview.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/overview.xml new file mode 100644 index 0000000..52b4b20 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/overview.xml @@ -0,0 +1,297 @@ + + + + + + Aperçu et liste des fonctionnalités des testeurs unitaires PHP et web de SimpleTest PHP + + +
    +

    + Le coeur de SimpleTest est un framework de test construit autour de classes de scénarios de test. Celles-ci sont écrites comme des extensions des classes premières de scénarios de test, chacune élargie avec des méthodes qui contiennent le code de test effectif. Les scripts de test de haut niveau invoque la méthode run() à chaque scénario de test successivement. Chaque méthode de test est écrite pour appeler des assertions diverses que le développeur suppose être vraies, assertEqual() par exemple. Si l'assertion est correcte, alors un succès est expédié au rapporteur observant le test, mais toute erreur déclenche une alerte et une description de la dissension. +

    +

    + Un scénario de test ressemble à... +MyTestCase extends UnitTestCase { + + function testLog() { + $log = &new Log('my.log'); + $log->message('Hello'); + $this->assertTrue(file_exists('my.log')); + } +} +]]> +

    +

    + Ces outils sont conçus pour le développeur. Les tests sont écrits en PHP directement, plus ou moins simultanément avec la construction de l'application elle-même. L'avantage d'utiliser PHP lui-même comme langage de test est qu'il n'y a pas de nouveau langage à apprendre, les tests peuvent commencer directement et le développeur peut tester n'importe quelle partie du code. Plus simplement, toutes les parties qui peuvent être accédées par le code de l'application peuvent aussi être accédées par le code de test si ils sont tous les deux dans le même langage. +

    +

    + Le type de scénario de test le plus simple est le UnitTestCase. Cette classe de scénario de test inclut les tests standards pour l'égalité, les références et l'appariement de motifs (via les expressions rationnelles). Ceux-ci testent ce que vous seriez en droit d'attendre du résultat d'une fonction ou d'une méthode. Il s'agit du type de test le plus commun pendant le quotidien du développeur, peut-être 95% des scénarios de test. +

    +

    + La tâche ultime d'une application web n'est cependant pas de produire une sortie correcte à partir de méthodes ou d'objets, mais plutôt de produire des pages web. La classe WebTestCase teste des pages web. Elle simule un navigateur web demandant une page, de façon exhaustive : cookies, proxies, connexions sécurisées, authentification, formulaires, cadres et la plupart des éléments de navigation. Avec ce type de scénario de test, le développeur peut garantir que telle ou telle information est présente dans la page et que les formulaires ainsi que les sessions sont gérés comme il faut. +

    +

    + Un scénario de test web ressemble à... +MySiteTest extends WebTestCase { + + function testHomePage() { + $this->get('http://www.my-site.com/index.php'); + $this->assertTitle('My Home Page'); + $this->clickLink('Contact'); + $this->assertTitle('Contact me'); + $this->assertWantedPattern('/Email me at/'); + } +} +]]> +

    +
    +
    +

    + Ci-dessous vous trouverez un canevas assez brut des fonctionnalités à aujourd'hui et pour demain, sans oublier leur date approximative de publication. J'ai bien peur qu'il soit modifiable sans pré-avis étant donné que les jalons dépendent beaucoup sur le temps disponible. Les trucs en vert ont été codés, mais pas forcément déjà rendus public. Si vous avez une besoin pressant pour une fonctionnalité verte mais pas encore publique alors vous devriez retirer le code directement sur le CVS chez SourceFourge. Une fonctionnalitée publiée est indiqué par "Fini". + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FonctionnalitéDescriptionPublication
    Scénariot de test unitaireLes classes de test et assertions de baseFini
    Affichage HTMLL'affichage le plus simple possibleFini
    Autochargement des scénarios de testLire un fichier avec des scénarios de test et les charger dans un groupe de tests automatiquementFini
    Générateur de code d'objets fantaisieDes objets capable de simuler d'autres objets, supprimant les dépendances dans les testsFini
    Bouchons serveurDes objets fantaisie sans résultat attendu à utiliser à l'extérieur des scénarios de test, pour le prototypage par exemple.Fini
    Intégration d'autres testeurs unitaires + La capacité de lire et simuler d'autres scénarios de test en provenance de PHPUnit et de PEAR::Phpunit.Fini
    Scénario de test webAppariement basique de motifs dans une page téléchargée.Fini
    Analyse de page HTMLPermet de suivre les liens et de trouver la balise de titreFini
    Simulacre partielSimuler des parties d'une classe pour tester moins qu'une classe ou dans des cas complexes.Fini
    Gestion des cookies WebGestion correcte des cookies au téléchargement d'une page.Fini
    Suivi des redirectionsLe téléchargement d'une page suit automatiquement une redirection 300.Fini
    Analyse d'un formulaireLa capacité de valider un formulaire simple et d'en lire les valeurs par défaut.Fini
    Interface en ligne de commandeAffiche le résultat des tests sans navigateur web.Fini
    Mise à nu des attentes d'une classePeut créer des tests précis avec des simulacres ainsi que des scénarios de test.Fini
    Sortie et analyse XMLPermet de tester sur plusieurs hôtes et d'intégrer des extensions d'acceptation de test.Fini
    Scénario de test en ligne de commandePermet de tester des outils ou scripts en ligne de commande et de manier des fichiers.Fini
    Compatibilité avec PHP DocumentorGénération automatique et complète de la documentation au niveau des classes.Fini
    Interface navigateurMise à nu des niveaux bas de l'interface du navigateur web pour des scénarios de test plus précis.Fini
    Authentification HTTPTéléchargement des pages web protégées avec une authentification basique seulement.Fini
    Boutons de navigation d'un navigateurArrière, avant et recommencerFini
    Support de SSLPeut se connecter à des pages de type https.Fini
    Support de proxyPeut se connecter via des proxys communsFini
    Support des cadresGère les cadres dans les scénarios de test web.Fini
    Test de l'upload de fichierPeut simuler la balise input de type file1.0.1
    Amélioration sur la machinerie des rapportsRetouche sur la transmission des messages pour une meilleur coopération avec les IDEs1.1
    Amélioration de l'affichage des testsUne meilleure interface graphique web, avec un arbre des scénarios de test.1.1
    LocalisationAbstraction des messages et génration du code à partir de fichiers XML.1.1
    Simulation d'interfacePeut générer des objets fantaisie tant vers des interfaces que vers des classes.2.0
    Test sur es exceptionsDans le même esprit que sur les tests des erreurs PHP.2.0
    Rercherche d'éléments avec XPathPeut utiliser Tidy HTML pour un appariement plus rapide et plus souple.2.0
    + La migration vers PHP5 commencera juste après la série des 1.0, à partir de là PHP4 ne sera plus supporté. SimpleTest est actuellement compatible avec PHP5 mais n'utilisera aucune des nouvelles fonctionnalités avant la version 2. +

    +
    +
    +

    + Le processus est au moins aussi important que les outils. Le type de procédure que fait un usage le plus intensif des outils de test pour développeur est bien sûr l'Extreme Programming. Il s'agit là d'une des méthodes agiles qui combinent plusieurs pratiques pour "lisser la courbe de coût" du développement logiciel. La plus extrème reste le développement piloté par les tests, où vous devez adhérer à la règle du pas de code avant d'avoir un test. Si vous êtes plutôt du genre planninficateur ou que vous estimez que l'expérience compte plus que l'évolution, vous préférerez peut-être l'approche RUP. Je ne l'ai pas testé mais je peux voir où vous aurez besoin d'outils de test (cf. illustration 9). +

    +

    + La plupart des testeurs unitaires sont dans une certaine mesure un clone de JUnit, au moins dans l'interface. Il y a énormément d'information sur le site de JUnit, à commencer par la FAQ quie contient pas mal de conseils généraux sur les tests. Une fois mordu par le bogue vous apprécierez sûrement la phrase infecté par les tests trouvée par Eric Gamma. Si vous êtes encore en train de tergiverser sur un testeur unitaire, sachez que les choix principaux sont PHPUnit et Pear PHP::PHPUnit. De nombreuses fonctionnalités de SimpleTest leurs font défaut, mais la version PEAR a d'ores et déjà été mise à jour pour PHP5. Elle est aussi recommandée si vous portez des scénarios de test existant depuis JUnit. +

    +

    + Les développeurs de bibliothèque n'ont pas l'air de livrer très souvent des tests avec leur code : c'est bien dommage. Le code d'une bibliothèque qui inclut des tests peut être remanié avec plus de sécurité et le code de test sert de documentation additionnelle dans un format assez standard. Ceci peut épargner la pêche aux indices dans le code source lorsque qu'un problème survient, en particulier lors de la mise à jour d'une telle bibliothèque. Parmi les bibliothèques utilisant SimpleTest comme testeur unitaire on retrouve WACT et PEAR::XML_HTMLSax. +

    +

    + Au jour d'aujourd'hui il manque tristement beaucoup de matière sur les objets fantaisie : dommage, surtout que tester unitairement sans eux représente pas mal de travail en plus. L'article original sur les objets fantaisie est très orienté Java, mais reste intéressant à lire. Etant donné qu'il s'agit d'une nouvelle technologie il y a beaucoup de discussions et de débats sur comment les utiliser, souvent sur des wikis comme Extreme Tuesday ou www.mockobjects.comou the original C2 Wiki. Injecter des objets fantaisie dans une classe est un des champs principaux du débat : cet article chez IBM en est un bon point de départ. +

    +

    + Il y a énormement d'outils de test web mais la plupart sont écrits en Java. De plus les tutoriels et autres conseils sont plutôt rares. Votre seul espoir est de regarder directement la documentation pour HTTPUnit, HTMLUnit ou JWebUnit et d'espérer y trouver pour des indices. Il y a aussi des frameworks basés sur XML, mais de nouveau la plupart ont besoin de Java pour tourner. +

    +
    +
    + + + Résumé rapide de l'outil SimpleTest pour PHP. + + + La liste des fonctionnalites, à la fois présentes et à venir. + + + Il y a beaucoup de ressources sur les tests unitaires sur le web. + + + + + Documentation pour SimpleTest. + + + Comment écrire des scénarios de test en PHP est un tutoriel plutôt avancé. + + + L'API de SimpleTest par phpdoc. + + + + + outils de développement logiciel, + programmation php, + outils pour l'extreme programming, + liens pour des outils de test, + ressources pour test en php, + objets fantaise, + junit, + jwebunit, + htmlunit, + itc, + liens pour tests en php, + conseil et documentation pour test unitaire, + extreme programming en php + + + + + + Marcus Baker + + Développeur principal{@link mailto:marcus@lastcraft.com marcus@lastcraft.com} + + + + Harry Fuecks + + Packageur{@link mailto:harryf@users.sourceforge.net harryf@users.sourceforge.net} + + + + Jason Sweat + + Documentation{@link mailto:jsweat_php@yahoo.com jsweat_php@yahoo.com} + + + + Perrick Penet + + Traduction{@link mailto:perrick@onpk.net perrick@onpk.net} + + + + +
    + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/partial_mocks_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/partial_mocks_documentation.xml new file mode 100644 index 0000000..7d15486 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/partial_mocks_documentation.xml @@ -0,0 +1,419 @@ + + + + + Documentation SimpleTest : les objets fantaisie partiels + + +

    + Un objet fantaisie partiel n'est ni plus ni moins + qu'un modèle de conception pour soulager un problème spécifique + du test avec des objets fantaisie, celui de placer + des objets fantaisie dans des coins serrés. + Il s'agit d'un outil assez limité et peut-être même + une idée pas si bonne que ça. Elle est incluse dans SimpleTest + pour la simple raison que je l'ai trouvée utile + à plus d'une occasion et qu'elle m'a épargnée + pas mal de travail dans ces moments-là. +

    +
    +
    +

    + Quand un objet en utilise un autre il est très simple + d'y faire circuler une version fantaisie déjà prête + avec ses attentes. Les choses deviennent un peu plus délicates + si un objet en crée un autre et que le créateur est celui + que l'on souhaite tester. Cela revient à dire que l'objet + créé devrait être une fantaisie, mais nous pouvons + difficilement dire à notre classe sous test de créer + un objet fantaisie plutôt qu'un "vrai" objet. + La classe testée ne sait même pas qu'elle travaille dans un environnement de test. +

    +

    + Par exemple, supposons que nous sommes en train + de construire un client telnet et qu'il a besoin + de créer une socket réseau pour envoyer ses messages. + La méthode de connexion pourrait ressemble à quelque chose comme... +read( ... ); + ... + } +} +?> +]]> + Nous voudrions vraiment avoir une version fantaisie + de l'objet socket, que pouvons nous faire ? +

    +

    + La première solution est de passer la socket en + tant que paramètre, ce qui force la création + au niveau inférieur. Charger le client de cette tâche + est effectivement une bonne approche si c'est possible + et devrait conduire à un remaniement -- de la création + à partir de l'action. En fait, c'est là une des manières + avec lesquels tester en s'appuyant sur des objets fantaisie + vous force à coder des solutions plus resserrées sur leur objectif. + Ils améliorent votre programmation. +

    +

    + Voici ce que ça devrait être... +function &connect(&$socket, $username, $password) { + $socket->read( ... ); + ... + } +} +?> +]]> + Sous-entendu, votre code de test est typique d'un cas + de test avec un objet fantaisie. + + $socket = &new MockSocket($this); + ... + $telnet = &new Telnet(); + $telnet->connect($socket, 'Me', 'Secret'); + ... + } +} +]]> + C'est assez évident que vous ne pouvez descendre que d'un niveau. + Vous ne voudriez pas que votre application de haut niveau + crée tous les fichiers de bas niveau, sockets et autres connexions + à la base de données dont elle aurait besoin. + Elle ne connaîtrait pas les paramètres du constructeur de toute façon. +

    +

    + La solution suivante est de passer l'objet créé sous la forme + d'un paramètre optionnel... + + function &connect($ip, $port, $username, $password, $socket = false) { + if (!$socket) { + $socket = &new Socket($ip, $port); + } + $socket->read( ... ); + ... + return $socket; + } +} +?> +]]> + Pour une solution rapide, c'est généralement suffisant. + Ensuite le test est très similaire : comme si le paramètre + était transmis formellement... + + $socket = &new MockSocket($this); + ... + $telnet = &new Telnet(); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret', &$socket); + ... + } +} +]]> + Le problème de cette approche tient dans son manque de netteté. + Il y a du code de test dans la classe principale et aussi + des paramètres transmis dans le scénario de test + qui ne sont jamais utilisés. Il s'agit là d'une approche + rapide et sale, mais qui ne reste pas moins efficace + dans la plupart des situations. +

    +

    + Une autre solution encore est de laisser un objet fabrique + s'occuper de la création... + + function Telnet(&$network) { + $this->_network = &$network; + } + ... + function &connect($ip, $port, $username, $password) { + $socket = &$this->_network->createSocket($ip, $port); + $socket->read( ... ); + ... + return $socket; + } +} +?> +]]> + Il s'agit là probablement de la réponse la plus travaillée + étant donné que la création est maintenant située + dans une petite classe spécialisée. La fabrique réseau + peut être testée séparément et utilisée en tant que fantaisie + quand nous testons la classe telnet... + + $socket = &new MockSocket($this); + ... + $network = &new MockNetwork($this); + $network->setReturnReference('createSocket', $socket); + $telnet = &new Telnet($network); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ... + } +} +]]> + Le problème reste que nous ajoutons beaucoup de classes + à la bibliothèque. Et aussi que nous utilisons beaucoup + de fabriques ce qui rend notre code un peu moins intuitif. + La solution la plus flexible, mais aussi la plus complexe. +

    +

    + Peut-on trouver un juste milieu ? +

    +
    +
    +

    + Il existe une technique pour palier à ce problème + sans créer de nouvelle classe dans l'application; + par contre elle induit la création d'une sous-classe au moment du test. + Premièrement nous déplaçons la création de la socket dans sa propre méthode... + + $socket = &$this->_createSocket($ip, $port); + $socket->read( ... ); + ... + } + + function &_createSocket($ip, $port) { + return new Socket($ip, $port); + } +} +?> +]]> + Il s'agit là de la seule modification dans le code de l'application. +

    +

    + Pour le scénario de test, nous devons créer + une sous-classe de manière à intercepter la création de la socket... +class TelnetTestVersion extends Telnet { + var $_mock; + + function TelnetTestVersion(&$mock) { + $this->_mock = &$mock; + $this->Telnet(); + } + + function &_createSocket() { + return $this->_mock; + } +} +]]> + Ici j'ai déplacé la fantaisie dans le constructeur, + mais un setter aurait fonctionné tout aussi bien. + Notez bien que la fantaisie est placée dans une variable + d'objet avant que le constructeur ne soit attaché. + C'est nécessaire dans le cas où le constructeur appelle + connect(). + Autrement il pourrait donner un valeur nulle à partir de + _createSocket(). +

    +

    + Après la réalisation de tout ce travail supplémentaire + le scénario de test est assez simple. + Nous avons juste besoin de tester notre nouvelle classe à la place... + + $socket = &new MockSocket($this); + ... + $telnet = &new TelnetTestVersion($socket); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ... + } +} +]]> + Cette nouvelle classe est très simple bien sûr. + Elle ne fait qu'initier une valeur renvoyée, à la manière + d'une fantaisie. Ce serait pas mal non plus si elle pouvait + vérifier les paramètres entrants. + Exactement comme un objet fantaisie. + Il se pourrait bien que nous ayons à réaliser cette astuce régulièrement : + serait-il possible d'automatiser la création de cette sous-classe ? +

    +
    +
    +

    + Bien sûr la réponse est "oui" + ou alors j'aurais arrêté d'écrire depuis quelques temps déjà ! + Le test précédent a représenté beaucoup de travail, + mais nous pouvons générer la sous-classe en utilisant + une approche à celle des objets fantaisie. +

    +

    + Voici donc une version avec objet fantaisie partiel du test... +Mock::generatePartial( + 'Telnet', + 'TelnetTestVersion', + array('_createSocket')); + +class TelnetTest extends UnitTestCase { + ... + function testConnection() { + $socket = &new MockSocket($this); + ... + $telnet = &new TelnetTestVersion($this); + $telnet->setReturnReference('_createSocket', $socket); + $telnet->Telnet(); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ... + } +} +]]> + La fantaisie partielle est une sous-classe de l'original + dont on aurait "remplacé" les méthodes sélectionnées + avec des versions de test. L'appel à generatePartial() + nécessite trois paramètres : la classe à sous classer, + le nom de la nouvelle classe et une liste des méthodes à simuler. +

    +

    + Instancier les objets qui en résultent est plutôt délicat. + L'unique paramètre du constructeur d'un objet fantaisie partiel + est la référence du testeur unitaire. + Comme avec les objets fantaisie classiques c'est nécessaire + pour l'envoi des résultats de test en réponse à la vérification des attentes. +

    +

    + Une nouvelle fois le constructeur original n'est pas lancé. + Indispensable dans le cas où le constructeur aurait besoin + des méthodes fantaisie : elles n'ont pas encore été initiées ! + Nous initions les valeurs retournées à cet instant et + ensuite lançons le constructeur avec ses paramètres normaux. + Cette construction en trois étapes de "new", + suivie par la mise en place des méthodes et ensuite + par la lancement du constructeur proprement dit est + ce qui distingue le code d'un objet fantaisie partiel. +

    +

    + A part pour leur construction, toutes ces méthodes + fantaisie ont les mêmes fonctionnalités que dans + le cas des objets fantaisie et toutes les méthodes + non fantaisie se comportent comme avant. + Nous pouvons mettre en place des attentes très facilement... +setReturnReference('_createSocket', $socket); + $telnet->expectOnce('_createSocket', array('127.0.0.1', 21)); + $telnet->Telnet(); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ... + $telnet->tally(); + } +} +]]> +

    +
    +
    +

    + Les méthodes issues d'un objet fantaisie n'ont pas + besoin d'être des méthodes fabrique, Il peut s'agir + de n'importe quelle sorte de méthode. + Ainsi les objets fantaisie partiels nous permettent + de prendre le contrôle de n'importe quelle partie d'une classe, + le constructeur excepté. Nous pourrions même aller jusqu'à + créer des fantaisies sur toutes les méthodes à part celle + que nous voulons effectivement tester. +

    +

    + Cette situation est assez hypothétique, étant donné + que je ne l'ai jamais essayée. Je suis ouvert à cette possibilité, + mais je crains qu'en forçant la granularité d'un objet + on n'obtienne pas forcément un code de meilleur qualité. + Personnellement j'utilise les objets fantaisie partiels + comme moyen de passer outre la création ou alors + de temps en temps pour tester le modèle de conception TemplateMethod. +

    +

    + Pour choisir le mécanisme à utiliser, on en revient + toujours aux standards de code de votre projet. +

    +
    +
    + + + Le problème de l'injection d'un objet fantaisie. + + + Déplacer la création vers une méthode fabrique protégée. + + + L'objet fantaisie partiel génère une sous-classe. + + + Les objets fantaisie partiels testent moins qu'une classe. + + + + + La page du projet SimpleTest sur + SourceForge. + + + L'API complète pour SimpleTest + à partir de PHPDoc. + + + La méthode fabrique protégée est décrite dans + + cet article d'IBM. Il s'agit de l'unique papier + formel que j'ai vu sur ce problème. + + + + + développement logiciel en php, + dévelopement d'un scénario de test en php, + programmation php avec base de données, + outils de développement logiciel, + tutoriel avancé en php, + scripts à la manière de phpunit, + architecture, + ressources php, + objets fantaisie, + junit, + framework de test en php, + test unitaire, + test en php + + +
    + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/reporter_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/reporter_documentation.xml new file mode 100644 index 0000000..e91adf1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/reporter_documentation.xml @@ -0,0 +1,488 @@ + + + + + Documentation SimpleTest : le rapporteur de test + + +

    + SimpleTest suit plutôt plus que moins le modèle MVC (Modèle-Vue-Contrôleur). + Les classes "reporter" sont les vues et les modèles + sont vos scénarios de test et leur hiérarchie. + Le contrôleur est le plus souvent masqué à l'utilisateur + de SimpleTest à moins de vouloir changer la façon + dont les tests sont effectivement exécutés, + auquel cas il est possible de surcharger les objets + "runner" (ceux de l'exécuteur) depuis l'intérieur + d'un scénario de test. Comme d'habitude avec MVC, + le contrôleur est plutôt indéfini et il existe d'autres endroits + pour contrôler l'exécution des tests. +

    +
    +
    +

    + L'affichage par défaut est minimal à l'extrême. + Il renvoie le succès ou l'échec avec les barres conventionnelles + - rouge et verte - et affichent une trace d'arborescence + des groupes de test pour chaque assertion erronée. Voici un tel échec... +

    +

    File test

    + Fail: createnewfile->True assertion failed.
    +
    1/1 test cases complete. + 0 passes, 1 fails and 0 exceptions.
    +
    + Alors qu'ici tous les tests passent... +
    +

    File test

    +
    1/1 test cases complete. + 1 passes, 0 fails and 0 exceptions.
    +
    + La bonne nouvelle, c'est qu'il existe pas mal de points + dans la hiérarchie de l'affichage pour créer des sous-classes. +

    +

    + Pour l'affichage basé sur des pages web, + il y a la classe HtmlReporter avec la signature suivante... + + Voici ce que certaines de ces méthodes veulent dire. + Premièrement les méthodes d'affichage que vous voudrez probablement surcharger... +

      +
    • + HtmlReporter(string $encoding)
      + est le constructeur. Notez que le test unitaire initie + le lien à l'affichage plutôt que l'opposé. + L'affichage est principalement un receveur passif + des évènements de tests. Cela permet d'adapter + facilement l'affichage pour d'autres systèmes + en dehors des tests unitaires, tel le suivi + de la charge de serveurs. + L'"encoding" est le type d'encodage + que vous souhaitez utiliser pour l'affichage du test. + Pour pouvoir effectuer un rendu correct de la sortie + de débogage quand on utilise le testeur web, + il doit correspondre à l'encodage du site testé. + Les chaînes de caractères disponibles + sont indiquées dans la fonction PHP + html_entities(). +
    • +
    • + void paintHeader(string $test_name)
      + est appelé une fois, au début du test quand l'évènement + de démarrage survient. Le premier évènement de démarrage + est souvent délivré par le groupe de tests du niveau + le plus haut et donc c'est de là que le + $test_name arrive. + Il peint les titres de la page, CSS, la balise "body", etc. + Il ne renvoie rien du tout (void). +
    • +
    • + void paintFooter(string $test_name)
      + est appelé à la toute fin du test pour fermer + les balises ouvertes par l'entête de la page. + Par défaut il affiche aussi la barre rouge ou verte + et le décompte final des résultats. + En fait la fin des tests arrive quand l'évènement + de fin de test arrive avec le même nom + que celui qui l'a initié au même niveau. + Le nid des tests en quelque sorte. + Fermer le dernier test finit l'affichage. +
    • +
    • + void paintMethodStart(string $test_name)
      + est appelé au début de chaque méthode de test. + Normalement le nom vient de celui de la méthode. + Les autres évènements de départ de test + se comportent de la même manière sauf que + celui du groupe de tests indique au rapporteur + le nombre de scénarios de test qu'il contient. + De la sorte le rapporteur peut afficher une barre + de progrès au fur et à mesure que l'exécuteur + passe en revue les scénarios de test. +
    • +
    • + void paintMethodEnd(string $test_name)
      + clôt le test lancé avec le même nom. +
    • +
    • + void paintFail(string $message)
      + peint un échec. Par défaut il ne fait qu'afficher + le mot "fail", une trace d'arborescence + affichant la position du test en cours + et le message transmis par l'assertion. +
    • +
    • + void paintPass(string $message)
      + ne fait rien, par défaut. +
    • +
    • + string getCss()
      + renvoie les styles CSS sous la forme d'une chaîne + à l'attention de la méthode d'entêtes d'une page. + Des styles additionnels peuvent être ajoutés ici + si vous ne surchargez pas les entêtes de la page. + Vous ne voudrez pas utiliser cette méthode dans + des entêtes d'une page surchargée si vous souhaitez + inclure le feuille de style CSS d'origine. +
    • +
    + Il y a aussi des accesseurs pour aller chercher l'information + sur l'état courant de la suite de test. Vous les utiliserez + pour enrichir l'affichage... +
      +
    • + array getTestList()
      + est la première méthode très commode pour les sous-classes. + Elle liste l'arborescence courante des tests + sous la forme d'une liste de noms de tests. + Le premier test -- celui le plus proche du coeur -- + sera le premier dans la liste et la méthode de test + en cours sera la dernière. +
    • +
    • + integer getPassCount()
      + renvoie le nombre de succès atteint. Il est nécessaire + pour l'affichage à la fin. +
    • +
    • + integer getFailCount()
      + renvoie de la même manière le nombre d'échecs. +
    • +
    • + integer getExceptionCount()
      + renvoie quant à lui le nombre d'erreurs. +
    • +
    • + integer getTestCaseCount()
      + est le nombre total de scénarios lors de l'exécution des tests. + Il comprend aussi les tests groupés. +
    • +
    • + integer getTestCaseProgress()
      + est le nombre de scénarios réalisés jusqu'à présent. +
    • +
    + Une modification simple : demander à l'HtmlReporter d'afficher + aussi bien les succès que les échecs et les erreurs... + +class ShowPasses extends HtmlReporter { + + function paintPass($message) { + parent::paintPass($message); + print "&Pass: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode("->", $breadcrumb); + print "->$message
    \n"; + } + + protected function getCss() { + return parent::getCss() . ' .pass { color: green; }'; + } +} +]]>
    +

    +

    + Une méthode qui a beaucoup fait jaser reste la méthode makeDry(). + Si vous lancez cette méthode, sans paramètre, + sur le rapporteur avant que la suite de test + ne soit exécutée alors aucune méthode de test + ne sera appelée. Vous continuerez à avoir + les évènements entrants et sortants des méthodes + et scénarios de test, mais aucun succès ni échec ou erreur, + parce que le code de test ne sera pas exécuté. +

    +

    + La raison ? Pour permettre un affichage complexe + d'une IHM (ou GUI) qui permettrait la sélection + de scénarios de test individuels. + Afin de construire une liste de tests possibles, + ils ont besoin d'un rapport sur la structure du test + pour l'affichage, par exemple, d'une vue en arbre + de la suite de test. Avec un rapporteur lancé + sur une exécution sèche qui ne renverrait + que les évènements d'affichage, cela devient + facilement réalisable. +

    +
    +
    +

    + Plutôt que de modifier l'affichage existant, + vous voudrez peut-être produire une présentation HTML + complètement différente, ou même générer une version texte ou XML. + Plutôt que de surcharger chaque méthode dans + HtmlReporter nous pouvons nous rendre + une étape plus haut dans la hiérarchie de classe vers + SimpleReporter dans le fichier source simple_test.php. +

    +

    + Un affichage sans rien, un canevas vierge + pour votre propre création, serait... + +require_once('simpletest/simple_test.php'); + +class MyDisplay extends SimpleReporter { + + function paintHeader($test_name) { + } + + function paintFooter($test_name) { + } + + function paintStart($test_name, $size) { + parent::paintStart($test_name, $size); + } + + function paintEnd($test_name, $size) { + parent::paintEnd($test_name, $size); + } + + function paintPass($message) { + parent::paintPass($message); + } + + function paintFail($message) { + parent::paintFail($message); + } +} +]]> + Aucune sortie ne viendrait de cette classe jusqu'à un ajout de votre part. +

    +
    +
    +

    + SimpleTest est aussi livré avec un rapporteur + en ligne de commande, minime lui aussi. + L'interface imite celle de JUnit, + sauf qu'elle envoie les messages d'erreur au fur + et à mesure de leur arrivée. + Pour utiliser le rapporteur en ligne de commande, + il suffit de l'intervertir avec celui de la version HTML... +addTestFile('tests/file_test.php'); +$test->run(new TextReporter()); +?> +]]> + Et ensuite d'invoquer la suite de test à partir d'une ligne de commande... +

    +php file_test.php
    +
    + Bien sûr vous aurez besoin d'installer PHP + en ligne de commande. Une suite de test qui + passerait toutes ses assertions ressemble à... +
    +File test
    +OK
    +Test cases run: 1/1, Failures: 0, Exceptions: 0
    +
    + Un échec déclenche un affichage comme... +
    +File test
    +1) True assertion failed.
    +    in createnewfile
    +FAILURES!!!
    +Test cases run: 1/1, Failures: 1, Exceptions: 0
    +
    +

    +

    + Une des principales raisons pour utiliser + une suite de test en ligne de commande tient + dans l'utilisation possible du testeur avec + un processus automatisé. Pour fonctionner comme + il faut dans des scripts shell le script de test + devrait renvoyer un code de sortie non-nul suite à un échec. + Si une suite de test échoue la valeur false + est renvoyée par la méthode SimpleTest::run(). + Nous pouvons utiliser ce résultat pour terminer le script + avec la bonne valeur renvoyée... +addTestFile('tests/file_test.php'); +exit ($test->run(new TextReporter()) ? 0 : 1); +?> +]]> + Bien sûr l'objectif n'est pas de créer deux scripts de test, + l'un en ligne de commande et l'autre pour un navigateur web, + pour chaque suite de test. + Le rapporteur en ligne de commande inclut + une méthode pour déterminer l'environnement d'exécution... +addTestFile('tests/file_test.php'); +if (TextReporter::inCli()) { + exit ($test->run(new TextReporter()) ? 0 : 1); +} +$test->run(new HtmlReporter()); +?> +]]> + Il s'agit là de la forme utilisée par SimpleTest lui-même. +

    +
    +
    +

    + SimpleTest est livré avec une classe XmlReporter + utilisée pour de la communication interne. + Lors de son exécution, le résultat ressemble à... +

    
    +
    +  
    +    Remote tests
    +    
    +      Visual test with 48 passes, 48 fails and 4 exceptions
    +      
    +        testofunittestcaseoutput
    +        
    +          testofresults
    +          This assertion passed
    +          This assertion failed
    +        
    +        
    +          ...
    +        
    +      
    +    
    +  
    +
    +]]>
    + Vous pouvez utiliser ce format avec le parseur + fourni dans SimpleTest lui-même. + Il s'agit de SimpleTestXmlParser + et se trouve xml.php à l'intérieur du paquet SimpleTest... +parse($test_output); +?> +]]> + $test_output devrait être au format XML, + à partir du rapporteur XML, et pourrait venir + d'une exécution en ligne de commande d'un scénario de test. + Le parseur envoie des évènements au rapporteur exactement + comme tout autre exécution de test. + Il y a des occasions bizarres dans lesquelles c'est en fait très utile. +

    +

    + Un problème des très grandes suites de test, + c'est qu'elles peuvent venir à bout de la limite de mémoire + par défaut d'un process PHP - 8Mb. + En plaçant la sortie des groupes de test dans du XML + et leur exécution dans des process différents, + le résultat peut être parsé à nouveau pour agréger + les résultats avec moins d'impact sur le test au premier niveau. +

    +

    + Parce que la sortie XML peut venir de n'importe où, + ça ouvre des possibilités d'agrégation d'exécutions de test + depuis des serveur distants. + Un scénario de test pour le réaliser existe déjà + à l'intérieur du framework SimpleTest, mais il est encore expérimental... + +require_once('../remote.php'); +require_once('../reporter.php'); + +$test_url = ...; +$dry_url = ...; + +$test = &new GroupTest('Remote tests'); +$test->addTestCase(new RemoteTestCase($test_url, $dry_url)); +$test->run(new HtmlReporter()); +?> +]]> + RemoteTestCase prend la localisation réelle + du lanceur de test, tout simplement un page web au format XML. + Il prend aussi l'URL d'un rapporteur initié + pour effectuer une exécution sèche. + Cette technique est employée pour que les progrès + soient correctement rapportés vers le haut. + RemoteTestCase peut être ajouté à + une suite de test comme n'importe quel autre groupe de tests. +

    +
    +
    + + + Afficher les résultats en HTML + + + Afficher et rapporter les résultats + dans d'autres formats + + + Utilisé SimpleTest depuis la ligne de commande + + + Utiliser XML pour des tests distants + + + + + La page du projet SimpleTest sur + SourceForge. + + + La page de téléchargement de SimpleTest sur + LastCraft. + + + L'API pour développeur de SimpleTest + donne tous les détails sur les classes et les assertions disponibles. + + + + + test unitaire en php, + documentation, + marcus baker, + perrick penet + test simple, + simpletest, + test distant, + tests xml, + test automatisé + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/simple_test.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/simple_test.xml new file mode 100644 index 0000000..68d5629 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/simple_test.xml @@ -0,0 +1,556 @@ + + + + + + Prise en main rapide de SimpleTest pour PHP - + Tests unitaire et objets fantaisie pour PHP + + + +

    + Le présent article présuppose que vous soyez familier avec + le concept de tests unitaires ainsi que celui de développement + web avec le langage PHP. Il s'agit d'un guide pour le nouvel + et impatient utilisateur de + SimpleTest. + Pour une documentation plus complète, particulièrement si + vous découvrez les tests unitaires, consultez la + documentation + en cours, et pour des exemples de scénarios de test, + consultez le + tutorial + sur les tests unitaires. +

    +
    +
    +

    + Parmi les outils de test pour logiciel, le testeur unitaire + est le plus proche du développeur. Dans un contexte de + développement agile, le code de test se place juste à côté + du code source étant donné que tous les deux sont écrits + simultanément. Dans ce contexte, SimpleTest aspire à être + une solution complète de test pour un développeur PHP et + s'appelle "Simple" parce qu'elle devrait être simple à + utiliser et à étendre. Ce nom n'était pas vraiment un bon + choix. Non seulement cette solution inclut toutes les + fonctions classiques qu'on est en droit d'attendre de la + part des portages de JUnit et des PHPUnit, + mais elle inclut aussi les objets fantaisie ou + "mock objects". +

    +

    + Ce qui rend cet outil immédiatement utile pour un développeur PHP, + c'est son navigateur web interne. + Il permet des tests qui parcourent des sites web, remplissent + des formulaires et testent le contenu des pages. + Etre capable d'écrire ces tests en PHP veut dire qu'il devient + facile d'écrire des tests de recette (ou d'intégration). + Un exemple serait de confirmer qu'un utilisateur a bien été ajouté + dans une base de données après s'être enregistré sur une site web. +

    +

    + La démonstration la plus rapide : l'exemple +

    +

    + Supposons que nous sommes en train de tester une simple + classe de log dans un fichier : elle s'appelle + Log dans classes/Log.php. Commençons + par créer un script de test, appelé + tests/log_test.php. Son contenu est le suivant... +require_once('simpletest/autorun.php'); +require_once('../classes/log.php'); + +class TestOfLogging extends UnitTestCase { +} +?> +]]> + Ici le répertoire simpletest est soit dans le + dossier courant, soit dans les dossiers pour fichiers + inclus. Vous auriez à éditer ces arborescences suivant + l'endroit où vous avez installé SimpleTest. + Le fichier "autorun.php" fait plus que juste inclure + les éléments de SimpleTest : il lance aussi les tests pour nous. +

    +

    + TestOfLogging est notre premier scénario de test + et il est pour l'instant vide. + Chaque scénario de test est une classe qui étend une des classes + de base de SimpleTest. Nous pouvons avoir autant de classes de ce type + que nous voulons. +

    +

    + Avec ces trois lignes d'échafaudage + l'inclusion de notre classe Log, nous avons une suite + de tests. Mais pas encore de test ! +

    +

    + Pour notre premier test, supposons que la classe Log + prenne le nom du fichier à écrire au sein du constructeur, + et que nous avons un répertoire temporaire dans lequel placer + ce fichier. +testLogCreatesNewFileOnFirstMessage() { + @unlink('/temp/test.log'); + $log = new Log('/temp/test.log'); + $this->assertFalse(file_exists('/temp/test.log')); + $log->message('Should write this to a file'); + $this->assertTrue(file_exists('/temp/test.log')); + } +} +?> +]]> + Au lancement du scénario de test, toutes les méthodes qui + commencent avec la chaîne test sont + identifiées puis exécutées. + Si la méthode commence par test, c'est un test. + Remarquez bien le nom très long de notre exemple : + testLogCreatesNewFileOnFirstMessage(). + C'est bel et bien délibéré : ce style est considéré désirable + et il rend la sortie du test plus lisible. +

    +

    + D'ordinaire nous avons bien plusieurs méthodes de tests. + Mais ce sera pour plus tard. +

    +

    + Les assertions dans les + méthodes de test envoient des messages vers le framework de + test qui affiche immédiatement le résultat. Cette réponse + immédiate est importante, non seulement lors d'un crash + causé par le code, mais aussi de manière à rapprocher + l'affichage de l'erreur au plus près du scénario de test + concerné via un appel à printcode>. +

    +

    + Pour voir ces résultats lançons effectivement les tests. + Aucun autre code n'est nécessaire, il suffit d'ouvrir + la page dans un navigateur. +

    +

    + En cas échec, l'affichage ressemble à... +

    +

    TestOfLogging

    + Fail: testcreatingnewfile->True assertion failed.
    +
    1/1 test cases complete. + 1 passes and 1 fails.
    +
    + ...et si ça passe, on obtient... +
    +

    TestOfLogging

    +
    1/1 test cases complete. + 2 passes and 0 fails.
    +
    + Et si vous obtenez ça... +
    + Fatal error: Failed opening required '../classes/log.php' (include_path='') in /home/marcus/projects/lastcraft/tutorial_tests/Log/tests/log_test.php on line 7 +
    + c'est qu'il vous manque le fichier classes/Log.php + qui pourrait ressembler à : + +class Log { + function Log($file_path) { + } + + function message() { + } +} +?> +]]> + C'est largement plus sympathique d'écrire le code après le test. + Plus que sympatique même - cette technique s'appelle + "Développement Piloté par les Tests" ou + "Test Driven Development" en anglais. +

    +

    + Pour plus de renseignements sur le testeur, voyez la + documentation pour les tests de régression. +

    +
    +
    +

    + Il est peu probable que dans une véritable application on + ait uniquement besoin de passer un seul scénario de test. + Cela veut dire que nous avons besoin de grouper les + scénarios dans un script de test qui peut, si nécessaire, + lancer tous les tests de l'application. +

    +

    + Notre première étape est de créer un nouveau fichier appelé + tests/all_tests.php et d'y inclure le code suivant... +require_once('simpletest/autorun.php'); + +class AllTests extends TestSuite { + function AllTests() { + $this->TestSuite('All tests'); + $this->addFile('log_test.php'); + } +} +?> +]]> + L'inclusion de "autorun" permettra à notre future suite + de tests d'être lancée juste en invoquant ce script. +

    +

    + La sous-classe TestSuite doit chaîner + son constructeur. Cette limitation sera supprimée dans + les versions à venir. +

    +

    + The method TestSuite::addFile() + will include the test case file and read any new classes + that are descended from SimpleTestCase. + + Cette méthode TestSuite::addTestFile() va + inclure le fichier de scénarios de test et lire parmi + toutes les nouvelles classes créées celles qui sont issues + de SimpleTestCase. + UnitTestCase est juste un exemple de classe dérivée + depuis SimpleTestCase et vous pouvez créer les vôtres. + TestSuite::addFile() peut aussi inclure d'autres suites. +

    +

    + La classe ne sera pas encore instanciée. + Quand la suite de tests est lancée, elle construira chaque instance + une fois le test atteint, et le détuira juste ensuite. + Cela veut dire que le constructeur n'est lancé qu'une fois avant + chaque initialisation de ce scénario de test et que le destructeur + est lui aussi lancé avant que le test suivant ne commence. +

    +

    + Il est commun de grouper des scénarios de test dans des super-classes + qui ne sont pas sensées être lancées, mais qui deviennent une classe de base + pour d'autres tests. + Pour que "autorun" fonctionne proprement les fichiers + des scénarios de test ne devraient pas lancer aveuglement + d'autres extensions de scénarios de test qui ne lanceraient pas + effectivement des tests. + Cela pourrait aboutir à un mauvais comptages des scénarios de test + pendant la procédure. + Pas vraiement un problème majeure, mais pour éviter cet inconvénient + il suffit de marquer vos classes de base comme abstract. + SimpleTest ne lance pas les classes abstraites. Et si vous utilisez encore + PHP4 alors une directive SimpleTestOptions::ignore() + dans votre fichier de scénario de test aura le même effet. +

    +

    + Par ailleurs, le fichier avec le scénario de test ne devrait pas + avoir été inclus ailleurs. Sinon aucun scénario de test + ne sera inclus à ce groupe. + Ceci pourrait se transformer en un problème plus grave : + si des fichiers ont déjà été inclus par PHP alors la méthode + TestSuite::addFile() ne les détectera pas. +

    +

    + Pour afficher les résultats, il est seulement nécessaire + d'invoquer tests/all_tests.php à partir du serveur + web. +

    +

    + Pour plus de renseignements des groupes de tests, voyez le + documentation sur le groupement des tests. +

    +
    +
    +

    + Avançons un peu plus dans le futur. +

    +

    + Supposons que notre class logging soit testée et terminée. + Supposons aussi que nous testons une autre classe qui ait + besoin d'écrire des messages de log, disons + SessionPool. Nous voulons tester une méthode + qui ressemblera probablement à quelque chose comme... + +class SessionPool { + ... + function logIn($username) { + ... + $this->_log->message('User $username logged in.'); + ... + } + ... +} + +]]> + Avec le concept de "réutilisation de code" comme fil + conducteur, nous utilisons notre class Log. Un + scénario de test classique ressemblera peut-être à... +require_once('../classes/session_pool.php'); + +class TestOfSessionLogging extends UnitTestCase { + + function setUp() { + @unlink('/temp/test.log'); + } + + function tearDown() { + @unlink('/temp/test.log'); + } + + function testLoggingInIsLogged() { + $log = new Log('/temp/test.log'); + $session_pool = &new SessionPool($log); + $session_pool->logIn('fred'); + $messages = file('/temp/test.log'); + $this->assertEqual($messages[0], "User fred logged in.\n"); + } +} +?> +]]> + Nous expliquerons les méthodes setUp() + et tearDown() plus tard. +

    +

    + Le design de ce scénario de test n'est pas complètement + mauvais, mais on peut l'améliorer. Nous passons du temps à + tripoter les fichiers de log qui ne font pas partie de + notre test. + Pire, nous avons créé des liens de proximité + entre la classe Log et ce test. Que se + passerait-il si nous n'utilisions plus de fichiers, mais la + bibliothèque syslog à la place ? + + Cela veut dire que notre test TestOfSessionLogging + enverra un échec alors même qu'il ne teste pas Logging. +

    +

    + Il est aussi fragile sur des petites retouches. Avez-vous + remarqué le retour chariot supplémentaire à la fin du + message ? A-t-il été ajouté par le loggueur ? Et si il + ajoutait aussi un timestamp ou d'autres données ? +

    +

    + L'unique partie à tester réellement est l'envoi d'un + message précis au loggueur. + Nous pouvons réduire le couplage en + créant une fausse classe de logging : elle ne fait + qu'enregistrer le message pour le test, mais ne produit + aucun résultat. Sauf qu'elle doit ressembler exactement à + l'original. +

    +

    + Si l'objet fantaisie n'écrit pas dans un fichier alors nous + nous épargnons la suppression du fichier avant et après le + test. Nous pourrions même nous épargner quelques lignes de + code supplémentaires si l'objet fantaisie pouvait exécuter + l'assertion. +

    +

    + Trop beau pour être vrai ? Pas vraiement on peut créer un tel + objet très facilement... +Mock::generate('Log'); + +class TestOfSessionLogging extends UnitTestCase { + + function testLoggingInIsLogged() { + $log = &new MockLog(); + $log->expectOnce('message', array('User fred logged in.')); + $session_pool = &new SessionPool($log); + $session_pool->logIn('fred'); + } +} +?> +]]> + L'appel Mock::generate() a généré + une nouvelle classe appelé MockLog. + Cela ressemble à un clone identique, sauf que nous pouvons + y adjoindre du code de test. + C'est ce que fait expectOnce(). + Cela dit que si message() m'est appelé, + il a intérêt à l'être avec le paramètre + "User fred logged in.". +

    +

    + L'appel tally() est nécessaire pour annoncer à + l'objet fantaisie qu'il n'y aura plus d'appels ultérieurs. + Sans ça l'objet fantaisie pourrait attendre pendant une + éternité l'appel de la méthode sans jamais prévenir le + scénario de test. Les autres tests sont déclenchés + automatiquement quand l'appel à message() est + invoqué sur l'objet MockLog par le code + SessionPool::logIn(). + L'appel mock va déclencher une comparaison des + paramètres et ensuite envoyer le message "pass" ou "fail" + au test pour l'affichage. Des jokers peuvent être inclus + pour ne pas avoir à tester tous les paramètres d'un appel + alors que vous ne souhaitez qu'en tester un. +

    +

    + Les objets fantaisie dans la suite SimpleTest peuvent avoir + un ensemble de valeurs de sortie arbitraires, des séquences + de sorties, des valeurs de sortie sélectionnées à partir + des arguments d'entrée, des séquences de paramètres + attendus et des limites sur le nombre de fois qu'une + méthode peut être invoquée. +

    +

    + Pour que ce test fonctionne la librairie avec les objets + fantaisie doit être incluse dans la suite de tests, par + exemple dans all_tests.php. +

    +

    + Pour plus de renseignements sur les objets fantaisie, voyez le + documentation sur les objets fantaisie. +

    +
    +
    +

    + Une des exigences des sites web, c'est qu'ils produisent + des pages web. Si vous construisez un projet de A à Z et + que vous voulez intégrer des tests au fur et à mesure alors + vous voulez un outil qui puisse effectuer une navigation + automatique et en examiner le résultat. C'est le boulot + d'un testeur web. +

    +

    + Effectuer un test web via SimpleTest reste assez primitif : + il n'y a pas de javascript par exemple. + La plupart des autres opérations d'un navigateur sont simulées. +

    +

    + Pour vous donner une idée, voici un exemple assez trivial : + aller chercher une page web, + à partir de là naviguer vers la page "about" + et finalement tester un contenu déterminé par le client. +require_once('simpletest/web_tester.php'); + +class TestOfAbout extends WebTestCase { + function testOurAboutPageGivesFreeReignToOurEgo() { + $this->get('http://test-server/index.php'); + $this->click('About'); + $this->assertTitle('About why we are so great'); + $this->assertText('We are really great'); + } +} +?> +]]> + Avec ce code comme test de recette, vous pouvez vous + assurer que le contenu corresponde toujours aux + spécifications à la fois des développeurs et des autres + parties prenantes au projet. +

    +

    + Vous pouvez aussi naviguer à travers des formulaires... +get('http://google.com/'); + $this->setField('q', 'simpletest'); + $this->click("I'm Feeling Lucky"); + $this->assertTitle('SimpleTest - Unit Testing for PHP'); + } +} +?> +]]> + ...même si cela pourrait constituer une violation + des documents juridiques de Google(tm). +

    +

    + Pour plus de renseignements sur comment tester une page web, voyez la + documentation sur tester des scripts web. +

    +

    + SourceForge.net Logo +

    +
    +
    + + + Utiliser le testeur rapidement + avec un exemple. + + + Groupes de tests + pour tester en un seul clic. + + + Utiliser les objets fantaisie + pour faciliter les tests et gagner en contrôle. + + + Tester des pages web + au niveau de l'HTML. + + + + + Télécharger PHP Simple Test + depuis SourceForge. + + + L'API de SimpleTest pour développeur + donne tous les détails sur les classes et assertions existantes. + + + + + développement logiciel, + programmation php, + outils de développement logiciel, + tutorial php, + scripts php gratuits, + architecture, + ressources php, + objets fantaise, + junit, + php testing, + php unit, + méthodologie, + développement piloté par les tests, + sourceforge, + open source, + unit test, + web tester, + web testing, + outils tests html, + tester des web pages, + php objets fantaise, + naviguer automatiquement sur des sites web, + test automatisé, + scripting web, + wget, + test curl, + jmock pour php, + jwebunit, + phpunit, + php unit testing, + php web testing, + jason sweat, + marcus baker, + perrick penet, + topstyle plug in, + phpedit plug in + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/subclass_tutorial.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/subclass_tutorial.xml new file mode 100644 index 0000000..d730c8c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/subclass_tutorial.xml @@ -0,0 +1,261 @@ + + + + + Tutorial de test unitaire en PHP - Sous classer un scénario de test + +
    +

    + Nous avions laissé notre test d'horloge avec un trou. + Si la fonction time() de PHP avançait pendant cette comparaison... +assertEqual($clock->now(), time(), 'Now is the right time'); +} +]]> + ...notre test aurait un écart d'une seconde + et entraînerait un faux échec. + Un comportement erratique de notre suite de test + n'est vraiment pas ce que nous souhaitons : + nous pourrions la lancer une centaine de fois par jour. +

    +

    + Nous pourrions ré-écrire notre test... + + $time1 = $clock->now(); + $time2 = time(); + $this->assertTrue(($time1 == $time2) || ($time1 + 1 == $time2), 'Now is the right time'); +} +]]> + Sauf que la conception n'est pas plus claire + et que nous devrons le répéter pour chaque test de chronométrage. + Les répétitions sont un ennemi public n°1 + et donc un très bon stimulant pour le remaniement de notre code de test. +UnitTestCase('Clock class test'); + } + function assertSameTime($time1, $time2, $message) { + $this->assertTrue( + ($time1 == $time2) || ($time1 + 1 == $time2), + $message); + } + function testClockTellsTime() { + $clock = new Clock(); + $this->assertSameTime($clock->now(), time(), 'Now is the right time'); + } + function testClockAdvance() { + $clock = new Clock(); + $clock->advance(10); + $this->assertSameTime($clock->now(), time() + 10, 'Advancement'); + } +} +]]> + Bien entendu à chaque modification je relance + les tests pour bien vérifier que nous sommes dans les clous. + Remaniement au vert. C'est beaucoup plus sûr. +

    +
    +
    +

    + Peut-être voulons nous ajouter d'autres tests + sensibles au temps. Peut-être lisons nous des timestamps + - en provenance d'une entrée dans une base de données ou d'ailleurs + - qui tiendraient compte d'une simple seconde pour basculer. + Pour que ces nouvelles classes de test profitent + de notre nouvelle assertion nous avons besoin + de la placer dans une "super classe". +

    +

    + Voici notre fichier clock_test.php + au complet après la promotion de notre méthode + assertSameTime() dans sa propre "super classe"... + + class TimeTestCase extends UnitTestCase { + function TimeTestCase($test_name) { + $this->UnitTestCase($test_name); + } + function assertSameTime($time1, $time2, $message) { + $this->assertTrue( + ($time1 == $time2) || ($time1 + 1 == $time2), + $message); + } + } + + class TestOfClock extends TimeTestCase { + function TestOfClock() { + $this->TimeTestCase('Clock class test'); + } + function testClockTellsTime() { + $clock = new Clock(); + $this->assertSameTime($clock->now(), time(), 'Now is the right time'); + } + function testClockAdvance() { + $clock = new Clock(); + $clock->advance(10); + $this->assertSameTime($clock->now(), time() + 10, 'Advancement'); + } + } +?> +]]> + Désormais nous bénéficions de notre nouvelle assertion + à chaque fois que nous héritons de notre propre classe + TimeTestCase plutôt que de la classe + par défaut UnitTestCase. + Nous retrouvons la conception de l'outil JUnit + et SimpleTest est un portage en PHP de cette interface. + Il s'agit d'un framework de test à partir duquel + votre propre système de test peut s'agrandir. +

    +

    + Si nous lançons les tests maintenant + une légère broutille survient... +

    + Warning: Missing argument 1 for timetestcase() + in /home/marcus/projects/lastcraft/tutorial_tests/tests/clock_test.php on line 5
    +

    All tests

    +
    3/3 test cases complete. + 6 passes and 0 fails.
    +
    + La raison est assez délicate. +

    +

    + Notre sous-classe exige un paramètre + dans le constructeur qui n'a pas été fourni + et pourtant il semblerait que nous l'ayons + bel et bien fourni. Quand nous avons hérité + de notre nouvelle casse nous lui avons passé + notre propre constructeur. C'est juste là... +TimeTestCase('Clock class test'); +} +]]> + En fait nous avons raison, + là n'est pas le problème. +

    +

    + Vous vous souvenez de quand nous avons construit + le test de groupe all_tests.php + en utilisant la méthode addTestFile(). + Cette méthode recherche les classes de scénario de test, + les instancie si elles sont nouvelles puis exécute + tous nos tests. Ce qui s'est passé ? + Elle a aussi trouvé notre extension de scénario de test. + C'est sans conséquence puisque qu'il n'y a pas + de méthode de test à l'intérieur - comprendre pas + de méthode commençant par "test". + Aucun test supplémentaire n'est exécuté. +

    +

    + Le problème vient du fait qu'il instancie la classe + et le fait sans le paramètre $test_name + qui déclenche l'avertissement. + Ce paramètre n'est généralement nécessaire + ni pour un scénario de test, ni pour son assertion. + Pour que notre scénario de test étendu corresponde + à l'interface de UnitTestCase, + nous avons besoin de le rendre optionnel... +$test_name = false) { + $this->UnitTestCase($test_name); + } + function assertSameTime($time1, $time2, $message = false) { + if (! $message) { + $message = "Time [$time1] should match time [$time2]"; + } + $this->assertTrue( + ($time1 == $time2) || ($time1 + 1 == $time2), + $message); + } +} +]]> + Bien sûr, que cette classe soit instanciée + sans raison par la suite de test devrait + continuer à vous ennuyer. + Voici une modification pour l'empêcher de s'exécuter... +SimpleTestOptions::ignore('TimeTestCase'); +class TimeTestCase extends UnitTestCase { + function TimeTestCase($test_name = false) { + $this->UnitTestCase($test_name); + } + function assertSameTime($time1, $time2, $message = '') { + if (!$message) { + $message = "Time [$time1] should match time [$time2]"; + } + $this->assertTrue( + ($time1 == $time2) || ($time1 + 1 == $time2), + $message); + } +} +]]> + Cette ligne ne fait que demander à SimpleTest + d'ignorer cette classe lors de la construction + des suites de test. Elle peut être ajoutée n'importe + où dans le fichier de scénario de test. +

    +

    + Les six succès ont l'air bien mais ne disent + pas à un observateur peu attentif ce qui a été testé. + Pour cela il faut regarder dans le code. + Si cela vous paraît trop de boulot et que vous + préfèreriez lire ces informations directement + alors vous devriez aller lire comment + afficher les succès. +

    +
    +
    + + + Une assertion insensible au chronomètre + qui permet de gagner une seconde. + + + Sous classer un scénario de test + afin de réutiliser la méthode de test. + + + + + Section précédente : + contrôler les variables de test. + + + Section suivante : + changer l'affichage des tests. + + + Vous aurez besoin du + testeur unitaire SimpleTest + pour les exemples. + + + + + développement logiciel, + programmation php, + outils de développement logiciel, + tutorial php, + scripts php gratuits, + organisation de tests unitaires, + création de sous-classe, + conseil de test, + astuce de développement, + exemple de code php, + objets fantaisie, + junit, + test php, + outil de test unitaire, + suite de test php + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/support_website.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/support_website.xml new file mode 100644 index 0000000..0494f65 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/support_website.xml @@ -0,0 +1,60 @@ + + + + + Mailing-list de support + + +

    + La mailing-list simpletest-support est probablement l'endroit + le plus actif autour de SimpleTest : aide, conseil, bugs et détours, c'est là + que tout se passe la plupart du temps. Attention tout de même : les échanges + ont lieu en anglais. +

    +
    +
    +

    + C'est vraiment + + simple de s'y abonner et en plus, on peut aussi + + l'utiliser pour ses recherches. +

    +

    + Au dernier pointage, il y avait 114 abonnés et 1908 messages envoyés. + Cela fait de 1 à 4 messages par jour en moyenne. +

    +
    +
    + + + Pour s'abonner. + + + + + S'inscrire à la + + mailing list "Support". + + + Lire les + + archives. + + + + + SimpleTest, + download, + source code, + stable release, + eclipse release, + eclipse plugin, + debian package, + drupal module, + pear channel, + pearified package + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/unit_test_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/unit_test_documentation.xml new file mode 100644 index 0000000..2ee95ef --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/unit_test_documentation.xml @@ -0,0 +1,330 @@ + + + + + Documentation SimpleTest pour les tests de régression en PHP + +
    +

    + Le coeur du système est un framework de tests de régression + construit autour des scénarios de test. + Un exemple de scénario de test ressemble à... +class FileTestCase extends UnitTestCase { +} +]]> + Si aucun nom de test n'est fourni au moment + de la liaison avec le constructeur alors + le nom de la classe sera utilisé. + Il s'agit du nom qui sera affiché dans les résultats du test. +

    +

    + Les véritables tests sont ajoutés en tant que méthode + dans le scénario de test dont le nom par défaut + commence par la chaîne "test" + et quand le scénario de test est appelé toutes les méthodes + de ce type sont exécutées dans l'ordre utilisé + par l'introspection de PHP pour les trouver. + Peuvent être ajoutées autant de méthodes de test que nécessaires. + Par exemple... +UnitTestCase('File test'); + } + + function setUp() { + @unlink('../temp/test.txt'); + } + + function tearDown() { + @unlink('../temp/test.txt'); + } + + function testCreation() { + $writer = &new FileWriter('../temp/test.txt'); + $writer->write('Hello'); + $this->assertTrue(file_exists('../temp/test.txt'), 'File created'); + } +} +]]> + Le constructeur est optionnel et souvent omis. Sans nom, + le nom de la classe est utilisé comme nom pour le scénario de test. +

    +

    + Notre unique méthode de test pour le moment est + testCreation() où nous vérifions + qu'un fichier a bien été créé par notre objet + Writer. Nous pourrions avoir mis + le code unlink() dans cette méthode, + mais en la plaçant dans setUp() + et tearDown() nous pouvons l'utiliser + pour nos autres méthodes de test que nous ajouterons. +

    +

    + La méthode setUp() est lancé + juste avant chaque méthode de test. + tearDown() est lancé après chaque méthode de test. +

    +

    + Vous pouvez placer une initialisation de + scénario de test dans le constructeur afin qu'elle soit lancée + pour toutes les méthodes dans le scénario de test + mais dans un tel cas vous vous exposeriez à des interférences. + Cette façon de faire est légèrement moins rapide, + mais elle est plus sûre. + Notez que si vous arrivez avec des notions de JUnit, + il ne s'agit pas du comportement auquel vous êtes habitués. + Bizarrement JUnit re-instancie le scénario de test + pour chaque méthode de test pour se prévenir + d'une telle interférence. + SimpleTest demande à l'utilisateur final d'utiliser + setUp(), mais fournit aux codeurs de bibliothèque d'autres crochets. +

    +

    + Pour rapporter les résultats de test, + le passage par une classe d'affichage - notifiée par + les différentes méthodes de type assert...() - + est utilisée. En voici la liste complète pour + la classe UnitTestCase, + celle par défaut dans SimpleTest... + + + + + + + + + + + + + + + + +
    assertTrue($x)Echoue si $x est faux
    assertFalse($x)Echoue si $x est vrai
    assertNull($x)Echoue si $x est initialisé
    assertNotNull($x)Echoue si $x n'est pas initialisé
    assertIsA($x, $t)Echoue si $x n'est pas de la classe ou du type $t
    assertEqual($x, $y)Echoue si $x == $y est faux
    assertNotEqual($x, $y)Echoue si $x == $y est vrai
    assertIdentical($x, $y)Echoue si $x === $y est faux
    assertNotIdentical($x, $y)Echoue si $x === $y est vrai
    assertReference($x, $y)Echoue sauf si $x et $y sont la même variable
    assertCopy($x, $y)Echoue si $x et $y sont la même variable
    assertWantedPattern($p, $x)Echoue sauf si l'expression rationnelle $p capture $x
    assertNoUnwantedPattern($p, $x)Echoue si l'expression rationnelle $p capture $x
    assertNoErrors()Echoue si une erreur PHP arrive
    assertError($x)Echoue si aucune erreur ou message incorrect de PHP n'arrive
    + Toutes les méthodes d'assertion peuvent recevoir + une description optionnelle : + cette description sert pour étiqueter le résultat. + Sans elle, une message par défaut est envoyée à la place : + il est généralement suffisant. + Ce message par défaut peut encore être encadré + dans votre propre message si vous incluez "%s" + dans la chaîne. + Toutes les assertions renvoient vrai / true en cas de succès + et faux / false en cas d'échec. +

    +

    + D'autres exemples... +$variable = null; +$this->assertNull($variable, 'Should be cleared'); +]]> + ...passera et normalement n'affichera aucun message. + Si vous avez + configuré le testeur pour afficher aussi les succès + alors le message sera affiché comme tel. +$this->assertIdentical(0, false, 'Zero is not false [%s]'); +]]> + Ceci échouera étant donné qu'il effectue une vérification + sur le type en plus d'une comparaison sur les deux valeurs. + La partie "%s" est remplacée par le message d'erreur + par défaut qui aurait été affiché si nous n'avions pas fourni le nôtre. + Cela nous permet d'emboîter les messages de test. +$a = 1; +$b = $a; +$this->assertReference($a, $b); +]]> + Échouera étant donné que la variable $b + est une copie de $a. +$this->assertWantedPattern('/hello/i', 'Hello world'); +]]> + Là, ça passe puisque la recherche est insensible + à la casse et que donc hello + est bien repérable dans Hello world. +trigger_error('Disaster'); +trigger_error('Catastrophe'); +$this->assertError(); +$this->assertError('Catastrophe'); +$this->assertNoErrors(); +]]> + Ici, il y a besoin d'une petite explication : + toutes passent ! +

    +

    + Les erreurs PHP dans SimpleTest sont piégées et + placées dans une queue. Ici la première vérification + d'erreur attrape le message "Disaster" + sans vérifier le texte et passe. Résultat : + l'erreur est supprimée de la queue. + La vérification suivante teste non seulement l'existence + de l'erreur mais aussi le texte qui correspond : + un autre succès. Désormais la queue est vide + et le dernier test passe aussi. + Si une autre erreur non vérifiée est encore + dans la queue à la fin de notre méthode de test + alors une exception sera rapportée dans le test. + Notez que SimpleTest ne peut pas attraper les erreurs PHP à la compilation. +

    +

    + Les scénarios de test peuvent utiliser des méthodes + bien pratiques pour déboguer le code ou pour étendre la suite... + + + + + + + + + + +
    setUp()Est lancée avant chaque méthode de test
    tearDown()Est lancée après chaque méthode de test
    pass()Envoie un succès
    fail()Envoie un échec
    error()Envoi un évènement exception
    sendMessage()Envoie un message d'état aux systèmes d'affichage qui le supporte
    signal($type, $payload)Envoie un message défini par l'utilisateur au rapporteur du test
    dump($var)Effectue un print_r() formaté pour du déboguage rapide et grossier
    swallowErrors()Vide les erreurs de la queue
    +

    +
    +
    +

    + Bien sûr des méthodes supplémentaires de test + peuvent être ajoutées pour créer d'autres types + de scénario de test afin d'étendre le framework... + +class FileTester extends UnitTestCase { + function FileTester($name = false) { + $this->UnitTestCase($name); + } + + function assertFileExists($filename, $message = '%s') { + $this->assertTrue( + file_exists($filename), + sprintf($message, 'File [$filename] existence check')); + } +} +]]> + Ici la bibliothèque SimpleTest est localisée + dans un répertoire local appelé simpletest. + Pensez à le modifier pour votre propre environnement. +

    +

    + Alternativement vous pourriez utiliser dans votre code + un directive SimpleTestOptions::ignore('FileTester');. +

    +

    + Ce nouveau scénario peut être hérité exactement + comme un scénario de test classique... +FileTester { + + function setUp() { + @unlink('../temp/test.txt'); + } + + function tearDown() { + @unlink('../temp/test.txt'); + } + + function testCreation() { + $writer = &new FileWriter('../temp/test.txt'); + $writer->write('Hello'); + $this->assertFileExists('../temp/test.txt'); + } +} +]]> +

    +

    + Si vous souhaitez un scénario de test sans + toutes les assertions de UnitTestCase + mais uniquement avec les vôtres propres, + vous aurez besoin d'étendre la classe + SimpleTestCase à la place. + Elle se trouve dans simple_test.php + en lieu et place de unit_tester.php. + A consulter plus tard + si vous souhaitez incorporer les scénarios + d'autres testeurs unitaires dans votre suite de test. +

    +
    +
    +

    + Ce n'est pas souvent qu'il faille lancer des scénarios + avec un unique test. Sauf lorsqu'il s'agit de s'arracher + les cheveux sur un module à problème sans pour + autant désorganiser la suite de test principale. + Avec autorun aucun échafaudage particulier + n'est nécessaire, il suffit de lancer votre test et + vous y êtes. +

    +

    + Vous pouvez même décider quel rapporteur + (par exemple, TextReporter ou HtmlReporter) + vous préférez pour un fichier spécifique quand il est lancé + tout seul... + +SimpleTest :: prefer(new TextReporter()); +require_once('../classes/writer.php'); + +class FileTestCase extends UnitTestCase { + ... +} +?> +]]> + Ce script sera lancé tel que mais il n'y aura + aucun succès ou échec avant que des méthodes de test soient ajoutées. +

    +
    +
    + + + Scénarios de test unitaire + et opérations basiques. + + + Étendre des scénarios de test + pour les personnaliser à votre propre projet. + + + Lancer un scénario seul + comme un script unique. + + + + + La page de SimpleTest sur + SourceForge. + + + La page de téléchargement de SimpleTest sur + LastCraft. + + + L'API complète de SimpleTest + à partir de PHPDoc. + + + + + test unitaire php, + test d'intégration, + documentation, + marcus baker, + perrick penet + simple test, + documentation simpletest, + phpunit, + junit, + xunit + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/web_tester_documentation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/web_tester_documentation.xml new file mode 100644 index 0000000..289ce5c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/docs/source/fr/web_tester_documentation.xml @@ -0,0 +1,377 @@ + + + + + Documentation SimpleTest : tester des scripts web + +
    +

    + Tester des classes c'est très bien. + Reste que PHP est avant tout un langage + pour créer des fonctionnalités à l'intérieur de pages web. + Comment pouvons tester la partie de devant + -- celle de l'interface -- dans nos applications en PHP ? + Etant donné qu'une page web n'est constituée que de texte, + nous devrions pouvoir les examiner exactement + comme n'importe quelle autre donnée de test. +

    +

    + Cela nous amène à une situation délicate. + Si nous testons dans un niveau trop bas, + vérifier des balises avec un motif ad hoc par exemple, + nos tests seront trop fragiles. Le moindre changement + dans la présentation pourrait casser un grand nombre de test. + Si nos tests sont situés trop haut, en utilisant + une version fantaisie du moteur de template pour + donner un cas précis, alors nous perdons complètement + la capacité à automatiser certaines classes de test. + Par exemple, l'interaction entre des formulaires + et la navigation devra être testé manuellement. + Ces types de test sont extrêmement fastidieux + et plutôt sensibles aux erreurs. +

    +

    + SimpleTest comprend une forme spéciale de scénario + de test pour tester les actions d'une page web. + WebTestCase inclut des facilités pour la navigation, + des vérifications sur le contenu + et les cookies ainsi que la gestion des formulaires. + Utiliser ces scénarios de test ressemble + fortement à UnitTestCase... +class TestOfLastcraft extends WebTestCase { +} +]]> + Ici nous sommes sur le point de tester + le site de Last Craft. + Si ce scénario de test est situé dans un fichier appelé + lastcraft_test.php alors il peut être chargé + dans un script de lancement tout comme des tests unitaires... + +require_once('simpletest/web_tester.php'); +SimpleTest::prefer(new TextReporter()); + +class WebTests extends TestSuite { + function WebTests() { + $this->TestSuite('Web site tests'); + $this->addFile('lastcraft_test.php'); + } +} +?> +]]> + J'utilise ici le rapporteur en mode texte + pour mieux distinguer le contenu au format HTML + du résultat du test proprement dit. +

    +

    + Rien n'est encore testé. Nous pouvons télécharger + la page d'accueil en utilisant la méthode get()... + + function testHomepage() { + $this->assertTrue($this->get('http://www.lastcraft.com/')); + } +} +]]> + La méthode get() renverra "true" + uniquement si le contenu de la page a bien été téléchargé. + C'est un moyen simple, mais efficace pour vérifier + qu'une page web a bien été délivré par le serveur web. + Cependant le contenu peut révéler être une erreur 404 + et dans ce cas notre méthode get() renverrait encore un succès. +

    +

    + En supposant que le serveur web pour le site Last Craft + soit opérationnel (malheureusement ce n'est pas toujours le cas), + nous devrions voir... +

    +Web site tests
    +OK
    +Test cases run: 1/1, Failures: 0, Exceptions: 0
    +
    + Nous avons vérifié qu'une page, de n'importe quel type, + a bien été renvoyée. Nous ne savons pas encore + s'il s'agit de celle que nous souhaitions. +

    +
    +
    +

    + Pour obtenir la confirmation que la page téléchargée + est bien celle que nous attendions, + nous devons vérifier son contenu. + + $this->get('http://www.lastcraft.com/'); + $this->assertWantedPattern('/why the last craft/i'); + } +} +]]> + La page obtenue par le dernier téléchargement est + placée dans un buffer au sein même du scénario de test. + Il n'est donc pas nécessaire de s'y référer directement. + La correspondance du motif est toujours effectuée + par rapport à ce buffer. +

    +

    + Voici une liste possible d'assertions sur le contenu... + + + + + + + + + + + + + + + + + + + +
    assertWantedPattern($pattern)Vérifier une correspondance sur le contenu via une expression rationnelle Perl
    assertNoUnwantedPattern($pattern)Une expression rationnelle Perl pour vérifier une absence
    assertTitle($title)Passe si le titre de la page correspond exactement
    assertLink($label)Passe si un lien avec ce texte est présent
    assertNoLink($label)Passe si aucun lien avec ce texte est présent
    assertLinkById($id)Passe si un lien avec cet attribut d'identification est présent
    assertField($name, $value)Passe si une balise input avec ce nom contient cette valeur
    assertFieldById($id, $value)Passe si une balise input avec cet identifiant contient cette valeur
    assertResponse($codes)Passe si la réponse HTTP trouve une correspondance dans la liste
    assertMime($types)Passe si le type MIME se retrouve dans cette liste
    assertAuthentication($protocol)Passe si l'authentification provoquée est de ce type de protocole
    assertNoAuthentication()Passe s'il n'y pas d'authentification provoquée en cours
    assertRealm($name)Passe si le domaine provoqué correspond
    assertHeader($header, $content)Passe si une entête téléchargée correspond à cette valeur
    assertNoUnwantedHeader($header)Passe si une entête n'a pas été téléchargé
    assertHeaderPattern($header, $pattern)Passe si une entête téléchargée correspond à cette expression rationnelle Perl
    assertCookie($name, $value)Passe s'il existe un cookie correspondant
    assertNoCookie($name)Passe s'il n'y a pas de cookie avec un tel nom
    + Comme d'habitude avec les assertions de SimpleTest, + elles renvoient toutes "false" en cas d'échec + et "true" si c'est un succès. + Elles renvoient aussi un message de test optionnel : + vous pouvez l'ajouter dans votre propre message en utilisant "%s". +

    +

    + A présent nous pourrions effectué le test sur le titre uniquement... +$this->assertTitle('The Last Craft?'); +]]> + En plus d'une simple vérification sur le contenu HTML, + nous pouvons aussi vérifier que le type MIME est bien d'un type acceptable... +$this->assertMime(array('text/plain', 'text/html')); +]]> + Plus intéressant encore est la vérification sur + le code de la réponse HTTP. Pareillement au type MIME, + nous pouvons nous assurer que le code renvoyé se trouve + bien dans un liste de valeurs possibles... +get('http://simpletest.sourceforge.net/'); + $this->assertResponse(200); + } +} +]]> + Ici nous vérifions que le téléchargement s'est + bien terminé en ne permettant qu'une réponse HTTP 200. + Ce test passera, mais ce n'est pas la meilleure façon de procéder. + Il n'existe aucune page sur http://simpletest.sourceforge.net/, + à la place le serveur renverra une redirection vers + http://www.lastcraft.com/simple_test.php. + WebTestCase suit automatiquement trois + de ces redirections. Les tests sont quelque peu plus + robustes de la sorte. Surtout qu'on est souvent plus intéressé + par l'interaction entre les pages que de leur simple livraison. + Si les redirections se révèlent être digne d'intérêt, + il reste possible de les supprimer... + + $this->setMaximumRedirects(0); + $this->get('http://simpletest.sourceforge.net/'); + $this->assertResponse(200); + } +} +]]> + Alors l'assertion échoue comme prévue... +

    +Web site tests
    +1) Expecting response in [200] got [302]
    +    in testhomepage
    +    in testoflastcraft
    +    in lastcraft_test.php
    +FAILURES!!!
    +Test cases run: 1/1, Failures: 1, Exceptions: 0
    +
    + Nous pouvons modifier le test pour accepter les redirections... +setMaximumRedirects(0); + $this->get('http://simpletest.sourceforge.net/'); + $this->assertResponse(array(301, 302, 303, 307)); + } +} +]]> + Maitenant ça passe. +

    +
    +
    +

    + Les utilisateurs ne naviguent pas souvent en tapant les URLs, + mais surtout en cliquant sur des liens et des boutons. + Ici nous confirmons que les informations sur le contact + peuvent être atteintes depuis la page d'accueil... +get('http://www.lastcraft.com/'); + $this->clickLink('About'); + $this->assertTitle('About Last Craft'); + } +} +]]> + Le paramètre est le texte du lien. +

    +

    + Il l'objectif est un bouton plutôt qu'une balise ancre, + alors clickSubmit() doit être utilisé avec + le titre du bouton... +$this->clickSubmit('Go!'); +]]> +

    +

    + La liste des méthodes de navigation est... + + + + + + + + + + + + + + + + + + + + + +
    get($url, $parameters)Envoie une requête GET avec ces paramètres
    post($url, $parameters)Envoie une requête POST avec ces paramètres
    head($url, $parameters)Envoie une requête HEAD sans remplacer le contenu de la page
    retry()Relance la dernière requête
    back()Identique au bouton "Précédent" du navigateur
    forward()Identique au bouton "Suivant" du navigateur
    authenticate($name, $password)Re-essaye avec une tentative d'authentification
    getFrameFocus()Le nom de la fenêtre en cours d'utilisation
    setFrameFocusByIndex($choice)Change le focus d'une fenêtre en commençant par 1
    setFrameFocus($name)Change le focus d'une fenêtre en utilisant son nom
    clearFrameFocus()Revient à un traitement de toutes les fenêtres comme une seule
    clickSubmit($label)Clique sur le premier bouton avec cette étiquette
    clickSubmitByName($name)Clique sur le bouton avec cet attribut de nom
    clickSubmitById($id)Clique sur le bouton avec cet attribut d'identification
    clickImage($label, $x, $y)Clique sur une balise input de type image par son titre (title="*") our son texte alternatif (alt="*")
    clickImageByName($name, $x, $y)Clique sur une balise input de type image par son attribut (name="*")
    clickImageById($id, $x, $y)Clique sur une balise input de type image par son identifiant (id="*")
    submitFormById($id)Soumet un formulaire sans valeur de soumission
    clickLink($label, $index)Clique sur une ancre avec ce texte d'étiquette visible
    clickLinkById($id)Clique sur une ancre avec cet attribut d'identification
    +

    +

    + Les paramètres dans les méthodes get(), + post() et head() sont optionnels. + Le téléchargement via HTTP HEAD ne modifie pas + le contexte du navigateur, il se limite au chargement des cookies. + Cela peut être utilise lorsqu'une image ou une feuille de style + initie un cookie pour bloquer un robot trop entreprenant. +

    +

    + Les commandes retry(), back() + et forward() fonctionnent exactement comme + dans un navigateur. Elles utilisent l'historique pour + relancer les pages. Une technique bien pratique pour + vérifier les effets d'un bouton retour sur vos formulaires. +

    +

    + Les méthodes sur les fenêtres méritent une petite explication. + Par défaut, une page avec des fenêtres est traitée comme toutes + les autres. Le contenu sera vérifié à travers l'ensemble de + la "frameset", par conséquent un lien fonctionnera, + peu importe la fenêtre qui contient la balise ancre. + Vous pouvez outrepassé ce comportement en exigeant + le focus sur une unique fenêtre. Si vous réalisez cela, + toutes les recherches et toutes les actions se limiteront + à cette unique fenêtre, y compris les demandes d'authentification. + Si un lien ou un bouton n'est pas dans la fenêtre en focus alors + il ne peut pas être cliqué. +

    +

    + Tester la navigation sur des pages fixes ne vous alerte que + quand vous avez cassé un script entier. + Pour des pages fortement dynamiques, + un forum de discussion par exemple, + ça peut être crucial pour vérifier l'état de l'application. + Pour la plupart des applications cependant, + la logique vraiment délicate se situe dans la gestion + des formulaires et des sessions. + Heureusement SimpleTest aussi inclut + + des outils pour tester des formulaires web. +

    +
    +
    +

    + Bien que SimpleTest n'ait pas comme objectif + de contrôler des erreurs réseau, il contient quand même + des méthodes pour modifier et déboguer les requêtes qu'il lance. + Voici une autre liste de méthode... + + + + + + + + + + + + +
    getTransportError()La dernière erreur de socket
    getUrl()La localisation courante
    showRequest()Déverse la requête sortante
    showHeaders()Déverse les entêtes d'entrée
    showSource()Déverse le contenu brut de la page HTML
    ignoreFrames()Ne recharge pas les framesets
    setCookie($name, $value)Initie un cookie à partir de maintenant
    addHeader($header)Ajoute toujours cette entête à la requête
    setMaximumRedirects($max)S'arrête après autant de redirections
    setConnectionTimeout($timeout)Termine la connexion après autant de temps entre les bytes
    useProxy($proxy, $name, $password)Effectue les requêtes à travers ce proxy d'URL
    +

    +
    +
    + + + Réussir à télécharger une page web + + + Tester le contenu de la page + + + Naviguer sur un site web pendant le test + + + Méthodes pour modifier une requête et pour déboguer + + + + + La page du projet SimpleTest sur + SourceForge. + + + La page de téléchargement de SimpleTest sur + LastCraft. + + + L'API du développeur pour SimpleTest + donne tous les détails sur les classes et les assertions disponibles. + + + + + développement logiciel, + programmation php pour des clients, + php orienté client, + outils de développement logiciel, + framework de test de recette, + scripts php gratuits, + architecture, + ressources php, + HTMLUnit, + JWebUnit, + test php, + ressource de test unitaire, + test web + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/dumper.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/dumper.php new file mode 100644 index 0000000..ef2662d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/dumper.php @@ -0,0 +1,359 @@ +getType($value); + switch($type) { + case "Null": + return "NULL"; + case "Boolean": + return "Boolean: " . ($value ? "true" : "false"); + case "Array": + return "Array: " . count($value) . " items"; + case "Object": + return "Object: of " . get_class($value); + case "String": + return "String: " . $this->clipString($value, 200); + default: + return "$type: $value"; + } + return "Unknown"; + } + + /** + * Gets the string representation of a type. + * @param mixed $value Variable to check against. + * @return string Type. + * @access public + */ + function getType($value) { + if (! isset($value)) { + return "Null"; + } elseif (is_bool($value)) { + return "Boolean"; + } elseif (is_string($value)) { + return "String"; + } elseif (is_integer($value)) { + return "Integer"; + } elseif (is_float($value)) { + return "Float"; + } elseif (is_array($value)) { + return "Array"; + } elseif (is_resource($value)) { + return "Resource"; + } elseif (is_object($value)) { + return "Object"; + } + return "Unknown"; + } + + /** + * Creates a human readable description of the + * difference between two variables. Uses a + * dynamic call. + * @param mixed $first First variable. + * @param mixed $second Value to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Description of difference. + * @access public + */ + function describeDifference($first, $second, $identical = false) { + if ($identical) { + if (! $this->isTypeMatch($first, $second)) { + return "with type mismatch as [" . $this->describeValue($first) . + "] does not match [" . $this->describeValue($second) . "]"; + } + } + $type = $this->getType($first); + if ($type == "Unknown") { + return "with unknown type"; + } + $method = 'describe' . $type . 'Difference'; + return $this->$method($first, $second, $identical); + } + + /** + * Tests to see if types match. + * @param mixed $first First variable. + * @param mixed $second Value to compare with. + * @return boolean True if matches. + * @access private + */ + protected function isTypeMatch($first, $second) { + return ($this->getType($first) == $this->getType($second)); + } + + /** + * Clips a string to a maximum length. + * @param string $value String to truncate. + * @param integer $size Minimum string size to show. + * @param integer $position Centre of string section. + * @return string Shortened version. + * @access public + */ + function clipString($value, $size, $position = 0) { + $length = strlen($value); + if ($length <= $size) { + return $value; + } + $position = min($position, $length); + $start = ($size/2 > $position ? 0 : $position - $size/2); + if ($start + $size > $length) { + $start = $length - $size; + } + $value = substr($value, $start, $size); + return ($start > 0 ? "..." : "") . $value . ($start + $size < $length ? "..." : ""); + } + + /** + * Creates a human readable description of the + * difference between two variables. The minimal + * version. + * @param null $first First value. + * @param mixed $second Value to compare with. + * @return string Human readable description. + * @access private + */ + protected function describeGenericDifference($first, $second) { + return "as [" . $this->describeValue($first) . + "] does not match [" . + $this->describeValue($second) . "]"; + } + + /** + * Creates a human readable description of the + * difference between a null and another variable. + * @param null $first First null. + * @param mixed $second Null to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + protected function describeNullDifference($first, $second, $identical) { + return $this->describeGenericDifference($first, $second); + } + + /** + * Creates a human readable description of the + * difference between a boolean and another variable. + * @param boolean $first First boolean. + * @param mixed $second Boolean to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + protected function describeBooleanDifference($first, $second, $identical) { + return $this->describeGenericDifference($first, $second); + } + + /** + * Creates a human readable description of the + * difference between a string and another variable. + * @param string $first First string. + * @param mixed $second String to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + protected function describeStringDifference($first, $second, $identical) { + if (is_object($second) || is_array($second)) { + return $this->describeGenericDifference($first, $second); + } + $position = $this->stringDiffersAt($first, $second); + $message = "at character $position"; + $message .= " with [" . + $this->clipString($first, 200, $position) . "] and [" . + $this->clipString($second, 200, $position) . "]"; + return $message; + } + + /** + * Creates a human readable description of the + * difference between an integer and another variable. + * @param integer $first First number. + * @param mixed $second Number to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + protected function describeIntegerDifference($first, $second, $identical) { + if (is_object($second) || is_array($second)) { + return $this->describeGenericDifference($first, $second); + } + return "because [" . $this->describeValue($first) . + "] differs from [" . + $this->describeValue($second) . "] by " . + abs($first - $second); + } + + /** + * Creates a human readable description of the + * difference between two floating point numbers. + * @param float $first First float. + * @param mixed $second Float to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + protected function describeFloatDifference($first, $second, $identical) { + if (is_object($second) || is_array($second)) { + return $this->describeGenericDifference($first, $second); + } + return "because [" . $this->describeValue($first) . + "] differs from [" . + $this->describeValue($second) . "] by " . + abs($first - $second); + } + + /** + * Creates a human readable description of the + * difference between two arrays. + * @param array $first First array. + * @param mixed $second Array to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + protected function describeArrayDifference($first, $second, $identical) { + if (! is_array($second)) { + return $this->describeGenericDifference($first, $second); + } + if (! $this->isMatchingKeys($first, $second, $identical)) { + return "as key list [" . + implode(", ", array_keys($first)) . "] does not match key list [" . + implode(", ", array_keys($second)) . "]"; + } + foreach (array_keys($first) as $key) { + if ($identical && ($first[$key] === $second[$key])) { + continue; + } + if (! $identical && ($first[$key] == $second[$key])) { + continue; + } + return "with member [$key] " . $this->describeDifference( + $first[$key], + $second[$key], + $identical); + } + return ""; + } + + /** + * Compares two arrays to see if their key lists match. + * For an identical match, the ordering and types of the keys + * is significant. + * @param array $first First array. + * @param array $second Array to compare with. + * @param boolean $identical If true then type anomolies count. + * @return boolean True if matching. + * @access private + */ + protected function isMatchingKeys($first, $second, $identical) { + $first_keys = array_keys($first); + $second_keys = array_keys($second); + if ($identical) { + return ($first_keys === $second_keys); + } + sort($first_keys); + sort($second_keys); + return ($first_keys == $second_keys); + } + + /** + * Creates a human readable description of the + * difference between a resource and another variable. + * @param resource $first First resource. + * @param mixed $second Resource to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + protected function describeResourceDifference($first, $second, $identical) { + return $this->describeGenericDifference($first, $second); + } + + /** + * Creates a human readable description of the + * difference between two objects. + * @param object $first First object. + * @param mixed $second Object to compare with. + * @param boolean $identical If true then type anomolies count. + * @return string Human readable description. + * @access private + */ + protected function describeObjectDifference($first, $second, $identical) { + if (! is_object($second)) { + return $this->describeGenericDifference($first, $second); + } + return $this->describeArrayDifference( + get_object_vars($first), + get_object_vars($second), + $identical); + } + + /** + * Find the first character position that differs + * in two strings by binary chop. + * @param string $first First string. + * @param string $second String to compare with. + * @return integer Position of first differing + * character. + * @access private + */ + protected function stringDiffersAt($first, $second) { + if (! $first || ! $second) { + return 0; + } + if (strlen($first) < strlen($second)) { + list($first, $second) = array($second, $first); + } + $position = 0; + $step = strlen($first); + while ($step > 1) { + $step = (integer)(($step + 1) / 2); + if (strncmp($first, $second, $position + $step) == 0) { + $position += $step; + } + } + return $position; + } + + /** + * Sends a formatted dump of a variable to a string. + * @param mixed $variable Variable to display. + * @return string Output from print_r(). + * @access public + */ + function dump($variable) { + ob_start(); + print_r($variable); + $formatted = ob_get_contents(); + ob_end_clean(); + return $formatted; + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/eclipse.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/eclipse.php new file mode 100644 index 0000000..bd0349a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/eclipse.php @@ -0,0 +1,307 @@ +listener = &$listener; + $this->SimpleScorer(); + $this->case = ""; + $this->group = ""; + $this->method = ""; + $this->cc = $cc; + $this->error = false; + $this->fail = false; + } + + /** + * Means to display human readable object comparisons. + * @return SimpleDumper Visual comparer. + */ + function getDumper() { + return new SimpleDumper(); + } + + /** + * Localhost connection from Eclipse. + * @param integer $port Port to connect to Eclipse. + * @param string $host Normally localhost. + * @return SimpleSocket Connection to Eclipse. + */ + function &createListener($port, $host="127.0.0.1"){ + $tmplistener = &new SimpleSocket($host, $port, 5); + return $tmplistener; + } + + /** + * Wraps the test in an output buffer. + * @param SimpleInvoker $invoker Current test runner. + * @return EclipseInvoker Decorator with output buffering. + * @access public + */ + function &createInvoker(&$invoker){ + $eclinvoker = &new EclipseInvoker($invoker, $this->listener); + return $eclinvoker; + } + + /** + * C style escaping. + * @param string $raw String with backslashes, quotes and whitespace. + * @return string Replaced with C backslashed tokens. + */ + function escapeVal($raw){ + $needle = array("\\","\"","/","\b","\f","\n","\r","\t"); + $replace = array('\\\\','\"','\/','\b','\f','\n','\r','\t'); + return str_replace($needle, $replace, $raw); + } + + /** + * Stash the first passing item. Clicking the test + * item goes to first pass. + * @param string $message Test message, but we only wnat the first. + * @access public + */ + function paintPass($message){ + if (! $this->pass){ + $this->message = $this->escapeVal($message); + } + $this->pass = true; + } + + /** + * Stash the first failing item. Clicking the test + * item goes to first fail. + * @param string $message Test message, but we only wnat the first. + * @access public + */ + function paintFail($message){ + //only get the first failure or error + if (! $this->fail && ! $this->error){ + $this->fail = true; + $this->message = $this->escapeVal($message); + $this->listener->write('{status:"fail",message:"'.$this->message.'",group:"'.$this->group.'",case:"'.$this->case.'",method:"'.$this->method.'"}'); + } + } + + /** + * Stash the first error. Clicking the test + * item goes to first error. + * @param string $message Test message, but we only wnat the first. + * @access public + */ + function paintError($message){ + if (! $this->fail && ! $this->error){ + $this->error = true; + $this->message = $this->escapeVal($message); + $this->listener->write('{status:"error",message:"'.$this->message.'",group:"'.$this->group.'",case:"'.$this->case.'",method:"'.$this->method.'"}'); + } + } + + + /** + * Stash the first exception. Clicking the test + * item goes to first message. + * @param string $message Test message, but we only wnat the first. + * @access public + */ + function paintException($exception){ + if (! $this->fail && ! $this->error){ + $this->error = true; + $message = 'Unexpected exception of type[' . get_class($exception) . + '] with message [' . $exception->getMessage() . '] in [' . + $exception->getFile() .' line '. $exception->getLine() . ']'; + $this->message = $this->escapeVal($message); + $this->listener->write( + '{status:"error",message:"' . $this->message . '",group:"' . + $this->group . '",case:"' . $this->case . '",method:"' . $this->method + . '"}'); + } + } + + + /** + * We don't display any special header. + * @param string $test_name First test top level + * to start. + * @access public + */ + function paintHeader($test_name) { + } + + /** + * We don't display any special footer. + * @param string $test_name The top level test. + * @access public + */ + function paintFooter($test_name) { + } + + /** + * Paints nothing at the start of a test method, but stash + * the method name for later. + * @param string $test_name Name of test that is starting. + * @access public + */ + function paintMethodStart($method) { + $this->pass = false; + $this->fail = false; + $this->error = false; + $this->method = $this->escapeVal($method); + } + + /** + * Only send one message if the test passes, after that + * suppress the message. + * @param string $test_name Name of test that is ending. + * @access public + */ + function paintMethodEnd($method){ + if ($this->fail || $this->error || ! $this->pass){ + } else { + $this->listener->write( + '{status:"pass",message:"' . $this->message . '",group:"' . + $this->group . '",case:"' . $this->case . '",method:"' . + $this->method . '"}'); + } + } + + /** + * Stashes the test case name for the later failure message. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseStart($case){ + $this->case = $this->escapeVal($case); + } + + /** + * Drops the name. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseEnd($case){ + $this->case = ""; + } + + /** + * Stashes the name of the test suite. Starts test coverage + * if enabled. + * @param string $group Name of test or other label. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($group, $size){ + $this->group = $this->escapeVal($group); + if ($this->cc){ + if (extension_loaded('xdebug')){ + xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE); + } + } + } + + /** + * Paints coverage report if enabled. + * @param string $group Name of test or other label. + * @access public + */ + function paintGroupEnd($group){ + $this->group = ""; + $cc = ""; + if ($this->cc){ + if (extension_loaded('xdebug')){ + $arrfiles = xdebug_get_code_coverage(); + xdebug_stop_code_coverage(); + $thisdir = dirname(__FILE__); + $thisdirlen = strlen($thisdir); + foreach ($arrfiles as $index=>$file){ + if (substr($index, 0, $thisdirlen)===$thisdir){ + continue; + } + $lcnt = 0; + $ccnt = 0; + foreach ($file as $line){ + if ($line == -2){ + continue; + } + $lcnt++; + if ($line==1){ + $ccnt++; + } + } + if ($lcnt > 0){ + $cc .= round(($ccnt/$lcnt) * 100, 2) . '%'; + }else{ + $cc .= "0.00%"; + } + $cc.= "\t". $index . "\n"; + } + } + } + $this->listener->write('{status:"coverage",message:"' . + EclipseReporter::escapeVal($cc) . '"}'); + } +} + +/** + * Invoker decorator for Eclipse. Captures output until + * the end of the test. + * @package SimpleTest + * @subpackage Eclipse + */ +class EclipseInvoker extends SimpleInvokerDecorator{ + function __construct(&$invoker, &$listener) { + $this->listener = &$listener; + $this->SimpleInvokerDecorator($invoker); + } + + /** + * Starts output buffering. + * @param string $method Test method to call. + * @access public + */ + function before($method){ + ob_start(); + $this->invoker->before($method); + } + + /** + * Stops output buffering and send the captured output + * to the listener. + * @param string $method Test method to call. + * @access public + */ + function after($method) { + $this->invoker->after($method); + $output = ob_get_contents(); + ob_end_clean(); + if ($output !== ""){ + $result = $this->listener->write('{status:"info",message:"' . + EclipseReporter::escapeVal($output) . '"}'); + } + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/encoding.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/encoding.php new file mode 100644 index 0000000..e44964d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/encoding.php @@ -0,0 +1,552 @@ +key = $key; + $this->value = $value; + } + + /** + * The pair as a single string. + * @return string Encoded pair. + * @access public + */ + function asRequest() { + return urlencode($this->key) . '=' . urlencode($this->value); + } + + /** + * The MIME part as a string. + * @return string MIME part encoding. + * @access public + */ + function asMime() { + $part = 'Content-Disposition: form-data; '; + $part .= "name=\"" . $this->key . "\"\r\n"; + $part .= "\r\n" . $this->value; + return $part; + } + + /** + * Is this the value we are looking for? + * @param string $key Identifier. + * @return boolean True if matched. + * @access public + */ + function isKey($key) { + return $key == $this->key; + } + + /** + * Is this the value we are looking for? + * @return string Identifier. + * @access public + */ + function getKey() { + return $this->key; + } + + /** + * Is this the value we are looking for? + * @return string Content. + * @access public + */ + function getValue() { + return $this->value; + } +} + +/** + * Single post parameter. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleAttachment { + private $key; + private $content; + private $filename; + + /** + * Stashes the data for rendering later. + * @param string $key Key to add value to. + * @param string $content Raw data. + * @param hash $filename Original filename. + */ + function __construct($key, $content, $filename) { + $this->key = $key; + $this->content = $content; + $this->filename = $filename; + } + + /** + * The pair as a single string. + * @return string Encoded pair. + * @access public + */ + function asRequest() { + return ''; + } + + /** + * The MIME part as a string. + * @return string MIME part encoding. + * @access public + */ + function asMime() { + $part = 'Content-Disposition: form-data; '; + $part .= 'name="' . $this->key . '"; '; + $part .= 'filename="' . $this->filename . '"'; + $part .= "\r\nContent-Type: " . $this->deduceMimeType(); + $part .= "\r\n\r\n" . $this->content; + return $part; + } + + /** + * Attempts to figure out the MIME type from the + * file extension and the content. + * @return string MIME type. + * @access private + */ + protected function deduceMimeType() { + if ($this->isOnlyAscii($this->content)) { + return 'text/plain'; + } + return 'application/octet-stream'; + } + + /** + * Tests each character is in the range 0-127. + * @param string $ascii String to test. + * @access private + */ + protected function isOnlyAscii($ascii) { + for ($i = 0, $length = strlen($ascii); $i < $length; $i++) { + if (ord($ascii[$i]) > 127) { + return false; + } + } + return true; + } + + /** + * Is this the value we are looking for? + * @param string $key Identifier. + * @return boolean True if matched. + * @access public + */ + function isKey($key) { + return $key == $this->key; + } + + /** + * Is this the value we are looking for? + * @return string Identifier. + * @access public + */ + function getKey() { + return $this->key; + } + + /** + * Is this the value we are looking for? + * @return string Content. + * @access public + */ + function getValue() { + return $this->filename; + } +} + +/** + * Bundle of GET/POST parameters. Can include + * repeated parameters. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleEncoding { + private $request; + + /** + * Starts empty. + * @param array $query Hash of parameters. + * Multiple values are + * as lists on a single key. + * @access public + */ + function __construct($query = false) { + if (! $query) { + $query = array(); + } + $this->clear(); + $this->merge($query); + } + + /** + * Empties the request of parameters. + * @access public + */ + function clear() { + $this->request = array(); + } + + /** + * Adds a parameter to the query. + * @param string $key Key to add value to. + * @param string/array $value New data. + * @access public + */ + function add($key, $value) { + if ($value === false) { + return; + } + if (is_array($value)) { + foreach ($value as $item) { + $this->addPair($key, $item); + } + } else { + $this->addPair($key, $value); + } + } + + /** + * Adds a new value into the request. + * @param string $key Key to add value to. + * @param string/array $value New data. + * @access private + */ + protected function addPair($key, $value) { + $this->request[] = new SimpleEncodedPair($key, $value); + } + + /** + * Adds a MIME part to the query. Does nothing for a + * form encoded packet. + * @param string $key Key to add value to. + * @param string $content Raw data. + * @param hash $filename Original filename. + * @access public + */ + function attach($key, $content, $filename) { + $this->request[] = new SimpleAttachment($key, $content, $filename); + } + + /** + * Adds a set of parameters to this query. + * @param array/SimpleQueryString $query Multiple values are + * as lists on a single key. + * @access public + */ + function merge($query) { + if (is_object($query)) { + $this->request = array_merge($this->request, $query->getAll()); + } elseif (is_array($query)) { + foreach ($query as $key => $value) { + $this->add($key, $value); + } + } + } + + /** + * Accessor for single value. + * @return string/array False if missing, string + * if present and array if + * multiple entries. + * @access public + */ + function getValue($key) { + $values = array(); + foreach ($this->request as $pair) { + if ($pair->isKey($key)) { + $values[] = $pair->getValue(); + } + } + if (count($values) == 0) { + return false; + } elseif (count($values) == 1) { + return $values[0]; + } else { + return $values; + } + } + + /** + * Accessor for listing of pairs. + * @return array All pair objects. + * @access public + */ + function getAll() { + return $this->request; + } + + /** + * Renders the query string as a URL encoded + * request part. + * @return string Part of URL. + * @access protected + */ + protected function encode() { + $statements = array(); + foreach ($this->request as $pair) { + if ($statement = $pair->asRequest()) { + $statements[] = $statement; + } + } + return implode('&', $statements); + } +} + +/** + * Bundle of GET parameters. Can include + * repeated parameters. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleGetEncoding extends SimpleEncoding { + + /** + * Starts empty. + * @param array $query Hash of parameters. + * Multiple values are + * as lists on a single key. + * @access public + */ + function __construct($query = false) { + parent::__construct($query); + } + + /** + * HTTP request method. + * @return string Always GET. + * @access public + */ + function getMethod() { + return 'GET'; + } + + /** + * Writes no extra headers. + * @param SimpleSocket $socket Socket to write to. + * @access public + */ + function writeHeadersTo(&$socket) { + } + + /** + * No data is sent to the socket as the data is encoded into + * the URL. + * @param SimpleSocket $socket Socket to write to. + * @access public + */ + function writeTo(&$socket) { + } + + /** + * Renders the query string as a URL encoded + * request part for attaching to a URL. + * @return string Part of URL. + * @access public + */ + function asUrlRequest() { + return $this->encode(); + } +} + +/** + * Bundle of URL parameters for a HEAD request. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHeadEncoding extends SimpleGetEncoding { + + /** + * Starts empty. + * @param array $query Hash of parameters. + * Multiple values are + * as lists on a single key. + * @access public + */ + function SimpleHeadEncoding($query = false) { + $this->SimpleGetEncoding($query); + } + + /** + * HTTP request method. + * @return string Always HEAD. + * @access public + */ + function getMethod() { + return 'HEAD'; + } +} + +/** + * Bundle of POST parameters. Can include + * repeated parameters. + * @package SimpleTest + * @subpackage WebTester + */ +class SimplePostEncoding extends SimpleEncoding { + + /** + * Starts empty. + * @param array $query Hash of parameters. + * Multiple values are + * as lists on a single key. + * @access public + */ + function __construct($query = false) { + if (is_array($query) and $this->hasMoreThanOneLevel($query)) { + $query = $this->rewriteArrayWithMultipleLevels($query); + } + parent::__construct($query); + } + + function hasMoreThanOneLevel($query) { + foreach ($query as $key => $value) { + if (is_array($value)) { + return true; + } + } + return false; + } + + function rewriteArrayWithMultipleLevels($query) { + $query_ = array(); + foreach ($query as $key => $value) { + if (is_array($value)) { + foreach ($value as $sub_key => $sub_value) { + $query_[$key."[".$sub_key."]"] = $sub_value; + } + } else { + $query_[$key] = $value; + } + } + if ($this->hasMoreThanOneLevel($query_)) { + $query_ = $this->rewriteArrayWithMultipleLevels($query_); + } + + return $query_; + } + + + /** + * HTTP request method. + * @return string Always POST. + * @access public + */ + function getMethod() { + return 'POST'; + } + + /** + * Dispatches the form headers down the socket. + * @param SimpleSocket $socket Socket to write to. + * @access public + */ + function writeHeadersTo(&$socket) { + $socket->write("Content-Length: " . (integer)strlen($this->encode()) . "\r\n"); + $socket->write("Content-Type: application/x-www-form-urlencoded\r\n"); + } + + /** + * Dispatches the form data down the socket. + * @param SimpleSocket $socket Socket to write to. + * @access public + */ + function writeTo(&$socket) { + $socket->write($this->encode()); + } + + /** + * Renders the query string as a URL encoded + * request part for attaching to a URL. + * @return string Part of URL. + * @access public + */ + function asUrlRequest() { + return ''; + } +} + +/** + * Bundle of POST parameters in the multipart + * format. Can include file uploads. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleMultipartEncoding extends SimplePostEncoding { + private $boundary; + + /** + * Starts empty. + * @param array $query Hash of parameters. + * Multiple values are + * as lists on a single key. + * @access public + */ + function __construct($query = false, $boundary = false) { + parent::__construct($query); + $this->boundary = ($boundary === false ? uniqid('st') : $boundary); + } + + /** + * Dispatches the form headers down the socket. + * @param SimpleSocket $socket Socket to write to. + * @access public + */ + function writeHeadersTo(&$socket) { + $socket->write("Content-Length: " . (integer)strlen($this->encode()) . "\r\n"); + $socket->write("Content-Type: multipart/form-data, boundary=" . $this->boundary . "\r\n"); + } + + /** + * Dispatches the form data down the socket. + * @param SimpleSocket $socket Socket to write to. + * @access public + */ + function writeTo(&$socket) { + $socket->write($this->encode()); + } + + /** + * Renders the query string as a URL encoded + * request part. + * @return string Part of URL. + * @access public + */ + function encode() { + $stream = ''; + foreach ($this->getAll() as $pair) { + $stream .= "--" . $this->boundary . "\r\n"; + $stream .= $pair->asMime() . "\r\n"; + } + $stream .= "--" . $this->boundary . "--\r\n"; + return $stream; + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/errors.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/errors.php new file mode 100644 index 0000000..52385ee --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/errors.php @@ -0,0 +1,257 @@ +createErrorQueue(); + set_error_handler('SimpleTestErrorHandler'); + parent::invoke($method); + restore_error_handler(); + $queue->tally(); + } + + /** + * Wires up the error queue for a single test. + * @return SimpleErrorQueue Queue connected to the test. + * @access private + */ + protected function createErrorQueue() { + $context = SimpleTest::getContext(); + $test = $this->getTestCase(); + $queue = $context->get('SimpleErrorQueue'); + $queue->setTestCase($test); + return $queue; + } +} + +/** + * Error queue used to record trapped + * errors. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleErrorQueue { + private $queue; + private $expectation_queue; + private $test; + private $using_expect_style = false; + + /** + * Starts with an empty queue. + */ + function __construct() { + $this->clear(); + } + + /** + * Discards the contents of the error queue. + * @access public + */ + function clear() { + $this->queue = array(); + $this->expectation_queue = array(); + } + + /** + * Sets the currently running test case. + * @param SimpleTestCase $test Test case to send messages to. + * @access public + */ + function setTestCase($test) { + $this->test = $test; + } + + /** + * Sets up an expectation of an error. If this is + * not fulfilled at the end of the test, a failure + * will occour. If the error does happen, then this + * will cancel it out and send a pass message. + * @param SimpleExpectation $expected Expected error match. + * @param string $message Message to display. + * @access public + */ + function expectError($expected, $message) { + array_push($this->expectation_queue, array($expected, $message)); + } + + /** + * Adds an error to the front of the queue. + * @param integer $severity PHP error code. + * @param string $content Text of error. + * @param string $filename File error occoured in. + * @param integer $line Line number of error. + * @access public + */ + function add($severity, $content, $filename, $line) { + $content = str_replace('%', '%%', $content); + $this->testLatestError($severity, $content, $filename, $line); + } + + /** + * Any errors still in the queue are sent to the test + * case. Any unfulfilled expectations trigger failures. + * @access public + */ + function tally() { + while (list($severity, $message, $file, $line) = $this->extract()) { + $severity = $this->getSeverityAsString($severity); + $this->test->error($severity, $message, $file, $line); + } + while (list($expected, $message) = $this->extractExpectation()) { + $this->test->assert($expected, false, "%s -> Expected error not caught"); + } + } + + /** + * Tests the error against the most recent expected + * error. + * @param integer $severity PHP error code. + * @param string $content Text of error. + * @param string $filename File error occoured in. + * @param integer $line Line number of error. + * @access private + */ + protected function testLatestError($severity, $content, $filename, $line) { + if ($expectation = $this->extractExpectation()) { + list($expected, $message) = $expectation; + $this->test->assert($expected, $content, sprintf( + $message, + "%s -> PHP error [$content] severity [" . + $this->getSeverityAsString($severity) . + "] in [$filename] line [$line]")); + } else { + $this->test->error($severity, $content, $filename, $line); + } + } + + /** + * Pulls the earliest error from the queue. + * @return mixed False if none, or a list of error + * information. Elements are: severity + * as the PHP error code, the error message, + * the file with the error, the line number + * and a list of PHP super global arrays. + * @access public + */ + function extract() { + if (count($this->queue)) { + return array_shift($this->queue); + } + return false; + } + + /** + * Pulls the earliest expectation from the queue. + * @return SimpleExpectation False if none. + * @access private + */ + protected function extractExpectation() { + if (count($this->expectation_queue)) { + return array_shift($this->expectation_queue); + } + return false; + } + + /** + * Converts an error code into it's string + * representation. + * @param $severity PHP integer error code. + * @return String version of error code. + * @access public + */ + static function getSeverityAsString($severity) { + static $map = array( + E_STRICT => 'E_STRICT', + E_ERROR => 'E_ERROR', + E_WARNING => 'E_WARNING', + E_PARSE => 'E_PARSE', + E_NOTICE => 'E_NOTICE', + E_CORE_ERROR => 'E_CORE_ERROR', + E_CORE_WARNING => 'E_CORE_WARNING', + E_COMPILE_ERROR => 'E_COMPILE_ERROR', + E_COMPILE_WARNING => 'E_COMPILE_WARNING', + E_USER_ERROR => 'E_USER_ERROR', + E_USER_WARNING => 'E_USER_WARNING', + E_USER_NOTICE => 'E_USER_NOTICE'); + if (defined('E_RECOVERABLE_ERROR')) { + $map[E_RECOVERABLE_ERROR] = 'E_RECOVERABLE_ERROR'; + } + if (defined('E_DEPRECATED')) { + $map[E_DEPRECATED] = 'E_DEPRECATED'; + } + return $map[$severity]; + } +} + +/** + * Error handler that simply stashes any errors into the global + * error queue. Simulates the existing behaviour with respect to + * logging errors, but this feature may be removed in future. + * @param $severity PHP error code. + * @param $message Text of error. + * @param $filename File error occoured in. + * @param $line Line number of error. + * @param $super_globals Hash of PHP super global arrays. + * @access public + */ +function SimpleTestErrorHandler($severity, $message, $filename = null, $line = null, $super_globals = null, $mask = null) { + $severity = $severity & error_reporting(); + if ($severity) { + restore_error_handler(); + if (IsNotCausedBySimpleTest($message)) { + if (ini_get('log_errors')) { + $label = SimpleErrorQueue::getSeverityAsString($severity); + error_log("$label: $message in $filename on line $line"); + } + $queue = SimpleTest::getContext()->get('SimpleErrorQueue'); + $queue->add($severity, $message, $filename, $line); + } + set_error_handler('SimpleTestErrorHandler'); + } + return true; +} + +/** + * Certain messages can be caused by the unit tester itself. + * These have to be filtered. + * @param string $message Message to filter. + * @return boolean True if genuine failure. + */ +function IsNotCausedBySimpleTest($message) { + return ! preg_match('/returned by reference/', $message); +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/exceptions.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/exceptions.php new file mode 100644 index 0000000..984015d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/exceptions.php @@ -0,0 +1,198 @@ +get('SimpleExceptionTrap'); + $trap->clear(); + try { + $has_thrown = false; + parent::invoke($method); + } catch (Exception $exception) { + $has_thrown = true; + if (! $trap->isExpected($this->getTestCase(), $exception)) { + $this->getTestCase()->exception($exception); + } + $trap->clear(); + } + if ($message = $trap->getOutstanding()) { + $this->getTestCase()->fail($message); + } + if ($has_thrown) { + try { + parent::getTestCase()->tearDown(); + } catch (Exception $e) { } + } + } +} + +/** + * Tests exceptions either by type or the exact + * exception. This could be improved to accept + * a pattern expectation to test the error + * message, but that will have to come later. + * @package SimpleTest + * @subpackage UnitTester + */ +class ExceptionExpectation extends SimpleExpectation { + private $expected; + + /** + * Sets up the conditions to test against. + * If the expected value is a string, then + * it will act as a test of the class name. + * An exception as the comparison will + * trigger an identical match. Writing this + * down now makes it look doubly dumb. I hope + * come up with a better scheme later. + * @param mixed $expected A class name or an actual + * exception to compare with. + * @param string $message Message to display. + */ + function __construct($expected, $message = '%s') { + $this->expected = $expected; + parent::__construct($message); + } + + /** + * Carry out the test. + * @param Exception $compare Value to check. + * @return boolean True if matched. + */ + function test($compare) { + if (is_string($this->expected)) { + return ($compare instanceof $this->expected); + } + if (get_class($compare) != get_class($this->expected)) { + return false; + } + return $compare->getMessage() == $this->expected->getMessage(); + } + + /** + * Create the message to display describing the test. + * @param Exception $compare Exception to match. + * @return string Final message. + */ + function testMessage($compare) { + if (is_string($this->expected)) { + return "Exception [" . $this->describeException($compare) . + "] should be type [" . $this->expected . "]"; + } + return "Exception [" . $this->describeException($compare) . + "] should match [" . + $this->describeException($this->expected) . "]"; + } + + /** + * Summary of an Exception object. + * @param Exception $compare Exception to describe. + * @return string Text description. + */ + protected function describeException($exception) { + return get_class($exception) . ": " . $exception->getMessage(); + } +} + +/** + * Stores expected exceptions for when they + * get thrown. Saves the irritating try...catch + * block. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleExceptionTrap { + private $expected; + private $message; + + /** + * Clears down the queue ready for action. + */ + function __construct() { + $this->clear(); + } + + /** + * Sets up an expectation of an exception. + * This has the effect of intercepting an + * exception that matches. + * @param SimpleExpectation $expected Expected exception to match. + * @param string $message Message to display. + * @access public + */ + function expectException($expected = false, $message = '%s') { + if ($expected === false) { + $expected = new AnythingExpectation(); + } + if (! SimpleExpectation::isExpectation($expected)) { + $expected = new ExceptionExpectation($expected); + } + $this->expected = $expected; + $this->message = $message; + } + + /** + * Compares the expected exception with any + * in the queue. Issues a pass or fail and + * returns the state of the test. + * @param SimpleTestCase $test Test case to send messages to. + * @param Exception $exception Exception to compare. + * @return boolean False on no match. + */ + function isExpected($test, $exception) { + if ($this->expected) { + return $test->assert($this->expected, $exception, $this->message); + } + return false; + } + + /** + * Tests for any left over exception. + * @return string/false The failure message or false if none. + */ + function getOutstanding() { + return sprintf($this->message, 'Failed to trap exception'); + } + + /** + * Discards the contents of the error queue. + */ + function clear() { + $this->expected = false; + $this->message = false; + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/expectation.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/expectation.php new file mode 100644 index 0000000..97daa97 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/expectation.php @@ -0,0 +1,901 @@ +message = $message; + } + + /** + * Tests the expectation. True if correct. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + * @abstract + */ + function test($compare) { + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + * @abstract + */ + function testMessage($compare) { + } + + /** + * Overlays the generated message onto the stored user + * message. An additional message can be interjected. + * @param mixed $compare Comparison value. + * @param SimpleDumper $dumper For formatting the results. + * @return string Description of success + * or failure. + * @access public + */ + function overlayMessage($compare, $dumper) { + $this->dumper = $dumper; + return sprintf($this->message, $this->testMessage($compare)); + } + + /** + * Accessor for the dumper. + * @return SimpleDumper Current value dumper. + * @access protected + */ + protected function getDumper() { + if (! $this->dumper) { + $dumper = new SimpleDumper(); + return $dumper; + } + return $this->dumper; + } + + /** + * Test to see if a value is an expectation object. + * A useful utility method. + * @param mixed $expectation Hopefully an Expectation + * class. + * @return boolean True if descended from + * this class. + * @access public + */ + static function isExpectation($expectation) { + return is_object($expectation) && + SimpleTestCompatibility::isA($expectation, 'SimpleExpectation'); + } +} + +/** + * A wildcard expectation always matches. + * @package SimpleTest + * @subpackage MockObjects + */ +class AnythingExpectation extends SimpleExpectation { + + /** + * Tests the expectation. Always true. + * @param mixed $compare Ignored. + * @return boolean True. + * @access public + */ + function test($compare) { + return true; + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + return 'Anything always matches [' . $dumper->describeValue($compare) . ']'; + } +} + +/** + * An expectation that never matches. + * @package SimpleTest + * @subpackage MockObjects + */ +class FailedExpectation extends SimpleExpectation { + + /** + * Tests the expectation. Always false. + * @param mixed $compare Ignored. + * @return boolean True. + * @access public + */ + function test($compare) { + return false; + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + return 'Failed expectation never matches [' . $dumper->describeValue($compare) . ']'; + } +} + +/** + * An expectation that passes on boolean true. + * @package SimpleTest + * @subpackage MockObjects + */ +class TrueExpectation extends SimpleExpectation { + + /** + * Tests the expectation. + * @param mixed $compare Should be true. + * @return boolean True on match. + * @access public + */ + function test($compare) { + return (boolean)$compare; + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + return 'Expected true, got [' . $dumper->describeValue($compare) . ']'; + } +} + +/** + * An expectation that passes on boolean false. + * @package SimpleTest + * @subpackage MockObjects + */ +class FalseExpectation extends SimpleExpectation { + + /** + * Tests the expectation. + * @param mixed $compare Should be false. + * @return boolean True on match. + * @access public + */ + function test($compare) { + return ! (boolean)$compare; + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + return 'Expected false, got [' . $dumper->describeValue($compare) . ']'; + } +} + +/** + * Test for equality. + * @package SimpleTest + * @subpackage UnitTester + */ +class EqualExpectation extends SimpleExpectation { + private $value; + + /** + * Sets the value to compare against. + * @param mixed $value Test value to match. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($value, $message = '%s') { + parent::__construct($message); + $this->value = $value; + } + + /** + * Tests the expectation. True if it matches the + * held value. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return (($this->value == $compare) && ($compare == $this->value)); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if ($this->test($compare)) { + return "Equal expectation [" . $this->dumper->describeValue($this->value) . "]"; + } else { + return "Equal expectation fails " . + $this->dumper->describeDifference($this->value, $compare); + } + } + + /** + * Accessor for comparison value. + * @return mixed Held value to compare with. + * @access protected + */ + protected function getValue() { + return $this->value; + } +} + +/** + * Test for inequality. + * @package SimpleTest + * @subpackage UnitTester + */ +class NotEqualExpectation extends EqualExpectation { + + /** + * Sets the value to compare against. + * @param mixed $value Test value to match. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($value, $message = '%s') { + parent::__construct($value, $message); + } + + /** + * Tests the expectation. True if it differs from the + * held value. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return ! parent::test($compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + if ($this->test($compare)) { + return "Not equal expectation passes " . + $dumper->describeDifference($this->getValue(), $compare); + } else { + return "Not equal expectation fails [" . + $dumper->describeValue($this->getValue()) . + "] matches"; + } + } +} + +/** + * Test for being within a range. + * @package SimpleTest + * @subpackage UnitTester + */ +class WithinMarginExpectation extends SimpleExpectation { + private $upper; + private $lower; + + /** + * Sets the value to compare against and the fuzziness of + * the match. Used for comparing floating point values. + * @param mixed $value Test value to match. + * @param mixed $margin Fuzziness of match. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($value, $margin, $message = '%s') { + parent::__construct($message); + $this->upper = $value + $margin; + $this->lower = $value - $margin; + } + + /** + * Tests the expectation. True if it matches the + * held value. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return (($compare <= $this->upper) && ($compare >= $this->lower)); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if ($this->test($compare)) { + return $this->withinMessage($compare); + } else { + return $this->outsideMessage($compare); + } + } + + /** + * Creates a the message for being within the range. + * @param mixed $compare Value being tested. + * @access private + */ + protected function withinMessage($compare) { + return "Within expectation [" . $this->dumper->describeValue($this->lower) . "] and [" . + $this->dumper->describeValue($this->upper) . "]"; + } + + /** + * Creates a the message for being within the range. + * @param mixed $compare Value being tested. + * @access private + */ + protected function outsideMessage($compare) { + if ($compare > $this->upper) { + return "Outside expectation " . + $this->dumper->describeDifference($compare, $this->upper); + } else { + return "Outside expectation " . + $this->dumper->describeDifference($compare, $this->lower); + } + } +} + +/** + * Test for being outside of a range. + * @package SimpleTest + * @subpackage UnitTester + */ +class OutsideMarginExpectation extends WithinMarginExpectation { + + /** + * Sets the value to compare against and the fuzziness of + * the match. Used for comparing floating point values. + * @param mixed $value Test value to not match. + * @param mixed $margin Fuzziness of match. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($value, $margin, $message = '%s') { + parent::__construct($value, $margin, $message); + } + + /** + * Tests the expectation. True if it matches the + * held value. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return ! parent::test($compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if (! $this->test($compare)) { + return $this->withinMessage($compare); + } else { + return $this->outsideMessage($compare); + } + } +} + +/** + * Test for reference. + * @package SimpleTest + * @subpackage UnitTester + */ +class ReferenceExpectation { + private $value; + + /** + * Sets the reference value to compare against. + * @param mixed $value Test reference to match. + * @param string $message Customised message on failure. + * @access public + */ + function __construct(&$value, $message = '%s') { + $this->message = $message; + $this->value = &$value; + } + + /** + * Tests the expectation. True if it exactly + * references the held value. + * @param mixed $compare Comparison reference. + * @return boolean True if correct. + * @access public + */ + function test(&$compare) { + return SimpleTestCompatibility::isReference($this->value, $compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if ($this->test($compare)) { + return "Reference expectation [" . $this->dumper->describeValue($this->value) . "]"; + } else { + return "Reference expectation fails " . + $this->dumper->describeDifference($this->value, $compare); + } + } + + /** + * Overlays the generated message onto the stored user + * message. An additional message can be interjected. + * @param mixed $compare Comparison value. + * @param SimpleDumper $dumper For formatting the results. + * @return string Description of success + * or failure. + * @access public + */ + function overlayMessage($compare, $dumper) { + $this->dumper = $dumper; + return sprintf($this->message, $this->testMessage($compare)); + } + + /** + * Accessor for the dumper. + * @return SimpleDumper Current value dumper. + * @access protected + */ + protected function getDumper() { + if (! $this->dumper) { + $dumper = new SimpleDumper(); + return $dumper; + } + return $this->dumper; + } +} + +/** + * Test for identity. + * @package SimpleTest + * @subpackage UnitTester + */ +class IdenticalExpectation extends EqualExpectation { + + /** + * Sets the value to compare against. + * @param mixed $value Test value to match. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($value, $message = '%s') { + parent::__construct($value, $message); + } + + /** + * Tests the expectation. True if it exactly + * matches the held value. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return SimpleTestCompatibility::isIdentical($this->getValue(), $compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + if ($this->test($compare)) { + return "Identical expectation [" . $dumper->describeValue($this->getValue()) . "]"; + } else { + return "Identical expectation [" . $dumper->describeValue($this->getValue()) . + "] fails with [" . + $dumper->describeValue($compare) . "] " . + $dumper->describeDifference($this->getValue(), $compare, TYPE_MATTERS); + } + } +} + +/** + * Test for non-identity. + * @package SimpleTest + * @subpackage UnitTester + */ +class NotIdenticalExpectation extends IdenticalExpectation { + + /** + * Sets the value to compare against. + * @param mixed $value Test value to match. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($value, $message = '%s') { + parent::__construct($value, $message); + } + + /** + * Tests the expectation. True if it differs from the + * held value. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return ! parent::test($compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + if ($this->test($compare)) { + return "Not identical expectation passes " . + $dumper->describeDifference($this->getValue(), $compare, TYPE_MATTERS); + } else { + return "Not identical expectation [" . $dumper->describeValue($this->getValue()) . "] matches"; + } + } +} + +/** + * Test for a pattern using Perl regex rules. + * @package SimpleTest + * @subpackage UnitTester + */ +class PatternExpectation extends SimpleExpectation { + private $pattern; + + /** + * Sets the value to compare against. + * @param string $pattern Pattern to search for. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($pattern, $message = '%s') { + parent::__construct($message); + $this->pattern = $pattern; + } + + /** + * Accessor for the pattern. + * @return string Perl regex as string. + * @access protected + */ + protected function getPattern() { + return $this->pattern; + } + + /** + * Tests the expectation. True if the Perl regex + * matches the comparison value. + * @param string $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return (boolean)preg_match($this->getPattern(), $compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if ($this->test($compare)) { + return $this->describePatternMatch($this->getPattern(), $compare); + } else { + $dumper = $this->getDumper(); + return "Pattern [" . $this->getPattern() . + "] not detected in [" . + $dumper->describeValue($compare) . "]"; + } + } + + /** + * Describes a pattern match including the string + * found and it's position. + * @param string $pattern Regex to match against. + * @param string $subject Subject to search. + * @access protected + */ + protected function describePatternMatch($pattern, $subject) { + preg_match($pattern, $subject, $matches); + $position = strpos($subject, $matches[0]); + $dumper = $this->getDumper(); + return "Pattern [$pattern] detected at character [$position] in [" . + $dumper->describeValue($subject) . "] as [" . + $matches[0] . "] in region [" . + $dumper->clipString($subject, 100, $position) . "]"; + } +} + +/** + * Fail if a pattern is detected within the + * comparison. + * @package SimpleTest + * @subpackage UnitTester + */ +class NoPatternExpectation extends PatternExpectation { + + /** + * Sets the reject pattern + * @param string $pattern Pattern to search for. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($pattern, $message = '%s') { + parent::__construct($pattern, $message); + } + + /** + * Tests the expectation. False if the Perl regex + * matches the comparison value. + * @param string $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return ! parent::test($compare); + } + + /** + * Returns a human readable test message. + * @param string $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if ($this->test($compare)) { + $dumper = $this->getDumper(); + return "Pattern [" . $this->getPattern() . + "] not detected in [" . + $dumper->describeValue($compare) . "]"; + } else { + return $this->describePatternMatch($this->getPattern(), $compare); + } + } +} + +/** + * Tests either type or class name if it's an object. + * @package SimpleTest + * @subpackage UnitTester + */ +class IsAExpectation extends SimpleExpectation { + private $type; + + /** + * Sets the type to compare with. + * @param string $type Type or class name. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($type, $message = '%s') { + parent::__construct($message); + $this->type = $type; + } + + /** + * Accessor for type to check against. + * @return string Type or class name. + * @access protected + */ + protected function getType() { + return $this->type; + } + + /** + * Tests the expectation. True if the type or + * class matches the string value. + * @param string $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + if (is_object($compare)) { + return SimpleTestCompatibility::isA($compare, $this->type); + } else { + return (strtolower(gettype($compare)) == $this->canonicalType($this->type)); + } + } + + /** + * Coerces type name into a gettype() match. + * @param string $type User type. + * @return string Simpler type. + * @access private + */ + protected function canonicalType($type) { + $type = strtolower($type); + $map = array( + 'bool' => 'boolean', + 'float' => 'double', + 'real' => 'double', + 'int' => 'integer'); + if (isset($map[$type])) { + $type = $map[$type]; + } + return $type; + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + return "Value [" . $dumper->describeValue($compare) . + "] should be type [" . $this->type . "]"; + } +} + +/** + * Tests either type or class name if it's an object. + * Will succeed if the type does not match. + * @package SimpleTest + * @subpackage UnitTester + */ +class NotAExpectation extends IsAExpectation { + private $type; + + /** + * Sets the type to compare with. + * @param string $type Type or class name. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($type, $message = '%s') { + parent::__construct($type, $message); + } + + /** + * Tests the expectation. False if the type or + * class matches the string value. + * @param string $compare Comparison value. + * @return boolean True if different. + * @access public + */ + function test($compare) { + return ! parent::test($compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + return "Value [" . $dumper->describeValue($compare) . + "] should not be type [" . $this->getType() . "]"; + } +} + +/** + * Tests for existance of a method in an object + * @package SimpleTest + * @subpackage UnitTester + */ +class MethodExistsExpectation extends SimpleExpectation { + private $method; + + /** + * Sets the value to compare against. + * @param string $method Method to check. + * @param string $message Customised message on failure. + * @access public + * @return void + */ + function __construct($method, $message = '%s') { + parent::__construct($message); + $this->method = &$method; + } + + /** + * Tests the expectation. True if the method exists in the test object. + * @param string $compare Comparison method name. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return (boolean)(is_object($compare) && method_exists($compare, $this->method)); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + if (! is_object($compare)) { + return 'No method on non-object [' . $dumper->describeValue($compare) . ']'; + } + $method = $this->method; + return "Object [" . $dumper->describeValue($compare) . + "] should contain method [$method]"; + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/colortext_reporter.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/colortext_reporter.php new file mode 100644 index 0000000..a265226 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/colortext_reporter.php @@ -0,0 +1,86 @@ + + * @package SimpleTest + * @subpackage Extensions + */ +class ColorTextReporter extends TextReporter { + var $_failColor = 41; + var $_passColor = 42; + + /** + * Handle initialization + * + * @param {@link TextReporter} + */ + function __construct() { + parent::__construct(); + } + + /** + * Capture the attempt to display the final test results and insert the + * ANSI-color codes in place. + * + * @param string + * @see TextReporter + * @access public + */ + function paintFooter($test_name) { + ob_start(); + parent::paintFooter($test_name); + $output = trim(ob_get_clean()); + if ($output) { + if (($this->getFailCount() + $this->getExceptionCount()) == 0) { + $color = $this->_passColor; + } else { + $color = $this->_failColor; + } + + $this->_setColor($color); + echo $output; + $this->_resetColor(); + } + } + + + /** + * Sets the terminal to an ANSI-standard $color + * + * @param int + * @access protected + */ + function _setColor($color) { + printf("%s[%sm\n", chr(27), $color); + } + + + /** + * Resets the color back to normal. + * + * @access protected + */ + function _resetColor() { + $this->_setColor(0); + } +} + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/css/webunit.css b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/css/webunit.css new file mode 100644 index 0000000..49da468 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/css/webunit.css @@ -0,0 +1,78 @@ +html { + font-face: Verdana, Arial, Helvetica, sans-serif; + font-weight: bold; + color: black; + } + +.pass { color: green; } +.fail { color: red; } +.activetab { + position: relative; + background: white; + border-color: black; + border-style: solid; + border-weight: 1; + border-top-color: white; + border-style: solid; + border-weight: 1; +} +.inactivetab{ + position: relative; + background: silver; + border-color: black; + border-style: solid; + border-weight: 1; +} +span.inactivetab a:link , +span.activetab a:link { + text-decoration: none; + color: black; +} +pre { background-color: lightgray; } +#wait { + background: #B7B8DD; + position: absolute; + visibility: hidden; + text-align: center; + border-color: blue; + border-style: solid; + border-weight: 2; + } +#webunit { + position: absolute; + visibility: hidden; + background: silver; + border-color: black; + border-style: solid; + border-weight: 2; + } +#visible_tab { + position: relative; + visibility: hidden; + overflow: auto; + background: white; + width: 100%; + height: 100%; + border-color: black; + border-style: solid; + border-weight: 1; + } +#visible_tab a:link { + text-decoration: none; + color: inherit; +} +#msg { + position: absolute; + visibility: hidden; + overflow: auto; + background: white; + border-color: black; + border-style: solid; + border-weight: 1; + } +#fail, +#tree { + position: absolute; + visibility: hidden; +} + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester.php new file mode 100644 index 0000000..72b6a51 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester.php @@ -0,0 +1,117 @@ + + * @version $Id: dom_tester.php 1804 2008-09-08 13:16:44Z pp11 $ + */ + +/**#@+ + * include SimpleTest files + */ +require_once dirname(__FILE__).'/../web_tester.php'; +require_once dirname(__FILE__).'/dom_tester/css_selector.php'; +/**#@-*/ + +/** + * CssSelectorExpectation + * + * Create a CSS Selector expectactation + * + * @param DomDocument $_dom + * @param string $_selector + * @param array $_value + * + */ +class CssSelectorExpectation extends SimpleExpectation { + var $_dom; + var $_selector; + var $_value; + + /** + * Sets the dom tree and the css selector to compare against + * @param mixed $dom Dom tree to search into. + * @param mixed $selector Css selector to match element. + * @param string $message Customised message on failure. + * @access public + */ + function CssSelectorExpectation($dom, $selector, $message = '%s') { + $this->SimpleExpectation($message); + $this->_dom = $dom; + $this->_selector = $selector; + + $css_selector = new CssSelector($this->_dom); + $this->_value = $css_selector->getTexts($this->_selector); + } + + /** + * Tests the expectation. True if it matches the + * held value. + * @param mixed $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return (($this->_value == $compare) && ($compare == $this->_value)); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = &$this->_getDumper(); + if (is_array($compare)) { + sort($compare); + } + if ($this->test($compare)) { + return "CSS selector expectation [" . $dumper->describeValue($this->_value) . "]". + " using [" . $dumper->describeValue($this->_selector) . "]"; + } else { + return "CSS selector expectation [" . $dumper->describeValue($this->_value) . "]". + " using [" . $dumper->describeValue($this->_selector) . "]". + " fails with [" . + $dumper->describeValue($compare) . "] " . + $dumper->describeDifference($this->_value, $compare); + } + } +} + +/** + * DomTestCase + * + * Extend Web test case with DOM related assertions, + * CSS selectors in particular + * + * @param DomDocument $dom + * + */ +class DomTestCase extends WebTestCase { + var $dom; + + function assertElementsBySelector($selector, $elements, $message = '%s') { + $this->dom = new DomDocument('1.0', 'utf-8'); + $this->dom->validateOnParse = true; + $this->dom->loadHTML($this->_browser->getContent()); + + return $this->assert( + new CssSelectorExpectation($this->dom, $selector), + $elements, + $message); + } + + function getElementsBySelector($selector) { + $this->dom = new DomDocument('1.0', 'utf-8'); + $this->dom->validateOnParse = true; + $this->dom->loadHTML($this->_browser->getContent()); + + $css_selector = new CssSelectorExpectation($this->dom, $selector); + return $css_selector->_value; + } +} + +?> diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester/css_selector.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester/css_selector.php new file mode 100644 index 0000000..1e9e244 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester/css_selector.php @@ -0,0 +1,539 @@ + + * @version $Id: css_selector.php 1802 2008-09-08 10:43:58Z maetl_ $ + */ + +/** + * CssSelector + * + * Allow to navigate a DOM with CSS selector. + * + * based on getElementsBySelector version 0.4 - Simon Willison, 2003-03-25 + * http://simon.incutio.com/archive/2003/03/25/getElementsBySelector + * + * derived from sfDomCssSelector Id 3053 - Fabien Potencier, 2006-12-16 + * http://www.symfony-project.com/api/symfony/util/sfDomCssSelector.html + * + * @package SimpleTest + * @subpackage Extensions + * @param DomDocument $dom + */ +class CssSelector +{ + public $nodes = array(); + + public function __construct($nodes) + { + if (!is_array($nodes)) + { + $nodes = array($nodes); + } + + $this->nodes = $nodes; + } + + public function getNodes() + { + return $this->nodes; + } + + public function getNode() + { + return $this->nodes ? $this->nodes[0] : null; + } + + public function getValue() + { + return $this->nodes[0]->nodeValue; + } + + public function getValues() + { + $values = array(); + foreach ($this->nodes as $node) + { + $values[] = $node->nodeValue; + } + + return $values; + } + + public function matchSingle($selector) + { + $nodes = $this->getElements($selector); + + return $nodes ? new CssSelector($nodes[0]) : new CssSelector(array()); + } + + public function matchAll($selector) + { + $nodes = $this->getElements($selector); + + return $nodes ? new CssSelector($nodes) : new CssSelector(array()); + } + + /* DEPRECATED */ + public function getTexts($selector) + { + $texts = array(); + foreach ($this->getElements($selector) as $element) + { + $texts[] = $element->nodeValue; + } + + return $texts; + } + + /* DEPRECATED */ + public function getElements($selector) + { + $nodes = array(); + foreach ($this->nodes as $node) + { + $result_nodes = $this->getElementsForNode($selector, $node); + if ($result_nodes) + { + $nodes = array_merge($nodes, $result_nodes); + } + } + + foreach ($nodes as $node) + { + $node->removeAttribute('sf_matched'); + } + + return $nodes; + } + + protected function getElementsForNode($selector, $root_node) + { + $all_nodes = array(); + foreach ($this->tokenize_selectors($selector) as $selector) + { + $nodes = array($root_node); + foreach ($this->tokenize($selector) as $token) + { + $combinator = $token['combinator']; + $selector = $token['selector']; + + $token = trim($token['name']); + + $pos = strpos($token, '#'); + if (false !== $pos && preg_match('/^[A-Za-z0-9]*$/', substr($token, 0, $pos))) + { + // Token is an ID selector + $tagName = substr($token, 0, $pos); + $id = substr($token, $pos + 1); + $xpath = new DomXPath($root_node); + $element = $xpath->query(sprintf("//*[@id = '%s']", $id))->item(0); + if (!$element || ($tagName && strtolower($element->nodeName) != $tagName)) + { + // tag with that ID not found + return array(); + } + + // Set nodes to contain just this element + $nodes = array($element); + $nodes = $this->matchCustomSelector($nodes, $selector); + + continue; // Skip to next token + } + + $pos = strpos($token, '.'); + if (false !== $pos && preg_match('/^[A-Za-z0-9\*]*$/', substr($token, 0, $pos))) + { + // Token contains a class selector + $tagName = substr($token, 0, $pos); + if (!$tagName) + { + $tagName = '*'; + } + $className = substr($token, $pos + 1); + + // Get elements matching tag, filter them for class selector + $founds = $this->getElementsByTagName($nodes, $tagName, $combinator); + $nodes = array(); + foreach ($founds as $found) + { + if (preg_match('/\b'.$className.'\b/', $found->getAttribute('class'))) + { + $nodes[] = $found; + } + } + + $nodes = $this->matchCustomSelector($nodes, $selector); + + continue; // Skip to next token + } + + // Code to deal with attribute selectors + if (preg_match('/^(\w+|\*)(\[.+\])$/', $token, $matches)) + { + $tagName = $matches[1] ? $matches[1] : '*'; + preg_match_all('/ + \[ + (\w+) # attribute + ([=~\|\^\$\*]?) # modifier (optional) + =? # equal (optional) + ( + "([^"]*)" # quoted value (optional) + | + ([^\]]*) # non quoted value (optional) + ) + \] + /x', $matches[2], $matches, PREG_SET_ORDER); + + // Grab all of the tagName elements within current node + $founds = $this->getElementsByTagName($nodes, $tagName, $combinator); + $nodes = array(); + foreach ($founds as $found) + { + $ok = false; + foreach ($matches as $match) + { + $attrName = $match[1]; + $attrOperator = $match[2]; + $attrValue = $match[4]; + + switch ($attrOperator) + { + case '=': // Equality + $ok = $found->getAttribute($attrName) == $attrValue; + break; + case '~': // Match one of space seperated words + $ok = preg_match('/\b'.preg_quote($attrValue, '/').'\b/', $found->getAttribute($attrName)); + break; + case '|': // Match start with value followed by optional hyphen + $ok = preg_match('/^'.preg_quote($attrValue, '/').'-?/', $found->getAttribute($attrName)); + break; + case '^': // Match starts with value + $ok = 0 === strpos($found->getAttribute($attrName), $attrValue); + break; + case '$': // Match ends with value + $ok = $attrValue == substr($found->getAttribute($attrName), -strlen($attrValue)); + break; + case '*': // Match ends with value + $ok = false !== strpos($found->getAttribute($attrName), $attrValue); + break; + default : + // Just test for existence of attribute + $ok = $found->hasAttribute($attrName); + } + + if (false == $ok) + { + break; + } + } + + if ($ok) + { + $nodes[] = $found; + } + } + + continue; // Skip to next token + } + + // If we get here, token is JUST an element (not a class or ID selector) + $nodes = $this->getElementsByTagName($nodes, $token, $combinator); + + $nodes = $this->matchCustomSelector($nodes, $selector); + } + + foreach ($nodes as $node) + { + if (!$node->getAttribute('sf_matched')) + { + $node->setAttribute('sf_matched', true); + $all_nodes[] = $node; + } + } + } + + return $all_nodes; + } + + protected function getElementsByTagName($nodes, $tagName, $combinator = ' ') + { + $founds = array(); + foreach ($nodes as $node) + { + switch ($combinator) + { + case ' ': + // Descendant selector + foreach ($node->getElementsByTagName($tagName) as $element) + { + $founds[] = $element; + } + break; + case '>': + // Child selector + foreach ($node->childNodes as $element) + { + if ($tagName == $element->nodeName) + { + $founds[] = $element; + } + } + break; + case '+': + // Adjacent selector + $element = $node->nextSibling; + if ($element && '#text' == $element->nodeName) + { + $element = $element->nextSibling; + } + + if ($element && $tagName == $element->nodeName) + { + $founds[] = $element; + } + break; + default: + throw new Exception(sprintf('Unrecognized combinator "%s".', $combinator)); + } + } + + return $founds; + } + + protected function tokenize_selectors($selector) + { + // split tokens by , except in an attribute selector + $tokens = array(); + $quoted = false; + $token = ''; + for ($i = 0, $max = strlen($selector); $i < $max; $i++) + { + if (',' == $selector[$i] && !$quoted) + { + $tokens[] = trim($token); + $token = ''; + } + else if ('"' == $selector[$i]) + { + $token .= $selector[$i]; + $quoted = $quoted ? false : true; + } + else + { + $token .= $selector[$i]; + } + } + if ($token) + { + $tokens[] = trim($token); + } + + return $tokens; + } + + protected function tokenize($selector) + { + // split tokens by space except if space is in an attribute selector + $tokens = array(); + $combinators = array(' ', '>', '+'); + $quoted = false; + $token = array('combinator' => ' ', 'name' => ''); + for ($i = 0, $max = strlen($selector); $i < $max; $i++) + { + if (in_array($selector[$i], $combinators) && !$quoted) + { + // remove all whitespaces around the combinator + $combinator = $selector[$i]; + while (in_array($selector[$i + 1], $combinators)) + { + if (' ' != $selector[++$i]) + { + $combinator = $selector[$i]; + } + } + + $tokens[] = $token; + $token = array('combinator' => $combinator, 'name' => ''); + } + else if ('"' == $selector[$i]) + { + $token['name'] .= $selector[$i]; + $quoted = $quoted ? false : true; + } + else + { + $token['name'] .= $selector[$i]; + } + } + if ($token['name']) + { + $tokens[] = $token; + } + + foreach ($tokens as &$token) + { + list($token['name'], $token['selector']) = $this->tokenize_selector_name($token['name']); + } + + return $tokens; + } + + protected function tokenize_selector_name($token_name) + { + // split custom selector + $quoted = false; + $name = ''; + $selector = ''; + $in_selector = false; + for ($i = 0, $max = strlen($token_name); $i < $max; $i++) + { + if ('"' == $token_name[$i]) + { + $quoted = $quoted ? false : true; + } + + if (!$quoted && ':' == $token_name[$i]) + { + $in_selector = true; + } + + if ($in_selector) + { + $selector .= $token_name[$i]; + } + else + { + $name .= $token_name[$i]; + } + } + + return array($name, $selector); + } + + protected function matchCustomSelector($nodes, $selector) + { + if (!$selector) + { + return $nodes; + } + + $selector = $this->tokenize_custom_selector($selector); + $matchingNodes = array(); + for ($i = 0, $max = count($nodes); $i < $max; $i++) + { + switch ($selector['selector']) + { + case 'contains': + if (false !== strpos($nodes[$i]->textContent, $selector['parameter'])) + { + $matchingNodes[] = $nodes[$i]; + } + break; + case 'nth-child': + if ($nodes[$i] === $this->nth($nodes[$i]->parentNode->firstChild, (integer) $selector['parameter'])) + { + $matchingNodes[] = $nodes[$i]; + } + break; + case 'first-child': + if ($nodes[$i] === $this->nth($nodes[$i]->parentNode->firstChild)) + { + $matchingNodes[] = $nodes[$i]; + } + break; + case 'last-child': + if ($nodes[$i] === $this->nth($nodes[$i]->parentNode->lastChild, 1, 'previousSibling')) + { + $matchingNodes[] = $nodes[$i]; + } + break; + case 'lt': + if ($i < (integer) $selector['parameter']) + { + $matchingNodes[] = $nodes[$i]; + } + break; + case 'gt': + if ($i > (integer) $selector['parameter']) + { + $matchingNodes[] = $nodes[$i]; + } + break; + case 'odd': + if ($i % 2) + { + $matchingNodes[] = $nodes[$i]; + } + break; + case 'even': + if (0 == $i % 2) + { + $matchingNodes[] = $nodes[$i]; + } + break; + case 'nth': + case 'eq': + if ($i == (integer) $selector['parameter']) + { + $matchingNodes[] = $nodes[$i]; + } + break; + case 'first': + if ($i == 0) + { + $matchingNodes[] = $nodes[$i]; + } + break; + case 'last': + if ($i == $max - 1) + { + $matchingNodes[] = $nodes[$i]; + } + break; + default: + throw new Exception(sprintf('Unrecognized selector "%s".', $selector['selector'])); + } + } + + return $matchingNodes; + } + + protected function tokenize_custom_selector($selector) + { + if (!preg_match('/ + ([a-zA-Z0-9\-]+) + (?: + \( + (?: + ("|\')(.*)?\2 + | + (.*?) + ) + \) + )? + /x', substr($selector, 1), $matches)) + { + throw new Exception(sprintf('Unable to parse custom selector "%s".', $selector)); + } + return array('selector' => $matches[1], 'parameter' => isset($matches[3]) ? ($matches[3] ? $matches[3] : $matches[4]) : ''); + } + + protected function nth($cur, $result = 1, $dir = 'nextSibling') + { + $num = 0; + for (; $cur; $cur = $cur->$dir) + { + if (1 == $cur->nodeType) + { + ++$num; + } + + if ($num == $result) + { + return $cur; + } + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester/test/dom_tester_doc_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester/test/dom_tester_doc_test.php new file mode 100644 index 0000000..da624bb --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester/test/dom_tester_doc_test.php @@ -0,0 +1,24 @@ +addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + } + + function testGet() { + $url = 'http://simpletest.org/'; + $this->assertTrue($this->get($url)); + $this->assertEqual($this->getUrl(), $url); + $this->assertElementsBySelector('h2', array('Screenshots', 'Documentation', 'Contributing')); + $this->assertElementsBySelector('a[href="http://simpletest.org/api/"]', array('the complete API', 'documented API')); + $this->assertElementsBySelector('div#content > p > strong', array('SimpleTest PHP unit tester')); + } +} + +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester/test/dom_tester_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester/test/dom_tester_test.php new file mode 100644 index 0000000..38f5efd --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester/test/dom_tester_test.php @@ -0,0 +1,223 @@ +addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + } + + function testGet() { + $url = 'file://'.dirname(__FILE__).'/support/dom_tester.html'; + $this->assertTrue($this->get($url)); + $this->assertElementsBySelector('h1', array('Test page')); + $this->assertElementsBySelector('ul#list li a[href]', array('link')); + $this->assertElementsBySelector('body h1', array('Test page')); + $this->assertElementsBySelector('#mybar', array('myfoo bis')); + } +} + +class TestOfCssSelectors extends UnitTestCase { + + function TestOfCssSelectors() { + $html = file_get_contents(dirname(__FILE__) . '/support/dom_tester.html'); + $this->dom = new DomDocument('1.0', 'utf-8'); + $this->dom->validateOnParse = true; + $this->dom->loadHTML($html); + } + + function testBasicSelector() { + $expectation = new CssSelectorExpectation($this->dom, 'h1'); + $this->assertTrue($expectation->test(array('Test page'))); + + $expectation = new CssSelectorExpectation($this->dom, 'h2'); + $this->assertTrue($expectation->test(array('Title 1', 'Title 2'))); + + $expectation = new CssSelectorExpectation($this->dom, '#footer'); + $this->assertTrue($expectation->test(array('footer'))); + + $expectation = new CssSelectorExpectation($this->dom, 'div#footer'); + $this->assertTrue($expectation->test(array('footer'))); + + $expectation = new CssSelectorExpectation($this->dom, '.header'); + $this->assertTrue($expectation->test(array('header'))); + + $expectation = new CssSelectorExpectation($this->dom, 'p.header'); + $this->assertTrue($expectation->test(array('header'))); + + $expectation = new CssSelectorExpectation($this->dom, 'div.header'); + $this->assertTrue($expectation->test(array())); + + $expectation = new CssSelectorExpectation($this->dom, 'ul#mylist ul li'); + $this->assertTrue($expectation->test(array('element 3', 'element 4'))); + + $expectation = new CssSelectorExpectation($this->dom, '#nonexistant'); + $this->assertTrue($expectation->test(array())); + } + + function testAttributeSelectors() { + $expectation = new CssSelectorExpectation($this->dom, 'ul#list li a[href]'); + $this->assertTrue($expectation->test(array('link'))); + + $expectation = new CssSelectorExpectation($this->dom, 'ul#list li a[class~="foo1"]'); + $this->assertTrue($expectation->test(array('link'))); + + $expectation = new CssSelectorExpectation($this->dom, 'ul#list li a[class~="bar1"]'); + $this->assertTrue($expectation->test(array('link'))); + + $expectation = new CssSelectorExpectation($this->dom, 'ul#list li a[class~="foobar1"]'); + $this->assertTrue($expectation->test(array('link'))); + + $expectation = new CssSelectorExpectation($this->dom, 'ul#list li a[class^="foo1"]'); + $this->assertTrue($expectation->test(array('link'))); + + $expectation = new CssSelectorExpectation($this->dom, 'ul#list li a[class$="foobar1"]'); + $this->assertTrue($expectation->test(array('link'))); + + $expectation = new CssSelectorExpectation($this->dom, 'ul#list li a[class*="oba"]'); + $this->assertTrue($expectation->test(array('link'))); + + $expectation = new CssSelectorExpectation($this->dom, 'ul#list li a[href="http://www.google.com/"]'); + $this->assertTrue($expectation->test(array('link'))); + + $expectation = new CssSelectorExpectation($this->dom, 'ul#anotherlist li a[class|="bar1"]'); + $this->assertTrue($expectation->test(array('another link'))); + + $expectation = new CssSelectorExpectation($this->dom, 'ul#list li a[class*="oba"][class*="ba"]'); + $this->assertTrue($expectation->test(array('link'))); + + $expectation = new CssSelectorExpectation($this->dom, 'p[class="myfoo"][id="mybar"]'); + $this->assertTrue($expectation->test(array('myfoo bis'))); + + $expectation = new CssSelectorExpectation($this->dom, 'p[onclick*="a . and a #"]'); + $this->assertTrue($expectation->test(array('works great'))); + } + + function testCombinators() { + $expectation = new CssSelectorExpectation($this->dom, 'body h1'); + $this->assertTrue($expectation->test(array('Test page'))); + + $expectation = new CssSelectorExpectation($this->dom, 'div#combinators > ul > li'); + $this->assertTrue($expectation->test(array('test 1', 'test 2'))); + + $expectation = new CssSelectorExpectation($this->dom, 'div#combinators>ul>li'); + $this->assertTrue($expectation->test(array('test 1', 'test 2'))); + + $expectation = new CssSelectorExpectation($this->dom, 'div#combinators li + li'); + $this->assertTrue($expectation->test(array('test 2', 'test 4'))); + + $expectation = new CssSelectorExpectation($this->dom, 'div#combinators li+li'); + $this->assertTrue($expectation->test(array('test 2', 'test 4'))); + + $expectation = new CssSelectorExpectation($this->dom, 'h1, h2'); + $this->assertTrue($expectation->test(array('Test page', 'Title 1', 'Title 2'))); + + $expectation = new CssSelectorExpectation($this->dom, 'h1,h2'); + $this->assertTrue($expectation->test(array('Test page', 'Title 1', 'Title 2'))); + + $expectation = new CssSelectorExpectation($this->dom, 'h1 , h2'); + $this->assertTrue($expectation->test(array('Test page', 'Title 1', 'Title 2'))); + + $expectation = new CssSelectorExpectation($this->dom, 'h1, h1,h1'); + $this->assertTrue($expectation->test(array('Test page'))); + + $expectation = new CssSelectorExpectation($this->dom, 'h1,h2,h1'); + $this->assertTrue($expectation->test(array('Test page', 'Title 1', 'Title 2'))); + + $expectation = new CssSelectorExpectation($this->dom, 'p[onclick*="a . and a #"], div#combinators > ul li + li'); + $this->assertTrue($expectation->test(array('works great', 'test 2', 'test 4'))); + } + + function testChildSelectors() { + $expectation = new CssSelectorExpectation($this->dom, '.myfoo:contains("bis")'); + $this->assertTrue($expectation->test(array('myfoo bis'))); + + $expectation = new CssSelectorExpectation($this->dom, '.myfoo:eq(1)'); + $this->assertTrue($expectation->test(array('myfoo bis'))); + + $expectation = new CssSelectorExpectation($this->dom, '.myfoo:last'); + $this->assertTrue($expectation->test(array('myfoo bis'))); + + $expectation = new CssSelectorExpectation($this->dom, '.myfoo:first'); + $this->assertTrue($expectation->test(array('myfoo'))); + + $expectation = new CssSelectorExpectation($this->dom, 'h2:first'); + $this->assertTrue($expectation->test(array('Title 1'))); + + $expectation = new CssSelectorExpectation($this->dom, 'h2:first'); + $this->assertTrue($expectation->test(array('Title 1'))); + + $expectation = new CssSelectorExpectation($this->dom, 'p.myfoo:first'); + $this->assertTrue($expectation->test(array('myfoo'))); + + $expectation = new CssSelectorExpectation($this->dom, 'p:lt(2)'); + $this->assertTrue($expectation->test(array('header', 'multi-classes'))); + + $expectation = new CssSelectorExpectation($this->dom, 'p:gt(2)'); + $this->assertTrue($expectation->test(array('myfoo bis', 'works great', 'First paragraph', 'Second paragraph', 'Third paragraph'))); + + $expectation = new CssSelectorExpectation($this->dom, 'p:odd'); + $this->assertTrue($expectation->test(array('multi-classes', 'myfoo bis', 'First paragraph', 'Third paragraph'))); + + $expectation = new CssSelectorExpectation($this->dom, 'p:even'); + $this->assertTrue($expectation->test(array('header', 'myfoo', 'works great', 'Second paragraph'))); + + $expectation = new CssSelectorExpectation($this->dom, '#simplelist li:first-child'); + $this->assertTrue($expectation->test(array('First', 'First'))); + + $expectation = new CssSelectorExpectation($this->dom, '#simplelist li:nth-child(1)'); + $this->assertTrue($expectation->test(array('First', 'First'))); + + $expectation = new CssSelectorExpectation($this->dom, '#simplelist li:nth-child(2)'); + $this->assertTrue($expectation->test(array('Second with a link', 'Second'))); + + $expectation = new CssSelectorExpectation($this->dom, '#simplelist li:nth-child(3)'); + $this->assertTrue($expectation->test(array('Third with another link'))); + + $expectation = new CssSelectorExpectation($this->dom, '#simplelist li:last-child'); + $this->assertTrue($expectation->test(array('Second with a link', 'Third with another link'))); + } +} + +class TestsOfChildAndAdjacentSelectors extends DomTestCase { + function TestsOfChildAndAdjacentSelectors() { + $html = file_get_contents(dirname(__FILE__) . '/support/child_adjacent.html'); + $this->dom = new DomDocument('1.0', 'utf-8'); + $this->dom->validateOnParse = true; + $this->dom->loadHTML($html); + } + + function testFirstChild() { + $expectation = new CssSelectorExpectation($this->dom, 'p:first-child'); + $this->assertTrue($expectation->test(array('First paragraph'))); + + $expectation = new CssSelectorExpectation($this->dom, 'body > p:first-child'); + $this->assertTrue($expectation->test(array('First paragraph'))); + + $expectation = new CssSelectorExpectation($this->dom, 'body > p > a:first-child'); + $this->assertTrue($expectation->test(array('paragraph'))); + } + + function testChildren() { + $expectation = new CssSelectorExpectation($this->dom, 'body > p'); + $this->assertTrue($expectation->test(array('First paragraph', 'Second paragraph', 'Third paragraph'))); + + $expectation = new CssSelectorExpectation($this->dom, 'body > p > a'); + $this->assertTrue($expectation->test(array('paragraph'))); + } + + function testAdjacents() { + $expectation = new CssSelectorExpectation($this->dom, 'p + p'); + $this->assertTrue($expectation->test(array('Second paragraph', 'Third paragraph'))); + + $expectation = new CssSelectorExpectation($this->dom, 'body > p + p'); + $this->assertTrue($expectation->test(array('Second paragraph', 'Third paragraph'))); + + $expectation = new CssSelectorExpectation($this->dom, 'body > p + p > a'); + $this->assertTrue($expectation->test(array('paragraph'))); + } +} + +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester/test/support/child_adjacent.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester/test/support/child_adjacent.html new file mode 100644 index 0000000..51bf72f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester/test/support/child_adjacent.html @@ -0,0 +1,24 @@ + + + Foo! + + + +

    First paragraph

    +

    Second paragraph

    +

    Third paragraph

    + + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester/test/support/dom_tester.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester/test/support/dom_tester.html new file mode 100644 index 0000000..666126e --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester/test/support/dom_tester.html @@ -0,0 +1,59 @@ + + + + +

    Test page

    + +

    Title 1

    +

    header

    +

    multi-classes

    +

    myfoo

    +

    myfoo bis

    + +

    works great

    + +
    +
      +
    • First
    • +
    • Second with a link
    • +
    + + +
    + +

    Title 2

    +
      +
    • element 1
    • +
    • element 2
    • +
    • +
        +
      • element 3
      • +
      • element 4
      • +
      +
    • +
    + +
    +
      +
    • test 1
    • +
    • test 2
    • +
        +
      • test 3
      • +
      • test 4
      • +
      +
    +
    + +
    +

    First paragraph

    +

    Second paragraph

    +

    Third paragraph

    +
    + + + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/img/wait.gif b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/img/wait.gif new file mode 100644 index 0000000..3ec7cec Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/img/wait.gif differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/js/tests/TestOfWebunit.js.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/js/tests/TestOfWebunit.js.html new file mode 100644 index 0000000..08f6249 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/js/tests/TestOfWebunit.js.html @@ -0,0 +1,34 @@ + + + +JsUnit Tests of webunit.js + + + + + + + + +

    JsUnit Tests of webunit.js

    + +

    This page contains tests for the webunit.js file functions.

    +
    +
    + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/js/webunit.js b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/js/webunit.js new file mode 100644 index 0000000..1c6acbb --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/js/webunit.js @@ -0,0 +1,194 @@ +// jsreporter.js +// Script to support JsReporter class +// Relies heavily on the X library in x.js +// X v3.14.1, Cross-Browser DHTML Library from Cross-Browser.com +// Copyright (c) 2004 Jason E. Sweat (jsweat_php@yahoo.com) +// +// SimpleTest - http://simpletest.sf.net/ +// Copyright (c) 2003,2004 Marcus Baker (marcus@lastcraft.com) +// $Id: webunit.js 506 2004-02-14 18:24:13Z jsweat $ + + +// Variables: +min_x=500; +min_y=400; +groups = new Array(); +cases = new Array(); +methods = new Array(); +current_group=0; +current_case=0; +current_method=0; + +Hash = { + Set : function(foo,bar) {this[foo] = bar;}, + Get : function(foo) {return this[foo];} +} + +// Functions: +function wait_start() { + var wait_x; + var wait_y; + + wait_x = xWidth('wait'); + wait_y = xHeight('wait'); + xMoveTo('wait', (xClientWidth()-wait_x)/2, (xClientHeight()-wait_y)/2); + xShow('wait'); +} + +function layout() { + xResizeTo('webunit', max(xClientWidth()-30,min_x), max(xClientHeight()-20,min_y)); + xMoveTo('webunit', 5, 5); + xResizeTo('tabs', xWidth('webunit')-10, xHeight('webunit')/3); + xLeft('tabs', 5); + xShow('webunit'); + xShow('tabs'); + activate_tab('fail'); + xShow('visible_tab'); + xZIndex('visible_tab', 2) + xResizeTo('msg', xWidth('webunit')-17, xHeight('webunit')/3-20); + xLeft('msg', 2); + xTop('msg',2*xHeight('webunit')/3); + xShow('msg'); +} + +function set_div_content(div, content) { + xGetElementById(div).innerHTML = content; +} + +function copy_div_content(divsrc, divtrgt) { + xGetElementById(divtrgt).innerHTML = xGetElementById(divsrc).innerHTML; +} + +function activate_tab(tab) { + if (tab == 'fail') { + copy_div_content('fail', 'visible_tab'); + xGetElementById('failtab').className = 'activetab'; + xZIndex('failtab', 3) + xGetElementById('treetab').className = 'inactivetab'; + xZIndex('treetab', 1) + } + if (tab == 'tree') { + copy_div_content('tree', 'visible_tab'); + xGetElementById('failtab').className = 'inactivetab'; + xZIndex('failtab', 1) + xGetElementById('treetab').className = 'activetab'; + xZIndex('treetab', 3) + } +} + +function add_group(group_name) { + var add; + + add = { + Set : function(foo,bar) {this[foo] = bar;}, + Get : function(foo) {return this[foo];} + } + add.Set('desc', group_name); + add.Set('pass', true); + groups[groups.length] = add; + current_group = groups.length - 1; + cases[current_group] = new Array(); + methods[current_group] = new Array(); +} + +function add_case(case_name) { + var curgroup; + var add; + + add = { + Set : function(foo,bar) {this[foo] = bar;}, + Get : function(foo) {return this[foo];} + } + add.Set('desc', case_name); + add.Set('pass', true); + curgroup = cases[current_group]; + cases[current_group][curgroup.length] = add; + current_case = curgroup.length - 1; + methods[current_group][current_case] = new Array(); +} + +function add_method(method_name) { + var curcase; + var add; + + add = { + Set : function(foo,bar) {this[foo] = bar;}, + Get : function(foo) {return this[foo];} + } + add.Set('desc', method_name); + add.Set('pass', true); + add.Set('msg',''); + curcase = methods[current_group][current_case]; + methods[current_group][current_case][curcase.length] = add; + current_method = curcase.length - 1; +} + +function add_fail(msg) { + var oldmsg; + add_log(msg); + groups[current_group].Set('pass', false); + cases[current_group][current_case].Set('pass', false); + methods[current_group][current_case][current_method].Set('pass', false); + oldmsg = methods[current_group][current_case][current_method].Get('msg'); + methods[current_group][current_case][current_method].Set('msg', oldmsg+msg); +} + +function add_log(msg) { + var faildiv; + faildiv = xGetElementById('fail'); + faildiv.innerHTML = faildiv.innerHTML + msg; +} + +function set_msg(gid, cid, mid) { + var passfail; + var msg=methods[gid][cid][mid].Get('msg'); + if ('' == msg) { + passfail = (methods[gid][cid][mid].Get('pass')) ? 'pass' : 'fail'; + msg = 'No output for ' + + groups[gid].Get('desc') + '->' + + cases[gid][cid].Get('desc') + '->' + + methods[gid][cid][mid].Get('desc') + '
    '; + } + xGetElementById('msg').innerHTML = msg; +} + +function make_tree() { + var content; + var passfail; + content = '
      '; + for (x in groups) { + passfail = (groups[x].Get('pass')) ? 'pass' : 'fail'; + content += '
    • '+groups[x].Get('desc')+'
        '; + for (y in cases[x]) { + passfail = (cases[x][y].Get('pass')) ? 'pass' : 'fail'; + content += '
      • '+cases[x][y].Get('desc')+'
          '; + for (z in methods[x][y]) { + passfail = (methods[x][y][z].Get('pass')) ? 'pass' : 'fail'; + content += '
        • '+methods[x][y][z].Get('desc')+'
        • '; + } + content += '
      • '; + } + content += '
    • '; + } + content += '
    '; + xGetElementById('tree').innerHTML = content; + if (xGetElementById('treetab').className == 'activetab') { + activate_tab('tree'); + } else { + activate_tab('fail'); + } +} + +function make_output(data) { +} + +function make_fail_msg(id, msg) { +} + +function max(n1, n2) { + if (n1 > n2) { + return n1; + } else { + return n2; + } +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/js/x.js b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/js/x.js new file mode 100644 index 0000000..78edb02 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/js/x.js @@ -0,0 +1,423 @@ +// x.js +// X v3.14.1, Cross-Browser DHTML Library from Cross-Browser.com +// Copyright (c) 2002,2003 Michael Foster (mike@cross-browser.com) +// This library is distributed under the terms of the LGPL (gnu.org) + +// Variables: +var xVersion='3.14.1',xOp7=false,xOp5or6=false,xIE4Up=false,xNN4=false,xUA=navigator.userAgent.toLowerCase(); +if(window.opera){ + xOp7=(xUA.indexOf('opera 7')!=-1 || xUA.indexOf('opera/7')!=-1); + if (!xOp7) xOp5or6=(xUA.indexOf('opera 5')!=-1 || xUA.indexOf('opera/5')!=-1 || xUA.indexOf('opera 6')!=-1 || xUA.indexOf('opera/6')!=-1); +} +else if(document.layers) {xNN4=true;} +else {xIE4Up=document.all && xUA.indexOf('msie')!=-1 && parseInt(navigator.appVersion)>=4;} +// Object: +function xGetElementById(e) { + if(typeof(e)!='string') return e; + if(document.getElementById) e=document.getElementById(e); + else if(document.all) e=document.all[e]; + else if(document.layers) e=xLayer(e); + else e=null; + return e; +} +function xParent(e,bNode){ + if (!(e=xGetElementById(e))) return null; + var p=null; + if (!bNode && xDef(e.offsetParent)) p=e.offsetParent; + else if (xDef(e.parentNode)) p=e.parentNode; + else if (xDef(e.parentElement)) p=e.parentElement; + else if (xDef(e.parentLayer)){if (e.parentLayer!=window) p=e.parentLayer;} + return p; +} +function xDef() { + for(var i=0; iwindow.innerHeight) w-=16; + } + return w; +} +function xClientHeight() { + var h=0; + if(xOp5or6) h=window.innerHeight; + else if(!window.opera && document.documentElement && document.documentElement.clientHeight) // v3.12 + h=document.documentElement.clientHeight; + else if(document.body && document.body.clientHeight) + h=document.body.clientHeight; + else if(xDef(window.innerWidth,window.innerHeight,document.width)) { + h=window.innerHeight; + if(document.width>window.innerWidth) h-=16; + } + return h; +} +// Animation: +function xSlideTo(e,x,y,uTime) { + if (!(e=xGetElementById(e))) return; + if (!e.timeout) e.timeout = 25; + e.xTarget = x; e.yTarget = y; e.slideTime = uTime; e.stop = false; + e.yA = e.yTarget - xTop(e); e.xA = e.xTarget - xLeft(e); // A = distance + e.B = Math.PI / (2 * e.slideTime); // B = period + e.yD = xTop(e); e.xD = xLeft(e); // D = initial position + var d = new Date(); e.C = d.getTime(); + if (!e.moving) xSlide(e); +} +function xSlide(e) { + if (!(e=xGetElementById(e))) return; + var now, s, t, newY, newX; + now = new Date(); + t = now.getTime() - e.C; + if (e.stop) { e.moving = false; } + else if (t < e.slideTime) { + setTimeout("xSlide('"+e.id+"')", e.timeout); + s = Math.sin(e.B * t); + newX = Math.round(e.xA * s + e.xD); + newY = Math.round(e.yA * s + e.yD); + xMoveTo(e, newX, newY); + e.moving = true; + } + else { + xMoveTo(e, e.xTarget, e.yTarget); + e.moving = false; + } +} +// Event: +function xAddEventListener(e,eventType,eventListener,useCapture) { + if(!(e=xGetElementById(e))) return; + eventType=eventType.toLowerCase(); + if((!xIE4Up && !xOp7) && e==window) { + if(eventType=='resize') { window.xPCW=xClientWidth(); window.xPCH=xClientHeight(); window.xREL=eventListener; xResizeEvent(); return; } + if(eventType=='scroll') { window.xPSL=xScrollLeft(); window.xPST=xScrollTop(); window.xSEL=eventListener; xScrollEvent(); return; } + } + var eh='e.on'+eventType+'=eventListener'; + if(e.addEventListener) e.addEventListener(eventType,eventListener,useCapture); + else if(e.attachEvent) e.attachEvent('on'+eventType,eventListener); + else if(e.captureEvents) { + if(useCapture||(eventType.indexOf('mousemove')!=-1)) { e.captureEvents(eval('Event.'+eventType.toUpperCase())); } + eval(eh); + } + else eval(eh); +} +function xRemoveEventListener(e,eventType,eventListener,useCapture) { + if(!(e=xGetElementById(e))) return; + eventType=eventType.toLowerCase(); + if((!xIE4Up && !xOp7) && e==window) { + if(eventType=='resize') { window.xREL=null; return; } + if(eventType=='scroll') { window.xSEL=null; return; } + } + var eh='e.on'+eventType+'=null'; + if(e.removeEventListener) e.removeEventListener(eventType,eventListener,useCapture); + else if(e.detachEvent) e.detachEvent('on'+eventType,eventListener); + else if(e.releaseEvents) { + if(useCapture||(eventType.indexOf('mousemove')!=-1)) { e.releaseEvents(eval('Event.'+eventType.toUpperCase())); } + eval(eh); + } + else eval(eh); +} +function xEvent(evt) { // cross-browser event object prototype + this.type = ''; + this.target = null; + this.pageX = 0; + this.pageY = 0; + this.offsetX = 0; + this.offsetY = 0; + this.keyCode = 0; + var e = evt ? evt : window.event; + if(!e) return; + if(e.type) this.type = e.type; + if(e.target) this.target = e.target; + else if(e.srcElement) this.target = e.srcElement; + else if(xNN4) this.target = xLayerFromPoint(e.pageX, e.pageY); + if(xOp5or6) { this.pageX = e.clientX; this.pageY = e.clientY; } + else if(xDef(e.pageX,e.pageY)) { this.pageX = e.pageX; this.pageY = e.pageY; } // v3.14 + else if(xDef(e.clientX,e.clientY)) { this.pageX = e.clientX + xScrollLeft(); this.pageY = e.clientY + xScrollTop(); } + if(xDef(e.offsetX,e.offsetY)) { this.offsetX = e.offsetX; this.offsetY = e.offsetY; } + else if(xDef(e.layerX,e.layerY)) { this.offsetX = e.layerX; this.offsetY = e.layerY; } + else { this.offsetX = this.pageX - xPageX(this.target); this.offsetY = this.pageY - xPageY(this.target); } + if (e.keyCode) { this.keyCode = e.keyCode; } // for moz/fb, if keyCode==0 use which + else if (xDef(e.which)) { this.keyCode = e.which; } +} +function xResizeEvent() { // window resize event simulation + if (window.xREL) setTimeout('xResizeEvent()', 250); + var cw = xClientWidth(), ch = xClientHeight(); + if (window.xPCW != cw || window.xPCH != ch) { window.xPCW = cw; window.xPCH = ch; if (window.xREL) window.xREL(); } +} +function xScrollEvent() { // window scroll event simulation + if (window.xSEL) setTimeout('xScrollEvent()', 250); + var sl = xScrollLeft(), st = xScrollTop(); + if (window.xPSL != sl || window.xPST != st) { window.xPSL = sl; window.xPST = st; if (window.xSEL) window.xSEL(); } +} +// end x.js diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/junit_xml_reporter.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/junit_xml_reporter.php new file mode 100644 index 0000000..3bb3892 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/junit_xml_reporter.php @@ -0,0 +1,118 @@ +SimpleReporter(); + + $this->doc = new DOMDocument(); + $this->doc->loadXML(''); + $this->root = $this->doc->documentElement; + } + + function paintHeader($test_name) { + $this->testsStart = microtime(true); + + $this->root->setAttribute('name', $test_name); + $this->root->setAttribute('timestamp', date('c')); + $this->root->setAttribute('hostname', 'localhost'); + + echo "\n"; + echo "\n"; + + $duration = microtime(true) - $this->testsStart; + + $this->root->setAttribute('tests', $this->getPassCount() + $this->getFailCount() + $this->getExceptionCount()); + $this->root->setAttribute('failures', $this->getFailCount()); + $this->root->setAttribute('errors', $this->getExceptionCount()); + $this->root->setAttribute('time', $duration); + + $this->doc->formatOutput = true; + $xml = $this->doc->saveXML(); + // Cut out XML declaration + echo preg_replace('/<\?[^>]*\?>/', "", $xml); + echo "\n"; + } + + function paintCaseStart($case) { + echo "- case start $case\n"; + $this->currentCaseName = $case; + } + + function paintCaseEnd($case) { + // No output here + } + + function paintMethodStart($test) { + echo " - test start: $test\n"; + + $this->methodStart = microtime(true); + $this->currCase = $this->doc->createElement('testcase'); + } + + function paintMethodEnd($test) { + $duration = microtime(true) - $this->methodStart; + + $this->currCase->setAttribute('name', $test); + $this->currCase->setAttribute('classname', $this->currentCaseName); + $this->currCase->setAttribute('time', $duration); + $this->root->appendChild($this->currCase); + } + + function paintFail($message) { + parent::paintFail($message); + + error_log("Failure: " . $message); + $this->terminateAbnormally($message); + } + + function paintException($exception) { + parent::paintException($exception); + + error_log("Exception: " . $exception); + $this->terminateAbnormally($exception); + } + + function terminateAbnormally($message) { + if (!$this->currCase) { + error_log("!! currCase was not set."); + return; + } + + $ch = $this->doc->createElement('failure'); + $breadcrumb = $this->getTestList(); + $ch->setAttribute('message', $breadcrumb[count($breadcrumb)-1]); + $ch->setAttribute('type', $breadcrumb[count($breadcrumb)-1]); + + $message = implode(' -> ', $breadcrumb) . "\n\n\n" . $message; + $content = $this->doc->createTextNode($message); + $ch->appendChild($content); + + $this->currCase->appendChild($ch); + } +} +?> + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/pear_test_case.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/pear_test_case.php new file mode 100644 index 0000000..4fc34f5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/pear_test_case.php @@ -0,0 +1,196 @@ +_loosely_typed = false; + } + + /** + * Will test straight equality if set to loose + * typing, or identity if not. + * @param $first First value. + * @param $second Comparison value. + * @param $message Message to display. + * @public + */ + function assertEquals($first, $second, $message = "%s", $delta = 0) { + if ($this->_loosely_typed) { + $expectation = new EqualExpectation($first); + } else { + $expectation = new IdenticalExpectation($first); + } + $this->assert($expectation, $second, $message); + } + + /** + * Passes if the value tested is not null. + * @param $value Value to test against. + * @param $message Message to display. + * @public + */ + function assertNotNull($value, $message = "%s") { + parent::assert(new TrueExpectation(), isset($value), $message); + } + + /** + * Passes if the value tested is null. + * @param $value Value to test against. + * @param $message Message to display. + * @public + */ + function assertNull($value, $message = "%s") { + parent::assert(new TrueExpectation(), !isset($value), $message); + } + + /** + * Identity test tests for the same object. + * @param $first First object handle. + * @param $second Hopefully the same handle. + * @param $message Message to display. + * @public + */ + function assertSame($first, $second, $message = "%s") { + $dumper = new SimpleDumper(); + $message = sprintf( + $message, + "[" . $dumper->describeValue($first) . + "] and [" . $dumper->describeValue($second) . + "] should reference the same object"); + return $this->assert( + new TrueExpectation(), + SimpleTestCompatibility::isReference($first, $second), + $message); + } + + /** + * Inverted identity test. + * @param $first First object handle. + * @param $second Hopefully a different handle. + * @param $message Message to display. + * @public + */ + function assertNotSame($first, $second, $message = "%s") { + $dumper = new SimpleDumper(); + $message = sprintf( + $message, + "[" . $dumper->describeValue($first) . + "] and [" . $dumper->describeValue($second) . + "] should not be the same object"); + return $this->assert( + new falseExpectation(), + SimpleTestCompatibility::isReference($first, $second), + $message); + } + + /** + * Sends pass if the test condition resolves true, + * a fail otherwise. + * @param $condition Condition to test true. + * @param $message Message to display. + * @public + */ + function assertTrue($condition, $message = "%s") { + parent::assert(new TrueExpectation(), $condition, $message); + } + + /** + * Sends pass if the test condition resolves false, + * a fail otherwise. + * @param $condition Condition to test false. + * @param $message Message to display. + * @public + */ + function assertFalse($condition, $message = "%s") { + parent::assert(new FalseExpectation(), $condition, $message); + } + + /** + * Tests a regex match. Needs refactoring. + * @param $pattern Regex to match. + * @param $subject String to search in. + * @param $message Message to display. + * @public + */ + function assertRegExp($pattern, $subject, $message = "%s") { + $this->assert(new PatternExpectation($pattern), $subject, $message); + } + + /** + * Tests the type of a value. + * @param $value Value to take type of. + * @param $type Hoped for type. + * @param $message Message to display. + * @public + */ + function assertType($value, $type, $message = "%s") { + parent::assert(new TrueExpectation(), gettype($value) == strtolower($type), $message); + } + + /** + * Sets equality operation to act as a simple equal + * comparison only, allowing a broader range of + * matches. + * @param $loosely_typed True for broader comparison. + * @public + */ + function setLooselyTyped($loosely_typed) { + $this->_loosely_typed = $loosely_typed; + } + + /** + * For progress indication during + * a test amongst other things. + * @return Usually one. + * @public + */ + function countTestCases() { + return $this->getSize(); + } + + /** + * Accessor for name, normally just the class + * name. + * @public + */ + function getName() { + return $this->getLabel(); + } + + /** + * Does nothing. For compatibility only. + * @param $name Dummy + * @public + */ + function setName($name) { + } + } +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/recorder.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/recorder.php new file mode 100644 index 0000000..80ea861 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/recorder.php @@ -0,0 +1,62 @@ +SimpleReporter(); + $this->results = array(); + } + + function paintPass($message) { + parent::paintPass($message); + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + $test = implode("->", $breadcrumb); + + $result["time"] = time(); + $result["status"] = "Passed"; + $result["test"] = $test; + $result["message"] = $message; + $this->results[] = $result; + } + + function paintFail($message) { + parent::paintFail($message); + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + $test = implode("->", $breadcrumb); + + $result["time"] = time(); + $result["status"] = "Failed"; + $result["test"] = $test; + $result["message"] = $message; + $this->results[] = $result; + } +} + +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/recorder/test/sample.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/recorder/test/sample.php new file mode 100755 index 0000000..4848c48 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/recorder/test/sample.php @@ -0,0 +1,14 @@ +assertTrue(true); + } + + function testFalseIsTrue() { + $this->assertFalse(true); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/recorder/test/test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/recorder/test/test.php new file mode 100755 index 0000000..ec72cdb --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/recorder/test/test.php @@ -0,0 +1,28 @@ +addTestFile(dirname(__FILE__) . '/sample.php'); + $recorder = new Recorder(); + $test->run($recorder); + $this->assertEqual(count($recorder->results), 2); + + $this->assertEqual(count($recorder->results[0]), 4); + $this->assertPattern("/".substr(time(), 9)."/", $recorder->results[0]['time']); + $this->assertEqual($recorder->results[0]['status'], "Passed"); + $this->assertPattern("/sample\.php->SampleTestForRecorder->testTrueIsTrue/i", $recorder->results[0]['test']); + $this->assertPattern("/ at \[.*recorder\/test\/sample\.php line 7\]/", $recorder->results[0]['message']); + + $this->assertEqual(count($recorder->results[1]), 4); + $this->assertPattern("/".substr(time(), 9)."/", $recorder->results[1]['time']); + $this->assertEqual($recorder->results[1]['status'], "Failed"); + $this->assertPattern("/sample\.php->SampleTestForRecorder->testFalseIsTrue/i", $recorder->results[1]['test']); + $this->assertPattern("/Expected false, got \[Boolean: true\] at \[.*recorder\/test\/sample\.php line 11\]/", $recorder->results[1]['message']); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/selenese_tester.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/selenese_tester.php new file mode 100644 index 0000000..9ae2d9c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/selenese_tester.php @@ -0,0 +1,393 @@ + + * @param simpleSelenium $selenium + * @param string $html + * @param string $testFile + * @param array $parsed_table + * @param string $logMessages + * @param array $_commandMap + * + */ +class SeleneseTestCase extends UnitTestCase { + var $selenium; + var $html; + var $testFile; + var $parsed_table; + var $logMessages; + var $_commandMap = array("verify", + "verifyErrorOnNext", + "verifyNotErrorOnNext", + "verifyFailureOnNext", + "verifyNotFailureOnNext", + "verifySelected", + "verifyNotSelected", + "verifyAlert", + "verifyNotAlert", + "verifyAllButtons", + "verifyNotAllButtons", + "verifyAllFields", + "verifyNotAllFields", + "verifyAllLinks", + "verifyNotAllLinks", + "verifyAllWindowIds", + "verifyNotAllWindowIds", + "verifyAllWindowNames", + "verifyNotAllWindowNames", + "verifyAllWindowTitles", + "verifyNotAllWindowTitles", + "verifyAttribute", + "verifyNotAttribute", + "verifyAttributeFromAllWindows", + "verifyNotAttributeFromAllWindows", + "verifyBodyText", + "verifyNotBodyText", + "verifyConfirmation", + "verifyNotConfirmation", + "verifyCookie", + "verifyNotCookie", + "verifyCursorPosition", + "verifyNotCursorPosition", + "verifyElementHeight", + "verifyNotElementHeight", + "verifyElementIndex", + "verifyNotElementIndex", + "verifyElementPositionLeft", + "verifyNotElementPositionLeft", + "verifyElementPositionTop", + "verifyNotElementPositionTop", + "verifyElementWidth", + "verifyNotElementWidth", + "verifyEval", + "verifyNotEval", + "verifyExpression", + "verifyNotExpression", + "verifyHtmlSource", + "verifyNotHtmlSource", + "verifyLocation", + "verifyNotLocation", + "verifyLogMessages", + "verifyNotLogMessages", + "verifyMouseSpeed", + "verifyNotMouseSpeed", + "verifyPrompt", + "verifyNotPrompt", + "verifySelectedId", + "verifyNotSelectedId", + "verifySelectedIds", + "verifyNotSelectedIds", + "verifySelectedIndex", + "verifyNotSelectedIndex", + "verifySelectedIndexes", + "verifyNotSelectedIndexes", + "verifySelectedLabel", + "verifyNotSelectedLabel", + "verifySelectedLabels", + "verifyNotSelectedLabels", + "verifySelectedValue", + "verifyNotSelectedValue", + "verifySelectedValues", + "verifyNotSelectedValues", + "verifySelectOptions", + "verifyNotSelectOptions", + "verifyTable", + "verifyNotTable", + "verifyText", + "verifyNotText", + "verifyTitle", + "verifyNotTitle", + "verifyValue", + "verifyNotValue", + "verifyWhetherThisFrameMatchFrameExpression", + "verifyNotWhetherThisFrameMatchFrameExpression", + "verifyWhetherThisWindowMatchWindowExpression", + "verifyNotWhetherThisWindowMatchWindowExpression", + "verifyAlertPresent", + "verifyAlertNotPresent", + "verifyChecked", + "verifyNotChecked", + "verifyConfirmationPresent", + "verifyConfirmationNotPresent", + "verifyEditable", + "verifyNotEditable", + "verifyElementPresent", + "verifyElementNotPresent", + "verifyOrdered", + "verifyNotOrdered", + "verifyPromptPresent", + "verifyPromptNotPresent", + "verifySomethingSelected", + "verifyNotSomethingSelected", + "verifyTextPresent", + "verifyTextNotPresent", + "verifyVisible", + "verifyNotVisible", + "assert", + "assertErrorOnNext", + "assertNotErrorOnNext", + "assertFailureOnNext", + "assertNotFailureOnNext", + "assertSelected", + "assertNotSelected", + "assertAlert", + "assertNotAlert", + "assertAllButtons", + "assertNotAllButtons", + "assertAllFields", + "assertNotAllFields", + "assertAllLinks", + "assertNotAllLinks", + "assertAllWindowIds", + "assertNotAllWindowIds", + "assertAllWindowNames", + "assertNotAllWindowNames", + "assertAllWindowTitles", + "assertNotAllWindowTitles", + "assertAttribute", + "assertNotAttribute", + "assertAttributeFromAllWindows", + "assertNotAttributeFromAllWindows", + "assertBodyText", + "assertNotBodyText", + "assertConfirmation", + "assertNotConfirmation", + "assertCookie", + "assertNotCookie", + "assertCursorPosition", + "assertNotCursorPosition", + "assertElementHeight", + "assertNotElementHeight", + "assertElementIndex", + "assertNotElementIndex", + "assertElementPositionLeft", + "assertNotElementPositionLeft", + "assertElementPositionTop", + "assertNotElementPositionTop", + "assertElementWidth", + "assertNotElementWidth", + "assertEval", + "assertNotEval", + "assertExpression", + "assertNotExpression", + "assertHtmlSource", + "assertNotHtmlSource", + "assertLocation", + "assertNotLocation", + "assertLogMessages", + "assertNotLogMessages", + "assertMouseSpeed", + "assertNotMouseSpeed", + "assertPrompt", + "assertNotPrompt", + "assertSelectedId", + "assertNotSelectedId", + "assertSelectedIds", + "assertNotSelectedIds", + "assertSelectedIndex", + "assertNotSelectedIndex", + "assertSelectedIndexes", + "assertNotSelectedIndexes", + "assertSelectedLabel", + "assertNotSelectedLabel", + "assertSelectedLabels", + "assertNotSelectedLabels", + "assertSelectedValue", + "assertNotSelectedValue", + "assertSelectedValues", + "assertNotSelectedValues", + "assertSelectOptions", + "assertNotSelectOptions", + "assertTable", + "assertNotTable", + "assertText", + "assertNotText", + "assertTitle", + "assertNotTitle", + "assertValue", + "assertNotValue", + "assertWhetherThisFrameMatchFrameExpression", + "assertNotWhetherThisFrameMatchFrameExpression", + "assertWhetherThisWindowMatchWindowExpression", + "assertNotWhetherThisWindowMatchWindowExpression", + "assertAlertPresent", + "assertAlertNotPresent", + "assertChecked", + "assertNotChecked", + "assertConfirmationPresent", + "assertConfirmationNotPresent", + "assertEditable", + "assertNotEditable", + "assertElementPresent", + "assertElementNotPresent", + "assertOrdered", + "assertNotOrdered", + "assertPromptPresent", + "assertPromptNotPresent", + "assertSomethingSelected", + "assertNotSomethingSelected", + "assertTextPresent", + "assertTextNotPresent", + "assertVisible", + "assertNotVisible"); + + /** + * constructor + * + * Construct the object with the specified browser and url + * + * @param string $browser + * @param string $url + */ + function __construct($browser, $url) { + $this->selenium = new SimpleSeleniumRemoteControl($browser, $url); + $this->parsed_table = array(); + } + + /** + * tidy + * + * Reformat the selenium-IDE html test suites + */ + function tidy() { + $tmp = $this->html; + preg_match('//', $tmp, $matche); + $matche[0] = str_replace("/>", ">", $matche[0]); + $matche[0] = str_replace(">", "/>", $matche[0]); + $tmp = preg_replace('//', $matche[0], $tmp); + $this->html = $tmp; + } + + /** + * parse + * + * Extract the called selenium fonction from the html suite + */ + function parse() { + $parsedTab = array(); + $key1 = 0; + + $contenthtml = new DOMDocument; + @$contenthtml->loadHtml($this->html); + $content = simplexml_import_dom($contenthtml); + foreach ($content->body->table->tbody->tr as $tr){ + $key2 = 0; + foreach ($tr->td as $td){ + $parsedTab[$key1][$key2] = $td; + $key2++; + } + $key1++; + } + + $this->parsed_table = $parsedTab; + } + + /** + * assertFunction + * + * Integrate selenium fonctions in simpletest + * + * @param string $function + * @param string $param1 + * @param string $param2 + * + */ + function assertFunction($function, $param1, $param2) { + $_verifyMap = array('verify', 'verifyTextPresent', 'verifyTextNotPresent', 'verifyValue'); + + $reponse = $this->selenium->__call($function, array($param1, $param2)); + + $message = $reponse; + $message .= " using command '".$function ."' with target '".$param1."'"; + if (!empty($param2)) { + $message .= " and value '".$param2."'"; + } + $message .= " in file '".$this->testFile."'"; + + if (!in_array($function, $_verifyMap)) { + $reponse = substr($reponse, 0, 2) == 'OK' ? true : false; + } + + $this->assertTrue($reponse, $message); + } + + /** + * launch + * + * Launch the html test suite from a PHP variable on the url declared wihle + * constructing the object. The filename is used to localize the error. + * + * @param string $testFile + * @param string $filename + * + */ + function launch($html="") { + $this->html = $html; + $this->tidy(); + $this->parse(); + + $this->selenium->start(); + foreach ($this->parsed_table as $test) { + if (in_array($test[0], $this->_commandMap)) { + $this->assertFunction($test[0], $test[1], $test[2]); + } else { + $this->selenium->__call($test[0], array($test[1], $test[2])); + } + } + $this->selenium->stop(); + } + + /** + * launchPhpFile + * + * Parse the PHP file then launch the computed test suite + * + * @param string $file + * + */ + function launchPhpFile($file) { + ob_start(); + require($file); + $data = ob_get_contents(); + ob_end_clean(); + + $this->testFile = $file; + $this->html = $data; + $this->launch($this->html); + } + + /** + * launchFile + * + * Launch the html test suite file on the url declared wihle constructing the object + * + * @param string $testFile + * + */ + function launchFile($testFile) { + $this->testFile = $testFile; + $this->html = file_get_contents($testFile); + $this->launch($this->html); + } +} + +?> diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/selenium.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/selenium.php new file mode 100644 index 0000000..2efaeb4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/selenium.php @@ -0,0 +1,136 @@ +browser)) { + trigger_error('browser property must be set in ' . get_class($this)); + exit; + } + + if (empty($this->browserUrl)) { + trigger_error('browserUrl property must be set in ' . get_class($this)); + exit; + } + } + + public function setUp() { + parent::setUp(); + + if (is_null($this->selenium)) { + $this->selenium = new SimpleSeleniumRemoteControl( + $this->browser, + $this->browserUrl, + $this->host, + $this->port, + $this->timeout + ); + $this->selenium->start(); + } + } + + public function tearDown() { + parent::tearDown(); + + if ($this->newInstanceEachTest) { + $this->selenium->stop(); + $this->selenium = null; + } + } + + public function __call($method, $arguments) { + if (substr($method, 0, 6) == 'verify') { + return $this->assertTrue( + call_user_func_array( + array($this->selenium, $method), + $arguments + ), + sprintf('%s failed', $method) + ); + } + + return call_user_func_array( + array($this->selenium, $method), + $arguments + ); + } + + public function verifyText($text) { + return $this->assertTrue( + $this->selenium->verifyText($text), + sprintf( + 'verifyText failed when on [%s]', + $text + ) + ); + } + + public function verifyTextPresent($text) { + return $this->assertTrue( + $this->selenium->verifyTextPresent($text), + sprintf( + 'verifyTextPresent failed when on [%s]', + $text + ) + ); + } + + public function verifyTextNotPresent($text) { + return $this->assertTrue( + $this->selenium->verifyTextNotPresent($text), + sprintf( + 'verifyTextNotPresent failed on [%s]', + $text + ) + ); + } + + public function verifyValue($selector, $value) { + return $this->assertTrue( + $this->selenium->verifyValue($selector, $value), + sprintf( + 'verifyValue failed on [%s] == [%s]', + $selector, + $value + ) + ); + } + + public function verifyTitle($pattern) { + return $this->assertTrue( + $this->selenium->verifyTitle($pattern), + sprintf( + 'verifyTitle failed on [%s]', + $pattern + ) + ); + } +} + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/selenium/remote-control.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/selenium/remote-control.php new file mode 100644 index 0000000..3227715 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/selenium/remote-control.php @@ -0,0 +1,128 @@ + + * + */ +class SimpleSeleniumRemoteControl +{ + private $_browser = ''; + private $_browserUrl = ''; + private $_host = 'localhost'; + private $_port = 4444; + private $_timeout = 30000; + private $_sessionId = null; + + private $_commandMap = array( + 'bool' => array( + 'verify', + 'verifyTextPresent', + 'verifyTextNotPresent', + 'verifyValue' + ), + 'string' => array( + 'getNewBrowserSession', + ), + ); + + public function __construct($browser, $browserUrl, $host = 'localhost', $port = 4444, $timeout = 30000) { + $this->_browser = $browser; + $this->_browserUrl = $browserUrl; + $this->_host = $host; + $this->_port = $port; + $this->_timeout = $timeout; + } + + public function sessionIdParser($response) { + return substr($response, 3); + } + + public function start() { + $response = $this->cmd('getNewBrowserSession', array($this->_browser, $this->_browserUrl)); + $this->_sessionId = $this->sessionIdParser($response); + } + + public function stop() { + $this->cmd('testComplete'); + $this->_sessionId = null; + } + + public function __call($method, $arguments) { + $response = $this->cmd($method, $arguments); + + foreach ($this->_commandMap as $type => $commands) { + if (!in_array($method, $commands)) { + continue; + $type = null; + } + break; + } + + switch ($type) { + case 'bool' : + return substr($response, 0, 2) == 'OK' ? true : false; + break; + + case 'string' : + default: + return $response; + } + } + + private function _server() { + return "http://{$this->_host}:{$this->_port}/selenium-server/driver/"; + } + + public function buildUrlCmd($method, $arguments = array()) { + $params = array( + 'cmd=' . urlencode($method), + ); + $i = 1; + foreach ($arguments as $param) { + $params[] = $i++ . '=' . urlencode(trim($param)); + } + if (isset($this->_sessionId)) { + $params[] = 'sessionId=' . $this->_sessionId; + } + + return $this->_server()."?".implode('&', $params); + } + + public function cmd($method, $arguments = array()) { + $url = $this->buildUrlCmd($method, $arguments); + $response = $this->_sendRequest($url); + return $response; + } + + public function isUp() { + return (bool)@fsockopen($this->_host, $this->_port, $errno, $errstr, 30); + } + + private function _initCurl($url) { + if (!function_exists('curl_init')) { + throw new Exception('this code currently requires the curl extension'); + } + if (!$ch = curl_init($url)) { + throw new Exception('Unable to setup curl'); + } + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_TIMEOUT, floor($this->_timeout)); + return $ch; + } + + private function _sendRequest($url) { + $ch = $this->_initCurl($url); + $result = curl_exec($ch); + if (($errno = curl_errno($ch)) != 0) { + throw new Exception('Curl returned non-null errno ' . $errno . ':' . curl_error($ch)); + } + curl_close($ch); + return $result; + } +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/selenium/test/remote-control_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/selenium/test/remote-control_test.php new file mode 100644 index 0000000..b705b6a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/selenium/test/remote-control_test.php @@ -0,0 +1,36 @@ +assertEqual($remote_control->sessionIdParser('OK,123456789123456789'), '123456789123456789'); + } + + function testIsUpReturnsFalseWhenDirectedToLocalhostDown() { + $remote_control = new SimpleSeleniumRemoteControl("tester", "http://simpletest.org/", "localhost-down");; + $this->assertFalse($remote_control->isUp()); + } + + function testIsUpReturnsTrueWhenDirectedToLocalhostOnPort80() { + $remote_control = new SimpleSeleniumRemoteControl("tester", "http://simpletest.org/", "localhost", "80"); + $this->assertTrue($remote_control->isUp()); + } +} + +class TestOfSimpleSeleniumRemoteControlWhenItIsUp extends UnitTestCase { + function skip() { + $remote_control = new SimpleSeleniumRemoteControl("*custom opera -nosession", "http://simpletest.org/"); + $this->skipUnless($remote_control->isUp(), 'Remote control tests desperatly need a working Selenium Server.'); + } + + function testOfCommandCreation() { + $remote_control = new SimpleSeleniumRemoteControl("tester", "http://simpletest.org/"); + $this->assertEqual($remote_control->buildUrlCmd("test"), 'http://localhost:4444/selenium-server/driver/?cmd=test'); + $this->assertEqual($remote_control->buildUrlCmd("test", array("next")), 'http://localhost:4444/selenium-server/driver/?cmd=test&1=next'); + $this->assertEqual($remote_control->buildUrlCmd("test", array("ŽtŽ")), 'http://localhost:4444/selenium-server/driver/?cmd=test&1=%C3%A9t%C3%A9'); + $this->assertEqual($remote_control->buildUrlCmd("test", array("next", "then")), 'http://localhost:4444/selenium-server/driver/?cmd=test&1=next&2=then'); + } +} \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/testdox.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/testdox.php new file mode 100644 index 0000000..dd9c61d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/testdox.php @@ -0,0 +1,55 @@ +_test_case_pattern = empty($test_case_pattern) ? '/^(.*)$/' : $test_case_pattern; + } + + function paintCaseStart($test_name) { + preg_match($this->_test_case_pattern, $test_name, $matches); + if (!empty($matches[1])) { + echo $matches[1] . "\n"; + } else { + echo $test_name . "\n"; + } + } + + function paintCaseEnd($test_name) { + echo "\n"; + } + + function paintMethodStart($test_name) { + if (!preg_match('/^test(.*)$/i', $test_name, $matches)) { + return; + } + $test_name = $matches[1]; + + $test_name = preg_replace('/([A-Z])([A-Z])/', '$1 $2', $test_name); + echo '- ' . strtolower(preg_replace('/([a-zA-Z])([A-Z0-9])/', '$1 $2', $test_name)); + } + + function paintMethodEnd($test_name) { + echo "\n"; + } + + function paintFail($message) { + echo " [FAILED]"; + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/testdox/test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/testdox/test.php new file mode 100644 index 0000000..b03abe5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/testdox/test.php @@ -0,0 +1,107 @@ +assertIsA($dox, 'SimpleScorer'); + $this->assertIsA($dox, 'SimpleReporter'); + } + + function testOutputsNameOfTestCase() { + $dox = new TestDoxReporter(); + ob_start(); + $dox->paintCaseStart('TestOfTestDoxReporter'); + $buffer = ob_get_clean(); + $this->assertPattern('/^TestDoxReporter/', $buffer); + } + + function testOutputOfTestCaseNameFilteredByConstructParameter() { + $dox = new TestDoxReporter('/^(.*)Test$/'); + ob_start(); + $dox->paintCaseStart('SomeGreatWidgetTest'); + $buffer = ob_get_clean(); + $this->assertPattern('/^SomeGreatWidget/', $buffer); + } + + function testIfTest_case_patternIsEmptyAssumeEverythingMatches() { + $dox = new TestDoxReporter(''); + ob_start(); + $dox->paintCaseStart('TestOfTestDoxReporter'); + $buffer = ob_get_clean(); + $this->assertPattern('/^TestOfTestDoxReporter/', $buffer); + } + + function testEmptyLineInsertedWhenCaseEnds() { + $dox = new TestDoxReporter(); + ob_start(); + $dox->paintCaseEnd('TestOfTestDoxReporter'); + $buffer = ob_get_clean(); + $this->assertEqual("\n", $buffer); + } + + function testPaintsTestMethodInTestDoxFormat() { + $dox = new TestDoxReporter(); + ob_start(); + $dox->paintMethodStart('testSomeGreatTestCase'); + $buffer = ob_get_clean(); + $this->assertEqual("- some great test case", $buffer); + unset($buffer); + + $random = rand(100, 200); + ob_start(); + $dox->paintMethodStart("testRandomNumberIs{$random}"); + $buffer = ob_get_clean(); + $this->assertEqual("- random number is {$random}", $buffer); + } + + function testDoesNotOutputAnythingOnNoneTestMethods() { + $dox = new TestDoxReporter(); + ob_start(); + $dox->paintMethodStart('nonMatchingMethod'); + $buffer = ob_get_clean(); + $this->assertEqual('', $buffer); + } + + function testPaintMethodAddLineBreak() { + $dox = new TestDoxReporter(); + ob_start(); + $dox->paintMethodEnd('someMethod'); + $buffer = ob_get_clean(); + $this->assertEqual("\n", $buffer); + } + + function testProperlySpacesSingleLettersInMethodName() { + $dox = new TestDoxReporter(); + ob_start(); + $dox->paintMethodStart('testAVerySimpleAgainAVerySimpleMethod'); + $buffer = ob_get_clean(); + $this->assertEqual('- a very simple again a very simple method', $buffer); + } + + function testOnFailureThisPrintsFailureNotice() { + $dox = new TestDoxReporter(); + ob_start(); + $dox->paintFail(''); + $buffer = ob_get_clean(); + $this->assertEqual(' [FAILED]', $buffer); + } + + function testWhenMatchingMethodNamesTestPrefixIsCaseInsensitive() { + $dox = new TestDoxReporter(); + ob_start(); + $dox->paintMethodStart('TESTSupportsAllUppercaseTestPrefixEvenThoughIDoNotKnowWhyYouWouldDoThat'); + $buffer = ob_get_clean(); + $this->assertEqual( + '- supports all uppercase test prefix even though i do not know why you would do that', + $buffer + ); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/treemap_reporter.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/treemap_reporter.php new file mode 100644 index 0000000..0dfacb4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/treemap_reporter.php @@ -0,0 +1,126 @@ +SimpleReporterDecorator(new TreemapRecorder()); + } + + /** + * basic CSS for floating nested divs + * @todo checkout some weird border bugs + */ + function _getCss() { + $css = ".pass{background-color:green;}.fail{background-color:red;}"; + $css .= "body {background-color:white;margin:0;padding:1em;}"; + $css .= "div{float:right;margin:0;color:black;}"; + $css .= "div{border-left:1px solid white;border-bottom:1px solid white;}"; + $css .= "h1 {font:normal 1.8em Arial;color:black;margin:0 0 0.3em 0.1em;}"; + $css .= ".clear { clear:both; }"; + return $css; + } + + /** + * paints the HTML header and sets up results + */ + function paintResultsHeader() { + $title = $this->_reporter->getTitle(); + echo ""; + echo "{$title}"; + echo ""; + echo ""; + echo "

    {$title}

    "; + } + + /** + * places a clearing break below the end of the test nodes + */ + function paintResultsFooter() { + echo "
    "; + echo ""; + } + + /** + * paints start tag for div representing a test node + */ + function paintRectangleStart($node, $horiz, $vert) { + $name = $node->getName(); + $description = $node->getDescription(); + $status = $node->getStatus(); + echo "
    "; + } + + /** + * paints end tag for test node div + */ + function paintRectangleEnd() { + echo "
    "; + } + + /** + * paints wrapping treemap divs + * @todo how to configure aspect and other parameters? + */ + function paintFooter($group) { + $aspect = 1; + $this->paintResultsHeader(); + $this->paintRectangleStart($this->_reporter->getGraph(), 100, 100); + $this->divideMapNodes($this->_reporter->getGraph(), $aspect); + $this->paintRectangleEnd(); + $this->paintResultsFooter(); + } + + /** + * divides the test results based on a slice and dice algorithm + * + * @param TreemapNode $map sorted + * @param boolean $aspect flips the aspect between horizontal and vertical + * @private + */ + function divideMapNodes($map, $aspect) { + $aspect = !$aspect; + $divisions = $map->getSize(); + $total = $map->getTotalSize(); + foreach($map->getChildren() as $node) { + if (!$node->isLeaf()) { + $dist = $node->getTotalSize() / $total * 100; + } else { + $dist = 1 / $total * 100; + } + if ($aspect) { + $horiz = $dist; + $vert = 100; + } else { + $horiz = 100; + $vert = $dist; + } + $this->paintRectangleStart($node, $horiz, $vert); + $this->divideMapNodes($node, $aspect); + $this->paintRectangleEnd(); + } + } + + function paintGroupEnd($group) { + $this->_reporter->paintGroupEnd($group); + if ($this->_reporter->isComplete()) { + $this->paintFooter($group); + } + } + +} + +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/treemap_reporter/jquery.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/treemap_reporter/jquery.php new file mode 100644 index 0000000..7de0e1e --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/treemap_reporter/jquery.php @@ -0,0 +1,75 @@ + representing treemap of test report, + * and attaches jQuery Treemap to render results. + * + * @package SimpleTest + * @subpackage Extensions + */ +class JqueryTreemapReporter extends TreemapReporter { + + function _getCss() { + $css = ".treemapView { color:white; } + .treemapCell {background-color:green;font-size:10px;font-family:Arial;} + .treemapHead {cursor:pointer;background-color:#B34700} + .treemapCell.selected, .treemapCell.selected .treemapCell.selected {background-color:#FFCC80} + .treemapCell.selected .treemapCell {background-color:#FF9900} + .treemapCell.selected .treemapHead {background-color:#B36B00} + .transfer {border:1px solid black}"; + return $css; + } + + function paintResultsHeader() { + $title = $this->_reporter->getTitle(); + echo ""; + echo "{$title}"; + echo ""; + echo ""; + echo ""; + echo ""; + echo "
      "; + } + + function paintRectangleStart($node) { + echo "
    • ". basename($node->getDescription()) . ""; + echo "" . $node->getTotalSize() . ""; + } + + function paintRectangleEnd() {} + + function paintResultsFooter() { + echo "
    "; + echo ""; + } + + function divideMapNodes($map) { + foreach($map->getChildren() as $node) { + if (!$node->isLeaf()) { + $this->paintRectangleStart($node); + $this->divideMapNodes($node); + } + } + } + +} + +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/treemap_reporter/test/treemap_node_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/treemap_reporter/test/treemap_node_test.php new file mode 100644 index 0000000..f3c6a23 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/treemap_reporter/test/treemap_node_test.php @@ -0,0 +1,69 @@ +assertEqual($node->getSize(), 0); + $this->assertEqual($node->getTotalSize(), 0); + } + + function testChildNodeDepth() { + $root = new TreemapNode("root", "test"); + $root->putChild(new TreemapNode("child", "test")); + $childOne = new TreemapNode("child1", "test"); + $childTwo = new TreemapNode("child2", "test"); + $childTwo->putChild(new TreemapNode("child3", "test")); + $childOne->putChild($childTwo); + $root->putChild($childOne); + $this->assertEqual($root->getSize(), 2); + $this->assertEqual($root->getTotalSize(), 4); + } + + function testGraphDepthSpread() { + $root = new TreemapNode("root", "test"); + $root->putChild(new TreemapNode("child", "test")); + $childOne = new TreemapNode("child1", "test"); + $childTwo = new TreemapNode("child2", "test"); + $childThree = new TreemapNode("child3", "test"); + $childFour = new TreemapNode("child4", "test"); + $childFive = new TreemapNode("child5", "test"); + $childSix = new TreemapNode("child6", "test"); + $childFour->putChild($childFive); + $childFour->putChild($childSix); + $this->assertEqual($childFour->getSize(), 2); + $this->assertEqual($childFour->getTotalSize(), 2); + $childThree->putChild($childFour); + $this->assertEqual($childThree->getSize(), 1); + $this->assertEqual($childThree->getTotalSize(), 3); + $childTwo->putChild($childThree); + $this->assertEqual($childTwo->getSize(), 1); + $this->assertEqual($childTwo->getTotalSize(), 4); + $childOne->putChild($childTwo); + $root->putChild($childOne); + $this->assertEqual($root->getSize(), 2); + $this->assertEqual($root->getTotalSize(), 7); + } + + function testMutableStack() { + $stack = new TreemapStack(); + $this->assertEqual($stack->size(), 0); + $stack->push(new TreemapNode("a", "one")); + $this->assertEqual($stack->size(), 1); + $stack->push(new TreemapNode("b", "one")); + $this->assertIdentical($stack->peek(), new TreemapNode("b", "one")); + $stack->push(new TreemapNode("c", "three")); + $stack->push(new TreemapNode("d", "four")); + $this->assertEqual($stack->size(), 4); + $this->assertIdentical($stack->pop(), new TreemapNode("d", "four")); + $this->assertEqual($stack->size(), 3); + $this->assertIdentical($stack->pop(), new TreemapNode("c", "three")); + $this->assertEqual($stack->size(), 2); + } + +} + +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/treemap_reporter/treemap_recorder.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/treemap_reporter/treemap_recorder.php new file mode 100644 index 0000000..0136e49 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/treemap_reporter/treemap_recorder.php @@ -0,0 +1,310 @@ +SimpleReporter(); + $this->_stack = new TreemapStack(); + $this->_graph = null; + } + + /** + * returns a reference to the root node of the + * collected treemap graph + */ + function getGraph() { + return $this->_graph; + } + + /** + * is this test run finished? + */ + function isComplete() { + return ($this->_graph != null); + } + + /** + * returns the title of the test + */ + function getTitle() { + return $this->_title; + } + + /** + * stashes the title of the test + */ + function paintHeader($title) { + $this->_title = $title; + } + + function paintFormattedMessage() { + } + + /** + * acceptor for start of test group node + */ + function paintGroupStart($message, $size) { + parent::paintGroupStart($message, $size); + $node = new TreemapNode("Group", $message); + $this->_stack->push($node); + } + + /** + * acceptor for start of test case node + */ + function paintCaseStart($message) { + parent::paintCaseStart($message); + $node = new TreemapNode("TestCase", $message); + $this->_stack->push($node); + } + + /** + * acceptor for start of test method node + */ + function paintMethodStart($message) { + parent::paintMethodStart($message); + $node = new TreemapNode("Method", $message); + $this->_stack->push($node); + } + + /** + * acceptor for passing assertion node + */ + function paintPass($message) { + parent::paintPass($message); + $node = new TreemapNode("Assertion", $message, true); + $current = $this->_stack->peek(); + if ($current) { + $current->putChild($node); + } else { + echo "no current node"; + } + } + + + /** + * acceptor for failing assertion node + */ + + function paintFail($message) { + parent::paintFail($message); + $node = new TreemapNode("Assertion", $message, false); + $current = $this->_stack->peek(); + $current->putChild($node); + $current->fail(); + } + + /** + * acceptor for end of method node + */ + function paintMethodEnd($message) { + parent::paintCaseEnd($message); + $node = $this->_stack->pop(); + $current = $this->_stack->peek(); + if ($node->isFailed()) $current->fail(); + $current->putChild($node); + } + + /** + * acceptor for end of test case + */ + function paintCaseEnd($message) { + parent::paintCaseEnd($message); + $node = $this->_stack->pop(); + $current = $this->_stack->peek(); + if ($node->isFailed()) $current->fail(); + $current->putChild($node); + } + + /** + * acceptor for end of test group. final group + * pops the collected treemap nodes and assigns + * it to the internal graph property. + */ + function paintGroupEnd($message) { + $node = $this->_stack->pop(); + $current = $this->_stack->peek(); + if ($current) { + if ($node->isFailed()) $current->fail(); + $current->putChild($node); + } else { + $this->_graph = $node; + } + parent::paintGroupEnd($message); + } + +} + +/** + * Creates a treemap graph, representing + * each node in a test visualization. + * + * @package SimpleTest + * @subpackage Extensions + */ +class TreemapNode { + var $_name; + var $_description; + var $_status; + var $_parent; + var $_size; + + function TreemapNode($name, $description, $status=true) { + $this->_name = $name; + $this->_description = $description; + $this->_status = $status; + $this->_children = array(); + } + + /** + * @return string label of this node + */ + function getName() { + return $this->_name; + } + + /** + * @return string description of this node + */ + function getDescription() { + return $this->_description; + } + + /** + * @return string status class string + */ + function getStatus() { + return ($this->_status) ? "pass" : "fail"; + } + + /** + * Return list of child nodes from direct edges. + */ + function getChildren() { + uksort($this->_children, array($this, 'compareChildren')); + return $this->_children; + } + + /** + * Comparator method to rank child nodes by total weight. + */ + function compareChildren($a, $b) { + if ($this->_children[$a]->getTotalSize() > $this->_children[$b]->getTotalSize()) { + $node_a = $this->_children[$a]; + $node_b = $this->_children[$b]; + $this->_children[$a] = $node_b; + $this->_children[$b] = $node_a; + } + } + + /** + * Gets the number of immediate child edges from this node. + */ + function getSize() { + return count($this->_children); + } + + /** + * depth first search to get the total number of nodes + * that are descendants of this node. + */ + function getTotalSize() { + if (!isset($this->_size)) { + $size = $this->getSize(); + if (!$this->isLeaf()) { + foreach($this->getChildren() as $child) { + $size += $child->getTotalSize(); + } + } + $this->_size = $size; + } + return $this->_size; + } + + /** + * Fail this node. + * @return void + */ + function fail() { + $this->_status = false; + } + + /** Is this node failed? */ + function isFailed() { + return ($this->_status == false); + } + + /** Add an edge to a child node */ + function putChild($node) { + $this->_children[] = $node; + } + + /** Is this node a leaf node? */ + function isLeaf() { + return (count($this->_children) == 0); + } + +} + +/** + * provides LIFO stack semantics + * + * @package SimpleTest + * @subpackage Extensions + */ +class TreemapStack { + var $_list; + + function TreemapStack() { + $this->_list = array(); + } + + /** + * Push an element onto the stack. + */ + function push($node) { + $this->_list[] = $node; + } + + /** + * Number of elements in the stack. + */ + function size() { + return count($this->_list); + } + + /** + * Take a peek at the top element on the + * stack. + */ + function peek() { + return end($this->_list); + } + + /** + * Pops an element off the stack. + */ + function pop() { + return array_pop($this->_list); + } + +} + +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/webunit_reporter.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/webunit_reporter.php new file mode 100644 index 0000000..52d25eb --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/webunit_reporter.php @@ -0,0 +1,285 @@ + + +%s + + + + + + +
    +

     Running %s 

    + Please wait...
    +
      +
    + +
    +

    +
    +
    visible tab content
    +     Fail   +   Tree   +
    +
    Click on a failed test case method in the tree tab to view output here.
    +
    +
    +
    + +'.$this->outputScript("xHide('wait');"); + $colour = ($this->getFailCount() + $this->getExceptionCount() > 0 ? "red" : "green"); + $content = "

    $test_name

    \n"; + $content .= "
    "; + $content .= $this->getTestCaseProgress() . "/" . $this->getTestCaseCount(); + $content .= " test cases complete:\n"; + $content .= "" . $this->getPassCount() . " passes, "; + $content .= "" . $this->getFailCount() . " fails and "; + $content .= "" . $this->getExceptionCount() . " exceptions."; + $content .= "
    \n"; + + echo $this->outputScript('foo = "'.$this->toJsString($content).'";'."\nset_div_content('run', foo);"); + echo "\n\n\n"; + } + + + /** + * Paints formatted text such as dumped variables. + * @param string $message Text to show. + * @access public + */ + function paintFormattedMessage($message) { + echo "add_log(\"".$this->toJsString("
    $message
    ", true)."\");\n"; + } + + /** + * Paints the start of a group test. Will also paint + * the page header and footer if this is the + * first test. Will stash the size if the first + * start. + * @param string $test_name Name of test that is starting. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_name, $size) { + Parent::paintGroupStart($test_name, $size); + echo "add_group('$test_name');\n"; + } + + /** + * Paints the start of a test case. Will also paint + * the page header and footer if this is the + * first test. Will stash the size if the first + * start. + * @param string $test_name Name of test that is starting. + * @access public + */ + function paintCaseStart($test_name) { + Parent::paintCaseStart($test_name); + echo "add_case('$test_name');\n"; + } + + + /** + * Paints the start of a test method. + * @param string $test_name Name of test that is starting. + * @access public + */ + function paintMethodStart($test_name) { + Parent::paintMethodStart($test_name); + echo "add_method('$test_name');\n"; + } + + /** + * Paints the end of a test method. + * @param string $test_name Name of test that is ending. + * @access public + */ + function paintMethodEnd($test_name) { + Parent::paintMethodEnd($test_name); + } + + /** + * Paints the test failure with a breadcrumbs + * trail of the nesting test suites below the + * top level test. + * @param string $message Failure message displayed in + * the context of the other tests. + * @access public + */ + function paintFail($message) { + parent::paintFail($message); + $msg = "Fail: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + $msg .= implode("->", $breadcrumb); + $msg .= "->" . htmlentities($message) . "
    "; + echo "add_fail('$msg');\n"; + } + + /** + * Paints a PHP error or exception. + * @param string $message Message is ignored. + * @access public + * @abstract + */ + function paintException($message) { + parent::paintException($message); + $msg = "Exception: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + $msg .= implode("->", $breadcrumb); + $msg .= "->" . htmlentities($message) . "
    "; + echo "add_fail('$msg');\n"; + } + + /** + * Returns the script passed in wrapped in script tags. + * + * @param string $script the script to output + * @return string the script wrapped with script tags + */ + function outputScript($script) + { + return "\n"; + } + + + /** + * Transform a string into a format acceptable to JavaScript + * @param string $str the string to transform + * @return string + */ + function toJsString($str, $preserveCr=false) { + $cr = ($preserveCr) ? '\\n' : ''; + return str_replace( + array('"' + ,"\n") + ,array('\"' + ,"$cr\"\n\t+\"") + ,$str + ); + } + } + +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/form.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/form.php new file mode 100644 index 0000000..d14f0f7 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/form.php @@ -0,0 +1,355 @@ +method = $tag->getAttribute('method'); + $this->action = $this->createAction($tag->getAttribute('action'), $page); + $this->encoding = $this->setEncodingClass($tag); + $this->default_target = false; + $this->id = $tag->getAttribute('id'); + $this->buttons = array(); + $this->images = array(); + $this->widgets = array(); + $this->radios = array(); + $this->checkboxes = array(); + } + + /** + * Creates the request packet to be sent by the form. + * @param SimpleTag $tag Form tag to read. + * @return string Packet class. + * @access private + */ + protected function setEncodingClass($tag) { + if (strtolower($tag->getAttribute('method')) == 'post') { + if (strtolower($tag->getAttribute('enctype')) == 'multipart/form-data') { + return 'SimpleMultipartEncoding'; + } + return 'SimplePostEncoding'; + } + return 'SimpleGetEncoding'; + } + + /** + * Sets the frame target within a frameset. + * @param string $frame Name of frame. + * @access public + */ + function setDefaultTarget($frame) { + $this->default_target = $frame; + } + + /** + * Accessor for method of form submission. + * @return string Either get or post. + * @access public + */ + function getMethod() { + return ($this->method ? strtolower($this->method) : 'get'); + } + + /** + * Combined action attribute with current location + * to get an absolute form target. + * @param string $action Action attribute from form tag. + * @param SimpleUrl $base Page location. + * @return SimpleUrl Absolute form target. + */ + protected function createAction($action, $page) { + if (($action === '') || ($action === false)) { + return $page->expandUrl($page->getUrl()); + } + return $page->expandUrl(new SimpleUrl($action));; + } + + /** + * Absolute URL of the target. + * @return SimpleUrl URL target. + * @access public + */ + function getAction() { + $url = $this->action; + if ($this->default_target && ! $url->getTarget()) { + $url->setTarget($this->default_target); + } + return $url; + } + + /** + * Creates the encoding for the current values in the + * form. + * @return SimpleFormEncoding Request to submit. + * @access private + */ + protected function encode() { + $class = $this->encoding; + $encoding = new $class(); + for ($i = 0, $count = count($this->widgets); $i < $count; $i++) { + $this->widgets[$i]->write($encoding); + } + return $encoding; + } + + /** + * ID field of form for unique identification. + * @return string Unique tag ID. + * @access public + */ + function getId() { + return $this->id; + } + + /** + * Adds a tag contents to the form. + * @param SimpleWidget $tag Input tag to add. + * @access public + */ + function addWidget($tag) { + if (strtolower($tag->getAttribute('type')) == 'submit') { + $this->buttons[] = $tag; + } elseif (strtolower($tag->getAttribute('type')) == 'image') { + $this->images[] = $tag; + } elseif ($tag->getName()) { + $this->setWidget($tag); + } + } + + /** + * Sets the widget into the form, grouping radio + * buttons if any. + * @param SimpleWidget $tag Incoming form control. + * @access private + */ + protected function setWidget($tag) { + if (strtolower($tag->getAttribute('type')) == 'radio') { + $this->addRadioButton($tag); + } elseif (strtolower($tag->getAttribute('type')) == 'checkbox') { + $this->addCheckbox($tag); + } else { + $this->widgets[] = &$tag; + } + } + + /** + * Adds a radio button, building a group if necessary. + * @param SimpleRadioButtonTag $tag Incoming form control. + * @access private + */ + protected function addRadioButton($tag) { + if (! isset($this->radios[$tag->getName()])) { + $this->widgets[] = new SimpleRadioGroup(); + $this->radios[$tag->getName()] = count($this->widgets) - 1; + } + $this->widgets[$this->radios[$tag->getName()]]->addWidget($tag); + } + + /** + * Adds a checkbox, making it a group on a repeated name. + * @param SimpleCheckboxTag $tag Incoming form control. + * @access private + */ + protected function addCheckbox($tag) { + if (! isset($this->checkboxes[$tag->getName()])) { + $this->widgets[] = $tag; + $this->checkboxes[$tag->getName()] = count($this->widgets) - 1; + } else { + $index = $this->checkboxes[$tag->getName()]; + if (! SimpleTestCompatibility::isA($this->widgets[$index], 'SimpleCheckboxGroup')) { + $previous = $this->widgets[$index]; + $this->widgets[$index] = new SimpleCheckboxGroup(); + $this->widgets[$index]->addWidget($previous); + } + $this->widgets[$index]->addWidget($tag); + } + } + + /** + * Extracts current value from form. + * @param SimpleSelector $selector Criteria to apply. + * @return string/array Value(s) as string or null + * if not set. + * @access public + */ + function getValue($selector) { + for ($i = 0, $count = count($this->widgets); $i < $count; $i++) { + if ($selector->isMatch($this->widgets[$i])) { + return $this->widgets[$i]->getValue(); + } + } + foreach ($this->buttons as $button) { + if ($selector->isMatch($button)) { + return $button->getValue(); + } + } + return null; + } + + /** + * Sets a widget value within the form. + * @param SimpleSelector $selector Criteria to apply. + * @param string $value Value to input into the widget. + * @return boolean True if value is legal, false + * otherwise. If the field is not + * present, nothing will be set. + * @access public + */ + function setField($selector, $value, $position=false) { + $success = false; + $_position = 0; + for ($i = 0, $count = count($this->widgets); $i < $count; $i++) { + if ($selector->isMatch($this->widgets[$i])) { + $_position++; + if ($position === false or $_position === (int)$position) { + if ($this->widgets[$i]->setValue($value)) { + $success = true; + } + } + } + } + return $success; + } + + /** + * Used by the page object to set widgets labels to + * external label tags. + * @param SimpleSelector $selector Criteria to apply. + * @access public + */ + function attachLabelBySelector($selector, $label) { + for ($i = 0, $count = count($this->widgets); $i < $count; $i++) { + if ($selector->isMatch($this->widgets[$i])) { + if (method_exists($this->widgets[$i], 'setLabel')) { + $this->widgets[$i]->setLabel($label); + return; + } + } + } + } + + /** + * Test to see if a form has a submit button. + * @param SimpleSelector $selector Criteria to apply. + * @return boolean True if present. + * @access public + */ + function hasSubmit($selector) { + foreach ($this->buttons as $button) { + if ($selector->isMatch($button)) { + return true; + } + } + return false; + } + + /** + * Test to see if a form has an image control. + * @param SimpleSelector $selector Criteria to apply. + * @return boolean True if present. + * @access public + */ + function hasImage($selector) { + foreach ($this->images as $image) { + if ($selector->isMatch($image)) { + return true; + } + } + return false; + } + + /** + * Gets the submit values for a selected button. + * @param SimpleSelector $selector Criteria to apply. + * @param hash $additional Additional data for the form. + * @return SimpleEncoding Submitted values or false + * if there is no such button + * in the form. + * @access public + */ + function submitButton($selector, $additional = false) { + $additional = $additional ? $additional : array(); + foreach ($this->buttons as $button) { + if ($selector->isMatch($button)) { + $encoding = $this->encode(); + $button->write($encoding); + if ($additional) { + $encoding->merge($additional); + } + return $encoding; + } + } + return false; + } + + /** + * Gets the submit values for an image. + * @param SimpleSelector $selector Criteria to apply. + * @param integer $x X-coordinate of click. + * @param integer $y Y-coordinate of click. + * @param hash $additional Additional data for the form. + * @return SimpleEncoding Submitted values or false + * if there is no such button in the + * form. + * @access public + */ + function submitImage($selector, $x, $y, $additional = false) { + $additional = $additional ? $additional : array(); + foreach ($this->images as $image) { + if ($selector->isMatch($image)) { + $encoding = $this->encode(); + $image->write($encoding, $x, $y); + if ($additional) { + $encoding->merge($additional); + } + return $encoding; + } + } + return false; + } + + /** + * Simply submits the form without the submit button + * value. Used when there is only one button or it + * is unimportant. + * @return hash Submitted values. + * @access public + */ + function submit() { + return $this->encode(); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/frames.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/frames.php new file mode 100644 index 0000000..d6d8e96 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/frames.php @@ -0,0 +1,592 @@ +frameset = $page; + $this->frames = array(); + $this->focus = false; + $this->names = array(); + } + + /** + * Adds a parsed page to the frameset. + * @param SimplePage $page Frame page. + * @param string $name Name of frame in frameset. + * @access public + */ + function addFrame($page, $name = false) { + $this->frames[] = $page; + if ($name) { + $this->names[$name] = count($this->frames) - 1; + } + } + + /** + * Replaces existing frame with another. If the + * frame is nested, then the call is passed down + * one level. + * @param array $path Path of frame in frameset. + * @param SimplePage $page Frame source. + * @access public + */ + function setFrame($path, $page) { + $name = array_shift($path); + if (isset($this->names[$name])) { + $index = $this->names[$name]; + } else { + $index = $name - 1; + } + if (count($path) == 0) { + $this->frames[$index] = &$page; + return; + } + $this->frames[$index]->setFrame($path, $page); + } + + /** + * Accessor for current frame focus. Will be + * false if no frame has focus. Will have the nested + * frame focus if any. + * @return array Labels or indexes of nested frames. + * @access public + */ + function getFrameFocus() { + if ($this->focus === false) { + return array(); + } + return array_merge( + array($this->getPublicNameFromIndex($this->focus)), + $this->frames[$this->focus]->getFrameFocus()); + } + + /** + * Turns an internal array index into the frames list + * into a public name, or if none, then a one offset + * index. + * @param integer $subject Internal index. + * @return integer/string Public name. + * @access private + */ + protected function getPublicNameFromIndex($subject) { + foreach ($this->names as $name => $index) { + if ($subject == $index) { + return $name; + } + } + return $subject + 1; + } + + /** + * Sets the focus by index. The integer index starts from 1. + * If already focused and the target frame also has frames, + * then the nested frame will be focused. + * @param integer $choice Chosen frame. + * @return boolean True if frame exists. + * @access public + */ + function setFrameFocusByIndex($choice) { + if (is_integer($this->focus)) { + if ($this->frames[$this->focus]->hasFrames()) { + return $this->frames[$this->focus]->setFrameFocusByIndex($choice); + } + } + if (($choice < 1) || ($choice > count($this->frames))) { + return false; + } + $this->focus = $choice - 1; + return true; + } + + /** + * Sets the focus by name. If already focused and the + * target frame also has frames, then the nested frame + * will be focused. + * @param string $name Chosen frame. + * @return boolean True if frame exists. + * @access public + */ + function setFrameFocus($name) { + if (is_integer($this->focus)) { + if ($this->frames[$this->focus]->hasFrames()) { + return $this->frames[$this->focus]->setFrameFocus($name); + } + } + if (in_array($name, array_keys($this->names))) { + $this->focus = $this->names[$name]; + return true; + } + return false; + } + + /** + * Clears the frame focus. + * @access public + */ + function clearFrameFocus() { + $this->focus = false; + $this->clearNestedFramesFocus(); + } + + /** + * Clears the frame focus for any nested frames. + * @access private + */ + protected function clearNestedFramesFocus() { + for ($i = 0; $i < count($this->frames); $i++) { + $this->frames[$i]->clearFrameFocus(); + } + } + + /** + * Test for the presence of a frameset. + * @return boolean Always true. + * @access public + */ + function hasFrames() { + return true; + } + + /** + * Accessor for frames information. + * @return array/string Recursive hash of frame URL strings. + * The key is either a numerical + * index or the name attribute. + * @access public + */ + function getFrames() { + $report = array(); + for ($i = 0; $i < count($this->frames); $i++) { + $report[$this->getPublicNameFromIndex($i)] = + $this->frames[$i]->getFrames(); + } + return $report; + } + + /** + * Accessor for raw text of either all the pages or + * the frame in focus. + * @return string Raw unparsed content. + * @access public + */ + function getRaw() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getRaw(); + } + $raw = ''; + for ($i = 0; $i < count($this->frames); $i++) { + $raw .= $this->frames[$i]->getRaw(); + } + return $raw; + } + + /** + * Accessor for plain text of either all the pages or + * the frame in focus. + * @return string Plain text content. + * @access public + */ + function getText() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getText(); + } + $raw = ''; + for ($i = 0; $i < count($this->frames); $i++) { + $raw .= ' ' . $this->frames[$i]->getText(); + } + return trim($raw); + } + + /** + * Accessor for last error. + * @return string Error from last response. + * @access public + */ + function getTransportError() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getTransportError(); + } + return $this->frameset->getTransportError(); + } + + /** + * Request method used to fetch this frame. + * @return string GET, POST or HEAD. + * @access public + */ + function getMethod() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getMethod(); + } + return $this->frameset->getMethod(); + } + + /** + * Original resource name. + * @return SimpleUrl Current url. + * @access public + */ + function getUrl() { + if (is_integer($this->focus)) { + $url = $this->frames[$this->focus]->getUrl(); + $url->setTarget($this->getPublicNameFromIndex($this->focus)); + } else { + $url = $this->frameset->getUrl(); + } + return $url; + } + + /** + * Page base URL. + * @return SimpleUrl Current url. + * @access public + */ + function getBaseUrl() { + if (is_integer($this->focus)) { + $url = $this->frames[$this->focus]->getBaseUrl(); + } else { + $url = $this->frameset->getBaseUrl(); + } + return $url; + } + + /** + * Expands expandomatic URLs into fully qualified + * URLs for the frameset page. + * @param SimpleUrl $url Relative URL. + * @return SimpleUrl Absolute URL. + * @access public + */ + function expandUrl($url) { + return $this->frameset->expandUrl($url); + } + + /** + * Original request data. + * @return mixed Sent content. + * @access public + */ + function getRequestData() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getRequestData(); + } + return $this->frameset->getRequestData(); + } + + /** + * Accessor for current MIME type. + * @return string MIME type as string; e.g. 'text/html' + * @access public + */ + function getMimeType() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getMimeType(); + } + return $this->frameset->getMimeType(); + } + + /** + * Accessor for last response code. + * @return integer Last HTTP response code received. + * @access public + */ + function getResponseCode() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getResponseCode(); + } + return $this->frameset->getResponseCode(); + } + + /** + * Accessor for last Authentication type. Only valid + * straight after a challenge (401). + * @return string Description of challenge type. + * @access public + */ + function getAuthentication() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getAuthentication(); + } + return $this->frameset->getAuthentication(); + } + + /** + * Accessor for last Authentication realm. Only valid + * straight after a challenge (401). + * @return string Name of security realm. + * @access public + */ + function getRealm() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getRealm(); + } + return $this->frameset->getRealm(); + } + + /** + * Accessor for outgoing header information. + * @return string Header block. + * @access public + */ + function getRequest() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getRequest(); + } + return $this->frameset->getRequest(); + } + + /** + * Accessor for raw header information. + * @return string Header block. + * @access public + */ + function getHeaders() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getHeaders(); + } + return $this->frameset->getHeaders(); + } + + /** + * Accessor for parsed title. + * @return string Title or false if no title is present. + * @access public + */ + function getTitle() { + return $this->frameset->getTitle(); + } + + /** + * Accessor for a list of all fixed links. + * @return array List of urls as strings. + * @access public + */ + function getUrls() { + if (is_integer($this->focus)) { + return $this->frames[$this->focus]->getUrls(); + } + $urls = array(); + foreach ($this->frames as $frame) { + $urls = array_merge($urls, $frame->getUrls()); + } + return array_values(array_unique($urls)); + } + + /** + * Accessor for URLs by the link label. Label will match + * regardess of whitespace issues and case. + * @param string $label Text of link. + * @return array List of links with that label. + * @access public + */ + function getUrlsByLabel($label) { + if (is_integer($this->focus)) { + return $this->tagUrlsWithFrame( + $this->frames[$this->focus]->getUrlsByLabel($label), + $this->focus); + } + $urls = array(); + foreach ($this->frames as $index => $frame) { + $urls = array_merge( + $urls, + $this->tagUrlsWithFrame( + $frame->getUrlsByLabel($label), + $index)); + } + return $urls; + } + + /** + * Accessor for a URL by the id attribute. If in a frameset + * then the first link found with that ID attribute is + * returned only. Focus on a frame if you want one from + * a specific part of the frameset. + * @param string $id Id attribute of link. + * @return string URL with that id. + * @access public + */ + function getUrlById($id) { + foreach ($this->frames as $index => $frame) { + if ($url = $frame->getUrlById($id)) { + if (! $url->gettarget()) { + $url->setTarget($this->getPublicNameFromIndex($index)); + } + return $url; + } + } + return false; + } + + /** + * Attaches the intended frame index to a list of URLs. + * @param array $urls List of SimpleUrls. + * @param string $frame Name of frame or index. + * @return array List of tagged URLs. + * @access private + */ + protected function tagUrlsWithFrame($urls, $frame) { + $tagged = array(); + foreach ($urls as $url) { + if (! $url->getTarget()) { + $url->setTarget($this->getPublicNameFromIndex($frame)); + } + $tagged[] = $url; + } + return $tagged; + } + + /** + * Finds a held form by button label. Will only + * search correctly built forms. + * @param SimpleSelector $selector Button finder. + * @return SimpleForm Form object containing + * the button. + * @access public + */ + function getFormBySubmit($selector) { + return $this->findForm('getFormBySubmit', $selector); + } + + /** + * Finds a held form by image using a selector. + * Will only search correctly built forms. The first + * form found either within the focused frame, or + * across frames, will be the one returned. + * @param SimpleSelector $selector Image finder. + * @return SimpleForm Form object containing + * the image. + * @access public + */ + function getFormByImage($selector) { + return $this->findForm('getFormByImage', $selector); + } + + /** + * Finds a held form by the form ID. A way of + * identifying a specific form when we have control + * of the HTML code. The first form found + * either within the focused frame, or across frames, + * will be the one returned. + * @param string $id Form label. + * @return SimpleForm Form object containing the matching ID. + * @access public + */ + function getFormById($id) { + return $this->findForm('getFormById', $id); + } + + /** + * General form finder. Will search all the frames or + * just the one in focus. + * @param string $method Method to use to find in a page. + * @param string $attribute Label, name or ID. + * @return SimpleForm Form object containing the matching ID. + * @access private + */ + protected function findForm($method, $attribute) { + if (is_integer($this->focus)) { + return $this->findFormInFrame( + $this->frames[$this->focus], + $this->focus, + $method, + $attribute); + } + for ($i = 0; $i < count($this->frames); $i++) { + $form = $this->findFormInFrame( + $this->frames[$i], + $i, + $method, + $attribute); + if ($form) { + return $form; + } + } + $null = null; + return $null; + } + + /** + * Finds a form in a page using a form finding method. Will + * also tag the form with the frame name it belongs in. + * @param SimplePage $page Page content of frame. + * @param integer $index Internal frame representation. + * @param string $method Method to use to find in a page. + * @param string $attribute Label, name or ID. + * @return SimpleForm Form object containing the matching ID. + * @access private + */ + protected function findFormInFrame($page, $index, $method, $attribute) { + $form = $this->frames[$index]->$method($attribute); + if (isset($form)) { + $form->setDefaultTarget($this->getPublicNameFromIndex($index)); + } + return $form; + } + + /** + * Sets a field on each form in which the field is + * available. + * @param SimpleSelector $selector Field finder. + * @param string $value Value to set field to. + * @return boolean True if value is valid. + * @access public + */ + function setField($selector, $value) { + if (is_integer($this->focus)) { + $this->frames[$this->focus]->setField($selector, $value); + } else { + for ($i = 0; $i < count($this->frames); $i++) { + $this->frames[$i]->setField($selector, $value); + } + } + } + + /** + * Accessor for a form element value within a page. + * @param SimpleSelector $selector Field finder. + * @return string/boolean A string if the field is + * present, false if unchecked + * and null if missing. + * @access public + */ + function getField($selector) { + for ($i = 0; $i < count($this->frames); $i++) { + $value = $this->frames[$i]->getField($selector); + if (isset($value)) { + return $value; + } + } + return null; + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/http.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/http.php new file mode 100644 index 0000000..15b555c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/http.php @@ -0,0 +1,628 @@ +url = $url; + } + + /** + * Resource name. + * @return SimpleUrl Current url. + * @access protected + */ + function getUrl() { + return $this->url; + } + + /** + * Creates the first line which is the actual request. + * @param string $method HTTP request method, usually GET. + * @return string Request line content. + * @access protected + */ + protected function getRequestLine($method) { + return $method . ' ' . $this->url->getPath() . + $this->url->getEncodedRequest() . ' HTTP/1.0'; + } + + /** + * Creates the host part of the request. + * @return string Host line content. + * @access protected + */ + protected function getHostLine() { + $line = 'Host: ' . $this->url->getHost(); + if ($this->url->getPort()) { + $line .= ':' . $this->url->getPort(); + } + return $line; + } + + /** + * Opens a socket to the route. + * @param string $method HTTP request method, usually GET. + * @param integer $timeout Connection timeout. + * @return SimpleSocket New socket. + * @access public + */ + function createConnection($method, $timeout) { + $default_port = ('https' == $this->url->getScheme()) ? 443 : 80; + $socket = $this->createSocket( + $this->url->getScheme() ? $this->url->getScheme() : 'http', + $this->url->getHost(), + $this->url->getPort() ? $this->url->getPort() : $default_port, + $timeout); + if (! $socket->isError()) { + $socket->write($this->getRequestLine($method) . "\r\n"); + $socket->write($this->getHostLine() . "\r\n"); + $socket->write("Connection: close\r\n"); + } + return $socket; + } + + /** + * Factory for socket. + * @param string $scheme Protocol to use. + * @param string $host Hostname to connect to. + * @param integer $port Remote port. + * @param integer $timeout Connection timeout. + * @return SimpleSocket/SimpleSecureSocket New socket. + * @access protected + */ + protected function createSocket($scheme, $host, $port, $timeout) { + if (in_array($scheme, array('file'))) { + return new SimpleFileSocket($this->url); + } elseif (in_array($scheme, array('https'))) { + return new SimpleSecureSocket($host, $port, $timeout); + } else { + return new SimpleSocket($host, $port, $timeout); + } + } +} + +/** + * Creates HTTP headers for the end point of + * a HTTP request via a proxy server. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleProxyRoute extends SimpleRoute { + private $proxy; + private $username; + private $password; + + /** + * Stashes the proxy address. + * @param SimpleUrl $url URL as object. + * @param string $proxy Proxy URL. + * @param string $username Username for autentication. + * @param string $password Password for autentication. + * @access public + */ + function __construct($url, $proxy, $username = false, $password = false) { + parent::__construct($url); + $this->proxy = $proxy; + $this->username = $username; + $this->password = $password; + } + + /** + * Creates the first line which is the actual request. + * @param string $method HTTP request method, usually GET. + * @param SimpleUrl $url URL as object. + * @return string Request line content. + * @access protected + */ + function getRequestLine($method) { + $url = $this->getUrl(); + $scheme = $url->getScheme() ? $url->getScheme() : 'http'; + $port = $url->getPort() ? ':' . $url->getPort() : ''; + return $method . ' ' . $scheme . '://' . $url->getHost() . $port . + $url->getPath() . $url->getEncodedRequest() . ' HTTP/1.0'; + } + + /** + * Creates the host part of the request. + * @param SimpleUrl $url URL as object. + * @return string Host line content. + * @access protected + */ + function getHostLine() { + $host = 'Host: ' . $this->proxy->getHost(); + $port = $this->proxy->getPort() ? $this->proxy->getPort() : 8080; + return "$host:$port"; + } + + /** + * Opens a socket to the route. + * @param string $method HTTP request method, usually GET. + * @param integer $timeout Connection timeout. + * @return SimpleSocket New socket. + * @access public + */ + function createConnection($method, $timeout) { + $socket = $this->createSocket( + $this->proxy->getScheme() ? $this->proxy->getScheme() : 'http', + $this->proxy->getHost(), + $this->proxy->getPort() ? $this->proxy->getPort() : 8080, + $timeout); + if ($socket->isError()) { + return $socket; + } + $socket->write($this->getRequestLine($method) . "\r\n"); + $socket->write($this->getHostLine() . "\r\n"); + if ($this->username && $this->password) { + $socket->write('Proxy-Authorization: Basic ' . + base64_encode($this->username . ':' . $this->password) . + "\r\n"); + } + $socket->write("Connection: close\r\n"); + return $socket; + } +} + +/** + * HTTP request for a web page. Factory for + * HttpResponse object. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHttpRequest { + private $route; + private $encoding; + private $headers; + private $cookies; + + /** + * Builds the socket request from the different pieces. + * These include proxy information, URL, cookies, headers, + * request method and choice of encoding. + * @param SimpleRoute $route Request route. + * @param SimpleFormEncoding $encoding Content to send with + * request. + * @access public + */ + function __construct($route, $encoding) { + $this->route = $route; + $this->encoding = $encoding; + $this->headers = array(); + $this->cookies = array(); + } + + /** + * Dispatches the content to the route's socket. + * @param integer $timeout Connection timeout. + * @return SimpleHttpResponse A response which may only have + * an error, but hopefully has a + * complete web page. + * @access public + */ + function fetch($timeout) { + $socket = $this->route->createConnection($this->encoding->getMethod(), $timeout); + if (! $socket->isError()) { + $this->dispatchRequest($socket, $this->encoding); + } + return $this->createResponse($socket); + } + + /** + * Sends the headers. + * @param SimpleSocket $socket Open socket. + * @param string $method HTTP request method, + * usually GET. + * @param SimpleFormEncoding $encoding Content to send with request. + * @access private + */ + protected function dispatchRequest($socket, $encoding) { + foreach ($this->headers as $header_line) { + $socket->write($header_line . "\r\n"); + } + if (count($this->cookies) > 0) { + $socket->write("Cookie: " . implode(";", $this->cookies) . "\r\n"); + } + $encoding->writeHeadersTo($socket); + $socket->write("\r\n"); + $encoding->writeTo($socket); + } + + /** + * Adds a header line to the request. + * @param string $header_line Text of full header line. + * @access public + */ + function addHeaderLine($header_line) { + $this->headers[] = $header_line; + } + + /** + * Reads all the relevant cookies from the + * cookie jar. + * @param SimpleCookieJar $jar Jar to read + * @param SimpleUrl $url Url to use for scope. + * @access public + */ + function readCookiesFromJar($jar, $url) { + $this->cookies = $jar->selectAsPairs($url); + } + + /** + * Wraps the socket in a response parser. + * @param SimpleSocket $socket Responding socket. + * @return SimpleHttpResponse Parsed response object. + * @access protected + */ + protected function createResponse($socket) { + $response = new SimpleHttpResponse( + $socket, + $this->route->getUrl(), + $this->encoding); + $socket->close(); + return $response; + } +} + +/** + * Collection of header lines in the response. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHttpHeaders { + private $raw_headers; + private $response_code; + private $http_version; + private $mime_type; + private $location; + private $cookies; + private $authentication; + private $realm; + + /** + * Parses the incoming header block. + * @param string $headers Header block. + * @access public + */ + function __construct($headers) { + $this->raw_headers = $headers; + $this->response_code = false; + $this->http_version = false; + $this->mime_type = ''; + $this->location = false; + $this->cookies = array(); + $this->authentication = false; + $this->realm = false; + foreach (split("\r\n", $headers) as $header_line) { + $this->parseHeaderLine($header_line); + } + } + + /** + * Accessor for parsed HTTP protocol version. + * @return integer HTTP error code. + * @access public + */ + function getHttpVersion() { + return $this->http_version; + } + + /** + * Accessor for raw header block. + * @return string All headers as raw string. + * @access public + */ + function getRaw() { + return $this->raw_headers; + } + + /** + * Accessor for parsed HTTP error code. + * @return integer HTTP error code. + * @access public + */ + function getResponseCode() { + return (integer)$this->response_code; + } + + /** + * Returns the redirected URL or false if + * no redirection. + * @return string URL or false for none. + * @access public + */ + function getLocation() { + return $this->location; + } + + /** + * Test to see if the response is a valid redirect. + * @return boolean True if valid redirect. + * @access public + */ + function isRedirect() { + return in_array($this->response_code, array(301, 302, 303, 307)) && + (boolean)$this->getLocation(); + } + + /** + * Test to see if the response is an authentication + * challenge. + * @return boolean True if challenge. + * @access public + */ + function isChallenge() { + return ($this->response_code == 401) && + (boolean)$this->authentication && + (boolean)$this->realm; + } + + /** + * Accessor for MIME type header information. + * @return string MIME type. + * @access public + */ + function getMimeType() { + return $this->mime_type; + } + + /** + * Accessor for authentication type. + * @return string Type. + * @access public + */ + function getAuthentication() { + return $this->authentication; + } + + /** + * Accessor for security realm. + * @return string Realm. + * @access public + */ + function getRealm() { + return $this->realm; + } + + /** + * Writes new cookies to the cookie jar. + * @param SimpleCookieJar $jar Jar to write to. + * @param SimpleUrl $url Host and path to write under. + * @access public + */ + function writeCookiesToJar($jar, $url) { + foreach ($this->cookies as $cookie) { + $jar->setCookie( + $cookie->getName(), + $cookie->getValue(), + $url->getHost(), + $cookie->getPath(), + $cookie->getExpiry()); + } + } + + /** + * Called on each header line to accumulate the held + * data within the class. + * @param string $header_line One line of header. + * @access protected + */ + protected function parseHeaderLine($header_line) { + if (preg_match('/HTTP\/(\d+\.\d+)\s+(\d+)/i', $header_line, $matches)) { + $this->http_version = $matches[1]; + $this->response_code = $matches[2]; + } + if (preg_match('/Content-type:\s*(.*)/i', $header_line, $matches)) { + $this->mime_type = trim($matches[1]); + } + if (preg_match('/Location:\s*(.*)/i', $header_line, $matches)) { + $this->location = trim($matches[1]); + } + if (preg_match('/Set-cookie:(.*)/i', $header_line, $matches)) { + $this->cookies[] = $this->parseCookie($matches[1]); + } + if (preg_match('/WWW-Authenticate:\s+(\S+)\s+realm=\"(.*?)\"/i', $header_line, $matches)) { + $this->authentication = $matches[1]; + $this->realm = trim($matches[2]); + } + } + + /** + * Parse the Set-cookie content. + * @param string $cookie_line Text after "Set-cookie:" + * @return SimpleCookie New cookie object. + * @access private + */ + protected function parseCookie($cookie_line) { + $parts = split(";", $cookie_line); + $cookie = array(); + preg_match('/\s*(.*?)\s*=(.*)/', array_shift($parts), $cookie); + foreach ($parts as $part) { + if (preg_match('/\s*(.*?)\s*=(.*)/', $part, $matches)) { + $cookie[$matches[1]] = trim($matches[2]); + } + } + return new SimpleCookie( + $cookie[1], + trim($cookie[2]), + isset($cookie["path"]) ? $cookie["path"] : "", + isset($cookie["expires"]) ? $cookie["expires"] : false); + } +} + +/** + * Basic HTTP response. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHttpResponse extends SimpleStickyError { + private $url; + private $encoding; + private $sent; + private $content; + private $headers; + + /** + * Constructor. Reads and parses the incoming + * content and headers. + * @param SimpleSocket $socket Network connection to fetch + * response text from. + * @param SimpleUrl $url Resource name. + * @param mixed $encoding Record of content sent. + * @access public + */ + function __construct($socket, $url, $encoding) { + parent::__construct(); + $this->url = $url; + $this->encoding = $encoding; + $this->sent = $socket->getSent(); + $this->content = false; + $raw = $this->readAll($socket); + if ($socket->isError()) { + $this->setError('Error reading socket [' . $socket->getError() . ']'); + return; + } + $this->parse($raw); + } + + /** + * Splits up the headers and the rest of the content. + * @param string $raw Content to parse. + * @access private + */ + protected function parse($raw) { + if (! $raw) { + $this->setError('Nothing fetched'); + $this->headers = new SimpleHttpHeaders(''); + } elseif ('file' == $this->url->getScheme()) { + $this->headers = new SimpleHttpHeaders(''); + $this->content = $raw; + } elseif (! strstr($raw, "\r\n\r\n")) { + $this->setError('Could not split headers from content'); + $this->headers = new SimpleHttpHeaders($raw); + } else { + list($headers, $this->content) = split("\r\n\r\n", $raw, 2); + $this->headers = new SimpleHttpHeaders($headers); + } + } + + /** + * Original request method. + * @return string GET, POST or HEAD. + * @access public + */ + function getMethod() { + return $this->encoding->getMethod(); + } + + /** + * Resource name. + * @return SimpleUrl Current url. + * @access public + */ + function getUrl() { + return $this->url; + } + + /** + * Original request data. + * @return mixed Sent content. + * @access public + */ + function getRequestData() { + return $this->encoding; + } + + /** + * Raw request that was sent down the wire. + * @return string Bytes actually sent. + * @access public + */ + function getSent() { + return $this->sent; + } + + /** + * Accessor for the content after the last + * header line. + * @return string All content. + * @access public + */ + function getContent() { + return $this->content; + } + + /** + * Accessor for header block. The response is the + * combination of this and the content. + * @return SimpleHeaders Wrapped header block. + * @access public + */ + function getHeaders() { + return $this->headers; + } + + /** + * Accessor for any new cookies. + * @return array List of new cookies. + * @access public + */ + function getNewCookies() { + return $this->headers->getNewCookies(); + } + + /** + * Reads the whole of the socket output into a + * single string. + * @param SimpleSocket $socket Unread socket. + * @return string Raw output if successful + * else false. + * @access private + */ + protected function readAll($socket) { + $all = ''; + while (! $this->isLastPacket($next = $socket->read())) { + $all .= $next; + } + return $all; + } + + /** + * Test to see if the packet from the socket is the + * last one. + * @param string $packet Chunk to interpret. + * @return boolean True if empty or EOF. + * @access private + */ + protected function isLastPacket($packet) { + if (is_string($packet)) { + return $packet === ''; + } + return ! $packet; + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/invoker.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/invoker.php new file mode 100644 index 0000000..ee31034 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/invoker.php @@ -0,0 +1,139 @@ +test_case = $test_case; + } + + /** + * Accessor for test case being run. + * @return SimpleTestCase Test case. + * @access public + */ + function getTestCase() { + return $this->test_case; + } + + /** + * Runs test level set up. Used for changing + * the mechanics of base test cases. + * @param string $method Test method to call. + * @access public + */ + function before($method) { + $this->test_case->before($method); + } + + /** + * Invokes a test method and buffered with setUp() + * and tearDown() calls. + * @param string $method Test method to call. + * @access public + */ + function invoke($method) { + $this->test_case->setUp(); + $this->test_case->$method(); + $this->test_case->tearDown(); + } + + /** + * Runs test level clean up. Used for changing + * the mechanics of base test cases. + * @param string $method Test method to call. + * @access public + */ + function after($method) { + $this->test_case->after($method); + } +} + +/** + * Do nothing decorator. Just passes the invocation + * straight through. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleInvokerDecorator { + private $invoker; + + /** + * Stores the invoker to wrap. + * @param SimpleInvoker $invoker Test method runner. + */ + function __construct($invoker) { + $this->invoker = $invoker; + } + + /** + * Accessor for test case being run. + * @return SimpleTestCase Test case. + * @access public + */ + function getTestCase() { + return $this->invoker->getTestCase(); + } + + /** + * Runs test level set up. Used for changing + * the mechanics of base test cases. + * @param string $method Test method to call. + * @access public + */ + function before($method) { + $this->invoker->before($method); + } + + /** + * Invokes a test method and buffered with setUp() + * and tearDown() calls. + * @param string $method Test method to call. + * @access public + */ + function invoke($method) { + $this->invoker->invoke($method); + } + + /** + * Runs test level clean up. Used for changing + * the mechanics of base test cases. + * @param string $method Test method to call. + * @access public + */ + function after($method) { + $this->invoker->after($method); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/mock_objects.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/mock_objects.php new file mode 100644 index 0000000..c3ed38b --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/mock_objects.php @@ -0,0 +1,1630 @@ +expected = $expected; + } + + /** + * Tests the assertion. True if correct. + * @param array $parameters Comparison values. + * @return boolean True if correct. + * @access public + */ + function test($parameters) { + if (! is_array($this->expected)) { + return true; + } + if (count($this->expected) != count($parameters)) { + return false; + } + for ($i = 0; $i < count($this->expected); $i++) { + if (! $this->testParameter($parameters[$i], $this->expected[$i])) { + return false; + } + } + return true; + } + + /** + * Tests an individual parameter. + * @param mixed $parameter Value to test. + * @param mixed $expected Comparison value. + * @return boolean True if expectation + * fulfilled. + * @access private + */ + protected function testParameter($parameter, $expected) { + $comparison = $this->coerceToExpectation($expected); + return $comparison->test($parameter); + } + + /** + * Returns a human readable test message. + * @param array $comparison Incoming parameter list. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($parameters) { + if ($this->test($parameters)) { + return "Expectation of " . count($this->expected) . + " arguments of [" . $this->renderArguments($this->expected) . + "] is correct"; + } else { + return $this->describeDifference($this->expected, $parameters); + } + } + + /** + * Message to display if expectation differs from + * the parameters actually received. + * @param array $expected Expected parameters as list. + * @param array $parameters Actual parameters received. + * @return string Description of difference. + * @access private + */ + protected function describeDifference($expected, $parameters) { + if (count($expected) != count($parameters)) { + return "Expected " . count($expected) . + " arguments of [" . $this->renderArguments($expected) . + "] but got " . count($parameters) . + " arguments of [" . $this->renderArguments($parameters) . "]"; + } + $messages = array(); + for ($i = 0; $i < count($expected); $i++) { + $comparison = $this->coerceToExpectation($expected[$i]); + if (! $comparison->test($parameters[$i])) { + $messages[] = "parameter " . ($i + 1) . " with [" . + $comparison->overlayMessage($parameters[$i], $this->getDumper()) . "]"; + } + } + return "Parameter expectation differs at " . implode(" and ", $messages); + } + + /** + * Creates an identical expectation if the + * object/value is not already some type + * of expectation. + * @param mixed $expected Expected value. + * @return SimpleExpectation Expectation object. + * @access private + */ + protected function coerceToExpectation($expected) { + if (SimpleExpectation::isExpectation($expected)) { + return $expected; + } + return new IdenticalExpectation($expected); + } + + /** + * Renders the argument list as a string for + * messages. + * @param array $args Incoming arguments. + * @return string Simple description of type and value. + * @access private + */ + protected function renderArguments($args) { + $descriptions = array(); + if (is_array($args)) { + foreach ($args as $arg) { + $dumper = new SimpleDumper(); + $descriptions[] = $dumper->describeValue($arg); + } + } + return implode(', ', $descriptions); + } +} + +/** + * Confirms that the number of calls on a method is as expected. + * @package SimpleTest + * @subpackage MockObjects + */ +class CallCountExpectation extends SimpleExpectation { + private $method; + private $count; + + /** + * Stashes the method and expected count for later + * reporting. + * @param string $method Name of method to confirm against. + * @param integer $count Expected number of calls. + * @param string $message Custom error message. + */ + function __construct($method, $count, $message = '%s') { + $this->method = $method; + $this->count = $count; + parent::__construct($message); + } + + /** + * Tests the assertion. True if correct. + * @param integer $compare Measured call count. + * @return boolean True if expected. + * @access public + */ + function test($compare) { + return ($this->count == $compare); + } + + /** + * Reports the comparison. + * @param integer $compare Measured call count. + * @return string Message to show. + * @access public + */ + function testMessage($compare) { + return 'Expected call count for [' . $this->method . + '] was [' . $this->count . + '] got [' . $compare . ']'; + } +} + +/** + * Confirms that the number of calls on a method is as expected. + * @package SimpleTest + * @subpackage MockObjects + */ +class MinimumCallCountExpectation extends SimpleExpectation { + private $method; + private $count; + + /** + * Stashes the method and expected count for later + * reporting. + * @param string $method Name of method to confirm against. + * @param integer $count Minimum number of calls. + * @param string $message Custom error message. + */ + function __construct($method, $count, $message = '%s') { + $this->method = $method; + $this->count = $count; + parent::__construct($message); + } + + /** + * Tests the assertion. True if correct. + * @param integer $compare Measured call count. + * @return boolean True if enough. + * @access public + */ + function test($compare) { + return ($this->count <= $compare); + } + + /** + * Reports the comparison. + * @param integer $compare Measured call count. + * @return string Message to show. + * @access public + */ + function testMessage($compare) { + return 'Minimum call count for [' . $this->method . + '] was [' . $this->count . + '] got [' . $compare . ']'; + } +} + +/** + * Confirms that the number of calls on a method is as expected. + * @package SimpleTest + * @subpackage MockObjects + */ +class MaximumCallCountExpectation extends SimpleExpectation { + private $method; + private $count; + + /** + * Stashes the method and expected count for later + * reporting. + * @param string $method Name of method to confirm against. + * @param integer $count Minimum number of calls. + * @param string $message Custom error message. + */ + function __construct($method, $count, $message = '%s') { + $this->method = $method; + $this->count = $count; + parent::__construct($message); + } + + /** + * Tests the assertion. True if correct. + * @param integer $compare Measured call count. + * @return boolean True if not over. + * @access public + */ + function test($compare) { + return ($this->count >= $compare); + } + + /** + * Reports the comparison. + * @param integer $compare Measured call count. + * @return string Message to show. + * @access public + */ + function testMessage($compare) { + return 'Maximum call count for [' . $this->method . + '] was [' . $this->count . + '] got [' . $compare . ']'; + } +} + +/** + * Retrieves method actions by searching the + * parameter lists until an expected match is found. + * @package SimpleTest + * @subpackage MockObjects + */ +class SimpleSignatureMap { + private $map; + + /** + * Creates an empty call map. + * @access public + */ + function __construct() { + $this->map = array(); + } + + /** + * Stashes a reference against a method call. + * @param array $parameters Array of arguments (including wildcards). + * @param mixed $action Reference placed in the map. + * @access public + */ + function add($parameters, $action) { + $place = count($this->map); + $this->map[$place] = array(); + $this->map[$place]['params'] = new ParametersExpectation($parameters); + $this->map[$place]['content'] = $action; + } + + /** + * Searches the call list for a matching parameter + * set. Returned by reference. + * @param array $parameters Parameters to search by + * without wildcards. + * @return object Object held in the first matching + * slot, otherwise null. + * @access public + */ + function &findFirstAction($parameters) { + $slot = $this->findFirstSlot($parameters); + if (isset($slot) && isset($slot['content'])) { + return $slot['content']; + } + $null = null; + return $null; + } + + /** + * Searches the call list for a matching parameter + * set. True if successful. + * @param array $parameters Parameters to search by + * without wildcards. + * @return boolean True if a match is present. + * @access public + */ + function isMatch($parameters) { + return ($this->findFirstSlot($parameters) != null); + } + + /** + * Compares the incoming parameters with the + * internal expectation. Uses the incoming $test + * to dispatch the test message. + * @param SimpleTestCase $test Test to dispatch to. + * @param array $parameters The actual calling arguments. + * @param string $message The message to overlay. + * @access public + */ + function test($test, $parameters, $message) { + } + + /** + * Searches the map for a matching item. + * @param array $parameters Parameters to search by + * without wildcards. + * @return array Reference to slot or null. + * @access private + */ + function &findFirstSlot($parameters) { + $count = count($this->map); + for ($i = 0; $i < $count; $i++) { + if ($this->map[$i]["params"]->test($parameters)) { + return $this->map[$i]; + } + } + $null = null; + return $null; + } +} + +/** + * Allows setting of actions against call signatures either + * at a specific time, or always. Specific time settings + * trump lasting ones, otherwise the most recently added + * will mask an earlier match. + * @package SimpleTest + * @subpackage MockObjects + */ +class SimpleCallSchedule { + private $wildcard = MOCK_ANYTHING; + private $always; + private $at; + + /** + * Sets up an empty response schedule. + * Creates an empty call map. + */ + function __construct() { + $this->always = array(); + $this->at = array(); + } + + /** + * Stores an action against a signature that + * will always fire unless masked by a time + * specific one. + * @param string $method Method name. + * @param array $args Calling parameters. + * @param SimpleAction $action Actually simpleByValue, etc. + * @access public + */ + function register($method, $args, $action) { + $args = $this->replaceWildcards($args); + $method = strtolower($method); + if (! isset($this->always[$method])) { + $this->always[$method] = new SimpleSignatureMap(); + } + $this->always[$method]->add($args, $action); + } + + /** + * Stores an action against a signature that + * will fire at a specific time in the future. + * @param integer $step delay of calls to this method, + * 0 is next. + * @param string $method Method name. + * @param array $args Calling parameters. + * @param SimpleAction $action Actually SimpleByValue, etc. + * @access public + */ + function registerAt($step, $method, $args, $action) { + $args = $this->replaceWildcards($args); + $method = strtolower($method); + if (! isset($this->at[$method])) { + $this->at[$method] = array(); + } + if (! isset($this->at[$method][$step])) { + $this->at[$method][$step] = new SimpleSignatureMap(); + } + $this->at[$method][$step]->add($args, $action); + } + + /** + * Sets up an expectation on the argument list. + * @param string $method Method to test. + * @param array $args Bare arguments or list of + * expectation objects. + * @param string $message Failure message. + */ + function expectArguments($method, $args, $message) { + $args = $this->replaceWildcards($args); + $message .= Mock::getExpectationLine(); + $this->expected_args[strtolower($method)] = + new ParametersExpectation($args, $message); + + } + + /** + * Actually carry out the action stored previously, + * if the parameters match. + * @param integer $step Time of call. + * @param string $method Method name. + * @param array $args The parameters making up the + * rest of the call. + * @return mixed The result of the action. + * @access public. + */ + function &respond($step, $method, $args) { + $method = strtolower($method); + if (isset($this->at[$method][$step])) { + if ($this->at[$method][$step]->isMatch($args)) { + $action = $this->at[$method][$step]->findFirstAction($args); + if (isset($action)) { + return $action->act(); + } + } + } + if (isset($this->always[$method])) { + $action = $this->always[$method]->findFirstAction($args); + if (isset($action)) { + return $action->act(); + } + } + $null = null; + return $null; + } + + /** + * Replaces wildcard matches with wildcard + * expectations in the argument list. + * @param array $args Raw argument list. + * @return array Argument list with + * expectations. + * @access private + */ + protected function replaceWildcards($args) { + if ($args === false) { + return false; + } + for ($i = 0; $i < count($args); $i++) { + if ($args[$i] === $this->wildcard) { + $args[$i] = new AnythingExpectation(); + } + } + return $args; + } +} + +/** + * A type of SimpleMethodAction. + * Stashes a value for returning later. Follows usual + * PHP5 semantics of objects being returned by reference. + * @package SimpleTest + * @subpackage MockObjects + */ +class SimpleReturn { + private $value; + + /** + * Stashes it for later. + * @param mixed $value You need to clone objects + * if you want copy semantics + * for these. + * @access public + */ + function __construct($value) { + $this->value = $value; + } + + /** + * Returns the value stored earlier. + * @return mixed Whatever was stashed. + * @access public + */ + function act() { + return $this->value; + } +} + +/** + * A type of SimpleMethodAction. + * Stashes a reference for returning later. + * @package SimpleTest + * @subpackage MockObjects + */ +class SimpleByReference { + private $reference; + + /** + * Stashes it for later. + * @param mixed $reference Actual PHP4 style reference. + * @access public + */ + function __construct(&$reference) { + $this->reference = &$reference; + } + + /** + * Returns the reference stored earlier. + * @return mixed Whatever was stashed. + * @access public + */ + function &act() { + return $this->reference; + } +} + +/** + * A type of SimpleMethodAction. + * Stashes a value for returning later. + * @package SimpleTest + * @subpackage MockObjects + */ +class SimpleByValue { + private $value; + + /** + * Stashes it for later. + * @param mixed $value You need to clone objects + * if you want copy semantics + * for these. + * @access public + */ + function __construct($value) { + $this->value = $value; + } + + /** + * Returns the value stored earlier. + * @return mixed Whatever was stashed. + * @access public + */ + function &act() { + $dummy = $this->value; + return $dummy; + } +} + +/** + * A type of SimpleMethodAction. + * Stashes an exception for throwing later. + * @package SimpleTest + * @subpackage MockObjects + */ +class SimpleThrower { + private $exception; + + /** + * Stashes it for later. + * @param Exception $exception The exception object to throw. + * @access public + */ + function __construct($exception) { + $this->exception = $exception; + } + + /** + * Throws the exceptins stashed earlier. + * @access public + */ + function act() { + throw $this->exception; + } +} + +/** + * A type of SimpleMethodAction. + * Stashes an error for emitting later. + * @package SimpleTest + * @subpackage MockObjects + */ +class SimpleErrorThrower { + private $error; + private $severity; + + /** + * Stashes an error to throw later. + * @param string $error Error message. + * @param integer $severity PHP error constant, e.g E_USER_ERROR. + * @access public + */ + function __construct($error, $severity) { + $this->error = $error; + $this->severity = $severity; + } + + /** + * Triggers the stashed error. + * @access public + */ + function &act() { + trigger_error($this->error, $this->severity); + $null = null; + return $null; + } +} + +/** + * A base class or delegate that extends an + * empty collection of methods that can have their + * return values set and expectations made of the + * calls upon them. The mock will assert the + * expectations against it's attached test case in + * addition to the server stub behaviour or returning + * preprogrammed responses. + * @package SimpleTest + * @subpackage MockObjects + */ +class SimpleMock { + private $actions; + private $expectations; + private $wildcard = MOCK_ANYTHING; + private $is_strict = true; + private $call_counts; + private $expected_counts; + private $max_counts; + private $expected_args; + private $expected_args_at; + + /** + * Creates an empty action list and expectation list. + * All call counts are set to zero. + * @access public + */ + function SimpleMock() { + $this->actions = new SimpleCallSchedule(); + $this->expectations = new SimpleCallSchedule(); + $this->call_counts = array(); + $this->expected_counts = array(); + $this->max_counts = array(); + $this->expected_args = array(); + $this->expected_args_at = array(); + $this->getCurrentTestCase()->tell($this); + } + + /** + * Disables a name check when setting expectations. + * This hack is needed for the partial mocks. + * @access public + */ + function disableExpectationNameChecks() { + $this->is_strict = false; + } + + /** + * Finds currently running test. + * @return SimpeTestCase Current test case. + * @access protected + */ + protected function getCurrentTestCase() { + return SimpleTest::getContext()->getTest(); + } + + /** + * Die if bad arguments array is passed. + * @param mixed $args The arguments value to be checked. + * @param string $task Description of task attempt. + * @return boolean Valid arguments + * @access private + */ + protected function checkArgumentsIsArray($args, $task) { + if (! is_array($args)) { + trigger_error( + "Cannot $task as \$args parameter is not an array", + E_USER_ERROR); + } + } + + /** + * Triggers a PHP error if the method is not part + * of this object. + * @param string $method Name of method. + * @param string $task Description of task attempt. + * @access protected + */ + protected function dieOnNoMethod($method, $task) { + if ($this->is_strict && ! method_exists($this, $method)) { + trigger_error( + "Cannot $task as no ${method}() in class " . get_class($this), + E_USER_ERROR); + } + } + + /** + * Replaces wildcard matches with wildcard + * expectations in the argument list. + * @param array $args Raw argument list. + * @return array Argument list with + * expectations. + * @access private + */ + function replaceWildcards($args) { + if ($args === false) { + return false; + } + for ($i = 0; $i < count($args); $i++) { + if ($args[$i] === $this->wildcard) { + $args[$i] = new AnythingExpectation(); + } + } + return $args; + } + + /** + * Adds one to the call count of a method. + * @param string $method Method called. + * @param array $args Arguments as an array. + * @access protected + */ + protected function addCall($method, $args) { + if (! isset($this->call_counts[$method])) { + $this->call_counts[$method] = 0; + } + $this->call_counts[$method]++; + } + + /** + * Fetches the call count of a method so far. + * @param string $method Method name called. + * @return integer Number of calls so far. + * @access public + */ + function getCallCount($method) { + $this->dieOnNoMethod($method, "get call count"); + $method = strtolower($method); + if (! isset($this->call_counts[$method])) { + return 0; + } + return $this->call_counts[$method]; + } + + /** + * Sets a return for a parameter list that will + * be passed on by all calls to this method that match. + * @param string $method Method name. + * @param mixed $value Result of call by value/handle. + * @param array $args List of parameters to match + * including wildcards. + * @access public + */ + function returns($method, $value, $args = false) { + $this->dieOnNoMethod($method, "set return"); + $this->actions->register($method, $args, new SimpleReturn($value)); + } + + /** + * Sets a return for a parameter list that will + * be passed only when the required call count + * is reached. + * @param integer $timing Number of calls in the future + * to which the result applies. If + * not set then all calls will return + * the value. + * @param string $method Method name. + * @param mixed $value Result of call passed. + * @param array $args List of parameters to match + * including wildcards. + * @access public + */ + function returnsAt($timing, $method, $value, $args = false) { + $this->dieOnNoMethod($method, "set return value sequence"); + $this->actions->registerAt($timing, $method, $args, new SimpleReturn($value)); + } + + /** + * Sets a return for a parameter list that will + * be passed by value for all calls to this method. + * @param string $method Method name. + * @param mixed $value Result of call passed by value. + * @param array $args List of parameters to match + * including wildcards. + * @access public + */ + function setReturnValue($method, $value, $args = false) { + $this->dieOnNoMethod($method, "set return value"); + $this->actions->register($method, $args, new SimpleByValue($value)); + } + + /** + * Sets a return for a parameter list that will + * be passed by value only when the required call count + * is reached. + * @param integer $timing Number of calls in the future + * to which the result applies. If + * not set then all calls will return + * the value. + * @param string $method Method name. + * @param mixed $value Result of call passed by value. + * @param array $args List of parameters to match + * including wildcards. + * @access public + */ + function setReturnValueAt($timing, $method, $value, $args = false) { + $this->dieOnNoMethod($method, "set return value sequence"); + $this->actions->registerAt($timing, $method, $args, new SimpleByValue($value)); + } + + /** + * Sets a return for a parameter list that will + * be passed by reference for all calls. + * @param string $method Method name. + * @param mixed $reference Result of the call will be this object. + * @param array $args List of parameters to match + * including wildcards. + * @access public + */ + function setReturnReference($method, &$reference, $args = false) { + $this->dieOnNoMethod($method, "set return reference"); + $this->actions->register($method, $args, new SimpleByReference($reference)); + } + + /** + * Sets a return for a parameter list that will + * be passed by value only when the required call count + * is reached. + * @param integer $timing Number of calls in the future + * to which the result applies. If + * not set then all calls will return + * the value. + * @param string $method Method name. + * @param mixed $reference Result of the call will be this object. + * @param array $args List of parameters to match + * including wildcards. + * @access public + */ + function setReturnReferenceAt($timing, $method, &$reference, $args = false) { + $this->dieOnNoMethod($method, "set return reference sequence"); + $this->actions->registerAt($timing, $method, $args, new SimpleByReference($reference)); + } + + /** + * Sets up an expected call with a set of + * expected parameters in that call. All + * calls will be compared to these expectations + * regardless of when the call is made. + * @param string $method Method call to test. + * @param array $args Expected parameters for the call + * including wildcards. + * @param string $message Overridden message. + * @access public + */ + function expect($method, $args, $message = '%s') { + $this->dieOnNoMethod($method, 'set expected arguments'); + $this->checkArgumentsIsArray($args, 'set expected arguments'); + $this->expectations->expectArguments($method, $args, $message); + $args = $this->replaceWildcards($args); + $message .= Mock::getExpectationLine(); + $this->expected_args[strtolower($method)] = + new ParametersExpectation($args, $message); + } + + /** + * Sets up an expected call with a set of + * expected parameters in that call. The + * expected call count will be adjusted if it + * is set too low to reach this call. + * @param integer $timing Number of calls in the future at + * which to test. Next call is 0. + * @param string $method Method call to test. + * @param array $args Expected parameters for the call + * including wildcards. + * @param string $message Overridden message. + * @access public + */ + function expectAt($timing, $method, $args, $message = '%s') { + $this->dieOnNoMethod($method, 'set expected arguments at time'); + $this->checkArgumentsIsArray($args, 'set expected arguments at time'); + $args = $this->replaceWildcards($args); + if (! isset($this->expected_args_at[$timing])) { + $this->expected_args_at[$timing] = array(); + } + $method = strtolower($method); + $message .= Mock::getExpectationLine(); + $this->expected_args_at[$timing][$method] = + new ParametersExpectation($args, $message); + } + + /** + * Sets an expectation for the number of times + * a method will be called. The tally method + * is used to check this. + * @param string $method Method call to test. + * @param integer $count Number of times it should + * have been called at tally. + * @param string $message Overridden message. + * @access public + */ + function expectCallCount($method, $count, $message = '%s') { + $this->dieOnNoMethod($method, 'set expected call count'); + $message .= Mock::getExpectationLine(); + $this->expected_counts[strtolower($method)] = + new CallCountExpectation($method, $count, $message); + } + + /** + * Sets the number of times a method may be called + * before a test failure is triggered. + * @param string $method Method call to test. + * @param integer $count Most number of times it should + * have been called. + * @param string $message Overridden message. + * @access public + */ + function expectMaximumCallCount($method, $count, $message = '%s') { + $this->dieOnNoMethod($method, 'set maximum call count'); + $message .= Mock::getExpectationLine(); + $this->max_counts[strtolower($method)] = + new MaximumCallCountExpectation($method, $count, $message); + } + + /** + * Sets the number of times to call a method to prevent + * a failure on the tally. + * @param string $method Method call to test. + * @param integer $count Least number of times it should + * have been called. + * @param string $message Overridden message. + * @access public + */ + function expectMinimumCallCount($method, $count, $message = '%s') { + $this->dieOnNoMethod($method, 'set minimum call count'); + $message .= Mock::getExpectationLine(); + $this->expected_counts[strtolower($method)] = + new MinimumCallCountExpectation($method, $count, $message); + } + + /** + * Convenience method for barring a method + * call. + * @param string $method Method call to ban. + * @param string $message Overridden message. + * @access public + */ + function expectNever($method, $message = '%s') { + $this->expectMaximumCallCount($method, 0, $message); + } + + /** + * Convenience method for a single method + * call. + * @param string $method Method call to track. + * @param array $args Expected argument list or + * false for any arguments. + * @param string $message Overridden message. + * @access public + */ + function expectOnce($method, $args = false, $message = '%s') { + $this->expectCallCount($method, 1, $message); + if ($args !== false) { + $this->expect($method, $args, $message); + } + } + + /** + * Convenience method for requiring a method + * call. + * @param string $method Method call to track. + * @param array $args Expected argument list or + * false for any arguments. + * @param string $message Overridden message. + * @access public + */ + function expectAtLeastOnce($method, $args = false, $message = '%s') { + $this->expectMinimumCallCount($method, 1, $message); + if ($args !== false) { + $this->expect($method, $args, $message); + } + } + + /** + * Sets up a trigger to throw an exception upon the + * method call. + * @param string $method Method name to throw on. + */ + function throwOn($method, $exception = false, $args = false) { + $this->dieOnNoMethod($method, "throw on"); + $this->actions->register($method, $args, + new SimpleThrower($exception ? $exception : new Exception())); + } + + /** + * Sets up a trigger to throw an exception upon the + * method call. + */ + function throwAt($timing, $method, $exception = false, $args = false) { + $this->dieOnNoMethod($method, "throw at"); + $this->actions->registerAt($timing, $method, $args, + new SimpleThrower($exception ? $exception : new Exception())); + } + + /** + * Sets up a trigger to throw an error upon the + * method call. + */ + function errorOn($method, $error = 'A mock error', $args = false, $severity = E_USER_ERROR) { + $this->dieOnNoMethod($method, "error on"); + $this->actions->register($method, $args, new SimpleErrorThrower($error, $severity)); + } + + /** + * Sets up a trigger to throw an error upon the + * method call. + */ + function errorAt($timing, $method, $error = 'A mock error', $args = false, $severity = E_USER_ERROR) { + $this->dieOnNoMethod($method, "error at"); + $this->actions->registerAt($timing, $method, $args, new SimpleErrorThrower($error, $severity)); + } + + /** + * Receives event from unit test that the current + * test method has finished. Totals up the call + * counts and triggers a test assertion if a test + * is present for expected call counts. + * @param string $test_method Current method name. + * @param SimpleTestCase $test Test to send message to. + * @access public + */ + function atTestEnd($test_method, &$test) { + foreach ($this->expected_counts as $method => $expectation) { + $test->assert($expectation, $this->getCallCount($method)); + } + foreach ($this->max_counts as $method => $expectation) { + if ($expectation->test($this->getCallCount($method))) { + $test->assert($expectation, $this->getCallCount($method)); + } + } + } + + /** + * Returns the expected value for the method name + * and checks expectations. Will generate any + * test assertions as a result of expectations + * if there is a test present. + * @param string $method Name of method to simulate. + * @param array $args Arguments as an array. + * @return mixed Stored return. + * @access private + */ + function &invoke($method, $args) { + $method = strtolower($method); + $step = $this->getCallCount($method); + $this->addCall($method, $args); + $this->checkExpectations($method, $args, $step); + $was = $this->disableEStrict(); + try { + $result = &$this->emulateCall($method, $args, $step); + } catch (Exception $e) { + $this->restoreEStrict($was); + throw $e; + } + $this->restoreEStrict($was); + return $result; + } + + /** + * Finds the return value matching the incoming + * arguments. If there is no matching value found + * then an error is triggered. + * @param string $method Method name. + * @param array $args Calling arguments. + * @param integer $step Current position in the + * call history. + * @return mixed Stored return or other action. + * @access protected + */ + protected function &emulateCall($method, $args, $step) { + return $this->actions->respond($step, $method, $args); + } + + /** + * Tests the arguments against expectations. + * @param string $method Method to check. + * @param array $args Argument list to match. + * @param integer $timing The position of this call + * in the call history. + * @access private + */ + protected function checkExpectations($method, $args, $timing) { + $test = $this->getCurrentTestCase(); + if (isset($this->max_counts[$method])) { + if (! $this->max_counts[$method]->test($timing + 1)) { + $test->assert($this->max_counts[$method], $timing + 1); + } + } + if (isset($this->expected_args_at[$timing][$method])) { + $test->assert( + $this->expected_args_at[$timing][$method], + $args, + "Mock method [$method] at [$timing] -> %s"); + } elseif (isset($this->expected_args[$method])) { + $test->assert( + $this->expected_args[$method], + $args, + "Mock method [$method] -> %s"); + } + } + + private function disableEStrict() { + $was = error_reporting(); + error_reporting($was & ~E_STRICT); + return $was; + } + + private function restoreEStrict($was) { + error_reporting($was); + } +} + +/** + * Static methods only service class for code generation of + * mock objects. + * @package SimpleTest + * @subpackage MockObjects + */ +class Mock { + + /** + * Factory for mock object classes. + * @access public + */ + function __construct() { + trigger_error('Mock factory methods are static.'); + } + + /** + * Clones a class' interface and creates a mock version + * that can have return values and expectations set. + * @param string $class Class to clone. + * @param string $mock_class New class name. Default is + * the old name with "Mock" + * prepended. + * @param array $methods Additional methods to add beyond + * those in the cloned class. Use this + * to emulate the dynamic addition of + * methods in the cloned class or when + * the class hasn't been written yet.sta + * @access public + */ + static function generate($class, $mock_class = false, $methods = false) { + $generator = new MockGenerator($class, $mock_class); + return @$generator->generateSubclass($methods); + } + + /** + * Generates a version of a class with selected + * methods mocked only. Inherits the old class + * and chains the mock methods of an aggregated + * mock object. + * @param string $class Class to clone. + * @param string $mock_class New class name. + * @param array $methods Methods to be overridden + * with mock versions. + * @access public + */ + static function generatePartial($class, $mock_class, $methods) { + $generator = new MockGenerator($class, $mock_class); + return @$generator->generatePartial($methods); + } + + /** + * Uses a stack trace to find the line of an assertion. + * @access public + */ + static function getExpectationLine() { + $trace = new SimpleStackTrace(array('expect')); + return $trace->traceMethod(); + } +} + +/** + * Service class for code generation of mock objects. + * @package SimpleTest + * @subpackage MockObjects + */ +class MockGenerator { + private $class; + private $mock_class; + private $mock_base; + private $reflection; + + /** + * Builds initial reflection object. + * @param string $class Class to be mocked. + * @param string $mock_class New class with identical interface, + * but no behaviour. + */ + function __construct($class, $mock_class) { + $this->class = $class; + $this->mock_class = $mock_class; + if (! $this->mock_class) { + $this->mock_class = 'Mock' . $this->class; + } + $this->mock_base = SimpleTest::getMockBaseClass(); + $this->reflection = new SimpleReflection($this->class); + } + + /** + * Clones a class' interface and creates a mock version + * that can have return values and expectations set. + * @param array $methods Additional methods to add beyond + * those in th cloned class. Use this + * to emulate the dynamic addition of + * methods in the cloned class or when + * the class hasn't been written yet. + * @access public + */ + function generate($methods) { + if (! $this->reflection->classOrInterfaceExists()) { + return false; + } + $mock_reflection = new SimpleReflection($this->mock_class); + if ($mock_reflection->classExistsSansAutoload()) { + return false; + } + $code = $this->createClassCode($methods ? $methods : array()); + return eval("$code return \$code;"); + } + + /** + * Subclasses a class and overrides every method with a mock one + * that can have return values and expectations set. Chains + * to an aggregated SimpleMock. + * @param array $methods Additional methods to add beyond + * those in the cloned class. Use this + * to emulate the dynamic addition of + * methods in the cloned class or when + * the class hasn't been written yet. + * @access public + */ + function generateSubclass($methods) { + if (! $this->reflection->classOrInterfaceExists()) { + return false; + } + $mock_reflection = new SimpleReflection($this->mock_class); + if ($mock_reflection->classExistsSansAutoload()) { + return false; + } + if ($this->reflection->isInterface() || $this->reflection->hasFinal()) { + $code = $this->createClassCode($methods ? $methods : array()); + return eval("$code return \$code;"); + } else { + $code = $this->createSubclassCode($methods ? $methods : array()); + return eval("$code return \$code;"); + } + } + + /** + * Generates a version of a class with selected + * methods mocked only. Inherits the old class + * and chains the mock methods of an aggregated + * mock object. + * @param array $methods Methods to be overridden + * with mock versions. + * @access public + */ + function generatePartial($methods) { + if (! $this->reflection->classExists($this->class)) { + return false; + } + $mock_reflection = new SimpleReflection($this->mock_class); + if ($mock_reflection->classExistsSansAutoload()) { + trigger_error('Partial mock class [' . $this->mock_class . '] already exists'); + return false; + } + $code = $this->extendClassCode($methods); + return eval("$code return \$code;"); + } + + /** + * The new mock class code as a string. + * @param array $methods Additional methods. + * @return string Code for new mock class. + * @access private + */ + protected function createClassCode($methods) { + $implements = ''; + $interfaces = $this->reflection->getInterfaces(); + if (function_exists('spl_classes')) { + $interfaces = array_diff($interfaces, array('Traversable')); + } + if (count($interfaces) > 0) { + $implements = 'implements ' . implode(', ', $interfaces); + } + $code = "class " . $this->mock_class . " extends " . $this->mock_base . " $implements {\n"; + $code .= " function " . $this->mock_class . "() {\n"; + $code .= " \$this->" . $this->mock_base . "();\n"; + $code .= " }\n"; + if (in_array('__construct', $this->reflection->getMethods())) { + $code .= " function __construct() {\n"; + $code .= " \$this->" . $this->mock_base . "();\n"; + $code .= " }\n"; + } + $code .= $this->createHandlerCode($methods); + $code .= "}\n"; + return $code; + } + + /** + * The new mock class code as a string. The mock will + * be a subclass of the original mocked class. + * @param array $methods Additional methods. + * @return string Code for new mock class. + * @access private + */ + protected function createSubclassCode($methods) { + $code = "class " . $this->mock_class . " extends " . $this->class . " {\n"; + $code .= " public \$mock;\n"; + $code .= $this->addMethodList(array_merge($methods, $this->reflection->getMethods())); + $code .= "\n"; + $code .= " function " . $this->mock_class . "() {\n"; + $code .= " \$this->mock = new " . $this->mock_base . "();\n"; + $code .= " \$this->mock->disableExpectationNameChecks();\n"; + $code .= " }\n"; + $code .= $this->chainMockReturns(); + $code .= $this->chainMockExpectations(); + $code .= $this->chainThrowMethods(); + $code .= $this->overrideMethods($this->reflection->getMethods()); + $code .= $this->createNewMethodCode($methods); + $code .= "}\n"; + return $code; + } + + /** + * The extension class code as a string. The class + * composites a mock object and chains mocked methods + * to it. + * @param array $methods Mocked methods. + * @return string Code for a new class. + * @access private + */ + protected function extendClassCode($methods) { + $code = "class " . $this->mock_class . " extends " . $this->class . " {\n"; + $code .= " protected \$mock;\n"; + $code .= $this->addMethodList($methods); + $code .= "\n"; + $code .= " function " . $this->mock_class . "() {\n"; + $code .= " \$this->mock = new " . $this->mock_base . "();\n"; + $code .= " \$this->mock->disableExpectationNameChecks();\n"; + $code .= " }\n"; + $code .= $this->chainMockReturns(); + $code .= $this->chainMockExpectations(); + $code .= $this->chainThrowMethods(); + $code .= $this->overrideMethods($methods); + $code .= "}\n"; + return $code; + } + + /** + * Creates code within a class to generate replaced + * methods. All methods call the invoke() handler + * with the method name and the arguments in an + * array. + * @param array $methods Additional methods. + * @access private + */ + protected function createHandlerCode($methods) { + $code = ''; + $methods = array_merge($methods, $this->reflection->getMethods()); + foreach ($methods as $method) { + if ($this->isConstructor($method)) { + continue; + } + $mock_reflection = new SimpleReflection($this->mock_base); + if (in_array($method, $mock_reflection->getMethods())) { + continue; + } + $code .= " " . $this->reflection->getSignature($method) . " {\n"; + $code .= " \$args = func_get_args();\n"; + $code .= " \$result = &\$this->invoke(\"$method\", \$args);\n"; + $code .= " return \$result;\n"; + $code .= " }\n"; + } + return $code; + } + + /** + * Creates code within a class to generate a new + * methods. All methods call the invoke() handler + * on the internal mock with the method name and + * the arguments in an array. + * @param array $methods Additional methods. + * @access private + */ + protected function createNewMethodCode($methods) { + $code = ''; + foreach ($methods as $method) { + if ($this->isConstructor($method)) { + continue; + } + $mock_reflection = new SimpleReflection($this->mock_base); + if (in_array($method, $mock_reflection->getMethods())) { + continue; + } + $code .= " " . $this->reflection->getSignature($method) . " {\n"; + $code .= " \$args = func_get_args();\n"; + $code .= " \$result = &\$this->mock->invoke(\"$method\", \$args);\n"; + $code .= " return \$result;\n"; + $code .= " }\n"; + } + return $code; + } + + /** + * Tests to see if a special PHP method is about to + * be stubbed by mistake. + * @param string $method Method name. + * @return boolean True if special. + * @access private + */ + protected function isConstructor($method) { + return in_array( + strtolower($method), + array('__construct', '__destruct')); + } + + /** + * Creates a list of mocked methods for error checking. + * @param array $methods Mocked methods. + * @return string Code for a method list. + * @access private + */ + protected function addMethodList($methods) { + return " protected \$mocked_methods = array('" . + implode("', '", array_map('strtolower', $methods)) . + "');\n"; + } + + /** + * Creates code to abandon the expectation if not mocked. + * @param string $alias Parameter name of method name. + * @return string Code for bail out. + * @access private + */ + protected function bailOutIfNotMocked($alias) { + $code = " if (! in_array(strtolower($alias), \$this->mocked_methods)) {\n"; + $code .= " trigger_error(\"Method [$alias] is not mocked\");\n"; + $code .= " \$null = null;\n"; + $code .= " return \$null;\n"; + $code .= " }\n"; + return $code; + } + + /** + * Creates source code for chaining to the composited + * mock object. + * @return string Code for mock set up. + * @access private + */ + protected function chainMockReturns() { + $code = " function returns(\$method, \$value, \$args = false) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->returns(\$method, \$value, \$args);\n"; + $code .= " }\n"; + $code .= " function returnsAt(\$timing, \$method, \$value, \$args = false) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->returnsAt(\$timing, \$method, \$value, \$args);\n"; + $code .= " }\n"; + $code .= " function setReturnValue(\$method, \$value, \$args = false) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->setReturnValue(\$method, \$value, \$args);\n"; + $code .= " }\n"; + $code .= " function setReturnValueAt(\$timing, \$method, \$value, \$args = false) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->setReturnValueAt(\$timing, \$method, \$value, \$args);\n"; + $code .= " }\n"; + $code .= " function setReturnReference(\$method, &\$ref, \$args = false) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->setReturnReference(\$method, \$ref, \$args);\n"; + $code .= " }\n"; + $code .= " function setReturnReferenceAt(\$timing, \$method, &\$ref, \$args = false) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->setReturnReferenceAt(\$timing, \$method, \$ref, \$args);\n"; + $code .= " }\n"; + return $code; + } + + /** + * Creates source code for chaining to an aggregated + * mock object. + * @return string Code for expectations. + * @access private + */ + protected function chainMockExpectations() { + $code = " function expect(\$method, \$args = false, \$msg = '%s') {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->expect(\$method, \$args, \$msg);\n"; + $code .= " }\n"; + $code .= " function expectAt(\$timing, \$method, \$args = false, \$msg = '%s') {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->expectAt(\$timing, \$method, \$args, \$msg);\n"; + $code .= " }\n"; + $code .= " function expectCallCount(\$method, \$count) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->expectCallCount(\$method, \$count, \$msg = '%s');\n"; + $code .= " }\n"; + $code .= " function expectMaximumCallCount(\$method, \$count, \$msg = '%s') {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->expectMaximumCallCount(\$method, \$count, \$msg = '%s');\n"; + $code .= " }\n"; + $code .= " function expectMinimumCallCount(\$method, \$count, \$msg = '%s') {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->expectMinimumCallCount(\$method, \$count, \$msg = '%s');\n"; + $code .= " }\n"; + $code .= " function expectNever(\$method) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->expectNever(\$method);\n"; + $code .= " }\n"; + $code .= " function expectOnce(\$method, \$args = false, \$msg = '%s') {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->expectOnce(\$method, \$args, \$msg);\n"; + $code .= " }\n"; + $code .= " function expectAtLeastOnce(\$method, \$args = false, \$msg = '%s') {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->expectAtLeastOnce(\$method, \$args, \$msg);\n"; + $code .= " }\n"; + return $code; + } + + /** + * Adds code for chaining the throw methods. + * @return string Code for chains. + * @access private + */ + protected function chainThrowMethods() { + $code = " function throwOn(\$method, \$exception = false, \$args = false) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->throwOn(\$method, \$exception, \$args);\n"; + $code .= " }\n"; + $code .= " function throwAt(\$timing, \$method, \$exception = false, \$args = false) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->throwAt(\$timing, \$method, \$exception, \$args);\n"; + $code .= " }\n"; + $code .= " function errorOn(\$method, \$error = 'A mock error', \$args = false, \$severity = E_USER_ERROR) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->errorOn(\$method, \$error, \$args, \$severity);\n"; + $code .= " }\n"; + $code .= " function errorAt(\$timing, \$method, \$error = 'A mock error', \$args = false, \$severity = E_USER_ERROR) {\n"; + $code .= $this->bailOutIfNotMocked("\$method"); + $code .= " \$this->mock->errorAt(\$timing, \$method, \$error, \$args, \$severity);\n"; + $code .= " }\n"; + return $code; + } + + /** + * Creates source code to override a list of methods + * with mock versions. + * @param array $methods Methods to be overridden + * with mock versions. + * @return string Code for overridden chains. + * @access private + */ + protected function overrideMethods($methods) { + $code = ""; + foreach ($methods as $method) { + if ($this->isConstructor($method)) { + continue; + } + $code .= " " . $this->reflection->getSignature($method) . " {\n"; + $code .= " \$args = func_get_args();\n"; + $code .= " \$result = &\$this->mock->invoke(\"$method\", \$args);\n"; + $code .= " return \$result;\n"; + $code .= " }\n"; + } + return $code; + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/README b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/README new file mode 100644 index 0000000..3cf668e --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/README @@ -0,0 +1,26 @@ +HOW TO MAKE A PACKAGE + +1. First make sure PEAR_PackageFileManager is installed; +(version 1.2.0 was used) + +$ pear install PEAR_PackageFileManager + +2. Edit the simpletest/packages/pear_package_create.php file (see comments for what +needs changing). + +3. Run the simpletest/packages/pear_package_create.php script, piping the output +to the file you want to create e.g.; + +$ ./pear_package_create.php > package.xml + +4. Copy the package.xml to the root of Simpletest. + +5. From the root of Simpletest type; + +$ pear package package.xml + +This creates the package zip + +6. Install with; + +$ pear install SimpleTest-x.x.x.tgz \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/build_tarball.sh b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/build_tarball.sh new file mode 100755 index 0000000..943a347 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/build_tarball.sh @@ -0,0 +1,132 @@ +#!/bin/sh + +# Builds project release. +# +cd ../.. + +NAME=simpletest_`cat simpletest/VERSION`.tar.gz +FILES=(simpletest/README \ + simpletest/VERSION \ + simpletest/LICENSE \ + simpletest/HELP_MY_TESTS_DONT_WORK_ANYMORE \ + simpletest/authentication.php \ + simpletest/autorun.php \ + simpletest/browser.php \ + simpletest/collector.php \ + simpletest/compatibility.php \ + simpletest/cookies.php \ + simpletest/default_reporter.php \ + simpletest/detached.php \ + simpletest/dumper.php \ + simpletest/eclipse.php \ + simpletest/encoding.php \ + simpletest/errors.php \ + simpletest/exceptions.php \ + simpletest/expectation.php \ + simpletest/form.php \ + simpletest/frames.php \ + simpletest/http.php \ + simpletest/invoker.php \ + simpletest/mock_objects.php \ + simpletest/page.php \ + simpletest/parser.php \ + simpletest/reflection_php4.php \ + simpletest/reflection_php5.php \ + simpletest/remote.php \ + simpletest/reporter.php \ + simpletest/scorer.php \ + simpletest/selector.php \ + simpletest/shell_tester.php \ + simpletest/simpletest.php \ + simpletest/socket.php \ + simpletest/tag.php \ + simpletest/test_case.php \ + simpletest/unit_tester.php \ + simpletest/url.php \ + simpletest/user_agent.php \ + simpletest/web_tester.php \ + simpletest/xml.php \ + simpletest/extensions/pear_test_case.php \ + simpletest/extensions/phpunit_test_case.php \ + simpletest/extensions/testdox.php \ + simpletest/extensions/testdox/test.php \ + simpletest/test/acceptance_test.php \ + simpletest/test/adapter_test.php \ + simpletest/test/all_tests.php \ + simpletest/test/authentication_test.php \ + simpletest/test/bad_test_suite.php \ + simpletest/test/browser_test.php \ + simpletest/test/collector_test.php \ + simpletest/test/command_line_test.php \ + simpletest/test/compatibility_test.php \ + simpletest/test/cookies_test.php \ + simpletest/test/detached_test.php \ + simpletest/test/dumper_test.php \ + simpletest/test/eclipse_test.php \ + simpletest/test/encoding_test.php \ + simpletest/test/errors_test.php \ + simpletest/test/exceptions_test.php \ + simpletest/test/expectation_test.php \ + simpletest/test/form_test.php \ + simpletest/test/frames_test.php \ + simpletest/test/http_test.php \ + simpletest/test/interfaces_test.php \ + simpletest/test/live_test.php \ + simpletest/test/mock_objects_test.php \ + simpletest/test/page_test.php \ + simpletest/test/parse_error_test.php \ + simpletest/test/parser_test.php \ + simpletest/test/reflection_php4_test.php \ + simpletest/test/reflection_php5_test.php \ + simpletest/test/remote_test.php \ + simpletest/test/shell_test.php \ + simpletest/test/shell_tester_test.php \ + simpletest/test/simpletest_test.php \ + simpletest/test/socket_test.php \ + simpletest/test/tag_test.php \ + simpletest/test/test_with_parse_error.php \ + simpletest/test/unit_tests.php \ + simpletest/test/unit_tester_test.php \ + simpletest/test/autorun_test.php \ + simpletest/test/url_test.php \ + simpletest/test/user_agent_test.php \ + simpletest/test/visual_test.php \ + simpletest/test/web_tester_test.php \ + simpletest/test/xml_test.php \ + simpletest/test/support/collector/collectable.1 \ + simpletest/test/support/collector/collectable.2 \ + simpletest/test/support/upload_sample.txt \ + simpletest/test/support/supplementary_upload_sample.txt \ + simpletest/test/support/latin1_sample \ + simpletest/test/support/spl_examples.php \ + simpletest/test/support/empty_test_file.php \ + simpletest/test/support/test1.php \ + simpletest/docs/en/docs.css \ + simpletest/docs/en/index.html \ + simpletest/docs/en/overview.html \ + simpletest/docs/en/unit_test_documentation.html \ + simpletest/docs/en/group_test_documentation.html \ + simpletest/docs/en/mock_objects_documentation.html \ + simpletest/docs/en/partial_mocks_documentation.html \ + simpletest/docs/en/reporter_documentation.html \ + simpletest/docs/en/expectation_documentation.html \ + simpletest/docs/en/web_tester_documentation.html \ + simpletest/docs/en/form_testing_documentation.html \ + simpletest/docs/en/authentication_documentation.html \ + simpletest/docs/en/browser_documentation.html \ + simpletest/docs/fr/docs.css \ + simpletest/docs/fr/index.html \ + simpletest/docs/fr/overview.html \ + simpletest/docs/fr/unit_test_documentation.html \ + simpletest/docs/fr/group_test_documentation.html \ + simpletest/docs/fr/server_stubs_documentation.html \ + simpletest/docs/fr/mock_objects_documentation.html \ + simpletest/docs/fr/partial_mocks_documentation.html \ + simpletest/docs/fr/reporter_documentation.html \ + simpletest/docs/fr/expectation_documentation.html \ + simpletest/docs/fr/web_tester_documentation.html \ + simpletest/docs/fr/form_testing_documentation.html \ + simpletest/docs/fr/authentication_documentation.html \ + simpletest/docs/fr/browser_documentation.html) + +tar -zcf $NAME ${FILES[*]} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/bundled_docs.xslt b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/bundled_docs.xslt new file mode 100644 index 0000000..c194611 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/bundled_docs.xslt @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + <xsl:value-of select="//long_title"/> + + + + + + + + + +
    + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ( pages) +
    +
    +
    + + +

    +
    + + + This page... +
      + +
    +
    + + + References and related information... +
      + +
    +
    + + + + + + +
    +            
    +                
    +            
    +        
    +
    + + + + + + + + +
    +            
    +                
    +            
    +        
    +
    + + +
    +            
    +                
    +            
    +        
    +
    + + +
    +            
    +        
    +
    + + +

    + + + + +

    + +
    + + + + + + + + + + + + + + + + .html + + + + + + + + + + + + + + + + +
  • +
    + + + + + + + + + + + + + + + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/bundled_map.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/bundled_map.xml new file mode 100644 index 0000000..b5b950f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/bundled_map.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/extension.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/extension.xml new file mode 100644 index 0000000..a897ab1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/extension.xml @@ -0,0 +1,40 @@ + + + + + + + Name of the extension to build + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/generate_package.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/generate_package.php new file mode 100644 index 0000000..dd433db --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/generate_package.php @@ -0,0 +1,54 @@ + 'svn', + 'simpleoutput' => true, + 'baseinstalldir' => 'simpletest', + 'packagedirectory' => dirname(__FILE__) . '/../', + 'clearcontents' => true, + 'ignore' => array('TODO.xml', 'VERSION', 'docs/', 'tutorials/', 'packages/', '.svn'), + 'dir_roles' => array( + 'test' => 'test' + ) +); + +$packagexml = PEAR_PackageFileManager2::importOptions($packagefile, $options); +$packagexml->setPackageType('php'); +$packagexml->setPackage('simpletest'); +$packagexml->setSummary('PHP Unit Tester'); +$packagexml->setDescription("Unit testing, mock objects and web testing framework for PHP"); + +// update this! do we have a default channel server? +$packagexml->setChannel('pear.php.net'); +$packagexml->setUri('http://os.coretxt.net.nz/simpletest-1.1'); + +$notes = file_get_contents(dirname(__FILE__).'/../README'); +$packagexml->setNotes($notes); + +$packagexml->setPhpDep('5.0.5'); +$packagexml->setPearinstallerDep('1.4.0'); +$packagexml->addPackageDepWithChannel('required', 'PEAR', 'pear.php.net', '1.4.0'); +$packagexml->addMaintainer('lead', 'lastcraft', 'Marcus Baker', 'marcus@lastcraft.com'); +$packagexml->setLicense('LGPL', 'http://www.gnu.org/licenses/lgpl-2.1.html'); + +preg_match("/([0-9\.]+)([a-z]+)/", file_get_contents(dirname(__FILE__).'/../VERSION'), $version); +$packagexml->setAPIVersion($version[1]); +$packagexml->setReleaseVersion($version[1]); +$packagexml->setReleaseStability($version[2]); +$packagexml->setAPIStability($version[2]); + +$packagexml->addRelease(); +$packagexml->generateContents(); + + +if (isset($_GET['make']) || (isset($_SERVER['argv']) && @$_SERVER['argv'][1] == 'make')) { + $packagexml->writePackageFile(); +} else { + $packagexml->debugPackageFile(); +} + +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/lastcraft.xslt b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/lastcraft.xslt new file mode 100644 index 0000000..e655b7d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/lastcraft.xslt @@ -0,0 +1,302 @@ + + + + + + + + + + + + + + + + + <xsl:value-of select="//long_title"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + +
    + + +
    + + LastCraft Home Page + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
    • + + + + +
    • +
      +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + +
    ( pages) +
    +
    +
    + + +
    +
    +

    +
    + +
    +
    + +
    +
    +
    +
    + + + This page... +
      + +
    +
    + + + Related... +
      + +
    +
    + + + + + + +
    +            
    +                
    +            
    +        
    +
    + + + + + + + + +
    +            
    +                
    +            
    +        
    +
    + + +
    +            
    +        
    +
    + + +

    + + +

    + +

    + +
    + + + + + + +

    + News: + +

    +
    + + + + + + + + + .php + + + + + + + + + + + + + + + + +
  • +
    + + + + + + + + + + + + + + + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/make_bundled_docs.sh b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/make_bundled_docs.sh new file mode 100755 index 0000000..6245ff4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/make_bundled_docs.sh @@ -0,0 +1,25 @@ +#!/bin/sh +xsltproc bundled_docs.xslt ../docs/source/en/simple_test.xml > ../docs/en/index.html +xsltproc bundled_docs.xslt ../docs/source/en/overview.xml > ../docs/en/overview.html +xsltproc bundled_docs.xslt ../docs/source/en/unit_test_documentation.xml > ../docs/en/unit_test_documentation.html +xsltproc bundled_docs.xslt ../docs/source/en/group_test_documentation.xml > ../docs/en/group_test_documentation.html +xsltproc bundled_docs.xslt ../docs/source/en/mock_objects_documentation.xml > ../docs/en/mock_objects_documentation.html +xsltproc bundled_docs.xslt ../docs/source/en/partial_mocks_documentation.xml > ../docs/en/partial_mocks_documentation.html +xsltproc bundled_docs.xslt ../docs/source/en/reporter_documentation.xml > ../docs/en/reporter_documentation.html +xsltproc bundled_docs.xslt ../docs/source/en/expectation_documentation.xml > ../docs/en/expectation_documentation.html +xsltproc bundled_docs.xslt ../docs/source/en/web_tester_documentation.xml > ../docs/en/web_tester_documentation.html +xsltproc bundled_docs.xslt ../docs/source/en/form_testing_documentation.xml > ../docs/en/form_testing_documentation.html +xsltproc bundled_docs.xslt ../docs/source/en/authentication_documentation.xml > ../docs/en/authentication_documentation.html +xsltproc bundled_docs.xslt ../docs/source/en/browser_documentation.xml > ../docs/en/browser_documentation.html +xsltproc bundled_docs.xslt ../docs/source/fr/simple_test.xml > ../docs/fr/index.html +xsltproc bundled_docs.xslt ../docs/source/fr/overview.xml > ../docs/fr/overview.html +xsltproc bundled_docs.xslt ../docs/source/fr/unit_test_documentation.xml > ../docs/fr/unit_test_documentation.html +xsltproc bundled_docs.xslt ../docs/source/fr/group_test_documentation.xml > ../docs/fr/group_test_documentation.html +xsltproc bundled_docs.xslt ../docs/source/fr/mock_objects_documentation.xml > ../docs/fr/mock_objects_documentation.html +xsltproc bundled_docs.xslt ../docs/source/fr/partial_mocks_documentation.xml > ../docs/fr/partial_mocks_documentation.html +xsltproc bundled_docs.xslt ../docs/source/fr/reporter_documentation.xml > ../docs/fr/reporter_documentation.html +xsltproc bundled_docs.xslt ../docs/source/fr/expectation_documentation.xml > ../docs/fr/expectation_documentation.html +xsltproc bundled_docs.xslt ../docs/source/fr/web_tester_documentation.xml > ../docs/fr/web_tester_documentation.html +xsltproc bundled_docs.xslt ../docs/source/fr/form_testing_documentation.xml > ../docs/fr/form_testing_documentation.html +xsltproc bundled_docs.xslt ../docs/source/fr/authentication_documentation.xml > ../docs/fr/authentication_documentation.html +xsltproc bundled_docs.xslt ../docs/source/fr/browser_documentation.xml > ../docs/fr/browser_documentation.html diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/make_bundled_docs_with_xalan.sh b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/make_bundled_docs_with_xalan.sh new file mode 100755 index 0000000..cfc6de3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/make_bundled_docs_with_xalan.sh @@ -0,0 +1,26 @@ +#!/bin/sh +Xalan -o ../docs/en/index.html ../docs/source/en/simple_test.xml bundled_docs.xslt +Xalan -o ../docs/en/overview.html ../docs/source/en/overview.xml bundled_docs.xslt +Xalan -o ../docs/en/unit_test_documentation.html ../docs/source/en/unit_test_documentation.xml bundled_docs.xslt +Xalan -o ../docs/en/group_test_documentation.html ../docs/source/en/group_test_documentation.xml bundled_docs.xslt +Xalan -o ../docs/en/mock_objects_documentation.html ../docs/source/en/mock_objects_documentation.xml bundled_docs.xslt +Xalan -o ../docs/en/partial_mocks_documentation.html ../docs/source/en/partial_mocks_documentation.xml bundled_docs.xslt +Xalan -o ../docs/en/reporter_documentation.html ../docs/source/en/reporter_documentation.xml bundled_docs.xslt +Xalan -o ../docs/en/expectation_documentation.html ../docs/source/en/expectation_documentation.xml bundled_docs.xslt +Xalan -o ../docs/en/web_tester_documentation.html ../docs/source/en/web_tester_documentation.xml bundled_docs.xslt +Xalan -o ../docs/en/form_testing_documentation.html ../docs/source/en/form_testing_documentation.xml bundled_docs.xslt +Xalan -o ../docs/en/authentication_documentation.html ../docs/source/en/authentication_documentation.xml bundled_docs.xslt +Xalan -o ../docs/en/browser_documentation.html ../docs/source/en/browser_documentation.xml bundled_docs.xslt +Xalan -o ../docs/fr/index.html ../docs/source/fr/simple_test.xml bundled_docs.xslt +Xalan -o ../docs/fr/overview.html ../docs/source/fr/overview.xml bundled_docs.xslt +Xalan -o ../docs/fr/unit_test_documentation.html ../docs/source/fr/unit_test_documentation.xml bundled_docs.xslt +Xalan -o ../docs/fr/group_test_documentation.html ../docs/source/fr/group_test_documentation.xml bundled_docs.xslt +Xalan -o ../docs/fr/server_stubs_documentation.html ../docs/source/fr/server_stubs_documentation.xml bundled_docs.xslt +Xalan -o ../docs/fr/mock_objects_documentation.html ../docs/source/fr/mock_objects_documentation.xml bundled_docs.xslt +Xalan -o ../docs/fr/partial_mocks_documentation.html ../docs/source/fr/partial_mocks_documentation.xml bundled_docs.xslt +Xalan -o ../docs/fr/reporter_documentation.html ../docs/source/fr/reporter_documentation.xml bundled_docs.xslt +Xalan -o ../docs/fr/expectation_documentation.html ../docs/source/fr/expectation_documentation.xml bundled_docs.xslt +Xalan -o ../docs/fr/web_tester_documentation.html ../docs/source/fr/web_tester_documentation.xml bundled_docs.xslt +Xalan -o ../docs/fr/form_testing_documentation.html ../docs/source/fr/form_testing_documentation.xml bundled_docs.xslt +Xalan -o ../docs/fr/authentication_documentation.html ../docs/source/fr/authentication_documentation.xml bundled_docs.xslt +Xalan -o ../docs/fr/browser_documentation.html ../docs/source/fr/browser_documentation.xml bundled_docs.xslt diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/make_lastcraft_docs.sh b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/make_lastcraft_docs.sh new file mode 100755 index 0000000..0eaf742 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/make_lastcraft_docs.sh @@ -0,0 +1,2 @@ +#!/bin/bash +php transform_all_lastcraft.php lastcraft.xslt ../docs/source/en/ ../docs/lastcraft/ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/make_phpdoc_docs.sh b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/make_phpdoc_docs.sh new file mode 100755 index 0000000..fd56f33 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/make_phpdoc_docs.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +#DEST_DIR=../docs/pkg +if [ ! -d ../tutorials ] +then + mkdir ../tutorials +fi +if [ ! -d ../tutorials/SimpleTest ] +then + mkdir ../tutorials/SimpleTest +fi +DEST_DIR=../tutorials/SimpleTest + +rm ${DEST_DIR}/*.pkg +cp ../docs/pkg/SimpleTest.pkg.ini ${DEST_DIR} + +#Xalan -o ${DEST_DIR}/QuickStart.pkg ../docs/source/en/simple_test.xml phpdoc_docs.xslt +Xalan -o ${DEST_DIR}/SimpleTest.pkg ../docs/source/en/overview.xml phpdoc_docs.xslt +Xalan -o ${DEST_DIR}/UnitTestCase.pkg ../docs/source/en/unit_test_documentation.xml phpdoc_docs.xslt +Xalan -o ${DEST_DIR}/GroupTests.pkg ../docs/source/en/group_test_documentation.xml phpdoc_docs.xslt +Xalan -o ${DEST_DIR}/MockObjects.pkg ../docs/source/en/mock_objects_documentation.xml phpdoc_docs.xslt +Xalan -o ${DEST_DIR}/PartialMock.pkg ../docs/source/en/partial_mocks_documentation.xml phpdoc_docs.xslt +Xalan -o ${DEST_DIR}/Reporting.pkg ../docs/source/en/reporter_documentation.xml phpdoc_docs.xslt +Xalan -o ${DEST_DIR}/Expectations.pkg ../docs/source/en/expectation_documentation.xml phpdoc_docs.xslt +Xalan -o ${DEST_DIR}/WebTester.pkg ../docs/source/en/web_tester_documentation.xml phpdoc_docs.xslt +Xalan -o ${DEST_DIR}/FormTesting.pkg ../docs/source/en/form_testing_documentation.xml phpdoc_docs.xslt +Xalan -o ${DEST_DIR}/Authentication.pkg ../docs/source/en/authentication_documentation.xml phpdoc_docs.xslt +Xalan -o ${DEST_DIR}/Browser.pkg ../docs/source/en/browser_documentation.xml phpdoc_docs.xslt + +# some cleanup work +cd $DEST_DIR + +# remove XML declaration +for f in $(ls *.pkg --color=none) +do + grep -v -e '^ tmp.pkg + mv tmp.pkg $f +done + +# fix overview title +cat SimpleTest.pkg | sed -e 's/Overview/Simple Test PHP Unit Test Framework/g;s/<\([A-Za-z0-9]*\)\/>/<\1><\/\1>/g' > tmp.pkg +mv tmp.pkg SimpleTest.pkg diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/onpk/map_onpk.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/onpk/map_onpk.xml new file mode 100644 index 0000000..b4f6b41 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/onpk/map_onpk.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/onpk/onpk.xslt b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/onpk/onpk.xslt new file mode 100644 index 0000000..2053066 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/onpk/onpk.xslt @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + <xsl:value-of select="//long_title"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + + + + + + + + + + + + + + + + + + + +
      + +
    • + + + +
    • +
      +
    +
    +
    + + + + + + + + + + + + +
    + +

    +
    + +
    +
    +
    + + + Cette page... +
      + +
    +
    + + + Pour aller plus loin... +
      + +
    +
    + + +
    + +
    +
    + + + + + + + + + + + + + + +
    +			
    +				
    +			
    +		
    +
    + + + + + + + + +

    + + +

    + +

    + +
    + + + + + + + + + + + + + .php + + + + + + + + + + + + + + + + +
  • +
    + + + + + + + + + + + + + + + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/onpk/transform_all_onpk.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/onpk/transform_all_onpk.php new file mode 100644 index 0000000..c80350a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/onpk/transform_all_onpk.php @@ -0,0 +1,32 @@ +"; + } else { + echo "erreur pour ".$destination." : ".xslt_error($xh)."
    "; + } + + xslt_free($xsltProcessor); +} +closedir($dir); +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/package.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/package.xml new file mode 100644 index 0000000..f82c599 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/package.xml @@ -0,0 +1,710 @@ + + + + SimpleTest + Unit testing, mock objects and web testing framework for PHP. + The heart of SimpleTest is a testing framework built around test case classes. +These are written as extensions of base test case classes, each extended with +methods that actually contain test code. Top level test scripts then invoke +the run() methods on every one of these test cases in order. Each test +method is written to invoke various assertions that the developer expects to +be true such as assertEqual(). If the expectation is correct, then a +successful result is dispatched to the observing test reporter, but any +failure triggers an alert and a description of the mismatch. + +These tools are designed for the developer. Tests are written in the PHP +language itself more or less as the application itself is built. The advantage +of using PHP itself as the testing language is that there are no new languages +to learn, testing can start straight away, and the developer can test any part +of the code. Basically, all parts that can be accessed by the application code +can also be accessed by the test code if they are in the same language. + + + lastcraft + Marcus Baker + marcus@lastcraft.com + lead + + + jsweat + Jason Sweat + jsweat_php@yahoo.com + helper + + + hfuecks + Harry Fuecks + hfuecks@phppatterns.com + helper + + + + 0.9.4 + 2004-02-20 + The Open Group Test Suite License + beta + This is the final version of the PHP unit and web testing tool before the +stable release 1.0 version. It features many improvements to the HTML form +parsing and exposure of the underlying web browser. There are also numerous +minor improvements and bug fixes. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.9.4 + 2004-02-20 + The Open Group Test Suite License + beta + This is the final version of the PHP unit and web testing tool before the +stable release 1.0 version. It features many improvements to the HTML form +parsing and exposure of the underlying web browser. There are also numerous +minor improvements and bug fixes. + + + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/pear_package_create.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/pear_package_create.php new file mode 100755 index 0000000..2b5b7ff --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/pear_package_create.php @@ -0,0 +1,170 @@ +#!/usr/bin/env php -q +'lastcraft','role'=>'lead','name'=>'Marcus Baker', 'email'=>'marcus@lastcraft.com'), + array ('handle'=>'jsweat','role'=>'helper','name'=>'Jason Sweat', 'email'=>'jsweat_php@yahoo.com'), + array ('handle'=>'hfuecks','role'=>'helper','name'=>'Harry Fuecks', 'email'=>'hfuecks@phppatterns.com'), +); +/*---------------------------------------------------------------------------*/ + +/** +* Code starts here +*/ +require_once('PEAR/PackageFileManager.php'); +$PPFM = new PEAR_PackageFileManager; + +if (version_compare(phpversion(), '4.3.0', '<') || + php_sapi_name() == 'cgi') { + define('STDOUT', fopen('php://stdout', 'w')); + define('STDERR', fopen('php://stderr', 'w')); + register_shutdown_function( + create_function('', 'fclose(STDOUT); fclose(STDERR); return true;')); +} + +/** +* A giant array to configure the PackageFileManager. For the "roles" see +* http://pear.php.net/manual/en/developers.packagedef.php +*/ +$options = array( + 'baseinstalldir' => 'simpletest', + 'version' => $version, + 'packagedirectory' => $packagedir, + 'outputdirectory' => $packagedir, + 'pathtopackagefile' => $packagedir, + 'state' => $state, + 'summary' => $shortDesc, + 'description' => $longDesc, + 'filelistgenerator' => 'file', + 'notes' => $releaseNotes, + 'package' => 'SimpleTest', + 'license' => 'The Open Group Test Suite License', + + 'dir_roles' => array( + 'docs' => 'doc', + 'test' => 'test', + 'extensions' => 'php', + //'tutorials' => 'doc', + //'tutorials/SimpleTest' => 'doc', + //'ui' => 'php', + //'ui/css' => 'data', + //'ui/img' => 'data', + //'ui/js' => 'data', + //'ui/js/tests' => 'test', + ), + 'exceptions' => + array( + 'HELP_MY_TESTS_DONT_WORK_ANYMORE' => 'doc', + 'LICENSE' => 'doc', + 'README' => 'doc', + 'TODO' => 'doc', + 'VERSION' => 'doc', + ), + 'ignore' => + array( + "$packagedir/packages", + "$packagedir/ui", + ), + ); + +$status = $PPFM->setOptions($options); + +if (PEAR::isError($status)) { + fwrite (STDERR,$status->getMessage()); + exit; +} + +foreach ( $maintainers as $maintainer ) { + $PPFM->addMaintainer( + $maintainer['handle'], + $maintainer['role'], + $maintainer['name'], + $maintainer['email'] ); +} + +// Adds a dependency of PHP 4.2.3+ +$status = $PPFM->addDependency('php', '4.2.3', 'ge', 'php'); +if (PEAR::isError($status)) { + fwrite (STDERR,$status->getMessage()); + exit; +} + +// hack (apparently) +$PPFM->addRole('tpl', 'php'); +$PPFM->addRole('png', 'php'); +$PPFM->addRole('gif', 'php'); +$PPFM->addRole('jpg', 'php'); +$PPFM->addRole('css', 'php'); +$PPFM->addRole('js', 'php'); +$PPFM->addRole('ini', 'php'); +$PPFM->addRole('inc', 'php'); +$PPFM->addRole('afm', 'php'); +$PPFM->addRole('pkg', 'doc'); +$PPFM->addRole('cls', 'doc'); +$PPFM->addRole('proc', 'doc'); +$PPFM->addRole('sh', 'script'); + +ob_start(); +$status = $PPFM->writePackageFile(false); +$output = ob_get_contents(); +ob_end_clean(); + +// Hacks to handle PPFM output +$start = strpos ($output,"getMessage()); +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/phpdoc_docs.xslt b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/phpdoc_docs.xslt new file mode 100644 index 0000000..6d09150 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/phpdoc_docs.xslt @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + <![CDATA[ + + + + ]]> + + + + + + + + + + + {@id } + <xsl:value-of select="@title"/> + + + + + + + + + + + + <xsl:value-of select="h2"/> + + + + + + + + + .html + + + + } + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.ini b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.ini new file mode 100755 index 0000000..8767680 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.ini @@ -0,0 +1,92 @@ +;; phpDocumentor parse configuration file +;; +;; This file is designed to cut down on repetitive typing on the command-line or web interface +;; You can copy this file to create a number of configuration files that can be used with the +;; command-line switch -c, as in phpdoc -c default.ini or phpdoc -c myini.ini. The web +;; interface will automatically generate a list of .ini files that can be used. +;; +;; default.ini is used to generate the online manual at http://www.phpdoc.org/docs +;; +;; ALL .ini files must be in the user subdirectory of phpDocumentor with an extension of .ini +;; +;; Copyright 2002, Greg Beaver +;; +;; WARNING: do not change the name of any command-line parameters, phpDocumentor will ignore them + +[Parse Data] +;; title of all the documentation +;; legal values: any string +title = SimpleTest API Documentation + +;; parse files that start with a . like .bash_profile +;; legal values: true, false +hidden = false + +;; show elements marked @access private in documentation by setting this to on +;; legal values: on, off +parseprivate = off + +;; parse with javadoc-like description (first sentence is always the short description) +;; legal values: on, off +javadocdesc = off + +;; add any custom @tags separated by commas here +;; legal values: any legal tagname separated by commas. +;customtags = mytag1,mytag2 + +;; This is only used by the XML:DocBook/peardoc2 converter +defaultcategoryname = Documentation + +;; what is the main package? +;; legal values: alphanumeric string plus - and _ +defaultpackagename = SimpleTest + +;; output any parsing information? set to on for cron jobs +;; legal values: on +;quiet = on + +;; parse a PEAR-style repository. Do not turn this on if your project does +;; not have a parent directory named "pear" +;; legal values: on/off +;pear = on + +;; where should the documentation be written? +;; legal values: a legal path +target = /home/groups/s/si/simpletest/htdocs/api + +;; limit output to the specified packages, even if others are parsed +;; legal values: package names separated by commas +;packageoutput = package1,package2 + +;; comma-separated list of files to parse +;; legal values: paths separated by commas +;filename = /path/to/file1,/path/to/file2,fileincurrentdirectory + +;; comma-separated list of directories to parse +;; legal values: directory paths separated by commas +;directory = /path1,/path2,.,..,subdirectory +;directory = /home/jeichorn/cvs/pear +directory = /home/groups/s/si/simpletest/simpletest_doc_target + +;; template base directory (the equivalent directory of /phpDocumentor) +;templatebase = /path/to/my/templates + +;; comma-separated list of files, directories or wildcards ? and * (any wildcard) to ignore +;; legal values: any wildcard strings separated by commas +;ignore = /path/to/ignore*,*list.php,myfile.php,subdirectory/ +;ignore = templates_c/,*HTML/default/*,spec/ +ignore = docs/,packages/,test/ + +;; comma-separated list of Converters to use in outputformat:Convertername:templatedirectory format +;; legal values: HTML:frames:default,HTML:frames:l0l33t,HTML:frames:phpdoc.de,HTML:frames:phphtmllib, +;; HTML:frames:earthli, +;; HTML:frames:DOM/default,HTML:frames:DOM/l0l33t,HTML:frames:DOM/phpdoc.de, +;; HTML:frames:DOM/phphtmllib,HTML:frames:DOM/earthli +;; HTML:Smarty:default,HTML:Smarty:PHP,HTML:Smarty:HandS +;; PDF:default:default,CHM:default:default,XML:DocBook/peardoc2:default +;output=HTML:frames:earthli,HTML:frames:default,HTML:frames:l0l33t,HTML:frames:phpdoc.de,HTML:frames:phphtmllib,HTML:frames:DOM/default,HTML:frames:DOM/l0l33t,HTML:frames:DOM/phpdoc.de,HTML:frames:DOM/earthli,HTML:frames:DOM/phphtmllib,HTML:frames:phpedit,HTML:Smarty:default,HTML:Smarty:PHP,HTML:Smarty:HandS,CHM:default:default,PDF:default:default +output=HTML:frames:DOM/earthlisf + +;; turn this option on if you want highlighted source code for every file +;; legal values: on/off +sourcecode = off diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/index.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/index.php new file mode 100644 index 0000000..9c8a6e6 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/index.php @@ -0,0 +1,54 @@ +destination(dirname(__FILE__).'/map.xml'); + + if (!empty($destination)) { + $page = file_get_contents(dirname(__FILE__).'/template.html'); + + $page = str_replace('KEYWORDS', $source->keywords(), $page); + $page = str_replace('TITLE', $source->title(), $page); + $page = str_replace('CONTENT', $source->content(), $page); + $page = str_replace('INTERNAL', $source->internal(), $page); + $page = str_replace('EXTERNAL', $source->external(), $page); + + $links = $source->links(dirname(__FILE__).'/map.xml'); + foreach ($links as $category => $link) { + $page = str_replace("LINKS_".strtoupper($category), $link, $page); + } + + $destination_dir = dirname($destination_path.$destination); + if (!is_dir($destination_dir)) { + mkdir($destination_dir); + } + + $ok = file_put_contents($destination_path.$destination, $page); + touch($destination_path.$destination, filemtime($source_path.$language.$file)); + + if ($ok) { + $result = "OK"; + } else { + $result = "KO"; + } + + $synchronisation = new PackagingSynchronisation($source_path.$language.$file); + $result .= " ".$synchronisation->result(); + + echo $destination_path.$destination." : ".$result."\n"; + } + } + } + closedir($dir); +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/integration.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/integration.php new file mode 100755 index 0000000..e4c5881 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/integration.php @@ -0,0 +1,41 @@ +updateTestLogs($cli_directory, $test); + +$working_copy = dirname(__FILE__)."/working-copies/simpletest"; +$binary = "svn"; +$integration->updateSvnLog($working_copy, $binary); + +class SimpleIntegration { + public $target_directory; + + function __construct($target_directory="") { + $this->target_directory = $target_directory; + } + + function updateTestLogs($cli_directory, $test_file) { + foreach(new DirectoryIterator($cli_directory) as $node) { + if ($node->isDir() and !$node->isDot()) { + $bin = $node->getPathname()."/bin/php"; + $result = shell_exec($bin." ".$test_file); + + $result_file = $this->target_directory."/simpletest.".$node->getFilename().".log"; + file_put_contents($result_file, $result); + } + } + } + + function updateSvnLog($working_copy, $binary="svn") { + $start = date("Y-m-d", strtotime('-1year')); + $command = $binary." log --xml --revision {".$start."}:HEAD ".$working_copy." > ".$this->target_directory."/svn.xml"; + return exec($command); + } +} + +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/map.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/map.xml new file mode 100644 index 0000000..78cb933 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/map.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/package.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/package.php new file mode 100644 index 0000000..f266625 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/package.php @@ -0,0 +1,440 @@ +xpath('//page'); + return $titles[0]->attributes()->title; + } + + function transform_code($code) { + $code = str_replace('', '', $code); + $code = str_replace('<strong>', '', $code); + $code = str_replace('</strong>', '', $code); + $code = str_replace('&', '&', $code); + + return $code; + } + + function content() { + $content = $this->introduction(); + $sections = $this->xpath('//section'); + if (count($sections) > 0) { + $content .= $this->content_with_sections(); + } else { + $content .= $this->content_without_sections(); + } + $content = preg_replace("/href=\"([a-z_]*)\.php\"/", "href=\"\\1.html\"", $content); + + return $content; + } + + function introduction() { + $content = ""; + + $introductions = $this->xpath('//introduction'); + foreach ($introductions as $introduction) { + foreach ($introduction as $element) { + $content .= $this->deal_with_php_code($element->asXML()); + } + } + + return $content; + } + + function content_without_sections() { + $content_without_sections = ""; + $contents = $this->xpath('//content'); + foreach ($contents as $content) { + $content_without_sections .= $this->deal_with_php_code($content->asXML()); + } + + return $content_without_sections; + } + + function deal_with_php_code($content) { + $elements_divided = preg_split('/|<\/php>/', $content); + $content_element = ''; + + if (count($elements_divided) > 1) { + foreach ($elements_divided as $element_divided) { + if (strpos($element_divided, '
    '.$this->transform_code($element_divided).'

    '; + } + $content_element .= $element_divided; + } + } else { + $content_element .= $content; + } + + return $content_element; + } + + function as_title($name) { + return ucfirst(str_replace("-", " ", $name)); + } + + function as_tracker_link($number) { + return "".$number.""; + } + + function content_with_sections() { + $content = ""; + $sections = $this->xpath('//section'); + $anchors = array(); + foreach ($sections as $section) { + if (!isset($anchors[(string)$section->attributes()->name])) { + $content .= "attributes()->name."\">"; + $anchors[(string)$section->attributes()->name] = true; + } + $content .= "

    ".(string)$section->attributes()->title."

    "; + foreach ($section->p as $paragraph) { + $content .= $this->deal_with_php_code($paragraph->asXML()); + } + $content .= $this->deal_with_milestones($section); + $content .= $this->deal_with_changelogs($section); + } + + return $content; + } + + function deal_with_changelogs($section) { + $content = ""; + + foreach ($section->changelog as $changelog) { + $content .= "

    Version ".(string)$changelog->attributes()->version."

    "; + $content .= "
      "; + foreach ($changelog->change as $change) { + $content .= "
    • "; + $content .= trim((string)$change); + $content .= "
    • "; + } + foreach ($changelog->bug as $bug) { + $content .= "
    • "; + $number = ""; + if (isset($bug->attributes()->tracker)) { + $number = " ".$this->as_tracker_link($bug->attributes()->tracker); + } + $content .= "[bug".$number."] ".trim((string)$bug); + $content .= "
    • "; + } + foreach ($changelog->patch as $patch) { + $content .= "
    • "; + $number = ""; + if (isset($patch->attributes()->tracker)) { + $number = " ".$this->as_tracker_link($patch->attributes()->tracker); + } + $content .= "[patch".$number."] ".trim((string)$patch); + $content .= "
    • "; + } + $content .= "
    "; + } + return $content; + } + + function deal_with_milestones($section) { + $content = ""; + + foreach ($section->milestone as $milestone) { + $content .= "

    ".(string)$milestone->attributes()->version."

    "; + foreach ($milestone->concern as $concern) { + if (!isset($anchors[(string)$concern->attributes()->name])) { + $content .= "attributes()->name."\">"; + $anchors[(string)$concern->attributes()->name] = true; + } + $content .= "

    ".$this->as_title($concern->attributes()->name)."

    "; + if (sizeof($concern) > 0) { + $content .= "
    "; + foreach ($concern as $type => $element) { + $status = ""; + if (isset($element->attributes()->status)) { + $status = " class=\"".$element->attributes()->status."\""; + } + $content .= "[".$type."] ".trim($element).""; + foreach ($element->attributes() as $name => $value) { + if ($name == "tracker" and $type == "bug") { + $value = $this->as_tracker_link($value); + } + $content .= "
    ".$name." : ".$value."
    "; + } + foreach ($element->note as $note) { + $content .= "
    ".trim((string)$note)."
    "; + } + } + $content .= "
    "; + } + } + } + + return $content; + } + + function internal() { + $internal = ""; + + if (isset($this->internal->link)) { + foreach ($this->internal->link as $link) { + $internal .= "
    ".$link->asXML()."
    "; + } + } + + return $internal; + } + + function external() { + $external = ""; + + if (isset($this->external->link)) { + foreach ($this->external->link as $link) { + $external .= "
    ".$link->asXML()."
    "; + } + } + + return $external; + } + + function keywords() { + return trim(preg_replace('/(\s+)/', ' ', $this->meta->keywords)); + } + + function here() { + $here = $this->xpath('@here'); + return (string)$here[0]; + } + + function parent($map) { + $here = $this->here(); + $pages = $map->xpath('//page[normalize-space(@here)="'.$here.'"]/parent::*'); + return $pages[0]->attributes()->here; + } + + function destination($path_to_map) { + $destination = ''; + $here = $this->here(); + + $map = simplexml_load_file($path_to_map); + $pages = $map->xpath('//page'); + $i = 0; + foreach ($pages as $page) { + $i++; + if ((string)$page->attributes()->here == $here) { + $destination = (string)$page->attributes()->file; + break; + } + } + return $destination; + } + + function url($file) { + $segments = explode("/", $file); + + return array_pop($segments); + } + + function links_from_xpath($xpath, $map) { + $link = ""; + + $here = $this->here(); + $pages = $map->xpath($xpath); + foreach ($pages as $page) { + $link .= '
  • '; + $link .= $page->attributes()->title.'
  • '; + } + + return $link; + } + + function links_parent_siblings_after($map) { + $here = $this->parent($map); + $query = '//page[normalize-space(@here)="'.$here.'"]/following-sibling::*'; + + return $this->links_from_xpath($query, $map); + } + + function links_parent($map) { + $here = $this->parent($map); + $query = '//page[normalize-space(@here)="'.$here.'"]'; + + return $this->links_from_xpath($query, $map); + } + + function links_parent_siblings_before($map) { + $here = $this->parent($map); + $query = '//page[normalize-space(@here)="'.$here.'"]/preceding-sibling::*'; + + return $this->links_from_xpath($query, $map); + } + + function links_parent_ancestors($map) { + $here = $this->parent($map); + return $this->links_ancestors_from($here, $map); + } + + function links_self_ancestors($map) { + $here = $this->here(); + return $this->links_ancestors_from($here, $map); + } + + function links_ancestors_from($here, $map) { + $link = ""; + + $pages = $map->xpath('//page[normalize-space(@here)="'.$here.'"]/ancestor::*'); + foreach ($pages as $page) { + $here = (string)$page->attributes()->here; + if ($this->level_from_root($here, $map) >= 2) { + $link .= '
  • '; + $link .= $page->attributes()->title.'
  • '; + } + } + + return $link; + } + function links_siblings_before($map) { + $here = $this->here(); + $query = '//page[normalize-space(@here)="'.$here.'"]/preceding-sibling::*'; + + return $this->links_from_xpath($query, $map); + } + + function links_self($map) { + $here = $this->here(); + $query = '//page[normalize-space(@here)="'.$here.'"]'; + + return $this->links_from_xpath($query, $map); + } + + function links_siblings_after($map) { + $here = $this->here(); + $query = '//page[normalize-space(@here)="'.$here.'"]/following-sibling::*'; + + return $this->links_from_xpath($query, $map); + } + + function links_children($map) { + $here = $this->here(); + $query = '//page[normalize-space(@here)="'.$here.'"]/child::*'; + + return $this->links_from_xpath($query, $map); + } + + function links($path_to_map) { + $links['download'] = ""; + $links['start_testing'] = ""; + $links['support'] = ""; + $links['contribute'] = ""; + + $map = simplexml_load_file($path_to_map); + + $link = '
      '; + $here = $this->here(); + $level = $this->level_from_root($here, $map); + if ($level == 2) { + $link .= $this->links_self($map); + $link .= $this->links_children($map); + } + if ($level == 3) { + $link .= $this->links_self_ancestors($map); + $link .= $this->links_siblings_before($map); + $link .= $this->links_self($map); + $chilren = $this->links_children($map); + if ($chilren) { + $link = preg_replace('/(<\/li>)$/', '', $link).'
        '.$chilren.'
      '; + } + $link .= $this->links_siblings_after($map); + } + if ($level == 4) { + $link .= $this->links_parent_ancestors($map); + $link .= $this->links_parent_siblings_before($map); + $link .= $this->links_parent($map); + $link = preg_replace('/(<\/li>)$/', '', $link).'
        '; + $link .= $this->links_siblings_before($map); + $link .= $this->links_self($map); + $chilren = $this->links_children($map); + if ($chilren) { + $link = preg_replace('/(<\/li>)$/', '', $link).'
          '.$chilren.'
        '; + } + $link .= $this->links_siblings_after($map); + $link .= '
      '; + $link .= $this->links_parent_siblings_after($map); + } + $link .= '
    '; + + if (strpos($link, 'download.html') !== false) { + $links['download'] = $link; + } elseif (strpos($link, 'start-testing.html') !== false) { + $links['start_testing'] = $link; + } elseif (strpos($link, 'support.html') !== false) { + $links['support'] = $link; + } elseif (strpos($link, 'todo.html') !== false) { + $links['contribute'] = $link; + } + + return $links; + } + + function level_from_root($here, $map) { + $ancestors = $map->xpath('//page[normalize-space(@here)="'.$here.'"]/ancestor::*'); + + return count($ancestors); + } +} + +class PackagingSynchronisation { + public $file; + public $lang; + public $content; + + function __construct($file, $lang="fr") { + $this->file = $file; + $this->lang = $lang; + $this->content = ""; + if (file_exists($this->file)) { + $this->content = file_get_contents($this->file); + } + } + + function isSynchronisable() { + return (bool)strpos($this->content, "isSynchronisable()) { + return "source"; + } elseif (!$this->sourceRevision()) { + return "missing id"; + } elseif ($this->sourceRevision() > $this->lastSynchroRevision()) { + return "late"; + } else { + return "synchro"; + } + } + + function revision() { + $matches = array(); + preg_match("/Id: [a-z_-]*\.[a-z]* ([0-9]*)/", $this->content, $matches); + return $matches[1]; + } + + function sourceLang() { + $matches = array(); + preg_match("/synchronisation.*lang=\"([a-z]*)\"/", $this->content, $matches); + return $matches[1]; + } + + function sourceRevision() { + $source_lang = $this->sourceLang(); + $source_file = str_replace("/".$this->lang."/", "/".$source_lang."/", $this->file); + if (file_exists($source_file)) { + $source = new PackagingSynchronisation($source_file, $source_lang); + return $source->revision(); + } + return false; + } + + function lastSynchroRevision() { + $matches = array(); + preg_match("/synchronisation.*version=\"([0-9]*)\"/", $this->content, $matches); + return $matches[1]; + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/template.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/template.html new file mode 100644 index 0000000..e785398 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/template.html @@ -0,0 +1,59 @@ + + + + + +TITLE + + + + +
    +
    + +
    +
    + + LINKS_DOWNLOAD +
    +
    + + LINKS_START_TESTING +
    +
    + + LINKS_SUPPORT +
    +
    + + LINKS_CONTRIBUTE +
    +
    + + SourceForge.net Logo + +
    +
    +
    +
    +

    TITLE

    + + CONTENT + +
    + +
    INTERNAL
    + +
    +
    + +
    EXTERNAL
    + +
    +
    +
    + + + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/content_without_section.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/content_without_section.xml new file mode 100644 index 0000000..d3b1308 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/content_without_section.xml @@ -0,0 +1,91 @@ + + + + Overview and feature list for the SimpleTest PHP unit tester and web tester + + +

    + A test case looks like this... +MyTestCase extends UnitTestCase { + + function testLogWroteMessage() { + $log = &new Log('my.log'); + $log->message('Hello'); + $this->assertTrue(file_exists('my.log')); + } +} +?> +]]> +

    +
    + + + Quick summary + of the SimpleTest tool for PHP. + + + List of features, + both current ones and those planned. + + + There are plenty of unit testing resources + on the web. + + + + + Documentation for SimpleTest. + + + How to write PHP test cases + is a fairly advanced tutorial. + + + SimpleTest API from phpdoc. + + + + + software development tools, + php programming, + programming php, + software development tools, + Tools for extreme programming, + free php scripts, + links of testing tools, + php testing resources, + mock objects, + junit, + jwebunit, + htmlunit, + itc, + php testing links, + unit test advice and documentation, + extreme programming in php + + + + + + Marcus Baker + + Primary Developer{@link mailto:marcus@lastcraft.com marcus@lastcraft.com} + + + + Harry Fuecks + + Packager{@link mailto:harryf@users.sourceforge.net harryf@users.sourceforge.net} + + + + Jason Sweat + + Documentation{@link mailto:jsweat_php@yahoo.com jsweat_php@yahoo.com} + + + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/en/synchronisation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/en/synchronisation.xml new file mode 100755 index 0000000..f95a8ad --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/en/synchronisation.xml @@ -0,0 +1,92 @@ + + + + + Overview and feature list for the SimpleTest PHP unit tester and web tester + + +

    + A test case looks like this... +MyTestCase extends UnitTestCase { + + function testLogWroteMessage() { + $log = &new Log('my.log'); + $log->message('Hello'); + $this->assertTrue(file_exists('my.log')); + } +} +?> +]]> +

    +
    + + + Quick summary + of the SimpleTest tool for PHP. + + + List of features, + both current ones and those planned. + + + There are plenty of unit testing resources + on the web. + + + + + Documentation for SimpleTest. + + + How to write PHP test cases + is a fairly advanced tutorial. + + + SimpleTest API from phpdoc. + + + + + software development tools, + php programming, + programming php, + software development tools, + Tools for extreme programming, + free php scripts, + links of testing tools, + php testing resources, + mock objects, + junit, + jwebunit, + htmlunit, + itc, + php testing links, + unit test advice and documentation, + extreme programming in php + + + + + + Marcus Baker + + Primary Developer{@link mailto:marcus@lastcraft.com marcus@lastcraft.com} + + + + Harry Fuecks + + Packager{@link mailto:harryf@users.sourceforge.net harryf@users.sourceforge.net} + + + + Jason Sweat + + Documentation{@link mailto:jsweat_php@yahoo.com jsweat_php@yahoo.com} + + + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/fr/no-synchronisation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/fr/no-synchronisation.xml new file mode 100755 index 0000000..d3b1308 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/fr/no-synchronisation.xml @@ -0,0 +1,91 @@ + + + + Overview and feature list for the SimpleTest PHP unit tester and web tester + + +

    + A test case looks like this... +MyTestCase extends UnitTestCase { + + function testLogWroteMessage() { + $log = &new Log('my.log'); + $log->message('Hello'); + $this->assertTrue(file_exists('my.log')); + } +} +?> +]]> +

    +
    + + + Quick summary + of the SimpleTest tool for PHP. + + + List of features, + both current ones and those planned. + + + There are plenty of unit testing resources + on the web. + + + + + Documentation for SimpleTest. + + + How to write PHP test cases + is a fairly advanced tutorial. + + + SimpleTest API from phpdoc. + + + + + software development tools, + php programming, + programming php, + software development tools, + Tools for extreme programming, + free php scripts, + links of testing tools, + php testing resources, + mock objects, + junit, + jwebunit, + htmlunit, + itc, + php testing links, + unit test advice and documentation, + extreme programming in php + + + + + + Marcus Baker + + Primary Developer{@link mailto:marcus@lastcraft.com marcus@lastcraft.com} + + + + Harry Fuecks + + Packager{@link mailto:harryf@users.sourceforge.net harryf@users.sourceforge.net} + + + + Jason Sweat + + Documentation{@link mailto:jsweat_php@yahoo.com jsweat_php@yahoo.com} + + + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/fr/synchronisation.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/fr/synchronisation.xml new file mode 100755 index 0000000..7fd400c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/fr/synchronisation.xml @@ -0,0 +1,93 @@ + + + + + + Le texte en français + + +

    + A test case looks like this... +MyTestCase extends UnitTestCase { + + function testLogWroteMessage() { + $log = &new Log('my.log'); + $log->message('Hello'); + $this->assertTrue(file_exists('my.log')); + } +} +?> +]]> +

    +
    + + + Quick summary + of the SimpleTest tool for PHP. + + + List of features, + both current ones and those planned. + + + There are plenty of unit testing resources + on the web. + + + + + Documentation for SimpleTest. + + + How to write PHP test cases + is a fairly advanced tutorial. + + + SimpleTest API from phpdoc. + + + + + software development tools, + php programming, + programming php, + software development tools, + Tools for extreme programming, + free php scripts, + links of testing tools, + php testing resources, + mock objects, + junit, + jwebunit, + htmlunit, + itc, + php testing links, + unit test advice and documentation, + extreme programming in php + + + + + + Marcus Baker + + Primary Developer{@link mailto:marcus@lastcraft.com marcus@lastcraft.com} + + + + Harry Fuecks + + Packager{@link mailto:harryf@users.sourceforge.net harryf@users.sourceforge.net} + + + + Jason Sweat + + Documentation{@link mailto:jsweat_php@yahoo.com jsweat_php@yahoo.com} + + + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/here_download.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/here_download.xml new file mode 100644 index 0000000..4179725 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/here_download.xml @@ -0,0 +1,91 @@ + + + + Overview and feature list for the SimpleTest PHP unit tester and web tester + + +

    + A test case looks like this... +MyTestCase extends UnitTestCase { + + function testLogWroteMessage() { + $log = &new Log('my.log'); + $log->message('Hello'); + $this->assertTrue(file_exists('my.log')); + } +} +?> +]]> +

    +
    + + + Quick summary + of the SimpleTest tool for PHP. + + + List of features, + both current ones and those planned. + + + There are plenty of unit testing resources + on the web. + + + + + Documentation for SimpleTest. + + + How to write PHP test cases + is a fairly advanced tutorial. + + + SimpleTest API from phpdoc. + + + + + software development tools, + php programming, + programming php, + software development tools, + Tools for extreme programming, + free php scripts, + links of testing tools, + php testing resources, + mock objects, + junit, + jwebunit, + htmlunit, + itc, + php testing links, + unit test advice and documentation, + extreme programming in php + + + + + + Marcus Baker + + Primary Developer{@link mailto:marcus@lastcraft.com marcus@lastcraft.com} + + + + Harry Fuecks + + Packager{@link mailto:harryf@users.sourceforge.net harryf@users.sourceforge.net} + + + + Jason Sweat + + Documentation{@link mailto:jsweat_php@yahoo.com jsweat_php@yahoo.com} + + + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/here_overview.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/here_overview.xml new file mode 100644 index 0000000..d3b1308 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/here_overview.xml @@ -0,0 +1,91 @@ + + + + Overview and feature list for the SimpleTest PHP unit tester and web tester + + +

    + A test case looks like this... +MyTestCase extends UnitTestCase { + + function testLogWroteMessage() { + $log = &new Log('my.log'); + $log->message('Hello'); + $this->assertTrue(file_exists('my.log')); + } +} +?> +]]> +

    +
    + + + Quick summary + of the SimpleTest tool for PHP. + + + List of features, + both current ones and those planned. + + + There are plenty of unit testing resources + on the web. + + + + + Documentation for SimpleTest. + + + How to write PHP test cases + is a fairly advanced tutorial. + + + SimpleTest API from phpdoc. + + + + + software development tools, + php programming, + programming php, + software development tools, + Tools for extreme programming, + free php scripts, + links of testing tools, + php testing resources, + mock objects, + junit, + jwebunit, + htmlunit, + itc, + php testing links, + unit test advice and documentation, + extreme programming in php + + + + + + Marcus Baker + + Primary Developer{@link mailto:marcus@lastcraft.com marcus@lastcraft.com} + + + + Harry Fuecks + + Packager{@link mailto:harryf@users.sourceforge.net harryf@users.sourceforge.net} + + + + Jason Sweat + + Documentation{@link mailto:jsweat_php@yahoo.com jsweat_php@yahoo.com} + + + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/here_simpletest.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/here_simpletest.xml new file mode 100644 index 0000000..428b3c7 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/here_simpletest.xml @@ -0,0 +1,91 @@ + + + + Overview and feature list for the SimpleTest PHP unit tester and web tester + + +

    + A test case looks like this... +MyTestCase extends UnitTestCase { + + function testLogWroteMessage() { + $log = &new Log('my.log'); + $log->message('Hello'); + $this->assertTrue(file_exists('my.log')); + } +} +?> +]]> +

    +
    + + + Quick summary + of the SimpleTest tool for PHP. + + + List of features, + both current ones and those planned. + + + There are plenty of unit testing resources + on the web. + + + + + Documentation for SimpleTest. + + + How to write PHP test cases + is a fairly advanced tutorial. + + + SimpleTest API from phpdoc. + + + + + software development tools, + php programming, + programming php, + software development tools, + Tools for extreme programming, + free php scripts, + links of testing tools, + php testing resources, + mock objects, + junit, + jwebunit, + htmlunit, + itc, + php testing links, + unit test advice and documentation, + extreme programming in php + + + + + + Marcus Baker + + Primary Developer{@link mailto:marcus@lastcraft.com marcus@lastcraft.com} + + + + Harry Fuecks + + Packager{@link mailto:harryf@users.sourceforge.net harryf@users.sourceforge.net} + + + + Jason Sweat + + Documentation{@link mailto:jsweat_php@yahoo.com jsweat_php@yahoo.com} + + + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/here_start_testing.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/here_start_testing.xml new file mode 100644 index 0000000..d3b1308 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/here_start_testing.xml @@ -0,0 +1,91 @@ + + + + Overview and feature list for the SimpleTest PHP unit tester and web tester + + +

    + A test case looks like this... +MyTestCase extends UnitTestCase { + + function testLogWroteMessage() { + $log = &new Log('my.log'); + $log->message('Hello'); + $this->assertTrue(file_exists('my.log')); + } +} +?> +]]> +

    +
    + + + Quick summary + of the SimpleTest tool for PHP. + + + List of features, + both current ones and those planned. + + + There are plenty of unit testing resources + on the web. + + + + + Documentation for SimpleTest. + + + How to write PHP test cases + is a fairly advanced tutorial. + + + SimpleTest API from phpdoc. + + + + + software development tools, + php programming, + programming php, + software development tools, + Tools for extreme programming, + free php scripts, + links of testing tools, + php testing resources, + mock objects, + junit, + jwebunit, + htmlunit, + itc, + php testing links, + unit test advice and documentation, + extreme programming in php + + + + + + Marcus Baker + + Primary Developer{@link mailto:marcus@lastcraft.com marcus@lastcraft.com} + + + + Harry Fuecks + + Packager{@link mailto:harryf@users.sourceforge.net harryf@users.sourceforge.net} + + + + Jason Sweat + + Documentation{@link mailto:jsweat_php@yahoo.com jsweat_php@yahoo.com} + + + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/here_support.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/here_support.xml new file mode 100644 index 0000000..4d43dc4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/here_support.xml @@ -0,0 +1,91 @@ + + + + Overview and feature list for the SimpleTest PHP unit tester and web tester + + +

    + A test case looks like this... +MyTestCase extends UnitTestCase { + + function testLogWroteMessage() { + $log = &new Log('my.log'); + $log->message('Hello'); + $this->assertTrue(file_exists('my.log')); + } +} +?> +]]> +

    +
    + + + Quick summary + of the SimpleTest tool for PHP. + + + List of features, + both current ones and those planned. + + + There are plenty of unit testing resources + on the web. + + + + + Documentation for SimpleTest. + + + How to write PHP test cases + is a fairly advanced tutorial. + + + SimpleTest API from phpdoc. + + + + + software development tools, + php programming, + programming php, + software development tools, + Tools for extreme programming, + free php scripts, + links of testing tools, + php testing resources, + mock objects, + junit, + jwebunit, + htmlunit, + itc, + php testing links, + unit test advice and documentation, + extreme programming in php + + + + + + Marcus Baker + + Primary Developer{@link mailto:marcus@lastcraft.com marcus@lastcraft.com} + + + + Harry Fuecks + + Packager{@link mailto:harryf@users.sourceforge.net harryf@users.sourceforge.net} + + + + Jason Sweat + + Documentation{@link mailto:jsweat_php@yahoo.com jsweat_php@yahoo.com} + + + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/here_unit-tester.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/here_unit-tester.xml new file mode 100644 index 0000000..7e32910 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/here_unit-tester.xml @@ -0,0 +1,91 @@ + + + + Overview and feature list for the SimpleTest PHP unit tester and web tester + + +

    + A test case looks like this... +MyTestCase extends UnitTestCase { + + function testLogWroteMessage() { + $log = &new Log('my.log'); + $log->message('Hello'); + $this->assertTrue(file_exists('my.log')); + } +} +?> +]]> +

    +
    + + + Quick summary + of the SimpleTest tool for PHP. + + + List of features, + both current ones and those planned. + + + There are plenty of unit testing resources + on the web. + + + + + Documentation for SimpleTest. + + + How to write PHP test cases + is a fairly advanced tutorial. + + + SimpleTest API from phpdoc. + + + + + software development tools, + php programming, + programming php, + software development tools, + Tools for extreme programming, + free php scripts, + links of testing tools, + php testing resources, + mock objects, + junit, + jwebunit, + htmlunit, + itc, + php testing links, + unit test advice and documentation, + extreme programming in php + + + + + + Marcus Baker + + Primary Developer{@link mailto:marcus@lastcraft.com marcus@lastcraft.com} + + + + Harry Fuecks + + Packager{@link mailto:harryf@users.sourceforge.net harryf@users.sourceforge.net} + + + + Jason Sweat + + Documentation{@link mailto:jsweat_php@yahoo.com jsweat_php@yahoo.com} + + + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/map.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/map.xml new file mode 100644 index 0000000..ec25596 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/map.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/one_section_changelogged.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/one_section_changelogged.xml new file mode 100755 index 0000000..22b1d90 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/one_section_changelogged.xml @@ -0,0 +1,65 @@ + + + + SimpleTest's Changelog + +
    + + + Patches and whitespace clean up + + + Some in line documentation fixes + + + SimpleFileLoader::load: fix for $test_file already included, by daniel hahler - blueyed + + + New tests for UTF8 inside the browser. + + + Fixing one of the incompatible interface errors + + + expectException() + + +
    +
    + + + Changelog for version 1.0.1 + + + + + SimpleTest project page on SourceForge. + + + The developer's API for SimpleTest + gives full detail on the classes and assertions available. + + + + + software development, + php programming for clients, + customer focused php, + software development tools, + acceptance testing framework, + free php scripts, + log in boxes, + unit testing authentication systems, + php resources, + HTMLUnit, + JWebUnit, + php testing, + unit test resource, + web testing, + HTTP authentication, + testing log in, + authentication testing, + security tests + + +
    \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/one_section_milestoned.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/one_section_milestoned.xml new file mode 100644 index 0000000..f495772 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/one_section_milestoned.xml @@ -0,0 +1,95 @@ + + + + Overview and feature list for the SimpleTest PHP unit tester and web tester + + +
    + + + Undefined property $_reporter + fatal error + + + + The HELP_MY_TESTS_DONT_WORK_ANYMORE needs to be updated. + + + + + PHP 5.3 compatible under E_STRICT + PHP 5.2.0-5 compatible under E_STRICT + PHP 5.1.0-6 compatible under E_STRICT + continuous integration + + error_reporting(E_ALL|E_STRICT)gives lots of warning + + We've know this for years, this is the time. + + + + Drop underscores from protected methods and + private variables. + Make all variables private and add protected + accessors where we use them internally. + + That way people will start complaining. + Upon each complaint we'll add an accessor and + capture the use case from them. + + + We'll stick the use cases in the feature request + tracker for now + + + Move web site to new server + + +
    +
    + + + Quick summary + of the SimpleTest tool for PHP. + + + List of features, + both current ones and those planned. + + + There are plenty of unit testing resources + on the web. + + + + + Documentation for SimpleTest. + + + How to write PHP test cases + is a fairly advanced tutorial. + + + SimpleTest API from phpdoc. + + + + + software development tools, + php programming, + programming php, + software development tools, + Tools for extreme programming, + free php scripts, + links of testing tools, + php testing resources, + mock objects, + junit, + jwebunit, + htmlunit, + itc, + php testing links, + unit test advice and documentation, + extreme programming in php + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/one_section_with_autorum_php.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/one_section_with_autorum_php.xml new file mode 100644 index 0000000..8e6ee88 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/one_section_with_autorum_php.xml @@ -0,0 +1,87 @@ + + + + Overview and feature list for the SimpleTest PHP unit tester and web tester + + +
    +

    + Use the &quote;autorun.php&quote; with no link. +

    +

    + Use the "autodive.php" with no link. +

    +

    + Use the autowalk. +

    +
    +
    + + + Quick summary + of the SimpleTest tool for PHP. + + + List of features, + both current ones and those planned. + + + There are plenty of unit testing resources + on the web. + + + + + Documentation for SimpleTest. + + + How to write PHP test cases + is a fairly advanced tutorial. + + + SimpleTest API from phpdoc. + + + + + software development tools, + php programming, + programming php, + software development tools, + Tools for extreme programming, + free php scripts, + links of testing tools, + php testing resources, + mock objects, + junit, + jwebunit, + htmlunit, + itc, + php testing links, + unit test advice and documentation, + extreme programming in php + + + + + + Marcus Baker + + Primary Developer{@link mailto:marcus@lastcraft.com marcus@lastcraft.com} + + + + Harry Fuecks + + Packager{@link mailto:harryf@users.sourceforge.net harryf@users.sourceforge.net} + + + + Jason Sweat + + Documentation{@link mailto:jsweat_php@yahoo.com jsweat_php@yahoo.com} + + + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/one_section_with_php_code.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/one_section_with_php_code.xml new file mode 100644 index 0000000..552647d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package/one_section_with_php_code.xml @@ -0,0 +1,93 @@ + + + + Overview and feature list for the SimpleTest PHP unit tester and web tester + + +
    +

    + A test case looks like this... +MyTestCase extends UnitTestCase { + + function testLogWroteMessage() { + $log = &new Log('my.log'); + $log->message('Hello'); + $this->assertTrue(file_exists('my.log')); + } +} +?> +]]> +

    +
    +
    + + + Quick summary + of the SimpleTest tool for PHP. + + + List of features, + both current ones and those planned. + + + There are plenty of unit testing resources + on the web. + + + + + Documentation for SimpleTest. + + + How to write PHP test cases + is a fairly advanced tutorial. + + + SimpleTest API from phpdoc. + + + + + software development tools, + php programming, + programming php, + software development tools, + Tools for extreme programming, + free php scripts, + links of testing tools, + php testing resources, + mock objects, + junit, + jwebunit, + htmlunit, + itc, + php testing links, + unit test advice and documentation, + extreme programming in php + + + + + + Marcus Baker + + Primary Developer{@link mailto:marcus@lastcraft.com marcus@lastcraft.com} + + + + Harry Fuecks + + Packager{@link mailto:harryf@users.sourceforge.net harryf@users.sourceforge.net} + + + + Jason Sweat + + Documentation{@link mailto:jsweat_php@yahoo.com jsweat_php@yahoo.com} + + + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package_test.php new file mode 100644 index 0000000..cb2b955 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/simpletest.org/test/package_test.php @@ -0,0 +1,150 @@ +assertEqual($synchro->result(), "source"); + + $source = dirname(__FILE__)."/package/en/synchronisation.xml"; + $synchro = new PackagingSynchronisation($source); + $this->assertEqual($synchro->result(), "source"); + } + + function testOfSynchronisationNecessary() { + $source = dirname(__FILE__)."/package/fr/synchronisation.xml"; + $synchro = new PackagingSynchronisation($source); + $this->assertEqual($synchro->revision(), "1672"); + $this->assertEqual($synchro->sourceRevision(), "1671"); + $this->assertEqual($synchro->sourceLang(), "en"); + $this->assertEqual($synchro->lastSynchroRevision(), "1475"); + $this->assertEqual($synchro->result(), "late"); + } +} + +class TestOfContentTransformationFromXMLToHTML extends UnitTestCase { + function testOfNonLinksFileWithPHPExtension() { + $file = dirname(__FILE__).'/package/one_section_with_autorum_php.xml'; + $source = simplexml_load_file($file, "SimpleTestXMLElement"); + $content = $source->content(); + $this->assertPattern('/autorun\.php/', $content); + $this->assertNoPattern('/autorun\.html/', $content); + $this->assertPattern('/autodive\.php/', $content); + $this->assertNoPattern('/autodive\.html/', $content); + $this->assertNoPattern('/autowalk\.php/', $content); + $this->assertPattern('/autowalk\.html/', $content); + } + + function testOfPHPTags() { + $file = dirname(__FILE__).'/package/one_section_with_php_code.xml'; + $source = simplexml_load_file($file, "SimpleTestXMLElement"); + $content = $source->content(); + $this->assertPattern('/
    /', $content);
    +		$this->assertNoPattern('/<\!\[CDATA\[/', $content);
    +		$this->assertPattern('/

    /', $content); + $this->assertPattern('/\$log = &new Log\(\'my.log\'\);/', $content); + $this->assertPattern('/log->message/', $content); + } + + function testOfContentWithoutSections() { + $file = dirname(__FILE__).'/package/content_without_section.xml'; + $source = simplexml_load_file($file, "SimpleTestXMLElement"); + $content = $source->content(); + $this->assertPattern('/

    /', $content); + } + + function testOfContentFromChangeLogSection() { + $file = dirname(__FILE__).'/package/one_section_changelogged.xml'; + $source = simplexml_load_file($file, "SimpleTestXMLElement"); + $content = $source->content(); + $this->assertPattern('/

    Version 1.0.1<\/h3>/', $content); + $this->assertPattern('/
  • \[bug\] Patches and whitespace clean up<\/li>/', $content); + $this->assertPattern('/
  • Some in line documentation fixes<\/li>/', $content); + $this->assertPattern('/
  • \[bug 1853765<\/a>\] Fixing one of the incompatible interface errors<\/li>/', $content); + } + + function testOfContentFromMilestoneSection() { + $file = dirname(__FILE__).'/package/one_section_milestoned.xml'; + $source = simplexml_load_file($file, "SimpleTestXMLElement"); + $content = $source->content(); + $this->assertPattern('/

    1\.1beta<\/h3>/', $content); + $this->assertPattern('/<\/a>/', $content); + $this->assertPattern('/

    Unit tester<\/h4>/', $content); + $this->assertPattern('/

    Documentation<\/h4>/', $content); + $this->assertPattern('/

    Extensions<\/h4>/', $content); + $this->assertPattern('/

    Build<\/h4>/', $content); + $this->assertPattern('/
    \[bug\] Undefined property \$_reporter \+ fatal error<\/dt>/', $content); + $this->assertPattern('/
    tracker : 1896582<\/a><\/dd>/', $content); + $this->assertPattern('/
    \[task\] The HELP_MY_TESTS_DONT_WORK_ANYMORE needs to be updated\.<\/dt>/', $content); + $this->assertPattern('/
    \[task\] PHP 5.3 compatible under E_STRICT<\/dt>/', $content); + $this->assertPattern('/
    \[bug\] continuous integration<\/dt>/', $content); + $this->assertPattern('/
    \[bug\] error_reporting\(E_ALL|E_STRICT\)gives lots of warning<\/dt>/', $content); + $this->assertPattern('/
    We\'ve know this for years, this is the time\.<\/dd>/', $content); + } + + function testOfSingleLink() { + $file = dirname(__FILE__).'/package/here_download.xml'; + $source = simplexml_load_file($file, "SimpleTestXMLElement"); + $map = dirname(__FILE__).'/package/map.xml'; + $links = $source->links($map); + $this->assertEqual(count($links), 4); + $links_download = ''; + $this->assertEqual($links['download'], $links_download); + } + + function testOfMultipleLinks() { + $file = dirname(__FILE__).'/package/here_support.xml'; + $source = simplexml_load_file($file, "SimpleTestXMLElement"); + $map = dirname(__FILE__).'/package/map.xml'; + $links = $source->links($map); + $this->assertEqual(count($links), 4); + $links_support = ''; + $this->assertEqual($links['support'], $links_support); + } + + function testOfHierarchicalLinks() { + $file = dirname(__FILE__).'/package/here_overview.xml'; + $source = simplexml_load_file($file, "SimpleTestXMLElement"); + $map = dirname(__FILE__).'/package/map.xml'; + $links = $source->links($map); + $this->assertEqual(count($links), 4); + $links_start_testing = ''; + $this->assertEqual($links['start_testing'], $links_start_testing); + } + + function testOfRootLinksWithHierarchy() { + $file = dirname(__FILE__).'/package/here_simpletest.xml'; + $source = simplexml_load_file($file, "SimpleTestXMLElement"); + $map = dirname(__FILE__).'/package/map.xml'; + $links = $source->links($map); + $this->assertEqual(count($links), 4); + $links_start_testing = ''; + $this->assertEqual($links['start_testing'], $links_start_testing); + } + + function testOfLinksWithNonRootParent() { + $file = dirname(__FILE__).'/package/here_unit-tester.xml'; + $source = simplexml_load_file($file, "SimpleTestXMLElement"); + $map = dirname(__FILE__).'/package/map.xml'; + $links = $source->links($map); + $this->assertEqual(count($links), 4); + $links_start_testing = ''; + $this->assertEqual($links['start_testing'], $links_start_testing); + } +} + +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/site_map.xml b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/site_map.xml new file mode 100644 index 0000000..10e5663 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/site_map.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/transform_all_lastcraft.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/transform_all_lastcraft.php new file mode 100644 index 0000000..9b317df --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/packages/transform_all_lastcraft.php @@ -0,0 +1,17 @@ + $destination\n"; + `$command`; + } + closedir($dir); +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/page.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/page.php new file mode 100644 index 0000000..cfaa255 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/page.php @@ -0,0 +1,979 @@ + 'SimpleAnchorTag', + 'title' => 'SimpleTitleTag', + 'base' => 'SimpleBaseTag', + 'button' => 'SimpleButtonTag', + 'textarea' => 'SimpleTextAreaTag', + 'option' => 'SimpleOptionTag', + 'label' => 'SimpleLabelTag', + 'form' => 'SimpleFormTag', + 'frame' => 'SimpleFrameTag'); + $attributes = $this->keysToLowerCase($attributes); + if (array_key_exists($name, $map)) { + $tag_class = $map[$name]; + return new $tag_class($attributes); + } elseif ($name == 'select') { + return $this->createSelectionTag($attributes); + } elseif ($name == 'input') { + return $this->createInputTag($attributes); + } + return new SimpleTag($name, $attributes); + } + + /** + * Factory for selection fields. + * @param hash $attributes Element attributes. + * @return SimpleTag Tag object. + * @access protected + */ + protected function createSelectionTag($attributes) { + if (isset($attributes['multiple'])) { + return new MultipleSelectionTag($attributes); + } + return new SimpleSelectionTag($attributes); + } + + /** + * Factory for input tags. + * @param hash $attributes Element attributes. + * @return SimpleTag Tag object. + * @access protected + */ + protected function createInputTag($attributes) { + if (! isset($attributes['type'])) { + return new SimpleTextTag($attributes); + } + $type = strtolower(trim($attributes['type'])); + $map = array( + 'submit' => 'SimpleSubmitTag', + 'image' => 'SimpleImageSubmitTag', + 'checkbox' => 'SimpleCheckboxTag', + 'radio' => 'SimpleRadioButtonTag', + 'text' => 'SimpleTextTag', + 'hidden' => 'SimpleTextTag', + 'password' => 'SimpleTextTag', + 'file' => 'SimpleUploadTag'); + if (array_key_exists($type, $map)) { + $tag_class = $map[$type]; + return new $tag_class($attributes); + } + return false; + } + + /** + * Make the keys lower case for case insensitive look-ups. + * @param hash $map Hash to convert. + * @return hash Unchanged values, but keys lower case. + * @access private + */ + protected function keysToLowerCase($map) { + $lower = array(); + foreach ($map as $key => $value) { + $lower[strtolower($key)] = $value; + } + return $lower; + } +} + +/** + * SAX event handler. Maintains a list of + * open tags and dispatches them as they close. + * @package SimpleTest + * @subpackage WebTester + */ +class SimplePageBuilder extends SimpleSaxListener { + private $tags; + private $page; + private $private_content_tag; + + /** + * Sets the builder up empty. + * @access public + */ + function __construct() { + parent::__construct(); + } + + /** + * Frees up any references so as to allow the PHP garbage + * collection from unset() to work. + * @access public + */ + function free() { + unset($this->tags); + unset($this->page); + unset($this->private_content_tags); + } + + /** + * Reads the raw content and send events + * into the page to be built. + * @param $response SimpleHttpResponse Fetched response. + * @return SimplePage Newly parsed page. + * @access public + */ + function parse($response) { + $this->tags = array(); + $this->page = $this->createPage($response); + $parser = $this->createParser($this); + $parser->parse($response->getContent()); + $this->page->acceptPageEnd(); + return $this->page; + } + + /** + * Creates an empty page. + * @return SimplePage New unparsed page. + * @access protected + */ + protected function createPage($response) { + return new SimplePage($response); + } + + /** + * Creates the parser used with the builder. + * @param $listener SimpleSaxListener Target of parser. + * @return SimpleSaxParser Parser to generate + * events for the builder. + * @access protected + */ + protected function createParser(&$listener) { + return new SimpleHtmlSaxParser($listener); + } + + /** + * Start of element event. Opens a new tag. + * @param string $name Element name. + * @param hash $attributes Attributes without content + * are marked as true. + * @return boolean False on parse error. + * @access public + */ + function startElement($name, $attributes) { + $factory = new SimpleTagBuilder(); + $tag = $factory->createTag($name, $attributes); + if (! $tag) { + return true; + } + if ($tag->getTagName() == 'label') { + $this->page->acceptLabelStart($tag); + $this->openTag($tag); + return true; + } + if ($tag->getTagName() == 'form') { + $this->page->acceptFormStart($tag); + return true; + } + if ($tag->getTagName() == 'frameset') { + $this->page->acceptFramesetStart($tag); + return true; + } + if ($tag->getTagName() == 'frame') { + $this->page->acceptFrame($tag); + return true; + } + if ($tag->isPrivateContent() && ! isset($this->private_content_tag)) { + $this->private_content_tag = &$tag; + } + if ($tag->expectEndTag()) { + $this->openTag($tag); + return true; + } + $this->page->acceptTag($tag); + return true; + } + + /** + * End of element event. + * @param string $name Element name. + * @return boolean False on parse error. + * @access public + */ + function endElement($name) { + if ($name == 'label') { + $this->page->acceptLabelEnd(); + return true; + } + if ($name == 'form') { + $this->page->acceptFormEnd(); + return true; + } + if ($name == 'frameset') { + $this->page->acceptFramesetEnd(); + return true; + } + if ($this->hasNamedTagOnOpenTagStack($name)) { + $tag = array_pop($this->tags[$name]); + if ($tag->isPrivateContent() && $this->private_content_tag->getTagName() == $name) { + unset($this->private_content_tag); + } + $this->addContentTagToOpenTags($tag); + $this->page->acceptTag($tag); + return true; + } + return true; + } + + /** + * Test to see if there are any open tags awaiting + * closure that match the tag name. + * @param string $name Element name. + * @return boolean True if any are still open. + * @access private + */ + protected function hasNamedTagOnOpenTagStack($name) { + return isset($this->tags[$name]) && (count($this->tags[$name]) > 0); + } + + /** + * Unparsed, but relevant data. The data is added + * to every open tag. + * @param string $text May include unparsed tags. + * @return boolean False on parse error. + * @access public + */ + function addContent($text) { + if (isset($this->private_content_tag)) { + $this->private_content_tag->addContent($text); + } else { + $this->addContentToAllOpenTags($text); + } + return true; + } + + /** + * Any content fills all currently open tags unless it + * is part of an option tag. + * @param string $text May include unparsed tags. + * @access private + */ + protected function addContentToAllOpenTags($text) { + foreach (array_keys($this->tags) as $name) { + for ($i = 0, $count = count($this->tags[$name]); $i < $count; $i++) { + $this->tags[$name][$i]->addContent($text); + } + } + } + + /** + * Parsed data in tag form. The parsed tag is added + * to every open tag. Used for adding options to select + * fields only. + * @param SimpleTag $tag Option tags only. + * @access private + */ + protected function addContentTagToOpenTags(&$tag) { + if ($tag->getTagName() != 'option') { + return; + } + foreach (array_keys($this->tags) as $name) { + for ($i = 0, $count = count($this->tags[$name]); $i < $count; $i++) { + $this->tags[$name][$i]->addTag($tag); + } + } + } + + /** + * Opens a tag for receiving content. Multiple tags + * will be receiving input at the same time. + * @param SimpleTag $tag New content tag. + * @access private + */ + protected function openTag($tag) { + $name = $tag->getTagName(); + if (! in_array($name, array_keys($this->tags))) { + $this->tags[$name] = array(); + } + $this->tags[$name][] = $tag; + } +} + +/** + * A wrapper for a web page. + * @package SimpleTest + * @subpackage WebTester + */ +class SimplePage { + private $links; + private $title; + private $last_widget; + private $label; + private $left_over_labels; + private $open_forms; + private $complete_forms; + private $frameset; + private $frames; + private $frameset_nesting_level; + private $transport_error; + private $raw; + private $text; + private $sent; + private $headers; + private $method; + private $url; + private $base = false; + private $request_data; + + /** + * Parses a page ready to access it's contents. + * @param SimpleHttpResponse $response Result of HTTP fetch. + * @access public + */ + function __construct($response = false) { + $this->links = array(); + $this->title = false; + $this->left_over_labels = array(); + $this->open_forms = array(); + $this->complete_forms = array(); + $this->frameset = false; + $this->frames = array(); + $this->frameset_nesting_level = 0; + $this->text = false; + if ($response) { + $this->extractResponse($response); + } else { + $this->noResponse(); + } + } + + /** + * Extracts all of the response information. + * @param SimpleHttpResponse $response Response being parsed. + * @access private + */ + protected function extractResponse($response) { + $this->transport_error = $response->getError(); + $this->raw = $response->getContent(); + $this->sent = $response->getSent(); + $this->headers = $response->getHeaders(); + $this->method = $response->getMethod(); + $this->url = $response->getUrl(); + $this->request_data = $response->getRequestData(); + } + + /** + * Sets up a missing response. + * @access private + */ + protected function noResponse() { + $this->transport_error = 'No page fetched yet'; + $this->raw = false; + $this->sent = false; + $this->headers = false; + $this->method = 'GET'; + $this->url = false; + $this->request_data = false; + } + + /** + * Original request as bytes sent down the wire. + * @return mixed Sent content. + * @access public + */ + function getRequest() { + return $this->sent; + } + + /** + * Accessor for raw text of page. + * @return string Raw unparsed content. + * @access public + */ + function getRaw() { + return $this->raw; + } + + /** + * Accessor for plain text of page as a text browser + * would see it. + * @return string Plain text of page. + * @access public + */ + function getText() { + if (! $this->text) { + $this->text = SimpleHtmlSaxParser::normalise($this->raw); + } + return $this->text; + } + + /** + * Accessor for raw headers of page. + * @return string Header block as text. + * @access public + */ + function getHeaders() { + if ($this->headers) { + return $this->headers->getRaw(); + } + return false; + } + + /** + * Original request method. + * @return string GET, POST or HEAD. + * @access public + */ + function getMethod() { + return $this->method; + } + + /** + * Original resource name. + * @return SimpleUrl Current url. + * @access public + */ + function getUrl() { + return $this->url; + } + + /** + * Base URL if set via BASE tag page url otherwise + * @return SimpleUrl Base url. + * @access public + */ + function getBaseUrl() { + return $this->base; + } + + /** + * Original request data. + * @return mixed Sent content. + * @access public + */ + function getRequestData() { + return $this->request_data; + } + + /** + * Accessor for last error. + * @return string Error from last response. + * @access public + */ + function getTransportError() { + return $this->transport_error; + } + + /** + * Accessor for current MIME type. + * @return string MIME type as string; e.g. 'text/html' + * @access public + */ + function getMimeType() { + if ($this->headers) { + return $this->headers->getMimeType(); + } + return false; + } + + /** + * Accessor for HTTP response code. + * @return integer HTTP response code received. + * @access public + */ + function getResponseCode() { + if ($this->headers) { + return $this->headers->getResponseCode(); + } + return false; + } + + /** + * Accessor for last Authentication type. Only valid + * straight after a challenge (401). + * @return string Description of challenge type. + * @access public + */ + function getAuthentication() { + if ($this->headers) { + return $this->headers->getAuthentication(); + } + return false; + } + + /** + * Accessor for last Authentication realm. Only valid + * straight after a challenge (401). + * @return string Name of security realm. + * @access public + */ + function getRealm() { + if ($this->headers) { + return $this->headers->getRealm(); + } + return false; + } + + /** + * Accessor for current frame focus. Will be + * false as no frames. + * @return array Always empty. + * @access public + */ + function getFrameFocus() { + return array(); + } + + /** + * Sets the focus by index. The integer index starts from 1. + * @param integer $choice Chosen frame. + * @return boolean Always false. + * @access public + */ + function setFrameFocusByIndex($choice) { + return false; + } + + /** + * Sets the focus by name. Always fails for a leaf page. + * @param string $name Chosen frame. + * @return boolean False as no frames. + * @access public + */ + function setFrameFocus($name) { + return false; + } + + /** + * Clears the frame focus. Does nothing for a leaf page. + * @access public + */ + function clearFrameFocus() { + } + + /** + * Adds a tag to the page. + * @param SimpleTag $tag Tag to accept. + * @access public + */ + function acceptTag($tag) { + if ($tag->getTagName() == "a") { + $this->addLink($tag); + } elseif ($tag->getTagName() == "base") { + $this->setBase($tag); + } elseif ($tag->getTagName() == "title") { + $this->setTitle($tag); + } elseif ($this->isFormElement($tag->getTagName())) { + for ($i = 0; $i < count($this->open_forms); $i++) { + $this->open_forms[$i]->addWidget($tag); + } + $this->last_widget = &$tag; + } + } + + /** + * Opens a label for a described widget. + * @param SimpleFormTag $tag Tag to accept. + * @access public + */ + function acceptLabelStart($tag) { + $this->label = $tag; + unset($this->last_widget); + } + + /** + * Closes the most recently opened label. + * @access public + */ + function acceptLabelEnd() { + if (isset($this->label)) { + if (isset($this->last_widget)) { + $this->last_widget->setLabel($this->label->getText()); + unset($this->last_widget); + } else { + $this->left_over_labels[] = SimpleTestCompatibility::copy($this->label); + } + unset($this->label); + } + } + + /** + * Tests to see if a tag is a possible form + * element. + * @param string $name HTML element name. + * @return boolean True if form element. + * @access private + */ + protected function isFormElement($name) { + return in_array($name, array('input', 'button', 'textarea', 'select')); + } + + /** + * Opens a form. New widgets go here. + * @param SimpleFormTag $tag Tag to accept. + * @access public + */ + function acceptFormStart($tag) { + $this->open_forms[] = new SimpleForm($tag, $this); + } + + /** + * Closes the most recently opened form. + * @access public + */ + function acceptFormEnd() { + if (count($this->open_forms)) { + $this->complete_forms[] = array_pop($this->open_forms); + } + } + + /** + * Opens a frameset. A frameset may contain nested + * frameset tags. + * @param SimpleFramesetTag $tag Tag to accept. + * @access public + */ + function acceptFramesetStart($tag) { + if (! $this->isLoadingFrames()) { + $this->frameset = $tag; + } + $this->frameset_nesting_level++; + } + + /** + * Closes the most recently opened frameset. + * @access public + */ + function acceptFramesetEnd() { + if ($this->isLoadingFrames()) { + $this->frameset_nesting_level--; + } + } + + /** + * Takes a single frame tag and stashes it in + * the current frame set. + * @param SimpleFrameTag $tag Tag to accept. + * @access public + */ + function acceptFrame($tag) { + if ($this->isLoadingFrames()) { + if ($tag->getAttribute('src')) { + $this->frames[] = $tag; + } + } + } + + /** + * Test to see if in the middle of reading + * a frameset. + * @return boolean True if inframeset. + * @access private + */ + protected function isLoadingFrames() { + if (! $this->frameset) { + return false; + } + return ($this->frameset_nesting_level > 0); + } + + /** + * Test to see if link is an absolute one. + * @param string $url Url to test. + * @return boolean True if absolute. + * @access protected + */ + protected function linkIsAbsolute($url) { + $parsed = new SimpleUrl($url); + return (boolean)($parsed->getScheme() && $parsed->getHost()); + } + + /** + * Adds a link to the page. + * @param SimpleAnchorTag $tag Link to accept. + * @access protected + */ + protected function addLink($tag) { + $this->links[] = $tag; + } + + /** + * Marker for end of complete page. Any work in + * progress can now be closed. + * @access public + */ + function acceptPageEnd() { + while (count($this->open_forms)) { + $this->complete_forms[] = array_pop($this->open_forms); + } + foreach ($this->left_over_labels as $label) { + for ($i = 0, $count = count($this->complete_forms); $i < $count; $i++) { + $this->complete_forms[$i]->attachLabelBySelector( + new SimpleById($label->getFor()), + $label->getText()); + } + } + } + + /** + * Test for the presence of a frameset. + * @return boolean True if frameset. + * @access public + */ + function hasFrames() { + return (boolean)$this->frameset; + } + + /** + * Accessor for frame name and source URL for every frame that + * will need to be loaded. Immediate children only. + * @return boolean/array False if no frameset or + * otherwise a hash of frame URLs. + * The key is either a numerical + * base one index or the name attribute. + * @access public + */ + function getFrameset() { + if (! $this->frameset) { + return false; + } + $urls = array(); + for ($i = 0; $i < count($this->frames); $i++) { + $name = $this->frames[$i]->getAttribute('name'); + $url = new SimpleUrl($this->frames[$i]->getAttribute('src')); + $urls[$name ? $name : $i + 1] = $this->expandUrl($url); + } + return $urls; + } + + /** + * Fetches a list of loaded frames. + * @return array/string Just the URL for a single page. + * @access public + */ + function getFrames() { + $url = $this->expandUrl($this->getUrl()); + return $url->asString(); + } + + /** + * Accessor for a list of all links. + * @return array List of urls with scheme of + * http or https and hostname. + * @access public + */ + function getUrls() { + $all = array(); + foreach ($this->links as $link) { + $url = $this->getUrlFromLink($link); + $all[] = $url->asString(); + } + return $all; + } + + /** + * Accessor for URLs by the link label. Label will match + * regardess of whitespace issues and case. + * @param string $label Text of link. + * @return array List of links with that label. + * @access public + */ + function getUrlsByLabel($label) { + $matches = array(); + foreach ($this->links as $link) { + if ($link->getText() == $label) { + $matches[] = $this->getUrlFromLink($link); + } + } + return $matches; + } + + /** + * Accessor for a URL by the id attribute. + * @param string $id Id attribute of link. + * @return SimpleUrl URL with that id of false if none. + * @access public + */ + function getUrlById($id) { + foreach ($this->links as $link) { + if ($link->getAttribute('id') === (string)$id) { + return $this->getUrlFromLink($link); + } + } + return false; + } + + /** + * Converts a link tag into a target URL. + * @param SimpleAnchor $link Parsed link. + * @return SimpleUrl URL with frame target if any. + * @access private + */ + protected function getUrlFromLink($link) { + $url = $this->expandUrl($link->getHref()); + if ($link->getAttribute('target')) { + $url->setTarget($link->getAttribute('target')); + } + return $url; + } + + /** + * Expands expandomatic URLs into fully qualified + * URLs. + * @param SimpleUrl $url Relative URL. + * @return SimpleUrl Absolute URL. + * @access public + */ + function expandUrl($url) { + if (! is_object($url)) { + $url = new SimpleUrl($url); + } + $location = $this->getBaseUrl() ? $this->getBaseUrl() : new SimpleUrl(); + return $url->makeAbsolute($location->makeAbsolute($this->getUrl())); + } + + /** + * Sets the base url for the page. + * @param SimpleTag $tag Base URL for page. + * @access protected + */ + protected function setBase($tag) { + $url = $tag->getAttribute('href'); + $this->base = new SimpleUrl($url); + } + + /** + * Sets the title tag contents. + * @param SimpleTitleTag $tag Title of page. + * @access protected + */ + protected function setTitle($tag) { + $this->title = $tag; + } + + /** + * Accessor for parsed title. + * @return string Title or false if no title is present. + * @access public + */ + function getTitle() { + if ($this->title) { + return $this->title->getText(); + } + return false; + } + + /** + * Finds a held form by button label. Will only + * search correctly built forms. + * @param SimpleSelector $selector Button finder. + * @return SimpleForm Form object containing + * the button. + * @access public + */ + function &getFormBySubmit($selector) { + for ($i = 0; $i < count($this->complete_forms); $i++) { + if ($this->complete_forms[$i]->hasSubmit($selector)) { + return $this->complete_forms[$i]; + } + } + $null = null; + return $null; + } + + /** + * Finds a held form by image using a selector. + * Will only search correctly built forms. + * @param SimpleSelector $selector Image finder. + * @return SimpleForm Form object containing + * the image. + * @access public + */ + function getFormByImage($selector) { + for ($i = 0; $i < count($this->complete_forms); $i++) { + if ($this->complete_forms[$i]->hasImage($selector)) { + return $this->complete_forms[$i]; + } + } + return null; + } + + /** + * Finds a held form by the form ID. A way of + * identifying a specific form when we have control + * of the HTML code. + * @param string $id Form label. + * @return SimpleForm Form object containing the matching ID. + * @access public + */ + function getFormById($id) { + for ($i = 0; $i < count($this->complete_forms); $i++) { + if ($this->complete_forms[$i]->getId() == $id) { + return $this->complete_forms[$i]; + } + } + return null; + } + + /** + * Sets a field on each form in which the field is + * available. + * @param SimpleSelector $selector Field finder. + * @param string $value Value to set field to. + * @return boolean True if value is valid. + * @access public + */ + function setField($selector, $value, $position=false) { + $is_set = false; + for ($i = 0; $i < count($this->complete_forms); $i++) { + if ($this->complete_forms[$i]->setField($selector, $value, $position)) { + $is_set = true; + } + } + return $is_set; + } + + /** + * Accessor for a form element value within a page. + * @param SimpleSelector $selector Field finder. + * @return string/boolean A string if the field is + * present, false if unchecked + * and null if missing. + * @access public + */ + function getField($selector) { + for ($i = 0; $i < count($this->complete_forms); $i++) { + $value = $this->complete_forms[$i]->getValue($selector); + if (isset($value)) { + return $value; + } + } + return null; + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/parser.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/parser.php new file mode 100644 index 0000000..e95df81 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/parser.php @@ -0,0 +1,760 @@ + $constant) { + if (! defined($constant)) { + define($constant, $i + 1); + } +} +/**#@-*/ + +/** + * Compounded regular expression. Any of + * the contained patterns could match and + * when one does, it's label is returned. + * @package SimpleTest + * @subpackage WebTester + */ +class ParallelRegex { + private $patterns; + private $labels; + private $regex; + private $case; + + /** + * Constructor. Starts with no patterns. + * @param boolean $case True for case sensitive, false + * for insensitive. + * @access public + */ + function __construct($case) { + $this->case = $case; + $this->patterns = array(); + $this->labels = array(); + $this->regex = null; + } + + /** + * Adds a pattern with an optional label. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $label Label of regex to be returned + * on a match. + * @access public + */ + function addPattern($pattern, $label = true) { + $count = count($this->patterns); + $this->patterns[$count] = $pattern; + $this->labels[$count] = $label; + $this->regex = null; + } + + /** + * Attempts to match all patterns at once against + * a string. + * @param string $subject String to match against. + * @param string $match First matched portion of + * subject. + * @return boolean True on success. + * @access public + */ + function match($subject, &$match) { + if (count($this->patterns) == 0) { + return false; + } + if (! preg_match($this->getCompoundedRegex(), $subject, $matches)) { + $match = ''; + return false; + } + $match = $matches[0]; + for ($i = 1; $i < count($matches); $i++) { + if ($matches[$i]) { + return $this->labels[$i - 1]; + } + } + return true; + } + + /** + * Compounds the patterns into a single + * regular expression separated with the + * "or" operator. Caches the regex. + * Will automatically escape (, ) and / tokens. + * @param array $patterns List of patterns in order. + * @access private + */ + protected function getCompoundedRegex() { + if ($this->regex == null) { + for ($i = 0, $count = count($this->patterns); $i < $count; $i++) { + $this->patterns[$i] = '(' . str_replace( + array('/', '(', ')'), + array('\/', '\(', '\)'), + $this->patterns[$i]) . ')'; + } + $this->regex = "/" . implode("|", $this->patterns) . "/" . $this->getPerlMatchingFlags(); + } + return $this->regex; + } + + /** + * Accessor for perl regex mode flags to use. + * @return string Perl regex flags. + * @access private + */ + protected function getPerlMatchingFlags() { + return ($this->case ? "msS" : "msSi"); + } +} + +/** + * States for a stack machine. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleStateStack { + private $stack; + + /** + * Constructor. Starts in named state. + * @param string $start Starting state name. + * @access public + */ + function __construct($start) { + $this->stack = array($start); + } + + /** + * Accessor for current state. + * @return string State. + * @access public + */ + function getCurrent() { + return $this->stack[count($this->stack) - 1]; + } + + /** + * Adds a state to the stack and sets it + * to be the current state. + * @param string $state New state. + * @access public + */ + function enter($state) { + array_push($this->stack, $state); + } + + /** + * Leaves the current state and reverts + * to the previous one. + * @return boolean False if we drop off + * the bottom of the list. + * @access public + */ + function leave() { + if (count($this->stack) == 1) { + return false; + } + array_pop($this->stack); + return true; + } +} + +/** + * Accepts text and breaks it into tokens. + * Some optimisation to make the sure the + * content is only scanned by the PHP regex + * parser once. Lexer modes must not start + * with leading underscores. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleLexer { + private $regexes; + private $parser; + private $mode; + private $mode_handlers; + private $case; + + /** + * Sets up the lexer in case insensitive matching + * by default. + * @param SimpleSaxParser $parser Handling strategy by + * reference. + * @param string $start Starting handler. + * @param boolean $case True for case sensitive. + * @access public + */ + function __construct($parser, $start = "accept", $case = false) { + $this->case = $case; + $this->regexes = array(); + $this->parser = $parser; + $this->mode = new SimpleStateStack($start); + $this->mode_handlers = array($start => $start); + } + + /** + * Adds a token search pattern for a particular + * parsing mode. The pattern does not change the + * current mode. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $mode Should only apply this + * pattern when dealing with + * this type of input. + * @access public + */ + function addPattern($pattern, $mode = "accept") { + if (! isset($this->regexes[$mode])) { + $this->regexes[$mode] = new ParallelRegex($this->case); + } + $this->regexes[$mode]->addPattern($pattern); + if (! isset($this->mode_handlers[$mode])) { + $this->mode_handlers[$mode] = $mode; + } + } + + /** + * Adds a pattern that will enter a new parsing + * mode. Useful for entering parenthesis, strings, + * tags, etc. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $mode Should only apply this + * pattern when dealing with + * this type of input. + * @param string $new_mode Change parsing to this new + * nested mode. + * @access public + */ + function addEntryPattern($pattern, $mode, $new_mode) { + if (! isset($this->regexes[$mode])) { + $this->regexes[$mode] = new ParallelRegex($this->case); + } + $this->regexes[$mode]->addPattern($pattern, $new_mode); + if (! isset($this->mode_handlers[$new_mode])) { + $this->mode_handlers[$new_mode] = $new_mode; + } + } + + /** + * Adds a pattern that will exit the current mode + * and re-enter the previous one. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $mode Mode to leave. + * @access public + */ + function addExitPattern($pattern, $mode) { + if (! isset($this->regexes[$mode])) { + $this->regexes[$mode] = new ParallelRegex($this->case); + } + $this->regexes[$mode]->addPattern($pattern, "__exit"); + if (! isset($this->mode_handlers[$mode])) { + $this->mode_handlers[$mode] = $mode; + } + } + + /** + * Adds a pattern that has a special mode. Acts as an entry + * and exit pattern in one go, effectively calling a special + * parser handler for this token only. + * @param string $pattern Perl style regex, but ( and ) + * lose the usual meaning. + * @param string $mode Should only apply this + * pattern when dealing with + * this type of input. + * @param string $special Use this mode for this one token. + * @access public + */ + function addSpecialPattern($pattern, $mode, $special) { + if (! isset($this->regexes[$mode])) { + $this->regexes[$mode] = new ParallelRegex($this->case); + } + $this->regexes[$mode]->addPattern($pattern, "_$special"); + if (! isset($this->mode_handlers[$special])) { + $this->mode_handlers[$special] = $special; + } + } + + /** + * Adds a mapping from a mode to another handler. + * @param string $mode Mode to be remapped. + * @param string $handler New target handler. + * @access public + */ + function mapHandler($mode, $handler) { + $this->mode_handlers[$mode] = $handler; + } + + /** + * Splits the page text into tokens. Will fail + * if the handlers report an error or if no + * content is consumed. If successful then each + * unparsed and parsed token invokes a call to the + * held listener. + * @param string $raw Raw HTML text. + * @return boolean True on success, else false. + * @access public + */ + function parse($raw) { + if (! isset($this->parser)) { + return false; + } + $length = strlen($raw); + while (is_array($parsed = $this->reduce($raw))) { + list($raw, $unmatched, $matched, $mode) = $parsed; + if (! $this->dispatchTokens($unmatched, $matched, $mode)) { + return false; + } + if ($raw === '') { + return true; + } + if (strlen($raw) == $length) { + return false; + } + $length = strlen($raw); + } + if (! $parsed) { + return false; + } + return $this->invokeParser($raw, LEXER_UNMATCHED); + } + + /** + * Sends the matched token and any leading unmatched + * text to the parser changing the lexer to a new + * mode if one is listed. + * @param string $unmatched Unmatched leading portion. + * @param string $matched Actual token match. + * @param string $mode Mode after match. A boolean + * false mode causes no change. + * @return boolean False if there was any error + * from the parser. + * @access private + */ + protected function dispatchTokens($unmatched, $matched, $mode = false) { + if (! $this->invokeParser($unmatched, LEXER_UNMATCHED)) { + return false; + } + if (is_bool($mode)) { + return $this->invokeParser($matched, LEXER_MATCHED); + } + if ($this->isModeEnd($mode)) { + if (! $this->invokeParser($matched, LEXER_EXIT)) { + return false; + } + return $this->mode->leave(); + } + if ($this->isSpecialMode($mode)) { + $this->mode->enter($this->decodeSpecial($mode)); + if (! $this->invokeParser($matched, LEXER_SPECIAL)) { + return false; + } + return $this->mode->leave(); + } + $this->mode->enter($mode); + return $this->invokeParser($matched, LEXER_ENTER); + } + + /** + * Tests to see if the new mode is actually to leave + * the current mode and pop an item from the matching + * mode stack. + * @param string $mode Mode to test. + * @return boolean True if this is the exit mode. + * @access private + */ + protected function isModeEnd($mode) { + return ($mode === "__exit"); + } + + /** + * Test to see if the mode is one where this mode + * is entered for this token only and automatically + * leaves immediately afterwoods. + * @param string $mode Mode to test. + * @return boolean True if this is the exit mode. + * @access private + */ + protected function isSpecialMode($mode) { + return (strncmp($mode, "_", 1) == 0); + } + + /** + * Strips the magic underscore marking single token + * modes. + * @param string $mode Mode to decode. + * @return string Underlying mode name. + * @access private + */ + protected function decodeSpecial($mode) { + return substr($mode, 1); + } + + /** + * Calls the parser method named after the current + * mode. Empty content will be ignored. The lexer + * has a parser handler for each mode in the lexer. + * @param string $content Text parsed. + * @param boolean $is_match Token is recognised rather + * than unparsed data. + * @access private + */ + protected function invokeParser($content, $is_match) { + if (($content === '') || ($content === false)) { + return true; + } + $handler = $this->mode_handlers[$this->mode->getCurrent()]; + return $this->parser->$handler($content, $is_match); + } + + /** + * Tries to match a chunk of text and if successful + * removes the recognised chunk and any leading + * unparsed data. Empty strings will not be matched. + * @param string $raw The subject to parse. This is the + * content that will be eaten. + * @return array/boolean Three item list of unparsed + * content followed by the + * recognised token and finally the + * action the parser is to take. + * True if no match, false if there + * is a parsing error. + * @access private + */ + protected function reduce($raw) { + if ($action = $this->regexes[$this->mode->getCurrent()]->match($raw, $match)) { + $unparsed_character_count = strpos($raw, $match); + $unparsed = substr($raw, 0, $unparsed_character_count); + $raw = substr($raw, $unparsed_character_count + strlen($match)); + return array($raw, $unparsed, $match, $action); + } + return true; + } +} + +/** + * Breaks HTML into SAX events. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHtmlLexer extends SimpleLexer { + + /** + * Sets up the lexer with case insensitive matching + * and adds the HTML handlers. + * @param SimpleSaxParser $parser Handling strategy by + * reference. + * @access public + */ + function __construct($parser) { + parent::__construct($parser, 'text'); + $this->mapHandler('text', 'acceptTextToken'); + $this->addSkipping(); + foreach ($this->getParsedTags() as $tag) { + $this->addTag($tag); + } + $this->addInTagTokens(); + } + + /** + * List of parsed tags. Others are ignored. + * @return array List of searched for tags. + * @access private + */ + protected function getParsedTags() { + return array('a', 'base', 'title', 'form', 'input', 'button', 'textarea', 'select', + 'option', 'frameset', 'frame', 'label'); + } + + /** + * The lexer has to skip certain sections such + * as server code, client code and styles. + * @access private + */ + protected function addSkipping() { + $this->mapHandler('css', 'ignore'); + $this->addEntryPattern('addExitPattern('', 'css'); + $this->mapHandler('js', 'ignore'); + $this->addEntryPattern('addExitPattern('', 'js'); + $this->mapHandler('comment', 'ignore'); + $this->addEntryPattern('', 'comment'); + } + + /** + * Pattern matches to start and end a tag. + * @param string $tag Name of tag to scan for. + * @access private + */ + protected function addTag($tag) { + $this->addSpecialPattern("", 'text', 'acceptEndToken'); + $this->addEntryPattern("<$tag", 'text', 'tag'); + } + + /** + * Pattern matches to parse the inside of a tag + * including the attributes and their quoting. + * @access private + */ + protected function addInTagTokens() { + $this->mapHandler('tag', 'acceptStartToken'); + $this->addSpecialPattern('\s+', 'tag', 'ignore'); + $this->addAttributeTokens(); + $this->addExitPattern('/>', 'tag'); + $this->addExitPattern('>', 'tag'); + } + + /** + * Matches attributes that are either single quoted, + * double quoted or unquoted. + * @access private + */ + protected function addAttributeTokens() { + $this->mapHandler('dq_attribute', 'acceptAttributeToken'); + $this->addEntryPattern('=\s*"', 'tag', 'dq_attribute'); + $this->addPattern("\\\\\"", 'dq_attribute'); + $this->addExitPattern('"', 'dq_attribute'); + $this->mapHandler('sq_attribute', 'acceptAttributeToken'); + $this->addEntryPattern("=\s*'", 'tag', 'sq_attribute'); + $this->addPattern("\\\\'", 'sq_attribute'); + $this->addExitPattern("'", 'sq_attribute'); + $this->mapHandler('uq_attribute', 'acceptAttributeToken'); + $this->addSpecialPattern('=\s*[^>\s]*', 'tag', 'uq_attribute'); + } +} + +/** + * Converts HTML tokens into selected SAX events. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleHtmlSaxParser { + private $lexer; + private $listener; + private $tag; + private $attributes; + private $current_attribute; + + /** + * Sets the listener. + * @param SimpleSaxListener $listener SAX event handler. + * @access public + */ + function __construct($listener) { + $this->listener = $listener; + $this->lexer = $this->createLexer($this); + $this->tag = ''; + $this->attributes = array(); + $this->current_attribute = ''; + } + + /** + * Runs the content through the lexer which + * should call back to the acceptors. + * @param string $raw Page text to parse. + * @return boolean False if parse error. + * @access public + */ + function parse($raw) { + return $this->lexer->parse($raw); + } + + /** + * Sets up the matching lexer. Starts in 'text' mode. + * @param SimpleSaxParser $parser Event generator, usually $self. + * @return SimpleLexer Lexer suitable for this parser. + * @access public + */ + static function createLexer(&$parser) { + return new SimpleHtmlLexer($parser); + } + + /** + * Accepts a token from the tag mode. If the + * starting element completes then the element + * is dispatched and the current attributes + * set back to empty. The element or attribute + * name is converted to lower case. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptStartToken($token, $event) { + if ($event == LEXER_ENTER) { + $this->tag = strtolower(substr($token, 1)); + return true; + } + if ($event == LEXER_EXIT) { + $success = $this->listener->startElement( + $this->tag, + $this->attributes); + $this->tag = ''; + $this->attributes = array(); + return $success; + } + if ($token != '=') { + $this->current_attribute = strtolower(SimpleHtmlSaxParser::decodeHtml($token)); + $this->attributes[$this->current_attribute] = ''; + } + return true; + } + + /** + * Accepts a token from the end tag mode. + * The element name is converted to lower case. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptEndToken($token, $event) { + if (! preg_match('/<\/(.*)>/', $token, $matches)) { + return false; + } + return $this->listener->endElement(strtolower($matches[1])); + } + + /** + * Part of the tag data. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptAttributeToken($token, $event) { + if ($this->current_attribute) { + if ($event == LEXER_UNMATCHED) { + $this->attributes[$this->current_attribute] .= + SimpleHtmlSaxParser::decodeHtml($token); + } + if ($event == LEXER_SPECIAL) { + $this->attributes[$this->current_attribute] .= + preg_replace('/^=\s*/' , '', SimpleHtmlSaxParser::decodeHtml($token)); + } + } + return true; + } + + /** + * A character entity. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptEntityToken($token, $event) { + } + + /** + * Character data between tags regarded as + * important. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function acceptTextToken($token, $event) { + return $this->listener->addContent($token); + } + + /** + * Incoming data to be ignored. + * @param string $token Incoming characters. + * @param integer $event Lexer event type. + * @return boolean False if parse error. + * @access public + */ + function ignore($token, $event) { + return true; + } + + /** + * Decodes any HTML entities. + * @param string $html Incoming HTML. + * @return string Outgoing plain text. + * @access public + */ + static function decodeHtml($html) { + return html_entity_decode($html, ENT_QUOTES); + } + + /** + * Turns HTML into text browser visible text. Images + * are converted to their alt text and tags are supressed. + * Entities are converted to their visible representation. + * @param string $html HTML to convert. + * @return string Plain text. + * @access public + */ + static function normalise($html) { + $text = preg_replace('||', '', $html); + $text = preg_replace('|]*>.*?|', '', $text); + $text = preg_replace('|]*alt\s*=\s*"([^"]*)"[^>]*>|', ' \1 ', $text); + $text = preg_replace('|]*alt\s*=\s*\'([^\']*)\'[^>]*>|', ' \1 ', $text); + $text = preg_replace('|]*alt\s*=\s*([a-zA-Z_]+)[^>]*>|', ' \1 ', $text); + $text = preg_replace('|<[^>]*>|', '', $text); + $text = SimpleHtmlSaxParser::decodeHtml($text); + $text = preg_replace('|\s+|', ' ', $text); + return trim(trim($text), "\xA0"); // TODO: The \xAO is a  . Add a test for this. + } +} + +/** + * SAX event handler. + * @package SimpleTest + * @subpackage WebTester + * @abstract + */ +class SimpleSaxListener { + + /** + * Sets the document to write to. + * @access public + */ + function __construct() { + } + + /** + * Start of element event. + * @param string $name Element name. + * @param hash $attributes Name value pairs. + * Attributes without content + * are marked as true. + * @return boolean False on parse error. + * @access public + */ + function startElement($name, $attributes) { + } + + /** + * End of element event. + * @param string $name Element name. + * @return boolean False on parse error. + * @access public + */ + function endElement($name) { + } + + /** + * Unparsed, but relevant data. + * @param string $text May include unparsed tags. + * @return boolean False on parse error. + * @access public + */ + function addContent($text) { + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/reflection_php4.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/reflection_php4.php new file mode 100644 index 0000000..6c93915 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/reflection_php4.php @@ -0,0 +1,136 @@ +_interface = $interface; + } + + /** + * Checks that a class has been declared. + * @return boolean True if defined. + * @access public + */ + function classExists() { + return class_exists($this->_interface); + } + + /** + * Needed to kill the autoload feature in PHP5 + * for classes created dynamically. + * @return boolean True if defined. + * @access public + */ + function classExistsSansAutoload() { + return class_exists($this->_interface); + } + + /** + * Checks that a class or interface has been + * declared. + * @return boolean True if defined. + * @access public + */ + function classOrInterfaceExists() { + return class_exists($this->_interface); + } + + /** + * Needed to kill the autoload feature in PHP5 + * for classes created dynamically. + * @return boolean True if defined. + * @access public + */ + function classOrInterfaceExistsSansAutoload() { + return class_exists($this->_interface); + } + + /** + * Gets the list of methods on a class or + * interface. + * @returns array List of method names. + * @access public + */ + function getMethods() { + return get_class_methods($this->_interface); + } + + /** + * Gets the list of interfaces from a class. If the + * class name is actually an interface then just that + * interface is returned. + * @returns array List of interfaces. + * @access public + */ + function getInterfaces() { + return array(); + } + + /** + * Finds the parent class name. + * @returns string Parent class name. + * @access public + */ + function getParent() { + return strtolower(get_parent_class($this->_interface)); + } + + /** + * Determines if the class is abstract, which for PHP 4 + * will never be the case. + * @returns boolean True if abstract. + * @access public + */ + function isAbstract() { + return false; + } + + /** + * Determines if the the entity is an interface, which for PHP 4 + * will never be the case. + * @returns boolean True if interface. + * @access public + */ + function isInterface() { + return false; + } + + /** + * Scans for final methods, but as it's PHP 4 there + * aren't any. + * @returns boolean True if the class has a final method. + * @access public + */ + function hasFinal() { + return false; + } + + /** + * Gets the source code matching the declaration + * of a method. + * @param string $method Method name. + * @access public + */ + function getSignature($method) { + return "function &$method()"; + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/reflection_php5.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/reflection_php5.php new file mode 100644 index 0000000..02cceb5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/reflection_php5.php @@ -0,0 +1,386 @@ +interface = $interface; + } + + /** + * Checks that a class has been declared. Versions + * before PHP5.0.2 need a check that it's not really + * an interface. + * @return boolean True if defined. + * @access public + */ + function classExists() { + if (! class_exists($this->interface)) { + return false; + } + $reflection = new ReflectionClass($this->interface); + return ! $reflection->isInterface(); + } + + /** + * Needed to kill the autoload feature in PHP5 + * for classes created dynamically. + * @return boolean True if defined. + * @access public + */ + function classExistsSansAutoload() { + return class_exists($this->interface, false); + } + + /** + * Checks that a class or interface has been + * declared. + * @return boolean True if defined. + * @access public + */ + function classOrInterfaceExists() { + return $this->classOrInterfaceExistsWithAutoload($this->interface, true); + } + + /** + * Needed to kill the autoload feature in PHP5 + * for classes created dynamically. + * @return boolean True if defined. + * @access public + */ + function classOrInterfaceExistsSansAutoload() { + return $this->classOrInterfaceExistsWithAutoload($this->interface, false); + } + + /** + * Needed to select the autoload feature in PHP5 + * for classes created dynamically. + * @param string $interface Class or interface name. + * @param boolean $autoload True totriggerautoload. + * @return boolean True if interface defined. + * @access private + */ + protected function classOrInterfaceExistsWithAutoload($interface, $autoload) { + if (function_exists('interface_exists')) { + if (interface_exists($this->interface, $autoload)) { + return true; + } + } + return class_exists($this->interface, $autoload); + } + + /** + * Gets the list of methods on a class or + * interface. + * @returns array List of method names. + * @access public + */ + function getMethods() { + return array_unique(get_class_methods($this->interface)); + } + + /** + * Gets the list of interfaces from a class. If the + * class name is actually an interface then just that + * interface is returned. + * @returns array List of interfaces. + * @access public + */ + function getInterfaces() { + $reflection = new ReflectionClass($this->interface); + if ($reflection->isInterface()) { + return array($this->interface); + } + return $this->onlyParents($reflection->getInterfaces()); + } + + /** + * Gets the list of methods for the implemented + * interfaces only. + * @returns array List of enforced method signatures. + * @access public + */ + function getInterfaceMethods() { + $methods = array(); + foreach ($this->getInterfaces() as $interface) { + $methods = array_merge($methods, get_class_methods($interface)); + } + return array_unique($methods); + } + + /** + * Checks to see if the method signature has to be tightly + * specified. + * @param string $method Method name. + * @returns boolean True if enforced. + * @access private + */ + protected function isInterfaceMethod($method) { + return in_array($method, $this->getInterfaceMethods()); + } + + /** + * Finds the parent class name. + * @returns string Parent class name. + * @access public + */ + function getParent() { + $reflection = new ReflectionClass($this->interface); + $parent = $reflection->getParentClass(); + if ($parent) { + return $parent->getName(); + } + return false; + } + + /** + * Trivially determines if the class is abstract. + * @returns boolean True if abstract. + * @access public + */ + function isAbstract() { + $reflection = new ReflectionClass($this->interface); + return $reflection->isAbstract(); + } + + /** + * Trivially determines if the class is an interface. + * @returns boolean True if interface. + * @access public + */ + function isInterface() { + $reflection = new ReflectionClass($this->interface); + return $reflection->isInterface(); + } + + /** + * Scans for final methods, as they screw up inherited + * mocks by not allowing you to override them. + * @returns boolean True if the class has a final method. + * @access public + */ + function hasFinal() { + $reflection = new ReflectionClass($this->interface); + foreach ($reflection->getMethods() as $method) { + if ($method->isFinal()) { + return true; + } + } + return false; + } + + /** + * Whittles a list of interfaces down to only the + * necessary top level parents. + * @param array $interfaces Reflection API interfaces + * to reduce. + * @returns array List of parent interface names. + * @access private + */ + protected function onlyParents($interfaces) { + $parents = array(); + $blacklist = array(); + foreach ($interfaces as $interface) { + foreach($interfaces as $possible_parent) { + if ($interface->getName() == $possible_parent->getName()) { + continue; + } + if ($interface->isSubClassOf($possible_parent)) { + $blacklist[$possible_parent->getName()] = true; + } + } + if (!isset($blacklist[$interface->getName()])) { + $parents[] = $interface->getName(); + } + } + return $parents; + } + + /** + * Checks whether a method is abstract or not. + * @param string $name Method name. + * @return bool true if method is abstract, else false + * @access private + */ + protected function isAbstractMethod($name) { + $interface = new ReflectionClass($this->interface); + if (! $interface->hasMethod($name)) { + return false; + } + return $interface->getMethod($name)->isAbstract(); + } + + /** + * Checks whether a method is the constructor. + * @param string $name Method name. + * @return bool true if method is the constructor + * @access private + */ + protected function isConstructor($name) { + return ($name == '__construct') || ($name == $this->interface); + } + + /** + * Checks whether a method is abstract in all parents or not. + * @param string $name Method name. + * @return bool true if method is abstract in parent, else false + * @access private + */ + protected function isAbstractMethodInParents($name) { + $interface = new ReflectionClass($this->interface); + $parent = $interface->getParentClass(); + while($parent) { + if (! $parent->hasMethod($name)) { + return false; + } + if ($parent->getMethod($name)->isAbstract()) { + return true; + } + $parent = $parent->getParentClass(); + } + return false; + } + + /** + * Checks whether a method is static or not. + * @param string $name Method name + * @return bool true if method is static, else false + * @access private + */ + protected function isStaticMethod($name) { + $interface = new ReflectionClass($this->interface); + if (! $interface->hasMethod($name)) { + return false; + } + return $interface->getMethod($name)->isStatic(); + } + + /** + * Writes the source code matching the declaration + * of a method. + * @param string $name Method name. + * @return string Method signature up to last + * bracket. + * @access public + */ + function getSignature($name) { + if ($name == '__set') { + return 'function __set($key, $value)'; + } + if ($name == '__call') { + return 'function __call($method, $arguments)'; + } + if (version_compare(phpversion(), '5.1.0', '>=')) { + if (in_array($name, array('__get', '__isset', $name == '__unset'))) { + return "function {$name}(\$key)"; + } + } + if ($name == '__toString') { + return "function $name()"; + } + + // This wonky try-catch is a work around for a faulty method_exists() + // in early versions of PHP 5 which would return false for static + // methods. The Reflection classes work fine, but hasMethod() + // doesn't exist prior to PHP 5.1.0, so we need to use a more crude + // detection method. + try { + $interface = new ReflectionClass($this->interface); + $interface->getMethod($name); + } catch (ReflectionException $e) { + return "function $name()"; + } + return $this->getFullSignature($name); + } + + /** + * For a signature specified in an interface, full + * details must be replicated to be a valid implementation. + * @param string $name Method name. + * @return string Method signature up to last + * bracket. + * @access private + */ + protected function getFullSignature($name) { + $interface = new ReflectionClass($this->interface); + $method = $interface->getMethod($name); + $reference = $method->returnsReference() ? '&' : ''; + $static = $method->isStatic() ? 'static ' : ''; + return "{$static}function $reference$name(" . + implode(', ', $this->getParameterSignatures($method)) . + ")"; + } + + /** + * Gets the source code for each parameter. + * @param ReflectionMethod $method Method object from + * reflection API + * @return array List of strings, each + * a snippet of code. + * @access private + */ + protected function getParameterSignatures($method) { + $signatures = array(); + foreach ($method->getParameters() as $parameter) { + $signature = ''; + $type = $parameter->getClass(); + if (is_null($type) && version_compare(phpversion(), '5.1.0', '>=') && $parameter->isArray()) { + $signature .= 'array '; + } elseif (!is_null($type)) { + $signature .= $type->getName() . ' '; + } + if ($parameter->isPassedByReference()) { + $signature .= '&'; + } + $signature .= '$' . $this->suppressSpurious($parameter->getName()); + if ($this->isOptional($parameter)) { + $signature .= ' = null'; + } + $signatures[] = $signature; + } + return $signatures; + } + + /** + * The SPL library has problems with the + * Reflection library. In particular, you can + * get extra characters in parameter names :(. + * @param string $name Parameter name. + * @return string Cleaner name. + * @access private + */ + protected function suppressSpurious($name) { + return str_replace(array('[', ']', ' '), '', $name); + } + + /** + * Test of a reflection parameter being optional + * that works with early versions of PHP5. + * @param reflectionParameter $parameter Is this optional. + * @return boolean True if optional. + * @access private + */ + protected function isOptional($parameter) { + if (method_exists($parameter, 'isOptional')) { + return $parameter->isOptional(); + } + return false; + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/remote.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/remote.php new file mode 100644 index 0000000..43cc40f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/remote.php @@ -0,0 +1,115 @@ +url = $url; + $this->dry_url = $dry_url ? $dry_url : $url; + $this->size = false; + } + + /** + * Accessor for the test name for subclasses. + * @return string Name of the test. + * @access public + */ + function getLabel() { + return $this->url; + } + + /** + * Runs the top level test for this class. Currently + * reads the data as a single chunk. I'll fix this + * once I have added iteration to the browser. + * @param SimpleReporter $reporter Target of test results. + * @returns boolean True if no failures. + * @access public + */ + function run($reporter) { + $browser = $this->createBrowser(); + $xml = $browser->get($this->url); + if (! $xml) { + trigger_error('Cannot read remote test URL [' . $this->url . ']'); + return false; + } + $parser = $this->createParser($reporter); + if (! $parser->parse($xml)) { + trigger_error('Cannot parse incoming XML from [' . $this->url . ']'); + return false; + } + return true; + } + + /** + * Creates a new web browser object for fetching + * the XML report. + * @return SimpleBrowser New browser. + * @access protected + */ + protected function createBrowser() { + return new SimpleBrowser(); + } + + /** + * Creates the XML parser. + * @param SimpleReporter $reporter Target of test results. + * @return SimpleTestXmlListener XML reader. + * @access protected + */ + protected function createParser($reporter) { + return new SimpleTestXmlParser($reporter); + } + + /** + * Accessor for the number of subtests. + * @return integer Number of test cases. + * @access public + */ + function getSize() { + if ($this->size === false) { + $browser = $this->createBrowser(); + $xml = $browser->get($this->dry_url); + if (! $xml) { + trigger_error('Cannot read remote test URL [' . $this->dry_url . ']'); + return false; + } + $reporter = new SimpleReporter(); + $parser = $this->createParser($reporter); + if (! $parser->parse($xml)) { + trigger_error('Cannot parse incoming XML from [' . $this->dry_url . ']'); + return false; + } + $this->size = $reporter->getTestCaseCount(); + } + return $this->size; + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/reporter.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/reporter.php new file mode 100644 index 0000000..aaed016 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/reporter.php @@ -0,0 +1,446 @@ +character_set = $character_set; + } + + /** + * Paints the top of the web page setting the + * title to the name of the starting test. + * @param string $test_name Name class of test. + * @access public + */ + function paintHeader($test_name) { + $this->sendNoCacheHeaders(); + print ""; + print "\n\n$test_name\n"; + print "\n"; + print "\n"; + print "\n\n"; + print "

    $test_name

    \n"; + flush(); + } + + /** + * Send the headers necessary to ensure the page is + * reloaded on every request. Otherwise you could be + * scratching your head over out of date test data. + * @access public + */ + static function sendNoCacheHeaders() { + if (! headers_sent()) { + header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); + header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); + header("Cache-Control: no-store, no-cache, must-revalidate"); + header("Cache-Control: post-check=0, pre-check=0", false); + header("Pragma: no-cache"); + } + } + + /** + * Paints the CSS. Add additional styles here. + * @return string CSS code as text. + * @access protected + */ + protected function getCss() { + return ".fail { background-color: inherit; color: red; }" . + ".pass { background-color: inherit; color: green; }" . + " pre { background-color: lightgray; color: inherit; }"; + } + + /** + * Paints the end of the test with a summary of + * the passes and failures. + * @param string $test_name Name class of test. + * @access public + */ + function paintFooter($test_name) { + $colour = ($this->getFailCount() + $this->getExceptionCount() > 0 ? "red" : "green"); + print "
    "; + print $this->getTestCaseProgress() . "/" . $this->getTestCaseCount(); + print " test cases complete:\n"; + print "" . $this->getPassCount() . " passes, "; + print "" . $this->getFailCount() . " fails and "; + print "" . $this->getExceptionCount() . " exceptions."; + print "
    \n"; + print "\n\n"; + } + + /** + * Paints the test failure with a breadcrumbs + * trail of the nesting test suites below the + * top level test. + * @param string $message Failure message displayed in + * the context of the other tests. + * @access public + */ + function paintFail($message) { + parent::paintFail($message); + print "Fail: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode(" -> ", $breadcrumb); + print " -> " . $this->htmlEntities($message) . "
    \n"; + } + + /** + * Paints a PHP error. + * @param string $message Message is ignored. + * @access public + */ + function paintError($message) { + parent::paintError($message); + print "Exception: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode(" -> ", $breadcrumb); + print " -> " . $this->htmlEntities($message) . "
    \n"; + } + + /** + * Paints a PHP exception. + * @param Exception $exception Exception to display. + * @access public + */ + function paintException($exception) { + parent::paintException($exception); + print "Exception: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode(" -> ", $breadcrumb); + $message = 'Unexpected exception of type [' . get_class($exception) . + '] with message ['. $exception->getMessage() . + '] in ['. $exception->getFile() . + ' line ' . $exception->getLine() . ']'; + print " -> " . $this->htmlEntities($message) . "
    \n"; + } + + /** + * Prints the message for skipping tests. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { + parent::paintSkip($message); + print "Skipped: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode(" -> ", $breadcrumb); + print " -> " . $this->htmlEntities($message) . "
    \n"; + } + + /** + * Paints formatted text such as dumped privateiables. + * @param string $message Text to show. + * @access public + */ + function paintFormattedMessage($message) { + print '
    ' . $this->htmlEntities($message) . '
    '; + } + + /** + * Character set adjusted entity conversion. + * @param string $message Plain text or Unicode message. + * @return string Browser readable message. + * @access protected + */ + protected function htmlEntities($message) { + return htmlentities($message, ENT_COMPAT, $this->character_set); + } +} + +/** + * Sample minimal test displayer. Generates only + * failure messages and a pass count. For command + * line use. I've tried to make it look like JUnit, + * but I wanted to output the errors as they arrived + * which meant dropping the dots. + * @package SimpleTest + * @subpackage UnitTester + */ +class TextReporter extends SimpleReporter { + + /** + * Does nothing yet. The first output will + * be sent on the first test start. + * @access public + */ + function __construct() { + parent::__construct(); + } + + /** + * Paints the title only. + * @param string $test_name Name class of test. + * @access public + */ + function paintHeader($test_name) { + if (! SimpleReporter::inCli()) { + header('Content-type: text/plain'); + } + print "$test_name\n"; + flush(); + } + + /** + * Paints the end of the test with a summary of + * the passes and failures. + * @param string $test_name Name class of test. + * @access public + */ + function paintFooter($test_name) { + if ($this->getFailCount() + $this->getExceptionCount() == 0) { + print "OK\n"; + } else { + print "FAILURES!!!\n"; + } + print "Test cases run: " . $this->getTestCaseProgress() . + "/" . $this->getTestCaseCount() . + ", Passes: " . $this->getPassCount() . + ", Failures: " . $this->getFailCount() . + ", Exceptions: " . $this->getExceptionCount() . "\n"; + } + + /** + * Paints the test failure as a stack trace. + * @param string $message Failure message displayed in + * the context of the other tests. + * @access public + */ + function paintFail($message) { + parent::paintFail($message); + print $this->getFailCount() . ") $message\n"; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print "\tin " . implode("\n\tin ", array_reverse($breadcrumb)); + print "\n"; + } + + /** + * Paints a PHP error or exception. + * @param string $message Message to be shown. + * @access public + * @abstract + */ + function paintError($message) { + parent::paintError($message); + print "Exception " . $this->getExceptionCount() . "!\n$message\n"; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print "\tin " . implode("\n\tin ", array_reverse($breadcrumb)); + print "\n"; + } + + /** + * Paints a PHP error or exception. + * @param Exception $exception Exception to describe. + * @access public + * @abstract + */ + function paintException($exception) { + parent::paintException($exception); + $message = 'Unexpected exception of type [' . get_class($exception) . + '] with message ['. $exception->getMessage() . + '] in ['. $exception->getFile() . + ' line ' . $exception->getLine() . ']'; + print "Exception " . $this->getExceptionCount() . "!\n$message\n"; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print "\tin " . implode("\n\tin ", array_reverse($breadcrumb)); + print "\n"; + } + + /** + * Prints the message for skipping tests. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { + parent::paintSkip($message); + print "Skip: $message\n"; + } + + /** + * Paints formatted text such as dumped privateiables. + * @param string $message Text to show. + * @access public + */ + function paintFormattedMessage($message) { + print "$message\n"; + flush(); + } +} + +/** + * Runs just a single test group, a single case or + * even a single test within that case. + * @package SimpleTest + * @subpackage UnitTester + */ +class SelectiveReporter extends SimpleReporterDecorator { + private $just_this_case = false; + private $just_this_test = false; + private $on; + + /** + * Selects the test case or group to be run, + * and optionally a specific test. + * @param SimpleScorer $reporter Reporter to receive events. + * @param string $just_this_case Only this case or group will run. + * @param string $just_this_test Only this test method will run. + */ + function __construct($reporter, $just_this_case = false, $just_this_test = false) { + if (isset($just_this_case) && $just_this_case) { + $this->just_this_case = strtolower($just_this_case); + $this->off(); + } else { + $this->on(); + } + if (isset($just_this_test) && $just_this_test) { + $this->just_this_test = strtolower($just_this_test); + } + parent::__construct($reporter); + } + + /** + * Compares criteria to actual the case/group name. + * @param string $test_case The incoming test. + * @return boolean True if matched. + * @access protected + */ + protected function matchesTestCase($test_case) { + return $this->just_this_case == strtolower($test_case); + } + + /** + * Compares criteria to actual the test name. If no + * name was specified at the beginning, then all tests + * can run. + * @param string $method The incoming test method. + * @return boolean True if matched. + * @access protected + */ + protected function shouldRunTest($test_case, $method) { + if ($this->isOn() || $this->matchesTestCase($test_case)) { + if ($this->just_this_test) { + return $this->just_this_test == strtolower($method); + } else { + return true; + } + } + return false; + } + + /** + * Switch on testing for the group or subgroup. + * @access private + */ + protected function on() { + $this->on = true; + } + + /** + * Switch off testing for the group or subgroup. + * @access private + */ + protected function off() { + $this->on = false; + } + + /** + * Is this group actually being tested? + * @return boolean True if the current test group is active. + * @access private + */ + protected function isOn() { + return $this->on; + } + + /** + * Veto everything that doesn't match the method wanted. + * @param string $test_case Name of test case. + * @param string $method Name of test method. + * @return boolean True if test should be run. + * @access public + */ + function shouldInvoke($test_case, $method) { + if ($this->shouldRunTest($test_case, $method)) { + return $this->reporter->shouldInvoke($test_case, $method); + } + return false; + } + + /** + * Paints the start of a group test. + * @param string $test_case Name of test or other label. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_case, $size) { + if ($this->just_this_case && $this->matchesTestCase($test_case)) { + $this->on(); + } + $this->reporter->paintGroupStart($test_case, $size); + } + + /** + * Paints the end of a group test. + * @param string $test_case Name of test or other label. + * @access public + */ + function paintGroupEnd($test_case) { + $this->reporter->paintGroupEnd($test_case); + if ($this->just_this_case && $this->matchesTestCase($test_case)) { + $this->off(); + } + } +} + +/** + * Suppresses skip messages. + * @package SimpleTest + * @subpackage UnitTester + */ +class NoSkipsReporter extends SimpleReporterDecorator { + + /** + * Does nothing. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/scorer.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/scorer.php new file mode 100644 index 0000000..fa7c543 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/scorer.php @@ -0,0 +1,862 @@ +passes = 0; + $this->fails = 0; + $this->exceptions = 0; + $this->is_dry_run = false; + } + + /** + * Signals that the next evaluation will be a dry + * run. That is, the structure events will be + * recorded, but no tests will be run. + * @param boolean $is_dry Dry run if true. + * @access public + */ + function makeDry($is_dry = true) { + $this->is_dry_run = $is_dry; + } + + /** + * The reporter has a veto on what should be run. + * @param string $test_case_name name of test case. + * @param string $method Name of test method. + * @access public + */ + function shouldInvoke($test_case_name, $method) { + return ! $this->is_dry_run; + } + + /** + * Can wrap the invoker in preperation for running + * a test. + * @param SimpleInvoker $invoker Individual test runner. + * @return SimpleInvoker Wrapped test runner. + * @access public + */ + function createInvoker($invoker) { + return $invoker; + } + + /** + * Accessor for current status. Will be false + * if there have been any failures or exceptions. + * Used for command line tools. + * @return boolean True if no failures. + * @access public + */ + function getStatus() { + if ($this->exceptions + $this->fails > 0) { + return false; + } + return true; + } + + /** + * Paints the start of a group test. + * @param string $test_name Name of test or other label. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_name, $size) { + } + + /** + * Paints the end of a group test. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintGroupEnd($test_name) { + } + + /** + * Paints the start of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseStart($test_name) { + } + + /** + * Paints the end of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseEnd($test_name) { + } + + /** + * Paints the start of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodStart($test_name) { + } + + /** + * Paints the end of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodEnd($test_name) { + } + + /** + * Increments the pass count. + * @param string $message Message is ignored. + * @access public + */ + function paintPass($message) { + $this->passes++; + } + + /** + * Increments the fail count. + * @param string $message Message is ignored. + * @access public + */ + function paintFail($message) { + $this->fails++; + } + + /** + * Deals with PHP 4 throwing an error. + * @param string $message Text of error formatted by + * the test case. + * @access public + */ + function paintError($message) { + $this->exceptions++; + } + + /** + * Deals with PHP 5 throwing an exception. + * @param Exception $exception The actual exception thrown. + * @access public + */ + function paintException($exception) { + $this->exceptions++; + } + + /** + * Prints the message for skipping tests. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { + } + + /** + * Accessor for the number of passes so far. + * @return integer Number of passes. + * @access public + */ + function getPassCount() { + return $this->passes; + } + + /** + * Accessor for the number of fails so far. + * @return integer Number of fails. + * @access public + */ + function getFailCount() { + return $this->fails; + } + + /** + * Accessor for the number of untrapped errors + * so far. + * @return integer Number of exceptions. + * @access public + */ + function getExceptionCount() { + return $this->exceptions; + } + + /** + * Paints a simple supplementary message. + * @param string $message Text to display. + * @access public + */ + function paintMessage($message) { + } + + /** + * Paints a formatted ASCII message such as a + * privateiable dump. + * @param string $message Text to display. + * @access public + */ + function paintFormattedMessage($message) { + } + + /** + * By default just ignores user generated events. + * @param string $type Event type as text. + * @param mixed $payload Message or object. + * @access public + */ + function paintSignal($type, $payload) { + } +} + +/** + * Recipient of generated test messages that can display + * page footers and headers. Also keeps track of the + * test nesting. This is the main base class on which + * to build the finished test (page based) displays. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleReporter extends SimpleScorer { + private $test_stack; + private $size; + private $progress; + + /** + * Starts the display with no results in. + * @access public + */ + function __construct() { + parent::__construct(); + $this->test_stack = array(); + $this->size = null; + $this->progress = 0; + } + + /** + * Gets the formatter for privateiables and other small + * generic data items. + * @return SimpleDumper Formatter. + * @access public + */ + function getDumper() { + return new SimpleDumper(); + } + + /** + * Paints the start of a group test. Will also paint + * the page header and footer if this is the + * first test. Will stash the size if the first + * start. + * @param string $test_name Name of test that is starting. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_name, $size) { + if (! isset($this->size)) { + $this->size = $size; + } + if (count($this->test_stack) == 0) { + $this->paintHeader($test_name); + } + $this->test_stack[] = $test_name; + } + + /** + * Paints the end of a group test. Will paint the page + * footer if the stack of tests has unwound. + * @param string $test_name Name of test that is ending. + * @param integer $progress Number of test cases ending. + * @access public + */ + function paintGroupEnd($test_name) { + array_pop($this->test_stack); + if (count($this->test_stack) == 0) { + $this->paintFooter($test_name); + } + } + + /** + * Paints the start of a test case. Will also paint + * the page header and footer if this is the + * first test. Will stash the size if the first + * start. + * @param string $test_name Name of test that is starting. + * @access public + */ + function paintCaseStart($test_name) { + if (! isset($this->size)) { + $this->size = 1; + } + if (count($this->test_stack) == 0) { + $this->paintHeader($test_name); + } + $this->test_stack[] = $test_name; + } + + /** + * Paints the end of a test case. Will paint the page + * footer if the stack of tests has unwound. + * @param string $test_name Name of test that is ending. + * @access public + */ + function paintCaseEnd($test_name) { + $this->progress++; + array_pop($this->test_stack); + if (count($this->test_stack) == 0) { + $this->paintFooter($test_name); + } + } + + /** + * Paints the start of a test method. + * @param string $test_name Name of test that is starting. + * @access public + */ + function paintMethodStart($test_name) { + $this->test_stack[] = $test_name; + } + + /** + * Paints the end of a test method. Will paint the page + * footer if the stack of tests has unwound. + * @param string $test_name Name of test that is ending. + * @access public + */ + function paintMethodEnd($test_name) { + array_pop($this->test_stack); + } + + /** + * Paints the test document header. + * @param string $test_name First test top level + * to start. + * @access public + * @abstract + */ + function paintHeader($test_name) { + } + + /** + * Paints the test document footer. + * @param string $test_name The top level test. + * @access public + * @abstract + */ + function paintFooter($test_name) { + } + + /** + * Accessor for internal test stack. For + * subclasses that need to see the whole test + * history for display purposes. + * @return array List of methods in nesting order. + * @access public + */ + function getTestList() { + return $this->test_stack; + } + + /** + * Accessor for total test size in number + * of test cases. Null until the first + * test is started. + * @return integer Total number of cases at start. + * @access public + */ + function getTestCaseCount() { + return $this->size; + } + + /** + * Accessor for the number of test cases + * completed so far. + * @return integer Number of ended cases. + * @access public + */ + function getTestCaseProgress() { + return $this->progress; + } + + /** + * Static check for running in the comand line. + * @return boolean True if CLI. + * @access public + */ + static function inCli() { + return php_sapi_name() == 'cli'; + } +} + +/** + * For modifying the behaviour of the visual reporters. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleReporterDecorator { + protected $reporter; + + /** + * Mediates between the reporter and the test case. + * @param SimpleScorer $reporter Reporter to receive events. + */ + function __construct($reporter) { + $this->reporter = $reporter; + } + + /** + * Signals that the next evaluation will be a dry + * run. That is, the structure events will be + * recorded, but no tests will be run. + * @param boolean $is_dry Dry run if true. + * @access public + */ + function makeDry($is_dry = true) { + $this->reporter->makeDry($is_dry); + } + + /** + * Accessor for current status. Will be false + * if there have been any failures or exceptions. + * Used for command line tools. + * @return boolean True if no failures. + * @access public + */ + function getStatus() { + return $this->reporter->getStatus(); + } + + /** + * The reporter has a veto on what should be run. + * @param string $test_case_name name of test case. + * @param string $method Name of test method. + * @return boolean True if test should be run. + * @access public + */ + function shouldInvoke($test_case_name, $method) { + return $this->reporter->shouldInvoke($test_case_name, $method); + } + + /** + * Can wrap the invoker in preperation for running + * a test. + * @param SimpleInvoker $invoker Individual test runner. + * @return SimpleInvoker Wrapped test runner. + * @access public + */ + function createInvoker($invoker) { + return $this->reporter->createInvoker($invoker); + } + + /** + * Gets the formatter for privateiables and other small + * generic data items. + * @return SimpleDumper Formatter. + * @access public + */ + function getDumper() { + return $this->reporter->getDumper(); + } + + /** + * Paints the start of a group test. + * @param string $test_name Name of test or other label. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_name, $size) { + $this->reporter->paintGroupStart($test_name, $size); + } + + /** + * Paints the end of a group test. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintGroupEnd($test_name) { + $this->reporter->paintGroupEnd($test_name); + } + + /** + * Paints the start of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseStart($test_name) { + $this->reporter->paintCaseStart($test_name); + } + + /** + * Paints the end of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseEnd($test_name) { + $this->reporter->paintCaseEnd($test_name); + } + + /** + * Paints the start of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodStart($test_name) { + $this->reporter->paintMethodStart($test_name); + } + + /** + * Paints the end of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodEnd($test_name) { + $this->reporter->paintMethodEnd($test_name); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Message is ignored. + * @access public + */ + function paintPass($message) { + $this->reporter->paintPass($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Message is ignored. + * @access public + */ + function paintFail($message) { + $this->reporter->paintFail($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text of error formatted by + * the test case. + * @access public + */ + function paintError($message) { + $this->reporter->paintError($message); + } + + /** + * Chains to the wrapped reporter. + * @param Exception $exception Exception to show. + * @access public + */ + function paintException($exception) { + $this->reporter->paintException($exception); + } + + /** + * Prints the message for skipping tests. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { + $this->reporter->paintSkip($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text to display. + * @access public + */ + function paintMessage($message) { + $this->reporter->paintMessage($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text to display. + * @access public + */ + function paintFormattedMessage($message) { + $this->reporter->paintFormattedMessage($message); + } + + /** + * Chains to the wrapped reporter. + * @param string $type Event type as text. + * @param mixed $payload Message or object. + * @return boolean Should return false if this + * type of signal should fail the + * test suite. + * @access public + */ + function paintSignal($type, $payload) { + $this->reporter->paintSignal($type, $payload); + } +} + +/** + * For sending messages to multiple reporters at + * the same time. + * @package SimpleTest + * @subpackage UnitTester + */ +class MultipleReporter { + private $reporters = array(); + + /** + * Adds a reporter to the subscriber list. + * @param SimpleScorer $reporter Reporter to receive events. + * @access public + */ + function attachReporter($reporter) { + $this->reporters[] = $reporter; + } + + /** + * Signals that the next evaluation will be a dry + * run. That is, the structure events will be + * recorded, but no tests will be run. + * @param boolean $is_dry Dry run if true. + * @access public + */ + function makeDry($is_dry = true) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->makeDry($is_dry); + } + } + + /** + * Accessor for current status. Will be false + * if there have been any failures or exceptions. + * If any reporter reports a failure, the whole + * suite fails. + * @return boolean True if no failures. + * @access public + */ + function getStatus() { + for ($i = 0; $i < count($this->reporters); $i++) { + if (! $this->reporters[$i]->getStatus()) { + return false; + } + } + return true; + } + + /** + * The reporter has a veto on what should be run. + * It requires all reporters to want to run the method. + * @param string $test_case_name name of test case. + * @param string $method Name of test method. + * @access public + */ + function shouldInvoke($test_case_name, $method) { + for ($i = 0; $i < count($this->reporters); $i++) { + if (! $this->reporters[$i]->shouldInvoke($test_case_name, $method)) { + return false; + } + } + return true; + } + + /** + * Every reporter gets a chance to wrap the invoker. + * @param SimpleInvoker $invoker Individual test runner. + * @return SimpleInvoker Wrapped test runner. + * @access public + */ + function createInvoker($invoker) { + for ($i = 0; $i < count($this->reporters); $i++) { + $invoker = $this->reporters[$i]->createInvoker($invoker); + } + return $invoker; + } + + /** + * Gets the formatter for privateiables and other small + * generic data items. + * @return SimpleDumper Formatter. + * @access public + */ + function getDumper() { + return new SimpleDumper(); + } + + /** + * Paints the start of a group test. + * @param string $test_name Name of test or other label. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_name, $size) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintGroupStart($test_name, $size); + } + } + + /** + * Paints the end of a group test. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintGroupEnd($test_name) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintGroupEnd($test_name); + } + } + + /** + * Paints the start of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseStart($test_name) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintCaseStart($test_name); + } + } + + /** + * Paints the end of a test case. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintCaseEnd($test_name) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintCaseEnd($test_name); + } + } + + /** + * Paints the start of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodStart($test_name) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintMethodStart($test_name); + } + } + + /** + * Paints the end of a test method. + * @param string $test_name Name of test or other label. + * @access public + */ + function paintMethodEnd($test_name) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintMethodEnd($test_name); + } + } + + /** + * Chains to the wrapped reporter. + * @param string $message Message is ignored. + * @access public + */ + function paintPass($message) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintPass($message); + } + } + + /** + * Chains to the wrapped reporter. + * @param string $message Message is ignored. + * @access public + */ + function paintFail($message) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintFail($message); + } + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text of error formatted by + * the test case. + * @access public + */ + function paintError($message) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintError($message); + } + } + + /** + * Chains to the wrapped reporter. + * @param Exception $exception Exception to display. + * @access public + */ + function paintException($exception) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintException($exception); + } + } + + /** + * Prints the message for skipping tests. + * @param string $message Text of skip condition. + * @access public + */ + function paintSkip($message) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintSkip($message); + } + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text to display. + * @access public + */ + function paintMessage($message) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintMessage($message); + } + } + + /** + * Chains to the wrapped reporter. + * @param string $message Text to display. + * @access public + */ + function paintFormattedMessage($message) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintFormattedMessage($message); + } + } + + /** + * Chains to the wrapped reporter. + * @param string $type Event type as text. + * @param mixed $payload Message or object. + * @return boolean Should return false if this + * type of signal should fail the + * test suite. + * @access public + */ + function paintSignal($type, $payload) { + for ($i = 0; $i < count($this->reporters); $i++) { + $this->reporters[$i]->paintSignal($type, $payload); + } + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/selector.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/selector.php new file mode 100644 index 0000000..ba2fed3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/selector.php @@ -0,0 +1,141 @@ +name = $name; + } + + /** + * Accessor for name. + * @returns string $name Name to match. + */ + function getName() { + return $this->name; + } + + /** + * Compares with name attribute of widget. + * @param SimpleWidget $widget Control to compare. + * @access public + */ + function isMatch($widget) { + return ($widget->getName() == $this->name); + } +} + +/** + * Used to extract form elements for testing against. + * Searches by visible label or alt text. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleByLabel { + private $label; + + /** + * Stashes the name for later comparison. + * @param string $label Visible text to match. + */ + function __construct($label) { + $this->label = $label; + } + + /** + * Comparison. Compares visible text of widget or + * related label. + * @param SimpleWidget $widget Control to compare. + * @access public + */ + function isMatch($widget) { + if (! method_exists($widget, 'isLabel')) { + return false; + } + return $widget->isLabel($this->label); + } +} + +/** + * Used to extract form elements for testing against. + * Searches dy id attribute. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleById { + private $id; + + /** + * Stashes the name for later comparison. + * @param string $id ID atribute to match. + */ + function __construct($id) { + $this->id = $id; + } + + /** + * Comparison. Compares id attribute of widget. + * @param SimpleWidget $widget Control to compare. + * @access public + */ + function isMatch($widget) { + return $widget->isId($this->id); + } +} + +/** + * Used to extract form elements for testing against. + * Searches by visible label, name or alt text. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleByLabelOrName { + private $label; + + /** + * Stashes the name/label for later comparison. + * @param string $label Visible text to match. + */ + function __construct($label) { + $this->label = $label; + } + + /** + * Comparison. Compares visible text of widget or + * related label or name. + * @param SimpleWidget $widget Control to compare. + * @access public + */ + function isMatch($widget) { + if (method_exists($widget, 'isLabel')) { + if ($widget->isLabel($this->label)) { + return true; + } + } + return ($widget->getName() == $this->label); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/shell_tester.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/shell_tester.php new file mode 100644 index 0000000..b9d3762 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/shell_tester.php @@ -0,0 +1,330 @@ +output = false; + } + + /** + * Actually runs the command. Does not trap the + * error stream output as this need PHP 4.3+. + * @param string $command The actual command line + * to run. + * @return integer Exit code. + * @access public + */ + function execute($command) { + $this->output = false; + exec($command, $this->output, $ret); + return $ret; + } + + /** + * Accessor for the last output. + * @return string Output as text. + * @access public + */ + function getOutput() { + return implode("\n", $this->output); + } + + /** + * Accessor for the last output. + * @return array Output as array of lines. + * @access public + */ + function getOutputAsList() { + return $this->output; + } +} + +/** + * Test case for testing of command line scripts and + * utilities. Usually scripts that are external to the + * PHP code, but support it in some way. + * @package SimpleTest + * @subpackage UnitTester + */ +class ShellTestCase extends SimpleTestCase { + private $current_shell; + private $last_status; + private $last_command; + + /** + * Creates an empty test case. Should be subclassed + * with test methods for a functional test case. + * @param string $label Name of test case. Will use + * the class name if none specified. + * @access public + */ + function __construct($label = false) { + parent::__construct($label); + $this->current_shell = $this->createShell(); + $this->last_status = false; + $this->last_command = ''; + } + + /** + * Executes a command and buffers the results. + * @param string $command Command to run. + * @return boolean True if zero exit code. + * @access public + */ + function execute($command) { + $shell = $this->getShell(); + $this->last_status = $shell->execute($command); + $this->last_command = $command; + return ($this->last_status === 0); + } + + /** + * Dumps the output of the last command. + * @access public + */ + function dumpOutput() { + $this->dump($this->getOutput()); + } + + /** + * Accessor for the last output. + * @return string Output as text. + * @access public + */ + function getOutput() { + $shell = $this->getShell(); + return $shell->getOutput(); + } + + /** + * Accessor for the last output. + * @return array Output as array of lines. + * @access public + */ + function getOutputAsList() { + $shell = $this->getShell(); + return $shell->getOutputAsList(); + } + + /** + * Called from within the test methods to register + * passes and failures. + * @param boolean $result Pass on true. + * @param string $message Message to display describing + * the test state. + * @return boolean True on pass + * @access public + */ + function assertTrue($result, $message = false) { + return $this->assert(new TrueExpectation(), $result, $message); + } + + /** + * Will be true on false and vice versa. False + * is the PHP definition of false, so that null, + * empty strings, zero and an empty array all count + * as false. + * @param boolean $result Pass on false. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertFalse($result, $message = '%s') { + return $this->assert(new FalseExpectation(), $result, $message); + } + + /** + * Will trigger a pass if the two parameters have + * the same value only. Otherwise a fail. This + * is for testing hand extracted text, etc. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertEqual($first, $second, $message = "%s") { + return $this->assert( + new EqualExpectation($first), + $second, + $message); + } + + /** + * Will trigger a pass if the two parameters have + * a different value. Otherwise a fail. This + * is for testing hand extracted text, etc. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertNotEqual($first, $second, $message = "%s") { + return $this->assert( + new NotEqualExpectation($first), + $second, + $message); + } + + /** + * Tests the last status code from the shell. + * @param integer $status Expected status of last + * command. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertExitCode($status, $message = "%s") { + $message = sprintf($message, "Expected status code of [$status] from [" . + $this->last_command . "], but got [" . + $this->last_status . "]"); + return $this->assertTrue($status === $this->last_status, $message); + } + + /** + * Attempt to exactly match the combined STDERR and + * STDOUT output. + * @param string $expected Expected output. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertOutput($expected, $message = "%s") { + $shell = $this->getShell(); + return $this->assert( + new EqualExpectation($expected), + $shell->getOutput(), + $message); + } + + /** + * Scans the output for a Perl regex. If found + * anywhere it passes, else it fails. + * @param string $pattern Regex to search for. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertOutputPattern($pattern, $message = "%s") { + $shell = $this->getShell(); + return $this->assert( + new PatternExpectation($pattern), + $shell->getOutput(), + $message); + } + + /** + * If a Perl regex is found anywhere in the current + * output then a failure is generated, else a pass. + * @param string $pattern Regex to search for. + * @param $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoOutputPattern($pattern, $message = "%s") { + $shell = $this->getShell(); + return $this->assert( + new NoPatternExpectation($pattern), + $shell->getOutput(), + $message); + } + + /** + * File existence check. + * @param string $path Full filename and path. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertFileExists($path, $message = "%s") { + $message = sprintf($message, "File [$path] should exist"); + return $this->assertTrue(file_exists($path), $message); + } + + /** + * File non-existence check. + * @param string $path Full filename and path. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertFileNotExists($path, $message = "%s") { + $message = sprintf($message, "File [$path] should not exist"); + return $this->assertFalse(file_exists($path), $message); + } + + /** + * Scans a file for a Perl regex. If found + * anywhere it passes, else it fails. + * @param string $pattern Regex to search for. + * @param string $path Full filename and path. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertFilePattern($pattern, $path, $message = "%s") { + return $this->assert( + new PatternExpectation($pattern), + implode('', file($path)), + $message); + } + + /** + * If a Perl regex is found anywhere in the named + * file then a failure is generated, else a pass. + * @param string $pattern Regex to search for. + * @param string $path Full filename and path. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoFilePattern($pattern, $path, $message = "%s") { + return $this->assert( + new NoPatternExpectation($pattern), + implode('', file($path)), + $message); + } + + /** + * Accessor for current shell. Used for testing the + * the tester itself. + * @return Shell Current shell. + * @access protected + */ + protected function getShell() { + return $this->current_shell; + } + + /** + * Factory for the shell to run the command on. + * @return Shell New shell object. + * @access protected + */ + protected function createShell() { + return new SimpleShell(); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/simpletest.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/simpletest.php new file mode 100644 index 0000000..cfdadf2 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/simpletest.php @@ -0,0 +1,396 @@ += 0) { + require_once(dirname(__FILE__) . '/reflection_php5.php'); +} else { + require_once(dirname(__FILE__) . '/reflection_php4.php'); +} +require_once(dirname(__FILE__) . '/default_reporter.php'); +require_once(dirname(__FILE__) . '/compatibility.php'); +/**#@-*/ + +/** + * Registry and test context. Includes a few + * global options that I'm slowly getting rid of. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleTest { + + /** + * Reads the SimpleTest version from the release file. + * @return string Version string. + * @access public + */ + static function getVersion() { + $content = file(dirname(__FILE__) . '/VERSION'); + return trim($content[0]); + } + + /** + * Sets the name of a test case to ignore, usually + * because the class is an abstract case that should + * not be run. Once PHP4 is dropped this will disappear + * as a public method and "abstract" will rule. + * @param string $class Add a class to ignore. + * @access public + */ + static function ignore($class) { + $registry = &SimpleTest::getRegistry(); + $registry['IgnoreList'][strtolower($class)] = true; + } + + /** + * Scans the now complete ignore list, and adds + * all parent classes to the list. If a class + * is not a runnable test case, then it's parents + * wouldn't be either. This is syntactic sugar + * to cut down on ommissions of ignore()'s or + * missing abstract declarations. This cannot + * be done whilst loading classes wiithout forcing + * a particular order on the class declarations and + * the ignore() calls. It's just nice to have the ignore() + * calls at the top of the file before the actual declarations. + * @param array $classes Class names of interest. + * @access public + */ + static function ignoreParentsIfIgnored($classes) { + $registry = &SimpleTest::getRegistry(); + foreach ($classes as $class) { + if (SimpleTest::isIgnored($class)) { + $reflection = new SimpleReflection($class); + if ($parent = $reflection->getParent()) { + SimpleTest::ignore($parent); + } + } + } + } + + /** + * Puts the object to the global pool of 'preferred' objects + * which can be retrieved with SimpleTest :: preferred() method. + * Instances of the same class are overwritten. + * @param object $object Preferred object + * @access public + * @see preferred() + */ + static function prefer($object) { + $registry = &SimpleTest::getRegistry(); + $registry['Preferred'][] = $object; + } + + /** + * Retrieves 'preferred' objects from global pool. Class filter + * can be applied in order to retrieve the object of the specific + * class + * @param array|string $classes Allowed classes or interfaces. + * @access public + * @return array|object|null + * @see prefer() + */ + static function preferred($classes) { + if (! is_array($classes)) { + $classes = array($classes); + } + $registry = &SimpleTest::getRegistry(); + for ($i = count($registry['Preferred']) - 1; $i >= 0; $i--) { + foreach ($classes as $class) { + if (SimpleTestCompatibility::isA($registry['Preferred'][$i], $class)) { + return $registry['Preferred'][$i]; + } + } + } + return null; + } + + /** + * Test to see if a test case is in the ignore + * list. Quite obviously the ignore list should + * be a separate object and will be one day. + * This method is internal to SimpleTest. Don't + * use it. + * @param string $class Class name to test. + * @return boolean True if should not be run. + * @access public + */ + static function isIgnored($class) { + $registry = &SimpleTest::getRegistry(); + return isset($registry['IgnoreList'][strtolower($class)]); + } + + /** + * Sets proxy to use on all requests for when + * testing from behind a firewall. Set host + * to false to disable. This will take effect + * if there are no other proxy settings. + * @param string $proxy Proxy host as URL. + * @param string $username Proxy username for authentication. + * @param string $password Proxy password for authentication. + * @access public + */ + static function useProxy($proxy, $username = false, $password = false) { + $registry = &SimpleTest::getRegistry(); + $registry['DefaultProxy'] = $proxy; + $registry['DefaultProxyUsername'] = $username; + $registry['DefaultProxyPassword'] = $password; + } + + /** + * Accessor for default proxy host. + * @return string Proxy URL. + * @access public + */ + static function getDefaultProxy() { + $registry = &SimpleTest::getRegistry(); + return $registry['DefaultProxy']; + } + + /** + * Accessor for default proxy username. + * @return string Proxy username for authentication. + * @access public + */ + static function getDefaultProxyUsername() { + $registry = &SimpleTest::getRegistry(); + return $registry['DefaultProxyUsername']; + } + + /** + * Accessor for default proxy password. + * @return string Proxy password for authentication. + * @access public + */ + static function getDefaultProxyPassword() { + $registry = &SimpleTest::getRegistry(); + return $registry['DefaultProxyPassword']; + } + + /** + * Accessor for global registry of options. + * @return hash All stored values. + * @access private + */ + protected static function &getRegistry() { + static $registry = false; + if (! $registry) { + $registry = SimpleTest::getDefaults(); + } + return $registry; + } + + /** + * Accessor for the context of the current + * test run. + * @return SimpleTestContext Current test run. + * @access public + */ + static function getContext() { + static $context = false; + if (! $context) { + $context = new SimpleTestContext(); + } + return $context; + } + + /** + * Constant default values. + * @return hash All registry defaults. + * @access private + */ + protected static function getDefaults() { + return array( + 'MockBaseClass' => 'SimpleMock', + 'IgnoreList' => array(), + 'DefaultProxy' => false, + 'DefaultProxyUsername' => false, + 'DefaultProxyPassword' => false, + 'Preferred' => array(new HtmlReporter(), new TextReporter(), new XmlReporter())); + } + + /** + * @deprecated + */ + static function setMockBaseClass($mock_base) { + $registry = &SimpleTest::getRegistry(); + $registry['MockBaseClass'] = $mock_base; + } + + /** + * @deprecated + */ + static function getMockBaseClass() { + $registry = &SimpleTest::getRegistry(); + return $registry['MockBaseClass']; + } +} + +/** + * Container for all components for a specific + * test run. Makes things like error queues + * available to PHP event handlers, and also + * gets around some nasty reference issues in + * the mocks. + * @package SimpleTest + */ +class SimpleTestContext { + private $test; + private $reporter; + private $resources; + + /** + * Clears down the current context. + * @access public + */ + function clear() { + $this->resources = array(); + } + + /** + * Sets the current test case instance. This + * global instance can be used by the mock objects + * to send message to the test cases. + * @param SimpleTestCase $test Test case to register. + * @access public + */ + function setTest($test) { + $this->clear(); + $this->test = $test; + } + + /** + * Accessor for currently running test case. + * @return SimpleTestCase Current test. + * @access public + */ + function getTest() { + return $this->test; + } + + /** + * Sets the current reporter. This + * global instance can be used by the mock objects + * to send messages. + * @param SimpleReporter $reporter Reporter to register. + * @access public + */ + function setReporter($reporter) { + $this->clear(); + $this->reporter = $reporter; + } + + /** + * Accessor for current reporter. + * @return SimpleReporter Current reporter. + * @access public + */ + function getReporter() { + return $this->reporter; + } + + /** + * Accessor for the Singleton resource. + * @return object Global resource. + * @access public + */ + function get($resource) { + if (! isset($this->resources[$resource])) { + $this->resources[$resource] = new $resource(); + } + return $this->resources[$resource]; + } +} + +/** + * Interrogates the stack trace to recover the + * failure point. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleStackTrace { + private $prefixes; + + /** + * Stashes the list of target prefixes. + * @param array $prefixes List of method prefixes + * to search for. + */ + function __construct($prefixes) { + $this->prefixes = $prefixes; + } + + /** + * Extracts the last method name that was not within + * Simpletest itself. Captures a stack trace if none given. + * @param array $stack List of stack frames. + * @return string Snippet of test report with line + * number and file. + * @access public + */ + function traceMethod($stack = false) { + $stack = $stack ? $stack : $this->captureTrace(); + foreach ($stack as $frame) { + if ($this->frameLiesWithinSimpleTestFolder($frame)) { + continue; + } + if ($this->frameMatchesPrefix($frame)) { + return ' at [' . $frame['file'] . ' line ' . $frame['line'] . ']'; + } + } + return ''; + } + + /** + * Test to see if error is generated by SimpleTest itself. + * @param array $frame PHP stack frame. + * @return boolean True if a SimpleTest file. + * @access private + */ + protected function frameLiesWithinSimpleTestFolder($frame) { + if (isset($frame['file'])) { + $path = substr(SIMPLE_TEST, 0, -1); + if (strpos($frame['file'], $path) === 0) { + if (dirname($frame['file']) == $path) { + return true; + } + } + } + return false; + } + + /** + * Tries to determine if the method call is an assert, etc. + * @param array $frame PHP stack frame. + * @return boolean True if matches a target. + * @access private + */ + protected function frameMatchesPrefix($frame) { + foreach ($this->prefixes as $prefix) { + if (strncmp($frame['function'], $prefix, strlen($prefix)) == 0) { + return true; + } + } + return false; + } + + /** + * Grabs a current stack trace. + * @return array Fulle trace. + * @access private + */ + protected function captureTrace() { + if (function_exists('debug_backtrace')) { + return array_reverse(debug_backtrace()); + } + return array(); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/socket.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/socket.php new file mode 100644 index 0000000..c30c877 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/socket.php @@ -0,0 +1,308 @@ +clearError(); + } + + /** + * Test for an outstanding error. + * @return boolean True if there is an error. + * @access public + */ + function isError() { + return ($this->error != ''); + } + + /** + * Accessor for an outstanding error. + * @return string Empty string if no error otherwise + * the error message. + * @access public + */ + function getError() { + return $this->error; + } + + /** + * Sets the internal error. + * @param string Error message to stash. + * @access protected + */ + function setError($error) { + $this->error = $error; + } + + /** + * Resets the error state to no error. + * @access protected + */ + function clearError() { + $this->setError(''); + } +} + +class SimpleFileSocket extends SimpleStickyError { + private $handle; + private $is_open = false; + private $sent = ''; + private $block_size; + + /** + * Opens a socket for reading and writing. + * @param SimpleUrl $file Target URI to fetch. + * @param integer $block_size Size of chunk to read. + * @access public + */ + function __construct($file, $block_size = 1024) { + parent::__construct(); + if (! ($this->handle = $this->openFile($file, $error))) { + $file_string = $file->asString(); + $this->setError("Cannot open [$file_string] with [$error]"); + return; + } + $this->is_open = true; + $this->block_size = $block_size; + } + + /** + * Writes some data to the socket and saves alocal copy. + * @param string $message String to send to socket. + * @return boolean True if successful. + * @access public + */ + function write($message) { + return true; + } + + /** + * Reads data from the socket. The error suppresion + * is a workaround for PHP4 always throwing a warning + * with a secure socket. + * @return integer/boolean Incoming bytes. False + * on error. + * @access public + */ + function read() { + $raw = @fread($this->handle, $this->block_size); + if ($raw === false) { + $this->setError('Cannot read from socket'); + $this->close(); + } + return $raw; + } + + /** + * Accessor for socket open state. + * @return boolean True if open. + * @access public + */ + function isOpen() { + return $this->is_open; + } + + /** + * Closes the socket preventing further reads. + * Cannot be reopened once closed. + * @return boolean True if successful. + * @access public + */ + function close() { + if (!$this->is_open) return false; + $this->is_open = false; + return fclose($this->handle); + } + + /** + * Accessor for content so far. + * @return string Bytes sent only. + * @access public + */ + function getSent() { + return $this->sent; + } + + /** + * Actually opens the low level socket. + * @param SimpleUrl $file SimpleUrl file target. + * @param string $error Recipient of error message. + * @param integer $timeout Maximum time to wait for connection. + * @access protected + */ + protected function openFile($file, &$error) { + return @fopen($file->asString(), 'r'); + } +} + +/** + * Wrapper for TCP/IP socket. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleSocket extends SimpleStickyError { + private $handle; + private $is_open = false; + private $sent = ''; + private $lock_size; + + /** + * Opens a socket for reading and writing. + * @param string $host Hostname to send request to. + * @param integer $port Port on remote machine to open. + * @param integer $timeout Connection timeout in seconds. + * @param integer $block_size Size of chunk to read. + * @access public + */ + function __construct($host, $port, $timeout, $block_size = 255) { + parent::__construct(); + if (! ($this->handle = $this->openSocket($host, $port, $error_number, $error, $timeout))) { + $this->setError("Cannot open [$host:$port] with [$error] within [$timeout] seconds"); + return; + } + $this->is_open = true; + $this->block_size = $block_size; + SimpleTestCompatibility::setTimeout($this->handle, $timeout); + } + + /** + * Writes some data to the socket and saves alocal copy. + * @param string $message String to send to socket. + * @return boolean True if successful. + * @access public + */ + function write($message) { + if ($this->isError() || ! $this->isOpen()) { + return false; + } + $count = fwrite($this->handle, $message); + if (! $count) { + if ($count === false) { + $this->setError('Cannot write to socket'); + $this->close(); + } + return false; + } + fflush($this->handle); + $this->sent .= $message; + return true; + } + + /** + * Reads data from the socket. The error suppresion + * is a workaround for PHP4 always throwing a warning + * with a secure socket. + * @return integer/boolean Incoming bytes. False + * on error. + * @access public + */ + function read() { + if ($this->isError() || ! $this->isOpen()) { + return false; + } + $raw = @fread($this->handle, $this->block_size); + if ($raw === false) { + $this->setError('Cannot read from socket'); + $this->close(); + } + return $raw; + } + + /** + * Accessor for socket open state. + * @return boolean True if open. + * @access public + */ + function isOpen() { + return $this->is_open; + } + + /** + * Closes the socket preventing further reads. + * Cannot be reopened once closed. + * @return boolean True if successful. + * @access public + */ + function close() { + $this->is_open = false; + return fclose($this->handle); + } + + /** + * Accessor for content so far. + * @return string Bytes sent only. + * @access public + */ + function getSent() { + return $this->sent; + } + + /** + * Actually opens the low level socket. + * @param string $host Host to connect to. + * @param integer $port Port on host. + * @param integer $error_number Recipient of error code. + * @param string $error Recipoent of error message. + * @param integer $timeout Maximum time to wait for connection. + * @access protected + */ + protected function openSocket($host, $port, &$error_number, &$error, $timeout) { + return @fsockopen($host, $port, $error_number, $error, $timeout); + } +} + +/** + * Wrapper for TCP/IP socket over TLS. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleSecureSocket extends SimpleSocket { + + /** + * Opens a secure socket for reading and writing. + * @param string $host Hostname to send request to. + * @param integer $port Port on remote machine to open. + * @param integer $timeout Connection timeout in seconds. + * @access public + */ + function __construct($host, $port, $timeout) { + parent::__construct($host, $port, $timeout); + } + + /** + * Actually opens the low level socket. + * @param string $host Host to connect to. + * @param integer $port Port on host. + * @param integer $error_number Recipient of error code. + * @param string $error Recipient of error message. + * @param integer $timeout Maximum time to wait for connection. + * @access protected + */ + function openSocket($host, $port, &$error_number, &$error, $timeout) { + return parent::openSocket("tls://$host", $port, $error_number, $error, $timeout); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tag.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tag.php new file mode 100644 index 0000000..c30417d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tag.php @@ -0,0 +1,1418 @@ +name = strtolower(trim($name)); + $this->attributes = $attributes; + $this->content = ''; + } + + /** + * Check to see if the tag can have both start and + * end tags with content in between. + * @return boolean True if content allowed. + * @access public + */ + function expectEndTag() { + return true; + } + + /** + * The current tag should not swallow all content for + * itself as it's searchable page content. Private + * content tags are usually widgets that contain default + * values. + * @return boolean False as content is available + * to other tags by default. + * @access public + */ + function isPrivateContent() { + return false; + } + + /** + * Appends string content to the current content. + * @param string $content Additional text. + * @access public + */ + function addContent($content) { + $this->content .= (string)$content; + } + + /** + * Adds an enclosed tag to the content. + * @param SimpleTag $tag New tag. + * @access public + */ + function addTag($tag) { + } + + /** + * Accessor for tag name. + * @return string Name of tag. + * @access public + */ + function getTagName() { + return $this->name; + } + + /** + * List of legal child elements. + * @return array List of element names. + * @access public + */ + function getChildElements() { + return array(); + } + + /** + * Accessor for an attribute. + * @param string $label Attribute name. + * @return string Attribute value. + * @access public + */ + function getAttribute($label) { + $label = strtolower($label); + if (! isset($this->attributes[$label])) { + return false; + } + return (string)$this->attributes[$label]; + } + + /** + * Sets an attribute. + * @param string $label Attribute name. + * @return string $value New attribute value. + * @access protected + */ + protected function setAttribute($label, $value) { + $this->attributes[strtolower($label)] = $value; + } + + /** + * Accessor for the whole content so far. + * @return string Content as big raw string. + * @access public + */ + function getContent() { + return $this->content; + } + + /** + * Accessor for content reduced to visible text. Acts + * like a text mode browser, normalising space and + * reducing images to their alt text. + * @return string Content as plain text. + * @access public + */ + function getText() { + return SimpleHtmlSaxParser::normalise($this->content); + } + + /** + * Test to see if id attribute matches. + * @param string $id ID to test against. + * @return boolean True on match. + * @access public + */ + function isId($id) { + return ($this->getAttribute('id') == $id); + } +} + +/** + * Base url. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleBaseTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('base', $attributes); + } + + /** + * Base tag is not a block tag. + * @return boolean false + * @access public + */ + function expectEndTag() { + return false; + } +} + +/** + * Page title. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleTitleTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('title', $attributes); + } +} + +/** + * Link. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleAnchorTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('a', $attributes); + } + + /** + * Accessor for URL as string. + * @return string Coerced as string. + * @access public + */ + function getHref() { + $url = $this->getAttribute('href'); + if (is_bool($url)) { + $url = ''; + } + return $url; + } +} + +/** + * Form element. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleWidget extends SimpleTag { + private $value; + private $label; + private $is_set; + + /** + * Starts with a named tag with attributes only. + * @param string $name Tag name. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($name, $attributes) { + parent::__construct($name, $attributes); + $this->value = false; + $this->label = false; + $this->is_set = false; + } + + /** + * Accessor for name submitted as the key in + * GET/POST privateiables hash. + * @return string Parsed value. + * @access public + */ + function getName() { + return $this->getAttribute('name'); + } + + /** + * Accessor for default value parsed with the tag. + * @return string Parsed value. + * @access public + */ + function getDefault() { + return $this->getAttribute('value'); + } + + /** + * Accessor for currently set value or default if + * none. + * @return string Value set by form or default + * if none. + * @access public + */ + function getValue() { + if (! $this->is_set) { + return $this->getDefault(); + } + return $this->value; + } + + /** + * Sets the current form element value. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + $this->value = $value; + $this->is_set = true; + return true; + } + + /** + * Resets the form element value back to the + * default. + * @access public + */ + function resetValue() { + $this->is_set = false; + } + + /** + * Allows setting of a label externally, say by a + * label tag. + * @param string $label Label to attach. + * @access public + */ + function setLabel($label) { + $this->label = trim($label); + } + + /** + * Reads external or internal label. + * @param string $label Label to test. + * @return boolean True is match. + * @access public + */ + function isLabel($label) { + return $this->label == trim($label); + } + + /** + * Dispatches the value into the form encoded packet. + * @param SimpleEncoding $encoding Form packet. + * @access public + */ + function write($encoding) { + if ($this->getName()) { + $encoding->add($this->getName(), $this->getValue()); + } + } +} + +/** + * Text, password and hidden field. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleTextTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->setAttribute('value', ''); + } + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Sets the current form element value. Cannot + * change the value of a hidden field. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + if ($this->getAttribute('type') == 'hidden') { + return false; + } + return parent::setValue($value); + } +} + +/** + * Submit button as input tag. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleSubmitTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->setAttribute('value', 'Submit'); + } + } + + /** + * Tag contains no end element. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Disables the setting of the button value. + * @param string $value Ignored. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Value of browser visible text. + * @return string Visible label. + * @access public + */ + function getLabel() { + return $this->getValue(); + } + + /** + * Test for a label match when searching. + * @param string $label Label to test. + * @return boolean True on match. + * @access public + */ + function isLabel($label) { + return trim($label) == trim($this->getLabel()); + } +} + +/** + * Image button as input tag. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleImageSubmitTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('input', $attributes); + } + + /** + * Tag contains no end element. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Disables the setting of the button value. + * @param string $value Ignored. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Value of browser visible text. + * @return string Visible label. + * @access public + */ + function getLabel() { + if ($this->getAttribute('title')) { + return $this->getAttribute('title'); + } + return $this->getAttribute('alt'); + } + + /** + * Test for a label match when searching. + * @param string $label Label to test. + * @return boolean True on match. + * @access public + */ + function isLabel($label) { + return trim($label) == trim($this->getLabel()); + } + + /** + * Dispatches the value into the form encoded packet. + * @param SimpleEncoding $encoding Form packet. + * @param integer $x X coordinate of click. + * @param integer $y Y coordinate of click. + * @access public + */ + function write($encoding, $x = 1, $y = 1) { + if ($this->getName()) { + $encoding->add($this->getName() . '.x', $x); + $encoding->add($this->getName() . '.y', $y); + } else { + $encoding->add('x', $x); + $encoding->add('y', $y); + } + } +} + +/** + * Submit button as button tag. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleButtonTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * Defaults are very browser dependent. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('button', $attributes); + } + + /** + * Check to see if the tag can have both start and + * end tags with content in between. + * @return boolean True if content allowed. + * @access public + */ + function expectEndTag() { + return true; + } + + /** + * Disables the setting of the button value. + * @param string $value Ignored. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Value of browser visible text. + * @return string Visible label. + * @access public + */ + function getLabel() { + return $this->getContent(); + } + + /** + * Test for a label match when searching. + * @param string $label Label to test. + * @return boolean True on match. + * @access public + */ + function isLabel($label) { + return trim($label) == trim($this->getLabel()); + } +} + +/** + * Content tag for text area. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleTextAreaTag extends SimpleWidget { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('textarea', $attributes); + } + + /** + * Accessor for starting value. + * @return string Parsed value. + * @access public + */ + function getDefault() { + return $this->wrap(SimpleHtmlSaxParser::decodeHtml($this->getContent())); + } + + /** + * Applies word wrapping if needed. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + return parent::setValue($this->wrap($value)); + } + + /** + * Test to see if text should be wrapped. + * @return boolean True if wrapping on. + * @access private + */ + function wrapIsEnabled() { + if ($this->getAttribute('cols')) { + $wrap = $this->getAttribute('wrap'); + if (($wrap == 'physical') || ($wrap == 'hard')) { + return true; + } + } + return false; + } + + /** + * Performs the formatting that is peculiar to + * this tag. There is strange behaviour in this + * one, including stripping a leading new line. + * Go figure. I am using Firefox as a guide. + * @param string $text Text to wrap. + * @return string Text wrapped with carriage + * returns and line feeds + * @access private + */ + protected function wrap($text) { + $text = str_replace("\r\r\n", "\r\n", str_replace("\n", "\r\n", $text)); + $text = str_replace("\r\n\n", "\r\n", str_replace("\r", "\r\n", $text)); + if (strncmp($text, "\r\n", strlen("\r\n")) == 0) { + $text = substr($text, strlen("\r\n")); + } + if ($this->wrapIsEnabled()) { + return wordwrap( + $text, + (integer)$this->getAttribute('cols'), + "\r\n"); + } + return $text; + } + + /** + * The content of textarea is not part of the page. + * @return boolean True. + * @access public + */ + function isPrivateContent() { + return true; + } +} + +/** + * File upload widget. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleUploadTag extends SimpleWidget { + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('input', $attributes); + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * Dispatches the value into the form encoded packet. + * @param SimpleEncoding $encoding Form packet. + * @access public + */ + function write($encoding) { + if (! file_exists($this->getValue())) { + return; + } + $encoding->attach( + $this->getName(), + implode('', file($this->getValue())), + basename($this->getValue())); + } +} + +/** + * Drop down widget. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleSelectionTag extends SimpleWidget { + private $options; + private $choice; + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('select', $attributes); + $this->options = array(); + $this->choice = false; + } + + /** + * Adds an option tag to a selection field. + * @param SimpleOptionTag $tag New option. + * @access public + */ + function addTag($tag) { + if ($tag->getTagName() == 'option') { + $this->options[] = $tag; + } + } + + /** + * Text within the selection element is ignored. + * @param string $content Ignored. + * @access public + */ + function addContent($content) { + } + + /** + * Scans options for defaults. If none, then + * the first option is selected. + * @return string Selected field. + * @access public + */ + function getDefault() { + for ($i = 0, $count = count($this->options); $i < $count; $i++) { + if ($this->options[$i]->getAttribute('selected') !== false) { + return $this->options[$i]->getDefault(); + } + } + if ($count > 0) { + return $this->options[0]->getDefault(); + } + return ''; + } + + /** + * Can only set allowed values. + * @param string $value New choice. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + for ($i = 0, $count = count($this->options); $i < $count; $i++) { + if ($this->options[$i]->isValue($value)) { + $this->choice = $i; + return true; + } + } + return false; + } + + /** + * Accessor for current selection value. + * @return string Value attribute or + * content of opton. + * @access public + */ + function getValue() { + if ($this->choice === false) { + return $this->getDefault(); + } + return $this->options[$this->choice]->getValue(); + } +} + +/** + * Drop down widget. + * @package SimpleTest + * @subpackage WebTester + */ +class MultipleSelectionTag extends SimpleWidget { + private $options; + private $values; + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('select', $attributes); + $this->options = array(); + $this->values = false; + } + + /** + * Adds an option tag to a selection field. + * @param SimpleOptionTag $tag New option. + * @access public + */ + function addTag($tag) { + if ($tag->getTagName() == 'option') { + $this->options[] = &$tag; + } + } + + /** + * Text within the selection element is ignored. + * @param string $content Ignored. + * @access public + */ + function addContent($content) { + } + + /** + * Scans options for defaults to populate the + * value array(). + * @return array Selected fields. + * @access public + */ + function getDefault() { + $default = array(); + for ($i = 0, $count = count($this->options); $i < $count; $i++) { + if ($this->options[$i]->getAttribute('selected') !== false) { + $default[] = $this->options[$i]->getDefault(); + } + } + return $default; + } + + /** + * Can only set allowed values. Any illegal value + * will result in a failure, but all correct values + * will be set. + * @param array $desired New choices. + * @return boolean True if all allowed. + * @access public + */ + function setValue($desired) { + $achieved = array(); + foreach ($desired as $value) { + $success = false; + for ($i = 0, $count = count($this->options); $i < $count; $i++) { + if ($this->options[$i]->isValue($value)) { + $achieved[] = $this->options[$i]->getValue(); + $success = true; + break; + } + } + if (! $success) { + return false; + } + } + $this->values = $achieved; + return true; + } + + /** + * Accessor for current selection value. + * @return array List of currently set options. + * @access public + */ + function getValue() { + if ($this->values === false) { + return $this->getDefault(); + } + return $this->values; + } +} + +/** + * Option for selection field. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleOptionTag extends SimpleWidget { + + /** + * Stashes the attributes. + */ + function __construct($attributes) { + parent::__construct('option', $attributes); + } + + /** + * Does nothing. + * @param string $value Ignored. + * @return boolean Not allowed. + * @access public + */ + function setValue($value) { + return false; + } + + /** + * Test to see if a value matches the option. + * @param string $compare Value to compare with. + * @return boolean True if possible match. + * @access public + */ + function isValue($compare) { + $compare = trim($compare); + if (trim($this->getValue()) == $compare) { + return true; + } + return trim($this->getContent()) == $compare; + } + + /** + * Accessor for starting value. Will be set to + * the option label if no value exists. + * @return string Parsed value. + * @access public + */ + function getDefault() { + if ($this->getAttribute('value') === false) { + return $this->getContent(); + } + return $this->getAttribute('value'); + } + + /** + * The content of options is not part of the page. + * @return boolean True. + * @access public + */ + function isPrivateContent() { + return true; + } +} + +/** + * Radio button. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleRadioButtonTag extends SimpleWidget { + + /** + * Stashes the attributes. + * @param array $attributes Hash of attributes. + */ + function __construct($attributes) { + parent::__construct('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->setAttribute('value', 'on'); + } + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * The only allowed value sn the one in the + * "value" attribute. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + if ($value === false) { + return parent::setValue($value); + } + if ($value != $this->getAttribute('value')) { + return false; + } + return parent::setValue($value); + } + + /** + * Accessor for starting value. + * @return string Parsed value. + * @access public + */ + function getDefault() { + if ($this->getAttribute('checked') !== false) { + return $this->getAttribute('value'); + } + return false; + } +} + +/** + * Checkbox widget. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleCheckboxTag extends SimpleWidget { + + /** + * Starts with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('input', $attributes); + if ($this->getAttribute('value') === false) { + $this->setAttribute('value', 'on'); + } + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } + + /** + * The only allowed value in the one in the + * "value" attribute. The default for this + * attribute is "on". If this widget is set to + * true, then the usual value will be taken. + * @param string $value New value. + * @return boolean True if allowed. + * @access public + */ + function setValue($value) { + if ($value === false) { + return parent::setValue($value); + } + if ($value === true) { + return parent::setValue($this->getAttribute('value')); + } + if ($value != $this->getAttribute('value')) { + return false; + } + return parent::setValue($value); + } + + /** + * Accessor for starting value. The default + * value is "on". + * @return string Parsed value. + * @access public + */ + function getDefault() { + if ($this->getAttribute('checked') !== false) { + return $this->getAttribute('value'); + } + return false; + } +} + +/** + * A group of multiple widgets with some shared behaviour. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleTagGroup { + private $widgets = array(); + + /** + * Adds a tag to the group. + * @param SimpleWidget $widget + * @access public + */ + function addWidget($widget) { + $this->widgets[] = $widget; + } + + /** + * Accessor to widget set. + * @return array All widgets. + * @access protected + */ + protected function &getWidgets() { + return $this->widgets; + } + + /** + * Accessor for an attribute. + * @param string $label Attribute name. + * @return boolean Always false. + * @access public + */ + function getAttribute($label) { + return false; + } + + /** + * Fetches the name for the widget from the first + * member. + * @return string Name of widget. + * @access public + */ + function getName() { + if (count($this->widgets) > 0) { + return $this->widgets[0]->getName(); + } + } + + /** + * Scans the widgets for one with the appropriate + * ID field. + * @param string $id ID value to try. + * @return boolean True if matched. + * @access public + */ + function isId($id) { + for ($i = 0, $count = count($this->widgets); $i < $count; $i++) { + if ($this->widgets[$i]->isId($id)) { + return true; + } + } + return false; + } + + /** + * Scans the widgets for one with the appropriate + * attached label. + * @param string $label Attached label to try. + * @return boolean True if matched. + * @access public + */ + function isLabel($label) { + for ($i = 0, $count = count($this->widgets); $i < $count; $i++) { + if ($this->widgets[$i]->isLabel($label)) { + return true; + } + } + return false; + } + + /** + * Dispatches the value into the form encoded packet. + * @param SimpleEncoding $encoding Form packet. + * @access public + */ + function write($encoding) { + $encoding->add($this->getName(), $this->getValue()); + } +} + +/** + * A group of tags with the same name within a form. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleCheckboxGroup extends SimpleTagGroup { + + /** + * Accessor for current selected widget or false + * if none. + * @return string/array Widget values or false if none. + * @access public + */ + function getValue() { + $values = array(); + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getValue() !== false) { + $values[] = $widgets[$i]->getValue(); + } + } + return $this->coerceValues($values); + } + + /** + * Accessor for starting value that is active. + * @return string/array Widget values or false if none. + * @access public + */ + function getDefault() { + $values = array(); + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getDefault() !== false) { + $values[] = $widgets[$i]->getDefault(); + } + } + return $this->coerceValues($values); + } + + /** + * Accessor for current set values. + * @param string/array/boolean $values Either a single string, a + * hash or false for nothing set. + * @return boolean True if all values can be set. + * @access public + */ + function setValue($values) { + $values = $this->makeArray($values); + if (! $this->valuesArePossible($values)) { + return false; + } + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + $possible = $widgets[$i]->getAttribute('value'); + if (in_array($widgets[$i]->getAttribute('value'), $values)) { + $widgets[$i]->setValue($possible); + } else { + $widgets[$i]->setValue(false); + } + } + return true; + } + + /** + * Tests to see if a possible value set is legal. + * @param string/array/boolean $values Either a single string, a + * hash or false for nothing set. + * @return boolean False if trying to set a + * missing value. + * @access private + */ + protected function valuesArePossible($values) { + $matches = array(); + $widgets = &$this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + $possible = $widgets[$i]->getAttribute('value'); + if (in_array($possible, $values)) { + $matches[] = $possible; + } + } + return ($values == $matches); + } + + /** + * Converts the output to an appropriate format. This means + * that no values is false, a single value is just that + * value and only two or more are contained in an array. + * @param array $values List of values of widgets. + * @return string/array/boolean Expected format for a tag. + * @access private + */ + protected function coerceValues($values) { + if (count($values) == 0) { + return false; + } elseif (count($values) == 1) { + return $values[0]; + } else { + return $values; + } + } + + /** + * Converts false or string into array. The opposite of + * the coercian method. + * @param string/array/boolean $value A single item is converted + * to a one item list. False + * gives an empty list. + * @return array List of values, possibly empty. + * @access private + */ + protected function makeArray($value) { + if ($value === false) { + return array(); + } + if (is_string($value)) { + return array($value); + } + return $value; + } +} + +/** + * A group of tags with the same name within a form. + * Used for radio buttons. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleRadioGroup extends SimpleTagGroup { + + /** + * Each tag is tried in turn until one is + * successfully set. The others will be + * unchecked if successful. + * @param string $value New value. + * @return boolean True if any allowed. + * @access public + */ + function setValue($value) { + if (! $this->valueIsPossible($value)) { + return false; + } + $index = false; + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if (! $widgets[$i]->setValue($value)) { + $widgets[$i]->setValue(false); + } + } + return true; + } + + /** + * Tests to see if a value is allowed. + * @param string Attempted value. + * @return boolean True if a valid value. + * @access private + */ + protected function valueIsPossible($value) { + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getAttribute('value') == $value) { + return true; + } + } + return false; + } + + /** + * Accessor for current selected widget or false + * if none. + * @return string/boolean Value attribute or + * content of opton. + * @access public + */ + function getValue() { + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getValue() !== false) { + return $widgets[$i]->getValue(); + } + } + return false; + } + + /** + * Accessor for starting value that is active. + * @return string/boolean Value of first checked + * widget or false if none. + * @access public + */ + function getDefault() { + $widgets = $this->getWidgets(); + for ($i = 0, $count = count($widgets); $i < $count; $i++) { + if ($widgets[$i]->getDefault() !== false) { + return $widgets[$i]->getDefault(); + } + } + return false; + } +} + +/** + * Tag to keep track of labels. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleLabelTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('label', $attributes); + } + + /** + * Access for the ID to attach the label to. + * @return string For attribute. + * @access public + */ + function getFor() { + return $this->getAttribute('for'); + } +} + +/** + * Tag to aid parsing the form. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleFormTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('form', $attributes); + } +} + +/** + * Tag to aid parsing the frames in a page. + * @package SimpleTest + * @subpackage WebTester + */ +class SimpleFrameTag extends SimpleTag { + + /** + * Starts with a named tag with attributes only. + * @param hash $attributes Attribute names and + * string values. + */ + function __construct($attributes) { + parent::__construct('frame', $attributes); + } + + /** + * Tag contains no content. + * @return boolean False. + * @access public + */ + function expectEndTag() { + return false; + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/acceptance_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/acceptance_test.php new file mode 100644 index 0000000..aada433 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/acceptance_test.php @@ -0,0 +1,1653 @@ +addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + $this->assertTrue($browser->get($this->samples() . 'network_confirm.php')); + $this->assertPattern('/target for the SimpleTest/', $browser->getContent()); + $this->assertPattern('/Request method.*?
    GET<\/dd>/', $browser->getContent()); + $this->assertEqual($browser->getTitle(), 'Simple test target file'); + $this->assertEqual($browser->getResponseCode(), 200); + $this->assertEqual($browser->getMimeType(), 'text/html'); + } + + function testPost() { + $browser = new SimpleBrowser(); + $browser->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + $this->assertTrue($browser->post($this->samples() . 'network_confirm.php')); + $this->assertPattern('/target for the SimpleTest/', $browser->getContent()); + $this->assertPattern('/Request method.*?
    POST<\/dd>/', $browser->getContent()); + } + + function testAbsoluteLinkFollowing() { + $browser = new SimpleBrowser(); + $browser->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + $browser->get($this->samples() . 'link_confirm.php'); + $this->assertTrue($browser->clickLink('Absolute')); + $this->assertPattern('/target for the SimpleTest/', $browser->getContent()); + } + + function testRelativeEncodedeLinkFollowing() { + $browser = new SimpleBrowser(); + $browser->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + $browser->get($this->samples() . 'link_confirm.php'); + $this->assertTrue($browser->clickLink("m�rc�l kiek'eboe")); + $this->assertPattern('/target for the SimpleTest/', $browser->getContent()); + } + + function testRelativeLinkFollowing() { + $browser = new SimpleBrowser(); + $browser->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + $browser->get($this->samples() . 'link_confirm.php'); + $this->assertTrue($browser->clickLink('Relative')); + $this->assertPattern('/target for the SimpleTest/', $browser->getContent()); + } + + function testUnifiedClickLinkClicking() { + $browser = new SimpleBrowser(); + $browser->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + $browser->get($this->samples() . 'link_confirm.php'); + $this->assertTrue($browser->click('Relative')); + $this->assertPattern('/target for the SimpleTest/', $browser->getContent()); + } + + function testIdLinkFollowing() { + $browser = new SimpleBrowser(); + $browser->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + $browser->get($this->samples() . 'link_confirm.php'); + $this->assertTrue($browser->clickLinkById(1)); + $this->assertPattern('/target for the SimpleTest/', $browser->getContent()); + } + + function testCookieReading() { + $browser = new SimpleBrowser(); + $browser->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + $browser->get($this->samples() . 'set_cookies.php'); + $this->assertEqual($browser->getCurrentCookieValue('session_cookie'), 'A'); + $this->assertEqual($browser->getCurrentCookieValue('short_cookie'), 'B'); + $this->assertEqual($browser->getCurrentCookieValue('day_cookie'), 'C'); + } + + function testSimpleSubmit() { + $browser = new SimpleBrowser(); + $browser->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + $browser->get($this->samples() . 'form.html'); + $this->assertTrue($browser->clickSubmit('Go!')); + $this->assertPattern('/Request method.*?
    POST<\/dd>/', $browser->getContent()); + $this->assertPattern('/go=\[Go!\]/', $browser->getContent()); + } + + function testUnifiedClickCanSubmit() { + $browser = new SimpleBrowser(); + $browser->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + $browser->get($this->samples() . 'form.html'); + $this->assertTrue($browser->click('Go!')); + $this->assertPattern('/go=\[Go!\]/', $browser->getContent()); + } +} + +class TestOfLocalFileBrowser extends UnitTestCase { + function samples() { + return 'file://'.dirname(__FILE__).'/site/'; + } + + function testGet() { + $browser = new SimpleBrowser(); + $browser->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + $this->assertTrue($browser->get($this->samples() . 'file.html')); + $this->assertPattern('/Link to SimpleTest/', $browser->getContent()); + $this->assertEqual($browser->getTitle(), 'Link to SimpleTest'); + $this->assertFalse($browser->getResponseCode()); + $this->assertEqual($browser->getMimeType(), ''); + } +} + +class TestRadioFields extends SimpleTestAcceptanceTest { + function testSetFieldAsInteger() { + $this->get($this->samples() . 'form_with_radio_buttons.html'); + $this->assertTrue($this->setField('tested_field', 2)); + $this->clickSubmitByName('send'); + $this->assertEqual($this->getUrl(), $this->samples() . 'form_with_radio_buttons.html?tested_field=2&send=click+me'); + } + + function testSetFieldAsString() { + $this->get($this->samples() . 'form_with_radio_buttons.html'); + $this->assertTrue($this->setField('tested_field', '2')); + $this->clickSubmitByName('send'); + $this->assertEqual($this->getUrl(), $this->samples() . 'form_with_radio_buttons.html?tested_field=2&send=click+me'); + } +} + +class TestOfLiveFetching extends SimpleTestAcceptanceTest { + function setUp() { + $this->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + } + + function testFormWithArrayBasedInputs() { + $this->get($this->samples() . 'form_with_array_based_inputs.php'); + $this->setField('value[]', '3', '1'); + $this->setField('value[]', '4', '2'); + $this->clickSubmit('Go'); + $this->assertPattern('/QUERY_STRING : value%5B%5D=3&value%5B%5D=4&submit=Go/'); + } + + function testFormWithQuotedValues() { + $this->get($this->samples() . 'form_with_quoted_values.php'); + $this->assertField('a', 'default'); + $this->assertFieldById('text_field', 'default'); + $this->clickSubmit('Go'); + $this->assertPattern('/a=default&submit=Go/'); + } + + function testGet() { + $this->assertTrue($this->get($this->samples() . 'network_confirm.php')); + $this->assertEqual($this->getUrl(), $this->samples() . 'network_confirm.php'); + $this->assertText('target for the SimpleTest'); + $this->assertPattern('/Request method.*?
    GET<\/dd>/'); + $this->assertTitle('Simple test target file'); + $this->assertTitle(new PatternExpectation('/target file/')); + $this->assertResponse(200); + $this->assertMime('text/html'); + $this->assertHeader('connection', 'close'); + $this->assertHeader('connection', new PatternExpectation('/los/')); + } + + function testSlowGet() { + $this->assertTrue($this->get($this->samples() . 'slow_page.php')); + } + + function testTimedOutGet() { + $this->setConnectionTimeout(1); + $this->ignoreErrors(); + $this->assertFalse($this->get($this->samples() . 'slow_page.php')); + } + + function testPost() { + $this->assertTrue($this->post($this->samples() . 'network_confirm.php')); + $this->assertText('target for the SimpleTest'); + $this->assertPattern('/Request method.*?
    POST<\/dd>/'); + } + + function testGetWithData() { + $this->get($this->samples() . 'network_confirm.php', array("a" => "aaa")); + $this->assertPattern('/Request method.*?
    GET<\/dd>/'); + $this->assertText('a=[aaa]'); + } + + function testPostWithData() { + $this->post($this->samples() . 'network_confirm.php', array("a" => "aaa")); + $this->assertPattern('/Request method.*?
    POST<\/dd>/'); + $this->assertText('a=[aaa]'); + } + + function testPostWithRecursiveData() { + $this->post($this->samples() . 'network_confirm.php', array("a" => "aaa")); + $this->assertPattern('/Request method.*?
    POST<\/dd>/'); + $this->assertText('a=[aaa]'); + + $this->post($this->samples() . 'network_confirm.php', array("a[aa]" => "aaa")); + $this->assertPattern('/Request method.*?
    POST<\/dd>/'); + $this->assertText('a=[aa=[aaa]]'); + + $this->post($this->samples() . 'network_confirm.php', array("a[aa][aaa]" => "aaaa")); + $this->assertPattern('/Request method.*?
    POST<\/dd>/'); + $this->assertText('a=[aa=[aaa=[aaaa]]]'); + + $this->post($this->samples() . 'network_confirm.php', array("a" => array("aa" => "aaa"))); + $this->assertPattern('/Request method.*?
    POST<\/dd>/'); + $this->assertText('a=[aa=[aaa]]'); + + $this->post($this->samples() . 'network_confirm.php', array("a" => array("aa" => array("aaa" => "aaaa")))); + $this->assertPattern('/Request method.*?
    POST<\/dd>/'); + $this->assertText('a=[aa=[aaa=[aaaa]]]'); + } + + function testRelativeGet() { + $this->get($this->samples() . 'link_confirm.php'); + $this->assertTrue($this->get('network_confirm.php')); + $this->assertText('target for the SimpleTest'); + } + + function testRelativePost() { + $this->post($this->samples() . 'link_confirm.php'); + $this->assertTrue($this->post('network_confirm.php')); + $this->assertText('target for the SimpleTest'); + } +} + +class TestOfLinkFollowing extends SimpleTestAcceptanceTest { + function setUp() { + $this->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + } + + function testLinkAssertions() { + $this->get($this->samples() . 'link_confirm.php'); + $this->assertLink('Absolute', $this->samples() . 'network_confirm.php'); + $this->assertLink('Absolute', new PatternExpectation('/confirm/')); + $this->assertClickable('Absolute'); + } + + function testAbsoluteLinkFollowing() { + $this->get($this->samples() . 'link_confirm.php'); + $this->assertTrue($this->clickLink('Absolute')); + $this->assertText('target for the SimpleTest'); + } + + function testRelativeLinkFollowing() { + $this->get($this->samples() . 'link_confirm.php'); + $this->assertTrue($this->clickLink('Relative')); + $this->assertText('target for the SimpleTest'); + } + + function testLinkIdFollowing() { + $this->get($this->samples() . 'link_confirm.php'); + $this->assertLinkById(1); + $this->assertTrue($this->clickLinkById(1)); + $this->assertText('target for the SimpleTest'); + } + + function testAbsoluteUrlBehavesAbsolutely() { + $this->get($this->samples() . 'link_confirm.php'); + $this->get('http://www.lastcraft.com'); + $this->assertText('No guarantee of quality is given or even intended'); + } + + function testRelativeUrlRespectsBaseTag() { + $this->get($this->samples() . 'base_tag/base_link.html'); + $this->click('Back to test pages'); + $this->assertTitle('Simple test target file'); + } +} + +class TestOfLivePageLinkingWithMinimalLinks extends SimpleTestAcceptanceTest { + function setUp() { + $this->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + } + + function testClickToExplicitelyNamedSelfReturns() { + $this->get($this->samples() . 'front_controller_style/a_page.php'); + $this->assertEqual($this->getUrl(), $this->samples() . 'front_controller_style/a_page.php'); + $this->assertTitle('Simple test page with links'); + $this->assertLink('Self'); + $this->clickLink('Self'); + $this->assertTitle('Simple test page with links'); + } + + function testClickToMissingPageReturnsToSamePage() { + $this->get($this->samples() . 'front_controller_style/a_page.php'); + $this->clickLink('No page'); + $this->assertTitle('Simple test page with links'); + $this->assertText('[action=no_page]'); + } + + function testClickToBareActionReturnsToSamePage() { + $this->get($this->samples() . 'front_controller_style/a_page.php'); + $this->clickLink('Bare action'); + $this->assertTitle('Simple test page with links'); + $this->assertText('[action=]'); + } + + function testClickToSingleQuestionMarkReturnsToSamePage() { + $this->get($this->samples() . 'front_controller_style/a_page.php'); + $this->clickLink('Empty query'); + $this->assertTitle('Simple test page with links'); + } + + function testClickToEmptyStringReturnsToSamePage() { + $this->get($this->samples() . 'front_controller_style/a_page.php'); + $this->clickLink('Empty link'); + $this->assertTitle('Simple test page with links'); + } + + function testClickToSingleDotGoesToCurrentDirectory() { + $this->get($this->samples() . 'front_controller_style/a_page.php'); + $this->clickLink('Current directory'); + $this->assertTitle( + 'Simple test front controller', + '%s -> index.php needs to be set as a default web server home page'); + } + + function testClickBackADirectoryLevel() { + $this->get($this->samples() . 'front_controller_style/'); + $this->clickLink('Down one'); + $this->assertPattern('|Index of .*?/test|i'); + } +} + +class TestOfLiveFrontControllerEmulation extends SimpleTestAcceptanceTest { + function setUp() { + $this->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + } + + function testJumpToNamedPage() { + $this->get($this->samples() . 'front_controller_style/'); + $this->assertText('Simple test front controller'); + $this->clickLink('Index'); + $this->assertResponse(200); + $this->assertText('[action=index]'); + } + + function testJumpToUnnamedPage() { + $this->get($this->samples() . 'front_controller_style/'); + $this->clickLink('No page'); + $this->assertResponse(200); + $this->assertText('Simple test front controller'); + $this->assertText('[action=no_page]'); + } + + function testJumpToUnnamedPageWithBareParameter() { + $this->get($this->samples() . 'front_controller_style/'); + $this->clickLink('Bare action'); + $this->assertResponse(200); + $this->assertText('Simple test front controller'); + $this->assertText('[action=]'); + } + + function testJumpToUnnamedPageWithEmptyQuery() { + $this->get($this->samples() . 'front_controller_style/'); + $this->clickLink('Empty query'); + $this->assertResponse(200); + $this->assertPattern('/Simple test front controller/'); + $this->assertPattern('/raw get data.*?\[\].*?get data/si'); + } + + function testJumpToUnnamedPageWithEmptyLink() { + $this->get($this->samples() . 'front_controller_style/'); + $this->clickLink('Empty link'); + $this->assertResponse(200); + $this->assertPattern('/Simple test front controller/'); + $this->assertPattern('/raw get data.*?\[\].*?get data/si'); + } + + function testJumpBackADirectoryLevel() { + $this->get($this->samples() . 'front_controller_style/'); + $this->clickLink('Down one'); + $this->assertPattern('|Index of .*?/test|'); + } + + function testSubmitToNamedPage() { + $this->get($this->samples() . 'front_controller_style/'); + $this->assertText('Simple test front controller'); + $this->clickSubmit('Index'); + $this->assertResponse(200); + $this->assertText('[action=Index]'); + } + + function testSubmitToSameDirectory() { + $this->get($this->samples() . 'front_controller_style/index.php'); + $this->clickSubmit('Same directory'); + $this->assertResponse(200); + $this->assertText('[action=Same+directory]'); + } + + function testSubmitToEmptyAction() { + $this->get($this->samples() . 'front_controller_style/index.php'); + $this->clickSubmit('Empty action'); + $this->assertResponse(200); + $this->assertText('[action=Empty+action]'); + } + + function testSubmitToNoAction() { + $this->get($this->samples() . 'front_controller_style/index.php'); + $this->clickSubmit('No action'); + $this->assertResponse(200); + $this->assertText('[action=No+action]'); + } + + function testSubmitBackADirectoryLevel() { + $this->get($this->samples() . 'front_controller_style/'); + $this->clickSubmit('Down one'); + $this->assertPattern('|Index of .*?/test|'); + } + + function testSubmitToNamedPageWithMixedPostAndGet() { + $this->get($this->samples() . 'front_controller_style/?a=A'); + $this->assertText('Simple test front controller'); + $this->clickSubmit('Index post'); + $this->assertText('action=[Index post]'); + $this->assertNoText('[a=A]'); + } + + function testSubmitToSameDirectoryMixedPostAndGet() { + $this->get($this->samples() . 'front_controller_style/index.php?a=A'); + $this->clickSubmit('Same directory post'); + $this->assertText('action=[Same directory post]'); + $this->assertNoText('[a=A]'); + } + + function testSubmitToEmptyActionMixedPostAndGet() { + $this->get($this->samples() . 'front_controller_style/index.php?a=A'); + $this->clickSubmit('Empty action post'); + $this->assertText('action=[Empty action post]'); + $this->assertText('[a=A]'); + } + + function testSubmitToNoActionMixedPostAndGet() { + $this->get($this->samples() . 'front_controller_style/index.php?a=A'); + $this->clickSubmit('No action post'); + $this->assertText('action=[No action post]'); + $this->assertText('[a=A]'); + } +} + +class TestOfLiveHeaders extends SimpleTestAcceptanceTest { + function setUp() { + $this->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + } + + function testConfirmingHeaderExistence() { + $this->get('http://www.lastcraft.com/'); + $this->assertHeader('content-type'); + $this->assertHeader('content-type', 'text/html'); + $this->assertHeader('content-type', new PatternExpectation('/HTML/i')); + $this->assertNoHeader('WWW-Authenticate'); + } +} + +class TestOfLiveRedirects extends SimpleTestAcceptanceTest { + function setUp() { + $this->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + } + + function testNoRedirects() { + $this->setMaximumRedirects(0); + $this->get($this->samples() . 'redirect.php'); + $this->assertTitle('Redirection test'); + } + + function testRedirects() { + $this->setMaximumRedirects(1); + $this->get($this->samples() . 'redirect.php'); + $this->assertTitle('Simple test target file'); + } + + function testRedirectLosesGetData() { + $this->get($this->samples() . 'redirect.php', array('a' => 'aaa')); + $this->assertNoText('a=[aaa]'); + } + + function testRedirectKeepsExtraRequestDataOfItsOwn() { + $this->get($this->samples() . 'redirect.php'); + $this->assertText('r=[rrr]'); + } + + function testRedirectLosesPostData() { + $this->post($this->samples() . 'redirect.php', array('a' => 'aaa')); + $this->assertTitle('Simple test target file'); + $this->assertNoText('a=[aaa]'); + } + + function testRedirectWithBaseUrlChange() { + $this->get($this->samples() . 'base_change_redirect.php'); + $this->assertTitle('Simple test target file in folder'); + $this->get($this->samples() . 'path/base_change_redirect.php'); + $this->assertTitle('Simple test target file'); + } + + function testRedirectWithDoubleBaseUrlChange() { + $this->get($this->samples() . 'double_base_change_redirect.php'); + $this->assertTitle('Simple test target file'); + } +} + +class TestOfLiveCookies extends SimpleTestAcceptanceTest { + function setUp() { + $this->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + } + + function here() { + return new SimpleUrl($this->samples()); + } + + function thisHost() { + $here = $this->here(); + return $here->getHost(); + } + + function thisPath() { + $here = $this->here(); + return $here->getPath(); + } + + function testCookieSettingAndAssertions() { + $this->setCookie('a', 'Test cookie a'); + $this->setCookie('b', 'Test cookie b', $this->thisHost()); + $this->setCookie('c', 'Test cookie c', $this->thisHost(), $this->thisPath()); + $this->get($this->samples() . 'network_confirm.php'); + $this->assertText('Test cookie a'); + $this->assertText('Test cookie b'); + $this->assertText('Test cookie c'); + $this->assertCookie('a'); + $this->assertCookie('b', 'Test cookie b'); + $this->assertTrue($this->getCookie('c') == 'Test cookie c'); + } + + function testNoCookieSetWhenCookiesDisabled() { + $this->setCookie('a', 'Test cookie a'); + $this->ignoreCookies(); + $this->get($this->samples() . 'network_confirm.php'); + $this->assertNoText('Test cookie a'); + } + + function testCookieReading() { + $this->get($this->samples() . 'set_cookies.php'); + $this->assertCookie('session_cookie', 'A'); + $this->assertCookie('short_cookie', 'B'); + $this->assertCookie('day_cookie', 'C'); + } + + function testNoCookie() { + $this->assertNoCookie('aRandomCookie'); + } + + function testNoCookieReadingWhenCookiesDisabled() { + $this->ignoreCookies(); + $this->get($this->samples() . 'set_cookies.php'); + $this->assertNoCookie('session_cookie'); + $this->assertNoCookie('short_cookie'); + $this->assertNoCookie('day_cookie'); + } + + function testCookiePatternAssertions() { + $this->get($this->samples() . 'set_cookies.php'); + $this->assertCookie('session_cookie', new PatternExpectation('/a/i')); + } + + function testTemporaryCookieExpiry() { + $this->get($this->samples() . 'set_cookies.php'); + $this->restart(); + $this->assertNoCookie('session_cookie'); + $this->assertCookie('day_cookie', 'C'); + } + + function testTimedCookieExpiryWith100SecondMargin() { + $this->get($this->samples() . 'set_cookies.php'); + $this->ageCookies(3600); + $this->restart(time() + 100); + $this->assertNoCookie('session_cookie'); + $this->assertNoCookie('hour_cookie'); + $this->assertCookie('day_cookie', 'C'); + } + + function testNoClockOverDriftBy100Seconds() { + $this->get($this->samples() . 'set_cookies.php'); + $this->restart(time() + 200); + $this->assertNoCookie( + 'short_cookie', + '%s -> Please check your computer clock setting if you are not using NTP'); + } + + function testNoClockUnderDriftBy100Seconds() { + $this->get($this->samples() . 'set_cookies.php'); + $this->restart(time() + 0); + $this->assertCookie( + 'short_cookie', + 'B', + '%s -> Please check your computer clock setting if you are not using NTP'); + } + + function testCookiePath() { + $this->get($this->samples() . 'set_cookies.php'); + $this->assertNoCookie('path_cookie', 'D'); + $this->get('./path/show_cookies.php'); + $this->assertPattern('/path_cookie/'); + $this->assertCookie('path_cookie', 'D'); + } +} + +class LiveTestOfForms extends SimpleTestAcceptanceTest { + function setUp() { + $this->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + } + + function testSimpleSubmit() { + $this->get($this->samples() . 'form.html'); + $this->assertTrue($this->clickSubmit('Go!')); + $this->assertPattern('/Request method.*?
    POST<\/dd>/'); + $this->assertText('go=[Go!]'); + } + + function testDefaultFormValues() { + $this->get($this->samples() . 'form.html'); + $this->assertFieldByName('a', ''); + $this->assertFieldByName('b', 'Default text'); + $this->assertFieldByName('c', ''); + $this->assertFieldByName('d', 'd1'); + $this->assertFieldByName('e', false); + $this->assertFieldByName('f', 'on'); + $this->assertFieldByName('g', 'g3'); + $this->assertFieldByName('h', 2); + $this->assertFieldByName('go', 'Go!'); + $this->assertClickable('Go!'); + $this->assertSubmit('Go!'); + $this->assertTrue($this->clickSubmit('Go!')); + $this->assertText('go=[Go!]'); + $this->assertText('a=[]'); + $this->assertText('b=[Default text]'); + $this->assertText('c=[]'); + $this->assertText('d=[d1]'); + $this->assertNoText('e=['); + $this->assertText('f=[on]'); + $this->assertText('g=[g3]'); + } + + function testFormSubmissionByButtonLabel() { + $this->get($this->samples() . 'form.html'); + $this->setFieldByName('a', 'aaa'); + $this->setFieldByName('b', 'bbb'); + $this->setFieldByName('c', 'ccc'); + $this->setFieldByName('d', 'D2'); + $this->setFieldByName('e', 'on'); + $this->setFieldByName('f', false); + $this->setFieldByName('g', 'g2'); + $this->setFieldByName('h', 1); + $this->assertTrue($this->clickSubmit('Go!')); + $this->assertText('a=[aaa]'); + $this->assertText('b=[bbb]'); + $this->assertText('c=[ccc]'); + $this->assertText('d=[d2]'); + $this->assertText('e=[on]'); + $this->assertNoText('f=['); + $this->assertText('g=[g2]'); + } + + function testAdditionalFormValues() { + $this->get($this->samples() . 'form.html'); + $this->assertTrue($this->clickSubmit('Go!', array('add' => 'A'))); + $this->assertText('go=[Go!]'); + $this->assertText('add=[A]'); + } + + function testFormSubmissionByName() { + $this->get($this->samples() . 'form.html'); + $this->setFieldByName('a', 'A'); + $this->assertTrue($this->clickSubmitByName('go')); + $this->assertText('a=[A]'); + } + + function testFormSubmissionByNameAndAdditionalParameters() { + $this->get($this->samples() . 'form.html'); + $this->assertTrue($this->clickSubmitByName('go', array('add' => 'A'))); + $this->assertText('go=[Go!]'); + $this->assertText('add=[A]'); + } + + function testFormSubmissionBySubmitButtonLabeledSubmit() { + $this->get($this->samples() . 'form.html'); + $this->assertTrue($this->clickSubmitByName('test')); + $this->assertText('test=[Submit]'); + } + + function testFormSubmissionWithIds() { + $this->get($this->samples() . 'form.html'); + $this->assertFieldById(1, ''); + $this->assertFieldById(2, 'Default text'); + $this->assertFieldById(3, ''); + $this->assertFieldById(4, 'd1'); + $this->assertFieldById(5, false); + $this->assertFieldById(6, 'on'); + $this->assertFieldById(8, 'g3'); + $this->assertFieldById(11, 2); + $this->setFieldById(1, 'aaa'); + $this->setFieldById(2, 'bbb'); + $this->setFieldById(3, 'ccc'); + $this->setFieldById(4, 'D2'); + $this->setFieldById(5, 'on'); + $this->setFieldById(6, false); + $this->setFieldById(8, 'g2'); + $this->setFieldById(11, 'H1'); + $this->assertTrue($this->clickSubmitById(99)); + $this->assertText('a=[aaa]'); + $this->assertText('b=[bbb]'); + $this->assertText('c=[ccc]'); + $this->assertText('d=[d2]'); + $this->assertText('e=[on]'); + $this->assertNoText('f=['); + $this->assertText('g=[g2]'); + $this->assertText('h=[1]'); + $this->assertText('go=[Go!]'); + } + + function testFormSubmissionWithLabels() { + $this->get($this->samples() . 'form.html'); + $this->assertField('Text A', ''); + $this->assertField('Text B', 'Default text'); + $this->assertField('Text area C', ''); + $this->assertField('Selection D', 'd1'); + $this->assertField('Checkbox E', false); + $this->assertField('Checkbox F', 'on'); + $this->assertField('3', 'g3'); + $this->assertField('Selection H', 2); + $this->setField('Text A', 'aaa'); + $this->setField('Text B', 'bbb'); + $this->setField('Text area C', 'ccc'); + $this->setField('Selection D', 'D2'); + $this->setField('Checkbox E', 'on'); + $this->setField('Checkbox F', false); + $this->setField('2', 'g2'); + $this->setField('Selection H', 'H1'); + $this->clickSubmit('Go!'); + $this->assertText('a=[aaa]'); + $this->assertText('b=[bbb]'); + $this->assertText('c=[ccc]'); + $this->assertText('d=[d2]'); + $this->assertText('e=[on]'); + $this->assertNoText('f=['); + $this->assertText('g=[g2]'); + $this->assertText('h=[1]'); + $this->assertText('go=[Go!]'); + } + + function testSettingCheckboxWithBooleanTrueSetsUnderlyingValue() { + $this->get($this->samples() . 'form.html'); + $this->setField('Checkbox E', true); + $this->assertField('Checkbox E', 'on'); + $this->clickSubmit('Go!'); + $this->assertText('e=[on]'); + } + + function testFormSubmissionWithMixedPostAndGet() { + $this->get($this->samples() . 'form_with_mixed_post_and_get.html'); + $this->setField('Text A', 'Hello'); + $this->assertTrue($this->clickSubmit('Go!')); + $this->assertText('a=[Hello]'); + $this->assertText('x=[X]'); + $this->assertText('y=[Y]'); + } + + function testFormSubmissionWithMixedPostAndEncodedGet() { + $this->get($this->samples() . 'form_with_mixed_post_and_get.html'); + $this->setField('Text B', 'Hello'); + $this->assertTrue($this->clickSubmit('Go encoded!')); + $this->assertText('b=[Hello]'); + $this->assertText('x=[X]'); + $this->assertText('y=[Y]'); + } + + function testFormSubmissionWithoutAction() { + $this->get($this->samples() . 'form_without_action.php?test=test'); + $this->assertText('_GET : [test]'); + $this->assertTrue($this->clickSubmit('Submit Post With Empty Action')); + $this->assertText('_GET : [test]'); + $this->assertText('_POST : [test]'); + } + + function testImageSubmissionByLabel() { + $this->get($this->samples() . 'form.html'); + $this->assertImage('Image go!'); + $this->assertTrue($this->clickImage('Image go!', 10, 12)); + $this->assertText('go_x=[10]'); + $this->assertText('go_y=[12]'); + } + + function testImageSubmissionByLabelWithAdditionalParameters() { + $this->get($this->samples() . 'form.html'); + $this->assertTrue($this->clickImage('Image go!', 10, 12, array('add' => 'A'))); + $this->assertText('add=[A]'); + } + + function testImageSubmissionByName() { + $this->get($this->samples() . 'form.html'); + $this->assertTrue($this->clickImageByName('go', 10, 12)); + $this->assertText('go_x=[10]'); + $this->assertText('go_y=[12]'); + } + + function testImageSubmissionById() { + $this->get($this->samples() . 'form.html'); + $this->assertTrue($this->clickImageById(97, 10, 12)); + $this->assertText('go_x=[10]'); + $this->assertText('go_y=[12]'); + } + + function testButtonSubmissionByLabel() { + $this->get($this->samples() . 'form.html'); + $this->assertTrue($this->clickSubmit('Button go!', 10, 12)); + $this->assertPattern('/go=\[ButtonGo\]/s'); + } + + function testNamelessSubmitSendsNoValue() { + $this->get($this->samples() . 'form_with_unnamed_submit.html'); + $this->click('Go!'); + $this->assertNoText('Go!'); + $this->assertNoText('submit'); + } + + function testNamelessImageSendsXAndYValues() { + $this->get($this->samples() . 'form_with_unnamed_submit.html'); + $this->clickImage('Image go!', 4, 5); + $this->assertNoText('ImageGo'); + $this->assertText('x=[4]'); + $this->assertText('y=[5]'); + } + + function testNamelessButtonSendsNoValue() { + $this->get($this->samples() . 'form_with_unnamed_submit.html'); + $this->click('Button Go!'); + $this->assertNoText('ButtonGo'); + } + + function testSelfSubmit() { + $this->get($this->samples() . 'self_form.php'); + $this->assertNoText('[Submitted]'); + $this->assertNoText('[Wrong form]'); + $this->assertTrue($this->clickSubmit()); + $this->assertText('[Submitted]'); + $this->assertNoText('[Wrong form]'); + $this->assertTitle('Test of form self submission'); + } + + function testSelfSubmitWithParameters() { + $this->get($this->samples() . 'self_form.php'); + $this->setFieldByName('visible', 'Resent'); + $this->assertTrue($this->clickSubmit()); + $this->assertText('[Resent]'); + } + + function testSettingOfBlankOption() { + $this->get($this->samples() . 'form.html'); + $this->assertTrue($this->setFieldByName('d', '')); + $this->clickSubmit('Go!'); + $this->assertText('d=[]'); + } + + function testAssertingFieldValueWithPattern() { + $this->get($this->samples() . 'form.html'); + $this->setField('c', 'A very long string'); + $this->assertField('c', new PatternExpectation('/very long/')); + } + + function testSendingMultipartFormDataEncodedForm() { + $this->get($this->samples() . 'form_data_encoded_form.html'); + $this->assertField('Text A', ''); + $this->assertField('Text B', 'Default text'); + $this->assertField('Text area C', ''); + $this->assertField('Selection D', 'd1'); + $this->assertField('Checkbox E', false); + $this->assertField('Checkbox F', 'on'); + $this->assertField('3', 'g3'); + $this->assertField('Selection H', 2); + $this->setField('Text A', 'aaa'); + $this->setField('Text B', 'bbb'); + $this->setField('Text area C', 'ccc'); + $this->setField('Selection D', 'D2'); + $this->setField('Checkbox E', 'on'); + $this->setField('Checkbox F', false); + $this->setField('2', 'g2'); + $this->setField('Selection H', 'H1'); + $this->assertTrue($this->clickSubmit('Go!')); + $this->assertText('a=[aaa]'); + $this->assertText('b=[bbb]'); + $this->assertText('c=[ccc]'); + $this->assertText('d=[d2]'); + $this->assertText('e=[on]'); + $this->assertNoText('f=['); + $this->assertText('g=[g2]'); + $this->assertText('h=[1]'); + $this->assertText('go=[Go!]'); + } + + function testSettingVariousBlanksInFields() { + $this->get($this->samples() . 'form_with_false_defaults.html'); + $this->assertField('Text A', ''); + $this->setField('Text A', '0'); + $this->assertField('Text A', '0'); + $this->assertField('Text area B', ''); + $this->setField('Text area B', '0'); + $this->assertField('Text area B', '0'); + $this->assertField('Text area C', " "); + $this->assertField('Selection D', ''); + $this->setField('Selection D', 'D2'); + $this->assertField('Selection D', 'D2'); + $this->setField('Selection D', 'D3'); + $this->assertField('Selection D', '0'); + $this->setField('Selection D', 'D4'); + $this->assertField('Selection D', '?'); + $this->assertField('Checkbox E', ''); + $this->assertField('Checkbox F', 'on'); + $this->assertField('Checkbox G', '0'); + $this->assertField('Checkbox H', '?'); + $this->assertFieldByName('i', 'on'); + $this->setFieldByName('i', ''); + $this->assertFieldByName('i', ''); + $this->setFieldByName('i', '0'); + $this->assertFieldByName('i', '0'); + $this->setFieldByName('i', '?'); + $this->assertFieldByName('i', '?'); + } + + function testSubmissionOfBlankFields() { + $this->get($this->samples() . 'form_with_false_defaults.html'); + $this->setField('Text A', ''); + $this->setField('Text area B', ''); + $this->setFieldByName('i', ''); + $this->click('Go!'); + $this->assertText('a=[]'); + $this->assertText('b=[]'); + $this->assertPattern('/c=\[ \]/'); + $this->assertText('d=[]'); + $this->assertText('e=[]'); + $this->assertText('i=[]'); + } + + function testSubmissionOfEmptyValues() { + $this->get($this->samples() . 'form_with_false_defaults.html'); + $this->setField('Selection D', 'D2'); + $this->click('Go!'); + $this->assertText('a=[]'); + $this->assertText('b=[]'); + $this->assertText('d=[D2]'); + $this->assertText('f=[on]'); + $this->assertText('i=[on]'); + } + + function testSubmissionOfZeroes() { + $this->get($this->samples() . 'form_with_false_defaults.html'); + $this->setField('Text A', '0'); + $this->setField('Text area B', '0'); + $this->setField('Selection D', 'D3'); + $this->setFieldByName('i', '0'); + $this->click('Go!'); + $this->assertText('a=[0]'); + $this->assertText('b=[0]'); + $this->assertText('d=[0]'); + $this->assertText('g=[0]'); + $this->assertText('i=[0]'); + } + + function testSubmissionOfQuestionMarks() { + $this->get($this->samples() . 'form_with_false_defaults.html'); + $this->setField('Text A', '?'); + $this->setField('Text area B', '?'); + $this->setField('Selection D', 'D4'); + $this->setFieldByName('i', '?'); + $this->click('Go!'); + $this->assertText('a=[?]'); + $this->assertText('b=[?]'); + $this->assertText('d=[?]'); + $this->assertText('h=[?]'); + $this->assertText('i=[?]'); + } + + function testSubmissionOfHtmlEncodedValues() { + $this->get($this->samples() . 'form_with_tricky_defaults.html'); + $this->assertField('Text A', '&\'"<>'); + $this->assertField('Text B', '"'); + $this->assertField('Text area C', '&\'"<>'); + $this->assertField('Selection D', "'"); + $this->assertField('Checkbox E', '&\'"<>'); + $this->assertField('Checkbox F', false); + $this->assertFieldByname('i', "'"); + $this->click('Go!'); + $this->assertText('a=[&\'"<>, "]'); + $this->assertText('c=[&\'"<>]'); + $this->assertText("d=[']"); + $this->assertText('e=[&\'"<>]'); + $this->assertText("i=[']"); + } + + function testFormActionRespectsBaseTag() { + $this->get($this->samples() . 'base_tag/form.html'); + $this->assertTrue($this->clickSubmit('Go!')); + $this->assertText('go=[Go!]'); + $this->assertText('a=[]'); + } +} + +class TestOfLiveMultiValueWidgets extends SimpleTestAcceptanceTest { + function setUp() { + $this->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + } + + function testDefaultFormValueSubmission() { + $this->get($this->samples() . 'multiple_widget_form.html'); + $this->assertFieldByName('a', array('a2', 'a3')); + $this->assertFieldByName('b', array('b2', 'b3')); + $this->assertFieldByName('c[]', array('c2', 'c3')); + $this->assertFieldByName('d', array('2', '3')); + $this->assertFieldByName('e', array('2', '3')); + $this->assertTrue($this->clickSubmit('Go!')); + $this->assertText('a=[a2, a3]'); + $this->assertText('b=[b2, b3]'); + $this->assertText('c=[c2, c3]'); + $this->assertText('d=[2, 3]'); + $this->assertText('e=[2, 3]'); + } + + function testSubmittingMultipleValues() { + $this->get($this->samples() . 'multiple_widget_form.html'); + $this->setFieldByName('a', array('a1', 'a4')); + $this->assertFieldByName('a', array('a1', 'a4')); + $this->assertFieldByName('a', array('a4', 'a1')); + $this->setFieldByName('b', array('b1', 'b4')); + $this->assertFieldByName('b', array('b1', 'b4')); + $this->setFieldByName('c[]', array('c1', 'c4')); + $this->assertField('c[]', array('c1', 'c4')); + $this->setFieldByName('d', array('1', '4')); + $this->assertField('d', array('1', '4')); + $this->setFieldByName('e', array('e1', 'e4')); + $this->assertField('e', array('1', '4')); + $this->assertTrue($this->clickSubmit('Go!')); + $this->assertText('a=[a1, a4]'); + $this->assertText('b=[b1, b4]'); + $this->assertText('c=[c1, c4]'); + $this->assertText('d=[1, 4]'); + $this->assertText('e=[1, 4]'); + } + + function testSettingByOptionValue() { + $this->get($this->samples() . 'multiple_widget_form.html'); + $this->setFieldByName('d', array('1', '4')); + $this->assertField('d', array('1', '4')); + $this->assertTrue($this->clickSubmit('Go!')); + $this->assertText('d=[1, 4]'); + } + + function testSubmittingMultipleValuesByLabel() { + $this->get($this->samples() . 'multiple_widget_form.html'); + $this->setField('Multiple selection A', array('a1', 'a4')); + $this->assertField('Multiple selection A', array('a1', 'a4')); + $this->assertField('Multiple selection A', array('a4', 'a1')); + $this->setField('multiple selection C', array('c1', 'c4')); + $this->assertField('multiple selection C', array('c1', 'c4')); + $this->assertTrue($this->clickSubmit('Go!')); + $this->assertText('a=[a1, a4]'); + $this->assertText('c=[c1, c4]'); + } + + function testSavantStyleHiddenFieldDefaults() { + $this->get($this->samples() . 'savant_style_form.html'); + $this->assertFieldByName('a', array('a0')); + $this->assertFieldByName('b', array('b0')); + $this->assertTrue($this->clickSubmit('Go!')); + $this->assertText('a=[a0]'); + $this->assertText('b=[b0]'); + } + + function testSavantStyleHiddenDefaultsAreOverridden() { + $this->get($this->samples() . 'savant_style_form.html'); + $this->assertTrue($this->setFieldByName('a', array('a1'))); + $this->assertTrue($this->setFieldByName('b', 'b1')); + $this->assertTrue($this->clickSubmit('Go!')); + $this->assertText('a=[a1]'); + $this->assertText('b=[b1]'); + } + + function testSavantStyleFormSettingById() { + $this->get($this->samples() . 'savant_style_form.html'); + $this->assertFieldById(1, array('a0')); + $this->assertFieldById(4, array('b0')); + $this->assertTrue($this->setFieldById(2, 'a1')); + $this->assertTrue($this->setFieldById(5, 'b1')); + $this->assertTrue($this->clickSubmitById(99)); + $this->assertText('a=[a1]'); + $this->assertText('b=[b1]'); + } +} + +class TestOfFileUploads extends SimpleTestAcceptanceTest { + function setUp() { + $this->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + } + + function testSingleFileUpload() { + $this->get($this->samples() . 'upload_form.html'); + $this->assertTrue($this->setField('Content:', + dirname(__FILE__) . '/support/upload_sample.txt')); + $this->assertField('Content:', dirname(__FILE__) . '/support/upload_sample.txt'); + $this->click('Go!'); + $this->assertText('Sample for testing file upload'); + } + + function testMultipleFileUpload() { + $this->get($this->samples() . 'upload_form.html'); + $this->assertTrue($this->setField('Content:', + dirname(__FILE__) . '/support/upload_sample.txt')); + $this->assertTrue($this->setField('Supplemental:', + dirname(__FILE__) . '/support/supplementary_upload_sample.txt')); + $this->assertField('Supplemental:', + dirname(__FILE__) . '/support/supplementary_upload_sample.txt'); + $this->click('Go!'); + $this->assertText('Sample for testing file upload'); + $this->assertText('Some more text content'); + } + + function testBinaryFileUpload() { + $this->get($this->samples() . 'upload_form.html'); + $this->assertTrue($this->setField('Content:', + dirname(__FILE__) . '/support/latin1_sample')); + $this->click('Go!'); + $this->assertText( + implode('', file(dirname(__FILE__) . '/support/latin1_sample'))); + } +} + +class TestOfLiveHistoryNavigation extends SimpleTestAcceptanceTest { + function setUp() { + $this->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + } + + function testRetry() { + $this->get($this->samples() . 'cookie_based_counter.php'); + $this->assertPattern('/count: 1/i'); + $this->retry(); + $this->assertPattern('/count: 2/i'); + $this->retry(); + $this->assertPattern('/count: 3/i'); + } + + function testOfBackButton() { + $this->get($this->samples() . '1.html'); + $this->clickLink('2'); + $this->assertTitle('2'); + $this->assertTrue($this->back()); + $this->assertTitle('1'); + $this->assertTrue($this->forward()); + $this->assertTitle('2'); + $this->assertFalse($this->forward()); + } + + function testGetRetryResubmitsData() { + $this->assertTrue($this->get( + $this->samples() . 'network_confirm.php?a=aaa')); + $this->assertPattern('/Request method.*?
    GET<\/dd>/'); + $this->assertText('a=[aaa]'); + $this->retry(); + $this->assertPattern('/Request method.*?
    GET<\/dd>/'); + $this->assertText('a=[aaa]'); + } + + function testGetRetryResubmitsExtraData() { + $this->assertTrue($this->get( + $this->samples() . 'network_confirm.php', + array('a' => 'aaa'))); + $this->assertPattern('/Request method.*?
    GET<\/dd>/'); + $this->assertText('a=[aaa]'); + $this->retry(); + $this->assertPattern('/Request method.*?
    GET<\/dd>/'); + $this->assertText('a=[aaa]'); + } + + function testPostRetryResubmitsData() { + $this->assertTrue($this->post( + $this->samples() . 'network_confirm.php', + array('a' => 'aaa'))); + $this->assertPattern('/Request method.*?
    POST<\/dd>/'); + $this->assertText('a=[aaa]'); + $this->retry(); + $this->assertPattern('/Request method.*?
    POST<\/dd>/'); + $this->assertText('a=[aaa]'); + } + + function testGetRetryResubmitsRepeatedData() { + $this->assertTrue($this->get( + $this->samples() . 'network_confirm.php?a=1&a=2')); + $this->assertPattern('/Request method.*?
    GET<\/dd>/'); + $this->assertText('a=[1, 2]'); + $this->retry(); + $this->assertPattern('/Request method.*?
    GET<\/dd>/'); + $this->assertText('a=[1, 2]'); + } +} + +class TestOfLiveAuthentication extends SimpleTestAcceptanceTest { + function setUp() { + $this->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + } + + function testChallengeFromProtectedPage() { + $this->get($this->samples() . 'protected/'); + $this->assertResponse(401); + $this->assertAuthentication('Basic'); + $this->assertRealm('SimpleTest basic authentication'); + $this->assertRealm(new PatternExpectation('/simpletest/i')); + $this->authenticate('test', 'secret'); + $this->assertResponse(200); + $this->retry(); + $this->assertResponse(200); + } + + function testTrailingSlashImpliedWithinRealm() { + $this->get($this->samples() . 'protected/'); + $this->authenticate('test', 'secret'); + $this->assertResponse(200); + $this->get($this->samples() . 'protected'); + $this->assertResponse(200); + } + + function testTrailingSlashImpliedSettingRealm() { + $this->get($this->samples() . 'protected'); + $this->authenticate('test', 'secret'); + $this->assertResponse(200); + $this->get($this->samples() . 'protected/'); + $this->assertResponse(200); + } + + function testEncodedAuthenticationFetchesPage() { + $this->get('http://test:secret@www.lastcraft.com/test/protected/'); + $this->assertResponse(200); + } + + function testEncodedAuthenticationFetchesPageAfterTrailingSlashRedirect() { + $this->get('http://test:secret@www.lastcraft.com/test/protected'); + $this->assertResponse(200); + } + + function testRealmExtendsToWholeDirectory() { + $this->get($this->samples() . 'protected/1.html'); + $this->authenticate('test', 'secret'); + $this->clickLink('2'); + $this->assertResponse(200); + $this->clickLink('3'); + $this->assertResponse(200); + } + + function testRedirectKeepsAuthentication() { + $this->get($this->samples() . 'protected/local_redirect.php'); + $this->authenticate('test', 'secret'); + $this->assertTitle('Simple test target file'); + } + + function testRedirectKeepsEncodedAuthentication() { + $this->get('http://test:secret@www.lastcraft.com/test/protected/local_redirect.php'); + $this->assertResponse(200); + $this->assertTitle('Simple test target file'); + } + + function testSessionRestartLosesAuthentication() { + $this->get($this->samples() . 'protected/'); + $this->authenticate('test', 'secret'); + $this->assertResponse(200); + $this->restart(); + $this->get($this->samples() . 'protected/'); + $this->assertResponse(401); + } +} + +class TestOfLoadingFrames extends SimpleTestAcceptanceTest { + function setUp() { + $this->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + } + + function testNoFramesContentWhenFramesDisabled() { + $this->ignoreFrames(); + $this->get($this->samples() . 'one_page_frameset.html'); + $this->assertTitle('Frameset for testing of SimpleTest'); + $this->assertText('This content is for no frames only'); + } + + function testPatternMatchCanReadTheOnlyFrame() { + $this->get($this->samples() . 'one_page_frameset.html'); + $this->assertText('A target for the SimpleTest test suite'); + $this->assertNoText('This content is for no frames only'); + } + + function testMessyFramesetResponsesByName() { + $this->assertTrue($this->get( + $this->samples() . 'messy_frameset.html')); + $this->assertTitle('Frameset for testing of SimpleTest'); + + $this->assertTrue($this->setFrameFocus('Front controller')); + $this->assertResponse(200); + $this->assertText('Simple test front controller'); + + $this->assertTrue($this->setFrameFocus('One')); + $this->assertResponse(200); + $this->assertLink('2'); + + $this->assertTrue($this->setFrameFocus('Frame links')); + $this->assertResponse(200); + $this->assertLink('Set one to 2'); + + $this->assertTrue($this->setFrameFocus('Counter')); + $this->assertResponse(200); + $this->assertText('Count: 1'); + + $this->assertTrue($this->setFrameFocus('Redirected')); + $this->assertResponse(200); + $this->assertText('r=rrr'); + + $this->assertTrue($this->setFrameFocus('Protected')); + $this->assertResponse(401); + + $this->assertTrue($this->setFrameFocus('Protected redirect')); + $this->assertResponse(401); + + $this->assertTrue($this->setFrameFocusByIndex(1)); + $this->assertResponse(200); + $this->assertText('Simple test front controller'); + + $this->assertTrue($this->setFrameFocusByIndex(2)); + $this->assertResponse(200); + $this->assertLink('2'); + + $this->assertTrue($this->setFrameFocusByIndex(3)); + $this->assertResponse(200); + $this->assertLink('Set one to 2'); + + $this->assertTrue($this->setFrameFocusByIndex(4)); + $this->assertResponse(200); + $this->assertText('Count: 1'); + + $this->assertTrue($this->setFrameFocusByIndex(5)); + $this->assertResponse(200); + $this->assertText('r=rrr'); + + $this->assertTrue($this->setFrameFocusByIndex(6)); + $this->assertResponse(401); + + $this->assertTrue($this->setFrameFocusByIndex(7)); + } + + function testReloadingFramesetPage() { + $this->get($this->samples() . 'messy_frameset.html'); + $this->assertText('Count: 1'); + $this->retry(); + $this->assertText('Count: 2'); + $this->retry(); + $this->assertText('Count: 3'); + } + + function testReloadingSingleFrameWithCookieCounter() { + $this->get($this->samples() . 'counting_frameset.html'); + $this->setFrameFocus('a'); + $this->assertText('Count: 1'); + $this->setFrameFocus('b'); + $this->assertText('Count: 2'); + + $this->setFrameFocus('a'); + $this->retry(); + $this->assertText('Count: 3'); + $this->retry(); + $this->assertText('Count: 4'); + $this->setFrameFocus('b'); + $this->assertText('Count: 2'); + } + + function testReloadingFrameWhenUnfocusedReloadsWholeFrameset() { + $this->get($this->samples() . 'counting_frameset.html'); + $this->setFrameFocus('a'); + $this->assertText('Count: 1'); + $this->setFrameFocus('b'); + $this->assertText('Count: 2'); + + $this->clearFrameFocus('a'); + $this->retry(); + + $this->assertTitle('Frameset for testing of SimpleTest'); + $this->setFrameFocus('a'); + $this->assertText('Count: 3'); + $this->setFrameFocus('b'); + $this->assertText('Count: 4'); + } + + function testClickingNormalLinkReplacesJustThatFrame() { + $this->get($this->samples() . 'messy_frameset.html'); + $this->clickLink('2'); + $this->assertLink('3'); + $this->assertText('Simple test front controller'); + } + + function testJumpToNamedPageReplacesJustThatFrame() { + $this->get($this->samples() . 'messy_frameset.html'); + $this->assertPattern('/Simple test front controller/'); + $this->clickLink('Index'); + $this->assertResponse(200); + $this->assertText('[action=index]'); + $this->assertText('Count: 1'); + } + + function testJumpToUnnamedPageReplacesJustThatFrame() { + $this->get($this->samples() . 'messy_frameset.html'); + $this->clickLink('No page'); + $this->assertResponse(200); + $this->assertText('Simple test front controller'); + $this->assertText('[action=no_page]'); + $this->assertText('Count: 1'); + } + + function testJumpToUnnamedPageWithBareParameterReplacesJustThatFrame() { + $this->get($this->samples() . 'messy_frameset.html'); + $this->clickLink('Bare action'); + $this->assertResponse(200); + $this->assertText('Simple test front controller'); + $this->assertText('[action=]'); + $this->assertText('Count: 1'); + } + + function testJumpToUnnamedPageWithEmptyQueryReplacesJustThatFrame() { + $this->get($this->samples() . 'messy_frameset.html'); + $this->clickLink('Empty query'); + $this->assertResponse(200); + $this->assertPattern('/Simple test front controller/'); + $this->assertPattern('/raw get data.*?\[\].*?get data/si'); + $this->assertPattern('/Count: 1/'); + } + + function testJumpToUnnamedPageWithEmptyLinkReplacesJustThatFrame() { + $this->get($this->samples() . 'messy_frameset.html'); + $this->clickLink('Empty link'); + $this->assertResponse(200); + $this->assertPattern('/Simple test front controller/'); + $this->assertPattern('/raw get data.*?\[\].*?get data/si'); + $this->assertPattern('/Count: 1/'); + } + + function testJumpBackADirectoryLevelReplacesJustThatFrame() { + $this->get($this->samples() . 'messy_frameset.html'); + $this->clickLink('Down one'); + $this->assertPattern('/index of .*\/test/i'); + $this->assertPattern('/Count: 1/'); + } + + function testSubmitToNamedPageReplacesJustThatFrame() { + $this->get($this->samples() . 'messy_frameset.html'); + $this->assertPattern('/Simple test front controller/'); + $this->clickSubmit('Index'); + $this->assertResponse(200); + $this->assertText('[action=Index]'); + $this->assertText('Count: 1'); + } + + function testSubmitToSameDirectoryReplacesJustThatFrame() { + $this->get($this->samples() . 'messy_frameset.html'); + $this->clickSubmit('Same directory'); + $this->assertResponse(200); + $this->assertText('[action=Same+directory]'); + $this->assertText('Count: 1'); + } + + function testSubmitToEmptyActionReplacesJustThatFrame() { + $this->get($this->samples() . 'messy_frameset.html'); + $this->clickSubmit('Empty action'); + $this->assertResponse(200); + $this->assertText('[action=Empty+action]'); + $this->assertText('Count: 1'); + } + + function testSubmitToNoActionReplacesJustThatFrame() { + $this->get($this->samples() . 'messy_frameset.html'); + $this->clickSubmit('No action'); + $this->assertResponse(200); + $this->assertText('[action=No+action]'); + $this->assertText('Count: 1'); + } + + function testSubmitBackADirectoryLevelReplacesJustThatFrame() { + $this->get($this->samples() . 'messy_frameset.html'); + $this->clickSubmit('Down one'); + $this->assertPattern('/index of .*\/test/i'); + $this->assertPattern('/Count: 1/'); + } + + function testTopLinkExitsFrameset() { + $this->get($this->samples() . 'messy_frameset.html'); + $this->clickLink('Exit the frameset'); + $this->assertTitle('Simple test target file'); + } + + function testLinkInOnePageCanLoadAnother() { + $this->get($this->samples() . 'messy_frameset.html'); + $this->assertNoLink('3'); + $this->clickLink('Set one to 2'); + $this->assertLink('3'); + $this->assertNoLink('2'); + $this->assertTitle('Frameset for testing of SimpleTest'); + } + + function testFrameWithRelativeLinksRespectsBaseTagForThatPage() { + $this->get($this->samples() . 'base_tag/frameset.html'); + $this->click('Back to test pages'); + $this->assertTitle('Frameset for testing of SimpleTest'); + $this->assertText('A target for the SimpleTest test suite'); + } + + function testRelativeLinkInFrameIsNotAffectedByFramesetBaseTag() { + $this->get($this->samples() . 'base_tag/frameset_with_base_tag.html'); + $this->assertText('This is page 1'); + $this->click('To page 2'); + $this->assertTitle('Frameset for testing of SimpleTest'); + $this->assertText('This is page 2'); + } +} + +class TestOfFrameAuthentication extends SimpleTestAcceptanceTest { + function setUp() { + $this->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + } + + function testUnauthenticatedFrameSendsChallenge() { + $this->get($this->samples() . 'protected/'); + $this->setFrameFocus('Protected'); + $this->assertAuthentication('Basic'); + $this->assertRealm('SimpleTest basic authentication'); + $this->assertResponse(401); + } + + function testCanReadFrameFromAlreadyAuthenticatedRealm() { + $this->get($this->samples() . 'protected/'); + $this->authenticate('test', 'secret'); + $this->get($this->samples() . 'messy_frameset.html'); + $this->setFrameFocus('Protected'); + $this->assertResponse(200); + $this->assertText('A target for the SimpleTest test suite'); + } + + function testCanAuthenticateFrame() { + $this->get($this->samples() . 'messy_frameset.html'); + $this->setFrameFocus('Protected'); + $this->authenticate('test', 'secret'); + $this->assertResponse(200); + $this->assertText('A target for the SimpleTest test suite'); + $this->clearFrameFocus(); + $this->assertText('Count: 1'); + } + + function testCanAuthenticateRedirectedFrame() { + $this->get($this->samples() . 'messy_frameset.html'); + $this->setFrameFocus('Protected redirect'); + $this->assertResponse(401); + $this->authenticate('test', 'secret'); + $this->assertResponse(200); + $this->assertText('A target for the SimpleTest test suite'); + $this->clearFrameFocus(); + $this->assertText('Count: 1'); + } +} + +class TestOfNestedFrames extends SimpleTestAcceptanceTest { + function setUp() { + $this->addHeader('User-Agent: SimpleTest ' . SimpleTest::getVersion()); + } + + function testCanNavigateToSpecificContent() { + $this->get($this->samples() . 'nested_frameset.html'); + $this->assertTitle('Nested frameset for testing of SimpleTest'); + + $this->assertPattern('/This is frame A/'); + $this->assertPattern('/This is frame B/'); + $this->assertPattern('/Simple test front controller/'); + $this->assertLink('2'); + $this->assertLink('Set one to 2'); + $this->assertPattern('/Count: 1/'); + $this->assertPattern('/r=rrr/'); + + $this->setFrameFocus('pair'); + $this->assertPattern('/This is frame A/'); + $this->assertPattern('/This is frame B/'); + $this->assertNoPattern('/Simple test front controller/'); + $this->assertNoLink('2'); + + $this->setFrameFocus('aaa'); + $this->assertPattern('/This is frame A/'); + $this->assertNoPattern('/This is frame B/'); + + $this->clearFrameFocus(); + $this->assertResponse(200); + $this->setFrameFocus('messy'); + $this->assertResponse(200); + $this->setFrameFocus('Front controller'); + $this->assertResponse(200); + $this->assertPattern('/Simple test front controller/'); + $this->assertNoLink('2'); + } + + function testReloadingFramesetPage() { + $this->get($this->samples() . 'nested_frameset.html'); + $this->assertPattern('/Count: 1/'); + $this->retry(); + $this->assertPattern('/Count: 2/'); + $this->retry(); + $this->assertPattern('/Count: 3/'); + } + + function testRetryingNestedPageOnlyRetriesThatSet() { + $this->get($this->samples() . 'nested_frameset.html'); + $this->assertPattern('/Count: 1/'); + $this->setFrameFocus('messy'); + $this->retry(); + $this->assertPattern('/Count: 2/'); + $this->setFrameFocus('Counter'); + $this->retry(); + $this->assertPattern('/Count: 3/'); + + $this->clearFrameFocus(); + $this->setFrameFocus('messy'); + $this->setFrameFocus('Front controller'); + $this->retry(); + + $this->clearFrameFocus(); + $this->assertPattern('/Count: 3/'); + } + + function testAuthenticatingNestedPage() { + $this->get($this->samples() . 'nested_frameset.html'); + $this->setFrameFocus('messy'); + $this->setFrameFocus('Protected'); + $this->assertAuthentication('Basic'); + $this->assertRealm('SimpleTest basic authentication'); + $this->assertResponse(401); + + $this->authenticate('test', 'secret'); + $this->assertResponse(200); + $this->assertPattern('/A target for the SimpleTest test suite/'); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/adapter_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/adapter_test.php new file mode 100644 index 0000000..c1a06a2 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/adapter_test.php @@ -0,0 +1,50 @@ +assertTrue(true, "PEAR true"); + $this->assertFalse(false, "PEAR false"); + } + + function testName() { + $this->assertTrue($this->getName() == get_class($this)); + } + + function testPass() { + $this->pass("PEAR pass"); + } + + function testNulls() { + $value = null; + $this->assertNull($value, "PEAR null"); + $value = 0; + $this->assertNotNull($value, "PEAR not null"); + } + + function testType() { + $this->assertType("Hello", "string", "PEAR type"); + } + + function testEquals() { + $this->assertEquals(12, 12, "PEAR identity"); + $this->setLooselyTyped(true); + $this->assertEquals("12", 12, "PEAR equality"); + } + + function testSame() { + $same = new SameTestClass(); + $this->assertSame($same, $same, "PEAR same"); + } + + function testRegExp() { + $this->assertRegExp('/hello/', "A big hello from me", "PEAR regex"); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/all_tests.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/all_tests.php new file mode 100644 index 0000000..99ce945 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/all_tests.php @@ -0,0 +1,13 @@ +TestSuite('All tests for SimpleTest ' . SimpleTest::getVersion()); + $this->addFile(dirname(__FILE__) . '/unit_tests.php'); + $this->addFile(dirname(__FILE__) . '/shell_test.php'); + $this->addFile(dirname(__FILE__) . '/live_test.php'); + $this->addFile(dirname(__FILE__) . '/acceptance_test.php'); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/authentication_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/authentication_test.php new file mode 100644 index 0000000..081cccd --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/authentication_test.php @@ -0,0 +1,145 @@ +assertTrue($realm->isWithin( + new SimpleUrl('http://www.here.com/path/hello.html'))); + } + + function testInsideWithLongerUrl() { + $realm = new SimpleRealm( + 'Basic', + new SimpleUrl('http://www.here.com/path/')); + $this->assertTrue($realm->isWithin( + new SimpleUrl('http://www.here.com/path/hello.html'))); + } + + function testBelowRootIsOutside() { + $realm = new SimpleRealm( + 'Basic', + new SimpleUrl('http://www.here.com/path/')); + $this->assertTrue($realm->isWithin( + new SimpleUrl('http://www.here.com/path/more/hello.html'))); + } + + function testOldNetscapeDefinitionIsOutside() { + $realm = new SimpleRealm( + 'Basic', + new SimpleUrl('http://www.here.com/path/')); + $this->assertFalse($realm->isWithin( + new SimpleUrl('http://www.here.com/pathmore/hello.html'))); + } + + function testInsideWithMissingTrailingSlash() { + $realm = new SimpleRealm( + 'Basic', + new SimpleUrl('http://www.here.com/path/')); + $this->assertTrue($realm->isWithin( + new SimpleUrl('http://www.here.com/path'))); + } + + function testDifferentPageNameStillInside() { + $realm = new SimpleRealm( + 'Basic', + new SimpleUrl('http://www.here.com/path/hello.html')); + $this->assertTrue($realm->isWithin( + new SimpleUrl('http://www.here.com/path/goodbye.html'))); + } + + function testNewUrlInSameDirectoryDoesNotChangeRealm() { + $realm = new SimpleRealm( + 'Basic', + new SimpleUrl('http://www.here.com/path/hello.html')); + $realm->stretch(new SimpleUrl('http://www.here.com/path/goodbye.html')); + $this->assertTrue($realm->isWithin( + new SimpleUrl('http://www.here.com/path/index.html'))); + $this->assertFalse($realm->isWithin( + new SimpleUrl('http://www.here.com/index.html'))); + } + + function testNewUrlMakesRealmTheCommonPath() { + $realm = new SimpleRealm( + 'Basic', + new SimpleUrl('http://www.here.com/path/here/hello.html')); + $realm->stretch(new SimpleUrl('http://www.here.com/path/there/goodbye.html')); + $this->assertTrue($realm->isWithin( + new SimpleUrl('http://www.here.com/path/here/index.html'))); + $this->assertTrue($realm->isWithin( + new SimpleUrl('http://www.here.com/path/there/index.html'))); + $this->assertTrue($realm->isWithin( + new SimpleUrl('http://www.here.com/path/index.html'))); + $this->assertFalse($realm->isWithin( + new SimpleUrl('http://www.here.com/index.html'))); + $this->assertFalse($realm->isWithin( + new SimpleUrl('http://www.here.com/paths/index.html'))); + $this->assertFalse($realm->isWithin( + new SimpleUrl('http://www.here.com/pathindex.html'))); + } +} + +class TestOfAuthenticator extends UnitTestCase { + + function testNoRealms() { + $request = new MockSimpleHttpRequest(); + $request->expectNever('addHeaderLine'); + $authenticator = new SimpleAuthenticator(); + $authenticator->addHeaders($request, new SimpleUrl('http://here.com/')); + } + + function &createSingleRealm() { + $authenticator = new SimpleAuthenticator(); + $authenticator->addRealm( + new SimpleUrl('http://www.here.com/path/hello.html'), + 'Basic', + 'Sanctuary'); + $authenticator->setIdentityForRealm('www.here.com', 'Sanctuary', 'test', 'secret'); + return $authenticator; + } + + function testOutsideRealm() { + $request = new MockSimpleHttpRequest(); + $request->expectNever('addHeaderLine'); + $authenticator = &$this->createSingleRealm(); + $authenticator->addHeaders( + $request, + new SimpleUrl('http://www.here.com/hello.html')); + } + + function testWithinRealm() { + $request = new MockSimpleHttpRequest(); + $request->expectOnce('addHeaderLine'); + $authenticator = &$this->createSingleRealm(); + $authenticator->addHeaders( + $request, + new SimpleUrl('http://www.here.com/path/more/hello.html')); + } + + function testRestartingClearsRealm() { + $request = new MockSimpleHttpRequest(); + $request->expectNever('addHeaderLine'); + $authenticator = &$this->createSingleRealm(); + $authenticator->restartSession(); + $authenticator->addHeaders( + $request, + new SimpleUrl('http://www.here.com/hello.html')); + } + + function testDifferentHostIsOutsideRealm() { + $request = new MockSimpleHttpRequest(); + $request->expectNever('addHeaderLine'); + $authenticator = &$this->createSingleRealm(); + $authenticator->addHeaders( + $request, + new SimpleUrl('http://here.com/path/hello.html')); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/autorun_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/autorun_test.php new file mode 100644 index 0000000..8f7ff81 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/autorun_test.php @@ -0,0 +1,13 @@ +addFile(dirname(__FILE__) . '/support/test1.php'); + $this->assertEqual($tests->getSize(), 1); + } +} + +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/bad_test_suite.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/bad_test_suite.php new file mode 100644 index 0000000..b426013 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/bad_test_suite.php @@ -0,0 +1,10 @@ +TestSuite('Two bad test cases'); + $this->addFile(dirname(__FILE__) . '/support/empty_test_file.php'); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/browser_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/browser_test.php new file mode 100644 index 0000000..eabe091 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/browser_test.php @@ -0,0 +1,774 @@ +assertIdentical($history->getUrl(), false); + $this->assertIdentical($history->getParameters(), false); + } + + function testCannotMoveInEmptyHistory() { + $history = new SimpleBrowserHistory(); + $this->assertFalse($history->back()); + $this->assertFalse($history->forward()); + } + + function testCurrentTargetAccessors() { + $history = new SimpleBrowserHistory(); + $history->recordEntry( + new SimpleUrl('http://www.here.com/'), + new SimpleGetEncoding()); + $this->assertIdentical($history->getUrl(), new SimpleUrl('http://www.here.com/')); + $this->assertIdentical($history->getParameters(), new SimpleGetEncoding()); + } + + function testSecondEntryAccessors() { + $history = new SimpleBrowserHistory(); + $history->recordEntry( + new SimpleUrl('http://www.first.com/'), + new SimpleGetEncoding()); + $history->recordEntry( + new SimpleUrl('http://www.second.com/'), + new SimplePostEncoding(array('a' => 1))); + $this->assertIdentical($history->getUrl(), new SimpleUrl('http://www.second.com/')); + $this->assertIdentical( + $history->getParameters(), + new SimplePostEncoding(array('a' => 1))); + } + + function testGoingBackwards() { + $history = new SimpleBrowserHistory(); + $history->recordEntry( + new SimpleUrl('http://www.first.com/'), + new SimpleGetEncoding()); + $history->recordEntry( + new SimpleUrl('http://www.second.com/'), + new SimplePostEncoding(array('a' => 1))); + $this->assertTrue($history->back()); + $this->assertIdentical($history->getUrl(), new SimpleUrl('http://www.first.com/')); + $this->assertIdentical($history->getParameters(), new SimpleGetEncoding()); + } + + function testGoingBackwardsOffBeginning() { + $history = new SimpleBrowserHistory(); + $history->recordEntry( + new SimpleUrl('http://www.first.com/'), + new SimpleGetEncoding()); + $this->assertFalse($history->back()); + $this->assertIdentical($history->getUrl(), new SimpleUrl('http://www.first.com/')); + $this->assertIdentical($history->getParameters(), new SimpleGetEncoding()); + } + + function testGoingForwardsOffEnd() { + $history = new SimpleBrowserHistory(); + $history->recordEntry( + new SimpleUrl('http://www.first.com/'), + new SimpleGetEncoding()); + $this->assertFalse($history->forward()); + $this->assertIdentical($history->getUrl(), new SimpleUrl('http://www.first.com/')); + $this->assertIdentical($history->getParameters(), new SimpleGetEncoding()); + } + + function testGoingBackwardsAndForwards() { + $history = new SimpleBrowserHistory(); + $history->recordEntry( + new SimpleUrl('http://www.first.com/'), + new SimpleGetEncoding()); + $history->recordEntry( + new SimpleUrl('http://www.second.com/'), + new SimplePostEncoding(array('a' => 1))); + $this->assertTrue($history->back()); + $this->assertTrue($history->forward()); + $this->assertIdentical($history->getUrl(), new SimpleUrl('http://www.second.com/')); + $this->assertIdentical( + $history->getParameters(), + new SimplePostEncoding(array('a' => 1))); + } + + function testNewEntryReplacesNextOne() { + $history = new SimpleBrowserHistory(); + $history->recordEntry( + new SimpleUrl('http://www.first.com/'), + new SimpleGetEncoding()); + $history->recordEntry( + new SimpleUrl('http://www.second.com/'), + new SimplePostEncoding(array('a' => 1))); + $history->back(); + $history->recordEntry( + new SimpleUrl('http://www.third.com/'), + new SimpleGetEncoding()); + $this->assertIdentical($history->getUrl(), new SimpleUrl('http://www.third.com/')); + $this->assertIdentical($history->getParameters(), new SimpleGetEncoding()); + } + + function testNewEntryDropsFutureEntries() { + $history = new SimpleBrowserHistory(); + $history->recordEntry( + new SimpleUrl('http://www.first.com/'), + new SimpleGetEncoding()); + $history->recordEntry( + new SimpleUrl('http://www.second.com/'), + new SimpleGetEncoding()); + $history->recordEntry( + new SimpleUrl('http://www.third.com/'), + new SimpleGetEncoding()); + $history->back(); + $history->back(); + $history->recordEntry( + new SimpleUrl('http://www.fourth.com/'), + new SimpleGetEncoding()); + $this->assertIdentical($history->getUrl(), new SimpleUrl('http://www.fourth.com/')); + $this->assertFalse($history->forward()); + $history->back(); + $this->assertIdentical($history->getUrl(), new SimpleUrl('http://www.first.com/')); + $this->assertFalse($history->back()); + } +} + +class TestOfParsedPageAccess extends UnitTestCase { + + function loadPage(&$page) { + $response = new MockSimpleHttpResponse($this); + $agent = new MockSimpleUserAgent($this); + $agent->returns('fetchResponse', $response); + + $browser = new MockParseSimpleBrowser($this); + $browser->returns('createUserAgent', $agent); + $browser->returns('parse', $page); + $browser->__construct(); + + $browser->get('http://this.com/page.html'); + return $browser; + } + + function testAccessorsWhenNoPage() { + $agent = new MockSimpleUserAgent($this); + $browser = new MockParseSimpleBrowser($this); + $browser->returns('createUserAgent', $agent); + $browser->__construct(); + $this->assertEqual($browser->getContent(), ''); + } + + function testParse() { + $page = new MockSimplePage(); + $page->setReturnValue('getRequest', "GET here.html\r\n\r\n"); + $page->setReturnValue('getRaw', 'Raw HTML'); + $page->setReturnValue('getTitle', 'Here'); + $page->setReturnValue('getFrameFocus', 'Frame'); + $page->setReturnValue('getMimeType', 'text/html'); + $page->setReturnValue('getResponseCode', 200); + $page->setReturnValue('getAuthentication', 'Basic'); + $page->setReturnValue('getRealm', 'Somewhere'); + $page->setReturnValue('getTransportError', 'Ouch!'); + + $browser = $this->loadPage($page); + $this->assertEqual($browser->getRequest(), "GET here.html\r\n\r\n"); + $this->assertEqual($browser->getContent(), 'Raw HTML'); + $this->assertEqual($browser->getTitle(), 'Here'); + $this->assertEqual($browser->getFrameFocus(), 'Frame'); + $this->assertIdentical($browser->getResponseCode(), 200); + $this->assertEqual($browser->getMimeType(), 'text/html'); + $this->assertEqual($browser->getAuthentication(), 'Basic'); + $this->assertEqual($browser->getRealm(), 'Somewhere'); + $this->assertEqual($browser->getTransportError(), 'Ouch!'); + } + + function testLinkAffirmationWhenPresent() { + $page = new MockSimplePage(); + $page->setReturnValue('getUrlsByLabel', array('http://www.nowhere.com')); + $page->expectOnce('getUrlsByLabel', array('a link label')); + $browser = $this->loadPage($page); + $this->assertIdentical($browser->getLink('a link label'), 'http://www.nowhere.com'); + } + + function testLinkAffirmationByIdWhenPresent() { + $page = new MockSimplePage(); + $page->setReturnValue('getUrlById', 'a_page.com', array(99)); + $page->setReturnValue('getUrlById', false, array('*')); + $browser = $this->loadPage($page); + $this->assertIdentical($browser->getLinkById(99), 'a_page.com'); + $this->assertFalse($browser->getLinkById(98)); + } + + function testSettingFieldIsPassedToPage() { + $page = new MockSimplePage(); + $page->expectOnce('setField', array(new SimpleByLabelOrName('key'), 'Value', false)); + $page->setReturnValue('getField', 'Value'); + $browser = $this->loadPage($page); + $this->assertEqual($browser->getField('key'), 'Value'); + $browser->setField('key', 'Value'); + } +} + +class TestOfBrowserNavigation extends UnitTestCase { + function createBrowser($agent, $page) { + $browser = new MockParseSimpleBrowser(); + $browser->returns('createUserAgent', $agent); + $browser->returns('parse', $page); + $browser->__construct(); + return $browser; + } + + function testClickLinkRequestsPage() { + $agent = new MockSimpleUserAgent(); + $agent->returns('fetchResponse', new MockSimpleHttpResponse()); + $agent->expectAt( + 0, + 'fetchResponse', + array(new SimpleUrl('http://this.com/page.html'), new SimpleGetEncoding())); + $agent->expectAt( + 1, + 'fetchResponse', + array(new SimpleUrl('http://this.com/new.html'), new SimpleGetEncoding())); + $agent->expectCallCount('fetchResponse', 2); + + $page = new MockSimplePage(); + $page->setReturnValue('getUrlsByLabel', array(new SimpleUrl('http://this.com/new.html'))); + $page->expectOnce('getUrlsByLabel', array('New')); + $page->setReturnValue('getRaw', 'A page'); + + $browser = $this->createBrowser($agent, $page); + $browser->get('http://this.com/page.html'); + $this->assertTrue($browser->clickLink('New')); + } + + function testClickLinkWithUnknownFrameStillRequestsWholePage() { + $agent = new MockSimpleUserAgent(); + $agent->returns('fetchResponse', new MockSimpleHttpResponse()); + $agent->expectAt( + 0, + 'fetchResponse', + array(new SimpleUrl('http://this.com/page.html'), new SimpleGetEncoding())); + $target = new SimpleUrl('http://this.com/new.html'); + $target->setTarget('missing'); + $agent->expectAt( + 1, + 'fetchResponse', + array($target, new SimpleGetEncoding())); + $agent->expectCallCount('fetchResponse', 2); + + $parsed_url = new SimpleUrl('http://this.com/new.html'); + $parsed_url->setTarget('missing'); + + $page = new MockSimplePage(); + $page->setReturnValue('getUrlsByLabel', array($parsed_url)); + $page->setReturnValue('hasFrames', false); + $page->expectOnce('getUrlsByLabel', array('New')); + $page->setReturnValue('getRaw', 'A page'); + + $browser = $this->createBrowser($agent, $page); + $browser->get('http://this.com/page.html'); + $this->assertTrue($browser->clickLink('New')); + } + + function testClickingMissingLinkFails() { + $agent = new MockSimpleUserAgent($this); + $agent->returns('fetchResponse', new MockSimpleHttpResponse()); + + $page = new MockSimplePage(); + $page->setReturnValue('getUrlsByLabel', array()); + $page->setReturnValue('getRaw', 'stuff'); + + $browser = $this->createBrowser($agent, $page); + $this->assertTrue($browser->get('http://this.com/page.html')); + $this->assertFalse($browser->clickLink('New')); + } + + function testClickIndexedLink() { + $agent = new MockSimpleUserAgent(); + $agent->returns('fetchResponse', new MockSimpleHttpResponse()); + $agent->expectAt( + 1, + 'fetchResponse', + array(new SimpleUrl('1.html'), new SimpleGetEncoding())); + $agent->expectCallCount('fetchResponse', 2); + + $page = new MockSimplePage(); + $page->setReturnValue( + 'getUrlsByLabel', + array(new SimpleUrl('0.html'), new SimpleUrl('1.html'))); + $page->setReturnValue('getRaw', 'A page'); + + $browser = $this->createBrowser($agent, $page); + $browser->get('http://this.com/page.html'); + $this->assertTrue($browser->clickLink('New', 1)); + } + + function testClinkLinkById() { + $agent = new MockSimpleUserAgent(); + $agent->returns('fetchResponse', new MockSimpleHttpResponse()); + $agent->expectAt(1, 'fetchResponse', array( + new SimpleUrl('http://this.com/link.html'), + new SimpleGetEncoding())); + $agent->expectCallCount('fetchResponse', 2); + + $page = new MockSimplePage(); + $page->setReturnValue('getUrlById', new SimpleUrl('http://this.com/link.html')); + $page->expectOnce('getUrlById', array(2)); + $page->setReturnValue('getRaw', 'A page'); + + $browser = $this->createBrowser($agent, $page); + $browser->get('http://this.com/page.html'); + $this->assertTrue($browser->clickLinkById(2)); + } + + function testClickingMissingLinkIdFails() { + $agent = new MockSimpleUserAgent(); + $agent->returns('fetchResponse', new MockSimpleHttpResponse()); + + $page = new MockSimplePage(); + $page->setReturnValue('getUrlById', false); + + $browser = $this->createBrowser($agent, $page); + $browser->get('http://this.com/page.html'); + $this->assertFalse($browser->clickLink(0)); + } + + function testSubmitFormByLabel() { + $agent = new MockSimpleUserAgent(); + $agent->returns('fetchResponse', new MockSimpleHttpResponse()); + $agent->expectAt(1, 'fetchResponse', array( + new SimpleUrl('http://this.com/handler.html'), + new SimplePostEncoding(array('a' => 'A')))); + $agent->expectCallCount('fetchResponse', 2); + + $form = new MockSimpleForm(); + $form->setReturnValue('getAction', new SimpleUrl('http://this.com/handler.html')); + $form->setReturnValue('getMethod', 'post'); + $form->setReturnValue('submitButton', new SimplePostEncoding(array('a' => 'A'))); + $form->expectOnce('submitButton', array(new SimpleByLabel('Go'), false)); + + $page = new MockSimplePage(); + $page->returns('getFormBySubmit', $form); + $page->expectOnce('getFormBySubmit', array(new SimpleByLabel('Go'))); + $page->setReturnValue('getRaw', 'stuff'); + + $browser = $this->createBrowser($agent, $page); + $browser->get('http://this.com/page.html'); + $this->assertTrue($browser->clickSubmit('Go')); + } + + function testDefaultSubmitFormByLabel() { + $agent = new MockSimpleUserAgent(); + $agent->returns('fetchResponse', new MockSimpleHttpResponse()); + $agent->expectAt(1, 'fetchResponse', array( + new SimpleUrl('http://this.com/page.html'), + new SimpleGetEncoding(array('a' => 'A')))); + $agent->expectCallCount('fetchResponse', 2); + + $form = new MockSimpleForm(); + $form->setReturnValue('getAction', new SimpleUrl('http://this.com/page.html')); + $form->setReturnValue('getMethod', 'get'); + $form->setReturnValue('submitButton', new SimpleGetEncoding(array('a' => 'A'))); + + $page = new MockSimplePage(); + $page->returns('getFormBySubmit', $form); + $page->expectOnce('getFormBySubmit', array(new SimpleByLabel('Submit'))); + $page->setReturnValue('getRaw', 'stuff'); + $page->setReturnValue('getUrl', new SimpleUrl('http://this.com/page.html')); + + $browser = $this->createBrowser($agent, $page); + $browser->get('http://this.com/page.html'); + $this->assertTrue($browser->clickSubmit()); + } + + function testSubmitFormByName() { + $agent = new MockSimpleUserAgent(); + $agent->returns('fetchResponse', new MockSimpleHttpResponse()); + + $form = new MockSimpleForm(); + $form->setReturnValue('getAction', new SimpleUrl('http://this.com/handler.html')); + $form->setReturnValue('getMethod', 'post'); + $form->setReturnValue('submitButton', new SimplePostEncoding(array('a' => 'A'))); + + $page = new MockSimplePage(); + $page->returns('getFormBySubmit', $form); + $page->expectOnce('getFormBySubmit', array(new SimpleByName('me'))); + $page->setReturnValue('getRaw', 'stuff'); + + $browser = $this->createBrowser($agent, $page); + $browser->get('http://this.com/page.html'); + $this->assertTrue($browser->clickSubmitByName('me')); + } + + function testSubmitFormById() { + $agent = new MockSimpleUserAgent(); + $agent->returns('fetchResponse', new MockSimpleHttpResponse()); + + $form = new MockSimpleForm(); + $form->setReturnValue('getAction', new SimpleUrl('http://this.com/handler.html')); + $form->setReturnValue('getMethod', 'post'); + $form->setReturnValue('submitButton', new SimplePostEncoding(array('a' => 'A'))); + $form->expectOnce('submitButton', array(new SimpleById(99), false)); + + $page = new MockSimplePage(); + $page->returns('getFormBySubmit', $form); + $page->expectOnce('getFormBySubmit', array(new SimpleById(99))); + $page->setReturnValue('getRaw', 'stuff'); + + $browser = $this->createBrowser($agent, $page); + $browser->get('http://this.com/page.html'); + $this->assertTrue($browser->clickSubmitById(99)); + } + + function testSubmitFormByImageLabel() { + $agent = new MockSimpleUserAgent(); + $agent->returns('fetchResponse', new MockSimpleHttpResponse()); + + $form = new MockSimpleForm(); + $form->setReturnValue('getAction', new SimpleUrl('http://this.com/handler.html')); + $form->setReturnValue('getMethod', 'post'); + $form->setReturnValue('submitImage', new SimplePostEncoding(array('a' => 'A'))); + $form->expectOnce('submitImage', array(new SimpleByLabel('Go!'), 10, 11, false)); + + $page = new MockSimplePage(); + $page->returns('getFormByImage', $form); + $page->expectOnce('getFormByImage', array(new SimpleByLabel('Go!'))); + $page->setReturnValue('getRaw', 'stuff'); + + $browser = $this->createBrowser($agent, $page); + $browser->get('http://this.com/page.html'); + $this->assertTrue($browser->clickImage('Go!', 10, 11)); + } + + function testSubmitFormByImageName() { + $agent = new MockSimpleUserAgent(); + $agent->returns('fetchResponse', new MockSimpleHttpResponse()); + + $form = new MockSimpleForm(); + $form->setReturnValue('getAction', new SimpleUrl('http://this.com/handler.html')); + $form->setReturnValue('getMethod', 'post'); + $form->setReturnValue('submitImage', new SimplePostEncoding(array('a' => 'A'))); + $form->expectOnce('submitImage', array(new SimpleByName('a'), 10, 11, false)); + + $page = new MockSimplePage(); + $page->returns('getFormByImage', $form); + $page->expectOnce('getFormByImage', array(new SimpleByName('a'))); + $page->setReturnValue('getRaw', 'stuff'); + + $browser = $this->createBrowser($agent, $page); + $browser->get('http://this.com/page.html'); + $this->assertTrue($browser->clickImageByName('a', 10, 11)); + } + + function testSubmitFormByImageId() { + $agent = new MockSimpleUserAgent(); + $agent->returns('fetchResponse', new MockSimpleHttpResponse()); + + $form = new MockSimpleForm(); + $form->setReturnValue('getAction', new SimpleUrl('http://this.com/handler.html')); + $form->setReturnValue('getMethod', 'post'); + $form->setReturnValue('submitImage', new SimplePostEncoding(array('a' => 'A'))); + $form->expectOnce('submitImage', array(new SimpleById(99), 10, 11, false)); + + $page = new MockSimplePage(); + $page->returns('getFormByImage', $form); + $page->expectOnce('getFormByImage', array(new SimpleById(99))); + $page->setReturnValue('getRaw', 'stuff'); + + $browser = $this->createBrowser($agent, $page); + $browser->get('http://this.com/page.html'); + $this->assertTrue($browser->clickImageById(99, 10, 11)); + } + + function testSubmitFormByFormId() { + $agent = new MockSimpleUserAgent(); + $agent->returns('fetchResponse', new MockSimpleHttpResponse()); + $agent->expectAt(1, 'fetchResponse', array( + new SimpleUrl('http://this.com/handler.html'), + new SimplePostEncoding(array('a' => 'A')))); + $agent->expectCallCount('fetchResponse', 2); + + $form = new MockSimpleForm(); + $form->setReturnValue('getAction', new SimpleUrl('http://this.com/handler.html')); + $form->setReturnValue('getMethod', 'post'); + $form->setReturnValue('submit', new SimplePostEncoding(array('a' => 'A'))); + + $page = new MockSimplePage(); + $page->returns('getFormById', $form); + $page->expectOnce('getFormById', array(33)); + $page->setReturnValue('getRaw', 'stuff'); + + $browser = $this->createBrowser($agent, $page); + $browser->get('http://this.com/page.html'); + $this->assertTrue($browser->submitFormById(33)); + } +} + +class TestOfBrowserFrames extends UnitTestCase { + + function createBrowser($agent) { + $browser = new MockUserAgentSimpleBrowser(); + $browser->returns('createUserAgent', $agent); + $browser->__construct(); + return $browser; + } + + function createUserAgent($pages) { + $agent = new MockSimpleUserAgent(); + foreach ($pages as $url => $raw) { + $url = new SimpleUrl($url); + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getUrl', $url); + $response->setReturnValue('getContent', $raw); + $agent->returns('fetchResponse', $response, array($url, '*')); + } + return $agent; + } + + function testSimplePageHasNoFrames() { + $browser = $this->createBrowser($this->createUserAgent( + array('http://site.with.no.frames/' => 'A non-framed page'))); + $this->assertEqual( + $browser->get('http://site.with.no.frames/'), + 'A non-framed page'); + $this->assertIdentical($browser->getFrames(), 'http://site.with.no.frames/'); + } + + function testFramesetWithNoFrames() { + $browser = $this->createBrowser($this->createUserAgent( + array('http://site.with.no.frames/' => ''))); + $this->assertEqual($browser->get('http://site.with.no.frames/'), ''); + $this->assertIdentical($browser->getFrames(), array()); + } + + function testFramesetWithSingleFrame() { + $frameset = ''; + $browser = $this->createBrowser($this->createUserAgent(array( + 'http://site.with.one.frame/' => $frameset, + 'http://site.with.one.frame/frame.html' => 'A frame'))); + $this->assertEqual($browser->get('http://site.with.one.frame/'), 'A frame'); + $this->assertIdentical( + $browser->getFrames(), + array('a' => 'http://site.with.one.frame/frame.html')); + } + + function testTitleTakenFromFramesetPage() { + $frameset = 'Frameset title' . + ''; + $browser = $this->createBrowser($this->createUserAgent(array( + 'http://site.with.one.frame/' => $frameset, + 'http://site.with.one.frame/frame.html' => 'Page title'))); + $browser->get('http://site.with.one.frame/'); + $this->assertEqual($browser->getTitle(), 'Frameset title'); + } + + function testFramesetWithSingleUnnamedFrame() { + $frameset = ''; + $browser = $this->createBrowser($this->createUserAgent(array( + 'http://site.with.one.frame/' => $frameset, + 'http://site.with.one.frame/frame.html' => 'One frame'))); + $this->assertEqual( + $browser->get('http://site.with.one.frame/'), + 'One frame'); + $this->assertIdentical( + $browser->getFrames(), + array(1 => 'http://site.with.one.frame/frame.html')); + } + + function testFramesetWithMultipleFrames() { + $frameset = '' . + '' . + '' . + '' . + ''; + $browser = $this->createBrowser($this->createUserAgent(array( + 'http://site.with.frames/' => $frameset, + 'http://site.with.frames/frame_a.html' => 'A frame', + 'http://site.with.frames/frame_b.html' => 'B frame', + 'http://site.with.frames/frame_c.html' => 'C frame'))); + $this->assertEqual( + $browser->get('http://site.with.frames/'), + 'A frameB frameC frame'); + $this->assertIdentical($browser->getFrames(), array( + 'a' => 'http://site.with.frames/frame_a.html', + 'b' => 'http://site.with.frames/frame_b.html', + 'c' => 'http://site.with.frames/frame_c.html')); + } + + function testFrameFocusByName() { + $frameset = '' . + '' . + '' . + '' . + ''; + $browser = $this->createBrowser($this->createUserAgent(array( + 'http://site.with.frames/' => $frameset, + 'http://site.with.frames/frame_a.html' => 'A frame', + 'http://site.with.frames/frame_b.html' => 'B frame', + 'http://site.with.frames/frame_c.html' => 'C frame'))); + $browser->get('http://site.with.frames/'); + $browser->setFrameFocus('a'); + $this->assertEqual($browser->getContent(), 'A frame'); + $browser->setFrameFocus('b'); + $this->assertEqual($browser->getContent(), 'B frame'); + $browser->setFrameFocus('c'); + $this->assertEqual($browser->getContent(), 'C frame'); + } + + function testFramesetWithSomeNamedFrames() { + $frameset = '' . + '' . + '' . + '' . + '' . + ''; + $browser = $this->createBrowser($this->createUserAgent(array( + 'http://site.with.frames/' => $frameset, + 'http://site.with.frames/frame_a.html' => 'A frame', + 'http://site.with.frames/frame_b.html' => 'B frame', + 'http://site.with.frames/frame_c.html' => 'C frame', + 'http://site.with.frames/frame_d.html' => 'D frame'))); + $this->assertEqual( + $browser->get('http://site.with.frames/'), + 'A frameB frameC frameD frame'); + $this->assertIdentical($browser->getFrames(), array( + 'a' => 'http://site.with.frames/frame_a.html', + 2 => 'http://site.with.frames/frame_b.html', + 'c' => 'http://site.with.frames/frame_c.html', + 4 => 'http://site.with.frames/frame_d.html')); + } + + function testFrameFocusWithMixedNamesAndIndexes() { + $frameset = '' . + '' . + '' . + '' . + '' . + ''; + $browser = $this->createBrowser($this->createUserAgent(array( + 'http://site.with.frames/' => $frameset, + 'http://site.with.frames/frame_a.html' => 'A frame', + 'http://site.with.frames/frame_b.html' => 'B frame', + 'http://site.with.frames/frame_c.html' => 'C frame', + 'http://site.with.frames/frame_d.html' => 'D frame'))); + $browser->get('http://site.with.frames/'); + $browser->setFrameFocus('a'); + $this->assertEqual($browser->getContent(), 'A frame'); + $browser->setFrameFocus(2); + $this->assertEqual($browser->getContent(), 'B frame'); + $browser->setFrameFocus('c'); + $this->assertEqual($browser->getContent(), 'C frame'); + $browser->setFrameFocus(4); + $this->assertEqual($browser->getContent(), 'D frame'); + $browser->clearFrameFocus(); + $this->assertEqual($browser->getContent(), 'A frameB frameC frameD frame'); + } + + function testNestedFrameset() { + $inner = '' . + '' . + ''; + $outer = '' . + '' . + ''; + $browser = $this->createBrowser($this->createUserAgent(array( + 'http://site.with.nested.frame/' => $outer, + 'http://site.with.nested.frame/inner.html' => $inner, + 'http://site.with.nested.frame/page.html' => 'The page'))); + $this->assertEqual( + $browser->get('http://site.with.nested.frame/'), + 'The page'); + $this->assertIdentical($browser->getFrames(), array( + 'inner' => array( + 'page' => 'http://site.with.nested.frame/page.html'))); + } + + function testCanNavigateToNestedFrame() { + $inner = '' . + '' . + '' . + ''; + $outer = '' . + '' . + '' . + ''; + $browser = $this->createBrowser($this->createUserAgent(array( + 'http://site.with.nested.frames/' => $outer, + 'http://site.with.nested.frames/inner.html' => $inner, + 'http://site.with.nested.frames/one.html' => 'Page one', + 'http://site.with.nested.frames/two.html' => 'Page two', + 'http://site.with.nested.frames/three.html' => 'Page three'))); + + $browser->get('http://site.with.nested.frames/'); + $this->assertEqual($browser->getContent(), 'Page onePage twoPage three'); + + $this->assertTrue($browser->setFrameFocus('inner')); + $this->assertEqual($browser->getFrameFocus(), array('inner')); + $this->assertTrue($browser->setFrameFocus('one')); + $this->assertEqual($browser->getFrameFocus(), array('inner', 'one')); + $this->assertEqual($browser->getContent(), 'Page one'); + + $this->assertTrue($browser->setFrameFocus('two')); + $this->assertEqual($browser->getFrameFocus(), array('inner', 'two')); + $this->assertEqual($browser->getContent(), 'Page two'); + + $browser->clearFrameFocus(); + $this->assertTrue($browser->setFrameFocus('three')); + $this->assertEqual($browser->getFrameFocus(), array('three')); + $this->assertEqual($browser->getContent(), 'Page three'); + + $this->assertTrue($browser->setFrameFocus('inner')); + $this->assertEqual($browser->getContent(), 'Page onePage two'); + } + + function testCanNavigateToNestedFrameByIndex() { + $inner = '' . + '' . + '' . + ''; + $outer = '' . + '' . + '' . + ''; + $browser = $this->createBrowser($this->createUserAgent(array( + 'http://site.with.nested.frames/' => $outer, + 'http://site.with.nested.frames/inner.html' => $inner, + 'http://site.with.nested.frames/one.html' => 'Page one', + 'http://site.with.nested.frames/two.html' => 'Page two', + 'http://site.with.nested.frames/three.html' => 'Page three'))); + + $browser->get('http://site.with.nested.frames/'); + $this->assertEqual($browser->getContent(), 'Page onePage twoPage three'); + + $this->assertTrue($browser->setFrameFocusByIndex(1)); + $this->assertEqual($browser->getFrameFocus(), array(1)); + $this->assertTrue($browser->setFrameFocusByIndex(1)); + $this->assertEqual($browser->getFrameFocus(), array(1, 1)); + $this->assertEqual($browser->getContent(), 'Page one'); + + $this->assertTrue($browser->setFrameFocusByIndex(2)); + $this->assertEqual($browser->getFrameFocus(), array(1, 2)); + $this->assertEqual($browser->getContent(), 'Page two'); + + $browser->clearFrameFocus(); + $this->assertTrue($browser->setFrameFocusByIndex(2)); + $this->assertEqual($browser->getFrameFocus(), array(2)); + $this->assertEqual($browser->getContent(), 'Page three'); + + $this->assertTrue($browser->setFrameFocusByIndex(1)); + $this->assertEqual($browser->getContent(), 'Page onePage two'); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/collector_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/collector_test.php new file mode 100644 index 0000000..efdbf37 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/collector_test.php @@ -0,0 +1,50 @@ +expectMinimumCallCount('addFile', 2); + $suite->expect( + 'addFile', + array(new PatternExpectation('/collectable\\.(1|2)$/'))); + $collector = new SimpleCollector(); + $collector->collect($suite, dirname(__FILE__) . '/support/collector/'); + } +} + +class TestOfPatternCollector extends UnitTestCase { + + function testAddingEverythingToGroup() { + $suite = new MockTestSuite(); + $suite->expectCallCount('addFile', 2); + $suite->expect( + 'addFile', + array(new PatternExpectation('/collectable\\.(1|2)$/'))); + $collector = new SimplePatternCollector('/.*/'); + $collector->collect($suite, dirname(__FILE__) . '/support/collector/'); + } + + function testOnlyMatchedFilesAreAddedToGroup() { + $suite = new MockTestSuite(); + $suite->expectOnce('addFile', array(new PathEqualExpectation( + dirname(__FILE__) . '/support/collector/collectable.1'))); + $collector = new SimplePatternCollector('/1$/'); + $collector->collect($suite, dirname(__FILE__) . '/support/collector/'); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/command_line_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/command_line_test.php new file mode 100644 index 0000000..5baabff --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/command_line_test.php @@ -0,0 +1,40 @@ +assertIdentical($parser->getTest(), ''); + $this->assertIdentical($parser->getTestCase(), ''); + } + + function testNotXmlByDefault() { + $parser = new SimpleCommandLineParser(array()); + $this->assertFalse($parser->isXml()); + } + + function testCanDetectRequestForXml() { + $parser = new SimpleCommandLineParser(array('--xml')); + $this->assertTrue($parser->isXml()); + } + + function testCanReadAssignmentSyntax() { + $parser = new SimpleCommandLineParser(array('--test=myTest')); + $this->assertEqual($parser->getTest(), 'myTest'); + } + + function testCanReadFollowOnSyntax() { + $parser = new SimpleCommandLineParser(array('--test', 'myTest')); + $this->assertEqual($parser->getTest(), 'myTest'); + } + + function testCanReadShortForms() { + $parser = new SimpleCommandLineParser(array('-t', 'myTest', '-c', 'MyClass', '-x')); + $this->assertEqual($parser->getTest(), 'myTest'); + $this->assertEqual($parser->getTestCase(), 'MyClass'); + $this->assertTrue($parser->isXml()); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/compatibility_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/compatibility_test.php new file mode 100644 index 0000000..b8635e5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/compatibility_test.php @@ -0,0 +1,87 @@ +assertTrue(SimpleTestCompatibility::isA( + new ComparisonClass(), + 'ComparisonClass')); + $this->assertFalse(SimpleTestCompatibility::isA( + new ComparisonClass(), + 'ComparisonSubclass')); + $this->assertTrue(SimpleTestCompatibility::isA( + new ComparisonSubclass(), + 'ComparisonClass')); + } + + function testIdentityOfNumericStrings() { + $numericString1 = "123"; + $numericString2 = "00123"; + $this->assertNotIdentical($numericString1, $numericString2); + } + + function testIdentityOfObjects() { + $object1 = new ComparisonClass(); + $object2 = new ComparisonClass(); + $this->assertIdentical($object1, $object2); + } + + function testReferences () { + $thing = "Hello"; + $thing_reference = &$thing; + $thing_copy = $thing; + $this->assertTrue(SimpleTestCompatibility::isReference( + $thing, + $thing)); + $this->assertTrue(SimpleTestCompatibility::isReference( + $thing, + $thing_reference)); + $this->assertFalse(SimpleTestCompatibility::isReference( + $thing, + $thing_copy)); + } + + function testObjectReferences () { + $object = new ComparisonClass(); + $object_reference = $object; + $object_copy = new ComparisonClass(); + $object_assignment = $object; + $this->assertTrue(SimpleTestCompatibility::isReference( + $object, + $object)); + $this->assertTrue(SimpleTestCompatibility::isReference( + $object, + $object_reference)); + $this->assertFalse(SimpleTestCompatibility::isReference( + $object, + $object_copy)); + if (version_compare(phpversion(), '5', '>=')) { + $this->assertTrue(SimpleTestCompatibility::isReference( + $object, + $object_assignment)); + } else { + $this->assertFalse(SimpleTestCompatibility::isReference( + $object, + $object_assignment)); + } + } + + function testInteraceComparison() { + $object = new ComparisonClassWithInterface(); + $this->assertFalse(SimpleTestCompatibility::isA( + new ComparisonClass(), + 'ComparisonInterface')); + $this->assertTrue(SimpleTestCompatibility::isA( + new ComparisonClassWithInterface(), + 'ComparisonInterface')); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/cookies_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/cookies_test.php new file mode 100644 index 0000000..0b49e43 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/cookies_test.php @@ -0,0 +1,227 @@ +assertFalse($cookie->getValue()); + $this->assertEqual($cookie->getPath(), "/"); + $this->assertIdentical($cookie->getHost(), false); + $this->assertFalse($cookie->getExpiry()); + $this->assertFalse($cookie->isSecure()); + } + + function testCookieAccessors() { + $cookie = new SimpleCookie( + "name", + "value", + "/path", + "Mon, 18 Nov 2002 15:50:29 GMT", + true); + $this->assertEqual($cookie->getName(), "name"); + $this->assertEqual($cookie->getValue(), "value"); + $this->assertEqual($cookie->getPath(), "/path/"); + $this->assertEqual($cookie->getExpiry(), "Mon, 18 Nov 2002 15:50:29 GMT"); + $this->assertTrue($cookie->isSecure()); + } + + function testFullHostname() { + $cookie = new SimpleCookie("name"); + $this->assertTrue($cookie->setHost("host.name.here")); + $this->assertEqual($cookie->getHost(), "host.name.here"); + $this->assertTrue($cookie->setHost("host.com")); + $this->assertEqual($cookie->getHost(), "host.com"); + } + + function testHostTruncation() { + $cookie = new SimpleCookie("name"); + $cookie->setHost("this.host.name.here"); + $this->assertEqual($cookie->getHost(), "host.name.here"); + $cookie->setHost("this.host.com"); + $this->assertEqual($cookie->getHost(), "host.com"); + $this->assertTrue($cookie->setHost("dashes.in-host.com")); + $this->assertEqual($cookie->getHost(), "in-host.com"); + } + + function testBadHosts() { + $cookie = new SimpleCookie("name"); + $this->assertFalse($cookie->setHost("gibberish")); + $this->assertFalse($cookie->setHost("host.here")); + $this->assertFalse($cookie->setHost("host..com")); + $this->assertFalse($cookie->setHost("...")); + $this->assertFalse($cookie->setHost("host.com.")); + } + + function testHostValidity() { + $cookie = new SimpleCookie("name"); + $cookie->setHost("this.host.name.here"); + $this->assertTrue($cookie->isValidHost("host.name.here")); + $this->assertTrue($cookie->isValidHost("that.host.name.here")); + $this->assertFalse($cookie->isValidHost("bad.host")); + $this->assertFalse($cookie->isValidHost("nearly.name.here")); + } + + function testPathValidity() { + $cookie = new SimpleCookie("name", "value", "/path"); + $this->assertFalse($cookie->isValidPath("/")); + $this->assertTrue($cookie->isValidPath("/path/")); + $this->assertTrue($cookie->isValidPath("/path/more")); + } + + function testSessionExpiring() { + $cookie = new SimpleCookie("name", "value", "/path"); + $this->assertTrue($cookie->isExpired(0)); + } + + function testTimestampExpiry() { + $cookie = new SimpleCookie("name", "value", "/path", 456); + $this->assertFalse($cookie->isExpired(0)); + $this->assertTrue($cookie->isExpired(457)); + $this->assertFalse($cookie->isExpired(455)); + } + + function testDateExpiry() { + $cookie = new SimpleCookie( + "name", + "value", + "/path", + "Mon, 18 Nov 2002 15:50:29 GMT"); + $this->assertTrue($cookie->isExpired("Mon, 18 Nov 2002 15:50:30 GMT")); + $this->assertFalse($cookie->isExpired("Mon, 18 Nov 2002 15:50:28 GMT")); + } + + function testAging() { + $cookie = new SimpleCookie("name", "value", "/path", 200); + $cookie->agePrematurely(199); + $this->assertFalse($cookie->isExpired(0)); + $cookie->agePrematurely(2); + $this->assertTrue($cookie->isExpired(0)); + } +} + +class TestOfCookieJar extends UnitTestCase { + + function testAddCookie() { + $jar = new SimpleCookieJar(); + $jar->setCookie("a", "A"); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/')), array('a=A')); + } + + function testHostFilter() { + $jar = new SimpleCookieJar(); + $jar->setCookie('a', 'A', 'my-host.com'); + $jar->setCookie('b', 'B', 'another-host.com'); + $jar->setCookie('c', 'C'); + $this->assertEqual( + $jar->selectAsPairs(new SimpleUrl('my-host.com')), + array('a=A', 'c=C')); + $this->assertEqual( + $jar->selectAsPairs(new SimpleUrl('another-host.com')), + array('b=B', 'c=C')); + $this->assertEqual( + $jar->selectAsPairs(new SimpleUrl('www.another-host.com')), + array('b=B', 'c=C')); + $this->assertEqual( + $jar->selectAsPairs(new SimpleUrl('new-host.org')), + array('c=C')); + $this->assertEqual( + $jar->selectAsPairs(new SimpleUrl('/')), + array('a=A', 'b=B', 'c=C')); + } + + function testPathFilter() { + $jar = new SimpleCookieJar(); + $jar->setCookie('a', 'A', false, '/path/'); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/')), array()); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/elsewhere')), array()); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/path/')), array('a=A')); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/path')), array('a=A')); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/pa')), array()); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/path/here')), array('a=A')); + } + + function testPathFilterDeeply() { + $jar = new SimpleCookieJar(); + $jar->setCookie('a', 'A', false, '/path/more_path/'); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/path/')), array()); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/path')), array()); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/pa')), array()); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/path/more_path/')), array('a=A')); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/path/more_path/and_more')), array('a=A')); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/path/not_here/')), array()); + } + + function testMultipleCookieWithDifferentPathsButSameName() { + $jar = new SimpleCookieJar(); + $jar->setCookie('a', 'abc', false, '/'); + $jar->setCookie('a', '123', false, '/path/here/'); + $this->assertEqual( + $jar->selectAsPairs(new SimpleUrl('/')), + array('a=abc')); + $this->assertEqual( + $jar->selectAsPairs(new SimpleUrl('my-host.com/')), + array('a=abc')); + $this->assertEqual( + $jar->selectAsPairs(new SimpleUrl('my-host.com/path/')), + array('a=abc')); + $this->assertEqual( + $jar->selectAsPairs(new SimpleUrl('my-host.com/path/here')), + array('a=abc', 'a=123')); + $this->assertEqual( + $jar->selectAsPairs(new SimpleUrl('my-host.com/path/here/there')), + array('a=abc', 'a=123')); + } + + function testOverwrite() { + $jar = new SimpleCookieJar(); + $jar->setCookie('a', 'abc', false, '/'); + $jar->setCookie('a', 'cde', false, '/'); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/')), array('a=cde')); + } + + function testClearSessionCookies() { + $jar = new SimpleCookieJar(); + $jar->setCookie('a', 'A', false, '/'); + $jar->restartSession(); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/')), array()); + } + + function testExpiryFilterByDate() { + $jar = new SimpleCookieJar(); + $jar->setCookie('a', 'A', false, '/', 'Wed, 25-Dec-02 04:24:20 GMT'); + $jar->restartSession("Wed, 25-Dec-02 04:24:19 GMT"); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/')), array('a=A')); + $jar->restartSession("Wed, 25-Dec-02 04:24:21 GMT"); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/')), array()); + } + + function testExpiryFilterByAgeing() { + $jar = new SimpleCookieJar(); + $jar->setCookie('a', 'A', false, '/', 'Wed, 25-Dec-02 04:24:20 GMT'); + $jar->restartSession("Wed, 25-Dec-02 04:24:19 GMT"); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/')), array('a=A')); + $jar->agePrematurely(2); + $jar->restartSession("Wed, 25-Dec-02 04:24:19 GMT"); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/')), array()); + } + + function testCookieClearing() { + $jar = new SimpleCookieJar(); + $jar->setCookie('a', 'abc', false, '/'); + $jar->setCookie('a', '', false, '/'); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/')), array('a=')); + } + + function testCookieClearByLoweringDate() { + $jar = new SimpleCookieJar(); + $jar->setCookie('a', 'abc', false, '/', 'Wed, 25-Dec-02 04:24:21 GMT'); + $jar->setCookie('a', 'def', false, '/', 'Wed, 25-Dec-02 04:24:19 GMT'); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/')), array('a=def')); + $jar->restartSession('Wed, 25-Dec-02 04:24:20 GMT'); + $this->assertEqual($jar->selectAsPairs(new SimpleUrl('/')), array()); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/detached_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/detached_test.php new file mode 100644 index 0000000..06db828 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/detached_test.php @@ -0,0 +1,15 @@ +addTestCase(new DetachedTestCase($command)); +if (SimpleReporter::inCli()) { + exit ($test->run(new TextReporter()) ? 0 : 1); +} +$test->run(new HtmlReporter()); +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/dumper_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/dumper_test.php new file mode 100644 index 0000000..789047d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/dumper_test.php @@ -0,0 +1,88 @@ +assertEqual( + $dumper->clipString("Hello", 6), + "Hello", + "Hello, 6->%s"); + $this->assertEqual( + $dumper->clipString("Hello", 5), + "Hello", + "Hello, 5->%s"); + $this->assertEqual( + $dumper->clipString("Hello world", 3), + "Hel...", + "Hello world, 3->%s"); + $this->assertEqual( + $dumper->clipString("Hello world", 6, 3), + "Hello ...", + "Hello world, 6, 3->%s"); + $this->assertEqual( + $dumper->clipString("Hello world", 3, 6), + "...o w...", + "Hello world, 3, 6->%s"); + $this->assertEqual( + $dumper->clipString("Hello world", 4, 11), + "...orld", + "Hello world, 4, 11->%s"); + $this->assertEqual( + $dumper->clipString("Hello world", 4, 12), + "...orld", + "Hello world, 4, 12->%s"); + } + + function testDescribeNull() { + $dumper = new SimpleDumper(); + $this->assertPattern('/null/i', $dumper->describeValue(null)); + } + + function testDescribeBoolean() { + $dumper = new SimpleDumper(); + $this->assertPattern('/boolean/i', $dumper->describeValue(true)); + $this->assertPattern('/true/i', $dumper->describeValue(true)); + $this->assertPattern('/false/i', $dumper->describeValue(false)); + } + + function testDescribeString() { + $dumper = new SimpleDumper(); + $this->assertPattern('/string/i', $dumper->describeValue('Hello')); + $this->assertPattern('/Hello/', $dumper->describeValue('Hello')); + } + + function testDescribeInteger() { + $dumper = new SimpleDumper(); + $this->assertPattern('/integer/i', $dumper->describeValue(35)); + $this->assertPattern('/35/', $dumper->describeValue(35)); + } + + function testDescribeFloat() { + $dumper = new SimpleDumper(); + $this->assertPattern('/float/i', $dumper->describeValue(0.99)); + $this->assertPattern('/0\.99/', $dumper->describeValue(0.99)); + } + + function testDescribeArray() { + $dumper = new SimpleDumper(); + $this->assertPattern('/array/i', $dumper->describeValue(array(1, 4))); + $this->assertPattern('/2/i', $dumper->describeValue(array(1, 4))); + } + + function testDescribeObject() { + $dumper = new SimpleDumper(); + $this->assertPattern( + '/object/i', + $dumper->describeValue(new DumperDummy())); + $this->assertPattern( + '/DumperDummy/i', + $dumper->describeValue(new DumperDummy())); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/eclipse_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/eclipse_test.php new file mode 100644 index 0000000..c90cbc9 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/eclipse_test.php @@ -0,0 +1,32 @@ +expectOnce('write',array($expected)); + $listener->setReturnValue('write',-1); + + $pathparts = pathinfo($fullpath); + $filename = $pathparts['basename']; + $test= &new TestSuite($filename); + $test->addTestFile($fullpath); + $test->run(new EclipseReporter($listener)); + $this->assertEqual($expected,$listener->output); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/encoding_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/encoding_test.php new file mode 100644 index 0000000..91d80db --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/encoding_test.php @@ -0,0 +1,213 @@ +assertEqual($pair->asRequest(), 'a=A'); + } + + function testMimeEncodedAsHeadersAndContent() { + $pair = new SimpleEncodedPair('a', 'A'); + $this->assertEqual( + $pair->asMime(), + "Content-Disposition: form-data; name=\"a\"\r\n\r\nA"); + } + + function testAttachmentEncodedAsHeadersWithDispositionAndContent() { + $part = new SimpleAttachment('a', 'A', 'aaa.txt'); + $this->assertEqual( + $part->asMime(), + "Content-Disposition: form-data; name=\"a\"; filename=\"aaa.txt\"\r\n" . + "Content-Type: text/plain\r\n\r\nA"); + } +} + +class TestOfEncoding extends UnitTestCase { + private $content_so_far; + + function write($content) { + $this->content_so_far .= $content; + } + + function clear() { + $this->content_so_far = ''; + } + + function assertWritten($encoding, $content, $message = '%s') { + $this->clear(); + $encoding->writeTo($this); + $this->assertIdentical($this->content_so_far, $content, $message); + } + + function testGetEmpty() { + $encoding = new SimpleGetEncoding(); + $this->assertIdentical($encoding->getValue('a'), false); + $this->assertIdentical($encoding->asUrlRequest(), ''); + } + + function testPostEmpty() { + $encoding = new SimplePostEncoding(); + $this->assertIdentical($encoding->getValue('a'), false); + $this->assertWritten($encoding, ''); + } + + function testPrefilled() { + $encoding = new SimplePostEncoding(array('a' => 'aaa')); + $this->assertIdentical($encoding->getValue('a'), 'aaa'); + $this->assertWritten($encoding, 'a=aaa'); + } + + function testPrefilledWithTwoLevels() { + $query = array('a' => array('aa' => 'aaa')); + $encoding = new SimplePostEncoding($query); + $this->assertTrue($encoding->hasMoreThanOneLevel($query)); + $this->assertEqual($encoding->rewriteArrayWithMultipleLevels($query), array('a[aa]' => 'aaa')); + $this->assertIdentical($encoding->getValue('a[aa]'), 'aaa'); + $this->assertWritten($encoding, 'a%5Baa%5D=aaa'); + } + + function testPrefilledWithThreeLevels() { + $query = array('a' => array('aa' => array('aaa' => 'aaaa'))); + $encoding = new SimplePostEncoding($query); + $this->assertTrue($encoding->hasMoreThanOneLevel($query)); + $this->assertEqual($encoding->rewriteArrayWithMultipleLevels($query), array('a[aa][aaa]' => 'aaaa')); + $this->assertIdentical($encoding->getValue('a[aa][aaa]'), 'aaaa'); + $this->assertWritten($encoding, 'a%5Baa%5D%5Baaa%5D=aaaa'); + } + + function testPrefilledWithObject() { + $encoding = new SimplePostEncoding(new SimpleEncoding(array('a' => 'aaa'))); + $this->assertIdentical($encoding->getValue('a'), 'aaa'); + $this->assertWritten($encoding, 'a=aaa'); + } + + function testMultiplePrefilled() { + $query = array('a' => array('a1', 'a2')); + $encoding = new SimplePostEncoding($query); + $this->assertTrue($encoding->hasMoreThanOneLevel($query)); + $this->assertEqual($encoding->rewriteArrayWithMultipleLevels($query), array('a[0]' => 'a1', 'a[1]' => 'a2')); + $this->assertIdentical($encoding->getValue('a[0]'), 'a1'); + $this->assertIdentical($encoding->getValue('a[1]'), 'a2'); + $this->assertWritten($encoding, 'a%5B0%5D=a1&a%5B1%5D=a2'); + } + + function testSingleParameter() { + $encoding = new SimplePostEncoding(); + $encoding->add('a', 'Hello'); + $this->assertEqual($encoding->getValue('a'), 'Hello'); + $this->assertWritten($encoding, 'a=Hello'); + } + + function testFalseParameter() { + $encoding = new SimplePostEncoding(); + $encoding->add('a', false); + $this->assertEqual($encoding->getValue('a'), false); + $this->assertWritten($encoding, ''); + } + + function testUrlEncoding() { + $encoding = new SimplePostEncoding(); + $encoding->add('a', 'Hello there!'); + $this->assertWritten($encoding, 'a=Hello+there%21'); + } + + function testUrlEncodingOfKey() { + $encoding = new SimplePostEncoding(); + $encoding->add('a!', 'Hello'); + $this->assertWritten($encoding, 'a%21=Hello'); + } + + function testMultipleParameter() { + $encoding = new SimplePostEncoding(); + $encoding->add('a', 'Hello'); + $encoding->add('b', 'Goodbye'); + $this->assertWritten($encoding, 'a=Hello&b=Goodbye'); + } + + function testEmptyParameters() { + $encoding = new SimplePostEncoding(); + $encoding->add('a', ''); + $encoding->add('b', ''); + $this->assertWritten($encoding, 'a=&b='); + } + + function testRepeatedParameter() { + $encoding = new SimplePostEncoding(); + $encoding->add('a', 'Hello'); + $encoding->add('a', 'Goodbye'); + $this->assertIdentical($encoding->getValue('a'), array('Hello', 'Goodbye')); + $this->assertWritten($encoding, 'a=Hello&a=Goodbye'); + } + + function testAddingLists() { + $encoding = new SimplePostEncoding(); + $encoding->add('a', array('Hello', 'Goodbye')); + $this->assertIdentical($encoding->getValue('a'), array('Hello', 'Goodbye')); + $this->assertWritten($encoding, 'a=Hello&a=Goodbye'); + } + + function testMergeInHash() { + $encoding = new SimpleGetEncoding(array('a' => 'A1', 'b' => 'B')); + $encoding->merge(array('a' => 'A2')); + $this->assertIdentical($encoding->getValue('a'), array('A1', 'A2')); + $this->assertIdentical($encoding->getValue('b'), 'B'); + } + + function testMergeInObject() { + $encoding = new SimpleGetEncoding(array('a' => 'A1', 'b' => 'B')); + $encoding->merge(new SimpleEncoding(array('a' => 'A2'))); + $this->assertIdentical($encoding->getValue('a'), array('A1', 'A2')); + $this->assertIdentical($encoding->getValue('b'), 'B'); + } + + function testPrefilledMultipart() { + $encoding = new SimpleMultipartEncoding(array('a' => 'aaa'), 'boundary'); + $this->assertIdentical($encoding->getValue('a'), 'aaa'); + $this->assertwritten($encoding, + "--boundary\r\n" . + "Content-Disposition: form-data; name=\"a\"\r\n" . + "\r\n" . + "aaa\r\n" . + "--boundary--\r\n"); + } + + function testAttachment() { + $encoding = new SimpleMultipartEncoding(array(), 'boundary'); + $encoding->attach('a', 'aaa', 'aaa.txt'); + $this->assertIdentical($encoding->getValue('a'), 'aaa.txt'); + $this->assertwritten($encoding, + "--boundary\r\n" . + "Content-Disposition: form-data; name=\"a\"; filename=\"aaa.txt\"\r\n" . + "Content-Type: text/plain\r\n" . + "\r\n" . + "aaa\r\n" . + "--boundary--\r\n"); + } +} + +class TestOfFormHeaders extends UnitTestCase { + + function testEmptyEncodingWritesZeroContentLength() { + $socket = new MockSimpleSocket(); + $socket->expectAt(0, 'write', array("Content-Length: 0\r\n")); + $socket->expectAt(1, 'write', array("Content-Type: application/x-www-form-urlencoded\r\n")); + $encoding = new SimplePostEncoding(); + $encoding->writeHeadersTo($socket); + } + + function testEmptyMultipartEncodingWritesEndBoundaryContentLength() { + $socket = new MockSimpleSocket(); + $socket->expectAt(0, 'write', array("Content-Length: 14\r\n")); + $socket->expectAt(1, 'write', array("Content-Type: multipart/form-data, boundary=boundary\r\n")); + $encoding = new SimpleMultipartEncoding(array(), 'boundary'); + $encoding->writeHeadersTo($socket); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/errors_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/errors_test.php new file mode 100644 index 0000000..ebb9e05 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/errors_test.php @@ -0,0 +1,229 @@ +get('SimpleErrorQueue'); + $queue->clear(); + } + + function tearDown() { + $context = SimpleTest::getContext(); + $queue = $context->get('SimpleErrorQueue'); + $queue->clear(); + } + + function testExpectationMatchCancelsIncomingError() { + $test = new MockSimpleTestCase(); + $test->expectOnce('assert', array( + new IdenticalExpectation(new AnythingExpectation()), + 'B', + 'a message')); + $test->setReturnValue('assert', true); + $test->expectNever('error'); + $queue = new SimpleErrorQueue(); + $queue->setTestCase($test); + $queue->expectError(new AnythingExpectation(), 'a message'); + $queue->add(1024, 'B', 'b.php', 100); + } +} + +class TestOfErrorTrap extends UnitTestCase { + private $old; + + function setUp() { + $this->old = error_reporting(E_ALL); + set_error_handler('SimpleTestErrorHandler'); + } + + function tearDown() { + restore_error_handler(); + error_reporting($this->old); + } + + function testQueueStartsEmpty() { + $context = SimpleTest::getContext(); + $queue = $context->get('SimpleErrorQueue'); + $this->assertFalse($queue->extract()); + } + + function testErrorsAreSwallowedByMatchingExpectation() { + $this->expectError('Ouch!'); + trigger_error('Ouch!'); + } + + function testErrorsAreSwallowedInOrder() { + $this->expectError('a'); + $this->expectError('b'); + trigger_error('a'); + trigger_error('b'); + } + + function testAnyErrorCanBeSwallowed() { + $this->expectError(); + trigger_error('Ouch!'); + } + + function testErrorCanBeSwallowedByPatternMatching() { + $this->expectError(new PatternExpectation('/ouch/i')); + trigger_error('Ouch!'); + } + + function testErrorWithPercentsPassesWithNoSprintfError() { + $this->expectError("%"); + trigger_error('%'); + } +} + +class TestOfErrors extends UnitTestCase { + private $old; + + function setUp() { + $this->old = error_reporting(E_ALL); + } + + function tearDown() { + error_reporting($this->old); + } + + function testDefaultWhenAllReported() { + error_reporting(E_ALL); + $this->expectError('Ouch!'); + trigger_error('Ouch!'); + } + + function testNoticeWhenReported() { + error_reporting(E_ALL); + $this->expectError('Ouch!'); + trigger_error('Ouch!', E_USER_NOTICE); + } + + function testWarningWhenReported() { + error_reporting(E_ALL); + $this->expectError('Ouch!'); + trigger_error('Ouch!', E_USER_WARNING); + } + + function testErrorWhenReported() { + error_reporting(E_ALL); + $this->expectError('Ouch!'); + trigger_error('Ouch!', E_USER_ERROR); + } + + function testNoNoticeWhenNotReported() { + error_reporting(0); + trigger_error('Ouch!', E_USER_NOTICE); + } + + function testNoWarningWhenNotReported() { + error_reporting(0); + trigger_error('Ouch!', E_USER_WARNING); + } + + function testNoticeSuppressedWhenReported() { + error_reporting(E_ALL); + @trigger_error('Ouch!', E_USER_NOTICE); + } + + function testWarningSuppressedWhenReported() { + error_reporting(E_ALL); + @trigger_error('Ouch!', E_USER_WARNING); + } + + function testErrorWithPercentsReportedWithNoSprintfError() { + $this->expectError('%'); + trigger_error('%'); + } +} + +class TestOfPHP52RecoverableErrors extends UnitTestCase { + function skip() { + $this->skipIf( + version_compare(phpversion(), '5.2', '<'), + 'E_RECOVERABLE_ERROR not tested for PHP below 5.2'); + } + + function testError() { + eval(' + class RecoverableErrorTestingStub { + function ouch(RecoverableErrorTestingStub $obj) { + } + } + '); + + $stub = new RecoverableErrorTestingStub(); + $this->expectError(new PatternExpectation('/must be an instance of RecoverableErrorTestingStub/i')); + $stub->ouch(new stdClass()); + } +} + +class TestOfErrorsExcludingPHP52AndAbove extends UnitTestCase { + function skip() { + $this->skipIf( + version_compare(phpversion(), '5.2', '>='), + 'E_USER_ERROR not tested for PHP 5.2 and above'); + } + + function testNoErrorWhenNotReported() { + error_reporting(0); + trigger_error('Ouch!', E_USER_ERROR); + } + + function testErrorSuppressedWhenReported() { + error_reporting(E_ALL); + @trigger_error('Ouch!', E_USER_ERROR); + } +} + +SimpleTest::ignore('TestOfNotEnoughErrors'); +/** + * This test is ignored as it is used by {@link TestRunnerForLeftOverAndNotEnoughErrors} + * to verify that it fails as expected. + * + * @ignore + */ +class TestOfNotEnoughErrors extends UnitTestCase { + function testExpectTwoErrorsThrowOne() { + $this->expectError('Error 1'); + trigger_error('Error 1'); + $this->expectError('Error 2'); + } +} + +SimpleTest::ignore('TestOfLeftOverErrors'); +/** + * This test is ignored as it is used by {@link TestRunnerForLeftOverAndNotEnoughErrors} + * to verify that it fails as expected. + * + * @ignore + */ +class TestOfLeftOverErrors extends UnitTestCase { + function testExpectOneErrorGetTwo() { + $this->expectError('Error 1'); + trigger_error('Error 1'); + trigger_error('Error 2'); + } +} + +class TestRunnerForLeftOverAndNotEnoughErrors extends UnitTestCase { + function testRunLeftOverErrorsTestCase() { + $test = new TestOfLeftOverErrors(); + $this->assertFalse($test->run(new SimpleReporter())); + } + + function testRunNotEnoughErrors() { + $test = new TestOfNotEnoughErrors(); + $this->assertFalse($test->run(new SimpleReporter())); + } +} + +// TODO: Add stacked error handler test +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/exceptions_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/exceptions_test.php new file mode 100644 index 0000000..9cc35c5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/exceptions_test.php @@ -0,0 +1,153 @@ +assertTrue($expectation->test(new MyTestException())); + $this->assertTrue($expectation->test(new HigherTestException())); + $this->assertFalse($expectation->test(new OtherTestException())); + } + + function testMatchesClassAndMessageWhenExceptionExpected() { + $expectation = new ExceptionExpectation(new MyTestException('Hello')); + $this->assertTrue($expectation->test(new MyTestException('Hello'))); + $this->assertFalse($expectation->test(new HigherTestException('Hello'))); + $this->assertFalse($expectation->test(new OtherTestException('Hello'))); + $this->assertFalse($expectation->test(new MyTestException('Goodbye'))); + $this->assertFalse($expectation->test(new MyTestException())); + } + + function testMessagelessExceptionMatchesOnlyOnClass() { + $expectation = new ExceptionExpectation(new MyTestException()); + $this->assertTrue($expectation->test(new MyTestException())); + $this->assertFalse($expectation->test(new HigherTestException())); + } +} + +class TestOfExceptionTrap extends UnitTestCase { + + function testNoExceptionsInQueueMeansNoTestMessages() { + $test = new MockSimpleTestCase(); + $test->expectNever('assert'); + $queue = new SimpleExceptionTrap(); + $this->assertFalse($queue->isExpected($test, new Exception())); + } + + function testMatchingExceptionGivesTrue() { + $expectation = new MockSimpleExpectation(); + $expectation->setReturnValue('test', true); + $test = new MockSimpleTestCase(); + $test->setReturnValue('assert', true); + $queue = new SimpleExceptionTrap(); + $queue->expectException($expectation, 'message'); + $this->assertTrue($queue->isExpected($test, new Exception())); + } + + function testMatchingExceptionTriggersAssertion() { + $test = new MockSimpleTestCase(); + $test->expectOnce('assert', array( + '*', + new ExceptionExpectation(new Exception()), + 'message')); + $queue = new SimpleExceptionTrap(); + $queue->expectException(new ExceptionExpectation(new Exception()), 'message'); + $queue->isExpected($test, new Exception()); + } +} + +class TestOfCatchingExceptions extends UnitTestCase { + + function testCanCatchAnyExpectedException() { + $this->expectException(); + throw new Exception(); + } + + function testCanMatchExceptionByClass() { + $this->expectException('MyTestException'); + throw new HigherTestException(); + } + + function testCanMatchExceptionExactly() { + $this->expectException(new Exception('Ouch')); + throw new Exception('Ouch'); + } + + function testLastListedExceptionIsTheOneThatCounts() { + $this->expectException('OtherTestException'); + $this->expectException('MyTestException'); + throw new HigherTestException(); + } +} + +class TestOfCallingTearDownAfterExceptions extends UnitTestCase { + private $debri = 0; + + function tearDown() { + $this->debri--; + } + + function testLeaveSomeDebri() { + $this->debri++; + $this->expectException(); + throw new Exception(__FUNCTION__); + } + + function testDebriWasRemovedOnce() { + $this->assertEqual($this->debri, 0); + } +} + +class TestOfExceptionThrownInSetUpDoesNotRunTestBody extends UnitTestCase { + + function setUp() { + $this->expectException(); + throw new Exception(); + } + + function testShouldNotBeRun() { + $this->fail('This test body should not be run'); + } + + function testShouldNotBeRunEither() { + $this->fail('This test body should not be run either'); + } +} + +class TestOfExpectExceptionWithSetUp extends UnitTestCase { + + function setUp() { + $this->expectException(); + } + + function testThisExceptionShouldBeCaught() { + throw new Exception(); + } + + function testJustThrowingMyTestException() { + throw new MyTestException(); + } +} + +class TestOfThrowingExceptionsInTearDown extends UnitTestCase { + + function tearDown() { + throw new Exception(); + } + + function testDoesntFatal() { + $this->expectException(); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/expectation_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/expectation_test.php new file mode 100644 index 0000000..2283c19 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/expectation_test.php @@ -0,0 +1,223 @@ +assertTrue($is_true->test(true)); + $this->assertFalse($is_true->test(false)); + } + + function testStringMatch() { + $hello = new EqualExpectation("Hello"); + $this->assertTrue($hello->test("Hello")); + $this->assertFalse($hello->test("Goodbye")); + } + + function testInteger() { + $fifteen = new EqualExpectation(15); + $this->assertTrue($fifteen->test(15)); + $this->assertFalse($fifteen->test(14)); + } + + function testFloat() { + $pi = new EqualExpectation(3.14); + $this->assertTrue($pi->test(3.14)); + $this->assertFalse($pi->test(3.15)); + } + + function testArray() { + $colours = new EqualExpectation(array("r", "g", "b")); + $this->assertTrue($colours->test(array("r", "g", "b"))); + $this->assertFalse($colours->test(array("g", "b", "r"))); + } + + function testHash() { + $is_blue = new EqualExpectation(array("r" => 0, "g" => 0, "b" => 255)); + $this->assertTrue($is_blue->test(array("r" => 0, "g" => 0, "b" => 255))); + $this->assertFalse($is_blue->test(array("r" => 0, "g" => 255, "b" => 0))); + } + + function testHashWithOutOfOrderKeysShouldStillMatch() { + $any_order = new EqualExpectation(array('a' => 1, 'b' => 2)); + $this->assertTrue($any_order->test(array('b' => 2, 'a' => 1))); + } +} + +class TestOfWithin extends UnitTestCase { + + function testWithinFloatingPointMargin() { + $within = new WithinMarginExpectation(1.0, 0.2); + $this->assertFalse($within->test(0.7)); + $this->assertTrue($within->test(0.8)); + $this->assertTrue($within->test(0.9)); + $this->assertTrue($within->test(1.1)); + $this->assertTrue($within->test(1.2)); + $this->assertFalse($within->test(1.3)); + } + + function testOutsideFloatingPointMargin() { + $within = new OutsideMarginExpectation(1.0, 0.2); + $this->assertTrue($within->test(0.7)); + $this->assertFalse($within->test(0.8)); + $this->assertFalse($within->test(1.2)); + $this->assertTrue($within->test(1.3)); + } +} + +class TestOfInequality extends UnitTestCase { + + function testStringMismatch() { + $not_hello = new NotEqualExpectation("Hello"); + $this->assertTrue($not_hello->test("Goodbye")); + $this->assertFalse($not_hello->test("Hello")); + } +} + +class RecursiveNasty { + private $me; + + function RecursiveNasty() { + $this->me = $this; + } +} + +class TestOfIdentity extends UnitTestCase { + + function testType() { + $string = new IdenticalExpectation("37"); + $this->assertTrue($string->test("37")); + $this->assertFalse($string->test(37)); + $this->assertFalse($string->test("38")); + } + + function _testNastyPhp5Bug() { + $this->assertFalse(new RecursiveNasty() != new RecursiveNasty()); + } + + function _testReallyHorribleRecursiveStructure() { + $hopeful = new IdenticalExpectation(new RecursiveNasty()); + $this->assertTrue($hopeful->test(new RecursiveNasty())); + } +} + +class DummyReferencedObject{} + +class TestOfReference extends UnitTestCase { + + function testReference() { + $foo = "foo"; + $ref = &$foo; + $not_ref = $foo; + $bar = "bar"; + + $expect = new ReferenceExpectation($foo); + $this->assertTrue($expect->test($ref)); + $this->assertFalse($expect->test($not_ref)); + $this->assertFalse($expect->test($bar)); + } +} + +class TestOfNonIdentity extends UnitTestCase { + + function testType() { + $string = new NotIdenticalExpectation("37"); + $this->assertTrue($string->test("38")); + $this->assertTrue($string->test(37)); + $this->assertFalse($string->test("37")); + } +} + +class TestOfPatterns extends UnitTestCase { + + function testWanted() { + $pattern = new PatternExpectation('/hello/i'); + $this->assertTrue($pattern->test("Hello world")); + $this->assertFalse($pattern->test("Goodbye world")); + } + + function testUnwanted() { + $pattern = new NoPatternExpectation('/hello/i'); + $this->assertFalse($pattern->test("Hello world")); + $this->assertTrue($pattern->test("Goodbye world")); + } +} + +class ExpectedMethodTarget { + function hasThisMethod() {} +} + +class TestOfMethodExistence extends UnitTestCase { + + function testHasMethod() { + $instance = new ExpectedMethodTarget(); + $expectation = new MethodExistsExpectation('hasThisMethod'); + $this->assertTrue($expectation->test($instance)); + $expectation = new MethodExistsExpectation('doesNotHaveThisMethod'); + $this->assertFalse($expectation->test($instance)); + } +} + +class TestOfIsA extends UnitTestCase { + + function testString() { + $expectation = new IsAExpectation('string'); + $this->assertTrue($expectation->test('Hello')); + $this->assertFalse($expectation->test(5)); + } + + function testBoolean() { + $expectation = new IsAExpectation('boolean'); + $this->assertTrue($expectation->test(true)); + $this->assertFalse($expectation->test(1)); + } + + function testBool() { + $expectation = new IsAExpectation('bool'); + $this->assertTrue($expectation->test(true)); + $this->assertFalse($expectation->test(1)); + } + + function testDouble() { + $expectation = new IsAExpectation('double'); + $this->assertTrue($expectation->test(5.0)); + $this->assertFalse($expectation->test(5)); + } + + function testFloat() { + $expectation = new IsAExpectation('float'); + $this->assertTrue($expectation->test(5.0)); + $this->assertFalse($expectation->test(5)); + } + + function testReal() { + $expectation = new IsAExpectation('real'); + $this->assertTrue($expectation->test(5.0)); + $this->assertFalse($expectation->test(5)); + } + + function testInteger() { + $expectation = new IsAExpectation('integer'); + $this->assertTrue($expectation->test(5)); + $this->assertFalse($expectation->test(5.0)); + } + + function testInt() { + $expectation = new IsAExpectation('int'); + $this->assertTrue($expectation->test(5)); + $this->assertFalse($expectation->test(5.0)); + } +} + +class TestOfNotA extends UnitTestCase { + + function testString() { + $expectation = new NotAExpectation('string'); + $this->assertFalse($expectation->test('Hello')); + $this->assertTrue($expectation->test(5)); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/extensions_tests.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/extensions_tests.php new file mode 100644 index 0000000..b32fa8e --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/extensions_tests.php @@ -0,0 +1,23 @@ +skipIf(version_compare(phpversion(), '5', '<'), + 'Many extensions only work with PHP5 and above'); + } + + function ExtensionsTests() { + $this->TestSuite('Extension tests for SimpleTest ' . SimpleTest::getVersion()); + + $nodes = new RecursiveDirectoryIterator(dirname(__FILE__).'/../extensions/'); + foreach(new RecursiveIteratorIterator($nodes) as $node) { + if (preg_match('/test\.php$/', $node->getFilename())) { + $this->addFile($node->getPathname()); + } + } + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/form_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/form_test.php new file mode 100644 index 0000000..de04537 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/form_test.php @@ -0,0 +1,324 @@ +returns('getUrl', new SimpleUrl($url)); + $page->returns('expandUrl', new SimpleUrl($url)); + return $page; + } + + function testFormAttributes() { + $tag = new SimpleFormTag(array('method' => 'GET', 'action' => 'here.php', 'id' => '33')); + $form = new SimpleForm($tag, $this->page('http://host/a/index.html')); + $this->assertEqual($form->getMethod(), 'get'); + $this->assertIdentical($form->getId(), '33'); + $this->assertNull($form->getValue(new SimpleByName('a'))); + } + + function testAction() { + $page = new MockSimplePage(); + $page->expectOnce('expandUrl', array(new SimpleUrl('here.php'))); + $page->setReturnValue('expandUrl', new SimpleUrl('http://host/here.php')); + $tag = new SimpleFormTag(array('method' => 'GET', 'action' => 'here.php')); + $form = new SimpleForm($tag, $page); + $this->assertEqual($form->getAction(), new SimpleUrl('http://host/here.php')); + } + + function testEmptyAction() { + $tag = new SimpleFormTag(array('method' => 'GET', 'action' => '', 'id' => '33')); + $form = new SimpleForm($tag, $this->page('http://host/a/index.html')); + $this->assertEqual( + $form->getAction(), + new SimpleUrl('http://host/a/index.html')); + } + + function testMissingAction() { + $tag = new SimpleFormTag(array('method' => 'GET')); + $form = new SimpleForm($tag, $this->page('http://host/a/index.html')); + $this->assertEqual( + $form->getAction(), + new SimpleUrl('http://host/a/index.html')); + } + + function testRootAction() { + $page = new MockSimplePage(); + $page->expectOnce('expandUrl', array(new SimpleUrl('/'))); + $page->setReturnValue('expandUrl', new SimpleUrl('http://host/')); + $tag = new SimpleFormTag(array('method' => 'GET', 'action' => '/')); + $form = new SimpleForm($tag, $page); + $this->assertEqual( + $form->getAction(), + new SimpleUrl('http://host/')); + } + + function testDefaultFrameTargetOnForm() { + $page = new MockSimplePage(); + $page->expectOnce('expandUrl', array(new SimpleUrl('here.php'))); + $page->setReturnValue('expandUrl', new SimpleUrl('http://host/here.php')); + $tag = new SimpleFormTag(array('method' => 'GET', 'action' => 'here.php')); + $form = new SimpleForm($tag, $page); + $form->setDefaultTarget('frame'); + $expected = new SimpleUrl('http://host/here.php'); + $expected->setTarget('frame'); + $this->assertEqual($form->getAction(), $expected); + } + + function testTextWidget() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('htp://host')); + $form->addWidget(new SimpleTextTag( + array('name' => 'me', 'type' => 'text', 'value' => 'Myself'))); + $this->assertIdentical($form->getValue(new SimpleByName('me')), 'Myself'); + $this->assertTrue($form->setField(new SimpleByName('me'), 'Not me')); + $this->assertFalse($form->setField(new SimpleByName('not_present'), 'Not me')); + $this->assertIdentical($form->getValue(new SimpleByName('me')), 'Not me'); + $this->assertNull($form->getValue(new SimpleByName('not_present'))); + } + + function testTextWidgetById() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('htp://host')); + $form->addWidget(new SimpleTextTag( + array('name' => 'me', 'type' => 'text', 'value' => 'Myself', 'id' => 50))); + $this->assertIdentical($form->getValue(new SimpleById(50)), 'Myself'); + $this->assertTrue($form->setField(new SimpleById(50), 'Not me')); + $this->assertIdentical($form->getValue(new SimpleById(50)), 'Not me'); + } + + function testTextWidgetByLabel() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('htp://host')); + $widget = new SimpleTextTag(array('name' => 'me', 'type' => 'text', 'value' => 'a')); + $form->addWidget($widget); + $widget->setLabel('thing'); + $this->assertIdentical($form->getValue(new SimpleByLabel('thing')), 'a'); + $this->assertTrue($form->setField(new SimpleByLabel('thing'), 'b')); + $this->assertIdentical($form->getValue(new SimpleByLabel('thing')), 'b'); + } + + function testSubmitEmpty() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('htp://host')); + $this->assertIdentical($form->submit(), new SimpleGetEncoding()); + } + + function testSubmitButton() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('http://host')); + $form->addWidget(new SimpleSubmitTag( + array('type' => 'submit', 'name' => 'go', 'value' => 'Go!', 'id' => '9'))); + $this->assertTrue($form->hasSubmit(new SimpleByName('go'))); + $this->assertEqual($form->getValue(new SimpleByName('go')), 'Go!'); + $this->assertEqual($form->getValue(new SimpleById(9)), 'Go!'); + $this->assertEqual( + $form->submitButton(new SimpleByName('go')), + new SimpleGetEncoding(array('go' => 'Go!'))); + $this->assertEqual( + $form->submitButton(new SimpleByLabel('Go!')), + new SimpleGetEncoding(array('go' => 'Go!'))); + $this->assertEqual( + $form->submitButton(new SimpleById(9)), + new SimpleGetEncoding(array('go' => 'Go!'))); + } + + function testSubmitWithAdditionalParameters() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('http://host')); + $form->addWidget(new SimpleSubmitTag( + array('type' => 'submit', 'name' => 'go', 'value' => 'Go!'))); + $this->assertEqual( + $form->submitButton(new SimpleByLabel('Go!'), array('a' => 'A')), + new SimpleGetEncoding(array('go' => 'Go!', 'a' => 'A'))); + } + + function testSubmitButtonWithLabelOfSubmit() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('http://host')); + $form->addWidget(new SimpleSubmitTag( + array('type' => 'submit', 'name' => 'test', 'value' => 'Submit'))); + $this->assertEqual( + $form->submitButton(new SimpleByName('test')), + new SimpleGetEncoding(array('test' => 'Submit'))); + $this->assertEqual( + $form->submitButton(new SimpleByLabel('Submit')), + new SimpleGetEncoding(array('test' => 'Submit'))); + } + + function testSubmitButtonWithWhitespacePaddedLabelOfSubmit() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('http://host')); + $form->addWidget(new SimpleSubmitTag( + array('type' => 'submit', 'name' => 'test', 'value' => ' Submit '))); + $this->assertEqual( + $form->submitButton(new SimpleByLabel('Submit')), + new SimpleGetEncoding(array('test' => ' Submit '))); + } + + function testImageSubmitButton() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('htp://host')); + $form->addWidget(new SimpleImageSubmitTag(array( + 'type' => 'image', + 'src' => 'source.jpg', + 'name' => 'go', + 'alt' => 'Go!', + 'id' => '9'))); + $this->assertTrue($form->hasImage(new SimpleByLabel('Go!'))); + $this->assertEqual( + $form->submitImage(new SimpleByLabel('Go!'), 100, 101), + new SimpleGetEncoding(array('go.x' => 100, 'go.y' => 101))); + $this->assertTrue($form->hasImage(new SimpleByName('go'))); + $this->assertEqual( + $form->submitImage(new SimpleByName('go'), 100, 101), + new SimpleGetEncoding(array('go.x' => 100, 'go.y' => 101))); + $this->assertTrue($form->hasImage(new SimpleById(9))); + $this->assertEqual( + $form->submitImage(new SimpleById(9), 100, 101), + new SimpleGetEncoding(array('go.x' => 100, 'go.y' => 101))); + } + + function testImageSubmitButtonWithAdditionalData() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('htp://host')); + $form->addWidget(new SimpleImageSubmitTag(array( + 'type' => 'image', + 'src' => 'source.jpg', + 'name' => 'go', + 'alt' => 'Go!'))); + $this->assertEqual( + $form->submitImage(new SimpleByLabel('Go!'), 100, 101, array('a' => 'A')), + new SimpleGetEncoding(array('go.x' => 100, 'go.y' => 101, 'a' => 'A'))); + } + + function testButtonTag() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('http://host')); + $widget = new SimpleButtonTag( + array('type' => 'submit', 'name' => 'go', 'value' => 'Go', 'id' => '9')); + $widget->addContent('Go!'); + $form->addWidget($widget); + $this->assertTrue($form->hasSubmit(new SimpleByName('go'))); + $this->assertTrue($form->hasSubmit(new SimpleByLabel('Go!'))); + $this->assertEqual( + $form->submitButton(new SimpleByName('go')), + new SimpleGetEncoding(array('go' => 'Go'))); + $this->assertEqual( + $form->submitButton(new SimpleByLabel('Go!')), + new SimpleGetEncoding(array('go' => 'Go'))); + $this->assertEqual( + $form->submitButton(new SimpleById(9)), + new SimpleGetEncoding(array('go' => 'Go'))); + } + + function testMultipleFieldsWithSameNameSubmitted() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('htp://host')); + $input = new SimpleTextTag(array('name' => 'elements[]', 'value' => '1')); + $form->addWidget($input); + $input = new SimpleTextTag(array('name' => 'elements[]', 'value' => '2')); + $form->addWidget($input); + $form->setField(new SimpleByLabelOrName('elements[]'), '3', 1); + $form->setField(new SimpleByLabelOrName('elements[]'), '4', 2); + $submit = $form->submit(); + $requests = $submit->getAll(); + $this->assertEqual(count($requests), 2); + $this->assertIdentical($requests[0], new SimpleEncodedPair('elements[]', '3')); + $this->assertIdentical($requests[1], new SimpleEncodedPair('elements[]', '4')); + } + + function testSingleSelectFieldSubmitted() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('htp://host')); + $select = new SimpleSelectionTag(array('name' => 'a')); + $select->addTag(new SimpleOptionTag( + array('value' => 'aaa', 'selected' => ''))); + $form->addWidget($select); + $this->assertIdentical( + $form->submit(), + new SimpleGetEncoding(array('a' => 'aaa'))); + } + + function testSingleSelectFieldSubmittedWithPost() { + $form = new SimpleForm(new SimpleFormTag(array('method' => 'post')), $this->page('htp://host')); + $select = new SimpleSelectionTag(array('name' => 'a')); + $select->addTag(new SimpleOptionTag( + array('value' => 'aaa', 'selected' => ''))); + $form->addWidget($select); + $this->assertIdentical( + $form->submit(), + new SimplePostEncoding(array('a' => 'aaa'))); + } + + function testUnchecked() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('htp://host')); + $form->addWidget(new SimpleCheckboxTag( + array('name' => 'me', 'type' => 'checkbox'))); + $this->assertIdentical($form->getValue(new SimpleByName('me')), false); + $this->assertTrue($form->setField(new SimpleByName('me'), 'on')); + $this->assertEqual($form->getValue(new SimpleByName('me')), 'on'); + $this->assertFalse($form->setField(new SimpleByName('me'), 'other')); + $this->assertEqual($form->getValue(new SimpleByName('me')), 'on'); + } + + function testChecked() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('htp://host')); + $form->addWidget(new SimpleCheckboxTag( + array('name' => 'me', 'value' => 'a', 'type' => 'checkbox', 'checked' => ''))); + $this->assertIdentical($form->getValue(new SimpleByName('me')), 'a'); + $this->assertTrue($form->setField(new SimpleByName('me'), 'a')); + $this->assertEqual($form->getValue(new SimpleByName('me')), 'a'); + $this->assertTrue($form->setField(new SimpleByName('me'), false)); + $this->assertEqual($form->getValue(new SimpleByName('me')), false); + } + + function testSingleUncheckedRadioButton() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('htp://host')); + $form->addWidget(new SimpleRadioButtonTag( + array('name' => 'me', 'value' => 'a', 'type' => 'radio'))); + $this->assertIdentical($form->getValue(new SimpleByName('me')), false); + $this->assertTrue($form->setField(new SimpleByName('me'), 'a')); + $this->assertEqual($form->getValue(new SimpleByName('me')), 'a'); + } + + function testSingleCheckedRadioButton() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('htp://host')); + $form->addWidget(new SimpleRadioButtonTag( + array('name' => 'me', 'value' => 'a', 'type' => 'radio', 'checked' => ''))); + $this->assertIdentical($form->getValue(new SimpleByName('me')), 'a'); + $this->assertFalse($form->setField(new SimpleByName('me'), 'other')); + } + + function testUncheckedRadioButtons() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('htp://host')); + $form->addWidget(new SimpleRadioButtonTag( + array('name' => 'me', 'value' => 'a', 'type' => 'radio'))); + $form->addWidget(new SimpleRadioButtonTag( + array('name' => 'me', 'value' => 'b', 'type' => 'radio'))); + $this->assertIdentical($form->getValue(new SimpleByName('me')), false); + $this->assertTrue($form->setField(new SimpleByName('me'), 'a')); + $this->assertIdentical($form->getValue(new SimpleByName('me')), 'a'); + $this->assertTrue($form->setField(new SimpleByName('me'), 'b')); + $this->assertIdentical($form->getValue(new SimpleByName('me')), 'b'); + $this->assertFalse($form->setField(new SimpleByName('me'), 'c')); + $this->assertIdentical($form->getValue(new SimpleByName('me')), 'b'); + } + + function testCheckedRadioButtons() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('htp://host')); + $form->addWidget(new SimpleRadioButtonTag( + array('name' => 'me', 'value' => 'a', 'type' => 'radio'))); + $form->addWidget(new SimpleRadioButtonTag( + array('name' => 'me', 'value' => 'b', 'type' => 'radio', 'checked' => ''))); + $this->assertIdentical($form->getValue(new SimpleByName('me')), 'b'); + $this->assertTrue($form->setField(new SimpleByName('me'), 'a')); + $this->assertIdentical($form->getValue(new SimpleByName('me')), 'a'); + } + + function testMultipleFieldsWithSameKey() { + $form = new SimpleForm(new SimpleFormTag(array()), $this->page('htp://host')); + $form->addWidget(new SimpleCheckboxTag( + array('name' => 'a', 'type' => 'checkbox', 'value' => 'me'))); + $form->addWidget(new SimpleCheckboxTag( + array('name' => 'a', 'type' => 'checkbox', 'value' => 'you'))); + $this->assertIdentical($form->getValue(new SimpleByName('a')), false); + $this->assertTrue($form->setField(new SimpleByName('a'), 'me')); + $this->assertIdentical($form->getValue(new SimpleByName('a')), 'me'); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/frames_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/frames_test.php new file mode 100644 index 0000000..e3ab719 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/frames_test.php @@ -0,0 +1,549 @@ +setReturnValue('getTitle', 'This page'); + $frameset = new SimpleFrameset($page); + $this->assertEqual($frameset->getTitle(), 'This page'); + } + + function TestHeadersReadFromFramesetByDefault() { + $page = new MockSimplePage(); + $page->setReturnValue('getHeaders', 'Header: content'); + $page->setReturnValue('getMimeType', 'text/xml'); + $page->setReturnValue('getResponseCode', 401); + $page->setReturnValue('getTransportError', 'Could not parse headers'); + $page->setReturnValue('getAuthentication', 'Basic'); + $page->setReturnValue('getRealm', 'Safe place'); + + $frameset = new SimpleFrameset($page); + + $this->assertIdentical($frameset->getHeaders(), 'Header: content'); + $this->assertIdentical($frameset->getMimeType(), 'text/xml'); + $this->assertIdentical($frameset->getResponseCode(), 401); + $this->assertIdentical($frameset->getTransportError(), 'Could not parse headers'); + $this->assertIdentical($frameset->getAuthentication(), 'Basic'); + $this->assertIdentical($frameset->getRealm(), 'Safe place'); + } + + function testEmptyFramesetHasNoContent() { + $page = new MockSimplePage(); + $page->setReturnValue('getRaw', 'This content'); + $frameset = new SimpleFrameset($page); + $this->assertEqual($frameset->getRaw(), ''); + } + + function testRawContentIsFromOnlyFrame() { + $page = new MockSimplePage(); + $page->expectNever('getRaw'); + + $frame = new MockSimplePage(); + $frame->setReturnValue('getRaw', 'Stuff'); + + $frameset = new SimpleFrameset($page); + $frameset->addFrame($frame); + $this->assertEqual($frameset->getRaw(), 'Stuff'); + } + + function testRawContentIsFromAllFrames() { + $page = new MockSimplePage(); + $page->expectNever('getRaw'); + + $frame1 = new MockSimplePage(); + $frame1->setReturnValue('getRaw', 'Stuff1'); + + $frame2 = new MockSimplePage(); + $frame2->setReturnValue('getRaw', 'Stuff2'); + + $frameset = new SimpleFrameset($page); + $frameset->addFrame($frame1); + $frameset->addFrame($frame2); + $this->assertEqual($frameset->getRaw(), 'Stuff1Stuff2'); + } + + function testTextContentIsFromOnlyFrame() { + $page = new MockSimplePage(); + $page->expectNever('getText'); + + $frame = new MockSimplePage(); + $frame->setReturnValue('getText', 'Stuff'); + + $frameset = new SimpleFrameset($page); + $frameset->addFrame($frame); + $this->assertEqual($frameset->getText(), 'Stuff'); + } + + function testTextContentIsFromAllFrames() { + $page = new MockSimplePage(); + $page->expectNever('getText'); + + $frame1 = new MockSimplePage(); + $frame1->setReturnValue('getText', 'Stuff1'); + + $frame2 = new MockSimplePage(); + $frame2->setReturnValue('getText', 'Stuff2'); + + $frameset = new SimpleFrameset($page); + $frameset->addFrame($frame1); + $frameset->addFrame($frame2); + $this->assertEqual($frameset->getText(), 'Stuff1 Stuff2'); + } + + function testFieldFoundIsFirstInFramelist() { + $frame1 = new MockSimplePage(); + $frame1->setReturnValue('getField', null); + $frame1->expectOnce('getField', array(new SimpleByName('a'))); + + $frame2 = new MockSimplePage(); + $frame2->setReturnValue('getField', 'A'); + $frame2->expectOnce('getField', array(new SimpleByName('a'))); + + $frame3 = new MockSimplePage(); + $frame3->expectNever('getField'); + + $page = new MockSimplePage(); + $frameset = new SimpleFrameset($page); + $frameset->addFrame($frame1); + $frameset->addFrame($frame2); + $frameset->addFrame($frame3); + $this->assertIdentical($frameset->getField(new SimpleByName('a')), 'A'); + } + + function testFrameReplacementByIndex() { + $page = new MockSimplePage(); + $page->expectNever('getRaw'); + + $frame1 = new MockSimplePage(); + $frame1->setReturnValue('getRaw', 'Stuff1'); + + $frame2 = new MockSimplePage(); + $frame2->setReturnValue('getRaw', 'Stuff2'); + + $frameset = new SimpleFrameset($page); + $frameset->addFrame($frame1); + $frameset->setFrame(array(1), $frame2); + $this->assertEqual($frameset->getRaw(), 'Stuff2'); + } + + function testFrameReplacementByName() { + $page = new MockSimplePage(); + $page->expectNever('getRaw'); + + $frame1 = new MockSimplePage(); + $frame1->setReturnValue('getRaw', 'Stuff1'); + + $frame2 = new MockSimplePage(); + $frame2->setReturnValue('getRaw', 'Stuff2'); + + $frameset = new SimpleFrameset($page); + $frameset->addFrame($frame1, 'a'); + $frameset->setFrame(array('a'), $frame2); + $this->assertEqual($frameset->getRaw(), 'Stuff2'); + } +} + +class TestOfFrameNavigation extends UnitTestCase { + + function testStartsWithoutFrameFocus() { + $page = new MockSimplePage(); + $frameset = new SimpleFrameset($page); + $frameset->addFrame(new MockSimplePage()); + $this->assertFalse($frameset->getFrameFocus()); + } + + function testCanFocusOnSingleFrame() { + $page = new MockSimplePage(); + $page->expectNever('getRaw'); + + $frame = new MockSimplePage(); + $frame->setReturnValue('getFrameFocus', array()); + $frame->setReturnValue('getRaw', 'Stuff'); + + $frameset = new SimpleFrameset($page); + $frameset->addFrame($frame); + + $this->assertFalse($frameset->setFrameFocusByIndex(0)); + $this->assertTrue($frameset->setFrameFocusByIndex(1)); + $this->assertEqual($frameset->getRaw(), 'Stuff'); + $this->assertFalse($frameset->setFrameFocusByIndex(2)); + $this->assertIdentical($frameset->getFrameFocus(), array(1)); + } + + function testContentComesFromFrameInFocus() { + $page = new MockSimplePage(); + + $frame1 = new MockSimplePage(); + $frame1->setReturnValue('getRaw', 'Stuff1'); + $frame1->setReturnValue('getFrameFocus', array()); + + $frame2 = new MockSimplePage(); + $frame2->setReturnValue('getRaw', 'Stuff2'); + $frame2->setReturnValue('getFrameFocus', array()); + + $frameset = new SimpleFrameset($page); + $frameset->addFrame($frame1); + $frameset->addFrame($frame2); + + $this->assertTrue($frameset->setFrameFocusByIndex(1)); + $this->assertEqual($frameset->getFrameFocus(), array(1)); + $this->assertEqual($frameset->getRaw(), 'Stuff1'); + + $this->assertTrue($frameset->setFrameFocusByIndex(2)); + $this->assertEqual($frameset->getFrameFocus(), array(2)); + $this->assertEqual($frameset->getRaw(), 'Stuff2'); + + $this->assertFalse($frameset->setFrameFocusByIndex(3)); + $this->assertEqual($frameset->getFrameFocus(), array(2)); + + $frameset->clearFrameFocus(); + $this->assertEqual($frameset->getRaw(), 'Stuff1Stuff2'); + } + + function testCanFocusByName() { + $page = new MockSimplePage(); + + $frame1 = new MockSimplePage(); + $frame1->setReturnValue('getRaw', 'Stuff1'); + $frame1->setReturnValue('getFrameFocus', array()); + + $frame2 = new MockSimplePage(); + $frame2->setReturnValue('getRaw', 'Stuff2'); + $frame2->setReturnValue('getFrameFocus', array()); + + $frameset = new SimpleFrameset($page); + $frameset->addFrame($frame1, 'A'); + $frameset->addFrame($frame2, 'B'); + + $this->assertTrue($frameset->setFrameFocus('A')); + $this->assertEqual($frameset->getFrameFocus(), array('A')); + $this->assertEqual($frameset->getRaw(), 'Stuff1'); + + $this->assertTrue($frameset->setFrameFocusByIndex(2)); + $this->assertEqual($frameset->getFrameFocus(), array('B')); + $this->assertEqual($frameset->getRaw(), 'Stuff2'); + + $this->assertFalse($frameset->setFrameFocus('z')); + + $frameset->clearFrameFocus(); + $this->assertEqual($frameset->getRaw(), 'Stuff1Stuff2'); + } +} + +class TestOfFramesetPageInterface extends UnitTestCase { + private $page_interface; + private $frameset_interface; + + function __construct() { + parent::__construct(); + $this->page_interface = $this->getPageMethods(); + $this->frameset_interface = $this->getFramesetMethods(); + } + + function assertListInAnyOrder($list, $expected) { + sort($list); + sort($expected); + $this->assertEqual($list, $expected); + } + + private function getPageMethods() { + $methods = array(); + foreach (get_class_methods('SimplePage') as $method) { + if (strtolower($method) == strtolower('SimplePage')) { + continue; + } + if (strtolower($method) == strtolower('getFrameset')) { + continue; + } + if (strncmp($method, '_', 1) == 0) { + continue; + } + if (strncmp($method, 'accept', 6) == 0) { + continue; + } + $methods[] = $method; + } + return $methods; + } + + private function getFramesetMethods() { + $methods = array(); + foreach (get_class_methods('SimpleFrameset') as $method) { + if (strtolower($method) == strtolower('SimpleFrameset')) { + continue; + } + if (strncmp($method, '_', 1) == 0) { + continue; + } + if (strncmp($method, 'add', 3) == 0) { + continue; + } + $methods[] = $method; + } + return $methods; + } + + function testFramsetHasPageInterface() { + $difference = array(); + foreach ($this->page_interface as $method) { + if (! in_array($method, $this->frameset_interface)) { + $this->fail("No [$method] in Frameset class"); + return; + } + } + $this->pass('Frameset covers Page interface'); + } + + function testHeadersReadFromFrameIfInFocus() { + $frame = new MockSimplePage(); + $frame->setReturnValue('getUrl', new SimpleUrl('http://localhost/stuff')); + + $frame->setReturnValue('getRequest', 'POST stuff'); + $frame->setReturnValue('getMethod', 'POST'); + $frame->setReturnValue('getRequestData', array('a' => 'A')); + $frame->setReturnValue('getHeaders', 'Header: content'); + $frame->setReturnValue('getMimeType', 'text/xml'); + $frame->setReturnValue('getResponseCode', 401); + $frame->setReturnValue('getTransportError', 'Could not parse headers'); + $frame->setReturnValue('getAuthentication', 'Basic'); + $frame->setReturnValue('getRealm', 'Safe place'); + + $frameset = new SimpleFrameset(new MockSimplePage()); + $frameset->addFrame($frame); + $frameset->setFrameFocusByIndex(1); + + $url = new SimpleUrl('http://localhost/stuff'); + $url->setTarget(1); + $this->assertIdentical($frameset->getUrl(), $url); + + $this->assertIdentical($frameset->getRequest(), 'POST stuff'); + $this->assertIdentical($frameset->getMethod(), 'POST'); + $this->assertIdentical($frameset->getRequestData(), array('a' => 'A')); + $this->assertIdentical($frameset->getHeaders(), 'Header: content'); + $this->assertIdentical($frameset->getMimeType(), 'text/xml'); + $this->assertIdentical($frameset->getResponseCode(), 401); + $this->assertIdentical($frameset->getTransportError(), 'Could not parse headers'); + $this->assertIdentical($frameset->getAuthentication(), 'Basic'); + $this->assertIdentical($frameset->getRealm(), 'Safe place'); + } + + function testUrlsComeFromBothFrames() { + $page = new MockSimplePage(); + $page->expectNever('getUrls'); + + $frame1 = new MockSimplePage(); + $frame1->setReturnValue( + 'getUrls', + array('http://www.lastcraft.com/', 'http://myserver/')); + + $frame2 = new MockSimplePage(); + $frame2->setReturnValue( + 'getUrls', + array('http://www.lastcraft.com/', 'http://test/')); + + $frameset = new SimpleFrameset($page); + $frameset->addFrame($frame1); + $frameset->addFrame($frame2); + $this->assertListInAnyOrder( + $frameset->getUrls(), + array('http://www.lastcraft.com/', 'http://myserver/', 'http://test/')); + } + + function testLabelledUrlsComeFromBothFrames() { + $frame1 = new MockSimplePage(); + $frame1->setReturnValue( + 'getUrlsByLabel', + array(new SimpleUrl('goodbye.php')), + array('a')); + + $frame2 = new MockSimplePage(); + $frame2->setReturnValue( + 'getUrlsByLabel', + array(new SimpleUrl('hello.php')), + array('a')); + + $frameset = new SimpleFrameset(new MockSimplePage()); + $frameset->addFrame($frame1); + $frameset->addFrame($frame2, 'Two'); + + $expected1 = new SimpleUrl('goodbye.php'); + $expected1->setTarget(1); + $expected2 = new SimpleUrl('hello.php'); + $expected2->setTarget('Two'); + $this->assertEqual( + $frameset->getUrlsByLabel('a'), + array($expected1, $expected2)); + } + + function testUrlByIdComesFromFirstFrameToRespond() { + $frame1 = new MockSimplePage(); + $frame1->setReturnValue('getUrlById', new SimpleUrl('four.php'), array(4)); + $frame1->setReturnValue('getUrlById', false, array(5)); + + $frame2 = new MockSimplePage(); + $frame2->setReturnValue('getUrlById', false, array(4)); + $frame2->setReturnValue('getUrlById', new SimpleUrl('five.php'), array(5)); + + $frameset = new SimpleFrameset(new MockSimplePage()); + $frameset->addFrame($frame1); + $frameset->addFrame($frame2); + + $four = new SimpleUrl('four.php'); + $four->setTarget(1); + $this->assertEqual($frameset->getUrlById(4), $four); + $five = new SimpleUrl('five.php'); + $five->setTarget(2); + $this->assertEqual($frameset->getUrlById(5), $five); + } + + function testReadUrlsFromFrameInFocus() { + $frame1 = new MockSimplePage(); + $frame1->setReturnValue('getUrls', array('a')); + $frame1->setReturnValue('getUrlsByLabel', array(new SimpleUrl('l'))); + $frame1->setReturnValue('getUrlById', new SimpleUrl('i')); + + $frame2 = new MockSimplePage(); + $frame2->expectNever('getUrls'); + $frame2->expectNever('getUrlsByLabel'); + $frame2->expectNever('getUrlById'); + + $frameset = new SimpleFrameset(new MockSimplePage()); + $frameset->addFrame($frame1, 'A'); + $frameset->addFrame($frame2, 'B'); + $frameset->setFrameFocus('A'); + + $this->assertIdentical($frameset->getUrls(), array('a')); + $expected = new SimpleUrl('l'); + $expected->setTarget('A'); + $this->assertIdentical($frameset->getUrlsByLabel('label'), array($expected)); + $expected = new SimpleUrl('i'); + $expected->setTarget('A'); + $this->assertIdentical($frameset->getUrlById(99), $expected); + } + + function testReadFrameTaggedUrlsFromFrameInFocus() { + $frame = new MockSimplePage(); + + $by_label = new SimpleUrl('l'); + $by_label->setTarget('L'); + $frame->setReturnValue('getUrlsByLabel', array($by_label)); + + $by_id = new SimpleUrl('i'); + $by_id->setTarget('I'); + $frame->setReturnValue('getUrlById', $by_id); + + $frameset = new SimpleFrameset(new MockSimplePage()); + $frameset->addFrame($frame, 'A'); + $frameset->setFrameFocus('A'); + + $this->assertIdentical($frameset->getUrlsByLabel('label'), array($by_label)); + $this->assertIdentical($frameset->getUrlById(99), $by_id); + } + + function testFindingFormsById() { + $frame = new MockSimplePage(); + $form = new MockSimpleForm(); + $frame->returns('getFormById', $form, array('a')); + + $frameset = new SimpleFrameset(new MockSimplePage()); + $frameset->addFrame(new MockSimplePage(), 'A'); + $frameset->addFrame($frame, 'B'); + $this->assertSame($frameset->getFormById('a'), $form); + + $frameset->setFrameFocus('A'); + $this->assertNull($frameset->getFormById('a')); + + $frameset->setFrameFocus('B'); + $this->assertSame($frameset->getFormById('a'), $form); + } + + function testFindingFormsBySubmit() { + $frame = new MockSimplePage(); + $form = new MockSimpleForm(); + $frame->returns( + 'getFormBySubmit', + $form, + array(new SimpleByLabel('a'))); + + $frameset = new SimpleFrameset(new MockSimplePage()); + $frameset->addFrame(new MockSimplePage(), 'A'); + $frameset->addFrame($frame, 'B'); + $this->assertSame($frameset->getFormBySubmit(new SimpleByLabel('a')), $form); + + $frameset->setFrameFocus('A'); + $this->assertNull($frameset->getFormBySubmit(new SimpleByLabel('a'))); + + $frameset->setFrameFocus('B'); + $this->assertSame($frameset->getFormBySubmit(new SimpleByLabel('a')), $form); + } + + function testFindingFormsByImage() { + $frame = new MockSimplePage(); + $form = new MockSimpleForm(); + $frame->returns( + 'getFormByImage', + $form, + array(new SimpleByLabel('a'))); + + $frameset = new SimpleFrameset(new MockSimplePage()); + $frameset->addFrame(new MockSimplePage(), 'A'); + $frameset->addFrame($frame, 'B'); + $this->assertSame($frameset->getFormByImage(new SimpleByLabel('a')), $form); + + $frameset->setFrameFocus('A'); + $this->assertNull($frameset->getFormByImage(new SimpleByLabel('a'))); + + $frameset->setFrameFocus('B'); + $this->assertSame($frameset->getFormByImage(new SimpleByLabel('a')), $form); + } + + function testSettingAllFrameFieldsWhenNoFrameFocus() { + $frame1 = new MockSimplePage(); + $frame1->expectOnce('setField', array(new SimpleById(22), 'A')); + + $frame2 = new MockSimplePage(); + $frame2->expectOnce('setField', array(new SimpleById(22), 'A')); + + $frameset = new SimpleFrameset(new MockSimplePage()); + $frameset->addFrame($frame1, 'A'); + $frameset->addFrame($frame2, 'B'); + $frameset->setField(new SimpleById(22), 'A'); + } + + function testOnlySettingFieldFromFocusedFrame() { + $frame1 = new MockSimplePage(); + $frame1->expectOnce('setField', array(new SimpleByLabelOrName('a'), 'A')); + + $frame2 = new MockSimplePage(); + $frame2->expectNever('setField'); + + $frameset = new SimpleFrameset(new MockSimplePage()); + $frameset->addFrame($frame1, 'A'); + $frameset->addFrame($frame2, 'B'); + $frameset->setFrameFocus('A'); + $frameset->setField(new SimpleByLabelOrName('a'), 'A'); + } + + function testOnlyGettingFieldFromFocusedFrame() { + $frame1 = new MockSimplePage(); + $frame1->setReturnValue('getField', 'f', array(new SimpleByName('a'))); + + $frame2 = new MockSimplePage(); + $frame2->expectNever('getField'); + + $frameset = new SimpleFrameset(new MockSimplePage()); + $frameset->addFrame($frame1, 'A'); + $frameset->addFrame($frame2, 'B'); + $frameset->setFrameFocus('A'); + $this->assertIdentical($frameset->getField(new SimpleByName('a')), 'f'); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/http_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/http_test.php new file mode 100644 index 0000000..3cf3d16 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/http_test.php @@ -0,0 +1,424 @@ +expectAt(0, 'write', array("GET /here.html HTTP/1.0\r\n")); + $socket->expectAt(1, 'write', array("Host: a.valid.host\r\n")); + $socket->expectAt(2, 'write', array("Connection: close\r\n")); + $socket->expectCallCount('write', 3); + $route = new PartialSimpleRoute(); + $route->setReturnReference('createSocket', $socket); + $route->__construct(new SimpleUrl('http://a.valid.host/here.html')); + $this->assertSame($route->createConnection('GET', 15), $socket); + } + + function testDefaultPostRequest() { + $socket = new MockSimpleSocket(); + $socket->expectAt(0, 'write', array("POST /here.html HTTP/1.0\r\n")); + $socket->expectAt(1, 'write', array("Host: a.valid.host\r\n")); + $socket->expectAt(2, 'write', array("Connection: close\r\n")); + $socket->expectCallCount('write', 3); + + $route = new PartialSimpleRoute(); + $route->setReturnReference('createSocket', $socket); + $route->__construct(new SimpleUrl('http://a.valid.host/here.html')); + + $route->createConnection('POST', 15); + } + + function testGetWithPort() { + $socket = new MockSimpleSocket(); + $socket->expectAt(0, 'write', array("GET /here.html HTTP/1.0\r\n")); + $socket->expectAt(1, 'write', array("Host: a.valid.host:81\r\n")); + $socket->expectAt(2, 'write', array("Connection: close\r\n")); + $socket->expectCallCount('write', 3); + + $route = new PartialSimpleRoute(); + $route->setReturnReference('createSocket', $socket); + $route->__construct(new SimpleUrl('http://a.valid.host:81/here.html')); + + $route->createConnection('GET', 15); + } + + function testGetWithParameters() { + $socket = new MockSimpleSocket(); + $socket->expectAt(0, 'write', array("GET /here.html?a=1&b=2 HTTP/1.0\r\n")); + $socket->expectAt(1, 'write', array("Host: a.valid.host\r\n")); + $socket->expectAt(2, 'write', array("Connection: close\r\n")); + $socket->expectCallCount('write', 3); + + $route = new PartialSimpleRoute(); + $route->setReturnReference('createSocket', $socket); + $route->__construct(new SimpleUrl('http://a.valid.host/here.html?a=1&b=2')); + + $route->createConnection('GET', 15); + } +} + +class TestOfProxyRoute extends UnitTestCase { + + function testDefaultGet() { + $socket = new MockSimpleSocket(); + $socket->expectAt(0, 'write', array("GET http://a.valid.host/here.html HTTP/1.0\r\n")); + $socket->expectAt(1, 'write', array("Host: my-proxy:8080\r\n")); + $socket->expectAt(2, 'write', array("Connection: close\r\n")); + $socket->expectCallCount('write', 3); + + $route = new PartialSimpleProxyRoute(); + $route->setReturnReference('createSocket', $socket); + $route->__construct( + new SimpleUrl('http://a.valid.host/here.html'), + new SimpleUrl('http://my-proxy')); + $route->createConnection('GET', 15); + } + + function testDefaultPost() { + $socket = new MockSimpleSocket(); + $socket->expectAt(0, 'write', array("POST http://a.valid.host/here.html HTTP/1.0\r\n")); + $socket->expectAt(1, 'write', array("Host: my-proxy:8080\r\n")); + $socket->expectAt(2, 'write', array("Connection: close\r\n")); + $socket->expectCallCount('write', 3); + + $route = new PartialSimpleProxyRoute(); + $route->setReturnReference('createSocket', $socket); + $route->__construct( + new SimpleUrl('http://a.valid.host/here.html'), + new SimpleUrl('http://my-proxy')); + $route->createConnection('POST', 15); + } + + function testGetWithPort() { + $socket = new MockSimpleSocket(); + $socket->expectAt(0, 'write', array("GET http://a.valid.host:81/here.html HTTP/1.0\r\n")); + $socket->expectAt(1, 'write', array("Host: my-proxy:8081\r\n")); + $socket->expectAt(2, 'write', array("Connection: close\r\n")); + $socket->expectCallCount('write', 3); + + $route = new PartialSimpleProxyRoute(); + $route->setReturnReference('createSocket', $socket); + $route->__construct( + new SimpleUrl('http://a.valid.host:81/here.html'), + new SimpleUrl('http://my-proxy:8081')); + $route->createConnection('GET', 15); + } + + function testGetWithParameters() { + $socket = new MockSimpleSocket(); + $socket->expectAt(0, 'write', array("GET http://a.valid.host/here.html?a=1&b=2 HTTP/1.0\r\n")); + $socket->expectAt(1, 'write', array("Host: my-proxy:8080\r\n")); + $socket->expectAt(2, 'write', array("Connection: close\r\n")); + $socket->expectCallCount('write', 3); + + $route = new PartialSimpleProxyRoute(); + $route->setReturnReference('createSocket', $socket); + $route->__construct( + new SimpleUrl('http://a.valid.host/here.html?a=1&b=2'), + new SimpleUrl('http://my-proxy')); + $route->createConnection('GET', 15); + } + + function testGetWithAuthentication() { + $encoded = base64_encode('Me:Secret'); + + $socket = new MockSimpleSocket(); + $socket->expectAt(0, 'write', array("GET http://a.valid.host/here.html HTTP/1.0\r\n")); + $socket->expectAt(1, 'write', array("Host: my-proxy:8080\r\n")); + $socket->expectAt(2, 'write', array("Proxy-Authorization: Basic $encoded\r\n")); + $socket->expectAt(3, 'write', array("Connection: close\r\n")); + $socket->expectCallCount('write', 4); + + $route = new PartialSimpleProxyRoute(); + $route->setReturnReference('createSocket', $socket); + $route->__construct( + new SimpleUrl('http://a.valid.host/here.html'), + new SimpleUrl('http://my-proxy'), + 'Me', + 'Secret'); + $route->createConnection('GET', 15); + } +} + +class TestOfHttpRequest extends UnitTestCase { + + function testReadingBadConnection() { + $socket = new MockSimpleSocket(); + $route = new MockSimpleRoute(); + $route->setReturnReference('createConnection', $socket); + $request = new SimpleHttpRequest($route, new SimpleGetEncoding()); + $reponse = $request->fetch(15); + $this->assertTrue($reponse->isError()); + } + + function testReadingGoodConnection() { + $socket = new MockSimpleSocket(); + $socket->expectOnce('write', array("\r\n")); + + $route = new MockSimpleRoute(); + $route->setReturnReference('createConnection', $socket); + $route->expect('createConnection', array('GET', 15)); + + $request = new SimpleHttpRequest($route, new SimpleGetEncoding()); + $this->assertIsA($request->fetch(15), 'SimpleHttpResponse'); + } + + function testWritingAdditionalHeaders() { + $socket = new MockSimpleSocket(); + $socket->expectAt(0, 'write', array("My: stuff\r\n")); + $socket->expectAt(1, 'write', array("\r\n")); + $socket->expectCallCount('write', 2); + + $route = new MockSimpleRoute(); + $route->setReturnReference('createConnection', $socket); + + $request = new SimpleHttpRequest($route, new SimpleGetEncoding()); + $request->addHeaderLine('My: stuff'); + $request->fetch(15); + } + + function testCookieWriting() { + $socket = new MockSimpleSocket(); + $socket->expectAt(0, 'write', array("Cookie: a=A\r\n")); + $socket->expectAt(1, 'write', array("\r\n")); + $socket->expectCallCount('write', 2); + + $route = new MockSimpleRoute(); + $route->setReturnReference('createConnection', $socket); + + $jar = new SimpleCookieJar(); + $jar->setCookie('a', 'A'); + + $request = new SimpleHttpRequest($route, new SimpleGetEncoding()); + $request->readCookiesFromJar($jar, new SimpleUrl('/')); + $this->assertIsA($request->fetch(15), 'SimpleHttpResponse'); + } + + function testMultipleCookieWriting() { + $socket = new MockSimpleSocket(); + $socket->expectAt(0, 'write', array("Cookie: a=A;b=B\r\n")); + + $route = new MockSimpleRoute(); + $route->setReturnReference('createConnection', $socket); + + $jar = new SimpleCookieJar(); + $jar->setCookie('a', 'A'); + $jar->setCookie('b', 'B'); + + $request = new SimpleHttpRequest($route, new SimpleGetEncoding()); + $request->readCookiesFromJar($jar, new SimpleUrl('/')); + $request->fetch(15); + } +} + +class TestOfHttpPostRequest extends UnitTestCase { + + function testReadingBadConnectionCausesErrorBecauseOfDeadSocket() { + $socket = new MockSimpleSocket(); + $route = new MockSimpleRoute(); + $route->setReturnReference('createConnection', $socket); + $request = new SimpleHttpRequest($route, new SimplePostEncoding()); + $reponse = $request->fetch(15); + $this->assertTrue($reponse->isError()); + } + + function testReadingGoodConnection() { + $socket = new MockSimpleSocket(); + $socket->expectAt(0, 'write', array("Content-Length: 0\r\n")); + $socket->expectAt(1, 'write', array("Content-Type: application/x-www-form-urlencoded\r\n")); + $socket->expectAt(2, 'write', array("\r\n")); + $socket->expectAt(3, 'write', array("")); + + $route = new MockSimpleRoute(); + $route->setReturnReference('createConnection', $socket); + $route->expect('createConnection', array('POST', 15)); + + $request = new SimpleHttpRequest($route, new SimplePostEncoding()); + $this->assertIsA($request->fetch(15), 'SimpleHttpResponse'); + } + + function testContentHeadersCalculated() { + $socket = new MockSimpleSocket(); + $socket->expectAt(0, 'write', array("Content-Length: 3\r\n")); + $socket->expectAt(1, 'write', array("Content-Type: application/x-www-form-urlencoded\r\n")); + $socket->expectAt(2, 'write', array("\r\n")); + $socket->expectAt(3, 'write', array("a=A")); + + $route = new MockSimpleRoute(); + $route->setReturnReference('createConnection', $socket); + $route->expect('createConnection', array('POST', 15)); + + $request = new SimpleHttpRequest( + $route, + new SimplePostEncoding(array('a' => 'A'))); + $this->assertIsA($request->fetch(15), 'SimpleHttpResponse'); + } +} + +class TestOfHttpHeaders extends UnitTestCase { + + function testParseBasicHeaders() { + $headers = new SimpleHttpHeaders( + "HTTP/1.1 200 OK\r\n" . + "Date: Mon, 18 Nov 2002 15:50:29 GMT\r\n" . + "Content-Type: text/plain\r\n" . + "Server: Apache/1.3.24 (Win32) PHP/4.2.3\r\n" . + "Connection: close"); + $this->assertIdentical($headers->getHttpVersion(), "1.1"); + $this->assertIdentical($headers->getResponseCode(), 200); + $this->assertEqual($headers->getMimeType(), "text/plain"); + } + + function testNonStandardResponseHeader() { + $headers = new SimpleHttpHeaders( + "HTTP/1.1 302 (HTTP-Version SP Status-Code CRLF)\r\n" . + "Connection: close"); + $this->assertIdentical($headers->getResponseCode(), 302); + } + + function testCanParseMultipleCookies() { + $jar = new MockSimpleCookieJar(); + $jar->expectAt(0, 'setCookie', array('a', 'aaa', 'host', '/here/', 'Wed, 25 Dec 2002 04:24:20 GMT')); + $jar->expectAt(1, 'setCookie', array('b', 'bbb', 'host', '/', false)); + + $headers = new SimpleHttpHeaders( + "HTTP/1.1 200 OK\r\n" . + "Date: Mon, 18 Nov 2002 15:50:29 GMT\r\n" . + "Content-Type: text/plain\r\n" . + "Server: Apache/1.3.24 (Win32) PHP/4.2.3\r\n" . + "Set-Cookie: a=aaa; expires=Wed, 25-Dec-02 04:24:20 GMT; path=/here/\r\n" . + "Set-Cookie: b=bbb\r\n" . + "Connection: close"); + $headers->writeCookiesToJar($jar, new SimpleUrl('http://host')); + } + + function testCanRecogniseRedirect() { + $headers = new SimpleHttpHeaders("HTTP/1.1 301 OK\r\n" . + "Content-Type: text/plain\r\n" . + "Content-Length: 0\r\n" . + "Location: http://www.somewhere-else.com/\r\n" . + "Connection: close"); + $this->assertIdentical($headers->getResponseCode(), 301); + $this->assertEqual($headers->getLocation(), "http://www.somewhere-else.com/"); + $this->assertTrue($headers->isRedirect()); + } + + function testCanParseChallenge() { + $headers = new SimpleHttpHeaders("HTTP/1.1 401 Authorization required\r\n" . + "Content-Type: text/plain\r\n" . + "Connection: close\r\n" . + "WWW-Authenticate: Basic realm=\"Somewhere\""); + $this->assertEqual($headers->getAuthentication(), 'Basic'); + $this->assertEqual($headers->getRealm(), 'Somewhere'); + $this->assertTrue($headers->isChallenge()); + } +} + +class TestOfHttpResponse extends UnitTestCase { + + function testBadRequest() { + $socket = new MockSimpleSocket(); + $socket->setReturnValue('getSent', ''); + + $response = new SimpleHttpResponse($socket, new SimpleUrl('here'), new SimpleGetEncoding()); + $this->assertTrue($response->isError()); + $this->assertPattern('/Nothing fetched/', $response->getError()); + $this->assertIdentical($response->getContent(), false); + $this->assertIdentical($response->getSent(), ''); + } + + function testBadSocketDuringResponse() { + $socket = new MockSimpleSocket(); + $socket->setReturnValueAt(0, "read", "HTTP/1.1 200 OK\r\n"); + $socket->setReturnValueAt(1, "read", "Date: Mon, 18 Nov 2002 15:50:29 GMT\r\n"); + $socket->setReturnValue("read", ""); + $socket->setReturnValue('getSent', 'HTTP/1.1 ...'); + + $response = new SimpleHttpResponse($socket, new SimpleUrl('here'), new SimpleGetEncoding()); + $this->assertTrue($response->isError()); + $this->assertEqual($response->getContent(), ''); + $this->assertEqual($response->getSent(), 'HTTP/1.1 ...'); + } + + function testIncompleteHeader() { + $socket = new MockSimpleSocket(); + $socket->setReturnValueAt(0, "read", "HTTP/1.1 200 OK\r\n"); + $socket->setReturnValueAt(1, "read", "Date: Mon, 18 Nov 2002 15:50:29 GMT\r\n"); + $socket->setReturnValueAt(2, "read", "Content-Type: text/plain\r\n"); + $socket->setReturnValue("read", ""); + + $response = new SimpleHttpResponse($socket, new SimpleUrl('here'), new SimpleGetEncoding()); + $this->assertTrue($response->isError()); + $this->assertEqual($response->getContent(), ""); + } + + function testParseOfResponseHeadersWhenChunked() { + $socket = new MockSimpleSocket(); + $socket->setReturnValueAt(0, "read", "HTTP/1.1 200 OK\r\nDate: Mon, 18 Nov 2002 15:50:29 GMT\r\n"); + $socket->setReturnValueAt(1, "read", "Content-Type: text/plain\r\n"); + $socket->setReturnValueAt(2, "read", "Server: Apache/1.3.24 (Win32) PHP/4.2.3\r\nConne"); + $socket->setReturnValueAt(3, "read", "ction: close\r\n\r\nthis is a test file\n"); + $socket->setReturnValueAt(4, "read", "with two lines in it\n"); + $socket->setReturnValue("read", ""); + + $response = new SimpleHttpResponse($socket, new SimpleUrl('here'), new SimpleGetEncoding()); + $this->assertFalse($response->isError()); + $this->assertEqual( + $response->getContent(), + "this is a test file\nwith two lines in it\n"); + $headers = $response->getHeaders(); + $this->assertIdentical($headers->getHttpVersion(), "1.1"); + $this->assertIdentical($headers->getResponseCode(), 200); + $this->assertEqual($headers->getMimeType(), "text/plain"); + $this->assertFalse($headers->isRedirect()); + $this->assertFalse($headers->getLocation()); + } + + function testRedirect() { + $socket = new MockSimpleSocket(); + $socket->setReturnValueAt(0, "read", "HTTP/1.1 301 OK\r\n"); + $socket->setReturnValueAt(1, "read", "Content-Type: text/plain\r\n"); + $socket->setReturnValueAt(2, "read", "Location: http://www.somewhere-else.com/\r\n"); + $socket->setReturnValueAt(3, "read", "Connection: close\r\n"); + $socket->setReturnValueAt(4, "read", "\r\n"); + $socket->setReturnValue("read", ""); + + $response = new SimpleHttpResponse($socket, new SimpleUrl('here'), new SimpleGetEncoding()); + $headers = $response->getHeaders(); + $this->assertTrue($headers->isRedirect()); + $this->assertEqual($headers->getLocation(), "http://www.somewhere-else.com/"); + } + + function testRedirectWithPort() { + $socket = new MockSimpleSocket(); + $socket->setReturnValueAt(0, "read", "HTTP/1.1 301 OK\r\n"); + $socket->setReturnValueAt(1, "read", "Content-Type: text/plain\r\n"); + $socket->setReturnValueAt(2, "read", "Location: http://www.somewhere-else.com:80/\r\n"); + $socket->setReturnValueAt(3, "read", "Connection: close\r\n"); + $socket->setReturnValueAt(4, "read", "\r\n"); + $socket->setReturnValue("read", ""); + + $response = new SimpleHttpResponse($socket, new SimpleUrl('here'), new SimpleGetEncoding()); + $headers = $response->getHeaders(); + $this->assertTrue($headers->isRedirect()); + $this->assertEqual($headers->getLocation(), "http://www.somewhere-else.com:80/"); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/interfaces_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/interfaces_test.php new file mode 100644 index 0000000..83c24d4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/interfaces_test.php @@ -0,0 +1,137 @@ +=')) { + include(dirname(__FILE__) . '/interfaces_test_php5_1.php'); +} + +interface DummyInterface { + function aMethod(); + function anotherMethod($a); + function &referenceMethod(&$a); +} + +Mock::generate('DummyInterface'); +Mock::generatePartial('DummyInterface', 'PartialDummyInterface', array()); + +class TestOfMockInterfaces extends UnitTestCase { + + function testCanMockAnInterface() { + $mock = new MockDummyInterface(); + $this->assertIsA($mock, 'SimpleMock'); + $this->assertIsA($mock, 'MockDummyInterface'); + $this->assertTrue(method_exists($mock, 'aMethod')); + $this->assertTrue(method_exists($mock, 'anotherMethod')); + $this->assertNull($mock->aMethod()); + } + + function testMockedInterfaceExpectsParameters() { + $mock = new MockDummyInterface(); + $this->expectError(); + $mock->anotherMethod(); + } + + function testCannotPartiallyMockAnInterface() { + $this->assertFalse(class_exists('PartialDummyInterface')); + } +} + +class TestOfSpl extends UnitTestCase { + + function skip() { + $this->skipUnless(function_exists('spl_classes'), 'No SPL module loaded'); + } + + function testCanMockAllSplClasses() { + if (! function_exists('spl_classes')) { + return; + } + foreach(spl_classes() as $class) { + if ($class == 'SplHeap') { + continue; + } + if (version_compare(PHP_VERSION, '5.1', '<') && + $class == 'CachingIterator' || + $class == 'CachingRecursiveIterator' || + $class == 'FilterIterator' || + $class == 'LimitIterator' || + $class == 'ParentIterator') { + // These iterators require an iterator be passed to them during + // construction in PHP 5.0; there is no way for SimpleTest + // to supply such an iterator, however, so support for it is + // disabled. + continue; + } + $mock_class = "Mock$class"; + Mock::generate($class); + $this->assertIsA(new $mock_class(), $mock_class); + } + } + + function testExtensionOfCommonSplClasses() { + Mock::generate('IteratorImplementation'); + $this->assertIsA( + new IteratorImplementation(), + 'IteratorImplementation'); + Mock::generate('IteratorAggregateImplementation'); + $this->assertIsA( + new IteratorAggregateImplementation(), + 'IteratorAggregateImplementation'); + } +} + +class WithHint { + function hinted(DummyInterface $object) { } +} + +class ImplementsDummy implements DummyInterface { + function aMethod() { } + function anotherMethod($a) { } + function &referenceMethod(&$a) { } + function extraMethod($a = false) { } +} +Mock::generate('ImplementsDummy'); + +class TestOfImplementations extends UnitTestCase { + + function testMockedInterfaceCanPassThroughTypeHint() { + $mock = new MockDummyInterface(); + $hinter = new WithHint(); + $hinter->hinted($mock); + } + + function testImplementedInterfacesAreCarried() { + $mock = new MockImplementsDummy(); + $hinter = new WithHint(); + $hinter->hinted($mock); + } + + function testNoSpuriousWarningsWhenSkippingDefaultedParameter() { + $mock = new MockImplementsDummy(); + $mock->extraMethod(); + } +} + +interface SampleInterfaceWithConstruct { + function __construct($something); +} + +class TestOfInterfaceMocksWithConstruct extends UnitTestCase { + function TODO_testBasicConstructOfAnInterface() { // Fails in PHP 5.3dev + Mock::generate('SampleInterfaceWithConstruct'); + } +} + +interface SampleInterfaceWithClone { + function __clone(); +} + +class TestOfSampleInterfaceWithClone extends UnitTestCase { + function testCanMockWithoutErrors() { + Mock::generate('SampleInterfaceWithClone'); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/interfaces_test_php5_1.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/interfaces_test_php5_1.php new file mode 100644 index 0000000..3d154f9 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/interfaces_test_php5_1.php @@ -0,0 +1,14 @@ +assertIsA($mock, 'SampleInterfaceWithHintInSignature'); + } +} + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/live_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/live_test.php new file mode 100644 index 0000000..3fbb544 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/live_test.php @@ -0,0 +1,47 @@ +assertTrue($socket->isError()); + $this->assertPattern( + '/Cannot open \\[bad_url:111\\] with \\[/', + $socket->getError()); + $this->assertFalse($socket->isOpen()); + $this->assertFalse($socket->write('A message')); + } + + function testSocketClosure() { + $socket = new SimpleSocket('www.lastcraft.com', 80, 15, 8); + $this->assertTrue($socket->isOpen()); + $this->assertTrue($socket->write("GET /test/network_confirm.php HTTP/1.0\r\n")); + $socket->write("Host: www.lastcraft.com\r\n"); + $socket->write("Connection: close\r\n\r\n"); + $this->assertEqual($socket->read(), "HTTP/1.1"); + $socket->close(); + $this->assertIdentical($socket->read(), false); + } + + function testRecordOfSentCharacters() { + $socket = new SimpleSocket('www.lastcraft.com', 80, 15); + $this->assertTrue($socket->write("GET /test/network_confirm.php HTTP/1.0\r\n")); + $socket->write("Host: www.lastcraft.com\r\n"); + $socket->write("Connection: close\r\n\r\n"); + $socket->close(); + $this->assertEqual($socket->getSent(), + "GET /test/network_confirm.php HTTP/1.0\r\n" . + "Host: www.lastcraft.com\r\n" . + "Connection: close\r\n\r\n"); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/mock_objects_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/mock_objects_test.php new file mode 100644 index 0000000..aac2396 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/mock_objects_test.php @@ -0,0 +1,1012 @@ +assertTrue($expectation->test(33)); + $this->assertTrue($expectation->test(false)); + $this->assertTrue($expectation->test(null)); + } +} + +class TestOfParametersExpectation extends UnitTestCase { + + function testEmptyMatch() { + $expectation = new ParametersExpectation(array()); + $this->assertTrue($expectation->test(array())); + $this->assertFalse($expectation->test(array(33))); + } + + function testSingleMatch() { + $expectation = new ParametersExpectation(array(0)); + $this->assertFalse($expectation->test(array(1))); + $this->assertTrue($expectation->test(array(0))); + } + + function testAnyMatch() { + $expectation = new ParametersExpectation(false); + $this->assertTrue($expectation->test(array())); + $this->assertTrue($expectation->test(array(1, 2))); + } + + function testMissingParameter() { + $expectation = new ParametersExpectation(array(0)); + $this->assertFalse($expectation->test(array())); + } + + function testNullParameter() { + $expectation = new ParametersExpectation(array(null)); + $this->assertTrue($expectation->test(array(null))); + $this->assertFalse($expectation->test(array())); + } + + function testAnythingExpectations() { + $expectation = new ParametersExpectation(array(new AnythingExpectation())); + $this->assertFalse($expectation->test(array())); + $this->assertIdentical($expectation->test(array(null)), true); + $this->assertIdentical($expectation->test(array(13)), true); + } + + function testOtherExpectations() { + $expectation = new ParametersExpectation( + array(new PatternExpectation('/hello/i'))); + $this->assertFalse($expectation->test(array('Goodbye'))); + $this->assertTrue($expectation->test(array('hello'))); + $this->assertTrue($expectation->test(array('Hello'))); + } + + function testIdentityOnly() { + $expectation = new ParametersExpectation(array("0")); + $this->assertFalse($expectation->test(array(0))); + $this->assertTrue($expectation->test(array("0"))); + } + + function testLongList() { + $expectation = new ParametersExpectation( + array("0", 0, new AnythingExpectation(), false)); + $this->assertTrue($expectation->test(array("0", 0, 37, false))); + $this->assertFalse($expectation->test(array("0", 0, 37, true))); + $this->assertFalse($expectation->test(array("0", 0, 37))); + } +} + +class TestOfSimpleSignatureMap extends UnitTestCase { + + function testEmpty() { + $map = new SimpleSignatureMap(); + $this->assertFalse($map->isMatch("any", array())); + $this->assertNull($map->findFirstAction("any", array())); + } + + function testDifferentCallSignaturesCanHaveDifferentReferences() { + $map = new SimpleSignatureMap(); + $fred = 'Fred'; + $jim = 'jim'; + $map->add(array(0), $fred); + $map->add(array('0'), $jim); + $this->assertSame($fred, $map->findFirstAction(array(0))); + $this->assertSame($jim, $map->findFirstAction(array('0'))); + } + + function testWildcard() { + $fred = 'Fred'; + $map = new SimpleSignatureMap(); + $map->add(array(new AnythingExpectation(), 1, 3), $fred); + $this->assertTrue($map->isMatch(array(2, 1, 3))); + $this->assertSame($map->findFirstAction(array(2, 1, 3)), $fred); + } + + function testAllWildcard() { + $fred = 'Fred'; + $map = new SimpleSignatureMap(); + $this->assertFalse($map->isMatch(array(2, 1, 3))); + $map->add('', $fred); + $this->assertTrue($map->isMatch(array(2, 1, 3))); + $this->assertSame($map->findFirstAction(array(2, 1, 3)), $fred); + } + + function testOrdering() { + $map = new SimpleSignatureMap(); + $map->add(array(1, 2), new SimpleByValue("1, 2")); + $map->add(array(1, 3), new SimpleByValue("1, 3")); + $map->add(array(1), new SimpleByValue("1")); + $map->add(array(1, 4), new SimpleByValue("1, 4")); + $map->add(array(new AnythingExpectation()), new SimpleByValue("Any")); + $map->add(array(2), new SimpleByValue("2")); + $map->add("", new SimpleByValue("Default")); + $map->add(array(), new SimpleByValue("None")); + $this->assertEqual($map->findFirstAction(array(1, 2)), new SimpleByValue("1, 2")); + $this->assertEqual($map->findFirstAction(array(1, 3)), new SimpleByValue("1, 3")); + $this->assertEqual($map->findFirstAction(array(1, 4)), new SimpleByValue("1, 4")); + $this->assertEqual($map->findFirstAction(array(1)), new SimpleByValue("1")); + $this->assertEqual($map->findFirstAction(array(2)), new SimpleByValue("Any")); + $this->assertEqual($map->findFirstAction(array(3)), new SimpleByValue("Any")); + $this->assertEqual($map->findFirstAction(array()), new SimpleByValue("Default")); + } +} + +class TestOfCallSchedule extends UnitTestCase { + function testCanBeSetToAlwaysReturnTheSameReference() { + $a = 5; + $schedule = new SimpleCallSchedule(); + $schedule->register('aMethod', false, new SimpleByReference($a)); + $this->assertReference($schedule->respond(0, 'aMethod', array()), $a); + $this->assertReference($schedule->respond(1, 'aMethod', array()), $a); + } + + function testSpecificSignaturesOverrideTheAlwaysCase() { + $any = 'any'; + $one = 'two'; + $schedule = new SimpleCallSchedule(); + $schedule->register('aMethod', array(1), new SimpleByReference($one)); + $schedule->register('aMethod', false, new SimpleByReference($any)); + $this->assertReference($schedule->respond(0, 'aMethod', array(2)), $any); + $this->assertReference($schedule->respond(0, 'aMethod', array(1)), $one); + } + + function testReturnsCanBeSetOverTime() { + $one = 'one'; + $two = 'two'; + $schedule = new SimpleCallSchedule(); + $schedule->registerAt(0, 'aMethod', false, new SimpleByReference($one)); + $schedule->registerAt(1, 'aMethod', false, new SimpleByReference($two)); + $this->assertReference($schedule->respond(0, 'aMethod', array()), $one); + $this->assertReference($schedule->respond(1, 'aMethod', array()), $two); + } + + function testReturnsOverTimecanBeAlteredByTheArguments() { + $one = '1'; + $two = '2'; + $two_a = '2a'; + $schedule = new SimpleCallSchedule(); + $schedule->registerAt(0, 'aMethod', false, new SimpleByReference($one)); + $schedule->registerAt(1, 'aMethod', array('a'), new SimpleByReference($two_a)); + $schedule->registerAt(1, 'aMethod', false, new SimpleByReference($two)); + $this->assertReference($schedule->respond(0, 'aMethod', array()), $one); + $this->assertReference($schedule->respond(1, 'aMethod', array()), $two); + $this->assertReference($schedule->respond(1, 'aMethod', array('a')), $two_a); + } + + function testCanReturnByValue() { + $a = 5; + $schedule = new SimpleCallSchedule(); + $schedule->register('aMethod', false, new SimpleByValue($a)); + $this->assertCopy($schedule->respond(0, 'aMethod', array()), $a); + } + + function testCanThrowException() { + if (version_compare(phpversion(), '5', '>=')) { + $schedule = new SimpleCallSchedule(); + $schedule->register('aMethod', false, new SimpleThrower(new Exception('Ouch'))); + $this->expectException(new Exception('Ouch')); + $schedule->respond(0, 'aMethod', array()); + } + } + + function testCanEmitError() { + $schedule = new SimpleCallSchedule(); + $schedule->register('aMethod', false, new SimpleErrorThrower('Ouch', E_USER_WARNING)); + $this->expectError('Ouch'); + $schedule->respond(0, 'aMethod', array()); + } +} + +class Dummy { + function Dummy() { + } + + function aMethod() { + return true; + } + + function &aReferenceMethod() { + return true; + } + + function anotherMethod() { + return true; + } +} +Mock::generate('Dummy'); +Mock::generate('Dummy', 'AnotherMockDummy'); +Mock::generate('Dummy', 'MockDummyWithExtraMethods', array('extraMethod')); + +class TestOfMockGeneration extends UnitTestCase { + + function testCloning() { + $mock = new MockDummy(); + $this->assertTrue(method_exists($mock, "aMethod")); + $this->assertNull($mock->aMethod()); + } + + function testCloningWithExtraMethod() { + $mock = new MockDummyWithExtraMethods(); + $this->assertTrue(method_exists($mock, "extraMethod")); + } + + function testCloningWithChosenClassName() { + $mock = new AnotherMockDummy(); + $this->assertTrue(method_exists($mock, "aMethod")); + } +} + +class TestOfMockReturns extends UnitTestCase { + + function testDefaultReturn() { + $mock = new MockDummy(); + $mock->setReturnValue("aMethod", "aaa"); + $this->assertIdentical($mock->aMethod(), "aaa"); + $this->assertIdentical($mock->aMethod(), "aaa"); + } + + function testParameteredReturn() { + $mock = new MockDummy(); + $mock->setReturnValue('aMethod', 'aaa', array(1, 2, 3)); + $this->assertNull($mock->aMethod()); + $this->assertIdentical($mock->aMethod(1, 2, 3), 'aaa'); + } + + function testSetReturnGivesObjectReference() { + $mock = new MockDummy(); + $object = new Dummy(); + $mock->returns('aMethod', $object, array(1, 2, 3)); + $this->assertSame($mock->aMethod(1, 2, 3), $object); + } + + function testSetReturnReferenceGivesOriginalReference() { + $mock = new MockDummy(); + $object = 1; + $mock->setReturnReference('aReferenceMethod', $object, array(1, 2, 3)); + $this->assertReference($mock->aReferenceMethod(1, 2, 3), $object); + } + + function testPatternMatchReturn() { + $mock = new MockDummy(); + $mock->setReturnValue( + "aMethod", + "aaa", + array(new PatternExpectation('/hello/i'))); + $this->assertIdentical($mock->aMethod('Hello'), "aaa"); + $this->assertNull($mock->aMethod('Goodbye')); + } + + function testMultipleMethods() { + $mock = new MockDummy(); + $mock->setReturnValue("aMethod", 100, array(1)); + $mock->setReturnValue("aMethod", 200, array(2)); + $mock->setReturnValue("anotherMethod", 10, array(1)); + $mock->setReturnValue("anotherMethod", 20, array(2)); + $this->assertIdentical($mock->aMethod(1), 100); + $this->assertIdentical($mock->anotherMethod(1), 10); + $this->assertIdentical($mock->aMethod(2), 200); + $this->assertIdentical($mock->anotherMethod(2), 20); + } + + function testReturnSequence() { + $mock = new MockDummy(); + $mock->setReturnValueAt(0, "aMethod", "aaa"); + $mock->setReturnValueAt(1, "aMethod", "bbb"); + $mock->setReturnValueAt(3, "aMethod", "ddd"); + $this->assertIdentical($mock->aMethod(), "aaa"); + $this->assertIdentical($mock->aMethod(), "bbb"); + $this->assertNull($mock->aMethod()); + $this->assertIdentical($mock->aMethod(), "ddd"); + } + + function testSetReturnReferenceAtGivesOriginal() { + $mock = new MockDummy(); + $object = 100; + $mock->setReturnReferenceAt(1, "aReferenceMethod", $object); + $this->assertNull($mock->aReferenceMethod()); + $this->assertReference($mock->aReferenceMethod(), $object); + $this->assertNull($mock->aReferenceMethod()); + } + + function testReturnsAtGivesOriginalObjectHandle() { + $mock = new MockDummy(); + $object = new Dummy(); + $mock->returnsAt(1, "aMethod", $object); + $this->assertNull($mock->aMethod()); + $this->assertSame($mock->aMethod(), $object); + $this->assertNull($mock->aMethod()); + } + + function testComplicatedReturnSequence() { + $mock = new MockDummy(); + $object = new Dummy(); + $mock->returnsAt(1, "aMethod", "aaa", array("a")); + $mock->returnsAt(1, "aMethod", "bbb"); + $mock->returnsAt(2, "aMethod", $object, array('*', 2)); + $mock->returnsAt(2, "aMethod", "value", array('*', 3)); + $mock->returns("aMethod", 3, array(3)); + $this->assertNull($mock->aMethod()); + $this->assertEqual($mock->aMethod("a"), "aaa"); + $this->assertSame($mock->aMethod(1, 2), $object); + $this->assertEqual($mock->aMethod(3), 3); + $this->assertNull($mock->aMethod()); + } + + function testMultipleMethodSequences() { + $mock = new MockDummy(); + $mock->setReturnValueAt(0, "aMethod", "aaa"); + $mock->setReturnValueAt(1, "aMethod", "bbb"); + $mock->setReturnValueAt(0, "anotherMethod", "ccc"); + $mock->setReturnValueAt(1, "anotherMethod", "ddd"); + $this->assertIdentical($mock->aMethod(), "aaa"); + $this->assertIdentical($mock->anotherMethod(), "ccc"); + $this->assertIdentical($mock->aMethod(), "bbb"); + $this->assertIdentical($mock->anotherMethod(), "ddd"); + } + + function testSequenceFallback() { + $mock = new MockDummy(); + $mock->setReturnValueAt(0, "aMethod", "aaa", array('a')); + $mock->setReturnValueAt(1, "aMethod", "bbb", array('a')); + $mock->setReturnValue("aMethod", "AAA"); + $this->assertIdentical($mock->aMethod('a'), "aaa"); + $this->assertIdentical($mock->aMethod('b'), "AAA"); + } + + function testMethodInterference() { + $mock = new MockDummy(); + $mock->setReturnValueAt(0, "anotherMethod", "aaa"); + $mock->setReturnValue("aMethod", "AAA"); + $this->assertIdentical($mock->aMethod(), "AAA"); + $this->assertIdentical($mock->anotherMethod(), "aaa"); + } +} + +class TestOfMockExpectationsThatPass extends UnitTestCase { + + function testAnyArgument() { + $mock = new MockDummy(); + $mock->expect('aMethod', array('*')); + $mock->aMethod(1); + $mock->aMethod('hello'); + } + + function testAnyTwoArguments() { + $mock = new MockDummy(); + $mock->expect('aMethod', array('*', '*')); + $mock->aMethod(1, 2); + } + + function testSpecificArgument() { + $mock = new MockDummy(); + $mock->expect('aMethod', array(1)); + $mock->aMethod(1); + } + + function testExpectation() { + $mock = new MockDummy(); + $mock->expect('aMethod', array(new IsAExpectation('Dummy'))); + $mock->aMethod(new Dummy()); + } + + function testArgumentsInSequence() { + $mock = new MockDummy(); + $mock->expectAt(0, 'aMethod', array(1, 2)); + $mock->expectAt(1, 'aMethod', array(3, 4)); + $mock->aMethod(1, 2); + $mock->aMethod(3, 4); + } + + function testAtLeastOnceSatisfiedByOneCall() { + $mock = new MockDummy(); + $mock->expectAtLeastOnce('aMethod'); + $mock->aMethod(); + } + + function testAtLeastOnceSatisfiedByTwoCalls() { + $mock = new MockDummy(); + $mock->expectAtLeastOnce('aMethod'); + $mock->aMethod(); + $mock->aMethod(); + } + + function testOnceSatisfiedByOneCall() { + $mock = new MockDummy(); + $mock->expectOnce('aMethod'); + $mock->aMethod(); + } + + function testMinimumCallsSatisfiedByEnoughCalls() { + $mock = new MockDummy(); + $mock->expectMinimumCallCount('aMethod', 1); + $mock->aMethod(); + } + + function testMinimumCallsSatisfiedByTooManyCalls() { + $mock = new MockDummy(); + $mock->expectMinimumCallCount('aMethod', 3); + $mock->aMethod(); + $mock->aMethod(); + $mock->aMethod(); + $mock->aMethod(); + } + + function testMaximumCallsSatisfiedByEnoughCalls() { + $mock = new MockDummy(); + $mock->expectMaximumCallCount('aMethod', 1); + $mock->aMethod(); + } + + function testMaximumCallsSatisfiedByNoCalls() { + $mock = new MockDummy(); + $mock->expectMaximumCallCount('aMethod', 1); + } +} + +class MockWithInjectedTestCase extends SimpleMock { + protected function getCurrentTestCase() { + return SimpleTest::getContext()->getTest()->getMockedTest(); + } +} +SimpleTest::setMockBaseClass('MockWithInjectedTestCase'); +Mock::generate('Dummy', 'MockDummyWithInjectedTestCase'); +SimpleTest::setMockBaseClass('SimpleMock'); +Mock::generate('SimpleTestCase'); + +class LikeExpectation extends IdenticalExpectation { + function __construct($expectation) { + $expectation->message = ''; + parent::__construct($expectation); + } + + function test($compare) { + $compare->message = ''; + return parent::test($compare); + } + + function testMessage($compare) { + $compare->message = ''; + return parent::testMessage($compare); + } +} + +class TestOfMockExpectations extends UnitTestCase { + private $test; + + function setUp() { + $this->test = new MockSimpleTestCase(); + } + + function getMockedTest() { + return $this->test; + } + + function testSettingExpectationOnNonMethodThrowsError() { + $mock = new MockDummyWithInjectedTestCase(); + $this->expectError(); + $mock->expectMaximumCallCount('aMissingMethod', 2); + } + + function testMaxCallsDetectsOverrun() { + $this->test->expectOnce('assert', array( + new LikeExpectation(new MaximumCallCountExpectation('aMethod', 2)), + 3)); + $mock = new MockDummyWithInjectedTestCase(); + $mock->expectMaximumCallCount('aMethod', 2); + $mock->aMethod(); + $mock->aMethod(); + $mock->aMethod(); + $mock->mock->atTestEnd('testSomething', $this->test); + } + + function testTallyOnMaxCallsSendsPassOnUnderrun() { + $this->test->expectOnce('assert', array( + new LikeExpectation(new MaximumCallCountExpectation('aMethod', 2)), + 2)); + $mock = new MockDummyWithInjectedTestCase(); + $mock->expectMaximumCallCount("aMethod", 2); + $mock->aMethod(); + $mock->aMethod(); + $mock->mock->atTestEnd('testSomething', $this->test); + } + + function testExpectNeverDetectsOverrun() { + $this->test->expectOnce('assert', array( + new LikeExpectation(new MaximumCallCountExpectation('aMethod', 0)), + 1)); + $mock = new MockDummyWithInjectedTestCase(); + $mock->expectNever('aMethod'); + $mock->aMethod(); + $mock->mock->atTestEnd('testSomething', $this->test); + } + + function testTallyOnExpectNeverStillSendsPassOnUnderrun() { + $this->test->expectOnce('assert', array( + new LikeExpectation(new MaximumCallCountExpectation('aMethod', 0)), + 0)); + $mock = new MockDummyWithInjectedTestCase(); + $mock->expectNever('aMethod'); + $mock->mock->atTestEnd('testSomething', $this->test); + } + + function testMinCalls() { + $this->test->expectOnce('assert', array( + new LikeExpectation(new MinimumCallCountExpectation('aMethod', 2)), + 2)); + $mock = new MockDummyWithInjectedTestCase(); + $mock->expectMinimumCallCount('aMethod', 2); + $mock->aMethod(); + $mock->aMethod(); + $mock->mock->atTestEnd('testSomething', $this->test); + } + + function testFailedNever() { + $this->test->expectOnce('assert', array( + new LikeExpectation(new MaximumCallCountExpectation('aMethod', 0)), + 1)); + $mock = new MockDummyWithInjectedTestCase(); + $mock->expectNever('aMethod'); + $mock->aMethod(); + $mock->mock->atTestEnd('testSomething', $this->test); + } + + function testUnderOnce() { + $this->test->expectOnce('assert', array( + new LikeExpectation(new CallCountExpectation('aMethod', 1)), + 0)); + $mock = new MockDummyWithInjectedTestCase(); + $mock->expectOnce('aMethod'); + $mock->mock->atTestEnd('testSomething', $this->test); + } + + function testOverOnce() { + $this->test->expectOnce('assert', array( + new LikeExpectation(new CallCountExpectation('aMethod', 1)), + 2)); + $mock = new MockDummyWithInjectedTestCase(); + $mock->expectOnce('aMethod'); + $mock->aMethod(); + $mock->aMethod(); + $mock->mock->atTestEnd('testSomething', $this->test); + } + + function testUnderAtLeastOnce() { + $this->test->expectOnce('assert', array( + new LikeExpectation(new MinimumCallCountExpectation('aMethod', 1)), + 0)); + $mock = new MockDummyWithInjectedTestCase(); + $mock->expectAtLeastOnce("aMethod"); + $mock->mock->atTestEnd('testSomething', $this->test); + } + + function testZeroArguments() { + $this->test->expectOnce('assert', array( + new LikeExpectation(new ParametersExpectation(array())), + array(), + '*')); + $mock = new MockDummyWithInjectedTestCase(); + $mock->expect("aMethod", array()); + $mock->aMethod(); + $mock->mock->atTestEnd('testSomething', $this->test); + } + + function testExpectedArguments() { + $this->test->expectOnce('assert', array( + new LikeExpectation(new ParametersExpectation(array(1, 2, 3))), + array(1, 2, 3), + '*')); + $mock = new MockDummyWithInjectedTestCase(); + $mock->expect('aMethod', array(1, 2, 3)); + $mock->aMethod(1, 2, 3); + $mock->mock->atTestEnd('testSomething', $this->test); + } + + function testFailedArguments() { + $this->test->expectOnce('assert', array( + new LikeExpectation(new ParametersExpectation(array('this'))), + array('that'), + '*')); + $mock = new MockDummyWithInjectedTestCase(); + $mock->expect('aMethod', array('this')); + $mock->aMethod('that'); + $mock->mock->atTestEnd('testSomething', $this->test); + } + + function testWildcardsAreTranslatedToAnythingExpectations() { + $this->test->expectOnce('assert', array( + new LikeExpectation(new ParametersExpectation(array( + new AnythingExpectation(), 123, new AnythingExpectation()))), + array(100, 123, 101), + '*')); + $mock = new MockDummyWithInjectedTestCase($this); + $mock->expect("aMethod", array('*', 123, '*')); + $mock->aMethod(100, 123, 101); + $mock->mock->atTestEnd('testSomething', $this->test); + } + + function testSpecificPassingSequence() { + $this->test->expectAt(0, 'assert', array( + new LikeExpectation(new ParametersExpectation(array(1, 2, 3))), + array(1, 2, 3), + '*')); + $this->test->expectAt(1, 'assert', array( + new LikeExpectation(new ParametersExpectation(array('Hello'))), + array('Hello'), + '*')); + $mock = new MockDummyWithInjectedTestCase(); + $mock->expectAt(1, 'aMethod', array(1, 2, 3)); + $mock->expectAt(2, 'aMethod', array('Hello')); + $mock->aMethod(); + $mock->aMethod(1, 2, 3); + $mock->aMethod('Hello'); + $mock->aMethod(); + $mock->mock->atTestEnd('testSomething', $this->test); + } + + function testNonArrayForExpectedParametersGivesError() { + $mock = new MockDummyWithInjectedTestCase(); + $this->expectError(new PatternExpectation('/\$args.*not an array/i')); + $mock->expect("aMethod", "foo"); + $mock->aMethod(); + $mock->mock->atTestEnd('testSomething', $this->test); + } +} + +class TestOfMockComparisons extends UnitTestCase { + + function testEqualComparisonOfMocksDoesNotCrash() { + $expectation = new EqualExpectation(new MockDummy()); + $this->assertTrue($expectation->test(new MockDummy(), true)); + } + + function testIdenticalComparisonOfMocksDoesNotCrash() { + $expectation = new IdenticalExpectation(new MockDummy()); + $this->assertTrue($expectation->test(new MockDummy())); + } +} + +class ClassWithSpecialMethods { + function __get($name) { } + function __set($name, $value) { } + function __isset($name) { } + function __unset($name) { } + function __call($method, $arguments) { } + function __toString() { } +} +Mock::generate('ClassWithSpecialMethods'); + +class TestOfSpecialMethodsAfterPHP51 extends UnitTestCase { + + function skip() { + $this->skipIf(version_compare(phpversion(), '5.1', '<'), '__isset and __unset overloading not tested unless PHP 5.1+'); + } + + function testCanEmulateIsset() { + $mock = new MockClassWithSpecialMethods(); + $mock->setReturnValue('__isset', true); + $this->assertIdentical(isset($mock->a), true); + } + + function testCanExpectUnset() { + $mock = new MockClassWithSpecialMethods(); + $mock->expectOnce('__unset', array('a')); + unset($mock->a); + } + +} + +class TestOfSpecialMethods extends UnitTestCase { + function skip() { + $this->skipIf(version_compare(phpversion(), '5', '<'), 'Overloading not tested unless PHP 5+'); + } + + function testCanMockTheThingAtAll() { + $mock = new MockClassWithSpecialMethods(); + } + + function testReturnFromSpecialAccessor() { + $mock = new MockClassWithSpecialMethods(); + $mock->setReturnValue('__get', '1st Return', array('first')); + $mock->setReturnValue('__get', '2nd Return', array('second')); + $this->assertEqual($mock->first, '1st Return'); + $this->assertEqual($mock->second, '2nd Return'); + } + + function testcanExpectTheSettingOfValue() { + $mock = new MockClassWithSpecialMethods(); + $mock->expectOnce('__set', array('a', 'A')); + $mock->a = 'A'; + } + + function testCanSimulateAnOverloadmethod() { + $mock = new MockClassWithSpecialMethods(); + $mock->expectOnce('__call', array('amOverloaded', array('A'))); + $mock->setReturnValue('__call', 'aaa'); + $this->assertIdentical($mock->amOverloaded('A'), 'aaa'); + } + + function testToStringMagic() { + $mock = new MockClassWithSpecialMethods(); + $mock->expectOnce('__toString'); + $mock->setReturnValue('__toString', 'AAA'); + ob_start(); + print $mock; + $output = ob_get_contents(); + ob_end_clean(); + $this->assertEqual($output, 'AAA'); + } +} + +class WithStaticMethod { + static function aStaticMethod() { } +} +Mock::generate('WithStaticMethod'); + +class TestOfMockingClassesWithStaticMethods extends UnitTestCase { + + function testStaticMethodIsMockedAsStatic() { + $mock = new WithStaticMethod(); + $reflection = new ReflectionClass($mock); + $method = $reflection->getMethod('aStaticMethod'); + $this->assertTrue($method->isStatic()); + } +} + +class MockTestException extends Exception { } + +class TestOfThrowingExceptionsFromMocks extends UnitTestCase { + + function testCanThrowOnMethodCall() { + $mock = new MockDummy(); + $mock->throwOn('aMethod'); + $this->expectException(); + $mock->aMethod(); + } + + function testCanThrowSpecificExceptionOnMethodCall() { + $mock = new MockDummy(); + $mock->throwOn('aMethod', new MockTestException()); + $this->expectException(); + $mock->aMethod(); + } + + function testThrowsOnlyWhenCallSignatureMatches() { + $mock = new MockDummy(); + $mock->throwOn('aMethod', new MockTestException(), array(3)); + $mock->aMethod(1); + $mock->aMethod(2); + $this->expectException(); + $mock->aMethod(3); + } + + function testCanThrowOnParticularInvocation() { + $mock = new MockDummy(); + $mock->throwAt(2, 'aMethod', new MockTestException()); + $mock->aMethod(); + $mock->aMethod(); + $this->expectException(); + $mock->aMethod(); + } +} + +class TestOfThrowingErrorsFromMocks extends UnitTestCase { + + function testCanGenerateErrorFromMethodCall() { + $mock = new MockDummy(); + $mock->errorOn('aMethod', 'Ouch!'); + $this->expectError('Ouch!'); + $mock->aMethod(); + } + + function testGeneratesErrorOnlyWhenCallSignatureMatches() { + $mock = new MockDummy(); + $mock->errorOn('aMethod', 'Ouch!', array(3)); + $mock->aMethod(1); + $mock->aMethod(2); + $this->expectError(); + $mock->aMethod(3); + } + + function testCanGenerateErrorOnParticularInvocation() { + $mock = new MockDummy(); + $mock->errorAt(2, 'aMethod', 'Ouch!'); + $mock->aMethod(); + $mock->aMethod(); + $this->expectError(); + $mock->aMethod(); + } +} + +Mock::generatePartial('Dummy', 'TestDummy', array('anotherMethod', 'aReferenceMethod')); + +class TestOfPartialMocks extends UnitTestCase { + + function testMethodReplacementWithNoBehaviourReturnsNull() { + $mock = new TestDummy(); + $this->assertEqual($mock->aMethod(99), 99); + $this->assertNull($mock->anotherMethod()); + } + + function testSettingReturns() { + $mock = new TestDummy(); + $mock->setReturnValue('anotherMethod', 33, array(3)); + $mock->setReturnValue('anotherMethod', 22); + $mock->setReturnValueAt(2, 'anotherMethod', 44, array(3)); + $this->assertEqual($mock->anotherMethod(), 22); + $this->assertEqual($mock->anotherMethod(3), 33); + $this->assertEqual($mock->anotherMethod(3), 44); + } + + function testSetReturnReferenceGivesOriginal() { + $mock = new TestDummy(); + $object = 99; + $mock->setReturnReferenceAt(0, 'aReferenceMethod', $object, array(3)); + $this->assertReference($mock->aReferenceMethod(3), $object); + } + + function testReturnsAtGivesOriginalObjectHandle() { + $mock = new TestDummy(); + $object = new Dummy(); + $mock->returnsAt(0, 'anotherMethod', $object, array(3)); + $this->assertSame($mock->anotherMethod(3), $object); + } + + function testExpectations() { + $mock = new TestDummy(); + $mock->expectCallCount('anotherMethod', 2); + $mock->expect('anotherMethod', array(77)); + $mock->expectAt(1, 'anotherMethod', array(66)); + $mock->anotherMethod(77); + $mock->anotherMethod(66); + } + + function testSettingExpectationOnMissingMethodThrowsError() { + $mock = new TestDummy(); + $this->expectError(); + $mock->expectCallCount('aMissingMethod', 2); + } +} + +class ConstructorSuperClass { + function ConstructorSuperClass() { } +} + +class ConstructorSubClass extends ConstructorSuperClass { } + +class TestOfPHP4StyleSuperClassConstruct extends UnitTestCase { + function testBasicConstruct() { + Mock::generate('ConstructorSubClass'); + $mock = new MockConstructorSubClass(); + $this->assertIsA($mock, 'ConstructorSubClass'); + $this->assertTrue(method_exists($mock, 'ConstructorSuperClass')); + } +} + +class TestOfPHP5StaticMethodMocking extends UnitTestCase { + function testCanCreateAMockObjectWithStaticMethodsWithoutError() { + eval(' + class SimpleObjectContainingStaticMethod { + static function someStatic() { } + } + '); + Mock::generate('SimpleObjectContainingStaticMethod'); + } +} + +class TestOfPHP5AbstractMethodMocking extends UnitTestCase { + function testCanCreateAMockObjectFromAnAbstractWithProperFunctionDeclarations() { + eval(' + abstract class SimpleAbstractClassContainingAbstractMethods { + abstract function anAbstract(); + abstract function anAbstractWithParameter($foo); + abstract function anAbstractWithMultipleParameters($foo, $bar); + } + '); + Mock::generate('SimpleAbstractClassContainingAbstractMethods'); + $this->assertTrue( + method_exists( + // Testing with class name alone does not work in PHP 5.0 + new MockSimpleAbstractClassContainingAbstractMethods, + 'anAbstract' + ) + ); + $this->assertTrue( + method_exists( + new MockSimpleAbstractClassContainingAbstractMethods, + 'anAbstractWithParameter' + ) + ); + $this->assertTrue( + method_exists( + new MockSimpleAbstractClassContainingAbstractMethods, + 'anAbstractWithMultipleParameters' + ) + ); + } + + function testMethodsDefinedAsAbstractInParentShouldHaveFullSignature() { + eval(' + abstract class SimpleParentAbstractClassContainingAbstractMethods { + abstract function anAbstract(); + abstract function anAbstractWithParameter($foo); + abstract function anAbstractWithMultipleParameters($foo, $bar); + } + + class SimpleChildAbstractClassContainingAbstractMethods extends SimpleParentAbstractClassContainingAbstractMethods { + function anAbstract(){} + function anAbstractWithParameter($foo){} + function anAbstractWithMultipleParameters($foo, $bar){} + } + + class EvenDeeperEmptyChildClass extends SimpleChildAbstractClassContainingAbstractMethods {} + '); + Mock::generate('SimpleChildAbstractClassContainingAbstractMethods'); + $this->assertTrue( + method_exists( + new MockSimpleChildAbstractClassContainingAbstractMethods, + 'anAbstract' + ) + ); + $this->assertTrue( + method_exists( + new MockSimpleChildAbstractClassContainingAbstractMethods, + 'anAbstractWithParameter' + ) + ); + $this->assertTrue( + method_exists( + new MockSimpleChildAbstractClassContainingAbstractMethods, + 'anAbstractWithMultipleParameters' + ) + ); + Mock::generate('EvenDeeperEmptyChildClass'); + $this->assertTrue( + method_exists( + new MockEvenDeeperEmptyChildClass, + 'anAbstract' + ) + ); + $this->assertTrue( + method_exists( + new MockEvenDeeperEmptyChildClass, + 'anAbstractWithParameter' + ) + ); + $this->assertTrue( + method_exists( + new MockEvenDeeperEmptyChildClass, + 'anAbstractWithMultipleParameters' + ) + ); + } +} + +class DummyWithProtected +{ + public function aMethodCallsProtected() { return $this->aProtectedMethod(); } + protected function aProtectedMethod() { return true; } +} + +Mock::generatePartial('DummyWithProtected', 'TestDummyWithProtected', array('aProtectedMethod')); +class TestOfProtectedMethodPartialMocks extends UnitTestCase +{ + function testProtectedMethodExists() { + $this->assertTrue( + method_exists( + new TestDummyWithProtected, + 'aProtectedMethod' + ) + ); + } + + function testProtectedMethodIsCalled() { + $object = new DummyWithProtected(); + $this->assertTrue($object->aMethodCallsProtected(), 'ensure original was called'); + } + + function testMockedMethodIsCalled() { + $object = new TestDummyWithProtected(); + $object->setReturnValue('aProtectedMethod', false); + $this->assertFalse($object->aMethodCallsProtected()); + } +} + +?> diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/page_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/page_test.php new file mode 100644 index 0000000..eb61c4f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/page_test.php @@ -0,0 +1,898 @@ + 'http://somewhere')); + $tag->addContent('Label'); + + $page = new MockSimplePage(); + $page->expect('acceptTag', array($tag)); + $page->expectCallCount('acceptTag', 1); + + $builder = new PartialSimplePageBuilder(); + $builder->returns('createPage', $page); + $builder->returns('createParser', new MockSimpleHtmlSaxParser()); + $builder->__construct(); + + $builder->parse(new MockSimpleHttpResponse()); + $this->assertTrue($builder->startElement( + 'a', + array('href' => 'http://somewhere'))); + $this->assertTrue($builder->addContent('Label')); + $this->assertTrue($builder->endElement('a')); + } + + function testLinkWithId() { + $tag = new SimpleAnchorTag(array("href" => "http://somewhere", "id" => "44")); + $tag->addContent("Label"); + + $page = new MockSimplePage(); + $page->expect("acceptTag", array($tag)); + $page->expectCallCount("acceptTag", 1); + + $builder = new PartialSimplePageBuilder(); + $builder->returns('createPage', $page); + $builder->returns('createParser', new MockSimpleHtmlSaxParser()); + $builder->__construct(); + + $builder->parse(new MockSimpleHttpResponse()); + $this->assertTrue($builder->startElement( + "a", + array("href" => "http://somewhere", "id" => "44"))); + $this->assertTrue($builder->addContent("Label")); + $this->assertTrue($builder->endElement("a")); + } + + function testLinkExtraction() { + $tag = new SimpleAnchorTag(array("href" => "http://somewhere")); + $tag->addContent("Label"); + + $page = new MockSimplePage(); + $page->expect("acceptTag", array($tag)); + $page->expectCallCount("acceptTag", 1); + + $builder = new PartialSimplePageBuilder(); + $builder->returns('createPage', $page); + $builder->returns('createParser', new MockSimpleHtmlSaxParser()); + $builder->__construct(); + + $builder->parse(new MockSimpleHttpResponse()); + $this->assertTrue($builder->addContent("Starting stuff")); + $this->assertTrue($builder->startElement( + "a", + array("href" => "http://somewhere"))); + $this->assertTrue($builder->addContent("Label")); + $this->assertTrue($builder->endElement("a")); + $this->assertTrue($builder->addContent("Trailing stuff")); + } + + function testMultipleLinks() { + $a1 = new SimpleAnchorTag(array("href" => "http://somewhere")); + $a1->addContent("1"); + + $a2 = new SimpleAnchorTag(array("href" => "http://elsewhere")); + $a2->addContent("2"); + + $page = new MockSimplePage(); + $page->expectAt(0, "acceptTag", array($a1)); + $page->expectAt(1, "acceptTag", array($a2)); + $page->expectCallCount("acceptTag", 2); + + $builder = new PartialSimplePageBuilder(); + $builder->returns('createPage', $page); + $builder->returns('createParser', new MockSimpleHtmlSaxParser()); + $builder->__construct(); + + $builder->parse(new MockSimpleHttpResponse()); + $builder->startElement("a", array("href" => "http://somewhere")); + $builder->addContent("1"); + $builder->endElement("a"); + $builder->addContent("Padding"); + $builder->startElement("a", array("href" => "http://elsewhere")); + $builder->addContent("2"); + $builder->endElement("a"); + } + + function testTitle() { + $tag = new SimpleTitleTag(array()); + $tag->addContent("HereThere"); + + $page = new MockSimplePage(); + $page->expect("acceptTag", array($tag)); + $page->expectCallCount("acceptTag", 1); + + $builder = new PartialSimplePageBuilder(); + $builder->returns('createPage', $page); + $builder->returns('createParser', new MockSimpleHtmlSaxParser()); + $builder->__construct(); + + $builder->parse(new MockSimpleHttpResponse()); + $builder->startElement("title", array()); + $builder->addContent("Here"); + $builder->addContent("There"); + $builder->endElement("title"); + } + + function testForm() { + $page = new MockSimplePage(); + $page->expectOnce("acceptFormStart", array(new SimpleFormTag(array()))); + $page->expectOnce("acceptFormEnd", array()); + + $builder = new PartialSimplePageBuilder(); + $builder->returns('createPage', $page); + $builder->returns('createParser', new MockSimpleHtmlSaxParser()); + $builder->__construct(); + + $builder->parse(new MockSimpleHttpResponse()); + $builder->startElement("form", array()); + $builder->addContent("Stuff"); + $builder->endElement("form"); + } +} + +class TestOfPageParsing extends UnitTestCase { + + function testParseMechanics() { + $parser = new MockSimpleHtmlSaxParser(); + $parser->expectOnce('parse', array('stuff')); + + $page = new MockSimplePage(); + $page->expectOnce('acceptPageEnd'); + + $builder = new PartialSimplePageBuilder(); + $builder->returns('createPage', $page); + $builder->returns('createParser', $parser); + $builder->__construct(); + + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', 'stuff'); + $builder->parse($response); + } +} + +class TestOfPageInterface extends UnitTestCase { + + function testInterfaceOnEmptyPage() { + $page = new SimplePage(); + $this->assertEqual($page->getTransportError(), 'No page fetched yet'); + $this->assertIdentical($page->getRaw(), false); + $this->assertIdentical($page->getHeaders(), false); + $this->assertIdentical($page->getMimeType(), false); + $this->assertIdentical($page->getResponseCode(), false); + $this->assertIdentical($page->getAuthentication(), false); + $this->assertIdentical($page->getRealm(), false); + $this->assertFalse($page->hasFrames()); + $this->assertIdentical($page->getUrls(), array()); + $this->assertIdentical($page->getTitle(), false); + } +} + +class TestOfPageHeaders extends UnitTestCase { + + function testUrlAccessor() { + $headers = new MockSimpleHttpHeaders(); + + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getHeaders', $headers); + $response->setReturnValue('getMethod', 'POST'); + $response->setReturnValue('getUrl', new SimpleUrl('here')); + $response->setReturnValue('getRequestData', array('a' => 'A')); + + $page = new SimplePage($response); + $this->assertEqual($page->getMethod(), 'POST'); + $this->assertEqual($page->getUrl(), new SimpleUrl('here')); + $this->assertEqual($page->getRequestData(), array('a' => 'A')); + } + + function testTransportError() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getError', 'Ouch'); + + $page = new SimplePage($response); + $this->assertEqual($page->getTransportError(), 'Ouch'); + } + + function testHeadersAccessor() { + $headers = new MockSimpleHttpHeaders(); + $headers->setReturnValue('getRaw', 'My: Headers'); + + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getHeaders', $headers); + + $page = new SimplePage($response); + $this->assertEqual($page->getHeaders(), 'My: Headers'); + } + + function testMimeAccessor() { + $headers = new MockSimpleHttpHeaders(); + $headers->setReturnValue('getMimeType', 'text/html'); + + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getHeaders', $headers); + + $page = new SimplePage($response); + $this->assertEqual($page->getMimeType(), 'text/html'); + } + + function testResponseAccessor() { + $headers = new MockSimpleHttpHeaders(); + $headers->setReturnValue('getResponseCode', 301); + + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getHeaders', $headers); + + $page = new SimplePage($response); + $this->assertIdentical($page->getResponseCode(), 301); + } + + function testAuthenticationAccessors() { + $headers = new MockSimpleHttpHeaders(); + $headers->setReturnValue('getAuthentication', 'Basic'); + $headers->setReturnValue('getRealm', 'Secret stuff'); + + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getHeaders', $headers); + + $page = new SimplePage($response); + $this->assertEqual($page->getAuthentication(), 'Basic'); + $this->assertEqual($page->getRealm(), 'Secret stuff'); + } +} + +class TestOfHtmlPage extends UnitTestCase { + + function testRawAccessor() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', 'Raw HTML'); + + $page = new SimplePage($response); + $this->assertEqual($page->getRaw(), 'Raw HTML'); + } + + function testTextAccessor() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', 'Some "messy" HTML'); + + $page = new SimplePage($response); + $this->assertEqual($page->getText(), 'Some "messy" HTML'); + } + + function testNoLinks() { + $page = new SimplePage(new MockSimpleHttpResponse()); + $this->assertIdentical($page->getUrls(), array()); + $this->assertIdentical($page->getUrlsByLabel('Label'), array()); + } + + function testAddAbsoluteLink() { + $link = new SimpleAnchorTag(array('href' => 'http://somewhere.com')); + $link->addContent('Label'); + $page = new SimplePage(new MockSimpleHttpResponse()); + $page->AcceptTag($link); + $this->assertEqual( + $page->getUrlsByLabel('Label'), + array(new SimpleUrl('http://somewhere.com'))); + } + + function testAddStrictRelativeLink() { + $link = new SimpleAnchorTag(array('href' => './somewhere.php')); + $link->addContent('Label'); + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getUrl', new SimpleUrl('http://host/')); + $page = new SimplePage($response); + $page->AcceptTag($link); + $this->assertEqual( + $page->getUrlsByLabel('Label'), + array(new SimpleUrl('http://host/somewhere.php'))); + } + + function testAddBareRelativeLink() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getUrl', new SimpleUrl('http://host/')); + $page = new SimplePage($response); + $page->AcceptTag(new SimpleAnchorTag(array('href' => 'somewhere.php'))); + $this->assertIdentical($page->getUrls(), array('http://host/somewhere.php')); + } + + function testAddRelativeLinkWithBaseTag() { + $link = new SimpleAnchorTag(array('href' => 'somewhere.php')); + $link->addContent('Label'); + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getUrl', new SimpleUrl('http://host/')); + $page = new SimplePage($response); + $page->AcceptTag($link); + $base = new SimpleBaseTag(array('href' => 'www.lastcraft.com/stuff/')); + $page->AcceptTag($base); + $this->assertEqual( + $page->getUrlsByLabel('Label'), + array(new SimpleUrl('www.lastcraft.com/stuff/somewhere.php'))); + } + + function testAddAbsoluteLinkWithBaseTag() { + $link = new SimpleAnchorTag(array('href' => 'http://here.com/somewhere.php')); + $link->addContent('Label'); + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getUrl', new SimpleUrl('http://host/')); + $page = new SimplePage($response); + $page->AcceptTag($link); + $base = new SimpleBaseTag(array('href' => 'www.lastcraft.com/stuff/')); + $page->AcceptTag($base); + $this->assertEqual( + $page->getUrlsByLabel('Label'), + array(new SimpleUrl('http://here.com/somewhere.php'))); + } + + function testLinkIds() { + $link = new SimpleAnchorTag(array('href' => './somewhere.php', 'id' => 33)); + $link->addContent('Label'); + + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getUrl', new SimpleUrl('http://host/')); + + $page = new SimplePage($response); + $page->AcceptTag($link); + + $this->assertEqual( + $page->getUrlsByLabel('Label'), + array(new SimpleUrl('http://host/somewhere.php'))); + $this->assertFalse($page->getUrlById(0)); + $this->assertEqual( + $page->getUrlById(33), + new SimpleUrl('http://host/somewhere.php')); + } + + function testFindLinkWithNormalisation() { + $link = new SimpleAnchorTag(array('href' => './somewhere.php', 'id' => 33)); + $link->addContent(' Long & thin '); + + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getUrl', new SimpleUrl('http://host/')); + + $page = new SimplePage($response); + $page->AcceptTag($link); + + $this->assertEqual( + $page->getUrlsByLabel('Long & thin'), + array(new SimpleUrl('http://host/somewhere.php'))); + } + + function testFindLinkWithImage() { + $link = new SimpleAnchorTag(array('href' => './somewhere.php', 'id' => 33)); + $link->addContent('<A picture>'); + + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getUrl', new SimpleUrl('http://host/')); + + $page = new SimplePage($response); + $page->AcceptTag($link); + + $this->assertEqual( + $page->getUrlsByLabel(''), + array(new SimpleUrl('http://host/somewhere.php'))); + } + + function testTitleSetting() { + $title = new SimpleTitleTag(array()); + $title->addContent('Title'); + $page = new SimplePage(new MockSimpleHttpResponse()); + $page->AcceptTag($title); + $this->assertEqual($page->getTitle(), 'Title'); + } + + function testFramesetAbsence() { + $url = new SimpleUrl('here'); + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getUrl', $url); + $page = new SimplePage($response); + $this->assertFalse($page->hasFrames()); + $this->assertIdentical($page->getFrameset(), false); + } + + function testHasEmptyFrameset() { + $page = new SimplePage(new MockSimpleHttpResponse()); + $page->acceptFramesetStart(new SimpleTag('frameset', array())); + $page->acceptFramesetEnd(); + $this->assertTrue($page->hasFrames()); + $this->assertIdentical($page->getFrameset(), array()); + } + + function testFramesInPage() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getUrl', new SimpleUrl('http://here')); + + $page = new SimplePage($response); + $page->acceptFrame(new SimpleFrameTag(array('src' => '1.html'))); + $page->acceptFramesetStart(new SimpleTag('frameset', array())); + $page->acceptFrame(new SimpleFrameTag(array('src' => '2.html'))); + $page->acceptFrame(new SimpleFrameTag(array('src' => '3.html'))); + $page->acceptFramesetEnd(); + $page->acceptFrame(new SimpleFrameTag(array('src' => '4.html'))); + + $this->assertTrue($page->hasFrames()); + $this->assertIdentical($page->getFrameset(), array( + 1 => new SimpleUrl('http://here/2.html'), + 2 => new SimpleUrl('http://here/3.html'))); + } + + function testNamedFramesInPage() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getUrl', new SimpleUrl('http://here')); + + $page = new SimplePage($response); + $page->acceptFramesetStart(new SimpleTag('frameset', array())); + $page->acceptFrame(new SimpleFrameTag(array('src' => '1.html'))); + $page->acceptFrame(new SimpleFrameTag(array('src' => '2.html', 'name' => 'A'))); + $page->acceptFrame(new SimpleFrameTag(array('src' => '3.html', 'name' => 'B'))); + $page->acceptFrame(new SimpleFrameTag(array('src' => '4.html'))); + $page->acceptFramesetEnd(); + + $this->assertTrue($page->hasFrames()); + $this->assertIdentical($page->getFrameset(), array( + 1 => new SimpleUrl('http://here/1.html'), + 'A' => new SimpleUrl('http://here/2.html'), + 'B' => new SimpleUrl('http://here/3.html'), + 4 => new SimpleUrl('http://here/4.html'))); + } + + function testRelativeFramesRespectBaseTag() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getUrl', new SimpleUrl('http://here.com/')); + $page = new SimplePage($response); + + $base = new SimpleBaseTag(array('href' => 'https://there.com/stuff/')); + $page->AcceptTag($base); + + $page->acceptFramesetStart(new SimpleTag('frameset', array())); + $page->acceptFrame(new SimpleFrameTag(array('src' => '1.html'))); + $page->acceptFramesetEnd(); + $this->assertIdentical( + $page->getFrameset(), + array(1 => new SimpleUrl('https://there.com/stuff/1.html'))); + } +} + +class TestOfFormsCreatedFromEventStream extends UnitTestCase { + + function testFormCanBeSubmitted() { + $page = new SimplePage(new MockSimpleHttpResponse()); + $page->acceptFormStart( + new SimpleFormTag(array('method' => 'GET', 'action' => 'here.php'))); + $page->AcceptTag( + new SimpleSubmitTag(array('type' => 'submit', 'name' => 's'))); + $page->acceptFormEnd(); + $form = &$page->getFormBySubmit(new SimpleByLabel('Submit')); + $this->assertEqual( + $form->submitButton(new SimpleByLabel('Submit')), + new SimpleGetEncoding(array('s' => 'Submit'))); + } + + function testInputFieldCanBeReadBack() { + $page = new SimplePage(new MockSimpleHttpResponse()); + $page->acceptFormStart( + new SimpleFormTag(array("method" => "GET", "action" => "here.php"))); + $page->AcceptTag( + new SimpleTextTag(array("type" => "text", "name" => "a", "value" => "A"))); + $page->AcceptTag( + new SimpleSubmitTag(array("type" => "submit", "name" => "s"))); + $page->acceptFormEnd(); + $this->assertEqual($page->getField(new SimpleByName('a')), 'A'); + } + + function testInputFieldCanBeReadBackByLabel() { + $label = new SimpleLabelTag(array()); + $page = new SimplePage(new MockSimpleHttpResponse()); + $page->acceptFormStart( + new SimpleFormTag(array("method" => "GET", "action" => "here.php"))); + $page->acceptLabelStart($label); + $label->addContent('l'); + $page->AcceptTag( + new SimpleTextTag(array("type" => "text", "name" => "a", "value" => "A"))); + $page->acceptLabelEnd(); + $page->AcceptTag( + new SimpleSubmitTag(array("type" => "submit", "name" => "s"))); + $page->acceptFormEnd(); + $this->assertEqual($page->getField(new SimpleByLabel('l')), 'A'); + } +} + +class TestOfPageScraping extends UnitTestCase { + + function parse($response) { + $builder = new SimplePageBuilder(); + $page = $builder->parse($response); + return $page; + } + + function testEmptyPage() { + $page = new SimplePage(new MockSimpleHttpResponse()); + $this->assertIdentical($page->getUrls(), array()); + $this->assertIdentical($page->getTitle(), false); + } + + function testUninterestingPage() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', '

    Stuff

    '); + $page = $this->parse($response); + $this->assertIdentical($page->getUrls(), array()); + } + + function testLinksPage() { + $raw = ''; + $raw .= '
    There'; + $raw .= 'That page'; + $raw .= ''; + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', $raw); + $response->setReturnValue('getUrl', new SimpleUrl('http://www.here.com/a/index.html')); + $page = $this->parse($response); + $this->assertIdentical( + $page->getUrls(), + array('http://www.here.com/a/there.html', 'http://there.com/that.html')); + $this->assertIdentical( + $page->getUrlsByLabel('There'), + array(new SimpleUrl('http://www.here.com/a/there.html'))); + $this->assertEqual( + $page->getUrlById('0'), + new SimpleUrl('http://there.com/that.html')); + } + + function testTitle() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', 'Me'); + $page = $this->parse($response); + $this->assertEqual($page->getTitle(), 'Me'); + } + + function testNastyTitle() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue( + 'getContent', + ' <b>Me&Me '); + $page = $this->parse($response); + $this->assertEqual($page->getTitle(), "Me&Me"); + } + + function testCompleteForm() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', + '
    ' . + '' . + '
    '); + $page = $this->parse($response); + $this->assertEqual($page->getField(new SimpleByName('here')), "Hello"); + } + + function testUnclosedForm() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', + '
    ' . + '' . + ''); + $page = $this->parse($response); + $this->assertEqual($page->getField(new SimpleByName('here')), "Hello"); + } + + function testEmptyFrameset() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue( + 'getContent', + ''); + $page = $this->parse($response); + $this->assertTrue($page->hasFrames()); + $this->assertIdentical($page->getFrameset(), array()); + } + + function testSingleFrame() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue( + 'getContent', + ''); + $response->setReturnValue('getUrl', new SimpleUrl('http://host/')); + $page = $this->parse($response); + $this->assertTrue($page->hasFrames()); + $this->assertIdentical( + $page->getFrameset(), + array(1 => new SimpleUrl('http://host/a.html'))); + } + + function testSingleFrameInNestedFrameset() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', + '' . + '' . + ''); + $response->setReturnValue('getUrl', new SimpleUrl('http://host/')); + $page = $this->parse($response); + $this->assertTrue($page->hasFrames()); + $this->assertIdentical( + $page->getFrameset(), + array(1 => new SimpleUrl('http://host/a.html'))); + } + + function testFrameWithNoSource() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue( + 'getContent', + ''); + $page = $this->parse($response); + $this->assertTrue($page->hasFrames()); + $this->assertIdentical($page->getFrameset(), array()); + } + + function testFramesCollectedWithNestedFramesetTags() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', + '' . + '' . + '' . + '' . + ''); + $response->setReturnValue('getUrl', new SimpleUrl('http://host/')); + $page = $this->parse($response); + $this->assertTrue($page->hasFrames()); + $this->assertIdentical($page->getFrameset(), array( + 1 => new SimpleUrl('http://host/a.html'), + 2 => new SimpleUrl('http://host/b.html'), + 3 => new SimpleUrl('http://host/c.html'))); + } + + function testNamedFrames() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', + '' . + '' . + '' . + '' . + '' . + ''); + $response->setReturnValue('getUrl', new SimpleUrl('http://host/')); + $page = $this->parse($response); + $this->assertTrue($page->hasFrames()); + $this->assertIdentical($page->getFrameset(), array( + 1 => new SimpleUrl('http://host/a.html'), + '_one' => new SimpleUrl('http://host/b.html'), + 3 => new SimpleUrl('http://host/c.html'), + '_two' => new SimpleUrl('http://host/d.html'))); + } + + function testFindFormByLabel() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue( + 'getContent', + '
    '); + $page = $this->parse($response); + $this->assertNull($page->getFormBySubmit(new SimpleByLabel('submit'))); + $this->assertNull($page->getFormBySubmit(new SimpleByName('submit'))); + $this->assertIsA( + $page->getFormBySubmit(new SimpleByLabel('Submit')), + 'SimpleForm'); + } + + function testConfirmSubmitAttributesAreCaseSensitive() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue( + 'getContent', + '
    '); + $page = $this->parse($response); + $this->assertIsA( + $page->getFormBySubmit(new SimpleByName('S')), + 'SimpleForm'); + $this->assertIsA( + $page->getFormBySubmit(new SimpleByLabel('S')), + 'SimpleForm'); + } + + function testFindFormByImage() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', + '
    ' . + '' . + '
    '); + $page = $this->parse($response); + $this->assertIsA( + $page->getFormByImage(new SimpleByLabel('Label')), + 'SimpleForm'); + $this->assertIsA( + $page->getFormByImage(new SimpleByName('me')), + 'SimpleForm'); + $this->assertIsA( + $page->getFormByImage(new SimpleById(100)), + 'SimpleForm'); + } + + function testFindFormByButtonTag() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', + '
    ' . + '' . + '
    '); + $page = $this->parse($response); + $this->assertNull($page->getFormBySubmit(new SimpleByLabel('b'))); + $this->assertNull($page->getFormBySubmit(new SimpleByLabel('B'))); + $this->assertIsA( + $page->getFormBySubmit(new SimpleByName('b')), + 'SimpleForm'); + $this->assertIsA( + $page->getFormBySubmit(new SimpleByLabel('BBB')), + 'SimpleForm'); + } + + function testFindFormById() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue( + 'getContent', + '
    '); + $page = $this->parse($response); + $this->assertNull($page->getFormById(54)); + $this->assertIsA($page->getFormById(55), 'SimpleForm'); + } + + function testReadingTextField() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', + '
    ' . + '' . + '' . + '
    '); + $page = $this->parse($response); + $this->assertNull($page->getField(new SimpleByName('missing'))); + $this->assertIdentical($page->getField(new SimpleByName('a')), ''); + $this->assertIdentical($page->getField(new SimpleByName('b')), 'bbb'); + } + + function testReadingTextFieldIsCaseInsensitive() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', + '
    ' . + '' . + '' . + '
    '); + $page = $this->parse($response); + $this->assertNull($page->getField(new SimpleByName('missing'))); + $this->assertIdentical($page->getField(new SimpleByName('a')), ''); + $this->assertIdentical($page->getField(new SimpleByName('b')), 'bbb'); + } + + function testSettingTextField() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', + '
    ' . + '' . + '' . + '' . + '
    '); + $page = $this->parse($response); + $this->assertTrue($page->setField(new SimpleByName('a'), 'aaa')); + $this->assertEqual($page->getField(new SimpleByName('a')), 'aaa'); + $this->assertTrue($page->setField(new SimpleById(3), 'bbb')); + $this->assertEqual($page->getField(new SimpleBYId(3)), 'bbb'); + $this->assertFalse($page->setField(new SimpleByName('z'), 'zzz')); + $this->assertNull($page->getField(new SimpleByName('z'))); + } + + function testSettingTextFieldByEnclosingLabel() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', + '
    ' . + '' . + '
    '); + $page = $this->parse($response); + $this->assertEqual($page->getField(new SimpleByName('a')), 'A'); + $this->assertEqual($page->getField(new SimpleByLabel('Stuff')), 'A'); + $this->assertTrue($page->setField(new SimpleByLabel('Stuff'), 'aaa')); + $this->assertEqual($page->getField(new SimpleByLabel('Stuff')), 'aaa'); + } + + function testGettingTextFieldByEnclosingLabelWithConflictingOtherFields() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', + '
    ' . + '' . + '' . + '
    '); + $page = $this->parse($response); + $this->assertEqual($page->getField(new SimpleByName('a')), 'A'); + $this->assertEqual($page->getField(new SimpleByName('b')), 'B'); + $this->assertEqual($page->getField(new SimpleByLabel('Stuff')), 'A'); + } + + function testSettingTextFieldByExternalLabel() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', + '
    ' . + '' . + '' . + '
    '); + $page = $this->parse($response); + $this->assertEqual($page->getField(new SimpleByLabel('Stuff')), 'A'); + $this->assertTrue($page->setField(new SimpleByLabel('Stuff'), 'aaa')); + $this->assertEqual($page->getField(new SimpleByLabel('Stuff')), 'aaa'); + } + + function testReadingTextArea() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', + '
    ' . + '' . + '' . + '
    '); + $page = $this->parse($response); + $this->assertEqual($page->getField(new SimpleByName('a')), 'aaa'); + } + + function testSettingTextArea() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', + '
    ' . + '' . + '' . + '
    '); + $page = $this->parse($response); + $this->assertTrue($page->setField(new SimpleByName('a'), 'AAA')); + $this->assertEqual($page->getField(new SimpleByName('a')), 'AAA'); + } + + function testSettingSelectionField() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', + '
    ' . + '' . + '' . + '
    '); + $page = $this->parse($response); + $this->assertEqual($page->getField(new SimpleByName('a')), 'bbb'); + $this->assertFalse($page->setField(new SimpleByName('a'), 'ccc')); + $this->assertTrue($page->setField(new SimpleByName('a'), 'aaa')); + $this->assertEqual($page->getField(new SimpleByName('a')), 'aaa'); + } + + function testSettingSelectionFieldByEnclosingLabel() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', + '
    ' . + '' . + '
    '); + $page = $this->parse($response); + $this->assertEqual($page->getField(new SimpleByLabel('Stuff')), 'A'); + $this->assertTrue($page->setField(new SimpleByLabel('Stuff'), 'B')); + $this->assertEqual($page->getField(new SimpleByLabel('Stuff')), 'B'); + } + + function testSettingRadioButtonByEnclosingLabel() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', + '
    ' . + '' . + '' . + '
    '); + $page = $this->parse($response); + $this->assertEqual($page->getField(new SimpleByLabel('A')), 'a'); + $this->assertTrue($page->setField(new SimpleBylabel('B'), 'b')); + $this->assertEqual($page->getField(new SimpleByLabel('B')), 'b'); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/parse_error_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/parse_error_test.php new file mode 100644 index 0000000..c3ffb3d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/parse_error_test.php @@ -0,0 +1,9 @@ +addFile('test_with_parse_error.php'); +$test->run(new HtmlReporter()); +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/parser_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/parser_test.php new file mode 100644 index 0000000..fd01d57 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/parser_test.php @@ -0,0 +1,551 @@ +assertFalse($regex->match("Hello", $match)); + $this->assertEqual($match, ""); + } + + function testNoSubject() { + $regex = new ParallelRegex(false); + $regex->addPattern(".*"); + $this->assertTrue($regex->match("", $match)); + $this->assertEqual($match, ""); + } + + function testMatchAll() { + $regex = new ParallelRegex(false); + $regex->addPattern(".*"); + $this->assertTrue($regex->match("Hello", $match)); + $this->assertEqual($match, "Hello"); + } + + function testCaseSensitive() { + $regex = new ParallelRegex(true); + $regex->addPattern("abc"); + $this->assertTrue($regex->match("abcdef", $match)); + $this->assertEqual($match, "abc"); + $this->assertTrue($regex->match("AAABCabcdef", $match)); + $this->assertEqual($match, "abc"); + } + + function testCaseInsensitive() { + $regex = new ParallelRegex(false); + $regex->addPattern("abc"); + $this->assertTrue($regex->match("abcdef", $match)); + $this->assertEqual($match, "abc"); + $this->assertTrue($regex->match("AAABCabcdef", $match)); + $this->assertEqual($match, "ABC"); + } + + function testMatchMultiple() { + $regex = new ParallelRegex(true); + $regex->addPattern("abc"); + $regex->addPattern("ABC"); + $this->assertTrue($regex->match("abcdef", $match)); + $this->assertEqual($match, "abc"); + $this->assertTrue($regex->match("AAABCabcdef", $match)); + $this->assertEqual($match, "ABC"); + $this->assertFalse($regex->match("Hello", $match)); + } + + function testPatternLabels() { + $regex = new ParallelRegex(false); + $regex->addPattern("abc", "letter"); + $regex->addPattern("123", "number"); + $this->assertIdentical($regex->match("abcdef", $match), "letter"); + $this->assertEqual($match, "abc"); + $this->assertIdentical($regex->match("0123456789", $match), "number"); + $this->assertEqual($match, "123"); + } +} + +class TestOfStateStack extends UnitTestCase { + + function testStartState() { + $stack = new SimpleStateStack("one"); + $this->assertEqual($stack->getCurrent(), "one"); + } + + function testExhaustion() { + $stack = new SimpleStateStack("one"); + $this->assertFalse($stack->leave()); + } + + function testStateMoves() { + $stack = new SimpleStateStack("one"); + $stack->enter("two"); + $this->assertEqual($stack->getCurrent(), "two"); + $stack->enter("three"); + $this->assertEqual($stack->getCurrent(), "three"); + $this->assertTrue($stack->leave()); + $this->assertEqual($stack->getCurrent(), "two"); + $stack->enter("third"); + $this->assertEqual($stack->getCurrent(), "third"); + $this->assertTrue($stack->leave()); + $this->assertTrue($stack->leave()); + $this->assertEqual($stack->getCurrent(), "one"); + } +} + +class TestParser { + + function accept() { + } + + function a() { + } + + function b() { + } +} +Mock::generate('TestParser'); + +class TestOfLexer extends UnitTestCase { + + function testEmptyPage() { + $handler = new MockTestParser(); + $handler->expectNever("accept"); + $handler->setReturnValue("accept", true); + $handler->expectNever("accept"); + $handler->setReturnValue("accept", true); + $lexer = new SimpleLexer($handler); + $lexer->addPattern("a+"); + $this->assertTrue($lexer->parse("")); + } + + function testSinglePattern() { + $handler = new MockTestParser(); + $handler->expectAt(0, "accept", array("aaa", LEXER_MATCHED)); + $handler->expectAt(1, "accept", array("x", LEXER_UNMATCHED)); + $handler->expectAt(2, "accept", array("a", LEXER_MATCHED)); + $handler->expectAt(3, "accept", array("yyy", LEXER_UNMATCHED)); + $handler->expectAt(4, "accept", array("a", LEXER_MATCHED)); + $handler->expectAt(5, "accept", array("x", LEXER_UNMATCHED)); + $handler->expectAt(6, "accept", array("aaa", LEXER_MATCHED)); + $handler->expectAt(7, "accept", array("z", LEXER_UNMATCHED)); + $handler->expectCallCount("accept", 8); + $handler->setReturnValue("accept", true); + $lexer = new SimpleLexer($handler); + $lexer->addPattern("a+"); + $this->assertTrue($lexer->parse("aaaxayyyaxaaaz")); + } + + function testMultiplePattern() { + $handler = new MockTestParser(); + $target = array("a", "b", "a", "bb", "x", "b", "a", "xxxxxx", "a", "x"); + for ($i = 0; $i < count($target); $i++) { + $handler->expectAt($i, "accept", array($target[$i], '*')); + } + $handler->expectCallCount("accept", count($target)); + $handler->setReturnValue("accept", true); + $lexer = new SimpleLexer($handler); + $lexer->addPattern("a+"); + $lexer->addPattern("b+"); + $this->assertTrue($lexer->parse("ababbxbaxxxxxxax")); + } +} + +class TestOfLexerModes extends UnitTestCase { + + function testIsolatedPattern() { + $handler = new MockTestParser(); + $handler->expectAt(0, "a", array("a", LEXER_MATCHED)); + $handler->expectAt(1, "a", array("b", LEXER_UNMATCHED)); + $handler->expectAt(2, "a", array("aa", LEXER_MATCHED)); + $handler->expectAt(3, "a", array("bxb", LEXER_UNMATCHED)); + $handler->expectAt(4, "a", array("aaa", LEXER_MATCHED)); + $handler->expectAt(5, "a", array("x", LEXER_UNMATCHED)); + $handler->expectAt(6, "a", array("aaaa", LEXER_MATCHED)); + $handler->expectAt(7, "a", array("x", LEXER_UNMATCHED)); + $handler->expectCallCount("a", 8); + $handler->setReturnValue("a", true); + $lexer = new SimpleLexer($handler, "a"); + $lexer->addPattern("a+", "a"); + $lexer->addPattern("b+", "b"); + $this->assertTrue($lexer->parse("abaabxbaaaxaaaax")); + } + + function testModeChange() { + $handler = new MockTestParser(); + $handler->expectAt(0, "a", array("a", LEXER_MATCHED)); + $handler->expectAt(1, "a", array("b", LEXER_UNMATCHED)); + $handler->expectAt(2, "a", array("aa", LEXER_MATCHED)); + $handler->expectAt(3, "a", array("b", LEXER_UNMATCHED)); + $handler->expectAt(4, "a", array("aaa", LEXER_MATCHED)); + $handler->expectAt(0, "b", array(":", LEXER_ENTER)); + $handler->expectAt(1, "b", array("a", LEXER_UNMATCHED)); + $handler->expectAt(2, "b", array("b", LEXER_MATCHED)); + $handler->expectAt(3, "b", array("a", LEXER_UNMATCHED)); + $handler->expectAt(4, "b", array("bb", LEXER_MATCHED)); + $handler->expectAt(5, "b", array("a", LEXER_UNMATCHED)); + $handler->expectAt(6, "b", array("bbb", LEXER_MATCHED)); + $handler->expectAt(7, "b", array("a", LEXER_UNMATCHED)); + $handler->expectCallCount("a", 5); + $handler->expectCallCount("b", 8); + $handler->setReturnValue("a", true); + $handler->setReturnValue("b", true); + $lexer = new SimpleLexer($handler, "a"); + $lexer->addPattern("a+", "a"); + $lexer->addEntryPattern(":", "a", "b"); + $lexer->addPattern("b+", "b"); + $this->assertTrue($lexer->parse("abaabaaa:ababbabbba")); + } + + function testNesting() { + $handler = new MockTestParser(); + $handler->setReturnValue("a", true); + $handler->setReturnValue("b", true); + $handler->expectAt(0, "a", array("aa", LEXER_MATCHED)); + $handler->expectAt(1, "a", array("b", LEXER_UNMATCHED)); + $handler->expectAt(2, "a", array("aa", LEXER_MATCHED)); + $handler->expectAt(3, "a", array("b", LEXER_UNMATCHED)); + $handler->expectAt(0, "b", array("(", LEXER_ENTER)); + $handler->expectAt(1, "b", array("bb", LEXER_MATCHED)); + $handler->expectAt(2, "b", array("a", LEXER_UNMATCHED)); + $handler->expectAt(3, "b", array("bb", LEXER_MATCHED)); + $handler->expectAt(4, "b", array(")", LEXER_EXIT)); + $handler->expectAt(4, "a", array("aa", LEXER_MATCHED)); + $handler->expectAt(5, "a", array("b", LEXER_UNMATCHED)); + $handler->expectCallCount("a", 6); + $handler->expectCallCount("b", 5); + $lexer = new SimpleLexer($handler, "a"); + $lexer->addPattern("a+", "a"); + $lexer->addEntryPattern("(", "a", "b"); + $lexer->addPattern("b+", "b"); + $lexer->addExitPattern(")", "b"); + $this->assertTrue($lexer->parse("aabaab(bbabb)aab")); + } + + function testSingular() { + $handler = new MockTestParser(); + $handler->setReturnValue("a", true); + $handler->setReturnValue("b", true); + $handler->expectAt(0, "a", array("aa", LEXER_MATCHED)); + $handler->expectAt(1, "a", array("aa", LEXER_MATCHED)); + $handler->expectAt(2, "a", array("xx", LEXER_UNMATCHED)); + $handler->expectAt(3, "a", array("xx", LEXER_UNMATCHED)); + $handler->expectAt(0, "b", array("b", LEXER_SPECIAL)); + $handler->expectAt(1, "b", array("bbb", LEXER_SPECIAL)); + $handler->expectCallCount("a", 4); + $handler->expectCallCount("b", 2); + $lexer = new SimpleLexer($handler, "a"); + $lexer->addPattern("a+", "a"); + $lexer->addSpecialPattern("b+", "a", "b"); + $this->assertTrue($lexer->parse("aabaaxxbbbxx")); + } + + function testUnwindTooFar() { + $handler = new MockTestParser(); + $handler->setReturnValue("a", true); + $handler->expectAt(0, "a", array("aa", LEXER_MATCHED)); + $handler->expectAt(1, "a", array(")", LEXER_EXIT)); + $handler->expectCallCount("a", 2); + $lexer = new SimpleLexer($handler, "a"); + $lexer->addPattern("a+", "a"); + $lexer->addExitPattern(")", "a"); + $this->assertFalse($lexer->parse("aa)aa")); + } +} + +class TestOfLexerHandlers extends UnitTestCase { + + function testModeMapping() { + $handler = new MockTestParser(); + $handler->setReturnValue("a", true); + $handler->expectAt(0, "a", array("aa", LEXER_MATCHED)); + $handler->expectAt(1, "a", array("(", LEXER_ENTER)); + $handler->expectAt(2, "a", array("bb", LEXER_MATCHED)); + $handler->expectAt(3, "a", array("a", LEXER_UNMATCHED)); + $handler->expectAt(4, "a", array("bb", LEXER_MATCHED)); + $handler->expectAt(5, "a", array(")", LEXER_EXIT)); + $handler->expectAt(6, "a", array("b", LEXER_UNMATCHED)); + $handler->expectCallCount("a", 7); + $lexer = new SimpleLexer($handler, "mode_a"); + $lexer->addPattern("a+", "mode_a"); + $lexer->addEntryPattern("(", "mode_a", "mode_b"); + $lexer->addPattern("b+", "mode_b"); + $lexer->addExitPattern(")", "mode_b"); + $lexer->mapHandler("mode_a", "a"); + $lexer->mapHandler("mode_b", "a"); + $this->assertTrue($lexer->parse("aa(bbabb)b")); + } +} + +class TestOfSimpleHtmlLexer extends UnitTestCase { + + function &createParser() { + $parser = new MockSimpleHtmlSaxParser(); + $parser->setReturnValue('acceptStartToken', true); + $parser->setReturnValue('acceptEndToken', true); + $parser->setReturnValue('acceptAttributeToken', true); + $parser->setReturnValue('acceptEntityToken', true); + $parser->setReturnValue('acceptTextToken', true); + $parser->setReturnValue('ignore', true); + return $parser; + } + + function testNoContent() { + $parser = &$this->createParser(); + $parser->expectNever('acceptStartToken'); + $parser->expectNever('acceptEndToken'); + $parser->expectNever('acceptAttributeToken'); + $parser->expectNever('acceptEntityToken'); + $parser->expectNever('acceptTextToken'); + $lexer = new SimpleHtmlLexer($parser); + $this->assertTrue($lexer->parse('')); + } + + function testUninteresting() { + $parser = &$this->createParser(); + $parser->expectOnce('acceptTextToken', array('', '*')); + $lexer = new SimpleHtmlLexer($parser); + $this->assertTrue($lexer->parse('')); + } + + function testSkipCss() { + $parser = &$this->createParser(); + $parser->expectNever('acceptTextToken'); + $parser->expectAtLeastOnce('ignore'); + $lexer = new SimpleHtmlLexer($parser); + $this->assertTrue($lexer->parse("")); + } + + function testSkipJavaScript() { + $parser = &$this->createParser(); + $parser->expectNever('acceptTextToken'); + $parser->expectAtLeastOnce('ignore'); + $lexer = new SimpleHtmlLexer($parser); + $this->assertTrue($lexer->parse("")); + } + + function testSkipHtmlComments() { + $parser = &$this->createParser(); + $parser->expectNever('acceptTextToken'); + $parser->expectAtLeastOnce('ignore'); + $lexer = new SimpleHtmlLexer($parser); + $this->assertTrue($lexer->parse("")); + } + + function testTagWithNoAttributes() { + $parser = &$this->createParser(); + $parser->expectAt(0, 'acceptStartToken', array('expectAt(1, 'acceptStartToken', array('>', '*')); + $parser->expectCallCount('acceptStartToken', 2); + $parser->expectOnce('acceptTextToken', array('Hello', '*')); + $parser->expectOnce('acceptEndToken', array('', '*')); + $lexer = new SimpleHtmlLexer($parser); + $this->assertTrue($lexer->parse('Hello')); + } + + function testTagWithAttributes() { + $parser = &$this->createParser(); + $parser->expectOnce('acceptTextToken', array('label', '*')); + $parser->expectAt(0, 'acceptStartToken', array('expectAt(1, 'acceptStartToken', array('href', '*')); + $parser->expectAt(2, 'acceptStartToken', array('>', '*')); + $parser->expectCallCount('acceptStartToken', 3); + $parser->expectAt(0, 'acceptAttributeToken', array('= "', '*')); + $parser->expectAt(1, 'acceptAttributeToken', array('here.html', '*')); + $parser->expectAt(2, 'acceptAttributeToken', array('"', '*')); + $parser->expectCallCount('acceptAttributeToken', 3); + $parser->expectOnce('acceptEndToken', array('', '*')); + $lexer = new SimpleHtmlLexer($parser); + $this->assertTrue($lexer->parse('label')); + } +} + +class TestOfHtmlSaxParser extends UnitTestCase { + + function &createListener() { + $listener = new MockSimpleSaxListener(); + $listener->setReturnValue('startElement', true); + $listener->setReturnValue('addContent', true); + $listener->setReturnValue('endElement', true); + return $listener; + } + + function testFramesetTag() { + $listener = &$this->createListener(); + $listener->expectOnce('startElement', array('frameset', array())); + $listener->expectOnce('addContent', array('Frames')); + $listener->expectOnce('endElement', array('frameset')); + $parser = new SimpleHtmlSaxParser($listener); + $this->assertTrue($parser->parse('Frames')); + } + + function testTagWithUnquotedAttributes() { + $listener = &$this->createListener(); + $listener->expectOnce( + 'startElement', + array('input', array('name' => 'a.b.c', 'value' => 'd'))); + $parser = new SimpleHtmlSaxParser($listener); + $this->assertTrue($parser->parse('')); + } + + function testTagInsideContent() { + $listener = &$this->createListener(); + $listener->expectOnce('startElement', array('a', array())); + $listener->expectAt(0, 'addContent', array('')); + $listener->expectAt(1, 'addContent', array('')); + $parser = new SimpleHtmlSaxParser($listener); + $this->assertTrue($parser->parse('')); + } + + function testTagWithInternalContent() { + $listener = &$this->createListener(); + $listener->expectOnce('startElement', array('a', array())); + $listener->expectOnce('addContent', array('label')); + $listener->expectOnce('endElement', array('a')); + $parser = new SimpleHtmlSaxParser($listener); + $this->assertTrue($parser->parse('label')); + } + + function testLinkAddress() { + $listener = &$this->createListener(); + $listener->expectOnce('startElement', array('a', array('href' => 'here.html'))); + $listener->expectOnce('addContent', array('label')); + $listener->expectOnce('endElement', array('a')); + $parser = new SimpleHtmlSaxParser($listener); + $this->assertTrue($parser->parse("label")); + } + + function testEncodedAttribute() { + $listener = &$this->createListener(); + $listener->expectOnce('startElement', array('a', array('href' => 'here&there.html'))); + $listener->expectOnce('addContent', array('label')); + $listener->expectOnce('endElement', array('a')); + $parser = new SimpleHtmlSaxParser($listener); + $this->assertTrue($parser->parse("label")); + } + + function testTagWithId() { + $listener = &$this->createListener(); + $listener->expectOnce('startElement', array('a', array('id' => '0'))); + $listener->expectOnce('addContent', array('label')); + $listener->expectOnce('endElement', array('a')); + $parser = new SimpleHtmlSaxParser($listener); + $this->assertTrue($parser->parse('label')); + } + + function testTagWithEmptyAttributes() { + $listener = &$this->createListener(); + $listener->expectOnce( + 'startElement', + array('option', array('value' => '', 'selected' => ''))); + $listener->expectOnce('addContent', array('label')); + $listener->expectOnce('endElement', array('option')); + $parser = new SimpleHtmlSaxParser($listener); + $this->assertTrue($parser->parse('')); + } + + function testComplexTagWithLotsOfCaseVariations() { + $listener = &$this->createListener(); + $listener->expectOnce( + 'startElement', + array('a', array('href' => 'here.html', 'style' => "'cool'"))); + $listener->expectOnce('addContent', array('label')); + $listener->expectOnce('endElement', array('a')); + $parser = new SimpleHtmlSaxParser($listener); + $this->assertTrue($parser->parse('label')); + } + + function testXhtmlSelfClosingTag() { + $listener = &$this->createListener(); + $listener->expectOnce( + 'startElement', + array('input', array('type' => 'submit', 'name' => 'N', 'value' => 'V'))); + $parser = new SimpleHtmlSaxParser($listener); + $this->assertTrue($parser->parse('')); + } + + function testNestedFrameInFrameset() { + $listener = &$this->createListener(); + $listener->expectAt(0, 'startElement', array('frameset', array())); + $listener->expectAt(1, 'startElement', array('frame', array('src' => 'frame.html'))); + $listener->expectCallCount('startElement', 2); + $listener->expectOnce('addContent', array('Hello')); + $listener->expectOnce('endElement', array('frameset')); + $parser = new SimpleHtmlSaxParser($listener); + $this->assertTrue($parser->parse( + 'Hello')); + } +} + +class TestOfTextExtraction extends UnitTestCase { + + function testImageSuppressionWhileKeepingParagraphsAndAltText() { + $this->assertEqual( + SimpleHtmlSaxParser::normalise('

    some text

    bar'), + 'some text bar'); + + } + + function testSpaceNormalisation() { + $this->assertEqual( + SimpleHtmlSaxParser::normalise("\nOne\tTwo \nThree\t"), + 'One Two Three'); + } + + function testMultilinesCommentSuppression() { + $this->assertEqual( + SimpleHtmlSaxParser::normalise(''), + ''); + } + + function testCommentSuppression() { + $this->assertEqual( + SimpleHtmlSaxParser::normalise(''), + ''); + } + + function testJavascriptSuppression() { + $this->assertEqual( + SimpleHtmlSaxParser::normalise(''), + ''); + $this->assertEqual( + SimpleHtmlSaxParser::normalise(''), + ''); + $this->assertEqual( + SimpleHtmlSaxParser::normalise(''), + ''); + } + + function testTagSuppression() { + $this->assertEqual( + SimpleHtmlSaxParser::normalise('Hello'), + 'Hello'); + } + + function testAdjoiningTagSuppression() { + $this->assertEqual( + SimpleHtmlSaxParser::normalise('HelloGoodbye'), + 'HelloGoodbye'); + } + + function testExtractImageAltTextWithDifferentQuotes() { + $this->assertEqual( + SimpleHtmlSaxParser::normalise('One\'Two\'Three'), + 'One Two Three'); + } + + function testExtractImageAltTextMultipleTimes() { + $this->assertEqual( + SimpleHtmlSaxParser::normalise('OneTwoThree'), + 'One Two Three'); + } + + function testHtmlEntityTranslation() { + $this->assertEqual( + SimpleHtmlSaxParser::normalise('<>"&''), + '<>"&\''); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/reflection_php4_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/reflection_php4_test.php new file mode 100644 index 0000000..8ee211b --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/reflection_php4_test.php @@ -0,0 +1,61 @@ +assertTrue($reflection->classOrInterfaceExists()); + $this->assertTrue($reflection->classOrInterfaceExistsSansAutoload()); + } + + function testClassNonExistence() { + $reflection = new SimpleReflection('UnknownThing'); + $this->assertFalse($reflection->classOrInterfaceExists()); + $this->assertFalse($reflection->classOrInterfaceExistsSansAutoload()); + } + + function testDetectionOfInterfacesAlwaysFalse() { + $reflection = new SimpleReflection('AnyOldThing'); + $this->assertFalse($reflection->isAbstract()); + $this->assertFalse($reflection->isInterface()); + } + + function testFindingParentClass() { + $reflection = new SimpleReflection('AnyOldChildThing'); + $this->assertEqual(strtolower($reflection->getParent()), 'anyoldthing'); + } + + function testMethodsListFromClass() { + $reflection = new SimpleReflection('AnyOldThing'); + $methods = $reflection->getMethods(); + $this->assertEqualIgnoringCase($methods[0], 'aMethod'); + } + + function testNoInterfacesForPHP4() { + $reflection = new SimpleReflection('AnyOldThing'); + $this->assertEqual( + $reflection->getInterfaces(), + array()); + } + + function testMostGeneralPossibleSignature() { + $reflection = new SimpleReflection('AnyOldThing'); + $this->assertEqualIgnoringCase( + $reflection->getSignature('aMethod'), + 'function &aMethod()'); + } + + function assertEqualIgnoringCase($a, $b) { + return $this->assertEqual(strtolower($a), strtolower($b)); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/reflection_php5_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/reflection_php5_test.php new file mode 100644 index 0000000..d9f46e6 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/reflection_php5_test.php @@ -0,0 +1,263 @@ +assertTrue($reflection->classOrInterfaceExists()); + $this->assertTrue($reflection->classOrInterfaceExistsSansAutoload()); + $this->assertFalse($reflection->isAbstract()); + $this->assertFalse($reflection->isInterface()); + } + + function testClassNonExistence() { + $reflection = new SimpleReflection('UnknownThing'); + $this->assertFalse($reflection->classOrInterfaceExists()); + $this->assertFalse($reflection->classOrInterfaceExistsSansAutoload()); + } + + function testDetectionOfAbstractClass() { + $reflection = new SimpleReflection('AnyOldClass'); + $this->assertTrue($reflection->isAbstract()); + } + + function testDetectionOfFinalMethods() { + $reflection = new SimpleReflection('AnyOldClass'); + $this->assertFalse($reflection->hasFinal()); + $reflection = new SimpleReflection('AnyOldLeafClassWithAFinal'); + $this->assertTrue($reflection->hasFinal()); + } + + function testFindingParentClass() { + $reflection = new SimpleReflection('AnyOldSubclass'); + $this->assertEqual($reflection->getParent(), 'AnyOldImplementation'); + } + + function testInterfaceExistence() { + $reflection = new SimpleReflection('AnyOldInterface'); + $this->assertTrue($reflection->classOrInterfaceExists()); + $this->assertTrue($reflection->classOrInterfaceExistsSansAutoload()); + $this->assertTrue($reflection->isInterface()); + } + + function testMethodsListFromClass() { + $reflection = new SimpleReflection('AnyOldClass'); + $this->assertIdentical($reflection->getMethods(), array('aMethod')); + } + + function testMethodsListFromInterface() { + $reflection = new SimpleReflection('AnyOldInterface'); + $this->assertIdentical($reflection->getMethods(), array('aMethod')); + $this->assertIdentical($reflection->getInterfaceMethods(), array('aMethod')); + } + + function testMethodsComeFromDescendentInterfacesASWell() { + $reflection = new SimpleReflection('AnyDescendentInterface'); + $this->assertIdentical($reflection->getMethods(), array('aMethod')); + } + + function testCanSeparateInterfaceMethodsFromOthers() { + $reflection = new SimpleReflection('AnyOldImplementation'); + $this->assertIdentical($reflection->getMethods(), array('aMethod', 'extraMethod')); + $this->assertIdentical($reflection->getInterfaceMethods(), array('aMethod')); + } + + function testMethodsComeFromDescendentInterfacesInAbstractClass() { + $reflection = new SimpleReflection('AnyAbstractImplementation'); + $this->assertIdentical($reflection->getMethods(), array('aMethod')); + } + + function testInterfaceHasOnlyItselfToImplement() { + $reflection = new SimpleReflection('AnyOldInterface'); + $this->assertEqual( + $reflection->getInterfaces(), + array('AnyOldInterface')); + } + + function testInterfacesListedForClass() { + $reflection = new SimpleReflection('AnyOldImplementation'); + $this->assertEqual( + $reflection->getInterfaces(), + array('AnyOldInterface')); + } + + function testInterfacesListedForSubclass() { + $reflection = new SimpleReflection('AnyOldSubclass'); + $this->assertEqual( + $reflection->getInterfaces(), + array('AnyOldInterface')); + } + + function testNoParameterCreationWhenNoInterface() { + $reflection = new SimpleReflection('AnyOldArgumentClass'); + $function = $reflection->getSignature('aMethod'); + if (version_compare(phpversion(), '5.0.2', '<=')) { + $this->assertEqual('function amethod($argument)', strtolower($function)); + } else { + $this->assertEqual('function aMethod($argument)', $function); + } + } + + function testParameterCreationWithoutTypeHinting() { + $reflection = new SimpleReflection('AnyOldArgumentImplementation'); + $function = $reflection->getSignature('aMethod'); + if (version_compare(phpversion(), '5.0.2', '<=')) { + $this->assertEqual('function amethod(AnyOldInterface $argument)', $function); + } else { + $this->assertEqual('function aMethod(AnyOldInterface $argument)', $function); + } + } + + function testParameterCreationForTypeHinting() { + $reflection = new SimpleReflection('AnyOldTypeHintedClass'); + $function = $reflection->getSignature('aMethod'); + if (version_compare(phpversion(), '5.0.2', '<=')) { + $this->assertEqual('function amethod(AnyOldInterface $argument)', $function); + } else { + $this->assertEqual('function aMethod(AnyOldInterface $argument)', $function); + } + } + + function testIssetFunctionSignature() { + $reflection = new SimpleReflection('AnyOldOverloadedClass'); + $function = $reflection->getSignature('__isset'); + $this->assertEqual('function __isset($key)', $function); + } + + function testUnsetFunctionSignature() { + $reflection = new SimpleReflection('AnyOldOverloadedClass'); + $function = $reflection->getSignature('__unset'); + $this->assertEqual('function __unset($key)', $function); + } + + function testProperlyReflectsTheFinalInterfaceWhenObjectImplementsAnExtendedInterface() { + $reflection = new SimpleReflection('AnyDescendentImplementation'); + $interfaces = $reflection->getInterfaces(); + $this->assertEqual(1, count($interfaces)); + $this->assertEqual('AnyDescendentInterface', array_shift($interfaces)); + } + + function testCreatingSignatureForAbstractMethod() { + $reflection = new SimpleReflection('AnotherOldAbstractClass'); + $this->assertEqual($reflection->getSignature('aMethod'), 'function aMethod(AnyOldInterface $argument)'); + } + + function testCanProperlyGenerateStaticMethodSignatures() { + $reflection = new SimpleReflection('AnyOldClassWithStaticMethods'); + $this->assertEqual('static function aStatic()', $reflection->getSignature('aStatic')); + $this->assertEqual( + 'static function aStaticWithParameters($arg1, $arg2)', + $reflection->getSignature('aStaticWithParameters') + ); + } +} + +class TestOfReflectionWithTypeHints extends UnitTestCase { + function skip() { + $this->skipIf(version_compare(phpversion(), '5.1.0', '<'), 'Reflection with type hints only tested for PHP 5.1.0 and above'); + } + + function testParameterCreationForTypeHintingWithArray() { + eval('interface AnyOldArrayTypeHintedInterface { + function amethod(array $argument); + } + class AnyOldArrayTypeHintedClass implements AnyOldArrayTypeHintedInterface { + function amethod(array $argument) {} + }'); + $reflection = new SimpleReflection('AnyOldArrayTypeHintedClass'); + $function = $reflection->getSignature('amethod'); + $this->assertEqual('function amethod(array $argument)', $function); + } +} + +class TestOfAbstractsWithAbstractMethods extends UnitTestCase { + function testCanProperlyGenerateAbstractMethods() { + $reflection = new SimpleReflection('AnyOldAbstractClassWithAbstractMethods'); + $this->assertEqual( + 'function anAbstract()', + $reflection->getSignature('anAbstract') + ); + $this->assertEqual( + 'function anAbstractWithParameter($foo)', + $reflection->getSignature('anAbstractWithParameter') + ); + $this->assertEqual( + 'function anAbstractWithMultipleParameters($foo, $bar)', + $reflection->getSignature('anAbstractWithMultipleParameters') + ); + } +} + +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/remote_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/remote_test.php new file mode 100644 index 0000000..5f3f96a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/remote_test.php @@ -0,0 +1,19 @@ +add(new RemoteTestCase($test_url . '?xml=yes', $test_url . '?xml=yes&dry=yes')); +if (SimpleReporter::inCli()) { + exit ($test->run(new TextReporter()) ? 0 : 1); +} +$test->run(new HtmlReporter()); diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/shell_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/shell_test.php new file mode 100644 index 0000000..d1d769a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/shell_test.php @@ -0,0 +1,38 @@ +assertIdentical($shell->execute('echo Hello'), 0); + $this->assertPattern('/Hello/', $shell->getOutput()); + } + + function testBadCommand() { + $shell = new SimpleShell(); + $this->assertNotEqual($ret = $shell->execute('blurgh! 2>&1'), 0); + } +} + +class TestOfShellTesterAndShell extends ShellTestCase { + + function testEcho() { + $this->assertTrue($this->execute('echo Hello')); + $this->assertExitCode(0); + $this->assertoutput('Hello'); + } + + function testFileExistence() { + $this->assertFileExists(dirname(__FILE__) . '/all_tests.php'); + $this->assertFileNotExists('wibble'); + } + + function testFilePatterns() { + $this->assertFilePattern('/all[_ ]tests/i', dirname(__FILE__) . '/all_tests.php'); + $this->assertNoFilePattern('/sputnik/i', dirname(__FILE__) . '/all_tests.php'); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/shell_tester_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/shell_tester_test.php new file mode 100644 index 0000000..b12c602 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/shell_tester_test.php @@ -0,0 +1,42 @@ +mock_shell; + } + + function testGenericEquality() { + $this->assertEqual('a', 'a'); + $this->assertNotEqual('a', 'A'); + } + + function testExitCode() { + $this->mock_shell = new MockSimpleShell(); + $this->mock_shell->setReturnValue('execute', 0); + $this->mock_shell->expectOnce('execute', array('ls')); + $this->assertTrue($this->execute('ls')); + $this->assertExitCode(0); + } + + function testOutput() { + $this->mock_shell = new MockSimpleShell(); + $this->mock_shell->setReturnValue('execute', 0); + $this->mock_shell->setReturnValue('getOutput', "Line 1\nLine 2\n"); + $this->assertOutput("Line 1\nLine 2\n"); + } + + function testOutputPatterns() { + $this->mock_shell = new MockSimpleShell(); + $this->mock_shell->setReturnValue('execute', 0); + $this->mock_shell->setReturnValue('getOutput', "Line 1\nLine 2\n"); + $this->assertOutputPattern('/line/i'); + $this->assertNoOutputPattern('/line 2/'); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/simpletest_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/simpletest_test.php new file mode 100644 index 0000000..daa65c6 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/simpletest_test.php @@ -0,0 +1,58 @@ +fail('Should be ignored'); + } +} + +class ShouldNeverBeRunEither extends ShouldNeverBeRun { } + +class TestOfStackTrace extends UnitTestCase { + + function testCanFindAssertInTrace() { + $trace = new SimpleStackTrace(array('assert')); + $this->assertEqual( + $trace->traceMethod(array(array( + 'file' => '/my_test.php', + 'line' => 24, + 'function' => 'assertSomething'))), + ' at [/my_test.php line 24]'); + } +} + +class DummyResource { } + +class TestOfContext extends UnitTestCase { + + function testCurrentContextIsUnique() { + $this->assertSame( + SimpleTest::getContext(), + SimpleTest::getContext()); + } + + function testContextHoldsCurrentTestCase() { + $context = SimpleTest::getContext(); + $this->assertSame($this, $context->getTest()); + } + + function testResourceIsSingleInstanceWithContext() { + $context = new SimpleTestContext(); + $this->assertSame( + $context->get('DummyResource'), + $context->get('DummyResource')); + } + + function testClearingContextResetsResources() { + $context = new SimpleTestContext(); + $resource = $context->get('DummyResource'); + $context->clear(); + $this->assertClone($resource, $context->get('DummyResource')); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/.htaccess b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/.htaccess new file mode 100644 index 0000000..fb3bc55 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/.htaccess @@ -0,0 +1,2 @@ +DirectoryIndex index.php +Options Indexes diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/1.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/1.html new file mode 100644 index 0000000..cdc3e0b --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/1.html @@ -0,0 +1,6 @@ + + 1 + + 2 + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/2.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/2.html new file mode 100644 index 0000000..fea1449 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/2.html @@ -0,0 +1,6 @@ + + 2 + + 3 + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/3.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/3.html new file mode 100644 index 0000000..fdd1380 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/3.html @@ -0,0 +1,6 @@ + + 3 + + 1 + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_change_redirect.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_change_redirect.php new file mode 100644 index 0000000..096c45d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_change_redirect.php @@ -0,0 +1,6 @@ + + Redirection test + This is a test page for the SimpleTest PHP unit tester + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/base_link.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/base_link.html new file mode 100644 index 0000000..fc4266b --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/base_link.html @@ -0,0 +1,9 @@ + + + Links and base tag + + + + Back to test pages + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/form.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/form.html new file mode 100644 index 0000000..dacb1bb --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/form.html @@ -0,0 +1,50 @@ + + + Test of form submission + + + +
    + +
    + +
    + +
    + +
    + +
    + +
    + Radio G + + + + +
    + +
    + +
    + +
    + +
    + +
    + +
    + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/frameset.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/frameset.html new file mode 100644 index 0000000..8ff7b75 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/frameset.html @@ -0,0 +1,9 @@ + + Frameset for testing of SimpleTest + + + + This content is for no frames only. + + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/frameset_with_base_tag.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/frameset_with_base_tag.html new file mode 100644 index 0000000..fb284e1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/frameset_with_base_tag.html @@ -0,0 +1,12 @@ + + + Frameset for testing of SimpleTest + + + + + + This content is for no frames only. + + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/page_1.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/page_1.html new file mode 100644 index 0000000..bb53264 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/page_1.html @@ -0,0 +1,7 @@ + + Page 1 + + This is page 1. + To page 2 + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/page_2.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/page_2.html new file mode 100644 index 0000000..715aaf4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/page_2.html @@ -0,0 +1,7 @@ + + Page 2 + + This is page 2. + To page 1 + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/relative_link.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/relative_link.html new file mode 100644 index 0000000..ac1edbe --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/base_tag/relative_link.html @@ -0,0 +1,8 @@ + + + Links without base tag + + + Back to test pages + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/cookie_based_counter.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/cookie_based_counter.php new file mode 100644 index 0000000..239b9c8 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/cookie_based_counter.php @@ -0,0 +1,10 @@ + + Cookie Counter + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/counting_frameset.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/counting_frameset.html new file mode 100644 index 0000000..eec24c4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/counting_frameset.html @@ -0,0 +1,10 @@ + + Frameset for testing of SimpleTest + + + + + <body>This content is for no frames only.</body> + + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/double_base_change_redirect.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/double_base_change_redirect.php new file mode 100644 index 0000000..a76172f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/double_base_change_redirect.php @@ -0,0 +1,6 @@ + + Redirection test + This is a test page for the SimpleTest PHP unit tester + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/file.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/file.html new file mode 100644 index 0000000..cc41aee --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/file.html @@ -0,0 +1,6 @@ + + Link to SimpleTest + + Link to SimpleTest + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form.html new file mode 100644 index 0000000..cc1138e --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form.html @@ -0,0 +1,47 @@ + + Test of form submission + +
    + +
    + +
    + +
    + +
    + +
    + +
    + Radio G + + + + +
    + +
    + +
    + +
    + +
    + +
    + +
    + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_data_encoded_form.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_data_encoded_form.html new file mode 100644 index 0000000..a0f6b01 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_data_encoded_form.html @@ -0,0 +1,47 @@ + + Test of form submission + +
    + +
    + +
    + +
    + +
    + +
    + +
    + Radio G + + + + +
    + +
    + +
    + +
    + +
    + +
    + +
    + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_array_based_inputs.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_array_based_inputs.php new file mode 100644 index 0000000..e4d6f82 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_array_based_inputs.php @@ -0,0 +1,15 @@ + + + Form with quoted values + + +

    + QUERY_STRING : +

    +
    + + + +
    + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_false_defaults.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_false_defaults.html new file mode 100644 index 0000000..9f3fb38 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_false_defaults.html @@ -0,0 +1,40 @@ + + Test of form submission + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + Radio I + + + + +
    + +
    + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_mixed_post_and_get.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_mixed_post_and_get.html new file mode 100644 index 0000000..cf8e551 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_mixed_post_and_get.html @@ -0,0 +1,15 @@ + + Test of form submission + +
    + +
    + +
    +
    + +
    + +
    + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_quoted_values.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_quoted_values.php new file mode 100644 index 0000000..2d552d7 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_quoted_values.php @@ -0,0 +1,14 @@ + + + Form with quoted values + + +

    + QUERY_STRING : +

    +
    + + +
    + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_radio_buttons.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_radio_buttons.html new file mode 100644 index 0000000..ba99969 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_radio_buttons.html @@ -0,0 +1,10 @@ + + +
    + 1 + 2 + 3 + +
    + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_tricky_defaults.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_tricky_defaults.html new file mode 100644 index 0000000..e028ceb --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_tricky_defaults.html @@ -0,0 +1,35 @@ + + Test of form submission + +
    + +
    + +
    + +
    + +
    + +
    + +
    + Radio I + + + + + +
    + +
    + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_unnamed_submit.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_unnamed_submit.html new file mode 100644 index 0000000..08801f1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_with_unnamed_submit.html @@ -0,0 +1,12 @@ + + Test of form submission + +
    + +
    + + + +
    + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_without_action.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_without_action.php new file mode 100644 index 0000000..e17bcf5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/form_without_action.php @@ -0,0 +1,11 @@ + + Test of form submission + +

    _GET : []

    +

    _POST : []

    +
    + + +
    + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/frame_a.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/frame_a.html new file mode 100644 index 0000000..6eeb802 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/frame_a.html @@ -0,0 +1,6 @@ + + A + + This is frame A
    + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/frame_b.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/frame_b.html new file mode 100644 index 0000000..1957614 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/frame_b.html @@ -0,0 +1,6 @@ + + B + + This is frame B
    + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/frame_links.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/frame_links.html new file mode 100644 index 0000000..864960a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/frame_links.html @@ -0,0 +1,7 @@ + + 1 + + Set one to 2 + Exit the frameset + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/frameset.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/frameset.html new file mode 100644 index 0000000..fb9217a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/frameset.html @@ -0,0 +1,10 @@ + + Frameset for testing of SimpleTest + + + + + <body>This content is for no frames only.</body> + + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/front_controller_style/a_page.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/front_controller_style/a_page.php new file mode 100644 index 0000000..5f28c24 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/front_controller_style/a_page.php @@ -0,0 +1,37 @@ + 0) { + $_COOKIE = $HTTP_COOKIE_VARS; + } + if (count($HTTP_GET_VARS) > 0) { + $_GET = $HTTP_GET_VARS; + } + if (count($HTTP_POST_VARS) > 0) { + $_POST = $HTTP_POST_VARS; + } + if (! isset($_SERVER)) { + $_SERVER = $HTTP_SERVER_VARS; + } + global $HTTP_RAW_POST_DATA; + + require_once('../page_request.php'); +?> + Simple test page with links + + Simple test page with links +

    Links

    + Self + No page + Bare action + Empty query + Empty link + Current directory + Down one +

    Forms

    +
    +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/front_controller_style/index.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/front_controller_style/index.php new file mode 100644 index 0000000..3f2dd08 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/front_controller_style/index.php @@ -0,0 +1,44 @@ + 0) { + $_COOKIE = $HTTP_COOKIE_VARS; + } + if (count($HTTP_GET_VARS) > 0) { + $_GET = $HTTP_GET_VARS; + } + if (count($HTTP_POST_VARS) > 0) { + $_POST = $HTTP_POST_VARS; + } + if (! isset($_SERVER)) { + $_SERVER = $HTTP_SERVER_VARS; + } + global $HTTP_RAW_POST_DATA; + + require_once('../page_request.php'); +?> + Simple test front controller + + Simple test front controller +

    Links

    + Index + No page + Bare action + Empty query + Empty link + Down one + +

    Forms

    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/front_controller_style/show_request.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/front_controller_style/show_request.php new file mode 100644 index 0000000..f38473a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/front_controller_style/show_request.php @@ -0,0 +1,49 @@ +

    Request

    +
    +
    Protocol version
    +
    Request method
    +
    Accept header
    +
    +

    Cookies

    + 0) { + foreach ($_COOKIE as $key => $value) { + print "$key=[$value]
    \n"; + } + } + ?> +

    Raw GET data

    + +

    GET data

    + 0) { + foreach ($get as $key => $value) { + if (is_array($value)) { + $value = implode(', ', $value); + } + print "$key=[$value]
    \n"; + } + } + ?> +

    Raw POST data

    + +
    +

    POST data

    + 0) { + foreach ($_POST as $key => $value) { + print $key . "=["; + if (is_array($value)) { + print implode(', ', $value); + } else { + print $value; + } + print "]
    \n"; + } + } + ?> diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/link_confirm.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/link_confirm.php new file mode 100644 index 0000000..373ba35 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/link_confirm.php @@ -0,0 +1,18 @@ + + SimpleTest testing links + +

    + A target for the + SimpleTest + test suite. +

    + + + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/local_redirect.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/local_redirect.php new file mode 100644 index 0000000..8535711 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/local_redirect.php @@ -0,0 +1,6 @@ + + Redirection test + This is a test page for the SimpleTest PHP unit tester + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/messy_frameset.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/messy_frameset.html new file mode 100644 index 0000000..8364977 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/messy_frameset.html @@ -0,0 +1,16 @@ + + Frameset for testing of SimpleTest + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/multiple_widget_form.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/multiple_widget_form.html new file mode 100644 index 0000000..ec26b11 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/multiple_widget_form.html @@ -0,0 +1,59 @@ + + Test of form submission + +
    + + +
    + Multiple checkboxes B + + + + +
    + PHP compatible + +
    + + +
    + + +
    + +
    + + +
    + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/nested_frameset.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/nested_frameset.html new file mode 100644 index 0000000..0227e5c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/nested_frameset.html @@ -0,0 +1,10 @@ + + Nested frameset for testing of SimpleTest + + + + + <body>This content is for no frames only.</body> + + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/network_confirm.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/network_confirm.php new file mode 100644 index 0000000..034ac3f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/network_confirm.php @@ -0,0 +1,84 @@ + 0) { + $_COOKIE = $HTTP_COOKIE_VARS; + } + if (count($HTTP_GET_VARS) > 0) { + $_GET = $HTTP_GET_VARS; + } + if (count($HTTP_POST_VARS) > 0) { + $_POST = $HTTP_POST_VARS; + } + if (! isset($_SERVER)) { + $_SERVER = $HTTP_SERVER_VARS; + } + global $HTTP_RAW_POST_DATA; + + require_once('page_request.php'); +?> + Simple test target file + + A target for the SimpleTest test suite. +

    Request

    +
    +
    Protocol version
    +
    Request method
    +
    Accept header
    +
    +

    Cookies

    + 0) { + foreach ($_COOKIE as $key => $value) { + print htmlentities($key) . "=[" . htmlentities($value) . "]
    \n"; + } + } + ?> +

    Raw GET data

    + +

    GET data

    + 0) { + foreach ($get as $key => $value) { + if (is_array($value)) { + $value = implode(', ', $value); + } + print htmlentities($key) . "=[" . htmlentities($value) . "]
    \n"; + } + } + ?> +

    Dump of $_GET data

    + '; + print_r($_GET); + print '

  • '; + ?> +

    Raw POST data

    + +
    +

    POST data

    + $value) { + $html .= htmlentities($key) . "=["; + if (is_array($value)) { + $html .= show_array_value($value); + } else { + $html .= htmlentities($value); + } + $html .= "]"; + } + + return $html; + } + + if (count($_POST) > 0) { + echo show_array_value($_POST)."
    \n"; + } + ?> + + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/one_page_frameset.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/one_page_frameset.html new file mode 100644 index 0000000..c3e0ac7 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/one_page_frameset.html @@ -0,0 +1,9 @@ + + Frameset for testing of SimpleTest + + + + <body>This content is for no frames only.</body> + + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/page_request.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/page_request.php new file mode 100644 index 0000000..19ae978 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/page_request.php @@ -0,0 +1,60 @@ +parsed = array(); + foreach ($statements as $statement) { + if (strpos($statement, '=') === false) { + continue; + } + $this->parseStatement($statement); + } + } + + private function parseStatement($statement) { + list($key, $value) = explode('=', $statement); + $key = urldecode($key); + if (preg_match('/(.*)\[\]$/', $key, $matches)) { + $key = $matches[1]; + if (! isset($this->parsed[$key])) { + $this->parsed[$key] = array(); + } + $this->addValue($key, $value); + } elseif (isset($this->parsed[$key])) { + $this->addValue($key, $value); + } else { + $this->setValue($key, $value); + } + } + + private function addValue($key, $value) { + if (! is_array($this->parsed[$key])) { + $this->parsed[$key] = array($this->parsed[$key]); + } + $this->parsed[$key][] = urldecode($value); + } + + private function setValue($key, $value) { + $this->parsed[$key] = urldecode($value); + } + + function getAll() { + return $this->parsed; + } + + function get() { + $request = &new PageRequest($_SERVER['QUERY_STRING']); + return $request->getAll(); + } + + function post() { + global $HTTP_RAW_POST_DATA; + $request = &new PageRequest($HTTP_RAW_POST_DATA); + return $request->getAll(); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/path/base_change_redirect.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/path/base_change_redirect.php new file mode 100644 index 0000000..08636a9 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/path/base_change_redirect.php @@ -0,0 +1,6 @@ + + Redirection test + This is a test page for the SimpleTest PHP unit tester + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/path/network_confirm.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/path/network_confirm.php new file mode 100644 index 0000000..a9fb318 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/path/network_confirm.php @@ -0,0 +1,71 @@ + 0) { + $_COOKIE = $HTTP_COOKIE_VARS; + } + if (count($HTTP_GET_VARS) > 0) { + $_GET = $HTTP_GET_VARS; + } + if (count($HTTP_POST_VARS) > 0) { + $_POST = $HTTP_POST_VARS; + } + if (!isset($_SERVER)) { + $_SERVER = $HTTP_SERVER_VARS; + } + global $HTTP_RAW_POST_DATA; + + require_once('../page_request.php'); +?> + Simple test target file in folder + + A target for the SimpleTest test suite. +

    Request

    +
    +
    Protocol version
    +
    Request method
    +
    Accept header
    +
    +

    Cookies

    + 0) { + foreach ($_COOKIE as $key => $value) { + print $key . "=[" . $value . "]
    \n"; + } + } + ?> +

    Raw GET data

    + +

    GET data

    + 0) { + foreach ($get as $key => $value) { + if (is_array($value)) { + $value = implode(', ', $value); + } + print $key . "=[" . $value . "]
    \n"; + } + } + ?> +

    Raw POST data

    + +
    +

    POST data

    + 0) { + foreach ($_POST as $key => $value) { + print $key . "=["; + if (is_array($value)) { + print implode(', ', $value); + } else { + print $value; + } + print "]
    \n"; + } + } + ?> + + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/path/show_cookies.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/path/show_cookies.php new file mode 100644 index 0000000..1135b04 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/path/show_cookies.php @@ -0,0 +1,18 @@ + 0) { + $_COOKIE = $HTTP_COOKIE_VARS; + } +?> + Simple test target file + + A target for the SimpleTest test suite that displays cookies. +

    Cookies

    + 0) { + foreach ($_COOKIE as $key => $value) { + print $key . "=" . $value . ";"; + } + } + ?> + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/.htaccess b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/.htaccess new file mode 100644 index 0000000..7e89c37 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/.htaccess @@ -0,0 +1,5 @@ +AuthName "Test of basic authentication" +AuthType Basic +AuthUserFile /home/marcus/projects/lastcraft/www/test/protected/.htpasswd +require valid-user + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/.htpasswd b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/.htpasswd new file mode 100644 index 0000000..f40e1f2 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/.htpasswd @@ -0,0 +1 @@ +test:wOGY3sAo.zsek diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/1.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/1.html new file mode 100644 index 0000000..cdc3e0b --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/1.html @@ -0,0 +1,6 @@ + + 1 + + 2 + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/2.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/2.html new file mode 100644 index 0000000..fea1449 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/2.html @@ -0,0 +1,6 @@ + + 2 + + 3 + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/3.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/3.html new file mode 100644 index 0000000..fdd1380 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/3.html @@ -0,0 +1,6 @@ + + 3 + + 1 + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/htaccess b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/htaccess new file mode 100644 index 0000000..866def1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/htaccess @@ -0,0 +1,4 @@ +AuthName "SimpleTest basic authentication" +AuthType Basic +AuthUserFile /web/guide/lastcraft/public_html/test/protected/.htpasswd +require valid-user diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/local_redirect.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/local_redirect.php new file mode 100644 index 0000000..8535711 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/local_redirect.php @@ -0,0 +1,6 @@ + + Redirection test + This is a test page for the SimpleTest PHP unit tester + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/network_confirm.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/network_confirm.php new file mode 100644 index 0000000..e0f8a6e --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/protected/network_confirm.php @@ -0,0 +1,71 @@ + 0) { + $_COOKIE = $HTTP_COOKIE_VARS; + } + if (count($HTTP_GET_VARS) > 0) { + $_GET = $HTTP_GET_VARS; + } + if (count($HTTP_POST_VARS) > 0) { + $_POST = $HTTP_POST_VARS; + } + if (!isset($_SERVER)) { + $_SERVER = $HTTP_SERVER_VARS; + } + global $HTTP_RAW_POST_DATA; + + require_once('../page_request.php'); +?> + Simple test target file + + A target for the SimpleTest test suite. +

    Request

    +
    +
    Protocol version
    +
    Request method
    +
    Accept header
    +
    +

    Cookies

    + 0) { + foreach ($_COOKIE as $key => $value) { + print $key . "=[" . $value . "]
    \n"; + } + } + ?> +

    Raw GET data

    + +

    GET data

    + 0) { + foreach ($get as $key => $value) { + if (is_array($value)) { + $value = implode(', ', $value); + } + print $key . "=[" . $value . "]
    \n"; + } + } + ?> +

    Raw POST data

    + +
    +

    POST data

    + 0) { + foreach ($_POST as $key => $value) { + print $key . "=["; + if (is_array($value)) { + print implode(', ', $value); + } else { + print $value; + } + print "]
    \n"; + } + } + ?> + + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/redirect.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/redirect.php new file mode 100644 index 0000000..14c23fa --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/redirect.php @@ -0,0 +1,6 @@ + + Redirection test + This is a test page for the SimpleTest PHP unit tester + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/savant_style_form.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/savant_style_form.html new file mode 100644 index 0000000..f510555 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/savant_style_form.html @@ -0,0 +1,20 @@ + + Test of form submission with Savant style controls + +
    + Checkbox A + + + +
    + Radio B + + + + + +
    + +
    + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/search.png b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/search.png new file mode 100644 index 0000000..342d1d3 Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/search.png differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/self.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/self.php new file mode 100644 index 0000000..2759f64 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/self.php @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/self_form.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/self_form.php new file mode 100644 index 0000000..7428d2b --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/self_form.php @@ -0,0 +1,21 @@ + 0) { + $_GET = $HTTP_GET_VARS; + } +?> + Test of form self submission + +
    + +
    +

    []

    +

    []

    +

    []

    +
    + + + +
    + + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/set_cookies.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/set_cookies.php new file mode 100644 index 0000000..a4a8a26 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/set_cookies.php @@ -0,0 +1,20 @@ + + SimpleTest testing links + +

    + A target for the + SimpleTest + test suite. + All it does is set some cookies which you can see + here. +

    + + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/slow_page.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/slow_page.php new file mode 100644 index 0000000..c1aef31 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/slow_page.php @@ -0,0 +1,6 @@ + + Slow page + This page takes at least two seconds + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/temp/.stop_cvs_removing_temp b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/temp/.stop_cvs_removing_temp new file mode 100644 index 0000000..e69de29 diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/timestamp.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/timestamp.php new file mode 100644 index 0000000..3117c48 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/timestamp.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/upload_form.html b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/upload_form.html new file mode 100644 index 0000000..23d3a87 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/upload_form.html @@ -0,0 +1,11 @@ + + Test of file upload + +
    + +
    +
    + +
    + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/upload_handler.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/upload_handler.php new file mode 100644 index 0000000..7680b88 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/site/upload_handler.php @@ -0,0 +1,18 @@ + + Test of file upload + +

    +

    + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/socket_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/socket_test.php new file mode 100644 index 0000000..729adda --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/socket_test.php @@ -0,0 +1,25 @@ +assertFalse($error->isError()); + $error->setError('Ouch'); + $this->assertTrue($error->isError()); + $this->assertEqual($error->getError(), 'Ouch'); + } + + function testClearingError() { + $error = new SimpleStickyError(); + $error->setError('Ouch'); + $this->assertTrue($error->isError()); + $error->clearError(); + $this->assertFalse($error->isError()); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/collector/collectable.1 b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/collector/collectable.1 new file mode 100644 index 0000000..e69de29 diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/collector/collectable.2 b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/collector/collectable.2 new file mode 100644 index 0000000..e69de29 diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/empty_test_file.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/empty_test_file.php new file mode 100644 index 0000000..31e3f7b --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/empty_test_file.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/latin1_sample b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/latin1_sample new file mode 100644 index 0000000..1903525 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/latin1_sample @@ -0,0 +1 @@ +£¹²³¼½¾@¶øþðßæ«»¢µ \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/spl_examples.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/spl_examples.php new file mode 100644 index 0000000..45add35 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/spl_examples.php @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/supplementary_upload_sample.txt b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/supplementary_upload_sample.txt new file mode 100644 index 0000000..d8aa9e8 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/supplementary_upload_sample.txt @@ -0,0 +1 @@ +Some more text content \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/test1.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/test1.php new file mode 100644 index 0000000..b414586 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/test1.php @@ -0,0 +1,7 @@ +assertEqual(3,1+2, "pass1"); + } +} +?> diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/upload_sample.txt b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/upload_sample.txt new file mode 100644 index 0000000..ec98d7c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/support/upload_sample.txt @@ -0,0 +1 @@ +Sample for testing file upload \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/tag_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/tag_test.php new file mode 100644 index 0000000..5e8a377 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/tag_test.php @@ -0,0 +1,554 @@ + '1', 'b' => '')); + $this->assertEqual($tag->getTagName(), 'title'); + $this->assertIdentical($tag->getAttribute('a'), '1'); + $this->assertIdentical($tag->getAttribute('b'), ''); + $this->assertIdentical($tag->getAttribute('c'), false); + $this->assertIdentical($tag->getContent(), ''); + } + + function testTitleContent() { + $tag = new SimpleTitleTag(array()); + $this->assertTrue($tag->expectEndTag()); + $tag->addContent('Hello'); + $tag->addContent('World'); + $this->assertEqual($tag->getText(), 'HelloWorld'); + } + + function testMessyTitleContent() { + $tag = new SimpleTitleTag(array()); + $this->assertTrue($tag->expectEndTag()); + $tag->addContent('Hello'); + $tag->addContent('World'); + $this->assertEqual($tag->getText(), 'HelloWorld'); + } + + function testTagWithNoEnd() { + $tag = new SimpleTextTag(array()); + $this->assertFalse($tag->expectEndTag()); + } + + function testAnchorHref() { + $tag = new SimpleAnchorTag(array('href' => 'http://here/')); + $this->assertEqual($tag->getHref(), 'http://here/'); + + $tag = new SimpleAnchorTag(array('href' => '')); + $this->assertIdentical($tag->getAttribute('href'), ''); + $this->assertIdentical($tag->getHref(), ''); + + $tag = new SimpleAnchorTag(array()); + $this->assertIdentical($tag->getAttribute('href'), false); + $this->assertIdentical($tag->getHref(), ''); + } + + function testIsIdMatchesIdAttribute() { + $tag = new SimpleAnchorTag(array('href' => 'http://here/', 'id' => 7)); + $this->assertIdentical($tag->getAttribute('id'), '7'); + $this->assertTrue($tag->isId(7)); + } +} + +class TestOfWidget extends UnitTestCase { + + function testTextEmptyDefault() { + $tag = new SimpleTextTag(array('type' => 'text')); + $this->assertIdentical($tag->getDefault(), ''); + $this->assertIdentical($tag->getValue(), ''); + } + + function testSettingOfExternalLabel() { + $tag = new SimpleTextTag(array('type' => 'text')); + $tag->setLabel('it'); + $this->assertTrue($tag->isLabel('it')); + } + + function testTextDefault() { + $tag = new SimpleTextTag(array('value' => 'aaa')); + $this->assertEqual($tag->getDefault(), 'aaa'); + $this->assertEqual($tag->getValue(), 'aaa'); + } + + function testSettingTextValue() { + $tag = new SimpleTextTag(array('value' => 'aaa')); + $tag->setValue('bbb'); + $this->assertEqual($tag->getValue(), 'bbb'); + $tag->resetValue(); + $this->assertEqual($tag->getValue(), 'aaa'); + } + + function testFailToSetHiddenValue() { + $tag = new SimpleTextTag(array('value' => 'aaa', 'type' => 'hidden')); + $this->assertFalse($tag->setValue('bbb')); + $this->assertEqual($tag->getValue(), 'aaa'); + } + + function testSubmitDefaults() { + $tag = new SimpleSubmitTag(array('type' => 'submit')); + $this->assertIdentical($tag->getName(), false); + $this->assertEqual($tag->getValue(), 'Submit'); + $this->assertFalse($tag->setValue('Cannot set this')); + $this->assertEqual($tag->getValue(), 'Submit'); + $this->assertEqual($tag->getLabel(), 'Submit'); + + $encoding = new MockSimpleMultipartEncoding(); + $encoding->expectNever('add'); + $tag->write($encoding); + } + + function testPopulatedSubmit() { + $tag = new SimpleSubmitTag( + array('type' => 'submit', 'name' => 's', 'value' => 'Ok!')); + $this->assertEqual($tag->getName(), 's'); + $this->assertEqual($tag->getValue(), 'Ok!'); + $this->assertEqual($tag->getLabel(), 'Ok!'); + + $encoding = new MockSimpleMultipartEncoding(); + $encoding->expectOnce('add', array('s', 'Ok!')); + $tag->write($encoding); + } + + function testImageSubmit() { + $tag = new SimpleImageSubmitTag( + array('type' => 'image', 'name' => 's', 'alt' => 'Label')); + $this->assertEqual($tag->getName(), 's'); + $this->assertEqual($tag->getLabel(), 'Label'); + + $encoding = new MockSimpleMultipartEncoding(); + $encoding->expectAt(0, 'add', array('s.x', 20)); + $encoding->expectAt(1, 'add', array('s.y', 30)); + $tag->write($encoding, 20, 30); + } + + function testImageSubmitTitlePreferredOverAltForLabel() { + $tag = new SimpleImageSubmitTag( + array('type' => 'image', 'name' => 's', 'alt' => 'Label', 'title' => 'Title')); + $this->assertEqual($tag->getLabel(), 'Title'); + } + + function testButton() { + $tag = new SimpleButtonTag( + array('type' => 'submit', 'name' => 's', 'value' => 'do')); + $tag->addContent('I am a button'); + $this->assertEqual($tag->getName(), 's'); + $this->assertEqual($tag->getValue(), 'do'); + $this->assertEqual($tag->getLabel(), 'I am a button'); + + $encoding = new MockSimpleMultipartEncoding(); + $encoding->expectOnce('add', array('s', 'do')); + $tag->write($encoding); + } +} + +class TestOfTextArea extends UnitTestCase { + + function testDefault() { + $tag = new SimpleTextAreaTag(array('name' => 'a')); + $tag->addContent('Some text'); + $this->assertEqual($tag->getName(), 'a'); + $this->assertEqual($tag->getDefault(), 'Some text'); + } + + function testWrapping() { + $tag = new SimpleTextAreaTag(array('cols' => '10', 'wrap' => 'physical')); + $tag->addContent("Lot's of text that should be wrapped"); + $this->assertEqual( + $tag->getDefault(), + "Lot's of\r\ntext that\r\nshould be\r\nwrapped"); + $tag->setValue("New long text\r\nwith two lines"); + $this->assertEqual( + $tag->getValue(), + "New long\r\ntext\r\nwith two\r\nlines"); + } + + function testWrappingRemovesLeadingcariageReturn() { + $tag = new SimpleTextAreaTag(array('cols' => '20', 'wrap' => 'physical')); + $tag->addContent("\rStuff"); + $this->assertEqual($tag->getDefault(), 'Stuff'); + $tag->setValue("\nNew stuff\n"); + $this->assertEqual($tag->getValue(), "New stuff\r\n"); + } + + function testBreaksAreNewlineAndCarriageReturn() { + $tag = new SimpleTextAreaTag(array('cols' => '10')); + $tag->addContent("Some\nText\rwith\r\nbreaks"); + $this->assertEqual($tag->getValue(), "Some\r\nText\r\nwith\r\nbreaks"); + } +} + +class TestOfCheckbox extends UnitTestCase { + + function testCanSetCheckboxToNamedValueWithBooleanTrue() { + $tag = new SimpleCheckboxTag(array('name' => 'a', 'value' => 'A')); + $this->assertEqual($tag->getValue(), false); + $tag->setValue(true); + $this->assertIdentical($tag->getValue(), 'A'); + } +} + +class TestOfSelection extends UnitTestCase { + + function testEmpty() { + $tag = new SimpleSelectionTag(array('name' => 'a')); + $this->assertIdentical($tag->getValue(), ''); + } + + function testSingle() { + $tag = new SimpleSelectionTag(array('name' => 'a')); + $option = new SimpleOptionTag(array()); + $option->addContent('AAA'); + $tag->addTag($option); + $this->assertEqual($tag->getValue(), 'AAA'); + } + + function testSingleDefault() { + $tag = new SimpleSelectionTag(array('name' => 'a')); + $option = new SimpleOptionTag(array('selected' => '')); + $option->addContent('AAA'); + $tag->addTag($option); + $this->assertEqual($tag->getValue(), 'AAA'); + } + + function testSingleMappedDefault() { + $tag = new SimpleSelectionTag(array('name' => 'a')); + $option = new SimpleOptionTag(array('selected' => '', 'value' => 'aaa')); + $option->addContent('AAA'); + $tag->addTag($option); + $this->assertEqual($tag->getValue(), 'aaa'); + } + + function testStartsWithDefault() { + $tag = new SimpleSelectionTag(array('name' => 'a')); + $a = new SimpleOptionTag(array()); + $a->addContent('AAA'); + $tag->addTag($a); + $b = new SimpleOptionTag(array('selected' => '')); + $b->addContent('BBB'); + $tag->addTag($b); + $c = new SimpleOptionTag(array()); + $c->addContent('CCC'); + $tag->addTag($c); + $this->assertEqual($tag->getValue(), 'BBB'); + } + + function testSettingOption() { + $tag = new SimpleSelectionTag(array('name' => 'a')); + $a = new SimpleOptionTag(array()); + $a->addContent('AAA'); + $tag->addTag($a); + $b = new SimpleOptionTag(array('selected' => '')); + $b->addContent('BBB'); + $tag->addTag($b); + $c = new SimpleOptionTag(array()); + $c->addContent('CCC'); + $tag->setValue('AAA'); + $this->assertEqual($tag->getValue(), 'AAA'); + } + + function testSettingMappedOption() { + $tag = new SimpleSelectionTag(array('name' => 'a')); + $a = new SimpleOptionTag(array('value' => 'aaa')); + $a->addContent('AAA'); + $tag->addTag($a); + $b = new SimpleOptionTag(array('value' => 'bbb', 'selected' => '')); + $b->addContent('BBB'); + $tag->addTag($b); + $c = new SimpleOptionTag(array('value' => 'ccc')); + $c->addContent('CCC'); + $tag->addTag($c); + $tag->setValue('AAA'); + $this->assertEqual($tag->getValue(), 'aaa'); + $tag->setValue('ccc'); + $this->assertEqual($tag->getValue(), 'ccc'); + } + + function testSelectionDespiteSpuriousWhitespace() { + $tag = new SimpleSelectionTag(array('name' => 'a')); + $a = new SimpleOptionTag(array()); + $a->addContent(' AAA '); + $tag->addTag($a); + $b = new SimpleOptionTag(array('selected' => '')); + $b->addContent(' BBB '); + $tag->addTag($b); + $c = new SimpleOptionTag(array()); + $c->addContent(' CCC '); + $tag->addTag($c); + $this->assertEqual($tag->getValue(), ' BBB '); + $tag->setValue('AAA'); + $this->assertEqual($tag->getValue(), ' AAA '); + } + + function testFailToSetIllegalOption() { + $tag = new SimpleSelectionTag(array('name' => 'a')); + $a = new SimpleOptionTag(array()); + $a->addContent('AAA'); + $tag->addTag($a); + $b = new SimpleOptionTag(array('selected' => '')); + $b->addContent('BBB'); + $tag->addTag($b); + $c = new SimpleOptionTag(array()); + $c->addContent('CCC'); + $tag->addTag($c); + $this->assertFalse($tag->setValue('Not present')); + $this->assertEqual($tag->getValue(), 'BBB'); + } + + function testNastyOptionValuesThatLookLikeFalse() { + $tag = new SimpleSelectionTag(array('name' => 'a')); + $a = new SimpleOptionTag(array('value' => '1')); + $a->addContent('One'); + $tag->addTag($a); + $b = new SimpleOptionTag(array('value' => '0')); + $b->addContent('Zero'); + $tag->addTag($b); + $this->assertIdentical($tag->getValue(), '1'); + $tag->setValue('Zero'); + $this->assertIdentical($tag->getValue(), '0'); + } + + function testBlankOption() { + $tag = new SimpleSelectionTag(array('name' => 'A')); + $a = new SimpleOptionTag(array()); + $tag->addTag($a); + $b = new SimpleOptionTag(array()); + $b->addContent('b'); + $tag->addTag($b); + $this->assertIdentical($tag->getValue(), ''); + $tag->setValue('b'); + $this->assertIdentical($tag->getValue(), 'b'); + $tag->setValue(''); + $this->assertIdentical($tag->getValue(), ''); + } + + function testMultipleDefaultWithNoSelections() { + $tag = new MultipleSelectionTag(array('name' => 'a', 'multiple' => '')); + $a = new SimpleOptionTag(array()); + $a->addContent('AAA'); + $tag->addTag($a); + $b = new SimpleOptionTag(array()); + $b->addContent('BBB'); + $tag->addTag($b); + $this->assertIdentical($tag->getDefault(), array()); + $this->assertIdentical($tag->getValue(), array()); + } + + function testMultipleDefaultWithSelections() { + $tag = new MultipleSelectionTag(array('name' => 'a', 'multiple' => '')); + $a = new SimpleOptionTag(array('selected' => '')); + $a->addContent('AAA'); + $tag->addTag($a); + $b = new SimpleOptionTag(array('selected' => '')); + $b->addContent('BBB'); + $tag->addTag($b); + $this->assertIdentical($tag->getDefault(), array('AAA', 'BBB')); + $this->assertIdentical($tag->getValue(), array('AAA', 'BBB')); + } + + function testSettingMultiple() { + $tag = new MultipleSelectionTag(array('name' => 'a', 'multiple' => '')); + $a = new SimpleOptionTag(array('selected' => '')); + $a->addContent('AAA'); + $tag->addTag($a); + $b = new SimpleOptionTag(array()); + $b->addContent('BBB'); + $tag->addTag($b); + $c = new SimpleOptionTag(array('selected' => '', 'value' => 'ccc')); + $c->addContent('CCC'); + $tag->addTag($c); + $this->assertIdentical($tag->getDefault(), array('AAA', 'ccc')); + $this->assertTrue($tag->setValue(array('BBB', 'ccc'))); + $this->assertIdentical($tag->getValue(), array('BBB', 'ccc')); + $this->assertTrue($tag->setValue(array())); + $this->assertIdentical($tag->getValue(), array()); + } + + function testFailToSetIllegalOptionsInMultiple() { + $tag = new MultipleSelectionTag(array('name' => 'a', 'multiple' => '')); + $a = new SimpleOptionTag(array('selected' => '')); + $a->addContent('AAA'); + $tag->addTag($a); + $b = new SimpleOptionTag(array()); + $b->addContent('BBB'); + $tag->addTag($b); + $this->assertFalse($tag->setValue(array('CCC'))); + $this->assertTrue($tag->setValue(array('AAA', 'BBB'))); + $this->assertFalse($tag->setValue(array('AAA', 'CCC'))); + } +} + +class TestOfRadioGroup extends UnitTestCase { + + function testEmptyGroup() { + $group = new SimpleRadioGroup(); + $this->assertIdentical($group->getDefault(), false); + $this->assertIdentical($group->getValue(), false); + $this->assertFalse($group->setValue('a')); + } + + function testReadingSingleButtonGroup() { + $group = new SimpleRadioGroup(); + $group->addWidget(new SimpleRadioButtonTag( + array('value' => 'A', 'checked' => ''))); + $this->assertIdentical($group->getDefault(), 'A'); + $this->assertIdentical($group->getValue(), 'A'); + } + + function testReadingMultipleButtonGroup() { + $group = new SimpleRadioGroup(); + $group->addWidget(new SimpleRadioButtonTag( + array('value' => 'A'))); + $group->addWidget(new SimpleRadioButtonTag( + array('value' => 'B', 'checked' => ''))); + $this->assertIdentical($group->getDefault(), 'B'); + $this->assertIdentical($group->getValue(), 'B'); + } + + function testFailToSetUnlistedValue() { + $group = new SimpleRadioGroup(); + $group->addWidget(new SimpleRadioButtonTag(array('value' => 'z'))); + $this->assertFalse($group->setValue('a')); + $this->assertIdentical($group->getValue(), false); + } + + function testSettingNewValueClearsTheOldOne() { + $group = new SimpleRadioGroup(); + $group->addWidget(new SimpleRadioButtonTag( + array('value' => 'A'))); + $group->addWidget(new SimpleRadioButtonTag( + array('value' => 'B', 'checked' => ''))); + $this->assertTrue($group->setValue('A')); + $this->assertIdentical($group->getValue(), 'A'); + } + + function testIsIdMatchesAnyWidgetInSet() { + $group = new SimpleRadioGroup(); + $group->addWidget(new SimpleRadioButtonTag( + array('value' => 'A', 'id' => 'i1'))); + $group->addWidget(new SimpleRadioButtonTag( + array('value' => 'B', 'id' => 'i2'))); + $this->assertFalse($group->isId('i0')); + $this->assertTrue($group->isId('i1')); + $this->assertTrue($group->isId('i2')); + } + + function testIsLabelMatchesAnyWidgetInSet() { + $group = new SimpleRadioGroup(); + $button1 = new SimpleRadioButtonTag(array('value' => 'A')); + $button1->setLabel('one'); + $group->addWidget($button1); + $button2 = new SimpleRadioButtonTag(array('value' => 'B')); + $button2->setLabel('two'); + $group->addWidget($button2); + $this->assertFalse($group->isLabel('three')); + $this->assertTrue($group->isLabel('one')); + $this->assertTrue($group->isLabel('two')); + } +} + +class TestOfTagGroup extends UnitTestCase { + + function testReadingMultipleCheckboxGroup() { + $group = new SimpleCheckboxGroup(); + $group->addWidget(new SimpleCheckboxTag(array('value' => 'A'))); + $group->addWidget(new SimpleCheckboxTag( + array('value' => 'B', 'checked' => ''))); + $this->assertIdentical($group->getDefault(), 'B'); + $this->assertIdentical($group->getValue(), 'B'); + } + + function testReadingMultipleUncheckedItems() { + $group = new SimpleCheckboxGroup(); + $group->addWidget(new SimpleCheckboxTag(array('value' => 'A'))); + $group->addWidget(new SimpleCheckboxTag(array('value' => 'B'))); + $this->assertIdentical($group->getDefault(), false); + $this->assertIdentical($group->getValue(), false); + } + + function testReadingMultipleCheckedItems() { + $group = new SimpleCheckboxGroup(); + $group->addWidget(new SimpleCheckboxTag( + array('value' => 'A', 'checked' => ''))); + $group->addWidget(new SimpleCheckboxTag( + array('value' => 'B', 'checked' => ''))); + $this->assertIdentical($group->getDefault(), array('A', 'B')); + $this->assertIdentical($group->getValue(), array('A', 'B')); + } + + function testSettingSingleValue() { + $group = new SimpleCheckboxGroup(); + $group->addWidget(new SimpleCheckboxTag(array('value' => 'A'))); + $group->addWidget(new SimpleCheckboxTag(array('value' => 'B'))); + $this->assertTrue($group->setValue('A')); + $this->assertIdentical($group->getValue(), 'A'); + $this->assertTrue($group->setValue('B')); + $this->assertIdentical($group->getValue(), 'B'); + } + + function testSettingMultipleValues() { + $group = new SimpleCheckboxGroup(); + $group->addWidget(new SimpleCheckboxTag(array('value' => 'A'))); + $group->addWidget(new SimpleCheckboxTag(array('value' => 'B'))); + $this->assertTrue($group->setValue(array('A', 'B'))); + $this->assertIdentical($group->getValue(), array('A', 'B')); + } + + function testSettingNoValue() { + $group = new SimpleCheckboxGroup(); + $group->addWidget(new SimpleCheckboxTag(array('value' => 'A'))); + $group->addWidget(new SimpleCheckboxTag(array('value' => 'B'))); + $this->assertTrue($group->setValue(false)); + $this->assertIdentical($group->getValue(), false); + } + + function testIsIdMatchesAnyIdInSet() { + $group = new SimpleCheckboxGroup(); + $group->addWidget(new SimpleCheckboxTag(array('id' => 1, 'value' => 'A'))); + $group->addWidget(new SimpleCheckboxTag(array('id' => 2, 'value' => 'B'))); + $this->assertFalse($group->isId(0)); + $this->assertTrue($group->isId(1)); + $this->assertTrue($group->isId(2)); + } +} + +class TestOfUploadWidget extends UnitTestCase { + + function testValueIsFilePath() { + $upload = new SimpleUploadTag(array('name' => 'a')); + $upload->setValue(dirname(__FILE__) . '/support/upload_sample.txt'); + $this->assertEqual($upload->getValue(), dirname(__FILE__) . '/support/upload_sample.txt'); + } + + function testSubmitsFileContents() { + $encoding = new MockSimpleMultipartEncoding(); + $encoding->expectOnce('attach', array( + 'a', + 'Sample for testing file upload', + 'upload_sample.txt')); + $upload = new SimpleUploadTag(array('name' => 'a')); + $upload->setValue(dirname(__FILE__) . '/support/upload_sample.txt'); + $upload->write($encoding); + } +} + +class TestOfLabelTag extends UnitTestCase { + + function testLabelShouldHaveAnEndTag() { + $label = new SimpleLabelTag(array()); + $this->assertTrue($label->expectEndTag()); + } + + function testContentIsTextOnly() { + $label = new SimpleLabelTag(array()); + $label->addContent('Here are words'); + $this->assertEqual($label->getText(), 'Here are words'); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/unit_tester_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/unit_tester_test.php new file mode 100644 index 0000000..ce9850f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/unit_tester_test.php @@ -0,0 +1,61 @@ +assertTrue($this->assertTrue(true)); + } + + function testAssertFalseReturnsAssertionAsBoolean() { + $this->assertTrue($this->assertFalse(false)); + } + + function testAssertEqualReturnsAssertionAsBoolean() { + $this->assertTrue($this->assertEqual(5, 5)); + } + + function testAssertIdenticalReturnsAssertionAsBoolean() { + $this->assertTrue($this->assertIdentical(5, 5)); + } + + function testCoreAssertionsDoNotThrowErrors() { + $this->assertIsA($this, 'UnitTestCase'); + $this->assertNotA($this, 'WebTestCase'); + } + + function testReferenceAssertionOnObjects() { + $a = new ReferenceForTesting(); + $b = $a; + $this->assertSame($a, $b); + } + + function testReferenceAssertionOnScalars() { + $a = 25; + $b = &$a; + $this->assertReference($a, $b); + } + + function testCloneOnObjects() { + $a = new ReferenceForTesting(); + $b = new ReferenceForTesting(); + $this->assertClone($a, $b); + } + + function TODO_testCloneOnScalars() { + $a = 25; + $b = 25; + $this->assertClone($a, $b); + } + + function testCopyOnScalars() { + $a = 25; + $b = 25; + $this->assertCopy($a, $b); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/unit_tests.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/unit_tests.php new file mode 100644 index 0000000..43c36a3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/unit_tests.php @@ -0,0 +1,46 @@ +TestSuite('Unit tests'); + $path = dirname(__FILE__); + $this->addFile($path . '/errors_test.php'); + $this->addFile($path . '/exceptions_test.php'); + $this->addFile($path . '/autorun_test.php'); + $this->addFile($path . '/compatibility_test.php'); + $this->addFile($path . '/simpletest_test.php'); + $this->addFile($path . '/dumper_test.php'); + $this->addFile($path . '/expectation_test.php'); + $this->addFile($path . '/unit_tester_test.php'); + $this->addFile($path . '/reflection_php5_test.php'); + $this->addFile($path . '/mock_objects_test.php'); + $this->addFile($path . '/interfaces_test.php'); + $this->addFile($path . '/collector_test.php'); + $this->addFile($path . '/adapter_test.php'); + $this->addFile($path . '/socket_test.php'); + $this->addFile($path . '/encoding_test.php'); + $this->addFile($path . '/url_test.php'); + $this->addFile($path . '/cookies_test.php'); + $this->addFile($path . '/http_test.php'); + $this->addFile($path . '/authentication_test.php'); + $this->addFile($path . '/user_agent_test.php'); + $this->addFile($path . '/parser_test.php'); + $this->addFile($path . '/tag_test.php'); + $this->addFile($path . '/form_test.php'); + $this->addFile($path . '/page_test.php'); + $this->addFile($path . '/frames_test.php'); + $this->addFile($path . '/browser_test.php'); + $this->addFile($path . '/web_tester_test.php'); + $this->addFile($path . '/shell_tester_test.php'); + $this->addFile($path . '/xml_test.php'); + $this->addFile($path . '/../extensions/testdox/test.php'); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/url_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/url_test.php new file mode 100644 index 0000000..666ab1e --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/url_test.php @@ -0,0 +1,496 @@ +assertEqual($url->getScheme(), ''); + $this->assertEqual($url->getHost(), ''); + $this->assertEqual($url->getScheme('http'), 'http'); + $this->assertEqual($url->getHost('localhost'), 'localhost'); + $this->assertEqual($url->getPath(), ''); + } + + function testBasicParsing() { + $url = new SimpleUrl('https://www.lastcraft.com/test/'); + $this->assertEqual($url->getScheme(), 'https'); + $this->assertEqual($url->getHost(), 'www.lastcraft.com'); + $this->assertEqual($url->getPath(), '/test/'); + } + + function testRelativeUrls() { + $url = new SimpleUrl('../somewhere.php'); + $this->assertEqual($url->getScheme(), false); + $this->assertEqual($url->getHost(), false); + $this->assertEqual($url->getPath(), '../somewhere.php'); + } + + function testParseBareParameter() { + $url = new SimpleUrl('?a'); + $this->assertEqual($url->getPath(), ''); + $this->assertEqual($url->getEncodedRequest(), '?a'); + $url->addRequestParameter('x', 'X'); + $this->assertEqual($url->getEncodedRequest(), '?a=&x=X'); + } + + function testParseEmptyParameter() { + $url = new SimpleUrl('?a='); + $this->assertEqual($url->getPath(), ''); + $this->assertEqual($url->getEncodedRequest(), '?a='); + $url->addRequestParameter('x', 'X'); + $this->assertEqual($url->getEncodedRequest(), '?a=&x=X'); + } + + function testParseParameterPair() { + $url = new SimpleUrl('?a=A'); + $this->assertEqual($url->getPath(), ''); + $this->assertEqual($url->getEncodedRequest(), '?a=A'); + $url->addRequestParameter('x', 'X'); + $this->assertEqual($url->getEncodedRequest(), '?a=A&x=X'); + } + + function testParseMultipleParameters() { + $url = new SimpleUrl('?a=A&b=B'); + $this->assertEqual($url->getEncodedRequest(), '?a=A&b=B'); + $url->addRequestParameter('x', 'X'); + $this->assertEqual($url->getEncodedRequest(), '?a=A&b=B&x=X'); + } + + function testParsingParameterMixture() { + $url = new SimpleUrl('?a=A&b=&c'); + $this->assertEqual($url->getEncodedRequest(), '?a=A&b=&c'); + $url->addRequestParameter('x', 'X'); + $this->assertEqual($url->getEncodedRequest(), '?a=A&b=&c=&x=X'); + } + + function testAddParametersFromScratch() { + $url = new SimpleUrl(''); + $url->addRequestParameter('a', 'A'); + $this->assertEqual($url->getEncodedRequest(), '?a=A'); + $url->addRequestParameter('b', 'B'); + $this->assertEqual($url->getEncodedRequest(), '?a=A&b=B'); + $url->addRequestParameter('a', 'aaa'); + $this->assertEqual($url->getEncodedRequest(), '?a=A&b=B&a=aaa'); + } + + function testClearingParameters() { + $url = new SimpleUrl(''); + $url->addRequestParameter('a', 'A'); + $url->clearRequest(); + $this->assertIdentical($url->getEncodedRequest(), ''); + } + + function testEncodingParameters() { + $url = new SimpleUrl(''); + $url->addRequestParameter('a', '?!"\'#~@[]{}:;<>,./|£$%^&*()_+-='); + $this->assertIdentical( + $request = $url->getEncodedRequest(), + '?a=%3F%21%22%27%23%7E%40%5B%5D%7B%7D%3A%3B%3C%3E%2C.%2F%7C%A3%24%25%5E%26%2A%28%29_%2B-%3D'); + } + + function testDecodingParameters() { + $url = new SimpleUrl('?a=%3F%21%22%27%23%7E%40%5B%5D%7B%7D%3A%3B%3C%3E%2C.%2F%7C%A3%24%25%5E%26%2A%28%29_%2B-%3D'); + $this->assertEqual( + $url->getEncodedRequest(), + '?a=' . urlencode('?!"\'#~@[]{}:;<>,./|£$%^&*()_+-=')); + } + + function testUrlInQueryDoesNotConfuseParsing() { + $url = new SimpleUrl('wibble/login.php?url=http://www.google.com/moo/'); + $this->assertFalse($url->getScheme()); + $this->assertFalse($url->getHost()); + $this->assertEqual($url->getPath(), 'wibble/login.php'); + $this->assertEqual($url->getEncodedRequest(), '?url=http://www.google.com/moo/'); + } + + function testSettingCordinates() { + $url = new SimpleUrl(''); + $url->setCoordinates('32', '45'); + $this->assertIdentical($url->getX(), 32); + $this->assertIdentical($url->getY(), 45); + $this->assertEqual($url->getEncodedRequest(), ''); + } + + function testParseCordinates() { + $url = new SimpleUrl('?32,45'); + $this->assertIdentical($url->getX(), 32); + $this->assertIdentical($url->getY(), 45); + } + + function testClearingCordinates() { + $url = new SimpleUrl('?32,45'); + $url->setCoordinates(); + $this->assertIdentical($url->getX(), false); + $this->assertIdentical($url->getY(), false); + } + + function testParsingParameterCordinateMixture() { + $url = new SimpleUrl('?a=A&b=&c?32,45'); + $this->assertIdentical($url->getX(), 32); + $this->assertIdentical($url->getY(), 45); + $this->assertEqual($url->getEncodedRequest(), '?a=A&b=&c'); + } + + function testParsingParameterWithBadCordinates() { + $url = new SimpleUrl('?a=A&b=&c?32'); + $this->assertIdentical($url->getX(), false); + $this->assertIdentical($url->getY(), false); + $this->assertEqual($url->getEncodedRequest(), '?a=A&b=&c?32'); + } + + function testPageSplitting() { + $url = new SimpleUrl('./here/../there/somewhere.php'); + $this->assertEqual($url->getPath(), './here/../there/somewhere.php'); + $this->assertEqual($url->getPage(), 'somewhere.php'); + $this->assertEqual($url->getBasePath(), './here/../there/'); + } + + function testAbsolutePathPageSplitting() { + $url = new SimpleUrl("http://host.com/here/there/somewhere.php"); + $this->assertEqual($url->getPath(), "/here/there/somewhere.php"); + $this->assertEqual($url->getPage(), "somewhere.php"); + $this->assertEqual($url->getBasePath(), "/here/there/"); + } + + function testSplittingUrlWithNoPageGivesEmptyPage() { + $url = new SimpleUrl('/here/there/'); + $this->assertEqual($url->getPath(), '/here/there/'); + $this->assertEqual($url->getPage(), ''); + $this->assertEqual($url->getBasePath(), '/here/there/'); + } + + function testPathNormalisation() { + $url = new SimpleUrl(); + $this->assertEqual( + $url->normalisePath('https://host.com/I/am/here/../there/somewhere.php'), + 'https://host.com/I/am/there/somewhere.php'); + } + + // regression test for #1535407 + function testPathNormalisationWithSinglePeriod() { + $url = new SimpleUrl(); + $this->assertEqual( + $url->normalisePath('https://host.com/I/am/here/./../there/somewhere.php'), + 'https://host.com/I/am/there/somewhere.php'); + } + + // regression test for #1852413 + function testHostnameExtractedFromUContainingAtSign() { + $url = new SimpleUrl("http://localhost/name@example.com"); + $this->assertEqual($url->getScheme(), "http"); + $this->assertEqual($url->getUsername(), ""); + $this->assertEqual($url->getPassword(), ""); + $this->assertEqual($url->getHost(), "localhost"); + $this->assertEqual($url->getPath(), "/name@example.com"); + } + + function testHostnameInLocalhost() { + $url = new SimpleUrl("http://localhost/name/example.com"); + $this->assertEqual($url->getScheme(), "http"); + $this->assertEqual($url->getUsername(), ""); + $this->assertEqual($url->getPassword(), ""); + $this->assertEqual($url->getHost(), "localhost"); + $this->assertEqual($url->getPath(), "/name/example.com"); + } + + function testUsernameAndPasswordAreUrlDecoded() { + $url = new SimpleUrl('http://' . urlencode('test@test') . + ':' . urlencode('$!£@*&%') . '@www.lastcraft.com'); + $this->assertEqual($url->getUsername(), 'test@test'); + $this->assertEqual($url->getPassword(), '$!£@*&%'); + } + + function testBlitz() { + $this->assertUrl( + "https://username:password@www.somewhere.com:243/this/that/here.php?a=1&b=2#anchor", + array("https", "username", "password", "www.somewhere.com", 243, "/this/that/here.php", "com", "?a=1&b=2", "anchor"), + array("a" => "1", "b" => "2")); + $this->assertUrl( + "username:password@www.somewhere.com/this/that/here.php?a=1", + array(false, "username", "password", "www.somewhere.com", false, "/this/that/here.php", "com", "?a=1", false), + array("a" => "1")); + $this->assertUrl( + "username:password@somewhere.com:243?1,2", + array(false, "username", "password", "somewhere.com", 243, "/", "com", "", false), + array(), + array(1, 2)); + $this->assertUrl( + "https://www.somewhere.com", + array("https", false, false, "www.somewhere.com", false, "/", "com", "", false)); + $this->assertUrl( + "username@www.somewhere.com:243#anchor", + array(false, "username", false, "www.somewhere.com", 243, "/", "com", "", "anchor")); + $this->assertUrl( + "/this/that/here.php?a=1&b=2?3,4", + array(false, false, false, false, false, "/this/that/here.php", false, "?a=1&b=2", false), + array("a" => "1", "b" => "2"), + array(3, 4)); + $this->assertUrl( + "username@/here.php?a=1&b=2", + array(false, "username", false, false, false, "/here.php", false, "?a=1&b=2", false), + array("a" => "1", "b" => "2")); + } + + function testAmbiguousHosts() { + $this->assertUrl( + "tigger", + array(false, false, false, false, false, "tigger", false, "", false)); + $this->assertUrl( + "/tigger", + array(false, false, false, false, false, "/tigger", false, "", false)); + $this->assertUrl( + "//tigger", + array(false, false, false, "tigger", false, "/", false, "", false)); + $this->assertUrl( + "//tigger/", + array(false, false, false, "tigger", false, "/", false, "", false)); + $this->assertUrl( + "tigger.com", + array(false, false, false, "tigger.com", false, "/", "com", "", false)); + $this->assertUrl( + "me.net/tigger", + array(false, false, false, "me.net", false, "/tigger", "net", "", false)); + } + + function testAsString() { + $this->assertPreserved('https://www.here.com'); + $this->assertPreserved('http://me:secret@www.here.com'); + $this->assertPreserved('http://here/there'); + $this->assertPreserved('http://here/there?a=A&b=B'); + $this->assertPreserved('http://here/there?a=1&a=2'); + $this->assertPreserved('http://here/there?a=1&a=2?9,8'); + $this->assertPreserved('http://host?a=1&a=2'); + $this->assertPreserved('http://host#stuff'); + $this->assertPreserved('http://me:secret@www.here.com/a/b/c/here.html?a=A?7,6'); + $this->assertPreserved('http://www.here.com/?a=A__b=B'); + $this->assertPreserved('http://www.example.com:8080/'); + } + + function assertUrl($raw, $parts, $params = false, $coords = false) { + if (! is_array($params)) { + $params = array(); + } + $url = new SimpleUrl($raw); + $this->assertIdentical($url->getScheme(), $parts[0], "[$raw] scheme -> %s"); + $this->assertIdentical($url->getUsername(), $parts[1], "[$raw] username -> %s"); + $this->assertIdentical($url->getPassword(), $parts[2], "[$raw] password -> %s"); + $this->assertIdentical($url->getHost(), $parts[3], "[$raw] host -> %s"); + $this->assertIdentical($url->getPort(), $parts[4], "[$raw] port -> %s"); + $this->assertIdentical($url->getPath(), $parts[5], "[$raw] path -> %s"); + $this->assertIdentical($url->getTld(), $parts[6], "[$raw] tld -> %s"); + $this->assertIdentical($url->getEncodedRequest(), $parts[7], "[$raw] encoded -> %s"); + $this->assertIdentical($url->getFragment(), $parts[8], "[$raw] fragment -> %s"); + if ($coords) { + $this->assertIdentical($url->getX(), $coords[0], "[$raw] x -> %s"); + $this->assertIdentical($url->getY(), $coords[1], "[$raw] y -> %s"); + } + } + + function testUrlWithTwoSlashesInPath() { + $url = new SimpleUrl('/article/categoryedit/insert//'); + $this->assertEqual($url->getPath(), '/article/categoryedit/insert//'); + } + + function assertPreserved($string) { + $url = new SimpleUrl($string); + $this->assertEqual($url->asString(), $string); + } +} + +class TestOfAbsoluteUrls extends UnitTestCase { + + function testDirectoriesAfterFilename() { + $string = '../../index.php/foo/bar'; + $url = new SimpleUrl($string); + $this->assertEqual($url->asString(), $string); + + $absolute = $url->makeAbsolute('http://www.domain.com/some/path/'); + $this->assertEqual($absolute->asString(), 'http://www.domain.com/index.php/foo/bar'); + } + + function testMakingAbsolute() { + $url = new SimpleUrl('../there/somewhere.php'); + $this->assertEqual($url->getPath(), '../there/somewhere.php'); + $absolute = $url->makeAbsolute('https://host.com:1234/I/am/here/'); + $this->assertEqual($absolute->getScheme(), 'https'); + $this->assertEqual($absolute->getHost(), 'host.com'); + $this->assertEqual($absolute->getPort(), 1234); + $this->assertEqual($absolute->getPath(), '/I/am/there/somewhere.php'); + } + + function testMakingAnEmptyUrlAbsolute() { + $url = new SimpleUrl(''); + $this->assertEqual($url->getPath(), ''); + $absolute = $url->makeAbsolute('http://host.com/I/am/here/page.html'); + $this->assertEqual($absolute->getScheme(), 'http'); + $this->assertEqual($absolute->getHost(), 'host.com'); + $this->assertEqual($absolute->getPath(), '/I/am/here/page.html'); + } + + function testMakingAnEmptyUrlAbsoluteWithMissingPageName() { + $url = new SimpleUrl(''); + $this->assertEqual($url->getPath(), ''); + $absolute = $url->makeAbsolute('http://host.com/I/am/here/'); + $this->assertEqual($absolute->getScheme(), 'http'); + $this->assertEqual($absolute->getHost(), 'host.com'); + $this->assertEqual($absolute->getPath(), '/I/am/here/'); + } + + function testMakingAShortQueryUrlAbsolute() { + $url = new SimpleUrl('?a#b'); + $this->assertEqual($url->getPath(), ''); + $absolute = $url->makeAbsolute('http://host.com/I/am/here/'); + $this->assertEqual($absolute->getScheme(), 'http'); + $this->assertEqual($absolute->getHost(), 'host.com'); + $this->assertEqual($absolute->getPath(), '/I/am/here/'); + $this->assertEqual($absolute->getEncodedRequest(), '?a'); + $this->assertEqual($absolute->getFragment(), 'b'); + } + + function testMakingADirectoryUrlAbsolute() { + $url = new SimpleUrl('hello/'); + $this->assertEqual($url->getPath(), 'hello/'); + $this->assertEqual($url->getBasePath(), 'hello/'); + $this->assertEqual($url->getPage(), ''); + $absolute = $url->makeAbsolute('http://host.com/I/am/here/page.html'); + $this->assertEqual($absolute->getPath(), '/I/am/here/hello/'); + } + + function testMakingARootUrlAbsolute() { + $url = new SimpleUrl('/'); + $this->assertEqual($url->getPath(), '/'); + $absolute = $url->makeAbsolute('http://host.com/I/am/here/page.html'); + $this->assertEqual($absolute->getPath(), '/'); + } + + function testMakingARootPageUrlAbsolute() { + $url = new SimpleUrl('/here.html'); + $absolute = $url->makeAbsolute('http://host.com/I/am/here/page.html'); + $this->assertEqual($absolute->getPath(), '/here.html'); + } + + function testCarryAuthenticationFromRootPage() { + $url = new SimpleUrl('here.html'); + $absolute = $url->makeAbsolute('http://test:secret@host.com/'); + $this->assertEqual($absolute->getPath(), '/here.html'); + $this->assertEqual($absolute->getUsername(), 'test'); + $this->assertEqual($absolute->getPassword(), 'secret'); + } + + function testMakingCoordinateUrlAbsolute() { + $url = new SimpleUrl('?1,2'); + $this->assertEqual($url->getPath(), ''); + $absolute = $url->makeAbsolute('http://host.com/I/am/here/'); + $this->assertEqual($absolute->getScheme(), 'http'); + $this->assertEqual($absolute->getHost(), 'host.com'); + $this->assertEqual($absolute->getPath(), '/I/am/here/'); + $this->assertEqual($absolute->getX(), 1); + $this->assertEqual($absolute->getY(), 2); + } + + function testMakingAbsoluteAppendedPath() { + $url = new SimpleUrl('./there/somewhere.php'); + $absolute = $url->makeAbsolute('https://host.com/here/'); + $this->assertEqual($absolute->getPath(), '/here/there/somewhere.php'); + } + + function testMakingAbsoluteBadlyFormedAppendedPath() { + $url = new SimpleUrl('there/somewhere.php'); + $absolute = $url->makeAbsolute('https://host.com/here/'); + $this->assertEqual($absolute->getPath(), '/here/there/somewhere.php'); + } + + function testMakingAbsoluteHasNoEffectWhenAlreadyAbsolute() { + $url = new SimpleUrl('https://test:secret@www.lastcraft.com:321/stuff/?a=1#f'); + $absolute = $url->makeAbsolute('http://host.com/here/'); + $this->assertEqual($absolute->getScheme(), 'https'); + $this->assertEqual($absolute->getUsername(), 'test'); + $this->assertEqual($absolute->getPassword(), 'secret'); + $this->assertEqual($absolute->getHost(), 'www.lastcraft.com'); + $this->assertEqual($absolute->getPort(), 321); + $this->assertEqual($absolute->getPath(), '/stuff/'); + $this->assertEqual($absolute->getEncodedRequest(), '?a=1'); + $this->assertEqual($absolute->getFragment(), 'f'); + } + + function testMakingAbsoluteCarriesAuthenticationWhenAlreadyAbsolute() { + $url = new SimpleUrl('https://www.lastcraft.com'); + $absolute = $url->makeAbsolute('http://test:secret@host.com/here/'); + $this->assertEqual($absolute->getHost(), 'www.lastcraft.com'); + $this->assertEqual($absolute->getUsername(), 'test'); + $this->assertEqual($absolute->getPassword(), 'secret'); + } + + function testMakingHostOnlyAbsoluteDoesNotCarryAnyOtherInformation() { + $url = new SimpleUrl('http://www.lastcraft.com'); + $absolute = $url->makeAbsolute('https://host.com:81/here/'); + $this->assertEqual($absolute->getScheme(), 'http'); + $this->assertEqual($absolute->getHost(), 'www.lastcraft.com'); + $this->assertIdentical($absolute->getPort(), false); + $this->assertEqual($absolute->getPath(), '/'); + } +} + +class TestOfFrameUrl extends UnitTestCase { + + function testTargetAttachment() { + $url = new SimpleUrl('http://www.site.com/home.html'); + $this->assertIdentical($url->getTarget(), false); + $url->setTarget('A frame'); + $this->assertIdentical($url->getTarget(), 'A frame'); + } +} + +/** + * @note Based off of http://www.mozilla.org/quality/networking/testing/filetests.html + */ +class TestOfFileUrl extends UnitTestCase { + + function testMinimalUrl() { + $url = new SimpleUrl('file:///'); + $this->assertEqual($url->getScheme(), 'file'); + $this->assertIdentical($url->getHost(), false); + $this->assertEqual($url->getPath(), '/'); + } + + function testUnixUrl() { + $url = new SimpleUrl('file:///fileInRoot'); + $this->assertEqual($url->getScheme(), 'file'); + $this->assertIdentical($url->getHost(), false); + $this->assertEqual($url->getPath(), '/fileInRoot'); + } + + function testDOSVolumeUrl() { + $url = new SimpleUrl('file:///C:/config.sys'); + $this->assertEqual($url->getScheme(), 'file'); + $this->assertIdentical($url->getHost(), false); + $this->assertEqual($url->getPath(), '/C:/config.sys'); + } + + function testDOSVolumePromotion() { + $url = new SimpleUrl('file://C:/config.sys'); + $this->assertEqual($url->getScheme(), 'file'); + $this->assertIdentical($url->getHost(), false); + $this->assertEqual($url->getPath(), '/C:/config.sys'); + } + + function testDOSBackslashes() { + $url = new SimpleUrl('file:///C:\config.sys'); + $this->assertEqual($url->getScheme(), 'file'); + $this->assertIdentical($url->getHost(), false); + $this->assertEqual($url->getPath(), '/C:/config.sys'); + } + + function testDOSDirnameAfterFile() { + $url = new SimpleUrl('file://C:\config.sys'); + $this->assertEqual($url->getScheme(), 'file'); + $this->assertIdentical($url->getHost(), false); + $this->assertEqual($url->getPath(), '/C:/config.sys'); + } + +} + +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/user_agent_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/user_agent_test.php new file mode 100644 index 0000000..030abeb --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/user_agent_test.php @@ -0,0 +1,348 @@ +headers = new MockSimpleHttpHeaders(); + $this->response = new MockSimpleHttpResponse(); + $this->response->setReturnValue('isError', false); + $this->response->returns('getHeaders', new MockSimpleHttpHeaders()); + $this->request = new MockSimpleHttpRequest(); + $this->request->returns('fetch', $this->response); + } + + function testGetRequestWithoutIncidentGivesNoErrors() { + $url = new SimpleUrl('http://test:secret@this.com/page.html'); + $url->addRequestParameters(array('a' => 'A', 'b' => 'B')); + + $agent = new MockRequestUserAgent(); + $agent->returns('createHttpRequest', $this->request); + $agent->__construct(); + + $response = $agent->fetchResponse( + new SimpleUrl('http://test:secret@this.com/page.html'), + new SimpleGetEncoding(array('a' => 'A', 'b' => 'B'))); + $this->assertFalse($response->isError()); + } +} + +class TestOfAdditionalHeaders extends UnitTestCase { + + function testAdditionalHeaderAddedToRequest() { + $response = new MockSimpleHttpResponse(); + $response->setReturnReference('getHeaders', new MockSimpleHttpHeaders()); + + $request = new MockSimpleHttpRequest(); + $request->setReturnReference('fetch', $response); + $request->expectOnce( + 'addHeaderLine', + array('User-Agent: SimpleTest')); + + $agent = new MockRequestUserAgent(); + $agent->setReturnReference('createHttpRequest', $request); + $agent->__construct(); + $agent->addHeader('User-Agent: SimpleTest'); + $response = $agent->fetchResponse(new SimpleUrl('http://this.host/'), new SimpleGetEncoding()); + } +} + +class TestOfBrowserCookies extends UnitTestCase { + + private function createStandardResponse() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue("isError", false); + $response->setReturnValue("getContent", "stuff"); + $response->setReturnReference("getHeaders", new MockSimpleHttpHeaders()); + return $response; + } + + private function createCookieSite($header_lines) { + $headers = new SimpleHttpHeaders($header_lines); + $response = new MockSimpleHttpResponse(); + $response->setReturnValue("isError", false); + $response->setReturnReference("getHeaders", $headers); + $response->setReturnValue("getContent", "stuff"); + $request = new MockSimpleHttpRequest(); + $request->setReturnReference("fetch", $response); + return $request; + } + + private function createMockedRequestUserAgent(&$request) { + $agent = new MockRequestUserAgent(); + $agent->setReturnReference('createHttpRequest', $request); + $agent->__construct(); + return $agent; + } + + function testCookieJarIsSentToRequest() { + $jar = new SimpleCookieJar(); + $jar->setCookie('a', 'A'); + + $request = new MockSimpleHttpRequest(); + $request->returns('fetch', $this->createStandardResponse()); + $request->expectOnce('readCookiesFromJar', array($jar, '*')); + + $agent = $this->createMockedRequestUserAgent($request); + $agent->setCookie('a', 'A'); + $agent->fetchResponse( + new SimpleUrl('http://this.com/this/path/page.html'), + new SimpleGetEncoding()); + } + + function testNoCookieJarIsSentToRequestWhenCookiesAreDisabled() { + $request = new MockSimpleHttpRequest(); + $request->returns('fetch', $this->createStandardResponse()); + $request->expectNever('readCookiesFromJar'); + + $agent = $this->createMockedRequestUserAgent($request); + $agent->setCookie('a', 'A'); + $agent->ignoreCookies(); + $agent->fetchResponse( + new SimpleUrl('http://this.com/this/path/page.html'), + new SimpleGetEncoding()); + } + + function testReadingNewCookie() { + $request = $this->createCookieSite('Set-cookie: a=AAAA'); + $agent = $this->createMockedRequestUserAgent($request); + $agent->fetchResponse( + new SimpleUrl('http://this.com/this/path/page.html'), + new SimpleGetEncoding()); + $this->assertEqual($agent->getCookieValue("this.com", "this/path/", "a"), "AAAA"); + } + + function testIgnoringNewCookieWhenCookiesDisabled() { + $request = $this->createCookieSite('Set-cookie: a=AAAA'); + $agent = $this->createMockedRequestUserAgent($request); + $agent->ignoreCookies(); + $agent->fetchResponse( + new SimpleUrl('http://this.com/this/path/page.html'), + new SimpleGetEncoding()); + $this->assertIdentical($agent->getCookieValue("this.com", "this/path/", "a"), false); + } + + function testOverwriteCookieThatAlreadyExists() { + $request = $this->createCookieSite('Set-cookie: a=AAAA'); + $agent = $this->createMockedRequestUserAgent($request); + $agent->setCookie('a', 'A'); + $agent->fetchResponse( + new SimpleUrl('http://this.com/this/path/page.html'), + new SimpleGetEncoding()); + $this->assertEqual($agent->getCookieValue("this.com", "this/path/", "a"), "AAAA"); + } + + function testClearCookieBySettingExpiry() { + $request = $this->createCookieSite('Set-cookie: a=b'); + $agent = $this->createMockedRequestUserAgent($request); + + $agent->setCookie("a", "A", "this/path/", "Wed, 25-Dec-02 04:24:21 GMT"); + $agent->fetchResponse( + new SimpleUrl('http://this.com/this/path/page.html'), + new SimpleGetEncoding()); + $this->assertIdentical( + $agent->getCookieValue("this.com", "this/path/", "a"), + "b"); + $agent->restart("Wed, 25-Dec-02 04:24:20 GMT"); + $this->assertIdentical( + $agent->getCookieValue("this.com", "this/path/", "a"), + false); + } + + function testAgeingAndClearing() { + $request = $this->createCookieSite('Set-cookie: a=A; expires=Wed, 25-Dec-02 04:24:21 GMT; path=/this/path'); + $agent = $this->createMockedRequestUserAgent($request); + + $agent->fetchResponse( + new SimpleUrl('http://this.com/this/path/page.html'), + new SimpleGetEncoding()); + $agent->restart("Wed, 25-Dec-02 04:24:20 GMT"); + $this->assertIdentical( + $agent->getCookieValue("this.com", "this/path/", "a"), + "A"); + $agent->ageCookies(2); + $agent->restart("Wed, 25-Dec-02 04:24:20 GMT"); + $this->assertIdentical( + $agent->getCookieValue("this.com", "this/path/", "a"), + false); + } + + function testReadingIncomingAndSettingNewCookies() { + $request = $this->createCookieSite('Set-cookie: a=AAA'); + $agent = $this->createMockedRequestUserAgent($request); + + $this->assertNull($agent->getBaseCookieValue("a", false)); + $agent->fetchResponse( + new SimpleUrl('http://this.com/this/path/page.html'), + new SimpleGetEncoding()); + $agent->setCookie("b", "BBB", "this.com", "this/path/"); + $this->assertEqual( + $agent->getBaseCookieValue("a", new SimpleUrl('http://this.com/this/path/page.html')), + "AAA"); + $this->assertEqual( + $agent->getBaseCookieValue("b", new SimpleUrl('http://this.com/this/path/page.html')), + "BBB"); + } +} + +class TestOfHttpRedirects extends UnitTestCase { + + function createRedirect($content, $redirect) { + $headers = new MockSimpleHttpHeaders(); + $headers->setReturnValue('isRedirect', (boolean)$redirect); + $headers->setReturnValue('getLocation', $redirect); + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('getContent', $content); + $response->setReturnReference('getHeaders', $headers); + $request = new MockSimpleHttpRequest(); + $request->setReturnReference('fetch', $response); + return $request; + } + + function testDisabledRedirects() { + $agent = new MockRequestUserAgent(); + $agent->returns( + 'createHttpRequest', + $this->createRedirect('stuff', 'there.html')); + $agent->expectOnce('createHttpRequest'); + $agent->__construct(); + $agent->setMaximumRedirects(0); + $response = $agent->fetchResponse(new SimpleUrl('here.html'), new SimpleGetEncoding()); + $this->assertEqual($response->getContent(), 'stuff'); + } + + function testSingleRedirect() { + $agent = new MockRequestUserAgent(); + $agent->returnsAt( + 0, + 'createHttpRequest', + $this->createRedirect('first', 'two.html')); + $agent->returnsAt( + 1, + 'createHttpRequest', + $this->createRedirect('second', 'three.html')); + $agent->expectCallCount('createHttpRequest', 2); + $agent->__construct(); + + $agent->setMaximumRedirects(1); + $response = $agent->fetchResponse(new SimpleUrl('one.html'), new SimpleGetEncoding()); + $this->assertEqual($response->getContent(), 'second'); + } + + function testDoubleRedirect() { + $agent = new MockRequestUserAgent(); + $agent->returnsAt( + 0, + 'createHttpRequest', + $this->createRedirect('first', 'two.html')); + $agent->returnsAt( + 1, + 'createHttpRequest', + $this->createRedirect('second', 'three.html')); + $agent->returnsAt( + 2, + 'createHttpRequest', + $this->createRedirect('third', 'four.html')); + $agent->expectCallCount('createHttpRequest', 3); + $agent->__construct(); + + $agent->setMaximumRedirects(2); + $response = $agent->fetchResponse(new SimpleUrl('one.html'), new SimpleGetEncoding()); + $this->assertEqual($response->getContent(), 'third'); + } + + function testSuccessAfterRedirect() { + $agent = new MockRequestUserAgent(); + $agent->returnsAt( + 0, + 'createHttpRequest', + $this->createRedirect('first', 'two.html')); + $agent->returnsAt( + 1, + 'createHttpRequest', + $this->createRedirect('second', false)); + $agent->returnsAt( + 2, + 'createHttpRequest', + $this->createRedirect('third', 'four.html')); + $agent->expectCallCount('createHttpRequest', 2); + $agent->__construct(); + + $agent->setMaximumRedirects(2); + $response = $agent->fetchResponse(new SimpleUrl('one.html'), new SimpleGetEncoding()); + $this->assertEqual($response->getContent(), 'second'); + } + + function testRedirectChangesPostToGet() { + $agent = new MockRequestUserAgent(); + $agent->returnsAt( + 0, + 'createHttpRequest', + $this->createRedirect('first', 'two.html')); + $agent->expectAt(0, 'createHttpRequest', array('*', new IsAExpectation('SimplePostEncoding'))); + $agent->returnsAt( + 1, + 'createHttpRequest', + $this->createRedirect('second', 'three.html')); + $agent->expectAt(1, 'createHttpRequest', array('*', new IsAExpectation('SimpleGetEncoding'))); + $agent->expectCallCount('createHttpRequest', 2); + $agent->__construct(); + $agent->setMaximumRedirects(1); + $response = $agent->fetchResponse(new SimpleUrl('one.html'), new SimplePostEncoding()); + } +} + +class TestOfBadHosts extends UnitTestCase { + + private function createSimulatedBadHost() { + $response = new MockSimpleHttpResponse(); + $response->setReturnValue('isError', true); + $response->setReturnValue('getError', 'Bad socket'); + $response->setReturnValue('getContent', false); + $request = new MockSimpleHttpRequest(); + $request->setReturnReference('fetch', $response); + return $request; + } + + function testUntestedHost() { + $request = $this->createSimulatedBadHost(); + $agent = new MockRequestUserAgent(); + $agent->setReturnReference('createHttpRequest', $request); + $agent->__construct(); + $response = $agent->fetchResponse( + new SimpleUrl('http://this.host/this/path/page.html'), + new SimpleGetEncoding()); + $this->assertTrue($response->isError()); + } +} + +class TestOfAuthorisation extends UnitTestCase { + + function testAuthenticateHeaderAdded() { + $response = new MockSimpleHttpResponse(); + $response->setReturnReference('getHeaders', new MockSimpleHttpHeaders()); + + $request = new MockSimpleHttpRequest(); + $request->returns('fetch', $response); + $request->expectOnce( + 'addHeaderLine', + array('Authorization: Basic ' . base64_encode('test:secret'))); + + $agent = new MockRequestUserAgent(); + $agent->returns('createHttpRequest', $request); + $agent->__construct(); + $response = $agent->fetchResponse( + new SimpleUrl('http://test:secret@this.host'), + new SimpleGetEncoding()); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/utf8_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/utf8_test.php new file mode 100644 index 0000000..8dc8f81 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/utf8_test.php @@ -0,0 +1,78 @@ +addPattern("eé"); + $this->assertTrue($regex->match("eéêè", $match)); + $this->assertEqual($match, "eé"); + } + + function testWithTextInLatin1() { + $regex = &new ParallelRegex(false); + $regex->addPattern(utf8_decode("eé")); + $this->assertTrue($regex->match(utf8_decode("eéêè"), $match)); + $this->assertEqual($match, utf8_decode("eé")); + } + + function &createParser() { + $parser = &new MockSimpleHtmlSaxParser(); + $parser->setReturnValue('acceptStartToken', true); + $parser->setReturnValue('acceptEndToken', true); + $parser->setReturnValue('acceptAttributeToken', true); + $parser->setReturnValue('acceptEntityToken', true); + $parser->setReturnValue('acceptTextToken', true); + $parser->setReturnValue('ignore', true); + return $parser; + } + + function testTagWithAttributesInUTF8() { + $parser = &$this->createParser(); + $parser->expectOnce('acceptTextToken', array('label', '*')); + $parser->expectAt(0, 'acceptStartToken', array('expectAt(1, 'acceptStartToken', array('href', '*')); + $parser->expectAt(2, 'acceptStartToken', array('>', '*')); + $parser->expectCallCount('acceptStartToken', 3); + $parser->expectAt(0, 'acceptAttributeToken', array('= "', '*')); + $parser->expectAt(1, 'acceptAttributeToken', array('hère.html', '*')); + $parser->expectAt(2, 'acceptAttributeToken', array('"', '*')); + $parser->expectCallCount('acceptAttributeToken', 3); + $parser->expectOnce('acceptEndToken', array('', '*')); + $lexer = &new SimpleHtmlLexer($parser); + $this->assertTrue($lexer->parse('label')); + } + + function testTagWithAttributesInLatin1() { + $parser = &$this->createParser(); + $parser->expectOnce('acceptTextToken', array('label', '*')); + $parser->expectAt(0, 'acceptStartToken', array('expectAt(1, 'acceptStartToken', array('href', '*')); + $parser->expectAt(2, 'acceptStartToken', array('>', '*')); + $parser->expectCallCount('acceptStartToken', 3); + $parser->expectAt(0, 'acceptAttributeToken', array('= "', '*')); + $parser->expectAt(1, 'acceptAttributeToken', array(utf8_decode('hère.html'), '*')); + $parser->expectAt(2, 'acceptAttributeToken', array('"', '*')); + $parser->expectCallCount('acceptAttributeToken', 3); + $parser->expectOnce('acceptEndToken', array('', '*')); + $lexer = &new SimpleHtmlLexer($parser); + $this->assertTrue($lexer->parse(utf8_decode('label'))); + } +} + +class TestOfUrlithDifferentCharset extends UnitTestCase { + function testUsernameAndPasswordInUTF8() { + $url = new SimpleUrl('http://pÈrick:penËt@www.lastcraft.com'); + $this->assertEqual($url->getUsername(), 'pÈrick'); + $this->assertEqual($url->getPassword(), 'penËt'); + } +} + +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/visual/visual_errors.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/visual/visual_errors.php new file mode 100644 index 0000000..15c0ebf --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/visual/visual_errors.php @@ -0,0 +1,65 @@ +dump('Four exceptions...'); + trigger_error('Default'); + trigger_error('Error', E_USER_ERROR); + trigger_error('Warning', E_USER_WARNING); + trigger_error('Notice', E_USER_NOTICE); + } + + function testErrorTrap() { + $this->dump('Pass...'); + $this->expectError(); + trigger_error('Error'); + } + + function testUnusedErrorExpectationsCauseFailures() { + $this->dump('Two failures...'); + $this->expectError('Some error'); + $this->expectError(); + } + + function testErrorTextIsSentImmediately() { + $this->dump('One failure...'); + $this->expectError('Error'); + trigger_error('Error almost'); + $this->dump('This should lie between the two errors'); + trigger_error('Error after'); + } +} + +class VisualTestOfExceptions extends UnitTestCase { + function skip() { + $this->skipUnless(version_compare(phpversion(), '5') >= 0); + } + + function testExceptionTrap() { + $this->dump('One exception...'); + $this->ouch(); + $this->fail('Should not be here'); + } + + function testExceptionExpectationShowsErrorWhenNoException() { + $this->dump('One failure...'); + $this->expectException('SomeException'); + $this->expectException('LaterException'); + } + + function testExceptionExpectationShowsPassWhenException() { + $this->dump('Pass...'); + $this->expectException(); + $this->ouch(); + } + + function ouch() { + eval('throw new Exception("Ouch!");'); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/visual_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/visual_test.php new file mode 100644 index 0000000..5bde610 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/visual_test.php @@ -0,0 +1,495 @@ +a = $a; + } + } + + class PassingUnitTestCaseOutput extends UnitTestCase { + + function testOfResults() { + $this->pass('Pass'); + } + + function testTrue() { + $this->assertTrue(true); + } + + function testFalse() { + $this->assertFalse(false); + } + + function testExpectation() { + $expectation = &new EqualExpectation(25, 'My expectation message: %s'); + $this->assert($expectation, 25, 'My assert message : %s'); + } + + function testNull() { + $this->assertNull(null, "%s -> Pass"); + $this->assertNotNull(false, "%s -> Pass"); + } + + function testType() { + $this->assertIsA("hello", "string", "%s -> Pass"); + $this->assertIsA($this, "PassingUnitTestCaseOutput", "%s -> Pass"); + $this->assertIsA($this, "UnitTestCase", "%s -> Pass"); + } + + function testTypeEquality() { + $this->assertEqual("0", 0, "%s -> Pass"); + } + + function testNullEquality() { + $this->assertNotEqual(null, 1, "%s -> Pass"); + $this->assertNotEqual(1, null, "%s -> Pass"); + } + + function testIntegerEquality() { + $this->assertNotEqual(1, 2, "%s -> Pass"); + } + + function testStringEquality() { + $this->assertEqual("a", "a", "%s -> Pass"); + $this->assertNotEqual("aa", "ab", "%s -> Pass"); + } + + function testHashEquality() { + $this->assertEqual(array("a" => "A", "b" => "B"), array("b" => "B", "a" => "A"), "%s -> Pass"); + } + + function testWithin() { + $this->assertWithinMargin(5, 5.4, 0.5, "%s -> Pass"); + } + + function testOutside() { + $this->assertOutsideMargin(5, 5.6, 0.5, "%s -> Pass"); + } + + function testStringIdentity() { + $a = "fred"; + $b = $a; + $this->assertIdentical($a, $b, "%s -> Pass"); + } + + function testTypeIdentity() { + $a = "0"; + $b = 0; + $this->assertNotIdentical($a, $b, "%s -> Pass"); + } + + function testNullIdentity() { + $this->assertNotIdentical(null, 1, "%s -> Pass"); + $this->assertNotIdentical(1, null, "%s -> Pass"); + } + + function testHashIdentity() { + } + + function testObjectEquality() { + $this->assertEqual(new TestDisplayClass(4), new TestDisplayClass(4), "%s -> Pass"); + $this->assertNotEqual(new TestDisplayClass(4), new TestDisplayClass(5), "%s -> Pass"); + } + + function testObjectIndentity() { + $this->assertIdentical(new TestDisplayClass(false), new TestDisplayClass(false), "%s -> Pass"); + $this->assertNotIdentical(new TestDisplayClass(false), new TestDisplayClass(0), "%s -> Pass"); + } + + function testReference() { + $a = "fred"; + $b = &$a; + $this->assertReference($a, $b, "%s -> Pass"); + } + + function testCloneOnDifferentObjects() { + $a = "fred"; + $b = $a; + $c = "Hello"; + $this->assertClone($a, $b, "%s -> Pass"); + } + + function testPatterns() { + $this->assertPattern('/hello/i', "Hello there", "%s -> Pass"); + $this->assertNoPattern('/hello/', "Hello there", "%s -> Pass"); + } + + function testLongStrings() { + $text = ""; + for ($i = 0; $i < 10; $i++) { + $text .= "0123456789"; + } + $this->assertEqual($text, $text); + } + } + + class FailingUnitTestCaseOutput extends UnitTestCase { + + function testOfResults() { + $this->fail('Fail'); // Fail. + } + + function testTrue() { + $this->assertTrue(false); // Fail. + } + + function testFalse() { + $this->assertFalse(true); // Fail. + } + + function testExpectation() { + $expectation = &new EqualExpectation(25, 'My expectation message: %s'); + $this->assert($expectation, 24, 'My assert message : %s'); // Fail. + } + + function testNull() { + $this->assertNull(false, "%s -> Fail"); // Fail. + $this->assertNotNull(null, "%s -> Fail"); // Fail. + } + + function testType() { + $this->assertIsA(14, "string", "%s -> Fail"); // Fail. + $this->assertIsA(14, "TestOfUnitTestCaseOutput", "%s -> Fail"); // Fail. + $this->assertIsA($this, "TestReporter", "%s -> Fail"); // Fail. + } + + function testTypeEquality() { + $this->assertNotEqual("0", 0, "%s -> Fail"); // Fail. + } + + function testNullEquality() { + $this->assertEqual(null, 1, "%s -> Fail"); // Fail. + $this->assertEqual(1, null, "%s -> Fail"); // Fail. + } + + function testIntegerEquality() { + $this->assertEqual(1, 2, "%s -> Fail"); // Fail. + } + + function testStringEquality() { + $this->assertNotEqual("a", "a", "%s -> Fail"); // Fail. + $this->assertEqual("aa", "ab", "%s -> Fail"); // Fail. + } + + function testHashEquality() { + $this->assertEqual(array("a" => "A", "b" => "B"), array("b" => "B", "a" => "Z"), "%s -> Fail"); + } + + function testWithin() { + $this->assertWithinMargin(5, 5.6, 0.5, "%s -> Fail"); // Fail. + } + + function testOutside() { + $this->assertOutsideMargin(5, 5.4, 0.5, "%s -> Fail"); // Fail. + } + + function testStringIdentity() { + $a = "fred"; + $b = $a; + $this->assertNotIdentical($a, $b, "%s -> Fail"); // Fail. + } + + function testTypeIdentity() { + $a = "0"; + $b = 0; + $this->assertIdentical($a, $b, "%s -> Fail"); // Fail. + } + + function testNullIdentity() { + $this->assertIdentical(null, 1, "%s -> Fail"); // Fail. + $this->assertIdentical(1, null, "%s -> Fail"); // Fail. + } + + function testHashIdentity() { + $this->assertIdentical(array("a" => "A", "b" => "B"), array("b" => "B", "a" => "A"), "%s -> fail"); // Fail. + } + + function testObjectEquality() { + $this->assertNotEqual(new TestDisplayClass(4), new TestDisplayClass(4), "%s -> Fail"); // Fail. + $this->assertEqual(new TestDisplayClass(4), new TestDisplayClass(5), "%s -> Fail"); // Fail. + } + + function testObjectIndentity() { + $this->assertNotIdentical(new TestDisplayClass(false), new TestDisplayClass(false), "%s -> Fail"); // Fail. + $this->assertIdentical(new TestDisplayClass(false), new TestDisplayClass(0), "%s -> Fail"); // Fail. + } + + function testReference() { + $a = "fred"; + $b = &$a; + $this->assertClone($a, $b, "%s -> Fail"); // Fail. + } + + function testCloneOnDifferentObjects() { + $a = "fred"; + $b = $a; + $c = "Hello"; + $this->assertClone($a, $c, "%s -> Fail"); // Fail. + } + + function testPatterns() { + $this->assertPattern('/hello/', "Hello there", "%s -> Fail"); // Fail. + $this->assertNoPattern('/hello/i', "Hello there", "%s -> Fail"); // Fail. + } + + function testLongStrings() { + $text = ""; + for ($i = 0; $i < 10; $i++) { + $text .= "0123456789"; + } + $this->assertEqual($text . $text, $text . "a" . $text); // Fail. + } +} + + class Dummy { + function Dummy() { + } + + function a() { + } + } + Mock::generate('Dummy'); + + class TestOfMockObjectsOutput extends UnitTestCase { + + function testCallCounts() { + $dummy = &new MockDummy(); + $dummy->expectCallCount('a', 1, 'My message: %s'); + $dummy->a(); + $dummy->a(); + } + + function testMinimumCallCounts() { + $dummy = &new MockDummy(); + $dummy->expectMinimumCallCount('a', 2, 'My message: %s'); + $dummy->a(); + $dummy->a(); + } + + function testEmptyMatching() { + $dummy = &new MockDummy(); + $dummy->expect('a', array()); + $dummy->a(); + $dummy->a(null); // Fail. + } + + function testEmptyMatchingWithCustomMessage() { + $dummy = &new MockDummy(); + $dummy->expect('a', array(), 'My expectation message: %s'); + $dummy->a(); + $dummy->a(null); // Fail. + } + + function testNullMatching() { + $dummy = &new MockDummy(); + $dummy->expect('a', array(null)); + $dummy->a(null); + $dummy->a(); // Fail. + } + + function testBooleanMatching() { + $dummy = &new MockDummy(); + $dummy->expect('a', array(true, false)); + $dummy->a(true, false); + $dummy->a(true, true); // Fail. + } + + function testIntegerMatching() { + $dummy = &new MockDummy(); + $dummy->expect('a', array(32, 33)); + $dummy->a(32, 33); + $dummy->a(32, 34); // Fail. + } + + function testFloatMatching() { + $dummy = &new MockDummy(); + $dummy->expect('a', array(3.2, 3.3)); + $dummy->a(3.2, 3.3); + $dummy->a(3.2, 3.4); // Fail. + } + + function testStringMatching() { + $dummy = &new MockDummy(); + $dummy->expect('a', array('32', '33')); + $dummy->a('32', '33'); + $dummy->a('32', '34'); // Fail. + } + + function testEmptyMatchingWithCustomExpectationMessage() { + $dummy = &new MockDummy(); + $dummy->expect( + 'a', + array(new EqualExpectation('A', 'My part expectation message: %s')), + 'My expectation message: %s'); + $dummy->a('A'); + $dummy->a('B'); // Fail. + } + + function testArrayMatching() { + $dummy = &new MockDummy(); + $dummy->expect('a', array(array(32), array(33))); + $dummy->a(array(32), array(33)); + $dummy->a(array(32), array('33')); // Fail. + } + + function testObjectMatching() { + $a = new Dummy(); + $a->a = 'a'; + $b = new Dummy(); + $b->b = 'b'; + $dummy = &new MockDummy(); + $dummy->expect('a', array($a, $b)); + $dummy->a($a, $b); + $dummy->a($a, $a); // Fail. + } + + function testBigList() { + $dummy = &new MockDummy(); + $dummy->expect('a', array(false, 0, 1, 1.0)); + $dummy->a(false, 0, 1, 1.0); + $dummy->a(true, false, 2, 2.0); // Fail. + } + } + + class TestOfPastBugs extends UnitTestCase { + + function testMixedTypes() { + $this->assertEqual(array(), null, "%s -> Pass"); + $this->assertIdentical(array(), null, "%s -> Fail"); // Fail. + } + + function testMockWildcards() { + $dummy = &new MockDummy(); + $dummy->expect('a', array('*', array(33))); + $dummy->a(array(32), array(33)); + $dummy->a(array(32), array('33')); // Fail. + } + } + + class TestOfVisualShell extends ShellTestCase { + + function testDump() { + $this->execute('ls'); + $this->dumpOutput(); + $this->execute('dir'); + $this->dumpOutput(); + } + + function testDumpOfList() { + $this->execute('ls'); + $this->dump($this->getOutputAsList()); + } + } + + class PassesAsWellReporter extends HtmlReporter { + + protected function getCss() { + return parent::getCss() . ' .pass { color: darkgreen; }'; + } + + function paintPass($message) { + parent::paintPass($message); + print "Pass: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode(" -> ", $breadcrumb); + print " -> " . htmlentities($message) . "
    \n"; + } + + function paintSignal($type, &$payload) { + print "$type: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode(" -> ", $breadcrumb); + print " -> " . htmlentities(serialize($payload)) . "
    \n"; + } + } + + class TestOfSkippingNoMatterWhat extends UnitTestCase { + function skip() { + $this->skipIf(true, 'Always skipped -> %s'); + } + + function testFail() { + $this->fail('This really shouldn\'t have happened'); + } + } + + class TestOfSkippingOrElse extends UnitTestCase { + function skip() { + $this->skipUnless(false, 'Always skipped -> %s'); + } + + function testFail() { + $this->fail('This really shouldn\'t have happened'); + } + } + + class TestOfSkippingTwiceOver extends UnitTestCase { + function skip() { + $this->skipIf(true, 'First reason -> %s'); + $this->skipIf(true, 'Second reason -> %s'); + } + + function testFail() { + $this->fail('This really shouldn\'t have happened'); + } + } + + class TestThatShouldNotBeSkipped extends UnitTestCase { + function skip() { + $this->skipIf(false); + $this->skipUnless(true); + } + + function testFail() { + $this->fail('We should see this message'); + } + + function testPass() { + $this->pass('We should see this message'); + } + } + + $test = &new TestSuite('Visual test with 46 passes, 47 fails and 0 exceptions'); + $test->add(new PassingUnitTestCaseOutput()); + $test->add(new FailingUnitTestCaseOutput()); + $test->add(new TestOfMockObjectsOutput()); + $test->add(new TestOfPastBugs()); + $test->add(new TestOfVisualShell()); + $test->add(new TestOfSkippingNoMatterWhat()); + $test->add(new TestOfSkippingOrElse()); + $test->add(new TestOfSkippingTwiceOver()); + $test->add(new TestThatShouldNotBeSkipped()); + + if (isset($_GET['xml']) || in_array('xml', (isset($argv) ? $argv : array()))) { + $reporter = new XmlReporter(); + } elseif (TextReporter::inCli()) { + $reporter = new TextReporter(); + } else { + $reporter = new PassesAsWellReporter(); + } + if (isset($_GET['dry']) || in_array('dry', (isset($argv) ? $argv : array()))) { + $reporter->makeDry(); + } + exit ($test->run($reporter) ? 0 : 1); +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/web_tester_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/web_tester_test.php new file mode 100644 index 0000000..8c3bf1a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/web_tester_test.php @@ -0,0 +1,155 @@ +assertTrue($expectation->test('a')); + $this->assertTrue($expectation->test(array('a'))); + $this->assertFalse($expectation->test('A')); + } + + function testMatchesInteger() { + $expectation = new FieldExpectation('1'); + $this->assertTrue($expectation->test('1')); + $this->assertTrue($expectation->test(1)); + $this->assertTrue($expectation->test(array('1'))); + $this->assertTrue($expectation->test(array(1))); + } + + function testNonStringFailsExpectation() { + $expectation = new FieldExpectation('a'); + $this->assertFalse($expectation->test(null)); + } + + function testUnsetFieldCanBeTestedFor() { + $expectation = new FieldExpectation(false); + $this->assertTrue($expectation->test(false)); + } + + function testMultipleValuesCanBeInAnyOrder() { + $expectation = new FieldExpectation(array('a', 'b')); + $this->assertTrue($expectation->test(array('a', 'b'))); + $this->assertTrue($expectation->test(array('b', 'a'))); + $this->assertFalse($expectation->test(array('a', 'a'))); + $this->assertFalse($expectation->test('a')); + } + + function testSingleItemCanBeArrayOrString() { + $expectation = new FieldExpectation(array('a')); + $this->assertTrue($expectation->test(array('a'))); + $this->assertTrue($expectation->test('a')); + } +} + +class TestOfHeaderExpectations extends UnitTestCase { + + function testExpectingOnlyTheHeaderName() { + $expectation = new HttpHeaderExpectation('a'); + $this->assertIdentical($expectation->test(false), false); + $this->assertIdentical($expectation->test('a: A'), true); + $this->assertIdentical($expectation->test('A: A'), true); + $this->assertIdentical($expectation->test('a: B'), true); + $this->assertIdentical($expectation->test(' a : A '), true); + } + + function testHeaderValueAsWell() { + $expectation = new HttpHeaderExpectation('a', 'A'); + $this->assertIdentical($expectation->test(false), false); + $this->assertIdentical($expectation->test('a: A'), true); + $this->assertIdentical($expectation->test('A: A'), true); + $this->assertIdentical($expectation->test('A: a'), false); + $this->assertIdentical($expectation->test('a: B'), false); + $this->assertIdentical($expectation->test(' a : A '), true); + $this->assertIdentical($expectation->test(' a : AB '), false); + } + + function testHeaderValueWithColons() { + $expectation = new HttpHeaderExpectation('a', 'A:B:C'); + $this->assertIdentical($expectation->test('a: A'), false); + $this->assertIdentical($expectation->test('a: A:B'), false); + $this->assertIdentical($expectation->test('a: A:B:C'), true); + $this->assertIdentical($expectation->test('a: A:B:C:D'), false); + } + + function testMultilineSearch() { + $expectation = new HttpHeaderExpectation('a', 'A'); + $this->assertIdentical($expectation->test("aa: A\r\nb: B\r\nc: C"), false); + $this->assertIdentical($expectation->test("aa: A\r\na: A\r\nb: B"), true); + } + + function testMultilineSearchWithPadding() { + $expectation = new HttpHeaderExpectation('a', ' A '); + $this->assertIdentical($expectation->test("aa:A\r\nb:B\r\nc:C"), false); + $this->assertIdentical($expectation->test("aa:A\r\na:A\r\nb:B"), true); + } + + function testPatternMatching() { + $expectation = new HttpHeaderExpectation('a', new PatternExpectation('/A/')); + $this->assertIdentical($expectation->test('a: A'), true); + $this->assertIdentical($expectation->test('A: A'), true); + $this->assertIdentical($expectation->test('A: a'), false); + $this->assertIdentical($expectation->test('a: B'), false); + $this->assertIdentical($expectation->test(' a : A '), true); + $this->assertIdentical($expectation->test(' a : AB '), true); + } + + function testCaseInsensitivePatternMatching() { + $expectation = new HttpHeaderExpectation('a', new PatternExpectation('/A/i')); + $this->assertIdentical($expectation->test('a: a'), true); + $this->assertIdentical($expectation->test('a: B'), false); + $this->assertIdentical($expectation->test(' a : A '), true); + $this->assertIdentical($expectation->test(' a : BAB '), true); + $this->assertIdentical($expectation->test(' a : bab '), true); + } + + function testUnwantedHeader() { + $expectation = new NoHttpHeaderExpectation('a'); + $this->assertIdentical($expectation->test(''), true); + $this->assertIdentical($expectation->test('stuff'), true); + $this->assertIdentical($expectation->test('b: B'), true); + $this->assertIdentical($expectation->test('a: A'), false); + $this->assertIdentical($expectation->test('A: A'), false); + } + + function testMultilineUnwantedSearch() { + $expectation = new NoHttpHeaderExpectation('a'); + $this->assertIdentical($expectation->test("aa:A\r\nb:B\r\nc:C"), true); + $this->assertIdentical($expectation->test("aa:A\r\na:A\r\nb:B"), false); + } + + function testLocationHeaderSplitsCorrectly() { + $expectation = new HttpHeaderExpectation('Location', 'http://here/'); + $this->assertIdentical($expectation->test('Location: http://here/'), true); + } +} + +class TestOfTextExpectations extends UnitTestCase { + + function testMatchingSubString() { + $expectation = new TextExpectation('wanted'); + $this->assertIdentical($expectation->test(''), false); + $this->assertIdentical($expectation->test('Wanted'), false); + $this->assertIdentical($expectation->test('wanted'), true); + $this->assertIdentical($expectation->test('the wanted text is here'), true); + } + + function testNotMatchingSubString() { + $expectation = new NoTextExpectation('wanted'); + $this->assertIdentical($expectation->test(''), true); + $this->assertIdentical($expectation->test('Wanted'), true); + $this->assertIdentical($expectation->test('wanted'), false); + $this->assertIdentical($expectation->test('the wanted text is here'), false); + } +} + +class TestOfGenericAssertionsInWebTester extends WebTestCase { + function testEquality() { + $this->assertEqual('a', 'a'); + $this->assertNotEqual('a', 'A'); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/xml_test.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/xml_test.php new file mode 100644 index 0000000..f99e0dc --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test/xml_test.php @@ -0,0 +1,187 @@ + 2)); + $this->assertEqual($nesting->getSize(), 2); + } +} + +class TestOfXmlStructureParsing extends UnitTestCase { + function testValidXml() { + $listener = new MockSimpleScorer(); + $listener->expectNever('paintGroupStart'); + $listener->expectNever('paintGroupEnd'); + $listener->expectNever('paintCaseStart'); + $listener->expectNever('paintCaseEnd'); + $parser = new SimpleTestXmlParser($listener); + $this->assertTrue($parser->parse("\n")); + $this->assertTrue($parser->parse("\n")); + $this->assertTrue($parser->parse("\n")); + } + + function testEmptyGroup() { + $listener = new MockSimpleScorer(); + $listener->expectOnce('paintGroupStart', array('a_group', 7)); + $listener->expectOnce('paintGroupEnd', array('a_group')); + $parser = new SimpleTestXmlParser($listener); + $parser->parse("\n"); + $parser->parse("\n"); + $this->assertTrue($parser->parse("\n")); + $this->assertTrue($parser->parse("a_group\n")); + $this->assertTrue($parser->parse("\n")); + $parser->parse("\n"); + } + + function testEmptyCase() { + $listener = new MockSimpleScorer(); + $listener->expectOnce('paintCaseStart', array('a_case')); + $listener->expectOnce('paintCaseEnd', array('a_case')); + $parser = new SimpleTestXmlParser($listener); + $parser->parse("\n"); + $parser->parse("\n"); + $this->assertTrue($parser->parse("\n")); + $this->assertTrue($parser->parse("a_case\n")); + $this->assertTrue($parser->parse("\n")); + $parser->parse("\n"); + } + + function testEmptyMethod() { + $listener = new MockSimpleScorer(); + $listener->expectOnce('paintCaseStart', array('a_case')); + $listener->expectOnce('paintCaseEnd', array('a_case')); + $listener->expectOnce('paintMethodStart', array('a_method')); + $listener->expectOnce('paintMethodEnd', array('a_method')); + $parser = new SimpleTestXmlParser($listener); + $parser->parse("\n"); + $parser->parse("\n"); + $parser->parse("\n"); + $parser->parse("a_case\n"); + $this->assertTrue($parser->parse("\n")); + $this->assertTrue($parser->parse("a_method\n")); + $this->assertTrue($parser->parse("\n")); + $parser->parse("\n"); + $parser->parse("\n"); + } + + function testNestedGroup() { + $listener = new MockSimpleScorer(); + $listener->expectAt(0, 'paintGroupStart', array('a_group', 7)); + $listener->expectAt(1, 'paintGroupStart', array('b_group', 3)); + $listener->expectCallCount('paintGroupStart', 2); + $listener->expectAt(0, 'paintGroupEnd', array('b_group')); + $listener->expectAt(1, 'paintGroupEnd', array('a_group')); + $listener->expectCallCount('paintGroupEnd', 2); + + $parser = new SimpleTestXmlParser($listener); + $parser->parse("\n"); + $parser->parse("\n"); + + $this->assertTrue($parser->parse("\n")); + $this->assertTrue($parser->parse("a_group\n")); + $this->assertTrue($parser->parse("\n")); + $this->assertTrue($parser->parse("b_group\n")); + $this->assertTrue($parser->parse("\n")); + $this->assertTrue($parser->parse("\n")); + $parser->parse("\n"); + } +} + +class AnyOldSignal { + public $stuff = true; +} + +class TestOfXmlResultsParsing extends UnitTestCase { + + function sendValidStart(&$parser) { + $parser->parse("\n"); + $parser->parse("\n"); + $parser->parse("\n"); + $parser->parse("a_case\n"); + $parser->parse("\n"); + $parser->parse("a_method\n"); + } + + function sendValidEnd(&$parser) { + $parser->parse("\n"); + $parser->parse("\n"); + $parser->parse("\n"); + } + + function testPass() { + $listener = new MockSimpleScorer(); + $listener->expectOnce('paintPass', array('a_message')); + $parser = new SimpleTestXmlParser($listener); + $this->sendValidStart($parser); + $this->assertTrue($parser->parse("a_message\n")); + $this->sendValidEnd($parser); + } + + function testFail() { + $listener = new MockSimpleScorer(); + $listener->expectOnce('paintFail', array('a_message')); + $parser = new SimpleTestXmlParser($listener); + $this->sendValidStart($parser); + $this->assertTrue($parser->parse("a_message\n")); + $this->sendValidEnd($parser); + } + + function testException() { + $listener = new MockSimpleScorer(); + $listener->expectOnce('paintError', array('a_message')); + $parser = new SimpleTestXmlParser($listener); + $this->sendValidStart($parser); + $this->assertTrue($parser->parse("a_message\n")); + $this->sendValidEnd($parser); + } + + function testSkip() { + $listener = new MockSimpleScorer(); + $listener->expectOnce('paintSkip', array('a_message')); + $parser = new SimpleTestXmlParser($listener); + $this->sendValidStart($parser); + $this->assertTrue($parser->parse("a_message\n")); + $this->sendValidEnd($parser); + } + + function testSignal() { + $signal = new AnyOldSignal(); + $signal->stuff = "Hello"; + $listener = new MockSimpleScorer(); + $listener->expectOnce('paintSignal', array('a_signal', $signal)); + $parser = new SimpleTestXmlParser($listener); + $this->sendValidStart($parser); + $this->assertTrue($parser->parse( + "\n")); + $this->sendValidEnd($parser); + } + + function testMessage() { + $listener = new MockSimpleScorer(); + $listener->expectOnce('paintMessage', array('a_message')); + $parser = new SimpleTestXmlParser($listener); + $this->sendValidStart($parser); + $this->assertTrue($parser->parse("a_message\n")); + $this->sendValidEnd($parser); + } + + function testFormattedMessage() { + $listener = new MockSimpleScorer(); + $listener->expectOnce('paintFormattedMessage', array("\na\tmessage\n")); + $parser = new SimpleTestXmlParser($listener); + $this->sendValidStart($parser); + $this->assertTrue($parser->parse("\n")); + $this->sendValidEnd($parser); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test_case.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test_case.php new file mode 100644 index 0000000..1666548 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/test_case.php @@ -0,0 +1,655 @@ +label = $label; + } + } + + /** + * Accessor for the test name for subclasses. + * @return string Name of the test. + * @access public + */ + function getLabel() { + return $this->label ? $this->label : get_class($this); + } + + /** + * This is a placeholder for skipping tests. In this + * method you place skipIf() and skipUnless() calls to + * set the skipping state. + * @access public + */ + function skip() { + } + + /** + * Will issue a message to the reporter and tell the test + * case to skip if the incoming flag is true. + * @param string $should_skip Condition causing the tests to be skipped. + * @param string $message Text of skip condition. + * @access public + */ + function skipIf($should_skip, $message = '%s') { + if ($should_skip && ! $this->should_skip) { + $this->should_skip = true; + $message = sprintf($message, 'Skipping [' . get_class($this) . ']'); + $this->reporter->paintSkip($message . $this->getAssertionLine()); + } + } + + /** + * Accessor for the private variable $_shoud_skip + * @access public + */ + function shouldSkip() { + return $this->should_skip; + } + + /** + * Will issue a message to the reporter and tell the test + * case to skip if the incoming flag is false. + * @param string $shouldnt_skip Condition causing the tests to be run. + * @param string $message Text of skip condition. + * @access public + */ + function skipUnless($shouldnt_skip, $message = false) { + $this->skipIf(! $shouldnt_skip, $message); + } + + /** + * Used to invoke the single tests. + * @return SimpleInvoker Individual test runner. + * @access public + */ + function createInvoker() { + return new SimpleExceptionTrappingInvoker( + new SimpleErrorTrappingInvoker(new SimpleInvoker($this))); + } + + /** + * Uses reflection to run every method within itself + * starting with the string "test" unless a method + * is specified. + * @param SimpleReporter $reporter Current test reporter. + * @return boolean True if all tests passed. + * @access public + */ + function run($reporter) { + $context = SimpleTest::getContext(); + $context->setTest($this); + $context->setReporter($reporter); + $this->reporter = $reporter; + $started = false; + foreach ($this->getTests() as $method) { + if ($reporter->shouldInvoke($this->getLabel(), $method)) { + $this->skip(); + if ($this->should_skip) { + break; + } + if (! $started) { + $reporter->paintCaseStart($this->getLabel()); + $started = true; + } + $invoker = $this->reporter->createInvoker($this->createInvoker()); + $invoker->before($method); + $invoker->invoke($method); + $invoker->after($method); + } + } + if ($started) { + $reporter->paintCaseEnd($this->getLabel()); + } + unset($this->reporter); + return $reporter->getStatus(); + } + + /** + * Gets a list of test names. Normally that will + * be all internal methods that start with the + * name "test". This method should be overridden + * if you want a different rule. + * @return array List of test names. + * @access public + */ + function getTests() { + $methods = array(); + foreach (get_class_methods(get_class($this)) as $method) { + if ($this->isTest($method)) { + $methods[] = $method; + } + } + return $methods; + } + + /** + * Tests to see if the method is a test that should + * be run. Currently any method that starts with 'test' + * is a candidate unless it is the constructor. + * @param string $method Method name to try. + * @return boolean True if test method. + * @access protected + */ + protected function isTest($method) { + if (strtolower(substr($method, 0, 4)) == 'test') { + return ! SimpleTestCompatibility::isA($this, strtolower($method)); + } + return false; + } + + /** + * Announces the start of the test. + * @param string $method Test method just started. + * @access public + */ + function before($method) { + $this->reporter->paintMethodStart($method); + $this->observers = array(); + } + + /** + * Sets up unit test wide variables at the start + * of each test method. To be overridden in + * actual user test cases. + * @access public + */ + function setUp() { + } + + /** + * Clears the data set in the setUp() method call. + * To be overridden by the user in actual user test cases. + * @access public + */ + function tearDown() { + } + + /** + * Announces the end of the test. Includes private clean up. + * @param string $method Test method just finished. + * @access public + */ + function after($method) { + for ($i = 0; $i < count($this->observers); $i++) { + $this->observers[$i]->atTestEnd($method, $this); + } + $this->reporter->paintMethodEnd($method); + } + + /** + * Sets up an observer for the test end. + * @param object $observer Must have atTestEnd() + * method. + * @access public + */ + function tell($observer) { + $this->observers[] = &$observer; + } + + /** + * @deprecated + */ + function pass($message = "Pass") { + if (! isset($this->reporter)) { + trigger_error('Can only make assertions within test methods'); + } + $this->reporter->paintPass( + $message . $this->getAssertionLine()); + return true; + } + + /** + * Sends a fail event with a message. + * @param string $message Message to send. + * @access public + */ + function fail($message = "Fail") { + if (! isset($this->reporter)) { + trigger_error('Can only make assertions within test methods'); + } + $this->reporter->paintFail( + $message . $this->getAssertionLine()); + return false; + } + + /** + * Formats a PHP error and dispatches it to the + * reporter. + * @param integer $severity PHP error code. + * @param string $message Text of error. + * @param string $file File error occoured in. + * @param integer $line Line number of error. + * @access public + */ + function error($severity, $message, $file, $line) { + if (! isset($this->reporter)) { + trigger_error('Can only make assertions within test methods'); + } + $this->reporter->paintError( + "Unexpected PHP error [$message] severity [$severity] in [$file line $line]"); + } + + /** + * Formats an exception and dispatches it to the + * reporter. + * @param Exception $exception Object thrown. + * @access public + */ + function exception($exception) { + $this->reporter->paintException($exception); + } + + /** + * For user defined expansion of the available messages. + * @param string $type Tag for sorting the signals. + * @param mixed $payload Extra user specific information. + */ + function signal($type, $payload) { + if (! isset($this->reporter)) { + trigger_error('Can only make assertions within test methods'); + } + $this->reporter->paintSignal($type, $payload); + } + + /** + * Runs an expectation directly, for extending the + * tests with new expectation classes. + * @param SimpleExpectation $expectation Expectation subclass. + * @param mixed $compare Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assert($expectation, $compare, $message = '%s') { + if ($expectation->test($compare)) { + return $this->pass(sprintf( + $message, + $expectation->overlayMessage($compare, $this->reporter->getDumper()))); + } else { + return $this->fail(sprintf( + $message, + $expectation->overlayMessage($compare, $this->reporter->getDumper()))); + } + } + + /** + * Uses a stack trace to find the line of an assertion. + * @return string Line number of first assert* + * method embedded in format string. + * @access public + */ + function getAssertionLine() { + $trace = new SimpleStackTrace(array('assert', 'expect', 'pass', 'fail', 'skip')); + return $trace->traceMethod(); + } + + /** + * Sends a formatted dump of a variable to the + * test suite for those emergency debugging + * situations. + * @param mixed $variable Variable to display. + * @param string $message Message to display. + * @return mixed The original variable. + * @access public + */ + function dump($variable, $message = false) { + $dumper = $this->reporter->getDumper(); + $formatted = $dumper->dump($variable); + if ($message) { + $formatted = $message . "\n" . $formatted; + } + $this->reporter->paintFormattedMessage($formatted); + return $variable; + } + + /** + * Accessor for the number of subtests including myelf. + * @return integer Number of test cases. + * @access public + */ + function getSize() { + return 1; + } +} + +/** + * Helps to extract test cases automatically from a file. + */ +class SimpleFileLoader { + + /** + * Builds a test suite from a library of test cases. + * The new suite is composed into this one. + * @param string $test_file File name of library with + * test case classes. + * @return TestSuite The new test suite. + * @access public + */ + function load($test_file) { + $existing_classes = get_declared_classes(); + $existing_globals = get_defined_vars(); + include_once($test_file); + $new_globals = get_defined_vars(); + $this->makeFileVariablesGlobal($existing_globals, $new_globals); + $new_classes = array_diff(get_declared_classes(), $existing_classes); + if (empty($new_classes)) { + $new_classes = $this->scrapeClassesFromFile($test_file); + } + $classes = $this->selectRunnableTests($new_classes); + return $this->createSuiteFromClasses($test_file, $classes); + } + + /** + * Imports new variables into the global namespace. + * @param hash $existing Variables before the file was loaded. + * @param hash $new Variables after the file was loaded. + * @access private + */ + protected function makeFileVariablesGlobal($existing, $new) { + $globals = array_diff(array_keys($new), array_keys($existing)); + foreach ($globals as $global) { + $_GLOBALS[$global] = $new[$global]; + } + } + + /** + * Lookup classnames from file contents, in case the + * file may have been included before. + * Note: This is probably too clever by half. Figuring this + * out after a failed test case is going to be tricky for us, + * never mind the user. A test case should not be included + * twice anyway. + * @param string $test_file File name with classes. + * @access private + */ + protected function scrapeClassesFromFile($test_file) { + preg_match_all('~^\s*class\s+(\w+)(\s+(extends|implements)\s+\w+)*\s*\{~mi', + file_get_contents($test_file), + $matches ); + return $matches[1]; + } + + /** + * Calculates the incoming test cases. Skips abstract + * and ignored classes. + * @param array $candidates Candidate classes. + * @return array New classes which are test + * cases that shouldn't be ignored. + * @access public + */ + function selectRunnableTests($candidates) { + $classes = array(); + foreach ($candidates as $class) { + if (TestSuite::getBaseTestCase($class)) { + $reflection = new SimpleReflection($class); + if ($reflection->isAbstract()) { + SimpleTest::ignore($class); + } else { + $classes[] = $class; + } + } + } + return $classes; + } + + /** + * Builds a test suite from a class list. + * @param string $title Title of new group. + * @param array $classes Test classes. + * @return TestSuite Group loaded with the new + * test cases. + * @access public + */ + function createSuiteFromClasses($title, $classes) { + if (count($classes) == 0) { + $suite = new BadTestSuite($title, "No runnable test cases in [$title]"); + return $suite; + } + SimpleTest::ignoreParentsIfIgnored($classes); + $suite = new TestSuite($title); + foreach ($classes as $class) { + if (! SimpleTest::isIgnored($class)) { + $suite->add($class); + } + } + return $suite; + } +} + +/** + * This is a composite test class for combining + * test cases and other RunnableTest classes into + * a group test. + * @package SimpleTest + * @subpackage UnitTester + */ +class TestSuite { + private $label; + private $test_cases; + + /** + * Sets the name of the test suite. + * @param string $label Name sent at the start and end + * of the test. + * @access public + */ + function TestSuite($label = false) { + $this->label = $label; + $this->test_cases = array(); + } + + /** + * Accessor for the test name for subclasses. If the suite + * wraps a single test case the label defaults to the name of that test. + * @return string Name of the test. + * @access public + */ + function getLabel() { + if (! $this->label) { + return ($this->getSize() == 1) ? + get_class($this->test_cases[0]) : get_class($this); + } else { + return $this->label; + } + } + + /** + * Adds a test into the suite by instance or class. The class will + * be instantiated if it's a test suite. + * @param SimpleTestCase $test_case Suite or individual test + * case implementing the + * runnable test interface. + * @access public + */ + function add($test_case) { + if (! is_string($test_case)) { + $this->test_cases[] = $test_case; + } elseif (TestSuite::getBaseTestCase($test_case) == 'testsuite') { + $this->test_cases[] = new $test_case(); + } else { + $this->test_cases[] = $test_case; + } + } + + /** + * Builds a test suite from a library of test cases. + * The new suite is composed into this one. + * @param string $test_file File name of library with + * test case classes. + * @access public + */ + function addFile($test_file) { + $extractor = new SimpleFileLoader(); + $this->add($extractor->load($test_file)); + } + + /** + * Delegates to a visiting collector to add test + * files. + * @param string $path Path to scan from. + * @param SimpleCollector $collector Directory scanner. + * @access public + */ + function collect($path, $collector) { + $collector->collect($this, $path); + } + + /** + * Invokes run() on all of the held test cases, instantiating + * them if necessary. + * @param SimpleReporter $reporter Current test reporter. + * @access public + */ + function run($reporter) { + $reporter->paintGroupStart($this->getLabel(), $this->getSize()); + for ($i = 0, $count = count($this->test_cases); $i < $count; $i++) { + if (is_string($this->test_cases[$i])) { + $class = $this->test_cases[$i]; + $test = new $class(); + $test->run($reporter); + unset($test); + } else { + $this->test_cases[$i]->run($reporter); + } + } + $reporter->paintGroupEnd($this->getLabel()); + return $reporter->getStatus(); + } + + /** + * Number of contained test cases. + * @return integer Total count of cases in the group. + * @access public + */ + function getSize() { + $count = 0; + foreach ($this->test_cases as $case) { + if (is_string($case)) { + if (! SimpleTest::isIgnored($case)) { + $count++; + } + } else { + $count += $case->getSize(); + } + } + return $count; + } + + /** + * Test to see if a class is derived from the + * SimpleTestCase class. + * @param string $class Class name. + * @access public + */ + static function getBaseTestCase($class) { + while ($class = get_parent_class($class)) { + $class = strtolower($class); + if ($class == 'simpletestcase' || $class == 'testsuite') { + return $class; + } + } + return false; + } +} + +/** + * This is a failing group test for when a test suite hasn't + * loaded properly. + * @package SimpleTest + * @subpackage UnitTester + */ +class BadTestSuite { + private $label; + private $error; + + /** + * Sets the name of the test suite and error message. + * @param string $label Name sent at the start and end + * of the test. + * @access public + */ + function BadTestSuite($label, $error) { + $this->label = $label; + $this->error = $error; + } + + /** + * Accessor for the test name for subclasses. + * @return string Name of the test. + * @access public + */ + function getLabel() { + return $this->label; + } + + /** + * Sends a single error to the reporter. + * @param SimpleReporter $reporter Current test reporter. + * @access public + */ + function run($reporter) { + $reporter->paintGroupStart($this->getLabel(), $this->getSize()); + $reporter->paintFail('Bad TestSuite [' . $this->getLabel() . + '] with error [' . $this->error . ']'); + $reporter->paintGroupEnd($this->getLabel()); + return $reporter->getStatus(); + } + + /** + * Number of contained test cases. Always zero. + * @return integer Total count of cases in the group. + * @access public + */ + function getSize() { + return 0; + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/Expectations.pkg b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/Expectations.pkg new file mode 100644 index 0000000..3ea6ec4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/Expectations.pkg @@ -0,0 +1,319 @@ + + + +Expectations + + + {@toc} + + +More control over mock objects + + The default behaviour of the + {@link mock objects.html mock objects} + in + {@link http://sourceforge.net/projects/simpletest/ SimpleTest} + is either an identical match on the argument or to allow any argument at all. + For almost all tests this is sufficient. + Sometimes, though, you want to weaken a test case. + + + One place where a test can be too tightly coupled is with + text matching. + Suppose we have a component that outputs a helpful error + message when something goes wrong. + You want to test that the correct error was sent, but the actual + text may be rather long. + If you test for the text exactly, then every time the exact wording + of the message changes, you will have to go back and edit the test suite. + + + For example, suppose we have a news service that has failed + to connect to its remote source. + +class NewsService { + ... + function publish(&$writer) { + if (! $this->isConnected()) { + $writer->write('Cannot connect to news service "' . + $this->_name . '" at this time. ' . + 'Please try again later.'); + } + ... + } +} + + Here it is sending its content to a + Writer class. + We could test this behaviour with a + MockWriter like so... + +class TestOfNewsService extends UnitTestCase { + ... + function testConnectionFailure() { + $writer = &new MockWriter($this); + $writer->expectOnce('write', array( + 'Cannot connect to news service ' . + '"BBC News" at this time. ' . + 'Please try again later.')); + + $service = &new NewsService('BBC News'); + $service->publish($writer); + + $writer->tally(); + } +} + + This is a good example of a brittle test. + If we decide to add additional instructions, such as + suggesting an alternative news source, we will break + our tests even though no underlying functionality + has been altered. + + + To get around this, we would like to do a regular expression + test rather than an exact match. + We can actually do this with... + +class TestOfNewsService extends UnitTestCase { + ... + function testConnectionFailure() { + $writer = &new MockWriter($this); + $writer->expectOnce( + 'write', + array(new WantedPatternExpectation('/cannot connect/i'))); + + $service = &new NewsService('BBC News'); + $service->publish($writer); + + $writer->tally(); + } +} + + Instead of passing in the expected parameter to the + MockWriter we pass an + expectation class called + WantedPatternExpectation. + The mock object is smart enough to recognise this as special + and to treat it differently. + Rather than simply comparing the incoming argument to this + object, it uses the expectation object itself to + perform the test. + + + The WantedPatternExpectation takes + the regular expression to match in its constructor. + Whenever a comparison is made by the MockWriter + against this expectation class, it will do a + preg_match() with this pattern. + With our test case above, as long as "cannot connect" + appears in the text of the string, the mock will issue a pass + to the unit tester. + The rest of the text does not matter. + + + The possible expectation classes are... + + + + +EqualExpectation + +An equality, rather than the stronger identity comparison + + + +NotEqualExpectation + +An inequality comparison + + + +IndenticalExpectation + +The default mock object check which must match exactly + + + +NotIndenticalExpectation + +Inverts the mock object logic + + + +WantedPatternExpectation + +Uses a Perl Regex to match a string + + + +NoUnwantedPatternExpectation + +Passes only if failing a Perl Regex + + + +IsAExpectation + +Checks the type or class name only + + + +NotAExpectation + +Opposite of the IsAExpectation + + + + +MethodExistsExpectation + +Checks a method is available on an object + + +
    + Most take the expected value in the constructor. + The exceptions are the pattern matchers, which take a regular expression, + and the IsAExpectation and NotAExpectation which takes a type + or class name as a string. +
    +
    + +Using expectations to control stubs + + The expectation classes can be used not just for sending assertions + from mock objects, but also for selecting behaviour for either + the + {@link mock objects.html mock objects} + or the + {@link server stubs.html server stubs}. + Anywhere a list of arguments is given, a list of expectation objects + can be inserted instead. + + + Suppose we want an authorisation server stub to simulate a successful login + only if it receives a valid session object. + We can do this as follows... + +Stub::generate('Authorisation'); + +$authorisation = new StubAuthorisation(); +$authorisation->setReturnValue( + 'isAllowed', + true, + array(new IsAExpectation('Session', 'Must be a session'))); +$authorisation->setReturnValue('isAllowed', false); + + We have set the default stub behaviour to return false when + isAllowed is called. + When we call the method with a single parameter that + is a Session object, it will return true. + We have also added a second parameter as a message. + This will be displayed as part of the mock object + failure message if this expectation is the cause of + a failure. + + + This kind of sophistication is rarely useful, but is included for + completeness. + + + +Creating your own expectations + + The expectation classes have a very simple structure. + So simple that it is easy to create your own versions for + commonly used test logic. + + + As an example here is the creation of a class to test for + valid IP addresses. + In order to work correctly with the stubs and mocks the new + expectation class should extend + SimpleExpectation... + +class ValidIp extends SimpleExpectation { + + function test($ip) { + return (ip2long($ip) != -1); + } + + function testMessage($ip) { + return "Address [$ip] should be a valid IP address"; + } +} + + There are only two methods to implement. + The test() method should + evaluate to true if the expectation is to pass, and + false otherwise. + The testMessage() method + should simply return some helpful text explaining the test + that was carried out. + + + This class can now be used in place of the earlier expectation + classes. + + + +Under the bonnet of the unit tester + + The {@link http://sourceforge.net/projects/simpletest/ SimpleTest unit testing framework} + also uses the expectation classes internally for the + {@link UnitTestCase class.html UnitTestCase class}. + We can also take advantage of these mechanisms to reuse our + homebrew expectation classes within the test suites directly. + + + The most crude way of doing this is to use the + SimpleTest::assertExpectation() method to + test against it directly... + +class TestOfNetworking extends UnitTestCase { + ... + function testGetValidIp() { + $server = &new Server(); + $this->assertExpectation( + new ValidIp(), + $server->getIp(), + 'Server IP address->%s'); + } +} + + This is a little untidy compared with our usual + assert...() syntax. + + + For such a simple case we would normally create a + separate assertion method on our test case rather + than bother using the expectation class. + If we pretend that our expectation is a little more + complicated for a moment, so that we want to reuse it, + we get... + +class TestOfNetworking extends UnitTestCase { + ... + function assertValidIp($ip, $message = '%s') { + $this->assertExpectation(new ValidIp(), $ip, $message); + } + + function testGetValidIp() { + $server = &new Server(); + $this->assertValidIp( + $server->getIp(), + 'Server IP address->%s'); + } +} + + It is unlikely we would ever need this degree of control + over the testing machinery. + It is rare to need the expectations for more than pattern + matching. + Also, complex expectation classes could make the tests + harder to read and debug. + These mechanisms are really of most use to authors of systems + that will extend the test framework to create their own tool set. + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/FormTesting.pkg b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/FormTesting.pkg new file mode 100644 index 0000000..e9a4330 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/FormTesting.pkg @@ -0,0 +1,218 @@ + + + +Testing forms + + + {@toc} + + +Submitting a simple form + + When a page is fetched by the WebTestCase + using get() or + post() the page content is + automatically parsed. + This results in any form controls that are inside <form> tags + being available from within the test case. + For example, if we have this snippet of HTML... +
    +<form>
    +    <input type="text" name="a" value="A default" />
    +    <input type="submit" value="Go" />
    +</form>
    +
    + Which looks like this... +
    + +
    + + +
    +
    + + We can navigate to this code, via the + {@link http://www.lastcraft.com/form_testing_documentation.php LastCraft} + site, with the following test... + +class SimpleFormTests extends WebTestCase { + + function testDefaultValue() { + $this->get('http://www.lastcraft.com/form_testing_documentation.php'); + $this->assertField('a', 'A default'); + } +} + + Immediately after loading the page all of the HTML controls are set at + their default values just as they would appear in the web browser. + The assertion tests that a HTML widget exists in the page with the + name "a" and that it is currently set to the value + "A default" + + + We could submit the form straight away, but first we'll change + the value of the text field and only then submit it... + +class SimpleFormTests extends WebTestCase { + + function testDefaultValue() { + $this->get('http://www.my-site.com/'); + $this->assertField('a', 'A default'); + $this->setField('a', 'New value'); + $this->clickSubmit('Go'); + } +} + + Because we didn't specify a method attribute on the form tag, and + didn't specify an action either, the test case will follow + the usual browser behaviour of submitting the form data as a GET + request back to the same location. + SimpleTest tries to emulate typical browser behaviour as much as possible, + rather than attempting to catch missing attributes on tags. + This is because the target of the testing framework is the PHP application + logic, not syntax or other errors in the HTML code. + For HTML errors, other tools such as + {@link http://www.w3.org/People/Raggett/tidy/ HTMLTidy} should be used. + + + If a field is not present in any form, or if an option is unavailable, + then WebTestCase::setField() will return + false. + For example, suppose we wish to verify that a "Superuser" + option is not present in this form... +
    +<strong>Select type of user to add:</strong>
    +<select name="type">
    +    <option>Subscriber</option>
    +    <option>Author</option>
    +    <option>Administrator</option>
    +</select>
    +
    + Which looks like... +
    + +
    + Select type of user to add: + +
    +
    + + The following test will confirm it... + +class SimpleFormTests extends WebTestCase { + ... + function testNoSuperuserChoiceAvailable() { + $this->get('http://www.lastcraft.com/form_testing_documentation.php'); + $this->assertFalse($this->setField('type', 'Superuser')); + } +} + + The selection will not be changed on a failure to set + a widget value. + + + Here is the full list of widgets currently supported... +
      +
    • Text fields, including hidden and password fields.
    • +
    • Submit buttons including the button tag, although not yet reset buttons
    • +
    • Text area. This includes text wrapping behaviour.
    • +
    • Checkboxes, including multiple checkboxes in the same form.
    • +
    • Drop down selections, including multiple selects.
    • +
    • Radio buttons.
    • +
    • Images.
    • +
    +
    + + Although most standard HTML widgets are catered for by SimpleTest's + built in parser, it is unlikely that JavaScript will be implemented + anytime soon. + +
    + +Fields with multiple values + + SimpleTest can cope with two types of multivalue controls: Multiple + selection drop downs, and multiple checkboxes with the same name + within a form. + The multivalue nature of these means that setting and testing + are slightly different. + Using checkboxes as an example... +
    +<form class="demo">
    +    <strong>Create privileges allowed:</strong>
    +    <input type="checkbox" name="crud" value="c" checked><br>
    +    <strong>Retrieve privileges allowed:</strong>
    +    <input type="checkbox" name="crud" value="r" checked><br>
    +    <strong>Update privileges allowed:</strong>
    +    <input type="checkbox" name="crud" value="u" checked><br>
    +    <strong>Destroy privileges allowed:</strong>
    +    <input type="checkbox" name="crud" value="d" checked><br>
    +    <input type="submit" value="Enable Privileges">
    +</form>
    +
    + Which renders as... +
    + +
    + Create privileges allowed: + +
    + Retrieve privileges allowed: + +
    + Update privileges allowed: + +
    + Destroy privileges allowed: + +
    + +
    +
    + + If we wish to disable all but the retrieval privileges and + submit this information we can do it like this... + +class SimpleFormTests extends WebTestCase { + ... + function testDisableNastyPrivileges() { + $this->get('http://www.lastcraft.com/form_testing_documentation.php'); + $this->assertField('crud', array('c', 'r', 'u', 'd')); + $this->setField('crud', array('r')); + $this->clickSubmit('Enable Privileges'); + } +} + + Instead of setting the field to a single value, we give it a list + of values. + We do the same when testing expected values. + We can then write other test code to confirm the effect of this, perhaps + by logging in as that user and attempting an update. + + + Raw posting + + + If you want to test a form handler, but have not yet written + or do not have access to the form itself, you can create a + form submission by hand. + +class SimpleFormTests extends WebTestCase { + ... + function testAttemptedHack() { + $this->post( + 'http://www.my-site.com/add_user.php', + array('type' => 'superuser')); + $this->assertNoUnwantedPattern('/user created/i'); + } +} + + By adding data to the WebTestCase::post() + method, we are attempting to fetch the page as a form submission. + +
    +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/GroupTests.pkg b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/GroupTests.pkg new file mode 100644 index 0000000..a695f5d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/GroupTests.pkg @@ -0,0 +1,297 @@ + + + +Group tests + + + {@toc} + + +Grouping tests + + To run test cases as part of a group the test cases should really + be placed in files without the runner code... + +<?php + require_once('../classes/io.php'); + + class FileTester extends UnitTestCase { + ... + } + + class SocketTester extends UnitTestCase { + ... + } +?> + + As many cases as needed can appear in a single file. + They should include any code they need, such as the library + being tested, but none of the simple test libraries. + + + If you have extended any test cases, you can include them + as well. + +<?php + require_once('../classes/io.php'); + + class MyFileTestCase extends UnitTestCase { + ... + } + SimpleTestOptions::ignore('MyFileTestCase'); + + class FileTester extends MyFileTestCase { + ... + } + + class SocketTester extends UnitTestCase { + ... + } +?> + + The FileTester class does + not contain any actual tests, but is a base class for other + test cases. + For this reason we use the + SimpleTestOptions::ignore() directive + to tell the upcoming group test to ignore it. + This directive can appear anywhere in the file and works + when a whole file of test cases is loaded (see below). + We will call this sample file_test.php. + + + Next we create a group test file, called say group_test.php. + You will think of a better name I am sure. + We will add the test file using a safe method... + +<?php + require_once('simpletest/unit_tester.php'); + require_once('simpletest/reporter.php'); + require_once('file_test.php'); + + $test = &new GroupTest('All file tests'); + $test->addTestCase(new FileTestCase()); + $test->run(new HtmlReporter()); +?> + + This instantiates the test case before the test suite is + run. + This could get a little expensive with a large number of test + cases, so another method is provided that will only + instantiate the class when it is needed... + +<?php + require_once('simpletest/unit_tester.php'); + require_once('simpletest/reporter.php'); + require_once('file_test.php'); + + $test = &new GroupTest('All file tests'); + $test->addTestClass('FileTestCase'); + $test->run(new HtmlReporter()); +?> + + The problem with this method is that for every test case + that we add we will have + to require_once() the test code + file and manually instantiate each and every test case. + We can save a lot of typing with... + +<?php + require_once('simpletest/unit_tester.php'); + require_once('simpletest/reporter.php'); + + $test = &new GroupTest('All file tests'); + $test->addTestFile('file_test.php'); + $test->run(new HtmlReporter()); +?&gt; + + What happens here is that the GroupTest + class has done the require_once() + for us. + It then checks to see if any new test case classes + have been created by the new file and automatically adds + them to the group test. + Now all we have to do is add each new file. + + + There are two things that could go wrong and which require care... +
      +
    1. + The file could already have been parsed by PHP and so no + new classes will have been added. You should make + sure that the test cases are only included in this file + and no others. +
    2. +
    3. + New test case extension classes that get included will be + placed in the group test and run also. + You will need to add a SimpleTestOptions::ignore() + directive for these classes or make sure that they are included + before the GroupTest::addTestFile() + line. +
    4. +
    +
    +
    + +Higher groupings + + The above method places all of the test cases into one large group. + For larger projects though this may not be flexible enough; you + may want to group the tests in all sorts of ways. + + + To get a more flexible group test we can subclass + GroupTest and then instantiate it as needed... + +<?php + require_once('simpletest/unit_tester.php'); + require_once('simpletest/reporter.php'); + + class FileGroupTest extends GroupTest { + function FileGroupTest() { + $this->GroupTest('All file tests'); + $this->addTestFile('file_test.php'); + } + } +?> + + This effectively names the test in the constructor and then + adds our test cases and a single group below. + Of course we can add more than one group at this point. + We can now invoke the tests from a separate runner file... + +<?php + require_once('file_group_test.php'); + + $test = &new FileGroupTest(); + $test->run(new HtmlReporter()); +?> + + ...or we can group them into even larger group tests... + +<?php + require_once('file_group_test.php'); + + $test = &new BigGroupTest('Big group'); + $test->addTestCase(new FileGroupTest()); + $test->addTestCase(...); + $test->run(new HtmlReporter()); +?> + + If we still wish to run the original group test and we + don't want all of these little runner files, we can + put the test runner code around guard bars when we create + each group. + +<?php + class FileGroupTest extends GroupTest { + function FileGroupTest() { + $this->GroupTest('All file tests'); + $test->addTestFile('file_test.php'); + } + } + + if (! defined('RUNNER')) { + define('RUNNER', true); + $test = &new FileGroupTest(); + $test->run(new HtmlReporter()); + } +?> + + This approach requires the guard to be set when including + the group test file, but this is still less hassle than + lots of separate runner files. + You include the same guard on the top level tests to make sure + that run() will run once only + from the top level script that has been invoked. + +<?php + define('RUNNER', true); + require_once('file_group_test.php'); + + $test = &new BigGroupTest('Big group'); + $test->addTestCase(new FileGroupTest()); + $test->addTestCase(...); + $test->run(new HtmlReporter()); +?> + + As with the normal test cases, a GroupTest can + be loaded with the GroupTest::addTestFile() method. + +<?php + define('RUNNER', true); + + $test = &new BigGroupTest('Big group'); + $test->addTestFile('file_group_test.php'); + $test->addTestFile(...); + $test->run(new HtmlReporter()); +?> + + + + +Integrating legacy test cases + + If you already have unit tests for your code or are extending external + classes that have tests, it is unlikely that all of the test cases + are in SimpleTest format. + Fortunately it is possible to incorporate test cases from other + unit testers directly into SimpleTest group tests. + + + Say we have the following + {@link http://sourceforge.net/projects/phpunit PhpUnit} + test case in the file config_test.php... + +class ConfigFileTest extends TestCase { + function ConfigFileTest() { + $this->TestCase('Config file test'); + } + + function testContents() { + $config = new ConfigFile('test.conf'); + $this->assertRegexp('/me/', $config->getValue('username')); + } +} + + The group test can recognise this as long as we include + the appropriate adapter class before we add the test + file... + +<?php + require_once('simpletest/unit_tester.php'); + require_once('simpletest/reporter.php'); + require_once('simpletest/adapters/phpunit_test_case.php'); + + $test = &new GroupTest('All file tests'); + $test->addTestFile('config_test.php'); + $test->run(new HtmlReporter()); +?> + + There are only two adapters, the other is for the + {@link http://pear.php.net/manual/en/package.php.phpunit.php PEAR} + 1.0 unit tester... + +<?php + require_once('simpletest/unit_tester.php'); + require_once('simpletest/reporter.php'); + require_once('simpletest/adapters/pear_test_case.php'); + + $test = &new GroupTest('All file tests'); + $test->addTestFile('some_pear_test_cases.php'); + $test->run(new HtmlReporter()); +?> + + The PEAR test cases can be freely mixed with SimpleTest + ones even in the same test file, + but you cannot use SimpleTest assertions in the legacy + test case versions. + This is done as a check that you are not accidently making + your test cases completely dependent on SimpleTest. + You may want to do a PEAR release of your library for example + which would mean shipping it with valid PEAR::PhpUnit test + cases. + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/MockObjects.pkg b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/MockObjects.pkg new file mode 100644 index 0000000..b22413a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/MockObjects.pkg @@ -0,0 +1,663 @@ + + + +Mock objects + + + {@toc} + + +What are mock objects? + + Mock objects have two roles during a test case: actor and critic. + + + The actor behaviour is to simulate objects that are difficult to + set up or time consuming to set up for a test. + The classic example is a database connection. + Setting up a test database at the start of each test would slow + testing to a crawl and would require the installation of the + database engine and test data on the test machine. + If we can simulate the connection and return data of our + choosing we not only win on the pragmatics of testing, but can + also feed our code spurious data to see how it responds. + We can simulate databases being down or other extremes + without having to create a broken database for real. + In other words, we get greater control of the test environment. + + + If mock objects only behaved as actors they would simply be + known as {@link server stubs.html server stubs}. + + + However, the mock objects not only play a part (by supplying chosen + return values on demand) they are also sensitive to the + messages sent to them (via expectations). + By setting expected parameters for a method call they act + as a guard that the calls upon them are made correctly. + If expectations are not met they save us the effort of + writing a failed test assertion by performing that duty on our + behalf. + In the case of an imaginary database connection they can + test that the query, say SQL, was correctly formed by + the object that is using the connection. + Set them up with fairly tight expectations and you will + hardly need manual assertions at all. + + + +Creating mock objects + + In the same way that we create server stubs, all we need is an + existing class, say a database connection that looks like this... + +class DatabaseConnection { + function DatabaseConnection() { + } + + function query() { + } + + function selectQuery() { + } +} + + The class does not need to have been implemented yet. + To create a mock version of the class we need to include the + mock object library and run the generator... + +require_once('simpletest/unit_tester.php'); +require_once('simpletest/mock_objects.php'); +require_once('database_connection.php'); + +Mock::generate('DatabaseConnection'); + + This generates a clone class called + MockDatabaseConnection. + We can now create instances of the new class within + our test case... + +require_once('simpletest/unit_tester.php'); +require_once('simpletest/mock_objects.php'); +require_once('database_connection.php'); + +Mock::generate('DatabaseConnection'); + +class MyTestCase extends UnitTestCase { + + function testSomething() { + $connection = &new MockDatabaseConnection($this); + } +} + + Unlike the generated stubs the mock constructor needs a reference + to the test case so that it can dispatch passes and failures while + checking its expectations. + This means that mock objects can only be used within test cases. + Despite this their extra power means that stubs are hardly ever used + if mocks are available. + + + Mocks as actors + + + The mock version of a class has all the methods of the original + so that operations like + $connection->query() are still + legal. + As with stubs we can replace the default null return values... + +$connection->setReturnValue('query', 37); + + Now every time we call + $connection->query() we get + the result of 37. + As with the stubs we can set wildcards and we can overload the + wildcard parameter. + We can also add extra methods to the mock when generating it + and choose our own class name... + +Mock::generate('DatabaseConnection', 'MyMockDatabaseConnection', array('setOptions')); + + Here the mock will behave as if the setOptions() + existed in the original class. + This is handy if a class has used the PHP overload() + mechanism to add dynamic methods. + You can create a special mock to simulate this situation. + + + All of the patterns available with server stubs are available + to mock objects... + +class Iterator { + function Iterator() { + } + + function next() { + } +} + + Again, assuming that this iterator only returns text until it + reaches the end, when it returns false, we can simulate it + with... + +Mock::generate('Iterator'); + +class IteratorTest extends UnitTestCase() { + + function testASequence() { + $iterator = &new MockIterator($this); + $iterator->setReturnValue('next', false); + $iterator->setReturnValueAt(0, 'next', 'First string'); + $iterator->setReturnValueAt(1, 'next', 'Second string'); + ... + } +} + + When next() is called on the + mock iterator it will first return "First string", + on the second call "Second string" will be returned + and on any other call false will + be returned. + The sequenced return values take precedence over the constant + return value. + The constant one is a kind of default if you like. + + + A repeat of the stubbed information holder with name/value pairs... + +class Configuration { + function Configuration() { + } + + function getValue($key) { + } +} + + This is a classic situation for using mock objects as + actual configuration will vary from machine to machine, + hardly helping the reliability of our tests if we use it + directly. + The problem though is that all the data comes through the + getValue() method and yet + we want different results for different keys. + Luckily the mocks have a filter system... + +$config = &new MockConfiguration($this); +$config->setReturnValue('getValue', 'primary', array('db_host')); +$config->setReturnValue('getValue', 'admin', array('db_user')); +$config->setReturnValue('getValue', 'secret', array('db_password')); + + The extra parameter is a list of arguments to attempt + to match. + In this case we are trying to match only one argument which + is the look up key. + Now when the mock object has the + getValue() method invoked + like this... + +$config->getValue('db_user') + + ...it will return "admin". + It finds this by attempting to match the calling arguments + to its list of returns one after another until + a complete match is found. + + + There are times when you want a specific object to be + dished out by the mock rather than a copy. + Again this is identical to the server stubs mechanism... + +class Thing { +} + +class Vector { + function Vector() { + } + + function get($index) { + } +} + + In this case you can set a reference into the mock's + return list... + +$thing = new Thing(); +$vector = &new MockVector($this); +$vector->setReturnReference('get', $thing, array(12)); + + With this arrangement you know that every time + $vector->get(12) is + called it will return the same + $thing each time. + + + +Mocks as critics + + Although the server stubs approach insulates your tests from + real world disruption, it is only half the benefit. + You can have the class under test receiving the required + messages, but is your new class sending correct ones? + Testing this can get messy without a mock objects library. + + + By way of example, suppose we have a + SessionPool class that we + want to add logging to. + Rather than grow the original class into something more + complicated, we want to add this behaviour with a decorator (GOF). + The SessionPool code currently looks + like this... + +class SessionPool { + function SessionPool() { + ... + } + + function &findSession($cookie) { + ... + } + ... +} + +class Session { + ... +} +</php> + While our logging code looks like this... +<php> +class Log { + function Log() { + ... + } + + function message() { + ... + } +} + +class LoggingSessionPool { + function LoggingSessionPool(&$session_pool, &$log) { + ... + } + + function &findSession(\$cookie) { + ... + } + ... +} + + Out of all of this, the only class we want to test here + is the LoggingSessionPool. + In particular we would like to check that the + findSession() method is + called with the correct session ID in the cookie and that + it sent the message "Starting session $cookie" + to the logger. + + + Despite the fact that we are testing only a few lines of + production code, here is what we would have to do in a + conventional test case: +
      +
    1. Create a log object.
    2. +
    3. Set a directory to place the log file.
    4. +
    5. Set the directory permissions so we can write the log.
    6. +
    7. Create a SessionPool object.
    8. +
    9. Hand start a session, which probably does lot's of things.
    10. +
    11. Invoke findSession().
    12. +
    13. Read the new Session ID (hope there is an accessor!).
    14. +
    15. Raise a test assertion to confirm that the ID matches the cookie.
    16. +
    17. Read the last line of the log file.
    18. +
    19. Pattern match out the extra logging timestamps, etc.
    20. +
    21. Assert that the session message is contained in the text.
    22. +
    + It is hardly surprising that developers hate writing tests + when they are this much drudgery. + To make things worse, every time the logging format changes or + the method of creating new sessions changes, we have to rewrite + parts of this test even though this test does not officially + test those parts of the system. + We are creating headaches for the writers of these other classes. +
    + + Instead, here is the complete test method using mock object magic... + +Mock::generate('Session'); +Mock::generate('SessionPool'); +Mock::generate('Log'); + +class LoggingSessionPoolTest extends UnitTestCase { + ... + function testFindSessionLogging() { + $session = &new MockSession($this); + $pool = &new MockSessionPool($this); + $pool->setReturnReference('findSession', $session); + $pool->expectOnce('findSession', array('abc')); + + $log = &new MockLog($this); + $log->expectOnce('message', array('Starting session abc')); + + $logging_pool = &new LoggingSessionPool($pool, $log); + $this->assertReference($logging_pool->findSession('abc'), $session); + $pool->tally(); + $log->tally(); + } +} + + We start by creating a dummy session. + We don't have to be too fussy about this as the check + for which session we want is done elsewhere. + We only need to check that it was the same one that came + from the session pool. + + + findSession() is a factory + method the simulation of which is described {@link #stub above}. + The point of departure comes with the first + expectOnce() call. + This line states that whenever + findSession() is invoked on the + mock, it will test the incoming arguments. + If it receives the single argument of a string "abc" + then a test pass is sent to the unit tester, otherwise a fail is + generated. + This was the part where we checked that the right session was asked for. + The argument list follows the same format as the one for setting + return values. + You can have wildcards and sequences and the order of + evaluation is the same. + + + If the call is never made then neither a pass nor a failure will + generated. + To get around this we must tell the mock when the test is over + so that the object can decide if the expectation has been met. + The unit tester assertion for this is triggered by the + tally() call at the end of + the test. + + + We use the same pattern to set up the mock logger. + We tell it that it should have + message() invoked + once only with the argument "Starting session abc". + By testing the calling arguments, rather than the logger output, + we insulate the test from any display changes in the logger. + + + We start to run our tests when we create the new + LoggingSessionPool and feed + it our preset mock objects. + Everything is now under our control. + Finally we confirm that the + $session we gave our decorator + is the one that we get back and tell the mocks to run their + internal call count tests with the + tally() calls. + + + This is still quite a bit of test code, but the code is very + strict. + If it still seems rather daunting there is a lot less of it + than if we tried this without mocks and this particular test, + interactions rather than output, is always more work to set + up. + More often you will be testing more complex situations without + needing this level or precision. + Also some of this can be refactored into a test case + setUp() method. + + + Here is the full list of expectations you can set on a mock object + in {@link http://www.lastcraft.com/simple_test.php SimpleTest}... + + + + + + + + + + +expectArguments($method, $args) + + No + + + +expectArgumentsAt($timing, $method, $args) + + No + + + +expectCallCount($method, $count) + + Yes + + + +expectMaximumCallCount($method, $count) + + No + + + +expectMinimumCallCount($method, $count) + + Yes + + + +expectNever($method) + + No + + + +expectOnce($method, $args) + + Yes + + + +expectAtLeastOnce($method, $args) + + Yes + + +
    ExpectationNeeds tally() +
    + Where the parameters are... +
    +
    $method
    +
    The method name, as a string, to apply the condition to.
    +
    $args
    +
    + The arguments as a list. Wildcards can be included in the same + manner as for setReturn(). + This argument is optional for expectOnce() + and expectAtLeastOnce(). +
    +
    $timing
    +
    + The only point in time to test the condition. + The first call starts at zero. +
    +
    $count
    +
    The number of calls expected.
    +
    + The method expectMaximumCallCount() + is slightly different in that it will only ever generate a failure. + It is silent if the limit is never reached. +
    + + Like the assertions within test cases, all of the expectations + can take a message override as an extra parameter. + Also the original failure message can be embedded in the output + as "%s". + +
    + +Other approaches + + There are three approaches to creating mocks including the one + that SimpleTest employs. + Coding them by hand using a base class, generating them to + a file and dynamically generating them on the fly. + + + Mock objects generated with {@link SimpleTest.html SimpleTest} + are dynamic. + They are created at run time in memory, using + eval(), rather than written + out to a file. + This makes the mocks easy to create, a one liner, + especially compared with hand + crafting them in a parallel class hierarchy. + The problem is that the behaviour is usually set up in the tests + themselves. + If the original objects change the mock versions + that the tests rely on can get out of sync. + This can happen with the parallel hierarchy approach as well, + but is far more quickly detected. + + + The solution, of course, is to add some real integration + tests. + You don't need very many and the convenience gained + from the mocks more than outweighs the small amount of + extra testing. + You cannot trust code that was only tested with mocks. + + + If you are still determined to build static libraries of mocks + because you want to simulate very specific behaviour, you can + achieve the same effect using the SimpleTest class generator. + In your library file, say mocks/connection.php for a + database connection, create a mock and inherit to override + special methods or add presets... + +<?php + require_once('simpletest/mock_objects.php'); + require_once('../classes/connection.php'); + + Mock::generate('Connection', 'BasicMockConnection'); + class MockConnection extends BasicMockConnection { + function MockConnection(&$test, $wildcard = '*') { + $this->BasicMockConnection($test, $wildcard); + $this->setReturn('query', false); + } + } +?> + + The generate call tells the class generator to create + a class called BasicMockConnection + rather than the usual MockConnection. + We then inherit from this to get our version of + MockConnection. + By intercepting in this way we can add behaviour, here setting + the default value of query() to be false. + By using the default name we make sure that the mock class + generator will not recreate a different one when invoked elsewhere in the + tests. + It never creates a class if it already exists. + As long as the above file is included first then all tests + that generated MockConnection should + now be using our one instead. + If we don't get the order right and the mock library + creates one first then the class creation will simply fail. + + + Use this trick if you find you have a lot of common mock behaviour + or you are getting frequent integration problems at later + stages of testing. + + + +I think SimpleTest stinks! + + But at the time of writing it is the only one with mock objects, + so are you stuck with it? + + + No, not at all. + {@link SimpleTest.html SimpleTest} is a toolkit and one of those + tools is the mock objects which can be employed independently. + Suppose you have your own favourite unit tester and all your current + test cases are written using it. + Pretend that you have called your unit tester PHPUnit (everyone else has) + and the core test class looks like this... + +class PHPUnit { + function PHPUnit() { + } + + function assertion($message, $assertion) { + } + ... +} + + All the assertion() method does + is print some fancy output and the boolean assertion parameter determines + whether to print a pass or a failure. + Let's say that it is used like this... + +$unit_test = new PHPUnit(); +$unit_test>assertion('I hope this file exists', file_exists('my_file')); + + How do you use mocks with this? + + + There is a protected method on the base mock class + SimpleMock called + _assertTrue() and + by overriding this method we can use our own assertion format. + We start with a subclass, in say my_mock.php... + +<?php + require_once('simpletest/mock_objects.php'); + + class MyMock extends SimpleMock() { + function MyMock(&$test, $wildcard) { + $this->SimpleMock($test, $wildcard); + } + + function _assertTrue($assertion, $message) { + $test = &$this->getTest(); + $test->assertion($message, $assertion); + } + } +?> + + Now instantiating MyMock will create + an object that speaks the same language as your tester. + The catch is of course that we never create such an object, the + code generator does. + We need just one more line of code to tell the generator to use + your mock instead... + +<?php + require_once('simpletst/mock_objects.php'); + + class MyMock extends SimpleMock() { + function MyMock($test, $wildcard) { + $this->SimpleMock(&$test, $wildcard); + } + + function _assertTrue($assertion, $message , &$test) { + $test->assertion($message, $assertion); + } + } + SimpleTestOptions::setMockBaseClass('MyMock'); +?> + + From now on you just include my_mock.php instead of the + default mock_objects.php version and you can introduce + mock objects into your existing test suite. + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/PartialMock.pkg b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/PartialMock.pkg new file mode 100644 index 0000000..1ee147c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/PartialMock.pkg @@ -0,0 +1,361 @@ + + + +Partial mocks + + A partial mock is simply a pattern to alleviate a specific problem + in testing with mock objects, + that of getting mock objects into tight corners. + It's quite a limited tool and possibly not even a good idea. + It is included with SimpleTest because I have found it useful + on more than one occasion and has saved a lot of work at that point. + + + {@toc} + + + +The mock injection problem + + When one object uses another it is very simple to just pass a mock + version in already set up with its expectations. + Things are rather tricker if one object creates another and the + creator is the one you want to test. + This means that the created object should be mocked, but we can + hardly tell our class under test to create a mock instead. + The tested class doesn't even know it is running inside a test + after all. + + + For example, suppose we are building a telnet client and it + needs to create a network socket to pass its messages. + The connection method might look something like... + +<?php + require_once('socket.php'); + + class Telnet { + ... + function &connect($ip, $port, $username, $password) { + $socket = &new Socket($ip, $port); + $socket->read( ... ); + ... + } + } +?> + + We would really like to have a mock object version of the socket + here, what can we do? + + + The first solution is to pass the socket in as a parameter, + forcing the creation up a level. + Having the client handle this is actually a very good approach + if you can manage it and should lead to factoring the creation from + the doing. + In fact, this is one way in which testing with mock objects actually + forces you to code more tightly focused solutions. + They improve your programming. + + + Here this would be... + +<?php + require_once('socket.php'); + + class Telnet { + ... + function &connect(&$socket, $username, $password) { + $socket->read( ... ); + ... + } + } +?> + + This means that the test code is typical for a test involving + mock objects. + +class TelnetTest extends UnitTestCase { + ... + function testConnection() { + $socket = &new MockSocket($this); + ... + $telnet = &new Telnet(); + $telnet->connect($socket, 'Me', 'Secret'); + ... + } +} + + It is pretty obvious though that one level is all you can go. + You would hardly want your top level application creating + every low level file, socket and database connection ever + needed. + It wouldn't know the constructor parameters anyway. + + + The next simplest compromise is to have the created object passed + in as an optional parameter... + +<?php + require_once('socket.php'); + + class Telnet { + ... + function &connect($ip, $port, $username, $password, $socket = false) { + if (!$socket) { + $socket = &new Socket($ip, $port); + } + $socket->read( ... ); + ... + return $socket; + } + } +?> + + For a quick solution this is usually good enough. + The test now looks almost the same as if the parameter + was formally passed... + +class TelnetTest extends UnitTestCase { + ... + function testConnection() { + $socket = &new MockSocket($this); + ... + $telnet = &new Telnet(); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret', &$socket); + ... + } +} + + The problem with this approach is its untidiness. + There is test code in the main class and parameters passed + in the test case that are never used. + This is a quick and dirty approach, but nevertheless effective + in most situations. + + + The next method is to pass in a factory object to do the creation... + +<?php + require_once('socket.php'); + + class Telnet { + function Telnet(&$network) { + $this->_network = &$network; + } + ... + function &connect($ip, $port, $username, $password) { + $socket = &$this->_network->createSocket($ip, $port); + $socket->read( ... ); + ... + return $socket; + } + } +?> + + This is probably the most highly factored answer as creation + is now moved into a small specialist class. + The networking factory can now be tested separately, but mocked + easily when we are testing the telnet class... + +class TelnetTest extends UnitTestCase { + ... + function testConnection() { + $socket = &new MockSocket($this); + ... + $network = &new MockNetwork($this); + $network->setReturnReference('createSocket', $socket); + $telnet = &new Telnet($network); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ... + } +} + + The downside is that we are adding a lot more classes to the + library. + Also we are passing a lot of factories around which will + make the code a little less intuitive. + The most flexible solution, but the most complex. + + + Is there a middle ground? + + + +Protected factory method + + There is a way we can circumvent the problem without creating + any new application classes, but it involves creating a subclass + when we do the actual testing. + Firstly we move the socket creation into its own method... + +<?php + require_once('socket.php'); + + class Telnet { + ... + function &connect($ip, $port, $username, $password) { + $socket = &$this->_createSocket($ip, $port); + $socket->read( ... ); + ... + } + + function &_createSocket($ip, $port) { + return new Socket($ip, $port); + } + } +?> + + This is the only change we make to the application code. + + + For the test case we have to create a subclass so that + we can intercept the socket creation... + +class TelnetTestVersion extends Telnet { + var $_mock; + + function TelnetTestVersion(&$mock) { + $this->_mock = &$mock; + $this->Telnet(); + } + + function &_createSocket() { + return $this->_mock; + } +} + + Here I have passed the mock in the constructor, but a + setter would have done just as well. + Note that the mock was set into the object variable + before the constructor was chained. + This is necessary in case the constructor calls + connect(). + Otherwise it could get a null value from + _createSocket(). + + + After the completion of all of this extra work the + actual test case is fairly easy. + We just test our new class instead... + +class TelnetTest extends UnitTestCase { + ... + function testConnection() { + $socket = &new MockSocket($this); + ... + $telnet = &new TelnetTestVersion($socket); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ... + } +} + + The new class is very simple of course. + It just sets up a return value, rather like a mock. + It would be nice if it also checked the incoming parameters + as well. + Just like a mock. + It seems we are likely to do this often, can + we automate the subclass creation? + + + +A partial mock + + Of course the answer is "yes" or I would have stopped writing + this by now! + The previous test case was a lot of work, but we can + generate the subclass using a similar approach to the mock objects. + + + Here is the partial mock version of the test... + +Mock::generatePartial( + 'Telnet', + 'TelnetTestVersion', + array('_createSocket')); + +class TelnetTest extends UnitTestCase { + ... + function testConnection() { + $socket = &new MockSocket($this); + ... + $telnet = &new TelnetTestVersion($this); + $telnet->setReturnReference('_createSocket', $socket); + $telnet->Telnet(); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ... + } +} + + The partial mock is a subclass of the original with + selected methods "knocked out" with test + versions. + The generatePartial() call + takes three parameters: the class to be subclassed, + the new test class name and a list of methods to mock. + + + Instantiating the resulting objects is slightly tricky. + The only constructor parameter of a partial mock is + the unit tester reference. + As with the normal mock objects this is needed for sending + test results in response to checked expectations. + + + The original constructor is not run yet. + This is necessary in case the constructor is going to + make use of the as yet unset mocked methods. + We set any return values at this point and then run the + constructor with its normal parameters. + This three step construction of "new", followed + by setting up the methods, followed by running the constructor + proper is what distinguishes the partial mock code. + + + Apart from construction, all of the mocked methods have + the same features as mock objects and all of the unmocked + methods behave as before. + We can set expectations very easily... + +class TelnetTest extends UnitTestCase { + ... + function testConnection() { + $socket = &new MockSocket($this); + ... + $telnet = &new TelnetTestVersion($this); + $telnet->setReturnReference('_createSocket', $socket); + $telnet->expectOnce('_createSocket', array('127.0.0.1', 21)); + $telnet->Telnet(); + $telnet->connect('127.0.0.1', 21, 'Me', 'Secret'); + ... + $telnet->tally(); + } +} + + + + +Testing less than a class + + The mocked out methods don't have to be factory methods, + they could be any sort of method. + In this way partial mocks allow us to take control of any part of + a class except the constructor. + We could even go as far as to mock every method + except one we actually want to test. + + + This last situation is all rather hypothetical, as I haven't + tried it. + I am open to the possibility, but a little worried that + forcing object granularity may be better for the code quality. + I personally use partial mocks as a way of overriding creation + or for occasional testing of the TemplateMethod pattern. + + + It's all going to come down to the coding standards of your + project to decide which mechanism you use. + + + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/Reporting.pkg b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/Reporting.pkg new file mode 100644 index 0000000..c60531e --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/Reporting.pkg @@ -0,0 +1,450 @@ + + + +Reporting + + SimpleTest pretty much follows the MVC pattern + (Model-View-Controller). + The reporter classes are the view and the model is your + test cases and their hiearchy. + The controller is mostly hidden from the user of + SimpleTest unless you want to change how the test cases + are actually run, in which case it is possible to + override the runner objects from within the test case. + As usual with MVC, the controller is mostly undefined + and there are other places to control the test run. + + + {@toc} + + + +Reporting results in HTML + + The default test display is minimal in the extreme. + It reports success and failure with the conventional red and + green bars and shows a breadcrumb trail of test groups + for every failed assertion. + Here's a fail... +
    +

    File test

    + Fail: createnewfile->True assertion failed.
    +
    1/1 test cases complete. + 0 passes, 1 fails and 0 exceptions.
    +
    + And here all tests passed... +
    +

    File test

    +
    1/1 test cases complete. + 1 passes, 0 fails and 0 exceptions.
    +
    + The good news is that there are several points in the display + hiearchy for subclassing. +
    + + For web page based displays there is the + HtmlReporter class with the following + signature... + +class HtmlReporter extends SimpleReporter { + public HtmlReporter($encoding) { ... } + public makeDry(boolean $is_dry) { ... } + public void paintHeader(string $test_name) { ... } + public void sendNoCacheHeaders() { ... } + public void paintFooter(string $test_name) { ... } + public void paintGroupStart(string $test_name, integer $size) { ... } + public void paintGroupEnd(string $test_name) { ... } + public void paintCaseStart(string $test_name) { ... } + public void paintCaseEnd(string $test_name) { ... } + public void paintMethodStart(string $test_name) { ... } + public void paintMethodEnd(string $test_name) { ... } + public void paintFail(string $message) { ... } + public void paintPass(string $message) { ... } + public void paintError(string $message) { ... } + public void paintException(string $message) { ... } + public void paintMessage(string $message) { ... } + public void paintFormattedMessage(string $message) { ... } + protected string _getCss() { ... } + public array getTestList() { ... } + public integer getPassCount() { ... } + public integer getFailCount() { ... } + public integer getExceptionCount() { ... } + public integer getTestCaseCount() { ... } + public integer getTestCaseProgress() { ... } +} + + Here is what some of these methods mean. First the display methods + that you will probably want to override... +
      +
    • + HtmlReporter(string $encoding) +
      + is the constructor. + Note that the unit test sets up the link to the display + rather than the other way around. + The display is a mostly passive receiver of test events. + This allows easy adaption of the display for other test + systems beside unit tests, such as monitoring servers. + The encoding is the character encoding you wish to + display the test output in. + In order to correctly render debug output when + using the web tester, this should match the encoding + of the site you are trying to test. + The available character set strings are described in + the PHP {@link http://www.php.net/manual/en/function.htmlentities.php html_entities()} + function. +
    • +
    • + void paintHeader(string $test_name) +
      + is called once at the very start of the test when the first + start event arrives. + The first start event is usually delivered by the top level group + test and so this is where $test_name + comes from. + It paints the page titles, CSS, body tag, etc. + It returns nothing (void). +
    • +
    • + void paintFooter(string $test_name) +
      + Called at the very end of the test to close any tags opened + by the page header. + By default it also displays the red/green bar and the final + count of results. + Actually the end of the test happens when a test end event + comes in with the same name as the one that started it all + at the same level. + The tests nest you see. + Closing the last test finishes the display. +
    • +
    • + void paintMethodStart(string $test_name) +
      + is called at the start of each test method. + The name normally comes from method name. + The other test start events behave the same way except + that the group test one tells the reporter how large + it is in number of held test cases. + This is so that the reporter can display a progress bar + as the runner churns through the test cases. +
    • +
    • + void paintMethodEnd(string $test_name) +
      + backs out of the test started with the same name. +
    • +
    • + void paintFail(string $message) +
      + paints a failure. + By default it just displays the word fail, a breadcrumbs trail + showing the current test nesting and the message issued by + the assertion. +
    • +
    • + void paintPass(string $message) +
      + by default does nothing. +
    • +
    • + string _getCss() +
      + Returns the CSS styles as a string for the page header + method. + Additional styles have to be appended here if you are + not overriding the page header. + You will want to use this method in an overriden page header + if you want to include the original CSS. +
    • +
    + There are also some accessors to get information on the current + state of the test suite. + Use these to enrich the display... +
      +
    • + array getTestList() +
      + is the first convenience method for subclasses. + Lists the current nesting of the tests as a list + of test names. + The first, most deeply nested test, is first in the + list and the current test method will be last. +
    • +
    • + integer getPassCount() +
      + returns the number of passes chalked up so far. + Needed for the display at the end. +
    • +
    • + integer getFailCount() +
      + is likewise the number of fails so far. +
    • +
    • + integer getExceptionCount() +
      + is likewise the number of errors so far. +
    • +
    • + integer getTestCaseCount() +
      + is the total number of test cases in the test run. + This includes the grouping tests themselves. +
    • +
    • + integer getTestCaseProgress() +
      + is the number of test cases completed so far. +
    • +
    + One simple modification is to get the HtmlReporter to display + the passes as well as the failures and errors... + +class ShowPasses extends HtmlReporter { + + function paintPass($message) { + parent::paintPass($message); + print "&<span class=\"pass\">Pass</span>: "; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print implode("-&gt;", $breadcrumb); + print "-&gt;$message<br />\n"; + } + + function _getCss() { + return parent::_getCss() . ' .pass { color: green; }'; + } +} + +
    + + One method that was glossed over was the makeDry() + method. + If you run this method, with no parameters, on the reporter + before the test suite is run no actual test methods + will be called. + You will still get the events of entering and leaving the + test methods and test cases, but no passes or failures etc, + because the test code will not actually be executed. + + + The reason for this is to allow for more sophistcated + GUI displays that allow the selection of individual test + cases. + In order to build a list of possible tests they need a + report on the test structure for drawing, say a tree view + of the test suite. + With a reporter set to dry run that just sends drawing events + this is easily accomplished. + +
    + +Extending the reporter + + Rather than simply modifying the existing display, you might want to + produce a whole new HTML look, or even generate text or XML. + Rather than override every method in + HtmlReporter we can take one + step up the class hiearchy to SimpleReporter + in the simple_test.php source file. + + + A do nothing display, a blank canvas for your own creation, would + be... + +require_once('simpletest/simple_test.php'); + +class MyDisplay extends SimpleReporter { + + function paintHeader($test_name) { + } + + function paintFooter($test_name) { + } + + function paintStart($test_name, $size) { + parent::paintStart($test_name, $size); + } + + function paintEnd($test_name, $size) { + parent::paintEnd($test_name, $size); + } + + function paintPass($message) { + parent::paintPass($message); + } + + function paintFail($message) { + parent::paintFail($message); + } +} + + No output would come from this class until you add it. + + + +The command line reporter + + SimpleTest also ships with a minimal command line reporter. + The interface mimics JUnit to some extent, but paints the + failure messages as they arrive. + To use the command line reporter simply substitute it + for the HTML version... + +<?php + require_once('simpletest/unit_tester.php'); + require_once('simpletest/reporter.php'); + + $test = &new GroupTest('File test'); + $test->addTestFile('tests/file_test.php'); + $test->run(new TextReporter()); +?> + + Then invoke the test suite from the command line... +
    +php file_test.php
    +
    + You will need the command line version of PHP installed + of course. + A passing test suite looks like this... +
    +File test
    +OK
    +Test cases run: 1/1, Failures: 0, Exceptions: 0
    +
    + A failure triggers a display like this... +
    +File test
    +1) True assertion failed.
    +	in createnewfile
    +FAILURES!!!
    +Test cases run: 1/1, Failures: 1, Exceptions: 0
    +
    +
    + + One of the main reasons for using a command line driven + test suite is of using the tester as part of some automated + process. + To function properly in shell scripts the test script should + return a non-zero exit code on failure. + If a test suite fails the value false + is returned from the SimpleTest::run() + method. + We can use that result to exit the script with the desired return + code... + +<?php + require_once('simpletest/unit_tester.php'); + require_once('simpletest/reporter.php'); + + $test = &new GroupTest('File test'); + $test->addTestFile('tests/file_test.php'); + exit ($test->run(new TextReporter()) ? 0 : 1); +?> + + Of course we don't really want to create two test scripts, + a command line one and a web browser one, for each test suite. + The command line reporter includes a method to sniff out the + run time environment... + +<?php + require_once('simpletest/unit_tester.php'); + require_once('simpletest/reporter.php'); + + $test = &new GroupTest('File test'); + $test->addTestFile('tests/file_test.php'); + if (TextReporter::inCli()) { + exit ($test->run(new TextReporter()) ? 0 : 1); + } + $test->run(new HtmlReporter()); +?> + + This is the form used within SimpleTest itself. + +
    + +Remote testing + + SimpleTest ships with an XmlReporter class + used for internal communication. + When run the output looks like... +
    +<?xml version="1.0"?>
    +<run>
    +  <group size="4">
    +    <name>Remote tests</name>
    +    <group size="4">
    +      <name>Visual test with 48 passes, 48 fails and 4 exceptions</name>
    +      <case>
    +        <name>testofunittestcaseoutput</name>
    +        <test>
    +          <name>testofresults</name>
    +          <pass>This assertion passed</pass>
    +          <fail>This assertion failed</fail>
    +        </test>
    +        <test>
    +          ...
    +        </test>
    +      </case>
    +    </group>
    +  </group>
    +</run>
    +
    + You can make use of this format with the parser + supplied as part of SimpleTest itself. + This is called SimpleTestXmlParser and + resides in xml.php within the SimpleTest package... + +<?php + require_once('simpletest/xml.php'); + + ... + $parser = &new SimpleTestXmlParser(new HtmlReporter()); + $parser->parse($test_output); +?> + + The $test_output should be the XML format + from the XML reporter, and could come from say a command + line run of a test case. + The parser sends events to the reporter just like any + other test run. + There are some odd occasions where this is actually useful. +
    + + A problem with large test suites is thet they can exhaust + the default 8Mb memory limit on a PHP process. + By having the test groups output in XML and run in + separate processes, the output can be reparsed to + aggregate the results into a much smaller footprint top level + test. + + + Because the XML output can come from anywhere, this opens + up the possibility of aggregating test runs from remote + servers. + A test case already exists to do this within the SimpleTest + framework, but it is currently experimental... + +<?php + require_once('../remote.php'); + require_once('../reporter.php'); + + $test_url = ...; + $dry_url = ...; + + $test = &new GroupTest('Remote tests'); + $test->addTestCase(new RemoteTestCase($test_url, $dry_url)); + $test->run(new HtmlReporter()); +?> + + The RemoteTestCase takes the actual location + of the test runner, basically a web page in XML format. + It also takes the URL of a reporter set to do a dry run. + This is so that progress can be reported upward correctly. + The RemoteTestCase can be added to test suites + just like any other group test. + +
    +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/ServerStubs.pkg b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/ServerStubs.pkg new file mode 100644 index 0000000..96073b5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/ServerStubs.pkg @@ -0,0 +1,323 @@ + + + +Server stubs + + + {@toc} + + +What are server stubs? + + This was originally a pattern named by Robert Binder (Testing + object-oriented systems: models, patterns, and tools, + Addison-Wesley) in 1999. + A server stub is a simulation of an object or component. + It should exactly replace a component in a system for test + or prototyping purposes, but remain lightweight. + This allows tests to run more quickly, or if the simulated + class has not been written, to run at all. + + + +Creating server stubs + + All we need is an existing class, say a database connection + that looks like this... + +class DatabaseConnection { + function DatabaseConnection() { + } + + function query() { + } + + function selectQuery() { + } +} + + The class does not need to have been implemented yet. + To create a stub version of the class we need to include the + server stub library and run the generator... + +require_once('simpletest/mock_objects.php'); +require_once('database_connection.php'); +Stub::generate('DatabaseConnection'); + + This generates a clone class called + StubDatabaseConnection. + We can now create instances of the new class within + our prototype script... + +require_once('simpletest/mock_objects.php'); +require_once('database_connection.php'); +Stub::generate('DatabaseConnection'); + +$connection = new StubDatabaseConnection(); + + + The stub version of a class has all the methods of the original + so that operations like + $connection->query() are still + legal. + The return value will be null, + but we can change that with... + +$connection->setReturnValue('query', 37) + + Now every time we call + $connection->query() we get + the result of 37. + We can set the return value to anything, say a hash of + imaginary database results or a list of persistent objects. + Parameters are irrelevant here, we always get the same + values back each time once they have been set up this way. + That may not sound like a convincing replica of a + database connection, but for the half a dozen lines of + a test method it is usually all you need. + + + +Simulation patterns + + Things aren't always that simple though. + One common problem is iterators, where constantly returning + the same value could cause an endless loop in the object + being tested. + For these we need to set up sequences of values. + Let's say we have a simple iterator that looks like this... + +class Iterator { + function Iterator() { + } + + function next() { + } +} + + This is about the simplest iterator you could have. + Assuming that this iterator only returns text until it + reaches the end, when it returns false, we can simulate it + with... + +Stub::generate('Iterator'); + +$iterator = new StubIterator(); +$iterator->setReturnValue('next', false); +$iterator->setReturnValueAt(0, 'next', 'First string'); +$iterator->setReturnValueAt(1, 'next', 'Second string'); + + When next() is called on the + stub iterator it will first return "First string", + on the second call "Second string" will be returned + and on any other call false will + be returned. + The sequenced return values take precedence over the constant + return value. + The constant one is a kind of default if you like. + + + Another tricky situation is an overloaded + get() operation. + An example of this is an information holder with name/value pairs. + Say we have a configuration class like... + +class Configuration { + function Configuration() { + } + + function getValue($key) { + } +} + + This is a classic situation for using stub objects as + actual configuration will vary from machine to machine, + hardly helping the reliability of our tests if we use it + directly. + The problem though is that all the data comes through the + getValue() method and yet + we want different results for different keys. + Luckily the stubs have a filter system... + +Stub::generate('Configuration'); + +$config = &new StubConfiguration(); +$config->setReturnValue('getValue', 'primary', array('db_host')); +$config->setReturnValue('getValue', 'admin', array('db_user')); +$config->setReturnValue('getValue', 'secret', array('db_password')); + + The extra parameter is a list of arguments to attempt + to match. + In this case we are trying to match only one argument which + is the look up key. + Now when the server stub has the + getValue() method invoked + like this... + +$config->getValue('db_user'); + + ...it will return "admin". + It finds this by attempting to match the calling arguments + to its list of returns one after another until + a complete match is found. + + + You can set a default argument argument like so... + +$config->setReturnValue('getValue', false, array('*')); + + This is not the same as setting the return value without + any argument requirements like this... + +$config->setReturnValue('getValue', false); + + In the first case it will accept any single argument, + but exactly one is required. + In the second case any number of arguments will do and + it acts as a catchall after all other matches. + Note that if we add further single parameter options after + the wildcard in the first case, they will be ignored as the wildcard + will match first. + With complex parameter lists the ordering could be important + or else desired matches could be masked by earlier wildcard + ones. + Declare the most specific matches first if you are not sure. + + + There are times when you want a specific object to be + dished out by the stub rather than just a copy. + The PHP copy semantics force us to use a different method + for this. + You might be simulating a container for example... + +class Thing { +} + +class Vector { + function Vector() { + } + + function get($index) { + } +} + + In this case you can set a reference into the stub's + return list... + +Stub::generate('Vector'); + +$thing = new Thing(); +$vector = &new StubVector(); +$vector->setReturnReference('get', $thing, array(12)); + + With this arrangement you know that every time + $vector->get(12) is + called it will return the same + $thing each time. + + + These three factors, timing, parameters and whether to copy, + can be combined orthogonally. + For example... + +$complex = &new StubComplexThing(); +$stuff = new Stuff(); +$complex->setReturnReferenceAt(3, 'get', $stuff, array('*', 1)); + + This will return the $stuff only on the third + call and only if two parameters were set the second of + which must be the integer 1. + That should cover most simple prototyping situations. + + + A final tricky case is one object creating another, known + as a factory pattern. + Suppose that on a successful query to our imaginary + database, a result set is returned as an iterator with + each call to next() giving + one row until false. + This sounds like a simulation nightmare, but in fact it can all + be stubbed using the mechanics above. + + + Here's how... + +Stub::generate('DatabaseConnection'); +Stub::generate('ResultIterator'); + +class DatabaseTest extends UnitTestCase { + + function testUserFinder() { + $result = &new StubResultIterator(); + $result->setReturnValue('next', false); + $result->setReturnValueAt(0, 'next', array(1, 'tom')); + $result->setReturnValueAt(1, 'next', array(3, 'dick')); + $result->setReturnValueAt(2, 'next', array(6, 'harry')); + + $connection = &new StubDatabaseConnection(); + $connection->setReturnValue('query', false); + $connection->setReturnReference( + 'query', + $result, + array('select id, name from users')); + + $finder = &new UserFinder($connection); + $this->assertIdentical( + $finder->findNames(), + array('tom', 'dick', 'harry')); + } +} + + Now only if our + $connection is called with the correct + query() will the + $result be returned that is + itself exhausted after the third call to next(). + This should be enough + information for our UserFinder class, + the class actually + being tested here, to come up with goods. + A very precise test and not a real database in sight. + + + +Stub creation options + + There are some additional options when creating stubs. + At the generation stage we can change the class name... + +Stub::generate('Iterator', 'MyStubIterator'); +$iterator = &new MyStubIterator(); + + + This is not very useful in itself as there would be no difference + in this class and the default except for the name. + However we can also add additional methods not found in the + original interface... + +class Iterator { +} +Stub::generate('Iterator', 'PrototypeIterator', array('next', 'isError')); +$iterator = &new PrototypeIterator(); +$iterator->setReturnValue('next', 0); + + + The next() and + isError() methods can now have + return values set just as if they existed in the original class. + + + One other esoteric way of customising the stubs is to change + the default wildcard used for parameter matching. + +Stub::generate('Connection'); +$iterator = &new StubConnection('wild'); +$iterator->setReturnValue('query', array('id' => 33), array('wild')); + + + The only reason to do this is if you genuinely wanted to test + against the literal string "*" and didn't want it + interpreted as "any". + + + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/SimpleTest.pkg b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/SimpleTest.pkg new file mode 100644 index 0000000..7def579 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/SimpleTest.pkg @@ -0,0 +1,387 @@ + + + +Overview + + + + + + Marcus Baker + + Primary Developer +{@link mailto:marcus@lastcraft.com marcus@lastcraft.com} + + + + Harry Fuecks + + Packager +{@link mailto:harryf@users.sourceforge.net harryf@users.sourceforge.net} + + + + Jason Sweat + + Documentation +{@link mailto:jsweat_php@yahoo.com jsweat_php@yahoo.com} + + + + + {@toc} + + +What is SimpleTest? + + The heart of SimpleTest is a testing framework built around + test case classes. + These are written as extensions of base test case classes, + each extended with methods that actually contain test code. + Top level test scripts then invoke the run() + methods on every one of these test cases in order. + Each test method is written to invoke various assertions that + the developer expects to be true such as + assertEqual(). + If the expectation is correct, then a successful result is dispatched to the + observing test reporter, but any failure triggers an alert + and a description of the mismatch. + + + A {@link test case.html test case} looks like this... + +<?php +class MyTestCase extends UnitTestCase { + + function testLog() { + $log = &new Log('my.log'); + $log->message('Hello'); + $this->assertTrue(file_exists('my.log')); + } +} +?> + + + + These tools are designed for the developer. + Tests are written in the PHP language itself more or less + as the application itself is built. + The advantage of using PHP itself as the testing language is that + there are no new languages to learn, testing can start straight away, + and the developer can test any part of the code. + Basically, all parts that can be accessed by the application code can also be + accessed by the test code if they are in the same language. + + + The simplest type of test case is the + {@link UnitTestCase.html UnitTestCase}. + This class of test case includes standard tests for equality, + references and pattern matching. + All these test the typical expectations of what you would + expect the result of a function or method to be. + This is by far the most common type of test in the daily + routine of development, making up about 95% of test cases. + + + The top level task of a web application though is not to + produce correct output from its methods and objects, but + to generate web pages. + The {@link WebTestCase.html WebTestCase} class tests web + pages. + It simulates a web browser requesting a page, complete with + cookies, proxies, secure connections, authentication, forms, frames and most + navigation elements. + With this type of test case, the developer can assert that + information is present in the page and that forms and + sessions are handled correctly. + + + A {@link WebTestCase.html WebTestCase} looks like this... + +<?php +class MySiteTest extends WebTestCase { + + function testHomePage() { + $this->get('http://www.my-site.com/index.php'); + $this->assertTitle('My Home Page'); + $this->clickLink('Contact'); + $this->assertTitle('Contact me'); + $this->assertWantedPattern('/Email me at/'); + } +} +?> + + + + +Feature list + + The following is a very rough outline of past and future features + and their expected point of release. + I am afraid it is liable to change without warning as meeting the + milestones rather depends on time available. + Green stuff has been coded, but not necessarily released yet. + If you have a pressing need for a green but unreleased feature + then you should check-out the code from sourceforge CVS directly. + A released feature is marked as "Done". + + + + + + + + + + + Unit test case + Core test case class and assertions + Done + + + Html display + Simplest possible display + Done + + + Autoloading of test cases + + Reading a file with test cases and loading them into a + group test automatically + + Done + + + Mock objects code generator + + Objects capable of simulating other objects removing + test dependencies + + Done + + + Server stubs + + Mocks without expectations to be used outside of test cases, + e.g. for prototyping + + Done + + + Integration of other unit testers + + The ability to read and simulate test cases from PHPUnit + and PEAR::PhpUnit + + Done + + + Web test case + Basic pattern matching of fetched pages + Done + + + HTML parsing of pages + Allows link following and title tag matching + Done + + + Partial mocks + + Mocking parts of a class for testing less than a class + or for complex simulations + + Done + + + Web cookie handling + Correct handling of cookies when fetching pages + Done + + + Following redirects + Page fetching automatically follows 300 redirects + Done + + + Form parsing + Ability to submit simple forms and read default form values + Done + + + Command line interface + Test display without the need of a web browser + Done + + + Exposure of expectation classes + Can create precise tests with mocks as well as test cases + Done + + + XML output and parsing + + Allows multi host testing and the integration of acceptance + testing extensions + + Done + + + Command line test case + Allows testing of utilities and file handling + Done + + + PHP Documentor compatibility + Fully generated class level documentation + Done + + + Browser interface + + Exposure of lower level web browser interface for more + detailed test cases + + Done + + + HTTP authentication + + Fetching protected web pages with basic authentication + only + + Done + + + Browser navigation buttons + Back, forward and retry + Done + + + SSL support + Can connect to https: pages + Done + + + Proxy support + Can connect via. common proxies + Done + + + Frames support + Handling of frames in web test cases + Done + + + Improved display + Better web GUI with tree display of test cases + 1.1 + + + Localisation + Messages abstracted and code generated from XML + 1.1 + + + File upload testing + Can simulate the input type file tag + 1.1 + + + Mocking interfaces + Can generate mock objects to interfaces as well as classes + 2.0 + + + Testing exceptions + Similar to testing PHP errors + 2.0 + + + XPath searching of elements + Can make use of HTML tidy for faster and more flexible content matching + 2.0 + + +
    FeatureDescriptionRelease
    + PHP5 migraton will start straight after the version 1.1 series, + whereupon PHP4 will no longer be supported. + SimpleTest is currently compatible with PHP5, but will not + make use of all of the new features until version 2. +
    +
    + +Web resources for testing + + Process is at least as important as tools. + The type of process that makes the heaviest use of a developer's + testing tool is of course + {@link http://www.extremeprogramming.org/ Extreme Programming}. + This is one of the + {@link http://www.agilealliance.com/articles/index Agile Methodologies} + which combine various practices to "flatten the cost curve" of software development. + More extreme still is {@link http://www.testdriven.com/modules/news/ Test Driven Development}, + where you very strictly adhere to the rule of no coding until you have a test. + If you're more of a planner or believe that experience trumps evolution, + you may prefer the + {@link http://www.therationaledge.com/content/dec_01/f_spiritOfTheRUP_pk.html RUP} approach. + I haven't tried it, but even I can see that you will need test tools (see figure 9). + + + Most unit testers clone {@link http://www.junit.org/ JUnit} to some degree, + as far as the interface at least. There is a wealth of information on the + JUnit site including the + {@link http://junit.sourceforge.net/doc/faq/faq.htm FAQ} + which contains plenty of general advice on testing. + Once you get bitten by the bug you will certainly appreciate the phrase + {@link http://junit.sourceforge.net/doc/testinfected/testing.htm test infected} + coined by Eric Gamma. + If you are still reviewing which unit tester to use the main choices + are {@link http://phpunit.sourceforge.net/ PHPUnit} + and {@link http://pear.php.net/manual/en/package.php.phpunit.php Pear PHP::PHPUnit}. + They currently lack a lot of features found in + {@link http://www.lastcraft.com/simple_test.php SimpleTest}, but the PEAR + version at least has been upgraded for PHP5 and is recommended if you are porting + existing {@link http://www.junit.org/ JUnit} test cases. + + + Library writers don't seem to ship tests with their code very often + which is a shame. + Library code that includes tests can be more safely refactored and + the test code can act as additional documentation in a fairly standard + form. + This can save trawling the source code for clues when problems occour, + especially when upgrading such a library. + Libraries using SimpleTest for their unit testing include + {@link http://wact.sourceforge.net/ WACT} and + {@link http://sourceforge.net/projects/htmlsax PEAR::XML_HTMLSax}. + + + There is currently a sad lack of material on mock objects, which is a shame + as unit testing without them is a lot more work. + The {@link http://www.sidewize.com/company/mockobjects.pdf original mock objects paper} + is very Java focused, but still worth a read. + As a new technology there are plenty of discussions and debate on how to use mocks, + often on Wikis such as + {@link http://xpdeveloper.com/cgi-bin/oldwiki.cgi?MockObjects Extreme Tuesday} + or {@link http://www.mockobjects.com/wiki/MocksObjectsPaper www.mockobjects.com} + or {@link http://c2.com/cgi/wiki?MockObject the original C2 Wiki}. + Injecting mocks into a class is the main area of debate for which this + {@link http://www-106.ibm.com/developerworks/java/library/j-mocktest.html paper on IBM} + makes a good starting point. + + + There are plenty of web testing tools, but most are written in Java and + tutorials and advice are rather thin on the ground. + The only hope is to look at the documentation for + {@link http://httpunit.sourceforge.net/ HTTPUnit}, + {@link http://htmlunit.sourceforge.net/ HTMLUnit} + or {@link http://jwebunit.sourceforge.net/ JWebUnit} and hope for clues. + There are some XML driven test frameworks, but again most + require Java to run. + As SimpleTest does not support JavaScript you would probably + have to look at these tools anyway if you have highly dynamic + pages. + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/SimpleTest.pkg.ini b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/SimpleTest.pkg.ini new file mode 100644 index 0000000..695548d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/SimpleTest.pkg.ini @@ -0,0 +1,10 @@ +[Linked Tutorials] +UnitTestCase +GroupTests +ServerStubs +MockObjects +PartialMock +Reporting +Expectations +WebTester +FormTesting \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/UnitTestCase.pkg b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/UnitTestCase.pkg new file mode 100644 index 0000000..3f48c5f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/UnitTestCase.pkg @@ -0,0 +1,405 @@ + + + +Unit tester + + + {@toc} + + +Unit test cases + + The core system is a regression testing framework built around + test cases. + A sample test case looks like this... + +class FileTestCase extends UnitTestCase { +} + + If no test name is supplied when chaining the constructor then + the class name will be taken instead. + This will be the name displayed in the test results. + + + Actual tests are added as methods in the test case whose names + by default start with the string "test" and + when the test case is invoked all such methods are run in + the order that PHP introspection finds them. + As many test methods can be added as needed. + For example... + +require_once('../classes/writer.php'); + +class FileTestCase extends UnitTestCase { + function FileTestCase() { + $this->UnitTestCase('File test'); + } + + function setUp() { + @unlink('../temp/test.txt'); + } + + function tearDown() { + @unlink('../temp/test.txt'); + } + + function testCreation() { + $writer = &new FileWriter('../temp/test.txt'); + $writer->write('Hello'); + $this->assertTrue(file_exists('../temp/test.txt'), 'File created'); + } +} + + The constructor is optional and usually omitted. + Without a name, the class name is taken as the name of the test case. + + + Our only test method at the moment is testCreation() + where we check that a file has been created by our + Writer object. + We could have put the unlink() + code into this method as well, but by placing it in + setUp() and + tearDown() we can use it with + other test methods that we add. + + + The setUp() method is run + just before each and every test method. + tearDown() is run just after + each and every test method. + + + You can place some test case set up into the constructor to + be run once for all the methods in the test case, but + you risk test inteference that way. + This way is slightly slower, but it is safer. + Note that if you come from a JUnit background this will not + be the behaviour you are used to. + JUnit surprisingly reinstantiates the test case for each test + method to prevent such interference. + SimpleTest requires the end user to use setUp(), but + supplies additional hooks for library writers. + + + The means of reporting test results (see below) are by a + visiting display class + that is notified by various assert...() + methods. + Here is the full list for the UnitTestCase + class, the default for SimpleTest... + + + + +assertTrue($x) + +Fail if $x is false + + + +assertFalse($x) + +Fail if $x is true + + + +assertNull($x) + +Fail if $x is set + + + +assertNotNull($x) + +Fail if $x not set + + + +assertIsA($x, $t) + +Fail if $x is not the class or type $t + + + +assertNotA($x, $t) + +Fail if $x is of the class or type $t + + + +assertEqual($x, $y) + +Fail if $x == $y is false + + + +assertNotEqual($x, $y) + +Fail if $x == $y is true + + + +assertIdentical($x, $y) + +Fail if $x == $y is false or a type mismatch + + + +assertNotIdentical($x, $y) + +Fail if $x == $y is true and types match + + + +assertReference($x, $y) + +Fail unless $x and $y are the same variable + + + +assertCopy($x, $y) + +Fail if $x and $y are the same variable + + + +assertWantedPattern($p, $x) + +Fail unless the regex $p matches $x + + + +assertNoUnwantedPattern($p, $x) + +Fail if the regex $p matches $x + + + +assertNoErrors() + +Fail if any PHP error occoured + + + +assertError($x) + +Fail if no PHP error or incorrect message + + + +assertErrorPattern($p) + +Fail unless the error matches the regex $p + + +
    + All assertion methods can take an optional description to + label the displayed result with. + If omitted a default message is sent instead which is usually + sufficient. + This default message can still be embedded in your own message + if you include "%s" within the string. + All the assertions return true on a pass or false on failure. +
    + + Some examples... + +$variable = null; +$this->assertNull($variable, 'Should be cleared'); + + ...will pass and normally show no message. + If you have + {@link http://www.lastcraft.com/display_subclass_tutorial.php set up the tester to display passes} + as well then the message will be displayed as is. + +$this->assertIdentical(0, false, 'Zero is not false [%s]'); + + This will fail as it performs a type + check as well as a comparison between the two values. + The "%s" part is replaced by the default + error message that would have been shown if we had not + supplied our own. + This also allows us to nest test messages. + +$a = 1; +$b = $a; +$this->assertReference($a, $b); + + Will fail as the variable $a is a copy of $b. + +$this->assertWantedPattern('/hello/i', 'Hello world'); + + This will pass as using a case insensitive match the string + hello is contained in Hello world. + +trigger_error('Disaster'); +trigger_error('Catastrophe'); +$this->assertError(); +$this->assertError('Catastrophe'); +$this->assertNoErrors(); + + This one takes some explanation as in fact they all pass! + + + PHP errors in SimpleTest are trapped and placed in a queue. + Here the first error check catches the "Disaster" + message without checking the text and passes. + This removes the error from the queue. + The next error check tests not only the existence of the error, + but also the text which here matches so another pass. + With the queue now empty the last test will pass as well. + If any unchecked errors are left at the end of a test method then + an exception will be reported in the test. + Note that SimpleTest cannot catch compile time PHP errors. + + + The test cases also have some convenience methods for debugging + code or extending the suite... + + + + +setUp() + +Runs this before each test method + + + +tearDown() + +Runs this after each test method + + + +pass() + +Sends a test pass + + + +fail() + +Sends a test failure + + + +error() + +Sends an exception event + + + +sendMessage() + +Sends a status message to those displays that support it + + + +signal($type, $payload) + +Sends a user defined message to the test reporter + + + +dump($var) + +Does a formatted print_r() for quick and dirty debugging + + + +swallowErrors() + +Clears the error queue + + +
    +
    +
    + +Extending test cases + + Of course additional test methods can be added to create + specific types of test case too so as to extend framework... + +require_once('simpletest/unit_tester.php'); + +class FileTester extends UnitTestCase { + function FileTester($name = false) { + $this->UnitTestCase($name); + } + + function assertFileExists($filename, $message = '%s') { + $this->assertTrue( + file_exists($filename), + sprintf($message, 'File [$filename] existence check')); + } +} + + Here the SimpleTest library is held in a folder called + simpletest that is local. + Substitute your own path for this. + + + This new case can be now be inherited just like + a normal test case... + +class FileTestCase extends FileTester { + + function setUp() { + @unlink('../temp/test.txt'); + } + + function tearDown() { + @unlink('../temp/test.txt'); + } + + function testCreation() { + $writer = &new FileWriter('../temp/test.txt'); + $writer->write('Hello'); + $this->assertFileExists('../temp/test.txt'); + } +} + + + + If you want a test case that does not have all of the + UnitTestCase assertions, + only your own and assertTrue(), + you need to extend the SimpleTestCase + class instead. + It is found in simple_test.php rather than + unit_tester.php. + See {@link later.html later} if you + want to incorporate other unit tester's + test cases in your test suites. + + + +Running a single test case + + You won't often run single test cases except when bashing + away at a module that is having difficulty and you don't + want to upset the main test suite. + Here is the scaffolding needed to run the a lone test case... + +<?php + require_once('simpletest/unit_tester.php'); + require_once('simpletest/reporter.php'); + require_once('../classes/writer.php'); + + class FileTestCase extends UnitTestCase { + function FileTestCase() { + $this->UnitTestCase('File test'); + } + } + + $test = &new FileTestCase(); + $test->run(new HtmlReporter()); +?> + + This script will run as is, but will output zero passes + and zero failures until test methods are added. + + +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/WebTester.pkg b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/WebTester.pkg new file mode 100644 index 0000000..1ceb5cc --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/tutorials/SimpleTest/WebTester.pkg @@ -0,0 +1,610 @@ + + + +Web tester + + + {@toc} + + +Fetching a page + + Testing classes is all very well, but PHP is predominately + a language for creating functionality within web pages. + How do we test the front end presentation role of our PHP + applications? + Well the web pages are just text, so we should be able to + examine them just like any other test data. + + + This leads to a tricky issue. + If we test at too low a level, testing for matching tags + in the page with pattern matching for example, our tests will + be brittle. + The slightest change in layout could break a large number of + tests. + If we test at too high a level, say using mock versions of a + template engine, then we lose the ability to automate some classes + of test. + For example, the interaction of forms and navigation will + have to be tested manually. + These types of test are extremely repetitive and error prone. + + + SimpleTest includes a special form of test case for the testing + of web page actions. + The WebTestCase includes facilities + for navigation, content and cookie checks and form handling. + Usage of these test cases is similar to the + {@link UnitTestCase.html UnitTestCase}... + +class TestOfLastcraft extends WebTestCase { +} + + Here we are about to test the + {@link http://www/lastcraft.com/ Last Craft} site itself. + If this test case is in a file called lastcraft_test.php + then it can be loaded in a runner script just like unit tests... + +<?php + require_once('simpletest/web_tester.php'); + require_once('simpletest/reporter.php'); + + $test = &new GroupTest('Web site tests'); + $test->addTestFile('lastcraft_test.php'); + exit ($test->run(new TextReporter()) ? 0 : 1); +?> + + I am using the text reporter here to more clearly + distinguish the web content from the test output. + + + Nothing is being tested yet. + We can fetch the home page by using the + get() method... + +class TestOfLastcraft extends WebTestCase { + + function testHomepage() { + $this->assertTrue($this->get('http://www.lastcraft.com/')); + } +} + + The get() method will + return true only if page content was successfully + loaded. + It is a simple, but crude way to check that a web page + was actually delivered by the web server. + However that content may be a 404 response and yet + our get() method will still return true. + + + Assuming that the web server for the Last Craft site is up + (sadly not always the case), we should see... +
    +Web site tests
    +OK
    +Test cases run: 1/1, Failures: 0, Exceptions: 0
    +
    + All we have really checked is that any kind of page was + returned. + We don't yet know if it was the right one. +
    +
    + +Testing page content + + To confirm that the page we think we are on is actually the + page we are on, we need to verify the page content. + +class TestOfLastcraft extends WebTestCase { + + function testHomepage() { + $this->get('http://www.lastcraft.com/'); + $this->assertWantedPattern('/why the last craft/i'); + } +} + + The page from the last fetch is held in a buffer in + the test case, so there is no need to refer to it directly. + The pattern match is always made against the buffer. + + + Here is the list of possible content assertions... + + + + +assertTitle($title) + +Pass if title is an exact match + + + +assertWantedPattern($pattern) + +A Perl pattern match against the page content + + + +assertNoUnwantedPattern($pattern) + +A Perl pattern match to not find content + + + +assertWantedText($text) + +Pass if matches visible and "alt" text + + + +assertNoUnwantedText($text) + +Pass if doesn't match visible and "alt" text + + + +assertLink($label) + +Pass if a link with this text is present + + + +assertNoLink($label) + +Pass if no link with this text is present + + + +assertLinkById($id) + +Pass if a link with this id attribute is present + + + +assertNoLinkById($id) + +Pass if no link with this id attribute is present + + + +assertField($name, $value) + +Pass if an input tag with this name has this value + + + +assertFieldById($id, $value) + +Pass if an input tag with this id has this value + + + +assertResponse($codes) + +Pass if HTTP response matches this list + + + +assertMime($types) + +Pass if MIME type is in this list + + + +assertAuthentication($protocol) + +Pass if the current challenge is this protocol + + + +assertNoAuthentication() + +Pass if there is no current challenge + + + +assertRealm($name) + +Pass if the current challenge realm matches + + + +assertHeader($header, $content) + +Pass if a header was fetched matching this value + + + +assertNoUnwantedHeader($header) + +Pass if a header was not fetched + + + +assertHeaderPattern($header, $pattern) + +Pass if a header was fetched matching this Perl regex + + + +assertCookie($name, $value) + +Pass if there is currently a matching cookie + + + +assertNoCookie($name) + +Pass if there is currently no cookie of this name + + +
    + As usual with the SimpleTest assertions, they all return + false on failure and true on pass. + They also allow an optional test message and you can embed + the original test message inside using "%s" inside + your custom message. +
    + + So now we could instead test against the title tag with... + +$this->assertTitle('The Last Craft? Web developer tutorials on PHP, Extreme programming and Object Oriented development'); + + As well as the simple HTML content checks we can check + that the MIME type is in a list of allowed types with... + +$this->assertMime(array('text/plain', 'text/html')); + + More interesting is checking the HTTP response code. + Like the MIME type, we can assert that the response code + is in a list of allowed values... + +class TestOfLastcraft extends WebTestCase { + + function testRedirects() { + $this->get('http://www.lastcraft.com/test/redirect.php'); + $this->assertResponse(200);</strong> + } +} + + Here we are checking that the fetch is successful by + allowing only a 200 HTTP response. + This test will pass, but it is not actually correct to do so. + There is no page, instead the server issues a redirect. + The WebTestCase will + automatically follow up to three such redirects. + The tests are more robust this way and we are usually + interested in the interaction with the pages rather + than their delivery. + If the redirects are of interest then this ability must + be disabled... + +class TestOfLastcraft extends WebTestCase { + + function testHomepage() { + $this->setMaximumRedirects(0); + $this->get('http://www.lastcraft.com/test/redirect.php'); + $this->assertResponse(200); + } +} + + The assertion now fails as expected... +
    +Web site tests
    +1) Expecting response in [200] got [302]
    +	in testhomepage
    +	in testoflastcraft
    +	in lastcraft_test.php
    +FAILURES!!!
    +Test cases run: 1/1, Failures: 1, Exceptions: 0
    +
    + We can modify the test to correctly assert redirects with... + +class TestOfLastcraft extends WebTestCase { + + function testHomepage() { + $this->setMaximumRedirects(0); + $this->get('http://www.lastcraft.com/test/redirect.php'); + $this->assertResponse(array(301, 302, 303, 307)); + } +} + + This now passes. +
    +
    + +Navigating a web site + + Users don't often navigate sites by typing in URLs, but by + clicking links and buttons. + Here we confirm that the contact details can be reached + from the home page... + +class TestOfLastcraft extends WebTestCase { + ... + function testContact() { + $this->get('http://www.lastcraft.com/'); + $this->clickLink('About'); + $this->assertTitle('About Last Craft'); + } +} + + The parameter is the text of the link. + + + If the target is a button rather than an anchor tag, then + clickSubmit() should be used + with the button title... + +$this->clickSubmit('Go!'); + + + + The list of navigation methods is... + + + + +getUrl() + +The current location + + + +get($url, $parameters) + +Send a GET request with these parameters + + + +post($url, $parameters) + +Send a POST request with these parameters + + + +head($url, $parameters) + +Send a HEAD request without replacing the page content + + + +retry() + +Reload the last request + + + +back() + +Like the browser back button + + + +forward() + +Like the browser forward button + + + +authenticate($name, $password) + +Retry after a challenge + + + +restart() + +Restarts the browser as if a new session + + + +getCookie($name) + +Gets the cookie value for the current context + + + +ageCookies($interval) + +Ages current cookies prior to a restart + + + +clearFrameFocus() + +Go back to treating all frames as one page + + + +clickSubmit($label) + +Click the first button with this label + + + +clickSubmitByName($name) + +Click the button with this name attribute + + + +clickSubmitById($id) + +Click the button with this ID attribute + + + +clickImage($label, $x, $y) + +Click an input tag of type image by title or alt text + + + +clickImageByName($name, $x, $y) + +Click an input tag of type image by name + + + +clickImageById($id, $x, $y) + +Click an input tag of type image by ID attribute + + + +submitFormById($id) + +Submit a form without the submit value + + + +clickLink($label, $index) + +Click an anchor by the visible label text + + + +clickLinkById($id) + +Click an anchor by the ID attribute + + + +getFrameFocus() + +The name of the currently selected frame + + + +setFrameFocusByIndex($choice) + +Focus on a frame counting from 1 + + + +setFrameFocus($name) + +Focus on a frame by name + + +
    +
    + + The parameters in the get(), post() or + head() methods are optional. + The HTTP HEAD fetch does not change the browser context, only loads + cookies. + This can be useful for when an image or stylesheet sets a cookie + for crafty robot blocking. + + + The retry(), back() and + forward() commands work as they would on + your web browser. + They use the history to retry pages. + This can be handy for checking the effect of hitting the + back button on your forms. + + + The frame methods need a little explanation. + By default a framed page is treated just like any other. + Content will be searced for throughout the entire frameset, + so clicking a link will work no matter which frame + the anchor tag is in. + You can override this behaviour by focusing on a single + frame. + If you do that, all searches and actions will apply to that + frame alone, such as authentication and retries. + If a link or button is not in a focused frame then it cannot + be clicked. + + + Testing navigation on fixed pages only tells you when you + have broken an entire script. + For highly dynamic pages, such as for bulletin boards, this can + be crucial for verifying the correctness of the application. + For most applications though, the really tricky logic is usually in + the handling of forms and sessions. + Fortunately SimpleTest includes + {@link tools for testing web forms.html tools for testing web forms} + as well. + +
    + +Modifying the request + + Although SimpleTest does not have the goal of testing networking + problems, it does include some methods to modify and debug + the requests it makes. + Here is another method list... + + + + +getTransportError() + +The last socket error + + + +showRequest() + +Dump the outgoing request + + + +showHeaders() + +Dump the incoming headers + + + +showSource() + +Dump the raw HTML page content + + + +ignoreFrames() + +Do not load framesets + + + +setCookie($name, $value) + +Set a cookie from now on + + + +addHeader($header) + +Always add this header to the request + + + +setMaximumRedirects($max) + +Stop after this many redirects + + + +setConnectionTimeout($timeout) + +Kill the connection after this time between bytes + + + +useProxy($proxy, $name, $password) + +Make requests via this proxy URL + + +
    + These methods are principally for debugging. +
    +
    +
    diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/unit_tester.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/unit_tester.php new file mode 100644 index 0000000..513f09a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/unit_tester.php @@ -0,0 +1,402 @@ +assert(new TrueExpectation(), $result, $message); + } + + /** + * Will be true on false and vice versa. False + * is the PHP definition of false, so that null, + * empty strings, zero and an empty array all count + * as false. + * @param boolean $result Pass on false. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertFalse($result, $message = '%s') { + return $this->assert(new FalseExpectation(), $result, $message); + } + + /** + * Will be true if the value is null. + * @param null $value Supposedly null value. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertNull($value, $message = '%s') { + $dumper = new SimpleDumper(); + $message = sprintf( + $message, + '[' . $dumper->describeValue($value) . '] should be null'); + return $this->assertTrue(! isset($value), $message); + } + + /** + * Will be true if the value is set. + * @param mixed $value Supposedly set value. + * @param string $message Message to display. + * @return boolean True on pass. + * @access public + */ + function assertNotNull($value, $message = '%s') { + $dumper = new SimpleDumper(); + $message = sprintf( + $message, + '[' . $dumper->describeValue($value) . '] should not be null'); + return $this->assertTrue(isset($value), $message); + } + + /** + * Type and class test. Will pass if class + * matches the type name or is a subclass or + * if not an object, but the type is correct. + * @param mixed $object Object to test. + * @param string $type Type name as string. + * @param string $message Message to display. + * @return boolean True on pass. + * @access public + */ + function assertIsA($object, $type, $message = '%s') { + return $this->assert( + new IsAExpectation($type), + $object, + $message); + } + + /** + * Type and class mismatch test. Will pass if class + * name or underling type does not match the one + * specified. + * @param mixed $object Object to test. + * @param string $type Type name as string. + * @param string $message Message to display. + * @return boolean True on pass. + * @access public + */ + function assertNotA($object, $type, $message = '%s') { + return $this->assert( + new NotAExpectation($type), + $object, + $message); + } + + /** + * Will trigger a pass if the two parameters have + * the same value only. Otherwise a fail. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertEqual($first, $second, $message = '%s') { + return $this->assert( + new EqualExpectation($first), + $second, + $message); + } + + /** + * Will trigger a pass if the two parameters have + * a different value. Otherwise a fail. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertNotEqual($first, $second, $message = '%s') { + return $this->assert( + new NotEqualExpectation($first), + $second, + $message); + } + + /** + * Will trigger a pass if the if the first parameter + * is near enough to the second by the margin. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param mixed $margin Fuzziness of match. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertWithinMargin($first, $second, $margin, $message = '%s') { + return $this->assert( + new WithinMarginExpectation($first, $margin), + $second, + $message); + } + + /** + * Will trigger a pass if the two parameters differ + * by more than the margin. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param mixed $margin Fuzziness of match. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertOutsideMargin($first, $second, $margin, $message = '%s') { + return $this->assert( + new OutsideMarginExpectation($first, $margin), + $second, + $message); + } + + /** + * Will trigger a pass if the two parameters have + * the same value and same type. Otherwise a fail. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertIdentical($first, $second, $message = '%s') { + return $this->assert( + new IdenticalExpectation($first), + $second, + $message); + } + + /** + * Will trigger a pass if the two parameters have + * the different value or different type. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertNotIdentical($first, $second, $message = '%s') { + return $this->assert( + new NotIdenticalExpectation($first), + $second, + $message); + } + + /** + * Will trigger a pass if both parameters refer + * to the same object or value. Fail otherwise. + * This will cause problems testing objects under + * E_STRICT. + * TODO: Replace with expectation. + * @param mixed $first Reference to check. + * @param mixed $second Hopefully the same variable. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertReference(&$first, &$second, $message = '%s') { + $dumper = new SimpleDumper(); + $message = sprintf( + $message, + '[' . $dumper->describeValue($first) . + '] and [' . $dumper->describeValue($second) . + '] should reference the same object'); + return $this->assertTrue( + SimpleTestCompatibility::isReference($first, $second), + $message); + } + + /** + * Will trigger a pass if both parameters refer + * to the same object. Fail otherwise. This has + * the same semantics at the PHPUnit assertSame. + * That is, if values are passed in it has roughly + * the same affect as assertIdentical. + * TODO: Replace with expectation. + * @param mixed $first Object reference to check. + * @param mixed $second Hopefully the same object. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertSame($first, $second, $message = '%s') { + $dumper = new SimpleDumper(); + $message = sprintf( + $message, + '[' . $dumper->describeValue($first) . + '] and [' . $dumper->describeValue($second) . + '] should reference the same object'); + return $this->assertTrue($first === $second, $message); + } + + /** + * Will trigger a pass if both parameters refer + * to different objects. Fail otherwise. The objects + * have to be identical though. + * @param mixed $first Object reference to check. + * @param mixed $second Hopefully not the same object. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertClone($first, $second, $message = '%s') { + $dumper = new SimpleDumper(); + $message = sprintf( + $message, + '[' . $dumper->describeValue($first) . + '] and [' . $dumper->describeValue($second) . + '] should not be the same object'); + $identical = new IdenticalExpectation($first); + return $this->assertTrue( + $identical->test($second) && ! ($first === $second), + $message); + } + + /** + * Will trigger a pass if both parameters refer + * to different variables. Fail otherwise. The objects + * have to be identical references though. + * This will fail under E_STRICT with objects. Use + * assertClone() for this. + * @param mixed $first Object reference to check. + * @param mixed $second Hopefully not the same object. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertCopy(&$first, &$second, $message = "%s") { + $dumper = new SimpleDumper(); + $message = sprintf( + $message, + "[" . $dumper->describeValue($first) . + "] and [" . $dumper->describeValue($second) . + "] should not be the same object"); + return $this->assertFalse( + SimpleTestCompatibility::isReference($first, $second), + $message); + } + + /** + * Will trigger a pass if the Perl regex pattern + * is found in the subject. Fail otherwise. + * @param string $pattern Perl regex to look for including + * the regex delimiters. + * @param string $subject String to search in. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertPattern($pattern, $subject, $message = '%s') { + return $this->assert( + new PatternExpectation($pattern), + $subject, + $message); + } + + /** + * Will trigger a pass if the perl regex pattern + * is not present in subject. Fail if found. + * @param string $pattern Perl regex to look for including + * the regex delimiters. + * @param string $subject String to search in. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertNoPattern($pattern, $subject, $message = '%s') { + return $this->assert( + new NoPatternExpectation($pattern), + $subject, + $message); + } + + /** + * Prepares for an error. If the error mismatches it + * passes through, otherwise it is swallowed. Any + * left over errors trigger failures. + * @param SimpleExpectation/string $expected The error to match. + * @param string $message Message on failure. + * @access public + */ + function expectError($expected = false, $message = '%s') { + $queue = SimpleTest::getContext()->get('SimpleErrorQueue'); + $queue->expectError($this->coerceExpectation($expected), $message); + } + + /** + * Prepares for an exception. If the error mismatches it + * passes through, otherwise it is swallowed. Any + * left over errors trigger failures. + * @param SimpleExpectation/Exception $expected The error to match. + * @param string $message Message on failure. + * @access public + */ + function expectException($expected = false, $message = '%s') { + $queue = SimpleTest::getContext()->get('SimpleExceptionTrap'); + $line = $this->getAssertionLine(); + $queue->expectException($expected, $message . $line); + } + + /** + * Creates an equality expectation if the + * object/value is not already some type + * of expectation. + * @param mixed $expected Expected value. + * @return SimpleExpectation Expectation object. + * @access private + */ + protected function coerceExpectation($expected) { + if ($expected == false) { + return new TrueExpectation(); + } + if (SimpleTestCompatibility::isA($expected, 'SimpleExpectation')) { + return $expected; + } + return new EqualExpectation( + is_string($expected) ? str_replace('%', '%%', $expected) : $expected); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/url.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/url.php new file mode 100644 index 0000000..00b0bca --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/url.php @@ -0,0 +1,550 @@ +chompCoordinates($url); + $this->setCoordinates($x, $y); + $this->scheme = $this->chompScheme($url); + if ($this->scheme === 'file') { + // Unescaped backslashes not used in directory separator context + // will get caught by this, but they should have been urlencoded + // anyway so we don't care. If this ends up being a problem, the + // host regexp must be modified to match for backslashes when + // the scheme is file. + $url = str_replace('\\', '/', $url); + } + list($this->username, $this->password) = $this->chompLogin($url); + $this->host = $this->chompHost($url); + $this->port = false; + if (preg_match('/(.*?):(.*)/', $this->host, $host_parts)) { + if ($this->scheme === 'file' && strlen($this->host) === 2) { + // DOS drive was placed in authority; promote it to path. + $url = '/' . $this->host . $url; + $this->host = false; + } else { + $this->host = $host_parts[1]; + $this->port = (integer)$host_parts[2]; + } + } + $this->path = $this->chompPath($url); + $this->request = $this->parseRequest($this->chompRequest($url)); + $this->fragment = (strncmp($url, "#", 1) == 0 ? substr($url, 1) : false); + $this->target = false; + } + + /** + * Extracts the X, Y coordinate pair from an image map. + * @param string $url URL so far. The coordinates will be + * removed. + * @return array X, Y as a pair of integers. + * @access private + */ + protected function chompCoordinates(&$url) { + if (preg_match('/(.*)\?(\d+),(\d+)$/', $url, $matches)) { + $url = $matches[1]; + return array((integer)$matches[2], (integer)$matches[3]); + } + return array(false, false); + } + + /** + * Extracts the scheme part of an incoming URL. + * @param string $url URL so far. The scheme will be + * removed. + * @return string Scheme part or false. + * @access private + */ + protected function chompScheme(&$url) { + if (preg_match('#^([^/:]*):(//)(.*)#', $url, $matches)) { + $url = $matches[2] . $matches[3]; + return $matches[1]; + } + return false; + } + + /** + * Extracts the username and password from the + * incoming URL. The // prefix will be reattached + * to the URL after the doublet is extracted. + * @param string $url URL so far. The username and + * password are removed. + * @return array Two item list of username and + * password. Will urldecode() them. + * @access private + */ + protected function chompLogin(&$url) { + $prefix = ''; + if (preg_match('#^(//)(.*)#', $url, $matches)) { + $prefix = $matches[1]; + $url = $matches[2]; + } + if (preg_match('#^([^/]*)@(.*)#', $url, $matches)) { + $url = $prefix . $matches[2]; + $parts = split(":", $matches[1]); + return array( + urldecode($parts[0]), + isset($parts[1]) ? urldecode($parts[1]) : false); + } + $url = $prefix . $url; + return array(false, false); + } + + /** + * Extracts the host part of an incoming URL. + * Includes the port number part. Will extract + * the host if it starts with // or it has + * a top level domain or it has at least two + * dots. + * @param string $url URL so far. The host will be + * removed. + * @return string Host part guess or false. + * @access private + */ + protected function chompHost(&$url) { + if (preg_match('!^(//)(.*?)(/.*|\?.*|#.*|$)!', $url, $matches)) { + $url = $matches[3]; + return $matches[2]; + } + if (preg_match('!(.*?)(\.\./|\./|/|\?|#|$)(.*)!', $url, $matches)) { + $tlds = SimpleUrl::getAllTopLevelDomains(); + if (preg_match('/[a-z0-9\-]+\.(' . $tlds . ')/i', $matches[1])) { + $url = $matches[2] . $matches[3]; + return $matches[1]; + } elseif (preg_match('/[a-z0-9\-]+\.[a-z0-9\-]+\.[a-z0-9\-]+/i', $matches[1])) { + $url = $matches[2] . $matches[3]; + return $matches[1]; + } + } + return false; + } + + /** + * Extracts the path information from the incoming + * URL. Strips this path from the URL. + * @param string $url URL so far. The host will be + * removed. + * @return string Path part or '/'. + * @access private + */ + protected function chompPath(&$url) { + if (preg_match('/(.*?)(\?|#|$)(.*)/', $url, $matches)) { + $url = $matches[2] . $matches[3]; + return ($matches[1] ? $matches[1] : ''); + } + return ''; + } + + /** + * Strips off the request data. + * @param string $url URL so far. The request will be + * removed. + * @return string Raw request part. + * @access private + */ + protected function chompRequest(&$url) { + if (preg_match('/\?(.*?)(#|$)(.*)/', $url, $matches)) { + $url = $matches[2] . $matches[3]; + return $matches[1]; + } + return ''; + } + + /** + * Breaks the request down into an object. + * @param string $raw Raw request. + * @return SimpleFormEncoding Parsed data. + * @access private + */ + protected function parseRequest($raw) { + $this->raw = $raw; + $request = new SimpleGetEncoding(); + foreach (split("&", $raw) as $pair) { + if (preg_match('/(.*?)=(.*)/', $pair, $matches)) { + $request->add($matches[1], urldecode($matches[2])); + } elseif ($pair) { + $request->add($pair, ''); + } + } + return $request; + } + + /** + * Accessor for protocol part. + * @param string $default Value to use if not present. + * @return string Scheme name, e.g "http". + * @access public + */ + function getScheme($default = false) { + return $this->scheme ? $this->scheme : $default; + } + + /** + * Accessor for user name. + * @return string Username preceding host. + * @access public + */ + function getUsername() { + return $this->username; + } + + /** + * Accessor for password. + * @return string Password preceding host. + * @access public + */ + function getPassword() { + return $this->password; + } + + /** + * Accessor for hostname and port. + * @param string $default Value to use if not present. + * @return string Hostname only. + * @access public + */ + function getHost($default = false) { + return $this->host ? $this->host : $default; + } + + /** + * Accessor for top level domain. + * @return string Last part of host. + * @access public + */ + function getTld() { + $path_parts = pathinfo($this->getHost()); + return (isset($path_parts['extension']) ? $path_parts['extension'] : false); + } + + /** + * Accessor for port number. + * @return integer TCP/IP port number. + * @access public + */ + function getPort() { + return $this->port; + } + + /** + * Accessor for path. + * @return string Full path including leading slash if implied. + * @access public + */ + function getPath() { + if (! $this->path && $this->host) { + return '/'; + } + return $this->path; + } + + /** + * Accessor for page if any. This may be a + * directory name if ambiguious. + * @return Page name. + * @access public + */ + function getPage() { + if (! preg_match('/([^\/]*?)$/', $this->getPath(), $matches)) { + return false; + } + return $matches[1]; + } + + /** + * Gets the path to the page. + * @return string Path less the page. + * @access public + */ + function getBasePath() { + if (! preg_match('/(.*\/)[^\/]*?$/', $this->getPath(), $matches)) { + return false; + } + return $matches[1]; + } + + /** + * Accessor for fragment at end of URL after the "#". + * @return string Part after "#". + * @access public + */ + function getFragment() { + return $this->fragment; + } + + /** + * Sets image coordinates. Set to false to clear + * them. + * @param integer $x Horizontal position. + * @param integer $y Vertical position. + * @access public + */ + function setCoordinates($x = false, $y = false) { + if (($x === false) || ($y === false)) { + $this->x = $this->y = false; + return; + } + $this->x = (integer)$x; + $this->y = (integer)$y; + } + + /** + * Accessor for horizontal image coordinate. + * @return integer X value. + * @access public + */ + function getX() { + return $this->x; + } + + /** + * Accessor for vertical image coordinate. + * @return integer Y value. + * @access public + */ + function getY() { + return $this->y; + } + + /** + * Accessor for current request parameters + * in URL string form. Will return teh original request + * if at all possible even if it doesn't make much + * sense. + * @return string Form is string "?a=1&b=2", etc. + * @access public + */ + function getEncodedRequest() { + if ($this->raw) { + $encoded = $this->raw; + } else { + $encoded = $this->request->asUrlRequest(); + } + if ($encoded) { + return '?' . preg_replace('/^\?/', '', $encoded); + } + return ''; + } + + /** + * Adds an additional parameter to the request. + * @param string $key Name of parameter. + * @param string $value Value as string. + * @access public + */ + function addRequestParameter($key, $value) { + $this->raw = false; + $this->request->add($key, $value); + } + + /** + * Adds additional parameters to the request. + * @param hash/SimpleFormEncoding $parameters Additional + * parameters. + * @access public + */ + function addRequestParameters($parameters) { + $this->raw = false; + $this->request->merge($parameters); + } + + /** + * Clears down all parameters. + * @access public + */ + function clearRequest() { + $this->raw = false; + $this->request = new SimpleGetEncoding(); + } + + /** + * Gets the frame target if present. Although + * not strictly part of the URL specification it + * acts as similarily to the browser. + * @return boolean/string Frame name or false if none. + * @access public + */ + function getTarget() { + return $this->target; + } + + /** + * Attaches a frame target. + * @param string $frame Name of frame. + * @access public + */ + function setTarget($frame) { + $this->raw = false; + $this->target = $frame; + } + + /** + * Renders the URL back into a string. + * @return string URL in canonical form. + * @access public + */ + function asString() { + $path = $this->path; + $scheme = $identity = $host = $port = $encoded = $fragment = ''; + if ($this->username && $this->password) { + $identity = $this->username . ':' . $this->password . '@'; + } + if ($this->getHost()) { + $scheme = $this->getScheme() ? $this->getScheme() : 'http'; + $scheme .= '://'; + $host = $this->getHost(); + } elseif ($this->getScheme() === 'file') { + // Safest way; otherwise, file URLs on Windows have an extra + // leading slash. It might be possible to convert file:// + // URIs to local file paths, but that requires more research. + $scheme = 'file://'; + } + if ($this->getPort() && $this->getPort() != 80 ) { + $port = ':'.$this->getPort(); + } + + if (substr($this->path, 0, 1) == '/') { + $path = $this->normalisePath($this->path); + } + $encoded = $this->getEncodedRequest(); + $fragment = $this->getFragment() ? '#'. $this->getFragment() : ''; + $coords = $this->getX() === false ? '' : '?' . $this->getX() . ',' . $this->getY(); + return "$scheme$identity$host$port$path$encoded$fragment$coords"; + } + + /** + * Replaces unknown sections to turn a relative + * URL into an absolute one. The base URL can + * be either a string or a SimpleUrl object. + * @param string/SimpleUrl $base Base URL. + * @access public + */ + function makeAbsolute($base) { + if (! is_object($base)) { + $base = new SimpleUrl($base); + } + if ($this->getHost()) { + $scheme = $this->getScheme(); + $host = $this->getHost(); + $port = $this->getPort() ? ':' . $this->getPort() : ''; + $identity = $this->getIdentity() ? $this->getIdentity() . '@' : ''; + if (! $identity) { + $identity = $base->getIdentity() ? $base->getIdentity() . '@' : ''; + } + } else { + $scheme = $base->getScheme(); + $host = $base->getHost(); + $port = $base->getPort() ? ':' . $base->getPort() : ''; + $identity = $base->getIdentity() ? $base->getIdentity() . '@' : ''; + } + $path = $this->normalisePath($this->extractAbsolutePath($base)); + $encoded = $this->getEncodedRequest(); + $fragment = $this->getFragment() ? '#'. $this->getFragment() : ''; + $coords = $this->getX() === false ? '' : '?' . $this->getX() . ',' . $this->getY(); + return new SimpleUrl("$scheme://$identity$host$port$path$encoded$fragment$coords"); + } + + /** + * Replaces unknown sections of the path with base parts + * to return a complete absolute one. + * @param string/SimpleUrl $base Base URL. + * @param string Absolute path. + * @access private + */ + protected function extractAbsolutePath($base) { + if ($this->getHost()) { + return $this->path; + } + if (! $this->isRelativePath($this->path)) { + return $this->path; + } + if ($this->path) { + return $base->getBasePath() . $this->path; + } + return $base->getPath(); + } + + /** + * Simple test to see if a path part is relative. + * @param string $path Path to test. + * @return boolean True if starts with a "/". + * @access private + */ + protected function isRelativePath($path) { + return (substr($path, 0, 1) != '/'); + } + + /** + * Extracts the username and password for use in rendering + * a URL. + * @return string/boolean Form of username:password or false. + * @access public + */ + function getIdentity() { + if ($this->username && $this->password) { + return $this->username . ':' . $this->password; + } + return false; + } + + /** + * Replaces . and .. sections of the path. + * @param string $path Unoptimised path. + * @return string Path with dots removed if possible. + * @access public + */ + function normalisePath($path) { + $path = preg_replace('|/\./|', '/', $path); + return preg_replace('|/[^/]+/\.\./|', '/', $path); + } + + /** + * A pipe seperated list of all TLDs that result in two part + * domain names. + * @return string Pipe separated list. + * @access public + */ + static function getAllTopLevelDomains() { + return 'com|edu|net|org|gov|mil|int|biz|info|name|pro|aero|coop|museum'; + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/user_agent.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/user_agent.php new file mode 100644 index 0000000..c7f6536 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/user_agent.php @@ -0,0 +1,328 @@ +cookie_jar = new SimpleCookieJar(); + $this->authenticator = new SimpleAuthenticator(); + } + + /** + * Removes expired and temporary cookies as if + * the browser was closed and re-opened. Authorisation + * has to be obtained again as well. + * @param string/integer $date Time when session restarted. + * If omitted then all persistent + * cookies are kept. + * @access public + */ + function restart($date = false) { + $this->cookie_jar->restartSession($date); + $this->authenticator->restartSession(); + } + + /** + * Adds a header to every fetch. + * @param string $header Header line to add to every + * request until cleared. + * @access public + */ + function addHeader($header) { + $this->additional_headers[] = $header; + } + + /** + * Ages the cookies by the specified time. + * @param integer $interval Amount in seconds. + * @access public + */ + function ageCookies($interval) { + $this->cookie_jar->agePrematurely($interval); + } + + /** + * Sets an additional cookie. If a cookie has + * the same name and path it is replaced. + * @param string $name Cookie key. + * @param string $value Value of cookie. + * @param string $host Host upon which the cookie is valid. + * @param string $path Cookie path if not host wide. + * @param string $expiry Expiry date. + * @access public + */ + function setCookie($name, $value, $host = false, $path = '/', $expiry = false) { + $this->cookie_jar->setCookie($name, $value, $host, $path, $expiry); + } + + /** + * Reads the most specific cookie value from the + * browser cookies. + * @param string $host Host to search. + * @param string $path Applicable path. + * @param string $name Name of cookie to read. + * @return string False if not present, else the + * value as a string. + * @access public + */ + function getCookieValue($host, $path, $name) { + return $this->cookie_jar->getCookieValue($host, $path, $name); + } + + /** + * Reads the current cookies within the base URL. + * @param string $name Key of cookie to find. + * @param SimpleUrl $base Base URL to search from. + * @return string/boolean Null if there is no base URL, false + * if the cookie is not set. + * @access public + */ + function getBaseCookieValue($name, $base) { + if (! $base) { + return null; + } + return $this->getCookieValue($base->getHost(), $base->getPath(), $name); + } + + /** + * Switches off cookie sending and recieving. + * @access public + */ + function ignoreCookies() { + $this->cookies_enabled = false; + } + + /** + * Switches back on the cookie sending and recieving. + * @access public + */ + function useCookies() { + $this->cookies_enabled = true; + } + + /** + * Sets the socket timeout for opening a connection. + * @param integer $timeout Maximum time in seconds. + * @access public + */ + function setConnectionTimeout($timeout) { + $this->connection_timeout = $timeout; + } + + /** + * Sets the maximum number of redirects before + * a page will be loaded anyway. + * @param integer $max Most hops allowed. + * @access public + */ + function setMaximumRedirects($max) { + $this->max_redirects = $max; + } + + /** + * Sets proxy to use on all requests for when + * testing from behind a firewall. Set URL + * to false to disable. + * @param string $proxy Proxy URL. + * @param string $username Proxy username for authentication. + * @param string $password Proxy password for authentication. + * @access public + */ + function useProxy($proxy, $username, $password) { + if (! $proxy) { + $this->proxy = false; + return; + } + if ((strncmp($proxy, 'http://', 7) != 0) && (strncmp($proxy, 'https://', 8) != 0)) { + $proxy = 'http://'. $proxy; + } + $this->proxy = new SimpleUrl($proxy); + $this->proxy_username = $username; + $this->proxy_password = $password; + } + + /** + * Test to see if the redirect limit is passed. + * @param integer $redirects Count so far. + * @return boolean True if over. + * @access private + */ + protected function isTooManyRedirects($redirects) { + return ($redirects > $this->max_redirects); + } + + /** + * Sets the identity for the current realm. + * @param string $host Host to which realm applies. + * @param string $realm Full name of realm. + * @param string $username Username for realm. + * @param string $password Password for realm. + * @access public + */ + function setIdentity($host, $realm, $username, $password) { + $this->authenticator->setIdentityForRealm($host, $realm, $username, $password); + } + + /** + * Fetches a URL as a response object. Will keep trying if redirected. + * It will also collect authentication realm information. + * @param string/SimpleUrl $url Target to fetch. + * @param SimpleEncoding $encoding Additional parameters for request. + * @return SimpleHttpResponse Hopefully the target page. + * @access public + */ + function fetchResponse($url, $encoding) { + if ($encoding->getMethod() != 'POST') { + $url->addRequestParameters($encoding); + $encoding->clear(); + } + $response = $this->fetchWhileRedirected($url, $encoding); + if ($headers = $response->getHeaders()) { + if ($headers->isChallenge()) { + $this->authenticator->addRealm( + $url, + $headers->getAuthentication(), + $headers->getRealm()); + } + } + return $response; + } + + /** + * Fetches the page until no longer redirected or + * until the redirect limit runs out. + * @param SimpleUrl $url Target to fetch. + * @param SimpelFormEncoding $encoding Additional parameters for request. + * @return SimpleHttpResponse Hopefully the target page. + * @access private + */ + protected function fetchWhileRedirected($url, $encoding) { + $redirects = 0; + do { + $response = $this->fetch($url, $encoding); + if ($response->isError()) { + return $response; + } + $headers = $response->getHeaders(); + $location = new SimpleUrl($headers->getLocation()); + $url = $location->makeAbsolute($url); + if ($this->cookies_enabled) { + $headers->writeCookiesToJar($this->cookie_jar, $url); + } + if (! $headers->isRedirect()) { + break; + } + $encoding = new SimpleGetEncoding(); + } while (! $this->isTooManyRedirects(++$redirects)); + return $response; + } + + /** + * Actually make the web request. + * @param SimpleUrl $url Target to fetch. + * @param SimpleFormEncoding $encoding Additional parameters for request. + * @return SimpleHttpResponse Headers and hopefully content. + * @access protected + */ + protected function fetch($url, $encoding) { + $request = $this->createRequest($url, $encoding); + return $request->fetch($this->connection_timeout); + } + + /** + * Creates a full page request. + * @param SimpleUrl $url Target to fetch as url object. + * @param SimpleFormEncoding $encoding POST/GET parameters. + * @return SimpleHttpRequest New request. + * @access private + */ + protected function createRequest($url, $encoding) { + $request = $this->createHttpRequest($url, $encoding); + $this->addAdditionalHeaders($request); + if ($this->cookies_enabled) { + $request->readCookiesFromJar($this->cookie_jar, $url); + } + $this->authenticator->addHeaders($request, $url); + return $request; + } + + /** + * Builds the appropriate HTTP request object. + * @param SimpleUrl $url Target to fetch as url object. + * @param SimpleFormEncoding $parameters POST/GET parameters. + * @return SimpleHttpRequest New request object. + * @access protected + */ + protected function createHttpRequest($url, $encoding) { + return new SimpleHttpRequest($this->createRoute($url), $encoding); + } + + /** + * Sets up either a direct route or via a proxy. + * @param SimpleUrl $url Target to fetch as url object. + * @return SimpleRoute Route to take to fetch URL. + * @access protected + */ + protected function createRoute($url) { + if ($this->proxy) { + return new SimpleProxyRoute( + $url, + $this->proxy, + $this->proxy_username, + $this->proxy_password); + } + return new SimpleRoute($url); + } + + /** + * Adds additional manual headers. + * @param SimpleHttpRequest $request Outgoing request. + * @access private + */ + protected function addAdditionalHeaders(&$request) { + foreach ($this->additional_headers as $header) { + $request->addHeaderLine($header); + } + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/web_tester.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/web_tester.php new file mode 100644 index 0000000..9c3cb06 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/web_tester.php @@ -0,0 +1,1495 @@ +value = $value; + } + + /** + * Tests the expectation. True if it matches + * a string value or an array value in any order. + * @param mixed $compare Comparison value. False for + * an unset field. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + if ($this->value === false) { + return ($compare === false); + } + if ($this->isSingle($this->value)) { + return $this->testSingle($compare); + } + if (is_array($this->value)) { + return $this->testMultiple($compare); + } + return false; + } + + /** + * Tests for valid field comparisons with a single option. + * @param mixed $value Value to type check. + * @return boolean True if integer, string or float. + * @access private + */ + protected function isSingle($value) { + return is_string($value) || is_integer($value) || is_float($value); + } + + /** + * String comparison for simple field with a single option. + * @param mixed $compare String to test against. + * @returns boolean True if matching. + * @access private + */ + protected function testSingle($compare) { + if (is_array($compare) && count($compare) == 1) { + $compare = $compare[0]; + } + if (! $this->isSingle($compare)) { + return false; + } + return ($this->value == $compare); + } + + /** + * List comparison for multivalue field. + * @param mixed $compare List in any order to test against. + * @returns boolean True if matching. + * @access private + */ + protected function testMultiple($compare) { + if (is_string($compare)) { + $compare = array($compare); + } + if (! is_array($compare)) { + return false; + } + sort($compare); + return ($this->value === $compare); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $dumper = $this->getDumper(); + if (is_array($compare)) { + sort($compare); + } + if ($this->test($compare)) { + return "Field expectation [" . $dumper->describeValue($this->value) . "]"; + } else { + return "Field expectation [" . $dumper->describeValue($this->value) . + "] fails with [" . + $dumper->describeValue($compare) . "] " . + $dumper->describeDifference($this->value, $compare); + } + } +} + +/** + * Test for a specific HTTP header within a header block. + * @package SimpleTest + * @subpackage WebTester + */ +class HttpHeaderExpectation extends SimpleExpectation { + private $expected_header; + private $expected_value; + + /** + * Sets the field and value to compare against. + * @param string $header Case insenstive trimmed header name. + * @param mixed $value Optional value to compare. If not + * given then any value will match. If + * an expectation object then that will + * be used instead. + * @param string $message Optiona message override. Can use %s as + * a placeholder for the original message. + */ + function __construct($header, $value = false, $message = '%s') { + parent::__construct($message); + $this->expected_header = $this->normaliseHeader($header); + $this->expected_value = $value; + } + + /** + * Accessor for aggregated object. + * @return mixed Expectation set in constructor. + * @access protected + */ + protected function getExpectation() { + return $this->expected_value; + } + + /** + * Removes whitespace at ends and case variations. + * @param string $header Name of header. + * @param string Trimmed and lowecased header + * name. + * @access private + */ + protected function normaliseHeader($header) { + return strtolower(trim($header)); + } + + /** + * Tests the expectation. True if it matches + * a string value or an array value in any order. + * @param mixed $compare Raw header block to search. + * @return boolean True if header present. + * @access public + */ + function test($compare) { + return is_string($this->findHeader($compare)); + } + + /** + * Searches the incoming result. Will extract the matching + * line as text. + * @param mixed $compare Raw header block to search. + * @return string Matching header line. + * @access protected + */ + protected function findHeader($compare) { + $lines = split("\r\n", $compare); + foreach ($lines as $line) { + if ($this->testHeaderLine($line)) { + return $line; + } + } + return false; + } + + /** + * Compares a single header line against the expectation. + * @param string $line A single line to compare. + * @return boolean True if matched. + * @access private + */ + protected function testHeaderLine($line) { + if (count($parsed = split(':', $line, 2)) < 2) { + return false; + } + list($header, $value) = $parsed; + if ($this->normaliseHeader($header) != $this->expected_header) { + return false; + } + return $this->testHeaderValue($value, $this->expected_value); + } + + /** + * Tests the value part of the header. + * @param string $value Value to test. + * @param mixed $expected Value to test against. + * @return boolean True if matched. + * @access protected + */ + protected function testHeaderValue($value, $expected) { + if ($expected === false) { + return true; + } + if (SimpleExpectation::isExpectation($expected)) { + return $expected->test(trim($value)); + } + return (trim($value) == trim($expected)); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Raw header block to search. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if (SimpleExpectation::isExpectation($this->expected_value)) { + $message = $this->expected_value->overlayMessage($compare, $this->getDumper()); + } else { + $message = $this->expected_header . + ($this->expected_value ? ': ' . $this->expected_value : ''); + } + if (is_string($line = $this->findHeader($compare))) { + return "Searching for header [$message] found [$line]"; + } else { + return "Failed to find header [$message]"; + } + } +} + +/** + * Test for a specific HTTP header within a header block that + * should not be found. + * @package SimpleTest + * @subpackage WebTester + */ +class NoHttpHeaderExpectation extends HttpHeaderExpectation { + private $expected_header; + private $expected_value; + + /** + * Sets the field and value to compare against. + * @param string $unwanted Case insenstive trimmed header name. + * @param string $message Optiona message override. Can use %s as + * a placeholder for the original message. + */ + function __construct($unwanted, $message = '%s') { + parent::__construct($unwanted, false, $message); + } + + /** + * Tests that the unwanted header is not found. + * @param mixed $compare Raw header block to search. + * @return boolean True if header present. + * @access public + */ + function test($compare) { + return ($this->findHeader($compare) === false); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Raw header block to search. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + $expectation = $this->getExpectation(); + if (is_string($line = $this->findHeader($compare))) { + return "Found unwanted header [$expectation] with [$line]"; + } else { + return "Did not find unwanted header [$expectation]"; + } + } +} + +/** + * Test for a text substring. + * @package SimpleTest + * @subpackage UnitTester + */ +class TextExpectation extends SimpleExpectation { + private $substring; + + /** + * Sets the value to compare against. + * @param string $substring Text to search for. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($substring, $message = '%s') { + parent::__construct($message); + $this->substring = $substring; + } + + /** + * Accessor for the substring. + * @return string Text to match. + * @access protected + */ + protected function getSubstring() { + return $this->substring; + } + + /** + * Tests the expectation. True if the text contains the + * substring. + * @param string $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return (strpos($compare, $this->substring) !== false); + } + + /** + * Returns a human readable test message. + * @param mixed $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if ($this->test($compare)) { + return $this->describeTextMatch($this->getSubstring(), $compare); + } else { + $dumper = $this->getDumper(); + return "Text [" . $this->getSubstring() . + "] not detected in [" . + $dumper->describeValue($compare) . "]"; + } + } + + /** + * Describes a pattern match including the string + * found and it's position. + * @param string $substring Text to search for. + * @param string $subject Subject to search. + * @access protected + */ + protected function describeTextMatch($substring, $subject) { + $position = strpos($subject, $substring); + $dumper = $this->getDumper(); + return "Text [$substring] detected at character [$position] in [" . + $dumper->describeValue($subject) . "] in region [" . + $dumper->clipString($subject, 100, $position) . "]"; + } +} + +/** + * Fail if a substring is detected within the + * comparison text. + * @package SimpleTest + * @subpackage UnitTester + */ +class NoTextExpectation extends TextExpectation { + + /** + * Sets the reject pattern + * @param string $substring Text to search for. + * @param string $message Customised message on failure. + * @access public + */ + function __construct($substring, $message = '%s') { + parent::__construct($substring, $message); + } + + /** + * Tests the expectation. False if the substring appears + * in the text. + * @param string $compare Comparison value. + * @return boolean True if correct. + * @access public + */ + function test($compare) { + return ! parent::test($compare); + } + + /** + * Returns a human readable test message. + * @param string $compare Comparison value. + * @return string Description of success + * or failure. + * @access public + */ + function testMessage($compare) { + if ($this->test($compare)) { + $dumper = $this->getDumper(); + return "Text [" . $this->getSubstring() . + "] not detected in [" . + $dumper->describeValue($compare) . "]"; + } else { + return $this->describeTextMatch($this->getSubstring(), $compare); + } + } +} + +/** + * Test case for testing of web pages. Allows + * fetching of pages, parsing of HTML and + * submitting forms. + * @package SimpleTest + * @subpackage WebTester + */ +class WebTestCase extends SimpleTestCase { + private $browser; + private $ignore_errors = false; + + /** + * Creates an empty test case. Should be subclassed + * with test methods for a functional test case. + * @param string $label Name of test case. Will use + * the class name if none specified. + * @access public + */ + function __construct($label = false) { + parent::__construct($label); + } + + /** + * Announces the start of the test. + * @param string $method Test method just started. + * @access public + */ + function before($method) { + parent::before($method); + $this->setBrowser($this->createBrowser()); + } + + /** + * Announces the end of the test. Includes private clean up. + * @param string $method Test method just finished. + * @access public + */ + function after($method) { + $this->unsetBrowser(); + parent::after($method); + } + + /** + * Gets a current browser reference for setting + * special expectations or for detailed + * examination of page fetches. + * @return SimpleBrowser Current test browser object. + * @access public + */ + function getBrowser() { + return $this->browser; + } + + /** + * Gets a current browser reference for setting + * special expectations or for detailed + * examination of page fetches. + * @param SimpleBrowser $browser New test browser object. + * @access public + */ + function setBrowser($browser) { + return $this->browser = $browser; + } + + /** + * Clears the current browser reference to help the + * PHP garbage collector. + * @access public + */ + function unsetBrowser() { + unset($this->browser); + } + + /** + * Creates a new default web browser object. + * Will be cleared at the end of the test method. + * @return TestBrowser New browser. + * @access public + */ + function createBrowser() { + return new SimpleBrowser(); + } + + /** + * Gets the last response error. + * @return string Last low level HTTP error. + * @access public + */ + function getTransportError() { + return $this->browser->getTransportError(); + } + + /** + * Accessor for the currently selected URL. + * @return string Current location or false if + * no page yet fetched. + * @access public + */ + function getUrl() { + return $this->browser->getUrl(); + } + + /** + * Dumps the current request for debugging. + * @access public + */ + function showRequest() { + $this->dump($this->browser->getRequest()); + } + + /** + * Dumps the current HTTP headers for debugging. + * @access public + */ + function showHeaders() { + $this->dump($this->browser->getHeaders()); + } + + /** + * Dumps the current HTML source for debugging. + * @access public + */ + function showSource() { + $this->dump($this->browser->getContent()); + } + + /** + * Dumps the visible text only for debugging. + * @access public + */ + function showText() { + $this->dump(wordwrap($this->browser->getContentAsText(), 80)); + } + + /** + * Simulates the closing and reopening of the browser. + * Temporary cookies will be discarded and timed + * cookies will be expired if later than the + * specified time. + * @param string/integer $date Time when session restarted. + * If ommitted then all persistent + * cookies are kept. Time is either + * Cookie format string or timestamp. + * @access public + */ + function restart($date = false) { + if ($date === false) { + $date = time(); + } + $this->browser->restart($date); + } + + /** + * Moves cookie expiry times back into the past. + * Useful for testing timeouts and expiries. + * @param integer $interval Amount to age in seconds. + * @access public + */ + function ageCookies($interval) { + $this->browser->ageCookies($interval); + } + + /** + * Disables frames support. Frames will not be fetched + * and the frameset page will be used instead. + * @access public + */ + function ignoreFrames() { + $this->browser->ignoreFrames(); + } + + /** + * Switches off cookie sending and recieving. + * @access public + */ + function ignoreCookies() { + $this->browser->ignoreCookies(); + } + + /** + * Skips errors for the next request only. You might + * want to confirm that a page is unreachable for + * example. + * @access public + */ + function ignoreErrors() { + $this->ignore_errors = true; + } + + /** + * Issues a fail if there is a transport error anywhere + * in the current frameset. Only one such error is + * reported. + * @param string/boolean $result HTML or failure. + * @return string/boolean $result Passes through result. + * @access private + */ + protected function failOnError($result) { + if (! $this->ignore_errors) { + if ($error = $this->browser->getTransportError()) { + $this->fail($error); + } + } + $this->ignore_errors = false; + return $result; + } + + /** + * Adds a header to every fetch. + * @param string $header Header line to add to every + * request until cleared. + * @access public + */ + function addHeader($header) { + $this->browser->addHeader($header); + } + + /** + * Sets the maximum number of redirects before + * the web page is loaded regardless. + * @param integer $max Maximum hops. + * @access public + */ + function setMaximumRedirects($max) { + if (! $this->browser) { + trigger_error( + 'Can only set maximum redirects in a test method, setUp() or tearDown()'); + } + $this->browser->setMaximumRedirects($max); + } + + /** + * Sets the socket timeout for opening a connection and + * receiving at least one byte of information. + * @param integer $timeout Maximum time in seconds. + * @access public + */ + function setConnectionTimeout($timeout) { + $this->browser->setConnectionTimeout($timeout); + } + + /** + * Sets proxy to use on all requests for when + * testing from behind a firewall. Set URL + * to false to disable. + * @param string $proxy Proxy URL. + * @param string $username Proxy username for authentication. + * @param string $password Proxy password for authentication. + * @access public + */ + function useProxy($proxy, $username = false, $password = false) { + $this->browser->useProxy($proxy, $username, $password); + } + + /** + * Fetches a page into the page buffer. If + * there is no base for the URL then the + * current base URL is used. After the fetch + * the base URL reflects the new location. + * @param string $url URL to fetch. + * @param hash $parameters Optional additional GET data. + * @return boolean/string Raw page on success. + * @access public + */ + function get($url, $parameters = false) { + return $this->failOnError($this->browser->get($url, $parameters)); + } + + /** + * Fetches a page by POST into the page buffer. + * If there is no base for the URL then the + * current base URL is used. After the fetch + * the base URL reflects the new location. + * @param string $url URL to fetch. + * @param hash $parameters Optional additional GET data. + * @return boolean/string Raw page on success. + * @access public + */ + function post($url, $parameters = false) { + return $this->failOnError($this->browser->post($url, $parameters)); + } + + /** + * Does a HTTP HEAD fetch, fetching only the page + * headers. The current base URL is unchanged by this. + * @param string $url URL to fetch. + * @param hash $parameters Optional additional GET data. + * @return boolean True on success. + * @access public + */ + function head($url, $parameters = false) { + return $this->failOnError($this->browser->head($url, $parameters)); + } + + /** + * Equivalent to hitting the retry button on the + * browser. Will attempt to repeat the page fetch. + * @return boolean True if fetch succeeded. + * @access public + */ + function retry() { + return $this->failOnError($this->browser->retry()); + } + + /** + * Equivalent to hitting the back button on the + * browser. + * @return boolean True if history entry and + * fetch succeeded. + * @access public + */ + function back() { + return $this->failOnError($this->browser->back()); + } + + /** + * Equivalent to hitting the forward button on the + * browser. + * @return boolean True if history entry and + * fetch succeeded. + * @access public + */ + function forward() { + return $this->failOnError($this->browser->forward()); + } + + /** + * Retries a request after setting the authentication + * for the current realm. + * @param string $username Username for realm. + * @param string $password Password for realm. + * @return boolean/string HTML on successful fetch. Note + * that authentication may still have + * failed. + * @access public + */ + function authenticate($username, $password) { + return $this->failOnError( + $this->browser->authenticate($username, $password)); + } + + /** + * Gets the cookie value for the current browser context. + * @param string $name Name of cookie. + * @return string Value of cookie or false if unset. + * @access public + */ + function getCookie($name) { + return $this->browser->getCurrentCookieValue($name); + } + + /** + * Sets a cookie in the current browser. + * @param string $name Name of cookie. + * @param string $value Cookie value. + * @param string $host Host upon which the cookie is valid. + * @param string $path Cookie path if not host wide. + * @param string $expiry Expiry date. + * @access public + */ + function setCookie($name, $value, $host = false, $path = '/', $expiry = false) { + $this->browser->setCookie($name, $value, $host, $path, $expiry); + } + + /** + * Accessor for current frame focus. Will be + * false if no frame has focus. + * @return integer/string/boolean Label if any, otherwise + * the position in the frameset + * or false if none. + * @access public + */ + function getFrameFocus() { + return $this->browser->getFrameFocus(); + } + + /** + * Sets the focus by index. The integer index starts from 1. + * @param integer $choice Chosen frame. + * @return boolean True if frame exists. + * @access public + */ + function setFrameFocusByIndex($choice) { + return $this->browser->setFrameFocusByIndex($choice); + } + + /** + * Sets the focus by name. + * @param string $name Chosen frame. + * @return boolean True if frame exists. + * @access public + */ + function setFrameFocus($name) { + return $this->browser->setFrameFocus($name); + } + + /** + * Clears the frame focus. All frames will be searched + * for content. + * @access public + */ + function clearFrameFocus() { + return $this->browser->clearFrameFocus(); + } + + /** + * Clicks a visible text item. Will first try buttons, + * then links and then images. + * @param string $label Visible text or alt text. + * @return string/boolean Raw page or false. + * @access public + */ + function click($label) { + return $this->failOnError($this->browser->click($label)); + } + + /** + * Checks for a click target. + * @param string $label Visible text or alt text. + * @return boolean True if click target. + * @access public + */ + function assertClickable($label, $message = '%s') { + return $this->assertTrue( + $this->browser->isClickable($label), + sprintf($message, "Click target [$label] should exist")); + } + + /** + * Clicks the submit button by label. The owning + * form will be submitted by this. + * @param string $label Button label. An unlabeled + * button can be triggered by 'Submit'. + * @param hash $additional Additional form values. + * @return boolean/string Page on success, else false. + * @access public + */ + function clickSubmit($label = 'Submit', $additional = false) { + return $this->failOnError( + $this->browser->clickSubmit($label, $additional)); + } + + /** + * Clicks the submit button by name attribute. The owning + * form will be submitted by this. + * @param string $name Name attribute of button. + * @param hash $additional Additional form values. + * @return boolean/string Page on success. + * @access public + */ + function clickSubmitByName($name, $additional = false) { + return $this->failOnError( + $this->browser->clickSubmitByName($name, $additional)); + } + + /** + * Clicks the submit button by ID attribute. The owning + * form will be submitted by this. + * @param string $id ID attribute of button. + * @param hash $additional Additional form values. + * @return boolean/string Page on success. + * @access public + */ + function clickSubmitById($id, $additional = false) { + return $this->failOnError( + $this->browser->clickSubmitById($id, $additional)); + } + + /** + * Checks for a valid button label. + * @param string $label Visible text. + * @return boolean True if click target. + * @access public + */ + function assertSubmit($label, $message = '%s') { + return $this->assertTrue( + $this->browser->isSubmit($label), + sprintf($message, "Submit button [$label] should exist")); + } + + /** + * Clicks the submit image by some kind of label. Usually + * the alt tag or the nearest equivalent. The owning + * form will be submitted by this. Clicking outside of + * the boundary of the coordinates will result in + * a failure. + * @param string $label Alt attribute of button. + * @param integer $x X-coordinate of imaginary click. + * @param integer $y Y-coordinate of imaginary click. + * @param hash $additional Additional form values. + * @return boolean/string Page on success. + * @access public + */ + function clickImage($label, $x = 1, $y = 1, $additional = false) { + return $this->failOnError( + $this->browser->clickImage($label, $x, $y, $additional)); + } + + /** + * Clicks the submit image by the name. Usually + * the alt tag or the nearest equivalent. The owning + * form will be submitted by this. Clicking outside of + * the boundary of the coordinates will result in + * a failure. + * @param string $name Name attribute of button. + * @param integer $x X-coordinate of imaginary click. + * @param integer $y Y-coordinate of imaginary click. + * @param hash $additional Additional form values. + * @return boolean/string Page on success. + * @access public + */ + function clickImageByName($name, $x = 1, $y = 1, $additional = false) { + return $this->failOnError( + $this->browser->clickImageByName($name, $x, $y, $additional)); + } + + /** + * Clicks the submit image by ID attribute. The owning + * form will be submitted by this. Clicking outside of + * the boundary of the coordinates will result in + * a failure. + * @param integer/string $id ID attribute of button. + * @param integer $x X-coordinate of imaginary click. + * @param integer $y Y-coordinate of imaginary click. + * @param hash $additional Additional form values. + * @return boolean/string Page on success. + * @access public + */ + function clickImageById($id, $x = 1, $y = 1, $additional = false) { + return $this->failOnError( + $this->browser->clickImageById($id, $x, $y, $additional)); + } + + /** + * Checks for a valid image with atht alt text or title. + * @param string $label Visible text. + * @return boolean True if click target. + * @access public + */ + function assertImage($label, $message = '%s') { + return $this->assertTrue( + $this->browser->isImage($label), + sprintf($message, "Image with text [$label] should exist")); + } + + /** + * Submits a form by the ID. + * @param string $id Form ID. No button information + * is submitted this way. + * @return boolean/string Page on success. + * @access public + */ + function submitFormById($id) { + return $this->failOnError($this->browser->submitFormById($id)); + } + + /** + * Follows a link by name. Will click the first link + * found with this link text by default, or a later + * one if an index is given. Match is case insensitive + * with normalised space. + * @param string $label Text between the anchor tags. + * @param integer $index Link position counting from zero. + * @return boolean/string Page on success. + * @access public + */ + function clickLink($label, $index = 0) { + return $this->failOnError($this->browser->clickLink($label, $index)); + } + + /** + * Follows a link by id attribute. + * @param string $id ID attribute value. + * @return boolean/string Page on success. + * @access public + */ + function clickLinkById($id) { + return $this->failOnError($this->browser->clickLinkById($id)); + } + + /** + * Tests for the presence of a link label. Match is + * case insensitive with normalised space. + * @param string $label Text between the anchor tags. + * @param mixed $expected Expected URL or expectation object. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if link present. + * @access public + */ + function assertLink($label, $expected = true, $message = '%s') { + $url = $this->browser->getLink($label); + if ($expected === true || ($expected !== true && $url === false)) { + return $this->assertTrue($url !== false, sprintf($message, "Link [$label] should exist")); + } + if (! SimpleExpectation::isExpectation($expected)) { + $expected = new IdenticalExpectation($expected); + } + return $this->assert($expected, $url->asString(), sprintf($message, "Link [$label] should match")); + } + + /** + * Tests for the non-presence of a link label. Match is + * case insensitive with normalised space. + * @param string/integer $label Text between the anchor tags + * or ID attribute. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if link missing. + * @access public + */ + function assertNoLink($label, $message = '%s') { + return $this->assertTrue( + $this->browser->getLink($label) === false, + sprintf($message, "Link [$label] should not exist")); + } + + /** + * Tests for the presence of a link id attribute. + * @param string $id Id attribute value. + * @param mixed $expected Expected URL or expectation object. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if link present. + * @access public + */ + function assertLinkById($id, $expected = true, $message = '%s') { + $url = $this->browser->getLinkById($id); + if ($expected === true) { + return $this->assertTrue($url !== false, sprintf($message, "Link ID [$id] should exist")); + } + if (! SimpleExpectation::isExpectation($expected)) { + $expected = new IdenticalExpectation($expected); + } + return $this->assert($expected, $url->asString(), sprintf($message, "Link ID [$id] should match")); + } + + /** + * Tests for the non-presence of a link label. Match is + * case insensitive with normalised space. + * @param string $id Id attribute value. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if link missing. + * @access public + */ + function assertNoLinkById($id, $message = '%s') { + return $this->assertTrue( + $this->browser->getLinkById($id) === false, + sprintf($message, "Link ID [$id] should not exist")); + } + + /** + * Sets all form fields with that label, or name if there + * is no label attached. + * @param string $name Name of field in forms. + * @param string $value New value of field. + * @return boolean True if field exists, otherwise false. + * @access public + */ + function setField($label, $value, $position=false) { + return $this->browser->setField($label, $value, $position); + } + + /** + * Sets all form fields with that name. + * @param string $name Name of field in forms. + * @param string $value New value of field. + * @return boolean True if field exists, otherwise false. + * @access public + */ + function setFieldByName($name, $value, $position=false) { + return $this->browser->setFieldByName($name, $value, $position); + } + + /** + * Sets all form fields with that id. + * @param string/integer $id Id of field in forms. + * @param string $value New value of field. + * @return boolean True if field exists, otherwise false. + * @access public + */ + function setFieldById($id, $value) { + return $this->browser->setFieldById($id, $value); + } + + /** + * Confirms that the form element is currently set + * to the expected value. A missing form will always + * fail. If no value is given then only the existence + * of the field is checked. + * @param string $name Name of field in forms. + * @param mixed $expected Expected string/array value or + * false for unset fields. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if pass. + * @access public + */ + function assertField($label, $expected = true, $message = '%s') { + $value = $this->browser->getField($label); + return $this->assertFieldValue($label, $value, $expected, $message); + } + + /** + * Confirms that the form element is currently set + * to the expected value. A missing form element will always + * fail. If no value is given then only the existence + * of the field is checked. + * @param string $name Name of field in forms. + * @param mixed $expected Expected string/array value or + * false for unset fields. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if pass. + * @access public + */ + function assertFieldByName($name, $expected = true, $message = '%s') { + $value = $this->browser->getFieldByName($name); + return $this->assertFieldValue($name, $value, $expected, $message); + } + + /** + * Confirms that the form element is currently set + * to the expected value. A missing form will always + * fail. If no ID is given then only the existence + * of the field is checked. + * @param string/integer $id Name of field in forms. + * @param mixed $expected Expected string/array value or + * false for unset fields. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if pass. + * @access public + */ + function assertFieldById($id, $expected = true, $message = '%s') { + $value = $this->browser->getFieldById($id); + return $this->assertFieldValue($id, $value, $expected, $message); + } + + /** + * Tests the field value against the expectation. + * @param string $identifier Name, ID or label. + * @param mixed $value Current field value. + * @param mixed $expected Expected value to match. + * @param string $message Failure message. + * @return boolean True if pass + * @access protected + */ + protected function assertFieldValue($identifier, $value, $expected, $message) { + if ($expected === true) { + return $this->assertTrue( + isset($value), + sprintf($message, "Field [$identifier] should exist")); + } + if (! SimpleExpectation::isExpectation($expected)) { + $identifier = str_replace('%', '%%', $identifier); + $expected = new FieldExpectation( + $expected, + "Field [$identifier] should match with [%s]"); + } + return $this->assert($expected, $value, $message); + } + + /** + * Checks the response code against a list + * of possible values. + * @param array $responses Possible responses for a pass. + * @param string $message Message to display. Default + * can be embedded with %s. + * @return boolean True if pass. + * @access public + */ + function assertResponse($responses, $message = '%s') { + $responses = (is_array($responses) ? $responses : array($responses)); + $code = $this->browser->getResponseCode(); + $message = sprintf($message, "Expecting response in [" . + implode(", ", $responses) . "] got [$code]"); + return $this->assertTrue(in_array($code, $responses), $message); + } + + /** + * Checks the mime type against a list + * of possible values. + * @param array $types Possible mime types for a pass. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertMime($types, $message = '%s') { + $types = (is_array($types) ? $types : array($types)); + $type = $this->browser->getMimeType(); + $message = sprintf($message, "Expecting mime type in [" . + implode(", ", $types) . "] got [$type]"); + return $this->assertTrue(in_array($type, $types), $message); + } + + /** + * Attempt to match the authentication type within + * the security realm we are currently matching. + * @param string $authentication Usually basic. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertAuthentication($authentication = false, $message = '%s') { + if (! $authentication) { + $message = sprintf($message, "Expected any authentication type, got [" . + $this->browser->getAuthentication() . "]"); + return $this->assertTrue( + $this->browser->getAuthentication(), + $message); + } else { + $message = sprintf($message, "Expected authentication [$authentication] got [" . + $this->browser->getAuthentication() . "]"); + return $this->assertTrue( + strtolower($this->browser->getAuthentication()) == strtolower($authentication), + $message); + } + } + + /** + * Checks that no authentication is necessary to view + * the desired page. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoAuthentication($message = '%s') { + $message = sprintf($message, "Expected no authentication type, got [" . + $this->browser->getAuthentication() . "]"); + return $this->assertFalse($this->browser->getAuthentication(), $message); + } + + /** + * Attempts to match the current security realm. + * @param string $realm Name of security realm. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertRealm($realm, $message = '%s') { + if (! SimpleExpectation::isExpectation($realm)) { + $realm = new EqualExpectation($realm); + } + return $this->assert( + $realm, + $this->browser->getRealm(), + "Expected realm -> $message"); + } + + /** + * Checks each header line for the required value. If no + * value is given then only an existence check is made. + * @param string $header Case insensitive header name. + * @param mixed $value Case sensitive trimmed string to + * match against. An expectation object + * can be used for pattern matching. + * @return boolean True if pass. + * @access public + */ + function assertHeader($header, $value = false, $message = '%s') { + return $this->assert( + new HttpHeaderExpectation($header, $value), + $this->browser->getHeaders(), + $message); + } + + /** + * Confirms that the header type has not been received. + * Only the landing page is checked. If you want to check + * redirect pages, then you should limit redirects so + * as to capture the page you want. + * @param string $header Case insensitive header name. + * @return boolean True if pass. + * @access public + */ + function assertNoHeader($header, $message = '%s') { + return $this->assert( + new NoHttpHeaderExpectation($header), + $this->browser->getHeaders(), + $message); + } + + /** + * Tests the text between the title tags. + * @param string/SimpleExpectation $title Expected title. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertTitle($title = false, $message = '%s') { + if (! SimpleExpectation::isExpectation($title)) { + $title = new EqualExpectation($title); + } + return $this->assert($title, $this->browser->getTitle(), $message); + } + + /** + * Will trigger a pass if the text is found in the plain + * text form of the page. + * @param string $text Text to look for. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertText($text, $message = '%s') { + return $this->assert( + new TextExpectation($text), + $this->browser->getContentAsText(), + $message); + } + + /** + * Will trigger a pass if the text is not found in the plain + * text form of the page. + * @param string $text Text to look for. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoText($text, $message = '%s') { + return $this->assert( + new NoTextExpectation($text), + $this->browser->getContentAsText(), + $message); + } + + /** + * Will trigger a pass if the Perl regex pattern + * is found in the raw content. + * @param string $pattern Perl regex to look for including + * the regex delimiters. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertPattern($pattern, $message = '%s') { + return $this->assert( + new PatternExpectation($pattern), + $this->browser->getContent(), + $message); + } + + /** + * Will trigger a pass if the perl regex pattern + * is not present in raw content. + * @param string $pattern Perl regex to look for including + * the regex delimiters. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoPattern($pattern, $message = '%s') { + return $this->assert( + new NoPatternExpectation($pattern), + $this->browser->getContent(), + $message); + } + + /** + * Checks that a cookie is set for the current page + * and optionally checks the value. + * @param string $name Name of cookie to test. + * @param string $expected Expected value as a string or + * false if any value will do. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertCookie($name, $expected = false, $message = '%s') { + $value = $this->getCookie($name); + if (! $expected) { + return $this->assertTrue( + $value, + sprintf($message, "Expecting cookie [$name]")); + } + if (! SimpleExpectation::isExpectation($expected)) { + $expected = new EqualExpectation($expected); + } + return $this->assert($expected, $value, "Expecting cookie [$name] -> $message"); + } + + /** + * Checks that no cookie is present or that it has + * been successfully cleared. + * @param string $name Name of cookie to test. + * @param string $message Message to display. + * @return boolean True if pass. + * @access public + */ + function assertNoCookie($name, $message = '%s') { + return $this->assertTrue( + $this->getCookie($name) === null or $this->getCookie($name) === false, + sprintf($message, "Not expecting cookie [$name]")); + } + + /** + * Called from within the test methods to register + * passes and failures. + * @param boolean $result Pass on true. + * @param string $message Message to display describing + * the test state. + * @return boolean True on pass + * @access public + */ + function assertTrue($result, $message = false) { + return $this->assert(new TrueExpectation(), $result, $message); + } + + /** + * Will be true on false and vice versa. False + * is the PHP definition of false, so that null, + * empty strings, zero and an empty array all count + * as false. + * @param boolean $result Pass on false. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertFalse($result, $message = '%s') { + return $this->assert(new FalseExpectation(), $result, $message); + } + + /** + * Will trigger a pass if the two parameters have + * the same value only. Otherwise a fail. This + * is for testing hand extracted text, etc. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertEqual($first, $second, $message = '%s') { + return $this->assert( + new EqualExpectation($first), + $second, + $message); + } + + /** + * Will trigger a pass if the two parameters have + * a different value. Otherwise a fail. This + * is for testing hand extracted text, etc. + * @param mixed $first Value to compare. + * @param mixed $second Value to compare. + * @param string $message Message to display. + * @return boolean True on pass + * @access public + */ + function assertNotEqual($first, $second, $message = '%s') { + return $this->assert( + new NotEqualExpectation($first), + $second, + $message); + } + + /** + * Uses a stack trace to find the line of an assertion. + * @return string Line number of first assert* + * method embedded in format string. + * @access public + */ + function getAssertionLine() { + $trace = new SimpleStackTrace(array('assert', 'click', 'pass', 'fail')); + return $trace->traceMethod(); + } +} +?> \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/xml.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/xml.php new file mode 100644 index 0000000..54fb6f5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/xml.php @@ -0,0 +1,647 @@ +namespace = ($namespace ? $namespace . ':' : ''); + $this->indent = $indent; + } + + /** + * Calculates the pretty printing indent level + * from the current level of nesting. + * @param integer $offset Extra indenting level. + * @return string Leading space. + * @access protected + */ + protected function getIndent($offset = 0) { + return str_repeat( + $this->indent, + count($this->getTestList()) + $offset); + } + + /** + * Converts character string to parsed XML + * entities string. + * @param string text Unparsed character data. + * @return string Parsed character data. + * @access public + */ + function toParsedXml($text) { + return str_replace( + array('&', '<', '>', '"', '\''), + array('&', '<', '>', '"', '''), + $text); + } + + /** + * Paints the start of a group test. + * @param string $test_name Name of test that is starting. + * @param integer $size Number of test cases starting. + * @access public + */ + function paintGroupStart($test_name, $size) { + parent::paintGroupStart($test_name, $size); + print $this->getIndent(); + print "<" . $this->namespace . "group size=\"$size\">\n"; + print $this->getIndent(1); + print "<" . $this->namespace . "name>" . + $this->toParsedXml($test_name) . + "namespace . "name>\n"; + } + + /** + * Paints the end of a group test. + * @param string $test_name Name of test that is ending. + * @access public + */ + function paintGroupEnd($test_name) { + print $this->getIndent(); + print "namespace . "group>\n"; + parent::paintGroupEnd($test_name); + } + + /** + * Paints the start of a test case. + * @param string $test_name Name of test that is starting. + * @access public + */ + function paintCaseStart($test_name) { + parent::paintCaseStart($test_name); + print $this->getIndent(); + print "<" . $this->namespace . "case>\n"; + print $this->getIndent(1); + print "<" . $this->namespace . "name>" . + $this->toParsedXml($test_name) . + "namespace . "name>\n"; + } + + /** + * Paints the end of a test case. + * @param string $test_name Name of test that is ending. + * @access public + */ + function paintCaseEnd($test_name) { + print $this->getIndent(); + print "namespace . "case>\n"; + parent::paintCaseEnd($test_name); + } + + /** + * Paints the start of a test method. + * @param string $test_name Name of test that is starting. + * @access public + */ + function paintMethodStart($test_name) { + parent::paintMethodStart($test_name); + print $this->getIndent(); + print "<" . $this->namespace . "test>\n"; + print $this->getIndent(1); + print "<" . $this->namespace . "name>" . + $this->toParsedXml($test_name) . + "namespace . "name>\n"; + } + + /** + * Paints the end of a test method. + * @param string $test_name Name of test that is ending. + * @param integer $progress Number of test cases ending. + * @access public + */ + function paintMethodEnd($test_name) { + print $this->getIndent(); + print "namespace . "test>\n"; + parent::paintMethodEnd($test_name); + } + + /** + * Paints pass as XML. + * @param string $message Message to encode. + * @access public + */ + function paintPass($message) { + parent::paintPass($message); + print $this->getIndent(1); + print "<" . $this->namespace . "pass>"; + print $this->toParsedXml($message); + print "namespace . "pass>\n"; + } + + /** + * Paints failure as XML. + * @param string $message Message to encode. + * @access public + */ + function paintFail($message) { + parent::paintFail($message); + print $this->getIndent(1); + print "<" . $this->namespace . "fail>"; + print $this->toParsedXml($message); + print "namespace . "fail>\n"; + } + + /** + * Paints error as XML. + * @param string $message Message to encode. + * @access public + */ + function paintError($message) { + parent::paintError($message); + print $this->getIndent(1); + print "<" . $this->namespace . "exception>"; + print $this->toParsedXml($message); + print "namespace . "exception>\n"; + } + + /** + * Paints exception as XML. + * @param Exception $exception Exception to encode. + * @access public + */ + function paintException($exception) { + parent::paintException($exception); + print $this->getIndent(1); + print "<" . $this->namespace . "exception>"; + $message = 'Unexpected exception of type [' . get_class($exception) . + '] with message ['. $exception->getMessage() . + '] in ['. $exception->getFile() . + ' line ' . $exception->getLine() . ']'; + print $this->toParsedXml($message); + print "namespace . "exception>\n"; + } + + /** + * Paints the skipping message and tag. + * @param string $message Text to display in skip tag. + * @access public + */ + function paintSkip($message) { + parent::paintSkip($message); + print $this->getIndent(1); + print "<" . $this->namespace . "skip>"; + print $this->toParsedXml($message); + print "namespace . "skip>\n"; + } + + /** + * Paints a simple supplementary message. + * @param string $message Text to display. + * @access public + */ + function paintMessage($message) { + parent::paintMessage($message); + print $this->getIndent(1); + print "<" . $this->namespace . "message>"; + print $this->toParsedXml($message); + print "namespace . "message>\n"; + } + + /** + * Paints a formatted ASCII message such as a + * privateiable dump. + * @param string $message Text to display. + * @access public + */ + function paintFormattedMessage($message) { + parent::paintFormattedMessage($message); + print $this->getIndent(1); + print "<" . $this->namespace . "formatted>"; + print ""; + print "namespace . "formatted>\n"; + } + + /** + * Serialises the event object. + * @param string $type Event type as text. + * @param mixed $payload Message or object. + * @access public + */ + function paintSignal($type, $payload) { + parent::paintSignal($type, $payload); + print $this->getIndent(1); + print "<" . $this->namespace . "signal type=\"$type\">"; + print ""; + print "namespace . "signal>\n"; + } + + /** + * Paints the test document header. + * @param string $test_name First test top level + * to start. + * @access public + * @abstract + */ + function paintHeader($test_name) { + if (! SimpleReporter::inCli()) { + header('Content-type: text/xml'); + } + print "namespace) { + print " xmlns:" . $this->namespace . + "=\"www.lastcraft.com/SimpleTest/Beta3/Report\""; + } + print "?>\n"; + print "<" . $this->namespace . "run>\n"; + } + + /** + * Paints the test document footer. + * @param string $test_name The top level test. + * @access public + * @abstract + */ + function paintFooter($test_name) { + print "namespace . "run>\n"; + } +} + +/** + * Accumulator for incoming tag. Holds the + * incoming test structure information for + * later dispatch to the reporter. + * @package SimpleTest + * @subpackage UnitTester + */ +class NestingXmlTag { + private $name; + private $attributes; + + /** + * Sets the basic test information except + * the name. + * @param hash $attributes Name value pairs. + * @access public + */ + function NestingXmlTag($attributes) { + $this->name = false; + $this->attributes = $attributes; + } + + /** + * Sets the test case/method name. + * @param string $name Name of test. + * @access public + */ + function setName($name) { + $this->name = $name; + } + + /** + * Accessor for name. + * @return string Name of test. + * @access public + */ + function getName() { + return $this->name; + } + + /** + * Accessor for attributes. + * @return hash All attributes. + * @access protected + */ + protected function getAttributes() { + return $this->attributes; + } +} + +/** + * Accumulator for incoming method tag. Holds the + * incoming test structure information for + * later dispatch to the reporter. + * @package SimpleTest + * @subpackage UnitTester + */ +class NestingMethodTag extends NestingXmlTag { + + /** + * Sets the basic test information except + * the name. + * @param hash $attributes Name value pairs. + * @access public + */ + function NestingMethodTag($attributes) { + $this->NestingXmlTag($attributes); + } + + /** + * Signals the appropriate start event on the + * listener. + * @param SimpleReporter $listener Target for events. + * @access public + */ + function paintStart(&$listener) { + $listener->paintMethodStart($this->getName()); + } + + /** + * Signals the appropriate end event on the + * listener. + * @param SimpleReporter $listener Target for events. + * @access public + */ + function paintEnd(&$listener) { + $listener->paintMethodEnd($this->getName()); + } +} + +/** + * Accumulator for incoming case tag. Holds the + * incoming test structure information for + * later dispatch to the reporter. + * @package SimpleTest + * @subpackage UnitTester + */ +class NestingCaseTag extends NestingXmlTag { + + /** + * Sets the basic test information except + * the name. + * @param hash $attributes Name value pairs. + * @access public + */ + function NestingCaseTag($attributes) { + $this->NestingXmlTag($attributes); + } + + /** + * Signals the appropriate start event on the + * listener. + * @param SimpleReporter $listener Target for events. + * @access public + */ + function paintStart(&$listener) { + $listener->paintCaseStart($this->getName()); + } + + /** + * Signals the appropriate end event on the + * listener. + * @param SimpleReporter $listener Target for events. + * @access public + */ + function paintEnd(&$listener) { + $listener->paintCaseEnd($this->getName()); + } +} + +/** + * Accumulator for incoming group tag. Holds the + * incoming test structure information for + * later dispatch to the reporter. + * @package SimpleTest + * @subpackage UnitTester + */ +class NestingGroupTag extends NestingXmlTag { + + /** + * Sets the basic test information except + * the name. + * @param hash $attributes Name value pairs. + * @access public + */ + function NestingGroupTag($attributes) { + $this->NestingXmlTag($attributes); + } + + /** + * Signals the appropriate start event on the + * listener. + * @param SimpleReporter $listener Target for events. + * @access public + */ + function paintStart(&$listener) { + $listener->paintGroupStart($this->getName(), $this->getSize()); + } + + /** + * Signals the appropriate end event on the + * listener. + * @param SimpleReporter $listener Target for events. + * @access public + */ + function paintEnd(&$listener) { + $listener->paintGroupEnd($this->getName()); + } + + /** + * The size in the attributes. + * @return integer Value of size attribute or zero. + * @access public + */ + function getSize() { + $attributes = $this->getAttributes(); + if (isset($attributes['SIZE'])) { + return (integer)$attributes['SIZE']; + } + return 0; + } +} + +/** + * Parser for importing the output of the XmlReporter. + * Dispatches that output to another reporter. + * @package SimpleTest + * @subpackage UnitTester + */ +class SimpleTestXmlParser { + private $listener; + private $expat; + private $tag_stack; + private $in_content_tag; + private $content; + private $attributes; + + /** + * Loads a listener with the SimpleReporter + * interface. + * @param SimpleReporter $listener Listener of tag events. + * @access public + */ + function SimpleTestXmlParser(&$listener) { + $this->listener = &$listener; + $this->expat = &$this->createParser(); + $this->tag_stack = array(); + $this->in_content_tag = false; + $this->content = ''; + $this->attributes = array(); + } + + /** + * Parses a block of XML sending the results to + * the listener. + * @param string $chunk Block of text to read. + * @return boolean True if valid XML. + * @access public + */ + function parse($chunk) { + if (! xml_parse($this->expat, $chunk)) { + trigger_error('XML parse error with ' . + xml_error_string(xml_get_error_code($this->expat))); + return false; + } + return true; + } + + /** + * Sets up expat as the XML parser. + * @return resource Expat handle. + * @access protected + */ + protected function &createParser() { + $expat = xml_parser_create(); + xml_set_object($expat, $this); + xml_set_element_handler($expat, 'startElement', 'endElement'); + xml_set_character_data_handler($expat, 'addContent'); + xml_set_default_handler($expat, 'defaultContent'); + return $expat; + } + + /** + * Opens a new test nesting level. + * @return NestedXmlTag The group, case or method tag + * to start. + * @access private + */ + protected function pushNestingTag($nested) { + array_unshift($this->tag_stack, $nested); + } + + /** + * Accessor for current test structure tag. + * @return NestedXmlTag The group, case or method tag + * being parsed. + * @access private + */ + protected function &getCurrentNestingTag() { + return $this->tag_stack[0]; + } + + /** + * Ends a nesting tag. + * @return NestedXmlTag The group, case or method tag + * just finished. + * @access private + */ + protected function popNestingTag() { + return array_shift($this->tag_stack); + } + + /** + * Test if tag is a leaf node with only text content. + * @param string $tag XML tag name. + * @return @boolean True if leaf, false if nesting. + * @private + */ + protected function isLeaf($tag) { + return in_array($tag, array( + 'NAME', 'PASS', 'FAIL', 'EXCEPTION', 'SKIP', 'MESSAGE', 'FORMATTED', 'SIGNAL')); + } + + /** + * Handler for start of event element. + * @param resource $expat Parser handle. + * @param string $tag Element name. + * @param hash $attributes Name value pairs. + * Attributes without content + * are marked as true. + * @access protected + */ + protected function startElement($expat, $tag, $attributes) { + $this->attributes = $attributes; + if ($tag == 'GROUP') { + $this->pushNestingTag(new NestingGroupTag($attributes)); + } elseif ($tag == 'CASE') { + $this->pushNestingTag(new NestingCaseTag($attributes)); + } elseif ($tag == 'TEST') { + $this->pushNestingTag(new NestingMethodTag($attributes)); + } elseif ($this->isLeaf($tag)) { + $this->in_content_tag = true; + $this->content = ''; + } + } + + /** + * End of element event. + * @param resource $expat Parser handle. + * @param string $tag Element name. + * @access protected + */ + protected function endElement($expat, $tag) { + $this->in_content_tag = false; + if (in_array($tag, array('GROUP', 'CASE', 'TEST'))) { + $nesting_tag = $this->popNestingTag(); + $nesting_tag->paintEnd($this->listener); + } elseif ($tag == 'NAME') { + $nesting_tag = &$this->getCurrentNestingTag(); + $nesting_tag->setName($this->content); + $nesting_tag->paintStart($this->listener); + } elseif ($tag == 'PASS') { + $this->listener->paintPass($this->content); + } elseif ($tag == 'FAIL') { + $this->listener->paintFail($this->content); + } elseif ($tag == 'EXCEPTION') { + $this->listener->paintError($this->content); + } elseif ($tag == 'SKIP') { + $this->listener->paintSkip($this->content); + } elseif ($tag == 'SIGNAL') { + $this->listener->paintSignal( + $this->attributes['TYPE'], + unserialize($this->content)); + } elseif ($tag == 'MESSAGE') { + $this->listener->paintMessage($this->content); + } elseif ($tag == 'FORMATTED') { + $this->listener->paintFormattedMessage($this->content); + } + } + + /** + * Content between start and end elements. + * @param resource $expat Parser handle. + * @param string $text Usually output messages. + * @access protected + */ + protected function addContent($expat, $text) { + if ($this->in_content_tag) { + $this->content .= $text; + } + return true; + } + + /** + * XML and Doctype handler. Discards all such content. + * @param resource $expat Parser handle. + * @param string $default Text of default content. + * @access protected + */ + protected function defaultContent($expat, $default) { + } +} +?> diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay.php new file mode 100644 index 0000000..b167915 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay.php @@ -0,0 +1,261 @@ +. + + */ + +//require 'Yay/Expectations.php'; +//require 'Yay/Matchers/OptionalMatcher.php'; +//require 'Yay/Matchers/AnyMatcher.php'; +//require 'Yay/Matchers/IdenticalMatcher.php'; +//require 'Yay/Matchers/EqualMatcher.php'; +//require 'Yay/Matchers/PatternMatcher.php'; +//require 'Yay/Matchers/ReferenceMatcher.php'; +//require 'Yay/Matchers/BoundsMatcher.php'; +//require 'Yay/Actions/ReturnValueAction.php'; +//require 'Yay/Actions/ReturnReferenceAction.php'; +//require 'Yay/Actions/ThrowAction.php'; +//require 'Yay/Actions/CallbackAction.php'; + +/** + * A convenience factory class. + * @author Chris Corbyn + * @package Yay + */ +class Yay +{ + + /** + * The classpath used for autoloading. + * @var string + * @access private + */ + private static $CLASSPATH = '.'; + + // -- Expectations + + /** + * Create a new Expectations builder instance. + * @return Yay_Expectations + */ + public static function expectations() + { + return new Yay_Expectations(); + } + + // -- Matchers + + /** + * Create a new Optional matcher, optionally wrapping $value. + * @param string $value, optional + * @return Yay_Matchers_OptionalMatcher + */ + public static function optional($value = null) + { + return new Yay_Matchers_OptionalMatcher($value); + } + + /** + * Create a new Any matcher, optionally constrained to $type. + * @param string $type, optional + * @return Yay_Matchers_AnyMatcher + */ + public static function any($type = null) + { + return new Yay_Matchers_AnyMatcher($type, true); + } + + /** + * Create a negated Any matcher, optionally constrained to $type. + * @param string $type, optional + * @return Yay_Matchers_AnyMatcher + */ + public static function none($type = null) + { + return new Yay_Matchers_AnyMatcher($type, false); + } + + /** + * Create a new Identical matcher for $value. + * @param mixed $value + * @return Yay_Matchers_IdenticalMatcher + */ + public static function identical($value) + { + return new Yay_Matchers_IdenticalMatcher($value, true); + } + + /** + * Create a negated Identical matcher for $value. + * @param mixed $value + * @return Yay_Matchers_IdenticalMatcher + */ + public static function notIdentical($value) + { + return new Yay_Matchers_IdenticalMatcher($value, false); + } + + /** + * Create a new Equal matcher for $value. + * @param mixed $value + * @return Yay_Matchers_EqualMatcher + */ + public static function equal($value) + { + return new Yay_Matchers_EqualMatcher($value, true); + } + + /** + * Create a negated Equal matcher for $value. + * @param mixed $value + * @return Yay_Matchers_EqualMatcher + */ + public static function notEqual($value) + { + return new Yay_Matchers_EqualMatcher($value, false); + } + + /** + * Create a new Pattern matcher for $pattern. + * @param string $pattern + * @return Yay_Matchers_IsAMatcher + */ + public static function pattern($pattern) + { + return new Yay_Matchers_PatternMatcher($pattern, true); + } + + /** + * Create a negated Pattern matcher for $pattern. + * @param string $pattern + * @return Yay_Matchers_IsAMatcher + */ + public static function noPattern($pattern) + { + return new Yay_Matchers_PatternMatcher($pattern, false); + } + + /** + * Create a new Reference matcher for $ref. + * @param mixed $ref + * @return Yay_Matchers_ReferenceMatcher + */ + public static function reference(&$ref) + { + return new Yay_Matchers_ReferenceMatcher($ref, true); + } + + /** + * Create a negated Reference matcher for $ref. + * @param mixed $ref + * @return Yay_Matchers_ReferenceMatcher + */ + public static function noReference(&$ref) + { + return new Yay_Matchers_ReferenceMatcher($ref, false); + } + + /** + * Create a new Bounds matcher for boundaries between $lower and $upper. + * @param mixed $lower + * @param mixed $upper + * @return Yay_Matchers_BoundsMatcher + */ + public static function bounds($lower, $upper) + { + return new Yay_Matchers_BoundsMatcher($lower, $upper, true); + } + + /** + * Create a negated Bounds matcher for boundaries outside $lower and $upper. + * @param mixed $lower + * @param mixed $upper + * @return Yay_Matchers_BoundsMatcher + */ + public static function outside($lower, $upper) + { + return new Yay_Matchers_BoundsMatcher($lower, $upper, false); + } + + // -- Actions + + /** + * Create a new ReturnValueAction with $value. + * @param mixed $value + * @return Yay_Actions_ReturnValueAction + */ + public static function returnValue($value) + { + return new Yay_Actions_ReturnValueAction($value); + } + + /** + * Create a new ReturnReferenceAction with &$ref. + * @param mixed $ref + * @return Yay_Actions_ReturnReferenceAction + */ + public static function returnReference(&$ref) + { + return new Yay_Actions_ReturnReferenceAction($ref); + } + + /** + * Create a new ThrowAction with $e. + * @param Exception $ref + * @return Yay_Actions_ThrowAction + */ + public static function throwException(Exception $e) + { + return new Yay_Actions_ThrowAction($e); + } + + /** + * Create a new CallbackAction with $callback. + * @param callback $callback + * @return Yay_Actions_CallbackAction + */ + public static function call($callback) + { + return new Yay_Actions_CallbackAction($callback); + } + + /** + * Set the classpath for autoloading. + * @param string $path + */ + public static function setClassPath($path) + { + self::$CLASSPATH = $path; + } + + /** + * Static autoloader registered in bootstrap file. + * @param string $class + */ + public static function autoload($class) + { + if (substr($class, 0, 3) != 'Yay') + { + return; + } + $file = str_replace('_', '/', $class) . '.php'; + $path = self::$CLASSPATH . '/' . $file; + if (is_file($path)) + { + require_once $path; + } + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Action.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Action.php new file mode 100644 index 0000000..d7b3a01 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Action.php @@ -0,0 +1,37 @@ +. + + */ + +//require 'Yay/Invocation.php'; +//require 'Yay/SelfDescribing.php'; + +/** + * An Action performed when an expected Invocation occurs. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_Action extends Yay_SelfDescribing +{ + + /** + * Mimmick the method Invocation and return a value. + * @param Yay_Invocation $invocation + * @return mixed + */ + public function &invoke(Yay_Invocation $invocation); + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Actions/CallbackAction.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Actions/CallbackAction.php new file mode 100644 index 0000000..8611441 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Actions/CallbackAction.php @@ -0,0 +1,66 @@ +. + + */ + +//require 'Yay/Action.php'; +//require 'Yay/Description.php'; + +/** + * An Action which delegates to a callback. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Actions_CallbackAction implements Yay_Action +{ + + /** + * The callback to invoke. + * @var callback + * @access private + */ + private $_callback; + + /** + * Create a new CallbackAction for $callback. + * @param callback $callback + */ + public function __construct($callback) + { + $this->_callback = $callback; + } + + /** + * Mimmick the method Invocation and return a value. + * @param Yay_Invocation $invocation + * @return mixed + */ + public function &invoke(Yay_Invocation $invocation) + { + $ret = call_user_func($this->_callback, $invocation); + return $ret; + } + + /** + * Describe this Expectation to $description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description) + { + $description->appendText(' Runs a callback;'); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Actions/ReturnReferenceAction.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Actions/ReturnReferenceAction.php new file mode 100644 index 0000000..e96394d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Actions/ReturnReferenceAction.php @@ -0,0 +1,64 @@ +. + + */ + +//require 'Yay/Action.php'; + +/** + * An Action which returns a reference. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Actions_ReturnReferenceAction implements Yay_Action +{ + + /** + * The reference to return. + * @var mixed + * @access private + */ + private $_ref; + + /** + * Create a new ReturnReferenceAction for &$ref. + * @param mixed $ref + */ + public function __construct(&$ref) + { + $this->_ref =& $ref; + } + + /** + * Mimmick the method Invocation and return the reference. + * @param Yay_Invocation $invocation + * @return mixed + */ + public function &invoke(Yay_Invocation $invocation) + { + return $this->_ref; + } + + /** + * Describe this Expectation to $description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description) + { + $description->appendText(' Returns a reference;'); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Actions/ReturnValueAction.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Actions/ReturnValueAction.php new file mode 100644 index 0000000..c749a1d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Actions/ReturnValueAction.php @@ -0,0 +1,100 @@ +. + + */ + +//require 'Yay/Action.php'; + +/** + * An Action which returns a specified value. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Actions_ReturnValueAction implements Yay_Action +{ + + /** + * The value to return. + * @var mixed + * @access private + */ + private $_value; + + /** + * Create a new ReturnValueAction for $value. + * @param mixed $value + */ + public function __construct($value) + { + $this->_value = $value; + } + + /** + * Mimmick the method Invocation and return a value. + * @param Yay_Invocation $invocation + * @return mixed + */ + public function &invoke(Yay_Invocation $invocation) + { + $value = $this->_value; + return $value; + } + + /** + * Describe this Expectation to $description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description) + { + $description->appendText(sprintf(' Returns %s;', $this->_describeValue('%s [%s]'))); + } + + private function _describeValue($format) + { + $description = ''; + $value = $this->_value; + if (is_int($value)) + { + $description = sprintf($format, 'int', $value); + } + elseif (is_float($value)) + { + $description = sprintf($format, 'float', preg_replace('/^(.{8}).+/', '$1..', $value)); + } + elseif (is_numeric($value)) + { + $description = sprintf($format, 'number', preg_replace('/^(.{8}).+/', '$1..', $value)); + } + elseif (is_string($value)) + { + $description = sprintf($format, 'string', preg_replace('/^(.{8}).+/', '$1..', $value)); + } + elseif (is_object($value)) + { + $description = sprintf($format, 'object', get_class($value)); + } + elseif (is_array($value)) + { + $description = sprintf($format, 'array', count($value) . ' items'); + } + else + { + $description = sprintf($format, gettype($value), preg_replace('/^(.{8}).+/', '$1..', (string) $value)); + } + return $description; + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Actions/ThrowAction.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Actions/ThrowAction.php new file mode 100644 index 0000000..bdec7c8 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Actions/ThrowAction.php @@ -0,0 +1,66 @@ +. + + */ + +//require 'Yay/Action.php'; + +/** + * An Action which throws an Exception. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Actions_ThrowAction implements Yay_Action +{ + + /** + * The Exception to throw. + * @var Exception + * @access private + */ + private $_e; + + /** + * Create a new ThrowAction for $e. + * @param Exception $e + */ + public function __construct(Exception $e) + { + $this->_e = $e; + } + + /** + * Mimmick the method Invocation and throw an Exception. + * @param Yay_Invocation $invocation + * @throws Exception + */ + public function &invoke(Yay_Invocation $invocation) + { + throw $this->_e; + } + + /** + * Describe this Expectation to $description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description) + { + $description->appendText( + sprintf(' Throws %s;', get_class($this->_e)) + ); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Description.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Description.php new file mode 100644 index 0000000..561e2a2 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Description.php @@ -0,0 +1,45 @@ +. + + */ + +/** + * A Description container for error messages. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_Description +{ + + /** + * Append an existing Description to this Description. + * @param Yay_Description + */ + public function appendDescription(Yay_Description $description); + + /** + * Append text content to this Description. + * @param string $text + */ + public function appendText($text); + + /** + * Get this description back as a formatted string. + * @return string + */ + public function toString(); + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectation.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectation.php new file mode 100644 index 0000000..de155f9 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectation.php @@ -0,0 +1,88 @@ +. + + */ + +//require 'Yay/MockObject.php'; +//require 'Yay/Invocation.php'; +//require 'Yay/Action.php'; +//require 'Yay/SelfDescribing.php'; +//require 'Yay/State.php'; +//require 'Yay/StatePredicate.php'; +//require 'Yay/Sequence.php'; + +/** + * An Invocation expectation. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_Expectation extends Yay_SelfDescribing +{ + + /** + * Specify the MockObject which the Invocation will occur. + * This method should return the mock object in record mode. + * @param Yay_MockObject $mock + * @return Yay_MockObject + */ + public function of(Yay_MockObject $mock); + + /** + * Notify the Expectation of an Invocation and check if it matches. + * @param Yay_Invocation $invocation + * @return boolean + */ + public function isExpected(Yay_Invocation $invocation); + + /** + * Specify the Action to run if a match occurs. + * @param Yay_Action $action + */ + public function will(Yay_Action $action); + + /** + * Only be expected when in the given State predicate. + * @param Yay_StatePredicate $predicate + */ + public function when(Yay_StatePredicate $predicate); + + /** + * Activate the given $state if a match occurs. + * @param Yay_State $state + */ + public function then(Yay_State $state); + + /** + * Constrain this expectation to be valid only if invoked in the given sequence. + * @param Yay_Sequence $sequence + */ + public function inSequence(Yay_Sequence $sequence); + + /** + * Test if all conditions of the Invocation are satisfied. + * @return boolean + */ + public function isSatisfied(); + + /** + * Get the Action for the given Invocation. + * This may have been specified by a will() clause. + * @param Yay_Invocation $invocation + * @return Yay_Action + */ + public function getAction(Yay_Invocation $invocation); + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/ExpectationProvider.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/ExpectationProvider.php new file mode 100644 index 0000000..8b90202 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/ExpectationProvider.php @@ -0,0 +1,33 @@ +. + + */ + +/** + * An Invocation expectation provider. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_ExpectationProvider +{ + + /** + * Returns the Expectations. + * @return array of Yay_Expectation + */ + public function getExpectations(); + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectations.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectations.php new file mode 100644 index 0000000..c8363cb --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectations.php @@ -0,0 +1,306 @@ +. + + */ + +//require 'Yay/Expectation.php'; +//require 'Yay/InvocationRecorder.php'; +//require 'Yay/InvocationProxy.php'; +//require 'Yay/Invocation.php'; +//require 'Yay/State.php'; +//require 'Yay/StatePredicate.php'; +//require 'Yay/Sequence.php'; +//require 'Yay/Expectations/ExactlyExpectation.php'; +//require 'Yay/Expectations/AtLeastExpectation.php'; +//require 'Yay/Expectations/AtMostExpectation.php'; +//require 'Yay/Expectations/BetweenExpectation.php'; +//require 'Yay/Action.php'; +//require 'Yay/Actions/ReturnValueAction.php'; +//require 'Yay/Actions/ReturnReferenceAction.php'; +//require 'Yay/Actions/ThrowAction.php'; +//require 'Yay/Actions/CallbackAction.php'; + +/** + * A group of expectations which can be specified in a fluid manner. + * Generally speaking this is where all expectations should be made for the sake + * of abstraction. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Expectations implements Yay_InvocationRecorder +{ + + /** + * The Expectation stack. + * @var array + * @access private + */ + private $_expectations = array(); + + /** + * The current Expectation to proxy any recording to. + * @var Yay_Expectation + * @access private + */ + private $_currentEndpoint; + + /** + * Create a new instance of Expectations. + * @return Yay_Expectations + */ + final public static function create() + { + return new self(); + } + + /** + * Expect one Invocation on the $mock object. + * Returns the mock object in record mode. + * @param Yay_MockObject $mock + * @return Yay_Expectations + */ + public function one(Yay_MockObject $mock) + { + return $this->exactly(1)->of($mock); + } + + /** + * Expect exactly $n Invocations on a mock object specified with a following + * of() clause. + * Example: Expectations::create()->exactly(2)->of($mock); + * @param integer $n + * @return Yay_Expectations + */ + public function exactly($n) + { + return $this->_setEndpoint(new Yay_Expectations_ExactlyExpectation($n)); + } + + /** + * Expect at least $n Invocations on a mock object specified with a following + * of() clause. + * Example: Expectations::create()->atLeast(2)->of($mock); + * @param integer $n + * @return Yay_Expectations + */ + public function atLeast($n) + { + return $this->_setEndpoint(new Yay_Expectations_AtLeastExpectation($n)); + } + + /** + * Expect at most $n Invocations on a mock object specified with a following + * of() clause. + * Example: Expectations::create()->atMost(2)->of($mock); + * @param integer $n + * @return Yay_Expectations + */ + public function atMost($n) + { + return $this->_setEndpoint(new Yay_Expectations_AtMostExpectation($n)); + } + + /** + * Expect at between $min and $max Invocations on a mock object specified + * with a following of() clause. + * Example: Expectations::create()->atLeast(2)->of($mock); + * @param integer $n + * @return Yay_Expectations + */ + public function between($min, $max) + { + return $this->_setEndpoint(new Yay_Expectations_BetweenExpectation($min, $max)); + } + + /** + * Ignore Invocations on the $mock object specified. + * @param Yay_MockObject $mock + * @return Yay_Expectations + */ + public function ignoring(Yay_MockObject $mock) + { + return $this->atLeast(0)->of($mock); + } + + /** + * Allow Invocations on the $mock object specified. + * This does exactly the same thing as ignoring() but it allows a semantically + * different meaning in the test case. + * @param Yay_MockObject $mock + * @return Yay_Expectations + */ + public function allowing(Yay_MockObject $mock) + { + return $this->ignoring($mock); + } + + /** + * Deny Invocations on the $mock object specified. + * @param Yay_MockObject $mock + * @return Yay_Expectations + */ + public function never(Yay_MockObject $mock) + { + return $this->exactly(0)->of($mock); + } + + /** + * Specify the MockObject which the Invocation will occur. + * This method returns the mock object in record mode. + * @param Yay_MockObject $mock + * @return Yay_InvocationProxy + */ + public function of(Yay_MockObject $mock) + { + $this->_getEndpoint()->of($mock); + return new Yay_InvocationProxy($this, $mock); + } + + /** + * Specify the Action to run if a match occurs. + * @param Yay_Action $action + */ + public function will(Yay_Action $action) + { + $this->_getEndpoint()->will($action); + return $this; + } + + /** + * Only be expected when in the given State predicate. + * @param Yay_StatePredicate $predicate + */ + public function when(Yay_StatePredicate $predicate) + { + $this->_getEndpoint()->when($predicate); + return $this; + } + + /** + * Activate the given $state if a match occurs. + * @param Yay_State $state + */ + public function then(Yay_State $state) + { + $this->_getEndpoint()->then($state); + return $this; + } + + /** + * Constrain the current expectation to occur in the given sequence. + * @param Yay_Sequence $seq + */ + public function inSequence(Yay_Sequence $seq) + { + $this->_getEndpoint()->inSequence($seq); + return $this; + } + + /** + * A wrapper for will(Yay::returnValue($value)). + * @param mixed $value + */ + public function returns($value) + { + $this->_getEndpoint()->will(new Yay_Actions_ReturnValueAction($value)); + return $this; + } + + /** + * A wrapper for will(Yay::returnReference($ref)). + * @param mixed $ref + */ + public function returnsReference(&$ref) + { + $this->_getEndpoint()->will(new Yay_Actions_ReturnReferenceAction($ref)); + return $this; + } + + /** + * A wrapper for will(Yay::throwException($e)). + * @param Exception $e + */ + public function throws(Exception $e) + { + $this->_getEndpoint()->will(new Yay_Actions_ThrowAction($e)); + return $this; + } + + /** + * A wrapper for will(Yay::call($callback)). + * @param callback $callback + */ + public function calls($callback) + { + $this->_getEndpoint()->will(new Yay_Actions_CallbackAction($callback)); + return $this; + } + + /** + * Record any Invocations on the MockObject whilst it's in record mode. + * @param Yay_Invocation $invocation + */ + public function recordInvocation(Yay_Invocation $invocation) + { + $this->_getEndpoint()->recordInvocation($invocation); + } + + /** + * Returns the Expectation stack. + * @return Yay_Expectation + */ + public function getExpectations() + { + return $this->_expectations; + } + + // -- Private methods + + /** + * Apply a new Expectation to the stack and tag it as the endpoint for recording. + * @param Yay_Expectation $expectation + * @return Yay_Expectations + * @access private + */ + private function _setEndpoint(Yay_Expectation $expectation) + { + $this->_expectations[] = $expectation; + $this->_currentEndpoint = $expectation; + return $this; + } + + /** + * Gets the current endpoint (current expectation). + * @return Yay_Expectation + * @access private + */ + private function _getEndpoint() + { + if (!isset($this->_currentEndpoint)) + { + throw new BadMethodCallException( + 'No cardinality clause has yet been made. First call one(), atLeast(), ' . + 'atMost(), exactly(), between(), ignoring(), allowing() or never() ' . + 'before performing this operation.' + ); + } + else + { + return $this->_currentEndpoint; + } + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectations/AbstractExpectation.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectations/AbstractExpectation.php new file mode 100644 index 0000000..b32a29a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectations/AbstractExpectation.php @@ -0,0 +1,345 @@ +. + + */ + +//require 'Yay/Mockery.php'; +//require 'Yay/Expectation.php'; +//require 'Yay/ExpectationProvider.php'; +//require 'Yay/Invocation.php'; +//require 'Yay/InvocationRecorder.php'; +//require 'Yay/InvocationProxy.php'; +//require 'Yay/Action.php'; +//require 'Yay/Matcher.php'; +//require 'Yay/Matchers/IdenticalMatcher.php'; +//require 'Yay/State.php'; +//require 'Yay/StatePredicate.php'; +//require 'Yay/Sequence.php'; +//require 'Yay/Description.php'; +//require 'Yay/MockGenerator.php'; + +/** + * A base Expectation which other Expectations extend. + * @author Chris Corbyn + * @package Yay + */ +abstract class Yay_Expectations_AbstractExpectation + implements Yay_Expectation, Yay_InvocationRecorder +{ + + /** + * The object to expect Invocations from. + * @var Yay_MockObject + * @access private + */ + private $_object; + + /** + * The method name to expect Invocations on. + * @var string + * @access private + */ + private $_method; + + /** + * The argument Matchers. + * @var array + * @access private + */ + private $_matchers = array(); + + /** + * The Action to use if matched. + * @var Yay_Action + * @access private + */ + private $_action; + + /** + * A state predicate to check for. + * @var Yay_StatePredicate + * @access private + */ + private $_statePredicate; + + /** + * A state to effect if matched. + * @var Yay_State + * @access private + */ + private $_state; + + /** + * The ID wanted to be valid in the Sequence. + * @var int + * @access private + */ + private $_wantedSequenceId; + + /** + * The Sequence to check for validity (if any). + * @var Yay_Sequence + * @access private + */ + private $_sequence; + + /** + * Invoked when the expectation matches so any counters can be incremented + * for example. + * @param Yay_Invocation $invocation + */ + abstract public function notifyMatchedInvocation(Yay_Invocation $invocation); + + /** + * Describe the boundaries of how many invocations can occur. + * @param Yay_Description $description + */ + abstract public function describeBounds(Yay_Description $description); + + /** + * Describe the current status of this expectation. + * @param Yay_Description $description + */ + abstract public function describeSatisfaction(Yay_Description $description); + + /** + * Specify the MockObject which the Invocation will occur. + * This method returns the mock object in record mode. + * @param Yay_MockObject $mock + * @return Yay_InvocationProxy + */ + public function of(Yay_MockObject $mock) + { + $this->_object = $mock; + return new Yay_InvocationProxy($this, $mock); + } + + /** + * Notify the Expectation of an Invocation and check if it matches. + * @param Yay_Invocation $invocation + * @return boolean + */ + public function isExpected(Yay_Invocation $invocation) + { + $matches = true; + $object = $invocation->getObject(); + if ($object === $this->_object) + { + if (isset($this->_statePredicate)) + { + $matches = $this->_statePredicate->isActive(); + } + + if ($matches && isset($this->_method)) + { + if ($this->_method == $invocation->getMethod()) + { + $args =& $invocation->getArguments(); + foreach ($this->_matchers as $i => $m) + { + if (!array_key_exists($i, $args)) + { + if ($m->isOptional()) + { + break; + } + else + { + $matches = false; + break; + } + } + else + { + if (!$m->matches($args[$i])) + { + $matches = false; + break; + } + } + } + } + else + { + $matches = false; + } + } + + if ($matches && isset($this->_sequence)) + { + $matches = $this->_sequence->isInSequence($this->_wantedSequenceId); + } + } + else + { + $matches = false; + } + + if ($matches) + { + $this->notifyMatchedInvocation($invocation); + } + + return $matches; + } + + /** + * Specify the Action to run if a match occurs. + * @param Yay_Action $action + */ + public function will(Yay_Action $action) + { + $this->_action = $action; + return $this; + } + + /** + * Only be expected when in the given State predicate. + * @param Yay_StatePredicate $predicate + */ + public function when(Yay_StatePredicate $predicate) + { + $this->_statePredicate = $predicate; + return $this; + } + + /** + * Activate the given $state if a match occurs. + * @param Yay_State $state + */ + public function then(Yay_State $state) + { + $this->_state = $state; + return $this; + } + + /** + * Constrain this expectation to be valid only if invoked in the given sequence. + * @param Yay_Sequence $sequence + */ + public function inSequence(Yay_Sequence $sequence) + { + $this->_wantedSequenceId = $sequence->requestSequenceId(); + $this->_sequence = $sequence; + return $this; + } + + /** + * Get the Action for the given Invocation. + * This may have been specified by a will() clause. + * @param Yay_Invocation $invocation + * @return Yay_Action + */ + public function getAction(Yay_Invocation $invocation) + { + if (isset($this->_state)) + { + $this->_state->activate(); + } + return $this->_action; + } + + /** + * Record any Invocations on the MockObject whilst it's in record mode. + * @param Yay_Invocation $invocation + */ + public function recordInvocation(Yay_Invocation $invocation) + { + $this->_method = $invocation->getMethod(); + $matchers =& $invocation->getArguments(); + foreach ($matchers as $matcher) + { + if ($matcher instanceof Yay_Matcher) + { + $this->_matchers[] = $matcher; + } + else + { + $this->_matchers[] = new Yay_Matchers_IdenticalMatcher($matcher); + } + } + } + + /** + * Returns the Expectations. + * @return array of Yay_Expectation + */ + public function getExpectations() + { + return array($this); + } + + /** + * Describe this Expectation to $description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description) + { + $this->describeBounds($description); + + $description->appendText(sprintf(' of %s;', $this->_getInvocationSignature())); + + if (isset($this->_sequence)) + { + $description->appendText(' in'); + $this->_sequence->describeTo($description); + } + + if (isset($this->_statePredicate)) + { + $description->appendText(' when'); + $this->_statePredicate->describeTo($description); + } + + if (isset($this->_action)) + { + $this->_action->describeTo($description); + } + + $this->describeSatisfaction($description); + } + + // -- Private methods + + private function _getInvocationSignature() + { + $class = Yay_MockGenerator::getInstance() + ->reverseNamingScheme(get_class($this->_object)); + if (isset($this->_method)) + { + $method = $this->_method; + } + else + { + $method = ''; + } + if (!empty($this->_matchers)) + { + $args = array(); + foreach ($this->_matchers as $matcher) + { + $args[] = $matcher->describeMatch('%s [%s]'); + } + $params = implode(', ', $args); + } + else + { + $params = ''; + } + return sprintf('%s::%s(%s)', $class, $method, $params); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectations/AtLeastExpectation.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectations/AtLeastExpectation.php new file mode 100644 index 0000000..a4d9aa7 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectations/AtLeastExpectation.php @@ -0,0 +1,101 @@ +. + + */ + +//require 'Yay/Expectations/AbstractExpectation.php'; +//require 'Yay/Invocation.php'; + +/** + * An Expectation which wants at least a set number of matching Invocations. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Expectations_AtLeastExpectation + extends Yay_Expectations_AbstractExpectation +{ + + /** + * The expected Invocation count. + * @var int + * @access private + */ + private $_count = 0; + + /** + * The number of matched Invocations. + * @var int + * @access private + */ + private $_matched = 0; + + /** + * Create a new AtLeastExpectation expecting at least $n Invocations. + * @param integer $n + */ + public function __construct($n) + { + $this->_count = $n; + } + + /** + * Test if all conditions of the Invocation are satisfied. + * @return boolean + */ + public function isSatisfied() + { + return ($this->_matched >= $this->_count); + } + + /** + * Increment the match counter by 1. + * @param Yay_Invocation $invocation + */ + public function notifyMatchedInvocation(Yay_Invocation $invocation) + { + $this->_matched++; + } + + /** + * Describe the boundaries of how many invocations can occur. + * @param Yay_Description $description + */ + public function describeBounds(Yay_Description $description) + { + if ($this->_count > 0) + { + $description->appendText(sprintf('At least %d', $this->_count)); + } + else + { + $description->appendText('Any number'); + } + } + + /** + * Describe the current status of this expectation. + * @param Yay_Description $description + */ + public function describeSatisfaction(Yay_Description $description) + { + if ($this->_matched >= $this->_count) + { + $description->appendText(' already'); + } + $description->appendText(sprintf(' occurred %d times', $this->_matched)); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectations/AtMostExpectation.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectations/AtMostExpectation.php new file mode 100644 index 0000000..07c0417 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectations/AtMostExpectation.php @@ -0,0 +1,117 @@ +. + + */ + +//require 'Yay/Expectations/AbstractExpectation.php'; +//require 'Yay/Invocation.php'; + +/** + * An Expectation which wants up to a set number of matching Invocations. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Expectations_AtMostExpectation + extends Yay_Expectations_AbstractExpectation +{ + + /** + * The expected Invocation count. + * @var int + * @access private + */ + private $_count = 0; + + /** + * The number of matched Invocations. + * @var int + * @access private + */ + private $_matched = 0; + + /** + * Create a new AtMostExpectation expecting at most $n Invocations. + * @param integer $n + */ + public function __construct($n) + { + $this->_count = $n; + } + + /** + * Test if this Invocation is one that was expected by this Expectation. + * @param Yay_Invocation $invocation + * @return boolean + */ + public function isExpected(Yay_Invocation $invocation) + { + return parent::isExpected($invocation) && ($this->_matched <= $this->_count); + } + + /** + * Test if all conditions of the Invocation are satisfied. + * @return boolean + */ + public function isSatisfied() + { + return true; + } + + /** + * Increment the match counter by 1. + * @param Yay_Invocation $invocation + */ + public function notifyMatchedInvocation(Yay_Invocation $invocation) + { + $this->_matched++; + } + + /** + * Describe the boundaries of how many invocations can occur. + * @param Yay_Description $description + */ + public function describeBounds(Yay_Description $description) + { + if ($this->_count > 0) + { + $description->appendText(sprintf('At most %d', $this->_count)); + } + else + { + $description->appendText('No invocations'); + } + } + + /** + * Describe the current status of this expectation. + * @param Yay_Description $description + */ + public function describeSatisfaction(Yay_Description $description) + { + if ($this->_matched >= $this->_count) + { + $description->appendText(' already'); + } + $description->appendText( + sprintf( + ' occurred %d times', + (($this->_matched < $this->_count) + ? $this->_matched + : $this->_count) + )); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectations/BetweenExpectation.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectations/BetweenExpectation.php new file mode 100644 index 0000000..f8936d8 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectations/BetweenExpectation.php @@ -0,0 +1,118 @@ +. + + */ + +//require 'Yay/Expectations/AbstractExpectation.php'; +//require 'Yay/Invocation.php'; + +/** + * An Expectation which allows a boundary for a number of matching Invocations. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Expectations_BetweenExpectation + extends Yay_Expectations_AbstractExpectation +{ + + /** + * The minimum Invocation count. + * @var int + * @access private + */ + private $_min = 0; + + /** + * The maximum Invocation count. + * @var int + * @access private + */ + private $_max = 0; + + /** + * The number of matched Invocations. + * @var int + * @access private + */ + private $_matched = 0; + + /** + * Create a new BetweenExpectation expecting between $min and $max Invocations. + * @param integer $n + */ + public function __construct($min, $max) + { + $this->_min = $min; + $this->_max = $max; + } + + /** + * Test if this Invocation is one that was expected by this Expectation. + * @param Yay_Invocation $invocation + * @return boolean + */ + public function isExpected(Yay_Invocation $invocation) + { + return parent::isExpected($invocation) && ($this->_matched <= $this->_max); + } + + /** + * Test if all conditions of the Invocation are satisfied. + * @return boolean + */ + public function isSatisfied() + { + return ($this->_matched >= $this->_min); + } + + /** + * Increment the match counter by 1. + * @param Yay_Invocation $invocation + */ + public function notifyMatchedInvocation(Yay_Invocation $invocation) + { + $this->_matched++; + } + + /** + * Describe the boundaries of how many invocations can occur. + * @param Yay_Description $description + */ + public function describeBounds(Yay_Description $description) + { + $description->appendText(sprintf('Between %d and %d', $this->_min, $this->_max)); + } + + /** + * Describe the current status of this expectation. + * @param Yay_Description $description + */ + public function describeSatisfaction(Yay_Description $description) + { + if ($this->_matched >= $this->_min) + { + $description->appendText(' already'); + } + $description->appendText( + sprintf( + ' occurred %d times', + (($this->_matched < $this->_max) + ? $this->_matched + : $this->_max) + )); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectations/ExactlyExpectation.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectations/ExactlyExpectation.php new file mode 100644 index 0000000..e8d2306 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Expectations/ExactlyExpectation.php @@ -0,0 +1,117 @@ +. + + */ + +//require 'Yay/Expectations/AbstractExpectation.php'; +//require 'Yay/Invocation.php'; + +/** + * An Expectation which wants an exact number of matching Invocations. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Expectations_ExactlyExpectation + extends Yay_Expectations_AbstractExpectation +{ + + /** + * The expected Invocation count. + * @var int + * @access private + */ + private $_count = 0; + + /** + * The number of matched Invocations. + * @var int + * @access private + */ + private $_matched = 0; + + /** + * Create a new ExactlyExpectation expecting $n Invocations. + * @param integer $n + */ + public function __construct($n) + { + $this->_count = $n; + } + + /** + * Test if this Invocation is one that was expected by this Expectation. + * @param Yay_Invocation $invocation + * @return boolean + */ + public function isExpected(Yay_Invocation $invocation) + { + return parent::isExpected($invocation) && ($this->_matched <= $this->_count); + } + + /** + * Test if all conditions of the Invocation are satisfied. + * @return boolean + */ + public function isSatisfied() + { + return ($this->_matched >= $this->_count); + } + + /** + * Increment the match counter by 1. + * @param Yay_Invocation $invocation + */ + public function notifyMatchedInvocation(Yay_Invocation $invocation) + { + $this->_matched++; + } + + /** + * Describe the boundaries of how many invocations can occur. + * @param Yay_Description $description + */ + public function describeBounds(Yay_Description $description) + { + if ($this->_count > 0) + { + $description->appendText(sprintf('Exactly %d', $this->_count)); + } + else + { + $description->appendText('No invocations'); + } + } + + /** + * Describe the current status of this expectation. + * @param Yay_Description $description + */ + public function describeSatisfaction(Yay_Description $description) + { + if ($this->_matched >= $this->_count) + { + $description->appendText(' already'); + } + $description->appendText( + sprintf( + ' occurred %d times', + (($this->_matched < $this->_count) + ? $this->_matched + : $this->_count) + )); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Invocation.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Invocation.php new file mode 100644 index 0000000..d382157 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Invocation.php @@ -0,0 +1,49 @@ +. + + */ + +//require 'Yay/SelfDescribing.php'; + +/** + * A representation of a Method invocation. + * This is a container for the object the method was invoked on, the method- + * name and the arguments in the invocation. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_Invocation extends Yay_SelfDescribing +{ + + /** + * Get the object which this Invocation occured on. + * @return object + */ + public function getObject(); + + /** + * Get the method name of the invoked method. + * @return string + */ + public function getMethod(); + + /** + * Get the argument list in the Invocation. + * @return array + */ + public function &getArguments(); + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/InvocationHandler.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/InvocationHandler.php new file mode 100644 index 0000000..a669fe5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/InvocationHandler.php @@ -0,0 +1,36 @@ +. + + */ + +//require 'Yay/Invocation.php'; + +/** + * Listens for Invocations and returns a suitable value. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_InvocationHandler +{ + + /** + * Handle the given $invocation and return a value for it. + * @param Yay_Invocation $invocation + * @return mixed + */ + public function &handleInvocation(Yay_Invocation $invocation); + +} \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/InvocationProxy.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/InvocationProxy.php new file mode 100644 index 0000000..e8d0aa9 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/InvocationProxy.php @@ -0,0 +1,90 @@ +. + + */ + +//require 'Yay/ExpectationProvider.php'; +//require 'Yay/InvocationRecorder.php'; +//require 'Yay/MockObject.php'; +//require 'Yay/SimpleInvocation.php'; + +/** + * Proxies Invocations on a Mock object to the recorder. + * @author Chris Corbyn + * @package Yay + */ +class Yay_InvocationProxy implements Yay_ExpectationProvider +{ + + /** + * The InvocationRecorder which is using this InvocationProxy. + * @var Yay_InvocationRecorder + * @access private + */ + private $_recorder; + + /** + * The Mock object where Invocations are recorded from. + * @var Yay_MockObject + * @access private + */ + private $_mock; + + /** + * Create a new InvocationProxy for $recorder and $mock. + * @param Yay_InvocationRecorder $recorder + * @param Yay_MockObject $mock + */ + public function __construct(Yay_InvocationRecorder $recorder, Yay_MockObject $mock) + { + $this->_recorder = $recorder; + $this->_mock = $mock; + } + + /** + * Direct all invocations to the recorder. + * @param string $method + * @param array $args + * @return Yay_InvocationRecorder + */ + public function __call($method, $args) + { + if (is_callable(array($this->_mock, $method))) + { + $invocation = new Yay_SimpleInvocation($this->_mock, $method, $args); + $this->_recorder->recordInvocation($invocation); + return $this->_recorder; + } + elseif (is_callable(array($this->_recorder, $method))) + { + return call_user_func_array(array($this->_recorder, $method), $args); + } + else + { + throw new BadMethodCallException('Mock method ' . $method . ' does not exist'); + } + } + + /** + * Returns the Expectation list. + * @return array of Yay_Expectation + */ + public function getExpectations() + { + return $this->__call(__FUNCTION__, array()); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/InvocationRecorder.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/InvocationRecorder.php new file mode 100644 index 0000000..f3abed3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/InvocationRecorder.php @@ -0,0 +1,36 @@ +. + + */ + +//require 'Yay/Invocation.php'; +//require 'Yay/ExpectationProvider.php'; + +/** + * Listens for Invocations and provides expectations based on them. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_InvocationRecorder extends Yay_ExpectationProvider +{ + + /** + * Record the given $invocation. + * @param Yay_Invocation $invocation + */ + public function recordInvocation(Yay_Invocation $invocation); + +} \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matcher.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matcher.php new file mode 100644 index 0000000..5dc3e74 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matcher.php @@ -0,0 +1,48 @@ +. + + */ + +/** + * The Matcher interface for comparing arguments. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_Matcher +{ + + /** + * Compare the $argument with whatever is expected to match it. + * @param mixed $argument + * @return boolean + */ + public function matches(&$argument); + + /** + * Returns true if the argument doesn't need to be present. + * @return boolean + */ + public function isOptional(); + + /** + * Writes the match description as a string following $format. + * $format is a sprintf() string with %s, $s as $matcherName, $value respectively. + * @param string $format + * @return string + */ + public function describeMatch($format); + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/AnyMatcher.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/AnyMatcher.php new file mode 100644 index 0000000..8592ba3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/AnyMatcher.php @@ -0,0 +1,85 @@ +. + + */ + +//require 'Yay/Matcher.php'; + +/** + * Allows anything to match. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Matchers_AnyMatcher implements Yay_Matcher +{ + + /** + * A type to compare with. + * @var string + * @access private + */ + private $_type; + + /** + * The desired result. + * @var boolean + * @access private + */ + private $_result; + + /** + * Create a new AnyMatcher, optionally constrained only to objects of $type. + * @param string $type, optional + * @param boolean $result + */ + public function __construct($type = null, $result = true) + { + $this->_type = $type; + $this->_result = $result; + } + + /** + * Always returns true where no type is given, and where the type matches otherwise. + * @param mixed $value + * @return boolean + */ + public function matches(&$value) + { + $return = (is_null($this->_type) || ($value instanceof $this->_type)); + return (($this->_result && $return) || (!$this->_result && !$return)); + } + + /** + * Returns true if the argument doesn't need to be present. + * @return boolean + */ + public function isOptional() + { + return false; + } + + /** + * Writes the match description as a string following $format. + * $format is a sprintf() string with %s, $s as $matcherName, $value respectively. + * @param string $format + * @return string + */ + public function describeMatch($format) + { + return 'ANYTHING'; + } + +} \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/BoundsMatcher.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/BoundsMatcher.php new file mode 100644 index 0000000..57aef64 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/BoundsMatcher.php @@ -0,0 +1,94 @@ +. + + */ + +//require 'Yay/Matcher.php'; + +/** + * Compares values to test if they are within given boundaries. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Matchers_BoundsMatcher implements Yay_Matcher +{ + + /** + * The upper bound. + * @var mixed + * @access private + */ + private $_upper; + + /** + * The lower bound. + * @var mixed + * @access private + */ + private $_lower; + + /** + * The desired result. + * @var boolean + * @access private + */ + private $_result; + + /** + * Create a new BoundsMatcher between $lower and $upper. + * @param mixed $lower + * @param mixed $upper + * @param boolean $result which is wanted + */ + public function __construct($lower, $upper, $result = true) + { + $this->_upper = $upper; + $this->_lower = $lower; + $this->_result = $result; + } + + /** + * Compare $value with the boundaries and return true if it is within them. + * @param mixed $value + * @return boolean + */ + public function matches(&$value) + { + $return = ($value <= $this->_upper && $value >= $this->_lower); + return (($this->_result && $return) || (!$this->_result && !$return)); + } + + /** + * Returns true if the argument doesn't need to be present. + * @return boolean + */ + public function isOptional() + { + return false; + } + + /** + * Writes the match description as a string following $format. + * $format is a sprintf() string with %s, $s as $matcherName, $value respectively. + * @param string $format + * @return string + */ + public function describeMatch($format) + { + return sprintf($format, 'between', $this->_min . ' and ' . $this->_max); + } + +} \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/EqualMatcher.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/EqualMatcher.php new file mode 100644 index 0000000..1d4f25d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/EqualMatcher.php @@ -0,0 +1,50 @@ +. + + */ + +//require 'Yay/Matchers/IdenticalMatcher.php'; + +/** + * Compares values for equality. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Matchers_EqualMatcher extends Yay_Matchers_IdenticalMatcher +{ + + /** + * Create a new EqualMatcher expecting $expected. + * @param mixed $expected + * @param boolean $result to be expected + */ + public function __construct($expected, $result = true) + { + parent::__construct($expected, $result); + } + + /** + * Compare $value with the expected value and return true if it is equal. + * @param mixed $value + * @return boolean + */ + public function matches(&$value) + { + $return = (($this->_expected == $value) && ($value == $this->_expected)); + return (($this->_result && $return) || (!$this->_result && !$return)); + } + +} \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/IdenticalMatcher.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/IdenticalMatcher.php new file mode 100644 index 0000000..c227b67 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/IdenticalMatcher.php @@ -0,0 +1,116 @@ +. + + */ + +//require 'Yay/Matcher.php'; + +/** + * Compares values for value and type. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Matchers_IdenticalMatcher implements Yay_Matcher +{ + + /** + * The expected value. + * @var mixed + * @access protected + */ + protected $_expected; + + /** + * The expected return value. + * @var boolean + * @access protected + */ + protected $_result; + + /** + * Create a new IdenticalMatcher expecting $expected. + * @param mixed $expected + * @param boolean $result to be expected + */ + public function __construct($expected, $result = true) + { + $this->_expected = $expected; + $this->_result = $result; + } + + /** + * Compare $value with the expected value and return true if it matches in + * type and in value. + * @param mixed $value + * @return boolean + */ + public function matches(&$value) + { + $return = (($this->_expected === $value) && ($value === $this->_expected)); + return (($this->_result && $return) || (!$this->_result && !$return)); + } + + /** + * Returns true if the argument doesn't need to be present. + * @return boolean + */ + public function isOptional() + { + return false; + } + + /** + * Writes the match description as a string following $format. + * $format is a sprintf() string with %s, $s as $matcherName, $value respectively. + * @param string $format + * @return string + */ + public function describeMatch($format) + { + $description = ''; + $value = $this->_expected; + if (is_int($value)) + { + $description = sprintf($format, 'int', $value); + } + elseif (is_float($value)) + { + $description = sprintf($format, 'float', preg_replace('/^(.{8}).+/', '$1..', $value)); + } + elseif (is_numeric($value)) + { + $description = sprintf($format, 'number', preg_replace('/^(.{8}).+/', '$1..', $value)); + } + elseif (is_string($value)) + { + $description = sprintf($format, 'string', preg_replace('/^(.{8}).+/', '$1..', $value)); + } + elseif (is_object($value)) + { + $description = sprintf($format, 'object', get_class($value)); + } + elseif (is_array($value)) + { + $description = sprintf($format, 'array', count($value) . ' items'); + } + else + { + $description = sprintf($format, gettype($value), preg_replace('/^(.{8}).+/', '$1..', (string) $value)); + } + return $description; + } + +} \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/OptionalMatcher.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/OptionalMatcher.php new file mode 100644 index 0000000..a348d57 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/OptionalMatcher.php @@ -0,0 +1,103 @@ +. + + */ + +//require 'Yay/Matcher.php'; +//require 'Yay/Matchers/IdenticalMatcher.php'; + +/** + * Wraps Matchers and makes them Optional. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Matchers_OptionalMatcher implements Yay_Matcher +{ + + /** + * A matcher to delegate to. + * @var Yay_Matcher + * @access private + */ + private $_matcher; + + /** + * Create a new OptionalMatcher, optionally wrapping $value. + * @param mixed $value, optional + */ + public function __construct($value = null) + { + if (isset($value)) + { + if ($value instanceof Yay_Matcher) + { + $this->_matcher = $value; + } + else + { + $this->_matcher = new Yay_Matchers_IdenticalMatcher($value); + } + } + } + + /** + * Returns true if no matcher set, otherwise it delegates to the given Matcher. + * @param mixed $value + * @return boolean + */ + public function matches(&$value) + { + if (isset($this->_matcher)) + { + $matches = $this->_matcher->matches($value); + } + else + { + $matches = true; + } + return $matches; + } + + /** + * Returns true if the argument doesn't need to be present. + * @return boolean + */ + public function isOptional() + { + return true; + } + + /** + * Writes the match description as a string following $format. + * $format is a sprintf() string with %s, $s as $matcherName, $value respectively. + * @param string $format + * @return string + */ + public function describeMatch($format) + { + $name = 'optional'; + if (isset($this->_matcher)) + { + $value = $this->_matcher->describeMatch($format); + } + else + { + $value = '*'; + } + return sprintf($format, $name, $value); + } + +} \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/PatternMatcher.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/PatternMatcher.php new file mode 100644 index 0000000..4f4043f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/PatternMatcher.php @@ -0,0 +1,88 @@ +. + + */ + +//require 'Yay/Matcher.php'; + +/** + * Compares values against a PCRE pattern. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Matchers_PatternMatcher implements Yay_Matcher +{ + + /** + * The expected pattern. + * @var string + * @access private + */ + private $_pattern; + + /** + * The desired return value. + * @var boolean + * @access private + */ + private $_result; + + /** + * Create a new PatternMatcher expecting $pattern. + * @param string $pattern + * @param boolean $result to be expected + */ + public function __construct($pattern, $result = true) + { + $this->_pattern = $pattern; + $this->_result = $result; + } + + /** + * Compare $value with the expected pattern and return true if it matches. + * @param string $value + * @return boolean + */ + public function matches(&$value) + { + $return = ( + (is_string($value) || is_numeric($value)) + && preg_match($this->_pattern, $value) + ); + return (($this->_result && $return) || (!$this->_result && !$return)); + } + + /** + * Returns true if the argument doesn't need to be present. + * @return boolean + */ + public function isOptional() + { + return false; + } + + /** + * Writes the match description as a string following $format. + * $format is a sprintf() string with %s, $s as $matcherName, $value respectively. + * @param string $format + * @return string + */ + public function describeMatch($format) + { + return sprintf($format, 'pattern', $this->_pattern); + } + +} \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/ReferenceMatcher.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/ReferenceMatcher.php new file mode 100644 index 0000000..cae6a08 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Matchers/ReferenceMatcher.php @@ -0,0 +1,104 @@ +. + + */ + +//require 'Yay/Matcher.php'; + +/** + * Compares values to see if they reference one another. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Matchers_ReferenceMatcher implements Yay_Matcher +{ + + /** + * The expected reference. + * @var mixed + * @access private + */ + private $_ref; + + /** + * The desired return value. + * @var boolean + * @access private + */ + private $_result; + + /** + * Create a new IdenticalMatcher expecting $expected. + * @param mixed $expected + * @param boolean $result to be expected + */ + public function __construct(&$ref, $result = true) + { + $this->_ref =& $ref; + $this->_result = $result; + } + + /** + * Compare $ref with the expected reference and return true if it is the same reference. + * @param mixed $ref + * @return boolean + */ + public function matches(&$ref) + { + if (is_object($ref)) + { + $isRef = ($this->_ref === $ref); + } + else + { + if ($this->_ref === $ref) + { + $copy = $ref; + $randomString = uniqid('yay'); + $ref = $randomString; + $isRef = ($this->_ref === $ref); + $ref = $copy; + } + else + { + $isRef = false; + } + } + + return (($this->_result && $isRef) || (!$this->_result && !$isRef)); + } + + /** + * Returns true if the argument doesn't need to be present. + * @return boolean + */ + public function isOptional() + { + return false; + } + + /** + * Writes the match description as a string following $format. + * $format is a sprintf() string with %s, $s as $matcherName, $value respectively. + * @param string $format + * @return string + */ + public function describeMatch($format) + { + return '[reference]'; + } + +} \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/MockGenerator.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/MockGenerator.php new file mode 100644 index 0000000..b3a9c78 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/MockGenerator.php @@ -0,0 +1,323 @@ +. + + */ + +/** + * Generates the code for a Mock object. + * This lives as a singleton for a few reasons. + * @author Chris Corbyn + * @package Yay + */ +class Yay_MockGenerator +{ + + /** The name of the Mock object interface */ + const MOCK_INTERFACE = 'Yay_MockObject'; + + /** Prefixed to types to create a Mock name */ + const MOCK_PREFIX = 'Yay_MockObjects_'; + + /** Singleton instance */ + private static $_instance = null; + + /** + * The path a template which draws a Mock. + * @var string + * @access private + */ + private $_template; + + /** + * A map of mocked type hints to their concrete class names. + * @var array + * @access private + */ + private $_mocked = array(); + + /** + * Constructor cannot be used. + */ + private function __construct() + { + } + + /** + * Get a singleton instance of this MockGenerator. + * @return Yay_MockGenerator + */ + public static function getInstance() + { + if (is_null(self::$_instance)) + { + self::$_instance = new self(); + } + return self::$_instance; + } + + /** + * Set the path to a template which can draw a mock class. + * @param string $path + */ + public function setMockTemplate($path) + { + $this->_template = $path; + } + + /** + * Produce class code for a MockObject of $typeHint and return its concrete name. + * @param string $typeHint + * @return string + */ + public function generateMock($typeHint) + { + if (!$className = $this->_getConcreteMockName($typeHint)) + { + $className = $this->_materializeMockCode($typeHint); + } + return $className; + } + + /** + * Use a fixed naming scheme to make a mock class name from $typeHint. + * @param string $typeHint + * @return string + */ + public function applyNamingScheme($typeHint) + { + return self::MOCK_PREFIX . $typeHint; + } + + /** + * Remove any adjustments that were made to an original type hint. + * @param string $typeHint + * @return string + */ + public function reverseNamingScheme($typeHint) + { + $len = strlen(self::MOCK_PREFIX); + if (substr($typeHint, 0, $len) == self::MOCK_PREFIX) + { + $typeHint = substr($typeHint, $len); + } + return $typeHint; + } + + // -- Private methods + + /** + * Try to lookup a mocked concrete class name for $typeHint. + * @param string $typeHint + * @return string + * @access private + */ + private function _getConcreteMockName($typeHint) + { + if (array_key_exists($typeHint, $this->_mocked)) + { + return $this->_mocked[$typeHint]; + } + } + + /** + * Produce the mock object code and return its name. + * @param string $typeHint + * @return string + * @access private + */ + private function _materializeMockCode($typeHint) + { + $reflector = new ReflectionClass($typeHint); + $mockData = array( + 'className' => $this->applyNamingScheme($typeHint), + 'extends' => $this->_getSuperclass($reflector), + 'interfaces' => $this->_getInterfaces($reflector), + 'methods' => $this->_getMethods($reflector) + ); + + extract($mockData); + $code = include($this->_template); + eval($code); + + $this->_mocked[$typeHint] = $mockData['className']; + return $mockData['className']; + } + + /** + * Get all known interfaces for $reflector. + * @param ReflectionClass $reflector + * @return array + * @access private + */ + private function _getInterfaces(ReflectionClass $reflector) + { + $interfaces = array(); + if ($reflector->isInterface()) + { + if ($reflector->getName() != self::MOCK_INTERFACE) + { + $interfaces[] = $reflector->getName(); + } + } + else + { + foreach ($reflector->getInterfaces() as $interfaceReflector) + { + if ($interfaceReflector->getName() != self::MOCK_INTERFACE) + { + $interfaces[] = $interfaceReflector->getName(); + } + } + } + return $interfaces; + } + + /** + * Get the superclass this mock object needs to extend. + * @param ReflectionClass $reflector + * @return string + * @access private + */ + private function _getSuperclass(ReflectionClass $reflector) + { + if ($this->_canExtend($reflector)) + { + $superclass = $reflector->getName(); + } + else + { + $superclass = ''; + } + return $superclass; + } + + /** + * Get all methods from $reflector. + * @param ReflectionClass $reflector + * @return array + * @access private + */ + private function _getMethods(ReflectionClass $reflector) + { + $methods = array(); + foreach ($reflector->getMethods() as $reflectionMethod) + { + if ($reflectionMethod->isConstructor() + || $reflectionMethod->getName() == '__clone') + { + continue; + } + if ($reflectionMethod->isPublic() || $reflectionMethod->isProtected()) + { + $methods[] = array( + 'name' => $reflectionMethod->getName(), + 'access' => $reflectionMethod->isPublic() ? 'public' : 'protected', + 'modifiers' => $reflectionMethod->isStatic() ? 'static' : '', + 'returnReference' => $reflectionMethod->returnsReference(), + 'parameters' => $this->_getParameters($reflectionMethod) + ); + } + } + return $methods; + } + + /** + * Get all parameters for $method. + * @param ReflectionMethod $method + * @return array + * @access private + */ + private function _getParameters(ReflectionMethod $method) + { + $parameters = array(); + foreach ($method->getParameters() as $reflectionParameter) + { + $hint = ''; + if ($reflectionParameter->isArray()) + { + $hint = 'array'; + } + elseif ($c = $reflectionParameter->getClass()) + { + $hint = $c->getName(); + } + $parameters[] = array( + 'hint' => $hint, + 'byReference' => $reflectionParameter->isPassedByReference(), + 'optional' => $reflectionParameter->isOptional() + ); + } + return $parameters; + } + + /** + * Determine if the reflector for the given class is safe to extend. + * @param ReflectionClass $reflector + * @return boolean + * @access private + */ + private function _canExtend(ReflectionClass $reflector) + { + $canExtend = true; + $warning = false; + if ($reflector->isInterface()) + { + $canExtend = false; + } + else + { + if ($constructor = $reflector->getConstructor()) + { + if ($constructor->isPrivate() || $constructor->isFinal()) + { + $canExtend = false; + $warning = 'has a private or final constructor'; + } + } + elseif ($reflector->isFinal()) + { + $canExtend = false; + $warning = 'is declared final'; + } + else + { + foreach ($reflector->getMethods() as $method) + { + if (($method->isPublic() || $method->isProtected()) && $method->isFinal()) + { + $canExtend = false; + $warning = 'contains final methods'; + } + } + } + } + if ($warning) + { + trigger_error( + sprintf('The type [%s] to be mocked %s.' . + ' Mocking classes which cannot be fully overridden results' . + ' in a loss of class type. It is safe to supress this warning if you' . + ' are aware of the conflict.', + $reflector->getName(), + $warning + ), + E_USER_WARNING + ); + } + return $canExtend; + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/MockObject.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/MockObject.php new file mode 100644 index 0000000..3c65de0 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/MockObject.php @@ -0,0 +1,25 @@ +. + + */ + +/** + * A tag placed on any MockObjects generated in the context of YayMock. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_MockObject { +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Mockery.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Mockery.php new file mode 100644 index 0000000..38d2410 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Mockery.php @@ -0,0 +1,183 @@ +. + + */ + +//require 'Yay/MockGenerator.php'; +//require 'Yay/SimpleInvocation.php'; +//require 'Yay/SimpleDescription.php'; +//require 'Yay/InvocationHandler.php'; +//require 'Yay/MockObject.php'; +//require 'Yay/ExpectationProvider.php'; +//require 'Yay/NotSatisfiedException.php'; +//require 'Yay/StateMachine.php'; +//require 'Yay/SimpleSequence.php'; + +/** + * The main Yay context. + * Handles the generation of MockObjects and the Invocation of methods. + * @author Chris Corbyn + * @package Yay + */ +class Yay_Mockery implements Yay_InvocationHandler +{ + + /** + * The Expectation stack which is being checked. + * @var array + * @access private + */ + private $_expectations = array(); + + /** + * Invocations which are not expected by any Expectations get caught here. + * @var array + * @access private + */ + private $_unexpectedInvocations = array(); + + /** + * A mock class generator. + * @var Yay_MockGenerator + * @access private + */ + private $_generator; + + /** + * Create a new Mockery. + */ + public function __construct() + { + $this->_generator = Yay_MockGenerator::getInstance(); + } + + /** + * Create a MockObject matching $typeHint. + * If the $typeHint is an interface the Mock will implement the interface + * and maintain the method signatures from that interface. + * If the $typeHint is a class name the Mock will extend the class overriding + * all public methods (HOWEVER, if the class contains final methods it is not + * possible to override all methods and hence, the mock will have no specific + * type. + * @param string $typeHint + * @return Yay_MockObject + */ + public function mock($typeHint) + { + $className = $this->_generator->generateMock($typeHint); + $reflector = new ReflectionClass($className); + return $reflector->newInstance($this); + } + + /** + * Specify an Expectation (or Expectations) to check. + * @param Yay_ExpectationProvider $provider + */ + public function checking(Yay_ExpectationProvider $provider) + { + foreach ($provider->getExpectations() as $expectation) + { + $this->_expectations[] = $expectation; + } + } + + /** + * Get a state machine named $name. + * @param string $name + * @return Yay_States + */ + public function states($name) + { + return new Yay_StateMachine($name); + } + + /** + * Create a new Sequence named $name. + * @param string $name + * @return Yay_Sequence + */ + public function sequence($name) + { + return new Yay_SimpleSequence($name); + } + + /** + * Used by YayMock internally (ignore this method!). + */ + public function &handleInvocation(Yay_Invocation $invocation) + { + $ret = null; + $expected = false; + foreach ($this->_expectations as $expectation) + { + if ($expectation->isExpected($invocation)) + { + $expected = true; + if ($action = $expectation->getAction($invocation)) + { + $ret =& $action->invoke($invocation); + } + break; + } + } + if (!$expected) + { + $this->_unexpectedInvocations[] = $invocation; + } + return $ret; + } + + /** + * Assert that all Expectations are satisfied. + * Throws an Exception of type Yay_NotSatisfiedException if any Expecations + * are not satisfied. + * @throws Yay_NotSatisfiedException + */ + public function assertIsSatisfied() + { + $description = new Yay_SimpleDescription(); + $satisfied = true; + foreach ($this->_unexpectedInvocations as $invocation) + { + $description->appendText('Unexpected invocation'); + $invocation->describeTo($description); + $description->appendText(PHP_EOL); + $satisfied = false; + } + if (!$satisfied) + { + $description->appendText(PHP_EOL); + } + foreach ($this->_expectations as $expectation) + { + if (!$expectation->isSatisfied()) + { + $description->appendText('* '); + $satisfied = false; + } + $expectation->describeTo($description); + $description->appendText(PHP_EOL); + } + if (!$satisfied) + { + throw new Yay_NotSatisfiedException( + 'Not all expectations were satisfied or a method was invoked unexpectedly.' . + PHP_EOL . PHP_EOL . $description->toString() . PHP_EOL + ); + } + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/NotSatisfiedException.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/NotSatisfiedException.php new file mode 100644 index 0000000..8a76daa --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/NotSatisfiedException.php @@ -0,0 +1,36 @@ +. + + */ + +/** + * The Exception thrown by Context::assertIsSatisfied() if assertion fails. + * @author Chris Corbyn + * @package Yay + */ +class Yay_NotSatisfiedException extends Exception +{ + + /** + * Create a new NotSatisfiedException with $message. + * @param string $message + */ + public function __construct($message) + { + parent::__construct($message); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/SelfDescribing.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/SelfDescribing.php new file mode 100644 index 0000000..186249a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/SelfDescribing.php @@ -0,0 +1,35 @@ +. + + */ + +//require 'Yay/Description.php'; + +/** + * Components implementing this can describe what they do to a Description instance. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_SelfDescribing +{ + + /** + * Write a description of this self describing object to Description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description); + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Sequence.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Sequence.php new file mode 100644 index 0000000..a2b7d38 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/Sequence.php @@ -0,0 +1,43 @@ +. + + */ + +//require 'Yay/SelfDescribing.php'; + +/** + * Provides a means for Expectations to verify they are called in the correct order. + * This allows Invocations to be forced in a particular order. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_Sequence extends Yay_SelfDescribing +{ + + /** + * Ask for a new Sequence Id and register the new sequence. + * @return integer $id + */ + public function requestSequenceId(); + + /** + * Check if the sequence has progressed far enough for this sequence ID to be used. + * @param integer $id + * @return boolean + */ + public function isInSequence($sequenceId); + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/SimpleDescription.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/SimpleDescription.php new file mode 100644 index 0000000..f86b4b7 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/SimpleDescription.php @@ -0,0 +1,63 @@ +. + + */ + +//require 'Yay/Description.php'; + +/** + * A basic Description container for error messages. + * @author Chris Corbyn + * @package Yay + */ +class Yay_SimpleDescription implements Yay_Description +{ + + /** + * An internal text buffer. + * @var string + * @access private + */ + private $_text = ''; + + /** + * Append an existing Description to this Description. + * @param Yay_Description + */ + public function appendDescription(Yay_Description $description) + { + $this->_text .= $description->toString(); + } + + /** + * Append text content to this Description. + * @param string $text + */ + public function appendText($text) + { + $this->_text .= $text; + } + + /** + * Get this description back as a formatted string. + * @return string + */ + public function toString() + { + return $this->_text; + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/SimpleInvocation.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/SimpleInvocation.php new file mode 100644 index 0000000..012b568 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/SimpleInvocation.php @@ -0,0 +1,163 @@ +. + + */ + +//require 'Yay/Invocation.php'; +//require 'Yay/MockGenerator.php'; + +/** + * The standard implementation of the Invocation interface. + * @author Chris Corbyn + * @package Yay + */ +class Yay_SimpleInvocation implements Yay_Invocation +{ + + /** + * The Object on which the Inovation occurred. + * @var object + * @access private + */ + private $_object; + + /** + * The method name invoked. + * @var string + * @access private + */ + private $_method; + + /** + * The arguments in the Invocation. + * @var array + * @access private + */ + private $_arguments; + + /** + * Create a new SimpleInvocation with the given details. + * @param object $object + * @param string $method + * @param array &$arguments + */ + public function __construct($object, $method, array &$arguments) + { + $this->_object = $object; + //Massage __call() overloading so the interface is tested correctly + if ($method == '__call') + { + $method = array_shift($arguments); + $args =& array_shift($arguments); + $arguments =& $args; + } + $this->_method = $method; + $this->_arguments =& $arguments; + } + + /** + * Get the object which this Invocation occured on. + * @return object + */ + public function getObject() + { + return $this->_object; + } + + /** + * Get the method name of the invoked method. + * @return string + */ + public function getMethod() + { + return $this->_method; + } + + /** + * Get the argument list in the Invocation. + * @return array + */ + public function &getArguments() + { + return $this->_arguments; + } + + /** + * Describe this Invocation to $description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description) + { + $description->appendText(sprintf(' of %s;', $this->_getInvocationSignature())); + } + + // -- Private methods + + private function _getInvocationSignature() + { + $class = Yay_MockGenerator::getInstance() + ->reverseNamingScheme(get_class($this->_object)); + if (!empty($this->_arguments)) + { + $args = array(); + foreach ($this->_arguments as $arg) + { + $args[] = $this->_describeArgument($arg, '%s [%s]'); + } + $params = implode(', ', $args); + } + else + { + $params = ''; + } + return sprintf('%s::%s(%s)', $class, $this->_method, $params); + } + + private function _describeArgument($arg, $format) + { + $description = ''; + if (is_int($arg)) + { + $description = sprintf($format, 'int', $arg); + } + elseif (is_float($arg)) + { + $description = sprintf($format, 'float', preg_replace('/^(.{8}).+/', '$1..', $arg)); + } + elseif (is_numeric($arg)) + { + $description = sprintf($format, 'number', preg_replace('/^(.{8}).+/', '$1..', $arg)); + } + elseif (is_string($arg)) + { + $description = sprintf($format, 'string', preg_replace('/^(.{8}).+/', '$1..', $arg)); + } + elseif (is_object($arg)) + { + $description = sprintf($format, 'object', get_class($arg)); + } + elseif (is_array($arg)) + { + $description = sprintf($format, 'array', count($arg) . ' items'); + } + else + { + $description = sprintf($format, gettype($arg), preg_replace('/^(.{8}).+/', '$1..', (string) $arg)); + } + return $description; + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/SimpleSequence.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/SimpleSequence.php new file mode 100644 index 0000000..17156d8 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/SimpleSequence.php @@ -0,0 +1,108 @@ +. + + */ + +/** + * Provides a means for Expectations to verify they are called in the correct order. + * This allows Invocations to be forced in a particular order. + * @author Chris Corbyn + * @package Yay + */ +class Yay_SimpleSequence implements Yay_Sequence +{ + + /** + * The name of this sequence. + * @var string + * @access private + */ + private $_name; + + /** + * The list of sequence IDs expected. + * @var array + * @access private + */ + private $_sequenceIds = array(); + + /** + * An internal sequence counter. + * @var int + * @access private + */ + private $_counter = 0; + + /** + * The current position in the sequence. + * @var int + * @access private + */ + private $_currentId = null; + + /** + * Create a new Sequence with $name. + * @param string $name + */ + public function __construct($name) + { + $this->_name = $name; + } + + /** + * Ask for a new Sequence Id and register the new sequence. + * @return integer $id + */ + public function requestSequenceId() + { + $id = $this->_counter++; + $this->_sequenceIds[] = $id; + return $id; + } + + /** + * Check if the sequence has progressed far enough for this sequence ID to be used. + * @param integer $id + * @return boolean + */ + public function isInSequence($sequenceId) + { + if ($this->_currentId === $sequenceId) + { + $inSequence = true; + } + elseif (current($this->_sequenceIds) === $sequenceId) + { + $this->_currentId = array_shift($this->_sequenceIds); + $inSequence = true; + } + else + { + $inSequence = false; + } + return $inSequence; + } + + /** + * Write a description of this self describing object to Description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description) + { + $description->appendText(sprintf(' sequence %s;', $this->_name)); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/SimpleState.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/SimpleState.php new file mode 100644 index 0000000..18c7621 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/SimpleState.php @@ -0,0 +1,49 @@ +. + + */ + +//require 'Yay/State.php'; +//require 'Yay/States.php'; +//require 'Yay/SimpleStatePredicate.php'; + +/** + * A State from a State machine. + * @author Chris Corbyn + * @package Yay + */ +class Yay_SimpleState extends Yay_SimpleStatePredicate implements Yay_State +{ + + /** + * Create a new State for $stateMachine to be $stateName. + * @param Yay_States $stateMachine + * @param string $stateName + */ + public function __construct(Yay_States $stateMachine, $stateName) + { + parent::__construct($stateMachine, $stateName, true); + } + + /** + * Make this State active. + */ + public function activate() + { + $this->_stateMachine->become($this->_stateName); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/SimpleStatePredicate.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/SimpleStatePredicate.php new file mode 100644 index 0000000..6c9458c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/SimpleStatePredicate.php @@ -0,0 +1,88 @@ +. + + */ + +//require 'Yay/StatePredicate.php'; +//require 'Yay/States.php'; + +/** + * An expectation about what State a state machine is in. + * @author Chris Corbyn + * @package Yay + */ +class Yay_SimpleStatePredicate implements Yay_StatePredicate +{ + + /** + * The state machine which this predicate checks. + * @var Yay_States + * @access private + */ + protected $_stateMachine; + + /** + * The state name to check for in the state machine. + * @var string + * @access private + */ + protected $_stateName; + + /** + * True if the state is wanted, false otherwise. + * @var boolean + * @access private + */ + private $_is = true; + + /** + * Create a new StatePredicate. + * @param Yay_States $stateMachine + * @param string $stateName to expect + * @param boolean $is (negation point) + */ + public function __construct(Yay_States $stateMachine, $stateName, $is = true) + { + $this->_stateMachine = $stateMachine; + $this->_stateName = $stateName; + $this->_is = $is; + } + + /** + * Return true if the state machine is in this state. + * @return boolean + */ + public function isActive() + { + return (($this->_is && $this->_stateMachine->getCurrentState() == $this->_stateName) + || (!$this->_is && $this->_stateMachine->getCurrentState() != $this->_stateName)); + } + + /** + * Write a description of this self describing object to Description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description) + { + $this->_stateMachine->describeTo($description); + $description->appendText(sprintf( + ' %s %s;', + ($this->_is ? 'is' : 'is not'), + $this->_stateName + )); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/State.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/State.php new file mode 100644 index 0000000..045e8bb --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/State.php @@ -0,0 +1,34 @@ +. + + */ + +//require 'Yay/StatePredicate.php'; + +/** + * A State from a State machine. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_State extends Yay_StatePredicate +{ + + /** + * Make this State active. + */ + public function activate(); + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/StateMachine.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/StateMachine.php new file mode 100644 index 0000000..2a9128a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/StateMachine.php @@ -0,0 +1,113 @@ +. + + */ + +//require 'Yay/State.php'; +//require 'Yay/SimpleState.php'; +//require 'Yay/StatePredicate.php'; +//require 'Yay/SimpleStatePredicate.php'; + +/** + * A basic state machine. + * @author Chris Corbyn + * @package Yay + */ +class Yay_StateMachine implements Yay_States +{ + + /** + * The name of this state machine. + * @var string + * @access private + */ + private $_name; + + /** + * The current state. + * @var string + * @access private + */ + private $_state; + + /** + * Create a new State machine with $name. + * @param string $name + */ + public function __construct($name) + { + $this->_name = $name; + } + + /** + * Set the initial state of this state machine. + * @param string $stateName + * @return Yay_States + */ + public function startsAs($stateName) + { + $this->become($stateName); + return $this; + } + + /** + * Get the state which puts the state machine into the named state. + * @param string $stateName + * @return Yay_State + */ + public function is($stateName) + { + return new Yay_SimpleState($this, $stateName); + } + + /** + * Get the predicate which indicates the state machine is NOT in the named state. + * @param string $stateName + * @return Yay_StatePredicate + */ + public function isNot($stateName) + { + return new Yay_SimpleStatePredicate($this, $stateName, false); + } + + /** + * Become the named state. + * @param string $stateName + */ + public function become($stateName) + { + $this->_state = $stateName; + } + + /** + * Get the name of the current state. + * @return string + */ + public function getCurrentState() + { + return $this->_state; + } + + /** + * Write a description of this self describing object to Description. + * @param Yay_Description $description + */ + public function describeTo(Yay_Description $description) + { + $description->appendText(sprintf(' %s', $this->_name)); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/StatePredicate.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/StatePredicate.php new file mode 100644 index 0000000..5941d76 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/StatePredicate.php @@ -0,0 +1,35 @@ +. + + */ + +//require 'Yay/SelfDescribing.php'; + +/** + * An expectation about what State a state machine is in. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_StatePredicate extends Yay_SelfDescribing +{ + + /** + * Return true if the state machine is in this state. + * @return boolean + */ + public function isActive(); + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/States.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/States.php new file mode 100644 index 0000000..c021c4f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/classes/Yay/States.php @@ -0,0 +1,62 @@ +. + + */ + +//require 'Yay/SelfDescribing.php'; + +/** + * A basic state machine. + * @author Chris Corbyn + * @package Yay + */ +interface Yay_States extends Yay_SelfDescribing +{ + + /** + * Set the initial state of this state machine. + * @param string $stateName + * @return Yay_States + */ + public function startsAs($stateName); + + /** + * Get the state which puts the state machine into the named state. + * @param string $stateName + * @return Yay_State + */ + public function is($stateName); + + /** + * Get the predicate which indicates the state machine is NOT in the named state. + * @param string $stateName + * @return Yay_StatePredicate + */ + public function isNot($stateName); + + /** + * Become the named state. + * @param string $stateName + */ + public function become($stateName); + + /** + * Get the name of the current state. + * @return string + */ + public function getCurrentState(); + +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/mock.tpl.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/mock.tpl.php new file mode 100644 index 0000000..937699e --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/mock.tpl.php @@ -0,0 +1,69 @@ +. + + */ + +?> + +class + + implements + + Yay_MockObject +{ + + private $_yayInvocationHandler; + + public function __construct(Yay_InvocationHandler $invocationHandler) + { + $this->_yayInvocationHandler = $invocationHandler; + } + + + function ( $param): ?> + 0) echo ','; ?> + + $arg_ + + ) + { + $value = null; + if (isset($this->_yayInvocationHandler)) + { + $args = array(); + for ($i = 0; $i < func_num_args(); ++$i) + { + $argName = 'arg_' . $i; + $args[] =& ${$argName}; + } + $invocation = new Yay_SimpleInvocation($this, __FUNCTION__, $args); + $value =& $this->_yayInvocationHandler->handleInvocation($invocation); + } + return $value; + } + + + public function __clone() + { + $this->_yayInvocationHandler = null; + } + +} + + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/yay_convenience.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/yay_convenience.php new file mode 100644 index 0000000..c145103 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/yay_convenience.php @@ -0,0 +1,176 @@ +. + + */ + +/** + * Provides non-namespaced classes and functions for brevity. + * Including this script is entirely optional. + * @author Chris Corbyn + * @package Yay + */ + +// Classes + +/** + * Allow occurences of Yay_Expectations::create() to be replaced with Expectations::create(). + */ +class Expectations extends Yay_Expectations { } + +/** + * Allows occurences of new Yay_Mockery() to be replaced with new Mockery(). + */ +class Mockery extends Yay_Mockery { } + +//Argument matchers + +/** + * Allows Yay::optional() to be called as optional(). + */ +function optional($value = null) +{ + return Yay::optional($value); +} + +/** + * Allows Yay::any() to be called as any(). + */ +function any($type = null) +{ + return Yay::any($type); +} + +/** + * Allows Yay::none() to be called as none(). + */ +function none($type = null) +{ + return Yay::none($type); +} + +/** + * Allows Yay::identical() to be called as identical(). + */ +function identical($value) +{ + return Yay::identical($value); +} + +/** + * Allows Yay::notIdentical() to be called as notIdentical(). + */ +function notIdentical($value) +{ + return Yay::notIdentical($value); +} + +/** + * Allows Yay::equal() to be called as equal(). + */ +function equal($value) +{ + return Yay::equal($value); +} + +/** + * Allows Yay::notEqual() to be called as notEqual(). + */ +function notEqual($value) +{ + return Yay::notEqual($value); +} + +/** + * Allows Yay::pattern() to be called as pattern(). + */ +function pattern($pattern) +{ + return Yay::pattern($pattern); +} + +/** + * Allows Yay::noPattern() to be called as noPattern(). + */ +function noPattern($pattern) +{ + return Yay::noPattern($pattern); +} + +/** + * Allows Yay::bounds() to be called as bounds(). + */ +function bounds($a, $b) +{ + return Yay::bounds($a, $b); +} + +/** + * Allows Yay::outside() to be called as outside(). + */ +function outside($a, $b) +{ + return Yay::outside($a, $b); +} + +/** + * Allows Yay::reference() to be called as reference(). + */ +function reference(&$ref) +{ + return Yay::reference($ref); +} + +/** + * Allows Yay::noReference() to be called as noReference(). + */ +function noReference(&$ref) +{ + return Yay::noReference($ref); +} + +//Actions + +/** + * Allows Yay::returnValue() to be called as returnValue(). + */ +function returnValue($value) +{ + return Yay::returnValue($value); +} + +/** + * Allows Yay::returnReference() to be called as returnReference(). + */ +function returnReference(&$ref) +{ + return Yay::returnReference($ref); +} + +/** + * Allows Yay::throwException() to be called as throwException(). + */ +function throwException(Exception $e) +{ + return Yay::throwException($e); +} + +/** + * Allows Yay::call() to be called as call(). + */ +function call($callback) +{ + return Yay::call($callback); +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/yay_mock.php b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/yay_mock.php new file mode 100644 index 0000000..7c76922 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/lib/yaymock/yay_mock.php @@ -0,0 +1,29 @@ +. + + */ + +/** + * YayMock include and bootstrap file. + * @author Chris Corbyn + */ + +require_once dirname(__FILE__) . '/classes/Yay.php'; +Yay::setClassPath(dirname(__FILE__) . '/classes'); +spl_autoload_register(array('Yay', 'autoload')); +Yay_MockGenerator::getInstance()->setMockTemplate(dirname(__FILE__) . '/mock.tpl.php'); + +//EOF diff --git a/vendor/swiftmailer/swiftmailer/test-suite/run.php b/vendor/swiftmailer/swiftmailer/test-suite/run.php new file mode 100644 index 0000000..fa67634 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/run.php @@ -0,0 +1,56 @@ +get('//./root/cimv2:Win32_Process.Handle="' . getmypid() . '"') + ->executablePath; + } +} + +$runner = new Sweety_Runner_CliRunner( + explode(PATH_SEPARATOR, SWEETY_TEST_PATH), + $exe . ' ' . $argv[0] + ); + +$name = !empty($argv[1]) ? $argv[1] : 'All Tests'; +$runner->setReporter(new Sweety_Reporter_CliReporter(sprintf('%s - %s', SWEETY_SUITE_NAME, $name))); + +$runner->setIgnoredClassRegex(SWEETY_IGNORED_CLASSES); + +$locators = preg_split('/\s*,\s*/', SWEETY_TEST_LOCATOR); +foreach ($locators as $locator) +{ + $runner->registerTestLocator(new $locator()); +} + +if (!empty($argv[1]) && !preg_match('~!?/.*?/~', $argv[1])) +{ + $testName = $argv[1]; + $format = !empty($argv[2]) ? $argv[2] : Sweety_Runner::REPORT_TEXT; + + $runner->runTestCase($testName, $format); +} +else +{ + $runner->runAllTests(); +} + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/sweety.js b/vendor/swiftmailer/swiftmailer/test-suite/sweety.js new file mode 100644 index 0000000..663ff94 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/sweety.js @@ -0,0 +1,471 @@ +/* + JavaScript wrapper around REST API in Sweety. + */ + +/** + * A convenience class for using XPath. + * @author Chris Corbyn + * @constructor + */ +function SweetyXpath() { + + /** + * Get the first node matching the given expression. + * @param {String} expr + * @param {Element} node + * @returns Element + */ + this.getFirstNode = function getFirstNode(expr, node) { + var firstNode = _getRootNode(node).evaluate( + expr, node, _getNsResolver(node), XPathResult.FIRST_ORDERED_NODE_TYPE, null); + return firstNode.singleNodeValue; + }, + + /** + * Get all nodes matching the given expression. + * The returned result is a Node Snapshot. + * @param {String} expr + * @param {Element} node + * @returns Element[] + */ + this.getNodes = function getNodes(expr, node) { + var nodes = _getRootNode(node).evaluate( + expr, node, _getNsResolver(node), XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + + var nodeSet = new Array(); + for (var i = 0, len = nodes.snapshotLength; i < len; i++) { + nodeSet.push(nodes.snapshotItem(i)); + } + return nodeSet; + }, + + /** + * Get the string value of the node matching the given expression. + * @param {String} expr + * @param {Element} node + * @returns String + */ + this.getValue = function getValue(expr, node) { + return _getRootNode(node).evaluate( + expr, node, _getNsResolver(node), XPathResult.STRING_TYPE, null).stringValue; + } + + /** + * Get the root node from which run evaluate. + * @param {Element} node + * @returns Element + */ + var _getRootNode = function _getRootNode(node) { + if (node.ownerDocument && node.ownerDocument.evaluate) { + return node.ownerDocument; + } else { + if (node.evaluate) { + return node; + } else { + return document; + } + } + } + + /** + * Get the NS Resolver used when searching. + * @param {Element} node + * @returns Element + */ + var _getNsResolver = function _getNsResolver(node) { + if (!document.createNSResolver) { + return null; + } + + if (node.ownerDocument) { + return document.createNSResolver(node.ownerDocument.documentElement); + } else { + return document.createNSResolver(node.documentElement); + } + } + +} + +/** + * The reporter interface so Sweety can tell the UI what's happening. + * @author Chris Corbyn + * @constructor + */ +function SweetyReporter() { //Interface/Base Class + + var _this = this; + + /** + * Create a sub-reporter for an individual test case. + * @param {String} testCaseName + * @returns SweetyReporter + */ + this.getReporterFor = function getReporterFor(testCaseName) { + return _this; + } + + /** + * Start reporting. + */ + this.start = function start() { + } + + /** + * Handle a skipped test case. + * @param {String} message + * @param {String} path + */ + this.reportSkip = function reportSkip(message, path) { + } + + /** + * Handle a passing assertion. + * @param {String} message + * @param {String} path + */ + this.reportPass = function reportPass(message, path) { + } + + /** + * Handle a failing assertion. + * @param {String} message + * @param {String} path + */ + this.reportFail = function reportFail(message, path) { + } + + /** + * Handle an unexpected exception. + * @param {String} message + * @param {String} path + */ + this.reportException = function reportException(message, path) { + } + + /** + * Handle miscellaneous test output. + * @param {String} output + * @param {String} path + */ + this.reportOutput = function reportOutput(output, path) { + } + + /** + * Finish reporting. + */ + this.finish = function finish() { + } + +} + + +/** + * Represents a single test case being run. + * @author Chris Corbyn + * @constructor + */ +function SweetyTestCaseRun(testClass, reporter) { + + var _this = this; + + /** The XMLHttpRequest used in testing */ + var _req; + + /** XPath handler */ + var _xpath = new SweetyXpath(); + + /** Callback function for completion event */ + this.oncompletion = function oncompletion() { + } + + /** + * Run this test. + */ + this.run = function run() { + if (!reporter.isStarted()) { + reporter.start(); + } + _req = _createHttpRequest(); + + if (!_req) { + return; + } + + _req.open("GET", "?test=" + testClass + "&format=xml", true); + _req.onreadystatechange = _handleXml; + _req.send(null); + } + + /** + * Get an XmlHttpRequest instance, cross browser compatible. + * @return Object + */ + var _createHttpRequest = function _createHttpRequest() { + var req = false; + + if (window.XMLHttpRequest && !(window.ActiveXObject)) { + try { + req = new XMLHttpRequest(); + } catch(e) { + req = false; + } + } else if (window.ActiveXObject) { + try { + req = new ActiveXObject("Msxml2.XMLHTTP"); + } catch(e) { + try { + req = new ActiveXObject("Microsoft.XMLHTTP"); + } catch(e) { + req = false; + } + } + } + + return req; + } + + /** + * Handle the XML response from the test. + */ + var _handleXml = function _handleXml() { + if (_req.readyState == 4) { + try { + + var xml = _req.responseXML; + var txt = _req.responseText.replace(/[\r\n]+/g, ""). + replace(/^(.+)<\?xml.*$/, "$1"); + + //Test case was skipped + var skipElements = xml.getElementsByTagName('skip'); + if (!skipElements || 1 != skipElements.length) + { + var runElements = xml.getElementsByTagName('run'); + //Invalid document, an error probably occured + if (!runElements || 1 != runElements.length) { + reporter.reportException( + "Invalid XML response: " + + _stripTags(txt.replace(/^\s*<\?xml.+<\/(?:name|pass|fail|exception)>/g, "")), testClass); + } else { + var everything = runElements.item(0); + _parseResults(everything, testClass); + reporter.finish(); + } + } + else + { + reporter.reportSkip(_textValueOf(skipElements.item(0)), testClass); + reporter.finish(); + } + } catch (ex) { + //Invalid document or an error occurred. + reporter.reportException( + "Invalid XML response: " + + _stripTags(txt.replace(/^\s*<\?xml.+<\/(?:name|pass|fail|exception)>/g, "")), testClass); + } + + //Invoke the callback + _this.oncompletion(); + } + } + + /** + * Cross browser method for reading the value of a node in XML. + * @param {Element} node + * @returns String + */ + var _textValueOf = function _textValueOf(node) { + if (!node.textContent && node.text) { + return node.text; + } else { + return node.textContent; + } + } + + var _stripTags = function _stripTags(txt) { + txt = txt.replace(/[\r\n]+/g, ""); + return txt.replace( + /<\/?(?:a|b|br|p|strong|u|i|em|span|div|ul|ol|li|table|thead|tbody|th|td|tr)\b.*?\/?>/g, + ""); + } + + /** + * Parse an arbitrary message output. + * @param {Element} node + * @param {String} path + */ + var _parseMessage = function _parseMessage(node, path) { + reporter.reportOutput(_textValueOf(node), path); + } + + /** + * Parse formatted text output (such as a dump()). + * @param {Element} node + * @param {String} path + */ + var _parseFormatted = function _parseFormatted(node, path) { + reporter.reportOutput(_textValueOf(node), path); + } + + /** + * Parse failing test assertion. + * @param {Element} node + * @param {String} path + */ + var _parseFail = function _parseFail(node, path) { + reporter.reportFail(_textValueOf(node), path); + } + + /** + * Parse an Exception. + * @param {Element} node + * @param {String} path + */ + var _parseException = function _parseException(node, path) { + reporter.reportException(_textValueOf(node), path); + } + + /** + * Parse passing test assertion. + * @param {Element} node + * @param {String} path + */ + var _parsePass = function _parsePass(node, path) { + reporter.reportPass(_textValueOf(node), path); + } + + /** + * Parse an entire test case + * @param {Element} node + * @param {String} path + */ + var _parseTestCase = function _parseTestCase(node, path) { + var testMethodNodes = _xpath.getNodes("./test", node); + + for (var x in testMethodNodes) { + var testMethodNode = testMethodNodes[x]; + var testMethodName = _xpath.getValue("./name", testMethodNode); + + var formattedNodes = _xpath.getNodes("./formatted", testMethodNode); + for (var i in formattedNodes) { + var formattedNode = formattedNodes[i]; + _parseFormatted(formattedNode, path + " -> " + testMethodName); + } + + var messageNodes = _xpath.getNodes("./message", testMethodNode); + for (var i in messageNodes) { + var messageNode = messageNodes[i]; + _parseMessage(messageNode, path + " -> " + testMethodName); + } + + var failNodes = _xpath.getNodes("./fail", testMethodNode); + for (var i in failNodes) { + var failNode = failNodes[i]; + _parseFail(failNode, path + " -> " + testMethodName); + } + + var exceptionNodes = _xpath.getNodes("./exception", testMethodNode); + for (var i in exceptionNodes) { + var exceptionNode = exceptionNodes[i]; + _parseException(exceptionNode, path + " -> " + testMethodName); + } + + var passNodes = _xpath.getNodes("./pass", testMethodNode); + for (var i in passNodes) { + var passNode = passNodes[i]; + _parsePass(passNode, path + " -> " + testMethodName); + } + } + } + + /** + * Parse an entire grouped or single test case. + * @param {Element} node + * @param {String} path + */ + var _parseResults = function _parseResults(node, path) { + var groupNodes = _xpath.getNodes("./group", node); + + if (0 != groupNodes.length) { + for (var i in groupNodes) { + var groupNode = groupNodes[i]; + var groupName = _xpath.getValue("./name", groupNode); + _parseResults(groupNode, path + " -> " + groupName); + } + } else { + var caseNodes = _xpath.getNodes("./case", node); + for (var i in caseNodes) { + var caseNode = caseNodes[i]; + _parseTestCase(caseNode, path); + } + } + } + +} + +/** + * Runs a list of test cases. + * @author Chris Corbyn + * @constructor + */ +function SweetyTestRunner() { + + var _this = this; + + SweetyTestRunner._currentInstance = _this; + + /** True if the test runner has been stopped */ + var _cancelled = false; + + /** + * Invoked to cause the test runner to stop execution at the next available + * opportunity. If XML is being parsed in another thread, or an AJAX request + * is in progress the test runner will wait until the next test. + * @param {Boolean} cancel + */ + this.cancelTesting = function cancelTesting(cancel) { + _cancelled = cancel; + } + + /** + * Run the given list of test cases. + * @param {String[]} tests + * @param {SweetyReporter} reporter + */ + this.runTests = function runTests(tests, reporter) { + if (!reporter.isStarted()) { + reporter.start(); + } + + if (_cancelled || !tests || !tests.length) { + _cancelled = false; + reporter.finish(); + return; + } + + var testCase = tests.shift(); + + var caseReporter = reporter.getReporterFor(testCase); + + var testRun = new SweetyTestCaseRun(testCase, caseReporter); + + //Repeat until no tests remaining in list + // Ok, I know, I know I'll try to eradicate this lazy use of recursion + testRun.oncompletion = function() { + _this.runTests(tests, reporter); + }; + + testRun.run(); + } + +} + +/** Active instance */ +SweetyTestRunner._currentInstance = null; + +/** + * Fetches the currently running instance of the TestRunner. + * @returns SweetyTestRunner + */ +SweetyTestRunner.getCurrentInstance = function getCurrentInstance() { + return this._currentInstance; +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/css/main.css b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/css/main.css new file mode 100644 index 0000000..3f6e630 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/css/main.css @@ -0,0 +1,203 @@ +/** Page structure **/ +/*------------------*/ + +ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,body,html,p,blockquote,fieldset,input { + margin: 0; padding: 0; +} + +body { + font-size: 0.8em; + font-family: arial,sans-serif; + background: #fff; + padding: 8px; +} + +img { + border: 0; +} + +input { + margin-bottom: 8px; + padding: 4px; + font-size: 16px; + width: 100px; +} + +input.sweety-text { + border: 2px solid #000; + width: 268px; + background-color: #fff; +} + +input.sweety-disabled { + background: #eee; + border: 2px solid #888; +} + +input.sweety-waiting { + background-image: url(../images/loading.gif); + background-position: 98% 50%; + background-repeat: no-repeat; +} + +input.sweety-check { + width: 12px !important; + height: 12px !important; + margin: 1px !important; + padding: 1px !important; +} + +h1 { + margin-bottom: 1em; +} + +div#sweety-page { + width: 100%; +} + +div#sweety-testlist { + float: left; + width: 400px; + background: #aaa; +} + +div#sweety-output { + margin-left: 400px; +} + +div#sweety-results { + padding: 8px; + font-size: 1.4em; +} + +div#sweety-messages { + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid #777; +} + +div#sweety-smoke-images { + margin-top: 8px; + padding-top: 8px; +} + +div#sweety-smoke-images img { + width: 150px; + height: 120px; + margin-right: 8px; +} + +div.sweety-clear { + width: 100%; + clear: both; +} + +/** Look and feel **/ +/*-----------------*/ + +input#sweety-filter { + visibility: hidden; +} + +div#sweety-communication { + float: right; + display: none; +} + +pre.sweety-raw-output { + display: block; + background: #ddd; + padding: 4px; + margin: 4px; +} + +div.sweety-pad { + padding: 8px; +} + +div.sweety-message { + padding: 4px; + margin: 4px; +} + +div.sweety-test-path { + padding-left: 2em; + font-style: italic; + color: #777; +} + +div.sweety-test { + font-size: 12px; + margin-bottom: 2px; + margin-left: 12px; + padding: 2px; +} + +div.sweety-test img { + float: right; + margin-right: 4px; +} + +div.sweety-package-header { + font-size: 12px; + margin-bottom: 2px; + border-bottom: 2px solid #444; + padding: 2px; +} + +div.sweety-package-header span.sweety-test-package { + font-weight: normal; +} + +div.sweety-package-header img { + margin: 0 2px; +} + +span.sweety-pkg-count { + display: block; + float: right; + padding-right: 2px; +} + +img.sweety-group-icon { + float: right; + margin: 4px !important; +} + +.sweety-pkg-idle { + background: #cacaca; +} + +span.sweety-test-package { + display: block; + font-size: 10px; + padding-left: 1em; +} + +.sweety-idle { + background: #eee; +} + +.sweety-running { + background: #ffff33 !important; +} + +.sweety-pass { + background: #00aa00 !important; + color: #fff; +} + +.sweety-fail { + background: #ee0000 !important; + color: #fff; +} + +.sweety-fail-text { + color: #ee0000; +} + +.sweety-skip-text { + color: #4444dd; + font-weight: bold; + text-decoration: underline; +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/darr.gif b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/darr.gif new file mode 100644 index 0000000..d4349a7 Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/darr.gif differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/group.gif b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/group.gif new file mode 100644 index 0000000..2e22ad2 Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/group.gif differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/htmlicon.gif b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/htmlicon.gif new file mode 100644 index 0000000..e110dcf Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/htmlicon.gif differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/loading.gif b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/loading.gif new file mode 100644 index 0000000..731ccea Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/loading.gif differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/network.gif b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/network.gif new file mode 100644 index 0000000..38c4d6b Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/network.gif differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/rarr.gif b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/rarr.gif new file mode 100644 index 0000000..b2d4d95 Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/rarr.gif differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/runicon.gif b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/runicon.gif new file mode 100644 index 0000000..a901fca Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/runicon.gif differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/xmlicon.gif b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/xmlicon.gif new file mode 100644 index 0000000..9df47b3 Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/images/xmlicon.gif differ diff --git a/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/js/sweety-template.js b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/js/sweety-template.js new file mode 100644 index 0000000..a47b1b5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/js/sweety-template.js @@ -0,0 +1,1155 @@ +/* + JavaScript for Sweety to wrap the standard template around the API. + */ + + +/** + * The UI Manager object for setting up the interface. + * @author Chris Corbyn + * @constructor + */ +function SweetyUIManager() { + + var _this = this; + + /** Packages toggled on or off */ + var _pkgs = { }; + + /** Test cases within packages */ + var _pkgTests = { }; + + /** An element cache */ + var _cached = { }; + + /** + * Initialize the user interface. + */ + this.initialize = function initialize() { + _this.showFilterBox(); + _this.loadTestList(); + _this.resetMessageDiv(); + _this.resetTotals(); + } + + /** + * Show or hide an entire package. + * @param {String} pkg + * @param {Boolean} onoff + */ + this.togglePackage = function togglePackage(pkg, onOff) { + if (typeof _pkgs[pkg] == "undefined") { + _pkgs[pkg] = true; + } + + //Toggle if not overridden + _pkgs[pkg] = (typeof onOff == "undefined") ? !_pkgs[pkg] : onOff; + + var pkgState = _pkgs[pkg] ? "1" : "0"; + + var date = new Date(); + date.setTime(date.getTime() + (30 * 24 * 60 * 60 * 1000)); + + document.cookie = escape("sweetyPkg" + pkg) + "=" + pkgState + + "; expires=" + date.toGMTString() + + "; path=/"; + + var pkgRegex = new RegExp("^" + pkg + "_[^_]+$"); + for (var testCase in sweetyTestCases) { + if (testCase.match(pkgRegex)) { + var testDiv = _getElementById(testCase); + if (_pkgs[pkg]) { + if (sweetyTestCases[testCase]) { + testDiv.style.display = "block"; + } + } else { + testDiv.style.display = "none"; + } + } + } + var headerImg; + if (headerImg = _getElementById("sweety-pkg-img-" + pkg)) + { + if (_pkgs[pkg]) { + headerImg.src = "templates/sweety/images/darr.gif"; + } else { + headerImg.src = "templates/sweety/images/rarr.gif"; + } + } + } + + /** + * Enable or disable user input. + * @param {Boolean} on + */ + this.allowInteractivity = function allowInteractivity(on) { + if (!on) { + _paintFilterDisabled(true); + _getRunButton().value = "Stop Tests"; + _getRunButton().onclick = function() { + try { + SweetyTestRunner.getCurrentInstance().cancelTesting(true); + _this.allowInteractivity(true); + } catch (e) { } + }; + for (var testCase in sweetyTestCases) { + _getElementById(testCase).onclick = function() { + return false; + }; + } + } else { + _paintFilterDisabled(false); + _getRunButton().value = "Run Tests"; + _getRunButton().onclick = function() { + _this.initialize(); + sweetyRunner.runAll(); + }; + for (var testCase in sweetyTestCases) { + _getElementById(testCase).onclick = function() { + _this.initialize(); + sweetyRunner.runTestCase(this.id); + }; + } + } + } + + /** + * Display the filter box. + */ + this.showFilterBox = function showFilterBox() { + _getFilter().style.visibility = 'visible'; + } + + /** + * Restore the UI on a new page load or reload. + */ + this.restore = function restore() { + for (var testName in sweetyTestCases) { + var pkgName = _pkgFor(testName); + if (typeof _pkgTests[pkgName] == "undefined") { + _pkgTests[pkgName] = { }; + } + _pkgTests[pkgName][testName] = true; + } + this.hideCheckboxes(); + _loadPkgsFromCookie(); + for (var pkg in _pkgs) { + this.togglePackage(pkg, _pkgs[pkg]); + } + } + + /** + * Hide all the checkboxes which are only applicable to the non-JS version. + */ + this.hideCheckboxes = function hideCheckboxes() { + var inputs = document.getElementsByTagName("input"); + for (var i = 0, len = inputs.length; i < len; i++) { + if (inputs.item(i).className == "sweety-check") { + inputs.item(i).style.display = "none"; + } + } + delete inputs; + } + + /** + * Load the available test case list in the UI. + */ + this.loadTestList = function loadTestList() { + var caseBox = _getListContainer(); + + //Show or hide any tests + for (var testCase in sweetyTestCases) { + var pkgName = _pkgFor(testCase); + _pkgTests[pkgName][testCase] = sweetyTestCases[testCase]; + + this.paintTestCaseIdle(testCase); + + var pkg = _pkgFor(testCase); + this.paintPkgIdle(pkg); + + var testDiv = _getElementById(testCase); + + //Make it look idle + testDiv.className = "sweety-test sweety-idle"; + + if (sweetyTestCases[testCase]) { + if (typeof _pkgs[pkg] == "undefined") { + testDiv.style.display = "block"; + } else if (_pkgs[pkg]) { + testDiv.style.display = "block"; + } + } else { + testDiv.style.display = "none"; + } + } + + //Show or hide any packages + for (var pkgName in _pkgTests) { + var display = false; + var len = 0; + for (var testCase in _pkgTests[pkgName]) { + if (_pkgTests[pkgName][testCase]) { + display = true; + //break; + len++; + } + } + this.showPkgCount(pkgName, len); + this.showHidePkg(pkgName, display); + } + } + + /** + * Shows or hides the headers for the given package. + * @param {String} pkg + * @param {Boolean} show + */ + this.showHidePkg = function showHidePkg(pkg, show) { + var pkgDiv = _getElementById("sweety-package-" + pkg); + if (show) { + pkgDiv.style.display = "block"; + } else { + pkgDiv.style.display = "none"; + } + } + + this.showPkgCount = function showPkgCount(pkg, n) { + var countBox = _getElementById("sweety-pkg-count-" + pkg); + _setContent(countBox, "(" + n + ")"); + } + + /** + * Reset all the aggregate results in the UI. + */ + this.resetTotals = function resetTotals() { + _this.paintNumCases(0); + _this.paintNumRun(0); + _this.paintNumPasses(0); + _this.paintNumFails(0); + _this.paintNumExceptions(0); + + _this.paintAllIdle(); + } + + /** + * Paint or unpaint the networking icon to indicate communication with the server. + * @param {Boolean} on + */ + this.paintNetworking = function paintNetworking(on) { + if (on) { + _getCommIcon().style.display = "block"; + } else { + _getCommIcon().style.display = "none"; + } + } + + /** + * Flush the contents of the assertion message area. + */ + this.resetMessageDiv = function resetMessageDiv() { + _getMessages().innerHTML = ""; + _getElementById("sweety-smoke-images").innerHTML = ""; + } + + /** + * Marks a given package as running (yellow) in the UI. + * @param {String} pkg + */ + this.paintPkgRunning = function paintPkgRunning(pkg) { + _getElementById("sweety-package-" + pkg).className = "sweety-package-header sweety-running"; + } + + /** + * Marks a given package as idle (grey) in the UI. + * @param {String} pkg + */ + this.paintPkgIdle = function paintPkgIdle(pkg) { + _getElementById("sweety-package-" + pkg).className = "sweety-package-header sweety-pkg-idle"; + } + + /** + * Marks a given package as passed (green) in the UI. + * @param {String} pkg + */ + this.paintPkgPassed = function paintPkgPassed(pkg) { + _getElementById("sweety-package-" + pkg).className = "sweety-package-header sweety-pass"; + } + + /** + * Marks a given package as failed (red) in the UI. + * @param {String} pkg + */ + this.paintPkgFailed = function paintPkgFailed(pkg) { + _getElementById("sweety-package-" + pkg).className = "sweety-package-header sweety-fail"; + } + + /** + * Marks a given test case as running (yellow) in the UI. + * @param {String} testCase + */ + this.paintTestCaseRunning = function paintTestCaseRunning(testCase) { + _getElementById(testCase).className = "sweety-test sweety-running"; + } + + /** + * Marks a given test case as idle (grey) in the UI. + * @param {String} testCase + */ + this.paintTestCaseIdle = function paintTestCaseIdle(testCase) { + _getElementById(testCase).className = "sweety-test sweety-idle"; + } + + /** + * Marks a given test case as failed (red) in the UI. + * @param {String} testCase + */ + this.paintTestCaseFailed = function paintTestCaseFailed(testCase) { + _getElementById(testCase).className = "sweety-test sweety-fail"; + } + + /** + * Marks a given test case as passed (green) in the UI. + * @param {String} testCase + */ + this.paintTestCasePassed = function paintTestCasePassed(testCase) { + _getElementById(testCase).className = "sweety-test sweety-pass"; + } + + /** + * Paints a skipped testcase message to the message area. + * @param {String} message + * @param {String} path + */ + this.paintSkip = function paintSkip(message, path) { + var skipDiv = document.createElement("div"); + skipDiv.className = "sweety-message"; + + var skipLabel = _createSkipLabel("Skip"); + skipDiv.appendChild(skipLabel); + + var messageSpan = document.createElement("strong"); + _setContent(messageSpan, ": " + message); + skipDiv.appendChild(messageSpan); + + var pathDiv = _createPathDiv(path); + skipDiv.appendChild(pathDiv); + + _getMessages().appendChild(skipDiv); + } + + /** + * Paints an unexpected exception notice to the message area. + * @param {String} message + * @param {String} path + */ + this.paintException = function paintException(message, path) { + var exceptionDiv = document.createElement("div"); + exceptionDiv.className = "sweety-message"; + + var exceptionLabel = _createFailLabel("Exception"); + exceptionDiv.appendChild(exceptionLabel); + + var messageSpan = document.createElement("strong"); + _setContent(messageSpan, ": " + message); + exceptionDiv.appendChild(messageSpan); + + var pathDiv = _createPathDiv(path); + exceptionDiv.appendChild(pathDiv); + + _getMessages().appendChild(exceptionDiv); + } + + /** + * Paints a failed assertion message to the message area. + * @param {String} message + * @param {String} path + */ + this.paintFail = function paintFail(message, path) { + var failDiv = document.createElement("div"); + failDiv.className = "sweety-message"; + + var failLabel = _createFailLabel("Fail"); + failDiv.appendChild(failLabel); + + var messageSpan = document.createElement("span"); + _setContent(messageSpan, ": " + message); + failDiv.appendChild(messageSpan); + + var pathDiv = _createPathDiv(path); + failDiv.appendChild(pathDiv); + + _getMessages().appendChild(failDiv); + } + + /** + * Paints dump() output to the message area. + * @param {String} output + * @param {String} path + */ + this.paintOutput = function paintOutput(output, path) { + var refs; + if (refs = /^\{image @ (.*?)\}$/.exec(output)) { + this.paintSmokeImage(refs[1]); + } else { + var outputPane = document.createElement("pre"); + outputPane.className = "sweety-raw-output"; + _setContent(outputPane, output); + + _getMessages().appendChild(outputPane); + } + } + + this.paintSmokeImage = function paintSmokeImage(imageSrc) { + var imagePane = _getElementById("sweety-smoke-images"); + var smokeImg = document.createElement("img"); + smokeImg.title = 'Smoke test image'; + smokeImg.src = imageSrc; + smokeImg.style.cursor = 'pointer'; + smokeImg.onclick = function() { window.open(imageSrc); }; + imagePane.appendChild(smokeImg); + } + + /** + * Paints an internal message to the message area. + * @param {String} message + * @param {String} path + */ + this.paintMessage = function paintMessage(message) { + var messageDiv = document.createElement("div"); + messageDiv.className = "sweety-message sweety-running"; + _setContent(messageDiv, message); + + _getMessages().appendChild(messageDiv); + } + + /** + * Paints the current number of test cases to the summary bar. + * @param {Number} num + */ + this.paintNumCases = function paintNumCases(num) { + _setContent(_getCases(), num); + } + + /** + * Paints the current number of finished test cases to the summary bar. + * @param {Number} num + */ + this.paintNumRun = function paintNumRun(num) { + _setContent(_getComplete(), num); + } + + /** + * Paints the current number of passing assertions to the summary bar. + * @param {Number} num + */ + this.paintNumPasses = function paintNumPasses(num) { + _setContent(_getPasses(), num); + } + + /** + * Paints the current number of failing assertions to the summary bar. + * @param {Number} num + */ + this.paintNumFails = function paintNumFails(num) { + _setContent(_getFails(), num); + } + + /** + * Paints the current number of exceptions to the summary bar. + * @param {Number} num + */ + this.paintNumExceptions = function paintNumExceptions(num) { + _setContent(_getExceptions(), num); + } + + /** + * Paints the summary bar (green) as passed. + */ + this.paintConclusionPassed = function paintConclusionPassed() { + _getResultsBar().className = "sweety-pass"; + } + + /** + * Paints the summary bar (red) as failed. + */ + this.paintConclusionFailed = function paintConclusionFailed() { + _getResultsBar().className = "sweety-fail"; + } + + /** + * Paints the summary bar (yellow) as running. + */ + this.paintAllRunning = function paintAllRunning() { + _getResultsBar().className = "sweety-running"; + } + + /** + * Paints the summary bar (grey) as idle. + */ + this.paintAllIdle = function paintAllIdle() { + _getResultsBar().className = "sweety-idle"; + } + + /** + * Puts the filter box in searching L&F. + */ + this.paintSearching = function paintSearching() { + _getFilter().className = "sweety-text sweety-waiting"; + } + + /** + * Returns the filter box the idle L&F. + */ + this.paintSearchComplete = function paintSearchComplete() { + _getFilter().className = "sweety-text"; + } + + /** + * Apply data to a page element. + * @param {Element} el + * @param {String} content + */ + var _setContent = function _setContent(el, content) { + if (typeof el.textContent != "undefined") { + el.textContent = content; + } else { + el.innerHTML = content; + } + } + + /** + * Create a label used at the start of a message to indicate a skipped test case. + * @param {String} label + * @returns HTMLSpanElement + */ + var _createSkipLabel = function _createSkipLabel(label) { + var skipLabel = document.createElement("span"); + skipLabel.className = "sweety-skip-text"; + _setContent(skipLabel, label); + return skipLabel; + } + + /** + * Create a label used at the start of a message to indicate failure. + * @param {String} label + * @returns HTMLSpanElement + */ + var _createFailLabel = function _createFailLabel(label) { + var failLabel = document.createElement("span"); + failLabel.className = "sweety-fail-text"; + _setContent(failLabel, label); + return failLabel; + } + + /** + * Creates the text which shows the complete pathway to a test method. + * The path includes all groups, the test case and the test method. + * @param {String} path + * @returns HTMLDivElement + */ + var _createPathDiv = function _createPathDiv(path) { + var pathDiv = document.createElement("div"); + pathDiv.className = "sweety-test-path"; + _setContent(pathDiv, "in " + path); + return pathDiv; + } + + var _paintFilterDisabled = function _paintFilterDisabled(disabled) { + if (disabled) { + _getFilter().disabled = true; + _getFilter().className = "sweety-text sweety-disabled"; + } else { + _getFilter().disabled = false; + _getFilter().className = "sweety-text"; + } + } + + /** + * A caching wrapper around document.getElementById(). + * @param {String} id + * @returns Element + */ + var _getElementById = function _getElementById(elId) { + if (!_cached[elId]) { + _cached[elId] = document.getElementById(elId); + } + return _cached[elId]; + } + + /** + * Get the icon which shows network activity. + * @returns Element + */ + var _getCommIcon = function _getCommIcon() { + return _getElementById("sweety-communication"); + } + + /** + * Get the container which holds the list of test cases. + * @returns Element + */ + var _getListContainer = function _getListContainer() { + return _getElementById("sweety-testlist-container"); + } + + /** + * Get the container where all assertion messages go. + * @returns Element + */ + var _getMessages = function _getMessages() { + return _getElementById("sweety-messages"); + } + + /** + * Get the element for number of test cases. + * @returns Element + */ + var _getCases = function _getCases() { + return _getElementById("sweety-num-cases"); + } + + /** + * Get the container for number of test cases finished. + * @returns Element + */ + var _getComplete = function _getComplete() { + return _getElementById("sweety-num-run"); + } + + /** + * Get the container for number of exceptions. + * @returns Element + */ + var _getExceptions = function _getExceptions() { + return _getElementById("sweety-num-exceptions"); + } + + /** + * Get the container for number of fails. + * @returns Element + */ + var _getFails = function _getFails() { + return _getElementById("sweety-num-fails"); + } + + /** + * Get the container for number of passes. + * @returns Element + */ + var _getPasses = function _getPasses() { + return _getElementById("sweety-num-passes"); + } + + + /** + * Get the bar showing aggregate results. + * @returns Element + */ + var _getResultsBar = function _getResutsBar() { + return _getElementById("sweety-results"); + } + + /** + * Get the filter input box. + * @returns Element + */ + var _getFilter = function _getFilter() { + return _getElementById("sweety-filter"); + } + + /** + * Get the button which operates the filter. + * @returns Element + */ + var _getRunButton = function _getRunButton() { + return _getElementById("sweety-run-button"); + } + + var _loadPkgsFromCookie = function _loadPkgsFromCookie() { + for (var testCase in sweetyTestCases) { + var pkg = _pkgFor(testCase); + _pkgs[pkg] = false; + } + var cookieBits = document.cookie.split(/\s*;\s*/g); + for (var i in cookieBits) { + if (cookieBits[i].substring(0, 9) != "sweetyPkg") + { + continue; + } + var nvp = cookieBits[i].substring(9).split('='); + _pkgs[unescape(nvp[0])] = (nvp[1] == "0") ? false : true; + //alert(unescape(nvp[0]) + " => " + _pkgs[unescape(nvp[0])]); + } + } + + var _pkgFor = function _pkgFor(testName) { + return testName.replace(/_?[^_]+$/, ""); + } + +} + +//Create an instance of the UI Manager for usage +var sweetyUI = new SweetyUIManager(); + + +/** + * A filter to hide/show test cases in the list. + * @author Chris Corbyn + * @consructor + */ +function SweetyFilter() { + + var _this = this; + + /** Asynchronous page timer (so nothing happens whilst typing) */ + var _timer; + + /** The sweety-filter element, lazy loaded */ + var _filter = null; + + /** + * Update the display once the search is complete. + */ + this.repaintUI = function repaintUI() { + sweetyUI.initialize(); + sweetyUI.paintSearchComplete(); + } + + /** + * Search for matching test cases. + */ + this.search = function search() { + sweetyUI.paintSearching(); + + var query = _getFilterInput().value.toLowerCase(); + var queryBits = query.split(/[^\!a-zA-Z0-9_]+/g); + + //Cancel searching if still typing + try { + window.clearTimeout(_timer); + } catch (e) { } + + for (var testCase in sweetyTestCases) { + for (var i in queryBits) { + var testFor = queryBits[i]; + var isNegated = ("!" == testFor.charAt(0)); + if (isNegated) { + testFor = testFor.substring(1); + } + + if (!isNegated && 0 > testCase.toLowerCase().indexOf(testFor)) { + sweetyTestCases[testCase] = false; + break; + } else if (isNegated && 0 < testCase.toLowerCase().indexOf(testFor)) { + sweetyTestCases[testCase] = false; + break; + } else { + sweetyTestCases[testCase] = true; + } + } + } + + //Only apply the search in 500ms, since user may be typing + _timer = window.setTimeout(_this.repaintUI, 500); + } + + /** + * Get a lazy loaded reference to the input element. + * @return HTMLInputElement + */ + var _getFilterInput = function _getFilterInput() { + if (!_filter) { + _filter = document.getElementById("sweety-filter"); + } + return _filter; + } + +} + +//Create a new instance of the filter +var sweetyFilter = new SweetyFilter(); + +/** + * The reporter which gathers aggregate results and displays a summary. + * @author Chris Corbyn + * @constructor + * @param {Boolean} reportPkgs if package status should be reported + */ +function SweetyTemplateAggregateReporter(testCaseList, reportPkgs) { + + var _this = this; + + /** True if this reporter instance is running now */ + var _started = false; + + /** Aggregate totals */ + var _aggregates = { cases : 0, run: 0, passes : 0, fails : 0, exceptions : 0 }; + + /** Aggregates per-package */ + var _pkgs = { }; + + /** Currently running package */ + var _currentPkg; + + /** + * Creates a reporter for the given testCase. + * @param {String} testCase + * @returns SweetyReporter + */ + this.getReporterFor = function getReporterFor(testCase) { + _aggregates.cases++; + + if (reportPkgs) { + var pkg = _getPkgName(testCase); + sweetyUI.paintPkgRunning(pkg); + + _pkgs[pkg].cases++; + + if (_currentPkg && _currentPkg != pkg) { + _updatePkgStatus(_currentPkg); + } + + _currentPkg = pkg; + } + + sweetyUI.paintNumCases(_aggregates.cases); + + var reporter = new SweetyTemplateCaseReporter(testCase, _this); + return reporter; + } + + /** + * Updates the UI with the new aggregate totals. + */ + this.notifyEnded = function notifyEnded(testCase) { + _aggregates.run++; + + if (reportPkgs) { + var pkg = _getPkgName(testCase); + _pkgs[pkg].run++; + } + + //Update the UI with new totals + sweetyUI.paintNumRun(_aggregates.run); + sweetyUI.paintNumPasses(_aggregates.passes); + sweetyUI.paintNumFails(_aggregates.fails); + sweetyUI.paintNumExceptions(_aggregates.exceptions); + } + + /** + * Returns true if this reporter instance is running. + * @returns Boolean + */ + this.isStarted = function isStarted() { + return _started; + } + + /** + * Start reporting. + */ + this.start = function start() { + _started = true; + + if (reportPkgs) + { + for (var i = 0, len = testCaseList.length; i < len; i++) { + var testCase = testCaseList[i]; + var pkg = _getPkgName(testCase); + if (typeof _pkgs[pkg] == "undefined") { + _pkgs[pkg] = { cases : 0, run : 0, passes : 0, fails : 0, exceptions : 0 }; + } + } + } + + sweetyUI.allowInteractivity(false); + sweetyUI.paintNetworking(true); + sweetyUI.paintAllRunning(); + } + + /** + * Report a skipped test case. + * @param {String} message + * @param {String} path + */ + this.reportSkip = function reportSkip(message, path) { + sweetyUI.paintSkip(message, path); + } + + /** + * Report a passing assertion. + * @param {String} message + * @param {String} path + */ + this.reportPass = function reportPass(message, path) { + _aggregates.passes++; + + if (reportPkgs) { + _pkgs[_currentPkg].passes++; + } + } + + /** + * Report a failing assertion. + * @param {String} message + * @param {String} path + */ + this.reportFail = function reportFail(message, path) { + _aggregates.fails++; + + if (reportPkgs) { + _pkgs[_currentPkg].fails++; + } + + sweetyUI.paintFail(message, path); + } + + /** + * Report an unexpected exception. + * @param {String} message + * @param {String} path + */ + this.reportException = function reportException(message, path) { + _aggregates.exceptions++; + + if (reportPkgs) { + _pkgs[_currentPkg].exceptions++; + } + + sweetyUI.paintException(message, path); + } + + /** + * Handle test case output from something like a dump(). + * @param {String} output + * @param {String} path + */ + this.reportOutput = function reportOutput(output, path) { + sweetyUI.paintOutput(output, path); + } + + /** + * End reporting. + * This method is used to come to a conclusion about the test results in the UI. + */ + this.finish = function finish() { + _started = false; + + if (reportPkgs) { + _updatePkgStatus(_currentPkg); + } + + sweetyUI.allowInteractivity(true); + + sweetyUI.paintNetworking(false); + + if ((!_aggregates.fails && !_aggregates.exceptions) + && (_aggregates.cases == _aggregates.run)) { + sweetyUI.paintConclusionPassed(); + } else { + sweetyUI.paintConclusionFailed(); + } + + var incompleteCount = _aggregates.cases - _aggregates.run; + + //Check if all tests actually got fully parsed (i.e. finished) + if (0 < incompleteCount) { + sweetyUI.paintMessage( + incompleteCount + " test case(s) did not complete." + + " This may be because invalid XML was output during the test run" + + " and/or because an error occured." + + " Incomplete test cases are shown in yellow. Click the HTML link " + + "next to the test for more detail."); + } + } + + var _getPkgName = function _getPkgName(testCase) { + return testCase.replace(/_?[^_]+$/, ""); + } + + var _updatePkgStatus = function _updatePkgStatus(pkg) { + if ((!_pkgs[pkg].fails && !_pkgs[pkg].exceptions) + && (_pkgs[pkg].cases == _pkgs[pkg].run)) { + sweetyUI.paintPkgPassed(pkg); + } else if (_pkgs[pkg].cases == _pkgs[pkg].run) { + sweetyUI.paintPkgFailed(pkg); + } + } + +} +SweetyTemplateAggregateReporter.prototype = new SweetyReporter(); + +/** + * The reporter class per-test case. + * @author Chris Corbyn + * @consructor + */ +function SweetyTemplateCaseReporter(testCase, reporter) { + + var _this = this; + + /** Aggregate totals */ + var _aggregates = { passes : 0, fails : 0, exceptions : 0 }; + + /** The DIV element showing this test case */ + var _testCaseDiv = document.getElementById(testCase); + + /** True only if this reporter is running */ + var _started = false; + + /** + * Stubbed only to return itself. + * @returns SweetyReporter + */ + this.getReporterFor = function getReporterFor(testCase) { + return _this; + } + + /** + * Returns true when the reporter is started. + * @returns Boolean + */ + this.isStarted = function isStarted() { + return _started; + } + + /** + * Start reporting. + */ + this.start = function start() { + _started = true; + sweetyUI.paintTestCaseRunning(testCase); + } + + /** + * Report a skipped test case. + * @param {String} message + * @param {String} path + */ + this.reportSkip = function reportSkip(message, path) { + reporter.reportSkip(message, path); + } + + /** + * Report a passing assertion. + * @param {String} message + * @param {String} path + */ + this.reportPass = function reportPass(message, path) { + _aggregates.passes++; + reporter.reportPass(message, path); + } + + /** + * Report a failing assertion. + * @param {String} message + * @param {String} path + */ + this.reportFail = function reportFail(message, path) { + _aggregates.fails++; + reporter.reportFail(message, path); + } + + /** + * Report an unexpected exception. + * @param {String} message + * @param {String} path + */ + this.reportException = function reportException(message, path) { + _aggregates.exceptions++; + reporter.reportException(message, path); + } + + /** + * Handle output from a test case in the form of something like a dump(). + * @param {String} output + * @param {string} path + */ + this.reportOutput = function reportOutput(output, path) { + reporter.reportOutput(output, path); + } + + /** + * End reporting. + */ + this.finish = function finish() { + _started = false; + + if (!_aggregates.fails && !_aggregates.exceptions) { + sweetyUI.paintTestCasePassed(testCase); + } else { + sweetyUI.paintTestCaseFailed(testCase); + } + + reporter.notifyEnded(testCase); + } + +} +SweetyTemplateCaseReporter.prototype = new SweetyReporter(); + +/** + * Wraps the invokation of SweetyTestRunner. + * @author Chris Corbyn + * @constructor + */ +function SweetyTestWrapper() { + + var _this = this; + + /** + * Run a single test case. + * @param {String} testClass + */ + this.runTestCase = function runTestCase(testClass) { + var testCaseList = new Array(); + testCaseList.push(testClass); + + var reporter = new SweetyTemplateAggregateReporter(testCaseList); + + var runner = new SweetyTestRunner(); + runner.runTests(testCaseList, reporter); + } + + /** + * Run all selected test cases. + */ + this.runAll = function runAll(pkg) { + var pkgRegex; + if (pkg) { + pkgRegex = new RegExp("^" + pkg + "_[^_]+$"); + } + + var testCaseList = new Array(); + + for (var testCase in sweetyTestCases) { + if (!sweetyTestCases[testCase] || (pkg && !testCase.match(pkgRegex))) { + continue; + } + testCaseList.push(testCase); + } + + var reporter = new SweetyTemplateAggregateReporter(testCaseList, true); + + var runner = new SweetyTestRunner(); + runner.runTests(testCaseList, reporter); + } + +} + +//Create an instance of the test runner for usage +var sweetyRunner = new SweetyTestWrapper(); + +if (typeof document.onreadystatechange != "undefined") { //IE 6/7 + document.onreadystatechange = function() { + if (document.readyState == "complete") { + sweetyUI.restore(); sweetyUI.initialize(); + } + }; +} else { //Fallback + window.onload = function() { + sweetyUI.restore(); sweetyUI.initialize(); + }; + + try { //FF + document.addEventListener("DOMContentLoaded", window.onload, false); + } catch (e) { + } +} diff --git a/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/suite-ui-noajax.tpl.php b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/suite-ui-noajax.tpl.php new file mode 100644 index 0000000..b9d3ef1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/suite-ui-noajax.tpl.php @@ -0,0 +1,155 @@ + + + + <?php echo $suiteName; ?> - No AJAX + + + +
    + +
    + +
    + +
    + +
    + + +
    + + +
    + + + + + +
    + Not available + Tests + + + +
    + + +
    + +
    + + As XML + As HTML + Run + + + checked="checked" + /> + + + + + + + +
    + +
    + + + +
    + +
    + +
    + +
    + +
    + +
    + +

    - No AJAX

    + +
    + / + test cases complete: + passes, + fails and + exceptions. +
    + +
    + +
    + Skip: + +
    + in +
    +
    + +
    + Fail: +
    + in +
    +
    + +
    + Exception: + +
    + in +
    +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/suite-ui.tpl.php b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/suite-ui.tpl.php new file mode 100644 index 0000000..90aa84c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/templates/sweety/suite-ui.tpl.php @@ -0,0 +1,132 @@ + + + + + <?php echo $suiteName; ?> + + + + + + + + +
    + +
    + +
    + +
    + +
    + + +
    + + +
    + + + + + +
    + + Toggle Display + Tests + + + +
    + + +
    + +
    + + As XML + As HTML + Run + + + checked="checked" + /> + + + + + + + +
    + +
    + + + +
    + +
    + +
    + +
    + +
    + +
    + +
    + Communicating +
    + +

    + +
    + 0/0 + test cases complete: + 0 passes, + 0 fails and + 0 exceptions. +
    + +
    +
    + +
    +
    + +
    + +
    + +
    + +
    + + diff --git a/vendor/swiftmailer/swiftmailer/test-suite/xpath-legacy.js b/vendor/swiftmailer/swiftmailer/test-suite/xpath-legacy.js new file mode 100644 index 0000000..38c3627 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/test-suite/xpath-legacy.js @@ -0,0 +1,2764 @@ +/* JavaScript-XPath 0.1.5 + * (c) 2007 Cybozu Labs, Inc. + * + * JavaScript-XPath is freely distributable under the terms of an MIT-style license. + * For details, see the JavaScript-XPath web site: http://coderepos.org/share/wiki/JavaScript-XPath + * +/*--------------------------------------------------------------------------*/ + +if (!document.implementation + || !document.implementation.hasFeature + || !document.implementation.hasFeature("XPath", null)) (function() { + +var undefined = void(0); + + +var defaultConfig = { + targetFrame: undefined +}; + +var config; + +if (window.jsxpath) { + config = window.jsxpath; +} +else { + var scriptElms = document.getElementsByTagName('script'); + var scriptElm = scriptElms[scriptElms.length - 1]; + var scriptSrc = scriptElm.src; + config = {}; + var scriptSrcMatchResult = scriptSrc.match(/\?(.*)$/); + if (scriptSrcMatchResult) { + var configStrings = scriptSrcMatchResult[1].split('&'); + for (var i = 0, l = configStrings.length; i < l; i ++) { + var configString = configStrings[i]; + var configStringSplited = configString.split('='); + config[configStringSplited[0]] = configStringSplited[1] || true; + } + } +} + +for (var n in defaultConfig) { + if (!(n in config)) config[n] = defaultConfig[n] +} + + +var BinaryExpr; +var FilterExpr; +var FunctionCall; +var Literal; +var NameTest; +var NodeSet; +var NodeType; +var NodeUtil; +var Number; +var PathExpr; +var Step; +var UnaryExpr; +var UnionExpr; +var VariableReference; + +/* + * object: user agent identifier + */ +var uai = new function() { + + var ua = navigator.userAgent; + + if (RegExp == undefined) { + if (ua.indexOf("Opera") >= 0) { + this.opera = true; + } else if (ua.indexOf("Netscape") >= 0) { + this.netscape = true; + } else if (ua.indexOf("Mozilla/") == 0) { + this.mozilla = true; + } else { + this.unknown = slide + } + + if (ua.indexOf("Gecko/") >= 0) { + this.gecko = true; + } + + if (ua.indexOf("Win") >= 0) { + this.windows = true; + } else if (ua.indexOf("Mac") >= 0) { + this.mac = true; + } else if (ua.indexOf("Linux") >= 0) { + this.linux = true; + } else if (ua.indexOf("BSD") >= 0) { + this.bsd = true; + } else if (ua.indexOf("SunOS") >= 0) { + this.sunos = true; + } + } + else { + + /* for Trident/Tasman */ + /*@cc_on + @if (@_jscript) + function jscriptVersion() { + switch (@_jscript_version) { + case 3.0: return "4.0"; + case 5.0: return "5.0"; + case 5.1: return "5.01"; + case 5.5: return "5.5"; + case 5.6: + if ("XMLHttpRequest" in window) return "7.0"; + return "6.0"; + case 5.7: + return "7.0"; + default: return true; + } + } + if (@_win16 || @_win32 || @_win64) { + this.windows = true; + this.trident = jscriptVersion(); + } else if (@_mac || navigator.platform.indexOf("Mac") >= 0) { + // '@_mac' may be 'NaN' even if the platform is Mac, + // so we check 'navigator.platform', too. + this.mac = true; + this.tasman = jscriptVersion(); + } + if (match = ua.match("MSIE ?(\\d+\\.\\d+)b?;")) { + this.ie = match[1]; + this['ie' + match[1].charAt(0)] = true; + } + @else @*/ + + /* for AppleWebKit */ + if (match = ua.match("AppleWebKit/(\\d+(\\.\\d+)*)")) { + this.applewebkit = match[1]; + this['applewebkit' + match[1].charAt(0)] = true; + } + + /* for Gecko */ + else if (typeof(Components) == "object") { + if (match = ua.match("Gecko/(\\d{8})")) { + this.gecko = match[1]; + } else if (navigator.product == "Gecko" + && (match = navigator.productSub.match("^(\\d{8})$"))) { + this.gecko = match[1]; + } + } + + /*@end @*/ + + if (typeof(opera) == "object" && typeof(opera.version) == "function") { + this.opera = opera.version(); + this['opera' + this.opera[0] + this.opera[2]] = true; + + } else if (typeof(opera) == "object" + && (match = ua.match("Opera[/ ](\\d+\\.\\d+)"))) { + this.opera = match[1]; + } else if (this.ie) { + } else if (match = ua.match("Safari/(\\d+(\\.\\d+)*)")) { + this.safari = match[1]; + } else if (match = ua.match("Konqueror/(\\d+(\\.\\d+)*)")) { + this.konqueror = match[1]; + } else if (ua.indexOf("(compatible;") < 0 + && (match = ua.match("^Mozilla/(\\d+\\.\\d+)"))) { + this.mozilla = match[1]; + if (match = ua.match("\\([^(]*rv:(\\d+(\\.\\d+)*).*?\\)")) + this.mozillarv = match[1]; + if (match = ua.match("Firefox/(\\d+(\\.\\d+)*)")) { + this.firefox = match[1]; + } else if (match = ua.match("Netscape\\d?/(\\d+(\\.\\d+)*)")) { + this.netscape = match[1]; + } + } else { + this.unknown = true; + } + + if (ua.indexOf("Win 9x 4.90") >= 0) { + this.windows = "ME"; + } else if (match = ua.match("Win(dows)? ?(NT ?(\\d+\\.\\d+)?|\\d+|XP|ME|Vista)")) { + this.windows = match[2]; + if (match[3]) { + this.winnt = match[3]; + } else switch (match[2]) { + case "2000": this.winnt = "5.0"; break; + case "XP": this.winnt = "5.1"; break; + case "Vista": this.winnt = "6.0"; break; + } + } else if (ua.indexOf("Mac") >= 0) { + this.mac = true; + } else if (ua.indexOf("Linux") >= 0) { + this.linux = true; + } else if (match = ua.match("\\w*BSD")) { + this.bsd = match[0]; + } else if (ua.indexOf("SunOS") >= 0) { + this.sunos = true; + } + } +}; + + +/** + * pseudo class: Lexer + */ +var Lexer = function(source) { + var proto = Lexer.prototype; + var tokens = source.match(proto.regs.token); + for (var i = 0, l = tokens.length; i < l; i ++) { + if (proto.regs.strip.test(tokens[i])) { + tokens.splice(i, 1); + } + } + for (var n in proto) tokens[n] = proto[n]; + tokens.index = 0; + return tokens; +}; + +Lexer.prototype.regs = { + token: /\$?(?:(?![0-9-])[\w-]+:)?(?![0-9-])[\w-]+|\/\/|\.\.|::|\d+(?:\.\d*)?|\.\d+|"[^"]*"|'[^']*'|[!<>]=|(?![0-9-])[\w-]+:\*|\s+|./g, + strip: /^\s/ +}; + +Lexer.prototype.peek = function(i) { + return this[this.index + (i||0)]; +}; +Lexer.prototype.next = function() { + return this[this.index++]; +}; +Lexer.prototype.back = function() { + this.index--; +}; +Lexer.prototype.empty = function() { + return this.length <= this.index; +}; + + +/** + * class: Ctx + */ +var Ctx = function(node, position, last) { + this.node = node; + this.position = position || 1; + this.last = last || 1; +}; + + +/** + * abstract class: BaseExpr + */ +var BaseExpr = function() {}; + +BaseExpr.prototype.number = function(ctx) { + var exrs = this.evaluate(ctx); + if (exrs.isNodeSet) return exrs.number(); + return + exrs; +}; + +BaseExpr.prototype.string = function(ctx) { + var exrs = this.evaluate(ctx); + if (exrs.isNodeSet) return exrs.string(); + return '' + exrs; +}; + +BaseExpr.prototype.bool = function(ctx) { + var exrs = this.evaluate(ctx); + if (exrs.isNodeSet) return exrs.bool(); + return !! exrs; +}; + + +/** + * abstract class: BaseExprHasPredicates + */ +var BaseExprHasPredicates = function() {}; + +BaseExprHasPredicates.parsePredicates = function(lexer, expr) { + while (lexer.peek() == '[') { + lexer.next(); + if (lexer.empty()) { + throw Error('missing predicate expr'); + } + var predicate = BinaryExpr.parse(lexer); + expr.predicate(predicate); + if (lexer.empty()) { + throw Error('unclosed predicate expr'); + } + if (lexer.next() != ']') { + lexer.back(); + throw Error('bad token: ' + lexer.next()); + } + } +}; + +BaseExprHasPredicates.prototyps = new BaseExpr(); + +BaseExprHasPredicates.prototype.evaluatePredicates = function(nodeset, start) { + var predicates, predicate, nodes, node, nodeset, position, reverse; + + reverse = this.reverse; + predicates = this.predicates; + + nodeset.sort(); + + for (var i = start || 0, l0 = predicates.length; i < l0; i ++) { + predicate = predicates[i]; + + var deleteIndexes = []; + var nodes = nodeset.list(); + + for (var j = 0, l1 = nodes.length; j < l1; j ++) { + + position = reverse ? (l1 - j) : (j + 1); + exrs = predicate.evaluate(new Ctx(nodes[j], position, l1)); + + switch (typeof exrs) { + case 'number': + exrs = (position == exrs); + break; + case 'string': + exrs = !!exrs; + break; + case 'object': + exrs = exrs.bool(); + break; + } + + if (!exrs) { + deleteIndexes.push(j); + } + } + + for (var j = deleteIndexes.length - 1, l1 = 0; j >= l1; j --) { + nodeset.del(deleteIndexes[j]); + } + + } + + return nodeset; +}; + + +/** + * class: BinaryExpr + */ +if (!window.BinaryExpr && window.defaultConfig) + window.BinaryExpr = null; + +BinaryExpr = function(op, left, right, datatype) { + this.op = op; + this.left = left; + this.right = right; + + this.datatype = BinaryExpr.ops[op][2]; + + this.needContextPosition = left.needContextPosition || right.needContextPosition; + this.needContextNode = left.needContextNode || right.needContextNode; + + // Optimize [@id="foo"] and [@name="bar"] + if (this.op == '=') { + if (!right.needContextNode && !right.needContextPosition && + right.datatype != 'nodeset' && right.datatype != 'void' && left.quickAttr) { + this.quickAttr = true; + this.attrName = left.attrName; + this.attrValueExpr = right; + } + else if (!left.needContextNode && !left.needContextPosition && + left.datatype != 'nodeset' && left.datatype != 'void' && right.quickAttr) { + this.quickAttr = true; + this.attrName = right.attrName; + this.attrValueExpr = left; + } + } +}; + +BinaryExpr.compare = function(op, comp, left, right, ctx) { + var type, lnodes, rnodes, nodes, nodeset, primitive; + + left = left.evaluate(ctx); + right = right.evaluate(ctx); + + if (left.isNodeSet && right.isNodeSet) { + lnodes = left.list(); + rnodes = right.list(); + for (var i = 0, l0 = lnodes.length; i < l0; i ++) + for (var j = 0, l1 = rnodes.length; j < l1; j ++) + if (comp(NodeUtil.to('string', lnodes[i]), NodeUtil.to('string', rnodes[j]))) + return true; + return false; + } + + if (left.isNodeSet || right.isNodeSet) { + if (left.isNodeSet) + nodeset = left, primitive = right; + else + nodeset = right, primitive = left; + + nodes = nodeset.list(); + type = typeof primitive; + for (var i = 0, l = nodes.length; i < l; i ++) { + if (comp(NodeUtil.to(type, nodes[i]), primitive)) + return true; + } + return false; + } + + if (op == '=' || op == '!=') { + if (typeof left == 'boolean' || typeof right == 'boolean') { + return comp(!!left, !!right); + } + if (typeof left == 'number' || typeof right == 'number') { + return comp(+left, +right); + } + return comp(left, right); + } + + return comp(+left, +right); +}; + + +BinaryExpr.ops = { + 'div': [6, function(left, right, ctx) { + return left.number(ctx) / right.number(ctx); + }, 'number'], + 'mod': [6, function(left, right, ctx) { + return left.number(ctx) % right.number(ctx); + }, 'number'], + '*': [6, function(left, right, ctx) { + return left.number(ctx) * right.number(ctx); + }, 'number'], + '+': [5, function(left, right, ctx) { + return left.number(ctx) + right.number(ctx); + }, 'number'], + '-': [5, function(left, right, ctx) { + return left.number(ctx) - right.number(ctx); + }, 'number'], + '<': [4, function(left, right, ctx) { + return BinaryExpr.compare('<', + function(a, b) { return a < b }, left, right, ctx); + }, 'boolean'], + '>': [4, function(left, right, ctx) { + return BinaryExpr.compare('>', + function(a, b) { return a > b }, left, right, ctx); + }, 'boolean'], + '<=': [4, function(left, right, ctx) { + return BinaryExpr.compare('<=', + function(a, b) { return a <= b }, left, right, ctx); + }, 'boolean'], + '>=': [4, function(left, right, ctx) { + return BinaryExpr.compare('>=', + function(a, b) { return a >= b }, left, right, ctx); + }, 'boolean'], + '=': [3, function(left, right, ctx) { + return BinaryExpr.compare('=', + function(a, b) { return a == b }, left, right, ctx); + }, 'boolean'], + '!=': [3, function(left, right, ctx) { + return BinaryExpr.compare('!=', + function(a, b) { return a != b }, left, right, ctx); + }, 'boolean'], + 'and': [2, function(left, right, ctx) { + return left.bool(ctx) && right.bool(ctx); + }, 'boolean'], + 'or': [1, function(left, right, ctx) { + return left.bool(ctx) || right.bool(ctx); + }, 'boolean'] +}; + + +BinaryExpr.parse = function(lexer) { + var op, precedence, info, expr, stack = [], index = lexer.index; + + while (true) { + + if (lexer.empty()) { + throw Error('missing right expression'); + } + expr = UnaryExpr.parse(lexer); + + op = lexer.next(); + if (!op) { + break; + } + + info = this.ops[op]; + precedence = info && info[0]; + if (!precedence) { + lexer.back(); + break; + } + + while (stack.length && precedence <= this.ops[stack[stack.length-1]][0]) { + expr = new BinaryExpr(stack.pop(), stack.pop(), expr); + } + + stack.push(expr, op); + } + + while (stack.length) { + expr = new BinaryExpr(stack.pop(), stack.pop(), expr); + } + + return expr; +}; + +BinaryExpr.prototype = new BaseExpr(); + +BinaryExpr.prototype.evaluate = function(ctx) { + return BinaryExpr.ops[this.op][1](this.left, this.right, ctx); +}; + +BinaryExpr.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'binary: ' + this.op + '\n'; + indent += ' '; + t += this.left.show(indent); + t += this.right.show(indent); + return t; +}; + + +/** + * class: UnaryExpr + */ +if (!window.UnaryExpr && window.defaultConfig) + window.UnaryExpr = null; + +UnaryExpr = function(op, expr) { + this.op = op; + this.expr = expr; + + this.needContextPosition = expr.needContextPosition; + this.needContextNode = expr.needContextNode; +}; + +UnaryExpr.ops = { '-': 1 }; + +UnaryExpr.parse = function(lexer) { + var token; + if (this.ops[lexer.peek()]) + return new UnaryExpr(lexer.next(), UnaryExpr.parse(lexer)); + else + return UnionExpr.parse(lexer); +}; + +UnaryExpr.prototype = new BaseExpr(); + +UnaryExpr.prototype.datatype = 'number'; + +UnaryExpr.prototype.evaluate = function(ctx) { + return - this.expr.number(ctx); +}; + +UnaryExpr.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'unary: ' + this.op + '\n'; + indent += ' '; + t += this.expr.show(indent); + return t; +}; + + +/** + * class: UnionExpr + */ +if (!window.UnionExpr && window.defaultConfig) + window.UnionExpr = null; + +UnionExpr = function() { + this.paths = []; +}; + +UnionExpr.ops = { '|': 1 }; + + +UnionExpr.parse = function(lexer) { + var union, expr; + + expr = PathExpr.parse(lexer); + if (!this.ops[lexer.peek()]) + return expr; + + union = new UnionExpr(); + union.path(expr); + + while (true) { + if (!this.ops[lexer.next()]) break; + if (lexer.empty()) { + throw Error('missing next union location path'); + } + union.path(PathExpr.parse(lexer)); + } + + + + lexer.back(); + return union; +}; + +UnionExpr.prototype = new BaseExpr(); + +UnionExpr.prototype.datatype = 'nodeset'; + +UnionExpr.prototype.evaluate = function(ctx) { + var paths = this.paths; + var nodeset = new NodeSet(); + for (var i = 0, l = paths.length; i < l; i ++) { + var exrs = paths[i].evaluate(ctx); + if (!exrs.isNodeSet) throw Error('PathExpr must be nodeset'); + nodeset.merge(exrs); + } + return nodeset; +}; + +UnionExpr.prototype.path = function(path) { + this.paths.push(path); + + if (path.needContextPosition) { + this.needContextPosition = true; + } + if (path.needContextNode) { + this.needContextNode = true; + } +} +UnionExpr.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'union:' + '\n'; + indent += ' '; + for (var i = 0; i < this.paths.length; i ++) { + t += this.paths[i].show(indent); + } + return t; +}; + + +/** + * class: PathExpr + */ +if (!window.PathExpr && window.defaultConfig) + window.PathExpr = null; + +PathExpr = function(filter) { + this.filter = filter; + this.steps = []; + + this.datatype = filter.datatype; + + this.needContextPosition = filter.needContextPosition; + this.needContextNode = filter.needContextNode; +}; + +PathExpr.ops = { '//': 1, '/': 1 }; + +PathExpr.parse = function(lexer) { + var op, expr, path, token; + + if (this.ops[lexer.peek()]) { + op = lexer.next(); + token = lexer.peek(); + + if (op == '/' && (lexer.empty() || + (token != '.' && token != '..' && token != '@' && token != '*' && + !token.match(/(?![0-9])[\w]/)))) { + return FilterExpr.root(); + } + + path = new PathExpr(FilterExpr.root()); // RootExpr + + if (lexer.empty()) { + throw Error('missing next location step'); + } + expr = Step.parse(lexer); + path.step(op, expr); + } + else { + expr = FilterExpr.parse(lexer); + if (!expr) { + expr = Step.parse(lexer); + path = new PathExpr(FilterExpr.context()); + path.step('/', expr); + } + else if (!this.ops[lexer.peek()]) + return expr; + else + path = new PathExpr(expr); + } + + while (true) { + if (!this.ops[lexer.peek()]) break; + op = lexer.next(); + if (lexer.empty()) { + throw Error('missing next location step'); + } + path.step(op, Step.parse(lexer)); + } + + return path; +}; + +PathExpr.prototype = new BaseExpr(); + +PathExpr.prototype.evaluate = function(ctx) { + var nodeset = this.filter.evaluate(ctx); + if (!nodeset.isNodeSet) throw Exception('Filter nodeset must be nodeset type'); + + var steps = this.steps; + + for (var i = 0, l0 = steps.length; i < l0 && nodeset.length; i ++) { + var step = steps[i][1]; + var reverse = step.reverse; + var iter = nodeset.iterator(reverse); + var prevNodeset = nodeset; + nodeset = null; + var node, next; + if (!step.needContextPosition && step.axis == 'following') { + for (node = iter(); next = iter(); node = next) { + + // Safari 2 node.contains problem + if (uai.applewebkit4) { + var contains = false; + var ancestor = next; + do { + if (ancestor == node) { + contains = true; + break; + } + } while (ancestor = ancestor.parentNode); + if (!contains) break; + } + else { + try { if (!node.contains(next)) break } + catch(e) { if (!(next.compareDocumentPosition(node) & 8)) break } + } + } + nodeset = step.evaluate(new Ctx(node)); + } + else if (!step.needContextPosition && step.axis == 'preceding') { + node = iter(); + nodeset = step.evaluate(new Ctx(node)); + } + else { + node = iter(); + var j = 0; + nodeset = step.evaluate(new Ctx(node), false, prevNodeset, j); + while (node = iter()) { + j ++; + nodeset.merge(step.evaluate(new Ctx(node), false, prevNodeset, j)); + } + } + } + + return nodeset; +}; + +PathExpr.prototype.step = function(op, step) { + step.op = op; + this.steps.push([op, step]); + + this.quickAttr = false; + + if (this.steps.length == 1) { + if (op == '/' && step.axis == 'attribute') { + var test = step.test; + if (!test.notOnlyElement && test.name != '*') { + this.quickAttr = true; + this.attrName = test.name; + } + } + } +}; + +PathExpr.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'path:' + '\n'; + indent += ' '; + t += indent + 'filter:' + '\n'; + t += this.filter.show(indent + ' '); + if (this.steps.length) { + t += indent + 'steps:' + '\n'; + indent += ' '; + for (var i = 0; i < this.steps.length; i ++) { + var step = this.steps[i]; + t += indent + 'operator: ' + step[0] + '\n'; + t += step[1].show(indent); + } + } + return t; +}; + + +/** + * class: FilterExpr + */ +if (!window.FilterExpr && window.defaultConfig) + window.FilterExpr = null; + +FilterExpr = function(primary) { + this.primary = primary; + this.predicates = []; + + this.datatype = primary.datatype; + + this.needContextPosition = primary.needContextPosition; + + this.needContextNode = primary.needContextNode; +}; + +FilterExpr.parse = function(lexer) { + var expr, filter, token, ch; + + token = lexer.peek(); + ch = token.charAt(0); + + switch (ch) { + case '$': + expr = VariableReference.parse(lexer); + break; + + case '(': + lexer.next(); + expr = BinaryExpr.parse(lexer); + if (lexer.empty()) { + throw Error('unclosed "("'); + } + if (lexer.next() != ')') { + lexer.back(); + throw Error('bad token: ' + lexer.next()); + } + break; + + case '"': + case "'": + expr = Literal.parse(lexer); + break; + + default: + if (!isNaN(+token)) { + expr = Number.parse(lexer); + } + + else if (NodeType.types[token]) { + return null; + } + + else if (ch.match(/(?![0-9])[\w]/) && lexer.peek(1) == '(') { + expr = FunctionCall.parse(lexer); + } + else { + return null; + } + break; + } + + if (lexer.peek() != '[') return expr; + + filter = new FilterExpr(expr); + + BaseExprHasPredicates.parsePredicates(lexer, filter); + + return filter; +}; + +FilterExpr.root = function() { + return new FunctionCall('root-node'); +}; +FilterExpr.context = function() { + return new FunctionCall('context-node'); +}; + +FilterExpr.prototype = new BaseExprHasPredicates(); + +FilterExpr.prototype.evaluate = function(ctx) { + var nodeset = this.primary.evaluate(ctx); + if(!nodeset.isNodeSet) { + if (this.predicates.length) + throw Error( + 'Primary result must be nodeset type ' + + 'if filter have predicate expression'); + return nodeset; + } + + return this.evaluatePredicates(nodeset); +}; + +FilterExpr.prototype.predicate = function(predicate) { + this.predicates.push(predicate); +}; + +FilterExpr.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'filter: ' + '\n'; + indent += ' '; + t += this.primary.show(indent); + if (this.predicates.length) { + t += indent + 'predicates: ' + '\n'; + indent += ' '; + for (var i = 0; i < this.predicates.length; i ++) { + t += this.predicates[i].show(indent); + } + } + return t; +}; + + +if (!window.NodeUtil && window.defaultConfig) + window.NodeUtil = null; + +NodeUtil = { + to: function(valueType, node) { + var type = node.nodeType; +/*@cc_on + if (type == 1 && node.nodeName.toLowerCase() == 'title') { + t = node.text; + } + else +@*/ + if (type == 9 || type == 1) { + if (type == 9) { + node = node.documentElement; + } + else { + node = node.firstChild; + } + for (var t = '', stack = [], i = 0; node;) { + do { + if (node.nodeType != 1) { + t += node.nodeValue; + } +/*@cc_on + else if (node.nodeName.toLowerCase() == 'title') { + t += node.text; + } +@*/ + stack[i++] = node; // push + } while (node = node.firstChild); + while (i && !(node = stack[--i].nextSibling)) {} + } + } + else { + var t = node.nodeValue; + } + switch (valueType) { + case 'number': + return + t; + case 'boolean': + return !! t; + default: + return t; + } + }, + attrPropMap: { + name: 'name', + 'class': 'className', + dir: 'dir', + id: 'id', + name: 'name', + title: 'title' + }, + attrMatch: function(node, attrName, attrValue) { +/*@cc_on @if (@_jscript) + var propName = NodeUtil.attrPropMap[attrName]; + if (!attrName || + attrValue == null && ( + propName && node[propName] || + !propName && node.getAttribute && node.getAttribute(attrName) + ) || + attrValue != null && ( + propName && node[propName] == attrValue || + !propName && node.getAttribute && node.getAttribute(attrName) == attrValue + )) { +@else @*/ + if (!attrName || + attrValue == null && node.hasAttribute && node.hasAttribute(attrName) || + attrValue != null && node.getAttribute && node.getAttribute(attrName) == attrValue) { +/*@end @*/ + return true; + } + else { + return false; + } + }, + getDescendantNodes: function(test, node, nodeset, attrName, attrValue, prevNodeset, prevIndex) { + if (prevNodeset) { + prevNodeset.delDescendant(node, prevIndex); + } +/*@cc_on + if (!test.notOnlyElement || test.type == 8 || (attrName && test.type == 0)) { + + var all = node.all; + if (!all) { + return nodeset; + } + + var name = test.name; + if (test.type == 8) name = '!'; + else if (test.type == 0) name = '*'; + + if (name != '*') { + all = all.tags(name); + if (!all) { + return nodeset; + } + } + + if (attrName) { + var result = [] + var i = 0; + if (attrValue != null && (attrName == 'id' || attrName == 'name')) { + all = all[attrValue]; + if (!all) { + return nodeset; + } + if (!all.length) { + all = [all]; + } + } + + while (node = all[i++]) { + if (NodeUtil.attrMatch(node, attrName, attrValue)) result.push(node); + } + + all = result; + } + + var i = 0; + while (node = all[i++]) { + if (name != '*' || node.tagName != '!') { + nodeset.push(node); + } + } + + return nodeset; + } + + (function (parent) { + var g = arguments.callee; + var node = parent.firstChild; + if (node) { + for (; node; node = node.nextSibling) { + if (NodeUtil.attrMatch(node, attrName, attrValue)) { + if (test.match(node)) nodeset.push(node); + } + g(node); + } + } + })(node); + + return nodeset; +@*/ + if (attrValue && attrName == 'id' && node.getElementById) { + node = node.getElementById(attrValue); + if (node && test.match(node)) { + nodeset.push(node); + } + } + else if (attrValue && attrName == 'name' && node.getElementsByName) { + var nodes = node.getElementsByName(attrValue); + for (var i = 0, l = nodes.length; i < l; i ++) { + node = nodes[i]; + if (uai.opera ? (node.name == attrValue && test.match(node)) : test.match(node)) { + nodeset.push(node); + } + } + } + else if (attrValue && attrName == 'class' && node.getElementsByClassName) { + var nodes = node.getElementsByClassName(attrValue); + for (var i = 0, l = nodes.length; i < l; i ++) { + node = nodes[i]; + if (node.className == attrValue && test.match(node)) { + nodeset.push(node); + } + } + } + else if (test.notOnlyElement) { + (function (parent) { + var f = arguments.callee; + for (var node = parent.firstChild; node; node = node.nextSibling) { + if (NodeUtil.attrMatch(node, attrName, attrValue)) { + if (test.match(node.nodeType)) nodeset.push(node); + } + f(node); + } + })(node); + } + else { + var name = test.name; + if (node.getElementsByTagName) { + var nodes = node.getElementsByTagName(name); + if (nodes) { + var i = 0; + while (node = nodes[i++]) { + if (NodeUtil.attrMatch(node, attrName, attrValue)) nodeset.push(node); + } + } + } + } + return nodeset; + }, + + getChildNodes: function(test, node, nodeset, attrName, attrValue) { + +/*@cc_on + var children; + + if ((!test.notOnlyElement || test.type == 8 || (attrName && test.type == 0)) && (children = node.children)) { + var name, elm; + + name = test.name; + if (test.type == 8) name = '!'; + else if (test.type == 0) name = '*'; + + if (name != '*') { + children = children.tags(name); + if (!children) { + return nodeset; + } + } + + if (attrName) { + var result = [] + var i = 0; + if (attrName == 'id' || attrName == 'name') { + children = children[attrValue]; + + if (!children) { + return nodeset; + } + + if (!children.length) { + children = [children]; + } + } + + while (node = children[i++]) { + if (NodeUtil.attrMatch(node, attrName, attrValue)) result.push(node); + } + children = result; + } + + var i = 0; + while (node = children[i++]) { + if (name != '*' || node.tagName != '!') { + nodeset.push(node); + } + } + + return nodeset; + } + + for (var i = 0, node = node.firstChild; node; i++, node = node.nextSibling) { + if (NodeUtil.attrMatch(node, attrName, attrValue)) { + if (test.match(node)) nodeset.push(node); + } + } + + return nodeset; +@*/ + for (var node = node.firstChild; node; node = node.nextSibling) { + if (NodeUtil.attrMatch(node, attrName, attrValue)) { + if (test.match(node)) nodeset.push(node); + } + } + return nodeset; + } +}; + +/*@cc_on +var AttributeWrapper = function(node, parent, sourceIndex) { + this.node = node; + this.nodeType = 2; + this.nodeValue = node.nodeValue; + this.nodeName = node.nodeName; + this.parentNode = parent; + this.ownerElement = parent; + this.parentSourceIndex = sourceIndex; +}; + +@*/ + + +/** + * class: Step + */ +if (!window.Step && window.defaultConfig) + window.Step = null; + +Step = function(axis, test) { + // TODO check arguments and throw axis error + this.axis = axis; + this.reverse = Step.axises[axis][0]; + this.func = Step.axises[axis][1]; + this.test = test; + this.predicates = []; + this._quickAttr = Step.axises[axis][2] +}; + +Step.axises = { + + ancestor: [true, function(test, node, nodeset, _, __, prevNodeset, prevIndex) { + while (node = node.parentNode) { + if (prevNodeset && node.nodeType == 1) { + prevNodeset.reserveDelByNode(node, prevIndex, true); + } + if (test.match(node)) nodeset.unshift(node); + } + return nodeset; + }], + + 'ancestor-or-self': [true, function(test, node, nodeset, _, __, prevNodeset, prevIndex) { + do { + if (prevNodeset && node.nodeType == 1) { + prevNodeset.reserveDelByNode(node, prevIndex, true); + } + if (test.match(node)) nodeset.unshift(node); + } while (node = node.parentNode) + return nodeset; + }], + + attribute: [false, function(test, node, nodeset) { + var attrs = node.attributes; + if (attrs) { +/*@cc_on + var sourceIndex = node.sourceIndex; +@*/ + if ((test.notOnlyElement && test.type == 0) || test.name == '*') { + for (var i = 0, l = attrs.length; i < l; i ++) { + var attr = attrs[i]; +/*@cc_on @if (@_jscript) + if (attr.nodeValue) { + nodeset.push(new AttributeWrapper(attr, node, sourceIndex)); + } +@else @*/ + nodeset.push(attr); +/*@end @*/ + } + } + else { + var attr = attrs.getNamedItem(test.name) + +/*@cc_on @if (@_jscript) + if (attr && attr.nodeValue) { + attr = new AttributeWrapper(attr, node, sourceIndex);; +@else @*/ + if (attr) { +/*@end @*/ + nodeset.push(attr); + } + } + } + return nodeset; + }], + + child: [false, NodeUtil.getChildNodes, true], + + descendant: [false, NodeUtil.getDescendantNodes, true], + + 'descendant-or-self': [false, function(test, node, nodeset, attrName, attrValue, prevNodeset, prevIndex) { + if (NodeUtil.attrMatch(node, attrName, attrValue)) { + if (test.match(node)) nodeset.push(node); + } + return NodeUtil.getDescendantNodes(test, node, nodeset, attrName, attrValue, prevNodeset, prevIndex); + }, true], + + following: [false, function(test, node, nodeset, attrName, attrValue) { + do { + var child = node; + while (child = child.nextSibling) { + if (NodeUtil.attrMatch(child, attrName, attrValue)) { + if (test.match(child)) nodeset.push(child); + } + nodeset = NodeUtil.getDescendantNodes(test, child, nodeset, attrName, attrValue); + } + } while (node = node.parentNode); + return nodeset; + }, true], + + 'following-sibling': [false, function(test, node, nodeset, _, __, prevNodeset, prevIndex) { + while (node = node.nextSibling) { + + if (prevNodeset && node.nodeType == 1) { + prevNodeset.reserveDelByNode(node, prevIndex); + } + + if (test.match(node)) { + nodeset.push(node); + } + } + return nodeset; + }], + + namespace: [false, function(test, node, nodeset) { + // not implemented + return nodeset; + }], + + parent: [false, function(test, node, nodeset) { + if (node.nodeType == 9) { + return nodeset; + } + if (node.nodeType == 2) { + nodeset.push(node.ownerElement); + return nodeset; + } + var node = node.parentNode; + if (test.match(node)) nodeset.push(node); + return nodeset; + }], + + preceding: [true, function(test, node, nodeset, attrName, attrValue) { + var parents = []; + do { + parents.unshift(node); + } while (node = node.parentNode); + + for (var i = 1, l0 = parents.length; i < l0; i ++) { + var siblings = []; + node = parents[i]; + while (node = node.previousSibling) { + siblings.unshift(node); + } + + for (var j = 0, l1 = siblings.length; j < l1; j ++) { + node = siblings[j]; + if (NodeUtil.attrMatch(node, attrName, attrValue)) { + if (test.match(node)) nodeset.push(node); + } + nodeset = NodeUtil.getDescendantNodes(test, node, nodeset, attrName, attrValue); + } + } + return nodeset; + }, true], + + 'preceding-sibling': [true, function(test, node, nodeset, _, __, prevNodeset, prevIndex) { + while (node = node.previousSibling) { + + if (prevNodeset && node.nodeType == 1) { + prevNodeset.reserveDelByNode(node, prevIndex, true); + } + + if (test.match(node)) { + nodeset.unshift(node) + } + } + return nodeset; + }], + + self: [false, function(test, node, nodeset) { + if (test.match(node)) nodeset.push(node); + return nodeset; + }] +}; + +Step.parse = function(lexer) { + var axis, test, step, token; + + if (lexer.peek() == '.') { + step = this.self(); + lexer.next(); + } + else if (lexer.peek() == '..') { + step = this.parent(); + lexer.next(); + } + else { + if (lexer.peek() == '@') { + axis = 'attribute'; + lexer.next(); + if (lexer.empty()) { + throw Error('missing attribute name'); + } + } + else { + if (lexer.peek(1) == '::') { + + if (!lexer.peek().charAt(0).match(/(?![0-9])[\w]/)) { + throw Error('bad token: ' + lexer.next()); + } + + axis = lexer.next(); + lexer.next(); + + if (!this.axises[axis]) { + throw Error('invalid axis: ' + axis); + } + if (lexer.empty()) { + throw Error('missing node name'); + } + } + else { + axis = 'child'; + } + } + + token = lexer.peek(); + if (!token.charAt(0).match(/(?![0-9])[\w]/)) { + if (token == '*') { + test = NameTest.parse(lexer) + } + else { + throw Error('bad token: ' + lexer.next()); + } + } + else { + if (lexer.peek(1) == '(') { + if (!NodeType.types[token]) { + throw Error('invalid node type: ' + token); + } + test = NodeType.parse(lexer) + } + else { + test = NameTest.parse(lexer); + } + } + step = new Step(axis, test); + } + + BaseExprHasPredicates.parsePredicates(lexer, step); + + return step; +}; + +Step.self = function() { + return new Step('self', new NodeType('node')); +}; + +Step.parent = function() { + return new Step('parent', new NodeType('node')); +}; + +Step.prototype = new BaseExprHasPredicates(); + +Step.prototype.evaluate = function(ctx, special, prevNodeset, prevIndex) { + var node = ctx.node; + var reverse = false; + + if (!special && this.op == '//') { + + if (!this.needContextPosition && this.axis == 'child') { + if (this.quickAttr) { + var attrValue = this.attrValueExpr ? this.attrValueExpr.string(ctx) : null; + var nodeset = NodeUtil.getDescendantNodes(this.test, node, new NodeSet(), this.attrName, attrValue, prevNodeset, prevIndex); + nodeset = this.evaluatePredicates(nodeset, 1); + } + else { + var nodeset = NodeUtil.getDescendantNodes(this.test, node, new NodeSet(), null, null, prevNodeset, prevIndex); + nodeset = this.evaluatePredicates(nodeset); + } + } + else { + var step = new Step('descendant-or-self', new NodeType('node')); + var nodes = step.evaluate(ctx, false, prevNodeset, prevIndex).list(); + var nodeset = null; + step.op = '/'; + for (var i = 0, l = nodes.length; i < l; i ++) { + if (!nodeset) { + nodeset = this.evaluate(new Ctx(nodes[i]), true); + } + else { + nodeset.merge(this.evaluate(new Ctx(nodes[i]), true)); + } + } + nodeset = nodeset || new NodeSet(); + } + } + else { + + if (this.needContextPosition) { + prevNodeset = null; + prevIndex = null; + } + + if (this.quickAttr) { + var attrValue = this.attrValueExpr ? this.attrValueExpr.string(ctx) : null; + var nodeset = this.func(this.test, node, new NodeSet(), this.attrName, attrValue, prevNodeset, prevIndex); + nodeset = this.evaluatePredicates(nodeset, 1); + } + else { + var nodeset = this.func(this.test, node, new NodeSet(), null, null, prevNodeset, prevIndex); + nodeset = this.evaluatePredicates(nodeset); + } + if (prevNodeset) { + prevNodeset.doDel(); + } + } + return nodeset; +}; + +Step.prototype.predicate = function(predicate) { + this.predicates.push(predicate); + + if (predicate.needContextPosition || + predicate.datatype == 'number'|| + predicate.datatype == 'void') { + this.needContextPosition = true; + } + + if (this._quickAttr && this.predicates.length == 1 && predicate.quickAttr) { + var attrName = predicate.attrName; +/*@cc_on @if (@_jscript) + this.attrName = attrName.toLowerCase(); +@else @*/ + this.attrName = attrName; +/*@end @*/ + this.attrValueExpr = predicate.attrValueExpr; + this.quickAttr = true; + } +}; + +Step.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'step: ' + '\n'; + indent += ' '; + if (this.axis) t += indent + 'axis: ' + this.axis + '\n'; + t += this.test.show(indent); + if (this.predicates.length) { + t += indent + 'predicates: ' + '\n'; + indent += ' '; + for (var i = 0; i < this.predicates.length; i ++) { + t += this.predicates[i].show(indent); + } + } + return t; +}; + + + +/** + * NodeType + */ +if (!window.NodeType && window.defaultConfig) + window.NodeType = null; + +NodeType = function(name, literal) { + this.name = name; + this.literal = literal; + + switch (name) { + case 'comment': + this.type = 8; + break; + case 'text': + this.type = 3; + break; + case 'processing-instruction': + this.type = 7; + break; + case 'node': + this.type = 0; + break; + } +}; + +NodeType.types = { + 'comment':1, 'text':1, 'processing-instruction':1, 'node':1 +}; + +NodeType.parse = function(lexer) { + var type, literal, ch; + type = lexer.next(); + lexer.next(); + if (lexer.empty()) { + throw Error('bad nodetype'); + } + ch = lexer.peek().charAt(0); + if (ch == '"' || ch == "'") { + literal = Literal.parse(lexer); + } + if (lexer.empty()) { + throw Error('bad nodetype'); + } + if (lexer.next() != ')') { + lexer.back(); + throw Error('bad token ' + lexer.next()); + } + return new NodeType(type, literal); +}; + +NodeType.prototype = new BaseExpr(); + +NodeType.prototype.notOnlyElement = true; + +NodeType.prototype.match = function(node) { + return !this.type || this.type == node.nodeType; +}; + +NodeType.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'nodetype: ' + this.type + '\n'; + if (this.literal) { + indent += ' '; + t += this.literal.show(indent); + } + return t; +}; + + +/** + * NodeType + */ +if (!window.NameTest && window.defaultConfig) + window.NameTest = null; + +NameTest = function(name) { + this.name = name.toLowerCase(); +}; + +NameTest.parse = function(lexer) { + if (lexer.peek() != '*' && lexer.peek(1) == ':' && lexer.peek(2) == '*') { + return new NameTest(lexer.next() + lexer.next() + lexer.next()); + } + return new NameTest(lexer.next()); +}; + +NameTest.prototype = new BaseExpr(); + +NameTest.prototype.match = function(node) { + var type = node.nodeType; + + if (type == 1 || type == 2) { + if (this.name == '*' || this.name == node.nodeName.toLowerCase()) { + return true; + } + } + return false; +}; + +NameTest.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'nametest: ' + this.name + '\n'; + return t; +}; + + +/** + * class: VariableRefernce + */ +if (!window.VariableReference && window.defaultConfig) + window.VariableReference = null; + +VariableReference = function(name) { + this.name = name.substring(1); +}; + + +VariableReference.parse = function(lexer) { + var token = lexer.next(); + if (token.length < 2) { + throw Error('unnamed variable reference'); + } + return new VariableReference(token) +}; + +VariableReference.prototype = new BaseExpr(); + +VariableReference.prototype.datatype = 'void'; + +VariableReference.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'variable: ' + this.name + '\n'; + return t; +}; + + +/** + * class: Literal + */ +if (!window.Literal && window.defaultConfig) + window.Literal = null; + +Literal = function(text) { + this.text = text.substring(1, text.length - 1); +}; + +Literal.parse = function(lexer) { + var token = lexer.next(); + if (token.length < 2) { + throw Error('unclosed literal string'); + } + return new Literal(token) +}; + +Literal.prototype = new BaseExpr(); + +Literal.prototype.datatype = 'string'; + +Literal.prototype.evaluate = function(ctx) { + return this.text; +}; + +Literal.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'literal: ' + this.text + '\n'; + return t; +}; + + +/** + * class: Number + */ +if (!window.Number && window.defaultConfig) + window.Number = null; + +Number = function(digit) { + this.digit = +digit; +}; + + +Number.parse = function(lexer) { + return new Number(lexer.next()); +}; + +Number.prototype = new BaseExpr(); + +Number.prototype.datatype = 'number'; + +Number.prototype.evaluate = function(ctx) { + return this.digit; +}; + +Number.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'number: ' + this.digit + '\n'; + return t; +}; + + +/** + * class: FunctionCall + */ +if (!window.FunctionCall && window.defaultConfig) + window.FunctionCall = null; + +FunctionCall = function(name) { + var info = FunctionCall.funcs[name]; + this.name = name; + this.func = info[0]; + this.args = []; + + this.datatype = info[1]; + + if (info[2]) { + this.needContextPosition = true; + } + + this.needContextNodeInfo = info[3]; + this.needContextNode = this.needContextNodeInfo[0] +}; + +FunctionCall.funcs = { + + // Original Function + 'context-node': [function() { + if (arguments.length != 0) { + throw Error('Function context-node expects ()'); + } + var ns; + ns = new NodeSet(); + ns.push(this.node); + return ns; + }, 'nodeset', false, [true]], + + // Original Function + 'root-node': [function() { + if (arguments.length != 0) { + throw Error('Function root-node expects ()'); + } + var ns, ctxn; + ns = new NodeSet(); + ctxn = this.node; + if (ctxn.nodeType == 9) + ns.push(ctxn); + else + ns.push(ctxn.ownerDocument); + return ns; + }, 'nodeset', false, []], + + last: [function() { + if (arguments.length != 0) { + throw Error('Function last expects ()'); + } + return this.last; + }, 'number', true, []], + + position: [function() { + if (arguments.length != 0) { + throw Error('Function position expects ()'); + } + return this.position; + }, 'number', true, []], + + count: [function(ns) { + if (arguments.length != 1 || !(ns = ns.evaluate(this)).isNodeSet) { + throw Error('Function count expects (nodeset)'); + } + return ns.length; + }, 'number', false, []], + + id: [function(s) { + var ids, ns, i, id, elm, ctxn, doc; + if (arguments.length != 1) { + throw Error('Function id expects (object)'); + } + ctxn = this.node; + if (ctxn.nodeType == 9) + doc = ctxn; + else + doc = ctxn.ownerDocument; +/*@cc_on + all = doc.all; +@*/ + s = s.string(this); + ids = s.split(/\s+/); + ns = new NodeSet(); + for (i = 0, l = ids.length; i < l; i ++) { + id = ids[i]; + +/*@cc_on @if (@_jscript) + elm = all[id]; + if (elm) { + if (elm.length) { + var elms = elm; + for (var j = 0, l0 = elms.length; j < l0; j ++) { + var elem = elms[j]; + if (id == elem.id) { + ns.push(elem); + break; + } + } + } + else if (id == elm.id) { + ns.push(elm) + } + } +@else @*/ + elm = doc.getElementById(id); + if (uai.opera && elm.id != id) { + var elms = doc.getElementsByName(id); + for (var j = 0, l0 = elms.length; j < l0; j ++) { + elm = elms[j]; + if (elm.id == id) { + ns.push(elm); + } + } + } + else { + if (elm) ns.push(elm) + } +/*@end @*/ + + } + ns.isSorted = false; + return ns; + }, 'nodeset', false, []], + + 'local-name': [function(ns) { + var nd; + switch (arguments.length) { + case 0: + nd = this.node; + break; + case 1: + if ((ns = ns.evaluate(this)).isNodeSet) { + nd = ns.first(); + break; + } + default: + throw Error('Function local-name expects (nodeset?)'); + break; + } + return '' + nd.nodeName.toLowerCase(); + }, 'string', false, [true, false]], + + name: [function(ns) { + // not implemented + return FunctionCall.funcs['local-name'][0].apply(this, arguments); + }, 'string', false, [true, false]], + + 'namespace-uri': [function(ns) { + // not implemented + return ''; + }, 'string', false, [true, false]], + + string: [function(s) { + switch (arguments.length) { + case 0: + s = NodeUtil.to('string', this.node); + break; + case 1: + s = s.string(this); + break; + default: + throw Error('Function string expects (object?)'); + break; + } + return s; + }, 'string', false, [true, false]], + + concat: [function(s1, s2) { + if (arguments.length < 2) { + throw Error('Function concat expects (string, string[, ...])'); + } + for (var t = '', i = 0, l = arguments.length; i < l; i ++) { + t += arguments[i].string(this); + } + return t; + }, 'string', false, []], + + 'starts-with': [function(s1, s2) { + if (arguments.length != 2) { + throw Error('Function starts-with expects (string, string)'); + } + s1 = s1.string(this); + s2 = s2.string(this); + return s1.indexOf(s2) == 0; + }, 'boolean', false, []], + + contains: [function(s1, s2) { + if (arguments.length != 2) { + throw Error('Function contains expects (string, string)'); + } + s1 = s1.string(this); + s2 = s2.string(this); + return s1.indexOf(s2) != -1; + }, 'boolean', false, []], + + substring: [function(s, n1, n2) { + var a1, a2; + s = s.string(this); + n1 = n1.number(this); + switch (arguments.length) { + case 2: + n2 = s.length - n1 + 1; + break; + case 3: + n2 = n2.number(this); + break; + default: + throw Error('Function substring expects (string, string)'); + break; + } + n1 = Math.round(n1); + n2 = Math.round(n2); + a1 = n1 - 1; + a2 = n1 + n2 - 1; + if (a2 == Infinity) { + return s.substring(a1 < 0 ? 0 : a1); + } + else { + return s.substring(a1 < 0 ? 0 : a1, a2) + } + }, 'string', false, []], + + 'substring-before': [function(s1, s2) { + var n; + if (arguments.length != 2) { + throw Error('Function substring-before expects (string, string)'); + } + s1 = s1.string(this); + s2 = s2.string(this); + n = s1.indexOf(s2); + if (n == -1) return ''; + return s1.substring(0, n); + }, 'string', false, []], + + 'substring-after': [function(s1, s2) { + if (arguments.length != 2) { + throw Error('Function substring-after expects (string, string)'); + } + s1 = s1.string(this); + s2 = s2.string(this); + var n = s1.indexOf(s2); + if (n == -1) return ''; + return s1.substring(n + s2.length); + }, 'string', false, []], + + 'string-length': [function(s) { + switch (arguments.length) { + case 0: + s = NodeUtil.to('string', this.node); + break; + case 1: + s = s.string(this); + break; + default: + throw Error('Function string-length expects (string?)'); + break; + } + return s.length; + }, 'number', false, [true, false]], + + 'normalize-space': [function(s) { + switch (arguments.length) { + case 0: + s = NodeUtil.to('string', this.node); + break; + case 1: + s = s.string(this); + break; + default: + throw Error('Function normalize-space expects (string?)'); + break; + } + return s.replace(/\s+/g, ' ').replace(/^ /, '').replace(/ $/, ''); + }, 'string', false, [true, false]], + + translate: [function(s1, s2, s3) { + if (arguments.length != 3) { + throw Error('Function translate expects (string, string, string)'); + } + s1 = s1.string(this); + s2 = s2.string(this); + s3 = s3.string(this); + + var map = []; + for (var i = 0, l = s2.length; i < l; i ++) { + var ch = s2.charAt(i); + if (!map[ch]) map[ch] = s3.charAt(i) || ''; + } + for (var t = '', i = 0, l = s1.length; i < l; i ++) { + var ch = s1.charAt(i); + var replace = map[ch] + t += (replace != undefined) ? replace : ch; + } + return t; + }, 'string', false, []], + + 'boolean': [function(b) { + if (arguments.length != 1) { + throw Error('Function boolean expects (object)'); + } + return b.bool(this) + }, 'boolean', false, []], + + not: [function(b) { + if (arguments.length != 1) { + throw Error('Function not expects (object)'); + } + return !b.bool(this) + }, 'boolean', false, []], + + 'true': [function() { + if (arguments.length != 0) { + throw Error('Function true expects ()'); + } + return true; + }, 'boolean', false, []], + + 'false': [function() { + if (arguments.length != 0) { + throw Error('Function false expects ()'); + } + return false; + }, 'boolean', false, []], + + lang: [function(s) { + // not implemented + return false; + }, 'boolean', false, []], + + number: [function(n) { + switch (arguments.length) { + case 0: + n = NodeUtil.to('number', this.node); + break; + case 1: + n = n.number(this); + break; + default: + throw Error('Function number expects (object?)'); + break; + } + return n; + }, 'number', false, [true, false]], + + sum: [function(ns) { + var nodes, n, i, l; + if (arguments.length != 1 || !(ns = ns.evaluate(this)).isNodeSet) { + throw Error('Function sum expects (nodeset)'); + } + nodes = ns.list(); + n = 0; + for (i = 0, l = nodes.length; i < l; i ++) { + n += NodeUtil.to('number', nodes[i]); + } + return n; + }, 'number', false, []], + + floor: [function(n) { + if (arguments.length != 1) { + throw Error('Function floor expects (number)'); + } + n = n.number(this); + return Math.floor(n); + }, 'number', false, []], + + ceiling: [function(n) { + if (arguments.length != 1) { + throw Error('Function ceiling expects (number)'); + } + n = n.number(this); + return Math.ceil(n); + }, 'number', false, []], + + round: [function(n) { + if (arguments.length != 1) { + throw Error('Function round expects (number)'); + } + n = n.number(this); + return Math.round(n); + }, 'number', false, []] +}; + +FunctionCall.parse = function(lexer) { + var expr, func = new FunctionCall(lexer.next()); + lexer.next(); + while (lexer.peek() != ')') { + if (lexer.empty()) { + throw Error('missing function argument list'); + } + expr = BinaryExpr.parse(lexer); + func.arg(expr); + if (lexer.peek() != ',') break; + lexer.next(); + } + if (lexer.empty()) { + throw Error('unclosed function argument list'); + } + if (lexer.next() != ')') { + lexer.back(); + throw Error('bad token: ' + lexer.next()); + } + return func +}; + +FunctionCall.prototype = new BaseExpr(); + +FunctionCall.prototype.evaluate = function (ctx) { + return this.func.apply(ctx, this.args); +}; + +FunctionCall.prototype.arg = function(arg) { + this.args.push(arg); + + if (arg.needContextPosition) { + this.needContextPosition = true; + } + + var args = this.args; + if (arg.needContextNode) { + args.needContexNode = true; + } + this.needContextNode = args.needContextNode || + this.needContextNodeInfo[args.length]; +}; + +FunctionCall.prototype.show = function(indent) { + indent = indent || ''; + var t = ''; + t += indent + 'function: ' + this.name + '\n'; + indent += ' '; + + if (this.args.length) { + t += indent + 'arguments: ' + '\n'; + indent += ' '; + for (var i = 0; i < this.args.length; i ++) { + t += this.args[i].show(indent); + } + } + + return t; +}; + + +/*@cc_on @if (@_jscript) +var NodeWrapper = function(node, sourceIndex, subIndex, attributeName) { + this.node = node; + this.nodeType = node.nodeType; + this.sourceIndex = sourceIndex; + this.subIndex = subIndex; + this.attributeName = attributeName || ''; + this.order = String.fromCharCode(sourceIndex) + String.fromCharCode(subIndex) + attributeName; +}; + +NodeWrapper.prototype.toString = function() { + return this.order; +}; +@else @*/ +var NodeID = { + uuid: 1, + get: function(node) { + return node.__jsxpath_id__ || (node.__jsxpath_id__ = this.uuid++); + } +}; +/*@end @*/ + +if (!window.NodeSet && window.defaultConfig) + window.NodeSet = null; + +NodeSet = function() { + this.length = 0; + this.nodes = []; + this.seen = {}; + this.idIndexMap = null; + this.reserveDels = []; +}; + +NodeSet.prototype.isNodeSet = true; +NodeSet.prototype.isSorted = true; + +/*@_cc_on +NodeSet.prototype.shortcut = true; +@*/ + +NodeSet.prototype.merge = function(nodeset) { + this.isSorted = false; + if (nodeset.only) { + return this.push(nodeset.only); + } + + if (this.only){ + var only = this.only; + delete this.only; + this.push(only); + this.length --; + } + + var nodes = nodeset.nodes; + for (var i = 0, l = nodes.length; i < l; i ++) { + this._add(nodes[i]); + } +}; + +NodeSet.prototype.sort = function() { + if (this.only) return; + if (this.sortOff) return; + + if (!this.isSorted) { + this.isSorted = true; + this.idIndexMap = null; + +/*@cc_on + if (this.shortcut) { + this.nodes.sort(); + } + else { + this.nodes.sort(function(a, b) { + var result; + result = a.sourceIndex - b.sourceIndex; + if (result == 0) + return a.subIndex - a.subIndex; + else + return result; + }); + } + return; +@*/ + var nodes = this.nodes; + nodes.sort(function(a, b) { + if (a == b) return 0; + + if (a.compareDocumentPosition) { + var result = a.compareDocumentPosition(b); + if (result & 2) return 1; + if (result & 4) return -1; + return 0; + } + else { + var node1 = a, node2 = b, ancestor1 = a, ancestor2 = b, deep1 = 0, deep2 = 0; + + while(ancestor1 = ancestor1.parentNode) deep1 ++; + while(ancestor2 = ancestor2.parentNode) deep2 ++; + + // same deep + if (deep1 > deep2) { + while (deep1-- != deep2) node1 = node1.parentNode; + if (node1 == node2) return 1; + } + else if (deep2 > deep1) { + while (deep2-- != deep1) node2 = node2.parentNode; + if (node1 == node2) return -1; + } + + while ((ancestor1 = node1.parentNode) != (ancestor2 = node2.parentNode)) { + node1 = ancestor1; + node2 = ancestor2; + } + + // node1 is node2's sibling + while (node1 = node1.nextSibling) if (node1 == node2) return -1; + + return 1; + } + }); + } +}; + + +/*@cc_on @if (@_jscript) +NodeSet.prototype.sourceOffset = 1; +NodeSet.prototype.subOffset = 2; +NodeSet.prototype.createWrapper = function(node) { + var parent, child, attributes, attributesLength, sourceIndex, subIndex, attributeName; + + sourceIndex = node.sourceIndex; + + if (typeof sourceIndex != 'number') { + type = node.nodeType; + switch (type) { + case 2: + parent = node.parentNode; + sourceIndex = node.parentSourceIndex; + subIndex = -1; + attributeName = node.nodeName; + break; + case 9: + subIndex = -2; + sourceIndex = -1; + break; + default: + child = node; + subIndex = 0; + do { + subIndex ++; + sourceIndex = child.sourceIndex; + if (sourceIndex) { + parent = child; + child = child.lastChild; + if (!child) { + child = parent; + break; + } + subIndex ++; + } + } while (child = child.previousSibling); + if (!sourceIndex) { + sourceIndex = node.parentNode.sourceIndex; + } + break; + } + } + else { + subIndex = -2; + } + + sourceIndex += this.sourceOffset; + subIndex += this.subOffset; + + return new NodeWrapper(node, sourceIndex, subIndex, attributeName); +}; + +NodeSet.prototype.reserveDelBySourceIndexAndSubIndex = function(sourceIndex, subIndex, offset, reverse) { + var map = this.createIdIndexMap(); + var index; + if ((map = map[sourceIndex]) && (index = map[subIndex])) { + if (reverse && (this.length - offset - 1) > index || !reverse && offset < index) { + var obj = { + value: index, + order: String.fromCharCode(index), + toString: function() { return this.order }, + valueOf: function() { return this.value } + }; + this.reserveDels.push(obj); + } + } +}; +@else @*/ +NodeSet.prototype.reserveDelByNodeID = function(id, offset, reverse) { + var map = this.createIdIndexMap(); + var index; + if (index = map[id]) { + if (reverse && (this.length - offset - 1) > index || !reverse && offset < index) { + var obj = { + value: index, + order: String.fromCharCode(index), + toString: function() { return this.order }, + valueOf: function() { return this.value } + }; + this.reserveDels.push(obj); + } + } +}; +/*@end @*/ + +NodeSet.prototype.reserveDelByNode = function(node, offset, reverse) { +/*@cc_on @if (@_jscript) + node = this.createWrapper(node); + this.reserveDelBySourceIndexAndSubIndex(node.sourceIndex, node.subIndex, offset, reverse); +@else @*/ + this.reserveDelByNodeID(NodeID.get(node), offset, reverse); +/*@end @*/ +}; + +NodeSet.prototype.doDel = function() { + if (!this.reserveDels.length) return; + + if (this.length < 0x10000) { + var dels = this.reserveDels.sort(function(a, b) { return b - a }); + } + else { + var dels = this.reserveDels.sort(function(a, b) { return b - a }); + } + for (var i = 0, l = dels.length; i < l; i ++) { + this.del(dels[i]); + } + this.reserveDels = []; + this.idIndexMap = null; +}; + +NodeSet.prototype.createIdIndexMap = function() { + if (this.idIndexMap) { + return this.idIndexMap; + } + else { + var map = this.idIndexMap = {}; + var nodes = this.nodes; + for (var i = 0, l = nodes.length; i < l; i ++) { + var node = nodes[i]; +/*@cc_on @if (@_jscript) + var sourceIndex = node.sourceIndex; + var subIndex = node.subIndex; + if (!map[sourceIndex]) map[sourceIndex] = {}; + map[sourceIndex][subIndex] = i; +@else @*/ + var id = NodeID.get(node); + map[id] = i; +/*@end @*/ + } + return map; + } +}; + +NodeSet.prototype.del = function(index) { + this.length --; + if (this.only) { + delete this.only; + } + else { + var node = this.nodes.splice(index, 1)[0]; + + if (this._first == node) { + delete this._first; + delete this._firstSourceIndex; + delete this._firstSubIndex; + } + +/*@cc_on @if (@_jscript) + delete this.seen[node.sourceIndex][node.subIndex]; +@else @*/ + delete this.seen[NodeID.get(node)]; +/*@end @*/ + } +}; + + +NodeSet.prototype.delDescendant = function(elm, offset) { + if (this.only) return; + var nodeType = elm.nodeType; + if (nodeType != 1 && nodeType != 9) return; + if (uai.applewebkit4) return; + + // element || document + if (!elm.contains) { + if (nodeType == 1) { + var _elm = elm; + elm = { + contains: function(node) { + return node.compareDocumentPosition(_elm) & 8; + } + }; + } + else { + // document + elm = { + contains: function() { + return true; + } + }; + } + } + + var nodes = this.nodes; + for (var i = offset + 1; i < nodes.length; i ++) { + +/*@cc_on @if (@_jscript) + if (nodes[i].node.nodeType == 1 && elm.contains(nodes[i].node)) { +@else @*/ + if (elm.contains(nodes[i])) { +/*@end @*/ + this.del(i); + i --; + } + } +}; + +NodeSet.prototype._add = function(node, reverse) { + +/*@cc_on @if (@_jscript) + + var first, firstSourceIndex, firstSubIndex, sourceIndex, subIndex, attributeName; + + sourceIndex = node.sourceIndex; + subIndex = node.subIndex; + attributeName = node.attributeName; + seen = this.seen; + + seen = seen[sourceIndex] || (seen[sourceIndex] = {}); + + if (node.nodeType == 2) { + seen = seen[subIndex] || (seen[subIndex] = {}); + if (seen[attributeName]) { + return true; + } + seen[attributeName] = true; + } + else { + if (seen[subIndex]) { + return true; + } + seen[subIndex] = true; + } + + if (sourceIndex >= 0x10000 || subIndex >= 0x10000) { + this.shortcut = false; + } + + // if this._first is undefined and this.nodes is not empty + // then first node shortcut is disabled. + if (this._first || this.nodes.length == 0) { + first = this._first; + firstSourceIndex = this._firstSourceIndex; + firstSubIndex = this._firstSubIndex; + if (!first || firstSourceIndex > sourceIndex || (firstSourceIndex == sourceIndex && firstSubIndex > subIndex)) { + this._first = node; + this._firstSourceIndex = sourceIndex; + this._firstSubIndex = subIndex + } + } + +@else @*/ + + var seen = this.seen; + var id = NodeID.get(node); + if (seen[id]) return true; + seen[id] = true; + +/*@end @*/ + + this.length++; + if (reverse) + this.nodes.unshift(node); + else + this.nodes.push(node); +}; + + +NodeSet.prototype.unshift = function(node) { + if (!this.length) { + this.length ++; + this.only = node; + return + } + if (this.only){ + var only = this.only; + delete this.only; + this.unshift(only); + this.length --; + } +/*@cc_on + node = this.createWrapper(node); +@*/ + return this._add(node, true); +}; + + +NodeSet.prototype.push = function(node) { + if (!this.length) { + this.length ++; + this.only = node; + return; + } + if (this.only) { + var only = this.only; + delete this.only; + this.push(only); + this.length --; + } +/*@cc_on + node = this.createWrapper(node); +@*/ + return this._add(node); +}; + +NodeSet.prototype.first = function() { + if (this.only) return this.only; +/*@cc_on + if (this._first) return this._first.node; + if (this.nodes.length > 1) this.sort(); + var node = this.nodes[0]; + return node ? node.node : undefined; +@*/ + if (this.nodes.length > 1) this.sort(); + return this.nodes[0]; +}; + +NodeSet.prototype.list = function() { + if (this.only) return [this.only]; + this.sort(); +/*@cc_on + var i, l, nodes, results; + nodes = this.nodes; + results = []; + for (i = 0, l = nodes.length; i < l; i ++) { + results.push(nodes[i].node); + } + return results; +@*/ + return this.nodes; +}; + +NodeSet.prototype.string = function() { + var node = this.only || this.first(); + return node ? NodeUtil.to('string', node) : ''; +}; + +NodeSet.prototype.bool = function() { + return !! (this.length || this.only); +}; + +NodeSet.prototype.number = function() { + return + this.string(); +}; + +NodeSet.prototype.iterator = function(reverse) { + this.sort(); + var nodeset = this; + + if (!reverse) { + var count = 0; + return function() { + if (nodeset.only && count++ == 0) return nodeset.only; +/*@cc_on @if(@_jscript) + var wrapper = nodeset.nodes[count++]; + if (wrapper) return wrapper.node; + return undefined; +@else @*/ + return nodeset.nodes[count++]; +/*@end @*/ + }; + } + else { + var count = 0; + return function() { + var index = nodeset.length - (count++) - 1; + if (nodeset.only && index == 0) return nodeset.only; +/*@cc_on @if(@_jscript) + var wrapper = nodeset.nodes[index]; + if (wrapper) return wrapper.node; + return undefined; +@else @*/ + return nodeset.nodes[index]; +/*@end @*/ + }; + } +}; + + +var install = function(win) { + + win = win || this; + + win.XPathExpression = function(expr) { + if (!expr.length) { + throw Error('no expression'); + } + var lexer = this.lexer = Lexer(expr); + if (lexer.empty()) { + throw Error('no expression'); + } + this.expr = BinaryExpr.parse(lexer); + if (!lexer.empty()) { + throw Error('bad token: ' + lexer.next()); + } + }; + + win.XPathExpression.prototype.evaluate = function(node, type) { + return new XPathResult(this.expr.evaluate(new Ctx(node)), type); + }; + + win.XPathResult = function (value, type) { + if (type == 0) { + switch (typeof value) { + case 'object': type ++; // 4 + case 'boolean': type ++; // 3 + case 'string': type ++; // 2 + case 'number': type ++; // 1 + } + } + + this.resultType = type; + + switch (type) { + case 1: + this.numberValue = value.isNodeSet ? value.number() : +value; + return; + case 2: + this.stringValue = value.isNodeSet ? value.string() : '' + value; + return; + case 3: + this.booleanValue = value.isNodeSet ? value.bool() : !! value; + return; + case 4: case 5: case 6: case 7: + this.nodes = value.list(); + this.snapshotLength = value.length; + this.index = 0; + this.invalidIteratorState = false; + break; + case 8: case 9: + this.singleNodeValue = value.first(); + return; + } + }; + + win.XPathResult.prototype.iterateNext = function() { return this.nodes[this.index++] }; + win.XPathResult.prototype.snapshotItem = function(i) { return this.nodes[i] }; + + win.XPathResult.ANY_TYPE = 0; + win.XPathResult.NUMBER_TYPE = 1; + win.XPathResult.STRING_TYPE = 2; + win.XPathResult.BOOLEAN_TYPE = 3; + win.XPathResult.UNORDERED_NODE_ITERATOR_TYPE = 4; + win.XPathResult.ORDERED_NODE_ITERATOR_TYPE = 5; + win.XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE = 6; + win.XPathResult.ORDERED_NODE_SNAPSHOT_TYPE = 7; + win.XPathResult.ANY_UNORDERED_NODE_TYPE = 8; + win.XPathResult.FIRST_ORDERED_NODE_TYPE = 9; + + + win.document.createExpression = function(expr) { + return new XPathExpression(expr, null); + }; + + win.document.evaluate = function(expr, context, _, type) { + return document.createExpression(expr, null).evaluate(context, type); + }; +}; + +var win; + +if (config.targetFrame) { + var frame = document.getElementById(config.targetFrame); + if (frame) win = frame.contentWindow; +} + +install(win || window); + +})(); + +// Thanks for reading this source code. We love JavaScript. + diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/iso-2022-jp/one.txt b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/iso-2022-jp/one.txt new file mode 100644 index 0000000..99ce65b --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/iso-2022-jp/one.txt @@ -0,0 +1,11 @@ +ISO-2022-JPã¯ã€ã‚¤ãƒ³ã‚¿ãƒ¼ãƒãƒƒãƒˆä¸Š(特ã«é›»å­ãƒ¡ãƒ¼ãƒ«)ãªã©ã§ä½¿ã‚ã‚Œã‚‹æ—¥æœ¬ã®æ–‡å­—ç”¨ã®æ–‡å­—符å·åŒ–æ–¹å¼ã€‚ISO/IEC 2022ã®ã‚¨ã‚¹ã‚±ãƒ¼ãƒ—シーケンスを利用ã—ã¦æ–‡å­—集åˆã‚’切り替ãˆã‚‹7ビットã®ã‚³ãƒ¼ãƒ‰ã§ã‚ã‚‹ã“ã¨ã‚’特徴ã¨ã™ã‚‹ (アナウンス機能ã®ã‚¨ã‚¹ã‚±ãƒ¼ãƒ—シーケンスã¯çœç•¥ã•れる)。俗ã«ã€ŒJISコードã€ã¨å‘¼ã°ã‚Œã‚‹ã“ã¨ã‚‚ã‚る。 + +æ¦‚è¦ +日本語表記ã¸ã®åˆ©ç”¨ãŒæƒ³å®šã•れã¦ã„る文字コードã§ã‚ã‚Šã€æ—¥æœ¬èªžã®åˆ©ç”¨ã•れるãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã«ãŠã„ã¦ã€æ—¥æœ¬ã®è¦æ ¼ã‚’応用ã—ãŸã‚‚ã®ã§ã‚る。ã¾ãŸæ–‡å­—集åˆã¨ã—ã¦ã¯ã€æ—¥æœ¬èªžã§ç”¨ã„られる漢字ã€ã²ã‚‰ãŒãªã€ã‚«ã‚¿ã‚«ãƒŠã¯ã‚‚ã¡ã‚ã‚“ã€ãƒ©ãƒ†ãƒ³æ–‡å­—ã€ã‚®ãƒªã‚·ã‚¢æ–‡å­—ã€ã‚­ãƒªãƒ«æ–‡å­—ãªã©ã‚‚å«ã‚“ã§ãŠã‚Šã€å­¦è¡“や産業ã®åˆ†é‡Žã§ã®åˆ©ç”¨ã‚‚考慮ãŸã‚‚ã®ã¨ãªã£ã¦ã„ã‚‹ã€‚è¦æ ¼åã«ã€ISOã®æ—¥æœ¬èªžã®è¨€èªžã‚³ãƒ¼ãƒ‰ã§ã‚ã‚‹jaã§ã¯ãªãã€å›½ãƒ»åœ°åŸŸåコードã®JPãŒç¤ºã•れã¦ã„るゆãˆã‚“ã§ã‚る。 +文字集åˆã¨ã—ã¦JIS X 0201ã®C0集åˆï¼ˆåˆ¶å¾¡æ–‡å­—)ã€JIS X 0201ã®ãƒ©ãƒ†ãƒ³æ–‡å­—集åˆã€ISO 646ã®å›½éš›åŸºæº–版図形文字ã€JIS X 0208ã®1978年版(JIS C 6226-1978)ã¨1983å¹´ãŠã‚ˆã³1990年版ãŒåˆ©ç”¨ã§ãる。JIS X 0201ã®ç‰‡ä»®å文字集åˆã¯åˆ©ç”¨ã§ããªã„。1986年以é™ã€æ—¥æœ¬ã®é›»å­ãƒ¡ãƒ¼ãƒ«ã§ç”¨ã„られã¦ããŸJUNETã‚³ãƒ¼ãƒ‰ã‚’ã€æ‘井純・Mark Crispin・Erik van der PoelãŒ1993å¹´ã«RFC化ã—ãŸã‚‚ã®(RFC 1468)。後ã«JIS X 0208:1997ã®é™„属書2ã¨ã—ã¦JISã«è¦å®šã•れãŸã€‚MIMEã«ãŠã‘る文字符å·åŒ–æ–¹å¼ã®è­˜åˆ¥ç”¨ã®åå‰ã¨ã—㦠IANA ã«ç™»éŒ²ã•れã¦ã„る。 +ãªãŠã€ç¬¦å·åŒ–ã®ä»•様ã«ã¤ã„ã¦ã¯ISO/IEC 2022#ISO-2022-JPã‚‚å‚照。 + +ISO-2022-JPã¨éžæ¨™æº–的拡張使用 +「JISコードã€ï¼ˆã¾ãŸã¯ã€ŒISO-2022-JPã€ï¼‰ã¨ã„ã†ã‚³ãƒ¼ãƒ‰åã®è¦å®šä¸‹ã§ã¯ã€ãã®ä»•様通りã®ä½¿ç”¨ãŒæ±‚ã‚られる。ã—ã‹ã—ã€Windows OS上ã§ã¯ã€å®Ÿéš›ã«ã¯CP932コード (Microsoftã«ã‚ˆã‚‹Shift JISã‚’æ‹¡å¼µã—ãŸäºœç¨®ã€‚ISO-2022-JPè¦å®šå¤–文字ãŒè¿½åŠ ã•れã¦ã„る。)ã«ã‚ˆã‚‹ç‹¬è‡ªæ‹¡å¼µï¼ˆã®æ–‡å­—)を断りãªã使ã†ã‚¢ãƒ—リケーションãŒå¤šã„。ã“ã®ä¾‹ã¨ã—ã¦Internet Explorerã‚„Outlook ExpressãŒã‚る。ã¾ãŸã€EmEditorã€ç§€ä¸¸ã‚¨ãƒ‡ã‚£ã‚¿ã‚„Thunderbirdã®ã‚ˆã†ãªMicrosoft社以外ã®Windowsアプリケーションã§ã‚‚åŒæ§˜ã®å ´åˆãŒã‚る。ã“ã®å ´åˆã€ISO-2022-JPã®ç¯„å›²å¤–ã®æ–‡å­—を使ã£ã¦ã—ã¾ã†ã¨ã€ç•°ãªã‚‹è£½å“é–“ã§ã¯æœªå®šç¾©ä¸æ˜Žæ–‡å­—ã¨ã—ã¦èªè­˜ã•れるã‹ã€ã‚‚ã—ãã¯æ–‡å­—化ã‘ã‚’èµ·ã“ã™åŽŸå› ã¨ãªã‚‹ã€‚ãã®ãŸã‚ã€Windows用ã®é›»å­ãƒ¡ãƒ¼ãƒ«ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆã§ã‚ã£ã¦ã‚‚ç‹¬è‡ªæ‹¡å¼µã®æ–‡å­—を使用ã™ã‚‹ã¨è­¦å‘Šã‚’出ã—ãŸã‚Šã€ã‚ãˆã¦ä½¿ãˆãªã„よã†ã«åˆ¶é™ã—ã¦ã„ã‚‹ã‚‚ã®ã‚‚存在ã™ã‚‹ã€‚ã•らã«ã¯ISO-2022-JPã®ç¯„囲内ã§ã‚ã£ã¦ã‚‚CP932ã¯éžæ¨™æº–文字(FULLWIDTH TILDE等)をæŒã¤ã®ã§æ–‡å­—化ã‘ã®åŽŸå› ã«ãªã‚Šå¾—る。 +ã¾ãŸã€ç¬¦å·åŒ–æ–¹å¼åã‚’ISO-2022-JPã¨ã—ã¦ã„ã‚‹ã®ã«ã€æ–‡å­—集åˆã¨ã—ã¦ã¯JIS X 0212 (ã„ã‚ゆる補助漢字) ã‚„JIS X 0201ã®ç‰‡ä»®åæ–‡å­—é›†åˆ (ã„ã‚ゆるåŠè§’カナ) をも符å·åŒ–ã—ã¦ã„る例ãŒã‚ã‚‹ãŒã€ISO-2022-JPã§ã¯ã“ã‚Œã‚‰ã®æ–‡å­—を許容ã—ã¦ã„ãªã„。ã“れらã®ç¬¦å·åŒ–ã¯ç‹¬è‡ªæ‹¡å¼µã®å®Ÿè£…ã§ã‚りã€ä¸­ã«ã¯ISO/IEC 2022ã®ä»•æ§˜ã«æº–æ‹ ã™ã‚‰ã—ã¦ã„ãªã„ã‚‚ã®ã‚‚ã‚ã‚‹[2]。従ã£ã¦å—ä¿¡å´ã®é›»å­ãƒ¡ãƒ¼ãƒ«ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆãŒã“れらã®ç‹¬è‡ªæ‹¡å¼µã«å¯¾å¿œã—ã¦ã„ãªã„å ´åˆã€ãã®æ–‡å­—ã‚ã‚‹ã„ã¯ãã®æ–‡å­—ã‚’å«ã‚€è¡Œã€æ™‚ã«ã¯ãƒ†ã‚­ã‚¹ãƒˆå…¨ä½“ãŒæ–‡å­—化ã‘ã™ã‚‹ã“ã¨ãŒã‚る。 + diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/iso-8859-1/one.txt b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/iso-8859-1/one.txt new file mode 100644 index 0000000..3101178 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/iso-8859-1/one.txt @@ -0,0 +1,19 @@ +Op mat eraus hinnen beschte, rou zënne schaddreg ké. Ké sin Eisen Kaffi prächteg, den haut esou Fielse wa, Well zielen d'Welt am dir. Aus grousse rëschten d'Stroos do, as dat Kléder gewëss d'Kàchen. Schied gehéiert d'Vioule net hu, rou ke zënter Säiten d'Hierz. Ze eise Fletschen mat, gei as gréng d'Lëtzebuerger. Wäit räich no mat. + +Säiten d'Liewen aus en. Un gëtt bléit lossen wee, da wéi alle weisen Kolrettchen. Et deser d'Pan d'Kirmes vun, en wuel Benn rëschten méi. En get drem ménger beschte, da wär Stad welle. Nun Dach d'Pied do, mä gét ruffen gehéiert. Ze onser ugedon fir, d'Liewen Plett'len ech no, si Räis wielen bereet wat. Iwer spilt fir jo. + +An hin däischter Margréitchen, eng ke Frot brommt, vu den Räis néierens. Da hir Hunn Frot nozegon, rout Fläiß Himmel zum si, net gutt Kaffi Gesträich fu. Vill lait Gaart sou wa, Land Mamm Schuebersonndeg rei do. Gei geet Minutt en, gei d'Leit beschte Kolrettchen et, Mamm fergiess un hun. + +Et gutt Heck kommen oft, Lann rëscht rei um, Hunn rëscht schéinste ke der. En lait zielen schnéiwäiss hir, fu rou botze éiweg Minutt, rem fest gudden schaddreg en. Noper bereet Margréitchen mat op, dem denkt d'Leit d'Vioule no, oft ké Himmel Hämmel. En denkt blénken Fréijor net, Gart Schiet d'Natur no wou. No hin Ierd Frot d'Kirmes. Hire aremt un rou, ké den éiweg wielen Milliounen. + +Mir si Hunn Blénkeg. Ké get ston derfir d'Kàchen. Haut d'Pan fu ons, dé frou löschteg d'Meereische rei. Sou op wuel Léift. Stret schlon grousse gin hu. Mä denkt d'Leit hinnen net, ké gét haut fort rëscht. + +Koum d'Pan hannendrun ass ké, ké den brét Kaffi geplot. Schéi Hären d'Pied fu gét, do d'Mier néierens bei. Rëm päift Hämmel am, wee Engel beschéngt mä. Brommt klinzecht der ke, wa rout jeitzt dén. Get Zalot d'Vioule däischter da, jo fir Bänk päift duerch, bei d'Beem schéinen Plett'len jo. Den haut Faarwen ze, eng en Biereg Kirmesdag, um sin alles Faarwen d'Vioule. + +Eng Hunn Schied et, wat wa Frot fest gebotzt. Bei jo bleiwe ruffen Klarinett. Un Feld klinzecht gét, rifft Margréitchen rem ke. Mir dé Noper duurch gewëss, ston sech kille sin en. Gei Stret d'Wise um, Haus Gart wee as. Monn ménger an blo, wat da Gart gefällt Hämmelsbrot. + +Brommt geplot och ze, dat wa Räis Well Kaffi. Do get spilt prächteg, as wär kille bleiwe gewalteg. Onser frësch Margréitchen rem ke, blo en huet ugedon. Onser Hemecht wär de, hu eraus d'Sonn dat, eise deser hannendrun da och. + +As durch Himmel hun, no fest iw'rem schéinste mir, Hunn séngt Hierz ke zum. Séngt iw'rem d'Natur zum an. Ke wär gutt Grénge. Kënnt gudden prächteg mä rei. Dé dir Blénkeg Klarinett Kolrettchen, da fort muerges d'Kanner wou, main Feld ruffen vu wéi. Da gin esou Zalot gewalteg, gét vill Hemecht blénken dé. + +Haut gréng nun et, nei vu Bass gréng d'Gaassen. Fest d'Beem uechter si gin. Oft vu sinn wellen kréien. Et ass lait Zalot schéinen. \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/one.txt b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/one.txt new file mode 100644 index 0000000..26c94d5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/one.txt @@ -0,0 +1,22 @@ +Код одно гринÑпана руководишь на. Его вы Ð·Ð½Ð°Ð½Ð¸Ñ Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ðµ. Ты две начать +одиночку, Ñказать оÑнователь удовольÑтвием но миф. Бы какие ÑиÑтема тем. +ПолноÑтью иÑпользует три мы, человек клоунов те наÑ, бы давать творчеÑкую +ÑзотеричеÑÐºÐ°Ñ ÑˆÐµÑ„. + +Мог не помнить никакого ÑÑкономленного, две либо какие пишите бы. Должен +компанию кто те, Ñтот заключалаÑÑŒ проектировщик не ты. Глупые периоды ты +длÑ. Вам который хороший он. Те любых ÐºÑ€ÐµÐ¼Ð½Ð¸Ñ ÐºÐ¾Ð½Ñ†ÐµÐ½Ñ‚Ñ€Ð¸Ñ€ÑƒÑŽÑ‚ÑÑ Ð¼Ð¾Ð³, +Ñобирать принадлежите без вы. + +ДжоÑла меньше хорошего вы миф, за тем году разработки. Даже управлÑющим +руководители был не. Три коде выпуÑкать заботитьÑÑ Ð½Ñƒ. То его ÑиÑтема +удовольÑтвием безоÑтановочно, или ты главной процеÑÑорах. Мы без джоÑл +Ð·Ð½Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð»ÑƒÑ‡Ð°Ñ‚, Ñтатьи оÑтальные мы ещё. + +Ðих руÑÑком каÑаетÑÑ Ð¿Ð¾Ñкольку по, образование должником +ÑиÑтематизированный ну мои. Прийти кандидата универÑитет но наÑ, Ð´Ð»Ñ Ð±Ñ‹ +должны никакого, биг многие причин Ð¸Ð½Ñ‚ÐµÑ€Ð²ÑŒÑŽÐ¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð·Ð°. + +Тем до плиту почему. Вот учёт такие одного бы, об биг разным внешних +промежуток. Ð’Ð°Ñ Ð´Ð¾ какому возможноÑтей безответÑтвенный, были погодите бы +его, по них глупые долгий количеÑтва. diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/three.txt b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/three.txt new file mode 100644 index 0000000..c81ccd5 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/three.txt @@ -0,0 +1,45 @@ +Αν ήδη διάβασε γλιτώσει μεταγλωτίσει, αυτήν θυμάμαι μου μα. Την κατάσταση χÏησιμοποίησέ να! Τα διαφοÏά φαινόμενο διολισθήσεις πες, υψηλότεÏη Ï€Ïοκαλείς πεÏισσότεÏες όχι κι. Με ελέγχου γίνεται σας, μικÏής δημιουÏγοÏν τη του. Τις τα γÏάψει εικόνες απαÏάδεκτη? + +Îα ότι Ï€Ïώτοι απαÏαίτητο. Άμεση πετάνε κακόκεφος τον ÏŽÏ‚, να χώÏου πιθανότητες του. Το μέχÏι οÏίστε λιγότεÏους σας. Πω ναί φυσικά εικόνες. + +Μου οι κώδικα αποκλειστικοÏÏ‚, λες το μάλλον συνεχώς. Îέου σημεία απίστευτα σας μα. ΧÏόνου μεταγλωτιστής σε νέα, τη τις πιάνει μποÏοÏσες Ï€ÏογÏαμματιστές. Των κάνε βγαίνει εντυπωσιακό τα? ΚÏατάει τεσσαÏών δυστυχώς της κι, ήδη υψηλότεÏη εξακολουθεί τα? + +ÎÏα πετάνε μποÏοÏσε λιγότεÏους αν, τα απαÏάδεκτη συγχωνευτεί Ïοή. Τη έγÏαψες συνηθίζουν σαν. Όλα με υλικό στήλες χειÏότεÏα. Ανώδυνη δουλέψει επί ως, αν διαδίκτυο εσωτεÏικών παÏάγοντες από. ΚεντÏικό επιτυχία πες το. + +Πω ναι λέει τελειώσει, έξι ως έÏγων τελειώσει. Με αÏχεία βουτήξουν ανταγωνιστής ÏŽÏα, Ï€Î¿Î»Ï Î³Ïαφικά σελίδων τα στη. ÎŒÏο οέλεγχος δημιουÏγοÏν δε, ας θέλεις ελέγχου συντακτικό ÏŒÏο! Της θυμάμαι επιδιόÏθωση τα. Για μποÏοÏσε πεÏισσότεÏο αν, μέγιστη σημαίνει αποφάσισε τα του, άτομο αποτελέσει τι στα. + +Τι στην αφήσεις διοίκηση στη. Τα εσφαλμένη δημιουÏγια επιχείÏιση έξι! Βήμα μαγικά εκτελέσει ανά τη. Όλη αφήσεις συνεχώς εμποÏικά αν, το λες κόλπα επιτυχία. Ότι οι ζώνη κειμένων. ÎŒÏο κι Ïωτάει γÏαμμής πελάτες, τελειώσει διολισθήσεις καθυστεÏοÏσε αν εγώ? Τι πετοÏν διοίκηση Ï€Ïοβλήματα ήδη. + +Τη γλιτώσει Î±Ï€Î¿Î¸Î·ÎºÎµÏ…Ï„Î¹ÎºÎ¿Ï Î¼Î¹Î±. Πω έξι δημιουÏγια πιθανότητες, ως πέντε ελέγχους εκτελείται λες. Πως εÏωτήσεις διοικητικό συγκεντÏωμένοι οι, ας συνεχώς διοικητικό αποστηθίσει σαν. Δε Ï€Ïώτες συνεχώς διολισθήσεις έχω, από τι κανένας βουτήξουν, γειτονιάς Ï€Ïοσεκτικά ανταγωνιστής κι σαν. + +ΔημιουÏγια συνηθίζουν κλπ τι? Όχι ποσοστό διακοπής κι. Κλπ φακέλους δεδομένη εξοÏγιστικά θα? Υποψήφιο καθοÏίζουν με όλη, στα πήÏε Ï€Ïοσοχή εταιÏείες πω, ÏŽÏ‚ τον συνάδελφος διοικητικό δημιουÏγήσεις! ΔοÏλευε επιτίθενται σας θα, με ένας παÏαγωγικής ένα, να ναι σημεία μέγιστη απαÏάδεκτη? + +Σας τεσσαÏών συνεντεÏξης τη, αÏπάζεις σίγουÏος μη για', επί τοπικές εντολές ακοÏσει θα? Ως δυστυχής μεταγλωτιστής όλη, να την είχαν σφάλμα απαÏαίτητο! Μην ÏŽÏ‚ άτομο διοÏθώσει χÏησιμοποιοÏνταν. Δεν τα κόλπα πετάξαμε, μη που άγχος Ï…ÏŒÏκη άμεση, Î±Ï†Î¿Ï Î´Ï…ÏƒÏ„Ï…Ï‡ÏŽÏ‚ διακόψουμε ÏŒÏο αν! Όλη μαγικά πετάνε επιδιοÏθώσεις δε, Ïοή φυσικά αποτελέσει πω. + +ΆπειÏα παÏαπάνω φαινόμενο πω ÏŽÏα, σαν πόÏτες κÏατήσουν συνηθίζουν ως. Κι ÏŽÏα Ï„Ïέξει είχαμε εφαÏμογή. Απλό σχεδιαστής μεταγλωτιστής ας επί, τις τα όταν έγÏαψες γÏαμμής? Όλα κάνεις συνάδελφος εÏγαζόμενοι θα, χαÏÏ„Î¹Î¿Ï Ï‡Î±Î¼Î·Î»ÏŒÏ‚ τα Ïοή. Ως ναι ÏŒÏοφο έÏθει, μην πελάτες αποφάσισε μεταφÏαστής με, να βιαστικά εκδόσεις αναζήτησης λες. Των φταίει εκθέσεις Ï€Ïοσπαθήσεις οι, σπίτι αποστηθίσει ας λες? + +ÎÏ‚ που υπηÏεσία απαÏαίτητο δημιουÏγείς. Μη άÏα χαÏά καθώς νÏχτας, πω ματ μπουν είχαν. Άμεση δημιουÏγείς ÏŽÏ‚ Ïοή, γÏάψει γÏαμμής σίγουÏος στα τι! Αν Î±Ï†Î¿Ï Ï€Ïώτοι εÏγαζόμενων ναί. + +Άμεση διοÏθώσεις με δÏο? Έχουν παÏάδειγμα των θα, μου έÏθει θυμάμαι πεÏισσότεÏο το. Ότι θα Î±Ï†Î¿Ï Ï‡Ïειάζονται πεÏισσότεÏες. Σαν συνεχώς πεÏίπου οι. + +ÎÏ‚ Ï€Ïώτης πετάξαμε λες, ÏŒÏο κι Ï€Ïώτες ζητήσεις δυστυχής. Ανά χÏόνου διακοπή επιχειÏηματίες ας, ÏŽÏ‚ μόλις άτομο χειÏότεÏα ÏŒÏο, κÏατάει σχεδιαστής Ï€Ïοσπαθήσεις νέο το. Πουλάς Ï€Ïοσθέσει όλη πω, Ï„Ïπου χαÏακτηÏιστικό εγώ σε, πω πιο δοÏλευε αναζήτησης? ΑναφοÏά δίνοντας σαν μη, μάθε δεδομένη εσωτεÏικών με ναι, αναφέÏονται πεÏιβάλλοντος ÏŽÏα αν. Και λέει απόλαυσε τα, που το ÏŒÏοφο Ï€ÏοσπαθοÏν? + +Πάντα χÏόνου χÏήματα ναι το, σαν σωστά θυμάμαι σκεφτείς τα. Μα αποτελέσει ανεπιθÏμητη την, πιο το τέτοιο ατόμου, τη των Ï„Ïόπο εÏγαλείων επιδιόÏθωσης. ΠεÏιβάλλον παÏαγωγικής σου κι, κλπ οι Ï„Ïπου κακόκεφους αποστηθίσει, δε των πλέον Ï„Ïόποι. Πιθανότητες χαÏακτηÏιστικών σας κι, γÏαφικά δημιουÏγήσεις μια οι, πω πολλοί εξαÏτάται Ï€Ïοσεκτικά εδώ. Σταματάς παÏάγοντες για' ÏŽÏ‚, στις Ïωτάει το ναι! ΚαÏέκλα ζητήσεις συνδυασμοÏÏ‚ τη ήδη! + +Για μαγικά συνεχώς ακοÏσει το. Σταματάς Ï€Ïοϊόντα βουτήξουν ÏŽÏ‚ Ïοή. Είχαν Ï€Ïώτες οι ναι, μα λες αποστηθίσει ανακαλÏπτεις. ÎŒÏοφο άλγεβÏα παÏαπάνω εδώ τη, Ï€Ïόσληψη λαμβάνουν καταλάθος ήδη ας? Ως και εισαγωγή κÏατήσουν, ένας κακόκεφους κι μας, όχι κώδικάς παίξουν πω. Πω νέα κÏατάει εκφÏάσουν, τότε τελικών τη όχι, ας της Ï„Ïέξει αλλάζοντας αποκλειστικοÏÏ‚. + +Ένας βιβλίο σε άÏα, ναι ως γÏάψει ταξινομεί διοÏθώσεις! Εδώ να γεγονός συγγÏαφείς, ÏŽÏ‚ ήδη διακόψουμε επιχειÏηματίες? Ότι πακέτων εσφαλμένη κι, θα ÏŒÏο κόλπα παÏαγωγικής? Αν έχω κεντÏικό υψηλότεÏη, κι δεν ίδιο πετάνε παÏατηÏοÏμενη! Που λοιπόν σημαντικό μα, Ï€Ïοκαλείς χειÏοκÏοτήματα ως όλα, μα επί κόλπα άγχος γÏαμμές! Δε σου κάνεις βουτήξουν, μη έÏγων επενδυτής χÏησιμοποίησέ στα, ως του Ï€Ïώτες διάσημα σημαντικό. + +Βιβλίο τεÏάστιο Ï€ÏοκÏπτουν σαν το, σαν Ï„Ïόπο επιδιόÏθωση ας. Είχαν Ï€Ïοσοχή Ï€Ïοσπάθεια κι ματ, εδώ ως έτσι σελίδων συζήτηση. Και στην βγαίνει εσφαλμένη με, δυστυχής παÏάδειγμα δε μας, από σε Ï…ÏŒÏκη επιδιόÏθωσης. Îέα πω νέου πιθανό, στήλες συγγÏαφείς μπαίνοντας μα για', το Ïωτήσει κακόκεφους της? Μου σε αÏέσει συγγÏαφής συγχωνευτεί, μη μου Ï…ÏŒÏκη ξέχασε διακοπής! ÎÏ‚ επί αποφάσισε αποκλειστικοÏÏ‚ χÏησιμοποιώντας, χÏήματα σελίδων ταξινομεί ναι με. + +Μη ανά γÏαμμή απόλαυσε, πω ναι μάτσο διασφαλίζεται. Τη έξι μόλις εÏγάστηκε δημιουÏγοÏν, έκδοση αναφοÏά δυσκολότεÏο οι νέο. Σας ως μποÏοÏσε παÏάδειγμα, αν ότι δοÏλευε μποÏοÏσε αποκλειστικοÏÏ‚, πιο λέει βουτήξουν διοÏθώσει ως. Έχω τελευταία κακόκεφους ας, όσο εÏγαζόμενων δημιουÏγήσεις τα. + +Του αν δουλέψει μποÏοÏσε, πετοÏν χαμηλός εδώ ας? ΚÏκλο Ï„Ïπους με που, δεν σε έχουν συνεχώς χειÏότεÏα, τις τι απαÏάδεκτη συνηθίζουν? Θα μην τους αυτήν, τη ένα πήÏε πακέτων, κι Ï€ÏοκÏπτουν πεÏιβάλλον πως. Μα για δουλέψει απόλαυσε εφαμοÏγής, ÏŽÏ‚ εδώ σημαίνει μποÏοÏσες, άμεση ακοÏσει Ï€Ïοσοχή τη εδώ? + +Στα δώσε αθόÏυβες λιγότεÏους οι, δε αναγκάζονται αποκλειστικοÏÏ‚ όλα! Ας μπουν διοικητικό μια, πάντα ελέγχου διοÏθώσεις ÏŽÏ‚ τον. Ότι πήÏε κανόνα μα. Που άτομα κάνεις δημιουÏγίες τα, οι μας Î±Ï†Î¿Ï ÎºÏŒÎ»Ï€Î± Ï€ÏογÏαμματιστής, Î±Ï†Î¿Ï Ï‰Ïαίο Ï€ÏοκÏπτουν στα ως. Θέμα χÏησιμοποιήσει αν όλα, του τα άλγεβÏα σελίδων. Τα ότι ανώδυνη δυστυχώς συνδυασμοÏÏ‚, μας οι πάντα γνωÏίζουμε ανταγωνιστής, όχι τα δοκιμάσεις σχεδιαστής! Στην συνεντεÏξης επιδιόÏθωση πιο τα, μα από πουλάς πεÏιβάλλον παÏαγωγικής. + +Έχουν μεταγλωτίσει σε σας, σε πάντα Ï€Ïώτης μειώσει των, γÏάψει Ïουτίνα δυσκολότεÏο ήδη μα? Ταξινομεί διοÏθώσεις να μας. Θα της Ï€ÏοσπαθοÏν πεÏιεχόμενα, δε έχω τοπικές στέλνοντάς. Ανά δε αλφα άμεση, κάποιο Ïωτάει γνωÏίζουμε πω στη, φÏάση μαγικά συνέχεια δε δÏο! Αν είχαμε μειώσει Ïοή, μας μετÏάει καθυστεÏοÏσε επιδιοÏθώσεις μη. Χάος Ï…ÏŒÏκη κεντÏικό έχω σε, ανά πεÏίπου αναγκάζονται πω. + +Όσο επιστÏέφουν χÏονοδιαγÏάμματα μη. Πως ωÏαίο κακόκεφος διαχειÏιστής ως, τις να διακοπής αναζήτησης. Κάποιο ποσοστό ταξινομεί επί τη? Μάθε άμεση αλλάζοντας δÏο με, μου νέου πάντα να. + +Πω του δυστυχώς πιθανότητες. Κι Ïωτάει υψηλότεÏη δημιουÏγια ότι, πω εισαγωγή τελευταία απομόνωση ναι. Των ζητήσεις γνωÏίζουμε ÏŽÏ‚? Για' μη παÏαδοτέου αναφέÏονται! Ύψος παÏαγωγικά Ïοή ως, φυσικά διάβασε εικόνες όσο σε? Δεν Ï…ÏŒÏκη διοÏθώσεις επεξεÏγασία θα, ως μέση σÏστημα χÏησιμοποιήσει τις. \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/two.txt b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/two.txt new file mode 100644 index 0000000..2443fc4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/_samples/charsets/utf-8/two.txt @@ -0,0 +1,3 @@ +रखति आवशà¥à¤¯à¤•त पà¥à¤°à¥‡à¤°à¤¨à¤¾ मà¥à¤–à¥à¤¯à¤¤à¤¹ हिंदी किà¤à¤²à¥‹à¤— असकà¥à¤·à¤® कारà¥à¤¯à¤²à¤¯ करते विवरण किके मानसिक दिनांक पà¥à¤°à¥à¤µ संसाध à¤à¤µà¤®à¥ कà¥à¤¶à¤²à¤¤à¤¾ अमितकà¥à¤®à¤¾à¤° पà¥à¤°à¥‹à¤¤à¥à¤¸à¤¾à¤¹à¤¿à¤¤ जनित देखने उदेशीत विकसित बलवान बà¥à¤°à¥Œà¤¶à¤° किà¤à¤²à¥‹à¤— विशà¥à¤²à¥‡à¤·à¤£ लोगो कैसे जागरà¥à¤• पà¥à¤°à¤µà¥à¤°à¥à¤¤à¤¿ पà¥à¤°à¥‹à¤¤à¥à¤¸à¤¾à¤¹à¤¿à¤¤ सदसà¥à¤¯ आवशà¥à¤¯à¤•त पà¥à¤°à¤¸à¤¾à¤°à¤¨ उपलबà¥à¤§à¤¤à¤¾ अथवा हिंदी जनित दरà¥à¤¶à¤¾à¤¤à¤¾ यनà¥à¤¤à¥à¤°à¤¾à¤²à¤¯ बलवान अतित सहयोग शà¥à¤°à¥à¤†à¤¤ सभीकà¥à¤› माहितीवानीजà¥à¤¯ लिये खरिदे है।अभी à¤à¤•तà¥à¤°à¤¿à¤¤ समà¥à¤ªà¤°à¥à¤• रिती मà¥à¤¶à¥à¤•िल पà¥à¤°à¤¾à¤¥à¤®à¤¿à¤• भेदनकà¥à¤·à¤®à¤¤à¤¾ विशà¥à¤µ उनà¥à¤¹à¥‡ गटको दà¥à¤µà¤¾à¤°à¤¾ तकरीबन + +विशà¥à¤µ दà¥à¤µà¤¾à¤°à¤¾ वà¥à¤¯à¤¾à¤–à¥à¤¯à¤¾ सके। आजपर वातावरण वà¥à¤¯à¤¾à¤–à¥à¤¯à¤¾à¤¨ पहोच। हमारी कीसे पà¥à¤°à¤¾à¤¥à¤®à¤¿à¤• विचारशिलता पà¥à¤°à¥à¤µ करती कमà¥à¤ªà¥à¤¯à¥à¤Ÿà¤° भेदनकà¥à¤·à¤®à¤¤à¤¾ लिये बलवान औरà¥à¥ªà¥«à¥¦ यायेका वारà¥à¤¤à¤¾à¤²à¤¾à¤ª सà¥à¤šà¤¨à¤¾ भारत शà¥à¤°à¥à¤†à¤¤ लाभानà¥à¤µà¤¿à¤¤ पढाठसंसà¥à¤¥à¤¾ वरà¥à¤£à¤¿à¤¤ मारà¥à¤—दरà¥à¤¶à¤¨ चà¥à¤¨à¤¨à¥‡ \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/dkim/dkim.test.priv b/vendor/swiftmailer/swiftmailer/tests/_samples/dkim/dkim.test.priv new file mode 100644 index 0000000..3bd381a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/_samples/dkim/dkim.test.priv @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDZeUdi1RKnm9cRYNn6E24xxrRTouh3Va8JOEHQ5SB018lvbjwH +2lW5mZ/I0kh/dHsTN0zcN0VE62WIbnLreMk/af/4Pg1i93+c9TmfXmoropsmdLos +w0tjq50jGbBqtHZNJYAokP/u3uUuRw8g0V/O4zlQ3GlO/PDH7xDQzekl9wIDAQAB +AoGAaoCBXD5a72hbb/BNb7HaUlgscZUjYWW93bcGTGYZef8/b+m9Tl83gjhgzvlk +db62k1eOtX3/11uskp78eqLhctv7yWc0mQQhgOogY2qCwHTCH8wja8kJkUAnKQhs +P9sa5iJvgckiuX3SdxgTMwib9d1VyGq6YywiORiZF9rxyhECQQD/xhiZSi7y0ciB +g4bassy0GVMS7EDRumMHc8wC23E1H2mj5yPE/QLqkW4ddmCv2BbJnYmyNvOaK9tk +T2W+mn3/AkEA2aqDGja9CaTlY4BCXfiT166n+xVl5+d+1DENQ4FK9O2jpSi1265J +tjEkXVxUOpV1ZEcUVOdK6RpvsKpc7vVICQJBALEFO5UsQJ4SD0GD9Ft8kCy9sj9Q +f/Qnmc5YmIQJuKpZmVW07Y6yxcfu61U8zuIlHnBftiM/4Q18+RTN1s86QaUCQHoL +9MTfCnH85q46/XuJZQRbp07O+bvlfqTl+CTwuyHImaiCwi2ydRxWQ6ihm4zZvuAC +RvEwWz2HGDc73y4RlFkCQDDdnN9e46l1nMDLDI4cyyGBVg4Z2IZ3IAu5GaoUCGjM +a8w6kxE8f1d8DD5vvqVbmfK89TA/DjT+7/arBNBCiCM= +-----END RSA PRIVATE KEY----- diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/dkim/dkim.test.pub b/vendor/swiftmailer/swiftmailer/tests/_samples/dkim/dkim.test.pub new file mode 100644 index 0000000..b503a91 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/_samples/dkim/dkim.test.pub @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZeUdi1RKnm9cRYNn6E24xxrRT +ouh3Va8JOEHQ5SB018lvbjwH2lW5mZ/I0kh/dHsTN0zcN0VE62WIbnLreMk/af/4 +Pg1i93+c9TmfXmoropsmdLosw0tjq50jGbBqtHZNJYAokP/u3uUuRw8g0V/O4zlQ +3GlO/PDH7xDQzekl9wIDAQAB +-----END PUBLIC KEY----- diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/files/data.txt b/vendor/swiftmailer/swiftmailer/tests/_samples/files/data.txt new file mode 100644 index 0000000..3f35021 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/_samples/files/data.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/files/textfile.zip b/vendor/swiftmailer/swiftmailer/tests/_samples/files/textfile.zip new file mode 100644 index 0000000..5a580ec Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/tests/_samples/files/textfile.zip differ diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.crt b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.crt new file mode 100644 index 0000000..f9b681d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.crt @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFvTCCA6WgAwIBAgIJAMfbOeZpF5T7MA0GCSqGSIb3DQEBBQUAMHUxCzAJBgNV +BAYTAkZSMQ8wDQYDVQQIDAZGcmFuY2UxDjAMBgNVBAcMBVBhcmlzMRQwEgYDVQQK +DAtTd2lmdG1haWxlcjEOMAwGA1UEAwwFc21pbWUxHzAdBgkqhkiG9w0BCQEWEGlu +Zm9AZXhhbXBsZS5jb20wHhcNMTIwNzA1MTMwNTA4WhcNMTMwNzA1MTMwNTA4WjB1 +MQswCQYDVQQGEwJGUjEPMA0GA1UECAwGRnJhbmNlMQ4wDAYDVQQHDAVQYXJpczEU +MBIGA1UECgwLU3dpZnRtYWlsZXIxDjAMBgNVBAMMBXNtaW1lMR8wHQYJKoZIhvcN +AQkBFhBpbmZvQGV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAm4Ry8cHla8loHgbR+TAWjtvUj6uEVV1ZzYY4EWX5Ny6iuFRezKpy8737 +Pv8HJ48OrGqWsWrXwjRsd11eIClOEyBvq7oIbzL5/wR9yelT55b+QGngQkq3nbTs +Aej8G/8HB/SEB3cA1jLRVykSssDPtuutMv5ggh4gj9piJU9cCmLpSoqXSvNQJf4V +wrTp4IHb4qRAUvlxYIYJZDHu5vsFUr+eXQvguZjxlSm3HKSe1biRuDy3RO34kt1m +fNT+53d4UNDJjdHs0yJClZv7NY3YCvC6TMkcsRJItlbl1i6Zrp/ZSjxNW6R7GaLC +NV4a34rLDbRp5GD2Kt5LVA5gpjKU6NUJU2QPdr8iMR7Iuyj2cttZFjaLOcM8p8sM +zlQqTjR0ZFVt6OapguWdi+BP/EyKKWIjCFQR/Rqr2ifpgldTGkU2gKC41W7X6+MJ +EwaxfekMvCRvEOvBDGEnHrMMRMpt0JQtrzCVaxzHsJjAqSHlLiL0SVYW9eGftRQr +sq4Pk5hSUxwCqQ9nCUft9/ttiJ6xee4Qv4wkUSHYscKl/yPJOqXYeqiD/SVlKTqC +98sCAPSsgaiiEaV5pzhJdFdhw7I/4h+liMQq7FRnjKyl815qd9+Uxrbl140eYb71 +p8QGBtnl+CyBxdHLvxagnkVccqknxkl4umJ7mYEjzdFn44J+cdUCAwEAAaNQME4w +HQYDVR0OBBYEFKPRSJpOWw7egbOTpaXoIx3CV273MB8GA1UdIwQYMBaAFKPRSJpO +Ww7egbOTpaXoIx3CV273MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggIB +AA3Kkp7u/fZd8NPqPmPQNhsQNhDxAXjtNKIC3crwO8rSJCWtaLFmngfVCCI8hsbe +KDUD3wsNwH2i08RXjwUkvCqFyEj/JlKtlYYzgq1pQ/cz5rAToHUBTMVq5k1HR7dp +J7OcOan4hV8VWO+ph6YrWDtSGqrErUPRiU1/Y7PNGnc+e2M/XUfJHcAdWgQ0C3uU +/GnuDtu12G9++yQODZw8CmsZp6/5DrTku7aBBrvgeuLt1629WhAD6tloIkP8dZtu +GMdcJFrAZ7l7Iovg7WREiC2sfTTPVM5u+7O2bA95rAPS2wL6X9jN/36m+iQZQxPC +F+/vt/4SaSya19PJkHr496qLNQmK/1rke6eg0ASv88aVBF1nyOcDZ8S+E1tPzfcH +Eao0hO89zBA8G1sLtAi4qF0zub7UJ79a6YMd+XI5ckmzN+MbEqKqoNB3Ay1PNPH+ +bH7sKN5wFwEbyPydQ/CZKBIApFy0J/xhLEpOOP/gePWJzFciXLJDD2D+d9mmAO1C +1/spWexhBoLa04WTzqUATVZ8grDzi1PDyFa0swhl5HRQgWK765A38xwaXqwzh+R7 +mYFJPr3hOkcTrb/RKQFHHSqtmKVf7TWcXN5liBx5Qwi9P57M/LKGFEAkk2iqM/TY +y/QiqwrLy7kNbYJKatJBmxwoZFF/bajBqzWgJ25cwQT3 +-----END CERTIFICATE----- diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.key b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.key new file mode 100644 index 0000000..602df00 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,6B641FB918566EBA + +JmfykH5+jbXqp31r838O3xg+MBD9zAN5KnLWhVBnIr+AjG7bzSldhpj9upSUy4C9 +rb+pq2KkDs2Z1fHS2RelOBvj1BeYrM4MEMQiAwHabQE1A2Q53Ef5uZH15hNfvL/J +ZgHCqR8EpZ7a/5Bybr5jJPiTTWOVxXq3SKxhCzqvQPD2z/ADgYGMw2AcLwN6EeAa +IHgLgvBfX280snL2CgoQpvog7Vzk1zoVu0Vf+n8iOi65F0xViAPY3r6PlIpcqb5U +lI6Tuy+32AKpimSTqgHSWj8iqmtMNRRZ988s01wGgVAjL2B1dk1wJaerU0X04dM3 +2acRKHTsdDqmm8KLhYYZjf/CkLp3ArLnIa7LUg50T8LiN/A3H2tXuLRknsTLtud4 +95PC6NHMztT1Vs6BxkAoB+bJGKTB+Rj0z7FvfPydS3Y5UlrYbOgRlVAF2LCF53kq +2Z0zEfQUC4wxZ3stX+xZ6fa8kf/ET19jzo2YhA4pDNHf05iizkzTPsiUlmHZiuC0 +kaw1XuT5/dsyF6xZqAtwvnIgS4VCicmwJGo20CCYx39icm1Uw6f3LvlIFVL3Tl5B +tHICscGTrRNlqszRoU2Qps5Svs40qJzOQhOhN5SfoRyFx/s3Nq1yBaLfO5IkXmlE +KvvG3DSJiQ0RaADHi0IKacJOs2bnAC4ZqZhndNUz4P58HXIy6LEj1Pulb55Rc/tt +kG4znrmoKjXIIs05RMzVFbKh7XRUEB3xhha9CmkqcLxSV3NM3qOKcHhXitrER7IS +ldXI8ksJqtEhyniN8XlsIHenypCv7lHOCpim1h6B0jEIaouB3AJTialNxfSRBwfd +X5yq6y5JKlujj5ybgE7y7sNHjGp7nC3sr7lf6QWBw2OoWJm/qQ5SpmHzfzzg0fkz +BOAFuSE/+/tiht/Ky5Knh4fK6xH6GrUWBYyAl8wD8l/YKH8uvR1ndjiorW7mC2H+ +pXXLum9bMfgubVBb/F2mP2LVtLsthlnwkH4pzg5n5NyI3VePguXZOZlI6gXRbaSc +13marX9KoW4gL9i7XZXzRIA0VhY3ixmhoQC2qjeO5fnhttPQtWsn1w3A1xPjUMJ+ +5SmPvXbd5fjTrPD9M+RqL6r4OWi0cH0kiub3kcM/xEgElgmEDp2eVTepkfBmcSeF +ZQt1sD5+9ho+GT61lJT0IuSRyhX1F7bUR1g3MNiu1g5j+vZUBjr2QaXYqyKNGHyZ +/SvRb91btzRkg3Sm8DgCyWavmm2GbUoWnnrghcu7HzHUn2om7UsT3yUmvL+17v4+ +WY45TM/iu4Jn7r+kQxBqx1hp8AEjEjQLHU1jw+J9ZPAGpTJiJLEUN3LByn3+urjt +Iivy1MlxAwuXCBeEs/UwGhG7fxmZJUFZ99eL8zoGb6kDDWvEyqdfmtvS3e5QJ+7X +MbCXO2yeLWPG+Ccy/YoZIXfTwlNEq4otwP+TqIg5HbPZPpP3m2FRQ3au5WJjrqID +AXLhgyAG+4QwMr3hx656n/bXNypNjNJQmQodGvSigy6wC2bRWnotD9MLkk0VHqDu +aIp95jADNchTMlafP/ZdGkU+uJT8v8bCFwFV+iHubpytnvaMj7VZ4U2E8tKnT02c +HnrzwSCU7TuldnMZ4C5UiFtaY7hB1a2XBR/fpJaeo9Veir1rHD62mf0C+jsxZ9Is +VCVv0JoJKj3W9Zgqc3YxPFYEPqAkgjQl22wlI24+9YW/t1icHAPyFPN/tUlCGyO1 +jJtvW9MIlGGnA2OClucA4/bThMj8DBn6a3Q236adimiyPamPN4HlyRHeaWNN4RBx +uysUP69E7doe72wEDLNCPLEPA8uKnhhWVJh1I0FzDmoOUp9VCC+qlozi+4KlVA7A +DW//4kOzOiIrnGBLJimQBhitU6lIarwWL7+ic3LTukFHcS8ChMeAt6yrfUnMSDNe +W9f1rOJOiSnV7XxOchc+dcwANPcyd78s0Upw6jLA/fOl4oX8AUE5/G2iplIlC6Si +3i/ES7MyxMdJPoJoxQWhs7xNo4P5XFhBT+uNK95B2Q/Hr9I3OcJ7Dnlr76aIRxhJ +59BZFCggooZAYaCpXWBH5AbGwLPTAFipHBhGKgkQDg+Wz9W6cxYL5v4sOUCE7p9n +EFFtM+vlYabKD11a51c1XkN7JwSTKmCK1HQ0cPw2jMXePWr9ldyGTk0euFVn97ZM +gD0uLqPn7uqw9ro8gztgTnmFl6Y3/gtlSVV5RnKw7UKTRZ3rfNycVd/vupj7JpW5 +dzkxF2nVrhELEa5eOJN9vck/KNfWmZO+ZzFVALZ+aFS5gxbNFWS+U6bB9C34oMIZ +6c+r5T/P4b2AnWSiJ8vVqRouWCdmEPdcX2j7I2onh7UJrQhWwxNgDZZKjQ5bCEiX +N2wSeh55smVy80ywGz67coYoF4Mt18o7cH/N6XLo1QMQfamElwPsaKEnCM+Ab3JM +GqnjdWavDJK80MxXtJNy+9ZUWl30eag967prvEQ7kCoXhiIrz20dmZMNMRLpgRS9 +CAtvSP/3K6O713qxqFgjA46P0ZCmlPvhVPYqjyVDmbYb61r0Io+4PPlUFlXBWTc1 +W6EV47SsGHqjWYBw/uh5omz6fMaPApOt5SzPb1wdFI8pDGUrjW5TLXwZ8ZKeEYY0 +4qlVG0H1dJUU/bnDMzIXuzB0CXnF92kiubtOBwFXqTDNxgZmQj3vf/qITJyqnLxF +03qtxEi7u8O+3ahI4OcrvmSsFwuTmMEYK0xySbKvkOr7mKRSMSR5MaGv2Q9EKBXD +bvnnMaAIC00i2vqfNhIIdOTnUweQj4CCawzL8bjiDSO4WH4HKV22RA+8SH4Aw5hw +4v/WHMw4ulacYvlEEicMcrmUGzcu3Hms46RF/Kz4d25qGv75LJhLKLX7dvXxBOt8 +zaehgPeJ5+Om3ckhnSfWOYyj2EAU7RfyLJat52xZcB4zPZzTxoFeCAIs3UjWId3g +PO+twFxtZ74Nd2l/x162Xjf9BhdIJT/5pf+I2vDi16r4t+7j4Jo+8+Zd/36QhXYv +7ilatnjbaWsAJQq8j3/P1uP4SSL3cXfU9rYwiNb7dN9+S9DpEklkBr7ZgbFFhHfA +OHpwbX/pWy65hHZ6OvZ8WCNtNcLIbwwJUbQAuzR8gNPBI2JlWlSwPvs1MRRcJgkP +-----END RSA PRIVATE KEY----- diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.crt b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.crt new file mode 100644 index 0000000..62c671f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.crt @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFZjCCA04CAQIwDQYJKoZIhvcNAQEFBQAwdTELMAkGA1UEBhMCRlIxDzANBgNV +BAgMBkZyYW5jZTEOMAwGA1UEBwwFUGFyaXMxFDASBgNVBAoMC1N3aWZ0bWFpbGVy +MQ4wDAYDVQQDDAVzbWltZTEfMB0GCSqGSIb3DQEJARYQaW5mb0BleGFtcGxlLmNv +bTAeFw0xMjA3MDUxMzQ4MDNaFw0xNDA3MDUxMzQ4MDNaMH0xCzAJBgNVBAYTAkZS +MQ8wDQYDVQQIDAZGcmFuY2UxDjAMBgNVBAcMBVBhcmlzMRQwEgYDVQQKDAtTd2lm +dG1haWxlcjEWMBQGA1UEAwwNc21pbWUtZW5jcnlwdDEfMB0GCSqGSIb3DQEJARYQ +aW5mb0BleGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +AMJ41eLHumb6qpE4tb6gOUWX2vxZciLAnn9P87VoYSTCTN9z/xSFAlf0wuBwarrt +6CR7WAMHq4JOMgQrZRPTw8tOeKjP42LrPh4xZcTKQof+L/xM0EoK64NbwHxjUE2N +qwNZJhCvWpjOYawHWGm+UFmNU31eC6MiPbALiBYjEhrQBdfJ/1/k9KWYkqe/doUc +XUgP4PoOFZy/RZ4O2s5EnJKYTgh5vL0T2XA6xHGpsnlRJLku0ll0uF8PhKsnB8qJ +uzWJbQjmve3G/QSZBm+/XJscSx37HiyzADD/aHZOFM83qUs7fV/GQiyCCPVzHbyI +QngunEwlWf1UcTfvxsfCqIVwxl3gLJvPRg1NIQFOMPf+1bB5ViBmn6DPPM89PhYF +vwpzimoXWLa7bIC8pA5i6cfmhtFn+w+8MbgoPnmtixmU1e5nqTguD8ChgIa0u+Z8 ++IHurVFaq6nC/3d658FmC2DKXoo4/lKyUV/U6leBLMo2kgDsnBpbt0FLZ7Q4Jc1w +XIunLedbXZ+LgCb1zKxmrqG4gBKgYHxd6HNWlqjL0PUy9XL+ZP1hqP39GvuQisL/ +BzwVhWXTHwxJ50gDkHVQhkohge9sBlSoV3MrMDRFDgvBaaLJEFd87cFjMY2XSSxn +3vBTK0TEjVSLw1WFS1xTXO55J2Un3nJ2iULl+QYIplz3AgMBAAEwDQYJKoZIhvcN +AQEFBQADggIBAEtVmMuuyaTnwGFca0HPTizzrsjAPUL7skgQ8iUTqDwJPmAmDqTM +ql9Iy/ZGc8IrYODi/Fl8t1LwAV9R1RU176jtQXGu6Jb3x/BBvCyw5ZhioLaDwx0w +jjifep2gQnYy8e9tm172WXStMYFe70oRY9tgTbqzQ7PzXadAcqtSShGePIoeLl/0 +NsO45IstaL0q9AlvyNrbn3JxYGTEGiQ3HmhEYFwJEfpULMmTUPa15lasZ8IkhQ65 +QzqKkmElIeNwzBgMRypKZ/IueKq7xCra9MoeVkShgmiw2xyv1kR50dDpxneBPg3E +vxczYq7tlF2sJC7PlIyfi9AJdfU1fl0lKav06ck0Ge3rEh2Sj+LJmKQ3DQggwJjy +E29L9C6LXAjs80Pe7plElDsYmFEwWy1aEhTTwlPp7J3PcDpsEmtycYDE6xCTzhda +eBoHkNmls+lNdLGYBGMf1VhZ5krbGDDXuPSBG+QFJgJS3LT5/sCShhQ5Nk82Vl7a +pdRA271mBrYHTjkQqcf6c0nb/ZLNfSDhi/vO+iQ6og5J95Muc8g3kK7r0j6Grx2h +skB9RnjC1TBtRVXoYc9fjKLLHuYCkNkeovtRax0qZaaY0NbwJQ5fIqvDaN9U6r5i +ftuEjVb2OXWob6ZQ0fsPBAnLGCT3FYU68sDLpmy+jms2hwxtWXnSyrBLMEowCgYI +KwYBBQUHAwSgFAYIKwYBBQUHAwIGCCsGAQUFBwMBDCZTd2lmdG1haWxlciBTL01J +TUUgRW5jcnlwdCBDZXJ0aWZpY2F0ZQ== +-----END CERTIFICATE----- diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.key b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.key new file mode 100644 index 0000000..c611651 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,7662C83C21081CC6 + +1j+olY4K5EcFUfTfheSMXTJF2aqw49syZ0ZjIKRtJle89Nx7raS1u2mHgAe4xxtq +CS+Hj2M86PqLP7J2gdVFwaMtnVnrqKiDYv34zwo8WPg4mz+IFYu96huswAA8UyCO +7M4IRWzPlb4U/bo2tEaEtfduenRBGNaORnFJy66+7tmmj7cYE1WUeHXfjHPnoTTG +670rWJIacJAZQDk2CiGM+Y2fpxgAZKKXFYIBj7qErIlLG7eVH5VqoABrIzOZjF+G +dvgVXrrKjjc3ZMV2f1wEYxUe2rTQQbQa1YFnN1aUBienRPdaXiyvCeUeIclKFS/o +nnE3WRc2XF52SeZeDSyEYFl0w0B1ccwtcTgaqTuTdXCgGP0vkOeg2fII9+EbxYx4 +xFiEMoXQnps24IzvcrQYsKkBEUhctwosmrDjSGTAtP2zMqLO8CJDQDDkMsMaSvCG +M0jMuTHz5gq50edKKlTvwThungqrvDKuCw5NXFR4Y4hFaN2oOStFji3P6fnLXi6/ +e61CEYxK5ClMVBzhKDfrUHSMLcS7C85rMDS4ua7k3E/QHFCI7Wspcl/54VZ1Ku3c +kYZfGjqnkqGPAnaBMDIZOboeNGqiSBqo9BsQH7sKnLGW/K1pHC8E33IxHT+KKWBW +A2AA18TL92/t1iHtUZzL7FBpBdd6cuotWVAJyghe7d8BcmZ/PrQn8uxbvIHLIXSo +hQSzjwvn8VQBqnkxB9IfJFJPXtoUkz1ViHA851F1KnnX1yCi+sc0WZrZPn2szH9h +W/CQnqJzwk8yn8WAJoj+CpFHLJ53nk3mjzJS90/7MRlNUF29sQl0Mm8ybCGncs0g +SJqoTMaWQoEzURnFRxWm57jppsxc/uKdxcfVE85qhd3IXGfIgY9DfIwkZfd9keVA +iP07Z7OCeVs+Q8MpD8UGunD7luZM0H1j1nqFOtlUKpiQGBnxySNmnktC40GZvSPP +V8e0K46sBLiS9E9fYKMJWxzieLkEoYnQrIH9Erz9nBFpG0k6uvd4B2kjkoG6JpLZ ++slJZUTmaV0Z3HGLd3myF+gb/mvFBQdSncDfyOD/hG0jiq2E/xRTb1ws369NNOTh +NvW+FNrevQtchAIQXrHIOIqmFM7i1LNLk9UNB/lXi+u+up18i4i+ad07/3iTON4I +v0RmYZOUuxpm+ReypOWB7mjSm+nk+WrCSCMVlK21m9VBGKa1z/Wr/sUIIJvCfmq6 +TAbJ7/+V9o2S7vbhGJ5zy5u6mM3Oi2wqnOl/MeKbfudkIXNcCFZQH4rXSAUX6ID0 +fkSloO4A1ZLym1OOAP+08ZHHj4r+qM2PnyYUmHZ2FExRjdtVm1W5N3zP+gsPNBZ2 +aK7XCsYFNb3hJw90INQfd9nj7dR0gmIkmE9tJ3kHXf+xwm2mOu+jt9ciCxrg575z +3mv5mbYdi5Vq3cn5uE80LNyFrmpLuz1SSOcznNQ4KX+BFV1qgoNyi1PwWGA7VH/9 +ApXIPR1M1ziZI8hY/yZmc7pdT+5JYv0mYZ4peOan6nBjYwE7/qR72H4ES6CIc0m/ +9ZW1cHLOigrJg+OKD7W0aoufzN+vdWfcyo2X7n7S/LyUSMtnHUtGhCR1IBv5w18q +B7u9Od6iiejrYNOnqc3no2UiZu9upePv+QtoJTIPCDD14U12NvOS4JApws1X5lH0 +iecKhyNZYz7chRCBXzCRwEHxHKc7GzKTsEbMkR+H/TCG6MYDF/yQj2nLpXBYraIK +ula/u7+yM7mHHUp09W8+tgk5cGRwjPqzeEuVVosfQg1UU+FnJw2wS4fGNCC1pzUT +HV3S79p0YX3/SrNtQ1wOfHsHBtzFtJqMDrneR4xAOxJot8wM7Mo0v6SRtz350egF +ZSYdymzn1Q8pw71voidFmbJ3VzULSyR9vak3K2mozdG/kT5tQ8AhWP+j66UmVCu8 +87rv02wmITi2WwjBtnNq8pwjSrtajezpSvt4S072rXE1iOwqodXL19Z7tS/fDTOJ +7Sf7WFwksVjhkIM+Lqrgz7G30CO0+OFE+gAC+Q3SbUkzo4vPrXzxqXuB2gLlEIsn +4HWhYHPMuJMXpgy/5fH1sS5CXvl3x4hoETaJOARmdje2pmPqGUxpq9mMnbQfKqM+ +QzLXW1cq7mIz8s4lF4SKgP0wpKo8tNhlz51SbYD664IRYGqAP36Y/XcMFWfTh1IX +ZOZVJFPJthGLCsatxu+OTR6xvx9/Sc3OcPh15wsy4BssRcTZumBzGOI9JuGtVUM/ +X44yWdFki3Nu5v9Ag2Qj6CHg5bwDRpQboabjHvSgopDHM/doRdVPytGSAIbZG8DI +dqe9HN94w8K1EDtCARNVCrHGOWu+hmta86+I11w1lx1mdfjWLZE/uJyeQFrTiI6W +IaoBf7m05XB8HaHxLnr+zELck2GXsyZGKOGdKQZ6kbt3IIE+hezpUaGMsHx7yLI1 +ZDZpYlEUVDZq41OxT6d8RkgesDCc/NEZMbpzqXrkwGOiLpYYKeYHx+yIhxrInE48 +Kv9AGAEiFxK1EeqoY9GLGbj7S3biKtLreT9jtczOP/k2XMujR0WaqPesyz0XYA1d +XvstAHDi8z7heuMhOHo3rJ36aF1D5rQwlT/C+zR5TuEZZdJo4UJo3Jtk6dbvluN0 +IpAOQyewnWH+UvIzjnoABKwRXVyjs8ypuNWpyUfVZoXt86gmgIUxjx8U8UeFdyt7 +qsk2eCsW8VyiecMk25MZ0ooirItlQ0KawfhHK0KbIqa4DSyecR+cSjQ7PpuOFqSK +2vedOd3qZuY+4HDDa5KWXrzt0jiP+Y4wEHQBxlk1rJJ+8UgysI5lUc4i+cBPugP+ +PYs4jknYpzwzlRMqF6RJeMytVHAdfzuIYGx0/39hY2mfSBHDd6Fi1yupxB0Hm697 +9VKwLoo3GiO/AkHGsTeylXAOWL5eDa8EBO8Lgj8nHTgsh7mM8UWFNMxO1F35OyRo +zT/GDus51Noe+KTnvVctlZF62mYlBpniQ0rZf3nNMDyAglZV1HsvtMGtqsTdwoqG +Wvo5GYqsrrttcIg0Ad7aw+ouk+fC/sH0rN6yMiCLoopf/uBmNNMFZQtU43FFssWl +eea1ZPaN5HcM7E35nZlTp9rFCPzfAYQ1BogDIRZSVOqLWqHudgjNP10HyeCHe6BS +-----END RSA PRIVATE KEY----- diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.p12 b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.p12 new file mode 100644 index 0000000..c00d300 Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.p12 differ diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.crt b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.crt new file mode 100644 index 0000000..f1dffd3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.crt @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +MIIFXzCCA0cCAQEwDQYJKoZIhvcNAQEFBQAwdTELMAkGA1UEBhMCRlIxDzANBgNV +BAgMBkZyYW5jZTEOMAwGA1UEBwwFUGFyaXMxFDASBgNVBAoMC1N3aWZ0bWFpbGVy +MQ4wDAYDVQQDDAVzbWltZTEfMB0GCSqGSIb3DQEJARYQaW5mb0BleGFtcGxlLmNv +bTAeFw0xMjA4MDgwODQ3NTVaFw0xMzA4MDgwODQ3NTVaMHYxCzAJBgNVBAYTAkZS +MQ8wDQYDVQQIDAZGcmFuY2UxDjAMBgNVBAcMBVBhcmlzMRQwEgYDVQQKDAtTd2lm +dG1haWxlcjEPMA0GA1UEAwwGc21pbWUyMR8wHQYJKoZIhvcNAQkBFhBpbmZvQGV4 +YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqOagZ7Ui +QNNtdSWj29NZIKnTt7IruP7EqEiPYgGQDk2tP1f8u1QS6JrLr9GEvLuT7jDT6q5d +tqFAomDbZR9kjv9V8Kq28k5JQ1SJYDxt7sIgRum30woUy7ua3dgB8v9LheNvoPzU +ytuJf9X9t33y/U75dxnjPOkD/7HwnihIPLJcf2N5Pyt8ggcc5tmMhqelzf2gy5be +xcdWuIPgsm+0KzdD9SRu1fB3QwTmWRIAxxuifS0732djBfLCu/DyVVqWVczRppEX +nu/euEoxzKXgAvJOYUNSbCi2SZO60v+gW8WWYoPvVCa8pClP181YkqQCP6BAxl/X +OS7nNt+vHclzJj6000clndy2bUa25wb4w49WpAusQUcb4j6PQFz+qzumTQmH16AE +GrOqwQKDNmkulRXXfDpV9PIwZpE3VPIlH7dPoeaiJAok5IghBpzy6J3VbXqjo7fe +3+Kgg2LjOt5tMAN0xCn4/nevN0/JByQcQj/NG8ZVt9Cyxfh0GD7Vicu3ym7OlSYb +313HWU1sOy2sIECgS7zkWe8Wy2l85RbFB1bFyBYmCf2eskEW0EyVc/2/DewtozFl +wPj6LC9CITLUydNTrosXtpyyeEETWbsNsA9gTzs6XI+BxLUXuoNmDKFsAisKDdrG +V5FUUt+FCuXiPzIJmOM2Vb95fOzjjqjCAjcCAwEAATANBgkqhkiG9w0BAQUFAAOC +AgEAQHHZk8zKAjDhA55bVs4MgPFlN06glTrURypSwpf2bXlnqZZnVjpLvY2Qz+Wb +N2bwXx2FhV5ZlTjYCzDBra8V/Yni5EauobB5NOzzrzZrlWaHFbkyNpoz/JDaV8Mg +twCtS0aftMhgEM/F+HB7SnSyAvLcjcqoEMx2RrMm63wdPM+LbX9Fkt4m4+FWWdP7 +QJO2eV1BjkNsoMClNp2JDcyWmY6loCJYqH9NqRF2zVpw81T+iYsClas5yEBZNRg5 +eHInRX52UtepD2KUqG7evX3Uh2NEsey17k2um4uGX4DE/rJHbsfLqENMfl9nynuL +bBpdnRzUlnk/ZpD4qvPH/VwpwjTyOXNetenqWaK4VzQLP/psA4B4VSl/8gVsi3PX +C91lvlw+a2t9Z4FqYzJS0ncEVH1Ulxr3S9Dm6LSVysOBd6BLP0urcWNR2edaUb0o +xwowCZS5MhsvzwEBCbxZ67pHXLA3O6uGhuF7Vq6HyVX91tRWbjERfukjgiha90zN +XGeEmhqi4JfDN2upFcXgKwlDrevAuLFD3RJs6DbGe0L8pp1sKB+F+9lWT9HgDYEy +6R55/KjN9o4P6zvW6EpbBIGGcxk0Hak84JP016WnSqk64xHnVydLDumvC6oss4Ry +C5xUujR+QuoZRSuc1lF50iptv1j1GXeow+UHstv+IznS7WgwPTAKBggrBgEFBQcD +BKAUBggrBgEFBQcDAgYIKwYBBQUHAwEMGVNtaW1lMiBFLU1haWwgQ2VydGlmaWNh +dGU= +-----END CERTIFICATE----- diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.key b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.key new file mode 100644 index 0000000..14e1412 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,AD54708BA16E6007 + +xWTYXMib1ULkXl0/KynSJ5P4qvcbYZvyclRoP0ppD+egj0WFi0HMRtQlfo2asIBf +DHk8myDsUp73mV6SpYmZPwzGqIJkoA3tZvyKWfb+5OG2x+KpLwzGtCcEoA+Z1TMm +/BCRvQwEbcYgKPoEBXLnFgK/mWBEPm3VtyFHJ11C5zzKCRmjlQBJSAWzJ8UFPkX0 +Dt/w5yBIKQL8dhdxCdWirqpx33wghltu6AMgWcGlsHXl/PExXw9ir3/ddyi9MRCW +/x9XXhqr+K1BSJKW8LIRj0O4ipHQ8mRvvUfCX87nn4Sp39yslyokJhhvttrjCg30 +YlyVeQZi3BgZCS5QG6F3VR9KAGIpw37dguEO4/LLhDYDVZxTfIvuCssY6mDfiGB0 +M/qeZq6OfGKOzuLtMXiCMtGacdc8mzEvLI2x5XmoTaNI7SRh1c2VlUdydvgrm+Fu +9EUt7zqkBlwCO9RAGHCjK21Mh9Zf7q/jzQLTDLXL5rURfga5Jx3XhRUpyb1VOV62 +75gaYU/rUpcFnsWiJxyWwPHV++AgXHzC/EoOeOBsRtVVZprhXR40sv/TjJxc3WTj +LnMmt9u44pVPmv1R/++sd834PgPvuSEKL1dPRb4zDsjEZhtV5ispZAXC3ljYv3eS +iwgJm3WLh1AmRBnoi7T+A6pG9cLbHX6hasPBAD3Wn/w/lGOFY9bt2v00dU3z5F1x +NWgefx0CX+evxfuS48yBG4XntL99DX+BVIsuqyQc1L4/pPWsxJ/Ihbgoi6NAz3BZ +I5M87mcy3rIBru7/bmMssV1hZ5ZS+vKVjuDHfT3oJAJzoHf9m82+4S+oS9+FUieR +ARj74aAmT+6PbPN4gxFsXv4cwOJbCZNuIyNdaAul2+X+k2zgwVbjOWoH6ge6iZXb +LFFU0cVUVKPtT4P8JiE/i8K7VH5mAf/CTmXqG57UD/YU175Q+U15ZKnZ70EMFoGy +clNtewyZZGBIIEMuy4pCDg4FQpk6lpCp0VTF5idDOIAyHCyzsYB3IQIbCathZ0XO +VtKFTcJqLGxPGVD9ElX4X21oXxNBQc7UiLGm/ESFRiUZR4mBGh8ZKpm3x5A+iNfy +bypwXCkmeehykXMglvbiQ1ROUGujQmFDGBe76V/eUhXGV99qAs8FtWzmqoW8iocR +jRH3hwSl4UL1N4dLn1Csb3sFyazBWmVOetvUab4LVY1yezESRCPaIngyLSL5dwRr +u8yhYF1exXBrk9a2nTeSfRn16IAGxOOr2fT0UmW5+Q11xBZzNlDBICErBjKe0T0P +UdQqFjX+kzekRThyJr+5Hcwr6dMiR/t/Um2m0jb1ejhW7Pq+G7hxN4N45zBvSZHN +R81454iHIBp9vtlHN3/hBhJg1HHmsxihqiiRzRpdMpw5mWJNI/+/w27RzhJUVgwx +qlym4Q38wCUoIUj3aqdGCgNqSuzYCRUSg2d87OBWqKLHFiNptzb7vzuwv0F4xFHK +aXB5N2ER5miY4GblWmxhBjPjc6gBwtfBe1ClMTZcABOD1Mhs7kKQi3U00qndykIW +GR0FDvHIMBTWQJFNTu9a+gd+B1cTqjscvjH9sVA46yuSjftBbqxc/GVuXWpAU7Gl +rFRiuf9yL9uWXzwsZTXWuPjYQxxEshJJ9fdCpuBnNODKppMUDIs0wgvXZ7gWmy1U +zkX+6Vwzs1hZUQqQOKk2XQgWIQR+dBrsC611pU1VDCr0f2uTLqFWmkcNSnyqJyOI +bNpbihkim+PJxhHf9T4rZthNGpynLQTXrsADndMaFM/CxBfoAHKvrS3B0DdYHA0e +J36sbYpjtd5SXiUVtmplkFXUC1vkq2fq9X7TbGbUOCZ18/O6SYxrZp6YXuTYK4lo +nqSqhgKohFo40I7mTivGU3P3WMeU6LVC8zj3UjVUaYh8EbhWvFUjU1rROl4ecTmE +H0WDEhXp7+0K9gEz689Ui3nTz/CJ7V9lbU4eZby1o8S9AKrAL7fZxiQz98Cgf0Qp +Q7WQXgyUzg1tyYVHq8XLkyggs/YNCzpA3fnxmdxQeNvYu1z+dIHLTesgbbxSXpvv +BJSTWJOT0uNL1aBrtlrSnFbOQBdYK1J7W6GRaMZFyqTFIjNqXP/6pl0GBVT8r6kk +5ksgcQ/0ZJeNXUhEnGQvhrKKPJqhYt8zM3iuL6oRyaxuMmR6q9+p6ulTONXcLBl6 +KnjkL2IKfEOebBmXtIbVaSgVxnl/5IwF7C0MN39cpfwQifElE/7zREXpgvccl1Av +59nRewkVcfDJUO7Fg3xZO3SpngifUAPiABhS5cQOfTp6M9LFAgbYHolGMJ6WXSWi +TI/8luBgUawGOhN+nTl9akzVw5FmaixLNWU6ahS5RE3I1ENXBVVO3RF+qhN4Lnbv +BtyXrorIBNvrNsnP4po3+nFLYGpoiUDA/h/nhuwBDG27bRxoesZCYSOOTKo4CDaO +0zmqsFKCWBoZwWDS05VVUHgWz3s8RLrzIQGDxQhkO3EjJ+FQxdizgnDC1GTCx3CT +510iKlXRqk1zqWJbmAPUevI6hicFwzVKWQsncrNATupG0WC8eDyCgBgbpdAni473 +F5wR1m8+pQ6fqpRVeVAE2hy4l+UDaAjNldgW06KAC4onqtRbdKek8AXFCC0wPOPG +NE3UmiT4auUkeKrAK9TKEehzSBXzVnAhthowkydApiUDJDcqxHhp/xfXTLS72l9L +MFEvDMZDHdyHYrD2tBVsSGiuqgBCxeCdbczJg9ocSRfVqutJAFzxs8Wo6aJJzXxg +azaWM4u1aPld9rtM65d3Z/UpS96qEuHlcPDhAOpg6wZfaNY+EeilMlrQU2ITy3m7 +qC1kzpu+fqYOBPBoLKIeXfCr+3ScCDn4XasZYE+YPoWmm3WELCPhCDDxea+qmh2A +/z5gUzZAdyt30NbKPyRRgyQRHvnJgLdQZW/jEcXq3wXBUsL+vzK8figqcbQR/Mew +xKH9vsIzojDXHd90isfFCreu6+xqBQib3chujMc7/jBlqZ3JUmtI7cWM9AamwujQ +zEVuKfQyrGqaJNy8w7yaRW45ffuJAqffpOjNQWIr2RMsKw2NetAIw8mMgp0CWb3p +6mFHZerpiEe7CPhSUj2bSATOavVDOxvHyg7AY9zytBuumMdBxi49fP2igCF7frBV +-----END RSA PRIVATE KEY----- diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.crt b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.crt new file mode 100644 index 0000000..cae05a0 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.crt @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIEYzCCAksCAQEwDQYJKoZIhvcNAQEFBQAwdTELMAkGA1UEBhMCRlIxDzANBgNV +BAgMBkZyYW5jZTEOMAwGA1UEBwwFUGFyaXMxFDASBgNVBAoMC1N3aWZ0bWFpbGVy +MQ4wDAYDVQQDDAVzbWltZTEfMB0GCSqGSIb3DQEJARYQaW5mb0BleGFtcGxlLmNv +bTAeFw0xMjA3MDUxMzEyNDhaFw0xNDA3MDUxMzEyNDhaMHoxCzAJBgNVBAYTAkZS +MQ8wDQYDVQQIDAZGcmFuY2UxDjAMBgNVBAcMBVBhcmlzMRQwEgYDVQQKDAtTd2lm +dG1haWxlcjETMBEGA1UEAwwKc21pbWUtc2lnbjEfMB0GCSqGSIb3DQEJARYQaW5m +b0BleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJzR +CMJ7ZqrqgCOUPcIAQHD/NStY4QOaU3P8/OOke9/ZgM4qKxXAKcWiqh0rOZWXTyFp +sIEVQJocsGxgsC967cmUcHn6o1cE6kAxL8PKwzQ9v4wqWB6eak1RAZSjjdNTkePi +mZSH/1I9DFp2wO94+paZtv2pgYh/Iu4Nrm7NuRypXNgMP2RuMaUgW6ayjP2lizXG +8Zc3cfTOHMmoBaXzmf6cRemB01vVkshKZoyVlZ7catn5fX198TozDgWV3JY1pVIt +fVP1N6yJLIfQVEeXCJsL+/ROdxCgArhpKazVL2FdsM4va6Wk6yqpu11g8bG7QWBq +v+PSkU6FEqj3AdDRrV8CAwEAATANBgkqhkiG9w0BAQUFAAOCAgEARdAkfO/jQ8Tq +XNn9vS6TVcqlv/hiW234wwKaGXWw7y0ToSVxzHJB8++5g762jsUDOnzd+hVI2bxS +3y7oaVR7ZLoHyCYLFTQa2cVU30YjZUKPFi5rffy6+71uZNW8OO0/5QQ0JcoXPjw4 +PoEGurhaku9nmxbhYnOtRFWjS+npq9XPQpn2oZa43n80nAfnR09RwyqKltRcbIP1 +CXcQ+7jVRUVhAMOr+OEQT2yK1HJ118Bu2I++Ep7FjBIo41b+70aKjFxZO8NUd8rV +meDb9LC4kF58qn4sH4kQIFJFRx25hEbSZ8D6UTgKDp6jrdH/z0xYLi0GGGMGvkoK +ADFjWbEZaLr5zKJEE3a5XZkjMaxDXMYkIZ3CIwdC3rpsuo2F931YalB0TRQnStxw +10H9cWpQk1386850ROMkZ4EcNlTGiHzyBZOYyGXf1zSyW8KoOyHvzss6D+Hwl/nu +bHm8ZZ4hJ8kyMgN+klR0yaJ5Ao8ED9UkPZ2vLlpnIblSHjO/8sTmqAsfx6D3z7kM +qN9JWwkXLVXasXfySeFb1rqbini59keKqmSnSXkjqCwgtzKm4iysVPjfqOaz31W/ +yDT76ANGO0z1Ko+7O7t+LydJMYVsbnHKuupiQSiludsr8KNTEWZ29PPRaQZjgVur +5hT7lvRoIK/KxLM9E1nUJBNi58MRqrYwSTAKBggrBgEFBQcDBKAUBggrBgEFBQcD +AgYIKwYBBQUHAwEMJVN3aWZ0bWFpbGVyIFMvTUlNRSBTaWduZXIgQ2VydGlmaWNh +dGU= +-----END CERTIFICATE----- diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.csr b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.csr new file mode 100644 index 0000000..3299a1a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICvzCCAacCAQAwejELMAkGA1UEBhMCRlIxDzANBgNVBAgMBkZyYW5jZTEOMAwG +A1UEBwwFUGFyaXMxFDASBgNVBAoMC1N3aWZ0bWFpbGVyMRMwEQYDVQQDDApzbWlt +ZS1zaWduMR8wHQYJKoZIhvcNAQkBFhBpbmZvQGV4YW1wbGUuY29tMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnNEIwntmquqAI5Q9wgBAcP81K1jhA5pT +c/z846R739mAziorFcApxaKqHSs5lZdPIWmwgRVAmhywbGCwL3rtyZRwefqjVwTq +QDEvw8rDND2/jCpYHp5qTVEBlKON01OR4+KZlIf/Uj0MWnbA73j6lpm2/amBiH8i +7g2ubs25HKlc2Aw/ZG4xpSBbprKM/aWLNcbxlzdx9M4cyagFpfOZ/pxF6YHTW9WS +yEpmjJWVntxq2fl9fX3xOjMOBZXcljWlUi19U/U3rIksh9BUR5cImwv79E53EKAC +uGkprNUvYV2wzi9rpaTrKqm7XWDxsbtBYGq/49KRToUSqPcB0NGtXwIDAQABoAAw +DQYJKoZIhvcNAQEFBQADggEBAFnUGItXD/3Cgc2cLCVcjlMAiiPT4wpRFAWmb3kA +hiuT5fdv5zM5vmnG5Kgfa5TY14BOXUqYP+ebKdzk6CcsX5Rl0yrYDaTiDt2YJYBJ +05puOqDVLQ8rcc6oykil4/1LG2FrrBQwMBd2Y8PD7ck+XMPMDCk5oYEe1jvgSeLZ +rHW+lcI002M1L84tLe81d1+2I3IUwN1sL5W8APRmXhtokMzEyP8CvfvpLzY27C6M +baaPwTnV4RcyDf9l4hudzTxtoFi2PEYc8MAgAXhwyTf8fj2iV6y5iZUjlMt+BLRk +icGQkpYoz24m8hkk2FOUgJDXcx6kF6CLjkAPoHeldlt/XaE= +-----END CERTIFICATE REQUEST----- diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.key b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.key new file mode 100644 index 0000000..78954d3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAnNEIwntmquqAI5Q9wgBAcP81K1jhA5pTc/z846R739mAzior +FcApxaKqHSs5lZdPIWmwgRVAmhywbGCwL3rtyZRwefqjVwTqQDEvw8rDND2/jCpY +Hp5qTVEBlKON01OR4+KZlIf/Uj0MWnbA73j6lpm2/amBiH8i7g2ubs25HKlc2Aw/ +ZG4xpSBbprKM/aWLNcbxlzdx9M4cyagFpfOZ/pxF6YHTW9WSyEpmjJWVntxq2fl9 +fX3xOjMOBZXcljWlUi19U/U3rIksh9BUR5cImwv79E53EKACuGkprNUvYV2wzi9r +paTrKqm7XWDxsbtBYGq/49KRToUSqPcB0NGtXwIDAQABAoIBACZzw1gc9jHWypDu +2q3lbefHTHxSkOpXPztjv4b6YD2lkcX75RCyTAvgNSJt5PLfZVHmHpeoZGzluAT7 +OHiMBh61vVbLtJ2z4NTYusOhxnfa5Es0XuMzKRHIWUDfUfmNtmpQuCQJrPu3aH4O +PIT7/0ZKbuqIU/Dqa+A/jpQw8cErDABrOviwsUgMjBlfE/ogjfQ99Glv27JWPoy0 +jXe5SWleCjPSQE3sKPxxFSKoMnlpf7P0cKPHVq2O53S3q5HWI4K+6QCcGMf19iR9 +xj2igdt2S5cgW3DSypLxGvIKBS7IDw2pm4bhSyTI1RAjyq+3GTlrsaDnkkbYYkpT +kZhDlKECgYEAzpjl7Tq8DjMkKI42wbNxlEIufN81M5peEwwha3qfD/S2Lxsfepqe +D8n+y75Nb9b9xwZaCMdfj3U7qEuBpd+R3MzquONxcTjJASI+zveGGVwciWfrL2fg +YLhia09rnoL0maZe/TrYunIHrRMKXXl0/qE+ZVSJBbaX+eX7w/Er/OkCgYEAwlC/ +vU+1/nmiaf4l4FzE7dvMD0jHcVO/c9IvjmiFvuq3Su6vAFa7NX+uH9wetfTLJ8Un +58nXOtpydXCvoA1uxxKeoCBFsEa4RDV9+oFoBId6wdOpLE3Qj3Yjg/pfzA2TiArH +VQQ5qboA4BvfLyJLR5z44dSQo6xm6eIksY/pywcCgYEAiA9INdEdsphZst/eDfl4 +zrQ7W3/A0OhUFmVByT/ra521MhbBx4P2nt1gEZyV8Rb88UsslmV/eMIzdOWtT6mK +rYjC+NrJhlBoiHeeN73qMd9o59FpoUHfErO8FYIrlSwXnO1l144e18NrbL+Cbwcj +VuKobiGbUAjt0+VzxpqZZqkCgYAeYQ15BWtIm6+erpMxXo+TOFfQ0+oKd7No4iV5 +ODIgxQVe+630eZrPJGUOqhVLEThpwVMZ096IXvqiDboHZdI54VlXY3RBuOy6DTri +3TRr8NpqTzMVsBy5MRkgWxZ7CmxUSosbaYuyU1grsW/VKKFoUsw+UkNCs4NrcWWX +NQ6yJQKBgC+AhhVFANliX7wJI9bz85q4HD3txDrfH3g9bJRBVTq+5CXDrSQSXaKR +bcEInr/b28NyVLBSxfYSc2EUoA+s9/2FZulYaTgmQ80PQCZQBiHmZGbKblFn9h4r +ENGPvkyCZXkOUsDBYPuSFAlpGCYFIh7xYQkEU8YA39yfM11H235C +-----END RSA PRIVATE KEY----- diff --git a/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.p12 b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.p12 new file mode 100644 index 0000000..c676fea Binary files /dev/null and b/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.p12 differ diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance.conf.php.default b/vendor/swiftmailer/swiftmailer/tests/acceptance.conf.php.default new file mode 100644 index 0000000..99f356a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance.conf.php.default @@ -0,0 +1,44 @@ +skipUnless( + SWIFT_TMP_DIR, 'Cannot run test without a writable directory to use (' . + 'define SWIFT_TMP_DIR in tests/config.php if you wish to run this test)' + ); + } + + public function setUp() + { + $this->_tmpDir = SWIFT_TMP_DIR; + $this->_testFile = $this->_tmpDir . '/swift-test-file' . __CLASS__; + file_put_contents($this->_testFile, 'abcdefghijklm'); + } + + public function tearDown() + { + unlink($this->_testFile); + } + + public function testFileDataCanBeRead() + { + $file = $this->_createFileStream($this->_testFile); + $str = ''; + while (false !== $bytes = $file->read(8192)) { + $str .= $bytes; + } + $this->assertEqual('abcdefghijklm', $str); + } + + public function testFileDataCanBeReadSequentially() + { + $file = $this->_createFileStream($this->_testFile); + $this->assertEqual('abcde', $file->read(5)); + $this->assertEqual('fghijklm', $file->read(8)); + $this->assertFalse($file->read(1)); + } + + public function testFilenameIsReturned() + { + $file = $this->_createFileStream($this->_testFile); + $this->assertEqual($this->_testFile, $file->getPath()); + } + + public function testFileCanBeWrittenTo() + { + $file = $this->_createFileStream( + $this->_testFile, true + ); + $file->write('foobar'); + $this->assertEqual('foobar', $file->read(8192)); + } + + public function testReadingFromThenWritingToFile() + { + $file = $this->_createFileStream( + $this->_testFile, true + ); + $file->write('foobar'); + $this->assertEqual('foobar', $file->read(8192)); + $file->write('zipbutton'); + $this->assertEqual('zipbutton', $file->read(8192)); + } + + public function testWritingToFileWithCanonicalization() + { + $file = $this->_createFileStream( + $this->_testFile, true + ); + $file->addFilter($this->_createFilter(array("\r\n", "\r"), "\n"), 'allToLF'); + $file->write("foo\r\nbar\r"); + $file->write("\nzip\r\ntest\r"); + $file->flushBuffers(); + $this->assertEqual("foo\nbar\nzip\ntest\n", file_get_contents($this->_testFile)); + } + + public function testBindingOtherStreamsMirrorsWriteOperations() + { + $file = $this->_createFileStream( + $this->_testFile, true + ); + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $this->_checking(Expectations::create() + -> one($is1)->write('x') + -> one($is2)->write('x') + -> one($is1)->write('y') + -> one($is2)->write('y') + ); + + $file->bind($is1); + $file->bind($is2); + + $file->write('x'); + $file->write('y'); + } + + public function testBindingOtherStreamsMirrorsFlushOperations() + { + $file = $this->_createFileStream( + $this->_testFile, true + ); + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $this->_checking(Expectations::create() + -> one($is1)->flushBuffers() + -> one($is2)->flushBuffers() + ); + + $file->bind($is1); + $file->bind($is2); + + $file->flushBuffers(); + } + + public function testUnbindingStreamPreventsFurtherWrites() + { + $file = $this->_createFileStream( + $this->_testFile, true + ); + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $this->_checking(Expectations::create() + -> one($is1)->write('x') + -> one($is2)->write('x') + -> one($is1)->write('y') + ); + + $file->bind($is1); + $file->bind($is2); + + $file->write('x'); + + $file->unbind($is2); + + $file->write('y'); + } + + // -- Creation methods + + private function _createFilter($search, $replace) + { + return new Swift_StreamFilters_StringReplacementFilter($search, $replace); + } + + private function _createMockInputStream() + { + return $this->_mock('Swift_InputByteStream'); + } + + private function _createFileStream($file, $writable = false) + { + return new Swift_ByteStream_FileByteStream($file, $writable); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/CharacterReaderFactory/SimpleCharacterReaderFactoryAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/CharacterReaderFactory/SimpleCharacterReaderFactoryAcceptanceTest.php new file mode 100644 index 0000000..c638337 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/CharacterReaderFactory/SimpleCharacterReaderFactoryAcceptanceTest.php @@ -0,0 +1,183 @@ +_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + } + + public function testCreatingUtf8Reader() + { + foreach (array('utf8', 'utf-8', 'UTF-8', 'UTF8') as $utf8) { + $reader = $this->_factory->getReaderFor($utf8); + $this->assertIsA($reader, $this->_prefix . 'Utf8Reader'); + } + } + + public function testCreatingIso8859XReaders() + { + $charsets = array(); + foreach (range(1, 16) as $number) { + foreach (array('iso', 'iec') as $body) { + $charsets[] = $body . '-8859-' . $number; + $charsets[] = $body . '8859-' . $number; + $charsets[] = strtoupper($body) . '-8859-' . $number; + $charsets[] = strtoupper($body) . '8859-' . $number; + } + } + + foreach ($charsets as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingWindows125XReaders() + { + $charsets = array(); + foreach (range(0, 8) as $number) { + $charsets[] = 'windows-125' . $number; + $charsets[] = 'windows125' . $number; + $charsets[] = 'WINDOWS-125' . $number; + $charsets[] = 'WINDOWS125' . $number; + } + + foreach ($charsets as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingCodePageReaders() + { + $charsets = array(); + foreach (range(0, 8) as $number) { + $charsets[] = 'cp-125' . $number; + $charsets[] = 'cp125' . $number; + $charsets[] = 'CP-125' . $number; + $charsets[] = 'CP125' . $number; + } + + foreach (array(437, 737, 850, 855, 857, 858, 860, + 861, 863, 865, 866, 869) as $number) + { + $charsets[] = 'cp-' . $number; + $charsets[] = 'cp' . $number; + $charsets[] = 'CP-' . $number; + $charsets[] = 'CP' . $number; + } + + foreach ($charsets as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingAnsiReader() + { + foreach (array('ansi', 'ANSI') as $ansi) { + $reader = $this->_factory->getReaderFor($ansi); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingMacintoshReader() + { + foreach (array('macintosh', 'MACINTOSH') as $mac) { + $reader = $this->_factory->getReaderFor($mac); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingKOIReaders() + { + $charsets = array(); + foreach (array('7', '8-r', '8-u', '8u', '8r') as $end) { + $charsets[] = 'koi-' . $end; + $charsets[] = 'koi' . $end; + $charsets[] = 'KOI-' . $end; + $charsets[] = 'KOI' . $end; + } + + foreach ($charsets as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingIsciiReaders() + { + foreach (array('iscii', 'ISCII', 'viscii', 'VISCII') as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingMIKReader() + { + foreach (array('mik', 'MIK') as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingCorkReader() + { + foreach (array('cork', 'CORK', 't1', 'T1') as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingUcs2Reader() + { + foreach (array('ucs-2', 'UCS-2', 'ucs2', 'UCS2') as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(2, $reader->getInitialByteSize()); + } + } + + public function testCreatingUtf16Reader() + { + foreach (array('utf-16', 'UTF-16', 'utf16', 'UTF16') as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(2, $reader->getInitialByteSize()); + } + } + + public function testCreatingUcs4Reader() + { + foreach (array('ucs-4', 'UCS-4', 'ucs4', 'UCS4') as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(4, $reader->getInitialByteSize()); + } + } + + public function testCreatingUtf32Reader() + { + foreach (array('utf-32', 'UTF-32', 'utf32', 'UTF32') as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertIsA($reader, $this->_prefix . 'GenericFixedWidthReader'); + $this->assertEqual(4, $reader->getInitialByteSize()); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/DependencyContainerAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/DependencyContainerAcceptanceTest.php new file mode 100644 index 0000000..8efa4fd --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/DependencyContainerAcceptanceTest.php @@ -0,0 +1,22 @@ +listItems() as $itemName) { + try { + $di->lookup($itemName); + } catch (Swift_DependencyException $e) { + $this->fail($e->getMessage()); + } + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EmbeddedFileAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EmbeddedFileAcceptanceTest.php new file mode 100644 index 0000000..44a8e48 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EmbeddedFileAcceptanceTest.php @@ -0,0 +1,13 @@ +_samplesDir = realpath(dirname(__FILE__) . '/../../../_samples/charsets'); + $this->_encoder = new Swift_Encoder_Base64Encoder(); + } + + public function testEncodingAndDecodingSamples() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) { + if (substr($encodingDir, 0, 1) == '.') { + continue; + } + + $sampleDir = $this->_samplesDir . '/' . $encodingDir; + + if (is_dir($sampleDir)) { + + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) { + if (substr($sampleFile, 0, 1) == '.') { + continue; + } + + $text = file_get_contents($sampleDir . '/' . $sampleFile); + $encodedText = $this->_encoder->encodeString($text); + + $this->assertEqual( + base64_decode($encodedText), $text, + '%s: Encoded string should decode back to original string for sample ' . + $sampleDir . '/' . $sampleFile + ); + } + closedir($fileFp); + } + + } + closedir($sampleFp); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/QpEncoderAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/QpEncoderAcceptanceTest.php new file mode 100644 index 0000000..4406e1d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/QpEncoderAcceptanceTest.php @@ -0,0 +1,58 @@ +_samplesDir = realpath(dirname(__FILE__) . '/../../../_samples/charsets'); + $this->_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + } + + public function testEncodingAndDecodingSamples() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) { + if (substr($encodingDir, 0, 1) == '.') { + continue; + } + + $encoding = $encodingDir; + $charStream = new Swift_CharacterStream_ArrayCharacterStream( + $this->_factory, $encoding); + $encoder = new Swift_Encoder_QpEncoder($charStream); + + $sampleDir = $this->_samplesDir . '/' . $encodingDir; + + if (is_dir($sampleDir)) { + + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) { + if (substr($sampleFile, 0, 1) == '.') { + continue; + } + + $text = file_get_contents($sampleDir . '/' . $sampleFile); + $encodedText = $encoder->encodeString($text); + + $this->assertEqual( + quoted_printable_decode($encodedText), $text, + '%s: Encoded string should decode back to original string for sample ' . + $sampleDir . '/' . $sampleFile + ); + } + closedir($fileFp); + } + + } + closedir($sampleFp); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/Rfc2231EncoderAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/Rfc2231EncoderAcceptanceTest.php new file mode 100644 index 0000000..8c472b1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/Rfc2231EncoderAcceptanceTest.php @@ -0,0 +1,56 @@ +_samplesDir = realpath(dirname(__FILE__) . '/../../../_samples/charsets'); + $this->_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + } + + public function testEncodingAndDecodingSamples() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) { + if (substr($encodingDir, 0, 1) == '.') { + continue; + } + + $encoding = $encodingDir; + $charStream = new Swift_CharacterStream_ArrayCharacterStream( + $this->_factory, $encoding); + $encoder = new Swift_Encoder_Rfc2231Encoder($charStream); + + $sampleDir = $this->_samplesDir . '/' . $encodingDir; + + if (is_dir($sampleDir)) { + + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) { + if (substr($sampleFile, 0, 1) == '.') { + continue; + } + + $text = file_get_contents($sampleDir . '/' . $sampleFile); + $encodedText = $encoder->encodeString($text); + + $this->assertEqual( + urldecode(implode('', explode("\r\n", $encodedText))), $text, + '%s: Encoded string should decode back to original string for sample ' . + $sampleDir . '/' . $sampleFile + ); + } + closedir($fileFp); + } + + } + closedir($sampleFp); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EncodingAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EncodingAcceptanceTest.php new file mode 100644 index 0000000..9fa461a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EncodingAcceptanceTest.php @@ -0,0 +1,31 @@ +assertEqual('7bit', $encoder->getName()); + } + + public function testGet8BitEncodingReturns8BitEncoder() + { + $encoder = Swift_Encoding::get8BitEncoding(); + $this->assertEqual('8bit', $encoder->getName()); + } + + public function testGetQpEncodingReturnsQpEncoder() + { + $encoder = Swift_Encoding::getQpEncoding(); + $this->assertEqual('quoted-printable', $encoder->getName()); + } + + public function testGetBase64EncodingReturnsBase64Encoder() + { + $encoder = Swift_Encoding::getBase64Encoding(); + $this->assertEqual('base64', $encoder->getName()); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/ArrayKeyCacheAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/ArrayKeyCacheAcceptanceTest.php new file mode 100644 index 0000000..da051f4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/ArrayKeyCacheAcceptanceTest.php @@ -0,0 +1,179 @@ +_cache = new Swift_KeyCache_ArrayKeyCache( + new Swift_KeyCache_SimpleKeyCacheInputStream() + ); + } + + public function testStringDataCanBeSetAndFetched() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('test', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testStringDataCanBeOverwritten() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'foo', 'whatever', Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('whatever', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testStringDataCanBeAppended() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'foo', 'ing', Swift_KeyCache::MODE_APPEND + ); + $this->assertEqual('testing', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testHasKeyReturnValue() + { + $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo')); + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo')); + } + + public function testNsKeyIsWellPartitioned() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key2, 'foo', 'ing', Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('test', $this->_cache->getString($this->_key1, 'foo')); + $this->assertEqual('ing', $this->_cache->getString($this->_key2, 'foo')); + } + + public function testItemKeyIsWellPartitioned() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'bar', 'ing', Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('test', $this->_cache->getString($this->_key1, 'foo')); + $this->assertEqual('ing', $this->_cache->getString($this->_key1, 'bar')); + } + + public function testByteStreamCanBeImported() + { + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write('abcdef'); + + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os, Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('abcdef', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testByteStreamCanBeAppended() + { + $os1 = new Swift_ByteStream_ArrayByteStream(); + $os1->write('abcdef'); + + $os2 = new Swift_ByteStream_ArrayByteStream(); + $os2->write('xyzuvw'); + + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os1, Swift_KeyCache::MODE_APPEND + ); + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os2, Swift_KeyCache::MODE_APPEND + ); + + $this->assertEqual('abcdefxyzuvw', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testByteStreamAndStringCanBeAppended() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_APPEND + ); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write('abcdef'); + + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os, Swift_KeyCache::MODE_APPEND + ); + $this->assertEqual('testabcdef', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testDataCanBeExportedToByteStream() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + + $is = new Swift_ByteStream_ArrayByteStream(); + + $this->_cache->exportToByteStream($this->_key1, 'foo', $is); + + $string = ''; + while (false !== $bytes = $is->read(8192)) { + $string .= $bytes; + } + + $this->assertEqual('test', $string); + } + + public function testKeyCanBeCleared() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo')); + $this->_cache->clearKey($this->_key1, 'foo'); + $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo')); + } + + public function testNsKeyCanBeCleared() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'bar', 'xyz', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo')); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'bar')); + $this->_cache->clearAll($this->_key1); + $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo')); + $this->assertFalse($this->_cache->hasKey($this->_key1, 'bar')); + } + + public function testKeyCacheInputStream() + { + $is = $this->_cache->getInputByteStream($this->_key1, 'foo'); + $is->write('abc'); + $is->write('xyz'); + $this->assertEqual('abcxyz', $this->_cache->getString($this->_key1, 'foo')); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/DiskKeyCacheAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/DiskKeyCacheAcceptanceTest.php new file mode 100644 index 0000000..a3a37c1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/DiskKeyCacheAcceptanceTest.php @@ -0,0 +1,189 @@ +skipUnless( + SWIFT_TMP_DIR, 'Cannot run test without a writable directory to use (' . + 'define SWIFT_TMP_DIR in tests/config.php if you wish to run this test)' + ); + } + + public function setUp() + { + $this->_key1 = uniqid(microtime(true), true); + $this->_key2 = uniqid(microtime(true), true); + $this->_cache = new Swift_KeyCache_DiskKeyCache( + new Swift_KeyCache_SimpleKeyCacheInputStream(), + SWIFT_TMP_DIR + ); + } + + public function testStringDataCanBeSetAndFetched() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('test', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testStringDataCanBeOverwritten() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'foo', 'whatever', Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('whatever', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testStringDataCanBeAppended() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'foo', 'ing', Swift_KeyCache::MODE_APPEND + ); + $this->assertEqual('testing', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testHasKeyReturnValue() + { + $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo')); + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo')); + } + + public function testNsKeyIsWellPartitioned() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key2, 'foo', 'ing', Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('test', $this->_cache->getString($this->_key1, 'foo')); + $this->assertEqual('ing', $this->_cache->getString($this->_key2, 'foo')); + } + + public function testItemKeyIsWellPartitioned() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'bar', 'ing', Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('test', $this->_cache->getString($this->_key1, 'foo')); + $this->assertEqual('ing', $this->_cache->getString($this->_key1, 'bar')); + } + + public function testByteStreamCanBeImported() + { + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write('abcdef'); + + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os, Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('abcdef', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testByteStreamCanBeAppended() + { + $os1 = new Swift_ByteStream_ArrayByteStream(); + $os1->write('abcdef'); + + $os2 = new Swift_ByteStream_ArrayByteStream(); + $os2->write('xyzuvw'); + + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os1, Swift_KeyCache::MODE_APPEND + ); + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os2, Swift_KeyCache::MODE_APPEND + ); + + $this->assertEqual('abcdefxyzuvw', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testByteStreamAndStringCanBeAppended() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_APPEND + ); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write('abcdef'); + + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os, Swift_KeyCache::MODE_APPEND + ); + $this->assertEqual('testabcdef', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testDataCanBeExportedToByteStream() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + + $is = new Swift_ByteStream_ArrayByteStream(); + + $this->_cache->exportToByteStream($this->_key1, 'foo', $is); + + $string = ''; + while (false !== $bytes = $is->read(8192)) { + $string .= $bytes; + } + + $this->assertEqual('test', $string); + } + + public function testKeyCanBeCleared() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo')); + $this->_cache->clearKey($this->_key1, 'foo'); + $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo')); + } + + public function testNsKeyCanBeCleared() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'bar', 'xyz', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo')); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'bar')); + $this->_cache->clearAll($this->_key1); + $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo')); + $this->assertFalse($this->_cache->hasKey($this->_key1, 'bar')); + } + + public function testKeyCacheInputStream() + { + $is = $this->_cache->getInputByteStream($this->_key1, 'foo'); + $is->write('abc'); + $is->write('xyz'); + $this->assertEqual('abcxyz', $this->_cache->getString($this->_key1, 'foo')); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MessageAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MessageAcceptanceTest.php new file mode 100644 index 0000000..89dab6c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MessageAcceptanceTest.php @@ -0,0 +1,58 @@ +_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + + $id = $message->getId(); + $date = $message->getDate(); + $boundary = $message->getBoundary(); + + $message->addPart('foo', 'text/plain', 'iso-8859-1'); + $message->addPart('test foo', 'text/html', 'iso-8859-1'); + + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/plain; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/html; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'test foo' . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n", + $message->toString() + ); + } + + // -- Private helpers + + protected function _createMessage() + { + Swift_DependencyContainer::getInstance() + ->register('properties.charset')->asValue(null); + + return Swift_Message::newInstance(); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/AttachmentAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/AttachmentAcceptanceTest.php new file mode 100644 index 0000000..3a318ae --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/AttachmentAcceptanceTest.php @@ -0,0 +1,137 @@ +_cache = new Swift_KeyCache_ArrayKeyCache( + new Swift_KeyCache_SimpleKeyCacheInputStream() + ); + $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + $this->_contentEncoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + + $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8') + ); + $paramEncoder = new Swift_Encoder_Rfc2231Encoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8') + ); + $this->_grammar = new Swift_Mime_Grammar(); + $this->_headers = new Swift_Mime_SimpleHeaderSet( + new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $this->_grammar) + ); + } + + public function testDispositionIsSetInHeader() + { + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setDisposition('inline'); + $this->assertEqual( + 'Content-Type: application/pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline' . "\r\n", + $attachment->toString() + ); + } + + public function testDispositionIsAttachmentByDefault() + { + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $this->assertEqual( + 'Content-Type: application/pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment' . "\r\n", + $attachment->toString() + ); + } + + public function testFilenameIsSetInHeader() + { + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $this->assertEqual( + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=foo.pdf' . "\r\n", + $attachment->toString() + ); + } + + public function testSizeIsSetInHeader() + { + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setSize(12340); + $this->assertEqual( + 'Content-Type: application/pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; size=12340' . "\r\n", + $attachment->toString() + ); + } + + public function testMultipleParametersInHeader() + { + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setSize(12340); + $this->assertEqual( + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=foo.pdf; size=12340' . "\r\n", + $attachment->toString() + ); + } + + public function testEndToEnd() + { + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setSize(12340); + $attachment->setBody('abcd'); + $this->assertEqual( + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=foo.pdf; size=12340' . "\r\n" . + "\r\n" . + base64_encode('abcd'), + $attachment->toString() + ); + } + + // -- Private helpers + + protected function _createAttachment() + { + $entity = new Swift_Mime_Attachment( + $this->_headers, + $this->_contentEncoder, + $this->_cache, + $this->_grammar + ); + + return $entity; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/Base64ContentEncoderAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/Base64ContentEncoderAcceptanceTest.php new file mode 100644 index 0000000..03ad0f7 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/Base64ContentEncoderAcceptanceTest.php @@ -0,0 +1,62 @@ +_samplesDir = realpath(dirname(__FILE__) . '/../../../../_samples/charsets'); + $this->_encoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + } + + public function testEncodingAndDecodingSamples() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) { + if (substr($encodingDir, 0, 1) == '.') { + continue; + } + + $sampleDir = $this->_samplesDir . '/' . $encodingDir; + + if (is_dir($sampleDir)) { + + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) { + if (substr($sampleFile, 0, 1) == '.') { + continue; + } + + $text = file_get_contents($sampleDir . '/' . $sampleFile); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write($text); + + $is = new Swift_ByteStream_ArrayByteStream(); + + $this->_encoder->encodeByteStream($os, $is); + + $encoded = ''; + while (false !== $bytes = $is->read(8192)) { + $encoded .= $bytes; + } + + $this->assertEqual( + base64_decode($encoded), $text, + '%s: Encoded string should decode back to original string for sample ' . + $sampleDir . '/' . $sampleFile + ); + } + closedir($fileFp); + } + + } + closedir($sampleFp); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/NativeQpContentEncoderAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/NativeQpContentEncoderAcceptanceTest.php new file mode 100644 index 0000000..38fbb0b --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/NativeQpContentEncoderAcceptanceTest.php @@ -0,0 +1,95 @@ +_samplesDir = realpath(dirname(__FILE__) . '/../../../../_samples/charsets'); + $this->_encoder = new Swift_Mime_ContentEncoder_NativeQpContentEncoder(); + } + + public function testEncodingAndDecodingSamples() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) { + if (substr($encodingDir, 0, 1) == '.') { + continue; + } + + $sampleDir = $this->_samplesDir . '/' . $encodingDir; + + if (is_dir($sampleDir)) { + + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) { + if (substr($sampleFile, 0, 1) == '.') { + continue; + } + + $text = file_get_contents($sampleDir . '/' . $sampleFile); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write($text); + + $is = new Swift_ByteStream_ArrayByteStream(); + $this->_encoder->encodeByteStream($os, $is); + + $encoded = ''; + while (false !== $bytes = $is->read(8192)) { + $encoded .= $bytes; + } + + $this->assertEqual( + quoted_printable_decode($encoded), + // CR and LF are converted to CRLF + preg_replace('~\r(?!\n)|(?_createEncoderFromContainer(); + $this->assertSame('=C3=A4=C3=B6=C3=BC=C3=9F', $encoder->encodeString('äöüß')); + } + + public function testCharsetChangeNotImplemented() + { + $this->_encoder->charsetChanged('utf-8'); + $this->expectException(new RuntimeException('Charset "charset" not supported. NativeQpContentEncoder only supports "utf-8"')); + $this->_encoder->charsetChanged('charset'); + $this->_encoder->encodeString('foo'); + } + + public function testGetName() + { + $this->assertSame('quoted-printable', $this->_encoder->getName()); + } + + // -- Private Methods + + private function _createEncoderFromContainer() + { + return Swift_DependencyContainer::getInstance() + ->lookup('mime.nativeqpcontentencoder') + ; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/PlainContentEncoderAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/PlainContentEncoderAcceptanceTest.php new file mode 100644 index 0000000..103bbf4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/PlainContentEncoderAcceptanceTest.php @@ -0,0 +1,96 @@ +_samplesDir = realpath(dirname(__FILE__) . '/../../../../_samples/charsets'); + $this->_encoder = new Swift_Mime_ContentEncoder_PlainContentEncoder('8bit'); + } + + public function testEncodingAndDecodingSamplesString() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) { + if (substr($encodingDir, 0, 1) == '.') { + continue; + } + + $sampleDir = $this->_samplesDir . '/' . $encodingDir; + + if (is_dir($sampleDir)) { + + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) { + if (substr($sampleFile, 0, 1) == '.') { + continue; + } + + $text = file_get_contents($sampleDir . '/' . $sampleFile); + $encodedText = $this->_encoder->encodeString($text); + + $this->assertEqual( + $encodedText, $text, + '%s: Encoded string should be identical to original string for sample ' . + $sampleDir . '/' . $sampleFile + ); + } + closedir($fileFp); + } + + } + closedir($sampleFp); + } + + public function testEncodingAndDecodingSamplesByteStream() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) { + if (substr($encodingDir, 0, 1) == '.') { + continue; + } + + $sampleDir = $this->_samplesDir . '/' . $encodingDir; + + if (is_dir($sampleDir)) { + + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) { + if (substr($sampleFile, 0, 1) == '.') { + continue; + } + + $text = file_get_contents($sampleDir . '/' . $sampleFile); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write($text); + + $is = new Swift_ByteStream_ArrayByteStream(); + + $this->_encoder->encodeByteStream($os, $is); + + $encoded = ''; + while (false !== $bytes = $is->read(8192)) { + $encoded .= $bytes; + } + + $this->assertEqual( + $encoded, $text, + '%s: Encoded string should be identical to original string for sample ' . + $sampleDir . '/' . $sampleFile + ); + } + closedir($fileFp); + } + + } + closedir($sampleFp); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/QpContentEncoderAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/QpContentEncoderAcceptanceTest.php new file mode 100644 index 0000000..1373fd3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/QpContentEncoderAcceptanceTest.php @@ -0,0 +1,170 @@ +_samplesDir = realpath(dirname(__FILE__) . '/../../../../_samples/charsets'); + $this->_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + } + + public function testEncodingAndDecodingSamples() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) { + if (substr($encodingDir, 0, 1) == '.') { + continue; + } + + $encoding = $encodingDir; + $charStream = new Swift_CharacterStream_NgCharacterStream( + $this->_factory, $encoding); + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + + $sampleDir = $this->_samplesDir . '/' . $encodingDir; + + if (is_dir($sampleDir)) { + + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) { + if (substr($sampleFile, 0, 1) == '.') { + continue; + } + + $text = file_get_contents($sampleDir . '/' . $sampleFile); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write($text); + + $is = new Swift_ByteStream_ArrayByteStream(); + $encoder->encodeByteStream($os, $is); + + $encoded = ''; + while (false !== $bytes = $is->read(8192)) { + $encoded .= $bytes; + } + + $this->assertEqual( + quoted_printable_decode($encoded), $text, + '%s: Encoded string should decode back to original string for sample ' . + $sampleDir . '/' . $sampleFile + ); + } + closedir($fileFp); + } + + } + closedir($sampleFp); + } + + public function testEncodingAndDecodingSamplesFromDiConfiguredInstance() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) { + if (substr($encodingDir, 0, 1) == '.') { + continue; + } + + $encoding = $encodingDir; + $encoder = $this->_createEncoderFromContainer(); + + $sampleDir = $this->_samplesDir . '/' . $encodingDir; + + if (is_dir($sampleDir)) { + + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) { + if (substr($sampleFile, 0, 1) == '.') { + continue; + } + + $text = file_get_contents($sampleDir . '/' . $sampleFile); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write($text); + + $is = new Swift_ByteStream_ArrayByteStream(); + $encoder->encodeByteStream($os, $is); + + $encoded = ''; + while (false !== $bytes = $is->read(8192)) { + $encoded .= $bytes; + } + + $this->assertEqual( + str_replace("\r\n", "\n", quoted_printable_decode($encoded)), str_replace("\r\n", "\n", $text), + '%s: Encoded string should decode back to original string for sample ' . + $sampleDir . '/' . $sampleFile + ); + } + closedir($fileFp); + } + + } + closedir($sampleFp); + } + + public function testEncodingLFTextWithDiConfiguredInstance() + { + $encoder = $this->_createEncoderFromContainer(); + $this->assertEqual("a\r\nb\r\nc", $encoder->encodeString("a\nb\nc")); + } + + public function testEncodingCRTextWithDiConfiguredInstance() + { + $encoder = $this->_createEncoderFromContainer(); + $this->assertEqual("a\r\nb\r\nc", $encoder->encodeString("a\rb\rc")); + } + + public function testEncodingLFCRTextWithDiConfiguredInstance() + { + $encoder = $this->_createEncoderFromContainer(); + $this->assertEqual("a\r\n\r\nb\r\n\r\nc", $encoder->encodeString("a\n\rb\n\rc")); + } + + public function testEncodingCRLFTextWithDiConfiguredInstance() + { + $encoder = $this->_createEncoderFromContainer(); + $this->assertEqual("a\r\nb\r\nc", $encoder->encodeString("a\r\nb\r\nc")); + } + + public function testEncodingDotStuffingWithDiConfiguredInstance() + { + // Enable DotEscaping + Swift_Preferences::getInstance()->setQPDotEscape(true); + $encoder = $this->_createEncoderFromContainer(); + $this->assertEqual("a=2E\r\n=2E\r\n=2Eb\r\nc", $encoder->encodeString("a.\r\n.\r\n.b\r\nc")); + // Return to default + Swift_Preferences::getInstance()->setQPDotEscape(false); + $encoder = $this->_createEncoderFromContainer(); + $this->assertEqual("a.\r\n.\r\n.b\r\nc", $encoder->encodeString("a.\r\n.\r\n.b\r\nc")); + } + + public function testDotStuffingEncodingAndDecodingSamplesFromDiConfiguredInstance() + { + // Enable DotEscaping + Swift_Preferences::getInstance()->setQPDotEscape(true); + $this->testEncodingAndDecodingSamplesFromDiConfiguredInstance(); + // Disable DotStuffing to continue + Swift_Preferences::getInstance()->setQPDotEscape(false); + } + + // -- Private Methods + + private function _createEncoderFromContainer() + { + return Swift_DependencyContainer::getInstance() + ->lookup('mime.qpcontentencoder') + ; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/EmbeddedFileAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/EmbeddedFileAcceptanceTest.php new file mode 100644 index 0000000..cf6186a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/EmbeddedFileAcceptanceTest.php @@ -0,0 +1,150 @@ +_cache = new Swift_KeyCache_ArrayKeyCache( + new Swift_KeyCache_SimpleKeyCacheInputStream() + ); + $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + $this->_contentEncoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + + $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8') + ); + $paramEncoder = new Swift_Encoder_Rfc2231Encoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8') + ); + $this->_grammar = new Swift_Mime_Grammar(); + $this->_headers = new Swift_Mime_SimpleHeaderSet( + new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $this->_grammar) + ); + } + + public function testContentIdIsSetInHeader() + { + $file = $this->_createEmbeddedFile(); + $file->setContentType('application/pdf'); + $file->setId('foo@bar'); + $this->assertEqual( + 'Content-Type: application/pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline' . "\r\n" . + 'Content-ID: ' . "\r\n", + $file->toString() + ); + } + + public function testDispositionIsSetInHeader() + { + $file = $this->_createEmbeddedFile(); + $id = $file->getId(); + $file->setContentType('application/pdf'); + $file->setDisposition('attachment'); + $this->assertEqual( + 'Content-Type: application/pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment' . "\r\n" . + 'Content-ID: <'. $id . '>' . "\r\n", + $file->toString() + ); + } + + public function testFilenameIsSetInHeader() + { + $file = $this->_createEmbeddedFile(); + $id = $file->getId(); + $file->setContentType('application/pdf'); + $file->setFilename('foo.pdf'); + $this->assertEqual( + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; filename=foo.pdf' . "\r\n" . + 'Content-ID: <'. $id . '>' . "\r\n", + $file->toString() + ); + } + + public function testSizeIsSetInHeader() + { + $file = $this->_createEmbeddedFile(); + $id = $file->getId(); + $file->setContentType('application/pdf'); + $file->setSize(12340); + $this->assertEqual( + 'Content-Type: application/pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; size=12340' . "\r\n" . + 'Content-ID: <'. $id . '>' . "\r\n", + $file->toString() + ); + } + + public function testMultipleParametersInHeader() + { + $file = $this->_createEmbeddedFile(); + $id = $file->getId(); + $file->setContentType('application/pdf'); + $file->setFilename('foo.pdf'); + $file->setSize(12340); + $this->assertEqual( + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; filename=foo.pdf; size=12340' . "\r\n" . + 'Content-ID: <'. $id . '>' . "\r\n", + $file->toString() + ); + } + + public function testEndToEnd() + { + $file = $this->_createEmbeddedFile(); + $id = $file->getId(); + $file->setContentType('application/pdf'); + $file->setFilename('foo.pdf'); + $file->setSize(12340); + $file->setBody('abcd'); + $this->assertEqual( + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; filename=foo.pdf; size=12340' . "\r\n" . + 'Content-ID: <'. $id . '>' . "\r\n" . + "\r\n" . + base64_encode('abcd'), + $file->toString() + ); + } + + // -- Private helpers + + protected function _createEmbeddedFile() + { + $entity = new Swift_Mime_EmbeddedFile( + $this->_headers, + $this->_contentEncoder, + $this->_cache, + $this->_grammar + ); + + return $entity; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/HeaderEncoder/Base64HeaderEncoderAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/HeaderEncoder/Base64HeaderEncoderAcceptanceTest.php new file mode 100644 index 0000000..1ef1609 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/HeaderEncoder/Base64HeaderEncoderAcceptanceTest.php @@ -0,0 +1,35 @@ +_encoder = new Swift_Mime_HeaderEncoder_Base64HeaderEncoder(); + } + + public function testEncodingJIS() + { + if (function_exists('mb_convert_encoding')) { + // base64_encode and split cannot handle long JIS text to fold + $subject = "é•·ã„é•·ã„é•·ã„é•·ã„é•·ã„é•·ã„é•·ã„é•·ã„é•·ã„é•·ã„é•·ã„é•·ã„é•·ã„é•·ã„é•·ã„é•·ã„é•·ã„é•·ã„é•·ã„é•·ã„ä»¶å"; + + $encodedWrapperLength = strlen('=?iso-2022-jp?' . $this->_encoder->getName() . '??='); + + $old = mb_internal_encoding(); + mb_internal_encoding('utf-8'); + $newstring = mb_encode_mimeheader($subject, 'iso-2022-jp', 'B', "\r\n"); + mb_internal_encoding($old); + + $encoded = $this->_encoder->encodeString($subject, 0, 75 - $encodedWrapperLength, 'iso-2022-jp'); + $this->assertEqual( + $encoded, $newstring, + 'Encoded string should decode back to original string for sample ' + ); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/MimePartAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/MimePartAcceptanceTest.php new file mode 100644 index 0000000..bd8d060 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/MimePartAcceptanceTest.php @@ -0,0 +1,143 @@ +_cache = new Swift_KeyCache_ArrayKeyCache( + new Swift_KeyCache_SimpleKeyCacheInputStream() + ); + $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + $this->_contentEncoder = new Swift_Mime_ContentEncoder_QpContentEncoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'), + new Swift_StreamFilters_ByteArrayReplacementFilter( + array(array(0x0D, 0x0A), array(0x0D), array(0x0A)), + array(array(0x0A), array(0x0A), array(0x0D, 0x0A)) + ) + ); + + $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8') + ); + $paramEncoder = new Swift_Encoder_Rfc2231Encoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8') + ); + $this->_grammar = new Swift_Mime_Grammar(); + $this->_headers = new Swift_Mime_SimpleHeaderSet( + new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $this->_grammar) + ); + } + + public function testCharsetIsSetInHeader() + { + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setCharset('utf-8'); + $part->setBody('foobar'); + $this->assertEqual( + 'Content-Type: text/plain; charset=utf-8' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foobar', + $part->toString() + ); + } + + public function testFormatIsSetInHeaders() + { + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setFormat('flowed'); + $part->setBody('> foobar'); + $this->assertEqual( + 'Content-Type: text/plain; format=flowed' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + '> foobar', + $part->toString() + ); + } + + public function testDelSpIsSetInHeaders() + { + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setDelSp(true); + $part->setBody('foobar'); + $this->assertEqual( + 'Content-Type: text/plain; delsp=yes' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foobar', + $part->toString() + ); + } + + public function testAll3ParamsInHeaders() + { + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setCharset('utf-8'); + $part->setFormat('fixed'); + $part->setDelSp(true); + $part->setBody('foobar'); + $this->assertEqual( + 'Content-Type: text/plain; charset=utf-8; format=fixed; delsp=yes' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foobar', + $part->toString() + ); + } + + public function testBodyIsCanonicalized() + { + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setCharset('utf-8'); + $part->setBody("foobar\r\rtest\ning\r"); + $this->assertEqual( + 'Content-Type: text/plain; charset=utf-8' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + "foobar\r\n" . + "\r\n" . + "test\r\n" . + "ing\r\n", + $part->toString() + ); + } + + // -- Private helpers + + protected function _createMimePart() + { + $entity = new Swift_Mime_MimePart( + $this->_headers, + $this->_contentEncoder, + $this->_cache, + $this->_grammar + ); + + return $entity; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/SimpleMessageAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/SimpleMessageAcceptanceTest.php new file mode 100644 index 0000000..87e4db8 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/SimpleMessageAcceptanceTest.php @@ -0,0 +1,1254 @@ +setCharset(null); //TODO: Test with the charset defined + } + + public function testBasicHeaders() + { + /* -- RFC 2822, 3.6. + */ + + $message = $this->_createMessage(); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString(), + '%s: Only required headers, and non-empty headers should be displayed' + ); + } + + public function testSubjectIsDisplayedIfSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testDateCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $id = $message->getId(); + $message->setDate(1234); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', 1234) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testMessageIdCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setId('foo@bar'); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: ' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testContentTypeCanBeChanged() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setContentType('text/html'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/html' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testCharsetCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setContentType('text/html'); + $message->setCharset('iso-8859-1'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/html; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testFormatCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFormat('flowed'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain; format=flowed' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testEncoderCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setContentType('text/html'); + $message->setEncoder( + new Swift_Mime_ContentEncoder_PlainContentEncoder('7bit') + ); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/html' . "\r\n" . + 'Content-Transfer-Encoding: 7bit' . "\r\n", + $message->toString() + ); + } + + public function testFromAddressCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom('chris.corbyn@swiftmailer.org'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: chris.corbyn@swiftmailer.org' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testFromAddressCanBeSetWithName() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testMultipleFromAddressesCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' + )); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn , mark@swiftmailer.org' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testReturnPathAddressCanBeSet() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testEmptyReturnPathHeaderCanBeUsed() + { + $message = $this->_createMessage(); + $message->setReturnPath(''); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Return-Path: <>' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testSenderCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setSender('chris.corbyn@swiftmailer.org'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Sender: chris.corbyn@swiftmailer.org' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testSenderCanBeSetWithName() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setSender(array('chris.corbyn@swiftmailer.org'=>'Chris')); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Sender: Chris ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testReplyToCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org'=>'Chris')); + $message->setReplyTo(array('chris@w3style.co.uk'=>'Myself')); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris ' . "\r\n" . + 'Reply-To: Myself ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testMultipleReplyAddressCanBeUsed() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org'=>'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me' + )); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris ' . "\r\n" . + 'Reply-To: Myself , Me ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testToAddressCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org'=>'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me' + )); + $message->setTo('mark@swiftmailer.org'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris ' . "\r\n" . + 'Reply-To: Myself , Me ' . "\r\n" . + 'To: mark@swiftmailer.org' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testMultipleToAddressesCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org'=>'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me' + )); + $message->setTo(array( + 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn' + )); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris ' . "\r\n" . + 'Reply-To: Myself , Me ' . "\r\n" . + 'To: mark@swiftmailer.org, Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testCcAddressCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org'=>'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me' + )); + $message->setTo(array( + 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn' + )); + $message->setCc('john@some-site.com'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris ' . "\r\n" . + 'Reply-To: Myself , Me ' . "\r\n" . + 'To: mark@swiftmailer.org, Chris Corbyn ' . "\r\n" . + 'Cc: john@some-site.com' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testMultipleCcAddressesCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org'=>'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me' + )); + $message->setTo(array( + 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn' + )); + $message->setCc(array( + 'john@some-site.com' => 'John West', + 'fred@another-site.co.uk' => 'Big Fred' + )); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris ' . "\r\n" . + 'Reply-To: Myself , Me ' . "\r\n" . + 'To: mark@swiftmailer.org, Chris Corbyn ' . "\r\n" . + 'Cc: John West , Big Fred ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testBccAddressCanBeSet() + { + //Obviously Transports need to setBcc(array()) and send to each Bcc recipient + // separately in accordance with RFC 2822/2821 + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org'=>'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me' + )); + $message->setTo(array( + 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn' + )); + $message->setCc(array( + 'john@some-site.com' => 'John West', + 'fred@another-site.co.uk' => 'Big Fred' + )); + $message->setBcc('x@alphabet.tld'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris ' . "\r\n" . + 'Reply-To: Myself , Me ' . "\r\n" . + 'To: mark@swiftmailer.org, Chris Corbyn ' . "\r\n" . + 'Cc: John West , Big Fred ' . "\r\n" . + 'Bcc: x@alphabet.tld' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testMultipleBccAddressesCanBeSet() + { + //Obviously Transports need to setBcc(array()) and send to each Bcc recipient + // separately in accordance with RFC 2822/2821 + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org'=>'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me' + )); + $message->setTo(array( + 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn' + )); + $message->setCc(array( + 'john@some-site.com' => 'John West', + 'fred@another-site.co.uk' => 'Big Fred' + )); + $message->setBcc(array('x@alphabet.tld', 'a@alphabet.tld' => 'A')); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris ' . "\r\n" . + 'Reply-To: Myself , Me ' . "\r\n" . + 'To: mark@swiftmailer.org, Chris Corbyn ' . "\r\n" . + 'Cc: John West , Big Fred ' . "\r\n" . + 'Bcc: x@alphabet.tld, A ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString() + ); + } + + public function testStringBodyIsAppended() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $message->setBody( + 'just a test body' . "\r\n" . + 'with a new line' + ); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'just a test body' . "\r\n" . + 'with a new line', + $message->toString() + ); + } + + public function testStringBodyIsEncoded() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $message->setBody( + 'Just s' . pack('C*', 0xC2, 0x01, 0x01) . 'me multi-' . "\r\n" . + 'line message!' + ); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'Just s=C2=01=01me multi-' . "\r\n" . + 'line message!', + $message->toString() + ); + } + + public function testChildrenCanBeAttached() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + + $id = $message->getId(); + $date = $message->getDate(); + $boundary = $message->getBoundary(); + + $part1 = $this->_createMimePart(); + $part1->setContentType('text/plain'); + $part1->setCharset('iso-8859-1'); + $part1->setBody('foo'); + + $message->attach($part1); + + $part2 = $this->_createMimePart(); + $part2->setContentType('text/html'); + $part2->setCharset('iso-8859-1'); + $part2->setBody('test foo'); + + $message->attach($part2); + + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/plain; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/html; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'test foo' . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n", + $message->toString() + ); + } + + public function testAttachmentsBeingAttached() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setCharset('iso-8859-1'); + $part->setBody('foo'); + + $message->attach($part); + + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setBody(''); + + $message->attach($attachment); + + $this->assertPattern( + '~^' . + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/mixed;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="(.*?)"' . "\r\n" . + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: text/plain; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo' . + "\r\n\r\n" . + '--\\1--' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=foo.pdf' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n" . + '$~D', + $message->toString() + ); + } + + public function testAttachmentsAndEmbeddedFilesBeingAttached() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setCharset('iso-8859-1'); + $part->setBody('foo'); + + $message->attach($part); + + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setBody(''); + + $message->attach($attachment); + + $file = $this->_createEmbeddedFile(); + $file->setContentType('image/jpeg'); + $file->setFilename('myimage.jpg'); + $file->setBody(''); + + $message->attach($file); + + $cid = $file->getId(); + + $this->assertPattern( + '~^' . + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/mixed;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="(.*?)"' . "\r\n" . + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: text/plain; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo' . + + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: multipart/related;' . "\r\n" . + ' boundary="(.*?)"' . "\r\n" . + "\r\n\r\n" . + '--\\2' . "\r\n" . + 'Content-Type: image/jpeg; name=myimage.jpg' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; filename=myimage.jpg' . "\r\n" . + 'Content-ID: <' . $cid . '>' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--\\2--' . "\r\n" . + "\r\n\r\n" . + '--\\1--' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=foo.pdf' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n" . + '$~D', + $message->toString() + ); + } + + public function testComplexEmbeddingOfContent() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setBody(''); + + $message->attach($attachment); + + $file = $this->_createEmbeddedFile(); + $file->setContentType('image/jpeg'); + $file->setFilename('myimage.jpg'); + $file->setBody(''); + + $part = $this->_createMimePart(); + $part->setContentType('text/html'); + $part->setCharset('iso-8859-1'); + $part->setBody('foo '); + + $message->attach($part); + + $cid = $file->getId(); + + $this->assertPattern( + '~^' . + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/mixed;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: multipart/related;' . "\r\n" . + ' boundary="(.*?)"' . "\r\n" . + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: text/html; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo ' . //=3D is just = in QP + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: image/jpeg; name=myimage.jpg' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; filename=myimage.jpg' . "\r\n" . + 'Content-ID: <' . $cid . '>' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--\\1--' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=foo.pdf' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n" . + '$~D', + $message->toString() + ); + } + + public function testAttachingAndDetachingContent() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setCharset('iso-8859-1'); + $part->setBody('foo'); + + $message->attach($part); + + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setBody(''); + + $message->attach($attachment); + + $file = $this->_createEmbeddedFile(); + $file->setContentType('image/jpeg'); + $file->setFilename('myimage.jpg'); + $file->setBody(''); + + $message->attach($file); + + $cid = $file->getId(); + + $message->detach($attachment); + + $this->assertPattern( + '~^' . + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/plain; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: multipart/related;' . "\r\n" . + ' boundary="(.*?)"' . "\r\n" . + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: image/jpeg; name=myimage.jpg' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; filename=myimage.jpg' . "\r\n" . + 'Content-ID: <' . $cid . '>' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--\\1--' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n" . + '$~D', + $message->toString(), + '%s: Attachment should have been detached' + ); + } + + public function testBoundaryDoesNotAppearAfterAllPartsAreDetached() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + + $id = $message->getId(); + $date = $message->getDate(); + $boundary = $message->getBoundary(); + + $part1 = $this->_createMimePart(); + $part1->setContentType('text/plain'); + $part1->setCharset('iso-8859-1'); + $part1->setBody('foo'); + + $message->attach($part1); + + $part2 = $this->_createMimePart(); + $part2->setContentType('text/html'); + $part2->setCharset('iso-8859-1'); + $part2->setBody('test foo'); + + $message->attach($part2); + + $message->detach($part1); + $message->detach($part2); + + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n", + $message->toString(), + '%s: Message should be restored to orignal state after parts are detached' + ); + } + + public function testCharsetFormatOrDelSpAreNotShownWhenBoundaryIsSet() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $message->setCharset('utf-8'); + $message->setFormat('flowed'); + $message->setDelSp(true); + + $id = $message->getId(); + $date = $message->getDate(); + $boundary = $message->getBoundary(); + + $part1 = $this->_createMimePart(); + $part1->setContentType('text/plain'); + $part1->setCharset('iso-8859-1'); + $part1->setBody('foo'); + + $message->attach($part1); + + $part2 = $this->_createMimePart(); + $part2->setContentType('text/html'); + $part2->setCharset('iso-8859-1'); + $part2->setBody('test foo'); + + $message->attach($part2); + + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/plain; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/html; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'test foo' . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n", + $message->toString() + ); + } + + public function testBodyCanBeSetWithAttachments() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $message->setContentType('text/html'); + $message->setCharset('iso-8859-1'); + $message->setBody('foo'); + + $id = $message->getId(); + $date = date('r', $message->getDate()); + $boundary = $message->getBoundary(); + + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setBody(''); + + $message->attach($attachment); + + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/mixed;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/html; charset=iso-8859-1' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: application/pdf; name=foo.pdf' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=foo.pdf' . "\r\n" . + "\r\n" . + base64_encode('') . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n", + $message->toString() + ); + } + + public function testHtmlPartAlwaysAppearsLast() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + + $id = $message->getId(); + $date = date('r', $message->getDate()); + $boundary = $message->getBoundary(); + + $part1 = $this->_createMimePart(); + $part1->setContentType('text/html'); + $part1->setBody('foo'); + + $part2 = $this->_createMimePart(); + $part2->setContentType('text/plain'); + $part2->setBody('bar'); + + $message->attach($part1); + $message->attach($part2); + + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'bar' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/html' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo' . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n", + $message->toString() + ); + } + + public function testBodyBecomesPartIfOtherPartsAttached() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $message->setContentType('text/html'); + $message->setBody('foo'); + + $id = $message->getId(); + $date = date('r', $message->getDate()); + $boundary = $message->getBoundary(); + + $part2 = $this->_createMimePart(); + $part2->setContentType('text/plain'); + $part2->setBody('bar'); + + $message->attach($part2); + + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'bar' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/html' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'foo' . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n", + $message->toString() + ); + } + + public function testBodyIsCanonicalized() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $message->setBody( + 'just a test body' . "\n" . + 'with a new line' + ); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEqual( + 'Return-Path: ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . date('r', $date) . "\r\n" . + 'Subject: just a test subject' . "\r\n" . + 'From: Chris Corbyn ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: text/plain' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'just a test body' . "\r\n" . + 'with a new line', + $message->toString() + ); + } + + // -- Private helpers + + protected function _createMessage() + { + return new Swift_Message(); + } + + protected function _createMimePart() + { + return new Swift_MimePart(); + } + + protected function _createAttachment() + { + return new Swift_Attachment(); + } + + protected function _createEmbeddedFile() + { + return new Swift_EmbeddedFile(); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MimePartAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MimePartAcceptanceTest.php new file mode 100644 index 0000000..e94795c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MimePartAcceptanceTest.php @@ -0,0 +1,15 @@ +register('properties.charset')->asValue(null); + + return Swift_MimePart::newInstance(); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php new file mode 100644 index 0000000..7b6c726 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php @@ -0,0 +1,122 @@ +_buffer = new Swift_Transport_StreamBuffer( + $this->_stub('Swift_ReplacementFilterFactory') + ); + } + + public function testReadLine() + { + $this->_initializeBuffer(); + + $line = $this->_buffer->readLine(0); + $this->assertPattern('/^[0-9]{3}.*?\r\n$/D', $line); + $seq = $this->_buffer->write("QUIT\r\n"); + $this->assertTrue($seq); + $line = $this->_buffer->readLine($seq); + $this->assertPattern('/^[0-9]{3}.*?\r\n$/D', $line); + $this->_buffer->terminate(); + } + + public function testWrite() + { + $this->_initializeBuffer(); + + $line = $this->_buffer->readLine(0); + $this->assertPattern('/^[0-9]{3}.*?\r\n$/D', $line); + + $seq = $this->_buffer->write("HELO foo\r\n"); + $this->assertTrue($seq); + $line = $this->_buffer->readLine($seq); + $this->assertPattern('/^[0-9]{3}.*?\r\n$/D', $line); + + $seq = $this->_buffer->write("QUIT\r\n"); + $this->assertTrue($seq); + $line = $this->_buffer->readLine($seq); + $this->assertPattern('/^[0-9]{3}.*?\r\n$/D', $line); + $this->_buffer->terminate(); + } + + public function testBindingOtherStreamsMirrorsWriteOperations() + { + $this->_initializeBuffer(); + + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $this->_checking(Expectations::create() + -> one($is1)->write('x') + -> one($is2)->write('x') + -> one($is1)->write('y') + -> one($is2)->write('y') + ); + + $this->_buffer->bind($is1); + $this->_buffer->bind($is2); + + $this->_buffer->write('x'); + $this->_buffer->write('y'); + } + + public function testBindingOtherStreamsMirrorsFlushOperations() + { + $this->_initializeBuffer(); + + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $this->_checking(Expectations::create() + -> one($is1)->flushBuffers() + -> one($is2)->flushBuffers() + ); + + $this->_buffer->bind($is1); + $this->_buffer->bind($is2); + + $this->_buffer->flushBuffers(); + } + + public function testUnbindingStreamPreventsFurtherWrites() + { + $this->_initializeBuffer(); + + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $this->_checking(Expectations::create() + -> one($is1)->write('x') + -> one($is2)->write('x') + -> one($is1)->write('y') + ); + + $this->_buffer->bind($is1); + $this->_buffer->bind($is2); + + $this->_buffer->write('x'); + + $this->_buffer->unbind($is2); + + $this->_buffer->write('y'); + } + + // -- Creation Methods + + private function _createMockInputStream() + { + return $this->_mock('Swift_InputByteStream'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/BasicSocketAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/BasicSocketAcceptanceTest.php new file mode 100644 index 0000000..364e353 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/BasicSocketAcceptanceTest.php @@ -0,0 +1,31 @@ +skipUnless(SWIFT_SMTP_HOST, + 'Cannot run test without an SMTP host to connect to (define ' . + 'SWIFT_SMTP_HOST in tests/acceptance.conf.php if you wish to run this test)' + ); + } + + protected function _initializeBuffer() + { + $parts = explode(':', SWIFT_SMTP_HOST); + $host = $parts[0]; + $port = isset($parts[1]) ? $parts[1] : 25; + + $this->_buffer->initialize(array( + 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET, + 'host' => $host, + 'port' => $port, + 'protocol' => 'tcp', + 'blocking' => 1, + 'timeout' => 15 + )); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/ProcessAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/ProcessAcceptanceTest.php new file mode 100644 index 0000000..a108ba2 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/ProcessAcceptanceTest.php @@ -0,0 +1,23 @@ +skipIf(!SWIFT_SENDMAIL_PATH, + 'Cannot run test without a path to sendmail (define ' . + 'SWIFT_SENDMAIL_PATH in tests/acceptance.conf.php if you wish to run this test)' + ); + } + + protected function _initializeBuffer() + { + $this->_buffer->initialize(array( + 'type' => Swift_Transport_IoBuffer::TYPE_PROCESS, + 'command' => SWIFT_SENDMAIL_PATH . ' -bs' + )); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SocketTimeoutTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SocketTimeoutTest.php new file mode 100644 index 0000000..93fb74a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SocketTimeoutTest.php @@ -0,0 +1,72 @@ +_buffer = new Swift_Transport_StreamBuffer( + $this->_stub('Swift_ReplacementFilterFactory') + ); + } + + public function skip() + { + $serverStarted=false; + for ($i=0; $i<5; ++$i) { + $this->_randomHighPort=rand(50000,65000); + $this->_server = stream_socket_server('tcp://127.0.0.1:' . $this->_randomHighPort); + if ($this->_server) { + $serverStarted=true; + } + } + $this->skipUnless(SWIFT_SMTP_HOST, + 'Cannot run test without an SMTP host to connect to (define ' . + 'SWIFT_SMTP_HOST in tests/acceptance.conf.php if you wish to run this test)' + ); + } + + protected function _initializeBuffer() + { + $host = '127.0.0.1'; + $port = $this->_randomHighPort; + + $this->_buffer->initialize(array( + 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET, + 'host' => $host, + 'port' => $port, + 'protocol' => 'tcp', + 'blocking' => 1, + 'timeout' => 1 + )); + } + + public function testTimeoutException() + { + $this->_initializeBuffer(); + $e=null; + try { + $line = $this->_buffer->readLine(0); + } catch (Exception $e) { + } + $this->assertIsA($e, 'Swift_IoException', 'IO Exception Not Thrown On Connection Timeout'); + $this->assertPattern('/Connection to .* Timed Out/', $e->getMessage()); + } + + public function tearDown() + { + if ($this->_server) { + stream_socket_shutdown($this->_server, STREAM_SHUT_RDWR); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SslSocketAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SslSocketAcceptanceTest.php new file mode 100644 index 0000000..4dea91c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SslSocketAcceptanceTest.php @@ -0,0 +1,35 @@ +skipIf(!in_array('ssl', $streams), + 'SSL is not configured for your system. It is not possible to run this test' + ); + $this->skipIf(!SWIFT_SSL_HOST, + 'Cannot run test without an SSL enabled SMTP host to connect to (define ' . + 'SWIFT_SSL_HOST in tests/acceptance.conf.php if you wish to run this test)' + ); + } + + protected function _initializeBuffer() + { + $parts = explode(':', SWIFT_SSL_HOST); + $host = $parts[0]; + $port = isset($parts[1]) ? $parts[1] : 25; + + $this->_buffer->initialize(array( + 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET, + 'host' => $host, + 'port' => $port, + 'protocol' => 'ssl', + 'blocking' => 1, + 'timeout' => 15 + )); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/TlsSocketAcceptanceTest.php b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/TlsSocketAcceptanceTest.php new file mode 100644 index 0000000..b1e38d7 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/TlsSocketAcceptanceTest.php @@ -0,0 +1,35 @@ +skipIf(!in_array('tls', $streams), + 'TLS is not configured for your system. It is not possible to run this test' + ); + $this->skipIf(!SWIFT_TLS_HOST, + 'Cannot run test without a TLS enabled SMTP host to connect to (define ' . + 'SWIFT_TLS_HOST in tests/acceptance.conf.php if you wish to run this test)' + ); + } + + protected function _initializeBuffer() + { + $parts = explode(':', SWIFT_TLS_HOST); + $host = $parts[0]; + $port = isset($parts[1]) ? $parts[1] : 25; + + $this->_buffer->initialize(array( + 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET, + 'host' => $host, + 'port' => $port, + 'protocol' => 'tls', + 'blocking' => 1, + 'timeout' => 15 + )); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug111Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug111Test.php new file mode 100644 index 0000000..1afb64c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug111Test.php @@ -0,0 +1,44 @@ + array( + 'email1@example.com', + 'email2@example.com', + 'email3@example.com', + 'email4@example.com', + 'email5@example.com', + ), + 'sub' => array( + '-name-' => array( + 'email1', + '"email2"', + 'email3\\', + 'email4', + 'email5', + ), + '-url-' => array( + 'http://google.com', + 'http://yahoo.com', + 'http://hotmail.com', + 'http://aol.com', + 'http://facebook.com', + ), + ) + ); + $json = json_encode($complicated_header); + + $message = new Swift_Message(); + $headers = $message->getHeaders(); + $headers->addTextHeader('X-SMTPAPI', $json); + $header = $headers->get('X-SMTPAPI'); + + $this->assertEqual('Swift_Mime_Headers_UnstructuredHeader', get_class($header)); + $this->assertEqual($json, $header->getFieldBody()); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug118Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug118Test.php new file mode 100644 index 0000000..0800a00 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug118Test.php @@ -0,0 +1,22 @@ +_message = new Swift_Message(); + } + + public function testCallingGenerateIdChangesTheMessageId() + { + $currentId = $this->_message->getId(); + $this->_message->generateId(); + $newId = $this->_message->getId(); + + $this->assertNotEqual($currentId, $newId); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug206Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug206Test.php new file mode 100644 index 0000000..8833eb2 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug206Test.php @@ -0,0 +1,40 @@ +_factory = new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $grammar); + } + + public function testMailboxHeaderEncoding() + { + $this->_testHeaderIsFullyEncoded('email@example.org', 'Family Name, Name', ' "Family Name, Name" '); + $this->_testHeaderIsFullyEncoded('email@example.org', 'Family Namé, Name', ' Family =?utf-8?Q?Nam=C3=A9=2C?= Name'); + $this->_testHeaderIsFullyEncoded('email@example.org', 'Family Namé , Name', ' Family =?utf-8?Q?Nam=C3=A9_=2C?= Name'); + $this->_testHeaderIsFullyEncoded('email@example.org', 'Family Namé ;Name', ' Family =?utf-8?Q?Nam=C3=A9_=3BName?= '); + } + + private function _testHeaderIsFullyEncoded($email, $name, $expected) + { + $mailboxHeader = $this->_factory->createMailboxHeader('To', array( + $email => $name + )); + + $headerBody = substr($mailboxHeader->toString(), 3, strlen($expected)); + + $this->assertEqual($expected, $headerBody); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug274Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug274Test.php new file mode 100644 index 0000000..0fab759 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug274Test.php @@ -0,0 +1,23 @@ +expectException(new Swift_IoException('The path cannot be empty')); + $message->attach(Swift_Attachment::fromPath('')); + } + + public function testNonEmptyFileNameAsAttachement() + { + $message = new Swift_Message(); + try { + $message->attach(Swift_Attachment::fromPath(__FILE__)); + } catch (Exception $e) { + $this->fail('Path should not be empty'); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug34Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug34Test.php new file mode 100644 index 0000000..c87bd78 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug34Test.php @@ -0,0 +1,77 @@ +setCharset('utf-8'); + } + + public function testEmbeddedFilesWithMultipartDataCreateMultipartRelatedContentAsAnAlternative() + { + $message = Swift_Message::newInstance(); + $message->setCharset('utf-8'); + $message->setSubject('test subject'); + $message->addPart('plain part', 'text/plain'); + + $image = Swift_Image::newInstance('', 'image.gif', 'image/gif'); + $cid = $message->embed($image); + + $message->setBody('', 'text/html'); + + $message->setTo(array('user@domain.tld' => 'User')); + + $message->setFrom(array('other@domain.tld' => 'Other')); + $message->setSender(array('other@domain.tld' => 'Other')); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + $cidVal = $image->getId(); + + $this->assertPattern( + '~^' . + 'Sender: Other ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: test subject' . "\r\n" . + 'From: Other ' . "\r\n" . + 'To: User ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/plain; charset=utf-8' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'plain part' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: multipart/related;' . "\r\n" . + ' boundary="(.*?)"' . "\r\n" . + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: text/html; charset=utf-8' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + '' . + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: image/gif; name=image.gif' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; filename=image.gif' . "\r\n" . + 'Content-ID: <' . $cidVal . '>' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--\\1--' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n" . + '$~D', + $message->toString() + ); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug35Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug35Test.php new file mode 100644 index 0000000..5138790 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug35Test.php @@ -0,0 +1,75 @@ +setCharset('utf-8'); + } + + public function testHTMLPartAppearsLastEvenWhenAttachmentsAdded() + { + $message = Swift_Message::newInstance(); + $message->setCharset('utf-8'); + $message->setSubject('test subject'); + $message->addPart('plain part', 'text/plain'); + + $attachment = Swift_Attachment::newInstance('', 'image.gif', 'image/gif'); + $message->attach($attachment); + + $message->setBody('HTML part', 'text/html'); + + $message->setTo(array('user@domain.tld' => 'User')); + + $message->setFrom(array('other@domain.tld' => 'Other')); + $message->setSender(array('other@domain.tld' => 'Other')); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + + $this->assertPattern( + '~^' . + 'Sender: Other ' . "\r\n" . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: test subject' . "\r\n" . + 'From: Other ' . "\r\n" . + 'To: User ' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/mixed;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: multipart/alternative;' . "\r\n" . + ' boundary="(.*?)"' . "\r\n" . + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: text/plain; charset=utf-8' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'plain part' . + "\r\n\r\n" . + '--\\1' . "\r\n" . + 'Content-Type: text/html; charset=utf-8' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'HTML part' . + "\r\n\r\n" . + '--\\1--' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: image/gif; name=image.gif' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=image.gif' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n" . + '$~D', + $message->toString() + ); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug38Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug38Test.php new file mode 100644 index 0000000..d28b8eb --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug38Test.php @@ -0,0 +1,196 @@ +_attFileName = 'data.txt'; + $this->_attFileType = 'text/plain'; + $this->_attFile = dirname(__FILE__) . '/../../_samples/files/data.txt'; + Swift_Preferences::getInstance()->setCharset('utf-8'); + } + + public function testWritingMessageToByteStreamProducesCorrectStructure() + { + $message = new Swift_Message(); + $message->setSubject('test subject'); + $message->setTo('user@domain.tld'); + $message->setCc('other@domain.tld'); + $message->setFrom('user@domain.tld'); + + $image = new Swift_Image('', 'image.gif', 'image/gif'); + + $cid = $message->embed($image); + $message->setBody('HTML part', 'text/html'); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + $imgId = $image->getId(); + + $stream = new Swift_ByteStream_ArrayByteStream(); + + $message->toByteStream($stream); + + $this->assertPatternInStream( + '~^' . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: test subject' . "\r\n" . + 'From: user@domain.tld' . "\r\n" . + 'To: user@domain.tld' . "\r\n" . + 'Cc: other@domain.tld' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/related;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/html; charset=utf-8' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'HTML part' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: image/gif; name=image.gif' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; filename=image.gif' . "\r\n" . + 'Content-ID: <' . preg_quote($imgId, '~') . '>' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n" . + '$~D', + $stream + ); + } + + public function testWritingMessageToByteStreamTwiceProducesCorrectStructure() + { + $message = new Swift_Message(); + $message->setSubject('test subject'); + $message->setTo('user@domain.tld'); + $message->setCc('other@domain.tld'); + $message->setFrom('user@domain.tld'); + + $image = new Swift_Image('', 'image.gif', 'image/gif'); + + $cid = $message->embed($image); + $message->setBody('HTML part', 'text/html'); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + $imgId = $image->getId(); + + $pattern = '~^' . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: test subject' . "\r\n" . + 'From: user@domain.tld' . "\r\n" . + 'To: user@domain.tld' . "\r\n" . + 'Cc: other@domain.tld' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/related;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/html; charset=utf-8' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'HTML part' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: image/gif; name=image.gif' . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: inline; filename=image.gif' . "\r\n" . + 'Content-ID: <' . preg_quote($imgId, '~') . '>' . "\r\n" . + "\r\n" . + preg_quote(base64_encode(''), '~') . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n" . + '$~D' + ; + + $streamA = new Swift_ByteStream_ArrayByteStream(); + $streamB = new Swift_ByteStream_ArrayByteStream(); + + $message->toByteStream($streamA); + $message->toByteStream($streamB); + + $this->assertPatternInStream($pattern, $streamA); + $this->assertPatternInStream($pattern, $streamB); + } + + public function testWritingMessageToByteStreamTwiceUsingAFileAttachment() + { + $message = new Swift_Message(); + $message->setSubject('test subject'); + $message->setTo('user@domain.tld'); + $message->setCc('other@domain.tld'); + $message->setFrom('user@domain.tld'); + + $attachment = Swift_Attachment::fromPath($this->_attFile); + + $message->attach($attachment); + + $message->setBody('HTML part', 'text/html'); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + + $streamA = new Swift_ByteStream_ArrayByteStream(); + $streamB = new Swift_ByteStream_ArrayByteStream(); + + $pattern = '~^' . + 'Message-ID: <' . $id . '>' . "\r\n" . + 'Date: ' . $date . "\r\n" . + 'Subject: test subject' . "\r\n" . + 'From: user@domain.tld' . "\r\n" . + 'To: user@domain.tld' . "\r\n" . + 'Cc: other@domain.tld' . "\r\n" . + 'MIME-Version: 1.0' . "\r\n" . + 'Content-Type: multipart/mixed;' . "\r\n" . + ' boundary="' . $boundary . '"' . "\r\n" . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: text/html; charset=utf-8' . "\r\n" . + 'Content-Transfer-Encoding: quoted-printable' . "\r\n" . + "\r\n" . + 'HTML part' . + "\r\n\r\n" . + '--' . $boundary . "\r\n" . + 'Content-Type: ' . $this->_attFileType . '; name=' . $this->_attFileName . "\r\n" . + 'Content-Transfer-Encoding: base64' . "\r\n" . + 'Content-Disposition: attachment; filename=' . $this->_attFileName . "\r\n" . + "\r\n" . + preg_quote(base64_encode(file_get_contents($this->_attFile)), '~') . + "\r\n\r\n" . + '--' . $boundary . '--' . "\r\n" . + '$~D' + ; + + $message->toByteStream($streamA); + $message->toByteStream($streamB); + + $this->assertPatternInStream($pattern, $streamA); + $this->assertPatternInStream($pattern, $streamB); + } + + // -- Helpers + + public function assertPatternInStream($pattern, $stream, $message = '%s') + { + $string = ''; + while (false !== $bytes = $stream->read(8192)) { + $string .= $bytes; + } + $this->assertPattern($pattern, $string, $message); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug51Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug51Test.php new file mode 100644 index 0000000..51d69b9 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug51Test.php @@ -0,0 +1,125 @@ +skipUnless( + is_writable(SWIFT_TMP_DIR), + '%s: This test requires tests/acceptance.conf.php to specify a ' . + 'writable SWIFT_TMP_DIR' + ); + } + + public function setUp() + { + $this->_attachmentFile = SWIFT_TMP_DIR . '/attach.rand.bin'; + file_put_contents($this->_attachmentFile, ''); + + $this->_outputFile = SWIFT_TMP_DIR . '/attach.out.bin'; + file_put_contents($this->_outputFile, ''); + } + + public function tearDown() + { + unlink($this->_attachmentFile); + unlink($this->_outputFile); + } + + public function testAttachmentsDoNotGetTruncatedUsingToByteStream() + { + //Run 100 times with 10KB attachments + for ($i = 0; $i < 10; ++$i) { + $message = $this->_createMessageWithRandomAttachment( + 10000, $this->_attachmentFile + ); + + file_put_contents($this->_outputFile, ''); + $message->toByteStream( + new Swift_ByteStream_FileByteStream($this->_outputFile, true) + ); + + $emailSource = file_get_contents($this->_outputFile); + + $this->assertAttachmentFromSourceMatches( + file_get_contents($this->_attachmentFile), + $emailSource + ); + } + } + + public function testAttachmentsDoNotGetTruncatedUsingToString() + { + //Run 100 times with 10KB attachments + for ($i = 0; $i < 10; ++$i) { + $message = $this->_createMessageWithRandomAttachment( + 10000, $this->_attachmentFile + ); + + $emailSource = $message->toString(); + + $this->assertAttachmentFromSourceMatches( + file_get_contents($this->_attachmentFile), + $emailSource + ); + } + } + + // -- Custom Assertions + + public function assertAttachmentFromSourceMatches($attachmentData, $source) + { + $encHeader = 'Content-Transfer-Encoding: base64'; + $base64declaration = strpos($source, $encHeader); + + $attachmentDataStart = strpos($source, "\r\n\r\n", $base64declaration); + $attachmentDataEnd = strpos($source, "\r\n--", $attachmentDataStart); + + if (false === $attachmentDataEnd) { + $attachmentBase64 = trim(substr($source, $attachmentDataStart)); + } else { + $attachmentBase64 = trim(substr( + $source, $attachmentDataStart, + $attachmentDataEnd - $attachmentDataStart + )); + } + + $this->assertIdenticalBinary($attachmentData, base64_decode($attachmentBase64)); + } + + // -- Creation Methods + + private function _fillFileWithRandomBytes($byteCount, $file) + { + // I was going to use dd with if=/dev/random but this way seems more + // cross platform even if a hella expensive!! + + file_put_contents($file, ''); + $fp = fopen($file, 'wb'); + for ($i = 0; $i < $byteCount; ++$i) { + $byteVal = rand(0, 255); + fwrite($fp, pack('i', $byteVal)); + } + fclose($fp); + } + + private function _createMessageWithRandomAttachment($size, $attachmentPath) + { + $this->_fillFileWithRandomBytes($size, $attachmentPath); + + $message = Swift_Message::newInstance() + ->setSubject('test') + ->setBody('test') + ->setFrom('a@b.c') + ->setTo('d@e.f') + ->attach(Swift_Attachment::fromPath($attachmentPath)) + ; + + return $message; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug71Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug71Test.php new file mode 100644 index 0000000..d00ebaf --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug71Test.php @@ -0,0 +1,22 @@ +_message = new Swift_Message('test'); + } + + public function testCallingToStringAfterSettingNewBodyReflectsChanges() + { + $this->_message->setBody('BODY1'); + $this->assertPattern('/BODY1/', $this->_message->toString()); + + $this->_message->setBody('BODY2'); + $this->assertPattern('/BODY2/', $this->_message->toString()); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug76Test.php b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug76Test.php new file mode 100644 index 0000000..ce8714b --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug76Test.php @@ -0,0 +1,86 @@ +skipUnless( + is_writable(SWIFT_TMP_DIR), + '%s: This test requires tests/acceptance.conf.php to specify a ' . + 'writable SWIFT_TMP_DIR' + ); + } + + public function setUp() + { + $this->_inputFile = SWIFT_TMP_DIR . '/in.bin'; + file_put_contents($this->_inputFile, ''); + + $this->_outputFile = SWIFT_TMP_DIR . '/out.bin'; + file_put_contents($this->_outputFile, ''); + + $this->_encoder = $this->_createEncoder(); + } + + public function tearDown() + { + unlink($this->_inputFile); + unlink($this->_outputFile); + } + + public function testBase64EncodedLineLengthNeverExceeds76CharactersEvenIfArgsDo() + { + $this->_fillFileWithRandomBytes(1000, $this->_inputFile); + + $os = $this->_createStream($this->_inputFile); + $is = $this->_createStream($this->_outputFile); + + $this->_encoder->encodeByteStream($os, $is, 0, 80); //Exceeds 76 + + $this->assertMaxLineLength(76, $this->_outputFile, + '%s: Line length should not exceed 76 characters' + ); + } + + // -- Custom Assertions + + public function assertMaxLineLength($length, $filePath, $message = '%s') + { + $lines = file($filePath); + foreach ($lines as $line) { + $this->assertTrue((strlen(trim($line)) <= 76), $message); + } + } + + // -- Creation Methods + + private function _fillFileWithRandomBytes($byteCount, $file) + { + // I was going to use dd with if=/dev/random but this way seems more + // cross platform even if a hella expensive!! + + file_put_contents($file, ''); + $fp = fopen($file, 'wb'); + for ($i = 0; $i < $byteCount; ++$i) { + $byteVal = rand(0, 255); + fwrite($fp, pack('i', $byteVal)); + } + fclose($fp); + } + + private function _createEncoder() + { + return new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + } + + private function _createStream($file) + { + return new Swift_ByteStream_FileByteStream($file, true); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/helpers/Swift/Tests/IdenticalBinaryExpectation.php b/vendor/swiftmailer/swiftmailer/tests/helpers/Swift/Tests/IdenticalBinaryExpectation.php new file mode 100644 index 0000000..e50c271 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/helpers/Swift/Tests/IdenticalBinaryExpectation.php @@ -0,0 +1,79 @@ +_left = $left; + } + + /** + * Get the given string of bytes as a stirng of Hexadecimal sequences. + * @param string $binary + * @return string + */ + public function asHexString($binary) + { + $hex = ''; + + $bytes = unpack('H*', $binary); + + foreach ($bytes as &$byte) { + $byte = strtoupper($byte); + } + + return implode('', $bytes); + } + + /** + * Test that the passed subject ($right) is identical to $left. + * @param string $right, subject + * @return boolean + */ + public function test($right) + { + $aHex = $this->asHexString($this->_left); + $bHex = $this->asHexString($right); + + return $aHex === $bHex; + } + + /** + * Get the message depending upon whether this expectation is satisfied. + * @param $right subject to compare against + * @return string + */ + public function testMessage($right) + { + if ($this->test($right)) { + return 'Identical binary expectation [' . $this->asHexString($right) . ']'; + } else { + $this->_dumper=new SimpleDumper(); + + return 'Identical binary expectation fails ' . + $this->_dumper->describeDifference( + $this->asHexString($this->_left), + $this->asHexString($right) + ); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/helpers/Swift/Tests/SwiftSmokeTestCase.php b/vendor/swiftmailer/swiftmailer/tests/helpers/Swift/Tests/SwiftSmokeTestCase.php new file mode 100644 index 0000000..e85563e --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/helpers/Swift/Tests/SwiftSmokeTestCase.php @@ -0,0 +1,53 @@ +skipUnless(SWIFT_SMOKE_TRANSPORT_TYPE, + '%s: Smoke tests are skipped if tests/smoke.conf.php is not editted' + ); + } + + protected function _getMailer() + { + switch (SWIFT_SMOKE_TRANSPORT_TYPE) { + case 'smtp': + $transport = Swift_DependencyContainer::getInstance()->lookup('transport.smtp') + ->setHost(SWIFT_SMOKE_SMTP_HOST) + ->setPort(SWIFT_SMOKE_SMTP_PORT) + ->setUsername(SWIFT_SMOKE_SMTP_USER) + ->setPassword(SWIFT_SMOKE_SMTP_PASS) + ->setEncryption(SWIFT_SMOKE_SMTP_ENCRYPTION) + ; + break; + case 'sendmail': + $transport = Swift_DependencyContainer::getInstance()->lookup('transport.sendmail') + ->setCommand(SWIFT_SMOKE_SENDMAIL_COMMAND) + ; + break; + case 'mail': + case 'nativemail': + $transport = Swift_DependencyContainer::getInstance()->lookup('transport.mail'); + break; + default: + throw new Exception('Undefined transport [' . SWIFT_SMOKE_TRANSPORT_TYPE . ']'); + } + + return new Swift_Mailer($transport); + } + + protected function _visualCheck($url) + { + $this->dump('{image @ ' . $url . '}'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/helpers/Swift/Tests/SwiftUnitTestCase.php b/vendor/swiftmailer/swiftmailer/tests/helpers/Swift/Tests/SwiftUnitTestCase.php new file mode 100644 index 0000000..93524ba --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/helpers/Swift/Tests/SwiftUnitTestCase.php @@ -0,0 +1,100 @@ +_mockery()->assertIsSatisfied(); + } catch (Yay_NotSatisfiedException $e) { + $this->fail($e->getMessage()); + } + $this->_mockery = null; + + return parent::after($method); + } + + /** + * Assert two binary strings are an exact match. + * @param string $a + * @param string $b + * @param string $s formatted message + */ + public function assertIdenticalBinary($a, $b, $s = '%s') + { + return $this->assert(new Swift_Tests_IdenticalBinaryExpectation($a), $b, $s); + } + + // -- Protected methods + + /** + * Returns a singleton-per-test method for Yay_Mockery. + * @return Yay_Mockery + */ + protected function _mockery() + { + if (!isset($this->_mockery)) { + $this->_mockery = new Yay_Mockery(); + } + + return $this->_mockery; + } + + /** + * Create a mock object. + * @param string $class + * @return Yay_Mock + */ + protected function _mock($class) + { + return $this->_mockery()->mock($class); + } + + /** + * Add mock expectations. + * @param Yay_Expectations $expectations + */ + protected function _checking($expectations) + { + return $this->_mockery()->checking($expectations); + } + + /** + * Create a mock object which does nothing. + * @param string $class + * @return Yay_Mock + */ + protected function _stub($class) + { + $stub = $this->_mockery()->mock($class); + $this->_mockery()->checking(Yay_Expectations::create() + -> ignoring($stub) + ); + + return $stub; + } + + protected function _states($machineName) + { + return $this->_mockery()->states($machineName); + } + + protected function _sequence($sequenceName) + { + return $this->_mockery()->sequence($sequenceName); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/smoke.conf.php.default b/vendor/swiftmailer/swiftmailer/tests/smoke.conf.php.default new file mode 100644 index 0000000..2a75b7d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/smoke.conf.php.default @@ -0,0 +1,63 @@ +_attFile = dirname(__FILE__) . '/../../../_samples/files/textfile.zip'; + } + + public function testAttachmentSending() + { + $mailer = $this->_getMailer(); + $message = Swift_Message::newInstance() + ->setSubject('[Swift Mailer] AttachmentSmokeTest') + ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'Swift Mailer')) + ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS) + ->setBody('This message should contain an attached ZIP file (named "textfile.zip").' . PHP_EOL . + 'When unzipped, the archive should produce a text file which reads:' . PHP_EOL . + '"This is part of a Swift Mailer v4 smoke test."' + ) + ->attach(Swift_Attachment::fromPath($this->_attFile)) + ; + $this->assertEqual(1, $mailer->send($message), + '%s: The smoke test should send a single message' + ); + $this->_visualCheck('http://swiftmailer.org/smoke/4.0.0/attachment.jpg'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/BasicSmokeTest.php b/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/BasicSmokeTest.php new file mode 100644 index 0000000..1436a22 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/BasicSmokeTest.php @@ -0,0 +1,24 @@ +_getMailer(); + $message = Swift_Message::newInstance() + ->setSubject('[Swift Mailer] BasicSmokeTest') + ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'Swift Mailer')) + ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS) + ->setBody('One, two, three, four, five...' . PHP_EOL . + 'six, seven, eight...' + ) + ; + $this->assertEqual(1, $mailer->send($message), + '%s: The smoke test should send a single message' + ); + $this->_visualCheck('http://swiftmailer.org/smoke/4.0.0/basic.jpg'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/HtmlWithAttachmentSmokeTest.php b/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/HtmlWithAttachmentSmokeTest.php new file mode 100644 index 0000000..e6fa4a8 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/HtmlWithAttachmentSmokeTest.php @@ -0,0 +1,30 @@ +_attFile = dirname(__FILE__) . '/../../../_samples/files/textfile.zip'; + } + + public function testAttachmentSending() + { + $mailer = $this->_getMailer(); + $message = Swift_Message::newInstance('[Swift Mailer] HtmlWithAttachmentSmokeTest') + ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'Swift Mailer')) + ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS) + ->attach(Swift_Attachment::fromPath($this->_attFile)) + ->setBody('

    This HTML-formatted message should contain an attached ZIP file (named "textfile.zip").' . PHP_EOL . + 'When unzipped, the archive should produce a text file which reads:

    ' . PHP_EOL . + '

    This is part of a Swift Mailer v4 smoke test.

    ', 'text/html' + ) + ; + $this->assertEqual(1, $mailer->send($message), + '%s: The smoke test should send a single message' + ); + $this->_visualCheck('http://swiftmailer.org/smoke/4.0.0/attachment.jpg'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/InternationalSmokeTest.php b/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/InternationalSmokeTest.php new file mode 100644 index 0000000..e3131c8 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/InternationalSmokeTest.php @@ -0,0 +1,37 @@ +_attFile = dirname(__FILE__) . '/../../../_samples/files/textfile.zip'; + } + + public function testAttachmentSending() + { + $mailer = $this->_getMailer(); + $message = Swift_Message::newInstance() + ->setCharset('utf-8') + ->setSubject('[Swift Mailer] InternationalSmokeTest (διεθνής)') + ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'ΧÏιστοφοÏου (Swift Mailer)')) + ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS) + ->setBody('This message should contain an attached ZIP file (named "κείμενο, εδάφιο, θέμα.zip").' . PHP_EOL . + 'When unzipped, the archive should produce a text file which reads:' . PHP_EOL . + '"This is part of a Swift Mailer v4 smoke test."' . PHP_EOL . + PHP_EOL . + 'Following is some arbitrary Greek text:' . PHP_EOL . + 'Δεν βÏέθηκαν λέξεις.' + ) + ->attach(Swift_Attachment::fromPath($this->_attFile) + ->setContentType('application/zip') + ->setFilename('κείμενο, εδάφιο, θέμα.zip') + ) + ; + $this->assertEqual(1, $mailer->send($message), + '%s: The smoke test should send a single message' + ); + $this->_visualCheck('http://swiftmailer.org/smoke/4.0.0/international.jpg'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/ByteStream/ArrayByteStreamTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/ByteStream/ArrayByteStreamTest.php new file mode 100644 index 0000000..80ca0c3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/ByteStream/ArrayByteStreamTest.php @@ -0,0 +1,204 @@ +_createArrayStream($input); + $output = array(); + while (false !== $bytes = $bs->read(1)) { + $output[] = $bytes; + } + $this->assertEqual($input, $output, + '%s: Bytes read from stream should be the same as bytes in constructor' + ); + } + + public function testReadingMultipleBytesFromBaseInput() + { + $input = array('a', 'b', 'c', 'd'); + $bs = $this->_createArrayStream($input); + $output = array(); + while (false !== $bytes = $bs->read(2)) { + $output[] = $bytes; + } + $this->assertEqual(array('ab', 'cd'), $output, + '%s: Bytes read from stream should be in pairs' + ); + } + + public function testReadingOddOffsetOnLastByte() + { + $input = array('a', 'b', 'c', 'd', 'e'); + $bs = $this->_createArrayStream($input); + $output = array(); + while (false !== $bytes = $bs->read(2)) { + $output[] = $bytes; + } + $this->assertEqual(array('ab', 'cd', 'e'), $output, + '%s: Bytes read from stream should be in pairs except final read' + ); + } + + public function testSettingPointerPartway() + { + $input = array('a', 'b', 'c'); + $bs = $this->_createArrayStream($input); + $bs->setReadPointer(1); + $this->assertEqual('b', $bs->read(1), + '%s: Byte should be second byte since pointer as at offset 1' + ); + } + + public function testResettingPointerAfterExhaustion() + { + $input = array('a', 'b', 'c'); + $bs = $this->_createArrayStream($input); + + while (false !== $bs->read(1)); + + $bs->setReadPointer(0); + $this->assertEqual('a', $bs->read(1), + '%s: Byte should be first byte since pointer as at offset 0' + ); + } + + public function testPointerNeverSetsBelowZero() + { + $input = array('a', 'b', 'c'); + $bs = $this->_createArrayStream($input); + + $bs->setReadPointer(-1); + $this->assertEqual('a', $bs->read(1), + '%s: Byte should be first byte since pointer should be at offset 0' + ); + } + + public function testPointerNeverSetsAboveStackSize() + { + $input = array('a', 'b', 'c'); + $bs = $this->_createArrayStream($input); + + $bs->setReadPointer(3); + $this->assertIdentical(false, $bs->read(1), + '%s: Stream should be at end and thus return false' + ); + } + + public function testBytesCanBeWrittenToStream() + { + $input = array('a', 'b', 'c'); + $bs = $this->_createArrayStream($input); + + $bs->write('de'); + + $output = array(); + while (false !== $bytes = $bs->read(1)) { + $output[] = $bytes; + } + $this->assertEqual(array('a', 'b', 'c', 'd', 'e'), $output, + '%s: Bytes read from stream should be from initial stack + written' + ); + } + + public function testContentsCanBeFlushed() + { + $input = array('a', 'b', 'c'); + $bs = $this->_createArrayStream($input); + + $bs->flushBuffers(); + + $this->assertIdentical(false, $bs->read(1), + '%s: Contents have been flushed so read() should return false' + ); + } + + public function testConstructorCanTakeStringArgument() + { + $bs = $this->_createArrayStream('abc'); + $output = array(); + while (false !== $bytes = $bs->read(1)) { + $output[] = $bytes; + } + $this->assertEqual(array('a', 'b', 'c'), $output, + '%s: Bytes read from stream should be the same as bytes in constructor' + ); + } + + public function testBindingOtherStreamsMirrorsWriteOperations() + { + $bs = $this->_createArrayStream(''); + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $this->_checking(Expectations::create() + -> one($is1)->write('x') + -> one($is2)->write('x') + -> one($is1)->write('y') + -> one($is2)->write('y') + ); + + $bs->bind($is1); + $bs->bind($is2); + + $bs->write('x'); + $bs->write('y'); + } + + public function testBindingOtherStreamsMirrorsFlushOperations() + { + $bs = $this->_createArrayStream(''); + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $this->_checking(Expectations::create() + -> one($is1)->flushBuffers() + -> one($is2)->flushBuffers() + ); + + $bs->bind($is1); + $bs->bind($is2); + + $bs->flushBuffers(); + } + + public function testUnbindingStreamPreventsFurtherWrites() + { + $bs = $this->_createArrayStream(''); + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $this->_checking(Expectations::create() + -> one($is1)->write('x') + -> one($is2)->write('x') + -> one($is1)->write('y') + ); + + $bs->bind($is1); + $bs->bind($is2); + + $bs->write('x'); + + $bs->unbind($is2); + + $bs->write('y'); + } + + // -- Creation Methods + + private function _createArrayStream($input) + { + return new Swift_ByteStream_ArrayByteStream($input); + } + + private function _createMockInputStream() + { + return $this->_mock('Swift_InputByteStream'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/GenericFixedWidthReaderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/GenericFixedWidthReaderTest.php new file mode 100644 index 0000000..130c47f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/GenericFixedWidthReaderTest.php @@ -0,0 +1,46 @@ +assertIdentical(1, $reader->getInitialByteSize()); + + $reader = new Swift_CharacterReader_GenericFixedWidthReader(4); + $this->assertIdentical(4, $reader->getInitialByteSize()); + } + + public function testValidationValueIsBasedOnOctetCount() + { + $reader = new Swift_CharacterReader_GenericFixedWidthReader(4); + + $this->assertIdentical( + 1, $reader->validateByteSequence(array(0x01, 0x02, 0x03), 3) + ); //3 octets + + $this->assertIdentical( + 2, $reader->validateByteSequence(array(0x01, 0x0A), 2) + ); //2 octets + + $this->assertIdentical( + 3, $reader->validateByteSequence(array(0xFE), 1) + ); //1 octet + + $this->assertIdentical( + 0, $reader->validateByteSequence(array(0xFE, 0x03, 0x67, 0x9A), 4) + ); //All 4 octets + } + + public function testValidationFailsIfTooManyOctets() + { + $reader = new Swift_CharacterReader_GenericFixedWidthReader(6); + + $this->assertIdentical(-1, $reader->validateByteSequence( + array(0xFE, 0x03, 0x67, 0x9A, 0x10, 0x09, 0x85), 7 + )); //7 octets + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/UsAsciiReaderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/UsAsciiReaderTest.php new file mode 100644 index 0000000..7611262 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/UsAsciiReaderTest.php @@ -0,0 +1,55 @@ +read($size); ) { + $c .= $bytes; + $size = $v->validateCharacter($c); + if (-1 == $size) { + throw new Exception( ... invalid char .. ); + } elseif (0 == $size) { + return $c; //next character in $os + } + } + + */ + + private $_reader; + + public function setUp() + { + $this->_reader = new Swift_CharacterReader_UsAsciiReader(); + } + + public function testAllValidAsciiCharactersReturnZero() + { + for ($ordinal = 0x00; $ordinal <= 0x7F; ++$ordinal) { + $this->assertIdentical( + 0, $this->_reader->validateByteSequence(array($ordinal), 1) + ); + } + } + + public function testMultipleBytesAreInvalid() + { + for ($ordinal = 0x00; $ordinal <= 0x7F; $ordinal += 2) { + $this->assertIdentical( + -1, $this->_reader->validateByteSequence(array($ordinal, $ordinal + 1), 2) + ); + } + } + + public function testBytesAboveAsciiRangeAreInvalid() + { + for ($ordinal = 0x80; $ordinal <= 0xFF; ++$ordinal) { + $this->assertIdentical( + -1, $this->_reader->validateByteSequence(array($ordinal), 1) + ); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/Utf8ReaderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/Utf8ReaderTest.php new file mode 100644 index 0000000..02e21b1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/Utf8ReaderTest.php @@ -0,0 +1,68 @@ +_reader = new Swift_CharacterReader_Utf8Reader(); + } + + public function testLeading7BitOctetCausesReturnZero() + { + for ($ordinal = 0x00; $ordinal <= 0x7F; ++$ordinal) { + $this->assertIdentical( + 0, $this->_reader->validateByteSequence(array($ordinal), 1) + ); + } + } + + public function testLeadingByteOf2OctetCharCausesReturn1() + { + for ($octet = 0xC0; $octet <= 0xDF; ++$octet) { + $this->assertIdentical( + 1, $this->_reader->validateByteSequence(array($octet), 1) + ); + } + } + + public function testLeadingByteOf3OctetCharCausesReturn2() + { + for ($octet = 0xE0; $octet <= 0xEF; ++$octet) { + $this->assertIdentical( + 2, $this->_reader->validateByteSequence(array($octet), 1) + ); + } + } + + public function testLeadingByteOf4OctetCharCausesReturn3() + { + for ($octet = 0xF0; $octet <= 0xF7; ++$octet) { + $this->assertIdentical( + 3, $this->_reader->validateByteSequence(array($octet), 1) + ); + } + } + + public function testLeadingByteOf5OctetCharCausesReturn4() + { + for ($octet = 0xF8; $octet <= 0xFB; ++$octet) { + $this->assertIdentical( + 4, $this->_reader->validateByteSequence(array($octet),1) + ); + } + } + + public function testLeadingByteOf6OctetCharCausesReturn5() + { + for ($octet = 0xFC; $octet <= 0xFD; ++$octet) { + $this->assertIdentical( + 5, $this->_reader->validateByteSequence(array($octet),1) + ); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterStream/ArrayCharacterStreamTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterStream/ArrayCharacterStreamTest.php new file mode 100644 index 0000000..f3e8297 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterStream/ArrayCharacterStreamTest.php @@ -0,0 +1,375 @@ +_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-sequence'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importString(pack('C*', + 0xD0, 0x94, + 0xD0, 0xB6, + 0xD0, 0xBE, + 0xD1, 0x8D, + 0xD0, 0xBB, + 0xD0, 0xB0 + ) + ); + } + + public function testCharactersWrittenUseValidator() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-sequence'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $stream->write(pack('C*', + 0xD0, 0xBB, + 0xD1, 0x8E, + 0xD0, 0xB1, + 0xD1, 0x8B, + 0xD1, 0x85 + ) + ); + } + + public function testReadCharactersAreInTact() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-sequence'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + //String + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + //Stream + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $stream->write(pack('C*', + 0xD0, 0xBB, + 0xD1, 0x8E, + 0xD0, 0xB1, + 0xD1, 0x8B, + 0xD1, 0x85 + ) + ); + + $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1)); + $this->assertIdenticalBinary( + pack('C*', 0xD0, 0xB6, 0xD0, 0xBE), $stream->read(2) + ); + $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBB), $stream->read(1)); + $this->assertIdenticalBinary( + pack('C*', 0xD1, 0x8E, 0xD0, 0xB1, 0xD1, 0x8B), $stream->read(3) + ); + $this->assertIdenticalBinary(pack('C*', 0xD1, 0x85), $stream->read(1)); + + $this->assertIdentical(false, $stream->read(1)); + } + + public function testCharactersCanBeReadAsByteArrays() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-sequence'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + //String + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + //Stream + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD1), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $stream->write(pack('C*', + 0xD0, 0xBB, + 0xD1, 0x8E, + 0xD0, 0xB1, + 0xD1, 0x8B, + 0xD1, 0x85 + ) + ); + + $this->assertEqual(array(0xD0, 0x94), $stream->readBytes(1)); + $this->assertEqual(array(0xD0, 0xB6, 0xD0, 0xBE), $stream->readBytes(2)); + $this->assertEqual(array(0xD0, 0xBB), $stream->readBytes(1)); + $this->assertEqual( + array(0xD1, 0x8E, 0xD0, 0xB1, 0xD1, 0x8B), $stream->readBytes(3) + ); + $this->assertEqual(array(0xD1, 0x85), $stream->readBytes(1)); + + $this->assertIdentical(false, $stream->readBytes(1)); + } + + public function testRequestingLargeCharCountPastEndOfStream() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-sequence'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE), + $stream->read(100) + ); + + $this->assertIdentical(false, $stream->read(1)); + } + + public function testRequestingByteArrayCountPastEndOfStream() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-sequence'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $this->assertEqual(array(0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE), + $stream->readBytes(100) + ); + + $this->assertIdentical(false, $stream->readBytes(1)); + } + + public function testPointerOffsetCanBeSet() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-sequence'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1)); + + $stream->setPointer(0); + + $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1)); + + $stream->setPointer(2); + + $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBE), $stream->read(1)); + } + + public function testContentsCanBeFlushed() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-sequence'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $stream->flushContents(); + + $this->assertIdentical(false, $stream->read(1)); + } + + public function testByteStreamCanBeImportingUsesValidator() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + $os = $this->_getByteStream(); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-stream'); + $this->_checking(Expectations::create() + -> between(0,1)->of($os)->setReadPointer(0) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xD0)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0x94)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xD0)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xB6)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xD0)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xBE)) + -> ignoring($os)->read(any()) -> returns(false) + ); + + $seq = $this->_mockery()->sequence('read-chars'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importByteStream($os); + } + + public function testImportingStreamProducesCorrectCharArray() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + $os = $this->_getByteStream(); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $seq = $this->_mockery()->sequence('read-stream'); + $this->_checking(Expectations::create() + -> between(0,1)->of($os)->setReadPointer(0) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xD0)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0x94)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xD0)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xB6)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xD0)) + -> one($os)->read(any()) -> inSequence($seq) -> returns(pack('C*', 0xBE)) + -> ignoring($os)->read(any()) -> returns(false) + ); + + $seq = $this->_mockery()->sequence('read-chars'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + -> one($reader)->validateByteSequence(array(0xD0), 1) -> inSequence($seq) -> returns(1) + ); + + $stream->importByteStream($os); + + $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1)); + $this->assertIdenticalBinary(pack('C*', 0xD0, 0xB6), $stream->read(1)); + $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBE), $stream->read(1)); + + $this->assertIdentical(false, $stream->read(1)); + } + + public function testAlgorithmWithFixedWidthCharsets() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $seq = $this->_mockery()->sequence('read-chars'); + $this->_checking(Expectations::create() + -> ignoring($reader)->getInitialByteSize() -> returns(2) + -> one($reader)->validateByteSequence(array(0xD1, 0x8D), 2) -> inSequence($seq) + -> one($reader)->validateByteSequence(array(0xD0, 0xBB), 2) -> inSequence($seq) + -> one($reader)->validateByteSequence(array(0xD0, 0xB0), 2) -> inSequence($seq) + ); + + $stream = new Swift_CharacterStream_ArrayCharacterStream( + $factory, 'utf-8' + ); + $stream->importString(pack('C*', 0xD1, 0x8D, 0xD0, 0xBB, 0xD0, 0xB0)); + + $this->assertIdenticalBinary(pack('C*', 0xD1, 0x8D), $stream->read(1)); + $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBB), $stream->read(1)); + $this->assertIdenticalBinary(pack('C*', 0xD0, 0xB0), $stream->read(1)); + + $this->assertIdentical(false, $stream->read(1)); + } + + // -- Creation methods + + private function _getReader() + { + return $this->_mock('Swift_CharacterReader'); + } + + private function _getFactory($reader) + { + $factory = $this->_mock('Swift_CharacterReaderFactory'); + $this->_checking(Expectations::create() + -> allowing($factory)->getReaderFor('utf-8') -> returns($reader) + ); + + return $factory; + } + + private function _getByteStream() + { + return $this->_mock('Swift_OutputByteStream'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/DependencyContainerTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/DependencyContainerTest.php new file mode 100644 index 0000000..4037e16 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/DependencyContainerTest.php @@ -0,0 +1,177 @@ +arg1 = $arg1; + $this->arg2 = $arg2; + } +} + +class Swift_DependencyContainerTest extends Swift_Tests_SwiftUnitTestCase +{ + private $_container; + + public function setUp() + { + $this->_container = new Swift_DependencyContainer(); + } + + public function testRegisterAndLookupValue() + { + $this->_container->register('foo')->asValue('bar'); + $this->assertIdentical('bar', $this->_container->lookup('foo')); + } + + public function testHasReturnsTrueForRegisteredValue() + { + $this->_container->register('foo')->asValue('bar'); + $this->assertTrue($this->_container->has('foo')); + } + + public function testHasReturnsFalseForUnregisteredValue() + { + $this->assertFalse($this->_container->has('foo')); + } + + public function testRegisterAndLookupNewInstance() + { + $this->_container->register('one')->asNewInstanceOf('One'); + $this->assertIsA($this->_container->lookup('one'), 'One'); + } + + public function testHasReturnsTrueForRegisteredInstance() + { + $this->_container->register('one')->asNewInstanceOf('One'); + $this->assertTrue($this->_container->has('one')); + } + + public function testNewInstanceIsAlwaysNew() + { + $this->_container->register('one')->asNewInstanceOf('One'); + $a = $this->_container->lookup('one'); + $b = $this->_container->lookup('one'); + $this->assertClone($a, $b); + } + + public function testRegisterAndLookupSharedInstance() + { + $this->_container->register('one')->asSharedInstanceOf('One'); + $this->assertIsA($this->_container->lookup('one'), 'One'); + } + + public function testHasReturnsTrueForSharedInstance() + { + $this->_container->register('one')->asSharedInstanceOf('One'); + $this->assertTrue($this->_container->has('one')); + } + + public function testMultipleSharedInstancesAreSameInstance() + { + $this->_container->register('one')->asSharedInstanceOf('One'); + $a = $this->_container->lookup('one'); + $b = $this->_container->lookup('one'); + $this->assertSame($a, $b); + } + + public function testNewInstanceWithDependencies() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('one')->asNewInstanceOf('One') + ->withDependencies(array('foo')); + $obj = $this->_container->lookup('one'); + $this->assertIdentical('FOO', $obj->arg1); + } + + public function testNewInstanceWithMultipleDependencies() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('bar')->asValue(42); + $this->_container->register('one')->asNewInstanceOf('One') + ->withDependencies(array('foo', 'bar')); + $obj = $this->_container->lookup('one'); + $this->assertIdentical('FOO', $obj->arg1); + $this->assertIdentical(42, $obj->arg2); + } + + public function testNewInstanceWithInjectedObjects() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('one')->asNewInstanceOf('One'); + $this->_container->register('two')->asNewInstanceOf('One') + ->withDependencies(array('one', 'foo')); + $obj = $this->_container->lookup('two'); + $this->assertClone($this->_container->lookup('one'), $obj->arg1); + $this->assertIdentical('FOO', $obj->arg2); + } + + public function testNewInstanceWithAddConstructorValue() + { + $this->_container->register('one')->asNewInstanceOf('One') + ->addConstructorValue('x') + ->addConstructorValue(99); + $obj = $this->_container->lookup('one'); + $this->assertIdentical('x', $obj->arg1); + $this->assertIdentical(99, $obj->arg2); + } + + public function testNewInstanceWithAddConstructorLookup() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('bar')->asValue(42); + $this->_container->register('one')->asNewInstanceOf('One') + ->addConstructorLookup('foo') + ->addConstructorLookup('bar'); + + $obj = $this->_container->lookup('one'); + $this->assertIdentical('FOO', $obj->arg1); + $this->assertIdentical(42, $obj->arg2); + } + + public function testResolvedDependenciesCanBeLookedUp() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('one')->asNewInstanceOf('One'); + $this->_container->register('two')->asNewInstanceOf('One') + ->withDependencies(array('one', 'foo')); + $deps = $this->_container->createDependenciesFor('two'); + $this->assertEqual( + array($this->_container->lookup('one'), 'FOO'), $deps + ); + } + + public function testArrayOfDependenciesCanBeSpecified() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('one')->asNewInstanceOf('One'); + $this->_container->register('two')->asNewInstanceOf('One') + ->withDependencies(array(array('one', 'foo'), 'foo')); + + $obj = $this->_container->lookup('two'); + $this->assertEqual(array($this->_container->lookup('one'), 'FOO'), $obj->arg1); + $this->assertIdentical('FOO', $obj->arg2); + } + + public function testAliasCanBeSet() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('bar')->asAliasOf('foo'); + + $this->assertIdentical('FOO', $this->_container->lookup('bar')); + } + + public function testAliasOfAliasCanBeSet() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('bar')->asAliasOf('foo'); + $this->_container->register('zip')->asAliasOf('bar'); + $this->_container->register('button')->asAliasOf('zip'); + + $this->assertIdentical('FOO', $this->_container->lookup('button')); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Base64EncoderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Base64EncoderTest.php new file mode 100644 index 0000000..1a271ac --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Base64EncoderTest.php @@ -0,0 +1,175 @@ +_encoder = new Swift_Encoder_Base64Encoder(); + } + + /* + There's really no point in testing the entire base64 encoding to the + level QP encoding has been tested. base64_encode() has been in PHP for + years. + */ + + public function testInputOutputRatioIs3to4Bytes() + { + /* + RFC 2045, 6.8 + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + */ + + $this->assertEqual( + 'MTIz', $this->_encoder->encodeString('123'), + '%s: 3 bytes of input should yield 4 bytes of output' + ); + $this->assertEqual( + 'MTIzNDU2', $this->_encoder->encodeString('123456'), + '%s: 6 bytes in input should yield 8 bytes of output' + ); + $this->assertEqual( + 'MTIzNDU2Nzg5', $this->_encoder->encodeString('123456789'), + '%s: 9 bytes in input should yield 12 bytes of output' + ); + } + + public function testPadLength() + { + /* + RFC 2045, 6.8 + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a body. When fewer than 24 input bits + are available in an input group, zero bits are added (on the right) + to form an integral number of 6-bit groups. Padding at the end of + the data is performed using the "=" character. Since all base64 + input is an integral number of octets, only the following cases can + arise: (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded output will be + an integral multiple of 4 characters with no "=" padding, (2) the + final quantum of encoding input is exactly 8 bits; here, the final + unit of encoded output will be two characters followed by two "=" + padding characters, or (3) the final quantum of encoding input is + exactly 16 bits; here, the final unit of encoded output will be three + characters followed by one "=" padding character. + */ + + for ($i = 0; $i < 30; ++$i) { + $input = pack('C', rand(0, 255)); + $this->assertPattern( + '~^[a-zA-Z0-9/\+]{2}==$~', $this->_encoder->encodeString($input), + '%s: A single byte should have 2 bytes of padding' + ); + } + + for ($i = 0; $i < 30; ++$i) { + $input = pack('C*', rand(0, 255), rand(0, 255)); + $this->assertPattern( + '~^[a-zA-Z0-9/\+]{3}=$~', $this->_encoder->encodeString($input), + '%s: Two bytes should have 1 byte of padding' + ); + } + + for ($i = 0; $i < 30; ++$i) { + $input = pack('C*', rand(0, 255), rand(0, 255), rand(0, 255)); + $this->assertPattern( + '~^[a-zA-Z0-9/\+]{4}$~', $this->_encoder->encodeString($input), + '%s: Three bytes should have no padding' + ); + } + } + + public function testMaximumLineLengthIs76Characters() + { + /* + The encoded output stream must be represented in lines of no more + than 76 characters each. All line breaks or other characters not + found in Table 1 must be ignored by decoding software. + */ + + $input = + 'abcdefghijklmnopqrstuvwxyz' . + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . + '1234567890' . + 'abcdefghijklmnopqrstuvwxyz' . + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . + '1234567890' . + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + $output = + 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQk' . //38 + 'NERUZHSElKS0xNTk9QUVJTVFVWV1hZWjEyMzQ1' . "\r\n" . //76 * + 'Njc4OTBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3' . //38 + 'h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFla' . "\r\n" . //76 * + 'MTIzNDU2Nzg5MEFCQ0RFRkdISUpLTE1OT1BRUl' . //38 + 'NUVVZXWFla'; //48 + + $this->assertEqual( + $output, $this->_encoder->encodeString($input), + '%s: Lines should be no more than 76 characters' + ); + } + + public function testMaximumLineLengthCanBeSpecified() + { + $input = + 'abcdefghijklmnopqrstuvwxyz' . + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . + '1234567890' . + 'abcdefghijklmnopqrstuvwxyz' . + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . + '1234567890' . + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + $output = + 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQk' . //38 + 'NERUZHSElKS0' . "\r\n" . //50 * + 'xNTk9QUVJTVFVWV1hZWjEyMzQ1Njc4OTBhYmNk' . //38 + 'ZWZnaGlqa2xt' . "\r\n" . //50 * + 'bm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1' . //38 + 'BRUlNUVVZXWF' . "\r\n" . //50 * + 'laMTIzNDU2Nzg5MEFCQ0RFRkdISUpLTE1OT1BR' . //38 + 'UlNUVVZXWFla'; //50 * + + $this->assertEqual( + $output, $this->_encoder->encodeString($input, 0, 50), + '%s: Lines should be no more than 100 characters' + ); + } + + public function testFirstLineLengthCanBeDifferent() + { + $input = + 'abcdefghijklmnopqrstuvwxyz' . + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . + '1234567890' . + 'abcdefghijklmnopqrstuvwxyz' . + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . + '1234567890' . + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + $output = + 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQk' . //38 + 'NERUZHSElKS0xNTk9QU' . "\r\n" . //57 * + 'VJTVFVWV1hZWjEyMzQ1Njc4OTBhYmNkZWZnaGl' . //38 + 'qa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLT' . "\r\n" . //76 * + 'E1OT1BRUlNUVVZXWFlaMTIzNDU2Nzg5MEFCQ0R' . //38 + 'FRkdISUpLTE1OT1BRUlNUVVZXWFla'; //67 + + $this->assertEqual( + $output, $this->_encoder->encodeString($input, 19), + '%s: First line offset is 19 so first line should be 57 chars long' + ); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/QpEncoderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/QpEncoderTest.php new file mode 100644 index 0000000..e87b26d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/QpEncoderTest.php @@ -0,0 +1,361 @@ +_createCharStream(); + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importString($char) + -> one($charStream)->readBytes(optional()) -> returns(array($ordinal)) + -> atLeast(1)->of($charStream)->readBytes(optional()) -> returns(false) + ); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + + $this->assertIdenticalBinary($char, $encoder->encodeString($char)); + } + } + + public function testWhiteSpaceAtLineEndingIsEncoded() + { + /* -- RFC 2045, 6.7 -- + (3) (White Space) Octets with values of 9 and 32 MAY be + represented as US-ASCII TAB (HT) and SPACE characters, + respectively, but MUST NOT be so represented at the end + of an encoded line. Any TAB (HT) or SPACE characters + on an encoded line MUST thus be followed on that line + by a printable character. In particular, an "=" at the + end of an encoded line, indicating a soft line break + (see rule #5) may follow one or more TAB (HT) or SPACE + characters. It follows that an octet with decimal + value 9 or 32 appearing at the end of an encoded line + must be represented according to Rule #1. This rule is + necessary because some MTAs (Message Transport Agents, + programs which transport messages from one user to + another, or perform a portion of such transfers) are + known to pad lines of text with SPACEs, and others are + known to remove "white space" characters from the end + of a line. Therefore, when decoding a Quoted-Printable + body, any trailing white space on a line must be + deleted, as it will necessarily have been added by + intermediate transport agents. + */ + + $HT = chr(0x09); //9 + $SPACE = chr(0x20); //32 + + //HT + $string = 'a' . $HT . $HT . "\r\n" . 'b'; + + $seq = $this->_mockery()->sequence('byte-sequence'); + $charStream = $this->_createCharStream(); + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importString($string) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('a'))) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x09)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x09)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0D)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0A)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('b'))) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(false) + ); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + $this->assertEqual( + 'a' . $HT . '=09' . "\r\n" . 'b', + $encoder->encodeString($string) + ); + + //SPACE + $string = 'a' . $SPACE . $SPACE . "\r\n" . 'b'; + + $seq = $this->_mockery()->sequence('byte-sequence'); + $charStream = $this->_createCharStream(); + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importString($string) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('a'))) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x20)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x20)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0D)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0A)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('b'))) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(false) + ); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + $this->assertEqual( + 'a' . $SPACE . '=20' . "\r\n" . 'b', + $encoder->encodeString($string) + ); + } + + public function testCRLFIsLeftAlone() + { + /* + (4) (Line Breaks) A line break in a text body, represented + as a CRLF sequence in the text canonical form, must be + represented by a (RFC 822) line break, which is also a + CRLF sequence, in the Quoted-Printable encoding. Since + the canonical representation of media types other than + text do not generally include the representation of + line breaks as CRLF sequences, no hard line breaks + (i.e. line breaks that are intended to be meaningful + and to be displayed to the user) can occur in the + quoted-printable encoding of such types. Sequences + like "=0D", "=0A", "=0A=0D" and "=0D=0A" will routinely + appear in non-text data represented in quoted- + printable, of course. + + Note that many implementations may elect to encode the + local representation of various content types directly + rather than converting to canonical form first, + encoding, and then converting back to local + representation. In particular, this may apply to plain + text material on systems that use newline conventions + other than a CRLF terminator sequence. Such an + implementation optimization is permissible, but only + when the combined canonicalization-encoding step is + equivalent to performing the three steps separately. + */ + + $string = 'a' . "\r\n" . 'b' . "\r\n" . 'c' . "\r\n"; + + $seq = $this->_mockery()->sequence('byte-sequence'); + $charStream = $this->_createCharStream(); + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importString($string) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('a'))) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0D)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0A)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('b'))) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0D)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0A)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('c'))) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0D)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(0x0A)) + -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(false) + ); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + $this->assertEqual($string, $encoder->encodeString($string)); + } + + public function testLinesLongerThan76CharactersAreSoftBroken() + { + /* + (5) (Soft Line Breaks) The Quoted-Printable encoding + REQUIRES that encoded lines be no more than 76 + characters long. If longer lines are to be encoded + with the Quoted-Printable encoding, "soft" line breaks + must be used. An equal sign as the last character on a + encoded line indicates such a non-significant ("soft") + line break in the encoded text. + */ + + $input = str_repeat('a', 140); + + $charStream = $this->_createCharStream(); + $seq = $this->_mockery()->sequence('byte-sequence'); + + $exps = Expectations::create(); + + $exps -> one($charStream)->flushContents() + -> one($charStream)->importString($input) + ; + + $output = ''; + for ($i = 0; $i < 140; ++$i) { + $exps -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('a'))); + + if (75 == $i) { + $output .= "=\r\n"; + } + $output .= 'a'; + } + + $exps -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(false); + + $this->_checking($exps); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + $this->assertEqual($output, $encoder->encodeString($input)); + } + + public function testMaxLineLengthCanBeSpecified() + { + $input = str_repeat('a', 100); + + $charStream = $this->_createCharStream(); + $seq = $this->_mockery()->sequence('byte-sequence'); + + $exps = Expectations::create(); + + $exps -> one($charStream)->flushContents() + -> one($charStream)->importString($input) + ; + + $output = ''; + for ($i = 0; $i < 100; ++$i) { + $exps -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('a'))); + + if (53 == $i) { + $output .= "=\r\n"; + } + $output .= 'a'; + } + $exps -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(false); + + $this->_checking($exps); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + $this->assertEqual($output, $encoder->encodeString($input, 0, 54)); + } + + public function testBytesBelowPermittedRangeAreEncoded() + { + /* + According to Rule (1 & 2) + */ + + foreach (range(0, 32) as $ordinal) { + $char = chr($ordinal); + + $charStream = $this->_createCharStream(); + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importString($char) + -> one($charStream)->readBytes(optional()) -> returns(array($ordinal)) + -> atLeast(1)->of($charStream)->readBytes(optional()) -> returns(false) + ); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + + $this->assertEqual( + sprintf('=%02X', $ordinal), $encoder->encodeString($char) + ); + } + } + + public function testDecimalByte61IsEncoded() + { + /* + According to Rule (1 & 2) + */ + + $char = '='; + + $charStream = $this->_createCharStream(); + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importString($char) + -> one($charStream)->readBytes(optional()) -> returns(array(61)) + -> atLeast(1)->of($charStream)->readBytes(optional()) -> returns(false) + ); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + + $this->assertEqual('=3D', $encoder->encodeString('=')); + } + + public function testBytesAbovePermittedRangeAreEncoded() + { + /* + According to Rule (1 & 2) + */ + + foreach (range(127, 255) as $ordinal) { + $char = chr($ordinal); + + $charStream = $this->_createCharStream(); + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importString($char) + -> one($charStream)->readBytes(optional()) -> returns(array($ordinal)) + -> atLeast(1)->of($charStream)->readBytes(optional()) -> returns(false) + ); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + + $this->assertEqual( + sprintf('=%02X', $ordinal), $encoder->encodeString($char) + ); + } + } + + public function testFirstLineLengthCanBeDifferent() + { + $input = str_repeat('a', 140); + + $charStream = $this->_createCharStream(); + $seq = $this->_mockery()->sequence('byte-sequence'); + + $exps = Expectations::create(); + + $exps -> one($charStream)->flushContents(); + $exps -> one($charStream)->importString($input); + + $output = ''; + for ($i = 0; $i < 140; ++$i) { + $exps -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(array(ord('a'))); + + if (53 == $i || 53 + 75 == $i) { + $output .= "=\r\n"; + } + $output .= 'a'; + } + + $exps -> one($charStream)->readBytes(optional()) -> inSequence($seq) -> returns(false); + + $this->_checking($exps); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + $this->assertEqual( + $output, $encoder->encodeString($input, 22), + '%s: First line should start at offset 22 so can only have max length 54' + ); + } + + // -- Creation methods + + private function _createCharStream() + { + return $this->_mock('Swift_CharacterStream'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Rfc2231EncoderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Rfc2231EncoderTest.php new file mode 100644 index 0000000..1e8e9f9 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Rfc2231EncoderTest.php @@ -0,0 +1,143 @@ +_mock('Swift_CharacterStream'); + $seq = $this->_sequence('byte-sequence'); + + $string = ''; + foreach (range(0x00, 0x7F) as $octet) { + $char = pack('C', $octet); + $string .= $char; + $this->_checking(Expectations::create() + -> one($charStream)->read(optional()) -> inSequence($seq) -> returns($char) + ); + } + $this->_checking(Expectations::create() + -> atLeast(1)->of($charStream)->read(optional()) -> inSequence($seq) -> returns(false) + -> one($charStream)->importString($string) + -> ignoring($charStream)->flushContents() + ); + + $encoder = new Swift_Encoder_Rfc2231Encoder($charStream); + $encoded = $encoder->encodeString($string); + + foreach (explode("\r\n", $encoded) as $line) { + $this->assertPattern($this->_rfc2045Token, $line, + '%s: Encoder should always return a valid RFC 2045 token.'); + } + + + } + + public function testEncodingNonAsciiCharactersProducesValidToken() + { + $charStream = $this->_mock('Swift_CharacterStream'); + $seq = $this->_sequence('byte-sequence'); + + $string = ''; + foreach (range(0x80, 0xFF) as $octet) { + $char = pack('C', $octet); + $string .= $char; + $this->_checking(Expectations::create() + -> one($charStream)->read(optional()) -> inSequence($seq) -> returns($char) + ); + } + $this->_checking(Expectations::create() + -> atLeast(1)->of($charStream)->read(optional()) -> inSequence($seq) -> returns(false) + -> one($charStream)->importString($string) + -> ignoring($charStream)->flushContents() + ); + $encoder = new Swift_Encoder_Rfc2231Encoder($charStream); + + $encoded = $encoder->encodeString($string); + + foreach (explode("\r\n", $encoded) as $line) { + $this->assertPattern($this->_rfc2045Token, $line, + '%s: Encoder should always return a valid RFC 2045 token.'); + } + + + } + + public function testMaximumLineLengthCanBeSet() + { + $charStream = $this->_mock('Swift_CharacterStream'); + $seq = $this->_sequence('byte-sequence'); + + $string = ''; + for ($x = 0; $x < 200; ++$x) { + $char = 'a'; + $string .= $char; + $this->_checking(Expectations::create() + -> one($charStream)->read(optional()) -> inSequence($seq) -> returns($char) + ); + } + $this->_checking(Expectations::create() + -> atLeast(1)->of($charStream)->read(optional()) -> inSequence($seq) -> returns(false) + -> one($charStream)->importString($string) + -> ignoring($charStream)->flushContents() + ); + $encoder = new Swift_Encoder_Rfc2231Encoder($charStream); + + $encoded = $encoder->encodeString($string, 0, 75); + + $this->assertEqual( + str_repeat('a', 75) . "\r\n" . + str_repeat('a', 75) . "\r\n" . + str_repeat('a', 50), + $encoded, + '%s: Lines should be wrapped at each 75 characters' + ); + + + } + + public function testFirstLineCanHaveShorterLength() + { + $charStream = $this->_mock('Swift_CharacterStream'); + $seq = $this->_sequence('byte-sequence'); + + $string = ''; + for ($x = 0; $x < 200; ++$x) { + $char = 'a'; + $string .= $char; + $this->_checking(Expectations::create() + -> one($charStream)->read(optional()) -> inSequence($seq) -> returns($char) + ); + } + $this->_checking(Expectations::create() + -> atLeast(1)->of($charStream)->read(optional()) -> inSequence($seq) -> returns(false) + -> one($charStream)->importString($string) + -> ignoring($charStream)->flushContents() + ); + $encoder = new Swift_Encoder_Rfc2231Encoder($charStream); + $encoded = $encoder->encodeString($string, 25, 75); + + $this->assertEqual( + str_repeat('a', 50) . "\r\n" . + str_repeat('a', 75) . "\r\n" . + str_repeat('a', 75), + $encoded, + '%s: First line should be 25 bytes shorter than the others.' + ); + + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/CommandEventTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/CommandEventTest.php new file mode 100644 index 0000000..dee6613 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/CommandEventTest.php @@ -0,0 +1,41 @@ +_createEvent($this->_createTransport(), "FOO\r\n"); + $this->assertEqual("FOO\r\n", $evt->getCommand()); + } + + public function testSuccessCodesCanBeFetchedViaGetter() + { + $evt = $this->_createEvent($this->_createTransport(), "FOO\r\n", array(250)); + $this->assertEqual(array(250), $evt->getSuccessCodes()); + } + + public function testSourceIsBuffer() + { + $transport = $this->_createTransport(); + $evt = $this->_createEvent($transport, "FOO\r\n"); + $ref = $evt->getSource(); + $this->assertReference($transport, $ref); + } + + // -- Creation Methods + + private function _createEvent(Swift_Transport $source, $command, + $successCodes = array()) + { + return new Swift_Events_CommandEvent($source, $command, $successCodes); + } + + private function _createTransport() + { + return $this->_stub('Swift_Transport'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/EventObjectTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/EventObjectTest.php new file mode 100644 index 0000000..f6fab7f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/EventObjectTest.php @@ -0,0 +1,37 @@ +_createEvent($source); + $ref = $evt->getSource(); + $this->assertReference($source, $ref); + } + + public function testEventDoesNotHaveCancelledBubbleWhenNew() + { + $source = new stdClass(); + $evt = $this->_createEvent($source); + $this->assertFalse($evt->bubbleCancelled()); + } + + public function testBubbleCanBeCancelledInEvent() + { + $source = new stdClass(); + $evt = $this->_createEvent($source); + $evt->cancelBubble(); + $this->assertTrue($evt->bubbleCancelled()); + } + + // -- Creation Methods + + private function _createEvent($source) + { + return new Swift_Events_EventObject($source); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/ResponseEventTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/ResponseEventTest.php new file mode 100644 index 0000000..adb4700 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/ResponseEventTest.php @@ -0,0 +1,44 @@ +_createEvent($this->_createTransport(), "250 Ok\r\n", true); + $this->assertEqual("250 Ok\r\n", $evt->getResponse(), + '%s: Response should be available via getResponse()' + ); + } + + public function testResultCanBeFetchedViaGetter() + { + $evt = $this->_createEvent($this->_createTransport(), "250 Ok\r\n", false); + $this->assertFalse($evt->isValid(), + '%s: Result should be checkable via isValid()' + ); + } + + public function testSourceIsBuffer() + { + $transport = $this->_createTransport(); + $evt = $this->_createEvent($transport, "250 Ok\r\n", true); + $ref = $evt->getSource(); + $this->assertReference($transport, $ref); + } + + // -- Creation Methods + + private function _createEvent(Swift_Transport $source, $response, $result) + { + return new Swift_Events_ResponseEvent($source, $response, $result); + } + + private function _createTransport() + { + return $this->_stub('Swift_Transport'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SendEventTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SendEventTest.php new file mode 100644 index 0000000..399c4e3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SendEventTest.php @@ -0,0 +1,104 @@ +_createMessage(); + $transport = $this->_createTransport(); + + $evt = $this->_createEvent($transport, $message); + + $ref = $evt->getMessage(); + $this->assertReference($message, $ref, + '%s: Message should be returned from getMessage()' + ); + } + + public function testTransportCanBeFetchViaGetter() + { + $message = $this->_createMessage(); + $transport = $this->_createTransport(); + + $evt = $this->_createEvent($transport, $message); + + $ref = $evt->getTransport(); + $this->assertReference($transport, $ref, + '%s: Transport should be returned from getTransport()' + ); + } + + public function testTransportCanBeFetchViaGetSource() + { + $message = $this->_createMessage(); + $transport = $this->_createTransport(); + + $evt = $this->_createEvent($transport, $message); + + $ref = $evt->getSource(); + $this->assertReference($transport, $ref, + '%s: Transport should be returned from getSource()' + ); + } + + public function testResultCanBeSetAndGet() + { + $message = $this->_createMessage(); + $transport = $this->_createTransport(); + + $evt = $this->_createEvent($transport, $message); + + $evt->setResult( + Swift_Events_SendEvent::RESULT_SUCCESS | Swift_Events_SendEvent::RESULT_TENTATIVE + ); + + $this->assertTrue($evt->getResult() & Swift_Events_SendEvent::RESULT_SUCCESS); + $this->assertTrue($evt->getResult() & Swift_Events_SendEvent::RESULT_TENTATIVE); + } + + public function testFailedRecipientsCanBeSetAndGet() + { + $message = $this->_createMessage(); + $transport = $this->_createTransport(); + + $evt = $this->_createEvent($transport, $message); + + $evt->setFailedRecipients(array('foo@bar', 'zip@button')); + + $this->assertEqual(array('foo@bar', 'zip@button'), $evt->getFailedRecipients(), + '%s: FailedRecipients should be returned from getter' + ); + } + + public function testFailedRecipientsGetsPickedUpCorrectly() + { + $message = $this->_createMessage(); + $transport = $this->_createTransport(); + + $evt = $this->_createEvent($transport, $message); + $this->assertEqual(array(), $evt->getFailedRecipients()); + } + + // -- Creation Methods + + private function _createEvent(Swift_Transport $source, + Swift_Mime_Message $message) + { + return new Swift_Events_SendEvent($source, $message); + } + + private function _createTransport() + { + return $this->_stub('Swift_Transport'); + } + + private function _createMessage() + { + return $this->_stub('Swift_Mime_Message'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SimpleEventDispatcherTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SimpleEventDispatcherTest.php new file mode 100644 index 0000000..aa05888 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SimpleEventDispatcherTest.php @@ -0,0 +1,167 @@ +_dispatcher = new Swift_Events_SimpleEventDispatcher(); + } + + public function testSendEventCanBeCreated() + { + $transport = $this->_stub('Swift_Transport'); + $message = $this->_stub('Swift_Mime_Message'); + $evt = $this->_dispatcher->createSendEvent($transport, $message); + $this->assertIsA($evt, 'Swift_Events_SendEvent'); + $this->assertSame($message, $evt->getMessage()); + $this->assertSame($transport, $evt->getTransport()); + } + + public function testCommandEventCanBeCreated() + { + $buf = $this->_stub('Swift_Transport'); + $evt = $this->_dispatcher->createCommandEvent($buf, "FOO\r\n", array(250)); + $this->assertIsA($evt, 'Swift_Events_CommandEvent'); + $this->assertSame($buf, $evt->getSource()); + $this->assertEqual("FOO\r\n", $evt->getCommand()); + $this->assertEqual(array(250), $evt->getSuccessCodes()); + } + + public function testResponseEventCanBeCreated() + { + $buf = $this->_stub('Swift_Transport'); + $evt = $this->_dispatcher->createResponseEvent($buf, "250 Ok\r\n", true); + $this->assertIsA($evt, 'Swift_Events_ResponseEvent'); + $this->assertSame($buf, $evt->getSource()); + $this->assertEqual("250 Ok\r\n", $evt->getResponse()); + $this->assertTrue($evt->isValid()); + } + + public function testTransportChangeEventCanBeCreated() + { + $transport = $this->_stub('Swift_Transport'); + $evt = $this->_dispatcher->createTransportChangeEvent($transport); + $this->assertIsA($evt, 'Swift_Events_TransportChangeEvent'); + $this->assertSame($transport, $evt->getSource()); + } + + public function testTransportExceptionEventCanBeCreated() + { + $transport = $this->_stub('Swift_Transport'); + $ex = new Swift_TransportException(''); + $evt = $this->_dispatcher->createTransportExceptionEvent($transport, $ex); + $this->assertIsA($evt, 'Swift_Events_TransportExceptionEvent'); + $this->assertSame($transport, $evt->getSource()); + $this->assertSame($ex, $evt->getException()); + } + + public function testListenersAreNotifiedOfDispatchedEvent() + { + $transport = $this->_stub('Swift_Transport'); + + $evt = $this->_dispatcher->createTransportChangeEvent($transport); + + $listenerA = $this->_mock('Swift_Events_TransportChangeListener'); + $listenerB = $this->_mock('Swift_Events_TransportChangeListener'); + + $this->_dispatcher->bindEventListener($listenerA); + $this->_dispatcher->bindEventListener($listenerB); + + $this->_checking(Expectations::create() + -> one($listenerA)->transportStarted($evt) + -> one($listenerB)->transportStarted($evt) + ); + + $this->_dispatcher->dispatchEvent($evt, 'transportStarted'); + } + + public function testListenersAreOnlyCalledIfImplementingCorrectInterface() + { + $transport = $this->_stub('Swift_Transport'); + $message = $this->_stub('Swift_Mime_Message'); + + $evt = $this->_dispatcher->createSendEvent($transport, $message); + + $targetListener = $this->_mock('Swift_Events_SendListener'); + $otherListener = $this->_mock('Swift_Events_TransportChangeListener'); + + $this->_dispatcher->bindEventListener($targetListener); + $this->_dispatcher->bindEventListener($otherListener); + + $this->_checking(Expectations::create() + -> one($targetListener)->sendPerformed($evt) + -> never($otherListener) + ); + + $this->_dispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + public function testListenersCanCancelBubblingOfEvent() + { + $transport = $this->_stub('Swift_Transport'); + $message = $this->_stub('Swift_Mime_Message'); + + $evt = $this->_dispatcher->createSendEvent($transport, $message); + + $listenerA = $this->_mock('Swift_Events_SendListener'); + $listenerB = $this->_mock('Swift_Events_SendListener'); + + $this->_dispatcher->bindEventListener($listenerA); + $this->_dispatcher->bindEventListener($listenerB); + + $this->_checking(Expectations::create() + -> one($listenerA)->sendPerformed($evt) -> calls(array($this, '_cancelBubble')) + -> never($listenerB) + ); + + $this->_dispatcher->dispatchEvent($evt, 'sendPerformed'); + + $this->assertTrue($evt->bubbleCancelled()); + } + + public function testAddingListenerTwiceDoesNotReceiveEventTwice() + { + $transport = $this->_stub('Swift_Transport'); + + $evt = $this->_dispatcher->createTransportChangeEvent($transport); + + $listener = $this->_mock('Swift_Events_TransportChangeListener'); + + $this->_dispatcher->bindEventListener($listener); + $this->_dispatcher->bindEventListener($listener); + + $this->_checking(Expectations::create() + -> one($listener)->transportStarted($evt) + -> never($listener)->transportStarted($evt) + ); + + $this->_dispatcher->dispatchEvent($evt, 'transportStarted'); + } + + // -- Mock callbacks + + public function _cancelBubble(Yay_Invocation $inv) + { + $args = $inv->getArguments(); + $args[0]->cancelBubble(true); + } + + // -- Private methods + + private function _createDispatcher(array $map) + { + $dispatcher = new Swift_Events_SimpleEventDispatcher($map); + + return $dispatcher; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportChangeEventTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportChangeEventTest.php new file mode 100644 index 0000000..b78e315 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportChangeEventTest.php @@ -0,0 +1,36 @@ +_createTransport(); + $evt = $this->_createEvent($transport); + $ref = $evt->getTransport(); + $this->assertReference($transport, $ref); + } + + public function testSourceIsTransport() + { + $transport = $this->_createTransport(); + $evt = $this->_createEvent($transport); + $ref = $evt->getSource(); + $this->assertReference($transport, $ref); + } + + // -- Creation Methods + + private function _createEvent(Swift_Transport $source) + { + return new Swift_Events_TransportChangeEvent($source); + } + + private function _createTransport() + { + return $this->_stub('Swift_Transport'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportExceptionEventTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportExceptionEventTest.php new file mode 100644 index 0000000..430117a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportExceptionEventTest.php @@ -0,0 +1,49 @@ +_createException(); + $transport = $this->_createTransport(); + $evt = $this->_createEvent($transport, $ex); + $ref = $evt->getException(); + $this->assertReference($ex, $ref, + '%s: Exception should be available via getException()' + ); + } + + public function testSourceIsTransport() + { + $ex = $this->_createException(); + $transport = $this->_createTransport(); + $evt = $this->_createEvent($transport, $ex); + $ref = $evt->getSource(); + $this->assertReference($transport, $ref, + '%s: Transport should be available via getSource()' + ); + } + + // -- Creation Methods + + private function _createEvent(Swift_Transport $transport, + Swift_TransportException $ex) + { + return new Swift_Events_TransportExceptionEvent($transport, $ex); + } + + private function _createTransport() + { + return $this->_stub('Swift_Transport'); + } + + private function _createException() + { + return new Swift_TransportException(''); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/ArrayKeyCacheTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/ArrayKeyCacheTest.php new file mode 100644 index 0000000..68ea3d1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/ArrayKeyCacheTest.php @@ -0,0 +1,242 @@ +_createKeyCacheInputStream(true); + $cache = $this->_createCache($is); + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('test', $cache->getString($this->_key1, 'foo')); + } + + public function testStringDataCanBeOverwritten() + { + $is = $this->_createKeyCacheInputStream(true); + $cache = $this->_createCache($is); + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $cache->setString( + $this->_key1, 'foo', 'whatever', Swift_KeyCache::MODE_WRITE + ); + + $this->assertEqual('whatever', $cache->getString($this->_key1, 'foo')); + } + + public function testStringDataCanBeAppended() + { + $is = $this->_createKeyCacheInputStream(true); + $cache = $this->_createCache($is); + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $cache->setString( + $this->_key1, 'foo', 'ing', Swift_KeyCache::MODE_APPEND + ); + + $this->assertEqual('testing', $cache->getString($this->_key1, 'foo')); + } + + public function testHasKeyReturnValue() + { + $is = $this->_createKeyCacheInputStream(true); + $cache = $this->_createCache($is); + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + + $this->assertTrue($cache->hasKey($this->_key1, 'foo')); + } + + public function testNsKeyIsWellPartitioned() + { + $is = $this->_createKeyCacheInputStream(true); + $cache = $this->_createCache($is); + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $cache->setString( + $this->_key2, 'foo', 'ing', Swift_KeyCache::MODE_WRITE + ); + + $this->assertEqual('test', $cache->getString($this->_key1, 'foo')); + $this->assertEqual('ing', $cache->getString($this->_key2, 'foo')); + } + + public function testItemKeyIsWellPartitioned() + { + $is = $this->_createKeyCacheInputStream(true); + $cache = $this->_createCache($is); + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $cache->setString( + $this->_key1, 'bar', 'ing', Swift_KeyCache::MODE_WRITE + ); + + $this->assertEqual('test', $cache->getString($this->_key1, 'foo')); + $this->assertEqual('ing', $cache->getString($this->_key1, 'bar')); + } + + public function testByteStreamCanBeImported() + { + $os = $this->_createOutputStream(); + $this->_checking(Expectations::create() + -> one($os)->read(optional()) -> returns('abc') + -> one($os)->read(optional()) -> returns('def') + -> one($os)->read(optional()) -> returns(false) + -> ignoring($os) + ); + $is = $this->_createKeyCacheInputStream(true); + $cache = $this->_createCache($is); + $cache->importFromByteStream( + $this->_key1, 'foo', $os, Swift_KeyCache::MODE_WRITE + ); + $this->assertEqual('abcdef', $cache->getString($this->_key1, 'foo')); + } + + public function testByteStreamCanBeAppended() + { + $os1 = $this->_createOutputStream(); + $os2 = $this->_createOutputStream(); + $this->_checking(Expectations::create() + -> one($os1)->read(optional()) -> returns('abc') + -> one($os1)->read(optional()) -> returns('def') + -> one($os1)->read(optional()) -> returns(false) + -> ignoring($os1) + + -> one($os2)->read(optional()) -> returns('xyz') + -> one($os2)->read(optional()) -> returns('uvw') + -> one($os2)->read(optional()) -> returns(false) + -> ignoring($os2) + ); + $is = $this->_createKeyCacheInputStream(true); + + $cache = $this->_createCache($is); + + $cache->importFromByteStream( + $this->_key1, 'foo', $os1, Swift_KeyCache::MODE_APPEND + ); + $cache->importFromByteStream( + $this->_key1, 'foo', $os2, Swift_KeyCache::MODE_APPEND + ); + + $this->assertEqual('abcdefxyzuvw', $cache->getString($this->_key1, 'foo')); + } + + public function testByteStreamAndStringCanBeAppended() + { + $os = $this->_createOutputStream(); + $this->_checking(Expectations::create() + -> one($os)->read(optional()) -> returns('abc') + -> one($os)->read(optional()) -> returns('def') + -> one($os)->read(optional()) -> returns(false) + -> ignoring($os) + ); + $is = $this->_createKeyCacheInputStream(true); + + $cache = $this->_createCache($is); + + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_APPEND + ); + $cache->importFromByteStream( + $this->_key1, 'foo', $os, Swift_KeyCache::MODE_APPEND + ); + $this->assertEqual('testabcdef', $cache->getString($this->_key1, 'foo')); + } + + public function testDataCanBeExportedToByteStream() + { + //See acceptance test for more detail + $is = $this->_createInputStream(); + $this->_checking(Expectations::create() + -> atLeast(1)->of($is)->write(any()) + -> ignoring($is) + ); + $kcis = $this->_createKeyCacheInputStream(true); + + $cache = $this->_createCache($kcis); + + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + + $cache->exportToByteStream($this->_key1, 'foo', $is); + } + + public function testKeyCanBeCleared() + { + $is = $this->_createKeyCacheInputStream(true); + $cache = $this->_createCache($is); + + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($cache->hasKey($this->_key1, 'foo')); + $cache->clearKey($this->_key1, 'foo'); + $this->assertFalse($cache->hasKey($this->_key1, 'foo')); + } + + public function testNsKeyCanBeCleared() + { + $is = $this->_createKeyCacheInputStream(true); + $cache = $this->_createCache($is); + + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $cache->setString( + $this->_key1, 'bar', 'xyz', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($cache->hasKey($this->_key1, 'foo')); + $this->assertTrue($cache->hasKey($this->_key1, 'bar')); + $cache->clearAll($this->_key1); + $this->assertFalse($cache->hasKey($this->_key1, 'foo')); + $this->assertFalse($cache->hasKey($this->_key1, 'bar')); + } + + // -- Creation methods + + private function _createCache($is) + { + return new Swift_KeyCache_ArrayKeyCache($is); + } + + private function _createKeyCacheInputStream($stub = false) + { + return $stub + ? $this->_stub('Swift_KeyCache_KeyCacheInputStream') + : $this->_mock('Swift_KeyCache_KeyCacheInputStream') + ; + } + + private function _createOutputStream($stub = false) + { + return $stub + ? $this->_stub('Swift_OutputByteStream') + : $this->_mock('Swift_OutputByteStream') + ; + } + + private function _createInputStream($stub = false) + { + return $stub + ? $this->_stub('Swift_InputByteStream') + : $this->_mock('Swift_InputByteStream') + ; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/SimpleKeyCacheInputStreamTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/SimpleKeyCacheInputStreamTest.php new file mode 100644 index 0000000..94a86ba --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/SimpleKeyCacheInputStreamTest.php @@ -0,0 +1,77 @@ +_createKeyCache(); + $this->_checking(Expectations::create() + -> one($cache)->setString($this->_nsKey, 'foo', 'a', Swift_KeyCache::MODE_APPEND) + -> one($cache)->setString($this->_nsKey, 'foo', 'b', Swift_KeyCache::MODE_APPEND) + -> one($cache)->setString($this->_nsKey, 'foo', 'c', Swift_KeyCache::MODE_APPEND) + ); + + $stream = new Swift_KeyCache_SimpleKeyCacheInputStream(); + $stream->setKeyCache($cache); + $stream->setNsKey($this->_nsKey); + $stream->setItemKey('foo'); + + $stream->write('a'); + $stream->write('b'); + $stream->write('c'); + } + + public function testFlushContentClearsKey() + { + $cache = $this->_createKeyCache(); + $this->_checking(Expectations::create() + -> one($cache)->clearKey($this->_nsKey, 'foo') + ); + + $stream = new Swift_KeyCache_SimpleKeyCacheInputStream(); + $stream->setKeyCache($cache); + $stream->setNsKey($this->_nsKey); + $stream->setItemKey('foo'); + + $stream->flushBuffers(); + } + + public function testClonedStreamStillReferencesSameCache() + { + $cache = $this->_createKeyCache(); + $this->_checking(Expectations::create() + -> one($cache)->setString($this->_nsKey, 'foo', 'a', Swift_KeyCache::MODE_APPEND) + -> one($cache)->setString($this->_nsKey, 'foo', 'b', Swift_KeyCache::MODE_APPEND) + -> one($cache)->setString('test', 'bar', 'x', Swift_KeyCache::MODE_APPEND) + ); + + $stream = new Swift_KeyCache_SimpleKeyCacheInputStream(); + $stream->setKeyCache($cache); + $stream->setNsKey($this->_nsKey); + $stream->setItemKey('foo'); + + $stream->write('a'); + $stream->write('b'); + + $newStream = clone $stream; + $newStream->setKeyCache($cache); + $newStream->setNsKey('test'); + $newStream->setItemKey('bar'); + + $newStream->write('x'); + } + + // -- Creation Methods + + private function _createKeyCache() + { + return $this->_mock('Swift_KeyCache'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mailer/ArrayRecipientIteratorTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mailer/ArrayRecipientIteratorTest.php new file mode 100644 index 0000000..f86b181 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mailer/ArrayRecipientIteratorTest.php @@ -0,0 +1,46 @@ +assertFalse($it->hasNext()); + } + + public function testHasNextReturnsTrueIfItemsLeft() + { + $it = new Swift_Mailer_ArrayRecipientIterator(array('foo@bar' => 'Foo')); + $this->assertTrue($it->hasNext()); + } + + public function testReadingToEndOfListCausesHasNextToReturnFalse() + { + $it = new Swift_Mailer_ArrayRecipientIterator(array('foo@bar' => 'Foo')); + $this->assertTrue($it->hasNext()); + $it->nextRecipient(); + $this->assertFalse($it->hasNext()); + } + + public function testReturnedValueHasPreservedKeyValuePair() + { + $it = new Swift_Mailer_ArrayRecipientIterator(array('foo@bar' => 'Foo')); + $this->assertEqual(array('foo@bar' => 'Foo'), $it->nextRecipient()); + } + + public function testIteratorMovesNextAfterEachIteration() + { + $it = new Swift_Mailer_ArrayRecipientIterator(array( + 'foo@bar' => 'Foo', + 'zip@button' => 'Zip thing', + 'test@test' => null + )); + $this->assertEqual(array('foo@bar' => 'Foo'), $it->nextRecipient()); + $this->assertEqual(array('zip@button' => 'Zip thing'), $it->nextRecipient()); + $this->assertEqual(array('test@test' => null), $it->nextRecipient()); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MailerTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MailerTest.php new file mode 100644 index 0000000..48c209d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MailerTest.php @@ -0,0 +1,149 @@ +_createTransport(); + $message = $this->_createMessage(); + $con = $this->_states('Connection')->startsAs('off'); + $this->_checking(Expectations::create() + -> allowing($transport)->isStarted() -> returns(false) -> when($con->is('off')) + -> allowing($transport)->isStarted() -> returns(false) -> when($con->is('on')) + -> one($transport)->start() -> when($con->is('off')) -> then($con->is('on')) + -> ignoring($transport) + -> ignoring($message) + ); + + $mailer = $this->_createMailer($transport); + $mailer->send($message); + } + + public function testTransportIsOnlyStartedOnce() + { + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $con = $this->_states('Connection')->startsAs('off'); + $this->_checking(Expectations::create() + -> allowing($transport)->isStarted() -> returns(false) -> when($con->is('off')) + -> allowing($transport)->isStarted() -> returns(false) -> when($con->is('on')) + -> one($transport)->start() -> when($con->is('off')) -> then($con->is('on')) + -> ignoring($transport) + -> ignoring($message) + ); + $mailer = $this->_createMailer($transport); + for ($i = 0; $i < 10; ++$i) { + $mailer->send($message); + } + } + + public function testMessageIsPassedToTransport() + { + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> one($transport)->send($message, optional()) + -> ignoring($transport) + -> ignoring($message) + ); + + $mailer = $this->_createMailer($transport); + $mailer->send($message); + } + + public function testSendReturnsCountFromTransport() + { + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> one($transport)->send($message, optional()) -> returns(57) + -> ignoring($transport) + -> ignoring($message) + ); + + $mailer = $this->_createMailer($transport); + $this->assertEqual(57, $mailer->send($message)); + } + + public function testFailedRecipientReferenceIsPassedToTransport() + { + $failures = array(); + + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> one($transport)->send($message, reference($failures)) + -> ignoring($transport) + -> ignoring($message) + ); + + $mailer = $this->_createMailer($transport); + $mailer->send($message, $failures); + } + + public function testSendRecordsRfcComplianceExceptionAsEntireSendFailure() + { + $failures = array(); + + $rfcException = new Swift_RfcComplianceException('test'); + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array('foo&invalid' => 'Foo', 'bar@valid.tld' => 'Bar')) + -> one($transport)->send($message, reference($failures)) -> throws($rfcException) + -> ignoring($transport) + -> ignoring($message) + ); + + $mailer = $this->_createMailer($transport); + $this->assertEqual(0, $mailer->send($message, $failures), '%s: Should return 0'); + $this->assertEqual(array('foo&invalid', 'bar@valid.tld'), $failures, '%s: Failures should contain all addresses since the entire message failed to compile'); + } + + public function testRegisterPluginDelegatesToTransport() + { + $plugin = $this->_createPlugin(); + $transport = $this->_createTransport(); + $mailer = $this->_createMailer($transport); + + $this->_checking(Expectations::create() + -> one($transport)->registerPlugin($plugin) + ); + $mailer->registerPlugin($plugin); + } + + // -- Creation methods + + private function _createPlugin() + { + return $this->_mock('Swift_Events_EventListener'); + } + + private function _createTransport() + { + return $this->_mock('Swift_Transport'); + } + + private function _createMessage() + { + return $this->_mock('Swift_Mime_Message'); + } + + private function _createIterator() + { + return $this->_mock('Swift_Mailer_RecipientIterator'); + } + + private function _createMailer(Swift_Transport $transport) + { + return new Swift_Mailer($transport); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AbstractMimeEntityTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AbstractMimeEntityTest.php new file mode 100644 index 0000000..0879279 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AbstractMimeEntityTest.php @@ -0,0 +1,1102 @@ +_createHeaderSet(); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $this->assertSame($headers, $entity->getHeaders()); + } + + public function testContentTypeIsReturnedFromHeader() + { + $ctype = $this->_createHeader('Content-Type', 'image/jpeg-test'); + $headers = $this->_createHeaderSet(array('Content-Type' => $ctype)); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $this->assertEqual('image/jpeg-test', $entity->getContentType()); + } + + public function testContentTypeIsSetInHeader() + { + $ctype = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $headers = $this->_createHeaderSet(array('Content-Type' => $ctype)); + $this->_checking(Expectations::create() + -> one($ctype)->setFieldBodyModel('image/jpeg') + -> ignoring($ctype) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setContentType('image/jpeg'); + } + + public function testContentTypeHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addParameterizedHeader('Content-Type', 'image/jpeg') + -> ignoring($headers) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setContentType('image/jpeg'); + } + + public function testContentTypeCanBeSetViaSetBody() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addParameterizedHeader('Content-Type', 'text/html') + -> ignoring($headers) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setBody('foo', 'text/html'); + } + + public function testGetEncoderFromConstructor() + { + $encoder = $this->_createEncoder('base64'); + $entity = $this->_createEntity($this->_createHeaderSet(), $encoder, + $this->_createCache() + ); + $this->assertSame($encoder, $entity->getEncoder()); + } + + public function testSetAndGetEncoder() + { + $encoder = $this->_createEncoder('base64'); + $headers = $this->_createHeaderSet(); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setEncoder($encoder); + $this->assertSame($encoder, $entity->getEncoder()); + } + + public function testSettingEncoderUpdatesTransferEncoding() + { + $encoder = $this->_createEncoder('base64'); + $encoding = $this->_createHeader( + 'Content-Transfer-Encoding', '8bit', array(), false + ); + $headers = $this->_createHeaderSet(array( + 'Content-Transfer-Encoding' => $encoding + )); + $this->_checking(Expectations::create() + -> one($encoding)->setFieldBodyModel('base64') + -> ignoring($encoding) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setEncoder($encoder); + } + + public function testSettingEncoderAddsEncodingHeaderIfNonePresent() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addTextHeader('Content-Transfer-Encoding', 'something') + -> ignoring($headers) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setEncoder($this->_createEncoder('something')); + } + + public function testIdIsReturnedFromHeader() + { + /* -- RFC 2045, 7. + In constructing a high-level user agent, it may be desirable to allow + one body to make reference to another. Accordingly, bodies may be + labelled using the "Content-ID" header field, which is syntactically + identical to the "Message-ID" header field + */ + + $cid = $this->_createHeader('Content-ID', 'zip@button'); + $headers = $this->_createHeaderSet(array('Content-ID' => $cid)); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $this->assertEqual('zip@button', $entity->getId()); + } + + public function testIdIsSetInHeader() + { + $cid = $this->_createHeader('Content-ID', 'zip@button', array(), false); + $headers = $this->_createHeaderSet(array('Content-ID' => $cid)); + $this->_checking(Expectations::create() + -> one($cid)->setFieldBodyModel('foo@bar') + -> ignoring($cid) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setId('foo@bar'); + } + + public function testIdIsAutoGenerated() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertPattern('/^.*?@.*?$/D', $entity->getId()); + } + + public function testGenerateIdCreatesNewId() + { + $headers = $this->_createHeaderSet(); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $id1 = $entity->generateId(); + $id2 = $entity->generateId(); + $this->assertNotEqual($id1, $id2); + } + + public function testGenerateIdSetsNewId() + { + $headers = $this->_createHeaderSet(); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $id = $entity->generateId(); + $this->assertEqual($id, $entity->getId()); + } + + public function testDescriptionIsReadFromHeader() + { + /* -- RFC 2045, 8. + The ability to associate some descriptive information with a given + body is often desirable. For example, it may be useful to mark an + "image" body as "a picture of the Space Shuttle Endeavor." Such text + may be placed in the Content-Description header field. This header + field is always optional. + */ + + $desc = $this->_createHeader('Content-Description', 'something'); + $headers = $this->_createHeaderSet(array('Content-Description' => $desc)); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $this->assertEqual('something', $entity->getDescription()); + } + + public function testDescriptionIsSetInHeader() + { + $desc = $this->_createHeader('Content-Description', '', array(), false); + $headers = $this->_createHeaderSet(array('Content-Description' => $desc)); + $this->_checking(Expectations::create() + -> one($desc)->setFieldBodyModel('whatever') + -> ignoring($desc) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setDescription('whatever'); + } + + public function testDescriptionHeaderIsAddedIfNotPresent() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addTextHeader('Content-Description', 'whatever') + -> ignoring($headers) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setDescription('whatever'); + } + + public function testSetAndGetMaxLineLength() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setMaxLineLength(60); + $this->assertEqual(60, $entity->getMaxLineLength()); + } + + public function testEncoderIsUsedForStringGeneration() + { + $encoder = $this->_createEncoder('base64', false); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString('blah', optional()) + -> ignoring($encoder) + ); + $entity = $this->_createEntity($this->_createHeaderSet(), + $encoder, $this->_createCache() + ); + $entity->setBody("blah"); + $entity->toString(); + } + + public function testMaxLineLengthIsProvidedWhenEncoding() + { + $encoder = $this->_createEncoder('base64', false); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString('blah', 0, 65) + -> ignoring($encoder) + ); + $entity = $this->_createEntity($this->_createHeaderSet(), + $encoder, $this->_createCache() + ); + $entity->setBody("blah"); + $entity->setMaxLineLength(65); + $entity->toString(); + } + + public function testHeadersAppearInString() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: text/plain; charset=utf-8\r\n" . + "X-MyHeader: foobar\r\n" + ) + -> ignoring($headers) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $this->assertEqual( + "Content-Type: text/plain; charset=utf-8\r\n" . + "X-MyHeader: foobar\r\n", + $entity->toString() + ); + } + + public function testSetAndGetBody() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setBody("blah\r\nblah!"); + $this->assertEqual("blah\r\nblah!", $entity->getBody()); + } + + public function testBodyIsAppended() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: text/plain; charset=utf-8\r\n" + ) + -> ignoring($headers) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setBody("blah\r\nblah!"); + $this->assertEqual( + "Content-Type: text/plain; charset=utf-8\r\n" . + "\r\n" . + "blah\r\nblah!", + $entity->toString() + ); + } + + public function testGetBodyReturnsStringFromByteStream() + { + $os = $this->_createOutputStream("byte stream string"); + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setBody($os); + $this->assertEqual("byte stream string", $entity->getBody()); + } + + public function testByteStreamBodyIsAppended() + { + $headers = $this->_createHeaderSet(array(), false); + $os = $this->_createOutputStream("streamed"); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: text/plain; charset=utf-8\r\n" + ) + -> ignoring($headers) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setBody($os); + $this->assertEqual( + "Content-Type: text/plain; charset=utf-8\r\n" . + "\r\n" . + "streamed", + $entity->toString() + ); + } + + public function testBoundaryCanBeRetrieved() + { + /* -- RFC 2046, 5.1.1. + boundary := 0*69 bcharsnospace + + bchars := bcharsnospace / " " + + bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / + "+" / "_" / "," / "-" / "." / + "/" / ":" / "=" / "?" + */ + + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertPattern( + '/^[a-zA-Z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-zA-Z0-9\'\(\)\+_\-,\.\/:=\?]$/D', + $entity->getBoundary() + ); + } + + public function testBoundaryNeverChanges() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $firstBoundary = $entity->getBoundary(); + for ($i = 0; $i < 10; $i++) { + $this->assertEqual($firstBoundary, $entity->getBoundary()); + } + } + + public function testBoundaryCanBeSet() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setBoundary('foobar'); + $this->assertEqual('foobar', $entity->getBoundary()); + } + + public function testAddingChildrenGeneratesBoundaryInHeaders() + { + $child = $this->_createChild(); + $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $this->_checking(Expectations::create() + -> one($cType)->setParameter('boundary', any()) + -> ignoring($cType) + ); + + $entity = $this->_createEntity($this->_createHeaderSet(array( + 'Content-Type' => $cType + )), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + } + + public function testChildrenOfLevelAttachmentAndLessCauseMultipartMixed() + { + for ($level = Swift_Mime_MimeEntity::LEVEL_MIXED; + $level > Swift_Mime_MimeEntity::LEVEL_TOP; $level /= 2) + { + $child = $this->_createChild($level); + $cType = $this->_createHeader( + 'Content-Type', 'text/plain', array(), false + ); + $this->_checking(Expectations::create() + -> one($cType)->setFieldBodyModel('multipart/mixed') + -> ignoring($cType) + ); + $entity = $this->_createEntity($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + } + } + + public function testChildrenOfLevelAlternativeAndLessCauseMultipartAlternative() + { + for ($level = Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE; + $level > Swift_Mime_MimeEntity::LEVEL_MIXED; $level /= 2) + { + $child = $this->_createChild($level); + $cType = $this->_createHeader( + 'Content-Type', 'text/plain', array(), false + ); + $this->_checking(Expectations::create() + -> one($cType)->setFieldBodyModel('multipart/alternative') + -> ignoring($cType) + ); + $entity = $this->_createEntity($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + } + } + + public function testChildrenOfLevelRelatedAndLessCauseMultipartRelated() + { + for ($level = Swift_Mime_MimeEntity::LEVEL_RELATED; + $level > Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE; $level /= 2) + { + $child = $this->_createChild($level); + $cType = $this->_createHeader( + 'Content-Type', 'text/plain', array(), false + ); + $this->_checking(Expectations::create() + -> one($cType)->setFieldBodyModel('multipart/related') + -> ignoring($cType) + ); + $entity = $this->_createEntity($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + } + } + + public function testHighestLevelChildDeterminesContentType() + { + $combinations = array( + array('levels' => array(Swift_Mime_MimeEntity::LEVEL_MIXED, + Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + Swift_Mime_MimeEntity::LEVEL_RELATED + ), + 'type' => 'multipart/mixed' + ), + array('levels' => array(Swift_Mime_MimeEntity::LEVEL_MIXED, + Swift_Mime_MimeEntity::LEVEL_RELATED + ), + 'type' => 'multipart/mixed' + ), + array('levels' => array(Swift_Mime_MimeEntity::LEVEL_MIXED, + Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE + ), + 'type' => 'multipart/mixed' + ), + array('levels' => array(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + Swift_Mime_MimeEntity::LEVEL_RELATED + ), + 'type' => 'multipart/alternative' + ) + ); + + foreach ($combinations as $combination) { + $children = array(); + foreach ($combination['levels'] as $level) { + $children[] = $this->_createChild($level); + } + + $cType = $this->_createHeader( + 'Content-Type', 'text/plain', array(), false + ); + $this->_checking(Expectations::create() + -> one($cType)->setFieldBodyModel($combination['type']) + -> ignoring($cType) + ); + $entity = $this->_createEntity($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren($children); + } + } + + public function testChildrenAppearNestedInString() + { + /* -- RFC 2046, 5.1.1. + (excerpt too verbose to paste here) + */ + + $headers = $this->_createHeaderSet(array(), false); + + $child1 = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + "Content-Type: text/plain\r\n" . + "\r\n" . + "foobar" + ); + + $child2 = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + "Content-Type: text/html\r\n" . + "\r\n" . + "foobar" + ); + + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n" + ) + -> ignoring($headers) + ); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setBoundary('xxx'); + $entity->setChildren(array($child1, $child2)); + + $this->assertEqual( + "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n" . + "\r\n" . + "\r\n--xxx\r\n" . + "Content-Type: text/plain\r\n" . + "\r\n" . + "foobar\r\n" . + "\r\n--xxx\r\n" . + "Content-Type: text/html\r\n" . + "\r\n" . + "foobar\r\n" . + "\r\n--xxx--\r\n", + $entity->toString() + ); + } + + public function testMixingLevelsIsHierarchical() + { + $headers = $this->_createHeaderSet(array(), false); + $newHeaders = $this->_createHeaderSet(array(), false); + + $part = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + "Content-Type: text/plain\r\n" . + "\r\n" . + "foobar" + ); + + $attachment = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_MIXED, + "Content-Type: application/octet-stream\r\n" . + "\r\n" . + "data" + ); + + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: multipart/mixed; boundary=\"xxx\"\r\n" + ) + -> ignoring($headers)->newInstance() -> returns($newHeaders) + -> ignoring($headers) + -> ignoring($newHeaders)->toString() -> returns( + "Content-Type: multipart/alternative; boundary=\"yyy\"\r\n" + ) + -> ignoring($newHeaders) + ); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setBoundary('xxx'); + $entity->setChildren(array($part, $attachment)); + + $this->assertPattern( + "~^" . + "Content-Type: multipart/mixed; boundary=\"xxx\"\r\n" . + "\r\n\r\n--xxx\r\n" . + "Content-Type: multipart/alternative; boundary=\"yyy\"\r\n" . + "\r\n\r\n--(.*?)\r\n" . + "Content-Type: text/plain\r\n" . + "\r\n" . + "foobar" . + "\r\n\r\n--\\1--\r\n" . + "\r\n\r\n--xxx\r\n" . + "Content-Type: application/octet-stream\r\n" . + "\r\n" . + "data" . + "\r\n\r\n--xxx--\r\n" . + "\$~", + $entity->toString() + ); + } + + public function testSettingEncoderNotifiesChildren() + { + $child = $this->_createChild(0, '', false); + $encoder = $this->_createEncoder('base64'); + + $this->_checking(Expectations::create() + -> one($child)->encoderChanged($encoder) + -> ignoring($child) + ); + + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + $entity->setEncoder($encoder); + } + + public function testReceiptOfEncoderChangeNotifiesChildren() + { + $child = $this->_createChild(0, '', false); + $encoder = $this->_createEncoder('base64'); + + $this->_checking(Expectations::create() + -> one($child)->encoderChanged($encoder) + -> ignoring($child) + ); + + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + $entity->encoderChanged($encoder); + } + + public function testReceiptOfCharsetChangeNotifiesChildren() + { + $child = $this->_createChild(0, '', false); + + $this->_checking(Expectations::create() + -> one($child)->charsetChanged('windows-874') + -> ignoring($child) + ); + + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + $entity->charsetChanged('windows-874'); + } + + public function testEntityIsWrittenToByteStream() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $is = $this->_createInputStream(false); + $this->_checking(Expectations::create() + -> atLeast(1)->of($is)->write(any()) + -> ignoring($is) + ); + + $entity->toByteStream($is); + } + + public function testEntityHeadersAreComittedToByteStream() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $is = $this->_createInputStream(false); + $this->_checking(Expectations::create() + -> atLeast(1)->of($is)->commit() + -> atLeast(1)->of($is)->write(any()) + -> ignoring($is) + ); + + $entity->toByteStream($is); + } + + public function testOrderingTextBeforeHtml() + { + $htmlChild = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + "Content-Type: text/html\r\n" . + "\r\n" . + "HTML PART", + false + ); + $textChild = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + "Content-Type: text/plain\r\n" . + "\r\n" . + "TEXT PART", + false + ); + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n" + ) + -> ignoring($headers) + -> ignoring($htmlChild)->getContentType() -> returns('text/html') + -> ignoring($htmlChild) + -> ignoring($textChild)->getContentType() -> returns('text/plain') + -> ignoring($textChild) + ); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setBoundary('xxx'); + $entity->setChildren(array($htmlChild, $textChild)); + + $this->assertEqual( + "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n" . + "\r\n\r\n--xxx\r\n" . + "Content-Type: text/plain\r\n" . + "\r\n" . + "TEXT PART" . + "\r\n\r\n--xxx\r\n" . + "Content-Type: text/html\r\n" . + "\r\n" . + "HTML PART" . + "\r\n\r\n--xxx--\r\n", + $entity->toString() + ); + } + + public function testUnsettingChildrenRestoresContentType() + { + $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $child = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE); + + $s = $this->_mockery()->sequence('Type setting'); + $this->_checking(Expectations::create() + -> one($cType)->setFieldBodyModel('image/jpeg') -> inSequence($s) + -> one($cType)->setFieldBodyModel('multipart/alternative') -> inSequence($s) + -> one($cType)->setFieldBodyModel('image/jpeg') -> inSequence($s) + -> ignoring($cType) + ); + + $entity = $this->_createEntity($this->_createHeaderSet(array( + 'Content-Type' => $cType + )), + $this->_createEncoder(), $this->_createCache() + ); + + $entity->setContentType('image/jpeg'); + $entity->setChildren(array($child)); + $entity->setChildren(array()); + } + + public function testBodyIsReadFromCacheWhenUsingToStringIfPresent() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: text/plain; charset=utf-8\r\n" + ) + -> ignoring($headers) + ); + + $cache = $this->_createCache(false); + $this->_checking(Expectations::create() + -> one($cache)->hasKey(any(), 'body') -> returns(true) + -> one($cache)->getString(any(), 'body') -> returns("\r\ncache\r\ncache!") + -> ignoring($cache) + ); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $cache + ); + + $entity->setBody("blah\r\nblah!"); + $this->assertEqual( + "Content-Type: text/plain; charset=utf-8\r\n" . + "\r\n" . + "cache\r\ncache!", + $entity->toString() + ); + } + + public function testBodyIsAddedToCacheWhenUsingToString() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: text/plain; charset=utf-8\r\n" + ) + -> ignoring($headers) + ); + + $cache = $this->_createCache(false); + $this->_checking(Expectations::create() + -> one($cache)->hasKey(any(), 'body') -> returns(false) + -> one($cache)->setString(any(), 'body', "\r\nblah\r\nblah!", Swift_KeyCache::MODE_WRITE) + -> ignoring($cache) + ); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $cache + ); + + $entity->setBody("blah\r\nblah!"); + $entity->toString(); + } + + public function testBodyIsClearedFromCacheIfNewBodySet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: text/plain; charset=utf-8\r\n" + ) + -> ignoring($headers) + ); + + $cache = $this->_createCache(false); + $this->_checking(Expectations::create() + -> one($cache)->clearKey(any(), 'body') + -> ignoring($cache) + ); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $cache + ); + + $entity->setBody("blah\r\nblah!"); + $entity->toString(); + + $entity->setBody("new\r\nnew!"); + } + + public function testBodyIsNotClearedFromCacheIfSameBodySet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: text/plain; charset=utf-8\r\n" + ) + -> ignoring($headers) + ); + + $cache = $this->_createCache(false); + $this->_checking(Expectations::create() + -> never($cache)->clearKey(any(), 'body') + -> ignoring($cache) + ); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $cache + ); + + $entity->setBody("blah\r\nblah!"); + $entity->toString(); + + $entity->setBody("blah\r\nblah!"); + } + + public function testBodyIsClearedFromCacheIfNewEncoderSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: text/plain; charset=utf-8\r\n" + ) + -> ignoring($headers) + ); + + $cache = $this->_createCache(false); + $this->_checking(Expectations::create() + -> one($cache)->clearKey(any(), 'body') + -> ignoring($cache) + ); + + $otherEncoder = $this->_createEncoder(); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $cache + ); + + $entity->setBody("blah\r\nblah!"); + $entity->toString(); + + $entity->setEncoder($otherEncoder); + } + + public function testBodyIsReadFromCacheWhenUsingToByteStreamIfPresent() + { + $is = $this->_createInputStream(); + $cache = $this->_createCache(false); + + $this->_checking(Expectations::create() + -> one($cache)->hasKey(any(), 'body') -> returns(true) + -> one($cache)->exportToByteStream(any(), 'body', $is) + -> ignoring($cache) + ); + + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $cache + ); + $entity->setBody('foo'); + + $entity->toByteStream($is); + } + + public function testBodyIsAddedToCacheWhenUsingToByteStream() + { + $is = $this->_createInputStream(); + $cache = $this->_createCache(false); + + $this->_checking(Expectations::create() + -> one($cache)->hasKey(any(), 'body') -> returns(false) + //The input stream should be fetched for writing + // Proving that it's actually written to is possible, but extremely + // fragile. Best let the acceptance tests cover this aspect + -> one($cache)->getInputByteStream(any(), 'body') + -> ignoring($cache) + ); + + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $cache + ); + $entity->setBody('foo'); + + $entity->toByteStream($is); + } + + public function testFluidInterface() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + + $this->assertSame($entity, + $entity + ->setContentType('text/plain') + ->setEncoder($this->_createEncoder()) + ->setId('foo@bar') + ->setDescription('my description') + ->setMaxLineLength(998) + ->setBody('xx') + ->setBoundary('xyz') + ->setChildren(array()) + ); + } + + // -- Private helpers + + abstract protected function _createEntity($headers, $encoder, $cache); + + protected function _createChild($level = null, $string = '', $stub = true) + { + $child = $this->_mock('Swift_Mime_MimeEntity'); + if (isset($level)) { + $this->_checking(Expectations::create() + -> ignoring($child)->getNestingLevel() -> returns($level) + ); + } + $this->_checking(Expectations::create() + -> ignoring($child)->toString() -> returns($string) + ); + if ($stub) { + $this->_checking(Expectations::create() + -> ignoring($child) + ); + } + + return $child; + } + + protected function _createEncoder($name = 'quoted-printable', $stub = true) + { + $encoder = $this->_mock('Swift_Mime_ContentEncoder'); + $this->_checking(Expectations::create() + -> ignoring($encoder)->getName() -> returns($name) + ); + + if ($stub) { + $this->_checking(Expectations::create() + -> ignoring($encoder)->encodeString(any(), optional()) + -> calls(array($this, 'returnStringFromEncoder')) + -> ignoring($encoder) + ); + } + + return $encoder; + } + + protected function _createCache($stub = true) + { + $cache = $this->_mock('Swift_KeyCache'); + + if ($stub) { + $this->_checking(Expectations::create() + -> ignoring($cache) + ); + } + + return $cache; + } + + protected function _createHeaderSet($headers = array(), $stub = true) + { + $set = $this->_mock('Swift_Mime_HeaderSet'); + foreach ($headers as $key => $header) { + $this->_checking(Expectations::create() + -> ignoring($set)->has($key) -> returns(true) + -> ignoring($set)->get($key) -> returns($header) + ); + } + if ($stub) { + $this->_checking(Expectations::create() + -> ignoring($set)->newInstance() -> returns($set) + -> ignoring($set) + ); + } + + return $set; + } + + protected function _createHeader($name, $model = null, $params = array(), $stub = true) + { + $header = $this->_mock('Swift_Mime_ParameterizedHeader'); + $this->_checking(Expectations::create() + -> ignoring($header)->getFieldName() -> returns($name) + -> ignoring($header)->getFieldBodyModel() -> returns($model) + ); + foreach ($params as $key => $value) { + $this->_checking(Expectations::create() + -> ignoring($header)->getParameter($key) -> returns($value) + ); + } + if ($stub) { + $this->_checking(Expectations::create() + -> ignoring($header) + ); + } + + return $header; + } + + protected function _createOutputStream($data = null, $stub = true) + { + $os = $this->_mock('Swift_OutputByteStream'); + if (isset($data)) { + $pos = $this->_mockery()->states('position')->startsAs('at beginning'); + $this->_checking(Expectations::create() + -> ignoring($os)->read(optional()) -> returns($data) + -> when($pos->isNot('at end')) -> then($pos->is('at end')) + + -> ignoring($os)->read(optional()) -> returns(false) + ); + if ($stub) { + $this->_checking(Expectations::create() + -> ignoring($os) + ); + } + } + + return $os; + } + + protected function _createInputStream($stub = true) + { + $is = $this->_mock('Swift_InputByteStream'); + if ($stub) { + $this->_checking(Expectations::create() + -> ignoring($is) + ); + } + + return $is; + } + + // -- Mock helpers + + public function returnStringFromEncoder(Yay_Invocation $invocation) + { + $args = $invocation->getArguments(); + + return array_shift($args); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AttachmentTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AttachmentTest.php new file mode 100644 index 0000000..f7817af --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AttachmentTest.php @@ -0,0 +1,298 @@ +_createAttachment($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual( + Swift_Mime_MimeEntity::LEVEL_MIXED, $attachment->getNestingLevel() + ); + } + + public function testDispositionIsReturnedFromHeader() + { + /* -- RFC 2183, 2.1, 2.2. + */ + + $disposition = $this->_createHeader('Content-Disposition', 'attachment'); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual('attachment', $attachment->getDisposition()); + } + + public function testDispositionIsSetInHeader() + { + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array(), false + ); + $this->_checking(Expectations::create() + -> one($disposition)->setFieldBodyModel('inline') + -> ignoring($disposition) + ); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition)), + $this->_createEncoder(), $this->_createCache() + ); + $attachment->setDisposition('inline'); + } + + public function testDispositionIsAddedIfNonePresent() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addParameterizedHeader('Content-Disposition', 'inline') + -> ignoring($headers) + ); + $attachment = $this->_createAttachment($headers, $this->_createEncoder(), + $this->_createCache() + ); + $attachment->setDisposition('inline'); + } + + public function testDispositionIsAutoDefaultedToAttachment() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addParameterizedHeader('Content-Disposition', 'attachment') + -> ignoring($headers) + ); + $attachment = $this->_createAttachment($headers, $this->_createEncoder(), + $this->_createCache() + ); + } + + public function testDefaultContentTypeInitializedToOctetStream() + { + $cType = $this->_createHeader('Content-Type', '', + array(), false + ); + $this->_checking(Expectations::create() + -> one($cType)->setFieldBodyModel('application/octet-stream') + -> ignoring($cType) + ); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + } + + public function testFilenameIsReturnedFromHeader() + { + /* -- RFC 2183, 2.3. + */ + + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array('filename'=>'foo.txt') + ); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual('foo.txt', $attachment->getFilename()); + } + + public function testFilenameIsSetInHeader() + { + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array('filename'=>'foo.txt'), false + ); + $this->_checking(Expectations::create() + -> one($disposition)->setParameter('filename', 'bar.txt') + -> ignoring($disposition) + ); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition)), + $this->_createEncoder(), $this->_createCache() + ); + $attachment->setFilename('bar.txt'); + } + + public function testSettingFilenameSetsNameInContentType() + { + /* + This is a legacy requirement which isn't covered by up-to-date RFCs. + */ + + $cType = $this->_createHeader('Content-Type', 'text/plain', + array(), false + ); + $this->_checking(Expectations::create() + -> one($cType)->setParameter('name', 'bar.txt') + -> ignoring($cType) + ); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $attachment->setFilename('bar.txt'); + } + + public function testSizeIsReturnedFromHeader() + { + /* -- RFC 2183, 2.7. + */ + + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array('size'=>1234) + ); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(1234, $attachment->getSize()); + } + + public function testSizeIsSetInHeader() + { + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array(), false + ); + $this->_checking(Expectations::create() + -> one($disposition)->setParameter('size', 12345) + -> ignoring($disposition) + ); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition)), + $this->_createEncoder(), $this->_createCache() + ); + $attachment->setSize(12345); + } + + public function testFilnameCanBeReadFromFileStream() + { + $file = $this->_createFileStream('/bar/file.ext', ''); + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array('filename'=>'foo.txt'), false + ); + $this->_checking(Expectations::create() + -> one($disposition)->setParameter('filename', 'file.ext') + -> ignoring($disposition) + ); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition)), + $this->_createEncoder(), $this->_createCache() + ); + $attachment->setFile($file); + } + + public function testContentTypeCanBeSetViaSetFile() + { + $file = $this->_createFileStream('/bar/file.ext', ''); + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array('filename'=>'foo.txt'), false + ); + $ctype = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $headers = $this->_createHeaderSet(array( + 'Content-Disposition' => $disposition, + 'Content-Type' => $ctype + )); + $this->_checking(Expectations::create() + -> one($disposition)->setParameter('filename', 'file.ext') + -> one($ctype)->setFieldBodyModel('text/html') + -> ignoring($disposition) + -> ignoring($ctype) + ); + $attachment = $this->_createAttachment($headers, $this->_createEncoder(), + $this->_createCache() + ); + $attachment->setFile($file, 'text/html'); + } + + public function XtestContentTypeCanBeLookedUpFromCommonListIfNotProvided() + { + $file = $this->_createFileStream('/bar/file.zip', ''); + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array('filename'=>'foo.zip'), false + ); + $ctype = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $headers = $this->_createHeaderSet(array( + 'Content-Disposition' => $disposition, + 'Content-Type' => $ctype + )); + $this->_checking(Expectations::create() + -> one($disposition)->setParameter('filename', 'file.zip') + -> one($ctype)->setFieldBodyModel('application/zip') + -> ignoring($disposition) + -> ignoring($ctype) + ); + $attachment = $this->_createAttachment($headers, $this->_createEncoder(), + $this->_createCache(), array('zip'=>'application/zip', 'txt'=>'text/plain') + ); + $attachment->setFile($file); + } + + public function testDataCanBeReadFromFile() + { + $file = $this->_createFileStream('/foo/file.ext', ''); + $attachment = $this->_createAttachment($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $attachment->setFile($file); + $this->assertEqual('', $attachment->getBody()); + } + + public function testFluidInterface() + { + $attachment = $this->_createAttachment($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertSame($attachment, + $attachment + ->setContentType('application/pdf') + ->setEncoder($this->_createEncoder()) + ->setId('foo@bar') + ->setDescription('my pdf') + ->setMaxLineLength(998) + ->setBody('xx') + ->setBoundary('xyz') + ->setChildren(array()) + ->setDisposition('inline') + ->setFilename('afile.txt') + ->setSize(123) + ->setFile($this->_createFileStream('foo.txt', '')) + ); + } + + // -- Private helpers + + protected function _createEntity($headers, $encoder, $cache) + { + return $this->_createAttachment($headers, $encoder, $cache); + } + + protected function _createAttachment($headers, $encoder, $cache, + $mimeTypes = array()) + { + return new Swift_Mime_Attachment($headers, $encoder, $cache, new Swift_Mime_Grammar(), $mimeTypes); + } + + protected function _createFileStream($path, $data, $stub = true) + { + $file = $this->_mock('Swift_FileStream'); + $pos = $this->_mockery()->states('position')->startsAs('at start'); + $this->_checking(Expectations::create() + -> ignoring($file)->getPath() -> returns($path) + -> ignoring($file)->read(optional()) -> returns($data) + -> when($pos->isNot('at end')) -> then($pos->is('at end')) + -> ignoring($file)->read(optional()) -> returns(false) + ); + if ($stub) { + $this->_checking(Expectations::create() + -> ignoring($file) + ); + } + + return $file; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/Base64ContentEncoderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/Base64ContentEncoderTest.php new file mode 100644 index 0000000..2f02765 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/Base64ContentEncoderTest.php @@ -0,0 +1,295 @@ +getArguments(); + $this->content .= current($args); + } + public function describeTo(Yay_Description $description) + { + $description->appendText(' gathers input;'); + } +} + +class Swift_Mime_ContentEncoder_Base64ContentEncoderTest + extends Swift_Tests_SwiftUnitTestCase +{ + private $_encoder; + + public function setUp() + { + $this->_encoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + } + + public function testNameIsBase64() + { + $this->assertEqual('base64', $this->_encoder->getName()); + } + + /* + There's really no point in testing the entire base64 encoding to the + level QP encoding has been tested. base64_encode() has been in PHP for + years. + */ + + public function testInputOutputRatioIs3to4Bytes() + { + /* + RFC 2045, 6.8 + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + */ + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('123') + -> allowing($os)->read(optional()) -> returns(false) + + -> ignoring($os) + ); + + $this->_encoder->encodeByteStream($os, $is); + $this->assertEqual('MTIz', $collection->content); + } + + public function testPadLength() + { + /* + RFC 2045, 6.8 + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a body. When fewer than 24 input bits + are available in an input group, zero bits are added (on the right) + to form an integral number of 6-bit groups. Padding at the end of + the data is performed using the "=" character. Since all base64 + input is an integral number of octets, only the following cases can + arise: (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded output will be + an integral multiple of 4 characters with no "=" padding, (2) the + final quantum of encoding input is exactly 8 bits; here, the final + unit of encoded output will be two characters followed by two "=" + padding characters, or (3) the final quantum of encoding input is + exactly 16 bits; here, the final unit of encoded output will be three + characters followed by one "=" padding character. + */ + + for ($i = 0; $i < 30; ++$i) { + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns(pack('C', rand(0, 255))) + -> allowing($os)->read(optional()) -> returns(false) + -> ignoring($os) + ); + + $this->_encoder->encodeByteStream($os, $is); + $this->assertPattern('~^[a-zA-Z0-9/\+]{2}==$~', $collection->content, + '%s: A single byte should have 2 bytes of padding' + ); + } + + for ($i = 0; $i < 30; ++$i) { + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns(pack('C*', rand(0, 255), rand(0, 255))) + -> allowing($os)->read(optional()) -> returns(false) + -> ignoring($os) + ); + + $this->_encoder->encodeByteStream($os, $is); + $this->assertPattern('~^[a-zA-Z0-9/\+]{3}=$~', $collection->content, + '%s: Two bytes should have 1 byte of padding' + ); + } + + for ($i = 0; $i < 30; ++$i) { + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns(pack('C*', rand(0, 255), rand(0, 255), rand(0, 255))) + -> allowing($os)->read(optional()) -> returns(false) + -> ignoring($os) + ); + + $this->_encoder->encodeByteStream($os, $is); + $this->assertPattern('~^[a-zA-Z0-9/\+]{4}$~', $collection->content, + '%s: Three bytes should have no padding' + ); + } + } + + public function testMaximumLineLengthIs76Characters() + { + /* + The encoded output stream must be represented in lines of no more + than 76 characters each. All line breaks or other characters not + found in Table 1 must be ignored by decoding software. + */ + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('abcdefghijkl') //12 + -> one($os)->read(optional()) -> returns('mnopqrstuvwx') //24 + -> one($os)->read(optional()) -> returns('yzabc1234567') //36 + -> one($os)->read(optional()) -> returns('890ABCDEFGHI') //48 + -> one($os)->read(optional()) -> returns('JKLMNOPQRSTU') //60 + -> one($os)->read(optional()) -> returns('VWXYZ1234567') //72 + -> one($os)->read(optional()) -> returns('abcdefghijkl') //84 + -> allowing($os)->read(optional()) -> returns(false) + -> ignoring($os) + ); + + $this->_encoder->encodeByteStream($os, $is); + $this->assertEqual( + "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3ODkwQUJDREVGR0hJSktMTU5PUFFS\r\n" . + "U1RVVldYWVoxMjM0NTY3YWJjZGVmZ2hpamts", + $collection->content + ); + } + + public function testMaximumLineLengthCanBeDifferent() + { + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('abcdefghijkl') //12 + -> one($os)->read(optional()) -> returns('mnopqrstuvwx') //24 + -> one($os)->read(optional()) -> returns('yzabc1234567') //36 + -> one($os)->read(optional()) -> returns('890ABCDEFGHI') //48 + -> one($os)->read(optional()) -> returns('JKLMNOPQRSTU') //60 + -> one($os)->read(optional()) -> returns('VWXYZ1234567') //72 + -> one($os)->read(optional()) -> returns('abcdefghijkl') //84 + -> allowing($os)->read(optional()) -> returns(false) + -> ignoring($os) + ); + + $this->_encoder->encodeByteStream($os, $is, 0, 50); + $this->assertEqual( + "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3OD\r\n" . + "kwQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVoxMjM0NTY3YWJj\r\n" . + "ZGVmZ2hpamts", + $collection->content + ); + } + + public function testMaximumLineLengthIsNeverMoreThan76Chars() + { + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('abcdefghijkl') //12 + -> one($os)->read(optional()) -> returns('mnopqrstuvwx') //24 + -> one($os)->read(optional()) -> returns('yzabc1234567') //36 + -> one($os)->read(optional()) -> returns('890ABCDEFGHI') //48 + -> one($os)->read(optional()) -> returns('JKLMNOPQRSTU') //60 + -> one($os)->read(optional()) -> returns('VWXYZ1234567') //72 + -> one($os)->read(optional()) -> returns('abcdefghijkl') //84 + -> allowing($os)->read(optional()) -> returns(false) + -> ignoring($os) + ); + + $this->_encoder->encodeByteStream($os, $is, 0, 100); + $this->assertEqual( + "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3ODkwQUJDREVGR0hJSktMTU5PUFFS\r\n" . + "U1RVVldYWVoxMjM0NTY3YWJjZGVmZ2hpamts", + $collection->content + ); + } + + public function testFirstLineLengthCanBeDifferent() + { + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('abcdefghijkl') //12 + -> one($os)->read(optional()) -> returns('mnopqrstuvwx') //24 + -> one($os)->read(optional()) -> returns('yzabc1234567') //36 + -> one($os)->read(optional()) -> returns('890ABCDEFGHI') //48 + -> one($os)->read(optional()) -> returns('JKLMNOPQRSTU') //60 + -> one($os)->read(optional()) -> returns('VWXYZ1234567') //72 + -> one($os)->read(optional()) -> returns('abcdefghijkl') //84 + -> allowing($os)->read(optional()) -> returns(false) + -> ignoring($os) + ); + + $this->_encoder->encodeByteStream($os, $is, 19); + $this->assertEqual( + "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3ODkwQUJDR\r\n" . + "EVGR0hJSktMTU5PUFFSU1RVVldYWVoxMjM0NTY3YWJjZGVmZ2hpamts", + $collection->content + ); + } + + // -- Private Methods + + private function _createOutputByteStream($stub = false) + { + return $stub + ? $this->_stub('Swift_OutputByteStream') + : $this->_mock('Swift_OutputByteStream') + ; + } + + private function _createInputByteStream($stub = false) + { + return $stub + ? $this->_stub('Swift_InputByteStream') + : $this->_mock('Swift_InputByteStream') + ; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/PlainContentEncoderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/PlainContentEncoderTest.php new file mode 100644 index 0000000..5da1aa0 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/PlainContentEncoderTest.php @@ -0,0 +1,277 @@ +getArguments(); + $this->content .= current($args); + } + public function describeTo(Yay_Description $description) + { + $description->appendText(' gathers input;'); + } +} + +class Swift_Mime_ContentEncoder_PlainContentEncoderTest + extends Swift_Tests_SwiftUnitTestCase +{ + public function testNameCanBeSpecifiedInConstructor() + { + $encoder = $this->_getEncoder('7bit'); + $this->assertEqual('7bit', $encoder->getName()); + + $encoder = $this->_getEncoder('8bit'); + $this->assertEqual('8bit', $encoder->getName()); + } + + public function testNoOctetsAreModifiedInString() + { + $encoder = $this->_getEncoder('7bit'); + foreach (range(0x00, 0xFF) as $octet) { + $byte = pack('C', $octet); + $this->assertIdenticalBinary($byte, $encoder->encodeString($byte)); + } + } + + public function testNoOctetsAreModifiedInByteStream() + { + $encoder = $this->_getEncoder('7bit'); + foreach (range(0x00, 0xFF) as $octet) { + $byte = pack('C', $octet); + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns($byte) + -> allowing($os)->read(optional()) -> returns(false) + + -> ignoring($os) + ); + + $encoder->encodeByteStream($os, $is); + $this->assertIdenticalBinary($byte, $collection->content); + } + } + + public function testLineLengthCanBeSpecified() + { + $encoder = $this->_getEncoder('7bit'); + + $chars = array(); + for ($i = 0; $i < 50; $i++) { + $chars[] = 'a'; + } + $input = implode(' ', $chars); //99 chars long + + $this->assertEqual( + 'a a a a a a a a a a a a a a a a a a a a a a a a a ' . "\r\n" . //50 * + 'a a a a a a a a a a a a a a a a a a a a a a a a a', //99 + $encoder->encodeString($input, 0, 50), + '%s: Lines should be wrapped at 50 chars' + ); + } + + public function testLineLengthCanBeSpecifiedInByteStream() + { + $encoder = $this->_getEncoder('7bit'); + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + ); + + for ($i = 0; $i < 50; $i++) { + $this->_checking(Expectations::create() + -> one($os)->read(optional()) -> returns('a ') + ); + } + + $this->_checking(Expectations::create() + -> allowing($os)->read(optional()) -> returns(false) + ); + + $encoder->encodeByteStream($os, $is, 0, 50); + $this->assertEqual( + str_repeat('a ', 25) . "\r\n" . str_repeat('a ', 25), + $collection->content + ); + } + + public function testencodeStringGeneratesCorrectCrlf() + { + $encoder = $this->_getEncoder('7bit', true); + $this->assertEqual("a\r\nb", $encoder->encodeString("a\rb"), + '%s: Line endings should be standardized' + ); + $this->assertEqual("a\r\nb", $encoder->encodeString("a\nb"), + '%s: Line endings should be standardized' + ); + $this->assertEqual("a\r\n\r\nb", $encoder->encodeString("a\n\rb"), + '%s: Line endings should be standardized' + ); + $this->assertEqual("a\r\n\r\nb", $encoder->encodeString("a\r\rb"), + '%s: Line endings should be standardized' + ); + $this->assertEqual("a\r\n\r\nb", $encoder->encodeString("a\n\nb"), + '%s: Line endings should be standardized' + ); + } + + public function testCanonicEncodeByteStreamGeneratesCorrectCrlf_1() + { + $encoder = $this->_getEncoder('7bit', true); + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('a') + -> one($os)->read(optional()) -> returns("\r") + -> one($os)->read(optional()) -> returns('b') + -> allowing($os)->read(optional()) -> returns(false) + + -> ignoring($os) + ); + + $encoder->encodeByteStream($os, $is); + $this->assertEqual("a\r\nb", $collection->content); + } + + public function testCanonicEncodeByteStreamGeneratesCorrectCrlf_2() + { + $encoder = $this->_getEncoder('7bit', true); + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('a') + -> one($os)->read(optional()) -> returns("\n") + -> one($os)->read(optional()) -> returns('b') + -> allowing($os)->read(optional()) -> returns(false) + + -> ignoring($os) + ); + + $encoder->encodeByteStream($os, $is); + $this->assertEqual("a\r\nb", $collection->content); + } + + public function testCanonicEncodeByteStreamGeneratesCorrectCrlf_3() + { + $encoder = $this->_getEncoder('7bit', true); + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('a') + -> one($os)->read(optional()) -> returns("\n\r") + -> one($os)->read(optional()) -> returns('b') + -> allowing($os)->read(optional()) -> returns(false) + + -> ignoring($os) + ); + + $encoder->encodeByteStream($os, $is); + $this->assertEqual("a\r\n\r\nb", $collection->content); + } + + public function testCanonicEncodeByteStreamGeneratesCorrectCrlf_4() + { + $encoder = $this->_getEncoder('7bit', true); + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('a') + -> one($os)->read(optional()) -> returns("\n\n") + -> one($os)->read(optional()) -> returns('b') + -> allowing($os)->read(optional()) -> returns(false) + + -> ignoring($os) + ); + + $encoder->encodeByteStream($os, $is); + $this->assertEqual("a\r\n\r\nb", $collection->content); + } + + public function testCanonicEncodeByteStreamGeneratesCorrectCrlf_5() + { + $encoder = $this->_getEncoder('7bit', true); + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> one($os)->read(optional()) -> returns('a') + -> one($os)->read(optional()) -> returns("\r\r") + -> one($os)->read(optional()) -> returns('b') + -> allowing($os)->read(optional()) -> returns(false) + + -> ignoring($os) + ); + + $encoder->encodeByteStream($os, $is); + $this->assertEqual("a\r\n\r\nb", $collection->content); + } + + // -- Private helpers + + private function _getEncoder($name, $canonical = false) + { + return new Swift_Mime_ContentEncoder_PlainContentEncoder($name, $canonical); + } + + private function _createOutputByteStream($stub = false) + { + return $stub + ? $this->_stub('Swift_OutputByteStream') + : $this->_mock('Swift_OutputByteStream') + ; + } + + private function _createInputByteStream($stub = false) + { + return $stub + ? $this->_stub('Swift_InputByteStream') + : $this->_mock('Swift_InputByteStream') + ; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/QpContentEncoderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/QpContentEncoderTest.php new file mode 100644 index 0000000..0682f91 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/QpContentEncoderTest.php @@ -0,0 +1,476 @@ +getArguments(); + $this->content .= current($args); + } + public function describeTo(Yay_Description $description) + { + $description->appendText(' gathers input;'); + } +} + +class Swift_Mime_ContentEncoder_QpContentEncoderTest + extends Swift_Tests_SwiftUnitTestCase +{ + public function testNameIsQuotedPrintable() + { + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder( + $this->_createCharacterStream(true) + ); + $this->assertEqual('quoted-printable', $encoder->getName()); + } + + /* -- RFC 2045, 6.7 -- + (1) (General 8bit representation) Any octet, except a CR or + LF that is part of a CRLF line break of the canonical + (standard) form of the data being encoded, may be + represented by an "=" followed by a two digit + hexadecimal representation of the octet's value. The + digits of the hexadecimal alphabet, for this purpose, + are "0123456789ABCDEF". Uppercase letters must be + used; lowercase letters are not allowed. Thus, for + example, the decimal value 12 (US-ASCII form feed) can + be represented by "=0C", and the decimal value 61 (US- + ASCII EQUAL SIGN) can be represented by "=3D". This + rule must be followed except when the following rules + allow an alternative encoding. + */ + + public function testPermittedCharactersAreNotEncoded() + { + /* -- RFC 2045, 6.7 -- + (2) (Literal representation) Octets with decimal values of + 33 through 60 inclusive, and 62 through 126, inclusive, + MAY be represented as the US-ASCII characters which + correspond to those octets (EXCLAMATION POINT through + LESS THAN, and GREATER THAN through TILDE, + respectively). + */ + + foreach (array_merge(range(33, 60), range(62, 126)) as $ordinal) { + $char = chr($ordinal); + + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + -> one($charStream)->readBytes(any()) -> returns(array($ordinal)) + -> allowing($charStream)->readBytes(any()) -> returns(false) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + $this->assertIdenticalBinary($char, $collection->content); + } + } + + public function testLinearWhiteSpaceAtLineEndingIsEncoded() + { + /* -- RFC 2045, 6.7 -- + (3) (White Space) Octets with values of 9 and 32 MAY be + represented as US-ASCII TAB (HT) and SPACE characters, + respectively, but MUST NOT be so represented at the end + of an encoded line. Any TAB (HT) or SPACE characters + on an encoded line MUST thus be followed on that line + by a printable character. In particular, an "=" at the + end of an encoded line, indicating a soft line break + (see rule #5) may follow one or more TAB (HT) or SPACE + characters. It follows that an octet with decimal + value 9 or 32 appearing at the end of an encoded line + must be represented according to Rule #1. This rule is + necessary because some MTAs (Message Transport Agents, + programs which transport messages from one user to + another, or perform a portion of such transfers) are + known to pad lines of text with SPACEs, and others are + known to remove "white space" characters from the end + of a line. Therefore, when decoding a Quoted-Printable + body, any trailing white space on a line must be + deleted, as it will necessarily have been added by + intermediate transport agents. + */ + + $HT = chr(0x09); //9 + $SPACE = chr(0x20); //32 + + //HT + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + -> one($charStream)->readBytes(any()) -> returns(array(ord('a'))) + -> one($charStream)->readBytes(any()) -> returns(array(0x09)) + -> one($charStream)->readBytes(any()) -> returns(array(0x09)) + -> one($charStream)->readBytes(any()) -> returns(array(0x0D)) + -> one($charStream)->readBytes(any()) -> returns(array(0x0A)) + -> one($charStream)->readBytes(any()) -> returns(array(ord('b'))) + -> allowing($charStream)->readBytes(any()) -> returns(false) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + + $this->assertEqual("a\t=09\r\nb", $collection->content); + + //SPACE + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + -> one($charStream)->readBytes(any()) -> returns(array(ord('a'))) + -> one($charStream)->readBytes(any()) -> returns(array(0x20)) + -> one($charStream)->readBytes(any()) -> returns(array(0x20)) + -> one($charStream)->readBytes(any()) -> returns(array(0x0D)) + -> one($charStream)->readBytes(any()) -> returns(array(0x0A)) + -> one($charStream)->readBytes(any()) -> returns(array(ord('b'))) + -> allowing($charStream)->readBytes(any()) -> returns(false) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + + $this->assertEqual("a =20\r\nb", $collection->content); + } + + public function testCRLFIsLeftAlone() + { + /* + (4) (Line Breaks) A line break in a text body, represented + as a CRLF sequence in the text canonical form, must be + represented by a (RFC 822) line break, which is also a + CRLF sequence, in the Quoted-Printable encoding. Since + the canonical representation of media types other than + text do not generally include the representation of + line breaks as CRLF sequences, no hard line breaks + (i.e. line breaks that are intended to be meaningful + and to be displayed to the user) can occur in the + quoted-printable encoding of such types. Sequences + like "=0D", "=0A", "=0A=0D" and "=0D=0A" will routinely + appear in non-text data represented in quoted- + printable, of course. + + Note that many implementations may elect to encode the + local representation of various content types directly + rather than converting to canonical form first, + encoding, and then converting back to local + representation. In particular, this may apply to plain + text material on systems that use newline conventions + other than a CRLF terminator sequence. Such an + implementation optimization is permissible, but only + when the combined canonicalization-encoding step is + equivalent to performing the three steps separately. + */ + + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + -> one($charStream)->readBytes(any()) -> returns(array(ord('a'))) + -> one($charStream)->readBytes(any()) -> returns(array(0x0D)) + -> one($charStream)->readBytes(any()) -> returns(array(0x0A)) + -> one($charStream)->readBytes(any()) -> returns(array(ord('b'))) + -> one($charStream)->readBytes(any()) -> returns(array(0x0D)) + -> one($charStream)->readBytes(any()) -> returns(array(0x0A)) + -> one($charStream)->readBytes(any()) -> returns(array(ord('c'))) + -> one($charStream)->readBytes(any()) -> returns(array(0x0D)) + -> one($charStream)->readBytes(any()) -> returns(array(0x0A)) + -> allowing($charStream)->readBytes(any()) -> returns(false) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + $this->assertEqual("a\r\nb\r\nc\r\n", $collection->content); + } + + public function testLinesLongerThan76CharactersAreSoftBroken() + { + /* + (5) (Soft Line Breaks) The Quoted-Printable encoding + REQUIRES that encoded lines be no more than 76 + characters long. If longer lines are to be encoded + with the Quoted-Printable encoding, "soft" line breaks + must be used. An equal sign as the last character on a + encoded line indicates such a non-significant ("soft") + line break in the encoded text. + */ + + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + for ($seq = 0; $seq <= 140; ++$seq) { + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(array(ord('a'))) + ); + } + $this->_checking(Expectations::create() + -> allowing($charStream)->readBytes(any()) -> returns(false) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + $this->assertEqual(str_repeat('a', 75) . "=\r\n" . str_repeat('a', 66), $collection->content); + } + + public function testMaxLineLengthCanBeSpecified() + { + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + + for ($seq = 0; $seq <= 100; ++$seq) { + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(array(ord('a'))) + ); + } + + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(false) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is, 0, 54); + $this->assertEqual(str_repeat('a', 53) . "=\r\n" . str_repeat('a', 48), $collection->content); + } + + public function testBytesBelowPermittedRangeAreEncoded() + { + /* + According to Rule (1 & 2) + */ + + foreach (range(0, 32) as $ordinal) { + $char = chr($ordinal); + + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + + -> one($charStream)->readBytes(any()) -> returns(array($ordinal)) + -> allowing($charStream)->readBytes(any()) -> returns(false) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + $this->assertEqual(sprintf('=%02X', $ordinal), $collection->content); + } + } + + public function testDecimalByte61IsEncoded() + { + /* + According to Rule (1 & 2) + */ + + $char = chr(61); + + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + + -> one($charStream)->readBytes(any()) -> returns(array(61)) + -> allowing($charStream)->readBytes(any()) -> returns(false) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + $this->assertEqual(sprintf('=%02X', 61), $collection->content); + } + + public function testBytesAbovePermittedRangeAreEncoded() + { + /* + According to Rule (1 & 2) + */ + + foreach (range(127, 255) as $ordinal) { + $char = chr($ordinal); + + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + + -> one($charStream)->readBytes(any()) -> returns(array($ordinal)) + -> allowing($charStream)->readBytes(any()) -> returns(false) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + $this->assertEqual(sprintf('=%02X', $ordinal), $collection->content); + } + } + + public function testFirstLineLengthCanBeDifferent() + { + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $this->_checking(Expectations::create() + -> one($charStream)->flushContents() + -> one($charStream)->importByteStream($os) + + -> allowing($is)->write(any(), optional()) -> will($collection) + -> ignoring($is) + + -> ignoring($os) + ); + + for ($seq = 0; $seq <= 140; ++$seq) { + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(array(ord('a'))) + ); + } + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(false) + ); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is, 22); + $this->assertEqual( + str_repeat('a', 53) . "=\r\n" . str_repeat('a', 75) . "=\r\n" . str_repeat('a', 13), + $collection->content + ); + } + + public function testObserverInterfaceCanChangeCharset() + { + $stream = $this->_createCharacterStream(); + $this->_checking(Expectations::create() + -> one($stream)->setCharacterSet('windows-1252') + -> ignoring($stream) + ); + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($stream); + $encoder->charsetChanged('windows-1252'); + } + + // -- Creation Methods + + private function _createCharacterStream($stub = false) + { + return $stub + ? $this->_stub('Swift_CharacterStream') + : $this->_mock('Swift_CharacterStream') + ; + } + + private function _createEncoder($charStream) + { + return new Swift_Mime_HeaderEncoder_QpHeaderEncoder($charStream); + } + + private function _createOutputByteStream($stub = false) + { + return $stub + ? $this->_stub('Swift_OutputByteStream') + : $this->_mock('Swift_OutputByteStream') + ; + } + + private function _createInputByteStream($stub = false) + { + return $stub + ? $this->_stub('Swift_InputByteStream') + : $this->_mock('Swift_InputByteStream') + ; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/EmbeddedFileTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/EmbeddedFileTest.php new file mode 100644 index 0000000..4b5c711 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/EmbeddedFileTest.php @@ -0,0 +1,63 @@ +_createEmbeddedFile($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual( + Swift_Mime_MimeEntity::LEVEL_RELATED, $file->getNestingLevel() + ); + } + + public function testIdIsAutoGenerated() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addIdHeader('Content-ID', pattern('/^.*?@.*?$/D')) + -> ignoring($headers) + ); + $file = $this->_createEmbeddedFile($headers, $this->_createEncoder(), + $this->_createCache() + ); + } + + public function testDefaultDispositionIsAttachment() + { //Overridden + } + + public function testDefaultDispositionIsInline() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addParameterizedHeader('Content-Disposition', 'inline') + -> ignoring($headers) + ); + $file = $this->_createEmbeddedFile($headers, $this->_createEncoder(), + $this->_createCache() + ); + } + + // -- Private helpers + + protected function _createAttachment($headers, $encoder, $cache, + $mimeTypes = array()) + { + return $this->_createEmbeddedFile($headers, $encoder, $cache, $mimeTypes); + } + + private function _createEmbeddedFile($headers, $encoder, $cache) + { + return new Swift_Mime_EmbeddedFile($headers, $encoder, $cache, new Swift_Mime_Grammar()); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/Base64HeaderEncoderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/Base64HeaderEncoderTest.php new file mode 100644 index 0000000..780d353 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/Base64HeaderEncoderTest.php @@ -0,0 +1,15 @@ +assertEqual('B', $encoder->getName()); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/QpHeaderEncoderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/QpHeaderEncoderTest.php new file mode 100644 index 0000000..0b61c1c --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/QpHeaderEncoderTest.php @@ -0,0 +1,222 @@ +_createEncoder( + $this->_createCharacterStream(true) + ); + $this->assertEqual('Q', $encoder->getName()); + } + + public function testSpaceAndTabNeverAppear() + { + /* -- RFC 2047, 4. + Only a subset of the printable ASCII characters may be used in + 'encoded-text'. Space and tab characters are not allowed, so that + the beginning and end of an 'encoded-word' are obvious. + */ + + $charStream = $this->_createCharacterStream(); + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(array(ord('a'))) + -> one($charStream)->readBytes(any()) -> returns(array(0x20)) + -> one($charStream)->readBytes(any()) -> returns(array(0x09)) + -> one($charStream)->readBytes(any()) -> returns(array(0x20)) + -> one($charStream)->readBytes(any()) -> returns(array(ord('b'))) + -> allowing($charStream)->readBytes(any()) -> returns(false) + -> ignoring($charStream) + ); + + $encoder = $this->_createEncoder($charStream); + $this->assertNoPattern('~[ \t]~', $encoder->encodeString("a \t b"), + '%s: encoded-words in headers cannot contain LWSP as per RFC 2047.' + ); + } + + public function testSpaceIsRepresentedByUnderscore() + { + /* -- RFC 2047, 4.2. + (2) The 8-bit hexadecimal value 20 (e.g., ISO-8859-1 SPACE) may be + represented as "_" (underscore, ASCII 95.). (This character may + not pass through some internetwork mail gateways, but its use + will greatly enhance readability of "Q" encoded data with mail + readers that do not support this encoding.) Note that the "_" + always represents hexadecimal 20, even if the SPACE character + occupies a different code position in the character set in use. + */ + $charStream = $this->_createCharacterStream(); + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(array(ord('a'))) + -> one($charStream)->readBytes(any()) -> returns(array(0x20)) + -> one($charStream)->readBytes(any()) -> returns(array(ord('b'))) + -> allowing($charStream)->readBytes(any()) -> returns(false) + -> ignoring($charStream) + ); + + $encoder = $this->_createEncoder($charStream); + $this->assertEqual('a_b', $encoder->encodeString('a b'), + '%s: Spaces can be represented by more readable underscores as per RFC 2047.' + ); + } + + public function testEqualsAndQuestionAndUnderscoreAreEncoded() + { + /* -- RFC 2047, 4.2. + (3) 8-bit values which correspond to printable ASCII characters other + than "=", "?", and "_" (underscore), MAY be represented as those + characters. (But see section 5 for restrictions.) In + particular, SPACE and TAB MUST NOT be represented as themselves + within encoded words. + */ + $charStream = $this->_createCharacterStream(); + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(array(ord('='))) + -> one($charStream)->readBytes(any()) -> returns(array(ord('?'))) + -> one($charStream)->readBytes(any()) -> returns(array(ord('_'))) + -> allowing($charStream)->readBytes(any()) -> returns(false) + -> ignoring($charStream) + ); + + $encoder = $this->_createEncoder($charStream); + $this->assertEqual('=3D=3F=5F', $encoder->encodeString('=?_'), + '%s: Chars =, ? and _ (underscore) may not appear as per RFC 2047.' + ); + } + + public function testParensAndQuotesAreEncoded() + { + /* -- RFC 2047, 5 (2). + A "Q"-encoded 'encoded-word' which appears in a 'comment' MUST NOT + contain the characters "(", ")" or " + */ + + $charStream = $this->_createCharacterStream(); + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(array(ord('('))) + -> one($charStream)->readBytes(any()) -> returns(array(ord('"'))) + -> one($charStream)->readBytes(any()) -> returns(array(ord(')'))) + -> allowing($charStream)->readBytes(any()) -> returns(false) + -> ignoring($charStream) + ); + + $encoder = $this->_createEncoder($charStream); + $this->assertEqual('=28=22=29', $encoder->encodeString('(")'), + '%s: Chars (, " (DQUOTE) and ) may not appear as per RFC 2047.' + ); + } + + public function testOnlyCharactersAllowedInPhrasesAreUsed() + { + /* -- RFC 2047, 5. + (3) As a replacement for a 'word' entity within a 'phrase', for example, + one that precedes an address in a From, To, or Cc header. The ABNF + definition for 'phrase' from RFC 822 thus becomes: + + phrase = 1*( encoded-word / word ) + + In this case the set of characters that may be used in a "Q"-encoded + 'encoded-word' is restricted to: . An 'encoded-word' that appears within a + 'phrase' MUST be separated from any adjacent 'word', 'text' or + 'special' by 'linear-white-space'. + */ + + $allowedBytes = array_merge( + range(ord('a'), ord('z')), range(ord('A'), ord('Z')), + range(ord('0'), ord('9')), + array(ord('!'), ord('*'), ord('+'), ord('-'), ord('/')) + ); + + foreach (range(0x00, 0xFF) as $byte) { + $char = pack('C', $byte); + + $charStream = $this->_createCharacterStream(); + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(array($byte)) + -> allowing($charStream)->readBytes(any()) -> returns(false) + -> ignoring($charStream) + ); + + $encoder = $this->_createEncoder($charStream); + $encodedChar = $encoder->encodeString($char); + + if (in_array($byte, $allowedBytes)) { + $this->assertEqual($char, $encodedChar, + '%s: Character ' . $char . ' should not be encoded.' + ); + } elseif (0x20 == $byte) //Special case + { + $this->assertEqual('_', $encodedChar, + '%s: Space character should be replaced.' + ); + } else { + $this->assertEqual(sprintf('=%02X', $byte), $encodedChar, + '%s: Byte ' . $byte . ' should be encoded.' + ); + } + } + } + + public function testEqualsNeverAppearsAtEndOfLine() + { + /* -- RFC 2047, 5 (3). + The 'encoded-text' in an 'encoded-word' must be self-contained; + 'encoded-text' MUST NOT be continued from one 'encoded-word' to + another. This implies that the 'encoded-text' portion of a "B" + 'encoded-word' will be a multiple of 4 characters long; for a "Q" + 'encoded-word', any "=" character that appears in the 'encoded-text' + portion will be followed by two hexadecimal characters. + */ + + $input = str_repeat('a', 140); + + $charStream = $this->_createCharacterStream(); + + $output = ''; + $seq = 0; + for (; $seq < 140; ++$seq) { + $this->_checking(Expectations::create() + -> one($charStream)->readBytes(any()) -> returns(array(ord('a'))) + ); + + if (75 == $seq) { + $output .= "\r\n"; // =\r\n + } + $output .= 'a'; + } + + $this->_checking(Expectations::create() + -> allowing($charStream)->readBytes(any()) -> returns(false) + -> ignoring($charStream) + ); + + $encoder = $this->_createEncoder($charStream); + $this->assertEqual($output, $encoder->encodeString($input)); + } + + // -- Creation Methods + + private function _createEncoder($charStream) + { + return new Swift_Mime_HeaderEncoder_QpHeaderEncoder($charStream); + } + + private function _createCharacterStream($stub = false) + { + return $stub + ? $this->_stub('Swift_CharacterStream') + : $this->_mock('Swift_CharacterStream') + ; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/DateHeaderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/DateHeaderTest.php new file mode 100644 index 0000000..3fa3adf --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/DateHeaderTest.php @@ -0,0 +1,76 @@ +_getHeader('Date'); + $this->assertEqual(Swift_Mime_Header::TYPE_DATE, $header->getFieldType()); + } + + public function testGetTimestamp() + { + $timestamp = time(); + $header = $this->_getHeader('Date'); + $header->setTimestamp($timestamp); + $this->assertIdentical($timestamp, $header->getTimestamp()); + } + + public function testTimestampCanBeSetBySetter() + { + $timestamp = time(); + $header = $this->_getHeader('Date'); + $header->setTimestamp($timestamp); + $this->assertIdentical($timestamp, $header->getTimestamp()); + } + + public function testIntegerTimestampIsConvertedToRfc2822Date() + { + $timestamp = time(); + $header = $this->_getHeader('Date'); + $header->setTimestamp($timestamp); + $this->assertEqual(date('r', $timestamp), $header->getFieldBody()); + } + + public function testSetBodyModel() + { + $timestamp = time(); + $header = $this->_getHeader('Date'); + $header->setFieldBodyModel($timestamp); + $this->assertEqual(date('r', $timestamp), $header->getFieldBody()); + } + + public function testGetBodyModel() + { + $timestamp = time(); + $header = $this->_getHeader('Date'); + $header->setTimestamp($timestamp); + $this->assertEqual($timestamp, $header->getFieldBodyModel()); + } + + public function testToString() + { + $timestamp = time(); + $header = $this->_getHeader('Date'); + $header->setTimestamp($timestamp); + $this->assertEqual('Date: ' . date('r', $timestamp) . "\r\n", + $header->toString() + ); + } + + // -- Private methods + + private function _getHeader($name) + { + return new Swift_Mime_Headers_DateHeader($name, new Swift_Mime_Grammar()); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/IdentificationHeaderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/IdentificationHeaderTest.php new file mode 100644 index 0000000..bf4ae96 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/IdentificationHeaderTest.php @@ -0,0 +1,199 @@ +_getHeader('Message-ID'); + $this->assertEqual(Swift_Mime_Header::TYPE_ID, $header->getFieldType()); + } + + public function testValueMatchesMsgIdSpec() + { + /* -- RFC 2822, 3.6.4. + message-id = "Message-ID:" msg-id CRLF + + in-reply-to = "In-Reply-To:" 1*msg-id CRLF + + references = "References:" 1*msg-id CRLF + + msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS] + + id-left = dot-atom-text / no-fold-quote / obs-id-left + + id-right = dot-atom-text / no-fold-literal / obs-id-right + + no-fold-quote = DQUOTE *(qtext / quoted-pair) DQUOTE + + no-fold-literal = "[" *(dtext / quoted-pair) "]" + */ + + $header = $this->_getHeader('Message-ID'); + $header->setId('id-left@id-right'); + $this->assertEqual('', $header->getFieldBody()); + } + + public function testIdCanBeRetrievedVerbatim() + { + $header = $this->_getHeader('Message-ID'); + $header->setId('id-left@id-right'); + $this->assertEqual('id-left@id-right', $header->getId()); + } + + public function testMultipleIdsCanBeSet() + { + $header = $this->_getHeader('References'); + $header->setIds(array('a@b', 'x@y')); + $this->assertEqual(array('a@b', 'x@y'), $header->getIds()); + } + + public function testSettingMultipleIdsProducesAListValue() + { + /* -- RFC 2822, 3.6.4. + The "References:" and "In-Reply-To:" field each contain one or more + unique message identifiers, optionally separated by CFWS. + + .. SNIP .. + + in-reply-to = "In-Reply-To:" 1*msg-id CRLF + + references = "References:" 1*msg-id CRLF + */ + + $header = $this->_getHeader('References'); + $header->setIds(array('a@b', 'x@y')); + $this->assertEqual(' ', $header->getFieldBody()); + } + + public function testIdLeftCanBeQuoted() + { + /* -- RFC 2822, 3.6.4. + id-left = dot-atom-text / no-fold-quote / obs-id-left + */ + + $header = $this->_getHeader('References'); + $header->setId('"ab"@c'); + $this->assertEqual('"ab"@c', $header->getId()); + $this->assertEqual('<"ab"@c>', $header->getFieldBody()); + } + + public function testIdLeftCanContainAnglesAsQuotedPairs() + { + /* -- RFC 2822, 3.6.4. + no-fold-quote = DQUOTE *(qtext / quoted-pair) DQUOTE + */ + + $header = $this->_getHeader('References'); + $header->setId('"a\\<\\>b"@c'); + $this->assertEqual('"a\\<\\>b"@c', $header->getId()); + $this->assertEqual('<"a\\<\\>b"@c>', $header->getFieldBody()); + } + + public function testIdLeftCanBeDotAtom() + { + $header = $this->_getHeader('References'); + $header->setId('a.b+&%$.c@d'); + $this->assertEqual('a.b+&%$.c@d', $header->getId()); + $this->assertEqual('', $header->getFieldBody()); + } + + public function testInvalidIdLeftThrowsException() + { + try { + $header = $this->_getHeader('References'); + $header->setId('a b c@d'); + $this->fail( + 'Exception should be thrown since "a b c" is not valid id-left.' + ); + } catch (Exception $e) { + $this->pass(); + } + } + + public function testIdRightCanBeDotAtom() + { + /* -- RFC 2822, 3.6.4. + id-right = dot-atom-text / no-fold-literal / obs-id-right + */ + + $header = $this->_getHeader('References'); + $header->setId('a@b.c+&%$.d'); + $this->assertEqual('a@b.c+&%$.d', $header->getId()); + $this->assertEqual('', $header->getFieldBody()); + } + + public function testIdRightCanBeLiteral() + { + /* -- RFC 2822, 3.6.4. + no-fold-literal = "[" *(dtext / quoted-pair) "]" + */ + + $header = $this->_getHeader('References'); + $header->setId('a@[1.2.3.4]'); + $this->assertEqual('a@[1.2.3.4]', $header->getId()); + $this->assertEqual('', $header->getFieldBody()); + } + + public function testInvalidIdRightThrowsException() + { + try { + $header = $this->_getHeader('References'); + $header->setId('a@b c d'); + $this->fail( + 'Exception should be thrown since "b c d" is not valid id-right.' + ); + } catch (Exception $e) { + $this->pass(); + } + } + + public function testMissingAtSignThrowsException() + { + /* -- RFC 2822, 3.6.4. + msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS] + */ + + try { + $header = $this->_getHeader('References'); + $header->setId('abc'); + $this->fail( + 'Exception should be thrown since "abc" is does not contain @.' + ); + } catch (Exception $e) { + $this->pass(); + } + } + + public function testSetBodyModel() + { + $header = $this->_getHeader('Message-ID'); + $header->setFieldBodyModel('a@b'); + $this->assertEqual(array('a@b'), $header->getIds()); + } + + public function testGetBodyModel() + { + $header = $this->_getHeader('Message-ID'); + $header->setId('a@b'); + $this->assertEqual(array('a@b'), $header->getFieldBodyModel()); + } + + public function testStringValue() + { + $header = $this->_getHeader('References'); + $header->setIds(array('a@b', 'x@y')); + $this->assertEqual('References: ' . "\r\n", $header->toString()); + } + + // -- Private methods + + private function _getHeader($name) + { + return new Swift_Mime_Headers_IdentificationHeader($name, new Swift_Mime_Grammar()); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/MailboxHeaderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/MailboxHeaderTest.php new file mode 100644 index 0000000..d8506d1 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/MailboxHeaderTest.php @@ -0,0 +1,340 @@ +_getHeader('To', $this->_getEncoder('Q', true)); + $this->assertEqual(Swift_Mime_Header::TYPE_MAILBOX, $header->getFieldType()); + } + + public function testMailboxIsSetForAddress() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setAddresses('chris@swiftmailer.org'); + $this->assertEqual(array('chris@swiftmailer.org'), + $header->getNameAddressStrings() + ); + } + + public function testMailboxIsRenderedForNameAddress() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array('chris@swiftmailer.org' => 'Chris Corbyn')); + $this->assertEqual( + array('Chris Corbyn '), $header->getNameAddressStrings() + ); + } + + public function testAddressCanBeReturnedForAddress() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setAddresses('chris@swiftmailer.org'); + $this->assertEqual(array('chris@swiftmailer.org'), $header->getAddresses()); + } + + public function testAddressCanBeReturnedForNameAddress() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array('chris@swiftmailer.org' => 'Chris Corbyn')); + $this->assertEqual(array('chris@swiftmailer.org'), $header->getAddresses()); + } + + public function testQuotesInNameAreQuoted() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn, "DHE"' + )); + $this->assertEqual( + array('"Chris Corbyn, \"DHE\"" '), + $header->getNameAddressStrings() + ); + } + + public function testEscapeCharsInNameAreQuoted() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn, \\escaped\\' + )); + $this->assertEqual( + array('"Chris Corbyn, \\\\escaped\\\\" '), + $header->getNameAddressStrings() + ); + } + + public function testGetMailboxesReturnsNameValuePairs() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn, DHE' + )); + $this->assertEqual( + array('chris@swiftmailer.org' => 'Chris Corbyn, DHE'), $header->getNameAddresses() + ); + } + + public function testMultipleAddressesCanBeSetAndFetched() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setAddresses(array( + 'chris@swiftmailer.org', 'mark@swiftmailer.org' + )); + $this->assertEqual( + array('chris@swiftmailer.org', 'mark@swiftmailer.org'), + $header->getAddresses() + ); + } + + public function testMultipleAddressesAsMailboxes() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setAddresses(array( + 'chris@swiftmailer.org', 'mark@swiftmailer.org' + )); + $this->assertEqual( + array('chris@swiftmailer.org'=>null, 'mark@swiftmailer.org'=>null), + $header->getNameAddresses() + ); + } + + public function testMultipleAddressesAsMailboxStrings() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setAddresses(array( + 'chris@swiftmailer.org', 'mark@swiftmailer.org' + )); + $this->assertEqual( + array('chris@swiftmailer.org', 'mark@swiftmailer.org'), + $header->getNameAddressStrings() + ); + } + + public function testMultipleNamedMailboxesReturnsMultipleAddresses() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn' + )); + $this->assertEqual( + array('chris@swiftmailer.org', 'mark@swiftmailer.org'), + $header->getAddresses() + ); + } + + public function testMultipleNamedMailboxesReturnsMultipleMailboxes() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn' + )); + $this->assertEqual(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn' + ), + $header->getNameAddresses() + ); + } + + public function testMultipleMailboxesProducesMultipleMailboxStrings() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn' + )); + $this->assertEqual(array( + 'Chris Corbyn ', + 'Mark Corbyn ' + ), + $header->getNameAddressStrings() + ); + } + + public function testSetAddressesOverwritesAnyMailboxes() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn' + )); + $this->assertEqual( + array('chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn'), + $header->getNameAddresses() + ); + $this->assertEqual( + array('chris@swiftmailer.org', 'mark@swiftmailer.org'), + $header->getAddresses() + ); + + $header->setAddresses(array('chris@swiftmailer.org', 'mark@swiftmailer.org')); + + $this->assertEqual( + array('chris@swiftmailer.org' => null, 'mark@swiftmailer.org' => null), + $header->getNameAddresses() + ); + $this->assertEqual( + array('chris@swiftmailer.org', 'mark@swiftmailer.org'), + $header->getAddresses() + ); + } + + public function testNameIsEncodedIfNonAscii() + { + $name = 'C' . pack('C', 0x8F) . 'rbyn'; + + $encoder = $this->_getEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($name, any(), any()) -> returns('C=8Frbyn') + -> ignoring($encoder) + ); + + $header = $this->_getHeader('From', $encoder); + $header->setNameAddresses(array('chris@swiftmailer.org'=>'Chris ' . $name)); + + $addresses = $header->getNameAddressStrings(); + $this->assertEqual( + 'Chris =?' . $this->_charset . '?Q?C=8Frbyn?= ', + array_shift($addresses) + ); + } + + public function testEncodingLineLengthCalculations() + { + /* -- RFC 2047, 2. + An 'encoded-word' may not be more than 75 characters long, including + 'charset', 'encoding', 'encoded-text', and delimiters. + */ + + $name = 'C' . pack('C', 0x8F) . 'rbyn'; + + $encoder = $this->_getEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($name, 6, 63) -> returns('C=8Frbyn') + -> ignoring($encoder) + ); + + $header = $this->_getHeader('From', $encoder); + $header->setNameAddresses(array('chris@swiftmailer.org'=>'Chris ' . $name)); + + $header->getNameAddressStrings(); + } + + public function testGetValueReturnsMailboxStringValue() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn' + )); + $this->assertEqual( + 'Chris Corbyn ', $header->getFieldBody() + ); + } + + public function testGetValueReturnsMailboxStringValueForMultipleMailboxes() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn' + )); + $this->assertEqual( + 'Chris Corbyn , Mark Corbyn ', + $header->getFieldBody() + ); + } + + public function testRemoveAddressesWithSingleValue() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn' + )); + $header->removeAddresses('chris@swiftmailer.org'); + $this->assertEqual(array('mark@swiftmailer.org'), + $header->getAddresses() + ); + } + + public function testRemoveAddressesWithList() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn' + )); + $header->removeAddresses( + array('chris@swiftmailer.org', 'mark@swiftmailer.org') + ); + $this->assertEqual(array(), $header->getAddresses()); + } + + public function testSetBodyModel() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setFieldBodyModel('chris@swiftmailer.org'); + $this->assertEqual(array('chris@swiftmailer.org'=>null), $header->getNameAddresses()); + } + + public function testGetBodyModel() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setAddresses(array('chris@swiftmailer.org')); + $this->assertEqual(array('chris@swiftmailer.org'=>null), $header->getFieldBodyModel()); + } + + public function testToString() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn' + )); + $this->assertEqual( + 'From: Chris Corbyn , ' . + 'Mark Corbyn ' . "\r\n", + $header->toString() + ); + } + + // -- Private methods + + private function _getHeader($name, $encoder) + { + $header = new Swift_Mime_Headers_MailboxHeader($name, $encoder, new Swift_Mime_Grammar()); + $header->setCharset($this->_charset); + + return $header; + } + + private function _getEncoder($type, $stub = false) + { + $encoder = $this->_mock('Swift_Mime_HeaderEncoder'); + $this->_checking(Expectations::create() + -> ignoring($encoder)->getName() -> returns($type) + ); + if ($stub) { + $this->_checking(Expectations::create() + -> ignoring($encoder) + ); + } + + return $encoder; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/ParameterizedHeaderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/ParameterizedHeaderTest.php new file mode 100644 index 0000000..2783aea --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/ParameterizedHeaderTest.php @@ -0,0 +1,422 @@ +_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $this->assertEqual(Swift_Mime_Header::TYPE_PARAMETERIZED, $header->getFieldType()); + } + + public function testValueIsReturnedVerbatim() + { + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setValue('text/plain'); + $this->assertEqual('text/plain', $header->getValue()); + } + + public function testParametersAreAppended() + { + /* -- RFC 2045, 5.1 + parameter := attribute "=" value + + attribute := token + ; Matching of attributes + ; is ALWAYS case-insensitive. + + value := token / quoted-string + + token := 1* + + tspecials := "(" / ")" / "<" / ">" / "@" / + "," / ";" / ":" / "\" / <"> + "/" / "[" / "]" / "?" / "=" + ; Must be in quoted-string, + ; to use within parameter values + */ + + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setValue('text/plain'); + $header->setParameters(array('charset' => 'utf-8')); + $this->assertEqual('text/plain; charset=utf-8', $header->getFieldBody()); + } + + public function testSpaceInParamResultsInQuotedString() + { + $header = $this->_getHeader('Content-Disposition', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setValue('attachment'); + $header->setParameters(array('filename' => 'my file.txt')); + $this->assertEqual('attachment; filename="my file.txt"', + $header->getFieldBody() + ); + } + + public function testLongParamsAreBrokenIntoMultipleAttributeStrings() + { + /* -- RFC 2231, 3. + The asterisk character ("*") followed + by a decimal count is employed to indicate that multiple parameters + are being used to encapsulate a single parameter value. The count + starts at 0 and increments by 1 for each subsequent section of the + parameter value. Decimal values are used and neither leading zeroes + nor gaps in the sequence are allowed. + + The original parameter value is recovered by concatenating the + various sections of the parameter, in order. For example, the + content-type field + + Content-Type: message/external-body; access-type=URL; + URL*0="ftp://"; + URL*1="cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar" + + is semantically identical to + + Content-Type: message/external-body; access-type=URL; + URL="ftp://cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar" + + Note that quotes around parameter values are part of the value + syntax; they are NOT part of the value itself. Furthermore, it is + explicitly permitted to have a mixture of quoted and unquoted + continuation fields. + */ + + $value = str_repeat('a', 180); + + $encoder = $this->_getParameterEncoder(); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($value, any(), 63) + -> returns(str_repeat('a', 63) . "\r\n" . + str_repeat('a', 63) . "\r\n" . str_repeat('a', 54)) + + -> ignoring($encoder) + ); + + $header = $this->_getHeader('Content-Disposition', + $this->_getHeaderEncoder('Q', true), $encoder + ); + $header->setValue('attachment'); + $header->setParameters(array('filename' => $value)); + $header->setMaxLineLength(78); + $this->assertEqual( + 'attachment; ' . + 'filename*0*=utf-8\'\'' . str_repeat('a', 63) . ";\r\n " . + 'filename*1*=' . str_repeat('a', 63) . ";\r\n " . + 'filename*2*=' . str_repeat('a', 54), + $header->getFieldBody() + ); + } + + public function testEncodedParamDataIncludesCharsetAndLanguage() + { + /* -- RFC 2231, 4. + Asterisks ("*") are reused to provide the indicator that language and + character set information is present and encoding is being used. A + single quote ("'") is used to delimit the character set and language + information at the beginning of the parameter value. Percent signs + ("%") are used as the encoding flag, which agrees with RFC 2047. + + Specifically, an asterisk at the end of a parameter name acts as an + indicator that character set and language information may appear at + the beginning of the parameter value. A single quote is used to + separate the character set, language, and actual value information in + the parameter value string, and an percent sign is used to flag + octets encoded in hexadecimal. For example: + + Content-Type: application/x-stuff; + title*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A + + Note that it is perfectly permissible to leave either the character + set or language field blank. Note also that the single quote + delimiters MUST be present even when one of the field values is + omitted. + */ + + $value = str_repeat('a', 20) . pack('C', 0x8F) . str_repeat('a', 10); + + $encoder = $this->_getParameterEncoder(); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($value, 12, 62) + -> returns(str_repeat('a', 20) . '%8F' . str_repeat('a', 10)) + + -> ignoring($encoder) + ); + + $header = $this->_getHeader('Content-Disposition', + $this->_getHeaderEncoder('Q', true), $encoder + ); + $header->setValue('attachment'); + $header->setParameters(array('filename' => $value)); + $header->setMaxLineLength(78); + $header->setLanguage($this->_lang); + $this->assertEqual( + 'attachment; filename*=' . $this->_charset . "'" . $this->_lang . "'" . + str_repeat('a', 20) . '%8F' . str_repeat('a', 10), + $header->getFieldBody() + ); + } + + public function testMultipleEncodedParamLinesAreFormattedCorrectly() + { + /* -- RFC 2231, 4.1. + Character set and language information may be combined with the + parameter continuation mechanism. For example: + + Content-Type: application/x-stuff + title*0*=us-ascii'en'This%20is%20even%20more%20 + title*1*=%2A%2A%2Afun%2A%2A%2A%20 + title*2="isn't it!" + + Note that: + + (1) Language and character set information only appear at + the beginning of a given parameter value. + + (2) Continuations do not provide a facility for using more + than one character set or language in the same + parameter value. + + (3) A value presented using multiple continuations may + contain a mixture of encoded and unencoded segments. + + (4) The first segment of a continuation MUST be encoded if + language and character set information are given. + + (5) If the first segment of a continued parameter value is + encoded the language and character set field delimiters + MUST be present even when the fields are left blank. + */ + + $value = str_repeat('a', 20) . pack('C', 0x8F) . str_repeat('a', 60); + + $encoder = $this->_getParameterEncoder(); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($value, 12, 62) + -> returns(str_repeat('a', 20) . '%8F' . str_repeat('a', 28) . "\r\n" . + str_repeat('a', 32)) + + -> ignoring($encoder) + ); + + $header = $this->_getHeader('Content-Disposition', + $this->_getHeaderEncoder('Q', true), $encoder + ); + $header->setValue('attachment'); + $header->setParameters(array('filename' => $value)); + $header->setMaxLineLength(78); + $header->setLanguage($this->_lang); + $this->assertEqual( + 'attachment; filename*0*=' . $this->_charset . "'" . $this->_lang . "'" . + str_repeat('a', 20) . '%8F' . str_repeat('a', 28) . ";\r\n " . + 'filename*1*=' . str_repeat('a', 32), + $header->getFieldBody() + ); + } + + public function testToString() + { + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setValue('text/html'); + $header->setParameters(array('charset' => 'utf-8')); + $this->assertEqual('Content-Type: text/html; charset=utf-8' . "\r\n", + $header->toString() + ); + } + + public function testValueCanBeEncodedIfNonAscii() + { + $value = 'fo' . pack('C', 0x8F) .'bar'; + + $encoder = $this->_getHeaderEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($value, any(), any()) -> returns('fo=8Fbar') + -> ignoring($encoder) + ); + + $header = $this->_getHeader('X-Foo', $encoder, $this->_getParameterEncoder(true)); + $header->setValue($value); + $header->setParameters(array('lookslike' => 'foobar')); + $this->assertEqual('X-Foo: =?utf-8?Q?fo=8Fbar?=; lookslike=foobar' . "\r\n", + $header->toString() + ); + } + + public function testValueAndParamCanBeEncodedIfNonAscii() + { + $value = 'fo' . pack('C', 0x8F) .'bar'; + + $encoder = $this->_getHeaderEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($value, any(), any()) -> returns('fo=8Fbar') + -> ignoring($encoder) + ); + + $paramEncoder = $this->_getParameterEncoder(); + $this->_checking(Expectations::create() + -> one($paramEncoder)->encodeString($value, any(), any()) -> returns('fo%8Fbar') + -> ignoring($paramEncoder) + ); + + $header = $this->_getHeader('X-Foo', $encoder, $paramEncoder); + $header->setValue($value); + $header->setParameters(array('says' => $value)); + $this->assertEqual("X-Foo: =?utf-8?Q?fo=8Fbar?=; says*=utf-8''fo%8Fbar\r\n", + $header->toString() + ); + } + + public function testParamsAreEncodedWithEncodedWordsIfNoParamEncoderSet() + { + $value = 'fo' . pack('C', 0x8F) .'bar'; + + $encoder = $this->_getHeaderEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($value, any(), any()) -> returns('fo=8Fbar') + -> ignoring($encoder) + ); + + $header = $this->_getHeader('X-Foo', $encoder, null); + $header->setValue('bar'); + $header->setParameters(array('says' => $value)); + $this->assertEqual("X-Foo: bar; says=\"=?utf-8?Q?fo=8Fbar?=\"\r\n", + $header->toString() + ); + } + + public function testLanguageInformationAppearsInEncodedWords() + { + /* -- RFC 2231, 5. + 5. Language specification in Encoded Words + + RFC 2047 provides support for non-US-ASCII character sets in RFC 822 + message header comments, phrases, and any unstructured text field. + This is done by defining an encoded word construct which can appear + in any of these places. Given that these are fields intended for + display, it is sometimes necessary to associate language information + with encoded words as well as just the character set. This + specification extends the definition of an encoded word to allow the + inclusion of such information. This is simply done by suffixing the + character set specification with an asterisk followed by the language + tag. For example: + + From: =?US-ASCII*EN?Q?Keith_Moore?= + */ + + $value = 'fo' . pack('C', 0x8F) .'bar'; + + $encoder = $this->_getHeaderEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($value, any(), any()) -> returns('fo=8Fbar') + -> ignoring($encoder) + ); + + $paramEncoder = $this->_getParameterEncoder(); + $this->_checking(Expectations::create() + -> one($paramEncoder)->encodeString($value, any(), any()) -> returns('fo%8Fbar') + -> ignoring($paramEncoder) + ); + + $header = $this->_getHeader('X-Foo', $encoder, $paramEncoder); + $header->setLanguage('en'); + $header->setValue($value); + $header->setParameters(array('says' => $value)); + $this->assertEqual("X-Foo: =?utf-8*en?Q?fo=8Fbar?=; says*=utf-8'en'fo%8Fbar\r\n", + $header->toString() + ); + } + + public function testSetBodyModel() + { + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setFieldBodyModel('text/html'); + $this->assertEqual('text/html', $header->getValue()); + } + + public function testGetBodyModel() + { + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setValue('text/plain'); + $this->assertEqual('text/plain', $header->getFieldBodyModel()); + } + + public function testSetParameter() + { + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setParameters(array('charset'=>'utf-8', 'delsp' => 'yes')); + $header->setParameter('delsp', 'no'); + $this->assertEqual(array('charset'=>'utf-8', 'delsp'=>'no'), + $header->getParameters() + ); + } + + public function testGetParameter() + { + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setParameters(array('charset'=>'utf-8', 'delsp' => 'yes')); + $this->assertEqual('utf-8', $header->getParameter('charset')); + } + + // -- Private helper + + private function _getHeader($name, $encoder, $paramEncoder) + { + $header = new Swift_Mime_Headers_ParameterizedHeader($name, $encoder, + $paramEncoder, new Swift_Mime_Grammar() + ); + $header->setCharset($this->_charset); + + return $header; + } + + private function _getHeaderEncoder($type, $stub = false) + { + $encoder = $this->_mock('Swift_Mime_HeaderEncoder'); + $this->_checking(Expectations::create() + -> ignoring($encoder)->getName() -> returns($type) + ); + if ($stub) { + $this->_checking(Expectations::create() + -> ignoring($encoder) + ); + } + + return $encoder; + } + + private function _getParameterEncoder($stub = false) + { + if ($stub) { + return $this->_stub('Swift_Encoder'); + } else { + return $this->_mock('Swift_Encoder'); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/PathHeaderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/PathHeaderTest.php new file mode 100644 index 0000000..256dd91 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/PathHeaderTest.php @@ -0,0 +1,84 @@ +_getHeader('Return-Path'); + $this->assertEqual(Swift_Mime_Header::TYPE_PATH, $header->getFieldType()); + } + + public function testSingleAddressCanBeSetAndFetched() + { + $header = $this->_getHeader('Return-Path'); + $header->setAddress('chris@swiftmailer.org'); + $this->assertEqual('chris@swiftmailer.org', $header->getAddress()); + } + + public function testAddressMustComplyWithRfc2822() + { + try { + $header = $this->_getHeader('Return-Path'); + $header->setAddress('chr is@swiftmailer.org'); + $this->fail('Address must be valid according to RFC 2822 addr-spec grammar.'); + } catch (Exception $e) { + $this->pass(); + } + } + + public function testValueIsAngleAddrWithValidAddress() + { + /* -- RFC 2822, 3.6.7. + + return = "Return-Path:" path CRLF + + path = ([CFWS] "<" ([CFWS] / addr-spec) ">" [CFWS]) / + obs-path + */ + + $header = $this->_getHeader('Return-Path'); + $header->setAddress('chris@swiftmailer.org'); + $this->assertEqual('', $header->getFieldBody()); + } + + public function testValueIsEmptyAngleBracketsIfEmptyAddressSet() + { + $header = $this->_getHeader('Return-Path'); + $header->setAddress(''); + $this->assertEqual('<>', $header->getFieldBody()); + } + + public function testSetBodyModel() + { + $header = $this->_getHeader('Return-Path'); + $header->setFieldBodyModel('foo@bar.tld'); + $this->assertEqual('foo@bar.tld', $header->getAddress()); + } + + public function testGetBodyModel() + { + $header = $this->_getHeader('Return-Path'); + $header->setAddress('foo@bar.tld'); + $this->assertEqual('foo@bar.tld', $header->getFieldBodyModel()); + } + + public function testToString() + { + $header = $this->_getHeader('Return-Path'); + $header->setAddress('chris@swiftmailer.org'); + $this->assertEqual('Return-Path: ' . "\r\n", + $header->toString() + ); + } + + // -- Private methods + + private function _getHeader($name) + { + return new Swift_Mime_Headers_PathHeader($name, new Swift_Mime_Grammar()); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/UnstructuredHeaderTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/UnstructuredHeaderTest.php new file mode 100644 index 0000000..f14519e --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/UnstructuredHeaderTest.php @@ -0,0 +1,366 @@ +_getHeader('Subject', $this->_getEncoder('Q', true)); + $this->assertEqual(Swift_Mime_Header::TYPE_TEXT, $header->getFieldType()); + } + + public function testGetNameReturnsNameVerbatim() + { + $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true)); + $this->assertEqual('Subject', $header->getFieldName()); + } + + public function testGetValueReturnsValueVerbatim() + { + $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true)); + $header->setValue('Test'); + $this->assertEqual('Test', $header->getValue()); + } + + public function testBasicStructureIsKeyValuePair() + { + /* -- RFC 2822, 2.2 + Header fields are lines composed of a field name, followed by a colon + (":"), followed by a field body, and terminated by CRLF. + */ + $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true)); + $header->setValue('Test'); + $this->assertEqual('Subject: Test' . "\r\n", $header->toString()); + } + + public function testLongHeadersAreFoldedAtWordBoundary() + { + /* -- RFC 2822, 2.2.3 + Each header field is logically a single line of characters comprising + the field name, the colon, and the field body. For convenience + however, and to deal with the 998/78 character limitations per line, + the field body portion of a header field can be split into a multiple + line representation; this is called "folding". The general rule is + that wherever this standard allows for folding white space (not + simply WSP characters), a CRLF may be inserted before any WSP. + */ + + $value = 'The quick brown fox jumped over the fence, he was a very very ' . + 'scary brown fox with a bushy tail'; + $header = $this->_getHeader('X-Custom-Header', + $this->_getEncoder('Q', true) + ); + $header->setValue($value); + $header->setMaxLineLength(78); //A safe [RFC 2822, 2.2.3] default + /* + X-Custom-Header: The quick brown fox jumped over the fence, he was a very very + scary brown fox with a bushy tail + */ + $this->assertEqual( + 'X-Custom-Header: The quick brown fox jumped over the fence, he was a' . + ' very very' . "\r\n" . //Folding + ' scary brown fox with a bushy tail' . "\r\n", + $header->toString(), '%s: The header should have been folded at 78th char' + ); + } + + public function testPrintableAsciiOnlyAppearsInHeaders() + { + /* -- RFC 2822, 2.2. + A field name MUST be composed of printable US-ASCII characters (i.e., + characters that have values between 33 and 126, inclusive), except + colon. A field body may be composed of any US-ASCII characters, + except for CR and LF. + */ + + $nonAsciiChar = pack('C', 0x8F); + $header = $this->_getHeader('X-Test', $this->_getEncoder('Q', true)); + $header->setValue($nonAsciiChar); + $this->assertPattern( + '~^[^:\x00-\x20\x80-\xFF]+: [^\x80-\xFF\r\n]+\r\n$~s', + $header->toString() + ); + } + + public function testEncodedWordsFollowGeneralStructure() + { + /* -- RFC 2047, 1. + Generally, an "encoded-word" is a sequence of printable ASCII + characters that begins with "=?", ends with "?=", and has two "?"s in + between. + */ + + $nonAsciiChar = pack('C', 0x8F); + $header = $this->_getHeader('X-Test', $this->_getEncoder('Q', true)); + $header->setValue($nonAsciiChar); + $this->assertPattern( + '~^X-Test: \=?.*?\?.*?\?.*?\?=\r\n$~s', + $header->toString() + ); + } + + public function testEncodedWordIncludesCharsetAndEncodingMethodAndText() + { + /* -- RFC 2047, 2. + An 'encoded-word' is defined by the following ABNF grammar. The + notation of RFC 822 is used, with the exception that white space + characters MUST NOT appear between components of an 'encoded-word'. + + encoded-word = "=?" charset "?" encoding "?" encoded-text "?=" + */ + + $nonAsciiChar = pack('C', 0x8F); + + $encoder = $this->_getEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($nonAsciiChar, any(), any()) -> returns('=8F') + -> ignoring($encoder) + ); + $header = $this->_getHeader('X-Test', $encoder); + $header->setValue($nonAsciiChar); + $this->assertEqual( + 'X-Test: =?' . $this->_charset . '?Q?=8F?=' . "\r\n", + $header->toString() + ); + } + + public function testEncodedWordsAreUsedToEncodedNonPrintableAscii() + { + //SPACE and TAB permitted + $nonPrintableBytes = array_merge( + range(0x00, 0x08), range(0x10, 0x19), array(0x7F) + ); + + foreach ($nonPrintableBytes as $byte) { + $char = pack('C', $byte); + $encodedChar = sprintf('=%02X', $byte); + + $encoder = $this->_getEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($char, any(), any()) -> returns($encodedChar) + -> ignoring($encoder) + ); + + $header = $this->_getHeader('X-A', $encoder); + $header->setValue($char); + + $this->assertEqual( + 'X-A: =?' . $this->_charset . '?Q?' . $encodedChar . '?=' . "\r\n", + $header->toString(), '%s: Non-printable ascii should be encoded' + ); + } + } + + public function testEncodedWordsAreUsedToEncode8BitOctets() + { + $_8BitBytes = range(0x80, 0xFF); + + foreach ($_8BitBytes as $byte) { + $char = pack('C', $byte); + $encodedChar = sprintf('=%02X', $byte); + + $encoder = $this->_getEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($char, any(), any()) -> returns($encodedChar) + -> ignoring($encoder) + ); + + $header = $this->_getHeader('X-A', $encoder); + $header->setValue($char); + + $this->assertEqual( + 'X-A: =?' . $this->_charset . '?Q?' . $encodedChar . '?=' . "\r\n", + $header->toString(), '%s: 8-bit octets should be encoded' + ); + } + } + + public function testEncodedWordsAreNoMoreThan75CharsPerLine() + { + /* -- RFC 2047, 2. + An 'encoded-word' may not be more than 75 characters long, including + 'charset', 'encoding', 'encoded-text', and delimiters. + + ... SNIP ... + + While there is no limit to the length of a multiple-line header + field, each line of a header field that contains one or more + 'encoded-word's is limited to 76 characters. + */ + + $nonAsciiChar = pack('C', 0x8F); + + $encoder = $this->_getEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($nonAsciiChar, 8, 63) -> returns('=8F') + -> ignoring($encoder) + ); + //Note that multi-line headers begin with LWSP which makes 75 + 1 = 76 + //Note also that =?utf-8?q??= is 12 chars which makes 75 - 12 = 63 + + //* X-Test: is 8 chars + $header = $this->_getHeader('X-Test', $encoder); + $header->setValue($nonAsciiChar); + + $this->assertEqual( + 'X-Test: =?' . $this->_charset . '?Q?=8F?=' . "\r\n", + $header->toString() + ); + } + + public function testFWSPIsUsedWhenEncoderReturnsMultipleLines() + { + /* --RFC 2047, 2. + If it is desirable to encode more text than will fit in an 'encoded-word' of + 75 characters, multiple 'encoded-word's (separated by CRLF SPACE) may + be used. + */ + + //Note the Mock does NOT return 8F encoded, the 8F merely triggers + // encoding for the sake of testing + $nonAsciiChar = pack('C', 0x8F); + + $encoder = $this->_getEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($nonAsciiChar, 8, 63) + -> returns('line_one_here' . "\r\n" . 'line_two_here') + -> ignoring($encoder) + ); + + //Note that multi-line headers begin with LWSP which makes 75 + 1 = 76 + //Note also that =?utf-8?q??= is 12 chars which makes 75 - 12 = 63 + + //* X-Test: is 8 chars + $header = $this->_getHeader('X-Test', $encoder); + $header->setValue($nonAsciiChar); + + $this->assertEqual( + 'X-Test: =?' . $this->_charset . '?Q?line_one_here?=' . "\r\n" . + ' =?' . $this->_charset . '?Q?line_two_here?=' . "\r\n", + $header->toString() + ); + } + + public function testAdjacentWordsAreEncodedTogether() + { + /* -- RFC 2047, 5 (1) + Ordinary ASCII text and 'encoded-word's may appear together in the + same header field. However, an 'encoded-word' that appears in a + header field defined as '*text' MUST be separated from any adjacent + 'encoded-word' or 'text' by 'linear-white-space'. + + -- RFC 2047, 2. + IMPORTANT: 'encoded-word's are designed to be recognized as 'atom's + by an RFC 822 parser. As a consequence, unencoded white space + characters (such as SPACE and HTAB) are FORBIDDEN within an + 'encoded-word'. + */ + + //It would be valid to encode all words needed, however it's probably + // easiest to encode the longest amount required at a time + + $word = 'w' . pack('C', 0x8F) . 'rd'; + $text = 'start ' . $word . ' ' . $word . ' then end ' . $word; + // 'start', ' word word', ' and end', ' word' + + $encoder = $this->_getEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($word . ' ' . $word, any(), any()) + -> returns('w=8Frd_w=8Frd') + -> one($encoder)->encodeString($word, any(), any()) -> returns('w=8Frd') + -> ignoring($encoder) + ); + + $header = $this->_getHeader('X-Test', $encoder); + $header->setValue($text); + + $headerString = $header->toString(); + + $this->assertEqual('X-Test: start =?' . $this->_charset . '?Q?' . + 'w=8Frd_w=8Frd?= then end =?' . $this->_charset . '?Q?'. + 'w=8Frd?=' . "\r\n", $headerString, + '%s: Adjacent encoded words should appear grouped with WSP encoded' + ); + } + + public function testLanguageInformationAppearsInEncodedWords() + { + /* -- RFC 2231, 5. + 5. Language specification in Encoded Words + + RFC 2047 provides support for non-US-ASCII character sets in RFC 822 + message header comments, phrases, and any unstructured text field. + This is done by defining an encoded word construct which can appear + in any of these places. Given that these are fields intended for + display, it is sometimes necessary to associate language information + with encoded words as well as just the character set. This + specification extends the definition of an encoded word to allow the + inclusion of such information. This is simply done by suffixing the + character set specification with an asterisk followed by the language + tag. For example: + + From: =?US-ASCII*EN?Q?Keith_Moore?= + */ + + $value = 'fo' . pack('C', 0x8F) . 'bar'; + + $encoder = $this->_getEncoder('Q'); + $this->_checking(Expectations::create() + -> one($encoder)->encodeString($value, any(), any()) -> returns('fo=8Fbar') + -> ignoring($encoder) + ); + + $header = $this->_getHeader('Subject', $encoder); + $header->setLanguage('en'); + $header->setValue($value); + $this->assertEqual("Subject: =?utf-8*en?Q?fo=8Fbar?=\r\n", + $header->toString() + ); + } + + public function testSetBodyModel() + { + $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true)); + $header->setFieldBodyModel('test'); + $this->assertEqual('test', $header->getValue()); + } + + public function testGetBodyModel() + { + $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true)); + $header->setValue('test'); + $this->assertEqual('test', $header->getFieldBodyModel()); + } + + // -- Private methods + + private function _getHeader($name, $encoder) + { + $header = new Swift_Mime_Headers_UnstructuredHeader($name, $encoder, new Swift_Mime_Grammar()); + $header->setCharset($this->_charset); + + return $header; + } + + private function _getEncoder($type, $stub = false) + { + $encoder = $this->_mock('Swift_Mime_HeaderEncoder'); + $this->_checking(Expectations::create() + -> ignoring($encoder)->getName() -> returns($type) + ); + if ($stub) { + $this->_checking(Expectations::create() + -> ignoring($encoder) + ); + } + + return $encoder; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/MimePartTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/MimePartTest.php new file mode 100644 index 0000000..aadf788 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/MimePartTest.php @@ -0,0 +1,252 @@ +_createMimePart($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual( + Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, $part->getNestingLevel() + ); + } + + public function testCharsetIsReturnedFromHeader() + { + /* -- RFC 2046, 4.1.2. + A critical parameter that may be specified in the Content-Type field + for "text/plain" data is the character set. This is specified with a + "charset" parameter, as in: + + Content-type: text/plain; charset=iso-8859-1 + + Unlike some other parameter values, the values of the charset + parameter are NOT case sensitive. The default character set, which + must be assumed in the absence of a charset parameter, is US-ASCII. + */ + + $cType = $this->_createHeader('Content-Type', 'text/plain', + array('charset' => 'iso-8859-1') + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual('iso-8859-1', $part->getCharset()); + } + + public function testCharsetIsSetInHeader() + { + $cType = $this->_createHeader('Content-Type', 'text/plain', + array('charset' => 'iso-8859-1'), false + ); + $this->_checking(Expectations::create() + -> one($cType)->setParameter('charset', 'utf-8') + -> ignoring($cType) + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $part->setCharset('utf-8'); + } + + public function testCharsetIsSetInHeaderIfPassedToSetBody() + { + $cType = $this->_createHeader('Content-Type', 'text/plain', + array('charset' => 'iso-8859-1'), false + ); + $this->_checking(Expectations::create() + -> one($cType)->setParameter('charset', 'utf-8') + -> ignoring($cType) + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $part->setBody('', 'text/plian', 'utf-8'); + } + + public function testSettingCharsetNotifiesEncoder() + { + $encoder = $this->_createEncoder('quoted-printable', false); + $this->_checking(Expectations::create() + -> one($encoder)->charsetChanged('utf-8') + -> ignoring($encoder) + ); + $part = $this->_createMimePart($this->_createHeaderSet(), + $encoder, $this->_createCache() + ); + $part->setCharset('utf-8'); + } + + public function testSettingCharsetNotifiesHeaders() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->charsetChanged('utf-8') + -> ignoring($headers) + ); + $part = $this->_createMimePart($headers, $this->_createEncoder(), + $this->_createCache() + ); + $part->setCharset('utf-8'); + } + + public function testSettingCharsetNotifiesChildren() + { + $child = $this->_createChild(0, '', false); + + $this->_checking(Expectations::create() + -> one($child)->charsetChanged('windows-874') + -> ignoring($child) + ); + + $part = $this->_createMimePart($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $part->setChildren(array($child)); + $part->setCharset('windows-874'); + } + + public function testCharsetChangeUpdatesCharset() + { + $cType = $this->_createHeader('Content-Type', 'text/plain', + array('charset' => 'iso-8859-1'), false + ); + $this->_checking(Expectations::create() + -> one($cType)->setParameter('charset', 'utf-8') + -> ignoring($cType) + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $part->charsetChanged('utf-8'); + } + + public function testSettingCharsetClearsCache() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> ignoring($headers)->toString() -> returns( + "Content-Type: text/plain; charset=utf-8\r\n" + ) + -> ignoring($headers) + ); + + $cache = $this->_createCache(false); + $this->_checking(Expectations::create() + -> one($cache)->clearKey(any(), 'body') + -> ignoring($cache) + ); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $cache + ); + + $entity->setBody("blah\r\nblah!"); + $entity->toString(); + + $entity->setCharset('iso-2022'); + } + + public function testFormatIsReturnedFromHeader() + { + /* -- RFC 3676. + */ + + $cType = $this->_createHeader('Content-Type', 'text/plain', + array('format' => 'flowed') + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual('flowed', $part->getFormat()); + } + + public function testFormatIsSetInHeader() + { + $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $this->_checking(Expectations::create() + -> one($cType)->setParameter('format', 'fixed') + -> ignoring($cType) + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $part->setFormat('fixed'); + } + + public function testDelSpIsReturnedFromHeader() + { + /* -- RFC 3676. + */ + + $cType = $this->_createHeader('Content-Type', 'text/plain', + array('delsp' => 'no') + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertIdentical(false, $part->getDelSp()); + } + + public function testDelSpIsSetInHeader() + { + $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $this->_checking(Expectations::create() + -> one($cType)->setParameter('delsp', 'yes') + -> ignoring($cType) + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType)), + $this->_createEncoder(), $this->_createCache() + ); + $part->setDelSp(true); + } + + public function testFluidInterface() + { + $part = $this->_createMimePart($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + + $this->assertSame($part, + $part + ->setContentType('text/plain') + ->setEncoder($this->_createEncoder()) + ->setId('foo@bar') + ->setDescription('my description') + ->setMaxLineLength(998) + ->setBody('xx') + ->setBoundary('xyz') + ->setChildren(array()) + ->setCharset('utf-8') + ->setFormat('flowed') + ->setDelSp(true) + ); + } + + // -- Private helpers + + //abstract + protected function _createEntity($headers, $encoder, $cache) + { + return $this->_createMimePart($headers, $encoder, $cache); + } + + protected function _createMimePart($headers, $encoder, $cache) + { + return new Swift_Mime_MimePart($headers, $encoder, $cache, new Swift_Mime_Grammar()); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderFactoryTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderFactoryTest.php new file mode 100644 index 0000000..4f56f52 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderFactoryTest.php @@ -0,0 +1,179 @@ +_factory = $this->_createFactory(); + } + + public function testMailboxHeaderIsCorrectType() + { + $header = $this->_factory->createMailboxHeader('X-Foo'); + $this->assertIsA($header, 'Swift_Mime_Headers_MailboxHeader'); + } + + public function testMailboxHeaderHasCorrectName() + { + $header = $this->_factory->createMailboxHeader('X-Foo'); + $this->assertEqual('X-Foo', $header->getFieldName()); + } + + public function testMailboxHeaderHasCorrectModel() + { + $header = $this->_factory->createMailboxHeader('X-Foo', + array('foo@bar'=>'FooBar') + ); + $this->assertEqual(array('foo@bar'=>'FooBar'), $header->getFieldBodyModel()); + } + + public function testDateHeaderHasCorrectType() + { + $header = $this->_factory->createDateHeader('X-Date'); + $this->assertIsA($header, 'Swift_Mime_Headers_DateHeader'); + } + + public function testDateHeaderHasCorrectName() + { + $header = $this->_factory->createDateHeader('X-Date'); + $this->assertEqual('X-Date', $header->getFieldName()); + } + + public function testDateHeaderHasCorrectModel() + { + $header = $this->_factory->createDateHeader('X-Date', 123); + $this->assertEqual(123, $header->getFieldBodyModel()); + } + + public function testTextHeaderHasCorrectType() + { + $header = $this->_factory->createTextHeader('X-Foo'); + $this->assertIsA($header, 'Swift_Mime_Headers_UnstructuredHeader'); + } + + public function testTextHeaderHasCorrectName() + { + $header = $this->_factory->createTextHeader('X-Foo'); + $this->assertEqual('X-Foo', $header->getFieldName()); + } + + public function testTextHeaderHasCorrectModel() + { + $header = $this->_factory->createTextHeader('X-Foo', 'bar'); + $this->assertEqual('bar', $header->getFieldBodyModel()); + } + + public function testParameterizedHeaderHasCorrectType() + { + $header = $this->_factory->createParameterizedHeader('X-Foo'); + $this->assertIsA($header, 'Swift_Mime_Headers_ParameterizedHeader'); + } + + public function testParameterizedHeaderHasCorrectName() + { + $header = $this->_factory->createParameterizedHeader('X-Foo'); + $this->assertEqual('X-Foo', $header->getFieldName()); + } + + public function testParameterizedHeaderHasCorrectModel() + { + $header = $this->_factory->createParameterizedHeader('X-Foo', 'bar'); + $this->assertEqual('bar', $header->getFieldBodyModel()); + } + + public function testParameterizedHeaderHasCorrectParams() + { + $header = $this->_factory->createParameterizedHeader('X-Foo', 'bar', + array('zip' => 'button') + ); + $this->assertEqual(array('zip'=>'button'), $header->getParameters()); + } + + public function testIdHeaderHasCorrectType() + { + $header = $this->_factory->createIdHeader('X-ID'); + $this->assertIsA($header, 'Swift_Mime_Headers_IdentificationHeader'); + } + + public function testIdHeaderHasCorrectName() + { + $header = $this->_factory->createIdHeader('X-ID'); + $this->assertEqual('X-ID', $header->getFieldName()); + } + + public function testIdHeaderHasCorrectModel() + { + $header = $this->_factory->createIdHeader('X-ID', 'xyz@abc'); + $this->assertEqual(array('xyz@abc'), $header->getFieldBodyModel()); + } + + public function testPathHeaderHasCorrectType() + { + $header = $this->_factory->createPathHeader('X-Path'); + $this->assertIsA($header, 'Swift_Mime_Headers_PathHeader'); + } + + public function testPathHeaderHasCorrectName() + { + $header = $this->_factory->createPathHeader('X-Path'); + $this->assertEqual('X-Path', $header->getFieldName()); + } + + public function testPathHeaderHasCorrectModel() + { + $header = $this->_factory->createPathHeader('X-Path', 'foo@bar'); + $this->assertEqual('foo@bar', $header->getFieldBodyModel()); + } + + public function testCharsetChangeNotificationNotifiesEncoders() + { + $encoder = $this->_createHeaderEncoder(false); + $paramEncoder = $this->_createParamEncoder(false); + + $factory = $this->_createFactory($encoder, $paramEncoder); + + $this->_checking(Expectations::create() + -> one($encoder)->charsetChanged('utf-8') + -> one($paramEncoder)->charsetChanged('utf-8') + -> ignoring($encoder) + -> ignoring($paramEncoder) + ); + + $factory->charsetChanged('utf-8'); + } + + // -- Creation methods + + private function _createFactory($encoder = null, $paramEncoder = null) + { + return new Swift_Mime_SimpleHeaderFactory( + $encoder + ? $encoder : $this->_createHeaderEncoder(), + $paramEncoder + ? $paramEncoder : $this->_createParamEncoder(), + new Swift_Mime_Grammar() + ); + } + + private function _createHeaderEncoder($stub = true) + { + return $stub + ? $this->_stub('Swift_Mime_HeaderEncoder') + : $this->_mock('Swift_Mime_HeaderEncoder'); + } + + private function _createParamEncoder($stub = true) + { + return $stub + ? $this->_stub('Swift_Encoder') + : $this->_mock('Swift_Encoder'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderSetTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderSetTest.php new file mode 100644 index 0000000..624e8dc --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderSetTest.php @@ -0,0 +1,653 @@ +_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createMailboxHeader('From', array('person@domain'=>'Person')) + -> returns($this->_createHeader('From')) + ); + $set = $this->_createSet($factory); + $set->addMailboxHeader('From', array('person@domain'=>'Person')); + } + + public function testAddDateHeaderDelegatesToFactory() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createDateHeader('Date', 1234) + -> returns($this->_createHeader('Date')) + ); + $set = $this->_createSet($factory); + $set->addDateHeader('Date', 1234); + } + + public function testAddTextHeaderDelegatesToFactory() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createTextHeader('Subject', 'some text') + -> returns($this->_createHeader('Subject')) + ); + $set = $this->_createSet($factory); + $set->addTextHeader('Subject', 'some text'); + } + + public function testAddParameterizedHeaderDelegatesToFactory() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createParameterizedHeader( + 'Content-Type', 'text/plain', array('charset'=>'utf-8') + ) -> returns($this->_createHeader('Content-Type')) + ); + $set = $this->_createSet($factory); + $set->addParameterizedHeader('Content-Type', 'text/plain', + array('charset'=>'utf-8') + ); + } + + public function testAddIdHeaderDelegatesToFactory() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($this->_createHeader('Message-ID')) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + } + + public function testAddPathHeaderDelegatesToFactory() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createPathHeader('Return-Path', 'some@path') + -> returns($this->_createHeader('Return-Path')) + ); + $set = $this->_createSet($factory); + $set->addPathHeader('Return-Path', 'some@path'); + } + + public function testHasReturnsFalseWhenNoHeaders() + { + $set = $this->_createSet($this->_createFactory()); + $this->assertFalse($set->has('Some-Header')); + } + + public function testAddedMailboxHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createMailboxHeader('From', array('person@domain'=>'Person')) + -> returns($this->_createHeader('From')) + ); + $set = $this->_createSet($factory); + $set->addMailboxHeader('From', array('person@domain'=>'Person')); + $this->assertTrue($set->has('From')); + } + + public function testAddedDateHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createDateHeader('Date', 1234) + -> returns($this->_createHeader('Date')) + ); + $set = $this->_createSet($factory); + $set->addDateHeader('Date', 1234); + $this->assertTrue($set->has('Date')); + } + + public function testAddedTextHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createTextHeader('Subject', 'some text') + -> returns($this->_createHeader('Subject')) + ); + $set = $this->_createSet($factory); + $set->addTextHeader('Subject', 'some text'); + $this->assertTrue($set->has('Subject')); + } + + public function testAddedParameterizedHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createParameterizedHeader( + 'Content-Type', 'text/plain', array('charset'=>'utf-8') + ) -> returns($this->_createHeader('Content-Type')) + ); + $set = $this->_createSet($factory); + $set->addParameterizedHeader('Content-Type', 'text/plain', + array('charset'=>'utf-8') + ); + $this->assertTrue($set->has('Content-Type')); + } + + public function testAddedIdHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($this->_createHeader('Message-ID')) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertTrue($set->has('Message-ID')); + } + + public function testAddedPathHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createPathHeader('Return-Path', 'some@path') + -> returns($this->_createHeader('Return-Path')) + ); + $set = $this->_createSet($factory); + $set->addPathHeader('Return-Path', 'some@path'); + $this->assertTrue($set->has('Return-Path')); + } + + public function testNewlySetHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $header = $this->_createHeader('X-Foo', 'bar'); + $set = $this->_createSet($factory); + $set->set($header); + $this->assertTrue($set->has('X-Foo')); + } + + public function testHasCanAcceptOffset() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($this->_createHeader('Message-ID')) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertTrue($set->has('Message-ID', 0)); + } + + public function testHasWithIllegalOffsetReturnsFalse() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($this->_createHeader('Message-ID')) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertFalse($set->has('Message-ID', 1)); + } + + public function testHasCanDistinguishMultipleHeaders() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($this->_createHeader('Message-ID')) + -> ignoring($factory)->createIdHeader('Message-ID', 'other@id') + -> returns($this->_createHeader('Message-ID')) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Message-ID', 'other@id'); + $this->assertTrue($set->has('Message-ID', 1)); + } + + public function testGetWithUnspecifiedOffset() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertSame($header, $set->get('Message-ID')); + } + + public function testGetWithSpeiciedOffset() + { + $header0 = $this->_createHeader('Message-ID'); + $header1 = $this->_createHeader('Message-ID'); + $header2 = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header0) + -> ignoring($factory)->createIdHeader('Message-ID', 'other@id') + -> returns($header1) + -> ignoring($factory)->createIdHeader('Message-ID', 'more@id') + -> returns($header2) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Message-ID', 'other@id'); + $set->addIdHeader('Message-ID', 'more@id'); + $this->assertSame($header1, $set->get('Message-ID', 1)); + } + + public function testGetReturnsNullIfHeaderNotSet() + { + $set = $this->_createSet($this->_createFactory()); + $this->assertNull($set->get('Message-ID', 99)); + } + + public function testGetAllReturnsAllHeadersMatchingName() + { + $header0 = $this->_createHeader('Message-ID'); + $header1 = $this->_createHeader('Message-ID'); + $header2 = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header0) + -> ignoring($factory)->createIdHeader('Message-ID', 'other@id') + -> returns($header1) + -> ignoring($factory)->createIdHeader('Message-ID', 'more@id') + -> returns($header2) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Message-ID', 'other@id'); + $set->addIdHeader('Message-ID', 'more@id'); + + $this->assertEqual(array($header0, $header1, $header2), + $set->getAll('Message-ID') + ); + } + + public function testGetAllReturnsAllHeadersIfNoArguments() + { + $header0 = $this->_createHeader('Message-ID'); + $header1 = $this->_createHeader('Subject'); + $header2 = $this->_createHeader('To'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header0) + -> ignoring($factory)->createIdHeader('Subject', 'thing') + -> returns($header1) + -> ignoring($factory)->createIdHeader('To', 'person@example.org') + -> returns($header2) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Subject', 'thing'); + $set->addIdHeader('To', 'person@example.org'); + + $this->assertEqual(array($header0, $header1, $header2), + $set->getAll() + ); + } + + public function testGetAllReturnsEmptyArrayIfNoneSet() + { + $set = $this->_createSet($this->_createFactory()); + $this->assertEqual(array(), $set->getAll('Received')); + } + + public function testRemoveWithUnspecifiedOffset() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->remove('Message-ID'); + $this->assertFalse($set->has('Message-ID')); + } + + public function testRemoveWithSpecifiedIndexRemovesHeader() + { + $header0 = $this->_createHeader('Message-ID'); + $header1 = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header0) + -> ignoring($factory)->createIdHeader('Message-ID', 'other@id') + -> returns($header1) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Message-ID', 'other@id'); + $set->remove('Message-ID', 1); + $this->assertFalse($set->has('Message-ID', 1)); + } + + public function testRemoveWithSpecifiedIndexLeavesOtherHeaders() + { + $header0 = $this->_createHeader('Message-ID'); + $header1 = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header0) + -> ignoring($factory)->createIdHeader('Message-ID', 'other@id') + -> returns($header1) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Message-ID', 'other@id'); + $set->remove('Message-ID', 1); + $this->assertTrue($set->has('Message-ID', 0)); + } + + public function testRemoveWithInvalidOffsetDoesNothing() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->remove('Message-ID', 50); + $this->assertTrue($set->has('Message-ID')); + } + + public function testRemoveAllRemovesAllHeadersWithName() + { + $header0 = $this->_createHeader('Message-ID'); + $header1 = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header0) + -> ignoring($factory)->createIdHeader('Message-ID', 'other@id') + -> returns($header1) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Message-ID', 'other@id'); + $set->removeAll('Message-ID'); + $this->assertFalse($set->has('Message-ID', 0)); + $this->assertFalse($set->has('Message-ID', 1)); + } + + public function testHasIsNotCaseSensitive() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertTrue($set->has('message-id')); + } + + public function testGetIsNotCaseSensitive() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertSame($header, $set->get('message-id')); + } + + public function testGetAllIsNotCaseSensitive() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertEqual(array($header), $set->getAll('message-id')); + } + + public function testRemoveIsNotCaseSensitive() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->remove('message-id'); + $this->assertFalse($set->has('Message-ID')); + } + + public function testRemoveAllIsNotCaseSensitive() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createIdHeader('Message-ID', 'some@id') + -> returns($header) + ); + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->removeAll('message-id'); + $this->assertFalse($set->has('Message-ID')); + } + + public function testNewInstance() + { + $set = $this->_createSet($this->_createFactory()); + $instance = $set->newInstance(); + $this->assertIsA($instance, 'Swift_Mime_HeaderSet'); + } + + public function testToStringJoinsHeadersTogether() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createTextHeader('Foo', 'bar') + -> returns($this->_createHeader('Foo', 'bar')) + -> one($factory)->createTextHeader('Zip', 'buttons') + -> returns($this->_createHeader('Zip', 'buttons')) + ); + $set = $this->_createSet($factory); + $set->addTextHeader('Foo', 'bar'); + $set->addTextHeader('Zip', 'buttons'); + $this->assertEqual( + "Foo: bar\r\n" . + "Zip: buttons\r\n", + $set->toString() + ); + } + + public function testHeadersWithoutBodiesAreNotDisplayed() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createTextHeader('Foo', 'bar') + -> returns($this->_createHeader('Foo', 'bar')) + -> one($factory)->createTextHeader('Zip', '') + -> returns($this->_createHeader('Zip', '')) + ); + $set = $this->_createSet($factory); + $set->addTextHeader('Foo', 'bar'); + $set->addTextHeader('Zip', ''); + $this->assertEqual( + "Foo: bar\r\n", + $set->toString() + ); + } + + public function testHeadersWithoutBodiesCanBeForcedToDisplay() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createTextHeader('Foo', '') + -> returns($this->_createHeader('Foo', '')) + -> one($factory)->createTextHeader('Zip', '') + -> returns($this->_createHeader('Zip', '')) + ); + $set = $this->_createSet($factory); + $set->addTextHeader('Foo', ''); + $set->addTextHeader('Zip', ''); + $set->setAlwaysDisplayed(array('Foo', 'Zip')); + $this->assertEqual( + "Foo: \r\n" . + "Zip: \r\n", + $set->toString() + ); + } + + public function testHeaderSequencesCanBeSpecified() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createTextHeader('First', 'one') + -> returns($this->_createHeader('First', 'one')) + -> one($factory)->createTextHeader('Second', 'two') + -> returns($this->_createHeader('Second', 'two')) + -> one($factory)->createTextHeader('Third', 'three') + -> returns($this->_createHeader('Third', 'three')) + ); + $set = $this->_createSet($factory); + $set->addTextHeader('Third', 'three'); + $set->addTextHeader('First', 'one'); + $set->addTextHeader('Second', 'two'); + + $set->defineOrdering(array('First', 'Second', 'Third')); + + $this->assertEqual( + "First: one\r\n" . + "Second: two\r\n" . + "Third: three\r\n", + $set->toString() + ); + } + + public function testUnsortedHeadersAppearAtEnd() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createTextHeader('First', 'one') + -> returns($this->_createHeader('First', 'one')) + -> one($factory)->createTextHeader('Second', 'two') + -> returns($this->_createHeader('Second', 'two')) + -> one($factory)->createTextHeader('Third', 'three') + -> returns($this->_createHeader('Third', 'three')) + -> one($factory)->createTextHeader('Fourth', 'four') + -> returns($this->_createHeader('Fourth', 'four')) + -> one($factory)->createTextHeader('Fifth', 'five') + -> returns($this->_createHeader('Fifth', 'five')) + ); + $set = $this->_createSet($factory); + $set->addTextHeader('Fourth', 'four'); + $set->addTextHeader('Fifth', 'five'); + $set->addTextHeader('Third', 'three'); + $set->addTextHeader('First', 'one'); + $set->addTextHeader('Second', 'two'); + + $set->defineOrdering(array('First', 'Second', 'Third')); + + $this->assertEqual( + "First: one\r\n" . + "Second: two\r\n" . + "Third: three\r\n" . + "Fourth: four\r\n" . + "Fifth: five\r\n", + $set->toString() + ); + } + + public function testSettingCharsetNotifiesAlreadyExistingHeaders() + { + $subject = $this->_createHeader('Subject', 'some text'); + $xHeader = $this->_createHeader('X-Header', 'some text'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createTextHeader('Subject', 'some text') + -> returns($subject) + -> ignoring($factory)->createTextHeader('X-Header', 'some text') + -> returns($xHeader) + -> ignoring($factory) + -> one($subject)->setCharset('utf-8') + -> one($xHeader)->setCharset('utf-8') + ); + $set = $this->_createSet($factory); + $set->addTextHeader('Subject', 'some text'); + $set->addTextHeader('X-Header', 'some text'); + + $set->setCharset('utf-8'); + } + + public function testCharsetChangeNotifiesAlreadyExistingHeaders() + { + $subject = $this->_createHeader('Subject', 'some text'); + $xHeader = $this->_createHeader('X-Header', 'some text'); + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> ignoring($factory)->createTextHeader('Subject', 'some text') + -> returns($subject) + -> ignoring($factory)->createTextHeader('X-Header', 'some text') + -> returns($xHeader) + -> ignoring($factory) + -> one($subject)->setCharset('utf-8') + -> one($xHeader)->setCharset('utf-8') + ); + $set = $this->_createSet($factory); + $set->addTextHeader('Subject', 'some text'); + $set->addTextHeader('X-Header', 'some text'); + + $set->charsetChanged('utf-8'); + } + + public function testCharsetChangeNotifiesFactory() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->charsetChanged('utf-8') + -> ignoring($factory) + ); + $set = $this->_createSet($factory); + + $set->setCharset('utf-8'); + } + + // -- Creation methods + + private function _createSet($factory) + { + return new Swift_Mime_SimpleHeaderSet($factory); + } + + private function _createFactory() + { + return $this->_mock('Swift_Mime_HeaderFactory'); + } + + private function _createHeader($name, $body = '') + { + $header = $this->_mock('Swift_Mime_Header'); + $this->_checking(Expectations::create() + -> ignoring($header)->getFieldName() -> returns($name) + -> ignoring($header)->toString() -> returns(sprintf("%s: %s\r\n", $name, $body)) + -> ignoring($header)->getFieldBody() -> returns($body) + ); + + return $header; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMessageTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMessageTest.php new file mode 100644 index 0000000..0b89416 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMessageTest.php @@ -0,0 +1,801 @@ +_createMessage($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual( + Swift_Mime_MimeEntity::LEVEL_TOP, $message->getNestingLevel() + ); + } + + public function testDateIsReturnedFromHeader() + { + $date = $this->_createHeader('Date', 123); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Date' => $date)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(123, $message->getDate()); + } + + public function testDateIsSetInHeader() + { + $date = $this->_createHeader('Date', 123, array(), false); + $this->_checking(Expectations::create() + -> one($date)->setFieldBodyModel(1234) + -> ignoring($date) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Date' => $date)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setDate(1234); + } + + public function testDateHeaderIsCreatedIfNonePresent() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addDateHeader('Date', 1234) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setDate(1234); + } + + public function testDateHeaderIsAddedDuringConstruction() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addDateHeader('Date', pattern('/^[0-9]+$/D')) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + } + + public function testIdIsReturnedFromHeader() + { + /* -- RFC 2045, 7. + In constructing a high-level user agent, it may be desirable to allow + one body to make reference to another. Accordingly, bodies may be + labelled using the "Content-ID" header field, which is syntactically + identical to the "Message-ID" header field + */ + + $messageId = $this->_createHeader('Message-ID', 'a@b'); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Message-ID' => $messageId)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual('a@b', $message->getId()); + } + + public function testIdIsSetInHeader() + { + $messageId = $this->_createHeader('Message-ID', 'a@b', array(), false); + $this->_checking(Expectations::create() + -> one($messageId)->setFieldBodyModel('x@y') + -> ignoring($messageId) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Message-ID' => $messageId)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setId('x@y'); + } + + public function testIdIsAutoGenerated() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addIdHeader('Message-ID', pattern('/^.*?@.*?$/D')) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + } + + public function testSubjectIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.5. + */ + + $subject = $this->_createHeader('Subject', 'example subject'); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Subject' => $subject)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual('example subject', $message->getSubject()); + } + + public function testSubjectIsSetInHeader() + { + $subject = $this->_createHeader('Subject', '', array(), false); + $this->_checking(Expectations::create() + -> one($subject)->setFieldBodyModel('foo') + -> ignoring($subject) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Subject' => $subject)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setSubject('foo'); + } + + public function testSubjectHeaderIsCreatedIfNotPresent() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addTextHeader('Subject', 'example subject') + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setSubject('example subject'); + } + + public function testReturnPathIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.7. + */ + + $path = $this->_createHeader('Return-Path', 'bounces@domain'); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Return-Path' => $path)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual('bounces@domain', $message->getReturnPath()); + } + + public function testReturnPathIsSetInHeader() + { + $path = $this->_createHeader('Return-Path', '', array(), false); + $this->_checking(Expectations::create() + -> one($path)->setFieldBodyModel('bounces@domain') + -> ignoring($path) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Return-Path' => $path)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setReturnPath('bounces@domain'); + } + + public function testReturnPathHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addPathHeader('Return-Path', 'bounces@domain') + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setReturnPath('bounces@domain'); + } + + public function testSenderIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.2. + */ + + $sender = $this->_createHeader('Sender', array('sender@domain'=>'Name')); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Sender' => $sender)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(array('sender@domain'=>'Name'), $message->getSender()); + } + + public function testSenderIsSetInHeader() + { + $sender = $this->_createHeader('Sender', array('sender@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($sender)->setFieldBodyModel(array('other@domain'=>'Other')) + -> ignoring($sender) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Sender' => $sender)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setSender(array('other@domain'=>'Other')); + } + + public function testSenderHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('Sender', (array) 'sender@domain') + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setSender('sender@domain'); + } + + public function testNameCanBeUsedInSenderHeader() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('Sender', array('sender@domain'=>'Name')) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setSender('sender@domain', 'Name'); + } + + public function testFromIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.2. + */ + + $from = $this->_createHeader('From', array('from@domain'=>'Name')); + $message = $this->_createMessage( + $this->_createHeaderSet(array('From' => $from)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(array('from@domain'=>'Name'), $message->getFrom()); + } + + public function testFromIsSetInHeader() + { + $from = $this->_createHeader('From', array('from@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($from)->setFieldBodyModel(array('other@domain'=>'Other')) + -> ignoring($from) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('From' => $from)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setFrom(array('other@domain'=>'Other')); + } + + public function testFromIsAddedToHeadersDuringAddFrom() + { + $from = $this->_createHeader('From', array('from@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($from)->setFieldBodyModel(array('from@domain'=>'Name', 'other@domain'=>'Other')) + -> ignoring($from) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('From' => $from)), + $this->_createEncoder(), $this->_createCache() + ); + $message->addFrom('other@domain', 'Other'); + } + + public function testFromHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('From', (array) 'from@domain') + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setFrom('from@domain'); + } + + public function testPersonalNameCanBeUsedInFromAddress() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('From', array('from@domain'=>'Name')) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setFrom('from@domain', 'Name'); + } + + public function testReplyToIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.2. + */ + + $reply = $this->_createHeader('Reply-To', array('reply@domain'=>'Name')); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Reply-To' => $reply)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(array('reply@domain'=>'Name'), $message->getReplyTo()); + } + + public function testReplyToIsSetInHeader() + { + $reply = $this->_createHeader('Reply-To', array('reply@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($reply)->setFieldBodyModel(array('other@domain'=>'Other')) + -> ignoring($reply) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Reply-To' => $reply)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setReplyTo(array('other@domain'=>'Other')); + } + + public function testReplyToIsAddedToHeadersDuringAddReplyTo() + { + $replyTo = $this->_createHeader('Reply-To', array('from@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($replyTo)->setFieldBodyModel(array('from@domain'=>'Name', 'other@domain'=>'Other')) + -> ignoring($replyTo) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Reply-To' => $replyTo)), + $this->_createEncoder(), $this->_createCache() + ); + $message->addReplyTo('other@domain', 'Other'); + } + + public function testReplyToHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('Reply-To', (array) 'reply@domain') + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setReplyTo('reply@domain'); + } + + public function testNameCanBeUsedInReplyTo() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('Reply-To', array('reply@domain'=>'Name')) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setReplyTo('reply@domain', 'Name'); + } + + public function testToIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.3. + */ + + $to = $this->_createHeader('To', array('to@domain'=>'Name')); + $message = $this->_createMessage( + $this->_createHeaderSet(array('To' => $to)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(array('to@domain'=>'Name'), $message->getTo()); + } + + public function testToIsSetInHeader() + { + $to = $this->_createHeader('To', array('to@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($to)->setFieldBodyModel(array('other@domain'=>'Other')) + -> ignoring($to) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('To' => $to)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setTo(array('other@domain'=>'Other')); + } + + public function testToIsAddedToHeadersDuringAddTo() + { + $to = $this->_createHeader('To', array('from@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($to)->setFieldBodyModel(array('from@domain'=>'Name', 'other@domain'=>'Other')) + -> ignoring($to) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('To' => $to)), + $this->_createEncoder(), $this->_createCache() + ); + $message->addTo('other@domain', 'Other'); + } + + public function testToHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('To', (array) 'to@domain') + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setTo('to@domain'); + } + + public function testNameCanBeUsedInToHeader() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('To', array('to@domain'=>'Name')) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setTo('to@domain', 'Name'); + } + + public function testCcIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.3. + */ + + $cc = $this->_createHeader('Cc', array('cc@domain'=>'Name')); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Cc' => $cc)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(array('cc@domain'=>'Name'), $message->getCc()); + } + + public function testCcIsSetInHeader() + { + $cc = $this->_createHeader('Cc', array('cc@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($cc)->setFieldBodyModel(array('other@domain'=>'Other')) + -> ignoring($cc) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Cc' => $cc)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setCc(array('other@domain'=>'Other')); + } + + public function testCcIsAddedToHeadersDuringAddCc() + { + $cc = $this->_createHeader('Cc', array('from@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($cc)->setFieldBodyModel(array('from@domain'=>'Name', 'other@domain'=>'Other')) + -> ignoring($cc) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Cc' => $cc)), + $this->_createEncoder(), $this->_createCache() + ); + $message->addCc('other@domain', 'Other'); + } + + public function testCcHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('Cc', (array) 'cc@domain') + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setCc('cc@domain'); + } + + public function testNameCanBeUsedInCcHeader() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('Cc', array('cc@domain'=>'Name')) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setCc('cc@domain', 'Name'); + } + + public function testBccIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.3. + */ + + $bcc = $this->_createHeader('Bcc', array('bcc@domain'=>'Name')); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Bcc' => $bcc)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(array('bcc@domain'=>'Name'), $message->getBcc()); + } + + public function testBccIsSetInHeader() + { + $bcc = $this->_createHeader('Bcc', array('bcc@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($bcc)->setFieldBodyModel(array('other@domain'=>'Other')) + -> ignoring($bcc) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Bcc' => $bcc)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setBcc(array('other@domain'=>'Other')); + } + + public function testBccIsAddedToHeadersDuringAddBcc() + { + $bcc = $this->_createHeader('Bcc', array('from@domain'=>'Name'), + array(), false + ); + $this->_checking(Expectations::create() + -> one($bcc)->setFieldBodyModel(array('from@domain'=>'Name', 'other@domain'=>'Other')) + -> ignoring($bcc) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Bcc' => $bcc)), + $this->_createEncoder(), $this->_createCache() + ); + $message->addBcc('other@domain', 'Other'); + } + + public function testBccHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('Bcc', (array) 'bcc@domain') + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setBcc('bcc@domain'); + } + + public function testNameCanBeUsedInBcc() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader('Bcc', array('bcc@domain'=>'Name')) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setBcc('bcc@domain', 'Name'); + } + + public function testPriorityIsReadFromHeader() + { + $prio = $this->_createHeader('X-Priority', '2 (High)'); + $message = $this->_createMessage( + $this->_createHeaderSet(array('X-Priority' => $prio)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(2, $message->getPriority()); + } + + public function testPriorityIsSetInHeader() + { + $prio = $this->_createHeader('X-Priority', '2 (High)', array(), false); + $this->_checking(Expectations::create() + -> one($prio)->setFieldBodyModel('5 (Lowest)') + -> ignoring($prio) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('X-Priority' => $prio)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setPriority(5); + } + + public function testPriorityHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addTextHeader('X-Priority', '4 (Low)') + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setPriority(4); + } + + public function testReadReceiptAddressReadFromHeader() + { + $rcpt = $this->_createHeader('Disposition-Notification-To', + array('chris@swiftmailer.org'=>'Chris') + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Disposition-Notification-To' => $rcpt)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEqual(array('chris@swiftmailer.org'=>'Chris'), + $message->getReadReceiptTo() + ); + } + + public function testReadReceiptIsSetInHeader() + { + $rcpt = $this->_createHeader('Disposition-Notification-To', array(), array(), false); + $this->_checking(Expectations::create() + -> one($rcpt)->setFieldBodyModel('mark@swiftmailer.org') + -> ignoring($rcpt) + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Disposition-Notification-To' => $rcpt)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setReadReceiptTo('mark@swiftmailer.org'); + } + + public function testReadReceiptHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $this->_checking(Expectations::create() + -> one($headers)->addMailboxHeader( + 'Disposition-Notification-To', 'mark@swiftmailer.org' + ) + -> ignoring($headers) + ); + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setReadReceiptTo('mark@swiftmailer.org'); + } + + public function testChildrenCanBeAttached() + { + $child1 = $this->_createChild(); + $child2 = $this->_createChild(); + + $message = $this->_createMessage($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + + $message->attach($child1); + $message->attach($child2); + + $this->assertEqual(array($child1, $child2), $message->getChildren()); + } + + public function testChildrenCanBeDetached() + { + $child1 = $this->_createChild(); + $child2 = $this->_createChild(); + + $message = $this->_createMessage($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + + $message->attach($child1); + $message->attach($child2); + + $message->detach($child1); + + $this->assertEqual(array($child2), $message->getChildren()); + } + + public function testEmbedAttachesChild() + { + $child = $this->_createChild(); + + $message = $this->_createMessage($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + + $message->embed($child); + + $this->assertEqual(array($child), $message->getChildren()); + } + + public function testEmbedReturnsValidCid() + { + $child = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_RELATED, '', + false + ); + $this->_checking(Expectations::create() + -> ignoring($child)->getId() -> returns('foo@bar') + -> ignoring($child) + ); + $message = $this->_createMessage($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + + $this->assertEqual('cid:foo@bar', $message->embed($child)); + } + + public function testFluidInterface() + { + $child = $this->_createChild(); + $message = $this->_createMessage($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertSame($message, + $message + ->setContentType('text/plain') + ->setEncoder($this->_createEncoder()) + ->setId('foo@bar') + ->setDescription('my description') + ->setMaxLineLength(998) + ->setBody('xx') + ->setBoundary('xyz') + ->setChildren(array()) + ->setCharset('iso-8859-1') + ->setFormat('flowed') + ->setDelSp(false) + ->setSubject('subj') + ->setDate(123) + ->setReturnPath('foo@bar') + ->setSender('foo@bar') + ->setFrom(array('x@y' => 'XY')) + ->setReplyTo(array('ab@cd' => 'ABCD')) + ->setTo(array('chris@site.tld', 'mark@site.tld')) + ->setCc('john@somewhere.tld') + ->setBcc(array('one@site', 'two@site' => 'Two')) + ->setPriority(4) + ->setReadReceiptTo('a@b') + ->attach($child) + ->detach($child) + ); + } + + // -- Private helpers + + //abstract + protected function _createEntity($headers, $encoder, $cache) + { + return $this->_createMessage($headers, $encoder, $cache); + } + + protected function _createMimePart($headers, $encoder, $cache) + { + return $this->_createMessage($headers, $encoder, $cache); + } + + private function _createMessage($headers, $encoder, $cache) + { + return new Swift_Mime_SimpleMessage($headers, $encoder, $cache, new Swift_Mime_Grammar()); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMimeEntityTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMimeEntityTest.php new file mode 100644 index 0000000..02277e3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMimeEntityTest.php @@ -0,0 +1,15 @@ +assertEqual(10, $plugin->getThreshold()); + $plugin->setThreshold(100); + $this->assertEqual(100, $plugin->getThreshold()); + } + + public function testSleepTimeCanBeSetAndFetched() + { + $plugin = new Swift_Plugins_AntiFloodPlugin(10, 5); + $this->assertEqual(5, $plugin->getSleepTime()); + $plugin->setSleepTime(1); + $this->assertEqual(1, $plugin->getSleepTime()); + } + + public function testPluginStopsConnectionAfterThreshold() + { + $transport = $this->_createTransport(); + $evt = $this->_createSendEvent($transport); + $this->_checking(Expectations::create() + -> one($transport)->start() + -> one($transport)->stop() + -> ignoring($transport) + ); + + $plugin = new Swift_Plugins_AntiFloodPlugin(10); + for ($i = 0; $i < 12; $i++) { + $plugin->sendPerformed($evt); + } + } + + public function testPluginCanStopAndStartMultipleTimes() + { + $transport = $this->_createTransport(); + $evt = $this->_createSendEvent($transport); + $this->_checking(Expectations::create() + -> exactly(5)->of($transport)->start() + -> exactly(5)->of($transport)->stop() + -> ignoring($transport) + ); + + $plugin = new Swift_Plugins_AntiFloodPlugin(2); + for ($i = 0; $i < 11; $i++) { + $plugin->sendPerformed($evt); + } + } + + public function testPluginCanSleepDuringRestart() + { + $sleeper = $this->_createSleeper(); + $transport = $this->_createTransport(); + $evt = $this->_createSendEvent($transport); + $this->_checking(Expectations::create() + -> one($sleeper)->sleep(10) + -> one($transport)->start() + -> one($transport)->stop() + -> ignoring($transport) + ); + + $plugin = new Swift_Plugins_AntiFloodPlugin(99, 10, $sleeper); + for ($i = 0; $i < 101; $i++) { + $plugin->sendPerformed($evt); + } + } + + // -- Creation Methods + + private function _createTransport() + { + return $this->_mock('Swift_Transport'); + } + + private function _createSendEvent($transport) + { + $evt = $this->_mock('Swift_Events_SendEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getSource() -> returns($transport) + -> ignoring($evt)->getTransport() -> returns($transport) + ); + + return $evt; + } + + private function _createSleeper() + { + return $this->_mock('Swift_Plugins_Sleeper'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/BandwidthMonitorPluginTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/BandwidthMonitorPluginTest.php new file mode 100644 index 0000000..aecce89 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/BandwidthMonitorPluginTest.php @@ -0,0 +1,129 @@ +_monitor = new Swift_Plugins_BandwidthMonitorPlugin(); + } + + public function testBytesOutIncreasesAccordingToMessageLength() + { + $message = $this->_createMessageWithByteCount(6); + $evt = $this->_createSendEvent($message); + + $this->assertEqual(0, $this->_monitor->getBytesOut()); + $this->_monitor->sendPerformed($evt); + $this->assertEqual(6, $this->_monitor->getBytesOut()); + $this->_monitor->sendPerformed($evt); + $this->assertEqual(12, $this->_monitor->getBytesOut()); + } + + public function testBytesOutIncreasesWhenCommandsSent() + { + $evt = $this->_createCommandEvent("RCPT TO: \r\n"); + + $this->assertEqual(0, $this->_monitor->getBytesOut()); + $this->_monitor->commandSent($evt); + $this->assertEqual(24, $this->_monitor->getBytesOut()); + $this->_monitor->commandSent($evt); + $this->assertEqual(48, $this->_monitor->getBytesOut()); + } + + public function testBytesInIncreasesWhenResponsesReceived() + { + $evt = $this->_createResponseEvent("250 Ok\r\n"); + + $this->assertEqual(0, $this->_monitor->getBytesIn()); + $this->_monitor->responseReceived($evt); + $this->assertEqual(8, $this->_monitor->getBytesIn()); + $this->_monitor->responseReceived($evt); + $this->assertEqual(16, $this->_monitor->getBytesIn()); + } + + public function testCountersCanBeReset() + { + $evt = $this->_createResponseEvent("250 Ok\r\n"); + + $this->assertEqual(0, $this->_monitor->getBytesIn()); + $this->_monitor->responseReceived($evt); + $this->assertEqual(8, $this->_monitor->getBytesIn()); + $this->_monitor->responseReceived($evt); + $this->assertEqual(16, $this->_monitor->getBytesIn()); + + $evt = $this->_createCommandEvent("RCPT TO: \r\n"); + + $this->assertEqual(0, $this->_monitor->getBytesOut()); + $this->_monitor->commandSent($evt); + $this->assertEqual(24, $this->_monitor->getBytesOut()); + $this->_monitor->commandSent($evt); + $this->assertEqual(48, $this->_monitor->getBytesOut()); + + $this->_monitor->reset(); + + $this->assertEqual(0, $this->_monitor->getBytesOut()); + $this->assertEqual(0, $this->_monitor->getBytesIn()); + } + + // -- Creation Methods + + private function _createSendEvent($message) + { + $evt = $this->_mock('Swift_Events_SendEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getMessage() -> returns($message) + ); + + return $evt; + } + + private function _createCommandEvent($command) + { + $evt = $this->_mock('Swift_Events_CommandEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getCommand() -> returns($command) + ); + + return $evt; + } + + private function _createResponseEvent($response) + { + $evt = $this->_mock('Swift_Events_ResponseEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getResponse() -> returns($response) + ); + + return $evt; + } + + private function _createMessageWithByteCount($bytes) + { + $this->_bytes = $bytes; + $msg = $this->_mock('Swift_Mime_Message'); + $this->_checking(Expectations::create() + -> ignoring($msg)->toByteStream(any()) -> calls(array($this, '_write')) + ); + + return $msg; + } + + private $_bytes = 0; + public function _write($invocation) + { + $args = $invocation->getArguments(); + $is = $args[0]; + for ($i = 0; $i < $this->_bytes; ++$i) { + $is->write('x'); + } + } + +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/DecoratorPluginTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/DecoratorPluginTest.php new file mode 100644 index 0000000..46bc63a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/DecoratorPluginTest.php @@ -0,0 +1,235 @@ +_createMessage( + $this->_createHeaders(), + array('zip@button.tld' => 'Zipathon'), + array('chris.corbyn@swiftmailer.org' => 'Chris'), + 'Subject', + 'Hello {name}, you are customer #{id}' + ); + $this->_checking(Expectations::create() + -> one($message)->setBody('Hello Zip, you are customer #456') + -> ignoring($message) + ); + + $plugin = $this->_createPlugin( + array('zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456')) + ); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + + public function testReplacementsCanBeAppliedToSameMessageMultipleTimes() + { + $message = $this->_createMessage( + $this->_createHeaders(), + array('zip@button.tld' => 'Zipathon', 'foo@bar.tld' => 'Foo'), + array('chris.corbyn@swiftmailer.org' => 'Chris'), + 'Subject', + 'Hello {name}, you are customer #{id}' + ); + $this->_checking(Expectations::create() + -> one($message)->setBody('Hello Zip, you are customer #456') + -> one($message)->setBody('Hello {name}, you are customer #{id}') + -> one($message)->setBody('Hello Foo, you are customer #123') + -> ignoring($message) + ); + + $plugin = $this->_createPlugin( + array( + 'foo@bar.tld' => array('{name}' => 'Foo', '{id}' => '123'), + 'zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456') + ) + ); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + + public function testReplacementsCanBeMadeInHeaders() + { + $headers = $this->_createHeaders(array( + $returnPathHeader = $this->_createHeader('Return-Path', 'foo-{id}@swiftmailer.org'), + $toHeader = $this->_createHeader('Subject', 'A message for {name}!') + )); + + $message = $this->_createMessage( + $headers, + array('zip@button.tld' => 'Zipathon'), + array('chris.corbyn@swiftmailer.org' => 'Chris'), + 'A message for {name}!', + 'Hello {name}, you are customer #{id}' + ); + $this->_checking(Expectations::create() + -> one($message)->setBody('Hello Zip, you are customer #456') + -> one($toHeader)->setFieldBodyModel('A message for Zip!') + -> one($returnPathHeader)->setFieldBodyModel('foo-456@swiftmailer.org') + -> ignoring($message) + -> ignoring($toHeader) + -> ignoring($returnPathHeader) + ); + + $plugin = $this->_createPlugin( + array('zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456')) + ); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + + public function testReplacementsAreMadeOnSubparts() + { + $part1 = $this->_createPart('text/plain', 'Your name is {name}?', '1@x'); + $part2 = $this->_createPart('text/html', 'Your name is {name}?', '2@x'); + $message = $this->_createMessage( + $this->_createHeaders(), + array('zip@button.tld' => 'Zipathon'), + array('chris.corbyn@swiftmailer.org' => 'Chris'), + 'A message for {name}!', + 'Subject' + ); + $this->_checking(Expectations::create() + -> ignoring($message)->getChildren() -> returns(array($part1, $part2)) + -> one($part1)->setBody('Your name is Zip?') + -> one($part2)->setBody('Your name is Zip?') + -> ignoring($part1) + -> ignoring($part2) + -> ignoring($message) + ); + + $plugin = $this->_createPlugin( + array('zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456')) + ); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + + public function testReplacementsCanBeTakenFromCustomReplacementsObject() + { + $message = $this->_createMessage( + $this->_createHeaders(), + array('foo@bar' => 'Foobar', 'zip@zap' => 'Zip zap'), + array('chris.corbyn@swiftmailer.org' => 'Chris'), + 'Subject', + 'Something {a}' + ); + + $replacements = $this->_createReplacements(); + + $this->_checking(Expectations::create() + -> one($message)->setBody('Something b') + -> one($message)->setBody('Something c') + -> one($replacements)->getReplacementsFor('foo@bar') -> returns(array('{a}'=>'b')) + -> one($replacements)->getReplacementsFor('zip@zap') -> returns(array('{a}'=>'c')) + -> ignoring($message) + ); + + $plugin = $this->_createPlugin($replacements); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + + // -- Creation methods + + private function _createMessage($headers, $to = array(), $from = null, $subject = null, + $body = null) + { + $message = $this->_mock('Swift_Mime_Message'); + foreach ($to as $addr => $name) { + $this->_checking(Expectations::create() + -> one($message)->getTo() -> returns(array($addr => $name)) + ); + } + $this->_checking(Expectations::create() + -> allowing($message)->getHeaders() -> returns($headers) + -> ignoring($message)->getFrom() -> returns($from) + -> ignoring($message)->getSubject() -> returns($subject) + -> ignoring($message)->getBody() -> returns($body) + ); + + return $message; + } + + private function _createPlugin($replacements) + { + return new Swift_Plugins_DecoratorPlugin($replacements); + } + + private function _createReplacements() + { + return $this->_mock('Swift_Plugins_Decorator_Replacements'); + } + + private function _createSendEvent(Swift_Mime_Message $message) + { + $evt = $this->_mock('Swift_Events_SendEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getMessage() -> returns($message) + -> ignoring($evt) + ); + + return $evt; + } + + private function _createPart($type, $body, $id) + { + $part = $this->_mock('Swift_Mime_MimeEntity'); + $this->_checking(Expectations::create() + -> ignoring($part)->getContentType() -> returns($type) + -> ignoring($part)->getBody() -> returns($body) + -> ignoring($part)->getId() -> returns($id) + ); + + return $part; + } + + private function _createHeaders($headers = array()) + { + $set = $this->_mock('Swift_Mime_HeaderSet'); + + $this->_checking(Expectations::create() + -> allowing($set)->getAll() -> returns($headers) + -> ignoring($set) + ); + + foreach ($headers as $header) { + $set->set($header); + } + + return $set; + } + + private function _createHeader($name, $body = '') + { + $header = $this->_mock('Swift_Mime_Header'); + $this->_checking(Expectations::create() + -> ignoring($header)->getFieldName() -> returns($name) + -> ignoring($header)->getFieldBodyModel() -> returns($body) + ); + + return $header; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/LoggerPluginTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/LoggerPluginTest.php new file mode 100644 index 0000000..9670689 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/LoggerPluginTest.php @@ -0,0 +1,198 @@ +_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->add('foo') + ); + + $plugin = $this->_createPlugin($logger); + $plugin->add('foo'); + } + + public function testLoggerDelegatesDumpingEntries() + { + $logger = $this->_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->dump() -> returns('foobar') + ); + + $plugin = $this->_createPlugin($logger); + $this->assertEqual('foobar', $plugin->dump()); + } + + public function testLoggerDelegatesClearingEntries() + { + $logger = $this->_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->clear() + ); + + $plugin = $this->_createPlugin($logger); + $plugin->clear(); + } + + public function testCommandIsSentToLogger() + { + $evt = $this->_createCommandEvent("foo\r\n"); + $logger = $this->_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->add(pattern('~foo\r\n~')) + ); + + $plugin = $this->_createPlugin($logger); + $plugin->commandSent($evt); + } + + public function testResponseIsSentToLogger() + { + $evt = $this->_createResponseEvent("354 Go ahead\r\n"); + $logger = $this->_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->add(pattern('~354 Go ahead\r\n~')) + ); + + $plugin = $this->_createPlugin($logger); + $plugin->responseReceived($evt); + } + + public function testTransportBeforeStartChangeIsSentToLogger() + { + $evt = $this->_createTransportChangeEvent(); + $logger = $this->_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->add(any()) + ); + + $plugin = $this->_createPlugin($logger); + $plugin->beforeTransportStarted($evt); + } + + public function testTransportStartChangeIsSentToLogger() + { + $evt = $this->_createTransportChangeEvent(); + $logger = $this->_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->add(any()) + ); + + $plugin = $this->_createPlugin($logger); + $plugin->transportStarted($evt); + } + + public function testTransportStopChangeIsSentToLogger() + { + $evt = $this->_createTransportChangeEvent(); + $logger = $this->_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->add(any()) + ); + + $plugin = $this->_createPlugin($logger); + $plugin->transportStopped($evt); + } + + public function testTransportBeforeStopChangeIsSentToLogger() + { + $evt = $this->_createTransportChangeEvent(); + $logger = $this->_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->add(any()) + ); + + $plugin = $this->_createPlugin($logger); + $plugin->beforeTransportStopped($evt); + } + + public function testExceptionsArePassedToDelegateAndLeftToBubbleUp() + { + $transport = $this->_createTransport(); + $evt = $this->_createTransportExceptionEvent(); + $logger = $this->_createLogger(); + $this->_checking(Expectations::create() + -> one($logger)->add(any()) + -> allowing($logger) + ); + + $plugin = $this->_createPlugin($logger); + try { + $plugin->exceptionThrown($evt); + $this->fail('Exception should bubble up.'); + } catch (Swift_TransportException $ex) { + } + } + + // -- Creation Methods + + private function _createLogger() + { + return $this->_mock('Swift_Plugins_Logger'); + } + + private function _createPlugin($logger) + { + return new Swift_Plugins_LoggerPlugin($logger); + } + + private function _createCommandEvent($command) + { + $evt = $this->_mock('Swift_Events_CommandEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getCommand() -> returns($command) + -> ignoring($evt) + ); + + return $evt; + } + + private function _createResponseEvent($response) + { + $evt = $this->_mock('Swift_Events_ResponseEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getResponse() -> returns($response) + -> ignoring($evt) + ); + + return $evt; + } + + private function _createTransport() + { + return $this->_mock('Swift_Transport'); + } + + private function _createTransportChangeEvent() + { + $evt = $this->_mock('Swift_Events_TransportChangeEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getSource() -> returns($this->_createTransport()) + -> ignoring($evt) + ); + + return $evt; + } + + private function _createTransportExceptionEvent() + { + $evt = $this->_mock('Swift_Events_TransportExceptionEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getException() -> returns(new Swift_TransportException('')) + -> ignoring($evt) + ); + + return $evt; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/ArrayLoggerTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/ArrayLoggerTest.php new file mode 100644 index 0000000..6ecff3a --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/ArrayLoggerTest.php @@ -0,0 +1,70 @@ +add(">> Foo\r\n"); + $this->assertEqual(">> Foo\r\n", $logger->dump()); + } + + public function testAddingMultipleEntriesDumpsMultipleLines() + { + $logger = new Swift_Plugins_Loggers_ArrayLogger(); + $logger->add(">> FOO\r\n"); + $logger->add("<< 502 That makes no sense\r\n"); + $logger->add(">> RSET\r\n"); + $logger->add("<< 250 OK\r\n"); + + $this->assertEqual( + ">> FOO\r\n" . PHP_EOL . + "<< 502 That makes no sense\r\n" . PHP_EOL . + ">> RSET\r\n" . PHP_EOL . + "<< 250 OK\r\n", + $logger->dump() + ); + } + + public function testLogCanBeCleared() + { + $logger = new Swift_Plugins_Loggers_ArrayLogger(); + $logger->add(">> FOO\r\n"); + $logger->add("<< 502 That makes no sense\r\n"); + $logger->add(">> RSET\r\n"); + $logger->add("<< 250 OK\r\n"); + + $this->assertEqual( + ">> FOO\r\n" . PHP_EOL . + "<< 502 That makes no sense\r\n" . PHP_EOL . + ">> RSET\r\n" . PHP_EOL . + "<< 250 OK\r\n", + $logger->dump() + ); + + $logger->clear(); + + $this->assertEqual('', $logger->dump()); + } + + public function testLengthCanBeTruncated() + { + $logger = new Swift_Plugins_Loggers_ArrayLogger(2); + $logger->add(">> FOO\r\n"); + $logger->add("<< 502 That makes no sense\r\n"); + $logger->add(">> RSET\r\n"); + $logger->add("<< 250 OK\r\n"); + + $this->assertEqual( + ">> RSET\r\n" . PHP_EOL . + "<< 250 OK\r\n", + $logger->dump(), + '%s: Log should be truncated to last 2 entries' + ); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/EchoLoggerTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/EchoLoggerTest.php new file mode 100644 index 0000000..1c5bcc6 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/EchoLoggerTest.php @@ -0,0 +1,29 @@ +add(">> Foo"); + $data = ob_get_clean(); + + $this->assertEqual(">> Foo" . PHP_EOL, $data); + } + + public function testAddingEntryDumpsEscapedLineWithHtml() + { + $logger = new Swift_Plugins_Loggers_EchoLogger(true); + ob_start(); + $logger->add(">> Foo"); + $data = ob_get_clean(); + + $this->assertEqual(">> Foo
    " . PHP_EOL, $data); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/PopBeforeSmtpPluginTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/PopBeforeSmtpPluginTest.php new file mode 100644 index 0000000..3836925 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/PopBeforeSmtpPluginTest.php @@ -0,0 +1,114 @@ +_createConnection(); + + $plugin = $this->_createPlugin('pop.host.tld', 110); + $plugin->setConnection($connection); + + $transport = $this->_createTransport(); + $evt = $this->_createTransportChangeEvent($transport); + + $this->_checking(Expectations::create() + -> one($connection)->connect() + -> ignoring($connection) + ); + + $plugin->beforeTransportStarted($evt); + } + + public function testPluginDisconnectsFromPop3HostBeforeTransportStarts() + { + $connection = $this->_createConnection(); + + $plugin = $this->_createPlugin('pop.host.tld', 110); + $plugin->setConnection($connection); + + $transport = $this->_createTransport(); + $evt = $this->_createTransportChangeEvent($transport); + + $this->_checking(Expectations::create() + -> one($connection)->disconnect() + -> ignoring($connection) + ); + + $plugin->beforeTransportStarted($evt); + } + + public function testPluginDoesNotConnectToSmtpIfBoundToDifferentTransport() + { + $connection = $this->_createConnection(); + + $smtp = $this->_createTransport(); + + $plugin = $this->_createPlugin('pop.host.tld', 110); + $plugin->setConnection($connection); + $plugin->bindSmtp($smtp); + + $transport = $this->_createTransport(); + $evt = $this->_createTransportChangeEvent($transport); + + $this->_checking(Expectations::create() + -> never($connection) + ); + + $plugin->beforeTransportStarted($evt); + } + + public function testPluginCanBindToSpecificTransport() + { + $connection = $this->_createConnection(); + + $smtp = $this->_createTransport(); + + $plugin = $this->_createPlugin('pop.host.tld', 110); + $plugin->setConnection($connection); + $plugin->bindSmtp($smtp); + + $evt = $this->_createTransportChangeEvent($smtp); + + $this->_checking(Expectations::create() + -> one($connection)->connect() + -> ignoring($connection) + ); + + $plugin->beforeTransportStarted($evt); + } + + // -- Creation Methods + + private function _createTransport() + { + return $this->_mock('Swift_Transport'); + } + + private function _createTransportChangeEvent($transport) + { + $evt = $this->_mock('Swift_Events_TransportChangeEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getSource() -> returns($transport) + -> ignoring($evt)->getTransport() -> returns($transport) + ); + + return $evt; + } + + public function _createConnection() + { + return $this->_mock('Swift_Plugins_Pop_Pop3Connection'); + } + + public function _createPlugin($host, $port, $crypto = null) + { + return new Swift_Plugins_PopBeforeSmtpPlugin($host, $port, $crypto); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/RedirectingPluginTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/RedirectingPluginTest.php new file mode 100644 index 0000000..6f65c25 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/RedirectingPluginTest.php @@ -0,0 +1,113 @@ +assertEqual('fabien@example.com', $plugin->getRecipient()); + $plugin->setRecipient('chris@example.com'); + $this->assertEqual('chris@example.com', $plugin->getRecipient()); + } + + public function testPluginChangesRecipients() + { + $message = Swift_Message::newInstance() + ->setSubject('...') + ->setFrom(array('john@example.com' => 'John Doe')) + ->setTo($to = array( + 'fabien-to@example.com' => 'Fabien (To)', + 'chris-to@example.com' => 'Chris (To)', + )) + ->setCc($cc = array( + 'fabien-cc@example.com' => 'Fabien (Cc)', + 'chris-cc@example.com' => 'Chris (Cc)', + )) + ->setBcc($bcc = array( + 'fabien-bcc@example.com' => 'Fabien (Bcc)', + 'chris-bcc@example.com' => 'Chris (Bcc)', + )) + ->setBody('...') + ; + + $plugin = new Swift_Plugins_RedirectingPlugin('god@example.com'); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + + $this->assertEqual($message->getTo(), array('god@example.com' => '')); + $this->assertEqual($message->getCc(), array()); + $this->assertEqual($message->getBcc(), array()); + + $plugin->sendPerformed($evt); + + $this->assertEqual($message->getTo(), $to); + $this->assertEqual($message->getCc(), $cc); + $this->assertEqual($message->getBcc(), $bcc); + } + + public function testPluginRespectsAWhitelistOfPatterns() + { + $message = Swift_Message::newInstance() + ->setSubject('...') + ->setFrom(array('john@example.com' => 'John Doe')) + ->setTo($to = array( + 'fabien-to@example.com' => 'Fabien (To)', + 'chris-to@example.com' => 'Chris (To)', + 'lars-to@internal.com' => 'Lars (To)', + )) + ->setCc($cc = array( + 'fabien-cc@example.com' => 'Fabien (Cc)', + 'chris-cc@example.com' => 'Chris (Cc)', + 'lars-cc@internal.org' => 'Lars (Cc)', + )) + ->setBcc($bcc = array( + 'fabien-bcc@example.com' => 'Fabien (Bcc)', + 'chris-bcc@example.com' => 'Chris (Bcc)', + 'john-bcc@example.org' => 'John (Bcc)', + )) + ->setBody('...') + ; + + $recipient = 'god@example.com'; + $patterns = array('/^.*@internal.[a-z]+$/', '/^john-.*$/'); + + $plugin = new Swift_Plugins_RedirectingPlugin($recipient, $patterns); + + $this->assertEqual($recipient, $plugin->getRecipient()); + $this->assertEqual($plugin->getWhitelist(), $patterns); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + + $this->assertEqual($message->getTo(), array('lars-to@internal.com' => 'Lars (To)', 'god@example.com' => null)); + $this->assertEqual($message->getCc(), array('lars-cc@internal.org' => 'Lars (Cc)')); + $this->assertEqual($message->getBcc(), array('john-bcc@example.org' => 'John (Bcc)')); + + $plugin->sendPerformed($evt); + + $this->assertEqual($message->getTo(), $to); + $this->assertEqual($message->getCc(), $cc); + $this->assertEqual($message->getBcc(), $bcc); + } + + // -- Creation Methods + + private function _createSendEvent(Swift_Mime_Message $message) + { + $evt = $this->_mock('Swift_Events_SendEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getMessage() -> returns($message) + -> ignoring($evt) + ); + + return $evt; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ReporterPluginTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ReporterPluginTest.php new file mode 100644 index 0000000..72279d7 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ReporterPluginTest.php @@ -0,0 +1,120 @@ +_createMessage(); + $evt = $this->_createSendEvent(); + $reporter = $this->_createReporter(); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array('foo@bar.tld' => 'Foo')) + -> allowing($evt)->getMessage() -> returns($message) + -> allowing($evt)->getFailedRecipients() -> returns(array()) + -> one($reporter)->notify($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS) + -> ignoring($message) + -> ignoring($evt) + ); + + $plugin = new Swift_Plugins_ReporterPlugin($reporter); + $plugin->sendPerformed($evt); + } + + public function testReportingFailedTo() + { + $message = $this->_createMessage(); + $evt = $this->_createSendEvent(); + $reporter = $this->_createReporter(); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array( + 'foo@bar.tld' => 'Foo', 'zip@button' => 'Zip' + )) + -> allowing($evt)->getMessage() -> returns($message) + -> allowing($evt)->getFailedRecipients() -> returns(array('zip@button')) + -> one($reporter)->notify($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS) + -> one($reporter)->notify($message, 'zip@button', Swift_Plugins_Reporter::RESULT_FAIL) + -> ignoring($message) + -> ignoring($evt) + ); + + $plugin = new Swift_Plugins_ReporterPlugin($reporter); + $plugin->sendPerformed($evt); + } + + public function testReportingFailedCc() + { + $message = $this->_createMessage(); + $evt = $this->_createSendEvent(); + $reporter = $this->_createReporter(); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array( + 'foo@bar.tld' => 'Foo' + )) + -> allowing($message)->getCc() -> returns(array( + 'zip@button' => 'Zip', 'test@test.com' => 'Test' + )) + -> allowing($evt)->getMessage() -> returns($message) + -> allowing($evt)->getFailedRecipients() -> returns(array('zip@button')) + -> one($reporter)->notify($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS) + -> one($reporter)->notify($message, 'zip@button', Swift_Plugins_Reporter::RESULT_FAIL) + -> one($reporter)->notify($message, 'test@test.com', Swift_Plugins_Reporter::RESULT_PASS) + -> ignoring($message) + -> ignoring($evt) + ); + + $plugin = new Swift_Plugins_ReporterPlugin($reporter); + $plugin->sendPerformed($evt); + } + + public function testReportingFailedBcc() + { + $message = $this->_createMessage(); + $evt = $this->_createSendEvent(); + $reporter = $this->_createReporter(); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array( + 'foo@bar.tld' => 'Foo' + )) + -> allowing($message)->getBcc() -> returns(array( + 'zip@button' => 'Zip', 'test@test.com' => 'Test' + )) + -> allowing($evt)->getMessage() -> returns($message) + -> allowing($evt)->getFailedRecipients() -> returns(array('zip@button')) + -> one($reporter)->notify($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS) + -> one($reporter)->notify($message, 'zip@button', Swift_Plugins_Reporter::RESULT_FAIL) + -> one($reporter)->notify($message, 'test@test.com', Swift_Plugins_Reporter::RESULT_PASS) + -> ignoring($message) + -> ignoring($evt) + ); + + $plugin = new Swift_Plugins_ReporterPlugin($reporter); + $plugin->sendPerformed($evt); + } + + // -- Creation Methods + + private function _createMessage() + { + return $this->_mock('Swift_Mime_Message'); + } + + private function _createSendEvent() + { + return $this->_mock('Swift_Events_SendEvent'); + } + + private function _createReporter() + { + return $this->_mock('Swift_Plugins_Reporter'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HitReporterTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HitReporterTest.php new file mode 100644 index 0000000..3d4e310 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HitReporterTest.php @@ -0,0 +1,69 @@ +_hitReporter = new Swift_Plugins_Reporters_HitReporter(); + $this->_message = $this->_mock('Swift_Mime_Message'); + } + + public function testReportingFail() + { + $this->_hitReporter->notify($this->_message, 'foo@bar.tld', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $this->assertEqual(array('foo@bar.tld'), + $this->_hitReporter->getFailedRecipients() + ); + } + + public function testMultipleReports() + { + $this->_hitReporter->notify($this->_message, 'foo@bar.tld', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $this->_hitReporter->notify($this->_message, 'zip@button', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $this->assertEqual(array('foo@bar.tld', 'zip@button'), + $this->_hitReporter->getFailedRecipients() + ); + } + + public function testReportingPassIsIgnored() + { + $this->_hitReporter->notify($this->_message, 'foo@bar.tld', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $this->_hitReporter->notify($this->_message, 'zip@button', + Swift_Plugins_Reporter::RESULT_PASS + ); + $this->assertEqual(array('foo@bar.tld'), + $this->_hitReporter->getFailedRecipients() + ); + } + + public function testBufferCanBeCleared() + { + $this->_hitReporter->notify($this->_message, 'foo@bar.tld', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $this->_hitReporter->notify($this->_message, 'zip@button', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $this->assertEqual(array('foo@bar.tld', 'zip@button'), + $this->_hitReporter->getFailedRecipients() + ); + $this->_hitReporter->clear(); + $this->assertEqual(array(), $this->_hitReporter->getFailedRecipients()); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HtmlReporterTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HtmlReporterTest.php new file mode 100644 index 0000000..76ab3e3 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HtmlReporterTest.php @@ -0,0 +1,59 @@ +_html = new Swift_Plugins_Reporters_HtmlReporter(); + $this->_message = $this->_mock('Swift_Mime_Message'); + } + + public function testReportingPass() + { + ob_start(); + $this->_html->notify($this->_message, 'foo@bar.tld', + Swift_Plugins_Reporter::RESULT_PASS + ); + $html = ob_get_clean(); + + $this->assertPattern('~ok|pass~i', $html, '%s: Reporter should indicate pass'); + $this->assertPattern('~foo@bar\.tld~', $html, '%s: Reporter should show address'); + } + + public function testReportingFail() + { + ob_start(); + $this->_html->notify($this->_message, 'zip@button', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $html = ob_get_clean(); + + $this->assertPattern('~fail~i', $html, '%s: Reporter should indicate fail'); + $this->assertPattern('~zip@button~', $html, '%s: Reporter should show address'); + } + + public function testMultipleReports() + { + ob_start(); + $this->_html->notify($this->_message, 'foo@bar.tld', + Swift_Plugins_Reporter::RESULT_PASS + ); + $this->_html->notify($this->_message, 'zip@button', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $html = ob_get_clean(); + + $this->assertPattern('~ok|pass~i', $html, '%s: Reporter should indicate pass'); + $this->assertPattern('~foo@bar\.tld~', $html, '%s: Reporter should show address'); + $this->assertPattern('~fail~i', $html, '%s: Reporter should indicate fail'); + $this->assertPattern('~zip@button~', $html, '%s: Reporter should show address'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ThrottlerPluginTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ThrottlerPluginTest.php new file mode 100644 index 0000000..1f7e985 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ThrottlerPluginTest.php @@ -0,0 +1,126 @@ +_createSleeper(); + $timer = $this->_createTimer(); + + //10MB/min + $plugin = new Swift_Plugins_ThrottlerPlugin( + 10000000, Swift_Plugins_ThrottlerPlugin::BYTES_PER_MINUTE, + $sleeper, $timer + ); + + $this->_checking(Expectations::create() + -> one($timer)->getTimestamp() -> returns(0) + -> one($timer)->getTimestamp() -> returns(1) //expected 0.6 + -> one($timer)->getTimestamp() -> returns(1) //expected 1.2 (sleep 1) + -> one($timer)->getTimestamp() -> returns(2) //expected 1.8 + -> one($timer)->getTimestamp() -> returns(2) //expected 2.4 (sleep 1) + -> ignoring($timer) + + -> exactly(2)->of($sleeper)->sleep(1) + ); + + //10,000,000 bytes per minute + //100,000 bytes per email + + // .: (10,000,000/100,000)/60 emails per second = 1.667 emais/sec + + $message = $this->_createMessageWithByteCount(100000); //100KB + + $evt = $this->_createSendEvent($message); + + for ($i = 0; $i < 5; ++$i) { + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + } + + public function testMessagesPerMinuteThrottling() + { + $sleeper = $this->_createSleeper(); + $timer = $this->_createTimer(); + + //60/min + $plugin = new Swift_Plugins_ThrottlerPlugin( + 60, Swift_Plugins_ThrottlerPlugin::MESSAGES_PER_MINUTE, + $sleeper, $timer + ); + + $this->_checking(Expectations::create() + -> one($timer)->getTimestamp() -> returns(0) + -> one($timer)->getTimestamp() -> returns(0) //expected 1 (sleep 1) + -> one($timer)->getTimestamp() -> returns(2) //expected 2 + -> one($timer)->getTimestamp() -> returns(2) //expected 3 (sleep 1) + -> one($timer)->getTimestamp() -> returns(4) //expected 4 + -> ignoring($timer) + + -> exactly(2)->of($sleeper)->sleep(1) + ); + + //60 messages per minute + //1 message per second + + $message = $this->_createMessageWithByteCount(10); + + $evt = $this->_createSendEvent($message); + + for ($i = 0; $i < 5; ++$i) { + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + } + + // -- Creation Methods + + private function _createSleeper() + { + return $this->_mock('Swift_Plugins_Sleeper'); + } + + private function _createTimer() + { + return $this->_mock('Swift_Plugins_Timer'); + } + + private function _createMessageWithByteCount($bytes) + { + $this->_bytes = $bytes; + $msg = $this->_mock('Swift_Mime_Message'); + $this->_checking(Expectations::create() + -> ignoring($msg)->toByteStream(any()) -> calls(array($this, '_write')) + ); + + return $msg; + } + + private function _createSendEvent($message) + { + $evt = $this->_mock('Swift_Events_SendEvent'); + $this->_checking(Expectations::create() + -> ignoring($evt)->getMessage() -> returns($message) + ); + + return $evt; + } + + private $_bytes = 0; + public function _write($invocation) + { + $args = $invocation->getArguments(); + $is = $args[0]; + for ($i = 0; $i < $this->_bytes; ++$i) { + $is->write('x'); + } + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/DKIMSignerTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/DKIMSignerTest.php new file mode 100644 index 0000000..5c11cac --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/DKIMSignerTest.php @@ -0,0 +1,212 @@ +_createHeaders(); + $messageContent = "Hello World"; + $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(dirname(__FILE__)))) . '/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector'); + /* @var $signer Swift_Signers_HeaderSigner */ + $altered = $signer->getAlteredHeaders(); + $signer->reset(); + // Headers + $signer->setHeaders($headers); + // Body + $signer->startBody(); + $signer->write($messageContent); + $signer->endBody(); + // Signing + $signer->addSignature($headers); + } + + // Default Signing + public function testSigningDefaults() + { + $headerSet = $this->_createHeaderSet(); + $messageContent = "Hello World"; + $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(dirname(__FILE__)))) . '/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector'); + $signer->setSignatureTimestamp('1299879181'); + $altered = $signer->getAlteredHeaders(); + $this->assertEqual(array('DKIM-Signature'), $altered); + $signer->reset(); + $signer->setHeaders($headerSet); + $this->assertFalse($headerSet->has('DKIM-Signature')); + $signer->startBody(); + $signer->write($messageContent); + $signer->endBody(); + $signer->addSignature($headerSet); + $this->assertTrue($headerSet->has('DKIM-Signature')); + $dkim = $headerSet->getAll('DKIM-Signature'); + $sig = reset($dkim); + $this->assertEqual($sig->getValue(), 'v=1; a=rsa-sha1; bh=wlbYcY9O9OPInGJ4D0E/rGsvMLE=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; t=1299879181; b=RMSNelzM2O5MAAnMjT3G3/VF36S3DGJXoPCXR001F1WDReu0prGphWjuzK/m6V1pwqQL8cCNg Hi74mTx2bvyAvmkjvQtJf1VMUOCc9WHGcm1Yec66I3ZWoNMGSWZ1EKAm2CtTzyG0IFw4ml9DI wSkyAFxlgicckDD6FibhqwX4w='); + } + + // SHA256 Signing + public function testSigning256() + { + $headerSet = $this->_createHeaderSet(); + $messageContent = "Hello World"; + $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(dirname(__FILE__)))) . '/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector'); + $signer->setHashAlgorithm('rsa-sha256'); + $signer->setSignatureTimestamp('1299879181'); + $altered = $signer->getAlteredHeaders(); + $this->assertEqual(array('DKIM-Signature'), $altered); + $signer->reset(); + $signer->setHeaders($headerSet); + $this->assertFalse($headerSet->has('DKIM-Signature')); + $signer->startBody(); + $signer->write($messageContent); + $signer->endBody(); + $signer->addSignature($headerSet); + $this->assertTrue($headerSet->has('DKIM-Signature')); + $dkim = $headerSet->getAll('DKIM-Signature'); + $sig = reset($dkim); + $this->assertEqual($sig->getValue(), 'v=1; a=rsa-sha256; bh=f+W+hu8dIhf2VAni89o8lF6WKTXi7nViA4RrMdpD5/U=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; t=1299879181; b=jqPmieHzF5vR9F4mXCAkowuphpO4iJ8IAVuioh1BFZ3VITXZj5jlOFxULJMBiiApm2keJirnh u4mzogj444QkpT3lJg8/TBGAYQPdcvkG3KC0jdyN6QpSgpITBJG2BwWa+keXsv2bkQgLRAzNx qRhP45vpHCKun0Tg9LrwW/KCg='); + } + + // Relaxed/Relaxed Hash Signing + public function testSigningRelaxedRelaxed256() + { + $headerSet = $this->_createHeaderSet(); + $messageContent = "Hello World"; + $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(dirname(__FILE__)))) . '/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector'); + $signer->setHashAlgorithm('rsa-sha256'); + $signer->setSignatureTimestamp('1299879181'); + $signer->setBodyCanon('relaxed'); + $signer->setHeaderCanon('relaxed'); + $altered = $signer->getAlteredHeaders(); + $this->assertEqual(array('DKIM-Signature'), $altered); + $signer->reset(); + $signer->setHeaders($headerSet); + $this->assertFalse($headerSet->has('DKIM-Signature')); + $signer->startBody(); + $signer->write($messageContent); + $signer->endBody(); + $signer->addSignature($headerSet); + $this->assertTrue($headerSet->has('DKIM-Signature')); + $dkim = $headerSet->getAll('DKIM-Signature'); + $sig = reset($dkim); + $this->assertEqual($sig->getValue(), 'v=1; a=rsa-sha256; bh=f+W+hu8dIhf2VAni89o8lF6WKTXi7nViA4RrMdpD5/U=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; c=relaxed/relaxed; t=1299879181; b=gzOI+PX6HpZKQFzwwmxzcVJsyirdLXOS+4pgfCpVHQIdqYusKLrhlLeFBTNoz75HrhNvGH6T0 Rt3w5aTqkrWfUuAEYt0Ns14GowLM7JojaFN+pZ4eYnRB3CBBgW6fee4NEMD5WPca3uS09tr1E 10RYh9ILlRtl+84sovhx5id3Y='); + } + + + // Relaxed/Simple Hash Signing + public function testSigningRelaxedSimple256() + { + $headerSet = $this->_createHeaderSet(); + $messageContent = "Hello World"; + $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(dirname(__FILE__)))) . '/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector'); + $signer->setHashAlgorithm('rsa-sha256'); + $signer->setSignatureTimestamp('1299879181'); + $signer->setHeaderCanon('relaxed'); + $altered = $signer->getAlteredHeaders(); + $this->assertEqual(array('DKIM-Signature'), $altered); + $signer->reset(); + $signer->setHeaders($headerSet); + $this->assertFalse($headerSet->has('DKIM-Signature')); + $signer->startBody(); + $signer->write($messageContent); + $signer->endBody(); + $signer->addSignature($headerSet); + $this->assertTrue($headerSet->has('DKIM-Signature')); + $dkim = $headerSet->getAll('DKIM-Signature'); + $sig = reset($dkim); + $this->assertEqual($sig->getValue(), 'v=1; a=rsa-sha256; bh=f+W+hu8dIhf2VAni89o8lF6WKTXi7nViA4RrMdpD5/U=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; c=relaxed; t=1299879181; b=dLPJNec5v81oelyzGOY0qPqTlGnQeNfUNBOrV/JKbStr3NqWGI9jH4JAe2YvO2V32lfPNoby1 4MMzZ6EPkaZkZDDSPa+53YbCPQAlqiD9QZZIUe2UNM33HN8yAMgiWEF5aP7MbQnxeVZMfVLEl 9S8qOImu+K5JZqhQQTL0dgLwA='); + } + + // Simple/Relaxed Hash Signing + public function testSigningSimpleRelaxed256() + { + $headerSet = $this->_createHeaderSet(); + $messageContent = "Hello World"; + $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(dirname(__FILE__)))) . '/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector'); + $signer->setHashAlgorithm('rsa-sha256'); + $signer->setSignatureTimestamp('1299879181'); + $signer->setBodyCanon('relaxed'); + $altered = $signer->getAlteredHeaders(); + $this->assertEqual(array('DKIM-Signature'), $altered); + $signer->reset(); + $signer->setHeaders($headerSet); + $this->assertFalse($headerSet->has('DKIM-Signature')); + $signer->startBody(); + $signer->write($messageContent); + $signer->endBody(); + $signer->addSignature($headerSet); + $this->assertTrue($headerSet->has('DKIM-Signature')); + $dkim = $headerSet->getAll('DKIM-Signature'); + $sig = reset($dkim); + $this->assertEqual($sig->getValue(), 'v=1; a=rsa-sha256; bh=f+W+hu8dIhf2VAni89o8lF6WKTXi7nViA4RrMdpD5/U=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; c=simple/relaxed; t=1299879181; b=M5eomH/zamyzix9kOes+6YLzQZxuJdBP4x3nP9zF2N26eMLG2/cBKbnNyqiOTDhJdYfWPbLIa 1CWnjST0j5p4CpeOkGYuiE+M4TWEZwhRmRWootlPO3Ii6XpbBJKFk1o9zviS7OmXblUUE4aqb yRSIMDhtLdCK5GlaCneFLN7RQ='); + } + + // -- Creation Methods + private function _createHeaderSet() + { + $cache = new Swift_KeyCache_ArrayKeyCache(new Swift_KeyCache_SimpleKeyCacheInputStream()); + $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + $contentEncoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + + $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder(new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')); + $paramEncoder = new Swift_Encoder_Rfc2231Encoder(new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')); + $grammar = new Swift_Mime_Grammar(); + $headers = new Swift_Mime_SimpleHeaderSet(new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $grammar)); + return $headers; + } + + /** + * @return Swift_Mime_Headers + */ + private function _createHeaders() + { + $x = 0; + $cache = new Swift_KeyCache_ArrayKeyCache(new Swift_KeyCache_SimpleKeyCacheInputStream()); + $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + $contentEncoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + + $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder(new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')); + $paramEncoder = new Swift_Encoder_Rfc2231Encoder(new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')); + $grammar = new Swift_Mime_Grammar(); + $headerFactory = new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $grammar); + $headers = $this->_mock('Swift_Mime_HeaderSet'); + $this->_checking(Expectations::create() + ->ignoring($headers) + ->listAll() + ->returns(array('From', 'To', 'Date', 'Subject')) + ->ignoring($headers) + ->has('From') + ->returns(True) + ->ignoring($headers) + ->getAll('From') + ->returns(array($headerFactory->createMailboxHeader('From', 'test@test.test'))) + ->ignoring($headers) + ->has('To') + ->returns(True) + ->ignoring($headers) + ->getAll('To') + ->returns(array($headerFactory->createMailboxHeader('To', 'test@test.test'))) + ->ignoring($headers) + ->has('Date') + ->returns(True) + ->ignoring($headers) + ->getAll('Date') + ->returns(array($headerFactory->createTextHeader('Date', 'Fri, 11 Mar 2011 20:56:12 +0000 (GMT)'))) + ->ignoring($headers) + ->has('Subject') + ->returns(True) + ->ignoring($headers) + ->getAll('Subject') + ->returns(array($headerFactory->createTextHeader('Subject', 'Foo Bar Text Message'))) + ->ignoring($headers) + ->addTextHeader('DKIM-Signature', any()) + ->returns(true) + ->ignoring($headers) + ->getAll('DKIM-Signature') + ->returns(array($headerFactory->createTextHeader('DKIM-Signature', 'Foo Bar Text Message')))); + return $headers; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/SMimeSignerTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/SMimeSignerTest.php new file mode 100644 index 0000000..8e9cfc8 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/SMimeSignerTest.php @@ -0,0 +1,515 @@ +replacementFactory = Swift_DependencyContainer::getInstance() + ->lookup('transport.replacementfactory'); + + $this->samplesDir = str_replace('\\', '/', realpath(dirname(__FILE__) . '/../../../_samples/')) . '/'; + } + + public function testUnSingedMessage() + { + $message = Swift_SignedMessage::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself'); + + $this->assertEqual('Here is the message itself', $message->getBody()); + } + + public function testSingedMessage() + { + $message = Swift_SignedMessage::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself'); + + $signer = new Swift_Signers_SMimeSigner(); + $signer->setSignCertificate($this->samplesDir . 'smime/sign.crt', $this->samplesDir . 'smime/sign.key'); + $message->attachSigner($signer); + + $messageStream = $this->newFilteredStream(); + $message->toByteStream($messageStream); + $messageStream->commit(); + + $entityString = $messageStream->getContent(); + $headers = self::getHeadersOfMessage($entityString); + + if (!($boundary = $this->getBoundary($headers['content-type']))) { + return false; + } + + $expectedBody = <<assertValidVerify($expectedBody, $messageStream); + unset($messageStream); + } + + public function testSingedMessageBinary() + { + $message = Swift_SignedMessage::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself'); + + $signer = new Swift_Signers_SMimeSigner(); + $signer->setSignCertificate($this->samplesDir . 'smime/sign.crt', $this->samplesDir . 'smime/sign.key', PKCS7_BINARY); + $message->attachSigner($signer); + + $messageStream = $this->newFilteredStream(); + $message->toByteStream($messageStream); + $messageStream->commit(); + + $entityString = $messageStream->getContent(); + $headers = self::getHeadersOfMessage($entityString); + + if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=signed\-data;#', $headers['content-type'])) { + $this->fail('Content-type does not match.'); + + return false; + } + + $this->assertEqual($headers['content-transfer-encoding'], 'base64'); + $this->assertEqual($headers['content-disposition'], 'attachment; filename="smime.p7m"'); + + $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})'; + + $messageStreamClean = $this->newFilteredStream(); + + $this->assertValidVerify($expectedBody, $messageStream); + unset($messageStreamClean, $messageStream); + } + + public function testSingedMessageWithAttachments() + { + $message = Swift_SignedMessage::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself'); + + $message->attach(Swift_Attachment::fromPath($this->samplesDir . '/files/textfile.zip')); + + $signer = new Swift_Signers_SMimeSigner(); + $signer->setSignCertificate($this->samplesDir . 'smime/sign.crt', $this->samplesDir . 'smime/sign.key'); + $message->attachSigner($signer); + + $messageStream = $this->newFilteredStream(); + $message->toByteStream($messageStream); + $messageStream->commit(); + + $entityString = $messageStream->getContent(); + $headers = self::getHeadersOfMessage($entityString); + + if (!($boundary = $this->getBoundary($headers['content-type']))) { + return false; + } + + $expectedBody = <<assertValidVerify($expectedBody, $messageStream); + unset($messageStream); + } + + public function testEncryptedMessage() + { + $message = Swift_SignedMessage::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself'); + + $originalMessage = $this->cleanMessage($message->toString()); + + $signer = new Swift_Signers_SMimeSigner(); + $signer->setEncryptCertificate($this->samplesDir . 'smime/encrypt.crt'); + $message->attachSigner($signer); + + $messageStream = new Swift_ByteStream_TemporaryFileByteStream(); + $message->toByteStream($messageStream); + $messageStream->commit(); + + $entityString = $messageStream->getContent(); + $headers = self::getHeadersOfMessage($entityString); + + if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=enveloped\-data;#', $headers['content-type'])) { + $this->fail('Content-type does not match.'); + + return false; + } + + $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})'; + + $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream(); + + if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://' . $this->samplesDir . 'smime/encrypt.crt', array('file://' . $this->samplesDir . 'smime/encrypt.key', 'swift'))) { + $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string())); + } + + $this->assertEqual($originalMessage, $decryptedMessageStream->getContent()); + unset($decryptedMessageStream, $messageStream); + } + + public function testEncryptedMessageWithMultipleCerts() + { + $message = Swift_SignedMessage::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself'); + + $originalMessage = $this->cleanMessage($message->toString()); + + $signer = new Swift_Signers_SMimeSigner(); + $signer->setEncryptCertificate(array($this->samplesDir . 'smime/encrypt.crt', $this->samplesDir . 'smime/encrypt2.crt')); + $message->attachSigner($signer); + + $messageStream = new Swift_ByteStream_TemporaryFileByteStream(); + $message->toByteStream($messageStream); + $messageStream->commit(); + + $entityString = $messageStream->getContent(); + $headers = self::getHeadersOfMessage($entityString); + + if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=enveloped\-data;#', $headers['content-type'])) { + $this->fail('Content-type does not match.'); + + return false; + } + + $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})'; + + $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream(); + + if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://' . $this->samplesDir . 'smime/encrypt.crt', array('file://' . $this->samplesDir . 'smime/encrypt.key', 'swift'))) { + $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string())); + } + + $this->assertEqual($originalMessage, $decryptedMessageStream->getContent()); + unset($decryptedMessageStream); + + $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream(); + + if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://' . $this->samplesDir . 'smime/encrypt2.crt', array('file://' . $this->samplesDir . 'smime/encrypt2.key', 'swift'))) { + $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string())); + } + + $this->assertEqual($originalMessage, $decryptedMessageStream->getContent()); + unset($decryptedMessageStream, $messageStream); + } + + public function testSignThenEncryptedMessage() + { + $message = Swift_SignedMessage::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself'); + + $signer = new Swift_Signers_SMimeSigner(); + $signer->setSignCertificate($this->samplesDir . 'smime/sign.crt', $this->samplesDir . 'smime/sign.key'); + $signer->setEncryptCertificate($this->samplesDir . 'smime/encrypt.crt'); + $message->attachSigner($signer); + + $messageStream = new Swift_ByteStream_TemporaryFileByteStream(); + $message->toByteStream($messageStream); + $messageStream->commit(); + + $entityString = $messageStream->getContent(); + $headers = self::getHeadersOfMessage($entityString); + + if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=enveloped\-data;#', $headers['content-type'])) { + $this->fail('Content-type does not match.'); + + return false; + } + + $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})'; + + $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream(); + + if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://' . $this->samplesDir . 'smime/encrypt.crt', array('file://' . $this->samplesDir . 'smime/encrypt.key', 'swift'))) { + $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string())); + } + + $entityString = $decryptedMessageStream->getContent(); + $headers = self::getHeadersOfMessage($entityString); + + if (!($boundary = $this->getBoundary($headers['content-type']))) { + return false; + } + + $expectedBody = <<assertValidVerify($expectedBody, $decryptedMessageStream)) { + return false; + } + + unset($decryptedMessageStream, $messageStream); + } + + public function testEncryptThenSignMessage() + { + $message = Swift_SignedMessage::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself'); + + $originalMessage = $this->cleanMessage($message->toString()); + + $signer = Swift_Signers_SMimeSigner::newInstance(); + $signer->setSignCertificate($this->samplesDir . 'smime/sign.crt', $this->samplesDir . 'smime/sign.key'); + $signer->setEncryptCertificate($this->samplesDir . 'smime/encrypt.crt'); + $signer->setSignThenEncrypt(false); + $message->attachSigner($signer); + + $messageStream = $this->newFilteredStream(); + $message->toByteStream($messageStream); + $messageStream->commit(); + + $entityString = $messageStream->getContent(); + $headers = self::getHeadersOfMessage($entityString); + + if (!($boundary = $this->getBoundary($headers['content-type']))) { + return false; + } + + $expectedBody = <<MIME-Version: 1\.0 +Content-Disposition: attachment; filename="smime\.p7m" +Content-Type: application/(x\-)?pkcs7-mime; smime-type=enveloped-data; name="smime\.p7m" +Content-Transfer-Encoding: base64 + +(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2}) + + +)--$boundary +Content-Type: application/(x\-)?pkcs7-signature; name="smime\.p7s" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="smime\.p7s" + +(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2}) + +--$boundary-- +OEL; + + if (!$this->assertValidVerify($expectedBody, $messageStream)) { + return false; + } + + $expectedBody = str_replace("\n", "\r\n", $expectedBody); + if (!preg_match('%' . $expectedBody . '*%m', $entityString, $entities)) { + $this->fail('Failed regex match.'); + + return false; + } + + $messageStreamClean = new Swift_ByteStream_TemporaryFileByteStream(); + $messageStreamClean->write($entities['encrypted_message']); + + $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream(); + + if (!openssl_pkcs7_decrypt($messageStreamClean->getPath(), $decryptedMessageStream->getPath(), 'file://' . $this->samplesDir . 'smime/encrypt.crt', array('file://' . $this->samplesDir . 'smime/encrypt.key', 'swift'))) { + $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string())); + } + + $this->assertEqual($originalMessage, $decryptedMessageStream->getContent()); + unset($messageStreamClean, $messageStream, $decryptedMessageStream); + } + + protected function assertValidVerify($expected, Swift_ByteStream_TemporaryFileByteStream $messageStream) + { + $actual = $messageStream->getContent(); + + // File is UNIX encoded so convert them to correct line ending + $expected = str_replace("\n", "\r\n", $expected); + + $actual = trim(self::getBodyOfMessage($actual)); + if (!$this->assertPattern('%^' . $expected . '$\s*%m', $actual)) { + return false; + } + + $opensslOutput = new Swift_ByteStream_TemporaryFileByteStream(); + $verify = openssl_pkcs7_verify($messageStream->getPath(), null, $opensslOutput->getPath(), array($this->samplesDir . 'smime/ca.crt')); + + if (false === $verify) { + $this->fail('Verification of the message failed.'); + + return false; + } elseif (-1 === $verify) { + $this->fail(sprintf('Verification of the message failed. Internal error "%s".', openssl_error_string())); + + return false; + } + + return true; + } + + protected function getBoundary($contentType) + { + if (!preg_match('/boundary=("[^"]+"|(?:[^\s]+|$))/is', $contentType, $contentTypeData)) { + $this->fail('Failed to find Boundary parameter'); + + return false; + } + + return trim($contentTypeData[1], '"'); + } + + protected function newFilteredStream() + { + $messageStream = new Swift_ByteStream_TemporaryFileByteStream(); + $messageStream->addFilter($this->replacementFactory->createFilter("\r\n", "\n"), 'CRLF to LF'); + $messageStream->addFilter($this->replacementFactory->createFilter("\n", "\r\n"), 'LF to CRLF'); + + return $messageStream; + } + + protected static function getBodyOfMessage($message) + { + return substr($message, strpos($message, "\r\n\r\n")); + } + + /** + * Strips of the sender headers and Mime-Version. + * + * @param Swift_ByteStream_TemporaryFileByteStream $messageStream + * @param Swift_ByteStream_TemporaryFileByteStream $inputStream + */ + protected function cleanMessage($content) + { + $newContent = ''; + + $headers = self::getHeadersOfMessage($content); + foreach ($headers as $headerName => $value) { + if (!in_array($headerName, array('content-type', 'content-transfer-encoding', 'content-disposition'))) { + continue; + } + + $headerName = explode('-', $headerName); + $headerName = array_map('ucfirst', $headerName); + $headerName = implode('-', $headerName); + + if (strlen($value) > 62) { + $value = wordwrap($value, 62, "\n "); + } + + $newContent .= "$headerName: $value\r\n"; + } + + return $newContent . "\r\n" . ltrim(self::getBodyOfMessage($content)); + } + + /** + * Returns the headers of the message. + * + * Header-names are lowercase. + * + * @param string $message + * + * @return array + */ + protected static function getHeadersOfMessage($message) + { + $headersPosEnd = strpos($message, "\r\n\r\n"); + $headerData = substr($message, 0, $headersPosEnd); + $headerLines = explode("\r\n", $headerData); + + if (empty($headerLines)) { + return array(); + } + + $headers = array(); + + foreach ($headerLines as $headerLine) { + if (ctype_space($headerLines[0]) || false === strpos($headerLine, ':')) { + $headers[$currentHeaderName] .= ' ' . trim($headerLine); + continue; + } + + $header = explode(':', $headerLine, 2); + $currentHeaderName = strtolower($header[0]); + $headers[$currentHeaderName] = trim($header[1]); + } + + return $headers; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/ByteArrayReplacementFilterTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/ByteArrayReplacementFilterTest.php new file mode 100644 index 0000000..5a6977f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/ByteArrayReplacementFilterTest.php @@ -0,0 +1,135 @@ +_createFilter(array(0x61, 0x62), array(0x63, 0x64)); + $this->assertEqual( + array(0x59, 0x60, 0x63, 0x64, 0x65), + $filter->filter(array(0x59, 0x60, 0x61, 0x62, 0x65)) + ); + } + + public function testShouldBufferReturnsTrueIfPartialMatchAtEndOfBuffer() + { + $filter = $this->_createFilter(array(0x61, 0x62), array(0x63, 0x64)); + $this->assertTrue($filter->shouldBuffer(array(0x59, 0x60, 0x61)), + '%s: Filter should buffer since 0x61 0x62 is the needle and the ending ' . + '0x61 could be from 0x61 0x62' + ); + } + + public function testFilterCanMakeMultipleReplacements() + { + $filter = $this->_createFilter(array(array(0x61), array(0x62)), array(0x63)); + $this->assertEqual( + array(0x60, 0x63, 0x60, 0x63, 0x60), + $filter->filter(array(0x60, 0x61, 0x60, 0x62, 0x60)) + ); + } + + public function testMultipleReplacementsCanBeDifferent() + { + $filter = $this->_createFilter(array(array(0x61), array(0x62)), array(array(0x63), array(0x64))); + $this->assertEqual( + array(0x60, 0x63, 0x60, 0x64, 0x60), + $filter->filter(array(0x60, 0x61, 0x60, 0x62, 0x60)) + ); + } + + public function testShouldBufferReturnsFalseIfPartialMatchNotAtEndOfString() + { + $filter = $this->_createFilter(array(0x0D, 0x0A), array(0x0A)); + $this->assertFalse($filter->shouldBuffer(array(0x61, 0x62, 0x0D, 0x0A, 0x63)), + '%s: Filter should not buffer since x0Dx0A is the needle and is not at EOF' + ); + } + + public function testShouldBufferReturnsTrueIfAnyOfMultipleMatchesAtEndOfString() + { + $filter = $this->_createFilter(array(array(0x61, 0x62), array(0x63)), array(0x64)); + $this->assertTrue($filter->shouldBuffer(array(0x59, 0x60, 0x61)), + '%s: Filter should buffer since 0x61 0x62 is a needle and the ending ' . + '0x61 could be from 0x61 0x62' + ); + } + + public function testConvertingAllLineEndingsToCRLFWhenInputIsLF() + { + $filter = $this->_createFilter( + array(array(0x0D, 0x0A), array(0x0D), array(0x0A)), + array(array(0x0A), array(0x0A), array(0x0D, 0x0A)) + ); + + $this->assertEqual( + array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63), + $filter->filter(array(0x60, 0x0A, 0x61, 0x0A, 0x62, 0x0A, 0x63)) + ); + } + + public function testConvertingAllLineEndingsToCRLFWhenInputIsCR() + { + $filter = $this->_createFilter( + array(array(0x0D, 0x0A), array(0x0D), array(0x0A)), + array(array(0x0A), array(0x0A), array(0x0D, 0x0A)) + ); + + $this->assertEqual( + array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63), + $filter->filter(array(0x60, 0x0D, 0x61, 0x0D, 0x62, 0x0D, 0x63)) + ); + } + + public function testConvertingAllLineEndingsToCRLFWhenInputIsCRLF() + { + $filter = $this->_createFilter( + array(array(0x0D, 0x0A), array(0x0D), array(0x0A)), + array(array(0x0A), array(0x0A), array(0x0D, 0x0A)) + ); + + $this->assertEqual( + array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63), + $filter->filter(array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63)) + ); + } + + public function testConvertingAllLineEndingsToCRLFWhenInputIsLFCR() + { + $filter = $this->_createFilter( + array(array(0x0D, 0x0A), array(0x0D), array(0x0A)), + array(array(0x0A), array(0x0A), array(0x0D, 0x0A)) + ); + + $this->assertEqual( + array(0x60, 0x0D, 0x0A, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x0D, 0x0A, 0x63), + $filter->filter(array(0x60, 0x0A, 0x0D, 0x61, 0x0A, 0x0D, 0x62, 0x0A, 0x0D, 0x63)) + ); + } + + public function testConvertingAllLineEndingsToCRLFWhenInputContainsLFLF() + { + //Lighthouse Bug #23 + + $filter = $this->_createFilter( + array(array(0x0D, 0x0A), array(0x0D), array(0x0A)), + array(array(0x0A), array(0x0A), array(0x0D, 0x0A)) + ); + + $this->assertEqual( + array(0x60, 0x0D, 0x0A, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x0D, 0x0A, 0x63), + $filter->filter(array(0x60, 0x0A, 0x0A, 0x61, 0x0A, 0x0A, 0x62, 0x0A, 0x0A, 0x63)) + ); + } + + // -- Creation methods + + private function _createFilter($search, $replace) + { + return new Swift_StreamFilters_ByteArrayReplacementFilter($search, $replace); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterFactoryTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterFactoryTest.php new file mode 100644 index 0000000..8190988 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterFactoryTest.php @@ -0,0 +1,41 @@ +_createFactory(); + $this->assertIsA($factory->createFilter('a', 'b'), + 'Swift_StreamFilters_StringReplacementFilter' + ); + } + + public function testSameInstancesAreCached() + { + $factory = $this->_createFactory(); + $filter1 = $factory->createFilter('a', 'b'); + $filter2 = $factory->createFilter('a', 'b'); + $this->assertSame($filter1, $filter2, '%s: Instances should be cached'); + } + + public function testDifferingInstancesAreNotCached() + { + $factory = $this->_createFactory(); + $filter1 = $factory->createFilter('a', 'b'); + $filter2 = $factory->createFilter('a', 'c'); + $this->assertNotEqual($filter1, $filter2, + '%s: Differing instances should not be cached' + ); + } + + // -- Creation methods + + private function _createFactory() + { + return new Swift_StreamFilters_StringReplacementFilterFactory(); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterTest.php new file mode 100644 index 0000000..712f1dc --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterTest.php @@ -0,0 +1,59 @@ +_createFilter('foo', 'bar'); + $this->assertEqual('XbarYbarZ', $filter->filter('XfooYfooZ')); + } + + public function testShouldBufferReturnsTrueIfPartialMatchAtEndOfBuffer() + { + $filter = $this->_createFilter('foo', 'bar'); + $this->assertTrue($filter->shouldBuffer('XfooYf'), + '%s: Filter should buffer since "foo" is the needle and the ending ' . + '"f" could be from "foo"' + ); + } + + public function testFilterCanMakeMultipleReplacements() + { + $filter = $this->_createFilter(array('a', 'b'), 'foo'); + $this->assertEqual('XfooYfooZ', $filter->filter('XaYbZ')); + } + + public function testMultipleReplacementsCanBeDifferent() + { + $filter = $this->_createFilter(array('a', 'b'), array('foo', 'zip')); + $this->assertEqual('XfooYzipZ', $filter->filter('XaYbZ')); + } + + public function testShouldBufferReturnsFalseIfPartialMatchNotAtEndOfString() + { + $filter = $this->_createFilter("\r\n", "\n"); + $this->assertFalse($filter->shouldBuffer("foo\r\nbar"), + '%s: Filter should not buffer since x0Dx0A is the needle and is not at EOF' + ); + } + + public function testShouldBufferReturnsTrueIfAnyOfMultipleMatchesAtEndOfString() + { + $filter = $this->_createFilter(array('foo', 'zip'), 'bar'); + $this->assertTrue($filter->shouldBuffer('XfooYzi'), + '%s: Filter should buffer since "zip" is a needle and the ending ' . + '"zi" could be from "zip"' + ); + } + + // -- Creation methods + + private function _createFilter($search, $replace) + { + return new Swift_StreamFilters_StringReplacementFilter($search, $replace); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php new file mode 100644 index 0000000..e1749f2 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php @@ -0,0 +1,378 @@ +_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $listener = $this->_mock('Swift_Events_EventListener'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> one($dispatcher)->bindEventListener($listener) + ); + $smtp->registerPlugin($listener); + } + + public function testSendingDispatchesBeforeSendEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $message = $this->_createMessage(); + $smtp = $this->_getTransport($buf, $dispatcher); + $evt = $this->_mock('Swift_Events_SendEvent'); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('chris@swiftmailer.org'=>null)) + -> allowing($message)->getTo() -> returns(array('mark@swiftmailer.org'=>'Mark')) + -> ignoring($message) + -> one($dispatcher)->createSendEvent(optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'beforeSendPerformed') + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(1, $smtp->send($message)); + } + + public function testSendingDispatchesSendEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $message = $this->_createMessage(); + $smtp = $this->_getTransport($buf, $dispatcher); + $evt = $this->_mock('Swift_Events_SendEvent'); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('chris@swiftmailer.org'=>null)) + -> allowing($message)->getTo() -> returns(array('mark@swiftmailer.org'=>'Mark')) + -> ignoring($message) + -> one($dispatcher)->createSendEvent(optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'sendPerformed') + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(1, $smtp->send($message)); + } + + public function testSendEventCapturesFailures() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_SendEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('chris@swiftmailer.org'=>null)) + -> allowing($message)->getTo() -> returns(array('mark@swiftmailer.org'=>'Mark')) + -> ignoring($message) + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns("500 Not now\r\n") + -> allowing($dispatcher)->createSendEvent($smtp, optional()) -> returns($evt) + -> one($evt)->setFailedRecipients(array('mark@swiftmailer.org')) + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(0, $smtp->send($message)); + } + + public function testSendEventHasResultFailedIfAllFailures() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_SendEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('chris@swiftmailer.org'=>null)) + -> allowing($message)->getTo() -> returns(array('mark@swiftmailer.org'=>'Mark')) + -> ignoring($message) + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns("500 Not now\r\n") + -> allowing($dispatcher)->createSendEvent($smtp, optional()) -> returns($evt) + -> one($evt)->setResult(Swift_Events_SendEvent::RESULT_FAILED) + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(0, $smtp->send($message)); + } + + public function testSendEventHasResultTentativeIfSomeFailures() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_SendEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('chris@swiftmailer.org'=>null)) + -> allowing($message)->getTo() -> returns(array( + 'mark@swiftmailer.org'=>'Mark', 'chris@site.tld'=>'Chris' + )) + -> ignoring($message) + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns("500 Not now\r\n") + -> allowing($dispatcher)->createSendEvent($smtp, optional()) -> returns($evt) + -> one($evt)->setResult(Swift_Events_SendEvent::RESULT_TENTATIVE) + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(1, $smtp->send($message)); + } + + public function testSendEventHasResultSuccessIfNoFailures() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_SendEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('chris@swiftmailer.org'=>null)) + -> allowing($message)->getTo() -> returns(array( + 'mark@swiftmailer.org'=>'Mark', 'chris@site.tld'=>'Chris' + )) + -> ignoring($message) + -> allowing($dispatcher)->createSendEvent($smtp, optional()) -> returns($evt) + -> one($evt)->setResult(Swift_Events_SendEvent::RESULT_SUCCESS) + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(2, $smtp->send($message)); + } + + public function testCancellingEventBubbleBeforeSendStopsEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_SendEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('chris@swiftmailer.org'=>null)) + -> allowing($message)->getTo() -> returns(array('mark@swiftmailer.org'=>'Mark')) + -> ignoring($message) + -> allowing($dispatcher)->createSendEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'beforeSendPerformed') + -> ignoring($dispatcher) + -> atLeast(1)->of($evt)->bubbleCancelled() -> returns(true) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(0, $smtp->send($message)); + } + + public function testStartingTransportDispatchesTransportChangeEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_TransportChangeEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> allowing($dispatcher)->createTransportChangeEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'transportStarted') + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + } + + public function testStartingTransportDispatchesBeforeTransportChangeEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_TransportChangeEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> allowing($dispatcher)->createTransportChangeEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'beforeTransportStarted') + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + } + + public function testCancellingBubbleBeforeTransportStartStopsEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_TransportChangeEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> allowing($dispatcher)->createTransportChangeEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'beforeTransportStarted') + -> allowing($evt)->bubbleCancelled() -> returns(true) + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + + $this->assertFalse($smtp->isStarted(), + '%s: Transport should not be started since event bubble was cancelled' + ); + } + + public function testStoppingTransportDispatchesTransportChangeEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_TransportChangeEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> allowing($dispatcher)->createTransportChangeEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'transportStopped') + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->stop(); + } + + public function testStoppingTransportDispatchesBeforeTransportChangeEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_TransportChangeEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> allowing($dispatcher)->createTransportChangeEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'beforeTransportStopped') + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->stop(); + } + + public function testCancellingBubbleBeforeTransportStoppedStopsEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_TransportChangeEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $seq = $this->_sequence('stopping transport'); + $this->_checking(Expectations::create() + -> allowing($dispatcher)->createTransportChangeEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'beforeTransportStopped') -> inSequence($seq) + -> allowing($evt)->bubbleCancelled() -> inSequence($seq) -> returns(true) + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->stop(); + + $this->assertTrue($smtp->isStarted(), + '%s: Transport should not be stopped since event bubble was cancelled' + ); + } + + public function testResponseEventsAreGenerated() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_ResponseEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> allowing($dispatcher)->createResponseEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'responseReceived') + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + } + + public function testCommandEventsAreGenerated() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_CommandEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> allowing($dispatcher)->createCommandEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'commandSent') + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + } + + public function testExceptionsCauseExceptionEvents() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_TransportExceptionEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> atLeast(1)->of($buf)->readLine(any()) -> returns("503 I'm sleepy, go away!\r\n") + -> allowing($dispatcher)->createTransportExceptionEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'exceptionThrown') + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + try { + $smtp->start(); + $this->fail('TransportException should be thrown on invalid response'); + } catch (Swift_TransportException $e) { + } + } + + public function testExceptionBubblesCanBeCancelled() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->_mock('Swift_Events_TransportExceptionEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + $this->_checking(Expectations::create() + -> atLeast(1)->of($buf)->readLine(any()) -> returns("503 I'm sleepy, go away!\r\n") + -> allowing($dispatcher)->createTransportExceptionEvent($smtp, optional()) -> returns($evt) + -> one($dispatcher)->dispatchEvent($evt, 'exceptionThrown') + -> atLeast(1)->of($evt)->bubbleCancelled() -> returns(true) + -> ignoring($dispatcher) + -> ignoring($evt) + ); + $this->_finishBuffer($buf); + $smtp->start(); + } + + // -- Creation Methods + + protected function _createEventDispatcher($stub = true) + { + return $stub + ? $this->_stub('Swift_Events_EventDispatcher') + : $this->_mock('Swift_Events_EventDispatcher') + ; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpTest.php new file mode 100644 index 0000000..298969e --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpTest.php @@ -0,0 +1,924 @@ +_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 some.server.tld bleh\r\n") + ); + $this->_finishBuffer($buf); + try { + $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started'); + $smtp->start(); + $this->assertTrue($smtp->isStarted(), '%s: start() should have started connection'); + } catch (Exception $e) { + $this->fail('220 is a valid SMTP greeting and should be accepted'); + } + } + + public function testBadGreetingCausesException() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("554 I'm busy\r\n") + ); + $this->_finishBuffer($buf); + try { + $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started'); + $smtp->start(); + $this->fail('554 greeting indicates an error and should cause an exception'); + } catch (Exception $e) { + $this->assertFalse($smtp->isStarted(), '%s: start() should have failed'); + } + } + + public function testStartSendsHeloToInitiate() + { + /* -- RFC 2821, 3.2. + + 3.2 Client Initiation + + Once the server has sent the welcoming message and the client has + received it, the client normally sends the EHLO command to the + server, indicating the client's identity. In addition to opening the + session, use of EHLO indicates that the client is able to process + service extensions and requests that the server provide a list of the + extensions it supports. Older SMTP systems which are unable to + support service extensions and contemporary clients which do not + require service extensions in the mail session being initiated, MAY + use HELO instead of EHLO. Servers MUST NOT return the extended + EHLO-style response to a HELO command. For a particular connection + attempt, if the server returns a "command not recognized" response to + EHLO, the client SHOULD be able to fall back and send HELO. + + In the EHLO command the host sending the command identifies itself; + the command may be interpreted as saying "Hello, I am " (and, + in the case of EHLO, "and I support service extension requests"). + + -- RFC 2281, 4.1.1.1. + + ehlo = "EHLO" SP Domain CRLF + helo = "HELO" SP Domain CRLF + + -- RFC 2821, 4.3.2. + + EHLO or HELO + S: 250 + E: 504, 550 + + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 some.server.tld bleh\r\n") + -> one($buf)->write(pattern('~^HELO .*?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns('250 ServerName' . "\r\n") + ); + $this->_finishBuffer($buf); + try { + $smtp->start(); + } catch (Exception $e) { + $this->fail('Starting SMTP should send HELO and accept 250 response'); + } + } + + public function testInvalidHeloResponseCausesException() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 some.server.tld bleh\r\n") + -> one($buf)->write(pattern('~^HELO .*?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns('504 WTF' . "\r\n") + ); + $this->_finishBuffer($buf); + try { + $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started'); + $smtp->start(); + $this->fail('Non 250 HELO response should raise Exception'); + } catch (Exception $e) { + $this->assertFalse($smtp->isStarted(), '%s: SMTP start() should have failed'); + } + } + + public function testDomainNameIsPlacedInHelo() + { + /* -- RFC 2821, 4.1.4. + + The SMTP client MUST, if possible, ensure that the domain parameter + to the EHLO command is a valid principal host name (not a CNAME or MX + name) for its host. If this is not possible (e.g., when the client's + address is dynamically assigned and the client does not have an + obvious name), an address literal SHOULD be substituted for the + domain name and supplemental information provided that will assist in + identifying the client. + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 some.server.tld bleh\r\n") + -> one($buf)->write("HELO mydomain.com\r\n") -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns('250 ServerName' . "\r\n") + ); + $this->_finishBuffer($buf); + $smtp->setLocalDomain('mydomain.com'); + $smtp->start(); + } + + public function testSuccessfulMailCommand() + { + /* -- RFC 2821, 3.3. + + There are three steps to SMTP mail transactions. The transaction + starts with a MAIL command which gives the sender identification. + + ..... + + The first step in the procedure is the MAIL command. + + MAIL FROM: [SP ] + + -- RFC 2821, 4.1.1.2. + + Syntax: + + "MAIL FROM:" ("<>" / Reverse-Path) + [SP Mail-parameters] CRLF + -- RFC 2821, 4.1.2. + + Reverse-path = Path + Forward-path = Path + Path = "<" [ A-d-l ":" ] Mailbox ">" + A-d-l = At-domain *( "," A-d-l ) + ; Note that this form, the so-called "source route", + ; MUST BE accepted, SHOULD NOT be generated, and SHOULD be + ; ignored. + At-domain = "@" domain + + -- RFC 2821, 4.3.2. + + MAIL + S: 250 + E: 552, 451, 452, 550, 553, 503 + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null)) + -> allowing($message) + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('250 OK' . "\r\n") + ); + $this->_finishBuffer($buf); + try { + $smtp->start(); + $smtp->send($message); + } catch (Exception $e) { + $this->fail('MAIL FROM should accept a 250 response'); + } + } + + public function testInvalidResponseCodeFromMailCausesException() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null)) + -> allowing($message) + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('553 Bad' . "\r\n") + ); + $this->_finishBuffer($buf); + try { + $smtp->start(); + $smtp->send($message); + $this->fail('MAIL FROM should accept a 250 response'); + } catch (Exception $e) { + } + } + + public function testSenderIsPreferredOverFrom() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getSender() -> returns(array('another@domain.com'=>'Someone')) + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null)) + -> allowing($message) + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('250 OK' . "\r\n") + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testReturnPathIsPreferredOverSender() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getSender() -> returns(array('another@domain.com'=>'Someone')) + -> allowing($message)->getReturnPath() -> returns('more@domain.com') + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null)) + -> allowing($message) + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('250 OK' . "\r\n") + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testSuccessfulRcptCommandWith250Response() + { + /* -- RFC 2821, 3.3. + + The second step in the procedure is the RCPT command. + + RCPT TO: [ SP ] + + The first or only argument to this command includes a forward-path + (normally a mailbox and domain, always surrounded by "<" and ">" + brackets) identifying one recipient. If accepted, the SMTP server + returns a 250 OK reply and stores the forward-path. If the recipient + is known not to be a deliverable address, the SMTP server returns a + 550 reply, typically with a string such as "no such user - " and the + mailbox name (other circumstances and reply codes are possible). + This step of the procedure can be repeated any number of times. + + -- RFC 2821, 4.1.1.3. + + This command is used to identify an individual recipient of the mail + data; multiple recipients are specified by multiple use of this + command. The argument field contains a forward-path and may contain + optional parameters. + + The forward-path normally consists of the required destination + mailbox. Sending systems SHOULD not generate the optional list of + hosts known as a source route. + + ....... + + "RCPT TO:" ("" / "" / Forward-Path) + [SP Rcpt-parameters] CRLF + + -- RFC 2821, 4.2.2. + + 250 Requested mail action okay, completed + 251 User not local; will forward to + (See section 3.4) + 252 Cannot VRFY user, but will accept message and attempt + delivery + + -- RFC 2821, 4.3.2. + + RCPT + S: 250, 251 (but see section 3.4 for discussion of 251 and 551) + E: 550, 551, 552, 553, 450, 451, 452, 503, 550 + */ + + //We'll treat 252 as accepted since it isn't really a failure + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $s = $this->_sequence('SMTP-envelope'); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null)) + -> allowing($message) + + -> one($buf)->write("MAIL FROM: \r\n") -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> returns('250 OK' . "\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> returns('250 OK' . "\r\n") + ); + $this->_finishBuffer($buf); + try { + $smtp->start(); + $smtp->send($message); + } catch (Exception $e) { + $this->fail('RCPT TO should accept a 250 response'); + } + } + + public function testMailFromCommandIsOnlySentOncePerMessage() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $s = $this->_sequence('SMTP-envelope'); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null)) + -> allowing($message) + + -> one($buf)->write("MAIL FROM: \r\n") -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> returns('250 OK' . "\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> returns('250 OK' . "\r\n") + -> never($buf)->write("MAIL FROM: \r\n") + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testMultipleRecipientsSendsMultipleRcpt() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array( + 'foo@bar' => null, + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> allowing($message) + + -> one($buf)->write("RCPT TO: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('250 OK' . "\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns('250 OK' . "\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(3) + -> one($buf)->readLine(3) -> returns('250 OK' . "\r\n") + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testCcRecipientsSendsMultipleRcpt() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + -> allowing($message)->getCc() -> returns(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> allowing($message) + + -> one($buf)->write("RCPT TO: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('250 OK' . "\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns('250 OK' . "\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(3) + -> one($buf)->readLine(3) -> returns('250 OK' . "\r\n") + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testSendReturnsNumberOfSuccessfulRecipients() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + -> allowing($message)->getCc() -> returns(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> allowing($message) + + -> one($buf)->write("RCPT TO: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('250 OK' . "\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns('501 Nobody here' . "\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(3) + -> one($buf)->readLine(3) -> returns('250 OK' . "\r\n") + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(2, $smtp->send($message), + '%s: 1 of 3 recipients failed so 2 should be returned' + ); + } + + public function testRsetIsSentIfNoSuccessfulRecipients() + { + /* --RFC 2821, 4.1.1.5. + + This command specifies that the current mail transaction will be + aborted. Any stored sender, recipients, and mail data MUST be + discarded, and all buffers and state tables cleared. The receiver + MUST send a "250 OK" reply to a RSET command with no arguments. A + reset command may be issued by the client at any time. + + -- RFC 2821, 4.3.2. + + RSET + S: 250 + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + -> allowing($message) + + -> one($buf)->write("RCPT TO: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('503 Bad' . "\r\n") + -> one($buf)->write("RSET\r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns('250 OK' . "\r\n") + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(0, $smtp->send($message), + '%s: 1 of 1 recipients failed so 0 should be returned' + ); + } + + public function testSuccessfulDataCommand() + { + /* -- RFC 2821, 3.3. + + The third step in the procedure is the DATA command (or some + alternative specified in a service extension). + + DATA + + If accepted, the SMTP server returns a 354 Intermediate reply and + considers all succeeding lines up to but not including the end of + mail data indicator to be the message text. + + -- RFC 2821, 4.1.1.4. + + The receiver normally sends a 354 response to DATA, and then treats + the lines (strings ending in sequences, as described in + section 2.3.7) following the command as mail data from the sender. + This command causes the mail data to be appended to the mail data + buffer. The mail data may contain any of the 128 ASCII character + codes, although experience has indicated that use of control + characters other than SP, HT, CR, and LF may cause problems and + SHOULD be avoided when possible. + + -- RFC 2821, 4.3.2. + + DATA + I: 354 -> data -> S: 250 + E: 552, 554, 451, 452 + E: 451, 554, 503 + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + -> allowing($message) + + -> one($buf)->write("DATA\r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('354 Go ahead' . "\r\n") + ); + $this->_finishBuffer($buf); + try { + $smtp->start(); + $smtp->send($message); + } catch (Exception $e) { + $this->fail('354 is the expected response to DATA'); + } + } + + public function testBadDataResponseCausesException() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + -> allowing($message) + + -> one($buf)->write("DATA\r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns('451 Bad' . "\r\n") + ); + $this->_finishBuffer($buf); + try { + $smtp->start(); + $smtp->send($message); + $this->fail('354 is the expected response to DATA (not observed)'); + } catch (Exception $e) { + } + } + + public function testMessageIsStreamedToBufferForData() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $s = $this->_sequence('DATA Streaming'); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + + -> one($buf)->write("DATA\r\n") -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> returns('354 OK' . "\r\n") + -> one($message)->toByteStream($buf) -> inSequence($s) + -> one($buf)->write("\r\n.\r\n") -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> returns('250 OK' . "\r\n") + + -> allowing($message) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testBadResponseAfterDataTransmissionCausesException() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $s = $this->_sequence('DATA Streaming'); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + + -> one($buf)->write("DATA\r\n") -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> returns('354 OK' . "\r\n") + -> one($message)->toByteStream($buf) -> inSequence($s) + -> one($buf)->write("\r\n.\r\n") -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> returns('554 Error' . "\r\n") + + -> allowing($message) + ); + $this->_finishBuffer($buf); + try { + $smtp->start(); + $smtp->send($message); + $this->fail('250 is the expected response after a DATA transmission (not observed)'); + } catch (Exception $e) { + } + } + + public function testBccRecipientsAreRemovedFromHeaders() + { + /* -- RFC 2821, 7.2. + + Addresses that do not appear in the message headers may appear in the + RCPT commands to an SMTP server for a number of reasons. The two + most common involve the use of a mailing address as a "list exploder" + (a single address that resolves into multiple addresses) and the + appearance of "blind copies". Especially when more than one RCPT + command is present, and in order to avoid defeating some of the + purpose of these mechanisms, SMTP clients and servers SHOULD NOT copy + the full set of RCPT command arguments into the headers, either as + part of trace headers or as informational or private-extension + headers. Since this rule is often violated in practice, and cannot + be enforced, sending SMTP systems that are aware of "bcc" use MAY + find it helpful to send each blind copy as a separate message + transaction containing only a single RCPT command. + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + -> allowing($message)->getBcc() -> returns(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> atLeast(1)->of($message)->setBcc(array()) + -> allowing($message) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testEachBccRecipientIsSentASeparateMessage() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + -> allowing($message)->getBcc() -> returns(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> atLeast(1)->of($message)->setBcc(array()) + -> one($message)->setBcc(array('zip@button' => 'Zip Button')) + -> one($message)->setBcc(array('test@domain' => 'Test user')) + -> atLeast(1)->of($message)->setBcc(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> allowing($message) + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns("250 OK\r\n") + -> one($buf)->write("DATA\r\n") -> returns(3) + -> one($buf)->readLine(3) -> returns("354 OK\r\n") + -> one($buf)->write("\r\n.\r\n") -> returns(4) + -> one($buf)->readLine(4) -> returns("250 OK\r\n") + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(5) + -> one($buf)->readLine(5) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(6) + -> one($buf)->readLine(6) -> returns("250 OK\r\n") + -> one($buf)->write("DATA\r\n") -> returns(7) + -> one($buf)->readLine(7) -> returns("354 OK\r\n") + -> one($buf)->write("\r\n.\r\n") -> returns(8) + -> one($buf)->readLine(8) -> returns("250 OK\r\n") + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(9) + -> one($buf)->readLine(9) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(10) + -> one($buf)->readLine(10) -> returns("250 OK\r\n") + -> one($buf)->write("DATA\r\n") -> returns(11) + -> one($buf)->readLine(11) -> returns("354 OK\r\n") + -> one($buf)->write("\r\n.\r\n") -> returns(12) + -> one($buf)->readLine(12) -> returns("250 OK\r\n") + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(3, $smtp->send($message)); + } + + public function testMessageStateIsRestoredOnFailure() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + -> allowing($message)->getBcc() -> returns(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> one($message)->setBcc(array()) + -> one($message)->setBcc(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> allowing($message) + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns("250 OK\r\n") + -> one($buf)->write("DATA\r\n") -> returns(3) + -> one($buf)->readLine(3) -> returns("451 No\r\n") + ); + $this->_finishBuffer($buf); + + $smtp->start(); + try { + $smtp->send($message); + $this->fail('A bad response was given so exception is expected'); + } catch (Exception $e) { + } + } + + public function testStopSendsQuitCommand() + { + /* -- RFC 2821, 4.1.1.10. + + This command specifies that the receiver MUST send an OK reply, and + then close the transmission channel. + + The receiver MUST NOT intentionally close the transmission channel + until it receives and replies to a QUIT command (even if there was an + error). The sender MUST NOT intentionally close the transmission + channel until it sends a QUIT command and SHOULD wait until it + receives the reply (even if there was an error response to a previous + command). If the connection is closed prematurely due to violations + of the above or system or network failure, the server MUST cancel any + pending transaction, but not undo any previously completed + transaction, and generally MUST act as if the command or transaction + in progress had received a temporary error (i.e., a 4yz response). + + The QUIT command may be issued at any time. + + Syntax: + "QUIT" CRLF + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> one($buf)->initialize() + -> one($buf)->write("QUIT\r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns("221 Bye\r\n") + -> one($buf)->terminate() + ); + $this->_finishBuffer($buf); + + $this->assertFalse($smtp->isStarted()); + $smtp->start(); + $this->assertTrue($smtp->isStarted()); + $smtp->stop(); + $this->assertFalse($smtp->isStarted()); + } + + public function testBufferCanBeFetched() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ref = $smtp->getBuffer(); + $this->assertReference($buf, $ref); + } + + public function testBufferCanBeWrittenToUsingExecuteCommand() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> one($buf)->write("FOO\r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns("250 OK\r\n") + -> ignoring($buf) + ); + + $res = $smtp->executeCommand("FOO\r\n"); + $this->assertEqual("250 OK\r\n", $res); + } + + public function testResponseCodesAreValidated() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> one($buf)->write("FOO\r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns("551 Not ok\r\n") + -> ignoring($buf) + ); + + try { + $smtp->executeCommand("FOO\r\n", array(250, 251)); + $this->fail('A 250 or 251 response was needed but 551 was returned.'); + } catch (Exception $e) { + } + } + + public function testFailedRecipientsCanBeCollectedByReference() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar' => null)) + -> allowing($message)->getBcc() -> returns(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> atLeast(1)->of($message)->setBcc(array()) + -> one($message)->setBcc(array('zip@button' => 'Zip Button')) + -> one($message)->setBcc(array('test@domain' => 'Test user')) + -> atLeast(1)->of($message)->setBcc(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user' + )) + -> allowing($message) + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(1) + -> one($buf)->readLine(1) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(2) + -> one($buf)->readLine(2) -> returns("250 OK\r\n") + -> one($buf)->write("DATA\r\n") -> returns(3) + -> one($buf)->readLine(3) -> returns("354 OK\r\n") + -> one($buf)->write("\r\n.\r\n") -> returns(4) + -> one($buf)->readLine(4) -> returns("250 OK\r\n") + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(5) + -> one($buf)->readLine(5) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(6) + -> one($buf)->readLine(6) -> returns("500 Bad\r\n") + -> one($buf)->write("RSET\r\n") -> returns(7) + -> one($buf)->readLine(7) -> returns("250 OK\r\n") + + -> one($buf)->write("MAIL FROM: \r\n") -> returns(8) + -> one($buf)->readLine(8) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> returns(9) + -> one($buf)->readLine(9) -> returns("500 Bad\r\n") + -> one($buf)->write("RSET\r\n") -> returns(10) + -> one($buf)->readLine(10) -> returns("250 OK\r\n") + ); + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEqual(1, $smtp->send($message, $failures)); + $this->assertEqual(array('zip@button', 'test@domain'), $failures, + '%s: Failures should be caught in an array' + ); + } + + public function testSendingRegeneratesMessageId() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain.com'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null)) + -> one($message)->generateId() + -> allowing($message) + ); + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + // -- Protected methods + + protected function _getBuffer() + { + return $this->_mock('Swift_Transport_IoBuffer'); + } + + protected function _createMessage() + { + return $this->_mock('Swift_Mime_Message'); + } + + protected function _finishBuffer($buf) + { + $this->_checking(Expectations::create() + -> ignoring($buf)->readLine(0) -> returns('220 server.com foo' . "\r\n") + -> ignoring($buf)->write(pattern('~^(EH|HE)LO .*?\r\n$~D')) -> returns($x = uniqid()) + -> ignoring($buf)->readLine($x) -> returns('250 ServerName' . "\r\n") + -> ignoring($buf)->write(pattern('~^MAIL FROM: <.*?>\r\n$~D')) -> returns($x = uniqid()) + -> ignoring($buf)->readLine($x) -> returns('250 OK' . "\r\n") + -> ignoring($buf)->write(pattern('~^RCPT TO: <.*?>\r\n$~D')) -> returns($x = uniqid()) + -> ignoring($buf)->readLine($x) -> returns('250 OK' . "\r\n") + -> ignoring($buf)->write("DATA\r\n") -> returns($x = uniqid()) + -> ignoring($buf)->readLine($x) -> returns('354 OK' . "\r\n") + -> ignoring($buf)->write("\r\n.\r\n") -> returns($x = uniqid()) + -> ignoring($buf)->readLine($x) -> returns('250 OK' . "\r\n") + -> ignoring($buf)->write("RSET\r\n") -> returns($x = uniqid()) + -> ignoring($buf)->readLine($x) -> returns("250 OK\r\n") + -> ignoring($buf) -> returns(false) + ); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/CramMd5AuthenticatorTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/CramMd5AuthenticatorTest.php new file mode 100644 index 0000000..ee0eab9 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/CramMd5AuthenticatorTest.php @@ -0,0 +1,67 @@ +_agent = $this->_mock('Swift_Transport_SmtpAgent'); + } + + public function testKeywordIsCramMd5() + { + /* -- RFC 2195, 2. + The authentication type associated with CRAM is "CRAM-MD5". + */ + + $cram = $this->_getAuthenticator(); + $this->assertEqual('CRAM-MD5', $cram->getAuthKeyword()); + } + + public function testSuccessfulAuthentication() + { + $cram = $this->_getAuthenticator(); + $this->_checking(Expectations::create() + -> one($this->_agent)->executeCommand("AUTH CRAM-MD5\r\n", array(334)) + -> returns('334 ' . base64_encode('') . "\r\n") + // The use of any() is controversial, but here to avoid crazy test logic + -> one($this->_agent)->executeCommand(any(), array(235)) + ); + + $this->assertTrue($cram->authenticate($this->_agent, 'jack', 'pass'), + '%s: The buffer accepted all commands authentication should succeed' + ); + } + + public function testAuthenticationFailureSendRsetAndReturnFalse() + { + $cram = $this->_getAuthenticator(); + $this->_checking(Expectations::create() + -> one($this->_agent)->executeCommand("AUTH CRAM-MD5\r\n", array(334)) + -> returns('334 ' . base64_encode('') . "\r\n") + // The use of any() is controversial, but here to avoid crazy test logic + -> one($this->_agent)->executeCommand(any(), array(235)) + -> throws(new Swift_TransportException("")) + + -> one($this->_agent)->executeCommand("RSET\r\n", array(250)) + ); + + $this->assertFalse($cram->authenticate($this->_agent, 'jack', 'pass'), + '%s: Authentication fails, so RSET should be sent' + ); + } + + // -- Private helpers + + private function _getAuthenticator() + { + return new Swift_Transport_Esmtp_Auth_CramMd5Authenticator(); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/LoginAuthenticatorTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/LoginAuthenticatorTest.php new file mode 100644 index 0000000..7a9f0b4 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/LoginAuthenticatorTest.php @@ -0,0 +1,60 @@ +_agent = $this->_mock('Swift_Transport_SmtpAgent'); + } + + public function testKeywordIsLogin() + { + $login = $this->_getAuthenticator(); + $this->assertEqual('LOGIN', $login->getAuthKeyword()); + } + + public function testSuccessfulAuthentication() + { + $login = $this->_getAuthenticator(); + $this->_checking(Expectations::create() + -> one($this->_agent)->executeCommand("AUTH LOGIN\r\n", array(334)) + -> one($this->_agent)->executeCommand(base64_encode('jack') . "\r\n", array(334)) + -> one($this->_agent)->executeCommand(base64_encode('pass') . "\r\n", array(235)) + ); + + $this->assertTrue($login->authenticate($this->_agent, 'jack', 'pass'), + '%s: The buffer accepted all commands authentication should succeed' + ); + } + + public function testAuthenticationFailureSendRsetAndReturnFalse() + { + $login = $this->_getAuthenticator(); + $this->_checking(Expectations::create() + -> one($this->_agent)->executeCommand("AUTH LOGIN\r\n", array(334)) + -> one($this->_agent)->executeCommand(base64_encode('jack') . "\r\n", array(334)) + -> one($this->_agent)->executeCommand(base64_encode('pass') . "\r\n", array(235)) + -> throws(new Swift_TransportException("")) + -> one($this->_agent)->executeCommand("RSET\r\n", array(250)) + ); + + $this->assertFalse($login->authenticate($this->_agent, 'jack', 'pass'), + '%s: Authentication fails, so RSET should be sent' + ); + } + + // -- Private helpers + + private function _getAuthenticator() + { + return new Swift_Transport_Esmtp_Auth_LoginAuthenticator(); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/PlainAuthenticatorTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/PlainAuthenticatorTest.php new file mode 100644 index 0000000..19c2bc2 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/PlainAuthenticatorTest.php @@ -0,0 +1,71 @@ +_agent = $this->_mock('Swift_Transport_SmtpAgent'); + } + + public function testKeywordIsPlain() + { + /* -- RFC 4616, 1. + The name associated with this mechanism is "PLAIN". + */ + + $login = $this->_getAuthenticator(); + $this->assertEqual('PLAIN', $login->getAuthKeyword()); + } + + public function testSuccessfulAuthentication() + { + /* -- RFC 4616, 2. + The client presents the authorization identity (identity to act as), + followed by a NUL (U+0000) character, followed by the authentication + identity (identity whose password will be used), followed by a NUL + (U+0000) character, followed by the clear-text password. + */ + + $plain = $this->_getAuthenticator(); + $this->_checking(Expectations::create() + -> one($this->_agent)->executeCommand('AUTH PLAIN ' . base64_encode( + 'jack' . chr(0) . 'jack' . chr(0) . 'pass' + ) . "\r\n", array(235)) + ); + + $this->assertTrue($plain->authenticate($this->_agent, 'jack', 'pass'), + '%s: The buffer accepted all commands authentication should succeed' + ); + } + + public function testAuthenticationFailureSendRsetAndReturnFalse() + { + $plain = $this->_getAuthenticator(); + $this->_checking(Expectations::create() + -> one($this->_agent)->executeCommand('AUTH PLAIN ' . base64_encode( + 'jack' . chr(0) . 'jack' . chr(0) . 'pass' + ) . "\r\n", array(235)) -> throws(new Swift_TransportException("")) + + -> one($this->_agent)->executeCommand("RSET\r\n", array(250)) + ); + + $this->assertFalse($plain->authenticate($this->_agent, 'jack', 'pass'), + '%s: Authentication fails, so RSET should be sent' + ); + } + + // -- Private helpers + + private function _getAuthenticator() + { + return new Swift_Transport_Esmtp_Auth_PlainAuthenticator(); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/AuthHandlerTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/AuthHandlerTest.php new file mode 100644 index 0000000..682381d --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/AuthHandlerTest.php @@ -0,0 +1,157 @@ +_agent = $this->_mock('Swift_Transport_SmtpAgent'); + } + + public function testKeywordIsAuth() + { + $auth = $this->_createHandler(array()); + $this->assertEqual('AUTH', $auth->getHandledKeyword()); + } + + public function testUsernameCanBeSetAndFetched() + { + $auth = $this->_createHandler(array()); + $auth->setUsername('jack'); + $this->assertEqual('jack', $auth->getUsername()); + } + + public function testPasswordCanBeSetAndFetched() + { + $auth = $this->_createHandler(array()); + $auth->setPassword('pass'); + $this->assertEqual('pass', $auth->getPassword()); + } + + public function testAuthModeCanBeSetAndFetched() + { + $auth = $this->_createHandler(array()); + $auth->setAuthMode('PLAIN'); + $this->assertEqual('PLAIN', $auth->getAuthMode()); + } + + public function testMixinMethods() + { + $auth = $this->_createHandler(array()); + $mixins = $auth->exposeMixinMethods(); + $this->assertTrue(in_array('getUsername', $mixins), + '%s: getUsername() should be accessible via mixin' + ); + $this->assertTrue(in_array('setUsername', $mixins), + '%s: setUsername() should be accessible via mixin' + ); + $this->assertTrue(in_array('getPassword', $mixins), + '%s: getPassword() should be accessible via mixin' + ); + $this->assertTrue(in_array('setPassword', $mixins), + '%s: setPassword() should be accessible via mixin' + ); + $this->assertTrue(in_array('setAuthMode', $mixins), + '%s: setAuthMode() should be accessible via mixin' + ); + $this->assertTrue(in_array('getAuthMode', $mixins), + '%s: getAuthMode() should be accessible via mixin' + ); + } + + public function testAuthenticatorsAreCalledAccordingToParamsAfterEhlo() + { + $a1 = $this->_createMockAuthenticator('PLAIN'); + $a2 = $this->_createMockAuthenticator('LOGIN'); + + $this->_checking(Expectations::create() + -> never($a1)->authenticate($this->_agent, 'jack', 'pass') + -> one($a2)->authenticate($this->_agent, 'jack', 'pass') -> returns(true) + ); + + $auth = $this->_createHandler(array($a1, $a2)); + $auth->setUsername('jack'); + $auth->setPassword('pass'); + + $auth->setKeywordParams(array('CRAM-MD5', 'LOGIN')); + $auth->afterEhlo($this->_agent); + } + + public function testAuthenticatorsAreNotUsedIfNoUsernameSet() + { + $a1 = $this->_createMockAuthenticator('PLAIN'); + $a2 = $this->_createMockAuthenticator('LOGIN'); + + $this->_checking(Expectations::create() + -> never($a1)->authenticate($this->_agent, 'jack', 'pass') + -> never($a2)->authenticate($this->_agent, 'jack', 'pass') -> returns(true) + ); + + $auth = $this->_createHandler(array($a1, $a2)); + + $auth->setKeywordParams(array('CRAM-MD5', 'LOGIN')); + $auth->afterEhlo($this->_agent); + } + + public function testSeveralAuthenticatorsAreTriedIfNeeded() + { + $a1 = $this->_createMockAuthenticator('PLAIN'); + $a2 = $this->_createMockAuthenticator('LOGIN'); + + $this->_checking(Expectations::create() + -> one($a1)->authenticate($this->_agent, 'jack', 'pass') -> returns(false) + -> one($a2)->authenticate($this->_agent, 'jack', 'pass') -> returns(true) + ); + + $auth = $this->_createHandler(array($a1, $a2)); + $auth->setUsername('jack'); + $auth->setPassword('pass'); + + $auth->setKeywordParams(array('PLAIN', 'LOGIN')); + $auth->afterEhlo($this->_agent); + } + + public function testFirstAuthenticatorToPassBreaksChain() + { + $a1 = $this->_createMockAuthenticator('PLAIN'); + $a2 = $this->_createMockAuthenticator('LOGIN'); + $a3 = $this->_createMockAuthenticator('CRAM-MD5'); + + $this->_checking(Expectations::create() + -> one($a1)->authenticate($this->_agent, 'jack', 'pass') -> returns(false) + -> one($a2)->authenticate($this->_agent, 'jack', 'pass') -> returns(true) + -> never($a3)->authenticate($this->_agent, 'jack', 'pass') + ); + + $auth = $this->_createHandler(array($a1, $a2)); + $auth->setUsername('jack'); + $auth->setPassword('pass'); + + $auth->setKeywordParams(array('PLAIN', 'LOGIN', 'CRAM-MD5')); + $auth->afterEhlo($this->_agent); + } + + // -- Private helpers + + private function _createHandler($authenticators) + { + return new Swift_Transport_Esmtp_AuthHandler($authenticators); + } + + private function _createMockAuthenticator($type) + { + $authenticator = $this->_mock('Swift_Transport_Esmtp_Authenticator'); + $this->_checking(Expectations::create() + -> ignoring($authenticator)->getAuthKeyword() -> returns($type) + ); + + return $authenticator; + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransport/ExtensionSupportTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransport/ExtensionSupportTest.php new file mode 100644 index 0000000..744541f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransport/ExtensionSupportTest.php @@ -0,0 +1,314 @@ +_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $this->_checking(Expectations::create() + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> allowing($ext1)->getPriorityOver('STARTTLS') -> returns(0) + -> allowing($ext2)->getHandledKeyword() -> returns('STARTTLS') + -> allowing($ext2)->getPriorityOver('AUTH') -> returns(-1) + -> ignoring($ext1) + -> ignoring($ext2) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2)); + $this->assertEqual(array($ext2, $ext1), $smtp->getExtensionHandlers()); + } + + public function testHandlersAreNotifiedOfParams() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $s = $this->_sequence('Initiation-sequence'); + $this->_checking(Expectations::create() + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 server.com foo\r\n") + -> one($buf)->write(pattern('~^EHLO .*?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-ServerName.tld\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-AUTH PLAIN LOGIN\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250 SIZE=123456\r\n") + + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> one($ext1)->setKeywordParams(array('PLAIN', 'LOGIN')) + -> allowing($ext2)->getHandledKeyword() -> returns('SIZE') + -> allowing($ext2)->setKeywordParams(array('123456')) + -> ignoring($ext1) + -> ignoring($ext2) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2)); + $smtp->start(); + } + + public function testSupportedExtensionHandlersAreRunAfterEhlo() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext3 = $this->_mock('Swift_Transport_EsmtpHandler'); + $s = $this->_sequence('Initiation-sequence'); + $this->_checking(Expectations::create() + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 server.com foo\r\n") + -> one($buf)->write(pattern('~^EHLO .*?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-ServerName.tld\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-AUTH PLAIN LOGIN\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250 SIZE=123456\r\n") + + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> one($ext1)->afterEhlo($smtp) + -> allowing($ext2)->getHandledKeyword() -> returns('SIZE') + -> allowing($ext2)->afterEhlo($smtp) + -> allowing($ext3)->getHandledKeyword() -> returns('STARTTLS') + -> never($ext3)->afterEhlo(any()) + -> ignoring($ext1) + -> ignoring($ext2) + -> ignoring($ext3) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3)); + $smtp->start(); + } + + public function testExtensionsCanModifyMailFromParams() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext3 = $this->_mock('Swift_Transport_EsmtpHandler'); + $message = $this->_createMessage(); + $s = $this->_sequence('Initiation-sequence'); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null)) + -> ignoring($message) + + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 server.com foo\r\n") + -> one($buf)->write(pattern('~^EHLO .*?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-ServerName.tld\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-AUTH PLAIN LOGIN\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250 SIZE=123456\r\n") + -> one($buf)->write("MAIL FROM: FOO ZIP\r\n") -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> inSequence($s) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: \r\n") -> inSequence($s) -> returns(3) + -> one($buf)->readLine(3) -> inSequence($s) -> returns("250 OK\r\n") + + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> one($ext1)->getMailParams() -> returns('FOO') + -> allowing($ext1)->getPriorityOver('AUTH') -> returns(-1) + -> allowing($ext2)->getHandledKeyword() -> returns('SIZE') + -> one($ext2)->getMailParams() -> returns('ZIP') + -> allowing($ext2)->getPriorityOver('AUTH') -> returns(1) + -> allowing($ext3)->getHandledKeyword() -> returns('STARTTLS') + -> never($ext3)->getMailParams() + -> ignoring($ext1) + -> ignoring($ext2) + -> ignoring($ext3) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3)); + $smtp->start(); + $smtp->send($message); + } + + public function testExtensionsCanModifyRcptParams() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext3 = $this->_mock('Swift_Transport_EsmtpHandler'); + $message = $this->_createMessage(); + $s = $this->_sequence('Initiation-sequence'); + $this->_checking(Expectations::create() + -> allowing($message)->getFrom() -> returns(array('me@domain'=>'Me')) + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null)) + -> ignoring($message) + + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 server.com foo\r\n") + -> one($buf)->write(pattern('~^EHLO .*?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-ServerName.tld\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-AUTH PLAIN LOGIN\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250 SIZE=123456\r\n") + -> one($buf)->write("MAIL FROM: \r\n") -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> inSequence($s) -> returns("250 OK\r\n") + -> one($buf)->write("RCPT TO: FOO ZIP\r\n") -> inSequence($s) -> returns(3) + -> one($buf)->readLine(3) -> inSequence($s) -> returns("250 OK\r\n") + + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> one($ext1)->getRcptParams() -> returns('FOO') + -> allowing($ext1)->getPriorityOver('AUTH') -> returns(-1) + -> allowing($ext2)->getHandledKeyword() -> returns('SIZE') + -> one($ext2)->getRcptParams() -> returns('ZIP') + -> allowing($ext2)->getPriorityOver('AUTH') -> returns(1) + -> allowing($ext3)->getHandledKeyword() -> returns('STARTTLS') + -> never($ext3)->getRcptParams() + -> ignoring($ext1) + -> ignoring($ext2) + -> ignoring($ext3) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3)); + $smtp->start(); + $smtp->send($message); + } + + public function testExtensionsAreNotifiedOnCommand() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext3 = $this->_mock('Swift_Transport_EsmtpHandler'); + $s = $this->_sequence('Initiation-sequence'); + $this->_checking(Expectations::create() + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 server.com foo\r\n") + -> one($buf)->write(pattern('~^EHLO .*?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-ServerName.tld\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-AUTH PLAIN LOGIN\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250 SIZE=123456\r\n") + -> one($buf)->write("FOO\r\n") -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> inSequence($s) -> returns("250 Cool\r\n") + + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> one($ext1)->onCommand($smtp, "FOO\r\n", array(250, 251), optional()) + -> allowing($ext2)->getHandledKeyword() -> returns('SIZE') + -> one($ext2)->onCommand($smtp, "FOO\r\n", array(250, 251), optional()) + -> allowing($ext3)->getHandledKeyword() -> returns('STARTTLS') + -> never($ext3)->onCommand(any(), any(), any(), optional()) + -> ignoring($ext1) + -> ignoring($ext2) + -> ignoring($ext3) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3)); + $smtp->start(); + $smtp->executeCommand("FOO\r\n", array(250, 251)); + } + + public function testChainOfCommandAlgorithmWhenNotifyingExtensions() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $ext3 = $this->_mock('Swift_Transport_EsmtpHandler'); + $s = $this->_sequence('Initiation-sequence'); + $this->_checking(Expectations::create() + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 server.com foo\r\n") + -> one($buf)->write(pattern('~^EHLO .*?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-ServerName.tld\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250-AUTH PLAIN LOGIN\r\n") + -> one($buf)->readLine(1) -> inSequence($s) -> returns("250 SIZE=123456\r\n") + -> never($buf)->write("FOO\r\n") + + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> one($ext1)->onCommand($smtp, "FOO\r\n", array(250, 251), optional()) -> calls(array($this, 'cbStopCommand')) + -> allowing($ext2)->getHandledKeyword() -> returns('SIZE') + -> never($ext2)->onCommand(any(), any(), any(), optional()) + -> allowing($ext3)->getHandledKeyword() -> returns('STARTTLS') + -> never($ext3)->onCommand(any(), any(), any(), optional()) + -> ignoring($ext1) + -> ignoring($ext2) + -> ignoring($ext3) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3)); + $smtp->start(); + $smtp->executeCommand("FOO\r\n", array(250, 251)); + } + + public function cbStopCommand(Yay_Invocation $invocation) + { + $args =& $invocation->getArguments(); + $stop =& $args[4]; + $stop = true; + + return "250 ok"; + } + + public function testExtensionsCanExposeMixinMethods() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandlerMixin'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $this->_checking(Expectations::create() + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> allowing($ext1)->exposeMixinMethods() -> returns(array('setUsername', 'setPassword')) + -> one($ext1)->setUsername('mick') + -> one($ext1)->setPassword('pass') + -> allowing($ext2)->getHandledKeyword() -> returns('STARTTLS') + -> ignoring($ext1) + -> ignoring($ext2) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2)); + $smtp->setUsername('mick'); + $smtp->setPassword('pass'); + } + + public function testMixinMethodsBeginningWithSetAndNullReturnAreFluid() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandlerMixin'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $this->_checking(Expectations::create() + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> allowing($ext1)->exposeMixinMethods() -> returns(array('setUsername', 'setPassword')) + -> one($ext1)->setUsername('mick') -> returns(NULL) + -> one($ext1)->setPassword('pass') -> returns(NULL) + -> allowing($ext2)->getHandledKeyword() -> returns('STARTTLS') + -> ignoring($ext1) + -> ignoring($ext2) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2)); + $ret = $smtp->setUsername('mick'); + $this->assertReference($smtp, $ret); + $ret = $smtp->setPassword('pass'); + $this->assertReference($smtp, $ret); + } + + public function testMixinSetterWhichReturnValuesAreNotFluid() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->_mock('Swift_Transport_EsmtpHandlerMixin'); + $ext2 = $this->_mock('Swift_Transport_EsmtpHandler'); + $this->_checking(Expectations::create() + -> allowing($ext1)->getHandledKeyword() -> returns('AUTH') + -> allowing($ext1)->exposeMixinMethods() -> returns(array('setUsername', 'setPassword')) + -> one($ext1)->setUsername('mick') -> returns('x') + -> one($ext1)->setPassword('pass') -> returns('x') + -> allowing($ext2)->getHandledKeyword() -> returns('STARTTLS') + -> ignoring($ext1) + -> ignoring($ext2) + ); + $this->_finishBuffer($buf); + $smtp->setExtensionHandlers(array($ext1, $ext2)); + $this->assertEqual('x', $smtp->setUsername('mick')); + $this->assertEqual('x', $smtp->setPassword('pass')); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransportTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransportTest.php new file mode 100644 index 0000000..5059699 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransportTest.php @@ -0,0 +1,235 @@ +_createEventDispatcher(); + } + + return new Swift_Transport_EsmtpTransport($buf, array(), $dispatcher); + } + + public function testHostCanBeSetAndFetched() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $smtp->setHost('foo'); + $this->assertEqual('foo', $smtp->getHost(), '%s: Host should be returned'); + } + + public function testPortCanBeSetAndFetched() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $smtp->setPort(25); + $this->assertEqual(25, $smtp->getPort(), '%s: Port should be returned'); + } + + public function testTimeoutCanBeSetAndFetched() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $this->_checking(Expectations::create() + -> one($buf)->setParam('timeout', 10)); + $smtp->setTimeout(10); + $this->assertEqual(10, $smtp->getTimeout(), '%s: Timeout should be returned'); + } + + public function testEncryptionCanBeSetAndFetched() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $smtp->setEncryption('tls'); + $this->assertEqual('tls', $smtp->getEncryption(), '%s: Crypto should be returned'); + } + + public function testStartSendsHeloToInitiate() + {//Overridden for EHLO instead + } + + public function testStartSendsEhloToInitiate() + { + /* -- RFC 2821, 3.2. + + 3.2 Client Initiation + + Once the server has sent the welcoming message and the client has + received it, the client normally sends the EHLO command to the + server, indicating the client's identity. In addition to opening the + session, use of EHLO indicates that the client is able to process + service extensions and requests that the server provide a list of the + extensions it supports. Older SMTP systems which are unable to + support service extensions and contemporary clients which do not + require service extensions in the mail session being initiated, MAY + use HELO instead of EHLO. Servers MUST NOT return the extended + EHLO-style response to a HELO command. For a particular connection + attempt, if the server returns a "command not recognized" response to + EHLO, the client SHOULD be able to fall back and send HELO. + + In the EHLO command the host sending the command identifies itself; + the command may be interpreted as saying "Hello, I am " (and, + in the case of EHLO, "and I support service extension requests"). + + -- RFC 2281, 4.1.1.1. + + ehlo = "EHLO" SP Domain CRLF + helo = "HELO" SP Domain CRLF + + -- RFC 2821, 4.3.2. + + EHLO or HELO + S: 250 + E: 504, 550 + + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 some.server.tld bleh\r\n") + -> one($buf)->write(pattern('~^EHLO .+?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns('250 ServerName' . "\r\n") + ); + $this->_finishBuffer($buf); + try { + $smtp->start(); + } catch (Exception $e) { + $this->fail('Starting Esmtp should send EHLO and accept 250 response'); + } + } + + public function testHeloIsUsedAsFallback() + { + /* -- RFC 2821, 4.1.4. + + If the EHLO command is not acceptable to the SMTP server, 501, 500, + or 502 failure replies MUST be returned as appropriate. The SMTP + server MUST stay in the same state after transmitting these replies + that it was in before the EHLO was received. + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 some.server.tld bleh\r\n") + -> one($buf)->write(pattern('~^EHLO .+?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns('501 WTF' . "\r\n") + -> one($buf)->write(pattern('~^HELO .+?\r\n$~D')) -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> inSequence($s) -> returns('250 HELO' . "\r\n") + ); + $this->_finishBuffer($buf); + try { + $smtp->start(); + } catch (Exception $e) { + $this->fail( + 'Starting Esmtp should fallback to HELO if needed and accept 250 response' + ); + } + } + + public function testInvalidHeloResponseCausesException() + {//Overridden to first try EHLO + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 some.server.tld bleh\r\n") + -> one($buf)->write(pattern('~^EHLO .*?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns('501 WTF' . "\r\n") + -> one($buf)->write(pattern('~^HELO .*?\r\n$~D')) -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> inSequence($s) -> returns('504 WTF' . "\r\n") + ); + $this->_finishBuffer($buf); + try { + $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started'); + $smtp->start(); + $this->fail('Non 250 HELO response should raise Exception'); + } catch (Exception $e) { + $this->assertFalse($smtp->isStarted(), '%s: SMTP start() should have failed'); + } + } + + public function testDomainNameIsPlacedInEhlo() + { + /* -- RFC 2821, 4.1.4. + + The SMTP client MUST, if possible, ensure that the domain parameter + to the EHLO command is a valid principal host name (not a CNAME or MX + name) for its host. If this is not possible (e.g., when the client's + address is dynamically assigned and the client does not have an + obvious name), an address literal SHOULD be substituted for the + domain name and supplemental information provided that will assist in + identifying the client. + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 some.server.tld bleh\r\n") + -> one($buf)->write("EHLO mydomain.com\r\n") -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns('250 ServerName' . "\r\n") + ); + $this->_finishBuffer($buf); + $smtp->setLocalDomain('mydomain.com'); + $smtp->start(); + } + + public function testDomainNameIsPlacedInHelo() + { //Overridden to include ESMTP + /* -- RFC 2821, 4.1.4. + + The SMTP client MUST, if possible, ensure that the domain parameter + to the EHLO command is a valid principal host name (not a CNAME or MX + name) for its host. If this is not possible (e.g., when the client's + address is dynamically assigned and the client does not have an + obvious name), an address literal SHOULD be substituted for the + domain name and supplemental information provided that will assist in + identifying the client. + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $s = $this->_sequence('SMTP-convo'); + $this->_checking(Expectations::create() + -> one($buf)->initialize() -> inSequence($s) + -> one($buf)->readLine(0) -> inSequence($s) -> returns("220 some.server.tld bleh\r\n") + -> one($buf)->write(pattern('~^EHLO .+?\r\n$~D')) -> inSequence($s) -> returns(1) + -> one($buf)->readLine(1) -> inSequence($s) -> returns('501 WTF' . "\r\n") + -> one($buf)->write("HELO mydomain.com\r\n") -> inSequence($s) -> returns(2) + -> one($buf)->readLine(2) -> inSequence($s) -> returns('250 ServerName' . "\r\n") + ); + $this->_finishBuffer($buf); + $smtp->setLocalDomain('mydomain.com'); + $smtp->start(); + } + + public function testFluidInterface() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $this->_checking(Expectations::create() + -> one($buf)->setParam('timeout', 30)); + + $ref = $smtp + ->setHost('foo') + ->setPort(25) + ->setEncryption('tls') + ->setTimeout(30) + ; + $this->assertReference($ref, $smtp); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/FailoverTransportTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/FailoverTransportTest.php new file mode 100644 index 0000000..5f7b5aa --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/FailoverTransportTest.php @@ -0,0 +1,335 @@ +mock('Swift_Mime_Message'); + $message2 = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message1) + -> ignoring($message2) + -> allowing($t1)->isStarted() -> returns(false) -> when($con->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con->is('on')) + -> one($t1)->start() -> when($con->isNot('on')) -> then($con->is('on')) + -> one($t1)->send($message1, optional()) -> returns(1) -> when($con->is('on')) + -> one($t1)->send($message2, optional()) -> returns(1) -> when($con->is('on')) + -> ignoring($t1) + -> never($t2)->start() + -> never($t2)->send(any(), optional()) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEqual(1, $transport->send($message1)); + $this->assertEqual(1, $transport->send($message2)); + $context->assertIsSatisfied(); + } + + public function testMessageCanBeTriedOnNextTransportIfExceptionThrown() + { + $e = new Swift_TransportException('b0rken'); + + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message, optional()) -> throws($e) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message, optional()) -> returns(1) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEqual(1, $transport->send($message)); + $context->assertIsSatisfied(); + } + + public function testZeroIsReturnedIfTransportReturnsZero() + { + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $con = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con->is('on')) + -> one($t1)->start() -> when($con->isNot('on')) -> then($con->is('on')) + -> one($t1)->send($message, optional()) -> returns(0) -> when($con->is('on')) + -> ignoring($t1) + ); + + $transport = $this->_getTransport(array($t1)); + $transport->start(); + $this->assertEqual(0, $transport->send($message)); + $context->assertIsSatisfied(); + } + + public function testTransportsWhichThrowExceptionsAreNotRetried() + { + $e = new Swift_TransportException('maur b0rken'); + + $context = new Mockery(); + $message1 = $context->mock('Swift_Mime_Message'); + $message2 = $context->mock('Swift_Mime_Message'); + $message3 = $context->mock('Swift_Mime_Message'); + $message4 = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message1) + -> ignoring($message2) + -> ignoring($message3) + -> ignoring($message4) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message1, optional()) -> throws($e) -> when($con1->is('on')) + -> never($t1)->send($message2, optional()) + -> never($t1)->send($message3, optional()) + -> never($t1)->send($message4, optional()) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message1, optional()) -> returns(1) -> when($con2->is('on')) + -> one($t2)->send($message2, optional()) -> returns(1) -> when($con2->is('on')) + -> one($t2)->send($message3, optional()) -> returns(1) -> when($con2->is('on')) + -> one($t2)->send($message4, optional()) -> returns(1) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEqual(1, $transport->send($message1)); + $this->assertEqual(1, $transport->send($message2)); + $this->assertEqual(1, $transport->send($message3)); + $this->assertEqual(1, $transport->send($message4)); + } + + public function testExceptionIsThrownIfAllTransportsDie() + { + $e = new Swift_TransportException('b0rken'); + + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message, optional()) -> throws($e) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message, optional()) -> throws($e) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + try { + $transport->send($message); + $this->fail('All transports failed so Exception should be thrown'); + } catch (Exception $e) { + } + $context->assertIsSatisfied(); + } + + public function testStoppingTransportStopsAllDelegates() + { + $context = new Mockery(); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('on'); + $con2 = $context->states('Connection')->startsAs('on'); + $context->checking(Expectations::create() + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->stop() -> when($con1->is('on')) -> then($con1->is('off')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->stop() -> when($con2->is('on')) -> then($con2->is('off')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $transport->stop(); + $context->assertIsSatisfied(); + } + + public function testTransportShowsAsNotStartedIfAllDelegatesDead() + { + $e = new Swift_TransportException('b0rken'); + + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message, optional()) -> throws($e) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message, optional()) -> throws($e) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertTrue($transport->isStarted()); + try { + $transport->send($message); + $this->fail('All transports failed so Exception should be thrown'); + } catch (Exception $e) { + $this->assertFalse($transport->isStarted()); + } + $context->assertIsSatisfied(); + } + + public function testRestartingTransportRestartsDeadDelegates() + { + $e = new Swift_TransportException('b0rken'); + + $context = new Mockery(); + $message1 = $context->mock('Swift_Mime_Message'); + $message2 = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message1) + -> ignoring($message2) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> exactly(2)->of($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message1, optional()) -> throws($e) -> when($con1->is('on')) -> then($con1->is('off')) + -> one($t1)->send($message2, optional()) -> returns(10) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message1, optional()) -> throws($e) -> when($con2->is('on')) + -> never($t2)->send($message2, optional()) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertTrue($transport->isStarted()); + try { + $transport->send($message1); + $this->fail('All transports failed so Exception should be thrown'); + } catch (Exception $e) { + $this->assertFalse($transport->isStarted()); + } + //Restart and re-try + $transport->start(); + $this->assertTrue($transport->isStarted()); + $this->assertEqual(10, $transport->send($message2)); + $context->assertIsSatisfied(); + } + + public function testFailureReferenceIsPassedToDelegates() + { + $failures = array(); + + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $con = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con->is('on')) + -> one($t1)->start() -> when($con->isNot('on')) -> then($con->is('on')) + -> one($t1)->send($message, reference($failures)) -> returns(1) -> when($con->is('on')) + -> ignoring($t1) + ); + + $transport = $this->_getTransport(array($t1)); + $transport->start(); + $transport->send($message, $failures); + $context->assertIsSatisfied(); + } + + public function testRegisterPluginDelegatesToLoadedTransports() + { + $context = new Mockery(); + + $plugin = $this->_createPlugin($context); + + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $context->checking(Expectations::create() + -> one($t1)->registerPlugin($plugin) + -> one($t2)->registerPlugin($plugin) + -> ignoring($t1) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->registerPlugin($plugin); + + $context->assertIsSatisfied(); + } + + // -- Private helpers + + private function _getTransport(array $transports) + { + $transport = new Swift_Transport_FailoverTransport(); + $transport->setTransports($transports); + + return $transport; + } + + private function _createPlugin($context) + { + return $context->mock('Swift_Events_EventListener'); + } + + private function _createInnerTransport() + { + return $this->_mockery()->mock('Swift_Transport'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/LoadBalancedTransportTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/LoadBalancedTransportTest.php new file mode 100644 index 0000000..794ff3f --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/LoadBalancedTransportTest.php @@ -0,0 +1,419 @@ +mock('Swift_Mime_Message'); + $message2 = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection 1')->startsAs('off'); + $con2 = $context->states('Connection 2')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message1) + -> ignoring($message2) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> allowing($t1)->start() -> when($con1->is('off')) -> then($con1->is('on')) + -> one($t1)->send($message1, optional()) -> returns(1) -> when($con1->is('on')) + -> never($t1)->send($message2, optional()) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> allowing($t2)->start() -> when($con2->is('off')) -> then($con2->is('on')) + -> one($t2)->send($message2, optional()) -> returns(1) -> when($con2->is('on')) + -> never($t2)->send($message1, optional()) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEqual(1, $transport->send($message1)); + $this->assertEqual(1, $transport->send($message2)); + + $context->assertIsSatisfied(); + } + + public function testTransportsAreReusedInRotatingFashion() + { + $context = new Mockery(); + $message1 = $context->mock('Swift_Mime_Message'); + $message2 = $context->mock('Swift_Mime_Message'); + $message3 = $context->mock('Swift_Mime_Message'); + $message4 = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection 1')->startsAs('off'); + $con2 = $context->states('Connection 2')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message1) + -> ignoring($message2) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> allowing($t1)->start() -> when($con1->is('off')) -> then($con1->is('on')) + -> one($t1)->send($message1, optional()) -> returns(1) -> when($con1->is('on')) + -> never($t1)->send($message2, optional()) + -> one($t1)->send($message3, optional()) -> returns(1) -> when($con1->is('on')) + -> never($t1)->send($message4, optional()) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> allowing($t2)->start() -> when($con2->is('off')) -> then($con2->is('on')) + -> one($t2)->send($message2, optional()) -> returns(1) -> when($con2->is('on')) + -> never($t2)->send($message1, optional()) + -> one($t2)->send($message4, optional()) -> returns(1) -> when($con2->is('on')) + -> never($t2)->send($message3, optional()) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + + $this->assertEqual(1, $transport->send($message1)); + $this->assertEqual(1, $transport->send($message2)); + $this->assertEqual(1, $transport->send($message3)); + $this->assertEqual(1, $transport->send($message4)); + + $context->assertIsSatisfied(); + } + + public function testMessageCanBeTriedOnNextTransportIfExceptionThrown() + { + $e = new Swift_TransportException('b0rken'); + + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message, optional()) -> throws($e) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message, optional()) -> returns(1) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEqual(1, $transport->send($message)); + $context->assertIsSatisfied(); + } + + public function testMessageIsTriedOnNextTransportIfZeroReturned() + { + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message, optional()) -> returns(0) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message, optional()) -> returns(1) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEqual(1, $transport->send($message)); + $context->assertIsSatisfied(); + } + + public function testZeroIsReturnedIfAllTransportsReturnZero() + { + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message, optional()) -> returns(0) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message, optional()) -> returns(0) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEqual(0, $transport->send($message)); + $context->assertIsSatisfied(); + } + + public function testTransportsWhichThrowExceptionsAreNotRetried() + { + $e = new Swift_TransportException('maur b0rken'); + + $context = new Mockery(); + $message1 = $context->mock('Swift_Mime_Message'); + $message2 = $context->mock('Swift_Mime_Message'); + $message3 = $context->mock('Swift_Mime_Message'); + $message4 = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message1) + -> ignoring($message2) + -> ignoring($message3) + -> ignoring($message4) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message1, optional()) -> throws($e) -> when($con1->is('on')) + -> never($t1)->send($message2, optional()) + -> never($t1)->send($message3, optional()) + -> never($t1)->send($message4, optional()) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message1, optional()) -> returns(1) -> when($con2->is('on')) + -> one($t2)->send($message2, optional()) -> returns(1) -> when($con2->is('on')) + -> one($t2)->send($message3, optional()) -> returns(1) -> when($con2->is('on')) + -> one($t2)->send($message4, optional()) -> returns(1) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEqual(1, $transport->send($message1)); + $this->assertEqual(1, $transport->send($message2)); + $this->assertEqual(1, $transport->send($message3)); + $this->assertEqual(1, $transport->send($message4)); + } + + public function testExceptionIsThrownIfAllTransportsDie() + { + $e = new Swift_TransportException('b0rken'); + + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message, optional()) -> throws($e) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message, optional()) -> throws($e) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + try { + $transport->send($message); + $this->fail('All transports failed so Exception should be thrown'); + } catch (Exception $e) { + } + $context->assertIsSatisfied(); + } + + public function testStoppingTransportStopsAllDelegates() + { + $context = new Mockery(); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('on'); + $con2 = $context->states('Connection')->startsAs('on'); + $context->checking(Expectations::create() + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->stop() -> when($con1->is('on')) -> then($con1->is('off')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->stop() -> when($con2->is('on')) -> then($con2->is('off')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $transport->stop(); + $context->assertIsSatisfied(); + } + + public function testTransportShowsAsNotStartedIfAllDelegatesDead() + { + $e = new Swift_TransportException('b0rken'); + + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> one($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message, optional()) -> throws($e) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message, optional()) -> throws($e) -> when($con2->is('on')) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertTrue($transport->isStarted()); + try { + $transport->send($message); + $this->fail('All transports failed so Exception should be thrown'); + } catch (Exception $e) { + $this->assertFalse($transport->isStarted()); + } + $context->assertIsSatisfied(); + } + + public function testRestartingTransportRestartsDeadDelegates() + { + $e = new Swift_TransportException('b0rken'); + + $context = new Mockery(); + $message1 = $context->mock('Swift_Mime_Message'); + $message2 = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $con1 = $context->states('Connection')->startsAs('off'); + $con2 = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message1) + -> ignoring($message2) + -> allowing($t1)->isStarted() -> returns(false) -> when($con1->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con1->is('on')) + -> exactly(2)->of($t1)->start() -> when($con1->isNot('on')) -> then($con1->is('on')) + -> one($t1)->send($message1, optional()) -> throws($e) -> when($con1->is('on')) -> then($con1->is('off')) + -> one($t1)->send($message2, optional()) -> returns(10) -> when($con1->is('on')) + -> ignoring($t1) + -> allowing($t2)->isStarted() -> returns(false) -> when($con2->is('off')) + -> allowing($t2)->isStarted() -> returns(true) -> when($con2->is('on')) + -> one($t2)->start() -> when($con2->isNot('on')) -> then($con2->is('on')) + -> one($t2)->send($message1, optional()) -> throws($e) -> when($con2->is('on')) + -> never($t2)->send($message2, optional()) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertTrue($transport->isStarted()); + try { + $transport->send($message1); + $this->fail('All transports failed so Exception should be thrown'); + } catch (Exception $e) { + $this->assertFalse($transport->isStarted()); + } + //Restart and re-try + $transport->start(); + $this->assertTrue($transport->isStarted()); + $this->assertEqual(10, $transport->send($message2)); + $context->assertIsSatisfied(); + } + + public function testFailureReferenceIsPassedToDelegates() + { + $failures = array(); + + $context = new Mockery(); + $message = $context->mock('Swift_Mime_Message'); + $t1 = $context->mock('Swift_Transport'); + $con = $context->states('Connection')->startsAs('off'); + $context->checking(Expectations::create() + -> ignoring($message) + -> allowing($t1)->isStarted() -> returns(false) -> when($con->is('off')) + -> allowing($t1)->isStarted() -> returns(true) -> when($con->is('on')) + -> one($t1)->start() -> when($con->isNot('on')) -> then($con->is('on')) + -> one($t1)->send($message, reference($failures)) -> returns(1) -> when($con->is('on')) + -> ignoring($t1) + ); + + $transport = $this->_getTransport(array($t1)); + $transport->start(); + $transport->send($message, $failures); + $context->assertIsSatisfied(); + } + + public function testRegisterPluginDelegatesToLoadedTransports() + { + $context = new Mockery(); + + $plugin = $this->_createPlugin($context); + + $t1 = $context->mock('Swift_Transport'); + $t2 = $context->mock('Swift_Transport'); + $context->checking(Expectations::create() + -> one($t1)->registerPlugin($plugin) + -> one($t2)->registerPlugin($plugin) + -> ignoring($t1) + -> ignoring($t2) + ); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->registerPlugin($plugin); + + $context->assertIsSatisfied(); + } + + // -- Private helpers + + private function _getTransport(array $transports) + { + $transport = new Swift_Transport_LoadBalancedTransport(); + $transport->setTransports($transports); + + return $transport; + } + + private function _createPlugin($context) + { + return $context->mock('Swift_Events_EventListener'); + } + + private function _createInnerTransport() + { + return $this->_mockery()->mock('Swift_Transport'); + } + +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/MailTransportTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/MailTransportTest.php new file mode 100644 index 0000000..90833cf --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/MailTransportTest.php @@ -0,0 +1,315 @@ +_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> one($invoker)->mail(any(), any(), any(), any(), optional()) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + ); + + $transport->send($message); + } + + public function testTransportUsesToFieldBodyInSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $to = $this->_createHeader(); + $headers = $this->_createHeaders(array( + 'To' => $to + )); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> allowing($to)->getFieldBody() -> returns("Foo ") + -> one($invoker)->mail("Foo ", any(), any(), any(), optional()) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + -> ignoring($to) + ); + + $transport->send($message); + } + + public function testTransportUsesSubjectFieldBodyInSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $subj = $this->_createHeader(); + $headers = $this->_createHeaders(array( + 'Subject' => $subj + )); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> allowing($subj)->getFieldBody() -> returns("Thing") + -> one($invoker)->mail(any(), "Thing", any(), any(), optional()) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + -> ignoring($subj) + ); + + $transport->send($message); + } + + public function testTransportUsesBodyOfMessage() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> allowing($message)->toString() -> returns( + "To: Foo \r\n" . + "\r\n" . + "This body" + ) + -> one($invoker)->mail(any(), any(), "This body", any(), optional()) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + ); + + $transport->send($message); + } + + public function testTransportUsesHeadersFromMessage() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> allowing($message)->toString() -> returns( + "Subject: Stuff\r\n" . + "\r\n" . + "This body" + ) + -> one($invoker)->mail(any(), any(), any(), "Subject: Stuff" . PHP_EOL, optional()) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + ); + + $transport->send($message); + } + + public function testTransportReturnsCountOfAllRecipientsIfInvokerReturnsTrue() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null, 'zip@button'=>null)) + -> allowing($message)->getCc() -> returns(array('test@test'=>null)) + -> one($invoker)->mail(any(), any(), any(), any(), optional()) -> returns(true) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + ); + + $this->assertEqual(3, $transport->send($message)); + } + + public function testTransportReturnsZeroIfInvokerReturnsFalse() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array('foo@bar'=>null, 'zip@button'=>null)) + -> allowing($message)->getCc() -> returns(array('test@test'=>null)) + -> one($invoker)->mail(any(), any(), any(), any(), optional()) -> returns(false) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + ); + + $this->assertEqual(0, $transport->send($message)); + } + + public function testToHeaderIsRemovedFromHeaderSetDuringSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $to = $this->_createHeader(); + $headers = $this->_createHeaders(array( + 'To' => $to + )); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> one($headers)->remove('To') + -> one($invoker)->mail(any(), any(), any(), any(), optional()) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + -> ignoring($to) + ); + + $transport->send($message); + } + + public function testSubjectHeaderIsRemovedFromHeaderSetDuringSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $subject = $this->_createHeader(); + $headers = $this->_createHeaders(array( + 'Subject' => $subject + )); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> one($headers)->remove('Subject') + -> one($invoker)->mail(any(), any(), any(), any(), optional()) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + -> ignoring($subject) + ); + + $transport->send($message); + } + + public function testToHeaderIsPutBackAfterSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $to = $this->_createHeader(); + $headers = $this->_createHeaders(array( + 'To' => $to + )); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> one($headers)->set($to, optional()) + -> one($invoker)->mail(any(), any(), any(), any(), optional()) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + -> ignoring($to) + ); + + $transport->send($message); + } + + public function testSubjectHeaderIsPutBackAfterSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $subject = $this->_createHeader(); + $headers = $this->_createHeaders(array( + 'Subject' => $subject + )); + $message = $this->_createMessage($headers); + + $this->_checking(Expectations::create() + -> one($headers)->set($subject, optional()) + -> one($invoker)->mail(any(), any(), any(), any(), optional()) + -> ignoring($dispatcher) + -> ignoring($headers) + -> ignoring($message) + -> ignoring($subject) + ); + + $transport->send($message); + } + + // -- Creation Methods + + private function _createTransport($invoker, $dispatcher) + { + return new Swift_Transport_MailTransport($invoker, $dispatcher); + } + + private function _createEventDispatcher() + { + return $this->_mock('Swift_Events_EventDispatcher'); + } + + private function _createInvoker() + { + return $this->_mock('Swift_Transport_MailInvoker'); + } + + private function _createMessage($headers) + { + $message = $this->_mock('Swift_Mime_Message'); + + $this->_checking(Expectations::create() + -> allowing($message)->getHeaders() -> returns($headers) + ); + + return $message; + } + + private function _createHeaders($headers = array()) + { + $set = $this->_mock('Swift_Mime_HeaderSet'); + + if (count($headers) > 0) { + foreach ($headers as $name => $header) { + $this->_checking(Expectations::create() + -> allowing($set)->get($name) -> returns($header) + -> allowing($set)->has($name) -> returns(true) + ); + } + } + + $header = $this->_createHeader(); + $this->_checking(Expectations::create() + -> allowing($set)->get(any()) -> returns($header) + -> allowing($set)->has(any()) -> returns(true) + -> ignoring($header) + ); + + return $set; + } + + private function _createHeader() + { + return $this->_mock('Swift_Mime_Header'); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/SendmailTransportTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/SendmailTransportTest.php new file mode 100644 index 0000000..558f414 --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/SendmailTransportTest.php @@ -0,0 +1,135 @@ +_createEventDispatcher(); + } + $transport = new Swift_Transport_SendmailTransport($buf, $dispatcher); + $transport->setCommand($command); + + return $transport; + } + + protected function _getSendmail($buf, $dispatcher = null) + { + if (!$dispatcher) { + $dispatcher = $this->_createEventDispatcher(); + } + $sendmail = new Swift_Transport_SendmailTransport($buf, $dispatcher); + + return $sendmail; + } + + public function testCommandCanBeSetAndFetched() + { + $buf = $this->_getBuffer(); + $sendmail = $this->_getSendmail($buf); + + $sendmail->setCommand('/usr/sbin/sendmail -bs'); + $this->assertEqual('/usr/sbin/sendmail -bs', $sendmail->getCommand()); + $sendmail->setCommand('/usr/sbin/sendmail -oi -t'); + $this->assertEqual('/usr/sbin/sendmail -oi -t', $sendmail->getCommand()); + } + + public function testSendingMessageIn_t_ModeUsesSimplePipe() + { + $buf = $this->_getBuffer(); + $sendmail = $this->_getSendmail($buf); + $message = $this->_createMessage(); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array('foo@bar'=>'Foobar', 'zip@button'=>'Zippy')) + -> one($message)->toByteStream($buf) + -> ignoring($message) + -> one($buf)->initialize() + -> one($buf)->terminate() + -> one($buf)->setWriteTranslations(array("\r\n"=>"\n", "\n." => "\n..")) + -> one($buf)->setWriteTranslations(array()) + -> ignoring($buf) + ); + + $sendmail->setCommand('/usr/sbin/sendmail -t'); + $this->assertEqual(2, $sendmail->send($message)); + } + + public function testSendingIn_t_ModeWith_i_FlagDoesntEscapeDot() + { + $buf = $this->_getBuffer(); + $sendmail = $this->_getSendmail($buf); + $message = $this->_createMessage(); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array('foo@bar'=>'Foobar', 'zip@button'=>'Zippy')) + -> one($message)->toByteStream($buf) + -> ignoring($message) + -> one($buf)->initialize() + -> one($buf)->terminate() + -> one($buf)->setWriteTranslations(array("\r\n"=>"\n")) + -> one($buf)->setWriteTranslations(array()) + -> ignoring($buf) + ); + + $sendmail->setCommand('/usr/sbin/sendmail -i -t'); + $this->assertEqual(2, $sendmail->send($message)); + } + + public function testSendingInTModeWith_oi_FlagDoesntEscapeDot() + { + $buf = $this->_getBuffer(); + $sendmail = $this->_getSendmail($buf); + $message = $this->_createMessage(); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array('foo@bar'=>'Foobar', 'zip@button'=>'Zippy')) + -> one($message)->toByteStream($buf) + -> ignoring($message) + -> one($buf)->initialize() + -> one($buf)->terminate() + -> one($buf)->setWriteTranslations(array("\r\n"=>"\n")) + -> one($buf)->setWriteTranslations(array()) + -> ignoring($buf) + ); + + $sendmail->setCommand('/usr/sbin/sendmail -oi -t'); + $this->assertEqual(2, $sendmail->send($message)); + } + + public function testSendingMessageRegeneratesId() + { + $buf = $this->_getBuffer(); + $sendmail = $this->_getSendmail($buf); + $message = $this->_createMessage(); + + $this->_checking(Expectations::create() + -> allowing($message)->getTo() -> returns(array('foo@bar'=>'Foobar', 'zip@button'=>'Zippy')) + -> one($message)->generateId() + -> ignoring($message) + -> one($buf)->initialize() + -> one($buf)->terminate() + -> one($buf)->setWriteTranslations(array("\r\n"=>"\n", "\n." => "\n..")) + -> one($buf)->setWriteTranslations(array()) + -> ignoring($buf) + ); + + $sendmail->setCommand('/usr/sbin/sendmail -t'); + $this->assertEqual(2, $sendmail->send($message)); + } + + public function testFluidInterface() + { + $buf = $this->_getBuffer(); + $sendmail = $this->_getTransport($buf); + + $ref = $sendmail->setCommand('/foo'); + $this->assertReference($ref, $sendmail); + } +} diff --git a/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/StreamBufferTest.php b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/StreamBufferTest.php new file mode 100644 index 0000000..8b225af --- /dev/null +++ b/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/StreamBufferTest.php @@ -0,0 +1,50 @@ +_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createFilter('a', 'b') -> returns($this->_createFilter()) + -> never($factory) + ); + $buffer = $this->_createBuffer($factory); + $buffer->setWriteTranslations(array('a' => 'b')); + } + + public function testOverridingTranslationsOnlyAddsNeededFilters() + { + $factory = $this->_createFactory(); + $this->_checking(Expectations::create() + -> one($factory)->createFilter('a', 'b') -> returns($this->_createFilter()) + -> one($factory)->createFilter('x', 'y') -> returns($this->_createFilter()) + -> never($factory) + ); + $buffer = $this->_createBuffer($factory); + $buffer->setWriteTranslations(array('a' => 'b')); + $buffer->setWriteTranslations(array('x' => 'y', 'a' => 'b')); + } + + // -- Creation methods + + private function _createBuffer($replacementFactory) + { + return new Swift_Transport_StreamBuffer($replacementFactory); + } + + private function _createFactory() + { + return $this->_mock('Swift_ReplacementFilterFactory'); + } + + private function _createFilter() + { + return $this->_stub('Swift_StreamFilter'); + } +} diff --git a/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/.gitignore b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/CHANGELOG.md b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/CHANGELOG.md new file mode 100644 index 0000000..d2b1074 --- /dev/null +++ b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/CHANGELOG.md @@ -0,0 +1,18 @@ +CHANGELOG +========= + +2.3.0 +----- + + * [BC BREAK] `Client::followRedirect()` won't redirect responses with + a non-3xx Status Code and `Location` header anymore, as per + http://tools.ietf.org/html/rfc2616#section-14.30 + + * added `Client::getInternalRequest()` and `Client::getInternalResponse()` to + have access to the BrowserKit internal request and response objects + +2.1.0 +----- + + * [BC BREAK] The CookieJar internals have changed to allow cookies with the + same name on different sub-domains/sub-paths diff --git a/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Client.php b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Client.php new file mode 100644 index 0000000..6c3cfb7 --- /dev/null +++ b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Client.php @@ -0,0 +1,585 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit; + +use Symfony\Component\DomCrawler\Crawler; +use Symfony\Component\DomCrawler\Link; +use Symfony\Component\DomCrawler\Form; +use Symfony\Component\Process\PhpProcess; + +/** + * Client simulates a browser. + * + * To make the actual request, you need to implement the doRequest() method. + * + * If you want to be able to run requests in their own process (insulated flag), + * you need to also implement the getScript() method. + * + * @author Fabien Potencier + * + * @api + */ +abstract class Client +{ + protected $history; + protected $cookieJar; + protected $server; + protected $internalRequest; + protected $request; + protected $internalResponse; + protected $response; + protected $crawler; + protected $insulated; + protected $redirect; + protected $followRedirects; + + private $maxRedirects; + private $redirectCount; + private $isMainRequest; + + /** + * Constructor. + * + * @param array $server The server parameters (equivalent of $_SERVER) + * @param History $history A History instance to store the browser history + * @param CookieJar $cookieJar A CookieJar instance to store the cookies + * + * @api + */ + public function __construct(array $server = array(), History $history = null, CookieJar $cookieJar = null) + { + $this->setServerParameters($server); + $this->history = null === $history ? new History() : $history; + $this->cookieJar = null === $cookieJar ? new CookieJar() : $cookieJar; + $this->insulated = false; + $this->followRedirects = true; + $this->maxRedirects = -1; + $this->redirectCount = 0; + $this->isMainRequest = true; + } + + /** + * Sets whether to automatically follow redirects or not. + * + * @param Boolean $followRedirect Whether to follow redirects + * + * @api + */ + public function followRedirects($followRedirect = true) + { + $this->followRedirects = (Boolean) $followRedirect; + } + + /** + * Sets the maximum number of requests that crawler can follow. + * + * @param integer $maxRedirects + */ + public function setMaxRedirects($maxRedirects) + { + $this->maxRedirects = $maxRedirects < 0 ? -1 : $maxRedirects; + $this->followRedirects = -1 != $this->maxRedirects; + } + + /** + * Sets the insulated flag. + * + * @param Boolean $insulated Whether to insulate the requests or not + * + * @throws \RuntimeException When Symfony Process Component is not installed + * + * @api + */ + public function insulate($insulated = true) + { + if ($insulated && !class_exists('Symfony\\Component\\Process\\Process')) { + // @codeCoverageIgnoreStart + throw new \RuntimeException('Unable to isolate requests as the Symfony Process Component is not installed.'); + // @codeCoverageIgnoreEnd + } + + $this->insulated = (Boolean) $insulated; + } + + /** + * Sets server parameters. + * + * @param array $server An array of server parameters + * + * @api + */ + public function setServerParameters(array $server) + { + $this->server = array_merge(array( + 'HTTP_HOST' => 'localhost', + 'HTTP_USER_AGENT' => 'Symfony2 BrowserKit', + ), $server); + } + + /** + * Sets single server parameter. + * + * @param string $key A key of the parameter + * @param string $value A value of the parameter + */ + public function setServerParameter($key, $value) + { + $this->server[$key] = $value; + } + + /** + * Gets single server parameter for specified key. + * + * @param string $key A key of the parameter to get + * @param string $default A default value when key is undefined + * + * @return string A value of the parameter + */ + public function getServerParameter($key, $default = '') + { + return (isset($this->server[$key])) ? $this->server[$key] : $default; + } + + /** + * Returns the History instance. + * + * @return History A History instance + * + * @api + */ + public function getHistory() + { + return $this->history; + } + + /** + * Returns the CookieJar instance. + * + * @return CookieJar A CookieJar instance + * + * @api + */ + public function getCookieJar() + { + return $this->cookieJar; + } + + /** + * Returns the current Crawler instance. + * + * @return Crawler|null A Crawler instance + * + * @api + */ + public function getCrawler() + { + return $this->crawler; + } + + /** + * Returns the current BrowserKit Response instance. + * + * @return Response|null A BrowserKit Response instance + * + * @api + */ + public function getInternalResponse() + { + return $this->internalResponse; + } + + /** + * Returns the current origin response instance. + * + * The origin response is the response instance that is returned + * by the code that handles requests. + * + * @return object|null A response instance + * + * @see doRequest + * + * @api + */ + public function getResponse() + { + return $this->response; + } + + /** + * Returns the current BrowserKit Request instance. + * + * @return Request|null A BrowserKit Request instance + * + * @api + */ + public function getInternalRequest() + { + return $this->internalRequest; + } + + /** + * Returns the current origin Request instance. + * + * The origin request is the request instance that is sent + * to the code that handles requests. + * + * @return object|null A Request instance + * + * @see doRequest + * + * @api + */ + public function getRequest() + { + return $this->request; + } + + /** + * Clicks on a given link. + * + * @param Link $link A Link instance + * + * @return Crawler + * + * @api + */ + public function click(Link $link) + { + if ($link instanceof Form) { + return $this->submit($link); + } + + return $this->request($link->getMethod(), $link->getUri()); + } + + /** + * Submits a form. + * + * @param Form $form A Form instance + * @param array $values An array of form field values + * + * @return Crawler + * + * @api + */ + public function submit(Form $form, array $values = array()) + { + $form->setValues($values); + + return $this->request($form->getMethod(), $form->getUri(), $form->getPhpValues(), $form->getPhpFiles()); + } + + /** + * Calls a URI. + * + * @param string $method The request method + * @param string $uri The URI to fetch + * @param array $parameters The Request parameters + * @param array $files The files + * @param array $server The server parameters (HTTP headers are referenced with a HTTP_ prefix as PHP does) + * @param string $content The raw body data + * @param Boolean $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload()) + * + * @return Crawler + * + * @api + */ + public function request($method, $uri, array $parameters = array(), array $files = array(), array $server = array(), $content = null, $changeHistory = true) + { + if ($this->isMainRequest) { + $this->redirectCount = 0; + } else { + ++$this->redirectCount; + } + + $uri = $this->getAbsoluteUri($uri); + + $server = array_merge($this->server, $server); + if (!$this->history->isEmpty()) { + $server['HTTP_REFERER'] = $this->history->current()->getUri(); + } + $server['HTTP_HOST'] = parse_url($uri, PHP_URL_HOST); + $server['HTTPS'] = 'https' == parse_url($uri, PHP_URL_SCHEME); + + $this->internalRequest = new Request($uri, $method, $parameters, $files, $this->cookieJar->allValues($uri), $server, $content); + + $this->request = $this->filterRequest($this->internalRequest); + + if (true === $changeHistory) { + $this->history->add($this->internalRequest); + } + + if ($this->insulated) { + $this->response = $this->doRequestInProcess($this->request); + } else { + $this->response = $this->doRequest($this->request); + } + + $this->internalResponse = $this->filterResponse($this->response); + + $this->cookieJar->updateFromResponse($this->internalResponse, $uri); + + $status = $this->internalResponse->getStatus(); + + if ($status >= 300 && $status < 400) { + $this->redirect = $this->internalResponse->getHeader('Location'); + } else { + $this->redirect = null; + } + + if ($this->followRedirects && $this->redirect) { + return $this->crawler = $this->followRedirect(); + } + + return $this->crawler = $this->createCrawlerFromContent($this->internalRequest->getUri(), $this->internalResponse->getContent(), $this->internalResponse->getHeader('Content-Type')); + } + + /** + * Makes a request in another process. + * + * @param object $request An origin request instance + * + * @return object An origin response instance + * + * @throws \RuntimeException When processing returns exit code + */ + protected function doRequestInProcess($request) + { + // We set the TMPDIR (for Macs) and TEMP (for Windows), because on these platforms the temp directory changes based on the user. + $process = new PhpProcess($this->getScript($request), null, array('TMPDIR' => sys_get_temp_dir(), 'TEMP' => sys_get_temp_dir())); + $process->run(); + + if (!$process->isSuccessful() || !preg_match('/^O\:\d+\:/', $process->getOutput())) { + throw new \RuntimeException(sprintf('OUTPUT: %s ERROR OUTPUT: %s', $process->getOutput(), $process->getErrorOutput())); + } + + return unserialize($process->getOutput()); + } + + /** + * Makes a request. + * + * @param object $request An origin request instance + * + * @return object An origin response instance + */ + abstract protected function doRequest($request); + + /** + * Returns the script to execute when the request must be insulated. + * + * @param object $request An origin request instance + * + * @throws \LogicException When this abstract class is not implemented + */ + protected function getScript($request) + { + // @codeCoverageIgnoreStart + throw new \LogicException('To insulate requests, you need to override the getScript() method.'); + // @codeCoverageIgnoreEnd + } + + /** + * Filters the BrowserKit request to the origin one. + * + * @param Request $request The BrowserKit Request to filter + * + * @return object An origin request instance + */ + protected function filterRequest(Request $request) + { + return $request; + } + + /** + * Filters the origin response to the BrowserKit one. + * + * @param object $response The origin response to filter + * + * @return Response An BrowserKit Response instance + */ + protected function filterResponse($response) + { + return $response; + } + + /** + * Creates a crawler. + * + * This method returns null if the DomCrawler component is not available. + * + * @param string $uri A uri + * @param string $content Content for the crawler to use + * @param string $type Content type + * + * @return Crawler|null + */ + protected function createCrawlerFromContent($uri, $content, $type) + { + if (!class_exists('Symfony\Component\DomCrawler\Crawler')) { + return null; + } + + $crawler = new Crawler(null, $uri); + $crawler->addContent($content, $type); + + return $crawler; + } + + /** + * Goes back in the browser history. + * + * @return Crawler + * + * @api + */ + public function back() + { + return $this->requestFromRequest($this->history->back(), false); + } + + /** + * Goes forward in the browser history. + * + * @return Crawler + * + * @api + */ + public function forward() + { + return $this->requestFromRequest($this->history->forward(), false); + } + + /** + * Reloads the current browser. + * + * @return Crawler + * + * @api + */ + public function reload() + { + return $this->requestFromRequest($this->history->current(), false); + } + + /** + * Follow redirects? + * + * @return Crawler + * + * @throws \LogicException If request was not a redirect + * + * @api + */ + public function followRedirect() + { + if (empty($this->redirect)) { + throw new \LogicException('The request was not redirected.'); + } + + if (-1 !== $this->maxRedirects) { + if ($this->redirectCount > $this->maxRedirects) { + throw new \LogicException(sprintf('The maximum number (%d) of redirections was reached.', $this->maxRedirects)); + } + } + + $request = $this->internalRequest; + + if (in_array($this->internalResponse->getStatus(), array(302, 303))) { + $method = 'get'; + $files = array(); + $content = null; + } else { + $method = $request->getMethod(); + $files = $request->getFiles(); + $content = $request->getContent(); + } + + $server = $request->getServer(); + unset($server['HTTP_IF_NONE_MATCH'], $server['HTTP_IF_MODIFIED_SINCE']); + + $this->isMainRequest = false; + + $response = $this->request($method, $this->redirect, $request->getParameters(), $files, $server, $content); + + $this->isMainRequest = true; + + return $response; + } + + /** + * Restarts the client. + * + * It flushes history and all cookies. + * + * @api + */ + public function restart() + { + $this->cookieJar->clear(); + $this->history->clear(); + } + + /** + * Takes a URI and converts it to absolute if it is not already absolute. + * + * @param string $uri A uri + * + * @return string An absolute uri + */ + protected function getAbsoluteUri($uri) + { + // already absolute? + if (0 === strpos($uri, 'http')) { + return $uri; + } + + if (!$this->history->isEmpty()) { + $currentUri = $this->history->current()->getUri(); + } else { + $currentUri = sprintf('http%s://%s/', + isset($this->server['HTTPS']) ? 's' : '', + isset($this->server['HTTP_HOST']) ? $this->server['HTTP_HOST'] : 'localhost' + ); + } + + // anchor? + if (!$uri || '#' == $uri[0]) { + return preg_replace('/#.*?$/', '', $currentUri).$uri; + } + + if ('/' !== $uri[0]) { + $path = parse_url($currentUri, PHP_URL_PATH); + + if ('/' !== substr($path, -1)) { + $path = substr($path, 0, strrpos($path, '/') + 1); + } + + $uri = $path.$uri; + } + + return preg_replace('#^(.*?//[^/]+)\/.*$#', '$1', $currentUri).$uri; + } + + /** + * Makes a request from a Request object directly. + * + * @param Request $request A Request instance + * @param Boolean $changeHistory Whether to update the history or not (only used internally for back(), forward(), and reload()) + * + * @return Crawler + */ + protected function requestFromRequest(Request $request, $changeHistory = true) + { + return $this->request($request->getMethod(), $request->getUri(), $request->getParameters(), $request->getFiles(), $request->getServer(), $request->getContent(), $changeHistory); + } +} diff --git a/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Cookie.php b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Cookie.php new file mode 100644 index 0000000..1e2c64c --- /dev/null +++ b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Cookie.php @@ -0,0 +1,324 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit; + +/** + * Cookie represents an HTTP cookie. + * + * @author Fabien Potencier + * + * @api + */ +class Cookie +{ + /** + * Handles dates as defined by RFC 2616 section 3.3.1, and also some other + * non-standard, but common formats. + * + * @var array + */ + private static $dateFormats = array( + 'D, d M Y H:i:s T', + 'D, d-M-y H:i:s T', + 'D, d-M-Y H:i:s T', + 'D, d-m-y H:i:s T', + 'D, d-m-Y H:i:s T', + 'D M j G:i:s Y', + 'D M d H:i:s Y T', + ); + + protected $name; + protected $value; + protected $expires; + protected $path; + protected $domain; + protected $secure; + protected $httponly; + protected $rawValue; + + /** + * Sets a cookie. + * + * @param string $name The cookie name + * @param string $value The value of the cookie + * @param string $expires The time the cookie expires + * @param string $path The path on the server in which the cookie will be available on + * @param string $domain The domain that the cookie is available + * @param Boolean $secure Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client + * @param Boolean $httponly The cookie httponly flag + * @param Boolean $encodedValue Whether the value is encoded or not + * + * @api + */ + public function __construct($name, $value, $expires = null, $path = null, $domain = '', $secure = false, $httponly = true, $encodedValue = false) + { + if ($encodedValue) { + $this->value = urldecode($value); + $this->rawValue = $value; + } else { + $this->value = $value; + $this->rawValue = urlencode($value); + } + $this->name = $name; + $this->expires = null === $expires ? null : (integer) $expires; + $this->path = empty($path) ? '/' : $path; + $this->domain = $domain; + $this->secure = (Boolean) $secure; + $this->httponly = (Boolean) $httponly; + } + + /** + * Returns the HTTP representation of the Cookie. + * + * @return string The HTTP representation of the Cookie + * + * @api + */ + public function __toString() + { + $cookie = sprintf('%s=%s', $this->name, $this->rawValue); + + if (null !== $this->expires) { + $cookie .= '; expires='.substr(\DateTime::createFromFormat('U', $this->expires, new \DateTimeZone('GMT'))->format(self::$dateFormats[0]), 0, -5); + } + + if ('' !== $this->domain) { + $cookie .= '; domain='.$this->domain; + } + + if ($this->path) { + $cookie .= '; path='.$this->path; + } + + if ($this->secure) { + $cookie .= '; secure'; + } + + if ($this->httponly) { + $cookie .= '; httponly'; + } + + return $cookie; + } + + /** + * Creates a Cookie instance from a Set-Cookie header value. + * + * @param string $cookie A Set-Cookie header value + * @param string $url The base URL + * + * @return Cookie A Cookie instance + * + * @throws \InvalidArgumentException + * + * @api + */ + public static function fromString($cookie, $url = null) + { + $parts = explode(';', $cookie); + + if (false === strpos($parts[0], '=')) { + throw new \InvalidArgumentException('The cookie string "%s" is not valid.'); + } + + list($name, $value) = explode('=', array_shift($parts), 2); + + $values = array( + 'name' => trim($name), + 'value' => trim($value), + 'expires' => null, + 'path' => '/', + 'domain' => '', + 'secure' => false, + 'httponly' => false, + 'passedRawValue' => true, + ); + + if (null !== $url) { + if ((false === $urlParts = parse_url($url)) || !isset($urlParts['host']) || !isset($urlParts['path'])) { + throw new \InvalidArgumentException(sprintf('The URL "%s" is not valid.', $url)); + } + + $values['domain'] = $urlParts['host']; + $values['path'] = substr($urlParts['path'], 0, strrpos($urlParts['path'], '/')); + } + + foreach ($parts as $part) { + $part = trim($part); + + if ('secure' === strtolower($part)) { + // Ignore the secure flag if the original URI is not given or is not HTTPS + if (!$url || !isset($urlParts['scheme']) || 'https' != $urlParts['scheme']) { + continue; + } + + $values['secure'] = true; + + continue; + } + + if ('httponly' === strtolower($part)) { + $values['httponly'] = true; + + continue; + } + + if (2 === count($elements = explode('=', $part, 2))) { + if ('expires' === strtolower($elements[0])) { + $elements[1] = self::parseDate($elements[1]); + } + + $values[strtolower($elements[0])] = $elements[1]; + } + } + + return new static( + $values['name'], + $values['value'], + $values['expires'], + $values['path'], + $values['domain'], + $values['secure'], + $values['httponly'], + $values['passedRawValue'] + ); + } + + private static function parseDate($dateValue) + { + // trim single quotes around date if present + if (($length = strlen($dateValue)) > 1 && "'" === $dateValue[0] && "'" === $dateValue[$length-1]) { + $dateValue = substr($dateValue, 1, -1); + } + + foreach (self::$dateFormats as $dateFormat) { + if (false !== $date = \DateTime::createFromFormat($dateFormat, $dateValue, new \DateTimeZone('GMT'))) { + return $date->getTimestamp(); + } + } + + // attempt a fallback for unusual formatting + if (false !== $date = date_create($dateValue, new \DateTimeZone('GMT'))) { + return $date->getTimestamp(); + } + + throw new \InvalidArgumentException(sprintf('Could not parse date "%s".', $dateValue)); + } + + /** + * Gets the name of the cookie. + * + * @return string The cookie name + * + * @api + */ + public function getName() + { + return $this->name; + } + + /** + * Gets the value of the cookie. + * + * @return string The cookie value + * + * @api + */ + public function getValue() + { + return $this->value; + } + + /** + * Gets the raw value of the cookie. + * + * @return string The cookie value + * + * @api + */ + public function getRawValue() + { + return $this->rawValue; + } + + /** + * Gets the expires time of the cookie. + * + * @return string The cookie expires time + * + * @api + */ + public function getExpiresTime() + { + return $this->expires; + } + + /** + * Gets the path of the cookie. + * + * @return string The cookie path + * + * @api + */ + public function getPath() + { + return $this->path; + } + + /** + * Gets the domain of the cookie. + * + * @return string The cookie domain + * + * @api + */ + public function getDomain() + { + return $this->domain; + } + + /** + * Returns the secure flag of the cookie. + * + * @return Boolean The cookie secure flag + * + * @api + */ + public function isSecure() + { + return $this->secure; + } + + /** + * Returns the httponly flag of the cookie. + * + * @return Boolean The cookie httponly flag + * + * @api + */ + public function isHttpOnly() + { + return $this->httponly; + } + + /** + * Returns true if the cookie has expired. + * + * @return Boolean true if the cookie has expired, false otherwise + * + * @api + */ + public function isExpired() + { + return null !== $this->expires && 0 !== $this->expires && $this->expires < time(); + } +} diff --git a/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/CookieJar.php b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/CookieJar.php new file mode 100644 index 0000000..e6b61d2 --- /dev/null +++ b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/CookieJar.php @@ -0,0 +1,249 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit; + +/** + * CookieJar. + * + * @author Fabien Potencier + * + * @api + */ +class CookieJar +{ + protected $cookieJar = array(); + + /** + * Sets a cookie. + * + * @param Cookie $cookie A Cookie instance + * + * @api + */ + public function set(Cookie $cookie) + { + $this->cookieJar[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; + } + + /** + * Gets a cookie by name. + * + * You should never use an empty domain, but if you do so, + * this method returns the first cookie for the given name/path + * (this behavior ensures a BC behavior with previous versions of + * Symfony). + * + * @param string $name The cookie name + * @param string $path The cookie path + * @param string $domain The cookie domain + * + * @return Cookie|null A Cookie instance or null if the cookie does not exist + * + * @api + */ + public function get($name, $path = '/', $domain = null) + { + $this->flushExpiredCookies(); + + if (!empty($domain)) { + return isset($this->cookieJar[$domain][$path][$name]) ? $this->cookieJar[$domain][$path][$name] : null; + } + + // avoid relying on this behavior that is mainly here for BC reasons + foreach ($this->cookieJar as $cookies) { + if (isset($cookies[$path][$name])) { + return $cookies[$path][$name]; + } + } + + return null; + } + + /** + * Removes a cookie by name. + * + * You should never use an empty domain, but if you do so, + * all cookies for the given name/path expire (this behavior + * ensures a BC behavior with previous versions of Symfony). + * + * @param string $name The cookie name + * @param string $path The cookie path + * @param string $domain The cookie domain + * + * @api + */ + public function expire($name, $path = '/', $domain = null) + { + if (null === $path) { + $path = '/'; + } + + if (empty($domain)) { + // an empty domain means any domain + // this should never happen but it allows for a better BC + $domains = array_keys($this->cookieJar); + } else { + $domains = array($domain); + } + + foreach ($domains as $domain) { + unset($this->cookieJar[$domain][$path][$name]); + + if (empty($this->cookieJar[$domain][$path])) { + unset($this->cookieJar[$domain][$path]); + + if (empty($this->cookieJar[$domain])) { + unset($this->cookieJar[$domain]); + } + } + } + } + + /** + * Removes all the cookies from the jar. + * + * @api + */ + public function clear() + { + $this->cookieJar = array(); + } + + /** + * Updates the cookie jar from a response Set-Cookie headers. + * + * @param array $setCookies Set-Cookie headers from an HTTP response + * @param string $uri The base URL + */ + public function updateFromSetCookie(array $setCookies, $uri = null) + { + $cookies = array(); + + foreach ($setCookies as $cookie) { + foreach (explode(',', $cookie) as $i => $part) { + if (0 === $i || preg_match('/^(?P\s*[0-9A-Za-z!#\$%\&\'\*\+\-\.^_`\|~]+)=/', $part)) { + $cookies[] = ltrim($part); + } else { + $cookies[count($cookies) - 1] .= ','.$part; + } + } + } + + foreach ($cookies as $cookie) { + try { + $this->set(Cookie::fromString($cookie, $uri)); + } catch (\InvalidArgumentException $e) { + // invalid cookies are just ignored + } + } + } + + /** + * Updates the cookie jar from a Response object. + * + * @param Response $response A Response object + * @param string $uri The base URL + */ + public function updateFromResponse(Response $response, $uri = null) + { + $this->updateFromSetCookie($response->getHeader('Set-Cookie', false), $uri); + } + + /** + * Returns not yet expired cookies. + * + * @return Cookie[] An array of cookies + */ + public function all() + { + $this->flushExpiredCookies(); + + $flattenedCookies = array(); + foreach ($this->cookieJar as $path) { + foreach ($path as $cookies) { + foreach ($cookies as $cookie) { + $flattenedCookies[] = $cookie; + } + } + } + + return $flattenedCookies; + } + + /** + * Returns not yet expired cookie values for the given URI. + * + * @param string $uri A URI + * @param Boolean $returnsRawValue Returns raw value or urldecoded value + * + * @return array An array of cookie values + */ + public function allValues($uri, $returnsRawValue = false) + { + $this->flushExpiredCookies(); + + $parts = array_replace(array('path' => '/'), parse_url($uri)); + $cookies = array(); + foreach ($this->cookieJar as $domain => $pathCookies) { + if ($domain) { + $domain = ltrim($domain, '.'); + if ($domain != substr($parts['host'], -strlen($domain))) { + continue; + } + } + + foreach ($pathCookies as $path => $namedCookies) { + if ($path != substr($parts['path'], 0, strlen($path))) { + continue; + } + + foreach ($namedCookies as $cookie) { + if ($cookie->isSecure() && 'https' != $parts['scheme']) { + continue; + } + + $cookies[$cookie->getName()] = $returnsRawValue ? $cookie->getRawValue() : $cookie->getValue(); + } + } + } + + return $cookies; + } + + /** + * Returns not yet expired raw cookie values for the given URI. + * + * @param string $uri A URI + * + * @return array An array of cookie values + */ + public function allRawValues($uri) + { + return $this->allValues($uri, true); + } + + /** + * Removes all expired cookies. + */ + public function flushExpiredCookies() + { + foreach ($this->cookieJar as $domain => $pathCookies) { + foreach ($pathCookies as $path => $namedCookies) { + foreach ($namedCookies as $name => $cookie) { + if ($cookie->isExpired()) { + unset($this->cookieJar[$domain][$path][$name]); + } + } + } + } + } +} diff --git a/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/History.php b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/History.php new file mode 100644 index 0000000..a22847e --- /dev/null +++ b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/History.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit; + +/** + * History. + * + * @author Fabien Potencier + */ +class History +{ + protected $stack = array(); + protected $position = -1; + + /** + * Constructor. + */ + public function __construct() + { + $this->clear(); + } + + /** + * Clears the history. + */ + public function clear() + { + $this->stack = array(); + $this->position = -1; + } + + /** + * Adds a Request to the history. + * + * @param Request $request A Request instance + */ + public function add(Request $request) + { + $this->stack = array_slice($this->stack, 0, $this->position + 1); + $this->stack[] = clone $request; + $this->position = count($this->stack) - 1; + } + + /** + * Returns true if the history is empty. + * + * @return Boolean true if the history is empty, false otherwise + */ + public function isEmpty() + { + return count($this->stack) == 0; + } + + /** + * Goes back in the history. + * + * @return Request A Request instance + * + * @throws \LogicException if the stack is already on the first page + */ + public function back() + { + if ($this->position < 1) { + throw new \LogicException('You are already on the first page.'); + } + + return clone $this->stack[--$this->position]; + } + + /** + * Goes forward in the history. + * + * @return Request A Request instance + * + * @throws \LogicException if the stack is already on the last page + */ + public function forward() + { + if ($this->position > count($this->stack) - 2) { + throw new \LogicException('You are already on the last page.'); + } + + return clone $this->stack[++$this->position]; + } + + /** + * Returns the current element in the history. + * + * @return Request A Request instance + * + * @throws \LogicException if the stack is empty + */ + public function current() + { + if (-1 == $this->position) { + throw new \LogicException('The page history is empty.'); + } + + return clone $this->stack[$this->position]; + } +} diff --git a/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/LICENSE b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/LICENSE new file mode 100644 index 0000000..88a57f8 --- /dev/null +++ b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2013 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/README.md b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/README.md new file mode 100644 index 0000000..da19188 --- /dev/null +++ b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/README.md @@ -0,0 +1,23 @@ +BrowserKit Component +==================== + +BrowserKit simulates the behavior of a web browser. + +The component only provide an abstract client and does not provide any +"default" backend for the HTTP layer. + +Resources +--------- + +For a simple implementation of a browser based on an HTTP layer, have a look +at [Goutte](https://github.com/fabpot/Goutte). + +For an implementation based on HttpKernelInterface, have a look at the +[Client](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/Client.php) +provided by the HttpKernel component. + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/BrowserKit/ + $ composer.phar install --dev + $ phpunit diff --git a/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Request.php b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Request.php new file mode 100644 index 0000000..6d381d2 --- /dev/null +++ b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Request.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit; + +/** + * Request object. + * + * @author Fabien Potencier + * + * @api + */ +class Request +{ + protected $uri; + protected $method; + protected $parameters; + protected $files; + protected $cookies; + protected $server; + protected $content; + + /** + * Constructor. + * + * @param string $uri The request URI + * @param string $method The HTTP method request + * @param array $parameters The request parameters + * @param array $files An array of uploaded files + * @param array $cookies An array of cookies + * @param array $server An array of server parameters + * @param string $content The raw body data + * + * @api + */ + public function __construct($uri, $method, array $parameters = array(), array $files = array(), array $cookies = array(), array $server = array(), $content = null) + { + $this->uri = $uri; + $this->method = $method; + $this->parameters = $parameters; + $this->files = $files; + $this->cookies = $cookies; + $this->server = $server; + $this->content = $content; + } + + /** + * Gets the request URI. + * + * @return string The request URI + * + * @api + */ + public function getUri() + { + return $this->uri; + } + + /** + * Gets the request HTTP method. + * + * @return string The request HTTP method + * + * @api + */ + public function getMethod() + { + return $this->method; + } + + /** + * Gets the request parameters. + * + * @return array The request parameters + * + * @api + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Gets the request server files. + * + * @return array The request files + * + * @api + */ + public function getFiles() + { + return $this->files; + } + + /** + * Gets the request cookies. + * + * @return array The request cookies + * + * @api + */ + public function getCookies() + { + return $this->cookies; + } + + /** + * Gets the request server parameters. + * + * @return array The request server parameters + * + * @api + */ + public function getServer() + { + return $this->server; + } + + /** + * Gets the request raw body data. + * + * @return string The request raw body data. + * + * @api + */ + public function getContent() + { + return $this->content; + } +} diff --git a/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Response.php b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Response.php new file mode 100644 index 0000000..182fdda --- /dev/null +++ b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Response.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit; + +/** + * Response object. + * + * @author Fabien Potencier + * + * @api + */ +class Response +{ + protected $content; + protected $status; + protected $headers; + + /** + * Constructor. + * + * The headers array is a set of key/value pairs. If a header is present multiple times + * then the value is an array of all the values. + * + * @param string $content The content of the response + * @param integer $status The response status code + * @param array $headers An array of headers + * + * @api + */ + public function __construct($content = '', $status = 200, array $headers = array()) + { + $this->content = $content; + $this->status = $status; + $this->headers = $headers; + } + + /** + * Converts the response object to string containing all headers and the response content. + * + * @return string The response with headers and content + */ + public function __toString() + { + $headers = ''; + foreach ($this->headers as $name => $value) { + if (is_string($value)) { + $headers .= $this->buildHeader($name, $value); + } else { + foreach ($value as $headerValue) { + $headers .= $this->buildHeader($name, $headerValue); + } + } + } + + return $headers."\n".$this->content; + } + + /** + * Returns the build header line. + * + * @param string $name The header name + * @param string $value The header value + * + * @return string The built header line + */ + protected function buildHeader($name, $value) + { + return sprintf("%s: %s\n", $name, $value); + } + + /** + * Gets the response content. + * + * @return string The response content + * + * @api + */ + public function getContent() + { + return $this->content; + } + + /** + * Gets the response status code. + * + * @return integer The response status code + * + * @api + */ + public function getStatus() + { + return $this->status; + } + + /** + * Gets the response headers. + * + * @return array The response headers + * + * @api + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Gets a response header. + * + * @param string $header The header name + * @param Boolean $first Whether to return the first value or all header values + * + * @return string|array The first header value if $first is true, an array of values otherwise + */ + public function getHeader($header, $first = true) + { + foreach ($this->headers as $key => $value) { + if (str_replace('-', '_', strtolower($key)) == str_replace('-', '_', strtolower($header))) { + if ($first) { + return is_array($value) ? (count($value) ? $value[0] : '') : $value; + } + + return is_array($value) ? $value : array($value); + } + } + + return $first ? null : array(); + } +} diff --git a/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/ClientTest.php b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/ClientTest.php new file mode 100644 index 0000000..1fe38a1 --- /dev/null +++ b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/ClientTest.php @@ -0,0 +1,552 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit\Tests; + +use Symfony\Component\BrowserKit\Client; +use Symfony\Component\BrowserKit\History; +use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\BrowserKit\Request; +use Symfony\Component\BrowserKit\Response; + +class SpecialResponse extends Response +{ +} + +class TestClient extends Client +{ + protected $nextResponse = null; + protected $nextScript = null; + + public function setNextResponse(Response $response) + { + $this->nextResponse = $response; + } + + public function setNextScript($script) + { + $this->nextScript = $script; + } + + protected function doRequest($request) + { + if (null === $this->nextResponse) { + return new Response(); + } + + $response = $this->nextResponse; + $this->nextResponse = null; + + return $response; + } + + protected function filterResponse($response) + { + if ($response instanceof SpecialResponse) { + return new Response($response->getContent(), $response->getStatus(), $response->getHeaders()); + } + + return $response; + } + + protected function getScript($request) + { + $r = new \ReflectionClass('Symfony\Component\BrowserKit\Response'); + $path = $r->getFileName(); + + return <<nextScript); +EOF; + } +} + +class ClientTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Symfony\Component\BrowserKit\Client::getHistory + */ + public function testGetHistory() + { + $client = new TestClient(array(), $history = new History()); + $this->assertSame($history, $client->getHistory(), '->getHistory() returns the History'); + } + + /** + * @covers Symfony\Component\BrowserKit\Client::getCookieJar + */ + public function testGetCookieJar() + { + $client = new TestClient(array(), null, $cookieJar = new CookieJar()); + $this->assertSame($cookieJar, $client->getCookieJar(), '->getCookieJar() returns the CookieJar'); + } + + /** + * @covers Symfony\Component\BrowserKit\Client::getRequest + */ + public function testGetRequest() + { + $client = new TestClient(); + $client->request('GET', 'http://example.com/'); + + $this->assertEquals('http://example.com/', $client->getRequest()->getUri(), '->getCrawler() returns the Request of the last request'); + } + + public function testGetResponse() + { + $client = new TestClient(); + $client->setNextResponse(new Response('foo')); + $client->request('GET', 'http://example.com/'); + + $this->assertEquals('foo', $client->getResponse()->getContent(), '->getCrawler() returns the Response of the last request'); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Response', $client->getResponse(), '->getCrawler() returns the Response of the last request'); + } + + public function testGetInternalResponse() + { + $client = new TestClient(); + $client->setNextResponse(new SpecialResponse('foo')); + $client->request('GET', 'http://example.com/'); + + $this->assertInstanceOf('Symfony\Component\BrowserKit\Response', $client->getInternalResponse()); + $this->assertNotInstanceOf('Symfony\Component\BrowserKit\Tests\SpecialResponse', $client->getInternalResponse()); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Tests\SpecialResponse', $client->getResponse()); + } + + public function testGetContent() + { + $json = '{"jsonrpc":"2.0","method":"echo","id":7,"params":["Hello World"]}'; + + $client = new TestClient(); + $client->request('POST', 'http://example.com/jsonrpc', array(), array(), array(), $json); + $this->assertEquals($json, $client->getRequest()->getContent()); + } + + /** + * @covers Symfony\Component\BrowserKit\Client::getCrawler + */ + public function testGetCrawler() + { + $client = new TestClient(); + $client->setNextResponse(new Response('foo')); + $crawler = $client->request('GET', 'http://example.com/'); + + $this->assertSame($crawler, $client->getCrawler(), '->getCrawler() returns the Crawler of the last request'); + } + + public function testRequestHttpHeaders() + { + $client = new TestClient(); + $client->request('GET', '/'); + $headers = $client->getRequest()->getServer(); + $this->assertEquals('localhost', $headers['HTTP_HOST'], '->request() sets the HTTP_HOST header'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com'); + $headers = $client->getRequest()->getServer(); + $this->assertEquals('www.example.com', $headers['HTTP_HOST'], '->request() sets the HTTP_HOST header'); + + $client->request('GET', 'https://www.example.com'); + $headers = $client->getRequest()->getServer(); + $this->assertTrue($headers['HTTPS'], '->request() sets the HTTPS header'); + } + + public function testRequestURIConversion() + { + $client = new TestClient(); + $client->request('GET', '/foo'); + $this->assertEquals('http://localhost/foo', $client->getRequest()->getUri(), '->request() converts the URI to an absolute one'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com'); + $this->assertEquals('http://www.example.com', $client->getRequest()->getUri(), '->request() does not change absolute URIs'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/'); + $client->request('GET', '/foo'); + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo'); + $client->request('GET', '#'); + $this->assertEquals('http://www.example.com/foo#', $client->getRequest()->getUri(), '->request() uses the previous request for #'); + $client->request('GET', '#'); + $this->assertEquals('http://www.example.com/foo#', $client->getRequest()->getUri(), '->request() uses the previous request for #'); + $client->request('GET', '#foo'); + $this->assertEquals('http://www.example.com/foo#foo', $client->getRequest()->getUri(), '->request() uses the previous request for #'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/'); + $client->request('GET', 'bar'); + $this->assertEquals('http://www.example.com/foo/bar', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs'); + + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->request('GET', 'bar'); + $this->assertEquals('http://www.example.com/foo/bar', $client->getRequest()->getUri(), '->request() uses the previous request for relative URLs'); + } + + public function testRequestReferer() + { + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->request('GET', 'bar'); + $server = $client->getRequest()->getServer(); + $this->assertEquals('http://www.example.com/foo/foobar', $server['HTTP_REFERER'], '->request() sets the referer'); + } + + public function testRequestHistory() + { + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->request('GET', 'bar'); + + $this->assertEquals('http://www.example.com/foo/bar', $client->getHistory()->current()->getUri(), '->request() updates the History'); + $this->assertEquals('http://www.example.com/foo/foobar', $client->getHistory()->back()->getUri(), '->request() updates the History'); + } + + public function testRequestCookies() + { + $client = new TestClient(); + $client->setNextResponse(new Response('foo', 200, array('Set-Cookie' => 'foo=bar'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $this->assertEquals(array('foo' => 'bar'), $client->getCookieJar()->allValues('http://www.example.com/foo/foobar'), '->request() updates the CookieJar'); + + $client->request('GET', 'bar'); + $this->assertEquals(array('foo' => 'bar'), $client->getCookieJar()->allValues('http://www.example.com/foo/foobar'), '->request() updates the CookieJar'); + } + + public function testRequestSecureCookies() + { + $client = new TestClient(); + $client->setNextResponse(new Response('foo', 200, array('Set-Cookie' => 'foo=bar; path=/; secure'))); + $client->request('GET', 'https://www.example.com/foo/foobar'); + + $this->assertTrue($client->getCookieJar()->get('foo', '/', 'www.example.com')->isSecure()); + } + + public function testClick() + { + if (!class_exists('Symfony\Component\DomCrawler\Crawler')) { + $this->markTestSkipped('The "DomCrawler" component is not available'); + } + + if (!class_exists('Symfony\Component\CssSelector\CssSelector')) { + $this->markTestSkipped('The "CssSelector" component is not available'); + } + + $client = new TestClient(); + $client->setNextResponse(new Response('foo')); + $crawler = $client->request('GET', 'http://www.example.com/foo/foobar'); + + $client->click($crawler->filter('a')->link()); + + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->click() clicks on links'); + } + + public function testClickForm() + { + if (!class_exists('Symfony\Component\DomCrawler\Crawler')) { + $this->markTestSkipped('The "DomCrawler" component is not available'); + } + + if (!class_exists('Symfony\Component\CssSelector\CssSelector')) { + $this->markTestSkipped('The "CssSelector" component is not available'); + } + + $client = new TestClient(); + $client->setNextResponse(new Response('
    ')); + $crawler = $client->request('GET', 'http://www.example.com/foo/foobar'); + + $client->click($crawler->filter('input')->form()); + + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->click() Form submit forms'); + } + + public function testSubmit() + { + if (!class_exists('Symfony\Component\DomCrawler\Crawler')) { + $this->markTestSkipped('The "DomCrawler" component is not available'); + } + + if (!class_exists('Symfony\Component\CssSelector\CssSelector')) { + $this->markTestSkipped('The "CssSelector" component is not available'); + } + + $client = new TestClient(); + $client->setNextResponse(new Response('
    ')); + $crawler = $client->request('GET', 'http://www.example.com/foo/foobar'); + + $client->submit($crawler->filter('input')->form()); + + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->submit() submit forms'); + } + + public function testSubmitPreserveAuth() + { + if (!class_exists('Symfony\Component\DomCrawler\Crawler')) { + $this->markTestSkipped('The "DomCrawler" component is not available'); + } + + if (!class_exists('Symfony\Component\CssSelector\CssSelector')) { + $this->markTestSkipped('The "CssSelector" component is not available'); + } + + $client = new TestClient(array('PHP_AUTH_USER' => 'foo', 'PHP_AUTH_PW' => 'bar')); + $client->setNextResponse(new Response('
    ')); + $crawler = $client->request('GET', 'http://www.example.com/foo/foobar'); + + $server = $client->getRequest()->getServer(); + $this->assertArrayHasKey('PHP_AUTH_USER', $server); + $this->assertEquals('foo', $server['PHP_AUTH_USER']); + $this->assertArrayHasKey('PHP_AUTH_PW', $server); + $this->assertEquals('bar', $server['PHP_AUTH_PW']); + + $client->submit($crawler->filter('input')->form()); + + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->submit() submit forms'); + + $server = $client->getRequest()->getServer(); + $this->assertArrayHasKey('PHP_AUTH_USER', $server); + $this->assertEquals('foo', $server['PHP_AUTH_USER']); + $this->assertArrayHasKey('PHP_AUTH_PW', $server); + $this->assertEquals('bar', $server['PHP_AUTH_PW']); + } + + public function testFollowRedirect() + { + $client = new TestClient(); + $client->followRedirects(false); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + try { + $client->followRedirect(); + $this->fail('->followRedirect() throws a \LogicException if the request was not redirected'); + } catch (\Exception $e) { + $this->assertInstanceof('LogicException', $e, '->followRedirect() throws a \LogicException if the request was not redirected'); + } + + $client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->followRedirect(); + + $this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect if any'); + + $client = new TestClient(); + $client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + $this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() automatically follows redirects if followRedirects is true'); + + $client = new TestClient(); + $client->setNextResponse(new Response('', 201, array('Location' => 'http://www.example.com/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + $this->assertEquals('http://www.example.com/foo/foobar', $client->getRequest()->getUri(), '->followRedirect() does not follow redirect if HTTP Code is not 30x'); + + $client = new TestClient(); + $client->setNextResponse(new Response('', 201, array('Location' => 'http://www.example.com/redirected'))); + $client->followRedirects(false); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + try { + $client->followRedirect(); + $this->fail('->followRedirect() throws a \LogicException if the request did not respond with 30x HTTP Code'); + } catch (\Exception $e) { + $this->assertInstanceof('LogicException', $e, '->followRedirect() throws a \LogicException if the request did not respond with 30x HTTP Code'); + } + } + + public function testFollowRedirectWithMaxRedirects() + { + $client = new TestClient(); + $client->setMaxRedirects(1); + $client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect if any'); + + $client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected2'))); + try { + $client->followRedirect(); + $this->fail('->followRedirect() throws a \LogicException if the request was redirected and limit of redirections was reached'); + } catch (\Exception $e) { + $this->assertInstanceof('LogicException', $e, '->followRedirect() throws a \LogicException if the request was redirected and limit of redirections was reached'); + } + + $client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected'))); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $this->assertEquals('http://www.example.com/redirected', $client->getRequest()->getUri(), '->followRedirect() follows a redirect if any'); + + $client->setNextResponse(new Response('', 302, array('Location' => 'http://www.example.com/redirected'))); + $client->request('POST', 'http://www.example.com/foo/foobar'); + + $this->assertEquals('get', $client->getRequest()->getMethod(), '->followRedirect() uses a get for 302'); + } + + public function testFollowRedirectWithCookies() + { + $client = new TestClient(); + $client->followRedirects(false); + $client->setNextResponse(new Response('', 302, array( + 'Location' => 'http://www.example.com/redirected', + 'Set-Cookie' => 'foo=bar', + ))); + $client->request('GET', 'http://www.example.com/'); + $this->assertEquals(array(), $client->getRequest()->getCookies()); + $client->followRedirect(); + $this->assertEquals(array('foo' => 'bar'), $client->getRequest()->getCookies()); + } + + public function testFollowRedirectWithHeaders() + { + $headers = array( + 'HTTP_HOST' => 'www.example.com', + 'HTTP_USER_AGENT' => 'Symfony2 BrowserKit', + 'CONTENT_TYPE' => 'application/vnd.custom+xml', + 'HTTPS' => false, + ); + + $client = new TestClient(); + $client->followRedirects(false); + $client->setNextResponse(new Response('', 302, array( + 'Location' => 'http://www.example.com/redirected', + ))); + $client->request('GET', 'http://www.example.com/', array(), array(), array( + 'CONTENT_TYPE' => 'application/vnd.custom+xml', + )); + + $this->assertEquals($headers, $client->getRequest()->getServer()); + + $client->followRedirect(); + + $headers['HTTP_REFERER'] = 'http://www.example.com/'; + + $this->assertEquals($headers, $client->getRequest()->getServer()); + } + + public function testBack() + { + $client = new TestClient(); + + $parameters = array('foo' => 'bar'); + $files = array('myfile.foo' => 'baz'); + $server = array('X_TEST_FOO' => 'bazbar'); + $content = 'foobarbaz'; + + $client->request('GET', 'http://www.example.com/foo/foobar', $parameters, $files, $server, $content); + $client->request('GET', 'http://www.example.com/foo'); + $client->back(); + + $this->assertEquals('http://www.example.com/foo/foobar', $client->getRequest()->getUri(), '->back() goes back in the history'); + $this->assertArrayHasKey('foo', $client->getRequest()->getParameters(), '->back() keeps parameters'); + $this->assertArrayHasKey('myfile.foo', $client->getRequest()->getFiles(), '->back() keeps files'); + $this->assertArrayHasKey('X_TEST_FOO', $client->getRequest()->getServer(), '->back() keeps $_SERVER'); + $this->assertEquals($content, $client->getRequest()->getContent(), '->back() keeps content'); + } + + public function testForward() + { + $client = new TestClient(); + + $parameters = array('foo' => 'bar'); + $files = array('myfile.foo' => 'baz'); + $server = array('X_TEST_FOO' => 'bazbar'); + $content = 'foobarbaz'; + + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->request('GET', 'http://www.example.com/foo', $parameters, $files, $server, $content); + $client->back(); + $client->forward(); + + $this->assertEquals('http://www.example.com/foo', $client->getRequest()->getUri(), '->forward() goes forward in the history'); + $this->assertArrayHasKey('foo', $client->getRequest()->getParameters(), '->forward() keeps parameters'); + $this->assertArrayHasKey('myfile.foo', $client->getRequest()->getFiles(), '->forward() keeps files'); + $this->assertArrayHasKey('X_TEST_FOO', $client->getRequest()->getServer(), '->forward() keeps $_SERVER'); + $this->assertEquals($content, $client->getRequest()->getContent(), '->forward() keeps content'); + } + + public function testReload() + { + $client = new TestClient(); + + $parameters = array('foo' => 'bar'); + $files = array('myfile.foo' => 'baz'); + $server = array('X_TEST_FOO' => 'bazbar'); + $content = 'foobarbaz'; + + $client->request('GET', 'http://www.example.com/foo/foobar', $parameters, $files, $server, $content); + $client->reload(); + + $this->assertEquals('http://www.example.com/foo/foobar', $client->getRequest()->getUri(), '->reload() reloads the current page'); + $this->assertArrayHasKey('foo', $client->getRequest()->getParameters(), '->reload() keeps parameters'); + $this->assertArrayHasKey('myfile.foo', $client->getRequest()->getFiles(), '->reload() keeps files'); + $this->assertArrayHasKey('X_TEST_FOO', $client->getRequest()->getServer(), '->reload() keeps $_SERVER'); + $this->assertEquals($content, $client->getRequest()->getContent(), '->reload() keeps content'); + } + + public function testRestart() + { + $client = new TestClient(); + $client->request('GET', 'http://www.example.com/foo/foobar'); + $client->restart(); + + $this->assertTrue($client->getHistory()->isEmpty(), '->restart() clears the history'); + $this->assertEquals(array(), $client->getCookieJar()->all(), '->restart() clears the cookies'); + } + + public function testInsulatedRequests() + { + if (!class_exists('Symfony\Component\Process\Process')) { + $this->markTestSkipped('The "Process" component is not available'); + } + + $client = new TestClient(); + $client->insulate(); + $client->setNextScript("new Symfony\Component\BrowserKit\Response('foobar')"); + $client->request('GET', 'http://www.example.com/foo/foobar'); + + $this->assertEquals('foobar', $client->getResponse()->getContent(), '->insulate() process the request in a forked process'); + + $client->setNextScript("new Symfony\Component\BrowserKit\Response('foobar)"); + + try { + $client->request('GET', 'http://www.example.com/foo/foobar'); + $this->fail('->request() throws a \RuntimeException if the script has an error'); + } catch (\Exception $e) { + $this->assertInstanceof('RuntimeException', $e, '->request() throws a \RuntimeException if the script has an error'); + } + } + + public function testGetServerParameter() + { + $client = new TestClient(); + $this->assertEquals('localhost', $client->getServerParameter('HTTP_HOST')); + $this->assertEquals('Symfony2 BrowserKit', $client->getServerParameter('HTTP_USER_AGENT')); + $this->assertEquals('testvalue', $client->getServerParameter('testkey', 'testvalue')); + } + + public function testSetServerParameter() + { + $client = new TestClient(); + + $this->assertEquals('localhost', $client->getServerParameter('HTTP_HOST')); + $this->assertEquals('Symfony2 BrowserKit', $client->getServerParameter('HTTP_USER_AGENT')); + + $client->setServerParameter('HTTP_HOST', 'testhost'); + $this->assertEquals('testhost', $client->getServerParameter('HTTP_HOST')); + + $client->setServerParameter('HTTP_USER_AGENT', 'testua'); + $this->assertEquals('testua', $client->getServerParameter('HTTP_USER_AGENT')); + } +} diff --git a/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/CookieJarTest.php b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/CookieJarTest.php new file mode 100644 index 0000000..bdbd40e --- /dev/null +++ b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/CookieJarTest.php @@ -0,0 +1,198 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit\Tests; + +use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\BrowserKit\Cookie; +use Symfony\Component\BrowserKit\Response; + +class CookieJarTest extends \PHPUnit_Framework_TestCase +{ + public function testSetGet() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie = new Cookie('foo', 'bar')); + + $this->assertEquals($cookie, $cookieJar->get('foo'), '->set() sets a cookie'); + + $this->assertNull($cookieJar->get('foobar'), '->get() returns null if the cookie does not exist'); + + $cookieJar->set($cookie = new Cookie('foo', 'bar', time() - 86400)); + $this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired'); + } + + public function testExpire() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie = new Cookie('foo', 'bar')); + $cookieJar->expire('foo'); + $this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired'); + } + + public function testAll() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar')); + $cookieJar->set($cookie2 = new Cookie('bar', 'foo')); + + $this->assertEquals(array($cookie1, $cookie2), $cookieJar->all(), '->all() returns all cookies in the jar'); + } + + public function testClear() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar')); + $cookieJar->set($cookie2 = new Cookie('bar', 'foo')); + + $cookieJar->clear(); + + $this->assertEquals(array(), $cookieJar->all(), '->clear() expires all cookies'); + } + + public function testUpdateFromResponse() + { + $response = new Response('', 200, array('Set-Cookie' => 'foo=foo')); + + $cookieJar = new CookieJar(); + $cookieJar->updateFromResponse($response); + + $this->assertEquals('foo', $cookieJar->get('foo')->getValue(), '->updateFromResponse() updates cookies from a Response objects'); + } + + public function testUpdateFromSetCookie() + { + $setCookies = array('foo=foo'); + + $cookieJar = new CookieJar(); + $cookieJar->set(new Cookie('bar', 'bar')); + $cookieJar->updateFromSetCookie($setCookies); + + $this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $cookieJar->get('foo')); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $cookieJar->get('bar')); + $this->assertEquals('foo', $cookieJar->get('foo')->getValue(), '->updateFromSetCookie() updates cookies from a Set-Cookie header'); + $this->assertEquals('bar', $cookieJar->get('bar')->getValue(), '->updateFromSetCookie() keeps existing cookies'); + } + + public function testUpdateFromEmptySetCookie() + { + $cookieJar = new CookieJar(); + $cookieJar->updateFromSetCookie(array('')); + $this->assertEquals(array(), $cookieJar->all()); + } + + public function testUpdateFromSetCookieWithMultipleCookies() + { + $timestamp = time() + 3600; + $date = gmdate('D, d M Y H:i:s \G\M\T', $timestamp); + $setCookies = array(sprintf('foo=foo; expires=%s; domain=.symfony.com; path=/, bar=bar; domain=.blog.symfony.com, PHPSESSID=id; expires=%s', $date, $date)); + + $cookieJar = new CookieJar(); + $cookieJar->updateFromSetCookie($setCookies); + + $fooCookie = $cookieJar->get('foo', '/', '.symfony.com'); + $barCookie = $cookieJar->get('bar', '/', '.blog.symfony.com'); + $phpCookie = $cookieJar->get('PHPSESSID'); + + $this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $fooCookie); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $barCookie); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Cookie', $phpCookie); + $this->assertEquals('foo', $fooCookie->getValue()); + $this->assertEquals('bar', $barCookie->getValue()); + $this->assertEquals('id', $phpCookie->getValue()); + $this->assertEquals($timestamp, $fooCookie->getExpiresTime()); + $this->assertNull($barCookie->getExpiresTime()); + $this->assertEquals($timestamp, $phpCookie->getExpiresTime()); + } + + /** + * @dataProvider provideAllValuesValues + */ + public function testAllValues($uri, $values) + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo_nothing', 'foo')); + $cookieJar->set($cookie2 = new Cookie('foo_expired', 'foo', time() - 86400)); + $cookieJar->set($cookie3 = new Cookie('foo_path', 'foo', null, '/foo')); + $cookieJar->set($cookie4 = new Cookie('foo_domain', 'foo', null, '/', '.example.com')); + $cookieJar->set($cookie4 = new Cookie('foo_strict_domain', 'foo', null, '/', '.www4.example.com')); + $cookieJar->set($cookie5 = new Cookie('foo_secure', 'foo', null, '/', '', true)); + + $this->assertEquals($values, array_keys($cookieJar->allValues($uri)), '->allValues() returns the cookie for a given URI'); + } + + public function provideAllValuesValues() + { + return array( + array('http://www.example.com', array('foo_nothing', 'foo_domain')), + array('http://www.example.com/', array('foo_nothing', 'foo_domain')), + array('http://foo.example.com/', array('foo_nothing', 'foo_domain')), + array('http://foo.example1.com/', array('foo_nothing')), + array('https://foo.example.com/', array('foo_nothing', 'foo_secure', 'foo_domain')), + array('http://www.example.com/foo/bar', array('foo_nothing', 'foo_path', 'foo_domain')), + array('http://www4.example.com/', array('foo_nothing', 'foo_domain', 'foo_strict_domain')), + ); + } + + public function testEncodedValues() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie = new Cookie('foo', 'bar%3Dbaz', null, '/', '', false, true, true)); + + $this->assertEquals(array('foo' => 'bar=baz'), $cookieJar->allValues('/')); + $this->assertEquals(array('foo' => 'bar%3Dbaz'), $cookieJar->allRawValues('/')); + } + + public function testCookieExpireWithSameNameButDifferentPaths() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/foo')); + $cookieJar->set($cookie2 = new Cookie('foo', 'bar2', null, '/bar')); + $cookieJar->expire('foo', '/foo'); + + $this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired'); + $this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/'))); + $this->assertEquals(array(), $cookieJar->allValues('http://example.com/foo')); + $this->assertEquals(array('foo' => 'bar2'), $cookieJar->allValues('http://example.com/bar')); + } + + public function testCookieExpireWithNullPaths() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/')); + $cookieJar->expire('foo', null); + + $this->assertNull($cookieJar->get('foo'), '->get() returns null if the cookie is expired'); + $this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/'))); + } + + public function testCookieWithSameNameButDifferentPaths() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/foo')); + $cookieJar->set($cookie2 = new Cookie('foo', 'bar2', null, '/bar')); + + $this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/'))); + $this->assertEquals(array('foo' => 'bar1'), $cookieJar->allValues('http://example.com/foo')); + $this->assertEquals(array('foo' => 'bar2'), $cookieJar->allValues('http://example.com/bar')); + } + + public function testCookieWithSameNameButDifferentDomains() + { + $cookieJar = new CookieJar(); + $cookieJar->set($cookie1 = new Cookie('foo', 'bar1', null, '/', 'foo.example.com')); + $cookieJar->set($cookie2 = new Cookie('foo', 'bar2', null, '/', 'bar.example.com')); + + $this->assertEquals(array(), array_keys($cookieJar->allValues('http://example.com/'))); + $this->assertEquals(array('foo' => 'bar1'), $cookieJar->allValues('http://foo.example.com/')); + $this->assertEquals(array('foo' => 'bar2'), $cookieJar->allValues('http://bar.example.com/')); + } +} diff --git a/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/CookieTest.php b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/CookieTest.php new file mode 100644 index 0000000..606b2e2 --- /dev/null +++ b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/CookieTest.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit\Tests; + +use Symfony\Component\BrowserKit\Cookie; + +class CookieTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getTestsForToFromString + */ + public function testToFromString($cookie, $url = null) + { + $this->assertEquals($cookie, (string) Cookie::fromString($cookie, $url)); + } + + public function getTestsForToFromString() + { + return array( + array('foo=bar; path=/'), + array('foo=bar; path=/foo'), + array('foo=bar; domain=google.com; path=/'), + array('foo=bar; domain=example.com; path=/; secure', 'https://example.com/'), + array('foo=bar; path=/; httponly'), + array('foo=bar; domain=google.com; path=/foo; secure; httponly', 'https://google.com/'), + array('foo=bar=baz; path=/'), + array('foo=bar%3Dbaz; path=/'), + ); + } + + public function testFromStringIgnoreSecureFlag() + { + $this->assertFalse(Cookie::fromString('foo=bar; secure')->isSecure()); + $this->assertFalse(Cookie::fromString('foo=bar; secure', 'http://example.com/')->isSecure()); + } + + /** + * @dataProvider getExpireCookieStrings + */ + public function testFromStringAcceptsSeveralExpiresDateFormats($cookie) + { + $this->assertEquals(1596185377, Cookie::fromString($cookie)->getExpiresTime()); + } + + public function getExpireCookieStrings() + { + return array( + array('foo=bar; expires=Fri, 31-Jul-2020 08:49:37 GMT'), + array('foo=bar; expires=Fri, 31 Jul 2020 08:49:37 GMT'), + array('foo=bar; expires=Fri, 31-07-2020 08:49:37 GMT'), + array('foo=bar; expires=Fri, 31-07-20 08:49:37 GMT'), + array('foo=bar; expires=Friday, 31-Jul-20 08:49:37 GMT'), + array('foo=bar; expires=Fri Jul 31 08:49:37 2020'), + array('foo=bar; expires=\'Fri Jul 31 08:49:37 2020\''), + array('foo=bar; expires=Friday July 31st 2020, 08:49:37 GMT'), + ); + } + + public function testFromStringWithCapitalization() + { + $this->assertEquals('Foo=Bar; path=/', (string) Cookie::fromString('Foo=Bar')); + $this->assertEquals('foo=bar; expires=Fri, 31 Dec 2010 23:59:59 GMT; path=/', (string) Cookie::fromString('foo=bar; Expires=Fri, 31 Dec 2010 23:59:59 GMT')); + $this->assertEquals('foo=bar; domain=www.example.org; path=/; httponly', (string) Cookie::fromString('foo=bar; DOMAIN=www.example.org; HttpOnly')); + } + + public function testFromStringWithUrl() + { + $this->assertEquals('foo=bar; domain=www.example.com; path=/', (string) Cookie::FromString('foo=bar', 'http://www.example.com/')); + $this->assertEquals('foo=bar; domain=www.example.com; path=/foo', (string) Cookie::FromString('foo=bar', 'http://www.example.com/foo/bar')); + $this->assertEquals('foo=bar; domain=www.example.com; path=/', (string) Cookie::FromString('foo=bar; path=/', 'http://www.example.com/foo/bar')); + $this->assertEquals('foo=bar; domain=www.myotherexample.com; path=/', (string) Cookie::FromString('foo=bar; domain=www.myotherexample.com', 'http://www.example.com/')); + } + + public function testFromStringThrowsAnExceptionIfCookieIsNotValid() + { + $this->setExpectedException('InvalidArgumentException'); + Cookie::FromString('foo'); + } + + public function testFromStringThrowsAnExceptionIfCookieDateIsNotValid() + { + $this->setExpectedException('InvalidArgumentException'); + Cookie::FromString('foo=bar; expires=Flursday July 31st 2020, 08:49:37 GMT'); + } + + public function testFromStringThrowsAnExceptionIfUrlIsNotValid() + { + $this->setExpectedException('InvalidArgumentException'); + Cookie::FromString('foo=bar', 'foobar'); + } + + public function testGetName() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertEquals('foo', $cookie->getName(), '->getName() returns the cookie name'); + } + + public function testGetValue() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertEquals('bar', $cookie->getValue(), '->getValue() returns the cookie value'); + + $cookie = new Cookie('foo', 'bar%3Dbaz', null, '/', '', false, true, true); // raw value + $this->assertEquals('bar=baz', $cookie->getValue(), '->getValue() returns the urldecoded cookie value'); + } + + public function testGetRawValue() + { + $cookie = new Cookie('foo', 'bar=baz'); // decoded value + $this->assertEquals('bar%3Dbaz', $cookie->getRawValue(), '->getRawValue() returns the urlencoded cookie value'); + $cookie = new Cookie('foo', 'bar%3Dbaz', null, '/', '', false, true, true); // raw value + $this->assertEquals('bar%3Dbaz', $cookie->getRawValue(), '->getRawValue() returns the non-urldecoded cookie value'); + } + + public function testGetPath() + { + $cookie = new Cookie('foo', 'bar', 0); + $this->assertEquals('/', $cookie->getPath(), '->getPath() returns / is no path is defined'); + + $cookie = new Cookie('foo', 'bar', 0, '/foo'); + $this->assertEquals('/foo', $cookie->getPath(), '->getPath() returns the cookie path'); + } + + public function testGetDomain() + { + $cookie = new Cookie('foo', 'bar', 0, '/', 'foo.com'); + $this->assertEquals('foo.com', $cookie->getDomain(), '->getDomain() returns the cookie domain'); + } + + public function testIsSecure() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertFalse($cookie->isSecure(), '->isSecure() returns false if not defined'); + + $cookie = new Cookie('foo', 'bar', 0, '/', 'foo.com', true); + $this->assertTrue($cookie->isSecure(), '->isSecure() returns the cookie secure flag'); + } + + public function testIsHttponly() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertTrue($cookie->isHttpOnly(), '->isHttpOnly() returns false if not defined'); + + $cookie = new Cookie('foo', 'bar', 0, '/', 'foo.com', false, true); + $this->assertTrue($cookie->isHttpOnly(), '->isHttpOnly() returns the cookie httponly flag'); + } + + public function testGetExpiresTime() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertNull($cookie->getExpiresTime(), '->getExpiresTime() returns the expires time'); + + $cookie = new Cookie('foo', 'bar', $time = time() - 86400); + $this->assertEquals($time, $cookie->getExpiresTime(), '->getExpiresTime() returns the expires time'); + } + + public function testIsExpired() + { + $cookie = new Cookie('foo', 'bar'); + $this->assertFalse($cookie->isExpired(), '->isExpired() returns false when the cookie never expires (null as expires time)'); + + $cookie = new Cookie('foo', 'bar', time() - 86400); + $this->assertTrue($cookie->isExpired(), '->isExpired() returns true when the cookie is expired'); + + $cookie = new Cookie('foo', 'bar', 0); + $this->assertFalse($cookie->isExpired()); + } +} diff --git a/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/HistoryTest.php b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/HistoryTest.php new file mode 100644 index 0000000..882b730 --- /dev/null +++ b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/HistoryTest.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit\Tests; + +use Symfony\Component\BrowserKit\History; +use Symfony\Component\BrowserKit\Request; + +class HistoryTest extends \PHPUnit_Framework_TestCase +{ + public function testAdd() + { + $history = new History(); + $history->add(new Request('http://www.example1.com/', 'get')); + $this->assertSame('http://www.example1.com/', $history->current()->getUri(), '->add() adds a request to the history'); + + $history->add(new Request('http://www.example2.com/', 'get')); + $this->assertSame('http://www.example2.com/', $history->current()->getUri(), '->add() adds a request to the history'); + + $history->add(new Request('http://www.example3.com/', 'get')); + $history->back(); + $history->add(new Request('http://www.example4.com/', 'get')); + $this->assertSame('http://www.example4.com/', $history->current()->getUri(), '->add() adds a request to the history'); + + $history->back(); + $this->assertSame('http://www.example2.com/', $history->current()->getUri(), '->add() adds a request to the history'); + } + + public function testClearIsEmpty() + { + $history = new History(); + $history->add(new Request('http://www.example.com/', 'get')); + + $this->assertFalse($history->isEmpty(), '->isEmpty() returns false if the history is not empty'); + + $history->clear(); + + $this->assertTrue($history->isEmpty(), '->isEmpty() true if the history is empty'); + } + + public function testCurrent() + { + $history = new History(); + + try { + $history->current(); + $this->fail('->current() throws a \LogicException if the history is empty'); + } catch (\Exception $e) { + $this->assertInstanceof('LogicException', $e, '->current() throws a \LogicException if the history is empty'); + } + + $history->add(new Request('http://www.example.com/', 'get')); + + $this->assertSame('http://www.example.com/', $history->current()->getUri(), '->current() returns the current request in the history'); + } + + public function testBack() + { + $history = new History(); + $history->add(new Request('http://www.example.com/', 'get')); + + try { + $history->back(); + $this->fail('->back() throws a \LogicException if the history is already on the first page'); + } catch (\Exception $e) { + $this->assertInstanceof('LogicException', $e, '->current() throws a \LogicException if the history is already on the first page'); + } + + $history->add(new Request('http://www.example1.com/', 'get')); + $history->back(); + + $this->assertSame('http://www.example.com/', $history->current()->getUri(), '->back() returns the previous request in the history'); + } + + public function testForward() + { + $history = new History(); + $history->add(new Request('http://www.example.com/', 'get')); + $history->add(new Request('http://www.example1.com/', 'get')); + + try { + $history->forward(); + $this->fail('->forward() throws a \LogicException if the history is already on the last page'); + } catch (\Exception $e) { + $this->assertInstanceof('LogicException', $e, '->forward() throws a \LogicException if the history is already on the last page'); + } + + $history->back(); + $history->forward(); + + $this->assertSame('http://www.example1.com/', $history->current()->getUri(), '->forward() returns the next request in the history'); + } +} diff --git a/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/RequestTest.php b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/RequestTest.php new file mode 100644 index 0000000..b75b5fb --- /dev/null +++ b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/RequestTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit\Tests; + +use Symfony\Component\BrowserKit\Request; + +class RequestTest extends \PHPUnit_Framework_TestCase +{ + public function testGetUri() + { + $request = new Request('http://www.example.com/', 'get'); + $this->assertEquals('http://www.example.com/', $request->getUri(), '->getUri() returns the URI of the request'); + } + + public function testGetMethod() + { + $request = new Request('http://www.example.com/', 'get'); + $this->assertEquals('get', $request->getMethod(), '->getMethod() returns the method of the request'); + } + + public function testGetParameters() + { + $request = new Request('http://www.example.com/', 'get', array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $request->getParameters(), '->getParameters() returns the parameters of the request'); + } + + public function testGetFiles() + { + $request = new Request('http://www.example.com/', 'get', array(), array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $request->getFiles(), '->getFiles() returns the uploaded files of the request'); + } + + public function testGetCookies() + { + $request = new Request('http://www.example.com/', 'get', array(), array(), array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $request->getCookies(), '->getCookies() returns the cookies of the request'); + } + + public function testGetServer() + { + $request = new Request('http://www.example.com/', 'get', array(), array(), array(), array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $request->getServer(), '->getServer() returns the server parameters of the request'); + } +} diff --git a/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/ResponseTest.php b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/ResponseTest.php new file mode 100644 index 0000000..878752c --- /dev/null +++ b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Tests/ResponseTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\BrowserKit\Tests; + +use Symfony\Component\BrowserKit\Response; + +class ResponseTest extends \PHPUnit_Framework_TestCase +{ + public function testGetUri() + { + $response = new Response('foo'); + $this->assertEquals('foo', $response->getContent(), '->getContent() returns the content of the response'); + } + + public function testGetStatus() + { + $response = new Response('foo', 304); + $this->assertEquals('304', $response->getStatus(), '->getStatus() returns the status of the response'); + } + + public function testGetHeaders() + { + $response = new Response('foo', 200, array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $response->getHeaders(), '->getHeaders() returns the headers of the response'); + } + + public function testGetHeader() + { + $response = new Response('foo', 200, array( + 'Content-Type' => 'text/html', + 'Set-Cookie' => array('foo=bar', 'bar=foo'), + )); + + $this->assertEquals('text/html', $response->getHeader('Content-Type'), '->getHeader() returns a header of the response'); + $this->assertEquals('text/html', $response->getHeader('content-type'), '->getHeader() returns a header of the response'); + $this->assertEquals('text/html', $response->getHeader('content_type'), '->getHeader() returns a header of the response'); + $this->assertEquals('foo=bar', $response->getHeader('Set-Cookie'), '->getHeader() returns the first header value'); + $this->assertEquals(array('foo=bar', 'bar=foo'), $response->getHeader('Set-Cookie', false), '->getHeader() returns all header values if first is false'); + + $this->assertNull($response->getHeader('foo'), '->getHeader() returns null if the header is not defined'); + $this->assertEquals(array(), $response->getHeader('foo', false), '->getHeader() returns an empty array if the header is not defined and first is set to false'); + } + + public function testMagicToString() + { + $response = new Response('foo', 304, array('foo' => 'bar')); + + $this->assertEquals("foo: bar\n\nfoo", $response->__toString(), '->__toString() returns the headers and the content as a string'); + } + + public function testMagicToStringWithMultipleSetCookieHeader() + { + $headers = array( + 'content-type' => 'text/html; charset=utf-8', + 'set-cookie' => array('foo=bar', 'bar=foo') + ); + + $expected = 'content-type: text/html; charset=utf-8'."\n"; + $expected.= 'set-cookie: foo=bar'."\n"; + $expected.= 'set-cookie: bar=foo'."\n\n"; + $expected.= 'foo'; + + $response = new Response('foo', 304, $headers); + + $this->assertEquals($expected, $response->__toString(), '->__toString() returns the headers and the content as a string'); + } +} diff --git a/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/composer.json b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/composer.json new file mode 100644 index 0000000..38a51e9 --- /dev/null +++ b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/composer.json @@ -0,0 +1,39 @@ +{ + "name": "symfony/browser-kit", + "type": "library", + "description": "Symfony BrowserKit Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3", + "symfony/dom-crawler": "~2.0" + }, + "require-dev": { + "symfony/process": "~2.0", + "symfony/css-selector": "~2.0" + }, + "suggest": { + "symfony/process": "" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\BrowserKit\\": "" } + }, + "target-dir": "Symfony/Component/BrowserKit", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + } +} diff --git a/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/phpunit.xml.dist b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/phpunit.xml.dist new file mode 100644 index 0000000..8dff1ea --- /dev/null +++ b/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/console/Symfony/Component/Console/.gitignore b/vendor/symfony/console/Symfony/Component/Console/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/console/Symfony/Component/Console/Application.php b/vendor/symfony/console/Symfony/Component/Console/Application.php new file mode 100644 index 0000000..0f4a6ef --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Application.php @@ -0,0 +1,1127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\HelpCommand; +use Symfony\Component\Console\Command\ListCommand; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Helper\DialogHelper; +use Symfony\Component\Console\Helper\ProgressHelper; +use Symfony\Component\Console\Helper\TableHelper; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleExceptionEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * An Application is the container for a collection of commands. + * + * It is the main entry point of a Console application. + * + * This class is optimized for a standard CLI environment. + * + * Usage: + * + * $app = new Application('myapp', '1.0 (stable)'); + * $app->add(new SimpleCommand()); + * $app->run(); + * + * @author Fabien Potencier + * + * @api + */ +class Application +{ + private $commands; + private $wantHelps = false; + private $runningCommand; + private $name; + private $version; + private $catchExceptions; + private $autoExit; + private $definition; + private $helperSet; + private $dispatcher; + + /** + * Constructor. + * + * @param string $name The name of the application + * @param string $version The version of the application + * + * @api + */ + public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') + { + $this->name = $name; + $this->version = $version; + $this->catchExceptions = true; + $this->autoExit = true; + $this->commands = array(); + $this->helperSet = $this->getDefaultHelperSet(); + $this->definition = $this->getDefaultInputDefinition(); + + foreach ($this->getDefaultCommands() as $command) { + $this->add($command); + } + } + + public function setDispatcher(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * Runs the current application. + * + * @param InputInterface $input An Input instance + * @param OutputInterface $output An Output instance + * + * @return integer 0 if everything went fine, or an error code + * + * @throws \Exception When doRun returns Exception + * + * @api + */ + public function run(InputInterface $input = null, OutputInterface $output = null) + { + if (null === $input) { + $input = new ArgvInput(); + } + + if (null === $output) { + $output = new ConsoleOutput(); + } + + $this->configureIO($input, $output); + + try { + $exitCode = $this->doRun($input, $output); + } catch (\Exception $e) { + if (!$this->catchExceptions) { + throw $e; + } + + if ($output instanceof ConsoleOutputInterface) { + $this->renderException($e, $output->getErrorOutput()); + } else { + $this->renderException($e, $output); + } + + $exitCode = $e->getCode(); + if (is_numeric($exitCode)) { + $exitCode = (int) $exitCode; + if (0 === $exitCode) { + $exitCode = 1; + } + } else { + $exitCode = 1; + } + } + + if ($this->autoExit) { + if ($exitCode > 255) { + $exitCode = 255; + } + // @codeCoverageIgnoreStart + exit($exitCode); + // @codeCoverageIgnoreEnd + } + + return $exitCode; + } + + /** + * Runs the current application. + * + * @param InputInterface $input An Input instance + * @param OutputInterface $output An Output instance + * + * @return integer 0 if everything went fine, or an error code + */ + public function doRun(InputInterface $input, OutputInterface $output) + { + if (true === $input->hasParameterOption(array('--version', '-V'))) { + $output->writeln($this->getLongVersion()); + + return 0; + } + + $name = $this->getCommandName($input); + if (true === $input->hasParameterOption(array('--help', '-h'))) { + if (!$name) { + $name = 'help'; + $input = new ArrayInput(array('command' => 'help')); + } else { + $this->wantHelps = true; + } + } + + if (!$name) { + $name = 'list'; + $input = new ArrayInput(array('command' => 'list')); + } + + // the command name MUST be the first element of the input + $command = $this->find($name); + + $this->runningCommand = $command; + $exitCode = $this->doRunCommand($command, $input, $output); + $this->runningCommand = null; + + return $exitCode; + } + + /** + * Set a helper set to be used with the command. + * + * @param HelperSet $helperSet The helper set + * + * @api + */ + public function setHelperSet(HelperSet $helperSet) + { + $this->helperSet = $helperSet; + } + + /** + * Get the helper set associated with the command. + * + * @return HelperSet The HelperSet instance associated with this command + * + * @api + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Set an input definition set to be used with this application + * + * @param InputDefinition $definition The input definition + * + * @api + */ + public function setDefinition(InputDefinition $definition) + { + $this->definition = $definition; + } + + /** + * Gets the InputDefinition related to this Application. + * + * @return InputDefinition The InputDefinition instance + */ + public function getDefinition() + { + return $this->definition; + } + + /** + * Gets the help message. + * + * @return string A help message. + */ + public function getHelp() + { + $messages = array( + $this->getLongVersion(), + '', + 'Usage:', + ' [options] command [arguments]', + '', + 'Options:', + ); + + foreach ($this->getDefinition()->getOptions() as $option) { + $messages[] = sprintf(' %-29s %s %s', + '--'.$option->getName().'', + $option->getShortcut() ? '-'.$option->getShortcut().'' : ' ', + $option->getDescription() + ); + } + + return implode(PHP_EOL, $messages); + } + + /** + * Sets whether to catch exceptions or not during commands execution. + * + * @param Boolean $boolean Whether to catch exceptions or not during commands execution + * + * @api + */ + public function setCatchExceptions($boolean) + { + $this->catchExceptions = (Boolean) $boolean; + } + + /** + * Sets whether to automatically exit after a command execution or not. + * + * @param Boolean $boolean Whether to automatically exit after a command execution or not + * + * @api + */ + public function setAutoExit($boolean) + { + $this->autoExit = (Boolean) $boolean; + } + + /** + * Gets the name of the application. + * + * @return string The application name + * + * @api + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the application name. + * + * @param string $name The application name + * + * @api + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Gets the application version. + * + * @return string The application version + * + * @api + */ + public function getVersion() + { + return $this->version; + } + + /** + * Sets the application version. + * + * @param string $version The application version + * + * @api + */ + public function setVersion($version) + { + $this->version = $version; + } + + /** + * Returns the long version of the application. + * + * @return string The long application version + * + * @api + */ + public function getLongVersion() + { + if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) { + return sprintf('%s version %s', $this->getName(), $this->getVersion()); + } + + return 'Console Tool'; + } + + /** + * Registers a new command. + * + * @param string $name The command name + * + * @return Command The newly created command + * + * @api + */ + public function register($name) + { + return $this->add(new Command($name)); + } + + /** + * Adds an array of command objects. + * + * @param Command[] $commands An array of commands + * + * @api + */ + public function addCommands(array $commands) + { + foreach ($commands as $command) { + $this->add($command); + } + } + + /** + * Adds a command object. + * + * If a command with the same name already exists, it will be overridden. + * + * @param Command $command A Command object + * + * @return Command The registered command + * + * @api + */ + public function add(Command $command) + { + $command->setApplication($this); + + if (!$command->isEnabled()) { + $command->setApplication(null); + + return; + } + + $this->commands[$command->getName()] = $command; + + foreach ($command->getAliases() as $alias) { + $this->commands[$alias] = $command; + } + + return $command; + } + + /** + * Returns a registered command by name or alias. + * + * @param string $name The command name or alias + * + * @return Command A Command object + * + * @throws \InvalidArgumentException When command name given does not exist + * + * @api + */ + public function get($name) + { + if (!isset($this->commands[$name])) { + throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name)); + } + + $command = $this->commands[$name]; + + if ($this->wantHelps) { + $this->wantHelps = false; + + $helpCommand = $this->get('help'); + $helpCommand->setCommand($command); + + return $helpCommand; + } + + return $command; + } + + /** + * Returns true if the command exists, false otherwise. + * + * @param string $name The command name or alias + * + * @return Boolean true if the command exists, false otherwise + * + * @api + */ + public function has($name) + { + return isset($this->commands[$name]); + } + + /** + * Returns an array of all unique namespaces used by currently registered commands. + * + * It does not returns the global namespace which always exists. + * + * @return array An array of namespaces + */ + public function getNamespaces() + { + $namespaces = array(); + foreach ($this->commands as $command) { + $namespaces[] = $this->extractNamespace($command->getName()); + + foreach ($command->getAliases() as $alias) { + $namespaces[] = $this->extractNamespace($alias); + } + } + + return array_values(array_unique(array_filter($namespaces))); + } + + /** + * Finds a registered namespace by a name or an abbreviation. + * + * @param string $namespace A namespace or abbreviation to search for + * + * @return string A registered namespace + * + * @throws \InvalidArgumentException When namespace is incorrect or ambiguous + */ + public function findNamespace($namespace) + { + $allNamespaces = $this->getNamespaces(); + $found = ''; + foreach (explode(':', $namespace) as $i => $part) { + // select sub-namespaces matching the current namespace we found + $namespaces = array(); + foreach ($allNamespaces as $n) { + if ('' === $found || 0 === strpos($n, $found)) { + $namespaces[$n] = explode(':', $n); + } + } + + $abbrevs = static::getAbbreviations(array_unique(array_values(array_filter(array_map(function ($p) use ($i) { return isset($p[$i]) ? $p[$i] : ''; }, $namespaces))))); + + if (!isset($abbrevs[$part])) { + $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); + + if (1 <= $i) { + $part = $found.':'.$part; + } + + if ($alternatives = $this->findAlternativeNamespace($part, $abbrevs)) { + if (1 == count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + + $message .= implode("\n ", $alternatives); + } + + throw new \InvalidArgumentException($message); + } + + // there are multiple matches, but $part is an exact match of one of them so we select it + if (in_array($part, $abbrevs[$part])) { + $abbrevs[$part] = array($part); + } + + if (count($abbrevs[$part]) > 1) { + throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions($abbrevs[$part]))); + } + + $found .= $found ? ':' . $abbrevs[$part][0] : $abbrevs[$part][0]; + } + + return $found; + } + + /** + * Finds a command by name or alias. + * + * Contrary to get, this command tries to find the best + * match if you give it an abbreviation of a name or alias. + * + * @param string $name A command name or a command alias + * + * @return Command A Command instance + * + * @throws \InvalidArgumentException When command name is incorrect or ambiguous + * + * @api + */ + public function find($name) + { + // namespace + $namespace = ''; + $searchName = $name; + if (false !== $pos = strrpos($name, ':')) { + $namespace = $this->findNamespace(substr($name, 0, $pos)); + $searchName = $namespace.substr($name, $pos); + } + + // name + $commands = array(); + foreach ($this->commands as $command) { + $extractedNamespace = $this->extractNamespace($command->getName()); + if ($extractedNamespace === $namespace + || !empty($namespace) && 0 === strpos($extractedNamespace, $namespace) + ) { + $commands[] = $command->getName(); + } + } + + $abbrevs = static::getAbbreviations(array_unique($commands)); + if (isset($abbrevs[$searchName]) && 1 == count($abbrevs[$searchName])) { + return $this->get($abbrevs[$searchName][0]); + } + + if (isset($abbrevs[$searchName]) && in_array($searchName, $abbrevs[$searchName])) { + return $this->get($searchName); + } + + if (isset($abbrevs[$searchName]) && count($abbrevs[$searchName]) > 1) { + $suggestions = $this->getAbbreviationSuggestions($abbrevs[$searchName]); + + throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions)); + } + + // aliases + $aliases = array(); + foreach ($this->commands as $command) { + foreach ($command->getAliases() as $alias) { + $extractedNamespace = $this->extractNamespace($alias); + if ($extractedNamespace === $namespace + || !empty($namespace) && 0 === strpos($extractedNamespace, $namespace) + ) { + $aliases[] = $alias; + } + } + } + + $aliases = static::getAbbreviations(array_unique($aliases)); + if (!isset($aliases[$searchName])) { + $message = sprintf('Command "%s" is not defined.', $name); + + if ($alternatives = $this->findAlternativeCommands($searchName, $abbrevs)) { + if (1 == count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + $message .= implode("\n ", $alternatives); + } + + throw new \InvalidArgumentException($message); + } + + if (count($aliases[$searchName]) > 1) { + throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $this->getAbbreviationSuggestions($aliases[$searchName]))); + } + + return $this->get($aliases[$searchName][0]); + } + + /** + * Gets the commands (registered in the given namespace if provided). + * + * The array keys are the full names and the values the command instances. + * + * @param string $namespace A namespace name + * + * @return Command[] An array of Command instances + * + * @api + */ + public function all($namespace = null) + { + if (null === $namespace) { + return $this->commands; + } + + $commands = array(); + foreach ($this->commands as $name => $command) { + if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) { + $commands[$name] = $command; + } + } + + return $commands; + } + + /** + * Returns an array of possible abbreviations given a set of names. + * + * @param array $names An array of names + * + * @return array An array of abbreviations + */ + public static function getAbbreviations($names) + { + $abbrevs = array(); + foreach ($names as $name) { + for ($len = strlen($name); $len > 0; --$len) { + $abbrev = substr($name, 0, $len); + $abbrevs[$abbrev][] = $name; + } + } + + return $abbrevs; + } + + /** + * Returns a text representation of the Application. + * + * @param string $namespace An optional namespace name + * @param boolean $raw Whether to return raw command list + * + * @return string A string representing the Application + * + * @deprecated Deprecated since version 2.3, to be removed in 3.0. + */ + public function asText($namespace = null, $raw = false) + { + $descriptor = new TextDescriptor(); + + return $descriptor->describe($this, array('namespace' => $namespace, 'raw_text' => $raw)); + } + + /** + * Returns an XML representation of the Application. + * + * @param string $namespace An optional namespace name + * @param Boolean $asDom Whether to return a DOM or an XML string + * + * @return string|\DOMDocument An XML string representing the Application + * + * @deprecated Deprecated since version 2.3, to be removed in 3.0. + */ + public function asXml($namespace = null, $asDom = false) + { + $descriptor = new XmlDescriptor(); + + return $descriptor->describe($this, array('namespace' => $namespace, 'as_dom' => $asDom)); + } + + /** + * Renders a caught exception. + * + * @param Exception $e An exception instance + * @param OutputInterface $output An OutputInterface instance + */ + public function renderException($e, $output) + { + $strlen = function ($string) { + if (!function_exists('mb_strlen')) { + return strlen($string); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return strlen($string); + } + + return mb_strlen($string, $encoding); + }; + + do { + $title = sprintf(' [%s] ', get_class($e)); + $len = $strlen($title); + $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX; + $lines = array(); + foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { + foreach (str_split($line, $width - 4) as $line) { + $lines[] = sprintf(' %s ', $line); + $len = max($strlen($line) + 4, $len); + } + } + + $messages = array(str_repeat(' ', $len), $title.str_repeat(' ', max(0, $len - $strlen($title)))); + + foreach ($lines as $line) { + $messages[] = $line.str_repeat(' ', $len - $strlen($line)); + } + + $messages[] = str_repeat(' ', $len); + + $output->writeln(""); + $output->writeln(""); + foreach ($messages as $message) { + $output->writeln(''.$message.''); + } + $output->writeln(""); + $output->writeln(""); + + if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $output->writeln('Exception trace:'); + + // exception related properties + $trace = $e->getTrace(); + array_unshift($trace, array( + 'function' => '', + 'file' => $e->getFile() != null ? $e->getFile() : 'n/a', + 'line' => $e->getLine() != null ? $e->getLine() : 'n/a', + 'args' => array(), + )); + + for ($i = 0, $count = count($trace); $i < $count; $i++) { + $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; + $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; + $function = $trace[$i]['function']; + $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; + $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; + + $output->writeln(sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line)); + } + + $output->writeln(""); + $output->writeln(""); + } + } while ($e = $e->getPrevious()); + + if (null !== $this->runningCommand) { + $output->writeln(sprintf('%s', sprintf($this->runningCommand->getSynopsis(), $this->getName()))); + $output->writeln(""); + $output->writeln(""); + } + } + + /** + * Tries to figure out the terminal width in which this application runs + * + * @return int|null + */ + protected function getTerminalWidth() + { + $dimensions = $this->getTerminalDimensions(); + + return $dimensions[0]; + } + + /** + * Tries to figure out the terminal height in which this application runs + * + * @return int|null + */ + protected function getTerminalHeight() + { + $dimensions = $this->getTerminalDimensions(); + + return $dimensions[1]; + } + + /** + * Tries to figure out the terminal dimensions based on the current environment + * + * @return array Array containing width and height + */ + public function getTerminalDimensions() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + // extract [w, H] from "wxh (WxH)" + if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) { + return array((int) $matches[1], (int) $matches[2]); + } + // extract [w, h] from "wxh" + if (preg_match('/^(\d+)x(\d+)$/', $this->getConsoleMode(), $matches)) { + return array((int) $matches[1], (int) $matches[2]); + } + } + + if ($sttyString = $this->getSttyColumns()) { + // extract [w, h] from "rows h; columns w;" + if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { + return array((int) $matches[2], (int) $matches[1]); + } + // extract [w, h] from "; h rows; w columns" + if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { + return array((int) $matches[2], (int) $matches[1]); + } + } + + return array(null, null); + } + + /** + * Configures the input and output instances based on the user arguments and options. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + */ + protected function configureIO(InputInterface $input, OutputInterface $output) + { + if (true === $input->hasParameterOption(array('--ansi'))) { + $output->setDecorated(true); + } elseif (true === $input->hasParameterOption(array('--no-ansi'))) { + $output->setDecorated(false); + } + + if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) { + $input->setInteractive(false); + } elseif (function_exists('posix_isatty') && $this->getHelperSet()->has('dialog')) { + $inputStream = $this->getHelperSet()->get('dialog')->getInputStream(); + if (!@posix_isatty($inputStream)) { + $input->setInteractive(false); + } + } + + if (true === $input->hasParameterOption(array('--quiet', '-q'))) { + $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); + } else { + if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) { + $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); + } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); + } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); + } + } + } + + /** + * Runs the current command. + * + * If an event dispatcher has been attached to the application, + * events are also dispatched during the life-cycle of the command. + * + * @param Command $command A Command instance + * @param InputInterface $input An Input instance + * @param OutputInterface $output An Output instance + * + * @return integer 0 if everything went fine, or an error code + */ + protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) + { + if (null === $this->dispatcher) { + return $command->run($input, $output); + } + + $event = new ConsoleCommandEvent($command, $input, $output); + $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event); + + try { + $exitCode = $command->run($input, $output); + } catch (\Exception $e) { + $event = new ConsoleTerminateEvent($command, $input, $output, $e->getCode()); + $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); + + $event = new ConsoleExceptionEvent($command, $input, $output, $e, $event->getExitCode()); + $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event); + + throw $event->getException(); + } + + $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); + $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); + + return $event->getExitCode(); + } + + /** + * Gets the name of the command based on input. + * + * @param InputInterface $input The input interface + * + * @return string The command name + */ + protected function getCommandName(InputInterface $input) + { + return $input->getFirstArgument(); + } + + /** + * Gets the default input definition. + * + * @return InputDefinition An InputDefinition instance + */ + protected function getDefaultInputDefinition() + { + return new InputDefinition(array( + new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), + + new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message.'), + new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message.'), + new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), + new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version.'), + new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output.'), + new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output.'), + new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question.'), + )); + } + + /** + * Gets the default commands that should always be available. + * + * @return Command[] An array of default Command instances + */ + protected function getDefaultCommands() + { + return array(new HelpCommand(), new ListCommand()); + } + + /** + * Gets the default helper set with the helpers that should always be available. + * + * @return HelperSet A HelperSet instance + */ + protected function getDefaultHelperSet() + { + return new HelperSet(array( + new FormatterHelper(), + new DialogHelper(), + new ProgressHelper(), + new TableHelper(), + )); + } + + /** + * Runs and parses stty -a if it's available, suppressing any error output + * + * @return string + */ + private function getSttyColumns() + { + if (!function_exists('proc_open')) { + return; + } + + $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')); + $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, array('suppress_errors' => true)); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + return $info; + } + } + + /** + * Runs and parses mode CON if it's available, suppressing any error output + * + * @return string x or null if it could not be parsed + */ + private function getConsoleMode() + { + if (!function_exists('proc_open')) { + return; + } + + $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')); + $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, array('suppress_errors' => true)); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { + return $matches[2].'x'.$matches[1]; + } + } + } + + /** + * Returns abbreviated suggestions in string format. + * + * @param array $abbrevs Abbreviated suggestions to convert + * + * @return string A formatted string of abbreviated suggestions + */ + private function getAbbreviationSuggestions($abbrevs) + { + return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''); + } + + /** + * Returns the namespace part of the command name. + * + * This method is not part of public API and should not be used directly. + * + * @param string $name The full name of the command + * @param string $limit The maximum number of parts of the namespace + * + * @return string The namespace of the command + */ + public function extractNamespace($name, $limit = null) + { + $parts = explode(':', $name); + array_pop($parts); + + return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit)); + } + + /** + * Finds alternative commands of $name + * + * @param string $name The full name of the command + * @param array $abbrevs The abbreviations + * + * @return array A sorted array of similar commands + */ + private function findAlternativeCommands($name, $abbrevs) + { + $callback = function($item) { + return $item->getName(); + }; + + return $this->findAlternatives($name, $this->commands, $abbrevs, $callback); + } + + /** + * Finds alternative namespace of $name + * + * @param string $name The full name of the namespace + * @param array $abbrevs The abbreviations + * + * @return array A sorted array of similar namespace + */ + private function findAlternativeNamespace($name, $abbrevs) + { + return $this->findAlternatives($name, $this->getNamespaces(), $abbrevs); + } + + /** + * Finds alternative of $name among $collection, + * if nothing is found in $collection, try in $abbrevs + * + * @param string $name The string + * @param array|Traversable $collection The collection + * @param array $abbrevs The abbreviations + * @param Closure|string|array $callback The callable to transform collection item before comparison + * + * @return array A sorted array of similar string + */ + private function findAlternatives($name, $collection, $abbrevs, $callback = null) + { + $alternatives = array(); + + foreach ($collection as $item) { + if (null !== $callback) { + $item = call_user_func($callback, $item); + } + + $lev = levenshtein($name, $item); + if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { + $alternatives[$item] = $lev; + } + } + + if (!$alternatives) { + foreach ($abbrevs as $key => $values) { + $lev = levenshtein($name, $key); + if ($lev <= strlen($name) / 3 || false !== strpos($key, $name)) { + foreach ($values as $value) { + $alternatives[$value] = $lev; + } + } + } + } + + asort($alternatives); + + return array_keys($alternatives); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/CHANGELOG.md b/vendor/symfony/console/Symfony/Component/Console/CHANGELOG.md new file mode 100644 index 0000000..e4213af --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/CHANGELOG.md @@ -0,0 +1,38 @@ +CHANGELOG +========= + +2.3.0 +----- + + * added multiselect support to the select dialog helper + * added Table Helper for tabular data rendering + * added support for events in `Application` + * added a way to normalize EOLs in `ApplicationTester::getDisplay()` and `CommandTester::getDisplay()` + * added a way to set the progress bar progress via the `setCurrent` method + * added support for multiple InputOption shortcuts, written as `'-a|-b|-c'` + * added two additional verbosity levels, VERBOSITY_VERY_VERBOSE and VERBOSITY_DEBUG + +2.2.0 +----- + + * added support for colorization on Windows via ConEmu + * add a method to Dialog Helper to ask for a question and hide the response + * added support for interactive selections in console (DialogHelper::select()) + * added support for autocompletion as you type in Dialog Helper + +2.1.0 +----- + + * added ConsoleOutputInterface + * added the possibility to disable a command (Command::isEnabled()) + * added suggestions when a command does not exist + * added a --raw option to the list command + * added support for STDERR in the console output class (errors are now sent + to STDERR) + * made the defaults (helper set, commands, input definition) in Application + more easily customizable + * added support for the shell even if readline is not available + * added support for process isolation in Symfony shell via + `--process-isolation` switch + * added support for `--`, which disables options parsing after that point + (tokens will be parsed as arguments) diff --git a/vendor/symfony/console/Symfony/Component/Console/Command/Command.php b/vendor/symfony/console/Symfony/Component/Console/Command/Command.php new file mode 100644 index 0000000..af1b91f --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Command/Command.php @@ -0,0 +1,605 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Helper\HelperSet; + +/** + * Base class for all commands. + * + * @author Fabien Potencier + * + * @api + */ +class Command +{ + private $application; + private $name; + private $aliases; + private $definition; + private $help; + private $description; + private $ignoreValidationErrors; + private $applicationDefinitionMerged; + private $applicationDefinitionMergedWithArgs; + private $code; + private $synopsis; + private $helperSet; + + /** + * Constructor. + * + * @param string $name The name of the command + * + * @throws \LogicException When the command name is empty + * + * @api + */ + public function __construct($name = null) + { + $this->definition = new InputDefinition(); + $this->ignoreValidationErrors = false; + $this->applicationDefinitionMerged = false; + $this->applicationDefinitionMergedWithArgs = false; + $this->aliases = array(); + + if (null !== $name) { + $this->setName($name); + } + + $this->configure(); + + if (!$this->name) { + throw new \LogicException('The command name cannot be empty.'); + } + } + + /** + * Ignores validation errors. + * + * This is mainly useful for the help command. + */ + public function ignoreValidationErrors() + { + $this->ignoreValidationErrors = true; + } + + /** + * Sets the application instance for this command. + * + * @param Application $application An Application instance + * + * @api + */ + public function setApplication(Application $application = null) + { + $this->application = $application; + if ($application) { + $this->setHelperSet($application->getHelperSet()); + } else { + $this->helperSet = null; + } + } + + /** + * Sets the helper set. + * + * @param HelperSet $helperSet A HelperSet instance + */ + public function setHelperSet(HelperSet $helperSet) + { + $this->helperSet = $helperSet; + } + + /** + * Gets the helper set. + * + * @return HelperSet A HelperSet instance + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Gets the application instance for this command. + * + * @return Application An Application instance + * + * @api + */ + public function getApplication() + { + return $this->application; + } + + /** + * Checks whether the command is enabled or not in the current environment + * + * Override this to check for x or y and return false if the command can not + * run properly under the current conditions. + * + * @return Boolean + */ + public function isEnabled() + { + return true; + } + + /** + * Configures the current command. + */ + protected function configure() + { + } + + /** + * Executes the current command. + * + * This method is not abstract because you can use this class + * as a concrete class. In this case, instead of defining the + * execute() method, you set the code to execute by passing + * a Closure to the setCode() method. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return null|integer null or 0 if everything went fine, or an error code + * + * @throws \LogicException When this abstract method is not implemented + * @see setCode() + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + throw new \LogicException('You must override the execute() method in the concrete command class.'); + } + + /** + * Interacts with the user. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + */ + protected function interact(InputInterface $input, OutputInterface $output) + { + } + + /** + * Initializes the command just after the input has been validated. + * + * This is mainly useful when a lot of commands extends one main command + * where some things need to be initialized based on the input arguments and options. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + */ + protected function initialize(InputInterface $input, OutputInterface $output) + { + } + + /** + * Runs the command. + * + * The code to execute is either defined directly with the + * setCode() method or by overriding the execute() method + * in a sub-class. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return integer The command exit code + * + * @throws \Exception + * + * @see setCode() + * @see execute() + * + * @api + */ + public function run(InputInterface $input, OutputInterface $output) + { + // force the creation of the synopsis before the merge with the app definition + $this->getSynopsis(); + + // add the application arguments and options + $this->mergeApplicationDefinition(); + + // bind the input against the command specific arguments/options + try { + $input->bind($this->definition); + } catch (\Exception $e) { + if (!$this->ignoreValidationErrors) { + throw $e; + } + } + + $this->initialize($input, $output); + + if ($input->isInteractive()) { + $this->interact($input, $output); + } + + $input->validate(); + + if ($this->code) { + $statusCode = call_user_func($this->code, $input, $output); + } else { + $statusCode = $this->execute($input, $output); + } + + return is_numeric($statusCode) ? (int) $statusCode : 0; + } + + /** + * Sets the code to execute when running this command. + * + * If this method is used, it overrides the code defined + * in the execute() method. + * + * @param callable $code A callable(InputInterface $input, OutputInterface $output) + * + * @return Command The current instance + * + * @throws \InvalidArgumentException + * + * @see execute() + * + * @api + */ + public function setCode($code) + { + if (!is_callable($code)) { + throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.'); + } + + $this->code = $code; + + return $this; + } + + /** + * Merges the application definition with the command definition. + * + * This method is not part of public API and should not be used directly. + * + * @param Boolean $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments + */ + public function mergeApplicationDefinition($mergeArgs = true) + { + if (null === $this->application || (true === $this->applicationDefinitionMerged && ($this->applicationDefinitionMergedWithArgs || !$mergeArgs))) { + return; + } + + if ($mergeArgs) { + $currentArguments = $this->definition->getArguments(); + $this->definition->setArguments($this->application->getDefinition()->getArguments()); + $this->definition->addArguments($currentArguments); + } + + $this->definition->addOptions($this->application->getDefinition()->getOptions()); + + $this->applicationDefinitionMerged = true; + if ($mergeArgs) { + $this->applicationDefinitionMergedWithArgs = true; + } + } + + /** + * Sets an array of argument and option instances. + * + * @param array|InputDefinition $definition An array of argument and option instances or a definition instance + * + * @return Command The current instance + * + * @api + */ + public function setDefinition($definition) + { + if ($definition instanceof InputDefinition) { + $this->definition = $definition; + } else { + $this->definition->setDefinition($definition); + } + + $this->applicationDefinitionMerged = false; + + return $this; + } + + /** + * Gets the InputDefinition attached to this Command. + * + * @return InputDefinition An InputDefinition instance + * + * @api + */ + public function getDefinition() + { + return $this->definition; + } + + /** + * Gets the InputDefinition to be used to create XML and Text representations of this Command. + * + * Can be overridden to provide the original command representation when it would otherwise + * be changed by merging with the application InputDefinition. + * + * This method is not part of public API and should not be used directly. + * + * @return InputDefinition An InputDefinition instance + */ + public function getNativeDefinition() + { + return $this->getDefinition(); + } + + /** + * Adds an argument. + * + * @param string $name The argument name + * @param integer $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL + * @param string $description A description text + * @param mixed $default The default value (for InputArgument::OPTIONAL mode only) + * + * @return Command The current instance + * + * @api + */ + public function addArgument($name, $mode = null, $description = '', $default = null) + { + $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); + + return $this; + } + + /** + * Adds an option. + * + * @param string $name The option name + * @param string $shortcut The shortcut (can be null) + * @param integer $mode The option mode: One of the InputOption::VALUE_* constants + * @param string $description A description text + * @param mixed $default The default value (must be null for InputOption::VALUE_REQUIRED or InputOption::VALUE_NONE) + * + * @return Command The current instance + * + * @api + */ + public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); + + return $this; + } + + /** + * Sets the name of the command. + * + * This method can set both the namespace and the name if + * you separate them by a colon (:) + * + * $command->setName('foo:bar'); + * + * @param string $name The command name + * + * @return Command The current instance + * + * @throws \InvalidArgumentException When command name given is empty + * + * @api + */ + public function setName($name) + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * Returns the command name. + * + * @return string The command name + * + * @api + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the description for the command. + * + * @param string $description The description for the command + * + * @return Command The current instance + * + * @api + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * Returns the description for the command. + * + * @return string The description for the command + * + * @api + */ + public function getDescription() + { + return $this->description; + } + + /** + * Sets the help for the command. + * + * @param string $help The help for the command + * + * @return Command The current instance + * + * @api + */ + public function setHelp($help) + { + $this->help = $help; + + return $this; + } + + /** + * Returns the help for the command. + * + * @return string The help for the command + * + * @api + */ + public function getHelp() + { + return $this->help; + } + + /** + * Returns the processed help for the command replacing the %command.name% and + * %command.full_name% patterns with the real values dynamically. + * + * @return string The processed help for the command + */ + public function getProcessedHelp() + { + $name = $this->name; + + $placeholders = array( + '%command.name%', + '%command.full_name%' + ); + $replacements = array( + $name, + $_SERVER['PHP_SELF'].' '.$name + ); + + return str_replace($placeholders, $replacements, $this->getHelp()); + } + + /** + * Sets the aliases for the command. + * + * @param array $aliases An array of aliases for the command + * + * @return Command The current instance + * + * @api + */ + public function setAliases($aliases) + { + foreach ($aliases as $alias) { + $this->validateName($alias); + } + + $this->aliases = $aliases; + + return $this; + } + + /** + * Returns the aliases for the command. + * + * @return array An array of aliases for the command + * + * @api + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * Returns the synopsis for the command. + * + * @return string The synopsis + */ + public function getSynopsis() + { + if (null === $this->synopsis) { + $this->synopsis = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis())); + } + + return $this->synopsis; + } + + /** + * Gets a helper instance by name. + * + * @param string $name The helper name + * + * @return mixed The helper value + * + * @throws \InvalidArgumentException if the helper is not defined + * + * @api + */ + public function getHelper($name) + { + return $this->helperSet->get($name); + } + + /** + * Returns a text representation of the command. + * + * @return string A string representing the command + * + * @deprecated Deprecated since version 2.3, to be removed in 3.0. + */ + public function asText() + { + $descriptor = new TextDescriptor(); + + return $descriptor->describe($this); + } + + /** + * Returns an XML representation of the command. + * + * @param Boolean $asDom Whether to return a DOM or an XML string + * + * @return string|\DOMDocument An XML string representing the command + * + * @deprecated Deprecated since version 2.3, to be removed in 3.0. + */ + public function asXml($asDom = false) + { + $descriptor = new XmlDescriptor(); + + return $descriptor->describe($this, array('as_dom' => $asDom)); + } + + private function validateName($name) + { + if (!preg_match('/^[^\:]+(\:[^\:]+)*$/', $name)) { + throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Command/HelpCommand.php b/vendor/symfony/console/Symfony/Component/Console/Command/HelpCommand.php new file mode 100644 index 0000000..2aa933e --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Command/HelpCommand.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * HelpCommand displays the help for a given command. + * + * @author Fabien Potencier + */ +class HelpCommand extends Command +{ + private $command; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->ignoreValidationErrors(); + + $this + ->setName('help') + ->setDefinition(array( + new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), + new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'To output help in other formats'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), + )) + ->setDescription('Displays help for a command') + ->setHelp(<<%command.name% command displays help for a given command: + + php %command.full_name% list + +You can also output the help in other formats by using the --format option: + + php %command.full_name% --format=xml list + +To display the list of available commands, please use the list command. +EOF + ) + ; + } + + /** + * Sets the command + * + * @param Command $command The command to set + */ + public function setCommand(Command $command) + { + $this->command = $command; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + if (null === $this->command) { + $this->command = $this->getApplication()->find($input->getArgument('command_name')); + } + + if ($input->getOption('xml')) { + $input->setOption('format', 'xml'); + } + + $helper = new DescriptorHelper(); + $helper->describe($output, $this->command, $input->getOption('format'), $input->getOption('raw')); + $this->command = null; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Command/ListCommand.php b/vendor/symfony/console/Symfony/Component/Console/Command/ListCommand.php new file mode 100644 index 0000000..f3d7438 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Command/ListCommand.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputDefinition; + +/** + * ListCommand displays the list of all available commands for the application. + * + * @author Fabien Potencier + */ +class ListCommand extends Command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('list') + ->setDefinition($this->createDefinition()) + ->setDescription('Lists commands') + ->setHelp(<<%command.name% command lists all commands: + + php %command.full_name% + +You can also display the commands for a specific namespace: + + php %command.full_name% test + +You can also output the information in other formats by using the --format option: + + php %command.full_name% --format=xml + +It's also possible to get raw list of commands (useful for embedding command runner): + + php %command.full_name% --raw +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + public function getNativeDefinition() + { + return $this->createDefinition(); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + if ($input->getOption('xml')) { + $input->setOption('format', 'xml'); + } + + $helper = new DescriptorHelper(); + $helper->describe($output, $this->getApplication(), $input->getOption('format'), $input->getOption('raw'), $input->getArgument('namespace')); + } + + /** + * {@inheritdoc} + */ + private function createDefinition() + { + return new InputDefinition(array( + new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), + new InputOption('xml', null, InputOption::VALUE_NONE, 'To output list as XML'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'To output list in other formats'), + )); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/ConsoleEvents.php b/vendor/symfony/console/Symfony/Component/Console/ConsoleEvents.php new file mode 100644 index 0000000..12ede2d --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/ConsoleEvents.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +/** + * Contains all events dispatched by an Application. + * + * @author Francesco Levorato + */ +final class ConsoleEvents +{ + /** + * The COMMAND event allows you to attach listeners before any command is + * executed by the console. It also allows you to modify the command, input and output + * before they are handled to the command. + * + * The event listener method receives a Symfony\Component\Console\Event\ConsoleCommandEvent + * instance. + * + * @var string + */ + const COMMAND = 'console.command'; + + /** + * The TERMINATE event allows you to attach listeners after a command is + * executed by the console. + * + * The event listener method receives a Symfony\Component\Console\Event\ConsoleTerminateEvent + * instance. + * + * @var string + */ + const TERMINATE = 'console.terminate'; + + /** + * The EXCEPTION event occurs when an uncaught exception appears. + * + * This event allows you to deal with the exception or + * to modify the thrown exception. The event listener method receives + * a Symfony\Component\Console\Event\ConsoleExceptionEvent + * instance. + * + * @var string + */ + const EXCEPTION = 'console.exception'; +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Descriptor/ApplicationDescription.php b/vendor/symfony/console/Symfony/Component/Console/Descriptor/ApplicationDescription.php new file mode 100644 index 0000000..cdf1493 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Descriptor/ApplicationDescription.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; + +/** + * @author Jean-François Simon + */ +class ApplicationDescription +{ + const GLOBAL_NAMESPACE = '_global'; + + /** + * @var Application + */ + private $application; + + /** + * @var null|string + */ + private $namespace; + + /** + * @var array + */ + private $namespaces; + + /** + * @var Command[] + */ + private $commands; + + /** + * @var Command[] + */ + private $aliases; + + /** + * Constructor. + * + * @param Application $application + * @param string|null $namespace + */ + public function __construct(Application $application, $namespace = null) + { + $this->application = $application; + $this->namespace = $namespace; + } + + /** + * @return array + */ + public function getNamespaces() + { + if (null === $this->namespaces) { + $this->inspectApplication(); + } + + return $this->namespaces; + } + + /** + * @return Command[] + */ + public function getCommands() + { + if (null === $this->commands) { + $this->inspectApplication(); + } + + return $this->commands; + } + + /** + * @param string $name + * + * @return Command + * + * @throws \InvalidArgumentException + */ + public function getCommand($name) + { + if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { + throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name)); + } + + return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name]; + } + + private function inspectApplication() + { + $this->commands = array(); + $this->namespaces = array(); + + $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null); + foreach ($this->sortCommands($all) as $namespace => $commands) { + $names = array(); + + /** @var Command $command */ + foreach ($commands as $name => $command) { + if (!$command->getName()) { + continue; + } + + if ($command->getName() === $name) { + $this->commands[$name] = $command; + } else { + $this->aliases[$name] = $command; + } + + $names[] = $name; + } + + $this->namespaces[$namespace] = array('id' => $namespace, 'commands' => $names); + } + } + + /** + * @param array $commands + * + * @return array + */ + private function sortCommands(array $commands) + { + $namespacedCommands = array(); + foreach ($commands as $name => $command) { + $key = $this->application->extractNamespace($name, 1); + if (!$key) { + $key = '_global'; + } + + $namespacedCommands[$key][$name] = $command; + } + ksort($namespacedCommands); + + foreach ($namespacedCommands as &$commands) { + ksort($commands); + } + + return $namespacedCommands; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Descriptor/Descriptor.php b/vendor/symfony/console/Symfony/Component/Console/Descriptor/Descriptor.php new file mode 100644 index 0000000..659b8f4 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Descriptor/Descriptor.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * @author Jean-François Simon + */ +abstract class Descriptor implements DescriptorInterface +{ + public function describe($object, array $options = array()) + { + switch (true) { + case $object instanceof InputArgument: + return $this->describeInputArgument($object, $options); + case $object instanceof InputOption: + return $this->describeInputOption($object, $options); + case $object instanceof InputDefinition: + return $this->describeInputDefinition($object, $options); + case $object instanceof Command: + return $this->describeCommand($object, $options); + case $object instanceof Application: + return $this->describeApplication($object, $options); + } + + throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object))); + } + + /** + * Describes an InputArgument instance. + * + * @param InputArgument $argument + * @param array $options + * + * @return string|mixed + */ + abstract protected function describeInputArgument(InputArgument $argument, array $options = array()); + + /** + * Describes an InputOption instance. + * + * @param InputOption $option + * @param array $options + * + * @return string|mixed + */ + abstract protected function describeInputOption(InputOption $option, array $options = array()); + + /** + * Describes an InputDefinition instance. + * + * @param InputDefinition $definition + * @param array $options + * + * @return string|mixed + */ + abstract protected function describeInputDefinition(InputDefinition $definition, array $options = array()); + + /** + * Describes a Command instance. + * + * @param Command $command + * @param array $options + * + * @return string|mixed + */ + abstract protected function describeCommand(Command $command, array $options = array()); + + /** + * Describes an Application instance. + * + * @param Application $application + * @param array $options + * + * @return string|mixed + */ + abstract protected function describeApplication(Application $application, array $options = array()); +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Descriptor/DescriptorInterface.php b/vendor/symfony/console/Symfony/Component/Console/Descriptor/DescriptorInterface.php new file mode 100644 index 0000000..a4e8633 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Descriptor/DescriptorInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +/** + * Descriptor interface. + * + * @author Jean-François Simon + */ +interface DescriptorInterface +{ + /** + * Describes an InputArgument instance. + * + * @param object $object + * @param array $options + * + * @return string|mixed + */ + public function describe($object, array $options = array()); +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Descriptor/JsonDescriptor.php b/vendor/symfony/console/Symfony/Component/Console/Descriptor/JsonDescriptor.php new file mode 100644 index 0000000..c2b2a53 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Descriptor/JsonDescriptor.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * JSON descriptor. + * + * @author Jean-François Simon + */ +class JsonDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + return $this->output(array( + 'name' => $argument->getName(), + 'is_required' => $argument->isRequired(), + 'is_array' => $argument->isArray(), + 'description' => $argument->getDescription(), + 'default' => $argument->getDefault(), + ), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + return $this->output(array( + 'name' => '--'.$option->getName(), + 'shortcut' => $option->getShortcut() ? '-'.implode('|-', explode('|', $option->getShortcut())) : '', + 'accept_value' => $option->acceptValue(), + 'is_value_required' => $option->isValueRequired(), + 'is_multiple' => $option->isArray(), + 'description' => $option->getDescription(), + 'default' => $option->getDefault(), + ), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + $inputArguments = array(); + foreach ($definition->getArguments() as $name => $argument) { + $inputArguments[$name] = $this->describeInputArgument($argument, array('as_array' => true)); + } + + $inputOptions = array(); + foreach ($definition->getOptions() as $name => $option) { + $inputOptions[$name] = $this->describeInputOption($option, array('as_array' => true)); + } + + return $this->output(array('arguments' => $inputArguments, 'options' => $inputOptions), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + return $this->output(array( + 'name' => $command->getName(), + 'usage' => $command->getSynopsis(), + 'description' => $command->getDescription(), + 'help' => $command->getProcessedHelp(), + 'aliases' => $command->getAliases(), + 'definition' => $this->describeInputDefinition($command->getNativeDefinition(), array('as_array' => true)), + ), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ApplicationDescription($application, $describedNamespace); + $commands = array(); + + foreach ($description->getCommands() as $command) { + $commands[] = $this->describeCommand($command, array('as_array' => true)); + } + + $data = $describedNamespace + ? array('commands' => $commands, 'namespace' => $describedNamespace) + : array('commands' => $commands, 'namespaces' => array_values($description->getNamespaces())); + + return $this->output($data, $options); + } + + /** + * Outputs data as array or string according to options. + * + * @param array $data + * @param array $options + * + * @return array|string + */ + private function output(array $data, array $options) + { + if (isset($options['as_array']) && $options['as_array']) { + return $data; + } + + return json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php b/vendor/symfony/console/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php new file mode 100644 index 0000000..770af38 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * Markdown descriptor. + * + * @author Jean-François Simon + */ +class MarkdownDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + return '**'.$argument->getName().':**'."\n\n" + .'* Name: '.($argument->getName() ?: '')."\n" + .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n" + .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n" + .'* Description: '.($argument->getDescription() ?: '')."\n" + .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`'; + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + return '**'.$option->getName().':**'."\n\n" + .'* Name: `--'.$option->getName().'`'."\n" + .'* Shortcut: '.($option->getShortcut() ? '`-'.implode('|-', explode('|', $option->getShortcut())).'`' : '')."\n" + .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n" + .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n" + .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n" + .'* Description: '.($option->getDescription() ?: '')."\n" + .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`'; + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + $blocks = array(); + + if (count($definition->getArguments()) > 0) { + $blocks[] = '### Arguments:'; + foreach ($definition->getArguments() as $argument) { + $blocks[] = $this->describeInputArgument($argument); + } + } + + if (count($definition->getOptions()) > 0) { + $blocks[] = '### Options:'; + foreach ($definition->getOptions() as $option) { + $blocks[] = $this->describeInputOption($option); + } + } + + return implode("\n\n", $blocks); + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + $markdown = $command->getName()."\n" + .str_repeat('-', strlen($command->getName()))."\n\n" + .'* Description: '.($command->getDescription() ?: '')."\n" + .'* Usage: `'.$command->getSynopsis().'`'."\n" + .'* Aliases: '.(count($command->getAliases()) ? '`'.implode('`, `', $command->getAliases()).'`' : ''); + + if ($help = $command->getProcessedHelp()) { + $markdown .= "\n\n".$help; + } + + if ($definitionMarkdown = $this->describeInputDefinition($command->getNativeDefinition())) { + $markdown .= "\n\n".$definitionMarkdown; + } + + return $markdown; + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ApplicationDescription($application, $describedNamespace); + $blocks = array($application->getName()."\n".str_repeat('=', strlen($application->getName()))); + + foreach ($description->getNamespaces() as $namespace) { + if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $blocks[] = '**'.$namespace['id'].':**'; + } + + $blocks[] = implode("\n", array_map(function ($commandName) { + return '* '.$commandName; + } , $namespace['commands'])); + } + + foreach ($description->getCommands() as $command) { + $blocks[] = $this->describeCommand($command); + } + + return implode("\n\n", $blocks); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Descriptor/TextDescriptor.php b/vendor/symfony/console/Symfony/Component/Console/Descriptor/TextDescriptor.php new file mode 100644 index 0000000..2003237 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Descriptor/TextDescriptor.php @@ -0,0 +1,210 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * Text descriptor. + * + * @author Jean-François Simon + */ +class TextDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + if (null !== $argument->getDefault() && (!is_array($argument->getDefault()) || count($argument->getDefault()))) { + $default = sprintf(' (default: %s)', $this->formatDefaultValue($argument->getDefault())); + } else { + $default = ''; + } + + $nameWidth = isset($options['name_width']) ? $options['name_width'] : strlen($argument->getName()); + $output = str_replace("\n", "\n".str_repeat(' ', $nameWidth + 2), $argument->getDescription()); + $output = sprintf(" %-${nameWidth}s %s%s", $argument->getName(), $output, $default); + + return isset($options['raw_text']) && $options['raw_text'] ? strip_tags($output) : $output; + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + if ($option->acceptValue() && null !== $option->getDefault() && (!is_array($option->getDefault()) || count($option->getDefault()))) { + $default = sprintf(' (default: %s)', $this->formatDefaultValue($option->getDefault())); + } else { + $default = ''; + } + + $nameWidth = isset($options['name_width']) ? $options['name_width'] : strlen($option->getName()); + $nameWithShortcutWidth = $nameWidth - strlen($option->getName()) - 2; + + $output = sprintf(" %s %-${nameWithShortcutWidth}s%s%s%s", + '--'.$option->getName(), + $option->getShortcut() ? sprintf('(-%s) ', $option->getShortcut()) : '', + str_replace("\n", "\n".str_repeat(' ', $nameWidth + 2), $option->getDescription()), + $default, + $option->isArray() ? ' (multiple values allowed)' : '' + ); + + return isset($options['raw_text']) && $options['raw_text'] ? strip_tags($output) : $output; + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + $nameWidth = 0; + foreach ($definition->getOptions() as $option) { + $nameLength = strlen($option->getName()) + 2; + if ($option->getShortcut()) { + $nameLength += strlen($option->getShortcut()) + 3; + } + $nameWidth = max($nameWidth, $nameLength); + } + foreach ($definition->getArguments() as $argument) { + $nameWidth = max($nameWidth, strlen($argument->getName())); + } + ++$nameWidth; + + $messages = array(); + + if ($definition->getArguments()) { + $messages[] = 'Arguments:'; + foreach ($definition->getArguments() as $argument) { + $messages[] = $this->describeInputArgument($argument, array('name_width' => $nameWidth)); + } + $messages[] = ''; + } + + if ($definition->getOptions()) { + $messages[] = 'Options:'; + foreach ($definition->getOptions() as $option) { + $messages[] = $this->describeInputOption($option, array('name_width' => $nameWidth)); + } + $messages[] = ''; + } + + $output = implode("\n", $messages); + + return isset($options['raw_text']) && $options['raw_text'] ? strip_tags($output) : $output; + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + $messages = array('Usage:', ' '.$command->getSynopsis(), ''); + + if ($command->getAliases()) { + $messages[] = 'Aliases: '.implode(', ', $command->getAliases()).''; + } + + $messages[] = $this->describeInputDefinition($command->getNativeDefinition()); + + if ($help = $command->getProcessedHelp()) { + $messages[] = 'Help:'; + $messages[] = ' '.str_replace("\n", "\n ", $help)."\n"; + } + + $output = implode("\n", $messages); + + return isset($options['raw_text']) && $options['raw_text'] ? strip_tags($output) : $output; + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ApplicationDescription($application, $describedNamespace); + $messages = array(); + + if (isset($options['raw_text']) && $options['raw_text']) { + $width = $this->getColumnWidth($description->getCommands()); + + foreach ($description->getCommands() as $command) { + $messages[] = sprintf("%-${width}s %s", $command->getName(), $command->getDescription()); + } + } else { + $width = $this->getColumnWidth($description->getCommands()); + + $messages[] = $application->getHelp(); + $messages[] = ''; + + if ($describedNamespace) { + $messages[] = sprintf("Available commands for the \"%s\" namespace:", $describedNamespace); + } else { + $messages[] = 'Available commands:'; + } + + // add commands by namespace + foreach ($description->getNamespaces() as $namespace) { + if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $messages[] = ''.$namespace['id'].''; + } + + foreach ($namespace['commands'] as $name) { + $messages[] = sprintf(" %-${width}s %s", $name, $description->getCommand($name)->getDescription()); + } + } + } + + $output = implode("\n", $messages); + + return isset($options['raw_text']) && $options['raw_text'] ? strip_tags($output) : $output; + } + + /** + * Formats input option/argument default value. + * + * @param mixed $default + * + * @return string + */ + private function formatDefaultValue($default) + { + if (version_compare(PHP_VERSION, '5.4', '<')) { + return str_replace('\/', '/', json_encode($default)); + } + + return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + /** + * @param Command[] $commands + * + * @return int + */ + private function getColumnWidth(array $commands) + { + $width = 0; + foreach ($commands as $command) { + $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width; + } + + return $width + 2; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Descriptor/XmlDescriptor.php b/vendor/symfony/console/Symfony/Component/Console/Descriptor/XmlDescriptor.php new file mode 100644 index 0000000..c828a9c --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Descriptor/XmlDescriptor.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * XML descriptor. + * + * @author Jean-François Simon + */ +class XmlDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $dom->appendChild($objectXML = $dom->createElement('argument')); + $objectXML->setAttribute('name', $argument->getName()); + $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0); + $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode($argument->getDescription())); + + $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); + $defaults = is_array($argument->getDefault()) ? $argument->getDefault() : (is_bool($argument->getDefault()) ? array(var_export($argument->getDefault(), true)) : ($argument->getDefault() ? array($argument->getDefault()) : array())); + foreach ($defaults as $default) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->appendChild($dom->createTextNode($default)); + } + + return $this->output($dom, $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $dom->appendChild($objectXML = $dom->createElement('option')); + $objectXML->setAttribute('name', '--'.$option->getName()); + $pos = strpos($option->getShortcut(), '|'); + if (false !== $pos) { + $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos)); + $objectXML->setAttribute('shortcuts', '-'.implode('|-', explode('|', $option->getShortcut()))); + } else { + $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : ''); + } + $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0); + $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0); + $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode($option->getDescription())); + + if ($option->acceptValue()) { + $defaults = is_array($option->getDefault()) ? $option->getDefault() : (is_bool($option->getDefault()) ? array(var_export($option->getDefault(), true)) : ($option->getDefault() ? array($option->getDefault()) : array())); + $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); + + if (!empty($defaults)) { + foreach ($defaults as $default) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->appendChild($dom->createTextNode($default)); + } + } + } + + return $this->output($dom, $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($definitionXML = $dom->createElement('definition')); + + $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments')); + foreach ($definition->getArguments() as $argument) { + $this->appendDocument($argumentsXML, $this->describeInputArgument($argument, array('as_dom' => true))); + } + + $definitionXML->appendChild($optionsXML = $dom->createElement('options')); + foreach ($definition->getOptions() as $option) { + $this->appendDocument($optionsXML, $this->describeInputOption($option, array('as_dom' => true))); + } + + return $this->output($dom, $options); + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($commandXML = $dom->createElement('command')); + + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + $commandXML->setAttribute('id', $command->getName()); + $commandXML->setAttribute('name', $command->getName()); + + $commandXML->appendChild($usageXML = $dom->createElement('usage')); + $usageXML->appendChild($dom->createTextNode(sprintf($command->getSynopsis(), ''))); + + $commandXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription()))); + + $commandXML->appendChild($helpXML = $dom->createElement('help')); + $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp()))); + + $commandXML->appendChild($aliasesXML = $dom->createElement('aliases')); + foreach ($command->getAliases() as $alias) { + $aliasesXML->appendChild($aliasXML = $dom->createElement('alias')); + $aliasXML->appendChild($dom->createTextNode($alias)); + } + + $definitionXML = $this->describeInputDefinition($command->getNativeDefinition(), array('as_dom' => true)); + $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); + + return $this->output($dom, $options); + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($rootXml = $dom->createElement('symfony')); + $rootXml->appendChild($commandsXML = $dom->createElement('commands')); + + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ApplicationDescription($application, $describedNamespace); + + if ($describedNamespace) { + $commandsXML->setAttribute('namespace', $describedNamespace); + } + + foreach ($description->getCommands() as $command) { + $this->appendDocument($commandsXML, $this->describeCommand($command, array('as_dom' => true))); + } + + if (!$describedNamespace) { + $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces')); + + foreach ($description->getNamespaces() as $namespace) { + $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace')); + $namespaceArrayXML->setAttribute('id', $namespace['id']); + + foreach ($namespace['commands'] as $name) { + $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command')); + $commandXML->appendChild($dom->createTextNode($name)); + } + } + } + + return $this->output($dom, $options); + } + + /** + * Appends document children to parent node. + * + * @param \DOMNode $parentNode + * @param \DOMNode $importedParent + */ + private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent) + { + foreach ($importedParent->childNodes as $childNode) { + $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); + } + } + + /** + * Outputs document as DOMDocument or string according to options. + * + * @param \DOMDocument $dom + * @param array $options + * + * @return \DOMDocument|string + */ + private function output(\DOMDocument $dom, array $options) + { + if (isset($options['as_dom']) && $options['as_dom']) { + return $dom; + } + + $dom->formatOutput = true; + + return $dom->saveXML(); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Event/ConsoleCommandEvent.php b/vendor/symfony/console/Symfony/Component/Console/Event/ConsoleCommandEvent.php new file mode 100644 index 0000000..222838c --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Event/ConsoleCommandEvent.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; + +/** + * Allows to do things before the command is executed. + * + * @author Fabien Potencier + */ +class ConsoleCommandEvent extends ConsoleEvent +{ +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Event/ConsoleEvent.php b/vendor/symfony/console/Symfony/Component/Console/Event/ConsoleEvent.php new file mode 100644 index 0000000..ab620c4 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Event/ConsoleEvent.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\EventDispatcher\Event; + +/** + * Allows to inspect input and output of a command. + * + * @author Francesco Levorato + */ +class ConsoleEvent extends Event +{ + protected $command; + + private $input; + private $output; + + public function __construct(Command $command, InputInterface $input, OutputInterface $output) + { + $this->command = $command; + $this->input = $input; + $this->output = $output; + } + + /** + * Gets the command that is executed. + * + * @return Command A Command instance + */ + public function getCommand() + { + return $this->command; + } + + /** + * Gets the input instance. + * + * @return InputInterface An InputInterface instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance. + * + * @return OutputInterface An OutputInterface instance + */ + public function getOutput() + { + return $this->output; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Event/ConsoleExceptionEvent.php b/vendor/symfony/console/Symfony/Component/Console/Event/ConsoleExceptionEvent.php new file mode 100644 index 0000000..fa054ec --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Event/ConsoleExceptionEvent.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Allows to handle exception thrown in a command. + * + * @author Fabien Potencier + */ +class ConsoleExceptionEvent extends ConsoleEvent +{ + private $exception; + private $exitCode; + + public function __construct(Command $command, InputInterface $input, OutputInterface $output, \Exception $exception, $exitCode) + { + parent::__construct($command, $input, $output); + + $this->setException($exception); + $this->exitCode = $exitCode; + } + + /** + * Returns the thrown exception. + * + * @return \Exception The thrown exception + */ + public function getException() + { + return $this->exception; + } + + /** + * Replaces the thrown exception. + * + * This exception will be thrown if no response is set in the event. + * + * @param \Exception $exception The thrown exception + */ + public function setException(\Exception $exception) + { + $this->exception = $exception; + } + + /** + * Gets the exit code. + * + * @return integer The command exit code + */ + public function getExitCode() + { + return $this->exitCode; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Event/ConsoleTerminateEvent.php b/vendor/symfony/console/Symfony/Component/Console/Event/ConsoleTerminateEvent.php new file mode 100644 index 0000000..9f80cb7 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Event/ConsoleTerminateEvent.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Allows to manipulate the exit code of a command after its execution. + * + * @author Francesco Levorato + */ +class ConsoleTerminateEvent extends ConsoleEvent +{ + /** + * The exit code of the command. + * + * @var integer + */ + private $exitCode; + + public function __construct(Command $command, InputInterface $input, OutputInterface $output, $exitCode) + { + parent::__construct($command, $input, $output); + + $this->setExitCode($exitCode); + } + + /** + * Sets the exit code. + * + * @param integer $exitCode The command exit code + */ + public function setExitCode($exitCode) + { + $this->exitCode = $exitCode; + } + + /** + * Gets the exit code. + * + * @return integer The command exit code + */ + public function getExitCode() + { + return $this->exitCode; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatter.php b/vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatter.php new file mode 100644 index 0000000..db4a2fa --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatter.php @@ -0,0 +1,248 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter class for console output. + * + * @author Konstantin Kudryashov + * + * @api + */ +class OutputFormatter implements OutputFormatterInterface +{ + /** + * The pattern to phrase the format. + */ + const FORMAT_PATTERN = '#(\\\\?)<(/?)([a-z][a-z0-9_=;-]+)?>((?: [^<\\\\]+ | (?!<(?:/?[a-z]|/>)). | .(?<=\\\\<) )*)#isx'; + + private $decorated; + private $styles = array(); + private $styleStack; + + /** + * Escapes "<" special char in given text. + * + * @param string $text Text to escape + * + * @return string Escaped text + */ + public static function escape($text) + { + return preg_replace('/([^\\\\]?) FormatterStyle" instances + * + * @api + */ + public function __construct($decorated = null, array $styles = array()) + { + $this->decorated = (Boolean) $decorated; + + $this->setStyle('error', new OutputFormatterStyle('white', 'red')); + $this->setStyle('info', new OutputFormatterStyle('green')); + $this->setStyle('comment', new OutputFormatterStyle('yellow')); + $this->setStyle('question', new OutputFormatterStyle('black', 'cyan')); + + foreach ($styles as $name => $style) { + $this->setStyle($name, $style); + } + + $this->styleStack = new OutputFormatterStyleStack(); + } + + /** + * Sets the decorated flag. + * + * @param Boolean $decorated Whether to decorate the messages or not + * + * @api + */ + public function setDecorated($decorated) + { + $this->decorated = (Boolean) $decorated; + } + + /** + * Gets the decorated flag. + * + * @return Boolean true if the output will decorate messages, false otherwise + * + * @api + */ + public function isDecorated() + { + return $this->decorated; + } + + /** + * Sets a new style. + * + * @param string $name The style name + * @param OutputFormatterStyleInterface $style The style instance + * + * @api + */ + public function setStyle($name, OutputFormatterStyleInterface $style) + { + $this->styles[strtolower($name)] = $style; + } + + /** + * Checks if output formatter has style with specified name. + * + * @param string $name + * + * @return Boolean + * + * @api + */ + public function hasStyle($name) + { + return isset($this->styles[strtolower($name)]); + } + + /** + * Gets style options from style with specified name. + * + * @param string $name + * + * @return OutputFormatterStyleInterface + * + * @throws \InvalidArgumentException When style isn't defined + * + * @api + */ + public function getStyle($name) + { + if (!$this->hasStyle($name)) { + throw new \InvalidArgumentException(sprintf('Undefined style: %s', $name)); + } + + return $this->styles[strtolower($name)]; + } + + /** + * Formats a message according to the given styles. + * + * @param string $message The message to style + * + * @return string The styled message + * + * @api + */ + public function format($message) + { + $message = preg_replace_callback(self::FORMAT_PATTERN, array($this, 'replaceStyle'), $message); + + return str_replace('\\<', '<', $message); + } + + /** + * @return OutputFormatterStyleStack + */ + public function getStyleStack() + { + return $this->styleStack; + } + + /** + * Replaces style of the output. + * + * @param array $match + * + * @return string The replaced style + */ + private function replaceStyle($match) + { + // we got "\<" escaped char + if ('\\' === $match[1]) { + return $this->applyCurrentStyle($match[0]); + } + + if ('' === $match[3]) { + if ('/' === $match[2]) { + // we got "" tag + $this->styleStack->pop(); + + return $this->applyCurrentStyle($match[4]); + } + + // we got "<>" tag + return '<>'.$this->applyCurrentStyle($match[4]); + } + + if (isset($this->styles[strtolower($match[3])])) { + $style = $this->styles[strtolower($match[3])]; + } else { + $style = $this->createStyleFromString($match[3]); + + if (false === $style) { + return $this->applyCurrentStyle($match[0]); + } + } + + if ('/' === $match[2]) { + $this->styleStack->pop($style); + } else { + $this->styleStack->push($style); + } + + return $this->applyCurrentStyle($match[4]); + } + + /** + * Tries to create new style instance from string. + * + * @param string $string + * + * @return OutputFormatterStyle|Boolean false if string is not format string + */ + private function createStyleFromString($string) + { + if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) { + return false; + } + + $style = new OutputFormatterStyle(); + foreach ($matches as $match) { + array_shift($match); + + if ('fg' == $match[0]) { + $style->setForeground($match[1]); + } elseif ('bg' == $match[0]) { + $style->setBackground($match[1]); + } else { + $style->setOption($match[1]); + } + } + + return $style; + } + + /** + * Applies current style from stack to text, if must be applied. + * + * @param string $text Input text + * + * @return string Styled text + */ + private function applyCurrentStyle($text) + { + return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterInterface.php b/vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterInterface.php new file mode 100644 index 0000000..0836084 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterInterface.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter interface for console output. + * + * @author Konstantin Kudryashov + * + * @api + */ +interface OutputFormatterInterface +{ + /** + * Sets the decorated flag. + * + * @param Boolean $decorated Whether to decorate the messages or not + * + * @api + */ + public function setDecorated($decorated); + + /** + * Gets the decorated flag. + * + * @return Boolean true if the output will decorate messages, false otherwise + * + * @api + */ + public function isDecorated(); + + /** + * Sets a new style. + * + * @param string $name The style name + * @param OutputFormatterStyleInterface $style The style instance + * + * @api + */ + public function setStyle($name, OutputFormatterStyleInterface $style); + + /** + * Checks if output formatter has style with specified name. + * + * @param string $name + * + * @return Boolean + * + * @api + */ + public function hasStyle($name); + + /** + * Gets style options from style with specified name. + * + * @param string $name + * + * @return OutputFormatterStyleInterface + * + * @api + */ + public function getStyle($name); + + /** + * Formats a message according to the given styles. + * + * @param string $message The message to style + * + * @return string The styled message + * + * @api + */ + public function format($message); +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyle.php b/vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyle.php new file mode 100644 index 0000000..ec47169 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyle.php @@ -0,0 +1,222 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter style class for defining styles. + * + * @author Konstantin Kudryashov + * + * @api + */ +class OutputFormatterStyle implements OutputFormatterStyleInterface +{ + private static $availableForegroundColors = array( + 'black' => 30, + 'red' => 31, + 'green' => 32, + 'yellow' => 33, + 'blue' => 34, + 'magenta' => 35, + 'cyan' => 36, + 'white' => 37 + ); + private static $availableBackgroundColors = array( + 'black' => 40, + 'red' => 41, + 'green' => 42, + 'yellow' => 43, + 'blue' => 44, + 'magenta' => 45, + 'cyan' => 46, + 'white' => 47 + ); + private static $availableOptions = array( + 'bold' => 1, + 'underscore' => 4, + 'blink' => 5, + 'reverse' => 7, + 'conceal' => 8 + ); + + private $foreground; + private $background; + private $options = array(); + + /** + * Initializes output formatter style. + * + * @param string $foreground The style foreground color name + * @param string $background The style background color name + * @param array $options The style options + * + * @api + */ + public function __construct($foreground = null, $background = null, array $options = array()) + { + if (null !== $foreground) { + $this->setForeground($foreground); + } + if (null !== $background) { + $this->setBackground($background); + } + if (count($options)) { + $this->setOptions($options); + } + } + + /** + * Sets style foreground color. + * + * @param string $color The color name + * + * @throws \InvalidArgumentException When the color name isn't defined + * + * @api + */ + public function setForeground($color = null) + { + if (null === $color) { + $this->foreground = null; + + return; + } + + if (!isset(static::$availableForegroundColors[$color])) { + throw new \InvalidArgumentException(sprintf( + 'Invalid foreground color specified: "%s". Expected one of (%s)', + $color, + implode(', ', array_keys(static::$availableForegroundColors)) + )); + } + + $this->foreground = static::$availableForegroundColors[$color]; + } + + /** + * Sets style background color. + * + * @param string $color The color name + * + * @throws \InvalidArgumentException When the color name isn't defined + * + * @api + */ + public function setBackground($color = null) + { + if (null === $color) { + $this->background = null; + + return; + } + + if (!isset(static::$availableBackgroundColors[$color])) { + throw new \InvalidArgumentException(sprintf( + 'Invalid background color specified: "%s". Expected one of (%s)', + $color, + implode(', ', array_keys(static::$availableBackgroundColors)) + )); + } + + $this->background = static::$availableBackgroundColors[$color]; + } + + /** + * Sets some specific style option. + * + * @param string $option The option name + * + * @throws \InvalidArgumentException When the option name isn't defined + * + * @api + */ + public function setOption($option) + { + if (!isset(static::$availableOptions[$option])) { + throw new \InvalidArgumentException(sprintf( + 'Invalid option specified: "%s". Expected one of (%s)', + $option, + implode(', ', array_keys(static::$availableOptions)) + )); + } + + if (false === array_search(static::$availableOptions[$option], $this->options)) { + $this->options[] = static::$availableOptions[$option]; + } + } + + /** + * Unsets some specific style option. + * + * @param string $option The option name + * + * @throws \InvalidArgumentException When the option name isn't defined + * + */ + public function unsetOption($option) + { + if (!isset(static::$availableOptions[$option])) { + throw new \InvalidArgumentException(sprintf( + 'Invalid option specified: "%s". Expected one of (%s)', + $option, + implode(', ', array_keys(static::$availableOptions)) + )); + } + + $pos = array_search(static::$availableOptions[$option], $this->options); + if (false !== $pos) { + unset($this->options[$pos]); + } + } + + /** + * Sets multiple style options at once. + * + * @param array $options + */ + public function setOptions(array $options) + { + $this->options = array(); + + foreach ($options as $option) { + $this->setOption($option); + } + } + + /** + * Applies the style to a given text. + * + * @param string $text The text to style + * + * @return string + */ + public function apply($text) + { + $codes = array(); + + if (null !== $this->foreground) { + $codes[] = $this->foreground; + } + if (null !== $this->background) { + $codes[] = $this->background; + } + if (count($this->options)) { + $codes = array_merge($codes, $this->options); + } + + if (0 === count($codes)) { + return $text; + } + + return sprintf("\033[%sm%s\033[0m", implode(';', $codes), $text); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php b/vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php new file mode 100644 index 0000000..e8642b3 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter style interface for defining styles. + * + * @author Konstantin Kudryashov + * + * @api + */ +interface OutputFormatterStyleInterface +{ + /** + * Sets style foreground color. + * + * @param string $color The color name + * + * @api + */ + public function setForeground($color = null); + + /** + * Sets style background color. + * + * @param string $color The color name + * + * @api + */ + public function setBackground($color = null); + + /** + * Sets some specific style option. + * + * @param string $option The option name + * + * @api + */ + public function setOption($option); + + /** + * Unsets some specific style option. + * + * @param string $option The option name + */ + public function unsetOption($option); + + /** + * Sets multiple style options at once. + * + * @param array $options + */ + public function setOptions(array $options); + + /** + * Applies the style to a given text. + * + * @param string $text The text to style + * + * @return string + */ + public function apply($text); +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php b/vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php new file mode 100644 index 0000000..5915023 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * @author Jean-François Simon + */ +class OutputFormatterStyleStack +{ + /** + * @var OutputFormatterStyleInterface[] + */ + private $styles; + + /** + * @var OutputFormatterStyleInterface + */ + private $emptyStyle; + + /** + * Constructor. + * + * @param OutputFormatterStyleInterface $emptyStyle + */ + public function __construct(OutputFormatterStyleInterface $emptyStyle = null) + { + $this->emptyStyle = $emptyStyle ?: new OutputFormatterStyle(); + $this->reset(); + } + + /** + * Resets stack (ie. empty internal arrays). + */ + public function reset() + { + $this->styles = array(); + } + + /** + * Pushes a style in the stack. + * + * @param OutputFormatterStyleInterface $style + */ + public function push(OutputFormatterStyleInterface $style) + { + $this->styles[] = $style; + } + + /** + * Pops a style from the stack. + * + * @param OutputFormatterStyleInterface $style + * + * @return OutputFormatterStyleInterface + * + * @throws \InvalidArgumentException When style tags incorrectly nested + */ + public function pop(OutputFormatterStyleInterface $style = null) + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + if (null === $style) { + return array_pop($this->styles); + } + + foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { + if ($style->apply('') === $stackedStyle->apply('')) { + $this->styles = array_slice($this->styles, 0, $index); + + return $stackedStyle; + } + } + + throw new \InvalidArgumentException('Incorrectly nested style tag found.'); + } + + /** + * Computes current style with stacks top codes. + * + * @return OutputFormatterStyle + */ + public function getCurrent() + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + return $this->styles[count($this->styles)-1]; + } + + /** + * @param OutputFormatterStyleInterface $emptyStyle + * + * @return OutputFormatterStyleStack + */ + public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle) + { + $this->emptyStyle = $emptyStyle; + + return $this; + } + + /** + * @return OutputFormatterStyleInterface + */ + public function getEmptyStyle() + { + return $this->emptyStyle; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Helper/DescriptorHelper.php b/vendor/symfony/console/Symfony/Component/Console/Helper/DescriptorHelper.php new file mode 100644 index 0000000..0317d5d --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Helper/DescriptorHelper.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Descriptor\DescriptorInterface; +use Symfony\Component\Console\Descriptor\JsonDescriptor; +use Symfony\Component\Console\Descriptor\MarkdownDescriptor; +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * This class adds helper method to describe objects in various formats. + * + * @author Jean-François Simon + */ +class DescriptorHelper extends Helper +{ + /** + * @var DescriptorInterface[] + */ + private $descriptors = array(); + + /** + * Constructor. + */ + public function __construct() + { + $this + ->register('txt', new TextDescriptor()) + ->register('xml', new XmlDescriptor()) + ->register('json', new JsonDescriptor()) + ->register('md', new MarkdownDescriptor()) + ; + } + + /** + * Describes an object if supported. + * + * @param OutputInterface $output + * @param object $object + * @param string $format + * @param boolean $raw + */ + public function describe(OutputInterface $output, $object, $format = null, $raw = false, $namespace = null) + { + $options = array('raw_text' => $raw, 'format' => $format ?: 'txt', 'namespace' => $namespace); + $type = !$raw && 'txt' === $options['format'] ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW; + + if (!isset($this->descriptors[$options['format']])) { + throw new \InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format'])); + } + + $descriptor = $this->descriptors[$options['format']]; + + $output->writeln($descriptor->describe($object, $options), $type); + } + + /** + * Registers a descriptor. + * + * @param string $format + * @param DescriptorInterface $descriptor + * + * @return DescriptorHelper + */ + public function register($format, DescriptorInterface $descriptor) + { + $this->descriptors[$format] = $descriptor; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'descriptor'; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Helper/DialogHelper.php b/vendor/symfony/console/Symfony/Component/Console/Helper/DialogHelper.php new file mode 100644 index 0000000..98d37ad --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Helper/DialogHelper.php @@ -0,0 +1,469 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; + +/** + * The Dialog class provides helpers to interact with the user. + * + * @author Fabien Potencier + */ +class DialogHelper extends Helper +{ + private $inputStream; + private static $shell; + private static $stty; + + /** + * Asks the user to select a value. + * + * @param OutputInterface $output An Output instance + * @param string|array $question The question to ask + * @param array $choices List of choices to pick from + * @param Boolean $default The default answer if the user enters nothing + * @param Boolean|integer $attempts Max number of times to ask before giving up (false by default, which means infinite) + * @param string $errorMessage Message which will be shown if invalid value from choice list would be picked + * @param Boolean $multiselect Select more than one value separated by comma + * + * @return integer|string|array The selected value or values (the key of the choices array) + * + * @throws \InvalidArgumentException + */ + public function select(OutputInterface $output, $question, $choices, $default = null, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false) + { + $width = max(array_map('strlen', array_keys($choices))); + + $messages = (array) $question; + foreach ($choices as $key => $value) { + $messages[] = sprintf(" [%-${width}s] %s", $key, $value); + } + + $output->writeln($messages); + + $result = $this->askAndValidate($output, '> ', function ($picked) use ($choices, $errorMessage, $multiselect) { + // Collapse all spaces. + $selectedChoices = str_replace(" ", "", $picked); + + if ($multiselect) { + // Check for a separated comma values + if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) { + throw new \InvalidArgumentException(sprintf($errorMessage, $picked)); + } + $selectedChoices = explode(",", $selectedChoices); + } else { + $selectedChoices = array($picked); + } + + $multiselectChoices = array(); + + foreach ($selectedChoices as $value) { + if (empty($choices[$value])) { + throw new \InvalidArgumentException(sprintf($errorMessage, $value)); + } + array_push($multiselectChoices, $value); + } + + if ($multiselect) { + return $multiselectChoices; + } + + return $picked; + }, $attempts, $default); + + return $result; + } + + /** + * Asks a question to the user. + * + * @param OutputInterface $output An Output instance + * @param string|array $question The question to ask + * @param string $default The default answer if none is given by the user + * @param array $autocomplete List of values to autocomplete + * + * @return string The user answer + * + * @throws \RuntimeException If there is no data to read in the input stream + */ + public function ask(OutputInterface $output, $question, $default = null, array $autocomplete = null) + { + $output->write($question); + + $inputStream = $this->inputStream ?: STDIN; + + if (null === $autocomplete || !$this->hasSttyAvailable()) { + $ret = fgets($inputStream, 4096); + if (false === $ret) { + throw new \RuntimeException('Aborted'); + } + $ret = trim($ret); + } else { + $ret = ''; + + $i = 0; + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + + $sttyMode = shell_exec('stty -g'); + + // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) + shell_exec('stty -icanon -echo'); + + // Add highlighted text style + $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); + + // Read a keypress + while (!feof($inputStream)) { + $c = fread($inputStream, 1); + + // Backspace Character + if ("\177" === $c) { + if (0 === $numMatches && 0 !== $i) { + $i--; + // Move cursor backwards + $output->write("\033[1D"); + } + + if ($i === 0) { + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + } else { + $numMatches = 0; + } + + // Pop the last character off the end of our string + $ret = substr($ret, 0, $i); + } elseif ("\033" === $c) { // Did we read an escape sequence? + $c .= fread($inputStream, 2); + + // A = Up Arrow. B = Down Arrow + if ('A' === $c[2] || 'B' === $c[2]) { + if ('A' === $c[2] && -1 === $ofs) { + $ofs = 0; + } + + if (0 === $numMatches) { + continue; + } + + $ofs += ('A' === $c[2]) ? -1 : 1; + $ofs = ($numMatches + $ofs) % $numMatches; + } + } elseif (ord($c) < 32) { + if ("\t" === $c || "\n" === $c) { + if ($numMatches > 0 && -1 !== $ofs) { + $ret = $matches[$ofs]; + // Echo out remaining chars for current match + $output->write(substr($ret, $i)); + $i = strlen($ret); + } + + if ("\n" === $c) { + $output->write($c); + break; + } + + $numMatches = 0; + } + + continue; + } else { + $output->write($c); + $ret .= $c; + $i++; + + $numMatches = 0; + $ofs = 0; + + foreach ($autocomplete as $value) { + // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) + if (0 === strpos($value, $ret) && $i !== strlen($value)) { + $matches[$numMatches++] = $value; + } + } + } + + // Erase characters from cursor to end of line + $output->write("\033[K"); + + if ($numMatches > 0 && -1 !== $ofs) { + // Save cursor position + $output->write("\0337"); + // Write highlighted text + $output->write(''.substr($matches[$ofs], $i).''); + // Restore cursor position + $output->write("\0338"); + } + } + + // Reset stty so it behaves normally again + shell_exec(sprintf('stty %s', $sttyMode)); + } + + return strlen($ret) > 0 ? $ret : $default; + } + + /** + * Asks a confirmation to the user. + * + * The question will be asked until the user answers by nothing, yes, or no. + * + * @param OutputInterface $output An Output instance + * @param string|array $question The question to ask + * @param Boolean $default The default answer if the user enters nothing + * + * @return Boolean true if the user has confirmed, false otherwise + */ + public function askConfirmation(OutputInterface $output, $question, $default = true) + { + $answer = 'z'; + while ($answer && !in_array(strtolower($answer[0]), array('y', 'n'))) { + $answer = $this->ask($output, $question); + } + + if (false === $default) { + return $answer && 'y' == strtolower($answer[0]); + } + + return !$answer || 'y' == strtolower($answer[0]); + } + + /** + * Asks a question to the user, the response is hidden + * + * @param OutputInterface $output An Output instance + * @param string|array $question The question + * @param Boolean $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not + * + * @return string The answer + * + * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden + */ + public function askHiddenResponse(OutputInterface $output, $question, $fallback = true) + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; + + // handle code running from a phar + if ('phar:' === substr(__FILE__, 0, 5)) { + $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; + copy($exe, $tmpExe); + $exe = $tmpExe; + } + + $output->write($question); + $value = rtrim(shell_exec($exe)); + $output->writeln(''); + + if (isset($tmpExe)) { + unlink($tmpExe); + } + + return $value; + } + + if ($this->hasSttyAvailable()) { + $output->write($question); + + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -echo'); + $value = fgets($this->inputStream ?: STDIN, 4096); + shell_exec(sprintf('stty %s', $sttyMode)); + + if (false === $value) { + throw new \RuntimeException('Aborted'); + } + + $value = trim($value); + $output->writeln(''); + + return $value; + } + + if (false !== $shell = $this->getShell()) { + $output->write($question); + $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword'; + $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); + $value = rtrim(shell_exec($command)); + $output->writeln(''); + + return $value; + } + + if ($fallback) { + return $this->ask($output, $question); + } + + throw new \RuntimeException('Unable to hide the response'); + } + + /** + * Asks for a value and validates the response. + * + * The validator receives the data to validate. It must return the + * validated data when the data is valid and throw an exception + * otherwise. + * + * @param OutputInterface $output An Output instance + * @param string|array $question The question to ask + * @param callable $validator A PHP callback + * @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite) + * @param string $default The default answer if none is given by the user + * @param array $autocomplete List of values to autocomplete + * + * @return mixed + * + * @throws \Exception When any of the validators return an error + */ + public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $default = null, array $autocomplete = null) + { + $that = $this; + + $interviewer = function() use ($output, $question, $default, $autocomplete, $that) { + return $that->ask($output, $question, $default, $autocomplete); + }; + + return $this->validateAttempts($interviewer, $output, $validator, $attempts); + } + + /** + * Asks for a value, hide and validates the response. + * + * The validator receives the data to validate. It must return the + * validated data when the data is valid and throw an exception + * otherwise. + * + * @param OutputInterface $output An Output instance + * @param string|array $question The question to ask + * @param callable $validator A PHP callback + * @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite) + * @param Boolean $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not + * + * @return string The response + * + * @throws \Exception When any of the validators return an error + * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden + * + */ + public function askHiddenResponseAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $fallback = true) + { + $that = $this; + + $interviewer = function() use ($output, $question, $fallback, $that) { + return $that->askHiddenResponse($output, $question, $fallback); + }; + + return $this->validateAttempts($interviewer, $output, $validator, $attempts); + } + + /** + * Sets the input stream to read from when interacting with the user. + * + * This is mainly useful for testing purpose. + * + * @param resource $stream The input stream + */ + public function setInputStream($stream) + { + $this->inputStream = $stream; + } + + /** + * Returns the helper's input stream + * + * @return string + */ + public function getInputStream() + { + return $this->inputStream; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'dialog'; + } + + /** + * Return a valid unix shell + * + * @return string|Boolean The valid shell name, false in case no valid shell is found + */ + private function getShell() + { + if (null !== self::$shell) { + return self::$shell; + } + + self::$shell = false; + + if (file_exists('/usr/bin/env')) { + // handle other OSs with bash/zsh/ksh/csh if available to hide the answer + $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; + foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) { + if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { + self::$shell = $sh; + break; + } + } + } + + return self::$shell; + } + + private function hasSttyAvailable() + { + if (null !== self::$stty) { + return self::$stty; + } + + exec('stty 2>&1', $output, $exitcode); + + return self::$stty = $exitcode === 0; + } + + /** + * Validate an attempt + * + * @param callable $interviewer A callable that will ask for a question and return the result + * @param OutputInterface $output An Output instance + * @param callable $validator A PHP callback + * @param integer $attempts Max number of times to ask before giving up ; false will ask infinitely + * + * @return string The validated response + * + * @throws \Exception In case the max number of attempts has been reached and no valid response has been given + */ + private function validateAttempts($interviewer, OutputInterface $output, $validator, $attempts) + { + $error = null; + while (false === $attempts || $attempts--) { + if (null !== $error) { + $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error')); + } + + try { + return call_user_func($validator, $interviewer()); + } catch (\Exception $error) { + } + } + + throw $error; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Helper/FormatterHelper.php b/vendor/symfony/console/Symfony/Component/Console/Helper/FormatterHelper.php new file mode 100644 index 0000000..a5f1d1c --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Helper/FormatterHelper.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * The Formatter class provides helpers to format messages. + * + * @author Fabien Potencier + */ +class FormatterHelper extends Helper +{ + /** + * Formats a message within a section. + * + * @param string $section The section name + * @param string $message The message + * @param string $style The style to apply to the section + * + * @return string The format section + */ + public function formatSection($section, $message, $style = 'info') + { + return sprintf('<%s>[%s] %s', $style, $section, $style, $message); + } + + /** + * Formats a message as a block of text. + * + * @param string|array $messages The message to write in the block + * @param string $style The style to apply to the whole block + * @param Boolean $large Whether to return a large block + * + * @return string The formatter message + */ + public function formatBlock($messages, $style, $large = false) + { + $messages = (array) $messages; + + $len = 0; + $lines = array(); + foreach ($messages as $message) { + $message = OutputFormatter::escape($message); + $lines[] = sprintf($large ? ' %s ' : ' %s ', $message); + $len = max($this->strlen($message) + ($large ? 4 : 2), $len); + } + + $messages = $large ? array(str_repeat(' ', $len)) : array(); + foreach ($lines as $line) { + $messages[] = $line.str_repeat(' ', $len - $this->strlen($line)); + } + if ($large) { + $messages[] = str_repeat(' ', $len); + } + + foreach ($messages as &$message) { + $message = sprintf('<%s>%s', $style, $message, $style); + } + + return implode("\n", $messages); + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'formatter'; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Helper/Helper.php b/vendor/symfony/console/Symfony/Component/Console/Helper/Helper.php new file mode 100644 index 0000000..534b9f4 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Helper/Helper.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * Helper is the base class for all helper classes. + * + * @author Fabien Potencier + */ +abstract class Helper implements HelperInterface +{ + protected $helperSet = null; + + /** + * Sets the helper set associated with this helper. + * + * @param HelperSet $helperSet A HelperSet instance + */ + public function setHelperSet(HelperSet $helperSet = null) + { + $this->helperSet = $helperSet; + } + + /** + * Gets the helper set associated with this helper. + * + * @return HelperSet A HelperSet instance + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Returns the length of a string, using mb_strlen if it is available. + * + * @param string $string The string to check its length + * + * @return integer The length of the string + */ + protected function strlen($string) + { + if (!function_exists('mb_strlen')) { + return strlen($string); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return strlen($string); + } + + return mb_strlen($string, $encoding); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Helper/HelperInterface.php b/vendor/symfony/console/Symfony/Component/Console/Helper/HelperInterface.php new file mode 100644 index 0000000..6d39449 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Helper/HelperInterface.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * HelperInterface is the interface all helpers must implement. + * + * @author Fabien Potencier + * + * @api + */ +interface HelperInterface +{ + /** + * Sets the helper set associated with this helper. + * + * @param HelperSet $helperSet A HelperSet instance + * + * @api + */ + public function setHelperSet(HelperSet $helperSet = null); + + /** + * Gets the helper set associated with this helper. + * + * @return HelperSet A HelperSet instance + * + * @api + */ + public function getHelperSet(); + + /** + * Returns the canonical name of this helper. + * + * @return string The canonical name + * + * @api + */ + public function getName(); +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Helper/HelperSet.php b/vendor/symfony/console/Symfony/Component/Console/Helper/HelperSet.php new file mode 100644 index 0000000..d95c9a3 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Helper/HelperSet.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Command\Command; + +/** + * HelperSet represents a set of helpers to be used with a command. + * + * @author Fabien Potencier + */ +class HelperSet +{ + private $helpers; + private $command; + + /** + * Constructor. + * + * @param Helper[] $helpers An array of helper. + */ + public function __construct(array $helpers = array()) + { + $this->helpers = array(); + foreach ($helpers as $alias => $helper) { + $this->set($helper, is_int($alias) ? null : $alias); + } + } + + /** + * Sets a helper. + * + * @param HelperInterface $helper The helper instance + * @param string $alias An alias + */ + public function set(HelperInterface $helper, $alias = null) + { + $this->helpers[$helper->getName()] = $helper; + if (null !== $alias) { + $this->helpers[$alias] = $helper; + } + + $helper->setHelperSet($this); + } + + /** + * Returns true if the helper if defined. + * + * @param string $name The helper name + * + * @return Boolean true if the helper is defined, false otherwise + */ + public function has($name) + { + return isset($this->helpers[$name]); + } + + /** + * Gets a helper value. + * + * @param string $name The helper name + * + * @return HelperInterface The helper instance + * + * @throws \InvalidArgumentException if the helper is not defined + */ + public function get($name) + { + if (!$this->has($name)) { + throw new \InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); + } + + return $this->helpers[$name]; + } + + /** + * Sets the command associated with this helper set. + * + * @param Command $command A Command instance + */ + public function setCommand(Command $command = null) + { + $this->command = $command; + } + + /** + * Gets the command associated with this helper set. + * + * @return Command A Command instance + */ + public function getCommand() + { + return $this->command; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Helper/ProgressHelper.php b/vendor/symfony/console/Symfony/Component/Console/Helper/ProgressHelper.php new file mode 100644 index 0000000..f24b504 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Helper/ProgressHelper.php @@ -0,0 +1,447 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * The Progress class provides helpers to display progress output. + * + * @author Chris Jones + * @author Fabien Potencier + */ +class ProgressHelper extends Helper +{ + const FORMAT_QUIET = ' %percent%%'; + const FORMAT_NORMAL = ' %current%/%max% [%bar%] %percent%%'; + const FORMAT_VERBOSE = ' %current%/%max% [%bar%] %percent%% Elapsed: %elapsed%'; + const FORMAT_QUIET_NOMAX = ' %current%'; + const FORMAT_NORMAL_NOMAX = ' %current% [%bar%]'; + const FORMAT_VERBOSE_NOMAX = ' %current% [%bar%] Elapsed: %elapsed%'; + + // options + private $barWidth = 28; + private $barChar = '='; + private $emptyBarChar = '-'; + private $progressChar = '>'; + private $format = null; + private $redrawFreq = 1; + + private $lastMessagesLength; + private $barCharOriginal; + + /** + * @var OutputInterface + */ + private $output; + + /** + * Current step + * + * @var integer + */ + private $current; + + /** + * Maximum number of steps + * + * @var integer + */ + private $max; + + /** + * Start time of the progress bar + * + * @var integer + */ + private $startTime; + + /** + * List of formatting variables + * + * @var array + */ + private $defaultFormatVars = array( + 'current', + 'max', + 'bar', + 'percent', + 'elapsed', + ); + + /** + * Available formatting variables + * + * @var array + */ + private $formatVars; + + /** + * Stored format part widths (used for padding) + * + * @var array + */ + private $widths = array( + 'current' => 4, + 'max' => 4, + 'percent' => 3, + 'elapsed' => 6, + ); + + /** + * Various time formats + * + * @var array + */ + private $timeFormats = array( + array(0, '???'), + array(2, '1 sec'), + array(59, 'secs', 1), + array(60, '1 min'), + array(3600, 'mins', 60), + array(5400, '1 hr'), + array(86400, 'hrs', 3600), + array(129600, '1 day'), + array(604800, 'days', 86400), + ); + + /** + * Sets the progress bar width. + * + * @param int $size The progress bar size + */ + public function setBarWidth($size) + { + $this->barWidth = (int) $size; + } + + /** + * Sets the bar character. + * + * @param string $char A character + */ + public function setBarCharacter($char) + { + $this->barChar = $char; + } + + /** + * Sets the empty bar character. + * + * @param string $char A character + */ + public function setEmptyBarCharacter($char) + { + $this->emptyBarChar = $char; + } + + /** + * Sets the progress bar character. + * + * @param string $char A character + */ + public function setProgressCharacter($char) + { + $this->progressChar = $char; + } + + /** + * Sets the progress bar format. + * + * @param string $format The format + */ + public function setFormat($format) + { + $this->format = $format; + } + + /** + * Sets the redraw frequency. + * + * @param int $freq The frequency in steps + */ + public function setRedrawFrequency($freq) + { + $this->redrawFreq = (int) $freq; + } + + /** + * Starts the progress output. + * + * @param OutputInterface $output An Output instance + * @param integer $max Maximum steps + */ + public function start(OutputInterface $output, $max = null) + { + $this->startTime = time(); + $this->current = 0; + $this->max = (int) $max; + $this->output = $output; + $this->lastMessagesLength = 0; + $this->barCharOriginal = ''; + + if (null === $this->format) { + switch ($output->getVerbosity()) { + case OutputInterface::VERBOSITY_QUIET: + $this->format = self::FORMAT_QUIET_NOMAX; + if ($this->max > 0) { + $this->format = self::FORMAT_QUIET; + } + break; + case OutputInterface::VERBOSITY_VERBOSE: + case OutputInterface::VERBOSITY_VERY_VERBOSE: + case OutputInterface::VERBOSITY_DEBUG: + $this->format = self::FORMAT_VERBOSE_NOMAX; + if ($this->max > 0) { + $this->format = self::FORMAT_VERBOSE; + } + break; + default: + $this->format = self::FORMAT_NORMAL_NOMAX; + if ($this->max > 0) { + $this->format = self::FORMAT_NORMAL; + } + break; + } + } + + $this->initialize(); + } + + /** + * Advances the progress output X steps. + * + * @param integer $step Number of steps to advance + * @param Boolean $redraw Whether to redraw or not + * + * @throws \LogicException + */ + public function advance($step = 1, $redraw = false) + { + if (null === $this->startTime) { + throw new \LogicException('You must start the progress bar before calling advance().'); + } + + if (0 === $this->current) { + $redraw = true; + } + + $this->current += $step; + if ($redraw || 0 === $this->current % $this->redrawFreq) { + $this->display(); + } + } + + /** + * Sets the current progress. + * + * @param integer $current The current progress + * @param Boolean $redraw Whether to redraw or not + * + * @throws \LogicException + */ + public function setCurrent($current, $redraw = false) + { + if (null === $this->startTime) { + throw new \LogicException('You must start the progress bar before calling setCurrent().'); + } + + $current = (int) $current; + + if ($current < $this->current) { + throw new \LogicException('You can\'t regress the progress bar'); + } + + if (0 === $this->current) { + $redraw = true; + } + + $this->current = $current; + if ($redraw || 0 === $this->current % $this->redrawFreq) { + $this->display(); + } + } + + /** + * Outputs the current progress string. + * + * @param Boolean $finish Forces the end result + * + * @throws \LogicException + */ + public function display($finish = false) + { + if (null === $this->startTime) { + throw new \LogicException('You must start the progress bar before calling display().'); + } + + $message = $this->format; + foreach ($this->generate($finish) as $name => $value) { + $message = str_replace("%{$name}%", $value, $message); + } + $this->overwrite($this->output, $message); + } + + /** + * Finishes the progress output. + */ + public function finish() + { + if (null === $this->startTime) { + throw new \LogicException('You must start the progress bar before calling finish().'); + } + + if (null !== $this->startTime) { + if (!$this->max) { + $this->barChar = $this->barCharOriginal; + $this->display(true); + } + $this->startTime = null; + $this->output->writeln(''); + $this->output = null; + } + } + + /** + * Initializes the progress helper. + */ + private function initialize() + { + $this->formatVars = array(); + foreach ($this->defaultFormatVars as $var) { + if (false !== strpos($this->format, "%{$var}%")) { + $this->formatVars[$var] = true; + } + } + + if ($this->max > 0) { + $this->widths['max'] = $this->strlen($this->max); + $this->widths['current'] = $this->widths['max']; + } else { + $this->barCharOriginal = $this->barChar; + $this->barChar = $this->emptyBarChar; + } + } + + /** + * Generates the array map of format variables to values. + * + * @param Boolean $finish Forces the end result + * + * @return array Array of format vars and values + */ + private function generate($finish = false) + { + $vars = array(); + $percent = 0; + if ($this->max > 0) { + $percent = (double) $this->current / $this->max; + } + + if (isset($this->formatVars['bar'])) { + $completeBars = 0; + + if ($this->max > 0) { + $completeBars = floor($percent * $this->barWidth); + } else { + if (!$finish) { + $completeBars = floor($this->current % $this->barWidth); + } else { + $completeBars = $this->barWidth; + } + } + + $emptyBars = $this->barWidth - $completeBars - $this->strlen($this->progressChar); + $bar = str_repeat($this->barChar, $completeBars); + if ($completeBars < $this->barWidth) { + $bar .= $this->progressChar; + $bar .= str_repeat($this->emptyBarChar, $emptyBars); + } + + $vars['bar'] = $bar; + } + + if (isset($this->formatVars['elapsed'])) { + $elapsed = time() - $this->startTime; + $vars['elapsed'] = str_pad($this->humaneTime($elapsed), $this->widths['elapsed'], ' ', STR_PAD_LEFT); + } + + if (isset($this->formatVars['current'])) { + $vars['current'] = str_pad($this->current, $this->widths['current'], ' ', STR_PAD_LEFT); + } + + if (isset($this->formatVars['max'])) { + $vars['max'] = $this->max; + } + + if (isset($this->formatVars['percent'])) { + $vars['percent'] = str_pad(floor($percent * 100), $this->widths['percent'], ' ', STR_PAD_LEFT); + } + + return $vars; + } + + /** + * Converts seconds into human-readable format. + * + * @param integer $secs Number of seconds + * + * @return string Time in readable format + */ + private function humaneTime($secs) + { + $text = ''; + foreach ($this->timeFormats as $format) { + if ($secs < $format[0]) { + if (count($format) == 2) { + $text = $format[1]; + break; + } else { + $text = ceil($secs / $format[2]).' '.$format[1]; + break; + } + } + } + + return $text; + } + + /** + * Overwrites a previous message to the output. + * + * @param OutputInterface $output An Output instance + * @param string $message The message + */ + private function overwrite(OutputInterface $output, $message) + { + $length = $this->strlen($message); + + // append whitespace to match the last line's length + if (null !== $this->lastMessagesLength && $this->lastMessagesLength > $length) { + $message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT); + } + + // carriage return + $output->write("\x0D"); + $output->write($message); + + $this->lastMessagesLength = $this->strlen($message); + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'progress'; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Helper/TableHelper.php b/vendor/symfony/console/Symfony/Component/Console/Helper/TableHelper.php new file mode 100644 index 0000000..c18f295 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Helper/TableHelper.php @@ -0,0 +1,459 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\OutputInterface; +use InvalidArgumentException; + +/** + * Provides helpers to display table output. + * + * @author Саша Стаменковић + */ +class TableHelper extends Helper +{ + const LAYOUT_DEFAULT = 0; + const LAYOUT_BORDERLESS = 1; + + /** + * Table headers. + * + * @var array + */ + private $headers = array(); + + /** + * Table rows. + * + * @var array + */ + private $rows = array(); + + // Rendering options + private $paddingChar; + private $horizontalBorderChar; + private $verticalBorderChar; + private $crossingChar; + private $cellHeaderFormat; + private $cellRowFormat; + private $borderFormat; + private $padType; + + /** + * Column widths cache. + * + * @var array + */ + private $columnWidths = array(); + + /** + * Number of columns cache. + * + * @var array + */ + private $numberOfColumns; + + /** + * @var OutputInterface + */ + private $output; + + public function __construct() + { + $this->setLayout(self::LAYOUT_DEFAULT); + } + + /** + * Sets table layout type. + * + * @param int $layout self::LAYOUT_* + * + * @return TableHelper + */ + public function setLayout($layout) + { + switch ($layout) { + case self::LAYOUT_BORDERLESS: + $this + ->setPaddingChar(' ') + ->setHorizontalBorderChar('=') + ->setVerticalBorderChar(' ') + ->setCrossingChar(' ') + ->setCellHeaderFormat('%s') + ->setCellRowFormat('%s') + ->setBorderFormat('%s') + ->setPadType(STR_PAD_RIGHT) + ; + break; + + case self::LAYOUT_DEFAULT: + $this + ->setPaddingChar(' ') + ->setHorizontalBorderChar('-') + ->setVerticalBorderChar('|') + ->setCrossingChar('+') + ->setCellHeaderFormat('%s') + ->setCellRowFormat('%s') + ->setBorderFormat('%s') + ->setPadType(STR_PAD_RIGHT) + ; + break; + + default: + throw new InvalidArgumentException(sprintf('Invalid table layout "%s".', $layout)); + break; + }; + + return $this; + } + + public function setHeaders(array $headers) + { + $this->headers = array_values($headers); + + return $this; + } + + public function setRows(array $rows) + { + $this->rows = array(); + + return $this->addRows($rows); + } + + public function addRows(array $rows) + { + foreach ($rows as $row) { + $this->addRow($row); + } + + return $this; + } + + public function addRow(array $row) + { + $this->rows[] = array_values($row); + + return $this; + } + + public function setRow($column, array $row) + { + $this->rows[$column] = $row; + + return $this; + } + + /** + * Sets padding character, used for cell padding. + * + * @param string $paddingChar + * + * @return TableHelper + */ + public function setPaddingChar($paddingChar) + { + $this->paddingChar = $paddingChar; + + return $this; + } + + /** + * Sets horizontal border character. + * + * @param string $horizontalBorderChar + * + * @return TableHelper + */ + public function setHorizontalBorderChar($horizontalBorderChar) + { + $this->horizontalBorderChar = $horizontalBorderChar; + + return $this; + } + + /** + * Sets vertical border character. + * + * @param string $verticalBorderChar + * + * @return TableHelper + */ + public function setVerticalBorderChar($verticalBorderChar) + { + $this->verticalBorderChar = $verticalBorderChar; + + return $this; + } + + /** + * Sets crossing character. + * + * @param string $crossingChar + * + * @return TableHelper + */ + public function setCrossingChar($crossingChar) + { + $this->crossingChar = $crossingChar; + + return $this; + } + + /** + * Sets header cell format. + * + * @param string $cellHeaderFormat + * + * @return TableHelper + */ + public function setCellHeaderFormat($cellHeaderFormat) + { + $this->cellHeaderFormat = $cellHeaderFormat; + + return $this; + } + + /** + * Sets row cell format. + * + * @param string $cellRowFormat + * + * @return TableHelper + */ + public function setCellRowFormat($cellRowFormat) + { + $this->cellRowFormat = $cellRowFormat; + + return $this; + } + + /** + * Sets table border format. + * + * @param string $borderFormat + * + * @return TableHelper + */ + public function setBorderFormat($borderFormat) + { + $this->borderFormat = $borderFormat; + + return $this; + } + + /** + * Sets cell padding type. + * + * @param integer $padType STR_PAD_* + * + * @return TableHelper + */ + public function setPadType($padType) + { + $this->padType = $padType; + + return $this; + } + + /** + * Renders table to output. + * + * Example: + * +---------------+-----------------------+------------------+ + * | ISBN | Title | Author | + * +---------------+-----------------------+------------------+ + * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | + * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | + * +---------------+-----------------------+------------------+ + * + * @param OutputInterface $output + */ + public function render(OutputInterface $output) + { + $this->output = $output; + + $this->renderRowSeparator(); + $this->renderRow($this->headers, $this->cellHeaderFormat); + if (!empty($this->headers)) { + $this->renderRowSeparator(); + } + foreach ($this->rows as $row) { + $this->renderRow($row, $this->cellRowFormat); + } + if (!empty($this->rows)) { + $this->renderRowSeparator(); + } + + $this->cleanup(); + } + + /** + * Renders horizontal header separator. + * + * Example: +-----+-----------+-------+ + */ + private function renderRowSeparator() + { + if (0 === $count = $this->getNumberOfColumns()) { + return; + } + + $markup = $this->crossingChar; + for ($column = 0; $column < $count; $column++) { + $markup .= str_repeat($this->horizontalBorderChar, $this->getColumnWidth($column)) + .$this->crossingChar + ; + } + + $this->output->writeln(sprintf($this->borderFormat, $markup)); + } + + /** + * Renders vertical column separator. + */ + private function renderColumnSeparator() + { + $this->output->write(sprintf($this->borderFormat, $this->verticalBorderChar)); + } + + /** + * Renders table row. + * + * Example: | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + * + * @param array $row + * @param string $cellFormat + */ + private function renderRow(array $row, $cellFormat) + { + if (empty($row)) { + return; + } + + $this->renderColumnSeparator(); + for ($column = 0, $count = $this->getNumberOfColumns(); $column < $count; $column++) { + $this->renderCell($row, $column, $cellFormat); + $this->renderColumnSeparator(); + } + $this->output->writeln(''); + } + + /** + * Renders table cell with padding. + * + * @param array $row + * @param integer $column + * @param string $cellFormat + */ + private function renderCell(array $row, $column, $cellFormat) + { + $cell = isset($row[$column]) ? $row[$column] : ''; + $width = $this->getColumnWidth($column); + + // str_pad won't work properly with multi-byte strings, we need to fix the padding + if (function_exists('mb_strlen') && false !== $encoding = mb_detect_encoding($cell)) { + $width += strlen($cell) - mb_strlen($cell, $encoding); + } + + $this->output->write(sprintf( + $cellFormat, + str_pad( + $this->paddingChar.$cell.$this->paddingChar, + $width, + $this->paddingChar, + $this->padType + ) + )); + } + + /** + * Gets number of columns for this table. + * + * @return int + */ + private function getNumberOfColumns() + { + if (null !== $this->numberOfColumns) { + return $this->numberOfColumns; + } + + $columns = array(0); + $columns[] = count($this->headers); + foreach ($this->rows as $row) { + $columns[] = count($row); + } + + return $this->numberOfColumns = max($columns); + } + + /** + * Gets column width. + * + * @param integer $column + * + * @return int + */ + private function getColumnWidth($column) + { + if (isset($this->columnWidths[$column])) { + return $this->columnWidths[$column]; + } + + $lengths = array(0); + $lengths[] = $this->getCellWidth($this->headers, $column); + foreach ($this->rows as $row) { + $lengths[] = $this->getCellWidth($row, $column); + } + + return $this->columnWidths[$column] = max($lengths) + 2; + } + + /** + * Gets cell width. + * + * @param array $row + * @param integer $column + * + * @return int + */ + private function getCellWidth(array $row, $column) + { + if ($column < 0) { + return 0; + } + + if (isset($row[$column])) { + return $this->strlen($row[$column]); + } + + return $this->getCellWidth($row, $column - 1); + } + + /** + * Called after rendering to cleanup cache data. + */ + private function cleanup() + { + $this->columnWidths = array(); + $this->numberOfColumns = null; + } + + /** + * {@inheritDoc} + */ + public function getName() + { + return 'table'; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Input/ArgvInput.php b/vendor/symfony/console/Symfony/Component/Console/Input/ArgvInput.php new file mode 100644 index 0000000..ce28504 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Input/ArgvInput.php @@ -0,0 +1,351 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * ArgvInput represents an input coming from the CLI arguments. + * + * Usage: + * + * $input = new ArgvInput(); + * + * By default, the `$_SERVER['argv']` array is used for the input values. + * + * This can be overridden by explicitly passing the input values in the constructor: + * + * $input = new ArgvInput($_SERVER['argv']); + * + * If you pass it yourself, don't forget that the first element of the array + * is the name of the running application. + * + * When passing an argument to the constructor, be sure that it respects + * the same rules as the argv one. It's almost always better to use the + * `StringInput` when you want to provide your own input. + * + * @author Fabien Potencier + * + * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html + * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02 + * + * @api + */ +class ArgvInput extends Input +{ + private $tokens; + private $parsed; + + /** + * Constructor. + * + * @param array $argv An array of parameters from the CLI (in the argv format) + * @param InputDefinition $definition A InputDefinition instance + * + * @api + */ + public function __construct(array $argv = null, InputDefinition $definition = null) + { + if (null === $argv) { + $argv = $_SERVER['argv']; + } + + // strip the application name + array_shift($argv); + + $this->tokens = $argv; + + parent::__construct($definition); + } + + protected function setTokens(array $tokens) + { + $this->tokens = $tokens; + } + + /** + * Processes command line arguments. + */ + protected function parse() + { + $parseOptions = true; + $this->parsed = $this->tokens; + while (null !== $token = array_shift($this->parsed)) { + if ($parseOptions && '' == $token) { + $this->parseArgument($token); + } elseif ($parseOptions && '--' == $token) { + $parseOptions = false; + } elseif ($parseOptions && 0 === strpos($token, '--')) { + $this->parseLongOption($token); + } elseif ($parseOptions && '-' === $token[0]) { + $this->parseShortOption($token); + } else { + $this->parseArgument($token); + } + } + } + + /** + * Parses a short option. + * + * @param string $token The current token. + */ + private function parseShortOption($token) + { + $name = substr($token, 1); + + if (strlen($name) > 1) { + if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) { + // an option with a value (with no space) + $this->addShortOption($name[0], substr($name, 1)); + } else { + $this->parseShortOptionSet($name); + } + } else { + $this->addShortOption($name, null); + } + } + + /** + * Parses a short option set. + * + * @param string $name The current token + * + * @throws \RuntimeException When option given doesn't exist + */ + private function parseShortOptionSet($name) + { + $len = strlen($name); + for ($i = 0; $i < $len; $i++) { + if (!$this->definition->hasShortcut($name[$i])) { + throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i])); + } + + $option = $this->definition->getOptionForShortcut($name[$i]); + if ($option->acceptValue()) { + $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); + + break; + } else { + $this->addLongOption($option->getName(), null); + } + } + } + + /** + * Parses a long option. + * + * @param string $token The current token + */ + private function parseLongOption($token) + { + $name = substr($token, 2); + + if (false !== $pos = strpos($name, '=')) { + $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1)); + } else { + $this->addLongOption($name, null); + } + } + + /** + * Parses an argument. + * + * @param string $token The current token + * + * @throws \RuntimeException When too many arguments are given + */ + private function parseArgument($token) + { + $c = count($this->arguments); + + // if input is expecting another argument, add it + if ($this->definition->hasArgument($c)) { + $arg = $this->definition->getArgument($c); + $this->arguments[$arg->getName()] = $arg->isArray()? array($token) : $token; + + // if last argument isArray(), append token to last argument + } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { + $arg = $this->definition->getArgument($c - 1); + $this->arguments[$arg->getName()][] = $token; + + // unexpected argument + } else { + throw new \RuntimeException('Too many arguments.'); + } + } + + /** + * Adds a short option value. + * + * @param string $shortcut The short option key + * @param mixed $value The value for the option + * + * @throws \RuntimeException When option given doesn't exist + */ + private function addShortOption($shortcut, $value) + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * Adds a long option value. + * + * @param string $name The long option key + * @param mixed $value The value for the option + * + * @throws \RuntimeException When option given doesn't exist + */ + private function addLongOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name)); + } + + $option = $this->definition->getOption($name); + + // Convert false values (from a previous call to substr()) to null + if (false === $value) { + $value = null; + } + + if (null !== $value && !$option->acceptValue()) { + throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name, $value)); + } + + if (null === $value && $option->acceptValue() && count($this->parsed)) { + // if option accepts an optional or mandatory argument + // let's see if there is one provided + $next = array_shift($this->parsed); + if (isset($next[0]) && '-' !== $next[0]) { + $value = $next; + } elseif (empty($next)) { + $value = ''; + } else { + array_unshift($this->parsed, $next); + } + } + + if (null === $value) { + if ($option->isValueRequired()) { + throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isArray()) { + $value = $option->isValueOptional() ? $option->getDefault() : true; + } + } + + if ($option->isArray()) { + $this->options[$name][] = $value; + } else { + $this->options[$name] = $value; + } + } + + /** + * Returns the first argument from the raw parameters (not parsed). + * + * @return string The value of the first argument or null otherwise + */ + public function getFirstArgument() + { + foreach ($this->tokens as $token) { + if ($token && '-' === $token[0]) { + continue; + } + + return $token; + } + } + + /** + * Returns true if the raw parameters (not parsed) contain a value. + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * + * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * + * @return Boolean true if the value is contained in the raw parameters + */ + public function hasParameterOption($values) + { + $values = (array) $values; + + foreach ($this->tokens as $token) { + foreach ($values as $value) { + if ($token === $value || 0 === strpos($token, $value.'=')) { + return true; + } + } + } + + return false; + } + + /** + * Returns the value of a raw option (not parsed). + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * + * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * @param mixed $default The default value to return if no result is found + * + * @return mixed The option value + */ + public function getParameterOption($values, $default = false) + { + $values = (array) $values; + + $tokens = $this->tokens; + while ($token = array_shift($tokens)) { + foreach ($values as $value) { + if ($token === $value || 0 === strpos($token, $value.'=')) { + if (false !== $pos = strpos($token, '=')) { + return substr($token, $pos + 1); + } + + return array_shift($tokens); + } + } + } + + return $default; + } + + /** + * Returns a stringified representation of the args passed to the command + * + * @return string + */ + public function __toString() + { + $self = $this; + $tokens = array_map(function ($token) use ($self) { + if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { + return $match[1] . $self->escapeToken($match[2]); + } + + if ($token && $token[0] !== '-') { + return $self->escapeToken($token); + } + + return $token; + }, $this->tokens); + + return implode(' ', $tokens); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Input/ArrayInput.php b/vendor/symfony/console/Symfony/Component/Console/Input/ArrayInput.php new file mode 100644 index 0000000..ad9f2ca --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Input/ArrayInput.php @@ -0,0 +1,209 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * ArrayInput represents an input provided as an array. + * + * Usage: + * + * $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar')); + * + * @author Fabien Potencier + * + * @api + */ +class ArrayInput extends Input +{ + private $parameters; + + /** + * Constructor. + * + * @param array $parameters An array of parameters + * @param InputDefinition $definition A InputDefinition instance + * + * @api + */ + public function __construct(array $parameters, InputDefinition $definition = null) + { + $this->parameters = $parameters; + + parent::__construct($definition); + } + + /** + * Returns the first argument from the raw parameters (not parsed). + * + * @return string The value of the first argument or null otherwise + */ + public function getFirstArgument() + { + foreach ($this->parameters as $key => $value) { + if ($key && '-' === $key[0]) { + continue; + } + + return $value; + } + } + + /** + * Returns true if the raw parameters (not parsed) contain a value. + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * + * @param string|array $values The values to look for in the raw parameters (can be an array) + * + * @return Boolean true if the value is contained in the raw parameters + */ + public function hasParameterOption($values) + { + $values = (array) $values; + + foreach ($this->parameters as $k => $v) { + if (!is_int($k)) { + $v = $k; + } + + if (in_array($v, $values)) { + return true; + } + } + + return false; + } + + /** + * Returns the value of a raw option (not parsed). + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * + * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * @param mixed $default The default value to return if no result is found + * + * @return mixed The option value + */ + public function getParameterOption($values, $default = false) + { + $values = (array) $values; + + foreach ($this->parameters as $k => $v) { + if (is_int($k) && in_array($v, $values)) { + return true; + } elseif (in_array($k, $values)) { + return $v; + } + } + + return $default; + } + + /** + * Returns a stringified representation of the args passed to the command + * + * @return string + */ + public function __toString() + { + $params = array(); + foreach ($this->parameters as $param => $val) { + if ($param && '-' === $param[0]) { + $params[] = $param . ('' != $val ? '='.$this->escapeToken($val) : ''); + } else { + $params[] = $this->escapeToken($val); + } + } + + return implode(' ', $params); + } + + /** + * Processes command line arguments. + */ + protected function parse() + { + foreach ($this->parameters as $key => $value) { + if (0 === strpos($key, '--')) { + $this->addLongOption(substr($key, 2), $value); + } elseif ('-' === $key[0]) { + $this->addShortOption(substr($key, 1), $value); + } else { + $this->addArgument($key, $value); + } + } + } + + /** + * Adds a short option value. + * + * @param string $shortcut The short option key + * @param mixed $value The value for the option + * + * @throws \InvalidArgumentException When option given doesn't exist + */ + private function addShortOption($shortcut, $value) + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * Adds a long option value. + * + * @param string $name The long option key + * @param mixed $value The value for the option + * + * @throws \InvalidArgumentException When option given doesn't exist + * @throws \InvalidArgumentException When a required value is missing + */ + private function addLongOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); + } + + $option = $this->definition->getOption($name); + + if (null === $value) { + if ($option->isValueRequired()) { + throw new \InvalidArgumentException(sprintf('The "--%s" option requires a value.', $name)); + } + + $value = $option->isValueOptional() ? $option->getDefault() : true; + } + + $this->options[$name] = $value; + } + + /** + * Adds an argument value. + * + * @param string $name The argument name + * @param mixed $value The value for the argument + * + * @throws \InvalidArgumentException When argument given doesn't exist + */ + private function addArgument($name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Input/Input.php b/vendor/symfony/console/Symfony/Component/Console/Input/Input.php new file mode 100644 index 0000000..fb84cf8 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Input/Input.php @@ -0,0 +1,225 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * Input is the base class for all concrete Input classes. + * + * Three concrete classes are provided by default: + * + * * `ArgvInput`: The input comes from the CLI arguments (argv) + * * `StringInput`: The input is provided as a string + * * `ArrayInput`: The input is provided as an array + * + * @author Fabien Potencier + */ +abstract class Input implements InputInterface +{ + protected $definition; + protected $options; + protected $arguments; + protected $interactive = true; + + /** + * Constructor. + * + * @param InputDefinition $definition A InputDefinition instance + */ + public function __construct(InputDefinition $definition = null) + { + if (null === $definition) { + $this->arguments = array(); + $this->options = array(); + $this->definition = new InputDefinition(); + } else { + $this->bind($definition); + $this->validate(); + } + } + + /** + * Binds the current Input instance with the given arguments and options. + * + * @param InputDefinition $definition A InputDefinition instance + */ + public function bind(InputDefinition $definition) + { + $this->arguments = array(); + $this->options = array(); + $this->definition = $definition; + + $this->parse(); + } + + /** + * Processes command line arguments. + */ + abstract protected function parse(); + + /** + * Validates the input. + * + * @throws \RuntimeException When not enough arguments are given + */ + public function validate() + { + if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) { + throw new \RuntimeException('Not enough arguments.'); + } + } + + /** + * Checks if the input is interactive. + * + * @return Boolean Returns true if the input is interactive + */ + public function isInteractive() + { + return $this->interactive; + } + + /** + * Sets the input interactivity. + * + * @param Boolean $interactive If the input should be interactive + */ + public function setInteractive($interactive) + { + $this->interactive = (Boolean) $interactive; + } + + /** + * Returns the argument values. + * + * @return array An array of argument values + */ + public function getArguments() + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + /** + * Returns the argument value for a given argument name. + * + * @param string $name The argument name + * + * @return mixed The argument value + * + * @throws \InvalidArgumentException When argument given doesn't exist + */ + public function getArgument($name) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault(); + } + + /** + * Sets an argument value by name. + * + * @param string $name The argument name + * @param string $value The argument value + * + * @throws \InvalidArgumentException When argument given doesn't exist + */ + public function setArgument($name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } + + /** + * Returns true if an InputArgument object exists by name or position. + * + * @param string|integer $name The InputArgument name or position + * + * @return Boolean true if the InputArgument object exists, false otherwise + */ + public function hasArgument($name) + { + return $this->definition->hasArgument($name); + } + + /** + * Returns the options values. + * + * @return array An array of option values + */ + public function getOptions() + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + /** + * Returns the option value for a given option name. + * + * @param string $name The option name + * + * @return mixed The option value + * + * @throws \InvalidArgumentException When option given doesn't exist + */ + public function getOption($name) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); + } + + /** + * Sets an option value by name. + * + * @param string $name The option name + * @param string $value The option value + * + * @throws \InvalidArgumentException When option given doesn't exist + */ + public function setOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + $this->options[$name] = $value; + } + + /** + * Returns true if an InputOption object exists by name. + * + * @param string $name The InputOption name + * + * @return Boolean true if the InputOption object exists, false otherwise + */ + public function hasOption($name) + { + return $this->definition->hasOption($name); + } + + /** + * Escapes a token through escapeshellarg if it contains unsafe chars + * + * @param string $token + * + * @return string + */ + public function escapeToken($token) + { + return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Input/InputArgument.php b/vendor/symfony/console/Symfony/Component/Console/Input/InputArgument.php new file mode 100644 index 0000000..ffe9772 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Input/InputArgument.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * Represents a command line argument. + * + * @author Fabien Potencier + * + * @api + */ +class InputArgument +{ + const REQUIRED = 1; + const OPTIONAL = 2; + const IS_ARRAY = 4; + + private $name; + private $mode; + private $default; + private $description; + + /** + * Constructor. + * + * @param string $name The argument name + * @param integer $mode The argument mode: self::REQUIRED or self::OPTIONAL + * @param string $description A description text + * @param mixed $default The default value (for self::OPTIONAL mode only) + * + * @throws \InvalidArgumentException When argument mode is not valid + * + * @api + */ + public function __construct($name, $mode = null, $description = '', $default = null) + { + if (null === $mode) { + $mode = self::OPTIONAL; + } elseif (!is_int($mode) || $mode > 7 || $mode < 1) { + throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->mode = $mode; + $this->description = $description; + + $this->setDefault($default); + } + + /** + * Returns the argument name. + * + * @return string The argument name + */ + public function getName() + { + return $this->name; + } + + /** + * Returns true if the argument is required. + * + * @return Boolean true if parameter mode is self::REQUIRED, false otherwise + */ + public function isRequired() + { + return self::REQUIRED === (self::REQUIRED & $this->mode); + } + + /** + * Returns true if the argument can take multiple values. + * + * @return Boolean true if mode is self::IS_ARRAY, false otherwise + */ + public function isArray() + { + return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); + } + + /** + * Sets the default value. + * + * @param mixed $default The default value + * + * @throws \LogicException When incorrect default value is given + */ + public function setDefault($default = null) + { + if (self::REQUIRED === $this->mode && null !== $default) { + throw new \LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = array(); + } elseif (!is_array($default)) { + throw new \LogicException('A default value for an array argument must be an array.'); + } + } + + $this->default = $default; + } + + /** + * Returns the default value. + * + * @return mixed The default value + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns the description text. + * + * @return string The description text + */ + public function getDescription() + { + return $this->description; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Input/InputDefinition.php b/vendor/symfony/console/Symfony/Component/Console/Input/InputDefinition.php new file mode 100644 index 0000000..257d057 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Input/InputDefinition.php @@ -0,0 +1,443 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Descriptor\XmlDescriptor; + +/** + * A InputDefinition represents a set of valid command line arguments and options. + * + * Usage: + * + * $definition = new InputDefinition(array( + * new InputArgument('name', InputArgument::REQUIRED), + * new InputOption('foo', 'f', InputOption::VALUE_REQUIRED), + * )); + * + * @author Fabien Potencier + * + * @api + */ +class InputDefinition +{ + private $arguments; + private $requiredCount; + private $hasAnArrayArgument = false; + private $hasOptional; + private $options; + private $shortcuts; + + /** + * Constructor. + * + * @param array $definition An array of InputArgument and InputOption instance + * + * @api + */ + public function __construct(array $definition = array()) + { + $this->setDefinition($definition); + } + + /** + * Sets the definition of the input. + * + * @param array $definition The definition array + * + * @api + */ + public function setDefinition(array $definition) + { + $arguments = array(); + $options = array(); + foreach ($definition as $item) { + if ($item instanceof InputOption) { + $options[] = $item; + } else { + $arguments[] = $item; + } + } + + $this->setArguments($arguments); + $this->setOptions($options); + } + + /** + * Sets the InputArgument objects. + * + * @param InputArgument[] $arguments An array of InputArgument objects + * + * @api + */ + public function setArguments($arguments = array()) + { + $this->arguments = array(); + $this->requiredCount = 0; + $this->hasOptional = false; + $this->hasAnArrayArgument = false; + $this->addArguments($arguments); + } + + /** + * Adds an array of InputArgument objects. + * + * @param InputArgument[] $arguments An array of InputArgument objects + * + * @api + */ + public function addArguments($arguments = array()) + { + if (null !== $arguments) { + foreach ($arguments as $argument) { + $this->addArgument($argument); + } + } + } + + /** + * Adds an InputArgument object. + * + * @param InputArgument $argument An InputArgument object + * + * @throws \LogicException When incorrect argument is given + * + * @api + */ + public function addArgument(InputArgument $argument) + { + if (isset($this->arguments[$argument->getName()])) { + throw new \LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); + } + + if ($this->hasAnArrayArgument) { + throw new \LogicException('Cannot add an argument after an array argument.'); + } + + if ($argument->isRequired() && $this->hasOptional) { + throw new \LogicException('Cannot add a required argument after an optional one.'); + } + + if ($argument->isArray()) { + $this->hasAnArrayArgument = true; + } + + if ($argument->isRequired()) { + ++$this->requiredCount; + } else { + $this->hasOptional = true; + } + + $this->arguments[$argument->getName()] = $argument; + } + + /** + * Returns an InputArgument by name or by position. + * + * @param string|integer $name The InputArgument name or position + * + * @return InputArgument An InputArgument object + * + * @throws \InvalidArgumentException When argument given doesn't exist + * + * @api + */ + public function getArgument($name) + { + if (!$this->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return $arguments[$name]; + } + + /** + * Returns true if an InputArgument object exists by name or position. + * + * @param string|integer $name The InputArgument name or position + * + * @return Boolean true if the InputArgument object exists, false otherwise + * + * @api + */ + public function hasArgument($name) + { + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return isset($arguments[$name]); + } + + /** + * Gets the array of InputArgument objects. + * + * @return InputArgument[] An array of InputArgument objects + * + * @api + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Returns the number of InputArguments. + * + * @return integer The number of InputArguments + */ + public function getArgumentCount() + { + return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); + } + + /** + * Returns the number of required InputArguments. + * + * @return integer The number of required InputArguments + */ + public function getArgumentRequiredCount() + { + return $this->requiredCount; + } + + /** + * Gets the default values. + * + * @return array An array of default values + */ + public function getArgumentDefaults() + { + $values = array(); + foreach ($this->arguments as $argument) { + $values[$argument->getName()] = $argument->getDefault(); + } + + return $values; + } + + /** + * Sets the InputOption objects. + * + * @param InputOption[] $options An array of InputOption objects + * + * @api + */ + public function setOptions($options = array()) + { + $this->options = array(); + $this->shortcuts = array(); + $this->addOptions($options); + } + + /** + * Adds an array of InputOption objects. + * + * @param InputOption[] $options An array of InputOption objects + * + * @api + */ + public function addOptions($options = array()) + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * Adds an InputOption object. + * + * @param InputOption $option An InputOption object + * + * @throws \LogicException When option given already exist + * + * @api + */ + public function addOption(InputOption $option) + { + if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { + throw new \LogicException(sprintf('An option named "%s" already exists.', $option->getName())); + } + + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) { + throw new \LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); + } + } + } + + $this->options[$option->getName()] = $option; + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + $this->shortcuts[$shortcut] = $option->getName(); + } + } + } + + /** + * Returns an InputOption by name. + * + * @param string $name The InputOption name + * + * @return InputOption A InputOption object + * + * @throws \InvalidArgumentException When option given doesn't exist + * + * @api + */ + public function getOption($name) + { + if (!$this->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); + } + + return $this->options[$name]; + } + + /** + * Returns true if an InputOption object exists by name. + * + * @param string $name The InputOption name + * + * @return Boolean true if the InputOption object exists, false otherwise + * + * @api + */ + public function hasOption($name) + { + return isset($this->options[$name]); + } + + /** + * Gets the array of InputOption objects. + * + * @return InputOption[] An array of InputOption objects + * + * @api + */ + public function getOptions() + { + return $this->options; + } + + /** + * Returns true if an InputOption object exists by shortcut. + * + * @param string $name The InputOption shortcut + * + * @return Boolean true if the InputOption object exists, false otherwise + */ + public function hasShortcut($name) + { + return isset($this->shortcuts[$name]); + } + + /** + * Gets an InputOption by shortcut. + * + * @param string $shortcut the Shortcut name + * + * @return InputOption An InputOption object + */ + public function getOptionForShortcut($shortcut) + { + return $this->getOption($this->shortcutToName($shortcut)); + } + + /** + * Gets an array of default values. + * + * @return array An array of all default values + */ + public function getOptionDefaults() + { + $values = array(); + foreach ($this->options as $option) { + $values[$option->getName()] = $option->getDefault(); + } + + return $values; + } + + /** + * Returns the InputOption name given a shortcut. + * + * @param string $shortcut The shortcut + * + * @return string The InputOption name + * + * @throws \InvalidArgumentException When option given does not exist + */ + private function shortcutToName($shortcut) + { + if (!isset($this->shortcuts[$shortcut])) { + throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + return $this->shortcuts[$shortcut]; + } + + /** + * Gets the synopsis. + * + * @return string The synopsis + */ + public function getSynopsis() + { + $elements = array(); + foreach ($this->getOptions() as $option) { + $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; + $elements[] = sprintf('['.($option->isValueRequired() ? '%s--%s="..."' : ($option->isValueOptional() ? '%s--%s[="..."]' : '%s--%s')).']', $shortcut, $option->getName()); + } + + foreach ($this->getArguments() as $argument) { + $elements[] = sprintf($argument->isRequired() ? '%s' : '[%s]', $argument->getName().($argument->isArray() ? '1' : '')); + + if ($argument->isArray()) { + $elements[] = sprintf('... [%sN]', $argument->getName()); + } + } + + return implode(' ', $elements); + } + + /** + * Returns a textual representation of the InputDefinition. + * + * @return string A string representing the InputDefinition + * + * @deprecated Deprecated since version 2.3, to be removed in 3.0. + */ + public function asText() + { + $descriptor = new TextDescriptor(); + + return $descriptor->describe($this); + } + + /** + * Returns an XML representation of the InputDefinition. + * + * @param Boolean $asDom Whether to return a DOM or an XML string + * + * @return string|\DOMDocument An XML string representing the InputDefinition + * + * @deprecated Deprecated since version 2.3, to be removed in 3.0. + */ + public function asXml($asDom = false) + { + $descriptor = new XmlDescriptor(); + + return $descriptor->describe($this, array('as_dom' => $asDom)); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Input/InputInterface.php b/vendor/symfony/console/Symfony/Component/Console/Input/InputInterface.php new file mode 100644 index 0000000..c39429d --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Input/InputInterface.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * InputInterface is the interface implemented by all input classes. + * + * @author Fabien Potencier + */ +interface InputInterface +{ + /** + * Returns the first argument from the raw parameters (not parsed). + * + * @return string The value of the first argument or null otherwise + */ + public function getFirstArgument(); + + /** + * Returns true if the raw parameters (not parsed) contain a value. + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * + * @param string|array $values The values to look for in the raw parameters (can be an array) + * + * @return Boolean true if the value is contained in the raw parameters + */ + public function hasParameterOption($values); + + /** + * Returns the value of a raw option (not parsed). + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * + * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * @param mixed $default The default value to return if no result is found + * + * @return mixed The option value + */ + public function getParameterOption($values, $default = false); + + /** + * Binds the current Input instance with the given arguments and options. + * + * @param InputDefinition $definition A InputDefinition instance + */ + public function bind(InputDefinition $definition); + + /** + * Validates if arguments given are correct. + * + * Throws an exception when not enough arguments are given. + * + * @throws \RuntimeException + */ + public function validate(); + + /** + * Returns all the given arguments merged with the default values. + * + * @return array + */ + public function getArguments(); + + /** + * Gets argument by name. + * + * @param string $name The name of the argument + * + * @return mixed + */ + public function getArgument($name); + + /** + * Sets an argument value by name. + * + * @param string $name The argument name + * @param string $value The argument value + * + * @throws \InvalidArgumentException When argument given doesn't exist + */ + public function setArgument($name, $value); + + /** + * Returns true if an InputArgument object exists by name or position. + * + * @param string|integer $name The InputArgument name or position + * + * @return Boolean true if the InputArgument object exists, false otherwise + */ + public function hasArgument($name); + + /** + * Returns all the given options merged with the default values. + * + * @return array + */ + public function getOptions(); + + /** + * Gets an option by name. + * + * @param string $name The name of the option + * + * @return mixed + */ + public function getOption($name); + + /** + * Sets an option value by name. + * + * @param string $name The option name + * @param string $value The option value + * + * @throws \InvalidArgumentException When option given doesn't exist + */ + public function setOption($name, $value); + + /** + * Returns true if an InputOption object exists by name. + * + * @param string $name The InputOption name + * + * @return Boolean true if the InputOption object exists, false otherwise + */ + public function hasOption($name); + + /** + * Is this input means interactive? + * + * @return Boolean + */ + public function isInteractive(); + + /** + * Sets the input interactivity. + * + * @param Boolean $interactive If the input should be interactive + */ + public function setInteractive($interactive); +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Input/InputOption.php b/vendor/symfony/console/Symfony/Component/Console/Input/InputOption.php new file mode 100644 index 0000000..e1fdea0 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Input/InputOption.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * Represents a command line option. + * + * @author Fabien Potencier + * + * @api + */ +class InputOption +{ + const VALUE_NONE = 1; + const VALUE_REQUIRED = 2; + const VALUE_OPTIONAL = 4; + const VALUE_IS_ARRAY = 8; + + private $name; + private $shortcut; + private $mode; + private $default; + private $description; + + /** + * Constructor. + * + * @param string $name The option name + * @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param integer $mode The option mode: One of the VALUE_* constants + * @param string $description A description text + * @param mixed $default The default value (must be null for self::VALUE_REQUIRED or self::VALUE_NONE) + * + * @throws \InvalidArgumentException If option mode is invalid or incompatible + * + * @api + */ + public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + if (0 === strpos($name, '--')) { + $name = substr($name, 2); + } + + if (empty($name)) { + throw new \InvalidArgumentException('An option name cannot be empty.'); + } + + if (empty($shortcut)) { + $shortcut = null; + } + + if (null !== $shortcut) { + if (is_array($shortcut)) { + $shortcut = implode('|', $shortcut); + } + $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); + $shortcuts = array_filter($shortcuts); + $shortcut = implode('|', $shortcuts); + + if (empty($shortcut)) { + throw new \InvalidArgumentException('An option shortcut cannot be empty.'); + } + } + + if (null === $mode) { + $mode = self::VALUE_NONE; + } elseif (!is_int($mode) || $mode > 15 || $mode < 1) { + throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->shortcut = $shortcut; + $this->mode = $mode; + $this->description = $description; + + if ($this->isArray() && !$this->acceptValue()) { + throw new \InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); + } + + $this->setDefault($default); + } + + /** + * Returns the option shortcut. + * + * @return string The shortcut + */ + public function getShortcut() + { + return $this->shortcut; + } + + /** + * Returns the option name. + * + * @return string The name + */ + public function getName() + { + return $this->name; + } + + /** + * Returns true if the option accepts a value. + * + * @return Boolean true if value mode is not self::VALUE_NONE, false otherwise + */ + public function acceptValue() + { + return $this->isValueRequired() || $this->isValueOptional(); + } + + /** + * Returns true if the option requires a value. + * + * @return Boolean true if value mode is self::VALUE_REQUIRED, false otherwise + */ + public function isValueRequired() + { + return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); + } + + /** + * Returns true if the option takes an optional value. + * + * @return Boolean true if value mode is self::VALUE_OPTIONAL, false otherwise + */ + public function isValueOptional() + { + return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); + } + + /** + * Returns true if the option can take multiple values. + * + * @return Boolean true if mode is self::VALUE_IS_ARRAY, false otherwise + */ + public function isArray() + { + return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); + } + + /** + * Sets the default value. + * + * @param mixed $default The default value + * + * @throws \LogicException When incorrect default value is given + */ + public function setDefault($default = null) + { + if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { + throw new \LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = array(); + } elseif (!is_array($default)) { + throw new \LogicException('A default value for an array option must be an array.'); + } + } + + $this->default = $this->acceptValue() ? $default : false; + } + + /** + * Returns the default value. + * + * @return mixed The default value + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns the description text. + * + * @return string The description text + */ + public function getDescription() + { + return $this->description; + } + + /** + * Checks whether the given option equals this one + * + * @param InputOption $option option to compare + * @return Boolean + */ + public function equals(InputOption $option) + { + return $option->getName() === $this->getName() + && $option->getShortcut() === $this->getShortcut() + && $option->getDefault() === $this->getDefault() + && $option->isArray() === $this->isArray() + && $option->isValueRequired() === $this->isValueRequired() + && $option->isValueOptional() === $this->isValueOptional() + ; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Input/StringInput.php b/vendor/symfony/console/Symfony/Component/Console/Input/StringInput.php new file mode 100644 index 0000000..7f87f71 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Input/StringInput.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * StringInput represents an input provided as a string. + * + * Usage: + * + * $input = new StringInput('foo --bar="foobar"'); + * + * @author Fabien Potencier + * + * @api + */ +class StringInput extends ArgvInput +{ + const REGEX_STRING = '([^\s]+?)(?:\s|(?setTokens($this->tokenize($input)); + + if (null !== $definition) { + $this->bind($definition); + } + } + + /** + * Tokenizes a string. + * + * @param string $input The input to tokenize + * + * @return array An array of tokens + * + * @throws \InvalidArgumentException When unable to parse input (should never happen) + */ + private function tokenize($input) + { + $tokens = array(); + $length = strlen($input); + $cursor = 0; + while ($cursor < $length) { + if (preg_match('/\s+/A', $input, $match, null, $cursor)) { + } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) { + $tokens[] = $match[1].$match[2].stripcslashes(str_replace(array('"\'', '\'"', '\'\'', '""'), '', substr($match[3], 1, strlen($match[3]) - 2))); + } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) { + $tokens[] = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2)); + } elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) { + $tokens[] = stripcslashes($match[1]); + } else { + // should never happen + // @codeCoverageIgnoreStart + throw new \InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10))); + // @codeCoverageIgnoreEnd + } + + $cursor += strlen($match[0]); + } + + return $tokens; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/LICENSE b/vendor/symfony/console/Symfony/Component/Console/LICENSE new file mode 100644 index 0000000..88a57f8 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2013 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/console/Symfony/Component/Console/Output/ConsoleOutput.php b/vendor/symfony/console/Symfony/Component/Console/Output/ConsoleOutput.php new file mode 100644 index 0000000..6dad744 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Output/ConsoleOutput.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; + +/** + * ConsoleOutput is the default class for all CLI output. It uses STDOUT. + * + * This class is a convenient wrapper around `StreamOutput`. + * + * $output = new ConsoleOutput(); + * + * This is equivalent to: + * + * $output = new StreamOutput(fopen('php://stdout', 'w')); + * + * @author Fabien Potencier + * + * @api + */ +class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface +{ + private $stderr; + + /** + * Constructor. + * + * @param integer $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param Boolean|null $decorated Whether to decorate messages (null for auto-guessing) + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + * + * @api + */ + public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) + { + $outputStream = 'php://stdout'; + if (!$this->hasStdoutSupport()) { + $outputStream = 'php://output'; + } + + parent::__construct(fopen($outputStream, 'w'), $verbosity, $decorated, $formatter); + + $this->stderr = new StreamOutput(fopen('php://stderr', 'w'), $verbosity, $decorated, $formatter); + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + parent::setDecorated($decorated); + $this->stderr->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->stderr->setFormatter($formatter); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + parent::setVerbosity($level); + $this->stderr->setVerbosity($level); + } + + /** + * {@inheritdoc} + */ + public function getErrorOutput() + { + return $this->stderr; + } + + /** + * {@inheritdoc} + */ + public function setErrorOutput(OutputInterface $error) + { + $this->stderr = $error; + } + + /** + * Returns true if current environment supports writing console output to + * STDOUT. + * + * IBM iSeries (OS400) exhibits character-encoding issues when writing to + * STDOUT and doesn't properly convert ASCII to EBCDIC, resulting in garbage + * output. + * + * @return boolean + */ + protected function hasStdoutSupport() + { + return ('OS400' != php_uname('s')); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Output/ConsoleOutputInterface.php b/vendor/symfony/console/Symfony/Component/Console/Output/ConsoleOutputInterface.php new file mode 100644 index 0000000..c63bd15 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Output/ConsoleOutputInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * ConsoleOutputInterface is the interface implemented by ConsoleOutput class. + * This adds information about stderr output stream. + * + * @author Dariusz Górecki + */ +interface ConsoleOutputInterface extends OutputInterface +{ + /** + * Gets the OutputInterface for errors. + * + * @return OutputInterface + */ + public function getErrorOutput(); + + /** + * Sets the OutputInterface used for errors. + * + * @param OutputInterface $error + */ + public function setErrorOutput(OutputInterface $error); +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Output/NullOutput.php b/vendor/symfony/console/Symfony/Component/Console/Output/NullOutput.php new file mode 100644 index 0000000..c75cfca --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Output/NullOutput.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * NullOutput suppresses all output. + * + * $output = new NullOutput(); + * + * @author Fabien Potencier + * @author Tobias Schultze + * + * @api + */ +class NullOutput implements OutputInterface +{ + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + // to comply with the interface we must return a OutputFormatterInterface + return new OutputFormatter(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return self::VERBOSITY_QUIET; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + // do nothing + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Output/Output.php b/vendor/symfony/console/Symfony/Component/Console/Output/Output.php new file mode 100644 index 0000000..0daedc3 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Output/Output.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * Base class for output classes. + * + * There are five levels of verbosity: + * + * * normal: no option passed (normal output) + * * verbose: -v (more output) + * * very verbose: -vv (highly extended output) + * * debug: -vvv (all debug output) + * * quiet: -q (no output) + * + * @author Fabien Potencier + * + * @api + */ +abstract class Output implements OutputInterface +{ + private $verbosity; + private $formatter; + + /** + * Constructor. + * + * @param integer $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param Boolean $decorated Whether to decorate messages + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + * + * @api + */ + public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = false, OutputFormatterInterface $formatter = null) + { + $this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity; + $this->formatter = null === $formatter ? new OutputFormatter() : $formatter; + $this->formatter->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + $this->formatter = $formatter; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->formatter; + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + $this->formatter->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->formatter->isDecorated(); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + $this->verbosity = (int) $level; + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + $this->write($messages, true, $type); + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + if (self::VERBOSITY_QUIET === $this->verbosity) { + return; + } + + $messages = (array) $messages; + + foreach ($messages as $message) { + switch ($type) { + case OutputInterface::OUTPUT_NORMAL: + $message = $this->formatter->format($message); + break; + case OutputInterface::OUTPUT_RAW: + break; + case OutputInterface::OUTPUT_PLAIN: + $message = strip_tags($this->formatter->format($message)); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type)); + } + + $this->doWrite($message, $newline); + } + } + + /** + * Writes a message to the output. + * + * @param string $message A message to write to the output + * @param Boolean $newline Whether to add a newline or not + */ + abstract protected function doWrite($message, $newline); +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Output/OutputInterface.php b/vendor/symfony/console/Symfony/Component/Console/Output/OutputInterface.php new file mode 100644 index 0000000..83d5013 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Output/OutputInterface.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * OutputInterface is the interface implemented by all Output classes. + * + * @author Fabien Potencier + * + * @api + */ +interface OutputInterface +{ + const VERBOSITY_QUIET = 0; + const VERBOSITY_NORMAL = 1; + const VERBOSITY_VERBOSE = 2; + const VERBOSITY_VERY_VERBOSE = 3; + const VERBOSITY_DEBUG = 4; + + const OUTPUT_NORMAL = 0; + const OUTPUT_RAW = 1; + const OUTPUT_PLAIN = 2; + + /** + * Writes a message to the output. + * + * @param string|array $messages The message as an array of lines or a single string + * @param Boolean $newline Whether to add a newline + * @param integer $type The type of output (one of the OUTPUT constants) + * + * @throws \InvalidArgumentException When unknown output type is given + * + * @api + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL); + + /** + * Writes a message to the output and adds a newline at the end. + * + * @param string|array $messages The message as an array of lines of a single string + * @param integer $type The type of output (one of the OUTPUT constants) + * + * @throws \InvalidArgumentException When unknown output type is given + * + * @api + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL); + + /** + * Sets the verbosity of the output. + * + * @param integer $level The level of verbosity (one of the VERBOSITY constants) + * + * @api + */ + public function setVerbosity($level); + + /** + * Gets the current verbosity of the output. + * + * @return integer The current level of verbosity (one of the VERBOSITY constants) + * + * @api + */ + public function getVerbosity(); + + /** + * Sets the decorated flag. + * + * @param Boolean $decorated Whether to decorate the messages + * + * @api + */ + public function setDecorated($decorated); + + /** + * Gets the decorated flag. + * + * @return Boolean true if the output will decorate messages, false otherwise + * + * @api + */ + public function isDecorated(); + + /** + * Sets output formatter. + * + * @param OutputFormatterInterface $formatter + * + * @api + */ + public function setFormatter(OutputFormatterInterface $formatter); + + /** + * Returns current output formatter instance. + * + * @return OutputFormatterInterface + * + * @api + */ + public function getFormatter(); +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Output/StreamOutput.php b/vendor/symfony/console/Symfony/Component/Console/Output/StreamOutput.php new file mode 100644 index 0000000..09a5ca3 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Output/StreamOutput.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * StreamOutput writes the output to a given stream. + * + * Usage: + * + * $output = new StreamOutput(fopen('php://stdout', 'w')); + * + * As `StreamOutput` can use any stream, you can also use a file: + * + * $output = new StreamOutput(fopen('/path/to/output.log', 'a', false)); + * + * @author Fabien Potencier + * + * @api + */ +class StreamOutput extends Output +{ + private $stream; + + /** + * Constructor. + * + * @param mixed $stream A stream resource + * @param integer $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param Boolean|null $decorated Whether to decorate messages (null for auto-guessing) + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + * + * @throws \InvalidArgumentException When first argument is not a real stream + * + * @api + */ + public function __construct($stream, $verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) + { + if (!is_resource($stream) || 'stream' !== get_resource_type($stream)) { + throw new \InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); + } + + $this->stream = $stream; + + if (null === $decorated) { + $decorated = $this->hasColorSupport(); + } + + parent::__construct($verbosity, $decorated, $formatter); + } + + /** + * Gets the stream attached to this StreamOutput instance. + * + * @return resource A stream resource + */ + public function getStream() + { + return $this->stream; + } + + /** + * {@inheritdoc} + */ + protected function doWrite($message, $newline) + { + if (false === @fwrite($this->stream, $message.($newline ? PHP_EOL : ''))) { + // @codeCoverageIgnoreStart + // should never happen + throw new \RuntimeException('Unable to write output.'); + // @codeCoverageIgnoreEnd + } + + fflush($this->stream); + } + + /** + * Returns true if the stream supports colorization. + * + * Colorization is disabled if not supported by the stream: + * + * - windows without ansicon and ConEmu + * - non tty consoles + * + * @return Boolean true if the stream supports colorization, false otherwise + */ + protected function hasColorSupport() + { + // @codeCoverageIgnoreStart + if (DIRECTORY_SEPARATOR == '\\') { + return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI'); + } + + return function_exists('posix_isatty') && @posix_isatty($this->stream); + // @codeCoverageIgnoreEnd + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/README.md b/vendor/symfony/console/Symfony/Component/Console/README.md new file mode 100644 index 0000000..fbf05fc --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/README.md @@ -0,0 +1,63 @@ +Console Component +================= + +Console eases the creation of beautiful and testable command line interfaces. + +The Application object manages the CLI application: + + use Symfony\Component\Console\Application; + + $console = new Application(); + $console->run(); + +The ``run()`` method parses the arguments and options passed on the command +line and executes the right command. + +Registering a new command can easily be done via the ``register()`` method, +which returns a ``Command`` instance: + + use Symfony\Component\Console\Input\InputInterface; + use Symfony\Component\Console\Input\InputArgument; + use Symfony\Component\Console\Input\InputOption; + use Symfony\Component\Console\Output\OutputInterface; + + $console + ->register('ls') + ->setDefinition(array( + new InputArgument('dir', InputArgument::REQUIRED, 'Directory name'), + )) + ->setDescription('Displays the files in the given directory') + ->setCode(function (InputInterface $input, OutputInterface $output) { + $dir = $input->getArgument('dir'); + + $output->writeln(sprintf('Dir listing for %s', $dir)); + }) + ; + +You can also register new commands via classes. + +The component provides a lot of features like output coloring, input and +output abstractions (so that you can easily unit-test your commands), +validation, automatic help messages, ... + +Tests +----- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/Console/ + $ composer.phar install --dev + $ phpunit + +Third Party +----------- + +`Resources/bin/hiddeninput.exe` is a third party binary provided within this +component. Find sources and license at https://github.com/Seldaek/hidden-input. + +Resources +--------- + +[The Console Component](http://symfony.com/doc/current/components/console.html) + +[How to create a Console Command](http://symfony.com/doc/current/cookbook/console/console_command.html) diff --git a/vendor/symfony/console/Symfony/Component/Console/Resources/bin/hiddeninput.exe b/vendor/symfony/console/Symfony/Component/Console/Resources/bin/hiddeninput.exe new file mode 100644 index 0000000..c8cf65e Binary files /dev/null and b/vendor/symfony/console/Symfony/Component/Console/Resources/bin/hiddeninput.exe differ diff --git a/vendor/symfony/console/Symfony/Component/Console/Shell.php b/vendor/symfony/console/Symfony/Component/Console/Shell.php new file mode 100644 index 0000000..ff8bef5 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Shell.php @@ -0,0 +1,230 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Process\ProcessBuilder; +use Symfony\Component\Process\PhpExecutableFinder; + +/** + * A Shell wraps an Application to add shell capabilities to it. + * + * Support for history and completion only works with a PHP compiled + * with readline support (either --with-readline or --with-libedit) + * + * @author Fabien Potencier + * @author Martin Hasoň + */ +class Shell +{ + private $application; + private $history; + private $output; + private $hasReadline; + private $processIsolation; + + /** + * Constructor. + * + * If there is no readline support for the current PHP executable + * a \RuntimeException exception is thrown. + * + * @param Application $application An application instance + */ + public function __construct(Application $application) + { + $this->hasReadline = function_exists('readline'); + $this->application = $application; + $this->history = getenv('HOME').'/.history_'.$application->getName(); + $this->output = new ConsoleOutput(); + $this->processIsolation = false; + } + + /** + * Runs the shell. + */ + public function run() + { + $this->application->setAutoExit(false); + $this->application->setCatchExceptions(true); + + if ($this->hasReadline) { + readline_read_history($this->history); + readline_completion_function(array($this, 'autocompleter')); + } + + $this->output->writeln($this->getHeader()); + $php = null; + if ($this->processIsolation) { + $finder = new PhpExecutableFinder(); + $php = $finder->find(); + $this->output->writeln(<<Running with process isolation, you should consider this: + * each command is executed as separate process, + * commands don't support interactivity, all params must be passed explicitly, + * commands output is not colorized. + +EOF + ); + } + + while (true) { + $command = $this->readline(); + + if (false === $command) { + $this->output->writeln("\n"); + + break; + } + + if ($this->hasReadline) { + readline_add_history($command); + readline_write_history($this->history); + } + + if ($this->processIsolation) { + $pb = new ProcessBuilder(); + + $process = $pb + ->add($php) + ->add($_SERVER['argv'][0]) + ->add($command) + ->inheritEnvironmentVariables(true) + ->getProcess() + ; + + $output = $this->output; + $process->run(function($type, $data) use ($output) { + $output->writeln($data); + }); + + $ret = $process->getExitCode(); + } else { + $ret = $this->application->run(new StringInput($command), $this->output); + } + + if (0 !== $ret) { + $this->output->writeln(sprintf('The command terminated with an error status (%s)', $ret)); + } + } + } + + /** + * Returns the shell header. + * + * @return string The header string + */ + protected function getHeader() + { + return <<{$this->application->getName()} shell ({$this->application->getVersion()}). + +At the prompt, type help for some help, +or list to get a list of available commands. + +To exit the shell, type ^D. + +EOF; + } + + /** + * Renders a prompt. + * + * @return string The prompt + */ + protected function getPrompt() + { + // using the formatter here is required when using readline + return $this->output->getFormatter()->format($this->application->getName().' > '); + } + + protected function getOutput() + { + return $this->output; + } + + protected function getApplication() + { + return $this->application; + } + + /** + * Tries to return autocompletion for the current entered text. + * + * @param string $text The last segment of the entered text + * + * @return Boolean|array A list of guessed strings or true + */ + private function autocompleter($text) + { + $info = readline_info(); + $text = substr($info['line_buffer'], 0, $info['end']); + + if ($info['point'] !== $info['end']) { + return true; + } + + // task name? + if (false === strpos($text, ' ') || !$text) { + return array_keys($this->application->all()); + } + + // options and arguments? + try { + $command = $this->application->find(substr($text, 0, strpos($text, ' '))); + } catch (\Exception $e) { + return true; + } + + $list = array('--help'); + foreach ($command->getDefinition()->getOptions() as $option) { + $list[] = '--'.$option->getName(); + } + + return $list; + } + + /** + * Reads a single line from standard input. + * + * @return string The single line from standard input + */ + private function readline() + { + if ($this->hasReadline) { + $line = readline($this->getPrompt()); + } else { + $this->output->write($this->getPrompt()); + $line = fgets(STDIN, 1024); + $line = (!$line && strlen($line) == 0) ? false : rtrim($line); + } + + return $line; + } + + public function getProcessIsolation() + { + return $this->processIsolation; + } + + public function setProcessIsolation($processIsolation) + { + $this->processIsolation = (Boolean) $processIsolation; + + if ($this->processIsolation && !class_exists('Symfony\\Component\\Process\\Process')) { + throw new \RuntimeException('Unable to isolate processes as the Symfony Process Component is not installed.'); + } + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tester/ApplicationTester.php b/vendor/symfony/console/Symfony/Component/Console/Tester/ApplicationTester.php new file mode 100644 index 0000000..2e57e1a --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tester/ApplicationTester.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\StreamOutput; + +/** + * Eases the testing of console applications. + * + * When testing an application, don't forget to disable the auto exit flag: + * + * $application = new Application(); + * $application->setAutoExit(false); + * + * @author Fabien Potencier + */ +class ApplicationTester +{ + private $application; + private $input; + private $output; + + /** + * Constructor. + * + * @param Application $application An Application instance to test. + */ + public function __construct(Application $application) + { + $this->application = $application; + } + + /** + * Executes the application. + * + * Available options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * + * @param array $input An array of arguments and options + * @param array $options An array of options + * + * @return integer The command exit code + */ + public function run(array $input, $options = array()) + { + $this->input = new ArrayInput($input); + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + $this->output = new StreamOutput(fopen('php://memory', 'w', false)); + if (isset($options['decorated'])) { + $this->output->setDecorated($options['decorated']); + } + if (isset($options['verbosity'])) { + $this->output->setVerbosity($options['verbosity']); + } + + return $this->application->run($this->input, $this->output); + } + + /** + * Gets the display returned by the last execution of the application. + * + * @param Boolean $normalize Whether to normalize end of lines to \n or not + * + * @return string The display + */ + public function getDisplay($normalize = false) + { + rewind($this->output->getStream()); + + $display = stream_get_contents($this->output->getStream()); + + if ($normalize) { + $display = str_replace(PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the input instance used by the last execution of the application. + * + * @return InputInterface The current input instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance used by the last execution of the application. + * + * @return OutputInterface The current output instance + */ + public function getOutput() + { + return $this->output; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tester/CommandTester.php b/vendor/symfony/console/Symfony/Component/Console/Tester/CommandTester.php new file mode 100644 index 0000000..23dae24 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tester/CommandTester.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\StreamOutput; + +/** + * Eases the testing of console commands. + * + * @author Fabien Potencier + */ +class CommandTester +{ + private $command; + private $input; + private $output; + + /** + * Constructor. + * + * @param Command $command A Command instance to test. + */ + public function __construct(Command $command) + { + $this->command = $command; + } + + /** + * Executes the command. + * + * Available options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * + * @param array $input An array of arguments and options + * @param array $options An array of options + * + * @return integer The command exit code + */ + public function execute(array $input, array $options = array()) + { + $this->input = new ArrayInput($input); + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + $this->output = new StreamOutput(fopen('php://memory', 'w', false)); + if (isset($options['decorated'])) { + $this->output->setDecorated($options['decorated']); + } + if (isset($options['verbosity'])) { + $this->output->setVerbosity($options['verbosity']); + } + + return $this->command->run($this->input, $this->output); + } + + /** + * Gets the display returned by the last execution of the command. + * + * @param Boolean $normalize Whether to normalize end of lines to \n or not + * + * @return string The display + */ + public function getDisplay($normalize = false) + { + rewind($this->output->getStream()); + + $display = stream_get_contents($this->output->getStream()); + + if ($normalize) { + $display = str_replace(PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the input instance used by the last execution of the command. + * + * @return InputInterface The current input instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance used by the last execution of the command. + * + * @return OutputInterface The current output instance + */ + public function getOutput() + { + return $this->output; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/ApplicationTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/ApplicationTest.php new file mode 100644 index 0000000..a557261 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/ApplicationTest.php @@ -0,0 +1,837 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\NullOutput; +use Symfony\Component\Console\Output\Output; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Tester\ApplicationTester; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleExceptionEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class ApplicationTest extends \PHPUnit_Framework_TestCase +{ + protected static $fixturesPath; + + public static function setUpBeforeClass() + { + self::$fixturesPath = realpath(__DIR__.'/Fixtures/'); + require_once self::$fixturesPath.'/FooCommand.php'; + require_once self::$fixturesPath.'/Foo1Command.php'; + require_once self::$fixturesPath.'/Foo2Command.php'; + require_once self::$fixturesPath.'/Foo3Command.php'; + require_once self::$fixturesPath.'/Foo4Command.php'; + } + + protected function normalizeLineBreaks($text) + { + return str_replace(PHP_EOL, "\n", $text); + } + + /** + * Replaces the dynamic placeholders of the command help text with a static version. + * The placeholder %command.full_name% includes the script path that is not predictable + * and can not be tested against. + */ + protected function ensureStaticCommandHelp(Application $application) + { + foreach ($application->all() as $command) { + $command->setHelp(str_replace('%command.full_name%', 'app/console %command.name%', $command->getHelp())); + } + } + + public function testConstructor() + { + $application = new Application('foo', 'bar'); + $this->assertEquals('foo', $application->getName(), '__construct() takes the application name as its first argument'); + $this->assertEquals('bar', $application->getVersion(), '__construct() takes the application version as its second argument'); + $this->assertEquals(array('help', 'list'), array_keys($application->all()), '__construct() registered the help and list commands by default'); + } + + public function testSetGetName() + { + $application = new Application(); + $application->setName('foo'); + $this->assertEquals('foo', $application->getName(), '->setName() sets the name of the application'); + } + + public function testSetGetVersion() + { + $application = new Application(); + $application->setVersion('bar'); + $this->assertEquals('bar', $application->getVersion(), '->setVersion() sets the version of the application'); + } + + public function testGetLongVersion() + { + $application = new Application('foo', 'bar'); + $this->assertEquals('foo version bar', $application->getLongVersion(), '->getLongVersion() returns the long version of the application'); + } + + public function testHelp() + { + $application = new Application(); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_gethelp.txt', $this->normalizeLineBreaks($application->getHelp()), '->setHelp() returns a help message'); + } + + public function testAll() + { + $application = new Application(); + $commands = $application->all(); + $this->assertEquals('Symfony\\Component\\Console\\Command\\HelpCommand', get_class($commands['help']), '->all() returns the registered commands'); + + $application->add(new \FooCommand()); + $commands = $application->all('foo'); + $this->assertEquals(1, count($commands), '->all() takes a namespace as its first argument'); + } + + public function testRegister() + { + $application = new Application(); + $command = $application->register('foo'); + $this->assertEquals('foo', $command->getName(), '->register() registers a new command'); + } + + public function testAdd() + { + $application = new Application(); + $application->add($foo = new \FooCommand()); + $commands = $application->all(); + $this->assertEquals($foo, $commands['foo:bar'], '->add() registers a command'); + + $application = new Application(); + $application->addCommands(array($foo = new \FooCommand(), $foo1 = new \Foo1Command())); + $commands = $application->all(); + $this->assertEquals(array($foo, $foo1), array($commands['foo:bar'], $commands['foo:bar1']), '->addCommands() registers an array of commands'); + } + + public function testHasGet() + { + $application = new Application(); + $this->assertTrue($application->has('list'), '->has() returns true if a named command is registered'); + $this->assertFalse($application->has('afoobar'), '->has() returns false if a named command is not registered'); + + $application->add($foo = new \FooCommand()); + $this->assertTrue($application->has('afoobar'), '->has() returns true if an alias is registered'); + $this->assertEquals($foo, $application->get('foo:bar'), '->get() returns a command by name'); + $this->assertEquals($foo, $application->get('afoobar'), '->get() returns a command by alias'); + + $application = new Application(); + $application->add($foo = new \FooCommand()); + // simulate --help + $r = new \ReflectionObject($application); + $p = $r->getProperty('wantHelps'); + $p->setAccessible(true); + $p->setValue($application, true); + $command = $application->get('foo:bar'); + $this->assertInstanceOf('Symfony\Component\Console\Command\HelpCommand', $command, '->get() returns the help command if --help is provided as the input'); + } + + public function testSilentHelp() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $tester = new ApplicationTester($application); + $tester->run(array('-h' => true, '-q' => true), array('decorated' => false)); + + $this->assertEmpty($tester->getDisplay(true)); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The command "foofoo" does not exist. + */ + public function testGetInvalidCommand() + { + $application = new Application(); + $application->get('foofoo'); + } + + public function testGetNamespaces() + { + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $this->assertEquals(array('foo'), $application->getNamespaces(), '->getNamespaces() returns an array of unique used namespaces'); + } + + public function testFindNamespace() + { + $application = new Application(); + $application->add(new \FooCommand()); + $this->assertEquals('foo', $application->findNamespace('foo'), '->findNamespace() returns the given namespace if it exists'); + $this->assertEquals('foo', $application->findNamespace('f'), '->findNamespace() finds a namespace given an abbreviation'); + $application->add(new \Foo2Command()); + $this->assertEquals('foo', $application->findNamespace('foo'), '->findNamespace() returns the given namespace if it exists'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The namespace "f" is ambiguous (foo, foo1). + */ + public function testFindAmbiguousNamespace() + { + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \Foo2Command()); + $application->findNamespace('f'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage There are no commands defined in the "bar" namespace. + */ + public function testFindInvalidNamespace() + { + $application = new Application(); + $application->findNamespace('bar'); + } + + public function testFind() + { + $application = new Application(); + $application->add(new \FooCommand()); + + $this->assertInstanceOf('FooCommand', $application->find('foo:bar'), '->find() returns a command if its name exists'); + $this->assertInstanceOf('Symfony\Component\Console\Command\HelpCommand', $application->find('h'), '->find() returns a command if its name exists'); + $this->assertInstanceOf('FooCommand', $application->find('f:bar'), '->find() returns a command if the abbreviation for the namespace exists'); + $this->assertInstanceOf('FooCommand', $application->find('f:b'), '->find() returns a command if the abbreviation for the namespace and the command name exist'); + $this->assertInstanceOf('FooCommand', $application->find('a'), '->find() returns a command if the abbreviation exists for an alias'); + } + + /** + * @dataProvider provideAmbiguousAbbreviations + */ + public function testFindWithAmbiguousAbbreviations($abbreviation, $expectedExceptionMessage) + { + $this->setExpectedException('InvalidArgumentException', $expectedExceptionMessage); + + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $application->add(new \Foo2Command()); + + $application->find($abbreviation); + } + + public function provideAmbiguousAbbreviations() + { + return array( + array('f', 'Command "f" is not defined.'), + array('a', 'Command "a" is ambiguous (afoobar, afoobar1 and 1 more).'), + array('foo:b', 'Command "foo:b" is ambiguous (foo:bar, foo:bar1).') + ); + } + + public function testFindCommandEqualNamespace() + { + $application = new Application(); + $application->add(new \Foo3Command()); + $application->add(new \Foo4Command()); + + $this->assertInstanceOf('Foo3Command', $application->find('foo3:bar'), '->find() returns the good command even if a namespace has same name'); + $this->assertInstanceOf('Foo4Command', $application->find('foo3:bar:toh'), '->find() returns a command even if its namespace equals another command name'); + } + + /** + * @dataProvider provideInvalidCommandNamesSingle + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Did you mean this + */ + public function testFindAlternativeExceptionMessageSingle($name) + { + $application = new Application(); + $application->add(new \FooCommand()); + $application->find($name); + } + + public function provideInvalidCommandNamesSingle() + { + return array( + array('foo:baR'), + array('foO:bar') + ); + } + + public function testFindAlternativeExceptionMessageMultiple() + { + $application = new Application(); + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $application->add(new \Foo2Command()); + + // Command + plural + try { + $application->find('foo:baR'); + $this->fail('->find() throws an \InvalidArgumentException if command does not exist, with alternatives'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist, with alternatives'); + $this->assertRegExp('/Did you mean one of these/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternatives'); + } + + // Namespace + plural + try { + $application->find('foo2:bar'); + $this->fail('->find() throws an \InvalidArgumentException if command does not exist, with alternatives'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist, with alternatives'); + $this->assertRegExp('/Did you mean one of these/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternatives'); + } + + $application->add(new \Foo3Command()); + $application->add(new \Foo4Command()); + + // Subnamespace + plural + try { + $a = $application->find('foo3:'); + $this->fail('->find() should throw an \InvalidArgumentException if a command is ambiguous because of a subnamespace, with alternatives'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e); + $this->assertRegExp('/foo3:bar/', $e->getMessage()); + $this->assertRegExp('/foo3:bar:toh/', $e->getMessage()); + } + } + + public function testFindAlternativeCommands() + { + $application = new Application(); + + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $application->add(new \Foo2Command()); + + try { + $application->find($commandName = 'Unknown command'); + $this->fail('->find() throws an \InvalidArgumentException if command does not exist'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist'); + $this->assertEquals(sprintf('Command "%s" is not defined.', $commandName), $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, without alternatives'); + } + + try { + $application->find($commandName = 'foo'); + $this->fail('->find() throws an \InvalidArgumentException if command does not exist'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist'); + $this->assertRegExp(sprintf('/Command "%s" is not defined./', $commandName), $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternatives'); + $this->assertRegExp('/foo:bar/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternative : "foo:bar"'); + $this->assertRegExp('/foo1:bar/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternative : "foo1:bar"'); + $this->assertRegExp('/foo:bar1/', $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternative : "foo:bar1"'); + } + + // Test if "foo1" command throw an "\InvalidArgumentException" and does not contain + // "foo:bar" as alternative because "foo1" is too far from "foo:bar" + try { + $application->find($commandName = 'foo1'); + $this->fail('->find() throws an \InvalidArgumentException if command does not exist'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if command does not exist'); + $this->assertRegExp(sprintf('/Command "%s" is not defined./', $commandName), $e->getMessage(), '->find() throws an \InvalidArgumentException if command does not exist, with alternatives'); + $this->assertFalse(strpos($e->getMessage(), 'foo:bar'), '->find() throws an \InvalidArgumentException if command does not exist, without "foo:bar" alternative'); + } + } + + public function testFindAlternativeNamespace() + { + $application = new Application(); + + $application->add(new \FooCommand()); + $application->add(new \Foo1Command()); + $application->add(new \Foo2Command()); + $application->add(new \foo3Command()); + + try { + $application->find('Unknown-namespace:Unknown-command'); + $this->fail('->find() throws an \InvalidArgumentException if namespace does not exist'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if namespace does not exist'); + $this->assertEquals('There are no commands defined in the "Unknown-namespace" namespace.', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, without alternatives'); + } + + try { + $application->find('foo2:command'); + $this->fail('->find() throws an \InvalidArgumentException if namespace does not exist'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->find() throws an \InvalidArgumentException if namespace does not exist'); + $this->assertRegExp('/There are no commands defined in the "foo2" namespace./', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, with alternative'); + $this->assertRegExp('/foo/', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, with alternative : "foo"'); + $this->assertRegExp('/foo1/', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, with alternative : "foo1"'); + $this->assertRegExp('/foo3/', $e->getMessage(), '->find() throws an \InvalidArgumentException if namespace does not exist, with alternative : "foo3"'); + } + } + + public function testFindNamespaceDoesNotFailOnDeepSimilarNamespaces() + { + $application = $this->getMock('Symfony\Component\Console\Application', array('getNamespaces')); + $application->expects($this->once()) + ->method('getNamespaces') + ->will($this->returnValue(array('foo:sublong', 'bar:sub'))); + + $this->assertEquals('foo:sublong', $application->findNamespace('f:sub')); + } + + public function testSetCatchExceptions() + { + $application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth')); + $application->setAutoExit(false); + $application->expects($this->any()) + ->method('getTerminalWidth') + ->will($this->returnValue(120)); + $tester = new ApplicationTester($application); + + $application->setCatchExceptions(true); + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception1.txt', $tester->getDisplay(true), '->setCatchExceptions() sets the catch exception flag'); + + $application->setCatchExceptions(false); + try { + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->fail('->setCatchExceptions() sets the catch exception flag'); + } catch (\Exception $e) { + $this->assertInstanceOf('\Exception', $e, '->setCatchExceptions() sets the catch exception flag'); + $this->assertEquals('Command "foo" is not defined.', $e->getMessage(), '->setCatchExceptions() sets the catch exception flag'); + } + } + + public function testAsText() + { + $application = new Application(); + $application->add(new \FooCommand); + $this->ensureStaticCommandHelp($application); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_astext1.txt', $this->normalizeLineBreaks($application->asText()), '->asText() returns a text representation of the application'); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_astext2.txt', $this->normalizeLineBreaks($application->asText('foo')), '->asText() returns a text representation of the application'); + } + + public function testAsXml() + { + $application = new Application(); + $application->add(new \FooCommand); + $this->ensureStaticCommandHelp($application); + $this->assertXmlStringEqualsXmlFile(self::$fixturesPath.'/application_asxml1.txt', $application->asXml(), '->asXml() returns an XML representation of the application'); + $this->assertXmlStringEqualsXmlFile(self::$fixturesPath.'/application_asxml2.txt', $application->asXml('foo'), '->asXml() returns an XML representation of the application'); + } + + public function testRenderException() + { + $application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth')); + $application->setAutoExit(false); + $application->expects($this->any()) + ->method('getTerminalWidth') + ->will($this->returnValue(120)); + $tester = new ApplicationTester($application); + + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception1.txt', $tester->getDisplay(true), '->renderException() renders a pretty exception'); + + $tester->run(array('command' => 'foo'), array('decorated' => false, 'verbosity' => Output::VERBOSITY_VERBOSE)); + $this->assertContains('Exception trace', $tester->getDisplay(), '->renderException() renders a pretty exception with a stack trace when verbosity is verbose'); + + $tester->run(array('command' => 'list', '--foo' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception2.txt', $tester->getDisplay(true), '->renderException() renders the command synopsis when an exception occurs in the context of a command'); + + $application->add(new \Foo3Command); + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo3:bar'), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception3.txt', $tester->getDisplay(true), '->renderException() renders a pretty exceptions with previous exceptions'); + + $application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth')); + $application->setAutoExit(false); + $application->expects($this->any()) + ->method('getTerminalWidth') + ->will($this->returnValue(32)); + $tester = new ApplicationTester($application); + + $tester->run(array('command' => 'foo'), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception4.txt', $tester->getDisplay(true), '->renderException() wraps messages when they are bigger than the terminal'); + } + + public function testRun() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + $application->add($command = new \Foo1Command()); + $_SERVER['argv'] = array('cli.php', 'foo:bar1'); + + ob_start(); + $application->run(); + ob_end_clean(); + + $this->assertSame('Symfony\Component\Console\Input\ArgvInput', get_class($command->input), '->run() creates an ArgvInput by default if none is given'); + $this->assertSame('Symfony\Component\Console\Output\ConsoleOutput', get_class($command->output), '->run() creates a ConsoleOutput by default if none is given'); + + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $this->ensureStaticCommandHelp($application); + $tester = new ApplicationTester($application); + + $tester->run(array(), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run1.txt', $tester->getDisplay(true), '->run() runs the list command if no argument is passed'); + + $tester->run(array('--help' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run2.txt', $tester->getDisplay(true), '->run() runs the help command if --help is passed'); + + $tester->run(array('-h' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run2.txt', $tester->getDisplay(true), '->run() runs the help command if -h is passed'); + + $tester->run(array('command' => 'list', '--help' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run3.txt', $tester->getDisplay(true), '->run() displays the help if --help is passed'); + + $tester->run(array('command' => 'list', '-h' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run3.txt', $tester->getDisplay(true), '->run() displays the help if -h is passed'); + + $tester->run(array('--ansi' => true)); + $this->assertTrue($tester->getOutput()->isDecorated(), '->run() forces color output if --ansi is passed'); + + $tester->run(array('--no-ansi' => true)); + $this->assertFalse($tester->getOutput()->isDecorated(), '->run() forces color output to be disabled if --no-ansi is passed'); + + $tester->run(array('--version' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run4.txt', $tester->getDisplay(true), '->run() displays the program version if --version is passed'); + + $tester->run(array('-V' => true), array('decorated' => false)); + $this->assertStringEqualsFile(self::$fixturesPath.'/application_run4.txt', $tester->getDisplay(true), '->run() displays the program version if -v is passed'); + + $tester->run(array('command' => 'list', '--quiet' => true)); + $this->assertSame('', $tester->getDisplay(), '->run() removes all output if --quiet is passed'); + + $tester->run(array('command' => 'list', '-q' => true)); + $this->assertSame('', $tester->getDisplay(), '->run() removes all output if -q is passed'); + + $tester->run(array('command' => 'list', '--verbose' => true)); + $this->assertSame(Output::VERBOSITY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if --verbose is passed'); + + $tester->run(array('command' => 'list', '--verbose' => 1)); + $this->assertSame(Output::VERBOSITY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if --verbose=1 is passed'); + + $tester->run(array('command' => 'list', '--verbose' => 2)); + $this->assertSame(Output::VERBOSITY_VERY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to very verbose if --verbose=2 is passed'); + + $tester->run(array('command' => 'list', '--verbose' => 3)); + $this->assertSame(Output::VERBOSITY_DEBUG, $tester->getOutput()->getVerbosity(), '->run() sets the output to debug if --verbose=3 is passed'); + + $tester->run(array('command' => 'list', '--verbose' => 4)); + $this->assertSame(Output::VERBOSITY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if unknown --verbose level is passed'); + + $tester->run(array('command' => 'list', '-v' => true)); + $this->assertSame(Output::VERBOSITY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if -v is passed'); + + $tester->run(array('command' => 'list', '-vv' => true)); + $this->assertSame(Output::VERBOSITY_VERY_VERBOSE, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if -v is passed'); + + $tester->run(array('command' => 'list', '-vvv' => true)); + $this->assertSame(Output::VERBOSITY_DEBUG, $tester->getOutput()->getVerbosity(), '->run() sets the output to verbose if -v is passed'); + + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + $application->add(new \FooCommand()); + $tester = new ApplicationTester($application); + + $tester->run(array('command' => 'foo:bar', '--no-interaction' => true), array('decorated' => false)); + $this->assertSame('called'.PHP_EOL, $tester->getDisplay(), '->run() does not call interact() if --no-interaction is passed'); + + $tester->run(array('command' => 'foo:bar', '-n' => true), array('decorated' => false)); + $this->assertSame('called'.PHP_EOL, $tester->getDisplay(), '->run() does not call interact() if -n is passed'); + } + + public function testRunReturnsIntegerExitCode() + { + $exception = new \Exception('', 4); + + $application = $this->getMock('Symfony\Component\Console\Application', array('doRun')); + $application->setAutoExit(false); + $application->expects($this->once()) + ->method('doRun') + ->will($this->throwException($exception)); + + $exitCode = $application->run(new ArrayInput(array()), new NullOutput()); + + $this->assertSame(4, $exitCode, '->run() returns integer exit code extracted from raised exception'); + } + + public function testRunReturnsExitCodeOneForExceptionCodeZero() + { + $exception = new \Exception('', 0); + + $application = $this->getMock('Symfony\Component\Console\Application', array('doRun')); + $application->setAutoExit(false); + $application->expects($this->once()) + ->method('doRun') + ->will($this->throwException($exception)); + + $exitCode = $application->run(new ArrayInput(array()), new NullOutput()); + + $this->assertSame(1, $exitCode, '->run() returns exit code 1 when exception code is 0'); + } + + /** + * @expectedException \LogicException + * @dataProvider getAddingAlreadySetDefinitionElementData + */ + public function testAddingAlreadySetDefinitionElementData($def) + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + $application + ->register('foo') + ->setDefinition(array($def)) + ->setCode(function (InputInterface $input, OutputInterface $output) {}) + ; + + $input = new ArrayInput(array('command' => 'foo')); + $output = new NullOutput(); + $application->run($input, $output); + } + + public function getAddingAlreadySetDefinitionElementData() + { + return array( + array(new InputArgument('command', InputArgument::REQUIRED)), + array(new InputOption('quiet', '', InputOption::VALUE_NONE)), + array(new InputOption('query', 'q', InputOption::VALUE_NONE)), + ); + } + + public function testGetDefaultHelperSetReturnsDefaultValues() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $helperSet = $application->getHelperSet(); + + $this->assertTrue($helperSet->has('formatter')); + $this->assertTrue($helperSet->has('dialog')); + $this->assertTrue($helperSet->has('progress')); + } + + public function testAddingSingleHelperSetOverwritesDefaultValues() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $application->setHelperSet(new HelperSet(array(new FormatterHelper()))); + + $helperSet = $application->getHelperSet(); + + $this->assertTrue($helperSet->has('formatter')); + + // no other default helper set should be returned + $this->assertFalse($helperSet->has('dialog')); + $this->assertFalse($helperSet->has('progress')); + } + + public function testOverwritingDefaultHelperSetOverwritesDefaultValues() + { + $application = new CustomApplication(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $application->setHelperSet(new HelperSet(array(new FormatterHelper()))); + + $helperSet = $application->getHelperSet(); + + $this->assertTrue($helperSet->has('formatter')); + + // no other default helper set should be returned + $this->assertFalse($helperSet->has('dialog')); + $this->assertFalse($helperSet->has('progress')); + } + + public function testGetDefaultInputDefinitionReturnsDefaultValues() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $inputDefinition = $application->getDefinition(); + + $this->assertTrue($inputDefinition->hasArgument('command')); + + $this->assertTrue($inputDefinition->hasOption('help')); + $this->assertTrue($inputDefinition->hasOption('quiet')); + $this->assertTrue($inputDefinition->hasOption('verbose')); + $this->assertTrue($inputDefinition->hasOption('version')); + $this->assertTrue($inputDefinition->hasOption('ansi')); + $this->assertTrue($inputDefinition->hasOption('no-ansi')); + $this->assertTrue($inputDefinition->hasOption('no-interaction')); + } + + public function testOverwritingDefaultInputDefinitionOverwritesDefaultValues() + { + $application = new CustomApplication(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $inputDefinition = $application->getDefinition(); + + // check whether the default arguments and options are not returned any more + $this->assertFalse($inputDefinition->hasArgument('command')); + + $this->assertFalse($inputDefinition->hasOption('help')); + $this->assertFalse($inputDefinition->hasOption('quiet')); + $this->assertFalse($inputDefinition->hasOption('verbose')); + $this->assertFalse($inputDefinition->hasOption('version')); + $this->assertFalse($inputDefinition->hasOption('ansi')); + $this->assertFalse($inputDefinition->hasOption('no-ansi')); + $this->assertFalse($inputDefinition->hasOption('no-interaction')); + + $this->assertTrue($inputDefinition->hasOption('custom')); + } + + public function testSettingCustomInputDefinitionOverwritesDefaultValues() + { + $application = new Application(); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $application->setDefinition(new InputDefinition(array(new InputOption('--custom', '-c', InputOption::VALUE_NONE, 'Set the custom input definition.')))); + + $inputDefinition = $application->getDefinition(); + + // check whether the default arguments and options are not returned any more + $this->assertFalse($inputDefinition->hasArgument('command')); + + $this->assertFalse($inputDefinition->hasOption('help')); + $this->assertFalse($inputDefinition->hasOption('quiet')); + $this->assertFalse($inputDefinition->hasOption('verbose')); + $this->assertFalse($inputDefinition->hasOption('version')); + $this->assertFalse($inputDefinition->hasOption('ansi')); + $this->assertFalse($inputDefinition->hasOption('no-ansi')); + $this->assertFalse($inputDefinition->hasOption('no-interaction')); + + $this->assertTrue($inputDefinition->hasOption('custom')); + } + + public function testRunWithDispatcher() + { + if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { + $this->markTestSkipped('The "EventDispatcher" component is not available'); + } + + $application = new Application(); + $application->setAutoExit(false); + $application->setDispatcher($this->getDispatcher()); + + $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('foo.'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo')); + $this->assertEquals('before.foo.after.', $tester->getDisplay()); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage caught + */ + public function testRunWithExceptionAndDispatcher() + { + if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { + $this->markTestSkipped('The "EventDispatcher" component is not available'); + } + + $application = new Application(); + $application->setDispatcher($this->getDispatcher()); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) { + throw new \RuntimeException('foo'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo')); + } + + public function testRunDispatchesAllEventsWithException() + { + if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { + $this->markTestSkipped('The "EventDispatcher" component is not available'); + } + + $application = new Application(); + $application->setDispatcher($this->getDispatcher()); + $application->setAutoExit(false); + + $application->register('foo')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('foo.'); + + throw new \RuntimeException('foo'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'foo')); + $this->assertContains('before.foo.after.caught.', $tester->getDisplay()); + } + + protected function getDispatcher() + { + $dispatcher = new EventDispatcher; + $dispatcher->addListener('console.command', function (ConsoleCommandEvent $event) { + $event->getOutput()->write('before.'); + }); + $dispatcher->addListener('console.terminate', function (ConsoleTerminateEvent $event) { + $event->getOutput()->write('after.'); + + $event->setExitCode(128); + }); + $dispatcher->addListener('console.exception', function (ConsoleExceptionEvent $event) { + $event->getOutput()->writeln('caught.'); + + $event->setException(new \LogicException('caught.', $event->getExitCode(), $event->getException())); + }); + + return $dispatcher; + } +} + +class CustomApplication extends Application +{ + /** + * Overwrites the default input definition. + * + * @return InputDefinition An InputDefinition instance + */ + protected function getDefaultInputDefinition() + { + return new InputDefinition(array(new InputOption('--custom', '-c', InputOption::VALUE_NONE, 'Set the custom input definition.'))); + } + + /** + * Gets the default helper set with the helpers that should always be available. + * + * @return HelperSet A HelperSet instance + */ + protected function getDefaultHelperSet() + { + return new HelperSet(array(new FormatterHelper())); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Command/CommandTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Command/CommandTest.php new file mode 100644 index 0000000..872bb7f --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Command/CommandTest.php @@ -0,0 +1,338 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\NullOutput; +use Symfony\Component\Console\Tester\CommandTester; + +class CommandTest extends \PHPUnit_Framework_TestCase +{ + protected static $fixturesPath; + + public static function setUpBeforeClass() + { + self::$fixturesPath = __DIR__.'/../Fixtures/'; + require_once self::$fixturesPath.'/TestCommand.php'; + } + + public function testConstructor() + { + $command = new Command('foo:bar'); + $this->assertEquals('foo:bar', $command->getName(), '__construct() takes the command name as its first argument'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The command name cannot be empty. + */ + public function testCommandNameCannotBeEmpty() + { + new Command(); + } + + public function testSetApplication() + { + $application = new Application(); + $command = new \TestCommand(); + $command->setApplication($application); + $this->assertEquals($application, $command->getApplication(), '->setApplication() sets the current application'); + } + + public function testSetGetDefinition() + { + $command = new \TestCommand(); + $ret = $command->setDefinition($definition = new InputDefinition()); + $this->assertEquals($command, $ret, '->setDefinition() implements a fluent interface'); + $this->assertEquals($definition, $command->getDefinition(), '->setDefinition() sets the current InputDefinition instance'); + $command->setDefinition(array(new InputArgument('foo'), new InputOption('bar'))); + $this->assertTrue($command->getDefinition()->hasArgument('foo'), '->setDefinition() also takes an array of InputArguments and InputOptions as an argument'); + $this->assertTrue($command->getDefinition()->hasOption('bar'), '->setDefinition() also takes an array of InputArguments and InputOptions as an argument'); + $command->setDefinition(new InputDefinition()); + } + + public function testAddArgument() + { + $command = new \TestCommand(); + $ret = $command->addArgument('foo'); + $this->assertEquals($command, $ret, '->addArgument() implements a fluent interface'); + $this->assertTrue($command->getDefinition()->hasArgument('foo'), '->addArgument() adds an argument to the command'); + } + + public function testAddOption() + { + $command = new \TestCommand(); + $ret = $command->addOption('foo'); + $this->assertEquals($command, $ret, '->addOption() implements a fluent interface'); + $this->assertTrue($command->getDefinition()->hasOption('foo'), '->addOption() adds an option to the command'); + } + + public function testGetNamespaceGetNameSetName() + { + $command = new \TestCommand(); + $this->assertEquals('namespace:name', $command->getName(), '->getName() returns the command name'); + $command->setName('foo'); + $this->assertEquals('foo', $command->getName(), '->setName() sets the command name'); + + $ret = $command->setName('foobar:bar'); + $this->assertEquals($command, $ret, '->setName() implements a fluent interface'); + $this->assertEquals('foobar:bar', $command->getName(), '->setName() sets the command name'); + } + + /** + * @dataProvider provideInvalidCommandNames + */ + public function testInvalidCommandNames($name) + { + $this->setExpectedException('InvalidArgumentException', sprintf('Command name "%s" is invalid.', $name)); + + $command = new \TestCommand(); + $command->setName($name); + } + + public function provideInvalidCommandNames() + { + return array( + array(''), + array('foo:') + ); + } + + public function testGetSetDescription() + { + $command = new \TestCommand(); + $this->assertEquals('description', $command->getDescription(), '->getDescription() returns the description'); + $ret = $command->setDescription('description1'); + $this->assertEquals($command, $ret, '->setDescription() implements a fluent interface'); + $this->assertEquals('description1', $command->getDescription(), '->setDescription() sets the description'); + } + + public function testGetSetHelp() + { + $command = new \TestCommand(); + $this->assertEquals('help', $command->getHelp(), '->getHelp() returns the help'); + $ret = $command->setHelp('help1'); + $this->assertEquals($command, $ret, '->setHelp() implements a fluent interface'); + $this->assertEquals('help1', $command->getHelp(), '->setHelp() sets the help'); + } + + public function testGetProcessedHelp() + { + $command = new \TestCommand(); + $command->setHelp('The %command.name% command does... Example: php %command.full_name%.'); + $this->assertContains('The namespace:name command does...', $command->getProcessedHelp(), '->getProcessedHelp() replaces %command.name% correctly'); + $this->assertNotContains('%command.full_name%', $command->getProcessedHelp(), '->getProcessedHelp() replaces %command.full_name%'); + } + + public function testGetSetAliases() + { + $command = new \TestCommand(); + $this->assertEquals(array('name'), $command->getAliases(), '->getAliases() returns the aliases'); + $ret = $command->setAliases(array('name1')); + $this->assertEquals($command, $ret, '->setAliases() implements a fluent interface'); + $this->assertEquals(array('name1'), $command->getAliases(), '->setAliases() sets the aliases'); + } + + public function testGetSynopsis() + { + $command = new \TestCommand(); + $command->addOption('foo'); + $command->addArgument('foo'); + $this->assertEquals('namespace:name [--foo] [foo]', $command->getSynopsis(), '->getSynopsis() returns the synopsis'); + } + + public function testGetHelper() + { + $application = new Application(); + $command = new \TestCommand(); + $command->setApplication($application); + $formatterHelper = new FormatterHelper(); + $this->assertEquals($formatterHelper->getName(), $command->getHelper('formatter')->getName(), '->getHelper() returns the correct helper'); + } + + public function testGet() + { + $application = new Application(); + $command = new \TestCommand(); + $command->setApplication($application); + $formatterHelper = new FormatterHelper(); + $this->assertEquals($formatterHelper->getName(), $command->getHelper('formatter')->getName(), '->__get() returns the correct helper'); + } + + public function testMergeApplicationDefinition() + { + $application1 = new Application(); + $application1->getDefinition()->addArguments(array(new InputArgument('foo'))); + $application1->getDefinition()->addOptions(array(new InputOption('bar'))); + $command = new \TestCommand(); + $command->setApplication($application1); + $command->setDefinition($definition = new InputDefinition(array(new InputArgument('bar'), new InputOption('foo')))); + + $r = new \ReflectionObject($command); + $m = $r->getMethod('mergeApplicationDefinition'); + $m->setAccessible(true); + $m->invoke($command); + $this->assertTrue($command->getDefinition()->hasArgument('foo'), '->mergeApplicationDefinition() merges the application arguments and the command arguments'); + $this->assertTrue($command->getDefinition()->hasArgument('bar'), '->mergeApplicationDefinition() merges the application arguments and the command arguments'); + $this->assertTrue($command->getDefinition()->hasOption('foo'), '->mergeApplicationDefinition() merges the application options and the command options'); + $this->assertTrue($command->getDefinition()->hasOption('bar'), '->mergeApplicationDefinition() merges the application options and the command options'); + + $m->invoke($command); + $this->assertEquals(3, $command->getDefinition()->getArgumentCount(), '->mergeApplicationDefinition() does not try to merge twice the application arguments and options'); + } + + public function testMergeApplicationDefinitionWithoutArgsThenWithArgsAddsArgs() + { + $application1 = new Application(); + $application1->getDefinition()->addArguments(array(new InputArgument('foo'))); + $application1->getDefinition()->addOptions(array(new InputOption('bar'))); + $command = new \TestCommand(); + $command->setApplication($application1); + $command->setDefinition($definition = new InputDefinition(array())); + + $r = new \ReflectionObject($command); + $m = $r->getMethod('mergeApplicationDefinition'); + $m->setAccessible(true); + $m->invoke($command, false); + $this->assertTrue($command->getDefinition()->hasOption('bar'), '->mergeApplicationDefinition(false) merges the application and the commmand options'); + $this->assertFalse($command->getDefinition()->hasArgument('foo'), '->mergeApplicationDefinition(false) does not merge the application arguments'); + + $m->invoke($command, true); + $this->assertTrue($command->getDefinition()->hasArgument('foo'), '->mergeApplicationDefinition(true) merges the application arguments and the command arguments'); + + $m->invoke($command); + $this->assertEquals(2, $command->getDefinition()->getArgumentCount(), '->mergeApplicationDefinition() does not try to merge twice the application arguments'); + } + + public function testRunInteractive() + { + $tester = new CommandTester(new \TestCommand()); + + $tester->execute(array(), array('interactive' => true)); + + $this->assertEquals('interact called'.PHP_EOL.'execute called'.PHP_EOL, $tester->getDisplay(), '->run() calls the interact() method if the input is interactive'); + } + + public function testRunNonInteractive() + { + $tester = new CommandTester(new \TestCommand()); + + $tester->execute(array(), array('interactive' => false)); + + $this->assertEquals('execute called'.PHP_EOL, $tester->getDisplay(), '->run() does not call the interact() method if the input is not interactive'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage You must override the execute() method in the concrete command class. + */ + public function testExecuteMethodNeedsToBeOverriden() + { + $command = new Command('foo'); + $command->run(new StringInput(''), new NullOutput()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "--bar" option does not exist. + */ + public function testRunWithInvalidOption() + { + $command = new \TestCommand(); + $tester = new CommandTester($command); + $tester->execute(array('--bar' => true)); + } + + public function testRunReturnsIntegerExitCode() + { + $command = new \TestCommand(); + $exitCode = $command->run(new StringInput(''), new NullOutput()); + $this->assertSame(0, $exitCode, '->run() returns integer exit code (treats null as 0)'); + + $command = $this->getMock('TestCommand', array('execute')); + $command->expects($this->once()) + ->method('execute') + ->will($this->returnValue('2.3')); + $exitCode = $command->run(new StringInput(''), new NullOutput()); + $this->assertSame(2, $exitCode, '->run() returns integer exit code (casts numeric to int)'); + } + + public function testRunReturnsAlwaysInteger() + { + $command = new \TestCommand(); + + $this->assertSame(0, $command->run(new StringInput(''), new NullOutput())); + } + + public function testSetCode() + { + $command = new \TestCommand(); + $ret = $command->setCode(function (InputInterface $input, OutputInterface $output) { + $output->writeln('from the code...'); + }); + $this->assertEquals($command, $ret, '->setCode() implements a fluent interface'); + $tester = new CommandTester($command); + $tester->execute(array()); + $this->assertEquals('interact called'.PHP_EOL.'from the code...'.PHP_EOL, $tester->getDisplay()); + } + + public function testSetCodeWithNonClosureCallable() + { + $command = new \TestCommand(); + $ret = $command->setCode(array($this, 'callableMethodCommand')); + $this->assertEquals($command, $ret, '->setCode() implements a fluent interface'); + $tester = new CommandTester($command); + $tester->execute(array()); + $this->assertEquals('interact called'.PHP_EOL.'from the code...'.PHP_EOL, $tester->getDisplay()); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Invalid callable provided to Command::setCode. + */ + public function testSetCodeWithNonCallable() + { + $command = new \TestCommand(); + $command->setCode(array($this, 'nonExistentMethod')); + } + + public function callableMethodCommand(InputInterface $input, OutputInterface $output) + { + $output->writeln('from the code...'); + } + + public function testAsText() + { + $command = new \TestCommand(); + $command->setApplication(new Application()); + $tester = new CommandTester($command); + $tester->execute(array('command' => $command->getName())); + $this->assertStringEqualsFile(self::$fixturesPath.'/command_astext.txt', $command->asText(), '->asText() returns a text representation of the command'); + } + + public function testAsXml() + { + $command = new \TestCommand(); + $command->setApplication(new Application()); + $tester = new CommandTester($command); + $tester->execute(array('command' => $command->getName())); + $this->assertXmlStringEqualsXmlFile(self::$fixturesPath.'/command_asxml.txt', $command->asXml(), '->asXml() returns an XML representation of the command'); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Command/HelpCommandTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Command/HelpCommandTest.php new file mode 100644 index 0000000..ea69c8b --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Command/HelpCommandTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Command; + +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Console\Command\HelpCommand; +use Symfony\Component\Console\Command\ListCommand; +use Symfony\Component\Console\Application; + +class HelpCommandTest extends \PHPUnit_Framework_TestCase +{ + public function testExecuteForCommandAlias() + { + $command = new HelpCommand(); + $command->setApplication(new Application()); + $commandTester = new CommandTester($command); + $commandTester->execute(array('command_name' => 'li')); + $this->assertRegExp('/list \[--xml\] \[--raw\] \[--format="\.\.\."\] \[namespace\]/', $commandTester->getDisplay(), '->execute() returns a text help for the given command alias'); + } + + public function testExecuteForCommand() + { + $command = new HelpCommand(); + $commandTester = new CommandTester($command); + $command->setCommand(new ListCommand()); + $commandTester->execute(array()); + $this->assertRegExp('/list \[--xml\] \[--raw\] \[--format="\.\.\."\] \[namespace\]/', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + } + + public function testExecuteForCommandWithXmlOption() + { + $command = new HelpCommand(); + $commandTester = new CommandTester($command); + $command->setCommand(new ListCommand()); + $commandTester->execute(array('--format' => 'xml')); + $this->assertRegExp('/getDisplay(), '->execute() returns an XML help text if --xml is passed'); + } + + public function testExecuteForApplicationCommand() + { + $application = new Application(); + $commandTester = new CommandTester($application->get('help')); + $commandTester->execute(array('command_name' => 'list')); + $this->assertRegExp('/list \[--xml\] \[--raw\] \[--format="\.\.\."\] \[namespace\]/', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + } + + public function testExecuteForApplicationCommandWithXmlOption() + { + $application = new Application(); + $commandTester = new CommandTester($application->get('help')); + $commandTester->execute(array('command_name' => 'list', '--format' => 'xml')); + $this->assertRegExp('/list \[--xml\] \[--raw\] \[--format="\.\.\."\] \[namespace\]/', $commandTester->getDisplay(), '->execute() returns a text help for the given command'); + $this->assertRegExp('/getDisplay(), '->execute() returns an XML help text if --format=xml is passed'); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Command/ListCommandTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Command/ListCommandTest.php new file mode 100644 index 0000000..1df06f5 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Command/ListCommandTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Command; + +use Symfony\Component\Console\Tester\CommandTester; +use Symfony\Component\Console\Application; + +class ListCommandTest extends \PHPUnit_Framework_TestCase +{ + public function testExecuteListsCommands() + { + $application = new Application(); + $commandTester = new CommandTester($command = $application->get('list')); + $commandTester->execute(array('command' => $command->getName()), array('decorated' => false)); + + $this->assertRegExp('/help Displays help for a command/', $commandTester->getDisplay(), '->execute() returns a list of available commands'); + } + + public function testExecuteListsCommandsWithXmlOption() + { + $application = new Application(); + $commandTester = new CommandTester($command = $application->get('list')); + $commandTester->execute(array('command' => $command->getName(), '--format' => 'xml')); + $this->assertRegExp('//', $commandTester->getDisplay(), '->execute() returns a list of available commands in XML if --xml is passed'); + } + + public function testExecuteListsCommandsWithRawOption() + { + $application = new Application(); + $commandTester = new CommandTester($command = $application->get('list')); + $commandTester->execute(array('command' => $command->getName(), '--raw' => true)); + $output = <<assertEquals($output, $commandTester->getDisplay(true)); + } + + public function testExecuteListsCommandsWithNamespaceArgument() + { + + require_once(realpath(__DIR__.'/../Fixtures/FooCommand.php')); + $application = new Application(); + $application->add(new \FooCommand()); + $commandTester = new CommandTester($command = $application->get('list')); + $commandTester->execute(array('command' => $command->getName(), 'namespace' => 'foo', '--raw' => true)); + $output = <<assertEquals($output, $commandTester->getDisplay(true)); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/AbstractDescriptorTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/AbstractDescriptorTest.php new file mode 100644 index 0000000..aab1cf3 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/AbstractDescriptorTest.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +abstract class AbstractDescriptorTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getDescribeInputArgumentTestData */ + public function testDescribeInputArgument(InputArgument $argument, $expectedDescription) + { + $this->assertEquals(trim($expectedDescription), trim($this->getDescriptor()->describe($argument))); + } + + /** @dataProvider getDescribeInputOptionTestData */ + public function testDescribeInputOption(InputOption $option, $expectedDescription) + { + $this->assertEquals(trim($expectedDescription), trim($this->getDescriptor()->describe($option))); + } + + /** @dataProvider getDescribeInputDefinitionTestData */ + public function testDescribeInputDefinition(InputDefinition $definition, $expectedDescription) + { + $this->assertEquals(trim($expectedDescription), trim($this->getDescriptor()->describe($definition))); + } + + /** @dataProvider getDescribeCommandTestData */ + public function testDescribeCommand(Command $command, $expectedDescription) + { + $this->assertEquals(trim($expectedDescription), trim($this->getDescriptor()->describe($command))); + } + + /** @dataProvider getDescribeApplicationTestData */ + public function testDescribeApplication(Application $application, $expectedDescription) + { + // Replaces the dynamic placeholders of the command help text with a static version. + // The placeholder %command.full_name% includes the script path that is not predictable + // and can not be tested against. + foreach ($application->all() as $command) { + $command->setHelp(str_replace('%command.full_name%', 'app/console %command.name%', $command->getHelp())); + } + + $this->assertEquals(trim($expectedDescription), trim(str_replace(PHP_EOL, "\n", $this->getDescriptor()->describe($application)))); + } + + public function getDescribeInputArgumentTestData() + { + return $this->getDescriptionTestData(ObjectsProvider::getInputArguments()); + } + + public function getDescribeInputOptionTestData() + { + return $this->getDescriptionTestData(ObjectsProvider::getInputOptions()); + } + + public function getDescribeInputDefinitionTestData() + { + return $this->getDescriptionTestData(ObjectsProvider::getInputDefinitions()); + } + + public function getDescribeCommandTestData() + { + return $this->getDescriptionTestData(ObjectsProvider::getCommands()); + } + + public function getDescribeApplicationTestData() + { + return $this->getDescriptionTestData(ObjectsProvider::getApplications()); + } + + abstract protected function getDescriptor(); + abstract protected function getFormat(); + + private function getDescriptionTestData(array $objects) + { + $data = array(); + foreach ($objects as $name => $object) { + $description = file_get_contents(sprintf('%s/../Fixtures/%s.%s', __DIR__, $name, $this->getFormat())); + $data[] = array($object, $description); + } + + return $data; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/JsonDescriptorTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/JsonDescriptorTest.php new file mode 100644 index 0000000..943ea29 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/JsonDescriptorTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Descriptor; + +use Symfony\Component\Console\Descriptor\JsonDescriptor; + +class JsonDescriptorTest extends AbstractDescriptorTest +{ + protected function getDescriptor() + { + return new JsonDescriptor(); + } + + protected function getFormat() + { + return 'json'; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/MarkdownDescriptorTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/MarkdownDescriptorTest.php new file mode 100644 index 0000000..c85e8a5 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/MarkdownDescriptorTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Descriptor; + +use Symfony\Component\Console\Descriptor\MarkdownDescriptor; + +class MarkdownDescriptorTest extends AbstractDescriptorTest +{ + protected function getDescriptor() + { + return new MarkdownDescriptor(); + } + + protected function getFormat() + { + return 'md'; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/ObjectsProvider.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/ObjectsProvider.php new file mode 100644 index 0000000..a3c49d7 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/ObjectsProvider.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Descriptor; + +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Tests\Fixtures\DescriptorApplication1; +use Symfony\Component\Console\Tests\Fixtures\DescriptorApplication2; +use Symfony\Component\Console\Tests\Fixtures\DescriptorCommand1; +use Symfony\Component\Console\Tests\Fixtures\DescriptorCommand2; + +/** + * @author Jean-François Simon + */ +class ObjectsProvider +{ + public static function getInputArguments() + { + return array( + 'input_argument_1' => new InputArgument('argument_name', InputArgument::REQUIRED), + 'input_argument_2' => new InputArgument('argument_name', InputArgument::IS_ARRAY, 'argument description'), + 'input_argument_3' => new InputArgument('argument_name', InputArgument::OPTIONAL, 'argument description', 'default_value'), + ); + } + + public static function getInputOptions() + { + return array( + 'input_option_1' => new InputOption('option_name', 'o', InputOption::VALUE_NONE), + 'input_option_2' => new InputOption('option_name', 'o', InputOption::VALUE_OPTIONAL, 'option description', 'default_value'), + 'input_option_3' => new InputOption('option_name', 'o', InputOption::VALUE_REQUIRED, 'option description'), + 'input_option_4' => new InputOption('option_name', 'o', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'option description', array()), + ); + } + + public static function getInputDefinitions() + { + return array( + 'input_definition_1' => new InputDefinition(), + 'input_definition_2' => new InputDefinition(array(new InputArgument('argument_name', InputArgument::REQUIRED))), + 'input_definition_3' => new InputDefinition(array(new InputOption('option_name', 'o', InputOption::VALUE_NONE))), + 'input_definition_4' => new InputDefinition(array( + new InputArgument('argument_name', InputArgument::REQUIRED), + new InputOption('option_name', 'o', InputOption::VALUE_NONE), + )), + ); + } + + public static function getCommands() + { + return array( + 'command_1' => new DescriptorCommand1(), + 'command_2' => new DescriptorCommand2(), + ); + } + + public static function getApplications() + { + return array( + 'application_1' => new DescriptorApplication1(), + 'application_2' => new DescriptorApplication2(), + ); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/TextDescriptorTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/TextDescriptorTest.php new file mode 100644 index 0000000..350b679 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/TextDescriptorTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Descriptor; + +use Symfony\Component\Console\Descriptor\TextDescriptor; + +class TextDescriptorTest extends AbstractDescriptorTest +{ + protected function getDescriptor() + { + return new TextDescriptor(); + } + + protected function getFormat() + { + return 'txt'; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/XmlDescriptorTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/XmlDescriptorTest.php new file mode 100644 index 0000000..59a5d1e --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Descriptor/XmlDescriptorTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Descriptor; + +use Symfony\Component\Console\Descriptor\XmlDescriptor; + +class XmlDescriptorTest extends AbstractDescriptorTest +{ + protected function getDescriptor() + { + return new XmlDescriptor(); + } + + protected function getFormat() + { + return 'xml'; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorApplication1.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorApplication1.php new file mode 100644 index 0000000..132b6d5 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorApplication1.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Fixtures; + +use Symfony\Component\Console\Application; + +class DescriptorApplication1 extends Application +{ +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorApplication2.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorApplication2.php new file mode 100644 index 0000000..ff55135 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorApplication2.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Fixtures; + +use Symfony\Component\Console\Application; + +class DescriptorApplication2 extends Application +{ + public function __construct() + { + parent::__construct('My Symfony application', 'v1.0'); + $this->add(new DescriptorCommand1()); + $this->add(new DescriptorCommand2()); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorCommand1.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorCommand1.php new file mode 100644 index 0000000..ede05d7 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorCommand1.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Fixtures; + +use Symfony\Component\Console\Command\Command; + +class DescriptorCommand1 extends Command +{ + protected function configure() + { + $this + ->setName('descriptor:command1') + ->setAliases(array('alias1', 'alias2')) + ->setDescription('command 1 description') + ->setHelp('command 1 help') + ; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorCommand2.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorCommand2.php new file mode 100644 index 0000000..bc04ca9 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/DescriptorCommand2.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Fixtures; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; + +class DescriptorCommand2 extends Command +{ + protected function configure() + { + $this + ->setName('descriptor:command2') + ->setDescription('command 2 description') + ->setHelp('command 2 help') + ->addArgument('argument_name', InputArgument::REQUIRED) + ->addOption('option_name', 'o', InputOption::VALUE_NONE) + ; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/Foo1Command.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/Foo1Command.php new file mode 100644 index 0000000..254162f --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/Foo1Command.php @@ -0,0 +1,26 @@ +setName('foo:bar1') + ->setDescription('The foo:bar1 command') + ->setAliases(array('afoobar1')) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->output = $output; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/Foo2Command.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/Foo2Command.php new file mode 100644 index 0000000..8071dc8 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/Foo2Command.php @@ -0,0 +1,21 @@ +setName('foo1:bar') + ->setDescription('The foo1:bar command') + ->setAliases(array('afoobar2')) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/Foo3Command.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/Foo3Command.php new file mode 100644 index 0000000..7349bc3 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/Foo3Command.php @@ -0,0 +1,25 @@ +setName('foo3:bar') + ->setDescription('The foo3:bar command') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + try { + throw new \Exception("First exception"); + } catch (\Exception $e) { + throw new \Exception("Second exception", 0, $e); + } + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/Foo4Command.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/Foo4Command.php new file mode 100644 index 0000000..1c54639 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/Foo4Command.php @@ -0,0 +1,11 @@ +setName('foo3:bar:toh'); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/FooCommand.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/FooCommand.php new file mode 100644 index 0000000..355e0ad --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/FooCommand.php @@ -0,0 +1,33 @@ +setName('foo:bar') + ->setDescription('The foo:bar command') + ->setAliases(array('afoobar')) + ; + } + + protected function interact(InputInterface $input, OutputInterface $output) + { + $output->writeln('interact called'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->output = $output; + + $output->writeln('called'); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/TestCommand.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/TestCommand.php new file mode 100644 index 0000000..dcd3273 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/TestCommand.php @@ -0,0 +1,28 @@ +setName('namespace:name') + ->setAliases(array('name')) + ->setDescription('description') + ->setHelp('help') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $output->writeln('execute called'); + } + + protected function interact(InputInterface $input, OutputInterface $output) + { + $output->writeln('interact called'); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_1.json b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_1.json new file mode 100644 index 0000000..84b587e --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_1.json @@ -0,0 +1 @@ +{"commands":[{"name":"help","usage":"help [--xml] [--format=\"...\"] [--raw] [command_name]","description":"Displays help for a command","help":"The help<\/info> command displays help for a given command:\n\n php app\/console help list<\/info>\n\nYou can also output the help in other formats by using the --format<\/comment> option:\n\n php app\/console help --format=xml list<\/info>\n\nTo display the list of available commands, please use the list<\/info> command.","aliases":[],"definition":{"arguments":{"command_name":{"name":"command_name","is_required":false,"is_array":false,"description":"The command name","default":"help"}},"options":{"xml":{"name":"--xml","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output help as XML","default":false},"format":{"name":"--format","shortcut":"","accept_value":true,"is_value_required":true,"is_multiple":false,"description":"To output help in other formats","default":null},"raw":{"name":"--raw","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output raw command help","default":false},"help":{"name":"--help","shortcut":"-h","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this help message.","default":false},"quiet":{"name":"--quiet","shortcut":"-q","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not output any message.","default":false},"verbose":{"name":"--verbose","shortcut":"-v|-vv|-vvv","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug","default":false},"version":{"name":"--version","shortcut":"-V","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this application version.","default":false},"ansi":{"name":"--ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Force ANSI output.","default":false},"no-ansi":{"name":"--no-ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Disable ANSI output.","default":false},"no-interaction":{"name":"--no-interaction","shortcut":"-n","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not ask any interactive question.","default":false}}}},{"name":"list","usage":"list [--xml] [--raw] [--format=\"...\"] [namespace]","description":"Lists commands","help":"The list<\/info> command lists all commands:\n\n php app\/console list<\/info>\n\nYou can also display the commands for a specific namespace:\n\n php app\/console list test<\/info>\n\nYou can also output the information in other formats by using the --format<\/comment> option:\n\n php app\/console list --format=xml<\/info>\n\nIt's also possible to get raw list of commands (useful for embedding command runner):\n\n php app\/console list --raw<\/info>","aliases":[],"definition":{"arguments":{"namespace":{"name":"namespace","is_required":false,"is_array":false,"description":"The namespace name","default":null}},"options":{"xml":{"name":"--xml","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output list as XML","default":false},"raw":{"name":"--raw","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output raw command list","default":false},"format":{"name":"--format","shortcut":"","accept_value":true,"is_value_required":true,"is_multiple":false,"description":"To output list in other formats","default":null}}}}],"namespaces":[{"id":"_global","commands":["help","list"]}]} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_1.md b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_1.md new file mode 100644 index 0000000..ae815a8 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_1.md @@ -0,0 +1,199 @@ +UNKNOWN +======= + +* help +* list + +help +---- + +* Description: Displays help for a command +* Usage: `help [--xml] [--format="..."] [--raw] [command_name]` +* Aliases: + +The help command displays help for a given command: + + php app/console help list + +You can also output the help in other formats by using the --format option: + + php app/console help --format=xml list + +To display the list of available commands, please use the list command. + +### Arguments: + +**command_name:** + +* Name: command_name +* Is required: no +* Is array: no +* Description: The command name +* Default: `'help'` + +### Options: + +**xml:** + +* Name: `--xml` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output help as XML +* Default: `false` + +**format:** + +* Name: `--format` +* Shortcut: +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Description: To output help in other formats +* Default: `NULL` + +**raw:** + +* Name: `--raw` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output raw command help +* Default: `false` + +**help:** + +* Name: `--help` +* Shortcut: `-h` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this help message. +* Default: `false` + +**quiet:** + +* Name: `--quiet` +* Shortcut: `-q` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not output any message. +* Default: `false` + +**verbose:** + +* Name: `--verbose` +* Shortcut: `-v|-vv|-vvv` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug +* Default: `false` + +**version:** + +* Name: `--version` +* Shortcut: `-V` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this application version. +* Default: `false` + +**ansi:** + +* Name: `--ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Force ANSI output. +* Default: `false` + +**no-ansi:** + +* Name: `--no-ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Disable ANSI output. +* Default: `false` + +**no-interaction:** + +* Name: `--no-interaction` +* Shortcut: `-n` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not ask any interactive question. +* Default: `false` + +list +---- + +* Description: Lists commands +* Usage: `list [--xml] [--raw] [--format="..."] [namespace]` +* Aliases: + +The list command lists all commands: + + php app/console list + +You can also display the commands for a specific namespace: + + php app/console list test + +You can also output the information in other formats by using the --format option: + + php app/console list --format=xml + +It's also possible to get raw list of commands (useful for embedding command runner): + + php app/console list --raw + +### Arguments: + +**namespace:** + +* Name: namespace +* Is required: no +* Is array: no +* Description: The namespace name +* Default: `NULL` + +### Options: + +**xml:** + +* Name: `--xml` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output list as XML +* Default: `false` + +**raw:** + +* Name: `--raw` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output raw command list +* Default: `false` + +**format:** + +* Name: `--format` +* Shortcut: +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Description: To output list in other formats +* Default: `NULL` diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_1.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_1.txt new file mode 100644 index 0000000..24ab4a0 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_1.txt @@ -0,0 +1,17 @@ +Console Tool + +Usage: + [options] command [arguments] + +Options: + --help -h Display this help message. + --quiet -q Do not output any message. + --verbose -v|vv|vvv Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + --version -V Display this application version. + --ansi Force ANSI output. + --no-ansi Disable ANSI output. + --no-interaction -n Do not ask any interactive question. + +Available commands: + help Displays help for a command + list Lists commands diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_1.xml b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_1.xml new file mode 100644 index 0000000..d5c57be --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_1.xml @@ -0,0 +1,104 @@ + + + + + help [--xml] [--format="..."] [--raw] [command_name] + Displays help for a command + The <info>help</info> command displays help for a given command: + + <info>php app/console help list</info> + + You can also output the help in other formats by using the <comment>--format</comment> option: + + <info>php app/console help --format=xml list</info> + + To display the list of available commands, please use the <info>list</info> command. + + + + The command name + + help + + + + + + + + + + + + + + + + + + list [--xml] [--raw] [--format="..."] [namespace] + Lists commands + The <info>list</info> command lists all commands: + + <info>php app/console list</info> + + You can also display the commands for a specific namespace: + + <info>php app/console list test</info> + + You can also output the information in other formats by using the <comment>--format</comment> option: + + <info>php app/console list --format=xml</info> + + It's also possible to get raw list of commands (useful for embedding command runner): + + <info>php app/console list --raw</info> + + + + The namespace name + + + + + + + + + + + + + help + list + + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_2.json b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_2.json new file mode 100644 index 0000000..e0c4b9a --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_2.json @@ -0,0 +1 @@ +{"commands":[{"name":"help","usage":"help [--xml] [--format=\"...\"] [--raw] [command_name]","description":"Displays help for a command","help":"The help<\/info> command displays help for a given command:\n\n php app\/console help list<\/info>\n\nYou can also output the help in other formats by using the --format<\/comment> option:\n\n php app\/console help --format=xml list<\/info>\n\nTo display the list of available commands, please use the list<\/info> command.","aliases":[],"definition":{"arguments":{"command_name":{"name":"command_name","is_required":false,"is_array":false,"description":"The command name","default":"help"}},"options":{"xml":{"name":"--xml","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output help as XML","default":false},"format":{"name":"--format","shortcut":"","accept_value":true,"is_value_required":true,"is_multiple":false,"description":"To output help in other formats","default":null},"raw":{"name":"--raw","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output raw command help","default":false},"help":{"name":"--help","shortcut":"-h","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this help message.","default":false},"quiet":{"name":"--quiet","shortcut":"-q","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not output any message.","default":false},"verbose":{"name":"--verbose","shortcut":"-v|-vv|-vvv","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug","default":false},"version":{"name":"--version","shortcut":"-V","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this application version.","default":false},"ansi":{"name":"--ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Force ANSI output.","default":false},"no-ansi":{"name":"--no-ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Disable ANSI output.","default":false},"no-interaction":{"name":"--no-interaction","shortcut":"-n","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not ask any interactive question.","default":false}}}},{"name":"list","usage":"list [--xml] [--raw] [--format=\"...\"] [namespace]","description":"Lists commands","help":"The list<\/info> command lists all commands:\n\n php app\/console list<\/info>\n\nYou can also display the commands for a specific namespace:\n\n php app\/console list test<\/info>\n\nYou can also output the information in other formats by using the --format<\/comment> option:\n\n php app\/console list --format=xml<\/info>\n\nIt's also possible to get raw list of commands (useful for embedding command runner):\n\n php app\/console list --raw<\/info>","aliases":[],"definition":{"arguments":{"namespace":{"name":"namespace","is_required":false,"is_array":false,"description":"The namespace name","default":null}},"options":{"xml":{"name":"--xml","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output list as XML","default":false},"raw":{"name":"--raw","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"To output raw command list","default":false},"format":{"name":"--format","shortcut":"","accept_value":true,"is_value_required":true,"is_multiple":false,"description":"To output list in other formats","default":null}}}},{"name":"descriptor:command1","usage":"descriptor:command1","description":"command 1 description","help":"command 1 help","aliases":["alias1","alias2"],"definition":{"arguments":[],"options":{"help":{"name":"--help","shortcut":"-h","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this help message.","default":false},"quiet":{"name":"--quiet","shortcut":"-q","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not output any message.","default":false},"verbose":{"name":"--verbose","shortcut":"-v|-vv|-vvv","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug","default":false},"version":{"name":"--version","shortcut":"-V","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this application version.","default":false},"ansi":{"name":"--ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Force ANSI output.","default":false},"no-ansi":{"name":"--no-ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Disable ANSI output.","default":false},"no-interaction":{"name":"--no-interaction","shortcut":"-n","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not ask any interactive question.","default":false}}}},{"name":"descriptor:command2","usage":"descriptor:command2 [-o|--option_name] argument_name","description":"command 2 description","help":"command 2 help","aliases":[],"definition":{"arguments":{"argument_name":{"name":"argument_name","is_required":true,"is_array":false,"description":"","default":null}},"options":{"option_name":{"name":"--option_name","shortcut":"-o","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"","default":false},"help":{"name":"--help","shortcut":"-h","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this help message.","default":false},"quiet":{"name":"--quiet","shortcut":"-q","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not output any message.","default":false},"verbose":{"name":"--verbose","shortcut":"-v|-vv|-vvv","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug","default":false},"version":{"name":"--version","shortcut":"-V","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Display this application version.","default":false},"ansi":{"name":"--ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Force ANSI output.","default":false},"no-ansi":{"name":"--no-ansi","shortcut":"","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Disable ANSI output.","default":false},"no-interaction":{"name":"--no-interaction","shortcut":"-n","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"Do not ask any interactive question.","default":false}}}}],"namespaces":[{"id":"_global","commands":["alias1","alias2","help","list"]},{"id":"descriptor","commands":["descriptor:command1","descriptor:command2"]}]} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_2.md b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_2.md new file mode 100644 index 0000000..bb242b7 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_2.md @@ -0,0 +1,388 @@ +My Symfony application +====================== + +* alias1 +* alias2 +* help +* list + +**descriptor:** + +* descriptor:command1 +* descriptor:command2 + +help +---- + +* Description: Displays help for a command +* Usage: `help [--xml] [--format="..."] [--raw] [command_name]` +* Aliases: + +The help command displays help for a given command: + + php app/console help list + +You can also output the help in other formats by using the --format option: + + php app/console help --format=xml list + +To display the list of available commands, please use the list command. + +### Arguments: + +**command_name:** + +* Name: command_name +* Is required: no +* Is array: no +* Description: The command name +* Default: `'help'` + +### Options: + +**xml:** + +* Name: `--xml` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output help as XML +* Default: `false` + +**format:** + +* Name: `--format` +* Shortcut: +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Description: To output help in other formats +* Default: `NULL` + +**raw:** + +* Name: `--raw` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output raw command help +* Default: `false` + +**help:** + +* Name: `--help` +* Shortcut: `-h` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this help message. +* Default: `false` + +**quiet:** + +* Name: `--quiet` +* Shortcut: `-q` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not output any message. +* Default: `false` + +**verbose:** + +* Name: `--verbose` +* Shortcut: `-v|-vv|-vvv` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug +* Default: `false` + +**version:** + +* Name: `--version` +* Shortcut: `-V` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this application version. +* Default: `false` + +**ansi:** + +* Name: `--ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Force ANSI output. +* Default: `false` + +**no-ansi:** + +* Name: `--no-ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Disable ANSI output. +* Default: `false` + +**no-interaction:** + +* Name: `--no-interaction` +* Shortcut: `-n` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not ask any interactive question. +* Default: `false` + +list +---- + +* Description: Lists commands +* Usage: `list [--xml] [--raw] [--format="..."] [namespace]` +* Aliases: + +The list command lists all commands: + + php app/console list + +You can also display the commands for a specific namespace: + + php app/console list test + +You can also output the information in other formats by using the --format option: + + php app/console list --format=xml + +It's also possible to get raw list of commands (useful for embedding command runner): + + php app/console list --raw + +### Arguments: + +**namespace:** + +* Name: namespace +* Is required: no +* Is array: no +* Description: The namespace name +* Default: `NULL` + +### Options: + +**xml:** + +* Name: `--xml` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output list as XML +* Default: `false` + +**raw:** + +* Name: `--raw` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: To output raw command list +* Default: `false` + +**format:** + +* Name: `--format` +* Shortcut: +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Description: To output list in other formats +* Default: `NULL` + +descriptor:command1 +------------------- + +* Description: command 1 description +* Usage: `descriptor:command1` +* Aliases: `alias1`, `alias2` + +command 1 help + +### Options: + +**help:** + +* Name: `--help` +* Shortcut: `-h` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this help message. +* Default: `false` + +**quiet:** + +* Name: `--quiet` +* Shortcut: `-q` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not output any message. +* Default: `false` + +**verbose:** + +* Name: `--verbose` +* Shortcut: `-v|-vv|-vvv` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug +* Default: `false` + +**version:** + +* Name: `--version` +* Shortcut: `-V` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this application version. +* Default: `false` + +**ansi:** + +* Name: `--ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Force ANSI output. +* Default: `false` + +**no-ansi:** + +* Name: `--no-ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Disable ANSI output. +* Default: `false` + +**no-interaction:** + +* Name: `--no-interaction` +* Shortcut: `-n` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not ask any interactive question. +* Default: `false` + +descriptor:command2 +------------------- + +* Description: command 2 description +* Usage: `descriptor:command2 [-o|--option_name] argument_name` +* Aliases: + +command 2 help + +### Arguments: + +**argument_name:** + +* Name: argument_name +* Is required: yes +* Is array: no +* Description: +* Default: `NULL` + +### Options: + +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: +* Default: `false` + +**help:** + +* Name: `--help` +* Shortcut: `-h` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this help message. +* Default: `false` + +**quiet:** + +* Name: `--quiet` +* Shortcut: `-q` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not output any message. +* Default: `false` + +**verbose:** + +* Name: `--verbose` +* Shortcut: `-v|-vv|-vvv` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug +* Default: `false` + +**version:** + +* Name: `--version` +* Shortcut: `-V` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Display this application version. +* Default: `false` + +**ansi:** + +* Name: `--ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Force ANSI output. +* Default: `false` + +**no-ansi:** + +* Name: `--no-ansi` +* Shortcut: +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Disable ANSI output. +* Default: `false` + +**no-interaction:** + +* Name: `--no-interaction` +* Shortcut: `-n` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: Do not ask any interactive question. +* Default: `false` diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_2.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_2.txt new file mode 100644 index 0000000..78580d0 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_2.txt @@ -0,0 +1,22 @@ +My Symfony application version v1.0 + +Usage: + [options] command [arguments] + +Options: + --help -h Display this help message. + --quiet -q Do not output any message. + --verbose -v|vv|vvv Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + --version -V Display this application version. + --ansi Force ANSI output. + --no-ansi Disable ANSI output. + --no-interaction -n Do not ask any interactive question. + +Available commands: + alias1 command 1 description + alias2 command 1 description + help Displays help for a command + list Lists commands +descriptor + descriptor:command1 command 1 description + descriptor:command2 command 2 description diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_2.xml b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_2.xml new file mode 100644 index 0000000..9aa77bb --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_2.xml @@ -0,0 +1,181 @@ + + + + + help [--xml] [--format="..."] [--raw] [command_name] + Displays help for a command + The <info>help</info> command displays help for a given command: + + <info>php app/console help list</info> + + You can also output the help in other formats by using the <comment>--format</comment> option: + + <info>php app/console help --format=xml list</info> + + To display the list of available commands, please use the <info>list</info> command. + + + + The command name + + help + + + + + + + + + + + + + + + + + + list [--xml] [--raw] [--format="..."] [namespace] + Lists commands + The <info>list</info> command lists all commands: + + <info>php app/console list</info> + + You can also display the commands for a specific namespace: + + <info>php app/console list test</info> + + You can also output the information in other formats by using the <comment>--format</comment> option: + + <info>php app/console list --format=xml</info> + + It's also possible to get raw list of commands (useful for embedding command runner): + + <info>php app/console list --raw</info> + + + + The namespace name + + + + + + + + + + + descriptor:command1 + command 1 description + command 1 help + + alias1 + alias2 + + + + + + + + + + + + + + descriptor:command2 [-o|--option_name] argument_name + command 2 description + command 2 help + + + + + + + + + + + + + + + + + + + + + + alias1 + alias2 + help + list + + + descriptor:command1 + descriptor:command2 + + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_astext1.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_astext1.txt new file mode 100644 index 0000000..8e5a162 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_astext1.txt @@ -0,0 +1,20 @@ +Console Tool + +Usage: + [options] command [arguments] + +Options: + --help -h Display this help message. + --quiet -q Do not output any message. + --verbose -v|vv|vvv Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + --version -V Display this application version. + --ansi Force ANSI output. + --no-ansi Disable ANSI output. + --no-interaction -n Do not ask any interactive question. + +Available commands: + afoobar The foo:bar command + help Displays help for a command + list Lists commands +foo + foo:bar The foo:bar command \ No newline at end of file diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_astext2.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_astext2.txt new file mode 100644 index 0000000..d27af91 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_astext2.txt @@ -0,0 +1,16 @@ +Console Tool + +Usage: + [options] command [arguments] + +Options: + --help -h Display this help message. + --quiet -q Do not output any message. + --verbose -v|vv|vvv Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + --version -V Display this application version. + --ansi Force ANSI output. + --no-ansi Disable ANSI output. + --no-interaction -n Do not ask any interactive question. + +Available commands for the "foo" namespace: + foo:bar The foo:bar command \ No newline at end of file diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_asxml1.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_asxml1.txt new file mode 100644 index 0000000..2eb3c30 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_asxml1.txt @@ -0,0 +1,140 @@ + + + + + help [--xml] [--format="..."] [--raw] [command_name] + Displays help for a command + The <info>help</info> command displays help for a given command: + + <info>php app/console help list</info> + + You can also output the help in other formats by using the <comment>--format</comment> option: + + <info>php app/console help --format=xml list</info> + + To display the list of available commands, please use the <info>list</info> command. + + + + The command name + + help + + + + + + + + + + + + + + + + + + list [--xml] [--raw] [--format="..."] [namespace] + Lists commands + The <info>list</info> command lists all commands: + + <info>php app/console list</info> + + You can also display the commands for a specific namespace: + + <info>php app/console list test</info> + + You can also output the information in other formats by using the <comment>--format</comment> option: + + <info>php app/console list --format=xml</info> + + It's also possible to get raw list of commands (useful for embedding command runner): + + <info>php app/console list --raw</info> + + + + The namespace name + + + + + + + + + + + foo:bar + The foo:bar command + + + afoobar + + + + + + + + + + + + + + + + afoobar + help + list + + + foo:bar + + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_asxml2.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_asxml2.txt new file mode 100644 index 0000000..5d61d2a --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_asxml2.txt @@ -0,0 +1,37 @@ + + + + + foo:bar + The foo:bar command + + + afoobar + + + + + + + + + + + + + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_gethelp.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_gethelp.txt new file mode 100644 index 0000000..79c36f9 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_gethelp.txt @@ -0,0 +1,13 @@ +Console Tool + +Usage: + [options] command [arguments] + +Options: + --help -h Display this help message. + --quiet -q Do not output any message. + --verbose -v|vv|vvv Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + --version -V Display this application version. + --ansi Force ANSI output. + --no-ansi Disable ANSI output. + --no-interaction -n Do not ask any interactive question. \ No newline at end of file diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_renderexception1.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_renderexception1.txt new file mode 100644 index 0000000..4629345 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_renderexception1.txt @@ -0,0 +1,8 @@ + + + + [InvalidArgumentException] + Command "foo" is not defined. + + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_renderexception2.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_renderexception2.txt new file mode 100644 index 0000000..c758129 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_renderexception2.txt @@ -0,0 +1,11 @@ + + + + [InvalidArgumentException] + The "--foo" option does not exist. + + + +list [--xml] [--raw] [--format="..."] [namespace] + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_renderexception3.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_renderexception3.txt new file mode 100644 index 0000000..c639924 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_renderexception3.txt @@ -0,0 +1,19 @@ + + + + [Exception] + Second exception + + + + + + + [Exception] + First exception + + + +foo3:bar + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_renderexception4.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_renderexception4.txt new file mode 100644 index 0000000..19f893b --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_renderexception4.txt @@ -0,0 +1,9 @@ + + + + [InvalidArgumentException] + Command "foo" is not define + d. + + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_run1.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_run1.txt new file mode 100644 index 0000000..5e9554f --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_run1.txt @@ -0,0 +1,17 @@ +Console Tool + +Usage: + [options] command [arguments] + +Options: + --help -h Display this help message. + --quiet -q Do not output any message. + --verbose -v|vv|vvv Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + --version -V Display this application version. + --ansi Force ANSI output. + --no-ansi Disable ANSI output. + --no-interaction -n Do not ask any interactive question. + +Available commands: + help Displays help for a command + list Lists commands diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_run2.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_run2.txt new file mode 100644 index 0000000..b9dab9a --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_run2.txt @@ -0,0 +1,30 @@ +Usage: + help [--xml] [--format="..."] [--raw] [command_name] + +Arguments: + command The command to execute + command_name The command name (default: "help") + +Options: + --xml To output help as XML + --format To output help in other formats + --raw To output raw command help + --help (-h) Display this help message. + --quiet (-q) Do not output any message. + --verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + --version (-V) Display this application version. + --ansi Force ANSI output. + --no-ansi Disable ANSI output. + --no-interaction (-n) Do not ask any interactive question. + +Help: + The help command displays help for a given command: + + php app/console help list + + You can also output the help in other formats by using the --format option: + + php app/console help --format=xml list + + To display the list of available commands, please use the list command. + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_run3.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_run3.txt new file mode 100644 index 0000000..68bf516 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_run3.txt @@ -0,0 +1,28 @@ +Usage: + list [--xml] [--raw] [--format="..."] [namespace] + +Arguments: + namespace The namespace name + +Options: + --xml To output list as XML + --raw To output raw command list + --format To output list in other formats + +Help: + The list command lists all commands: + + php app/console list + + You can also display the commands for a specific namespace: + + php app/console list test + + You can also output the information in other formats by using the --format option: + + php app/console list --format=xml + + It's also possible to get raw list of commands (useful for embedding command runner): + + php app/console list --raw + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_run4.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_run4.txt new file mode 100644 index 0000000..47187fc --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/application_run4.txt @@ -0,0 +1 @@ +Console Tool diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_1.json b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_1.json new file mode 100644 index 0000000..0c1675d --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_1.json @@ -0,0 +1 @@ +{"name":"descriptor:command1","usage":"descriptor:command1","description":"command 1 description","help":"command 1 help","aliases":["alias1","alias2"],"definition":{"arguments":[],"options":[]}} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_1.md b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_1.md new file mode 100644 index 0000000..2cef9a2 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_1.md @@ -0,0 +1,8 @@ +descriptor:command1 +------------------- + +* Description: command 1 description +* Usage: `descriptor:command1` +* Aliases: `alias1`, `alias2` + +command 1 help diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_1.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_1.txt new file mode 100644 index 0000000..2375ac0 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_1.txt @@ -0,0 +1,7 @@ +Usage: + descriptor:command1 + +Aliases: alias1, alias2 + +Help: + command 1 help diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_1.xml b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_1.xml new file mode 100644 index 0000000..dcfa6fa --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_1.xml @@ -0,0 +1,12 @@ + + + descriptor:command1 + command 1 description + command 1 help + + alias1 + alias2 + + + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_2.json b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_2.json new file mode 100644 index 0000000..493b584 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_2.json @@ -0,0 +1 @@ +{"name":"descriptor:command2","usage":"descriptor:command2 [-o|--option_name] argument_name","description":"command 2 description","help":"command 2 help","aliases":[],"definition":{"arguments":{"argument_name":{"name":"argument_name","is_required":true,"is_array":false,"description":"","default":null}},"options":{"option_name":{"name":"--option_name","shortcut":"-o","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"","default":false}}}} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_2.md b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_2.md new file mode 100644 index 0000000..5257c0d --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_2.md @@ -0,0 +1,30 @@ +descriptor:command2 +------------------- + +* Description: command 2 description +* Usage: `descriptor:command2 [-o|--option_name] argument_name` +* Aliases: + +command 2 help + +### Arguments: + +**argument_name:** + +* Name: argument_name +* Is required: yes +* Is array: no +* Description: +* Default: `NULL` + +### Options: + +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: +* Default: `false` diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_2.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_2.txt new file mode 100644 index 0000000..1da9f3d --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_2.txt @@ -0,0 +1,11 @@ +Usage: + descriptor:command2 [-o|--option_name] argument_name + +Arguments: + argument_name + +Options: + --option_name (-o) + +Help: + command 2 help diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_2.xml b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_2.xml new file mode 100644 index 0000000..c411c36 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_2.xml @@ -0,0 +1,18 @@ + + + descriptor:command2 [-o|--option_name] argument_name + command 2 description + command 2 help + + + + + + + + + + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_astext.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_astext.txt new file mode 100644 index 0000000..8b6c74c --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_astext.txt @@ -0,0 +1,18 @@ +Usage: + namespace:name + +Aliases: name +Arguments: + command The command to execute + +Options: + --help (-h) Display this help message. + --quiet (-q) Do not output any message. + --verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug + --version (-V) Display this application version. + --ansi Force ANSI output. + --no-ansi Disable ANSI output. + --no-interaction (-n) Do not ask any interactive question. + +Help: + help diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_asxml.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_asxml.txt new file mode 100644 index 0000000..4b0012d --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/command_asxml.txt @@ -0,0 +1,38 @@ + + + namespace:name + description + help + + name + + + + The command to execute + + + + + + + + + + + + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/definition_astext.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/definition_astext.txt new file mode 100644 index 0000000..a7d7e0d --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/definition_astext.txt @@ -0,0 +1,11 @@ +Arguments: + foo The foo argument + baz The baz argument (default: true) + bar The bar argument (default: ["http://foo.com/"]) + +Options: + --foo (-f) The foo option + --baz The baz option (default: false) + --bar (-b) The bar option (default: "bar") + --qux The qux option (default: ["http://foo.com/","bar"]) (multiple values allowed) + --qux2 The qux2 option (default: {"foo":"bar"}) (multiple values allowed) diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/definition_asxml.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/definition_asxml.txt new file mode 100644 index 0000000..eec8c07 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/definition_asxml.txt @@ -0,0 +1,39 @@ + + + + + The foo argument + + + + The baz argument + + true + + + + The bar argument + + bar + + + + + + + + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_1.json b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_1.json new file mode 100644 index 0000000..b8173b6 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_1.json @@ -0,0 +1 @@ +{"name":"argument_name","is_required":true,"is_array":false,"description":"","default":null} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_1.md b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_1.md new file mode 100644 index 0000000..88f311a --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_1.md @@ -0,0 +1,7 @@ +**argument_name:** + +* Name: argument_name +* Is required: yes +* Is array: no +* Description: +* Default: `NULL` diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_1.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_1.txt new file mode 100644 index 0000000..111e515 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_1.txt @@ -0,0 +1 @@ + argument_name diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_1.xml b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_1.xml new file mode 100644 index 0000000..cb37f81 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_1.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_2.json b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_2.json new file mode 100644 index 0000000..ef06b09 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_2.json @@ -0,0 +1 @@ +{"name":"argument_name","is_required":false,"is_array":true,"description":"argument description","default":[]} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_2.md b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_2.md new file mode 100644 index 0000000..3cdb00c --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_2.md @@ -0,0 +1,7 @@ +**argument_name:** + +* Name: argument_name +* Is required: no +* Is array: yes +* Description: argument description +* Default: `array ()` diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_2.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_2.txt new file mode 100644 index 0000000..9497b1c --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_2.txt @@ -0,0 +1 @@ + argument_name argument description diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_2.xml b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_2.xml new file mode 100644 index 0000000..629da5a --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_2.xml @@ -0,0 +1,5 @@ + + + argument description + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_3.json b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_3.json new file mode 100644 index 0000000..de8484e --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_3.json @@ -0,0 +1 @@ +{"name":"argument_name","is_required":false,"is_array":false,"description":"argument description","default":"default_value"} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_3.md b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_3.md new file mode 100644 index 0000000..be1c443 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_3.md @@ -0,0 +1,7 @@ +**argument_name:** + +* Name: argument_name +* Is required: no +* Is array: no +* Description: argument description +* Default: `'default_value'` diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_3.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_3.txt new file mode 100644 index 0000000..c421fc9 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_3.txt @@ -0,0 +1 @@ + argument_name argument description (default: "default_value") diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_3.xml b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_3.xml new file mode 100644 index 0000000..399a5c8 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_argument_3.xml @@ -0,0 +1,7 @@ + + + argument description + + default_value + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_1.json b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_1.json new file mode 100644 index 0000000..c7a7d83 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_1.json @@ -0,0 +1 @@ +{"arguments":[],"options":[]} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_1.md b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_1.md new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_1.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_1.txt new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_1.xml b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_1.xml new file mode 100644 index 0000000..b5481ce --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_1.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_2.json b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_2.json new file mode 100644 index 0000000..9964a55 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_2.json @@ -0,0 +1 @@ +{"arguments":{"argument_name":{"name":"argument_name","is_required":true,"is_array":false,"description":"","default":null}},"options":[]} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_2.md b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_2.md new file mode 100644 index 0000000..923191c --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_2.md @@ -0,0 +1,9 @@ +### Arguments: + +**argument_name:** + +* Name: argument_name +* Is required: yes +* Is array: no +* Description: +* Default: `NULL` diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_2.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_2.txt new file mode 100644 index 0000000..0db9f66 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_2.txt @@ -0,0 +1,2 @@ +Arguments: + argument_name diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_2.xml b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_2.xml new file mode 100644 index 0000000..102efc1 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_2.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_3.json b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_3.json new file mode 100644 index 0000000..6a86056 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_3.json @@ -0,0 +1 @@ +{"arguments":[],"options":{"option_name":{"name":"--option_name","shortcut":"-o","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"","default":false}}} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_3.md b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_3.md new file mode 100644 index 0000000..40fd7b0 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_3.md @@ -0,0 +1,11 @@ +### Options: + +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: +* Default: `false` diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_3.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_3.txt new file mode 100644 index 0000000..c6fb2cc --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_3.txt @@ -0,0 +1,2 @@ +Options: + --option_name (-o) diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_3.xml b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_3.xml new file mode 100644 index 0000000..bc95151 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_3.xml @@ -0,0 +1,9 @@ + + + + + + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_4.json b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_4.json new file mode 100644 index 0000000..c5a0019 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_4.json @@ -0,0 +1 @@ +{"arguments":{"argument_name":{"name":"argument_name","is_required":true,"is_array":false,"description":"","default":null}},"options":{"option_name":{"name":"--option_name","shortcut":"-o","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"","default":false}}} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_4.md b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_4.md new file mode 100644 index 0000000..a31feea --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_4.md @@ -0,0 +1,21 @@ +### Arguments: + +**argument_name:** + +* Name: argument_name +* Is required: yes +* Is array: no +* Description: +* Default: `NULL` + +### Options: + +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: +* Default: `false` diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_4.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_4.txt new file mode 100644 index 0000000..e17c61c --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_4.txt @@ -0,0 +1,5 @@ +Arguments: + argument_name + +Options: + --option_name (-o) diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_4.xml b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_4.xml new file mode 100644 index 0000000..cffceec --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_definition_4.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_1.json b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_1.json new file mode 100644 index 0000000..60c5b56 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_1.json @@ -0,0 +1 @@ +{"name":"--option_name","shortcut":"-o","accept_value":false,"is_value_required":false,"is_multiple":false,"description":"","default":false} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_1.md b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_1.md new file mode 100644 index 0000000..6f9e9a7 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_1.md @@ -0,0 +1,9 @@ +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: no +* Is value required: no +* Is multiple: no +* Description: +* Default: `false` diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_1.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_1.txt new file mode 100644 index 0000000..daf83d0 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_1.txt @@ -0,0 +1 @@ + --option_name (-o) diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_1.xml b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_1.xml new file mode 100644 index 0000000..8a64ea6 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_1.xml @@ -0,0 +1,4 @@ + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_2.json b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_2.json new file mode 100644 index 0000000..04e4228 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_2.json @@ -0,0 +1 @@ +{"name":"--option_name","shortcut":"-o","accept_value":true,"is_value_required":false,"is_multiple":false,"description":"option description","default":"default_value"} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_2.md b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_2.md new file mode 100644 index 0000000..634ac0b --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_2.md @@ -0,0 +1,9 @@ +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: yes +* Is value required: no +* Is multiple: no +* Description: option description +* Default: `'default_value'` diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_2.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_2.txt new file mode 100644 index 0000000..627e3c1 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_2.txt @@ -0,0 +1 @@ + --option_name (-o) option description (default: "default_value") diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_2.xml b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_2.xml new file mode 100644 index 0000000..4afac5b --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_2.xml @@ -0,0 +1,7 @@ + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_3.json b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_3.json new file mode 100644 index 0000000..c1ea120 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_3.json @@ -0,0 +1 @@ +{"name":"--option_name","shortcut":"-o","accept_value":true,"is_value_required":true,"is_multiple":false,"description":"option description","default":null} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_3.md b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_3.md new file mode 100644 index 0000000..3428289 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_3.md @@ -0,0 +1,9 @@ +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Description: option description +* Default: `NULL` diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_3.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_3.txt new file mode 100644 index 0000000..b88b12d --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_3.txt @@ -0,0 +1 @@ + --option_name (-o) option description diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_3.xml b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_3.xml new file mode 100644 index 0000000..dcc0631 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_3.xml @@ -0,0 +1,5 @@ + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_4.json b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_4.json new file mode 100644 index 0000000..1b671d8 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_4.json @@ -0,0 +1 @@ +{"name":"--option_name","shortcut":"-o","accept_value":true,"is_value_required":false,"is_multiple":true,"description":"option description","default":[]} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_4.md b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_4.md new file mode 100644 index 0000000..8ffba56 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_4.md @@ -0,0 +1,9 @@ +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o` +* Accept value: yes +* Is value required: no +* Is multiple: yes +* Description: option description +* Default: `array ()` diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_4.txt b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_4.txt new file mode 100644 index 0000000..5dba5e6 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_4.txt @@ -0,0 +1 @@ + --option_name (-o) option description (multiple values allowed) diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_4.xml b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_4.xml new file mode 100644 index 0000000..5e2418b --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Fixtures/input_option_4.xml @@ -0,0 +1,5 @@ + + diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Formatter/OutputFormatterStyleStackTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Formatter/OutputFormatterStyleStackTest.php new file mode 100644 index 0000000..e99ebc5 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Formatter/OutputFormatterStyleStackTest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Formatter; + +use Symfony\Component\Console\Formatter\OutputFormatterStyleStack; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; + +class OutputFormatterStyleStackTest extends \PHPUnit_Framework_TestCase +{ + public function testPush() + { + $stack = new OutputFormatterStyleStack(); + $stack->push($s1 = new OutputFormatterStyle('white', 'black')); + $stack->push($s2 = new OutputFormatterStyle('yellow', 'blue')); + + $this->assertEquals($s2, $stack->getCurrent()); + + $stack->push($s3 = new OutputFormatterStyle('green', 'red')); + + $this->assertEquals($s3, $stack->getCurrent()); + } + + public function testPop() + { + $stack = new OutputFormatterStyleStack(); + $stack->push($s1 = new OutputFormatterStyle('white', 'black')); + $stack->push($s2 = new OutputFormatterStyle('yellow', 'blue')); + + $this->assertEquals($s2, $stack->pop()); + $this->assertEquals($s1, $stack->pop()); + } + + public function testPopEmpty() + { + $stack = new OutputFormatterStyleStack(); + $style = new OutputFormatterStyle(); + + $this->assertEquals($style, $stack->pop()); + } + + public function testPopNotLast() + { + $stack = new OutputFormatterStyleStack(); + $stack->push($s1 = new OutputFormatterStyle('white', 'black')); + $stack->push($s2 = new OutputFormatterStyle('yellow', 'blue')); + $stack->push($s3 = new OutputFormatterStyle('green', 'red')); + + $this->assertEquals($s2, $stack->pop($s2)); + $this->assertEquals($s1, $stack->pop()); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidPop() + { + $stack = new OutputFormatterStyleStack(); + $stack->push(new OutputFormatterStyle('white', 'black')); + $stack->pop(new OutputFormatterStyle('yellow', 'blue')); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Formatter/OutputFormatterStyleTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Formatter/OutputFormatterStyleTest.php new file mode 100644 index 0000000..6890a9b --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Formatter/OutputFormatterStyleTest.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Formatter; + +use Symfony\Component\Console\Formatter\OutputFormatterStyle; + +class OutputFormatterStyleTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $style = new OutputFormatterStyle('green', 'black', array('bold', 'underscore')); + $this->assertEquals("\033[32;40;1;4mfoo\033[0m", $style->apply('foo')); + + $style = new OutputFormatterStyle('red', null, array('blink')); + $this->assertEquals("\033[31;5mfoo\033[0m", $style->apply('foo')); + + $style = new OutputFormatterStyle(null, 'white'); + $this->assertEquals("\033[47mfoo\033[0m", $style->apply('foo')); + } + + public function testForeground() + { + $style = new OutputFormatterStyle(); + + $style->setForeground('black'); + $this->assertEquals("\033[30mfoo\033[0m", $style->apply('foo')); + + $style->setForeground('blue'); + $this->assertEquals("\033[34mfoo\033[0m", $style->apply('foo')); + + $this->setExpectedException('InvalidArgumentException'); + $style->setForeground('undefined-color'); + } + + public function testBackground() + { + $style = new OutputFormatterStyle(); + + $style->setBackground('black'); + $this->assertEquals("\033[40mfoo\033[0m", $style->apply('foo')); + + $style->setBackground('yellow'); + $this->assertEquals("\033[43mfoo\033[0m", $style->apply('foo')); + + $this->setExpectedException('InvalidArgumentException'); + $style->setBackground('undefined-color'); + } + + public function testOptions() + { + $style = new OutputFormatterStyle(); + + $style->setOptions(array('reverse', 'conceal')); + $this->assertEquals("\033[7;8mfoo\033[0m", $style->apply('foo')); + + $style->setOption('bold'); + $this->assertEquals("\033[7;8;1mfoo\033[0m", $style->apply('foo')); + + $style->unsetOption('reverse'); + $this->assertEquals("\033[8;1mfoo\033[0m", $style->apply('foo')); + + $style->setOption('bold'); + $this->assertEquals("\033[8;1mfoo\033[0m", $style->apply('foo')); + + $style->setOptions(array('bold')); + $this->assertEquals("\033[1mfoo\033[0m", $style->apply('foo')); + + try { + $style->setOption('foo'); + $this->fail('->setOption() throws an \InvalidArgumentException when the option does not exist in the available options'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->setOption() throws an \InvalidArgumentException when the option does not exist in the available options'); + $this->assertContains('Invalid option specified: "foo"', $e->getMessage(), '->setOption() throws an \InvalidArgumentException when the option does not exist in the available options'); + } + + try { + $style->unsetOption('foo'); + $this->fail('->unsetOption() throws an \InvalidArgumentException when the option does not exist in the available options'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->unsetOption() throws an \InvalidArgumentException when the option does not exist in the available options'); + $this->assertContains('Invalid option specified: "foo"', $e->getMessage(), '->unsetOption() throws an \InvalidArgumentException when the option does not exist in the available options'); + } + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php new file mode 100644 index 0000000..497630e --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php @@ -0,0 +1,231 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Formatter; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; + +class FormatterStyleTest extends \PHPUnit_Framework_TestCase +{ + public function testEmptyTag() + { + $formatter = new OutputFormatter(true); + $this->assertEquals("foo<>bar", $formatter->format('foo<>bar')); + } + + public function testLGCharEscaping() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals("fooformat('foo\\assertEquals("some info", $formatter->format('\\some info\\')); + $this->assertEquals("\\some info\\", OutputFormatter::escape('some info')); + + $this->assertEquals( + "\033[33mSymfony\\Component\\Console does work very well!\033[0m", + $formatter->format('Symfony\Component\Console does work very well!') + ); + } + + public function testBundledStyles() + { + $formatter = new OutputFormatter(true); + + $this->assertTrue($formatter->hasStyle('error')); + $this->assertTrue($formatter->hasStyle('info')); + $this->assertTrue($formatter->hasStyle('comment')); + $this->assertTrue($formatter->hasStyle('question')); + + $this->assertEquals( + "\033[37;41msome error\033[0m", + $formatter->format('some error') + ); + $this->assertEquals( + "\033[32msome info\033[0m", + $formatter->format('some info') + ); + $this->assertEquals( + "\033[33msome comment\033[0m", + $formatter->format('some comment') + ); + $this->assertEquals( + "\033[30;46msome question\033[0m", + $formatter->format('some question') + ); + } + + public function testNestedStyles() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals( + "\033[37;41msome \033[0m\033[32msome info\033[0m\033[37;41m error\033[0m", + $formatter->format('some some info error') + ); + } + + public function testStyleMatchingNotGreedy() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals( + "(\033[32m>=2.0,<2.3\033[0m)", + $formatter->format('(>=2.0,<2.3)') + ); + } + + public function testStyleEscaping() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals( + "(\033[32mz>=2.0,format('('.$formatter->escape('z>=2.0,)') + ); + } + + public function testDeepNestedStyles() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals( + "\033[37;41merror\033[0m\033[32minfo\033[0m\033[33mcomment\033[0m\033[37;41merror\033[0m", + $formatter->format('errorinfocommenterror') + ); + } + + public function testNewStyle() + { + $formatter = new OutputFormatter(true); + + $style = new OutputFormatterStyle('blue', 'white'); + $formatter->setStyle('test', $style); + + $this->assertEquals($style, $formatter->getStyle('test')); + $this->assertNotEquals($style, $formatter->getStyle('info')); + + $this->assertEquals("\033[34;47msome custom msg\033[0m", $formatter->format('some custom msg')); + } + + public function testRedefineStyle() + { + $formatter = new OutputFormatter(true); + + $style = new OutputFormatterStyle('blue', 'white'); + $formatter->setStyle('info', $style); + + $this->assertEquals("\033[34;47msome custom msg\033[0m", $formatter->format('some custom msg')); + } + + public function testInlineStyle() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals("\033[34;41msome text\033[0m", $formatter->format('some text')); + $this->assertEquals("\033[34;41msome text\033[0m", $formatter->format('some text')); + } + + public function testNonStyleTag() + { + $formatter = new OutputFormatter(true); + $this->assertEquals("\033[32msome \033[0m\033[32m styled\033[0m", $formatter->format('some styled')); + } + + public function testNotDecoratedFormatter() + { + $formatter = new OutputFormatter(false); + + $this->assertTrue($formatter->hasStyle('error')); + $this->assertTrue($formatter->hasStyle('info')); + $this->assertTrue($formatter->hasStyle('comment')); + $this->assertTrue($formatter->hasStyle('question')); + + $this->assertEquals( + "some error", $formatter->format('some error') + ); + $this->assertEquals( + "some info", $formatter->format('some info') + ); + $this->assertEquals( + "some comment", $formatter->format('some comment') + ); + $this->assertEquals( + "some question", $formatter->format('some question') + ); + + $formatter->setDecorated(true); + + $this->assertEquals( + "\033[37;41msome error\033[0m", $formatter->format('some error') + ); + $this->assertEquals( + "\033[32msome info\033[0m", $formatter->format('some info') + ); + $this->assertEquals( + "\033[33msome comment\033[0m", $formatter->format('some comment') + ); + $this->assertEquals( + "\033[30;46msome question\033[0m", $formatter->format('some question') + ); + } + + public function testContentWithLineBreaks() + { + $formatter = new OutputFormatter(true); + + $this->assertEquals(<<format(<< +some text +EOF + )); + + $this->assertEquals(<<format(<<some text + +EOF + )); + + $this->assertEquals(<<format(<< +some text + +EOF + )); + + $this->assertEquals(<<format(<< +some text +more text + +EOF + )); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Helper/DialogHelperTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Helper/DialogHelperTest.php new file mode 100644 index 0000000..108f709 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Helper/DialogHelperTest.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use Symfony\Component\Console\Helper\DialogHelper; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Output\StreamOutput; + +class DialogHelperTest extends \PHPUnit_Framework_TestCase +{ + public function testSelect() + { + $dialog = new DialogHelper(); + + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $heroes = array('Superman', 'Batman', 'Spiderman'); + + $dialog->setInputStream($this->getInputStream("\n1\n 1 \nFabien\n1\nFabien\n1\n0,2\n 0 , 2 \n\n\n")); + $this->assertEquals('2', $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, '2')); + $this->assertEquals('1', $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes)); + $this->assertEquals('1', $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes)); + $this->assertEquals('1', $dialog->select($output = $this->getOutputStream(), 'What is your favorite superhero?', $heroes, null, false, 'Input "%s" is not a superhero!', false)); + + rewind($output->getStream()); + $this->assertContains('Input "Fabien" is not a superhero!', stream_get_contents($output->getStream())); + + try { + $this->assertEquals('1', $dialog->select($output = $this->getOutputStream(), 'What is your favorite superhero?', $heroes, null, 1)); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertEquals('Value "Fabien" is invalid', $e->getMessage()); + } + + $this->assertEquals(array('1'), $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, null, false, 'Input "%s" is not a superhero!', true)); + $this->assertEquals(array('0', '2'), $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, null, false, 'Input "%s" is not a superhero!', true)); + $this->assertEquals(array('0', '2'), $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, null, false, 'Input "%s" is not a superhero!', true)); + $this->assertEquals(array('0', '1'), $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, '0,1', false, 'Input "%s" is not a superhero!', true)); + $this->assertEquals(array('0', '1'), $dialog->select($this->getOutputStream(), 'What is your favorite superhero?', $heroes, ' 0 , 1 ', false, 'Input "%s" is not a superhero!', true)); + } + + public function testAsk() + { + $dialog = new DialogHelper(); + + $dialog->setInputStream($this->getInputStream("\n8AM\n")); + + $this->assertEquals('2PM', $dialog->ask($this->getOutputStream(), 'What time is it?', '2PM')); + $this->assertEquals('8AM', $dialog->ask($output = $this->getOutputStream(), 'What time is it?', '2PM')); + + rewind($output->getStream()); + $this->assertEquals('What time is it?', stream_get_contents($output->getStream())); + } + + public function testAskWithAutocomplete() + { + if (!$this->hasSttyAvailable()) { + $this->markTestSkipped('`stty` is required to test autocomplete functionality'); + } + + // Acm + // AcsTest + // + // + // Test + // + // S + // F00oo + $inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\nS\177\177\033[B\033[B\nF00\177\177oo\t\n"); + + $dialog = new DialogHelper(); + $dialog->setInputStream($inputStream); + + $bundles = array('AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle'); + + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + $this->assertEquals('AsseticBundleTest', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + $this->assertEquals('FrameworkBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + $this->assertEquals('SecurityBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + $this->assertEquals('FooBundleTest', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + $this->assertEquals('AsseticBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + $this->assertEquals('FooBundle', $dialog->ask($this->getOutputStream(), 'Please select a bundle', 'FrameworkBundle', $bundles)); + } + + public function testAskHiddenResponse() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('This test is not supported on Windows'); + } + + $dialog = new DialogHelper(); + + $dialog->setInputStream($this->getInputStream("8AM\n")); + + $this->assertEquals('8AM', $dialog->askHiddenResponse($this->getOutputStream(), 'What time is it?')); + } + + public function testAskConfirmation() + { + $dialog = new DialogHelper(); + + $dialog->setInputStream($this->getInputStream("\n\n")); + $this->assertTrue($dialog->askConfirmation($this->getOutputStream(), 'Do you like French fries?')); + $this->assertFalse($dialog->askConfirmation($this->getOutputStream(), 'Do you like French fries?', false)); + + $dialog->setInputStream($this->getInputStream("y\nyes\n")); + $this->assertTrue($dialog->askConfirmation($this->getOutputStream(), 'Do you like French fries?', false)); + $this->assertTrue($dialog->askConfirmation($this->getOutputStream(), 'Do you like French fries?', false)); + + $dialog->setInputStream($this->getInputStream("n\nno\n")); + $this->assertFalse($dialog->askConfirmation($this->getOutputStream(), 'Do you like French fries?', true)); + $this->assertFalse($dialog->askConfirmation($this->getOutputStream(), 'Do you like French fries?', true)); + } + + public function testAskAndValidate() + { + $dialog = new DialogHelper(); + $helperSet = new HelperSet(array(new FormatterHelper())); + $dialog->setHelperSet($helperSet); + + $question ='What color was the white horse of Henry IV?'; + $error = 'This is not a color!'; + $validator = function ($color) use ($error) { + if (!in_array($color, array('white', 'black'))) { + throw new \InvalidArgumentException($error); + } + + return $color; + }; + + $dialog->setInputStream($this->getInputStream("\nblack\n")); + $this->assertEquals('white', $dialog->askAndValidate($this->getOutputStream(), $question, $validator, 2, 'white')); + $this->assertEquals('black', $dialog->askAndValidate($this->getOutputStream(), $question, $validator, 2, 'white')); + + $dialog->setInputStream($this->getInputStream("green\nyellow\norange\n")); + try { + $this->assertEquals('white', $dialog->askAndValidate($this->getOutputStream(), $question, $validator, 2, 'white')); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertEquals($error, $e->getMessage()); + } + } + + protected function getInputStream($input) + { + $stream = fopen('php://memory', 'r+', false); + fputs($stream, $input); + rewind($stream); + + return $stream; + } + + protected function getOutputStream() + { + return new StreamOutput(fopen('php://memory', 'r+', false)); + } + + private function hasSttyAvailable() + { + exec('stty 2>&1', $output, $exitcode); + + return $exitcode === 0; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Helper/FormatterHelperTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Helper/FormatterHelperTest.php new file mode 100644 index 0000000..87a45e1 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Helper/FormatterHelperTest.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use Symfony\Component\Console\Helper\FormatterHelper; + +class FormatterHelperTest extends \PHPUnit_Framework_TestCase +{ + public function testFormatSection() + { + $formatter = new FormatterHelper(); + + $this->assertEquals( + '[cli] Some text to display', + $formatter->formatSection('cli', 'Some text to display'), + '::formatSection() formats a message in a section' + ); + } + + public function testFormatBlock() + { + $formatter = new FormatterHelper(); + + $this->assertEquals( + ' Some text to display ', + $formatter->formatBlock('Some text to display', 'error'), + '::formatBlock() formats a message in a block' + ); + + $this->assertEquals( + ' Some text to display '."\n" . + ' foo bar ', + $formatter->formatBlock(array('Some text to display', 'foo bar'), 'error'), + '::formatBlock() formats a message in a block' + ); + + $this->assertEquals( + ' '."\n" . + ' Some text to display '."\n" . + ' ', + $formatter->formatBlock('Some text to display', 'error', true), + '::formatBlock() formats a message in a block' + ); + } + + public function testFormatBlockWithDiacriticLetters() + { + if (!extension_loaded('mbstring')) { + $this->markTestSkipped('This test requires mbstring to work.'); + } + + $formatter = new FormatterHelper(); + + $this->assertEquals( + ' '."\n" . + ' Du texte à afficher '."\n" . + ' ', + $formatter->formatBlock('Du texte à afficher', 'error', true), + '::formatBlock() formats a message in a block' + ); + } + + public function testFormatBlockLGEscaping() + { + $formatter = new FormatterHelper(); + + $this->assertEquals( + ' '."\n" . + ' \some info\ '."\n" . + ' ', + $formatter->formatBlock('some info', 'error', true), + '::formatBlock() escapes \'<\' chars' + ); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Helper/HelperSetTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Helper/HelperSetTest.php new file mode 100644 index 0000000..c983273 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Helper/HelperSetTest.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Command\Command; + +class HelperSetTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers \Symfony\Component\Console\Helper\HelperSet::__construct + */ + public function testConstructor() + { + $mock_helper = $this->getGenericMockHelper('fake_helper'); + $helperset = new HelperSet(array('fake_helper_alias' => $mock_helper)); + + $this->assertEquals($mock_helper, $helperset->get('fake_helper_alias'), '__construct sets given helper to helpers'); + $this->assertTrue($helperset->has('fake_helper_alias'), '__construct sets helper alias for given helper'); + } + + /** + * @covers \Symfony\Component\Console\Helper\HelperSet::set + */ + public function testSet() + { + $helperset = new HelperSet(); + $helperset->set($this->getGenericMockHelper('fake_helper', $helperset)); + $this->assertTrue($helperset->has('fake_helper'), '->set() adds helper to helpers'); + + $helperset = new HelperSet(); + $helperset->set($this->getGenericMockHelper('fake_helper_01', $helperset)); + $helperset->set($this->getGenericMockHelper('fake_helper_02', $helperset)); + $this->assertTrue($helperset->has('fake_helper_01'), '->set() will set multiple helpers on consecutive calls'); + $this->assertTrue($helperset->has('fake_helper_02'), '->set() will set multiple helpers on consecutive calls'); + + $helperset = new HelperSet(); + $helperset->set($this->getGenericMockHelper('fake_helper', $helperset), 'fake_helper_alias'); + $this->assertTrue($helperset->has('fake_helper'), '->set() adds helper alias when set'); + $this->assertTrue($helperset->has('fake_helper_alias'), '->set() adds helper alias when set'); + } + + /** + * @covers \Symfony\Component\Console\Helper\HelperSet::has + */ + public function testHas() + { + $helperset = new HelperSet(array('fake_helper_alias' => $this->getGenericMockHelper('fake_helper'))); + $this->assertTrue($helperset->has('fake_helper'), '->has() finds set helper'); + $this->assertTrue($helperset->has('fake_helper_alias'), '->has() finds set helper by alias'); + } + + /** + * @covers \Symfony\Component\Console\Helper\HelperSet::get + */ + public function testGet() + { + $helper_01 = $this->getGenericMockHelper('fake_helper_01'); + $helper_02 = $this->getGenericMockHelper('fake_helper_02'); + $helperset = new HelperSet(array('fake_helper_01_alias' => $helper_01, 'fake_helper_02_alias' => $helper_02)); + $this->assertEquals($helper_01, $helperset->get('fake_helper_01'), '->get() returns correct helper by name'); + $this->assertEquals($helper_01, $helperset->get('fake_helper_01_alias'), '->get() returns correct helper by alias'); + $this->assertEquals($helper_02, $helperset->get('fake_helper_02'), '->get() returns correct helper by name'); + $this->assertEquals($helper_02, $helperset->get('fake_helper_02_alias'), '->get() returns correct helper by alias'); + + $helperset = new HelperSet(); + try { + $helperset->get('foo'); + $this->fail('->get() throws \InvalidArgumentException when helper not found'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->get() throws \InvalidArgumentException when helper not found'); + $this->assertContains('The helper "foo" is not defined.', $e->getMessage(), '->get() throws \InvalidArgumentException when helper not found'); + } + } + + /** + * @covers \Symfony\Component\Console\Helper\HelperSet::setCommand + */ + public function testSetCommand() + { + $cmd_01 = new Command('foo'); + $cmd_02 = new Command('bar'); + + $helperset = new HelperSet(); + $helperset->setCommand($cmd_01); + $this->assertEquals($cmd_01, $helperset->getCommand(), '->setCommand() stores given command'); + + $helperset = new HelperSet(); + $helperset->setCommand($cmd_01); + $helperset->setCommand($cmd_02); + $this->assertEquals($cmd_02, $helperset->getCommand(), '->setCommand() overwrites stored command with consecutive calls'); + } + + /** + * @covers \Symfony\Component\Console\Helper\HelperSet::getCommand + */ + public function testGetCommand() + { + $cmd = new Command('foo'); + $helperset = new HelperSet(); + $helperset->setCommand($cmd); + $this->assertEquals($cmd, $helperset->getCommand(), '->getCommand() retrieves stored command'); + } + + /** + * Create a generic mock for the helper interface. Optionally check for a call to setHelperSet with a specific + * helperset instance. + * + * @param string $name + * @param HelperSet $helperset allows a mock to verify a particular helperset set is being added to the Helper + */ + private function getGenericMockHelper($name, HelperSet $helperset = null) + { + $mock_helper = $this->getMock('\Symfony\Component\Console\Helper\HelperInterface'); + $mock_helper->expects($this->any()) + ->method('getName') + ->will($this->returnValue($name)); + + if ($helperset) { + $mock_helper->expects($this->any()) + ->method('setHelperSet') + ->with($this->equalTo($helperset)); + } + + return $mock_helper; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Helper/ProgressHelperTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Helper/ProgressHelperTest.php new file mode 100644 index 0000000..9f8ced1 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Helper/ProgressHelperTest.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use Symfony\Component\Console\Helper\ProgressHelper; +use Symfony\Component\Console\Output\StreamOutput; + +class ProgressHelperTest extends \PHPUnit_Framework_TestCase +{ + public function testAdvance() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream()); + $progress->advance(); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 1 [->--------------------------]'), stream_get_contents($output->getStream())); + } + + public function testAdvanceWithStep() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream()); + $progress->advance(5); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 5 [----->----------------------]'), stream_get_contents($output->getStream())); + } + + public function testAdvanceMultipleTimes() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream()); + $progress->advance(3); + $progress->advance(2); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 3 [--->------------------------]').$this->generateOutput(' 5 [----->----------------------]'), stream_get_contents($output->getStream())); + } + + public function testCustomizations() + { + $progress = new ProgressHelper(); + $progress->setBarWidth(10); + $progress->setBarCharacter('_'); + $progress->setEmptyBarCharacter(' '); + $progress->setProgressCharacter('/'); + $progress->setFormat(' %current%/%max% [%bar%] %percent%%'); + $progress->start($output = $this->getOutputStream(), 10); + $progress->advance(); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 1/10 [_/ ] 10%'), stream_get_contents($output->getStream())); + } + + public function testPercent() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream(), 50); + $progress->display(); + $progress->advance(); + $progress->advance(); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 0/50 [>---------------------------] 0%').$this->generateOutput(' 1/50 [>---------------------------] 2%').$this->generateOutput(' 2/50 [=>--------------------------] 4%'), stream_get_contents($output->getStream())); + } + + public function testOverwriteWithShorterLine() + { + $progress = new ProgressHelper(); + $progress->setFormat(' %current%/%max% [%bar%] %percent%%'); + $progress->start($output = $this->getOutputStream(), 50); + $progress->display(); + $progress->advance(); + + // set shorter format + $progress->setFormat(' %current%/%max% [%bar%]'); + $progress->advance(); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0/50 [>---------------------------] 0%') . + $this->generateOutput(' 1/50 [>---------------------------] 2%') . + $this->generateOutput(' 2/50 [=>--------------------------] '), + stream_get_contents($output->getStream()) + ); + } + + public function testSetCurrentProgress() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream(), 50); + $progress->display(); + $progress->advance(); + $progress->setCurrent(15); + $progress->setCurrent(25); + + rewind($output->getStream()); + $this->assertEquals( + $this->generateOutput(' 0/50 [>---------------------------] 0%') . + $this->generateOutput(' 1/50 [>---------------------------] 2%') . + $this->generateOutput(' 15/50 [========>-------------------] 30%') . + $this->generateOutput(' 25/50 [==============>-------------] 50%'), + stream_get_contents($output->getStream()) + ); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage You must start the progress bar + */ + public function testSetCurrentBeforeStarting() + { + $progress = new ProgressHelper(); + $progress->setCurrent(15); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage You can't regress the progress bar + */ + public function testRegressProgress() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream(), 50); + $progress->setCurrent(15); + $progress->setCurrent(10); + } + + public function testMultiByteSupport() + { + if (!function_exists('mb_strlen') || (false === $encoding = mb_detect_encoding('■'))) { + $this->markTestSkipped('The mbstring extension is needed for multi-byte support'); + } + + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream()); + $progress->setBarCharacter('■'); + $progress->advance(3); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 3 [■■■>------------------------]'), stream_get_contents($output->getStream())); + } + + public function testPercentNotHundredBeforeComplete() + { + $progress = new ProgressHelper(); + $progress->start($output = $this->getOutputStream(), 200); + $progress->display(); + $progress->advance(199); + $progress->advance(); + + rewind($output->getStream()); + $this->assertEquals($this->generateOutput(' 0/200 [>---------------------------] 0%').$this->generateOutput(' 199/200 [===========================>] 99%').$this->generateOutput(' 200/200 [============================] 100%'), stream_get_contents($output->getStream())); + } + + protected function getOutputStream() + { + return new StreamOutput(fopen('php://memory', 'r+', false)); + } + + protected $lastMessagesLength; + + protected function generateOutput($expected) + { + $expectedout = $expected; + + if ($this->lastMessagesLength !== null) { + $expectedout = str_pad($expected, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT); + } + + $this->lastMessagesLength = strlen($expectedout); + + return "\x0D".$expectedout; + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Helper/TableHelperTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Helper/TableHelperTest.php new file mode 100644 index 0000000..5873a7f --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Helper/TableHelperTest.php @@ -0,0 +1,215 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Helper; + +use Symfony\Component\Console\Helper\TableHelper; +use Symfony\Component\Console\Output\StreamOutput; + +class TableHelperTest extends \PHPUnit_Framework_TestCase +{ + protected $stream; + + protected function setUp() + { + $this->stream = fopen('php://memory', 'r+'); + } + + protected function tearDown() + { + fclose($this->stream); + $this->stream = null; + } + + /** + * @dataProvider testRenderProvider + */ + public function testRender($headers, $rows, $layout, $expected) + { + $table = new TableHelper(); + $table + ->setHeaders($headers) + ->setRows($rows) + ->setLayout($layout) + ; + $table->render($output = $this->getOutputStream()); + + $this->assertEquals($expected, $this->getOutputContent($output)); + } + + /** + * @dataProvider testRenderProvider + */ + public function testRenderAddRows($headers, $rows, $layout, $expected) + { + $table = new TableHelper(); + $table + ->setHeaders($headers) + ->addRows($rows) + ->setLayout($layout) + ; + $table->render($output = $this->getOutputStream()); + + $this->assertEquals($expected, $this->getOutputContent($output)); + } + + /** + * @dataProvider testRenderProvider + */ + public function testRenderAddRowsOneByOne($headers, $rows, $layout, $expected) + { + $table = new TableHelper(); + $table + ->setHeaders($headers) + ->setLayout($layout) + ; + foreach ($rows as $row) { + $table->addRow($row); + } + $table->render($output = $this->getOutputStream()); + + $this->assertEquals($expected, $this->getOutputContent($output)); + } + + public function testRenderProvider() + { + return array( + array( + array('ISBN', 'Title', 'Author'), + array( + array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'), + array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'), + array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'), + array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'), + ), + TableHelper::LAYOUT_DEFAULT, +<<markTestSkipped('The "mbstring" extension is not available'); + } + + $table = new TableHelper(); + $table + ->setHeaders(array('■■')) + ->setRows(array(array(1234))) + ->setLayout(TableHelper::LAYOUT_DEFAULT) + ; + $table->render($output = $this->getOutputStream()); + + $expected = +<<
    assertEquals($expected, $this->getOutputContent($output)); + } + + protected function getOutputStream() + { + return new StreamOutput($this->stream, StreamOutput::VERBOSITY_NORMAL, false); + } + + protected function getOutputContent(StreamOutput $output) + { + rewind($output->getStream()); + + return str_replace(PHP_EOL, "\n", stream_get_contents($output->getStream())); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Input/ArgvInputTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Input/ArgvInputTest.php new file mode 100644 index 0000000..26029c7 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Input/ArgvInputTest.php @@ -0,0 +1,309 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Input; + +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; + +class ArgvInputTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $_SERVER['argv'] = array('cli.php', 'foo'); + $input = new ArgvInput(); + $r = new \ReflectionObject($input); + $p = $r->getProperty('tokens'); + $p->setAccessible(true); + + $this->assertEquals(array('foo'), $p->getValue($input), '__construct() automatically get its input from the argv server variable'); + } + + public function testParseArguments() + { + $input = new ArgvInput(array('cli.php', 'foo')); + $input->bind(new InputDefinition(array(new InputArgument('name')))); + $this->assertEquals(array('name' => 'foo'), $input->getArguments(), '->parse() parses required arguments'); + + $input->bind(new InputDefinition(array(new InputArgument('name')))); + $this->assertEquals(array('name' => 'foo'), $input->getArguments(), '->parse() is stateless'); + } + + /** + * @dataProvider provideOptions + */ + public function testParseOptions($input, $options, $expectedOptions, $message) + { + $input = new ArgvInput($input); + $input->bind(new InputDefinition($options)); + + $this->assertEquals($expectedOptions, $input->getOptions(), $message); + } + + public function provideOptions() + { + return array( + array( + array('cli.php', '--foo'), + array(new InputOption('foo')), + array('foo' => true), + '->parse() parses long options without a value' + ), + array( + array('cli.php', '--foo=bar'), + array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED)), + array('foo' => 'bar'), + '->parse() parses long options with a required value (with a = separator)' + ), + array( + array('cli.php', '--foo', 'bar'), + array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED)), + array('foo' => 'bar'), + '->parse() parses long options with a required value (with a space separator)' + ), + array( + array('cli.php', '-f'), + array(new InputOption('foo', 'f')), + array('foo' => true), + '->parse() parses short options without a value' + ), + array( + array('cli.php', '-fbar'), + array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED)), + array('foo' => 'bar'), + '->parse() parses short options with a required value (with no separator)' + ), + array( + array('cli.php', '-f', 'bar'), + array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED)), + array('foo' => 'bar'), + '->parse() parses short options with a required value (with a space separator)' + ), + array( + array('cli.php', '-f', ''), + array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL)), + array('foo' => ''), + '->parse() parses short options with an optional empty value' + ), + array( + array('cli.php', '-f', '', 'foo'), + array(new InputArgument('name'), new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL)), + array('foo' => ''), + '->parse() parses short options with an optional empty value followed by an argument' + ), + array( + array('cli.php', '-f', '', '-b'), + array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL), new InputOption('bar', 'b')), + array('foo' => '', 'bar' => true), + '->parse() parses short options with an optional empty value followed by an option' + ), + array( + array('cli.php', '-f', '-b', 'foo'), + array(new InputArgument('name'), new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL), new InputOption('bar', 'b')), + array('foo' => null, 'bar' => true), + '->parse() parses short options with an optional value which is not present' + ), + array( + array('cli.php', '-fb'), + array(new InputOption('foo', 'f'), new InputOption('bar', 'b')), + array('foo' => true, 'bar' => true), + '->parse() parses short options when they are aggregated as a single one' + ), + array( + array('cli.php', '-fb', 'bar'), + array(new InputOption('foo', 'f'), new InputOption('bar', 'b', InputOption::VALUE_REQUIRED)), + array('foo' => true, 'bar' => 'bar'), + '->parse() parses short options when they are aggregated as a single one and the last one has a required value' + ), + array( + array('cli.php', '-fb', 'bar'), + array(new InputOption('foo', 'f'), new InputOption('bar', 'b', InputOption::VALUE_OPTIONAL)), + array('foo' => true, 'bar' => 'bar'), + '->parse() parses short options when they are aggregated as a single one and the last one has an optional value' + ), + array( + array('cli.php', '-fbbar'), + array(new InputOption('foo', 'f'), new InputOption('bar', 'b', InputOption::VALUE_OPTIONAL)), + array('foo' => true, 'bar' => 'bar'), + '->parse() parses short options when they are aggregated as a single one and the last one has an optional value with no separator' + ), + array( + array('cli.php', '-fbbar'), + array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL), new InputOption('bar', 'b', InputOption::VALUE_OPTIONAL)), + array('foo' => 'bbar', 'bar' => null), + '->parse() parses short options when they are aggregated as a single one and one of them takes a value' + ) + ); + } + + /** + * @dataProvider provideInvalidInput + */ + public function testInvalidInput($argv, $definition, $expectedExceptionMessage) + { + $this->setExpectedException('RuntimeException', $expectedExceptionMessage); + + $input = new ArgvInput($argv); + $input->bind($definition); + } + + public function provideInvalidInput() + { + return array( + array( + array('cli.php', '--foo'), + new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED))), + 'The "--foo" option requires a value.' + ), + array( + array('cli.php', '-f'), + new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED))), + 'The "--foo" option requires a value.' + ), + array( + array('cli.php', '-ffoo'), + new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_NONE))), + 'The "-o" option does not exist.' + ), + array( + array('cli.php', '--foo=bar'), + new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_NONE))), + 'The "--foo" option does not accept a value.' + ), + array( + array('cli.php', 'foo', 'bar'), + new InputDefinition(), + 'Too many arguments.' + ), + array( + array('cli.php', '--foo'), + new InputDefinition(), + 'The "--foo" option does not exist.' + ), + array( + array('cli.php', '-f'), + new InputDefinition(), + 'The "-f" option does not exist.' + ), + array( + array('cli.php', '-1'), + new InputDefinition(array(new InputArgument('number'))), + 'The "-1" option does not exist.' + ) + ); + } + + public function testParseArrayArgument() + { + $input = new ArgvInput(array('cli.php', 'foo', 'bar', 'baz', 'bat')); + $input->bind(new InputDefinition(array(new InputArgument('name', InputArgument::IS_ARRAY)))); + + $this->assertEquals(array('name' => array('foo', 'bar', 'baz', 'bat')), $input->getArguments(), '->parse() parses array arguments'); + } + + public function testParseArrayOption() + { + $input = new ArgvInput(array('cli.php', '--name=foo', '--name=bar', '--name=baz')); + $input->bind(new InputDefinition(array(new InputOption('name', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY)))); + + $this->assertEquals(array('name' => array('foo', 'bar', 'baz')), $input->getOptions(), '->parse() parses array options ("--option=value" syntax)'); + + $input = new ArgvInput(array('cli.php', '--name', 'foo', '--name', 'bar', '--name', 'baz')); + $input->bind(new InputDefinition(array(new InputOption('name', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY)))); + $this->assertEquals(array('name' => array('foo', 'bar', 'baz')), $input->getOptions(), '->parse() parses array options ("--option value" syntax)'); + + $input = new ArgvInput(array('cli.php', '--name=foo', '--name=bar', '--name=')); + $input->bind(new InputDefinition(array(new InputOption('name', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY)))); + $this->assertSame(array('name' => array('foo', 'bar', null)), $input->getOptions(), '->parse() parses empty array options as null ("--option=value" syntax)'); + + $input = new ArgvInput(array('cli.php', '--name', 'foo', '--name', 'bar', '--name', '--anotherOption')); + $input->bind(new InputDefinition(array( + new InputOption('name', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY), + new InputOption('anotherOption', null, InputOption::VALUE_NONE), + ))); + $this->assertSame(array('name' => array('foo', 'bar', null), 'anotherOption' => true), $input->getOptions(), '->parse() parses empty array options as null ("--option value" syntax)'); + } + + public function testParseNegativeNumberAfterDoubleDash() + { + $input = new ArgvInput(array('cli.php', '--', '-1')); + $input->bind(new InputDefinition(array(new InputArgument('number')))); + $this->assertEquals(array('number' => '-1'), $input->getArguments(), '->parse() parses arguments with leading dashes as arguments after having encountered a double-dash sequence'); + + $input = new ArgvInput(array('cli.php', '-f', 'bar', '--', '-1')); + $input->bind(new InputDefinition(array(new InputArgument('number'), new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL)))); + $this->assertEquals(array('foo' => 'bar'), $input->getOptions(), '->parse() parses arguments with leading dashes as options before having encountered a double-dash sequence'); + $this->assertEquals(array('number' => '-1'), $input->getArguments(), '->parse() parses arguments with leading dashes as arguments after having encountered a double-dash sequence'); + } + + public function testParseEmptyStringArgument() + { + $input = new ArgvInput(array('cli.php', '-f', 'bar', '')); + $input->bind(new InputDefinition(array(new InputArgument('empty'), new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL)))); + + $this->assertEquals(array('empty' => ''), $input->getArguments(), '->parse() parses empty string arguments'); + } + + public function testGetFirstArgument() + { + $input = new ArgvInput(array('cli.php', '-fbbar')); + $this->assertEquals('', $input->getFirstArgument(), '->getFirstArgument() returns the first argument from the raw input'); + + $input = new ArgvInput(array('cli.php', '-fbbar', 'foo')); + $this->assertEquals('foo', $input->getFirstArgument(), '->getFirstArgument() returns the first argument from the raw input'); + } + + public function testHasParameterOption() + { + $input = new ArgvInput(array('cli.php', '-f', 'foo')); + $this->assertTrue($input->hasParameterOption('-f'), '->hasParameterOption() returns true if the given short option is in the raw input'); + + $input = new ArgvInput(array('cli.php', '--foo', 'foo')); + $this->assertTrue($input->hasParameterOption('--foo'), '->hasParameterOption() returns true if the given short option is in the raw input'); + + $input = new ArgvInput(array('cli.php', 'foo')); + $this->assertFalse($input->hasParameterOption('--foo'), '->hasParameterOption() returns false if the given short option is not in the raw input'); + + $input = new ArgvInput(array('cli.php', '--foo=bar')); + $this->assertTrue($input->hasParameterOption('--foo'), '->hasParameterOption() returns true if the given option with provided value is in the raw input'); + } + + public function testToString() + { + $input = new ArgvInput(array('cli.php', '-f', 'foo')); + $this->assertEquals('-f foo', (string) $input); + + $input = new ArgvInput(array('cli.php', '-f', '--bar=foo', 'a b c d', "A\nB'C")); + $this->assertEquals('-f --bar=foo '.escapeshellarg('a b c d').' '.escapeshellarg("A\nB'C"), (string) $input); + } + + /** + * @dataProvider provideGetParameterOptionValues + */ + public function testGetParameterOptionEqualSign($argv, $key, $expected) + { + $input = new ArgvInput($argv); + $this->assertEquals($expected, $input->getParameterOption($key), '->getParameterOption() returns the expected value'); + } + + public function provideGetParameterOptionValues() + { + return array( + array(array('app/console', 'foo:bar', '-e', 'dev'), '-e', 'dev'), + array(array('app/console', 'foo:bar', '--env=dev'), '--env', 'dev'), + array(array('app/console', 'foo:bar', '-e', 'dev'), array('-e', '--env'), 'dev'), + array(array('app/console', 'foo:bar', '--env=dev'), array('-e', '--env'), 'dev'), + array(array('app/console', 'foo:bar', '--env=dev', '--en=1'), array('--en'), '1'), + ); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Input/ArrayInputTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Input/ArrayInputTest.php new file mode 100644 index 0000000..73f2e33 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Input/ArrayInputTest.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Input; + +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; + +class ArrayInputTest extends \PHPUnit_Framework_TestCase +{ + public function testGetFirstArgument() + { + $input = new ArrayInput(array()); + $this->assertNull($input->getFirstArgument(), '->getFirstArgument() returns null if no argument were passed'); + $input = new ArrayInput(array('name' => 'Fabien')); + $this->assertEquals('Fabien', $input->getFirstArgument(), '->getFirstArgument() returns the first passed argument'); + $input = new ArrayInput(array('--foo' => 'bar', 'name' => 'Fabien')); + $this->assertEquals('Fabien', $input->getFirstArgument(), '->getFirstArgument() returns the first passed argument'); + } + + public function testHasParameterOption() + { + $input = new ArrayInput(array('name' => 'Fabien', '--foo' => 'bar')); + $this->assertTrue($input->hasParameterOption('--foo'), '->hasParameterOption() returns true if an option is present in the passed parameters'); + $this->assertFalse($input->hasParameterOption('--bar'), '->hasParameterOption() returns false if an option is not present in the passed parameters'); + + $input = new ArrayInput(array('--foo')); + $this->assertTrue($input->hasParameterOption('--foo'), '->hasParameterOption() returns true if an option is present in the passed parameters'); + } + + public function testParseArguments() + { + $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name')))); + + $this->assertEquals(array('name' => 'foo'), $input->getArguments(), '->parse() parses required arguments'); + } + + /** + * @dataProvider provideOptions + */ + public function testParseOptions($input, $options, $expectedOptions, $message) + { + $input = new ArrayInput($input, new InputDefinition($options)); + + $this->assertEquals($expectedOptions, $input->getOptions(), $message); + } + + public function provideOptions() + { + return array( + array( + array('--foo' => 'bar'), + array(new InputOption('foo')), + array('foo' => 'bar'), + '->parse() parses long options' + ), + array( + array('--foo' => 'bar'), + array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL, '', 'default')), + array('foo' => 'bar'), + '->parse() parses long options with a default value' + ), + array( + array('--foo' => null), + array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL, '', 'default')), + array('foo' => 'default'), + '->parse() parses long options with a default value' + ), + array( + array('-f' => 'bar'), + array(new InputOption('foo', 'f')), + array('foo' => 'bar'), + '->parse() parses short options' + ) + ); + } + + /** + * @dataProvider provideInvalidInput + */ + public function testParseInvalidInput($parameters, $definition, $expectedExceptionMessage) + { + $this->setExpectedException('InvalidArgumentException', $expectedExceptionMessage); + + new ArrayInput($parameters, $definition); + } + + public function provideInvalidInput() + { + return array( + array( + array('foo' => 'foo'), + new InputDefinition(array(new InputArgument('name'))), + 'The "foo" argument does not exist.' + ), + array( + array('--foo' => null), + new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED))), + 'The "--foo" option requires a value.' + ), + array( + array('--foo' => 'foo'), + new InputDefinition(), + 'The "--foo" option does not exist.' + ), + array( + array('-o' => 'foo'), + new InputDefinition(), + 'The "-o" option does not exist.' + ) + ); + } + + public function testToString() + { + $input = new ArrayInput(array('-f' => null, '-b' => 'bar', '--foo' => 'b a z', '--lala' => null, 'test' => 'Foo', 'test2' => "A\nB'C")); + $this->assertEquals('-f -b=bar --foo='.escapeshellarg('b a z').' --lala Foo '.escapeshellarg("A\nB'C"), (string) $input); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Input/InputArgumentTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Input/InputArgumentTest.php new file mode 100644 index 0000000..3bfc796 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Input/InputArgumentTest.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Input; + +use Symfony\Component\Console\Input\InputArgument; + +class InputArgumentTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $argument = new InputArgument('foo'); + $this->assertEquals('foo', $argument->getName(), '__construct() takes a name as its first argument'); + } + + public function testModes() + { + $argument = new InputArgument('foo'); + $this->assertFalse($argument->isRequired(), '__construct() gives a "InputArgument::OPTIONAL" mode by default'); + + $argument = new InputArgument('foo', null); + $this->assertFalse($argument->isRequired(), '__construct() can take "InputArgument::OPTIONAL" as its mode'); + + $argument = new InputArgument('foo', InputArgument::OPTIONAL); + $this->assertFalse($argument->isRequired(), '__construct() can take "InputArgument::OPTIONAL" as its mode'); + + $argument = new InputArgument('foo', InputArgument::REQUIRED); + $this->assertTrue($argument->isRequired(), '__construct() can take "InputArgument::REQUIRED" as its mode'); + } + + /** + * @dataProvider provideInvalidModes + */ + public function testInvalidModes($mode) + { + $this->setExpectedException('InvalidArgumentException', sprintf('Argument mode "%s" is not valid.', $mode)); + + new InputArgument('foo', $mode); + } + + public function provideInvalidModes() + { + return array( + array('ANOTHER_ONE'), + array(-1) + ); + } + + public function testIsArray() + { + $argument = new InputArgument('foo', InputArgument::IS_ARRAY); + $this->assertTrue($argument->isArray(), '->isArray() returns true if the argument can be an array'); + $argument = new InputArgument('foo', InputArgument::OPTIONAL | InputArgument::IS_ARRAY); + $this->assertTrue($argument->isArray(), '->isArray() returns true if the argument can be an array'); + $argument = new InputArgument('foo', InputArgument::OPTIONAL); + $this->assertFalse($argument->isArray(), '->isArray() returns false if the argument can not be an array'); + } + + public function testGetDescription() + { + $argument = new InputArgument('foo', null, 'Some description'); + $this->assertEquals('Some description', $argument->getDescription(), '->getDescription() return the message description'); + } + + public function testGetDefault() + { + $argument = new InputArgument('foo', InputArgument::OPTIONAL, '', 'default'); + $this->assertEquals('default', $argument->getDefault(), '->getDefault() return the default value'); + } + + public function testSetDefault() + { + $argument = new InputArgument('foo', InputArgument::OPTIONAL, '', 'default'); + $argument->setDefault(null); + $this->assertNull($argument->getDefault(), '->setDefault() can reset the default value by passing null'); + $argument->setDefault('another'); + $this->assertEquals('another', $argument->getDefault(), '->setDefault() changes the default value'); + + $argument = new InputArgument('foo', InputArgument::OPTIONAL | InputArgument::IS_ARRAY); + $argument->setDefault(array(1, 2)); + $this->assertEquals(array(1, 2), $argument->getDefault(), '->setDefault() changes the default value'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Cannot set a default value except for InputArgument::OPTIONAL mode. + */ + public function testSetDefaultWithRequiredArgument() + { + $argument = new InputArgument('foo', InputArgument::REQUIRED); + $argument->setDefault('default'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage A default value for an array argument must be an array. + */ + public function testSetDefaultWithArrayArgument() + { + $argument = new InputArgument('foo', InputArgument::IS_ARRAY); + $argument->setDefault('default'); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Input/InputDefinitionTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Input/InputDefinitionTest.php new file mode 100644 index 0000000..5cf5011 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Input/InputDefinitionTest.php @@ -0,0 +1,420 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Input; + +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; + +class InputDefinitionTest extends \PHPUnit_Framework_TestCase +{ + protected static $fixtures; + + protected $foo, $bar, $foo1, $foo2; + + public static function setUpBeforeClass() + { + self::$fixtures = __DIR__.'/../Fixtures/'; + } + + public function testConstructorArguments() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $this->assertEquals(array(), $definition->getArguments(), '__construct() creates a new InputDefinition object'); + + $definition = new InputDefinition(array($this->foo, $this->bar)); + $this->assertEquals(array('foo' => $this->foo, 'bar' => $this->bar), $definition->getArguments(), '__construct() takes an array of InputArgument objects as its first argument'); + } + + public function testConstructorOptions() + { + $this->initializeOptions(); + + $definition = new InputDefinition(); + $this->assertEquals(array(), $definition->getOptions(), '__construct() creates a new InputDefinition object'); + + $definition = new InputDefinition(array($this->foo, $this->bar)); + $this->assertEquals(array('foo' => $this->foo, 'bar' => $this->bar), $definition->getOptions(), '__construct() takes an array of InputOption objects as its first argument'); + } + + public function testSetArguments() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->setArguments(array($this->foo)); + $this->assertEquals(array('foo' => $this->foo), $definition->getArguments(), '->setArguments() sets the array of InputArgument objects'); + $definition->setArguments(array($this->bar)); + + $this->assertEquals(array('bar' => $this->bar), $definition->getArguments(), '->setArguments() clears all InputArgument objects'); + } + + public function testAddArguments() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArguments(array($this->foo)); + $this->assertEquals(array('foo' => $this->foo), $definition->getArguments(), '->addArguments() adds an array of InputArgument objects'); + $definition->addArguments(array($this->bar)); + $this->assertEquals(array('foo' => $this->foo, 'bar' => $this->bar), $definition->getArguments(), '->addArguments() does not clear existing InputArgument objects'); + } + + public function testAddArgument() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArgument($this->foo); + $this->assertEquals(array('foo' => $this->foo), $definition->getArguments(), '->addArgument() adds a InputArgument object'); + $definition->addArgument($this->bar); + $this->assertEquals(array('foo' => $this->foo, 'bar' => $this->bar), $definition->getArguments(), '->addArgument() adds a InputArgument object'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage An argument with name "foo" already exists. + */ + public function testArgumentsMustHaveDifferentNames() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArgument($this->foo); + $definition->addArgument($this->foo1); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Cannot add an argument after an array argument. + */ + public function testArrayArgumentHasToBeLast() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArgument(new InputArgument('fooarray', InputArgument::IS_ARRAY)); + $definition->addArgument(new InputArgument('anotherbar')); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Cannot add a required argument after an optional one. + */ + public function testRequiredArgumentCannotFollowAnOptionalOne() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArgument($this->foo); + $definition->addArgument($this->foo2); + } + + public function testGetArgument() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArguments(array($this->foo)); + $this->assertEquals($this->foo, $definition->getArgument('foo'), '->getArgument() returns a InputArgument by its name'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "bar" argument does not exist. + */ + public function testGetInvalidArgument() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArguments(array($this->foo)); + $definition->getArgument('bar'); + } + + public function testHasArgument() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArguments(array($this->foo)); + + $this->assertTrue($definition->hasArgument('foo'), '->hasArgument() returns true if a InputArgument exists for the given name'); + $this->assertFalse($definition->hasArgument('bar'), '->hasArgument() returns false if a InputArgument exists for the given name'); + } + + public function testGetArgumentRequiredCount() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArgument($this->foo2); + $this->assertEquals(1, $definition->getArgumentRequiredCount(), '->getArgumentRequiredCount() returns the number of required arguments'); + $definition->addArgument($this->foo); + $this->assertEquals(1, $definition->getArgumentRequiredCount(), '->getArgumentRequiredCount() returns the number of required arguments'); + } + + public function testGetArgumentCount() + { + $this->initializeArguments(); + + $definition = new InputDefinition(); + $definition->addArgument($this->foo2); + $this->assertEquals(1, $definition->getArgumentCount(), '->getArgumentCount() returns the number of arguments'); + $definition->addArgument($this->foo); + $this->assertEquals(2, $definition->getArgumentCount(), '->getArgumentCount() returns the number of arguments'); + } + + public function testGetArgumentDefaults() + { + $definition = new InputDefinition(array( + new InputArgument('foo1', InputArgument::OPTIONAL), + new InputArgument('foo2', InputArgument::OPTIONAL, '', 'default'), + new InputArgument('foo3', InputArgument::OPTIONAL | InputArgument::IS_ARRAY), + // new InputArgument('foo4', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, '', array(1, 2)), + )); + $this->assertEquals(array('foo1' => null, 'foo2' => 'default', 'foo3' => array()), $definition->getArgumentDefaults(), '->getArgumentDefaults() return the default values for each argument'); + + $definition = new InputDefinition(array( + new InputArgument('foo4', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, '', array(1, 2)), + )); + $this->assertEquals(array('foo4' => array(1, 2)), $definition->getArgumentDefaults(), '->getArgumentDefaults() return the default values for each argument'); + } + + public function testSetOptions() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->foo)); + $this->assertEquals(array('foo' => $this->foo), $definition->getOptions(), '->setOptions() sets the array of InputOption objects'); + $definition->setOptions(array($this->bar)); + $this->assertEquals(array('bar' => $this->bar), $definition->getOptions(), '->setOptions() clears all InputOption objects'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "-f" option does not exist. + */ + public function testSetOptionsClearsOptions() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->foo)); + $definition->setOptions(array($this->bar)); + $definition->getOptionForShortcut('f'); + } + + public function testAddOptions() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->foo)); + $this->assertEquals(array('foo' => $this->foo), $definition->getOptions(), '->addOptions() adds an array of InputOption objects'); + $definition->addOptions(array($this->bar)); + $this->assertEquals(array('foo' => $this->foo, 'bar' => $this->bar), $definition->getOptions(), '->addOptions() does not clear existing InputOption objects'); + } + + public function testAddOption() + { + $this->initializeOptions(); + + $definition = new InputDefinition(); + $definition->addOption($this->foo); + $this->assertEquals(array('foo' => $this->foo), $definition->getOptions(), '->addOption() adds a InputOption object'); + $definition->addOption($this->bar); + $this->assertEquals(array('foo' => $this->foo, 'bar' => $this->bar), $definition->getOptions(), '->addOption() adds a InputOption object'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage An option named "foo" already exists. + */ + public function testAddDuplicateOption() + { + $this->initializeOptions(); + + $definition = new InputDefinition(); + $definition->addOption($this->foo); + $definition->addOption($this->foo2); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage An option with shortcut "f" already exists. + */ + public function testAddDuplicateShortcutOption() + { + $this->initializeOptions(); + + $definition = new InputDefinition(); + $definition->addOption($this->foo); + $definition->addOption($this->foo1); + } + + public function testGetOption() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->foo)); + $this->assertEquals($this->foo, $definition->getOption('foo'), '->getOption() returns a InputOption by its name'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "--bar" option does not exist. + */ + public function testGetInvalidOption() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->foo)); + $definition->getOption('bar'); + } + + public function testHasOption() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->foo)); + $this->assertTrue($definition->hasOption('foo'), '->hasOption() returns true if a InputOption exists for the given name'); + $this->assertFalse($definition->hasOption('bar'), '->hasOption() returns false if a InputOption exists for the given name'); + } + + public function testHasShortcut() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->foo)); + $this->assertTrue($definition->hasShortcut('f'), '->hasShortcut() returns true if a InputOption exists for the given shortcut'); + $this->assertFalse($definition->hasShortcut('b'), '->hasShortcut() returns false if a InputOption exists for the given shortcut'); + } + + public function testGetOptionForShortcut() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->foo)); + $this->assertEquals($this->foo, $definition->getOptionForShortcut('f'), '->getOptionForShortcut() returns a InputOption by its shortcut'); + } + + public function testGetOptionForMultiShortcut() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->multi)); + $this->assertEquals($this->multi, $definition->getOptionForShortcut('m'), '->getOptionForShortcut() returns a InputOption by its shortcut'); + $this->assertEquals($this->multi, $definition->getOptionForShortcut('mmm'), '->getOptionForShortcut() returns a InputOption by its shortcut'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "-l" option does not exist. + */ + public function testGetOptionForInvalidShortcut() + { + $this->initializeOptions(); + + $definition = new InputDefinition(array($this->foo)); + $definition->getOptionForShortcut('l'); + } + + public function testGetOptionDefaults() + { + $definition = new InputDefinition(array( + new InputOption('foo1', null, InputOption::VALUE_NONE), + new InputOption('foo2', null, InputOption::VALUE_REQUIRED), + new InputOption('foo3', null, InputOption::VALUE_REQUIRED, '', 'default'), + new InputOption('foo4', null, InputOption::VALUE_OPTIONAL), + new InputOption('foo5', null, InputOption::VALUE_OPTIONAL, '', 'default'), + new InputOption('foo6', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY), + new InputOption('foo7', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, '', array(1, 2)), + )); + $defaults = array( + 'foo1' => null, + 'foo2' => null, + 'foo3' => 'default', + 'foo4' => null, + 'foo5' => 'default', + 'foo6' => array(), + 'foo7' => array(1, 2), + ); + $this->assertEquals($defaults, $definition->getOptionDefaults(), '->getOptionDefaults() returns the default values for all options'); + } + + public function testGetSynopsis() + { + $definition = new InputDefinition(array(new InputOption('foo'))); + $this->assertEquals('[--foo]', $definition->getSynopsis(), '->getSynopsis() returns a synopsis of arguments and options'); + $definition = new InputDefinition(array(new InputOption('foo', 'f'))); + $this->assertEquals('[-f|--foo]', $definition->getSynopsis(), '->getSynopsis() returns a synopsis of arguments and options'); + $definition = new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_REQUIRED))); + $this->assertEquals('[-f|--foo="..."]', $definition->getSynopsis(), '->getSynopsis() returns a synopsis of arguments and options'); + $definition = new InputDefinition(array(new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL))); + $this->assertEquals('[-f|--foo[="..."]]', $definition->getSynopsis(), '->getSynopsis() returns a synopsis of arguments and options'); + + $definition = new InputDefinition(array(new InputArgument('foo'))); + $this->assertEquals('[foo]', $definition->getSynopsis(), '->getSynopsis() returns a synopsis of arguments and options'); + $definition = new InputDefinition(array(new InputArgument('foo', InputArgument::REQUIRED))); + $this->assertEquals('foo', $definition->getSynopsis(), '->getSynopsis() returns a synopsis of arguments and options'); + $definition = new InputDefinition(array(new InputArgument('foo', InputArgument::IS_ARRAY))); + $this->assertEquals('[foo1] ... [fooN]', $definition->getSynopsis(), '->getSynopsis() returns a synopsis of arguments and options'); + $definition = new InputDefinition(array(new InputArgument('foo', InputArgument::REQUIRED | InputArgument::IS_ARRAY))); + $this->assertEquals('foo1 ... [fooN]', $definition->getSynopsis(), '->getSynopsis() returns a synopsis of arguments and options'); + } + + public function testAsText() + { + $definition = new InputDefinition(array( + new InputArgument('foo', InputArgument::OPTIONAL, 'The foo argument'), + new InputArgument('baz', InputArgument::OPTIONAL, 'The baz argument', true), + new InputArgument('bar', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'The bar argument', array('http://foo.com/')), + new InputOption('foo', 'f', InputOption::VALUE_REQUIRED, 'The foo option'), + new InputOption('baz', null, InputOption::VALUE_OPTIONAL, 'The baz option', false), + new InputOption('bar', 'b', InputOption::VALUE_OPTIONAL, 'The bar option', 'bar'), + new InputOption('qux', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The qux option', array('http://foo.com/', 'bar')), + new InputOption('qux2', '', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The qux2 option', array('foo' => 'bar')), + )); + $this->assertStringEqualsFile(self::$fixtures.'/definition_astext.txt', $definition->asText(), '->asText() returns a textual representation of the InputDefinition'); + } + + public function testAsXml() + { + $definition = new InputDefinition(array( + new InputArgument('foo', InputArgument::OPTIONAL, 'The foo argument'), + new InputArgument('baz', InputArgument::OPTIONAL, 'The baz argument', true), + new InputArgument('bar', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'The bar argument', array('bar')), + new InputOption('foo', 'f', InputOption::VALUE_REQUIRED, 'The foo option'), + new InputOption('baz', null, InputOption::VALUE_OPTIONAL, 'The baz option', false), + new InputOption('bar', 'b', InputOption::VALUE_OPTIONAL, 'The bar option', 'bar'), + )); + $this->assertXmlStringEqualsXmlFile(self::$fixtures.'/definition_asxml.txt', $definition->asXml(), '->asText() returns a textual representation of the InputDefinition'); + } + + protected function initializeArguments() + { + $this->foo = new InputArgument('foo'); + $this->bar = new InputArgument('bar'); + $this->foo1 = new InputArgument('foo'); + $this->foo2 = new InputArgument('foo2', InputArgument::REQUIRED); + } + + protected function initializeOptions() + { + $this->foo = new InputOption('foo', 'f'); + $this->bar = new InputOption('bar', 'b'); + $this->foo1 = new InputOption('fooBis', 'f'); + $this->foo2 = new InputOption('foo', 'p'); + $this->multi = new InputOption('multi', 'm|mm|mmm'); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Input/InputOptionTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Input/InputOptionTest.php new file mode 100644 index 0000000..5817e8f --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Input/InputOptionTest.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Input; + +use Symfony\Component\Console\Input\InputOption; + +class InputOptionTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $option = new InputOption('foo'); + $this->assertEquals('foo', $option->getName(), '__construct() takes a name as its first argument'); + $option = new InputOption('--foo'); + $this->assertEquals('foo', $option->getName(), '__construct() removes the leading -- of the option name'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value. + */ + public function testArrayModeWithoutValue() + { + new InputOption('foo', 'f', InputOption::VALUE_IS_ARRAY); + } + + public function testShortcut() + { + $option = new InputOption('foo', 'f'); + $this->assertEquals('f', $option->getShortcut(), '__construct() can take a shortcut as its second argument'); + $option = new InputOption('foo', '-f|-ff|fff'); + $this->assertEquals('f|ff|fff', $option->getShortcut(), '__construct() removes the leading - of the shortcuts'); + $option = new InputOption('foo', array('f', 'ff', '-fff')); + $this->assertEquals('f|ff|fff', $option->getShortcut(), '__construct() removes the leading - of the shortcuts'); + $option = new InputOption('foo'); + $this->assertNull($option->getShortcut(), '__construct() makes the shortcut null by default'); + } + + public function testModes() + { + $option = new InputOption('foo', 'f'); + $this->assertFalse($option->acceptValue(), '__construct() gives a "InputOption::VALUE_NONE" mode by default'); + $this->assertFalse($option->isValueRequired(), '__construct() gives a "InputOption::VALUE_NONE" mode by default'); + $this->assertFalse($option->isValueOptional(), '__construct() gives a "InputOption::VALUE_NONE" mode by default'); + + $option = new InputOption('foo', 'f', null); + $this->assertFalse($option->acceptValue(), '__construct() can take "InputOption::VALUE_NONE" as its mode'); + $this->assertFalse($option->isValueRequired(), '__construct() can take "InputOption::VALUE_NONE" as its mode'); + $this->assertFalse($option->isValueOptional(), '__construct() can take "InputOption::VALUE_NONE" as its mode'); + + $option = new InputOption('foo', 'f', InputOption::VALUE_NONE); + $this->assertFalse($option->acceptValue(), '__construct() can take "InputOption::VALUE_NONE" as its mode'); + $this->assertFalse($option->isValueRequired(), '__construct() can take "InputOption::VALUE_NONE" as its mode'); + $this->assertFalse($option->isValueOptional(), '__construct() can take "InputOption::VALUE_NONE" as its mode'); + + $option = new InputOption('foo', 'f', InputOption::VALUE_REQUIRED); + $this->assertTrue($option->acceptValue(), '__construct() can take "InputOption::VALUE_REQUIRED" as its mode'); + $this->assertTrue($option->isValueRequired(), '__construct() can take "InputOption::VALUE_REQUIRED" as its mode'); + $this->assertFalse($option->isValueOptional(), '__construct() can take "InputOption::VALUE_REQUIRED" as its mode'); + + $option = new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL); + $this->assertTrue($option->acceptValue(), '__construct() can take "InputOption::VALUE_OPTIONAL" as its mode'); + $this->assertFalse($option->isValueRequired(), '__construct() can take "InputOption::VALUE_OPTIONAL" as its mode'); + $this->assertTrue($option->isValueOptional(), '__construct() can take "InputOption::VALUE_OPTIONAL" as its mode'); + } + + /** + * @dataProvider provideInvalidModes + */ + public function testInvalidModes($mode) + { + $this->setExpectedException('InvalidArgumentException', sprintf('Option mode "%s" is not valid.', $mode)); + + new InputOption('foo', 'f', $mode); + } + + public function provideInvalidModes() + { + return array( + array('ANOTHER_ONE'), + array(-1) + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testEmptyNameIsInvalid() + { + new InputOption(''); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testDoubleDashNameIsInvalid() + { + new InputOption('--'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSingleDashOptionIsInvalid() + { + new InputOption('foo', '-'); + } + + public function testIsArray() + { + $option = new InputOption('foo', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY); + $this->assertTrue($option->isArray(), '->isArray() returns true if the option can be an array'); + $option = new InputOption('foo', null, InputOption::VALUE_NONE); + $this->assertFalse($option->isArray(), '->isArray() returns false if the option can not be an array'); + } + + public function testGetDescription() + { + $option = new InputOption('foo', 'f', null, 'Some description'); + $this->assertEquals('Some description', $option->getDescription(), '->getDescription() returns the description message'); + } + + public function testGetDefault() + { + $option = new InputOption('foo', null, InputOption::VALUE_OPTIONAL, '', 'default'); + $this->assertEquals('default', $option->getDefault(), '->getDefault() returns the default value'); + + $option = new InputOption('foo', null, InputOption::VALUE_REQUIRED, '', 'default'); + $this->assertEquals('default', $option->getDefault(), '->getDefault() returns the default value'); + + $option = new InputOption('foo', null, InputOption::VALUE_REQUIRED); + $this->assertNull($option->getDefault(), '->getDefault() returns null if no default value is configured'); + + $option = new InputOption('foo', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY); + $this->assertEquals(array(), $option->getDefault(), '->getDefault() returns an empty array if option is an array'); + + $option = new InputOption('foo', null, InputOption::VALUE_NONE); + $this->assertFalse($option->getDefault(), '->getDefault() returns false if the option does not take a value'); + } + + public function testSetDefault() + { + $option = new InputOption('foo', null, InputOption::VALUE_REQUIRED, '', 'default'); + $option->setDefault(null); + $this->assertNull($option->getDefault(), '->setDefault() can reset the default value by passing null'); + $option->setDefault('another'); + $this->assertEquals('another', $option->getDefault(), '->setDefault() changes the default value'); + + $option = new InputOption('foo', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY); + $option->setDefault(array(1, 2)); + $this->assertEquals(array(1, 2), $option->getDefault(), '->setDefault() changes the default value'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Cannot set a default value when using InputOption::VALUE_NONE mode. + */ + public function testDefaultValueWithValueNoneMode() + { + $option = new InputOption('foo', 'f', InputOption::VALUE_NONE); + $option->setDefault('default'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage A default value for an array option must be an array. + */ + public function testDefaultValueWithIsArrayMode() + { + $option = new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY); + $option->setDefault('default'); + } + + public function testEquals() + { + $option = new InputOption('foo', 'f', null, 'Some description'); + $option2 = new InputOption('foo', 'f', null, 'Alternative description'); + $this->assertTrue($option->equals($option2)); + + $option = new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL, 'Some description'); + $option2 = new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL, 'Some description', true); + $this->assertFalse($option->equals($option2)); + + $option = new InputOption('foo', 'f', null, 'Some description'); + $option2 = new InputOption('bar', 'f', null, 'Some description'); + $this->assertFalse($option->equals($option2)); + + $option = new InputOption('foo', 'f', null, 'Some description'); + $option2 = new InputOption('foo', '', null, 'Some description'); + $this->assertFalse($option->equals($option2)); + + $option = new InputOption('foo', 'f', null, 'Some description'); + $option2 = new InputOption('foo', 'f', InputOption::VALUE_OPTIONAL, 'Some description'); + $this->assertFalse($option->equals($option2)); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Input/InputTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Input/InputTest.php new file mode 100644 index 0000000..0b3e38f --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Input/InputTest.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Input; + +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; + +class InputTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name')))); + $this->assertEquals('foo', $input->getArgument('name'), '->__construct() takes a InputDefinition as an argument'); + } + + public function testOptions() + { + $input = new ArrayInput(array('--name' => 'foo'), new InputDefinition(array(new InputOption('name')))); + $this->assertEquals('foo', $input->getOption('name'), '->getOption() returns the value for the given option'); + + $input->setOption('name', 'bar'); + $this->assertEquals('bar', $input->getOption('name'), '->setOption() sets the value for a given option'); + $this->assertEquals(array('name' => 'bar'), $input->getOptions(), '->getOptions() returns all option values'); + + $input = new ArrayInput(array('--name' => 'foo'), new InputDefinition(array(new InputOption('name'), new InputOption('bar', '', InputOption::VALUE_OPTIONAL, '', 'default')))); + $this->assertEquals('default', $input->getOption('bar'), '->getOption() returns the default value for optional options'); + $this->assertEquals(array('name' => 'foo', 'bar' => 'default'), $input->getOptions(), '->getOptions() returns all option values, even optional ones'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "foo" option does not exist. + */ + public function testSetInvalidOption() + { + $input = new ArrayInput(array('--name' => 'foo'), new InputDefinition(array(new InputOption('name'), new InputOption('bar', '', InputOption::VALUE_OPTIONAL, '', 'default')))); + $input->setOption('foo', 'bar'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "foo" option does not exist. + */ + public function testGetInvalidOption() + { + $input = new ArrayInput(array('--name' => 'foo'), new InputDefinition(array(new InputOption('name'), new InputOption('bar', '', InputOption::VALUE_OPTIONAL, '', 'default')))); + $input->getOption('foo'); + } + + public function testArguments() + { + $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name')))); + $this->assertEquals('foo', $input->getArgument('name'), '->getArgument() returns the value for the given argument'); + + $input->setArgument('name', 'bar'); + $this->assertEquals('bar', $input->getArgument('name'), '->setArgument() sets the value for a given argument'); + $this->assertEquals(array('name' => 'bar'), $input->getArguments(), '->getArguments() returns all argument values'); + + $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name'), new InputArgument('bar', InputArgument::OPTIONAL, '', 'default')))); + $this->assertEquals('default', $input->getArgument('bar'), '->getArgument() returns the default value for optional arguments'); + $this->assertEquals(array('name' => 'foo', 'bar' => 'default'), $input->getArguments(), '->getArguments() returns all argument values, even optional ones'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "foo" argument does not exist. + */ + public function testSetInvalidArgument() + { + $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name'), new InputArgument('bar', InputArgument::OPTIONAL, '', 'default')))); + $input->setArgument('foo', 'bar'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The "foo" argument does not exist. + */ + public function testGetInvalidArgument() + { + $input = new ArrayInput(array('name' => 'foo'), new InputDefinition(array(new InputArgument('name'), new InputArgument('bar', InputArgument::OPTIONAL, '', 'default')))); + $input->getArgument('foo'); + } + + /** + * @expectedException \RuntimeException + * @expectedExceptionMessage Not enough arguments. + */ + public function testValidateWithMissingArguments() + { + $input = new ArrayInput(array()); + $input->bind(new InputDefinition(array(new InputArgument('name', InputArgument::REQUIRED)))); + $input->validate(); + } + + public function testValidate() + { + $input = new ArrayInput(array('name' => 'foo')); + $input->bind(new InputDefinition(array(new InputArgument('name', InputArgument::REQUIRED)))); + + $this->assertNull($input->validate()); + } + + public function testSetGetInteractive() + { + $input = new ArrayInput(array()); + $this->assertTrue($input->isInteractive(), '->isInteractive() returns whether the input should be interactive or not'); + $input->setInteractive(false); + $this->assertFalse($input->isInteractive(), '->setInteractive() changes the interactive flag'); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Input/StringInputTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Input/StringInputTest.php new file mode 100644 index 0000000..b284320 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Input/StringInputTest.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Input; + +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Input\StringInput; + +class StringInputTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getTokenizeData + */ + public function testTokenize($input, $tokens, $message) + { + $input = new StringInput($input); + $r = new \ReflectionClass('Symfony\Component\Console\Input\ArgvInput'); + $p = $r->getProperty('tokens'); + $p->setAccessible(true); + $this->assertEquals($tokens, $p->getValue($input), $message); + } + + public function testInputOptionWithGivenString() + { + $definition = new InputDefinition( + array(new InputOption('foo', null, InputOption::VALUE_REQUIRED)) + ); + + // call to bind + $input = new StringInput('--foo=bar'); + $input->bind($definition); + $this->assertEquals('bar', $input->getOption('foo')); + + // definition in constructor + $input = new StringInput('--foo=bar', $definition); + $this->assertEquals('bar', $input->getOption('foo')); + } + + public function getTokenizeData() + { + return array( + array('', array(), '->tokenize() parses an empty string'), + array('foo', array('foo'), '->tokenize() parses arguments'), + array(' foo bar ', array('foo', 'bar'), '->tokenize() ignores whitespaces between arguments'), + array('"quoted"', array('quoted'), '->tokenize() parses quoted arguments'), + array("'quoted'", array('quoted'), '->tokenize() parses quoted arguments'), + array("'a\rb\nc\td'", array("a\rb\nc\td"), '->tokenize() parses whitespace chars in strings'), + array("'a'\r'b'\n'c'\t'd'", array('a','b','c','d'), '->tokenize() parses whitespace chars between args as spaces'), + array('\"quoted\"', array('"quoted"'), '->tokenize() parses escaped-quoted arguments'), + array("\'quoted\'", array('\'quoted\''), '->tokenize() parses escaped-quoted arguments'), + array('-a', array('-a'), '->tokenize() parses short options'), + array('-azc', array('-azc'), '->tokenize() parses aggregated short options'), + array('-awithavalue', array('-awithavalue'), '->tokenize() parses short options with a value'), + array('-a"foo bar"', array('-afoo bar'), '->tokenize() parses short options with a value'), + array('-a"foo bar""foo bar"', array('-afoo barfoo bar'), '->tokenize() parses short options with a value'), + array('-a\'foo bar\'', array('-afoo bar'), '->tokenize() parses short options with a value'), + array('-a\'foo bar\'\'foo bar\'', array('-afoo barfoo bar'), '->tokenize() parses short options with a value'), + array('-a\'foo bar\'"foo bar"', array('-afoo barfoo bar'), '->tokenize() parses short options with a value'), + array('--long-option', array('--long-option'), '->tokenize() parses long options'), + array('--long-option=foo', array('--long-option=foo'), '->tokenize() parses long options with a value'), + array('--long-option="foo bar"', array('--long-option=foo bar'), '->tokenize() parses long options with a value'), + array('--long-option="foo bar""another"', array('--long-option=foo baranother'), '->tokenize() parses long options with a value'), + array('--long-option=\'foo bar\'', array('--long-option=foo bar'), '->tokenize() parses long options with a value'), + array("--long-option='foo bar''another'", array("--long-option=foo baranother"), '->tokenize() parses long options with a value'), + array("--long-option='foo bar'\"another\"", array("--long-option=foo baranother"), '->tokenize() parses long options with a value'), + array('foo -a -ffoo --long bar', array('foo', '-a', '-ffoo', '--long', 'bar'), '->tokenize() parses when several arguments and options'), + ); + } + + public function testToString() + { + $input = new StringInput('-f foo'); + $this->assertEquals('-f foo', (string) $input); + + $input = new StringInput('-f --bar=foo "a b c d"'); + $this->assertEquals('-f --bar=foo '.escapeshellarg('a b c d'), (string) $input); + + $input = new StringInput('-f --bar=foo \'a b c d\' '."'A\nB\\'C'"); + $this->assertEquals('-f --bar=foo '.escapeshellarg('a b c d').' '.escapeshellarg("A\nB'C"), (string) $input); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Output/ConsoleOutputTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Output/ConsoleOutputTest.php new file mode 100644 index 0000000..7a3ede3 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Output/ConsoleOutputTest.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Output; + +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\Output; + +class ConsoleOutputTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $output = new ConsoleOutput(Output::VERBOSITY_QUIET, true); + $this->assertEquals(Output::VERBOSITY_QUIET, $output->getVerbosity(), '__construct() takes the verbosity as its first argument'); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Output/NullOutputTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Output/NullOutputTest.php new file mode 100644 index 0000000..b20ae4e --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Output/NullOutputTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Output; + +use Symfony\Component\Console\Output\NullOutput; +use Symfony\Component\Console\Output\OutputInterface; + +class NullOutputTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $output = new NullOutput(); + + ob_start(); + $output->write('foo'); + $buffer = ob_get_clean(); + + $this->assertSame('', $buffer, '->write() does nothing (at least nothing is printed)'); + $this->assertFalse($output->isDecorated(), '->isDecorated() returns false'); + } + + public function testVerbosity() + { + $output = new NullOutput(); + $this->assertSame(OutputInterface::VERBOSITY_QUIET, $output->getVerbosity(), '->getVerbosity() returns VERBOSITY_QUIET for NullOutput by default'); + + $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); + $this->assertSame(OutputInterface::VERBOSITY_QUIET, $output->getVerbosity(), '->getVerbosity() always returns VERBOSITY_QUIET for NullOutput'); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Output/OutputTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Output/OutputTest.php new file mode 100644 index 0000000..be58b72 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Output/OutputTest.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Output; + +use Symfony\Component\Console\Output\Output; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; + +class OutputTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $output = new TestOutput(Output::VERBOSITY_QUIET, true); + $this->assertEquals(Output::VERBOSITY_QUIET, $output->getVerbosity(), '__construct() takes the verbosity as its first argument'); + $this->assertTrue($output->isDecorated(), '__construct() takes the decorated flag as its second argument'); + } + + public function testSetIsDecorated() + { + $output = new TestOutput(); + $output->setDecorated(true); + $this->assertTrue($output->isDecorated(), 'setDecorated() sets the decorated flag'); + } + + public function testSetGetVerbosity() + { + $output = new TestOutput(); + $output->setVerbosity(Output::VERBOSITY_QUIET); + $this->assertEquals(Output::VERBOSITY_QUIET, $output->getVerbosity(), '->setVerbosity() sets the verbosity'); + } + + public function testWriteWithVerbosityQuiet() + { + $output = new TestOutput(Output::VERBOSITY_QUIET); + $output->writeln('foo'); + $this->assertEquals('', $output->output, '->writeln() outputs nothing if verbosity is set to VERBOSITY_QUIET'); + } + + public function testWriteAnArrayOfMessages() + { + $output = new TestOutput(); + $output->writeln(array('foo', 'bar')); + $this->assertEquals("foo\nbar\n", $output->output, '->writeln() can take an array of messages to output'); + } + + /** + * @dataProvider provideWriteArguments + */ + public function testWriteRawMessage($message, $type, $expectedOutput) + { + $output = new TestOutput(); + $output->writeln($message, $type); + $this->assertEquals($expectedOutput, $output->output); + } + + public function provideWriteArguments() + { + return array( + array('foo', Output::OUTPUT_RAW, "foo\n"), + array('foo', Output::OUTPUT_PLAIN, "foo\n"), + ); + } + + public function testWriteWithDecorationTurnedOff() + { + $output = new TestOutput(); + $output->setDecorated(false); + $output->writeln('foo'); + $this->assertEquals("foo\n", $output->output, '->writeln() strips decoration tags if decoration is set to false'); + } + + public function testWriteDecoratedMessage() + { + $fooStyle = new OutputFormatterStyle('yellow', 'red', array('blink')); + $output = new TestOutput(); + $output->getFormatter()->setStyle('FOO', $fooStyle); + $output->setDecorated(true); + $output->writeln('foo'); + $this->assertEquals("\033[33;41;5mfoo\033[0m\n", $output->output, '->writeln() decorates the output'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Unknown output type given (24) + */ + public function testWriteWithInvalidOutputType() + { + $output = new TestOutput(); + $output->writeln('foo', 24); + } + + public function testWriteWithInvalidStyle() + { + $output = new TestOutput(); + + $output->clear(); + $output->write('foo'); + $this->assertEquals('foo', $output->output, '->write() do nothing when a style does not exist'); + + $output->clear(); + $output->writeln('foo'); + $this->assertEquals("foo\n", $output->output, '->writeln() do nothing when a style does not exist'); + } +} + +class TestOutput extends Output +{ + public $output = ''; + + public function clear() + { + $this->output = ''; + } + + protected function doWrite($message, $newline) + { + $this->output .= $message.($newline ? "\n" : ''); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Output/StreamOutputTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Output/StreamOutputTest.php new file mode 100644 index 0000000..2fd4f61 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Output/StreamOutputTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Output; + +use Symfony\Component\Console\Output\Output; +use Symfony\Component\Console\Output\StreamOutput; + +class StreamOutputTest extends \PHPUnit_Framework_TestCase +{ + protected $stream; + + protected function setUp() + { + $this->stream = fopen('php://memory', 'a', false); + } + + protected function tearDown() + { + $this->stream = null; + } + + public function testConstructor() + { + $output = new StreamOutput($this->stream, Output::VERBOSITY_QUIET, true); + $this->assertEquals(Output::VERBOSITY_QUIET, $output->getVerbosity(), '__construct() takes the verbosity as its first argument'); + $this->assertTrue($output->isDecorated(), '__construct() takes the decorated flag as its second argument'); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The StreamOutput class needs a stream as its first argument. + */ + public function testStreamIsRequired() + { + new StreamOutput('foo'); + } + + public function testGetStream() + { + $output = new StreamOutput($this->stream); + $this->assertEquals($this->stream, $output->getStream(), '->getStream() returns the current stream'); + } + + public function testDoWrite() + { + $output = new StreamOutput($this->stream); + $output->writeln('foo'); + rewind($output->getStream()); + $this->assertEquals('foo'.PHP_EOL, stream_get_contents($output->getStream()), '->doWrite() writes to the stream'); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Tester/ApplicationTesterTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Tester/ApplicationTesterTest.php new file mode 100644 index 0000000..6ce30ab --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Tester/ApplicationTesterTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Tester; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Output\Output; +use Symfony\Component\Console\Tester\ApplicationTester; + +class ApplicationTesterTest extends \PHPUnit_Framework_TestCase +{ + protected $application; + protected $tester; + + protected function setUp() + { + $this->application = new Application(); + $this->application->setAutoExit(false); + $this->application->register('foo') + ->addArgument('foo') + ->setCode(function ($input, $output) { $output->writeln('foo'); }) + ; + + $this->tester = new ApplicationTester($this->application); + $this->tester->run(array('command' => 'foo', 'foo' => 'bar'), array('interactive' => false, 'decorated' => false, 'verbosity' => Output::VERBOSITY_VERBOSE)); + } + + protected function tearDown() + { + $this->application = null; + $this->tester = null; + } + + public function testRun() + { + $this->assertFalse($this->tester->getInput()->isInteractive(), '->execute() takes an interactive option'); + $this->assertFalse($this->tester->getOutput()->isDecorated(), '->execute() takes a decorated option'); + $this->assertEquals(Output::VERBOSITY_VERBOSE, $this->tester->getOutput()->getVerbosity(), '->execute() takes a verbosity option'); + } + + public function testGetInput() + { + $this->assertEquals('bar', $this->tester->getInput()->getArgument('foo'), '->getInput() returns the current input instance'); + } + + public function testGetOutput() + { + rewind($this->tester->getOutput()->getStream()); + $this->assertEquals('foo'.PHP_EOL, stream_get_contents($this->tester->getOutput()->getStream()), '->getOutput() returns the current output instance'); + } + + public function testGetDisplay() + { + $this->assertEquals('foo'.PHP_EOL, $this->tester->getDisplay(), '->getDisplay() returns the display of the last execution'); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/Tests/Tester/CommandTesterTest.php b/vendor/symfony/console/Symfony/Component/Console/Tests/Tester/CommandTesterTest.php new file mode 100644 index 0000000..da5bf84 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/Tests/Tester/CommandTesterTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tests\Tester; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Output\Output; +use Symfony\Component\Console\Tester\CommandTester; + +class CommandTesterTest extends \PHPUnit_Framework_TestCase +{ + protected $command; + protected $tester; + + protected function setUp() + { + $this->command = new Command('foo'); + $this->command->addArgument('command'); + $this->command->addArgument('foo'); + $this->command->setCode(function ($input, $output) { $output->writeln('foo'); }); + + $this->tester = new CommandTester($this->command); + $this->tester->execute(array('foo' => 'bar'), array('interactive' => false, 'decorated' => false, 'verbosity' => Output::VERBOSITY_VERBOSE)); + } + + protected function tearDown() + { + $this->command = null; + $this->tester = null; + } + + public function testExecute() + { + $this->assertFalse($this->tester->getInput()->isInteractive(), '->execute() takes an interactive option'); + $this->assertFalse($this->tester->getOutput()->isDecorated(), '->execute() takes a decorated option'); + $this->assertEquals(Output::VERBOSITY_VERBOSE, $this->tester->getOutput()->getVerbosity(), '->execute() takes a verbosity option'); + } + + public function testGetInput() + { + $this->assertEquals('bar', $this->tester->getInput()->getArgument('foo'), '->getInput() returns the current input instance'); + } + + public function testGetOutput() + { + rewind($this->tester->getOutput()->getStream()); + $this->assertEquals('foo'.PHP_EOL, stream_get_contents($this->tester->getOutput()->getStream()), '->getOutput() returns the current output instance'); + } + + public function testGetDisplay() + { + $this->assertEquals('foo'.PHP_EOL, $this->tester->getDisplay(), '->getDisplay() returns the display of the last execution'); + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/composer.json b/vendor/symfony/console/Symfony/Component/Console/composer.json new file mode 100644 index 0000000..472b4f2 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/console", + "type": "library", + "description": "Symfony Console Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/event-dispatcher": "~2.1" + }, + "suggest": { + "symfony/event-dispatcher": "" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\Console\\": "" } + }, + "target-dir": "Symfony/Component/Console", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + } +} diff --git a/vendor/symfony/console/Symfony/Component/Console/phpunit.xml.dist b/vendor/symfony/console/Symfony/Component/Console/phpunit.xml.dist new file mode 100644 index 0000000..8a7edd4 --- /dev/null +++ b/vendor/symfony/console/Symfony/Component/Console/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/.gitignore b/vendor/symfony/css-selector/Symfony/Component/CssSelector/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/CHANGELOG.md b/vendor/symfony/css-selector/Symfony/Component/CssSelector/CHANGELOG.md new file mode 100644 index 0000000..be10abe --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +2.1.0 +----- + + * none diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/CssSelector.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/CssSelector.php new file mode 100644 index 0000000..7a12a8b --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/CssSelector.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector; + +use Symfony\Component\CssSelector\Parser\Shortcut\ClassParser; +use Symfony\Component\CssSelector\Parser\Shortcut\ElementParser; +use Symfony\Component\CssSelector\Parser\Shortcut\EmptyStringParser; +use Symfony\Component\CssSelector\Parser\Shortcut\HashParser; +use Symfony\Component\CssSelector\XPath\Extension\HtmlExtension; +use Symfony\Component\CssSelector\XPath\Translator; + +/** + * CssSelector is the main entry point of the component and can convert CSS + * selectors to XPath expressions. + * + * $xpath = CssSelector::toXpath('h1.foo'); + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Fabien Potencier + * + * @api + */ +class CssSelector +{ + private static $html = true; + + /** + * Translates a CSS expression to its XPath equivalent. + * Optionally, a prefix can be added to the resulting XPath + * expression with the $prefix parameter. + * + * @param mixed $cssExpr The CSS expression. + * @param string $prefix An optional prefix for the XPath expression. + * + * @return string + * + * @api + */ + public static function toXPath($cssExpr, $prefix = 'descendant-or-self::') + { + $translator = new Translator(); + + if (self::$html) { + $translator->registerExtension(new HtmlExtension($translator)); + } + + $translator + ->registerParserShortcut(new EmptyStringParser()) + ->registerParserShortcut(new ElementParser()) + ->registerParserShortcut(new ClassParser()) + ->registerParserShortcut(new HashParser()) + ; + + return $translator->cssToXPath($cssExpr, $prefix); + } + + /** + * Enables the HTML extension. + */ + public static function enableHtmlExtension() + { + self::$html = true; + } + + /** + * Disables the HTML extension. + */ + public static function disableHtmlExtension() + { + self::$html = false; + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/ExceptionInterface.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/ExceptionInterface.php new file mode 100644 index 0000000..da01c2b --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/ExceptionInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Exception; + +/** + * Interface for exceptions. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/ExpressionErrorException.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/ExpressionErrorException.php new file mode 100644 index 0000000..151dbf0 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/ExpressionErrorException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Exception; + +/** + * ParseException is thrown when a CSS selector syntax is not valid. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class ExpressionErrorException extends ParseException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/InternalErrorException.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/InternalErrorException.php new file mode 100644 index 0000000..8a815fb --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/InternalErrorException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Exception; + +/** + * ParseException is thrown when a CSS selector syntax is not valid. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class InternalErrorException extends ParseException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/ParseException.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/ParseException.php new file mode 100644 index 0000000..9c119f8 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/ParseException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Exception; + +/** + * ParseException is thrown when a CSS selector syntax is not valid. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Fabien Potencier + */ +class ParseException extends \Exception implements ExceptionInterface +{ +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/SyntaxErrorException.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/SyntaxErrorException.php new file mode 100644 index 0000000..529b891 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Exception/SyntaxErrorException.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Exception; + +use Symfony\Component\CssSelector\Parser\Token; + +/** + * ParseException is thrown when a CSS selector syntax is not valid. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class SyntaxErrorException extends ParseException implements ExceptionInterface +{ + /** + * @param string $expectedValue + * @param Token $foundToken + * + * @return SyntaxErrorException + */ + public static function unexpectedToken($expectedValue, Token $foundToken) + { + return new self(sprintf('Expected %s, but %s found.', $expectedValue, $foundToken)); + } + + /** + * @param string $pseudoElement + * @param string $unexpectedLocation + * + * @return SyntaxErrorException + */ + public static function pseudoElementFound($pseudoElement, $unexpectedLocation) + { + return new self(sprintf('Unexpected pseudo-element "::%s" found %s.', $pseudoElement, $unexpectedLocation)); + } + + /** + * @param int $position + * + * @return SyntaxErrorException + */ + public static function unclosedString($position) + { + return new self(sprintf('Unclosed/invalid string at %s.', $position)); + } + + /** + * @return SyntaxErrorException + */ + public static function nestedNot() + { + return new self('Got nested ::not().'); + } + + /** + * @return SyntaxErrorException + */ + public static function stringAsFunctionArgument() + { + return new self('String not allowed as function argument.'); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/LICENSE b/vendor/symfony/css-selector/Symfony/Component/CssSelector/LICENSE new file mode 100644 index 0000000..88a57f8 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2013 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/AbstractNode.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/AbstractNode.php new file mode 100644 index 0000000..f5324e1 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/AbstractNode.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Abstract base node class. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +abstract class AbstractNode implements NodeInterface +{ + /** + * @var string + */ + private $nodeName; + + /** + * @return string + */ + public function getNodeName() + { + if (null === $this->nodeName) { + $this->nodeName = preg_replace('~.*\\\\([^\\\\]+)Node$~', '$1', get_called_class()); + } + + return $this->nodeName; + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/AttributeNode.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/AttributeNode.php new file mode 100644 index 0000000..e2fa9a2 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/AttributeNode.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a "[| ]" node. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class AttributeNode extends AbstractNode +{ + /** + * @var NodeInterface + */ + private $selector; + + /** + * @var string + */ + private $namespace; + + /** + * @var string + */ + private $attribute; + + /** + * @var string + */ + private $operator; + + /** + * @var string + */ + private $value; + + /** + * @param NodeInterface $selector + * @param string $namespace + * @param string $attribute + * @param string $operator + * @param string $value + */ + public function __construct(NodeInterface $selector, $namespace, $attribute, $operator, $value) + { + $this->selector = $selector; + $this->namespace = $namespace; + $this->attribute = $attribute; + $this->operator = $operator; + $this->value = $value; + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * @return string + */ + public function getAttribute() + { + return $this->attribute; + } + + /** + * @return string + */ + public function getOperator() + { + return $this->operator; + } + + /** + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + $attribute = $this->namespace ? $this->namespace.'|'.$this->attribute : $this->attribute; + + return 'exists' === $this->operator + ? sprintf('%s[%s[%s]]', $this->getNodeName(), $this->selector, $attribute) + : sprintf("%s[%s[%s %s '%s']]", $this->getNodeName(), $this->selector, $attribute, $this->operator, $this->value); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/ClassNode.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/ClassNode.php new file mode 100644 index 0000000..a7a59a3 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/ClassNode.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a "." node. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class ClassNode extends AbstractNode +{ + /** + * @var NodeInterface + */ + private $selector; + + /** + * @var string + */ + private $name; + + /** + * @param NodeInterface $selector + * @param string $name + */ + public function __construct(NodeInterface $selector, $name) + { + $this->selector = $selector; + $this->name = $name; + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('%s[%s.%s]', $this->getNodeName(), $this->selector, $this->name); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/CombinedSelectorNode.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/CombinedSelectorNode.php new file mode 100644 index 0000000..4e085ea --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/CombinedSelectorNode.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a combined node. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class CombinedSelectorNode extends AbstractNode +{ + /** + * @var NodeInterface + */ + private $selector; + + /** + * @var string + */ + private $combinator; + + /** + * @var NodeInterface + */ + private $subSelector; + + /** + * @param NodeInterface $selector + * @param string $combinator + * @param NodeInterface $subSelector + */ + public function __construct(NodeInterface $selector, $combinator, NodeInterface $subSelector) + { + $this->selector = $selector; + $this->combinator = $combinator; + $this->subSelector = $subSelector; + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return string + */ + public function getCombinator() + { + return $this->combinator; + } + + /** + * @return NodeInterface + */ + public function getSubSelector() + { + return $this->subSelector; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity()); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + $combinator = ' ' === $this->combinator ? '' : $this->combinator; + + return sprintf('%s[%s %s %s]', $this->getNodeName(), $this->selector, $combinator, $this->subSelector); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/ElementNode.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/ElementNode.php new file mode 100644 index 0000000..9ab13c3 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/ElementNode.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a "|" node. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class ElementNode extends AbstractNode +{ + /** + * @var string|null + */ + private $namespace; + + /** + * @var string|null + */ + private $element; + + /** + * @param string|null $namespace + * @param string|null $element + */ + public function __construct($namespace = null, $element = null) + { + $this->namespace = $namespace; + $this->element = $element; + } + + /** + * @return null|string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * @return null|string + */ + public function getElement() + { + return $this->element; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return new Specificity(0, 0, $this->element ? 1 : 0); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + $element = $this->element ?: '*'; + + return sprintf('%s[%s]', $this->getNodeName(), $this->namespace ? $this->namespace.'|'.$element : $element); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/FunctionNode.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/FunctionNode.php new file mode 100644 index 0000000..ecd11a5 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/FunctionNode.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +use Symfony\Component\CssSelector\Parser\Token; + +/** + * Represents a ":()" node. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class FunctionNode extends AbstractNode +{ + /** + * @var NodeInterface + */ + private $selector; + + /** + * @var string + */ + private $name; + + /** + * @var Token[] + */ + private $arguments; + + /** + * @param NodeInterface $selector + * @param string $name + * @param Token[] $arguments + */ + public function __construct(NodeInterface $selector, $name, array $arguments = array()) + { + $this->selector = $selector; + $this->name = strtolower($name); + $this->arguments = $arguments; + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return Token[] + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + $arguments = implode(', ', array_map(function (Token $token) { + return "'".$token->getValue()."'"; + }, $this->arguments)); + + return sprintf('%s[%s:%s(%s)]', $this->getNodeName(), $this->selector, $this->name, $arguments ? '['.$arguments.']' : ''); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/HashNode.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/HashNode.php new file mode 100644 index 0000000..7fb4075 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/HashNode.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a "#" node. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class HashNode extends AbstractNode +{ + /** + * @var NodeInterface + */ + private $selector; + + /** + * @var string + */ + private $id; + + /** + * @param NodeInterface $selector + * @param string $id + */ + public function __construct(NodeInterface $selector, $id) + { + $this->selector = $selector; + $this->id = $id; + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus(new Specificity(1, 0, 0)); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('%s[%s#%s]', $this->getNodeName(), $this->selector, $this->id); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/NegationNode.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/NegationNode.php new file mode 100644 index 0000000..2529689 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/NegationNode.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a ":not()" node. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class NegationNode extends AbstractNode +{ + /** + * @var NodeInterface + */ + private $selector; + + /** + * @var NodeInterface + */ + private $subSelector; + + /** + * @param NodeInterface $selector + * @param NodeInterface $subSelector + */ + public function __construct(NodeInterface $selector, NodeInterface $subSelector) + { + $this->selector = $selector; + $this->subSelector = $subSelector; + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return NodeInterface + */ + public function getSubSelector() + { + return $this->subSelector; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity()); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('%s[%s:not(%s)]', $this->getNodeName(), $this->selector, $this->subSelector); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/NodeInterface.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/NodeInterface.php new file mode 100644 index 0000000..1601c33 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/NodeInterface.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Interface for nodes. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +interface NodeInterface +{ + /** + * Returns node's name. + * + * @return string + */ + public function getNodeName(); + + /** + * Returns node's specificity. + * + * @return Specificity + */ + public function getSpecificity(); + + /** + * Returns node's string representation. + * + * @return string + */ + public function __toString(); +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/PseudoNode.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/PseudoNode.php new file mode 100644 index 0000000..4f2d538 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/PseudoNode.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a ":" node. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class PseudoNode extends AbstractNode +{ + /** + * @var NodeInterface + */ + private $selector; + + /** + * @var string + */ + private $identifier; + + /** + * @param NodeInterface $selector + * @param string $identifier + */ + public function __construct(NodeInterface $selector, $identifier) + { + $this->selector = $selector; + $this->identifier = strtolower($identifier); + } + + /** + * @return NodeInterface + */ + public function getSelector() + { + return $this->selector; + } + + /** + * @return string + */ + public function getIdentifier() + { + return $this->identifier; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('%s[%s:%s]', $this->getNodeName(), $this->selector, $this->identifier); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/SelectorNode.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/SelectorNode.php new file mode 100644 index 0000000..49f417f --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/SelectorNode.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a "(::|:)" node. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class SelectorNode extends AbstractNode +{ + /** + * @var NodeInterface + */ + private $tree; + + /** + * @var null|string + */ + private $pseudoElement; + + /** + * @param NodeInterface $tree + * @param null|string $pseudoElement + */ + public function __construct(NodeInterface $tree, $pseudoElement = null) + { + $this->tree = $tree; + $this->pseudoElement = $pseudoElement ? strtolower($pseudoElement) : null; + } + + /** + * @return NodeInterface + */ + public function getTree() + { + return $this->tree; + } + + /** + * @return null|string + */ + public function getPseudoElement() + { + return $this->pseudoElement; + } + + /** + * {@inheritdoc} + */ + public function getSpecificity() + { + return $this->tree->getSpecificity()->plus(new Specificity(0, 0, $this->pseudoElement ? 1 : 0)); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return sprintf('%s[%s%s]', $this->getNodeName(), $this->tree, $this->pseudoElement ? '::'.$this->pseudoElement : ''); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/Specificity.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/Specificity.php new file mode 100644 index 0000000..96bbd11 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Node/Specificity.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Node; + +/** + * Represents a node specificity. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @see http://www.w3.org/TR/selectors/#specificity + * + * @author Jean-François Simon + */ +class Specificity +{ + const A_FACTOR = 100; + const B_FACTOR = 10; + const C_FACTOR = 1; + + /** + * @var int + */ + private $a; + + /** + * @var int + */ + private $b; + + /** + * @var int + */ + private $c; + + /** + * Constructor. + * + * @param int $a + * @param int $b + * @param int $c + */ + public function __construct($a, $b, $c) + { + $this->a = $a; + $this->b = $b; + $this->c = $c; + } + + /** + * @param Specificity $specificity + * + * @return Specificity + */ + public function plus(Specificity $specificity) + { + return new self($this->a + $specificity->a, $this->b + $specificity->b, $this->c + $specificity->c); + } + + /** + * Returns global specificity value. + * + * @return int + */ + public function getValue() + { + return $this->a * self::A_FACTOR + $this->b * self::B_FACTOR + $this->c * self::C_FACTOR; + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/CommentHandler.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/CommentHandler.php new file mode 100644 index 0000000..97c3f8d --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/CommentHandler.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * CSS selector comment handler. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class CommentHandler implements HandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle(Reader $reader, TokenStream $stream) + { + if ('/*' !== $reader->getSubstring(2)) { + return false; + } + + $offset = $reader->getOffset('*/'); + + if (false === $offset) { + $reader->moveToEnd(); + } else { + $reader->moveForward($offset + 2); + } + + return true; + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/HandlerInterface.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/HandlerInterface.php new file mode 100644 index 0000000..4ac11a1 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/HandlerInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * CSS selector handler interface. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +interface HandlerInterface +{ + /** + * @param Reader $reader + * @param TokenStream $stream + * + * @return boolean + */ + public function handle(Reader $reader, TokenStream $stream); +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/HashHandler.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/HashHandler.php new file mode 100644 index 0000000..2227ea6 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/HashHandler.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; + +/** + * CSS selector comment handler. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class HashHandler implements HandlerInterface +{ + /** + * @var TokenizerPatterns + */ + private $patterns; + + /** + * @var TokenizerEscaping + */ + private $escaping; + + /** + * @param TokenizerPatterns $patterns + * @param TokenizerEscaping $escaping + */ + public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) + { + $this->patterns = $patterns; + $this->escaping = $escaping; + } + + /** + * {@inheritdoc} + */ + public function handle(Reader $reader, TokenStream $stream) + { + $match = $reader->findPattern($this->patterns->getHashPattern()); + + if (!$match) { + return false; + } + + $value = $this->escaping->escapeUnicode($match[1]); + $stream->push(new Token(Token::TYPE_HASH, $value, $reader->getPosition())); + $reader->moveForward(strlen($match[0])); + + return true; + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/IdentifierHandler.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/IdentifierHandler.php new file mode 100644 index 0000000..346532e --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/IdentifierHandler.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; + +/** + * CSS selector comment handler. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class IdentifierHandler implements HandlerInterface +{ + /** + * @var TokenizerPatterns + */ + private $patterns; + + /** + * @var TokenizerEscaping + */ + private $escaping; + + /** + * @param TokenizerPatterns $patterns + * @param TokenizerEscaping $escaping + */ + public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) + { + $this->patterns = $patterns; + $this->escaping = $escaping; + } + + /** + * {@inheritdoc} + */ + public function handle(Reader $reader, TokenStream $stream) + { + $match = $reader->findPattern($this->patterns->getIdentifierPattern()); + + if (!$match) { + return false; + } + + $value = $this->escaping->escapeUnicode($match[0]); + $stream->push(new Token(Token::TYPE_IDENTIFIER, $value, $reader->getPosition())); + $reader->moveForward(strlen($match[0])); + + return true; + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/NumberHandler.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/NumberHandler.php new file mode 100644 index 0000000..208f83c --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/NumberHandler.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; + +/** + * CSS selector comment handler. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class NumberHandler implements HandlerInterface +{ + /** + * @var TokenizerPatterns + */ + private $patterns; + + /** + * @param TokenizerPatterns $patterns + */ + public function __construct(TokenizerPatterns $patterns) + { + $this->patterns = $patterns; + } + + /** + * {@inheritdoc} + */ + public function handle(Reader $reader, TokenStream $stream) + { + $match = $reader->findPattern($this->patterns->getNumberPattern()); + + if (!$match) { + return false; + } + + $stream->push(new Token(Token::TYPE_NUMBER, $match[0], $reader->getPosition())); + $reader->moveForward(strlen($match[0])); + + return true; + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/StringHandler.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/StringHandler.php new file mode 100644 index 0000000..2663fe8 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/StringHandler.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Exception\InternalErrorException; +use Symfony\Component\CssSelector\Exception\SyntaxErrorException; +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; + +/** + * CSS selector comment handler. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class StringHandler implements HandlerInterface +{ + /** + * @var TokenizerPatterns + */ + private $patterns; + + /** + * @var TokenizerEscaping + */ + private $escaping; + + /** + * @param TokenizerPatterns $patterns + * @param TokenizerEscaping $escaping + */ + public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) + { + $this->patterns = $patterns; + $this->escaping = $escaping; + } + + /** + * {@inheritdoc} + */ + public function handle(Reader $reader, TokenStream $stream) + { + $quote = $reader->getSubstring(1); + + if (!in_array($quote, array("'", '"'))) { + return false; + } + + $reader->moveForward(1); + $match = $reader->findPattern($this->patterns->getQuotedStringPattern($quote)); + + if (!$match) { + throw new InternalErrorException(sprintf('Should have found at least an empty match at %s.', $reader->getPosition())); + } + + // check unclosed strings + if (strlen($match[0]) === $reader->getRemainingLength()) { + throw SyntaxErrorException::unclosedString($reader->getPosition() - 1); + } + + // check quotes pairs validity + if ($quote !== $reader->getSubstring(1, strlen($match[0]))) { + throw SyntaxErrorException::unclosedString($reader->getPosition() - 1); + } + + $string = $this->escaping->escapeUnicodeAndNewLine($match[0]); + $stream->push(new Token(Token::TYPE_STRING, $string, $reader->getPosition())); + $reader->moveForward(strlen($match[0]) + 1); + + return true; + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/WhitespaceHandler.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/WhitespaceHandler.php new file mode 100644 index 0000000..806cfbb --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Handler/WhitespaceHandler.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * CSS selector whitespace handler. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class WhitespaceHandler implements HandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handle(Reader $reader, TokenStream $stream) + { + $match = $reader->findPattern('~^[ \t\r\n\f]+~'); + + if (false === $match) { + return false; + } + + $stream->push(new Token(Token::TYPE_WHITESPACE, $match[0], $reader->getPosition())); + $reader->moveForward(strlen($match[0])); + + return true; + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Parser.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Parser.php new file mode 100644 index 0000000..edf8ac0 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Parser.php @@ -0,0 +1,394 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser; + +use Symfony\Component\CssSelector\Exception\SyntaxErrorException; +use Symfony\Component\CssSelector\Node; +use Symfony\Component\CssSelector\Parser\Tokenizer\Tokenizer; + +/** + * CSS selector parser. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class Parser implements ParserInterface +{ + /** + * @var Tokenizer + */ + private $tokenizer; + + /** + * Constructor. + * + * @param null|Tokenizer $tokenizer + */ + public function __construct(Tokenizer $tokenizer = null) + { + $this->tokenizer = $tokenizer ?: new Tokenizer(); + } + + /** + * {@inheritdoc} + */ + public function parse($source) + { + $reader = new Reader($source); + $stream = $this->tokenizer->tokenize($reader); + + return $this->parseSelectorList($stream); + } + + /** + * Parses the arguments for ":nth-child()" and friends. + * + * @param Token[] $tokens + * + * @throws SyntaxErrorException + * + * @return array + */ + public static function parseSeries(array $tokens) + { + foreach ($tokens as $token) { + if ($token->isString()) { + throw SyntaxErrorException::stringAsFunctionArgument(); + } + } + + $joined = trim(implode('', array_map(function (Token $token) { + return $token->getValue(); + }, $tokens))); + + $int = function ($string) { + if (!is_numeric($string)) { + throw SyntaxErrorException::stringAsFunctionArgument(); + } + + return (int) $string; + }; + + switch (true) { + case 'odd' === $joined: + return array(2, 1); + case 'even' === $joined: + return array(2, 0); + case 'n' === $joined: + return array(1, 0); + case false === strpos($joined, 'n'): + return array(0, $int($joined)); + } + + $split = explode('n', $joined); + $first = isset($split[0]) ? $split[0] : null; + + return array( + $first ? ('-' === $first || '+' === $first ? $int($first.'1') : $int($first)) : 1, + isset($split[1]) && $split[1] ? $int($split[1]) : 0 + ); + } + + /** + * Parses selector nodes. + * + * @param TokenStream $stream + * + * @return array + */ + private function parseSelectorList(TokenStream $stream) + { + $stream->skipWhitespace(); + $selectors = array(); + + while (true) { + $selectors[] = $this->parserSelectorNode($stream); + + if ($stream->getPeek()->isDelimiter(array(','))) { + $stream->getNext(); + $stream->skipWhitespace(); + } else { + break; + } + } + + return $selectors; + } + + /** + * Parses next selector or combined node. + * + * @param TokenStream $stream + * + * @throws SyntaxErrorException + * + * @return Node\SelectorNode + */ + private function parserSelectorNode(TokenStream $stream) + { + list($result, $pseudoElement) = $this->parseSimpleSelector($stream); + + while (true) { + $stream->skipWhitespace(); + $peek = $stream->getPeek(); + + if ($peek->isFileEnd() || $peek->isDelimiter(array(','))) { + break; + } + + if (null !== $pseudoElement) { + throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector'); + } + + if ($peek->isDelimiter(array('+', '>', '~'))) { + $combinator = $stream->getNext()->getValue(); + $stream->skipWhitespace(); + } else { + $combinator = ' '; + } + + list($nextSelector, $pseudoElement) = $this->parseSimpleSelector($stream); + $result = new Node\CombinedSelectorNode($result, $combinator, $nextSelector); + } + + return new Node\SelectorNode($result, $pseudoElement); + } + + /** + * Parses next simple node (hash, class, pseudo, negation). + * + * @param TokenStream $stream + * @param boolean $insideNegation + * + * @throws SyntaxErrorException + * + * @return array + */ + private function parseSimpleSelector(TokenStream $stream, $insideNegation = false) + { + $stream->skipWhitespace(); + + $selectorStart = count($stream->getUsed()); + $result = $this->parseElementNode($stream); + $pseudoElement = null; + + while (true) { + $peek = $stream->getPeek(); + if ($peek->isWhitespace() + || $peek->isFileEnd() + || $peek->isDelimiter(array(',', '+', '>', '~')) + || ($insideNegation && $peek->isDelimiter(array(')'))) + ) { + break; + } + + if (null !== $pseudoElement) { + throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector'); + } + + if ($peek->isHash()) { + $result = new Node\HashNode($result, $stream->getNext()->getValue()); + } elseif ($peek->isDelimiter(array('.'))) { + $stream->getNext(); + $result = new Node\ClassNode($result, $stream->getNextIdentifier()); + } elseif ($peek->isDelimiter(array('['))) { + $stream->getNext(); + $result = $this->parseAttributeNode($result, $stream); + } elseif ($peek->isDelimiter(array(':'))) { + $stream->getNext(); + + if ($stream->getPeek()->isDelimiter(array(':'))) { + $stream->getNext(); + $pseudoElement = $stream->getNextIdentifier(); + + continue; + } + + $identifier = $stream->getNextIdentifier(); + if (in_array(strtolower($identifier), array('first-line', 'first-letter', 'before', 'after'))) { + // Special case: CSS 2.1 pseudo-elements can have a single ':'. + // Any new pseudo-element must have two. + $pseudoElement = $identifier; + + continue; + } + + if (!$stream->getPeek()->isDelimiter(array('('))) { + $result = new Node\PseudoNode($result, $identifier); + + continue; + } + + $stream->getNext(); + $stream->skipWhitespace(); + + if ('not' === strtolower($identifier)) { + if ($insideNegation) { + throw SyntaxErrorException::nestedNot(); + } + + list($argument, $argumentPseudoElement) = $this->parseSimpleSelector($stream, true); + $next = $stream->getNext(); + + if (null !== $argumentPseudoElement) { + throw SyntaxErrorException::pseudoElementFound($argumentPseudoElement, 'inside ::not()'); + } + + if (!$next->isDelimiter(array(')'))) { + throw SyntaxErrorException::unexpectedToken('")"', $next); + } + + $result = new Node\NegationNode($result, $argument); + } else { + $arguments = array(); + $next = null; + + while (true) { + $stream->skipWhitespace(); + $next = $stream->getNext(); + + if ($next->isIdentifier() + || $next->isString() + || $next->isNumber() + || $next->isDelimiter(array('+', '-')) + ) { + $arguments[] = $next; + } elseif ($next->isDelimiter(array(')'))) { + break; + } else { + throw SyntaxErrorException::unexpectedToken('an argument', $next); + } + } + + if (empty($arguments)) { + throw SyntaxErrorException::unexpectedToken('at least one argument', $next); + } + + $result = new Node\FunctionNode($result, $identifier, $arguments); + } + } else { + throw SyntaxErrorException::unexpectedToken('selector', $peek); + } + } + + if (count($stream->getUsed()) === $selectorStart) { + throw SyntaxErrorException::unexpectedToken('selector', $stream->getPeek()); + } + + return array($result, $pseudoElement); + } + + /** + * Parses next element node. + * + * @param TokenStream $stream + * + * @return Node\ElementNode + */ + private function parseElementNode(TokenStream $stream) + { + $peek = $stream->getPeek(); + + if ($peek->isIdentifier() || $peek->isDelimiter(array('*'))) { + if ($peek->isIdentifier()) { + $namespace = $stream->getNext()->getValue(); + } else { + $stream->getNext(); + $namespace = null; + } + + if ($stream->getPeek()->isDelimiter(array('|'))) { + $stream->getNext(); + $element = $stream->getNextIdentifierOrStar(); + } else { + $element = $namespace; + $namespace = null; + } + } else { + $element = $namespace = null; + } + + return new Node\ElementNode($namespace, $element); + } + + /** + * Parses next attribute node. + * + * @param Node\NodeInterface $selector + * @param TokenStream $stream + * + * @throws SyntaxErrorException + * + * @return Node\AttributeNode + */ + private function parseAttributeNode(Node\NodeInterface $selector, TokenStream $stream) + { + $stream->skipWhitespace(); + $attribute = $stream->getNextIdentifierOrStar(); + + if (null === $attribute && !$stream->getPeek()->isDelimiter(array('|'))) { + throw SyntaxErrorException::unexpectedToken('"|"', $stream->getPeek()); + } + + if ($stream->getPeek()->isDelimiter(array('|'))) { + $stream->getNext(); + + if ($stream->getPeek()->isDelimiter(array('='))) { + $namespace = null; + $stream->getNext(); + $operator = '|='; + } else { + $namespace = $attribute; + $attribute = $stream->getNextIdentifier(); + $operator = null; + } + } else { + $namespace = $operator = null; + } + + if (null === $operator) { + $stream->skipWhitespace(); + $next = $stream->getNext(); + + if ($next->isDelimiter(array(']'))) { + return new Node\AttributeNode($selector, $namespace, $attribute, 'exists', null); + } elseif ($next->isDelimiter(array('='))) { + $operator = '='; + } elseif ($next->isDelimiter(array('^', '$', '*', '~', '|', '!')) + && $stream->getPeek()->isDelimiter(array('=')) + ) { + $operator = $next->getValue().'='; + $stream->getNext(); + } else { + throw SyntaxErrorException::unexpectedToken('operator', $next); + } + } + + $stream->skipWhitespace(); + $value = $stream->getNext(); + + if (!($value->isIdentifier() || $value->isString())) { + throw SyntaxErrorException::unexpectedToken('string or identifier', $value); + } + + $stream->skipWhitespace(); + $next = $stream->getNext(); + + if (!$next->isDelimiter(array(']'))) { + throw SyntaxErrorException::unexpectedToken('"]"', $next); + } + + return new Node\AttributeNode($selector, $namespace, $attribute, $operator, $value->getValue()); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/ParserInterface.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/ParserInterface.php new file mode 100644 index 0000000..b27f79f --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/ParserInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser; + +use Symfony\Component\CssSelector\Node\SelectorNode; + +/** + * CSS selector parser interface. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +interface ParserInterface +{ + /** + * Parses given selector source into an array of tokens. + * + * @param string $source + * + * @return SelectorNode[] + */ + public function parse($source); +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Reader.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Reader.php new file mode 100644 index 0000000..8bd8ed6 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Reader.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser; + +/** + * CSS selector reader. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class Reader +{ + /** + * @var string + */ + private $source; + + /** + * @var int + */ + private $length; + + /** + * @var int + */ + private $position; + + /** + * @param string $source + */ + public function __construct($source) + { + $this->source = $source; + $this->length = strlen($source); + $this->position = 0; + } + + /** + * @return bool + */ + public function isEOF() + { + return $this->position >= $this->length; + } + + /** + * @return int + */ + public function getPosition() + { + return $this->position; + } + + /** + * @return int + */ + public function getRemainingLength() + { + return $this->length - $this->position; + } + + /** + * @param int $length + * @param int $offset + * + * @return string + */ + public function getSubstring($length, $offset = 0) + { + return substr($this->source, $this->position + $offset, $length); + } + + /** + * @param string $string + * + * @return int + */ + public function getOffset($string) + { + $position = strpos($this->source, $string, $this->position); + + return false === $position ? false : $position - $this->position; + } + + /** + * @param string $pattern + * + * @return bool + */ + public function findPattern($pattern) + { + $source = substr($this->source, $this->position); + + if (preg_match($pattern, $source, $matches)) { + return $matches; + } + + return false; + } + + /** + * @param int $length + */ + public function moveForward($length) + { + $this->position += $length; + } + + /** + */ + public function moveToEnd() + { + $this->position = $this->length; + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/ClassParser.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/ClassParser.php new file mode 100644 index 0000000..db35233 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/ClassParser.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\ClassNode; +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\ParserInterface; + +/** + * CSS selector class parser shortcut. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class ClassParser implements ParserInterface +{ + /** + * {@inheritdoc} + */ + public function parse($source) + { + // Matches an optional namespace, optional element, and required class + // $source = 'test|input.ab6bd_field'; + // $matches = array (size=5) + // 0 => string 'test:input.ab6bd_field' (length=22) + // 1 => string 'test:' (length=5) + // 2 => string 'test' (length=4) + // 3 => string 'input' (length=5) + // 4 => string 'ab6bd_field' (length=11) + if (preg_match('/^(([a-z]+)\|)?([\w-]+|\*)?\.([\w-]+)$/i', trim($source), $matches)) { + return array( + new SelectorNode(new ClassNode(new ElementNode($matches[2] ?: null, $matches[3] ?: null), $matches[4])) + ); + } + + return array(); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/ElementParser.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/ElementParser.php new file mode 100644 index 0000000..eae18b9 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/ElementParser.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\ParserInterface; + +/** + * CSS selector element parser shortcut. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class ElementParser implements ParserInterface +{ + /** + * {@inheritdoc} + */ + public function parse($source) + { + // Matches an optional namespace, required element or `*` + // $source = 'testns|testel'; + // $matches = array (size=4) + // 0 => string 'testns:testel' (length=13) + // 1 => string 'testns:' (length=7) + // 2 => string 'testns' (length=6) + // 3 => string 'testel' (length=6) + if (preg_match('/^(([a-z]+)\|)?([\w-]+|\*)$/i', trim($source), $matches)) { + return array(new SelectorNode(new ElementNode($matches[2] ?: null, $matches[3]))); + } + + return array(); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/EmptyStringParser.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/EmptyStringParser.php new file mode 100644 index 0000000..0031f7c --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/EmptyStringParser.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\ParserInterface; + +/** + * CSS selector class parser shortcut. + * + * This shortcut ensure compatibility with previous version. + * - The parser fails to parse an empty string. + * - In the previous version, an empty string matches each tags. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class EmptyStringParser implements ParserInterface +{ + /** + * {@inheritdoc} + */ + public function parse($source) + { + // Matches an empty string + if ($source == '') { + return array(new SelectorNode(new ElementNode(null, '*'))); + } + + return array(); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/HashParser.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/HashParser.php new file mode 100644 index 0000000..95d7d5f --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Shortcut/HashParser.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\HashNode; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\ParserInterface; + +/** + * CSS selector hash parser shortcut. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class HashParser implements ParserInterface +{ + /** + * {@inheritdoc} + */ + public function parse($source) + { + // Matches an optional namespace, optional element, and required id + // $source = 'test|input#ab6bd_field'; + // $matches = array (size=5) + // 0 => string 'test:input#ab6bd_field' (length=22) + // 1 => string 'test:' (length=5) + // 2 => string 'test' (length=4) + // 3 => string 'input' (length=5) + // 4 => string 'ab6bd_field' (length=11) + if (preg_match('/^(([a-z]+)\|)?([\w-]+|\*)?#([\w-]+)$/i', trim($source), $matches)) { + return array( + new SelectorNode(new HashNode(new ElementNode($matches[2] ?: null, $matches[3] ?: null), $matches[4])) + ); + } + + return array(); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Token.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Token.php new file mode 100644 index 0000000..0abe190 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Token.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser; + +/** + * CSS selector token. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class Token +{ + const TYPE_FILE_END = 'eof'; + const TYPE_DELIMITER = 'delimiter'; + const TYPE_WHITESPACE = 'whitespace'; + const TYPE_IDENTIFIER = 'identifier'; + const TYPE_HASH = 'hash'; + const TYPE_NUMBER = 'number'; + const TYPE_STRING = 'string'; + + /** + * @var int + */ + private $type; + + /** + * @var string + */ + private $value; + + /** + * @var int + */ + private $position; + + /** + * @param int $type + * @param string $value + * @param int $position + */ + public function __construct($type, $value, $position) + { + $this->type = $type; + $this->value = $value; + $this->position = $position; + } + + /** + * @return int + */ + public function getType() + { + return $this->type; + } + + /** + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * @return int + */ + public function getPosition() + { + return $this->position; + } + + /** + * @return boolean + */ + public function isFileEnd() + { + return self::TYPE_FILE_END === $this->type; + } + + /** + * @param array $values + * + * @return boolean + */ + public function isDelimiter(array $values = array()) + { + if (self::TYPE_DELIMITER !== $this->type) { + return false; + } + + if (empty($values)) { + return true; + } + + return in_array($this->value, $values); + } + + /** + * @return boolean + */ + public function isWhitespace() + { + return self::TYPE_WHITESPACE === $this->type; + } + + /** + * @return boolean + */ + public function isIdentifier() + { + return self::TYPE_IDENTIFIER === $this->type; + } + + /** + * @return boolean + */ + public function isHash() + { + return self::TYPE_HASH === $this->type; + } + + /** + * @return boolean + */ + public function isNumber() + { + return self::TYPE_NUMBER === $this->type; + } + + /** + * @return boolean + */ + public function isString() + { + return self::TYPE_STRING === $this->type; + } + + /** + * @return string + */ + public function __toString() + { + if ($this->value) { + return sprintf('<%s "%s" at %s>', $this->type, $this->value, $this->position); + } + + return sprintf('<%s at %s>', $this->type, $this->position); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/TokenStream.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/TokenStream.php new file mode 100644 index 0000000..40f1152 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/TokenStream.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser; + +use Symfony\Component\CssSelector\Exception\InternalErrorException; +use Symfony\Component\CssSelector\Exception\SyntaxErrorException; + +/** + * CSS selector token stream. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class TokenStream +{ + /** + * @var Token[] + */ + private $tokens = array(); + + /** + * @var boolean + */ + private $frozen = false; + + /** + * @var Token[] + */ + private $used = array(); + + /** + * @var int + */ + private $cursor = 0; + + /** + * @var Token|null + */ + private $peeked = null; + + /** + * @var boolean + */ + private $peeking = false; + + /** + * Pushes a token. + * + * @param Token $token + * + * @return TokenStream + */ + public function push(Token $token) + { + $this->tokens[] = $token; + + return $this; + } + + /** + * Freezes stream. + * + * @return TokenStream + */ + public function freeze() + { + $this->frozen = true; + + return $this; + } + + /** + * Returns next token. + * + * @throws InternalErrorException If there is no more token + * + * @return Token + */ + public function getNext() + { + if ($this->peeking) { + $this->peeking = false; + $this->used[] = $this->peeked; + + return $this->peeked; + } + + if (!isset($this->tokens[$this->cursor])) { + throw new InternalErrorException('Unexpected token stream end.'); + } + + return $this->tokens[$this->cursor ++]; + } + + /** + * Returns peeked token. + * + * @return Token + */ + public function getPeek() + { + if (!$this->peeking) { + $this->peeked = $this->getNext(); + $this->peeking = true; + } + + return $this->peeked; + } + + /** + * Returns used tokens. + * + * @return Token[] + */ + public function getUsed() + { + return $this->used; + } + + /** + * Returns nex identifier token. + * + * @throws SyntaxErrorException If next token is not an identifier + * + * @return string The identifier token value + */ + public function getNextIdentifier() + { + $next = $this->getNext(); + + if (!$next->isIdentifier()) { + throw SyntaxErrorException::unexpectedToken('identifier', $next); + } + + return $next->getValue(); + } + + /** + * Returns nex identifier or star delimiter token. + * + * @throws SyntaxErrorException If next token is not an identifier or a star delimiter + * + * @return null|string The identifier token value or null if star found + */ + public function getNextIdentifierOrStar() + { + $next = $this->getNext(); + + if ($next->isIdentifier()) { + return $next->getValue(); + } + + if ($next->isDelimiter(array('*'))) { + return null; + } + + throw SyntaxErrorException::unexpectedToken('identifier or "*"', $next); + } + + /** + * Skips next whitespace if any. + */ + public function skipWhitespace() + { + $peek = $this->getPeek(); + + if ($peek->isWhitespace()) { + $this->getNext(); + } + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/Tokenizer.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/Tokenizer.php new file mode 100644 index 0000000..c850276 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/Tokenizer.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Tokenizer; + +use Symfony\Component\CssSelector\Parser\Handler; +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * CSS selector tokenizer. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class Tokenizer +{ + /** + * @var Handler\HandlerInterface[] + */ + private $handlers; + + /** + * Constructor. + */ + public function __construct() + { + $patterns = new TokenizerPatterns(); + $escaping = new TokenizerEscaping($patterns); + + $this->handlers = array( + new Handler\WhitespaceHandler(), + new Handler\IdentifierHandler($patterns, $escaping), + new Handler\HashHandler($patterns, $escaping), + new Handler\StringHandler($patterns, $escaping), + new Handler\NumberHandler($patterns), + new Handler\CommentHandler(), + ); + } + + /** + * Tokenize selector source code. + * + * @param Reader $reader + * + * @return TokenStream + */ + public function tokenize(Reader $reader) + { + $stream = new TokenStream(); + + while (!$reader->isEOF()) { + foreach ($this->handlers as $handler) { + if ($handler->handle($reader, $stream)) { + continue 2; + } + } + + $stream->push(new Token(Token::TYPE_DELIMITER, $reader->getSubstring(1), $reader->getPosition())); + $reader->moveForward(1); + } + + return $stream + ->push(new Token(Token::TYPE_FILE_END, null, $reader->getPosition())) + ->freeze(); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerEscaping.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerEscaping.php new file mode 100644 index 0000000..c90fc10 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerEscaping.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Tokenizer; + +/** + * CSS selector tokenizer escaping applier. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class TokenizerEscaping +{ + /** + * @var TokenizerPatterns + */ + private $patterns; + + /** + * @param TokenizerPatterns $patterns + */ + public function __construct(TokenizerPatterns $patterns) + { + $this->patterns = $patterns; + } + + /** + * @param string $value + * + * @return string + */ + public function escapeUnicode($value) + { + $value = $this->replaceUnicodeSequences($value); + + return preg_replace($this->patterns->getSimpleEscapePattern(), '$1', $value); + } + + /** + * @param string $value + * + * @return string + */ + public function escapeUnicodeAndNewLine($value) + { + $value = preg_replace($this->patterns->getNewLineEscapePattern(), '', $value); + + return $this->escapeUnicode($value); + } + + /** + * @param string $value + * + * @return string + */ + private function replaceUnicodeSequences($value) + { + return preg_replace_callback($this->patterns->getUnicodeEscapePattern(), function (array $match) { + $code = $match[1]; + + if (bin2hex($code) > 0xFFFD) { + $code = '\\FFFD'; + } + + return mb_convert_encoding(pack('H*', $code), 'UTF-8', 'UCS-2BE'); + }, $value); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerPatterns.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerPatterns.php new file mode 100644 index 0000000..6fc98b7 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Parser/Tokenizer/TokenizerPatterns.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Parser\Tokenizer; + +/** + * CSS selector tokenizer patterns builder. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class TokenizerPatterns +{ + /** + * @var string + */ + private $unicodeEscapePattern; + + /** + * @var string + */ + private $simpleEscapePattern; + + /** + * @var string + */ + private $newLineEscapePattern; + + /** + * @var string + */ + private $escapePattern; + + /** + * @var string + */ + private $stringEscapePattern; + + /** + * @var string + */ + private $nonAsciiPattern; + + /** + * @var string + */ + private $nmCharPattern; + + /** + * @var string + */ + private $nmStartPattern; + + /** + * @var string + */ + private $identifierPattern; + + /** + * @var string + */ + private $hashPattern; + + /** + * @var string + */ + private $numberPattern; + + /** + * @var string + */ + private $quotedStringPattern; + + /** + * Constructor. + */ + public function __construct() + { + $this->unicodeEscapePattern = '\\\\([0-9a-f]{1,6})(?:\r\n|[ \n\r\t\f])?'; + $this->simpleEscapePattern = '\\\\(.)'; + $this->newLineEscapePattern = '\\\\(?:\n|\r\n|\r|\f)'; + $this->escapePattern = $this->unicodeEscapePattern.'|\\\\[^\n\r\f0-9a-f]'; + $this->stringEscapePattern = $this->newLineEscapePattern.'|'.$this->escapePattern; + $this->nonAsciiPattern = '[^\x00-\x7F]'; + $this->nmCharPattern = '[_a-z0-9-]|'.$this->escapePattern.'|'.$this->nonAsciiPattern; + $this->nmStartPattern = '[_a-z]|'.$this->escapePattern.'|'.$this->nonAsciiPattern; + $this->identifierPattern = '(?:'.$this->nmStartPattern.')(?:'.$this->nmCharPattern.')*'; + $this->hashPattern = '#((?:'.$this->nmCharPattern.')+)'; + $this->numberPattern = '[+-]?(?:[0-9]*\.[0-9]+|[0-9]+)'; + $this->quotedStringPattern = '([^\n\r\f%s]|'.$this->stringEscapePattern.')*'; + } + + /** + * @return string + */ + public function getNewLineEscapePattern() + { + return '~^'.$this->newLineEscapePattern.'~'; + } + + /** + * @return string + */ + public function getSimpleEscapePattern() + { + return '~^'.$this->simpleEscapePattern.'~'; + } + + /** + * @return string + */ + public function getUnicodeEscapePattern() + { + return '~^'.$this->unicodeEscapePattern.'~i'; + } + + /** + * @return string + */ + public function getIdentifierPattern() + { + return '~^'.$this->identifierPattern.'~i'; + } + + /** + * @return string + */ + public function getHashPattern() + { + return '~^'.$this->hashPattern.'~i'; + } + + /** + * @return string + */ + public function getNumberPattern() + { + return '~^'.$this->numberPattern.'~'; + } + + /** + * @param string $quote + * + * @return string + */ + public function getQuotedStringPattern($quote) + { + return '~^'.sprintf($this->quotedStringPattern, $quote).'~i'; + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/README.md b/vendor/symfony/css-selector/Symfony/Component/CssSelector/README.md new file mode 100644 index 0000000..5075cdb --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/README.md @@ -0,0 +1,44 @@ +CssSelector Component +===================== + +CssSelector converts CSS selectors to XPath expressions. + +The component only goal is to convert CSS selectors to their XPath +equivalents: + + use Symfony\Component\CssSelector\CssSelector; + + print CssSelector::toXPath('div.item > h4 > a'); + +HTML and XML are different +-------------------------- + +The `CssSelector` component comes with an `HTML` extension which is enabled by +default. If you need to use this component with `XML` documents, you have to +disable this `HTML` extension. That's because, `HTML` tag & attribute names +are always lower-cased, but case-sensitive in `XML`: + + // disable `HTML` extension: + CssSelector::disableHtmlExtension(); + + // re-enable `HTML` extension: + CssSelector::enableHtmlExtension(); + +When the `HTML` extension is enabled, tag names are lower-cased, attribute +names are lower-cased, the following extra pseudo-classes are supported: +`checked`, `link`, `disabled`, `enabled`, `selected`, `invalid`, `hover`, +`visited`, and the `lang()` function is also added. + +Resources +--------- + +This component is a port of the Python lxml library, which is copyright Infrae +and distributed under the BSD license. + +Current code is a port of https://github.com/SimonSapin/cssselect@v0.7.1 + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/CssSelector/ + $ composer.phar install --dev + $ phpunit diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/CssSelectorTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/CssSelectorTest.php new file mode 100644 index 0000000..50daecc --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/CssSelectorTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests; + +use Symfony\Component\CssSelector\CssSelector; + +class CssSelectorTest extends \PHPUnit_Framework_TestCase +{ + public function testCssToXPath() + { + $this->assertEquals('descendant-or-self::*', CssSelector::toXPath('')); + $this->assertEquals('descendant-or-self::h1', CssSelector::toXPath('h1')); + $this->assertEquals("descendant-or-self::h1[@id = 'foo']", CssSelector::toXPath('h1#foo')); + $this->assertEquals("descendant-or-self::h1[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]", CssSelector::toXPath('h1.foo')); + $this->assertEquals('descendant-or-self::foo:h1', CssSelector::toXPath('foo|h1')); + } + + /** @dataProvider getCssToXPathWithoutPrefixTestData */ + public function testCssToXPathWithoutPrefix($css, $xpath) + { + $this->assertEquals($xpath, CssSelector::toXPath($css, ''), '->parse() parses an input string and returns a node'); + } + + public function testParseExceptions() + { + try { + CssSelector::toXPath('h1:'); + $this->fail('->parse() throws an Exception if the css selector is not valid'); + } catch (\Exception $e) { + $this->assertInstanceOf('\Symfony\Component\CssSelector\Exception\ParseException', $e, '->parse() throws an Exception if the css selector is not valid'); + $this->assertEquals("Expected identifier, but found.", $e->getMessage(), '->parse() throws an Exception if the css selector is not valid'); + } + } + + public function getCssToXPathWithoutPrefixTestData() + { + return array( + array('h1', "h1"), + array('foo|h1', "foo:h1"), + array('h1, h2, h3', "h1 | h2 | h3"), + array('h1:nth-child(3n+1)', "*/*[name() = 'h1' and (position() - 1 >= 0 and (position() - 1) mod 3 = 0)]"), + array('h1 > p', "h1/p"), + array('h1#foo', "h1[@id = 'foo']"), + array('h1.foo', "h1[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"), + array('h1[class*="foo bar"]', "h1[@class and contains(@class, 'foo bar')]"), + array('h1[foo|class*="foo bar"]', "h1[@foo:class and contains(@foo:class, 'foo bar')]"), + array('h1[class]', "h1[@class]"), + array('h1 .foo', "h1/descendant-or-self::*/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"), + array('h1 #foo', "h1/descendant-or-self::*/*[@id = 'foo']"), + array('h1 [class*=foo]', "h1/descendant-or-self::*/*[@class and contains(@class, 'foo')]"), + array('div>.foo', "div/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"), + array('div > .foo', "div/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]"), + ); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/AbstractNodeTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/AbstractNodeTest.php new file mode 100644 index 0000000..16a3a34 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/AbstractNodeTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\NodeInterface; + +abstract class AbstractNodeTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getToStringConversionTestData */ + public function testToStringConversion(NodeInterface $node, $representation) + { + $this->assertEquals($representation, (string) $node); + } + + /** @dataProvider getSpecificityValueTestData */ + public function testSpecificityValue(NodeInterface $node, $value) + { + $this->assertEquals($value, $node->getSpecificity()->getValue()); + } + + abstract public function getToStringConversionTestData(); + abstract public function getSpecificityValueTestData(); +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/AttributeNodeTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/AttributeNodeTest.php new file mode 100644 index 0000000..1fd090f --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/AttributeNodeTest.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\AttributeNode; +use Symfony\Component\CssSelector\Node\ElementNode; + +class AttributeNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new AttributeNode(new ElementNode(), null, 'attribute', 'exists', null), 'Attribute[Element[*][attribute]]'), + array(new AttributeNode(new ElementNode(), null, 'attribute', '$=', 'value'), "Attribute[Element[*][attribute $= 'value']]"), + array(new AttributeNode(new ElementNode(), 'namespace', 'attribute', '$=', 'value'), "Attribute[Element[*][namespace|attribute $= 'value']]"), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new AttributeNode(new ElementNode(), null, 'attribute', 'exists', null), 10), + array(new AttributeNode(new ElementNode(null, 'element'), null, 'attribute', 'exists', null), 11), + array(new AttributeNode(new ElementNode(), null, 'attribute', '$=', 'value'), 10), + array(new AttributeNode(new ElementNode(), 'namespace', 'attribute', '$=', 'value'), 10), + ); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/ClassNodeTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/ClassNodeTest.php new file mode 100644 index 0000000..e0ab45a --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/ClassNodeTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ClassNode; +use Symfony\Component\CssSelector\Node\ElementNode; + +class ClassNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new ClassNode(new ElementNode(), 'class'), 'Class[Element[*].class]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new ClassNode(new ElementNode(), 'class'), 10), + array(new ClassNode(new ElementNode(null, 'element'), 'class'), 11), + ); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/CombinedSelectorNodeTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/CombinedSelectorNodeTest.php new file mode 100644 index 0000000..9547298 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/CombinedSelectorNodeTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\CombinedSelectorNode; +use Symfony\Component\CssSelector\Node\ElementNode; + +class CombinedSelectorNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new CombinedSelectorNode(new ElementNode(), '>', new ElementNode()), 'CombinedSelector[Element[*] > Element[*]]'), + array(new CombinedSelectorNode(new ElementNode(), ' ', new ElementNode()), 'CombinedSelector[Element[*] Element[*]]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new CombinedSelectorNode(new ElementNode(), '>', new ElementNode()), 0), + array(new CombinedSelectorNode(new ElementNode(null, 'element'), '>', new ElementNode()), 1), + array(new CombinedSelectorNode(new ElementNode(null, 'element'), '>', new ElementNode(null, 'element')), 2), + ); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/ElementNodeTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/ElementNodeTest.php new file mode 100644 index 0000000..1db6a59 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/ElementNodeTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ElementNode; + +class ElementNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new ElementNode(), 'Element[*]'), + array(new ElementNode(null, 'element'), 'Element[element]'), + array(new ElementNode('namespace', 'element'), 'Element[namespace|element]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new ElementNode(), 0), + array(new ElementNode(null, 'element'), 1), + array(new ElementNode('namespace', 'element'),1), + ); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/FunctionNodeTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/FunctionNodeTest.php new file mode 100644 index 0000000..ee3ce51 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/FunctionNodeTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\FunctionNode; +use Symfony\Component\CssSelector\Parser\Token; + +class FunctionNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new FunctionNode(new ElementNode(), 'function'), 'Function[Element[*]:function()]'), + array(new FunctionNode(new ElementNode(), 'function', array( + new Token(Token::TYPE_IDENTIFIER, 'value', 0), + )), "Function[Element[*]:function(['value'])]"), + array(new FunctionNode(new ElementNode(), 'function', array( + new Token(Token::TYPE_STRING, 'value1', 0), + new Token(Token::TYPE_NUMBER, 'value2', 0), + )), "Function[Element[*]:function(['value1', 'value2'])]"), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new FunctionNode(new ElementNode(), 'function'), 10), + array(new FunctionNode(new ElementNode(), 'function', array( + new Token(Token::TYPE_IDENTIFIER, 'value', 0), + )), 10), + array(new FunctionNode(new ElementNode(), 'function', array( + new Token(Token::TYPE_STRING, 'value1', 0), + new Token(Token::TYPE_NUMBER, 'value2', 0), + )), 10), + ); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/HashNodeTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/HashNodeTest.php new file mode 100644 index 0000000..8554b22 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/HashNodeTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\HashNode; +use Symfony\Component\CssSelector\Node\ElementNode; + +class HashNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new HashNode(new ElementNode(), 'id'), 'Hash[Element[*]#id]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new HashNode(new ElementNode(), 'id'), 100), + array(new HashNode(new ElementNode(null, 'id'), 'class'), 101), + ); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/NegationNodeTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/NegationNodeTest.php new file mode 100644 index 0000000..edf4552 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/NegationNodeTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ClassNode; +use Symfony\Component\CssSelector\Node\NegationNode; +use Symfony\Component\CssSelector\Node\ElementNode; + +class NegationNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new NegationNode(new ElementNode(), new ClassNode(new ElementNode(), 'class')), 'Negation[Element[*]:not(Class[Element[*].class])]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new NegationNode(new ElementNode(), new ClassNode(new ElementNode(), 'class')), 10), + ); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/PseudoNodeTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/PseudoNodeTest.php new file mode 100644 index 0000000..bc57813 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/PseudoNodeTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\PseudoNode; + +class PseudoNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new PseudoNode(new ElementNode(), 'pseudo'), 'Pseudo[Element[*]:pseudo]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new PseudoNode(new ElementNode(), 'pseudo'), 10), + ); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/SelectorNodeTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/SelectorNodeTest.php new file mode 100644 index 0000000..5badf71 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/SelectorNodeTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\ElementNode; +use Symfony\Component\CssSelector\Node\SelectorNode; + +class SelectorNodeTest extends AbstractNodeTest +{ + public function getToStringConversionTestData() + { + return array( + array(new SelectorNode(new ElementNode()), 'Selector[Element[*]]'), + array(new SelectorNode(new ElementNode(), 'pseudo'), 'Selector[Element[*]::pseudo]'), + ); + } + + public function getSpecificityValueTestData() + { + return array( + array(new SelectorNode(new ElementNode()), 0), + array(new SelectorNode(new ElementNode(), 'pseudo'), 1), + ); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/SpecificityTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/SpecificityTest.php new file mode 100644 index 0000000..1f200cf --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Node/SpecificityTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Node; + +use Symfony\Component\CssSelector\Node\Specificity; + +class SpecificityTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getValueTestData */ + public function testValue(Specificity $specificity, $value) + { + $this->assertEquals($value, $specificity->getValue()); + } + + /** @dataProvider getValueTestData */ + public function testPlusValue(Specificity $specificity, $value) + { + $this->assertEquals($value + 123, $specificity->plus(new Specificity(1, 2, 3))->getValue()); + } + + public function getValueTestData() + { + return array( + array(new Specificity(0, 0, 0), 0), + array(new Specificity(0, 0, 2), 2), + array(new Specificity(0, 3, 0), 30), + array(new Specificity(4, 0, 0), 400), + array(new Specificity(4, 3, 2), 432), + ); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/AbstractHandlerTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/AbstractHandlerTest.php new file mode 100644 index 0000000..a0d80a1 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/AbstractHandlerTest.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Handler; + +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; + +/** + * @author Jean-François Simon + */ +abstract class AbstractHandlerTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getHandleValueTestData */ + public function testHandleValue($value, Token $expectedToken, $remainingContent) + { + $reader = new Reader($value); + $stream = new TokenStream(); + + $this->assertTrue($this->generateHandler()->handle($reader, $stream)); + $this->assertEquals($expectedToken, $stream->getNext()); + $this->assertRemainingContent($reader, $remainingContent); + } + + /** @dataProvider getDontHandleValueTestData */ + public function testDontHandleValue($value) + { + $reader = new Reader($value); + $stream = new TokenStream(); + + $this->assertFalse($this->generateHandler()->handle($reader, $stream)); + $this->assertStreamEmpty($stream); + $this->assertRemainingContent($reader, $value); + } + + abstract public function getHandleValueTestData(); + abstract public function getDontHandleValueTestData(); + abstract protected function generateHandler(); + + protected function assertStreamEmpty(TokenStream $stream) + { + $property = new \ReflectionProperty($stream, 'tokens'); + $property->setAccessible(true); + + $this->assertEquals(array(), $property->getValue($stream)); + } + + protected function assertRemainingContent(Reader $reader, $remainingContent) + { + if ('' === $remainingContent) { + $this->assertEquals(0, $reader->getRemainingLength()); + $this->assertTrue($reader->isEOF()); + } else { + $this->assertEquals(strlen($remainingContent), $reader->getRemainingLength()); + $this->assertEquals(0, $reader->getOffset($remainingContent)); + } + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/CommentHandlerTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/CommentHandlerTest.php new file mode 100644 index 0000000..27e53cd --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/CommentHandlerTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Handler; + +use Symfony\Component\CssSelector\Parser\Handler\CommentHandler; +use Symfony\Component\CssSelector\Parser\Reader; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; + +class CommentHandlerTest extends AbstractHandlerTest +{ + /** @dataProvider getHandleValueTestData */ + public function testHandleValue($value, Token $unusedArgument, $remainingContent) + { + $reader = new Reader($value); + $stream = new TokenStream(); + + $this->assertTrue($this->generateHandler()->handle($reader, $stream)); + // comments are ignored (not pushed as token in stream) + $this->assertStreamEmpty($stream); + $this->assertRemainingContent($reader, $remainingContent); + } + + public function getHandleValueTestData() + { + return array( + // 2nd argument only exists for inherited method compatibility + array('/* comment */', new Token(null, null, null), ''), + array('/* comment */foo', new Token(null, null, null), 'foo'), + ); + } + + public function getDontHandleValueTestData() + { + return array( + array('>'), + array('+'), + array(' '), + ); + } + + protected function generateHandler() + { + return new CommentHandler(); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/HashHandlerTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/HashHandlerTest.php new file mode 100644 index 0000000..04f71b7 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/HashHandlerTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Handler; + +use Symfony\Component\CssSelector\Parser\Handler\HashHandler; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; + +class HashHandlerTest extends AbstractHandlerTest +{ + public function getHandleValueTestData() + { + return array( + array('#id', new Token(Token::TYPE_HASH, 'id', 0), ''), + array('#123', new Token(Token::TYPE_HASH, '123', 0), ''), + + array('#id.class', new Token(Token::TYPE_HASH, 'id', 0), '.class'), + array('#id element', new Token(Token::TYPE_HASH, 'id', 0), ' element'), + ); + } + + public function getDontHandleValueTestData() + { + return array( + array('id'), + array('123'), + array('<'), + array('<'), + array('#'), + ); + } + + protected function generateHandler() + { + $patterns = new TokenizerPatterns(); + + return new HashHandler($patterns, new TokenizerEscaping($patterns)); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/IdentifierHandlerTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/IdentifierHandlerTest.php new file mode 100644 index 0000000..84ee1d8 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/IdentifierHandlerTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Handler; + +use Symfony\Component\CssSelector\Parser\Handler\IdentifierHandler; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; + +class IdentifierHandlerTest extends AbstractHandlerTest +{ + public function getHandleValueTestData() + { + return array( + array('foo', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), ''), + array('foo|bar', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), '|bar'), + array('foo.class', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), '.class'), + array('foo[attr]', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), '[attr]'), + array('foo bar', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), ' bar'), + ); + } + + public function getDontHandleValueTestData() + { + return array( + array('>'), + array('+'), + array(' '), + array('*|foo'), + array('/* comment */'), + ); + } + + protected function generateHandler() + { + $patterns = new TokenizerPatterns(); + + return new IdentifierHandler($patterns, new TokenizerEscaping($patterns)); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/NumberHandlerTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/NumberHandlerTest.php new file mode 100644 index 0000000..e8782e8 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/NumberHandlerTest.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Handler; + +use Symfony\Component\CssSelector\Parser\Handler\NumberHandler; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; + +class NumberHandlerTest extends AbstractHandlerTest +{ + public function getHandleValueTestData() + { + return array( + array('12', new Token(Token::TYPE_NUMBER, '12', 0), ''), + array('12.34', new Token(Token::TYPE_NUMBER, '12.34', 0), ''), + array('+12.34', new Token(Token::TYPE_NUMBER, '+12.34', 0), ''), + array('-12.34', new Token(Token::TYPE_NUMBER, '-12.34', 0), ''), + + array('12 arg', new Token(Token::TYPE_NUMBER, '12', 0), ' arg'), + array('12]', new Token(Token::TYPE_NUMBER, '12', 0), ']'), + ); + } + + public function getDontHandleValueTestData() + { + return array( + array('hello'), + array('>'), + array('+'), + array(' '), + array('/* comment */'), + ); + } + + protected function generateHandler() + { + $patterns = new TokenizerPatterns(); + + return new NumberHandler($patterns, new TokenizerEscaping($patterns)); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/StringHandlerTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/StringHandlerTest.php new file mode 100644 index 0000000..32ce59a --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/StringHandlerTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Handler; + +use Symfony\Component\CssSelector\Parser\Handler\StringHandler; +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; +use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; + +class StringHandlerTest extends AbstractHandlerTest +{ + public function getHandleValueTestData() + { + return array( + array('"hello"', new Token(Token::TYPE_STRING, 'hello', 1), ''), + array('"1"', new Token(Token::TYPE_STRING, '1', 1), ''), + array('" "', new Token(Token::TYPE_STRING, ' ', 1), ''), + array('""', new Token(Token::TYPE_STRING, '', 1), ''), + array("'hello'", new Token(Token::TYPE_STRING, 'hello', 1), ''), + + array("'foo'bar", new Token(Token::TYPE_STRING, 'foo', 1), 'bar'), + ); + } + + public function getDontHandleValueTestData() + { + return array( + array('hello'), + array('>'), + array('1'), + array(' '), + ); + } + + protected function generateHandler() + { + $patterns = new TokenizerPatterns(); + + return new StringHandler($patterns, new TokenizerEscaping($patterns)); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/WhitespaceHandlerTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/WhitespaceHandlerTest.php new file mode 100644 index 0000000..0d91404 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Handler/WhitespaceHandlerTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Handler; + +use Symfony\Component\CssSelector\Parser\Handler\WhitespaceHandler; +use Symfony\Component\CssSelector\Parser\Token; + +class WhitespaceHandlerTest extends AbstractHandlerTest +{ + public function getHandleValueTestData() + { + return array( + array(' ', new Token(Token::TYPE_WHITESPACE, ' ', 0), ''), + array("\n", new Token(Token::TYPE_WHITESPACE, "\n", 0), ''), + array("\t", new Token(Token::TYPE_WHITESPACE, "\t", 0), ''), + + array(' foo', new Token(Token::TYPE_WHITESPACE, ' ', 0), 'foo'), + array(' .foo', new Token(Token::TYPE_WHITESPACE, ' ', 0), '.foo'), + ); + } + + public function getDontHandleValueTestData() + { + return array( + array('>'), + array('1'), + array('a'), + ); + } + + protected function generateHandler() + { + return new WhitespaceHandler(); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/ParserTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/ParserTest.php new file mode 100644 index 0000000..d94bbbc --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/ParserTest.php @@ -0,0 +1,247 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser; + +use Symfony\Component\CssSelector\Exception\SyntaxErrorException; +use Symfony\Component\CssSelector\Node\FunctionNode; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\Parser; +use Symfony\Component\CssSelector\Parser\Token; + +class ParserTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getParserTestData */ + public function testParser($source, $representation) + { + $parser = new Parser(); + + $this->assertEquals($representation, array_map(function (SelectorNode $node) { + return (string) $node->getTree(); + }, $parser->parse($source))); + } + + /** @dataProvider getParserExceptionTestData */ + public function testParserException($source, $message) + { + $parser = new Parser(); + + try { + $parser->parse($source); + $this->fail('Parser should throw a SyntaxErrorException.'); + } catch (SyntaxErrorException $e) { + $this->assertEquals($message, $e->getMessage()); + } + } + + /** @dataProvider getPseudoElementsTestData */ + public function testPseudoElements($source, $element, $pseudo) + { + $parser = new Parser(); + $selectors = $parser->parse($source); + $this->assertEquals(1, count($selectors)); + + /** @var SelectorNode $selector */ + $selector = $selectors[0]; + $this->assertEquals($element, (string) $selector->getTree()); + $this->assertEquals($pseudo, (string) $selector->getPseudoElement()); + } + + /** @dataProvider getSpecificityTestData */ + public function testSpecificity($source, $value) + { + $parser = new Parser(); + $selectors = $parser->parse($source); + $this->assertEquals(1, count($selectors)); + + /** @var SelectorNode $selector */ + $selector = $selectors[0]; + $this->assertEquals($value, $selector->getSpecificity()->getValue()); + } + + /** @dataProvider getParseSeriesTestData */ + public function testParseSeries($series, $a, $b) + { + $parser = new Parser(); + $selectors = $parser->parse(sprintf(':nth-child(%s)', $series)); + $this->assertEquals(1, count($selectors)); + + /** @var FunctionNode $function */ + $function = $selectors[0]->getTree(); + $this->assertEquals(array($a, $b), Parser::parseSeries($function->getArguments())); + } + + /** @dataProvider getParseSeriesExceptionTestData */ + public function testParseSeriesException($series) + { + $parser = new Parser(); + $selectors = $parser->parse(sprintf(':nth-child(%s)', $series)); + $this->assertEquals(1, count($selectors)); + + /** @var FunctionNode $function */ + $function = $selectors[0]->getTree(); + $this->setExpectedException('Symfony\Component\CssSelector\Exception\SyntaxErrorException'); + Parser::parseSeries($function->getArguments()); + } + + public function getParserTestData() + { + return array( + array('*', array('Element[*]')), + array('*|*', array('Element[*]')), + array('*|foo', array('Element[foo]')), + array('foo|*', array('Element[foo|*]')), + array('foo|bar', array('Element[foo|bar]')), + array('#foo#bar', array('Hash[Hash[Element[*]#foo]#bar]')), + array('div>.foo', array('CombinedSelector[Element[div] > Class[Element[*].foo]]')), + array('div> .foo', array('CombinedSelector[Element[div] > Class[Element[*].foo]]')), + array('div >.foo', array('CombinedSelector[Element[div] > Class[Element[*].foo]]')), + array('div > .foo', array('CombinedSelector[Element[div] > Class[Element[*].foo]]')), + array("div \n> \t \t .foo", array('CombinedSelector[Element[div] > Class[Element[*].foo]]')), + array('td.foo,.bar', array('Class[Element[td].foo]', 'Class[Element[*].bar]')), + array('td.foo, .bar', array('Class[Element[td].foo]', 'Class[Element[*].bar]')), + array("td.foo\t\r\n\f ,\t\r\n\f .bar", array('Class[Element[td].foo]', 'Class[Element[*].bar]')), + array('td.foo,.bar', array('Class[Element[td].foo]', 'Class[Element[*].bar]')), + array('td.foo, .bar', array('Class[Element[td].foo]', 'Class[Element[*].bar]')), + array("td.foo\t\r\n\f ,\t\r\n\f .bar", array('Class[Element[td].foo]', 'Class[Element[*].bar]')), + array('div, td.foo, div.bar span', array('Element[div]', 'Class[Element[td].foo]', 'CombinedSelector[Class[Element[div].bar] Element[span]]')), + array('div > p', array('CombinedSelector[Element[div] > Element[p]]')), + array('td:first', array('Pseudo[Element[td]:first]')), + array('td :first', array('CombinedSelector[Element[td] Pseudo[Element[*]:first]]')), + array('a[name]', array('Attribute[Element[a][name]]')), + array("a[ name\t]", array('Attribute[Element[a][name]]')), + array('a [name]', array('CombinedSelector[Element[a] Attribute[Element[*][name]]]')), + array('a[rel="include"]', array("Attribute[Element[a][rel = 'include']]")), + array('a[rel = include]', array("Attribute[Element[a][rel = 'include']]")), + array("a[hreflang |= 'en']", array("Attribute[Element[a][hreflang |= 'en']]")), + array('a[hreflang|=en]', array("Attribute[Element[a][hreflang |= 'en']]")), + array('div:nth-child(10)', array("Function[Element[div]:nth-child(['10'])]")), + array(':nth-child(2n+2)', array("Function[Element[*]:nth-child(['2', 'n', '+2'])]")), + array('div:nth-of-type(10)', array("Function[Element[div]:nth-of-type(['10'])]")), + array('div div:nth-of-type(10) .aclass', array("CombinedSelector[CombinedSelector[Element[div] Function[Element[div]:nth-of-type(['10'])]] Class[Element[*].aclass]]")), + array('label:only', array('Pseudo[Element[label]:only]')), + array('a:lang(fr)', array("Function[Element[a]:lang(['fr'])]")), + array('div:contains("foo")', array("Function[Element[div]:contains(['foo'])]")), + array('div#foobar', array('Hash[Element[div]#foobar]')), + array('div:not(div.foo)', array('Negation[Element[div]:not(Class[Element[div].foo])]')), + array('td ~ th', array('CombinedSelector[Element[td] ~ Element[th]]')), + ); + } + + public function getParserExceptionTestData() + { + return array( + array('attributes(href)/html/body/a', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '(', 10))->getMessage()), + array('attributes(href)', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '(', 10))->getMessage()), + array('html/body/a', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '/', 4))->getMessage()), + array(' ', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_FILE_END, '', 1))->getMessage()), + array('div, ', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_FILE_END, '', 5))->getMessage()), + array(' , div', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, ',', 1))->getMessage()), + array('p, , div', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, ',', 3))->getMessage()), + array('div > ', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_FILE_END, '', 6))->getMessage()), + array(' > div', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '>', 2))->getMessage()), + array('foo|#bar', SyntaxErrorException::unexpectedToken('identifier or "*"', new Token(Token::TYPE_HASH, 'bar', 4))->getMessage()), + array('#.foo', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '#', 0))->getMessage()), + array('.#foo', SyntaxErrorException::unexpectedToken('identifier', new Token(Token::TYPE_HASH, 'foo', 1))->getMessage()), + array(':#foo', SyntaxErrorException::unexpectedToken('identifier', new Token(Token::TYPE_HASH, 'foo', 1))->getMessage()), + array('[*]', SyntaxErrorException::unexpectedToken('"|"', new Token(Token::TYPE_DELIMITER, ']', 2))->getMessage()), + array('[foo|]', SyntaxErrorException::unexpectedToken('identifier', new Token(Token::TYPE_DELIMITER, ']', 5))->getMessage()), + array('[#]', SyntaxErrorException::unexpectedToken('identifier or "*"', new Token(Token::TYPE_DELIMITER, '#', 1))->getMessage()), + array('[foo=#]', SyntaxErrorException::unexpectedToken('string or identifier', new Token(Token::TYPE_DELIMITER, '#', 5))->getMessage()), + array(':nth-child()', SyntaxErrorException::unexpectedToken('at least one argument', new Token(Token::TYPE_DELIMITER, ')', 11))->getMessage()), + array('[href]a', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_IDENTIFIER, 'a', 6))->getMessage()), + array('[rel:stylesheet]', SyntaxErrorException::unexpectedToken('operator', new Token(Token::TYPE_DELIMITER, ':', 4))->getMessage()), + array('[rel=stylesheet', SyntaxErrorException::unexpectedToken('"]"', new Token(Token::TYPE_FILE_END, '', 15))->getMessage()), + array(':lang(fr', SyntaxErrorException::unexpectedToken('an argument', new Token(Token::TYPE_FILE_END, '', 8))->getMessage()), + array(':contains("foo', SyntaxErrorException::unclosedString(10)->getMessage()), + array('foo!', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '!', 3))->getMessage()), + ); + } + + public function getPseudoElementsTestData() + { + return array( + array('foo', 'Element[foo]', ''), + array('*', 'Element[*]', ''), + array(':empty', 'Pseudo[Element[*]:empty]', ''), + array(':BEfore', 'Element[*]', 'before'), + array(':aftER', 'Element[*]', 'after'), + array(':First-Line', 'Element[*]', 'first-line'), + array(':First-Letter', 'Element[*]', 'first-letter'), + array('::befoRE', 'Element[*]', 'before'), + array('::AFter', 'Element[*]', 'after'), + array('::firsT-linE', 'Element[*]', 'first-line'), + array('::firsT-letteR', 'Element[*]', 'first-letter'), + array('::Selection', 'Element[*]', 'selection'), + array('foo:after', 'Element[foo]', 'after'), + array('foo::selection', 'Element[foo]', 'selection'), + array('lorem#ipsum ~ a#b.c[href]:empty::selection', 'CombinedSelector[Hash[Element[lorem]#ipsum] ~ Pseudo[Attribute[Class[Hash[Element[a]#b].c][href]]:empty]]', 'selection'), + ); + } + + public function getSpecificityTestData() + { + return array( + array('*', 0), + array(' foo', 1), + array(':empty ', 10), + array(':before', 1), + array('*:before', 1), + array(':nth-child(2)', 10), + array('.bar', 10), + array('[baz]', 10), + array('[baz="4"]', 10), + array('[baz^="4"]', 10), + array('#lipsum', 100), + array(':not(*)', 0), + array(':not(foo)', 1), + array(':not(.foo)', 10), + array(':not([foo])', 10), + array(':not(:empty)', 10), + array(':not(#foo)', 100), + array('foo:empty', 11), + array('foo:before', 2), + array('foo::before', 2), + array('foo:empty::before', 12), + array('#lorem + foo#ipsum:first-child > bar:first-line', 213), + ); + } + + public function getParseSeriesTestData() + { + return array( + array('1n+3', 1, 3), + array('1n +3', 1, 3), + array('1n + 3', 1, 3), + array('1n+ 3', 1, 3), + array('1n-3', 1, -3), + array('1n -3', 1, -3), + array('1n - 3', 1, -3), + array('1n- 3', 1, -3), + array('n-5', 1, -5), + array('odd', 2, 1), + array('even', 2, 0), + array('3n', 3, 0), + array('n', 1, 0), + array('+n', 1, 0), + array('-n', -1, 0), + array('5', 0, 5), + ); + } + + public function getParseSeriesExceptionTestData() + { + return array( + array('foo'), + array('n+'), + ); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/ReaderTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/ReaderTest.php new file mode 100644 index 0000000..03c054e --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/ReaderTest.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser; + +use Symfony\Component\CssSelector\Parser\Reader; + +class ReaderTest extends \PHPUnit_Framework_TestCase +{ + public function testIsEOF() + { + $reader = new Reader(''); + $this->assertTrue($reader->isEOF()); + + $reader = new Reader('hello'); + $this->assertFalse($reader->isEOF()); + + $this->assignPosition($reader, 2); + $this->assertFalse($reader->isEOF()); + + $this->assignPosition($reader, 5); + $this->assertTrue($reader->isEOF()); + } + + public function testGetRemainingLength() + { + $reader = new Reader('hello'); + $this->assertEquals(5, $reader->getRemainingLength()); + + $this->assignPosition($reader, 2); + $this->assertEquals(3, $reader->getRemainingLength()); + + $this->assignPosition($reader, 5); + $this->assertEquals(0, $reader->getRemainingLength()); + } + + public function testGetSubstring() + { + $reader = new Reader('hello'); + $this->assertEquals('he', $reader->getSubstring(2)); + $this->assertEquals('el', $reader->getSubstring(2, 1)); + + $this->assignPosition($reader, 2); + $this->assertEquals('ll', $reader->getSubstring(2)); + $this->assertEquals('lo', $reader->getSubstring(2, 1)); + } + + public function testGetOffset() + { + $reader = new Reader('hello'); + $this->assertEquals(2, $reader->getOffset('ll')); + $this->assertFalse($reader->getOffset('w')); + + $this->assignPosition($reader, 2); + $this->assertEquals(0, $reader->getOffset('ll')); + $this->assertFalse($reader->getOffset('he')); + } + + public function testFindPattern() + { + $reader = new Reader('hello'); + + $this->assertFalse($reader->findPattern('/world/')); + $this->assertEquals(array('hello', 'h'), $reader->findPattern('/^([a-z]).*/')); + + $this->assignPosition($reader, 2); + $this->assertFalse($reader->findPattern('/^h.*/')); + $this->assertEquals(array('llo'), $reader->findPattern('/^llo$/')); + } + + public function testMoveForward() + { + $reader = new Reader('hello'); + $this->assertEquals(0, $reader->getPosition()); + + $reader->moveForward(2); + $this->assertEquals(2, $reader->getPosition()); + } + + public function testToEnd() + { + $reader = new Reader('hello'); + $reader->moveToEnd(); + $this->assertTrue($reader->isEOF()); + } + + private function assignPosition(Reader $reader, $value) + { + $position = new \ReflectionProperty($reader, 'position'); + $position->setAccessible(true); + $position->setValue($reader, $value); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/ClassParserTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/ClassParserTest.php new file mode 100644 index 0000000..403c7af --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/ClassParserTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\Shortcut\ClassParser; + +/** + * @author Jean-François Simon + */ +class ClassParserTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getParseTestData */ + public function testParse($source, $representation) + { + $parser = new ClassParser(); + $selectors = $parser->parse($source); + $this->assertEquals(1, count($selectors)); + + /** @var SelectorNode $selector */ + $selector = $selectors[0]; + $this->assertEquals($representation, (string) $selector->getTree()); + } + + public function getParseTestData() + { + return array( + array('.testclass', 'Class[Element[*].testclass]'), + array('testel.testclass', 'Class[Element[testel].testclass]'), + array('testns|.testclass', 'Class[Element[testns|*].testclass]'), + array('testns|*.testclass', 'Class[Element[testns|*].testclass]'), + array('testns|testel.testclass', 'Class[Element[testns|testel].testclass]'), + ); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/ElementParserTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/ElementParserTest.php new file mode 100644 index 0000000..2e436da --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/ElementParserTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\Shortcut\ElementParser; + +/** + * @author Jean-François Simon + */ +class ElementParserTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getParseTestData */ + public function testParse($source, $representation) + { + $parser = new ElementParser(); + $selectors = $parser->parse($source); + $this->assertEquals(1, count($selectors)); + + /** @var SelectorNode $selector */ + $selector = $selectors[0]; + $this->assertEquals($representation, (string) $selector->getTree()); + } + + public function getParseTestData() + { + return array( + array('*', 'Element[*]'), + array('testel', 'Element[testel]'), + array('testns|*', 'Element[testns|*]'), + array('testns|testel', 'Element[testns|testel]'), + ); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/EmptyStringParserTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/EmptyStringParserTest.php new file mode 100644 index 0000000..8477fa1 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/EmptyStringParserTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\Shortcut\EmptyStringParser; + +/** + * @author Jean-François Simon + */ +class EmptyStringParserTest extends \PHPUnit_Framework_TestCase +{ + public function testParse() + { + $parser = new EmptyStringParser(); + $selectors = $parser->parse(''); + $this->assertEquals(1, count($selectors)); + + /** @var SelectorNode $selector */ + $selector = $selectors[0]; + $this->assertEquals('Element[*]', (string) $selector->getTree()); + + $selectors = $parser->parse('this will produce an empty array'); + $this->assertEquals(0, count($selectors)); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/HashParserTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/HashParserTest.php new file mode 100644 index 0000000..41ecfc6 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/Shortcut/HashParserTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser\Shortcut; + +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\Shortcut\HashParser; + +/** + * @author Jean-François Simon + */ +class HashParserTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getParseTestData */ + public function testParse($source, $representation) + { + $parser = new HashParser(); + $selectors = $parser->parse($source); + $this->assertEquals(1, count($selectors)); + + /** @var SelectorNode $selector */ + $selector = $selectors[0]; + $this->assertEquals($representation, (string) $selector->getTree()); + } + + public function getParseTestData() + { + return array( + array('#testid', 'Hash[Element[*]#testid]'), + array('testel#testid', 'Hash[Element[testel]#testid]'), + array('testns|#testid', 'Hash[Element[testns|*]#testid]'), + array('testns|*#testid', 'Hash[Element[testns|*]#testid]'), + array('testns|testel#testid', 'Hash[Element[testns|testel]#testid]'), + ); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/TokenStreamTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/TokenStreamTest.php new file mode 100644 index 0000000..8f3253a --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/Parser/TokenStreamTest.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\Parser; + +use Symfony\Component\CssSelector\Parser\Token; +use Symfony\Component\CssSelector\Parser\TokenStream; + +class TokenStreamTest extends \PHPUnit_Framework_TestCase +{ + public function testGetNext() + { + $stream = new TokenStream(); + $stream->push($t1 = new Token(Token::TYPE_IDENTIFIER, 'h1', 0)); + $stream->push($t2 = new Token(Token::TYPE_DELIMITER, '.', 2)); + $stream->push($t3 = new Token(Token::TYPE_IDENTIFIER, 'title', 3)); + + $this->assertSame($t1, $stream->getNext()); + $this->assertSame($t2, $stream->getNext()); + $this->assertSame($t3, $stream->getNext()); + } + + public function testGetPeek() + { + $stream = new TokenStream(); + $stream->push($t1 = new Token(Token::TYPE_IDENTIFIER, 'h1', 0)); + $stream->push($t2 = new Token(Token::TYPE_DELIMITER, '.', 2)); + $stream->push($t3 = new Token(Token::TYPE_IDENTIFIER, 'title', 3)); + + $this->assertSame($t1, $stream->getPeek()); + $this->assertSame($t1, $stream->getNext()); + $this->assertSame($t2, $stream->getPeek()); + $this->assertSame($t2, $stream->getPeek()); + $this->assertSame($t2, $stream->getNext()); + } + + public function testGetNextIdentifier() + { + $stream = new TokenStream(); + $stream->push(new Token(Token::TYPE_IDENTIFIER, 'h1', 0)); + + $this->assertEquals('h1', $stream->getNextIdentifier()); + } + + public function testFailToGetNextIdentifier() + { + $this->setExpectedException('Symfony\Component\CssSelector\Exception\SyntaxErrorException'); + + $stream = new TokenStream(); + $stream->push(new Token(Token::TYPE_DELIMITER, '.', 2)); + $stream->getNextIdentifier(); + } + + public function testGetNextIdentifierOrStar() + { + $stream = new TokenStream(); + + $stream->push(new Token(Token::TYPE_IDENTIFIER, 'h1', 0)); + $this->assertEquals('h1', $stream->getNextIdentifierOrStar()); + + $stream->push(new Token(Token::TYPE_DELIMITER, '*', 0)); + $this->assertNull($stream->getNextIdentifierOrStar()); + } + + public function testFailToGetNextIdentifierOrStar() + { + $this->setExpectedException('Symfony\Component\CssSelector\Exception\SyntaxErrorException'); + + $stream = new TokenStream(); + $stream->push(new Token(Token::TYPE_DELIMITER, '.', 2)); + $stream->getNextIdentifierOrStar(); + } + + public function testSkipWhitespace() + { + $stream = new TokenStream(); + $stream->push($t1 = new Token(Token::TYPE_IDENTIFIER, 'h1', 0)); + $stream->push($t2 = new Token(Token::TYPE_WHITESPACE, ' ', 2)); + $stream->push($t3 = new Token(Token::TYPE_IDENTIFIER, 'h1', 3)); + + $stream->skipWhitespace(); + $this->assertSame($t1, $stream->getNext()); + + $stream->skipWhitespace(); + $this->assertSame($t3, $stream->getNext()); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/Fixtures/ids.html b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/Fixtures/ids.html new file mode 100644 index 0000000..5799fad --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/Fixtures/ids.html @@ -0,0 +1,48 @@ + + + + +
    + + + + link +
      +
    1. content
    2. +
    3. +
      +
      +
    4. +
    5. +
    6. +
    7. +
    8. +
    9. +
    +

    + hi there + guy + + + + + + + +

    + + +
    +

    +
      +
    + + + + +
    +
    + diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/Fixtures/lang.xml b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/Fixtures/lang.xml new file mode 100644 index 0000000..14f8dbe --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/Fixtures/lang.xml @@ -0,0 +1,11 @@ + + a + b + c + d + e + f + + + + diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/Fixtures/shakespear.html b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/Fixtures/shakespear.html new file mode 100644 index 0000000..15d1ad3 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/Fixtures/shakespear.html @@ -0,0 +1,308 @@ + + + + + + +
    +
    +

    As You Like It

    +
    + by William Shakespeare +
    +
    +

    ACT I, SCENE III. A room in the palace.

    +
    +
    Enter CELIA and ROSALIND
    +
    +
    CELIA
    +
    +
    Why, cousin! why, Rosalind! Cupid have mercy! not a word?
    +
    +
    ROSALIND
    +
    +
    Not one to throw at a dog.
    +
    +
    CELIA
    +
    +
    No, thy words are too precious to be cast away upon
    +
    curs; throw some of them at me; come, lame me with reasons.
    +
    +
    ROSALIND
    +
    CELIA
    +
    +
    But is all this for your father?
    +
    +
    +
    Then there were two cousins laid up; when the one
    +
    should be lamed with reasons and the other mad
    +
    without any.
    +
    +
    ROSALIND
    +
    +
    No, some of it is for my child's father. O, how
    +
    full of briers is this working-day world!
    +
    +
    CELIA
    +
    +
    They are but burs, cousin, thrown upon thee in
    +
    holiday foolery: if we walk not in the trodden
    +
    paths our very petticoats will catch them.
    +
    +
    ROSALIND
    +
    +
    I could shake them off my coat: these burs are in my heart.
    +
    +
    CELIA
    +
    +
    Hem them away.
    +
    +
    ROSALIND
    +
    +
    I would try, if I could cry 'hem' and have him.
    +
    +
    CELIA
    +
    +
    Come, come, wrestle with thy affections.
    +
    +
    ROSALIND
    +
    +
    O, they take the part of a better wrestler than myself!
    +
    +
    CELIA
    +
    +
    O, a good wish upon you! you will try in time, in
    +
    despite of a fall. But, turning these jests out of
    +
    service, let us talk in good earnest: is it
    +
    possible, on such a sudden, you should fall into so
    +
    strong a liking with old Sir Rowland's youngest son?
    +
    +
    ROSALIND
    +
    +
    The duke my father loved his father dearly.
    +
    +
    CELIA
    +
    +
    Doth it therefore ensue that you should love his son
    +
    dearly? By this kind of chase, I should hate him,
    +
    for my father hated his father dearly; yet I hate
    +
    not Orlando.
    +
    +
    ROSALIND
    +
    +
    No, faith, hate him not, for my sake.
    +
    +
    CELIA
    +
    +
    Why should I not? doth he not deserve well?
    +
    +
    ROSALIND
    +
    +
    Let me love him for that, and do you love him
    +
    because I do. Look, here comes the duke.
    +
    +
    CELIA
    +
    +
    With his eyes full of anger.
    +
    Enter DUKE FREDERICK, with Lords
    +
    +
    DUKE FREDERICK
    +
    +
    Mistress, dispatch you with your safest haste
    +
    And get you from our court.
    +
    +
    ROSALIND
    +
    +
    Me, uncle?
    +
    +
    DUKE FREDERICK
    +
    +
    You, cousin
    +
    Within these ten days if that thou be'st found
    +
    So near our public court as twenty miles,
    +
    Thou diest for it.
    +
    +
    ROSALIND
    +
    +
    I do beseech your grace,
    +
    Let me the knowledge of my fault bear with me:
    +
    If with myself I hold intelligence
    +
    Or have acquaintance with mine own desires,
    +
    If that I do not dream or be not frantic,--
    +
    As I do trust I am not--then, dear uncle,
    +
    Never so much as in a thought unborn
    +
    Did I offend your highness.
    +
    +
    DUKE FREDERICK
    +
    +
    Thus do all traitors:
    +
    If their purgation did consist in words,
    +
    They are as innocent as grace itself:
    +
    Let it suffice thee that I trust thee not.
    +
    +
    ROSALIND
    +
    +
    Yet your mistrust cannot make me a traitor:
    +
    Tell me whereon the likelihood depends.
    +
    +
    DUKE FREDERICK
    +
    +
    Thou art thy father's daughter; there's enough.
    +
    +
    ROSALIND
    +
    +
    So was I when your highness took his dukedom;
    +
    So was I when your highness banish'd him:
    +
    Treason is not inherited, my lord;
    +
    Or, if we did derive it from our friends,
    +
    What's that to me? my father was no traitor:
    +
    Then, good my liege, mistake me not so much
    +
    To think my poverty is treacherous.
    +
    +
    CELIA
    +
    +
    Dear sovereign, hear me speak.
    +
    +
    DUKE FREDERICK
    +
    +
    Ay, Celia; we stay'd her for your sake,
    +
    Else had she with her father ranged along.
    +
    +
    CELIA
    +
    +
    I did not then entreat to have her stay;
    +
    It was your pleasure and your own remorse:
    +
    I was too young that time to value her;
    +
    But now I know her: if she be a traitor,
    +
    Why so am I; we still have slept together,
    +
    Rose at an instant, learn'd, play'd, eat together,
    +
    And wheresoever we went, like Juno's swans,
    +
    Still we went coupled and inseparable.
    +
    +
    DUKE FREDERICK
    +
    +
    She is too subtle for thee; and her smoothness,
    +
    Her very silence and her patience
    +
    Speak to the people, and they pity her.
    +
    Thou art a fool: she robs thee of thy name;
    +
    And thou wilt show more bright and seem more virtuous
    +
    When she is gone. Then open not thy lips:
    +
    Firm and irrevocable is my doom
    +
    Which I have pass'd upon her; she is banish'd.
    +
    +
    CELIA
    +
    +
    Pronounce that sentence then on me, my liege:
    +
    I cannot live out of her company.
    +
    +
    DUKE FREDERICK
    +
    +
    You are a fool. You, niece, provide yourself:
    +
    If you outstay the time, upon mine honour,
    +
    And in the greatness of my word, you die.
    +
    Exeunt DUKE FREDERICK and Lords
    +
    +
    CELIA
    +
    +
    O my poor Rosalind, whither wilt thou go?
    +
    Wilt thou change fathers? I will give thee mine.
    +
    I charge thee, be not thou more grieved than I am.
    +
    +
    ROSALIND
    +
    +
    I have more cause.
    +
    +
    CELIA
    +
    +
    Thou hast not, cousin;
    +
    Prithee be cheerful: know'st thou not, the duke
    +
    Hath banish'd me, his daughter?
    +
    +
    ROSALIND
    +
    +
    That he hath not.
    +
    +
    CELIA
    +
    +
    No, hath not? Rosalind lacks then the love
    +
    Which teacheth thee that thou and I am one:
    +
    Shall we be sunder'd? shall we part, sweet girl?
    +
    No: let my father seek another heir.
    +
    Therefore devise with me how we may fly,
    +
    Whither to go and what to bear with us;
    +
    And do not seek to take your change upon you,
    +
    To bear your griefs yourself and leave me out;
    +
    For, by this heaven, now at our sorrows pale,
    +
    Say what thou canst, I'll go along with thee.
    +
    +
    ROSALIND
    +
    +
    Why, whither shall we go?
    +
    +
    CELIA
    +
    +
    To seek my uncle in the forest of Arden.
    +
    +
    ROSALIND
    +
    +
    Alas, what danger will it be to us,
    +
    Maids as we are, to travel forth so far!
    +
    Beauty provoketh thieves sooner than gold.
    +
    +
    CELIA
    +
    +
    I'll put myself in poor and mean attire
    +
    And with a kind of umber smirch my face;
    +
    The like do you: so shall we pass along
    +
    And never stir assailants.
    +
    +
    ROSALIND
    +
    +
    Were it not better,
    +
    Because that I am more than common tall,
    +
    That I did suit me all points like a man?
    +
    A gallant curtle-axe upon my thigh,
    +
    A boar-spear in my hand; and--in my heart
    +
    Lie there what hidden woman's fear there will--
    +
    We'll have a swashing and a martial outside,
    +
    As many other mannish cowards have
    +
    That do outface it with their semblances.
    +
    +
    CELIA
    +
    +
    What shall I call thee when thou art a man?
    +
    +
    ROSALIND
    +
    +
    I'll have no worse a name than Jove's own page;
    +
    And therefore look you call me Ganymede.
    +
    But what will you be call'd?
    +
    +
    CELIA
    +
    +
    Something that hath a reference to my state
    +
    No longer Celia, but Aliena.
    +
    +
    ROSALIND
    +
    +
    But, cousin, what if we assay'd to steal
    +
    The clownish fool out of your father's court?
    +
    Would he not be a comfort to our travel?
    +
    +
    CELIA
    +
    +
    He'll go along o'er the wide world with me;
    +
    Leave me alone to woo him. Let's away,
    +
    And get our jewels and our wealth together,
    +
    Devise the fittest time and safest way
    +
    To hide us from pursuit that will be made
    +
    After my flight. Now go we in content
    +
    To liberty and not to banishment.
    +
    Exeunt
    +
    +
    +
    +
    + + diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php new file mode 100644 index 0000000..7a90c3b --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/Tests/XPath/TranslatorTest.php @@ -0,0 +1,322 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\Tests\XPath; + +use Symfony\Component\CssSelector\XPath\Extension\HtmlExtension; +use Symfony\Component\CssSelector\XPath\Translator; + +class TranslatorTest extends \PHPUnit_Framework_TestCase +{ + /** @dataProvider getXpathLiteralTestData */ + public function testXpathLiteral($value, $literal) + { + $this->assertEquals($literal, Translator::getXpathLiteral($value)); + } + + /** @dataProvider getCssToXPathTestData */ + public function testCssToXPath($css, $xpath) + { + $translator = new Translator(); + $translator->registerExtension(new HtmlExtension($translator)); + $this->assertEquals($xpath, $translator->cssToXPath($css, '')); + } + + /** @dataProvider getXmlLangTestData */ + public function testXmlLang($css, array $elementsId) + { + $translator = new Translator(); + $document = new \SimpleXMLElement(file_get_contents(__DIR__.'/Fixtures/lang.xml')); + $elements = $document->xpath($translator->cssToXPath($css)); + $this->assertEquals(count($elementsId), count($elements)); + foreach ($elements as $element) { + $this->assertTrue(in_array($element->attributes()->id, $elementsId)); + } + } + + /** @dataProvider getHtmlIdsTestData */ + public function testHtmlIds($css, array $elementsId) + { + $translator = new Translator(); + $translator->registerExtension(new HtmlExtension($translator)); + $document = new \DOMDocument(); + $document->strictErrorChecking = false; + libxml_use_internal_errors(true); + $document->loadHTMLFile(__DIR__.'/Fixtures/ids.html'); + $document = simplexml_import_dom($document); + $elements = $document->xpath($translator->cssToXPath($css)); + $this->assertCount(count($elementsId), $elementsId); + foreach ($elements as $element) { + if (null !== $element->attributes()->id) { + $this->assertTrue(in_array($element->attributes()->id, $elementsId)); + } + } + } + + /** @dataProvider getHtmlShakespearTestData */ + public function testHtmlShakespear($css, $count) + { + $translator = new Translator(); + $translator->registerExtension(new HtmlExtension($translator)); + $document = new \DOMDocument(); + $document->strictErrorChecking = false; + $document->loadHTMLFile(__DIR__.'/Fixtures/shakespear.html'); + $document = simplexml_import_dom($document); + $bodies = $document->xpath('//body'); + $elements = $bodies[0]->xpath($translator->cssToXPath($css)); + $this->assertEquals($count, count($elements)); + } + + public function getXpathLiteralTestData() + { + return array( + array('foo', "'foo'"), + array("foo's bar", '"foo\'s bar"'), + array("foo's \"middle\" bar", 'concat(\'foo\', "\'", \'s "middle" bar\')'), + array("foo's 'middle' \"bar\"", 'concat(\'foo\', "\'", \'s \', "\'", \'middle\', "\'", \' "bar"\')'), + ); + } + + public function getCssToXPathTestData() + { + return array( + array('*', "*"), + array('e', "e"), + array('*|e', "e"), + array('e|f', "e:f"), + array('e[foo]', "e[@foo]"), + array('e[foo|bar]', "e[@foo:bar]"), + array('e[foo="bar"]', "e[@foo = 'bar']"), + array('e[foo~="bar"]', "e[@foo and contains(concat(' ', normalize-space(@foo), ' '), ' bar ')]"), + array('e[foo^="bar"]', "e[@foo and starts-with(@foo, 'bar')]"), + array('e[foo$="bar"]', "e[@foo and substring(@foo, string-length(@foo)-2) = 'bar']"), + array('e[foo*="bar"]', "e[@foo and contains(@foo, 'bar')]"), + array('e[hreflang|="en"]', "e[@hreflang and (@hreflang = 'en' or starts-with(@hreflang, 'en-'))]"), + array('e:nth-child(1)', "*/*[name() = 'e' and (position() = 1)]"), + array('e:nth-last-child(1)', "*/*[name() = 'e' and (position() = last() - 0)]"), + array('e:nth-last-child(2n+2)', "*/*[name() = 'e' and (last() - position() - 1 >= 0 and (last() - position() - 1) mod 2 = 0)]"), + array('e:nth-of-type(1)', "*/e[position() = 1]"), + array('e:nth-last-of-type(1)', "*/e[position() = last() - 0]"), + array('div e:nth-last-of-type(1) .aclass', "div/descendant-or-self::*/e[position() = last() - 0]/descendant-or-self::*/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' aclass ')]"), + array('e:first-child', "*/*[name() = 'e' and (position() = 1)]"), + array('e:last-child', "*/*[name() = 'e' and (position() = last())]"), + array('e:first-of-type', "*/e[position() = 1]"), + array('e:last-of-type', "*/e[position() = last()]"), + array('e:only-child', "*/*[name() = 'e' and (last() = 1)]"), + array('e:only-of-type', "e[last() = 1]"), + array('e:empty', "e[not(*) and not(string-length())]"), + array('e:EmPTY', "e[not(*) and not(string-length())]"), + array('e:root', "e[not(parent::*)]"), + array('e:hover', "e[0]"), + array('e:contains("foo")', "e[contains(string(.), 'foo')]"), + array('e:ConTains(foo)', "e[contains(string(.), 'foo')]"), + array('e.warning', "e[@class and contains(concat(' ', normalize-space(@class), ' '), ' warning ')]"), + array('e#myid', "e[@id = 'myid']"), + array('e:not(:nth-child(odd))', "e[not(position() - 1 >= 0 and (position() - 1) mod 2 = 0)]"), + array('e:nOT(*)', "e[0]"), + array('e f', "e/descendant-or-self::*/f"), + array('e > f', "e/f"), + array('e + f', "e/following-sibling::*[name() = 'f' and (position() = 1)]"), + array('e ~ f', "e/following-sibling::f"), + array('div#container p', "div[@id = 'container']/descendant-or-self::*/p"), + ); + } + + public function getXmlLangTestData() + { + return array( + array(':lang("EN")', array('first', 'second', 'third', 'fourth')), + array(':lang("en-us")', array('second', 'fourth')), + array(':lang(en-nz)', array('third')), + array(':lang(fr)', array('fifth')), + array(':lang(ru)', array('sixth')), + array(":lang('ZH')", array('eighth')), + array(':lang(de) :lang(zh)', array('eighth')), + array(':lang(en), :lang(zh)', array('first', 'second', 'third', 'fourth', 'eighth')), + array(':lang(es)', array()), + ); + } + + public function getHtmlIdsTestData() + { + return array( + array('div', array('outer-div', 'li-div', 'foobar-div')), + array('DIV', array('outer-div', 'li-div', 'foobar-div')), // case-insensitive in HTML + array('div div', array('li-div')), + array('div, div div', array('outer-div', 'li-div', 'foobar-div')), + array('a[name]', array('name-anchor')), + array('a[NAme]', array('name-anchor')), // case-insensitive in HTML: + array('a[rel]', array('tag-anchor', 'nofollow-anchor')), + array('a[rel="tag"]', array('tag-anchor')), + array('a[href*="localhost"]', array('tag-anchor')), + array('a[href*=""]', array()), + array('a[href^="http"]', array('tag-anchor', 'nofollow-anchor')), + array('a[href^="http:"]', array('tag-anchor')), + array('a[href^=""]', array()), + array('a[href$="org"]', array('nofollow-anchor')), + array('a[href$=""]', array()), + array('div[foobar~="bc"]', array('foobar-div')), + array('div[foobar~="cde"]', array('foobar-div')), + array('[foobar~="ab bc"]', array('foobar-div')), + array('[foobar~=""]', array()), + array('[foobar~=" \t"]', array()), + array('div[foobar~="cd"]', array()), + array('*[lang|="En"]', array('second-li')), + array('[lang|="En-us"]', array('second-li')), + // Attribute values are case sensitive + array('*[lang|="en"]', array()), + array('[lang|="en-US"]', array()), + array('*[lang|="e"]', array()), + // ... :lang() is not. + array(':lang("EN")', array('second-li', 'li-div')), + array('*:lang(en-US)', array('second-li', 'li-div')), + array(':lang("e")', array()), + array('li:nth-child(3)', array('third-li')), + array('li:nth-child(10)', array()), + array('li:nth-child(2n)', array('second-li', 'fourth-li', 'sixth-li')), + array('li:nth-child(even)', array('second-li', 'fourth-li', 'sixth-li')), + array('li:nth-child(2n+0)', array('second-li', 'fourth-li', 'sixth-li')), + array('li:nth-child(+2n+1)', array('first-li', 'third-li', 'fifth-li', 'seventh-li')), + array('li:nth-child(odd)', array('first-li', 'third-li', 'fifth-li', 'seventh-li')), + array('li:nth-child(2n+4)', array('fourth-li', 'sixth-li')), + array('li:nth-child(3n+1)', array('first-li', 'fourth-li', 'seventh-li')), + array('li:nth-child(n)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-child(n-1)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-child(n+1)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-child(n+3)', array('third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-child(-n)', array()), + array('li:nth-child(-n-1)', array()), + array('li:nth-child(-n+1)', array('first-li')), + array('li:nth-child(-n+3)', array('first-li', 'second-li', 'third-li')), + array('li:nth-last-child(0)', array()), + array('li:nth-last-child(2n)', array('second-li', 'fourth-li', 'sixth-li')), + array('li:nth-last-child(even)', array('second-li', 'fourth-li', 'sixth-li')), + array('li:nth-last-child(2n+2)', array('second-li', 'fourth-li', 'sixth-li')), + array('li:nth-last-child(n)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-last-child(n-1)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-last-child(n-3)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-last-child(n+1)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li')), + array('li:nth-last-child(n+3)', array('first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li')), + array('li:nth-last-child(-n)', array()), + array('li:nth-last-child(-n-1)', array()), + array('li:nth-last-child(-n+1)', array('seventh-li')), + array('li:nth-last-child(-n+3)', array('fifth-li', 'sixth-li', 'seventh-li')), + array('ol:first-of-type', array('first-ol')), + array('ol:nth-child(1)', array('first-ol')), + array('ol:nth-of-type(2)', array('second-ol')), + array('ol:nth-last-of-type(1)', array('second-ol')), + array('span:only-child', array('foobar-span')), + array('li div:only-child', array('li-div')), + array('div *:only-child', array('li-div', 'foobar-span')), + array('p:only-of-type', array('paragraph')), + array('a:empty', array('name-anchor')), + array('a:EMpty', array('name-anchor')), + array('li:empty', array('third-li', 'fourth-li', 'fifth-li', 'sixth-li')), + array(':root', array('html')), + array('html:root', array('html')), + array('li:root', array()), + array('* :root', array()), + array('*:contains("link")', array('html', 'outer-div', 'tag-anchor', 'nofollow-anchor')), + array(':CONtains("link")', array('html', 'outer-div', 'tag-anchor', 'nofollow-anchor')), + array('*:contains("LInk")', array()), // case sensitive + array('*:contains("e")', array('html', 'nil', 'outer-div', 'first-ol', 'first-li', 'paragraph', 'p-em')), + array('*:contains("E")', array()), // case-sensitive + array('.a', array('first-ol')), + array('.b', array('first-ol')), + array('*.a', array('first-ol')), + array('ol.a', array('first-ol')), + array('.c', array('first-ol', 'third-li', 'fourth-li')), + array('*.c', array('first-ol', 'third-li', 'fourth-li')), + array('ol *.c', array('third-li', 'fourth-li')), + array('ol li.c', array('third-li', 'fourth-li')), + array('li ~ li.c', array('third-li', 'fourth-li')), + array('ol > li.c', array('third-li', 'fourth-li')), + array('#first-li', array('first-li')), + array('li#first-li', array('first-li')), + array('*#first-li', array('first-li')), + array('li div', array('li-div')), + array('li > div', array('li-div')), + array('div div', array('li-div')), + array('div > div', array()), + array('div>.c', array('first-ol')), + array('div > .c', array('first-ol')), + array('div + div', array('foobar-div')), + array('a ~ a', array('tag-anchor', 'nofollow-anchor')), + array('a[rel="tag"] ~ a', array('nofollow-anchor')), + array('ol#first-ol li:last-child', array('seventh-li')), + array('ol#first-ol *:last-child', array('li-div', 'seventh-li')), + array('#outer-div:first-child', array('outer-div')), + array('#outer-div :first-child', array('name-anchor', 'first-li', 'li-div', 'p-b', 'checkbox-fieldset-disabled', 'area-href')), + array('a[href]', array('tag-anchor', 'nofollow-anchor')), + array(':not(*)', array()), + array('a:not([href])', array('name-anchor')), + array('ol :Not(li[class])', array('first-li', 'second-li', 'li-div', 'fifth-li', 'sixth-li', 'seventh-li')), + // HTML-specific + array(':link', array('link-href', 'tag-anchor', 'nofollow-anchor', 'area-href')), + array(':visited', array()), + array(':enabled', array('link-href', 'tag-anchor', 'nofollow-anchor', 'checkbox-unchecked', 'text-checked', 'checkbox-checked', 'area-href')), + array(':disabled', array('checkbox-disabled', 'checkbox-disabled-checked', 'fieldset', 'checkbox-fieldset-disabled')), + array(':checked', array('checkbox-checked', 'checkbox-disabled-checked')), + ); + } + + public function getHtmlShakespearTestData() + { + return array( + array('*', 246), + array('div:contains(CELIA)', 26), + array('div:only-child', 22), // ? + array('div:nth-child(even)', 106), + array('div:nth-child(2n)', 106), + array('div:nth-child(odd)', 137), + array('div:nth-child(2n+1)', 137), + array('div:nth-child(n)', 243), + array('div:last-child', 53), + array('div:first-child', 51), + array('div > div', 242), + array('div + div', 190), + array('div ~ div', 190), + array('body', 1), + array('body div', 243), + array('div', 243), + array('div div', 242), + array('div div div', 241), + array('div, div, div', 243), + array('div, a, span', 243), + array('.dialog', 51), + array('div.dialog', 51), + array('div .dialog', 51), + array('div.character, div.dialog', 99), + array('div.direction.dialog', 0), + array('div.dialog.direction', 0), + array('div.dialog.scene', 1), + array('div.scene.scene', 1), + array('div.scene .scene', 0), + array('div.direction .dialog ', 0), + array('div .dialog .direction', 4), + array('div.dialog .dialog .direction', 4), + array('#speech5', 1), + array('div#speech5', 1), + array('div #speech5', 1), + array('div.scene div.dialog', 49), + array('div#scene1 div.dialog div', 142), + array('#scene1 #speech1', 1), + array('div[class]', 103), + array('div[class=dialog]', 50), + array('div[class^=dia]', 51), + array('div[class$=log]', 50), + array('div[class*=sce]', 1), + array('div[class|=dialog]', 50), // ? Seems right + array('div[class!=madeup]', 243), // ? Seems right + array('div[class~=dialog]', 51), // ? Seems right + ); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/AbstractExtension.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/AbstractExtension.php new file mode 100644 index 0000000..1b147e9 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/AbstractExtension.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath\Extension; + +/** + * XPath expression translator abstract extension. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +abstract class AbstractExtension implements ExtensionInterface +{ + /** + * {@inheritdoc} + */ + public function getNodeTranslators() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function getCombinationTranslators() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function getFunctionTranslators() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function getPseudoClassTranslators() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function getAttributeMatchingTranslators() + { + return array(); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/AttributeMatchingExtension.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/AttributeMatchingExtension.php new file mode 100644 index 0000000..1b1f00f --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/AttributeMatchingExtension.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath\Extension; + +use Symfony\Component\CssSelector\XPath\Translator; +use Symfony\Component\CssSelector\XPath\XPathExpr; + +/** + * XPath expression translator attribute extension. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class AttributeMatchingExtension extends AbstractExtension +{ + /** + * {@inheritdoc} + */ + public function getAttributeMatchingTranslators() + { + return array( + 'exists' => array($this, 'translateExists'), + '=' => array($this, 'translateEquals'), + '~=' => array($this, 'translateIncludes'), + '|=' => array($this, 'translateDashMatch'), + '^=' => array($this, 'translatePrefixMatch'), + '$=' => array($this, 'translateSuffixMatch'), + '*=' => array($this, 'translateSubstringMatch'), + '!=' => array($this, 'translateDifferent'), + ); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateExists(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition($attribute); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateEquals(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition(sprintf('%s = %s', $attribute, Translator::getXpathLiteral($value))); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateIncludes(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition($value ? sprintf( + '%1$s and contains(concat(\' \', normalize-space(%1$s), \' \'), %2$s)', + $attribute, + Translator::getXpathLiteral(' '.$value.' ') + ) : '0'); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateDashMatch(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition(sprintf( + '%1$s and (%1$s = %2$s or starts-with(%1$s, %3$s))', + $attribute, + Translator::getXpathLiteral($value), + Translator::getXpathLiteral($value.'-') + )); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translatePrefixMatch(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition($value ? sprintf( + '%1$s and starts-with(%1$s, %2$s)', + $attribute, + Translator::getXpathLiteral($value) + ) : '0'); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateSuffixMatch(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition($value ? sprintf( + '%1$s and substring(%1$s, string-length(%1$s)-%2$s) = %3$s', + $attribute, + strlen($value) - 1, + Translator::getXpathLiteral($value) + ) : '0'); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateSubstringMatch(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition($value ? sprintf( + '%1$s and contains(%1$s, %2$s)', + $attribute, + Translator::getXpathLiteral($value) + ) : '0'); + } + + /** + * @param XPathExpr $xpath + * @param string $attribute + * @param string $value + * + * @return XPathExpr + */ + public function translateDifferent(XPathExpr $xpath, $attribute, $value) + { + return $xpath->addCondition(sprintf( + $value ? 'not(%1$s) or %1$s != %2$s' : '%s != %s', + $attribute, + Translator::getXpathLiteral($value) + )); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'attribute-matching'; + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/CombinationExtension.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/CombinationExtension.php new file mode 100644 index 0000000..639e924 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/CombinationExtension.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath\Extension; + +use Symfony\Component\CssSelector\XPath\XPathExpr; + +/** + * XPath expression translator combination extension. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class CombinationExtension extends AbstractExtension +{ + /** + * {@inheritdoc} + */ + public function getCombinationTranslators() + { + return array( + ' ' => array($this, 'translateDescendant'), + '>' => array($this, 'translateChild'), + '+' => array($this, 'translateDirectAdjacent'), + '~' => array($this, 'translateIndirectAdjacent'), + ); + } + + /** + * @param XPathExpr $xpath + * @param XPathExpr $combinedXpath + * + * @return XPathExpr + */ + public function translateDescendant(XPathExpr $xpath, XPathExpr $combinedXpath) + { + return $xpath->join('/descendant-or-self::*/', $combinedXpath); + } + + /** + * @param XPathExpr $xpath + * @param XPathExpr $combinedXpath + * + * @return XPathExpr + */ + public function translateChild(XPathExpr $xpath, XPathExpr $combinedXpath) + { + return $xpath->join('/', $combinedXpath); + } + + /** + * @param XPathExpr $xpath + * @param XPathExpr $combinedXpath + * + * @return XPathExpr + */ + public function translateDirectAdjacent(XPathExpr $xpath, XPathExpr $combinedXpath) + { + return $xpath + ->join('/following-sibling::', $combinedXpath) + ->addNameTest() + ->addCondition('position() = 1'); + } + + /** + * @param XPathExpr $xpath + * @param XPathExpr $combinedXpath + * + * @return XPathExpr + */ + public function translateIndirectAdjacent(XPathExpr $xpath, XPathExpr $combinedXpath) + { + return $xpath->join('/following-sibling::', $combinedXpath); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'combination'; + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/ExtensionInterface.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/ExtensionInterface.php new file mode 100644 index 0000000..65ab287 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/ExtensionInterface.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath\Extension; + +/** + * XPath expression translator extension interface. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +interface ExtensionInterface +{ + /** + * Returns node translators. + * + * @return callable[] + */ + public function getNodeTranslators(); + + /** + * Returns combination translators. + * + * @return callable[] + */ + public function getCombinationTranslators(); + + /** + * Returns function translators. + * + * @return callable[] + */ + public function getFunctionTranslators(); + + /** + * Returns pseudo-class translators. + * + * @return callable[] + */ + public function getPseudoClassTranslators(); + + /** + * Returns attribute operation translators. + * + * @return callable[] + */ + public function getAttributeMatchingTranslators(); + + /** + * Returns extension name. + * + * @return string + */ + public function getName(); +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/FunctionExtension.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/FunctionExtension.php new file mode 100644 index 0000000..5cc6693 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/FunctionExtension.php @@ -0,0 +1,209 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath\Extension; + +use Symfony\Component\CssSelector\Exception\ExpressionErrorException; +use Symfony\Component\CssSelector\Exception\SyntaxErrorException; +use Symfony\Component\CssSelector\Node\FunctionNode; +use Symfony\Component\CssSelector\Parser\Parser; +use Symfony\Component\CssSelector\XPath\Translator; +use Symfony\Component\CssSelector\XPath\XPathExpr; + +/** + * XPath expression translator function extension. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class FunctionExtension extends AbstractExtension +{ + /** + * {@inheritdoc} + */ + public function getFunctionTranslators() + { + return array( + 'nth-child' => array($this, 'translateNthChild'), + 'nth-last-child' => array($this, 'translateNthLastChild'), + 'nth-of-type' => array($this, 'translateNthOfType'), + 'nth-last-of-type' => array($this, 'translateNthLastOfType'), + 'contains' => array($this, 'translateContains'), + 'lang' => array($this, 'translateLang'), + ); + } + + /** + * @param XPathExpr $xpath + * @param FunctionNode $function + * @param boolean $last + * @param boolean $addNameTest + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateNthChild(XPathExpr $xpath, FunctionNode $function, $last = false, $addNameTest = true) + { + try { + list($a, $b) = Parser::parseSeries($function->getArguments()); + } catch (SyntaxErrorException $e) { + throw new ExpressionErrorException(sprintf('Invalid series: %s', implode(', ', $function->getArguments())), 0, $e); + } + + $xpath->addStarPrefix(); + if ($addNameTest) { + $xpath->addNameTest(); + } + + if (0 === $a) { + return $xpath->addCondition('position() = '.($last ? 'last() - '.($b - 1) : $b)); + } + + if ($a < 0) { + if ($b < 1) { + return $xpath->addCondition('false()'); + } + + $sign = '<='; + } else { + $sign = '>='; + } + + $expr = 'position()'; + + if ($last) { + $expr = 'last() - '.$expr; + $b--; + } + + if (0 !== $b) { + $expr .= ' - '.$b; + } + + $conditions = array(sprintf('%s %s 0', $expr, $sign)); + + if (1 !== $a && -1 !== $a) { + $conditions[] = sprintf('(%s) mod %d = 0', $expr, $a); + } + + return $xpath->addCondition(implode(' and ', $conditions)); + + // todo: handle an+b, odd, even + // an+b means every-a, plus b, e.g., 2n+1 means odd + // 0n+b means b + // n+0 means a=1, i.e., all elements + // an means every a elements, i.e., 2n means even + // -n means -1n + // -1n+6 means elements 6 and previous + } + + /** + * @param XPathExpr $xpath + * @param FunctionNode $function + * + * @return XPathExpr + */ + public function translateNthLastChild(XPathExpr $xpath, FunctionNode $function) + { + return $this->translateNthChild($xpath, $function, true); + } + + /** + * @param XPathExpr $xpath + * @param FunctionNode $function + * + * @return XPathExpr + */ + public function translateNthOfType(XPathExpr $xpath, FunctionNode $function) + { + return $this->translateNthChild($xpath, $function, false, false); + } + + /** + * @param XPathExpr $xpath + * @param FunctionNode $function + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateNthLastOfType(XPathExpr $xpath, FunctionNode $function) + { + if ('*' === $xpath->getElement()) { + throw new ExpressionErrorException('"*:nth-of-type()" is not implemented.'); + } + + return $this->translateNthChild($xpath, $function, true, false); + } + + /** + * @param XPathExpr $xpath + * @param FunctionNode $function + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateContains(XPathExpr $xpath, FunctionNode $function) + { + $arguments = $function->getArguments(); + foreach ($arguments as $token) { + if (!($token->isString() || $token->isIdentifier())) { + throw new ExpressionErrorException( + 'Expected a single string or identifier for :contains(), got ' + .implode(', ', $arguments) + ); + } + } + + return $xpath->addCondition(sprintf( + 'contains(string(.), %s)', + Translator::getXpathLiteral($arguments[0]->getValue()) + )); + } + + /** + * @param XPathExpr $xpath + * @param FunctionNode $function + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateLang(XPathExpr $xpath, FunctionNode $function) + { + $arguments = $function->getArguments(); + foreach ($arguments as $token) { + if (!($token->isString() || $token->isIdentifier())) { + throw new ExpressionErrorException( + 'Expected a single string or identifier for :lang(), got ' + .implode(', ', $arguments) + ); + } + } + + return $xpath->addCondition(sprintf( + 'lang(%s)', + Translator::getXpathLiteral($arguments[0]->getValue()) + )); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'function'; + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/HtmlExtension.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/HtmlExtension.php new file mode 100644 index 0000000..aef8052 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/HtmlExtension.php @@ -0,0 +1,238 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath\Extension; + +use Symfony\Component\CssSelector\Exception\ExpressionErrorException; +use Symfony\Component\CssSelector\Node\FunctionNode; +use Symfony\Component\CssSelector\XPath\Translator; +use Symfony\Component\CssSelector\XPath\XPathExpr; + +/** + * XPath expression translator HTML extension. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class HtmlExtension extends AbstractExtension +{ + /** + * Constructor. + * + * @param Translator $translator + */ + public function __construct(Translator $translator) + { + $translator + ->getExtension('node') + ->setFlag(NodeExtension::ELEMENT_NAME_IN_LOWER_CASE, true) + ->setFlag(NodeExtension::ATTRIBUTE_NAME_IN_LOWER_CASE, true); + } + + /** + * {@inheritdoc} + */ + public function getPseudoClassTranslators() + { + return array( + 'checked' => array($this, 'translateChecked'), + 'link' => array($this, 'translateLink'), + 'disabled' => array($this, 'translateDisabled'), + 'enabled' => array($this, 'translateEnabled'), + 'selected' => array($this, 'translateSelected'), + 'invalid' => array($this, 'translateInvalid'), + 'hover' => array($this, 'translateHover'), + 'visited' => array($this, 'translateVisited'), + ); + } + + /** + * {@inheritdoc} + */ + public function getFunctionTranslators() + { + return array( + 'lang' => array($this, 'translateLang'), + ); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateChecked(XPathExpr $xpath) + { + return $xpath->addCondition( + '(@checked ' + ."and (name(.) = 'input' or name(.) = 'command')" + ."and (@type = 'checkbox' or @type = 'radio'))" + ); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateLink(XPathExpr $xpath) + { + return $xpath->addCondition("@href and (name(.) = 'a' or name(.) = 'link' or name(.) = 'area')"); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateDisabled(XPathExpr $xpath) + { + return $xpath->addCondition( + "(" + ."@disabled and" + ."(" + ."(name(.) = 'input' and @type != 'hidden')" + ." or name(.) = 'button'" + ." or name(.) = 'select'" + ." or name(.) = 'textarea'" + ." or name(.) = 'command'" + ." or name(.) = 'fieldset'" + ." or name(.) = 'optgroup'" + ." or name(.) = 'option'" + .")" + .") or (" + ."(name(.) = 'input' and @type != 'hidden')" + ." or name(.) = 'button'" + ." or name(.) = 'select'" + ." or name(.) = 'textarea'" + .")" + ." and ancestor::fieldset[@disabled]" + ); + // todo: in the second half, add "and is not a descendant of that fieldset element's first legend element child, if any." + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateEnabled(XPathExpr $xpath) + { + return $xpath->addCondition( + '(' + .'@href and (' + ."name(.) = 'a'" + ." or name(.) = 'link'" + ." or name(.) = 'area'" + .')' + .') or (' + .'(' + ."name(.) = 'command'" + ." or name(.) = 'fieldset'" + ." or name(.) = 'optgroup'" + .')' + .' and not(@disabled)' + .') or (' + .'(' + ."(name(.) = 'input' and @type != 'hidden')" + ." or name(.) = 'button'" + ." or name(.) = 'select'" + ." or name(.) = 'textarea'" + ." or name(.) = 'keygen'" + .')' + ." and not (@disabled or ancestor::fieldset[@disabled])" + .') or (' + ."name(.) = 'option' and not(" + ."@disabled or ancestor::optgroup[@disabled]" + .')' + .')' + ); + } + + /** + * @param XPathExpr $xpath + * @param FunctionNode $function + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateLang(XPathExpr $xpath, FunctionNode $function) + { + $arguments = $function->getArguments(); + foreach ($arguments as $token) { + if (!($token->isString() || $token->isIdentifier())) { + throw new ExpressionErrorException( + 'Expected a single string or identifier for :lang(), got ' + .implode(', ', $arguments) + ); + } + } + + return $xpath->addCondition(sprintf( + 'ancestor-or-self::*[@lang][1][starts-with(concat(' + ."translate(@%s, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '-')" + .', %s)]', + 'lang', + Translator::getXpathLiteral(strtolower($arguments[0]->getValue()).'-') + )); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateSelected(XPathExpr $xpath) + { + return $xpath->addCondition("(@selected and name(.) = 'option')"); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateInvalid(XPathExpr $xpath) + { + return $xpath->addCondition('0'); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateHover(XPathExpr $xpath) + { + return $xpath->addCondition('0'); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateVisited(XPathExpr $xpath) + { + return $xpath->addCondition('0'); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'html'; + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/NodeExtension.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/NodeExtension.php new file mode 100644 index 0000000..7d22d98 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/NodeExtension.php @@ -0,0 +1,270 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath\Extension; + +use Symfony\Component\CssSelector\Node; +use Symfony\Component\CssSelector\XPath\Translator; +use Symfony\Component\CssSelector\XPath\XPathExpr; + +/** + * XPath expression translator node extension. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class NodeExtension extends AbstractExtension +{ + const ELEMENT_NAME_IN_LOWER_CASE = 1; + const ATTRIBUTE_NAME_IN_LOWER_CASE = 2; + const ATTRIBUTE_VALUE_IN_LOWER_CASE = 4; + + /** + * @var Translator + */ + private $translator; + + /** + * @var int + */ + private $flags; + + /** + * Constructor. + * + * @param Translator $translator + * @param int $flags + */ + public function __construct(Translator $translator, $flags = 0) + { + $this->translator = $translator; + $this->flags = $flags; + } + + /** + * @param int $flag + * @param boolean $on + * + * @return NodeExtension + */ + public function setFlag($flag, $on) + { + if ($on && !$this->hasFlag($flag)) { + $this->flags += $flag; + } + + if (!$on && $this->hasFlag($flag)) { + $this->flags -= $flag; + } + + return $this; + } + + /** + * @param int $flag + * + * @return boolean + */ + public function hasFlag($flag) + { + return $this->flags & $flag; + } + + /** + * {@inheritdoc} + */ + public function getNodeTranslators() + { + return array( + 'Selector' => array($this, 'translateSelector'), + 'CombinedSelector' => array($this, 'translateCombinedSelector'), + 'Negation' => array($this, 'translateNegation'), + 'Function' => array($this, 'translateFunction'), + 'Pseudo' => array($this, 'translatePseudo'), + 'Attribute' => array($this, 'translateAttribute'), + 'Class' => array($this, 'translateClass'), + 'Hash' => array($this, 'translateHash'), + 'Element' => array($this, 'translateElement'), + ); + } + + /** + * @param Node\SelectorNode $node + * + * @return XPathExpr + */ + public function translateSelector(Node\SelectorNode $node) + { + return $this->translator->nodeToXPath($node->getTree()); + } + + /** + * @param Node\CombinedSelectorNode $node + * + * @return XPathExpr + */ + public function translateCombinedSelector(Node\CombinedSelectorNode $node) + { + return $this->translator->addCombination($node->getCombinator(), $node->getSelector(), $node->getSubSelector()); + } + + /** + * @param Node\NegationNode $node + * + * @return XPathExpr + */ + public function translateNegation(Node\NegationNode $node) + { + $xpath = $this->translator->nodeToXPath($node->getSelector()); + $subXpath = $this->translator->nodeToXPath($node->getSubSelector()); + $subXpath->addNameTest(); + + if ($subXpath->getCondition()) { + return $xpath->addCondition(sprintf('not(%s)', $subXpath->getCondition())); + } + + return $xpath->addCondition('0'); + } + + /** + * @param Node\FunctionNode $node + * + * @return XPathExpr + */ + public function translateFunction(Node\FunctionNode $node) + { + $xpath = $this->translator->nodeToXPath($node->getSelector()); + + return $this->translator->addFunction($xpath, $node); + } + + /** + * @param Node\PseudoNode $node + * + * @return XPathExpr + */ + public function translatePseudo(Node\PseudoNode $node) + { + $xpath = $this->translator->nodeToXPath($node->getSelector()); + + return $this->translator->addPseudoClass($xpath, $node->getIdentifier()); + } + + /** + * @param Node\AttributeNode $node + * + * @return XPathExpr + */ + public function translateAttribute(Node\AttributeNode $node) + { + $name = $node->getAttribute(); + $safe = $this->isSafeName($name); + + if ($this->hasFlag(self::ATTRIBUTE_NAME_IN_LOWER_CASE)) { + $name = strtolower($name); + } + + if ($node->getNamespace()) { + $name = sprintf('%s:%s', $node->getNamespace(), $name); + $safe = $safe && $this->isSafeName($node->getNamespace()); + } + + $attribute = $safe ? '@'.$name : sprintf('attribute::*[name() = %s]', Translator::getXpathLiteral($name)); + $value = $node->getValue(); + $xpath = $this->translator->nodeToXPath($node->getSelector()); + + if ($this->hasFlag(self::ATTRIBUTE_VALUE_IN_LOWER_CASE)) { + $value = strtolower($value); + } + + return $this->translator->addAttributeMatching($xpath, $node->getOperator(), $attribute, $value); + } + + /** + * @param Node\ClassNode $node + * + * @return XPathExpr + */ + public function translateClass(Node\ClassNode $node) + { + $xpath = $this->translator->nodeToXPath($node->getSelector()); + + return $this->translator->addAttributeMatching($xpath, '~=', '@class', $node->getName()); + } + + /** + * @param Node\HashNode $node + * + * @return XPathExpr + */ + public function translateHash(Node\HashNode $node) + { + $xpath = $this->translator->nodeToXPath($node->getSelector()); + + return $this->translator->addAttributeMatching($xpath, '=', '@id', $node->getId()); + } + + /** + * @param Node\ElementNode $node + * + * @return XPathExpr + */ + public function translateElement(Node\ElementNode $node) + { + $element = $node->getElement(); + + if ($this->hasFlag(self::ELEMENT_NAME_IN_LOWER_CASE)) { + $element = strtolower($element); + } + + if ($element) { + $safe = $this->isSafeName($element); + } else { + $element = '*'; + $safe = true; + } + + if ($node->getNamespace()) { + $element = sprintf('%s:%s', $node->getNamespace(), $element); + $safe = $safe && $this->isSafeName($node->getNamespace()); + } + + $xpath = new XPathExpr('', $element); + + if (!$safe) { + $xpath->addNameTest(); + } + + return $xpath; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'node'; + } + + /** + * Tests if given name is safe. + * + * @param string $name + * + * @return boolean + */ + private function isSafeName($name) + { + return 0 < preg_match('~^[a-zA-Z_][a-zA-Z0-9_.-]*$~', $name); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/PseudoClassExtension.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/PseudoClassExtension.php new file mode 100644 index 0000000..d230dd7 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Extension/PseudoClassExtension.php @@ -0,0 +1,162 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath\Extension; + +use Symfony\Component\CssSelector\Exception\ExpressionErrorException; +use Symfony\Component\CssSelector\XPath\XPathExpr; + +/** + * XPath expression translator pseudo-class extension. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class PseudoClassExtension extends AbstractExtension +{ + /** + * {@inheritdoc} + */ + public function getPseudoClassTranslators() + { + return array( + 'root' => array($this, 'translateRoot'), + 'first-child' => array($this, 'translateFirstChild'), + 'last-child' => array($this, 'translateLastChild'), + 'first-of-type' => array($this, 'translateFirstOfType'), + 'last-of-type' => array($this, 'translateLastOfType'), + 'only-child' => array($this, 'translateOnlyChild'), + 'only-of-type' => array($this, 'translateOnlyOfType'), + 'empty' => array($this, 'translateEmpty'), + ); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateRoot(XPathExpr $xpath) + { + return $xpath->addCondition('not(parent::*)'); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateFirstChild(XPathExpr $xpath) + { + return $xpath + ->addStarPrefix() + ->addNameTest() + ->addCondition('position() = 1'); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateLastChild(XPathExpr $xpath) + { + return $xpath + ->addStarPrefix() + ->addNameTest() + ->addCondition('position() = last()'); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateFirstOfType(XPathExpr $xpath) + { + if ('*' === $xpath->getElement()) { + throw new ExpressionErrorException('"*:first-of-type" is not implemented.'); + } + + return $xpath + ->addStarPrefix() + ->addCondition('position() = 1'); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateLastOfType(XPathExpr $xpath) + { + if ('*' === $xpath->getElement()) { + throw new ExpressionErrorException('"*:last-of-type" is not implemented.'); + } + + return $xpath + ->addStarPrefix() + ->addCondition('position() = last()'); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateOnlyChild(XPathExpr $xpath) + { + return $xpath + ->addStarPrefix() + ->addNameTest() + ->addCondition('last() = 1'); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function translateOnlyOfType(XPathExpr $xpath) + { + if ('*' === $xpath->getElement()) { + throw new ExpressionErrorException('"*:only-of-type" is not implemented.'); + } + + return $xpath->addCondition('last() = 1'); + } + + /** + * @param XPathExpr $xpath + * + * @return XPathExpr + */ + public function translateEmpty(XPathExpr $xpath) + { + return $xpath->addCondition('not(*) and not(string-length())'); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'pseudo-class'; + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Translator.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Translator.php new file mode 100644 index 0000000..1ff3381 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/Translator.php @@ -0,0 +1,302 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath; + +use Symfony\Component\CssSelector\Exception\ExpressionErrorException; +use Symfony\Component\CssSelector\Node\FunctionNode; +use Symfony\Component\CssSelector\Node\NodeInterface; +use Symfony\Component\CssSelector\Node\SelectorNode; +use Symfony\Component\CssSelector\Parser\Parser; +use Symfony\Component\CssSelector\Parser\ParserInterface; +use Symfony\Component\CssSelector\XPath\Extension; + +/** + * XPath expression translator interface. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class Translator implements TranslatorInterface +{ + /** + * @var ParserInterface + */ + private $mainParser; + + /** + * @var ParserInterface[] + */ + private $shortcutParsers = array(); + + /** + * @var Extension\ExtensionInterface + */ + private $extensions = array(); + + /** + * @var array + */ + private $nodeTranslators = array(); + + /** + * @var array + */ + private $combinationTranslators = array(); + + /** + * @var array + */ + private $functionTranslators = array(); + + /** + * @var array + */ + private $pseudoClassTranslators = array(); + + /** + * @var array + */ + private $attributeMatchingTranslators = array(); + + /** + * Constructor. + */ + public function __construct(ParserInterface $parser = null) + { + $this->mainParser = $parser ?: new Parser(); + + $this + ->registerExtension(new Extension\NodeExtension($this)) + ->registerExtension(new Extension\CombinationExtension()) + ->registerExtension(new Extension\FunctionExtension()) + ->registerExtension(new Extension\PseudoClassExtension()) + ->registerExtension(new Extension\AttributeMatchingExtension()) + ; + } + + /** + * @param string $element + * + * @return string + */ + public static function getXpathLiteral($element) + { + if (false === strpos($element, "'")) { + return "'".$element."'"; + } + + if (false === strpos($element, '"')) { + return '"'.$element.'"'; + } + + $string = $element; + $parts = array(); + while (true) { + if (false !== $pos = strpos($string, "'")) { + $parts[] = sprintf("'%s'", substr($string, 0, $pos)); + $parts[] = "\"'\""; + $string = substr($string, $pos + 1); + } else { + $parts[] = "'$string'"; + break; + } + } + + return sprintf('concat(%s)', implode($parts, ', ')); + } + + /** + * {@inheritdoc} + */ + public function cssToXPath($cssExpr, $prefix = 'descendant-or-self::') + { + $selectors = $this->parseSelectors($cssExpr); + + /** @var SelectorNode $selector */ + foreach ($selectors as $selector) { + if (null !== $selector->getPseudoElement()) { + throw new ExpressionErrorException('Pseudo-elements are not supported.'); + } + } + + $translator = $this; + + return implode(' | ', array_map(function (SelectorNode $selector) use ($translator, $prefix) { + return $translator->selectorToXPath($selector, $prefix); + }, $selectors)); + } + + /** + * {@inheritdoc} + */ + public function selectorToXPath(SelectorNode $selector, $prefix = 'descendant-or-self::') + { + return ($prefix ?: '').$this->nodeToXPath($selector); + } + + /** + * Registers an extension. + * + * @param Extension\ExtensionInterface $extension + * + * @return Translator + */ + public function registerExtension(Extension\ExtensionInterface $extension) + { + $this->extensions[$extension->getName()] = $extension; + + $this->nodeTranslators = array_merge($this->nodeTranslators, $extension->getNodeTranslators()); + $this->combinationTranslators = array_merge($this->combinationTranslators, $extension->getCombinationTranslators()); + $this->functionTranslators = array_merge($this->functionTranslators, $extension->getFunctionTranslators()); + $this->pseudoClassTranslators = array_merge($this->pseudoClassTranslators, $extension->getPseudoClassTranslators()); + $this->attributeMatchingTranslators = array_merge($this->attributeMatchingTranslators, $extension->getAttributeMatchingTranslators()); + + return $this; + } + + /** + * @param string $name + * + * @return Extension\ExtensionInterface + * + * @throws ExpressionErrorException + */ + public function getExtension($name) + { + if (!isset($this->extensions[$name])) { + throw new ExpressionErrorException(sprintf('Extension "%s" not registered.', $name)); + } + + return $this->extensions[$name]; + } + + /** + * Registers a shortcut parser. + * + * @param ParserInterface $shortcut + * + * @return Translator + */ + public function registerParserShortcut(ParserInterface $shortcut) + { + $this->shortcutParsers[] = $shortcut; + + return $this; + } + + /** + * @param NodeInterface $node + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function nodeToXPath(NodeInterface $node) + { + if (!isset($this->nodeTranslators[$node->getNodeName()])) { + throw new ExpressionErrorException(sprintf('Node "%s" not supported.', $node->getNodeName())); + } + + return call_user_func($this->nodeTranslators[$node->getNodeName()], $node); + } + + /** + * @param string $combiner + * @param NodeInterface $xpath + * @param NodeInterface $combinedXpath + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function addCombination($combiner, NodeInterface $xpath, NodeInterface $combinedXpath) + { + if (!isset($this->combinationTranslators[$combiner])) { + throw new ExpressionErrorException(sprintf('Combiner "%s" not supported.', $combiner)); + } + + return call_user_func($this->combinationTranslators[$combiner], $this->nodeToXPath($xpath), $this->nodeToXPath($combinedXpath)); + } + + /** + * @param XPathExpr $xpath + * @param FunctionNode $function + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function addFunction(XPathExpr $xpath, FunctionNode $function) + { + if (!isset($this->functionTranslators[$function->getName()])) { + throw new ExpressionErrorException(sprintf('Function "%s" not supported.', $function->getName())); + } + + return call_user_func($this->functionTranslators[$function->getName()], $xpath, $function); + } + + /** + * @param XPathExpr $xpath + * @param string $pseudoClass + * + * @return XPathExpr + * + * @throws ExpressionErrorException + */ + public function addPseudoClass(XPathExpr $xpath, $pseudoClass) + { + if (!isset($this->pseudoClassTranslators[$pseudoClass])) { + throw new ExpressionErrorException(sprintf('Pseudo-class "%s" not supported.', $pseudoClass)); + } + + return call_user_func($this->pseudoClassTranslators[$pseudoClass], $xpath); + } + + /** + * @param XPathExpr $xpath + * @param string $operator + * @param string $attribute + * @param string $value + * + * @throws ExpressionErrorException + * + * @return XPathExpr + */ + public function addAttributeMatching(XPathExpr $xpath, $operator, $attribute, $value) + { + if (!isset($this->attributeMatchingTranslators[$operator])) { + throw new ExpressionErrorException(sprintf('Attribute matcher operator "%s" not supported.', $operator)); + } + + return call_user_func($this->attributeMatchingTranslators[$operator], $xpath, $attribute, $value); + } + + /** + * @param string $css + * + * @return SelectorNode[] + */ + private function parseSelectors($css) + { + foreach ($this->shortcutParsers as $shortcut) { + $tokens = $shortcut->parse($css); + + if (!empty($tokens)) { + return $tokens; + } + } + + return $this->mainParser->parse($css); + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/TranslatorInterface.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/TranslatorInterface.php new file mode 100644 index 0000000..b26cf5b --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/TranslatorInterface.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath; + +use Symfony\Component\CssSelector\Node\SelectorNode; + +/** + * XPath expression translator interface. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +interface TranslatorInterface +{ + /** + * Translates a CSS selector to an XPath expression. + * + * @param string $cssExpr + * @param string $prefix + * + * @return XPathExpr + */ + public function cssToXPath($cssExpr, $prefix = 'descendant-or-self::'); + + /** + * Translates a parsed selector node to an XPath expression + * + * @param SelectorNode $selector + * @param string $prefix + * + * @return XPathExpr + */ + public function selectorToXPath(SelectorNode $selector, $prefix = 'descendant-or-self::'); +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/XPathExpr.php b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/XPathExpr.php new file mode 100644 index 0000000..6f5162e --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/XPath/XPathExpr.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\CssSelector\XPath; + +/** + * XPath expression translator interface. + * + * This component is a port of the Python cssselector library, + * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. + * + * @author Jean-François Simon + */ +class XPathExpr +{ + /** + * @var string + */ + private $path; + + /** + * @var string + */ + private $element; + + /** + * @var string + */ + private $condition; + + /** + * @param string $path + * @param string $element + * @param string $condition + * @param boolean $starPrefix + */ + public function __construct($path = '', $element = '*', $condition = '', $starPrefix = false) + { + $this->path = $path; + $this->element = $element; + $this->condition = $condition; + + if ($starPrefix) { + $this->addStarPrefix(); + } + } + + /** + * @return string + */ + public function getElement() + { + return $this->element; + } + + /** + * @param $condition + * + * @return XPathExpr + */ + public function addCondition($condition) + { + $this->condition = $this->condition ? sprintf('%s and (%s)', $this->condition, $condition) : $condition; + + return $this; + } + + /** + * @return string + */ + public function getCondition() + { + return $this->condition; + } + + /** + * @return XPathExpr + */ + public function addNameTest() + { + if ('*' !== $this->element) { + $this->addCondition('name() = '.Translator::getXpathLiteral($this->element)); + $this->element = '*'; + } + + return $this; + } + + /** + * @return XPathExpr + */ + public function addStarPrefix() + { + $this->path .= '*/'; + + return $this; + } + + /** + * Joins another XPathExpr with a combiner. + * + * @param string $combiner + * @param XPathExpr $expr + * + * @return XPathExpr + */ + public function join($combiner, XPathExpr $expr) + { + $path = $this->__toString().$combiner; + + if ('*/' !== $expr->path) { + $path .= $expr->path; + } + + $this->path = $path; + $this->element = $expr->element; + $this->condition = $expr->condition; + + return $this; + } + + /** + * @return string + */ + public function __toString() + { + $path = $this->path.$this->element; + $condition = null === $this->condition || '' === $this->condition ? '' : '['.$this->condition.']'; + + return $path.$condition; + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/composer.json b/vendor/symfony/css-selector/Symfony/Component/CssSelector/composer.json new file mode 100644 index 0000000..12d8c8e --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/css-selector", + "type": "library", + "description": "Symfony CssSelector Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\CssSelector\\": "" } + }, + "target-dir": "Symfony/Component/CssSelector", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + } +} diff --git a/vendor/symfony/css-selector/Symfony/Component/CssSelector/phpunit.xml.dist b/vendor/symfony/css-selector/Symfony/Component/CssSelector/phpunit.xml.dist new file mode 100644 index 0000000..a19dc00 --- /dev/null +++ b/vendor/symfony/css-selector/Symfony/Component/CssSelector/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/debug/Symfony/Component/Debug/.gitignore b/vendor/symfony/debug/Symfony/Component/Debug/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/debug/Symfony/Component/Debug/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/debug/Symfony/Component/Debug/CHANGELOG.md b/vendor/symfony/debug/Symfony/Component/Debug/CHANGELOG.md new file mode 100644 index 0000000..2ad5ce6 --- /dev/null +++ b/vendor/symfony/debug/Symfony/Component/Debug/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +2.3.0 +----- + + * added the component diff --git a/vendor/symfony/debug/Symfony/Component/Debug/Debug.php b/vendor/symfony/debug/Symfony/Component/Debug/Debug.php new file mode 100644 index 0000000..2e36805 --- /dev/null +++ b/vendor/symfony/debug/Symfony/Component/Debug/Debug.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +use Symfony\Component\ClassLoader\DebugClassLoader; + +/** + * Registers all the debug tools. + * + * @author Fabien Potencier + */ +class Debug +{ + private static $enabled = false; + + /** + * Enables the debug tools. + * + * This method registers an error handler and an exception handler. + * + * If the Symfony ClassLoader component is available, a special + * class loader is also registered. + * + * @param integer $errorReportingLevel The level of error reporting you want + * @param Boolean $displayErrors Whether to display errors (for development) or just log them (for production) + */ + public static function enable($errorReportingLevel = null, $displayErrors = true) + { + if (static::$enabled) { + return; + } + + static::$enabled = true; + + error_reporting(-1); + + ErrorHandler::register($errorReportingLevel, $displayErrors); + if ('cli' !== php_sapi_name()) { + ExceptionHandler::register(); + // CLI - display errors only if they're not already logged to STDERR + } elseif ($displayErrors && (!ini_get('log_errors') || ini_get('error_log'))) { + ini_set('display_errors', 1); + } + + if (class_exists('Symfony\Component\ClassLoader\DebugClassLoader')) { + DebugClassLoader::enable(); + } + } +} diff --git a/vendor/symfony/debug/Symfony/Component/Debug/ErrorHandler.php b/vendor/symfony/debug/Symfony/Component/Debug/ErrorHandler.php new file mode 100644 index 0000000..52fd7a5 --- /dev/null +++ b/vendor/symfony/debug/Symfony/Component/Debug/ErrorHandler.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\Exception\ContextErrorException; +use Psr\Log\LoggerInterface; + +/** + * ErrorHandler. + * + * @author Fabien Potencier + * @author Konstantin Myakshin + */ +class ErrorHandler +{ + const TYPE_DEPRECATION = -100; + + private $levels = array( + E_WARNING => 'Warning', + E_NOTICE => 'Notice', + E_USER_ERROR => 'User Error', + E_USER_WARNING => 'User Warning', + E_USER_NOTICE => 'User Notice', + E_STRICT => 'Runtime Notice', + E_RECOVERABLE_ERROR => 'Catchable Fatal Error', + E_DEPRECATED => 'Deprecated', + E_USER_DEPRECATED => 'User Deprecated', + E_ERROR => 'Error', + E_CORE_ERROR => 'Core Error', + E_COMPILE_ERROR => 'Compile Error', + E_PARSE => 'Parse', + ); + + private $level; + + private $reservedMemory; + + private $displayErrors; + + /** + * @var LoggerInterface[] Loggers for channels + */ + private static $loggers = array(); + + /** + * Registers the error handler. + * + * @param integer $level The level at which the conversion to Exception is done (null to use the error_reporting() value and 0 to disable) + * @param Boolean $displayErrors Display errors (for dev environment) or just log they (production usage) + * + * @return The registered error handler + */ + public static function register($level = null, $displayErrors = true) + { + $handler = new static(); + $handler->setLevel($level); + $handler->setDisplayErrors($displayErrors); + + ini_set('display_errors', 0); + set_error_handler(array($handler, 'handle')); + register_shutdown_function(array($handler, 'handleFatal')); + $handler->reservedMemory = str_repeat('x', 10240); + + return $handler; + } + + public function setLevel($level) + { + $this->level = null === $level ? error_reporting() : $level; + } + + public function setDisplayErrors($displayErrors) + { + $this->displayErrors = $displayErrors; + } + + public static function setLogger(LoggerInterface $logger, $channel = 'deprecation') + { + self::$loggers[$channel] = $logger; + } + + /** + * @throws ContextErrorException When error_reporting returns error + */ + public function handle($level, $message, $file = 'unknown', $line = 0, $context = array()) + { + if (0 === $this->level) { + return false; + } + + if ($level & (E_USER_DEPRECATED | E_DEPRECATED)) { + if (isset(self::$loggers['deprecation'])) { + if (version_compare(PHP_VERSION, '5.4', '<')) { + $stack = array_map( + function ($row) { + unset($row['args']); + + return $row; + }, + array_slice(debug_backtrace(false), 0, 10) + ); + } else { + $stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10); + } + + self::$loggers['deprecation']->warning($message, array('type' => self::TYPE_DEPRECATION, 'stack' => $stack)); + } + + return true; + } + + if ($this->displayErrors && error_reporting() & $level && $this->level & $level) { + // make sure the ContextErrorException class is loaded (https://bugs.php.net/bug.php?id=65322) + if (!class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) { + require __DIR__.'/Exception/ContextErrorException.php'; + } + + throw new ContextErrorException(sprintf('%s: %s in %s line %d', isset($this->levels[$level]) ? $this->levels[$level] : $level, $message, $file, $line), 0, $level, $file, $line, $context); + } + + return false; + } + + public function handleFatal() + { + if (null === $error = error_get_last()) { + return; + } + + unset($this->reservedMemory); + $type = $error['type']; + if (0 === $this->level || !in_array($type, array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE))) { + return; + } + + if (isset(self::$loggers['emergency'])) { + $fatal = array( + 'type' => $type, + 'file' => $error['file'], + 'line' => $error['line'], + ); + + self::$loggers['emergency']->emerg($error['message'], $fatal); + } + + if (!$this->displayErrors) { + return; + } + + // get current exception handler + $exceptionHandler = set_exception_handler(function() {}); + restore_exception_handler(); + + if (is_array($exceptionHandler) && $exceptionHandler[0] instanceof ExceptionHandler) { + $level = isset($this->levels[$type]) ? $this->levels[$type] : $type; + $message = sprintf('%s: %s in %s line %d', $level, $error['message'], $error['file'], $error['line']); + $exception = new FatalErrorException($message, 0, $type, $error['file'], $error['line']); + $exceptionHandler[0]->handle($exception); + } + } +} diff --git a/vendor/symfony/debug/Symfony/Component/Debug/Exception/ContextErrorException.php b/vendor/symfony/debug/Symfony/Component/Debug/Exception/ContextErrorException.php new file mode 100644 index 0000000..ea27922 --- /dev/null +++ b/vendor/symfony/debug/Symfony/Component/Debug/Exception/ContextErrorException.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Error Exception with Variable Context. + * + * @author Christian Sciberras + */ +class ContextErrorException extends \ErrorException +{ + private $context = array(); + + public function __construct($message, $code, $severity, $filename, $lineno, $context = array()) + { + parent::__construct($message, $code, $severity, $filename, $lineno); + $this->context = $context; + } + + /** + * @return array Array of variables that existed when the exception occured + */ + public function getContext() + { + return $this->context; + } +} diff --git a/vendor/symfony/debug/Symfony/Component/Debug/Exception/FatalErrorException.php b/vendor/symfony/debug/Symfony/Component/Debug/Exception/FatalErrorException.php new file mode 100644 index 0000000..bf37ef8 --- /dev/null +++ b/vendor/symfony/debug/Symfony/Component/Debug/Exception/FatalErrorException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Fatal Error Exception. + * + * @author Konstanton Myakshin + */ +class FatalErrorException extends \ErrorException +{ +} diff --git a/vendor/symfony/debug/Symfony/Component/Debug/Exception/FlattenException.php b/vendor/symfony/debug/Symfony/Component/Debug/Exception/FlattenException.php new file mode 100644 index 0000000..4f0e815 --- /dev/null +++ b/vendor/symfony/debug/Symfony/Component/Debug/Exception/FlattenException.php @@ -0,0 +1,277 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; + +/** + * FlattenException wraps a PHP Exception to be able to serialize it. + * + * Basically, this class removes all objects from the trace. + * + * @author Fabien Potencier + */ +class FlattenException +{ + private $message; + private $code; + private $previous; + private $trace; + private $class; + private $statusCode; + private $headers; + private $file; + private $line; + + public static function create(\Exception $exception, $statusCode = null, array $headers = array()) + { + $e = new static(); + $e->setMessage($exception->getMessage()); + $e->setCode($exception->getCode()); + + if ($exception instanceof HttpExceptionInterface) { + $statusCode = $exception->getStatusCode(); + $headers = array_merge($headers, $exception->getHeaders()); + } + + if (null === $statusCode) { + $statusCode = 500; + } + + $e->setStatusCode($statusCode); + $e->setHeaders($headers); + $e->setTraceFromException($exception); + $e->setClass(get_class($exception)); + $e->setFile($exception->getFile()); + $e->setLine($exception->getLine()); + if ($exception->getPrevious()) { + $e->setPrevious(static::create($exception->getPrevious())); + } + + return $e; + } + + public function toArray() + { + $exceptions = array(); + foreach (array_merge(array($this), $this->getAllPrevious()) as $exception) { + $exceptions[] = array( + 'message' => $exception->getMessage(), + 'class' => $exception->getClass(), + 'trace' => $exception->getTrace(), + ); + } + + return $exceptions; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function setStatusCode($code) + { + $this->statusCode = $code; + } + + public function getHeaders() + { + return $this->headers; + } + + public function setHeaders(array $headers) + { + $this->headers = $headers; + } + + public function getClass() + { + return $this->class; + } + + public function setClass($class) + { + $this->class = $class; + } + + public function getFile() + { + return $this->file; + } + + public function setFile($file) + { + $this->file = $file; + } + + public function getLine() + { + return $this->line; + } + + public function setLine($line) + { + $this->line = $line; + } + + public function getMessage() + { + return $this->message; + } + + public function setMessage($message) + { + $this->message = $message; + } + + public function getCode() + { + return $this->code; + } + + public function setCode($code) + { + $this->code = $code; + } + + public function getPrevious() + { + return $this->previous; + } + + public function setPrevious(FlattenException $previous) + { + $this->previous = $previous; + } + + public function getAllPrevious() + { + $exceptions = array(); + $e = $this; + while ($e = $e->getPrevious()) { + $exceptions[] = $e; + } + + return $exceptions; + } + + public function getTrace() + { + return $this->trace; + } + + public function setTraceFromException(\Exception $exception) + { + $trace = $exception->getTrace(); + + if ($exception instanceof FatalErrorException) { + if (function_exists('xdebug_get_function_stack')) { + $trace = array_slice(array_reverse(xdebug_get_function_stack()), 4); + + foreach ($trace as $i => $frame) { + // XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695 + if (!isset($frame['type'])) { + $trace[$i]['type'] = '??'; + } + + if ('dynamic' === $trace[$i]['type']) { + $trace[$i]['type'] = '->'; + } elseif ('static' === $trace[$i]['type']) { + $trace[$i]['type'] = '::'; + } + + // XDebug also has a different name for the parameters array + if (isset($frame['params']) && !isset($frame['args'])) { + $trace[$i]['args'] = $frame['params']; + unset($trace[$i]['params']); + } + } + } else { + $trace = array_slice(array_reverse($trace), 1); + } + } + + $this->setTrace($trace, $exception->getFile(), $exception->getLine()); + } + + public function setTrace($trace, $file, $line) + { + $this->trace = array(); + $this->trace[] = array( + 'namespace' => '', + 'short_class' => '', + 'class' => '', + 'type' => '', + 'function' => '', + 'file' => $file, + 'line' => $line, + 'args' => array(), + ); + foreach ($trace as $entry) { + $class = ''; + $namespace = ''; + if (isset($entry['class'])) { + $parts = explode('\\', $entry['class']); + $class = array_pop($parts); + $namespace = implode('\\', $parts); + } + + $this->trace[] = array( + 'namespace' => $namespace, + 'short_class' => $class, + 'class' => isset($entry['class']) ? $entry['class'] : '', + 'type' => isset($entry['type']) ? $entry['type'] : '', + 'function' => isset($entry['function']) ? $entry['function'] : null, + 'file' => isset($entry['file']) ? $entry['file'] : null, + 'line' => isset($entry['line']) ? $entry['line'] : null, + 'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : array(), + ); + } + } + + private function flattenArgs($args, $level = 0) + { + $result = array(); + foreach ($args as $key => $value) { + if (is_object($value)) { + $result[$key] = array('object', get_class($value)); + } elseif (is_array($value)) { + if ($level > 10) { + $result[$key] = array('array', '*DEEP NESTED ARRAY*'); + } else { + $result[$key] = array('array', $this->flattenArgs($value, ++$level)); + } + } elseif (null === $value) { + $result[$key] = array('null', null); + } elseif (is_bool($value)) { + $result[$key] = array('boolean', $value); + } elseif (is_resource($value)) { + $result[$key] = array('resource', get_resource_type($value)); + } elseif ($value instanceof \__PHP_Incomplete_Class) { + // Special case of object, is_object will return false + $result[$key] = array('incomplete-object', $this->getClassNameFromIncomplete($value)); + } else { + $result[$key] = array('string', (string) $value); + } + } + + return $result; + } + + private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value) + { + $array = new \ArrayObject($value); + + return $array['__PHP_Incomplete_Class_Name']; + } +} diff --git a/vendor/symfony/debug/Symfony/Component/Debug/ExceptionHandler.php b/vendor/symfony/debug/Symfony/Component/Debug/ExceptionHandler.php new file mode 100644 index 0000000..cd781b5 --- /dev/null +++ b/vendor/symfony/debug/Symfony/Component/Debug/ExceptionHandler.php @@ -0,0 +1,316 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Debug\Exception\FlattenException; + +if (!defined('ENT_SUBSTITUTE')) { + define('ENT_SUBSTITUTE', 8); +} + +/** + * ExceptionHandler converts an exception to a Response object. + * + * It is mostly useful in debug mode to replace the default PHP/XDebug + * output with something prettier and more useful. + * + * As this class is mainly used during Kernel boot, where nothing is yet + * available, the Response content is always HTML. + * + * @author Fabien Potencier + */ +class ExceptionHandler +{ + private $debug; + private $charset; + + public function __construct($debug = true, $charset = 'UTF-8') + { + $this->debug = $debug; + $this->charset = $charset; + } + + /** + * Registers the exception handler. + * + * @param Boolean $debug + * + * @return ExceptionHandler The registered exception handler + */ + public static function register($debug = true) + { + $handler = new static($debug); + + set_exception_handler(array($handler, 'handle')); + + return $handler; + } + + /** + * Sends a response for the given Exception. + * + * If you have the Symfony HttpFoundation component installed, + * this method will use it to create and send the response. If not, + * it will fallback to plain PHP functions. + * + * @param \Exception $exception An \Exception instance + * + * @see sendPhpResponse + * @see createResponse + */ + public function handle(\Exception $exception) + { + if (class_exists('Symfony\Component\HttpFoundation\Response')) { + $this->createResponse($exception)->send(); + } else { + $this->sendPhpResponse($exception); + } + } + + /** + * Sends the error associated with the given Exception as a plain PHP response. + * + * This method uses plain PHP functions like header() and echo to output + * the response. + * + * @param \Exception|FlattenException $exception An \Exception instance + */ + public function sendPhpResponse($exception) + { + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + + header(sprintf('HTTP/1.0 %s', $exception->getStatusCode())); + foreach ($exception->getHeaders() as $name => $value) { + header($name.': '.$value, false); + } + + echo $this->decorate($this->getContent($exception), $this->getStylesheet($exception)); + } + + /** + * Creates the error Response associated with the given Exception. + * + * @param \Exception|FlattenException $exception An \Exception instance + * + * @return Response A Response instance + */ + public function createResponse($exception) + { + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + + return new Response($this->decorate($this->getContent($exception), $this->getStylesheet($exception)), $exception->getStatusCode(), $exception->getHeaders()); + } + + /** + * Gets the HTML content associated with the given exception. + * + * @param FlattenException $exception A FlattenException instance + * + * @return string The content as a string + */ + public function getContent(FlattenException $exception) + { + switch ($exception->getStatusCode()) { + case 404: + $title = 'Sorry, the page you are looking for could not be found.'; + break; + default: + $title = 'Whoops, looks like something went wrong.'; + } + + $content = ''; + if ($this->debug) { + try { + $count = count($exception->getAllPrevious()); + $total = $count + 1; + foreach ($exception->toArray() as $position => $e) { + $ind = $count - $position + 1; + $class = $this->abbrClass($e['class']); + $message = nl2br($e['message']); + $content .= sprintf(<< +

    %d/%d %s: %s

    + +
    +
      + +EOF + , $ind, $total, $class, $message); + foreach ($e['trace'] as $trace) { + $content .= '
    1. '; + if ($trace['function']) { + $content .= sprintf('at %s%s%s(%s)', $this->abbrClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args'])); + } + if (isset($trace['file']) && isset($trace['line'])) { + if ($linkFormat = ini_get('xdebug.file_link_format')) { + $link = str_replace(array('%f', '%l'), array($trace['file'], $trace['line']), $linkFormat); + $content .= sprintf(' in %s line %s', $link, $trace['file'], $trace['line']); + } else { + $content .= sprintf(' in %s line %s', $trace['file'], $trace['line']); + } + } + $content .= "
    2. \n"; + } + + $content .= "
    \n
    \n"; + } + } catch (\Exception $e) { + // something nasty happened and we cannot throw an exception anymore + if ($this->debug) { + $title = sprintf('Exception thrown when handling an exception (%s: %s)', get_class($exception), $exception->getMessage()); + } else { + $title = 'Whoops, looks like something went wrong.'; + } + } + } + + return << +

    $title

    + $content + +EOF; + } + + /** + * Gets the stylesheet associated with the given exception. + * + * @param FlattenException $exception A FlattenException instance + * + * @return string The stylesheet as a string + */ + public function getStylesheet(FlattenException $exception) + { + return << + + + + + + + + $content + + +EOF; + } + + private function abbrClass($class) + { + $parts = explode('\\', $class); + + return sprintf("%s", $class, array_pop($parts)); + } + + /** + * Formats an array as a string. + * + * @param array $args The argument array + * + * @return string + */ + private function formatArgs(array $args) + { + $result = array(); + foreach ($args as $key => $item) { + if ('object' === $item[0]) { + $formattedValue = sprintf("object(%s)", $this->abbrClass($item[1])); + } elseif ('array' === $item[0]) { + $formattedValue = sprintf("array(%s)", is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); + } elseif ('string' === $item[0]) { + $formattedValue = sprintf("'%s'", htmlspecialchars($item[1], ENT_QUOTES | ENT_SUBSTITUTE, $this->charset)); + } elseif ('null' === $item[0]) { + $formattedValue = 'null'; + } elseif ('boolean' === $item[0]) { + $formattedValue = ''.strtolower(var_export($item[1], true)).''; + } elseif ('resource' === $item[0]) { + $formattedValue = 'resource'; + } else { + $formattedValue = str_replace("\n", '', var_export(htmlspecialchars((string) $item[1], ENT_QUOTES | ENT_SUBSTITUTE, $this->charset), true)); + } + + $result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue); + } + + return implode(', ', $result); + } +} diff --git a/vendor/symfony/debug/Symfony/Component/Debug/LICENSE b/vendor/symfony/debug/Symfony/Component/Debug/LICENSE new file mode 100644 index 0000000..88a57f8 --- /dev/null +++ b/vendor/symfony/debug/Symfony/Component/Debug/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2013 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/debug/Symfony/Component/Debug/README.md b/vendor/symfony/debug/Symfony/Component/Debug/README.md new file mode 100644 index 0000000..dd5bdca --- /dev/null +++ b/vendor/symfony/debug/Symfony/Component/Debug/README.md @@ -0,0 +1,40 @@ +Debug Component +=============== + +Debug provides tools to make debugging easier. + +Enabling all debug tools is as easy as calling the `enable()` method on the +main `Debug` class: + + use Symfony\Component\Debug\Debug; + + Debug::enable(); + +You can also use the tools individually: + + use Symfony\Component\Debug\ErrorHandler; + use Symfony\Component\Debug\ExceptionHandler; + + error_reporting(-1); + + ErrorHandler::register($errorReportingLevel); + if ('cli' !== php_sapi_name()) { + ExceptionHandler::register(); + } elseif (!ini_get('log_errors') || ini_get('error_log')) { + ini_set('display_errors', 1); + } + +Note that the `Debug::enable()` call also registers the debug class loader +from the Symfony ClassLoader component when available. + +This component can optionally take advantage of the features of the HttpKernel +and HttpFoundation components. + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/Debug/ + $ composer.phar install --dev + $ phpunit diff --git a/vendor/symfony/debug/Symfony/Component/Debug/Tests/ErrorHandlerTest.php b/vendor/symfony/debug/Symfony/Component/Debug/Tests/ErrorHandlerTest.php new file mode 100644 index 0000000..24c422f --- /dev/null +++ b/vendor/symfony/debug/Symfony/Component/Debug/Tests/ErrorHandlerTest.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests; + +use Symfony\Component\Debug\ErrorHandler; + +/** + * ErrorHandlerTest + * + * @author Robert Schönthal + */ +class ErrorHandlerTest extends \PHPUnit_Framework_TestCase +{ + public function testCompileTimeError() + { + // the ContextErrorException must not be loaded for this test to work + if (class_exists('Symfony\Component\Debug\Exception\ContextErrorException', false)) { + $this->markTestSkipped('The ContextErrorException class is already loaded.'); + } + + $handler = ErrorHandler::register(E_ALL | E_STRICT); + $displayErrors = ini_get('display_errors'); + ini_set('display_errors', '1'); + + try { + // trigger compile time error + eval(<<<'PHP' +class _BaseCompileTimeError { function foo() {} } +class _CompileTimeError extends _BaseCompileTimeError { function foo($invalid) {} } +PHP + ); + } catch(\Exception $e) { + // if an exception is thrown, the test passed + } + + ini_set('display_errors', $displayErrors); + restore_error_handler(); + } + + public function testConstruct() + { + $handler = ErrorHandler::register(3); + + $level = new \ReflectionProperty($handler, 'level'); + $level->setAccessible(true); + + $this->assertEquals(3, $level->getValue($handler)); + + restore_error_handler(); + } + + public function testHandle() + { + $handler = ErrorHandler::register(0); + $this->assertFalse($handler->handle(0, 'foo', 'foo.php', 12, 'foo')); + + restore_error_handler(); + + $handler = ErrorHandler::register(3); + $this->assertFalse($handler->handle(4, 'foo', 'foo.php', 12, 'foo')); + + restore_error_handler(); + + $handler = ErrorHandler::register(3); + try { + $handler->handle(111, 'foo', 'foo.php', 12, 'foo'); + } catch (\ErrorException $e) { + $this->assertSame('111: foo in foo.php line 12', $e->getMessage()); + $this->assertSame(111, $e->getSeverity()); + $this->assertSame('foo.php', $e->getFile()); + $this->assertSame(12, $e->getLine()); + } + + restore_error_handler(); + + $handler = ErrorHandler::register(E_USER_DEPRECATED); + $this->assertTrue($handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, 'foo')); + + restore_error_handler(); + + $handler = ErrorHandler::register(E_DEPRECATED); + $this->assertTrue($handler->handle(E_DEPRECATED, 'foo', 'foo.php', 12, 'foo')); + + restore_error_handler(); + + $logger = $this->getMock('Psr\Log\LoggerInterface'); + + $that = $this; + $warnArgCheck = function($message, $context) use ($that) { + $that->assertEquals('foo', $message); + $that->assertArrayHasKey('type', $context); + $that->assertEquals($context['type'], ErrorHandler::TYPE_DEPRECATION); + $that->assertArrayHasKey('stack', $context); + $that->assertInternalType('array', $context['stack']); + }; + + $logger + ->expects($this->once()) + ->method('warning') + ->will($this->returnCallback($warnArgCheck)) + ; + + $handler = ErrorHandler::register(E_USER_DEPRECATED); + $handler->setLogger($logger); + $handler->handle(E_USER_DEPRECATED, 'foo', 'foo.php', 12, 'foo'); + + restore_error_handler(); + } +} diff --git a/vendor/symfony/debug/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php b/vendor/symfony/debug/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php new file mode 100644 index 0000000..4a1d99e --- /dev/null +++ b/vendor/symfony/debug/Symfony/Component/Debug/Tests/Exception/FlattenExceptionTest.php @@ -0,0 +1,235 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests\Exception; + +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; +use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException; +use Symfony\Component\HttpKernel\Exception\ConflictHttpException; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\GoneHttpException; +use Symfony\Component\HttpKernel\Exception\LengthRequiredHttpException; +use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException; +use Symfony\Component\HttpKernel\Exception\PreconditionRequiredHttpException; +use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; +use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; +use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException; + +class FlattenExceptionTest extends \PHPUnit_Framework_TestCase +{ + public function testStatusCode() + { + $flattened = FlattenException::create(new \RuntimeException(), 403); + $this->assertEquals('403', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new \RuntimeException()); + $this->assertEquals('500', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new NotFoundHttpException()); + $this->assertEquals('404', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new UnauthorizedHttpException('Basic realm="My Realm"')); + $this->assertEquals('401', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new BadRequestHttpException()); + $this->assertEquals('400', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new NotAcceptableHttpException()); + $this->assertEquals('406', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new ConflictHttpException()); + $this->assertEquals('409', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new MethodNotAllowedHttpException(array('POST'))); + $this->assertEquals('405', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new AccessDeniedHttpException()); + $this->assertEquals('403', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new GoneHttpException()); + $this->assertEquals('410', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new LengthRequiredHttpException()); + $this->assertEquals('411', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new PreconditionFailedHttpException()); + $this->assertEquals('412', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new PreconditionRequiredHttpException()); + $this->assertEquals('428', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new ServiceUnavailableHttpException()); + $this->assertEquals('503', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new TooManyRequestsHttpException()); + $this->assertEquals('429', $flattened->getStatusCode()); + + $flattened = FlattenException::create(new UnsupportedMediaTypeHttpException()); + $this->assertEquals('415', $flattened->getStatusCode()); + } + + public function testHeadersForHttpException() + { + $flattened = FlattenException::create(new MethodNotAllowedHttpException(array('POST'))); + $this->assertEquals(array('Allow' => 'POST'), $flattened->getHeaders()); + + $flattened = FlattenException::create(new UnauthorizedHttpException('Basic realm="My Realm"')); + $this->assertEquals(array('WWW-Authenticate' => 'Basic realm="My Realm"'), $flattened->getHeaders()); + + $flattened = FlattenException::create(new ServiceUnavailableHttpException('Fri, 31 Dec 1999 23:59:59 GMT')); + $this->assertEquals(array('Retry-After' => 'Fri, 31 Dec 1999 23:59:59 GMT'), $flattened->getHeaders()); + + $flattened = FlattenException::create(new ServiceUnavailableHttpException(120)); + $this->assertEquals(array('Retry-After' => 120), $flattened->getHeaders()); + + $flattened = FlattenException::create(new TooManyRequestsHttpException('Fri, 31 Dec 1999 23:59:59 GMT')); + $this->assertEquals(array('Retry-After' => 'Fri, 31 Dec 1999 23:59:59 GMT'), $flattened->getHeaders()); + + $flattened = FlattenException::create(new TooManyRequestsHttpException(120)); + $this->assertEquals(array('Retry-After' => 120), $flattened->getHeaders()); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testFlattenHttpException(\Exception $exception, $statusCode) + { + $flattened = FlattenException::create($exception); + $flattened2 = FlattenException::create($exception); + + $flattened->setPrevious($flattened2); + + $this->assertEquals($exception->getMessage(), $flattened->getMessage(), 'The message is copied from the original exception.'); + $this->assertEquals($exception->getCode(), $flattened->getCode(), 'The code is copied from the original exception.'); + $this->assertEquals(get_class($exception), $flattened->getClass(), 'The class is set to the class of the original exception'); + + } + + /** + * @dataProvider flattenDataProvider + */ + public function testPrevious(\Exception $exception, $statusCode) + { + $flattened = FlattenException::create($exception); + $flattened2 = FlattenException::create($exception); + + $flattened->setPrevious($flattened2); + + $this->assertSame($flattened2,$flattened->getPrevious()); + + $this->assertSame(array($flattened2),$flattened->getAllPrevious()); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testLine(\Exception $exception) + { + $flattened = FlattenException::create($exception); + $this->assertSame($exception->getLine(), $flattened->getLine()); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testFile(\Exception $exception) + { + $flattened = FlattenException::create($exception); + $this->assertSame($exception->getFile(), $flattened->getFile()); + } + + /** + * @dataProvider flattenDataProvider + */ + public function testToArray(\Exception $exception, $statusCode) + { + $flattened = FlattenException::create($exception); + $flattened->setTrace(array(), 'foo.php', 123); + + $this->assertEquals(array( + array( + 'message'=> 'test', + 'class'=>'Exception', + 'trace'=>array(array( + 'namespace' => '', 'short_class' => '', 'class' => '','type' => '','function' => '', 'file' => 'foo.php', 'line' => 123, + 'args' => array() + )), + ) + ), $flattened->toArray()); + } + + public function flattenDataProvider() + { + return array( + array(new \Exception('test', 123), 500), + ); + } + + public function testRecursionInArguments() + { + $a = array('foo', array(2, &$a)); + $exception = $this->createException($a); + + $flattened = FlattenException::create($exception); + $trace = $flattened->getTrace(); + $this->assertContains('*DEEP NESTED ARRAY*', serialize($trace)); + } + + private function createException($foo) + { + return new \Exception(); + } + + public function testSetTraceIncompleteClass() + { + $flattened = FlattenException::create(new \Exception('test', 123)); + $flattened->setTrace( + array( + array( + 'file' => __FILE__, + 'line' => 123, + 'function' => 'test', + 'args' => array( + unserialize('O:14:"BogusTestClass":0:{}') + ), + ), + ), + 'foo.php', 123 + ); + + $this->assertEquals(array( + array( + 'message'=> 'test', + 'class'=>'Exception', + 'trace'=>array( + array( + 'namespace' => '', 'short_class' => '', 'class' => '','type' => '','function' => '', + 'file' => 'foo.php', 'line' => 123, + 'args' => array(), + ), + array( + 'namespace' => '', 'short_class' => '', 'class' => '','type' => '','function' => 'test', + 'file' => __FILE__, 'line' => 123, + 'args' => array( + array( + 'incomplete-object', 'BogusTestClass' + ), + ), + ) + ), + ) + ), $flattened->toArray()); + } +} diff --git a/vendor/symfony/debug/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php b/vendor/symfony/debug/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php new file mode 100644 index 0000000..f187e2d --- /dev/null +++ b/vendor/symfony/debug/Symfony/Component/Debug/Tests/ExceptionHandlerTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Tests; + +use Symfony\Component\Debug\ExceptionHandler; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; + +class ExceptionHandlerTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + public function testDebug() + { + $handler = new ExceptionHandler(false); + $response = $handler->createResponse(new \RuntimeException('Foo')); + + $this->assertContains('

    Whoops, looks like something went wrong.

    ', $response->getContent()); + $this->assertNotContains('
    ', $response->getContent()); + + $handler = new ExceptionHandler(true); + $response = $handler->createResponse(new \RuntimeException('Foo')); + + $this->assertContains('

    Whoops, looks like something went wrong.

    ', $response->getContent()); + $this->assertContains('
    ', $response->getContent()); + } + + public function testStatusCode() + { + $handler = new ExceptionHandler(false); + + $response = $handler->createResponse(new \RuntimeException('Foo')); + $this->assertEquals('500', $response->getStatusCode()); + $this->assertContains('Whoops, looks like something went wrong.', $response->getContent()); + + $response = $handler->createResponse(new NotFoundHttpException('Foo')); + $this->assertEquals('404', $response->getStatusCode()); + $this->assertContains('Sorry, the page you are looking for could not be found.', $response->getContent()); + } + + public function testHeaders() + { + $handler = new ExceptionHandler(false); + + $response = $handler->createResponse(new MethodNotAllowedHttpException(array('POST'))); + $this->assertEquals('405', $response->getStatusCode()); + $this->assertEquals('POST', $response->headers->get('Allow')); + } + + public function testNestedExceptions() + { + $handler = new ExceptionHandler(true); + $response = $handler->createResponse(new \RuntimeException('Foo', null, new \RuntimeException('Bar'))); + } +} diff --git a/vendor/symfony/debug/Symfony/Component/Debug/composer.json b/vendor/symfony/debug/Symfony/Component/Debug/composer.json new file mode 100644 index 0000000..35b170a --- /dev/null +++ b/vendor/symfony/debug/Symfony/Component/Debug/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/debug", + "type": "library", + "description": "Symfony Debug Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/http-kernel": "~2.1", + "symfony/http-foundation": "~2.1" + }, + "suggest": { + "symfony/http-foundation": "", + "symfony/http-kernel": "", + "symfony/class-loader": "" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\Debug\\": "" } + }, + "target-dir": "Symfony/Component/Debug", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + } +} diff --git a/vendor/symfony/debug/Symfony/Component/Debug/phpunit.xml.dist b/vendor/symfony/debug/Symfony/Component/Debug/phpunit.xml.dist new file mode 100644 index 0000000..8bab165 --- /dev/null +++ b/vendor/symfony/debug/Symfony/Component/Debug/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/.gitignore b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/CHANGELOG.md b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/CHANGELOG.md new file mode 100644 index 0000000..2343a51 --- /dev/null +++ b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/CHANGELOG.md @@ -0,0 +1,26 @@ +CHANGELOG +========= + +2.3.0 +----- + + * added Crawler::html() + * [BC BREAK] Crawler::each() and Crawler::reduce() now return Crawler instances instead of DomElement instances + * added schema relative URL support to links + * added support for HTML5 'form' attribute + +2.2.0 +----- + + * added a way to set raw path to the file in FileFormField - necessary for + simulating HTTP requests + +2.1.0 +----- + + * added support for the HTTP PATCH method + * refactored the Form class internals to support multi-dimensional fields + (the public API is backward compatible) + * added a way to get parsing errors for Crawler::addHtmlContent() and + Crawler::addXmlContent() via libxml functions + * added support for submitting a form without a submit button diff --git a/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Crawler.php b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Crawler.php new file mode 100644 index 0000000..c2be830 --- /dev/null +++ b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Crawler.php @@ -0,0 +1,777 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler; + +use Symfony\Component\CssSelector\CssSelector; + +/** + * Crawler eases navigation of a list of \DOMNode objects. + * + * @author Fabien Potencier + * + * @api + */ +class Crawler extends \SplObjectStorage +{ + /** + * @var string The current URI or the base href value + */ + protected $uri; + + /** + * Constructor. + * + * @param mixed $node A Node to use as the base for the crawling + * @param string $uri The current URI or the base href value + * + * @api + */ + public function __construct($node = null, $uri = null) + { + $this->uri = $uri; + + $this->add($node); + } + + /** + * Removes all the nodes. + * + * @api + */ + public function clear() + { + $this->removeAll($this); + } + + /** + * Adds a node to the current list of nodes. + * + * This method uses the appropriate specialized add*() method based + * on the type of the argument. + * + * @param \DOMNodeList|\DOMNode|array|string|null $node A node + * + * @throws \InvalidArgumentException When node is not the expected type. + * + * @api + */ + public function add($node) + { + if ($node instanceof \DOMNodeList) { + $this->addNodeList($node); + } elseif ($node instanceof \DOMNode) { + $this->addNode($node); + } elseif (is_array($node)) { + $this->addNodes($node); + } elseif (is_string($node)) { + $this->addContent($node); + } elseif (null !== $node) { + throw new \InvalidArgumentException(sprintf('Expecting a DOMNodeList or DOMNode instance, an array, a string, or null, but got "%s".', is_object($node) ? get_class($node) : gettype($node))); + } + } + + /** + * Adds HTML/XML content. + * + * If the charset is not set via the content type, it is assumed + * to be ISO-8859-1, which is the default charset defined by the + * HTTP 1.1 specification. + * + * @param string $content A string to parse as HTML/XML + * @param null|string $type The content type of the string + * + * @return null|void + */ + public function addContent($content, $type = null) + { + if (empty($type)) { + $type = 'text/html'; + } + + // DOM only for HTML/XML content + if (!preg_match('/(x|ht)ml/i', $type, $matches)) { + return null; + } + + $charset = 'ISO-8859-1'; + if (false !== $pos = strpos($type, 'charset=')) { + $charset = substr($type, $pos + 8); + if (false !== $pos = strpos($charset, ';')) { + $charset = substr($charset, 0, $pos); + } + } + + if ('x' === $matches[1]) { + $this->addXmlContent($content, $charset); + } else { + $this->addHtmlContent($content, $charset); + } + } + + /** + * Adds an HTML content to the list of nodes. + * + * The libxml errors are disabled when the content is parsed. + * + * If you want to get parsing errors, be sure to enable + * internal errors via libxml_use_internal_errors(true) + * and then, get the errors via libxml_get_errors(). Be + * sure to clear errors with libxml_clear_errors() afterward. + * + * @param string $content The HTML content + * @param string $charset The charset + * + * @api + */ + public function addHtmlContent($content, $charset = 'UTF-8') + { + $current = libxml_use_internal_errors(true); + $disableEntities = libxml_disable_entity_loader(true); + + $dom = new \DOMDocument('1.0', $charset); + $dom->validateOnParse = true; + + if (function_exists('mb_convert_encoding') && in_array(strtolower($charset), array_map('strtolower', mb_list_encodings()))) { + $content = mb_convert_encoding($content, 'HTML-ENTITIES', $charset); + } + + @$dom->loadHTML($content); + + libxml_use_internal_errors($current); + libxml_disable_entity_loader($disableEntities); + + $this->addDocument($dom); + + $base = $this->filterXPath('descendant-or-self::base')->extract(array('href')); + + $baseHref = current($base); + if (count($base) && !empty($baseHref)) { + if ($this->uri) { + $linkNode = $dom->createElement('a'); + $linkNode->setAttribute('href', $baseHref); + $link = new Link($linkNode, $this->uri); + $this->uri = $link->getUri(); + } else { + $this->uri = $baseHref; + } + } + } + + /** + * Adds an XML content to the list of nodes. + * + * The libxml errors are disabled when the content is parsed. + * + * If you want to get parsing errors, be sure to enable + * internal errors via libxml_use_internal_errors(true) + * and then, get the errors via libxml_get_errors(). Be + * sure to clear errors with libxml_clear_errors() afterward. + * + * @param string $content The XML content + * @param string $charset The charset + * + * @api + */ + public function addXmlContent($content, $charset = 'UTF-8') + { + $current = libxml_use_internal_errors(true); + $disableEntities = libxml_disable_entity_loader(true); + + $dom = new \DOMDocument('1.0', $charset); + $dom->validateOnParse = true; + + // remove the default namespace to make XPath expressions simpler + @$dom->loadXML(str_replace('xmlns', 'ns', $content), LIBXML_NONET); + + libxml_use_internal_errors($current); + libxml_disable_entity_loader($disableEntities); + + $this->addDocument($dom); + } + + /** + * Adds a \DOMDocument to the list of nodes. + * + * @param \DOMDocument $dom A \DOMDocument instance + * + * @api + */ + public function addDocument(\DOMDocument $dom) + { + if ($dom->documentElement) { + $this->addNode($dom->documentElement); + } + } + + /** + * Adds a \DOMNodeList to the list of nodes. + * + * @param \DOMNodeList $nodes A \DOMNodeList instance + * + * @api + */ + public function addNodeList(\DOMNodeList $nodes) + { + foreach ($nodes as $node) { + $this->addNode($node); + } + } + + /** + * Adds an array of \DOMNode instances to the list of nodes. + * + * @param \DOMNode[] $nodes An array of \DOMNode instances + * + * @api + */ + public function addNodes(array $nodes) + { + foreach ($nodes as $node) { + $this->add($node); + } + } + + /** + * Adds a \DOMNode instance to the list of nodes. + * + * @param \DOMNode $node A \DOMNode instance + * + * @api + */ + public function addNode(\DOMNode $node) + { + if ($node instanceof \DOMDocument) { + $this->attach($node->documentElement); + } else { + $this->attach($node); + } + } + + /** + * Returns a node given its position in the node list. + * + * @param integer $position The position + * + * @return Crawler A new instance of the Crawler with the selected node, or an empty Crawler if it does not exist. + * + * @api + */ + public function eq($position) + { + foreach ($this as $i => $node) { + if ($i == $position) { + return new static($node, $this->uri); + } + } + + return new static(null, $this->uri); + } + + /** + * Calls an anonymous function on each node of the list. + * + * The anonymous function receives the position and the node wrapped + * in a Crawler instance as arguments. + * + * Example: + * + * $crawler->filter('h1')->each(function ($node, $i) { + * return $node->text(); + * }); + * + * @param \Closure $closure An anonymous function + * + * @return array An array of values returned by the anonymous function + * + * @api + */ + public function each(\Closure $closure) + { + $data = array(); + foreach ($this as $i => $node) { + $data[] = $closure(new static($node, $this->uri), $i); + } + + return $data; + } + + /** + * Reduces the list of nodes by calling an anonymous function. + * + * To remove a node from the list, the anonymous function must return false. + * + * @param \Closure $closure An anonymous function + * + * @return Crawler A Crawler instance with the selected nodes. + * + * @api + */ + public function reduce(\Closure $closure) + { + $nodes = array(); + foreach ($this as $i => $node) { + if (false !== $closure(new static($node, $this->uri), $i)) { + $nodes[] = $node; + } + } + + return new static($nodes, $this->uri); + } + + /** + * Returns the first node of the current selection + * + * @return Crawler A Crawler instance with the first selected node + * + * @api + */ + public function first() + { + return $this->eq(0); + } + + /** + * Returns the last node of the current selection + * + * @return Crawler A Crawler instance with the last selected node + * + * @api + */ + public function last() + { + return $this->eq(count($this) - 1); + } + + /** + * Returns the siblings nodes of the current selection + * + * @return Crawler A Crawler instance with the sibling nodes + * + * @throws \InvalidArgumentException When current node is empty + * + * @api + */ + public function siblings() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return new static($this->sibling($this->getNode(0)->parentNode->firstChild), $this->uri); + } + + /** + * Returns the next siblings nodes of the current selection + * + * @return Crawler A Crawler instance with the next sibling nodes + * + * @throws \InvalidArgumentException When current node is empty + * + * @api + */ + public function nextAll() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return new static($this->sibling($this->getNode(0)), $this->uri); + } + + /** + * Returns the previous sibling nodes of the current selection + * + * @return Crawler A Crawler instance with the previous sibling nodes + * + * @throws \InvalidArgumentException + * + * @api + */ + public function previousAll() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return new static($this->sibling($this->getNode(0), 'previousSibling'), $this->uri); + } + + /** + * Returns the parents nodes of the current selection + * + * @return Crawler A Crawler instance with the parents nodes of the current selection + * + * @throws \InvalidArgumentException When current node is empty + * + * @api + */ + public function parents() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $node = $this->getNode(0); + $nodes = array(); + + while ($node = $node->parentNode) { + if (1 === $node->nodeType && '_root' !== $node->nodeName) { + $nodes[] = $node; + } + } + + return new static($nodes, $this->uri); + } + + /** + * Returns the children nodes of the current selection + * + * @return Crawler A Crawler instance with the children nodes + * + * @throws \InvalidArgumentException When current node is empty + * + * @api + */ + public function children() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $node = $this->getNode(0)->firstChild; + + return new static($node ? $this->sibling($node) : array(), $this->uri); + } + + /** + * Returns the attribute value of the first node of the list. + * + * @param string $attribute The attribute name + * + * @return string The attribute value + * + * @throws \InvalidArgumentException When current node is empty + * + * @api + */ + public function attr($attribute) + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return $this->getNode(0)->getAttribute($attribute); + } + + /** + * Returns the node value of the first node of the list. + * + * @return string The node value + * + * @throws \InvalidArgumentException When current node is empty + * + * @api + */ + public function text() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + return $this->getNode(0)->nodeValue; + } + + /** + * Returns the first node of the list as HTML. + * + * @return string The node html + * + * @throws \InvalidArgumentException When current node is empty + */ + public function html() + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $html = ''; + foreach ($this->getNode(0)->childNodes as $child) { + if (version_compare(PHP_VERSION, '5.3.6', '>=')) { + // node parameter was added to the saveHTML() method in PHP 5.3.6 + // @see http://php.net/manual/en/domdocument.savehtml.php + $html .= $child->ownerDocument->saveHTML($child); + } else { + $document = new \DOMDocument('1.0', 'UTF-8'); + $document->appendChild($document->importNode($child, true)); + $html .= rtrim($document->saveHTML()); + } + } + + return $html; + } + + /** + * Extracts information from the list of nodes. + * + * You can extract attributes or/and the node value (_text). + * + * Example: + * + * $crawler->filter('h1 a')->extract(array('_text', 'href')); + * + * @param array $attributes An array of attributes + * + * @return array An array of extracted values + * + * @api + */ + public function extract($attributes) + { + $attributes = (array) $attributes; + + $data = array(); + foreach ($this as $node) { + $elements = array(); + foreach ($attributes as $attribute) { + if ('_text' === $attribute) { + $elements[] = $node->nodeValue; + } else { + $elements[] = $node->getAttribute($attribute); + } + } + + $data[] = count($attributes) > 1 ? $elements : $elements[0]; + } + + return $data; + } + + /** + * Filters the list of nodes with an XPath expression. + * + * @param string $xpath An XPath expression + * + * @return Crawler A new instance of Crawler with the filtered list of nodes + * + * @api + */ + public function filterXPath($xpath) + { + $document = new \DOMDocument('1.0', 'UTF-8'); + $root = $document->appendChild($document->createElement('_root')); + foreach ($this as $node) { + $root->appendChild($document->importNode($node, true)); + } + + $domxpath = new \DOMXPath($document); + + return new static($domxpath->query($xpath), $this->uri); + } + + /** + * Filters the list of nodes with a CSS selector. + * + * This method only works if you have installed the CssSelector Symfony Component. + * + * @param string $selector A CSS selector + * + * @return Crawler A new instance of Crawler with the filtered list of nodes + * + * @throws \RuntimeException if the CssSelector Component is not available + * + * @api + */ + public function filter($selector) + { + if (!class_exists('Symfony\\Component\\CssSelector\\CssSelector')) { + // @codeCoverageIgnoreStart + throw new \RuntimeException('Unable to filter with a CSS selector as the Symfony CssSelector is not installed (you can use filterXPath instead).'); + // @codeCoverageIgnoreEnd + } + + return $this->filterXPath(CssSelector::toXPath($selector)); + } + + /** + * Selects links by name or alt value for clickable images. + * + * @param string $value The link text + * + * @return Crawler A new instance of Crawler with the filtered list of nodes + * + * @api + */ + public function selectLink($value) + { + $xpath = sprintf('//a[contains(concat(\' \', normalize-space(string(.)), \' \'), %s)] ', static::xpathLiteral(' '.$value.' ')). + sprintf('| //a/img[contains(concat(\' \', normalize-space(string(@alt)), \' \'), %s)]/ancestor::a', static::xpathLiteral(' '.$value.' ')); + + return $this->filterXPath($xpath); + } + + /** + * Selects a button by name or alt value for images. + * + * @param string $value The button text + * + * @return Crawler A new instance of Crawler with the filtered list of nodes + * + * @api + */ + public function selectButton($value) + { + $xpath = sprintf('//input[((@type="submit" or @type="button") and contains(concat(\' \', normalize-space(string(@value)), \' \'), %s)) ', static::xpathLiteral(' '.$value.' ')). + sprintf('or (@type="image" and contains(concat(\' \', normalize-space(string(@alt)), \' \'), %s)) or @id="%s" or @name="%s"] ', static::xpathLiteral(' '.$value.' '), $value, $value). + sprintf('| //button[contains(concat(\' \', normalize-space(string(.)), \' \'), %s) or @id="%s" or @name="%s"]', static::xpathLiteral(' '.$value.' '), $value, $value); + + return $this->filterXPath($xpath); + } + + /** + * Returns a Link object for the first node in the list. + * + * @param string $method The method for the link (get by default) + * + * @return Link A Link instance + * + * @throws \InvalidArgumentException If the current node list is empty + * + * @api + */ + public function link($method = 'get') + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $node = $this->getNode(0); + + return new Link($node, $this->uri, $method); + } + + /** + * Returns an array of Link objects for the nodes in the list. + * + * @return Link[] An array of Link instances + * + * @api + */ + public function links() + { + $links = array(); + foreach ($this as $node) { + $links[] = new Link($node, $this->uri, 'get'); + } + + return $links; + } + + /** + * Returns a Form object for the first node in the list. + * + * @param array $values An array of values for the form fields + * @param string $method The method for the form + * + * @return Form A Form instance + * + * @throws \InvalidArgumentException If the current node list is empty + * + * @api + */ + public function form(array $values = null, $method = null) + { + if (!count($this)) { + throw new \InvalidArgumentException('The current node list is empty.'); + } + + $form = new Form($this->getNode(0), $this->uri, $method); + + if (null !== $values) { + $form->setValues($values); + } + + return $form; + } + + /** + * Converts string for XPath expressions. + * + * Escaped characters are: quotes (") and apostrophe ('). + * + * Examples: + * + * echo Crawler::xpathLiteral('foo " bar'); + * //prints 'foo " bar' + * + * echo Crawler::xpathLiteral("foo ' bar"); + * //prints "foo ' bar" + * + * echo Crawler::xpathLiteral('a\'b"c'); + * //prints concat('a', "'", 'b"c') + * + * + * @param string $s String to be escaped + * + * @return string Converted string + */ + public static function xpathLiteral($s) + { + if (false === strpos($s, "'")) { + return sprintf("'%s'", $s); + } + + if (false === strpos($s, '"')) { + return sprintf('"%s"', $s); + } + + $string = $s; + $parts = array(); + while (true) { + if (false !== $pos = strpos($string, "'")) { + $parts[] = sprintf("'%s'", substr($string, 0, $pos)); + $parts[] = "\"'\""; + $string = substr($string, $pos + 1); + } else { + $parts[] = "'$string'"; + break; + } + } + + return sprintf("concat(%s)", implode($parts, ', ')); + } + + protected function getNode($position) + { + foreach ($this as $i => $node) { + if ($i == $position) { + return $node; + } + // @codeCoverageIgnoreStart + } + + return null; + // @codeCoverageIgnoreEnd + } + + protected function sibling($node, $siblingDir = 'nextSibling') + { + $nodes = array(); + + do { + if ($node !== $this->getNode(0) && $node->nodeType === 1) { + $nodes[] = $node; + } + } while ($node = $node->$siblingDir); + + return $nodes; + } +} diff --git a/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/ChoiceFormField.php b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/ChoiceFormField.php new file mode 100644 index 0000000..2e192cb --- /dev/null +++ b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/ChoiceFormField.php @@ -0,0 +1,307 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Field; + +/** + * ChoiceFormField represents a choice form field. + * + * It is constructed from a HTML select tag, or a HTML checkbox, or radio inputs. + * + * @author Fabien Potencier + * + * @api + */ +class ChoiceFormField extends FormField +{ + /** + * @var string + */ + private $type; + /** + * @var Boolean + */ + private $multiple; + /** + * @var array + */ + private $options; + + /** + * Returns true if the field should be included in the submitted values. + * + * @return Boolean true if the field should be included in the submitted values, false otherwise + */ + public function hasValue() + { + // don't send a value for unchecked checkboxes + if (in_array($this->type, array('checkbox', 'radio')) && null === $this->value) { + return false; + } + + return true; + } + + /** + * Check if the current selected option is disabled + * + * @return Boolean + */ + public function isDisabled() + { + foreach ($this->options as $option) { + if ($option['value'] == $this->value && $option['disabled']) { + return true; + } + } + + return false; + } + + /** + * Sets the value of the field. + * + * @param string $value The value of the field + * + * @api + */ + public function select($value) + { + $this->setValue($value); + } + + /** + * Ticks a checkbox. + * + * @throws \LogicException When the type provided is not correct + * + * @api + */ + public function tick() + { + if ('checkbox' !== $this->type) { + throw new \LogicException(sprintf('You cannot tick "%s" as it is not a checkbox (%s).', $this->name, $this->type)); + } + + $this->setValue(true); + } + + /** + * Ticks a checkbox. + * + * @throws \LogicException When the type provided is not correct + * + * @api + */ + public function untick() + { + if ('checkbox' !== $this->type) { + throw new \LogicException(sprintf('You cannot tick "%s" as it is not a checkbox (%s).', $this->name, $this->type)); + } + + $this->setValue(false); + } + + /** + * Sets the value of the field. + * + * @param string $value The value of the field + * + * @throws \InvalidArgumentException When value type provided is not correct + */ + public function setValue($value) + { + if ('checkbox' == $this->type && false === $value) { + // uncheck + $this->value = null; + } elseif ('checkbox' == $this->type && true === $value) { + // check + $this->value = $this->options[0]['value']; + } else { + if (is_array($value)) { + if (!$this->multiple) { + throw new \InvalidArgumentException(sprintf('The value for "%s" cannot be an array.', $this->name)); + } + + foreach ($value as $v) { + if (!$this->containsOption($v, $this->options)) { + throw new \InvalidArgumentException(sprintf('Input "%s" cannot take "%s" as a value (possible values: %s).', $this->name, $v, implode(', ', $this->availableOptionValues()))); + } + } + } elseif (!$this->containsOption($value, $this->options)) { + throw new \InvalidArgumentException(sprintf('Input "%s" cannot take "%s" as a value (possible values: %s).', $this->name, $value, implode(', ', $this->availableOptionValues()))); + } + + if ($this->multiple) { + $value = (array) $value; + } + + if (is_array($value)) { + $this->value = $value; + } else { + parent::setValue($value); + } + } + } + + /** + * Adds a choice to the current ones. + * + * This method should only be used internally. + * + * @param \DOMNode $node A \DOMNode + * + * @throws \LogicException When choice provided is not multiple nor radio + */ + public function addChoice(\DOMNode $node) + { + if (!$this->multiple && 'radio' != $this->type) { + throw new \LogicException(sprintf('Unable to add a choice for "%s" as it is not multiple or is not a radio button.', $this->name)); + } + + $option = $this->buildOptionValue($node); + $this->options[] = $option; + + if ($node->getAttribute('checked')) { + $this->value = $option['value']; + } + } + + /** + * Returns the type of the choice field (radio, select, or checkbox). + * + * @return string The type + */ + public function getType() + { + return $this->type; + } + + /** + * Returns true if the field accepts multiple values. + * + * @return Boolean true if the field accepts multiple values, false otherwise + */ + public function isMultiple() + { + return $this->multiple; + } + + /** + * Initializes the form field. + * + * @throws \LogicException When node type is incorrect + */ + protected function initialize() + { + if ('input' != $this->node->nodeName && 'select' != $this->node->nodeName) { + throw new \LogicException(sprintf('A ChoiceFormField can only be created from an input or select tag (%s given).', $this->node->nodeName)); + } + + if ('input' == $this->node->nodeName && 'checkbox' != $this->node->getAttribute('type') && 'radio' != $this->node->getAttribute('type')) { + throw new \LogicException(sprintf('A ChoiceFormField can only be created from an input tag with a type of checkbox or radio (given type is %s).', $this->node->getAttribute('type'))); + } + + $this->value = null; + $this->options = array(); + $this->multiple = false; + + if ('input' == $this->node->nodeName) { + $this->type = $this->node->getAttribute('type'); + $optionValue = $this->buildOptionValue($this->node); + $this->options[] = $optionValue; + + if ($this->node->getAttribute('checked')) { + $this->value = $optionValue['value']; + } + } else { + $this->type = 'select'; + if ($this->node->hasAttribute('multiple')) { + $this->multiple = true; + $this->value = array(); + $this->name = str_replace('[]', '', $this->name); + } + + $found = false; + foreach ($this->xpath->query('descendant::option', $this->node) as $option) { + $this->options[] = $this->buildOptionValue($option); + + if ($option->getAttribute('selected')) { + $found = true; + if ($this->multiple) { + $this->value[] = $option->getAttribute('value'); + } else { + $this->value = $option->getAttribute('value'); + } + } + } + + // if no option is selected and if it is a simple select box, take the first option as the value + $option = $this->xpath->query('descendant::option', $this->node)->item(0); + if (!$found && !$this->multiple && $option instanceof \DOMElement) { + $this->value = $option->getAttribute('value'); + } + } + } + + /** + * Returns option value with associated disabled flag + * + * @param \DOMNode $node + * + * @return array + */ + private function buildOptionValue($node) + { + $option = array(); + + $defaultValue = (isset($node->nodeValue) && !empty($node->nodeValue)) ? $node->nodeValue : '1'; + $option['value'] = $node->hasAttribute('value') ? $node->getAttribute('value') : $defaultValue; + $option['disabled'] = ($node->hasAttribute('disabled') && $node->getAttribute('disabled') == 'disabled'); + + return $option; + } + + /** + * Checks whether given vale is in the existing options + * + * @param string $optionValue + * @param array $options + * + * @return bool + */ + public function containsOption($optionValue, $options) + { + foreach ($options as $option) { + if ($option['value'] == $optionValue) { + return true; + } + } + + return false; + } + + /** + * Returns list of available field options + * + * @return array + */ + public function availableOptionValues() + { + $values = array(); + + foreach ($this->options as $option) { + $values[] = $option['value']; + } + + return $values; + } +} diff --git a/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/FileFormField.php b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/FileFormField.php new file mode 100644 index 0000000..4f82220 --- /dev/null +++ b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/FileFormField.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Field; + +/** + * FileFormField represents a file form field (an HTML file input tag). + * + * @author Fabien Potencier + * + * @api + */ +class FileFormField extends FormField +{ + /** + * Sets the PHP error code associated with the field. + * + * @param integer $error The error code (one of UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE, UPLOAD_ERR_PARTIAL, UPLOAD_ERR_NO_FILE, UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, or UPLOAD_ERR_EXTENSION) + * + * @throws \InvalidArgumentException When error code doesn't exist + */ + public function setErrorCode($error) + { + $codes = array(UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE, UPLOAD_ERR_PARTIAL, UPLOAD_ERR_NO_FILE, UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, UPLOAD_ERR_EXTENSION); + if (!in_array($error, $codes)) { + throw new \InvalidArgumentException(sprintf('The error code %s is not valid.', $error)); + } + + $this->value = array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => $error, 'size' => 0); + } + + /** + * Sets the value of the field. + * + * @param string $value The value of the field + * + * @api + */ + public function upload($value) + { + $this->setValue($value); + } + + /** + * Sets the value of the field. + * + * @param string $value The value of the field + */ + public function setValue($value) + { + if (null !== $value && is_readable($value)) { + $error = UPLOAD_ERR_OK; + $size = filesize($value); + $info = pathinfo($value); + $name = $info['basename']; + + // copy to a tmp location + $tmp = sys_get_temp_dir().'/'.sha1(uniqid(mt_rand(), true)); + if (array_key_exists('extension', $info)) { + $tmp .= '.'.$info['extension']; + } + if (is_file($tmp)) { + unlink($tmp); + } + copy($value, $tmp); + $value = $tmp; + } else { + $error = UPLOAD_ERR_NO_FILE; + $size = 0; + $name = ''; + $value = ''; + } + + $this->value = array('name' => $name, 'type' => '', 'tmp_name' => $value, 'error' => $error, 'size' => $size); + } + + /** + * Sets path to the file as string for simulating HTTP request + * + * @param string $path The path to the file + */ + public function setFilePath($path) + { + parent::setValue($path); + } + + /** + * Initializes the form field. + * + * @throws \LogicException When node type is incorrect + */ + protected function initialize() + { + if ('input' != $this->node->nodeName) { + throw new \LogicException(sprintf('A FileFormField can only be created from an input tag (%s given).', $this->node->nodeName)); + } + + if ('file' != $this->node->getAttribute('type')) { + throw new \LogicException(sprintf('A FileFormField can only be created from an input tag with a type of file (given type is %s).', $this->node->getAttribute('type'))); + } + + $this->setValue(null); + } +} diff --git a/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/FormField.php b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/FormField.php new file mode 100644 index 0000000..6412272 --- /dev/null +++ b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/FormField.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Field; + +/** + * FormField is the abstract class for all form fields. + * + * @author Fabien Potencier + */ +abstract class FormField +{ + /** + * @var \DOMNode + */ + protected $node; + /** + * @var string + */ + protected $name; + /** + * @var string + */ + protected $value; + /** + * @var \DOMDocument + */ + protected $document; + /** + * @var \DOMXPath + */ + protected $xpath; + /** + * @var Boolean + */ + protected $disabled; + + /** + * Constructor. + * + * @param \DOMNode $node The node associated with this field + */ + public function __construct(\DOMNode $node) + { + $this->node = $node; + $this->name = $node->getAttribute('name'); + + $this->document = new \DOMDocument('1.0', 'UTF-8'); + $this->node = $this->document->importNode($this->node, true); + + $root = $this->document->appendChild($this->document->createElement('_root')); + $root->appendChild($this->node); + $this->xpath = new \DOMXPath($this->document); + + $this->initialize(); + } + + /** + * Returns the name of the field. + * + * @return string The name of the field + */ + public function getName() + { + return $this->name; + } + + /** + * Gets the value of the field. + * + * @return string|array The value of the field + */ + public function getValue() + { + return $this->value; + } + + /** + * Sets the value of the field. + * + * @param string $value The value of the field + * + * @api + */ + public function setValue($value) + { + $this->value = (string) $value; + } + + /** + * Returns true if the field should be included in the submitted values. + * + * @return Boolean true if the field should be included in the submitted values, false otherwise + */ + public function hasValue() + { + return true; + } + + /** + * Check if the current field is disabled + * + * @return Boolean + */ + public function isDisabled() + { + return $this->node->hasAttribute('disabled'); + } + + /** + * Initializes the form field. + */ + abstract protected function initialize(); +} diff --git a/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/InputFormField.php b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/InputFormField.php new file mode 100644 index 0000000..d3d3957 --- /dev/null +++ b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/InputFormField.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Field; + +/** + * InputFormField represents an input form field (an HTML input tag). + * + * For inputs with type of file, checkbox, or radio, there are other more + * specialized classes (cf. FileFormField and ChoiceFormField). + * + * @author Fabien Potencier + * + * @api + */ +class InputFormField extends FormField +{ + /** + * Initializes the form field. + * + * @throws \LogicException When node type is incorrect + */ + protected function initialize() + { + if ('input' != $this->node->nodeName && 'button' != $this->node->nodeName) { + throw new \LogicException(sprintf('An InputFormField can only be created from an input or button tag (%s given).', $this->node->nodeName)); + } + + if ('checkbox' == $this->node->getAttribute('type')) { + throw new \LogicException('Checkboxes should be instances of ChoiceFormField.'); + } + + if ('file' == $this->node->getAttribute('type')) { + throw new \LogicException('File inputs should be instances of FileFormField.'); + } + + $this->value = $this->node->getAttribute('value'); + } +} diff --git a/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/TextareaFormField.php b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/TextareaFormField.php new file mode 100644 index 0000000..794e966 --- /dev/null +++ b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Field/TextareaFormField.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Field; + +/** + * TextareaFormField represents a textarea form field (an HTML textarea tag). + * + * @author Fabien Potencier + * + * @api + */ +class TextareaFormField extends FormField +{ + /** + * Initializes the form field. + * + * @throws \LogicException When node type is incorrect + */ + protected function initialize() + { + if ('textarea' != $this->node->nodeName) { + throw new \LogicException(sprintf('A TextareaFormField can only be created from a textarea tag (%s given).', $this->node->nodeName)); + } + + $this->value = null; + foreach ($this->node->childNodes as $node) { + $this->value .= $this->document->saveXML($node); + } + } +} diff --git a/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Form.php b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Form.php new file mode 100644 index 0000000..936a594 --- /dev/null +++ b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Form.php @@ -0,0 +1,425 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler; + +use Symfony\Component\DomCrawler\Field\FormField; + +/** + * Form represents an HTML form. + * + * @author Fabien Potencier + * + * @api + */ +class Form extends Link implements \ArrayAccess +{ + /** + * @var \DOMNode + */ + private $button; + + /** + * @var Field\FormField[] + */ + private $fields; + + /** + * Constructor. + * + * @param \DOMNode $node A \DOMNode instance + * @param string $currentUri The URI of the page where the form is embedded + * @param string $method The method to use for the link (if null, it defaults to the method defined by the form) + * + * @throws \LogicException if the node is not a button inside a form tag + * + * @api + */ + public function __construct(\DOMNode $node, $currentUri, $method = null) + { + parent::__construct($node, $currentUri, $method); + + $this->initialize(); + } + + /** + * Gets the form node associated with this form. + * + * @return \DOMNode A \DOMNode instance + */ + public function getFormNode() + { + return $this->node; + } + + /** + * Sets the value of the fields. + * + * @param array $values An array of field values + * + * @return Form + * + * @api + */ + public function setValues(array $values) + { + foreach ($values as $name => $value) { + $this->fields->set($name, $value); + } + + return $this; + } + + /** + * Gets the field values. + * + * The returned array does not include file fields (@see getFiles). + * + * @return array An array of field values. + * + * @api + */ + public function getValues() + { + $values = array(); + foreach ($this->fields->all() as $name => $field) { + if ($field->isDisabled()) { + continue; + } + + if (!$field instanceof Field\FileFormField && $field->hasValue()) { + $values[$name] = $field->getValue(); + } + } + + return $values; + } + + /** + * Gets the file field values. + * + * @return array An array of file field values. + * + * @api + */ + public function getFiles() + { + if (!in_array($this->getMethod(), array('POST', 'PUT', 'DELETE', 'PATCH'))) { + return array(); + } + + $files = array(); + + foreach ($this->fields->all() as $name => $field) { + if ($field->isDisabled()) { + continue; + } + + if ($field instanceof Field\FileFormField) { + $files[$name] = $field->getValue(); + } + } + + return $files; + } + + /** + * Gets the field values as PHP. + * + * This method converts fields with the array notation + * (like foo[bar] to arrays) like PHP does. + * + * @return array An array of field values. + * + * @api + */ + public function getPhpValues() + { + $qs = http_build_query($this->getValues(), '', '&'); + parse_str($qs, $values); + + return $values; + } + + /** + * Gets the file field values as PHP. + * + * This method converts fields with the array notation + * (like foo[bar] to arrays) like PHP does. + * + * @return array An array of field values. + * + * @api + */ + public function getPhpFiles() + { + $qs = http_build_query($this->getFiles(), '', '&'); + parse_str($qs, $values); + + return $values; + } + + /** + * Gets the URI of the form. + * + * The returned URI is not the same as the form "action" attribute. + * This method merges the value if the method is GET to mimics + * browser behavior. + * + * @return string The URI + * + * @api + */ + public function getUri() + { + $uri = parent::getUri(); + + if (!in_array($this->getMethod(), array('POST', 'PUT', 'DELETE', 'PATCH')) && $queryString = http_build_query($this->getValues(), null, '&')) { + $sep = false === strpos($uri, '?') ? '?' : '&'; + $uri .= $sep.$queryString; + } + + return $uri; + } + + protected function getRawUri() + { + return $this->node->getAttribute('action'); + } + + /** + * Gets the form method. + * + * If no method is defined in the form, GET is returned. + * + * @return string The method + * + * @api + */ + public function getMethod() + { + if (null !== $this->method) { + return $this->method; + } + + return $this->node->getAttribute('method') ? strtoupper($this->node->getAttribute('method')) : 'GET'; + } + + /** + * Returns true if the named field exists. + * + * @param string $name The field name + * + * @return Boolean true if the field exists, false otherwise + * + * @api + */ + public function has($name) + { + return $this->fields->has($name); + } + + /** + * Removes a field from the form. + * + * @param string $name The field name + * + * @throws \InvalidArgumentException when the name is malformed + * + * @api + */ + public function remove($name) + { + $this->fields->remove($name); + } + + /** + * Gets a named field. + * + * @param string $name The field name + * + * @return FormField The field instance + * + * @throws \InvalidArgumentException When field is not present in this form + * + * @api + */ + public function get($name) + { + return $this->fields->get($name); + } + + /** + * Sets a named field. + * + * @param FormField $field The field + * + * @api + */ + public function set(FormField $field) + { + $this->fields->add($field); + } + + /** + * Gets all fields. + * + * @return FormField[] An array of fields + * + * @api + */ + public function all() + { + return $this->fields->all(); + } + + /** + * Returns true if the named field exists. + * + * @param string $name The field name + * + * @return Boolean true if the field exists, false otherwise + */ + public function offsetExists($name) + { + return $this->has($name); + } + + /** + * Gets the value of a field. + * + * @param string $name The field name + * + * @return FormField The associated Field instance + * + * @throws \InvalidArgumentException if the field does not exist + */ + public function offsetGet($name) + { + return $this->fields->get($name); + } + + /** + * Sets the value of a field. + * + * @param string $name The field name + * @param string|array $value The value of the field + * + * @throws \InvalidArgumentException if the field does not exist + */ + public function offsetSet($name, $value) + { + $this->fields->set($name, $value); + } + + /** + * Removes a field from the form. + * + * @param string $name The field name + */ + public function offsetUnset($name) + { + $this->fields->remove($name); + } + + /** + * Sets the node for the form. + * + * Expects a 'submit' button \DOMNode and finds the corresponding form element. + * + * @param \DOMNode $node A \DOMNode instance + * + * @throws \LogicException If given node is not a button or input or does not have a form ancestor + */ + protected function setNode(\DOMNode $node) + { + $this->button = $node; + if ('button' == $node->nodeName || ('input' == $node->nodeName && in_array($node->getAttribute('type'), array('submit', 'button', 'image')))) { + if ($node->hasAttribute('form')) { + // if the node has the HTML5-compliant 'form' attribute, use it + $formId = $node->getAttribute('form'); + $form = $node->ownerDocument->getElementById($formId); + if (null === $form) { + throw new \LogicException(sprintf('The selected node has an invalid form attribute (%s).', $formId)); + } + $this->node = $form; + + return; + } + // we loop until we find a form ancestor + do { + if (null === $node = $node->parentNode) { + throw new \LogicException('The selected node does not have a form ancestor.'); + } + } while ('form' != $node->nodeName); + } elseif ('form' != $node->nodeName) { + throw new \LogicException(sprintf('Unable to submit on a "%s" tag.', $node->nodeName)); + } + + $this->node = $node; + } + + private function initialize() + { + $this->fields = new FormFieldRegistry(); + + $document = new \DOMDocument('1.0', 'UTF-8'); + $node = $document->importNode($this->node, true); + $button = $document->importNode($this->button, true); + $root = $document->appendChild($document->createElement('_root')); + $root->appendChild($node); + $root->appendChild($button); + $xpath = new \DOMXPath($document); + + // add descendant elements to the form + $fieldNodes = $xpath->query('descendant::input | descendant::button | descendant::textarea | descendant::select', $root); + foreach ($fieldNodes as $node) { + $this->addField($node, $button); + } + + // find form elements corresponding to the current form by the HTML5 form attribute + if ($this->node->hasAttribute('id')) { + $formId = Crawler::xpathLiteral($this->node->getAttribute('id')); + $xpath = new \DOMXPath($this->node->ownerDocument); + $fieldNodes = $xpath->query(sprintf('descendant::input[@form=%s] | descendant::button[@form=%s] | descendant::textarea[@form=%s] | descendant::select[@form=%s]', $formId, $formId, $formId, $formId)); + foreach ($fieldNodes as $node) { + $this->addField($node, $button); + } + } + } + + private function addField(\DOMNode $node, \DOMNode $button) + { + if (!$node->hasAttribute('name') || !$node->getAttribute('name')) { + return; + } + + $nodeName = $node->nodeName; + + if ($node === $button) { + $this->set(new Field\InputFormField($node)); + } elseif ('select' == $nodeName || 'input' == $nodeName && 'checkbox' == $node->getAttribute('type')) { + $this->set(new Field\ChoiceFormField($node)); + } elseif ('input' == $nodeName && 'radio' == $node->getAttribute('type')) { + if ($this->has($node->getAttribute('name'))) { + $this->get($node->getAttribute('name'))->addChoice($node); + } else { + $this->set(new Field\ChoiceFormField($node)); + } + } elseif ('input' == $nodeName && 'file' == $node->getAttribute('type')) { + $this->set(new Field\FileFormField($node)); + } elseif ('input' == $nodeName && !in_array($node->getAttribute('type'), array('submit', 'button', 'image'))) { + $this->set(new Field\InputFormField($node)); + } elseif ('textarea' == $nodeName) { + $this->set(new Field\TextareaFormField($node)); + } + } +} diff --git a/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/FormFieldRegistry.php b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/FormFieldRegistry.php new file mode 100644 index 0000000..677a9aa --- /dev/null +++ b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/FormFieldRegistry.php @@ -0,0 +1,220 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler; + +use Symfony\Component\DomCrawler\Field\FormField; + +/** + * This is an internal class that must not be used directly. + */ +class FormFieldRegistry +{ + private $fields = array(); + + private $base; + + /** + * Adds a field to the registry. + * + * @param FormField $field The field + * + * @throws \InvalidArgumentException when the name is malformed + */ + public function add(FormField $field) + { + $segments = $this->getSegments($field->getName()); + + $target =& $this->fields; + while ($segments) { + if (!is_array($target)) { + $target = array(); + } + $path = array_shift($segments); + if ('' === $path) { + $target =& $target[]; + } else { + $target =& $target[$path]; + } + } + $target = $field; + } + + /** + * Removes a field and its children from the registry. + * + * @param string $name The fully qualified name of the base field + * + * @throws \InvalidArgumentException when the name is malformed + */ + public function remove($name) + { + $segments = $this->getSegments($name); + $target =& $this->fields; + while (count($segments) > 1) { + $path = array_shift($segments); + if (!array_key_exists($path, $target)) { + return; + } + $target =& $target[$path]; + } + unset($target[array_shift($segments)]); + } + + /** + * Returns the value of the field and its children. + * + * @param string $name The fully qualified name of the field + * + * @return mixed The value of the field + * + * @throws \InvalidArgumentException when the name is malformed + * @throws \InvalidArgumentException if the field does not exist + */ + public function &get($name) + { + $segments = $this->getSegments($name); + $target =& $this->fields; + while ($segments) { + $path = array_shift($segments); + if (!array_key_exists($path, $target)) { + throw new \InvalidArgumentException(sprintf('Unreachable field "%s"', $path)); + } + $target =& $target[$path]; + } + + return $target; + } + + /** + * Tests whether the form has the given field. + * + * @param string $name The fully qualified name of the field + * + * @return Boolean Whether the form has the given field + */ + public function has($name) + { + try { + $this->get($name); + + return true; + } catch (\InvalidArgumentException $e) { + return false; + } + } + + /** + * Set the value of a field and its children. + * + * @param string $name The fully qualified name of the field + * @param mixed $value The value + * + * @throws \InvalidArgumentException when the name is malformed + * @throws \InvalidArgumentException if the field does not exist + */ + public function set($name, $value) + { + $target =& $this->get($name); + if (!is_array($value) || $target instanceof Field\ChoiceFormField) { + $target->setValue($value); + } else { + $fields = self::create($name, $value); + foreach ($fields->all() as $k => $v) { + $this->set($k, $v); + } + } + } + + /** + * Returns the list of field with their value. + * + * @return array The list of fields as array((string) Fully qualified name => (mixed) value) + */ + public function all() + { + return $this->walk($this->fields, $this->base); + } + + /** + * Creates an instance of the class. + * + * This function is made private because it allows overriding the $base and + * the $values properties without any type checking. + * + * @param string $base The fully qualified name of the base field + * @param array $values The values of the fields + * + * @return FormFieldRegistry + */ + private static function create($base, array $values) + { + $registry = new static(); + $registry->base = $base; + $registry->fields = $values; + + return $registry; + } + + /** + * Transforms a PHP array in a list of fully qualified name / value. + * + * @param array $array The PHP array + * @param string $base The name of the base field + * @param array $output The initial values + * + * @return array The list of fields as array((string) Fully qualified name => (mixed) value) + */ + private function walk(array $array, $base = '', array &$output = array()) + { + foreach ($array as $k => $v) { + $path = empty($base) ? $k : sprintf("%s[%s]", $base, $k); + if (is_array($v)) { + $this->walk($v, $path, $output); + } else { + $output[$path] = $v; + } + } + + return $output; + } + + /** + * Splits a field name into segments as a web browser would do. + * + * + * getSegments('base[foo][3][]') = array('base', 'foo, '3', ''); + * + * + * @param string $name The name of the field + * + * @return array The list of segments + * + * @throws \InvalidArgumentException when the name is malformed + */ + private function getSegments($name) + { + if (preg_match('/^(?P[^[]+)(?P(\[.*)|$)/', $name, $m)) { + $segments = array($m['base']); + while (!empty($m['extra'])) { + if (preg_match('/^\[(?P.*?)\](?P.*)$/', $m['extra'], $m)) { + $segments[] = $m['segment']; + } else { + throw new \InvalidArgumentException(sprintf('Malformed field path "%s"', $name)); + } + } + + return $segments; + } + + throw new \InvalidArgumentException(sprintf('Malformed field path "%s"', $name)); + } +} diff --git a/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/LICENSE b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/LICENSE new file mode 100644 index 0000000..88a57f8 --- /dev/null +++ b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2013 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Link.php b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Link.php new file mode 100644 index 0000000..d1d1e51 --- /dev/null +++ b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Link.php @@ -0,0 +1,197 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler; + +/** + * Link represents an HTML link (an HTML a tag). + * + * @author Fabien Potencier + * + * @api + */ +class Link +{ + /** + * @var \DOMNode A \DOMNode instance + */ + protected $node; + /** + * @var string The method to use for the link + */ + protected $method; + /** + * @var string The URI of the page where the link is embedded (or the base href) + */ + protected $currentUri; + + /** + * Constructor. + * + * @param \DOMNode $node A \DOMNode instance + * @param string $currentUri The URI of the page where the link is embedded (or the base href) + * @param string $method The method to use for the link (get by default) + * + * @throws \InvalidArgumentException if the node is not a link + * + * @api + */ + public function __construct(\DOMNode $node, $currentUri, $method = 'GET') + { + if (!in_array(strtolower(substr($currentUri, 0, 4)), array('http', 'file'))) { + throw new \InvalidArgumentException(sprintf('Current URI must be an absolute URL ("%s").', $currentUri)); + } + + $this->setNode($node); + $this->method = $method ? strtoupper($method) : null; + $this->currentUri = $currentUri; + } + + /** + * Gets the node associated with this link. + * + * @return \DOMNode A \DOMNode instance + */ + public function getNode() + { + return $this->node; + } + + /** + * Gets the method associated with this link. + * + * @return string The method + * + * @api + */ + public function getMethod() + { + return $this->method; + } + + /** + * Gets the URI associated with this link. + * + * @return string The URI + * + * @api + */ + public function getUri() + { + $uri = trim($this->getRawUri()); + + // absolute URL? + if (null !== parse_url($uri, PHP_URL_SCHEME)) { + return $uri; + } + + // empty URI + if (!$uri) { + return $this->currentUri; + } + + // only an anchor + if ('#' === $uri[0]) { + $baseUri = $this->currentUri; + if (false !== $pos = strpos($baseUri, '#')) { + $baseUri = substr($baseUri, 0, $pos); + } + + return $baseUri.$uri; + } + + // only a query string + if ('?' === $uri[0]) { + $baseUri = $this->currentUri; + + // remove the query string from the current uri + if (false !== $pos = strpos($baseUri, '?')) { + $baseUri = substr($baseUri, 0, $pos); + } + + return $baseUri.$uri; + } + + // absolute URL with relative schema + if (0 === strpos($uri, '//')) { + return preg_replace('#^([^/]*)//.*$#', '$1', $this->currentUri).$uri; + } + + $baseUri = preg_replace('#^(.*?//[^/]*)(?:\/.*)?$#', '$1', $this->currentUri); + + // absolute path + if ('/' === $uri[0]) { + return $baseUri.$uri; + } + + // relative path + $path = parse_url(substr($this->currentUri, strlen($baseUri)), PHP_URL_PATH); + $path = $this->canonicalizePath(substr($path, 0, strrpos($path, '/')).'/'.$uri); + + return $baseUri.('' === $path || '/' !== $path[0] ? '/' : '').$path; + } + + /** + * Returns raw uri data. + * + * @return string + */ + protected function getRawUri() + { + return $this->node->getAttribute('href'); + } + + /** + * Returns the canonicalized URI path (see RFC 3986, section 5.2.4) + * + * @param string $path URI path + * + * @return string + */ + protected function canonicalizePath($path) + { + if ('' === $path || '/' === $path) { + return $path; + } + + if ('.' === substr($path, -1)) { + $path = $path.'/'; + } + + $output = array(); + + foreach (explode('/', $path) as $segment) { + if ('..' === $segment) { + array_pop($output); + } elseif ('.' !== $segment) { + array_push($output, $segment); + } + } + + return implode('/', $output); + } + + /** + * Sets current \DOMNode instance. + * + * @param \DOMNode $node A \DOMNode instance + * + * @throws \LogicException If given node is not an anchor + */ + protected function setNode(\DOMNode $node) + { + if ('a' != $node->nodeName) { + throw new \LogicException(sprintf('Unable to click on a "%s" tag.', $node->nodeName)); + } + + $this->node = $node; + } +} diff --git a/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/README.md b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/README.md new file mode 100644 index 0000000..721c0d9 --- /dev/null +++ b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/README.md @@ -0,0 +1,32 @@ +DomCrawler Component +==================== + +DomCrawler eases DOM navigation for HTML and XML documents. + +If you are familiar with jQuery, DomCrawler is a PHP equivalent: + + use Symfony\Component\DomCrawler\Crawler; + + $crawler = new Crawler(); + $crawler->addContent('

    Hello World!

    '); + + print $crawler->filterXPath('descendant-or-self::body/p')->text(); + +If you are also using the CssSelector component, you can use CSS Selectors +instead of XPath expressions: + + use Symfony\Component\DomCrawler\Crawler; + + $crawler = new Crawler(); + $crawler->addContent('

    Hello World!

    '); + + print $crawler->filter('body > p')->text(); + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/DomCrawler/ + $ composer.phar install --dev + $ phpunit diff --git a/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Tests/CrawlerTest.php b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Tests/CrawlerTest.php new file mode 100644 index 0000000..e7a3484 --- /dev/null +++ b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Tests/CrawlerTest.php @@ -0,0 +1,675 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Tests; + +use Symfony\Component\DomCrawler\Crawler; + +class CrawlerTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $crawler = new Crawler(); + $this->assertCount(0, $crawler, '__construct() returns an empty crawler'); + + $crawler = new Crawler(new \DOMNode()); + $this->assertCount(1, $crawler, '__construct() takes a node as a first argument'); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::add + */ + public function testAdd() + { + $crawler = new Crawler(); + $crawler->add($this->createDomDocument()); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from a \DOMDocument'); + + $crawler = new Crawler(); + $crawler->add($this->createNodeList()); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from a \DOMNodeList'); + + foreach ($this->createNodeList() as $node) { + $list[] = $node; + } + $crawler = new Crawler(); + $crawler->add($list); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from an array of nodes'); + + $crawler = new Crawler(); + $crawler->add($this->createNodeList()->item(0)); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from an \DOMNode'); + + $crawler = new Crawler(); + $crawler->add('Foo'); + $this->assertEquals('Foo', $crawler->filterXPath('//body')->text(), '->add() adds nodes from a string'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testAddInvalidNode() + { + $crawler = new Crawler(); + $crawler->add(1); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent + */ + public function testAddHtmlContent() + { + $crawler = new Crawler(); + $crawler->addHtmlContent('
    ', 'UTF-8'); + + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addHtmlContent() adds nodes from an HTML string'); + + $crawler->addHtmlContent('', 'UTF-8'); + + $this->assertEquals('http://symfony.com', $crawler->filterXPath('//base')->attr('href'), '->addHtmlContent() adds nodes from an HTML string'); + $this->assertEquals('http://symfony.com/contact', $crawler->filterXPath('//a')->link()->getUri(), '->addHtmlContent() adds nodes from an HTML string'); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent + */ + public function testAddHtmlContentCharset() + { + $crawler = new Crawler(); + $crawler->addHtmlContent('
    Tiếng Việt', 'UTF-8'); + + $this->assertEquals('Tiếng Việt', $crawler->filterXPath('//div')->text()); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent + */ + public function testAddHtmlContentInvalidBaseTag() + { + $crawler = new Crawler(null, 'http://symfony.com'); + + $crawler->addHtmlContent('', 'UTF-8'); + + $this->assertEquals('http://symfony.com/contact', current($crawler->filterXPath('//a')->links())->getUri(), '->addHtmlContent() correctly handles a non-existent base tag href attribute'); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent + */ + public function testAddHtmlContentUnsupportedCharset() + { + $crawler = new Crawler(); + $crawler->addHtmlContent(file_get_contents(__DIR__.'/Fixtures/windows-1250.html'), 'Windows-1250'); + + $this->assertEquals('ŽťÄýů', $crawler->filterXPath('//p')->text()); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addHtmlContent + */ + public function testAddHtmlContentWithErrors() + { + libxml_use_internal_errors(true); + + $crawler = new Crawler(); + $crawler->addHtmlContent(<< + + + + + + + +EOF + , 'UTF-8'); + + $errors = libxml_get_errors(); + $this->assertCount(1, $errors); + $this->assertEquals("Tag nav invalid\n", $errors[0]->message); + + libxml_clear_errors(); + libxml_use_internal_errors(false); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addXmlContent + */ + public function testAddXmlContent() + { + $crawler = new Crawler(); + $crawler->addXmlContent('
    ', 'UTF-8'); + + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addXmlContent() adds nodes from an XML string'); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addXmlContent + */ + public function testAddXmlContentCharset() + { + $crawler = new Crawler(); + $crawler->addXmlContent('
    Tiếng Việt
    ', 'UTF-8'); + + $this->assertEquals('Tiếng Việt', $crawler->filterXPath('//div')->text()); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addXmlContent + */ + public function testAddXmlContentWithErrors() + { + libxml_use_internal_errors(true); + + $crawler = new Crawler(); + $crawler->addXmlContent(<< + + + + +
    + + +EOF + , 'UTF-8'); + + $this->assertTrue(count(libxml_get_errors()) > 1); + + libxml_clear_errors(); + libxml_use_internal_errors(false); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addContent + */ + public function testAddContent() + { + $crawler = new Crawler(); + $crawler->addContent('
    ', 'text/html; charset=UTF-8'); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an HTML string'); + + $crawler = new Crawler(); + $crawler->addContent('
    ', 'text/html; charset=UTF-8; dir=RTL'); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an HTML string with extended content type'); + + $crawler = new Crawler(); + $crawler->addContent('
    '); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() uses text/html as the default type'); + + $crawler = new Crawler(); + $crawler->addContent('
    ', 'text/xml; charset=UTF-8'); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an XML string'); + + $crawler = new Crawler(); + $crawler->addContent('
    ', 'text/xml'); + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an XML string'); + + $crawler = new Crawler(); + $crawler->addContent('foo bar', 'text/plain'); + $this->assertCount(0, $crawler, '->addContent() does nothing if the type is not (x|ht)ml'); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addDocument + */ + public function testAddDocument() + { + $crawler = new Crawler(); + $crawler->addDocument($this->createDomDocument()); + + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addDocument() adds nodes from a \DOMDocument'); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addNodeList + */ + public function testAddNodeList() + { + $crawler = new Crawler(); + $crawler->addNodeList($this->createNodeList()); + + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addNodeList() adds nodes from a \DOMNodeList'); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addNodes + */ + public function testAddNodes() + { + foreach ($this->createNodeList() as $node) { + $list[] = $node; + } + + $crawler = new Crawler(); + $crawler->addNodes($list); + + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addNodes() adds nodes from an array of nodes'); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::addNode + */ + public function testAddNode() + { + $crawler = new Crawler(); + $crawler->addNode($this->createNodeList()->item(0)); + + $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addNode() adds nodes from an \DOMNode'); + } + + public function testClear() + { + $crawler = new Crawler(new \DOMNode()); + $crawler->clear(); + $this->assertCount(0, $crawler, '->clear() removes all the nodes from the crawler'); + } + + public function testEq() + { + $crawler = $this->createTestCrawler()->filterXPath('//li'); + $this->assertNotSame($crawler, $crawler->eq(0), '->eq() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->eq() returns a new instance of a crawler'); + + $this->assertEquals('Two', $crawler->eq(1)->text(), '->eq() returns the nth node of the list'); + $this->assertCount(0, $crawler->eq(100), '->eq() returns an empty crawler if the nth node does not exist'); + } + + public function testEach() + { + $data = $this->createTestCrawler()->filterXPath('//ul[1]/li')->each(function ($node, $i) { + return $i.'-'.$node->text(); + }); + + $this->assertEquals(array('0-One', '1-Two', '2-Three'), $data, '->each() executes an anonymous function on each node of the list'); + } + + public function testReduce() + { + $crawler = $this->createTestCrawler()->filterXPath('//ul[1]/li'); + $nodes = $crawler->reduce(function ($node, $i) { + return $i == 1 ? false : true; + }); + $this->assertNotSame($nodes, $crawler, '->reduce() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $nodes, '->reduce() returns a new instance of a crawler'); + + $this->assertCount(2, $nodes, '->reduce() filters the nodes in the list'); + } + + public function testAttr() + { + $this->assertEquals('first', $this->createTestCrawler()->filterXPath('//li')->attr('class'), '->attr() returns the attribute of the first element of the node list'); + + try { + $this->createTestCrawler()->filterXPath('//ol')->attr('class'); + $this->fail('->attr() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->attr() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testText() + { + $this->assertEquals('One', $this->createTestCrawler()->filterXPath('//li')->text(), '->text() returns the node value of the first element of the node list'); + + try { + $this->createTestCrawler()->filterXPath('//ol')->text(); + $this->fail('->text() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->text() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testHtml() + { + $this->assertEquals('Bar', $this->createTestCrawler()->filterXPath('//a[5]')->html()); + $this->assertEquals('' + , trim($this->createTestCrawler()->filterXPath('//form[@id="FooFormId"]')->html())); + + try { + $this->createTestCrawler()->filterXPath('//ol')->html(); + $this->fail('->html() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->html() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testExtract() + { + $crawler = $this->createTestCrawler()->filterXPath('//ul[1]/li'); + + $this->assertEquals(array('One', 'Two', 'Three'), $crawler->extract('_text'), '->extract() returns an array of extracted data from the node list'); + $this->assertEquals(array(array('One', 'first'), array('Two', ''), array('Three', '')), $crawler->extract(array('_text', 'class')), '->extract() returns an array of extracted data from the node list'); + + $this->assertEquals(array(), $this->createTestCrawler()->filterXPath('//ol')->extract('_text'), '->extract() returns an empty array if the node list is empty'); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::filterXPath + */ + public function testFilterXPath() + { + $crawler = $this->createTestCrawler(); + $this->assertNotSame($crawler, $crawler->filterXPath('//li'), '->filterXPath() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->filterXPath() returns a new instance of a crawler'); + + $crawler = $this->createTestCrawler()->filterXPath('//ul'); + + $this->assertCount(6, $crawler->filterXPath('//li'), '->filterXPath() filters the node list with the XPath expression'); + } + + /** + * @covers Symfony\Component\DomCrawler\Crawler::filter + */ + public function testFilter() + { + if (!class_exists('Symfony\Component\CssSelector\CssSelector')) { + $this->markTestSkipped('The "CssSelector" component is not available'); + } + + $crawler = $this->createTestCrawler(); + $this->assertNotSame($crawler, $crawler->filter('li'), '->filter() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->filter() returns a new instance of a crawler'); + + $crawler = $this->createTestCrawler()->filter('ul'); + + $this->assertCount(6, $crawler->filter('li'), '->filter() filters the node list with the CSS selector'); + } + + public function testSelectLink() + { + $crawler = $this->createTestCrawler(); + $this->assertNotSame($crawler, $crawler->selectLink('Foo'), '->selectLink() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->selectLink() returns a new instance of a crawler'); + + $this->assertCount(1, $crawler->selectLink('Fabien\'s Foo'), '->selectLink() selects links by the node values'); + $this->assertCount(1, $crawler->selectLink('Fabien\'s Bar'), '->selectLink() selects links by the alt attribute of a clickable image'); + + $this->assertCount(2, $crawler->selectLink('Fabien"s Foo'), '->selectLink() selects links by the node values'); + $this->assertCount(2, $crawler->selectLink('Fabien"s Bar'), '->selectLink() selects links by the alt attribute of a clickable image'); + + $this->assertCount(1, $crawler->selectLink('\' Fabien"s Foo'), '->selectLink() selects links by the node values'); + $this->assertCount(1, $crawler->selectLink('\' Fabien"s Bar'), '->selectLink() selects links by the alt attribute of a clickable image'); + + $this->assertCount(4, $crawler->selectLink('Foo'), '->selectLink() selects links by the node values'); + $this->assertCount(4, $crawler->selectLink('Bar'), '->selectLink() selects links by the node values'); + } + + public function testSelectButton() + { + $crawler = $this->createTestCrawler(); + $this->assertNotSame($crawler, $crawler->selectButton('FooValue'), '->selectButton() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->selectButton() returns a new instance of a crawler'); + + $this->assertEquals(1, $crawler->selectButton('FooValue')->count(), '->selectButton() selects buttons'); + $this->assertEquals(1, $crawler->selectButton('FooName')->count(), '->selectButton() selects buttons'); + $this->assertEquals(1, $crawler->selectButton('FooId')->count(), '->selectButton() selects buttons'); + + $this->assertEquals(1, $crawler->selectButton('BarValue')->count(), '->selectButton() selects buttons'); + $this->assertEquals(1, $crawler->selectButton('BarName')->count(), '->selectButton() selects buttons'); + $this->assertEquals(1, $crawler->selectButton('BarId')->count(), '->selectButton() selects buttons'); + + $this->assertEquals(1, $crawler->selectButton('FooBarValue')->count(), '->selectButton() selects buttons with form attribute too'); + $this->assertEquals(1, $crawler->selectButton('FooBarName')->count(), '->selectButton() selects buttons with form attribute too'); + } + + public function testLink() + { + $crawler = $this->createTestCrawler('http://example.com/bar/')->selectLink('Foo'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Link', $crawler->link(), '->link() returns a Link instance'); + + $this->assertEquals('POST', $crawler->link('post')->getMethod(), '->link() takes a method as its argument'); + + $crawler = $this->createTestCrawler('http://example.com/bar')->selectLink('GetLink'); + $this->assertEquals('http://example.com/bar?get=param', $crawler->link()->getUri(), '->link() returns a Link instance'); + + try { + $this->createTestCrawler()->filterXPath('//ol')->link(); + $this->fail('->link() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->link() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testLinks() + { + $crawler = $this->createTestCrawler('http://example.com/bar/')->selectLink('Foo'); + $this->assertInternalType('array', $crawler->links(), '->links() returns an array'); + + $this->assertCount(4, $crawler->links(), '->links() returns an array'); + $links = $crawler->links(); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Link', $links[0], '->links() returns an array of Link instances'); + + $this->assertEquals(array(), $this->createTestCrawler()->filterXPath('//ol')->links(), '->links() returns an empty array if the node selection is empty'); + } + + public function testForm() + { + $testCrawler = $this->createTestCrawler('http://example.com/bar/'); + $crawler = $testCrawler->selectButton('FooValue'); + $crawler2 = $testCrawler->selectButton('FooBarValue'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Form', $crawler->form(), '->form() returns a Form instance'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Form', $crawler2->form(), '->form() returns a Form instance'); + + $this->assertEquals($crawler->form()->getFormNode()->getAttribute('id'), $crawler2->form()->getFormNode()->getAttribute('id'), '->form() works on elements with form attribute'); + + $this->assertEquals(array('FooName' => 'FooBar', 'TextName' => 'TextValue', 'FooTextName' => 'FooTextValue'), $crawler->form(array('FooName' => 'FooBar'))->getValues(), '->form() takes an array of values to submit as its first argument'); + $this->assertEquals(array('FooName' => 'FooValue', 'TextName' => 'TextValue', 'FooTextName' => 'FooTextValue'), $crawler->form()->getValues(), '->form() takes an array of values to submit as its first argument'); + $this->assertEquals(array('FooBarName' => 'FooBarValue', 'TextName' => 'TextValue', 'FooTextName' => 'FooTextValue'), $crawler2->form()->getValues(), '->form() takes an array of values to submit as its first argument'); + + try { + $this->createTestCrawler()->filterXPath('//ol')->form(); + $this->fail('->form() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->form() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testLast() + { + $crawler = $this->createTestCrawler()->filterXPath('//ul[1]/li'); + $this->assertNotSame($crawler, $crawler->last(), '->last() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->last() returns a new instance of a crawler'); + + $this->assertEquals('Three', $crawler->last()->text()); + } + + public function testFirst() + { + $crawler = $this->createTestCrawler()->filterXPath('//li'); + $this->assertNotSame($crawler, $crawler->first(), '->first() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->first() returns a new instance of a crawler'); + + $this->assertEquals('One', $crawler->first()->text()); + } + + public function testSiblings() + { + $crawler = $this->createTestCrawler()->filterXPath('//li')->eq(1); + $this->assertNotSame($crawler, $crawler->siblings(), '->siblings() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->siblings() returns a new instance of a crawler'); + + $nodes = $crawler->siblings(); + $this->assertEquals(2, $nodes->count()); + $this->assertEquals('One', $nodes->eq(0)->text()); + $this->assertEquals('Three', $nodes->eq(1)->text()); + + $nodes = $this->createTestCrawler()->filterXPath('//li')->eq(0)->siblings(); + $this->assertEquals(2, $nodes->count()); + $this->assertEquals('Two', $nodes->eq(0)->text()); + $this->assertEquals('Three', $nodes->eq(1)->text()); + + try { + $this->createTestCrawler()->filterXPath('//ol')->siblings(); + $this->fail('->siblings() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->siblings() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testNextAll() + { + $crawler = $this->createTestCrawler()->filterXPath('//li')->eq(1); + $this->assertNotSame($crawler, $crawler->nextAll(), '->nextAll() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->nextAll() returns a new instance of a crawler'); + + $nodes = $crawler->nextAll(); + $this->assertEquals(1, $nodes->count()); + $this->assertEquals('Three', $nodes->eq(0)->text()); + + try { + $this->createTestCrawler()->filterXPath('//ol')->nextAll(); + $this->fail('->nextAll() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->nextAll() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testPreviousAll() + { + $crawler = $this->createTestCrawler()->filterXPath('//li')->eq(2); + $this->assertNotSame($crawler, $crawler->previousAll(), '->previousAll() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->previousAll() returns a new instance of a crawler'); + + $nodes = $crawler->previousAll(); + $this->assertEquals(2, $nodes->count()); + $this->assertEquals('Two', $nodes->eq(0)->text()); + + try { + $this->createTestCrawler()->filterXPath('//ol')->previousAll(); + $this->fail('->previousAll() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->previousAll() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testChildren() + { + $crawler = $this->createTestCrawler()->filterXPath('//ul'); + $this->assertNotSame($crawler, $crawler->children(), '->children() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->children() returns a new instance of a crawler'); + + $nodes = $crawler->children(); + $this->assertEquals(3, $nodes->count()); + $this->assertEquals('One', $nodes->eq(0)->text()); + $this->assertEquals('Two', $nodes->eq(1)->text()); + $this->assertEquals('Three', $nodes->eq(2)->text()); + + try { + $this->createTestCrawler()->filterXPath('//ol')->children(); + $this->fail('->children() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->children() throws an \InvalidArgumentException if the node list is empty'); + } + + try { + $crawler = new Crawler('

    '); + $crawler->filter('p')->children(); + $this->assertTrue(true, '->children() does not trigger a notice if the node has no children'); + } catch (\PHPUnit_Framework_Error_Notice $e) { + $this->fail('->children() does not trigger a notice if the node has no children'); + } + } + + public function testParents() + { + $crawler = $this->createTestCrawler()->filterXPath('//li[1]'); + $this->assertNotSame($crawler, $crawler->parents(), '->parents() returns a new instance of a crawler'); + $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->parents() returns a new instance of a crawler'); + + $nodes = $crawler->parents(); + $this->assertEquals(3, $nodes->count()); + + $nodes = $this->createTestCrawler()->filterXPath('//html')->parents(); + $this->assertEquals(0, $nodes->count()); + + try { + $this->createTestCrawler()->filterXPath('//ol')->parents(); + $this->fail('->parents() throws an \InvalidArgumentException if the node list is empty'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->parents() throws an \InvalidArgumentException if the node list is empty'); + } + } + + public function testBaseTag() + { + $crawler = new Crawler('
    '); + $this->assertEquals('http://base.com/link', $crawler->filterXPath('//a')->link()->getUri()); + + $crawler = new Crawler('', 'https://domain.com'); + $this->assertEquals('https://base.com/link', $crawler->filterXPath('//a')->link()->getUri(), ' tag can use a schema-less url'); + + $crawler = new Crawler('', 'https://domain.com'); + $this->assertEquals('https://domain.com/path/link', $crawler->filterXPath('//a')->link()->getUri(), ' tag can set a path'); + } + + public function createTestCrawler($uri = null) + { + $dom = new \DOMDocument(); + $dom->loadHTML(' + + + Foo + Fabien\'s Foo + Fabien"s Foo + \' Fabien"s Foo + + Bar +    Fabien\'s Bar   + Fabien"s Bar + \' Fabien"s Bar + + GetLink + +
    + + + + ', + array('bar' => array('InputFormField', 'bar')), + ), + array( + 'appends the submitted button value but not other submit buttons', + ' + ', + array('foobar' => array('InputFormField', 'foobar')), + ), + array( + 'returns textareas', + ' + ', + array('foo' => array('TextareaFormField', 'foo')), + ), + array( + 'returns inputs', + ' + ', + array('foo' => array('InputFormField', 'foo')), + ), + array( + 'returns checkboxes', + ' + ', + array('foo' => array('ChoiceFormField', 'foo')), + ), + array( + 'returns not-checked checkboxes', + ' + ', + array('foo' => array('ChoiceFormField', false)), + ), + array( + 'returns radio buttons', + ' + + ', + array('foo' => array('ChoiceFormField', 'bar')), + ), + array( + 'returns file inputs', + ' + ', + array('foo' => array('FileFormField', array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0))), + ), + ); + } + + public function testGetFormNode() + { + $dom = new \DOMDocument(); + $dom->loadHTML('
    '); + + $form = new Form($dom->getElementsByTagName('input')->item(0), 'http://example.com'); + + $this->assertSame($dom->getElementsByTagName('form')->item(0), $form->getFormNode(), '->getFormNode() returns the form node associated with this form'); + } + + public function testGetMethod() + { + $form = $this->createForm('
    '); + $this->assertEquals('GET', $form->getMethod(), '->getMethod() returns get if no method is defined'); + + $form = $this->createForm('
    '); + $this->assertEquals('POST', $form->getMethod(), '->getMethod() returns the method attribute value of the form'); + + $form = $this->createForm('
    ', 'put'); + $this->assertEquals('PUT', $form->getMethod(), '->getMethod() returns the method defined in the constructor if provided'); + + $form = $this->createForm('
    ', 'delete'); + $this->assertEquals('DELETE', $form->getMethod(), '->getMethod() returns the method defined in the constructor if provided'); + + $form = $this->createForm('
    ', 'patch'); + $this->assertEquals('PATCH', $form->getMethod(), '->getMethod() returns the method defined in the constructor if provided'); + } + + public function testGetSetValue() + { + $form = $this->createForm('
    '); + + $this->assertEquals('foo', $form['foo']->getValue(), '->offsetGet() returns the value of a form field'); + + $form['foo'] = 'bar'; + + $this->assertEquals('bar', $form['foo']->getValue(), '->offsetSet() changes the value of a form field'); + + try { + $form['foobar'] = 'bar'; + $this->fail('->offsetSet() throws an \InvalidArgumentException exception if the field does not exist'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->offsetSet() throws an \InvalidArgumentException exception if the field does not exist'); + } + + try { + $form['foobar']; + $this->fail('->offsetSet() throws an \InvalidArgumentException exception if the field does not exist'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->offsetSet() throws an \InvalidArgumentException exception if the field does not exist'); + } + } + + public function testSetValueOnMultiValuedFieldsWithMalformedName() + { + $form = $this->createForm('
    '); + + try { + $form['foo[bar'] = 'bar'; + $this->fail('->offsetSet() throws an \InvalidArgumentException exception if the name is malformed.'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->offsetSet() throws an \InvalidArgumentException exception if the name is malformed.'); + } + } + + public function testOffsetUnset() + { + $form = $this->createForm('
    '); + unset($form['foo']); + $this->assertFalse(isset($form['foo']), '->offsetUnset() removes a field'); + } + + public function testOffsetExists() + { + $form = $this->createForm('
    '); + + $this->assertTrue(isset($form['foo']), '->offsetExists() return true if the field exists'); + $this->assertFalse(isset($form['bar']), '->offsetExists() return false if the field does not exist'); + } + + public function testGetValues() + { + $form = $this->createForm('
    '); + $this->assertEquals(array('foo[bar]' => 'foo', 'bar' => 'bar'), $form->getValues(), '->getValues() returns all form field values'); + + $form = $this->createForm('
    '); + $this->assertEquals(array('bar' => 'bar'), $form->getValues(), '->getValues() does not include not-checked checkboxes'); + + $form = $this->createForm('
    '); + $this->assertEquals(array('bar' => 'bar'), $form->getValues(), '->getValues() does not include file input fields'); + + $form = $this->createForm('
    '); + $this->assertEquals(array('bar' => 'bar'), $form->getValues(), '->getValues() does not include disabled fields'); + } + + public function testSetValues() + { + $form = $this->createForm('
    '); + $form->setValues(array('foo' => false, 'bar' => 'foo')); + $this->assertEquals(array('bar' => 'foo'), $form->getValues(), '->setValues() sets the values of fields'); + } + + public function testMultiselectSetValues() + { + $form = $this->createForm('
    '); + $form->setValues(array('multi' => array("foo", "bar"))); + $this->assertEquals(array('multi' => array('foo', 'bar')), $form->getValues(), '->setValue() sets the values of select'); + } + + public function testGetPhpValues() + { + $form = $this->createForm('
    '); + $this->assertEquals(array('foo' => array('bar' => 'foo'), 'bar' => 'bar'), $form->getPhpValues(), '->getPhpValues() converts keys with [] to arrays'); + } + + public function testGetFiles() + { + $form = $this->createForm('
    '); + $this->assertEquals(array(), $form->getFiles(), '->getFiles() returns an empty array if method is get'); + + $form = $this->createForm('
    '); + $this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for POST'); + + $form = $this->createForm('
    ', 'put'); + $this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for PUT'); + + $form = $this->createForm('
    ', 'delete'); + $this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for DELETE'); + + $form = $this->createForm('
    ', 'patch'); + $this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for PATCH'); + + $form = $this->createForm('
    '); + $this->assertEquals(array(), $form->getFiles(), '->getFiles() does not include disabled file fields'); + } + + public function testGetPhpFiles() + { + $form = $this->createForm('
    '); + $this->assertEquals(array('foo' => array('bar' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0))), $form->getPhpFiles(), '->getPhpFiles() converts keys with [] to arrays'); + } + + /** + * @dataProvider provideGetUriValues + */ + public function testGetUri($message, $form, $values, $uri, $method = null) + { + $form = $this->createForm($form, $method); + $form->setValues($values); + + $this->assertEquals('http://example.com'.$uri, $form->getUri(), '->getUri() '.$message); + } + + public function testGetBaseUri() + { + $dom = new \DOMDocument(); + $dom->loadHTML('
    '); + + $nodes = $dom->getElementsByTagName('input'); + $form = new Form($nodes->item($nodes->length - 1), 'http://www.foo.com/'); + $this->assertEquals('http://www.foo.com/foo.php', $form->getUri()); + } + + public function testGetUriWithAnchor() + { + $form = $this->createForm('
    ', null, 'http://example.com/id/123'); + + $this->assertEquals('http://example.com/id/123#foo', $form->getUri()); + } + + public function testGetUriActionAbsolute() + { + $formHtml='
    '; + + $form = $this->createForm($formHtml); + $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); + + $form = $this->createForm($formHtml, null, 'https://login.foo.com'); + $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); + + $form = $this->createForm($formHtml, null, 'https://login.foo.com/bar/'); + $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); + + // The action URI haven't the same domain Host have an another domain as Host + $form = $this->createForm($formHtml, null, 'https://www.foo.com'); + $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); + + $form = $this->createForm($formHtml, null, 'https://www.foo.com/bar/'); + $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); + } + + public function testGetUriAbsolute() + { + $form = $this->createForm('
    ', null, 'http://localhost/foo/'); + $this->assertEquals('http://localhost/foo/foo', $form->getUri(), '->getUri() returns absolute URIs'); + + $form = $this->createForm('
    ', null, 'http://localhost/foo/'); + $this->assertEquals('http://localhost/foo', $form->getUri(), '->getUri() returns absolute URIs'); + } + + public function testGetUriWithOnlyQueryString() + { + $form = $this->createForm('
    ', null, 'http://localhost/foo/bar'); + $this->assertEquals('http://localhost/foo/bar?get=param', $form->getUri(), '->getUri() returns absolute URIs only if the host has been defined in the constructor'); + } + + public function testGetUriWithoutAction() + { + $form = $this->createForm('
    ', null, 'http://localhost/foo/bar'); + $this->assertEquals('http://localhost/foo/bar', $form->getUri(), '->getUri() returns path if no action defined'); + } + + public function provideGetUriValues() + { + return array( + array( + 'returns the URI of the form', + '
    ', + array(), + '/foo' + ), + array( + 'appends the form values if the method is get', + '
    ', + array(), + '/foo?foo=foo' + ), + array( + 'appends the form values and merges the submitted values', + '
    ', + array('foo' => 'bar'), + '/foo?foo=bar' + ), + array( + 'does not append values if the method is post', + '
    ', + array(), + '/foo' + ), + array( + 'does not append values if the method is patch', + '
    ', + array(), + '/foo', + 'PUT' + ), + array( + 'does not append values if the method is delete', + '
    ', + array(), + '/foo', + 'DELETE' + ), + array( + 'does not append values if the method is put', + '
    ', + array(), + '/foo', + 'PATCH' + ), + array( + 'appends the form values to an existing query string', + '
    ', + array(), + '/foo?bar=bar&foo=foo' + ), + array( + 'returns an empty URI if the action is empty', + '
    ', + array(), + '/', + ), + array( + 'appends the form values even if the action is empty', + '
    ', + array(), + '/?foo=foo', + ), + array( + 'chooses the path if the action attribute value is a sharp (#)', + '
    ', + array(), + '/#', + ), + ); + } + + public function testHas() + { + $form = $this->createForm('
    '); + + $this->assertFalse($form->has('foo'), '->has() returns false if a field is not in the form'); + $this->assertTrue($form->has('bar'), '->has() returns true if a field is in the form'); + } + + public function testRemove() + { + $form = $this->createForm('
    '); + $form->remove('bar'); + $this->assertFalse($form->has('bar'), '->remove() removes a field'); + } + + public function testGet() + { + $form = $this->createForm('
    '); + + $this->assertEquals('Symfony\\Component\\DomCrawler\\Field\\InputFormField', get_class($form->get('bar')), '->get() returns the field object associated with the given name'); + + try { + $form->get('foo'); + $this->fail('->get() throws an \InvalidArgumentException if the field does not exist'); + } catch (\InvalidArgumentException $e) { + $this->assertTrue(true, '->get() throws an \InvalidArgumentException if the field does not exist'); + } + } + + public function testAll() + { + $form = $this->createForm('
    '); + + $fields = $form->all(); + $this->assertEquals(1, count($fields), '->all() return an array of form field objects'); + $this->assertEquals('Symfony\\Component\\DomCrawler\\Field\\InputFormField', get_class($fields['bar']), '->all() return an array of form field objects'); + } + + public function testSubmitWithoutAFormButton() + { + $dom = new \DOMDocument(); + $dom->loadHTML(' + +
    + + + + '); + + $nodes = $dom->getElementsByTagName('form'); + $form = new Form($nodes->item(0), 'http://example.com'); + $this->assertSame($nodes->item(0), $form->getFormNode(), '->getFormNode() returns the form node associated with this form'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFormFieldRegistryAddThrowAnExceptionWhenTheNameIsMalformed() + { + $registry = new FormFieldRegistry(); + $registry->add($this->getFormFieldMock('[foo]')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFormFieldRegistryRemoveThrowAnExceptionWhenTheNameIsMalformed() + { + $registry = new FormFieldRegistry(); + $registry->remove('[foo]'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFormFieldRegistryGetThrowAnExceptionWhenTheNameIsMalformed() + { + $registry = new FormFieldRegistry(); + $registry->get('[foo]'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFormFieldRegistryGetThrowAnExceptionWhenTheFieldDoesNotExist() + { + $registry = new FormFieldRegistry(); + $registry->get('foo'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFormFieldRegistrySetThrowAnExceptionWhenTheNameIsMalformed() + { + $registry = new FormFieldRegistry(); + $registry->set('[foo]', null); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testFormFieldRegistrySetThrowAnExceptionWhenTheFieldDoesNotExist() + { + $registry = new FormFieldRegistry(); + $registry->set('foo', null); + } + + public function testFormFieldRegistryHasReturnsTrueWhenTheFQNExists() + { + $registry = new FormFieldRegistry(); + $registry->add($this->getFormFieldMock('foo[bar]')); + + $this->assertTrue($registry->has('foo')); + $this->assertTrue($registry->has('foo[bar]')); + $this->assertFalse($registry->has('bar')); + $this->assertFalse($registry->has('foo[foo]')); + } + + public function testFormRegistryFieldsCanBeRemoved() + { + $registry = new FormFieldRegistry(); + $registry->add($this->getFormFieldMock('foo')); + $registry->remove('foo'); + $this->assertFalse($registry->has('foo')); + } + + public function testFormRegistrySupportsMultivaluedFields() + { + $registry = new FormFieldRegistry(); + $registry->add($this->getFormFieldMock('foo[]')); + $registry->add($this->getFormFieldMock('foo[]')); + $registry->add($this->getFormFieldMock('bar[5]')); + $registry->add($this->getFormFieldMock('bar[]')); + $registry->add($this->getFormFieldMock('bar[baz]')); + + $this->assertEquals( + array('foo[0]', 'foo[1]', 'bar[5]', 'bar[6]', 'bar[baz]'), + array_keys($registry->all()) + ); + } + + public function testFormRegistrySetValues() + { + $registry = new FormFieldRegistry(); + $registry->add($f2 = $this->getFormFieldMock('foo[2]')); + $registry->add($f3 = $this->getFormFieldMock('foo[3]')); + $registry->add($fbb = $this->getFormFieldMock('foo[bar][baz]')); + + $f2 + ->expects($this->exactly(2)) + ->method('setValue') + ->with(2) + ; + + $f3 + ->expects($this->exactly(2)) + ->method('setValue') + ->with(3) + ; + + $fbb + ->expects($this->exactly(2)) + ->method('setValue') + ->with('fbb') + ; + + $registry->set('foo[2]', 2); + $registry->set('foo[3]', 3); + $registry->set('foo[bar][baz]', 'fbb'); + + $registry->set('foo', array( + 2 => 2, + 3 => 3, + 'bar' => array( + 'baz' => 'fbb' + ) + )); + } + + protected function getFormFieldMock($name, $value = null) + { + $field = $this + ->getMockBuilder('Symfony\\Component\\DomCrawler\\Field\\FormField') + ->setMethods(array('getName', 'getValue', 'setValue', 'initialize')) + ->disableOriginalConstructor() + ->getMock() + ; + + $field + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue($name)) + ; + + $field + ->expects($this->any()) + ->method('getValue') + ->will($this->returnValue($value)) + ; + + return $field; + } + + protected function createForm($form, $method = null, $currentUri = null) + { + $dom = new \DOMDocument(); + $dom->loadHTML(''.$form.''); + + $nodes = $dom->getElementsByTagName('input'); + $xPath = new \DOMXPath($dom); + $nodes = $xPath->query('//input | //button'); + + if (null === $currentUri) { + $currentUri = 'http://example.com/'; + } + + return new Form($nodes->item($nodes->length - 1), $currentUri, $method); + } +} diff --git a/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Tests/LinkTest.php b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Tests/LinkTest.php new file mode 100644 index 0000000..e051f14 --- /dev/null +++ b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/Tests/LinkTest.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DomCrawler\Tests; + +use Symfony\Component\DomCrawler\Link; + +class LinkTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \LogicException + */ + public function testConstructorWithANonATag() + { + $dom = new \DOMDocument(); + $dom->loadHTML('
    '); + + new Link($dom->getElementsByTagName('div')->item(0), 'http://www.example.com/'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testConstructorWithAnInvalidCurrentUri() + { + $dom = new \DOMDocument(); + $dom->loadHTML('foo'); + + new Link($dom->getElementsByTagName('a')->item(0), 'example.com'); + } + + public function testGetNode() + { + $dom = new \DOMDocument(); + $dom->loadHTML('foo'); + + $node = $dom->getElementsByTagName('a')->item(0); + $link = new Link($node, 'http://example.com/'); + + $this->assertEquals($node, $link->getNode(), '->getNode() returns the node associated with the link'); + } + + public function testGetMethod() + { + $dom = new \DOMDocument(); + $dom->loadHTML('foo'); + + $node = $dom->getElementsByTagName('a')->item(0); + $link = new Link($node, 'http://example.com/'); + + $this->assertEquals('GET', $link->getMethod(), '->getMethod() returns the method of the link'); + + $link = new Link($node, 'http://example.com/', 'post'); + $this->assertEquals('POST', $link->getMethod(), '->getMethod() returns the method of the link'); + } + + /** + * @dataProvider getGetUriTests + */ + public function testGetUri($url, $currentUri, $expected) + { + $dom = new \DOMDocument(); + $dom->loadHTML(sprintf('foo', $url)); + $link = new Link($dom->getElementsByTagName('a')->item(0), $currentUri); + + $this->assertEquals($expected, $link->getUri()); + } + + public function getGetUriTests() + { + return array( + array('/foo', 'http://localhost/bar/foo/', 'http://localhost/foo'), + array('/foo', 'http://localhost/bar/foo', 'http://localhost/foo'), + array(' + /foo', 'http://localhost/bar/foo/', 'http://localhost/foo'), + array('/foo + ', 'http://localhost/bar/foo', 'http://localhost/foo'), + + array('foo', 'http://localhost/bar/foo/', 'http://localhost/bar/foo/foo'), + array('foo', 'http://localhost/bar/foo', 'http://localhost/bar/foo'), + + array('', 'http://localhost/bar/', 'http://localhost/bar/'), + array('#', 'http://localhost/bar/', 'http://localhost/bar/#'), + array('#bar', 'http://localhost/bar/#foo', 'http://localhost/bar/#bar'), + array('?a=b', 'http://localhost/bar/', 'http://localhost/bar/?a=b'), + + array('http://login.foo.com/foo', 'http://localhost/bar/', 'http://login.foo.com/foo'), + array('https://login.foo.com/foo', 'https://localhost/bar/', 'https://login.foo.com/foo'), + array('mailto:foo@bar.com', 'http://localhost/foo', 'mailto:foo@bar.com'), + + // tests schema relative URL (issue #7169) + array('//login.foo.com/foo', 'http://localhost/bar/', 'http://login.foo.com/foo'), + array('//login.foo.com/foo', 'https://localhost/bar/', 'https://login.foo.com/foo'), + + array('?foo=2', 'http://localhost?foo=1', 'http://localhost?foo=2'), + array('?foo=2', 'http://localhost/?foo=1', 'http://localhost/?foo=2'), + array('?foo=2', 'http://localhost/bar?foo=1', 'http://localhost/bar?foo=2'), + array('?foo=2', 'http://localhost/bar/?foo=1', 'http://localhost/bar/?foo=2'), + array('?bar=2', 'http://localhost?foo=1', 'http://localhost?bar=2'), + + array('foo', 'http://login.foo.com/bar/baz?/query/string', 'http://login.foo.com/bar/foo'), + + array('.', 'http://localhost/foo/bar/baz', 'http://localhost/foo/bar/'), + array('./', 'http://localhost/foo/bar/baz', 'http://localhost/foo/bar/'), + array('./foo', 'http://localhost/foo/bar/baz', 'http://localhost/foo/bar/foo'), + array('..', 'http://localhost/foo/bar/baz', 'http://localhost/foo/'), + array('../', 'http://localhost/foo/bar/baz', 'http://localhost/foo/'), + array('../foo', 'http://localhost/foo/bar/baz', 'http://localhost/foo/foo'), + array('../..', 'http://localhost/foo/bar/baz', 'http://localhost/'), + array('../../', 'http://localhost/foo/bar/baz', 'http://localhost/'), + array('../../foo', 'http://localhost/foo/bar/baz', 'http://localhost/foo'), + array('../../foo', 'http://localhost/bar/foo/', 'http://localhost/foo'), + array('../bar/../../foo', 'http://localhost/bar/foo/', 'http://localhost/foo'), + array('../bar/./../../foo', 'http://localhost/bar/foo/', 'http://localhost/foo'), + array('../../', 'http://localhost/', 'http://localhost/'), + array('../../', 'http://localhost', 'http://localhost/'), + + array('/foo', 'file:///', 'file:///foo'), + array('/foo', 'file:///bar/baz', 'file:///foo'), + array('foo', 'file:///', 'file:///foo'), + array('foo', 'file:///bar/baz', 'file:///bar/foo'), + ); + } +} diff --git a/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/composer.json b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/composer.json new file mode 100644 index 0000000..4e56c92 --- /dev/null +++ b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/dom-crawler", + "type": "library", + "description": "Symfony DomCrawler Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/css-selector": "~2.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\DomCrawler\\": "" } + }, + "target-dir": "Symfony/Component/DomCrawler", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + } +} diff --git a/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/phpunit.xml.dist b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/phpunit.xml.dist new file mode 100644 index 0000000..0f8d2da --- /dev/null +++ b/vendor/symfony/dom-crawler/Symfony/Component/DomCrawler/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/.gitignore b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/CHANGELOG.md b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/CHANGELOG.md new file mode 100644 index 0000000..536c5ac --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/CHANGELOG.md @@ -0,0 +1,16 @@ +CHANGELOG +========= + +2.1.0 +----- + + * added TraceableEventDispatcherInterface + * added ContainerAwareEventDispatcher + * added a reference to the EventDispatcher on the Event + * added a reference to the Event name on the event + * added fluid interface to the dispatch() method which now returns the Event + object + * added GenericEvent event class + * added the possibility for subscribers to subscribe several times for the + same event + * added ImmutableEventDispatcher diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.php b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.php new file mode 100644 index 0000000..9448ed4 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/ContainerAwareEventDispatcher.php @@ -0,0 +1,202 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Lazily loads listeners and subscribers from the dependency injection + * container + * + * @author Fabien Potencier + * @author Bernhard Schussek + * @author Jordan Alliot + */ +class ContainerAwareEventDispatcher extends EventDispatcher +{ + /** + * The container from where services are loaded + * @var ContainerInterface + */ + private $container; + + /** + * The service IDs of the event listeners and subscribers + * @var array + */ + private $listenerIds = array(); + + /** + * The services registered as listeners + * @var array + */ + private $listeners = array(); + + /** + * Constructor. + * + * @param ContainerInterface $container A ContainerInterface instance + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * Adds a service as event listener + * + * @param string $eventName Event for which the listener is added + * @param array $callback The service ID of the listener service & the method + * name that has to be called + * @param integer $priority The higher this value, the earlier an event listener + * will be triggered in the chain. + * Defaults to 0. + * + * @throws \InvalidArgumentException + */ + public function addListenerService($eventName, $callback, $priority = 0) + { + if (!is_array($callback) || 2 !== count($callback)) { + throw new \InvalidArgumentException('Expected an array("service", "method") argument'); + } + + $this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority); + } + + public function removeListener($eventName, $listener) + { + $this->lazyLoad($eventName); + + if (isset($this->listeners[$eventName])) { + foreach ($this->listeners[$eventName] as $key => $l) { + foreach ($this->listenerIds[$eventName] as $i => $args) { + list($serviceId, $method, $priority) = $args; + if ($key === $serviceId.'.'.$method) { + if ($listener === array($l, $method)) { + unset($this->listeners[$eventName][$key]); + if (empty($this->listeners[$eventName])) { + unset($this->listeners[$eventName]); + } + unset($this->listenerIds[$eventName][$i]); + if (empty($this->listenerIds[$eventName])) { + unset($this->listenerIds[$eventName]); + } + } + } + } + } + } + + parent::removeListener($eventName, $listener); + } + + /** + * @see EventDispatcherInterface::hasListeners + */ + public function hasListeners($eventName = null) + { + if (null === $eventName) { + return (Boolean) count($this->listenerIds) || (Boolean) count($this->listeners); + } + + if (isset($this->listenerIds[$eventName])) { + return true; + } + + return parent::hasListeners($eventName); + } + + /** + * @see EventDispatcherInterface::getListeners + */ + public function getListeners($eventName = null) + { + if (null === $eventName) { + foreach (array_keys($this->listenerIds) as $serviceEventName) { + $this->lazyLoad($serviceEventName); + } + } else { + $this->lazyLoad($eventName); + } + + return parent::getListeners($eventName); + } + + /** + * Adds a service as event subscriber + * + * @param string $serviceId The service ID of the subscriber service + * @param string $class The service's class name (which must implement EventSubscriberInterface) + */ + public function addSubscriberService($serviceId, $class) + { + foreach ($class::getSubscribedEvents() as $eventName => $params) { + if (is_string($params)) { + $this->listenerIds[$eventName][] = array($serviceId, $params, 0); + } elseif (is_string($params[0])) { + $this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0); + } + } + } + } + + /** + * {@inheritDoc} + * + * Lazily loads listeners for this event from the dependency injection + * container. + * + * @throws \InvalidArgumentException if the service is not defined + */ + public function dispatch($eventName, Event $event = null) + { + $this->lazyLoad($eventName); + + return parent::dispatch($eventName, $event); + } + + public function getContainer() + { + return $this->container; + } + + /** + * Lazily loads listeners for this event from the dependency injection + * container. + * + * @param string $eventName The name of the event to dispatch. The name of + * the event is the name of the method that is + * invoked on listeners. + */ + protected function lazyLoad($eventName) + { + if (isset($this->listenerIds[$eventName])) { + foreach ($this->listenerIds[$eventName] as $args) { + list($serviceId, $method, $priority) = $args; + $listener = $this->container->get($serviceId); + + $key = $serviceId.'.'.$method; + if (!isset($this->listeners[$eventName][$key])) { + $this->addListener($eventName, array($listener, $method), $priority); + } elseif ($listener !== $this->listeners[$eventName][$key]) { + parent::removeListener($eventName, array($this->listeners[$eventName][$key], $method)); + $this->addListener($eventName, array($listener, $method), $priority); + } + + $this->listeners[$eventName][$key] = $listener; + } + } + } +} diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcherInterface.php b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcherInterface.php new file mode 100644 index 0000000..a67a979 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcherInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +/** + * @author Fabien Potencier + */ +interface TraceableEventDispatcherInterface +{ + /** + * Gets the called listeners. + * + * @return array An array of called listeners + */ + public function getCalledListeners(); + + /** + * Gets the not called listeners. + * + * @return array An array of not called listeners + */ + public function getNotCalledListeners(); +} diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Event.php b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Event.php new file mode 100644 index 0000000..42f09ea --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Event.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * Event is the base class for classes containing event data. + * + * This class contains no event data. It is used by events that do not pass + * state information to an event handler when an event is raised. + * + * You can call the method stopPropagation() to abort the execution of + * further listeners in your event listener. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * + * @api + */ +class Event +{ + /** + * @var Boolean Whether no further event listeners should be triggered + */ + private $propagationStopped = false; + + /** + * @var EventDispatcher Dispatcher that dispatched this event + */ + private $dispatcher; + + /** + * @var string This event's name + */ + private $name; + + /** + * Returns whether further event listeners should be triggered. + * + * @see Event::stopPropagation + * @return Boolean Whether propagation was already stopped for this event. + * + * @api + */ + public function isPropagationStopped() + { + return $this->propagationStopped; + } + + /** + * Stops the propagation of the event to further event listeners. + * + * If multiple event listeners are connected to the same event, no + * further event listener will be triggered once any trigger calls + * stopPropagation(). + * + * @api + */ + public function stopPropagation() + { + $this->propagationStopped = true; + } + + /** + * Stores the EventDispatcher that dispatches this Event + * + * @param EventDispatcherInterface $dispatcher + * + * @api + */ + public function setDispatcher(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * Returns the EventDispatcher that dispatches this Event + * + * @return EventDispatcherInterface + * + * @api + */ + public function getDispatcher() + { + return $this->dispatcher; + } + + /** + * Gets the event's name. + * + * @return string + * + * @api + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the event's name property. + * + * @param string $name The event name. + * + * @api + */ + public function setName($name) + { + $this->name = $name; + } +} diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventDispatcher.php b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventDispatcher.php new file mode 100644 index 0000000..eb1fb59 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventDispatcher.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * The EventDispatcherInterface is the central point of Symfony's event listener system. + * + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * @author Fabien Potencier + * @author Jordi Boggiano + * @author Jordan Alliot + * + * @api + */ +class EventDispatcher implements EventDispatcherInterface +{ + private $listeners = array(); + private $sorted = array(); + + /** + * @see EventDispatcherInterface::dispatch + * + * @api + */ + public function dispatch($eventName, Event $event = null) + { + if (null === $event) { + $event = new Event(); + } + + $event->setDispatcher($this); + $event->setName($eventName); + + if (!isset($this->listeners[$eventName])) { + return $event; + } + + $this->doDispatch($this->getListeners($eventName), $eventName, $event); + + return $event; + } + + /** + * @see EventDispatcherInterface::getListeners + */ + public function getListeners($eventName = null) + { + if (null !== $eventName) { + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + + return $this->sorted[$eventName]; + } + + foreach (array_keys($this->listeners) as $eventName) { + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + } + + return $this->sorted; + } + + /** + * @see EventDispatcherInterface::hasListeners + */ + public function hasListeners($eventName = null) + { + return (Boolean) count($this->getListeners($eventName)); + } + + /** + * @see EventDispatcherInterface::addListener + * + * @api + */ + public function addListener($eventName, $listener, $priority = 0) + { + $this->listeners[$eventName][$priority][] = $listener; + unset($this->sorted[$eventName]); + } + + /** + * @see EventDispatcherInterface::removeListener + */ + public function removeListener($eventName, $listener) + { + if (!isset($this->listeners[$eventName])) { + return; + } + + foreach ($this->listeners[$eventName] as $priority => $listeners) { + if (false !== ($key = array_search($listener, $listeners, true))) { + unset($this->listeners[$eventName][$priority][$key], $this->sorted[$eventName]); + } + } + } + + /** + * @see EventDispatcherInterface::addSubscriber + * + * @api + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (is_string($params)) { + $this->addListener($eventName, array($subscriber, $params)); + } elseif (is_string($params[0])) { + $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0); + } + } + } + } + + /** + * @see EventDispatcherInterface::removeSubscriber + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (is_array($params) && is_array($params[0])) { + foreach ($params as $listener) { + $this->removeListener($eventName, array($subscriber, $listener[0])); + } + } else { + $this->removeListener($eventName, array($subscriber, is_string($params) ? $params : $params[0])); + } + } + } + + /** + * Triggers the listeners of an event. + * + * This method can be overridden to add functionality that is executed + * for each listener. + * + * @param array[callback] $listeners The event listeners. + * @param string $eventName The name of the event to dispatch. + * @param Event $event The event object to pass to the event handlers/listeners. + */ + protected function doDispatch($listeners, $eventName, Event $event) + { + foreach ($listeners as $listener) { + call_user_func($listener, $event); + if ($event->isPropagationStopped()) { + break; + } + } + } + + /** + * Sorts the internal list of listeners for the given event by priority. + * + * @param string $eventName The name of the event. + */ + private function sortListeners($eventName) + { + $this->sorted[$eventName] = array(); + + if (isset($this->listeners[$eventName])) { + krsort($this->listeners[$eventName]); + $this->sorted[$eventName] = call_user_func_array('array_merge', $this->listeners[$eventName]); + } + } +} diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventDispatcherInterface.php new file mode 100644 index 0000000..7aead23 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventDispatcherInterface.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * The EventDispatcherInterface is the central point of Symfony's event listener system. + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @author Bernhard Schussek + * + * @api + */ +interface EventDispatcherInterface +{ + /** + * Dispatches an event to all registered listeners. + * + * @param string $eventName The name of the event to dispatch. The name of + * the event is the name of the method that is + * invoked on listeners. + * @param Event $event The event to pass to the event handlers/listeners. + * If not supplied, an empty Event instance is created. + * + * @return Event + * + * @api + */ + public function dispatch($eventName, Event $event = null); + + /** + * Adds an event listener that listens on the specified events. + * + * @param string $eventName The event to listen on + * @param callable $listener The listener + * @param integer $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + * + * @api + */ + public function addListener($eventName, $listener, $priority = 0); + + /** + * Adds an event subscriber. + * + * The subscriber is asked for all the events he is + * interested in and added as a listener for these events. + * + * @param EventSubscriberInterface $subscriber The subscriber. + * + * @api + */ + public function addSubscriber(EventSubscriberInterface $subscriber); + + /** + * Removes an event listener from the specified events. + * + * @param string|array $eventName The event(s) to remove a listener from + * @param callable $listener The listener to remove + */ + public function removeListener($eventName, $listener); + + /** + * Removes an event subscriber. + * + * @param EventSubscriberInterface $subscriber The subscriber + */ + public function removeSubscriber(EventSubscriberInterface $subscriber); + + /** + * Gets the listeners of a specific event or all listeners. + * + * @param string $eventName The name of the event + * + * @return array The event listeners for the specified event, or all event listeners by event name + */ + public function getListeners($eventName = null); + + /** + * Checks whether an event has any registered listeners. + * + * @param string $eventName The name of the event + * + * @return Boolean true if the specified event has any listeners, false otherwise + */ + public function hasListeners($eventName = null); +} diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventSubscriberInterface.php b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventSubscriberInterface.php new file mode 100644 index 0000000..080f892 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/EventSubscriberInterface.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * An EventSubscriber knows himself what events he is interested in. + * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes + * {@link getSubscribedEvents} and registers the subscriber as a listener for all + * returned events. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * + * @api + */ +interface EventSubscriberInterface +{ + /** + * Returns an array of event names this subscriber wants to listen to. + * + * The array keys are event names and the value can be: + * + * * The method name to call (priority defaults to 0) + * * An array composed of the method name to call and the priority + * * An array of arrays composed of the method names to call and respective + * priorities, or 0 if unset + * + * For instance: + * + * * array('eventName' => 'methodName') + * * array('eventName' => array('methodName', $priority)) + * * array('eventName' => array(array('methodName1', $priority), array('methodName2')) + * + * @return array The event names to listen to + * + * @api + */ + public static function getSubscribedEvents(); +} diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/GenericEvent.php b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/GenericEvent.php new file mode 100644 index 0000000..3a5efcf --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/GenericEvent.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * Event encapsulation class. + * + * Encapsulates events thus decoupling the observer from the subject they encapsulate. + * + * @author Drak + */ +class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate +{ + /** + * Observer pattern subject. + * + * @var mixed usually object or callable + */ + protected $subject; + + /** + * Array of arguments. + * + * @var array + */ + protected $arguments; + + /** + * Encapsulate an event with $subject and $args. + * + * @param mixed $subject The subject of the event, usually an object. + * @param array $arguments Arguments to store in the event. + */ + public function __construct($subject = null, array $arguments = array()) + { + $this->subject = $subject; + $this->arguments = $arguments; + } + + /** + * Getter for subject property. + * + * @return mixed $subject The observer subject. + */ + public function getSubject() + { + return $this->subject; + } + + /** + * Get argument by key. + * + * @param string $key Key. + * + * @throws \InvalidArgumentException If key is not found. + * + * @return mixed Contents of array key. + */ + public function getArgument($key) + { + if ($this->hasArgument($key)) { + return $this->arguments[$key]; + } + + throw new \InvalidArgumentException(sprintf('%s not found in %s', $key, $this->getName())); + } + + /** + * Add argument to event. + * + * @param string $key Argument name. + * @param mixed $value Value. + * + * @return GenericEvent + */ + public function setArgument($key, $value) + { + $this->arguments[$key] = $value; + + return $this; + } + + /** + * Getter for all arguments. + * + * @return array + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Set args property. + * + * @param array $args Arguments. + * + * @return GenericEvent + */ + public function setArguments(array $args = array()) + { + $this->arguments = $args; + + return $this; + } + + /** + * Has argument. + * + * @param string $key Key of arguments array. + * + * @return boolean + */ + public function hasArgument($key) + { + return array_key_exists($key, $this->arguments); + } + + /** + * ArrayAccess for argument getter. + * + * @param string $key Array key. + * + * @throws \InvalidArgumentException If key does not exist in $this->args. + * + * @return mixed + */ + public function offsetGet($key) + { + return $this->getArgument($key); + } + + /** + * ArrayAccess for argument setter. + * + * @param string $key Array key to set. + * @param mixed $value Value. + */ + public function offsetSet($key, $value) + { + $this->setArgument($key, $value); + } + + /** + * ArrayAccess for unset argument. + * + * @param string $key Array key. + */ + public function offsetUnset($key) + { + if ($this->hasArgument($key)) { + unset($this->arguments[$key]); + } + } + + /** + * ArrayAccess has argument. + * + * @param string $key Array key. + * + * @return boolean + */ + public function offsetExists($key) + { + return $this->hasArgument($key); + } + + /** + * IteratorAggregate for iterating over the object like an array + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->arguments); + } +} diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php new file mode 100644 index 0000000..b70b81a --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * A read-only proxy for an event dispatcher. + * + * @author Bernhard Schussek + */ +class ImmutableEventDispatcher implements EventDispatcherInterface +{ + /** + * The proxied dispatcher. + * @var EventDispatcherInterface + */ + private $dispatcher; + + /** + * Creates an unmodifiable proxy for an event dispatcher. + * + * @param EventDispatcherInterface $dispatcher The proxied event dispatcher. + */ + public function __construct(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + return $this->dispatcher->dispatch($eventName, $event); + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } +} diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/LICENSE b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/LICENSE new file mode 100644 index 0000000..88a57f8 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2013 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/README.md b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/README.md new file mode 100644 index 0000000..11f6b18 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/README.md @@ -0,0 +1,25 @@ +EventDispatcher Component +========================= + +EventDispatcher implements a lightweight version of the Observer design +pattern. + + use Symfony\Component\EventDispatcher\EventDispatcher; + use Symfony\Component\EventDispatcher\Event; + + $dispatcher = new EventDispatcher(); + + $dispatcher->addListener('event_name', function (Event $event) { + // ... + }); + + $dispatcher->dispatch('event_name'); + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/EventDispatcher/ + $ composer.phar install --dev + $ phpunit diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/ContainerAwareEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/ContainerAwareEventDispatcherTest.php new file mode 100644 index 0000000..71f3ad0 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/ContainerAwareEventDispatcherTest.php @@ -0,0 +1,257 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests; + +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\Scope; +use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class ContainerAwareEventDispatcherTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\DependencyInjection\Container')) { + $this->markTestSkipped('The "DependencyInjection" component is not available'); + } + } + + public function testAddAListenerService() + { + $event = new Event(); + + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $dispatcher->dispatch('onEvent', $event); + } + + public function testAddASubscriberService() + { + $event = new Event(); + + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\SubscriberService'); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container = new Container(); + $container->set('service.subscriber', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addSubscriberService('service.subscriber', 'Symfony\Component\EventDispatcher\Tests\SubscriberService'); + + $dispatcher->dispatch('onEvent', $event); + } + + public function testPreventDuplicateListenerService() + { + $event = new Event(); + + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 5); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent'), 10); + + $dispatcher->dispatch('onEvent', $event); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testTriggerAListenerServiceOutOfScope() + { + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $scope = new Scope('scope'); + $container = new Container(); + $container->addScope($scope); + $container->enterScope('scope'); + + $container->set('service.listener', $service, 'scope'); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $container->leaveScope('scope'); + $dispatcher->dispatch('onEvent'); + } + + public function testReEnteringAScope() + { + $event = new Event(); + + $service1 = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $service1 + ->expects($this->exactly(2)) + ->method('onEvent') + ->with($event) + ; + + $scope = new Scope('scope'); + $container = new Container(); + $container->addScope($scope); + $container->enterScope('scope'); + + $container->set('service.listener', $service1, 'scope'); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + $dispatcher->dispatch('onEvent', $event); + + $service2 = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $service2 + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $container->enterScope('scope'); + $container->set('service.listener', $service2, 'scope'); + + $dispatcher->dispatch('onEvent', $event); + + $container->leaveScope('scope'); + + $dispatcher->dispatch('onEvent'); + } + + public function testHasListenersOnLazyLoad() + { + $event = new Event(); + + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $event->setDispatcher($dispatcher); + $event->setName('onEvent'); + + $service + ->expects($this->once()) + ->method('onEvent') + ->with($event) + ; + + $this->assertTrue($dispatcher->hasListeners()); + + if ($dispatcher->hasListeners('onEvent')) { + $dispatcher->dispatch('onEvent'); + } + } + + public function testGetListenersOnLazyLoad() + { + $event = new Event(); + + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $listeners = $dispatcher->getListeners(); + + $this->assertTrue(isset($listeners['onEvent'])); + + $this->assertCount(1, $dispatcher->getListeners('onEvent')); + } + + public function testRemoveAfterDispatch() + { + $event = new Event(); + + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $dispatcher->dispatch('onEvent', new Event()); + $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent')); + $this->assertFalse($dispatcher->hasListeners('onEvent')); + } + + public function testRemoveBeforeDispatch() + { + $event = new Event(); + + $service = $this->getMock('Symfony\Component\EventDispatcher\Tests\Service'); + + $container = new Container(); + $container->set('service.listener', $service); + + $dispatcher = new ContainerAwareEventDispatcher($container); + $dispatcher->addListenerService('onEvent', array('service.listener', 'onEvent')); + + $dispatcher->removeListener('onEvent', array($container->get('service.listener'), 'onEvent')); + $this->assertFalse($dispatcher->hasListeners('onEvent')); + } +} + +class Service +{ + public function onEvent(Event $e) + { + } +} + +class SubscriberService implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + 'onEvent' => 'onEvent', + 'onEvent' => array('onEvent', 10), + 'onEvent' => array('onEvent'), + ); + } + + public function onEvent(Event $e) + { + } +} diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php new file mode 100644 index 0000000..ad7e448 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/EventDispatcherTest.php @@ -0,0 +1,320 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class EventDispatcherTest extends \PHPUnit_Framework_TestCase +{ + /* Some pseudo events */ + const preFoo = 'pre.foo'; + const postFoo = 'post.foo'; + const preBar = 'pre.bar'; + const postBar = 'post.bar'; + + private $dispatcher; + + private $listener; + + protected function setUp() + { + $this->dispatcher = new EventDispatcher(); + $this->listener = new TestEventListener(); + } + + protected function tearDown() + { + $this->dispatcher = null; + $this->listener = null; + } + + public function testInitialState() + { + $this->assertEquals(array(), $this->dispatcher->getListeners()); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + $this->assertFalse($this->dispatcher->hasListeners(self::postFoo)); + } + + public function testAddListener() + { + $this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo')); + $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo')); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertTrue($this->dispatcher->hasListeners(self::postFoo)); + $this->assertCount(1, $this->dispatcher->getListeners(self::preFoo)); + $this->assertCount(1, $this->dispatcher->getListeners(self::postFoo)); + $this->assertCount(2, $this->dispatcher->getListeners()); + } + + public function testGetListenersSortsByPriority() + { + $listener1 = new TestEventListener(); + $listener2 = new TestEventListener(); + $listener3 = new TestEventListener(); + $listener1->name = '1'; + $listener2->name = '2'; + $listener3->name = '3'; + + $this->dispatcher->addListener('pre.foo', array($listener1, 'preFoo'), -10); + $this->dispatcher->addListener('pre.foo', array($listener2, 'preFoo'), 10); + $this->dispatcher->addListener('pre.foo', array($listener3, 'preFoo')); + + $expected = array( + array($listener2, 'preFoo'), + array($listener3, 'preFoo'), + array($listener1, 'preFoo'), + ); + + $this->assertSame($expected, $this->dispatcher->getListeners('pre.foo')); + } + + public function testGetAllListenersSortsByPriority() + { + $listener1 = new TestEventListener(); + $listener2 = new TestEventListener(); + $listener3 = new TestEventListener(); + $listener4 = new TestEventListener(); + $listener5 = new TestEventListener(); + $listener6 = new TestEventListener(); + + $this->dispatcher->addListener('pre.foo', $listener1, -10); + $this->dispatcher->addListener('pre.foo', $listener2); + $this->dispatcher->addListener('pre.foo', $listener3, 10); + $this->dispatcher->addListener('post.foo', $listener4, -10); + $this->dispatcher->addListener('post.foo', $listener5); + $this->dispatcher->addListener('post.foo', $listener6, 10); + + $expected = array( + 'pre.foo' => array($listener3, $listener2, $listener1), + 'post.foo' => array($listener6, $listener5, $listener4), + ); + + $this->assertSame($expected, $this->dispatcher->getListeners()); + } + + public function testDispatch() + { + $this->dispatcher->addListener('pre.foo', array($this->listener, 'preFoo')); + $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo')); + $this->dispatcher->dispatch(self::preFoo); + $this->assertTrue($this->listener->preFooInvoked); + $this->assertFalse($this->listener->postFooInvoked); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch('noevent')); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\Event', $this->dispatcher->dispatch(self::preFoo)); + $event = new Event(); + $return = $this->dispatcher->dispatch(self::preFoo, $event); + $this->assertEquals('pre.foo', $event->getName()); + $this->assertSame($event, $return); + } + + public function testDispatchForClosure() + { + $invoked = 0; + $listener = function () use (&$invoked) { + $invoked++; + }; + $this->dispatcher->addListener('pre.foo', $listener); + $this->dispatcher->addListener('post.foo', $listener); + $this->dispatcher->dispatch(self::preFoo); + $this->assertEquals(1, $invoked); + } + + public function testStopEventPropagation() + { + $otherListener = new TestEventListener(); + + // postFoo() stops the propagation, so only one listener should + // be executed + // Manually set priority to enforce $this->listener to be called first + $this->dispatcher->addListener('post.foo', array($this->listener, 'postFoo'), 10); + $this->dispatcher->addListener('post.foo', array($otherListener, 'preFoo')); + $this->dispatcher->dispatch(self::postFoo); + $this->assertTrue($this->listener->postFooInvoked); + $this->assertFalse($otherListener->postFooInvoked); + } + + public function testDispatchByPriority() + { + $invoked = array(); + $listener1 = function () use (&$invoked) { + $invoked[] = '1'; + }; + $listener2 = function () use (&$invoked) { + $invoked[] = '2'; + }; + $listener3 = function () use (&$invoked) { + $invoked[] = '3'; + }; + $this->dispatcher->addListener('pre.foo', $listener1, -10); + $this->dispatcher->addListener('pre.foo', $listener2); + $this->dispatcher->addListener('pre.foo', $listener3, 10); + $this->dispatcher->dispatch(self::preFoo); + $this->assertEquals(array('3', '2', '1'), $invoked); + } + + public function testRemoveListener() + { + $this->dispatcher->addListener('pre.bar', $this->listener); + $this->assertTrue($this->dispatcher->hasListeners(self::preBar)); + $this->dispatcher->removeListener('pre.bar', $this->listener); + $this->assertFalse($this->dispatcher->hasListeners(self::preBar)); + $this->dispatcher->removeListener('notExists', $this->listener); + } + + public function testAddSubscriber() + { + $eventSubscriber = new TestEventSubscriber(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertTrue($this->dispatcher->hasListeners(self::postFoo)); + } + + public function testAddSubscriberWithPriorities() + { + $eventSubscriber = new TestEventSubscriber(); + $this->dispatcher->addSubscriber($eventSubscriber); + + $eventSubscriber = new TestEventSubscriberWithPriorities(); + $this->dispatcher->addSubscriber($eventSubscriber); + + $listeners = $this->dispatcher->getListeners('pre.foo'); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertCount(2, $listeners); + $this->assertInstanceOf('Symfony\Component\EventDispatcher\Tests\TestEventSubscriberWithPriorities', $listeners[0][0]); + } + + public function testAddSubscriberWithMultipleListeners() + { + $eventSubscriber = new TestEventSubscriberWithMultipleListeners(); + $this->dispatcher->addSubscriber($eventSubscriber); + + $listeners = $this->dispatcher->getListeners('pre.foo'); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertCount(2, $listeners); + $this->assertEquals('preFoo2', $listeners[0][1]); + } + + public function testRemoveSubscriber() + { + $eventSubscriber = new TestEventSubscriber(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertTrue($this->dispatcher->hasListeners(self::postFoo)); + $this->dispatcher->removeSubscriber($eventSubscriber); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + $this->assertFalse($this->dispatcher->hasListeners(self::postFoo)); + } + + public function testRemoveSubscriberWithPriorities() + { + $eventSubscriber = new TestEventSubscriberWithPriorities(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->dispatcher->removeSubscriber($eventSubscriber); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + } + + public function testRemoveSubscriberWithMultipleListeners() + { + $eventSubscriber = new TestEventSubscriberWithMultipleListeners(); + $this->dispatcher->addSubscriber($eventSubscriber); + $this->assertTrue($this->dispatcher->hasListeners(self::preFoo)); + $this->assertCount(2, $this->dispatcher->getListeners(self::preFoo)); + $this->dispatcher->removeSubscriber($eventSubscriber); + $this->assertFalse($this->dispatcher->hasListeners(self::preFoo)); + } + + public function testEventReceivesTheDispatcherInstance() + { + $test = $this; + $this->dispatcher->addListener('test', function ($event) use (&$dispatcher) { + $dispatcher = $event->getDispatcher(); + }); + $this->dispatcher->dispatch('test'); + $this->assertSame($this->dispatcher, $dispatcher); + } + + /** + * @see https://bugs.php.net/bug.php?id=62976 + * + * This bug affects: + * - The PHP 5.3 branch for versions < 5.3.18 + * - The PHP 5.4 branch for versions < 5.4.8 + * - The PHP 5.5 branch is not affected + */ + public function testWorkaroundForPhpBug62976() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener('bug.62976', new CallableClass()); + $dispatcher->removeListener('bug.62976', function() {}); + $this->assertTrue($dispatcher->hasListeners('bug.62976')); + } +} + +class CallableClass +{ + public function __invoke() + { + } +} + +class TestEventListener +{ + public $preFooInvoked = false; + public $postFooInvoked = false; + + /* Listener methods */ + + public function preFoo(Event $e) + { + $this->preFooInvoked = true; + } + + public function postFoo(Event $e) + { + $this->postFooInvoked = true; + + $e->stopPropagation(); + } +} + +class TestEventSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array('pre.foo' => 'preFoo', 'post.foo' => 'postFoo'); + } +} + +class TestEventSubscriberWithPriorities implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array( + 'pre.foo' => array('preFoo', 10), + 'post.foo' => array('postFoo'), + ); + } +} + +class TestEventSubscriberWithMultipleListeners implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array('pre.foo' => array( + array('preFoo1'), + array('preFoo2', 10) + )); + } +} diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/EventTest.php b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/EventTest.php new file mode 100644 index 0000000..52aa9ad --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/EventTest.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcher; + +/** + * Test class for Event. + */ +class EventTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Symfony\Component\EventDispatcher\Event + */ + protected $event; + + /** + * @var \Symfony\Component\EventDispatcher\EventDispatcher + */ + protected $dispatcher; + + /** + * Sets up the fixture, for example, opens a network connection. + * This method is called before a test is executed. + */ + protected function setUp() + { + $this->event = new Event; + $this->dispatcher = new EventDispatcher(); + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() + { + $this->event = null; + $this->eventDispatcher = null; + } + + public function testIsPropagationStopped() + { + $this->assertFalse($this->event->isPropagationStopped()); + } + + public function testStopPropagationAndIsPropagationStopped() + { + $this->event->stopPropagation(); + $this->assertTrue($this->event->isPropagationStopped()); + } + + public function testSetDispatcher() + { + $this->event->setDispatcher($this->dispatcher); + $this->assertSame($this->dispatcher, $this->event->getDispatcher()); + } + + public function testGetDispatcher() + { + $this->assertNull($this->event->getDispatcher()); + } + + public function testGetName() + { + $this->assertNull($this->event->getName()); + } + + public function testSetName() + { + $this->event->setName('foo'); + $this->assertEquals('foo', $this->event->getName()); + } +} diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/GenericEventTest.php b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/GenericEventTest.php new file mode 100644 index 0000000..8dd6f5b --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/GenericEventTest.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\GenericEvent; + +/** + * Test class for Event. + */ +class GenericEventTest extends \PHPUnit_Framework_TestCase +{ + + /** + * @var GenericEvent + */ + private $event; + + private $subject; + + /** + * Prepares the environment before running a test. + */ + protected function setUp() + { + parent::setUp(); + + $this->subject = new \StdClass(); + $this->event = new GenericEvent($this->subject, array('name' => 'Event'), 'foo'); + } + + /** + * Cleans up the environment after running a test. + */ + protected function tearDown() + { + $this->subject = null; + $this->event = null; + + parent::tearDown(); + } + + public function testConstruct() + { + $this->assertEquals($this->event, new GenericEvent($this->subject, array('name' => 'Event'))); + } + + /** + * Tests Event->getArgs() + */ + public function testGetArguments() + { + // test getting all + $this->assertSame(array('name' => 'Event'), $this->event->getArguments()); + } + + public function testSetArguments() + { + $result = $this->event->setArguments(array('foo' => 'bar')); + $this->assertAttributeSame(array('foo' => 'bar'), 'arguments', $this->event); + $this->assertSame($this->event, $result); + } + + public function testSetArgument() + { + $result = $this->event->setArgument('foo2', 'bar2'); + $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event); + $this->assertEquals($this->event, $result); + } + + public function testGetArgument() + { + // test getting key + $this->assertEquals('Event', $this->event->getArgument('name')); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetArgException() + { + $this->event->getArgument('nameNotExist'); + } + + public function testOffsetGet() + { + // test getting key + $this->assertEquals('Event', $this->event['name']); + + // test getting invalid arg + $this->setExpectedException('InvalidArgumentException'); + $this->assertFalse($this->event['nameNotExist']); + } + + public function testOffsetSet() + { + $this->event['foo2'] = 'bar2'; + $this->assertAttributeSame(array('name' => 'Event', 'foo2' => 'bar2'), 'arguments', $this->event); + } + + public function testOffsetUnset() + { + unset($this->event['name']); + $this->assertAttributeSame(array(), 'arguments', $this->event); + } + + public function testOffsetIsset() + { + $this->assertTrue(isset($this->event['name'])); + $this->assertFalse(isset($this->event['nameNotExist'])); + } + + public function testHasArgument() + { + $this->assertTrue($this->event->hasArgument('name')); + $this->assertFalse($this->event->hasArgument('nameNotExist')); + } + + public function testGetSubject() + { + $this->assertSame($this->subject, $this->event->getSubject()); + } + + public function testHasIterator() + { + $data = array(); + foreach ($this->event as $key => $value) { + $data[$key] = $value; + } + $this->assertEquals(array('name' => 'Event'), $data); + } +} diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/ImmutableEventDispatcherTest.php b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/ImmutableEventDispatcherTest.php new file mode 100644 index 0000000..6402f89 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/Tests/ImmutableEventDispatcherTest.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Tests; + +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\ImmutableEventDispatcher; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * @author Bernhard Schussek + */ +class ImmutableEventDispatcherTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $innerDispatcher; + + /** + * @var ImmutableEventDispatcher + */ + private $dispatcher; + + protected function setUp() + { + $this->innerDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $this->dispatcher = new ImmutableEventDispatcher($this->innerDispatcher); + } + + public function testDispatchDelegates() + { + $event = new Event(); + + $this->innerDispatcher->expects($this->once()) + ->method('dispatch') + ->with('event', $event) + ->will($this->returnValue('result')); + + $this->assertSame('result', $this->dispatcher->dispatch('event', $event)); + } + + public function testGetListenersDelegates() + { + $this->innerDispatcher->expects($this->once()) + ->method('getListeners') + ->with('event') + ->will($this->returnValue('result')); + + $this->assertSame('result', $this->dispatcher->getListeners('event')); + } + + public function testHasListenersDelegates() + { + $this->innerDispatcher->expects($this->once()) + ->method('hasListeners') + ->with('event') + ->will($this->returnValue('result')); + + $this->assertSame('result', $this->dispatcher->hasListeners('event')); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testAddListenerDisallowed() + { + $this->dispatcher->addListener('event', function () { return 'foo'; }); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testAddSubscriberDisallowed() + { + $subscriber = $this->getMock('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + + $this->dispatcher->addSubscriber($subscriber); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRemoveListenerDisallowed() + { + $this->dispatcher->removeListener('event', function () { return 'foo'; }); + } + + /** + * @expectedException \BadMethodCallException + */ + public function testRemoveSubscriberDisallowed() + { + $subscriber = $this->getMock('Symfony\Component\EventDispatcher\EventSubscriberInterface'); + + $this->dispatcher->removeSubscriber($subscriber); + } +} diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/composer.json b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/composer.json new file mode 100644 index 0000000..1db2ecf --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/event-dispatcher", + "type": "library", + "description": "Symfony EventDispatcher Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/dependency-injection": "~2.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\EventDispatcher\\": "" } + }, + "target-dir": "Symfony/Component/EventDispatcher", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + } +} diff --git a/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/phpunit.xml.dist b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/phpunit.xml.dist new file mode 100644 index 0000000..0c3de4f --- /dev/null +++ b/vendor/symfony/event-dispatcher/Symfony/Component/EventDispatcher/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/filesystem/Symfony/Component/Filesystem/.gitignore b/vendor/symfony/filesystem/Symfony/Component/Filesystem/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/filesystem/Symfony/Component/Filesystem/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/filesystem/Symfony/Component/Filesystem/CHANGELOG.md b/vendor/symfony/filesystem/Symfony/Component/Filesystem/CHANGELOG.md new file mode 100644 index 0000000..e6aee66 --- /dev/null +++ b/vendor/symfony/filesystem/Symfony/Component/Filesystem/CHANGELOG.md @@ -0,0 +1,18 @@ +CHANGELOG +========= + +2.3.0 +----- + + * added the dumpFile() method to atomically write files + +2.2.0 +----- + + * added a delete option for the mirror() method + +2.1.0 +----- + + * 24eb396 : BC Break : mkdir() function now throws exception in case of failure instead of returning Boolean value + * created the component diff --git a/vendor/symfony/filesystem/Symfony/Component/Filesystem/Exception/ExceptionInterface.php b/vendor/symfony/filesystem/Symfony/Component/Filesystem/Exception/ExceptionInterface.php new file mode 100644 index 0000000..bc9748d --- /dev/null +++ b/vendor/symfony/filesystem/Symfony/Component/Filesystem/Exception/ExceptionInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Romain Neutron + * + * @api + */ +interface ExceptionInterface +{ + +} diff --git a/vendor/symfony/filesystem/Symfony/Component/Filesystem/Exception/IOException.php b/vendor/symfony/filesystem/Symfony/Component/Filesystem/Exception/IOException.php new file mode 100644 index 0000000..5b27e66 --- /dev/null +++ b/vendor/symfony/filesystem/Symfony/Component/Filesystem/Exception/IOException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * Exception class thrown when a filesystem operation failure happens + * + * @author Romain Neutron + * + * @api + */ +class IOException extends \RuntimeException implements ExceptionInterface +{ + +} diff --git a/vendor/symfony/filesystem/Symfony/Component/Filesystem/Filesystem.php b/vendor/symfony/filesystem/Symfony/Component/Filesystem/Filesystem.php new file mode 100644 index 0000000..1d51090 --- /dev/null +++ b/vendor/symfony/filesystem/Symfony/Component/Filesystem/Filesystem.php @@ -0,0 +1,471 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem; + +use Symfony\Component\Filesystem\Exception\IOException; + +/** + * Provides basic utility to manipulate the file system. + * + * @author Fabien Potencier + */ +class Filesystem +{ + /** + * Copies a file. + * + * This method only copies the file if the origin file is newer than the target file. + * + * By default, if the target already exists, it is not overridden. + * + * @param string $originFile The original filename + * @param string $targetFile The target filename + * @param boolean $override Whether to override an existing file or not + * + * @throws IOException When copy fails + */ + public function copy($originFile, $targetFile, $override = false) + { + if (stream_is_local($originFile) && !is_file($originFile)) { + throw new IOException(sprintf('Failed to copy %s because file not exists', $originFile)); + } + + $this->mkdir(dirname($targetFile)); + + if (!$override && is_file($targetFile)) { + $doCopy = filemtime($originFile) > filemtime($targetFile); + } else { + $doCopy = true; + } + + if ($doCopy) { + // https://bugs.php.net/bug.php?id=64634 + $source = fopen($originFile, 'r'); + $target = fopen($targetFile, 'w+'); + stream_copy_to_stream($source, $target); + fclose($source); + fclose($target); + unset($source, $target); + + if (!is_file($targetFile)) { + throw new IOException(sprintf('Failed to copy %s to %s', $originFile, $targetFile)); + } + } + } + + /** + * Creates a directory recursively. + * + * @param string|array|\Traversable $dirs The directory path + * @param integer $mode The directory mode + * + * @throws IOException On any directory creation failure + */ + public function mkdir($dirs, $mode = 0777) + { + foreach ($this->toIterator($dirs) as $dir) { + if (is_dir($dir)) { + continue; + } + + if (true !== @mkdir($dir, $mode, true)) { + throw new IOException(sprintf('Failed to create %s', $dir)); + } + } + } + + /** + * Checks the existence of files or directories. + * + * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to check + * + * @return Boolean true if the file exists, false otherwise + */ + public function exists($files) + { + foreach ($this->toIterator($files) as $file) { + if (!file_exists($file)) { + return false; + } + } + + return true; + } + + /** + * Sets access and modification time of file. + * + * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to create + * @param integer $time The touch time as a unix timestamp + * @param integer $atime The access time as a unix timestamp + * + * @throws IOException When touch fails + */ + public function touch($files, $time = null, $atime = null) + { + foreach ($this->toIterator($files) as $file) { + $touch = $time ? @touch($file, $time, $atime) : @touch($file); + if (true !== $touch) { + throw new IOException(sprintf('Failed to touch %s', $file)); + } + } + } + + /** + * Removes files or directories. + * + * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to remove + * + * @throws IOException When removal fails + */ + public function remove($files) + { + $files = iterator_to_array($this->toIterator($files)); + $files = array_reverse($files); + foreach ($files as $file) { + if (!file_exists($file) && !is_link($file)) { + continue; + } + + if (is_dir($file) && !is_link($file)) { + $this->remove(new \FilesystemIterator($file)); + + if (true !== @rmdir($file)) { + throw new IOException(sprintf('Failed to remove directory %s', $file)); + } + } else { + // https://bugs.php.net/bug.php?id=52176 + if (defined('PHP_WINDOWS_VERSION_MAJOR') && is_dir($file)) { + if (true !== @rmdir($file)) { + throw new IOException(sprintf('Failed to remove file %s', $file)); + } + } else { + if (true !== @unlink($file)) { + throw new IOException(sprintf('Failed to remove file %s', $file)); + } + } + } + } + } + + /** + * Change mode for an array of files or directories. + * + * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to change mode + * @param integer $mode The new mode (octal) + * @param integer $umask The mode mask (octal) + * @param Boolean $recursive Whether change the mod recursively or not + * + * @throws IOException When the change fail + */ + public function chmod($files, $mode, $umask = 0000, $recursive = false) + { + foreach ($this->toIterator($files) as $file) { + if ($recursive && is_dir($file) && !is_link($file)) { + $this->chmod(new \FilesystemIterator($file), $mode, $umask, true); + } + if (true !== @chmod($file, $mode & ~$umask)) { + throw new IOException(sprintf('Failed to chmod file %s', $file)); + } + } + } + + /** + * Change the owner of an array of files or directories + * + * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to change owner + * @param string $user The new owner user name + * @param Boolean $recursive Whether change the owner recursively or not + * + * @throws IOException When the change fail + */ + public function chown($files, $user, $recursive = false) + { + foreach ($this->toIterator($files) as $file) { + if ($recursive && is_dir($file) && !is_link($file)) { + $this->chown(new \FilesystemIterator($file), $user, true); + } + if (is_link($file) && function_exists('lchown')) { + if (true !== @lchown($file, $user)) { + throw new IOException(sprintf('Failed to chown file %s', $file)); + } + } else { + if (true !== @chown($file, $user)) { + throw new IOException(sprintf('Failed to chown file %s', $file)); + } + } + } + } + + /** + * Change the group of an array of files or directories + * + * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to change group + * @param string $group The group name + * @param Boolean $recursive Whether change the group recursively or not + * + * @throws IOException When the change fail + */ + public function chgrp($files, $group, $recursive = false) + { + foreach ($this->toIterator($files) as $file) { + if ($recursive && is_dir($file) && !is_link($file)) { + $this->chgrp(new \FilesystemIterator($file), $group, true); + } + if (is_link($file) && function_exists('lchgrp')) { + if (true !== @lchgrp($file, $group)) { + throw new IOException(sprintf('Failed to chgrp file %s', $file)); + } + } else { + if (true !== @chgrp($file, $group)) { + throw new IOException(sprintf('Failed to chgrp file %s', $file)); + } + } + } + } + + /** + * Renames a file or a directory. + * + * @param string $origin The origin filename or directory + * @param string $target The new filename or directory + * @param Boolean $overwrite Whether to overwrite the target if it already exists + * + * @throws IOException When target file or directory already exists + * @throws IOException When origin cannot be renamed + */ + public function rename($origin, $target, $overwrite = false) + { + // we check that target does not exist + if (!$overwrite && is_readable($target)) { + throw new IOException(sprintf('Cannot rename because the target "%s" already exist.', $target)); + } + + if (true !== @rename($origin, $target)) { + throw new IOException(sprintf('Cannot rename "%s" to "%s".', $origin, $target)); + } + } + + /** + * Creates a symbolic link or copy a directory. + * + * @param string $originDir The origin directory path + * @param string $targetDir The symbolic link name + * @param Boolean $copyOnWindows Whether to copy files if on Windows + * + * @throws IOException When symlink fails + */ + public function symlink($originDir, $targetDir, $copyOnWindows = false) + { + if (!function_exists('symlink') && $copyOnWindows) { + $this->mirror($originDir, $targetDir); + + return; + } + + $this->mkdir(dirname($targetDir)); + + $ok = false; + if (is_link($targetDir)) { + if (readlink($targetDir) != $originDir) { + $this->remove($targetDir); + } else { + $ok = true; + } + } + + if (!$ok) { + if (true !== @symlink($originDir, $targetDir)) { + $report = error_get_last(); + if (is_array($report)) { + if (defined('PHP_WINDOWS_VERSION_MAJOR') && false !== strpos($report['message'], 'error code(1314)')) { + throw new IOException('Unable to create symlink due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?'); + } + } + throw new IOException(sprintf('Failed to create symbolic link from %s to %s', $originDir, $targetDir)); + } + } + } + + /** + * Given an existing path, convert it to a path relative to a given starting path + * + * @param string $endPath Absolute path of target + * @param string $startPath Absolute path where traversal begins + * + * @return string Path of target relative to starting path + */ + public function makePathRelative($endPath, $startPath) + { + // Normalize separators on windows + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + $endPath = strtr($endPath, '\\', '/'); + $startPath = strtr($startPath, '\\', '/'); + } + + // Split the paths into arrays + $startPathArr = explode('/', trim($startPath, '/')); + $endPathArr = explode('/', trim($endPath, '/')); + + // Find for which directory the common path stops + $index = 0; + while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) { + $index++; + } + + // Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels) + $depth = count($startPathArr) - $index; + + // Repeated "../" for each level need to reach the common path + $traverser = str_repeat('../', $depth); + + $endPathRemainder = implode('/', array_slice($endPathArr, $index)); + + // Construct $endPath from traversing to the common path, then to the remaining $endPath + $relativePath = $traverser.(strlen($endPathRemainder) > 0 ? $endPathRemainder.'/' : ''); + + return (strlen($relativePath) === 0) ? './' : $relativePath; + } + + /** + * Mirrors a directory to another. + * + * @param string $originDir The origin directory + * @param string $targetDir The target directory + * @param \Traversable $iterator A Traversable instance + * @param array $options An array of boolean options + * Valid options are: + * - $options['override'] Whether to override an existing file on copy or not (see copy()) + * - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink()) + * - $options['delete'] Whether to delete files that are not in the source directory (defaults to false) + * + * @throws IOException When file type is unknown + */ + public function mirror($originDir, $targetDir, \Traversable $iterator = null, $options = array()) + { + $targetDir = rtrim($targetDir, '/\\'); + $originDir = rtrim($originDir, '/\\'); + + // Iterate in destination folder to remove obsolete entries + if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) { + $deleteIterator = $iterator; + if (null === $deleteIterator) { + $flags = \FilesystemIterator::SKIP_DOTS; + $deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST); + } + foreach ($deleteIterator as $file) { + $origin = str_replace($targetDir, $originDir, $file->getPathname()); + if (!$this->exists($origin)) { + $this->remove($file); + } + } + } + + $copyOnWindows = false; + if (isset($options['copy_on_windows']) && !function_exists('symlink')) { + $copyOnWindows = $options['copy_on_windows']; + } + + if (null === $iterator) { + $flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS; + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST); + } + + foreach ($iterator as $file) { + $target = str_replace($originDir, $targetDir, $file->getPathname()); + + if ($copyOnWindows) { + if (is_link($file) || is_file($file)) { + $this->copy($file, $target, isset($options['override']) ? $options['override'] : false); + } elseif (is_dir($file)) { + $this->mkdir($target); + } else { + throw new IOException(sprintf('Unable to guess "%s" file type.', $file)); + } + } else { + if (is_link($file)) { + $this->symlink($file, $target); + } elseif (is_dir($file)) { + $this->mkdir($target); + } elseif (is_file($file)) { + $this->copy($file, $target, isset($options['override']) ? $options['override'] : false); + } else { + throw new IOException(sprintf('Unable to guess "%s" file type.', $file)); + } + } + } + } + + /** + * Returns whether the file path is an absolute path. + * + * @param string $file A file path + * + * @return Boolean + */ + public function isAbsolutePath($file) + { + if (strspn($file, '/\\', 0, 1) + || (strlen($file) > 3 && ctype_alpha($file[0]) + && substr($file, 1, 1) === ':' + && (strspn($file, '/\\', 2, 1)) + ) + || null !== parse_url($file, PHP_URL_SCHEME) + ) { + return true; + } + + return false; + } + + /** + * @param mixed $files + * + * @return \Traversable + */ + private function toIterator($files) + { + if (!$files instanceof \Traversable) { + $files = new \ArrayObject(is_array($files) ? $files : array($files)); + } + + return $files; + } + + /** + * Atomically dumps content into a file. + * + * @param string $filename The file to be written to. + * @param string $content The data to write into the file. + * @param integer $mode The file mode (octal). + * @throws IOException If the file cannot be written to. + */ + public function dumpFile($filename, $content, $mode = 0666) + { + $dir = dirname($filename); + + if (!is_dir($dir)) { + $this->mkdir($dir); + } elseif (!is_writable($dir)) { + throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir)); + } + + $tmpFile = tempnam($dir, basename($filename)); + + if (false === @file_put_contents($tmpFile, $content)) { + throw new IOException(sprintf('Failed to write file "%s".', $filename)); + } + + $this->rename($tmpFile, $filename, true); + $this->chmod($filename, $mode); + } +} diff --git a/vendor/symfony/filesystem/Symfony/Component/Filesystem/LICENSE b/vendor/symfony/filesystem/Symfony/Component/Filesystem/LICENSE new file mode 100644 index 0000000..88a57f8 --- /dev/null +++ b/vendor/symfony/filesystem/Symfony/Component/Filesystem/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2013 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/filesystem/Symfony/Component/Filesystem/README.md b/vendor/symfony/filesystem/Symfony/Component/Filesystem/README.md new file mode 100644 index 0000000..94ac146 --- /dev/null +++ b/vendor/symfony/filesystem/Symfony/Component/Filesystem/README.md @@ -0,0 +1,45 @@ +Filesystem Component +==================== + +Filesystem provides basic utility to manipulate the file system: + +```php +copy($originFile, $targetFile, $override = false); + +$filesystem->mkdir($dirs, $mode = 0777); + +$filesystem->touch($files, $time = null, $atime = null); + +$filesystem->remove($files); + +$filesystem->chmod($files, $mode, $umask = 0000, $recursive = false); + +$filesystem->chown($files, $user, $recursive = false); + +$filesystem->chgrp($files, $group, $recursive = false); + +$filesystem->rename($origin, $target); + +$filesystem->symlink($originDir, $targetDir, $copyOnWindows = false); + +$filesystem->makePathRelative($endPath, $startPath); + +$filesystem->mirror($originDir, $targetDir, \Traversable $iterator = null, $options = array()); + +$filesystem->isAbsolutePath($file); +``` + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/Filesystem/ + $ composer.phar install --dev + $ phpunit diff --git a/vendor/symfony/filesystem/Symfony/Component/Filesystem/Tests/FilesystemTest.php b/vendor/symfony/filesystem/Symfony/Component/Filesystem/Tests/FilesystemTest.php new file mode 100644 index 0000000..6dcf38d --- /dev/null +++ b/vendor/symfony/filesystem/Symfony/Component/Filesystem/Tests/FilesystemTest.php @@ -0,0 +1,879 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Tests; + +use Symfony\Component\Filesystem\Filesystem; + +/** + * Test class for Filesystem. + */ +class FilesystemTest extends FilesystemTestCase +{ + /** + * @var \Symfony\Component\Filesystem\Filesystem $filesystem + */ + private $filesystem = null; + + public function setUp() + { + parent::setUp(); + $this->filesystem = new Filesystem(); + } + + public function testCopyCreatesNewFile() + { + $sourceFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_source_file'; + $targetFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_target_file'; + + file_put_contents($sourceFilePath, 'SOURCE FILE'); + + $this->filesystem->copy($sourceFilePath, $targetFilePath); + + $this->assertFileExists($targetFilePath); + $this->assertEquals('SOURCE FILE', file_get_contents($targetFilePath)); + } + + /** + * @expectedException \Symfony\Component\Filesystem\Exception\IOException + */ + public function testCopyFails() + { + $sourceFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_source_file'; + $targetFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_target_file'; + + $this->filesystem->copy($sourceFilePath, $targetFilePath); + } + + public function testCopyOverridesExistingFileIfModified() + { + $sourceFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_source_file'; + $targetFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_target_file'; + + file_put_contents($sourceFilePath, 'SOURCE FILE'); + file_put_contents($targetFilePath, 'TARGET FILE'); + touch($targetFilePath, time() - 1000); + + $this->filesystem->copy($sourceFilePath, $targetFilePath); + + $this->assertFileExists($targetFilePath); + $this->assertEquals('SOURCE FILE', file_get_contents($targetFilePath)); + } + + public function testCopyDoesNotOverrideExistingFileByDefault() + { + $sourceFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_source_file'; + $targetFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_target_file'; + + file_put_contents($sourceFilePath, 'SOURCE FILE'); + file_put_contents($targetFilePath, 'TARGET FILE'); + + // make sure both files have the same modification time + $modificationTime = time() - 1000; + touch($sourceFilePath, $modificationTime); + touch($targetFilePath, $modificationTime); + + $this->filesystem->copy($sourceFilePath, $targetFilePath); + + $this->assertFileExists($targetFilePath); + $this->assertEquals('TARGET FILE', file_get_contents($targetFilePath)); + } + + public function testCopyOverridesExistingFileIfForced() + { + $sourceFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_source_file'; + $targetFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_target_file'; + + file_put_contents($sourceFilePath, 'SOURCE FILE'); + file_put_contents($targetFilePath, 'TARGET FILE'); + + // make sure both files have the same modification time + $modificationTime = time() - 1000; + touch($sourceFilePath, $modificationTime); + touch($targetFilePath, $modificationTime); + + $this->filesystem->copy($sourceFilePath, $targetFilePath, true); + + $this->assertFileExists($targetFilePath); + $this->assertEquals('SOURCE FILE', file_get_contents($targetFilePath)); + } + + public function testCopyCreatesTargetDirectoryIfItDoesNotExist() + { + $sourceFilePath = $this->workspace.DIRECTORY_SEPARATOR.'copy_source_file'; + $targetFileDirectory = $this->workspace.DIRECTORY_SEPARATOR.'directory'; + $targetFilePath = $targetFileDirectory.DIRECTORY_SEPARATOR.'copy_target_file'; + + file_put_contents($sourceFilePath, 'SOURCE FILE'); + + $this->filesystem->copy($sourceFilePath, $targetFilePath); + + $this->assertTrue(is_dir($targetFileDirectory)); + $this->assertFileExists($targetFilePath); + $this->assertEquals('SOURCE FILE', file_get_contents($targetFilePath)); + } + + public function testMkdirCreatesDirectoriesRecursively() + { + $directory = $this->workspace + .DIRECTORY_SEPARATOR.'directory' + .DIRECTORY_SEPARATOR.'sub_directory'; + + $this->filesystem->mkdir($directory); + + $this->assertTrue(is_dir($directory)); + } + + public function testMkdirCreatesDirectoriesFromArray() + { + $basePath = $this->workspace.DIRECTORY_SEPARATOR; + $directories = array( + $basePath.'1', $basePath.'2', $basePath.'3' + ); + + $this->filesystem->mkdir($directories); + + $this->assertTrue(is_dir($basePath.'1')); + $this->assertTrue(is_dir($basePath.'2')); + $this->assertTrue(is_dir($basePath.'3')); + } + + public function testMkdirCreatesDirectoriesFromTraversableObject() + { + $basePath = $this->workspace.DIRECTORY_SEPARATOR; + $directories = new \ArrayObject(array( + $basePath.'1', $basePath.'2', $basePath.'3' + )); + + $this->filesystem->mkdir($directories); + + $this->assertTrue(is_dir($basePath.'1')); + $this->assertTrue(is_dir($basePath.'2')); + $this->assertTrue(is_dir($basePath.'3')); + } + + /** + * @expectedException \Symfony\Component\Filesystem\Exception\IOException + */ + public function testMkdirCreatesDirectoriesFails() + { + $basePath = $this->workspace.DIRECTORY_SEPARATOR; + $dir = $basePath.'2'; + + file_put_contents($dir, ''); + + $this->filesystem->mkdir($dir); + } + + public function testTouchCreatesEmptyFile() + { + $file = $this->workspace.DIRECTORY_SEPARATOR.'1'; + + $this->filesystem->touch($file); + + $this->assertFileExists($file); + } + + /** + * @expectedException \Symfony\Component\Filesystem\Exception\IOException + */ + public function testTouchFails() + { + $file = $this->workspace.DIRECTORY_SEPARATOR.'1'.DIRECTORY_SEPARATOR.'2'; + + $this->filesystem->touch($file); + } + + public function testTouchCreatesEmptyFilesFromArray() + { + $basePath = $this->workspace.DIRECTORY_SEPARATOR; + $files = array( + $basePath.'1', $basePath.'2', $basePath.'3' + ); + + $this->filesystem->touch($files); + + $this->assertFileExists($basePath.'1'); + $this->assertFileExists($basePath.'2'); + $this->assertFileExists($basePath.'3'); + } + + public function testTouchCreatesEmptyFilesFromTraversableObject() + { + $basePath = $this->workspace.DIRECTORY_SEPARATOR; + $files = new \ArrayObject(array( + $basePath.'1', $basePath.'2', $basePath.'3' + )); + + $this->filesystem->touch($files); + + $this->assertFileExists($basePath.'1'); + $this->assertFileExists($basePath.'2'); + $this->assertFileExists($basePath.'3'); + } + + public function testRemoveCleansFilesAndDirectoriesIteratively() + { + $basePath = $this->workspace.DIRECTORY_SEPARATOR.'directory'.DIRECTORY_SEPARATOR; + + mkdir($basePath); + mkdir($basePath.'dir'); + touch($basePath.'file'); + + $this->filesystem->remove($basePath); + + $this->assertTrue(!is_dir($basePath)); + } + + public function testRemoveCleansArrayOfFilesAndDirectories() + { + $basePath = $this->workspace.DIRECTORY_SEPARATOR; + + mkdir($basePath.'dir'); + touch($basePath.'file'); + + $files = array( + $basePath.'dir', $basePath.'file' + ); + + $this->filesystem->remove($files); + + $this->assertTrue(!is_dir($basePath.'dir')); + $this->assertTrue(!is_file($basePath.'file')); + } + + public function testRemoveCleansTraversableObjectOfFilesAndDirectories() + { + $basePath = $this->workspace.DIRECTORY_SEPARATOR; + + mkdir($basePath.'dir'); + touch($basePath.'file'); + + $files = new \ArrayObject(array( + $basePath.'dir', $basePath.'file' + )); + + $this->filesystem->remove($files); + + $this->assertTrue(!is_dir($basePath.'dir')); + $this->assertTrue(!is_file($basePath.'file')); + } + + public function testRemoveIgnoresNonExistingFiles() + { + $basePath = $this->workspace.DIRECTORY_SEPARATOR; + + mkdir($basePath.'dir'); + + $files = array( + $basePath.'dir', $basePath.'file' + ); + + $this->filesystem->remove($files); + + $this->assertTrue(!is_dir($basePath.'dir')); + } + + public function testRemoveCleansInvalidLinks() + { + $this->markAsSkippedIfSymlinkIsMissing(); + + $basePath = $this->workspace.DIRECTORY_SEPARATOR.'directory'.DIRECTORY_SEPARATOR; + + mkdir($basePath); + mkdir($basePath.'dir'); + // create symlink to unexisting file + @symlink($basePath.'file', $basePath.'link'); + + $this->filesystem->remove($basePath); + + $this->assertTrue(!is_dir($basePath)); + } + + public function testFilesExists() + { + $basePath = $this->workspace.DIRECTORY_SEPARATOR.'directory'.DIRECTORY_SEPARATOR; + + mkdir($basePath); + touch($basePath.'file1'); + mkdir($basePath.'folder'); + + $this->assertTrue($this->filesystem->exists($basePath.'file1')); + $this->assertTrue($this->filesystem->exists($basePath.'folder')); + } + + public function testFilesExistsTraversableObjectOfFilesAndDirectories() + { + $basePath = $this->workspace.DIRECTORY_SEPARATOR; + + mkdir($basePath.'dir'); + touch($basePath.'file'); + + $files = new \ArrayObject(array( + $basePath.'dir', $basePath.'file' + )); + + $this->assertTrue($this->filesystem->exists($files)); + } + + public function testFilesNotExistsTraversableObjectOfFilesAndDirectories() + { + $basePath = $this->workspace.DIRECTORY_SEPARATOR; + + mkdir($basePath.'dir'); + touch($basePath.'file'); + touch($basePath.'file2'); + + $files = new \ArrayObject(array( + $basePath.'dir', $basePath.'file', $basePath.'file2' + )); + + unlink($basePath.'file'); + + $this->assertFalse($this->filesystem->exists($files)); + } + + public function testInvalidFileNotExists() + { + $basePath = $this->workspace.DIRECTORY_SEPARATOR.'directory'.DIRECTORY_SEPARATOR; + + $this->assertFalse($this->filesystem->exists($basePath.time())); + } + + public function testChmodChangesFileMode() + { + $this->markAsSkippedIfChmodIsMissing(); + + $dir = $this->workspace.DIRECTORY_SEPARATOR.'dir'; + mkdir($dir); + $file = $dir.DIRECTORY_SEPARATOR.'file'; + touch($file); + + $this->filesystem->chmod($file, 0400); + $this->filesystem->chmod($dir, 0753); + + $this->assertFilePermissions(753, $dir); + $this->assertFilePermissions(400, $file); + } + + public function testChmodWrongMod() + { + $this->markAsSkippedIfChmodIsMissing(); + + $dir = $this->workspace.DIRECTORY_SEPARATOR.'file'; + touch($dir); + + $this->filesystem->chmod($dir, 'Wrongmode'); + } + + public function testChmodRecursive() + { + $this->markAsSkippedIfChmodIsMissing(); + + $dir = $this->workspace.DIRECTORY_SEPARATOR.'dir'; + mkdir($dir); + $file = $dir.DIRECTORY_SEPARATOR.'file'; + touch($file); + + $this->filesystem->chmod($file, 0400, 0000, true); + $this->filesystem->chmod($dir, 0753, 0000, true); + + $this->assertFilePermissions(753, $dir); + $this->assertFilePermissions(753, $file); + } + + public function testChmodAppliesUmask() + { + $this->markAsSkippedIfChmodIsMissing(); + + $file = $this->workspace.DIRECTORY_SEPARATOR.'file'; + touch($file); + + $this->filesystem->chmod($file, 0770, 0022); + $this->assertFilePermissions(750, $file); + } + + public function testChmodChangesModeOfArrayOfFiles() + { + $this->markAsSkippedIfChmodIsMissing(); + + $directory = $this->workspace.DIRECTORY_SEPARATOR.'directory'; + $file = $this->workspace.DIRECTORY_SEPARATOR.'file'; + $files = array($directory, $file); + + mkdir($directory); + touch($file); + + $this->filesystem->chmod($files, 0753); + + $this->assertFilePermissions(753, $file); + $this->assertFilePermissions(753, $directory); + } + + public function testChmodChangesModeOfTraversableFileObject() + { + $this->markAsSkippedIfChmodIsMissing(); + + $directory = $this->workspace.DIRECTORY_SEPARATOR.'directory'; + $file = $this->workspace.DIRECTORY_SEPARATOR.'file'; + $files = new \ArrayObject(array($directory, $file)); + + mkdir($directory); + touch($file); + + $this->filesystem->chmod($files, 0753); + + $this->assertFilePermissions(753, $file); + $this->assertFilePermissions(753, $directory); + } + + public function testChown() + { + $this->markAsSkippedIfPosixIsMissing(); + + $dir = $this->workspace.DIRECTORY_SEPARATOR.'dir'; + mkdir($dir); + + $this->filesystem->chown($dir, $this->getFileOwner($dir)); + } + + public function testChownRecursive() + { + $this->markAsSkippedIfPosixIsMissing(); + + $dir = $this->workspace.DIRECTORY_SEPARATOR.'dir'; + mkdir($dir); + $file = $dir.DIRECTORY_SEPARATOR.'file'; + touch($file); + + $this->filesystem->chown($dir, $this->getFileOwner($dir), true); + } + + public function testChownSymlink() + { + $this->markAsSkippedIfSymlinkIsMissing(); + + $file = $this->workspace.DIRECTORY_SEPARATOR.'file'; + $link = $this->workspace.DIRECTORY_SEPARATOR.'link'; + + touch($file); + + $this->filesystem->symlink($file, $link); + + $this->filesystem->chown($link, $this->getFileOwner($link)); + } + + /** + * @expectedException \Symfony\Component\Filesystem\Exception\IOException + */ + public function testChownSymlinkFails() + { + $this->markAsSkippedIfSymlinkIsMissing(); + + $file = $this->workspace.DIRECTORY_SEPARATOR.'file'; + $link = $this->workspace.DIRECTORY_SEPARATOR.'link'; + + touch($file); + + $this->filesystem->symlink($file, $link); + + $this->filesystem->chown($link, 'user'.time().mt_rand(1000, 9999)); + } + + /** + * @expectedException \Symfony\Component\Filesystem\Exception\IOException + */ + public function testChownFail() + { + $this->markAsSkippedIfPosixIsMissing(); + + $dir = $this->workspace.DIRECTORY_SEPARATOR.'dir'; + mkdir($dir); + + $this->filesystem->chown($dir, 'user'.time().mt_rand(1000, 9999)); + } + + public function testChgrp() + { + $this->markAsSkippedIfPosixIsMissing(); + + $dir = $this->workspace.DIRECTORY_SEPARATOR.'dir'; + mkdir($dir); + + $this->filesystem->chgrp($dir, $this->getFileGroup($dir)); + } + + public function testChgrpRecursive() + { + $this->markAsSkippedIfPosixIsMissing(); + + $dir = $this->workspace.DIRECTORY_SEPARATOR.'dir'; + mkdir($dir); + $file = $dir.DIRECTORY_SEPARATOR.'file'; + touch($file); + + $this->filesystem->chgrp($dir, $this->getFileGroup($dir), true); + } + + public function testChgrpSymlink() + { + $this->markAsSkippedIfSymlinkIsMissing(); + + $file = $this->workspace.DIRECTORY_SEPARATOR.'file'; + $link = $this->workspace.DIRECTORY_SEPARATOR.'link'; + + touch($file); + + $this->filesystem->symlink($file, $link); + + $this->filesystem->chgrp($link, $this->getFileGroup($link)); + } + + /** + * @expectedException \Symfony\Component\Filesystem\Exception\IOException + */ + public function testChgrpSymlinkFails() + { + $this->markAsSkippedIfSymlinkIsMissing(); + + $file = $this->workspace.DIRECTORY_SEPARATOR.'file'; + $link = $this->workspace.DIRECTORY_SEPARATOR.'link'; + + touch($file); + + $this->filesystem->symlink($file, $link); + + $this->filesystem->chgrp($link, 'user'.time().mt_rand(1000, 9999)); + } + + /** + * @expectedException \Symfony\Component\Filesystem\Exception\IOException + */ + public function testChgrpFail() + { + $this->markAsSkippedIfPosixIsMissing(); + + $dir = $this->workspace.DIRECTORY_SEPARATOR.'dir'; + mkdir($dir); + + $this->filesystem->chgrp($dir, 'user'.time().mt_rand(1000, 9999)); + } + + public function testRename() + { + $file = $this->workspace.DIRECTORY_SEPARATOR.'file'; + $newPath = $this->workspace.DIRECTORY_SEPARATOR.'new_file'; + touch($file); + + $this->filesystem->rename($file, $newPath); + + $this->assertFileNotExists($file); + $this->assertFileExists($newPath); + } + + /** + * @expectedException \Symfony\Component\Filesystem\Exception\IOException + */ + public function testRenameThrowsExceptionIfTargetAlreadyExists() + { + $file = $this->workspace.DIRECTORY_SEPARATOR.'file'; + $newPath = $this->workspace.DIRECTORY_SEPARATOR.'new_file'; + + touch($file); + touch($newPath); + + $this->filesystem->rename($file, $newPath); + } + + public function testRenameOverwritesTheTargetIfItAlreadyExists() + { + $file = $this->workspace.DIRECTORY_SEPARATOR.'file'; + $newPath = $this->workspace.DIRECTORY_SEPARATOR.'new_file'; + + touch($file); + touch($newPath); + + $this->filesystem->rename($file, $newPath, true); + + $this->assertFileNotExists($file); + $this->assertFileExists($newPath); + } + + /** + * @expectedException \Symfony\Component\Filesystem\Exception\IOException + */ + public function testRenameThrowsExceptionOnError() + { + $file = $this->workspace.DIRECTORY_SEPARATOR.uniqid(); + $newPath = $this->workspace.DIRECTORY_SEPARATOR.'new_file'; + + $this->filesystem->rename($file, $newPath); + } + + public function testSymlink() + { + $this->markAsSkippedIfSymlinkIsMissing(); + + $file = $this->workspace.DIRECTORY_SEPARATOR.'file'; + $link = $this->workspace.DIRECTORY_SEPARATOR.'link'; + + touch($file); + + $this->filesystem->symlink($file, $link); + + $this->assertTrue(is_link($link)); + $this->assertEquals($file, readlink($link)); + } + + /** + * @depends testSymlink + */ + public function testRemoveSymlink() + { + $this->markAsSkippedIfSymlinkIsMissing(); + + $link = $this->workspace.DIRECTORY_SEPARATOR.'link'; + + $this->filesystem->remove($link); + + $this->assertTrue(!is_link($link)); + } + + public function testSymlinkIsOverwrittenIfPointsToDifferentTarget() + { + $this->markAsSkippedIfSymlinkIsMissing(); + + $file = $this->workspace.DIRECTORY_SEPARATOR.'file'; + $link = $this->workspace.DIRECTORY_SEPARATOR.'link'; + + touch($file); + symlink($this->workspace, $link); + + $this->filesystem->symlink($file, $link); + + $this->assertTrue(is_link($link)); + $this->assertEquals($file, readlink($link)); + } + + public function testSymlinkIsNotOverwrittenIfAlreadyCreated() + { + $this->markAsSkippedIfSymlinkIsMissing(); + + $file = $this->workspace.DIRECTORY_SEPARATOR.'file'; + $link = $this->workspace.DIRECTORY_SEPARATOR.'link'; + + touch($file); + symlink($file, $link); + + $this->filesystem->symlink($file, $link); + + $this->assertTrue(is_link($link)); + $this->assertEquals($file, readlink($link)); + } + + public function testSymlinkCreatesTargetDirectoryIfItDoesNotExist() + { + $this->markAsSkippedIfSymlinkIsMissing(); + + $file = $this->workspace.DIRECTORY_SEPARATOR.'file'; + $link1 = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'link'; + $link2 = $this->workspace.DIRECTORY_SEPARATOR.'dir'.DIRECTORY_SEPARATOR.'subdir'.DIRECTORY_SEPARATOR.'link'; + + touch($file); + + $this->filesystem->symlink($file, $link1); + $this->filesystem->symlink($file, $link2); + + $this->assertTrue(is_link($link1)); + $this->assertEquals($file, readlink($link1)); + $this->assertTrue(is_link($link2)); + $this->assertEquals($file, readlink($link2)); + } + + /** + * @dataProvider providePathsForMakePathRelative + */ + public function testMakePathRelative($endPath, $startPath, $expectedPath) + { + $path = $this->filesystem->makePathRelative($endPath, $startPath); + + $this->assertEquals($expectedPath, $path); + } + + /** + * @return array + */ + public function providePathsForMakePathRelative() + { + $paths = array( + array('/var/lib/symfony/src/Symfony/', '/var/lib/symfony/src/Symfony/Component', '../'), + array('/var/lib/symfony/src/Symfony/', '/var/lib/symfony/src/Symfony/Component/', '../'), + array('/var/lib/symfony/src/Symfony', '/var/lib/symfony/src/Symfony/Component', '../'), + array('/var/lib/symfony/src/Symfony', '/var/lib/symfony/src/Symfony/Component/', '../'), + array('var/lib/symfony/', 'var/lib/symfony/src/Symfony/Component', '../../../'), + array('/usr/lib/symfony/', '/var/lib/symfony/src/Symfony/Component', '../../../../../../usr/lib/symfony/'), + array('/var/lib/symfony/src/Symfony/', '/var/lib/symfony/', 'src/Symfony/'), + array('/aa/bb', '/aa/bb', './'), + array('/aa/bb', '/aa/bb/', './'), + array('/aa/bb/', '/aa/bb', './'), + array('/aa/bb/', '/aa/bb/', './'), + array('/aa/bb/cc', '/aa/bb/cc/dd', '../'), + array('/aa/bb/cc', '/aa/bb/cc/dd/', '../'), + array('/aa/bb/cc/', '/aa/bb/cc/dd', '../'), + array('/aa/bb/cc/', '/aa/bb/cc/dd/', '../'), + array('/aa/bb/cc', '/aa', 'bb/cc/'), + array('/aa/bb/cc', '/aa/', 'bb/cc/'), + array('/aa/bb/cc/', '/aa', 'bb/cc/'), + array('/aa/bb/cc/', '/aa/', 'bb/cc/'), + array('/a/aab/bb', '/a/aa', '../aab/bb/'), + array('/a/aab/bb', '/a/aa/', '../aab/bb/'), + array('/a/aab/bb/', '/a/aa', '../aab/bb/'), + array('/a/aab/bb/', '/a/aa/', '../aab/bb/'), + ); + + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + $paths[] = array('c:\var\lib/symfony/src/Symfony/', 'c:/var/lib/symfony/', 'src/Symfony/'); + } + + return $paths; + } + + public function testMirrorCopiesFilesAndDirectoriesRecursively() + { + $sourcePath = $this->workspace.DIRECTORY_SEPARATOR.'source'.DIRECTORY_SEPARATOR; + $directory = $sourcePath.'directory'.DIRECTORY_SEPARATOR; + $file1 = $directory.'file1'; + $file2 = $sourcePath.'file2'; + + mkdir($sourcePath); + mkdir($directory); + file_put_contents($file1, 'FILE1'); + file_put_contents($file2, 'FILE2'); + + $targetPath = $this->workspace.DIRECTORY_SEPARATOR.'target'.DIRECTORY_SEPARATOR; + + $this->filesystem->mirror($sourcePath, $targetPath); + + $this->assertTrue(is_dir($targetPath)); + $this->assertTrue(is_dir($targetPath.'directory')); + $this->assertFileEquals($file1, $targetPath.'directory'.DIRECTORY_SEPARATOR.'file1'); + $this->assertFileEquals($file2, $targetPath.'file2'); + + $this->filesystem->remove($file1); + + $this->filesystem->mirror($sourcePath, $targetPath, null, array('delete' => false)); + $this->assertTrue($this->filesystem->exists($targetPath.'directory'.DIRECTORY_SEPARATOR.'file1')); + + $this->filesystem->mirror($sourcePath, $targetPath, null, array('delete' => true)); + $this->assertFalse($this->filesystem->exists($targetPath.'directory'.DIRECTORY_SEPARATOR.'file1')); + + file_put_contents($file1, 'FILE1'); + + $this->filesystem->mirror($sourcePath, $targetPath, null, array('delete' => true)); + $this->assertTrue($this->filesystem->exists($targetPath.'directory'.DIRECTORY_SEPARATOR.'file1')); + + $this->filesystem->remove($directory); + $this->filesystem->mirror($sourcePath, $targetPath, null, array('delete' => true)); + $this->assertFalse($this->filesystem->exists($targetPath.'directory')); + $this->assertFalse($this->filesystem->exists($targetPath.'directory'.DIRECTORY_SEPARATOR.'file1')); + } + + public function testMirrorCopiesLinks() + { + $this->markAsSkippedIfSymlinkIsMissing(); + + $sourcePath = $this->workspace.DIRECTORY_SEPARATOR.'source'.DIRECTORY_SEPARATOR; + + mkdir($sourcePath); + file_put_contents($sourcePath.'file1', 'FILE1'); + symlink($sourcePath.'file1', $sourcePath.'link1'); + + $targetPath = $this->workspace.DIRECTORY_SEPARATOR.'target'.DIRECTORY_SEPARATOR; + + $this->filesystem->mirror($sourcePath, $targetPath); + + $this->assertTrue(is_dir($targetPath)); + $this->assertFileEquals($sourcePath.'file1', $targetPath.DIRECTORY_SEPARATOR.'link1'); + $this->assertTrue(is_link($targetPath.DIRECTORY_SEPARATOR.'link1')); + } + + public function testMirrorCopiesLinkedDirectoryContents() + { + $this->markAsSkippedIfSymlinkIsMissing(); + + $sourcePath = $this->workspace.DIRECTORY_SEPARATOR.'source'.DIRECTORY_SEPARATOR; + + mkdir($sourcePath.'nested/', 0777, true); + file_put_contents($sourcePath.'/nested/file1.txt', 'FILE1'); + // Note: We symlink directory, not file + symlink($sourcePath.'nested', $sourcePath.'link1'); + + $targetPath = $this->workspace.DIRECTORY_SEPARATOR.'target'.DIRECTORY_SEPARATOR; + + $this->filesystem->mirror($sourcePath, $targetPath); + + $this->assertTrue(is_dir($targetPath)); + $this->assertFileEquals($sourcePath.'/nested/file1.txt', $targetPath.DIRECTORY_SEPARATOR.'link1/file1.txt'); + $this->assertTrue(is_link($targetPath.DIRECTORY_SEPARATOR.'link1')); + } + + /** + * @dataProvider providePathsForIsAbsolutePath + */ + public function testIsAbsolutePath($path, $expectedResult) + { + $result = $this->filesystem->isAbsolutePath($path); + + $this->assertEquals($expectedResult, $result); + } + + /** + * @return array + */ + public function providePathsForIsAbsolutePath() + { + return array( + array('/var/lib', true), + array('c:\\\\var\\lib', true), + array('\\var\\lib', true), + array('var/lib', false), + array('../var/lib', false), + array('', false), + array(null, false) + ); + } + + public function testDumpFile() + { + $filename = $this->workspace.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'baz.txt'; + + $this->filesystem->dumpFile($filename, 'bar', 0753); + + $this->assertFileExists($filename); + $this->assertSame('bar', file_get_contents($filename)); + + // skip mode check on windows + if (!defined('PHP_WINDOWS_VERSION_MAJOR')) { + $this->assertFilePermissions(753, $filename); + } + } + + public function testDumpFileOverwritesAnExistingFile() + { + $filename = $this->workspace.DIRECTORY_SEPARATOR.'foo.txt'; + file_put_contents($filename, 'FOO BAR'); + + $this->filesystem->dumpFile($filename, 'bar'); + + $this->assertFileExists($filename); + $this->assertSame('bar', file_get_contents($filename)); + } +} diff --git a/vendor/symfony/filesystem/Symfony/Component/Filesystem/Tests/FilesystemTestCase.php b/vendor/symfony/filesystem/Symfony/Component/Filesystem/Tests/FilesystemTestCase.php new file mode 100644 index 0000000..b4b2230 --- /dev/null +++ b/vendor/symfony/filesystem/Symfony/Component/Filesystem/Tests/FilesystemTestCase.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Tests; + +class FilesystemTestCase extends \PHPUnit_Framework_TestCase +{ + /** + * @var string $workspace + */ + protected $workspace = null; + + protected static $symlinkOnWindows = null; + + public static function setUpBeforeClass() + { + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + static::$symlinkOnWindows = true; + $originDir = tempnam(sys_get_temp_dir(), 'sl'); + $targetDir = tempnam(sys_get_temp_dir(), 'sl'); + if (true !== @symlink($originDir, $targetDir)) { + $report = error_get_last(); + if (is_array($report) && false !== strpos($report['message'], 'error code(1314)')) { + static::$symlinkOnWindows = false; + } + } + } + } + + public function setUp() + { + $this->workspace = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.time().rand(0, 1000); + mkdir($this->workspace, 0777, true); + $this->workspace = realpath($this->workspace); + } + + public function tearDown() + { + $this->clean($this->workspace); + } + + /** + * @param string $file + */ + protected function clean($file) + { + if (is_dir($file) && !is_link($file)) { + $dir = new \FilesystemIterator($file); + foreach ($dir as $childFile) { + $this->clean($childFile); + } + + rmdir($file); + } else { + unlink($file); + } + } + + /** + * @param int $expectedFilePerms expected file permissions as three digits (i.e. 755) + * @param string $filePath + */ + protected function assertFilePermissions($expectedFilePerms, $filePath) + { + $actualFilePerms = (int) substr(sprintf('%o', fileperms($filePath)), -3); + $this->assertEquals( + $expectedFilePerms, + $actualFilePerms, + sprintf('File permissions for %s must be %s. Actual %s', $filePath, $expectedFilePerms, $actualFilePerms) + ); + } + + protected function getFileOwner($filepath) + { + $this->markAsSkippedIfPosixIsMissing(); + + $infos = stat($filepath); + if ($datas = posix_getpwuid($infos['uid'])) { + return $datas['name']; + } + } + + protected function getFileGroup($filepath) + { + $this->markAsSkippedIfPosixIsMissing(); + + $infos = stat($filepath); + if ($datas = posix_getgrgid($infos['gid'])) { + return $datas['name']; + } + } + + protected function markAsSkippedIfSymlinkIsMissing() + { + if (!function_exists('symlink')) { + $this->markTestSkipped('symlink is not supported'); + } + + if (defined('PHP_WINDOWS_VERSION_MAJOR') && false === static::$symlinkOnWindows) { + $this->markTestSkipped('symlink requires "Create symbolic links" privilege on windows'); + } + } + + protected function markAsSkippedIfChmodIsMissing() + { + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + $this->markTestSkipped('chmod is not supported on windows'); + } + } + + protected function markAsSkippedIfPosixIsMissing() + { + if (defined('PHP_WINDOWS_VERSION_MAJOR') || !function_exists('posix_isatty')) { + $this->markTestSkipped('Posix is not supported'); + } + } +} diff --git a/vendor/symfony/filesystem/Symfony/Component/Filesystem/composer.json b/vendor/symfony/filesystem/Symfony/Component/Filesystem/composer.json new file mode 100644 index 0000000..bab7c4f --- /dev/null +++ b/vendor/symfony/filesystem/Symfony/Component/Filesystem/composer.json @@ -0,0 +1,31 @@ +{ + "name": "symfony/filesystem", + "type": "library", + "description": "Symfony Filesystem Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\Filesystem\\": "" } + }, + "target-dir": "Symfony/Component/Filesystem", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + } +} diff --git a/vendor/symfony/filesystem/Symfony/Component/Filesystem/phpunit.xml.dist b/vendor/symfony/filesystem/Symfony/Component/Filesystem/phpunit.xml.dist new file mode 100644 index 0000000..ef0bf95 --- /dev/null +++ b/vendor/symfony/filesystem/Symfony/Component/Filesystem/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + + + + diff --git a/vendor/symfony/finder/Symfony/Component/Finder/.gitignore b/vendor/symfony/finder/Symfony/Component/Finder/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Adapter/AbstractAdapter.php b/vendor/symfony/finder/Symfony/Component/Finder/Adapter/AbstractAdapter.php new file mode 100644 index 0000000..46473b3 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Adapter/AbstractAdapter.php @@ -0,0 +1,236 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +/** + * Interface for finder engine implementations. + * + * @author Jean-François Simon + */ +abstract class AbstractAdapter implements AdapterInterface +{ + protected $followLinks = false; + protected $mode = 0; + protected $minDepth = 0; + protected $maxDepth = PHP_INT_MAX; + protected $exclude = array(); + protected $names = array(); + protected $notNames = array(); + protected $contains = array(); + protected $notContains = array(); + protected $sizes = array(); + protected $dates = array(); + protected $filters = array(); + protected $sort = false; + protected $paths = array(); + protected $notPaths = array(); + protected $ignoreUnreadableDirs = false; + + private static $areSupported = array(); + + /** + * {@inheritDoc} + */ + public function isSupported() + { + $name = $this->getName(); + + if (!array_key_exists($name, self::$areSupported)) { + self::$areSupported[$name] = $this->canBeUsed(); + } + + return self::$areSupported[$name]; + } + + /** + * {@inheritdoc} + */ + public function setFollowLinks($followLinks) + { + $this->followLinks = $followLinks; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setMode($mode) + { + $this->mode = $mode; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setDepths(array $depths) + { + $this->minDepth = 0; + $this->maxDepth = PHP_INT_MAX; + + foreach ($depths as $comparator) { + switch ($comparator->getOperator()) { + case '>': + $this->minDepth = $comparator->getTarget() + 1; + break; + case '>=': + $this->minDepth = $comparator->getTarget(); + break; + case '<': + $this->maxDepth = $comparator->getTarget() - 1; + break; + case '<=': + $this->maxDepth = $comparator->getTarget(); + break; + default: + $this->minDepth = $this->maxDepth = $comparator->getTarget(); + } + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setExclude(array $exclude) + { + $this->exclude = $exclude; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setNames(array $names) + { + $this->names = $names; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setNotNames(array $notNames) + { + $this->notNames = $notNames; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setContains(array $contains) + { + $this->contains = $contains; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setNotContains(array $notContains) + { + $this->notContains = $notContains; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setSizes(array $sizes) + { + $this->sizes = $sizes; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setDates(array $dates) + { + $this->dates = $dates; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setFilters(array $filters) + { + $this->filters = $filters; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setSort($sort) + { + $this->sort = $sort; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setPath(array $paths) + { + $this->paths = $paths; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setNotPath(array $notPaths) + { + $this->notPaths = $notPaths; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function ignoreUnreadableDirs($ignore = true) + { + $this->ignoreUnreadableDirs = (Boolean) $ignore; + + return $this; + } + + /** + * Returns whether the adapter is supported in the current environment. + * + * This method should be implemented in all adapters. Do not implement + * isSupported in the adapters as the generic implementation provides a cache + * layer. + * + * @see isSupported + * + * @return Boolean Whether the adapter is supported + */ + abstract protected function canBeUsed(); +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php b/vendor/symfony/finder/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php new file mode 100644 index 0000000..028a5e5 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php @@ -0,0 +1,327 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +use Symfony\Component\Finder\Exception\AccessDeniedException; +use Symfony\Component\Finder\Iterator; +use Symfony\Component\Finder\Shell\Shell; +use Symfony\Component\Finder\Expression\Expression; +use Symfony\Component\Finder\Shell\Command; +use Symfony\Component\Finder\Iterator\SortableIterator; +use Symfony\Component\Finder\Comparator\NumberComparator; +use Symfony\Component\Finder\Comparator\DateComparator; + +/** + * Shell engine implementation using GNU find command. + * + * @author Jean-François Simon + */ +abstract class AbstractFindAdapter extends AbstractAdapter +{ + /** + * @var Shell + */ + protected $shell; + + /** + * Constructor. + */ + public function __construct() + { + $this->shell = new Shell(); + } + + /** + * {@inheritdoc} + */ + public function searchInDirectory($dir) + { + // having "/../" in path make find fail + $dir = realpath($dir); + + // searching directories containing or not containing strings leads to no result + if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode && ($this->contains || $this->notContains)) { + return new Iterator\FilePathsIterator(array(), $dir); + } + + $command = Command::create(); + $find = $this->buildFindCommand($command, $dir); + + if ($this->followLinks) { + $find->add('-follow'); + } + + $find->add('-mindepth')->add($this->minDepth + 1); + + if (PHP_INT_MAX !== $this->maxDepth) { + $find->add('-maxdepth')->add($this->maxDepth + 1); + } + + if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode) { + $find->add('-type d'); + } elseif (Iterator\FileTypeFilterIterator::ONLY_FILES === $this->mode) { + $find->add('-type f'); + } + + $this->buildNamesFiltering($find, $this->names); + $this->buildNamesFiltering($find, $this->notNames, true); + $this->buildPathsFiltering($find, $dir, $this->paths); + $this->buildPathsFiltering($find, $dir, $this->notPaths, true); + $this->buildSizesFiltering($find, $this->sizes); + $this->buildDatesFiltering($find, $this->dates); + + $useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs'); + $useSort = is_int($this->sort) && $this->shell->testCommand('sort') && $this->shell->testCommand('cut'); + + if ($useGrep && ($this->contains || $this->notContains)) { + $grep = $command->ins('grep'); + $this->buildContentFiltering($grep, $this->contains); + $this->buildContentFiltering($grep, $this->notContains, true); + } + + if ($useSort) { + $this->buildSorting($command, $this->sort); + } + + $command->setErrorHandler( + $this->ignoreUnreadableDirs + // If directory is unreadable and finder is set to ignore it, `stderr` is ignored. + ? function ($stderr) { return; } + : function ($stderr) { throw new AccessDeniedException($stderr); } + ); + + $paths = $this->shell->testCommand('uniq') ? $command->add('| uniq')->execute() : array_unique($command->execute()); + $iterator = new Iterator\FilePathsIterator($paths, $dir); + + if ($this->exclude) { + $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); + } + + if (!$useGrep && ($this->contains || $this->notContains)) { + $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); + } + + if ($this->filters) { + $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); + } + + if (!$useSort && $this->sort) { + $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); + $iterator = $iteratorAggregate->getIterator(); + } + + return $iterator; + } + + /** + * {@inheritdoc} + */ + protected function canBeUsed() + { + return $this->shell->testCommand('find'); + } + + /** + * @param Command $command + * @param string $dir + * + * @return Command + */ + protected function buildFindCommand(Command $command, $dir) + { + return $command + ->ins('find') + ->add('find ') + ->arg($dir) + ->add('-noleaf'); // the -noleaf option is required for filesystems that don't follow the '.' and '..' conventions + } + + /** + * @param Command $command + * @param string[] $names + * @param Boolean $not + */ + private function buildNamesFiltering(Command $command, array $names, $not = false) + { + if (0 === count($names)) { + return; + } + + $command->add($not ? '-not' : null)->cmd('('); + + foreach ($names as $i => $name) { + $expr = Expression::create($name); + + // Find does not support expandable globs ("*.{a,b}" syntax). + if ($expr->isGlob() && $expr->getGlob()->isExpandable()) { + $expr = Expression::create($expr->getGlob()->toRegex(false)); + } + + // Fixes 'not search' and 'full path matching' regex problems. + // - Jokers '.' are replaced by [^/]. + // - We add '[^/]*' before and after regex (if no ^|$ flags are present). + if ($expr->isRegex()) { + $regex = $expr->getRegex(); + $regex->prepend($regex->hasStartFlag() ? '/' : '/[^/]*') + ->setStartFlag(false) + ->setStartJoker(true) + ->replaceJokers('[^/]'); + if (!$regex->hasEndFlag() || $regex->hasEndJoker()) { + $regex->setEndJoker(false)->append('[^/]*'); + } + } + + $command + ->add($i > 0 ? '-or' : null) + ->add($expr->isRegex() + ? ($expr->isCaseSensitive() ? '-regex' : '-iregex') + : ($expr->isCaseSensitive() ? '-name' : '-iname') + ) + ->arg($expr->renderPattern()); + } + + $command->cmd(')'); + } + + /** + * @param Command $command + * @param string $dir + * @param string[] $paths + * @param Boolean $not + */ + private function buildPathsFiltering(Command $command, $dir, array $paths, $not = false) + { + if (0 === count($paths)) { + return; + } + + $command->add($not ? '-not' : null)->cmd('('); + + foreach ($paths as $i => $path) { + $expr = Expression::create($path); + + // Find does not support expandable globs ("*.{a,b}" syntax). + if ($expr->isGlob() && $expr->getGlob()->isExpandable()) { + $expr = Expression::create($expr->getGlob()->toRegex(false)); + } + + // Fixes 'not search' regex problems. + if ($expr->isRegex()) { + $regex = $expr->getRegex(); + $regex->prepend($regex->hasStartFlag() ? $dir.DIRECTORY_SEPARATOR : '.*')->setEndJoker(!$regex->hasEndFlag()); + } else { + $expr->prepend('*')->append('*'); + } + + $command + ->add($i > 0 ? '-or' : null) + ->add($expr->isRegex() + ? ($expr->isCaseSensitive() ? '-regex' : '-iregex') + : ($expr->isCaseSensitive() ? '-path' : '-ipath') + ) + ->arg($expr->renderPattern()); + } + + $command->cmd(')'); + } + + /** + * @param Command $command + * @param NumberComparator[] $sizes + */ + private function buildSizesFiltering(Command $command, array $sizes) + { + foreach ($sizes as $i => $size) { + $command->add($i > 0 ? '-and' : null); + + switch ($size->getOperator()) { + case '<=': + $command->add('-size -'.($size->getTarget() + 1).'c'); + break; + case '>=': + $command->add('-size +'. ($size->getTarget() - 1).'c'); + break; + case '>': + $command->add('-size +'.$size->getTarget().'c'); + break; + case '!=': + $command->add('-size -'.$size->getTarget().'c'); + $command->add('-size +'.$size->getTarget().'c'); + case '<': + default: + $command->add('-size -'.$size->getTarget().'c'); + } + } + } + + /** + * @param Command $command + * @param DateComparator[] $dates + */ + private function buildDatesFiltering(Command $command, array $dates) + { + foreach ($dates as $i => $date) { + $command->add($i > 0 ? '-and' : null); + + $mins = (int) round((time()-$date->getTarget()) / 60); + + if (0 > $mins) { + // mtime is in the future + $command->add(' -mmin -0'); + // we will have no result so we don't need to continue + return; + } + + switch ($date->getOperator()) { + case '<=': + $command->add('-mmin +'.($mins - 1)); + break; + case '>=': + $command->add('-mmin -'.($mins + 1)); + break; + case '>': + $command->add('-mmin -'.$mins); + break; + case '!=': + $command->add('-mmin +'.$mins.' -or -mmin -'.$mins); + break; + case '<': + default: + $command->add('-mmin +'.$mins); + } + } + } + + /** + * @param Command $command + * @param string $sort + * + * @throws \InvalidArgumentException + */ + private function buildSorting(Command $command, $sort) + { + $this->buildFormatSorting($command, $sort); + } + + /** + * @param Command $command + * @param string $sort + */ + abstract protected function buildFormatSorting(Command $command, $sort); + + /** + * @param Command $command + * @param array $contains + * @param Boolean $not + */ + abstract protected function buildContentFiltering(Command $command, array $contains, $not = false); +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Adapter/AdapterInterface.php b/vendor/symfony/finder/Symfony/Component/Finder/Adapter/AdapterInterface.php new file mode 100644 index 0000000..50c018b --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Adapter/AdapterInterface.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +/** + * @author Jean-François Simon + */ +interface AdapterInterface +{ + /** + * @param Boolean $followLinks + * + * @return AdapterInterface Current instance + */ + public function setFollowLinks($followLinks); + + /** + * @param integer $mode + * + * @return AdapterInterface Current instance + */ + public function setMode($mode); + + /** + * @param array $exclude + * + * @return AdapterInterface Current instance + */ + public function setExclude(array $exclude); + + /** + * @param array $depths + * + * @return AdapterInterface Current instance + */ + public function setDepths(array $depths); + + /** + * @param array $names + * + * @return AdapterInterface Current instance + */ + public function setNames(array $names); + + /** + * @param array $notNames + * + * @return AdapterInterface Current instance + */ + public function setNotNames(array $notNames); + + /** + * @param array $contains + * + * @return AdapterInterface Current instance + */ + public function setContains(array $contains); + + /** + * @param array $notContains + * + * @return AdapterInterface Current instance + */ + public function setNotContains(array $notContains); + + /** + * @param array $sizes + * + * @return AdapterInterface Current instance + */ + public function setSizes(array $sizes); + + /** + * @param array $dates + * + * @return AdapterInterface Current instance + */ + public function setDates(array $dates); + + /** + * @param array $filters + * + * @return AdapterInterface Current instance + */ + public function setFilters(array $filters); + + /** + * @param \Closure|integer $sort + * + * @return AdapterInterface Current instance + */ + public function setSort($sort); + + /** + * @param array $paths + * + * @return AdapterInterface Current instance + */ + public function setPath(array $paths); + + /** + * @param array $notPaths + * + * @return AdapterInterface Current instance + */ + public function setNotPath(array $notPaths); + + /** + * @param boolean $ignore + * + * @return AdapterInterface Current instance + */ + public function ignoreUnreadableDirs($ignore = true); + + /** + * @param string $dir + * + * @return \Iterator Result iterator + */ + public function searchInDirectory($dir); + + /** + * Tests adapter support for current platform. + * + * @return Boolean + */ + public function isSupported(); + + /** + * Returns adapter name. + * + * @return string + */ + public function getName(); +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Adapter/BsdFindAdapter.php b/vendor/symfony/finder/Symfony/Component/Finder/Adapter/BsdFindAdapter.php new file mode 100644 index 0000000..4a25bae --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Adapter/BsdFindAdapter.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +use Symfony\Component\Finder\Shell\Shell; +use Symfony\Component\Finder\Shell\Command; +use Symfony\Component\Finder\Iterator\SortableIterator; +use Symfony\Component\Finder\Expression\Expression; + +/** + * Shell engine implementation using BSD find command. + * + * @author Jean-François Simon + */ +class BsdFindAdapter extends AbstractFindAdapter +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'bsd_find'; + } + + /** + * {@inheritdoc} + */ + protected function canBeUsed() + { + return in_array($this->shell->getType(), array(Shell::TYPE_BSD, Shell::TYPE_DARWIN)) && parent::canBeUsed(); + } + + /** + * {@inheritdoc} + */ + protected function buildFormatSorting(Command $command, $sort) + { + switch ($sort) { + case SortableIterator::SORT_BY_NAME: + $command->ins('sort')->add('| sort'); + + return; + case SortableIterator::SORT_BY_TYPE: + $format = '%HT'; + break; + case SortableIterator::SORT_BY_ACCESSED_TIME: + $format = '%a'; + break; + case SortableIterator::SORT_BY_CHANGED_TIME: + $format = '%c'; + break; + case SortableIterator::SORT_BY_MODIFIED_TIME: + $format = '%m'; + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown sort options: %s.', $sort)); + } + + $command + ->add('-print0 | xargs -0 stat -f') + ->arg($format.'%t%N') + ->add('| sort | cut -f 2'); + } + + /** + * {@inheritdoc} + */ + protected function buildFindCommand(Command $command, $dir) + { + parent::buildFindCommand($command, $dir)->addAtIndex('-E', 1); + + return $command; + } + + /** + * {@inheritdoc} + */ + protected function buildContentFiltering(Command $command, array $contains, $not = false) + { + foreach ($contains as $contain) { + $expr = Expression::create($contain); + + // todo: avoid forking process for each $pattern by using multiple -e options + $command + ->add('| grep -v \'^$\'') + ->add('| xargs -I{} grep -I') + ->add($expr->isCaseSensitive() ? null : '-i') + ->add($not ? '-L' : '-l') + ->add('-Ee')->arg($expr->renderPattern()) + ->add('{}') + ; + } + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Adapter/GnuFindAdapter.php b/vendor/symfony/finder/Symfony/Component/Finder/Adapter/GnuFindAdapter.php new file mode 100644 index 0000000..b72451e --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Adapter/GnuFindAdapter.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +use Symfony\Component\Finder\Shell\Shell; +use Symfony\Component\Finder\Shell\Command; +use Symfony\Component\Finder\Iterator\SortableIterator; +use Symfony\Component\Finder\Expression\Expression; + +/** + * Shell engine implementation using GNU find command. + * + * @author Jean-François Simon + */ +class GnuFindAdapter extends AbstractFindAdapter +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'gnu_find'; + } + + /** + * {@inheritdoc} + */ + protected function buildFormatSorting(Command $command, $sort) + { + switch ($sort) { + case SortableIterator::SORT_BY_NAME: + $command->ins('sort')->add('| sort'); + + return; + case SortableIterator::SORT_BY_TYPE: + $format = '%y'; + break; + case SortableIterator::SORT_BY_ACCESSED_TIME: + $format = '%A@'; + break; + case SortableIterator::SORT_BY_CHANGED_TIME: + $format = '%C@'; + break; + case SortableIterator::SORT_BY_MODIFIED_TIME: + $format = '%T@'; + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown sort options: %s.', $sort)); + } + + $command + ->get('find') + ->add('-printf') + ->arg($format.' %h/%f\\n') + ->add('| sort | cut') + ->arg('-d ') + ->arg('-f2-') + ; + } + + /** + * {@inheritdoc} + */ + protected function canBeUsed() + { + return $this->shell->getType() === Shell::TYPE_UNIX && parent::canBeUsed(); + } + + /** + * {@inheritdoc} + */ + protected function buildFindCommand(Command $command, $dir) + { + return parent::buildFindCommand($command, $dir)->add('-regextype posix-extended'); + } + + /** + * {@inheritdoc} + */ + protected function buildContentFiltering(Command $command, array $contains, $not = false) + { + foreach ($contains as $contain) { + $expr = Expression::create($contain); + + // todo: avoid forking process for each $pattern by using multiple -e options + $command + ->add('| xargs -I{} -r grep -I') + ->add($expr->isCaseSensitive() ? null : '-i') + ->add($not ? '-L' : '-l') + ->add('-Ee')->arg($expr->renderPattern()) + ->add('{}') + ; + } + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Adapter/PhpAdapter.php b/vendor/symfony/finder/Symfony/Component/Finder/Adapter/PhpAdapter.php new file mode 100644 index 0000000..378a26a --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Adapter/PhpAdapter.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +use Symfony\Component\Finder\Iterator; + +/** + * PHP finder engine implementation. + * + * @author Jean-François Simon + */ +class PhpAdapter extends AbstractAdapter +{ + /** + * {@inheritdoc} + */ + public function searchInDirectory($dir) + { + $flags = \RecursiveDirectoryIterator::SKIP_DOTS; + + if ($this->followLinks) { + $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; + } + + $iterator = new \RecursiveIteratorIterator( + new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs), + \RecursiveIteratorIterator::SELF_FIRST + ); + + if ($this->minDepth > 0 || $this->maxDepth < PHP_INT_MAX) { + $iterator = new Iterator\DepthRangeFilterIterator($iterator, $this->minDepth, $this->maxDepth); + } + + if ($this->mode) { + $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); + } + + if ($this->exclude) { + $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); + } + + if ($this->names || $this->notNames) { + $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); + } + + if ($this->contains || $this->notContains) { + $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); + } + + if ($this->sizes) { + $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); + } + + if ($this->dates) { + $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); + } + + if ($this->filters) { + $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); + } + + if ($this->sort) { + $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); + $iterator = $iteratorAggregate->getIterator(); + } + + if ($this->paths || $this->notPaths) { + $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $this->notPaths); + } + + return $iterator; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'php'; + } + + /** + * {@inheritdoc} + */ + protected function canBeUsed() + { + return true; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/CHANGELOG.md b/vendor/symfony/finder/Symfony/Component/Finder/CHANGELOG.md new file mode 100644 index 0000000..7ad2308 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/CHANGELOG.md @@ -0,0 +1,30 @@ +CHANGELOG +========= + +2.3.0 +----- + + * added a way to ignore unreadable directories (via Finder::ignoreUnreadableDirs()) + * unified the way subfolders that are not executable are handled by always throwing an AccessDeniedException exception + +2.2.0 +----- + + * added Finder::path() and Finder::notPath() methods + * added finder adapters to improve performance on specific platforms + * added support for wildcard characters (glob patterns) in the paths passed + to Finder::in() + +2.1.0 +----- + + * added Finder::sortByAccessedTime(), Finder::sortByChangedTime(), and + Finder::sortByModifiedTime() + * added Countable to Finder + * added support for an array of directories as an argument to + Finder::exclude() + * added searching based on the file content via Finder::contains() and + Finder::notContains() + * added support for the != operator in the Comparator + * [BC BREAK] filter expressions (used for file name and content) are no more + considered as regexps but glob patterns when they are enclosed in '*' or '?' diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Comparator/Comparator.php b/vendor/symfony/finder/Symfony/Component/Finder/Comparator/Comparator.php new file mode 100644 index 0000000..83c511a --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Comparator/Comparator.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * Comparator. + * + * @author Fabien Potencier + */ +class Comparator +{ + private $target; + private $operator = '=='; + + /** + * Gets the target value. + * + * @return string The target value + */ + public function getTarget() + { + return $this->target; + } + + /** + * Sets the target value. + * + * @param string $target The target value + */ + public function setTarget($target) + { + $this->target = $target; + } + + /** + * Gets the comparison operator. + * + * @return string The operator + */ + public function getOperator() + { + return $this->operator; + } + + /** + * Sets the comparison operator. + * + * @param string $operator A valid operator + * + * @throws \InvalidArgumentException + */ + public function setOperator($operator) + { + if (!$operator) { + $operator = '=='; + } + + if (!in_array($operator, array('>', '<', '>=', '<=', '==', '!='))) { + throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); + } + + $this->operator = $operator; + } + + /** + * Tests against the target. + * + * @param mixed $test A test value + * + * @return Boolean + */ + public function test($test) + { + switch ($this->operator) { + case '>': + return $test > $this->target; + case '>=': + return $test >= $this->target; + case '<': + return $test < $this->target; + case '<=': + return $test <= $this->target; + case '!=': + return $test != $this->target; + } + + return $test == $this->target; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Comparator/DateComparator.php b/vendor/symfony/finder/Symfony/Component/Finder/Comparator/DateComparator.php new file mode 100644 index 0000000..9de444f --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Comparator/DateComparator.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * DateCompare compiles date comparisons. + * + * @author Fabien Potencier + */ +class DateComparator extends Comparator +{ + + /** + * Constructor. + * + * @param string $test A comparison string + * + * @throws \InvalidArgumentException If the test is not understood + */ + public function __construct($test) + { + if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) { + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test)); + } + + try { + $date = new \DateTime($matches[2]); + $target = $date->format('U'); + } catch (\Exception $e) { + throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); + } + + $operator = isset($matches[1]) ? $matches[1] : '=='; + if ('since' === $operator || 'after' === $operator) { + $operator = '>'; + } + + if ('until' === $operator || 'before' === $operator) { + $operator = '<'; + } + + $this->setOperator($operator); + $this->setTarget($target); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Comparator/NumberComparator.php b/vendor/symfony/finder/Symfony/Component/Finder/Comparator/NumberComparator.php new file mode 100644 index 0000000..955cc67 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Comparator/NumberComparator.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * NumberComparator compiles a simple comparison to an anonymous + * subroutine, which you can call with a value to be tested again. + * + * Now this would be very pointless, if NumberCompare didn't understand + * magnitudes. + * + * The target value may use magnitudes of kilobytes (k, ki), + * megabytes (m, mi), or gigabytes (g, gi). Those suffixed + * with an i use the appropriate 2**n version in accordance with the + * IEC standard: http://physics.nist.gov/cuu/Units/binary.html + * + * Based on the Perl Number::Compare module. + * + * @author Fabien Potencier PHP port + * @author Richard Clamp Perl version + * + * @copyright 2004-2005 Fabien Potencier + * @copyright 2002 Richard Clamp + * + * @see http://physics.nist.gov/cuu/Units/binary.html + */ +class NumberComparator extends Comparator +{ + /** + * Constructor. + * + * @param string $test A comparison string + * + * @throws \InvalidArgumentException If the test is not understood + */ + public function __construct($test) + { + if (!preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) { + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test)); + } + + $target = $matches[2]; + if (!is_numeric($target)) { + throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target)); + } + if (isset($matches[3])) { + // magnitude + switch (strtolower($matches[3])) { + case 'k': + $target *= 1000; + break; + case 'ki': + $target *= 1024; + break; + case 'm': + $target *= 1000000; + break; + case 'mi': + $target *= 1024*1024; + break; + case 'g': + $target *= 1000000000; + break; + case 'gi': + $target *= 1024*1024*1024; + break; + } + } + + $this->setTarget($target); + $this->setOperator(isset($matches[1]) ? $matches[1] : '=='); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Exception/AccessDeniedException.php b/vendor/symfony/finder/Symfony/Component/Finder/Exception/AccessDeniedException.php new file mode 100644 index 0000000..ee195ea --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Exception/AccessDeniedException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +/** + * @author Jean-François Simon + */ +class AccessDeniedException extends \UnexpectedValueException +{ +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Exception/AdapterFailureException.php b/vendor/symfony/finder/Symfony/Component/Finder/Exception/AdapterFailureException.php new file mode 100644 index 0000000..15fa221 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Exception/AdapterFailureException.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +use Symfony\Component\Finder\Adapter\AdapterInterface; + +/** + * Base exception for all adapter failures. + * + * @author Jean-François Simon + */ +class AdapterFailureException extends \RuntimeException implements ExceptionInterface +{ + /** + * @var \Symfony\Component\Finder\Adapter\AdapterInterface + */ + private $adapter; + + /** + * @param AdapterInterface $adapter + * @param string|null $message + * @param \Exception|null $previous + */ + public function __construct(AdapterInterface $adapter, $message = null, \Exception $previous = null) + { + $this->adapter = $adapter; + parent::__construct($message ?: 'Search failed with "'.$adapter->getName().'" adapter.', $previous); + } + + /** + * {@inheritdoc} + */ + public function getAdapter() + { + return $this->adapter; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Exception/ExceptionInterface.php b/vendor/symfony/finder/Symfony/Component/Finder/Exception/ExceptionInterface.php new file mode 100644 index 0000000..bff0214 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Exception/ExceptionInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +/** + * @author Jean-François Simon + */ +interface ExceptionInterface +{ + /** + * @return \Symfony\Component\Finder\Adapter\AdapterInterface + */ + public function getAdapter(); +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Exception/OperationNotPermitedException.php b/vendor/symfony/finder/Symfony/Component/Finder/Exception/OperationNotPermitedException.php new file mode 100644 index 0000000..3663112 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Exception/OperationNotPermitedException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +/** + * @author Jean-François Simon + */ +class OperationNotPermitedException extends AdapterFailureException +{ +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Exception/ShellCommandFailureException.php b/vendor/symfony/finder/Symfony/Component/Finder/Exception/ShellCommandFailureException.php new file mode 100644 index 0000000..2658f6a --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Exception/ShellCommandFailureException.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +use Symfony\Component\Finder\Adapter\AdapterInterface; +use Symfony\Component\Finder\Shell\Command; + +/** + * @author Jean-François Simon + */ +class ShellCommandFailureException extends AdapterFailureException +{ + /** + * @var Command + */ + private $command; + + /** + * @param AdapterInterface $adapter + * @param Command $command + * @param \Exception|null $previous + */ + public function __construct(AdapterInterface $adapter, Command $command, \Exception $previous = null) + { + $this->command = $command; + parent::__construct($adapter, 'Shell command failed: "'.$command->join().'".', $previous); + } + + /** + * @return Command + */ + public function getCommand() + { + return $this->command; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Expression/Expression.php b/vendor/symfony/finder/Symfony/Component/Finder/Expression/Expression.php new file mode 100644 index 0000000..ceedbc1 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Expression/Expression.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Expression; + +/** + * @author Jean-François Simon + */ +class Expression implements ValueInterface +{ + const TYPE_REGEX = 1; + const TYPE_GLOB = 2; + + /** + * @var ValueInterface + */ + private $value; + + /** + * @param string $expr + * + * @return Expression + */ + public static function create($expr) + { + return new self($expr); + } + + /** + * @param string $expr + */ + public function __construct($expr) + { + try { + $this->value = Regex::create($expr); + } catch (\InvalidArgumentException $e) { + $this->value = new Glob($expr); + } + } + + /** + * @return string + */ + public function __toString() + { + return $this->render(); + } + + /** + * {@inheritdoc} + */ + public function render() + { + return $this->value->render(); + } + + /** + * {@inheritdoc} + */ + public function renderPattern() + { + return $this->value->renderPattern(); + } + + /** + * @return bool + */ + public function isCaseSensitive() + { + return $this->value->isCaseSensitive(); + } + + /** + * @return int + */ + public function getType() + { + return $this->value->getType(); + } + + /** + * {@inheritdoc} + */ + public function prepend($expr) + { + $this->value->prepend($expr); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function append($expr) + { + $this->value->append($expr); + + return $this; + } + + /** + * @return bool + */ + public function isRegex() + { + return self::TYPE_REGEX === $this->value->getType(); + } + + /** + * @return bool + */ + public function isGlob() + { + return self::TYPE_GLOB === $this->value->getType(); + } + + /** + * @throws \LogicException + * + * @return Glob + */ + public function getGlob() + { + if (self::TYPE_GLOB !== $this->value->getType()) { + throw new \LogicException('Regex cant be transformed to glob.'); + } + + return $this->value; + } + + /** + * @return Regex + */ + public function getRegex() + { + return self::TYPE_REGEX === $this->value->getType() ? $this->value : $this->value->toRegex(); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Expression/Glob.php b/vendor/symfony/finder/Symfony/Component/Finder/Expression/Glob.php new file mode 100644 index 0000000..3023cee --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Expression/Glob.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Expression; + +/** + * @author Jean-François Simon + */ +class Glob implements ValueInterface +{ + /** + * @var string + */ + private $pattern; + + /** + * @param string $pattern + */ + public function __construct($pattern) + { + $this->pattern = $pattern; + } + + /** + * {@inheritdoc} + */ + public function render() + { + return $this->pattern; + } + + /** + * {@inheritdoc} + */ + public function renderPattern() + { + return $this->pattern; + } + + /** + * {@inheritdoc} + */ + public function getType() + { + return Expression::TYPE_GLOB; + } + + /** + * {@inheritdoc} + */ + public function isCaseSensitive() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function prepend($expr) + { + $this->pattern = $expr.$this->pattern; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function append($expr) + { + $this->pattern .= $expr; + + return $this; + } + + /** + * Tests if glob is expandable ("*.{a,b}" syntax). + * + * @return bool + */ + public function isExpandable() + { + return false !== strpos($this->pattern, '{') + && false !== strpos($this->pattern, '}'); + } + + /** + * @param bool $strictLeadingDot + * @param bool $strictWildcardSlash + * + * @return Regex + */ + public function toRegex($strictLeadingDot = true, $strictWildcardSlash = true) + { + $firstByte = true; + $escaping = false; + $inCurlies = 0; + $regex = ''; + $sizeGlob = strlen($this->pattern); + for ($i = 0; $i < $sizeGlob; $i++) { + $car = $this->pattern[$i]; + if ($firstByte) { + if ($strictLeadingDot && '.' !== $car) { + $regex .= '(?=[^\.])'; + } + + $firstByte = false; + } + + if ('/' === $car) { + $firstByte = true; + } + + if ('.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) { + $regex .= "\\$car"; + } elseif ('*' === $car) { + $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); + } elseif ('?' === $car) { + $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.'); + } elseif ('{' === $car) { + $regex .= $escaping ? '\\{' : '('; + if (!$escaping) { + ++$inCurlies; + } + } elseif ('}' === $car && $inCurlies) { + $regex .= $escaping ? '}' : ')'; + if (!$escaping) { + --$inCurlies; + } + } elseif (',' === $car && $inCurlies) { + $regex .= $escaping ? ',' : '|'; + } elseif ('\\' === $car) { + if ($escaping) { + $regex .= '\\\\'; + $escaping = false; + } else { + $escaping = true; + } + + continue; + } else { + $regex .= $car; + } + $escaping = false; + } + + return new Regex('^'.$regex.'$'); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Expression/Regex.php b/vendor/symfony/finder/Symfony/Component/Finder/Expression/Regex.php new file mode 100644 index 0000000..05544d0 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Expression/Regex.php @@ -0,0 +1,317 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Expression; + +/** + * @author Jean-François Simon + */ +class Regex implements ValueInterface +{ + const START_FLAG = '^'; + const END_FLAG = '$'; + const BOUNDARY = '~'; + const JOKER = '.*'; + const ESCAPING = '\\'; + + /** + * @var string + */ + private $pattern; + + /** + * @var array + */ + private $options; + + /** + * @var bool + */ + private $startFlag; + + /** + * @var bool + */ + private $endFlag; + + /** + * @var bool + */ + private $startJoker; + + /** + * @var bool + */ + private $endJoker; + + /** + * @param string $expr + * + * @return Regex + * + * @throws \InvalidArgumentException + */ + public static function create($expr) + { + if (preg_match('/^(.{3,}?)([imsxuADU]*)$/', $expr, $m)) { + $start = substr($m[1], 0, 1); + $end = substr($m[1], -1); + + if (($start === $end && !preg_match('/[*?[:alnum:] \\\\]/', $start)) || ($start === '{' && $end === '}')) { + return new self(substr($m[1], 1, -1), $m[2], $end); + } + } + + throw new \InvalidArgumentException('Given expression is not a regex.'); + } + + /** + * @param string $pattern + * @param string $options + * @param string $delimiter + */ + public function __construct($pattern, $options = '', $delimiter = null) + { + if (null !== $delimiter) { + // removes delimiter escaping + $pattern = str_replace('\\'.$delimiter, $delimiter, $pattern); + } + + $this->parsePattern($pattern); + $this->options = $options; + } + + /** + * @return string + */ + public function __toString() + { + return $this->render(); + } + + /** + * {@inheritdoc} + */ + public function render() + { + return self::BOUNDARY + .$this->renderPattern() + .self::BOUNDARY + .$this->options; + } + + /** + * {@inheritdoc} + */ + public function renderPattern() + { + return ($this->startFlag ? self::START_FLAG : '') + .($this->startJoker ? self::JOKER : '') + .str_replace(self::BOUNDARY, '\\'.self::BOUNDARY, $this->pattern) + .($this->endJoker ? self::JOKER : '') + .($this->endFlag ? self::END_FLAG : ''); + } + + /** + * {@inheritdoc} + */ + public function isCaseSensitive() + { + return !$this->hasOption('i'); + } + + /** + * {@inheritdoc} + */ + public function getType() + { + return Expression::TYPE_REGEX; + } + + /** + * {@inheritdoc} + */ + public function prepend($expr) + { + $this->pattern = $expr.$this->pattern; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function append($expr) + { + $this->pattern .= $expr; + + return $this; + } + + /** + * @param string $option + * + * @return bool + */ + public function hasOption($option) + { + return false !== strpos($this->options, $option); + } + + /** + * @param string $option + * + * @return Regex + */ + public function addOption($option) + { + if (!$this->hasOption($option)) { + $this->options.= $option; + } + + return $this; + } + + /** + * @param string $option + * + * @return Regex + */ + public function removeOption($option) + { + $this->options = str_replace($option, '', $this->options); + + return $this; + } + + /** + * @param bool $startFlag + * + * @return Regex + */ + public function setStartFlag($startFlag) + { + $this->startFlag = $startFlag; + + return $this; + } + + /** + * @return bool + */ + public function hasStartFlag() + { + return $this->startFlag; + } + + /** + * @param bool $endFlag + * + * @return Regex + */ + public function setEndFlag($endFlag) + { + $this->endFlag = (bool) $endFlag; + + return $this; + } + + /** + * @return bool + */ + public function hasEndFlag() + { + return $this->endFlag; + } + + /** + * @param bool $startJoker + * + * @return Regex + */ + public function setStartJoker($startJoker) + { + $this->startJoker = $startJoker; + + return $this; + } + + /** + * @return bool + */ + public function hasStartJoker() + { + return $this->startJoker; + } + + /** + * @param bool $endJoker + * + * @return Regex + */ + public function setEndJoker($endJoker) + { + $this->endJoker = (bool) $endJoker; + + return $this; + } + + /** + * @return bool + */ + public function hasEndJoker() + { + return $this->endJoker; + } + + /** + * @param array $replacement + * + * @return Regex + */ + public function replaceJokers($replacement) + { + $replace = function ($subject) use ($replacement) { + $subject = $subject[0]; + $replace = 0 === substr_count($subject, '\\') % 2; + + return $replace ? str_replace('.', $replacement, $subject) : $subject; + }; + + $this->pattern = preg_replace_callback('~[\\\\]*\\.~', $replace, $this->pattern); + + return $this; + } + + /** + * @param string $pattern + */ + private function parsePattern($pattern) + { + if ($this->startFlag = self::START_FLAG === substr($pattern, 0, 1)) { + $pattern = substr($pattern, 1); + } + + if ($this->startJoker = self::JOKER === substr($pattern, 0, 2)) { + $pattern = substr($pattern, 2); + } + + if ($this->endFlag = (self::END_FLAG === substr($pattern, -1) && self::ESCAPING !== substr($pattern, -2, -1))) { + $pattern = substr($pattern, 0, -1); + } + + if ($this->endJoker = (self::JOKER === substr($pattern, -2) && self::ESCAPING !== substr($pattern, -3, -2))) { + $pattern = substr($pattern, 0, -2); + } + + $this->pattern = $pattern; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Expression/ValueInterface.php b/vendor/symfony/finder/Symfony/Component/Finder/Expression/ValueInterface.php new file mode 100644 index 0000000..34ce0e7 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Expression/ValueInterface.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Expression; + +/** + * @author Jean-François Simon + */ +interface ValueInterface +{ + /** + * Renders string representation of expression. + * + * @return string + */ + public function render(); + + /** + * Renders string representation of pattern. + * + * @return string + */ + public function renderPattern(); + + /** + * Returns value case sensitivity. + * + * @return bool + */ + public function isCaseSensitive(); + + /** + * Returns expression type. + * + * @return int + */ + public function getType(); + + /** + * @param string $expr + * + * @return ValueInterface + */ + public function prepend($expr); + + /** + * @param string $expr + * + * @return ValueInterface + */ + public function append($expr); +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Finder.php b/vendor/symfony/finder/Symfony/Component/Finder/Finder.php new file mode 100644 index 0000000..43a8643 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Finder.php @@ -0,0 +1,829 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +use Symfony\Component\Finder\Adapter\AdapterInterface; +use Symfony\Component\Finder\Adapter\GnuFindAdapter; +use Symfony\Component\Finder\Adapter\BsdFindAdapter; +use Symfony\Component\Finder\Adapter\PhpAdapter; +use Symfony\Component\Finder\Exception\ExceptionInterface; + +/** + * Finder allows to build rules to find files and directories. + * + * It is a thin wrapper around several specialized iterator classes. + * + * All rules may be invoked several times. + * + * All methods return the current Finder object to allow easy chaining: + * + * $finder = Finder::create()->files()->name('*.php')->in(__DIR__); + * + * @author Fabien Potencier + * + * @api + */ +class Finder implements \IteratorAggregate, \Countable +{ + const IGNORE_VCS_FILES = 1; + const IGNORE_DOT_FILES = 2; + + private $mode = 0; + private $names = array(); + private $notNames = array(); + private $exclude = array(); + private $filters = array(); + private $depths = array(); + private $sizes = array(); + private $followLinks = false; + private $sort = false; + private $ignore = 0; + private $dirs = array(); + private $dates = array(); + private $iterators = array(); + private $contains = array(); + private $notContains = array(); + private $adapters = array(); + private $paths = array(); + private $notPaths = array(); + private $ignoreUnreadableDirs = false; + + private static $vcsPatterns = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'); + + /** + * Constructor. + */ + public function __construct() + { + $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; + + $this + ->addAdapter(new GnuFindAdapter()) + ->addAdapter(new BsdFindAdapter()) + ->addAdapter(new PhpAdapter(), -50) + ->setAdapter('php') + ; + } + + /** + * Creates a new Finder. + * + * @return Finder A new Finder instance + * + * @api + */ + public static function create() + { + return new static(); + } + + /** + * Registers a finder engine implementation. + * + * @param AdapterInterface $adapter An adapter instance + * @param integer $priority Highest is selected first + * + * @return Finder The current Finder instance + */ + public function addAdapter(Adapter\AdapterInterface $adapter, $priority = 0) + { + $this->adapters[$adapter->getName()] = array( + 'adapter' => $adapter, + 'priority' => $priority, + 'selected' => false, + ); + + return $this->sortAdapters(); + } + + /** + * Sets the selected adapter to the best one according to the current platform the code is run on. + * + * @return Finder The current Finder instance + */ + public function useBestAdapter() + { + $this->resetAdapterSelection(); + + return $this->sortAdapters(); + } + + /** + * Selects the adapter to use. + * + * @param string $name + * + * @throws \InvalidArgumentException + * + * @return Finder The current Finder instance + */ + public function setAdapter($name) + { + if (!isset($this->adapters[$name])) { + throw new \InvalidArgumentException(sprintf('Adapter "%s" does not exist.', $name)); + } + + $this->resetAdapterSelection(); + $this->adapters[$name]['selected'] = true; + + return $this->sortAdapters(); + } + + /** + * Removes all adapters registered in the finder. + * + * @return Finder The current Finder instance + */ + public function removeAdapters() + { + $this->adapters = array(); + + return $this; + } + + /** + * Returns registered adapters ordered by priority without extra information. + * + * @return AdapterInterface[] + */ + public function getAdapters() + { + return array_values(array_map(function(array $adapter) { + return $adapter['adapter']; + }, $this->adapters)); + } + + /** + * Restricts the matching to directories only. + * + * @return Finder The current Finder instance + * + * @api + */ + public function directories() + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; + + return $this; + } + + /** + * Restricts the matching to files only. + * + * @return Finder The current Finder instance + * + * @api + */ + public function files() + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; + + return $this; + } + + /** + * Adds tests for the directory depth. + * + * Usage: + * + * $finder->depth('> 1') // the Finder will start matching at level 1. + * $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point. + * + * @param int $level The depth level expression + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\DepthRangeFilterIterator + * @see Symfony\Component\Finder\Comparator\NumberComparator + * + * @api + */ + public function depth($level) + { + $this->depths[] = new Comparator\NumberComparator($level); + + return $this; + } + + /** + * Adds tests for file dates (last modified). + * + * The date must be something that strtotime() is able to parse: + * + * $finder->date('since yesterday'); + * $finder->date('until 2 days ago'); + * $finder->date('> now - 2 hours'); + * $finder->date('>= 2005-10-15'); + * + * @param string $date A date rage string + * + * @return Finder The current Finder instance + * + * @see strtotime + * @see Symfony\Component\Finder\Iterator\DateRangeFilterIterator + * @see Symfony\Component\Finder\Comparator\DateComparator + * + * @api + */ + public function date($date) + { + $this->dates[] = new Comparator\DateComparator($date); + + return $this; + } + + /** + * Adds rules that files must match. + * + * You can use patterns (delimited with / sign), globs or simple strings. + * + * $finder->name('*.php') + * $finder->name('/\.php$/') // same as above + * $finder->name('test.php') + * + * @param string $pattern A pattern (a regexp, a glob, or a string) + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\FilenameFilterIterator + * + * @api + */ + public function name($pattern) + { + $this->names[] = $pattern; + + return $this; + } + + /** + * Adds rules that files must not match. + * + * @param string $pattern A pattern (a regexp, a glob, or a string) + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\FilenameFilterIterator + * + * @api + */ + public function notName($pattern) + { + $this->notNames[] = $pattern; + + return $this; + } + + /** + * Adds tests that file contents must match. + * + * Strings or PCRE patterns can be used: + * + * $finder->contains('Lorem ipsum') + * $finder->contains('/Lorem ipsum/i') + * + * @param string $pattern A pattern (string or regexp) + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\FilecontentFilterIterator + */ + public function contains($pattern) + { + $this->contains[] = $pattern; + + return $this; + } + + /** + * Adds tests that file contents must not match. + * + * Strings or PCRE patterns can be used: + * + * $finder->notContains('Lorem ipsum') + * $finder->notContains('/Lorem ipsum/i') + * + * @param string $pattern A pattern (string or regexp) + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\FilecontentFilterIterator + */ + public function notContains($pattern) + { + $this->notContains[] = $pattern; + + return $this; + } + + /** + * Adds rules that filenames must match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->path('some/special/dir') + * $finder->path('/some\/special\/dir/') // same as above + * + * Use only / as dirname separator. + * + * @param string $pattern A pattern (a regexp or a string) + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\FilenameFilterIterator + */ + public function path($pattern) + { + $this->paths[] = $pattern; + + return $this; + } + + /** + * Adds rules that filenames must not match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->notPath('some/special/dir') + * $finder->notPath('/some\/special\/dir/') // same as above + * + * Use only / as dirname separator. + * + * @param string $pattern A pattern (a regexp or a string) + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\FilenameFilterIterator + */ + public function notPath($pattern) + { + $this->notPaths[] = $pattern; + + return $this; + } + + /** + * Adds tests for file sizes. + * + * $finder->size('> 10K'); + * $finder->size('<= 1Ki'); + * $finder->size(4); + * + * @param string $size A size range string + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\SizeRangeFilterIterator + * @see Symfony\Component\Finder\Comparator\NumberComparator + * + * @api + */ + public function size($size) + { + $this->sizes[] = new Comparator\NumberComparator($size); + + return $this; + } + + /** + * Excludes directories. + * + * @param string|array $dirs A directory path or an array of directories + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator + * + * @api + */ + public function exclude($dirs) + { + $this->exclude = array_merge($this->exclude, (array) $dirs); + + return $this; + } + + /** + * Excludes "hidden" directories and files (starting with a dot). + * + * @param Boolean $ignoreDotFiles Whether to exclude "hidden" files or not + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator + * + * @api + */ + public function ignoreDotFiles($ignoreDotFiles) + { + if ($ignoreDotFiles) { + $this->ignore = $this->ignore | static::IGNORE_DOT_FILES; + } else { + $this->ignore = $this->ignore & ~static::IGNORE_DOT_FILES; + } + + return $this; + } + + /** + * Forces the finder to ignore version control directories. + * + * @param Boolean $ignoreVCS Whether to exclude VCS files or not + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator + * + * @api + */ + public function ignoreVCS($ignoreVCS) + { + if ($ignoreVCS) { + $this->ignore = $this->ignore | static::IGNORE_VCS_FILES; + } else { + $this->ignore = $this->ignore & ~static::IGNORE_VCS_FILES; + } + + return $this; + } + + /** + * Adds VCS patterns. + * + * @see ignoreVCS + * + * @param string|string[] $pattern VCS patterns to ignore + */ + public static function addVCSPattern($pattern) + { + foreach ((array) $pattern as $p) { + self::$vcsPatterns[] = $p; + } + + self::$vcsPatterns = array_unique(self::$vcsPatterns); + } + + /** + * Sorts files and directories by an anonymous function. + * + * The anonymous function receives two \SplFileInfo instances to compare. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @param \Closure $closure An anonymous function + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\SortableIterator + * + * @api + */ + public function sort(\Closure $closure) + { + $this->sort = $closure; + + return $this; + } + + /** + * Sorts files and directories by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\SortableIterator + * + * @api + */ + public function sortByName() + { + $this->sort = Iterator\SortableIterator::SORT_BY_NAME; + + return $this; + } + + /** + * Sorts files and directories by type (directories before files), then by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\SortableIterator + * + * @api + */ + public function sortByType() + { + $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; + + return $this; + } + + /** + * Sorts files and directories by the last accessed time. + * + * This is the time that the file was last accessed, read or written to. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\SortableIterator + * + * @api + */ + public function sortByAccessedTime() + { + $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; + + return $this; + } + + /** + * Sorts files and directories by the last inode changed time. + * + * This is the time that the inode information was last modified (permissions, owner, group or other metadata). + * + * On Windows, since inode is not available, changed time is actually the file creation time. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\SortableIterator + * + * @api + */ + public function sortByChangedTime() + { + $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; + + return $this; + } + + /** + * Sorts files and directories by the last modified time. + * + * This is the last time the actual contents of the file were last modified. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\SortableIterator + * + * @api + */ + public function sortByModifiedTime() + { + $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; + + return $this; + } + + /** + * Filters the iterator with an anonymous function. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @param \Closure $closure An anonymous function + * + * @return Finder The current Finder instance + * + * @see Symfony\Component\Finder\Iterator\CustomFilterIterator + * + * @api + */ + public function filter(\Closure $closure) + { + $this->filters[] = $closure; + + return $this; + } + + /** + * Forces the following of symlinks. + * + * @return Finder The current Finder instance + * + * @api + */ + public function followLinks() + { + $this->followLinks = true; + + return $this; + } + + /** + * Tells finder to ignore unreadable directories. + * + * By default, scanning unreadable directories content throws an AccessDeniedException. + * + * @param boolean $ignore + * + * @return Finder The current Finder instance + */ + public function ignoreUnreadableDirs($ignore = true) + { + $this->ignoreUnreadableDirs = (Boolean) $ignore; + + return $this; + } + + /** + * Searches files and directories which match defined rules. + * + * @param string|array $dirs A directory path or an array of directories + * + * @return Finder The current Finder instance + * + * @throws \InvalidArgumentException if one of the directories does not exist + * + * @api + */ + public function in($dirs) + { + $resolvedDirs = array(); + + foreach ((array) $dirs as $dir) { + if (is_dir($dir)) { + $resolvedDirs[] = $dir; + } elseif ($glob = glob($dir, GLOB_ONLYDIR)) { + $resolvedDirs = array_merge($resolvedDirs, $glob); + } else { + throw new \InvalidArgumentException(sprintf('The "%s" directory does not exist.', $dir)); + } + } + + $this->dirs = array_merge($this->dirs, $resolvedDirs); + + return $this; + } + + /** + * Returns an Iterator for the current Finder configuration. + * + * This method implements the IteratorAggregate interface. + * + * @return \Iterator An iterator + * + * @throws \LogicException if the in() method has not been called + */ + public function getIterator() + { + if (0 === count($this->dirs) && 0 === count($this->iterators)) { + throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); + } + + if (1 === count($this->dirs) && 0 === count($this->iterators)) { + return $this->searchInDirectory($this->dirs[0]); + } + + $iterator = new \AppendIterator(); + foreach ($this->dirs as $dir) { + $iterator->append($this->searchInDirectory($dir)); + } + + foreach ($this->iterators as $it) { + $iterator->append($it); + } + + return $iterator; + } + + /** + * Appends an existing set of files/directories to the finder. + * + * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array. + * + * @param mixed $iterator + * + * @return Finder The finder + * + * @throws \InvalidArgumentException When the given argument is not iterable. + */ + public function append($iterator) + { + if ($iterator instanceof \IteratorAggregate) { + $this->iterators[] = $iterator->getIterator(); + } elseif ($iterator instanceof \Iterator) { + $this->iterators[] = $iterator; + } elseif ($iterator instanceof \Traversable || is_array($iterator)) { + $it = new \ArrayIterator(); + foreach ($iterator as $file) { + $it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file)); + } + $this->iterators[] = $it; + } else { + throw new \InvalidArgumentException('Finder::append() method wrong argument type.'); + } + + return $this; + } + + /** + * Counts all the results collected by the iterators. + * + * @return int + */ + public function count() + { + return iterator_count($this->getIterator()); + } + + /** + * @return Finder The current Finder instance + */ + private function sortAdapters() + { + uasort($this->adapters, function (array $a, array $b) { + if ($a['selected'] || $b['selected']) { + return $a['selected'] ? -1 : 1; + } + + return $a['priority'] > $b['priority'] ? -1 : 1; + }); + + return $this; + } + + /** + * @param $dir + * + * @return \Iterator + * + * @throws \RuntimeException When none of the adapters are supported + */ + private function searchInDirectory($dir) + { + if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { + $this->exclude = array_merge($this->exclude, self::$vcsPatterns); + } + + if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) { + $this->notPaths[] = '#(^|/)\..+(/|$)#'; + } + + foreach ($this->adapters as $adapter) { + if ($adapter['adapter']->isSupported()) { + try { + return $this + ->buildAdapter($adapter['adapter']) + ->searchInDirectory($dir); + } catch (ExceptionInterface $e) {} + } + } + + throw new \RuntimeException('No supported adapter found.'); + } + + /** + * @param AdapterInterface $adapter + * + * @return AdapterInterface + */ + private function buildAdapter(AdapterInterface $adapter) + { + return $adapter + ->setFollowLinks($this->followLinks) + ->setDepths($this->depths) + ->setMode($this->mode) + ->setExclude($this->exclude) + ->setNames($this->names) + ->setNotNames($this->notNames) + ->setContains($this->contains) + ->setNotContains($this->notContains) + ->setSizes($this->sizes) + ->setDates($this->dates) + ->setFilters($this->filters) + ->setSort($this->sort) + ->setPath($this->paths) + ->setNotPath($this->notPaths) + ->ignoreUnreadableDirs($this->ignoreUnreadableDirs); + } + + /** + * Unselects all adapters. + */ + private function resetAdapterSelection() + { + $this->adapters = array_map(function (array $properties) { + $properties['selected'] = false; + + return $properties; + }, $this->adapters); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Glob.php b/vendor/symfony/finder/Symfony/Component/Finder/Glob.php new file mode 100644 index 0000000..dd26384 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Glob.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Glob matches globbing patterns against text. + * + * if match_glob("foo.*", "foo.bar") echo "matched\n"; + * + * // prints foo.bar and foo.baz + * $regex = glob_to_regex("foo.*"); + * for (array('foo.bar', 'foo.baz', 'foo', 'bar') as $t) + * { + * if (/$regex/) echo "matched: $car\n"; + * } + * + * Glob implements glob(3) style matching that can be used to match + * against text, rather than fetching names from a filesystem. + * + * Based on the Perl Text::Glob module. + * + * @author Fabien Potencier PHP port + * @author Richard Clamp Perl version + * @copyright 2004-2005 Fabien Potencier + * @copyright 2002 Richard Clamp + */ +class Glob +{ + /** + * Returns a regexp which is the equivalent of the glob pattern. + * + * @param string $glob The glob pattern + * @param Boolean $strictLeadingDot + * @param Boolean $strictWildcardSlash + * + * @return string regex The regexp + */ + public static function toRegex($glob, $strictLeadingDot = true, $strictWildcardSlash = true) + { + $firstByte = true; + $escaping = false; + $inCurlies = 0; + $regex = ''; + $sizeGlob = strlen($glob); + for ($i = 0; $i < $sizeGlob; $i++) { + $car = $glob[$i]; + if ($firstByte) { + if ($strictLeadingDot && '.' !== $car) { + $regex .= '(?=[^\.])'; + } + + $firstByte = false; + } + + if ('/' === $car) { + $firstByte = true; + } + + if ('.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) { + $regex .= "\\$car"; + } elseif ('*' === $car) { + $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); + } elseif ('?' === $car) { + $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.'); + } elseif ('{' === $car) { + $regex .= $escaping ? '\\{' : '('; + if (!$escaping) { + ++$inCurlies; + } + } elseif ('}' === $car && $inCurlies) { + $regex .= $escaping ? '}' : ')'; + if (!$escaping) { + --$inCurlies; + } + } elseif (',' === $car && $inCurlies) { + $regex .= $escaping ? ',' : '|'; + } elseif ('\\' === $car) { + if ($escaping) { + $regex .= '\\\\'; + $escaping = false; + } else { + $escaping = true; + } + + continue; + } else { + $regex .= $car; + } + $escaping = false; + } + + return '#^'.$regex.'$#'; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Iterator/CustomFilterIterator.php b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/CustomFilterIterator.php new file mode 100644 index 0000000..58976c0 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/CustomFilterIterator.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * CustomFilterIterator filters files by applying anonymous functions. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @author Fabien Potencier + */ +class CustomFilterIterator extends FilterIterator +{ + private $filters = array(); + + /** + * Constructor. + * + * @param \Iterator $iterator The Iterator to filter + * @param array $filters An array of PHP callbacks + * + * @throws \InvalidArgumentException + */ + public function __construct(\Iterator $iterator, array $filters) + { + foreach ($filters as $filter) { + if (!is_callable($filter)) { + throw new \InvalidArgumentException('Invalid PHP callback.'); + } + } + $this->filters = $filters; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->current(); + + foreach ($this->filters as $filter) { + if (false === call_user_func($filter, $fileinfo)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Iterator/DateRangeFilterIterator.php b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/DateRangeFilterIterator.php new file mode 100644 index 0000000..3e5713b --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/DateRangeFilterIterator.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Comparator\DateComparator; + +/** + * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates). + * + * @author Fabien Potencier + */ +class DateRangeFilterIterator extends FilterIterator +{ + private $comparators = array(); + + /** + * Constructor. + * + * @param \Iterator $iterator The Iterator to filter + * @param DateComparator[] $comparators An array of DateComparator instances + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->current(); + + if (!$fileinfo->isFile()) { + return true; + } + + $filedate = $fileinfo->getMTime(); + foreach ($this->comparators as $compare) { + if (!$compare->test($filedate)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Iterator/DepthRangeFilterIterator.php b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/DepthRangeFilterIterator.php new file mode 100644 index 0000000..67cf5de --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/DepthRangeFilterIterator.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * DepthRangeFilterIterator limits the directory depth. + * + * @author Fabien Potencier + */ +class DepthRangeFilterIterator extends FilterIterator +{ + private $minDepth = 0; + + /** + * Constructor. + * + * @param \RecursiveIteratorIterator $iterator The Iterator to filter + * @param int $minDepth The min depth + * @param int $maxDepth The max depth + */ + public function __construct(\RecursiveIteratorIterator $iterator, $minDepth = 0, $maxDepth = PHP_INT_MAX) + { + $this->minDepth = $minDepth; + $iterator->setMaxDepth(PHP_INT_MAX === $maxDepth ? -1 : $maxDepth); + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + return $this->getInnerIterator()->getDepth() >= $this->minDepth; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Iterator/ExcludeDirectoryFilterIterator.php b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/ExcludeDirectoryFilterIterator.php new file mode 100644 index 0000000..42c8ce7 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/ExcludeDirectoryFilterIterator.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * ExcludeDirectoryFilterIterator filters out directories. + * + * @author Fabien Potencier + */ +class ExcludeDirectoryFilterIterator extends FilterIterator +{ + private $patterns; + + /** + * Constructor. + * + * @param \Iterator $iterator The Iterator to filter + * @param array $directories An array of directories to exclude + */ + public function __construct(\Iterator $iterator, array $directories) + { + $this->patterns = array(); + foreach ($directories as $directory) { + $this->patterns[] = '#(^|/)'.preg_quote($directory, '#').'(/|$)#'; + } + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath(); + $path = strtr($path, '\\', '/'); + foreach ($this->patterns as $pattern) { + if (preg_match($pattern, $path)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Iterator/FilePathsIterator.php b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/FilePathsIterator.php new file mode 100644 index 0000000..48634f2 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/FilePathsIterator.php @@ -0,0 +1,131 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\SplFileInfo; + +/** + * Iterate over shell command result. + * + * @author Jean-François Simon + */ +class FilePathsIterator extends \ArrayIterator +{ + /** + * @var string + */ + private $baseDir; + + /** + * @var int + */ + private $baseDirLength; + + /** + * @var string + */ + private $subPath; + + /** + * @var string + */ + private $subPathname; + + /** + * @var SplFileInfo + */ + private $current; + + /** + * @param array $paths List of paths returned by shell command + * @param string $baseDir Base dir for relative path building + */ + public function __construct(array $paths, $baseDir) + { + $this->baseDir = $baseDir; + $this->baseDirLength = strlen($baseDir); + + parent::__construct($paths); + } + + /** + * @param string $name + * @param array $arguments + * + * @return mixed + */ + public function __call($name, array $arguments) + { + return call_user_func_array(array($this->current(), $name), $arguments); + } + + /** + * Return an instance of SplFileInfo with support for relative paths. + * + * @return SplFileInfo File information + */ + public function current() + { + return $this->current; + } + + /** + * @return string + */ + public function key() + { + return $this->current->getPathname(); + } + + public function next() + { + parent::next(); + $this->buildProperties(); + } + + public function rewind() + { + parent::rewind(); + $this->buildProperties(); + } + + /** + * @return string + */ + public function getSubPath() + { + return $this->subPath; + } + + /** + * @return string + */ + public function getSubPathname() + { + return $this->subPathname; + } + + private function buildProperties() + { + $absolutePath = parent::current(); + + if ($this->baseDir === substr($absolutePath, 0, $this->baseDirLength)) { + $this->subPathname = ltrim(substr($absolutePath, $this->baseDirLength), '/\\'); + $dir = dirname($this->subPathname); + $this->subPath = '.' === $dir ? '' : $dir; + } else { + $this->subPath = $this->subPathname = ''; + } + + $this->current = new SplFileInfo(parent::current(), $this->subPath, $this->subPathname); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Iterator/FileTypeFilterIterator.php b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/FileTypeFilterIterator.php new file mode 100644 index 0000000..5b7bbb1 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/FileTypeFilterIterator.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * FileTypeFilterIterator only keeps files, directories, or both. + * + * @author Fabien Potencier + */ +class FileTypeFilterIterator extends FilterIterator +{ + const ONLY_FILES = 1; + const ONLY_DIRECTORIES = 2; + + private $mode; + + /** + * Constructor. + * + * @param \Iterator $iterator The Iterator to filter + * @param integer $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) + */ + public function __construct(\Iterator $iterator, $mode) + { + $this->mode = $mode; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->current(); + if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) { + return false; + } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) { + return false; + } + + return true; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Iterator/FilecontentFilterIterator.php b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/FilecontentFilterIterator.php new file mode 100644 index 0000000..953f9c3 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/FilecontentFilterIterator.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings). + * + * @author Fabien Potencier + * @author Włodzimierz Gajda + */ +class FilecontentFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + if (!$this->matchRegexps && !$this->noMatchRegexps) { + return true; + } + + $fileinfo = $this->current(); + + if ($fileinfo->isDir() || !$fileinfo->isReadable()) { + return false; + } + + $content = $fileinfo->getContents(); + if (!$content) { + return false; + } + + // should at least not match one rule to exclude + foreach ($this->noMatchRegexps as $regex) { + if (preg_match($regex, $content)) { + return false; + } + } + + // should at least match one rule + $match = true; + if ($this->matchRegexps) { + $match = false; + foreach ($this->matchRegexps as $regex) { + if (preg_match($regex, $content)) { + return true; + } + } + } + + return $match; + } + + /** + * Converts string to regexp if necessary. + * + * @param string $str Pattern: string or regexp + * + * @return string regexp corresponding to a given string or regexp + */ + protected function toRegex($str) + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php new file mode 100644 index 0000000..3c0f3aa --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/FilenameFilterIterator.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Expression\Expression; + +/** + * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). + * + * @author Fabien Potencier + */ +class FilenameFilterIterator extends MultiplePcreFilterIterator +{ + + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + $filename = $this->current()->getFilename(); + + // should at least not match one rule to exclude + foreach ($this->noMatchRegexps as $regex) { + if (preg_match($regex, $filename)) { + return false; + } + } + + // should at least match one rule + $match = true; + if ($this->matchRegexps) { + $match = false; + foreach ($this->matchRegexps as $regex) { + if (preg_match($regex, $filename)) { + return true; + } + } + } + + return $match; + } + + /** + * Converts glob to regexp. + * + * PCRE patterns are left unchanged. + * Glob strings are transformed with Glob::toRegex(). + * + * @param string $str Pattern: glob or regexp + * + * @return string regexp corresponding to a given glob or regexp + */ + protected function toRegex($str) + { + return Expression::create($str)->getRegex()->render(); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Iterator/FilterIterator.php b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/FilterIterator.php new file mode 100644 index 0000000..f4da44c --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/FilterIterator.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * This iterator just overrides the rewind method in order to correct a PHP bug. + * + * @see https://bugs.php.net/bug.php?id=49104 + * + * @author Alex Bogomazov + */ +abstract class FilterIterator extends \FilterIterator +{ + /** + * This is a workaround for the problem with \FilterIterator leaving inner \FilesystemIterator in wrong state after + * rewind in some cases. + * + * @see FilterIterator::rewind() + */ + public function rewind() + { + $iterator = $this; + while ($iterator instanceof \OuterIterator) { + $innerIterator = $iterator->getInnerIterator(); + + if ($innerIterator instanceof RecursiveDirectoryIterator) { + if ($innerIterator->isRewindable()) { + $innerIterator->next(); + $innerIterator->rewind(); + } + } elseif ($iterator->getInnerIterator() instanceof \FilesystemIterator) { + $iterator->getInnerIterator()->next(); + $iterator->getInnerIterator()->rewind(); + } + $iterator = $iterator->getInnerIterator(); + } + + parent::rewind(); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Iterator/MultiplePcreFilterIterator.php b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/MultiplePcreFilterIterator.php new file mode 100644 index 0000000..3a9dd55 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/MultiplePcreFilterIterator.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Expression\Expression; + +/** + * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings). + * + * @author Fabien Potencier + */ +abstract class MultiplePcreFilterIterator extends FilterIterator +{ + protected $matchRegexps; + protected $noMatchRegexps; + + /** + * Constructor. + * + * @param \Iterator $iterator The Iterator to filter + * @param array $matchPatterns An array of patterns that need to match + * @param array $noMatchPatterns An array of patterns that need to not match + */ + public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) + { + $this->matchRegexps = array(); + foreach ($matchPatterns as $pattern) { + $this->matchRegexps[] = $this->toRegex($pattern); + } + + $this->noMatchRegexps = array(); + foreach ($noMatchPatterns as $pattern) { + $this->noMatchRegexps[] = $this->toRegex($pattern); + } + + parent::__construct($iterator); + } + + /** + * Checks whether the string is a regex. + * + * @param string $str + * + * @return Boolean Whether the given string is a regex + */ + protected function isRegex($str) + { + return Expression::create($str)->isRegex(); + } + + /** + * Converts string into regexp. + * + * @param string $str Pattern + * + * @return string regexp corresponding to a given string + */ + abstract protected function toRegex($str); +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Iterator/PathFilterIterator.php b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/PathFilterIterator.php new file mode 100644 index 0000000..c736f0b --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/PathFilterIterator.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * PathFilterIterator filters files by path patterns (e.g. some/special/dir). + * + * @author Fabien Potencier + * @author Włodzimierz Gajda + */ +class PathFilterIterator extends MultiplePcreFilterIterator +{ + + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + $filename = $this->current()->getRelativePathname(); + + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + $filename = strtr($filename, '\\', '/'); + } + + // should at least not match one rule to exclude + foreach ($this->noMatchRegexps as $regex) { + if (preg_match($regex, $filename)) { + return false; + } + } + + // should at least match one rule + $match = true; + if ($this->matchRegexps) { + $match = false; + foreach ($this->matchRegexps as $regex) { + if (preg_match($regex, $filename)) { + return true; + } + } + } + + return $match; + } + + /** + * Converts strings to regexp. + * + * PCRE patterns are left unchanged. + * + * Default conversion: + * 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/' + * + * Use only / as directory separator (on Windows also). + * + * @param string $str Pattern: regexp or dirname. + * + * @return string regexp corresponding to a given string or regexp + */ + protected function toRegex($str) + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php new file mode 100644 index 0000000..f189712 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Exception\AccessDeniedException; +use Symfony\Component\Finder\SplFileInfo; + +/** + * Extends the \RecursiveDirectoryIterator to support relative paths + * + * @author Victor Berchet + */ +class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator +{ + /** + * @var boolean + */ + private $ignoreUnreadableDirs; + + /** + * @var Boolean + */ + private $rewindable; + + /** + * Constructor. + * + * @param string $path + * @param int $flags + * @param boolean $ignoreUnreadableDirs + * + * @throws \RuntimeException + */ + public function __construct($path, $flags, $ignoreUnreadableDirs = false) + { + if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) { + throw new \RuntimeException('This iterator only support returning current as fileinfo.'); + } + + parent::__construct($path, $flags); + $this->ignoreUnreadableDirs = $ignoreUnreadableDirs; + } + + /** + * Return an instance of SplFileInfo with support for relative paths + * + * @return SplFileInfo File information + */ + public function current() + { + return new SplFileInfo(parent::current()->getPathname(), $this->getSubPath(), $this->getSubPathname()); + } + + /** + * @return \RecursiveIterator + * + * @throws AccessDeniedException + */ + public function getChildren() + { + try { + return parent::getChildren(); + } catch (\UnexpectedValueException $e) { + if ($this->ignoreUnreadableDirs) { + // If directory is unreadable and finder is set to ignore it, a fake empty content is returned. + return new \RecursiveArrayIterator(array()); + } else { + throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); + } + } + } + + /** + * Do nothing for non rewindable stream + */ + public function rewind() + { + if (false === $this->isRewindable()) { + return; + } + + // @see https://bugs.php.net/bug.php?id=49104 + parent::next(); + + parent::rewind(); + } + + /** + * Checks if the stream is rewindable. + * + * @return Boolean true when the stream is rewindable, false otherwise + */ + public function isRewindable() + { + if (null !== $this->rewindable) { + return $this->rewindable; + } + + if (false !== $stream = @opendir($this->getPath())) { + $infos = stream_get_meta_data($stream); + closedir($stream); + + if ($infos['seekable']) { + return $this->rewindable = true; + } + } + + return $this->rewindable = false; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Iterator/SizeRangeFilterIterator.php b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/SizeRangeFilterIterator.php new file mode 100644 index 0000000..77f9cc4 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/SizeRangeFilterIterator.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Comparator\NumberComparator; + +/** + * SizeRangeFilterIterator filters out files that are not in the given size range. + * + * @author Fabien Potencier + */ +class SizeRangeFilterIterator extends FilterIterator +{ + private $comparators = array(); + + /** + * Constructor. + * + * @param \Iterator $iterator The Iterator to filter + * @param NumberComparator[] $comparators An array of NumberComparator instances + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return Boolean true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->current(); + if (!$fileinfo->isFile()) { + return true; + } + + $filesize = $fileinfo->getSize(); + foreach ($this->comparators as $compare) { + if (!$compare->test($filesize)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Iterator/SortableIterator.php b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/SortableIterator.php new file mode 100644 index 0000000..cdc734f --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Iterator/SortableIterator.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * SortableIterator applies a sort on a given Iterator. + * + * @author Fabien Potencier + */ +class SortableIterator implements \IteratorAggregate +{ + const SORT_BY_NAME = 1; + const SORT_BY_TYPE = 2; + const SORT_BY_ACCESSED_TIME = 3; + const SORT_BY_CHANGED_TIME = 4; + const SORT_BY_MODIFIED_TIME = 5; + + private $iterator; + private $sort; + + /** + * Constructor. + * + * @param \Traversable $iterator The Iterator to filter + * @param integer|callback $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback) + * + * @throws \InvalidArgumentException + */ + public function __construct(\Traversable $iterator, $sort) + { + $this->iterator = $iterator; + + if (self::SORT_BY_NAME === $sort) { + $this->sort = function ($a, $b) { + return strcmp($a->getRealpath(), $b->getRealpath()); + }; + } elseif (self::SORT_BY_TYPE === $sort) { + $this->sort = function ($a, $b) { + if ($a->isDir() && $b->isFile()) { + return -1; + } elseif ($a->isFile() && $b->isDir()) { + return 1; + } + + return strcmp($a->getRealpath(), $b->getRealpath()); + }; + } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { + $this->sort = function ($a, $b) { + return ($a->getATime() > $b->getATime()); + }; + } elseif (self::SORT_BY_CHANGED_TIME === $sort) { + $this->sort = function ($a, $b) { + return ($a->getCTime() > $b->getCTime()); + }; + } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { + $this->sort = function ($a, $b) { + return ($a->getMTime() > $b->getMTime()); + }; + } elseif (is_callable($sort)) { + $this->sort = $sort; + } else { + throw new \InvalidArgumentException('The SortableIterator takes a PHP callback or a valid built-in sort algorithm as an argument.'); + } + } + + public function getIterator() + { + $array = iterator_to_array($this->iterator, true); + uasort($array, $this->sort); + + return new \ArrayIterator($array); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/LICENSE b/vendor/symfony/finder/Symfony/Component/Finder/LICENSE new file mode 100644 index 0000000..88a57f8 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2013 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/finder/Symfony/Component/Finder/README.md b/vendor/symfony/finder/Symfony/Component/Finder/README.md new file mode 100644 index 0000000..a4caf93 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/README.md @@ -0,0 +1,41 @@ +Finder Component +================ + +Finder finds files and directories via an intuitive fluent interface. + + use Symfony\Component\Finder\Finder; + + $finder = new Finder(); + + $iterator = $finder + ->files() + ->name('*.php') + ->depth(0) + ->size('>= 1K') + ->in(__DIR__); + + foreach ($iterator as $file) { + print $file->getRealpath()."\n"; + } + +But you can also use it to find files stored remotely like in this example where +we are looking for files on Amazon S3: + + $s3 = new \Zend_Service_Amazon_S3($key, $secret); + $s3->registerStreamWrapper("s3"); + + $finder = new Finder(); + $finder->name('photos*')->size('< 100K')->date('since 1 hour ago'); + foreach ($finder->in('s3://bucket-name') as $file) { + print $file->getFilename()."\n"; + } + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/Finder/ + $ composer.phar install --dev + $ phpunit + diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Shell/Command.php b/vendor/symfony/finder/Symfony/Component/Finder/Shell/Command.php new file mode 100644 index 0000000..5fcfed8 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Shell/Command.php @@ -0,0 +1,296 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Shell; + +/** + * @author Jean-François Simon + */ +class Command +{ + /** + * @var Command|null + */ + private $parent; + + /** + * @var array + */ + private $bits; + + /** + * @var array + */ + private $labels; + + /** + * @var \Closure|null + */ + private $errorHandler; + + /** + * Constructor. + * + * @param Command $parent Parent command + */ + public function __construct(Command $parent = null) + { + $this->parent = $parent; + $this->bits = array(); + $this->labels = array(); + } + + /** + * Returns command as string. + * + * @return string + */ + public function __toString() + { + return $this->join(); + } + + /** + * Creates a new Command instance. + * + * @param Command $parent Parent command + * + * @return Command New Command instance + */ + public static function create(Command $parent = null) + { + return new self($parent); + } + + /** + * Escapes special chars from input. + * + * @param string $input A string to escape + * + * @return string The escaped string + */ + public static function escape($input) + { + return escapeshellcmd($input); + } + + /** + * Quotes input. + * + * @param string $input An argument string + * + * @return string The quoted string + */ + public static function quote($input) + { + return escapeshellarg($input); + } + + /** + * Appends a string or a Command instance. + * + * @param string|Command $bit + * + * @return Command The current Command instance + */ + public function add($bit) + { + $this->bits[] = $bit; + + return $this; + } + + /** + * Prepends a string or a command instance. + * + * @param string|Command $bit + * + * @return Command The current Command instance + */ + public function top($bit) + { + array_unshift($this->bits, $bit); + + foreach ($this->labels as $label => $index) { + $this->labels[$label] += 1; + } + + return $this; + } + + /** + * Appends an argument, will be quoted. + * + * @param string $arg + * + * @return Command The current Command instance + */ + public function arg($arg) + { + $this->bits[] = self::quote($arg); + + return $this; + } + + /** + * Appends escaped special command chars. + * + * @param string $esc + * + * @return Command The current Command instance + */ + public function cmd($esc) + { + $this->bits[] = self::escape($esc); + + return $this; + } + + /** + * Inserts a labeled command to feed later. + * + * @param string $label The unique label + * + * @return Command The current Command instance + * + * @throws \RuntimeException If label already exists + */ + public function ins($label) + { + if (isset($this->labels[$label])) { + throw new \RuntimeException(sprintf('Label "%s" already exists.', $label)); + } + + $this->bits[] = self::create($this); + $this->labels[$label] = count($this->bits)-1; + + return $this->bits[$this->labels[$label]]; + } + + /** + * Retrieves a previously labeled command. + * + * @param string $label + * + * @return Command The labeled command + * + * @throws \RuntimeException + */ + public function get($label) + { + if (!isset($this->labels[$label])) { + throw new \RuntimeException(sprintf('Label "%s" does not exist.', $label)); + } + + return $this->bits[$this->labels[$label]]; + } + + /** + * Returns parent command (if any). + * + * @return Command Parent command + * + * @throws \RuntimeException If command has no parent + */ + public function end() + { + if (null === $this->parent) { + throw new \RuntimeException('Calling end on root command doesn\'t make sense.'); + } + + return $this->parent; + } + + /** + * Counts bits stored in command. + * + * @return int The bits count + */ + public function length() + { + return count($this->bits); + } + + /** + * @param \Closure $errorHandler + * + * @return Command + */ + public function setErrorHandler(\Closure $errorHandler) + { + $this->errorHandler = $errorHandler; + + return $this; + } + + /** + * @return callable|null + */ + public function getErrorHandler() + { + return $this->errorHandler; + } + + /** + * Executes current command. + * + * @return array The command result + * + * @throws \RuntimeException + */ + public function execute() + { + if (null === $this->errorHandler) { + exec($this->join(), $output); + } else { + $process = proc_open($this->join(), array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $pipes); + $output = preg_split('~(\r\n|\r|\n)~', stream_get_contents($pipes[1]), -1, PREG_SPLIT_NO_EMPTY); + + if ($error = stream_get_contents($pipes[2])) { + call_user_func($this->errorHandler, $error); + } + + proc_close($process); + } + + return $output ?: array(); + } + + /** + * Joins bits. + * + * @return string + */ + public function join() + { + return implode(' ', array_filter( + array_map(function($bit) { + return $bit instanceof Command ? $bit->join() : ($bit ?: null); + }, $this->bits), + function($bit) { return null !== $bit; } + )); + } + + /** + * Insert a string or a Command instance before the bit at given position $index (index starts from 0). + * + * @param string|Command $bit + * @param integer $index + * + * @return Command The current Command instance + */ + public function addAtIndex($bit, $index) + { + array_splice($this->bits, $index, 0, $bit); + + return $this; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Shell/Shell.php b/vendor/symfony/finder/Symfony/Component/Finder/Shell/Shell.php new file mode 100644 index 0000000..0b01636 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Shell/Shell.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Shell; + +/** + * @author Jean-François Simon + */ +class Shell +{ + const TYPE_UNIX = 1; + const TYPE_DARWIN = 2; + const TYPE_CYGWIN = 3; + const TYPE_WINDOWS = 4; + const TYPE_BSD = 5; + + /** + * @var string|null + */ + private $type; + + /** + * Returns guessed OS type. + * + * @return int + */ + public function getType() + { + if (null === $this->type) { + $this->type = $this->guessType(); + } + + return $this->type; + } + + /** + * Tests if a command is available. + * + * @param string $command + * + * @return bool + */ + public function testCommand($command) + { + if (self::TYPE_WINDOWS === $this->type) { + // todo: find a way to test if windows command exists + return false; + } + + if (!function_exists('exec')) { + return false; + } + + // todo: find a better way (command could not be available) + exec('command -v '.$command, $output, $code); + + return 0 === $code && count($output) > 0; + } + + /** + * Guesses OS type. + * + * @return int + */ + private function guessType() + { + $os = strtolower(PHP_OS); + + if (false !== strpos($os, 'cygwin')) { + return self::TYPE_CYGWIN; + } + + if (false !== strpos($os, 'darwin')) { + return self::TYPE_DARWIN; + } + + if (false !== strpos($os, 'bsd')) { + return self::TYPE_BSD; + } + + if (0 === strpos($os, 'win')) { + return self::TYPE_WINDOWS; + } + + return self::TYPE_UNIX; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/SplFileInfo.php b/vendor/symfony/finder/Symfony/Component/Finder/SplFileInfo.php new file mode 100644 index 0000000..d911fe0 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/SplFileInfo.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Extends \SplFileInfo to support relative paths + * + * @author Fabien Potencier + */ +class SplFileInfo extends \SplFileInfo +{ + private $relativePath; + private $relativePathname; + + /** + * Constructor + * + * @param string $file The file name + * @param string $relativePath The relative path + * @param string $relativePathname The relative path name + */ + public function __construct($file, $relativePath, $relativePathname) + { + parent::__construct($file); + $this->relativePath = $relativePath; + $this->relativePathname = $relativePathname; + } + + /** + * Returns the relative path + * + * @return string the relative path + */ + public function getRelativePath() + { + return $this->relativePath; + } + + /** + * Returns the relative path name + * + * @return string the relative path name + */ + public function getRelativePathname() + { + return $this->relativePathname; + } + + /** + * Returns the contents of the file + * + * @return string the contents of the file + * + * @throws \RuntimeException + */ + public function getContents() + { + $level = error_reporting(0); + $content = file_get_contents($this->getPathname()); + error_reporting($level); + if (false === $content) { + $error = error_get_last(); + throw new \RuntimeException($error['message']); + } + + return $content; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Comparator/ComparatorTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Comparator/ComparatorTest.php new file mode 100644 index 0000000..bf59844 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Comparator/ComparatorTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Comparator; + +use Symfony\Component\Finder\Comparator\Comparator; + +class ComparatorTest extends \PHPUnit_Framework_TestCase +{ + public function testGetSetOperator() + { + $comparator = new Comparator(); + try { + $comparator->setOperator('foo'); + $this->fail('->setOperator() throws an \InvalidArgumentException if the operator is not valid.'); + } catch (\Exception $e) { + $this->assertInstanceOf('InvalidArgumentException', $e, '->setOperator() throws an \InvalidArgumentException if the operator is not valid.'); + } + + $comparator = new Comparator(); + $comparator->setOperator('>'); + $this->assertEquals('>', $comparator->getOperator(), '->getOperator() returns the current operator'); + } + + public function testGetSetTarget() + { + $comparator = new Comparator(); + $comparator->setTarget(8); + $this->assertEquals(8, $comparator->getTarget(), '->getTarget() returns the target'); + } + + /** + * @dataProvider getTestData + */ + public function testTest($operator, $target, $match, $noMatch) + { + $c = new Comparator(); + $c->setOperator($operator); + $c->setTarget($target); + + foreach ($match as $m) { + $this->assertTrue($c->test($m), '->test() tests a string against the expression'); + } + + foreach ($noMatch as $m) { + $this->assertFalse($c->test($m), '->test() tests a string against the expression'); + } + } + + public function getTestData() + { + return array( + array('<', '1000', array('500', '999'), array('1000', '1500')), + ); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Comparator/DateComparatorTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Comparator/DateComparatorTest.php new file mode 100644 index 0000000..ac8256a --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Comparator/DateComparatorTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Comparator; + +use Symfony\Component\Finder\Comparator\DateComparator; + +class DateComparatorTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + try { + new DateComparator('foobar'); + $this->fail('__construct() throws an \InvalidArgumentException if the test expression is not valid.'); + } catch (\Exception $e) { + $this->assertInstanceOf('InvalidArgumentException', $e, '__construct() throws an \InvalidArgumentException if the test expression is not valid.'); + } + + try { + new DateComparator(''); + $this->fail('__construct() throws an \InvalidArgumentException if the test expression is not valid.'); + } catch (\Exception $e) { + $this->assertInstanceOf('InvalidArgumentException', $e, '__construct() throws an \InvalidArgumentException if the test expression is not valid.'); + } + } + + /** + * @dataProvider getTestData + */ + public function testTest($test, $match, $noMatch) + { + $c = new DateComparator($test); + + foreach ($match as $m) { + $this->assertTrue($c->test($m), '->test() tests a string against the expression'); + } + + foreach ($noMatch as $m) { + $this->assertFalse($c->test($m), '->test() tests a string against the expression'); + } + } + + public function getTestData() + { + return array( + array('< 2005-10-10', array(strtotime('2005-10-09')), array(strtotime('2005-10-15'))), + array('until 2005-10-10', array(strtotime('2005-10-09')), array(strtotime('2005-10-15'))), + array('before 2005-10-10', array(strtotime('2005-10-09')), array(strtotime('2005-10-15'))), + array('> 2005-10-10', array(strtotime('2005-10-15')), array(strtotime('2005-10-09'))), + array('after 2005-10-10', array(strtotime('2005-10-15')), array(strtotime('2005-10-09'))), + array('since 2005-10-10', array(strtotime('2005-10-15')), array(strtotime('2005-10-09'))), + array('!= 2005-10-10', array(strtotime('2005-10-11')), array(strtotime('2005-10-10'))), + ); + + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Comparator/NumberComparatorTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Comparator/NumberComparatorTest.php new file mode 100644 index 0000000..b07870b --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Comparator/NumberComparatorTest.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Comparator; + +use Symfony\Component\Finder\Comparator\NumberComparator; + +class NumberComparatorTest extends \PHPUnit_Framework_TestCase +{ + + /** + * @dataProvider getConstructorTestData + */ + public function testConstructor($successes, $failures) + { + foreach ($successes as $s) { + new NumberComparator($s); + } + + foreach ($failures as $f) { + try { + new NumberComparator($f); + $this->fail('__construct() throws an \InvalidArgumentException if the test expression is not valid.'); + } catch (\Exception $e) { + $this->assertInstanceOf('InvalidArgumentException', $e, '__construct() throws an \InvalidArgumentException if the test expression is not valid.'); + } + } + } + + /** + * @dataProvider getTestData + */ + public function testTest($test, $match, $noMatch) + { + $c = new NumberComparator($test); + + foreach ($match as $m) { + $this->assertTrue($c->test($m), '->test() tests a string against the expression'); + } + + foreach ($noMatch as $m) { + $this->assertFalse($c->test($m), '->test() tests a string against the expression'); + } + } + + public function getTestData() + { + return array( + array('< 1000', array('500', '999'), array('1000', '1500')), + + array('< 1K', array('500', '999'), array('1000', '1500')), + array('<1k', array('500', '999'), array('1000', '1500')), + array(' < 1 K ', array('500', '999'), array('1000', '1500')), + array('<= 1K', array('1000'), array('1001')), + array('> 1K', array('1001'), array('1000')), + array('>= 1K', array('1000'), array('999')), + + array('< 1KI', array('500', '1023'), array('1024', '1500')), + array('<= 1KI', array('1024'), array('1025')), + array('> 1KI', array('1025'), array('1024')), + array('>= 1KI', array('1024'), array('1023')), + + array('1KI', array('1024'), array('1023', '1025')), + array('==1KI', array('1024'), array('1023', '1025')), + + array('==1m', array('1000000'), array('999999', '1000001')), + array('==1mi', array(1024*1024), array(1024*1024-1, 1024*1024+1)), + + array('==1g', array('1000000000'), array('999999999', '1000000001')), + array('==1gi', array(1024*1024*1024), array(1024*1024*1024-1, 1024*1024*1024+1)), + + array('!= 1000', array('500', '999'), array('1000')), + ); + } + + public function getConstructorTestData() + { + return array( + array( + array( + '1', '0', + '3.5', '33.55', '123.456', '123456.78', + '.1', '.123', + '.0', '0.0', + '1.', '0.', '123.', + '==1', '!=1', '<1', '>1', '<=1', '>=1', + '==1k', '==1ki', '==1m', '==1mi', '==1g', '==1gi', + '1k', '1ki', '1m', '1mi', '1g', '1gi', + ), + array( + false, null, '', + ' ', 'foobar', + '=1', '===1', + '0 . 1', '123 .45', '234. 567', + '..', '.0.', '0.1.2', + ) + ), + ); + } + +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Expression/ExpressionTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Expression/ExpressionTest.php new file mode 100644 index 0000000..c907d6a --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Expression/ExpressionTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests; + +use Symfony\Component\Finder\Expression\Expression; + +class ExpressionTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getTypeGuesserData + */ + public function testTypeGuesser($expr, $type) + { + $this->assertEquals($type, Expression::create($expr)->getType()); + } + + /** + * @dataProvider getCaseSensitiveData + */ + public function testCaseSensitive($expr, $isCaseSensitive) + { + $this->assertEquals($isCaseSensitive, Expression::create($expr)->isCaseSensitive()); + } + + /** + * @dataProvider getRegexRenderingData + */ + public function testRegexRendering($expr, $body) + { + $this->assertEquals($body, Expression::create($expr)->renderPattern()); + } + + public function getTypeGuesserData() + { + return array( + array('{foo}', Expression::TYPE_REGEX), + array('/foo/', Expression::TYPE_REGEX), + array('foo', Expression::TYPE_GLOB), + array('foo*', Expression::TYPE_GLOB), + ); + } + + public function getCaseSensitiveData() + { + return array( + array('{foo}m', true), + array('/foo/i', false), + array('foo*', true), + ); + } + + public function getRegexRenderingData() + { + return array( + array('{foo}m', 'foo'), + array('/foo/i', 'foo'), + ); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Expression/GlobTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Expression/GlobTest.php new file mode 100644 index 0000000..fbaeb0e --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Expression/GlobTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests; + +use Symfony\Component\Finder\Expression\Expression; + +class GlobTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getToRegexData + */ + public function testGlobToRegex($glob, $match, $noMatch) + { + foreach ($match as $m) { + $this->assertRegExp(Expression::create($glob)->getRegex()->render(), $m, '::toRegex() converts a glob to a regexp'); + } + + foreach ($noMatch as $m) { + $this->assertNotRegExp(Expression::create($glob)->getRegex()->render(), $m, '::toRegex() converts a glob to a regexp'); + } + } + + public function getToRegexData() + { + return array( + array('', array(''), array('f', '/')), + array('*', array('foo'), array('foo/', '/foo')), + array('foo.*', array('foo.php', 'foo.a', 'foo.'), array('fooo.php', 'foo.php/foo')), + array('fo?', array('foo', 'fot'), array('fooo', 'ffoo', 'fo/')), + array('fo{o,t}', array('foo', 'fot'), array('fob', 'fo/')), + array('foo(bar|foo)', array('foo(bar|foo)'), array('foobar', 'foofoo')), + array('foo,bar', array('foo,bar'), array('foo', 'bar')), + array('fo{o,\\,}', array('foo', 'fo,'), array()), + array('fo{o,\\\\}', array('foo', 'fo\\'), array()), + array('/foo', array('/foo'), array('foo')), + ); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Expression/RegexTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Expression/RegexTest.php new file mode 100644 index 0000000..f252696 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Expression/RegexTest.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests; + +use Symfony\Component\Finder\Expression\Expression; + +class RegexTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getHasFlagsData + */ + public function testHasFlags($regex, $start, $end) + { + $expr = new Expression($regex); + + $this->assertEquals($start, $expr->getRegex()->hasStartFlag()); + $this->assertEquals($end, $expr->getRegex()->hasEndFlag()); + } + + /** + * @dataProvider getHasJokersData + */ + public function testHasJokers($regex, $start, $end) + { + $expr = new Expression($regex); + + $this->assertEquals($start, $expr->getRegex()->hasStartJoker()); + $this->assertEquals($end, $expr->getRegex()->hasEndJoker()); + } + + /** + * @dataProvider getSetFlagsData + */ + public function testSetFlags($regex, $start, $end, $expected) + { + $expr = new Expression($regex); + $expr->getRegex()->setStartFlag($start)->setEndFlag($end); + + $this->assertEquals($expected, $expr->render()); + } + + /** + * @dataProvider getSetJokersData + */ + public function testSetJokers($regex, $start, $end, $expected) + { + $expr = new Expression($regex); + $expr->getRegex()->setStartJoker($start)->setEndJoker($end); + + $this->assertEquals($expected, $expr->render()); + } + + public function testOptions() + { + $expr = new Expression('~abc~is'); + $expr->getRegex()->removeOption('i')->addOption('m'); + + $this->assertEquals('~abc~sm', $expr->render()); + } + + public function testMixFlagsAndJokers() + { + $expr = new Expression('~^.*abc.*$~is'); + + $expr->getRegex()->setStartFlag(false)->setEndFlag(false)->setStartJoker(false)->setEndJoker(false); + $this->assertEquals('~abc~is', $expr->render()); + + $expr->getRegex()->setStartFlag(true)->setEndFlag(true)->setStartJoker(true)->setEndJoker(true); + $this->assertEquals('~^.*abc.*$~is', $expr->render()); + } + + /** + * @dataProvider getReplaceJokersTestData + */ + public function testReplaceJokers($regex, $expected) + { + $expr = new Expression($regex); + $expr = $expr->getRegex()->replaceJokers('@'); + + $this->assertEquals($expected, $expr->renderPattern()); + } + + public function getHasFlagsData() + { + return array( + array('~^abc~', true, false), + array('~abc$~', false, true), + array('~abc~', false, false), + array('~^abc$~', true, true), + array('~^abc\\$~', true, false), + ); + } + + public function getHasJokersData() + { + return array( + array('~.*abc~', true, false), + array('~abc.*~', false, true), + array('~abc~', false, false), + array('~.*abc.*~', true, true), + array('~.*abc\\.*~', true, false), + ); + } + + public function getSetFlagsData() + { + return array( + array('~abc~', true, false, '~^abc~'), + array('~abc~', false, true, '~abc$~'), + array('~abc~', false, false, '~abc~'), + array('~abc~', true, true, '~^abc$~'), + ); + } + + public function getSetJokersData() + { + return array( + array('~abc~', true, false, '~.*abc~'), + array('~abc~', false, true, '~abc.*~'), + array('~abc~', false, false, '~abc~'), + array('~abc~', true, true, '~.*abc.*~'), + ); + } + + public function getReplaceJokersTestData() + { + return array( + array('~.abc~', '@abc'), + array('~\\.abc~', '\\.abc'), + array('~\\\\.abc~', '\\\\@abc'), + array('~\\\\\\.abc~', '\\\\\\.abc'), + ); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php new file mode 100644 index 0000000..0cbae14 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/DummyAdapter.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\FakeAdapter; + +use Symfony\Component\Finder\Adapter\AbstractAdapter; + +/** + * @author Jean-François Simon + */ +class DummyAdapter extends AbstractAdapter +{ + /** + * @var \Iterator + */ + private $iterator; + + /** + * @param \Iterator $iterator + */ + public function __construct(\Iterator $iterator) + { + $this->iterator = $iterator; + } + + /** + * {@inheritdoc} + */ + public function searchInDirectory($dir) + { + return $this->iterator; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'yes'; + } + + /** + * {@inheritdoc} + */ + protected function canBeUsed() + { + return true; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/FailingAdapter.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/FailingAdapter.php new file mode 100644 index 0000000..6e6ed24 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/FailingAdapter.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\FakeAdapter; + +use Symfony\Component\Finder\Adapter\AbstractAdapter; +use Symfony\Component\Finder\Exception\AdapterFailureException; + +/** + * @author Jean-François Simon + */ +class FailingAdapter extends AbstractAdapter +{ + /** + * {@inheritdoc} + */ + public function searchInDirectory($dir) + { + throw new AdapterFailureException($this); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'failing'; + } + + /** + * {@inheritdoc} + */ + protected function canBeUsed() + { + return true; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/NamedAdapter.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/NamedAdapter.php new file mode 100644 index 0000000..5a260b0 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/NamedAdapter.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\FakeAdapter; + +use Symfony\Component\Finder\Adapter\AbstractAdapter; + +/** + * @author Jean-François Simon + */ +class NamedAdapter extends AbstractAdapter +{ + /** + * @var string + */ + private $name; + + /** + * @param string $name + */ + public function __construct($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function searchInDirectory($dir) + { + return new \ArrayIterator(array()); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + protected function canBeUsed() + { + return true; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/UnsupportedAdapter.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/UnsupportedAdapter.php new file mode 100644 index 0000000..1f91b98 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/FakeAdapter/UnsupportedAdapter.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\FakeAdapter; + +use Symfony\Component\Finder\Adapter\AbstractAdapter; + +/** + * @author Jean-François Simon + */ +class UnsupportedAdapter extends AbstractAdapter +{ + /** + * {@inheritdoc} + */ + public function searchInDirectory($dir) + { + return new \ArrayIterator(array()); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'unsupported'; + } + + /** + * {@inheritdoc} + */ + protected function canBeUsed() + { + return false; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/FinderTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/FinderTest.php new file mode 100644 index 0000000..c325c59 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/FinderTest.php @@ -0,0 +1,842 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests; + +use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\Adapter; +use Symfony\Component\Finder\Tests\FakeAdapter; + +class FinderTest extends Iterator\RealIteratorTestCase +{ + + public function testCreate() + { + $this->assertInstanceOf('Symfony\Component\Finder\Finder', Finder::create()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testDirectories($adapter) + { + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->directories()); + $this->assertIterator($this->toAbsolute(array('foo', 'toto')), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder($adapter); + $finder->directories(); + $finder->files(); + $finder->directories(); + $this->assertIterator($this->toAbsolute(array('foo', 'toto')), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testFiles($adapter) + { + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->files()); + $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'test.py', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder($adapter); + $finder->files(); + $finder->directories(); + $finder->files(); + $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'test.py', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testDepth($adapter) + { + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->depth('< 1')); + $this->assertIterator($this->toAbsolute(array('foo', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->depth('<= 0')); + $this->assertIterator($this->toAbsolute(array('foo', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->depth('>= 1')); + $this->assertIterator($this->toAbsolute(array('foo/bar.tmp')), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder($adapter); + $finder->depth('< 1')->depth('>= 1'); + $this->assertIterator(array(), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testName($adapter) + { + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->name('*.php')); + $this->assertIterator($this->toAbsolute(array('test.php')), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder($adapter); + $finder->name('test.ph*'); + $finder->name('test.py'); + $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder($adapter); + $finder->name('~^test~i'); + $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder($adapter); + $finder->name('~\\.php$~i'); + $this->assertIterator($this->toAbsolute(array('test.php')), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder($adapter); + $finder->name('test.p{hp,y}'); + $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testNotName($adapter) + { + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->notName('*.php')); + $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder($adapter); + $finder->notName('*.php'); + $finder->notName('*.py'); + $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder($adapter); + $finder->name('test.ph*'); + $finder->name('test.py'); + $finder->notName('*.php'); + $finder->notName('*.py'); + $this->assertIterator(array(), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder($adapter); + $finder->name('test.ph*'); + $finder->name('test.py'); + $finder->notName('*.p{hp,y}'); + $this->assertIterator(array(), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getRegexNameTestData + * + * @group regexName + */ + public function testRegexName($adapter, $regex) + { + $finder = $this->buildFinder($adapter); + $finder->name($regex); + $this->assertIterator($this->toAbsolute(array('test.py', 'test.php')), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testSize($adapter) + { + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->files()->size('< 1K')->size('> 500')); + $this->assertIterator($this->toAbsolute(array('test.php')), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testDate($adapter) + { + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->files()->date('until last month')); + $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php')), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testExclude($adapter) + { + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->exclude('foo')); + $this->assertIterator($this->toAbsolute(array('test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testIgnoreVCS($adapter) + { + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->ignoreVCS(false)->ignoreDotFiles(false)); + $this->assertIterator($this->toAbsolute(array('.git', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder($adapter); + $finder->ignoreVCS(false)->ignoreVCS(false)->ignoreDotFiles(false); + $this->assertIterator($this->toAbsolute(array('.git', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->ignoreVCS(true)->ignoreDotFiles(false)); + $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testIgnoreDotFiles($adapter) + { + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->ignoreDotFiles(false)->ignoreVCS(false)); + $this->assertIterator($this->toAbsolute(array('.git', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder($adapter); + $finder->ignoreDotFiles(false)->ignoreDotFiles(false)->ignoreVCS(false); + $this->assertIterator($this->toAbsolute(array('.git', '.bar', '.foo', '.foo/.bar', '.foo/bar', 'foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->ignoreDotFiles(true)->ignoreVCS(false)); + $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testSortByName($adapter) + { + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->sortByName()); + $this->assertIterator($this->toAbsolute(array('foo', 'foo bar', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testSortByType($adapter) + { + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->sortByType()); + $this->assertIterator($this->toAbsolute(array('foo', 'foo bar', 'toto', 'foo/bar.tmp', 'test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testSortByAccessedTime($adapter) + { + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->sortByAccessedTime()); + $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'toto', 'test.py', 'foo', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testSortByChangedTime($adapter) + { + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->sortByChangedTime()); + $this->assertIterator($this->toAbsolute(array('toto', 'test.py', 'test.php', 'foo/bar.tmp', 'foo', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testSortByModifiedTime($adapter) + { + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->sortByModifiedTime()); + $this->assertIterator($this->toAbsolute(array('foo/bar.tmp', 'test.php', 'toto', 'test.py', 'foo', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testSort($adapter) + { + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->sort(function (\SplFileInfo $a, \SplFileInfo $b) { return strcmp($a->getRealpath(), $b->getRealpath()); })); + $this->assertIterator($this->toAbsolute(array('foo', 'foo bar', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testFilter($adapter) + { + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->filter(function (\SplFileInfo $f) { return preg_match('/test/', $f) > 0; })); + $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testFollowLinks($adapter) + { + if ('\\' == DIRECTORY_SEPARATOR) { + return; + } + + $finder = $this->buildFinder($adapter); + $this->assertSame($finder, $finder->followLinks()); + $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto', 'foo bar')), $finder->in(self::$tmpDir)->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testIn($adapter) + { + $finder = $this->buildFinder($adapter); + try { + $finder->in('foobar'); + $this->fail('->in() throws a \InvalidArgumentException if the directory does not exist'); + } catch (\Exception $e) { + $this->assertInstanceOf('InvalidArgumentException', $e, '->in() throws a \InvalidArgumentException if the directory does not exist'); + } + + $finder = $this->buildFinder($adapter); + $iterator = $finder->files()->name('*.php')->depth('< 1')->in(array(self::$tmpDir, __DIR__))->getIterator(); + + $this->assertIterator(array(self::$tmpDir.DIRECTORY_SEPARATOR.'test.php', __DIR__.DIRECTORY_SEPARATOR.'FinderTest.php'), $iterator); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testInWithGlob($adapter) + { + $finder = $this->buildFinder($adapter); + $finder->in(array(__DIR__.'/Fixtures/*/B/C', __DIR__.'/Fixtures/*/*/B/C'))->getIterator(); + + $this->assertIterator($this->toAbsoluteFixtures(array('A/B/C/abc.dat', 'copy/A/B/C/abc.dat.copy')), $finder); + } + + /** + * @dataProvider getAdaptersTestData + * @expectedException \InvalidArgumentException + */ + public function testInWithNonDirectoryGlob($adapter) + { + $finder = $this->buildFinder($adapter); + $finder->in(__DIR__.'/Fixtures/A/a*'); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testGetIterator($adapter) + { + $finder = $this->buildFinder($adapter); + try { + $finder->getIterator(); + $this->fail('->getIterator() throws a \LogicException if the in() method has not been called'); + } catch (\Exception $e) { + $this->assertInstanceOf('LogicException', $e, '->getIterator() throws a \LogicException if the in() method has not been called'); + } + + $finder = $this->buildFinder($adapter); + $dirs = array(); + foreach ($finder->directories()->in(self::$tmpDir) as $dir) { + $dirs[] = (string) $dir; + } + + $expected = $this->toAbsolute(array('foo', 'toto')); + + sort($dirs); + sort($expected); + + $this->assertEquals($expected, $dirs, 'implements the \IteratorAggregate interface'); + + $finder = $this->buildFinder($adapter); + $this->assertEquals(2, iterator_count($finder->directories()->in(self::$tmpDir)), 'implements the \IteratorAggregate interface'); + + $finder = $this->buildFinder($adapter); + $a = iterator_to_array($finder->directories()->in(self::$tmpDir)); + $a = array_values(array_map(function ($a) { return (string) $a; }, $a)); + sort($a); + $this->assertEquals($expected, $a, 'implements the \IteratorAggregate interface'); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testRelativePath($adapter) + { + $finder = $this->buildFinder($adapter)->in(self::$tmpDir); + + $paths = array(); + + foreach ($finder as $file) { + $paths[] = $file->getRelativePath(); + } + + $ref = array("", "", "", "", "foo", ""); + + sort($ref); + sort($paths); + + $this->assertEquals($ref, $paths); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testRelativePathname($adapter) + { + $finder = $this->buildFinder($adapter)->in(self::$tmpDir)->sortByName(); + + $paths = array(); + + foreach ($finder as $file) { + $paths[] = $file->getRelativePathname(); + } + + $ref = array("test.php", "toto", "test.py", "foo", "foo".DIRECTORY_SEPARATOR."bar.tmp", "foo bar"); + + sort($paths); + sort($ref); + + $this->assertEquals($ref, $paths); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testAppendWithAFinder($adapter) + { + $finder = $this->buildFinder($adapter); + $finder->files()->in(self::$tmpDir.DIRECTORY_SEPARATOR.'foo'); + + $finder1 = $this->buildFinder($adapter); + $finder1->directories()->in(self::$tmpDir); + + $finder = $finder->append($finder1); + + $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'toto')), $finder->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testAppendWithAnArray($adapter) + { + $finder = $this->buildFinder($adapter); + $finder->files()->in(self::$tmpDir.DIRECTORY_SEPARATOR.'foo'); + + $finder->append($this->toAbsolute(array('foo', 'toto'))); + + $this->assertIterator($this->toAbsolute(array('foo', 'foo/bar.tmp', 'toto')), $finder->getIterator()); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testAppendReturnsAFinder($adapter) + { + $this->assertInstanceOf('Symfony\\Component\\Finder\\Finder', $this->buildFinder($adapter)->append(array())); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testAppendDoesNotRequireIn($adapter) + { + $finder = $this->buildFinder($adapter); + $finder->in(self::$tmpDir.DIRECTORY_SEPARATOR.'foo'); + + $finder1 = Finder::create()->append($finder); + + $this->assertIterator(iterator_to_array($finder->getIterator()), $finder1->getIterator()); + } + + public function testCountDirectories() + { + $directory = Finder::create()->directories()->in(self::$tmpDir); + $i = 0; + + foreach ($directory as $dir) { + $i++; + } + + $this->assertCount($i, $directory); + } + + public function testCountFiles() + { + $files = Finder::create()->files()->in(__DIR__.DIRECTORY_SEPARATOR.'Fixtures'); + $i = 0; + + foreach ($files as $file) { + $i++; + } + + $this->assertCount($i, $files); + } + + /** + * @expectedException \LogicException + */ + public function testCountWithoutIn() + { + $finder = Finder::create()->files(); + count($finder); + } + + /** + * @dataProvider getContainsTestData + * @group grep + */ + public function testContains($adapter, $matchPatterns, $noMatchPatterns, $expected) + { + $finder = $this->buildFinder($adapter); + $finder->in(__DIR__.DIRECTORY_SEPARATOR.'Fixtures') + ->name('*.txt')->sortByName() + ->contains($matchPatterns) + ->notContains($noMatchPatterns); + + $this->assertIterator($this->toAbsoluteFixtures($expected), $finder); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testContainsOnDirectory(Adapter\AdapterInterface $adapter) + { + $finder = $this->buildFinder($adapter); + $finder->in(__DIR__) + ->directories() + ->name('Fixtures') + ->contains('abc'); + $this->assertIterator(array(), $finder); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testNotContainsOnDirectory(Adapter\AdapterInterface $adapter) + { + $finder = $this->buildFinder($adapter); + $finder->in(__DIR__) + ->directories() + ->name('Fixtures') + ->notContains('abc'); + $this->assertIterator(array(), $finder); + } + + /** + * Searching in multiple locations involves AppendIterator which does an unnecessary rewind which leaves FilterIterator + * with inner FilesystemIterator in an invalid state. + * + * @see https://bugs.php.net/bug.php?id=49104 + * + * @dataProvider getAdaptersTestData + */ + public function testMultipleLocations(Adapter\AdapterInterface $adapter) + { + $locations = array( + self::$tmpDir.'/', + self::$tmpDir.'/toto/', + ); + + // it is expected that there are test.py test.php in the tmpDir + $finder = $this->buildFinder($adapter); + $finder->in($locations)->depth('< 1')->name('test.php'); + + $this->assertEquals(1, count($finder)); + } + + /** + * Iterator keys must be the file pathname. + * + * @dataProvider getAdaptersTestData + */ + public function testIteratorKeys(Adapter\AdapterInterface $adapter) + { + $finder = $this->buildFinder($adapter)->in(self::$tmpDir); + foreach ($finder as $key => $file) { + $this->assertEquals($file->getPathname(), $key); + } + } + + public function testAdaptersOrdering() + { + $finder = Finder::create() + ->removeAdapters() + ->addAdapter(new FakeAdapter\NamedAdapter('a'), 0) + ->addAdapter(new FakeAdapter\NamedAdapter('b'), -50) + ->addAdapter(new FakeAdapter\NamedAdapter('c'), 50) + ->addAdapter(new FakeAdapter\NamedAdapter('d'), -25) + ->addAdapter(new FakeAdapter\NamedAdapter('e'), 25); + + $this->assertEquals( + array('c', 'e', 'a', 'd', 'b'), + array_map(function(Adapter\AdapterInterface $adapter) { + return $adapter->getName(); + }, $finder->getAdapters()) + ); + } + + public function testAdaptersChaining() + { + $iterator = new \ArrayIterator(array()); + $filenames = $this->toAbsolute(array('foo', 'foo/bar.tmp', 'test.php', 'test.py', 'toto')); + foreach ($filenames as $file) { + $iterator->append(new \Symfony\Component\Finder\SplFileInfo($file, null, null)); + } + + $finder = Finder::create() + ->removeAdapters() + ->addAdapter(new FakeAdapter\UnsupportedAdapter(), 3) + ->addAdapter(new FakeAdapter\FailingAdapter(), 2) + ->addAdapter(new FakeAdapter\DummyAdapter($iterator), 1); + + $this->assertIterator($filenames, $finder->in(sys_get_temp_dir())->getIterator()); + } + + public function getAdaptersTestData() + { + return array_map( + function ($adapter) { return array($adapter); }, + $this->getValidAdapters() + ); + } + + public function getContainsTestData() + { + $tests = array( + array('', '', array()), + array('foo', 'bar', array()), + array('', 'foobar', array('dolor.txt', 'ipsum.txt', 'lorem.txt')), + array('lorem ipsum dolor sit amet', 'foobar', array('lorem.txt')), + array('sit', 'bar', array('dolor.txt', 'ipsum.txt', 'lorem.txt')), + array('dolor sit amet', '@^L@m', array('dolor.txt', 'ipsum.txt')), + array('/^lorem ipsum dolor sit amet$/m', 'foobar', array('lorem.txt')), + array('lorem', 'foobar', array('lorem.txt')), + array('', 'lorem', array('dolor.txt', 'ipsum.txt')), + array('ipsum dolor sit amet', '/^IPSUM/m', array('lorem.txt')), + ); + + return $this->buildTestData($tests); + } + + public function getRegexNameTestData() + { + $tests = array( + array('~.+\\.p.+~i'), + array('~t.*s~i'), + ); + + return $this->buildTestData($tests); + } + + /** + * @dataProvider getTestPathData + */ + public function testPath(Adapter\AdapterInterface $adapter, $matchPatterns, $noMatchPatterns, array $expected) + { + $finder = $this->buildFinder($adapter); + $finder->in(__DIR__.DIRECTORY_SEPARATOR.'Fixtures') + ->path($matchPatterns) + ->notPath($noMatchPatterns); + + $this->assertIterator($this->toAbsoluteFixtures($expected), $finder); + } + + public function testAdapterSelection() + { + // test that by default, PhpAdapter is selected + $adapters = Finder::create()->getAdapters(); + $this->assertTrue($adapters[0] instanceof Adapter\PhpAdapter); + + // test another adapter selection + $adapters = Finder::create()->setAdapter('gnu_find')->getAdapters(); + $this->assertTrue($adapters[0] instanceof Adapter\GnuFindAdapter); + + // test that useBestAdapter method removes selection + $adapters = Finder::create()->useBestAdapter()->getAdapters(); + $this->assertFalse($adapters[0] instanceof Adapter\PhpAdapter); + } + + public function getTestPathData() + { + $tests = array( + array('', '', array()), + array('/^A\/B\/C/', '/C$/', + array('A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat') + ), + array('/^A\/B/', 'foobar', + array( + 'A'.DIRECTORY_SEPARATOR.'B', + 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C', + 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'ab.dat', + 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat', + ) + ), + array('A/B/C', 'foobar', + array( + 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C', + 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat', + 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C', + 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat.copy', + ) + ), + array('A/B', 'foobar', + array( + //dirs + 'A'.DIRECTORY_SEPARATOR.'B', + 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C', + 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B', + 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C', + //files + 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'ab.dat', + 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat', + 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'ab.dat.copy', + 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat.copy', + ) + ), + array('/^with space\//', 'foobar', + array( + 'with space'.DIRECTORY_SEPARATOR.'foo.txt', + ) + ), + ); + + return $this->buildTestData($tests); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testAccessDeniedException(Adapter\AdapterInterface $adapter) + { + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + $this->markTestSkipped('chmod is not supported on windows'); + } + + $finder = $this->buildFinder($adapter); + $finder->files()->in(self::$tmpDir); + + // make 'foo' directory non-readable + chmod(self::$tmpDir.DIRECTORY_SEPARATOR.'foo', 0333); + + try { + $this->assertIterator($this->toAbsolute(array('foo bar', 'test.php', 'test.py')), $finder->getIterator()); + $this->fail('Finder should throw an exception when opening a non-readable directory.'); + } catch (\Exception $e) { + $this->assertEquals('Symfony\\Component\\Finder\\Exception\\AccessDeniedException', get_class($e)); + } + + // restore original permissions + chmod(self::$tmpDir.DIRECTORY_SEPARATOR.'foo', 0777); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testIgnoredAccessDeniedException(Adapter\AdapterInterface $adapter) + { + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + $this->markTestSkipped('chmod is not supported on windows'); + } + + $finder = $this->buildFinder($adapter); + $finder->files()->ignoreUnreadableDirs()->in(self::$tmpDir); + + // make 'foo' directory non-readable + chmod(self::$tmpDir.DIRECTORY_SEPARATOR.'foo', 0333); + + $this->assertIterator($this->toAbsolute(array('foo bar', 'test.php', 'test.py')), $finder->getIterator()); + + // restore original permissions + chmod(self::$tmpDir.DIRECTORY_SEPARATOR.'foo', 0777); + } + + private function buildTestData(array $tests) + { + $data = array(); + foreach ($this->getValidAdapters() as $adapter) { + foreach ($tests as $test) { + $data[] = array_merge(array($adapter), $test); + } + } + + return $data; + } + + private function buildFinder(Adapter\AdapterInterface $adapter) + { + return Finder::create() + ->removeAdapters() + ->addAdapter($adapter); + } + + private function getValidAdapters() + { + return array_filter( + array( + new Adapter\BsdFindAdapter(), + new Adapter\GnuFindAdapter(), + new Adapter\PhpAdapter() + ), + function (Adapter\AdapterInterface $adapter) { + return $adapter->isSupported(); + } + ); + } + + /** + * Searching in multiple locations with sub directories involves + * AppendIterator which does an unnecessary rewind which leaves + * FilterIterator with inner FilesystemIterator in an ivalid state. + * + * @see https://bugs.php.net/bug.php?id=49104 + */ + public function testMultipleLocationsWithSubDirectories() + { + $locations = array( + __DIR__.'/Fixtures/one', + self::$tmpDir.DIRECTORY_SEPARATOR.'toto', + ); + + $finder = new Finder(); + $finder->in($locations)->depth('< 10')->name('*.neon'); + + $expected = array( + __DIR__.'/Fixtures/one'.DIRECTORY_SEPARATOR.'b'.DIRECTORY_SEPARATOR.'c.neon', + __DIR__.'/Fixtures/one'.DIRECTORY_SEPARATOR.'b'.DIRECTORY_SEPARATOR.'d.neon', + ); + + $this->assertIterator($expected, $finder); + $this->assertIteratorInForeach($expected, $finder); + } + + public function testNonSeekableStream() + { + try { + $i = Finder::create()->in('ftp://ftp.mozilla.org/')->depth(0)->getIterator(); + } catch (\UnexpectedValueException $e) { + $this->markTestSkipped(sprintf('Unsupported stream "%s".', 'ftp')); + } + + $contains = array( + 'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'README', + 'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'index.html', + 'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'pub', + ); + + $this->assertIteratorInForeach($contains, $i); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/A/B/C/abc.dat b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/A/B/C/abc.dat new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/A/B/ab.dat b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/A/B/ab.dat new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/A/a.dat b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/A/a.dat new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/copy/A/B/C/abc.dat.copy b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/copy/A/B/C/abc.dat.copy new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/copy/A/B/ab.dat.copy b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/copy/A/B/ab.dat.copy new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/copy/A/a.dat.copy b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/copy/A/a.dat.copy new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/dolor.txt b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/dolor.txt new file mode 100644 index 0000000..658bec6 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/dolor.txt @@ -0,0 +1,2 @@ +dolor sit amet +DOLOR SIT AMET \ No newline at end of file diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/ipsum.txt b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/ipsum.txt new file mode 100644 index 0000000..c7f392d --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/ipsum.txt @@ -0,0 +1,2 @@ +ipsum dolor sit amet +IPSUM DOLOR SIT AMET \ No newline at end of file diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/lorem.txt b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/lorem.txt new file mode 100644 index 0000000..2991a2c --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/lorem.txt @@ -0,0 +1,2 @@ +lorem ipsum dolor sit amet +LOREM IPSUM DOLOR SIT AMET \ No newline at end of file diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/one/a b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/one/a new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/one/b/c.neon b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/one/b/c.neon new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/one/b/d.neon b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/one/b/d.neon new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/with space/foo.txt b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Fixtures/with space/foo.txt new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/CustomFilterIteratorTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/CustomFilterIteratorTest.php new file mode 100644 index 0000000..62629b1 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/CustomFilterIteratorTest.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Iterator; + +use Symfony\Component\Finder\Iterator\CustomFilterIterator; + +class CustomFilterIteratorTest extends IteratorTestCase +{ + /** + * @expectedException \InvalidArgumentException + */ + public function testWithInvalidFilter() + { + new CustomFilterIterator(new Iterator(), array('foo')); + } + + /** + * @dataProvider getAcceptData + */ + public function testAccept($filters, $expected) + { + $inner = new Iterator(array('test.php', 'test.py', 'foo.php')); + + $iterator = new CustomFilterIterator($inner, $filters); + + $this->assertIterator($expected, $iterator); + } + + public function getAcceptData() + { + return array( + array(array(function (\SplFileInfo $fileinfo) { return false; }), array()), + array(array(function (\SplFileInfo $fileinfo) { return preg_match('/^test/', $fileinfo) > 0; }), array('test.php', 'test.py')), + array(array('is_dir'), array()), + ); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/DateRangeFilterIteratorTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/DateRangeFilterIteratorTest.php new file mode 100644 index 0000000..18896d5 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/DateRangeFilterIteratorTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Iterator; + +use Symfony\Component\Finder\Iterator\DateRangeFilterIterator; +use Symfony\Component\Finder\Comparator\DateComparator; + +class DateRangeFilterIteratorTest extends RealIteratorTestCase +{ + /** + * @dataProvider getAcceptData + */ + public function testAccept($size, $expected) + { + $inner = new Iterator(self::$files); + + $iterator = new DateRangeFilterIterator($inner, $size); + + $this->assertIterator($expected, $iterator); + } + + public function getAcceptData() + { + $since20YearsAgo = array( + '.git', + 'test.py', + 'foo', + 'foo/bar.tmp', + 'test.php', + 'toto', + '.bar', + '.foo', + '.foo/.bar', + 'foo bar', + '.foo/bar', + ); + + $since2MonthsAgo = array( + '.git', + 'test.py', + 'foo', + 'toto', + '.bar', + '.foo', + '.foo/.bar', + 'foo bar', + '.foo/bar', + ); + + $untilLastMonth = array( + '.git', + 'foo', + 'foo/bar.tmp', + 'test.php', + 'toto', + '.foo', + ); + + return array( + array(array(new DateComparator('since 20 years ago')), $this->toAbsolute($since20YearsAgo)), + array(array(new DateComparator('since 2 months ago')), $this->toAbsolute($since2MonthsAgo)), + array(array(new DateComparator('until last month')), $this->toAbsolute($untilLastMonth)), + ); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/DepthRangeFilterIteratorTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/DepthRangeFilterIteratorTest.php new file mode 100644 index 0000000..be06f1c --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/DepthRangeFilterIteratorTest.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Iterator; + +use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator; + +class DepthRangeFilterIteratorTest extends RealIteratorTestCase +{ + /** + * @dataProvider getAcceptData + */ + public function testAccept($minDepth, $maxDepth, $expected) + { + $inner = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->toAbsolute(), \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST); + + $iterator = new DepthRangeFilterIterator($inner, $minDepth, $maxDepth); + + $actual = array_keys(iterator_to_array($iterator)); + sort($expected); + sort($actual); + $this->assertEquals($expected, $actual); + } + + public function getAcceptData() + { + $lessThan1 = array( + '.git', + 'test.py', + 'foo', + 'test.php', + 'toto', + '.foo', + '.bar', + 'foo bar', + ); + + $lessThanOrEqualTo1 = array( + '.git', + 'test.py', + 'foo', + 'foo/bar.tmp', + 'test.php', + 'toto', + '.foo', + '.foo/.bar', + '.bar', + 'foo bar', + '.foo/bar', + ); + + $graterThanOrEqualTo1 = array( + 'foo/bar.tmp', + '.foo/.bar', + '.foo/bar', + ); + + $equalTo1 = array( + 'foo/bar.tmp', + '.foo/.bar', + '.foo/bar', + ); + + return array( + array(0, 0, $this->toAbsolute($lessThan1)), + array(0, 1, $this->toAbsolute($lessThanOrEqualTo1)), + array(2, PHP_INT_MAX, array()), + array(1, PHP_INT_MAX, $this->toAbsolute($graterThanOrEqualTo1)), + array(1, 1, $this->toAbsolute($equalTo1)), + ); + } + +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/ExcludeDirectoryFilterIteratorTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/ExcludeDirectoryFilterIteratorTest.php new file mode 100644 index 0000000..e299fe7 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/ExcludeDirectoryFilterIteratorTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Iterator; + +use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator; +use Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator; + +class ExcludeDirectoryFilterIteratorTest extends RealIteratorTestCase +{ + /** + * @dataProvider getAcceptData + */ + public function testAccept($directories, $expected) + { + $inner = new \RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->toAbsolute(), \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST); + + $iterator = new ExcludeDirectoryFilterIterator($inner, $directories); + + $this->assertIterator($expected, $iterator); + } + + public function getAcceptData() + { + $foo = array( + '.bar', + '.foo', + '.foo/.bar', + '.foo/bar', + '.git', + 'test.py', + 'test.php', + 'toto', + 'foo bar' + ); + + $fo = array( + '.bar', + '.foo', + '.foo/.bar', + '.foo/bar', + '.git', + 'test.py', + 'foo', + 'foo/bar.tmp', + 'test.php', + 'toto', + 'foo bar' + ); + + return array( + array(array('foo'), $this->toAbsolute($foo)), + array(array('fo'), $this->toAbsolute($fo)), + ); + } + +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilePathsIteratorTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilePathsIteratorTest.php new file mode 100644 index 0000000..61f0e9b --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilePathsIteratorTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Iterator; + +use Symfony\Component\Finder\Iterator\FilePathsIterator; + +class FilePathsIteratorTest extends RealIteratorTestCase +{ + /** + * @dataProvider getSubPathData + */ + public function testSubPath($baseDir, array $paths, array $subPaths, array $subPathnames) + { + $iterator = new FilePathsIterator($paths, $baseDir); + + foreach ($iterator as $index => $file) { + $this->assertEquals($paths[$index], $file->getPathname()); + $this->assertEquals($subPaths[$index], $iterator->getSubPath()); + $this->assertEquals($subPathnames[$index], $iterator->getSubPathname()); + } + } + + public function getSubPathData() + { + $tmpDir = sys_get_temp_dir().'/symfony2_finder'; + + return array( + array( + $tmpDir, + array( // paths + $tmpDir.DIRECTORY_SEPARATOR.'.git' => $tmpDir.DIRECTORY_SEPARATOR.'.git', + $tmpDir.DIRECTORY_SEPARATOR.'test.py' => $tmpDir.DIRECTORY_SEPARATOR.'test.py', + $tmpDir.DIRECTORY_SEPARATOR.'foo' => $tmpDir.DIRECTORY_SEPARATOR.'foo', + $tmpDir.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'bar.tmp' => $tmpDir.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'bar.tmp', + $tmpDir.DIRECTORY_SEPARATOR.'test.php' => $tmpDir.DIRECTORY_SEPARATOR.'test.php', + $tmpDir.DIRECTORY_SEPARATOR.'toto' => $tmpDir.DIRECTORY_SEPARATOR.'toto' + ), + array( // subPaths + $tmpDir.DIRECTORY_SEPARATOR.'.git' => '', + $tmpDir.DIRECTORY_SEPARATOR.'test.py' => '', + $tmpDir.DIRECTORY_SEPARATOR.'foo' => '', + $tmpDir.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'bar.tmp' => 'foo', + $tmpDir.DIRECTORY_SEPARATOR.'test.php' => '', + $tmpDir.DIRECTORY_SEPARATOR.'toto' => '' + ), + array( // subPathnames + $tmpDir.DIRECTORY_SEPARATOR.'.git' => '.git', + $tmpDir.DIRECTORY_SEPARATOR.'test.py' => 'test.py', + $tmpDir.DIRECTORY_SEPARATOR.'foo' => 'foo', + $tmpDir.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'bar.tmp' => 'foo'.DIRECTORY_SEPARATOR.'bar.tmp', + $tmpDir.DIRECTORY_SEPARATOR.'test.php' => 'test.php', + $tmpDir.DIRECTORY_SEPARATOR.'toto' => 'toto' + ), + ), + ); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FileTypeFilterIteratorTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FileTypeFilterIteratorTest.php new file mode 100644 index 0000000..b2432ca --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FileTypeFilterIteratorTest.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Iterator; + +use Symfony\Component\Finder\Iterator\FileTypeFilterIterator; + +class FileTypeFilterIteratorTest extends RealIteratorTestCase +{ + /** + * @dataProvider getAcceptData + */ + public function testAccept($mode, $expected) + { + $inner = new InnerTypeIterator(self::$files); + + $iterator = new FileTypeFilterIterator($inner, $mode); + + $this->assertIterator($expected, $iterator); + } + + public function getAcceptData() + { + $onlyFiles = array( + 'test.py', + 'foo/bar.tmp', + 'test.php', + '.bar', + '.foo/.bar', + '.foo/bar', + 'foo bar', + ); + + $onlyDirectories = array( + '.git', + 'foo', + 'toto', + '.foo', + ); + + return array( + array(FileTypeFilterIterator::ONLY_FILES, $this->toAbsolute($onlyFiles)), + array(FileTypeFilterIterator::ONLY_DIRECTORIES, $this->toAbsolute($onlyDirectories)), + ); + } +} + +class InnerTypeIterator extends \ArrayIterator +{ + public function current() + { + return new \SplFileInfo(parent::current()); + } + + public function isFile() + { + return $this->current()->isFile(); + } + + public function isDir() + { + return $this->current()->isDir(); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilecontentFilterIteratorTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilecontentFilterIteratorTest.php new file mode 100644 index 0000000..5f2b398 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilecontentFilterIteratorTest.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Iterator; + +use Symfony\Component\Finder\Iterator\FilecontentFilterIterator; +use Symfony\Component\Finder\Tests\Iterator\MockSplFileInfo; +use Symfony\Component\Finder\Tests\Iterator\MockFileListIterator; + +class FilecontentFilterIteratorTest extends IteratorTestCase +{ + + public function testAccept() + { + $inner = new MockFileListIterator(array('test.txt')); + $iterator = new FilecontentFilterIterator($inner, array(), array()); + $this->assertIterator(array('test.txt'), $iterator); + } + + public function testDirectory() + { + $inner = new MockFileListIterator(array('directory')); + $iterator = new FilecontentFilterIterator($inner, array('directory'), array()); + $this->assertIterator(array(), $iterator); + } + + public function testUnreadableFile() + { + $inner = new MockFileListIterator(array('file r-')); + $iterator = new FilecontentFilterIterator($inner, array('file r-'), array()); + $this->assertIterator(array(), $iterator); + } + + /** + * @dataProvider getTestFilterData + */ + public function testFilter(\Iterator $inner, array $matchPatterns, array $noMatchPatterns, array $resultArray) + { + $iterator = new FilecontentFilterIterator($inner, $matchPatterns, $noMatchPatterns); + $this->assertIterator($resultArray, $iterator); + } + + public function getTestFilterData() + { + $inner = new MockFileListIterator(); + + $inner[] = new MockSplFileInfo(array( + 'name' => 'a.txt', + 'contents' => 'Lorem ipsum...', + 'type' => 'file', + 'mode' => 'r+') + ); + + $inner[] = new MockSplFileInfo(array( + 'name' => 'b.yml', + 'contents' => 'dolor sit...', + 'type' => 'file', + 'mode' => 'r+') + ); + + $inner[] = new MockSplFileInfo(array( + 'name' => 'some/other/dir/third.php', + 'contents' => 'amet...', + 'type' => 'file', + 'mode' => 'r+') + ); + + $inner[] = new MockSplFileInfo(array( + 'name' => 'unreadable-file.txt', + 'contents' => false, + 'type' => 'file', + 'mode' => 'r+') + ); + + return array( + array($inner, array('.'), array(), array('a.txt', 'b.yml', 'some/other/dir/third.php')), + array($inner, array('ipsum'), array(), array('a.txt')), + array($inner, array('i', 'amet'), array('Lorem', 'amet'), array('b.yml')), + ); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilenameFilterIteratorTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilenameFilterIteratorTest.php new file mode 100644 index 0000000..c4b9795 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilenameFilterIteratorTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Iterator; + +use Symfony\Component\Finder\Iterator\FilenameFilterIterator; + +class FilenameFilterIteratorTest extends IteratorTestCase +{ + /** + * @dataProvider getAcceptData + */ + public function testAccept($matchPatterns, $noMatchPatterns, $expected) + { + $inner = new InnerNameIterator(array('test.php', 'test.py', 'foo.php')); + + $iterator = new FilenameFilterIterator($inner, $matchPatterns, $noMatchPatterns); + + $this->assertIterator($expected, $iterator); + } + + public function getAcceptData() + { + return array( + array(array('test.*'), array(), array('test.php', 'test.py')), + array(array(), array('test.*'), array('foo.php')), + array(array('*.php'), array('test.*'), array('foo.php')), + array(array('*.php', '*.py'), array('foo.*'), array('test.php', 'test.py')), + array(array('/\.php$/'), array(), array('test.php', 'foo.php')), + array(array(), array('/\.php$/'), array('test.py')), + ); + } +} + +class InnerNameIterator extends \ArrayIterator +{ + public function current() + { + return new \SplFileInfo(parent::current()); + } + + public function getFilename() + { + return parent::current(); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilterIteratorTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilterIteratorTest.php new file mode 100644 index 0000000..029a266 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/FilterIteratorTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Iterator; + +/** + * @author Alex Bogomazov + */ +class FilterIteratorTest extends RealIteratorTestCase +{ + public function testFilterFilesystemIterators() + { + $i = new \FilesystemIterator($this->toAbsolute()); + + // it is expected that there are test.py test.php in the tmpDir + $i = $this->getMockForAbstractClass('Symfony\Component\Finder\Iterator\FilterIterator', array($i)); + $i->expects($this->any()) + ->method('accept') + ->will($this->returnCallback(function () use ($i) { + return (bool) preg_match('/\.php/', (string) $i->current()); + }) + ); + + $c = 0; + foreach ($i as $item) { + $c++; + } + + $this->assertEquals(1, $c); + + $i->rewind(); + + $c = 0; + foreach ($i as $item) { + $c++; + } + + // This would fail with \FilterIterator but works with Symfony\Component\Finder\Iterator\FilterIterator + // see https://bugs.php.net/bug.php?id=49104 + $this->assertEquals(1, $c); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/Iterator.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/Iterator.php new file mode 100644 index 0000000..7ffd382 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/Iterator.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Iterator; + +class Iterator implements \Iterator +{ + protected $values; + + public function __construct(array $values = array()) + { + $this->values = array(); + foreach ($values as $value) { + $this->attach(new \SplFileInfo($value)); + } + $this->rewind(); + } + + public function attach(\SplFileInfo $fileinfo) + { + $this->values[] = $fileinfo; + } + + public function rewind() + { + reset($this->values); + } + + public function valid() + { + return false !== $this->current(); + } + + public function next() + { + next($this->values); + } + + public function current() + { + return current($this->values); + } + + public function key() + { + return key($this->values); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php new file mode 100644 index 0000000..504cfb0 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Iterator; + +abstract class IteratorTestCase extends \PHPUnit_Framework_TestCase +{ + protected function assertIterator($expected, \Traversable $iterator) + { + // set iterator_to_array $use_key to false to avoid values merge + // this made FinderTest::testAppendWithAnArray() failed with GnuFinderAdapter + $values = array_map(function (\SplFileInfo $fileinfo) { return str_replace('/', DIRECTORY_SEPARATOR, $fileinfo->getPathname()); }, iterator_to_array($iterator, false)); + + $expected = array_map(function ($path) { return str_replace('/', DIRECTORY_SEPARATOR, $path); }, $expected); + + sort($values); + sort($expected); + + $this->assertEquals($expected, array_values($values)); + } + + protected function assertOrderedIterator($expected, \Traversable $iterator) + { + $values = array_map(function (\SplFileInfo $fileinfo) { return $fileinfo->getPathname(); }, iterator_to_array($iterator)); + + $this->assertEquals($expected, array_values($values)); + } + + /** + * Same as IteratorTestCase::assertIterator with foreach usage + * + * @param array $expected + * @param \Traversable $iterator + */ + protected function assertIteratorInForeach($expected, \Traversable $iterator) + { + $values = array(); + foreach ($iterator as $file) { + $this->assertInstanceOf('Symfony\\Component\\Finder\\SplFileInfo', $file); + $values[] = $file->getPathname(); + } + + sort($values); + sort($expected); + + $this->assertEquals($expected, array_values($values)); + } + + /** + * Same as IteratorTestCase::assertOrderedIterator with foreach usage + * + * @param array $expected + * @param \Traversable $iterator + */ + protected function assertOrderedIteratorInForeach($expected, \Traversable $iterator) + { + $values = array(); + foreach ($iterator as $file) { + $this->assertInstanceOf('Symfony\\Component\\Finder\\SplFileInfo', $file); + $values[] = $file->getPathname(); + } + + $this->assertEquals($expected, array_values($values)); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php new file mode 100644 index 0000000..6d8ae39 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MockFileListIterator.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Iterator; + +class MockFileListIterator extends \ArrayIterator +{ + public function __construct(array $filesArray = array()) + { + $files = array_map(function($file){ return new MockSplFileInfo($file); }, $filesArray); + parent::__construct($files); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php new file mode 100644 index 0000000..a50e4c9 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MockSplFileInfo.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Iterator; + +class MockSplFileInfo extends \SplFileInfo +{ + const TYPE_DIRECTORY = 1; + const TYPE_FILE = 2; + const TYPE_UNKNOWN = 3; + + private $contents = null; + private $mode = null; + private $type = null; + private $relativePath = null; + private $relativePathname = null; + + public function __construct($param) + { + if (is_string($param)) { + parent::__construct($param); + } elseif (is_array($param)) { + $defaults = array( + 'name' => 'file.txt', + 'contents' => null, + 'mode' => null, + 'type' => null, + 'relativePath' => null, + 'relativePathname' => null, + ); + $defaults = array_merge($defaults, $param); + parent::__construct($defaults['name']); + $this->setContents($defaults['contents']); + $this->setMode($defaults['mode']); + $this->setType($defaults['type']); + $this->setRelativePath($defaults['relativePath']); + $this->setRelativePathname($defaults['relativePathname']); + } else { + throw new \RuntimeException(sprintf('Incorrect parameter "%s"', $param)); + } + } + + public function isFile() + { + if ($this->type === null) { + return preg_match('/file/', $this->getFilename()); + }; + + return self::TYPE_FILE === $this->type; + } + + public function isDir() + { + if ($this->type === null) { + return preg_match('/directory/', $this->getFilename()); + } + + return self::TYPE_DIRECTORY === $this->type; + } + + public function isReadable() + { + if ($this->mode === null) { + return preg_match('/r\+/', $this->getFilename()); + } + + return preg_match('/r\+/', $this->mode); + } + + public function getContents() + { + return $this->contents; + } + + public function setContents($contents) + { + $this->contents = $contents; + } + + public function setMode($mode) + { + $this->mode = $mode; + } + + public function setType($type) + { + if (is_string($type)) { + switch ($type) { + case 'directory': + $this->type = self::TYPE_DIRECTORY; + case 'd': + $this->type = self::TYPE_DIRECTORY; + break; + case 'file': + $this->type = self::TYPE_FILE; + case 'f': + $this->type = self::TYPE_FILE; + break; + default: + $this->type = self::TYPE_UNKNOWN; + } + } else { + $this->type = $type; + } + } + + public function setRelativePath($relativePath) + { + $this->relativePath = $relativePath; + } + + public function setRelativePathname($relativePathname) + { + $this->relativePathname = $relativePathname; + } + + public function getRelativePath() + { + return $this->relativePath; + } + + public function getRelativePathname() + { + return $this->relativePathname; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MultiplePcreFilterIteratorTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MultiplePcreFilterIteratorTest.php new file mode 100644 index 0000000..89d8edb --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/MultiplePcreFilterIteratorTest.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Iterator; + +use Symfony\Component\Finder\Iterator\MultiplePcreFilterIterator; + +class MultiplePcreFilterIteratorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getIsRegexFixtures + */ + public function testIsRegex($string, $isRegex, $message) + { + $testIterator = new TestMultiplePcreFilterIterator(); + $this->assertEquals($isRegex, $testIterator->isRegex($string), $message); + } + + public function getIsRegexFixtures() + { + return array( + array('foo', false, 'string'), + array(' foo ', false, '" " is not a valid delimiter'), + array('\\foo\\', false, '"\\" is not a valid delimiter'), + array('afooa', false, '"a" is not a valid delimiter'), + array('//', false, 'the pattern should contain at least 1 character'), + array('/a/', true, 'valid regex'), + array('/foo/', true, 'valid regex'), + array('/foo/i', true, 'valid regex with a single modifier'), + array('/foo/imsxu', true, 'valid regex with multiple modifiers'), + array('#foo#', true, '"#" is a valid delimiter'), + array('{foo}', true, '"{,}" is a valid delimiter pair'), + array('*foo.*', false, '"*" is not considered as a valid delimiter'), + array('?foo.?', false, '"?" is not considered as a valid delimiter'), + ); + } +} + +class TestMultiplePcreFilterIterator extends MultiplePcreFilterIterator +{ + public function __construct() + { + } + + public function accept() + { + throw new \BadFunctionCallException('Not implemented'); + } + + public function isRegex($str) + { + return parent::isRegex($str); + } + + public function toRegex($str) + { + throw new \BadFunctionCallException('Not implemented'); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/PathFilterIteratorTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/PathFilterIteratorTest.php new file mode 100644 index 0000000..6f83e44 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/PathFilterIteratorTest.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Iterator; + +use Symfony\Component\Finder\Iterator\PathFilterIterator; + +class PathFilterIteratorTest extends IteratorTestCase +{ + + /** + * @dataProvider getTestFilterData + */ + public function testFilter(\Iterator $inner, array $matchPatterns, array $noMatchPatterns, array $resultArray) + { + $iterator = new PathFilterIterator($inner, $matchPatterns, $noMatchPatterns); + $this->assertIterator($resultArray, $iterator); + } + + public function getTestFilterData() + { + $inner = new MockFileListIterator(); + + //PATH: A/B/C/abc.dat + $inner[] = new MockSplFileInfo(array( + 'name' => 'abc.dat', + 'relativePathname' => 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat', + )); + + //PATH: A/B/ab.dat + $inner[] = new MockSplFileInfo(array( + 'name' => 'ab.dat', + 'relativePathname' => 'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'ab.dat', + )); + + //PATH: A/a.dat + $inner[] = new MockSplFileInfo(array( + 'name' => 'a.dat', + 'relativePathname' => 'A'.DIRECTORY_SEPARATOR.'a.dat', + )); + + //PATH: copy/A/B/C/abc.dat.copy + $inner[] = new MockSplFileInfo(array( + 'name' => 'abc.dat.copy', + 'relativePathname' => 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'C'.DIRECTORY_SEPARATOR.'abc.dat', + )); + + //PATH: copy/A/B/ab.dat.copy + $inner[] = new MockSplFileInfo(array( + 'name' => 'ab.dat.copy', + 'relativePathname' => 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'B'.DIRECTORY_SEPARATOR.'ab.dat', + )); + + //PATH: copy/A/a.dat.copy + $inner[] = new MockSplFileInfo(array( + 'name' => 'a.dat.copy', + 'relativePathname' => 'copy'.DIRECTORY_SEPARATOR.'A'.DIRECTORY_SEPARATOR.'a.dat', + )); + + return array( + array($inner, array('/^A/'), array(), array('abc.dat', 'ab.dat', 'a.dat')), + array($inner, array('/^A\/B/'), array(), array('abc.dat', 'ab.dat')), + array($inner, array('/^A\/B\/C/'), array(), array('abc.dat')), + array($inner, array('/A\/B\/C/'), array(), array('abc.dat', 'abc.dat.copy')), + + array($inner, array('A'), array(), array('abc.dat', 'ab.dat', 'a.dat', 'abc.dat.copy', 'ab.dat.copy', 'a.dat.copy')), + array($inner, array('A/B'), array(), array('abc.dat', 'ab.dat', 'abc.dat.copy', 'ab.dat.copy')), + array($inner, array('A/B/C'), array(), array('abc.dat', 'abc.dat.copy')), + + array($inner, array('copy/A'), array(), array('abc.dat.copy', 'ab.dat.copy', 'a.dat.copy')), + array($inner, array('copy/A/B'), array(), array('abc.dat.copy', 'ab.dat.copy')), + array($inner, array('copy/A/B/C'), array(), array('abc.dat.copy')), + + ); + } + +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/RealIteratorTestCase.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/RealIteratorTestCase.php new file mode 100644 index 0000000..65edb1e --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/RealIteratorTestCase.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Iterator; + +abstract class RealIteratorTestCase extends IteratorTestCase +{ + + protected static $tmpDir; + protected static $files; + + public static function setUpBeforeClass() + { + self::$tmpDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'symfony2_finder'; + + self::$files = array( + '.git/', + '.foo/', + '.foo/.bar', + '.foo/bar', + '.bar', + 'test.py', + 'foo/', + 'foo/bar.tmp', + 'test.php', + 'toto/', + 'foo bar' + ); + + self::$files = self::toAbsolute(self::$files); + + if (is_dir(self::$tmpDir)) { + self::tearDownAfterClass(); + } else { + mkdir(self::$tmpDir); + } + + foreach (self::$files as $file) { + if (DIRECTORY_SEPARATOR === $file[strlen($file) - 1]) { + mkdir($file); + } else { + touch($file); + } + } + + file_put_contents(self::toAbsolute('test.php'), str_repeat(' ', 800)); + file_put_contents(self::toAbsolute('test.py'), str_repeat(' ', 2000)); + + touch(self::toAbsolute('foo/bar.tmp'), strtotime('2005-10-15')); + touch(self::toAbsolute('test.php'), strtotime('2005-10-15')); + } + + public static function tearDownAfterClass() + { + foreach (array_reverse(self::$files) as $file) { + if (DIRECTORY_SEPARATOR === $file[strlen($file) - 1]) { + @rmdir($file); + } else { + @unlink($file); + } + } + } + + protected static function toAbsolute($files = null) + { + /* + * Without the call to setUpBeforeClass() property can be null. + */ + if (!self::$tmpDir) { + self::$tmpDir = realpath(sys_get_temp_dir()).DIRECTORY_SEPARATOR.'symfony2_finder'; + } + + if (is_array($files)) { + $f = array(); + foreach ($files as $file) { + $f[] = self::$tmpDir.DIRECTORY_SEPARATOR.str_replace('/', DIRECTORY_SEPARATOR, $file); + } + + return $f; + } + + if (is_string($files)) { + return self::$tmpDir.DIRECTORY_SEPARATOR.str_replace('/', DIRECTORY_SEPARATOR, $files); + } + + return self::$tmpDir; + } + + protected static function toAbsoluteFixtures($files) + { + $f = array(); + foreach ($files as $file) { + $f[] = realpath(__DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.$file); + } + + return $f; + } + +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php new file mode 100644 index 0000000..f762514 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php @@ -0,0 +1,83 @@ + +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code. +*/ + +namespace Symfony\Component\Finder\Tests\Iterator; + +use Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator; + +class RecursiveDirectoryIteratorTest extends IteratorTestCase +{ + /** + * @dataProvider getPaths + * + * @param string $path + * @param Boolean $seekable + * @param Boolean $supports + * @param string $message + */ + public function testRewind($path, $seekable, $contains, $message = null) + { + try { + $i = new RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS); + } catch (\UnexpectedValueException $e) { + $this->markTestSkipped(sprintf('Unsupported stream "%s".', $path)); + } + + $i->rewind(); + + $this->assertTrue(true, $message); + } + + /** + * @dataProvider getPaths + * + * @param string $path + * @param Boolean $seekable + * @param Boolean $supports + * @param string $message + */ + public function testSeek($path, $seekable, $contains, $message = null) + { + try { + $i = new RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS); + } catch (\UnexpectedValueException $e) { + $this->markTestSkipped(sprintf('Unsupported stream "%s".', $path)); + } + + $actual = array(); + + $i->seek(0); + $actual[] = $i->getPathname(); + + $i->seek(1); + $actual[] = $i->getPathname(); + + $i->seek(2); + $actual[] = $i->getPathname(); + + $this->assertEquals($contains, $actual); + } + + public function getPaths() + { + $data = array(); + + // ftp + $contains = array( + 'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'README', + 'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'index.html', + 'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'pub', + ); + $data[] = array('ftp://ftp.mozilla.org/', false, $contains); + + return $data; + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/SizeRangeFilterIteratorTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/SizeRangeFilterIteratorTest.php new file mode 100644 index 0000000..726df9e --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/SizeRangeFilterIteratorTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Iterator; + +use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator; +use Symfony\Component\Finder\Comparator\NumberComparator; + +class SizeRangeFilterIteratorTest extends RealIteratorTestCase +{ + /** + * @dataProvider getAcceptData + */ + public function testAccept($size, $expected) + { + $inner = new InnerSizeIterator(self::$files); + + $iterator = new SizeRangeFilterIterator($inner, $size); + + $this->assertIterator($expected, $iterator); + } + + public function getAcceptData() + { + $lessThan1KGreaterThan05K = array( + '.foo', + '.git', + 'foo', + 'test.php', + 'toto', + ); + + return array( + array(array(new NumberComparator('< 1K'), new NumberComparator('> 0.5K')), $this->toAbsolute($lessThan1KGreaterThan05K)), + ); + } +} + +class InnerSizeIterator extends \ArrayIterator +{ + public function current() + { + return new \SplFileInfo(parent::current()); + } + + public function getFilename() + { + return parent::current(); + } + + public function isFile() + { + return $this->current()->isFile(); + } + + public function getSize() + { + return $this->current()->getSize(); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php new file mode 100644 index 0000000..64e1e3e --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/Tests/Iterator/SortableIteratorTest.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Tests\Iterator; + +use Symfony\Component\Finder\Iterator\SortableIterator; + +class SortableIteratorTest extends RealIteratorTestCase +{ + public function testConstructor() + { + try { + new SortableIterator(new Iterator(array()), 'foobar'); + $this->fail('__construct() throws an \InvalidArgumentException exception if the mode is not valid'); + } catch (\Exception $e) { + $this->assertInstanceOf('InvalidArgumentException', $e, '__construct() throws an \InvalidArgumentException exception if the mode is not valid'); + } + } + + /** + * @dataProvider getAcceptData + */ + public function testAccept($mode, $expected) + { + $inner = new Iterator(self::$files); + + $iterator = new SortableIterator($inner, $mode); + + $this->assertOrderedIterator($expected, $iterator); + } + + public function getAcceptData() + { + + $sortByName = array( + '.bar', + '.foo', + '.foo/.bar', + '.foo/bar', + '.git', + 'foo', + 'foo bar', + 'foo/bar.tmp', + 'test.php', + 'test.py', + 'toto', + ); + + $sortByType = array( + '.foo', + '.git', + 'foo', + 'toto', + '.bar', + '.foo/.bar', + '.foo/bar', + 'foo bar', + 'foo/bar.tmp', + 'test.php', + 'test.py', + ); + + $customComparison = array( + '.bar', + '.foo', + '.foo/.bar', + '.foo/bar', + '.git', + 'foo', + 'foo bar', + 'foo/bar.tmp', + 'test.php', + 'test.py', + 'toto', + ); + + return array( + array(SortableIterator::SORT_BY_NAME, $this->toAbsolute($sortByName)), + array(SortableIterator::SORT_BY_TYPE, $this->toAbsolute($sortByType)), + array(function (\SplFileInfo $a, \SplFileInfo $b) { return strcmp($a->getRealpath(), $b->getRealpath()); }, $this->toAbsolute($customComparison)), + ); + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/composer.json b/vendor/symfony/finder/Symfony/Component/Finder/composer.json new file mode 100644 index 0000000..7480b3e --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/composer.json @@ -0,0 +1,31 @@ +{ + "name": "symfony/finder", + "type": "library", + "description": "Symfony Finder Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\Finder\\": "" } + }, + "target-dir": "Symfony/Component/Finder", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + } +} diff --git a/vendor/symfony/finder/Symfony/Component/Finder/phpunit.xml.dist b/vendor/symfony/finder/Symfony/Component/Finder/phpunit.xml.dist new file mode 100644 index 0000000..2327223 --- /dev/null +++ b/vendor/symfony/finder/Symfony/Component/Finder/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/.gitignore b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/AcceptHeader.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/AcceptHeader.php new file mode 100644 index 0000000..48c10c1 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/AcceptHeader.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents an Accept-* header. + * + * An accept header is compound with a list of items, + * sorted by descending quality. + * + * @author Jean-François Simon + */ +class AcceptHeader +{ + /** + * @var AcceptHeaderItem[] + */ + private $items = array(); + + /** + * @var bool + */ + private $sorted = true; + + /** + * Constructor. + * + * @param AcceptHeaderItem[] $items + */ + public function __construct(array $items) + { + foreach ($items as $item) { + $this->add($item); + } + } + + /** + * Builds an AcceptHeader instance from a string. + * + * @param string $headerValue + * + * @return AcceptHeader + */ + public static function fromString($headerValue) + { + $index = 0; + + return new self(array_map(function ($itemValue) use (&$index) { + $item = AcceptHeaderItem::fromString($itemValue); + $item->setIndex($index++); + + return $item; + }, preg_split('/\s*(?:,*("[^"]+"),*|,*(\'[^\']+\'),*|,+)\s*/', $headerValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE))); + } + + /** + * Returns header value's string representation. + * + * @return string + */ + public function __toString() + { + return implode(',', $this->items); + } + + /** + * Tests if header has given value. + * + * @param string $value + * + * @return Boolean + */ + public function has($value) + { + return isset($this->items[$value]); + } + + /** + * Returns given value's item, if exists. + * + * @param string $value + * + * @return AcceptHeaderItem|null + */ + public function get($value) + { + return isset($this->items[$value]) ? $this->items[$value] : null; + } + + /** + * Adds an item. + * + * @param AcceptHeaderItem $item + * + * @return AcceptHeader + */ + public function add(AcceptHeaderItem $item) + { + $this->items[$item->getValue()] = $item; + $this->sorted = false; + + return $this; + } + + /** + * Returns all items. + * + * @return AcceptHeaderItem[] + */ + public function all() + { + $this->sort(); + + return $this->items; + } + + /** + * Filters items on their value using given regex. + * + * @param string $pattern + * + * @return AcceptHeader + */ + public function filter($pattern) + { + return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) { + return preg_match($pattern, $item->getValue()); + })); + } + + /** + * Returns first item. + * + * @return AcceptHeaderItem|null + */ + public function first() + { + $this->sort(); + + return !empty($this->items) ? reset($this->items) : null; + } + + /** + * Sorts items by descending quality + */ + private function sort() + { + if (!$this->sorted) { + uasort($this->items, function ($a, $b) { + $qA = $a->getQuality(); + $qB = $b->getQuality(); + + if ($qA === $qB) { + return $a->getIndex() > $b->getIndex() ? 1 : -1; + } + + return $qA > $qB ? -1 : 1; + }); + + $this->sorted = true; + } + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/AcceptHeaderItem.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/AcceptHeaderItem.php new file mode 100644 index 0000000..9d4c313 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/AcceptHeaderItem.php @@ -0,0 +1,226 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents an Accept-* header item. + * + * @author Jean-François Simon + */ +class AcceptHeaderItem +{ + /** + * @var string + */ + private $value; + + /** + * @var float + */ + private $quality = 1.0; + + /** + * @var int + */ + private $index = 0; + + /** + * @var array + */ + private $attributes = array(); + + /** + * Constructor. + * + * @param string $value + * @param array $attributes + */ + public function __construct($value, array $attributes = array()) + { + $this->value = $value; + foreach ($attributes as $name => $value) { + $this->setAttribute($name, $value); + } + } + + /** + * Builds an AcceptHeaderInstance instance from a string. + * + * @param string $itemValue + * + * @return AcceptHeaderItem + */ + public static function fromString($itemValue) + { + $bits = preg_split('/\s*(?:;*("[^"]+");*|;*(\'[^\']+\');*|;+)\s*/', $itemValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + $value = array_shift($bits); + $attributes = array(); + + $lastNullAttribute = null; + foreach ($bits as $bit) { + if (($start = substr($bit, 0, 1)) === ($end = substr($bit, -1)) && ($start === '"' || $start === '\'')) { + $attributes[$lastNullAttribute] = substr($bit, 1, -1); + } elseif ('=' === $end) { + $lastNullAttribute = $bit = substr($bit, 0, -1); + $attributes[$bit] = null; + } else { + $parts = explode('=', $bit); + $attributes[$parts[0]] = isset($parts[1]) && strlen($parts[1]) > 0 ? $parts[1] : ''; + } + } + + return new self(($start = substr($value, 0, 1)) === ($end = substr($value, -1)) && ($start === '"' || $start === '\'') ? substr($value, 1, -1) : $value, $attributes); + } + + /** + * Returns header value's string representation. + * + * @return string + */ + public function __toString() + { + $string = $this->value.($this->quality < 1 ? ';q='.$this->quality : ''); + if (count($this->attributes) > 0) { + $string .= ';'.implode(';', array_map(function($name, $value) { + return sprintf(preg_match('/[,;=]/', $value) ? '%s="%s"' : '%s=%s', $name, $value); + }, array_keys($this->attributes), $this->attributes)); + } + + return $string; + } + + /** + * Set the item value. + * + * @param string $value + * + * @return AcceptHeaderItem + */ + public function setValue($value) + { + $this->value = $value; + + return $this; + } + + /** + * Returns the item value. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Set the item quality. + * + * @param float $quality + * + * @return AcceptHeaderItem + */ + public function setQuality($quality) + { + $this->quality = $quality; + + return $this; + } + + /** + * Returns the item quality. + * + * @return float + */ + public function getQuality() + { + return $this->quality; + } + + /** + * Set the item index. + * + * @param int $index + * + * @return AcceptHeaderItem + */ + public function setIndex($index) + { + $this->index = $index; + + return $this; + } + + /** + * Returns the item index. + * + * @return int + */ + public function getIndex() + { + return $this->index; + } + + /** + * Tests if an attribute exists. + * + * @param string $name + * + * @return Boolean + */ + public function hasAttribute($name) + { + return isset($this->attributes[$name]); + } + + /** + * Returns an attribute by its name. + * + * @param string $name + * @param mixed $default + * + * @return mixed + */ + public function getAttribute($name, $default = null) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : $default; + } + + /** + * Returns all attributes. + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Set an attribute. + * + * @param string $name + * @param string $value + * + * @return AcceptHeaderItem + */ + public function setAttribute($name, $value) + { + if ('q' === $name) { + $this->quality = (float) $value; + } else { + $this->attributes[$name] = (string) $value; + } + + return $this; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/ApacheRequest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/ApacheRequest.php new file mode 100644 index 0000000..84803eb --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/ApacheRequest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Request represents an HTTP request from an Apache server. + * + * @author Fabien Potencier + */ +class ApacheRequest extends Request +{ + /** + * {@inheritdoc} + */ + protected function prepareRequestUri() + { + return $this->server->get('REQUEST_URI'); + } + + /** + * {@inheritdoc} + */ + protected function prepareBaseUrl() + { + $baseUrl = $this->server->get('SCRIPT_NAME'); + + if (false === strpos($this->server->get('REQUEST_URI'), $baseUrl)) { + // assume mod_rewrite + return rtrim(dirname($baseUrl), '/\\'); + } + + return $baseUrl; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/BinaryFileResponse.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/BinaryFileResponse.php new file mode 100644 index 0000000..06d530d --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/BinaryFileResponse.php @@ -0,0 +1,280 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\File\Exception\FileException; + +/** + * BinaryFileResponse represents an HTTP response delivering a file. + * + * @author Niklas Fiekas + * @author stealth35 + * @author Igor Wiedler + * @author Jordan Alliot + * @author Sergey Linnik + */ +class BinaryFileResponse extends Response +{ + protected static $trustXSendfileTypeHeader = false; + + protected $file; + protected $offset; + protected $maxlen; + + /** + * Constructor. + * + * @param SplFileInfo|string $file The file to stream + * @param integer $status The response status code + * @param array $headers An array of response headers + * @param boolean $public Files are public by default + * @param null|string $contentDisposition The type of Content-Disposition to set automatically with the filename + * @param boolean $autoEtag Whether the ETag header should be automatically set + * @param boolean $autoLastModified Whether the Last-Modified header should be automatically set + */ + public function __construct($file, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true) + { + parent::__construct(null, $status, $headers); + + $this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified); + + if ($public) { + $this->setPublic(); + } + } + + /** + * {@inheritdoc} + */ + public static function create($file = null, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true) + { + return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified); + } + + /** + * Sets the file to stream. + * + * @param SplFileInfo|string $file The file to stream + * @param string $contentDisposition + * @param Boolean $autoEtag + * @param Boolean $autoLastModified + * + * @return BinaryFileResponse + * + * @throws FileException + */ + public function setFile($file, $contentDisposition = null, $autoEtag = false, $autoLastModified = true) + { + $file = new File((string) $file); + + if (!$file->isReadable()) { + throw new FileException('File must be readable.'); + } + + $this->file = $file; + + if ($autoEtag) { + $this->setAutoEtag(); + } + + if ($autoLastModified) { + $this->setAutoLastModified(); + } + + if ($contentDisposition) { + $this->setContentDisposition($contentDisposition); + } + + return $this; + } + + /** + * Gets the file. + * + * @return File The file to stream + */ + public function getFile() + { + return $this->file; + } + + /** + * Automatically sets the Last-Modified header according the file modification date. + */ + public function setAutoLastModified() + { + $this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime())); + + return $this; + } + + /** + * Automatically sets the ETag header according to the checksum of the file. + */ + public function setAutoEtag() + { + $this->setEtag(sha1_file($this->file->getPathname())); + + return $this; + } + + /** + * Sets the Content-Disposition header with the given filename. + * + * @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT + * @param string $filename Optionally use this filename instead of the real name of the file + * @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename + * + * @return BinaryFileResponse + */ + public function setContentDisposition($disposition, $filename = '', $filenameFallback = '') + { + if ($filename === '') { + $filename = $this->file->getFilename(); + } + + $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback); + $this->headers->set('Content-Disposition', $dispositionHeader); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function prepare(Request $request) + { + $this->headers->set('Content-Length', $this->file->getSize()); + $this->headers->set('Accept-Ranges', 'bytes'); + $this->headers->set('Content-Transfer-Encoding', 'binary'); + + if (!$this->headers->has('Content-Type')) { + $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream'); + } + + if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) { + $this->setProtocolVersion('1.1'); + } + + $this->ensureIEOverSSLCompatibility($request); + + $this->offset = 0; + $this->maxlen = -1; + + if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) { + // Use X-Sendfile, do not send any content. + $type = $request->headers->get('X-Sendfile-Type'); + $path = $this->file->getRealPath(); + if (strtolower($type) == 'x-accel-redirect') { + // Do X-Accel-Mapping substitutions. + foreach (explode(',', $request->headers->get('X-Accel-Mapping', '')) as $mapping) { + $mapping = explode('=', $mapping, 2); + + if (2 == count($mapping)) { + $location = trim($mapping[0]); + $pathPrefix = trim($mapping[1]); + + if (substr($path, 0, strlen($pathPrefix)) == $pathPrefix) { + $path = $location.substr($path, strlen($pathPrefix)); + break; + } + } + } + } + $this->headers->set($type, $path); + $this->maxlen = 0; + } elseif ($request->headers->has('Range')) { + // Process the range headers. + if (!$request->headers->has('If-Range') || $this->getEtag() == $request->headers->get('If-Range')) { + $range = $request->headers->get('Range'); + $fileSize = $this->file->getSize(); + + list($start, $end) = explode('-', substr($range, 6), 2) + array(0); + + $end = ('' === $end) ? $fileSize - 1 : (int) $end; + + if ('' === $start) { + $start = $fileSize - $end; + $end = $fileSize - 1; + } else { + $start = (int) $start; + } + + $start = max($start, 0); + $end = min($end, $fileSize - 1); + + $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1; + $this->offset = $start; + + $this->setStatusCode(206); + $this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize)); + } + } + + return $this; + } + + /** + * Sends the file. + */ + public function sendContent() + { + if (!$this->isSuccessful()) { + parent::sendContent(); + + return; + } + + if (0 === $this->maxlen) { + return; + } + + $out = fopen('php://output', 'wb'); + $file = fopen($this->file->getPathname(), 'rb'); + + stream_copy_to_stream($file, $out, $this->maxlen, $this->offset); + + fclose($out); + fclose($file); + } + + /** + * {@inheritdoc} + * + * @throws \LogicException when the content is not null + */ + public function setContent($content) + { + if (null !== $content) { + throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.'); + } + } + + /** + * {@inheritdoc} + * + * @return false + */ + public function getContent() + { + return false; + } + + /** + * Trust X-Sendfile-Type header. + */ + public static function trustXSendfileTypeHeader() + { + self::$trustXSendfileTypeHeader = true; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/CHANGELOG.md b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/CHANGELOG.md new file mode 100644 index 0000000..41e8eb2 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -0,0 +1,98 @@ +CHANGELOG +========= + +2.3.0 +----- + + * added support for ranges of IPs in trusted proxies + * `UploadedFile::isValid` now returns false if the file was not uploaded via HTTP (in a non-test mode) + * Improved error-handling of `\Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler` + to ensure the supplied PDO handler throws Exceptions on error (as the class expects). Added related test cases + to verify that Exceptions are properly thrown when the PDO queries fail. + +2.2.0 +----- + + * fixed the Request::create() precedence (URI information always take precedence now) + * added Request::getTrustedProxies() + * deprecated Request::isProxyTrusted() + * [BC BREAK] JsonResponse does not turn a top level empty array to an object anymore, use an ArrayObject to enforce objects + * added a IpUtils class to check if an IP belongs to a CIDR + * added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method) + * disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to + enable it, and Request::getHttpMethodParameterOverride() to check if it is supported) + * Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3 + * Deprecated Flashbag::count() and \Countable interface, will be removed in 2.3 + +2.1.0 +----- + + * added Request::getSchemeAndHttpHost() and Request::getUserInfo() + * added a fluent interface to the Response class + * added Request::isProxyTrusted() + * added JsonResponse + * added a getTargetUrl method to RedirectResponse + * added support for streamed responses + * made Response::prepare() method the place to enforce HTTP specification + * [BC BREAK] moved management of the locale from the Session class to the Request class + * added a generic access to the PHP built-in filter mechanism: ParameterBag::filter() + * made FileBinaryMimeTypeGuesser command configurable + * added Request::getUser() and Request::getPassword() + * added support for the PATCH method in Request + * removed the ContentTypeMimeTypeGuesser class as it is deprecated and never used on PHP 5.3 + * added ResponseHeaderBag::makeDisposition() (implements RFC 6266) + * made mimetype to extension conversion configurable + * [BC BREAK] Moved all session related classes and interfaces into own namespace, as + `Symfony\Component\HttpFoundation\Session` and renamed classes accordingly. + Session handlers are located in the subnamespace `Symfony\Component\HttpFoundation\Session\Handler`. + * SessionHandlers must implement `\SessionHandlerInterface` or extend from the + `Symfony\Component\HttpFoundation\Storage\Handler\NativeSessionHandler` base class. + * Added internal storage driver proxy mechanism for forward compatibility with + PHP 5.4 `\SessionHandler` class. + * Added session handlers for custom Memcache, Memcached and Null session save handlers. + * [BC BREAK] Removed `NativeSessionStorage` and replaced with `NativeFileSessionHandler`. + * [BC BREAK] `SessionStorageInterface` methods removed: `write()`, `read()` and + `remove()`. Added `getBag()`, `registerBag()`. The `NativeSessionStorage` class + is a mediator for the session storage internals including the session handlers + which do the real work of participating in the internal PHP session workflow. + * [BC BREAK] Introduced mock implementations of `SessionStorage` to enable unit + and functional testing without starting real PHP sessions. Removed + `ArraySessionStorage`, and replaced with `MockArraySessionStorage` for unit + tests; removed `FilesystemSessionStorage`, and replaced with`MockFileSessionStorage` + for functional tests. These do not interact with global session ini + configuration values, session functions or `$_SESSION` superglobal. This means + they can be configured directly allowing multiple instances to work without + conflicting in the same PHP process. + * [BC BREAK] Removed the `close()` method from the `Session` class, as this is + now redundant. + * Deprecated the following methods from the Session class: `setFlash()`, `setFlashes()` + `getFlash()`, `hasFlash()`, and `removeFlash()`. Use `getFlashBag()` instead + which returns a `FlashBagInterface`. + * `Session->clear()` now only clears session attributes as before it cleared + flash messages and attributes. `Session->getFlashBag()->all()` clears flashes now. + * Session data is now managed by `SessionBagInterface` to better encapsulate + session data. + * Refactored session attribute and flash messages system to their own + `SessionBagInterface` implementations. + * Added `FlashBag`. Flashes expire when retrieved by `get()` or `all()`. This + implementation is ESI compatible. + * Added `AutoExpireFlashBag` (default) to replicate Symfony 2.0.x auto expire + behaviour of messages auto expiring after one page page load. Messages must + be retrieved by `get()` or `all()`. + * Added `Symfony\Component\HttpFoundation\Attribute\AttributeBag` to replicate + attributes storage behaviour from 2.0.x (default). + * Added `Symfony\Component\HttpFoundation\Attribute\NamespacedAttributeBag` for + namespace session attributes. + * Flash API can stores messages in an array so there may be multiple messages + per flash type. The old `Session` class API remains without BC break as it + will allow single messages as before. + * Added basic session meta-data to the session to record session create time, + last updated time, and the lifetime of the session cookie that was provided + to the client. + * Request::getClientIp() method doesn't take a parameter anymore but bases + itself on the trustProxy parameter. + * Added isMethod() to Request object. + * [BC BREAK] The methods `getPathInfo()`, `getBaseUrl()` and `getBasePath()` of + a `Request` now all return a raw value (vs a urldecoded value before). Any call + to one of these methods must be checked and wrapped in a `rawurldecode()` if + needed. diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Cookie.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Cookie.php new file mode 100644 index 0000000..68fe853 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Cookie.php @@ -0,0 +1,210 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents a cookie + * + * @author Johannes M. Schmitt + * + * @api + */ +class Cookie +{ + protected $name; + protected $value; + protected $domain; + protected $expire; + protected $path; + protected $secure; + protected $httpOnly; + + /** + * Constructor. + * + * @param string $name The name of the cookie + * @param string $value The value of the cookie + * @param integer|string|\DateTime $expire The time the cookie expires + * @param string $path The path on the server in which the cookie will be available on + * @param string $domain The domain that the cookie is available to + * @param Boolean $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client + * @param Boolean $httpOnly Whether the cookie will be made accessible only through the HTTP protocol + * + * @throws \InvalidArgumentException + * + * @api + */ + public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true) + { + // from PHP source code + if (preg_match("/[=,; \t\r\n\013\014]/", $name)) { + throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name)); + } + + if (empty($name)) { + throw new \InvalidArgumentException('The cookie name cannot be empty.'); + } + + // convert expiration time to a Unix timestamp + if ($expire instanceof \DateTime) { + $expire = $expire->format('U'); + } elseif (!is_numeric($expire)) { + $expire = strtotime($expire); + + if (false === $expire || -1 === $expire) { + throw new \InvalidArgumentException('The cookie expiration time is not valid.'); + } + } + + $this->name = $name; + $this->value = $value; + $this->domain = $domain; + $this->expire = $expire; + $this->path = empty($path) ? '/' : $path; + $this->secure = (Boolean) $secure; + $this->httpOnly = (Boolean) $httpOnly; + } + + /** + * Returns the cookie as a string. + * + * @return string The cookie + */ + public function __toString() + { + $str = urlencode($this->getName()).'='; + + if ('' === (string) $this->getValue()) { + $str .= 'deleted; expires='.gmdate("D, d-M-Y H:i:s T", time() - 31536001); + } else { + $str .= urlencode($this->getValue()); + + if ($this->getExpiresTime() !== 0) { + $str .= '; expires='.gmdate("D, d-M-Y H:i:s T", $this->getExpiresTime()); + } + } + + if ($this->path) { + $str .= '; path='.$this->path; + } + + if ($this->getDomain()) { + $str .= '; domain='.$this->getDomain(); + } + + if (true === $this->isSecure()) { + $str .= '; secure'; + } + + if (true === $this->isHttpOnly()) { + $str .= '; httponly'; + } + + return $str; + } + + /** + * Gets the name of the cookie. + * + * @return string + * + * @api + */ + public function getName() + { + return $this->name; + } + + /** + * Gets the value of the cookie. + * + * @return string + * + * @api + */ + public function getValue() + { + return $this->value; + } + + /** + * Gets the domain that the cookie is available to. + * + * @return string + * + * @api + */ + public function getDomain() + { + return $this->domain; + } + + /** + * Gets the time the cookie expires. + * + * @return integer + * + * @api + */ + public function getExpiresTime() + { + return $this->expire; + } + + /** + * Gets the path on the server in which the cookie will be available on. + * + * @return string + * + * @api + */ + public function getPath() + { + return $this->path; + } + + /** + * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client. + * + * @return Boolean + * + * @api + */ + public function isSecure() + { + return $this->secure; + } + + /** + * Checks whether the cookie will be made accessible only through the HTTP protocol. + * + * @return Boolean + * + * @api + */ + public function isHttpOnly() + { + return $this->httpOnly; + } + + /** + * Whether this cookie is about to be cleared + * + * @return Boolean + * + * @api + */ + public function isCleared() + { + return $this->expire < time(); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/AccessDeniedException.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/AccessDeniedException.php new file mode 100644 index 0000000..41f7a46 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/AccessDeniedException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when the access on a file was denied. + * + * @author Bernhard Schussek + */ +class AccessDeniedException extends FileException +{ + /** + * Constructor. + * + * @param string $path The path to the accessed file + */ + public function __construct($path) + { + parent::__construct(sprintf('The file %s could not be accessed', $path)); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/FileException.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/FileException.php new file mode 100644 index 0000000..68f827b --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/FileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an error occurred in the component File + * + * @author Bernhard Schussek + */ +class FileException extends \RuntimeException +{ +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/FileNotFoundException.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/FileNotFoundException.php new file mode 100644 index 0000000..bd70094 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/FileNotFoundException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when a file was not found + * + * @author Bernhard Schussek + */ +class FileNotFoundException extends FileException +{ + /** + * Constructor. + * + * @param string $path The path to the file that was not found + */ + public function __construct($path) + { + parent::__construct(sprintf('The file "%s" does not exist', $path)); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/UnexpectedTypeException.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/UnexpectedTypeException.php new file mode 100644 index 0000000..0444b87 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/UnexpectedTypeException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +class UnexpectedTypeException extends FileException +{ + public function __construct($value, $expectedType) + { + parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, is_object($value) ? get_class($value) : gettype($value))); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/UploadException.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/UploadException.php new file mode 100644 index 0000000..382282e --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/Exception/UploadException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an error occurred during file upload + * + * @author Bernhard Schussek + */ +class UploadException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/File.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/File.php new file mode 100644 index 0000000..9002b51 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/File.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser; +use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser; + +/** + * A file in the file system. + * + * @author Bernhard Schussek + * + * @api + */ +class File extends \SplFileInfo +{ + /** + * Constructs a new file from the given path. + * + * @param string $path The path to the file + * @param Boolean $checkPath Whether to check the path or not + * + * @throws FileNotFoundException If the given path is not a file + * + * @api + */ + public function __construct($path, $checkPath = true) + { + if ($checkPath && !is_file($path)) { + throw new FileNotFoundException($path); + } + + parent::__construct($path); + } + + /** + * Returns the extension based on the mime type. + * + * If the mime type is unknown, returns null. + * + * This method uses the mime type as guessed by getMimeType() + * to guess the file extension. + * + * @return string|null The guessed extension or null if it cannot be guessed + * + * @api + * + * @see ExtensionGuesser + * @see getMimeType() + */ + public function guessExtension() + { + $type = $this->getMimeType(); + $guesser = ExtensionGuesser::getInstance(); + + return $guesser->guess($type); + } + + /** + * Returns the mime type of the file. + * + * The mime type is guessed using a MimeTypeGuesser instance, which uses finfo(), + * mime_content_type() and the system binary "file" (in this order), depending on + * which of those are available. + * + * @return string|null The guessed mime type (i.e. "application/pdf") + * + * @see MimeTypeGuesser + * + * @api + */ + public function getMimeType() + { + $guesser = MimeTypeGuesser::getInstance(); + + return $guesser->guess($this->getPathname()); + } + + /** + * Returns the extension of the file. + * + * \SplFileInfo::getExtension() is not available before PHP 5.3.6 + * + * @return string The extension + * + * @api + */ + public function getExtension() + { + return pathinfo($this->getBasename(), PATHINFO_EXTENSION); + } + + /** + * Moves the file to a new location. + * + * @param string $directory The destination folder + * @param string $name The new file name + * + * @return File A File object representing the new file + * + * @throws FileException if the target file could not be created + * + * @api + */ + public function move($directory, $name = null) + { + $target = $this->getTargetFile($directory, $name); + + if (!@rename($this->getPathname(), $target)) { + $error = error_get_last(); + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message']))); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } + + protected function getTargetFile($directory, $name = null) + { + if (!is_dir($directory)) { + if (false === @mkdir($directory, 0777, true)) { + throw new FileException(sprintf('Unable to create the "%s" directory', $directory)); + } + } elseif (!is_writable($directory)) { + throw new FileException(sprintf('Unable to write in the "%s" directory', $directory)); + } + + $target = $directory.DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name)); + + return new File($target, false); + } + + /** + * Returns locale independent base name of the given path. + * + * @param string $name The new file name + * + * @return string containing + */ + protected function getName($name) + { + $originalName = str_replace('\\', '/', $name); + $pos = strrpos($originalName, '/'); + $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1); + + return $originalName; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/ExtensionGuesser.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/ExtensionGuesser.php new file mode 100644 index 0000000..cc64618 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/ExtensionGuesser.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +/** + * A singleton mime type to file extension guesser. + * + * A default guesser is provided. + * You can register custom guessers by calling the register() + * method on the singleton instance: + * + * $guesser = ExtensionGuesser::getInstance(); + * $guesser->register(new MyCustomExtensionGuesser()); + * + * The last registered guesser is preferred over previously registered ones. + */ +class ExtensionGuesser implements ExtensionGuesserInterface +{ + /** + * The singleton instance + * + * @var ExtensionGuesser + */ + private static $instance = null; + + /** + * All registered ExtensionGuesserInterface instances + * + * @var array + */ + protected $guessers = array(); + + /** + * Returns the singleton instance + * + * @return ExtensionGuesser + */ + public static function getInstance() + { + if (null === self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Registers all natively provided extension guessers + */ + private function __construct() + { + $this->register(new MimeTypeExtensionGuesser()); + } + + /** + * Registers a new extension guesser + * + * When guessing, this guesser is preferred over previously registered ones. + * + * @param ExtensionGuesserInterface $guesser + */ + public function register(ExtensionGuesserInterface $guesser) + { + array_unshift($this->guessers, $guesser); + } + + /** + * Tries to guess the extension + * + * The mime type is passed to each registered mime type guesser in reverse order + * of their registration (last registered is queried first). Once a guesser + * returns a value that is not NULL, this method terminates and returns the + * value. + * + * @param string $mimeType The mime type + * @return string The guessed extension or NULL, if none could be guessed + */ + public function guess($mimeType) + { + foreach ($this->guessers as $guesser) { + $extension = $guesser->guess($mimeType); + + if (null !== $extension) { + break; + } + } + + return $extension; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/ExtensionGuesserInterface.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/ExtensionGuesserInterface.php new file mode 100644 index 0000000..8373696 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/ExtensionGuesserInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +/** + * Guesses the file extension corresponding to a given mime type + */ +interface ExtensionGuesserInterface +{ + /** + * Makes a best guess for a file extension, given a mime type + * + * @param string $mimeType The mime type + * @return string The guessed extension or NULL, if none could be guessed + */ + public function guess($mimeType); +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/FileBinaryMimeTypeGuesser.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/FileBinaryMimeTypeGuesser.php new file mode 100644 index 0000000..f23ddd2 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/FileBinaryMimeTypeGuesser.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; + +/** + * Guesses the mime type with the binary "file" (only available on *nix) + * + * @author Bernhard Schussek + */ +class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface +{ + private $cmd; + + /** + * Constructor. + * + * The $cmd pattern must contain a "%s" string that will be replaced + * with the file name to guess. + * + * The command output must start with the mime type of the file. + * + * @param string $cmd The command to run to get the mime type of a file + */ + public function __construct($cmd = 'file -b --mime %s 2>/dev/null') + { + $this->cmd = $cmd; + } + + /** + * Returns whether this guesser is supported on the current OS + * + * @return Boolean + */ + public static function isSupported() + { + return !defined('PHP_WINDOWS_VERSION_BUILD') && function_exists('passthru') && function_exists('escapeshellarg'); + } + + /** + * {@inheritdoc} + */ + public function guess($path) + { + if (!is_file($path)) { + throw new FileNotFoundException($path); + } + + if (!is_readable($path)) { + throw new AccessDeniedException($path); + } + + if (!self::isSupported()) { + return null; + } + + ob_start(); + + // need to use --mime instead of -i. see #6641 + passthru(sprintf($this->cmd, escapeshellarg($path)), $return); + if ($return > 0) { + ob_end_clean(); + + return null; + } + + $type = trim(ob_get_clean()); + + if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\.]+)#i', $type, $match)) { + // it's not a type, but an error message + return null; + } + + return $match[1]; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/FileinfoMimeTypeGuesser.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/FileinfoMimeTypeGuesser.php new file mode 100644 index 0000000..a6950df --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/FileinfoMimeTypeGuesser.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; + +/** + * Guesses the mime type using the PECL extension FileInfo. + * + * @author Bernhard Schussek + */ +class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface +{ + private $magicFile; + + /** + * Constructor. + * + * @param string $magicFile A magic file to use with the finfo instance + * + * @link http://www.php.net/manual/en/function.finfo-open.php + */ + public function __construct($magicFile = null) + { + $this->magicFile = $magicFile; + } + + /** + * Returns whether this guesser is supported on the current OS/PHP setup + * + * @return Boolean + */ + public static function isSupported() + { + return function_exists('finfo_open'); + } + + /** + * {@inheritdoc} + */ + public function guess($path) + { + if (!is_file($path)) { + throw new FileNotFoundException($path); + } + + if (!is_readable($path)) { + throw new AccessDeniedException($path); + } + + if (!self::isSupported()) { + return null; + } + + if (!$finfo = new \finfo(FILEINFO_MIME_TYPE, $this->magicFile)) { + return null; + } + + return $finfo->file($path); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeExtensionGuesser.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeExtensionGuesser.php new file mode 100644 index 0000000..42e7b77 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeExtensionGuesser.php @@ -0,0 +1,805 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +/** + * Provides a best-guess mapping of mime type to file extension. + */ +class MimeTypeExtensionGuesser implements ExtensionGuesserInterface +{ + /** + * A map of mime types and their default extensions. + * + * This list has been placed under the public domain by the Apache HTTPD project. + * This list has been updated from upstream on 2013-04-23. + * + * @see http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types + * + * @var array + */ + protected $defaultExtensions = array( + 'application/andrew-inset' => 'ez', + 'application/applixware' => 'aw', + 'application/atom+xml' => 'atom', + 'application/atomcat+xml' => 'atomcat', + 'application/atomsvc+xml' => 'atomsvc', + 'application/ccxml+xml' => 'ccxml', + 'application/cdmi-capability' => 'cdmia', + 'application/cdmi-container' => 'cdmic', + 'application/cdmi-domain' => 'cdmid', + 'application/cdmi-object' => 'cdmio', + 'application/cdmi-queue' => 'cdmiq', + 'application/cu-seeme' => 'cu', + 'application/davmount+xml' => 'davmount', + 'application/docbook+xml' => 'dbk', + 'application/dssc+der' => 'dssc', + 'application/dssc+xml' => 'xdssc', + 'application/ecmascript' => 'ecma', + 'application/emma+xml' => 'emma', + 'application/epub+zip' => 'epub', + 'application/exi' => 'exi', + 'application/font-tdpfr' => 'pfr', + 'application/gml+xml' => 'gml', + 'application/gpx+xml' => 'gpx', + 'application/gxf' => 'gxf', + 'application/hyperstudio' => 'stk', + 'application/inkml+xml' => 'ink', + 'application/ipfix' => 'ipfix', + 'application/java-archive' => 'jar', + 'application/java-serialized-object' => 'ser', + 'application/java-vm' => 'class', + 'application/javascript' => 'js', + 'application/json' => 'json', + 'application/jsonml+json' => 'jsonml', + 'application/lost+xml' => 'lostxml', + 'application/mac-binhex40' => 'hqx', + 'application/mac-compactpro' => 'cpt', + 'application/mads+xml' => 'mads', + 'application/marc' => 'mrc', + 'application/marcxml+xml' => 'mrcx', + 'application/mathematica' => 'ma', + 'application/mathml+xml' => 'mathml', + 'application/mbox' => 'mbox', + 'application/mediaservercontrol+xml' => 'mscml', + 'application/metalink+xml' => 'metalink', + 'application/metalink4+xml' => 'meta4', + 'application/mets+xml' => 'mets', + 'application/mods+xml' => 'mods', + 'application/mp21' => 'm21', + 'application/mp4' => 'mp4s', + 'application/msword' => 'doc', + 'application/mxf' => 'mxf', + 'application/octet-stream' => 'bin', + 'application/oda' => 'oda', + 'application/oebps-package+xml' => 'opf', + 'application/ogg' => 'ogx', + 'application/omdoc+xml' => 'omdoc', + 'application/onenote' => 'onetoc', + 'application/oxps' => 'oxps', + 'application/patch-ops-error+xml' => 'xer', + 'application/pdf' => 'pdf', + 'application/pgp-encrypted' => 'pgp', + 'application/pgp-signature' => 'asc', + 'application/pics-rules' => 'prf', + 'application/pkcs10' => 'p10', + 'application/pkcs7-mime' => 'p7m', + 'application/pkcs7-signature' => 'p7s', + 'application/pkcs8' => 'p8', + 'application/pkix-attr-cert' => 'ac', + 'application/pkix-cert' => 'cer', + 'application/pkix-crl' => 'crl', + 'application/pkix-pkipath' => 'pkipath', + 'application/pkixcmp' => 'pki', + 'application/pls+xml' => 'pls', + 'application/postscript' => 'ai', + 'application/prs.cww' => 'cww', + 'application/pskc+xml' => 'pskcxml', + 'application/rdf+xml' => 'rdf', + 'application/reginfo+xml' => 'rif', + 'application/relax-ng-compact-syntax' => 'rnc', + 'application/resource-lists+xml' => 'rl', + 'application/resource-lists-diff+xml' => 'rld', + 'application/rls-services+xml' => 'rs', + 'application/rpki-ghostbusters' => 'gbr', + 'application/rpki-manifest' => 'mft', + 'application/rpki-roa' => 'roa', + 'application/rsd+xml' => 'rsd', + 'application/rss+xml' => 'rss', + 'application/rtf' => 'rtf', + 'application/sbml+xml' => 'sbml', + 'application/scvp-cv-request' => 'scq', + 'application/scvp-cv-response' => 'scs', + 'application/scvp-vp-request' => 'spq', + 'application/scvp-vp-response' => 'spp', + 'application/sdp' => 'sdp', + 'application/set-payment-initiation' => 'setpay', + 'application/set-registration-initiation' => 'setreg', + 'application/shf+xml' => 'shf', + 'application/smil+xml' => 'smi', + 'application/sparql-query' => 'rq', + 'application/sparql-results+xml' => 'srx', + 'application/srgs' => 'gram', + 'application/srgs+xml' => 'grxml', + 'application/sru+xml' => 'sru', + 'application/ssdl+xml' => 'ssdl', + 'application/ssml+xml' => 'ssml', + 'application/tei+xml' => 'tei', + 'application/thraud+xml' => 'tfi', + 'application/timestamped-data' => 'tsd', + 'application/vnd.3gpp.pic-bw-large' => 'plb', + 'application/vnd.3gpp.pic-bw-small' => 'psb', + 'application/vnd.3gpp.pic-bw-var' => 'pvb', + 'application/vnd.3gpp2.tcap' => 'tcap', + 'application/vnd.3m.post-it-notes' => 'pwn', + 'application/vnd.accpac.simply.aso' => 'aso', + 'application/vnd.accpac.simply.imp' => 'imp', + 'application/vnd.acucobol' => 'acu', + 'application/vnd.acucorp' => 'atc', + 'application/vnd.adobe.air-application-installer-package+zip' => 'air', + 'application/vnd.adobe.formscentral.fcdt' => 'fcdt', + 'application/vnd.adobe.fxp' => 'fxp', + 'application/vnd.adobe.xdp+xml' => 'xdp', + 'application/vnd.adobe.xfdf' => 'xfdf', + 'application/vnd.ahead.space' => 'ahead', + 'application/vnd.airzip.filesecure.azf' => 'azf', + 'application/vnd.airzip.filesecure.azs' => 'azs', + 'application/vnd.amazon.ebook' => 'azw', + 'application/vnd.americandynamics.acc' => 'acc', + 'application/vnd.amiga.ami' => 'ami', + 'application/vnd.android.package-archive' => 'apk', + 'application/vnd.anser-web-certificate-issue-initiation' => 'cii', + 'application/vnd.anser-web-funds-transfer-initiation' => 'fti', + 'application/vnd.antix.game-component' => 'atx', + 'application/vnd.apple.installer+xml' => 'mpkg', + 'application/vnd.apple.mpegurl' => 'm3u8', + 'application/vnd.aristanetworks.swi' => 'swi', + 'application/vnd.astraea-software.iota' => 'iota', + 'application/vnd.audiograph' => 'aep', + 'application/vnd.blueice.multipass' => 'mpm', + 'application/vnd.bmi' => 'bmi', + 'application/vnd.businessobjects' => 'rep', + 'application/vnd.chemdraw+xml' => 'cdxml', + 'application/vnd.chipnuts.karaoke-mmd' => 'mmd', + 'application/vnd.cinderella' => 'cdy', + 'application/vnd.claymore' => 'cla', + 'application/vnd.cloanto.rp9' => 'rp9', + 'application/vnd.clonk.c4group' => 'c4g', + 'application/vnd.cluetrust.cartomobile-config' => 'c11amc', + 'application/vnd.cluetrust.cartomobile-config-pkg' => 'c11amz', + 'application/vnd.commonspace' => 'csp', + 'application/vnd.contact.cmsg' => 'cdbcmsg', + 'application/vnd.cosmocaller' => 'cmc', + 'application/vnd.crick.clicker' => 'clkx', + 'application/vnd.crick.clicker.keyboard' => 'clkk', + 'application/vnd.crick.clicker.palette' => 'clkp', + 'application/vnd.crick.clicker.template' => 'clkt', + 'application/vnd.crick.clicker.wordbank' => 'clkw', + 'application/vnd.criticaltools.wbs+xml' => 'wbs', + 'application/vnd.ctc-posml' => 'pml', + 'application/vnd.cups-ppd' => 'ppd', + 'application/vnd.curl.car' => 'car', + 'application/vnd.curl.pcurl' => 'pcurl', + 'application/vnd.dart' => 'dart', + 'application/vnd.data-vision.rdz' => 'rdz', + 'application/vnd.dece.data' => 'uvf', + 'application/vnd.dece.ttml+xml' => 'uvt', + 'application/vnd.dece.unspecified' => 'uvx', + 'application/vnd.dece.zip' => 'uvz', + 'application/vnd.denovo.fcselayout-link' => 'fe_launch', + 'application/vnd.dna' => 'dna', + 'application/vnd.dolby.mlp' => 'mlp', + 'application/vnd.dpgraph' => 'dpg', + 'application/vnd.dreamfactory' => 'dfac', + 'application/vnd.ds-keypoint' => 'kpxx', + 'application/vnd.dvb.ait' => 'ait', + 'application/vnd.dvb.service' => 'svc', + 'application/vnd.dynageo' => 'geo', + 'application/vnd.ecowin.chart' => 'mag', + 'application/vnd.enliven' => 'nml', + 'application/vnd.epson.esf' => 'esf', + 'application/vnd.epson.msf' => 'msf', + 'application/vnd.epson.quickanime' => 'qam', + 'application/vnd.epson.salt' => 'slt', + 'application/vnd.epson.ssf' => 'ssf', + 'application/vnd.eszigno3+xml' => 'es3', + 'application/vnd.ezpix-album' => 'ez2', + 'application/vnd.ezpix-package' => 'ez3', + 'application/vnd.fdf' => 'fdf', + 'application/vnd.fdsn.mseed' => 'mseed', + 'application/vnd.fdsn.seed' => 'seed', + 'application/vnd.flographit' => 'gph', + 'application/vnd.fluxtime.clip' => 'ftc', + 'application/vnd.framemaker' => 'fm', + 'application/vnd.frogans.fnc' => 'fnc', + 'application/vnd.frogans.ltf' => 'ltf', + 'application/vnd.fsc.weblaunch' => 'fsc', + 'application/vnd.fujitsu.oasys' => 'oas', + 'application/vnd.fujitsu.oasys2' => 'oa2', + 'application/vnd.fujitsu.oasys3' => 'oa3', + 'application/vnd.fujitsu.oasysgp' => 'fg5', + 'application/vnd.fujitsu.oasysprs' => 'bh2', + 'application/vnd.fujixerox.ddd' => 'ddd', + 'application/vnd.fujixerox.docuworks' => 'xdw', + 'application/vnd.fujixerox.docuworks.binder' => 'xbd', + 'application/vnd.fuzzysheet' => 'fzs', + 'application/vnd.genomatix.tuxedo' => 'txd', + 'application/vnd.geogebra.file' => 'ggb', + 'application/vnd.geogebra.tool' => 'ggt', + 'application/vnd.geometry-explorer' => 'gex', + 'application/vnd.geonext' => 'gxt', + 'application/vnd.geoplan' => 'g2w', + 'application/vnd.geospace' => 'g3w', + 'application/vnd.gmx' => 'gmx', + 'application/vnd.google-earth.kml+xml' => 'kml', + 'application/vnd.google-earth.kmz' => 'kmz', + 'application/vnd.grafeq' => 'gqf', + 'application/vnd.groove-account' => 'gac', + 'application/vnd.groove-help' => 'ghf', + 'application/vnd.groove-identity-message' => 'gim', + 'application/vnd.groove-injector' => 'grv', + 'application/vnd.groove-tool-message' => 'gtm', + 'application/vnd.groove-tool-template' => 'tpl', + 'application/vnd.groove-vcard' => 'vcg', + 'application/vnd.hal+xml' => 'hal', + 'application/vnd.handheld-entertainment+xml' => 'zmm', + 'application/vnd.hbci' => 'hbci', + 'application/vnd.hhe.lesson-player' => 'les', + 'application/vnd.hp-hpgl' => 'hpgl', + 'application/vnd.hp-hpid' => 'hpid', + 'application/vnd.hp-hps' => 'hps', + 'application/vnd.hp-jlyt' => 'jlt', + 'application/vnd.hp-pcl' => 'pcl', + 'application/vnd.hp-pclxl' => 'pclxl', + 'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx', + 'application/vnd.ibm.minipay' => 'mpy', + 'application/vnd.ibm.modcap' => 'afp', + 'application/vnd.ibm.rights-management' => 'irm', + 'application/vnd.ibm.secure-container' => 'sc', + 'application/vnd.iccprofile' => 'icc', + 'application/vnd.igloader' => 'igl', + 'application/vnd.immervision-ivp' => 'ivp', + 'application/vnd.immervision-ivu' => 'ivu', + 'application/vnd.insors.igm' => 'igm', + 'application/vnd.intercon.formnet' => 'xpw', + 'application/vnd.intergeo' => 'i2g', + 'application/vnd.intu.qbo' => 'qbo', + 'application/vnd.intu.qfx' => 'qfx', + 'application/vnd.ipunplugged.rcprofile' => 'rcprofile', + 'application/vnd.irepository.package+xml' => 'irp', + 'application/vnd.is-xpr' => 'xpr', + 'application/vnd.isac.fcs' => 'fcs', + 'application/vnd.jam' => 'jam', + 'application/vnd.jcp.javame.midlet-rms' => 'rms', + 'application/vnd.jisp' => 'jisp', + 'application/vnd.joost.joda-archive' => 'joda', + 'application/vnd.kahootz' => 'ktz', + 'application/vnd.kde.karbon' => 'karbon', + 'application/vnd.kde.kchart' => 'chrt', + 'application/vnd.kde.kformula' => 'kfo', + 'application/vnd.kde.kivio' => 'flw', + 'application/vnd.kde.kontour' => 'kon', + 'application/vnd.kde.kpresenter' => 'kpr', + 'application/vnd.kde.kspread' => 'ksp', + 'application/vnd.kde.kword' => 'kwd', + 'application/vnd.kenameaapp' => 'htke', + 'application/vnd.kidspiration' => 'kia', + 'application/vnd.kinar' => 'kne', + 'application/vnd.koan' => 'skp', + 'application/vnd.kodak-descriptor' => 'sse', + 'application/vnd.las.las+xml' => 'lasxml', + 'application/vnd.llamagraphics.life-balance.desktop' => 'lbd', + 'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe', + 'application/vnd.lotus-1-2-3' => '123', + 'application/vnd.lotus-approach' => 'apr', + 'application/vnd.lotus-freelance' => 'pre', + 'application/vnd.lotus-notes' => 'nsf', + 'application/vnd.lotus-organizer' => 'org', + 'application/vnd.lotus-screencam' => 'scm', + 'application/vnd.lotus-wordpro' => 'lwp', + 'application/vnd.macports.portpkg' => 'portpkg', + 'application/vnd.mcd' => 'mcd', + 'application/vnd.medcalcdata' => 'mc1', + 'application/vnd.mediastation.cdkey' => 'cdkey', + 'application/vnd.mfer' => 'mwf', + 'application/vnd.mfmp' => 'mfm', + 'application/vnd.micrografx.flo' => 'flo', + 'application/vnd.micrografx.igx' => 'igx', + 'application/vnd.mif' => 'mif', + 'application/vnd.mobius.daf' => 'daf', + 'application/vnd.mobius.dis' => 'dis', + 'application/vnd.mobius.mbk' => 'mbk', + 'application/vnd.mobius.mqy' => 'mqy', + 'application/vnd.mobius.msl' => 'msl', + 'application/vnd.mobius.plc' => 'plc', + 'application/vnd.mobius.txf' => 'txf', + 'application/vnd.mophun.application' => 'mpn', + 'application/vnd.mophun.certificate' => 'mpc', + 'application/vnd.mozilla.xul+xml' => 'xul', + 'application/vnd.ms-artgalry' => 'cil', + 'application/vnd.ms-cab-compressed' => 'cab', + 'application/vnd.ms-excel' => 'xls', + 'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam', + 'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb', + 'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm', + 'application/vnd.ms-excel.template.macroenabled.12' => 'xltm', + 'application/vnd.ms-fontobject' => 'eot', + 'application/vnd.ms-htmlhelp' => 'chm', + 'application/vnd.ms-ims' => 'ims', + 'application/vnd.ms-lrm' => 'lrm', + 'application/vnd.ms-officetheme' => 'thmx', + 'application/vnd.ms-pki.seccat' => 'cat', + 'application/vnd.ms-pki.stl' => 'stl', + 'application/vnd.ms-powerpoint' => 'ppt', + 'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam', + 'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm', + 'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm', + 'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm', + 'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm', + 'application/vnd.ms-project' => 'mpp', + 'application/vnd.ms-word.document.macroenabled.12' => 'docm', + 'application/vnd.ms-word.template.macroenabled.12' => 'dotm', + 'application/vnd.ms-works' => 'wps', + 'application/vnd.ms-wpl' => 'wpl', + 'application/vnd.ms-xpsdocument' => 'xps', + 'application/vnd.mseq' => 'mseq', + 'application/vnd.musician' => 'mus', + 'application/vnd.muvee.style' => 'msty', + 'application/vnd.mynfc' => 'taglet', + 'application/vnd.neurolanguage.nlu' => 'nlu', + 'application/vnd.nitf' => 'ntf', + 'application/vnd.noblenet-directory' => 'nnd', + 'application/vnd.noblenet-sealer' => 'nns', + 'application/vnd.noblenet-web' => 'nnw', + 'application/vnd.nokia.n-gage.data' => 'ngdat', + 'application/vnd.nokia.n-gage.symbian.install' => 'n-gage', + 'application/vnd.nokia.radio-preset' => 'rpst', + 'application/vnd.nokia.radio-presets' => 'rpss', + 'application/vnd.novadigm.edm' => 'edm', + 'application/vnd.novadigm.edx' => 'edx', + 'application/vnd.novadigm.ext' => 'ext', + 'application/vnd.oasis.opendocument.chart' => 'odc', + 'application/vnd.oasis.opendocument.chart-template' => 'otc', + 'application/vnd.oasis.opendocument.database' => 'odb', + 'application/vnd.oasis.opendocument.formula' => 'odf', + 'application/vnd.oasis.opendocument.formula-template' => 'odft', + 'application/vnd.oasis.opendocument.graphics' => 'odg', + 'application/vnd.oasis.opendocument.graphics-template' => 'otg', + 'application/vnd.oasis.opendocument.image' => 'odi', + 'application/vnd.oasis.opendocument.image-template' => 'oti', + 'application/vnd.oasis.opendocument.presentation' => 'odp', + 'application/vnd.oasis.opendocument.presentation-template' => 'otp', + 'application/vnd.oasis.opendocument.spreadsheet' => 'ods', + 'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots', + 'application/vnd.oasis.opendocument.text' => 'odt', + 'application/vnd.oasis.opendocument.text-master' => 'odm', + 'application/vnd.oasis.opendocument.text-template' => 'ott', + 'application/vnd.oasis.opendocument.text-web' => 'oth', + 'application/vnd.olpc-sugar' => 'xo', + 'application/vnd.oma.dd2+xml' => 'dd2', + 'application/vnd.openofficeorg.extension' => 'oxt', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', + 'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx', + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx', + 'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx', + 'application/vnd.osgeo.mapguide.package' => 'mgp', + 'application/vnd.osgi.dp' => 'dp', + 'application/vnd.osgi.subsystem' => 'esa', + 'application/vnd.palm' => 'pdb', + 'application/vnd.pawaafile' => 'paw', + 'application/vnd.pg.format' => 'str', + 'application/vnd.pg.osasli' => 'ei6', + 'application/vnd.picsel' => 'efif', + 'application/vnd.pmi.widget' => 'wg', + 'application/vnd.pocketlearn' => 'plf', + 'application/vnd.powerbuilder6' => 'pbd', + 'application/vnd.previewsystems.box' => 'box', + 'application/vnd.proteus.magazine' => 'mgz', + 'application/vnd.publishare-delta-tree' => 'qps', + 'application/vnd.pvi.ptid1' => 'ptid', + 'application/vnd.quark.quarkxpress' => 'qxd', + 'application/vnd.realvnc.bed' => 'bed', + 'application/vnd.recordare.musicxml' => 'mxl', + 'application/vnd.recordare.musicxml+xml' => 'musicxml', + 'application/vnd.rig.cryptonote' => 'cryptonote', + 'application/vnd.rim.cod' => 'cod', + 'application/vnd.rn-realmedia' => 'rm', + 'application/vnd.rn-realmedia-vbr' => 'rmvb', + 'application/vnd.route66.link66+xml' => 'link66', + 'application/vnd.sailingtracker.track' => 'st', + 'application/vnd.seemail' => 'see', + 'application/vnd.sema' => 'sema', + 'application/vnd.semd' => 'semd', + 'application/vnd.semf' => 'semf', + 'application/vnd.shana.informed.formdata' => 'ifm', + 'application/vnd.shana.informed.formtemplate' => 'itp', + 'application/vnd.shana.informed.interchange' => 'iif', + 'application/vnd.shana.informed.package' => 'ipk', + 'application/vnd.simtech-mindmapper' => 'twd', + 'application/vnd.smaf' => 'mmf', + 'application/vnd.smart.teacher' => 'teacher', + 'application/vnd.solent.sdkm+xml' => 'sdkm', + 'application/vnd.spotfire.dxp' => 'dxp', + 'application/vnd.spotfire.sfs' => 'sfs', + 'application/vnd.stardivision.calc' => 'sdc', + 'application/vnd.stardivision.draw' => 'sda', + 'application/vnd.stardivision.impress' => 'sdd', + 'application/vnd.stardivision.math' => 'smf', + 'application/vnd.stardivision.writer' => 'sdw', + 'application/vnd.stardivision.writer-global' => 'sgl', + 'application/vnd.stepmania.package' => 'smzip', + 'application/vnd.stepmania.stepchart' => 'sm', + 'application/vnd.sun.xml.calc' => 'sxc', + 'application/vnd.sun.xml.calc.template' => 'stc', + 'application/vnd.sun.xml.draw' => 'sxd', + 'application/vnd.sun.xml.draw.template' => 'std', + 'application/vnd.sun.xml.impress' => 'sxi', + 'application/vnd.sun.xml.impress.template' => 'sti', + 'application/vnd.sun.xml.math' => 'sxm', + 'application/vnd.sun.xml.writer' => 'sxw', + 'application/vnd.sun.xml.writer.global' => 'sxg', + 'application/vnd.sun.xml.writer.template' => 'stw', + 'application/vnd.sus-calendar' => 'sus', + 'application/vnd.svd' => 'svd', + 'application/vnd.symbian.install' => 'sis', + 'application/vnd.syncml+xml' => 'xsm', + 'application/vnd.syncml.dm+wbxml' => 'bdm', + 'application/vnd.syncml.dm+xml' => 'xdm', + 'application/vnd.tao.intent-module-archive' => 'tao', + 'application/vnd.tcpdump.pcap' => 'pcap', + 'application/vnd.tmobile-livetv' => 'tmo', + 'application/vnd.trid.tpt' => 'tpt', + 'application/vnd.triscape.mxs' => 'mxs', + 'application/vnd.trueapp' => 'tra', + 'application/vnd.ufdl' => 'ufd', + 'application/vnd.uiq.theme' => 'utz', + 'application/vnd.umajin' => 'umj', + 'application/vnd.unity' => 'unityweb', + 'application/vnd.uoml+xml' => 'uoml', + 'application/vnd.vcx' => 'vcx', + 'application/vnd.visio' => 'vsd', + 'application/vnd.visionary' => 'vis', + 'application/vnd.vsf' => 'vsf', + 'application/vnd.wap.wbxml' => 'wbxml', + 'application/vnd.wap.wmlc' => 'wmlc', + 'application/vnd.wap.wmlscriptc' => 'wmlsc', + 'application/vnd.webturbo' => 'wtb', + 'application/vnd.wolfram.player' => 'nbp', + 'application/vnd.wordperfect' => 'wpd', + 'application/vnd.wqd' => 'wqd', + 'application/vnd.wt.stf' => 'stf', + 'application/vnd.xara' => 'xar', + 'application/vnd.xfdl' => 'xfdl', + 'application/vnd.yamaha.hv-dic' => 'hvd', + 'application/vnd.yamaha.hv-script' => 'hvs', + 'application/vnd.yamaha.hv-voice' => 'hvp', + 'application/vnd.yamaha.openscoreformat' => 'osf', + 'application/vnd.yamaha.openscoreformat.osfpvg+xml' => 'osfpvg', + 'application/vnd.yamaha.smaf-audio' => 'saf', + 'application/vnd.yamaha.smaf-phrase' => 'spf', + 'application/vnd.yellowriver-custom-menu' => 'cmp', + 'application/vnd.zul' => 'zir', + 'application/vnd.zzazz.deck+xml' => 'zaz', + 'application/voicexml+xml' => 'vxml', + 'application/widget' => 'wgt', + 'application/winhlp' => 'hlp', + 'application/wsdl+xml' => 'wsdl', + 'application/wspolicy+xml' => 'wspolicy', + 'application/x-7z-compressed' => '7z', + 'application/x-abiword' => 'abw', + 'application/x-ace-compressed' => 'ace', + 'application/x-apple-diskimage' => 'dmg', + 'application/x-authorware-bin' => 'aab', + 'application/x-authorware-map' => 'aam', + 'application/x-authorware-seg' => 'aas', + 'application/x-bcpio' => 'bcpio', + 'application/x-bittorrent' => 'torrent', + 'application/x-blorb' => 'blb', + 'application/x-bzip' => 'bz', + 'application/x-bzip2' => 'bz2', + 'application/x-cbr' => 'cbr', + 'application/x-cdlink' => 'vcd', + 'application/x-cfs-compressed' => 'cfs', + 'application/x-chat' => 'chat', + 'application/x-chess-pgn' => 'pgn', + 'application/x-conference' => 'nsc', + 'application/x-cpio' => 'cpio', + 'application/x-csh' => 'csh', + 'application/x-debian-package' => 'deb', + 'application/x-dgc-compressed' => 'dgc', + 'application/x-director' => 'dir', + 'application/x-doom' => 'wad', + 'application/x-dtbncx+xml' => 'ncx', + 'application/x-dtbook+xml' => 'dtb', + 'application/x-dtbresource+xml' => 'res', + 'application/x-dvi' => 'dvi', + 'application/x-envoy' => 'evy', + 'application/x-eva' => 'eva', + 'application/x-font-bdf' => 'bdf', + 'application/x-font-ghostscript' => 'gsf', + 'application/x-font-linux-psf' => 'psf', + 'application/x-font-otf' => 'otf', + 'application/x-font-pcf' => 'pcf', + 'application/x-font-snf' => 'snf', + 'application/x-font-ttf' => 'ttf', + 'application/x-font-type1' => 'pfa', + 'application/x-font-woff' => 'woff', + 'application/x-freearc' => 'arc', + 'application/x-futuresplash' => 'spl', + 'application/x-gca-compressed' => 'gca', + 'application/x-glulx' => 'ulx', + 'application/x-gnumeric' => 'gnumeric', + 'application/x-gramps-xml' => 'gramps', + 'application/x-gtar' => 'gtar', + 'application/x-hdf' => 'hdf', + 'application/x-install-instructions' => 'install', + 'application/x-iso9660-image' => 'iso', + 'application/x-java-jnlp-file' => 'jnlp', + 'application/x-latex' => 'latex', + 'application/x-lzh-compressed' => 'lzh', + 'application/x-mie' => 'mie', + 'application/x-mobipocket-ebook' => 'prc', + 'application/x-ms-application' => 'application', + 'application/x-ms-shortcut' => 'lnk', + 'application/x-ms-wmd' => 'wmd', + 'application/x-ms-wmz' => 'wmz', + 'application/x-ms-xbap' => 'xbap', + 'application/x-msaccess' => 'mdb', + 'application/x-msbinder' => 'obd', + 'application/x-mscardfile' => 'crd', + 'application/x-msclip' => 'clp', + 'application/x-msdownload' => 'exe', + 'application/x-msmediaview' => 'mvb', + 'application/x-msmetafile' => 'wmf', + 'application/x-msmoney' => 'mny', + 'application/x-mspublisher' => 'pub', + 'application/x-msschedule' => 'scd', + 'application/x-msterminal' => 'trm', + 'application/x-mswrite' => 'wri', + 'application/x-netcdf' => 'nc', + 'application/x-nzb' => 'nzb', + 'application/x-pkcs12' => 'p12', + 'application/x-pkcs7-certificates' => 'p7b', + 'application/x-pkcs7-certreqresp' => 'p7r', + 'application/x-rar-compressed' => 'rar', + 'application/x-rar' => 'rar', + 'application/x-research-info-systems' => 'ris', + 'application/x-sh' => 'sh', + 'application/x-shar' => 'shar', + 'application/x-shockwave-flash' => 'swf', + 'application/x-silverlight-app' => 'xap', + 'application/x-sql' => 'sql', + 'application/x-stuffit' => 'sit', + 'application/x-stuffitx' => 'sitx', + 'application/x-subrip' => 'srt', + 'application/x-sv4cpio' => 'sv4cpio', + 'application/x-sv4crc' => 'sv4crc', + 'application/x-t3vm-image' => 't3', + 'application/x-tads' => 'gam', + 'application/x-tar' => 'tar', + 'application/x-tcl' => 'tcl', + 'application/x-tex' => 'tex', + 'application/x-tex-tfm' => 'tfm', + 'application/x-texinfo' => 'texinfo', + 'application/x-tgif' => 'obj', + 'application/x-ustar' => 'ustar', + 'application/x-wais-source' => 'src', + 'application/x-x509-ca-cert' => 'der', + 'application/x-xfig' => 'fig', + 'application/x-xliff+xml' => 'xlf', + 'application/x-xpinstall' => 'xpi', + 'application/x-xz' => 'xz', + 'application/x-zmachine' => 'z1', + 'application/xaml+xml' => 'xaml', + 'application/xcap-diff+xml' => 'xdf', + 'application/xenc+xml' => 'xenc', + 'application/xhtml+xml' => 'xhtml', + 'application/xml' => 'xml', + 'application/xml-dtd' => 'dtd', + 'application/xop+xml' => 'xop', + 'application/xproc+xml' => 'xpl', + 'application/xslt+xml' => 'xslt', + 'application/xspf+xml' => 'xspf', + 'application/xv+xml' => 'mxml', + 'application/yang' => 'yang', + 'application/yin+xml' => 'yin', + 'application/zip' => 'zip', + 'audio/adpcm' => 'adp', + 'audio/basic' => 'au', + 'audio/midi' => 'mid', + 'audio/mp4' => 'mp4a', + 'audio/mpeg' => 'mpga', + 'audio/ogg' => 'oga', + 'audio/s3m' => 's3m', + 'audio/silk' => 'sil', + 'audio/vnd.dece.audio' => 'uva', + 'audio/vnd.digital-winds' => 'eol', + 'audio/vnd.dra' => 'dra', + 'audio/vnd.dts' => 'dts', + 'audio/vnd.dts.hd' => 'dtshd', + 'audio/vnd.lucent.voice' => 'lvp', + 'audio/vnd.ms-playready.media.pya' => 'pya', + 'audio/vnd.nuera.ecelp4800' => 'ecelp4800', + 'audio/vnd.nuera.ecelp7470' => 'ecelp7470', + 'audio/vnd.nuera.ecelp9600' => 'ecelp9600', + 'audio/vnd.rip' => 'rip', + 'audio/webm' => 'weba', + 'audio/x-aac' => 'aac', + 'audio/x-aiff' => 'aif', + 'audio/x-caf' => 'caf', + 'audio/x-flac' => 'flac', + 'audio/x-matroska' => 'mka', + 'audio/x-mpegurl' => 'm3u', + 'audio/x-ms-wax' => 'wax', + 'audio/x-ms-wma' => 'wma', + 'audio/x-pn-realaudio' => 'ram', + 'audio/x-pn-realaudio-plugin' => 'rmp', + 'audio/x-wav' => 'wav', + 'audio/xm' => 'xm', + 'chemical/x-cdx' => 'cdx', + 'chemical/x-cif' => 'cif', + 'chemical/x-cmdf' => 'cmdf', + 'chemical/x-cml' => 'cml', + 'chemical/x-csml' => 'csml', + 'chemical/x-xyz' => 'xyz', + 'image/bmp' => 'bmp', + 'image/cgm' => 'cgm', + 'image/g3fax' => 'g3', + 'image/gif' => 'gif', + 'image/ief' => 'ief', + 'image/jpeg' => 'jpeg', + 'image/ktx' => 'ktx', + 'image/png' => 'png', + 'image/prs.btif' => 'btif', + 'image/sgi' => 'sgi', + 'image/svg+xml' => 'svg', + 'image/tiff' => 'tiff', + 'image/vnd.adobe.photoshop' => 'psd', + 'image/vnd.dece.graphic' => 'uvi', + 'image/vnd.dvb.subtitle' => 'sub', + 'image/vnd.djvu' => 'djvu', + 'image/vnd.dwg' => 'dwg', + 'image/vnd.dxf' => 'dxf', + 'image/vnd.fastbidsheet' => 'fbs', + 'image/vnd.fpx' => 'fpx', + 'image/vnd.fst' => 'fst', + 'image/vnd.fujixerox.edmics-mmr' => 'mmr', + 'image/vnd.fujixerox.edmics-rlc' => 'rlc', + 'image/vnd.ms-modi' => 'mdi', + 'image/vnd.ms-photo' => 'wdp', + 'image/vnd.net-fpx' => 'npx', + 'image/vnd.wap.wbmp' => 'wbmp', + 'image/vnd.xiff' => 'xif', + 'image/webp' => 'webp', + 'image/x-3ds' => '3ds', + 'image/x-cmu-raster' => 'ras', + 'image/x-cmx' => 'cmx', + 'image/x-freehand' => 'fh', + 'image/x-icon' => 'ico', + 'image/x-mrsid-image' => 'sid', + 'image/x-pcx' => 'pcx', + 'image/x-pict' => 'pic', + 'image/x-portable-anymap' => 'pnm', + 'image/x-portable-bitmap' => 'pbm', + 'image/x-portable-graymap' => 'pgm', + 'image/x-portable-pixmap' => 'ppm', + 'image/x-rgb' => 'rgb', + 'image/x-tga' => 'tga', + 'image/x-xbitmap' => 'xbm', + 'image/x-xpixmap' => 'xpm', + 'image/x-xwindowdump' => 'xwd', + 'message/rfc822' => 'eml', + 'model/iges' => 'igs', + 'model/mesh' => 'msh', + 'model/vnd.collada+xml' => 'dae', + 'model/vnd.dwf' => 'dwf', + 'model/vnd.gdl' => 'gdl', + 'model/vnd.gtw' => 'gtw', + 'model/vnd.mts' => 'mts', + 'model/vnd.vtu' => 'vtu', + 'model/vrml' => 'wrl', + 'model/x3d+binary' => 'x3db', + 'model/x3d+vrml' => 'x3dv', + 'model/x3d+xml' => 'x3d', + 'text/cache-manifest' => 'appcache', + 'text/calendar' => 'ics', + 'text/css' => 'css', + 'text/csv' => 'csv', + 'text/html' => 'html', + 'text/n3' => 'n3', + 'text/plain' => 'txt', + 'text/prs.lines.tag' => 'dsc', + 'text/richtext' => 'rtx', + 'text/sgml' => 'sgml', + 'text/tab-separated-values' => 'tsv', + 'text/troff' => 't', + 'text/turtle' => 'ttl', + 'text/uri-list' => 'uri', + 'text/vcard' => 'vcard', + 'text/vnd.curl' => 'curl', + 'text/vnd.curl.dcurl' => 'dcurl', + 'text/vnd.curl.scurl' => 'scurl', + 'text/vnd.curl.mcurl' => 'mcurl', + 'text/vnd.dvb.subtitle' => 'sub', + 'text/vnd.fly' => 'fly', + 'text/vnd.fmi.flexstor' => 'flx', + 'text/vnd.graphviz' => 'gv', + 'text/vnd.in3d.3dml' => '3dml', + 'text/vnd.in3d.spot' => 'spot', + 'text/vnd.sun.j2me.app-descriptor' => 'jad', + 'text/vnd.wap.wml' => 'wml', + 'text/vnd.wap.wmlscript' => 'wmls', + 'text/x-asm' => 's', + 'text/x-c' => 'c', + 'text/x-fortran' => 'f', + 'text/x-pascal' => 'p', + 'text/x-java-source' => 'java', + 'text/x-opml' => 'opml', + 'text/x-nfo' => 'nfo', + 'text/x-setext' => 'etx', + 'text/x-sfv' => 'sfv', + 'text/x-uuencode' => 'uu', + 'text/x-vcalendar' => 'vcs', + 'text/x-vcard' => 'vcf', + 'video/3gpp' => '3gp', + 'video/3gpp2' => '3g2', + 'video/h261' => 'h261', + 'video/h263' => 'h263', + 'video/h264' => 'h264', + 'video/jpeg' => 'jpgv', + 'video/jpm' => 'jpm', + 'video/mj2' => 'mj2', + 'video/mp4' => 'mp4', + 'video/mpeg' => 'mpeg', + 'video/ogg' => 'ogv', + 'video/quicktime' => 'qt', + 'video/vnd.dece.hd' => 'uvh', + 'video/vnd.dece.mobile' => 'uvm', + 'video/vnd.dece.pd' => 'uvp', + 'video/vnd.dece.sd' => 'uvs', + 'video/vnd.dece.video' => 'uvv', + 'video/vnd.dvb.file' => 'dvb', + 'video/vnd.fvt' => 'fvt', + 'video/vnd.mpegurl' => 'mxu', + 'video/vnd.ms-playready.media.pyv' => 'pyv', + 'video/vnd.uvvu.mp4' => 'uvu', + 'video/vnd.vivo' => 'viv', + 'video/webm' => 'webm', + 'video/x-f4v' => 'f4v', + 'video/x-fli' => 'fli', + 'video/x-flv' => 'flv', + 'video/x-m4v' => 'm4v', + 'video/x-matroska' => 'mkv', + 'video/x-mng' => 'mng', + 'video/x-ms-asf' => 'asf', + 'video/x-ms-vob' => 'vob', + 'video/x-ms-wm' => 'wm', + 'video/x-ms-wmv' => 'wmv', + 'video/x-ms-wmx' => 'wmx', + 'video/x-ms-wvx' => 'wvx', + 'video/x-msvideo' => 'avi', + 'video/x-sgi-movie' => 'movie', + 'video/x-smv' => 'smv', + 'x-conference/x-cooltalk' => 'ice', + ); + + /** + * {@inheritdoc} + */ + public function guess($mimeType) + { + return isset($this->defaultExtensions[$mimeType]) ? $this->defaultExtensions[$mimeType] : null; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesser.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesser.php new file mode 100644 index 0000000..f6bf2cd --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesser.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; + +/** + * A singleton mime type guesser. + * + * By default, all mime type guessers provided by the framework are installed + * (if available on the current OS/PHP setup). + * + * You can register custom guessers by calling the register() method on the + * singleton instance. Custom guessers are always called before any default ones. + * + * $guesser = MimeTypeGuesser::getInstance(); + * $guesser->register(new MyCustomMimeTypeGuesser()); + * + * If you want to change the order of the default guessers, just re-register your + * preferred one as a custom one. The last registered guesser is preferred over + * previously registered ones. + * + * Re-registering a built-in guesser also allows you to configure it: + * + * $guesser = MimeTypeGuesser::getInstance(); + * $guesser->register(new FileinfoMimeTypeGuesser('/path/to/magic/file')); + * + * @author Bernhard Schussek + */ +class MimeTypeGuesser implements MimeTypeGuesserInterface +{ + /** + * The singleton instance + * + * @var MimeTypeGuesser + */ + private static $instance = null; + + /** + * All registered MimeTypeGuesserInterface instances + * + * @var array + */ + protected $guessers = array(); + + /** + * Returns the singleton instance + * + * @return MimeTypeGuesser + */ + public static function getInstance() + { + if (null === self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Registers all natively provided mime type guessers + */ + private function __construct() + { + if (FileBinaryMimeTypeGuesser::isSupported()) { + $this->register(new FileBinaryMimeTypeGuesser()); + } + + if (FileinfoMimeTypeGuesser::isSupported()) { + $this->register(new FileinfoMimeTypeGuesser()); + } + } + + /** + * Registers a new mime type guesser + * + * When guessing, this guesser is preferred over previously registered ones. + * + * @param MimeTypeGuesserInterface $guesser + */ + public function register(MimeTypeGuesserInterface $guesser) + { + array_unshift($this->guessers, $guesser); + } + + /** + * Tries to guess the mime type of the given file + * + * The file is passed to each registered mime type guesser in reverse order + * of their registration (last registered is queried first). Once a guesser + * returns a value that is not NULL, this method terminates and returns the + * value. + * + * @param string $path The path to the file + * + * @return string The mime type or NULL, if none could be guessed + * + * @throws \LogicException + * @throws FileNotFoundException + * @throws AccessDeniedException + */ + public function guess($path) + { + if (!is_file($path)) { + throw new FileNotFoundException($path); + } + + if (!is_readable($path)) { + throw new AccessDeniedException($path); + } + + if (!$this->guessers) { + throw new \LogicException('Unable to guess the mime type as no guessers are available (Did you enable the php_fileinfo extension?)'); + } + + foreach ($this->guessers as $guesser) { + if (null !== $mimeType = $guesser->guess($path)) { + return $mimeType; + } + } + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesserInterface.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesserInterface.php new file mode 100644 index 0000000..87ea20f --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesserInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; + +/** + * Guesses the mime type of a file + * + * @author Bernhard Schussek + */ +interface MimeTypeGuesserInterface +{ + /** + * Guesses the mime type of the file with the given path. + * + * @param string $path The path to the file + * + * @return string The mime type or NULL, if none could be guessed + * + * @throws FileNotFoundException If the file does not exist + * @throws AccessDeniedException If the file could not be read + */ + public function guess($path); +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/UploadedFile.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/UploadedFile.php new file mode 100644 index 0000000..1f23c35 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/File/UploadedFile.php @@ -0,0 +1,305 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser; + +/** + * A file uploaded through a form. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + * @author Fabien Potencier + * + * @api + */ +class UploadedFile extends File +{ + /** + * Whether the test mode is activated. + * + * Local files are used in test mode hence the code should not enforce HTTP uploads. + * + * @var Boolean + */ + private $test = false; + + /** + * The original name of the uploaded file. + * + * @var string + */ + private $originalName; + + /** + * The mime type provided by the uploader. + * + * @var string + */ + private $mimeType; + + /** + * The file size provided by the uploader. + * + * @var string + */ + private $size; + + /** + * The UPLOAD_ERR_XXX constant provided by the uploader. + * + * @var integer + */ + private $error; + + /** + * Accepts the information of the uploaded file as provided by the PHP global $_FILES. + * + * The file object is only created when the uploaded file is valid (i.e. when the + * isValid() method returns true). Otherwise the only methods that could be called + * on an UploadedFile instance are: + * + * * getClientOriginalName, + * * getClientMimeType, + * * isValid, + * * getError. + * + * Calling any other method on an non-valid instance will cause an unpredictable result. + * + * @param string $path The full temporary path to the file + * @param string $originalName The original file name + * @param string $mimeType The type of the file as provided by PHP + * @param integer $size The file size + * @param integer $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants) + * @param Boolean $test Whether the test mode is active + * + * @throws FileException If file_uploads is disabled + * @throws FileNotFoundException If the file does not exist + * + * @api + */ + public function __construct($path, $originalName, $mimeType = null, $size = null, $error = null, $test = false) + { + if (!ini_get('file_uploads')) { + throw new FileException(sprintf('Unable to create UploadedFile because "file_uploads" is disabled in your php.ini file (%s)', get_cfg_var('cfg_file_path'))); + } + + $this->originalName = $this->getName($originalName); + $this->mimeType = $mimeType ?: 'application/octet-stream'; + $this->size = $size; + $this->error = $error ?: UPLOAD_ERR_OK; + $this->test = (Boolean) $test; + + parent::__construct($path, UPLOAD_ERR_OK === $this->error); + } + + /** + * Returns the original file name. + * + * It is extracted from the request from which the file has been uploaded. + * Then is should not be considered as a safe value. + * + * @return string|null The original name + * + * @api + */ + public function getClientOriginalName() + { + return $this->originalName; + } + + /** + * Returns the original file extension + * + * It is extracted from the original file name that was uploaded. + * Then is should not be considered as a safe value. + * + * @return string The extension + */ + public function getClientOriginalExtension() + { + return pathinfo($this->originalName, PATHINFO_EXTENSION); + } + + /** + * Returns the file mime type. + * + * The client mime type is extracted from the request from which the file + * was uploaded, so it should not be considered as a safe value. + * + * For a trusted mime type, use getMimeType() instead (which guesses the mime + * type based on the file content). + * + * @return string|null The mime type + * + * @see getMimeType + * + * @api + */ + public function getClientMimeType() + { + return $this->mimeType; + } + + /** + * Returns the extension based on the client mime type. + * + * If the mime type is unknown, returns null. + * + * This method uses the mime type as guessed by getClientMimeType() + * to guess the file extension. As such, the extension returned + * by this method cannot be trusted. + * + * For a trusted extension, use guessExtension() instead (which guesses + * the extension based on the guessed mime type for the file). + * + * @return string|null The guessed extension or null if it cannot be guessed + * + * @see guessExtension() + * @see getClientMimeType() + */ + public function guessClientExtension() + { + $type = $this->getClientMimeType(); + $guesser = ExtensionGuesser::getInstance(); + + return $guesser->guess($type); + } + + /** + * Returns the file size. + * + * It is extracted from the request from which the file has been uploaded. + * Then is should not be considered as a safe value. + * + * @return integer|null The file size + * + * @api + */ + public function getClientSize() + { + return $this->size; + } + + /** + * Returns the upload error. + * + * If the upload was successful, the constant UPLOAD_ERR_OK is returned. + * Otherwise one of the other UPLOAD_ERR_XXX constants is returned. + * + * @return integer The upload error + * + * @api + */ + public function getError() + { + return $this->error; + } + + /** + * Returns whether the file was uploaded successfully. + * + * @return Boolean True if the file has been uploaded with HTTP and no error occurred. + * + * @api + */ + public function isValid() + { + $isOk = $this->error === UPLOAD_ERR_OK; + + return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname()); + } + + /** + * Moves the file to a new location. + * + * @param string $directory The destination folder + * @param string $name The new file name + * + * @return File A File object representing the new file + * + * @throws FileException if, for any reason, the file could not have been moved + * + * @api + */ + public function move($directory, $name = null) + { + if ($this->isValid()) { + if ($this->test) { + return parent::move($directory, $name); + } + + $target = $this->getTargetFile($directory, $name); + + if (!@move_uploaded_file($this->getPathname(), $target)) { + $error = error_get_last(); + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message']))); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } + + throw new FileException($this->getErrorMessage($this->getError())); + } + + /** + * Returns the maximum size of an uploaded file as configured in php.ini + * + * @return int The maximum size of an uploaded file in bytes + */ + public static function getMaxFilesize() + { + $max = strtolower(ini_get('upload_max_filesize')); + + if ('' === $max) { + return PHP_INT_MAX; + } + + if (preg_match('#^\+?(0x?)?(.*?)([kmg]?)$#', $max, $match)) { + $shifts = array('' => 0, 'k' => 10, 'm' => 20, 'g' => 30); + $bases = array('' => 10, '0' => 8, '0x' => 16); + + return intval($match[2], $bases[$match[1]]) << $shifts[$match[3]]; + } + + return 0; + } + + /** + * Returns an informative upload error message. + * + * @param int $code The error code returned by an upload attempt + * + * @return string The error message regarding the specified error code + */ + private function getErrorMessage($errorCode) + { + static $errors = array( + UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d kb).', + UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.', + UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.', + UPLOAD_ERR_NO_FILE => 'No file was uploaded.', + UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.', + UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.', + UPLOAD_ERR_EXTENSION => 'File upload was stopped by a php extension.', + ); + + $maxFilesize = $errorCode === UPLOAD_ERR_INI_SIZE ? self::getMaxFilesize() / 1024 : 0; + $message = isset($errors[$errorCode]) ? $errors[$errorCode] : 'The file "%s" was not uploaded due to an unknown error.'; + + return sprintf($message, $this->getClientOriginalName(), $maxFilesize); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/FileBag.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/FileBag.php new file mode 100644 index 0000000..b2775ef --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/FileBag.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\File\UploadedFile; + +/** + * FileBag is a container for HTTP headers. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + * + * @api + */ +class FileBag extends ParameterBag +{ + private static $fileKeys = array('error', 'name', 'size', 'tmp_name', 'type'); + + /** + * Constructor. + * + * @param array $parameters An array of HTTP files + * + * @api + */ + public function __construct(array $parameters = array()) + { + $this->replace($parameters); + } + + /** + * {@inheritdoc} + * + * @api + */ + public function replace(array $files = array()) + { + $this->parameters = array(); + $this->add($files); + } + + /** + * {@inheritdoc} + * + * @api + */ + public function set($key, $value) + { + if (!is_array($value) && !$value instanceof UploadedFile) { + throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.'); + } + + parent::set($key, $this->convertFileInformation($value)); + } + + /** + * {@inheritdoc} + * + * @api + */ + public function add(array $files = array()) + { + foreach ($files as $key => $file) { + $this->set($key, $file); + } + } + + /** + * Converts uploaded files to UploadedFile instances. + * + * @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information + * + * @return array A (multi-dimensional) array of UploadedFile instances + */ + protected function convertFileInformation($file) + { + if ($file instanceof UploadedFile) { + return $file; + } + + $file = $this->fixPhpFilesArray($file); + if (is_array($file)) { + $keys = array_keys($file); + sort($keys); + + if ($keys == self::$fileKeys) { + if (UPLOAD_ERR_NO_FILE == $file['error']) { + $file = null; + } else { + $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['size'], $file['error']); + } + } else { + $file = array_map(array($this, 'convertFileInformation'), $file); + } + } + + return $file; + } + + /** + * Fixes a malformed PHP $_FILES array. + * + * PHP has a bug that the format of the $_FILES array differs, depending on + * whether the uploaded file fields had normal field names or array-like + * field names ("normal" vs. "parent[child]"). + * + * This method fixes the array to look like the "normal" $_FILES array. + * + * It's safe to pass an already converted array, in which case this method + * just returns the original array unmodified. + * + * @param array $data + * + * @return array + */ + protected function fixPhpFilesArray($data) + { + if (!is_array($data)) { + return $data; + } + + $keys = array_keys($data); + sort($keys); + + if (self::$fileKeys != $keys || !isset($data['name']) || !is_array($data['name'])) { + return $data; + } + + $files = $data; + foreach (self::$fileKeys as $k) { + unset($files[$k]); + } + + foreach (array_keys($data['name']) as $key) { + $files[$key] = $this->fixPhpFilesArray(array( + 'error' => $data['error'][$key], + 'name' => $data['name'][$key], + 'type' => $data['type'][$key], + 'tmp_name' => $data['tmp_name'][$key], + 'size' => $data['size'][$key] + )); + } + + return $files; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/HeaderBag.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/HeaderBag.php new file mode 100644 index 0000000..b579eb9 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/HeaderBag.php @@ -0,0 +1,325 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * HeaderBag is a container for HTTP headers. + * + * @author Fabien Potencier + * + * @api + */ +class HeaderBag implements \IteratorAggregate, \Countable +{ + protected $headers; + protected $cacheControl; + + /** + * Constructor. + * + * @param array $headers An array of HTTP headers + * + * @api + */ + public function __construct(array $headers = array()) + { + $this->cacheControl = array(); + $this->headers = array(); + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + + /** + * Returns the headers as a string. + * + * @return string The headers + */ + public function __toString() + { + if (!$this->headers) { + return ''; + } + + $max = max(array_map('strlen', array_keys($this->headers))) + 1; + $content = ''; + ksort($this->headers); + foreach ($this->headers as $name => $values) { + $name = implode('-', array_map('ucfirst', explode('-', $name))); + foreach ($values as $value) { + $content .= sprintf("%-{$max}s %s\r\n", $name.':', $value); + } + } + + return $content; + } + + /** + * Returns the headers. + * + * @return array An array of headers + * + * @api + */ + public function all() + { + return $this->headers; + } + + /** + * Returns the parameter keys. + * + * @return array An array of parameter keys + * + * @api + */ + public function keys() + { + return array_keys($this->headers); + } + + /** + * Replaces the current HTTP headers by a new set. + * + * @param array $headers An array of HTTP headers + * + * @api + */ + public function replace(array $headers = array()) + { + $this->headers = array(); + $this->add($headers); + } + + /** + * Adds new headers the current HTTP headers set. + * + * @param array $headers An array of HTTP headers + * + * @api + */ + public function add(array $headers) + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + + /** + * Returns a header value by name. + * + * @param string $key The header name + * @param mixed $default The default value + * @param Boolean $first Whether to return the first value or all header values + * + * @return string|array The first header value if $first is true, an array of values otherwise + * + * @api + */ + public function get($key, $default = null, $first = true) + { + $key = strtr(strtolower($key), '_', '-'); + + if (!array_key_exists($key, $this->headers)) { + if (null === $default) { + return $first ? null : array(); + } + + return $first ? $default : array($default); + } + + if ($first) { + return count($this->headers[$key]) ? $this->headers[$key][0] : $default; + } + + return $this->headers[$key]; + } + + /** + * Sets a header by name. + * + * @param string $key The key + * @param string|array $values The value or an array of values + * @param Boolean $replace Whether to replace the actual value or not (true by default) + * + * @api + */ + public function set($key, $values, $replace = true) + { + $key = strtr(strtolower($key), '_', '-'); + + $values = array_values((array) $values); + + if (true === $replace || !isset($this->headers[$key])) { + $this->headers[$key] = $values; + } else { + $this->headers[$key] = array_merge($this->headers[$key], $values); + } + + if ('cache-control' === $key) { + $this->cacheControl = $this->parseCacheControl($values[0]); + } + } + + /** + * Returns true if the HTTP header is defined. + * + * @param string $key The HTTP header + * + * @return Boolean true if the parameter exists, false otherwise + * + * @api + */ + public function has($key) + { + return array_key_exists(strtr(strtolower($key), '_', '-'), $this->headers); + } + + /** + * Returns true if the given HTTP header contains the given value. + * + * @param string $key The HTTP header name + * @param string $value The HTTP value + * + * @return Boolean true if the value is contained in the header, false otherwise + * + * @api + */ + public function contains($key, $value) + { + return in_array($value, $this->get($key, null, false)); + } + + /** + * Removes a header. + * + * @param string $key The HTTP header name + * + * @api + */ + public function remove($key) + { + $key = strtr(strtolower($key), '_', '-'); + + unset($this->headers[$key]); + + if ('cache-control' === $key) { + $this->cacheControl = array(); + } + } + + /** + * Returns the HTTP header value converted to a date. + * + * @param string $key The parameter key + * @param \DateTime $default The default value + * + * @return null|\DateTime The parsed DateTime or the default value if the header does not exist + * + * @throws \RuntimeException When the HTTP header is not parseable + * + * @api + */ + public function getDate($key, \DateTime $default = null) + { + if (null === $value = $this->get($key)) { + return $default; + } + + if (false === $date = \DateTime::createFromFormat(DATE_RFC2822, $value)) { + throw new \RuntimeException(sprintf('The %s HTTP header is not parseable (%s).', $key, $value)); + } + + return $date; + } + + public function addCacheControlDirective($key, $value = true) + { + $this->cacheControl[$key] = $value; + + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + + public function hasCacheControlDirective($key) + { + return array_key_exists($key, $this->cacheControl); + } + + public function getCacheControlDirective($key) + { + return array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null; + } + + public function removeCacheControlDirective($key) + { + unset($this->cacheControl[$key]); + + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + + /** + * Returns an iterator for headers. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->headers); + } + + /** + * Returns the number of headers. + * + * @return int The number of headers + */ + public function count() + { + return count($this->headers); + } + + protected function getCacheControlHeader() + { + $parts = array(); + ksort($this->cacheControl); + foreach ($this->cacheControl as $key => $value) { + if (true === $value) { + $parts[] = $key; + } else { + if (preg_match('#[^a-zA-Z0-9._-]#', $value)) { + $value = '"'.$value.'"'; + } + + $parts[] = "$key=$value"; + } + } + + return implode(', ', $parts); + } + + /** + * Parses a Cache-Control HTTP header. + * + * @param string $header The value of the Cache-Control HTTP header + * + * @return array An array representing the attribute values + */ + protected function parseCacheControl($header) + { + $cacheControl = array(); + preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $cacheControl[strtolower($match[1])] = isset($match[3]) ? $match[3] : (isset($match[2]) ? $match[2] : true); + } + + return $cacheControl; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/IpUtils.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/IpUtils.php new file mode 100644 index 0000000..7c3742e --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/IpUtils.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Http utility functions. + * + * @author Fabien Potencier + */ +class IpUtils +{ + /** + * This class should not be instantiated + */ + private function __construct() {} + + /** + * Validates an IPv4 or IPv6 address. + * + * @param string $requestIp + * @param string|array $ips + * + * @return boolean Whether the IP is valid + */ + public static function checkIp($requestIp, $ips) + { + if (!is_array($ips)) { + $ips = array($ips); + } + + $method = false !== strpos($requestIp, ':') ? 'checkIp6': 'checkIp4'; + + foreach ($ips as $ip) { + if (self::$method($requestIp, $ip)) { + return true; + } + } + + return false; + } + + /** + * Validates an IPv4 address. + * + * @param string $requestIp + * @param string $ip + * + * @return boolean Whether the IP is valid + */ + public static function checkIp4($requestIp, $ip) + { + if (false !== strpos($ip, '/')) { + list($address, $netmask) = explode('/', $ip, 2); + + if ($netmask < 1 || $netmask > 32) { + return false; + } + } else { + $address = $ip; + $netmask = 32; + } + + return 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask); + } + + /** + * Validates an IPv6 address. + * + * @author David Soria Parra + * @see https://github.com/dsp/v6tools + * + * @param string $requestIp + * @param string $ip + * + * @return boolean Whether the IP is valid + * + * @throws \RuntimeException When IPV6 support is not enabled + */ + public static function checkIp6($requestIp, $ip) + { + if (!((extension_loaded('sockets') && defined('AF_INET6')) || @inet_pton('::1'))) { + throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".'); + } + + if (false !== strpos($ip, '/')) { + list($address, $netmask) = explode('/', $ip, 2); + + if ($netmask < 1 || $netmask > 128) { + return false; + } + } else { + $address = $ip; + $netmask = 128; + } + + $bytesAddr = unpack("n*", inet_pton($address)); + $bytesTest = unpack("n*", inet_pton($requestIp)); + + for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; $i++) { + $left = $netmask - 16 * ($i-1); + $left = ($left <= 16) ? $left : 16; + $mask = ~(0xffff >> $left) & 0xffff; + if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/JsonResponse.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/JsonResponse.php new file mode 100644 index 0000000..eafccaa --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/JsonResponse.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Response represents an HTTP response in JSON format. + * + * Note that this class does not force the returned JSON content to be an + * object. It is however recommended that you do return an object as it + * protects yourself against XSSI and JSON-JavaScript Hijacking. + * + * @see https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside + * + * @author Igor Wiedler + */ +class JsonResponse extends Response +{ + protected $data; + protected $callback; + + /** + * Constructor. + * + * @param mixed $data The response data + * @param integer $status The response status code + * @param array $headers An array of response headers + */ + public function __construct($data = null, $status = 200, $headers = array()) + { + parent::__construct('', $status, $headers); + + if (null === $data) { + $data = new \ArrayObject(); + } + $this->setData($data); + } + + /** + * {@inheritDoc} + */ + public static function create($data = null, $status = 200, $headers = array()) + { + return new static($data, $status, $headers); + } + + /** + * Sets the JSONP callback. + * + * @param string $callback + * + * @return JsonResponse + * + * @throws \InvalidArgumentException + */ + public function setCallback($callback = null) + { + if (null !== $callback) { + // taken from http://www.geekality.net/2011/08/03/valid-javascript-identifier/ + $pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*+$/u'; + $parts = explode('.', $callback); + foreach ($parts as $part) { + if (!preg_match($pattern, $part)) { + throw new \InvalidArgumentException('The callback name is not valid.'); + } + } + } + + $this->callback = $callback; + + return $this->update(); + } + + /** + * Sets the data to be sent as json. + * + * @param mixed $data + * + * @return JsonResponse + */ + public function setData($data = array()) + { + // Encode <, >, ', &, and " for RFC4627-compliant JSON, which may also be embedded into HTML. + $this->data = json_encode($data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT); + + return $this->update(); + } + + /** + * Updates the content and headers according to the json data and callback. + * + * @return JsonResponse + */ + protected function update() + { + if (null !== $this->callback) { + // Not using application/javascript for compatibility reasons with older browsers. + $this->headers->set('Content-Type', 'text/javascript'); + + return $this->setContent(sprintf('%s(%s);', $this->callback, $this->data)); + } + + // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback) + // in order to not overwrite a custom definition. + if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) { + $this->headers->set('Content-Type', 'application/json'); + } + + return $this->setContent($this->data); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/LICENSE b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/LICENSE new file mode 100644 index 0000000..88a57f8 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2013 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/ParameterBag.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/ParameterBag.php new file mode 100644 index 0000000..c8720cd --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/ParameterBag.php @@ -0,0 +1,305 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ParameterBag is a container for key/value pairs. + * + * @author Fabien Potencier + * + * @api + */ +class ParameterBag implements \IteratorAggregate, \Countable +{ + /** + * Parameter storage. + * + * @var array + */ + protected $parameters; + + /** + * Constructor. + * + * @param array $parameters An array of parameters + * + * @api + */ + public function __construct(array $parameters = array()) + { + $this->parameters = $parameters; + } + + /** + * Returns the parameters. + * + * @return array An array of parameters + * + * @api + */ + public function all() + { + return $this->parameters; + } + + /** + * Returns the parameter keys. + * + * @return array An array of parameter keys + * + * @api + */ + public function keys() + { + return array_keys($this->parameters); + } + + /** + * Replaces the current parameters by a new set. + * + * @param array $parameters An array of parameters + * + * @api + */ + public function replace(array $parameters = array()) + { + $this->parameters = $parameters; + } + + /** + * Adds parameters. + * + * @param array $parameters An array of parameters + * + * @api + */ + public function add(array $parameters = array()) + { + $this->parameters = array_replace($this->parameters, $parameters); + } + + /** + * Returns a parameter by name. + * + * @param string $path The key + * @param mixed $default The default value if the parameter key does not exist + * @param boolean $deep If true, a path like foo[bar] will find deeper items + * + * @return mixed + * + * @throws \InvalidArgumentException + * + * @api + */ + public function get($path, $default = null, $deep = false) + { + if (!$deep || false === $pos = strpos($path, '[')) { + return array_key_exists($path, $this->parameters) ? $this->parameters[$path] : $default; + } + + $root = substr($path, 0, $pos); + if (!array_key_exists($root, $this->parameters)) { + return $default; + } + + $value = $this->parameters[$root]; + $currentKey = null; + for ($i = $pos, $c = strlen($path); $i < $c; $i++) { + $char = $path[$i]; + + if ('[' === $char) { + if (null !== $currentKey) { + throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "[" at position %d.', $i)); + } + + $currentKey = ''; + } elseif (']' === $char) { + if (null === $currentKey) { + throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "]" at position %d.', $i)); + } + + if (!is_array($value) || !array_key_exists($currentKey, $value)) { + return $default; + } + + $value = $value[$currentKey]; + $currentKey = null; + } else { + if (null === $currentKey) { + throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "%s" at position %d.', $char, $i)); + } + + $currentKey .= $char; + } + } + + if (null !== $currentKey) { + throw new \InvalidArgumentException(sprintf('Malformed path. Path must end with "]".')); + } + + return $value; + } + + /** + * Sets a parameter by name. + * + * @param string $key The key + * @param mixed $value The value + * + * @api + */ + public function set($key, $value) + { + $this->parameters[$key] = $value; + } + + /** + * Returns true if the parameter is defined. + * + * @param string $key The key + * + * @return Boolean true if the parameter exists, false otherwise + * + * @api + */ + public function has($key) + { + return array_key_exists($key, $this->parameters); + } + + /** + * Removes a parameter. + * + * @param string $key The key + * + * @api + */ + public function remove($key) + { + unset($this->parameters[$key]); + } + + /** + * Returns the alphabetic characters of the parameter value. + * + * @param string $key The parameter key + * @param mixed $default The default value if the parameter key does not exist + * @param boolean $deep If true, a path like foo[bar] will find deeper items + * + * @return string The filtered value + * + * @api + */ + public function getAlpha($key, $default = '', $deep = false) + { + return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default, $deep)); + } + + /** + * Returns the alphabetic characters and digits of the parameter value. + * + * @param string $key The parameter key + * @param mixed $default The default value if the parameter key does not exist + * @param boolean $deep If true, a path like foo[bar] will find deeper items + * + * @return string The filtered value + * + * @api + */ + public function getAlnum($key, $default = '', $deep = false) + { + return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default, $deep)); + } + + /** + * Returns the digits of the parameter value. + * + * @param string $key The parameter key + * @param mixed $default The default value if the parameter key does not exist + * @param boolean $deep If true, a path like foo[bar] will find deeper items + * + * @return string The filtered value + * + * @api + */ + public function getDigits($key, $default = '', $deep = false) + { + // we need to remove - and + because they're allowed in the filter + return str_replace(array('-', '+'), '', $this->filter($key, $default, $deep, FILTER_SANITIZE_NUMBER_INT)); + } + + /** + * Returns the parameter value converted to integer. + * + * @param string $key The parameter key + * @param mixed $default The default value if the parameter key does not exist + * @param boolean $deep If true, a path like foo[bar] will find deeper items + * + * @return integer The filtered value + * + * @api + */ + public function getInt($key, $default = 0, $deep = false) + { + return (int) $this->get($key, $default, $deep); + } + + /** + * Filter key. + * + * @param string $key Key. + * @param mixed $default Default = null. + * @param boolean $deep Default = false. + * @param integer $filter FILTER_* constant. + * @param mixed $options Filter options. + * + * @see http://php.net/manual/en/function.filter-var.php + * + * @return mixed + */ + public function filter($key, $default = null, $deep = false, $filter=FILTER_DEFAULT, $options=array()) + { + $value = $this->get($key, $default, $deep); + + // Always turn $options into an array - this allows filter_var option shortcuts. + if (!is_array($options) && $options) { + $options = array('flags' => $options); + } + + // Add a convenience check for arrays. + if (is_array($value) && !isset($options['flags'])) { + $options['flags'] = FILTER_REQUIRE_ARRAY; + } + + return filter_var($value, $filter, $options); + } + + /** + * Returns an iterator for parameters. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->parameters); + } + + /** + * Returns the number of parameters. + * + * @return int The number of parameters + */ + public function count() + { + return count($this->parameters); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/README.md b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/README.md new file mode 100644 index 0000000..ed49b4e --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/README.md @@ -0,0 +1,48 @@ +HttpFoundation Component +======================== + +HttpFoundation defines an object-oriented layer for the HTTP specification. + +It provides an abstraction for requests, responses, uploaded files, cookies, +sessions, ... + +In this example, we get a Request object from the current PHP global +variables: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + + $request = Request::createFromGlobals(); + echo $request->getPathInfo(); + +You can also create a Request directly -- that's interesting for unit testing: + + $request = Request::create('/?foo=bar', 'GET'); + echo $request->getPathInfo(); + +And here is how to create and send a Response: + + $response = new Response('Not Found', 404, array('Content-Type' => 'text/plain')); + $response->send(); + +The Request and the Response classes have many other methods that implement +the HTTP specification. + +Loading +------- + +If you are not using Composer but are using PHP 5.3.x, you must add the following to your autoloader: + + // SessionHandlerInterface + if (!interface_exists('SessionHandlerInterface')) { + $loader->registerPrefixFallback(__DIR__.'/../vendor/symfony/src/Symfony/Component/HttpFoundation/Resources/stubs'); + } + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/HttpFoundation/ + $ composer.phar install --dev + $ phpunit diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/RedirectResponse.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/RedirectResponse.php new file mode 100644 index 0000000..54f5721 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/RedirectResponse.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RedirectResponse represents an HTTP response doing a redirect. + * + * @author Fabien Potencier + * + * @api + */ +class RedirectResponse extends Response +{ + protected $targetUrl; + + /** + * Creates a redirect response so that it conforms to the rules defined for a redirect status code. + * + * @param string $url The URL to redirect to + * @param integer $status The status code (302 by default) + * @param array $headers The headers (Location is always set to the given url) + * + * @throws \InvalidArgumentException + * + * @see http://tools.ietf.org/html/rfc2616#section-10.3 + * + * @api + */ + public function __construct($url, $status = 302, $headers = array()) + { + if (empty($url)) { + throw new \InvalidArgumentException('Cannot redirect to an empty URL.'); + } + + parent::__construct('', $status, $headers); + + $this->setTargetUrl($url); + + if (!$this->isRedirect()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status)); + } + } + + /** + * {@inheritDoc} + */ + public static function create($url = '', $status = 302, $headers = array()) + { + return new static($url, $status, $headers); + } + + /** + * Returns the target URL. + * + * @return string target URL + */ + public function getTargetUrl() + { + return $this->targetUrl; + } + + /** + * Sets the redirect target of this response. + * + * @param string $url The URL to redirect to + * + * @return RedirectResponse The current response. + * + * @throws \InvalidArgumentException + */ + public function setTargetUrl($url) + { + if (empty($url)) { + throw new \InvalidArgumentException('Cannot redirect to an empty URL.'); + } + + $this->targetUrl = $url; + + $this->setContent( + sprintf(' + + + + + + Redirecting to %1$s + + + Redirecting to %1$s. + +', htmlspecialchars($url, ENT_QUOTES, 'UTF-8'))); + + $this->headers->set('Location', $url); + + return $this; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Request.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Request.php new file mode 100644 index 0000000..7f79df9 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Request.php @@ -0,0 +1,1768 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * Request represents an HTTP request. + * + * The methods dealing with URL accept / return a raw path (% encoded): + * * getBasePath + * * getBaseUrl + * * getPathInfo + * * getRequestUri + * * getUri + * * getUriForPath + * + * @author Fabien Potencier + * + * @api + */ +class Request +{ + const HEADER_CLIENT_IP = 'client_ip'; + const HEADER_CLIENT_HOST = 'client_host'; + const HEADER_CLIENT_PROTO = 'client_proto'; + const HEADER_CLIENT_PORT = 'client_port'; + + protected static $trustedProxies = array(); + + /** + * @var string[] + */ + protected static $trustedHostPatterns = array(); + + /** + * @var string[] + */ + protected static $trustedHosts = array(); + + /** + * Names for headers that can be trusted when + * using trusted proxies. + * + * The default names are non-standard, but widely used + * by popular reverse proxies (like Apache mod_proxy or Amazon EC2). + */ + protected static $trustedHeaders = array( + self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR', + self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST', + self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO', + self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT', + ); + + protected static $httpMethodParameterOverride = false; + + /** + * @var \Symfony\Component\HttpFoundation\ParameterBag + * + * @api + */ + public $attributes; + + /** + * @var \Symfony\Component\HttpFoundation\ParameterBag + * + * @api + */ + public $request; + + /** + * @var \Symfony\Component\HttpFoundation\ParameterBag + * + * @api + */ + public $query; + + /** + * @var \Symfony\Component\HttpFoundation\ServerBag + * + * @api + */ + public $server; + + /** + * @var \Symfony\Component\HttpFoundation\FileBag + * + * @api + */ + public $files; + + /** + * @var \Symfony\Component\HttpFoundation\ParameterBag + * + * @api + */ + public $cookies; + + /** + * @var \Symfony\Component\HttpFoundation\HeaderBag + * + * @api + */ + public $headers; + + /** + * @var string + */ + protected $content; + + /** + * @var array + */ + protected $languages; + + /** + * @var array + */ + protected $charsets; + + /** + * @var array + */ + protected $acceptableContentTypes; + + /** + * @var string + */ + protected $pathInfo; + + /** + * @var string + */ + protected $requestUri; + + /** + * @var string + */ + protected $baseUrl; + + /** + * @var string + */ + protected $basePath; + + /** + * @var string + */ + protected $method; + + /** + * @var string + */ + protected $format; + + /** + * @var \Symfony\Component\HttpFoundation\Session\SessionInterface + */ + protected $session; + + /** + * @var string + */ + protected $locale; + + /** + * @var string + */ + protected $defaultLocale = 'en'; + + /** + * @var array + */ + protected static $formats; + + /** + * Constructor. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string $content The raw body data + * + * @api + */ + public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + { + $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content); + } + + /** + * Sets the parameters for this request. + * + * This method also re-initializes all properties. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string $content The raw body data + * + * @api + */ + public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + { + $this->request = new ParameterBag($request); + $this->query = new ParameterBag($query); + $this->attributes = new ParameterBag($attributes); + $this->cookies = new ParameterBag($cookies); + $this->files = new FileBag($files); + $this->server = new ServerBag($server); + $this->headers = new HeaderBag($this->server->getHeaders()); + + $this->content = $content; + $this->languages = null; + $this->charsets = null; + $this->acceptableContentTypes = null; + $this->pathInfo = null; + $this->requestUri = null; + $this->baseUrl = null; + $this->basePath = null; + $this->method = null; + $this->format = null; + } + + /** + * Creates a new request with values from PHP's super globals. + * + * @return Request A new request + * + * @api + */ + public static function createFromGlobals() + { + $request = new static($_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER); + + if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded') + && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH')) + ) { + parse_str($request->getContent(), $data); + $request->request = new ParameterBag($data); + } + + return $request; + } + + /** + * Creates a Request based on a given URI and configuration. + * + * The information contained in the URI always take precedence + * over the other information (server and parameters). + * + * @param string $uri The URI + * @param string $method The HTTP method + * @param array $parameters The query (GET) or request (POST) parameters + * @param array $cookies The request cookies ($_COOKIE) + * @param array $files The request files ($_FILES) + * @param array $server The server parameters ($_SERVER) + * @param string $content The raw body data + * + * @return Request A Request instance + * + * @api + */ + public static function create($uri, $method = 'GET', $parameters = array(), $cookies = array(), $files = array(), $server = array(), $content = null) + { + $server = array_replace(array( + 'SERVER_NAME' => 'localhost', + 'SERVER_PORT' => 80, + 'HTTP_HOST' => 'localhost', + 'HTTP_USER_AGENT' => 'Symfony/2.X', + 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5', + 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', + 'REMOTE_ADDR' => '127.0.0.1', + 'SCRIPT_NAME' => '', + 'SCRIPT_FILENAME' => '', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'REQUEST_TIME' => time(), + ), $server); + + $server['PATH_INFO'] = ''; + $server['REQUEST_METHOD'] = strtoupper($method); + + $components = parse_url($uri); + if (isset($components['host'])) { + $server['SERVER_NAME'] = $components['host']; + $server['HTTP_HOST'] = $components['host']; + } + + if (isset($components['scheme'])) { + if ('https' === $components['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + + if (isset($components['port'])) { + $server['SERVER_PORT'] = $components['port']; + $server['HTTP_HOST'] = $server['HTTP_HOST'].':'.$components['port']; + } + + if (isset($components['user'])) { + $server['PHP_AUTH_USER'] = $components['user']; + } + + if (isset($components['pass'])) { + $server['PHP_AUTH_PW'] = $components['pass']; + } + + if (!isset($components['path'])) { + $components['path'] = '/'; + } + + switch (strtoupper($method)) { + case 'POST': + case 'PUT': + case 'DELETE': + if (!isset($server['CONTENT_TYPE'])) { + $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; + } + case 'PATCH': + $request = $parameters; + $query = array(); + break; + default: + $request = array(); + $query = $parameters; + break; + } + + if (isset($components['query'])) { + parse_str(html_entity_decode($components['query']), $qs); + $query = array_replace($qs, $query); + } + $queryString = http_build_query($query, '', '&'); + + $server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : ''); + $server['QUERY_STRING'] = $queryString; + + return new static($query, $request, array(), $cookies, $files, $server, $content); + } + + /** + * Clones a request and overrides some of its parameters. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * + * @return Request The duplicated request + * + * @api + */ + public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) + { + $dup = clone $this; + if ($query !== null) { + $dup->query = new ParameterBag($query); + } + if ($request !== null) { + $dup->request = new ParameterBag($request); + } + if ($attributes !== null) { + $dup->attributes = new ParameterBag($attributes); + } + if ($cookies !== null) { + $dup->cookies = new ParameterBag($cookies); + } + if ($files !== null) { + $dup->files = new FileBag($files); + } + if ($server !== null) { + $dup->server = new ServerBag($server); + $dup->headers = new HeaderBag($dup->server->getHeaders()); + } + $dup->languages = null; + $dup->charsets = null; + $dup->acceptableContentTypes = null; + $dup->pathInfo = null; + $dup->requestUri = null; + $dup->baseUrl = null; + $dup->basePath = null; + $dup->method = null; + $dup->format = null; + + if (!$dup->get('_format')) { + $dup->setRequestFormat($this->getRequestFormat()); + } + + return $dup; + } + + /** + * Clones the current request. + * + * Note that the session is not cloned as duplicated requests + * are most of the time sub-requests of the main one. + */ + public function __clone() + { + $this->query = clone $this->query; + $this->request = clone $this->request; + $this->attributes = clone $this->attributes; + $this->cookies = clone $this->cookies; + $this->files = clone $this->files; + $this->server = clone $this->server; + $this->headers = clone $this->headers; + } + + /** + * Returns the request as a string. + * + * @return string The request + */ + public function __toString() + { + return + sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n". + $this->headers."\r\n". + $this->getContent(); + } + + /** + * Overrides the PHP global variables according to this request instance. + * + * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE. + * $_FILES is never override, see rfc1867 + * + * @api + */ + public function overrideGlobals() + { + $_GET = $this->query->all(); + $_POST = $this->request->all(); + $_SERVER = $this->server->all(); + $_COOKIE = $this->cookies->all(); + + foreach ($this->headers->all() as $key => $value) { + $key = strtoupper(str_replace('-', '_', $key)); + if (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) { + $_SERVER[$key] = implode(', ', $value); + } else { + $_SERVER['HTTP_'.$key] = implode(', ', $value); + } + } + + $request = array('g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE); + + $requestOrder = ini_get('request_order') ?: ini_get('variable_order'); + $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp'; + + $_REQUEST = array(); + foreach (str_split($requestOrder) as $order) { + $_REQUEST = array_merge($_REQUEST, $request[$order]); + } + } + + /** + * Sets a list of trusted proxies. + * + * You should only list the reverse proxies that you manage directly. + * + * @param array $proxies A list of trusted proxies + * + * @api + */ + public static function setTrustedProxies(array $proxies) + { + self::$trustedProxies = $proxies; + } + + /** + * Gets the list of trusted proxies. + * + * @return array An array of trusted proxies. + */ + public static function getTrustedProxies() + { + return self::$trustedProxies; + } + + /** + * Sets a list of trusted host patterns. + * + * You should only list the hosts you manage using regexs. + * + * @param array $hostPatterns A list of trusted host patterns + */ + public static function setTrustedHosts(array $hostPatterns) + { + self::$trustedHostPatterns = array_map(function ($hostPattern) { + return sprintf('{%s}i', str_replace('}', '\\}', $hostPattern)); + }, $hostPatterns); + // we need to reset trusted hosts on trusted host patterns change + self::$trustedHosts = array(); + } + + /** + * Gets the list of trusted host patterns. + * + * @return array An array of trusted host patterns. + */ + public static function getTrustedHosts() + { + return self::$trustedHostPatterns; + } + + /** + * Sets the name for trusted headers. + * + * The following header keys are supported: + * + * * Request::HEADER_CLIENT_IP: defaults to X-Forwarded-For (see getClientIp()) + * * Request::HEADER_CLIENT_HOST: defaults to X-Forwarded-Host (see getClientHost()) + * * Request::HEADER_CLIENT_PORT: defaults to X-Forwarded-Port (see getClientPort()) + * * Request::HEADER_CLIENT_PROTO: defaults to X-Forwarded-Proto (see getScheme() and isSecure()) + * + * Setting an empty value allows to disable the trusted header for the given key. + * + * @param string $key The header key + * @param string $value The header name + * + * @throws \InvalidArgumentException + */ + public static function setTrustedHeaderName($key, $value) + { + if (!array_key_exists($key, self::$trustedHeaders)) { + throw new \InvalidArgumentException(sprintf('Unable to set the trusted header name for key "%s".', $key)); + } + + self::$trustedHeaders[$key] = $value; + } + + /** + * Gets the trusted proxy header name. + * + * @param string $key The header key + * + * @return string The header name + * + * @throws \InvalidArgumentException + */ + public static function getTrustedHeaderName($key) + { + if (!array_key_exists($key, self::$trustedHeaders)) { + throw new \InvalidArgumentException(sprintf('Unable to get the trusted header name for key "%s".', $key)); + } + + return self::$trustedHeaders[$key]; + } + + /** + * Normalizes a query string. + * + * It builds a normalized query string, where keys/value pairs are alphabetized, + * have consistent escaping and unneeded delimiters are removed. + * + * @param string $qs Query string + * + * @return string A normalized query string for the Request + */ + public static function normalizeQueryString($qs) + { + if ('' == $qs) { + return ''; + } + + $parts = array(); + $order = array(); + + foreach (explode('&', $qs) as $param) { + if ('' === $param || '=' === $param[0]) { + // Ignore useless delimiters, e.g. "x=y&". + // Also ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway. + // PHP also does not include them when building _GET. + continue; + } + + $keyValuePair = explode('=', $param, 2); + + // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded). + // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str. This is why we use urldecode and then normalize to + // RFC 3986 with rawurlencode. + $parts[] = isset($keyValuePair[1]) ? + rawurlencode(urldecode($keyValuePair[0])).'='.rawurlencode(urldecode($keyValuePair[1])) : + rawurlencode(urldecode($keyValuePair[0])); + $order[] = urldecode($keyValuePair[0]); + } + + array_multisort($order, SORT_ASC, $parts); + + return implode('&', $parts); + } + + /** + * Enables support for the _method request parameter to determine the intended HTTP method. + * + * Be warned that enabling this feature might lead to CSRF issues in your code. + * Check that you are using CSRF tokens when required. + * + * The HTTP method can only be overridden when the real HTTP method is POST. + */ + public static function enableHttpMethodParameterOverride() + { + self::$httpMethodParameterOverride = true; + } + + /** + * Checks whether support for the _method request parameter is enabled. + * + * @return Boolean True when the _method request parameter is enabled, false otherwise + */ + public static function getHttpMethodParameterOverride() + { + return self::$httpMethodParameterOverride; + } + + /** + * Gets a "parameter" value. + * + * This method is mainly useful for libraries that want to provide some flexibility. + * + * Order of precedence: GET, PATH, POST + * + * Avoid using this method in controllers: + * + * * slow + * * prefer to get from a "named" source + * + * It is better to explicitly get request parameters from the appropriate + * public property instead (query, attributes, request). + * + * @param string $key the key + * @param mixed $default the default value + * @param Boolean $deep is parameter deep in multidimensional array + * + * @return mixed + */ + public function get($key, $default = null, $deep = false) + { + return $this->query->get($key, $this->attributes->get($key, $this->request->get($key, $default, $deep), $deep), $deep); + } + + /** + * Gets the Session. + * + * @return SessionInterface|null The session + * + * @api + */ + public function getSession() + { + return $this->session; + } + + /** + * Whether the request contains a Session which was started in one of the + * previous requests. + * + * @return Boolean + * + * @api + */ + public function hasPreviousSession() + { + // the check for $this->session avoids malicious users trying to fake a session cookie with proper name + return $this->hasSession() && $this->cookies->has($this->session->getName()); + } + + /** + * Whether the request contains a Session object. + * + * This method does not give any information about the state of the session object, + * like whether the session is started or not. It is just a way to check if this Request + * is associated with a Session instance. + * + * @return Boolean true when the Request contains a Session object, false otherwise + * + * @api + */ + public function hasSession() + { + return null !== $this->session; + } + + /** + * Sets the Session. + * + * @param SessionInterface $session The Session + * + * @api + */ + public function setSession(SessionInterface $session) + { + $this->session = $session; + } + + /** + * Returns the client IP addresses. + * + * The least trusted IP address is first, and the most trusted one last. + * The "real" client IP address is the first one, but this is also the + * least trusted one. + * + * Use this method carefully; you should use getClientIp() instead. + * + * @return array The client IP addresses + * + * @see getClientIp() + */ + public function getClientIps() + { + $ip = $this->server->get('REMOTE_ADDR'); + + if (!self::$trustedProxies) { + return array($ip); + } + + if (!self::$trustedHeaders[self::HEADER_CLIENT_IP] || !$this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP])) { + return array($ip); + } + + $clientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP]))); + $clientIps[] = $ip; + + $trustedProxies = !self::$trustedProxies ? array($ip) : self::$trustedProxies; + $ip = $clientIps[0]; + + foreach ($clientIps as $key => $clientIp) { + if (IpUtils::checkIp($clientIp, $trustedProxies)) { + unset($clientIps[$key]); + + continue; + } + } + + return $clientIps ? array_reverse($clientIps) : array($ip); + } + + /** + * Returns the client IP address. + * + * This method can read the client IP address from the "X-Forwarded-For" header + * when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For" + * header value is a comma+space separated list of IP addresses, the left-most + * being the original client, and each successive proxy that passed the request + * adding the IP address where it received the request from. + * + * If your reverse proxy uses a different header name than "X-Forwarded-For", + * ("Client-Ip" for instance), configure it via "setTrustedHeaderName()" with + * the "client-ip" key. + * + * @return string The client IP address + * + * @see getClientIps() + * @see http://en.wikipedia.org/wiki/X-Forwarded-For + * + * @api + */ + public function getClientIp() + { + $ipAddresses = $this->getClientIps(); + + return $ipAddresses[0]; + } + + /** + * Returns current script name. + * + * @return string + * + * @api + */ + public function getScriptName() + { + return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', '')); + } + + /** + * Returns the path being requested relative to the executed script. + * + * The path info always starts with a /. + * + * Suppose this request is instantiated from /mysite on localhost: + * + * * http://localhost/mysite returns an empty string + * * http://localhost/mysite/about returns '/about' + * * http://localhost/mysite/enco%20ded returns '/enco%20ded' + * * http://localhost/mysite/about?var=1 returns '/about' + * + * @return string The raw path (i.e. not urldecoded) + * + * @api + */ + public function getPathInfo() + { + if (null === $this->pathInfo) { + $this->pathInfo = $this->preparePathInfo(); + } + + return $this->pathInfo; + } + + /** + * Returns the root path from which this request is executed. + * + * Suppose that an index.php file instantiates this request object: + * + * * http://localhost/index.php returns an empty string + * * http://localhost/index.php/page returns an empty string + * * http://localhost/web/index.php returns '/web' + * * http://localhost/we%20b/index.php returns '/we%20b' + * + * @return string The raw path (i.e. not urldecoded) + * + * @api + */ + public function getBasePath() + { + if (null === $this->basePath) { + $this->basePath = $this->prepareBasePath(); + } + + return $this->basePath; + } + + /** + * Returns the root url from which this request is executed. + * + * The base URL never ends with a /. + * + * This is similar to getBasePath(), except that it also includes the + * script filename (e.g. index.php) if one exists. + * + * @return string The raw url (i.e. not urldecoded) + * + * @api + */ + public function getBaseUrl() + { + if (null === $this->baseUrl) { + $this->baseUrl = $this->prepareBaseUrl(); + } + + return $this->baseUrl; + } + + /** + * Gets the request's scheme. + * + * @return string + * + * @api + */ + public function getScheme() + { + return $this->isSecure() ? 'https' : 'http'; + } + + /** + * Returns the port on which the request is made. + * + * This method can read the client port from the "X-Forwarded-Port" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Port" header must contain the client port. + * + * If your reverse proxy uses a different header name than "X-Forwarded-Port", + * configure it via "setTrustedHeaderName()" with the "client-port" key. + * + * @return string + * + * @api + */ + public function getPort() + { + if (self::$trustedProxies) { + if (self::$trustedHeaders[self::HEADER_CLIENT_PORT] && $port = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PORT])) { + return $port; + } + + if (self::$trustedHeaders[self::HEADER_CLIENT_PROTO] && 'https' === $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PROTO], 'http')) { + return 443; + } + } + + return $this->server->get('SERVER_PORT'); + } + + /** + * Returns the user. + * + * @return string|null + */ + public function getUser() + { + return $this->server->get('PHP_AUTH_USER'); + } + + /** + * Returns the password. + * + * @return string|null + */ + public function getPassword() + { + return $this->server->get('PHP_AUTH_PW'); + } + + /** + * Gets the user info. + * + * @return string A user name and, optionally, scheme-specific information about how to gain authorization to access the server + */ + public function getUserInfo() + { + $userinfo = $this->getUser(); + + $pass = $this->getPassword(); + if ('' != $pass) { + $userinfo .= ":$pass"; + } + + return $userinfo; + } + + /** + * Returns the HTTP host being requested. + * + * The port name will be appended to the host if it's non-standard. + * + * @return string + * + * @api + */ + public function getHttpHost() + { + $scheme = $this->getScheme(); + $port = $this->getPort(); + + if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) { + return $this->getHost(); + } + + return $this->getHost().':'.$port; + } + + /** + * Returns the requested URI. + * + * @return string The raw URI (i.e. not urldecoded) + * + * @api + */ + public function getRequestUri() + { + if (null === $this->requestUri) { + $this->requestUri = $this->prepareRequestUri(); + } + + return $this->requestUri; + } + + /** + * Gets the scheme and HTTP host. + * + * If the URL was called with basic authentication, the user + * and the password are not added to the generated string. + * + * @return string The scheme and HTTP host + */ + public function getSchemeAndHttpHost() + { + return $this->getScheme().'://'.$this->getHttpHost(); + } + + /** + * Generates a normalized URI for the Request. + * + * @return string A normalized URI for the Request + * + * @see getQueryString() + * + * @api + */ + public function getUri() + { + if (null !== $qs = $this->getQueryString()) { + $qs = '?'.$qs; + } + + return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs; + } + + /** + * Generates a normalized URI for the given path. + * + * @param string $path A path to use instead of the current one + * + * @return string The normalized URI for the path + * + * @api + */ + public function getUriForPath($path) + { + return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path; + } + + /** + * Generates the normalized query string for the Request. + * + * It builds a normalized query string, where keys/value pairs are alphabetized + * and have consistent escaping. + * + * @return string|null A normalized query string for the Request + * + * @api + */ + public function getQueryString() + { + $qs = static::normalizeQueryString($this->server->get('QUERY_STRING')); + + return '' === $qs ? null : $qs; + } + + /** + * Checks whether the request is secure or not. + * + * This method can read the client port from the "X-Forwarded-Proto" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http". + * + * If your reverse proxy uses a different header name than "X-Forwarded-Proto" + * ("SSL_HTTPS" for instance), configure it via "setTrustedHeaderName()" with + * the "client-proto" key. + * + * @return Boolean + * + * @api + */ + public function isSecure() + { + if (self::$trustedProxies && self::$trustedHeaders[self::HEADER_CLIENT_PROTO] && $proto = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PROTO])) { + return in_array(strtolower($proto), array('https', 'on', '1')); + } + + return 'on' == strtolower($this->server->get('HTTPS')) || 1 == $this->server->get('HTTPS'); + } + + /** + * Returns the host name. + * + * This method can read the client port from the "X-Forwarded-Host" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Host" header must contain the client host name. + * + * If your reverse proxy uses a different header name than "X-Forwarded-Host", + * configure it via "setTrustedHeaderName()" with the "client-host" key. + * + * @return string + * + * @throws \UnexpectedValueException when the host name is invalid + * + * @api + */ + public function getHost() + { + if (self::$trustedProxies && self::$trustedHeaders[self::HEADER_CLIENT_HOST] && $host = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_HOST])) { + $elements = explode(',', $host); + + $host = $elements[count($elements) - 1]; + } elseif (!$host = $this->headers->get('HOST')) { + if (!$host = $this->server->get('SERVER_NAME')) { + $host = $this->server->get('SERVER_ADDR', ''); + } + } + + // trim and remove port number from host + // host is lowercase as per RFC 952/2181 + $host = strtolower(preg_replace('/:\d+$/', '', trim($host))); + + // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user) + // check that it does not contain forbidden characters (see RFC 952 and RFC 2181) + if ($host && !preg_match('/^\[?(?:[a-zA-Z0-9-:\]_]+\.?)+$/', $host)) { + throw new \UnexpectedValueException('Invalid Host "'.$host.'"'); + } + + if (count(self::$trustedHostPatterns) > 0) { + // to avoid host header injection attacks, you should provide a list of trusted host patterns + + if (in_array($host, self::$trustedHosts)) { + return $host; + } + + foreach (self::$trustedHostPatterns as $pattern) { + if (preg_match($pattern, $host)) { + self::$trustedHosts[] = $host; + + return $host; + } + } + + throw new \UnexpectedValueException('Untrusted Host "'.$host.'"'); + } + + return $host; + } + + /** + * Sets the request method. + * + * @param string $method + * + * @api + */ + public function setMethod($method) + { + $this->method = null; + $this->server->set('REQUEST_METHOD', $method); + } + + /** + * Gets the request "intended" method. + * + * If the X-HTTP-Method-Override header is set, and if the method is a POST, + * then it is used to determine the "real" intended HTTP method. + * + * The _method request parameter can also be used to determine the HTTP method, + * but only if enableHttpMethodParameterOverride() has been called. + * + * The method is always an uppercased string. + * + * @return string The request method + * + * @api + * + * @see getRealMethod + */ + public function getMethod() + { + if (null === $this->method) { + $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + + if ('POST' === $this->method) { + if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) { + $this->method = strtoupper($method); + } elseif (self::$httpMethodParameterOverride) { + $this->method = strtoupper($this->request->get('_method', $this->query->get('_method', 'POST'))); + } + } + } + + return $this->method; + } + + /** + * Gets the "real" request method. + * + * @return string The request method + * + * @see getMethod + */ + public function getRealMethod() + { + return strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + } + + /** + * Gets the mime type associated with the format. + * + * @param string $format The format + * + * @return string The associated mime type (null if not found) + * + * @api + */ + public function getMimeType($format) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + return isset(static::$formats[$format]) ? static::$formats[$format][0] : null; + } + + /** + * Gets the format associated with the mime type. + * + * @param string $mimeType The associated mime type + * + * @return string|null The format (null if not found) + * + * @api + */ + public function getFormat($mimeType) + { + if (false !== $pos = strpos($mimeType, ';')) { + $mimeType = substr($mimeType, 0, $pos); + } + + if (null === static::$formats) { + static::initializeFormats(); + } + + foreach (static::$formats as $format => $mimeTypes) { + if (in_array($mimeType, (array) $mimeTypes)) { + return $format; + } + } + + return null; + } + + /** + * Associates a format with mime types. + * + * @param string $format The format + * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type) + * + * @api + */ + public function setFormat($format, $mimeTypes) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + static::$formats[$format] = is_array($mimeTypes) ? $mimeTypes : array($mimeTypes); + } + + /** + * Gets the request format. + * + * Here is the process to determine the format: + * + * * format defined by the user (with setRequestFormat()) + * * _format request parameter + * * $default + * + * @param string $default The default format + * + * @return string The request format + * + * @api + */ + public function getRequestFormat($default = 'html') + { + if (null === $this->format) { + $this->format = $this->get('_format', $default); + } + + return $this->format; + } + + /** + * Sets the request format. + * + * @param string $format The request format. + * + * @api + */ + public function setRequestFormat($format) + { + $this->format = $format; + } + + /** + * Gets the format associated with the request. + * + * @return string|null The format (null if no content type is present) + * + * @api + */ + public function getContentType() + { + return $this->getFormat($this->headers->get('CONTENT_TYPE')); + } + + /** + * Sets the default locale. + * + * @param string $locale + * + * @api + */ + public function setDefaultLocale($locale) + { + $this->defaultLocale = $locale; + + if (null === $this->locale) { + $this->setPhpDefaultLocale($locale); + } + } + + /** + * Sets the locale. + * + * @param string $locale + * + * @api + */ + public function setLocale($locale) + { + $this->setPhpDefaultLocale($this->locale = $locale); + } + + /** + * Get the locale. + * + * @return string + */ + public function getLocale() + { + return null === $this->locale ? $this->defaultLocale : $this->locale; + } + + /** + * Checks if the request method is of specified type. + * + * @param string $method Uppercase request method (GET, POST etc). + * + * @return Boolean + */ + public function isMethod($method) + { + return $this->getMethod() === strtoupper($method); + } + + /** + * Checks whether the method is safe or not. + * + * @return Boolean + * + * @api + */ + public function isMethodSafe() + { + return in_array($this->getMethod(), array('GET', 'HEAD')); + } + + /** + * Returns the request body content. + * + * @param Boolean $asResource If true, a resource will be returned + * + * @return string|resource The request body content or a resource to read the body stream. + * + * @throws \LogicException + */ + public function getContent($asResource = false) + { + if (false === $this->content || (true === $asResource && null !== $this->content)) { + throw new \LogicException('getContent() can only be called once when using the resource return type.'); + } + + if (true === $asResource) { + $this->content = false; + + return fopen('php://input', 'rb'); + } + + if (null === $this->content) { + $this->content = file_get_contents('php://input'); + } + + return $this->content; + } + + /** + * Gets the Etags. + * + * @return array The entity tags + */ + public function getETags() + { + return preg_split('/\s*,\s*/', $this->headers->get('if_none_match'), null, PREG_SPLIT_NO_EMPTY); + } + + /** + * @return Boolean + */ + public function isNoCache() + { + return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma'); + } + + /** + * Returns the preferred language. + * + * @param array $locales An array of ordered available locales + * + * @return string|null The preferred locale + * + * @api + */ + public function getPreferredLanguage(array $locales = null) + { + $preferredLanguages = $this->getLanguages(); + + if (empty($locales)) { + return isset($preferredLanguages[0]) ? $preferredLanguages[0] : null; + } + + if (!$preferredLanguages) { + return $locales[0]; + } + + $extendedPreferredLanguages = array(); + foreach ($preferredLanguages as $language) { + $extendedPreferredLanguages[] = $language; + if (false !== $position = strpos($language, '_')) { + $superLanguage = substr($language, 0, $position); + if (!in_array($superLanguage, $preferredLanguages)) { + $extendedPreferredLanguages[] = $superLanguage; + } + } + } + + $preferredLanguages = array_values(array_intersect($extendedPreferredLanguages, $locales)); + + return isset($preferredLanguages[0]) ? $preferredLanguages[0] : $locales[0]; + } + + /** + * Gets a list of languages acceptable by the client browser. + * + * @return array Languages ordered in the user browser preferences + * + * @api + */ + public function getLanguages() + { + if (null !== $this->languages) { + return $this->languages; + } + + $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all(); + $this->languages = array(); + foreach (array_keys($languages) as $lang) { + if (strstr($lang, '-')) { + $codes = explode('-', $lang); + if ($codes[0] == 'i') { + // Language not listed in ISO 639 that are not variants + // of any listed language, which can be registered with the + // i-prefix, such as i-cherokee + if (count($codes) > 1) { + $lang = $codes[1]; + } + } else { + for ($i = 0, $max = count($codes); $i < $max; $i++) { + if ($i == 0) { + $lang = strtolower($codes[0]); + } else { + $lang .= '_'.strtoupper($codes[$i]); + } + } + } + } + + $this->languages[] = $lang; + } + + return $this->languages; + } + + /** + * Gets a list of charsets acceptable by the client browser. + * + * @return array List of charsets in preferable order + * + * @api + */ + public function getCharsets() + { + if (null !== $this->charsets) { + return $this->charsets; + } + + return $this->charsets = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all()); + } + + /** + * Gets a list of content types acceptable by the client browser + * + * @return array List of content types in preferable order + * + * @api + */ + public function getAcceptableContentTypes() + { + if (null !== $this->acceptableContentTypes) { + return $this->acceptableContentTypes; + } + + return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all()); + } + + /** + * Returns true if the request is a XMLHttpRequest. + * + * It works if your JavaScript library set an X-Requested-With HTTP header. + * It is known to work with common JavaScript frameworks: + * @link http://en.wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript + * + * @return Boolean true if the request is an XMLHttpRequest, false otherwise + * + * @api + */ + public function isXmlHttpRequest() + { + return 'XMLHttpRequest' == $this->headers->get('X-Requested-With'); + } + + /* + * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24) + * + * Code subject to the new BSD license (http://framework.zend.com/license/new-bsd). + * + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + */ + + protected function prepareRequestUri() + { + $requestUri = ''; + + if ($this->headers->has('X_ORIGINAL_URL')) { + // IIS with Microsoft Rewrite Module + $requestUri = $this->headers->get('X_ORIGINAL_URL'); + $this->headers->remove('X_ORIGINAL_URL'); + $this->server->remove('HTTP_X_ORIGINAL_URL'); + $this->server->remove('UNENCODED_URL'); + $this->server->remove('IIS_WasUrlRewritten'); + } elseif ($this->headers->has('X_REWRITE_URL')) { + // IIS with ISAPI_Rewrite + $requestUri = $this->headers->get('X_REWRITE_URL'); + $this->headers->remove('X_REWRITE_URL'); + } elseif ($this->server->get('IIS_WasUrlRewritten') == '1' && $this->server->get('UNENCODED_URL') != '') { + // IIS7 with URL Rewrite: make sure we get the unencoded url (double slash problem) + $requestUri = $this->server->get('UNENCODED_URL'); + $this->server->remove('UNENCODED_URL'); + $this->server->remove('IIS_WasUrlRewritten'); + } elseif ($this->server->has('REQUEST_URI')) { + $requestUri = $this->server->get('REQUEST_URI'); + // HTTP proxy reqs setup request uri with scheme and host [and port] + the url path, only use url path + $schemeAndHttpHost = $this->getSchemeAndHttpHost(); + if (strpos($requestUri, $schemeAndHttpHost) === 0) { + $requestUri = substr($requestUri, strlen($schemeAndHttpHost)); + } + } elseif ($this->server->has('ORIG_PATH_INFO')) { + // IIS 5.0, PHP as CGI + $requestUri = $this->server->get('ORIG_PATH_INFO'); + if ('' != $this->server->get('QUERY_STRING')) { + $requestUri .= '?'.$this->server->get('QUERY_STRING'); + } + $this->server->remove('ORIG_PATH_INFO'); + } + + // normalize the request URI to ease creating sub-requests from this request + $this->server->set('REQUEST_URI', $requestUri); + + return $requestUri; + } + + /** + * Prepares the base URL. + * + * @return string + */ + protected function prepareBaseUrl() + { + $filename = basename($this->server->get('SCRIPT_FILENAME')); + + if (basename($this->server->get('SCRIPT_NAME')) === $filename) { + $baseUrl = $this->server->get('SCRIPT_NAME'); + } elseif (basename($this->server->get('PHP_SELF')) === $filename) { + $baseUrl = $this->server->get('PHP_SELF'); + } elseif (basename($this->server->get('ORIG_SCRIPT_NAME')) === $filename) { + $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility + } else { + // Backtrack up the script_filename to find the portion matching + // php_self + $path = $this->server->get('PHP_SELF', ''); + $file = $this->server->get('SCRIPT_FILENAME', ''); + $segs = explode('/', trim($file, '/')); + $segs = array_reverse($segs); + $index = 0; + $last = count($segs); + $baseUrl = ''; + do { + $seg = $segs[$index]; + $baseUrl = '/'.$seg.$baseUrl; + ++$index; + } while (($last > $index) && (false !== ($pos = strpos($path, $baseUrl))) && (0 != $pos)); + } + + // Does the baseUrl have anything in common with the request_uri? + $requestUri = $this->getRequestUri(); + + if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) { + // full $baseUrl matches + return $prefix; + } + + if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, dirname($baseUrl))) { + // directory portion of $baseUrl matches + return rtrim($prefix, '/'); + } + + $truncatedRequestUri = $requestUri; + if (($pos = strpos($requestUri, '?')) !== false) { + $truncatedRequestUri = substr($requestUri, 0, $pos); + } + + $basename = basename($baseUrl); + if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) { + // no match whatsoever; set it blank + return ''; + } + + // If using mod_rewrite or ISAPI_Rewrite strip the script filename + // out of baseUrl. $pos !== 0 makes sure it is not matching a value + // from PATH_INFO or QUERY_STRING + if ((strlen($requestUri) >= strlen($baseUrl)) && ((false !== ($pos = strpos($requestUri, $baseUrl))) && ($pos !== 0))) { + $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl)); + } + + return rtrim($baseUrl, '/'); + } + + /** + * Prepares the base path. + * + * @return string base path + */ + protected function prepareBasePath() + { + $filename = basename($this->server->get('SCRIPT_FILENAME')); + $baseUrl = $this->getBaseUrl(); + if (empty($baseUrl)) { + return ''; + } + + if (basename($baseUrl) === $filename) { + $basePath = dirname($baseUrl); + } else { + $basePath = $baseUrl; + } + + if ('\\' === DIRECTORY_SEPARATOR) { + $basePath = str_replace('\\', '/', $basePath); + } + + return rtrim($basePath, '/'); + } + + /** + * Prepares the path info. + * + * @return string path info + */ + protected function preparePathInfo() + { + $baseUrl = $this->getBaseUrl(); + + if (null === ($requestUri = $this->getRequestUri())) { + return '/'; + } + + $pathInfo = '/'; + + // Remove the query string from REQUEST_URI + if ($pos = strpos($requestUri, '?')) { + $requestUri = substr($requestUri, 0, $pos); + } + + if ((null !== $baseUrl) && (false === ($pathInfo = substr($requestUri, strlen($baseUrl))))) { + // If substr() returns false then PATH_INFO is set to an empty string + return '/'; + } elseif (null === $baseUrl) { + return $requestUri; + } + + return (string) $pathInfo; + } + + /** + * Initializes HTTP request formats. + */ + protected static function initializeFormats() + { + static::$formats = array( + 'html' => array('text/html', 'application/xhtml+xml'), + 'txt' => array('text/plain'), + 'js' => array('application/javascript', 'application/x-javascript', 'text/javascript'), + 'css' => array('text/css'), + 'json' => array('application/json', 'application/x-json'), + 'xml' => array('text/xml', 'application/xml', 'application/x-xml'), + 'rdf' => array('application/rdf+xml'), + 'atom' => array('application/atom+xml'), + 'rss' => array('application/rss+xml'), + ); + } + + /** + * Sets the default PHP locale. + * + * @param string $locale + */ + private function setPhpDefaultLocale($locale) + { + // if either the class Locale doesn't exist, or an exception is thrown when + // setting the default locale, the intl module is not installed, and + // the call can be ignored: + try { + if (class_exists('Locale', false)) { + \Locale::setDefault($locale); + } + } catch (\Exception $e) { + } + } + + /* + * Returns the prefix as encoded in the string when the string starts with + * the given prefix, false otherwise. + * + * @param string $string The urlencoded string + * @param string $prefix The prefix not encoded + * + * @return string|false The prefix as it is encoded in $string, or false + */ + private function getUrlencodedPrefix($string, $prefix) + { + if (0 !== strpos(rawurldecode($string), $prefix)) { + return false; + } + + $len = strlen($prefix); + + if (preg_match("#^(%[[:xdigit:]]{2}|.){{$len}}#", $string, $match)) { + return $match[0]; + } + + return false; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/RequestMatcher.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/RequestMatcher.php new file mode 100644 index 0000000..da95c3a --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/RequestMatcher.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RequestMatcher compares a pre-defined set of checks against a Request instance. + * + * @author Fabien Potencier + * + * @api + */ +class RequestMatcher implements RequestMatcherInterface +{ + /** + * @var string + */ + private $path; + + /** + * @var string + */ + private $host; + + /** + * @var array + */ + private $methods = array(); + + /** + * @var string + */ + private $ips = array(); + + /** + * @var array + */ + private $attributes = array(); + + /** + * @param string|null $path + * @param string|null $host + * @param string|string[]|null $methods + * @param string|string[]|null $ips + * @param array $attributes + */ + public function __construct($path = null, $host = null, $methods = null, $ips = null, array $attributes = array()) + { + $this->matchPath($path); + $this->matchHost($host); + $this->matchMethod($methods); + $this->matchIps($ips); + foreach ($attributes as $k => $v) { + $this->matchAttribute($k, $v); + } + } + + /** + * Adds a check for the URL host name. + * + * @param string $regexp A Regexp + */ + public function matchHost($regexp) + { + $this->host = $regexp; + } + + /** + * Adds a check for the URL path info. + * + * @param string $regexp A Regexp + */ + public function matchPath($regexp) + { + $this->path = $regexp; + } + + /** + * Adds a check for the client IP. + * + * @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + */ + public function matchIp($ip) + { + $this->matchIps($ip); + } + + /** + * Adds a check for the client IP. + * + * @param string|string[] $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + */ + public function matchIps($ips) + { + $this->ips = (array) $ips; + } + + /** + * Adds a check for the HTTP method. + * + * @param string|string[]|null $method An HTTP method or an array of HTTP methods + */ + public function matchMethod($method) + { + $this->methods = array_map('strtoupper', (array) $method); + } + + /** + * Adds a check for request attribute. + * + * @param string $key The request attribute name + * @param string $regexp A Regexp + */ + public function matchAttribute($key, $regexp) + { + $this->attributes[$key] = $regexp; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function matches(Request $request) + { + if ($this->methods && !in_array($request->getMethod(), $this->methods)) { + return false; + } + + foreach ($this->attributes as $key => $pattern) { + if (!preg_match('{'.$pattern.'}', $request->attributes->get($key))) { + return false; + } + } + + if (null !== $this->path && !preg_match('{'.$this->path.'}', rawurldecode($request->getPathInfo()))) { + return false; + } + + if (null !== $this->host && !preg_match('{'.$this->host.'}i', $request->getHost())) { + return false; + } + + if (IpUtils::checkIp($request->getClientIp(), $this->ips)) { + return true; + } + + // Note to future implementors: add additional checks above the + // foreach above or else your check might not be run! + return count($this->ips) === 0; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/RequestMatcherInterface.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/RequestMatcherInterface.php new file mode 100644 index 0000000..695fd21 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/RequestMatcherInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RequestMatcherInterface is an interface for strategies to match a Request. + * + * @author Fabien Potencier + * + * @api + */ +interface RequestMatcherInterface +{ + /** + * Decides whether the rule(s) implemented by the strategy matches the supplied request. + * + * @param Request $request The request to check for a match + * + * @return Boolean true if the request matches, false otherwise + * + * @api + */ + public function matches(Request $request); +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Resources/stubs/SessionHandlerInterface.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Resources/stubs/SessionHandlerInterface.php new file mode 100644 index 0000000..b6bbfc2 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Resources/stubs/SessionHandlerInterface.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * SessionHandlerInterface + * + * Provides forward compatibility with PHP 5.4 + * + * Extensive documentation can be found at php.net, see links: + * + * @see http://php.net/sessionhandlerinterface + * @see http://php.net/session.customhandler + * @see http://php.net/session-set-save-handler + * + * @author Drak + */ +interface SessionHandlerInterface +{ + /** + * Open session. + * + * @see http://php.net/sessionhandlerinterface.open + * + * @param string $savePath Save path. + * @param string $sessionName Session Name. + * + * @throws \RuntimeException If something goes wrong starting the session. + * + * @return boolean + */ + public function open($savePath, $sessionName); + + /** + * Close session. + * + * @see http://php.net/sessionhandlerinterface.close + * + * @return boolean + */ + public function close(); + + /** + * Read session. + * + * @param string $sessionId + * + * @see http://php.net/sessionhandlerinterface.read + * + * @throws \RuntimeException On fatal error but not "record not found". + * + * @return string String as stored in persistent storage or empty string in all other cases. + */ + public function read($sessionId); + + /** + * Commit session to storage. + * + * @see http://php.net/sessionhandlerinterface.write + * + * @param string $sessionId Session ID. + * @param string $data Session serialized data to save. + * + * @return boolean + */ + public function write($sessionId, $data); + + /** + * Destroys this session. + * + * @see http://php.net/sessionhandlerinterface.destroy + * + * @param string $sessionId Session ID. + * + * @throws \RuntimeException On fatal error. + * + * @return boolean + */ + public function destroy($sessionId); + + /** + * Garbage collection for storage. + * + * @see http://php.net/sessionhandlerinterface.gc + * + * @param integer $lifetime Max lifetime in seconds to keep sessions stored. + * + * @throws \RuntimeException On fatal error. + * + * @return boolean + */ + public function gc($lifetime); +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Response.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Response.php new file mode 100644 index 0000000..25c49c9 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Response.php @@ -0,0 +1,1196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Response represents an HTTP response. + * + * @author Fabien Potencier + * + * @api + */ +class Response +{ + /** + * @var \Symfony\Component\HttpFoundation\ResponseHeaderBag + */ + public $headers; + + /** + * @var string + */ + protected $content; + + /** + * @var string + */ + protected $version; + + /** + * @var integer + */ + protected $statusCode; + + /** + * @var string + */ + protected $statusText; + + /** + * @var string + */ + protected $charset; + + /** + * Status codes translation table. + * + * The list of codes is complete according to the + * {@link http://www.iana.org/assignments/http-status-codes/ Hypertext Transfer Protocol (HTTP) Status Code Registry} + * (last updated 2012-02-13). + * + * Unless otherwise noted, the status code is defined in RFC2616. + * + * @var array + */ + public static $statusTexts = array( + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', // RFC2518 + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', // RFC4918 + 208 => 'Already Reported', // RFC5842 + 226 => 'IM Used', // RFC3229 + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Reserved', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', // RFC-reschke-http-status-308-07 + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', // RFC2324 + 422 => 'Unprocessable Entity', // RFC4918 + 423 => 'Locked', // RFC4918 + 424 => 'Failed Dependency', // RFC4918 + 425 => 'Reserved for WebDAV advanced collections expired proposal', // RFC2817 + 426 => 'Upgrade Required', // RFC2817 + 428 => 'Precondition Required', // RFC6585 + 429 => 'Too Many Requests', // RFC6585 + 431 => 'Request Header Fields Too Large', // RFC6585 + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates (Experimental)', // RFC2295 + 507 => 'Insufficient Storage', // RFC4918 + 508 => 'Loop Detected', // RFC5842 + 510 => 'Not Extended', // RFC2774 + 511 => 'Network Authentication Required', // RFC6585 + ); + + /** + * Constructor. + * + * @param string $content The response content + * @param integer $status The response status code + * @param array $headers An array of response headers + * + * @throws \InvalidArgumentException When the HTTP status code is not valid + * + * @api + */ + public function __construct($content = '', $status = 200, $headers = array()) + { + $this->headers = new ResponseHeaderBag($headers); + $this->setContent($content); + $this->setStatusCode($status); + $this->setProtocolVersion('1.0'); + if (!$this->headers->has('Date')) { + $this->setDate(new \DateTime(null, new \DateTimeZone('UTC'))); + } + } + + /** + * Factory method for chainability + * + * Example: + * + * return Response::create($body, 200) + * ->setSharedMaxAge(300); + * + * @param string $content The response content + * @param integer $status The response status code + * @param array $headers An array of response headers + * + * @return Response + */ + public static function create($content = '', $status = 200, $headers = array()) + { + return new static($content, $status, $headers); + } + + /** + * Returns the Response as an HTTP string. + * + * The string representation of the Response is the same as the + * one that will be sent to the client only if the prepare() method + * has been called before. + * + * @return string The Response as an HTTP string + * + * @see prepare() + */ + public function __toString() + { + return + sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n". + $this->headers."\r\n". + $this->getContent(); + } + + /** + * Clones the current Response instance. + */ + public function __clone() + { + $this->headers = clone $this->headers; + } + + /** + * Prepares the Response before it is sent to the client. + * + * This method tweaks the Response to ensure that it is + * compliant with RFC 2616. Most of the changes are based on + * the Request that is "associated" with this Response. + * + * @param Request $request A Request instance + * + * @return Response The current response. + */ + public function prepare(Request $request) + { + $headers = $this->headers; + + if ($this->isInformational() || in_array($this->statusCode, array(204, 304))) { + $this->setContent(null); + } + + // Content-type based on the Request + if (!$headers->has('Content-Type')) { + $format = $request->getRequestFormat(); + if (null !== $format && $mimeType = $request->getMimeType($format)) { + $headers->set('Content-Type', $mimeType); + } + } + + // Fix Content-Type + $charset = $this->charset ?: 'UTF-8'; + if (!$headers->has('Content-Type')) { + $headers->set('Content-Type', 'text/html; charset='.$charset); + } elseif (0 === strpos($headers->get('Content-Type'), 'text/') && false === strpos($headers->get('Content-Type'), 'charset')) { + // add the charset + $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset); + } + + // Fix Content-Length + if ($headers->has('Transfer-Encoding')) { + $headers->remove('Content-Length'); + } + + if ($request->isMethod('HEAD')) { + // cf. RFC2616 14.13 + $length = $headers->get('Content-Length'); + $this->setContent(null); + if ($length) { + $headers->set('Content-Length', $length); + } + } + + // Fix protocol + if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) { + $this->setProtocolVersion('1.1'); + } + + // Check if we need to send extra expire info headers + if ('1.0' == $this->getProtocolVersion() && 'no-cache' == $this->headers->get('Cache-Control')) { + $this->headers->set('pragma', 'no-cache'); + $this->headers->set('expires', -1); + } + + $this->ensureIEOverSSLCompatibility($request); + + return $this; + } + + /** + * Sends HTTP headers. + * + * @return Response + */ + public function sendHeaders() + { + // headers have already been sent by the developer + if (headers_sent()) { + return $this; + } + + // status + header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)); + + // headers + foreach ($this->headers->allPreserveCase() as $name => $values) { + foreach ($values as $value) { + header($name.': '.$value, false); + } + } + + // cookies + foreach ($this->headers->getCookies() as $cookie) { + setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); + } + + return $this; + } + + /** + * Sends content for the current web response. + * + * @return Response + */ + public function sendContent() + { + echo $this->content; + + return $this; + } + + /** + * Sends HTTP headers and content. + * + * @return Response + * + * @api + */ + public function send() + { + $this->sendHeaders(); + $this->sendContent(); + + if (function_exists('fastcgi_finish_request')) { + fastcgi_finish_request(); + } elseif ('cli' !== PHP_SAPI) { + // ob_get_level() never returns 0 on some Windows configurations, so if + // the level is the same two times in a row, the loop should be stopped. + $previous = null; + $obStatus = ob_get_status(1); + while (($level = ob_get_level()) > 0 && $level !== $previous) { + $previous = $level; + if ($obStatus[$level - 1]) { + if (version_compare(PHP_VERSION, '5.4', '>=')) { + if (isset($obStatus[$level - 1]['flags']) && ($obStatus[$level - 1]['flags'] & PHP_OUTPUT_HANDLER_REMOVABLE)) { + ob_end_flush(); + } + } else { + if (isset($obStatus[$level - 1]['del']) && $obStatus[$level - 1]['del']) { + ob_end_flush(); + } + } + } + } + flush(); + } + + return $this; + } + + /** + * Sets the response content. + * + * Valid types are strings, numbers, and objects that implement a __toString() method. + * + * @param mixed $content + * + * @return Response + * + * @throws \UnexpectedValueException + * + * @api + */ + public function setContent($content) + { + if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable(array($content, '__toString'))) { + throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', gettype($content))); + } + + $this->content = (string) $content; + + return $this; + } + + /** + * Gets the current response content. + * + * @return string Content + * + * @api + */ + public function getContent() + { + return $this->content; + } + + /** + * Sets the HTTP protocol version (1.0 or 1.1). + * + * @param string $version The HTTP protocol version + * + * @return Response + * + * @api + */ + public function setProtocolVersion($version) + { + $this->version = $version; + + return $this; + } + + /** + * Gets the HTTP protocol version. + * + * @return string The HTTP protocol version + * + * @api + */ + public function getProtocolVersion() + { + return $this->version; + } + + /** + * Sets the response status code. + * + * @param integer $code HTTP status code + * @param mixed $text HTTP status text + * + * If the status text is null it will be automatically populated for the known + * status codes and left empty otherwise. + * + * @return Response + * + * @throws \InvalidArgumentException When the HTTP status code is not valid + * + * @api + */ + public function setStatusCode($code, $text = null) + { + $this->statusCode = $code = (int) $code; + if ($this->isInvalid()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code)); + } + + if (null === $text) { + $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : ''; + + return $this; + } + + if (false === $text) { + $this->statusText = ''; + + return $this; + } + + $this->statusText = $text; + + return $this; + } + + /** + * Retrieves the status code for the current web response. + * + * @return integer Status code + * + * @api + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * Sets the response charset. + * + * @param string $charset Character set + * + * @return Response + * + * @api + */ + public function setCharset($charset) + { + $this->charset = $charset; + + return $this; + } + + /** + * Retrieves the response charset. + * + * @return string Character set + * + * @api + */ + public function getCharset() + { + return $this->charset; + } + + /** + * Returns true if the response is worth caching under any circumstance. + * + * Responses marked "private" with an explicit Cache-Control directive are + * considered uncacheable. + * + * Responses with neither a freshness lifetime (Expires, max-age) nor cache + * validator (Last-Modified, ETag) are considered uncacheable. + * + * @return Boolean true if the response is worth caching, false otherwise + * + * @api + */ + public function isCacheable() + { + if (!in_array($this->statusCode, array(200, 203, 300, 301, 302, 404, 410))) { + return false; + } + + if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) { + return false; + } + + return $this->isValidateable() || $this->isFresh(); + } + + /** + * Returns true if the response is "fresh". + * + * Fresh responses may be served from cache without any interaction with the + * origin. A response is considered fresh when it includes a Cache-Control/max-age + * indicator or Expires header and the calculated age is less than the freshness lifetime. + * + * @return Boolean true if the response is fresh, false otherwise + * + * @api + */ + public function isFresh() + { + return $this->getTtl() > 0; + } + + /** + * Returns true if the response includes headers that can be used to validate + * the response with the origin server using a conditional GET request. + * + * @return Boolean true if the response is validateable, false otherwise + * + * @api + */ + public function isValidateable() + { + return $this->headers->has('Last-Modified') || $this->headers->has('ETag'); + } + + /** + * Marks the response as "private". + * + * It makes the response ineligible for serving other clients. + * + * @return Response + * + * @api + */ + public function setPrivate() + { + $this->headers->removeCacheControlDirective('public'); + $this->headers->addCacheControlDirective('private'); + + return $this; + } + + /** + * Marks the response as "public". + * + * It makes the response eligible for serving other clients. + * + * @return Response + * + * @api + */ + public function setPublic() + { + $this->headers->addCacheControlDirective('public'); + $this->headers->removeCacheControlDirective('private'); + + return $this; + } + + /** + * Returns true if the response must be revalidated by caches. + * + * This method indicates that the response must not be served stale by a + * cache in any circumstance without first revalidating with the origin. + * When present, the TTL of the response should not be overridden to be + * greater than the value provided by the origin. + * + * @return Boolean true if the response must be revalidated by a cache, false otherwise + * + * @api + */ + public function mustRevalidate() + { + return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->has('proxy-revalidate'); + } + + /** + * Returns the Date header as a DateTime instance. + * + * @return \DateTime A \DateTime instance + * + * @throws \RuntimeException When the header is not parseable + * + * @api + */ + public function getDate() + { + return $this->headers->getDate('Date', new \DateTime()); + } + + /** + * Sets the Date header. + * + * @param \DateTime $date A \DateTime instance + * + * @return Response + * + * @api + */ + public function setDate(\DateTime $date) + { + $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; + } + + /** + * Returns the age of the response. + * + * @return integer The age of the response in seconds + */ + public function getAge() + { + if (null !== $age = $this->headers->get('Age')) { + return (int) $age; + } + + return max(time() - $this->getDate()->format('U'), 0); + } + + /** + * Marks the response stale by setting the Age header to be equal to the maximum age of the response. + * + * @return Response + * + * @api + */ + public function expire() + { + if ($this->isFresh()) { + $this->headers->set('Age', $this->getMaxAge()); + } + + return $this; + } + + /** + * Returns the value of the Expires header as a DateTime instance. + * + * @return \DateTime|null A DateTime instance or null if the header does not exist + * + * @api + */ + public function getExpires() + { + try { + return $this->headers->getDate('Expires'); + } catch (\RuntimeException $e) { + // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past + return \DateTime::createFromFormat(DATE_RFC2822, 'Sat, 01 Jan 00 00:00:00 +0000'); + } + } + + /** + * Sets the Expires HTTP header with a DateTime instance. + * + * Passing null as value will remove the header. + * + * @param \DateTime|null $date A \DateTime instance or null to remove the header + * + * @return Response + * + * @api + */ + public function setExpires(\DateTime $date = null) + { + if (null === $date) { + $this->headers->remove('Expires'); + } else { + $date = clone $date; + $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT'); + } + + return $this; + } + + /** + * Returns the number of seconds after the time specified in the response's Date + * header when the response should no longer be considered fresh. + * + * First, it checks for a s-maxage directive, then a max-age directive, and then it falls + * back on an expires header. It returns null when no maximum age can be established. + * + * @return integer|null Number of seconds + * + * @api + */ + public function getMaxAge() + { + if ($this->headers->hasCacheControlDirective('s-maxage')) { + return (int) $this->headers->getCacheControlDirective('s-maxage'); + } + + if ($this->headers->hasCacheControlDirective('max-age')) { + return (int) $this->headers->getCacheControlDirective('max-age'); + } + + if (null !== $this->getExpires()) { + return $this->getExpires()->format('U') - $this->getDate()->format('U'); + } + + return null; + } + + /** + * Sets the number of seconds after which the response should no longer be considered fresh. + * + * This methods sets the Cache-Control max-age directive. + * + * @param integer $value Number of seconds + * + * @return Response + * + * @api + */ + public function setMaxAge($value) + { + $this->headers->addCacheControlDirective('max-age', $value); + + return $this; + } + + /** + * Sets the number of seconds after which the response should no longer be considered fresh by shared caches. + * + * This methods sets the Cache-Control s-maxage directive. + * + * @param integer $value Number of seconds + * + * @return Response + * + * @api + */ + public function setSharedMaxAge($value) + { + $this->setPublic(); + $this->headers->addCacheControlDirective('s-maxage', $value); + + return $this; + } + + /** + * Returns the response's time-to-live in seconds. + * + * It returns null when no freshness information is present in the response. + * + * When the responses TTL is <= 0, the response may not be served from cache without first + * revalidating with the origin. + * + * @return integer|null The TTL in seconds + * + * @api + */ + public function getTtl() + { + if (null !== $maxAge = $this->getMaxAge()) { + return $maxAge - $this->getAge(); + } + + return null; + } + + /** + * Sets the response's time-to-live for shared caches. + * + * This method adjusts the Cache-Control/s-maxage directive. + * + * @param integer $seconds Number of seconds + * + * @return Response + * + * @api + */ + public function setTtl($seconds) + { + $this->setSharedMaxAge($this->getAge() + $seconds); + + return $this; + } + + /** + * Sets the response's time-to-live for private/client caches. + * + * This method adjusts the Cache-Control/max-age directive. + * + * @param integer $seconds Number of seconds + * + * @return Response + * + * @api + */ + public function setClientTtl($seconds) + { + $this->setMaxAge($this->getAge() + $seconds); + + return $this; + } + + /** + * Returns the Last-Modified HTTP header as a DateTime instance. + * + * @return \DateTime|null A DateTime instance or null if the header does not exist + * + * @throws \RuntimeException When the HTTP header is not parseable + * + * @api + */ + public function getLastModified() + { + return $this->headers->getDate('Last-Modified'); + } + + /** + * Sets the Last-Modified HTTP header with a DateTime instance. + * + * Passing null as value will remove the header. + * + * @param \DateTime|null $date A \DateTime instance or null to remove the header + * + * @return Response + * + * @api + */ + public function setLastModified(\DateTime $date = null) + { + if (null === $date) { + $this->headers->remove('Last-Modified'); + } else { + $date = clone $date; + $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT'); + } + + return $this; + } + + /** + * Returns the literal value of the ETag HTTP header. + * + * @return string|null The ETag HTTP header or null if it does not exist + * + * @api + */ + public function getEtag() + { + return $this->headers->get('ETag'); + } + + /** + * Sets the ETag value. + * + * @param string|null $etag The ETag unique identifier or null to remove the header + * @param Boolean $weak Whether you want a weak ETag or not + * + * @return Response + * + * @api + */ + public function setEtag($etag = null, $weak = false) + { + if (null === $etag) { + $this->headers->remove('Etag'); + } else { + if (0 !== strpos($etag, '"')) { + $etag = '"'.$etag.'"'; + } + + $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag); + } + + return $this; + } + + /** + * Sets the response's cache headers (validation and/or expiration). + * + * Available options are: etag, last_modified, max_age, s_maxage, private, and public. + * + * @param array $options An array of cache options + * + * @return Response + * + * @throws \InvalidArgumentException + * + * @api + */ + public function setCache(array $options) + { + if ($diff = array_diff(array_keys($options), array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public'))) { + throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', array_values($diff)))); + } + + if (isset($options['etag'])) { + $this->setEtag($options['etag']); + } + + if (isset($options['last_modified'])) { + $this->setLastModified($options['last_modified']); + } + + if (isset($options['max_age'])) { + $this->setMaxAge($options['max_age']); + } + + if (isset($options['s_maxage'])) { + $this->setSharedMaxAge($options['s_maxage']); + } + + if (isset($options['public'])) { + if ($options['public']) { + $this->setPublic(); + } else { + $this->setPrivate(); + } + } + + if (isset($options['private'])) { + if ($options['private']) { + $this->setPrivate(); + } else { + $this->setPublic(); + } + } + + return $this; + } + + /** + * Modifies the response so that it conforms to the rules defined for a 304 status code. + * + * This sets the status, removes the body, and discards any headers + * that MUST NOT be included in 304 responses. + * + * @return Response + * + * @see http://tools.ietf.org/html/rfc2616#section-10.3.5 + * + * @api + */ + public function setNotModified() + { + $this->setStatusCode(304); + $this->setContent(null); + + // remove headers that MUST NOT be included with 304 Not Modified responses + foreach (array('Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified') as $header) { + $this->headers->remove($header); + } + + return $this; + } + + /** + * Returns true if the response includes a Vary header. + * + * @return Boolean true if the response includes a Vary header, false otherwise + * + * @api + */ + public function hasVary() + { + return null !== $this->headers->get('Vary'); + } + + /** + * Returns an array of header names given in the Vary header. + * + * @return array An array of Vary names + * + * @api + */ + public function getVary() + { + if (!$vary = $this->headers->get('Vary')) { + return array(); + } + + return is_array($vary) ? $vary : preg_split('/[\s,]+/', $vary); + } + + /** + * Sets the Vary header. + * + * @param string|array $headers + * @param Boolean $replace Whether to replace the actual value of not (true by default) + * + * @return Response + * + * @api + */ + public function setVary($headers, $replace = true) + { + $this->headers->set('Vary', $headers, $replace); + + return $this; + } + + /** + * Determines if the Response validators (ETag, Last-Modified) match + * a conditional value specified in the Request. + * + * If the Response is not modified, it sets the status code to 304 and + * removes the actual content by calling the setNotModified() method. + * + * @param Request $request A Request instance + * + * @return Boolean true if the Response validators match the Request, false otherwise + * + * @api + */ + public function isNotModified(Request $request) + { + if (!$request->isMethodSafe()) { + return false; + } + + $lastModified = $request->headers->get('If-Modified-Since'); + $notModified = false; + if ($etags = $request->getEtags()) { + $notModified = (in_array($this->getEtag(), $etags) || in_array('*', $etags)) && (!$lastModified || $this->headers->get('Last-Modified') == $lastModified); + } elseif ($lastModified) { + $notModified = $lastModified == $this->headers->get('Last-Modified'); + } + + if ($notModified) { + $this->setNotModified(); + } + + return $notModified; + } + + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + /** + * Is response invalid? + * + * @return Boolean + * + * @api + */ + public function isInvalid() + { + return $this->statusCode < 100 || $this->statusCode >= 600; + } + + /** + * Is response informative? + * + * @return Boolean + * + * @api + */ + public function isInformational() + { + return $this->statusCode >= 100 && $this->statusCode < 200; + } + + /** + * Is response successful? + * + * @return Boolean + * + * @api + */ + public function isSuccessful() + { + return $this->statusCode >= 200 && $this->statusCode < 300; + } + + /** + * Is the response a redirect? + * + * @return Boolean + * + * @api + */ + public function isRedirection() + { + return $this->statusCode >= 300 && $this->statusCode < 400; + } + + /** + * Is there a client error? + * + * @return Boolean + * + * @api + */ + public function isClientError() + { + return $this->statusCode >= 400 && $this->statusCode < 500; + } + + /** + * Was there a server side error? + * + * @return Boolean + * + * @api + */ + public function isServerError() + { + return $this->statusCode >= 500 && $this->statusCode < 600; + } + + /** + * Is the response OK? + * + * @return Boolean + * + * @api + */ + public function isOk() + { + return 200 === $this->statusCode; + } + + /** + * Is the response forbidden? + * + * @return Boolean + * + * @api + */ + public function isForbidden() + { + return 403 === $this->statusCode; + } + + /** + * Is the response a not found error? + * + * @return Boolean + * + * @api + */ + public function isNotFound() + { + return 404 === $this->statusCode; + } + + /** + * Is the response a redirect of some form? + * + * @param string $location + * + * @return Boolean + * + * @api + */ + public function isRedirect($location = null) + { + return in_array($this->statusCode, array(201, 301, 302, 303, 307, 308)) && (null === $location ?: $location == $this->headers->get('Location')); + } + + /** + * Is the response empty? + * + * @return Boolean + * + * @api + */ + public function isEmpty() + { + return in_array($this->statusCode, array(201, 204, 304)); + } + + /** + * Check if we need to remove Cache-Control for ssl encrypted downloads when using IE < 9 + * + * @link http://support.microsoft.com/kb/323308 + */ + protected function ensureIEOverSSLCompatibility(Request $request) + { + if (false !== stripos($this->headers->get('Content-Disposition'), 'attachment') && preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT'), $match) == 1 && true === $request->isSecure()) { + if (intval(preg_replace("/(MSIE )(.*?);/", "$2", $match[0])) < 9) { + $this->headers->remove('Cache-Control'); + } + } + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/ResponseHeaderBag.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/ResponseHeaderBag.php new file mode 100644 index 0000000..f52f048 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/ResponseHeaderBag.php @@ -0,0 +1,319 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ResponseHeaderBag is a container for Response HTTP headers. + * + * @author Fabien Potencier + * + * @api + */ +class ResponseHeaderBag extends HeaderBag +{ + const COOKIES_FLAT = 'flat'; + const COOKIES_ARRAY = 'array'; + + const DISPOSITION_ATTACHMENT = 'attachment'; + const DISPOSITION_INLINE = 'inline'; + + /** + * @var array + */ + protected $computedCacheControl = array(); + + /** + * @var array + */ + protected $cookies = array(); + + /** + * @var array + */ + protected $headerNames = array(); + + /** + * Constructor. + * + * @param array $headers An array of HTTP headers + * + * @api + */ + public function __construct(array $headers = array()) + { + parent::__construct($headers); + + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + $cookies = ''; + foreach ($this->getCookies() as $cookie) { + $cookies .= 'Set-Cookie: '.$cookie."\r\n"; + } + + ksort($this->headerNames); + + return parent::__toString().$cookies; + } + + /** + * Returns the headers, with original capitalizations. + * + * @return array An array of headers + */ + public function allPreserveCase() + { + return array_combine($this->headerNames, $this->headers); + } + + /** + * {@inheritdoc} + * + * @api + */ + public function replace(array $headers = array()) + { + $this->headerNames = array(); + + parent::replace($headers); + + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + } + + /** + * {@inheritdoc} + * + * @api + */ + public function set($key, $values, $replace = true) + { + parent::set($key, $values, $replace); + + $uniqueKey = strtr(strtolower($key), '_', '-'); + $this->headerNames[$uniqueKey] = $key; + + // ensure the cache-control header has sensible defaults + if (in_array($uniqueKey, array('cache-control', 'etag', 'last-modified', 'expires'))) { + $computed = $this->computeCacheControlValue(); + $this->headers['cache-control'] = array($computed); + $this->headerNames['cache-control'] = 'Cache-Control'; + $this->computedCacheControl = $this->parseCacheControl($computed); + } + } + + /** + * {@inheritdoc} + * + * @api + */ + public function remove($key) + { + parent::remove($key); + + $uniqueKey = strtr(strtolower($key), '_', '-'); + unset($this->headerNames[$uniqueKey]); + + if ('cache-control' === $uniqueKey) { + $this->computedCacheControl = array(); + } + } + + /** + * {@inheritdoc} + */ + public function hasCacheControlDirective($key) + { + return array_key_exists($key, $this->computedCacheControl); + } + + /** + * {@inheritdoc} + */ + public function getCacheControlDirective($key) + { + return array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null; + } + + /** + * Sets a cookie. + * + * @param Cookie $cookie + * + * @api + */ + public function setCookie(Cookie $cookie) + { + $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; + } + + /** + * Removes a cookie from the array, but does not unset it in the browser + * + * @param string $name + * @param string $path + * @param string $domain + * + * @api + */ + public function removeCookie($name, $path = '/', $domain = null) + { + if (null === $path) { + $path = '/'; + } + + unset($this->cookies[$domain][$path][$name]); + + if (empty($this->cookies[$domain][$path])) { + unset($this->cookies[$domain][$path]); + + if (empty($this->cookies[$domain])) { + unset($this->cookies[$domain]); + } + } + } + + /** + * Returns an array with all cookies + * + * @param string $format + * + * @throws \InvalidArgumentException When the $format is invalid + * + * @return array + * + * @api + */ + public function getCookies($format = self::COOKIES_FLAT) + { + if (!in_array($format, array(self::COOKIES_FLAT, self::COOKIES_ARRAY))) { + throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', array(self::COOKIES_FLAT, self::COOKIES_ARRAY)))); + } + + if (self::COOKIES_ARRAY === $format) { + return $this->cookies; + } + + $flattenedCookies = array(); + foreach ($this->cookies as $path) { + foreach ($path as $cookies) { + foreach ($cookies as $cookie) { + $flattenedCookies[] = $cookie; + } + } + } + + return $flattenedCookies; + } + + /** + * Clears a cookie in the browser + * + * @param string $name + * @param string $path + * @param string $domain + * + * @api + */ + public function clearCookie($name, $path = '/', $domain = null) + { + $this->setCookie(new Cookie($name, null, 1, $path, $domain)); + } + + /** + * Generates a HTTP Content-Disposition field-value. + * + * @param string $disposition One of "inline" or "attachment" + * @param string $filename A unicode string + * @param string $filenameFallback A string containing only ASCII characters that + * is semantically equivalent to $filename. If the filename is already ASCII, + * it can be omitted, or just copied from $filename + * + * @return string A string suitable for use as a Content-Disposition field-value. + * + * @throws \InvalidArgumentException + * @see RFC 6266 + */ + public function makeDisposition($disposition, $filename, $filenameFallback = '') + { + if (!in_array($disposition, array(self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE))) { + throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE)); + } + + if ('' == $filenameFallback) { + $filenameFallback = $filename; + } + + // filenameFallback is not ASCII. + if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) { + throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.'); + } + + // percent characters aren't safe in fallback. + if (false !== strpos($filenameFallback, '%')) { + throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.'); + } + + // path separators aren't allowed in either. + if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) { + throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.'); + } + + $output = sprintf('%s; filename="%s"', $disposition, str_replace('"', '\\"', $filenameFallback)); + + if ($filename !== $filenameFallback) { + $output .= sprintf("; filename*=utf-8''%s", rawurlencode($filename)); + } + + return $output; + } + + /** + * Returns the calculated value of the cache-control header. + * + * This considers several other headers and calculates or modifies the + * cache-control header to a sensible, conservative value. + * + * @return string + */ + protected function computeCacheControlValue() + { + if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) { + return 'no-cache'; + } + + if (!$this->cacheControl) { + // conservative by default + return 'private, must-revalidate'; + } + + $header = $this->getCacheControlHeader(); + if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) { + return $header; + } + + // public if s-maxage is defined, private otherwise + if (!isset($this->cacheControl['s-maxage'])) { + return $header.', private'; + } + + return $header; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/ServerBag.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/ServerBag.php new file mode 100644 index 0000000..d7aadc6 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/ServerBag.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ServerBag is a container for HTTP headers from the $_SERVER variable. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + * @author Robert Kiss + */ +class ServerBag extends ParameterBag +{ + /** + * Gets the HTTP headers. + * + * @return array + */ + public function getHeaders() + { + $headers = array(); + $contentHeaders = array('CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true); + foreach ($this->parameters as $key => $value) { + if (0 === strpos($key, 'HTTP_')) { + $headers[substr($key, 5)] = $value; + } + // CONTENT_* are not prefixed with HTTP_ + elseif (isset($contentHeaders[$key])) { + $headers[$key] = $value; + } + } + + if (isset($this->parameters['PHP_AUTH_USER'])) { + $headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER']; + $headers['PHP_AUTH_PW'] = isset($this->parameters['PHP_AUTH_PW']) ? $this->parameters['PHP_AUTH_PW'] : ''; + } else { + /* + * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default + * For this workaround to work, add these lines to your .htaccess file: + * RewriteCond %{HTTP:Authorization} ^(.+)$ + * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + * + * A sample .htaccess file: + * RewriteEngine On + * RewriteCond %{HTTP:Authorization} ^(.+)$ + * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + * RewriteCond %{REQUEST_FILENAME} !-f + * RewriteRule ^(.*)$ app.php [QSA,L] + */ + + $authorizationHeader = null; + if (isset($this->parameters['HTTP_AUTHORIZATION'])) { + $authorizationHeader = $this->parameters['HTTP_AUTHORIZATION']; + } elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) { + $authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION']; + } + + // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic + if ((null !== $authorizationHeader) && (0 === stripos($authorizationHeader, 'basic'))) { + $exploded = explode(':', base64_decode(substr($authorizationHeader, 6))); + if (count($exploded) == 2) { + list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded; + } + } + } + + // PHP_AUTH_USER/PHP_AUTH_PW + if (isset($headers['PHP_AUTH_USER'])) { + $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']); + } + + return $headers; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.php new file mode 100644 index 0000000..e9d0257 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBag.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +/** + * This class relates to session attribute storage + */ +class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable +{ + private $name = 'attributes'; + + /** + * @var string + */ + private $storageKey; + + /** + * @var array + */ + protected $attributes = array(); + + /** + * Constructor. + * + * @param string $storageKey The key used to store attributes in the session. + */ + public function __construct($storageKey = '_sf2_attributes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$attributes) + { + $this->attributes = &$attributes; + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + return array_key_exists($name, $this->attributes); + } + + /** + * {@inheritdoc} + */ + public function get($name, $default = null) + { + return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; + } + + /** + * {@inheritdoc} + */ + public function set($name, $value) + { + $this->attributes[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->attributes; + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->attributes = array(); + foreach ($attributes as $key => $value) { + $this->set($key, $value); + } + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + $retval = null; + if (array_key_exists($name, $this->attributes)) { + $retval = $this->attributes[$name]; + unset($this->attributes[$name]); + } + + return $retval; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $return = $this->attributes; + $this->attributes = array(); + + return $return; + } + + /** + * Returns an iterator for attributes. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->attributes); + } + + /** + * Returns the number of attributes. + * + * @return int The number of attributes + */ + public function count() + { + return count($this->attributes); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.php new file mode 100644 index 0000000..5f1f37b --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Attribute/AttributeBagInterface.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * Attributes store. + * + * @author Drak + */ +interface AttributeBagInterface extends SessionBagInterface +{ + /** + * Checks if an attribute is defined. + * + * @param string $name The attribute name + * + * @return Boolean true if the attribute is defined, false otherwise + */ + public function has($name); + + /** + * Returns an attribute. + * + * @param string $name The attribute name + * @param mixed $default The default value if not found. + * + * @return mixed + */ + public function get($name, $default = null); + + /** + * Sets an attribute. + * + * @param string $name + * @param mixed $value + */ + public function set($name, $value); + + /** + * Returns attributes. + * + * @return array Attributes + */ + public function all(); + + /** + * Sets attributes. + * + * @param array $attributes Attributes + */ + public function replace(array $attributes); + + /** + * Removes an attribute. + * + * @param string $name + * + * @return mixed The removed value + */ + public function remove($name); +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Attribute/NamespacedAttributeBag.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Attribute/NamespacedAttributeBag.php new file mode 100644 index 0000000..c0dd358 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Attribute/NamespacedAttributeBag.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +/** + * This class provides structured storage of session attributes using + * a name spacing character in the key. + * + * @author Drak + */ +class NamespacedAttributeBag extends AttributeBag +{ + /** + * Namespace character. + * + * @var string + */ + private $namespaceCharacter; + + /** + * Constructor. + * + * @param string $storageKey Session storage key. + * @param string $namespaceCharacter Namespace character to use in keys. + */ + public function __construct($storageKey = '_sf2_attributes', $namespaceCharacter = '/') + { + $this->namespaceCharacter = $namespaceCharacter; + parent::__construct($storageKey); + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + $attributes = $this->resolveAttributePath($name); + $name = $this->resolveKey($name); + + if (null === $attributes) { + return false; + } + + return array_key_exists($name, $attributes); + } + + /** + * {@inheritdoc} + */ + public function get($name, $default = null) + { + $attributes = $this->resolveAttributePath($name); + $name = $this->resolveKey($name); + + if (null === $attributes) { + return $default; + } + + return array_key_exists($name, $attributes) ? $attributes[$name] : $default; + } + + /** + * {@inheritdoc} + */ + public function set($name, $value) + { + $attributes = & $this->resolveAttributePath($name, true); + $name = $this->resolveKey($name); + $attributes[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + $retval = null; + $attributes = & $this->resolveAttributePath($name); + $name = $this->resolveKey($name); + if (null !== $attributes && array_key_exists($name, $attributes)) { + $retval = $attributes[$name]; + unset($attributes[$name]); + } + + return $retval; + } + + /** + * Resolves a path in attributes property and returns it as a reference. + * + * This method allows structured namespacing of session attributes. + * + * @param string $name Key name + * @param boolean $writeContext Write context, default false + * + * @return array + */ + protected function &resolveAttributePath($name, $writeContext = false) + { + $array = & $this->attributes; + $name = (strpos($name, $this->namespaceCharacter) === 0) ? substr($name, 1) : $name; + + // Check if there is anything to do, else return + if (!$name) { + return $array; + } + + $parts = explode($this->namespaceCharacter, $name); + if (count($parts) < 2) { + if (!$writeContext) { + return $array; + } + + $array[$parts[0]] = array(); + + return $array; + } + + unset($parts[count($parts)-1]); + + foreach ($parts as $part) { + if (null !== $array && !array_key_exists($part, $array)) { + $array[$part] = $writeContext ? array() : null; + } + + $array = & $array[$part]; + } + + return $array; + } + + /** + * Resolves the key from the name. + * + * This is the last part in a dot separated string. + * + * @param string $name + * + * @return string + */ + protected function resolveKey($name) + { + if (strpos($name, $this->namespaceCharacter) !== false) { + $name = substr($name, strrpos($name, $this->namespaceCharacter)+1, strlen($name)); + } + + return $name; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php new file mode 100644 index 0000000..c6e41de --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +/** + * AutoExpireFlashBag flash message container. + * + * @author Drak + */ +class AutoExpireFlashBag implements FlashBagInterface +{ + private $name = 'flashes'; + + /** + * Flash messages. + * + * @var array + */ + private $flashes = array(); + + /** + * The storage key for flashes in the session + * + * @var string + */ + private $storageKey; + + /** + * Constructor. + * + * @param string $storageKey The key used to store flashes in the session. + */ + public function __construct($storageKey = '_sf2_flashes') + { + $this->storageKey = $storageKey; + $this->flashes = array('display' => array(), 'new' => array()); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + $this->flashes = &$flashes; + + // The logic: messages from the last request will be stored in new, so we move them to previous + // This request we will show what is in 'display'. What is placed into 'new' this time round will + // be moved to display next time round. + $this->flashes['display'] = array_key_exists('new', $this->flashes) ? $this->flashes['new'] : array(); + $this->flashes['new'] = array(); + } + + /** + * {@inheritdoc} + */ + public function add($type, $message) + { + $this->flashes['new'][$type][] = $message; + } + + /** + * {@inheritdoc} + */ + public function peek($type, array $default = array()) + { + return $this->has($type) ? $this->flashes['display'][$type] : $default; + } + + /** + * {@inheritdoc} + */ + public function peekAll() + { + return array_key_exists('display', $this->flashes) ? (array) $this->flashes['display'] : array(); + } + + /** + * {@inheritdoc} + */ + public function get($type, array $default = array()) + { + $return = $default; + + if (!$this->has($type)) { + return $return; + } + + if (isset($this->flashes['display'][$type])) { + $return = $this->flashes['display'][$type]; + unset($this->flashes['display'][$type]); + } + + return $return; + } + + /** + * {@inheritdoc} + */ + public function all() + { + $return = $this->flashes['display']; + $this->flashes = array('new' => array(), 'display' => array()); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function setAll(array $messages) + { + $this->flashes['new'] = $messages; + } + + /** + * {@inheritdoc} + */ + public function set($type, $messages) + { + $this->flashes['new'][$type] = (array) $messages; + } + + /** + * {@inheritdoc} + */ + public function has($type) + { + return array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type]; + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->flashes['display']); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->all(); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Flash/FlashBag.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Flash/FlashBag.php new file mode 100644 index 0000000..d62e383 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Flash/FlashBag.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +/** + * FlashBag flash message container. + * + * @author Drak + */ +class FlashBag implements FlashBagInterface, \IteratorAggregate +{ + private $name = 'flashes'; + + /** + * Flash messages. + * + * @var array + */ + private $flashes = array(); + + /** + * The storage key for flashes in the session + * + * @var string + */ + private $storageKey; + + /** + * Constructor. + * + * @param string $storageKey The key used to store flashes in the session. + */ + public function __construct($storageKey = '_sf2_flashes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + $this->flashes = &$flashes; + } + + /** + * {@inheritdoc} + */ + public function add($type, $message) + { + $this->flashes[$type][] = $message; + } + + /** + * {@inheritdoc} + */ + public function peek($type, array $default =array()) + { + return $this->has($type) ? $this->flashes[$type] : $default; + } + + /** + * {@inheritdoc} + */ + public function peekAll() + { + return $this->flashes; + } + + /** + * {@inheritdoc} + */ + public function get($type, array $default = array()) + { + if (!$this->has($type)) { + return $default; + } + + $return = $this->flashes[$type]; + + unset($this->flashes[$type]); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function all() + { + $return = $this->peekAll(); + $this->flashes = array(); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function set($type, $messages) + { + $this->flashes[$type] = (array) $messages; + } + + /** + * {@inheritdoc} + */ + public function setAll(array $messages) + { + $this->flashes = $messages; + } + + /** + * {@inheritdoc} + */ + public function has($type) + { + return array_key_exists($type, $this->flashes) && $this->flashes[$type]; + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->flashes); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->all(); + } + + /** + * Returns an iterator for flashes. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->all()); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.php new file mode 100644 index 0000000..a68dcfd --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Flash/FlashBagInterface.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * FlashBagInterface. + * + * @author Drak + */ +interface FlashBagInterface extends SessionBagInterface +{ + /** + * Adds a flash message for type. + * + * @param string $type + * @param string $message + */ + public function add($type, $message); + + /** + * Registers a message for a given type. + * + * @param string $type + * @param string|array $message + */ + public function set($type, $message); + + /** + * Gets flash messages for a given type. + * + * @param string $type Message category type. + * @param array $default Default value if $type does not exist. + * + * @return array + */ + public function peek($type, array $default = array()); + + /** + * Gets all flash messages. + * + * @return array + */ + public function peekAll(); + + /** + * Gets and clears flash from the stack. + * + * @param string $type + * @param array $default Default value if $type does not exist. + * + * @return array + */ + public function get($type, array $default = array()); + + /** + * Gets and clears flashes from the stack. + * + * @return array + */ + public function all(); + + /** + * Sets all flash messages. + */ + public function setAll(array $messages); + + /** + * Has flash messages for a given type? + * + * @param string $type + * + * @return boolean + */ + public function has($type); + + /** + * Returns a list of all defined types. + * + * @return array + */ + public function keys(); +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Session.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Session.php new file mode 100644 index 0000000..15df097 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Session.php @@ -0,0 +1,252 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; + +/** + * Session. + * + * @author Fabien Potencier + * @author Drak + * + * @api + */ +class Session implements SessionInterface, \IteratorAggregate, \Countable +{ + /** + * Storage driver. + * + * @var SessionStorageInterface + */ + protected $storage; + + /** + * @var string + */ + private $flashName; + + /** + * @var string + */ + private $attributeName; + + /** + * Constructor. + * + * @param SessionStorageInterface $storage A SessionStorageInterface instance. + * @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag) + * @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for default FlashBag) + */ + public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) + { + $this->storage = $storage ?: new NativeSessionStorage(); + + $attributes = $attributes ?: new AttributeBag(); + $this->attributeName = $attributes->getName(); + $this->registerBag($attributes); + + $flashes = $flashes ?: new FlashBag(); + $this->flashName = $flashes->getName(); + $this->registerBag($flashes); + } + + /** + * {@inheritdoc} + */ + public function start() + { + return $this->storage->start(); + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + return $this->storage->getBag($this->attributeName)->has($name); + } + + /** + * {@inheritdoc} + */ + public function get($name, $default = null) + { + return $this->storage->getBag($this->attributeName)->get($name, $default); + } + + /** + * {@inheritdoc} + */ + public function set($name, $value) + { + $this->storage->getBag($this->attributeName)->set($name, $value); + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->storage->getBag($this->attributeName)->all(); + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->storage->getBag($this->attributeName)->replace($attributes); + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + return $this->storage->getBag($this->attributeName)->remove($name); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->storage->getBag($this->attributeName)->clear(); + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->storage->isStarted(); + } + + /** + * Returns an iterator for attributes. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->storage->getBag($this->attributeName)->all()); + } + + /** + * Returns the number of attributes. + * + * @return int The number of attributes + */ + public function count() + { + return count($this->storage->getBag($this->attributeName)->all()); + } + + /** + * {@inheritdoc} + */ + public function invalidate($lifetime = null) + { + $this->storage->clear(); + + return $this->migrate(true, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function migrate($destroy = false, $lifetime = null) + { + return $this->storage->regenerate($destroy, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function save() + { + $this->storage->save(); + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->storage->getId(); + } + + /** + * {@inheritdoc} + */ + public function setId($id) + { + $this->storage->setId($id); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->storage->getName(); + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->storage->setName($name); + } + + /** + * {@inheritdoc} + */ + public function getMetadataBag() + { + return $this->storage->getMetadataBag(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + $this->storage->registerBag($bag); + } + + /** + * {@inheritdoc} + */ + public function getBag($name) + { + return $this->storage->getBag($name); + } + + /** + * Gets the flashbag interface. + * + * @return FlashBagInterface + */ + public function getFlashBag() + { + return $this->getBag($this->flashName); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/SessionBagInterface.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/SessionBagInterface.php new file mode 100644 index 0000000..f8d3d32 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/SessionBagInterface.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * Session Bag store. + * + * @author Drak + */ +interface SessionBagInterface +{ + /** + * Gets this bag's name + * + * @return string + */ + public function getName(); + + /** + * Initializes the Bag + * + * @param array $array + */ + public function initialize(array &$array); + + /** + * Gets the storage key for this bag. + * + * @return string + */ + public function getStorageKey(); + + /** + * Clears out data from bag. + * + * @return mixed Whatever data was contained. + */ + public function clear(); +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/SessionInterface.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/SessionInterface.php new file mode 100644 index 0000000..a94fad0 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/SessionInterface.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; + +/** + * Interface for the session. + * + * @author Drak + */ +interface SessionInterface +{ + /** + * Starts the session storage. + * + * @return Boolean True if session started. + * + * @throws \RuntimeException If session fails to start. + * + * @api + */ + public function start(); + + /** + * Returns the session ID. + * + * @return string The session ID. + * + * @api + */ + public function getId(); + + /** + * Sets the session ID + * + * @param string $id + * + * @api + */ + public function setId($id); + + /** + * Returns the session name. + * + * @return mixed The session name. + * + * @api + */ + public function getName(); + + /** + * Sets the session name. + * + * @param string $name + * + * @api + */ + public function setName($name); + + /** + * Invalidates the current session. + * + * Clears all session attributes and flashes and regenerates the + * session and deletes the old session from persistence. + * + * @param integer $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return Boolean True if session invalidated, false if error. + * + * @api + */ + public function invalidate($lifetime = null); + + /** + * Migrates the current session to a new session id while maintaining all + * session attributes. + * + * @param Boolean $destroy Whether to delete the old session or leave it to garbage collection. + * @param integer $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return Boolean True if session migrated, false if error. + * + * @api + */ + public function migrate($destroy = false, $lifetime = null); + + /** + * Force the session to be saved and closed. + * + * This method is generally not required for real sessions as + * the session will be automatically saved at the end of + * code execution. + */ + public function save(); + + /** + * Checks if an attribute is defined. + * + * @param string $name The attribute name + * + * @return Boolean true if the attribute is defined, false otherwise + * + * @api + */ + public function has($name); + + /** + * Returns an attribute. + * + * @param string $name The attribute name + * @param mixed $default The default value if not found. + * + * @return mixed + * + * @api + */ + public function get($name, $default = null); + + /** + * Sets an attribute. + * + * @param string $name + * @param mixed $value + * + * @api + */ + public function set($name, $value); + + /** + * Returns attributes. + * + * @return array Attributes + * + * @api + */ + public function all(); + + /** + * Sets attributes. + * + * @param array $attributes Attributes + */ + public function replace(array $attributes); + + /** + * Removes an attribute. + * + * @param string $name + * + * @return mixed The removed value + * + * @api + */ + public function remove($name); + + /** + * Clears all attributes. + * + * @api + */ + public function clear(); + + /** + * Checks if the session was started. + * + * @return Boolean + */ + public function isStarted(); + + /** + * Registers a SessionBagInterface with the session. + * + * @param SessionBagInterface $bag + */ + public function registerBag(SessionBagInterface $bag); + + /** + * Gets a bag instance by name. + * + * @param string $name + * + * @return SessionBagInterface + */ + public function getBag($name); + + /** + * Gets session meta. + * + * @return MetadataBag + */ + public function getMetadataBag(); +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcacheSessionHandler.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcacheSessionHandler.php new file mode 100644 index 0000000..4a5e639 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcacheSessionHandler.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * MemcacheSessionHandler. + * + * @author Drak + */ +class MemcacheSessionHandler implements \SessionHandlerInterface +{ + /** + * @var \Memcache Memcache driver. + */ + private $memcache; + + /** + * @var integer Time to live in seconds + */ + private $ttl; + + /** + * @var string Key prefix for shared environments. + */ + private $prefix; + + /** + * Constructor. + * + * List of available options: + * * prefix: The prefix to use for the memcache keys in order to avoid collision + * * expiretime: The time to live in seconds + * + * @param \Memcache $memcache A \Memcache instance + * @param array $options An associative array of Memcache options + * + * @throws \InvalidArgumentException When unsupported options are passed + */ + public function __construct(\Memcache $memcache, array $options = array()) + { + if ($diff = array_diff(array_keys($options), array('prefix', 'expiretime'))) { + throw new \InvalidArgumentException(sprintf( + 'The following options are not supported "%s"', implode(', ', $diff) + )); + } + + $this->memcache = $memcache; + $this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400; + $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s'; + } + + /** + * {@inheritDoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritDoc} + */ + public function close() + { + return $this->memcache->close(); + } + + /** + * {@inheritDoc} + */ + public function read($sessionId) + { + return $this->memcache->get($this->prefix.$sessionId) ?: ''; + } + + /** + * {@inheritDoc} + */ + public function write($sessionId, $data) + { + return $this->memcache->set($this->prefix.$sessionId, $data, 0, time() + $this->ttl); + } + + /** + * {@inheritDoc} + */ + public function destroy($sessionId) + { + return $this->memcache->delete($this->prefix.$sessionId); + } + + /** + * {@inheritDoc} + */ + public function gc($lifetime) + { + // not required here because memcache will auto expire the records anyhow. + return true; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php new file mode 100644 index 0000000..b3ca0bd --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * MemcachedSessionHandler. + * + * Memcached based session storage handler based on the Memcached class + * provided by the PHP memcached extension. + * + * @see http://php.net/memcached + * + * @author Drak + */ +class MemcachedSessionHandler implements \SessionHandlerInterface +{ + /** + * @var \Memcached Memcached driver. + */ + private $memcached; + + /** + * @var integer Time to live in seconds + */ + private $ttl; + + /** + * @var string Key prefix for shared environments. + */ + private $prefix; + + /** + * Constructor. + * + * List of available options: + * * prefix: The prefix to use for the memcached keys in order to avoid collision + * * expiretime: The time to live in seconds + * + * @param \Memcached $memcached A \Memcached instance + * @param array $options An associative array of Memcached options + * + * @throws \InvalidArgumentException When unsupported options are passed + */ + public function __construct(\Memcached $memcached, array $options = array()) + { + $this->memcached = $memcached; + + if ($diff = array_diff(array_keys($options), array('prefix', 'expiretime'))) { + throw new \InvalidArgumentException(sprintf( + 'The following options are not supported "%s"', implode(', ', $diff) + )); + } + + $this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400; + $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s'; + } + + /** + * {@inheritDoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritDoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function read($sessionId) + { + return $this->memcached->get($this->prefix.$sessionId) ?: ''; + } + + /** + * {@inheritDoc} + */ + public function write($sessionId, $data) + { + return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl); + } + + /** + * {@inheritDoc} + */ + public function destroy($sessionId) + { + return $this->memcached->delete($this->prefix.$sessionId); + } + + /** + * {@inheritDoc} + */ + public function gc($lifetime) + { + // not required here because memcached will auto expire the records anyhow. + return true; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php new file mode 100644 index 0000000..69ebae9 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * MongoDB session handler + * + * @author Markus Bachmann + */ +class MongoDbSessionHandler implements \SessionHandlerInterface +{ + /** + * @var \Mongo + */ + private $mongo; + + /** + * @var \MongoCollection + */ + private $collection; + + /** + * @var array + */ + private $options; + + /** + * Constructor. + * + * List of available options: + * * database: The name of the database [required] + * * collection: The name of the collection [required] + * * id_field: The field name for storing the session id [default: _id] + * * data_field: The field name for storing the session data [default: data] + * * time_field: The field name for storing the timestamp [default: time] + * + * @param \Mongo|\MongoClient $mongo A MongoClient or Mongo instance + * @param array $options An associative array of field options + * + * @throws \InvalidArgumentException When MongoClient or Mongo instance not provided + * @throws \InvalidArgumentException When "database" or "collection" not provided + */ + public function __construct($mongo, array $options) + { + if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo)) { + throw new \InvalidArgumentException('MongoClient or Mongo instance required'); + } + + if (!isset($options['database']) || !isset($options['collection'])) { + throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler'); + } + + $this->mongo = $mongo; + + $this->options = array_merge(array( + 'id_field' => '_id', + 'data_field' => 'data', + 'time_field' => 'time', + ), $options); + } + + /** + * {@inheritDoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritDoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function destroy($sessionId) + { + $this->getCollection()->remove(array( + $this->options['id_field'] => $sessionId + )); + + return true; + } + + /** + * {@inheritDoc} + */ + public function gc($lifetime) + { + /* Note: MongoDB 2.2+ supports TTL collections, which may be used in + * place of this method by indexing the "time_field" field with an + * "expireAfterSeconds" option. Regardless of whether TTL collections + * are used, consider indexing this field to make the remove query more + * efficient. + * + * See: http://docs.mongodb.org/manual/tutorial/expire-data/ + */ + $time = new \MongoDate(time() - $lifetime); + + $this->getCollection()->remove(array( + $this->options['time_field'] => array('$lt' => $time), + )); + + return true; + } + + /** + * {@inheritDoc] + */ + public function write($sessionId, $data) + { + $this->getCollection()->update( + array($this->options['id_field'] => $sessionId), + array('$set' => array( + $this->options['data_field'] => new \MongoBinData($data, \MongoBinData::BYTE_ARRAY), + $this->options['time_field'] => new \MongoDate(), + )), + array('upsert' => true, 'multiple' => false) + ); + + return true; + } + + /** + * {@inheritDoc} + */ + public function read($sessionId) + { + $dbData = $this->getCollection()->findOne(array( + $this->options['id_field'] => $sessionId, + )); + + return null === $dbData ? '' : $dbData[$this->options['data_field']]->bin; + } + + /** + * Return a "MongoCollection" instance + * + * @return \MongoCollection + */ + private function getCollection() + { + if (null === $this->collection) { + $this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']); + } + + return $this->collection; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeFileSessionHandler.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeFileSessionHandler.php new file mode 100644 index 0000000..f39235c --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeFileSessionHandler.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * NativeFileSessionHandler. + * + * Native session handler using PHP's built in file storage. + * + * @author Drak + */ +class NativeFileSessionHandler extends NativeSessionHandler +{ + /** + * Constructor. + * + * @param string $savePath Path of directory to save session files. + * Default null will leave setting as defined by PHP. + * '/path', 'N;/path', or 'N;octal-mode;/path + * + * @see http://php.net/session.configuration.php#ini.session.save-path for further details. + * + * @throws \InvalidArgumentException On invalid $savePath + */ + public function __construct($savePath = null) + { + if (null === $savePath) { + $savePath = ini_get('session.save_path'); + } + + $baseDir = $savePath; + + if ($count = substr_count($savePath, ';')) { + if ($count > 2) { + throw new \InvalidArgumentException(sprintf('Invalid argument $savePath \'%s\'', $savePath)); + } + + // characters after last ';' are the path + $baseDir = ltrim(strrchr($savePath, ';'), ';'); + } + + if ($baseDir && !is_dir($baseDir)) { + mkdir($baseDir, 0777, true); + } + + ini_set('session.save_path', $savePath); + ini_set('session.save_handler', 'files'); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeSessionHandler.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeSessionHandler.php new file mode 100644 index 0000000..1260ad0 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeSessionHandler.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Adds SessionHandler functionality if available. + * + * @see http://php.net/sessionhandler + */ + +if (version_compare(phpversion(), '5.4.0', '>=')) { + class NativeSessionHandler extends \SessionHandler {} +} else { + class NativeSessionHandler {} +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php new file mode 100644 index 0000000..62068af --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * NullSessionHandler. + * + * Can be used in unit testing or in a situations where persisted sessions are not desired. + * + * @author Drak + * + * @api + */ +class NullSessionHandler implements \SessionHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function gc($lifetime) + { + return true; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php new file mode 100644 index 0000000..347cbee --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php @@ -0,0 +1,243 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * PdoSessionHandler. + * + * @author Fabien Potencier + * @author Michael Williams + */ +class PdoSessionHandler implements \SessionHandlerInterface +{ + /** + * @var \PDO PDO instance. + */ + private $pdo; + + /** + * @var array Database options. + */ + private $dbOptions; + + /** + * Constructor. + * + * List of available options: + * * db_table: The name of the table [required] + * * db_id_col: The column where to store the session id [default: sess_id] + * * db_data_col: The column where to store the session data [default: sess_data] + * * db_time_col: The column where to store the timestamp [default: sess_time] + * + * @param \PDO $pdo A \PDO instance + * @param array $dbOptions An associative array of DB options + * + * @throws \InvalidArgumentException When "db_table" option is not provided + */ + public function __construct(\PDO $pdo, array $dbOptions = array()) + { + if (!array_key_exists('db_table', $dbOptions)) { + throw new \InvalidArgumentException('You must provide the "db_table" option for a PdoSessionStorage.'); + } + if (\PDO::ERRMODE_EXCEPTION !== $pdo->getAttribute(\PDO::ATTR_ERRMODE)) { + throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__)); + } + $this->pdo = $pdo; + $this->dbOptions = array_merge(array( + 'db_id_col' => 'sess_id', + 'db_data_col' => 'sess_data', + 'db_time_col' => 'sess_time', + ), $dbOptions); + } + + /** + * {@inheritDoc} + */ + public function open($path, $name) + { + return true; + } + + /** + * {@inheritDoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritDoc} + */ + public function destroy($id) + { + // get table/column + $dbTable = $this->dbOptions['db_table']; + $dbIdCol = $this->dbOptions['db_id_col']; + + // delete the record associated with this id + $sql = "DELETE FROM $dbTable WHERE $dbIdCol = :id"; + + try { + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $id, \PDO::PARAM_STR); + $stmt->execute(); + } catch (\PDOException $e) { + throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e); + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function gc($lifetime) + { + // get table/column + $dbTable = $this->dbOptions['db_table']; + $dbTimeCol = $this->dbOptions['db_time_col']; + + // delete the session records that have expired + $sql = "DELETE FROM $dbTable WHERE $dbTimeCol < :time"; + + try { + $stmt = $this->pdo->prepare($sql); + $stmt->bindValue(':time', time() - $lifetime, \PDO::PARAM_INT); + $stmt->execute(); + } catch (\PDOException $e) { + throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e); + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function read($id) + { + // get table/columns + $dbTable = $this->dbOptions['db_table']; + $dbDataCol = $this->dbOptions['db_data_col']; + $dbIdCol = $this->dbOptions['db_id_col']; + + try { + $sql = "SELECT $dbDataCol FROM $dbTable WHERE $dbIdCol = :id"; + + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $id, \PDO::PARAM_STR); + + $stmt->execute(); + // it is recommended to use fetchAll so that PDO can close the DB cursor + // we anyway expect either no rows, or one row with one column. fetchColumn, seems to be buggy #4777 + $sessionRows = $stmt->fetchAll(\PDO::FETCH_NUM); + + if (count($sessionRows) == 1) { + return base64_decode($sessionRows[0][0]); + } + + // session does not exist, create it + $this->createNewSession($id); + + return ''; + } catch (\PDOException $e) { + throw new \RuntimeException(sprintf('PDOException was thrown when trying to read the session data: %s', $e->getMessage()), 0, $e); + } + } + + /** + * {@inheritDoc} + */ + public function write($id, $data) + { + // get table/column + $dbTable = $this->dbOptions['db_table']; + $dbDataCol = $this->dbOptions['db_data_col']; + $dbIdCol = $this->dbOptions['db_id_col']; + $dbTimeCol = $this->dbOptions['db_time_col']; + + //session data can contain non binary safe characters so we need to encode it + $encoded = base64_encode($data); + + try { + $driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + + if ('mysql' === $driver) { + // MySQL would report $stmt->rowCount() = 0 on UPDATE when the data is left unchanged + // it could result in calling createNewSession() whereas the session already exists in + // the DB which would fail as the id is unique + $stmt = $this->pdo->prepare( + "INSERT INTO $dbTable ($dbIdCol, $dbDataCol, $dbTimeCol) VALUES (:id, :data, :time) " . + "ON DUPLICATE KEY UPDATE $dbDataCol = VALUES($dbDataCol), $dbTimeCol = VALUES($dbTimeCol)" + ); + $stmt->bindParam(':id', $id, \PDO::PARAM_STR); + $stmt->bindParam(':data', $encoded, \PDO::PARAM_STR); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->execute(); + } elseif ('oci' === $driver) { + $stmt = $this->pdo->prepare("MERGE INTO $dbTable USING DUAL ON($dbIdCol = :id) ". + "WHEN NOT MATCHED THEN INSERT ($dbIdCol, $dbDataCol, $dbTimeCol) VALUES (:id, :data, sysdate) " . + "WHEN MATCHED THEN UPDATE SET $dbDataCol = :data WHERE $dbIdCol = :id"); + + $stmt->bindParam(':id', $id, \PDO::PARAM_STR); + $stmt->bindParam(':data', $encoded, \PDO::PARAM_STR); + $stmt->execute(); + } else { + $stmt = $this->pdo->prepare("UPDATE $dbTable SET $dbDataCol = :data, $dbTimeCol = :time WHERE $dbIdCol = :id"); + $stmt->bindParam(':id', $id, \PDO::PARAM_STR); + $stmt->bindParam(':data', $encoded, \PDO::PARAM_STR); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->execute(); + + if (!$stmt->rowCount()) { + // No session exists in the database to update. This happens when we have called + // session_regenerate_id() + $this->createNewSession($id, $data); + } + } + } catch (\PDOException $e) { + throw new \RuntimeException(sprintf('PDOException was thrown when trying to write the session data: %s', $e->getMessage()), 0, $e); + } + + return true; + } + + /** + * Creates a new session with the given $id and $data + * + * @param string $id + * @param string $data + * + * @return boolean True. + */ + private function createNewSession($id, $data = '') + { + // get table/column + $dbTable = $this->dbOptions['db_table']; + $dbDataCol = $this->dbOptions['db_data_col']; + $dbIdCol = $this->dbOptions['db_id_col']; + $dbTimeCol = $this->dbOptions['db_time_col']; + + $sql = "INSERT INTO $dbTable ($dbIdCol, $dbDataCol, $dbTimeCol) VALUES (:id, :data, :time)"; + + //session data can contain non binary safe characters so we need to encode it + $encoded = base64_encode($data); + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $id, \PDO::PARAM_STR); + $stmt->bindParam(':data', $encoded, \PDO::PARAM_STR); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->execute(); + + return true; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php new file mode 100644 index 0000000..892d004 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/MetadataBag.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * Metadata container. + * + * Adds metadata to the session. + * + * @author Drak + */ +class MetadataBag implements SessionBagInterface +{ + const CREATED = 'c'; + const UPDATED = 'u'; + const LIFETIME = 'l'; + + /** + * @var string + */ + private $name = '__metadata'; + + /** + * @var string + */ + private $storageKey; + + /** + * @var array + */ + protected $meta = array(); + + /** + * Unix timestamp. + * + * @var integer + */ + private $lastUsed; + + /** + * Constructor. + * + * @param string $storageKey The key used to store bag in the session. + */ + public function __construct($storageKey = '_sf2_meta') + { + $this->storageKey = $storageKey; + $this->meta = array(self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0); + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$array) + { + $this->meta = &$array; + + if (isset($array[self::CREATED])) { + $this->lastUsed = $this->meta[self::UPDATED]; + $this->meta[self::UPDATED] = time(); + } else { + $this->stampCreated(); + } + } + + /** + * Gets the lifetime that the session cookie was set with. + * + * @return integer + */ + public function getLifetime() + { + return $this->meta[self::LIFETIME]; + } + + /** + * Stamps a new session's metadata. + * + * @param integer $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + */ + public function stampNew($lifetime = null) + { + $this->stampCreated($lifetime); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * Gets the created timestamp metadata. + * + * @return integer Unix timestamp + */ + public function getCreated() + { + return $this->meta[self::CREATED]; + } + + /** + * Gets the last used metadata. + * + * @return integer Unix timestamp + */ + public function getLastUsed() + { + return $this->lastUsed; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // nothing to do + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * Sets name. + * + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + private function stampCreated($lifetime = null) + { + $timeStamp = time(); + $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp; + $this->meta[self::LIFETIME] = (null === $lifetime) ? ini_get('session.cookie_lifetime') : $lifetime; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php new file mode 100644 index 0000000..a1fcf53 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/MockArraySessionStorage.php @@ -0,0 +1,268 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; + +/** + * MockArraySessionStorage mocks the session for unit tests. + * + * No PHP session is actually started since a session can be initialized + * and shutdown only once per PHP execution cycle. + * + * When doing functional testing, you should use MockFileSessionStorage instead. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + * @author Drak + */ +class MockArraySessionStorage implements SessionStorageInterface +{ + /** + * @var string + */ + protected $id = ''; + + /** + * @var string + */ + protected $name; + + /** + * @var boolean + */ + protected $started = false; + + /** + * @var boolean + */ + protected $closed = false; + + /** + * @var array + */ + protected $data = array(); + + /** + * @var MetadataBag + */ + protected $metadataBag; + + /** + * @var array + */ + protected $bags; + + /** + * Constructor. + * + * @param string $name Session name + * @param MetadataBag $metaBag MetadataBag instance. + */ + public function __construct($name = 'MOCKSESSID', MetadataBag $metaBag = null) + { + $this->name = $name; + $this->setMetadataBag($metaBag); + } + + /** + * Sets the session data. + * + * @param array $array + */ + public function setSessionData(array $array) + { + $this->data = $array; + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started && !$this->closed) { + return true; + } + + if (empty($this->id)) { + $this->id = $this->generateId(); + } + + $this->loadSession(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function regenerate($destroy = false, $lifetime = null) + { + if (!$this->started) { + $this->start(); + } + + $this->metadataBag->stampNew($lifetime); + $this->id = $this->generateId(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->id; + } + + /** + * {@inheritdoc} + */ + public function setId($id) + { + if ($this->started) { + throw new \LogicException('Cannot set session ID after the session has started.'); + } + + $this->id = $id; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function save() + { + if (!$this->started || $this->closed) { + throw new \RuntimeException("Trying to save a session that was not started yet or was already closed"); + } + // nothing to do since we don't persist the session data + $this->closed = false; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // clear out the session + $this->data = array(); + + // reconnect the bags to the session + $this->loadSession(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + $this->bags[$bag->getName()] = $bag; + } + + /** + * {@inheritdoc} + */ + public function getBag($name) + { + if (!isset($this->bags[$name])) { + throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name)); + } + + if (!$this->started) { + $this->start(); + } + + return $this->bags[$name]; + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->started; + } + + /** + * Sets the MetadataBag. + * + * @param MetadataBag $bag + */ + public function setMetadataBag(MetadataBag $bag = null) + { + if (null === $bag) { + $bag = new MetadataBag(); + } + + $this->metadataBag = $bag; + } + + /** + * Gets the MetadataBag. + * + * @return MetadataBag + */ + public function getMetadataBag() + { + return $this->metadataBag; + } + + /** + * Generates a session ID. + * + * This doesn't need to be particularly cryptographically secure since this is just + * a mock. + * + * @return string + */ + protected function generateId() + { + return sha1(uniqid(mt_rand())); + } + + protected function loadSession() + { + $bags = array_merge($this->bags, array($this->metadataBag)); + + foreach ($bags as $bag) { + $key = $bag->getStorageKey(); + $this->data[$key] = isset($this->data[$key]) ? $this->data[$key] : array(); + $bag->initialize($this->data[$key]); + } + + $this->started = true; + $this->closed = false; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php new file mode 100644 index 0000000..f1a699b --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +/** + * MockFileSessionStorage is used to mock sessions for + * functional testing when done in a single PHP process. + * + * No PHP session is actually started since a session can be initialized + * and shutdown only once per PHP execution cycle and this class does + * not pollute any session related globals, including session_*() functions + * or session.* PHP ini directives. + * + * @author Drak + */ +class MockFileSessionStorage extends MockArraySessionStorage +{ + /** + * @var string + */ + private $savePath; + + /** + * Constructor. + * + * @param string $savePath Path of directory to save session files. + * @param string $name Session name. + * @param MetadataBag $metaBag MetadataBag instance. + */ + public function __construct($savePath = null, $name = 'MOCKSESSID', MetadataBag $metaBag = null) + { + if (null === $savePath) { + $savePath = sys_get_temp_dir(); + } + + if (!is_dir($savePath)) { + mkdir($savePath, 0777, true); + } + + $this->savePath = $savePath; + + parent::__construct($name, $metaBag); + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (!$this->id) { + $this->id = $this->generateId(); + } + + $this->read(); + + $this->started = true; + + return true; + } + + /** + * {@inheritdoc} + */ + public function regenerate($destroy = false, $lifetime = null) + { + if (!$this->started) { + $this->start(); + } + + if ($destroy) { + $this->destroy(); + } + + return parent::regenerate($destroy, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function save() + { + if (!$this->started) { + throw new \RuntimeException("Trying to save a session that was not started yet or was already closed"); + } + + file_put_contents($this->getFilePath(), serialize($this->data)); + + // this is needed for Silex, where the session object is re-used across requests + // in functional tests. In Symfony, the container is rebooted, so we don't have + // this issue + $this->started = false; + } + + /** + * Deletes a session from persistent storage. + * Deliberately leaves session data in memory intact. + */ + private function destroy() + { + if (is_file($this->getFilePath())) { + unlink($this->getFilePath()); + } + } + + /** + * Calculate path to file. + * + * @return string File path + */ + private function getFilePath() + { + return $this->savePath.'/'.$this->id.'.mocksess'; + } + + /** + * Reads session from storage and loads session. + */ + private function read() + { + $filePath = $this->getFilePath(); + $this->data = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : array(); + + $this->loadSession(); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php new file mode 100644 index 0000000..d86ff33 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php @@ -0,0 +1,431 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; + +/** + * This provides a base class for session attribute storage. + * + * @author Drak + */ +class NativeSessionStorage implements SessionStorageInterface +{ + /** + * Array of SessionBagInterface + * + * @var SessionBagInterface[] + */ + protected $bags; + + /** + * @var Boolean + */ + protected $started = false; + + /** + * @var Boolean + */ + protected $closed = false; + + /** + * @var AbstractProxy + */ + protected $saveHandler; + + /** + * @var MetadataBag + */ + protected $metadataBag; + + /** + * Constructor. + * + * Depending on how you want the storage driver to behave you probably + * want to override this constructor entirely. + * + * List of options for $options array with their defaults. + * @see http://php.net/session.configuration for options + * but we omit 'session.' from the beginning of the keys for convenience. + * + * ("auto_start", is not supported as it tells PHP to start a session before + * PHP starts to execute user-land code. Setting during runtime has no effect). + * + * cache_limiter, "nocache" (use "0" to prevent headers from being sent entirely). + * cookie_domain, "" + * cookie_httponly, "" + * cookie_lifetime, "0" + * cookie_path, "/" + * cookie_secure, "" + * entropy_file, "" + * entropy_length, "0" + * gc_divisor, "100" + * gc_maxlifetime, "1440" + * gc_probability, "1" + * hash_bits_per_character, "4" + * hash_function, "0" + * name, "PHPSESSID" + * referer_check, "" + * serialize_handler, "php" + * use_cookies, "1" + * use_only_cookies, "1" + * use_trans_sid, "0" + * upload_progress.enabled, "1" + * upload_progress.cleanup, "1" + * upload_progress.prefix, "upload_progress_" + * upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS" + * upload_progress.freq, "1%" + * upload_progress.min-freq, "1" + * url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset=" + * + * @param array $options Session configuration options. + * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $handler + * @param MetadataBag $metaBag MetadataBag. + */ + public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null) + { + session_cache_limiter(''); // disable by default because it's managed by HeaderBag (if used) + ini_set('session.use_cookies', 1); + + if (version_compare(phpversion(), '5.4.0', '>=')) { + session_register_shutdown(); + } else { + register_shutdown_function('session_write_close'); + } + + $this->setMetadataBag($metaBag); + $this->setOptions($options); + $this->setSaveHandler($handler); + } + + /** + * Gets the save handler instance. + * + * @return AbstractProxy + */ + public function getSaveHandler() + { + return $this->saveHandler; + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started && !$this->closed) { + return true; + } + + if (version_compare(phpversion(), '5.4.0', '>=') && \PHP_SESSION_ACTIVE === session_status()) { + throw new \RuntimeException('Failed to start the session: already started by PHP.'); + } + + if (version_compare(phpversion(), '5.4.0', '<') && isset($_SESSION) && session_id()) { + // not 100% fool-proof, but is the most reliable way to determine if a session is active in PHP 5.3 + throw new \RuntimeException('Failed to start the session: already started by PHP ($_SESSION is set).'); + } + + if (ini_get('session.use_cookies') && headers_sent($file, $line)) { + throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line)); + } + + // ok to try and start the session + if (!session_start()) { + throw new \RuntimeException('Failed to start the session'); + } + + $this->loadSession(); + if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) { + // This condition matches only PHP 5.3 with internal save handlers + $this->saveHandler->setActive(true); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + if (!$this->started) { + return ''; // returning empty is consistent with session_id() behaviour + } + + return $this->saveHandler->getId(); + } + + /** + * {@inheritdoc} + */ + public function setId($id) + { + $this->saveHandler->setId($id); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->saveHandler->getName(); + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->saveHandler->setName($name); + } + + /** + * {@inheritdoc} + */ + public function regenerate($destroy = false, $lifetime = null) + { + if (null !== $lifetime) { + ini_set('session.cookie_lifetime', $lifetime); + } + + if ($destroy) { + $this->metadataBag->stampNew(); + } + + $ret = session_regenerate_id($destroy); + + // workaround for https://bugs.php.net/bug.php?id=61470 as suggested by David Grudl + if ('files' === $this->getSaveHandler()->getSaveHandlerName()) { + session_write_close(); + if (isset($_SESSION)) { + $backup = $_SESSION; + session_start(); + $_SESSION = $backup; + } else { + session_start(); + } + } + + return $ret; + } + + /** + * {@inheritdoc} + */ + public function save() + { + session_write_close(); + + if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) { + // This condition matches only PHP 5.3 with internal save handlers + $this->saveHandler->setActive(false); + } + + $this->closed = true; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // clear out the session + $_SESSION = array(); + + // reconnect the bags to the session + $this->loadSession(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + $this->bags[$bag->getName()] = $bag; + } + + /** + * {@inheritdoc} + */ + public function getBag($name) + { + if (!isset($this->bags[$name])) { + throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name)); + } + + if ($this->saveHandler->isActive() && !$this->started) { + $this->loadSession(); + } elseif (!$this->started) { + $this->start(); + } + + return $this->bags[$name]; + } + + /** + * Sets the MetadataBag. + * + * @param MetadataBag $metaBag + */ + public function setMetadataBag(MetadataBag $metaBag = null) + { + if (null === $metaBag) { + $metaBag = new MetadataBag(); + } + + $this->metadataBag = $metaBag; + } + + /** + * Gets the MetadataBag. + * + * @return MetadataBag + */ + public function getMetadataBag() + { + return $this->metadataBag; + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->started; + } + + /** + * Sets session.* ini variables. + * + * For convenience we omit 'session.' from the beginning of the keys. + * Explicitly ignores other ini keys. + * + * @param array $options Session ini directives array(key => value). + * + * @see http://php.net/session.configuration + */ + public function setOptions(array $options) + { + $validOptions = array_flip(array( + 'cache_limiter', 'cookie_domain', 'cookie_httponly', + 'cookie_lifetime', 'cookie_path', 'cookie_secure', + 'entropy_file', 'entropy_length', 'gc_divisor', + 'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character', + 'hash_function', 'name', 'referer_check', + 'serialize_handler', 'use_cookies', + 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled', + 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name', + 'upload_progress.freq', 'upload_progress.min-freq', 'url_rewriter.tags', + )); + + foreach ($options as $key => $value) { + if (isset($validOptions[$key])) { + ini_set('session.'.$key, $value); + } + } + } + + /** + * Registers session save handler as a PHP session handler. + * + * To use internal PHP session save handlers, override this method using ini_set with + * session.save_handler and session.save_path e.g. + * + * ini_set('session.save_handler', 'files'); + * ini_set('session.save_path', /tmp'); + * + * or pass in a NativeSessionHandler instance which configures session.save_handler in the + * constructor, for a template see NativeFileSessionHandler or use handlers in + * composer package drak/native-session + * + * @see http://php.net/session-set-save-handler + * @see http://php.net/sessionhandlerinterface + * @see http://php.net/sessionhandler + * @see http://github.com/drak/NativeSession + * + * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $saveHandler + * + * @throws \InvalidArgumentException + */ + public function setSaveHandler($saveHandler = null) + { + if (!$saveHandler instanceof AbstractProxy && + !$saveHandler instanceof NativeSessionHandler && + !$saveHandler instanceof \SessionHandlerInterface && + null !== $saveHandler) { + throw new \InvalidArgumentException('Must be instance of AbstractProxy or NativeSessionHandler; implement \SessionHandlerInterface; or be null.'); + } + + // Wrap $saveHandler in proxy and prevent double wrapping of proxy + if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) { + $saveHandler = new SessionHandlerProxy($saveHandler); + } elseif (!$saveHandler instanceof AbstractProxy) { + $saveHandler = version_compare(phpversion(), '5.4.0', '>=') ? + new SessionHandlerProxy(new \SessionHandler()) : new NativeProxy(); + } + $this->saveHandler = $saveHandler; + + if ($this->saveHandler instanceof \SessionHandlerInterface) { + if (version_compare(phpversion(), '5.4.0', '>=')) { + session_set_save_handler($this->saveHandler, false); + } else { + session_set_save_handler( + array($this->saveHandler, 'open'), + array($this->saveHandler, 'close'), + array($this->saveHandler, 'read'), + array($this->saveHandler, 'write'), + array($this->saveHandler, 'destroy'), + array($this->saveHandler, 'gc') + ); + } + } + } + + /** + * Load the session with attributes. + * + * After starting the session, PHP retrieves the session from whatever handlers + * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()). + * PHP takes the return value from the read() handler, unserializes it + * and populates $_SESSION with the result automatically. + * + * @param array|null $session + */ + protected function loadSession(array &$session = null) + { + if (null === $session) { + $session = &$_SESSION; + } + + $bags = array_merge($this->bags, array($this->metadataBag)); + + foreach ($bags as $bag) { + $key = $bag->getStorageKey(); + $session[$key] = isset($session[$key]) ? $session[$key] : array(); + $bag->initialize($session[$key]); + } + + $this->started = true; + $this->closed = false; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php new file mode 100644 index 0000000..0f00203 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/PhpBridgeSessionStorage.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler; + +/** + * Allows session to be started by PHP and managed by Symfony2 + * + * @author Drak + */ +class PhpBridgeSessionStorage extends NativeSessionStorage +{ + /** + * Constructor. + * + * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $handler + * @param MetadataBag $metaBag MetadataBag + */ + public function __construct($handler = null, MetadataBag $metaBag = null) + { + $this->setMetadataBag($metaBag); + $this->setSaveHandler($handler); + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started && !$this->closed) { + return true; + } + + $this->loadSession(); + if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) { + // This condition matches only PHP 5.3 + internal save handlers + $this->saveHandler->setActive(true); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags and nothing else that may be set + // since the purpose of this driver is to share a handler + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // reconnect the bags to the session + $this->loadSession(); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php new file mode 100644 index 0000000..ee6eb89 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; + +/** + * AbstractProxy. + * + * @author Drak + */ +abstract class AbstractProxy +{ + /** + * Flag if handler wraps an internal PHP session handler (using \SessionHandler). + * + * @var boolean + */ + protected $wrapper = false; + + /** + * @var boolean + */ + protected $active = false; + + /** + * @var string + */ + protected $saveHandlerName; + + /** + * Gets the session.save_handler name. + * + * @return string + */ + public function getSaveHandlerName() + { + return $this->saveHandlerName; + } + + /** + * Is this proxy handler and instance of \SessionHandlerInterface. + * + * @return boolean + */ + public function isSessionHandlerInterface() + { + return ($this instanceof \SessionHandlerInterface); + } + + /** + * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. + * + * @return Boolean + */ + public function isWrapper() + { + return $this->wrapper; + } + + /** + * Has a session started? + * + * @return Boolean + */ + public function isActive() + { + if (version_compare(phpversion(), '5.4.0', '>=')) { + return $this->active = \PHP_SESSION_ACTIVE === session_status(); + } + + return $this->active; + } + + /** + * Sets the active flag. + * + * Has no effect under PHP 5.4+ as status is detected + * automatically in isActive() + * + * @internal + * + * @param Boolean $flag + * + * @throws \LogicException + */ + public function setActive($flag) + { + if (version_compare(phpversion(), '5.4.0', '>=')) { + throw new \LogicException('This method is disabled in PHP 5.4.0+'); + } + + $this->active = (bool) $flag; + } + + /** + * Gets the session ID. + * + * @return string + */ + public function getId() + { + return session_id(); + } + + /** + * Sets the session ID. + * + * @param string $id + * + * @throws \LogicException + */ + public function setId($id) + { + if ($this->isActive()) { + throw new \LogicException('Cannot change the ID of an active session'); + } + + session_id($id); + } + + /** + * Gets the session name. + * + * @return string + */ + public function getName() + { + return session_name(); + } + + /** + * Sets the session name. + * + * @param string $name + * + * @throws \LogicException + */ + public function setName($name) + { + if ($this->isActive()) { + throw new \LogicException('Cannot change the name of an active session'); + } + + session_name($name); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Proxy/NativeProxy.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Proxy/NativeProxy.php new file mode 100644 index 0000000..23eebb3 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Proxy/NativeProxy.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; + +/** + * NativeProxy. + * + * This proxy is built-in session handlers in PHP 5.3.x + * + * @author Drak + */ +class NativeProxy extends AbstractProxy +{ + /** + * Constructor. + */ + public function __construct() + { + // this makes an educated guess as to what the handler is since it should already be set. + $this->saveHandlerName = ini_get('session.save_handler'); + } + + /** + * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. + * + * @return Boolean False. + */ + public function isWrapper() + { + return false; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php new file mode 100644 index 0000000..e1f4fff --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; + +/** + * SessionHandler proxy. + * + * @author Drak + */ +class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface +{ + /** + * @var \SessionHandlerInterface + */ + protected $handler; + + /** + * Constructor. + * + * @param \SessionHandlerInterface $handler + */ + public function __construct(\SessionHandlerInterface $handler) + { + $this->handler = $handler; + $this->wrapper = ($handler instanceof \SessionHandler); + $this->saveHandlerName = $this->wrapper ? ini_get('session.save_handler') : 'user'; + } + + // \SessionHandlerInterface + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + $return = (bool) $this->handler->open($savePath, $sessionName); + + if (true === $return) { + $this->active = true; + } + + return $return; + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->active = false; + + return (bool) $this->handler->close(); + } + + /** + * {@inheritdoc} + */ + public function read($id) + { + return (string) $this->handler->read($id); + } + + /** + * {@inheritdoc} + */ + public function write($id, $data) + { + return (bool) $this->handler->write($id, $data); + } + + /** + * {@inheritdoc} + */ + public function destroy($id) + { + return (bool) $this->handler->destroy($id); + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + return (bool) $this->handler->gc($maxlifetime); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php new file mode 100644 index 0000000..711eaa2 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session/Storage/SessionStorageInterface.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; + +/** + * StorageInterface. + * + * @author Fabien Potencier + * @author Drak + * + * @api + */ +interface SessionStorageInterface +{ + /** + * Starts the session. + * + * @throws \RuntimeException If something goes wrong starting the session. + * + * @return boolean True if started. + * + * @api + */ + public function start(); + + /** + * Checks if the session is started. + * + * @return boolean True if started, false otherwise. + */ + public function isStarted(); + + /** + * Returns the session ID + * + * @return string The session ID or empty. + * + * @api + */ + public function getId(); + + /** + * Sets the session ID + * + * @param string $id + * + * @api + */ + public function setId($id); + + /** + * Returns the session name + * + * @return mixed The session name. + * + * @api + */ + public function getName(); + + /** + * Sets the session name + * + * @param string $name + * + * @api + */ + public function setName($name); + + /** + * Regenerates id that represents this storage. + * + * This method must invoke session_regenerate_id($destroy) unless + * this interface is used for a storage object designed for unit + * or functional testing where a real PHP session would interfere + * with testing. + * + * Note regenerate+destroy should not clear the session data in memory + * only delete the session data from persistent storage. + * + * @param Boolean $destroy Destroy session when regenerating? + * @param integer $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return Boolean True if session regenerated, false if error + * + * @throws \RuntimeException If an error occurs while regenerating this storage + * + * @api + */ + public function regenerate($destroy = false, $lifetime = null); + + /** + * Force the session to be saved and closed. + * + * This method must invoke session_write_close() unless this interface is + * used for a storage object design for unit or functional testing where + * a real PHP session would interfere with testing, in which case it + * it should actually persist the session data if required. + * + * @throws \RuntimeException If the session is saved without being started, or if the session + * is already closed. + */ + public function save(); + + /** + * Clear all session data in memory. + */ + public function clear(); + + /** + * Gets a SessionBagInterface by name. + * + * @param string $name + * + * @return SessionBagInterface + * + * @throws \InvalidArgumentException If the bag does not exist + */ + public function getBag($name); + + /** + * Registers a SessionBagInterface for use. + * + * @param SessionBagInterface $bag + */ + public function registerBag(SessionBagInterface $bag); + + /** + * @return MetadataBag + */ + public function getMetadataBag(); +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/StreamedResponse.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/StreamedResponse.php new file mode 100644 index 0000000..ae579be --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/StreamedResponse.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * StreamedResponse represents a streamed HTTP response. + * + * A StreamedResponse uses a callback for its content. + * + * The callback should use the standard PHP functions like echo + * to stream the response back to the client. The flush() method + * can also be used if needed. + * + * @see flush() + * + * @author Fabien Potencier + * + * @api + */ +class StreamedResponse extends Response +{ + protected $callback; + protected $streamed; + + /** + * Constructor. + * + * @param mixed $callback A valid PHP callback + * @param integer $status The response status code + * @param array $headers An array of response headers + * + * @api + */ + public function __construct($callback = null, $status = 200, $headers = array()) + { + parent::__construct(null, $status, $headers); + + if (null !== $callback) { + $this->setCallback($callback); + } + $this->streamed = false; + } + + /** + * {@inheritDoc} + */ + public static function create($callback = null, $status = 200, $headers = array()) + { + return new static($callback, $status, $headers); + } + + /** + * Sets the PHP callback associated with this Response. + * + * @param mixed $callback A valid PHP callback + * + * @throws \LogicException + */ + public function setCallback($callback) + { + if (!is_callable($callback)) { + throw new \LogicException('The Response callback must be a valid PHP callable.'); + } + $this->callback = $callback; + } + + /** + * {@inheritdoc} + */ + public function prepare(Request $request) + { + $this->headers->set('Cache-Control', 'no-cache'); + + return parent::prepare($request); + } + + /** + * {@inheritdoc} + * + * This method only sends the content once. + */ + public function sendContent() + { + if ($this->streamed) { + return; + } + + $this->streamed = true; + + if (null === $this->callback) { + throw new \LogicException('The Response callback must not be null.'); + } + + call_user_func($this->callback); + } + + /** + * {@inheritdoc} + * + * @throws \LogicException when the content is not null + */ + public function setContent($content) + { + if (null !== $content) { + throw new \LogicException('The content cannot be set on a StreamedResponse instance.'); + } + } + + /** + * {@inheritdoc} + * + * @return false + */ + public function getContent() + { + return false; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/AcceptHeaderItemTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/AcceptHeaderItemTest.php new file mode 100644 index 0000000..582fbde --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/AcceptHeaderItemTest.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\AcceptHeaderItem; + +class AcceptHeaderItemTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider provideFromStringData + */ + public function testFromString($string, $value, array $attributes) + { + $item = AcceptHeaderItem::fromString($string); + $this->assertEquals($value, $item->getValue()); + $this->assertEquals($attributes, $item->getAttributes()); + } + + public function provideFromStringData() + { + return array( + array( + 'text/html', + 'text/html', array() + ), + array( + '"this;should,not=matter"', + 'this;should,not=matter', array() + ), + array( + "text/plain; charset=utf-8;param=\"this;should,not=matter\";\tfootnotes=true", + 'text/plain', array('charset' => 'utf-8', 'param' => 'this;should,not=matter', 'footnotes' => 'true') + ), + array( + '"this;should,not=matter";charset=utf-8', + 'this;should,not=matter', array('charset' => 'utf-8') + ), + ); + } + + /** + * @dataProvider provideToStringData + */ + public function testToString($value, array $attributes, $string) + { + $item = new AcceptHeaderItem($value, $attributes); + $this->assertEquals($string, (string) $item); + } + + public function provideToStringData() + { + return array( + array( + 'text/html', array(), + 'text/html' + ), + array( + 'text/plain', array('charset' => 'utf-8', 'param' => 'this;should,not=matter', 'footnotes' => 'true'), + 'text/plain;charset=utf-8;param="this;should,not=matter";footnotes=true' + ), + ); + } + + public function testValue() + { + $item = new AcceptHeaderItem('value', array()); + $this->assertEquals('value', $item->getValue()); + + $item->setValue('new value'); + $this->assertEquals('new value', $item->getValue()); + + $item->setValue(1); + $this->assertEquals('1', $item->getValue()); + } + + public function testQuality() + { + $item = new AcceptHeaderItem('value', array()); + $this->assertEquals(1.0, $item->getQuality()); + + $item->setQuality(0.5); + $this->assertEquals(0.5, $item->getQuality()); + + $item->setAttribute('q', 0.75); + $this->assertEquals(0.75, $item->getQuality()); + $this->assertFalse($item->hasAttribute('q')); + } + + public function testAttribute() + { + $item = new AcceptHeaderItem('value', array()); + $this->assertEquals(array(), $item->getAttributes()); + $this->assertFalse($item->hasAttribute('test')); + $this->assertNull($item->getAttribute('test')); + $this->assertEquals('default', $item->getAttribute('test', 'default')); + + $item->setAttribute('test', 'value'); + $this->assertEquals(array('test' => 'value'), $item->getAttributes()); + $this->assertTrue($item->hasAttribute('test')); + $this->assertEquals('value', $item->getAttribute('test')); + $this->assertEquals('value', $item->getAttribute('test', 'default')); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/AcceptHeaderTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/AcceptHeaderTest.php new file mode 100644 index 0000000..9b3b58e --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/AcceptHeaderTest.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\AcceptHeader; +use Symfony\Component\HttpFoundation\AcceptHeaderItem; + +class AcceptHeaderTest extends \PHPUnit_Framework_TestCase +{ + public function testFirst() + { + $header = AcceptHeader::fromString('text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c'); + $this->assertSame('text/html', $header->first()->getValue()); + } + + /** + * @dataProvider provideFromStringData + */ + public function testFromString($string, array $items) + { + $header = AcceptHeader::fromString($string); + $parsed = array_values($header->all()); + // reset index since the fixtures don't have them set + foreach ($parsed as $item) { + $item->setIndex(0); + } + $this->assertEquals($items, $parsed); + } + + public function provideFromStringData() + { + return array( + array('', array()), + array('gzip', array(new AcceptHeaderItem('gzip'))), + array('gzip,deflate,sdch', array(new AcceptHeaderItem('gzip'), new AcceptHeaderItem('deflate'), new AcceptHeaderItem('sdch'))), + array("gzip, deflate\t,sdch", array(new AcceptHeaderItem('gzip'), new AcceptHeaderItem('deflate'), new AcceptHeaderItem('sdch'))), + array('"this;should,not=matter"', array(new AcceptHeaderItem('this;should,not=matter'))), + ); + } + + /** + * @dataProvider provideToStringData + */ + public function testToString(array $items, $string) + { + $header = new AcceptHeader($items); + $this->assertEquals($string, (string) $header); + } + + public function provideToStringData() + { + return array( + array(array(), ''), + array(array(new AcceptHeaderItem('gzip')), 'gzip'), + array(array(new AcceptHeaderItem('gzip'), new AcceptHeaderItem('deflate'), new AcceptHeaderItem('sdch')), 'gzip,deflate,sdch'), + array(array(new AcceptHeaderItem('this;should,not=matter')), 'this;should,not=matter'), + ); + } + + /** + * @dataProvider provideFilterData + */ + public function testFilter($string, $filter, array $values) + { + $header = AcceptHeader::fromString($string)->filter($filter); + $this->assertEquals($values, array_keys($header->all())); + } + + public function provideFilterData() + { + return array( + array('fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4', '/fr.*/', array('fr-FR', 'fr')), + ); + } + + /** + * @dataProvider provideSortingData + */ + public function testSorting($string, array $values) + { + $header = AcceptHeader::fromString($string); + $this->assertEquals($values, array_keys($header->all())); + } + + public function provideSortingData() + { + return array( + 'quality has priority' => array('*;q=0.3,ISO-8859-1,utf-8;q=0.7', array('ISO-8859-1', 'utf-8', '*')), + 'order matters when q is equal' => array('*;q=0.3,ISO-8859-1;q=0.7,utf-8;q=0.7', array('ISO-8859-1', 'utf-8', '*')), + 'order matters when q is equal2' => array('*;q=0.3,utf-8;q=0.7,ISO-8859-1;q=0.7', array('utf-8', 'ISO-8859-1', '*')), + ); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ApacheRequestTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ApacheRequestTest.php new file mode 100644 index 0000000..965a7d2 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ApacheRequestTest.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\ApacheRequest; + +class ApacheRequestTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider provideServerVars + */ + public function testUriMethods($server, $expectedRequestUri, $expectedBaseUrl, $expectedPathInfo) + { + $request = new ApacheRequest(); + $request->server->replace($server); + + $this->assertEquals($expectedRequestUri, $request->getRequestUri(), '->getRequestUri() is correct'); + $this->assertEquals($expectedBaseUrl, $request->getBaseUrl(), '->getBaseUrl() is correct'); + $this->assertEquals($expectedPathInfo, $request->getPathInfo(), '->getPathInfo() is correct'); + } + + public function provideServerVars() + { + return array( + array( + array( + 'REQUEST_URI' => '/foo/app_dev.php/bar', + 'SCRIPT_NAME' => '/foo/app_dev.php', + 'PATH_INFO' => '/bar', + ), + '/foo/app_dev.php/bar', + '/foo/app_dev.php', + '/bar' + ), + array( + array( + 'REQUEST_URI' => '/foo/bar', + 'SCRIPT_NAME' => '/foo/app_dev.php', + ), + '/foo/bar', + '/foo', + '/bar', + ), + array( + array( + 'REQUEST_URI' => '/app_dev.php/foo/bar', + 'SCRIPT_NAME' => '/app_dev.php', + 'PATH_INFO' => '/foo/bar', + ), + '/app_dev.php/foo/bar', + '/app_dev.php', + '/foo/bar', + ), + array( + array( + 'REQUEST_URI' => '/foo/bar', + 'SCRIPT_NAME' => '/app_dev.php', + ), + '/foo/bar', + '', + '/foo/bar', + ), + array( + array( + 'REQUEST_URI' => '/app_dev.php', + 'SCRIPT_NAME' => '/app_dev.php', + ), + '/app_dev.php', + '/app_dev.php', + '/', + ), + array( + array( + 'REQUEST_URI' => '/', + 'SCRIPT_NAME' => '/app_dev.php', + ), + '/', + '', + '/', + ), + ); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php new file mode 100644 index 0000000..c3d324f --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; + +class BinaryFileResponseTest extends ResponseTestCase +{ + public function testConstruction() + { + $response = new BinaryFileResponse('README.md', 404, array('X-Header' => 'Foo'), true, null, true, true); + $this->assertEquals(404, $response->getStatusCode()); + $this->assertEquals('Foo', $response->headers->get('X-Header')); + $this->assertTrue($response->headers->has('ETag')); + $this->assertTrue($response->headers->has('Last-Modified')); + $this->assertFalse($response->headers->has('Content-Disposition')); + + $response = BinaryFileResponse::create('README.md', 404, array(), true, ResponseHeaderBag::DISPOSITION_INLINE); + $this->assertEquals(404, $response->getStatusCode()); + $this->assertFalse($response->headers->has('ETag')); + $this->assertEquals('inline; filename="README.md"', $response->headers->get('Content-Disposition')); + } + + /** + * @expectedException \LogicException + */ + public function testSetContent() + { + $response = new BinaryFileResponse('README.md'); + $response->setContent('foo'); + } + + public function testGetContent() + { + $response = new BinaryFileResponse('README.md'); + $this->assertFalse($response->getContent()); + } + + /** + * @dataProvider provideRanges + */ + public function testRequests($requestRange, $offset, $length, $responseRange) + { + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif')->setAutoEtag(); + + // do a request to get the ETag + $request = Request::create('/'); + $response->prepare($request); + $etag = $response->headers->get('ETag'); + + // prepare a request for a range of the testing file + $request = Request::create('/'); + $request->headers->set('If-Range', $etag); + $request->headers->set('Range', $requestRange); + + $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r'); + fseek($file, $offset); + $data = fread($file, $length); + fclose($file); + + $this->expectOutputString($data); + $response = clone $response; + $response->prepare($request); + $response->sendContent(); + + $this->assertEquals(206, $response->getStatusCode()); + $this->assertEquals('binary', $response->headers->get('Content-Transfer-Encoding')); + $this->assertEquals($responseRange, $response->headers->get('Content-Range')); + } + + public function provideRanges() + { + return array( + array('bytes=1-4', 1, 4, 'bytes 1-4/35'), + array('bytes=-5', 30, 5, 'bytes 30-34/35'), + array('bytes=-35', 0, 35, 'bytes 0-34/35'), + array('bytes=-40', 0, 35, 'bytes 0-34/35'), + array('bytes=30-', 30, 5, 'bytes 30-34/35'), + array('bytes=30-30', 30, 1, 'bytes 30-30/35'), + array('bytes=30-34', 30, 5, 'bytes 30-34/35'), + array('bytes=30-40', 30, 5, 'bytes 30-34/35') + ); + } + + public function testXSendfile() + { + $request = Request::create('/'); + $request->headers->set('X-Sendfile-Type', 'X-Sendfile'); + + BinaryFileResponse::trustXSendfileTypeHeader(); + $response = BinaryFileResponse::create('README.md'); + $response->prepare($request); + + $this->expectOutputString(''); + $response->sendContent(); + + $this->assertContains('README.md', $response->headers->get('X-Sendfile')); + } + + /** + * @dataProvider getSampleXAccelMappings + */ + public function testXAccelMapping($realpath, $mapping, $virtual) + { + $request = Request::create('/'); + $request->headers->set('X-Sendfile-Type', 'X-Accel-Redirect'); + $request->headers->set('X-Accel-Mapping', $mapping); + + $file = $this->getMockBuilder('Symfony\Component\HttpFoundation\File\File') + ->disableOriginalConstructor() + ->getMock(); + $file->expects($this->any()) + ->method('getRealPath') + ->will($this->returnValue($realpath)); + $file->expects($this->any()) + ->method('isReadable') + ->will($this->returnValue(true)); + + BinaryFileResponse::trustXSendFileTypeHeader(); + $response = new BinaryFileResponse('README.md'); + $reflection = new \ReflectionObject($response); + $property = $reflection->getProperty('file'); + $property->setAccessible(true); + $property->setValue($response, $file); + + $response->prepare($request); + $this->assertEquals($virtual, $response->headers->get('X-Accel-Redirect')); + } + + public function getSampleXAccelMappings() + { + return array( + array('/var/www/var/www/files/foo.txt', '/files/=/var/www/', '/files/var/www/files/foo.txt'), + array('/home/foo/bar.txt', '/files/=/var/www/,/baz/=/home/foo/', '/baz/bar.txt'), + ); + } + + protected function provideResponse() + { + return new BinaryFileResponse('README.md'); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/CookieTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/CookieTest.php new file mode 100644 index 0000000..f4c9ec1 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/CookieTest.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\Cookie; + +/** + * CookieTest + * + * @author John Kary + * @author Hugo Hamon + */ +class CookieTest extends \PHPUnit_Framework_TestCase +{ + public function invalidNames() + { + return array( + array(''), + array(",MyName"), + array(";MyName"), + array(" MyName"), + array("\tMyName"), + array("\rMyName"), + array("\nMyName"), + array("\013MyName"), + array("\014MyName"), + ); + } + + /** + * @dataProvider invalidNames + * @expectedException InvalidArgumentException + * @covers Symfony\Component\HttpFoundation\Cookie::__construct + */ + public function testInstantiationThrowsExceptionIfCookieNameContainsInvalidCharacters($name) + { + new Cookie($name); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testInvalidExpiration() + { + $cookie = new Cookie('MyCookie', 'foo','bar'); + } + + /** + * @covers Symfony\Component\HttpFoundation\Cookie::getValue + */ + public function testGetValue() + { + $value = 'MyValue'; + $cookie = new Cookie('MyCookie', $value); + + $this->assertSame($value, $cookie->getValue(), '->getValue() returns the proper value'); + } + + public function testGetPath() + { + $cookie = new Cookie('foo', 'bar'); + + $this->assertSame('/', $cookie->getPath(), '->getPath() returns / as the default path'); + } + + public function testGetExpiresTime() + { + $cookie = new Cookie('foo', 'bar', 3600); + + $this->assertEquals(3600, $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date'); + } + + public function testConstructorWithDateTime() + { + $expire = new \DateTime(); + $cookie = new Cookie('foo', 'bar', $expire); + + $this->assertEquals($expire->format('U'), $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date'); + } + + public function testGetExpiresTimeWithStringValue() + { + $value = "+1 day"; + $cookie = new Cookie('foo', 'bar', $value); + $expire = strtotime($value); + + $this->assertEquals($expire, $cookie->getExpiresTime(), '->getExpiresTime() returns the expire date'); + } + + public function testGetDomain() + { + $cookie = new Cookie('foo', 'bar', 3600, '/', '.myfoodomain.com'); + + $this->assertEquals('.myfoodomain.com', $cookie->getDomain(), '->getDomain() returns the domain name on which the cookie is valid'); + } + + public function testIsSecure() + { + $cookie = new Cookie('foo', 'bar', 3600, '/', '.myfoodomain.com', true); + + $this->assertTrue($cookie->isSecure(), '->isSecure() returns whether the cookie is transmitted over HTTPS'); + } + + public function testIsHttpOnly() + { + $cookie = new Cookie('foo', 'bar', 3600, '/', '.myfoodomain.com', false, true); + + $this->assertTrue($cookie->isHttpOnly(), '->isHttpOnly() returns whether the cookie is only transmitted over HTTP'); + } + + public function testCookieIsNotCleared() + { + $cookie = new Cookie('foo', 'bar', time()+3600*24); + + $this->assertFalse($cookie->isCleared(), '->isCleared() returns false if the cookie did not expire yet'); + } + + public function testCookieIsCleared() + { + $cookie = new Cookie('foo', 'bar', time()-20); + + $this->assertTrue($cookie->isCleared(), '->isCleared() returns true if the cookie has expired'); + } + + public function testToString() + { + $cookie = new Cookie('foo', 'bar', strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true); + $this->assertEquals('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; path=/; domain=.myfoodomain.com; secure; httponly', $cookie->__toString(), '->__toString() returns string representation of the cookie'); + + $cookie = new Cookie('foo', null, 1, '/admin/', '.myfoodomain.com'); + $this->assertEquals('foo=deleted; expires='.gmdate("D, d-M-Y H:i:s T", time()-31536001).'; path=/admin/; domain=.myfoodomain.com; httponly', $cookie->__toString(), '->__toString() returns string representation of a cleared cookie if value is NULL'); + + $cookie = new Cookie('foo', 'bar', 0, '/', ''); + $this->assertEquals('foo=bar; path=/; httponly', $cookie->__toString()); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/FileTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/FileTest.php new file mode 100644 index 0000000..b64d5f5 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/FileTest.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\File; + +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser; + +class FileTest extends \PHPUnit_Framework_TestCase +{ + protected $file; + + public function testGetMimeTypeUsesMimeTypeGuessers() + { + $file = new File(__DIR__.'/Fixtures/test.gif'); + $guesser = $this->createMockGuesser($file->getPathname(), 'image/gif'); + + MimeTypeGuesser::getInstance()->register($guesser); + + $this->assertEquals('image/gif', $file->getMimeType()); + } + + public function testGuessExtensionWithoutGuesser() + { + $file = new File(__DIR__.'/Fixtures/directory/.empty'); + + $this->assertNull($file->guessExtension()); + } + + public function testGuessExtensionIsBasedOnMimeType() + { + $file = new File(__DIR__.'/Fixtures/test'); + $guesser = $this->createMockGuesser($file->getPathname(), 'image/gif'); + + MimeTypeGuesser::getInstance()->register($guesser); + + $this->assertEquals('gif', $file->guessExtension()); + } + + public function testConstructWhenFileNotExists() + { + $this->setExpectedException('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException'); + + new File(__DIR__.'/Fixtures/not_here'); + } + + public function testMove() + { + $path = __DIR__.'/Fixtures/test.copy.gif'; + $targetDir = __DIR__.'/Fixtures/directory'; + $targetPath = $targetDir.'/test.copy.gif'; + @unlink($path); + @unlink($targetPath); + copy(__DIR__.'/Fixtures/test.gif', $path); + + $file = new File($path); + $movedFile = $file->move($targetDir); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\File\File', $movedFile); + + $this->assertTrue(file_exists($targetPath)); + $this->assertFalse(file_exists($path)); + $this->assertEquals(realpath($targetPath), $movedFile->getRealPath()); + + @unlink($targetPath); + } + + public function testMoveWithNewName() + { + $path = __DIR__.'/Fixtures/test.copy.gif'; + $targetDir = __DIR__.'/Fixtures/directory'; + $targetPath = $targetDir.'/test.newname.gif'; + @unlink($path); + @unlink($targetPath); + copy(__DIR__.'/Fixtures/test.gif', $path); + + $file = new File($path); + $movedFile = $file->move($targetDir, 'test.newname.gif'); + + $this->assertTrue(file_exists($targetPath)); + $this->assertFalse(file_exists($path)); + $this->assertEquals(realpath($targetPath), $movedFile->getRealPath()); + + @unlink($targetPath); + } + + public function getFilenameFixtures() + { + return array( + array('original.gif', 'original.gif'), + array('..\\..\\original.gif', 'original.gif'), + array('../../original.gif', 'original.gif'), + array('файлfile.gif', 'файлfile.gif'), + array('..\\..\\файлfile.gif', 'файлfile.gif'), + array('../../файлfile.gif', 'файлfile.gif'), + ); + } + + /** + * @dataProvider getFilenameFixtures + */ + public function testMoveWithNonLatinName($filename, $sanitizedFilename) + { + $path = __DIR__.'/Fixtures/'.$sanitizedFilename; + $targetDir = __DIR__.'/Fixtures/directory/'; + $targetPath = $targetDir.$sanitizedFilename; + @unlink($path); + @unlink($targetPath); + copy(__DIR__.'/Fixtures/test.gif', $path); + + $file = new File($path); + $movedFile = $file->move($targetDir,$filename); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\File\File', $movedFile); + + $this->assertTrue(file_exists($targetPath)); + $this->assertFalse(file_exists($path)); + $this->assertEquals(realpath($targetPath), $movedFile->getRealPath()); + + @unlink($targetPath); + } + + public function testMoveToAnUnexistentDirectory() + { + $sourcePath = __DIR__.'/Fixtures/test.copy.gif'; + $targetDir = __DIR__.'/Fixtures/directory/sub'; + $targetPath = $targetDir.'/test.copy.gif'; + @unlink($sourcePath); + @unlink($targetPath); + @rmdir($targetDir); + copy(__DIR__.'/Fixtures/test.gif', $sourcePath); + + $file = new File($sourcePath); + $movedFile = $file->move($targetDir); + + $this->assertFileExists($targetPath); + $this->assertFileNotExists($sourcePath); + $this->assertEquals(realpath($targetPath), $movedFile->getRealPath()); + + @unlink($sourcePath); + @unlink($targetPath); + @rmdir($targetDir); + } + + public function testGetExtension() + { + $file = new File(__DIR__.'/Fixtures/test.gif'); + $this->assertEquals('gif', $file->getExtension()); + } + + protected function createMockGuesser($path, $mimeType) + { + $guesser = $this->getMock('Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesserInterface'); + $guesser + ->expects($this->once()) + ->method('guess') + ->with($this->equalTo($path)) + ->will($this->returnValue($mimeType)) + ; + + return $guesser; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/Fixtures/.unknownextension b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/Fixtures/.unknownextension new file mode 100644 index 0000000..4d1ae35 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/Fixtures/.unknownextension @@ -0,0 +1 @@ +f \ No newline at end of file diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/Fixtures/directory/.empty b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/Fixtures/directory/.empty new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/Fixtures/test b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/Fixtures/test new file mode 100644 index 0000000..b636f4b Binary files /dev/null and b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/Fixtures/test differ diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/Fixtures/test.gif b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/Fixtures/test.gif new file mode 100644 index 0000000..b636f4b Binary files /dev/null and b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/Fixtures/test.gif differ diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/MimeType/MimeTypeTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/MimeType/MimeTypeTest.php new file mode 100644 index 0000000..7bf10a2 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/MimeType/MimeTypeTest.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\File; + +use Symfony\Component\HttpFoundation\File\File; +use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser; +use Symfony\Component\HttpFoundation\File\MimeType\FileBinaryMimeTypeGuesser; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; + +class MimeTypeTest extends \PHPUnit_Framework_TestCase +{ + protected $path; + + public function testGuessImageWithoutExtension() + { + if (extension_loaded('fileinfo')) { + $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test')); + } else { + $this->assertNull(MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test')); + } + } + + public function testGuessImageWithDirectory() + { + $this->setExpectedException('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException'); + + MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/directory'); + } + + public function testGuessImageWithFileBinaryMimeTypeGuesser() + { + $guesser = MimeTypeGuesser::getInstance(); + $guesser->register(new FileBinaryMimeTypeGuesser()); + if (extension_loaded('fileinfo')) { + $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test')); + } else { + $this->assertNull(MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test')); + } + } + + public function testGuessImageWithKnownExtension() + { + if (extension_loaded('fileinfo')) { + $this->assertEquals('image/gif', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test.gif')); + } else { + $this->assertNull(MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/test.gif')); + } + } + + public function testGuessFileWithUnknownExtension() + { + if (extension_loaded('fileinfo')) { + $this->assertEquals('application/octet-stream', MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/.unknownextension')); + } else { + $this->assertNull(MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/.unknownextension')); + } + } + + public function testGuessWithIncorrectPath() + { + $this->setExpectedException('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException'); + MimeTypeGuesser::getInstance()->guess(__DIR__.'/../Fixtures/not_here'); + } + + public function testGuessWithNonReadablePath() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Can not verify chmod operations on Windows'); + } + + if (in_array(get_current_user(), array('root'))) { + $this->markTestSkipped('This test will fail if run under superuser'); + } + + $path = __DIR__.'/../Fixtures/to_delete'; + touch($path); + @chmod($path, 0333); + + if (get_current_user() != 'root' && substr(sprintf('%o', fileperms($path)), -4) == '0333') { + $this->setExpectedException('Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException'); + MimeTypeGuesser::getInstance()->guess($path); + } else { + $this->markTestSkipped('Can not verify chmod operations, change of file permissions failed'); + } + } + + public static function tearDownAfterClass() + { + $path = __DIR__.'/../Fixtures/to_delete'; + if (file_exists($path)) { + @chmod($path, 0666); + @unlink($path); + } + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/UploadedFileTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/UploadedFileTest.php new file mode 100644 index 0000000..f6ea340 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/File/UploadedFileTest.php @@ -0,0 +1,272 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\File; + +use Symfony\Component\HttpFoundation\File\UploadedFile; + +class UploadedFileTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!ini_get('file_uploads')) { + $this->markTestSkipped('file_uploads is disabled in php.ini'); + } + } + + public function testConstructWhenFileNotExists() + { + $this->setExpectedException('Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException'); + + new UploadedFile( + __DIR__.'/Fixtures/not_here', + 'original.gif', + null + ); + } + + public function testFileUploadsWithNoMimeType() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/test.gif'), + UPLOAD_ERR_OK + ); + + $this->assertEquals('application/octet-stream', $file->getClientMimeType()); + + if (extension_loaded('fileinfo')) { + $this->assertEquals('image/gif', $file->getMimeType()); + } + } + + public function testFileUploadsWithUnknownMimeType() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/.unknownextension', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/.unknownextension'), + UPLOAD_ERR_OK + ); + + $this->assertEquals('application/octet-stream', $file->getClientMimeType()); + } + + public function testGuessClientExtension() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals('gif', $file->guessClientExtension()); + } + + public function testGuessClientExtensionWithIncorrectMimeType() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/jpeg', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals('jpeg', $file->guessClientExtension()); + } + + public function testErrorIsOkByDefault() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals(UPLOAD_ERR_OK, $file->getError()); + } + + public function testGetClientOriginalName() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals('original.gif', $file->getClientOriginalName()); + } + + public function testGetClientOriginalExtension() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals('gif', $file->getClientOriginalExtension()); + } + + /** + * @expectedException \Symfony\Component\HttpFoundation\File\Exception\FileException + */ + public function testMoveLocalFileIsNotAllowed() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + UPLOAD_ERR_OK + ); + + $movedFile = $file->move(__DIR__.'/Fixtures/directory'); + } + + public function testMoveLocalFileIsAllowedInTestMode() + { + $path = __DIR__.'/Fixtures/test.copy.gif'; + $targetDir = __DIR__.'/Fixtures/directory'; + $targetPath = $targetDir.'/test.copy.gif'; + @unlink($path); + @unlink($targetPath); + copy(__DIR__.'/Fixtures/test.gif', $path); + + $file = new UploadedFile( + $path, + 'original.gif', + 'image/gif', + filesize($path), + UPLOAD_ERR_OK, + true + ); + + $movedFile = $file->move(__DIR__.'/Fixtures/directory'); + + $this->assertTrue(file_exists($targetPath)); + $this->assertFalse(file_exists($path)); + $this->assertEquals(realpath($targetPath), $movedFile->getRealPath()); + + @unlink($targetPath); + } + + public function testGetClientOriginalNameSanitizeFilename() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + '../../original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals('original.gif', $file->getClientOriginalName()); + } + + public function testGetSize() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + 'image/gif', + filesize(__DIR__.'/Fixtures/test.gif'), + null + ); + + $this->assertEquals(filesize(__DIR__.'/Fixtures/test.gif'), $file->getSize()); + + $file = new UploadedFile( + __DIR__.'/Fixtures/test', + 'original.gif', + 'image/gif' + ); + + $this->assertEquals(filesize(__DIR__.'/Fixtures/test'), $file->getSize()); + } + + public function testGetExtension() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + null + ); + + $this->assertEquals('gif', $file->getExtension()); + } + + public function testIsValid() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/test.gif'), + UPLOAD_ERR_OK, + true + ); + + $this->assertTrue($file->isValid()); + } + + /** + * @dataProvider uploadedFileErrorProvider + */ + public function testIsInvalidOnUploadError($error) + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/test.gif'), + $error + ); + + $this->assertFalse($file->isValid()); + } + + public function uploadedFileErrorProvider() + { + return array( + array(UPLOAD_ERR_INI_SIZE), + array(UPLOAD_ERR_FORM_SIZE), + array(UPLOAD_ERR_PARTIAL), + array(UPLOAD_ERR_NO_TMP_DIR), + array(UPLOAD_ERR_EXTENSION), + ); + } + + public function testIsInvalidIfNotHttpUpload() + { + $file = new UploadedFile( + __DIR__.'/Fixtures/test.gif', + 'original.gif', + null, + filesize(__DIR__.'/Fixtures/test.gif'), + UPLOAD_ERR_OK + ); + + $this->assertFalse($file->isValid()); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/FileBagTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/FileBagTest.php new file mode 100644 index 0000000..1f29d56 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/FileBagTest.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\FileBag; + +/** + * FileBagTest. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + */ +class FileBagTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \InvalidArgumentException + */ + public function testFileMustBeAnArrayOrUploadedFile() + { + new FileBag(array('file' => 'foo')); + } + + public function testShouldConvertsUploadedFiles() + { + $tmpFile = $this->createTempFile(); + $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0); + + $bag = new FileBag(array('file' => array( + 'name' => basename($tmpFile), + 'type' => 'text/plain', + 'tmp_name' => $tmpFile, + 'error' => 0, + 'size' => 100 + ))); + + $this->assertEquals($file, $bag->get('file')); + } + + public function testShouldSetEmptyUploadedFilesToNull() + { + $bag = new FileBag(array('file' => array( + 'name' => '', + 'type' => '', + 'tmp_name' => '', + 'error' => UPLOAD_ERR_NO_FILE, + 'size' => 0 + ))); + + $this->assertNull($bag->get('file')); + } + + public function testShouldConvertUploadedFilesWithPhpBug() + { + $tmpFile = $this->createTempFile(); + $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0); + + $bag = new FileBag(array( + 'child' => array( + 'name' => array( + 'file' => basename($tmpFile), + ), + 'type' => array( + 'file' => 'text/plain', + ), + 'tmp_name' => array( + 'file' => $tmpFile, + ), + 'error' => array( + 'file' => 0, + ), + 'size' => array( + 'file' => 100, + ), + ) + )); + + $files = $bag->all(); + $this->assertEquals($file, $files['child']['file']); + } + + public function testShouldConvertNestedUploadedFilesWithPhpBug() + { + $tmpFile = $this->createTempFile(); + $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0); + + $bag = new FileBag(array( + 'child' => array( + 'name' => array( + 'sub' => array('file' => basename($tmpFile)) + ), + 'type' => array( + 'sub' => array('file' => 'text/plain') + ), + 'tmp_name' => array( + 'sub' => array('file' => $tmpFile) + ), + 'error' => array( + 'sub' => array('file' => 0) + ), + 'size' => array( + 'sub' => array('file' => 100) + ), + ) + )); + + $files = $bag->all(); + $this->assertEquals($file, $files['child']['sub']['file']); + } + + public function testShouldNotConvertNestedUploadedFiles() + { + $tmpFile = $this->createTempFile(); + $file = new UploadedFile($tmpFile, basename($tmpFile), 'text/plain', 100, 0); + $bag = new FileBag(array('image' => array('file' => $file))); + + $files = $bag->all(); + $this->assertEquals($file, $files['image']['file']); + } + + protected function createTempFile() + { + return tempnam(sys_get_temp_dir().'/form_test', 'FormTest'); + } + + protected function setUp() + { + mkdir(sys_get_temp_dir().'/form_test', 0777, true); + } + + protected function tearDown() + { + foreach (glob(sys_get_temp_dir().'/form_test/*') as $file) { + unlink($file); + } + + rmdir(sys_get_temp_dir().'/form_test'); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/HeaderBagTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/HeaderBagTest.php new file mode 100644 index 0000000..b1bfefb --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/HeaderBagTest.php @@ -0,0 +1,216 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\HeaderBag; + +class HeaderBagTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Symfony\Component\HttpFoundation\HeaderBag::__construct + */ + public function testConstructor() + { + $bag = new HeaderBag(array('foo' => 'bar')); + $this->assertTrue($bag->has('foo')); + } + + public function testToStringNull() + { + $bag = new HeaderBag(); + $this->assertEquals('', $bag->__toString()); + } + + public function testToStringNotNull() + { + $bag = new HeaderBag(array('foo' => 'bar')); + $this->assertEquals("Foo: bar\r\n", $bag->__toString()); + } + + public function testKeys() + { + $bag = new HeaderBag(array('foo' => 'bar')); + $keys = $bag->keys(); + $this->assertEquals("foo", $keys[0]); + } + + public function testGetDate() + { + $bag = new HeaderBag(array('foo' => 'Tue, 4 Sep 2012 20:00:00 +0200')); + $headerDate = $bag->getDate('foo'); + $this->assertInstanceOf('DateTime', $headerDate); + } + + /** + * @expectedException \RuntimeException + */ + public function testGetDateException() + { + $bag = new HeaderBag(array('foo' => 'Tue')); + $headerDate = $bag->getDate('foo'); + } + + public function testGetCacheControlHeader() + { + $bag = new HeaderBag(); + $bag->addCacheControlDirective('public', '#a'); + $this->assertTrue($bag->hasCacheControlDirective('public')); + $this->assertEquals('#a', $bag->getCacheControlDirective('public')); + } + + /** + * @covers Symfony\Component\HttpFoundation\HeaderBag::all + */ + public function testAll() + { + $bag = new HeaderBag(array('foo' => 'bar')); + $this->assertEquals(array('foo' => array('bar')), $bag->all(), '->all() gets all the input'); + + $bag = new HeaderBag(array('FOO' => 'BAR')); + $this->assertEquals(array('foo' => array('BAR')), $bag->all(), '->all() gets all the input key are lower case'); + } + + /** + * @covers Symfony\Component\HttpFoundation\HeaderBag::replace + */ + public function testReplace() + { + $bag = new HeaderBag(array('foo' => 'bar')); + + $bag->replace(array('NOPE' => 'BAR')); + $this->assertEquals(array('nope' => array('BAR')), $bag->all(), '->replace() replaces the input with the argument'); + $this->assertFalse($bag->has('foo'), '->replace() overrides previously set the input'); + } + + /** + * @covers Symfony\Component\HttpFoundation\HeaderBag::get + */ + public function testGet() + { + $bag = new HeaderBag(array('foo' => 'bar', 'fuzz' => 'bizz')); + $this->assertEquals( 'bar', $bag->get('foo'), '->get return current value'); + $this->assertEquals( 'bar', $bag->get('FoO'), '->get key in case insensitive'); + $this->assertEquals( array('bar'), $bag->get('foo', 'nope', false), '->get return the value as array'); + + // defaults + $this->assertNull($bag->get('none'), '->get unknown values returns null'); + $this->assertEquals( 'default', $bag->get('none', 'default'), '->get unknown values returns default'); + $this->assertEquals( array('default'), $bag->get('none', 'default', false), '->get unknown values returns default as array'); + + $bag->set('foo', 'bor', false); + $this->assertEquals( 'bar', $bag->get('foo'), '->get return first value'); + $this->assertEquals( array('bar', 'bor'), $bag->get('foo', 'nope', false), '->get return all values as array'); + } + + public function testSetAssociativeArray() + { + $bag = new HeaderBag(); + $bag->set('foo', array('bad-assoc-index' => 'value')); + $this->assertSame('value', $bag->get('foo')); + $this->assertEquals(array('value'), $bag->get('foo', 'nope', false), 'assoc indices of multi-valued headers are ignored'); + } + + /** + * @covers Symfony\Component\HttpFoundation\HeaderBag::contains + */ + public function testContains() + { + $bag = new HeaderBag(array('foo' => 'bar', 'fuzz' => 'bizz')); + $this->assertTrue( $bag->contains('foo', 'bar'), '->contains first value'); + $this->assertTrue( $bag->contains('fuzz', 'bizz'), '->contains second value'); + $this->assertFalse( $bag->contains('nope', 'nope'), '->contains unknown value'); + $this->assertFalse( $bag->contains('foo', 'nope'), '->contains unknown value'); + + // Multiple values + $bag->set('foo', 'bor', false); + $this->assertTrue( $bag->contains('foo', 'bar'), '->contains first value'); + $this->assertTrue( $bag->contains('foo', 'bor'), '->contains second value'); + $this->assertFalse( $bag->contains('foo', 'nope'), '->contains unknown value'); + } + + public function testCacheControlDirectiveAccessors() + { + $bag = new HeaderBag(); + $bag->addCacheControlDirective('public'); + + $this->assertTrue($bag->hasCacheControlDirective('public')); + $this->assertTrue($bag->getCacheControlDirective('public')); + $this->assertEquals('public', $bag->get('cache-control')); + + $bag->addCacheControlDirective('max-age', 10); + $this->assertTrue($bag->hasCacheControlDirective('max-age')); + $this->assertEquals(10, $bag->getCacheControlDirective('max-age')); + $this->assertEquals('max-age=10, public', $bag->get('cache-control')); + + $bag->removeCacheControlDirective('max-age'); + $this->assertFalse($bag->hasCacheControlDirective('max-age')); + } + + public function testCacheControlDirectiveParsing() + { + $bag = new HeaderBag(array('cache-control' => 'public, max-age=10')); + $this->assertTrue($bag->hasCacheControlDirective('public')); + $this->assertTrue($bag->getCacheControlDirective('public')); + + $this->assertTrue($bag->hasCacheControlDirective('max-age')); + $this->assertEquals(10, $bag->getCacheControlDirective('max-age')); + + $bag->addCacheControlDirective('s-maxage', 100); + $this->assertEquals('max-age=10, public, s-maxage=100', $bag->get('cache-control')); + } + + public function testCacheControlDirectiveParsingQuotedZero() + { + $bag = new HeaderBag(array('cache-control' => 'max-age="0"')); + $this->assertTrue($bag->hasCacheControlDirective('max-age')); + $this->assertEquals(0, $bag->getCacheControlDirective('max-age')); + } + + public function testCacheControlDirectiveOverrideWithReplace() + { + $bag = new HeaderBag(array('cache-control' => 'private, max-age=100')); + $bag->replace(array('cache-control' => 'public, max-age=10')); + $this->assertTrue($bag->hasCacheControlDirective('public')); + $this->assertTrue($bag->getCacheControlDirective('public')); + + $this->assertTrue($bag->hasCacheControlDirective('max-age')); + $this->assertEquals(10, $bag->getCacheControlDirective('max-age')); + } + + /** + * @covers Symfony\Component\HttpFoundation\HeaderBag::getIterator + */ + public function testGetIterator() + { + $headers = array('foo' => 'bar', 'hello' => 'world', 'third' => 'charm'); + $headerBag = new HeaderBag($headers); + + $i = 0; + foreach ($headerBag as $key => $val) { + $i++; + $this->assertEquals(array($headers[$key]), $val); + } + + $this->assertEquals(count($headers), $i); + } + + /** + * @covers Symfony\Component\HttpFoundation\HeaderBag::count + */ + public function testCount() + { + $headers = array('foo' => 'bar', 'HELLO' => 'WORLD'); + $headerBag = new HeaderBag($headers); + + $this->assertEquals(count($headers), count($headerBag)); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php new file mode 100644 index 0000000..726ba6a --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/IpUtilsTest.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\IpUtils; + +class IpUtilsTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider testIpv4Provider + */ + public function testIpv4($matches, $remoteAddr, $cidr) + { + $this->assertSame($matches, IpUtils::checkIp($remoteAddr, $cidr)); + } + + public function testIpv4Provider() + { + return array( + array(true, '192.168.1.1', '192.168.1.1'), + array(true, '192.168.1.1', '192.168.1.1/1'), + array(true, '192.168.1.1', '192.168.1.0/24'), + array(false, '192.168.1.1', '1.2.3.4/1'), + array(false, '192.168.1.1', '192.168.1/33'), + array(true, '192.168.1.1', array('1.2.3.4/1', '192.168.1.0/24')), + array(true, '192.168.1.1', array('192.168.1.0/24', '1.2.3.4/1')), + array(false, '192.168.1.1', array('1.2.3.4/1', '4.3.2.1/1')), + ); + } + + /** + * @dataProvider testIpv6Provider + */ + public function testIpv6($matches, $remoteAddr, $cidr) + { + if (!defined('AF_INET6')) { + $this->markTestSkipped('Only works when PHP is compiled without the option "disable-ipv6".'); + } + + $this->assertSame($matches, IpUtils::checkIp($remoteAddr, $cidr)); + } + + public function testIpv6Provider() + { + return array( + array(true, '2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'), + array(false, '2a00:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'), + array(false, '2a01:198:603:0:396e:4789:8e99:890f', '::1'), + array(true, '0:0:0:0:0:0:0:1', '::1'), + array(false, '0:0:603:0:396e:4789:8e99:0001', '::1'), + array(true, '2a01:198:603:0:396e:4789:8e99:890f', array('::1', '2a01:198:603:0::/65')), + array(true, '2a01:198:603:0:396e:4789:8e99:890f', array('2a01:198:603:0::/65', '::1')), + array(false, '2a01:198:603:0:396e:4789:8e99:890f', array('::1', '1a01:198:603:0::/65')), + ); + } + + /** + * @expectedException \RuntimeException + */ + public function testAnIpv6WithOptionDisabledIpv6() + { + if (!extension_loaded('sockets')) { + $this->markTestSkipped('Only works when the socket extension is enabled'); + } + + if (defined('AF_INET6')) { + $this->markTestSkipped('Only works when PHP is compiled with the option "disable-ipv6".'); + } + + IpUtils::checkIp('2a01:198:603:0:396e:4789:8e99:890f', '2a01:198:603:0::/65'); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php new file mode 100644 index 0000000..8f1383f --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\JsonResponse; + +class JsonResponseTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructorEmptyCreatesJsonObject() + { + $response = new JsonResponse(); + $this->assertSame('{}', $response->getContent()); + } + + public function testConstructorWithArrayCreatesJsonArray() + { + $response = new JsonResponse(array(0, 1, 2, 3)); + $this->assertSame('[0,1,2,3]', $response->getContent()); + } + + public function testConstructorWithAssocArrayCreatesJsonObject() + { + $response = new JsonResponse(array('foo' => 'bar')); + $this->assertSame('{"foo":"bar"}', $response->getContent()); + } + + public function testConstructorWithSimpleTypes() + { + $response = new JsonResponse('foo'); + $this->assertSame('"foo"', $response->getContent()); + + $response = new JsonResponse(0); + $this->assertSame('0', $response->getContent()); + + $response = new JsonResponse(0.1); + $this->assertSame('0.1', $response->getContent()); + + $response = new JsonResponse(true); + $this->assertSame('true', $response->getContent()); + } + + public function testConstructorWithCustomStatus() + { + $response = new JsonResponse(array(), 202); + $this->assertSame(202, $response->getStatusCode()); + } + + public function testConstructorAddsContentTypeHeader() + { + $response = new JsonResponse(); + $this->assertSame('application/json', $response->headers->get('Content-Type')); + } + + public function testConstructorWithCustomHeaders() + { + $response = new JsonResponse(array(), 200, array('ETag' => 'foo')); + $this->assertSame('application/json', $response->headers->get('Content-Type')); + $this->assertSame('foo', $response->headers->get('ETag')); + } + + public function testConstructorWithCustomContentType() + { + $headers = array('Content-Type' => 'application/vnd.acme.blog-v1+json'); + + $response = new JsonResponse(array(), 200, $headers); + $this->assertSame('application/vnd.acme.blog-v1+json', $response->headers->get('Content-Type')); + } + + public function testCreate() + { + $response = JsonResponse::create(array('foo' => 'bar'), 204); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertEquals('{"foo":"bar"}', $response->getContent()); + $this->assertEquals(204, $response->getStatusCode()); + } + + public function testStaticCreateEmptyJsonObject() + { + $response = JsonResponse::create(); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('{}', $response->getContent()); + } + + public function testStaticCreateJsonArray() + { + $response = JsonResponse::create(array(0, 1, 2, 3)); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('[0,1,2,3]', $response->getContent()); + } + + public function testStaticCreateJsonObject() + { + $response = JsonResponse::create(array('foo' => 'bar')); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('{"foo":"bar"}', $response->getContent()); + } + + public function testStaticCreateWithSimpleTypes() + { + $response = JsonResponse::create('foo'); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('"foo"', $response->getContent()); + + $response = JsonResponse::create(0); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('0', $response->getContent()); + + $response = JsonResponse::create(0.1); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('0.1', $response->getContent()); + + $response = JsonResponse::create(true); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\JsonResponse', $response); + $this->assertSame('true', $response->getContent()); + } + + public function testStaticCreateWithCustomStatus() + { + $response = JsonResponse::create(array(), 202); + $this->assertSame(202, $response->getStatusCode()); + } + + public function testStaticCreateAddsContentTypeHeader() + { + $response = JsonResponse::create(); + $this->assertSame('application/json', $response->headers->get('Content-Type')); + } + + public function testStaticCreateWithCustomHeaders() + { + $response = JsonResponse::create(array(), 200, array('ETag' => 'foo')); + $this->assertSame('application/json', $response->headers->get('Content-Type')); + $this->assertSame('foo', $response->headers->get('ETag')); + } + + public function testStaticCreateWithCustomContentType() + { + $headers = array('Content-Type' => 'application/vnd.acme.blog-v1+json'); + + $response = JsonResponse::create(array(), 200, $headers); + $this->assertSame('application/vnd.acme.blog-v1+json', $response->headers->get('Content-Type')); + } + + public function testSetCallback() + { + $response = JsonResponse::create(array('foo' => 'bar'))->setCallback('callback'); + + $this->assertEquals('callback({"foo":"bar"});', $response->getContent()); + $this->assertEquals('text/javascript', $response->headers->get('Content-Type')); + } + + public function testSetCallbackInvalidIdentifier() + { + $response = new JsonResponse('foo'); + + $this->setExpectedException('InvalidArgumentException'); + $response->setCallback('+invalid'); + } + + public function testJsonEncodeFlags() + { + $response = new JsonResponse('<>\'&"'); + + $this->assertEquals('"\u003C\u003E\u0027\u0026\u0022"', $response->getContent()); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php new file mode 100644 index 0000000..6cd42bf --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ParameterBagTest.php @@ -0,0 +1,254 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\ParameterBag; + +class ParameterBagTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Symfony\Component\HttpFoundation\ParameterBag::__construct + */ + public function testConstructor() + { + $this->testAll(); + } + + /** + * @covers Symfony\Component\HttpFoundation\ParameterBag::all + */ + public function testAll() + { + $bag = new ParameterBag(array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $bag->all(), '->all() gets all the input'); + } + + public function testKeys() + { + $bag = new ParameterBag(array('foo' => 'bar')); + $this->assertEquals(array('foo'), $bag->keys()); + } + + public function testAdd() + { + $bag = new ParameterBag(array('foo' => 'bar')); + $bag->add(array('bar' => 'bas')); + $this->assertEquals(array('foo' => 'bar', 'bar' => 'bas'), $bag->all()); + } + + public function testRemove() + { + $bag = new ParameterBag(array('foo' => 'bar')); + $bag->add(array('bar' => 'bas')); + $this->assertEquals(array('foo' => 'bar', 'bar' => 'bas'), $bag->all()); + $bag->remove('bar'); + $this->assertEquals(array('foo' => 'bar'), $bag->all()); + } + + /** + * @covers Symfony\Component\HttpFoundation\ParameterBag::replace + */ + public function testReplace() + { + $bag = new ParameterBag(array('foo' => 'bar')); + + $bag->replace(array('FOO' => 'BAR')); + $this->assertEquals(array('FOO' => 'BAR'), $bag->all(), '->replace() replaces the input with the argument'); + $this->assertFalse($bag->has('foo'), '->replace() overrides previously set the input'); + } + + /** + * @covers Symfony\Component\HttpFoundation\ParameterBag::get + */ + public function testGet() + { + $bag = new ParameterBag(array('foo' => 'bar', 'null' => null)); + + $this->assertEquals('bar', $bag->get('foo'), '->get() gets the value of a parameter'); + $this->assertEquals('default', $bag->get('unknown', 'default'), '->get() returns second argument as default if a parameter is not defined'); + $this->assertNull($bag->get('null', 'default'), '->get() returns null if null is set'); + } + + public function testGetDoesNotUseDeepByDefault() + { + $bag = new ParameterBag(array('foo' => array('bar' => 'moo'))); + + $this->assertNull($bag->get('foo[bar]')); + } + + /** + * @dataProvider getInvalidPaths + * @expectedException \InvalidArgumentException + */ + public function testGetDeepWithInvalidPaths($path) + { + $bag = new ParameterBag(array('foo' => array('bar' => 'moo'))); + + $bag->get($path, null, true); + } + + public function getInvalidPaths() + { + return array( + array('foo[['), + array('foo[d'), + array('foo[bar]]'), + array('foo[bar]d'), + ); + } + + public function testGetDeep() + { + $bag = new ParameterBag(array('foo' => array('bar' => array('moo' => 'boo')))); + + $this->assertEquals(array('moo' => 'boo'), $bag->get('foo[bar]', null, true)); + $this->assertEquals('boo', $bag->get('foo[bar][moo]', null, true)); + $this->assertEquals('default', $bag->get('foo[bar][foo]', 'default', true)); + $this->assertEquals('default', $bag->get('bar[moo][foo]', 'default', true)); + } + + /** + * @covers Symfony\Component\HttpFoundation\ParameterBag::set + */ + public function testSet() + { + $bag = new ParameterBag(array()); + + $bag->set('foo', 'bar'); + $this->assertEquals('bar', $bag->get('foo'), '->set() sets the value of parameter'); + + $bag->set('foo', 'baz'); + $this->assertEquals('baz', $bag->get('foo'), '->set() overrides previously set parameter'); + } + + /** + * @covers Symfony\Component\HttpFoundation\ParameterBag::has + */ + public function testHas() + { + $bag = new ParameterBag(array('foo' => 'bar')); + + $this->assertTrue($bag->has('foo'), '->has() returns true if a parameter is defined'); + $this->assertFalse($bag->has('unknown'), '->has() return false if a parameter is not defined'); + } + + /** + * @covers Symfony\Component\HttpFoundation\ParameterBag::getAlpha + */ + public function testGetAlpha() + { + $bag = new ParameterBag(array('word' => 'foo_BAR_012')); + + $this->assertEquals('fooBAR', $bag->getAlpha('word'), '->getAlpha() gets only alphabetic characters'); + $this->assertEquals('', $bag->getAlpha('unknown'), '->getAlpha() returns empty string if a parameter is not defined'); + } + + /** + * @covers Symfony\Component\HttpFoundation\ParameterBag::getAlnum + */ + public function testGetAlnum() + { + $bag = new ParameterBag(array('word' => 'foo_BAR_012')); + + $this->assertEquals('fooBAR012', $bag->getAlnum('word'), '->getAlnum() gets only alphanumeric characters'); + $this->assertEquals('', $bag->getAlnum('unknown'), '->getAlnum() returns empty string if a parameter is not defined'); + } + + /** + * @covers Symfony\Component\HttpFoundation\ParameterBag::getDigits + */ + public function testGetDigits() + { + $bag = new ParameterBag(array('word' => 'foo_BAR_012')); + + $this->assertEquals('012', $bag->getDigits('word'), '->getDigits() gets only digits as string'); + $this->assertEquals('', $bag->getDigits('unknown'), '->getDigits() returns empty string if a parameter is not defined'); + } + + /** + * @covers Symfony\Component\HttpFoundation\ParameterBag::getInt + */ + public function testGetInt() + { + $bag = new ParameterBag(array('digits' => '0123')); + + $this->assertEquals(123, $bag->getInt('digits'), '->getInt() gets a value of parameter as integer'); + $this->assertEquals(0, $bag->getInt('unknown'), '->getInt() returns zero if a parameter is not defined'); + } + + /** + * @covers Symfony\Component\HttpFoundation\ParameterBag::filter + */ + public function testFilter() + { + $bag = new ParameterBag(array( + 'digits' => '0123ab', + 'email' => 'example@example.com', + 'url' => 'http://example.com/foo', + 'dec' => '256', + 'hex' => '0x100', + 'array' => array('bang'), + )); + + $this->assertEmpty($bag->filter('nokey'), '->filter() should return empty by default if no key is found'); + + $this->assertEquals('0123', $bag->filter('digits', '', false, FILTER_SANITIZE_NUMBER_INT), '->filter() gets a value of parameter as integer filtering out invalid characters'); + + $this->assertEquals('example@example.com', $bag->filter('email', '', false, FILTER_VALIDATE_EMAIL), '->filter() gets a value of parameter as email'); + + $this->assertEquals('http://example.com/foo', $bag->filter('url', '', false, FILTER_VALIDATE_URL, array('flags' => FILTER_FLAG_PATH_REQUIRED)), '->filter() gets a value of parameter as url with a path'); + + // This test is repeated for code-coverage + $this->assertEquals('http://example.com/foo', $bag->filter('url', '', false, FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED), '->filter() gets a value of parameter as url with a path'); + + $this->assertFalse($bag->filter('dec', '', false, FILTER_VALIDATE_INT, array( + 'flags' => FILTER_FLAG_ALLOW_HEX, + 'options' => array('min_range' => 1, 'max_range' => 0xff)) + ), '->filter() gets a value of parameter as integer between boundaries'); + + $this->assertFalse($bag->filter('hex', '', false, FILTER_VALIDATE_INT, array( + 'flags' => FILTER_FLAG_ALLOW_HEX, + 'options' => array('min_range' => 1, 'max_range' => 0xff)) + ), '->filter() gets a value of parameter as integer between boundaries'); + + $this->assertEquals(array('bang'), $bag->filter('array', '', false), '->filter() gets a value of parameter as an array'); + + } + + /** + * @covers Symfony\Component\HttpFoundation\ParameterBag::getIterator + */ + public function testGetIterator() + { + $parameters = array('foo' => 'bar', 'hello' => 'world'); + $bag = new ParameterBag($parameters); + + $i = 0; + foreach ($bag as $key => $val) { + $i++; + $this->assertEquals($parameters[$key], $val); + } + + $this->assertEquals(count($parameters), $i); + } + + /** + * @covers Symfony\Component\HttpFoundation\ParameterBag::count + */ + public function testCount() + { + $parameters = array('foo' => 'bar', 'hello' => 'world'); + $bag = new ParameterBag($parameters); + + $this->assertEquals(count($parameters), count($bag)); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/RedirectResponseTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/RedirectResponseTest.php new file mode 100644 index 0000000..330d9fe --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/RedirectResponseTest.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use \Symfony\Component\HttpFoundation\RedirectResponse; + +class RedirectResponseTest extends \PHPUnit_Framework_TestCase +{ + public function testGenerateMetaRedirect() + { + $response = new RedirectResponse('foo.bar'); + + $this->assertEquals(1, preg_match( + '##', + preg_replace(array('/\s+/', '/\'/'), array(' ', '"'), $response->getContent()) + )); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRedirectResponseConstructorNullUrl() + { + $response = new RedirectResponse(null); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRedirectResponseConstructorWrongStatusCode() + { + $response = new RedirectResponse('foo.bar', 404); + } + + public function testGenerateLocationHeader() + { + $response = new RedirectResponse('foo.bar'); + + $this->assertTrue($response->headers->has('Location')); + $this->assertEquals('foo.bar', $response->headers->get('Location')); + } + + public function testGetTargetUrl() + { + $response = new RedirectResponse('foo.bar'); + + $this->assertEquals('foo.bar', $response->getTargetUrl()); + } + + public function testSetTargetUrl() + { + $response = new RedirectResponse('foo.bar'); + $response->setTargetUrl('baz.beep'); + + $this->assertEquals('baz.beep', $response->getTargetUrl()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSetTargetUrlNull() + { + $response = new RedirectResponse('foo.bar'); + $response->setTargetUrl(null); + } + + public function testCreate() + { + $response = RedirectResponse::create('foo', 301); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\RedirectResponse', $response); + $this->assertEquals(301, $response->getStatusCode()); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php new file mode 100644 index 0000000..0e1a0f5 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/RequestMatcherTest.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\RequestMatcher; +use Symfony\Component\HttpFoundation\Request; + +class RequestMatcherTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider testMethodFixtures + */ + public function testMethod($requestMethod, $matcherMethod, $isMatch) + { + $matcher = new RequestMatcher(); + $matcher->matchMethod($matcherMethod); + $request = Request::create('', $requestMethod); + $this->assertSame($isMatch, $matcher->matches($request)); + + $matcher = new RequestMatcher(null, null, $matcherMethod); + $request = Request::create('', $requestMethod); + $this->assertSame($isMatch, $matcher->matches($request)); + } + + public function testMethodFixtures() + { + return array( + array('get', 'get', true), + array('get', array('get', 'post'), true), + array('get', 'post', false), + array('get', 'GET', true), + array('get', array('GET', 'POST'), true), + array('get', 'POST', false), + ); + } + + /** + * @dataProvider testHostFixture + */ + public function testHost($pattern, $isMatch) + { + $matcher = new RequestMatcher(); + $request = Request::create('', 'get', array(), array(), array(), array('HTTP_HOST' => 'foo.example.com')); + + $matcher->matchHost($pattern); + $this->assertSame($isMatch, $matcher->matches($request)); + + $matcher= new RequestMatcher(null, $pattern); + $this->assertSame($isMatch, $matcher->matches($request)); + } + + public function testHostFixture() + { + return array( + array('.*\.example\.com', true), + array('\.example\.com$', true), + array('^.*\.example\.com$', true), + array('.*\.sensio\.com', false), + array('.*\.example\.COM', true), + array('\.example\.COM$', true), + array('^.*\.example\.COM$', true), + array('.*\.sensio\.COM', false), ); + } + + public function testPath() + { + $matcher = new RequestMatcher(); + + $request = Request::create('/admin/foo'); + + $matcher->matchPath('/admin/.*'); + $this->assertTrue($matcher->matches($request)); + + $matcher->matchPath('/admin'); + $this->assertTrue($matcher->matches($request)); + + $matcher->matchPath('^/admin/.*$'); + $this->assertTrue($matcher->matches($request)); + + $matcher->matchMethod('/blog/.*'); + $this->assertFalse($matcher->matches($request)); + } + + public function testPathWithLocaleIsNotSupported() + { + $matcher = new RequestMatcher(); + $request = Request::create('/en/login'); + $request->setLocale('en'); + + $matcher->matchPath('^/{_locale}/login$'); + $this->assertFalse($matcher->matches($request)); + } + + public function testPathWithEncodedCharacters() + { + $matcher = new RequestMatcher(); + $request = Request::create('/admin/fo%20o'); + $matcher->matchPath('^/admin/fo o*$'); + $this->assertTrue($matcher->matches($request)); + } + + public function testAttributes() + { + $matcher = new RequestMatcher(); + + $request = Request::create('/admin/foo'); + $request->attributes->set('foo', 'foo_bar'); + + $matcher->matchAttribute('foo', 'foo_.*'); + $this->assertTrue($matcher->matches($request)); + + $matcher->matchAttribute('foo', 'foo'); + $this->assertTrue($matcher->matches($request)); + + $matcher->matchAttribute('foo', '^foo_bar$'); + $this->assertTrue($matcher->matches($request)); + + $matcher->matchAttribute('foo', 'babar'); + $this->assertFalse($matcher->matches($request)); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/RequestTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/RequestTest.php new file mode 100644 index 0000000..ba95c4a --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/RequestTest.php @@ -0,0 +1,1543 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Request; + +class RequestTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Symfony\Component\HttpFoundation\Request::__construct + */ + public function testConstructor() + { + $this->testInitialize(); + } + + /** + * @covers Symfony\Component\HttpFoundation\Request::initialize + */ + public function testInitialize() + { + $request = new Request(); + + $request->initialize(array('foo' => 'bar')); + $this->assertEquals('bar', $request->query->get('foo'), '->initialize() takes an array of query parameters as its first argument'); + + $request->initialize(array(), array('foo' => 'bar')); + $this->assertEquals('bar', $request->request->get('foo'), '->initialize() takes an array of request parameters as its second argument'); + + $request->initialize(array(), array(), array('foo' => 'bar')); + $this->assertEquals('bar', $request->attributes->get('foo'), '->initialize() takes an array of attributes as its third argument'); + + $request->initialize(array(), array(), array(), array(), array(), array('HTTP_FOO' => 'bar')); + $this->assertEquals('bar', $request->headers->get('FOO'), '->initialize() takes an array of HTTP headers as its fourth argument'); + } + + public function testGetLocale() + { + $request = new Request(); + $request->setLocale('pl'); + $locale = $request->getLocale(); + $this->assertEquals('pl', $locale); + } + + public function testGetUser() + { + $request = Request::create('http://user_test:password_test@test.com/'); + $user = $request->getUser(); + + $this->assertEquals('user_test', $user); + } + + public function testGetPassword() + { + $request = Request::create('http://user_test:password_test@test.com/'); + $password = $request->getPassword(); + + $this->assertEquals('password_test', $password); + } + + public function testIsNoCache() + { + $request = new Request(); + $isNoCache = $request->isNoCache(); + + $this->assertFalse($isNoCache); + } + + public function testGetContentType() + { + $request = new Request(); + $contentType = $request->getContentType(); + + $this->assertNull($contentType); + } + + public function testSetDefaultLocale() + { + $request = new Request(); + $request->setDefaultLocale('pl'); + $locale = $request->getLocale(); + + $this->assertEquals('pl', $locale); + } + + /** + * @covers Symfony\Component\HttpFoundation\Request::create + */ + public function testCreate() + { + $request = Request::create('http://test.com/foo?bar=baz'); + $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('bar=baz', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://test.com/foo', 'GET', array('bar' => 'baz')); + $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('bar=baz', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://test.com/foo?bar=foo', 'GET', array('bar' => 'baz')); + $this->assertEquals('http://test.com/foo?bar=baz', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('bar=baz', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('https://test.com/foo?bar=baz'); + $this->assertEquals('https://test.com/foo?bar=baz', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('bar=baz', $request->getQueryString()); + $this->assertEquals(443, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertTrue($request->isSecure()); + + $request = Request::create('test.com:90/foo'); + $this->assertEquals('http://test.com:90/foo', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('test.com', $request->getHost()); + $this->assertEquals('test.com:90', $request->getHttpHost()); + $this->assertEquals(90, $request->getPort()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('https://test.com:90/foo'); + $this->assertEquals('https://test.com:90/foo', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('test.com', $request->getHost()); + $this->assertEquals('test.com:90', $request->getHttpHost()); + $this->assertEquals(90, $request->getPort()); + $this->assertTrue($request->isSecure()); + + $request = Request::create('https://127.0.0.1:90/foo'); + $this->assertEquals('https://127.0.0.1:90/foo', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('127.0.0.1', $request->getHost()); + $this->assertEquals('127.0.0.1:90', $request->getHttpHost()); + $this->assertEquals(90, $request->getPort()); + $this->assertTrue($request->isSecure()); + + $request = Request::create('https://[::1]:90/foo'); + $this->assertEquals('https://[::1]:90/foo', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('[::1]', $request->getHost()); + $this->assertEquals('[::1]:90', $request->getHttpHost()); + $this->assertEquals(90, $request->getPort()); + $this->assertTrue($request->isSecure()); + + $json = '{"jsonrpc":"2.0","method":"echo","id":7,"params":["Hello World"]}'; + $request = Request::create('http://example.com/jsonrpc', 'POST', array(), array(), array(), array(), $json); + $this->assertEquals($json, $request->getContent()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://test.com'); + $this->assertEquals('http://test.com/', $request->getUri()); + $this->assertEquals('/', $request->getPathInfo()); + $this->assertEquals('', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://test.com?test=1'); + $this->assertEquals('http://test.com/?test=1', $request->getUri()); + $this->assertEquals('/', $request->getPathInfo()); + $this->assertEquals('test=1', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://test.com:90/?test=1'); + $this->assertEquals('http://test.com:90/?test=1', $request->getUri()); + $this->assertEquals('/', $request->getPathInfo()); + $this->assertEquals('test=1', $request->getQueryString()); + $this->assertEquals(90, $request->getPort()); + $this->assertEquals('test.com:90', $request->getHttpHost()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://test:test@test.com'); + $this->assertEquals('http://test.com/', $request->getUri()); + $this->assertEquals('/', $request->getPathInfo()); + $this->assertEquals('', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertEquals('test', $request->getUser()); + $this->assertEquals('test', $request->getPassword()); + $this->assertFalse($request->isSecure()); + + $request = Request::create('http://testnopass@test.com'); + $this->assertEquals('http://test.com/', $request->getUri()); + $this->assertEquals('/', $request->getPathInfo()); + $this->assertEquals('', $request->getQueryString()); + $this->assertEquals(80, $request->getPort()); + $this->assertEquals('test.com', $request->getHttpHost()); + $this->assertEquals('testnopass', $request->getUser()); + $this->assertNull($request->getPassword()); + $this->assertFalse($request->isSecure()); + } + + /** + * @covers Symfony\Component\HttpFoundation\Request::create + */ + public function testCreateCheckPrecedence() + { + // server is used by default + $request = Request::create('/', 'DELETE', array(), array(), array(), array( + 'HTTP_HOST' => 'example.com', + 'HTTPS' => 'on', + 'SERVER_PORT' => 443, + 'PHP_AUTH_USER' => 'fabien', + 'PHP_AUTH_PW' => 'pa$$', + 'QUERY_STRING' => 'foo=bar', + 'CONTENT_TYPE' => 'application/json', + )); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(443, $request->getPort()); + $this->assertTrue($request->isSecure()); + $this->assertEquals('fabien', $request->getUser()); + $this->assertEquals('pa$$', $request->getPassword()); + $this->assertEquals('', $request->getQueryString()); + $this->assertEquals('application/json', $request->headers->get('CONTENT_TYPE')); + + // URI has precedence over server + $request = Request::create('http://thomas:pokemon@example.net:8080/?foo=bar', 'GET', array(), array(), array(), array( + 'HTTP_HOST' => 'example.com', + 'HTTPS' => 'on', + 'SERVER_PORT' => 443, + )); + $this->assertEquals('example.net', $request->getHost()); + $this->assertEquals(8080, $request->getPort()); + $this->assertFalse($request->isSecure()); + $this->assertEquals('thomas', $request->getUser()); + $this->assertEquals('pokemon', $request->getPassword()); + $this->assertEquals('foo=bar', $request->getQueryString()); + } + + /** + * @covers Symfony\Component\HttpFoundation\Request::duplicate + */ + public function testDuplicate() + { + $request = new Request(array('foo' => 'bar'), array('foo' => 'bar'), array('foo' => 'bar'), array(), array(), array('HTTP_FOO' => 'bar')); + $dup = $request->duplicate(); + + $this->assertEquals($request->query->all(), $dup->query->all(), '->duplicate() duplicates a request an copy the current query parameters'); + $this->assertEquals($request->request->all(), $dup->request->all(), '->duplicate() duplicates a request an copy the current request parameters'); + $this->assertEquals($request->attributes->all(), $dup->attributes->all(), '->duplicate() duplicates a request an copy the current attributes'); + $this->assertEquals($request->headers->all(), $dup->headers->all(), '->duplicate() duplicates a request an copy the current HTTP headers'); + + $dup = $request->duplicate(array('foo' => 'foobar'), array('foo' => 'foobar'), array('foo' => 'foobar'), array(), array(), array('HTTP_FOO' => 'foobar')); + + $this->assertEquals(array('foo' => 'foobar'), $dup->query->all(), '->duplicate() overrides the query parameters if provided'); + $this->assertEquals(array('foo' => 'foobar'), $dup->request->all(), '->duplicate() overrides the request parameters if provided'); + $this->assertEquals(array('foo' => 'foobar'), $dup->attributes->all(), '->duplicate() overrides the attributes if provided'); + $this->assertEquals(array('foo' => array('foobar')), $dup->headers->all(), '->duplicate() overrides the HTTP header if provided'); + } + + /** + * @covers Symfony\Component\HttpFoundation\Request::getFormat + * @covers Symfony\Component\HttpFoundation\Request::setFormat + * @dataProvider getFormatToMimeTypeMapProvider + */ + public function testGetFormatFromMimeType($format, $mimeTypes) + { + $request = new Request(); + foreach ($mimeTypes as $mime) { + $this->assertEquals($format, $request->getFormat($mime)); + } + $request->setFormat($format, $mimeTypes); + foreach ($mimeTypes as $mime) { + $this->assertEquals($format, $request->getFormat($mime)); + } + } + + /** + * @covers Symfony\Component\HttpFoundation\Request::getFormat + */ + public function testGetFormatFromMimeTypeWithParameters() + { + $request = new Request(); + $this->assertEquals('json', $request->getFormat('application/json; charset=utf-8')); + } + + /** + * @covers Symfony\Component\HttpFoundation\Request::getMimeType + * @dataProvider getFormatToMimeTypeMapProvider + */ + public function testGetMimeTypeFromFormat($format, $mimeTypes) + { + if (null !== $format) { + $request = new Request(); + $this->assertEquals($mimeTypes[0], $request->getMimeType($format)); + } + } + + public function getFormatToMimeTypeMapProvider() + { + return array( + array(null, array(null, 'unexistent-mime-type')), + array('txt', array('text/plain')), + array('js', array('application/javascript', 'application/x-javascript', 'text/javascript')), + array('css', array('text/css')), + array('json', array('application/json', 'application/x-json')), + array('xml', array('text/xml', 'application/xml', 'application/x-xml')), + array('rdf', array('application/rdf+xml')), + array('atom',array('application/atom+xml')), + ); + } + + /** + * @covers Symfony\Component\HttpFoundation\Request::getUri + */ + public function testGetUri() + { + $server = array(); + + // Standard Request on non default PORT + // http://host:8080/index.php/path/info?query=string + + $server['HTTP_HOST'] = 'host:8080'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '8080'; + + $server['QUERY_STRING'] = 'query=string'; + $server['REQUEST_URI'] = '/index.php/path/info?query=string'; + $server['SCRIPT_NAME'] = '/index.php'; + $server['PATH_INFO'] = '/path/info'; + $server['PATH_TRANSLATED'] = 'redirect:/index.php/path/info'; + $server['PHP_SELF'] = '/index_dev.php/path/info'; + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + + $request = new Request(); + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://host:8080/index.php/path/info?query=string', $request->getUri(), '->getUri() with non default port'); + + // Use std port number + $server['HTTP_HOST'] = 'host'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://host/index.php/path/info?query=string', $request->getUri(), '->getUri() with default port'); + + // Without HOST HEADER + unset($server['HTTP_HOST']); + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://servername/index.php/path/info?query=string', $request->getUri(), '->getUri() with default port without HOST_HEADER'); + + // Request with URL REWRITING (hide index.php) + // RewriteCond %{REQUEST_FILENAME} !-f + // RewriteRule ^(.*)$ index.php [QSA,L] + // http://host:8080/path/info?query=string + $server = array(); + $server['HTTP_HOST'] = 'host:8080'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '8080'; + + $server['REDIRECT_QUERY_STRING'] = 'query=string'; + $server['REDIRECT_URL'] = '/path/info'; + $server['SCRIPT_NAME'] = '/index.php'; + $server['QUERY_STRING'] = 'query=string'; + $server['REQUEST_URI'] = '/path/info?toto=test&1=1'; + $server['SCRIPT_NAME'] = '/index.php'; + $server['PHP_SELF'] = '/index.php'; + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://host:8080/path/info?query=string', $request->getUri(), '->getUri() with rewrite'); + + // Use std port number + // http://host/path/info?query=string + $server['HTTP_HOST'] = 'host'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://host/path/info?query=string', $request->getUri(), '->getUri() with rewrite and default port'); + + // Without HOST HEADER + unset($server['HTTP_HOST']); + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://servername/path/info?query=string', $request->getUri(), '->getUri() with rewrite, default port without HOST_HEADER'); + + // With encoded characters + + $server = array( + 'HTTP_HOST' => 'host:8080', + 'SERVER_NAME' => 'servername', + 'SERVER_PORT' => '8080', + 'QUERY_STRING' => 'query=string', + 'REQUEST_URI' => '/ba%20se/index_dev.php/foo%20bar/in+fo?query=string', + 'SCRIPT_NAME' => '/ba se/index_dev.php', + 'PATH_TRANSLATED' => 'redirect:/index.php/foo bar/in+fo', + 'PHP_SELF' => '/ba se/index_dev.php/path/info', + 'SCRIPT_FILENAME' => '/some/where/ba se/index_dev.php', + ); + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals( + 'http://host:8080/ba%20se/index_dev.php/foo%20bar/in+fo?query=string', + $request->getUri() + ); + + // with user info + + $server['PHP_AUTH_USER'] = 'fabien'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://host:8080/ba%20se/index_dev.php/foo%20bar/in+fo?query=string', $request->getUri()); + + $server['PHP_AUTH_PW'] = 'symfony'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://host:8080/ba%20se/index_dev.php/foo%20bar/in+fo?query=string', $request->getUri()); + } + + /** + * @covers Symfony\Component\HttpFoundation\Request::getUriForPath + */ + public function testGetUriForPath() + { + $request = Request::create('http://test.com/foo?bar=baz'); + $this->assertEquals('http://test.com/some/path', $request->getUriForPath('/some/path')); + + $request = Request::create('http://test.com:90/foo?bar=baz'); + $this->assertEquals('http://test.com:90/some/path', $request->getUriForPath('/some/path')); + + $request = Request::create('https://test.com/foo?bar=baz'); + $this->assertEquals('https://test.com/some/path', $request->getUriForPath('/some/path')); + + $request = Request::create('https://test.com:90/foo?bar=baz'); + $this->assertEquals('https://test.com:90/some/path', $request->getUriForPath('/some/path')); + + $server = array(); + + // Standard Request on non default PORT + // http://host:8080/index.php/path/info?query=string + + $server['HTTP_HOST'] = 'host:8080'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '8080'; + + $server['QUERY_STRING'] = 'query=string'; + $server['REQUEST_URI'] = '/index.php/path/info?query=string'; + $server['SCRIPT_NAME'] = '/index.php'; + $server['PATH_INFO'] = '/path/info'; + $server['PATH_TRANSLATED'] = 'redirect:/index.php/path/info'; + $server['PHP_SELF'] = '/index_dev.php/path/info'; + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + + $request = new Request(); + + $request->initialize(array(), array(), array(), array(), array(),$server); + + $this->assertEquals('http://host:8080/index.php/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with non default port'); + + // Use std port number + $server['HTTP_HOST'] = 'host'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://host/index.php/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with default port'); + + // Without HOST HEADER + unset($server['HTTP_HOST']); + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://servername/index.php/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with default port without HOST_HEADER'); + + // Request with URL REWRITING (hide index.php) + // RewriteCond %{REQUEST_FILENAME} !-f + // RewriteRule ^(.*)$ index.php [QSA,L] + // http://host:8080/path/info?query=string + $server = array(); + $server['HTTP_HOST'] = 'host:8080'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '8080'; + + $server['REDIRECT_QUERY_STRING'] = 'query=string'; + $server['REDIRECT_URL'] = '/path/info'; + $server['SCRIPT_NAME'] = '/index.php'; + $server['QUERY_STRING'] = 'query=string'; + $server['REQUEST_URI'] = '/path/info?toto=test&1=1'; + $server['SCRIPT_NAME'] = '/index.php'; + $server['PHP_SELF'] = '/index.php'; + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://host:8080/some/path', $request->getUriForPath('/some/path'), '->getUri() with rewrite'); + + // Use std port number + // http://host/path/info?query=string + $server['HTTP_HOST'] = 'host'; + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://host/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with rewrite and default port'); + + // Without HOST HEADER + unset($server['HTTP_HOST']); + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '80'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path'), '->getUriForPath() with rewrite, default port without HOST_HEADER'); + $this->assertEquals('servername', $request->getHttpHost()); + + // with user info + + $server['PHP_AUTH_USER'] = 'fabien'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path')); + + $server['PHP_AUTH_PW'] = 'symfony'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path')); + } + + /** + * @covers Symfony\Component\HttpFoundation\Request::getUserInfo + */ + public function testGetUserInfo() + { + $request = new Request(); + + $server['PHP_AUTH_USER'] = 'fabien'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('fabien', $request->getUserInfo()); + + $server['PHP_AUTH_USER'] = '0'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('0', $request->getUserInfo()); + + $server['PHP_AUTH_PW'] = '0'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('0:0', $request->getUserInfo()); + } + + /** + * @covers Symfony\Component\HttpFoundation\Request::getSchemeAndHttpHost + */ + public function testGetSchemeAndHttpHost() + { + $request = new Request(); + + $server['SERVER_NAME'] = 'servername'; + $server['SERVER_PORT'] = '90'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost()); + + $server['PHP_AUTH_USER'] = 'fabien'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost()); + + $server['PHP_AUTH_USER'] = '0'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost()); + + $server['PHP_AUTH_PW'] = '0'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('http://servername:90', $request->getSchemeAndHttpHost()); + } + + /** + * @covers Symfony\Component\HttpFoundation\Request::getQueryString + * @covers Symfony\Component\HttpFoundation\Request::normalizeQueryString + * @dataProvider getQueryStringNormalizationData + */ + public function testGetQueryString($query, $expectedQuery, $msg) + { + $request = new Request(); + + $request->server->set('QUERY_STRING', $query); + $this->assertSame($expectedQuery, $request->getQueryString(), $msg); + } + + public function getQueryStringNormalizationData() + { + return array( + array('foo', 'foo', 'works with valueless parameters'), + array('foo=', 'foo=', 'includes a dangling equal sign'), + array('bar=&foo=bar', 'bar=&foo=bar', '->works with empty parameters'), + array('foo=bar&bar=', 'bar=&foo=bar', 'sorts keys alphabetically'), + + // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded). + // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str. + array('him=John%20Doe&her=Jane+Doe', 'her=Jane%20Doe&him=John%20Doe', 'normalizes spaces in both encodings "%20" and "+"'), + + array('foo[]=1&foo[]=2', 'foo%5B%5D=1&foo%5B%5D=2', 'allows array notation'), + array('foo=1&foo=2', 'foo=1&foo=2', 'allows repeated parameters'), + array('pa%3Dram=foo%26bar%3Dbaz&test=test', 'pa%3Dram=foo%26bar%3Dbaz&test=test', 'works with encoded delimiters'), + array('0', '0', 'allows "0"'), + array('Jane Doe&John%20Doe', 'Jane%20Doe&John%20Doe', 'normalizes encoding in keys'), + array('her=Jane Doe&him=John%20Doe', 'her=Jane%20Doe&him=John%20Doe', 'normalizes encoding in values'), + array('foo=bar&&&test&&', 'foo=bar&test', 'removes unneeded delimiters'), + array('formula=e=m*c^2', 'formula=e%3Dm%2Ac%5E2', 'correctly treats only the first "=" as delimiter and the next as value'), + + // Ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway. + // PHP also does not include them when building _GET. + array('foo=bar&=a=b&=x=y', 'foo=bar', 'removes params with empty key'), + ); + } + + public function testGetQueryStringReturnsNull() + { + $request = new Request(); + + $this->assertNull($request->getQueryString(), '->getQueryString() returns null for non-existent query string'); + + $request->server->set('QUERY_STRING', ''); + $this->assertNull($request->getQueryString(), '->getQueryString() returns null for empty query string'); + } + + public function testGetHost() + { + $request = new Request(); + + $request->initialize(array('foo' => 'bar')); + $this->assertEquals('', $request->getHost(), '->getHost() return empty string if not initialized'); + + $request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.exemple.com')); + $this->assertEquals('www.exemple.com', $request->getHost(), '->getHost() from Host Header'); + + // Host header with port number + $request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.exemple.com:8080')); + $this->assertEquals('www.exemple.com', $request->getHost(), '->getHost() from Host Header with port number'); + + // Server values + $request->initialize(array(), array(), array(), array(), array(), array('SERVER_NAME' => 'www.exemple.com')); + $this->assertEquals('www.exemple.com', $request->getHost(), '->getHost() from server name'); + + $request->initialize(array(), array(), array(), array(), array(), array('SERVER_NAME' => 'www.exemple.com', 'HTTP_HOST' => 'www.host.com')); + $this->assertEquals('www.host.com', $request->getHost(), '->getHost() value from Host header has priority over SERVER_NAME '); + } + + public function testGetPort() + { + $request = Request::create('http://example.com', 'GET', array(), array(), array(), array( + 'HTTP_X_FORWARDED_PROTO' => 'https', + 'HTTP_X_FORWARDED_PORT' => '443' + )); + $port = $request->getPort(); + + $this->assertEquals(80, $port, 'Without trusted proxies FORWARDED_PROTO and FORWARDED_PORT are ignored.'); + + Request::setTrustedProxies(array('1.1.1.1')); + $request = Request::create('http://example.com', 'GET', array(), array(), array(), array( + 'HTTP_X_FORWARDED_PROTO' => 'https', + 'HTTP_X_FORWARDED_PORT' => '8443' + )); + $port = $request->getPort(); + + $this->assertEquals(8443, $port, 'With PROTO and PORT set PORT takes precedence.'); + + $request = Request::create('http://example.com', 'GET', array(), array(), array(), array( + 'HTTP_X_FORWARDED_PROTO' => 'https' + )); + $port = $request->getPort(); + + $this->assertEquals(443, $port, 'With only PROTO set getPort() defaults to 443.'); + + $request = Request::create('http://example.com', 'GET', array(), array(), array(), array( + 'HTTP_X_FORWARDED_PROTO' => 'http' + )); + $port = $request->getPort(); + + $this->assertEquals(80, $port, 'If X_FORWARDED_PROTO is set to http return 80.'); + Request::setTrustedProxies(array()); + } + + /** + * @expectedException RuntimeException + */ + public function testGetHostWithFakeHttpHostValue() + { + $request = new Request(); + $request->initialize(array(), array(), array(), array(), array(), array('HTTP_HOST' => 'www.host.com?query=string')); + $request->getHost(); + } + + /** + * @covers Symfony\Component\HttpFoundation\Request::setMethod + * @covers Symfony\Component\HttpFoundation\Request::getMethod + */ + public function testGetSetMethod() + { + $request = new Request(); + + $this->assertEquals('GET', $request->getMethod(), '->getMethod() returns GET if no method is defined'); + + $request->setMethod('get'); + $this->assertEquals('GET', $request->getMethod(), '->getMethod() returns an uppercased string'); + + $request->setMethod('PURGE'); + $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method even if it is not a standard one'); + + $request->setMethod('POST'); + $this->assertEquals('POST', $request->getMethod(), '->getMethod() returns the method POST if no _method is defined'); + + $request->setMethod('POST'); + $request->request->set('_method', 'purge'); + $this->assertEquals('POST', $request->getMethod(), '->getMethod() does not return the method from _method if defined and POST but support not enabled'); + + $request = new Request(); + $request->setMethod('POST'); + $request->request->set('_method', 'purge'); + + $this->assertFalse(Request::getHttpMethodParameterOverride(), 'httpMethodParameterOverride should be disabled by default'); + + Request::enableHttpMethodParameterOverride(); + + $this->assertTrue(Request::getHttpMethodParameterOverride(), 'httpMethodParameterOverride should be enabled now but it is not'); + + $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method from _method if defined and POST'); + $this->disableHttpMethodParameterOverride(); + + $request = new Request(); + $request->setMethod('POST'); + $request->query->set('_method', 'purge'); + $this->assertEquals('POST', $request->getMethod(), '->getMethod() does not return the method from _method if defined and POST but support not enabled'); + + $request = new Request(); + $request->setMethod('POST'); + $request->query->set('_method', 'purge'); + Request::enableHttpMethodParameterOverride(); + $this->assertEquals('PURGE', $request->getMethod(), '->getMethod() returns the method from _method if defined and POST'); + $this->disableHttpMethodParameterOverride(); + + $request = new Request(); + $request->setMethod('POST'); + $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'delete'); + $this->assertEquals('DELETE', $request->getMethod(), '->getMethod() returns the method from X-HTTP-Method-Override even though _method is set if defined and POST'); + + $request = new Request(); + $request->setMethod('POST'); + $request->headers->set('X-HTTP-METHOD-OVERRIDE', 'delete'); + $this->assertEquals('DELETE', $request->getMethod(), '->getMethod() returns the method from X-HTTP-Method-Override if defined and POST'); + } + + /** + * @dataProvider testGetClientIpsProvider + */ + public function testGetClientIp($expected, $remoteAddr, $httpForwardedFor, $trustedProxies) + { + $request = $this->getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies); + + $this->assertEquals($expected[0], $request->getClientIp()); + + Request::setTrustedProxies(array()); + } + + /** + * @dataProvider testGetClientIpsProvider + */ + public function testGetClientIps($expected, $remoteAddr, $httpForwardedFor, $trustedProxies) + { + $request = $this->getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies); + + $this->assertEquals($expected, $request->getClientIps()); + + Request::setTrustedProxies(array()); + } + + public function testGetClientIpsProvider() + { + // $expected $remoteAddr $httpForwardedFor $trustedProxies + return array( + // simple IPv4 + array(array('88.88.88.88'), '88.88.88.88', null, null), + // trust the IPv4 remote addr + array(array('88.88.88.88'), '88.88.88.88', null, array('88.88.88.88')), + + // simple IPv6 + array(array('::1'), '::1', null, null), + // trust the IPv6 remote addr + array(array('::1'), '::1', null, array('::1')), + + // forwarded for with remote IPv4 addr not trusted + array(array('127.0.0.1'), '127.0.0.1', '88.88.88.88', null), + // forwarded for with remote IPv4 addr trusted + array(array('88.88.88.88'), '127.0.0.1', '88.88.88.88', array('127.0.0.1')), + // forwarded for with remote IPv4 and all FF addrs trusted + array(array('88.88.88.88'), '127.0.0.1', '88.88.88.88', array('127.0.0.1', '88.88.88.88')), + // forwarded for with remote IPv4 range trusted + array(array('88.88.88.88'), '123.45.67.89', '88.88.88.88', array('123.45.67.0/24')), + + // forwarded for with remote IPv6 addr not trusted + array(array('1620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '2620:0:1cfe:face:b00c::3', null), + // forwarded for with remote IPv6 addr trusted + array(array('2620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3')), + // forwarded for with remote IPv6 range trusted + array(array('88.88.88.88'), '2a01:198:603:0:396e:4789:8e99:890f', '88.88.88.88', array('2a01:198:603:0::/65')), + + // multiple forwarded for with remote IPv4 addr trusted + array(array('88.88.88.88', '87.65.43.21', '127.0.0.1'), '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89')), + // multiple forwarded for with remote IPv4 addr and some reverse proxies trusted + array(array('87.65.43.21', '127.0.0.1'), '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89', '88.88.88.88')), + // multiple forwarded for with remote IPv4 addr and some reverse proxies trusted but in the middle + array(array('88.88.88.88', '127.0.0.1'), '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89', '87.65.43.21')), + // multiple forwarded for with remote IPv4 addr and all reverse proxies trusted + array(array('127.0.0.1'), '123.45.67.89', '127.0.0.1, 87.65.43.21, 88.88.88.88', array('123.45.67.89', '87.65.43.21', '88.88.88.88', '127.0.0.1')), + + // multiple forwarded for with remote IPv6 addr trusted + array(array('2620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3,2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3')), + // multiple forwarded for with remote IPv6 addr and some reverse proxies trusted + array(array('3620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3,2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3', '2620:0:1cfe:face:b00c::3')), + // multiple forwarded for with remote IPv4 addr and some reverse proxies trusted but in the middle + array(array('2620:0:1cfe:face:b00c::3', '4620:0:1cfe:face:b00c::3'), '1620:0:1cfe:face:b00c::3', '4620:0:1cfe:face:b00c::3,3620:0:1cfe:face:b00c::3,2620:0:1cfe:face:b00c::3', array('1620:0:1cfe:face:b00c::3', '3620:0:1cfe:face:b00c::3')), + ); + } + + public function testGetContentWorksTwiceInDefaultMode() + { + $req = new Request; + $this->assertEquals('', $req->getContent()); + $this->assertEquals('', $req->getContent()); + } + + public function testGetContentReturnsResource() + { + $req = new Request; + $retval = $req->getContent(true); + $this->assertInternalType('resource', $retval); + $this->assertEquals("", fread($retval, 1)); + $this->assertTrue(feof($retval)); + } + + /** + * @expectedException LogicException + * @dataProvider getContentCantBeCalledTwiceWithResourcesProvider + */ + public function testGetContentCantBeCalledTwiceWithResources($first, $second) + { + $req = new Request; + $req->getContent($first); + $req->getContent($second); + } + + public function getContentCantBeCalledTwiceWithResourcesProvider() + { + return array( + 'Resource then fetch' => array(true, false), + 'Resource then resource' => array(true, true), + 'Fetch then resource' => array(false, true), + ); + } + + public function provideOverloadedMethods() + { + return array( + array('PUT'), + array('DELETE'), + array('PATCH'), + array('put'), + array('delete'), + array('patch'), + + ); + } + + /** + * @dataProvider provideOverloadedMethods + */ + public function testCreateFromGlobals($method) + { + $normalizedMethod = strtoupper($method); + + $_GET['foo1'] = 'bar1'; + $_POST['foo2'] = 'bar2'; + $_COOKIE['foo3'] = 'bar3'; + $_FILES['foo4'] = array('bar4'); + $_SERVER['foo5'] = 'bar5'; + + $request = Request::createFromGlobals(); + $this->assertEquals('bar1', $request->query->get('foo1'), '::fromGlobals() uses values from $_GET'); + $this->assertEquals('bar2', $request->request->get('foo2'), '::fromGlobals() uses values from $_POST'); + $this->assertEquals('bar3', $request->cookies->get('foo3'), '::fromGlobals() uses values from $_COOKIE'); + $this->assertEquals(array('bar4'), $request->files->get('foo4'), '::fromGlobals() uses values from $_FILES'); + $this->assertEquals('bar5', $request->server->get('foo5'), '::fromGlobals() uses values from $_SERVER'); + + unset($_GET['foo1'], $_POST['foo2'], $_COOKIE['foo3'], $_FILES['foo4'], $_SERVER['foo5']); + + $_SERVER['REQUEST_METHOD'] = $method; + $_SERVER['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; + $request = RequestContentProxy::createFromGlobals(); + $this->assertEquals($normalizedMethod, $request->getMethod()); + $this->assertEquals('mycontent', $request->request->get('content')); + + unset($_SERVER['REQUEST_METHOD'], $_SERVER['CONTENT_TYPE']); + + Request::createFromGlobals(); + Request::enableHttpMethodParameterOverride(); + $_POST['_method'] = $method; + $_POST['foo6'] = 'bar6'; + $_SERVER['REQUEST_METHOD'] = 'PoSt'; + $request = Request::createFromGlobals(); + $this->assertEquals($normalizedMethod, $request->getMethod()); + $this->assertEquals('POST', $request->getRealMethod()); + $this->assertEquals('bar6', $request->request->get('foo6')); + + unset($_POST['_method'], $_POST['foo6'], $_SERVER['REQUEST_METHOD']); + $this->disableHttpMethodParameterOverride(); + } + + public function testOverrideGlobals() + { + $request = new Request(); + $request->initialize(array('foo' => 'bar')); + + // as the Request::overrideGlobals really work, it erase $_SERVER, so we must backup it + $server = $_SERVER; + + $request->overrideGlobals(); + + $this->assertEquals(array('foo' => 'bar'), $_GET); + + $request->initialize(array(), array('foo' => 'bar')); + $request->overrideGlobals(); + + $this->assertEquals(array('foo' => 'bar'), $_POST); + + $this->assertArrayNotHasKey('HTTP_X_FORWARDED_PROTO', $_SERVER); + + $request->headers->set('X_FORWARDED_PROTO', 'https'); + + Request::setTrustedProxies(array('1.1.1.1')); + $this->assertTrue($request->isSecure()); + Request::setTrustedProxies(array()); + + $request->overrideGlobals(); + + $this->assertArrayHasKey('HTTP_X_FORWARDED_PROTO', $_SERVER); + + // restore initial $_SERVER array + $_SERVER = $server; + } + + public function testGetScriptName() + { + $request = new Request(); + $this->assertEquals('', $request->getScriptName()); + + $server = array(); + $server['SCRIPT_NAME'] = '/index.php'; + + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/index.php', $request->getScriptName()); + + $server = array(); + $server['ORIG_SCRIPT_NAME'] = '/frontend.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/frontend.php', $request->getScriptName()); + + $server = array(); + $server['SCRIPT_NAME'] = '/index.php'; + $server['ORIG_SCRIPT_NAME'] = '/frontend.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/index.php', $request->getScriptName()); + } + + public function testGetBasePath() + { + $request = new Request(); + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $server['SCRIPT_NAME'] = '/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $server['PHP_SELF'] = '/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('', $request->getBasePath()); + + $server = array(); + $server['SCRIPT_FILENAME'] = '/some/where/index.php'; + $server['ORIG_SCRIPT_NAME'] = '/index.php'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('', $request->getBasePath()); + } + + public function testGetPathInfo() + { + $request = new Request(); + $this->assertEquals('/', $request->getPathInfo()); + + $server = array(); + $server['REQUEST_URI'] = '/path/info'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/path/info', $request->getPathInfo()); + + $server = array(); + $server['REQUEST_URI'] = '/path%20test/info'; + $request->initialize(array(), array(), array(), array(), array(), $server); + + $this->assertEquals('/path%20test/info', $request->getPathInfo()); + } + + public function testGetPreferredLanguage() + { + $request = new Request(); + $this->assertNull($request->getPreferredLanguage()); + $this->assertNull($request->getPreferredLanguage(array())); + $this->assertEquals('fr', $request->getPreferredLanguage(array('fr'))); + $this->assertEquals('fr', $request->getPreferredLanguage(array('fr', 'en'))); + $this->assertEquals('en', $request->getPreferredLanguage(array('en', 'fr'))); + $this->assertEquals('fr-ch', $request->getPreferredLanguage(array('fr-ch', 'fr-fr'))); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); + $this->assertEquals('en', $request->getPreferredLanguage(array('en', 'en-us'))); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); + $this->assertEquals('en', $request->getPreferredLanguage(array('fr', 'en'))); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8'); + $this->assertEquals('en', $request->getPreferredLanguage(array('fr', 'en'))); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, fr-fr; q=0.6, fr; q=0.5'); + $this->assertEquals('en', $request->getPreferredLanguage(array('fr', 'en'))); + } + + public function testIsXmlHttpRequest() + { + $request = new Request(); + $this->assertFalse($request->isXmlHttpRequest()); + + $request->headers->set('X-Requested-With', 'XMLHttpRequest'); + $this->assertTrue($request->isXmlHttpRequest()); + + $request->headers->remove('X-Requested-With'); + $this->assertFalse($request->isXmlHttpRequest()); + } + + public function testIntlLocale() + { + if (!extension_loaded('intl')) { + $this->markTestSkipped('The intl extension is needed to run this test.'); + } + + $request = new Request(); + + $request->setDefaultLocale('fr'); + $this->assertEquals('fr', $request->getLocale()); + $this->assertEquals('fr', \Locale::getDefault()); + + $request->setLocale('en'); + $this->assertEquals('en', $request->getLocale()); + $this->assertEquals('en', \Locale::getDefault()); + + $request->setDefaultLocale('de'); + $this->assertEquals('en', $request->getLocale()); + $this->assertEquals('en', \Locale::getDefault()); + } + + public function testGetCharsets() + { + $request = new Request(); + $this->assertEquals(array(), $request->getCharsets()); + $request->headers->set('Accept-Charset', 'ISO-8859-1, US-ASCII, UTF-8; q=0.8, ISO-10646-UCS-2; q=0.6'); + $this->assertEquals(array(), $request->getCharsets()); // testing caching + + $request = new Request(); + $request->headers->set('Accept-Charset', 'ISO-8859-1, US-ASCII, UTF-8; q=0.8, ISO-10646-UCS-2; q=0.6'); + $this->assertEquals(array('ISO-8859-1', 'US-ASCII', 'UTF-8', 'ISO-10646-UCS-2'), $request->getCharsets()); + + $request = new Request(); + $request->headers->set('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.7'); + $this->assertEquals(array('ISO-8859-1', 'utf-8', '*'), $request->getCharsets()); + } + + public function testGetAcceptableContentTypes() + { + $request = new Request(); + $this->assertEquals(array(), $request->getAcceptableContentTypes()); + $request->headers->set('Accept', 'application/vnd.wap.wmlscriptc, text/vnd.wap.wml, application/vnd.wap.xhtml+xml, application/xhtml+xml, text/html, multipart/mixed, */*'); + $this->assertEquals(array(), $request->getAcceptableContentTypes()); // testing caching + + $request = new Request(); + $request->headers->set('Accept', 'application/vnd.wap.wmlscriptc, text/vnd.wap.wml, application/vnd.wap.xhtml+xml, application/xhtml+xml, text/html, multipart/mixed, */*'); + $this->assertEquals(array('application/vnd.wap.wmlscriptc', 'text/vnd.wap.wml', 'application/vnd.wap.xhtml+xml', 'application/xhtml+xml', 'text/html', 'multipart/mixed', '*/*'), $request->getAcceptableContentTypes()); + } + + public function testGetLanguages() + { + $request = new Request(); + $this->assertEquals(array(), $request->getLanguages()); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); + $this->assertEquals(array('zh', 'en_US', 'en'), $request->getLanguages()); + $this->assertEquals(array('zh', 'en_US', 'en'), $request->getLanguages()); + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en-us; q=0.6, en; q=0.8'); + $this->assertEquals(array('zh', 'en', 'en_US'), $request->getLanguages()); // Test out of order qvalues + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, en, en-us'); + $this->assertEquals(array('zh', 'en', 'en_US'), $request->getLanguages()); // Test equal weighting without qvalues + + $request = new Request(); + $request->headers->set('Accept-language', 'zh; q=0.6, en, en-us; q=0.6'); + $this->assertEquals(array('en', 'zh', 'en_US'), $request->getLanguages()); // Test equal weighting with qvalues + + $request = new Request(); + $request->headers->set('Accept-language', 'zh, i-cherokee; q=0.6'); + $this->assertEquals(array('zh', 'cherokee'), $request->getLanguages()); + } + + public function testGetRequestFormat() + { + $request = new Request(); + $this->assertEquals('html', $request->getRequestFormat()); + + $request = new Request(); + $this->assertNull($request->getRequestFormat(null)); + + $request = new Request(); + $this->assertNull($request->setRequestFormat('foo')); + $this->assertEquals('foo', $request->getRequestFormat(null)); + } + + public function testHasSession() + { + $request = new Request(); + + $this->assertFalse($request->hasSession()); + $request->setSession(new Session(new MockArraySessionStorage())); + $this->assertTrue($request->hasSession()); + } + + public function testGetSession() + { + $request = new Request(); + + $request->setSession(new Session(new MockArraySessionStorage())); + $this->assertTrue($request->hasSession()); + + $session = $request->getSession(); + $this->assertObjectHasAttribute('storage', $session); + $this->assertObjectHasAttribute('flashName', $session); + $this->assertObjectHasAttribute('attributeName', $session); + } + + public function testHasPreviousSession() + { + $request = new Request(); + + $this->assertFalse($request->hasPreviousSession()); + $request->cookies->set('MOCKSESSID', 'foo'); + $this->assertFalse($request->hasPreviousSession()); + $request->setSession(new Session(new MockArraySessionStorage())); + $this->assertTrue($request->hasPreviousSession()); + } + + public function testToString() + { + $request = new Request(); + + $request->headers->set('Accept-language', 'zh, en-us; q=0.8, en; q=0.6'); + + $this->assertContains('Accept-Language: zh, en-us; q=0.8, en; q=0.6', $request->__toString()); + } + + public function testIsMethod() + { + $request = new Request(); + $request->setMethod('POST'); + $this->assertTrue($request->isMethod('POST')); + $this->assertTrue($request->isMethod('post')); + $this->assertFalse($request->isMethod('GET')); + $this->assertFalse($request->isMethod('get')); + + $request->setMethod('GET'); + $this->assertTrue($request->isMethod('GET')); + $this->assertTrue($request->isMethod('get')); + $this->assertFalse($request->isMethod('POST')); + $this->assertFalse($request->isMethod('post')); + } + + /** + * @dataProvider getBaseUrlData + */ + public function testGetBaseUrl($uri, $server, $expectedBaseUrl, $expectedPathInfo) + { + $request = Request::create($uri, 'GET', array(), array(), array(), $server); + + $this->assertSame($expectedBaseUrl, $request->getBaseUrl(), 'baseUrl'); + $this->assertSame($expectedPathInfo, $request->getPathInfo(), 'pathInfo'); + } + + public function getBaseUrlData() + { + return array( + array( + '/foo%20bar', + array( + 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php', + 'SCRIPT_NAME' => '/foo bar/app.php', + 'PHP_SELF' => '/foo bar/app.php', + ), + '/foo%20bar', + '/', + ), + array( + '/foo%20bar/home', + array( + 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php', + 'SCRIPT_NAME' => '/foo bar/app.php', + 'PHP_SELF' => '/foo bar/app.php', + ), + '/foo%20bar', + '/home', + ), + array( + '/foo%20bar/app.php/home', + array( + 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php', + 'SCRIPT_NAME' => '/foo bar/app.php', + 'PHP_SELF' => '/foo bar/app.php', + ), + '/foo%20bar/app.php', + '/home', + ), + array( + '/foo%20bar/app.php/home%3Dbaz', + array( + 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo bar/app.php', + 'SCRIPT_NAME' => '/foo bar/app.php', + 'PHP_SELF' => '/foo bar/app.php', + ), + '/foo%20bar/app.php', + '/home%3Dbaz', + ), + array( + '/foo/bar+baz', + array( + 'SCRIPT_FILENAME' => '/home/John Doe/public_html/foo/app.php', + 'SCRIPT_NAME' => '/foo/app.php', + 'PHP_SELF' => '/foo/app.php', + ), + '/foo', + '/bar+baz', + ), + ); + } + + /** + * @dataProvider urlencodedStringPrefixData + */ + public function testUrlencodedStringPrefix($string, $prefix, $expect) + { + $request = new Request; + + $me = new \ReflectionMethod($request, 'getUrlencodedPrefix'); + $me->setAccessible(true); + + $this->assertSame($expect, $me->invoke($request, $string, $prefix)); + } + + public function urlencodedStringPrefixData() + { + return array( + array('foo', 'foo', 'foo'), + array('fo%6f', 'foo', 'fo%6f'), + array('foo/bar', 'foo', 'foo'), + array('fo%6f/bar', 'foo', 'fo%6f'), + array('f%6f%6f/bar', 'foo', 'f%6f%6f'), + array('%66%6F%6F/bar', 'foo', '%66%6F%6F'), + array('fo+o/bar', 'fo+o', 'fo+o'), + array('fo%2Bo/bar', 'fo+o', 'fo%2Bo'), + ); + } + + private function disableHttpMethodParameterOverride() + { + $class = new \ReflectionClass('Symfony\\Component\\HttpFoundation\\Request'); + $property = $class->getProperty('httpMethodParameterOverride'); + $property->setAccessible(true); + $property->setValue(false); + } + + private function getRequestInstanceForClientIpTests($remoteAddr, $httpForwardedFor, $trustedProxies) + { + $request = new Request(); + + $server = array('REMOTE_ADDR' => $remoteAddr); + if (null !== $httpForwardedFor) { + $server['HTTP_X_FORWARDED_FOR'] = $httpForwardedFor; + } + + if ($trustedProxies) { + Request::setTrustedProxies($trustedProxies); + } + + $request->initialize(array(), array(), array(), array(), array(), $server); + + return $request; + } + + public function testTrustedProxies() + { + $request = Request::create('http://example.com/'); + $request->server->set('REMOTE_ADDR', '3.3.3.3'); + $request->headers->set('X_FORWARDED_FOR', '1.1.1.1, 2.2.2.2'); + $request->headers->set('X_FORWARDED_HOST', 'foo.example.com, real.example.com:8080'); + $request->headers->set('X_FORWARDED_PROTO', 'https'); + $request->headers->set('X_FORWARDED_PORT', 443); + $request->headers->set('X_MY_FOR', '3.3.3.3, 4.4.4.4'); + $request->headers->set('X_MY_HOST', 'my.example.com'); + $request->headers->set('X_MY_PROTO', 'http'); + $request->headers->set('X_MY_PORT', 81); + + // no trusted proxies + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // disabling proxy trusting + Request::setTrustedProxies(array()); + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // trusted proxy via setTrustedProxies() + Request::setTrustedProxies(array('3.3.3.3', '2.2.2.2')); + $this->assertEquals('1.1.1.1', $request->getClientIp()); + $this->assertEquals('real.example.com', $request->getHost()); + $this->assertEquals(443, $request->getPort()); + $this->assertTrue($request->isSecure()); + + // custom header names + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_MY_FOR'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X_MY_HOST'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X_MY_PORT'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_MY_PROTO'); + $this->assertEquals('4.4.4.4', $request->getClientIp()); + $this->assertEquals('my.example.com', $request->getHost()); + $this->assertEquals(81, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // disabling via empty header names + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, null); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, null); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, null); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, null); + $this->assertEquals('3.3.3.3', $request->getClientIp()); + $this->assertEquals('example.com', $request->getHost()); + $this->assertEquals(80, $request->getPort()); + $this->assertFalse($request->isSecure()); + + // reset + Request::setTrustedProxies(array()); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_FORWARDED_FOR'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X_FORWARDED_HOST'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X_FORWARDED_PORT'); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X_FORWARDED_PROTO'); + } + + /** + * @dataProvider iisRequestUriProvider + */ + public function testIISRequestUri($headers, $server, $expectedRequestUri) + { + $request = new Request(); + $request->headers->replace($headers); + $request->server->replace($server); + + $this->assertEquals($expectedRequestUri, $request->getRequestUri(), '->getRequestUri() is correct'); + + $subRequestUri = '/bar/foo'; + $subRequest = $request::create($subRequestUri, 'get', array(), array(), array(), $request->server->all()); + $this->assertEquals($subRequestUri, $subRequest->getRequestUri(), '->getRequestUri() is correct in sub request'); + } + + public function iisRequestUriProvider() + { + return array( + array( + array( + 'X_ORIGINAL_URL' => '/foo/bar', + ), + array(), + '/foo/bar' + ), + array( + array( + 'X_REWRITE_URL' => '/foo/bar', + ), + array(), + '/foo/bar' + ), + array( + array(), + array( + 'IIS_WasUrlRewritten' => '1', + 'UNENCODED_URL' => '/foo/bar' + ), + '/foo/bar' + ), + array( + array( + 'X_ORIGINAL_URL' => '/foo/bar', + ), + array( + 'HTTP_X_ORIGINAL_URL' => '/foo/bar' + ), + '/foo/bar' + ), + array( + array( + 'X_ORIGINAL_URL' => '/foo/bar', + ), + array( + 'IIS_WasUrlRewritten' => '1', + 'UNENCODED_URL' => '/foo/bar' + ), + '/foo/bar' + ), + array( + array( + 'X_ORIGINAL_URL' => '/foo/bar', + ), + array( + 'HTTP_X_ORIGINAL_URL' => '/foo/bar', + 'IIS_WasUrlRewritten' => '1', + 'UNENCODED_URL' => '/foo/bar' + ), + '/foo/bar' + ), + array( + array(), + array( + 'ORIG_PATH_INFO' => '/foo/bar', + ), + '/foo/bar' + ), + array( + array(), + array( + 'ORIG_PATH_INFO' => '/foo/bar', + 'QUERY_STRING' => 'foo=bar', + ), + '/foo/bar?foo=bar' + ) + ); + } + + public function testTrustedHosts() + { + // create a request + $request = Request::create('/'); + + // no trusted host set -> no host check + $request->headers->set('host', 'evil.com'); + $this->assertEquals('evil.com', $request->getHost()); + + // add a trusted domain and all its subdomains + Request::setTrustedHosts(array('.*\.?trusted.com$')); + + // untrusted host + $request->headers->set('host', 'evil.com'); + try { + $request->getHost(); + $this->fail('Request::getHost() should throw an exception when host is not trusted.'); + } catch (\UnexpectedValueException $e) { + $this->assertEquals('Untrusted Host "evil.com"', $e->getMessage()); + } + + // trusted hosts + $request->headers->set('host', 'trusted.com'); + $this->assertEquals('trusted.com', $request->getHost()); + $request->headers->set('host', 'subdomain.trusted.com'); + $this->assertEquals('subdomain.trusted.com', $request->getHost()); + + // reset request for following tests + Request::setTrustedHosts(array()); + } +} + +class RequestContentProxy extends Request +{ + public function getContent($asResource = false) + { + return http_build_query(array('_method' => 'PUT', 'content' => 'mycontent')); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php new file mode 100644 index 0000000..1cb3278 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php @@ -0,0 +1,286 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\ResponseHeaderBag; +use Symfony\Component\HttpFoundation\Cookie; + +class ResponseHeaderBagTest extends \PHPUnit_Framework_TestCase +{ + /** + * @covers Symfony\Component\HttpFoundation\ResponseHeaderBag::allPreserveCase + * @dataProvider provideAllPreserveCase + */ + public function testAllPreserveCase($headers, $expected) + { + $bag = new ResponseHeaderBag($headers); + + $this->assertEquals($expected, $bag->allPreserveCase(), '->allPreserveCase() gets all input keys in original case'); + } + + public function provideAllPreserveCase() + { + return array( + array( + array('fOo' => 'BAR'), + array('fOo' => array('BAR'), 'Cache-Control' => array('no-cache')) + ), + array( + array('ETag' => 'xyzzy'), + array('ETag' => array('xyzzy'), 'Cache-Control' => array('private, must-revalidate')) + ), + array( + array('Content-MD5' => 'Q2hlY2sgSW50ZWdyaXR5IQ=='), + array('Content-MD5' => array('Q2hlY2sgSW50ZWdyaXR5IQ=='), 'Cache-Control' => array('no-cache')) + ), + array( + array('P3P' => 'CP="CAO PSA OUR"'), + array('P3P' => array('CP="CAO PSA OUR"'), 'Cache-Control' => array('no-cache')) + ), + array( + array('WWW-Authenticate' => 'Basic realm="WallyWorld"'), + array('WWW-Authenticate' => array('Basic realm="WallyWorld"'), 'Cache-Control' => array('no-cache')) + ), + array( + array('X-UA-Compatible' => 'IE=edge,chrome=1'), + array('X-UA-Compatible' => array('IE=edge,chrome=1'), 'Cache-Control' => array('no-cache')) + ), + array( + array('X-XSS-Protection' => '1; mode=block'), + array('X-XSS-Protection' => array('1; mode=block'), 'Cache-Control' => array('no-cache')) + ), + ); + } + + public function testCacheControlHeader() + { + $bag = new ResponseHeaderBag(array()); + $this->assertEquals('no-cache', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('no-cache')); + + $bag = new ResponseHeaderBag(array('Cache-Control' => 'public')); + $this->assertEquals('public', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('public')); + + $bag = new ResponseHeaderBag(array('ETag' => 'abcde')); + $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('private')); + $this->assertTrue($bag->hasCacheControlDirective('must-revalidate')); + $this->assertFalse($bag->hasCacheControlDirective('max-age')); + + $bag = new ResponseHeaderBag(array('Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT')); + $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array( + 'Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT', + 'Cache-Control' => 'max-age=3600' + )); + $this->assertEquals('max-age=3600, private', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array('Last-Modified' => 'abcde')); + $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array('Etag' => 'abcde', 'Last-Modified' => 'abcde')); + $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array('cache-control' => 'max-age=100')); + $this->assertEquals('max-age=100, private', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array('cache-control' => 's-maxage=100')); + $this->assertEquals('s-maxage=100', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array('cache-control' => 'private, max-age=100')); + $this->assertEquals('max-age=100, private', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(array('cache-control' => 'public, max-age=100')); + $this->assertEquals('max-age=100, public', $bag->get('Cache-Control')); + + $bag = new ResponseHeaderBag(); + $bag->set('Last-Modified', 'abcde'); + $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); + } + + public function testToStringIncludesCookieHeaders() + { + $bag = new ResponseHeaderBag(array()); + $bag->setCookie(new Cookie('foo', 'bar')); + + $this->assertContains("Set-Cookie: foo=bar; path=/; httponly", explode("\r\n", $bag->__toString())); + + $bag->clearCookie('foo'); + + $this->assertContains("Set-Cookie: foo=deleted; expires=".gmdate("D, d-M-Y H:i:s T", time() - 31536001)."; path=/; httponly", explode("\r\n", $bag->__toString())); + } + + public function testReplace() + { + $bag = new ResponseHeaderBag(array()); + $this->assertEquals('no-cache', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('no-cache')); + + $bag->replace(array('Cache-Control' => 'public')); + $this->assertEquals('public', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('public')); + } + + public function testReplaceWithRemove() + { + $bag = new ResponseHeaderBag(array()); + $this->assertEquals('no-cache', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('no-cache')); + + $bag->remove('Cache-Control'); + $bag->replace(array()); + $this->assertEquals('no-cache', $bag->get('Cache-Control')); + $this->assertTrue($bag->hasCacheControlDirective('no-cache')); + } + + public function testCookiesWithSameNames() + { + $bag = new ResponseHeaderBag(); + $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/foo', 'foo.bar')); + $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/bar', 'foo.bar')); + $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/bar', 'bar.foo')); + $bag->setCookie(new Cookie('foo', 'bar')); + + $this->assertCount(4, $bag->getCookies()); + + $headers = explode("\r\n", $bag->__toString()); + $this->assertContains("Set-Cookie: foo=bar; path=/path/foo; domain=foo.bar; httponly", $headers); + $this->assertContains("Set-Cookie: foo=bar; path=/path/foo; domain=foo.bar; httponly", $headers); + $this->assertContains("Set-Cookie: foo=bar; path=/path/bar; domain=bar.foo; httponly", $headers); + $this->assertContains("Set-Cookie: foo=bar; path=/; httponly", $headers); + + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertTrue(isset($cookies['foo.bar']['/path/foo']['foo'])); + $this->assertTrue(isset($cookies['foo.bar']['/path/bar']['foo'])); + $this->assertTrue(isset($cookies['bar.foo']['/path/bar']['foo'])); + $this->assertTrue(isset($cookies['']['/']['foo'])); + } + + public function testRemoveCookie() + { + $bag = new ResponseHeaderBag(); + $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/foo', 'foo.bar')); + $bag->setCookie(new Cookie('bar', 'foo', 0, '/path/bar', 'foo.bar')); + + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertTrue(isset($cookies['foo.bar']['/path/foo'])); + + $bag->removeCookie('foo', '/path/foo', 'foo.bar'); + + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertFalse(isset($cookies['foo.bar']['/path/foo'])); + + $bag->removeCookie('bar', '/path/bar', 'foo.bar'); + + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertFalse(isset($cookies['foo.bar'])); + } + + public function testRemoveCookieWithNullRemove() + { + $bag = new ResponseHeaderBag(); + $bag->setCookie(new Cookie('foo', 'bar', 0)); + $bag->setCookie(new Cookie('bar', 'foo', 0)); + + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertTrue(isset($cookies['']['/'])); + + $bag->removeCookie('foo', null); + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertFalse(isset($cookies['']['/']['foo'])); + + $bag->removeCookie('bar', null); + $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); + $this->assertFalse(isset($cookies['']['/']['bar'])); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testGetCookiesWithInvalidArgument() + { + $bag = new ResponseHeaderBag(); + + $cookies = $bag->getCookies('invalid_argument'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testMakeDispositionInvalidDisposition() + { + $headers = new ResponseHeaderBag(); + + $headers->makeDisposition('invalid', 'foo.html'); + } + + /** + * @dataProvider provideMakeDisposition + */ + public function testMakeDisposition($disposition, $filename, $filenameFallback, $expected) + { + $headers = new ResponseHeaderBag(); + + $this->assertEquals($expected, $headers->makeDisposition($disposition, $filename, $filenameFallback)); + } + + public function testToStringDoesntMessUpHeaders() + { + $headers = new ResponseHeaderBag(); + + $headers->set('Location', 'http://www.symfony.com'); + $headers->set('Content-type', 'text/html'); + + (string) $headers; + + $allHeaders = $headers->allPreserveCase(); + $this->assertEquals(array('http://www.symfony.com'), $allHeaders['Location']); + $this->assertEquals(array('text/html'), $allHeaders['Content-type']); + } + + public function provideMakeDisposition() + { + return array( + array('attachment', 'foo.html', 'foo.html', 'attachment; filename="foo.html"'), + array('attachment', 'foo.html', '', 'attachment; filename="foo.html"'), + array('attachment', 'foo bar.html', '', 'attachment; filename="foo bar.html"'), + array('attachment', 'foo "bar".html', '', 'attachment; filename="foo \\"bar\\".html"'), + array('attachment', 'foo%20bar.html', 'foo bar.html', 'attachment; filename="foo bar.html"; filename*=utf-8\'\'foo%2520bar.html'), + array('attachment', 'föö.html', 'foo.html', 'attachment; filename="foo.html"; filename*=utf-8\'\'f%C3%B6%C3%B6.html'), + ); + } + + /** + * @dataProvider provideMakeDispositionFail + * @expectedException \InvalidArgumentException + */ + public function testMakeDispositionFail($disposition, $filename) + { + $headers = new ResponseHeaderBag(); + + $headers->makeDisposition($disposition, $filename); + } + + public function provideMakeDispositionFail() + { + return array( + array('attachment', 'foo%20bar.html'), + array('attachment', 'foo/bar.html'), + array('attachment', '/foo.html'), + array('attachment', 'foo\bar.html'), + array('attachment', '\foo.html'), + array('attachment', 'föö.html'), + ); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ResponseTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ResponseTest.php new file mode 100644 index 0000000..3a08495 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ResponseTest.php @@ -0,0 +1,717 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class ResponseTest extends ResponseTestCase +{ + public function testCreate() + { + $response = Response::create('foo', 301, array('Foo' => 'bar')); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); + $this->assertEquals(301, $response->getStatusCode()); + $this->assertEquals('bar', $response->headers->get('foo')); + } + + public function testToString() + { + $response = new Response(); + $response = explode("\r\n", $response); + $this->assertEquals("HTTP/1.0 200 OK", $response[0]); + $this->assertEquals("Cache-Control: no-cache", $response[1]); + } + + public function testClone() + { + $response = new Response(); + $responseClone = clone $response; + $this->assertEquals($response, $responseClone); + } + + public function testSendHeaders() + { + $response = new Response(); + $headers = $response->sendHeaders(); + $this->assertObjectHasAttribute('headers', $headers); + $this->assertObjectHasAttribute('content', $headers); + $this->assertObjectHasAttribute('version', $headers); + $this->assertObjectHasAttribute('statusCode', $headers); + $this->assertObjectHasAttribute('statusText', $headers); + $this->assertObjectHasAttribute('charset', $headers); + } + + public function testSend() + { + $response = new Response(); + $responseSend = $response->send(); + $this->assertObjectHasAttribute('headers', $responseSend); + $this->assertObjectHasAttribute('content', $responseSend); + $this->assertObjectHasAttribute('version', $responseSend); + $this->assertObjectHasAttribute('statusCode', $responseSend); + $this->assertObjectHasAttribute('statusText', $responseSend); + $this->assertObjectHasAttribute('charset', $responseSend); + } + + public function testGetCharset() + { + $response = new Response(); + $charsetOrigin = 'UTF-8'; + $response->setCharset($charsetOrigin); + $charset = $response->getCharset(); + $this->assertEquals($charsetOrigin, $charset); + } + + public function testIsCacheable() + { + $response = new Response(); + $this->assertFalse($response->isCacheable()); + } + + public function testIsCacheableWithSetTtl() + { + $response = new Response(); + $response->setTtl(10); + $this->assertTrue($response->isCacheable()); + } + + public function testMustRevalidate() + { + $response = new Response(); + $this->assertFalse($response->mustRevalidate()); + } + + public function testSetNotModified() + { + $response = new Response(); + $modified = $response->setNotModified(); + $this->assertObjectHasAttribute('headers', $modified); + $this->assertObjectHasAttribute('content', $modified); + $this->assertObjectHasAttribute('version', $modified); + $this->assertObjectHasAttribute('statusCode', $modified); + $this->assertObjectHasAttribute('statusText', $modified); + $this->assertObjectHasAttribute('charset', $modified); + $this->assertEquals(304, $modified->getStatusCode()); + } + + public function testIsSuccessful() + { + $response = new Response(); + $this->assertTrue($response->isSuccessful()); + } + + public function testIsNotModified() + { + $response = new Response(); + $modified = $response->isNotModified(new Request()); + $this->assertFalse($modified); + } + + public function testIsValidateable() + { + $response = new Response('', 200, array('Last-Modified' => $this->createDateTimeOneHourAgo()->format(DATE_RFC2822))); + $this->assertTrue($response->isValidateable(), '->isValidateable() returns true if Last-Modified is present'); + + $response = new Response('', 200, array('ETag' => '"12345"')); + $this->assertTrue($response->isValidateable(), '->isValidateable() returns true if ETag is present'); + + $response = new Response(); + $this->assertFalse($response->isValidateable(), '->isValidateable() returns false when no validator is present'); + } + + public function testGetDate() + { + $response = new Response('', 200, array('Date' => $this->createDateTimeOneHourAgo()->format(DATE_RFC2822))); + $this->assertEquals(0, $this->createDateTimeOneHourAgo()->diff($response->getDate())->format('%s'), '->getDate() returns the Date header if present'); + + $response = new Response(); + $date = $response->getDate(); + $this->assertLessThan(1, $date->diff(new \DateTime(), true)->format('%s'), '->getDate() returns the current Date if no Date header present'); + + $response = new Response('', 200, array('Date' => $this->createDateTimeOneHourAgo()->format(DATE_RFC2822))); + $now = $this->createDateTimeNow(); + $response->headers->set('Date', $now->format(DATE_RFC2822)); + $this->assertEquals(0, $now->diff($response->getDate())->format('%s'), '->getDate() returns the date when the header has been modified'); + + $response = new Response('', 200); + $response->headers->remove('Date'); + $this->assertInstanceOf('\DateTime', $response->getDate()); + } + + public function testGetMaxAge() + { + $response = new Response(); + $response->headers->set('Cache-Control', 's-maxage=600, max-age=0'); + $this->assertEquals(600, $response->getMaxAge(), '->getMaxAge() uses s-maxage cache control directive when present'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'max-age=600'); + $this->assertEquals(600, $response->getMaxAge(), '->getMaxAge() falls back to max-age when no s-maxage directive present'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'must-revalidate'); + $response->headers->set('Expires', $this->createDateTimeOneHourLater()->format(DATE_RFC2822)); + $this->assertEquals(3600, $response->getMaxAge(), '->getMaxAge() falls back to Expires when no max-age or s-maxage directive present'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'must-revalidate'); + $response->headers->set('Expires', -1); + $this->assertEquals('Sat, 01 Jan 00 00:00:00 +0000', $response->getExpires()->format(DATE_RFC822)); + + $response = new Response(); + $this->assertNull($response->getMaxAge(), '->getMaxAge() returns null if no freshness information available'); + } + + public function testSetSharedMaxAge() + { + $response = new Response(); + $response->setSharedMaxAge(20); + + $cacheControl = $response->headers->get('Cache-Control'); + $this->assertEquals('public, s-maxage=20', $cacheControl); + } + + public function testIsPrivate() + { + $response = new Response(); + $response->headers->set('Cache-Control', 'max-age=100'); + $response->setPrivate(); + $this->assertEquals(100, $response->headers->getCacheControlDirective('max-age'), '->isPrivate() adds the private Cache-Control directive when set to true'); + $this->assertTrue($response->headers->getCacheControlDirective('private'), '->isPrivate() adds the private Cache-Control directive when set to true'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'public, max-age=100'); + $response->setPrivate(); + $this->assertEquals(100, $response->headers->getCacheControlDirective('max-age'), '->isPrivate() adds the private Cache-Control directive when set to true'); + $this->assertTrue($response->headers->getCacheControlDirective('private'), '->isPrivate() adds the private Cache-Control directive when set to true'); + $this->assertFalse($response->headers->hasCacheControlDirective('public'), '->isPrivate() removes the public Cache-Control directive'); + } + + public function testExpire() + { + $response = new Response(); + $response->headers->set('Cache-Control', 'max-age=100'); + $response->expire(); + $this->assertEquals(100, $response->headers->get('Age'), '->expire() sets the Age to max-age when present'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'max-age=100, s-maxage=500'); + $response->expire(); + $this->assertEquals(500, $response->headers->get('Age'), '->expire() sets the Age to s-maxage when both max-age and s-maxage are present'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'max-age=5, s-maxage=500'); + $response->headers->set('Age', '1000'); + $response->expire(); + $this->assertEquals(1000, $response->headers->get('Age'), '->expire() does nothing when the response is already stale/expired'); + + $response = new Response(); + $response->expire(); + $this->assertFalse($response->headers->has('Age'), '->expire() does nothing when the response does not include freshness information'); + + $response = new Response(); + $response->headers->set('Expires', -1); + $response->expire(); + $this->assertNull($response->headers->get('Age'), '->expire() does not set the Age when the response is expired'); + } + + public function testGetTtl() + { + $response = new Response(); + $this->assertNull($response->getTtl(), '->getTtl() returns null when no Expires or Cache-Control headers are present'); + + $response = new Response(); + $response->headers->set('Expires', $this->createDateTimeOneHourLater()->format(DATE_RFC2822)); + $this->assertLessThan(1, 3600 - $response->getTtl(), '->getTtl() uses the Expires header when no max-age is present'); + + $response = new Response(); + $response->headers->set('Expires', $this->createDateTimeOneHourAgo()->format(DATE_RFC2822)); + $this->assertLessThan(0, $response->getTtl(), '->getTtl() returns negative values when Expires is in past'); + + $response = new Response(); + $response->headers->set('Expires', $response->getDate()->format(DATE_RFC2822)); + $response->headers->set('Age', 0); + $this->assertSame(0, $response->getTtl(), '->getTtl() correctly handles zero'); + + $response = new Response(); + $response->headers->set('Cache-Control', 'max-age=60'); + $this->assertLessThan(1, 60 - $response->getTtl(), '->getTtl() uses Cache-Control max-age when present'); + } + + public function testSetClientTtl() + { + $response = new Response(); + $response->setClientTtl(10); + + $this->assertEquals($response->getMaxAge(), $response->getAge() + 10); + } + + public function testGetSetProtocolVersion() + { + $response = new Response(); + + $this->assertEquals('1.0', $response->getProtocolVersion()); + + $response->setProtocolVersion('1.1'); + + $this->assertEquals('1.1', $response->getProtocolVersion()); + } + + public function testGetVary() + { + $response = new Response(); + $this->assertEquals(array(), $response->getVary(), '->getVary() returns an empty array if no Vary header is present'); + + $response = new Response(); + $response->headers->set('Vary', 'Accept-Language'); + $this->assertEquals(array('Accept-Language'), $response->getVary(), '->getVary() parses a single header name value'); + + $response = new Response(); + $response->headers->set('Vary', 'Accept-Language User-Agent X-Foo'); + $this->assertEquals(array('Accept-Language', 'User-Agent', 'X-Foo'), $response->getVary(), '->getVary() parses multiple header name values separated by spaces'); + + $response = new Response(); + $response->headers->set('Vary', 'Accept-Language,User-Agent, X-Foo'); + $this->assertEquals(array('Accept-Language', 'User-Agent', 'X-Foo'), $response->getVary(), '->getVary() parses multiple header name values separated by commas'); + } + + public function testSetVary() + { + $response = new Response(); + $response->setVary('Accept-Language'); + $this->assertEquals(array('Accept-Language'), $response->getVary()); + + $response->setVary('Accept-Language, User-Agent'); + $this->assertEquals(array('Accept-Language', 'User-Agent'), $response->getVary(), '->setVary() replace the vary header by default'); + + $response->setVary('X-Foo', false); + $this->assertEquals(array('Accept-Language', 'User-Agent'), $response->getVary(), '->setVary() doesn\'t change the Vary header if replace is set to false'); + } + + public function testDefaultContentType() + { + $headerMock = $this->getMock('Symfony\Component\HttpFoundation\ResponseHeaderBag', array('set')); + $headerMock->expects($this->at(0)) + ->method('set') + ->with('Content-Type', 'text/html'); + $headerMock->expects($this->at(1)) + ->method('set') + ->with('Content-Type', 'text/html; charset=UTF-8'); + + $response = new Response('foo'); + $response->headers = $headerMock; + + $response->prepare(new Request()); + } + + public function testContentTypeCharset() + { + $response = new Response(); + $response->headers->set('Content-Type', 'text/css'); + + // force fixContentType() to be called + $response->prepare(new Request()); + + $this->assertEquals('text/css; charset=UTF-8', $response->headers->get('Content-Type')); + } + + public function testPrepareDoesNothingIfContentTypeIsSet() + { + $response = new Response('foo'); + $response->headers->set('Content-Type', 'text/plain'); + + $response->prepare(new Request()); + + $this->assertEquals('text/plain; charset=UTF-8', $response->headers->get('content-type')); + } + + public function testPrepareDoesNothingIfRequestFormatIsNotDefined() + { + $response = new Response('foo'); + + $response->prepare(new Request()); + + $this->assertEquals('text/html; charset=UTF-8', $response->headers->get('content-type')); + } + + public function testPrepareSetContentType() + { + $response = new Response('foo'); + $request = Request::create('/'); + $request->setRequestFormat('json'); + + $response->prepare($request); + + $this->assertEquals('application/json', $response->headers->get('content-type')); + } + + public function testPrepareRemovesContentForHeadRequests() + { + $response = new Response('foo'); + $request = Request::create('/', 'HEAD'); + + $response->prepare($request); + + $this->assertEquals('', $response->getContent()); + } + + public function testPrepareSetsPragmaOnHttp10Only() + { + $request = Request::create('/', 'GET'); + $request->server->set('SERVER_PROTOCOL', 'HTTP/1.0'); + + $response = new Response('foo'); + $response->prepare($request); + $this->assertEquals('no-cache', $response->headers->get('pragma')); + $this->assertEquals('-1', $response->headers->get('expires')); + + $request->server->set('SERVER_PROTOCOL', 'HTTP/1.1'); + $response = new Response('foo'); + $response->prepare($request); + $this->assertFalse($response->headers->has('pragma')); + $this->assertFalse($response->headers->has('expires')); + } + + public function testSetCache() + { + $response = new Response(); + //array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public') + try { + $response->setCache(array("wrong option" => "value")); + $this->fail('->setCache() throws an InvalidArgumentException if an option is not supported'); + } catch (\Exception $e) { + $this->assertInstanceOf('InvalidArgumentException', $e, '->setCache() throws an InvalidArgumentException if an option is not supported'); + $this->assertContains('"wrong option"', $e->getMessage()); + } + + $options = array('etag' => '"whatever"'); + $response->setCache($options); + $this->assertEquals($response->getEtag(), '"whatever"'); + + $now = new \DateTime(); + $options = array('last_modified' => $now); + $response->setCache($options); + $this->assertEquals($response->getLastModified()->getTimestamp(), $now->getTimestamp()); + + $options = array('max_age' => 100); + $response->setCache($options); + $this->assertEquals($response->getMaxAge(), 100); + + $options = array('s_maxage' => 200); + $response->setCache($options); + $this->assertEquals($response->getMaxAge(), 200); + + $this->assertTrue($response->headers->hasCacheControlDirective('public')); + $this->assertFalse($response->headers->hasCacheControlDirective('private')); + + $response->setCache(array('public' => true)); + $this->assertTrue($response->headers->hasCacheControlDirective('public')); + $this->assertFalse($response->headers->hasCacheControlDirective('private')); + + $response->setCache(array('public' => false)); + $this->assertFalse($response->headers->hasCacheControlDirective('public')); + $this->assertTrue($response->headers->hasCacheControlDirective('private')); + + $response->setCache(array('private' => true)); + $this->assertFalse($response->headers->hasCacheControlDirective('public')); + $this->assertTrue($response->headers->hasCacheControlDirective('private')); + + $response->setCache(array('private' => false)); + $this->assertTrue($response->headers->hasCacheControlDirective('public')); + $this->assertFalse($response->headers->hasCacheControlDirective('private')); + } + + public function testSendContent() + { + $response = new Response('test response rendering', 200); + + ob_start(); + $response->sendContent(); + $string = ob_get_clean(); + $this->assertContains('test response rendering', $string); + } + + public function testSetPublic() + { + $response = new Response(); + $response->setPublic(); + + $this->assertTrue($response->headers->hasCacheControlDirective('public')); + $this->assertFalse($response->headers->hasCacheControlDirective('private')); + } + + public function testSetExpires() + { + $response = new Response(); + $response->setExpires(null); + + $this->assertNull($response->getExpires(), '->setExpires() remove the header when passed null'); + + $now = new \DateTime(); + $response->setExpires($now); + + $this->assertEquals($response->getExpires()->getTimestamp(), $now->getTimestamp()); + } + + public function testSetLastModified() + { + $response = new Response(); + $response->setLastModified(new \DateTime()); + $this->assertNotNull($response->getLastModified()); + + $response->setLastModified(null); + $this->assertNull($response->getLastModified()); + } + + public function testIsInvalid() + { + $response = new Response(); + + try { + $response->setStatusCode(99); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertTrue($response->isInvalid()); + } + + try { + $response->setStatusCode(650); + $this->fail(); + } catch (\InvalidArgumentException $e) { + $this->assertTrue($response->isInvalid()); + } + + $response = new Response('', 200); + $this->assertFalse($response->isInvalid()); + } + + /** + * @dataProvider getStatusCodeFixtures + */ + public function testSetStatusCode($code, $text, $expectedText) + { + $response = new Response(); + + $response->setStatusCode($code, $text); + + $statusText = new \ReflectionProperty($response, 'statusText'); + $statusText->setAccessible(true); + + $this->assertEquals($expectedText, $statusText->getValue($response)); + } + + public function getStatusCodeFixtures() + { + return array( + array('200', null, 'OK'), + array('200', false, ''), + array('200', 'foo', 'foo'), + array('199', null, ''), + array('199', false, ''), + array('199', 'foo', 'foo') + ); + } + + public function testIsInformational() + { + $response = new Response('', 100); + $this->assertTrue($response->isInformational()); + + $response = new Response('', 200); + $this->assertFalse($response->isInformational()); + } + + public function testIsRedirectRedirection() + { + foreach (array(301, 302, 303, 307) as $code) { + $response = new Response('', $code); + $this->assertTrue($response->isRedirection()); + $this->assertTrue($response->isRedirect()); + } + + $response = new Response('', 304); + $this->assertTrue($response->isRedirection()); + $this->assertFalse($response->isRedirect()); + + $response = new Response('', 200); + $this->assertFalse($response->isRedirection()); + $this->assertFalse($response->isRedirect()); + + $response = new Response('', 404); + $this->assertFalse($response->isRedirection()); + $this->assertFalse($response->isRedirect()); + + $response = new Response('', 301, array('Location' => '/good-uri')); + $this->assertFalse($response->isRedirect('/bad-uri')); + $this->assertTrue($response->isRedirect('/good-uri')); + } + + public function testIsNotFound() + { + $response = new Response('', 404); + $this->assertTrue($response->isNotFound()); + + $response = new Response('', 200); + $this->assertFalse($response->isNotFound()); + } + + public function testIsEmpty() + { + foreach (array(201, 204, 304) as $code) { + $response = new Response('', $code); + $this->assertTrue($response->isEmpty()); + } + + $response = new Response('', 200); + $this->assertFalse($response->isEmpty()); + } + + public function testIsForbidden() + { + $response = new Response('', 403); + $this->assertTrue($response->isForbidden()); + + $response = new Response('', 200); + $this->assertFalse($response->isForbidden()); + } + + public function testIsOk() + { + $response = new Response('', 200); + $this->assertTrue($response->isOk()); + + $response = new Response('', 404); + $this->assertFalse($response->isOk()); + } + + public function testIsServerOrClientError() + { + $response = new Response('', 404); + $this->assertTrue($response->isClientError()); + $this->assertFalse($response->isServerError()); + + $response = new Response('', 500); + $this->assertFalse($response->isClientError()); + $this->assertTrue($response->isServerError()); + } + + public function testHasVary() + { + $response = new Response(); + $this->assertFalse($response->hasVary()); + + $response->setVary('User-Agent'); + $this->assertTrue($response->hasVary()); + } + + public function testSetEtag() + { + $response = new Response('', 200, array('ETag' => '"12345"')); + $response->setEtag(); + + $this->assertNull($response->headers->get('Etag'), '->setEtag() removes Etags when call with null'); + } + + /** + * @dataProvider validContentProvider + */ + public function testSetContent($content) + { + $response = new Response(); + $response->setContent($content); + $this->assertEquals((string) $content, $response->getContent()); + } + + /** + * @expectedException UnexpectedValueException + * @dataProvider invalidContentProvider + */ + public function testSetContentInvalid($content) + { + $response = new Response(); + $response->setContent($content); + } + + public function testSettersAreChainable() + { + $response = new Response(); + + $setters = array( + 'setProtocolVersion' => '1.0', + 'setCharset' => 'UTF-8', + 'setPublic' => null, + 'setPrivate' => null, + 'setDate' => new \DateTime, + 'expire' => null, + 'setMaxAge' => 1, + 'setSharedMaxAge' => 1, + 'setTtl' => 1, + 'setClientTtl' => 1, + ); + + foreach ($setters as $setter => $arg) { + $this->assertEquals($response, $response->{$setter}($arg)); + } + } + + public function validContentProvider() + { + return array( + 'obj' => array(new StringableObject), + 'string' => array('Foo'), + 'int' => array(2), + ); + } + + public function invalidContentProvider() + { + return array( + 'obj' => array(new \stdClass), + 'array' => array(array()), + 'bool' => array(true, '1'), + ); + } + + protected function createDateTimeOneHourAgo() + { + $date = new \DateTime(); + + return $date->sub(new \DateInterval('PT1H')); + } + + protected function createDateTimeOneHourLater() + { + $date = new \DateTime(); + + return $date->add(new \DateInterval('PT1H')); + } + + protected function createDateTimeNow() + { + return new \DateTime(); + } + + protected function provideResponse() + { + return new Response(); + } +} + +class StringableObject +{ + public function __toString() + { + return 'Foo'; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ResponseTestCase.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ResponseTestCase.php new file mode 100644 index 0000000..94c770a --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ResponseTestCase.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\Request; + +abstract class ResponseTestCase extends \PHPUnit_Framework_TestCase +{ + public function testNoCacheControlHeaderOnAttachmentUsingHTTPSAndMSIE() + { + // Check for HTTPS and IE 8 + $request = new Request(); + $request->server->set('HTTPS', true); + $request->server->set('HTTP_USER_AGENT', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)'); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertFalse($response->headers->has('Cache-Control')); + + // Check for IE 10 and HTTPS + $request->server->set('HTTP_USER_AGENT', 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)'); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertTrue($response->headers->has('Cache-Control')); + + // Check for IE 9 and HTTPS + $request->server->set('HTTP_USER_AGENT', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 7.1; Trident/5.0)'); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertTrue($response->headers->has('Cache-Control')); + + // Check for IE 9 and HTTP + $request->server->set('HTTPS', false); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertTrue($response->headers->has('Cache-Control')); + + // Check for IE 8 and HTTP + $request->server->set('HTTP_USER_AGENT', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)'); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertTrue($response->headers->has('Cache-Control')); + + // Check for non-IE and HTTPS + $request->server->set('HTTPS', true); + $request->server->set('HTTP_USER_AGENT', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17'); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertTrue($response->headers->has('Cache-Control')); + + // Check for non-IE and HTTP + $request->server->set('HTTPS', false); + + $response = $this->provideResponse(); + $response->headers->set('Content-Disposition', 'attachment; filename="fname.ext"'); + $response->prepare($request); + + $this->assertTrue($response->headers->has('Cache-Control')); + } + + abstract protected function provideResponse(); +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ServerBagTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ServerBagTest.php new file mode 100644 index 0000000..f8e487d --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/ServerBagTest.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\ServerBag; + +/** + * ServerBagTest + * + * @author Bulat Shakirzyanov + */ +class ServerBagTest extends \PHPUnit_Framework_TestCase +{ + public function testShouldExtractHeadersFromServerArray() + { + $server = array( + 'SOME_SERVER_VARIABLE' => 'value', + 'SOME_SERVER_VARIABLE2' => 'value', + 'ROOT' => 'value', + 'HTTP_CONTENT_TYPE' => 'text/html', + 'HTTP_CONTENT_LENGTH' => '0', + 'HTTP_ETAG' => 'asdf', + 'PHP_AUTH_USER' => 'foo', + 'PHP_AUTH_PW' => 'bar', + ); + + $bag = new ServerBag($server); + + $this->assertEquals(array( + 'CONTENT_TYPE' => 'text/html', + 'CONTENT_LENGTH' => '0', + 'ETAG' => 'asdf', + 'AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'), + 'PHP_AUTH_USER' => 'foo', + 'PHP_AUTH_PW' => 'bar', + ), $bag->getHeaders()); + } + + public function testHttpPasswordIsOptional() + { + $bag = new ServerBag(array('PHP_AUTH_USER' => 'foo')); + + $this->assertEquals(array( + 'AUTHORIZATION' => 'Basic '.base64_encode('foo:'), + 'PHP_AUTH_USER' => 'foo', + 'PHP_AUTH_PW' => '' + ), $bag->getHeaders()); + } + + public function testHttpBasicAuthWithPhpCgi() + { + $bag = new ServerBag(array('HTTP_AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'))); + + $this->assertEquals(array( + 'AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'), + 'PHP_AUTH_USER' => 'foo', + 'PHP_AUTH_PW' => 'bar' + ), $bag->getHeaders()); + } + + public function testHttpBasicAuthWithPhpCgiRedirect() + { + $bag = new ServerBag(array('REDIRECT_HTTP_AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'))); + + $this->assertEquals(array( + 'AUTHORIZATION' => 'Basic '.base64_encode('foo:bar'), + 'PHP_AUTH_USER' => 'foo', + 'PHP_AUTH_PW' => 'bar' + ), $bag->getHeaders()); + } + + public function testHttpBasicAuthWithPhpCgiEmptyPassword() + { + $bag = new ServerBag(array('HTTP_AUTHORIZATION' => 'Basic '.base64_encode('foo:'))); + + $this->assertEquals(array( + 'AUTHORIZATION' => 'Basic '.base64_encode('foo:'), + 'PHP_AUTH_USER' => 'foo', + 'PHP_AUTH_PW' => '' + ), $bag->getHeaders()); + } + + public function testOAuthBearerAuth() + { + $headerContent = 'Bearer L-yLEOr9zhmUYRkzN1jwwxwQ-PBNiKDc8dgfB4hTfvo'; + $bag = new ServerBag(array('HTTP_AUTHORIZATION' => $headerContent)); + + $this->assertEquals(array( + 'AUTHORIZATION' => $headerContent, + ), $bag->getHeaders()); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Attribute/AttributeBagTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Attribute/AttributeBagTest.php new file mode 100644 index 0000000..93c634c --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Attribute/AttributeBagTest.php @@ -0,0 +1,193 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Attribute; + +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; + +/** + * Tests AttributeBag + * + * @author Drak + */ +class AttributeBagTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var array + */ + private $array; + + /** + * @var AttributeBag + */ + private $bag; + + protected function setUp() + { + $this->array = array( + 'hello' => 'world', + 'always' => 'be happy', + 'user.login' => 'drak', + 'csrf.token' => array( + 'a' => '1234', + 'b' => '4321', + ), + 'category' => array( + 'fishing' => array( + 'first' => 'cod', + 'second' => 'sole') + ), + ); + $this->bag = new AttributeBag('_sf2'); + $this->bag->initialize($this->array); + } + + protected function tearDown() + { + $this->bag = null; + $this->array = array(); + } + + public function testInitialize() + { + $bag = new AttributeBag(); + $bag->initialize($this->array); + $this->assertEquals($this->array, $bag->all()); + $array = array('should' => 'change'); + $bag->initialize($array); + $this->assertEquals($array, $bag->all()); + } + + public function testGetStorageKey() + { + $this->assertEquals('_sf2', $this->bag->getStorageKey()); + $attributeBag = new AttributeBag('test'); + $this->assertEquals('test', $attributeBag->getStorageKey()); + } + + public function testGetSetName() + { + $this->assertEquals('attributes', $this->bag->getName()); + $this->bag->setName('foo'); + $this->assertEquals('foo', $this->bag->getName()); + } + + /** + * @dataProvider attributesProvider + */ + public function testHas($key, $value, $exists) + { + $this->assertEquals($exists, $this->bag->has($key)); + } + + /** + * @dataProvider attributesProvider + */ + public function testGet($key, $value, $expected) + { + $this->assertEquals($value, $this->bag->get($key)); + } + + public function testGetDefaults() + { + $this->assertNull($this->bag->get('user2.login')); + $this->assertEquals('default', $this->bag->get('user2.login', 'default')); + } + + /** + * @dataProvider attributesProvider + */ + public function testSet($key, $value, $expected) + { + $this->bag->set($key, $value); + $this->assertEquals($value, $this->bag->get($key)); + } + + public function testAll() + { + $this->assertEquals($this->array, $this->bag->all()); + + $this->bag->set('hello', 'fabien'); + $array = $this->array; + $array['hello'] = 'fabien'; + $this->assertEquals($array, $this->bag->all()); + } + + public function testReplace() + { + $array = array(); + $array['name'] = 'jack'; + $array['foo.bar'] = 'beep'; + $this->bag->replace($array); + $this->assertEquals($array, $this->bag->all()); + $this->assertNull($this->bag->get('hello')); + $this->assertNull($this->bag->get('always')); + $this->assertNull($this->bag->get('user.login')); + } + + public function testRemove() + { + $this->assertEquals('world', $this->bag->get('hello')); + $this->bag->remove('hello'); + $this->assertNull($this->bag->get('hello')); + + $this->assertEquals('be happy', $this->bag->get('always')); + $this->bag->remove('always'); + $this->assertNull($this->bag->get('always')); + + $this->assertEquals('drak', $this->bag->get('user.login')); + $this->bag->remove('user.login'); + $this->assertNull($this->bag->get('user.login')); + } + + public function testClear() + { + $this->bag->clear(); + $this->assertEquals(array(), $this->bag->all()); + } + + public function attributesProvider() + { + return array( + array('hello', 'world', true), + array('always', 'be happy', true), + array('user.login', 'drak', true), + array('csrf.token', array('a' => '1234', 'b' => '4321'), true), + array('category', array('fishing' => array('first' => 'cod', 'second' => 'sole')), true), + array('user2.login', null, false), + array('never', null, false), + array('bye', null, false), + array('bye/for/now', null, false), + ); + } + + /** + * @covers Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag::getIterator + */ + public function testGetIterator() + { + $i = 0; + foreach ($this->bag as $key => $val) { + $this->assertEquals($this->array[$key], $val); + $i++; + } + + $this->assertEquals(count($this->array), $i); + } + + /** + * @covers Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag::count + */ + public function testCount() + { + $this->assertEquals(count($this->array), count($this->bag)); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php new file mode 100644 index 0000000..0c46a51 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Attribute/NamespacedAttributeBagTest.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Attribute; + +use Symfony\Component\HttpFoundation\Session\Attribute\NamespacedAttributeBag; + +/** + * Tests NamespacedAttributeBag + * + * @author Drak + */ +class NamespacedAttributeBagTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var array + */ + private $array; + + /** + * @var NamespacedAttributeBag + */ + private $bag; + + protected function setUp() + { + $this->array = array( + 'hello' => 'world', + 'always' => 'be happy', + 'user.login' => 'drak', + 'csrf.token' => array( + 'a' => '1234', + 'b' => '4321', + ), + 'category' => array( + 'fishing' => array( + 'first' => 'cod', + 'second' => 'sole') + ), + ); + $this->bag = new NamespacedAttributeBag('_sf2', '/'); + $this->bag->initialize($this->array); + } + + protected function tearDown() + { + $this->bag = null; + $this->array = array(); + } + + public function testInitialize() + { + $bag = new NamespacedAttributeBag(); + $bag->initialize($this->array); + $this->assertEquals($this->array, $this->bag->all()); + $array = array('should' => 'not stick'); + $bag->initialize($array); + + // should have remained the same + $this->assertEquals($this->array, $this->bag->all()); + } + + public function testGetStorageKey() + { + $this->assertEquals('_sf2', $this->bag->getStorageKey()); + $attributeBag = new NamespacedAttributeBag('test'); + $this->assertEquals('test', $attributeBag->getStorageKey()); + } + + /** + * @dataProvider attributesProvider + */ + public function testHas($key, $value, $exists) + { + $this->assertEquals($exists, $this->bag->has($key)); + } + + /** + * @dataProvider attributesProvider + */ + public function testGet($key, $value, $expected) + { + $this->assertEquals($value, $this->bag->get($key)); + } + + public function testGetDefaults() + { + $this->assertNull($this->bag->get('user2.login')); + $this->assertEquals('default', $this->bag->get('user2.login', 'default')); + } + + /** + * @dataProvider attributesProvider + */ + public function testSet($key, $value, $expected) + { + $this->bag->set($key, $value); + $this->assertEquals($value, $this->bag->get($key)); + } + + public function testAll() + { + $this->assertEquals($this->array, $this->bag->all()); + + $this->bag->set('hello', 'fabien'); + $array = $this->array; + $array['hello'] = 'fabien'; + $this->assertEquals($array, $this->bag->all()); + } + + public function testReplace() + { + $array = array(); + $array['name'] = 'jack'; + $array['foo.bar'] = 'beep'; + $this->bag->replace($array); + $this->assertEquals($array, $this->bag->all()); + $this->assertNull($this->bag->get('hello')); + $this->assertNull($this->bag->get('always')); + $this->assertNull($this->bag->get('user.login')); + } + + public function testRemove() + { + $this->assertEquals('world', $this->bag->get('hello')); + $this->bag->remove('hello'); + $this->assertNull($this->bag->get('hello')); + + $this->assertEquals('be happy', $this->bag->get('always')); + $this->bag->remove('always'); + $this->assertNull($this->bag->get('always')); + + $this->assertEquals('drak', $this->bag->get('user.login')); + $this->bag->remove('user.login'); + $this->assertNull($this->bag->get('user.login')); + } + + public function testRemoveExistingNamespacedAttribute() + { + $this->assertSame('cod', $this->bag->remove('category/fishing/first')); + } + + public function testRemoveNonexistingNamespacedAttribute() + { + $this->assertNull($this->bag->remove('foo/bar/baz')); + } + + public function testClear() + { + $this->bag->clear(); + $this->assertEquals(array(), $this->bag->all()); + } + + public function attributesProvider() + { + return array( + array('hello', 'world', true), + array('always', 'be happy', true), + array('user.login', 'drak', true), + array('csrf.token', array('a' => '1234', 'b' => '4321'), true), + array('csrf.token/a', '1234', true), + array('csrf.token/b', '4321', true), + array('category', array('fishing' => array('first' => 'cod', 'second' => 'sole')), true), + array('category/fishing', array('first' => 'cod', 'second' => 'sole'), true), + array('category/fishing/missing/first', null, false), + array('category/fishing/first', 'cod', true), + array('category/fishing/second', 'sole', true), + array('category/fishing/missing/second', null, false), + array('user2.login', null, false), + array('never', null, false), + array('bye', null, false), + array('bye/for/now', null, false), + ); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Flash/AutoExpireFlashBagTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Flash/AutoExpireFlashBagTest.php new file mode 100644 index 0000000..5d44b78 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Flash/AutoExpireFlashBagTest.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Flash; + +use Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag as FlashBag; + +/** + * AutoExpireFlashBagTest + * + * @author Drak + */ +class AutoExpireFlashBagTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Symfony\Component\HttpFoundation\Session\Flash\AutoExpireFlashBag + */ + private $bag; + + /** + * @var array + */ + protected $array = array(); + + protected function setUp() + { + parent::setUp(); + $this->bag = new FlashBag(); + $this->array = array('new' => array('notice' => array('A previous flash message'))); + $this->bag->initialize($this->array); + } + + public function tearDown() + { + $this->bag = null; + parent::tearDown(); + } + + public function testInitialize() + { + $bag = new FlashBag(); + $array = array('new' => array('notice' => array('A previous flash message'))); + $bag->initialize($array); + $this->assertEquals(array('A previous flash message'), $bag->peek('notice')); + $array = array('new' => array( + 'notice' => array('Something else'), + 'error' => array('a'), + )); + $bag->initialize($array); + $this->assertEquals(array('Something else'), $bag->peek('notice')); + $this->assertEquals(array('a'), $bag->peek('error')); + } + + public function testGetStorageKey() + { + $this->assertEquals('_sf2_flashes', $this->bag->getStorageKey()); + $attributeBag = new FlashBag('test'); + $this->assertEquals('test', $attributeBag->getStorageKey()); + } + + public function testGetSetName() + { + $this->assertEquals('flashes', $this->bag->getName()); + $this->bag->setName('foo'); + $this->assertEquals('foo', $this->bag->getName()); + } + + public function testPeek() + { + $this->assertEquals(array(), $this->bag->peek('non_existing')); + $this->assertEquals(array('default'), $this->bag->peek('non_existing', array('default'))); + $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice')); + $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice')); + } + + public function testSet() + { + $this->bag->set('notice', 'Foo'); + $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice')); + } + + public function testHas() + { + $this->assertFalse($this->bag->has('nothing')); + $this->assertTrue($this->bag->has('notice')); + } + + public function testKeys() + { + $this->assertEquals(array('notice'), $this->bag->keys()); + } + + public function testPeekAll() + { + $array = array( + 'new' => array( + 'notice' => 'Foo', + 'error' => 'Bar', + ), + ); + + $this->bag->initialize($array); + $this->assertEquals(array( + 'notice' => 'Foo', + 'error' => 'Bar', + ), $this->bag->peekAll() + ); + + $this->assertEquals(array( + 'notice' => 'Foo', + 'error' => 'Bar', + ), $this->bag->peekAll() + ); + } + + public function testGet() + { + $this->assertEquals(array(), $this->bag->get('non_existing')); + $this->assertEquals(array('default'), $this->bag->get('non_existing', array('default'))); + $this->assertEquals(array('A previous flash message'), $this->bag->get('notice')); + $this->assertEquals(array(), $this->bag->get('notice')); + } + + public function testSetAll() + { + $this->bag->setAll(array('a' => 'first', 'b' => 'second')); + $this->assertFalse($this->bag->has('a')); + $this->assertFalse($this->bag->has('b')); + } + + public function testAll() + { + $this->bag->set('notice', 'Foo'); + $this->bag->set('error', 'Bar'); + $this->assertEquals(array( + 'notice' => array('A previous flash message'), + ), $this->bag->all() + ); + + $this->assertEquals(array(), $this->bag->all()); + } + + public function testClear() + { + $this->assertEquals(array('notice' => array('A previous flash message')), $this->bag->clear()); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Flash/FlashBagTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Flash/FlashBagTest.php new file mode 100644 index 0000000..1dbfd2f --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Flash/FlashBagTest.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Flash; + +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; + +/** + * FlashBagTest + * + * @author Drak + */ +class FlashBagTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Symfony\Component\HttpFoundation\SessionFlash\FlashBagInterface + */ + private $bag; + + /** + * @var array + */ + protected $array = array(); + + protected function setUp() + { + parent::setUp(); + $this->bag = new FlashBag(); + $this->array = array('notice' => array('A previous flash message')); + $this->bag->initialize($this->array); + } + + public function tearDown() + { + $this->bag = null; + parent::tearDown(); + } + + public function testInitialize() + { + $bag = new FlashBag(); + $bag->initialize($this->array); + $this->assertEquals($this->array, $bag->peekAll()); + $array = array('should' => array('change')); + $bag->initialize($array); + $this->assertEquals($array, $bag->peekAll()); + } + + public function testGetStorageKey() + { + $this->assertEquals('_sf2_flashes', $this->bag->getStorageKey()); + $attributeBag = new FlashBag('test'); + $this->assertEquals('test', $attributeBag->getStorageKey()); + } + + public function testGetSetName() + { + $this->assertEquals('flashes', $this->bag->getName()); + $this->bag->setName('foo'); + $this->assertEquals('foo', $this->bag->getName()); + } + + public function testPeek() + { + $this->assertEquals(array(), $this->bag->peek('non_existing')); + $this->assertEquals(array('default'), $this->bag->peek('not_existing', array('default'))); + $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice')); + $this->assertEquals(array('A previous flash message'), $this->bag->peek('notice')); + } + + public function testGet() + { + $this->assertEquals(array(), $this->bag->get('non_existing')); + $this->assertEquals(array('default'), $this->bag->get('not_existing', array('default'))); + $this->assertEquals(array('A previous flash message'), $this->bag->get('notice')); + $this->assertEquals(array(), $this->bag->get('notice')); + } + + public function testAll() + { + $this->bag->set('notice', 'Foo'); + $this->bag->set('error', 'Bar'); + $this->assertEquals(array( + 'notice' => array('Foo'), + 'error' => array('Bar')), $this->bag->all() + ); + + $this->assertEquals(array(), $this->bag->all()); + } + + public function testSet() + { + $this->bag->set('notice', 'Foo'); + $this->bag->set('notice', 'Bar'); + $this->assertEquals(array('Bar'), $this->bag->peek('notice')); + } + + public function testHas() + { + $this->assertFalse($this->bag->has('nothing')); + $this->assertTrue($this->bag->has('notice')); + } + + public function testKeys() + { + $this->assertEquals(array('notice'), $this->bag->keys()); + } + + public function testPeekAll() + { + $this->bag->set('notice', 'Foo'); + $this->bag->set('error', 'Bar'); + $this->assertEquals(array( + 'notice' => array('Foo'), + 'error' => array('Bar'), + ), $this->bag->peekAll() + ); + $this->assertTrue($this->bag->has('notice')); + $this->assertTrue($this->bag->has('error')); + $this->assertEquals(array( + 'notice' => array('Foo'), + 'error' => array('Bar'), + ), $this->bag->peekAll() + ); + } + + /** + * @covers Symfony\Component\HttpFoundation\Session\Flash\FlashBag::getIterator + */ + public function testGetIterator() + { + $flashes = array('hello' => 'world', 'beep' => 'boop', 'notice' => 'nope'); + foreach ($flashes as $key => $val) { + $this->bag->set($key, $val); + } + + $i = 0; + foreach ($this->bag as $key => $val) { + $this->assertEquals(array($flashes[$key]), $val); + $i++; + } + + $this->assertEquals(count($flashes), $i); + $this->assertEquals(0, count($this->bag->all())); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/SessionTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/SessionTest.php new file mode 100644 index 0000000..9d5ad35 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/SessionTest.php @@ -0,0 +1,227 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session; + +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; + +/** + * SessionTest + * + * @author Fabien Potencier + * @author Robert Schönthal + * @author Drak + */ +class SessionTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface + */ + protected $storage; + + /** + * @var \Symfony\Component\HttpFoundation\Session\SessionInterface + */ + protected $session; + + protected function setUp() + { + $this->storage = new MockArraySessionStorage(); + $this->session = new Session($this->storage, new AttributeBag(), new FlashBag()); + } + + protected function tearDown() + { + $this->storage = null; + $this->session = null; + } + + public function testStart() + { + $this->assertEquals('', $this->session->getId()); + $this->assertTrue($this->session->start()); + $this->assertNotEquals('', $this->session->getId()); + } + + public function testIsStarted() + { + $this->assertFalse($this->session->isStarted()); + $this->session->start(); + $this->assertTrue($this->session->isStarted()); + } + + public function testSetId() + { + $this->assertEquals('', $this->session->getId()); + $this->session->setId('0123456789abcdef'); + $this->session->start(); + $this->assertEquals('0123456789abcdef', $this->session->getId()); + } + + public function testSetName() + { + $this->assertEquals('MOCKSESSID', $this->session->getName()); + $this->session->setName('session.test.com'); + $this->session->start(); + $this->assertEquals('session.test.com', $this->session->getName()); + } + + public function testGet() + { + // tests defaults + $this->assertNull($this->session->get('foo')); + $this->assertEquals(1, $this->session->get('foo', 1)); + } + + /** + * @dataProvider setProvider + */ + public function testSet($key, $value) + { + $this->session->set($key, $value); + $this->assertEquals($value, $this->session->get($key)); + } + + /** + * @dataProvider setProvider + */ + public function testHas($key, $value) + { + $this->session->set($key, $value); + $this->assertTrue($this->session->has($key)); + $this->assertFalse($this->session->has($key.'non_value')); + } + + public function testReplace() + { + $this->session->replace(array('happiness' => 'be good', 'symfony' => 'awesome')); + $this->assertEquals(array('happiness' => 'be good', 'symfony' => 'awesome'), $this->session->all()); + $this->session->replace(array()); + $this->assertEquals(array(), $this->session->all()); + } + + /** + * @dataProvider setProvider + */ + public function testAll($key, $value, $result) + { + $this->session->set($key, $value); + $this->assertEquals($result, $this->session->all()); + } + + /** + * @dataProvider setProvider + */ + public function testClear($key, $value) + { + $this->session->set('hi', 'fabien'); + $this->session->set($key, $value); + $this->session->clear(); + $this->assertEquals(array(), $this->session->all()); + } + + public function setProvider() + { + return array( + array('foo', 'bar', array('foo' => 'bar')), + array('foo.bar', 'too much beer', array('foo.bar' => 'too much beer')), + array('great', 'symfony2 is great', array('great' => 'symfony2 is great')), + ); + } + + /** + * @dataProvider setProvider + */ + public function testRemove($key, $value) + { + $this->session->set('hi.world', 'have a nice day'); + $this->session->set($key, $value); + $this->session->remove($key); + $this->assertEquals(array('hi.world' => 'have a nice day'), $this->session->all()); + } + + public function testInvalidate() + { + $this->session->set('invalidate', 123); + $this->session->invalidate(); + $this->assertEquals(array(), $this->session->all()); + } + + public function testMigrate() + { + $this->session->set('migrate', 321); + $this->session->migrate(); + $this->assertEquals(321, $this->session->get('migrate')); + } + + public function testMigrateDestroy() + { + $this->session->set('migrate', 333); + $this->session->migrate(true); + $this->assertEquals(333, $this->session->get('migrate')); + } + + public function testSave() + { + $this->session->start(); + $this->session->save(); + } + + public function testGetId() + { + $this->assertEquals('', $this->session->getId()); + $this->session->start(); + $this->assertNotEquals('', $this->session->getId()); + } + + public function testGetFlashBag() + { + $this->assertInstanceOf('Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface', $this->session->getFlashBag()); + } + + /** + * @covers Symfony\Component\HttpFoundation\Session\Session::getIterator + */ + public function testGetIterator() + { + $attributes = array('hello' => 'world', 'symfony2' => 'rocks'); + foreach ($attributes as $key => $val) { + $this->session->set($key, $val); + } + + $i = 0; + foreach ($this->session as $key => $val) { + $this->assertEquals($attributes[$key], $val); + $i++; + } + + $this->assertEquals(count($attributes), $i); + } + + /** + * @covers \Symfony\Component\HttpFoundation\Session\Session::count + */ + public function testGetCount() + { + $this->session->set('hello', 'world'); + $this->session->set('symfony2', 'rocks'); + + $this->assertEquals(2, count($this->session)); + } + + public function testGetMeta() + { + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\MetadataBag', $this->session->getMetadataBag()); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php new file mode 100644 index 0000000..da0440d --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcacheSessionHandlerTest.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcacheSessionHandler; + +class MemcacheSessionHandlerTest extends \PHPUnit_Framework_TestCase +{ + const PREFIX = 'prefix_'; + const TTL = 1000; + /** + * @var MemcacheSessionHandler + */ + protected $storage; + + protected $memcache; + + protected function setUp() + { + if (!class_exists('Memcache')) { + $this->markTestSkipped('Skipped tests Memcache class is not present'); + } + + $this->memcache = $this->getMock('Memcache'); + $this->storage = new MemcacheSessionHandler( + $this->memcache, + array('prefix' => self::PREFIX, 'expiretime' => self::TTL) + ); + } + + protected function tearDown() + { + $this->memcache = null; + $this->storage = null; + } + + public function testOpenSession() + { + $this->assertTrue($this->storage->open('', '')); + } + + public function testCloseSession() + { + $this->memcache + ->expects($this->once()) + ->method('close') + ->will($this->returnValue(true)) + ; + + $this->assertTrue($this->storage->close()); + } + + public function testReadSession() + { + $this->memcache + ->expects($this->once()) + ->method('get') + ->with(self::PREFIX.'id') + ; + + $this->assertEquals('', $this->storage->read('id')); + } + + public function testWriteSession() + { + $this->memcache + ->expects($this->once()) + ->method('set') + ->with(self::PREFIX.'id', 'data', 0, $this->equalTo(time() + self::TTL, 2)) + ->will($this->returnValue(true)) + ; + + $this->assertTrue($this->storage->write('id', 'data')); + } + + public function testDestroySession() + { + $this->memcache + ->expects($this->once()) + ->method('delete') + ->with(self::PREFIX.'id') + ->will($this->returnValue(true)) + ; + + $this->assertTrue($this->storage->destroy('id')); + } + + public function testGcSession() + { + $this->assertTrue($this->storage->gc(123)); + } + + /** + * @dataProvider getOptionFixtures + */ + public function testSupportedOptions($options, $supported) + { + try { + new MemcacheSessionHandler($this->memcache, $options); + $this->assertTrue($supported); + } catch (\InvalidArgumentException $e) { + $this->assertFalse($supported); + } + } + + public function getOptionFixtures() + { + return array( + array(array('prefix' => 'session'), true), + array(array('expiretime' => 100), true), + array(array('prefix' => 'session', 'expiretime' => 200), true), + array(array('expiretime' => 100, 'foo' => 'bar'), false), + ); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php new file mode 100644 index 0000000..985fae4 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MemcachedSessionHandlerTest.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler; + +class MemcachedSessionHandlerTest extends \PHPUnit_Framework_TestCase +{ + const PREFIX = 'prefix_'; + const TTL = 1000; + + /** + * @var MemcachedSessionHandler + */ + protected $storage; + + protected $memcached; + + protected function setUp() + { + if (!class_exists('Memcached')) { + $this->markTestSkipped('Skipped tests Memcached class is not present'); + } + + $this->memcached = $this->getMock('Memcached'); + $this->storage = new MemcachedSessionHandler( + $this->memcached, + array('prefix' => self::PREFIX, 'expiretime' => self::TTL) + ); + } + + protected function tearDown() + { + $this->memcached = null; + $this->storage = null; + } + + public function testOpenSession() + { + $this->assertTrue($this->storage->open('', '')); + } + + public function testCloseSession() + { + $this->assertTrue($this->storage->close()); + } + + public function testReadSession() + { + $this->memcached + ->expects($this->once()) + ->method('get') + ->with(self::PREFIX.'id') + ; + + $this->assertEquals('', $this->storage->read('id')); + } + + public function testWriteSession() + { + $this->memcached + ->expects($this->once()) + ->method('set') + ->with(self::PREFIX.'id', 'data', $this->equalTo(time() + self::TTL, 2)) + ->will($this->returnValue(true)) + ; + + $this->assertTrue($this->storage->write('id', 'data')); + } + + public function testDestroySession() + { + $this->memcached + ->expects($this->once()) + ->method('delete') + ->with(self::PREFIX.'id') + ->will($this->returnValue(true)) + ; + + $this->assertTrue($this->storage->destroy('id')); + } + + public function testGcSession() + { + $this->assertTrue($this->storage->gc(123)); + } + + /** + * @dataProvider getOptionFixtures + */ + public function testSupportedOptions($options, $supported) + { + try { + new MemcachedSessionHandler($this->memcached, $options); + $this->assertTrue($supported); + } catch (\InvalidArgumentException $e) { + $this->assertFalse($supported); + } + } + + public function getOptionFixtures() + { + return array( + array(array('prefix' => 'session'), true), + array(array('expiretime' => 100), true), + array(array('prefix' => 'session', 'expiretime' => 200), true), + array(array('expiretime' => 100, 'foo' => 'bar'), false), + ); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php new file mode 100644 index 0000000..1cfd117 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/MongoDbSessionHandlerTest.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler; + +/** + * @author Markus Bachmann + */ +class MongoDbSessionHandlerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $mongo; + private $storage; + public $options; + + protected function setUp() + { + if (!extension_loaded('mongo')) { + $this->markTestSkipped('MongoDbSessionHandler requires the PHP "mongo" extension.'); + } + + $mongoClass = (version_compare(phpversion('mongo'), '1.3.0', '<')) ? 'Mongo' : 'MongoClient'; + + $this->mongo = $this->getMockBuilder($mongoClass) + ->disableOriginalConstructor() + ->getMock(); + + $this->options = array( + 'id_field' => '_id', + 'data_field' => 'data', + 'time_field' => 'time', + 'database' => 'sf2-test', + 'collection' => 'session-test' + ); + + $this->storage = new MongoDbSessionHandler($this->mongo, $this->options); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorShouldThrowExceptionForInvalidMongo() + { + new MongoDbSessionHandler(new \stdClass(), $this->options); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testConstructorShouldThrowExceptionForMissingOptions() + { + new MongoDbSessionHandler($this->mongo, array()); + } + + public function testOpenMethodAlwaysReturnTrue() + { + $this->assertTrue($this->storage->open('test', 'test'), 'The "open" method should always return true'); + } + + public function testCloseMethodAlwaysReturnTrue() + { + $this->assertTrue($this->storage->close(), 'The "close" method should always return true'); + } + + public function testWrite() + { + $collection = $this->getMockBuilder('MongoCollection') + ->disableOriginalConstructor() + ->getMock(); + + $this->mongo->expects($this->once()) + ->method('selectCollection') + ->with($this->options['database'], $this->options['collection']) + ->will($this->returnValue($collection)); + + $that = $this; + $data = array(); + + $collection->expects($this->once()) + ->method('update') + ->will($this->returnCallback(function($criteria, $updateData, $options) use ($that, &$data) { + $that->assertEquals(array($that->options['id_field'] => 'foo'), $criteria); + $that->assertEquals(array('upsert' => true, 'multiple' => false), $options); + + $data = $updateData['$set']; + })); + + $this->assertTrue($this->storage->write('foo', 'bar')); + + $this->assertEquals('bar', $data[$this->options['data_field']]->bin); + $that->assertInstanceOf('MongoDate', $data[$this->options['time_field']]); + } + + public function testReplaceSessionData() + { + $collection = $this->getMockBuilder('MongoCollection') + ->disableOriginalConstructor() + ->getMock(); + + $this->mongo->expects($this->once()) + ->method('selectCollection') + ->with($this->options['database'], $this->options['collection']) + ->will($this->returnValue($collection)); + + $data = array(); + + $collection->expects($this->exactly(2)) + ->method('update') + ->will($this->returnCallback(function($criteria, $updateData, $options) use (&$data) { + $data = $updateData; + })); + + $this->storage->write('foo', 'bar'); + $this->storage->write('foo', 'foobar'); + + $this->assertEquals('foobar', $data['$set'][$this->options['data_field']]->bin); + } + + public function testDestroy() + { + $collection = $this->getMockBuilder('MongoCollection') + ->disableOriginalConstructor() + ->getMock(); + + $this->mongo->expects($this->once()) + ->method('selectCollection') + ->with($this->options['database'], $this->options['collection']) + ->will($this->returnValue($collection)); + + $collection->expects($this->once()) + ->method('remove') + ->with(array($this->options['id_field'] => 'foo')); + + $this->assertTrue($this->storage->destroy('foo')); + } + + public function testGc() + { + $collection = $this->getMockBuilder('MongoCollection') + ->disableOriginalConstructor() + ->getMock(); + + $this->mongo->expects($this->once()) + ->method('selectCollection') + ->with($this->options['database'], $this->options['collection']) + ->will($this->returnValue($collection)); + + $that = $this; + + $collection->expects($this->once()) + ->method('remove') + ->will($this->returnCallback(function($criteria) use ($that) { + $that->assertInstanceOf('MongoDate', $criteria[$that->options['time_field']]['$lt']); + $that->assertGreaterThanOrEqual(time() - -1, $criteria[$that->options['time_field']]['$lt']->sec); + })); + + $this->assertTrue($this->storage->gc(-1)); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php new file mode 100644 index 0000000..20cefab --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/NativeFileSessionHandlerTest.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; + +/** + * Test class for NativeFileSessionHandler. + * + * @author Drak + * + * @runTestsInSeparateProcesses + */ +class NativeFileSessionHandlerTest extends \PHPUnit_Framework_TestCase +{ + public function testConstruct() + { + $storage = new NativeSessionStorage(array('name' => 'TESTING'), new NativeFileSessionHandler(sys_get_temp_dir())); + + if (version_compare(phpversion(), '5.4.0', '<')) { + $this->assertEquals('files', $storage->getSaveHandler()->getSaveHandlerName()); + $this->assertEquals('files', ini_get('session.save_handler')); + } else { + $this->assertEquals('files', $storage->getSaveHandler()->getSaveHandlerName()); + $this->assertEquals('user', ini_get('session.save_handler')); + } + + $this->assertEquals(sys_get_temp_dir(), ini_get('session.save_path')); + $this->assertEquals('TESTING', ini_get('session.name')); + } + + /** + * @dataProvider savePathDataProvider + */ + public function testConstructSavePath($savePath, $expectedSavePath, $path) + { + $handler = new NativeFileSessionHandler($savePath); + $this->assertEquals($expectedSavePath, ini_get('session.save_path')); + $this->assertTrue(is_dir(realpath($path))); + + rmdir($path); + } + + public function savePathDataProvider() + { + $base = sys_get_temp_dir(); + + return array( + array("$base/foo", "$base/foo", "$base/foo"), + array("5;$base/foo", "5;$base/foo", "$base/foo"), + array("5;0600;$base/foo", "5;0600;$base/foo", "$base/foo"), + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testConstructException() + { + $handler = new NativeFileSessionHandler('something;invalid;with;too-many-args'); + } + + public function testConstructDefault() + { + $path = ini_get('session.save_path'); + $storage = new NativeSessionStorage(array('name' => 'TESTING'), new NativeFileSessionHandler()); + + $this->assertEquals($path, ini_get('session.save_path')); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php new file mode 100644 index 0000000..7880615 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/NativeSessionHandlerTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler; + +/** + * Test class for NativeSessionHandler. + * + * @author Drak + * + * @runTestsInSeparateProcesses + */ +class NativeSessionHandlerTest extends \PHPUnit_Framework_TestCase +{ + public function testConstruct() + { + $handler = new NativeSessionHandler(); + + // note for PHPUnit optimisers - the use of assertTrue/False + // here is deliberate since the tests do not require the classes to exist - drak + if (version_compare(phpversion(), '5.4.0', '<')) { + $this->assertFalse($handler instanceof \SessionHandler); + $this->assertTrue($handler instanceof NativeSessionHandler); + } else { + $this->assertTrue($handler instanceof \SessionHandler); + $this->assertTrue($handler instanceof NativeSessionHandler); + } + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php new file mode 100644 index 0000000..45a47ce --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/NullSessionHandlerTest.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Session; + +/** + * Test class for NullSessionHandler. + * + * @author Drak + * + * @runTestsInSeparateProcesses + */ +class NullSessionStorageTest extends \PHPUnit_Framework_TestCase +{ + public function testSaveHandlers() + { + $storage = $this->getStorage(); + $this->assertEquals('user', ini_get('session.save_handler')); + } + + public function testSession() + { + session_id('nullsessionstorage'); + $storage = $this->getStorage(); + $session = new Session($storage); + $this->assertNull($session->get('something')); + $session->set('something', 'unique'); + $this->assertEquals('unique', $session->get('something')); + } + + public function testNothingIsPersisted() + { + session_id('nullsessionstorage'); + $storage = $this->getStorage(); + $session = new Session($storage); + $session->start(); + $this->assertEquals('nullsessionstorage', $session->getId()); + $this->assertNull($session->get('something')); + } + + public function getStorage() + { + return new NativeSessionStorage(array(), new NullSessionHandler()); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php new file mode 100644 index 0000000..1abf384 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/PdoSessionHandlerTest.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; + +class PdoSessionHandlerTest extends \PHPUnit_Framework_TestCase +{ + private $pdo; + + protected function setUp() + { + if (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers())) { + $this->markTestSkipped('This test requires SQLite support in your environment'); + } + + $this->pdo = new \PDO("sqlite::memory:"); + $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $sql = "CREATE TABLE sessions (sess_id VARCHAR(255) PRIMARY KEY, sess_data TEXT, sess_time INTEGER)"; + $this->pdo->exec($sql); + } + + public function testIncompleteOptions() + { + $this->setExpectedException('InvalidArgumentException'); + $storage = new PdoSessionHandler($this->pdo, array(), array()); + } + + public function testWrongPdoErrMode() + { + $pdo = new \PDO("sqlite::memory:"); + $pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_SILENT); + $pdo->exec("CREATE TABLE sessions (sess_id VARCHAR(255) PRIMARY KEY, sess_data TEXT, sess_time INTEGER)"); + + $this->setExpectedException('InvalidArgumentException'); + $storage = new PdoSessionHandler($pdo, array('db_table' => 'sessions'), array()); + } + + public function testWrongTableOptionsWrite() + { + $storage = new PdoSessionHandler($this->pdo, array('db_table' => 'bad_name'), array()); + $this->setExpectedException('RuntimeException'); + $storage->write('foo', 'bar'); + } + + public function testWrongTableOptionsRead() + { + $storage = new PdoSessionHandler($this->pdo, array('db_table' => 'bad_name'), array()); + $this->setExpectedException('RuntimeException'); + $storage->read('foo', 'bar'); + } + + public function testWriteRead() + { + $storage = new PdoSessionHandler($this->pdo, array('db_table' => 'sessions'), array()); + $storage->write('foo', 'bar'); + $this->assertEquals('bar', $storage->read('foo'), 'written value can be read back correctly'); + } + + public function testMultipleInstances() + { + $storage1 = new PdoSessionHandler($this->pdo, array('db_table' => 'sessions'), array()); + $storage1->write('foo', 'bar'); + + $storage2 = new PdoSessionHandler($this->pdo, array('db_table' => 'sessions'), array()); + $this->assertEquals('bar', $storage2->read('foo'), 'values persist between instances'); + } + + public function testSessionDestroy() + { + $storage = new PdoSessionHandler($this->pdo, array('db_table' => 'sessions'), array()); + $storage->write('foo', 'bar'); + $this->assertEquals(1, count($this->pdo->query('SELECT * FROM sessions')->fetchAll())); + + $storage->destroy('foo'); + + $this->assertEquals(0, count($this->pdo->query('SELECT * FROM sessions')->fetchAll())); + } + + public function testSessionGC() + { + $storage = new PdoSessionHandler($this->pdo, array('db_table' => 'sessions'), array()); + + $storage->write('foo', 'bar'); + $storage->write('baz', 'bar'); + + $this->assertEquals(2, count($this->pdo->query('SELECT * FROM sessions')->fetchAll())); + + $storage->gc(-1); + $this->assertEquals(0, count($this->pdo->query('SELECT * FROM sessions')->fetchAll())); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/MetadataBagTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/MetadataBagTest.php new file mode 100644 index 0000000..ef70281 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/MetadataBagTest.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; + +/** + * Test class for MetadataBag. + */ +class MetadataBagTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var MetadataBag + */ + protected $bag; + + /** + * @var array + */ + protected $array = array(); + + protected function setUp() + { + $this->bag = new MetadataBag(); + $this->array = array(MetadataBag::CREATED => 1234567, MetadataBag::UPDATED => 12345678, MetadataBag::LIFETIME => 0); + $this->bag->initialize($this->array); + } + + protected function tearDown() + { + $this->array = array(); + $this->bag = null; + } + + public function testInitialize() + { + $p = new \ReflectionProperty('Symfony\Component\HttpFoundation\Session\Storage\MetadataBag', 'meta'); + $p->setAccessible(true); + + $bag1 = new MetadataBag(); + $array = array(); + $bag1->initialize($array); + $this->assertGreaterThanOrEqual(time(), $bag1->getCreated()); + $this->assertEquals($bag1->getCreated(), $bag1->getLastUsed()); + + sleep(1); + $bag2 = new MetadataBag(); + $array2 = $p->getValue($bag1); + $bag2->initialize($array2); + $this->assertEquals($bag1->getCreated(), $bag2->getCreated()); + $this->assertEquals($bag1->getLastUsed(), $bag2->getLastUsed()); + $this->assertEquals($bag2->getCreated(), $bag2->getLastUsed()); + + sleep(1); + $bag3 = new MetadataBag(); + $array3 = $p->getValue($bag2); + $bag3->initialize($array3); + $this->assertEquals($bag1->getCreated(), $bag3->getCreated()); + $this->assertGreaterThan($bag2->getLastUsed(), $bag3->getLastUsed()); + $this->assertNotEquals($bag3->getCreated(), $bag3->getLastUsed()); + } + + public function testGetSetName() + { + $this->assertEquals('__metadata', $this->bag->getName()); + $this->bag->setName('foo'); + $this->assertEquals('foo', $this->bag->getName()); + + } + + public function testGetStorageKey() + { + $this->assertEquals('_sf2_meta', $this->bag->getStorageKey()); + } + + public function testGetLifetime() + { + $bag = new MetadataBag(); + $array = array(MetadataBag::CREATED => 1234567, MetadataBag::UPDATED => 12345678, MetadataBag::LIFETIME => 1000); + $bag->initialize($array); + $this->assertEquals(1000, $bag->getLifetime()); + } + + public function testGetCreated() + { + $this->assertEquals(1234567, $this->bag->getCreated()); + } + + public function testGetLastUsed() + { + $this->assertLessThanOrEqual(time(), $this->bag->getLastUsed()); + } + + public function testClear() + { + $this->bag->clear(); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/MockArraySessionStorageTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/MockArraySessionStorageTest.php new file mode 100644 index 0000000..2a6f6bb --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/MockArraySessionStorageTest.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; + +/** + * Test class for MockArraySessionStorage. + * + * @author Drak + */ +class MockArraySessionStorageTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var MockArraySessionStorage + */ + private $storage; + + /** + * @var AttributeBag + */ + private $attributes; + + /** + * @var FlashBag + */ + private $flashes; + + private $data; + + protected function setUp() + { + $this->attributes = new AttributeBag(); + $this->flashes = new FlashBag(); + + $this->data = array( + $this->attributes->getStorageKey() => array('foo' => 'bar'), + $this->flashes->getStorageKey() => array('notice' => 'hello'), + ); + + $this->storage = new MockArraySessionStorage(); + $this->storage->registerBag($this->flashes); + $this->storage->registerBag($this->attributes); + $this->storage->setSessionData($this->data); + } + + protected function tearDown() + { + $this->data = null; + $this->flashes = null; + $this->attributes = null; + $this->storage = null; + } + + public function testStart() + { + $this->assertEquals('', $this->storage->getId()); + $this->storage->start(); + $id = $this->storage->getId(); + $this->assertNotEquals('', $id); + $this->storage->start(); + $this->assertEquals($id, $this->storage->getId()); + } + + public function testRegenerate() + { + $this->storage->start(); + $id = $this->storage->getId(); + $this->storage->regenerate(); + $this->assertNotEquals($id, $this->storage->getId()); + $this->assertEquals(array('foo' => 'bar'), $this->storage->getBag('attributes')->all()); + $this->assertEquals(array('notice' => 'hello'), $this->storage->getBag('flashes')->peekAll()); + + $id = $this->storage->getId(); + $this->storage->regenerate(true); + $this->assertNotEquals($id, $this->storage->getId()); + $this->assertEquals(array('foo' => 'bar'), $this->storage->getBag('attributes')->all()); + $this->assertEquals(array('notice' => 'hello'), $this->storage->getBag('flashes')->peekAll()); + } + + public function testGetId() + { + $this->assertEquals('', $this->storage->getId()); + $this->storage->start(); + $this->assertNotEquals('', $this->storage->getId()); + } + + /** + * @expectedException RuntimeException + */ + public function testUnstartedSave() + { + $this->storage->save(); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/MockFileSessionStorageTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/MockFileSessionStorageTest.php new file mode 100644 index 0000000..d42c62a --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/MockFileSessionStorageTest.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; + +/** + * Test class for MockFileSessionStorage. + * + * @author Drak + */ +class MockFileSessionStorageTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var string + */ + private $sessionDir; + + /** + * @var FileMockSessionStorage + */ + protected $storage; + + protected function setUp() + { + $this->sessionDir = sys_get_temp_dir().'/sf2test'; + $this->storage = $this->getStorage(); + } + + protected function tearDown() + { + $this->sessionDir = null; + $this->storage = null; + array_map('unlink', glob($this->sessionDir.'/*.session')); + if (is_dir($this->sessionDir)) { + rmdir($this->sessionDir); + } + } + + public function testStart() + { + $this->assertEquals('', $this->storage->getId()); + $this->assertTrue($this->storage->start()); + $id = $this->storage->getId(); + $this->assertNotEquals('', $this->storage->getId()); + $this->assertTrue($this->storage->start()); + $this->assertEquals($id, $this->storage->getId()); + } + + public function testRegenerate() + { + $this->storage->start(); + $this->storage->getBag('attributes')->set('regenerate', 1234); + $this->storage->regenerate(); + $this->assertEquals(1234, $this->storage->getBag('attributes')->get('regenerate')); + $this->storage->regenerate(true); + $this->assertEquals(1234, $this->storage->getBag('attributes')->get('regenerate')); + } + + public function testGetId() + { + $this->assertEquals('', $this->storage->getId()); + $this->storage->start(); + $this->assertNotEquals('', $this->storage->getId()); + } + + public function testSave() + { + $this->storage->start(); + $id = $this->storage->getId(); + $this->assertNotEquals('108', $this->storage->getBag('attributes')->get('new')); + $this->assertFalse($this->storage->getBag('flashes')->has('newkey')); + $this->storage->getBag('attributes')->set('new', '108'); + $this->storage->getBag('flashes')->set('newkey', 'test'); + $this->storage->save(); + + $storage = $this->getStorage(); + $storage->setId($id); + $storage->start(); + $this->assertEquals('108', $storage->getBag('attributes')->get('new')); + $this->assertTrue($storage->getBag('flashes')->has('newkey')); + $this->assertEquals(array('test'), $storage->getBag('flashes')->peek('newkey')); + } + + public function testMultipleInstances() + { + $storage1 = $this->getStorage(); + $storage1->start(); + $storage1->getBag('attributes')->set('foo', 'bar'); + $storage1->save(); + + $storage2 = $this->getStorage(); + $storage2->setId($storage1->getId()); + $storage2->start(); + $this->assertEquals('bar', $storage2->getBag('attributes')->get('foo'), 'values persist between instances'); + } + + /** + * @expectedException RuntimeException + */ + public function testSaveWithoutStart() + { + $storage1 = $this->getStorage(); + $storage1->save(); + } + + private function getStorage() + { + $storage = new MockFileSessionStorage($this->sessionDir); + $storage->registerBag(new FlashBag()); + $storage->registerBag(new AttributeBag()); + + return $storage; + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php new file mode 100644 index 0000000..14ae52f --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; + +/** + * Test class for NativeSessionStorage. + * + * @author Drak + * + * These tests require separate processes. + * + * @runTestsInSeparateProcesses + */ +class NativeSessionStorageTest extends \PHPUnit_Framework_TestCase +{ + private $savePath; + + protected function setUp() + { + ini_set('session.save_handler', 'files'); + ini_set('session.save_path', $this->savePath = sys_get_temp_dir().'/sf2test'); + if (!is_dir($this->savePath)) { + mkdir($this->savePath); + } + } + + protected function tearDown() + { + session_write_close(); + array_map('unlink', glob($this->savePath.'/*')); + if (is_dir($this->savePath)) { + rmdir($this->savePath); + } + + $this->savePath = null; + } + + /** + * @param array $options + * + * @return NativeSessionStorage + */ + protected function getStorage(array $options = array()) + { + $storage = new NativeSessionStorage($options); + $storage->registerBag(new AttributeBag); + + return $storage; + } + + public function testBag() + { + $storage = $this->getStorage(); + $bag = new FlashBag(); + $storage->registerBag($bag); + $this->assertSame($bag, $storage->getBag($bag->getName())); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRegisterBagException() + { + $storage = $this->getStorage(); + $storage->getBag('non_existing'); + } + + public function testGetId() + { + $storage = $this->getStorage(); + $this->assertEquals('', $storage->getId()); + $storage->start(); + $this->assertNotEquals('', $storage->getId()); + } + + public function testRegenerate() + { + $storage = $this->getStorage(); + $storage->start(); + $id = $storage->getId(); + $storage->getBag('attributes')->set('lucky', 7); + $storage->regenerate(); + $this->assertNotEquals($id, $storage->getId()); + $this->assertEquals(7, $storage->getBag('attributes')->get('lucky')); + } + + public function testRegenerateDestroy() + { + $storage = $this->getStorage(); + $storage->start(); + $id = $storage->getId(); + $storage->getBag('attributes')->set('legs', 11); + $storage->regenerate(true); + $this->assertNotEquals($id, $storage->getId()); + $this->assertEquals(11, $storage->getBag('attributes')->get('legs')); + } + + public function testDefaultSessionCacheLimiter() + { + ini_set('session.cache_limiter', 'nocache'); + + $storage = new NativeSessionStorage(); + $this->assertEquals('', ini_get('session.cache_limiter')); + } + + public function testExplicitSessionCacheLimiter() + { + ini_set('session.cache_limiter', 'nocache'); + + $storage = new NativeSessionStorage(array('cache_limiter' => 'public')); + $this->assertEquals('public', ini_get('session.cache_limiter')); + } + + public function testCookieOptions() + { + $options = array( + 'cookie_lifetime' => 123456, + 'cookie_path' => '/my/cookie/path', + 'cookie_domain' => 'symfony2.example.com', + 'cookie_secure' => true, + 'cookie_httponly' => false, + ); + + $this->getStorage($options); + $temp = session_get_cookie_params(); + $gco = array(); + + foreach ($temp as $key => $value) { + $gco['cookie_'.$key] = $value; + } + + $this->assertEquals($options, $gco); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSetSaveHandlerException() + { + $storage = $this->getStorage(); + $storage->setSaveHandler(new \StdClass); + } + + public function testSetSaveHandler53() + { + if (version_compare(phpversion(), '5.4.0', '>=')) { + $this->markTestSkipped('Test skipped, for PHP 5.3 only.'); + } + + ini_set('session.save_handler', 'files'); + $storage = $this->getStorage(); + $storage->setSaveHandler(); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(null); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(new NativeSessionHandler()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(new SessionHandlerProxy(new SessionHandler())); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(new SessionHandler()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(new NativeProxy()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy', $storage->getSaveHandler()); + } + + public function testSetSaveHandler54() + { + if (version_compare(phpversion(), '5.4.0', '<')) { + $this->markTestSkipped('Test skipped, for PHP 5.4 only.'); + } + + ini_set('session.save_handler', 'files'); + $storage = $this->getStorage(); + $storage->setSaveHandler(); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(null); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(new SessionHandlerProxy(new NativeSessionHandler())); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(new NativeSessionHandler()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(new SessionHandlerProxy(new SessionHandler())); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + $storage->setSaveHandler(new SessionHandler()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); + } + + /** + * @expectedException \RuntimeException + */ + public function testStartedOutside53() + { + if (version_compare(phpversion(), '5.4.0', '>=')) { + $this->markTestSkipped('Test skipped, for PHP 5.3 only.'); + } + + $storage = $this->getStorage(); + + $this->assertFalse(isset($_SESSION)); + + session_start(); + $this->assertTrue(isset($_SESSION)); + // PHP session might have started, but the storage driver has not, so false is correct here + $this->assertFalse($storage->isStarted()); + + $key = $storage->getMetadataBag()->getStorageKey(); + $this->assertFalse(isset($_SESSION[$key])); + $storage->start(); + } + + /** + * @expectedException \RuntimeException + */ + public function testCanStartOutside54() + { + if (version_compare(phpversion(), '5.4.0', '<')) { + $this->markTestSkipped('Test skipped, for PHP 5.4 only.'); + } + + $storage = $this->getStorage(); + + $this->assertFalse(isset($_SESSION)); + $this->assertFalse($storage->getSaveHandler()->isActive()); + $this->assertFalse($storage->isStarted()); + + session_start(); + $this->assertTrue(isset($_SESSION)); + $this->assertTrue($storage->getSaveHandler()->isActive()); + // PHP session might have started, but the storage driver has not, so false is correct here + $this->assertFalse($storage->isStarted()); + + $key = $storage->getMetadataBag()->getStorageKey(); + $this->assertFalse(isset($_SESSION[$key])); + $storage->start(); + } +} + +class SessionHandler implements \SessionHandlerInterface +{ + public function open($savePath, $sessionName) + { + } + + public function close() + { + } + + public function read($id) + { + } + + public function write($id, $data) + { + } + + public function destroy($id) + { + } + + public function gc($maxlifetime) + { + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php new file mode 100644 index 0000000..d5a66d6 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/PhpBridgeSessionStorageTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\Storage\PhpBridgeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; + +/** + * Test class for PhpSessionStorage. + * + * @author Drak + * + * These tests require separate processes. + * + * @runTestsInSeparateProcesses + */ +class PhpSessionStorageTest extends \PHPUnit_Framework_TestCase +{ + private $savePath; + + protected function setUp() + { + ini_set('session.save_handler', 'files'); + ini_set('session.save_path', $this->savePath = sys_get_temp_dir().'/sf2test'); + if (!is_dir($this->savePath)) { + mkdir($this->savePath); + } + } + + protected function tearDown() + { + session_write_close(); + array_map('unlink', glob($this->savePath.'/*')); + if (is_dir($this->savePath)) { + rmdir($this->savePath); + } + + $this->savePath = null; + } + + /** + * @return PhpBridgeSessionStorage + */ + protected function getStorage() + { + $storage = new PhpBridgeSessionStorage(); + $storage->registerBag(new AttributeBag); + + return $storage; + } + + public function testPhpSession53() + { + if (version_compare(phpversion(), '5.4.0', '>=')) { + $this->markTestSkipped('Test skipped, for PHP 5.3 only.'); + } + + $storage = $this->getStorage(); + + $this->assertFalse(isset($_SESSION)); + $this->assertFalse($storage->getSaveHandler()->isActive()); + + session_start(); + $this->assertTrue(isset($_SESSION)); + // in PHP 5.3 we cannot reliably tell if a session has started + $this->assertFalse($storage->getSaveHandler()->isActive()); + // PHP session might have started, but the storage driver has not, so false is correct here + $this->assertFalse($storage->isStarted()); + + $key = $storage->getMetadataBag()->getStorageKey(); + $this->assertFalse(isset($_SESSION[$key])); + $storage->start(); + $this->assertTrue(isset($_SESSION[$key])); + } + + public function testPhpSession54() + { + if (version_compare(phpversion(), '5.4.0', '<')) { + $this->markTestSkipped('Test skipped, for PHP 5.4 only.'); + } + + $storage = $this->getStorage(); + + $this->assertFalse(isset($_SESSION)); + $this->assertFalse($storage->getSaveHandler()->isActive()); + $this->assertFalse($storage->isStarted()); + + session_start(); + $this->assertTrue(isset($_SESSION)); + // in PHP 5.4 we can reliably detect a session started + $this->assertTrue($storage->getSaveHandler()->isActive()); + // PHP session might have started, but the storage driver has not, so false is correct here + $this->assertFalse($storage->isStarted()); + + $key = $storage->getMetadataBag()->getStorageKey(); + $this->assertFalse(isset($_SESSION[$key])); + $storage->start(); + $this->assertTrue(isset($_SESSION[$key])); + } + + public function testClear() + { + $storage = $this->getStorage(); + session_start(); + $_SESSION['drak'] = 'loves symfony'; + $storage->getBag('attributes')->set('symfony', 'greatness'); + $key = $storage->getBag('attributes')->getStorageKey(); + $this->assertEquals($_SESSION[$key], array('symfony' => 'greatness')); + $this->assertEquals($_SESSION['drak'], 'loves symfony'); + $storage->clear(); + $this->assertEquals($_SESSION[$key], array()); + $this->assertEquals($_SESSION['drak'], 'loves symfony'); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php new file mode 100644 index 0000000..6b8bba0 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy; + +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; + +// Note until PHPUnit_Mock_Objects 1.2 is released you cannot mock abstracts due to +// https://github.com/sebastianbergmann/phpunit-mock-objects/issues/73 +class ConcreteProxy extends AbstractProxy +{ + +} + +class ConcreteSessionHandlerInterfaceProxy extends AbstractProxy implements \SessionHandlerInterface +{ + public function open($savePath, $sessionName) + { + } + + public function close() + { + } + + public function read($id) + { + } + + public function write($id, $data) + { + } + + public function destroy($id) + { + } + + public function gc($maxlifetime) + { + } +} + +/** + * Test class for AbstractProxy. + * + * @author Drak + */ +class AbstractProxyTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var AbstractProxy + */ + protected $proxy; + + protected function setUp() + { + $this->proxy = new ConcreteProxy(); + } + + protected function tearDown() + { + $this->proxy = null; + } + + public function testGetSaveHandlerName() + { + $this->assertNull($this->proxy->getSaveHandlerName()); + } + + public function testIsSessionHandlerInterface() + { + $this->assertFalse($this->proxy->isSessionHandlerInterface()); + $sh = new ConcreteSessionHandlerInterfaceProxy(); + $this->assertTrue($sh->isSessionHandlerInterface()); + } + + public function testIsWrapper() + { + $this->assertFalse($this->proxy->isWrapper()); + } + + public function testIsActivePhp53() + { + if (version_compare(phpversion(), '5.4.0', '>=')) { + $this->markTestSkipped('Test skipped, for PHP 5.3 only.'); + } + + $this->assertFalse($this->proxy->isActive()); + } + + /** + * @runInSeparateProcess + */ + public function testIsActivePhp54() + { + if (version_compare(phpversion(), '5.4.0', '<')) { + $this->markTestSkipped('Test skipped, for PHP 5.4 only.'); + } + + $this->assertFalse($this->proxy->isActive()); + session_start(); + $this->assertTrue($this->proxy->isActive()); + } + + public function testSetActivePhp53() + { + if (version_compare(phpversion(), '5.4.0', '>=')) { + $this->markTestSkipped('Test skipped, for PHP 5.3 only.'); + } + + $this->proxy->setActive(true); + $this->assertTrue($this->proxy->isActive()); + $this->proxy->setActive(false); + $this->assertFalse($this->proxy->isActive()); + } + + /** + * @runInSeparateProcess + * @expectedException \LogicException + */ + public function testSetActivePhp54() + { + if (version_compare(phpversion(), '5.4.0', '<')) { + $this->markTestSkipped('Test skipped, for PHP 5.4 only.'); + } + + $this->proxy->setActive(true); + } + + /** + * @runInSeparateProcess + */ + public function testName() + { + $this->assertEquals(session_name(), $this->proxy->getName()); + $this->proxy->setName('foo'); + $this->assertEquals('foo', $this->proxy->getName()); + $this->assertEquals(session_name(), $this->proxy->getName()); + } + + /** + * @expectedException \LogicException + */ + public function testNameExceptionPhp53() + { + if (version_compare(phpversion(), '5.4.0', '>=')) { + $this->markTestSkipped('Test skipped, for PHP 5.3 only.'); + } + + $this->proxy->setActive(true); + $this->proxy->setName('foo'); + } + + /** + * @runInSeparateProcess + * @expectedException \LogicException + */ + public function testNameExceptionPhp54() + { + if (version_compare(phpversion(), '5.4.0', '<')) { + $this->markTestSkipped('Test skipped, for PHP 5.4 only.'); + } + + session_start(); + $this->proxy->setName('foo'); + } + + /** + * @runInSeparateProcess + */ + public function testId() + { + $this->assertEquals(session_id(), $this->proxy->getId()); + $this->proxy->setId('foo'); + $this->assertEquals('foo', $this->proxy->getId()); + $this->assertEquals(session_id(), $this->proxy->getId()); + } + + /** + * @expectedException \LogicException + */ + public function testIdExceptionPhp53() + { + if (version_compare(phpversion(), '5.4.0', '>=')) { + $this->markTestSkipped('Test skipped, for PHP 5.3 only.'); + } + + $this->proxy->setActive(true); + $this->proxy->setId('foo'); + } + + /** + * @runInSeparateProcess + * @expectedException \LogicException + */ + public function testIdExceptionPhp54() + { + if (version_compare(phpversion(), '5.4.0', '<')) { + $this->markTestSkipped('Test skipped, for PHP 5.4 only.'); + } + + session_start(); + $this->proxy->setId('foo'); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/NativeProxyTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/NativeProxyTest.php new file mode 100644 index 0000000..e9184ce --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/NativeProxyTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy; + +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy; + +/** + * Test class for NativeProxy. + * + * @author Drak + */ +class NativeProxyTest extends \PHPUnit_Framework_TestCase +{ + public function testIsWrapper() + { + $proxy = new NativeProxy(); + $this->assertFalse($proxy->isWrapper()); + } + + public function testGetSaveHandlerName() + { + $name = ini_get('session.save_handler'); + $proxy = new NativeProxy(); + $this->assertEquals($name, $proxy->getSaveHandlerName()); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php new file mode 100644 index 0000000..74d8419 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Proxy; + +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; + +/** + * Tests for SessionHandlerProxy class. + * + * @author Drak + * + * @runTestsInSeparateProcesses + */ +class SessionHandlerProxyTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_Matcher + */ + private $mock; + + /** + * @var SessionHandlerProxy + */ + private $proxy; + + protected function setUp() + { + $this->mock = $this->getMock('SessionHandlerInterface'); + $this->proxy = new SessionHandlerProxy($this->mock); + } + + protected function tearDown() + { + $this->mock = null; + $this->proxy = null; + } + + public function testOpen() + { + $this->mock->expects($this->once()) + ->method('open') + ->will($this->returnValue(true)); + + $this->assertFalse($this->proxy->isActive()); + $this->proxy->open('name', 'id'); + if (version_compare(phpversion(), '5.4.0', '<')) { + $this->assertTrue($this->proxy->isActive()); + } else { + $this->assertFalse($this->proxy->isActive()); + } + } + + public function testOpenFalse() + { + $this->mock->expects($this->once()) + ->method('open') + ->will($this->returnValue(false)); + + $this->assertFalse($this->proxy->isActive()); + $this->proxy->open('name', 'id'); + $this->assertFalse($this->proxy->isActive()); + } + + public function testClose() + { + $this->mock->expects($this->once()) + ->method('close') + ->will($this->returnValue(true)); + + $this->assertFalse($this->proxy->isActive()); + $this->proxy->close(); + $this->assertFalse($this->proxy->isActive()); + } + + public function testCloseFalse() + { + $this->mock->expects($this->once()) + ->method('close') + ->will($this->returnValue(false)); + + $this->assertFalse($this->proxy->isActive()); + $this->proxy->close(); + $this->assertFalse($this->proxy->isActive()); + } + + public function testRead() + { + $this->mock->expects($this->once()) + ->method('read'); + + $this->proxy->read('id'); + } + + public function testWrite() + { + $this->mock->expects($this->once()) + ->method('write'); + + $this->proxy->write('id', 'data'); + } + + public function testDestroy() + { + $this->mock->expects($this->once()) + ->method('destroy'); + + $this->proxy->destroy('id'); + } + + public function testGc() + { + $this->mock->expects($this->once()) + ->method('gc'); + + $this->proxy->gc(86400); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/StreamedResponseTest.php b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/StreamedResponseTest.php new file mode 100644 index 0000000..0dfe651 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Tests/StreamedResponseTest.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\StreamedResponse; + +class StreamedResponseTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $response = new StreamedResponse(function () { echo 'foo'; }, 404, array('Content-Type' => 'text/plain')); + + $this->assertEquals(404, $response->getStatusCode()); + $this->assertEquals('text/plain', $response->headers->get('Content-Type')); + } + + public function testPrepareWith11Protocol() + { + $response = new StreamedResponse(function () { echo 'foo'; }); + $request = Request::create('/'); + $request->server->set('SERVER_PROTOCOL', 'HTTP/1.1'); + + $response->prepare($request); + + $this->assertEquals('1.1', $response->getProtocolVersion()); + $this->assertNotEquals('chunked', $response->headers->get('Transfer-Encoding'), 'Apache assumes responses with a Transfer-Encoding header set to chunked to already be encoded.'); + $this->assertEquals('no-cache, private', $response->headers->get('Cache-Control')); + } + + public function testPrepareWith10Protocol() + { + $response = new StreamedResponse(function () { echo 'foo'; }); + $request = Request::create('/'); + $request->server->set('SERVER_PROTOCOL', 'HTTP/1.0'); + + $response->prepare($request); + + $this->assertEquals('1.0', $response->getProtocolVersion()); + $this->assertNull($response->headers->get('Transfer-Encoding')); + $this->assertEquals('no-cache, private', $response->headers->get('Cache-Control')); + } + + public function testPrepareWithHeadRequest() + { + $response = new StreamedResponse(function () { echo 'foo'; }); + $request = Request::create('/', 'HEAD'); + + $response->prepare($request); + } + + public function testSendContent() + { + $called = 0; + + $response = new StreamedResponse(function () use (&$called) { ++$called; }); + + $response->sendContent(); + $this->assertEquals(1, $called); + + $response->sendContent(); + $this->assertEquals(1, $called); + } + + /** + * @expectedException \LogicException + */ + public function testSendContentWithNonCallable() + { + $response = new StreamedResponse(null); + $response->sendContent(); + } + + /** + * @expectedException \LogicException + */ + public function testSetCallbackNonCallable() + { + $response = new StreamedResponse(null); + $response->setCallback(null); + } + + /** + * @expectedException \LogicException + */ + public function testSetContent() + { + $response = new StreamedResponse(function () { echo 'foo'; }); + $response->setContent('foo'); + } + + public function testGetContent() + { + $response = new StreamedResponse(function () { echo 'foo'; }); + $this->assertFalse($response->getContent()); + } + + public function testCreate() + { + $response = StreamedResponse::create(function () {}, 204); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\StreamedResponse', $response); + $this->assertEquals(204, $response->getStatusCode()); + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/composer.json b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/composer.json new file mode 100644 index 0000000..f77e08e --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/composer.json @@ -0,0 +1,32 @@ +{ + "name": "symfony/http-foundation", + "type": "library", + "description": "Symfony HttpFoundation Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\HttpFoundation\\": "" }, + "classmap": [ "Symfony/Component/HttpFoundation/Resources/stubs" ] + }, + "target-dir": "Symfony/Component/HttpFoundation", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + } +} diff --git a/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/phpunit.xml.dist b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/phpunit.xml.dist new file mode 100644 index 0000000..df11f72 --- /dev/null +++ b/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Resources + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/.gitignore b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/.gitignore new file mode 100644 index 0000000..22450b7 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/.gitignore @@ -0,0 +1,5 @@ +vendor/ +composer.lock +phpunit.xml +Tests/ProjectContainer.php +Tests/classes.map diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Bundle/Bundle.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Bundle/Bundle.php new file mode 100644 index 0000000..215d1e1 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Bundle/Bundle.php @@ -0,0 +1,197 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Bundle; + +use Symfony\Component\DependencyInjection\ContainerAware; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\Console\Application; +use Symfony\Component\Finder\Finder; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; + +/** + * An implementation of BundleInterface that adds a few conventions + * for DependencyInjection extensions and Console commands. + * + * @author Fabien Potencier + * + * @api + */ +abstract class Bundle extends ContainerAware implements BundleInterface +{ + protected $name; + protected $reflected; + protected $extension; + + /** + * Boots the Bundle. + */ + public function boot() + { + } + + /** + * Shutdowns the Bundle. + */ + public function shutdown() + { + } + + /** + * Builds the bundle. + * + * It is only ever called once when the cache is empty. + * + * This method can be overridden to register compilation passes, + * other extensions, ... + * + * @param ContainerBuilder $container A ContainerBuilder instance + */ + public function build(ContainerBuilder $container) + { + } + + /** + * Returns the bundle's container extension. + * + * @return ExtensionInterface|null The container extension + * + * @throws \LogicException + * + * @api + */ + public function getContainerExtension() + { + if (null === $this->extension) { + $basename = preg_replace('/Bundle$/', '', $this->getName()); + + $class = $this->getNamespace().'\\DependencyInjection\\'.$basename.'Extension'; + if (class_exists($class)) { + $extension = new $class(); + + // check naming convention + $expectedAlias = Container::underscore($basename); + if ($expectedAlias != $extension->getAlias()) { + throw new \LogicException(sprintf( + 'The extension alias for the default extension of a '. + 'bundle must be the underscored version of the '. + 'bundle name ("%s" instead of "%s")', + $expectedAlias, $extension->getAlias() + )); + } + + $this->extension = $extension; + } else { + $this->extension = false; + } + } + + if ($this->extension) { + return $this->extension; + } + } + + /** + * Gets the Bundle namespace. + * + * @return string The Bundle namespace + * + * @api + */ + public function getNamespace() + { + if (null === $this->reflected) { + $this->reflected = new \ReflectionObject($this); + } + + return $this->reflected->getNamespaceName(); + } + + /** + * Gets the Bundle directory path. + * + * @return string The Bundle absolute path + * + * @api + */ + public function getPath() + { + if (null === $this->reflected) { + $this->reflected = new \ReflectionObject($this); + } + + return dirname($this->reflected->getFileName()); + } + + /** + * Returns the bundle parent name. + * + * @return string The Bundle parent name it overrides or null if no parent + * + * @api + */ + public function getParent() + { + return null; + } + + /** + * Returns the bundle name (the class short name). + * + * @return string The Bundle name + * + * @api + */ + final public function getName() + { + if (null !== $this->name) { + return $this->name; + } + + $name = get_class($this); + $pos = strrpos($name, '\\'); + + return $this->name = false === $pos ? $name : substr($name, $pos + 1); + } + + /** + * Finds and registers Commands. + * + * Override this method if your bundle commands do not follow the conventions: + * + * * Commands are in the 'Command' sub-directory + * * Commands extend Symfony\Component\Console\Command\Command + * + * @param Application $application An Application instance + */ + public function registerCommands(Application $application) + { + if (!is_dir($dir = $this->getPath().'/Command')) { + return; + } + + $finder = new Finder(); + $finder->files()->name('*Command.php')->in($dir); + + $prefix = $this->getNamespace().'\\Command'; + foreach ($finder as $file) { + $ns = $prefix; + if ($relativePath = $file->getRelativePath()) { + $ns .= '\\'.strtr($relativePath, '/', '\\'); + } + $r = new \ReflectionClass($ns.'\\'.$file->getBasename('.php')); + if ($r->isSubclassOf('Symfony\\Component\\Console\\Command\\Command') && !$r->isAbstract() && !$r->getConstructor()->getNumberOfRequiredParameters()) { + $application->add($r->newInstance()); + } + } + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Bundle/BundleInterface.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Bundle/BundleInterface.php new file mode 100644 index 0000000..3203d84 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Bundle/BundleInterface.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Bundle; + +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; + +/** + * BundleInterface. + * + * @author Fabien Potencier + * + * @api + */ +interface BundleInterface extends ContainerAwareInterface +{ + /** + * Boots the Bundle. + * + * @api + */ + public function boot(); + + /** + * Shutdowns the Bundle. + * + * @api + */ + public function shutdown(); + + /** + * Builds the bundle. + * + * It is only ever called once when the cache is empty. + * + * @param ContainerBuilder $container A ContainerBuilder instance + * + * @api + */ + public function build(ContainerBuilder $container); + + /** + * Returns the container extension that should be implicitly loaded. + * + * @return ExtensionInterface|null The default extension or null if there is none + * + * @api + */ + public function getContainerExtension(); + + /** + * Returns the bundle name that this bundle overrides. + * + * Despite its name, this method does not imply any parent/child relationship + * between the bundles, just a way to extend and override an existing + * bundle. + * + * @return string The Bundle name it overrides or null if no parent + * + * @api + */ + public function getParent(); + + /** + * Returns the bundle name (the class short name). + * + * @return string The Bundle name + * + * @api + */ + public function getName(); + + /** + * Gets the Bundle namespace. + * + * @return string The Bundle namespace + * + * @api + */ + public function getNamespace(); + + /** + * Gets the Bundle directory path. + * + * The path should always be returned as a Unix path (with /). + * + * @return string The Bundle absolute path + * + * @api + */ + public function getPath(); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CHANGELOG.md b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CHANGELOG.md new file mode 100644 index 0000000..c06dd3f --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CHANGELOG.md @@ -0,0 +1,51 @@ +CHANGELOG +========= + +2.3.0 +----- + + * [BC BREAK] renamed `Symfony\Component\HttpKernel\EventListener\DeprecationLoggerListener` to `Symfony\Component\HttpKernel\EventListener\ErrorsLoggerListener` and changed its constructor + * deprecated `Symfony\Component\HttpKernel\Debug\ErrorHandler`, `Symfony\Component\HttpKernel\Debug\ExceptionHandler`, + `Symfony\Component\HttpKernel\Exception\FatalErrorException`, and `Symfony\Component\HttpKernel\Exception\FlattenException` + * deprecated `Symfony\Component\HttpKernel\Kernel::init()`` + * added the possibility to specify an id an extra attributes to hinclude tags + * added the collect of data if a controller is a Closure in the Request collector + * pass exceptions from the ExceptionListener to the logger using the logging context to allow for more + detailed messages + +2.2.0 +----- + + * [BC BREAK] the path info for sub-request is now always _fragment (or whatever you configured instead of the default) + * added Symfony\Component\HttpKernel\EventListener\FragmentListener + * added Symfony\Component\HttpKernel\UriSigner + * added Symfony\Component\HttpKernel\FragmentRenderer and rendering strategies (in Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface) + * added Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel + * added ControllerReference to create reference of Controllers (used in the FragmentRenderer class) + * [BC BREAK] renamed TimeDataCollector::getTotalTime() to + TimeDataCollector::getDuration() + * updated the MemoryDataCollector to include the memory used in the + kernel.terminate event listeners + * moved the Stopwatch classes to a new component + * added TraceableControllerResolver + * added TraceableEventDispatcher (removed ContainerAwareTraceableEventDispatcher) + * added support for WinCache opcode cache in ConfigDataCollector + +2.1.0 +----- + + * [BC BREAK] the charset is now configured via the Kernel::getCharset() method + * [BC BREAK] the current locale for the user is not stored anymore in the session + * added the HTTP method to the profiler storage + * updated all listeners to implement EventSubscriberInterface + * added TimeDataCollector + * added ContainerAwareTraceableEventDispatcher + * moved TraceableEventDispatcherInterface to the EventDispatcher component + * added RouterListener, LocaleListener, and StreamedResponseListener + * added CacheClearerInterface (and ChainCacheClearer) + * added a kernel.terminate event (via TerminableInterface and PostResponseEvent) + * added a Stopwatch class + * added WarmableInterface + * improved extensibility between bundles + * added profiler storages for Memcache(d), File-based, MongoDB, Redis + * moved Filesystem class to its own component diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CacheClearer/CacheClearerInterface.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CacheClearer/CacheClearerInterface.php new file mode 100644 index 0000000..d4a2db3 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CacheClearer/CacheClearerInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheClearer; + +/** + * CacheClearerInterface. + * + * @author Dustin Dobervich + */ +interface CacheClearerInterface +{ + /** + * Clears any caches necessary. + * + * @param string $cacheDir The cache directory. + */ + public function clear($cacheDir); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CacheClearer/ChainCacheClearer.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CacheClearer/ChainCacheClearer.php new file mode 100644 index 0000000..7b492d0 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CacheClearer/ChainCacheClearer.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheClearer; + +/** + * ChainCacheClearer. + * + * @author Dustin Dobervich + */ +class ChainCacheClearer implements CacheClearerInterface +{ + /** + * @var array $clearers + */ + protected $clearers; + + /** + * Constructs a new instance of ChainCacheClearer. + * + * @param array $clearers The initial clearers. + */ + public function __construct(array $clearers = array()) + { + $this->clearers = $clearers; + } + + /** + * {@inheritDoc} + */ + public function clear($cacheDir) + { + foreach ($this->clearers as $clearer) { + $clearer->clear($cacheDir); + } + } + + /** + * Adds a cache clearer to the aggregate. + * + * @param CacheClearerInterface $clearer + */ + public function add(CacheClearerInterface $clearer) + { + $this->clearers[] = $clearer; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmer.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmer.php new file mode 100644 index 0000000..948b3ff --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmer.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +/** + * Abstract cache warmer that knows how to write a file to the cache. + * + * @author Fabien Potencier + */ +abstract class CacheWarmer implements CacheWarmerInterface +{ + protected function writeCacheFile($file, $content) + { + $tmpFile = tempnam(dirname($file), basename($file)); + if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) { + @chmod($file, 0666 & ~umask()); + + return; + } + + throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file)); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php new file mode 100644 index 0000000..eb26ac5 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerAggregate.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +/** + * Aggregates several cache warmers into a single one. + * + * @author Fabien Potencier + */ +class CacheWarmerAggregate implements CacheWarmerInterface +{ + protected $warmers; + protected $optionalsEnabled; + + public function __construct(array $warmers = array()) + { + $this->setWarmers($warmers); + $this->optionalsEnabled = false; + } + + public function enableOptionalWarmers() + { + $this->optionalsEnabled = true; + } + + /** + * Warms up the cache. + * + * @param string $cacheDir The cache directory + */ + public function warmUp($cacheDir) + { + foreach ($this->warmers as $warmer) { + if (!$this->optionalsEnabled && $warmer->isOptional()) { + continue; + } + + $warmer->warmUp($cacheDir); + } + } + + /** + * Checks whether this warmer is optional or not. + * + * @return Boolean always true + */ + public function isOptional() + { + return false; + } + + public function setWarmers(array $warmers) + { + $this->warmers = array(); + foreach ($warmers as $warmer) { + $this->add($warmer); + } + } + + public function add(CacheWarmerInterface $warmer) + { + $this->warmers[] = $warmer; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerInterface.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerInterface.php new file mode 100644 index 0000000..ed76ce3 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CacheWarmer/CacheWarmerInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +/** + * Interface for classes able to warm up the cache. + * + * @author Fabien Potencier + */ +interface CacheWarmerInterface extends WarmableInterface +{ + /** + * Checks whether this warmer is optional or not. + * + * Optional warmers can be ignored on certain conditions. + * + * A warmer should return true if the cache can be + * generated incrementally and on-demand. + * + * @return Boolean true if the warmer is optional, false otherwise + */ + public function isOptional(); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CacheWarmer/WarmableInterface.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CacheWarmer/WarmableInterface.php new file mode 100644 index 0000000..25d8ee8 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/CacheWarmer/WarmableInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +/** + * Interface for classes that support warming their cache. + * + * @author Fabien Potencier + */ +interface WarmableInterface +{ + /** + * Warms up the cache. + * + * @param string $cacheDir The cache directory + */ + public function warmUp($cacheDir); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Client.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Client.php new file mode 100644 index 0000000..bb427b3 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Client.php @@ -0,0 +1,211 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\BrowserKit\Client as BaseClient; +use Symfony\Component\BrowserKit\Request as DomRequest; +use Symfony\Component\BrowserKit\Response as DomResponse; +use Symfony\Component\BrowserKit\Cookie as DomCookie; +use Symfony\Component\BrowserKit\History; +use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\HttpKernel\TerminableInterface; + +/** + * Client simulates a browser and makes requests to a Kernel object. + * + * @author Fabien Potencier + * + * @api + */ +class Client extends BaseClient +{ + protected $kernel; + + /** + * Constructor. + * + * @param HttpKernelInterface $kernel An HttpKernel instance + * @param array $server The server parameters (equivalent of $_SERVER) + * @param History $history A History instance to store the browser history + * @param CookieJar $cookieJar A CookieJar instance to store the cookies + */ + public function __construct(HttpKernelInterface $kernel, array $server = array(), History $history = null, CookieJar $cookieJar = null) + { + $this->kernel = $kernel; + + parent::__construct($server, $history, $cookieJar); + + $this->followRedirects = false; + } + + /** + * {@inheritdoc} + * + * @return Request|null A Request instance + */ + public function getRequest() + { + return parent::getRequest(); + } + + /** + * {@inheritdoc} + * + * @return Response|null A Response instance + */ + public function getResponse() + { + return parent::getResponse(); + } + + /** + * Makes a request. + * + * @param Request $request A Request instance + * + * @return Response A Response instance + */ + protected function doRequest($request) + { + $response = $this->kernel->handle($request); + + if ($this->kernel instanceof TerminableInterface) { + $this->kernel->terminate($request, $response); + } + + return $response; + } + + /** + * Returns the script to execute when the request must be insulated. + * + * @param Request $request A Request instance + * + * @return string + */ + protected function getScript($request) + { + $kernel = str_replace("'", "\\'", serialize($this->kernel)); + $request = str_replace("'", "\\'", serialize($request)); + + $r = new \ReflectionClass('\\Symfony\\Component\\ClassLoader\\ClassLoader'); + $requirePath = str_replace("'", "\\'", $r->getFileName()); + $symfonyPath = str_replace("'", "\\'", realpath(__DIR__.'/../../..')); + + return <<addPrefix('Symfony', '$symfonyPath'); +\$loader->register(); + +\$kernel = unserialize('$kernel'); +echo serialize(\$kernel->handle(unserialize('$request'))); +EOF; + } + + /** + * Converts the BrowserKit request to a HttpKernel request. + * + * @param DomRequest $request A DomRequest instance + * + * @return Request A Request instance + */ + protected function filterRequest(DomRequest $request) + { + $httpRequest = Request::create($request->getUri(), $request->getMethod(), $request->getParameters(), $request->getCookies(), $request->getFiles(), $request->getServer(), $request->getContent()); + + $httpRequest->files->replace($this->filterFiles($httpRequest->files->all())); + + return $httpRequest; + } + + /** + * Filters an array of files. + * + * This method created test instances of UploadedFile so that the move() + * method can be called on those instances. + * + * If the size of a file is greater than the allowed size (from php.ini) then + * an invalid UploadedFile is returned with an error set to UPLOAD_ERR_INI_SIZE. + * + * @see Symfony\Component\HttpFoundation\File\UploadedFile + * + * @param array $files An array of files + * + * @return array An array with all uploaded files marked as already moved + */ + protected function filterFiles(array $files) + { + $filtered = array(); + foreach ($files as $key => $value) { + if (is_array($value)) { + $filtered[$key] = $this->filterFiles($value); + } elseif ($value instanceof UploadedFile) { + if ($value->isValid() && $value->getSize() > UploadedFile::getMaxFilesize()) { + $filtered[$key] = new UploadedFile( + '', + $value->getClientOriginalName(), + $value->getClientMimeType(), + 0, + UPLOAD_ERR_INI_SIZE, + true + ); + } else { + $filtered[$key] = new UploadedFile( + $value->getPathname(), + $value->getClientOriginalName(), + $value->getClientMimeType(), + $value->getClientSize(), + $value->getError(), + true + ); + } + } else { + $filtered[$key] = $value; + } + } + + return $filtered; + } + + /** + * Converts the HttpKernel response to a BrowserKit response. + * + * @param Response $response A Response instance + * + * @return DomResponse A DomResponse instance + */ + protected function filterResponse($response) + { + $headers = $response->headers->all(); + if ($response->headers->getCookies()) { + $cookies = array(); + foreach ($response->headers->getCookies() as $cookie) { + $cookies[] = new DomCookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); + } + $headers['Set-Cookie'] = $cookies; + } + + // this is needed to support StreamedResponse + ob_start(); + $response->sendContent(); + $content = ob_get_clean(); + + return new DomResponse($content, $response->getStatusCode(), $headers); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Config/FileLocator.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Config/FileLocator.php new file mode 100644 index 0000000..47b543c --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Config/FileLocator.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Config; + +use Symfony\Component\Config\FileLocator as BaseFileLocator; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * FileLocator uses the KernelInterface to locate resources in bundles. + * + * @author Fabien Potencier + */ +class FileLocator extends BaseFileLocator +{ + private $kernel; + private $path; + + /** + * Constructor. + * + * @param KernelInterface $kernel A KernelInterface instance + * @param null|string $path The path the global resource directory + * @param array $paths An array of paths where to look for resources + */ + public function __construct(KernelInterface $kernel, $path = null, array $paths = array()) + { + $this->kernel = $kernel; + if (null !== $path) { + $this->path = $path; + $paths[] = $path; + } + + parent::__construct($paths); + } + + /** + * {@inheritdoc} + */ + public function locate($file, $currentPath = null, $first = true) + { + if ('@' === $file[0]) { + return $this->kernel->locateResource($file, $this->path, $first); + } + + return parent::locate($file, $currentPath, $first); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Controller/ControllerReference.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Controller/ControllerReference.php new file mode 100644 index 0000000..22d6cd3 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Controller/ControllerReference.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +/** + * Acts as a marker and a data holder for a Controller. + * + * Some methods in Symfony accept both a URI (as a string) or a controller as + * an argument. In the latter case, instead of passing an array representing + * the controller, you can use an instance of this class. + * + * @author Fabien Potencier + * + * @see Symfony\Component\HttpKernel\FragmentRenderer + * @see Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface + */ +class ControllerReference +{ + public $controller; + public $attributes = array(); + public $query = array(); + + /** + * Constructor. + * + * @param string $controller The controller name + * @param array $attributes An array of parameters to add to the Request attributes + * @param array $query An array of parameters to add to the Request query string + */ + public function __construct($controller, array $attributes = array(), array $query = array()) + { + $this->controller = $controller; + $this->attributes = $attributes; + $this->query = $query; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Controller/ControllerResolver.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Controller/ControllerResolver.php new file mode 100644 index 0000000..047ade1 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Controller/ControllerResolver.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * ControllerResolver. + * + * This implementation uses the '_controller' request attribute to determine + * the controller to execute and uses the request attributes to determine + * the controller method arguments. + * + * @author Fabien Potencier + * + * @api + */ +class ControllerResolver implements ControllerResolverInterface +{ + private $logger; + + /** + * Constructor. + * + * @param LoggerInterface $logger A LoggerInterface instance + */ + public function __construct(LoggerInterface $logger = null) + { + $this->logger = $logger; + } + + /** + * Returns the Controller instance associated with a Request. + * + * This method looks for a '_controller' request attribute that represents + * the controller name (a string like ClassName::MethodName). + * + * @param Request $request A Request instance + * + * @return mixed|Boolean A PHP callable representing the Controller, + * or false if this resolver is not able to determine the controller + * + * @throws \InvalidArgumentException|\LogicException If the controller can't be found + * + * @api + */ + public function getController(Request $request) + { + if (!$controller = $request->attributes->get('_controller')) { + if (null !== $this->logger) { + $this->logger->warning('Unable to look for the controller as the "_controller" parameter is missing'); + } + + return false; + } + + if (is_array($controller) || (is_object($controller) && method_exists($controller, '__invoke'))) { + return $controller; + } + + if (false === strpos($controller, ':')) { + if (method_exists($controller, '__invoke')) { + return new $controller; + } elseif (function_exists($controller)) { + return $controller; + } + } + + $callable = $this->createController($controller); + + if (!is_callable($callable)) { + throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable.', $request->getPathInfo())); + } + + return $callable; + } + + /** + * Returns the arguments to pass to the controller. + * + * @param Request $request A Request instance + * @param mixed $controller A PHP callable + * + * @return array + * + * @throws \RuntimeException When value for argument given is not provided + * + * @api + */ + public function getArguments(Request $request, $controller) + { + if (is_array($controller)) { + $r = new \ReflectionMethod($controller[0], $controller[1]); + } elseif (is_object($controller) && !$controller instanceof \Closure) { + $r = new \ReflectionObject($controller); + $r = $r->getMethod('__invoke'); + } else { + $r = new \ReflectionFunction($controller); + } + + return $this->doGetArguments($request, $controller, $r->getParameters()); + } + + protected function doGetArguments(Request $request, $controller, array $parameters) + { + $attributes = $request->attributes->all(); + $arguments = array(); + foreach ($parameters as $param) { + if (array_key_exists($param->name, $attributes)) { + $arguments[] = $attributes[$param->name]; + } elseif ($param->getClass() && $param->getClass()->isInstance($request)) { + $arguments[] = $request; + } elseif ($param->isDefaultValueAvailable()) { + $arguments[] = $param->getDefaultValue(); + } else { + if (is_array($controller)) { + $repr = sprintf('%s::%s()', get_class($controller[0]), $controller[1]); + } elseif (is_object($controller)) { + $repr = get_class($controller); + } else { + $repr = $controller; + } + + throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument (because there is no default value or because there is a non optional argument after this one).', $repr, $param->name)); + } + } + + return $arguments; + } + + /** + * Returns a callable for the given controller. + * + * @param string $controller A Controller string + * + * @return mixed A PHP callable + * + * @throws \InvalidArgumentException + */ + protected function createController($controller) + { + if (false === strpos($controller, '::')) { + throw new \InvalidArgumentException(sprintf('Unable to find controller "%s".', $controller)); + } + + list($class, $method) = explode('::', $controller, 2); + + if (!class_exists($class)) { + throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); + } + + return array(new $class(), $method); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php new file mode 100644 index 0000000..f58f50d --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Controller/ControllerResolverInterface.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; + +/** + * A ControllerResolverInterface implementation knows how to determine the + * controller to execute based on a Request object. + * + * It can also determine the arguments to pass to the Controller. + * + * A Controller can be any valid PHP callable. + * + * @author Fabien Potencier + * + * @api + */ +interface ControllerResolverInterface +{ + /** + * Returns the Controller instance associated with a Request. + * + * As several resolvers can exist for a single application, a resolver must + * return false when it is not able to determine the controller. + * + * The resolver must only throw an exception when it should be able to load + * controller but cannot because of some errors made by the developer. + * + * @param Request $request A Request instance + * + * @return mixed|Boolean A PHP callable representing the Controller, + * or false if this resolver is not able to determine the controller + * + * @throws \InvalidArgumentException|\LogicException If the controller can't be found + * + * @api + */ + public function getController(Request $request); + + /** + * Returns the arguments to pass to the controller. + * + * @param Request $request A Request instance + * @param mixed $controller A PHP callable + * + * @return array An array of arguments to pass to the controller + * + * @throws \RuntimeException When value for argument given is not provided + * + * @api + */ + public function getArguments(Request $request, $controller); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Controller/TraceableControllerResolver.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Controller/TraceableControllerResolver.php new file mode 100644 index 0000000..f8de31c --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Controller/TraceableControllerResolver.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\HttpFoundation\Request; + +/** + * TraceableControllerResolver. + * + * @author Fabien Potencier + */ +class TraceableControllerResolver implements ControllerResolverInterface +{ + private $resolver; + private $stopwatch; + + /** + * Constructor. + * + * @param ControllerResolverInterface $resolver A ControllerResolverInterface instance + * @param Stopwatch $stopwatch A Stopwatch instance + */ + public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch) + { + $this->resolver = $resolver; + $this->stopwatch = $stopwatch; + } + + /** + * {@inheritdoc} + */ + public function getController(Request $request) + { + $e = $this->stopwatch->start('controller.get_callable'); + + $ret = $this->resolver->getController($request); + + $e->stop(); + + return $ret; + } + + /** + * {@inheritdoc} + */ + public function getArguments(Request $request, $controller) + { + $e = $this->stopwatch->start('controller.get_arguments'); + + $ret = $this->resolver->getArguments($request, $controller); + + $e->stop(); + + return $ret; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php new file mode 100644 index 0000000..47529fd --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php @@ -0,0 +1,246 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * ConfigDataCollector. + * + * @author Fabien Potencier + */ +class ConfigDataCollector extends DataCollector +{ + private $kernel; + private $name; + private $version; + + /** + * Constructor. + * + * @param string $name The name of the application using the web profiler + * @param string $version The version of the application using the web profiler + */ + public function __construct($name = null, $version = null) + { + $this->name = $name; + $this->version = $version; + } + + /** + * Sets the Kernel associated with this Request. + * + * @param KernelInterface $kernel A KernelInterface instance + */ + public function setKernel(KernelInterface $kernel = null) + { + $this->kernel = $kernel; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->data = array( + 'app_name' => $this->name, + 'app_version' => $this->version, + 'token' => $response->headers->get('X-Debug-Token'), + 'symfony_version' => Kernel::VERSION, + 'name' => isset($this->kernel) ? $this->kernel->getName() : 'n/a', + 'env' => isset($this->kernel) ? $this->kernel->getEnvironment() : 'n/a', + 'debug' => isset($this->kernel) ? $this->kernel->isDebug() : 'n/a', + 'php_version' => PHP_VERSION, + 'xdebug_enabled' => extension_loaded('xdebug'), + 'eaccel_enabled' => extension_loaded('eaccelerator') && ini_get('eaccelerator.enable'), + 'apc_enabled' => extension_loaded('apc') && ini_get('apc.enabled'), + 'xcache_enabled' => extension_loaded('xcache') && ini_get('xcache.cacher'), + 'wincache_enabled' => extension_loaded('wincache') && ini_get('wincache.ocenabled'), + 'zend_opcache_enabled' => extension_loaded('Zend OPcache') && ini_get('opcache.enable'), + 'bundles' => array(), + 'sapi_name' => php_sapi_name() + ); + + if (isset($this->kernel)) { + foreach ($this->kernel->getBundles() as $name => $bundle) { + $this->data['bundles'][$name] = $bundle->getPath(); + } + } + } + + public function getApplicationName() + { + return $this->data['app_name']; + } + + public function getApplicationVersion() + { + return $this->data['app_version']; + } + + /** + * Gets the token. + * + * @return string The token + */ + public function getToken() + { + return $this->data['token']; + } + + /** + * Gets the Symfony version. + * + * @return string The Symfony version + */ + public function getSymfonyVersion() + { + return $this->data['symfony_version']; + } + + /** + * Gets the PHP version. + * + * @return string The PHP version + */ + public function getPhpVersion() + { + return $this->data['php_version']; + } + + /** + * Gets the application name. + * + * @return string The application name + */ + public function getAppName() + { + return $this->data['name']; + } + + /** + * Gets the environment. + * + * @return string The environment + */ + public function getEnv() + { + return $this->data['env']; + } + + /** + * Returns true if the debug is enabled. + * + * @return Boolean true if debug is enabled, false otherwise + */ + public function isDebug() + { + return $this->data['debug']; + } + + /** + * Returns true if the XDebug is enabled. + * + * @return Boolean true if XDebug is enabled, false otherwise + */ + public function hasXDebug() + { + return $this->data['xdebug_enabled']; + } + + /** + * Returns true if EAccelerator is enabled. + * + * @return Boolean true if EAccelerator is enabled, false otherwise + */ + public function hasEAccelerator() + { + return $this->data['eaccel_enabled']; + } + + /** + * Returns true if APC is enabled. + * + * @return Boolean true if APC is enabled, false otherwise + */ + public function hasApc() + { + return $this->data['apc_enabled']; + } + + /** + * Returns true if Zend OPcache is enabled + * + * @return Boolean true if Zend OPcache is enabled, false otherwise + */ + public function hasZendOpcache() + { + return $this->data['zend_opcache_enabled']; + } + + /** + * Returns true if XCache is enabled. + * + * @return Boolean true if XCache is enabled, false otherwise + */ + public function hasXCache() + { + return $this->data['xcache_enabled']; + } + + /** + * Returns true if WinCache is enabled. + * + * @return Boolean true if WinCache is enabled, false otherwise + */ + public function hasWinCache() + { + return $this->data['wincache_enabled']; + } + + /** + * Returns true if any accelerator is enabled. + * + * @return Boolean true if any accelerator is enabled, false otherwise + */ + public function hasAccelerator() + { + return $this->hasApc() || $this->hasZendOpcache() || $this->hasEAccelerator() || $this->hasXCache() || $this->hasWinCache(); + } + + public function getBundles() + { + return $this->data['bundles']; + } + + /** + * Gets the PHP SAPI name. + * + * @return string The environment + */ + public function getSapiName() + { + return $this->data['sapi_name']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'config'; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/DataCollector.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/DataCollector.php new file mode 100644 index 0000000..7d9c289 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/DataCollector.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +/** + * DataCollector. + * + * Children of this class must store the collected data in the data property. + * + * @author Fabien Potencier + */ +abstract class DataCollector implements DataCollectorInterface, \Serializable +{ + protected $data; + + public function serialize() + { + return serialize($this->data); + } + + public function unserialize($data) + { + $this->data = unserialize($data); + } + + /** + * Converts a PHP variable to a string. + * + * @param mixed $var A PHP variable + * + * @return string The string representation of the variable + */ + protected function varToString($var) + { + if (is_object($var)) { + return sprintf('Object(%s)', get_class($var)); + } + + if (is_array($var)) { + $a = array(); + foreach ($var as $k => $v) { + $a[] = sprintf('%s => %s', $k, $this->varToString($v)); + } + + return sprintf("Array(%s)", implode(', ', $a)); + } + + if (is_resource($var)) { + return sprintf('Resource(%s)', get_resource_type($var)); + } + + if (null === $var) { + return 'null'; + } + + if (false === $var) { + return 'false'; + } + + if (true === $var) { + return 'true'; + } + + return (string) $var; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.php new file mode 100644 index 0000000..cf4cdfd --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/DataCollectorInterface.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * DataCollectorInterface. + * + * @author Fabien Potencier + * + * @api + */ +interface DataCollectorInterface +{ + /** + * Collects data for the given Request and Response. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * @param \Exception $exception An Exception instance + * + * @api + */ + public function collect(Request $request, Response $response, \Exception $exception = null); + + /** + * Returns the name of the collector. + * + * @return string The collector name + * + * @api + */ + public function getName(); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php new file mode 100644 index 0000000..cd7f787 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface; + +/** + * EventDataCollector. + * + * @author Fabien Potencier + */ +class EventDataCollector extends DataCollector +{ + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->data = array( + 'called_listeners' => array(), + 'not_called_listeners' => array(), + ); + } + + /** + * Sets the called listeners. + * + * @param array $listeners An array of called listeners + * + * @see TraceableEventDispatcherInterface + */ + public function setCalledListeners(array $listeners) + { + $this->data['called_listeners'] = $listeners; + } + + /** + * Gets the called listeners. + * + * @return array An array of called listeners + * + * @see TraceableEventDispatcherInterface + */ + public function getCalledListeners() + { + return $this->data['called_listeners']; + } + + /** + * Sets the not called listeners. + * + * @param array $listeners An array of not called listeners + * + * @see TraceableEventDispatcherInterface + */ + public function setNotCalledListeners(array $listeners) + { + $this->data['not_called_listeners'] = $listeners; + } + + /** + * Gets the not called listeners. + * + * @return array An array of not called listeners + * + * @see TraceableEventDispatcherInterface + */ + public function getNotCalledListeners() + { + return $this->data['not_called_listeners']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'events'; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/ExceptionDataCollector.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/ExceptionDataCollector.php new file mode 100644 index 0000000..10a010b --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/ExceptionDataCollector.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\FlattenException; + +/** + * ExceptionDataCollector. + * + * @author Fabien Potencier + */ +class ExceptionDataCollector extends DataCollector +{ + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if (null !== $exception) { + $this->data = array( + 'exception' => FlattenException::create($exception), + ); + } + } + + /** + * Checks if the exception is not null. + * + * @return Boolean true if the exception is not null, false otherwise + */ + public function hasException() + { + return isset($this->data['exception']); + } + + /** + * Gets the exception. + * + * @return \Exception The exception + */ + public function getException() + { + return $this->data['exception']; + } + + /** + * Gets the exception message. + * + * @return string The exception message + */ + public function getMessage() + { + return $this->data['exception']->getMessage(); + } + + /** + * Gets the exception code. + * + * @return integer The exception code + */ + public function getCode() + { + return $this->data['exception']->getCode(); + } + + /** + * Gets the status code. + * + * @return integer The status code + */ + public function getStatusCode() + { + return $this->data['exception']->getStatusCode(); + } + + /** + * Gets the exception trace. + * + * @return array The exception trace + */ + public function getTrace() + { + return $this->data['exception']->getTrace(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'exception'; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php new file mode 100644 index 0000000..f08720e --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Debug\ErrorHandler; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; + +/** + * LogDataCollector. + * + * @author Fabien Potencier + */ +class LoggerDataCollector extends DataCollector +{ + private $logger; + + public function __construct($logger = null) + { + if (null !== $logger && $logger instanceof DebugLoggerInterface) { + $this->logger = $logger; + } + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if (null !== $this->logger) { + $this->data = array( + 'error_count' => $this->logger->countErrors(), + 'logs' => $this->sanitizeLogs($this->logger->getLogs()), + 'deprecation_count' => $this->computeDeprecationCount() + ); + } + } + + /** + * Gets the called events. + * + * @return array An array of called events + * + * @see TraceableEventDispatcherInterface + */ + public function countErrors() + { + return isset($this->data['error_count']) ? $this->data['error_count'] : 0; + } + + /** + * Gets the logs. + * + * @return array An array of logs + */ + public function getLogs() + { + return isset($this->data['logs']) ? $this->data['logs'] : array(); + } + + public function countDeprecations() + { + return isset($this->data['deprecation_count']) ? $this->data['deprecation_count'] : 0; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'logger'; + } + + private function sanitizeLogs($logs) + { + foreach ($logs as $i => $log) { + $logs[$i]['context'] = $this->sanitizeContext($log['context']); + } + + return $logs; + } + + private function sanitizeContext($context) + { + if (is_array($context)) { + foreach ($context as $key => $value) { + $context[$key] = $this->sanitizeContext($value); + } + + return $context; + } + + if (is_resource($context)) { + return sprintf('Resource(%s)', get_resource_type($context)); + } + + if (is_object($context)) { + return sprintf('Object(%s)', get_class($context)); + } + + return $context; + } + + private function computeDeprecationCount() + { + $count = 0; + foreach ($this->logger->getLogs() as $log) { + if (isset($log['context']['type']) && ErrorHandler::TYPE_DEPRECATION === $log['context']['type']) { + $count++; + } + } + + return $count; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/MemoryDataCollector.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/MemoryDataCollector.php new file mode 100644 index 0000000..5540a1b --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/MemoryDataCollector.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * MemoryDataCollector. + * + * @author Fabien Potencier + */ +class MemoryDataCollector extends DataCollector +{ + public function __construct() + { + $this->data = array( + 'memory' => 0, + 'memory_limit' => $this->convertToBytes(strtolower(ini_get('memory_limit'))), + ); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->updateMemoryUsage(); + } + + /** + * Gets the memory. + * + * @return integer The memory + */ + public function getMemory() + { + return $this->data['memory']; + } + + /** + * Gets the PHP memory limit. + * + * @return integer The memory limit + */ + public function getMemoryLimit() + { + return $this->data['memory_limit']; + } + + /** + * Updates the memory usage data. + */ + public function updateMemoryUsage() + { + $this->data['memory'] = memory_get_peak_usage(true); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'memory'; + } + + private function convertToBytes($memoryLimit) + { + if ('-1' === $memoryLimit) { + return -1; + } + + if (preg_match('#^\+?(0x?)?(.*?)([kmg]?)$#', $memoryLimit, $match)) { + $shifts = array('' => 0, 'k' => 10, 'm' => 20, 'g' => 30); + $bases = array('' => 10, '0' => 8, '0x' => 16); + + return intval($match[2], $bases[$match[1]]) << $shifts[$match[3]]; + } + + return 0; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php new file mode 100644 index 0000000..f66cb15 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php @@ -0,0 +1,326 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\HeaderBag; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * RequestDataCollector. + * + * @author Fabien Potencier + */ +class RequestDataCollector extends DataCollector implements EventSubscriberInterface +{ + protected $controllers; + + public function __construct() + { + $this->controllers = new \SplObjectStorage(); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $responseHeaders = $response->headers->all(); + $cookies = array(); + foreach ($response->headers->getCookies() as $cookie) { + $cookies[] = $this->getCookieHeader($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); + } + if (count($cookies) > 0) { + $responseHeaders['Set-Cookie'] = $cookies; + } + + $attributes = array(); + foreach ($request->attributes->all() as $key => $value) { + if ('_route' == $key && is_object($value)) { + $attributes['_route'] = $this->varToString($value->getPath()); + } elseif ('_route_params' == $key) { + foreach ($value as $key => $v) { + $attributes['_route_params'][$key] = $this->varToString($v); + } + } else { + $attributes[$key] = $this->varToString($value); + } + } + + $content = null; + try { + $content = $request->getContent(); + } catch (\LogicException $e) { + // the user already got the request content as a resource + $content = false; + } + + $sessionMetadata = array(); + $sessionAttributes = array(); + $flashes = array(); + if ($request->hasSession()) { + $session = $request->getSession(); + if ($session->isStarted()) { + $sessionMetadata['Created'] = date(DATE_RFC822, $session->getMetadataBag()->getCreated()); + $sessionMetadata['Last used'] = date(DATE_RFC822, $session->getMetadataBag()->getLastUsed()); + $sessionMetadata['Lifetime'] = $session->getMetadataBag()->getLifetime(); + $sessionAttributes = $session->all(); + $flashes = $session->getFlashBag()->peekAll(); + } + } + + $statusCode = $response->getStatusCode(); + + $this->data = array( + 'format' => $request->getRequestFormat(), + 'content' => $content, + 'content_type' => $response->headers->get('Content-Type') ? $response->headers->get('Content-Type') : 'text/html', + 'status_text' => isset(Response::$statusTexts[$statusCode]) ? Response::$statusTexts[$statusCode] : '', + 'status_code' => $statusCode, + 'request_query' => $request->query->all(), + 'request_request' => $request->request->all(), + 'request_headers' => $request->headers->all(), + 'request_server' => $request->server->all(), + 'request_cookies' => $request->cookies->all(), + 'request_attributes' => $attributes, + 'response_headers' => $responseHeaders, + 'session_metadata' => $sessionMetadata, + 'session_attributes' => $sessionAttributes, + 'flashes' => $flashes, + 'path_info' => $request->getPathInfo(), + 'controller' => 'n/a', + 'locale' => $request->getLocale(), + ); + + if (isset($this->data['request_headers']['php-auth-pw'])) { + $this->data['request_headers']['php-auth-pw'] = '******'; + } + + if (isset($this->data['request_server']['PHP_AUTH_PW'])) { + $this->data['request_server']['PHP_AUTH_PW'] = '******'; + } + + if (isset($this->controllers[$request])) { + $controller = $this->controllers[$request]; + if (is_array($controller)) { + try { + $r = new \ReflectionMethod($controller[0], $controller[1]); + $this->data['controller'] = array( + 'class' => is_object($controller[0]) ? get_class($controller[0]) : $controller[0], + 'method' => $controller[1], + 'file' => $r->getFilename(), + 'line' => $r->getStartLine(), + ); + } catch (\ReflectionException $re) { + if (is_callable($controller)) { + // using __call or __callStatic + $this->data['controller'] = array( + 'class' => is_object($controller[0]) ? get_class($controller[0]) : $controller[0], + 'method' => $controller[1], + 'file' => 'n/a', + 'line' => 'n/a', + ); + } + } + } elseif ($controller instanceof \Closure) { + $r = new \ReflectionFunction($controller); + $this->data['controller'] = array( + 'class' => $r->getName(), + 'method' => null, + 'file' => $r->getFilename(), + 'line' => $r->getStartLine(), + ); + } else { + $this->data['controller'] = (string) $controller ?: 'n/a'; + } + unset($this->controllers[$request]); + } + } + + public function getPathInfo() + { + return $this->data['path_info']; + } + + public function getRequestRequest() + { + return new ParameterBag($this->data['request_request']); + } + + public function getRequestQuery() + { + return new ParameterBag($this->data['request_query']); + } + + public function getRequestHeaders() + { + return new HeaderBag($this->data['request_headers']); + } + + public function getRequestServer() + { + return new ParameterBag($this->data['request_server']); + } + + public function getRequestCookies() + { + return new ParameterBag($this->data['request_cookies']); + } + + public function getRequestAttributes() + { + return new ParameterBag($this->data['request_attributes']); + } + + public function getResponseHeaders() + { + return new ResponseHeaderBag($this->data['response_headers']); + } + + public function getSessionMetadata() + { + return $this->data['session_metadata']; + } + + public function getSessionAttributes() + { + return $this->data['session_attributes']; + } + + public function getFlashes() + { + return $this->data['flashes']; + } + + public function getContent() + { + return $this->data['content']; + } + + public function getContentType() + { + return $this->data['content_type']; + } + + public function getStatusText() + { + return $this->data['status_text']; + } + + public function getStatusCode() + { + return $this->data['status_code']; + } + + public function getFormat() + { + return $this->data['format']; + } + + public function getLocale() + { + return $this->data['locale']; + } + + /** + * Gets the route name. + * + * The _route request attributes is automatically set by the Router Matcher. + * + * @return string The route + */ + public function getRoute() + { + return isset($this->data['request_attributes']['_route']) ? $this->data['request_attributes']['_route'] : ''; + } + + /** + * Gets the route parameters. + * + * The _route_params request attributes is automatically set by the RouterListener. + * + * @return array The parameters + */ + public function getRouteParams() + { + return isset($this->data['request_attributes']['_route_params']) ? $this->data['request_attributes']['_route_params'] : array(); + } + + /** + * Gets the controller. + * + * @return string The controller as a string + */ + public function getController() + { + return $this->data['controller']; + } + + public function onKernelController(FilterControllerEvent $event) + { + $this->controllers[$event->getRequest()] = $event->getController(); + } + + public static function getSubscribedEvents() + { + return array(KernelEvents::CONTROLLER => 'onKernelController'); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'request'; + } + + private function getCookieHeader($name, $value, $expires, $path, $domain, $secure, $httponly) + { + $cookie = sprintf('%s=%s', $name, urlencode($value)); + + if (0 !== $expires) { + if (is_numeric($expires)) { + $expires = (int) $expires; + } elseif ($expires instanceof \DateTime) { + $expires = $expires->getTimestamp(); + } else { + $expires = strtotime($expires); + if (false === $expires || -1 == $expires) { + throw new \InvalidArgumentException(sprintf('The "expires" cookie parameter is not valid.', $expires)); + } + } + + $cookie .= '; expires='.substr(\DateTime::createFromFormat('U', $expires, new \DateTimeZone('UTC'))->format('D, d-M-Y H:i:s T'), 0, -5); + } + + if ($domain) { + $cookie .= '; domain='.$domain; + } + + $cookie .= '; path='.$path; + + if ($secure) { + $cookie .= '; secure'; + } + + if ($httponly) { + $cookie .= '; httponly'; + } + + return $cookie; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/RouterDataCollector.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/RouterDataCollector.php new file mode 100644 index 0000000..e44da38 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/RouterDataCollector.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; + +/** + * RouterDataCollector. + * + * @author Fabien Potencier + */ +class RouterDataCollector extends DataCollector +{ + protected $controllers; + + public function __construct() + { + $this->controllers = new \SplObjectStorage(); + + $this->data = array( + 'redirect' => false, + 'url' => null, + 'route' => null, + ); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if ($response instanceof RedirectResponse) { + $this->data['redirect'] = true; + $this->data['url'] = $response->getTargetUrl(); + + if ($this->controllers->contains($request)) { + $this->data['route'] = $this->guessRoute($request, $this->controllers[$request]); + } + } + + unset($this->controllers[$request]); + } + + protected function guessRoute(Request $request, $controller) + { + return 'n/a'; + } + + /** + * Remembers the controller associated to each request. + * + * @param FilterControllerEvent $event The filter controller event + */ + public function onKernelController(FilterControllerEvent $event) + { + $this->controllers[$event->getRequest()] = $event->getController(); + } + + /** + * @return Boolean Whether this request will result in a redirect + */ + public function getRedirect() + { + return $this->data['redirect']; + } + + /** + * @return string|null The target URL + */ + public function getTargetUrl() + { + return $this->data['url']; + } + + /** + * @return string|null The target route + */ + public function getTargetRoute() + { + return $this->data['route']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'router'; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php new file mode 100644 index 0000000..74d7616 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DataCollector/TimeDataCollector.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\KernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * TimeDataCollector. + * + * @author Fabien Potencier + */ +class TimeDataCollector extends DataCollector +{ + protected $kernel; + + public function __construct(KernelInterface $kernel = null) + { + $this->kernel = $kernel; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if (null !== $this->kernel) { + $startTime = $this->kernel->getStartTime(); + } else { + $startTime = $request->server->get('REQUEST_TIME_FLOAT', $request->server->get('REQUEST_TIME')); + } + + $this->data = array( + 'start_time' => $startTime * 1000, + 'events' => array(), + ); + } + + /** + * Sets the request events. + * + * @param array $events The request events + */ + public function setEvents(array $events) + { + foreach ($events as $event) { + $event->ensureStopped(); + } + + $this->data['events'] = $events; + } + + /** + * Gets the request events. + * + * @return array The request events + */ + public function getEvents() + { + return $this->data['events']; + } + + /** + * Gets the request elapsed time. + * + * @return float The elapsed time + */ + public function getDuration() + { + if (!isset($this->data['events']['__section__'])) { + return 0; + } + + $lastEvent = $this->data['events']['__section__']; + + return $lastEvent->getOrigin() + $lastEvent->getDuration() - $this->getStartTime(); + } + + /** + * Gets the initialization time. + * + * This is the time spent until the beginning of the request handling. + * + * @return float The elapsed time + */ + public function getInitTime() + { + if (!isset($this->data['events']['__section__'])) { + return 0; + } + + return $this->data['events']['__section__']->getOrigin() - $this->getStartTime(); + } + + /** + * Gets the request time. + * + * @return integer The time + */ + public function getStartTime() + { + return $this->data['start_time']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'time'; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Debug/ErrorHandler.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Debug/ErrorHandler.php new file mode 100644 index 0000000..2718f89 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Debug/ErrorHandler.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Debug; + +use Symfony\Component\Debug\ErrorHandler as DebugErrorHandler; + +/** + * ErrorHandler. + * + * @author Fabien Potencier + * + * @deprecated Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead. + */ +class ErrorHandler extends DebugErrorHandler +{ +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Debug/ExceptionHandler.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Debug/ExceptionHandler.php new file mode 100644 index 0000000..581e29c --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Debug/ExceptionHandler.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Debug; + +use Symfony\Component\Debug\ExceptionHandler as DebugExceptionHandler; + +/** + * ExceptionHandler converts an exception to a Response object. + * + * @author Fabien Potencier + * + * @deprecated Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead. + */ +class ExceptionHandler extends DebugExceptionHandler +{ +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php new file mode 100644 index 0000000..6bfd7a0 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Debug/TraceableEventDispatcher.php @@ -0,0 +1,469 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Debug; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\HttpKernel\KernelEvents; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpKernel\Profiler\Profile; +use Symfony\Component\HttpKernel\Profiler\Profiler; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface; + +/** + * Collects some data about event listeners. + * + * This event dispatcher delegates the dispatching to another one. + * + * @author Fabien Potencier + */ +class TraceableEventDispatcher implements EventDispatcherInterface, TraceableEventDispatcherInterface +{ + private $logger; + private $called; + private $stopwatch; + private $profiler; + private $dispatcher; + private $wrappedListeners; + private $firstCalledEvent; + private $id; + + /** + * Constructor. + * + * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance + * @param Stopwatch $stopwatch A Stopwatch instance + * @param LoggerInterface $logger A LoggerInterface instance + */ + public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null) + { + $this->dispatcher = $dispatcher; + $this->stopwatch = $stopwatch; + $this->logger = $logger; + $this->called = array(); + $this->wrappedListeners = array(); + $this->firstCalledEvent = array(); + } + + /** + * Sets the profiler. + * + * @param Profiler|null $profiler A Profiler instance + */ + public function setProfiler(Profiler $profiler = null) + { + $this->profiler = $profiler; + } + + /** + * {@inheritDoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + $this->dispatcher->addListener($eventName, $listener, $priority); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + $this->dispatcher->addSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + return $this->dispatcher->removeListener($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + return $this->dispatcher->removeSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + if (null === $event) { + $event = new Event(); + } + + $this->id = spl_object_hash($event); + + $this->preDispatch($eventName, $event); + + $e = $this->stopwatch->start($eventName, 'section'); + + $this->firstCalledEvent[$eventName] = $this->stopwatch->start($eventName.'.loading', 'event_listener_loading'); + + if (!$this->dispatcher->hasListeners($eventName)) { + $this->firstCalledEvent[$eventName]->stop(); + } + + $this->dispatcher->dispatch($eventName, $event); + + // reset the id as another event might have been dispatched during the dispatching of this event + $this->id = spl_object_hash($event); + + unset($this->firstCalledEvent[$eventName]); + + $e->stop(); + + $this->postDispatch($eventName, $event); + + return $event; + } + + /** + * {@inheritDoc} + */ + public function getCalledListeners() + { + return $this->called; + } + + /** + * {@inheritDoc} + */ + public function getNotCalledListeners() + { + $notCalled = array(); + + foreach ($this->getListeners() as $name => $listeners) { + foreach ($listeners as $listener) { + $info = $this->getListenerInfo($listener, $name); + if (!isset($this->called[$name.'.'.$info['pretty']])) { + $notCalled[$name.'.'.$info['pretty']] = $info; + } + } + } + + return $notCalled; + } + + /** + * Proxies all method calls to the original event dispatcher. + * + * @param string $method The method name + * @param array $arguments The method arguments + * + * @return mixed + */ + public function __call($method, $arguments) + { + return call_user_func_array(array($this->dispatcher, $method), $arguments); + } + + /** + * This is a private method and must not be used. + * + * This method is public because it is used in a closure. + * Whenever Symfony will require PHP 5.4, this could be changed + * to a proper private method. + */ + public function logSkippedListeners($eventName, Event $event, $listener) + { + if (null === $this->logger) { + return; + } + + $info = $this->getListenerInfo($listener, $eventName); + + $this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName)); + + $skippedListeners = $this->getListeners($eventName); + $skipped = false; + + foreach ($skippedListeners as $skippedListener) { + $skippedListener = $this->unwrapListener($skippedListener); + + if ($skipped) { + $info = $this->getListenerInfo($skippedListener, $eventName); + $this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', $info['pretty'], $eventName)); + } + + if ($skippedListener === $listener) { + $skipped = true; + } + } + } + + /** + * This is a private method. + * + * This method is public because it is used in a closure. + * Whenever Symfony will require PHP 5.4, this could be changed + * to a proper private method. + */ + public function preListenerCall($eventName, $listener) + { + // is it the first called listener? + if (isset($this->firstCalledEvent[$eventName])) { + $this->firstCalledEvent[$eventName]->stop(); + + unset($this->firstCalledEvent[$eventName]); + } + + $info = $this->getListenerInfo($listener, $eventName); + + if (null !== $this->logger) { + $this->logger->debug(sprintf('Notified event "%s" to listener "%s".', $eventName, $info['pretty'])); + } + + $this->called[$eventName.'.'.$info['pretty']] = $info; + + return $this->stopwatch->start(isset($info['class']) ? $info['class'] : $info['type'], 'event_listener'); + } + + /** + * Returns information about the listener + * + * @param object $listener The listener + * @param string $eventName The event name + * + * @return array Informations about the listener + */ + private function getListenerInfo($listener, $eventName) + { + $listener = $this->unwrapListener($listener); + + $info = array( + 'event' => $eventName, + ); + if ($listener instanceof \Closure) { + $info += array( + 'type' => 'Closure', + 'pretty' => 'closure' + ); + } elseif (is_string($listener)) { + try { + $r = new \ReflectionFunction($listener); + $file = $r->getFileName(); + $line = $r->getStartLine(); + } catch (\ReflectionException $e) { + $file = null; + $line = null; + } + $info += array( + 'type' => 'Function', + 'function' => $listener, + 'file' => $file, + 'line' => $line, + 'pretty' => $listener, + ); + } elseif (is_array($listener) || (is_object($listener) && is_callable($listener))) { + if (!is_array($listener)) { + $listener = array($listener, '__invoke'); + } + $class = is_object($listener[0]) ? get_class($listener[0]) : $listener[0]; + try { + $r = new \ReflectionMethod($class, $listener[1]); + $file = $r->getFileName(); + $line = $r->getStartLine(); + } catch (\ReflectionException $e) { + $file = null; + $line = null; + } + $info += array( + 'type' => 'Method', + 'class' => $class, + 'method' => $listener[1], + 'file' => $file, + 'line' => $line, + 'pretty' => $class.'::'.$listener[1], + ); + } + + return $info; + } + + /** + * Updates the stopwatch data in the profile hierarchy. + * + * @param string $token Profile token + * @param Boolean $updateChildren Whether to update the children altogether + */ + private function updateProfiles($token, $updateChildren) + { + if (!$this->profiler || !$profile = $this->profiler->loadProfile($token)) { + return; + } + + $this->saveInfoInProfile($profile, $updateChildren); + } + + /** + * Update the profiles with the timing and events information and saves them. + * + * @param Profile $profile The root profile + * @param Boolean $updateChildren Whether to update the children altogether + */ + private function saveInfoInProfile(Profile $profile, $updateChildren) + { + try { + $collector = $profile->getCollector('memory'); + $collector->updateMemoryUsage(); + } catch (\InvalidArgumentException $e) { + } + + try { + $collector = $profile->getCollector('time'); + $collector->setEvents($this->stopwatch->getSectionEvents($profile->getToken())); + } catch (\InvalidArgumentException $e) { + } + + try { + $collector = $profile->getCollector('events'); + $collector->setCalledListeners($this->getCalledListeners()); + $collector->setNotCalledListeners($this->getNotCalledListeners()); + } catch (\InvalidArgumentException $e) { + } + + $this->profiler->saveProfile($profile); + + if ($updateChildren) { + foreach ($profile->getChildren() as $child) { + $this->saveInfoInProfile($child, true); + } + } + } + + private function preDispatch($eventName, Event $event) + { + // wrap all listeners before they are called + $this->wrappedListeners[$this->id] = new \SplObjectStorage(); + + $listeners = $this->dispatcher->getListeners($eventName); + + foreach ($listeners as $listener) { + $this->dispatcher->removeListener($eventName, $listener); + $wrapped = $this->wrapListener($eventName, $listener); + $this->wrappedListeners[$this->id][$wrapped] = $listener; + $this->dispatcher->addListener($eventName, $wrapped); + } + + switch ($eventName) { + case KernelEvents::REQUEST: + $this->stopwatch->openSection(); + break; + case KernelEvents::VIEW: + case KernelEvents::RESPONSE: + // stop only if a controller has been executed + if ($this->stopwatch->isStarted('controller')) { + $this->stopwatch->stop('controller'); + } + break; + case KernelEvents::TERMINATE: + $token = $event->getResponse()->headers->get('X-Debug-Token'); + // There is a very special case when using builtin AppCache class as kernel wrapper, in the case + // of an ESI request leading to a `stale` response [B] inside a `fresh` cached response [A]. + // In this case, `$token` contains the [B] debug token, but the open `stopwatch` section ID + // is equal to the [A] debug token. Trying to reopen section with the [B] token throws an exception + // which must be caught. + try { + $this->stopwatch->openSection($token); + } catch (\LogicException $e) {} + break; + } + } + + private function postDispatch($eventName, Event $event) + { + switch ($eventName) { + case KernelEvents::CONTROLLER: + $this->stopwatch->start('controller', 'section'); + break; + case KernelEvents::RESPONSE: + $token = $event->getResponse()->headers->get('X-Debug-Token'); + $this->stopwatch->stopSection($token); + if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) { + // The profiles can only be updated once they have been created + // that is after the 'kernel.response' event of the main request + $this->updateProfiles($token, true); + } + break; + case KernelEvents::TERMINATE: + $token = $event->getResponse()->headers->get('X-Debug-Token'); + // In the special case described in the `preDispatch` method above, the `$token` section + // does not exist, then closing it throws an exception which must be caught. + try { + $this->stopwatch->stopSection($token); + } catch (\LogicException $e) {} + // The children profiles have been updated by the previous 'kernel.response' + // event. Only the root profile need to be updated with the 'kernel.terminate' + // timing informations. + $this->updateProfiles($token, false); + break; + } + + foreach ($this->wrappedListeners[$this->id] as $wrapped) { + $this->dispatcher->removeListener($eventName, $wrapped); + $this->dispatcher->addListener($eventName, $this->wrappedListeners[$this->id][$wrapped]); + } + + unset($this->wrappedListeners[$this->id]); + } + + private function wrapListener($eventName, $listener) + { + $self = $this; + + return function (Event $event) use ($self, $eventName, $listener) { + $e = $self->preListenerCall($eventName, $listener); + + call_user_func($listener, $event); + + $e->stop(); + + if ($event->isPropagationStopped()) { + $self->logSkippedListeners($eventName, $event, $listener); + } + }; + } + + private function unwrapListener($listener) + { + // get the original listener + if (is_object($listener) && isset($this->wrappedListeners[$this->id][$listener])) { + return $this->wrappedListeners[$this->id][$listener]; + } + + return $listener; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/AddClassesToCachePass.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/AddClassesToCachePass.php new file mode 100644 index 0000000..7e694eb --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/AddClassesToCachePass.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\HttpKernel\Kernel; + +/** + * Sets the classes to compile in the cache for the container. + * + * @author Fabien Potencier + */ +class AddClassesToCachePass implements CompilerPassInterface +{ + private $kernel; + + public function __construct(Kernel $kernel) + { + $this->kernel = $kernel; + } + + /** + * {@inheritDoc} + */ + public function process(ContainerBuilder $container) + { + $classes = array(); + foreach ($container->getExtensions() as $extension) { + if ($extension instanceof Extension) { + $classes = array_merge($classes, $extension->getClassesToCompile()); + } + } + + $this->kernel->setClassCache(array_unique($container->getParameterBag()->resolveValue($classes))); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/ConfigurableExtension.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/ConfigurableExtension.php new file mode 100644 index 0000000..1a24da0 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/ConfigurableExtension.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * This extension sub-class provides first-class integration with the + * Config/Definition Component. + * + * You can use this as base class if you + * + * a) use the Config/Definition component for configuration + * b) your configuration class is named "Configuration" and + * c) the configuration class resides in the DependencyInjection sub-folder + * + * @author Johannes M. Schmitt + */ +abstract class ConfigurableExtension extends Extension +{ + /** + * {@inheritDoc} + */ + final public function load(array $configs, ContainerBuilder $container) + { + $this->loadInternal($this->processConfiguration($this->getConfiguration($configs, $container), $configs), $container); + } + + /** + * Configures the passed container according to the merged configuration. + * + * @param array $mergedConfig + * @param ContainerBuilder $container + */ + abstract protected function loadInternal(array $mergedConfig, ContainerBuilder $container); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php new file mode 100644 index 0000000..c9b8a21 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Scope; + +/** + * Adds a managed request scope. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class ContainerAwareHttpKernel extends HttpKernel +{ + protected $container; + + /** + * Constructor. + * + * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance + * @param ContainerInterface $container A ContainerInterface instance + * @param ControllerResolverInterface $controllerResolver A ControllerResolverInterface instance + */ + public function __construct(EventDispatcherInterface $dispatcher, ContainerInterface $container, ControllerResolverInterface $controllerResolver) + { + parent::__construct($dispatcher, $controllerResolver); + + $this->container = $container; + + // the request scope might have been created before (see FrameworkBundle) + if (!$container->hasScope('request')) { + $container->addScope(new Scope('request')); + } + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + $request->headers->set('X-Php-Ob-Level', ob_get_level()); + + $this->container->enterScope('request'); + $this->container->set('request', $request, 'request'); + + try { + $response = parent::handle($request, $type, $catch); + } catch (\Exception $e) { + $this->container->set('request', null, 'request'); + $this->container->leaveScope('request'); + + throw $e; + } + + $this->container->set('request', null, 'request'); + $this->container->leaveScope('request'); + + return $response; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/Extension.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/Extension.php new file mode 100644 index 0000000..2ca0f13 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/Extension.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Extension\Extension as BaseExtension; + +/** + * Allow adding classes to the class cache. + * + * @author Fabien Potencier + */ +abstract class Extension extends BaseExtension +{ + private $classes = array(); + + /** + * Gets the classes to cache. + * + * @return array An array of classes + */ + public function getClassesToCompile() + { + return $this->classes; + } + + /** + * Adds classes to the class cache. + * + * @param array $classes An array of classes + */ + public function addClassesToCompile(array $classes) + { + $this->classes = array_merge($this->classes, $classes); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/MergeExtensionConfigurationPass.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/MergeExtensionConfigurationPass.php new file mode 100644 index 0000000..dcd7382 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/MergeExtensionConfigurationPass.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPass as BaseMergeExtensionConfigurationPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Ensures certain extensions are always loaded. + * + * @author Kris Wallsmith + */ +class MergeExtensionConfigurationPass extends BaseMergeExtensionConfigurationPass +{ + private $extensions; + + public function __construct(array $extensions) + { + $this->extensions = $extensions; + } + + public function process(ContainerBuilder $container) + { + foreach ($this->extensions as $extension) { + if (!count($container->getExtensionConfig($extension))) { + $container->loadFromExtension($extension, array()); + } + } + + parent::process($container); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/RegisterListenersPass.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/RegisterListenersPass.php new file mode 100644 index 0000000..ee00fbb --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/DependencyInjection/RegisterListenersPass.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; + +/** + * Compiler pass to register tagged services for an event dispatcher. + */ +class RegisterListenersPass implements CompilerPassInterface +{ + /** + * @var string + */ + protected $dispatcherService; + + /** + * @var string + */ + protected $listenerTag; + + /** + * @var string + */ + protected $subscriberTag; + + /** + * Constructor. + * + * @param string $dispatcherService Service name of the event dispatcher in processed container + * @param string $listenerTag Tag name used for listener + * @param string $subscriberTag Tag name used for subscribers + */ + public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'kernel.event_listener', $subscriberTag = 'kernel.event_subscriber') + { + $this->dispatcherService = $dispatcherService; + $this->listenerTag = $listenerTag; + $this->subscriberTag = $subscriberTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->dispatcherService)) { + return; + } + + $definition = $container->getDefinition($this->dispatcherService); + + foreach ($container->findTaggedServiceIds($this->listenerTag) as $id => $events) { + foreach ($events as $event) { + $priority = isset($event['priority']) ? $event['priority'] : 0; + + if (!isset($event['event'])) { + throw new \InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "kernel.event_listener" tags.', $id)); + } + + if (!isset($event['method'])) { + $event['method'] = 'on'.preg_replace_callback(array( + '/(?<=\b)[a-z]/i', + '/[^a-z0-9]/i', + ), function ($matches) { return strtoupper($matches[0]); }, $event['event']); + $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); + } + + $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority)); + } + } + + foreach ($container->findTaggedServiceIds($this->subscriberTag) as $id => $attributes) { + // We must assume that the class value has been correctly filled, even if the service is created by a factory + $class = $container->getDefinition($id)->getClass(); + + $refClass = new \ReflectionClass($class); + $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; + if (!$refClass->implementsInterface($interface)) { + throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); + } + + $definition->addMethodCall('addSubscriberService', array($id, $class)); + } + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/FilterControllerEvent.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/FilterControllerEvent.php new file mode 100644 index 0000000..2b4860c --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/FilterControllerEvent.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Allows filtering of a controller callable + * + * You can call getController() to retrieve the current controller. With + * setController() you can set a new controller that is used in the processing + * of the request. + * + * Controllers should be callables. + * + * @author Bernhard Schussek + * + * @api + */ +class FilterControllerEvent extends KernelEvent +{ + /** + * The current controller + * @var callable + */ + private $controller; + + public function __construct(HttpKernelInterface $kernel, $controller, Request $request, $requestType) + { + parent::__construct($kernel, $request, $requestType); + + $this->setController($controller); + } + + /** + * Returns the current controller + * + * @return callable + * + * @api + */ + public function getController() + { + return $this->controller; + } + + /** + * Sets a new controller + * + * @param callable $controller + * + * @throws \LogicException + * + * @api + */ + public function setController($controller) + { + // controller must be a callable + if (!is_callable($controller)) { + throw new \LogicException(sprintf('The controller must be a callable (%s given).', $this->varToString($controller))); + } + + $this->controller = $controller; + } + + private function varToString($var) + { + if (is_object($var)) { + return sprintf('Object(%s)', get_class($var)); + } + + if (is_array($var)) { + $a = array(); + foreach ($var as $k => $v) { + $a[] = sprintf('%s => %s', $k, $this->varToString($v)); + } + + return sprintf("Array(%s)", implode(', ', $a)); + } + + if (is_resource($var)) { + return sprintf('Resource(%s)', get_resource_type($var)); + } + + if (null === $var) { + return 'null'; + } + + if (false === $var) { + return 'false'; + } + + if (true === $var) { + return 'true'; + } + + return (string) $var; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/FilterResponseEvent.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/FilterResponseEvent.php new file mode 100644 index 0000000..8de76a5 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/FilterResponseEvent.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Allows to filter a Response object + * + * You can call getResponse() to retrieve the current response. With + * setResponse() you can set a new response that will be returned to the + * browser. + * + * @author Bernhard Schussek + * + * @api + */ +class FilterResponseEvent extends KernelEvent +{ + /** + * The current response object + * @var Response + */ + private $response; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, Response $response) + { + parent::__construct($kernel, $request, $requestType); + + $this->setResponse($response); + } + + /** + * Returns the current response object + * + * @return Response + * + * @api + */ + public function getResponse() + { + return $this->response; + } + + /** + * Sets a new response object + * + * @param Response $response + * + * @api + */ + public function setResponse(Response $response) + { + $this->response = $response; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/GetResponseEvent.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/GetResponseEvent.php new file mode 100644 index 0000000..f71ccae --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/GetResponseEvent.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Response; + +/** + * Allows to create a response for a request + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * @author Bernhard Schussek + * + * @api + */ +class GetResponseEvent extends KernelEvent +{ + /** + * The response object + * @var Response + */ + private $response; + + /** + * Returns the response object + * + * @return Response + * + * @api + */ + public function getResponse() + { + return $this->response; + } + + /** + * Sets a response and stops event propagation + * + * @param Response $response + * + * @api + */ + public function setResponse(Response $response) + { + $this->response = $response; + + $this->stopPropagation(); + } + + /** + * Returns whether a response was set + * + * @return Boolean Whether a response was set + * + * @api + */ + public function hasResponse() + { + return null !== $this->response; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/GetResponseForControllerResultEvent.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/GetResponseForControllerResultEvent.php new file mode 100644 index 0000000..1bc0f98 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/GetResponseForControllerResultEvent.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Allows to create a response for the return value of a controller + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * @author Bernhard Schussek + * + * @api + */ +class GetResponseForControllerResultEvent extends GetResponseEvent +{ + /** + * The return value of the controller + * + * @var mixed + */ + private $controllerResult; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, $controllerResult) + { + parent::__construct($kernel, $request, $requestType); + + $this->controllerResult = $controllerResult; + } + + /** + * Returns the return value of the controller. + * + * @return mixed The controller return value + * + * @api + */ + public function getControllerResult() + { + return $this->controllerResult; + } + + /** + * Assigns the return value of the controller. + * + * @param mixed The controller return value + * + * @api + */ + public function setControllerResult($controllerResult) + { + $this->controllerResult = $controllerResult; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/GetResponseForExceptionEvent.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/GetResponseForExceptionEvent.php new file mode 100644 index 0000000..3787f62 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/GetResponseForExceptionEvent.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Allows to create a response for a thrown exception + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * You can also call setException() to replace the thrown exception. This + * exception will be thrown if no response is set during processing of this + * event. + * + * @author Bernhard Schussek + * + * @api + */ +class GetResponseForExceptionEvent extends GetResponseEvent +{ + /** + * The exception object + * @var \Exception + */ + private $exception; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, \Exception $e) + { + parent::__construct($kernel, $request, $requestType); + + $this->setException($e); + } + + /** + * Returns the thrown exception + * + * @return \Exception The thrown exception + * + * @api + */ + public function getException() + { + return $this->exception; + } + + /** + * Replaces the thrown exception + * + * This exception will be thrown if no response is set in the event. + * + * @param \Exception $exception The thrown exception + * + * @api + */ + public function setException(\Exception $exception) + { + $this->exception = $exception; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/KernelEvent.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/KernelEvent.php new file mode 100644 index 0000000..e57eed4 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/KernelEvent.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\EventDispatcher\Event; + +/** + * Base class for events thrown in the HttpKernel component + * + * @author Bernhard Schussek + * + * @api + */ +class KernelEvent extends Event +{ + /** + * The kernel in which this event was thrown + * @var HttpKernelInterface + */ + private $kernel; + + /** + * The request the kernel is currently processing + * @var Request + */ + private $request; + + /** + * The request type the kernel is currently processing. One of + * HttpKernelInterface::MASTER_REQUEST and HttpKernelInterface::SUB_REQUEST + * @var integer + */ + private $requestType; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType) + { + $this->kernel = $kernel; + $this->request = $request; + $this->requestType = $requestType; + } + + /** + * Returns the kernel in which this event was thrown + * + * @return HttpKernelInterface + * + * @api + */ + public function getKernel() + { + return $this->kernel; + } + + /** + * Returns the request the kernel is currently processing + * + * @return Request + * + * @api + */ + public function getRequest() + { + return $this->request; + } + + /** + * Returns the request type the kernel is currently processing + * + * @return integer One of HttpKernelInterface::MASTER_REQUEST and + * HttpKernelInterface::SUB_REQUEST + * + * @api + */ + public function getRequestType() + { + return $this->requestType; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/PostResponseEvent.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/PostResponseEvent.php new file mode 100644 index 0000000..caa7b9f --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Event/PostResponseEvent.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Allows to execute logic after a response was sent + * + * @author Jordi Boggiano + */ +class PostResponseEvent extends Event +{ + /** + * The kernel in which this event was thrown + * @var HttpKernelInterface + */ + private $kernel; + + private $request; + + private $response; + + public function __construct(HttpKernelInterface $kernel, Request $request, Response $response) + { + $this->kernel = $kernel; + $this->request = $request; + $this->response = $response; + } + + /** + * Returns the kernel in which this event was thrown. + * + * @return HttpKernelInterface + */ + public function getKernel() + { + return $this->kernel; + } + + /** + * Returns the request for which this event was thrown. + * + * @return Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * Returns the response for which this event was thrown. + * + * @return Response + */ + public function getResponse() + { + return $this->response; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/ErrorsLoggerListener.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/ErrorsLoggerListener.php new file mode 100644 index 0000000..13940ab --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/ErrorsLoggerListener.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpKernel\Debug\ErrorHandler; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Injects the logger into the ErrorHandler, so that it can log various errors. + * + * @author Colin Frei + * @author Konstantin Myakshin + */ +class ErrorsLoggerListener implements EventSubscriberInterface +{ + private $channel; + + private $logger; + + public function __construct($channel, LoggerInterface $logger = null) + { + $this->channel = $channel; + $this->logger = $logger; + } + + public function injectLogger() + { + if (null !== $this->logger) { + ErrorHandler::setLogger($this->logger, $this->channel); + } + } + + public static function getSubscribedEvents() + { + return array(KernelEvents::REQUEST => 'injectLogger'); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/EsiListener.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/EsiListener.php new file mode 100644 index 0000000..b90562b --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/EsiListener.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * EsiListener adds a Surrogate-Control HTTP header when the Response needs to be parsed for ESI. + * + * @author Fabien Potencier + */ +class EsiListener implements EventSubscriberInterface +{ + private $esi; + + /** + * Constructor. + * + * @param Esi $esi An ESI instance + */ + public function __construct(Esi $esi = null) + { + $this->esi = $esi; + } + + /** + * Filters the Response. + * + * @param FilterResponseEvent $event A FilterResponseEvent instance + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType() || null === $this->esi) { + return; + } + + $this->esi->addSurrogateControl($event->getResponse()); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::RESPONSE => 'onKernelResponse', + ); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php new file mode 100644 index 0000000..55950b2 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Exception\FlattenException; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * ExceptionListener. + * + * @author Fabien Potencier + */ +class ExceptionListener implements EventSubscriberInterface +{ + protected $controller; + protected $logger; + + public function __construct($controller, LoggerInterface $logger = null) + { + $this->controller = $controller; + $this->logger = $logger; + } + + public function onKernelException(GetResponseForExceptionEvent $event) + { + static $handling; + + if (true === $handling) { + return false; + } + + $handling = true; + + $exception = $event->getException(); + $request = $event->getRequest(); + + $this->logException($exception, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine())); + + $attributes = array( + '_controller' => $this->controller, + 'exception' => FlattenException::create($exception), + 'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null, + // keep for BC -- as $format can be an argument of the controller callable + // see src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php + // @deprecated in 2.4, to be removed in 3.0 + 'format' => $request->getRequestFormat(), + ); + + $request = $request->duplicate(null, null, $attributes); + $request->setMethod('GET'); + + try { + $response = $event->getKernel()->handle($request, HttpKernelInterface::SUB_REQUEST, true); + } catch (\Exception $e) { + $this->logException($exception, sprintf('Exception thrown when handling an exception (%s: %s)', get_class($e), $e->getMessage()), false); + + // set handling to false otherwise it wont be able to handle further more + $handling = false; + + // re-throw the exception from within HttpKernel as this is a catch-all + return; + } + + $event->setResponse($response); + + $handling = false; + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::EXCEPTION => array('onKernelException', -128), + ); + } + + /** + * Logs an exception. + * + * @param \Exception $exception The original \Exception instance + * @param string $message The error message to log + * @param Boolean $original False when the handling of the exception thrown another exception + */ + protected function logException(\Exception $exception, $message, $original = true) + { + $isCritical = !$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500; + $context = array('exception' => $exception); + if (null !== $this->logger) { + if ($isCritical) { + $this->logger->critical($message, $context); + } else { + $this->logger->error($message, $context); + } + } elseif (!$original || $isCritical) { + error_log($message); + } + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/FragmentListener.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/FragmentListener.php new file mode 100644 index 0000000..ef3fad3 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/FragmentListener.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\IpUtils; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\UriSigner; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Handles content fragments represented by special URIs. + * + * All URL paths starting with /_fragment are handled as + * content fragments by this listener. + * + * If the request does not come from a trusted IP, it throws an + * AccessDeniedHttpException exception. + * + * @author Fabien Potencier + */ +class FragmentListener implements EventSubscriberInterface +{ + private $signer; + private $fragmentPath; + + /** + * Constructor. + * + * @param UriSigner $signer A UriSigner instance + * @param string $fragmentPath The path that triggers this listener + */ + public function __construct(UriSigner $signer, $fragmentPath = '/_fragment') + { + $this->signer = $signer; + $this->fragmentPath = $fragmentPath; + } + + /** + * Fixes request attributes when the path is '/_fragment'. + * + * @param GetResponseEvent $event A GetResponseEvent instance + * + * @throws AccessDeniedHttpException if the request does not come from a trusted IP. + */ + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + + if ($this->fragmentPath !== rawurldecode($request->getPathInfo())) { + return; + } + + $this->validateRequest($request); + + parse_str($request->query->get('_path', ''), $attributes); + $request->attributes->add($attributes); + $request->attributes->set('_route_params', array_replace($request->attributes->get('_route_params', array()), $attributes)); + $request->query->remove('_path'); + } + + protected function validateRequest(Request $request) + { + // is the Request safe? + if (!$request->isMethodSafe()) { + throw new AccessDeniedHttpException(); + } + + // does the Request come from a trusted IP? + $trustedIps = array_merge($this->getLocalIpAddresses(), $request->getTrustedProxies()); + $remoteAddress = $request->server->get('REMOTE_ADDR'); + if (IpUtils::checkIp($remoteAddress, $trustedIps)) { + return; + } + + // is the Request signed? + // we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering) + if ($this->signer->check($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().(null !== ($qs = $request->server->get('QUERY_STRING')) ? '?'.$qs : ''))) { + return; + } + + throw new AccessDeniedHttpException(); + } + + protected function getLocalIpAddresses() + { + return array('127.0.0.1', 'fe80::1', '::1'); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array(array('onKernelRequest', 48)), + ); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/LocaleListener.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/LocaleListener.php new file mode 100644 index 0000000..0b864c0 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/LocaleListener.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\RequestContextAwareInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Initializes the locale based on the current request. + * + * @author Fabien Potencier + */ +class LocaleListener implements EventSubscriberInterface +{ + private $router; + private $defaultLocale; + + public function __construct($defaultLocale = 'en', RequestContextAwareInterface $router = null) + { + $this->defaultLocale = $defaultLocale; + $this->router = $router; + } + + public function setRequest(Request $request = null) + { + if (null === $request) { + return; + } + + if ($locale = $request->attributes->get('_locale')) { + $request->setLocale($locale); + } + + if (null !== $this->router) { + $this->router->getContext()->setParameter('_locale', $request->getLocale()); + } + } + + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + $request->setDefaultLocale($this->defaultLocale); + + $this->setRequest($request); + } + + public static function getSubscribedEvents() + { + return array( + // must be registered after the Router to have access to the _locale + KernelEvents::REQUEST => array(array('onKernelRequest', 16)), + ); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php new file mode 100644 index 0000000..43dfaff --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/ProfilerListener.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Profiler\Profile; +use Symfony\Component\HttpKernel\Profiler\Profiler; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * ProfilerListener collects data for the current request by listening to the onKernelResponse event. + * + * @author Fabien Potencier + */ +class ProfilerListener implements EventSubscriberInterface +{ + protected $profiler; + protected $matcher; + protected $onlyException; + protected $onlyMasterRequests; + protected $exception; + protected $children; + protected $requests; + protected $profiles; + + /** + * Constructor. + * + * @param Profiler $profiler A Profiler instance + * @param RequestMatcherInterface $matcher A RequestMatcher instance + * @param Boolean $onlyException true if the profiler only collects data when an exception occurs, false otherwise + * @param Boolean $onlyMasterRequests true if the profiler only collects data when the request is a master request, false otherwise + */ + public function __construct(Profiler $profiler, RequestMatcherInterface $matcher = null, $onlyException = false, $onlyMasterRequests = false) + { + $this->profiler = $profiler; + $this->matcher = $matcher; + $this->onlyException = (Boolean) $onlyException; + $this->onlyMasterRequests = (Boolean) $onlyMasterRequests; + $this->children = new \SplObjectStorage(); + $this->profiles = array(); + } + + /** + * Handles the onKernelException event. + * + * @param GetResponseForExceptionEvent $event A GetResponseForExceptionEvent instance + */ + public function onKernelException(GetResponseForExceptionEvent $event) + { + if ($this->onlyMasterRequests && HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { + return; + } + + $this->exception = $event->getException(); + } + + public function onKernelRequest(GetResponseEvent $event) + { + $this->requests[] = $event->getRequest(); + } + + /** + * Handles the onKernelResponse event. + * + * @param FilterResponseEvent $event A FilterResponseEvent instance + */ + public function onKernelResponse(FilterResponseEvent $event) + { + $master = HttpKernelInterface::MASTER_REQUEST === $event->getRequestType(); + if ($this->onlyMasterRequests && !$master) { + return; + } + + if ($this->onlyException && null === $this->exception) { + return; + } + + $request = $event->getRequest(); + $exception = $this->exception; + $this->exception = null; + + if (null !== $this->matcher && !$this->matcher->matches($request)) { + return; + } + + if (!$profile = $this->profiler->collect($request, $event->getResponse(), $exception)) { + return; + } + + $this->profiles[] = $profile; + + if (null !== $exception) { + foreach ($this->profiles as $profile) { + $this->profiler->saveProfile($profile); + } + + return; + } + + // keep the profile as the child of its parent + if (!$master) { + array_pop($this->requests); + + $parent = end($this->requests); + + // when simulating requests, we might not have the parent + if ($parent) { + $profiles = isset($this->children[$parent]) ? $this->children[$parent] : array(); + $profiles[] = $profile; + $this->children[$parent] = $profiles; + } + } + + if (isset($this->children[$request])) { + foreach ($this->children[$request] as $child) { + $profile->addChild($child); + } + $this->children[$request] = array(); + } + + if ($master) { + $this->saveProfiles($profile); + + unset($this->children); + } + } + + public static function getSubscribedEvents() + { + return array( + // kernel.request must be registered as early as possible to not break + // when an exception is thrown in any other kernel.request listener + KernelEvents::REQUEST => array('onKernelRequest', 1024), + KernelEvents::RESPONSE => array('onKernelResponse', -100), + KernelEvents::EXCEPTION => 'onKernelException', + ); + } + + /** + * Saves the profile hierarchy. + * + * @param Profile $profile The root profile + */ + private function saveProfiles(Profile $profile) + { + $this->profiler->saveProfile($profile); + foreach ($profile->getChildren() as $profile) { + $this->saveProfiles($profile); + } + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/ResponseListener.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/ResponseListener.php new file mode 100644 index 0000000..669980c --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/ResponseListener.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * ResponseListener fixes the Response headers based on the Request. + * + * @author Fabien Potencier + */ +class ResponseListener implements EventSubscriberInterface +{ + private $charset; + + public function __construct($charset) + { + $this->charset = $charset; + } + + /** + * Filters the Response. + * + * @param FilterResponseEvent $event A FilterResponseEvent instance + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { + return; + } + + $response = $event->getResponse(); + + if (null === $response->getCharset()) { + $response->setCharset($this->charset); + } + + $response->prepare($event->getRequest()); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::RESPONSE => 'onKernelResponse', + ); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/RouterListener.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/RouterListener.php new file mode 100644 index 0000000..f68716c --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/RouterListener.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; +use Symfony\Component\Routing\Matcher\RequestMatcherInterface; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RequestContextAwareInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * Initializes the context from the request and sets request attributes based on a matching route. + * + * @author Fabien Potencier + */ +class RouterListener implements EventSubscriberInterface +{ + private $matcher; + private $context; + private $logger; + private $request; + + /** + * Constructor. + * + * @param UrlMatcherInterface|RequestMatcherInterface $matcher The Url or Request matcher + * @param RequestContext|null $context The RequestContext (can be null when $matcher implements RequestContextAwareInterface) + * @param LoggerInterface|null $logger The logger + * + * @throws \InvalidArgumentException + */ + public function __construct($matcher, RequestContext $context = null, LoggerInterface $logger = null) + { + if (!$matcher instanceof UrlMatcherInterface && !$matcher instanceof RequestMatcherInterface) { + throw new \InvalidArgumentException('Matcher must either implement UrlMatcherInterface or RequestMatcherInterface.'); + } + + if (null === $context && !$matcher instanceof RequestContextAwareInterface) { + throw new \InvalidArgumentException('You must either pass a RequestContext or the matcher must implement RequestContextAwareInterface.'); + } + + $this->matcher = $matcher; + $this->context = $context ?: $matcher->getContext(); + $this->logger = $logger; + } + + /** + * Sets the current Request. + * + * The application should call this method whenever the Request + * object changes (entering a Request scope for instance, but + * also when leaving a Request scope -- especially when they are + * nested). + * + * @param Request|null $request A Request instance + */ + public function setRequest(Request $request = null) + { + if (null !== $request && $this->request !== $request) { + $this->context->fromRequest($request); + } + $this->request = $request; + } + + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + + // initialize the context that is also used by the generator (assuming matcher and generator share the same context instance) + // we call setRequest even if most of the time, it has already been done to keep compatibility + // with frameworks which do not use the Symfony service container + $this->setRequest($request); + + if ($request->attributes->has('_controller')) { + // routing is already done + return; + } + + // add attributes based on the request (routing) + try { + // matching a request is more powerful than matching a URL path + context, so try that first + if ($this->matcher instanceof RequestMatcherInterface) { + $parameters = $this->matcher->matchRequest($request); + } else { + $parameters = $this->matcher->match($request->getPathInfo()); + } + + if (null !== $this->logger) { + $this->logger->info(sprintf('Matched route "%s" (parameters: %s)', $parameters['_route'], $this->parametersToString($parameters))); + } + + $request->attributes->add($parameters); + unset($parameters['_route']); + unset($parameters['_controller']); + $request->attributes->set('_route_params', $parameters); + } catch (ResourceNotFoundException $e) { + $message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getPathInfo()); + + throw new NotFoundHttpException($message, $e); + } catch (MethodNotAllowedException $e) { + $message = sprintf('No route found for "%s %s": Method Not Allowed (Allow: %s)', $request->getMethod(), $request->getPathInfo(), strtoupper(implode(', ', $e->getAllowedMethods()))); + + throw new MethodNotAllowedHttpException($e->getAllowedMethods(), $message, $e); + } + } + + private function parametersToString(array $parameters) + { + $pieces = array(); + foreach ($parameters as $key => $val) { + $pieces[] = sprintf('"%s": "%s"', $key, (is_string($val) ? $val : json_encode($val))); + } + + return implode(', ', $pieces); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array(array('onKernelRequest', 32)), + ); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/StreamedResponseListener.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/StreamedResponseListener.php new file mode 100644 index 0000000..88505fa --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/EventListener/StreamedResponseListener.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * StreamedResponseListener is responsible for sending the Response + * to the client. + * + * @author Fabien Potencier + */ +class StreamedResponseListener implements EventSubscriberInterface +{ + /** + * Filters the Response. + * + * @param FilterResponseEvent $event A FilterResponseEvent instance + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { + return; + } + + $response = $event->getResponse(); + + if ($response instanceof StreamedResponse) { + $response->send(); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::RESPONSE => array('onKernelResponse', -1024), + ); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/AccessDeniedHttpException.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/AccessDeniedHttpException.php new file mode 100644 index 0000000..714102b --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/AccessDeniedHttpException.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * AccessDeniedHttpException. + * + * @author Fabien Potencier + * @author Christophe Coevoet + */ +class AccessDeniedHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param integer $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(403, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/BadRequestHttpException.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/BadRequestHttpException.php new file mode 100644 index 0000000..3346345 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/BadRequestHttpException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * BadRequestHttpException. + * + * @author Ben Ramsey + */ +class BadRequestHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param integer $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(400, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/ConflictHttpException.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/ConflictHttpException.php new file mode 100644 index 0000000..e416b34 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/ConflictHttpException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * ConflictHttpException. + * + * @author Ben Ramsey + */ +class ConflictHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param integer $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(409, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/FatalErrorException.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/FatalErrorException.php new file mode 100644 index 0000000..1f1ef1a --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/FatalErrorException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +use Symfony\Component\Debug\Exception\FatalErrorException as DebugFatalErrorException; + +/** + * Fatal Error Exception. + * + * @author Konstanton Myakshin + * + * @deprecated Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead. + */ +class FatalErrorException extends DebugFatalErrorException +{ +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/FlattenException.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/FlattenException.php new file mode 100644 index 0000000..0168afc --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/FlattenException.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +use Symfony\Component\Debug\Exception\FlattenException as DebugFlattenException; + +/** + * FlattenException wraps a PHP Exception to be able to serialize it. + * + * Basically, this class removes all objects from the trace. + * + * @author Fabien Potencier + * + * @deprecated Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead. + */ +class FlattenException extends DebugFlattenException +{ +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/GoneHttpException.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/GoneHttpException.php new file mode 100644 index 0000000..9fea164 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/GoneHttpException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * GoneHttpException. + * + * @author Ben Ramsey + */ +class GoneHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param integer $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(410, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/HttpException.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/HttpException.php new file mode 100644 index 0000000..4e1b526 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/HttpException.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * HttpException. + * + * @author Kris Wallsmith + */ +class HttpException extends \RuntimeException implements HttpExceptionInterface +{ + private $statusCode; + private $headers; + + public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = array(), $code = 0) + { + $this->statusCode = $statusCode; + $this->headers = $headers; + + parent::__construct($message, $code, $previous); + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/HttpExceptionInterface.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/HttpExceptionInterface.php new file mode 100644 index 0000000..dd4a9dc --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/HttpExceptionInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * Interface for HTTP error exceptions. + * + * @author Kris Wallsmith + */ +interface HttpExceptionInterface +{ + /** + * Returns the status code. + * + * @return integer An HTTP response status code + */ + public function getStatusCode(); + + /** + * Returns response headers. + * + * @return array Response headers + */ + public function getHeaders(); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/LengthRequiredHttpException.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/LengthRequiredHttpException.php new file mode 100644 index 0000000..7aca014 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/LengthRequiredHttpException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * LengthRequiredHttpException. + * + * @author Ben Ramsey + */ +class LengthRequiredHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param integer $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(411, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/MethodNotAllowedHttpException.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/MethodNotAllowedHttpException.php new file mode 100644 index 0000000..3a81586 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/MethodNotAllowedHttpException.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * MethodNotAllowedHttpException. + * + * @author Kris Wallsmith + */ +class MethodNotAllowedHttpException extends HttpException +{ + /** + * Constructor. + * + * @param array $allow An array of allowed methods + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param integer $code The internal exception code + */ + public function __construct(array $allow, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array('Allow' => strtoupper(implode(', ', $allow))); + + parent::__construct(405, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/NotAcceptableHttpException.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/NotAcceptableHttpException.php new file mode 100644 index 0000000..6ac448a --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/NotAcceptableHttpException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * NotAcceptableHttpException. + * + * @author Ben Ramsey + */ +class NotAcceptableHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param integer $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(406, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/NotFoundHttpException.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/NotFoundHttpException.php new file mode 100644 index 0000000..547976c --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/NotFoundHttpException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * NotFoundHttpException. + * + * @author Fabien Potencier + */ +class NotFoundHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param integer $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(404, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/PreconditionFailedHttpException.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/PreconditionFailedHttpException.php new file mode 100644 index 0000000..4126c88 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/PreconditionFailedHttpException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * PreconditionFailedHttpException. + * + * @author Ben Ramsey + */ +class PreconditionFailedHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param integer $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(412, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/PreconditionRequiredHttpException.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/PreconditionRequiredHttpException.php new file mode 100644 index 0000000..75ba177 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/PreconditionRequiredHttpException.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * PreconditionRequiredHttpException. + * + * @author Ben Ramsey + * @see http://tools.ietf.org/html/rfc6585 + */ +class PreconditionRequiredHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param integer $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(428, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/ServiceUnavailableHttpException.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/ServiceUnavailableHttpException.php new file mode 100644 index 0000000..09bbb6e --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/ServiceUnavailableHttpException.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * ServiceUnavailableHttpException. + * + * @author Ben Ramsey + */ +class ServiceUnavailableHttpException extends HttpException +{ + /** + * Constructor. + * + * @param int|string $retryAfter The number of seconds or HTTP-date after which the request may be retried + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param integer $code The internal exception code + */ + public function __construct($retryAfter = null, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array(); + if ($retryAfter) { + $headers = array('Retry-After' => $retryAfter); + } + + parent::__construct(503, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/TooManyRequestsHttpException.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/TooManyRequestsHttpException.php new file mode 100644 index 0000000..b1232ef --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/TooManyRequestsHttpException.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * TooManyRequestsHttpException. + * + * @author Ben Ramsey + * @see http://tools.ietf.org/html/rfc6585 + */ +class TooManyRequestsHttpException extends HttpException +{ + /** + * Constructor. + * + * @param integer|string $retryAfter The number of seconds or HTTP-date after which the request may be retried + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param integer $code The internal exception code + */ + public function __construct($retryAfter = null, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array(); + if ($retryAfter) { + $headers = array('Retry-After' => $retryAfter); + } + + parent::__construct(429, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/UnauthorizedHttpException.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/UnauthorizedHttpException.php new file mode 100644 index 0000000..2527d62 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/UnauthorizedHttpException.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * UnauthorizedHttpException. + * + * @author Ben Ramsey + */ +class UnauthorizedHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $challenge WWW-Authenticate challenge string + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param integer $code The internal exception code + */ + public function __construct($challenge, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array('WWW-Authenticate' => $challenge); + + parent::__construct(401, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/UnsupportedMediaTypeHttpException.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/UnsupportedMediaTypeHttpException.php new file mode 100644 index 0000000..88bceec --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Exception/UnsupportedMediaTypeHttpException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * UnsupportedMediaTypeHttpException. + * + * @author Ben Ramsey + */ +class UnsupportedMediaTypeHttpException extends HttpException +{ + /** + * Constructor. + * + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param integer $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(415, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/EsiFragmentRenderer.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/EsiFragmentRenderer.php new file mode 100644 index 0000000..68b1a87 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/EsiFragmentRenderer.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\HttpCache\Esi; + +/** + * Implements the ESI rendering strategy. + * + * @author Fabien Potencier + */ +class EsiFragmentRenderer extends RoutableFragmentRenderer +{ + private $esi; + private $inlineStrategy; + + /** + * Constructor. + * + * The "fallback" strategy when ESI is not available should always be an + * instance of InlineFragmentRenderer. + * + * @param Esi $esi An Esi instance + * @param InlineFragmentRenderer $inlineStrategy The inline strategy to use when ESI is not supported + */ + public function __construct(Esi $esi, InlineFragmentRenderer $inlineStrategy) + { + $this->esi = $esi; + $this->inlineStrategy = $inlineStrategy; + } + + /** + * {@inheritdoc} + * + * Note that if the current Request has no ESI capability, this method + * falls back to use the inline rendering strategy. + * + * Additional available options: + * + * * alt: an alternative URI to render in case of an error + * * comment: a comment to add when returning an esi:include tag + * + * @see Symfony\Component\HttpKernel\HttpCache\ESI + */ + public function render($uri, Request $request, array $options = array()) + { + if (!$this->esi->hasSurrogateEsiCapability($request)) { + return $this->inlineStrategy->render($uri, $request, $options); + } + + if ($uri instanceof ControllerReference) { + $uri = $this->generateFragmentUri($uri, $request); + } + + $alt = isset($options['alt']) ? $options['alt'] : null; + if ($alt instanceof ControllerReference) { + $alt = $this->generateFragmentUri($alt, $request); + } + + $tag = $this->esi->renderIncludeTag($uri, $alt, isset($options['ignore_errors']) ? $options['ignore_errors'] : false, isset($options['comment']) ? $options['comment'] : ''); + + return new Response($tag); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'esi'; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php new file mode 100644 index 0000000..af9b9ba --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/FragmentHandler.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +/** + * Renders a URI that represents a resource fragment. + * + * This class handles the rendering of resource fragments that are included into + * a main resource. The handling of the rendering is managed by specialized renderers. + * + * @author Fabien Potencier + * + * @see FragmentRendererInterface + */ +class FragmentHandler +{ + private $debug; + private $renderers; + private $request; + + /** + * Constructor. + * + * @param FragmentRendererInterface[] $renderers An array of FragmentRendererInterface instances + * @param Boolean $debug Whether the debug mode is enabled or not + */ + public function __construct(array $renderers = array(), $debug = false) + { + $this->renderers = array(); + foreach ($renderers as $renderer) { + $this->addRenderer($renderer); + } + $this->debug = $debug; + } + + /** + * Adds a renderer. + * + * @param FragmentRendererInterface $renderer A FragmentRendererInterface instance + */ + public function addRenderer(FragmentRendererInterface $renderer) + { + $this->renderers[$renderer->getName()] = $renderer; + } + + /** + * Sets the current Request. + * + * @param Request $request The current Request + */ + public function setRequest(Request $request = null) + { + $this->request = $request; + } + + /** + * Renders a URI and returns the Response content. + * + * Available options: + * + * * ignore_errors: true to return an empty string in case of an error + * + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance + * @param string $renderer The renderer name + * @param array $options An array of options + * + * @return string|null The Response content or null when the Response is streamed + * + * @throws \InvalidArgumentException when the renderer does not exist + * @throws \RuntimeException when the Response is not successful + */ + public function render($uri, $renderer = 'inline', array $options = array()) + { + if (!isset($options['ignore_errors'])) { + $options['ignore_errors'] = !$this->debug; + } + + if (!isset($this->renderers[$renderer])) { + throw new \InvalidArgumentException(sprintf('The "%s" renderer does not exist.', $renderer)); + } + + if (null === $this->request) { + throw new \LogicException('Rendering a fragment can only be done when handling a master Request.'); + } + + return $this->deliver($this->renderers[$renderer]->render($uri, $this->request, $options)); + } + + /** + * Delivers the Response as a string. + * + * When the Response is a StreamedResponse, the content is streamed immediately + * instead of being returned. + * + * @param Response $response A Response instance + * + * @return string|null The Response content or null when the Response is streamed + * + * @throws \RuntimeException when the Response is not successful + */ + protected function deliver(Response $response) + { + if (!$response->isSuccessful()) { + throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $this->request->getUri(), $response->getStatusCode())); + } + + if (!$response instanceof StreamedResponse) { + return $response->getContent(); + } + + $response->sendContent(); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/FragmentRendererInterface.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/FragmentRendererInterface.php new file mode 100644 index 0000000..a758d2c --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/FragmentRendererInterface.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +/** + * Interface implemented by all rendering strategies. + * + * @author Fabien Potencier + * + * @see Symfony\Component\HttpKernel\FragmentRenderer + */ +interface FragmentRendererInterface +{ + /** + * Renders a URI and returns the Response content. + * + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance + * @param Request $request A Request instance + * @param array $options An array of options + * + * @return Response A Response instance + */ + public function render($uri, Request $request, array $options = array()); + + /** + * Gets the name of the strategy. + * + * @return string The strategy name + */ + public function getName(); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/HIncludeFragmentRenderer.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/HIncludeFragmentRenderer.php new file mode 100644 index 0000000..882bc2b --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/HIncludeFragmentRenderer.php @@ -0,0 +1,162 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +if (!defined('ENT_SUBSTITUTE')) { + define('ENT_SUBSTITUTE', 8); +} + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Templating\EngineInterface; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\UriSigner; + +/** + * Implements the Hinclude rendering strategy. + * + * @author Fabien Potencier + */ +class HIncludeFragmentRenderer extends RoutableFragmentRenderer +{ + private $globalDefaultTemplate; + private $signer; + private $templating; + private $charset; + + /** + * Constructor. + * + * @param EngineInterface|\Twig_Environment $templating An EngineInterface or a \Twig_Environment instance + * @param UriSigner $signer A UriSigner instance + * @param string $globalDefaultTemplate The global default content (it can be a template name or the content) + * @param string $charset + */ + public function __construct($templating = null, UriSigner $signer = null, $globalDefaultTemplate = null, $charset = 'utf-8') + { + $this->setTemplating($templating); + $this->globalDefaultTemplate = $globalDefaultTemplate; + $this->signer = $signer; + $this->charset = $charset; + } + + /** + * Sets the templating engine to use to render the default content. + * + * @param EngineInterface|\Twig_Environment|null $templating An EngineInterface or a \Twig_Environment instance + * + * @throws \InvalidArgumentException + */ + public function setTemplating($templating) + { + if (null !== $templating && !$templating instanceof EngineInterface && !$templating instanceof \Twig_Environment) { + throw new \InvalidArgumentException('The hinclude rendering strategy needs an instance of \Twig_Environment or Symfony\Component\Templating\EngineInterface'); + } + + $this->templating = $templating; + } + + /** + * Checks if a templating engine has been set. + * + * @return Boolean true if the templating engine has been set, false otherwise + */ + public function hasTemplating() + { + return null !== $this->templating; + } + + /** + * {@inheritdoc} + * + * Additional available options: + * + * * default: The default content (it can be a template name or the content) + * * id: An optional hx:include tag id attribute + * * attributes: An optional array of hx:include tag attributes + */ + public function render($uri, Request $request, array $options = array()) + { + if ($uri instanceof ControllerReference) { + if (null === $this->signer) { + throw new \LogicException('You must use a proper URI when using the Hinclude rendering strategy or set a URL signer.'); + } + + $uri = $this->signer->sign($this->generateFragmentUri($uri, $request)); + } + + // We need to replace ampersands in the URI with the encoded form in order to return valid html/xml content. + $uri = str_replace('&', '&', $uri); + + $template = isset($options['default']) ? $options['default'] : $this->globalDefaultTemplate; + if (null !== $this->templating && $template && $this->templateExists($template)) { + $content = $this->templating->render($template); + } else { + $content = $template; + } + + $attributes = isset($options['attributes']) && is_array($options['attributes']) ? $options['attributes'] : array(); + if (isset($options['id']) && $options['id']) { + $attributes['id'] = $options['id']; + } + $renderedAttributes = ''; + if (count($attributes) > 0) { + foreach ($attributes as $attribute => $value) { + $renderedAttributes .= sprintf( + ' %s="%s"', + htmlspecialchars($attribute, ENT_QUOTES | ENT_SUBSTITUTE, $this->charset, false), + htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, $this->charset, false) + ); + } + } + + return new Response(sprintf('%s', $uri, $renderedAttributes, $content)); + } + + /** + * @param string $template + * + * @return boolean + */ + private function templateExists($template) + { + if ($this->templating instanceof EngineInterface) { + try { + return $this->templating->exists($template); + } catch (\InvalidArgumentException $e) { + return false; + } + } + + $loader = $this->templating->getLoader(); + if ($loader instanceof \Twig_ExistsLoaderInterface) { + return $loader->exists($template); + } + + try { + $loader->getSource($template); + + return true; + } catch (\Twig_Error_Loader $e) { + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'hinclude'; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php new file mode 100644 index 0000000..a3f37c3 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/InlineFragmentRenderer.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * Implements the inline rendering strategy where the Request is rendered by the current HTTP kernel. + * + * @author Fabien Potencier + */ +class InlineFragmentRenderer extends RoutableFragmentRenderer +{ + private $kernel; + private $dispatcher; + + /** + * Constructor. + * + * @param HttpKernelInterface $kernel A HttpKernelInterface instance + */ + public function __construct(HttpKernelInterface $kernel, EventDispatcherInterface $dispatcher = null) + { + $this->kernel = $kernel; + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + * + * Additional available options: + * + * * alt: an alternative URI to render in case of an error + */ + public function render($uri, Request $request, array $options = array()) + { + $reference = null; + if ($uri instanceof ControllerReference) { + $reference = $uri; + + // Remove attributes from the generated URI because if not, the Symfony + // routing system will use them to populate the Request attributes. We don't + // want that as we want to preserve objects (so we manually set Request attributes + // below instead) + $attributes = $reference->attributes; + $reference->attributes = array(); + + // The request format and locale might have been overriden by the user + foreach (array('_format', '_locale') as $key) { + if (isset($attributes[$key])) { + $reference->attributes[$key] = $attributes[$key]; + } + } + + $uri = $this->generateFragmentUri($uri, $request); + $reference->attributes = array_merge($attributes, $reference->attributes); + } + + $subRequest = $this->createSubRequest($uri, $request); + + // override Request attributes as they can be objects (which are not supported by the generated URI) + if (null !== $reference) { + $subRequest->attributes->add($reference->attributes); + } + + $level = ob_get_level(); + try { + return $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false); + } catch (\Exception $e) { + // we dispatch the exception event to trigger the logging + // the response that comes back is simply ignored + if (isset($options['ignore_errors']) && $options['ignore_errors'] && $this->dispatcher) { + $event = new GetResponseForExceptionEvent($this->kernel, $request, HttpKernelInterface::SUB_REQUEST, $e); + + $this->dispatcher->dispatch(KernelEvents::EXCEPTION, $event); + } + + // let's clean up the output buffers that were created by the sub-request + while (ob_get_level() > $level) { + ob_get_clean(); + } + + if (isset($options['alt'])) { + $alt = $options['alt']; + unset($options['alt']); + + return $this->render($alt, $request, $options); + } + + if (!isset($options['ignore_errors']) || !$options['ignore_errors']) { + throw $e; + } + + return new Response(); + } + } + + protected function createSubRequest($uri, Request $request) + { + $cookies = $request->cookies->all(); + $server = $request->server->all(); + + // Override the arguments to emulate a sub-request. + // Sub-request object will point to localhost as client ip and real client ip + // will be included into trusted header for client ip + try { + if ($trustedHeaderName = Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)) { + $currentXForwardedFor = $request->headers->get($trustedHeaderName, ''); + + $server['HTTP_'.$trustedHeaderName] = ($currentXForwardedFor ? $currentXForwardedFor.', ' : '').$request->getClientIp(); + } + } catch (\InvalidArgumentException $e) { + // Do nothing + } + + $server['REMOTE_ADDR'] = '127.0.0.1'; + + $subRequest = $request::create($uri, 'get', array(), $cookies, array(), $server); + if ($request->headers->has('Surrogate-Capability')) { + $subRequest->headers->set('Surrogate-Capability', $request->headers->get('Surrogate-Capability')); + } + + if ($session = $request->getSession()) { + $subRequest->setSession($session); + } + + return $subRequest; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'inline'; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/RoutableFragmentRenderer.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/RoutableFragmentRenderer.php new file mode 100644 index 0000000..bd133ce --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Fragment/RoutableFragmentRenderer.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\EventListener\FragmentListener; + +/** + * Adds the possibility to generate a fragment URI for a given Controller. + * + * @author Fabien Potencier + */ +abstract class RoutableFragmentRenderer implements FragmentRendererInterface +{ + private $fragmentPath = '/_fragment'; + + /** + * Sets the fragment path that triggers the fragment listener. + * + * @param string $path The path + * + * @see FragmentListener + */ + public function setFragmentPath($path) + { + $this->fragmentPath = $path; + } + + /** + * Generates a fragment URI for a given controller. + * + * @param ControllerReference $reference A ControllerReference instance + * @param Request $request A Request instance + * @param Boolean $absolute Whether to generate an absolute URL or not + * + * @return string A fragment URI + */ + protected function generateFragmentUri(ControllerReference $reference, Request $request, $absolute = false) + { + // We need to forward the current _format and _locale values as we don't have + // a proper routing pattern to do the job for us. + // This makes things inconsistent if you switch from rendering a controller + // to rendering a route if the route pattern does not contain the special + // _format and _locale placeholders. + if (!isset($reference->attributes['_format'])) { + $reference->attributes['_format'] = $request->getRequestFormat(); + } + if (!isset($reference->attributes['_locale'])) { + $reference->attributes['_locale'] = $request->getLocale(); + } + + $reference->attributes['_controller'] = $reference->controller; + + $reference->query['_path'] = http_build_query($reference->attributes, '', '&'); + + $path = $this->fragmentPath.'?'.http_build_query($reference->query, '', '&'); + + if ($absolute) { + return $request->getUriForPath($path); + } + + return $request->getBaseUrl().$path; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/Esi.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/Esi.php new file mode 100644 index 0000000..455b3dc --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/Esi.php @@ -0,0 +1,245 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Esi implements the ESI capabilities to Request and Response instances. + * + * For more information, read the following W3C notes: + * + * * ESI Language Specification 1.0 (http://www.w3.org/TR/esi-lang) + * + * * Edge Architecture Specification (http://www.w3.org/TR/edge-arch) + * + * @author Fabien Potencier + */ +class Esi +{ + private $contentTypes; + + /** + * Constructor. + * + * @param array $contentTypes An array of content-type that should be parsed for ESI information. + * (default: text/html, text/xml, application/xhtml+xml, and application/xml) + */ + public function __construct(array $contentTypes = array('text/html', 'text/xml', 'application/xhtml+xml', 'application/xml')) + { + $this->contentTypes = $contentTypes; + } + + /** + * Returns a new cache strategy instance. + * + * @return EsiResponseCacheStrategyInterface A EsiResponseCacheStrategyInterface instance + */ + public function createCacheStrategy() + { + return new EsiResponseCacheStrategy(); + } + + /** + * Checks that at least one surrogate has ESI/1.0 capability. + * + * @param Request $request A Request instance + * + * @return Boolean true if one surrogate has ESI/1.0 capability, false otherwise + */ + public function hasSurrogateEsiCapability(Request $request) + { + if (null === $value = $request->headers->get('Surrogate-Capability')) { + return false; + } + + return false !== strpos($value, 'ESI/1.0'); + } + + /** + * Adds ESI/1.0 capability to the given Request. + * + * @param Request $request A Request instance + */ + public function addSurrogateEsiCapability(Request $request) + { + $current = $request->headers->get('Surrogate-Capability'); + $new = 'symfony2="ESI/1.0"'; + + $request->headers->set('Surrogate-Capability', $current ? $current.', '.$new : $new); + } + + /** + * Adds HTTP headers to specify that the Response needs to be parsed for ESI. + * + * This method only adds an ESI HTTP header if the Response has some ESI tags. + * + * @param Response $response A Response instance + */ + public function addSurrogateControl(Response $response) + { + if (false !== strpos($response->getContent(), 'headers->set('Surrogate-Control', 'content="ESI/1.0"'); + } + } + + /** + * Checks that the Response needs to be parsed for ESI tags. + * + * @param Response $response A Response instance + * + * @return Boolean true if the Response needs to be parsed, false otherwise + */ + public function needsEsiParsing(Response $response) + { + if (!$control = $response->headers->get('Surrogate-Control')) { + return false; + } + + return (Boolean) preg_match('#content="[^"]*ESI/1.0[^"]*"#', $control); + } + + /** + * Renders an ESI tag. + * + * @param string $uri A URI + * @param string $alt An alternate URI + * @param Boolean $ignoreErrors Whether to ignore errors or not + * @param string $comment A comment to add as an esi:include tag + * + * @return string + */ + public function renderIncludeTag($uri, $alt = null, $ignoreErrors = true, $comment = '') + { + $html = sprintf('', + $uri, + $ignoreErrors ? ' onerror="continue"' : '', + $alt ? sprintf(' alt="%s"', $alt) : '' + ); + + if (!empty($comment)) { + return sprintf("\n%s", $comment, $html); + } + + return $html; + } + + /** + * Replaces a Response ESI tags with the included resource content. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * + * @return Response + */ + public function process(Request $request, Response $response) + { + $this->request = $request; + $type = $response->headers->get('Content-Type'); + if (empty($type)) { + $type = 'text/html'; + } + + $parts = explode(';', $type); + if (!in_array($parts[0], $this->contentTypes)) { + return $response; + } + + // we don't use a proper XML parser here as we can have ESI tags in a plain text response + $content = $response->getContent(); + $content = str_replace(array('', ''), $content); + $content = preg_replace_callback('##', array($this, 'handleEsiIncludeTag'), $content); + $content = preg_replace('#]*(?:/|#', '', $content); + $content = preg_replace('#.*?#', '', $content); + + $response->setContent($content); + $response->headers->set('X-Body-Eval', 'ESI'); + + // remove ESI/1.0 from the Surrogate-Control header + if ($response->headers->has('Surrogate-Control')) { + $value = $response->headers->get('Surrogate-Control'); + if ('content="ESI/1.0"' == $value) { + $response->headers->remove('Surrogate-Control'); + } elseif (preg_match('#,\s*content="ESI/1.0"#', $value)) { + $response->headers->set('Surrogate-Control', preg_replace('#,\s*content="ESI/1.0"#', '', $value)); + } elseif (preg_match('#content="ESI/1.0",\s*#', $value)) { + $response->headers->set('Surrogate-Control', preg_replace('#content="ESI/1.0",\s*#', '', $value)); + } + } + } + + /** + * Handles an ESI from the cache. + * + * @param HttpCache $cache An HttpCache instance + * @param string $uri The main URI + * @param string $alt An alternative URI + * @param Boolean $ignoreErrors Whether to ignore errors or not + * + * @return string + * + * @throws \RuntimeException + * @throws \Exception + */ + public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors) + { + $subRequest = Request::create($uri, 'get', array(), $cache->getRequest()->cookies->all(), array(), $cache->getRequest()->server->all()); + + try { + $response = $cache->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true); + + if (!$response->isSuccessful()) { + throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $subRequest->getUri(), $response->getStatusCode())); + } + + return $response->getContent(); + } catch (\Exception $e) { + if ($alt) { + return $this->handle($cache, $alt, '', $ignoreErrors); + } + + if (!$ignoreErrors) { + throw $e; + } + } + } + + /** + * Handles an ESI include tag (called internally). + * + * @param array $attributes An array containing the attributes. + * + * @return string The response content for the include. + * + * @throws \RuntimeException + */ + private function handleEsiIncludeTag($attributes) + { + $options = array(); + preg_match_all('/(src|onerror|alt)="([^"]*?)"/', $attributes[1], $matches, PREG_SET_ORDER); + foreach ($matches as $set) { + $options[$set[1]] = $set[2]; + } + + if (!isset($options['src'])) { + throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.'); + } + + return sprintf('esi->handle($this, \'%s\', \'%s\', %s) ?>'."\n", + $options['src'], + isset($options['alt']) ? $options['alt'] : null, + isset($options['onerror']) && 'continue' == $options['onerror'] ? 'true' : 'false' + ); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategy.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategy.php new file mode 100644 index 0000000..6384af9 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategy.php @@ -0,0 +1,85 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Response; + +/** + * EsiResponseCacheStrategy knows how to compute the Response cache HTTP header + * based on the different ESI response cache headers. + * + * This implementation changes the master response TTL to the smallest TTL received + * or force validation if one of the ESI has validation cache strategy. + * + * @author Fabien Potencier + */ +class EsiResponseCacheStrategy implements EsiResponseCacheStrategyInterface +{ + private $cacheable = true; + private $embeddedResponses = 0; + private $ttls = array(); + private $maxAges = array(); + + /** + * {@inheritdoc} + */ + public function add(Response $response) + { + if ($response->isValidateable()) { + $this->cacheable = false; + } else { + $this->ttls[] = $response->getTtl(); + $this->maxAges[] = $response->getMaxAge(); + } + + $this->embeddedResponses++; + } + + /** + * {@inheritdoc} + */ + public function update(Response $response) + { + // if we have no embedded Response, do nothing + if (0 === $this->embeddedResponses) { + return; + } + + // Remove validation related headers in order to avoid browsers using + // their own cache, because some of the response content comes from + // at least one embedded response (which likely has a different caching strategy). + if ($response->isValidateable()) { + $response->setEtag(null); + $response->setLastModified(null); + $this->cacheable = false; + } + + if (!$this->cacheable) { + $response->headers->set('Cache-Control', 'no-cache, must-revalidate'); + + return; + } + + $this->ttls[] = $response->getTtl(); + $this->maxAges[] = $response->getMaxAge(); + + if (null !== $maxAge = min($this->maxAges)) { + $response->setSharedMaxAge($maxAge); + $response->headers->set('Age', $maxAge - min($this->ttls)); + } + $response->setMaxAge(0); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategyInterface.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategyInterface.php new file mode 100644 index 0000000..0fb8a12 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/EsiResponseCacheStrategyInterface.php @@ -0,0 +1,41 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Response; + +/** + * EsiResponseCacheStrategyInterface implementations know how to compute the + * Response cache HTTP header based on the different ESI response cache headers. + * + * @author Fabien Potencier + */ +interface EsiResponseCacheStrategyInterface +{ + /** + * Adds a Response. + * + * @param Response $response + */ + public function add(Response $response); + + /** + * Updates the Response HTTP headers based on the embedded Responses. + * + * @param Response $response + */ + public function update(Response $response); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/HttpCache.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/HttpCache.php new file mode 100644 index 0000000..63cde7e --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/HttpCache.php @@ -0,0 +1,688 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\TerminableInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpCache\Esi; + +/** + * Cache provides HTTP caching. + * + * @author Fabien Potencier + * + * @api + */ +class HttpCache implements HttpKernelInterface, TerminableInterface +{ + private $kernel; + private $store; + private $request; + private $esi; + private $esiCacheStrategy; + private $traces; + + /** + * Constructor. + * + * The available options are: + * + * * debug: If true, the traces are added as a HTTP header to ease debugging + * + * * default_ttl The number of seconds that a cache entry should be considered + * fresh when no explicit freshness information is provided in + * a response. Explicit Cache-Control or Expires headers + * override this value. (default: 0) + * + * * private_headers Set of request headers that trigger "private" cache-control behavior + * on responses that don't explicitly state whether the response is + * public or private via a Cache-Control directive. (default: Authorization and Cookie) + * + * * allow_reload Specifies whether the client can force a cache reload by including a + * Cache-Control "no-cache" directive in the request. Set it to ``true`` + * for compliance with RFC 2616. (default: false) + * + * * allow_revalidate Specifies whether the client can force a cache revalidate by including + * a Cache-Control "max-age=0" directive in the request. Set it to ``true`` + * for compliance with RFC 2616. (default: false) + * + * * stale_while_revalidate Specifies the default number of seconds (the granularity is the second as the + * Response TTL precision is a second) during which the cache can immediately return + * a stale response while it revalidates it in the background (default: 2). + * This setting is overridden by the stale-while-revalidate HTTP Cache-Control + * extension (see RFC 5861). + * + * * stale_if_error Specifies the default number of seconds (the granularity is the second) during which + * the cache can serve a stale response when an error is encountered (default: 60). + * This setting is overridden by the stale-if-error HTTP Cache-Control extension + * (see RFC 5861). + * + * @param HttpKernelInterface $kernel An HttpKernelInterface instance + * @param StoreInterface $store A Store instance + * @param Esi $esi An Esi instance + * @param array $options An array of options + */ + public function __construct(HttpKernelInterface $kernel, StoreInterface $store, Esi $esi = null, array $options = array()) + { + $this->store = $store; + $this->kernel = $kernel; + + // needed in case there is a fatal error because the backend is too slow to respond + register_shutdown_function(array($this->store, 'cleanup')); + + $this->options = array_merge(array( + 'debug' => false, + 'default_ttl' => 0, + 'private_headers' => array('Authorization', 'Cookie'), + 'allow_reload' => false, + 'allow_revalidate' => false, + 'stale_while_revalidate' => 2, + 'stale_if_error' => 60, + ), $options); + $this->esi = $esi; + $this->traces = array(); + } + + /** + * Gets the current store. + * + * @return StoreInterface $store A StoreInterface instance + */ + public function getStore() + { + return $this->store; + } + + /** + * Returns an array of events that took place during processing of the last request. + * + * @return array An array of events + */ + public function getTraces() + { + return $this->traces; + } + + /** + * Returns a log message for the events of the last request processing. + * + * @return string A log message + */ + public function getLog() + { + $log = array(); + foreach ($this->traces as $request => $traces) { + $log[] = sprintf('%s: %s', $request, implode(', ', $traces)); + } + + return implode('; ', $log); + } + + /** + * Gets the Request instance associated with the master request. + * + * @return Request A Request instance + */ + public function getRequest() + { + return $this->request; + } + + /** + * Gets the Kernel instance + * + * @return HttpKernelInterface An HttpKernelInterface instance + */ + public function getKernel() + { + return $this->kernel; + } + + + /** + * Gets the Esi instance + * + * @return Esi An Esi instance + */ + public function getEsi() + { + return $this->esi; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + // FIXME: catch exceptions and implement a 500 error page here? -> in Varnish, there is a built-in error page mechanism + if (HttpKernelInterface::MASTER_REQUEST === $type) { + $this->traces = array(); + $this->request = $request; + if (null !== $this->esi) { + $this->esiCacheStrategy = $this->esi->createCacheStrategy(); + } + } + + $path = $request->getPathInfo(); + if ($qs = $request->getQueryString()) { + $path .= '?'.$qs; + } + $this->traces[$request->getMethod().' '.$path] = array(); + + if (!$request->isMethodSafe()) { + $response = $this->invalidate($request, $catch); + } elseif ($request->headers->has('expect')) { + $response = $this->pass($request, $catch); + } else { + $response = $this->lookup($request, $catch); + } + + $this->restoreResponseBody($request, $response); + + $response->setDate(new \DateTime(null, new \DateTimeZone('UTC'))); + + if (HttpKernelInterface::MASTER_REQUEST === $type && $this->options['debug']) { + $response->headers->set('X-Symfony-Cache', $this->getLog()); + } + + if (null !== $this->esi) { + if (HttpKernelInterface::MASTER_REQUEST === $type) { + $this->esiCacheStrategy->update($response); + } else { + $this->esiCacheStrategy->add($response); + } + } + + $response->prepare($request); + + $response->isNotModified($request); + + return $response; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function terminate(Request $request, Response $response) + { + if ($this->getKernel() instanceof TerminableInterface) { + $this->getKernel()->terminate($request, $response); + } + } + + /** + * Forwards the Request to the backend without storing the Response in the cache. + * + * @param Request $request A Request instance + * @param Boolean $catch Whether to process exceptions + * + * @return Response A Response instance + */ + protected function pass(Request $request, $catch = false) + { + $this->record($request, 'pass'); + + return $this->forward($request, $catch); + } + + /** + * Invalidates non-safe methods (like POST, PUT, and DELETE). + * + * @param Request $request A Request instance + * @param Boolean $catch Whether to process exceptions + * + * @return Response A Response instance + * + * @throws \Exception + * + * @see RFC2616 13.10 + */ + protected function invalidate(Request $request, $catch = false) + { + $response = $this->pass($request, $catch); + + // invalidate only when the response is successful + if ($response->isSuccessful() || $response->isRedirect()) { + try { + $this->store->invalidate($request, $catch); + + // As per the RFC, invalidate Location and Content-Location URLs if present + foreach (array('Location', 'Content-Location') as $header) { + if ($uri = $response->headers->get($header)) { + $subRequest = $request::create($uri, 'get', array(), array(), array(), $request->server->all()); + + $this->store->invalidate($subRequest); + } + } + + $this->record($request, 'invalidate'); + } catch (\Exception $e) { + $this->record($request, 'invalidate-failed'); + + if ($this->options['debug']) { + throw $e; + } + } + } + + return $response; + } + + /** + * Lookups a Response from the cache for the given Request. + * + * When a matching cache entry is found and is fresh, it uses it as the + * response without forwarding any request to the backend. When a matching + * cache entry is found but is stale, it attempts to "validate" the entry with + * the backend using conditional GET. When no matching cache entry is found, + * it triggers "miss" processing. + * + * @param Request $request A Request instance + * @param Boolean $catch whether to process exceptions + * + * @return Response A Response instance + * + * @throws \Exception + */ + protected function lookup(Request $request, $catch = false) + { + // if allow_reload and no-cache Cache-Control, allow a cache reload + if ($this->options['allow_reload'] && $request->isNoCache()) { + $this->record($request, 'reload'); + + return $this->fetch($request); + } + + try { + $entry = $this->store->lookup($request); + } catch (\Exception $e) { + $this->record($request, 'lookup-failed'); + + if ($this->options['debug']) { + throw $e; + } + + return $this->pass($request, $catch); + } + + if (null === $entry) { + $this->record($request, 'miss'); + + return $this->fetch($request, $catch); + } + + if (!$this->isFreshEnough($request, $entry)) { + $this->record($request, 'stale'); + + return $this->validate($request, $entry, $catch); + } + + $this->record($request, 'fresh'); + + $entry->headers->set('Age', $entry->getAge()); + + return $entry; + } + + /** + * Validates that a cache entry is fresh. + * + * The original request is used as a template for a conditional + * GET request with the backend. + * + * @param Request $request A Request instance + * @param Response $entry A Response instance to validate + * @param Boolean $catch Whether to process exceptions + * + * @return Response A Response instance + */ + protected function validate(Request $request, Response $entry, $catch = false) + { + $subRequest = clone $request; + + // send no head requests because we want content + $subRequest->setMethod('GET'); + + // add our cached last-modified validator + $subRequest->headers->set('if_modified_since', $entry->headers->get('Last-Modified')); + + // Add our cached etag validator to the environment. + // We keep the etags from the client to handle the case when the client + // has a different private valid entry which is not cached here. + $cachedEtags = $entry->getEtag() ? array($entry->getEtag()) : array(); + $requestEtags = $request->getEtags(); + if ($etags = array_unique(array_merge($cachedEtags, $requestEtags))) { + $subRequest->headers->set('if_none_match', implode(', ', $etags)); + } + + $response = $this->forward($subRequest, $catch, $entry); + + if (304 == $response->getStatusCode()) { + $this->record($request, 'valid'); + + // return the response and not the cache entry if the response is valid but not cached + $etag = $response->getEtag(); + if ($etag && in_array($etag, $requestEtags) && !in_array($etag, $cachedEtags)) { + return $response; + } + + $entry = clone $entry; + $entry->headers->remove('Date'); + + foreach (array('Date', 'Expires', 'Cache-Control', 'ETag', 'Last-Modified') as $name) { + if ($response->headers->has($name)) { + $entry->headers->set($name, $response->headers->get($name)); + } + } + + $response = $entry; + } else { + $this->record($request, 'invalid'); + } + + if ($response->isCacheable()) { + $this->store($request, $response); + } + + return $response; + } + + /** + * Forwards the Request to the backend and determines whether the response should be stored. + * + * This methods is triggered when the cache missed or a reload is required. + * + * @param Request $request A Request instance + * @param Boolean $catch whether to process exceptions + * + * @return Response A Response instance + */ + protected function fetch(Request $request, $catch = false) + { + $subRequest = clone $request; + + // send no head requests because we want content + $subRequest->setMethod('GET'); + + // avoid that the backend sends no content + $subRequest->headers->remove('if_modified_since'); + $subRequest->headers->remove('if_none_match'); + + $response = $this->forward($subRequest, $catch); + + if ($this->isPrivateRequest($request) && !$response->headers->hasCacheControlDirective('public')) { + $response->setPrivate(true); + } elseif ($this->options['default_ttl'] > 0 && null === $response->getTtl() && !$response->headers->getCacheControlDirective('must-revalidate')) { + $response->setTtl($this->options['default_ttl']); + } + + if ($response->isCacheable()) { + $this->store($request, $response); + } + + return $response; + } + + /** + * Forwards the Request to the backend and returns the Response. + * + * @param Request $request A Request instance + * @param Boolean $catch Whether to catch exceptions or not + * @param Response $entry A Response instance (the stale entry if present, null otherwise) + * + * @return Response A Response instance + */ + protected function forward(Request $request, $catch = false, Response $entry = null) + { + if ($this->esi) { + $this->esi->addSurrogateEsiCapability($request); + } + + // modify the X-Forwarded-For header if needed + $forwardedFor = $request->headers->get('X-Forwarded-For'); + if ($forwardedFor) { + $request->headers->set('X-Forwarded-For', $forwardedFor.', '.$request->server->get('REMOTE_ADDR')); + } else { + $request->headers->set('X-Forwarded-For', $request->server->get('REMOTE_ADDR')); + } + + // fix the client IP address by setting it to 127.0.0.1 as HttpCache + // is always called from the same process as the backend. + $request->server->set('REMOTE_ADDR', '127.0.0.1'); + + // always a "master" request (as the real master request can be in cache) + $response = $this->kernel->handle($request, HttpKernelInterface::MASTER_REQUEST, $catch); + // FIXME: we probably need to also catch exceptions if raw === true + + // we don't implement the stale-if-error on Requests, which is nonetheless part of the RFC + if (null !== $entry && in_array($response->getStatusCode(), array(500, 502, 503, 504))) { + if (null === $age = $entry->headers->getCacheControlDirective('stale-if-error')) { + $age = $this->options['stale_if_error']; + } + + if (abs($entry->getTtl()) < $age) { + $this->record($request, 'stale-if-error'); + + return $entry; + } + } + + $this->processResponseBody($request, $response); + + return $response; + } + + /** + * Checks whether the cache entry is "fresh enough" to satisfy the Request. + * + * @param Request $request A Request instance + * @param Response $entry A Response instance + * + * @return Boolean true if the cache entry if fresh enough, false otherwise + */ + protected function isFreshEnough(Request $request, Response $entry) + { + if (!$entry->isFresh()) { + return $this->lock($request, $entry); + } + + if ($this->options['allow_revalidate'] && null !== $maxAge = $request->headers->getCacheControlDirective('max-age')) { + return $maxAge > 0 && $maxAge >= $entry->getAge(); + } + + return true; + } + + /** + * Locks a Request during the call to the backend. + * + * @param Request $request A Request instance + * @param Response $entry A Response instance + * + * @return Boolean true if the cache entry can be returned even if it is staled, false otherwise + */ + protected function lock(Request $request, Response $entry) + { + // try to acquire a lock to call the backend + $lock = $this->store->lock($request, $entry); + + // there is already another process calling the backend + if (true !== $lock) { + // check if we can serve the stale entry + if (null === $age = $entry->headers->getCacheControlDirective('stale-while-revalidate')) { + $age = $this->options['stale_while_revalidate']; + } + + if (abs($entry->getTtl()) < $age) { + $this->record($request, 'stale-while-revalidate'); + + // server the stale response while there is a revalidation + return true; + } + + // wait for the lock to be released + $wait = 0; + while ($this->store->isLocked($request) && $wait < 5000000) { + usleep(50000); + $wait += 50000; + } + + if ($wait < 2000000) { + // replace the current entry with the fresh one + $new = $this->lookup($request); + $entry->headers = $new->headers; + $entry->setContent($new->getContent()); + $entry->setStatusCode($new->getStatusCode()); + $entry->setProtocolVersion($new->getProtocolVersion()); + foreach ($new->headers->getCookies() as $cookie) { + $entry->headers->setCookie($cookie); + } + } else { + // backend is slow as hell, send a 503 response (to avoid the dog pile effect) + $entry->setStatusCode(503); + $entry->setContent('503 Service Unavailable'); + $entry->headers->set('Retry-After', 10); + } + + return true; + } + + // we have the lock, call the backend + return false; + } + + /** + * Writes the Response to the cache. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * + * @throws \Exception + */ + protected function store(Request $request, Response $response) + { + try { + $this->store->write($request, $response); + + $this->record($request, 'store'); + + $response->headers->set('Age', $response->getAge()); + } catch (\Exception $e) { + $this->record($request, 'store-failed'); + + if ($this->options['debug']) { + throw $e; + } + } + + // now that the response is cached, release the lock + $this->store->unlock($request); + } + + /** + * Restores the Response body. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * + * @return Response A Response instance + */ + private function restoreResponseBody(Request $request, Response $response) + { + if ($request->isMethod('HEAD') || 304 === $response->getStatusCode()) { + $response->setContent(null); + $response->headers->remove('X-Body-Eval'); + $response->headers->remove('X-Body-File'); + + return; + } + + if ($response->headers->has('X-Body-Eval')) { + ob_start(); + + if ($response->headers->has('X-Body-File')) { + include $response->headers->get('X-Body-File'); + } else { + eval('; ?>'.$response->getContent().'setContent(ob_get_clean()); + $response->headers->remove('X-Body-Eval'); + if (!$response->headers->has('Transfer-Encoding')) { + $response->headers->set('Content-Length', strlen($response->getContent())); + } + } elseif ($response->headers->has('X-Body-File')) { + $response->setContent(file_get_contents($response->headers->get('X-Body-File'))); + } else { + return; + } + + $response->headers->remove('X-Body-File'); + } + + protected function processResponseBody(Request $request, Response $response) + { + if (null !== $this->esi && $this->esi->needsEsiParsing($response)) { + $this->esi->process($request, $response); + } + } + + /** + * Checks if the Request includes authorization or other sensitive information + * that should cause the Response to be considered private by default. + * + * @param Request $request A Request instance + * + * @return Boolean true if the Request is private, false otherwise + */ + private function isPrivateRequest(Request $request) + { + foreach ($this->options['private_headers'] as $key) { + $key = strtolower(str_replace('HTTP_', '', $key)); + + if ('cookie' === $key) { + if (count($request->cookies->all())) { + return true; + } + } elseif ($request->headers->has($key)) { + return true; + } + } + + return false; + } + + /** + * Records that an event took place. + * + * @param Request $request A Request instance + * @param string $event The event name + */ + private function record(Request $request, $event) + { + $path = $request->getPathInfo(); + if ($qs = $request->getQueryString()) { + $path .= '?'.$qs; + } + $this->traces[$request->getMethod().' '.$path][] = $event; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/Store.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/Store.php new file mode 100644 index 0000000..a1cda1f --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/Store.php @@ -0,0 +1,429 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Store implements all the logic for storing cache metadata (Request and Response headers). + * + * @author Fabien Potencier + */ +class Store implements StoreInterface +{ + protected $root; + private $keyCache; + private $locks; + + /** + * Constructor. + * + * @param string $root The path to the cache directory + */ + public function __construct($root) + { + $this->root = $root; + if (!is_dir($this->root)) { + mkdir($this->root, 0777, true); + } + $this->keyCache = new \SplObjectStorage(); + $this->locks = array(); + } + + /** + * Cleanups storage. + */ + public function cleanup() + { + // unlock everything + foreach ($this->locks as $lock) { + @unlink($lock); + } + + $error = error_get_last(); + if (1 === $error['type'] && false === headers_sent()) { + // send a 503 + header('HTTP/1.0 503 Service Unavailable'); + header('Retry-After: 10'); + echo '503 Service Unavailable'; + } + } + + /** + * Locks the cache for a given Request. + * + * @param Request $request A Request instance + * + * @return Boolean|string true if the lock is acquired, the path to the current lock otherwise + */ + public function lock(Request $request) + { + $path = $this->getPath($this->getCacheKey($request).'.lck'); + if (!is_dir(dirname($path)) && false === @mkdir(dirname($path), 0777, true)) { + return false; + } + + $lock = @fopen($path, 'x'); + if (false !== $lock) { + fclose($lock); + + $this->locks[] = $path; + + return true; + } + + return !file_exists($path) ?: $path; + } + + /** + * Releases the lock for the given Request. + * + * @param Request $request A Request instance + * + * @return Boolean False if the lock file does not exist or cannot be unlocked, true otherwise + */ + public function unlock(Request $request) + { + $file = $this->getPath($this->getCacheKey($request).'.lck'); + + return is_file($file) ? @unlink($file) : false; + } + + public function isLocked(Request $request) + { + return is_file($this->getPath($this->getCacheKey($request).'.lck')); + } + + /** + * Locates a cached Response for the Request provided. + * + * @param Request $request A Request instance + * + * @return Response|null A Response instance, or null if no cache entry was found + */ + public function lookup(Request $request) + { + $key = $this->getCacheKey($request); + + if (!$entries = $this->getMetadata($key)) { + return null; + } + + // find a cached entry that matches the request. + $match = null; + foreach ($entries as $entry) { + if ($this->requestsMatch(isset($entry[1]['vary'][0]) ? $entry[1]['vary'][0] : '', $request->headers->all(), $entry[0])) { + $match = $entry; + + break; + } + } + + if (null === $match) { + return null; + } + + list($req, $headers) = $match; + if (is_file($body = $this->getPath($headers['x-content-digest'][0]))) { + return $this->restoreResponse($headers, $body); + } + + // TODO the metaStore referenced an entity that doesn't exist in + // the entityStore. We definitely want to return nil but we should + // also purge the entry from the meta-store when this is detected. + return null; + } + + /** + * Writes a cache entry to the store for the given Request and Response. + * + * Existing entries are read and any that match the response are removed. This + * method calls write with the new list of cache entries. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * + * @return string The key under which the response is stored + * + * @throws \RuntimeException + */ + public function write(Request $request, Response $response) + { + $key = $this->getCacheKey($request); + $storedEnv = $this->persistRequest($request); + + // write the response body to the entity store if this is the original response + if (!$response->headers->has('X-Content-Digest')) { + $digest = $this->generateContentDigest($response); + + if (false === $this->save($digest, $response->getContent())) { + throw new \RuntimeException('Unable to store the entity.'); + } + + $response->headers->set('X-Content-Digest', $digest); + + if (!$response->headers->has('Transfer-Encoding')) { + $response->headers->set('Content-Length', strlen($response->getContent())); + } + } + + // read existing cache entries, remove non-varying, and add this one to the list + $entries = array(); + $vary = $response->headers->get('vary'); + foreach ($this->getMetadata($key) as $entry) { + if (!isset($entry[1]['vary'][0])) { + $entry[1]['vary'] = array(''); + } + + if ($vary != $entry[1]['vary'][0] || !$this->requestsMatch($vary, $entry[0], $storedEnv)) { + $entries[] = $entry; + } + } + + $headers = $this->persistResponse($response); + unset($headers['age']); + + array_unshift($entries, array($storedEnv, $headers)); + + if (false === $this->save($key, serialize($entries))) { + throw new \RuntimeException('Unable to store the metadata.'); + } + + return $key; + } + + /** + * Returns content digest for $response. + * + * @param Response $response + * + * @return string + */ + protected function generateContentDigest(Response $response) + { + return 'en'.sha1($response->getContent()); + } + + /** + * Invalidates all cache entries that match the request. + * + * @param Request $request A Request instance + * + * @throws \RuntimeException + */ + public function invalidate(Request $request) + { + $modified = false; + $key = $this->getCacheKey($request); + + $entries = array(); + foreach ($this->getMetadata($key) as $entry) { + $response = $this->restoreResponse($entry[1]); + if ($response->isFresh()) { + $response->expire(); + $modified = true; + $entries[] = array($entry[0], $this->persistResponse($response)); + } else { + $entries[] = $entry; + } + } + + if ($modified) { + if (false === $this->save($key, serialize($entries))) { + throw new \RuntimeException('Unable to store the metadata.'); + } + } + } + + /** + * Determines whether two Request HTTP header sets are non-varying based on + * the vary response header value provided. + * + * @param string $vary A Response vary header + * @param array $env1 A Request HTTP header array + * @param array $env2 A Request HTTP header array + * + * @return Boolean true if the two environments match, false otherwise + */ + private function requestsMatch($vary, $env1, $env2) + { + if (empty($vary)) { + return true; + } + + foreach (preg_split('/[\s,]+/', $vary) as $header) { + $key = strtr(strtolower($header), '_', '-'); + $v1 = isset($env1[$key]) ? $env1[$key] : null; + $v2 = isset($env2[$key]) ? $env2[$key] : null; + if ($v1 !== $v2) { + return false; + } + } + + return true; + } + + /** + * Gets all data associated with the given key. + * + * Use this method only if you know what you are doing. + * + * @param string $key The store key + * + * @return array An array of data associated with the key + */ + private function getMetadata($key) + { + if (false === $entries = $this->load($key)) { + return array(); + } + + return unserialize($entries); + } + + /** + * Purges data for the given URL. + * + * @param string $url A URL + * + * @return Boolean true if the URL exists and has been purged, false otherwise + */ + public function purge($url) + { + if (is_file($path = $this->getPath($this->getCacheKey(Request::create($url))))) { + unlink($path); + + return true; + } + + return false; + } + + /** + * Loads data for the given key. + * + * @param string $key The store key + * + * @return string The data associated with the key + */ + private function load($key) + { + $path = $this->getPath($key); + + return is_file($path) ? file_get_contents($path) : false; + } + + /** + * Save data for the given key. + * + * @param string $key The store key + * @param string $data The data to store + * + * @return Boolean + */ + private function save($key, $data) + { + $path = $this->getPath($key); + if (!is_dir(dirname($path)) && false === @mkdir(dirname($path), 0777, true)) { + return false; + } + + $tmpFile = tempnam(dirname($path), basename($path)); + if (false === $fp = @fopen($tmpFile, 'wb')) { + return false; + } + @fwrite($fp, $data); + @fclose($fp); + + if ($data != file_get_contents($tmpFile)) { + return false; + } + + if (false === @rename($tmpFile, $path)) { + return false; + } + + @chmod($path, 0666 & ~umask()); + } + + public function getPath($key) + { + return $this->root.DIRECTORY_SEPARATOR.substr($key, 0, 2).DIRECTORY_SEPARATOR.substr($key, 2, 2).DIRECTORY_SEPARATOR.substr($key, 4, 2).DIRECTORY_SEPARATOR.substr($key, 6); + } + + /** + * Returns a cache key for the given Request. + * + * @param Request $request A Request instance + * + * @return string A key for the given Request + */ + private function getCacheKey(Request $request) + { + if (isset($this->keyCache[$request])) { + return $this->keyCache[$request]; + } + + return $this->keyCache[$request] = 'md'.sha1($request->getUri()); + } + + /** + * Persists the Request HTTP headers. + * + * @param Request $request A Request instance + * + * @return array An array of HTTP headers + */ + private function persistRequest(Request $request) + { + return $request->headers->all(); + } + + /** + * Persists the Response HTTP headers. + * + * @param Response $response A Response instance + * + * @return array An array of HTTP headers + */ + private function persistResponse(Response $response) + { + $headers = $response->headers->all(); + $headers['X-Status'] = array($response->getStatusCode()); + + return $headers; + } + + /** + * Restores a Response from the HTTP headers and body. + * + * @param array $headers An array of HTTP headers for the Response + * @param string $body The Response body + * + * @return Response + */ + private function restoreResponse($headers, $body = null) + { + $status = $headers['X-Status'][0]; + unset($headers['X-Status']); + + if (null !== $body) { + $headers['X-Body-File'] = array($body); + } + + return new Response($body, $status, $headers); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/StoreInterface.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/StoreInterface.php new file mode 100644 index 0000000..29a54d8 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpCache/StoreInterface.php @@ -0,0 +1,96 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Interface implemented by HTTP cache stores. + * + * @author Fabien Potencier + */ +interface StoreInterface +{ + /** + * Locates a cached Response for the Request provided. + * + * @param Request $request A Request instance + * + * @return Response|null A Response instance, or null if no cache entry was found + */ + public function lookup(Request $request); + + /** + * Writes a cache entry to the store for the given Request and Response. + * + * Existing entries are read and any that match the response are removed. This + * method calls write with the new list of cache entries. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * + * @return string The key under which the response is stored + */ + public function write(Request $request, Response $response); + + /** + * Invalidates all cache entries that match the request. + * + * @param Request $request A Request instance + */ + public function invalidate(Request $request); + + /** + * Locks the cache for a given Request. + * + * @param Request $request A Request instance + * + * @return Boolean|string true if the lock is acquired, the path to the current lock otherwise + */ + public function lock(Request $request); + + /** + * Releases the lock for the given Request. + * + * @param Request $request A Request instance + * + * @return Boolean False if the lock file does not exist or cannot be unlocked, true otherwise + */ + public function unlock(Request $request); + + /** + * Returns whether or not a lock exists. + * + * @param Request $request A Request instance + * + * @return Boolean true if lock exists, false otherwise + */ + public function isLocked(Request $request); + + /** + * Purges data for the given URL. + * + * @param string $url A URL + * + * @return Boolean true if the URL exists and has been purged, false otherwise + */ + public function purge($url); + + /** + * Cleanups storage. + */ + public function cleanup(); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpKernel.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpKernel.php new file mode 100644 index 0000000..837a16f --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpKernel.php @@ -0,0 +1,243 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\PostResponseEvent; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * HttpKernel notifies events to convert a Request object to a Response one. + * + * @author Fabien Potencier + * + * @api + */ +class HttpKernel implements HttpKernelInterface, TerminableInterface +{ + protected $dispatcher; + protected $resolver; + + /** + * Constructor + * + * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance + * @param ControllerResolverInterface $resolver A ControllerResolverInterface instance + * + * @api + */ + public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver) + { + $this->dispatcher = $dispatcher; + $this->resolver = $resolver; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + try { + return $this->handleRaw($request, $type); + } catch (\Exception $e) { + if (false === $catch) { + throw $e; + } + + return $this->handleException($e, $request, $type); + } + } + + /** + * {@inheritdoc} + * + * @api + */ + public function terminate(Request $request, Response $response) + { + $this->dispatcher->dispatch(KernelEvents::TERMINATE, new PostResponseEvent($this, $request, $response)); + } + + /** + * Handles a request to convert it to a response. + * + * Exceptions are not caught. + * + * @param Request $request A Request instance + * @param integer $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST) + * + * @return Response A Response instance + * + * @throws \LogicException If one of the listener does not behave as expected + * @throws NotFoundHttpException When controller cannot be found + */ + private function handleRaw(Request $request, $type = self::MASTER_REQUEST) + { + // request + $event = new GetResponseEvent($this, $request, $type); + $this->dispatcher->dispatch(KernelEvents::REQUEST, $event); + + if ($event->hasResponse()) { + return $this->filterResponse($event->getResponse(), $request, $type); + } + + // load controller + if (false === $controller = $this->resolver->getController($request)) { + throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". Maybe you forgot to add the matching route in your routing configuration?', $request->getPathInfo())); + } + + $event = new FilterControllerEvent($this, $controller, $request, $type); + $this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event); + $controller = $event->getController(); + + // controller arguments + $arguments = $this->resolver->getArguments($request, $controller); + + // call controller + $response = call_user_func_array($controller, $arguments); + + // view + if (!$response instanceof Response) { + $event = new GetResponseForControllerResultEvent($this, $request, $type, $response); + $this->dispatcher->dispatch(KernelEvents::VIEW, $event); + + if ($event->hasResponse()) { + $response = $event->getResponse(); + } + + if (!$response instanceof Response) { + $msg = sprintf('The controller must return a response (%s given).', $this->varToString($response)); + + // the user may have forgotten to return something + if (null === $response) { + $msg .= ' Did you forget to add a return statement somewhere in your controller?'; + } + throw new \LogicException($msg); + } + } + + return $this->filterResponse($response, $request, $type); + } + + /** + * Filters a response object. + * + * @param Response $response A Response instance + * @param Request $request An error message in case the response is not a Response object + * @param integer $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST) + * + * @return Response The filtered Response instance + * + * @throws \RuntimeException if the passed object is not a Response instance + */ + private function filterResponse(Response $response, Request $request, $type) + { + $event = new FilterResponseEvent($this, $request, $type, $response); + + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + return $event->getResponse(); + } + + /** + * Handles an exception by trying to convert it to a Response. + * + * @param \Exception $e An \Exception instance + * @param Request $request A Request instance + * @param integer $type The type of the request + * + * @return Response A Response instance + * + * @throws \Exception + */ + private function handleException(\Exception $e, $request, $type) + { + $event = new GetResponseForExceptionEvent($this, $request, $type, $e); + $this->dispatcher->dispatch(KernelEvents::EXCEPTION, $event); + + // a listener might have replaced the exception + $e = $event->getException(); + + if (!$event->hasResponse()) { + throw $e; + } + + $response = $event->getResponse(); + + // the developer asked for a specific status code + if ($response->headers->has('X-Status-Code')) { + $response->setStatusCode($response->headers->get('X-Status-Code')); + + $response->headers->remove('X-Status-Code'); + } elseif (!$response->isClientError() && !$response->isServerError() && !$response->isRedirect()) { + // ensure that we actually have an error response + if ($e instanceof HttpExceptionInterface) { + // keep the HTTP status code and headers + $response->setStatusCode($e->getStatusCode()); + $response->headers->add($e->getHeaders()); + } else { + $response->setStatusCode(500); + } + } + + try { + return $this->filterResponse($response, $request, $type); + } catch (\Exception $e) { + return $response; + } + } + + private function varToString($var) + { + if (is_object($var)) { + return sprintf('Object(%s)', get_class($var)); + } + + if (is_array($var)) { + $a = array(); + foreach ($var as $k => $v) { + $a[] = sprintf('%s => %s', $k, $this->varToString($v)); + } + + return sprintf("Array(%s)", implode(', ', $a)); + } + + if (is_resource($var)) { + return sprintf('Resource(%s)', get_resource_type($var)); + } + + if (null === $var) { + return 'null'; + } + + if (false === $var) { + return 'false'; + } + + if (true === $var) { + return 'true'; + } + + return (string) $var; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpKernelInterface.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpKernelInterface.php new file mode 100644 index 0000000..f49d37c --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpKernelInterface.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * HttpKernelInterface handles a Request to convert it to a Response. + * + * @author Fabien Potencier + * + * @api + */ +interface HttpKernelInterface +{ + const MASTER_REQUEST = 1; + const SUB_REQUEST = 2; + + /** + * Handles a Request to convert it to a Response. + * + * When $catch is true, the implementation must catch all exceptions + * and do its best to convert them to a Response instance. + * + * @param Request $request A Request instance + * @param integer $type The type of the request + * (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST) + * @param Boolean $catch Whether to catch exceptions or not + * + * @return Response A Response instance + * + * @throws \Exception When an Exception occurs during processing + * + * @api + */ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Kernel.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Kernel.php new file mode 100644 index 0000000..2ac450f --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Kernel.php @@ -0,0 +1,793 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator; +use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Dumper\PhpDumper; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +use Symfony\Component\DependencyInjection\Loader\IniFileLoader; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Symfony\Component\DependencyInjection\Loader\ClosureLoader; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; +use Symfony\Component\HttpKernel\Config\FileLocator; +use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; +use Symfony\Component\HttpKernel\DependencyInjection\AddClassesToCachePass; +use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\Config\Loader\DelegatingLoader; +use Symfony\Component\Config\ConfigCache; +use Symfony\Component\ClassLoader\ClassCollectionLoader; + +/** + * The Kernel is the heart of the Symfony system. + * + * It manages an environment made of bundles. + * + * @author Fabien Potencier + * + * @api + */ +abstract class Kernel implements KernelInterface, TerminableInterface +{ + /** + * @var BundleInterface[] + */ + protected $bundles; + + protected $bundleMap; + protected $container; + protected $rootDir; + protected $environment; + protected $debug; + protected $booted; + protected $name; + protected $startTime; + protected $loadClassCache; + + const VERSION = '2.3.5-DEV'; + const VERSION_ID = '20305'; + const MAJOR_VERSION = '2'; + const MINOR_VERSION = '3'; + const RELEASE_VERSION = '5'; + const EXTRA_VERSION = 'DEV'; + + /** + * Constructor. + * + * @param string $environment The environment + * @param Boolean $debug Whether to enable debugging or not + * + * @api + */ + public function __construct($environment, $debug) + { + $this->environment = $environment; + $this->debug = (Boolean) $debug; + $this->booted = false; + $this->rootDir = $this->getRootDir(); + $this->name = $this->getName(); + $this->bundles = array(); + + if ($this->debug) { + $this->startTime = microtime(true); + } + + $this->init(); + } + + /** + * @deprecated Deprecated since version 2.3, to be removed in 3.0. Move your logic in the constructor instead. + */ + public function init() + { + } + + public function __clone() + { + if ($this->debug) { + $this->startTime = microtime(true); + } + + $this->booted = false; + $this->container = null; + } + + /** + * Boots the current kernel. + * + * @api + */ + public function boot() + { + if (true === $this->booted) { + return; + } + + if ($this->loadClassCache) { + $this->doLoadClassCache($this->loadClassCache[0], $this->loadClassCache[1]); + } + + // init bundles + $this->initializeBundles(); + + // init container + $this->initializeContainer(); + + foreach ($this->getBundles() as $bundle) { + $bundle->setContainer($this->container); + $bundle->boot(); + } + + $this->booted = true; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function terminate(Request $request, Response $response) + { + if (false === $this->booted) { + return; + } + + if ($this->getHttpKernel() instanceof TerminableInterface) { + $this->getHttpKernel()->terminate($request, $response); + } + } + + /** + * {@inheritdoc} + * + * @api + */ + public function shutdown() + { + if (false === $this->booted) { + return; + } + + $this->booted = false; + + foreach ($this->getBundles() as $bundle) { + $bundle->shutdown(); + $bundle->setContainer(null); + } + + $this->container = null; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + if (false === $this->booted) { + $this->boot(); + } + + return $this->getHttpKernel()->handle($request, $type, $catch); + } + + /** + * Gets a http kernel from the container + * + * @return HttpKernel + */ + protected function getHttpKernel() + { + return $this->container->get('http_kernel'); + } + + /** + * {@inheritdoc} + * + * @api + */ + public function getBundles() + { + return $this->bundles; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function isClassInActiveBundle($class) + { + foreach ($this->getBundles() as $bundle) { + if (0 === strpos($class, $bundle->getNamespace())) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function getBundle($name, $first = true) + { + if (!isset($this->bundleMap[$name])) { + throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the registerBundles() method of your %s.php file?', $name, get_class($this))); + } + + if (true === $first) { + return $this->bundleMap[$name][0]; + } + + return $this->bundleMap[$name]; + } + + /** + * Returns the file path for a given resource. + * + * A Resource can be a file or a directory. + * + * The resource name must follow the following pattern: + * + * @/path/to/a/file.something + * + * where BundleName is the name of the bundle + * and the remaining part is the relative path in the bundle. + * + * If $dir is passed, and the first segment of the path is "Resources", + * this method will look for a file named: + * + * $dir//path/without/Resources + * + * before looking in the bundle resource folder. + * + * @param string $name A resource name to locate + * @param string $dir A directory where to look for the resource first + * @param Boolean $first Whether to return the first path or paths for all matching bundles + * + * @return string|array The absolute path of the resource or an array if $first is false + * + * @throws \InvalidArgumentException if the file cannot be found or the name is not valid + * @throws \RuntimeException if the name contains invalid/unsafe + * @throws \RuntimeException if a custom resource is hidden by a resource in a derived bundle + * + * @api + */ + public function locateResource($name, $dir = null, $first = true) + { + if ('@' !== $name[0]) { + throw new \InvalidArgumentException(sprintf('A resource name must start with @ ("%s" given).', $name)); + } + + if (false !== strpos($name, '..')) { + throw new \RuntimeException(sprintf('File name "%s" contains invalid characters (..).', $name)); + } + + $bundleName = substr($name, 1); + $path = ''; + if (false !== strpos($bundleName, '/')) { + list($bundleName, $path) = explode('/', $bundleName, 2); + } + + $isResource = 0 === strpos($path, 'Resources') && null !== $dir; + $overridePath = substr($path, 9); + $resourceBundle = null; + $bundles = $this->getBundle($bundleName, false); + $files = array(); + + foreach ($bundles as $bundle) { + if ($isResource && file_exists($file = $dir.'/'.$bundle->getName().$overridePath)) { + if (null !== $resourceBundle) { + throw new \RuntimeException(sprintf('"%s" resource is hidden by a resource from the "%s" derived bundle. Create a "%s" file to override the bundle resource.', + $file, + $resourceBundle, + $dir.'/'.$bundles[0]->getName().$overridePath + )); + } + + if ($first) { + return $file; + } + $files[] = $file; + } + + if (file_exists($file = $bundle->getPath().'/'.$path)) { + if ($first && !$isResource) { + return $file; + } + $files[] = $file; + $resourceBundle = $bundle->getName(); + } + } + + if (count($files) > 0) { + return $first && $isResource ? $files[0] : $files; + } + + throw new \InvalidArgumentException(sprintf('Unable to find file "%s".', $name)); + } + + /** + * {@inheritdoc} + * + * @api + */ + public function getName() + { + if (null === $this->name) { + $this->name = preg_replace('/[^a-zA-Z0-9_]+/', '', basename($this->rootDir)); + } + + return $this->name; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function getEnvironment() + { + return $this->environment; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function isDebug() + { + return $this->debug; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function getRootDir() + { + if (null === $this->rootDir) { + $r = new \ReflectionObject($this); + $this->rootDir = str_replace('\\', '/', dirname($r->getFileName())); + } + + return $this->rootDir; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function getContainer() + { + return $this->container; + } + + /** + * Loads the PHP class cache. + * + * This methods only registers the fact that you want to load the cache classes. + * The cache will actually only be loaded when the Kernel is booted. + * + * That optimization is mainly useful when using the HttpCache class in which + * case the class cache is not loaded if the Response is in the cache. + * + * @param string $name The cache name prefix + * @param string $extension File extension of the resulting file + */ + public function loadClassCache($name = 'classes', $extension = '.php') + { + $this->loadClassCache = array($name, $extension); + } + + /** + * Used internally. + */ + public function setClassCache(array $classes) + { + file_put_contents($this->getCacheDir().'/classes.map', sprintf('debug ? $this->startTime : -INF; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function getCacheDir() + { + return $this->rootDir.'/cache/'.$this->environment; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function getLogDir() + { + return $this->rootDir.'/logs'; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function getCharset() + { + return 'UTF-8'; + } + + protected function doLoadClassCache($name, $extension) + { + if (!$this->booted && is_file($this->getCacheDir().'/classes.map')) { + ClassCollectionLoader::load(include($this->getCacheDir().'/classes.map'), $this->getCacheDir(), $name, $this->debug, false, $extension); + } + } + + /** + * Initializes the data structures related to the bundle management. + * + * - the bundles property maps a bundle name to the bundle instance, + * - the bundleMap property maps a bundle name to the bundle inheritance hierarchy (most derived bundle first). + * + * @throws \LogicException if two bundles share a common name + * @throws \LogicException if a bundle tries to extend a non-registered bundle + * @throws \LogicException if a bundle tries to extend itself + * @throws \LogicException if two bundles extend the same ancestor + */ + protected function initializeBundles() + { + // init bundles + $this->bundles = array(); + $topMostBundles = array(); + $directChildren = array(); + + foreach ($this->registerBundles() as $bundle) { + $name = $bundle->getName(); + if (isset($this->bundles[$name])) { + throw new \LogicException(sprintf('Trying to register two bundles with the same name "%s"', $name)); + } + $this->bundles[$name] = $bundle; + + if ($parentName = $bundle->getParent()) { + if (isset($directChildren[$parentName])) { + throw new \LogicException(sprintf('Bundle "%s" is directly extended by two bundles "%s" and "%s".', $parentName, $name, $directChildren[$parentName])); + } + if ($parentName == $name) { + throw new \LogicException(sprintf('Bundle "%s" can not extend itself.', $name)); + } + $directChildren[$parentName] = $name; + } else { + $topMostBundles[$name] = $bundle; + } + } + + // look for orphans + if (count($diff = array_values(array_diff(array_keys($directChildren), array_keys($this->bundles))))) { + throw new \LogicException(sprintf('Bundle "%s" extends bundle "%s", which is not registered.', $directChildren[$diff[0]], $diff[0])); + } + + // inheritance + $this->bundleMap = array(); + foreach ($topMostBundles as $name => $bundle) { + $bundleMap = array($bundle); + $hierarchy = array($name); + + while (isset($directChildren[$name])) { + $name = $directChildren[$name]; + array_unshift($bundleMap, $this->bundles[$name]); + $hierarchy[] = $name; + } + + foreach ($hierarchy as $bundle) { + $this->bundleMap[$bundle] = $bundleMap; + array_pop($bundleMap); + } + } + + } + + /** + * Gets the container class. + * + * @return string The container class + */ + protected function getContainerClass() + { + return $this->name.ucfirst($this->environment).($this->debug ? 'Debug' : '').'ProjectContainer'; + } + + /** + * Gets the container's base class. + * + * All names except Container must be fully qualified. + * + * @return string + */ + protected function getContainerBaseClass() + { + return 'Container'; + } + + /** + * Initializes the service container. + * + * The cached version of the service container is used when fresh, otherwise the + * container is built. + */ + protected function initializeContainer() + { + $class = $this->getContainerClass(); + $cache = new ConfigCache($this->getCacheDir().'/'.$class.'.php', $this->debug); + $fresh = true; + if (!$cache->isFresh()) { + $container = $this->buildContainer(); + $container->compile(); + $this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass()); + + $fresh = false; + } + + require_once $cache; + + $this->container = new $class(); + $this->container->set('kernel', $this); + + if (!$fresh && $this->container->has('cache_warmer')) { + $this->container->get('cache_warmer')->warmUp($this->container->getParameter('kernel.cache_dir')); + } + } + + /** + * Returns the kernel parameters. + * + * @return array An array of kernel parameters + */ + protected function getKernelParameters() + { + $bundles = array(); + foreach ($this->bundles as $name => $bundle) { + $bundles[$name] = get_class($bundle); + } + + return array_merge( + array( + 'kernel.root_dir' => $this->rootDir, + 'kernel.environment' => $this->environment, + 'kernel.debug' => $this->debug, + 'kernel.name' => $this->name, + 'kernel.cache_dir' => $this->getCacheDir(), + 'kernel.logs_dir' => $this->getLogDir(), + 'kernel.bundles' => $bundles, + 'kernel.charset' => $this->getCharset(), + 'kernel.container_class' => $this->getContainerClass(), + ), + $this->getEnvParameters() + ); + } + + /** + * Gets the environment parameters. + * + * Only the parameters starting with "SYMFONY__" are considered. + * + * @return array An array of parameters + */ + protected function getEnvParameters() + { + $parameters = array(); + foreach ($_SERVER as $key => $value) { + if (0 === strpos($key, 'SYMFONY__')) { + $parameters[strtolower(str_replace('__', '.', substr($key, 9)))] = $value; + } + } + + return $parameters; + } + + /** + * Builds the service container. + * + * @return ContainerBuilder The compiled service container + * + * @throws \RuntimeException + */ + protected function buildContainer() + { + foreach (array('cache' => $this->getCacheDir(), 'logs' => $this->getLogDir()) as $name => $dir) { + if (!is_dir($dir)) { + if (false === @mkdir($dir, 0777, true)) { + throw new \RuntimeException(sprintf("Unable to create the %s directory (%s)\n", $name, $dir)); + } + } elseif (!is_writable($dir)) { + throw new \RuntimeException(sprintf("Unable to write in the %s directory (%s)\n", $name, $dir)); + } + } + + $container = $this->getContainerBuilder(); + $container->addObjectResource($this); + $this->prepareContainer($container); + + if (null !== $cont = $this->registerContainerConfiguration($this->getContainerLoader($container))) { + $container->merge($cont); + } + + $container->addCompilerPass(new AddClassesToCachePass($this)); + + return $container; + } + + /** + * Prepares the ContainerBuilder before it is compiled. + * + * @param ContainerBuilder $container A ContainerBuilder instance + */ + protected function prepareContainer(ContainerBuilder $container) + { + $extensions = array(); + foreach ($this->bundles as $bundle) { + if ($extension = $bundle->getContainerExtension()) { + $container->registerExtension($extension); + $extensions[] = $extension->getAlias(); + } + + if ($this->debug) { + $container->addObjectResource($bundle); + } + } + foreach ($this->bundles as $bundle) { + $bundle->build($container); + } + + // ensure these extensions are implicitly loaded + $container->getCompilerPassConfig()->setMergePass(new MergeExtensionConfigurationPass($extensions)); + } + + /** + * Gets a new ContainerBuilder instance used to build the service container. + * + * @return ContainerBuilder + */ + protected function getContainerBuilder() + { + $container = new ContainerBuilder(new ParameterBag($this->getKernelParameters())); + + if (class_exists('ProxyManager\Configuration')) { + $container->setProxyInstantiator(new RuntimeInstantiator()); + } + + return $container; + } + + /** + * Dumps the service container to PHP code in the cache. + * + * @param ConfigCache $cache The config cache + * @param ContainerBuilder $container The service container + * @param string $class The name of the class to generate + * @param string $baseClass The name of the container's base class + */ + protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container, $class, $baseClass) + { + // cache the container + $dumper = new PhpDumper($container); + + if (class_exists('ProxyManager\Configuration')) { + $dumper->setProxyDumper(new ProxyDumper()); + } + + $content = $dumper->dump(array('class' => $class, 'base_class' => $baseClass)); + if (!$this->debug) { + $content = self::stripComments($content); + } + + $cache->write($content, $container->getResources()); + } + + /** + * Returns a loader for the container. + * + * @param ContainerInterface $container The service container + * + * @return DelegatingLoader The loader + */ + protected function getContainerLoader(ContainerInterface $container) + { + $locator = new FileLocator($this); + $resolver = new LoaderResolver(array( + new XmlFileLoader($container, $locator), + new YamlFileLoader($container, $locator), + new IniFileLoader($container, $locator), + new PhpFileLoader($container, $locator), + new ClosureLoader($container), + )); + + return new DelegatingLoader($resolver); + } + + /** + * Removes comments from a PHP source string. + * + * We don't use the PHP php_strip_whitespace() function + * as we want the content to be readable and well-formatted. + * + * @param string $source A PHP string + * + * @return string The PHP string with the comments removed + */ + public static function stripComments($source) + { + if (!function_exists('token_get_all')) { + return $source; + } + + $rawChunk = ''; + $output = ''; + $tokens = token_get_all($source); + for (reset($tokens); false !== $token = current($tokens); next($tokens)) { + if (is_string($token)) { + $rawChunk .= $token; + } elseif (T_START_HEREDOC === $token[0]) { + $output .= preg_replace(array('/\s+$/Sm', '/\n+/S'), "\n", $rawChunk).$token[1]; + do { + $token = next($tokens); + $output .= $token[1]; + } while ($token[0] !== T_END_HEREDOC); + $rawChunk = ''; + } elseif (!in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) { + $rawChunk .= $token[1]; + } + } + + // replace multiple new lines with a single newline + $output .= preg_replace(array('/\s+$/Sm', '/\n+/S'), "\n", $rawChunk); + + return $output; + } + + public function serialize() + { + return serialize(array($this->environment, $this->debug)); + } + + public function unserialize($data) + { + list($environment, $debug) = unserialize($data); + + $this->__construct($environment, $debug); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/KernelEvents.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/KernelEvents.php new file mode 100644 index 0000000..fce48ac --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/KernelEvents.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +/** + * Contains all events thrown in the HttpKernel component + * + * @author Bernhard Schussek + * + * @api + */ +final class KernelEvents +{ + /** + * The REQUEST event occurs at the very beginning of request + * dispatching + * + * This event allows you to create a response for a request before any + * other code in the framework is executed. The event listener method + * receives a Symfony\Component\HttpKernel\Event\GetResponseEvent + * instance. + * + * @var string + * + * @api + */ + const REQUEST = 'kernel.request'; + + /** + * The EXCEPTION event occurs when an uncaught exception appears + * + * This event allows you to create a response for a thrown exception or + * to modify the thrown exception. The event listener method receives + * a Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent + * instance. + * + * @var string + * + * @api + */ + const EXCEPTION = 'kernel.exception'; + + /** + * The VIEW event occurs when the return value of a controller + * is not a Response instance + * + * This event allows you to create a response for the return value of the + * controller. The event listener method receives a + * Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent + * instance. + * + * @var string + * + * @api + */ + const VIEW = 'kernel.view'; + + /** + * The CONTROLLER event occurs once a controller was found for + * handling a request + * + * This event allows you to change the controller that will handle the + * request. The event listener method receives a + * Symfony\Component\HttpKernel\Event\FilterControllerEvent instance. + * + * @var string + * + * @api + */ + const CONTROLLER = 'kernel.controller'; + + /** + * The RESPONSE event occurs once a response was created for + * replying to a request + * + * This event allows you to modify or replace the response that will be + * replied. The event listener method receives a + * Symfony\Component\HttpKernel\Event\FilterResponseEvent instance. + * + * @var string + * + * @api + */ + const RESPONSE = 'kernel.response'; + + /** + * The TERMINATE event occurs once a response was sent + * + * This event allows you to run expensive post-response jobs. + * The event listener method receives a + * Symfony\Component\HttpKernel\Event\PostResponseEvent instance. + * + * @var string + */ + const TERMINATE = 'kernel.terminate'; +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/KernelInterface.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/KernelInterface.php new file mode 100644 index 0000000..dd37b60 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/KernelInterface.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; +use Symfony\Component\Config\Loader\LoaderInterface; + +/** + * The Kernel is the heart of the Symfony system. + * + * It manages an environment made of bundles. + * + * @author Fabien Potencier + * + * @api + */ +interface KernelInterface extends HttpKernelInterface, \Serializable +{ + /** + * Returns an array of bundles to registers. + * + * @return BundleInterface[] An array of bundle instances. + * + * @api + */ + public function registerBundles(); + + /** + * Loads the container configuration + * + * @param LoaderInterface $loader A LoaderInterface instance + * + * @api + */ + public function registerContainerConfiguration(LoaderInterface $loader); + + /** + * Boots the current kernel. + * + * @api + */ + public function boot(); + + /** + * Shutdowns the kernel. + * + * This method is mainly useful when doing functional testing. + * + * @api + */ + public function shutdown(); + + /** + * Gets the registered bundle instances. + * + * @return BundleInterface[] An array of registered bundle instances + * + * @api + */ + public function getBundles(); + + /** + * Checks if a given class name belongs to an active bundle. + * + * @param string $class A class name + * + * @return Boolean true if the class belongs to an active bundle, false otherwise + * + * @api + */ + public function isClassInActiveBundle($class); + + /** + * Returns a bundle and optionally its descendants by its name. + * + * @param string $name Bundle name + * @param Boolean $first Whether to return the first bundle only or together with its descendants + * + * @return BundleInterface|BundleInterface[] A BundleInterface instance or an array of BundleInterface instances if $first is false + * + * @throws \InvalidArgumentException when the bundle is not enabled + * + * @api + */ + public function getBundle($name, $first = true); + + /** + * Returns the file path for a given resource. + * + * A Resource can be a file or a directory. + * + * The resource name must follow the following pattern: + * + * @BundleName/path/to/a/file.something + * + * where BundleName is the name of the bundle + * and the remaining part is the relative path in the bundle. + * + * If $dir is passed, and the first segment of the path is Resources, + * this method will look for a file named: + * + * $dir/BundleName/path/without/Resources + * + * @param string $name A resource name to locate + * @param string $dir A directory where to look for the resource first + * @param Boolean $first Whether to return the first path or paths for all matching bundles + * + * @return string|array The absolute path of the resource or an array if $first is false + * + * @throws \InvalidArgumentException if the file cannot be found or the name is not valid + * @throws \RuntimeException if the name contains invalid/unsafe characters + * + * @api + */ + public function locateResource($name, $dir = null, $first = true); + + /** + * Gets the name of the kernel + * + * @return string The kernel name + * + * @api + */ + public function getName(); + + /** + * Gets the environment. + * + * @return string The current environment + * + * @api + */ + public function getEnvironment(); + + /** + * Checks if debug mode is enabled. + * + * @return Boolean true if debug mode is enabled, false otherwise + * + * @api + */ + public function isDebug(); + + /** + * Gets the application root dir. + * + * @return string The application root dir + * + * @api + */ + public function getRootDir(); + + /** + * Gets the current container. + * + * @return ContainerInterface A ContainerInterface instance + * + * @api + */ + public function getContainer(); + + /** + * Gets the request start time (not available if debug is disabled). + * + * @return integer The request start timestamp + * + * @api + */ + public function getStartTime(); + + /** + * Gets the cache directory. + * + * @return string The cache directory + * + * @api + */ + public function getCacheDir(); + + /** + * Gets the log directory. + * + * @return string The log directory + * + * @api + */ + public function getLogDir(); + + /** + * Gets the charset of the application. + * + * @return string The charset + * + * @api + */ + public function getCharset(); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/LICENSE b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/LICENSE new file mode 100644 index 0000000..88a57f8 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2013 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Log/DebugLoggerInterface.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Log/DebugLoggerInterface.php new file mode 100644 index 0000000..4442c63 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Log/DebugLoggerInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Log; + +/** + * DebugLoggerInterface. + * + * @author Fabien Potencier + */ +interface DebugLoggerInterface +{ + /** + * Returns an array of logs. + * + * A log is an array with the following mandatory keys: + * timestamp, message, priority, and priorityName. + * It can also have an optional context key containing an array. + * + * @return array An array of logs + */ + public function getLogs(); + + /** + * Returns the number of errors. + * + * @return integer The number of errors + */ + public function countErrors(); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Log/LoggerInterface.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Log/LoggerInterface.php new file mode 100644 index 0000000..148c92c --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Log/LoggerInterface.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Log; + +use Psr\Log\LoggerInterface as PsrLogger; + +/** + * LoggerInterface. + * + * @author Fabien Potencier + * + * @deprecated since 2.2, to be removed in 3.0. Type-hint \Psr\Log\LoggerInterface instead. + * @api + */ +interface LoggerInterface extends PsrLogger +{ + /** + * @api + * @deprecated since 2.2, to be removed in 3.0. Use emergency() which is PSR-3 compatible. + */ + public function emerg($message, array $context = array()); + + /** + * @api + * @deprecated since 2.2, to be removed in 3.0. Use critical() which is PSR-3 compatible. + */ + public function crit($message, array $context = array()); + + /** + * @api + * @deprecated since 2.2, to be removed in 3.0. Use error() which is PSR-3 compatible. + */ + public function err($message, array $context = array()); + + /** + * @api + * @deprecated since 2.2, to be removed in 3.0. Use warning() which is PSR-3 compatible. + */ + public function warn($message, array $context = array()); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Log/NullLogger.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Log/NullLogger.php new file mode 100644 index 0000000..98f932a --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Log/NullLogger.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Log; + +use Psr\Log\NullLogger as PsrNullLogger; + +/** + * NullLogger. + * + * @author Fabien Potencier + * + * @api + */ +class NullLogger extends PsrNullLogger implements LoggerInterface +{ + /** + * @api + * @deprecated since 2.2, to be removed in 3.0. Use emergency() which is PSR-3 compatible. + */ + public function emerg($message, array $context = array()) + { + } + + /** + * @api + * @deprecated since 2.2, to be removed in 3.0. Use critical() which is PSR-3 compatible. + */ + public function crit($message, array $context = array()) + { + } + + /** + * @api + * @deprecated since 2.2, to be removed in 3.0. Use error() which is PSR-3 compatible. + */ + public function err($message, array $context = array()) + { + } + + /** + * @api + * @deprecated since 2.2, to be removed in 3.0. Use warning() which is PSR-3 compatible. + */ + public function warn($message, array $context = array()) + { + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/BaseMemcacheProfilerStorage.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/BaseMemcacheProfilerStorage.php new file mode 100644 index 0000000..69ff973 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/BaseMemcacheProfilerStorage.php @@ -0,0 +1,308 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +/** + * Base Memcache storage for profiling information in a Memcache. + * + * @author Andrej Hudec + */ +abstract class BaseMemcacheProfilerStorage implements ProfilerStorageInterface +{ + const TOKEN_PREFIX = 'sf_profiler_'; + + protected $dsn; + protected $lifetime; + + /** + * Constructor. + * + * @param string $dsn A data source name + * @param string $username + * @param string $password + * @param int $lifetime The lifetime to use for the purge + */ + public function __construct($dsn, $username = '', $password = '', $lifetime = 86400) + { + $this->dsn = $dsn; + $this->lifetime = (int) $lifetime; + } + + /** + * {@inheritdoc} + */ + public function find($ip, $url, $limit, $method, $start = null, $end = null) + { + $indexName = $this->getIndexName(); + + $indexContent = $this->getValue($indexName); + if (!$indexContent) { + return array(); + } + + $profileList = explode("\n", $indexContent); + $result = array(); + + foreach ($profileList as $item) { + + if ($limit === 0) { + break; + } + + if ($item=='') { + continue; + } + + list($itemToken, $itemIp, $itemMethod, $itemUrl, $itemTime, $itemParent) = explode("\t", $item, 6); + + $itemTime = (int) $itemTime; + + if ($ip && false === strpos($itemIp, $ip) || $url && false === strpos($itemUrl, $url) || $method && false === strpos($itemMethod, $method)) { + continue; + } + + if (!empty($start) && $itemTime < $start) { + continue; + } + + if (!empty($end) && $itemTime > $end) { + continue; + } + + $result[$itemToken] = array( + 'token' => $itemToken, + 'ip' => $itemIp, + 'method' => $itemMethod, + 'url' => $itemUrl, + 'time' => $itemTime, + 'parent' => $itemParent, + ); + --$limit; + } + + usort($result, function($a, $b) { + if ($a['time'] === $b['time']) { + return 0; + } + + return $a['time'] > $b['time'] ? -1 : 1; + }); + + return $result; + } + + /** + * {@inheritdoc} + */ + public function purge() + { + // delete only items from index + $indexName = $this->getIndexName(); + + $indexContent = $this->getValue($indexName); + + if (!$indexContent) { + return false; + } + + $profileList = explode("\n", $indexContent); + + foreach ($profileList as $item) { + if ($item == '') { + continue; + } + + if (false !== $pos = strpos($item, "\t")) { + $this->delete($this->getItemName(substr($item, 0, $pos))); + } + } + + return $this->delete($indexName); + } + + /** + * {@inheritdoc} + */ + public function read($token) + { + if (empty($token)) { + return false; + } + + $profile = $this->getValue($this->getItemName($token)); + + if (false !== $profile) { + $profile = $this->createProfileFromData($token, $profile); + } + + return $profile; + } + + /** + * {@inheritdoc} + */ + public function write(Profile $profile) + { + $data = array( + 'token' => $profile->getToken(), + 'parent' => $profile->getParentToken(), + 'children' => array_map(function ($p) { return $p->getToken(); }, $profile->getChildren()), + 'data' => $profile->getCollectors(), + 'ip' => $profile->getIp(), + 'method' => $profile->getMethod(), + 'url' => $profile->getUrl(), + 'time' => $profile->getTime(), + ); + + $profileIndexed = false !== $this->getValue($this->getItemName($profile->getToken())); + + if ($this->setValue($this->getItemName($profile->getToken()), $data, $this->lifetime)) { + + if (!$profileIndexed) { + // Add to index + $indexName = $this->getIndexName(); + + $indexRow = implode("\t", array( + $profile->getToken(), + $profile->getIp(), + $profile->getMethod(), + $profile->getUrl(), + $profile->getTime(), + $profile->getParentToken(), + ))."\n"; + + return $this->appendValue($indexName, $indexRow, $this->lifetime); + } + + return true; + } + + return false; + } + + /** + * Retrieve item from the memcache server + * + * @param string $key + * + * @return mixed + */ + abstract protected function getValue($key); + + /** + * Store an item on the memcache server under the specified key + * + * @param string $key + * @param mixed $value + * @param int $expiration + * + * @return boolean + */ + abstract protected function setValue($key, $value, $expiration = 0); + + /** + * Delete item from the memcache server + * + * @param string $key + * + * @return boolean + */ + abstract protected function delete($key); + + /** + * Append data to an existing item on the memcache server + * @param string $key + * @param string $value + * @param int $expiration + * + * @return boolean + */ + abstract protected function appendValue($key, $value, $expiration = 0); + + private function createProfileFromData($token, $data, $parent = null) + { + $profile = new Profile($token); + $profile->setIp($data['ip']); + $profile->setMethod($data['method']); + $profile->setUrl($data['url']); + $profile->setTime($data['time']); + $profile->setCollectors($data['data']); + + if (!$parent && $data['parent']) { + $parent = $this->read($data['parent']); + } + + if ($parent) { + $profile->setParent($parent); + } + + foreach ($data['children'] as $token) { + if (!$token) { + continue; + } + + if (!$childProfileData = $this->getValue($this->getItemName($token))) { + continue; + } + + $profile->addChild($this->createProfileFromData($token, $childProfileData, $profile)); + } + + return $profile; + } + + /** + * Get item name + * + * @param string $token + * + * @return string + */ + private function getItemName($token) + { + $name = self::TOKEN_PREFIX.$token; + + if ($this->isItemNameValid($name)) { + return $name; + } + + return false; + } + + /** + * Get name of index + * + * @return string + */ + private function getIndexName() + { + $name = self::TOKEN_PREFIX.'index'; + + if ($this->isItemNameValid($name)) { + return $name; + } + + return false; + } + + private function isItemNameValid($name) + { + $length = strlen($name); + + if ($length > 250) { + throw new \RuntimeException(sprintf('The memcache item key "%s" is too long (%s bytes). Allowed maximum size is 250 bytes.', $name, $length)); + } + + return true; + } + +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php new file mode 100644 index 0000000..9265fc1 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/FileProfilerStorage.php @@ -0,0 +1,277 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +/** + * Storage for profiler using files. + * + * @author Alexandre Salomé + */ +class FileProfilerStorage implements ProfilerStorageInterface +{ + /** + * Folder where profiler data are stored. + * + * @var string + */ + private $folder; + + /** + * Constructs the file storage using a "dsn-like" path. + * + * Example : "file:/path/to/the/storage/folder" + * + * @param string $dsn The DSN + * + * @throws \RuntimeException + */ + public function __construct($dsn) + { + if (0 !== strpos($dsn, 'file:')) { + throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use FileStorage with an invalid dsn "%s". The expected format is "file:/path/to/the/storage/folder".', $dsn)); + } + $this->folder = substr($dsn, 5); + + if (!is_dir($this->folder)) { + mkdir($this->folder, 0777, true); + } + } + + /** + * {@inheritdoc} + */ + public function find($ip, $url, $limit, $method, $start = null, $end = null) + { + $file = $this->getIndexFilename(); + + if (!file_exists($file)) { + return array(); + } + + $file = fopen($file, 'r'); + fseek($file, 0, SEEK_END); + + $result = array(); + while (count($result) < $limit && $line = $this->readLineFromFile($file)) { + list($csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent) = str_getcsv($line); + + $csvTime = (int) $csvTime; + + if ($ip && false === strpos($csvIp, $ip) || $url && false === strpos($csvUrl, $url) || $method && false === strpos($csvMethod, $method)) { + continue; + } + + if (!empty($start) && $csvTime < $start) { + continue; + } + + if (!empty($end) && $csvTime > $end) { + continue; + } + + $result[$csvToken] = array( + 'token' => $csvToken, + 'ip' => $csvIp, + 'method' => $csvMethod, + 'url' => $csvUrl, + 'time' => $csvTime, + 'parent' => $csvParent, + ); + } + + fclose($file); + + return array_values($result); + } + + /** + * {@inheritdoc} + */ + public function purge() + { + $flags = \FilesystemIterator::SKIP_DOTS; + $iterator = new \RecursiveDirectoryIterator($this->folder, $flags); + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($iterator as $file) { + if (is_file($file)) { + unlink($file); + } else { + rmdir($file); + } + } + } + + /** + * {@inheritdoc} + */ + public function read($token) + { + if (!$token || !file_exists($file = $this->getFilename($token))) { + return null; + } + + return $this->createProfileFromData($token, unserialize(file_get_contents($file))); + } + + /** + * {@inheritdoc} + */ + public function write(Profile $profile) + { + $file = $this->getFilename($profile->getToken()); + + $profileIndexed = is_file($file); + if (!$profileIndexed) { + // Create directory + $dir = dirname($file); + if (!is_dir($dir)) { + mkdir($dir, 0777, true); + } + } + + // Store profile + $data = array( + 'token' => $profile->getToken(), + 'parent' => $profile->getParentToken(), + 'children' => array_map(function ($p) { return $p->getToken(); }, $profile->getChildren()), + 'data' => $profile->getCollectors(), + 'ip' => $profile->getIp(), + 'method' => $profile->getMethod(), + 'url' => $profile->getUrl(), + 'time' => $profile->getTime(), + ); + + if (false === file_put_contents($file, serialize($data))) { + return false; + } + + if (!$profileIndexed) { + // Add to index + if (false === $file = fopen($this->getIndexFilename(), 'a')) { + return false; + } + + fputcsv($file, array( + $profile->getToken(), + $profile->getIp(), + $profile->getMethod(), + $profile->getUrl(), + $profile->getTime(), + $profile->getParentToken(), + )); + fclose($file); + } + + return true; + } + + /** + * Gets filename to store data, associated to the token. + * + * @param string $token + * + * @return string The profile filename + */ + protected function getFilename($token) + { + // Uses 4 last characters, because first are mostly the same. + $folderA = substr($token, -2, 2); + $folderB = substr($token, -4, 2); + + return $this->folder.'/'.$folderA.'/'.$folderB.'/'.$token; + } + + /** + * Gets the index filename. + * + * @return string The index filename + */ + protected function getIndexFilename() + { + return $this->folder.'/index.csv'; + } + + /** + * Reads a line in the file, backward. + * + * This function automatically skips the empty lines and do not include the line return in result value. + * + * @param resource $file The file resource, with the pointer placed at the end of the line to read + * + * @return mixed A string representing the line or null if beginning of file is reached + */ + protected function readLineFromFile($file) + { + $line = ''; + $position = ftell($file); + + if (0 === $position) { + return null; + } + + while (true) { + $chunkSize = min($position, 1024); + $position -= $chunkSize; + fseek($file, $position); + + if (0 === $chunkSize) { + // bof reached + break; + } + + $buffer = fread($file, $chunkSize); + + if (false === ($upTo = strrpos($buffer, "\n"))) { + $line = $buffer.$line; + continue; + } + + $position += $upTo; + $line = substr($buffer, $upTo + 1).$line; + fseek($file, max(0, $position), SEEK_SET); + + if ('' !== $line) { + break; + } + } + + return '' === $line ? null : $line; + } + + protected function createProfileFromData($token, $data, $parent = null) + { + $profile = new Profile($token); + $profile->setIp($data['ip']); + $profile->setMethod($data['method']); + $profile->setUrl($data['url']); + $profile->setTime($data['time']); + $profile->setCollectors($data['data']); + + if (!$parent && $data['parent']) { + $parent = $this->read($data['parent']); + } + + if ($parent) { + $profile->setParent($parent); + } + + foreach ($data['children'] as $token) { + if (!$token || !file_exists($file = $this->getFilename($token))) { + continue; + } + + $profile->addChild($this->createProfileFromData($token, unserialize(file_get_contents($file)), $profile)); + } + + return $profile; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/MemcacheProfilerStorage.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/MemcacheProfilerStorage.php new file mode 100644 index 0000000..a78cec0 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/MemcacheProfilerStorage.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +use Memcache; + +/** + * Memcache Profiler Storage + * + * @author Andrej Hudec + */ +class MemcacheProfilerStorage extends BaseMemcacheProfilerStorage +{ + /** + * @var Memcache + */ + private $memcache; + + /** + * Internal convenience method that returns the instance of the Memcache + * + * @return Memcache + * + * @throws \RuntimeException + */ + protected function getMemcache() + { + if (null === $this->memcache) { + if (!preg_match('#^memcache://(?(?=\[.*\])\[(.*)\]|(.*)):(.*)$#', $this->dsn, $matches)) { + throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use Memcache with an invalid dsn "%s". The expected format is "memcache://[host]:port".', $this->dsn)); + } + + $host = $matches[1] ?: $matches[2]; + $port = $matches[3]; + + $memcache = new Memcache; + $memcache->addServer($host, $port); + + $this->memcache = $memcache; + } + + return $this->memcache; + } + + /** + * Set instance of the Memcache + * + * @param Memcache $memcache + */ + public function setMemcache($memcache) + { + $this->memcache = $memcache; + } + + /** + * {@inheritdoc} + */ + protected function getValue($key) + { + return $this->getMemcache()->get($key); + } + + /** + * {@inheritdoc} + */ + protected function setValue($key, $value, $expiration = 0) + { + return $this->getMemcache()->set($key, $value, false, time() + $expiration); + } + + /** + * {@inheritdoc} + */ + protected function delete($key) + { + return $this->getMemcache()->delete($key); + } + + /** + * {@inheritdoc} + */ + protected function appendValue($key, $value, $expiration = 0) + { + $memcache = $this->getMemcache(); + + if (method_exists($memcache, 'append')) { + + //Memcache v3.0 + if (!$result = $memcache->append($key, $value, false, $expiration)) { + return $memcache->set($key, $value, false, $expiration); + } + + return $result; + } + + //simulate append in Memcache <3.0 + $content = $memcache->get($key); + + return $memcache->set($key, $content.$value, false, $expiration); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/MemcachedProfilerStorage.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/MemcachedProfilerStorage.php new file mode 100644 index 0000000..f7f6842 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/MemcachedProfilerStorage.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +use Memcached; + +/** + * Memcached Profiler Storage + * + * @author Andrej Hudec + */ +class MemcachedProfilerStorage extends BaseMemcacheProfilerStorage +{ + /** + * @var Memcached + */ + private $memcached; + + /** + * Internal convenience method that returns the instance of the Memcached + * + * @return Memcached + * + * @throws \RuntimeException + */ + protected function getMemcached() + { + if (null === $this->memcached) { + if (!preg_match('#^memcached://(?(?=\[.*\])\[(.*)\]|(.*)):(.*)$#', $this->dsn, $matches)) { + throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use Memcached with an invalid dsn "%s". The expected format is "memcached://[host]:port".', $this->dsn)); + } + + $host = $matches[1] ?: $matches[2]; + $port = $matches[3]; + + $memcached = new Memcached; + + //disable compression to allow appending + $memcached->setOption(Memcached::OPT_COMPRESSION, false); + + $memcached->addServer($host, $port); + + $this->memcached = $memcached; + } + + return $this->memcached; + } + + /** + * Set instance of the Memcached + * + * @param Memcached $memcached + */ + public function setMemcached($memcached) + { + $this->memcached = $memcached; + } + + /** + * {@inheritdoc} + */ + protected function getValue($key) + { + return $this->getMemcached()->get($key); + } + + /** + * {@inheritdoc} + */ + protected function setValue($key, $value, $expiration = 0) + { + return $this->getMemcached()->set($key, $value, time() + $expiration); + } + + /** + * {@inheritdoc} + */ + protected function delete($key) + { + return $this->getMemcached()->delete($key); + } + + /** + * {@inheritdoc} + */ + protected function appendValue($key, $value, $expiration = 0) + { + $memcached = $this->getMemcached(); + + if (!$result = $memcached->append($key, $value)) { + return $memcached->set($key, $value, $expiration); + } + + return $result; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/MongoDbProfilerStorage.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/MongoDbProfilerStorage.php new file mode 100644 index 0000000..38a522a --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/MongoDbProfilerStorage.php @@ -0,0 +1,236 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +class MongoDbProfilerStorage implements ProfilerStorageInterface +{ + protected $dsn; + protected $lifetime; + private $mongo; + + /** + * Constructor. + * + * @param string $dsn A data source name + * @param string $username Not used + * @param string $password Not used + * @param integer $lifetime The lifetime to use for the purge + */ + public function __construct($dsn, $username = '', $password = '', $lifetime = 86400) + { + $this->dsn = $dsn; + $this->lifetime = (int) $lifetime; + } + + /** + * {@inheritdoc} + */ + public function find($ip, $url, $limit, $method, $start = null, $end = null) + { + $cursor = $this->getMongo()->find($this->buildQuery($ip, $url, $method, $start, $end), array('_id', 'parent', 'ip', 'method', 'url', 'time'))->sort(array('time' => -1))->limit($limit); + + $tokens = array(); + foreach ($cursor as $profile) { + $tokens[] = $this->getData($profile); + } + + return $tokens; + } + + /** + * {@inheritdoc} + */ + public function purge() + { + $this->getMongo()->remove(array()); + } + + /** + * {@inheritdoc} + */ + public function read($token) + { + $profile = $this->getMongo()->findOne(array('_id' => $token, 'data' => array('$exists' => true))); + + if (null !== $profile) { + $profile = $this->createProfileFromData($this->getData($profile)); + } + + return $profile; + } + + /** + * {@inheritdoc} + */ + public function write(Profile $profile) + { + $this->cleanup(); + + $record = array( + '_id' => $profile->getToken(), + 'parent' => $profile->getParentToken(), + 'data' => base64_encode(serialize($profile->getCollectors())), + 'ip' => $profile->getIp(), + 'method' => $profile->getMethod(), + 'url' => $profile->getUrl(), + 'time' => $profile->getTime() + ); + + $result = $this->getMongo()->update(array('_id' => $profile->getToken()), array_filter($record, function ($v) { return !empty($v); }), array('upsert' => true)); + + return (boolean) (isset($result['ok']) ? $result['ok'] : $result); + } + + /** + * Internal convenience method that returns the instance of the MongoDB Collection + * + * @return \MongoCollection + * + * @throws \RuntimeException + */ + protected function getMongo() + { + if ($this->mongo === null) { + if (preg_match('#^(mongodb://.*)/(.*)/(.*)$#', $this->dsn, $matches)) { + $server = $matches[1].(!empty($matches[2]) ? '/'.$matches[2] : ''); + $database = $matches[2]; + $collection = $matches[3]; + + $mongoClass = (version_compare(phpversion('mongo'), '1.3.0', '<')) ? '\Mongo' : '\MongoClient'; + $mongo = new $mongoClass($server); + $this->mongo = $mongo->selectCollection($database, $collection); + } else { + throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use MongoDB with an invalid dsn "%s". The expected format is "mongodb://[user:pass@]host/database/collection"', $this->dsn)); + } + } + + return $this->mongo; + } + + /** + * @param array $data + * + * @return Profile + */ + protected function createProfileFromData(array $data) + { + $profile = $this->getProfile($data); + + if ($data['parent']) { + $parent = $this->getMongo()->findOne(array('_id' => $data['parent'], 'data' => array('$exists' => true))); + if ($parent) { + $profile->setParent($this->getProfile($this->getData($parent))); + } + } + + $profile->setChildren($this->readChildren($data['token'])); + + return $profile; + } + + /** + * @param string $token + * + * @return Profile[] An array of Profile instances + */ + protected function readChildren($token) + { + $profiles = array(); + + $cursor = $this->getMongo()->find(array('parent' => $token, 'data' => array('$exists' => true))); + foreach ($cursor as $d) { + $profiles[] = $this->getProfile($this->getData($d)); + } + + return $profiles; + } + + protected function cleanup() + { + $this->getMongo()->remove(array('time' => array('$lt' => time() - $this->lifetime))); + } + + /** + * @param string $ip + * @param string $url + * @param string $method + * @param int $start + * @param int $end + * + * @return array + */ + private function buildQuery($ip, $url, $method, $start, $end) + { + $query = array(); + + if (!empty($ip)) { + $query['ip'] = $ip; + } + + if (!empty($url)) { + $query['url'] = $url; + } + + if (!empty($method)) { + $query['method'] = $method; + } + + if (!empty($start) || !empty($end)) { + $query['time'] = array(); + } + + if (!empty($start)) { + $query['time']['$gte'] = $start; + } + + if (!empty($end)) { + $query['time']['$lte'] = $end; + } + + return $query; + } + + /** + * @param array $data + * + * @return array + */ + private function getData(array $data) + { + return array( + 'token' => $data['_id'], + 'parent' => isset($data['parent']) ? $data['parent'] : null, + 'ip' => isset($data['ip']) ? $data['ip'] : null, + 'method' => isset($data['method']) ? $data['method'] : null, + 'url' => isset($data['url']) ? $data['url'] : null, + 'time' => isset($data['time']) ? $data['time'] : null, + 'data' => isset($data['data']) ? $data['data'] : null, + ); + } + + /** + * @param array $data + * + * @return Profile + */ + private function getProfile(array $data) + { + $profile = new Profile($data['token']); + $profile->setIp($data['ip']); + $profile->setMethod($data['method']); + $profile->setUrl($data['url']); + $profile->setTime($data['time']); + $profile->setCollectors(unserialize(base64_decode($data['data']))); + + return $profile; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/MysqlProfilerStorage.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/MysqlProfilerStorage.php new file mode 100644 index 0000000..0aee1b5 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/MysqlProfilerStorage.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +/** + * A ProfilerStorage for Mysql + * + * @author Jan Schumann + */ +class MysqlProfilerStorage extends PdoProfilerStorage +{ + /** + * {@inheritdoc} + */ + protected function initDb() + { + if (null === $this->db) { + if (0 !== strpos($this->dsn, 'mysql')) { + throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use Mysql with an invalid dsn "%s". The expected format is "mysql:dbname=database_name;host=host_name".', $this->dsn)); + } + + if (!class_exists('PDO') || !in_array('mysql', \PDO::getAvailableDrivers(), true)) { + throw new \RuntimeException('You need to enable PDO_Mysql extension for the profiler to run properly.'); + } + + $db = new \PDO($this->dsn, $this->username, $this->password); + $db->exec('CREATE TABLE IF NOT EXISTS sf_profiler_data (token VARCHAR(255) PRIMARY KEY, data LONGTEXT, ip VARCHAR(64), method VARCHAR(6), url VARCHAR(255), time INTEGER UNSIGNED, parent VARCHAR(255), created_at INTEGER UNSIGNED, KEY (created_at), KEY (ip), KEY (method), KEY (url), KEY (parent))'); + + $this->db = $db; + } + + return $this->db; + } + + /** + * {@inheritdoc} + */ + protected function buildCriteria($ip, $url, $start, $end, $limit, $method) + { + $criteria = array(); + $args = array(); + + if ($ip = preg_replace('/[^\d\.]/', '', $ip)) { + $criteria[] = 'ip LIKE :ip'; + $args[':ip'] = '%'.$ip.'%'; + } + + if ($url) { + $criteria[] = 'url LIKE :url'; + $args[':url'] = '%'.addcslashes($url, '%_\\').'%'; + } + + if ($method) { + $criteria[] = 'method = :method'; + $args[':method'] = $method; + } + + if (!empty($start)) { + $criteria[] = 'time >= :start'; + $args[':start'] = $start; + } + + if (!empty($end)) { + $criteria[] = 'time <= :end'; + $args[':end'] = $end; + } + + return array($criteria, $args); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/PdoProfilerStorage.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/PdoProfilerStorage.php new file mode 100644 index 0000000..3f9e03d --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/PdoProfilerStorage.php @@ -0,0 +1,264 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +/** + * Base PDO storage for profiling information in a PDO database. + * + * @author Fabien Potencier + * @author Jan Schumann + */ +abstract class PdoProfilerStorage implements ProfilerStorageInterface +{ + protected $dsn; + protected $username; + protected $password; + protected $lifetime; + protected $db; + + /** + * Constructor. + * + * @param string $dsn A data source name + * @param string $username The username for the database + * @param string $password The password for the database + * @param integer $lifetime The lifetime to use for the purge + */ + public function __construct($dsn, $username = '', $password = '', $lifetime = 86400) + { + $this->dsn = $dsn; + $this->username = $username; + $this->password = $password; + $this->lifetime = (int) $lifetime; + } + + /** + * {@inheritdoc} + */ + public function find($ip, $url, $limit, $method, $start = null, $end = null) + { + if (null === $start) { + $start = 0; + } + + if (null === $end) { + $end = time(); + } + + list($criteria, $args) = $this->buildCriteria($ip, $url, $start, $end, $limit, $method); + + $criteria = $criteria ? 'WHERE '.implode(' AND ', $criteria) : ''; + + $db = $this->initDb(); + $tokens = $this->fetch($db, 'SELECT token, ip, method, url, time, parent FROM sf_profiler_data '.$criteria.' ORDER BY time DESC LIMIT '.((integer) $limit), $args); + $this->close($db); + + return $tokens; + } + + /** + * {@inheritdoc} + */ + public function read($token) + { + $db = $this->initDb(); + $args = array(':token' => $token); + $data = $this->fetch($db, 'SELECT data, parent, ip, method, url, time FROM sf_profiler_data WHERE token = :token LIMIT 1', $args); + $this->close($db); + if (isset($data[0]['data'])) { + return $this->createProfileFromData($token, $data[0]); + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function write(Profile $profile) + { + $db = $this->initDb(); + $args = array( + ':token' => $profile->getToken(), + ':parent' => $profile->getParentToken(), + ':data' => base64_encode(serialize($profile->getCollectors())), + ':ip' => $profile->getIp(), + ':method' => $profile->getMethod(), + ':url' => $profile->getUrl(), + ':time' => $profile->getTime(), + ':created_at' => time(), + ); + + try { + if ($this->has($profile->getToken())) { + $this->exec($db, 'UPDATE sf_profiler_data SET parent = :parent, data = :data, ip = :ip, method = :method, url = :url, time = :time, created_at = :created_at WHERE token = :token', $args); + } else { + $this->exec($db, 'INSERT INTO sf_profiler_data (token, parent, data, ip, method, url, time, created_at) VALUES (:token, :parent, :data, :ip, :method, :url, :time, :created_at)', $args); + } + $this->cleanup(); + $status = true; + } catch (\Exception $e) { + $status = false; + } + + $this->close($db); + + return $status; + } + + /** + * {@inheritdoc} + */ + public function purge() + { + $db = $this->initDb(); + $this->exec($db, 'DELETE FROM sf_profiler_data'); + $this->close($db); + } + + /** + * Build SQL criteria to fetch records by ip and url + * + * @param string $ip The IP + * @param string $url The URL + * @param string $start The start period to search from + * @param string $end The end period to search to + * @param string $limit The maximum number of tokens to return + * @param string $method The request method + * + * @return array An array with (criteria, args) + */ + abstract protected function buildCriteria($ip, $url, $start, $end, $limit, $method); + + /** + * Initializes the database + * + * @throws \RuntimeException When the requested database driver is not installed + */ + abstract protected function initDb(); + + protected function cleanup() + { + $db = $this->initDb(); + $this->exec($db, 'DELETE FROM sf_profiler_data WHERE created_at < :time', array(':time' => time() - $this->lifetime)); + $this->close($db); + } + + protected function exec($db, $query, array $args = array()) + { + $stmt = $this->prepareStatement($db, $query); + + foreach ($args as $arg => $val) { + $stmt->bindValue($arg, $val, is_int($val) ? \PDO::PARAM_INT : \PDO::PARAM_STR); + } + $success = $stmt->execute(); + if (!$success) { + throw new \RuntimeException(sprintf('Error executing query "%s"', $query)); + } + } + + protected function prepareStatement($db, $query) + { + try { + $stmt = $db->prepare($query); + } catch (\Exception $e) { + $stmt = false; + } + + if (false === $stmt) { + throw new \RuntimeException('The database cannot successfully prepare the statement'); + } + + return $stmt; + } + + protected function fetch($db, $query, array $args = array()) + { + $stmt = $this->prepareStatement($db, $query); + + foreach ($args as $arg => $val) { + $stmt->bindValue($arg, $val, is_int($val) ? \PDO::PARAM_INT : \PDO::PARAM_STR); + } + $stmt->execute(); + $return = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + return $return; + } + + protected function close($db) + { + } + + protected function createProfileFromData($token, $data, $parent = null) + { + $profile = new Profile($token); + $profile->setIp($data['ip']); + $profile->setMethod($data['method']); + $profile->setUrl($data['url']); + $profile->setTime($data['time']); + $profile->setCollectors(unserialize(base64_decode($data['data']))); + + if (!$parent && !empty($data['parent'])) { + $parent = $this->read($data['parent']); + } + + if ($parent) { + $profile->setParent($parent); + } + + $profile->setChildren($this->readChildren($token, $profile)); + + return $profile; + } + + /** + * Reads the child profiles for the given token. + * + * @param string $token The parent token + * @param string $parent The parent instance + * + * @return Profile[] An array of Profile instance + */ + protected function readChildren($token, $parent) + { + $db = $this->initDb(); + $data = $this->fetch($db, 'SELECT token, data, ip, method, url, time FROM sf_profiler_data WHERE parent = :token', array(':token' => $token)); + $this->close($db); + + if (!$data) { + return array(); + } + + $profiles = array(); + foreach ($data as $d) { + $profiles[] = $this->createProfileFromData($d['token'], $d, $parent); + } + + return $profiles; + } + + /** + * Returns whether data for the given token already exists in storage. + * + * @param string $token The profile token + * + * @return string + */ + protected function has($token) + { + $db = $this->initDb(); + $tokenExists = $this->fetch($db, 'SELECT 1 FROM sf_profiler_data WHERE token = :token LIMIT 1', array(':token' => $token)); + $this->close($db); + + return !empty($tokenExists); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/Profile.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/Profile.php new file mode 100644 index 0000000..b3fa551 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/Profile.php @@ -0,0 +1,275 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; + +/** + * Profile. + * + * @author Fabien Potencier + */ +class Profile +{ + private $token; + + /** + * @var DataCollectorInterface[] + */ + private $collectors = array(); + + private $ip; + private $method; + private $url; + private $time; + + /** + * @var Profile + */ + private $parent; + + /** + * @var Profile[] + */ + private $children = array(); + + /** + * Constructor. + * + * @param string $token The token + */ + public function __construct($token) + { + $this->token = $token; + } + + /** + * Sets the token. + * + * @param string $token The token + */ + public function setToken($token) + { + $this->token = $token; + } + + /** + * Gets the token. + * + * @return string The token + */ + public function getToken() + { + return $this->token; + } + + /** + * Sets the parent token + * + * @param Profile $parent The parent Profile + */ + public function setParent(Profile $parent) + { + $this->parent = $parent; + } + + /** + * Returns the parent profile. + * + * @return Profile The parent profile + */ + public function getParent() + { + return $this->parent; + } + + /** + * Returns the parent token. + * + * @return null|string The parent token + */ + public function getParentToken() + { + return $this->parent ? $this->parent->getToken() : null; + } + + /** + * Returns the IP. + * + * @return string The IP + */ + public function getIp() + { + return $this->ip; + } + + /** + * Sets the IP. + * + * @param string $ip + */ + public function setIp($ip) + { + $this->ip = $ip; + } + + /** + * Returns the request method. + * + * @return string The request method + */ + public function getMethod() + { + return $this->method; + } + + public function setMethod($method) + { + $this->method = $method; + } + + /** + * Returns the URL. + * + * @return string The URL + */ + public function getUrl() + { + return $this->url; + } + + public function setUrl($url) + { + $this->url = $url; + } + + /** + * Returns the time. + * + * @return string The time + */ + public function getTime() + { + if (null === $this->time) { + return 0; + } + + return $this->time; + } + + public function setTime($time) + { + $this->time = $time; + } + + /** + * Finds children profilers. + * + * @return Profile[] An array of Profile + */ + public function getChildren() + { + return $this->children; + } + + /** + * Sets children profiler. + * + * @param Profile[] $children An array of Profile + */ + public function setChildren(array $children) + { + $this->children = array(); + foreach ($children as $child) { + $this->addChild($child); + } + } + + /** + * Adds the child token + * + * @param Profile $child The child Profile + */ + public function addChild(Profile $child) + { + $this->children[] = $child; + $child->setParent($this); + } + + /** + * Gets a Collector by name. + * + * @param string $name A collector name + * + * @return DataCollectorInterface A DataCollectorInterface instance + * + * @throws \InvalidArgumentException if the collector does not exist + */ + public function getCollector($name) + { + if (!isset($this->collectors[$name])) { + throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name)); + } + + return $this->collectors[$name]; + } + + /** + * Gets the Collectors associated with this profile. + * + * @return DataCollectorInterface[] + */ + public function getCollectors() + { + return $this->collectors; + } + + /** + * Sets the Collectors associated with this profile. + * + * @param DataCollectorInterface[] $collectors + */ + public function setCollectors(array $collectors) + { + $this->collectors = array(); + foreach ($collectors as $collector) { + $this->addCollector($collector); + } + } + + /** + * Adds a Collector. + * + * @param DataCollectorInterface $collector A DataCollectorInterface instance + */ + public function addCollector(DataCollectorInterface $collector) + { + $this->collectors[$collector->getName()] = $collector; + } + + /** + * Returns true if a Collector for the given name exists. + * + * @param string $name A collector name + * + * @return Boolean + */ + public function hasCollector($name) + { + return isset($this->collectors[$name]); + } + + public function __sleep() + { + return array('token', 'parent', 'children', 'collectors', 'ip', 'method', 'url', 'time'); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/Profiler.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/Profiler.php new file mode 100644 index 0000000..49f3137 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/Profiler.php @@ -0,0 +1,287 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface; +use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; +use Psr\Log\LoggerInterface; + +/** + * Profiler. + * + * @author Fabien Potencier + */ +class Profiler +{ + /** + * @var ProfilerStorageInterface + */ + private $storage; + + /** + * @var DataCollectorInterface[] + */ + private $collectors = array(); + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var Boolean + */ + private $enabled = true; + + /** + * Constructor. + * + * @param ProfilerStorageInterface $storage A ProfilerStorageInterface instance + * @param LoggerInterface $logger A LoggerInterface instance + */ + public function __construct(ProfilerStorageInterface $storage, LoggerInterface $logger = null) + { + $this->storage = $storage; + $this->logger = $logger; + } + + /** + * Disables the profiler. + */ + public function disable() + { + $this->enabled = false; + } + + /** + * Enables the profiler. + */ + public function enable() + { + $this->enabled = true; + } + + /** + * Loads the Profile for the given Response. + * + * @param Response $response A Response instance + * + * @return Profile A Profile instance + */ + public function loadProfileFromResponse(Response $response) + { + if (!$token = $response->headers->get('X-Debug-Token')) { + return false; + } + + return $this->loadProfile($token); + } + + /** + * Loads the Profile for the given token. + * + * @param string $token A token + * + * @return Profile A Profile instance + */ + public function loadProfile($token) + { + return $this->storage->read($token); + } + + /** + * Saves a Profile. + * + * @param Profile $profile A Profile instance + * + * @return Boolean + */ + public function saveProfile(Profile $profile) + { + if (!($ret = $this->storage->write($profile)) && null !== $this->logger) { + $this->logger->warning('Unable to store the profiler information.'); + } + + return $ret; + } + + /** + * Purges all data from the storage. + */ + public function purge() + { + $this->storage->purge(); + } + + /** + * Exports the current profiler data. + * + * @param Profile $profile A Profile instance + * + * @return string The exported data + */ + public function export(Profile $profile) + { + return base64_encode(serialize($profile)); + } + + /** + * Imports data into the profiler storage. + * + * @param string $data A data string as exported by the export() method + * + * @return Profile A Profile instance + */ + public function import($data) + { + $profile = unserialize(base64_decode($data)); + + if ($this->storage->read($profile->getToken())) { + return false; + } + + $this->saveProfile($profile); + + return $profile; + } + + /** + * Finds profiler tokens for the given criteria. + * + * @param string $ip The IP + * @param string $url The URL + * @param string $limit The maximum number of tokens to return + * @param string $method The request method + * @param string $start The start date to search from + * @param string $end The end date to search to + * + * @return array An array of tokens + * + * @see http://fr2.php.net/manual/en/datetime.formats.php for the supported date/time formats + */ + public function find($ip, $url, $limit, $method, $start, $end) + { + if ('' != $start && null !== $start) { + $start = new \DateTime($start); + $start = $start->getTimestamp(); + } else { + $start = null; + } + + if ('' != $end && null !== $end) { + $end = new \DateTime($end); + $end = $end->getTimestamp(); + } else { + $end = null; + } + + return $this->storage->find($ip, $url, $limit, $method, $start, $end); + } + + /** + * Collects data for the given Response. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * @param \Exception $exception An exception instance if the request threw one + * + * @return Profile|null A Profile instance or null if the profiler is disabled + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if (false === $this->enabled) { + return; + } + + $profile = new Profile(substr(sha1(uniqid(mt_rand(), true)), 0, 6)); + $profile->setTime(time()); + $profile->setUrl($request->getUri()); + $profile->setIp($request->getClientIp()); + $profile->setMethod($request->getMethod()); + + $response->headers->set('X-Debug-Token', $profile->getToken()); + + foreach ($this->collectors as $collector) { + $collector->collect($request, $response, $exception); + + // forces collectors to become "read/only" (they loose their object dependencies) + $profile->addCollector(unserialize(serialize($collector))); + } + + return $profile; + } + + /** + * Gets the Collectors associated with this profiler. + * + * @return array An array of collectors + */ + public function all() + { + return $this->collectors; + } + + /** + * Sets the Collectors associated with this profiler. + * + * @param DataCollectorInterface[] $collectors An array of collectors + */ + public function set(array $collectors = array()) + { + $this->collectors = array(); + foreach ($collectors as $collector) { + $this->add($collector); + } + } + + /** + * Adds a Collector. + * + * @param DataCollectorInterface $collector A DataCollectorInterface instance + */ + public function add(DataCollectorInterface $collector) + { + $this->collectors[$collector->getName()] = $collector; + } + + /** + * Returns true if a Collector for the given name exists. + * + * @param string $name A collector name + * + * @return Boolean + */ + public function has($name) + { + return isset($this->collectors[$name]); + } + + /** + * Gets a Collector by name. + * + * @param string $name A collector name + * + * @return DataCollectorInterface A DataCollectorInterface instance + * + * @throws \InvalidArgumentException if the collector does not exist + */ + public function get($name) + { + if (!isset($this->collectors[$name])) { + throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name)); + } + + return $this->collectors[$name]; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/ProfilerStorageInterface.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/ProfilerStorageInterface.php new file mode 100644 index 0000000..f4b9e5e --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/ProfilerStorageInterface.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +/** + * ProfilerStorageInterface. + * + * @author Fabien Potencier + */ +interface ProfilerStorageInterface +{ + /** + * Finds profiler tokens for the given criteria. + * + * @param string $ip The IP + * @param string $url The URL + * @param string $limit The maximum number of tokens to return + * @param string $method The request method + * @param int|null $start The start date to search from + * @param int|null $end The end date to search to + * + * @return array An array of tokens + */ + public function find($ip, $url, $limit, $method, $start = null, $end = null); + + /** + * Reads data associated with the given token. + * + * The method returns false if the token does not exist in the storage. + * + * @param string $token A token + * + * @return Profile The profile associated with token + */ + public function read($token); + + /** + * Saves a Profile. + * + * @param Profile $profile A Profile instance + * + * @return Boolean Write operation successful + */ + public function write(Profile $profile); + + /** + * Purges all data from the database. + */ + public function purge(); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/RedisProfilerStorage.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/RedisProfilerStorage.php new file mode 100644 index 0000000..d62d6c7 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/RedisProfilerStorage.php @@ -0,0 +1,393 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +use Redis; + +/** + * RedisProfilerStorage stores profiling information in Redis. + * + * @author Andrej Hudec + * @author Stephane PY + */ +class RedisProfilerStorage implements ProfilerStorageInterface +{ + const TOKEN_PREFIX = 'sf_profiler_'; + + const REDIS_OPT_SERIALIZER = 1; + const REDIS_OPT_PREFIX = 2; + const REDIS_SERIALIZER_NONE = 0; + const REDIS_SERIALIZER_PHP = 1; + + protected $dsn; + protected $lifetime; + + /** + * @var Redis + */ + private $redis; + + /** + * Constructor. + * + * @param string $dsn A data source name + * @param string $username Not used + * @param string $password Not used + * @param int $lifetime The lifetime to use for the purge + */ + public function __construct($dsn, $username = '', $password = '', $lifetime = 86400) + { + $this->dsn = $dsn; + $this->lifetime = (int) $lifetime; + } + + /** + * {@inheritdoc} + */ + public function find($ip, $url, $limit, $method, $start = null, $end = null) + { + $indexName = $this->getIndexName(); + + if (!$indexContent = $this->getValue($indexName, self::REDIS_SERIALIZER_NONE)) { + return array(); + } + + $profileList = array_reverse(explode("\n", $indexContent)); + $result = array(); + + foreach ($profileList as $item) { + if ($limit === 0) { + break; + } + + if ($item == '') { + continue; + } + + list($itemToken, $itemIp, $itemMethod, $itemUrl, $itemTime, $itemParent) = explode("\t", $item, 6); + + $itemTime = (int) $itemTime; + + if ($ip && false === strpos($itemIp, $ip) || $url && false === strpos($itemUrl, $url) || $method && false === strpos($itemMethod, $method)) { + continue; + } + + if (!empty($start) && $itemTime < $start) { + continue; + } + + if (!empty($end) && $itemTime > $end) { + continue; + } + + $result[] = array( + 'token' => $itemToken, + 'ip' => $itemIp, + 'method' => $itemMethod, + 'url' => $itemUrl, + 'time' => $itemTime, + 'parent' => $itemParent, + ); + --$limit; + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function purge() + { + // delete only items from index + $indexName = $this->getIndexName(); + + $indexContent = $this->getValue($indexName, self::REDIS_SERIALIZER_NONE); + + if (!$indexContent) { + return false; + } + + $profileList = explode("\n", $indexContent); + + $result = array(); + + foreach ($profileList as $item) { + if ($item == '') { + continue; + } + + if (false !== $pos = strpos($item, "\t")) { + $result[] = $this->getItemName(substr($item, 0, $pos)); + } + } + + $result[] = $indexName; + + return $this->delete($result); + } + + /** + * {@inheritdoc} + */ + public function read($token) + { + if (empty($token)) { + return false; + } + + $profile = $this->getValue($this->getItemName($token), self::REDIS_SERIALIZER_PHP); + + if (false !== $profile) { + $profile = $this->createProfileFromData($token, $profile); + } + + return $profile; + } + + /** + * {@inheritdoc} + */ + public function write(Profile $profile) + { + $data = array( + 'token' => $profile->getToken(), + 'parent' => $profile->getParentToken(), + 'children' => array_map(function ($p) { return $p->getToken(); }, $profile->getChildren()), + 'data' => $profile->getCollectors(), + 'ip' => $profile->getIp(), + 'method' => $profile->getMethod(), + 'url' => $profile->getUrl(), + 'time' => $profile->getTime(), + ); + + $profileIndexed = false !== $this->getValue($this->getItemName($profile->getToken())); + + if ($this->setValue($this->getItemName($profile->getToken()), $data, $this->lifetime, self::REDIS_SERIALIZER_PHP)) { + + if (!$profileIndexed) { + // Add to index + $indexName = $this->getIndexName(); + + $indexRow = implode("\t", array( + $profile->getToken(), + $profile->getIp(), + $profile->getMethod(), + $profile->getUrl(), + $profile->getTime(), + $profile->getParentToken(), + ))."\n"; + + return $this->appendValue($indexName, $indexRow, $this->lifetime); + } + + return true; + } + + return false; + } + + /** + * Internal convenience method that returns the instance of Redis. + * + * @return Redis + * + * @throws \RuntimeException + */ + protected function getRedis() + { + if (null === $this->redis) { + $data = parse_url($this->dsn); + + if (false === $data || !isset($data['scheme']) || $data['scheme'] !== 'redis' || !isset($data['host']) || !isset($data['port'])) { + throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use Redis with an invalid dsn "%s". The minimal expected format is "redis://[host]:port".', $this->dsn)); + } + + if (!extension_loaded('redis')) { + throw new \RuntimeException('RedisProfilerStorage requires that the redis extension is loaded.'); + } + + $redis = new Redis; + $redis->connect($data['host'], $data['port']); + + if (isset($data['path'])) { + $redis->select(substr($data['path'], 1)); + } + + if (isset($data['pass'])) { + $redis->auth($data['pass']); + } + + $redis->setOption(self::REDIS_OPT_PREFIX, self::TOKEN_PREFIX); + + $this->redis = $redis; + } + + return $this->redis; + } + + /** + * Set instance of the Redis + * + * @param Redis $redis + */ + public function setRedis($redis) + { + $this->redis = $redis; + } + + private function createProfileFromData($token, $data, $parent = null) + { + $profile = new Profile($token); + $profile->setIp($data['ip']); + $profile->setMethod($data['method']); + $profile->setUrl($data['url']); + $profile->setTime($data['time']); + $profile->setCollectors($data['data']); + + if (!$parent && $data['parent']) { + $parent = $this->read($data['parent']); + } + + if ($parent) { + $profile->setParent($parent); + } + + foreach ($data['children'] as $token) { + if (!$token) { + continue; + } + + if (!$childProfileData = $this->getValue($this->getItemName($token), self::REDIS_SERIALIZER_PHP)) { + continue; + } + + $profile->addChild($this->createProfileFromData($token, $childProfileData, $profile)); + } + + return $profile; + } + + /** + * Gets the item name. + * + * @param string $token + * + * @return string + */ + private function getItemName($token) + { + $name = $token; + + if ($this->isItemNameValid($name)) { + return $name; + } + + return false; + } + + /** + * Gets the name of the index. + * + * @return string + */ + private function getIndexName() + { + $name = 'index'; + + if ($this->isItemNameValid($name)) { + return $name; + } + + return false; + } + + private function isItemNameValid($name) + { + $length = strlen($name); + + if ($length > 2147483648) { + throw new \RuntimeException(sprintf('The Redis item key "%s" is too long (%s bytes). Allowed maximum size is 2^31 bytes.', $name, $length)); + } + + return true; + } + + /** + * Retrieves an item from the Redis server. + * + * @param string $key + * @param int $serializer + * + * @return mixed + */ + private function getValue($key, $serializer = self::REDIS_SERIALIZER_NONE) + { + $redis = $this->getRedis(); + $redis->setOption(self::REDIS_OPT_SERIALIZER, $serializer); + + return $redis->get($key); + } + + /** + * Stores an item on the Redis server under the specified key. + * + * @param string $key + * @param mixed $value + * @param int $expiration + * @param int $serializer + * + * @return Boolean + */ + private function setValue($key, $value, $expiration = 0, $serializer = self::REDIS_SERIALIZER_NONE) + { + $redis = $this->getRedis(); + $redis->setOption(self::REDIS_OPT_SERIALIZER, $serializer); + + return $redis->setex($key, $expiration, $value); + } + + /** + * Appends data to an existing item on the Redis server. + * + * @param string $key + * @param string $value + * @param int $expiration + * + * @return Boolean + */ + private function appendValue($key, $value, $expiration = 0) + { + $redis = $this->getRedis(); + $redis->setOption(self::REDIS_OPT_SERIALIZER, self::REDIS_SERIALIZER_NONE); + + if ($redis->exists($key)) { + $redis->append($key, $value); + + return $redis->setTimeout($key, $expiration); + } + + return $redis->setex($key, $expiration, $value); + } + + /** + * Removes the specified keys. + * + * @param array $keys + * + * @return Boolean + */ + private function delete(array $keys) + { + return (bool) $this->getRedis()->delete($keys); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/SqliteProfilerStorage.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/SqliteProfilerStorage.php new file mode 100644 index 0000000..0c25bc9 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Profiler/SqliteProfilerStorage.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +/** + * SqliteProfilerStorage stores profiling information in a SQLite database. + * + * @author Fabien Potencier + */ +class SqliteProfilerStorage extends PdoProfilerStorage +{ + /** + * @throws \RuntimeException When neither of SQLite3 or PDO_SQLite extension is enabled + */ + protected function initDb() + { + if (null === $this->db || $this->db instanceof \SQLite3) { + if (0 !== strpos($this->dsn, 'sqlite')) { + throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use Sqlite with an invalid dsn "%s". The expected format is "sqlite:/path/to/the/db/file".', $this->dsn)); + } + if (class_exists('SQLite3')) { + $db = new \SQLite3(substr($this->dsn, 7, strlen($this->dsn)), \SQLITE3_OPEN_READWRITE | \SQLITE3_OPEN_CREATE); + if (method_exists($db, 'busyTimeout')) { + // busyTimeout only exists for PHP >= 5.3.3 + $db->busyTimeout(1000); + } + } elseif (class_exists('PDO') && in_array('sqlite', \PDO::getAvailableDrivers(), true)) { + $db = new \PDO($this->dsn); + } else { + throw new \RuntimeException('You need to enable either the SQLite3 or PDO_SQLite extension for the profiler to run properly.'); + } + + $db->exec('PRAGMA temp_store=MEMORY; PRAGMA journal_mode=MEMORY;'); + $db->exec('CREATE TABLE IF NOT EXISTS sf_profiler_data (token STRING, data STRING, ip STRING, method STRING, url STRING, time INTEGER, parent STRING, created_at INTEGER)'); + $db->exec('CREATE INDEX IF NOT EXISTS data_created_at ON sf_profiler_data (created_at)'); + $db->exec('CREATE INDEX IF NOT EXISTS data_ip ON sf_profiler_data (ip)'); + $db->exec('CREATE INDEX IF NOT EXISTS data_method ON sf_profiler_data (method)'); + $db->exec('CREATE INDEX IF NOT EXISTS data_url ON sf_profiler_data (url)'); + $db->exec('CREATE INDEX IF NOT EXISTS data_parent ON sf_profiler_data (parent)'); + $db->exec('CREATE UNIQUE INDEX IF NOT EXISTS data_token ON sf_profiler_data (token)'); + + $this->db = $db; + } + + return $this->db; + } + + protected function exec($db, $query, array $args = array()) + { + if ($db instanceof \SQLite3) { + $stmt = $this->prepareStatement($db, $query); + foreach ($args as $arg => $val) { + $stmt->bindValue($arg, $val, is_int($val) ? \SQLITE3_INTEGER : \SQLITE3_TEXT); + } + + $res = $stmt->execute(); + if (false === $res) { + throw new \RuntimeException(sprintf('Error executing SQLite query "%s"', $query)); + } + $res->finalize(); + } else { + parent::exec($db, $query, $args); + } + } + + protected function fetch($db, $query, array $args = array()) + { + $return = array(); + + if ($db instanceof \SQLite3) { + $stmt = $this->prepareStatement($db, $query, true); + foreach ($args as $arg => $val) { + $stmt->bindValue($arg, $val, is_int($val) ? \SQLITE3_INTEGER : \SQLITE3_TEXT); + } + $res = $stmt->execute(); + while ($row = $res->fetchArray(\SQLITE3_ASSOC)) { + $return[] = $row; + } + $res->finalize(); + $stmt->close(); + } else { + $return = parent::fetch($db, $query, $args); + } + + return $return; + } + + /** + * {@inheritdoc} + */ + protected function buildCriteria($ip, $url, $start, $end, $limit, $method) + { + $criteria = array(); + $args = array(); + + if ($ip = preg_replace('/[^\d\.]/', '', $ip)) { + $criteria[] = 'ip LIKE :ip'; + $args[':ip'] = '%'.$ip.'%'; + } + + if ($url) { + $criteria[] = 'url LIKE :url ESCAPE "\"'; + $args[':url'] = '%'.addcslashes($url, '%_\\').'%'; + } + + if ($method) { + $criteria[] = 'method = :method'; + $args[':method'] = $method; + } + + if (!empty($start)) { + $criteria[] = 'time >= :start'; + $args[':start'] = $start; + } + + if (!empty($end)) { + $criteria[] = 'time <= :end'; + $args[':end'] = $end; + } + + return array($criteria, $args); + } + + protected function close($db) + { + if ($db instanceof \SQLite3) { + $db->close(); + } + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/README.md b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/README.md new file mode 100644 index 0000000..e0f3c98 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/README.md @@ -0,0 +1,89 @@ +HttpKernel Component +==================== + +HttpKernel provides the building blocks to create flexible and fast HTTP-based +frameworks. + +``HttpKernelInterface`` is the core interface of the Symfony2 full-stack +framework: + + interface HttpKernelInterface + { + /** + * Handles a Request to convert it to a Response. + * + * @param Request $request A Request instance + * + * @return Response A Response instance + */ + function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true); + } + +It takes a ``Request`` as an input and should return a ``Response`` as an +output. Using this interface makes your code compatible with all frameworks +using the Symfony2 components. And this will give you many cool features for +free. + +Creating a framework based on the Symfony2 components is really easy. Here is +a very simple, but fully-featured framework based on the Symfony2 components: + + $routes = new RouteCollection(); + $routes->add('hello', new Route('/hello', array('_controller' => + function (Request $request) { + return new Response(sprintf("Hello %s", $request->get('name'))); + } + ))); + + $request = Request::createFromGlobals(); + + $context = new RequestContext(); + $context->fromRequest($request); + + $matcher = new UrlMatcher($routes, $context); + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new RouterListener($matcher)); + + $resolver = new ControllerResolver(); + + $kernel = new HttpKernel($dispatcher, $resolver); + + $kernel->handle($request)->send(); + +This is all you need to create a flexible framework with the Symfony2 +components. + +Want to add an HTTP reverse proxy and benefit from HTTP caching and Edge Side +Includes? + + $kernel = new HttpKernel($dispatcher, $resolver); + + $kernel = new HttpCache($kernel, new Store(__DIR__.'/cache')); + +Want to functional test this small framework? + + $client = new Client($kernel); + $crawler = $client->request('GET', '/hello/Fabien'); + + $this->assertEquals('Fabien', $crawler->filter('p > span')->text()); + +Want nice error pages instead of ugly PHP exceptions? + + $dispatcher->addSubscriber(new ExceptionListener(function (Request $request) { + $msg = 'Something went wrong! ('.$request->get('exception')->getMessage().')'; + + return new Response($msg, 500); + })); + +And that's why the simple looking ``HttpKernelInterface`` is so powerful. It +gives you access to a lot of cool features, ready to be used out of the box, +with no efforts. + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/HttpKernel/ + $ composer.phar install --dev + $ phpunit diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/TerminableInterface.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/TerminableInterface.php new file mode 100644 index 0000000..fc0e580 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/TerminableInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Terminable extends the Kernel request/response cycle with dispatching a post + * response event after sending the response and before shutting down the kernel. + * + * @author Jordi Boggiano + * @author Pierre Minnieur + * + * @api + */ +interface TerminableInterface +{ + /** + * Terminates a request/response cycle. + * + * Should be called after sending the response and before shutting down the kernel. + * + * @param Request $request A Request instance + * @param Response $response A Response instance + * + * @api + */ + public function terminate(Request $request, Response $response); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Bundle/BundleTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Bundle/BundleTest.php new file mode 100644 index 0000000..8affb5f --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Bundle/BundleTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Bundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\ExtensionPresentBundle; +use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionAbsentBundle\ExtensionAbsentBundle; +use Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\Command\FooCommand; + +class BundleTest extends \PHPUnit_Framework_TestCase +{ + public function testRegisterCommands() + { + if (!class_exists('Symfony\Component\Console\Application')) { + $this->markTestSkipped('The "Console" component is not available'); + } + + if (!interface_exists('Symfony\Component\DependencyInjection\ContainerAwareInterface')) { + $this->markTestSkipped('The "DependencyInjection" component is not available'); + } + + if (!class_exists('Symfony\Component\Finder\Finder')) { + $this->markTestSkipped('The "Finder" component is not available'); + } + + $cmd = new FooCommand(); + $app = $this->getMock('Symfony\Component\Console\Application'); + $app->expects($this->once())->method('add')->with($this->equalTo($cmd)); + + $bundle = new ExtensionPresentBundle(); + $bundle->registerCommands($app); + + $bundle2 = new ExtensionAbsentBundle(); + + $this->assertNull($bundle2->registerCommands($app)); + + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/CacheClearer/ChainCacheClearerTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/CacheClearer/ChainCacheClearerTest.php new file mode 100644 index 0000000..c4816ba --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/CacheClearer/ChainCacheClearerTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\CacheClearer; + +use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface; +use Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer; + +class ChainCacheClearerTest extends \PHPUnit_Framework_TestCase +{ + protected static $cacheDir; + + public static function setUpBeforeClass() + { + self::$cacheDir = tempnam(sys_get_temp_dir(), 'sf2_cache_clearer_dir'); + } + + public static function tearDownAfterClass() + { + @unlink(self::$cacheDir); + } + + public function testInjectClearersInConstructor() + { + $clearer = $this->getMockClearer(); + $clearer + ->expects($this->once()) + ->method('clear'); + + $chainClearer = new ChainCacheClearer(array($clearer)); + $chainClearer->clear(self::$cacheDir); + } + + public function testInjectClearerUsingAdd() + { + $clearer = $this->getMockClearer(); + $clearer + ->expects($this->once()) + ->method('clear'); + + $chainClearer = new ChainCacheClearer(); + $chainClearer->add($clearer); + $chainClearer->clear(self::$cacheDir); + } + + protected function getMockClearer() + { + return $this->getMock('Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface'); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php new file mode 100644 index 0000000..24e7fd9 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/CacheWarmer/CacheWarmerAggregateTest.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\CacheWarmer; + +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerAggregate; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmer; + +class CacheWarmerAggregateTest extends \PHPUnit_Framework_TestCase +{ + protected static $cacheDir; + + public static function setUpBeforeClass() + { + self::$cacheDir = tempnam(sys_get_temp_dir(), 'sf2_cache_warmer_dir'); + } + + public static function tearDownAfterClass() + { + @unlink(self::$cacheDir); + } + + public function testInjectWarmersUsingConstructor() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('warmUp'); + $aggregate = new CacheWarmerAggregate(array($warmer)); + $aggregate->warmUp(self::$cacheDir); + } + + public function testInjectWarmersUsingAdd() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('warmUp'); + $aggregate = new CacheWarmerAggregate(); + $aggregate->add($warmer); + $aggregate->warmUp(self::$cacheDir); + } + + public function testInjectWarmersUsingSetWarmers() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('warmUp'); + $aggregate = new CacheWarmerAggregate(); + $aggregate->setWarmers(array($warmer)); + $aggregate->warmUp(self::$cacheDir); + } + + public function testWarmupDoesCallWarmupOnOptionalWarmersWhenEnableOptionalWarmersIsEnabled() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->never()) + ->method('isOptional'); + $warmer + ->expects($this->once()) + ->method('warmUp'); + + $aggregate = new CacheWarmerAggregate(array($warmer)); + $aggregate->enableOptionalWarmers(); + $aggregate->warmUp(self::$cacheDir); + } + + public function testWarmupDoesNotCallWarmupOnOptionalWarmersWhenEnableOptionalWarmersIsNotEnabled() + { + $warmer = $this->getCacheWarmerMock(); + $warmer + ->expects($this->once()) + ->method('isOptional') + ->will($this->returnValue(true)); + $warmer + ->expects($this->never()) + ->method('warmUp'); + + $aggregate = new CacheWarmerAggregate(array($warmer)); + $aggregate->warmUp(self::$cacheDir); + } + + protected function getCacheWarmerMock() + { + $warmer = $this->getMockBuilder('Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface') + ->disableOriginalConstructor() + ->getMock(); + + return $warmer; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/CacheWarmer/CacheWarmerTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/CacheWarmer/CacheWarmerTest.php new file mode 100644 index 0000000..f0ab05d --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/CacheWarmer/CacheWarmerTest.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\CacheWarmer; + +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmer; + +class CacheWarmerTest extends \PHPUnit_Framework_TestCase +{ + protected static $cacheFile; + + public static function setUpBeforeClass() + { + self::$cacheFile = tempnam(sys_get_temp_dir(), 'sf2_cache_warmer_dir'); + } + + public static function tearDownAfterClass() + { + @unlink(self::$cacheFile); + } + + public function testWriteCacheFileCreatesTheFile() + { + $warmer = new TestCacheWarmer(self::$cacheFile); + $warmer->warmUp(dirname(self::$cacheFile)); + + $this->assertTrue(file_exists(self::$cacheFile)); + } + + /** + * @expectedException \RuntimeException + */ + public function testWriteNonWritableCacheFileThrowsARuntimeException() + { + $nonWritableFile = '/this/file/is/very/probably/not/writable'; + $warmer = new TestCacheWarmer($nonWritableFile); + $warmer->warmUp(dirname($nonWritableFile)); + } +} + +class TestCacheWarmer extends CacheWarmer +{ + protected $file; + + public function __construct($file) + { + $this->file = $file; + } + + public function warmUp($cacheDir) + { + $this->writeCacheFile($this->file, 'content'); + } + + public function isOptional() + { + return false; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/ClientTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/ClientTest.php new file mode 100644 index 0000000..755d7f6 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/ClientTest.php @@ -0,0 +1,180 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use Symfony\Component\HttpKernel\Client; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpKernel\Tests\Fixtures\TestClient; + +class ClientTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\BrowserKit\Client')) { + $this->markTestSkipped('The "BrowserKit" component is not available'); + } + } + + public function testDoRequest() + { + $client = new Client(new TestHttpKernel()); + + $client->request('GET', '/'); + $this->assertEquals('Request: /', $client->getResponse()->getContent(), '->doRequest() uses the request handler to make the request'); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Request', $client->getInternalRequest()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Request', $client->getRequest()); + $this->assertInstanceOf('Symfony\Component\BrowserKit\Response', $client->getInternalResponse()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $client->getResponse()); + + $client->request('GET', 'http://www.example.com/'); + $this->assertEquals('Request: /', $client->getResponse()->getContent(), '->doRequest() uses the request handler to make the request'); + $this->assertEquals('www.example.com', $client->getRequest()->getHost(), '->doRequest() uses the request handler to make the request'); + + $client->request('GET', 'http://www.example.com/?parameter=http://google.com'); + $this->assertEquals('http://www.example.com/?parameter='.urlencode('http://google.com'), $client->getRequest()->getUri(), '->doRequest() uses the request handler to make the request'); + } + + public function testGetScript() + { + if (!class_exists('Symfony\Component\Process\Process')) { + $this->markTestSkipped('The "Process" component is not available'); + } + + if (!class_exists('Symfony\Component\ClassLoader\ClassLoader')) { + $this->markTestSkipped('The "ClassLoader" component is not available'); + } + + $client = new TestClient(new TestHttpKernel()); + $client->insulate(); + $client->request('GET', '/'); + + $this->assertEquals('Request: /', $client->getResponse()->getContent(), '->getScript() returns a script that uses the request handler to make the request'); + } + + public function testFilterResponseConvertsCookies() + { + $client = new Client(new TestHttpKernel()); + + $r = new \ReflectionObject($client); + $m = $r->getMethod('filterResponse'); + $m->setAccessible(true); + + $expected = array( + 'foo=bar; expires=Sun, 15 Feb 2009 20:00:00 GMT; domain=http://example.com; path=/foo; secure; httponly', + 'foo1=bar1; expires=Sun, 15 Feb 2009 20:00:00 GMT; domain=http://example.com; path=/foo; secure; httponly' + ); + + $response = new Response(); + $response->headers->setCookie(new Cookie('foo', 'bar', \DateTime::createFromFormat('j-M-Y H:i:s T', '15-Feb-2009 20:00:00 GMT')->format('U'), '/foo', 'http://example.com', true, true)); + $domResponse = $m->invoke($client, $response); + $this->assertEquals($expected[0], $domResponse->getHeader('Set-Cookie')); + + $response = new Response(); + $response->headers->setCookie(new Cookie('foo', 'bar', \DateTime::createFromFormat('j-M-Y H:i:s T', '15-Feb-2009 20:00:00 GMT')->format('U'), '/foo', 'http://example.com', true, true)); + $response->headers->setCookie(new Cookie('foo1', 'bar1', \DateTime::createFromFormat('j-M-Y H:i:s T', '15-Feb-2009 20:00:00 GMT')->format('U'), '/foo', 'http://example.com', true, true)); + $domResponse = $m->invoke($client, $response); + $this->assertEquals($expected[0], $domResponse->getHeader('Set-Cookie')); + $this->assertEquals($expected, $domResponse->getHeader('Set-Cookie', false)); + } + + public function testFilterResponseSupportsStreamedResponses() + { + $client = new Client(new TestHttpKernel()); + + $r = new \ReflectionObject($client); + $m = $r->getMethod('filterResponse'); + $m->setAccessible(true); + + $response = new StreamedResponse(function () { + echo 'foo'; + }); + + $domResponse = $m->invoke($client, $response); + $this->assertEquals('foo', $domResponse->getContent()); + } + + public function testUploadedFile() + { + $source = tempnam(sys_get_temp_dir(), 'source'); + $target = sys_get_temp_dir().'/sf.moved.file'; + @unlink($target); + + $kernel = new TestHttpKernel(); + $client = new Client($kernel); + + $files = array( + array('tmp_name' => $source, 'name' => 'original', 'type' => 'mime/original', 'size' => 123, 'error' => UPLOAD_ERR_OK), + new UploadedFile($source, 'original', 'mime/original', 123, UPLOAD_ERR_OK, true), + ); + + foreach ($files as $file) { + $client->request('POST', '/', array(), array('foo' => $file)); + + $files = $client->getRequest()->files->all(); + + $this->assertCount(1, $files); + + $file = $files['foo']; + + $this->assertEquals('original', $file->getClientOriginalName()); + $this->assertEquals('mime/original', $file->getClientMimeType()); + $this->assertEquals('123', $file->getClientSize()); + $this->assertTrue($file->isValid()); + } + + $file->move(dirname($target), basename($target)); + + $this->assertFileExists($target); + unlink($target); + } + + public function testUploadedFileWhenSizeExceedsUploadMaxFileSize() + { + $source = tempnam(sys_get_temp_dir(), 'source'); + + $kernel = new TestHttpKernel(); + $client = new Client($kernel); + + $file = $this + ->getMockBuilder('Symfony\Component\HttpFoundation\File\UploadedFile') + ->setConstructorArgs(array($source, 'original', 'mime/original', 123, UPLOAD_ERR_OK, true)) + ->setMethods(array('getSize')) + ->getMock() + ; + + $file->expects($this->once()) + ->method('getSize') + ->will($this->returnValue(INF)) + ; + + $client->request('POST', '/', array(), array($file)); + + $files = $client->getRequest()->files->all(); + + $this->assertCount(1, $files); + + $file = $files[0]; + + $this->assertFalse($file->isValid()); + $this->assertEquals(UPLOAD_ERR_INI_SIZE, $file->getError()); + $this->assertEquals('mime/original', $file->getClientMimeType()); + $this->assertEquals('original', $file->getClientOriginalName()); + $this->assertEquals(0, $file->getClientSize()); + + unlink($source); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Config/FileLocatorTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Config/FileLocatorTest.php new file mode 100644 index 0000000..be59486 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Config/FileLocatorTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Config; + +use Symfony\Component\HttpKernel\Config\FileLocator; + +class FileLocatorTest extends \PHPUnit_Framework_TestCase +{ + public function testLocate() + { + $kernel = $this->getMock('Symfony\Component\HttpKernel\KernelInterface'); + $kernel + ->expects($this->atLeastOnce()) + ->method('locateResource') + ->with('@BundleName/some/path', null, true) + ->will($this->returnValue('/bundle-name/some/path')); + $locator = new FileLocator($kernel); + $this->assertEquals('/bundle-name/some/path', $locator->locate('@BundleName/some/path')); + + $kernel + ->expects($this->never()) + ->method('locateResource'); + $this->setExpectedException('LogicException'); + $locator->locate('/some/path'); + } + + public function testLocateWithGlobalResourcePath() + { + $kernel = $this->getMock('Symfony\Component\HttpKernel\KernelInterface'); + $kernel + ->expects($this->atLeastOnce()) + ->method('locateResource') + ->with('@BundleName/some/path', '/global/resource/path', false); + + $locator = new FileLocator($kernel, '/global/resource/path'); + $locator->locate('@BundleName/some/path', null, false); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php new file mode 100644 index 0000000..aa401f4 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php @@ -0,0 +1,190 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use Symfony\Component\HttpKernel\Controller\ControllerResolver; +use Symfony\Component\HttpFoundation\Request; + +class ControllerResolverTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + public function testGetController() + { + $logger = new Logger(); + $resolver = new ControllerResolver($logger); + + $request = Request::create('/'); + $this->assertFalse($resolver->getController($request), '->getController() returns false when the request has no _controller attribute'); + $this->assertEquals(array('Unable to look for the controller as the "_controller" parameter is missing'), $logger->getLogs('warning')); + + $request->attributes->set('_controller', 'Symfony\Component\HttpKernel\Tests\ControllerResolverTest::testGetController'); + $controller = $resolver->getController($request); + $this->assertInstanceOf('Symfony\Component\HttpKernel\Tests\ControllerResolverTest', $controller[0], '->getController() returns a PHP callable'); + + $request->attributes->set('_controller', $lambda = function () {}); + $controller = $resolver->getController($request); + $this->assertSame($lambda, $controller); + + $request->attributes->set('_controller', $this); + $controller = $resolver->getController($request); + $this->assertSame($this, $controller); + + $request->attributes->set('_controller', 'Symfony\Component\HttpKernel\Tests\ControllerResolverTest'); + $controller = $resolver->getController($request); + $this->assertInstanceOf('Symfony\Component\HttpKernel\Tests\ControllerResolverTest', $controller); + + $request->attributes->set('_controller', array($this, 'controllerMethod1')); + $controller = $resolver->getController($request); + $this->assertSame(array($this, 'controllerMethod1'), $controller); + + $request->attributes->set('_controller', array('Symfony\Component\HttpKernel\Tests\ControllerResolverTest', 'controllerMethod4')); + $controller = $resolver->getController($request); + $this->assertSame(array('Symfony\Component\HttpKernel\Tests\ControllerResolverTest', 'controllerMethod4'), $controller); + + $request->attributes->set('_controller', 'Symfony\Component\HttpKernel\Tests\some_controller_function'); + $controller = $resolver->getController($request); + $this->assertSame('Symfony\Component\HttpKernel\Tests\some_controller_function', $controller); + + $request->attributes->set('_controller', 'foo'); + try { + $resolver->getController($request); + $this->fail('->getController() throws an \InvalidArgumentException if the _controller attribute is not well-formatted'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->getController() throws an \InvalidArgumentException if the _controller attribute is not well-formatted'); + } + + $request->attributes->set('_controller', 'foo::bar'); + try { + $resolver->getController($request); + $this->fail('->getController() throws an \InvalidArgumentException if the _controller attribute contains a non-existent class'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->getController() throws an \InvalidArgumentException if the _controller attribute contains a non-existent class'); + } + + $request->attributes->set('_controller', 'Symfony\Component\HttpKernel\Tests\ControllerResolverTest::bar'); + try { + $resolver->getController($request); + $this->fail('->getController() throws an \InvalidArgumentException if the _controller attribute contains a non-existent method'); + } catch (\Exception $e) { + $this->assertInstanceOf('\InvalidArgumentException', $e, '->getController() throws an \InvalidArgumentException if the _controller attribute contains a non-existent method'); + } + } + + public function testGetArguments() + { + $resolver = new ControllerResolver(); + + $request = Request::create('/'); + $controller = array(new self(), 'testGetArguments'); + $this->assertEquals(array(), $resolver->getArguments($request, $controller), '->getArguments() returns an empty array if the method takes no arguments'); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = array(new self(), 'controllerMethod1'); + $this->assertEquals(array('foo'), $resolver->getArguments($request, $controller), '->getArguments() returns an array of arguments for the controller method'); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = array(new self(), 'controllerMethod2'); + $this->assertEquals(array('foo', null), $resolver->getArguments($request, $controller), '->getArguments() uses default values if present'); + + $request->attributes->set('bar', 'bar'); + $this->assertEquals(array('foo', 'bar'), $resolver->getArguments($request, $controller), '->getArguments() overrides default values if provided in the request attributes'); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = function ($foo) {}; + $this->assertEquals(array('foo'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = function ($foo, $bar = 'bar') {}; + $this->assertEquals(array('foo', 'bar'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $controller = new self(); + $this->assertEquals(array('foo', null), $resolver->getArguments($request, $controller)); + $request->attributes->set('bar', 'bar'); + $this->assertEquals(array('foo', 'bar'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('foobar', 'foobar'); + $controller = 'Symfony\Component\HttpKernel\Tests\some_controller_function'; + $this->assertEquals(array('foo', 'foobar'), $resolver->getArguments($request, $controller)); + + $request = Request::create('/'); + $request->attributes->set('foo', 'foo'); + $request->attributes->set('foobar', 'foobar'); + $controller = array(new self(), 'controllerMethod3'); + + if (version_compare(PHP_VERSION, '5.3.16', '==')) { + $this->markTestSkipped('PHP 5.3.16 has a major bug in the Reflection sub-system'); + } else { + try { + $resolver->getArguments($request, $controller); + $this->fail('->getArguments() throws a \RuntimeException exception if it cannot determine the argument value'); + } catch (\Exception $e) { + $this->assertInstanceOf('\RuntimeException', $e, '->getArguments() throws a \RuntimeException exception if it cannot determine the argument value'); + } + } + + $request = Request::create('/'); + $controller = array(new self(), 'controllerMethod5'); + $this->assertEquals(array($request), $resolver->getArguments($request, $controller), '->getArguments() injects the request'); + } + + public function testCreateControllerCanReturnAnyCallable() + { + $mock = $this->getMock('Symfony\Component\HttpKernel\Controller\ControllerResolver', array('createController')); + $mock->expects($this->once())->method('createController')->will($this->returnValue('Symfony\Component\HttpKernel\Tests\some_controller_function')); + + $request = Request::create('/'); + $request->attributes->set('_controller', 'foobar'); + $mock->getController($request); + } + + public function __invoke($foo, $bar = null) + { + } + + protected function controllerMethod1($foo) + { + } + + protected function controllerMethod2($foo, $bar = null) + { + } + + protected function controllerMethod3($foo, $bar = null, $foobar) + { + } + + protected static function controllerMethod4() + { + } + + protected function controllerMethod5(Request $request) + { + } +} + +function some_controller_function($foo, $foobar) +{ +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DataCollector/ConfigDataCollectorTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DataCollector/ConfigDataCollectorTest.php new file mode 100644 index 0000000..192c808 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DataCollector/ConfigDataCollectorTest.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\ConfigDataCollector; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class ConfigDataCollectorTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + public function testCollect() + { + $kernel = new KernelForTest('test', true); + $c = new ConfigDataCollector(); + $c->setKernel($kernel); + $c->collect(new Request(), new Response()); + + $this->assertSame('test',$c->getEnv()); + $this->assertTrue($c->isDebug()); + $this->assertSame('config',$c->getName()); + $this->assertSame('testkernel',$c->getAppName()); + $this->assertSame(PHP_VERSION,$c->getPhpVersion()); + $this->assertSame(Kernel::VERSION,$c->getSymfonyVersion()); + $this->assertNull($c->getToken()); + + // if else clause because we don't know it + if (extension_loaded('xdebug')) { + $this->assertTrue($c->hasXdebug()); + } else { + $this->assertFalse($c->hasXdebug()); + } + + // if else clause because we don't know it + if (((extension_loaded('eaccelerator') && ini_get('eaccelerator.enable')) + || + (extension_loaded('apc') && ini_get('apc.enabled')) + || + (extension_loaded('Zend OPcache') && ini_get('opcache.enable')) + || + (extension_loaded('xcache') && ini_get('xcache.cacher')) + || + (extension_loaded('wincache') && ini_get('wincache.ocenabled')))) { + $this->assertTrue($c->hasAccelerator()); + } else { + $this->assertFalse($c->hasAccelerator()); + } + } +} + +class KernelForTest extends Kernel +{ + public function getName() + { + return 'testkernel'; + } + + public function registerBundles() + { + } + + public function init() + { + } + + public function getBundles() + { + return array(); + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DataCollector/ExceptionDataCollectorTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DataCollector/ExceptionDataCollectorTest.php new file mode 100644 index 0000000..54a8aab --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DataCollector/ExceptionDataCollectorTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector; +use Symfony\Component\HttpKernel\Exception\FlattenException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class ExceptionDataCollectorTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + public function testCollect() + { + $e = new \Exception('foo',500); + $c = new ExceptionDataCollector(); + $flattened = FlattenException::create($e); + $trace = $flattened->getTrace(); + + $this->assertFalse($c->hasException()); + + $c->collect(new Request(), new Response(),$e); + + $this->assertTrue($c->hasException()); + $this->assertEquals($flattened,$c->getException()); + $this->assertSame('foo',$c->getMessage()); + $this->assertSame(500,$c->getCode()); + $this->assertSame('exception',$c->getName()); + $this->assertSame($trace,$c->getTrace()); + } + +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php new file mode 100644 index 0000000..ea82d9d --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector; +use Symfony\Component\HttpKernel\Debug\ErrorHandler; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class LoggerDataCollectorTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + /** + * @dataProvider getCollectTestData + */ + public function testCollect($nb, $logs, $expectedLogs, $expectedDeprecationCount) + { + $logger = $this->getMock('Symfony\Component\HttpKernel\Log\DebugLoggerInterface'); + $logger->expects($this->once())->method('countErrors')->will($this->returnValue($nb)); + $logger->expects($this->exactly(2))->method('getLogs')->will($this->returnValue($logs)); + + $c = new LoggerDataCollector($logger); + $c->collect(new Request(), new Response()); + + $this->assertSame('logger', $c->getName()); + $this->assertSame($nb, $c->countErrors()); + $this->assertSame($expectedLogs ? $expectedLogs : $logs, $c->getLogs()); + $this->assertSame($expectedDeprecationCount, $c->countDeprecations()); + } + + public function getCollectTestData() + { + return array( + array( + 1, + array(array('message' => 'foo', 'context' => array())), + null, + 0 + ), + array( + 1, + array(array('message' => 'foo', 'context' => array('foo' => fopen(__FILE__, 'r')))), + array(array('message' => 'foo', 'context' => array('foo' => 'Resource(stream)'))), + 0 + ), + array( + 1, + array(array('message' => 'foo', 'context' => array('foo' => new \stdClass()))), + array(array('message' => 'foo', 'context' => array('foo' => 'Object(stdClass)'))), + 0 + ), + array( + 1, + array( + array('message' => 'foo', 'context' => array('type' => ErrorHandler::TYPE_DEPRECATION)), + array('message' => 'foo2', 'context' => array('type' => ErrorHandler::TYPE_DEPRECATION)) + ), + null, + 2 + ), + ); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DataCollector/MemoryDataCollectorTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DataCollector/MemoryDataCollectorTest.php new file mode 100644 index 0000000..607a580 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DataCollector/MemoryDataCollectorTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\MemoryDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class MemoryDataCollectorTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + public function testCollect() + { + $collector = new MemoryDataCollector(); + $collector->collect(new Request(), new Response()); + + $this->assertInternalType('integer', $collector->getMemory()); + $this->assertInternalType('integer', $collector->getMemoryLimit()); + $this->assertSame('memory', $collector->getName()); + } + + /** @dataProvider getBytesConversionTestData */ + public function testBytesConversion($limit, $bytes) + { + $collector = new MemoryDataCollector(); + $method = new \ReflectionMethod($collector, 'convertToBytes'); + $method->setAccessible(true); + $this->assertEquals($bytes, $method->invoke($collector, $limit)); + } + + public function getBytesConversionTestData() + { + return array( + array('2k', 2048), + array('2 k', 2048), + array('8m', 8 * 1024 * 1024), + array('+2 k', 2048), + array('+2???k', 2048), + array('0x10', 16), + array('0xf', 15), + array('010', 8), + array('+0x10 k', 16 * 1024), + array('1g', 1024 * 1024 * 1024), + array('-1', -1), + array('0', 0), + array('2mk', 2048), // the unit must be the last char, so in this case 'k', not 'm' + ); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php new file mode 100644 index 0000000..8c14604 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class RequestDataCollectorTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + /** + * @dataProvider provider + */ + public function testCollect(Request $request, Response $response) + { + $c = new RequestDataCollector(); + + $c->collect($request, $response); + + $this->assertSame('request',$c->getName()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\HeaderBag',$c->getRequestHeaders()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag',$c->getRequestServer()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag',$c->getRequestCookies()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag',$c->getRequestAttributes()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag',$c->getRequestRequest()); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag',$c->getRequestQuery()); + $this->assertEquals('html',$c->getFormat()); + $this->assertEquals(array(),$c->getSessionAttributes()); + $this->assertEquals('en',$c->getLocale()); + + $this->assertInstanceOf('Symfony\Component\HttpFoundation\HeaderBag',$c->getResponseHeaders()); + $this->assertEquals('OK',$c->getStatusText()); + $this->assertEquals(200,$c->getStatusCode()); + $this->assertEquals('application/json',$c->getContentType()); + } + + /** + * Test various types of controller callables. + * + * @dataProvider provider + */ + public function testControllerInspection(Request $request, Response $response) + { + // make sure we always match the line number + $r1 = new \ReflectionMethod($this, 'testControllerInspection'); + $r2 = new \ReflectionMethod($this, 'staticControllerMethod'); + // test name, callable, expected + $controllerTests = array( + array( + '"Regular" callable', + array($this, 'testControllerInspection'), + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'testControllerInspection', + 'file' => __FILE__, + 'line' => $r1->getStartLine() + ), + ), + + array( + 'Closure', + function() { return 'foo'; }, + array( + 'class' => __NAMESPACE__.'\{closure}', + 'method' => null, + 'file' => __FILE__, + 'line' => __LINE__ - 5, + ), + ), + + array( + 'Static callback as string', + 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest::staticControllerMethod', + 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest::staticControllerMethod', + ), + + array( + 'Static callable with instance', + array($this, 'staticControllerMethod'), + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'staticControllerMethod', + 'file' => __FILE__, + 'line' => $r2->getStartLine() + ), + ), + + array( + 'Static callable with class name', + array('Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', 'staticControllerMethod'), + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'staticControllerMethod', + 'file' => __FILE__, + 'line' => $r2->getStartLine() + ), + ), + + array( + 'Callable with instance depending on __call()', + array($this, 'magicMethod'), + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'magicMethod', + 'file' => 'n/a', + 'line' => 'n/a' + ), + ), + + array( + 'Callable with class name depending on __callStatic()', + array('Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', 'magicMethod'), + array( + 'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest', + 'method' => 'magicMethod', + 'file' => 'n/a', + 'line' => 'n/a' + ), + ), + ); + + $c = new RequestDataCollector(); + + foreach ($controllerTests as $controllerTest) { + $this->injectController($c, $controllerTest[1], $request); + $c->collect($request, $response); + $this->assertEquals($controllerTest[2], $c->getController(), sprintf('Testing: %s', $controllerTest[0])); + } + } + + public function provider() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + return array(array(null, null)); + } + + $request = Request::create('http://test.com/foo?bar=baz'); + $request->attributes->set('foo', 'bar'); + + $response = new Response(); + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'application/json'); + $response->headers->setCookie(new Cookie('foo','bar',1,'/foo','localhost',true,true)); + $response->headers->setCookie(new Cookie('bar','foo',new \DateTime('@946684800'))); + $response->headers->setCookie(new Cookie('bazz','foo','2000-12-12')); + + return array( + array($request, $response) + ); + } + + /** + * Inject the given controller callable into the data collector. + */ + protected function injectController($collector, $controller, $request) + { + $resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface'); + $httpKernel = new HttpKernel(new EventDispatcher(), $resolver); + $event = new FilterControllerEvent($httpKernel, $controller, $request, HttpKernelInterface::MASTER_REQUEST); + $collector->onKernelController($event); + } + + /** + * Dummy method used as controller callable + */ + public static function staticControllerMethod() + { + throw new \LogicException('Unexpected method call'); + } + + /** + * Magic method to allow non existing methods to be called and delegated. + */ + public function __call($method, $args) + { + throw new \LogicException('Unexpected method call'); + } + + /** + * Magic method to allow non existing methods to be called and delegated. + */ + public static function __callStatic($method, $args) + { + throw new \LogicException('Unexpected method call'); + } + +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DataCollector/TimeDataCollectorTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DataCollector/TimeDataCollectorTest.php new file mode 100644 index 0000000..13abb67 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DataCollector/TimeDataCollectorTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\TimeDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class TimeDataCollectorTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + public function testCollect() + { + $c = new TimeDataCollector; + + $request = new Request(); + $request->server->set('REQUEST_TIME', 1); + + $c->collect($request, new Response()); + + $this->assertEquals(1000, $c->getStartTime()); + + $request->server->set('REQUEST_TIME_FLOAT', 2); + + $c->collect($request, new Response()); + + $this->assertEquals(2000, $c->getStartTime()); + + $request = new Request(); + $c->collect($request, new Response); + $this->assertEquals(0, $c->getStartTime()); + + $kernel = $this->getMock('Symfony\Component\HttpKernel\KernelInterface'); + $kernel->expects($this->once())->method('getStartTime')->will($this->returnValue(123456)); + + $c = new TimeDataCollector($kernel); + $request = new Request(); + $request->server->set('REQUEST_TIME', 1); + + $c->collect($request, new Response()); + $this->assertEquals(123456000, $c->getStartTime()); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php new file mode 100644 index 0000000..a5f507c --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Debug/TraceableEventDispatcherTest.php @@ -0,0 +1,237 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Debug; + +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Stopwatch\Stopwatch; + +class TraceableEventDispatcherTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { + $this->markTestSkipped('The "EventDispatcher" component is not available'); + } + + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + public function testAddRemoveListener() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $tdispatcher->addListener('foo', $listener = function () { ; }); + $listeners = $dispatcher->getListeners('foo'); + $this->assertCount(1, $listeners); + $this->assertSame($listener, $listeners[0]); + + $tdispatcher->removeListener('foo', $listener); + $this->assertCount(0, $dispatcher->getListeners('foo')); + } + + public function testGetListeners() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $tdispatcher->addListener('foo', $listener = function () { ; }); + $this->assertSame($dispatcher->getListeners('foo'), $tdispatcher->getListeners('foo')); + } + + public function testHasListeners() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $this->assertFalse($dispatcher->hasListeners('foo')); + $this->assertFalse($tdispatcher->hasListeners('foo')); + + $tdispatcher->addListener('foo', $listener = function () { ; }); + $this->assertTrue($dispatcher->hasListeners('foo')); + $this->assertTrue($tdispatcher->hasListeners('foo')); + } + + public function testAddRemoveSubscriber() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + + $subscriber = new EventSubscriber(); + + $tdispatcher->addSubscriber($subscriber); + $listeners = $dispatcher->getListeners('foo'); + $this->assertCount(1, $listeners); + $this->assertSame(array($subscriber, 'call'), $listeners[0]); + + $tdispatcher->removeSubscriber($subscriber); + $this->assertCount(0, $dispatcher->getListeners('foo')); + } + + public function testGetCalledListeners() + { + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + $tdispatcher->addListener('foo', $listener = function () { ; }); + + $this->assertEquals(array(), $tdispatcher->getCalledListeners()); + $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'type' => 'Closure', 'pretty' => 'closure')), $tdispatcher->getNotCalledListeners()); + + $tdispatcher->dispatch('foo'); + + $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'type' => 'Closure', 'pretty' => 'closure')), $tdispatcher->getCalledListeners()); + $this->assertEquals(array(), $tdispatcher->getNotCalledListeners()); + } + + public function testLogger() + { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger); + $tdispatcher->addListener('foo', $listener1 = function () { ; }); + $tdispatcher->addListener('foo', $listener2 = function () { ; }); + + $logger->expects($this->at(0))->method('debug')->with("Notified event \"foo\" to listener \"closure\"."); + $logger->expects($this->at(1))->method('debug')->with("Notified event \"foo\" to listener \"closure\"."); + + $tdispatcher->dispatch('foo'); + } + + public function testLoggerWithStoppedEvent() + { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch(), $logger); + $tdispatcher->addListener('foo', $listener1 = function (Event $event) { $event->stopPropagation(); }); + $tdispatcher->addListener('foo', $listener2 = function () { ; }); + + $logger->expects($this->at(0))->method('debug')->with("Notified event \"foo\" to listener \"closure\"."); + $logger->expects($this->at(1))->method('debug')->with("Listener \"closure\" stopped propagation of the event \"foo\"."); + $logger->expects($this->at(2))->method('debug')->with("Listener \"closure\" was not called for event \"foo\"."); + + $tdispatcher->dispatch('foo'); + } + + public function testDispatchCallListeners() + { + $called = array(); + + $dispatcher = new EventDispatcher(); + $tdispatcher = new TraceableEventDispatcher($dispatcher, new Stopwatch()); + $tdispatcher->addListener('foo', $listener1 = function () use (&$called) { $called[] = 'foo1'; }); + $tdispatcher->addListener('foo', $listener2 = function () use (&$called) { $called[] = 'foo2'; }); + + $tdispatcher->dispatch('foo'); + + $this->assertEquals(array('foo1', 'foo2'), $called); + } + + public function testDispatchNested() + { + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $loop = 1; + $dispatcher->addListener('foo', $listener1 = function () use ($dispatcher, &$loop) { + ++$loop; + if (2 == $loop) { + $dispatcher->dispatch('foo'); + } + }); + + $dispatcher->dispatch('foo'); + } + + public function testStopwatchSections() + { + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), $stopwatch = new Stopwatch()); + $kernel = $this->getHttpKernel($dispatcher, function () { return new Response(); }); + $request = Request::create('/'); + $response = $kernel->handle($request); + $kernel->terminate($request, $response); + + $events = $stopwatch->getSectionEvents($response->headers->get('X-Debug-Token')); + $this->assertEquals(array( + '__section__', + 'kernel.request', + 'kernel.request.loading', + 'kernel.controller', + 'kernel.controller.loading', + 'controller', + 'kernel.response', + 'kernel.response.loading', + 'kernel.terminate', + 'kernel.terminate.loading', + ), array_keys($events)); + } + + public function testStopwatchCheckControllerOnRequestEvent() + { + $stopwatch = $this->getMockBuilder('Symfony\Component\Stopwatch\Stopwatch') + ->setMethods(array('isStarted')) + ->getMock(); + $stopwatch->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(false)); + + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), $stopwatch); + + $kernel = $this->getHttpKernel($dispatcher, function () { return new Response(); }); + $request = Request::create('/'); + $kernel->handle($request); + } + + public function testStopwatchStopControllerOnRequestEvent() + { + $stopwatch = $this->getMockBuilder('Symfony\Component\Stopwatch\Stopwatch') + ->setMethods(array('isStarted', 'stop', 'stopSection')) + ->getMock(); + $stopwatch->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(true)); + $stopwatch->expects($this->once()) + ->method('stop'); + $stopwatch->expects($this->once()) + ->method('stopSection'); + + $dispatcher = new TraceableEventDispatcher(new EventDispatcher(), $stopwatch); + + $kernel = $this->getHttpKernel($dispatcher, function () { return new Response(); }); + $request = Request::create('/'); + $kernel->handle($request); + } + + protected function getHttpKernel($dispatcher, $controller) + { + $resolver = $this->getMock('Symfony\Component\HttpKernel\Controller\ControllerResolverInterface'); + $resolver->expects($this->once())->method('getController')->will($this->returnValue($controller)); + $resolver->expects($this->once())->method('getArguments')->will($this->returnValue(array())); + + return new HttpKernel($dispatcher, $resolver); + } +} + +class EventSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents() + { + return array('foo' => 'call'); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DependencyInjection/ContainerAwareHttpKernelTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DependencyInjection/ContainerAwareHttpKernelTest.php new file mode 100644 index 0000000..61372cd --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DependencyInjection/ContainerAwareHttpKernelTest.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class ContainerAwareHttpKernelTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\DependencyInjection\Container')) { + $this->markTestSkipped('The "DependencyInjection" component is not available'); + } + + if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { + $this->markTestSkipped('The "EventDispatcher" component is not available'); + } + + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + /** + * @dataProvider getProviderTypes + */ + public function testHandle($type) + { + $request = new Request(); + $expected = new Response(); + + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $container + ->expects($this->once()) + ->method('enterScope') + ->with($this->equalTo('request')) + ; + $container + ->expects($this->once()) + ->method('leaveScope') + ->with($this->equalTo('request')) + ; + $container + ->expects($this->at(0)) + ->method('hasScope') + ->with($this->equalTo('request')) + ->will($this->returnValue(false)); + $container + ->expects($this->at(1)) + ->method('addScope') + ->with($this->isInstanceOf('Symfony\Component\DependencyInjection\Scope')); + // enterScope() + $container + ->expects($this->at(3)) + ->method('set') + ->with($this->equalTo('request'), $this->equalTo($request), $this->equalTo('request')) + ; + $container + ->expects($this->at(4)) + ->method('set') + ->with($this->equalTo('request'), $this->equalTo(null), $this->equalTo('request')) + ; + + $dispatcher = new EventDispatcher(); + $resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface'); + $kernel = new ContainerAwareHttpKernel($dispatcher, $container, $resolver); + + $controller = function() use ($expected) { + return $expected; + }; + + $resolver->expects($this->once()) + ->method('getController') + ->with($request) + ->will($this->returnValue($controller)); + $resolver->expects($this->once()) + ->method('getArguments') + ->with($request, $controller) + ->will($this->returnValue(array())); + + $actual = $kernel->handle($request, $type); + + $this->assertSame($expected, $actual, '->handle() returns the response'); + } + + /** + * @dataProvider getProviderTypes + */ + public function testHandleRestoresThePreviousRequestOnException($type) + { + $request = new Request(); + $expected = new \Exception(); + + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $container + ->expects($this->once()) + ->method('enterScope') + ->with($this->equalTo('request')) + ; + $container + ->expects($this->once()) + ->method('leaveScope') + ->with($this->equalTo('request')) + ; + $container + ->expects($this->at(0)) + ->method('hasScope') + ->with($this->equalTo('request')) + ->will($this->returnValue(true)); + // enterScope() + $container + ->expects($this->at(2)) + ->method('set') + ->with($this->equalTo('request'), $this->equalTo($request), $this->equalTo('request')) + ; + $container + ->expects($this->at(3)) + ->method('set') + ->with($this->equalTo('request'), $this->equalTo(null), $this->equalTo('request')) + ; + + $dispatcher = new EventDispatcher(); + $resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface'); + $kernel = new ContainerAwareHttpKernel($dispatcher, $container, $resolver); + + $controller = function() use ($expected) { + throw $expected; + }; + + $resolver->expects($this->once()) + ->method('getController') + ->with($request) + ->will($this->returnValue($controller)); + $resolver->expects($this->once()) + ->method('getArguments') + ->with($request, $controller) + ->will($this->returnValue(array())); + + try { + $kernel->handle($request, $type); + $this->fail('->handle() suppresses the controller exception'); + } catch (\PHPUnit_Framework_Exception $exception) { + throw $exception; + } catch (\Exception $actual) { + $this->assertSame($expected, $actual, '->handle() throws the controller exception'); + } + } + + public function getProviderTypes() + { + return array( + array(HttpKernelInterface::MASTER_REQUEST), + array(HttpKernelInterface::SUB_REQUEST), + ); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php new file mode 100644 index 0000000..0800758 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; + +class MergeExtensionConfigurationPassTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\DependencyInjection\Container')) { + $this->markTestSkipped('The "DependencyInjection" component is not available'); + } + + if (!class_exists('Symfony\Component\Config\FileLocator')) { + $this->markTestSkipped('The "Config" component is not available'); + } + } + + public function testAutoloadMainExtension() + { + $container = $this->getMock('Symfony\\Component\\DependencyInjection\\ContainerBuilder'); + $params = $this->getMock('Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBag'); + + $container->expects($this->at(0)) + ->method('getExtensionConfig') + ->with('loaded') + ->will($this->returnValue(array(array()))); + $container->expects($this->at(1)) + ->method('getExtensionConfig') + ->with('notloaded') + ->will($this->returnValue(array())); + $container->expects($this->once()) + ->method('loadFromExtension') + ->with('notloaded', array()); + + $container->expects($this->any()) + ->method('getParameterBag') + ->will($this->returnValue($params)); + $params->expects($this->any()) + ->method('all') + ->will($this->returnValue(array())); + $container->expects($this->any()) + ->method('getDefinitions') + ->will($this->returnValue(array())); + $container->expects($this->any()) + ->method('getAliases') + ->will($this->returnValue(array())); + $container->expects($this->any()) + ->method('getExtensions') + ->will($this->returnValue(array())); + + $configPass = new MergeExtensionConfigurationPass(array('loaded', 'notloaded')); + $configPass->process($container); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterListenersPassTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterListenersPassTest.php new file mode 100644 index 0000000..d1e825a --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/DependencyInjection/RegisterListenersPassTest.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\HttpKernel\DependencyInjection\RegisterListenersPass; + +class RegisterListenersPassTest extends \PHPUnit_Framework_TestCase +{ + /** + * Tests that event subscribers not implementing EventSubscriberInterface + * trigger an exception. + * + * @expectedException \InvalidArgumentException + */ + public function testEventSubscriberWithoutInterface() + { + // one service, not implementing any interface + $services = array( + 'my_event_subscriber' => array(0 => array()), + ); + + $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('stdClass')); + + $builder = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder'); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.event_listener here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->onConsecutiveCalls(array(), $services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->returnValue($definition)); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($builder); + } + + public function testValidEventSubscriber() + { + $services = array( + 'my_event_subscriber' => array(0 => array()), + ); + + $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $definition->expects($this->atLeastOnce()) + ->method('getClass') + ->will($this->returnValue('Symfony\Component\HttpKernel\Tests\DependencyInjection\SubscriberService')); + + $builder = $this->getMock('Symfony\Component\DependencyInjection\ContainerBuilder'); + $builder->expects($this->any()) + ->method('hasDefinition') + ->will($this->returnValue(true)); + + // We don't test kernel.event_listener here + $builder->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->onConsecutiveCalls(array(), $services)); + + $builder->expects($this->atLeastOnce()) + ->method('getDefinition') + ->will($this->returnValue($definition)); + + $registerListenersPass = new RegisterListenersPass(); + $registerListenersPass->process($builder); + } +} + +class SubscriberService implements \Symfony\Component\EventDispatcher\EventSubscriberInterface +{ + public static function getSubscribedEvents() {} +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/EventListener/EsiListenerTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/EventListener/EsiListenerTest.php new file mode 100644 index 0000000..2eddc57 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/EventListener/EsiListenerTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpKernel\EventListener\EsiListener; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class EsiListenerTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { + $this->markTestSkipped('The "EventDispatcher" component is not available'); + } + } + + public function testFilterDoesNothingForSubRequests() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $response = new Response('foo '); + $listener = new EsiListener(new Esi()); + + $dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse')); + $event = new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::SUB_REQUEST, $response); + $dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('', $event->getResponse()->headers->get('Surrogate-Control')); + } + + public function testFilterWhenThereIsSomeEsiIncludes() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $response = new Response('foo '); + $listener = new EsiListener(new Esi()); + + $dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse')); + $event = new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::MASTER_REQUEST, $response); + $dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('content="ESI/1.0"', $event->getResponse()->headers->get('Surrogate-Control')); + } + + public function testFilterWhenThereIsNoEsiIncludes() + { + $dispatcher = new EventDispatcher(); + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $response = new Response('foo'); + $listener = new EsiListener(new Esi()); + + $dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse')); + $event = new FilterResponseEvent($kernel, new Request(), HttpKernelInterface::MASTER_REQUEST, $response); + $dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('', $event->getResponse()->headers->get('Surrogate-Control')); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/EventListener/ExceptionListenerTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/EventListener/ExceptionListenerTest.php new file mode 100644 index 0000000..c6a0128 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/EventListener/ExceptionListenerTest.php @@ -0,0 +1,157 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\EventListener\ExceptionListener; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Tests\Logger; + +/** + * ExceptionListenerTest + * + * @author Robert Schönthal + */ +class ExceptionListenerTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { + $this->markTestSkipped('The "EventDispatcher" component is not available'); + } + + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + public function testConstruct() + { + $logger = new TestLogger(); + $l = new ExceptionListener('foo', $logger); + + $_logger = new \ReflectionProperty(get_class($l), 'logger'); + $_logger->setAccessible(true); + $_controller = new \ReflectionProperty(get_class($l), 'controller'); + $_controller->setAccessible(true); + + $this->assertSame($logger, $_logger->getValue($l)); + $this->assertSame('foo', $_controller->getValue($l)); + } + + /** + * @dataProvider provider + */ + public function testHandleWithoutLogger($event, $event2) + { + // store the current error_log, and disable it temporarily + $errorLog = ini_set('error_log', file_exists('/dev/null') ? '/dev/null' : 'nul'); + + $l = new ExceptionListener('foo'); + $l->onKernelException($event); + + $this->assertEquals(new Response('foo'), $event->getResponse()); + + try { + $l->onKernelException($event2); + } catch (\Exception $e) { + $this->assertSame('foo', $e->getMessage()); + } + + // restore the old error_log + ini_set('error_log', $errorLog); + } + + /** + * @dataProvider provider + */ + public function testHandleWithLogger($event, $event2) + { + $logger = new TestLogger(); + + $l = new ExceptionListener('foo', $logger); + $l->onKernelException($event); + + $this->assertEquals(new Response('foo'), $event->getResponse()); + + try { + $l->onKernelException($event2); + } catch (\Exception $e) { + $this->assertSame('foo', $e->getMessage()); + } + + $this->assertEquals(3, $logger->countErrors()); + $this->assertCount(3, $logger->getLogs('critical')); + } + + public function provider() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + return array(array(null, null)); + } + + $request = new Request(); + $exception = new \Exception('foo'); + $event = new GetResponseForExceptionEvent(new TestKernel(), $request, 'foo', $exception); + $event2 = new GetResponseForExceptionEvent(new TestKernelThatThrowsException(), $request, 'foo', $exception); + + return array( + array($event, $event2) + ); + } + + public function testSubRequestFormat() + { + $listener = new ExceptionListener('foo', $this->getMock('Psr\Log\LoggerInterface')); + + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $kernel->expects($this->once())->method('handle')->will($this->returnCallback(function (Request $request) { + return new Response($request->getRequestFormat()); + })); + + $request = Request::create('/'); + $request->setRequestFormat('xml'); + + $event = new GetResponseForExceptionEvent($kernel, $request, 'foo', new \Exception('foo')); + $listener->onKernelException($event); + + $response = $event->getResponse(); + $this->assertEquals('xml', $response->getContent()); + } +} + +class TestLogger extends Logger implements DebugLoggerInterface +{ + public function countErrors() + { + return count($this->logs['critical']); + } +} + +class TestKernel implements HttpKernelInterface +{ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) + { + return new Response('foo'); + } +} + +class TestKernelThatThrowsException implements HttpKernelInterface +{ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) + { + throw new \Exception('bar'); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/EventListener/FragmentListenerTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/EventListener/FragmentListenerTest.php new file mode 100644 index 0000000..153d8a4 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/EventListener/FragmentListenerTest.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use Symfony\Component\HttpKernel\EventListener\FragmentListener; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\UriSigner; + +class FragmentListenerTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { + $this->markTestSkipped('The "EventDispatcher" component is not available'); + } + } + + public function testOnlyTriggeredOnFragmentRoute() + { + $request = Request::create('http://example.com/foo?_path=foo%3Dbar%26_controller%3Dfoo'); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request); + + $expected = $request->attributes->all(); + + $listener->onKernelRequest($event); + + $this->assertEquals($expected, $request->attributes->all()); + $this->assertTrue($request->query->has('_path')); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testAccessDeniedWithNonSafeMethods() + { + $request = Request::create('http://example.com/_fragment', 'POST'); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testAccessDeniedWithNonLocalIps() + { + $request = Request::create('http://example.com/_fragment', 'GET', array(), array(), array(), array('REMOTE_ADDR' => '10.0.0.1')); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + */ + public function testAccessDeniedWithWrongSignature() + { + $request = Request::create('http://example.com/_fragment', 'GET', array(), array(), array(), array('REMOTE_ADDR' => '10.0.0.1')); + + $listener = new FragmentListener(new UriSigner('foo')); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + } + + public function testWithSignature() + { + $signer = new UriSigner('foo'); + $request = Request::create($signer->sign('http://example.com/_fragment?_path=foo%3Dbar%26_controller%3Dfoo'), 'GET', array(), array(), array(), array('REMOTE_ADDR' => '10.0.0.1')); + + $listener = new FragmentListener($signer); + $event = $this->createGetResponseEvent($request); + + $listener->onKernelRequest($event); + + $this->assertEquals(array('foo' => 'bar', '_controller' => 'foo'), $request->attributes->get('_route_params')); + $this->assertFalse($request->query->has('_path')); + } + + private function createGetResponseEvent(Request $request) + { + return new GetResponseEvent($this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'), $request, HttpKernelInterface::MASTER_REQUEST); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/EventListener/LocaleListenerTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/EventListener/LocaleListenerTest.php new file mode 100644 index 0000000..e5e4e3a --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/EventListener/LocaleListenerTest.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use Symfony\Component\HttpKernel\EventListener\LocaleListener; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; + +class LocaleListenerTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { + $this->markTestSkipped('The "EventDispatcher" component is not available'); + } + } + + public function testDefaultLocaleWithoutSession() + { + $listener = new LocaleListener('fr'); + $event = $this->getEvent($request = Request::create('/')); + + $listener->onKernelRequest($event); + $this->assertEquals('fr', $request->getLocale()); + } + + public function testLocaleFromRequestAttribute() + { + $request = Request::create('/'); + session_name('foo'); + $request->cookies->set('foo', 'value'); + + $request->attributes->set('_locale', 'es'); + $listener = new LocaleListener('fr'); + $event = $this->getEvent($request); + + $listener->onKernelRequest($event); + $this->assertEquals('es', $request->getLocale()); + } + + public function testLocaleSetForRoutingContext() + { + if (!class_exists('Symfony\Component\Routing\Router')) { + $this->markTestSkipped('The "Routing" component is not available'); + } + + // the request context is updated + $context = $this->getMock('Symfony\Component\Routing\RequestContext'); + $context->expects($this->once())->method('setParameter')->with('_locale', 'es'); + + $router = $this->getMock('Symfony\Component\Routing\Router', array('getContext'), array(), '', false); + $router->expects($this->once())->method('getContext')->will($this->returnValue($context)); + + $request = Request::create('/'); + + $request->attributes->set('_locale', 'es'); + $listener = new LocaleListener('fr', $router); + $listener->onKernelRequest($this->getEvent($request)); + } + + public function testRequestLocaleIsNotOverridden() + { + $request = Request::create('/'); + $request->setLocale('de'); + $listener = new LocaleListener('fr'); + $event = $this->getEvent($request); + + $listener->onKernelRequest($event); + $this->assertEquals('de', $request->getLocale()); + } + + private function getEvent(Request $request) + { + return new GetResponseEvent($this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'), $request, HttpKernelInterface::MASTER_REQUEST); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/EventListener/ResponseListenerTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/EventListener/ResponseListenerTest.php new file mode 100644 index 0000000..3f8e852 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/EventListener/ResponseListenerTest.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use Symfony\Component\HttpKernel\EventListener\ResponseListener; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class ResponseListenerTest extends \PHPUnit_Framework_TestCase +{ + private $dispatcher; + + private $kernel; + + protected function setUp() + { + if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { + $this->markTestSkipped('The "EventDispatcher" component is not available'); + } + + $this->dispatcher = new EventDispatcher(); + $listener = new ResponseListener('UTF-8'); + $this->dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse')); + + $this->kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + + } + + protected function tearDown() + { + $this->dispatcher = null; + $this->kernel = null; + } + + public function testFilterDoesNothingForSubRequests() + { + $response = new Response('foo'); + + $event = new FilterResponseEvent($this->kernel, new Request(), HttpKernelInterface::SUB_REQUEST, $response); + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('', $event->getResponse()->headers->get('content-type')); + } + + public function testFilterSetsNonDefaultCharsetIfNotOverridden() + { + $listener = new ResponseListener('ISO-8859-15'); + $this->dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse'), 1); + + $response = new Response('foo'); + + $event = new FilterResponseEvent($this->kernel, Request::create('/'), HttpKernelInterface::MASTER_REQUEST, $response); + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('ISO-8859-15', $response->getCharset()); + } + + public function testFilterDoesNothingIfCharsetIsOverridden() + { + $listener = new ResponseListener('ISO-8859-15'); + $this->dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse'), 1); + + $response = new Response('foo'); + $response->setCharset('ISO-8859-1'); + + $event = new FilterResponseEvent($this->kernel, Request::create('/'), HttpKernelInterface::MASTER_REQUEST, $response); + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('ISO-8859-1', $response->getCharset()); + } + + public function testFiltersSetsNonDefaultCharsetIfNotOverriddenOnNonTextContentType() + { + $listener = new ResponseListener('ISO-8859-15'); + $this->dispatcher->addListener(KernelEvents::RESPONSE, array($listener, 'onKernelResponse'), 1); + + $response = new Response('foo'); + $request = Request::create('/'); + $request->setRequestFormat('application/json'); + + $event = new FilterResponseEvent($this->kernel, $request, HttpKernelInterface::MASTER_REQUEST, $response); + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->assertEquals('ISO-8859-15', $response->getCharset()); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/EventListener/RouterListenerTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/EventListener/RouterListenerTest.php new file mode 100644 index 0000000..3ba56a8 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/EventListener/RouterListenerTest.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\EventListener; + +use Symfony\Component\HttpKernel\EventListener\RouterListener; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\Routing\RequestContext; + +class RouterListenerTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { + $this->markTestSkipped('The "EventDispatcher" component is not available'); + } + + if (!class_exists('Symfony\Component\Routing\Router')) { + $this->markTestSkipped('The "Routing" component is not available'); + } + } + + /** + * @dataProvider getPortData + */ + public function testPort($defaultHttpPort, $defaultHttpsPort, $uri, $expectedHttpPort, $expectedHttpsPort) + { + $urlMatcher = $this->getMockBuilder('Symfony\Component\Routing\Matcher\UrlMatcherInterface') + ->disableOriginalConstructor() + ->getMock(); + $context = new RequestContext(); + $context->setHttpPort($defaultHttpPort); + $context->setHttpsPort($defaultHttpsPort); + $urlMatcher->expects($this->any()) + ->method('getContext') + ->will($this->returnValue($context)); + + $listener = new RouterListener($urlMatcher); + $event = $this->createGetResponseEventForUri($uri); + $listener->onKernelRequest($event); + + $this->assertEquals($expectedHttpPort, $context->getHttpPort()); + $this->assertEquals($expectedHttpsPort, $context->getHttpsPort()); + $this->assertEquals(0 === strpos($uri, 'https') ? 'https' : 'http', $context->getScheme()); + } + + public function getPortData() + { + return array( + array(80, 443, 'http://localhost/', 80, 443), + array(80, 443, 'http://localhost:90/', 90, 443), + array(80, 443, 'https://localhost/', 80, 443), + array(80, 443, 'https://localhost:90/', 80, 90), + ); + } + + /** + * @param string $uri + * + * @return GetResponseEvent + */ + private function createGetResponseEventForUri($uri) + { + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $request = Request::create($uri); + $request->attributes->set('_controller', null); // Prevents going in to routing process + + return new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidMatcher() + { + new RouterListener(new \stdClass()); + } + + public function testRequestMatcher() + { + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $request = Request::create('http://localhost/'); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + + $requestMatcher = $this->getMock('Symfony\Component\Routing\Matcher\RequestMatcherInterface'); + $requestMatcher->expects($this->once()) + ->method('matchRequest') + ->with($this->isInstanceOf('Symfony\Component\HttpFoundation\Request')) + ->will($this->returnValue(array())); + + $listener = new RouterListener($requestMatcher, new RequestContext()); + $listener->onKernelRequest($event); + } + + public function testSubRequestWithDifferentMethod() + { + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $request = Request::create('http://localhost/', 'post'); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + + $requestMatcher = $this->getMock('Symfony\Component\Routing\Matcher\RequestMatcherInterface'); + $requestMatcher->expects($this->any()) + ->method('matchRequest') + ->with($this->isInstanceOf('Symfony\Component\HttpFoundation\Request')) + ->will($this->returnValue(array())); + + $context = new RequestContext(); + $requestMatcher->expects($this->any()) + ->method('getContext') + ->will($this->returnValue($context)); + + $listener = new RouterListener($requestMatcher, new RequestContext()); + $listener->onKernelRequest($event); + + // sub-request with another HTTP method + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $request = Request::create('http://localhost/', 'get'); + $event = new GetResponseEvent($kernel, $request, HttpKernelInterface::SUB_REQUEST); + + $listener->onKernelRequest($event); + + $this->assertEquals('GET', $context->getMethod()); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/BaseBundle/Resources/foo.txt b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/BaseBundle/Resources/foo.txt new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/BaseBundle/Resources/hide.txt b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/BaseBundle/Resources/hide.txt new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/Bundle1Bundle/Resources/foo.txt b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/Bundle1Bundle/Resources/foo.txt new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/Bundle1Bundle/bar.txt b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/Bundle1Bundle/bar.txt new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/Bundle1Bundle/foo.txt b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/Bundle1Bundle/foo.txt new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/Bundle2Bundle/foo.txt b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/Bundle2Bundle/foo.txt new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ChildBundle/Resources/foo.txt b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ChildBundle/Resources/foo.txt new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ChildBundle/Resources/hide.txt b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ChildBundle/Resources/hide.txt new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php new file mode 100644 index 0000000..c8bfd36 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionAbsentBundle/ExtensionAbsentBundle.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionAbsentBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class ExtensionAbsentBundle extends Bundle +{ +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.php new file mode 100644 index 0000000..3b31781 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionLoadedBundle/DependencyInjection/ExtensionLoadedExtension.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionLoadedBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; + +class ExtensionLoadedExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container) + { + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php new file mode 100644 index 0000000..3af81cb --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionLoadedBundle/ExtensionLoadedBundle.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionLoadedBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class ExtensionLoadedBundle extends Bundle +{ +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php new file mode 100644 index 0000000..b9a5417 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionPresentBundle/Command/BarCommand.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\Command; + +use Symfony\Component\Console\Command\Command; + +class FooCommand extends Command +{ + protected function configure() + { + $this->setName('foo'); + } + +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionPresentBundle/DependencyInjection/ExtensionPresentExtension.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionPresentBundle/DependencyInjection/ExtensionPresentExtension.php new file mode 100644 index 0000000..e42f816 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionPresentBundle/DependencyInjection/ExtensionPresentExtension.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; + +class ExtensionPresentExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container) + { + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionPresentBundle/ExtensionPresentBundle.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionPresentBundle/ExtensionPresentBundle.php new file mode 100644 index 0000000..36a7ad4 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/ExtensionPresentBundle/ExtensionPresentBundle.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures\ExtensionPresentBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class ExtensionPresentBundle extends Bundle +{ +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/FooBarBundle.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/FooBarBundle.php new file mode 100644 index 0000000..f940f83 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/FooBarBundle.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures; + +use Symfony\Component\HttpKernel\Bundle\Bundle; + +class FooBarBundle extends Bundle +{ + // We need a full namespaced bundle instance to test isClassInActiveBundle +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/KernelForOverrideName.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/KernelForOverrideName.php new file mode 100644 index 0000000..32c05f4 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/KernelForOverrideName.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures; + +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Config\Loader\LoaderInterface; + +class KernelForOverrideName extends Kernel +{ + protected $name = 'overridden'; + + public function registerBundles() + { + + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/KernelForTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/KernelForTest.php new file mode 100644 index 0000000..e24daef --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/KernelForTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures; + +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Config\Loader\LoaderInterface; + +class KernelForTest extends Kernel +{ + public function getBundleMap() + { + return $this->bundleMap; + } + + public function registerBundles() + { + } + + public function init() + { + } + + public function registerBundleDirs() + { + } + + public function registerContainerConfiguration(LoaderInterface $loader) + { + } + + public function initializeBundles() + { + parent::initializeBundles(); + } + + public function isBooted() + { + return $this->booted; + } + + public function setIsBooted($value) + { + $this->booted = (Boolean) $value; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/Resources/BaseBundle/hide.txt b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/Resources/BaseBundle/hide.txt new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/Resources/Bundle1Bundle/foo.txt b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/Resources/Bundle1Bundle/foo.txt new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/Resources/ChildBundle/foo.txt b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/Resources/ChildBundle/foo.txt new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/Resources/FooBundle/foo.txt b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/Resources/FooBundle/foo.txt new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/TestClient.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/TestClient.php new file mode 100644 index 0000000..e7d60cf --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/TestClient.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures; + +use Symfony\Component\HttpKernel\Client; + +class TestClient extends Client +{ + protected function getScript($request) + { + $script = parent::getScript($request); + + $autoload = file_exists(__DIR__.'/../../vendor/autoload.php') + ? __DIR__.'/../../vendor/autoload.php' + : __DIR__.'/../../../../../../vendor/autoload.php' + ; + + $script = preg_replace('/(\->register\(\);)/', "$0\nrequire_once '$autoload';\n", $script); + + return $script; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/TestEventDispatcher.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/TestEventDispatcher.php new file mode 100644 index 0000000..da7ef5b --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fixtures/TestEventDispatcher.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fixtures; + +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class TestEventDispatcher extends EventDispatcher implements TraceableEventDispatcherInterface +{ + public function getCalledListeners() + { + return array('foo'); + } + + public function getNotCalledListeners() + { + return array('bar'); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fragment/EsiFragmentRendererTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fragment/EsiFragmentRendererTest.php new file mode 100644 index 0000000..e6b8b2c --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fragment/EsiFragmentRendererTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fragment\FragmentRenderer; + +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Fragment\EsiFragmentRenderer; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpFoundation\Request; + +class EsiFragmentRendererTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + public function testRenderFallbackToInlineStrategyIfNoRequest() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy(true)); + $strategy->render('/', Request::create('/')); + } + + public function testRenderFallbackToInlineStrategyIfEsiNotSupported() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy(true)); + $strategy->render('/', Request::create('/')); + } + + public function testRender() + { + $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy()); + + $request = Request::create('/'); + $request->setLocale('fr'); + $request->headers->set('Surrogate-Capability', 'ESI/1.0'); + + $this->assertEquals('', $strategy->render('/', $request)->getContent()); + $this->assertEquals("\n", $strategy->render('/', $request, array('comment' => 'This is a comment'))->getContent()); + $this->assertEquals('', $strategy->render('/', $request, array('alt' => 'foo'))->getContent()); + $this->assertEquals('', $strategy->render(new ControllerReference('main_controller', array(), array()), $request, array('alt' => new ControllerReference('alt_controller', array(), array())))->getContent()); + } + + private function getInlineStrategy($called = false) + { + $inline = $this->getMockBuilder('Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer')->disableOriginalConstructor()->getMock(); + + if ($called) { + $inline->expects($this->once())->method('render'); + } + + return $inline; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fragment/FragmentHandlerTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fragment/FragmentHandlerTest.php new file mode 100644 index 0000000..cec8ae9 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fragment/FragmentHandlerTest.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Fragment; + +use Symfony\Component\HttpKernel\Fragment\FragmentHandler; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class FragmentHandlerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \InvalidArgumentException + */ + public function testRenderWhenRendererDoesNotExist() + { + $handler = new FragmentHandler(); + $handler->render('/', 'foo'); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testRenderWithUnknownRenderer() + { + $handler = $this->getHandler($this->returnValue(new Response('foo'))); + + $handler->render('/', 'bar'); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage Error when rendering "http://localhost/" (Status code is 404). + */ + public function testDeliverWithUnsuccessfulResponse() + { + $handler = $this->getHandler($this->returnValue(new Response('foo', 404))); + + $handler->render('/', 'foo'); + } + + public function testRender() + { + $handler = $this->getHandler($this->returnValue(new Response('foo')), array('/', Request::create('/'), array('foo' => 'foo', 'ignore_errors' => true))); + + $this->assertEquals('foo', $handler->render('/', 'foo', array('foo' => 'foo'))); + } + + protected function getHandler($returnValue, $arguments = array()) + { + $renderer = $this->getMock('Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface'); + $renderer + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue('foo')) + ; + $e = $renderer + ->expects($this->any()) + ->method('render') + ->will($returnValue) + ; + + if ($arguments) { + call_user_func_array(array($e, 'with'), $arguments); + } + + $handler = new FragmentHandler(); + $handler->addRenderer($renderer); + $handler->setRequest(Request::create('/')); + + return $handler; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fragment/HIncludeFragmentRendererTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fragment/HIncludeFragmentRendererTest.php new file mode 100644 index 0000000..22b03ce --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fragment/HIncludeFragmentRendererTest.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment\Tests\FragmentRenderer; + +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Fragment\HIncludeFragmentRenderer; +use Symfony\Component\HttpKernel\UriSigner; +use Symfony\Component\HttpFoundation\Request; + +class HIncludeFragmentRendererTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + /** + * @expectedException \LogicException + */ + public function testRenderExceptionWhenControllerAndNoSigner() + { + $strategy = new HIncludeFragmentRenderer(); + $strategy->render(new ControllerReference('main_controller', array(), array()), Request::create('/')); + } + + public function testRenderWithControllerAndSigner() + { + $strategy = new HIncludeFragmentRenderer(null, new UriSigner('foo')); + + $this->assertEquals('', $strategy->render(new ControllerReference('main_controller', array(), array()), Request::create('/'))->getContent()); + } + + public function testRenderWithUri() + { + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('', $strategy->render('/foo', Request::create('/'))->getContent()); + + $strategy = new HIncludeFragmentRenderer(null, new UriSigner('foo')); + $this->assertEquals('', $strategy->render('/foo', Request::create('/'))->getContent()); + } + + public function testRenderWithDefault() + { + // only default + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('default', $strategy->render('/foo', Request::create('/'), array('default' => 'default'))->getContent()); + + // only global default + $strategy = new HIncludeFragmentRenderer(null, null, 'global_default'); + $this->assertEquals('global_default', $strategy->render('/foo', Request::create('/'), array())->getContent()); + + // global default and default + $strategy = new HIncludeFragmentRenderer(null, null, 'global_default'); + $this->assertEquals('default', $strategy->render('/foo', Request::create('/'), array('default' => 'default'))->getContent()); + } + + public function testRenderWithAttributesOptions() + { + // with id + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('default', $strategy->render('/foo', Request::create('/'), array('default' => 'default', 'id' => 'bar'))->getContent()); + + // with attributes + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('default', $strategy->render('/foo', Request::create('/'), array('default' => 'default', 'attributes' => array('p1' => 'v1', 'p2' => 'v2')))->getContent()); + + // with id & attributes + $strategy = new HIncludeFragmentRenderer(); + $this->assertEquals('default', $strategy->render('/foo', Request::create('/'), array('default' => 'default', 'id' => 'bar', 'attributes' => array('p1' => 'v1', 'p2' => 'v2')))->getContent()); + } + + public function testRenderWithDefaultText() + { + $engine = $this->getMock('Symfony\\Component\\Templating\\EngineInterface'); + $engine->expects($this->once()) + ->method('exists') + ->with('default') + ->will($this->throwException(new \InvalidArgumentException())); + + // only default + $strategy = new HIncludeFragmentRenderer($engine); + $this->assertEquals('default', $strategy->render('/foo', Request::create('/'), array('default' => 'default'))->getContent()); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php new file mode 100644 index 0000000..ffc0c0a --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fragment/InlineFragmentRendererTest.php @@ -0,0 +1,199 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment\Tests\FragmentRenderer; + +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class InlineFragmentRendererTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { + $this->markTestSkipped('The "EventDispatcher" component is not available'); + } + + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + public function testRender() + { + $strategy = new InlineFragmentRenderer($this->getKernel($this->returnValue(new Response('foo')))); + + $this->assertEquals('foo', $strategy->render('/', Request::create('/'))->getContent()); + } + + public function testRenderWithControllerReference() + { + $strategy = new InlineFragmentRenderer($this->getKernel($this->returnValue(new Response('foo')))); + + $this->assertEquals('foo', $strategy->render(new ControllerReference('main_controller', array(), array()), Request::create('/'))->getContent()); + } + + public function testRenderWithObjectsAsAttributes() + { + $object = new \stdClass(); + + $subRequest = Request::create('/_fragment?_path=_format%3Dhtml%26_locale%3Den%26_controller%3Dmain_controller'); + $subRequest->attributes->replace(array('object' => $object, '_format' => 'html', '_controller' => 'main_controller', '_locale' => 'en')); + $subRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + $subRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $kernel + ->expects($this->any()) + ->method('handle') + ->with($subRequest) + ; + + $strategy = new InlineFragmentRenderer($kernel); + + $strategy->render(new ControllerReference('main_controller', array('object' => $object), array()), Request::create('/')); + } + + public function testRenderWithTrustedHeaderDisabled() + { + $trustedHeaderName = Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP); + + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, ''); + + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $kernel + ->expects($this->any()) + ->method('handle') + ->with(Request::create('/')) + ; + + $strategy = new InlineFragmentRenderer($kernel); + + $strategy->render('/', Request::create('/')); + + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, $trustedHeaderName); + } + + /** + * @expectedException \RuntimeException + */ + public function testRenderExceptionNoIgnoreErrors() + { + $dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $dispatcher->expects($this->never())->method('dispatch'); + + $strategy = new InlineFragmentRenderer($this->getKernel($this->throwException(new \RuntimeException('foo'))), $dispatcher); + + $this->assertEquals('foo', $strategy->render('/', Request::create('/'))->getContent()); + } + + public function testRenderExceptionIgnoreErrors() + { + $dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $dispatcher->expects($this->once())->method('dispatch')->with(KernelEvents::EXCEPTION); + + $strategy = new InlineFragmentRenderer($this->getKernel($this->throwException(new \RuntimeException('foo'))), $dispatcher); + + $this->assertEmpty($strategy->render('/', Request::create('/'), array('ignore_errors' => true))->getContent()); + } + + public function testRenderExceptionIgnoreErrorsWithAlt() + { + $strategy = new InlineFragmentRenderer($this->getKernel($this->onConsecutiveCalls( + $this->throwException(new \RuntimeException('foo')), + $this->returnValue(new Response('bar')) + ))); + + $this->assertEquals('bar', $strategy->render('/', Request::create('/'), array('ignore_errors' => true, 'alt' => '/foo'))->getContent()); + } + + private function getKernel($returnValue) + { + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $kernel + ->expects($this->any()) + ->method('handle') + ->will($returnValue) + ; + + return $kernel; + } + + public function testExceptionInSubRequestsDoesNotMangleOutputBuffers() + { + $resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface'); + $resolver + ->expects($this->once()) + ->method('getController') + ->will($this->returnValue(function () { + ob_start(); + echo 'bar'; + throw new \RuntimeException(); + })) + ; + $resolver + ->expects($this->once()) + ->method('getArguments') + ->will($this->returnValue(array())) + ; + + $kernel = new HttpKernel(new EventDispatcher(), $resolver); + $renderer = new InlineFragmentRenderer($kernel); + + // simulate a main request with output buffering + ob_start(); + echo 'Foo'; + + // simulate a sub-request with output buffering and an exception + $renderer->render('/', Request::create('/'), array('ignore_errors' => true)); + + $this->assertEquals('Foo', ob_get_clean()); + } + + public function testESIHeaderIsKeptInSubrequest() + { + $expectedSubRequest = Request::create('/'); + $expectedSubRequest->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + + if (Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)) { + $expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1')); + $expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1'); + } + + $kernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + $kernel + ->expects($this->any()) + ->method('handle') + ->with($expectedSubRequest) + ; + + $strategy = new InlineFragmentRenderer($kernel); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + $strategy->render('/', $request); + } + + public function testESIHeaderIsKeptInSubrequestWithTrustedHeaderDisabled() + { + $trustedHeaderName = Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, ''); + + $this->testESIHeaderIsKeptInSubrequest(); + + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, $trustedHeaderName); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fragment/RoutableFragmentRendererTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fragment/RoutableFragmentRendererTest.php new file mode 100644 index 0000000..e8df887 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Fragment/RoutableFragmentRendererTest.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment\Tests\FragmentRenderer; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Fragment\RoutableFragmentRenderer; + +class RoutableFragmentRendererTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getGenerateFragmentUriData + */ + public function testGenerateFragmentUri($uri, $controller) + { + $this->assertEquals($uri, $this->getRenderer()->doGenerateFragmentUri($controller, Request::create('/'))); + } + + /** + * @dataProvider getGenerateFragmentUriData + */ + public function testGenerateAbsoluteFragmentUri($uri, $controller) + { + $this->assertEquals('http://localhost'.$uri, $this->getRenderer()->doGenerateFragmentUri($controller, Request::create('/'), true)); + } + + public function getGenerateFragmentUriData() + { + return array( + array('/_fragment?_path=_format%3Dhtml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array(), array())), + array('/_fragment?_path=_format%3Dxml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array('_format' => 'xml'), array())), + array('/_fragment?_path=foo%3Dfoo%26_format%3Djson%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array('foo' => 'foo', '_format' => 'json'), array())), + array('/_fragment?bar=bar&_path=foo%3Dfoo%26_format%3Dhtml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array('foo' => 'foo'), array('bar' => 'bar'))), + array('/_fragment?foo=foo&_path=_format%3Dhtml%26_locale%3Den%26_controller%3Dcontroller', new ControllerReference('controller', array(), array('foo' => 'foo'))), + ); + } + + public function testGenerateFragmentUriWithARequest() + { + $request = Request::create('/'); + $request->attributes->set('_format', 'json'); + $request->setLocale('fr'); + $controller = new ControllerReference('controller', array(), array()); + + $this->assertEquals('/_fragment?_path=_format%3Djson%26_locale%3Dfr%26_controller%3Dcontroller', $this->getRenderer()->doGenerateFragmentUri($controller, $request)); + } + + private function getRenderer() + { + return new Renderer(); + } +} + +class Renderer extends RoutableFragmentRenderer +{ + public function render($uri, Request $request, array $options = array()) {} + public function getName() {} + + public function doGenerateFragmentUri(ControllerReference $reference, Request $request, $absolute = false) + { + return parent::generateFragmentUri($reference, $request, $absolute); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php new file mode 100644 index 0000000..7180da1 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/EsiTest.php @@ -0,0 +1,227 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class EsiTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + public function testHasSurrogateEsiCapability() + { + $esi = new Esi(); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'abc="ESI/1.0"'); + $this->assertTrue($esi->hasSurrogateEsiCapability($request)); + + $request = Request::create('/'); + $request->headers->set('Surrogate-Capability', 'foobar'); + $this->assertFalse($esi->hasSurrogateEsiCapability($request)); + + $request = Request::create('/'); + $this->assertFalse($esi->hasSurrogateEsiCapability($request)); + } + + public function testAddSurrogateEsiCapability() + { + $esi = new Esi(); + + $request = Request::create('/'); + $esi->addSurrogateEsiCapability($request); + $this->assertEquals('symfony2="ESI/1.0"', $request->headers->get('Surrogate-Capability')); + + $esi->addSurrogateEsiCapability($request); + $this->assertEquals('symfony2="ESI/1.0", symfony2="ESI/1.0"', $request->headers->get('Surrogate-Capability')); + } + + public function testAddSurrogateControl() + { + $esi = new Esi(); + + $response = new Response('foo '); + $esi->addSurrogateControl($response); + $this->assertEquals('content="ESI/1.0"', $response->headers->get('Surrogate-Control')); + + $response = new Response('foo'); + $esi->addSurrogateControl($response); + $this->assertEquals('', $response->headers->get('Surrogate-Control')); + } + + public function testNeedsEsiParsing() + { + $esi = new Esi(); + + $response = new Response(); + $response->headers->set('Surrogate-Control', 'content="ESI/1.0"'); + $this->assertTrue($esi->needsEsiParsing($response)); + + $response = new Response(); + $this->assertFalse($esi->needsEsiParsing($response)); + } + + public function testRenderIncludeTag() + { + $esi = new Esi(); + + $this->assertEquals('', $esi->renderIncludeTag('/', '/alt', true)); + $this->assertEquals('', $esi->renderIncludeTag('/', '/alt', false)); + $this->assertEquals('', $esi->renderIncludeTag('/')); + $this->assertEquals(''."\n".'', $esi->renderIncludeTag('/', '/alt', true, 'some comment')); + } + + public function testProcessDoesNothingIfContentTypeIsNotHtml() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response(); + $response->headers->set('Content-Type', 'text/plain'); + $esi->process($request, $response); + + $this->assertFalse($response->headers->has('x-body-eval')); + } + + public function testProcess() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response('foo '); + $esi->process($request, $response); + + $this->assertEquals('foo esi->handle($this, \'...\', \'alt\', true) ?>'."\n", $response->getContent()); + $this->assertEquals('ESI', $response->headers->get('x-body-eval')); + + $response = new Response('foo '); + $esi->process($request, $response); + + $this->assertEquals('foo esi->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent()); + + $response = new Response('foo '); + $esi->process($request, $response); + + $this->assertEquals('foo esi->handle($this, \'...\', \'\', false) ?>'."\n", $response->getContent()); + } + + public function testProcessEscapesPhpTags() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response('foo <%= "lala" %>'); + $esi->process($request, $response); + + $this->assertEquals('foo php die("foo"); ?>= "lala" %>', $response->getContent()); + } + + /** + * @expectedException RuntimeException + */ + public function testProcessWhenNoSrcInAnEsi() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response('foo '); + $esi->process($request, $response); + } + + public function testProcessRemoveSurrogateControlHeader() + { + $esi = new Esi(); + + $request = Request::create('/'); + $response = new Response('foo '); + $response->headers->set('Surrogate-Control', 'content="ESI/1.0"'); + $esi->process($request, $response); + $this->assertEquals('ESI', $response->headers->get('x-body-eval')); + + $response->headers->set('Surrogate-Control', 'no-store, content="ESI/1.0"'); + $esi->process($request, $response); + $this->assertEquals('ESI', $response->headers->get('x-body-eval')); + $this->assertEquals('no-store', $response->headers->get('surrogate-control')); + + $response->headers->set('Surrogate-Control', 'content="ESI/1.0", no-store'); + $esi->process($request, $response); + $this->assertEquals('ESI', $response->headers->get('x-body-eval')); + $this->assertEquals('no-store', $response->headers->get('surrogate-control')); + } + + public function testHandle() + { + $esi = new Esi(); + $cache = $this->getCache(Request::create('/'), new Response('foo')); + $this->assertEquals('foo', $esi->handle($cache, '/', '/alt', true)); + } + + /** + * @expectedException RuntimeException + */ + public function testHandleWhenResponseIsNot200() + { + $esi = new Esi(); + $response = new Response('foo'); + $response->setStatusCode(404); + $cache = $this->getCache(Request::create('/'), $response); + $esi->handle($cache, '/', '/alt', false); + } + + public function testHandleWhenResponseIsNot200AndErrorsAreIgnored() + { + $esi = new Esi(); + $response = new Response('foo'); + $response->setStatusCode(404); + $cache = $this->getCache(Request::create('/'), $response); + $this->assertEquals('', $esi->handle($cache, '/', '/alt', true)); + } + + public function testHandleWhenResponseIsNot200AndAltIsPresent() + { + $esi = new Esi(); + $response1 = new Response('foo'); + $response1->setStatusCode(404); + $response2 = new Response('bar'); + $cache = $this->getCache(Request::create('/'), array($response1, $response2)); + $this->assertEquals('bar', $esi->handle($cache, '/', '/alt', false)); + } + + protected function getCache($request, $response) + { + $cache = $this->getMock('Symfony\Component\HttpKernel\HttpCache\HttpCache', array('getRequest', 'handle'), array(), '', false); + $cache->expects($this->any()) + ->method('getRequest') + ->will($this->returnValue($request)) + ; + if (is_array($response)) { + $cache->expects($this->any()) + ->method('handle') + ->will(call_user_func_array(array($this, 'onConsecutiveCalls'), $response)) + ; + } else { + $cache->expects($this->any()) + ->method('handle') + ->will($this->returnValue($response)) + ; + } + + return $cache; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php new file mode 100644 index 0000000..a8064b8 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTest.php @@ -0,0 +1,1106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use Symfony\Component\HttpKernel\HttpCache\HttpCache; +use Symfony\Component\HttpKernel\HttpCache\StoreInterface; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class HttpCacheTest extends HttpCacheTestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + public function testTerminateDelegatesTerminationOnlyForTerminableInterface() + { + if (!class_exists('Symfony\Component\DependencyInjection\Container')) { + $this->markTestSkipped('The "DependencyInjection" component is not available'); + } + + $storeMock = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\HttpCache\\StoreInterface') + ->disableOriginalConstructor() + ->getMock(); + + // does not implement TerminableInterface + $kernelMock = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\HttpKernelInterface') + ->disableOriginalConstructor() + ->getMock(); + + $kernelMock->expects($this->never()) + ->method('terminate'); + + $kernel = new HttpCache($kernelMock, $storeMock); + $kernel->terminate(Request::create('/'), new Response()); + + // implements TerminableInterface + $kernelMock = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Kernel') + ->disableOriginalConstructor() + ->setMethods(array('terminate', 'registerBundles', 'registerContainerConfiguration')) + ->getMock(); + + $kernelMock->expects($this->once()) + ->method('terminate'); + + $kernel = new HttpCache($kernelMock, $storeMock); + $kernel->terminate(Request::create('/'), new Response()); + } + + public function testPassesOnNonGetHeadRequests() + { + $this->setNextResponse(200); + $this->request('POST', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertTraceContains('pass'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testInvalidatesOnPostPutDeleteRequests() + { + foreach (array('post', 'put', 'delete') as $method) { + $this->setNextResponse(200); + $this->request($method, '/'); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertTraceContains('invalidate'); + $this->assertTraceContains('pass'); + } + } + + public function testDoesNotCacheWithAuthorizationRequestHeaderAndNonPublicResponse() + { + $this->setNextResponse(200, array('ETag' => '"Foo"')); + $this->request('GET', '/', array('HTTP_AUTHORIZATION' => 'basic foobarbaz')); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertEquals('private', $this->response->headers->get('Cache-Control')); + + $this->assertTraceContains('miss'); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testDoesCacheWithAuthorizationRequestHeaderAndPublicResponse() + { + $this->setNextResponse(200, array('Cache-Control' => 'public', 'ETag' => '"Foo"')); + $this->request('GET', '/', array('HTTP_AUTHORIZATION' => 'basic foobarbaz')); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertTrue($this->response->headers->has('Age')); + $this->assertEquals('public', $this->response->headers->get('Cache-Control')); + } + + public function testDoesNotCacheWithCookieHeaderAndNonPublicResponse() + { + $this->setNextResponse(200, array('ETag' => '"Foo"')); + $this->request('GET', '/', array(), array('foo' => 'bar')); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertEquals('private', $this->response->headers->get('Cache-Control')); + $this->assertTraceContains('miss'); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testDoesNotCacheRequestsWithACookieHeader() + { + $this->setNextResponse(200); + $this->request('GET', '/', array(), array('foo' => 'bar')); + + $this->assertHttpKernelIsCalled(); + $this->assertResponseOk(); + $this->assertEquals('private', $this->response->headers->get('Cache-Control')); + $this->assertTraceContains('miss'); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testRespondsWith304WhenIfModifiedSinceMatchesLastModified() + { + $time = new \DateTime(); + + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Last-Modified' => $time->format(DATE_RFC2822), 'Content-Type' => 'text/plain'), 'Hello World'); + $this->request('GET', '/', array('HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822))); + + $this->assertHttpKernelIsCalled(); + $this->assertEquals(304, $this->response->getStatusCode()); + $this->assertEquals('', $this->response->headers->get('Content-Type')); + $this->assertEmpty($this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + } + + public function testRespondsWith304WhenIfNoneMatchMatchesETag() + { + $this->setNextResponse(200, array('Cache-Control' => 'public', 'ETag' => '12345', 'Content-Type' => 'text/plain'), 'Hello World'); + $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '12345')); + + $this->assertHttpKernelIsCalled(); + $this->assertEquals(304, $this->response->getStatusCode()); + $this->assertEquals('', $this->response->headers->get('Content-Type')); + $this->assertTrue($this->response->headers->has('ETag')); + $this->assertEmpty($this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + } + + public function testRespondsWith304OnlyIfIfNoneMatchAndIfModifiedSinceBothMatch() + { + $time = new \DateTime(); + + $this->setNextResponse(200, array(), '', function ($request, $response) use ($time) { + $response->setStatusCode(200); + $response->headers->set('ETag', '12345'); + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + $response->headers->set('Content-Type', 'text/plain'); + $response->setContent('Hello World'); + }); + + // only ETag matches + $t = \DateTime::createFromFormat('U', time() - 3600); + $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '12345', 'HTTP_IF_MODIFIED_SINCE' => $t->format(DATE_RFC2822))); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + + // only Last-Modified matches + $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '1234', 'HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822))); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + + // Both matches + $this->request('GET', '/', array('HTTP_IF_NONE_MATCH' => '12345', 'HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822))); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(304, $this->response->getStatusCode()); + } + + public function testValidatesPrivateResponsesCachedOnTheClient() + { + $this->setNextResponse(200, array(), '', function ($request, $response) { + $etags = preg_split('/\s*,\s*/', $request->headers->get('IF_NONE_MATCH')); + if ($request->cookies->has('authenticated')) { + $response->headers->set('Cache-Control', 'private, no-store'); + $response->setETag('"private tag"'); + if (in_array('"private tag"', $etags)) { + $response->setStatusCode(304); + } else { + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'text/plain'); + $response->setContent('private data'); + } + } else { + $response->headers->set('Cache-Control', 'public'); + $response->setETag('"public tag"'); + if (in_array('"public tag"', $etags)) { + $response->setStatusCode(304); + } else { + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'text/plain'); + $response->setContent('public data'); + } + } + }); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('"public tag"', $this->response->headers->get('ETag')); + $this->assertEquals('public data', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $this->request('GET', '/', array(), array('authenticated' => '')); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('"private tag"', $this->response->headers->get('ETag')); + $this->assertEquals('private data', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('invalid'); + $this->assertTraceNotContains('store'); + } + + public function testStoresResponsesWhenNoCacheRequestDirectivePresent() + { + $time = \DateTime::createFromFormat('U', time() + 5); + + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822))); + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache')); + + $this->assertHttpKernelIsCalled(); + $this->assertTraceContains('store'); + $this->assertTrue($this->response->headers->has('Age')); + } + + public function testReloadsResponsesWhenCacheHitsButNoCacheRequestDirectivePresentWhenAllowReloadIsSetTrue() + { + $count = 0; + + $this->setNextResponse(200, array('Cache-Control' => 'public, max-age=10000'), '', function ($request, $response) use (&$count) { + ++$count; + $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World'); + }); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('store'); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + $this->cacheConfig['allow_reload'] = true; + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Goodbye World', $this->response->getContent()); + $this->assertTraceContains('reload'); + $this->assertTraceContains('store'); + } + + public function testDoesNotReloadResponsesWhenAllowReloadIsSetFalseDefault() + { + $count = 0; + + $this->setNextResponse(200, array('Cache-Control' => 'public, max-age=10000'), '', function ($request, $response) use (&$count) { + ++$count; + $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World'); + }); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('store'); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + $this->cacheConfig['allow_reload'] = false; + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceNotContains('reload'); + + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'no-cache')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceNotContains('reload'); + } + + public function testRevalidatesFreshCacheEntryWhenMaxAgeRequestDirectiveIsExceededWhenAllowRevalidateOptionIsSetTrue() + { + $count = 0; + + $this->setNextResponse(200, array(), '', function ($request, $response) use (&$count) { + ++$count; + $response->headers->set('Cache-Control', 'public, max-age=10000'); + $response->setETag($count); + $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World'); + }); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('store'); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + $this->cacheConfig['allow_revalidate'] = true; + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'max-age=0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Goodbye World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('invalid'); + $this->assertTraceContains('store'); + } + + public function testDoesNotRevalidateFreshCacheEntryWhenEnableRevalidateOptionIsSetFalseDefault() + { + $count = 0; + + $this->setNextResponse(200, array(), '', function ($request, $response) use (&$count) { + ++$count; + $response->headers->set('Cache-Control', 'public, max-age=10000'); + $response->setETag($count); + $response->setContent(1 == $count ? 'Hello World' : 'Goodbye World'); + }); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('store'); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + $this->cacheConfig['allow_revalidate'] = false; + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'max-age=0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceNotContains('stale'); + $this->assertTraceNotContains('invalid'); + $this->assertTraceContains('fresh'); + + $this->request('GET', '/', array('HTTP_CACHE_CONTROL' => 'max-age=0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceNotContains('stale'); + $this->assertTraceNotContains('invalid'); + $this->assertTraceContains('fresh'); + } + + public function testFetchesResponseFromBackendWhenCacheMisses() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('miss'); + $this->assertTrue($this->response->headers->has('Age')); + } + + public function testDoesNotCacheSomeStatusCodeResponses() + { + foreach (array_merge(range(201, 202), range(204, 206), range(303, 305), range(400, 403), range(405, 409), range(411, 417), range(500, 505)) as $code) { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse($code, array('Expires' => $time->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertEquals($code, $this->response->getStatusCode()); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + } + + public function testDoesNotCacheResponsesWithExplicitNoStoreDirective() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Expires' => $time->format(DATE_RFC2822), 'Cache-Control' => 'no-store')); + + $this->request('GET', '/'); + $this->assertTraceNotContains('store'); + $this->assertFalse($this->response->headers->has('Age')); + } + + public function testDoesNotCacheResponsesWithoutFreshnessInformationOrAValidator() + { + $this->setNextResponse(); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceNotContains('store'); + } + + public function testCachesResponsesWithExplicitNoCacheDirective() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Expires' => $time->format(DATE_RFC2822), 'Cache-Control' => 'public, no-cache')); + + $this->request('GET', '/'); + $this->assertTraceContains('store'); + $this->assertTrue($this->response->headers->has('Age')); + } + + public function testCachesResponsesWithAnExpirationHeader() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + } + + public function testCachesResponsesWithAMaxAgeDirective() + { + $this->setNextResponse(200, array('Cache-Control' => 'public, max-age=5')); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + } + + public function testCachesResponsesWithASMaxAgeDirective() + { + $this->setNextResponse(200, array('Cache-Control' => 's-maxage=5')); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + } + + public function testCachesResponsesWithALastModifiedValidatorButNoFreshnessInformation() + { + $time = \DateTime::createFromFormat('U', time()); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Last-Modified' => $time->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + } + + public function testCachesResponsesWithAnETagValidatorButNoFreshnessInformation() + { + $this->setNextResponse(200, array('Cache-Control' => 'public', 'ETag' => '"123456"')); + + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + } + + public function testHitsCachedResponsesWithExpiresHeader() + { + $time1 = \DateTime::createFromFormat('U', time() - 5); + $time2 = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Date' => $time1->format(DATE_RFC2822), 'Expires' => $time2->format(DATE_RFC2822))); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTrue(strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')) < 2); + $this->assertTrue($this->response->headers->get('Age') > 0); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testHitsCachedResponseWithMaxAgeDirective() + { + $time = \DateTime::createFromFormat('U', time() - 5); + $this->setNextResponse(200, array('Date' => $time->format(DATE_RFC2822), 'Cache-Control' => 'public, max-age=10')); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTrue(strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')) < 2); + $this->assertTrue($this->response->headers->get('Age') > 0); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testHitsCachedResponseWithSMaxAgeDirective() + { + $time = \DateTime::createFromFormat('U', time() - 5); + $this->setNextResponse(200, array('Date' => $time->format(DATE_RFC2822), 'Cache-Control' => 's-maxage=10, max-age=0')); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTrue(strtotime($this->responses[0]->headers->get('Date')) - strtotime($this->response->headers->get('Date')) < 2); + $this->assertTrue($this->response->headers->get('Age') > 0); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testAssignsDefaultTtlWhenResponseHasNoFreshnessInformation() + { + $this->setNextResponse(); + + $this->cacheConfig['default_ttl'] = 10; + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertRegExp('/s-maxage=10/', $this->response->headers->get('Cache-Control')); + + $this->cacheConfig['default_ttl'] = 10; + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testDoesNotAssignDefaultTtlWhenResponseHasMustRevalidateDirective() + { + $this->setNextResponse(200, array('Cache-Control' => 'must-revalidate')); + + $this->cacheConfig['default_ttl'] = 10; + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('miss'); + $this->assertTraceNotContains('store'); + $this->assertNotRegExp('/s-maxage/', $this->response->headers->get('Cache-Control')); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testFetchesFullResponseWhenCacheStaleAndNoValidatorsPresent() + { + $time = \DateTime::createFromFormat('U', time() + 5); + $this->setNextResponse(200, array('Cache-Control' => 'public', 'Expires' => $time->format(DATE_RFC2822))); + + // build initial request + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Date')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertNotNull($this->response->headers->get('Age')); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + + # go in and play around with the cached metadata directly ... + $values = $this->getMetaStorageValues(); + $this->assertCount(1, $values); + $tmp = unserialize($values[0]); + $time = \DateTime::createFromFormat('U', time()); + $tmp[0][1]['expires'] = $time->format(DATE_RFC2822); + $r = new \ReflectionObject($this->store); + $m = $r->getMethod('save'); + $m->setAccessible(true); + $m->invoke($this->store, 'md'.sha1('http://localhost/'), serialize($tmp)); + + // build subsequent request; should be found but miss due to freshness + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTrue($this->response->headers->get('Age') <= 1); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTraceContains('stale'); + $this->assertTraceNotContains('fresh'); + $this->assertTraceNotContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Hello World', $this->response->getContent()); + } + + public function testValidatesCachedResponsesWithLastModifiedAndNoFreshnessInformation() + { + $time = \DateTime::createFromFormat('U', time()); + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($time) { + $response->headers->set('Cache-Control', 'public'); + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + if ($time->format(DATE_RFC2822) == $request->headers->get('IF_MODIFIED_SINCE')) { + $response->setStatusCode(304); + $response->setContent(''); + } + }); + + // build initial request + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Last-Modified')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertTraceNotContains('stale'); + + // build subsequent request; should be found but miss due to freshness + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('Last-Modified')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTrue($this->response->headers->get('Age') <= 1); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('valid'); + $this->assertTraceContains('store'); + $this->assertTraceNotContains('miss'); + } + + public function testValidatesCachedResponsesWithETagAndNoFreshnessInformation() + { + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) { + $response->headers->set('Cache-Control', 'public'); + $response->headers->set('ETag', '"12345"'); + if ($response->getETag() == $request->headers->get('IF_NONE_MATCH')) { + $response->setStatusCode(304); + $response->setContent(''); + } + }); + + // build initial request + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('ETag')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + // build subsequent request; should be found but miss due to freshness + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertNotNull($this->response->headers->get('ETag')); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $this->assertTrue($this->response->headers->get('Age') <= 1); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('valid'); + $this->assertTraceContains('store'); + $this->assertTraceNotContains('miss'); + } + + public function testReplacesCachedResponsesWhenValidationResultsInNon304Response() + { + $time = \DateTime::createFromFormat('U', time()); + $count = 0; + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($time, &$count) { + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + $response->headers->set('Cache-Control', 'public'); + switch (++$count) { + case 1: + $response->setContent('first response'); + break; + case 2: + $response->setContent('second response'); + break; + case 3: + $response->setContent(''); + $response->setStatusCode(304); + break; + } + }); + + // first request should fetch from backend and store in cache + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('first response', $this->response->getContent()); + + // second request is validated, is invalid, and replaces cached entry + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('second response', $this->response->getContent()); + + // third response is validated, valid, and returns cached entry + $this->request('GET', '/'); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('second response', $this->response->getContent()); + + $this->assertEquals(3, $count); + } + + public function testPassesHeadRequestsThroughDirectlyOnPass() + { + $that = $this; + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($that) { + $response->setContent(''); + $response->setStatusCode(200); + $that->assertEquals('HEAD', $request->getMethod()); + }); + + $this->request('HEAD', '/', array('HTTP_EXPECT' => 'something ...')); + $this->assertHttpKernelIsCalled(); + $this->assertEquals('', $this->response->getContent()); + } + + public function testUsesCacheToRespondToHeadRequestsWhenFresh() + { + $that = $this; + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($that) { + $response->headers->set('Cache-Control', 'public, max-age=10'); + $response->setContent('Hello World'); + $response->setStatusCode(200); + $that->assertNotEquals('HEAD', $request->getMethod()); + }); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('HEAD', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('', $this->response->getContent()); + $this->assertEquals(strlen('Hello World'), $this->response->headers->get('Content-Length')); + } + + public function testSendsNoContentWhenFresh() + { + $time = \DateTime::createFromFormat('U', time()); + $that = $this; + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) use ($that, $time) { + $response->headers->set('Cache-Control', 'public, max-age=10'); + $response->headers->set('Last-Modified', $time->format(DATE_RFC2822)); + }); + + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals('Hello World', $this->response->getContent()); + + $this->request('GET', '/', array('HTTP_IF_MODIFIED_SINCE' => $time->format(DATE_RFC2822))); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(304, $this->response->getStatusCode()); + $this->assertEquals('', $this->response->getContent()); + } + + public function testInvalidatesCachedResponsesOnPost() + { + $this->setNextResponse(200, array(), 'Hello World', function ($request, $response) { + if ('GET' == $request->getMethod()) { + $response->setStatusCode(200); + $response->headers->set('Cache-Control', 'public, max-age=500'); + $response->setContent('Hello World'); + } elseif ('POST' == $request->getMethod()) { + $response->setStatusCode(303); + $response->headers->set('Location', '/'); + $response->headers->remove('Cache-Control'); + $response->setContent(''); + } + }); + + // build initial request to enter into the cache + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + // make sure it is valid + $this->request('GET', '/'); + $this->assertHttpKernelIsNotCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('fresh'); + + // now POST to same URL + $this->request('POST', '/helloworld'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals('/', $this->response->headers->get('Location')); + $this->assertTraceContains('invalidate'); + $this->assertTraceContains('pass'); + $this->assertEquals('', $this->response->getContent()); + + // now make sure it was actually invalidated + $this->request('GET', '/'); + $this->assertHttpKernelIsCalled(); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Hello World', $this->response->getContent()); + $this->assertTraceContains('stale'); + $this->assertTraceContains('invalid'); + $this->assertTraceContains('store'); + } + + public function testServesFromCacheWhenHeadersMatch() + { + $count = 0; + $this->setNextResponse(200, array('Cache-Control' => 'max-age=10000'), '', function ($request, $response) use (&$count) { + $response->headers->set('Vary', 'Accept User-Agent Foo'); + $response->headers->set('Cache-Control', 'public, max-age=10'); + $response->headers->set('X-Response-Count', ++$count); + $response->setContent($request->headers->get('USER_AGENT')); + }); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Bob/1.0', $this->response->getContent()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Bob/1.0', $this->response->getContent()); + $this->assertTraceContains('fresh'); + $this->assertTraceNotContains('store'); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + } + + public function testStoresMultipleResponsesWhenHeadersDiffer() + { + $count = 0; + $this->setNextResponse(200, array('Cache-Control' => 'max-age=10000'), '', function ($request, $response) use (&$count) { + $response->headers->set('Vary', 'Accept User-Agent Foo'); + $response->headers->set('Cache-Control', 'public, max-age=10'); + $response->headers->set('X-Response-Count', ++$count); + $response->setContent($request->headers->get('USER_AGENT')); + }); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertEquals('Bob/1.0', $this->response->getContent()); + $this->assertEquals(1, $this->response->headers->get('X-Response-Count')); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/2.0')); + $this->assertEquals(200, $this->response->getStatusCode()); + $this->assertTraceContains('miss'); + $this->assertTraceContains('store'); + $this->assertEquals('Bob/2.0', $this->response->getContent()); + $this->assertEquals(2, $this->response->headers->get('X-Response-Count')); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/1.0')); + $this->assertTraceContains('fresh'); + $this->assertEquals('Bob/1.0', $this->response->getContent()); + $this->assertEquals(1, $this->response->headers->get('X-Response-Count')); + + $this->request('GET', '/', array('HTTP_ACCEPT' => 'text/html', 'HTTP_USER_AGENT' => 'Bob/2.0')); + $this->assertTraceContains('fresh'); + $this->assertEquals('Bob/2.0', $this->response->getContent()); + $this->assertEquals(2, $this->response->headers->get('X-Response-Count')); + + $this->request('GET', '/', array('HTTP_USER_AGENT' => 'Bob/2.0')); + $this->assertTraceContains('miss'); + $this->assertEquals('Bob/2.0', $this->response->getContent()); + $this->assertEquals(3, $this->response->headers->get('X-Response-Count')); + } + + public function testShouldCatchExceptions() + { + $this->catchExceptions(); + + $this->setNextResponse(); + $this->request('GET', '/'); + + $this->assertExceptionsAreCaught(); + } + + public function testShouldNotCatchExceptions() + { + $this->catchExceptions(false); + + $this->setNextResponse(); + $this->request('GET', '/'); + + $this->assertExceptionsAreNotCaught(); + } + + public function testEsiCacheSendsTheLowestTtl() + { + $responses = array( + array( + 'status' => 200, + 'body' => ' ', + 'headers' => array( + 'Cache-Control' => 's-maxage=300', + 'Surrogate-Control' => 'content="ESI/1.0"', + ), + ), + array( + 'status' => 200, + 'body' => 'Hello World!', + 'headers' => array('Cache-Control' => 's-maxage=300'), + ), + array( + 'status' => 200, + 'body' => 'My name is Bobby.', + 'headers' => array('Cache-Control' => 's-maxage=100'), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertEquals("Hello World! My name is Bobby.", $this->response->getContent()); + + // check for 100 or 99 as the test can be executed after a second change + $this->assertTrue(in_array($this->response->getTtl(), array(99, 100))); + } + + public function testEsiCacheForceValidation() + { + $responses = array( + array( + 'status' => 200, + 'body' => ' ', + 'headers' => array( + 'Cache-Control' => 's-maxage=300', + 'Surrogate-Control' => 'content="ESI/1.0"', + ), + ), + array( + 'status' => 200, + 'body' => 'Hello World!', + 'headers' => array('ETag' => 'foobar'), + ), + array( + 'status' => 200, + 'body' => 'My name is Bobby.', + 'headers' => array('Cache-Control' => 's-maxage=100'), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertEquals('Hello World! My name is Bobby.', $this->response->getContent()); + $this->assertNull($this->response->getTtl()); + $this->assertTrue($this->response->mustRevalidate()); + $this->assertTrue($this->response->headers->hasCacheControlDirective('private')); + $this->assertTrue($this->response->headers->hasCacheControlDirective('no-cache')); + } + + public function testEsiRecalculateContentLengthHeader() + { + $responses = array( + array( + 'status' => 200, + 'body' => '', + 'headers' => array( + 'Content-Length' => 26, + 'Cache-Control' => 's-maxage=300', + 'Surrogate-Control' => 'content="ESI/1.0"', + ), + ), + array( + 'status' => 200, + 'body' => 'Hello World!', + 'headers' => array(), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertEquals('Hello World!', $this->response->getContent()); + $this->assertEquals(12, $this->response->headers->get('Content-Length')); + } + + public function testClientIpIsAlwaysLocalhostForForwardedRequests() + { + $this->setNextResponse(); + $this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1')); + + $this->assertEquals('127.0.0.1', $this->kernel->getBackendRequest()->server->get('REMOTE_ADDR')); + } + + /** + * @dataProvider getXForwardedForData + */ + public function testXForwarderForHeaderForForwardedRequests($xForwardedFor, $expected) + { + $this->setNextResponse(); + $server = array('REMOTE_ADDR' => '10.0.0.1'); + if (false !== $xForwardedFor) { + $server['HTTP_X_FORWARDED_FOR'] = $xForwardedFor; + } + $this->request('GET', '/', $server); + + $this->assertEquals($expected, $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For')); + } + + public function getXForwardedForData() + { + return array( + array(false, '10.0.0.1'), + array('10.0.0.2', '10.0.0.2, 10.0.0.1'), + array('10.0.0.2, 10.0.0.3', '10.0.0.2, 10.0.0.3, 10.0.0.1'), + ); + } + + public function testXForwarderForHeaderForPassRequests() + { + $this->setNextResponse(); + $server = array('REMOTE_ADDR' => '10.0.0.1'); + $this->request('POST', '/', $server); + + $this->assertEquals('10.0.0.1', $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For')); + } + + public function testEsiCacheRemoveValidationHeadersIfEmbeddedResponses() + { + $time = new \DateTime; + + $responses = array( + array( + 'status' => 200, + 'body' => '', + 'headers' => array( + 'Surrogate-Control' => 'content="ESI/1.0"', + 'ETag' => 'hey', + 'Last-Modified' => $time->format(DATE_RFC2822), + ), + ), + array( + 'status' => 200, + 'body' => 'Hey!', + 'headers' => array(), + ), + ); + + $this->setNextResponses($responses); + + $this->request('GET', '/', array(), array(), true); + $this->assertNull($this->response->getETag()); + $this->assertNull($this->response->getLastModified()); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php new file mode 100644 index 0000000..4377f61 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/HttpCacheTestCase.php @@ -0,0 +1,179 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpCache\Esi; +use Symfony\Component\HttpKernel\HttpCache\HttpCache; +use Symfony\Component\HttpKernel\HttpCache\Store; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +class HttpCacheTestCase extends \PHPUnit_Framework_TestCase +{ + protected $kernel; + protected $cache; + protected $caches; + protected $cacheConfig; + protected $request; + protected $response; + protected $responses; + protected $catch; + protected $esi; + + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + + $this->kernel = null; + + $this->cache = null; + $this->esi = null; + $this->caches = array(); + $this->cacheConfig = array(); + + $this->request = null; + $this->response = null; + $this->responses = array(); + + $this->catch = false; + + $this->clearDirectory(sys_get_temp_dir().'/http_cache'); + } + + protected function tearDown() + { + $this->kernel = null; + $this->cache = null; + $this->caches = null; + $this->request = null; + $this->response = null; + $this->responses = null; + $this->cacheConfig = null; + $this->catch = null; + $this->esi = null; + + $this->clearDirectory(sys_get_temp_dir().'/http_cache'); + } + + public function assertHttpKernelIsCalled() + { + $this->assertTrue($this->kernel->hasBeenCalled()); + } + + public function assertHttpKernelIsNotCalled() + { + $this->assertFalse($this->kernel->hasBeenCalled()); + } + + public function assertResponseOk() + { + $this->assertEquals(200, $this->response->getStatusCode()); + } + + public function assertTraceContains($trace) + { + $traces = $this->cache->getTraces(); + $traces = current($traces); + + $this->assertRegExp('/'.$trace.'/', implode(', ', $traces)); + } + + public function assertTraceNotContains($trace) + { + $traces = $this->cache->getTraces(); + $traces = current($traces); + + $this->assertNotRegExp('/'.$trace.'/', implode(', ', $traces)); + } + + public function assertExceptionsAreCaught() + { + $this->assertTrue($this->kernel->isCatchingExceptions()); + } + + public function assertExceptionsAreNotCaught() + { + $this->assertFalse($this->kernel->isCatchingExceptions()); + } + + public function request($method, $uri = '/', $server = array(), $cookies = array(), $esi = false) + { + if (null === $this->kernel) { + throw new \LogicException('You must call setNextResponse() before calling request().'); + } + + $this->kernel->reset(); + + $this->store = new Store(sys_get_temp_dir().'/http_cache'); + + $this->cacheConfig['debug'] = true; + + $this->esi = $esi ? new Esi() : null; + $this->cache = new HttpCache($this->kernel, $this->store, $this->esi, $this->cacheConfig); + $this->request = Request::create($uri, $method, array(), $cookies, array(), $server); + + $this->response = $this->cache->handle($this->request, HttpKernelInterface::MASTER_REQUEST, $this->catch); + + $this->responses[] = $this->response; + } + + public function getMetaStorageValues() + { + $values = array(); + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(sys_get_temp_dir().'/http_cache/md', \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + $values[] = file_get_contents($file); + } + + return $values; + } + + // A basic response with 200 status code and a tiny body. + public function setNextResponse($statusCode = 200, array $headers = array(), $body = 'Hello World', \Closure $customizer = null) + { + $this->kernel = new TestHttpKernel($body, $statusCode, $headers, $customizer); + } + + public function setNextResponses($responses) + { + $this->kernel = new TestMultipleHttpKernel($responses); + } + + public function catchExceptions($catch = true) + { + $this->catch = $catch; + } + + public static function clearDirectory($directory) + { + if (!is_dir($directory)) { + return; + } + + $fp = opendir($directory); + while (false !== $file = readdir($fp)) { + if (!in_array($file, array('.', '..'))) { + if (is_link($directory.'/'.$file)) { + unlink($directory.'/'.$file); + } elseif (is_dir($directory.'/'.$file)) { + self::clearDirectory($directory.'/'.$file); + rmdir($directory.'/'.$file); + } else { + unlink($directory.'/'.$file); + } + } + } + + closedir($fp); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php new file mode 100644 index 0000000..6a3e565 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/StoreTest.php @@ -0,0 +1,263 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpCache\Store; + +class StoreTest extends \PHPUnit_Framework_TestCase +{ + protected $request; + protected $response; + protected $store; + + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + + $this->request = Request::create('/'); + $this->response = new Response('hello world', 200, array()); + + HttpCacheTestCase::clearDirectory(sys_get_temp_dir().'/http_cache'); + + $this->store = new Store(sys_get_temp_dir().'/http_cache'); + } + + protected function tearDown() + { + $this->store = null; + $this->request = null; + $this->response = null; + + HttpCacheTestCase::clearDirectory(sys_get_temp_dir().'/http_cache'); + } + + public function testReadsAnEmptyArrayWithReadWhenNothingCachedAtKey() + { + $this->assertEmpty($this->getStoreMetadata('/nothing')); + } + + public function testUnlockFileThatDoesExist() + { + $cacheKey = $this->storeSimpleEntry(); + $this->store->lock($this->request); + + $this->assertTrue($this->store->unlock($this->request)); + } + + public function testUnlockFileThatDoesNotExist() + { + $this->assertFalse($this->store->unlock($this->request)); + } + + public function testRemovesEntriesForKeyWithPurge() + { + $request = Request::create('/foo'); + $this->store->write($request, new Response('foo')); + + $metadata = $this->getStoreMetadata($request); + $this->assertNotEmpty($metadata); + + $this->assertTrue($this->store->purge('/foo')); + $this->assertEmpty($this->getStoreMetadata($request)); + + // cached content should be kept after purging + $path = $this->store->getPath($metadata[0][1]['x-content-digest'][0]); + $this->assertTrue(is_file($path)); + + $this->assertFalse($this->store->purge('/bar')); + } + + public function testStoresACacheEntry() + { + $cacheKey = $this->storeSimpleEntry(); + + $this->assertNotEmpty($this->getStoreMetadata($cacheKey)); + } + + public function testSetsTheXContentDigestResponseHeaderBeforeStoring() + { + $cacheKey = $this->storeSimpleEntry(); + $entries = $this->getStoreMetadata($cacheKey); + list ($req, $res) = $entries[0]; + + $this->assertEquals('ena94a8fe5ccb19ba61c4c0873d391e987982fbbd3', $res['x-content-digest'][0]); + } + + public function testFindsAStoredEntryWithLookup() + { + $this->storeSimpleEntry(); + $response = $this->store->lookup($this->request); + + $this->assertNotNull($response); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); + } + + public function testDoesNotFindAnEntryWithLookupWhenNoneExists() + { + $request = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + + $this->assertNull($this->store->lookup($request)); + } + + public function testCanonizesUrlsForCacheKeys() + { + $this->storeSimpleEntry($path = '/test?x=y&p=q'); + $hitsReq = Request::create($path); + $missReq = Request::create('/test?p=x'); + + $this->assertNotNull($this->store->lookup($hitsReq)); + $this->assertNull($this->store->lookup($missReq)); + } + + public function testDoesNotFindAnEntryWithLookupWhenTheBodyDoesNotExist() + { + $this->storeSimpleEntry(); + $this->assertNotNull($this->response->headers->get('X-Content-Digest')); + $path = $this->getStorePath($this->response->headers->get('X-Content-Digest')); + @unlink($path); + $this->assertNull($this->store->lookup($this->request)); + } + + public function testRestoresResponseHeadersProperlyWithLookup() + { + $this->storeSimpleEntry(); + $response = $this->store->lookup($this->request); + + $this->assertEquals($response->headers->all(), array_merge(array('content-length' => 4, 'x-body-file' => array($this->getStorePath($response->headers->get('X-Content-Digest')))), $this->response->headers->all())); + } + + public function testRestoresResponseContentFromEntityStoreWithLookup() + { + $this->storeSimpleEntry(); + $response = $this->store->lookup($this->request); + $this->assertEquals($this->getStorePath('en'.sha1('test')), $response->getContent()); + } + + public function testInvalidatesMetaAndEntityStoreEntriesWithInvalidate() + { + $this->storeSimpleEntry(); + $this->store->invalidate($this->request); + $response = $this->store->lookup($this->request); + $this->assertInstanceOf('Symfony\Component\HttpFoundation\Response', $response); + $this->assertFalse($response->isFresh()); + } + + public function testSucceedsQuietlyWhenInvalidateCalledWithNoMatchingEntries() + { + $req = Request::create('/test'); + $this->store->invalidate($req); + $this->assertNull($this->store->lookup($this->request)); + } + + public function testDoesNotReturnEntriesThatVaryWithLookup() + { + $req1 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $req2 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam')); + $res = new Response('test', 200, array('Vary' => 'Foo Bar')); + $this->store->write($req1, $res); + + $this->assertNull($this->store->lookup($req2)); + } + + public function testStoresMultipleResponsesForEachVaryCombination() + { + $req1 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $res1 = new Response('test 1', 200, array('Vary' => 'Foo Bar')); + $key = $this->store->write($req1, $res1); + + $req2 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam')); + $res2 = new Response('test 2', 200, array('Vary' => 'Foo Bar')); + $this->store->write($req2, $res2); + + $req3 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Baz', 'HTTP_BAR' => 'Boom')); + $res3 = new Response('test 3', 200, array('Vary' => 'Foo Bar')); + $this->store->write($req3, $res3); + + $this->assertEquals($this->getStorePath('en'.sha1('test 3')), $this->store->lookup($req3)->getContent()); + $this->assertEquals($this->getStorePath('en'.sha1('test 2')), $this->store->lookup($req2)->getContent()); + $this->assertEquals($this->getStorePath('en'.sha1('test 1')), $this->store->lookup($req1)->getContent()); + + $this->assertCount(3, $this->getStoreMetadata($key)); + } + + public function testOverwritesNonVaryingResponseWithStore() + { + $req1 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $res1 = new Response('test 1', 200, array('Vary' => 'Foo Bar')); + $key = $this->store->write($req1, $res1); + $this->assertEquals($this->getStorePath('en'.sha1('test 1')), $this->store->lookup($req1)->getContent()); + + $req2 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam')); + $res2 = new Response('test 2', 200, array('Vary' => 'Foo Bar')); + $this->store->write($req2, $res2); + $this->assertEquals($this->getStorePath('en'.sha1('test 2')), $this->store->lookup($req2)->getContent()); + + $req3 = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $res3 = new Response('test 3', 200, array('Vary' => 'Foo Bar')); + $key = $this->store->write($req3, $res3); + $this->assertEquals($this->getStorePath('en'.sha1('test 3')), $this->store->lookup($req3)->getContent()); + + $this->assertCount(2, $this->getStoreMetadata($key)); + } + + public function testLocking() + { + $req = Request::create('/test', 'get', array(), array(), array(), array('HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar')); + $this->assertTrue($this->store->lock($req)); + + $path = $this->store->lock($req); + $this->assertTrue($this->store->isLocked($req)); + + $this->store->unlock($req); + $this->assertFalse($this->store->isLocked($req)); + } + + protected function storeSimpleEntry($path = null, $headers = array()) + { + if (null === $path) { + $path = '/test'; + } + + $this->request = Request::create($path, 'get', array(), array(), array(), $headers); + $this->response = new Response('test', 200, array('Cache-Control' => 'max-age=420')); + + return $this->store->write($this->request, $this->response); + } + + protected function getStoreMetadata($key) + { + $r = new \ReflectionObject($this->store); + $m = $r->getMethod('getMetadata'); + $m->setAccessible(true); + + if ($key instanceof Request) { + $m1 = $r->getMethod('getCacheKey'); + $m1->setAccessible(true); + $key = $m1->invoke($this->store, $key); + } + + return $m->invoke($this->store, $key); + } + + protected function getStorePath($key) + { + $r = new \ReflectionObject($this->store); + $m = $r->getMethod('getPath'); + $m->setAccessible(true); + + return $m->invoke($this->store, $key); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/TestHttpKernel.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/TestHttpKernel.php new file mode 100644 index 0000000..cf23d7b --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/TestHttpKernel.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class TestHttpKernel extends HttpKernel implements ControllerResolverInterface +{ + protected $body; + protected $status; + protected $headers; + protected $called; + protected $customizer; + protected $catch; + protected $backendRequest; + + public function __construct($body, $status, $headers, \Closure $customizer = null) + { + $this->body = $body; + $this->status = $status; + $this->headers = $headers; + $this->customizer = $customizer; + $this->called = false; + $this->catch = false; + + parent::__construct(new EventDispatcher(), $this); + } + + public function getBackendRequest() + { + return $this->backendRequest; + } + + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = false) + { + $this->catch = $catch; + $this->backendRequest = $request; + + return parent::handle($request, $type, $catch); + } + + public function isCatchingExceptions() + { + return $this->catch; + } + + public function getController(Request $request) + { + return array($this, 'callController'); + } + + public function getArguments(Request $request, $controller) + { + return array($request); + } + + public function callController(Request $request) + { + $this->called = true; + + $response = new Response($this->body, $this->status, $this->headers); + + if (null !== $this->customizer) { + call_user_func($this->customizer, $request, $response); + } + + return $response; + } + + public function hasBeenCalled() + { + return $this->called; + } + + public function reset() + { + $this->called = false; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/TestMultipleHttpKernel.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/TestMultipleHttpKernel.php new file mode 100644 index 0000000..6dd3d9e --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpCache/TestMultipleHttpKernel.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\HttpCache; + +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class TestMultipleHttpKernel extends HttpKernel implements ControllerResolverInterface +{ + protected $bodies; + protected $statuses; + protected $headers; + protected $catch; + protected $call; + protected $backendRequest; + + public function __construct($responses) + { + $this->bodies = array(); + $this->statuses = array(); + $this->headers = array(); + $this->call = false; + + foreach ($responses as $response) { + $this->bodies[] = $response['body']; + $this->statuses[] = $response['status']; + $this->headers[] = $response['headers']; + } + + parent::__construct(new EventDispatcher(), $this); + } + + public function getBackendRequest() + { + return $this->backendRequest; + } + + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = false) + { + $this->backendRequest = $request; + + return parent::handle($request, $type, $catch); + } + + public function getController(Request $request) + { + return array($this, 'callController'); + } + + public function getArguments(Request $request, $controller) + { + return array($request); + } + + public function callController(Request $request) + { + $this->called = true; + + $response = new Response(array_shift($this->bodies), array_shift($this->statuses), array_shift($this->headers)); + + return $response; + } + + public function hasBeenCalled() + { + return $this->called; + } + + public function reset() + { + $this->call = false; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php new file mode 100644 index 0000000..367e3e2 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/HttpKernelTest.php @@ -0,0 +1,297 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class HttpKernelTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) { + $this->markTestSkipped('The "EventDispatcher" component is not available'); + } + + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + /** + * @expectedException RuntimeException + */ + public function testHandleWhenControllerThrowsAnExceptionAndRawIsTrue() + { + $kernel = new HttpKernel(new EventDispatcher(), $this->getResolver(function () { throw new \RuntimeException(); })); + + $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, true); + } + + /** + * @expectedException RuntimeException + */ + public function testHandleWhenControllerThrowsAnExceptionAndRawIsFalseAndNoListenerIsRegistered() + { + $kernel = new HttpKernel(new EventDispatcher(), $this->getResolver(function () { throw new \RuntimeException(); })); + + $kernel->handle(new Request(), HttpKernelInterface::MASTER_REQUEST, false); + } + + public function testHandleWhenControllerThrowsAnExceptionAndRawIsFalse() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) { + $event->setResponse(new Response($event->getException()->getMessage())); + }); + + $kernel = new HttpKernel($dispatcher, $this->getResolver(function () { throw new \RuntimeException('foo'); })); + $response = $kernel->handle(new Request()); + + $this->assertEquals('500', $response->getStatusCode()); + $this->assertEquals('foo', $response->getContent()); + } + + public function testHandleExceptionWithARedirectionResponse() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) { + $event->setResponse(new RedirectResponse('/login', 301)); + }); + + $kernel = new HttpKernel($dispatcher, $this->getResolver(function () { throw new AccessDeniedHttpException(); })); + $response = $kernel->handle(new Request()); + + $this->assertEquals('301', $response->getStatusCode()); + $this->assertEquals('/login', $response->headers->get('Location')); + } + + public function testHandleHttpException() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) { + $event->setResponse(new Response($event->getException()->getMessage())); + }); + + $kernel = new HttpKernel($dispatcher, $this->getResolver(function () { throw new MethodNotAllowedHttpException(array('POST')); })); + $response = $kernel->handle(new Request()); + + $this->assertEquals('405', $response->getStatusCode()); + $this->assertEquals('POST', $response->headers->get('Allow')); + } + + /** + * @dataProvider getStatusCodes + */ + public function testHandleWhenAnExceptionIsHandledWithASpecificStatusCode($responseStatusCode, $expectedStatusCode) + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::EXCEPTION, function ($event) use ($responseStatusCode, $expectedStatusCode) { + $event->setResponse(new Response('', $responseStatusCode, array('X-Status-Code' => $expectedStatusCode))); + }); + + $kernel = new HttpKernel($dispatcher, $this->getResolver(function () { throw new \RuntimeException(); })); + $response = $kernel->handle(new Request()); + + $this->assertEquals($expectedStatusCode, $response->getStatusCode()); + $this->assertFalse($response->headers->has('X-Status-Code')); + } + + public function getStatusCodes() + { + return array( + array(200, 404), + array(404, 200), + array(301, 200), + array(500, 200), + ); + } + + public function testHandleWhenAListenerReturnsAResponse() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::REQUEST, function ($event) { + $event->setResponse(new Response('hello')); + }); + + $kernel = new HttpKernel($dispatcher, $this->getResolver()); + + $this->assertEquals('hello', $kernel->handle(new Request())->getContent()); + } + + /** + * @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + public function testHandleWhenNoControllerIsFound() + { + $dispatcher = new EventDispatcher(); + $kernel = new HttpKernel($dispatcher, $this->getResolver(false)); + + $kernel->handle(new Request()); + } + + /** + * @expectedException LogicException + */ + public function testHandleWhenTheControllerIsNotACallable() + { + $dispatcher = new EventDispatcher(); + $kernel = new HttpKernel($dispatcher, $this->getResolver('foobar')); + + $kernel->handle(new Request()); + } + + public function testHandleWhenTheControllerIsAClosure() + { + $response = new Response('foo'); + $dispatcher = new EventDispatcher(); + $kernel = new HttpKernel($dispatcher, $this->getResolver(function () use ($response) { return $response; })); + + $this->assertSame($response, $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAnObjectWithInvoke() + { + $dispatcher = new EventDispatcher(); + $kernel = new HttpKernel($dispatcher, $this->getResolver(new Controller())); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAFunction() + { + $dispatcher = new EventDispatcher(); + $kernel = new HttpKernel($dispatcher, $this->getResolver('Symfony\Component\HttpKernel\Tests\controller_func')); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAnArray() + { + $dispatcher = new EventDispatcher(); + $kernel = new HttpKernel($dispatcher, $this->getResolver(array(new Controller(), 'controller'))); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + public function testHandleWhenTheControllerIsAStaticArray() + { + $dispatcher = new EventDispatcher(); + $kernel = new HttpKernel($dispatcher, $this->getResolver(array('Symfony\Component\HttpKernel\Tests\Controller', 'staticcontroller'))); + + $this->assertResponseEquals(new Response('foo'), $kernel->handle(new Request())); + } + + /** + * @expectedException LogicException + */ + public function testHandleWhenTheControllerDoesNotReturnAResponse() + { + $dispatcher = new EventDispatcher(); + $kernel = new HttpKernel($dispatcher, $this->getResolver(function () { return 'foo'; })); + + $kernel->handle(new Request()); + } + + public function testHandleWhenTheControllerDoesNotReturnAResponseButAViewIsRegistered() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::VIEW, function ($event) { + $event->setResponse(new Response($event->getControllerResult())); + }); + $kernel = new HttpKernel($dispatcher, $this->getResolver(function () { return 'foo'; })); + + $this->assertEquals('foo', $kernel->handle(new Request())->getContent()); + } + + public function testHandleWithAResponseListener() + { + $dispatcher = new EventDispatcher(); + $dispatcher->addListener(KernelEvents::RESPONSE, function ($event) { + $event->setResponse(new Response('foo')); + }); + $kernel = new HttpKernel($dispatcher, $this->getResolver()); + + $this->assertEquals('foo', $kernel->handle(new Request())->getContent()); + } + + public function testTerminate() + { + $dispatcher = new EventDispatcher(); + $kernel = new HttpKernel($dispatcher, $this->getResolver()); + $dispatcher->addListener(KernelEvents::TERMINATE, function ($event) use (&$called, &$capturedKernel, &$capturedRequest, &$capturedResponse) { + $called = true; + $capturedKernel = $event->getKernel(); + $capturedRequest = $event->getRequest(); + $capturedResponse = $event->getResponse(); + }); + + $kernel->terminate($request = Request::create('/'), $response = new Response()); + $this->assertTrue($called); + $this->assertEquals($kernel, $capturedKernel); + $this->assertEquals($request, $capturedRequest); + $this->assertEquals($response, $capturedResponse); + } + + protected function getResolver($controller = null) + { + if (null === $controller) { + $controller = function() { return new Response('Hello'); }; + } + + $resolver = $this->getMock('Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface'); + $resolver->expects($this->any()) + ->method('getController') + ->will($this->returnValue($controller)); + $resolver->expects($this->any()) + ->method('getArguments') + ->will($this->returnValue(array())); + + return $resolver; + } + + protected function assertResponseEquals(Response $expected, Response $actual) + { + $expected->setDate($actual->getDate()); + $this->assertEquals($expected, $actual); + } +} + +class Controller +{ + public function __invoke() + { + return new Response('foo'); + } + + public function controller() + { + return new Response('foo'); + } + + public static function staticController() + { + return new Response('foo'); + } +} + +function controller_func() +{ + return new Response('foo'); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/KernelTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/KernelTest.php new file mode 100644 index 0000000..b36f909 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/KernelTest.php @@ -0,0 +1,875 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\Bundle\Bundle; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest; +use Symfony\Component\HttpKernel\Tests\Fixtures\KernelForOverrideName; +use Symfony\Component\HttpKernel\Tests\Fixtures\FooBarBundle; + +class KernelTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\DependencyInjection\Container')) { + $this->markTestSkipped('The "DependencyInjection" component is not available'); + } + } + + public function testConstructor() + { + $env = 'test_env'; + $debug = true; + $kernel = new KernelForTest($env, $debug); + + $this->assertEquals($env, $kernel->getEnvironment()); + $this->assertEquals($debug, $kernel->isDebug()); + $this->assertFalse($kernel->isBooted()); + $this->assertLessThanOrEqual(microtime(true), $kernel->getStartTime()); + $this->assertNull($kernel->getContainer()); + } + + public function testClone() + { + $env = 'test_env'; + $debug = true; + $kernel = new KernelForTest($env, $debug); + + $clone = clone $kernel; + + $this->assertEquals($env, $clone->getEnvironment()); + $this->assertEquals($debug, $clone->isDebug()); + $this->assertFalse($clone->isBooted()); + $this->assertLessThanOrEqual(microtime(true), $clone->getStartTime()); + $this->assertNull($clone->getContainer()); + } + + public function testBootInitializesBundlesAndContainer() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('initializeBundles', 'initializeContainer', 'getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('initializeBundles'); + $kernel->expects($this->once()) + ->method('initializeContainer'); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array())); + + $kernel->boot(); + } + + public function testBootSetsTheContainerToTheBundles() + { + $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle') + ->disableOriginalConstructor() + ->getMock(); + $bundle->expects($this->once()) + ->method('setContainer'); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('initializeBundles', 'initializeContainer', 'getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array($bundle))); + + $kernel->boot(); + } + + public function testBootSetsTheBootedFlagToTrue() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('initializeBundles', 'initializeContainer', 'getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array())); + + $kernel->boot(); + + $this->assertTrue($kernel->isBooted()); + } + + public function testClassCacheIsLoaded() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('initializeBundles', 'initializeContainer', 'getBundles', 'doLoadClassCache')) + ->getMock(); + $kernel->loadClassCache('name', '.extension'); + $kernel->expects($this->any()) + ->method('getBundles') + ->will($this->returnValue(array())); + $kernel->expects($this->once()) + ->method('doLoadClassCache') + ->with('name', '.extension'); + + $kernel->boot(); + } + + public function testClassCacheIsNotLoadedByDefault() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('initializeBundles', 'initializeContainer', 'getBundles', 'doLoadClassCache')) + ->getMock(); + $kernel->expects($this->any()) + ->method('getBundles') + ->will($this->returnValue(array())); + $kernel->expects($this->never()) + ->method('doLoadClassCache'); + + $kernel->boot(); + } + + public function testClassCacheIsNotLoadedWhenKernelIsNotBooted() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('initializeBundles', 'initializeContainer', 'getBundles', 'doLoadClassCache')) + ->getMock(); + $kernel->loadClassCache(); + $kernel->expects($this->any()) + ->method('getBundles') + ->will($this->returnValue(array())); + $kernel->expects($this->never()) + ->method('doLoadClassCache'); + } + + public function testBootKernelSeveralTimesOnlyInitializesBundlesOnce() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('initializeBundles', 'initializeContainer', 'getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array())); + + $kernel->boot(); + $kernel->boot(); + } + + public function testShutdownCallsShutdownOnAllBundles() + { + $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle') + ->disableOriginalConstructor() + ->getMock(); + $bundle->expects($this->once()) + ->method('shutdown'); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array($bundle))); + + $kernel->shutdown(); + } + + public function testShutdownGivesNullContainerToAllBundles() + { + $bundle = $this->getMockBuilder('Symfony\Component\HttpKernel\Bundle\Bundle') + ->disableOriginalConstructor() + ->getMock(); + $bundle->expects($this->once()) + ->method('setContainer') + ->with(null); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array($bundle))); + + $kernel->shutdown(); + } + + public function testHandleCallsHandleOnHttpKernel() + { + $type = HttpKernelInterface::MASTER_REQUEST; + $catch = true; + $request = new Request(); + + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernel') + ->disableOriginalConstructor() + ->getMock(); + $httpKernelMock + ->expects($this->once()) + ->method('handle') + ->with($request, $type, $catch); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getHttpKernel')) + ->getMock(); + + $kernel->expects($this->once()) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->handle($request, $type, $catch); + } + + public function testHandleBootsTheKernel() + { + $type = HttpKernelInterface::MASTER_REQUEST; + $catch = true; + $request = new Request(); + + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernel') + ->disableOriginalConstructor() + ->getMock(); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getHttpKernel', 'boot')) + ->getMock(); + + $kernel->expects($this->once()) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->expects($this->once()) + ->method('boot'); + + // required as this value is initialized + // in the kernel constructor, which we don't call + $kernel->setIsBooted(false); + + $kernel->handle($request, $type, $catch); + } + + public function testStripComments() + { + if (!function_exists('token_get_all')) { + $this->markTestSkipped('The function token_get_all() is not available.'); + + return; + } + $source = <<<'EOF' +assertEquals($expected, $output); + } + + public function testIsClassInActiveBundleFalse() + { + $kernel = $this->getKernelMockForIsClassInActiveBundleTest(); + + $this->assertFalse($kernel->isClassInActiveBundle('Not\In\Active\Bundle')); + } + + public function testIsClassInActiveBundleFalseNoNamespace() + { + $kernel = $this->getKernelMockForIsClassInActiveBundleTest(); + + $this->assertFalse($kernel->isClassInActiveBundle('NotNamespacedClass')); + } + + public function testIsClassInActiveBundleTrue() + { + $kernel = $this->getKernelMockForIsClassInActiveBundleTest(); + + $this->assertTrue($kernel->isClassInActiveBundle(__NAMESPACE__.'\Fixtures\FooBarBundle\SomeClass')); + } + + protected function getKernelMockForIsClassInActiveBundleTest() + { + $bundle = new FooBarBundle(); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getBundles')) + ->getMock(); + $kernel->expects($this->once()) + ->method('getBundles') + ->will($this->returnValue(array($bundle))); + + return $kernel; + } + + public function testGetRootDir() + { + $kernel = new KernelForTest('test', true); + + $this->assertEquals(__DIR__.DIRECTORY_SEPARATOR.'Fixtures', realpath($kernel->getRootDir())); + } + + public function testGetName() + { + $kernel = new KernelForTest('test', true); + + $this->assertEquals('Fixtures', $kernel->getName()); + } + + public function testOverrideGetName() + { + $kernel = new KernelForOverrideName('test', true); + + $this->assertEquals('overridden', $kernel->getName()); + } + + public function testSerialize() + { + $env = 'test_env'; + $debug = true; + $kernel = new KernelForTest($env, $debug); + + $expected = serialize(array($env, $debug)); + $this->assertEquals($expected, $kernel->serialize()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLocateResourceThrowsExceptionWhenNameIsNotValid() + { + $this->getKernelForInvalidLocateResource()->locateResource('Foo'); + } + + /** + * @expectedException \RuntimeException + */ + public function testLocateResourceThrowsExceptionWhenNameIsUnsafe() + { + $this->getKernelForInvalidLocateResource()->locateResource('@FooBundle/../bar'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLocateResourceThrowsExceptionWhenBundleDoesNotExist() + { + $this->getKernelForInvalidLocateResource()->locateResource('@FooBundle/config/routing.xml'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLocateResourceThrowsExceptionWhenResourceDoesNotExist() + { + $kernel = $this->getKernel(); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle')))) + ; + + $kernel->locateResource('@Bundle1Bundle/config/routing.xml'); + } + + public function testLocateResourceReturnsTheFirstThatMatches() + { + $kernel = $this->getKernel(); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle')))) + ; + + $this->assertEquals(__DIR__.'/Fixtures/Bundle1Bundle/foo.txt', $kernel->locateResource('@Bundle1Bundle/foo.txt')); + } + + public function testLocateResourceReturnsTheFirstThatMatchesWithParent() + { + $parent = $this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle'); + $child = $this->getBundle(__DIR__.'/Fixtures/Bundle2Bundle'); + + $kernel = $this->getKernel(); + $kernel + ->expects($this->exactly(2)) + ->method('getBundle') + ->will($this->returnValue(array($child, $parent))) + ; + + $this->assertEquals(__DIR__.'/Fixtures/Bundle2Bundle/foo.txt', $kernel->locateResource('@ParentAABundle/foo.txt')); + $this->assertEquals(__DIR__.'/Fixtures/Bundle1Bundle/bar.txt', $kernel->locateResource('@ParentAABundle/bar.txt')); + } + + public function testLocateResourceReturnsAllMatches() + { + $parent = $this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle'); + $child = $this->getBundle(__DIR__.'/Fixtures/Bundle2Bundle'); + + $kernel = $this->getKernel(); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($child, $parent))) + ; + + $this->assertEquals(array( + __DIR__.'/Fixtures/Bundle2Bundle/foo.txt', + __DIR__.'/Fixtures/Bundle1Bundle/foo.txt'), + $kernel->locateResource('@Bundle1Bundle/foo.txt', null, false)); + } + + public function testLocateResourceReturnsAllMatchesBis() + { + $kernel = $this->getKernel(); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array( + $this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle'), + $this->getBundle(__DIR__.'/Foobar') + ))) + ; + + $this->assertEquals( + array(__DIR__.'/Fixtures/Bundle1Bundle/foo.txt'), + $kernel->locateResource('@Bundle1Bundle/foo.txt', null, false) + ); + } + + public function testLocateResourceIgnoresDirOnNonResource() + { + $kernel = $this->getKernel(); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle')))) + ; + + $this->assertEquals( + __DIR__.'/Fixtures/Bundle1Bundle/foo.txt', + $kernel->locateResource('@Bundle1Bundle/foo.txt', __DIR__.'/Fixtures') + ); + } + + public function testLocateResourceReturnsTheDirOneForResources() + { + $kernel = $this->getKernel(); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/FooBundle', null, null, 'FooBundle')))) + ; + + $this->assertEquals( + __DIR__.'/Fixtures/Resources/FooBundle/foo.txt', + $kernel->locateResource('@FooBundle/Resources/foo.txt', __DIR__.'/Fixtures/Resources') + ); + } + + public function testLocateResourceReturnsTheDirOneForResourcesAndBundleOnes() + { + $kernel = $this->getKernel(); + $kernel + ->expects($this->once()) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle', null, null, 'Bundle1Bundle')))) + ; + + $this->assertEquals(array( + __DIR__.'/Fixtures/Resources/Bundle1Bundle/foo.txt', + __DIR__.'/Fixtures/Bundle1Bundle/Resources/foo.txt'), + $kernel->locateResource('@Bundle1Bundle/Resources/foo.txt', __DIR__.'/Fixtures/Resources', false) + ); + } + + public function testLocateResourceOverrideBundleAndResourcesFolders() + { + $parent = $this->getBundle(__DIR__.'/Fixtures/BaseBundle', null, 'BaseBundle', 'BaseBundle'); + $child = $this->getBundle(__DIR__.'/Fixtures/ChildBundle', 'ParentBundle', 'ChildBundle', 'ChildBundle'); + + $kernel = $this->getKernel(); + $kernel + ->expects($this->exactly(4)) + ->method('getBundle') + ->will($this->returnValue(array($child, $parent))) + ; + + $this->assertEquals(array( + __DIR__.'/Fixtures/Resources/ChildBundle/foo.txt', + __DIR__.'/Fixtures/ChildBundle/Resources/foo.txt', + __DIR__.'/Fixtures/BaseBundle/Resources/foo.txt', + ), + $kernel->locateResource('@BaseBundle/Resources/foo.txt', __DIR__.'/Fixtures/Resources', false) + ); + + $this->assertEquals( + __DIR__.'/Fixtures/Resources/ChildBundle/foo.txt', + $kernel->locateResource('@BaseBundle/Resources/foo.txt', __DIR__.'/Fixtures/Resources') + ); + + try { + $kernel->locateResource('@BaseBundle/Resources/hide.txt', __DIR__.'/Fixtures/Resources', false); + $this->fail('Hidden resources should raise an exception when returning an array of matching paths'); + } catch (\RuntimeException $e) { + } + + try { + $kernel->locateResource('@BaseBundle/Resources/hide.txt', __DIR__.'/Fixtures/Resources', true); + $this->fail('Hidden resources should raise an exception when returning the first matching path'); + } catch (\RuntimeException $e) { + } + } + + public function testLocateResourceOnDirectories() + { + $kernel = $this->getKernel(); + $kernel + ->expects($this->exactly(2)) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/FooBundle', null, null, 'FooBundle')))) + ; + + $this->assertEquals( + __DIR__.'/Fixtures/Resources/FooBundle/', + $kernel->locateResource('@FooBundle/Resources/', __DIR__.'/Fixtures/Resources') + ); + $this->assertEquals( + __DIR__.'/Fixtures/Resources/FooBundle', + $kernel->locateResource('@FooBundle/Resources', __DIR__.'/Fixtures/Resources') + ); + + $kernel = $this->getKernel(); + $kernel + ->expects($this->exactly(2)) + ->method('getBundle') + ->will($this->returnValue(array($this->getBundle(__DIR__.'/Fixtures/Bundle1Bundle', null, null, 'Bundle1Bundle')))) + ; + + $this->assertEquals( + __DIR__.'/Fixtures/Bundle1Bundle/Resources/', + $kernel->locateResource('@Bundle1Bundle/Resources/') + ); + $this->assertEquals( + __DIR__.'/Fixtures/Bundle1Bundle/Resources', + $kernel->locateResource('@Bundle1Bundle/Resources') + ); + } + + public function testInitializeBundles() + { + $parent = $this->getBundle(null, null, 'ParentABundle'); + $child = $this->getBundle(null, 'ParentABundle', 'ChildABundle'); + + $kernel = $this->getKernel(); + $kernel + ->expects($this->once()) + ->method('registerBundles') + ->will($this->returnValue(array($parent, $child))) + ; + $kernel->initializeBundles(); + + $map = $kernel->getBundleMap(); + $this->assertEquals(array($child, $parent), $map['ParentABundle']); + } + + public function testInitializeBundlesSupportInheritanceCascade() + { + $grandparent = $this->getBundle(null, null, 'GrandParentBBundle'); + $parent = $this->getBundle(null, 'GrandParentBBundle', 'ParentBBundle'); + $child = $this->getBundle(null, 'ParentBBundle', 'ChildBBundle'); + + $kernel = $this->getKernel(); + $kernel + ->expects($this->once()) + ->method('registerBundles') + ->will($this->returnValue(array($grandparent, $parent, $child))) + ; + + $kernel->initializeBundles(); + + $map = $kernel->getBundleMap(); + $this->assertEquals(array($child, $parent, $grandparent), $map['GrandParentBBundle']); + $this->assertEquals(array($child, $parent), $map['ParentBBundle']); + $this->assertEquals(array($child), $map['ChildBBundle']); + } + + /** + * @expectedException \LogicException + */ + public function testInitializeBundlesThrowsExceptionWhenAParentDoesNotExists() + { + $child = $this->getBundle(null, 'FooBar', 'ChildCBundle'); + + $kernel = $this->getKernel(); + $kernel + ->expects($this->once()) + ->method('registerBundles') + ->will($this->returnValue(array($child))) + ; + $kernel->initializeBundles(); + } + + public function testInitializeBundlesSupportsArbitraryBundleRegistrationOrder() + { + $grandparent = $this->getBundle(null, null, 'GrandParentCCundle'); + $parent = $this->getBundle(null, 'GrandParentCCundle', 'ParentCCundle'); + $child = $this->getBundle(null, 'ParentCCundle', 'ChildCCundle'); + + $kernel = $this->getKernel(); + $kernel + ->expects($this->once()) + ->method('registerBundles') + ->will($this->returnValue(array($parent, $grandparent, $child))) + ; + + $kernel->initializeBundles(); + + $map = $kernel->getBundleMap(); + $this->assertEquals(array($child, $parent, $grandparent), $map['GrandParentCCundle']); + $this->assertEquals(array($child, $parent), $map['ParentCCundle']); + $this->assertEquals(array($child), $map['ChildCCundle']); + } + + /** + * @expectedException \LogicException + */ + public function testInitializeBundlesThrowsExceptionWhenABundleIsDirectlyExtendedByTwoBundles() + { + $parent = $this->getBundle(null, null, 'ParentCBundle'); + $child1 = $this->getBundle(null, 'ParentCBundle', 'ChildC1Bundle'); + $child2 = $this->getBundle(null, 'ParentCBundle', 'ChildC2Bundle'); + + $kernel = $this->getKernel(); + $kernel + ->expects($this->once()) + ->method('registerBundles') + ->will($this->returnValue(array($parent, $child1, $child2))) + ; + $kernel->initializeBundles(); + } + + /** + * @expectedException \LogicException + */ + public function testInitializeBundleThrowsExceptionWhenRegisteringTwoBundlesWithTheSameName() + { + $fooBundle = $this->getBundle(null, null, 'FooBundle', 'DuplicateName'); + $barBundle = $this->getBundle(null, null, 'BarBundle', 'DuplicateName'); + + $kernel = $this->getKernel(); + $kernel + ->expects($this->once()) + ->method('registerBundles') + ->will($this->returnValue(array($fooBundle, $barBundle))) + ; + $kernel->initializeBundles(); + } + + /** + * @expectedException \LogicException + */ + public function testInitializeBundleThrowsExceptionWhenABundleExtendsItself() + { + $circularRef = $this->getBundle(null, 'CircularRefBundle', 'CircularRefBundle'); + + $kernel = $this->getKernel(); + $kernel + ->expects($this->once()) + ->method('registerBundles') + ->will($this->returnValue(array($circularRef))) + ; + $kernel->initializeBundles(); + } + + public function testTerminateReturnsSilentlyIfKernelIsNotBooted() + { + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getHttpKernel')) + ->getMock(); + + $kernel->expects($this->never()) + ->method('getHttpKernel'); + + $kernel->setIsBooted(false); + $kernel->terminate(Request::create('/'), new Response()); + } + + public function testTerminateDelegatesTerminationOnlyForTerminableInterface() + { + // does not implement TerminableInterface + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface') + ->disableOriginalConstructor() + ->getMock(); + + $httpKernelMock + ->expects($this->never()) + ->method('terminate'); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getHttpKernel')) + ->getMock(); + + $kernel->expects($this->once()) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->setIsBooted(true); + $kernel->terminate(Request::create('/'), new Response()); + + // implements TerminableInterface + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernel') + ->disableOriginalConstructor() + ->setMethods(array('terminate')) + ->getMock(); + + $httpKernelMock + ->expects($this->once()) + ->method('terminate'); + + $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getHttpKernel')) + ->getMock(); + + $kernel->expects($this->exactly(2)) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->setIsBooted(true); + $kernel->terminate(Request::create('/'), new Response()); + } + + protected function getBundle($dir = null, $parent = null, $className = null, $bundleName = null) + { + $bundle = $this + ->getMockBuilder('Symfony\Component\HttpKernel\Bundle\BundleInterface') + ->setMethods(array('getPath', 'getParent', 'getName')) + ->disableOriginalConstructor() + ; + + if ($className) { + $bundle->setMockClassName($className); + } + + $bundle = $bundle->getMockForAbstractClass(); + + $bundle + ->expects($this->any()) + ->method('getName') + ->will($this->returnValue(null === $bundleName ? get_class($bundle) : $bundleName)) + ; + + $bundle + ->expects($this->any()) + ->method('getPath') + ->will($this->returnValue($dir)) + ; + + $bundle + ->expects($this->any()) + ->method('getParent') + ->will($this->returnValue($parent)) + ; + + return $bundle; + } + + protected function getKernel() + { + return $this + ->getMockBuilder('Symfony\Component\HttpKernel\Tests\Fixtures\KernelForTest') + ->setMethods(array('getBundle', 'registerBundles')) + ->disableOriginalConstructor() + ->getMock() + ; + } + + protected function getKernelForInvalidLocateResource() + { + return $this + ->getMockBuilder('Symfony\Component\HttpKernel\Kernel') + ->disableOriginalConstructor() + ->getMockForAbstractClass() + ; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Logger.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Logger.php new file mode 100644 index 0000000..1be77f2 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Logger.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use Psr\Log\LoggerInterface; + +class Logger implements LoggerInterface +{ + protected $logs; + + public function __construct() + { + $this->clear(); + } + + public function getLogs($level = false) + { + return false === $level ? $this->logs : $this->logs[$level]; + } + + public function clear() + { + $this->logs = array( + 'emergency' => array(), + 'alert' => array(), + 'critical' => array(), + 'error' => array(), + 'warning' => array(), + 'notice' => array(), + 'info' => array(), + 'debug' => array(), + ); + } + + public function log($level, $message, array $context = array()) + { + $this->logs[$level][] = $message; + } + + public function emergency($message, array $context = array()) + { + $this->log('emergency', $message, $context); + } + + public function alert($message, array $context = array()) + { + $this->log('alert', $message, $context); + } + + public function critical($message, array $context = array()) + { + $this->log('critical', $message, $context); + } + + public function error($message, array $context = array()) + { + $this->log('error', $message, $context); + } + + public function warning($message, array $context = array()) + { + $this->log('warning', $message, $context); + } + + public function notice($message, array $context = array()) + { + $this->log('notice', $message, $context); + } + + public function info($message, array $context = array()) + { + $this->log('info', $message, $context); + } + + public function debug($message, array $context = array()) + { + $this->log('debug', $message, $context); + } + + /** + * @deprecated + */ + public function emerg($message, array $context = array()) + { + trigger_error('Use emergency() which is PSR-3 compatible', E_USER_DEPRECATED); + + $this->log('emergency', $message, $context); + } + + /** + * @deprecated + */ + public function crit($message, array $context = array()) + { + trigger_error('Use crit() which is PSR-3 compatible', E_USER_DEPRECATED); + + $this->log('critical', $message, $context); + } + + /** + * @deprecated + */ + public function err($message, array $context = array()) + { + trigger_error('Use err() which is PSR-3 compatible', E_USER_DEPRECATED); + + $this->log('error', $message, $context); + } + + /** + * @deprecated + */ + public function warn($message, array $context = array()) + { + trigger_error('Use warn() which is PSR-3 compatible', E_USER_DEPRECATED); + + $this->log('warning', $message, $context); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/AbstractProfilerStorageTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/AbstractProfilerStorageTest.php new file mode 100644 index 0000000..4657ff1 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/AbstractProfilerStorageTest.php @@ -0,0 +1,253 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Profiler; + +use Symfony\Component\HttpKernel\Profiler\Profile; + +abstract class AbstractProfilerStorageTest extends \PHPUnit_Framework_TestCase +{ + public function testStore() + { + for ($i = 0; $i < 10; $i ++) { + $profile = new Profile('token_'.$i); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar'); + $profile->setMethod('GET'); + $this->getStorage()->write($profile); + } + $this->assertCount(10, $this->getStorage()->find('127.0.0.1', 'http://foo.bar', 20, 'GET'), '->write() stores data in the storage'); + } + + public function testChildren() + { + $parentProfile = new Profile('token_parent'); + $parentProfile->setIp('127.0.0.1'); + $parentProfile->setUrl('http://foo.bar/parent'); + + $childProfile = new Profile('token_child'); + $childProfile->setIp('127.0.0.1'); + $childProfile->setUrl('http://foo.bar/child'); + + $parentProfile->addChild($childProfile); + + $this->getStorage()->write($parentProfile); + $this->getStorage()->write($childProfile); + + // Load them from storage + $parentProfile = $this->getStorage()->read('token_parent'); + $childProfile = $this->getStorage()->read('token_child'); + + // Check child has link to parent + $this->assertNotNull($childProfile->getParent()); + $this->assertEquals($parentProfile->getToken(), $childProfile->getParentToken()); + + // Check parent has child + $children = $parentProfile->getChildren(); + $this->assertCount(1, $children); + $this->assertEquals($childProfile->getToken(), $children[0]->getToken()); + } + + public function testStoreSpecialCharsInUrl() + { + // The storage accepts special characters in URLs (Even though URLs are not + // supposed to contain them) + $profile = new Profile('simple_quote'); + $profile->setUrl('http://foo.bar/\''); + $this->getStorage()->write($profile); + $this->assertTrue(false !== $this->getStorage()->read('simple_quote'), '->write() accepts single quotes in URL'); + + $profile = new Profile('double_quote'); + $profile->setUrl('http://foo.bar/"'); + $this->getStorage()->write($profile); + $this->assertTrue(false !== $this->getStorage()->read('double_quote'), '->write() accepts double quotes in URL'); + + $profile = new Profile('backslash'); + $profile->setUrl('http://foo.bar/\\'); + $this->getStorage()->write($profile); + $this->assertTrue(false !== $this->getStorage()->read('backslash'), '->write() accepts backslash in URL'); + + $profile = new Profile('comma'); + $profile->setUrl('http://foo.bar/,'); + $this->getStorage()->write($profile); + $this->assertTrue(false !== $this->getStorage()->read('comma'), '->write() accepts comma in URL'); + } + + public function testStoreDuplicateToken() + { + $profile = new Profile('token'); + $profile->setUrl('http://example.com/'); + + $this->assertTrue($this->getStorage()->write($profile), '->write() returns true when the token is unique'); + + $profile->setUrl('http://example.net/'); + + $this->assertTrue($this->getStorage()->write($profile), '->write() returns true when the token is already present in the storage'); + $this->assertEquals('http://example.net/', $this->getStorage()->read('token')->getUrl(), '->write() overwrites the current profile data'); + + $this->assertCount(1, $this->getStorage()->find('', '', 1000, ''), '->find() does not return the same profile twice'); + } + + public function testRetrieveByIp() + { + $profile = new Profile('token'); + $profile->setIp('127.0.0.1'); + $profile->setMethod('GET'); + $this->getStorage()->write($profile); + + $this->assertCount(1, $this->getStorage()->find('127.0.0.1', '', 10, 'GET'), '->find() retrieve a record by IP'); + $this->assertCount(0, $this->getStorage()->find('127.0.%.1', '', 10, 'GET'), '->find() does not interpret a "%" as a wildcard in the IP'); + $this->assertCount(0, $this->getStorage()->find('127.0._.1', '', 10, 'GET'), '->find() does not interpret a "_" as a wildcard in the IP'); + } + + public function testRetrieveByUrl() + { + $profile = new Profile('simple_quote'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/\''); + $profile->setMethod('GET'); + $this->getStorage()->write($profile); + + $profile = new Profile('double_quote'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/"'); + $profile->setMethod('GET'); + $this->getStorage()->write($profile); + + $profile = new Profile('backslash'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo\\bar/'); + $profile->setMethod('GET'); + $this->getStorage()->write($profile); + + $profile = new Profile('percent'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/%'); + $profile->setMethod('GET'); + $this->getStorage()->write($profile); + + $profile = new Profile('underscore'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/_'); + $profile->setMethod('GET'); + $this->getStorage()->write($profile); + + $profile = new Profile('semicolon'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar/;'); + $profile->setMethod('GET'); + $this->getStorage()->write($profile); + + $this->assertCount(1, $this->getStorage()->find('127.0.0.1', 'http://foo.bar/\'', 10, 'GET'), '->find() accepts single quotes in URLs'); + $this->assertCount(1, $this->getStorage()->find('127.0.0.1', 'http://foo.bar/"', 10, 'GET'), '->find() accepts double quotes in URLs'); + $this->assertCount(1, $this->getStorage()->find('127.0.0.1', 'http://foo\\bar/', 10, 'GET'), '->find() accepts backslash in URLs'); + $this->assertCount(1, $this->getStorage()->find('127.0.0.1', 'http://foo.bar/;', 10, 'GET'), '->find() accepts semicolon in URLs'); + $this->assertCount(1, $this->getStorage()->find('127.0.0.1', 'http://foo.bar/%', 10, 'GET'), '->find() does not interpret a "%" as a wildcard in the URL'); + $this->assertCount(1, $this->getStorage()->find('127.0.0.1', 'http://foo.bar/_', 10, 'GET'), '->find() does not interpret a "_" as a wildcard in the URL'); + } + + public function testStoreTime() + { + $dt = new \DateTime('now'); + $start = $dt->getTimestamp(); + + for ($i = 0; $i < 3; $i++) { + $dt->modify('+1 minute'); + $profile = new Profile('time_'.$i); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://foo.bar'); + $profile->setTime($dt->getTimestamp()); + $profile->setMethod('GET'); + $this->getStorage()->write($profile); + } + + $records = $this->getStorage()->find('', '', 3, 'GET', $start, time() + 3 * 60); + $this->assertCount(3, $records, '->find() returns all previously added records'); + $this->assertEquals($records[0]['token'], 'time_2', '->find() returns records ordered by time in descendant order'); + $this->assertEquals($records[1]['token'], 'time_1', '->find() returns records ordered by time in descendant order'); + $this->assertEquals($records[2]['token'], 'time_0', '->find() returns records ordered by time in descendant order'); + + $records = $this->getStorage()->find('', '', 3, 'GET', $start, time() + 2 * 60); + $this->assertCount(2, $records, '->find() should return only first two of the previously added records'); + } + + public function testRetrieveByEmptyUrlAndIp() + { + for ($i = 0; $i < 5; $i++) { + $profile = new Profile('token_'.$i); + $profile->setMethod('GET'); + $this->getStorage()->write($profile); + } + $this->assertCount(5, $this->getStorage()->find('', '', 10, 'GET'), '->find() returns all previously added records'); + $this->getStorage()->purge(); + } + + public function testRetrieveByMethodAndLimit() + { + foreach (array('POST', 'GET') as $method) { + for ($i = 0; $i < 5; $i++) { + $profile = new Profile('token_'.$i.$method); + $profile->setMethod($method); + $this->getStorage()->write($profile); + } + } + + $this->assertCount(5, $this->getStorage()->find('', '', 5, 'POST')); + + $this->getStorage()->purge(); + } + + public function testPurge() + { + $profile = new Profile('token1'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://example.com/'); + $profile->setMethod('GET'); + $this->getStorage()->write($profile); + + $this->assertTrue(false !== $this->getStorage()->read('token1')); + $this->assertCount(1, $this->getStorage()->find('127.0.0.1', '', 10, 'GET')); + + $profile = new Profile('token2'); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://example.net/'); + $profile->setMethod('GET'); + $this->getStorage()->write($profile); + + $this->assertTrue(false !== $this->getStorage()->read('token2')); + $this->assertCount(2, $this->getStorage()->find('127.0.0.1', '', 10, 'GET')); + + $this->getStorage()->purge(); + + $this->assertEmpty($this->getStorage()->read('token'), '->purge() removes all data stored by profiler'); + $this->assertCount(0, $this->getStorage()->find('127.0.0.1', '', 10, 'GET'), '->purge() removes all items from index'); + } + + public function testDuplicates() + { + for ($i = 1; $i <= 5; $i++) { + $profile = new Profile('foo'.$i); + $profile->setIp('127.0.0.1'); + $profile->setUrl('http://example.net/'); + $profile->setMethod('GET'); + + ///three duplicates + $this->getStorage()->write($profile); + $this->getStorage()->write($profile); + $this->getStorage()->write($profile); + } + $this->assertCount(3, $this->getStorage()->find('127.0.0.1', 'http://example.net/', 3, 'GET'), '->find() method returns incorrect number of entries'); + } + + /** + * @return \Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface + */ + abstract protected function getStorage(); +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/FileProfilerStorageTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/FileProfilerStorageTest.php new file mode 100644 index 0000000..3c2d04c --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/FileProfilerStorageTest.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Profiler; + +use Symfony\Component\HttpKernel\Profiler\FileProfilerStorage; +use Symfony\Component\HttpKernel\Profiler\Profile; + +class FileProfilerStorageTest extends AbstractProfilerStorageTest +{ + protected static $tmpDir; + protected static $storage; + + protected static function cleanDir() + { + $flags = \FilesystemIterator::SKIP_DOTS; + $iterator = new \RecursiveDirectoryIterator(self::$tmpDir, $flags); + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); + + foreach ($iterator as $file) { + if (is_file($file)) { + unlink($file); + } + } + } + + public static function setUpBeforeClass() + { + self::$tmpDir = sys_get_temp_dir().'/sf2_profiler_file_storage'; + if (is_dir(self::$tmpDir)) { + self::cleanDir(); + } + self::$storage = new FileProfilerStorage('file:'.self::$tmpDir); + } + + public static function tearDownAfterClass() + { + self::cleanDir(); + } + + protected function setUp() + { + self::$storage->purge(); + } + + /** + * @return \Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface + */ + protected function getStorage() + { + return self::$storage; + } + + public function testMultiRowIndexFile() + { + $iteration = 3; + for ($i = 0; $i < $iteration; $i++) { + $profile = new Profile('token'.$i); + $profile->setIp('127.0.0.'.$i); + $profile->setUrl('http://foo.bar/'.$i); + $storage = $this->getStorage(); + + $storage->write($profile); + $storage->write($profile); + $storage->write($profile); + } + + $handle = fopen(self::$tmpDir.'/index.csv', 'r'); + for ($i = 0; $i < $iteration; $i++) { + $row = fgetcsv($handle); + $this->assertEquals('token'.$i, $row[0]); + $this->assertEquals('127.0.0.'.$i, $row[1]); + $this->assertEquals('http://foo.bar/'.$i, $row[3]); + } + $this->assertFalse(fgetcsv($handle)); + } + + public function testReadLineFromFile() + { + $r = new \ReflectionMethod(self::$storage, 'readLineFromFile'); + + $r->setAccessible(true); + + $h = tmpfile(); + + fwrite($h, "line1\n\n\nline2\n"); + fseek($h, 0, SEEK_END); + + $this->assertEquals("line2", $r->invoke(self::$storage, $h)); + $this->assertEquals("line1", $r->invoke(self::$storage, $h)); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/MemcacheProfilerStorageTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/MemcacheProfilerStorageTest.php new file mode 100644 index 0000000..f582dff --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/MemcacheProfilerStorageTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Profiler; + +use Symfony\Component\HttpKernel\Profiler\MemcacheProfilerStorage; +use Symfony\Component\HttpKernel\Tests\Profiler\Mock\MemcacheMock; + +class MemcacheProfilerStorageTest extends AbstractProfilerStorageTest +{ + protected static $storage; + + protected function setUp() + { + $memcacheMock = new MemcacheMock(); + $memcacheMock->addServer('127.0.0.1', 11211); + + self::$storage = new MemcacheProfilerStorage('memcache://127.0.0.1:11211', '', '', 86400); + self::$storage->setMemcache($memcacheMock); + + if (self::$storage) { + self::$storage->purge(); + } + } + + protected function tearDown() + { + if (self::$storage) { + self::$storage->purge(); + self::$storage = false; + } + } + + /** + * @return \Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface + */ + protected function getStorage() + { + return self::$storage; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/MemcachedProfilerStorageTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/MemcachedProfilerStorageTest.php new file mode 100644 index 0000000..565ac35 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/MemcachedProfilerStorageTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Profiler; + +use Symfony\Component\HttpKernel\Profiler\MemcachedProfilerStorage; +use Symfony\Component\HttpKernel\Tests\Profiler\Mock\MemcachedMock; + +class MemcachedProfilerStorageTest extends AbstractProfilerStorageTest +{ + protected static $storage; + + protected function setUp() + { + $memcachedMock = new MemcachedMock(); + $memcachedMock->addServer('127.0.0.1', 11211); + + self::$storage = new MemcachedProfilerStorage('memcached://127.0.0.1:11211', '', '', 86400); + self::$storage->setMemcached($memcachedMock); + + if (self::$storage) { + self::$storage->purge(); + } + } + + protected function tearDown() + { + if (self::$storage) { + self::$storage->purge(); + self::$storage = false; + } + } + + /** + * @return \Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface + */ + protected function getStorage() + { + return self::$storage; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/Mock/MemcacheMock.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/Mock/MemcacheMock.php new file mode 100644 index 0000000..014f549 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/Mock/MemcacheMock.php @@ -0,0 +1,260 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Profiler\Mock; + +/** + * MemcacheMock for simulating Memcache extension in tests. + * + * @author Andrej Hudec + */ +class MemcacheMock +{ + private $connected; + private $storage; + + public function __construct() + { + $this->connected = false; + $this->storage = array(); + } + + /** + * Open memcached server connection + * + * @param string $host + * @param integer $port + * @param integer $timeout + * + * @return boolean + */ + public function connect($host, $port = null, $timeout = null) + { + if ('127.0.0.1' == $host && 11211 == $port) { + $this->connected = true; + + return true; + } + + return false; + } + + /** + * Open memcached server persistent connection + * + * @param string $host + * @param integer $port + * @param integer $timeout + * + * @return boolean + */ + public function pconnect($host, $port = null, $timeout = null) + { + if ('127.0.0.1' == $host && 11211 == $port) { + $this->connected = true; + + return true; + } + + return false; + } + + /** + * Add a memcached server to connection pool + * + * @param string $host + * @param integer $port + * @param boolean $persistent + * @param integer $weight + * @param integer $timeout + * @param integer $retry_interval + * @param boolean $status + * @param callable $failure_callback + * @param integer $timeoutms + * + * @return boolean + */ + public function addServer($host, $port = 11211, $persistent = null, $weight = null, $timeout = null, $retry_interval = null, $status = null, $failure_callback = null, $timeoutms = null) + { + if ('127.0.0.1' == $host && 11211 == $port) { + $this->connected = true; + + return true; + } + + return false; + } + + /** + * Add an item to the server only if such key doesn't exist at the server yet. + * + * @param string $key + * @param mixed $var + * @param integer $flag + * @param integer $expire + * + * @return boolean + */ + public function add($key, $var, $flag = null, $expire = null) + { + if (!$this->connected) { + return false; + } + + if (!isset($this->storage[$key])) { + $this->storeData($key, $var); + + return true; + } + + return false; + } + + /** + * Store data at the server. + * + * @param string $key + * @param string $var + * @param integer $flag + * @param integer $expire + * + * @return boolean + */ + public function set($key, $var, $flag = null, $expire = null) + { + if (!$this->connected) { + return false; + } + + $this->storeData($key, $var); + + return true; + } + + /** + * Replace value of the existing item. + * + * @param string $key + * @param mixed $var + * @param integer $flag + * @param integer $expire + * + * @return boolean + */ + public function replace($key, $var, $flag = null, $expire = null) + { + if (!$this->connected) { + return false; + } + + if (isset($this->storage[$key])) { + $this->storeData($key, $var); + + return true; + } + + return false; + } + + /** + * Retrieve item from the server. + * + * @param string|array $key + * @param integer|array $flags + * + * @return mixed + */ + public function get($key, &$flags = null) + { + if (!$this->connected) { + return false; + } + + if (is_array($key)) { + $result = array(); + foreach ($key as $k) { + if (isset($this->storage[$k])) { + $result[] = $this->getData($k); + } + } + + return $result; + } + + return $this->getData($key); + } + + /** + * Delete item from the server + * + * @param string $key + * + * @return boolean + */ + public function delete($key) + { + if (!$this->connected) { + return false; + } + + if (isset($this->storage[$key])) { + unset($this->storage[$key]); + + return true; + } + + return false; + } + + /** + * Flush all existing items at the server + * + * @return boolean + */ + public function flush() + { + if (!$this->connected) { + return false; + } + + $this->storage = array(); + + return true; + } + + /** + * Close memcached server connection + * + * @return boolean + */ + public function close() + { + $this->connected = false; + + return true; + } + + private function getData($key) + { + if (isset($this->storage[$key])) { + return unserialize($this->storage[$key]); + } + + return false; + } + + private function storeData($key, $value) + { + $this->storage[$key] = serialize($value); + + return true; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/Mock/MemcachedMock.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/Mock/MemcachedMock.php new file mode 100644 index 0000000..2b17d70 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/Mock/MemcachedMock.php @@ -0,0 +1,225 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Profiler\Mock; + +/** + * MemcachedMock for simulating Memcached extension in tests. + * + * @author Andrej Hudec + */ +class MemcachedMock +{ + private $connected; + private $storage; + + public function __construct() + { + $this->connected = false; + $this->storage = array(); + } + + /** + * Set a Memcached option + * + * @param integer $option + * @param mixed $value + * + * @return boolean + */ + public function setOption($option, $value) + { + return true; + } + + /** + * Add a memcached server to connection pool + * + * @param string $host + * @param integer $port + * @param integer $weight + * + * @return boolean + */ + public function addServer($host, $port = 11211, $weight = 0) + { + if ('127.0.0.1' == $host && 11211 == $port) { + $this->connected = true; + + return true; + } + + return false; + } + + /** + * Add an item to the server only if such key doesn't exist at the server yet. + * + * @param string $key + * @param mixed $value + * @param integer $expiration + * + * @return boolean + */ + public function add($key, $value, $expiration = 0) + { + if (!$this->connected) { + return false; + } + + if (!isset($this->storage[$key])) { + $this->storeData($key, $value); + + return true; + } + + return false; + } + + /** + * Store data at the server. + * + * @param string $key + * @param mixed $value + * @param integer $expiration + * + * @return boolean + */ + public function set($key, $value, $expiration = null) + { + if (!$this->connected) { + return false; + } + + $this->storeData($key, $value); + + return true; + } + + /** + * Replace value of the existing item. + * + * @param string $key + * @param mixed $value + * @param integer $expiration + * + * @return boolean + */ + public function replace($key, $value, $expiration = null) + { + if (!$this->connected) { + return false; + } + + if (isset($this->storage[$key])) { + $this->storeData($key, $value); + + return true; + } + + return false; + } + + /** + * Retrieve item from the server. + * + * @param string $key + * @param callable $cache_cb + * @param float $cas_token + * + * @return boolean + */ + public function get($key, $cache_cb = null, &$cas_token = null) + { + if (!$this->connected) { + return false; + } + + return $this->getData($key); + } + + /** + * Append data to an existing item + * + * @param string $key + * @param string $value + * + * @return boolean + */ + public function append($key, $value) + { + if (!$this->connected) { + return false; + } + + if (isset($this->storage[$key])) { + $this->storeData($key, $this->getData($key).$value); + + return true; + } + + return false; + } + + /** + * Delete item from the server + * + * @param string $key + * + * @return boolean + */ + public function delete($key) + { + if (!$this->connected) { + return false; + } + + if (isset($this->storage[$key])) { + unset($this->storage[$key]); + + return true; + } + + return false; + } + + /** + * Flush all existing items at the server + * + * @return boolean + */ + public function flush() + { + if (!$this->connected) { + return false; + } + + $this->storage = array(); + + return true; + } + + private function getData($key) + { + if (isset($this->storage[$key])) { + return unserialize($this->storage[$key]); + } + + return false; + } + + private function storeData($key, $value) + { + $this->storage[$key] = serialize($value); + + return true; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/Mock/RedisMock.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/Mock/RedisMock.php new file mode 100644 index 0000000..ca2980e --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/Mock/RedisMock.php @@ -0,0 +1,254 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Profiler\Mock; + +/** + * RedisMock for simulating Redis extension in tests. + * + * @author Andrej Hudec + */ +class RedisMock +{ + + private $connected; + private $storage; + + public function __construct() + { + $this->connected = false; + $this->storage = array(); + } + + /** + * Add a server to connection pool + * + * @param string $host + * @param integer $port + * @param float $timeout + * + * @return boolean + */ + public function connect($host, $port = 6379, $timeout = 0) + { + if ('127.0.0.1' == $host && 6379 == $port) { + $this->connected = true; + + return true; + } + + return false; + } + + /** + * Set client option. + * + * @param integer $name + * @param integer $value + * + * @return boolean + */ + public function setOption($name, $value) + { + if (!$this->connected) { + return false; + } + + return true; + } + + /** + * Verify if the specified key exists. + * + * @param string $key + * + * @return boolean + */ + public function exists($key) + { + if (!$this->connected) { + return false; + } + + return isset($this->storage[$key]); + } + + /** + * Store data at the server with expiration time. + * + * @param string $key + * @param integer $ttl + * @param mixed $value + * + * @return boolean + */ + public function setex($key, $ttl, $value) + { + if (!$this->connected) { + return false; + } + + $this->storeData($key, $value); + + return true; + } + + /** + * Sets an expiration time on an item. + * + * @param string $key + * @param integer $ttl + * + * @return boolean + */ + public function setTimeout($key, $ttl) + { + if (!$this->connected) { + return false; + } + + if (isset($this->storage[$key])) { + return true; + } + + return false; + } + + /** + * Retrieve item from the server. + * + * @param string $key + * + * @return boolean + */ + public function get($key) + { + if (!$this->connected) { + return false; + } + + return $this->getData($key); + } + + /** + * Append data to an existing item + * + * @param string $key + * @param string $value + * + * @return integer Size of the value after the append. + */ + public function append($key, $value) + { + if (!$this->connected) { + return false; + } + + if (isset($this->storage[$key])) { + $this->storeData($key, $this->getData($key).$value); + + return strlen($this->storage[$key]); + } + + return false; + } + + /** + * Remove specified keys. + * + * @param string|array $key + * + * @return integer + */ + public function delete($key) + { + if (!$this->connected) { + return false; + } + + if (is_array($key)) { + $result = 0; + foreach ($key as $k) { + if (isset($this->storage[$k])) { + unset($this->storage[$k]); + ++$result; + } + } + + return $result; + } + + if (isset($this->storage[$key])) { + unset($this->storage[$key]); + + return 1; + } + + return 0; + } + + /** + * Flush all existing items from all databases at the server. + * + * @return boolean + */ + public function flushAll() + { + if (!$this->connected) { + return false; + } + + $this->storage = array(); + + return true; + } + + /** + * Close Redis server connection + * + * @return boolean + */ + public function close() + { + $this->connected = false; + + return true; + } + + private function getData($key) + { + if (isset($this->storage[$key])) { + return unserialize($this->storage[$key]); + } + + return false; + } + + private function storeData($key, $value) + { + $this->storage[$key] = serialize($value); + + return true; + } + + public function select($dbnum) + { + if (!$this->connected) { + return false; + } + + if (0 > $dbnum) { + return false; + } + + return true; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/MongoDbProfilerStorageTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/MongoDbProfilerStorageTest.php new file mode 100644 index 0000000..b63b84c --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/MongoDbProfilerStorageTest.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Profiler; + +use Symfony\Component\HttpKernel\Profiler\MongoDbProfilerStorage; +use Symfony\Component\HttpKernel\Profiler\Profile; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class DummyMongoDbProfilerStorage extends MongoDbProfilerStorage +{ + public function getMongo() + { + return parent::getMongo(); + } +} + +class MongoDbProfilerStorageTestDataCollector extends DataCollector +{ + public function setData($data) + { + $this->data = $data; + } + + public function getData() + { + return $this->data; + } + + public function collect(Request $request, Response $response, \Exception $exception = null) + { + } + + public function getName() + { + return 'test_data_collector'; + } +} + +class MongoDbProfilerStorageTest extends AbstractProfilerStorageTest +{ + protected static $storage; + + public static function setUpBeforeClass() + { + if (extension_loaded('mongo')) { + self::$storage = new DummyMongoDbProfilerStorage('mongodb://localhost/symfony_tests/profiler_data', '', '', 86400); + try { + self::$storage->getMongo(); + } catch (\MongoConnectionException $e) { + self::$storage = null; + } + } + } + + public static function tearDownAfterClass() + { + if (self::$storage) { + self::$storage->purge(); + self::$storage = null; + } + } + + public function testCleanup() + { + $dt = new \DateTime('-2 day'); + for ($i = 0; $i < 3; $i++) { + $dt->modify('-1 day'); + $profile = new Profile('time_'.$i); + $profile->setTime($dt->getTimestamp()); + $profile->setMethod('GET'); + self::$storage->write($profile); + } + $records = self::$storage->find('', '', 3, 'GET'); + $this->assertCount(1, $records, '->find() returns only one record'); + $this->assertEquals($records[0]['token'], 'time_2', '->find() returns the latest added record'); + self::$storage->purge(); + } + + public function testUtf8() + { + $profile = new Profile('utf8_test_profile'); + + $data = 'HÐʃʃϿ, Ï¢orЃd!'; + $nonUtf8Data = mb_convert_encoding($data, 'UCS-2'); + + $collector = new MongoDbProfilerStorageTestDataCollector(); + $collector->setData($nonUtf8Data); + + $profile->setCollectors(array($collector)); + + self::$storage->write($profile); + + $readProfile = self::$storage->read('utf8_test_profile'); + $collectors = $readProfile->getCollectors(); + + $this->assertCount(1, $collectors); + $this->assertArrayHasKey('test_data_collector', $collectors); + $this->assertEquals($nonUtf8Data, $collectors['test_data_collector']->getData(), 'Non-UTF8 data is properly encoded/decoded'); + } + + /** + * @return \Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface + */ + protected function getStorage() + { + return self::$storage; + } + + protected function setUp() + { + if (self::$storage) { + self::$storage->purge(); + } else { + $this->markTestSkipped('MongoDbProfilerStorageTest requires the mongo PHP extension and a MongoDB server on localhost'); + } + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/ProfilerTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/ProfilerTest.php new file mode 100644 index 0000000..2a41531 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/ProfilerTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Profiler; + +use Symfony\Component\HttpKernel\DataCollector\RequestDataCollector; +use Symfony\Component\HttpKernel\Profiler\SqliteProfilerStorage; +use Symfony\Component\HttpKernel\Profiler\Profiler; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class ProfilerTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + } + + public function testCollect() + { + if (!class_exists('SQLite3') && (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers()))) { + $this->markTestSkipped('This test requires SQLite support in your environment'); + } + + $request = new Request(); + $request->query->set('foo', 'bar'); + $response = new Response(); + $collector = new RequestDataCollector(); + + $tmp = tempnam(sys_get_temp_dir(), 'sf2_profiler'); + if (file_exists($tmp)) { + @unlink($tmp); + } + $storage = new SqliteProfilerStorage('sqlite:'.$tmp); + $storage->purge(); + + $profiler = new Profiler($storage); + $profiler->add($collector); + $profile = $profiler->collect($request, $response); + + $profile = $profiler->loadProfile($profile->getToken()); + $this->assertEquals(array('foo' => 'bar'), $profiler->get('request')->getRequestQuery()->all()); + + @unlink($tmp); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/RedisProfilerStorageTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/RedisProfilerStorageTest.php new file mode 100644 index 0000000..91354ae --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/RedisProfilerStorageTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Profiler; + +use Symfony\Component\HttpKernel\Profiler\RedisProfilerStorage; +use Symfony\Component\HttpKernel\Tests\Profiler\Mock\RedisMock; + +class RedisProfilerStorageTest extends AbstractProfilerStorageTest +{ + protected static $storage; + + protected function setUp() + { + $redisMock = new RedisMock(); + $redisMock->connect('127.0.0.1', 6379); + + self::$storage = new RedisProfilerStorage('redis://127.0.0.1:6379', '', '', 86400); + self::$storage->setRedis($redisMock); + + if (self::$storage) { + self::$storage->purge(); + } + } + + protected function tearDown() + { + if (self::$storage) { + self::$storage->purge(); + self::$storage = false; + } + } + + /** + * @return \Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface + */ + protected function getStorage() + { + return self::$storage; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/SqliteProfilerStorageTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/SqliteProfilerStorageTest.php new file mode 100644 index 0000000..43546c1 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/Profiler/SqliteProfilerStorageTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\Profiler; + +use Symfony\Component\HttpKernel\Profiler\SqliteProfilerStorage; + +class SqliteProfilerStorageTest extends AbstractProfilerStorageTest +{ + protected static $dbFile; + protected static $storage; + + public static function setUpBeforeClass() + { + self::$dbFile = tempnam(sys_get_temp_dir(), 'sf2_sqlite_storage'); + if (file_exists(self::$dbFile)) { + @unlink(self::$dbFile); + } + self::$storage = new SqliteProfilerStorage('sqlite:'.self::$dbFile); + } + + public static function tearDownAfterClass() + { + @unlink(self::$dbFile); + } + + protected function setUp() + { + if (!class_exists('SQLite3') && (!class_exists('PDO') || !in_array('sqlite', \PDO::getAvailableDrivers()))) { + $this->markTestSkipped('This test requires SQLite support in your environment'); + } + self::$storage->purge(); + } + + /** + * @return \Symfony\Component\HttpKernel\Profiler\ProfilerStorageInterface + */ + protected function getStorage() + { + return self::$storage; + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/TestHttpKernel.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/TestHttpKernel.php new file mode 100644 index 0000000..d526c4d --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/TestHttpKernel.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\EventDispatcher\EventDispatcher; + +class TestHttpKernel extends HttpKernel implements ControllerResolverInterface +{ + public function __construct() + { + parent::__construct(new EventDispatcher(), $this); + } + + public function getController(Request $request) + { + return array($this, 'callController'); + } + + public function getArguments(Request $request, $controller) + { + return array($request); + } + + public function callController(Request $request) + { + return new Response('Request: '.$request->getRequestUri()); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/UriSignerTest.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/UriSignerTest.php new file mode 100644 index 0000000..8ffc2bf --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Tests/UriSignerTest.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests; + +use Symfony\Component\HttpKernel\UriSigner; + +class UriSignerTest extends \PHPUnit_Framework_TestCase +{ + public function testSign() + { + $signer = new UriSigner('foobar'); + + $this->assertContains('?_hash=', $signer->sign('http://example.com/foo')); + $this->assertContains('&_hash=', $signer->sign('http://example.com/foo?foo=bar')); + } + + public function testCheck() + { + $signer = new UriSigner('foobar'); + + $this->assertFalse($signer->check('http://example.com/foo?_hash=foo')); + $this->assertFalse($signer->check('http://example.com/foo?foo=bar&_hash=foo')); + $this->assertFalse($signer->check('http://example.com/foo?foo=bar&_hash=foo&bar=foo')); + + $this->assertTrue($signer->check($signer->sign('http://example.com/foo'))); + $this->assertTrue($signer->check($signer->sign('http://example.com/foo?foo=bar'))); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/UriSigner.php b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/UriSigner.php new file mode 100644 index 0000000..665e99e --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/UriSigner.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +/** + * Signs URIs. + * + * @author Fabien Potencier + */ +class UriSigner +{ + private $secret; + + /** + * Constructor. + * + * @param string $secret A secret + */ + public function __construct($secret) + { + $this->secret = $secret; + } + + /** + * Signs a URI. + * + * The given URI is signed by adding a _hash query string parameter + * which value depends on the URI and the secret. + * + * @param string $uri A URI to sign + * + * @return string The signed URI + */ + public function sign($uri) + { + return $uri.(false === (strpos($uri, '?')) ? '?' : '&').'_hash='.$this->computeHash($uri); + } + + /** + * Checks that a URI contains the correct hash. + * + * The _hash query string parameter must be the last one + * (as it is generated that way by the sign() method, it should + * never be a problem). + * + * @param string $uri A signed URI + * + * @return Boolean True if the URI is signed correctly, false otherwise + */ + public function check($uri) + { + if (!preg_match('/^(.*)(?:\?|&)_hash=(.+?)$/', $uri, $matches)) { + return false; + } + + return $this->computeHash($matches[1]) === $matches[2]; + } + + private function computeHash($uri) + { + return urlencode(base64_encode(hash_hmac('sha1', $uri, $this->secret, true))); + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/composer.json b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/composer.json new file mode 100644 index 0000000..af659da --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/composer.json @@ -0,0 +1,55 @@ +{ + "name": "symfony/http-kernel", + "type": "library", + "description": "Symfony HttpKernel Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3", + "symfony/event-dispatcher": "~2.1", + "symfony/http-foundation": "~2.2", + "symfony/debug": "~2.3", + "psr/log": "~1.0" + }, + "require-dev": { + "symfony/browser-kit": "~2.2", + "symfony/class-loader": "~2.1", + "symfony/config": "~2.0", + "symfony/console": "~2.2", + "symfony/dependency-injection": "~2.0", + "symfony/finder": "~2.0", + "symfony/process": "~2.0", + "symfony/routing": "~2.2", + "symfony/stopwatch": "~2.2", + "symfony/templating": "~2.2" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\HttpKernel\\": "" } + }, + "target-dir": "Symfony/Component/HttpKernel", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + } +} diff --git a/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/phpunit.xml.dist b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/phpunit.xml.dist new file mode 100644 index 0000000..f8490c3 --- /dev/null +++ b/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + diff --git a/vendor/symfony/process/Symfony/Component/Process/.gitignore b/vendor/symfony/process/Symfony/Component/Process/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/process/Symfony/Component/Process/CHANGELOG.md b/vendor/symfony/process/Symfony/Component/Process/CHANGELOG.md new file mode 100644 index 0000000..3bad982 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/CHANGELOG.md @@ -0,0 +1,26 @@ +CHANGELOG +========= + +2.3.0 +----- + + * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows + * added Process::signal() + * added Process::getPid() + * added support for a TTY mode + +2.2.0 +----- + + * added ProcessBuilder::setArguments() to reset the arguments on a builder + * added a way to retrieve the standard and error output incrementally + * added Process:restart() + +2.1.0 +----- + + * added support for non-blocking processes (start(), wait(), isRunning(), stop()) + * enhanced Windows compatibility + * added Process::getExitCodeText() that returns a string representation for + the exit code returned by the process + * added ProcessBuilder diff --git a/vendor/symfony/process/Symfony/Component/Process/Exception/ExceptionInterface.php b/vendor/symfony/process/Symfony/Component/Process/Exception/ExceptionInterface.php new file mode 100644 index 0000000..75c1c9e --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * Marker Interface for the Process Component. + * + * @author Johannes M. Schmitt + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Exception/InvalidArgumentException.php b/vendor/symfony/process/Symfony/Component/Process/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..926ee21 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * InvalidArgumentException for the Process Component. + * + * @author Romain Neutron + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Exception/LogicException.php b/vendor/symfony/process/Symfony/Component/Process/Exception/LogicException.php new file mode 100644 index 0000000..be3d490 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Exception/LogicException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * LogicException for the Process Component. + * + * @author Romain Neutron + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Exception/ProcessFailedException.php b/vendor/symfony/process/Symfony/Component/Process/Exception/ProcessFailedException.php new file mode 100644 index 0000000..8909359 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Exception/ProcessFailedException.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception for failed processes. + * + * @author Johannes M. Schmitt + */ +class ProcessFailedException extends RuntimeException +{ + private $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + parent::__construct( + sprintf( + 'The command "%s" failed.'."\nExit Code: %s(%s)\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", + $process->getCommandLine(), + $process->getExitCode(), + $process->getExitCodeText(), + $process->getOutput(), + $process->getErrorOutput() + ) + ); + + $this->process = $process; + } + + public function getProcess() + { + return $this->process; + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Exception/RuntimeException.php b/vendor/symfony/process/Symfony/Component/Process/Exception/RuntimeException.php new file mode 100644 index 0000000..adead25 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * RuntimeException for the Process Component. + * + * @author Johannes M. Schmitt + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Symfony/Component/Process/ExecutableFinder.php b/vendor/symfony/process/Symfony/Component/Process/ExecutableFinder.php new file mode 100644 index 0000000..5cc99c7 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/ExecutableFinder.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * Generic executable finder. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class ExecutableFinder +{ + private $suffixes = array('.exe', '.bat', '.cmd', '.com'); + + /** + * Replaces default suffixes of executable. + * + * @param array $suffixes + */ + public function setSuffixes(array $suffixes) + { + $this->suffixes = $suffixes; + } + + /** + * Adds new possible suffix to check for executable. + * + * @param string $suffix + */ + public function addSuffix($suffix) + { + $this->suffixes[] = $suffix; + } + + /** + * Finds an executable by name. + * + * @param string $name The executable name (without the extension) + * @param string $default The default to return if no executable is found + * @param array $extraDirs Additional dirs to check into + * + * @return string The executable path or default value + */ + public function find($name, $default = null, array $extraDirs = array()) + { + if (ini_get('open_basedir')) { + $searchPath = explode(PATH_SEPARATOR, getenv('open_basedir')); + $dirs = array(); + foreach ($searchPath as $path) { + if (is_dir($path)) { + $dirs[] = $path; + } else { + $file = str_replace(dirname($path), '', $path); + if ($file == $name && is_executable($path)) { + return $path; + } + } + } + } else { + $dirs = array_merge( + explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), + $extraDirs + ); + } + + $suffixes = array(''); + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $pathExt = getenv('PATHEXT'); + $suffixes = $pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes; + } + foreach ($suffixes as $suffix) { + foreach ($dirs as $dir) { + if (is_file($file = $dir.DIRECTORY_SEPARATOR.$name.$suffix) && (defined('PHP_WINDOWS_VERSION_BUILD') || is_executable($file))) { + return $file; + } + } + } + + return $default; + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/LICENSE b/vendor/symfony/process/Symfony/Component/Process/LICENSE new file mode 100644 index 0000000..88a57f8 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2013 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/process/Symfony/Component/Process/PhpExecutableFinder.php b/vendor/symfony/process/Symfony/Component/Process/PhpExecutableFinder.php new file mode 100644 index 0000000..6c9b8a1 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/PhpExecutableFinder.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * An executable finder specifically designed for the PHP executable. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class PhpExecutableFinder +{ + private $executableFinder; + + public function __construct() + { + $this->executableFinder = new ExecutableFinder(); + } + + /** + * Finds The PHP executable. + * + * @return string|false The PHP executable path or false if it cannot be found + */ + public function find() + { + // PHP_BINARY return the current sapi executable + if (defined('PHP_BINARY') && PHP_BINARY && ('cli' === PHP_SAPI) && is_file(PHP_BINARY)) { + return PHP_BINARY; + } + + if ($php = getenv('PHP_PATH')) { + if (!is_executable($php)) { + return false; + } + + return $php; + } + + if ($php = getenv('PHP_PEAR_PHP_BIN')) { + if (is_executable($php)) { + return $php; + } + } + + $dirs = array(PHP_BINDIR); + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $dirs[] = 'C:\xampp\php\\'; + } + + return $this->executableFinder->find('php', false, $dirs); + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/PhpProcess.php b/vendor/symfony/process/Symfony/Component/Process/PhpProcess.php new file mode 100644 index 0000000..d146057 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/PhpProcess.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * PhpProcess runs a PHP script in an independent process. + * + * $p = new PhpProcess(''); + * $p->run(); + * print $p->getOutput()."\n"; + * + * @author Fabien Potencier + * + * @api + */ +class PhpProcess extends Process +{ + private $executableFinder; + + /** + * Constructor. + * + * @param string $script The PHP script to run (as a string) + * @param string $cwd The working directory + * @param array $env The environment variables + * @param integer $timeout The timeout in seconds + * @param array $options An array of options for proc_open + * + * @api + */ + public function __construct($script, $cwd = null, array $env = array(), $timeout = 60, array $options = array()) + { + parent::__construct(null, $cwd, $env, $script, $timeout, $options); + + $this->executableFinder = new PhpExecutableFinder(); + } + + /** + * Sets the path to the PHP binary to use. + * + * @api + */ + public function setPhpBinary($php) + { + $this->setCommandLine($php); + } + + /** + * {@inheritdoc} + */ + public function start($callback = null) + { + if (null === $this->getCommandLine()) { + if (false === $php = $this->executableFinder->find()) { + throw new RuntimeException('Unable to find the PHP executable.'); + } + $this->setCommandLine($php); + } + + parent::start($callback); + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Process.php b/vendor/symfony/process/Symfony/Component/Process/Process.php new file mode 100644 index 0000000..9d73fc5 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Process.php @@ -0,0 +1,1291 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * Process is a thin wrapper around proc_* functions to ease + * start independent PHP processes. + * + * @author Fabien Potencier + * + * @api + */ +class Process +{ + const ERR = 'err'; + const OUT = 'out'; + + const STATUS_READY = 'ready'; + const STATUS_STARTED = 'started'; + const STATUS_TERMINATED = 'terminated'; + + const STDIN = 0; + const STDOUT = 1; + const STDERR = 2; + + // Timeout Precision in seconds. + const TIMEOUT_PRECISION = 0.2; + + private $callback; + private $commandline; + private $cwd; + private $env; + private $stdin; + private $starttime; + private $timeout; + private $options; + private $exitcode; + private $fallbackExitcode; + private $processInformation; + private $stdout; + private $stderr; + private $enhanceWindowsCompatibility; + private $enhanceSigchildCompatibility; + private $pipes; + private $process; + private $status = self::STATUS_READY; + private $incrementalOutputOffset; + private $incrementalErrorOutputOffset; + private $tty; + + private $fileHandles; + private $readBytes; + + private static $sigchild; + + /** + * Exit codes translation table. + * + * User-defined errors must use exit codes in the 64-113 range. + * + * @var array + */ + public static $exitCodes = array( + 0 => 'OK', + 1 => 'General error', + 2 => 'Misuse of shell builtins', + + 126 => 'Invoked command cannot execute', + 127 => 'Command not found', + 128 => 'Invalid exit argument', + + // signals + 129 => 'Hangup', + 130 => 'Interrupt', + 131 => 'Quit and dump core', + 132 => 'Illegal instruction', + 133 => 'Trace/breakpoint trap', + 134 => 'Process aborted', + 135 => 'Bus error: "access to undefined portion of memory object"', + 136 => 'Floating point exception: "erroneous arithmetic operation"', + 137 => 'Kill (terminate immediately)', + 138 => 'User-defined 1', + 139 => 'Segmentation violation', + 140 => 'User-defined 2', + 141 => 'Write to pipe with no one reading', + 142 => 'Signal raised by alarm', + 143 => 'Termination (request to terminate)', + // 144 - not defined + 145 => 'Child process terminated, stopped (or continued*)', + 146 => 'Continue if stopped', + 147 => 'Stop executing temporarily', + 148 => 'Terminal stop signal', + 149 => 'Background process attempting to read from tty ("in")', + 150 => 'Background process attempting to write to tty ("out")', + 151 => 'Urgent data available on socket', + 152 => 'CPU time limit exceeded', + 153 => 'File size limit exceeded', + 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', + 155 => 'Profiling timer expired', + // 156 - not defined + 157 => 'Pollable event', + // 158 - not defined + 159 => 'Bad syscall', + ); + + /** + * Constructor. + * + * @param string $commandline The command line to run + * @param string $cwd The working directory + * @param array $env The environment variables or null to inherit + * @param string $stdin The STDIN content + * @param integer $timeout The timeout in seconds + * @param array $options An array of options for proc_open + * + * @throws RuntimeException When proc_open is not installed + * + * @api + */ + public function __construct($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array()) + { + if (!function_exists('proc_open')) { + throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.'); + } + + $this->commandline = $commandline; + $this->cwd = $cwd; + + // on windows, if the cwd changed via chdir(), proc_open defaults to the dir where php was started + // on gnu/linux, PHP builds with --enable-maintainer-zts are also affected + // @see : https://bugs.php.net/bug.php?id=51800 + // @see : https://bugs.php.net/bug.php?id=50524 + + if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || defined('PHP_WINDOWS_VERSION_BUILD'))) { + $this->cwd = getcwd(); + } + if (null !== $env) { + $this->setEnv($env); + } else { + $this->env = null; + } + $this->stdin = $stdin; + $this->setTimeout($timeout); + $this->enhanceWindowsCompatibility = true; + $this->enhanceSigchildCompatibility = !defined('PHP_WINDOWS_VERSION_BUILD') && $this->isSigchildEnabled(); + $this->options = array_replace(array('suppress_errors' => true, 'binary_pipes' => true), $options); + } + + public function __destruct() + { + // stop() will check if we have a process running. + $this->stop(); + } + + public function __clone() + { + $this->resetProcessData(); + } + + /** + * Runs the process. + * + * The callback receives the type of output (out or err) and + * some bytes from the output in real-time. It allows to have feedback + * from the independent process during execution. + * + * The STDOUT and STDERR are also available after the process is finished + * via the getOutput() and getErrorOutput() methods. + * + * @param callback|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return integer The exit status code + * + * @throws RuntimeException When process can't be launch or is stopped + * + * @api + */ + public function run($callback = null) + { + $this->start($callback); + + return $this->wait(); + } + + /** + * Starts the process and returns after sending the STDIN. + * + * This method blocks until all STDIN data is sent to the process then it + * returns while the process runs in the background. + * + * The termination of the process can be awaited with wait(). + * + * The callback receives the type of output (out or err) and some bytes from + * the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * If there is no callback passed, the wait() method can be called + * with true as a second parameter then the callback will get all data occurred + * in (and since) the start call. + * + * @param callback|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @throws RuntimeException When process can't be launch or is stopped + * @throws RuntimeException When process is already running + */ + public function start($callback = null) + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running'); + } + + $this->resetProcessData(); + $this->starttime = microtime(true); + $this->callback = $this->buildCallback($callback); + $descriptors = $this->getDescriptors(); + + $commandline = $this->commandline; + + if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->enhanceWindowsCompatibility) { + $commandline = 'cmd /V:ON /E:ON /C "'.$commandline.'"'; + if (!isset($this->options['bypass_shell'])) { + $this->options['bypass_shell'] = true; + } + } + + $this->process = proc_open($commandline, $descriptors, $this->pipes, $this->cwd, $this->env, $this->options); + + if (!is_resource($this->process)) { + throw new RuntimeException('Unable to launch a new process.'); + } + $this->status = self::STATUS_STARTED; + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, false); + } + + $this->writePipes(); + $this->updateStatus(false); + $this->checkTimeout(); + } + + /** + * Restarts the process. + * + * Be warned that the process is cloned before being started. + * + * @param callable $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return Process The new process + * + * @throws RuntimeException When process can't be launch or is stopped + * @throws RuntimeException When process is already running + * + * @see start() + */ + public function restart($callback = null) + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running'); + } + + $process = clone $this; + $process->start($callback); + + return $process; + } + + /** + * Waits for the process to terminate. + * + * The callback receives the type of output (out or err) and some bytes + * from the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @param callback|null $callback A valid PHP callback + * + * @return integer The exitcode of the process + * + * @throws RuntimeException When process timed out + * @throws RuntimeException When process stopped after receiving signal + */ + public function wait($callback = null) + { + $this->updateStatus(false); + if (null !== $callback) { + $this->callback = $this->buildCallback($callback); + } + while ($this->pipes || (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles)) { + $this->checkTimeout(); + $this->readPipes(true); + } + $this->updateStatus(false); + if ($this->processInformation['signaled']) { + if ($this->isSigchildEnabled()) { + throw new RuntimeException('The process has been signaled.'); + } + + throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); + } + + $time = 0; + while ($this->isRunning() && $time < 1000000) { + $time += 1000; + usleep(1000); + } + + if ($this->processInformation['signaled']) { + if ($this->isSigchildEnabled()) { + throw new RuntimeException('The process has been signaled.'); + } + + throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); + } + + return $this->exitcode; + } + + /** + * Returns the Pid (process identifier), if applicable. + * + * @return integer|null The process id if running, null otherwise + * + * @throws RuntimeException In case --enable-sigchild is activated + */ + public function getPid() + { + if ($this->isSigchildEnabled()) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->isRunning() ? $this->processInformation['pid'] : null; + } + + /** + * Sends a posix signal to the process. + * + * @param integer $signal A valid posix signal (see http://www.php.net/manual/en/pcntl.constants.php) + * @return Process + * + * @throws LogicException In case the process is not running + * @throws RuntimeException In case --enable-sigchild is activated + * @throws RuntimeException In case of failure + */ + public function signal($signal) + { + if (!$this->isRunning()) { + throw new LogicException('Can not send signal on a non running process.'); + } + + if ($this->isSigchildEnabled()) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + } + + if (true !== @proc_terminate($this->process, $signal)) { + throw new RuntimeException(sprintf('Error while sending signal `%d`.', $signal)); + } + + return $this; + } + + /** + * Returns the current output of the process (STDOUT). + * + * @return string The process output + * + * @api + */ + public function getOutput() + { + $this->readPipes(false); + + return $this->stdout; + } + + /** + * Returns the output incrementally. + * + * In comparison with the getOutput method which always return the whole + * output, this one returns the new output since the last call. + * + * @return string The process output since the last call + */ + public function getIncrementalOutput() + { + $data = $this->getOutput(); + + $latest = substr($data, $this->incrementalOutputOffset); + $this->incrementalOutputOffset = strlen($data); + + return $latest; + } + + /** + * Returns the current error output of the process (STDERR). + * + * @return string The process error output + * + * @api + */ + public function getErrorOutput() + { + $this->readPipes(false); + + return $this->stderr; + } + + /** + * Returns the errorOutput incrementally. + * + * In comparison with the getErrorOutput method which always return the + * whole error output, this one returns the new error output since the last + * call. + * + * @return string The process error output since the last call + */ + public function getIncrementalErrorOutput() + { + $data = $this->getErrorOutput(); + + $latest = substr($data, $this->incrementalErrorOutputOffset); + $this->incrementalErrorOutputOffset = strlen($data); + + return $latest; + } + + /** + * Returns the exit code returned by the process. + * + * @return integer The exit status code + * + * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled + * + * @api + */ + public function getExitCode() + { + if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method'); + } + + $this->updateStatus(false); + + return $this->exitcode; + } + + /** + * Returns a string representation for the exit code returned by the process. + * + * This method relies on the Unix exit code status standardization + * and might not be relevant for other operating systems. + * + * @return string A string representation for the exit status code + * + * @see http://tldp.org/LDP/abs/html/exitcodes.html + * @see http://en.wikipedia.org/wiki/Unix_signal + */ + public function getExitCodeText() + { + $exitcode = $this->getExitCode(); + + return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; + } + + /** + * Checks if the process ended successfully. + * + * @return Boolean true if the process ended successfully, false otherwise + * + * @api + */ + public function isSuccessful() + { + return 0 === $this->getExitCode(); + } + + /** + * Returns true if the child process has been terminated by an uncaught signal. + * + * It always returns false on Windows. + * + * @return Boolean + * + * @throws RuntimeException In case --enable-sigchild is activated + * + * @api + */ + public function hasBeenSignaled() + { + if ($this->isSigchildEnabled()) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); + } + + $this->updateStatus(false); + + return $this->processInformation['signaled']; + } + + /** + * Returns the number of the signal that caused the child process to terminate its execution. + * + * It is only meaningful if hasBeenSignaled() returns true. + * + * @return integer + * + * @throws RuntimeException In case --enable-sigchild is activated + * + * @api + */ + public function getTermSignal() + { + if ($this->isSigchildEnabled()) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved'); + } + + $this->updateStatus(false); + + return $this->processInformation['termsig']; + } + + /** + * Returns true if the child process has been stopped by a signal. + * + * It always returns false on Windows. + * + * @return Boolean + * + * @api + */ + public function hasBeenStopped() + { + $this->updateStatus(false); + + return $this->processInformation['stopped']; + } + + /** + * Returns the number of the signal that caused the child process to stop its execution. + * + * It is only meaningful if hasBeenStopped() returns true. + * + * @return integer + * + * @api + */ + public function getStopSignal() + { + $this->updateStatus(false); + + return $this->processInformation['stopsig']; + } + + /** + * Checks if the process is currently running. + * + * @return Boolean true if the process is currently running, false otherwise + */ + public function isRunning() + { + if (self::STATUS_STARTED !== $this->status) { + return false; + } + + $this->updateStatus(false); + + return $this->processInformation['running']; + } + + /** + * Checks if the process has been started with no regard to the current state. + * + * @return Boolean true if status is ready, false otherwise + */ + public function isStarted() + { + return $this->status != self::STATUS_READY; + } + + /** + * Checks if the process is terminated. + * + * @return Boolean true if process is terminated, false otherwise + */ + public function isTerminated() + { + $this->updateStatus(false); + + return $this->status == self::STATUS_TERMINATED; + } + + /** + * Gets the process status. + * + * The status is one of: ready, started, terminated. + * + * @return string The current process status + */ + public function getStatus() + { + $this->updateStatus(false); + + return $this->status; + } + + /** + * Stops the process. + * + * @param integer|float $timeout The timeout in seconds + * @param integer $signal A posix signal to send in case the process has not stop at timeout, default is SIGKILL + * + * @return integer The exit-code of the process + * + * @throws RuntimeException if the process got signaled + */ + public function stop($timeout = 10, $signal = null) + { + $timeoutMicro = microtime(true) + $timeout; + if ($this->isRunning()) { + proc_terminate($this->process); + do { + usleep(1000); + } while ($this->isRunning() && microtime(true) < $timeoutMicro); + + if ($this->isRunning() && !$this->isSigchildEnabled()) { + if (null !== $signal || defined('SIGKILL')) { + $this->signal($signal ?: SIGKILL); + } + } + + $this->updateStatus(false); + } + $this->status = self::STATUS_TERMINATED; + + return $this->exitcode; + } + + /** + * Adds a line to the STDOUT stream. + * + * @param string $line The line to append + */ + public function addOutput($line) + { + $this->stdout .= $line; + } + + /** + * Adds a line to the STDERR stream. + * + * @param string $line The line to append + */ + public function addErrorOutput($line) + { + $this->stderr .= $line; + } + + /** + * Gets the command line to be executed. + * + * @return string The command to execute + */ + public function getCommandLine() + { + return $this->commandline; + } + + /** + * Sets the command line to be executed. + * + * @param string $commandline The command to execute + * + * @return self The current Process instance + */ + public function setCommandLine($commandline) + { + $this->commandline = $commandline; + + return $this; + } + + /** + * Gets the process timeout. + * + * @return integer|null The timeout in seconds or null if it's disabled + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Sets the process timeout. + * + * To disable the timeout, set this value to null. + * + * @param float|null $timeout The timeout in seconds + * + * @return self The current Process instance + * + * @throws InvalidArgumentException if the timeout is negative + */ + public function setTimeout($timeout) + { + if (null === $timeout) { + $this->timeout = null; + + return $this; + } + + $timeout = (float) $timeout; + + if ($timeout < 0) { + throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + $this->timeout = $timeout; + + return $this; + } + + /** + * Enables or disables the TTY mode. + * + * @param boolean $tty True to enabled and false to disable + * + * @return self The current Process instance + */ + public function setTty($tty) + { + $this->tty = (Boolean) $tty; + + return $this; + } + + /** + * Checks if the TTY mode is enabled. + * + * @return Boolean true if the TTY mode is enabled, false otherwise + */ + public function isTty() + { + return $this->tty; + } + + /** + * Gets the working directory. + * + * @return string The current working directory + */ + public function getWorkingDirectory() + { + // This is for BC only + if (null === $this->cwd) { + // getcwd() will return false if any one of the parent directories does not have + // the readable or search mode set, even if the current directory does + return getcwd() ?: null; + } + + return $this->cwd; + } + + /** + * Sets the current working directory. + * + * @param string $cwd The new working directory + * + * @return self The current Process instance + */ + public function setWorkingDirectory($cwd) + { + $this->cwd = $cwd; + + return $this; + } + + /** + * Gets the environment variables. + * + * @return array The current environment variables + */ + public function getEnv() + { + return $this->env; + } + + /** + * Sets the environment variables. + * + * An environment variable value should be a string. + * If it is an array, the variable is ignored. + * + * That happens in PHP when 'argv' is registered into + * the $_ENV array for instance. + * + * @param array $env The new environment variables + * + * @return self The current Process instance + */ + public function setEnv(array $env) + { + // Process can not handle env values that are arrays + $env = array_filter($env, function ($value) { if (!is_array($value)) { return true; } }); + + $this->env = array(); + foreach ($env as $key => $value) { + $this->env[(binary) $key] = (binary) $value; + } + + return $this; + } + + /** + * Gets the contents of STDIN. + * + * @return string The current contents + */ + public function getStdin() + { + return $this->stdin; + } + + /** + * Sets the contents of STDIN. + * + * @param string $stdin The new contents + * + * @return self The current Process instance + */ + public function setStdin($stdin) + { + $this->stdin = $stdin; + + return $this; + } + + /** + * Gets the options for proc_open. + * + * @return array The current options + */ + public function getOptions() + { + return $this->options; + } + + /** + * Sets the options for proc_open. + * + * @param array $options The new options + * + * @return self The current Process instance + */ + public function setOptions(array $options) + { + $this->options = $options; + + return $this; + } + + /** + * Gets whether or not Windows compatibility is enabled. + * + * This is true by default. + * + * @return Boolean + */ + public function getEnhanceWindowsCompatibility() + { + return $this->enhanceWindowsCompatibility; + } + + /** + * Sets whether or not Windows compatibility is enabled. + * + * @param Boolean $enhance + * + * @return self The current Process instance + */ + public function setEnhanceWindowsCompatibility($enhance) + { + $this->enhanceWindowsCompatibility = (Boolean) $enhance; + + return $this; + } + + /** + * Returns whether sigchild compatibility mode is activated or not. + * + * @return Boolean + */ + public function getEnhanceSigchildCompatibility() + { + return $this->enhanceSigchildCompatibility; + } + + /** + * Activates sigchild compatibility mode. + * + * Sigchild compatibility mode is required to get the exit code and + * determine the success of a process when PHP has been compiled with + * the --enable-sigchild option + * + * @param Boolean $enhance + * + * @return self The current Process instance + */ + public function setEnhanceSigchildCompatibility($enhance) + { + $this->enhanceSigchildCompatibility = (Boolean) $enhance; + + return $this; + } + + /** + * Performs a check between the timeout definition and the time the process started. + * + * In case you run a background process (with the start method), you should + * trigger this method regularly to ensure the process timeout + * + * @throws RuntimeException In case the timeout was reached + */ + public function checkTimeout() + { + if (0 < $this->timeout && $this->timeout < microtime(true) - $this->starttime) { + $this->stop(0); + + throw new RuntimeException('The process timed-out.'); + } + } + + /** + * Creates the descriptors needed by the proc_open. + * + * @return array + */ + private function getDescriptors() + { + //Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big. + //Workaround for this problem is to use temporary files instead of pipes on Windows platform. + //@see https://bugs.php.net/bug.php?id=51800 + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->fileHandles = array( + self::STDOUT => tmpfile(), + ); + if (false === $this->fileHandles[self::STDOUT]) { + throw new RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable'); + } + $this->readBytes = array( + self::STDOUT => 0, + ); + + return array(array('pipe', 'r'), $this->fileHandles[self::STDOUT], array('pipe', 'w')); + } + + if ($this->tty) { + $descriptors = array( + array('file', '/dev/tty', 'r'), + array('file', '/dev/tty', 'w'), + array('file', '/dev/tty', 'w'), + ); + } else { + $descriptors = array( + array('pipe', 'r'), // stdin + array('pipe', 'w'), // stdout + array('pipe', 'w'), // stderr + ); + } + + if ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + // last exit code is output on the fourth pipe and caught to work around --enable-sigchild + $descriptors = array_merge($descriptors, array(array('pipe', 'w'))); + + $this->commandline = '('.$this->commandline.') 3>/dev/null; code=$?; echo $code >&3; exit $code'; + } + + return $descriptors; + } + + /** + * Builds up the callback used by wait(). + * + * The callbacks adds all occurred output to the specific buffer and calls + * the user callback (if present) with the received output. + * + * @param callback|null $callback The user defined PHP callback + * + * @return callback A PHP callable + */ + protected function buildCallback($callback) + { + $that = $this; + $out = self::OUT; + $err = self::ERR; + $callback = function ($type, $data) use ($that, $callback, $out, $err) { + if ($out == $type) { + $that->addOutput($data); + } else { + $that->addErrorOutput($data); + } + + if (null !== $callback) { + call_user_func($callback, $type, $data); + } + }; + + return $callback; + } + + /** + * Updates the status of the process, reads pipes. + * + * @param Boolean $blocking Whether to use a clocking read call. + */ + protected function updateStatus($blocking) + { + if (self::STATUS_STARTED !== $this->status) { + return; + } + + $this->readPipes($blocking); + + $this->processInformation = proc_get_status($this->process); + $this->captureExitCode(); + if (!$this->processInformation['running']) { + $this->close(); + $this->status = self::STATUS_TERMINATED; + } + } + + /** + * Returns whether PHP has been compiled with the '--enable-sigchild' option or not. + * + * @return Boolean + */ + protected function isSigchildEnabled() + { + if (null !== self::$sigchild) { + return self::$sigchild; + } + + ob_start(); + phpinfo(INFO_GENERAL); + + return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); + } + + /** + * Handles the windows file handles fallbacks. + * + * @param Boolean $closeEmptyHandles if true, handles that are empty will be assumed closed + */ + private function processFileHandles($closeEmptyHandles = false) + { + $fh = $this->fileHandles; + foreach ($fh as $type => $fileHandle) { + fseek($fileHandle, $this->readBytes[$type]); + $data = fread($fileHandle, 8192); + if (strlen($data) > 0) { + $this->readBytes[$type] += strlen($data); + call_user_func($this->callback, $type == 1 ? self::OUT : self::ERR, $data); + } + if (false === $data || ($closeEmptyHandles && '' === $data && feof($fileHandle))) { + fclose($fileHandle); + unset($this->fileHandles[$type]); + } + } + } + + /** + * Returns true if a system call has been interrupted. + * + * @return Boolean + */ + private function hasSystemCallBeenInterrupted() + { + $lastError = error_get_last(); + + // stream_select returns false when the `select` system call is interrupted by an incoming signal + return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); + } + + /** + * Reads pipes, executes callback. + * + * @param Boolean $blocking Whether to use blocking calls or not. + */ + private function readPipes($blocking) + { + if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles) { + $this->processFileHandles(!$this->pipes); + } + + if ($this->pipes) { + $r = $this->pipes; + $w = null; + $e = null; + + // let's have a look if something changed in streams + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? ceil(self::TIMEOUT_PRECISION * 1E6) : 0)) { + // if a system call has been interrupted, forget about it, let's try again + // otherwise, an error occured, let's reset pipes + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = array(); + } + + return; + } + + // nothing has changed + if (0 === $n) { + return; + } + + $this->processReadPipes($r); + } + } + + /** + * Writes data to pipes. + * + * @param Boolean $blocking Whether to use blocking calls or not. + */ + private function writePipes() + { + if ($this->tty) { + $this->status = self::STATUS_TERMINATED; + + return; + } + + if (null === $this->stdin) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + + return; + } + + $writePipes = array($this->pipes[0]); + unset($this->pipes[0]); + $stdinLen = strlen($this->stdin); + $stdinOffset = 0; + + while ($writePipes) { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->processFileHandles(); + } + + $r = $this->pipes; + $w = $writePipes; + $e = null; + + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? ceil(static::TIMEOUT_PRECISION * 1E6) : 0)) { + // if a system call has been interrupted, forget about it, let's try again + if ($this->hasSystemCallBeenInterrupted()) { + continue; + } + break; + } + + // nothing has changed, let's wait until the process is ready + if (0 === $n) { + continue; + } + + if ($w) { + $written = fwrite($writePipes[0], (binary) substr($this->stdin, $stdinOffset), 8192); + if (false !== $written) { + $stdinOffset += $written; + } + if ($stdinOffset >= $stdinLen) { + fclose($writePipes[0]); + $writePipes = null; + } + } + + $this->processReadPipes($r); + } + } + + /** + * Processes read pipes, executes callback on it. + * + * @param array $pipes + */ + private function processReadPipes(array $pipes) + { + foreach ($pipes as $pipe) { + $type = array_search($pipe, $this->pipes); + $data = fread($pipe, 8192); + + if (strlen($data) > 0) { + // last exit code is output and caught to work around --enable-sigchild + if (3 == $type) { + $this->fallbackExitcode = (int) $data; + } else { + call_user_func($this->callback, $type == 1 ? self::OUT : self::ERR, $data); + } + } + if (false === $data || feof($pipe)) { + fclose($pipe); + unset($this->pipes[$type]); + } + } + } + + /** + * Captures the exitcode if mentioned in the process informations. + */ + private function captureExitCode() + { + if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) { + $this->exitcode = $this->processInformation['exitcode']; + } + } + + + /** + * Closes process resource, closes file handles, sets the exitcode. + * + * @return Integer The exitcode + */ + private function close() + { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + + $this->pipes = null; + $exitcode = -1; + + if (is_resource($this->process)) { + $exitcode = proc_close($this->process); + } + + $this->exitcode = $this->exitcode !== null ? $this->exitcode : -1; + $this->exitcode = -1 != $exitcode ? $exitcode : $this->exitcode; + + if (-1 == $this->exitcode && null !== $this->fallbackExitcode) { + $this->exitcode = $this->fallbackExitcode; + } elseif (-1 === $this->exitcode && $this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { + // if process has been signaled, no exitcode but a valid termsig, apply unix convention + $this->exitcode = 128 + $this->processInformation['termsig']; + } + + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + foreach ($this->fileHandles as $fileHandle) { + fclose($fileHandle); + } + $this->fileHandles = array(); + } + + return $this->exitcode; + } + + /** + * Resets data related to the latest run of the process. + */ + private function resetProcessData() + { + $this->starttime = null; + $this->callback = null; + $this->exitcode = null; + $this->fallbackExitcode = null; + $this->processInformation = null; + $this->stdout = null; + $this->stderr = null; + $this->pipes = null; + $this->process = null; + $this->status = self::STATUS_READY; + $this->fileHandles = null; + $this->readBytes = null; + $this->incrementalOutputOffset = 0; + $this->incrementalErrorOutputOffset = 0; + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/ProcessBuilder.php b/vendor/symfony/process/Symfony/Component/Process/ProcessBuilder.php new file mode 100644 index 0000000..ddd064a --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/ProcessBuilder.php @@ -0,0 +1,174 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; +use Symfony\Component\Process\Exception\LogicException; + +/** + * Process builder. + * + * @author Kris Wallsmith + */ +class ProcessBuilder +{ + private $arguments; + private $cwd; + private $env; + private $stdin; + private $timeout; + private $options; + private $inheritEnv; + private $prefix; + + public function __construct(array $arguments = array()) + { + $this->arguments = $arguments; + + $this->timeout = 60; + $this->options = array(); + $this->env = array(); + $this->inheritEnv = true; + } + + public static function create(array $arguments = array()) + { + return new static($arguments); + } + + /** + * Adds an unescaped argument to the command string. + * + * @param string $argument A command argument + * + * @return ProcessBuilder + */ + public function add($argument) + { + $this->arguments[] = $argument; + + return $this; + } + + /** + * Adds an unescaped prefix to the command string. + * + * The prefix is preserved when reseting arguments. + * + * @param string $prefix A command prefix + * + * @return ProcessBuilder + */ + public function setPrefix($prefix) + { + $this->prefix = $prefix; + + return $this; + } + + /** + * @param array $arguments + * + * @return ProcessBuilder + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + + return $this; + } + + public function setWorkingDirectory($cwd) + { + $this->cwd = $cwd; + + return $this; + } + + public function inheritEnvironmentVariables($inheritEnv = true) + { + $this->inheritEnv = $inheritEnv; + + return $this; + } + + public function setEnv($name, $value) + { + $this->env[$name] = $value; + + return $this; + } + + public function setInput($stdin) + { + $this->stdin = $stdin; + + return $this; + } + + /** + * Sets the process timeout. + * + * To disable the timeout, set this value to null. + * + * @param float|null + * + * @return ProcessBuilder + * + * @throws InvalidArgumentException + */ + public function setTimeout($timeout) + { + if (null === $timeout) { + $this->timeout = null; + + return $this; + } + + $timeout = (float) $timeout; + + if ($timeout < 0) { + throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + $this->timeout = $timeout; + + return $this; + } + + public function setOption($name, $value) + { + $this->options[$name] = $value; + + return $this; + } + + public function getProcess() + { + if (!$this->prefix && !count($this->arguments)) { + throw new LogicException('You must add() command arguments before calling getProcess().'); + } + + $options = $this->options; + + $arguments = $this->prefix ? array_merge(array($this->prefix), $this->arguments) : $this->arguments; + $script = implode(' ', array_map(array(__NAMESPACE__.'\\ProcessUtils', 'escapeArgument'), $arguments)); + + if ($this->inheritEnv) { + $env = $this->env ? $this->env + $_ENV : null; + } else { + $env = $this->env; + } + + return new Process($script, $this->cwd, $env, $this->stdin, $this->timeout, $options); + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/ProcessUtils.php b/vendor/symfony/process/Symfony/Component/Process/ProcessUtils.php new file mode 100644 index 0000000..4a5b7d6 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/ProcessUtils.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * ProcessUtils is a bunch of utility methods. + * + * This class contains static methods only and is not meant to be instantiated. + * + * @author Martin Hasoň + */ +class ProcessUtils +{ + /** + * This class should not be instantiated + */ + private function __construct() + { + } + + /** + * Escapes a string to be used as a shell argument. + * + * @param string $argument The argument that will be escaped + * + * @return string The escaped argument + */ + public static function escapeArgument($argument) + { + //Fix for PHP bug #43784 escapeshellarg removes % from given string + //Fix for PHP bug #49446 escapeshellarg dosn`t work on windows + //@see https://bugs.php.net/bug.php?id=43784 + //@see https://bugs.php.net/bug.php?id=49446 + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + if ('' === $argument) { + return escapeshellarg($argument); + } + + $escapedArgument = ''; + foreach (preg_split('/([%"])/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) { + if ('"' === $part) { + $escapedArgument .= '\\"'; + } elseif ('%' === $part) { + $escapedArgument .= '^%'; + } else { + $escapedArgument .= escapeshellarg($part); + } + } + + return $escapedArgument; + } + + return escapeshellarg($argument); + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/README.md b/vendor/symfony/process/Symfony/Component/Process/README.md new file mode 100644 index 0000000..7b9f307 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/README.md @@ -0,0 +1,47 @@ +Process Component +================= + +Process executes commands in sub-processes. + +In this example, we run a simple directory listing and get the result back: + + use Symfony\Component\Process\Process; + + $process = new Process('ls -lsa'); + $process->setTimeout(3600); + $process->run(); + if (!$process->isSuccessful()) { + throw new RuntimeException($process->getErrorOutput()); + } + + print $process->getOutput(); + +You can think that this is easy to achieve with plain PHP but it's not especially +if you want to take care of the subtle differences between the different platforms. + +And if you want to be able to get some feedback in real-time, just pass an +anonymous function to the ``run()`` method and you will get the output buffer +as it becomes available: + + use Symfony\Component\Process\Process; + + $process = new Process('ls -lsa'); + $process->run(function ($type, $buffer) { + if ('err' === $type) { + echo 'ERR > '.$buffer; + } else { + echo 'OUT > '.$buffer; + } + }); + +That's great if you want to execute a long running command (like rsync-ing files to a +remote server) and give feedback to the user in real-time. + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/XXX/ + $ composer.phar install --dev + $ phpunit diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/AbstractProcessTest.php b/vendor/symfony/process/Symfony/Component/Process/Tests/AbstractProcessTest.php new file mode 100644 index 0000000..d0228f0 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/AbstractProcessTest.php @@ -0,0 +1,641 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\Process; +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * @author Robert Schönthal + */ +abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException + */ + public function testNegativeTimeoutFromConstructor() + { + $this->getProcess('', null, null, null, -1); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException + */ + public function testNegativeTimeoutFromSetter() + { + $p = $this->getProcess(''); + $p->setTimeout(-1); + } + + public function testNullTimeout() + { + $p = $this->getProcess(''); + $p->setTimeout(10); + $p->setTimeout(null); + + $this->assertNull($p->getTimeout()); + } + + public function testStopWithTimeoutIsActuallyWorking() + { + $this->verifyPosixIsEnabled(); + + // exec is mandatory here since we send a signal to the process + // see https://github.com/symfony/symfony/issues/5030 about prepending + // command with exec + $p = $this->getProcess('exec php '.__DIR__.'/NonStopableProcess.php 3'); + $p->start(); + usleep(100000); + $start = microtime(true); + $p->stop(1.1, SIGKILL); + while ($p->isRunning()) { + usleep(1000); + } + $duration = microtime(true) - $start; + + $this->assertLessThan(1.8, $duration); + } + + public function testCallbacksAreExecutedWithStart() + { + $data = ''; + + $process = $this->getProcess('echo "foo";sleep 1;echo "foo"'); + $process->start(function ($type, $buffer) use (&$data) { + $data .= $buffer; + }); + + $start = microtime(true); + while ($process->isRunning()) { + usleep(10000); + } + + $this->assertEquals("foo\nfoo\n", $data); + } + + /** + * tests results from sub processes + * + * @dataProvider responsesCodeProvider + */ + public function testProcessResponses($expected, $getter, $code) + { + $p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code))); + $p->run(); + + $this->assertSame($expected, $p->$getter()); + } + + /** + * tests results from sub processes + * + * @dataProvider pipesCodeProvider + */ + public function testProcessPipes($code, $size) + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Test hangs on Windows & PHP due to https://bugs.php.net/bug.php?id=60120 and https://bugs.php.net/bug.php?id=51800'); + } + + $expected = str_repeat(str_repeat('*', 1024), $size) . '!'; + $expectedLength = (1024 * $size) + 1; + + $p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code))); + $p->setStdin($expected); + $p->run(); + + $this->assertEquals($expectedLength, strlen($p->getOutput())); + $this->assertEquals($expectedLength, strlen($p->getErrorOutput())); + } + + public function chainedCommandsOutputProvider() + { + return array( + array("1\n1\n", ';', '1'), + array("2\n2\n", '&&', '2'), + ); + } + + /** + * + * @dataProvider chainedCommandsOutputProvider + */ + public function testChainedCommandsOutput($expected, $operator, $input) + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Does it work on windows ?'); + } + + $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input)); + $process->run(); + $this->assertEquals($expected, $process->getOutput()); + } + + public function testCallbackIsExecutedForOutput() + { + $p = $this->getProcess(sprintf('php -r %s', escapeshellarg('echo \'foo\';'))); + + $called = false; + $p->run(function ($type, $buffer) use (&$called) { + $called = $buffer === 'foo'; + }); + + $this->assertTrue($called, 'The callback should be executed with the output'); + } + + public function testGetErrorOutput() + { + $p = new Process(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }'))); + + $p->run(); + $this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches)); + } + + public function testGetIncrementalErrorOutput() + { + $p = new Process(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { usleep(50000); file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }'))); + + $p->start(); + while ($p->isRunning()) { + $this->assertLessThanOrEqual(1, preg_match_all('/ERROR/', $p->getIncrementalErrorOutput(), $matches)); + usleep(20000); + } + } + + public function testGetOutput() + { + $p = new Process(sprintf('php -r %s', escapeshellarg('$n=0;while ($n<3) {echo \' foo \';$n++;}'))); + + $p->run(); + $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches)); + } + + public function testGetIncrementalOutput() + { + $p = new Process(sprintf('php -r %s', escapeshellarg('$n=0;while ($n<3) { echo \' foo \'; usleep(50000); $n++; }'))); + + $p->start(); + while ($p->isRunning()) { + $this->assertLessThanOrEqual(1, preg_match_all('/foo/', $p->getIncrementalOutput(), $matches)); + usleep(20000); + } + } + + public function testExitCodeCommandFailed() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Windows does not support POSIX exit code'); + } + + // such command run in bash return an exitcode 127 + $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis'); + $process->run(); + + $this->assertGreaterThan(0, $process->getExitCode()); + } + + public function testTTYCommand() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Windows does have /dev/tty support'); + } + + $process = $this->getProcess('echo "foo" >> /dev/null'); + $process->setTTY(true); + $process->run(); + + $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus()); + } + + public function testExitCodeText() + { + $process = $this->getProcess(''); + $r = new \ReflectionObject($process); + $p = $r->getProperty('exitcode'); + $p->setAccessible(true); + + $p->setValue($process, 2); + $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText()); + } + + public function testStartIsNonBlocking() + { + $process = $this->getProcess('php -r "sleep(4);"'); + $start = microtime(true); + $process->start(); + $end = microtime(true); + $this->assertLessThan(1 , $end-$start); + } + + public function testUpdateStatus() + { + $process = $this->getProcess('php -h'); + $process->run(); + $this->assertTrue(strlen($process->getOutput()) > 0); + } + + public function testGetExitCodeIsNullOnStart() + { + $process = $this->getProcess('php -r "usleep(200000);"'); + $this->assertNull($process->getExitCode()); + $process->start(); + $this->assertNull($process->getExitCode()); + $process->wait(); + $this->assertEquals(0, $process->getExitCode()); + } + + public function testGetExitCodeIsNullOnWhenStartingAgain() + { + $process = $this->getProcess('php -r "usleep(200000);"'); + $process->run(); + $this->assertEquals(0, $process->getExitCode()); + $process->start(); + $this->assertNull($process->getExitCode()); + $process->wait(); + $this->assertEquals(0, $process->getExitCode()); + } + + public function testGetExitCode() + { + $process = $this->getProcess('php -m'); + $process->run(); + $this->assertEquals(0, $process->getExitCode()); + } + + public function testStatus() + { + $process = $this->getProcess('php -r "usleep(500000);"'); + $this->assertFalse($process->isRunning()); + $this->assertFalse($process->isStarted()); + $this->assertFalse($process->isTerminated()); + $this->assertSame(Process::STATUS_READY, $process->getStatus()); + $process->start(); + $this->assertTrue($process->isRunning()); + $this->assertTrue($process->isStarted()); + $this->assertFalse($process->isTerminated()); + $this->assertSame(Process::STATUS_STARTED, $process->getStatus()); + $process->wait(); + $this->assertFalse($process->isRunning()); + $this->assertTrue($process->isStarted()); + $this->assertTrue($process->isTerminated()); + $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus()); + } + + public function testStop() + { + $process = $this->getProcess('php -r "sleep(4);"'); + $process->start(); + $this->assertTrue($process->isRunning()); + $process->stop(); + $this->assertFalse($process->isRunning()); + } + + public function testIsSuccessful() + { + $process = $this->getProcess('php -m'); + $process->run(); + $this->assertTrue($process->isSuccessful()); + } + + public function testIsSuccessfulOnlyAfterTerminated() + { + $process = $this->getProcess('sleep 1'); + $process->start(); + while ($process->isRunning()) { + $this->assertFalse($process->isSuccessful()); + usleep(300000); + } + + $this->assertTrue($process->isSuccessful()); + } + + public function testIsNotSuccessful() + { + $process = $this->getProcess('php -r "sleep(4);"'); + $process->start(); + $this->assertTrue($process->isRunning()); + $process->stop(); + $this->assertFalse($process->isSuccessful()); + } + + public function testProcessIsNotSignaled() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + + $process = $this->getProcess('php -m'); + $process->run(); + $this->assertFalse($process->hasBeenSignaled()); + } + + public function testProcessWithoutTermSignalIsNotSignaled() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + + $process = $this->getProcess('php -m'); + $process->run(); + $this->assertFalse($process->hasBeenSignaled()); + } + + public function testProcessWithoutTermSignal() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + + $process = $this->getProcess('php -m'); + $process->run(); + $this->assertEquals(0, $process->getTermSignal()); + } + + public function testProcessIsSignaledIfStopped() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + + $process = $this->getProcess('php -r "sleep(4);"'); + $process->start(); + $process->stop(); + $this->assertTrue($process->hasBeenSignaled()); + } + + public function testProcessWithTermSignal() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + + // SIGTERM is only defined if pcntl extension is present + $termSignal = defined('SIGTERM') ? SIGTERM : 15; + + $process = $this->getProcess('php -r "sleep(4);"'); + $process->start(); + $process->stop(); + + $this->assertEquals($termSignal, $process->getTermSignal()); + } + + public function testProcessThrowsExceptionWhenExternallySignaled() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + + if (!function_exists('posix_kill')) { + $this->markTestSkipped('posix_kill is required for this test'); + } + + $termSignal = defined('SIGKILL') ? SIGKILL : 9; + + $process = $this->getProcess('exec php -r "while (true) {}"'); + $process->start(); + posix_kill($process->getPid(), $termSignal); + + $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'The process has been signaled with signal "9".'); + $process->wait(); + } + + public function testRestart() + { + $process1 = $this->getProcess('php -r "echo getmypid();"'); + $process1->run(); + $process2 = $process1->restart(); + + usleep(300000); // wait for output + + // Ensure that both processed finished and the output is numeric + $this->assertFalse($process1->isRunning()); + $this->assertFalse($process2->isRunning()); + $this->assertTrue(is_numeric($process1->getOutput())); + $this->assertTrue(is_numeric($process2->getOutput())); + + // Ensure that restart returned a new process by check that the output is different + $this->assertNotEquals($process1->getOutput(), $process2->getOutput()); + } + + public function testPhpDeadlock() + { + $this->markTestSkipped('Can course php to hang'); + + // Sleep doesn't work as it will allow the process to handle signals and close + // file handles from the other end. + $process = $this->getProcess('php -r "while (true) {}"'); + $process->start(); + + // PHP will deadlock when it tries to cleanup $process + } + + public function testRunProcessWithTimeout() + { + $timeout = 0.5; + $process = $this->getProcess('sleep 3'); + $process->setTimeout($timeout); + $start = microtime(true); + try { + $process->run(); + $this->fail('A RuntimeException should have been raised'); + } catch (RuntimeException $e) { + + } + $duration = microtime(true) - $start; + + $this->assertLessThan($timeout + Process::TIMEOUT_PRECISION, $duration); + } + + public function testCheckTimeoutOnStartedProcess() + { + $timeout = 0.5; + $precision = 100000; + $process = $this->getProcess('sleep 3'); + $process->setTimeout($timeout); + $start = microtime(true); + + $process->start(); + + try { + while ($process->isRunning()) { + $process->checkTimeout(); + usleep($precision); + } + $this->fail('A RuntimeException should have been raised'); + } catch (RuntimeException $e) { + + } + $duration = microtime(true) - $start; + + $this->assertLessThan($timeout + $precision, $duration); + $this->assertFalse($process->isSuccessful()); + } + + public function testGetPid() + { + $process = $this->getProcess('php -r "sleep(1);"'); + $process->start(); + $this->assertGreaterThan(0, $process->getPid()); + $process->stop(); + } + + public function testGetPidIsNullBeforeStart() + { + $process = $this->getProcess('php -r "sleep(1);"'); + $this->assertNull($process->getPid()); + } + + public function testGetPidIsNullAfterRun() + { + $process = $this->getProcess('php -m'); + $process->run(); + $this->assertNull($process->getPid()); + } + + public function testSignal() + { + $this->verifyPosixIsEnabled(); + + $process = $this->getProcess('exec php -f ' . __DIR__ . '/SignalListener.php'); + $process->start(); + usleep(500000); + $process->signal(SIGUSR1); + + while ($process->isRunning() && false === strpos($process->getoutput(), 'Caught SIGUSR1')) { + usleep(10000); + } + + $this->assertEquals('Caught SIGUSR1', $process->getOutput()); + } + + public function testExitCodeIsAvailableAfterSignal() + { + $this->verifyPosixIsEnabled(); + + $process = $this->getProcess('sleep 4'); + $process->start(); + $process->signal(SIGKILL); + + while ($process->isRunning()) { + usleep(10000); + } + + $this->assertFalse($process->isRunning()); + $this->assertTrue($process->hasBeenSignaled()); + $this->assertFalse($process->isSuccessful()); + $this->assertEquals(137, $process->getExitCode()); + } + + /** + * @expectedException Symfony\Component\Process\Exception\LogicException + */ + public function testSignalProcessNotRunning() + { + $this->verifyPosixIsEnabled(); + $process = $this->getProcess('php -m'); + $process->signal(SIGHUP); + } + + private function verifyPosixIsEnabled() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('POSIX signals do not work on windows'); + } + if (!defined('SIGUSR1')) { + $this->markTestSkipped('The pcntl extension is not enabled'); + } + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testSignalWithWrongIntSignal() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('POSIX signals do not work on windows'); + } + + $process = $this->getProcess('php -r "sleep(3);"'); + $process->start(); + $process->signal(-4); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testSignalWithWrongNonIntSignal() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->markTestSkipped('POSIX signals do not work on windows'); + } + + $process = $this->getProcess('php -r "sleep(3);"'); + $process->start(); + $process->signal('Céphalopodes'); + } + + public function responsesCodeProvider() + { + return array( + //expected output / getter / code to execute + //array(1,'getExitCode','exit(1);'), + //array(true,'isSuccessful','exit();'), + array('output', 'getOutput', 'echo \'output\';'), + ); + } + + public function pipesCodeProvider() + { + $variations = array( + 'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);', + 'include \''.__DIR__.'/ProcessTestHelper.php\';', + ); + + $codes = array(); + foreach (array(1, 16, 64, 1024, 4096) as $size) { + foreach ($variations as $code) { + $codes[] = array($code, $size); + } + } + + return $codes; + } + + /** + * provides default method names for simple getter/setter + */ + public function methodProvider() + { + $defaults = array( + array('CommandLine'), + array('Timeout'), + array('WorkingDirectory'), + array('Env'), + array('Stdin'), + array('Options') + ); + + return $defaults; + } + + /** + * @param string $commandline + * @param null $cwd + * @param array $env + * @param null $stdin + * @param integer $timeout + * @param array $options + * + * @return Process + */ + abstract protected function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array()); +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/NonStopableProcess.php b/vendor/symfony/process/Symfony/Component/Process/Tests/NonStopableProcess.php new file mode 100644 index 0000000..a4db838 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/NonStopableProcess.php @@ -0,0 +1,37 @@ + (microtime(true) - $start)) { + usleep(1000); +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php b/vendor/symfony/process/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php new file mode 100644 index 0000000..99c4a1e --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/PhpExecutableFinderTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\PhpExecutableFinder; + +/** + * @author Robert Schönthal + */ +class PhpExecutableFinderTest extends \PHPUnit_Framework_TestCase +{ + /** + * tests find() with the env var PHP_PATH + */ + public function testFindWithPhpPath() + { + if (defined('PHP_BINARY')) { + $this->markTestSkipped('The PHP binary is easily available as of PHP 5.4'); + } + + $f = new PhpExecutableFinder(); + + $current = $f->find(); + + //not executable PHP_PATH + putenv('PHP_PATH=/not/executable/php'); + $this->assertFalse($f->find(), '::find() returns false for not executable php'); + + //executable PHP_PATH + putenv('PHP_PATH='.$current); + $this->assertEquals($f->find(), $current, '::find() returns the executable php'); + } + + /** + * tests find() with default executable + */ + public function testFindWithSuffix() + { + if (defined('PHP_BINARY')) { + $this->markTestSkipped('The PHP binary is easily available as of PHP 5.4'); + } + + putenv('PHP_PATH='); + putenv('PHP_PEAR_PHP_BIN='); + $f = new PhpExecutableFinder(); + + $current = $f->find(); + + //TODO maybe php executable is custom or even windows + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->assertTrue(is_executable($current)); + $this->assertTrue((bool) preg_match('/'.addSlashes(DIRECTORY_SEPARATOR).'php\.(exe|bat|cmd|com)$/i', $current), '::find() returns the executable php with suffixes'); + } + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/PhpProcessTest.php b/vendor/symfony/process/Symfony/Component/Process/Tests/PhpProcessTest.php new file mode 100644 index 0000000..df66ad6 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/PhpProcessTest.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\PhpProcess; + +class PhpProcessTest extends \PHPUnit_Framework_TestCase +{ + public function testNonBlockingWorks() + { + $expected = 'hello world!'; + $process = new PhpProcess(<<start(); + $process->wait(); + $this->assertEquals($expected, $process->getOutput()); + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessBuilderTest.php b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessBuilderTest.php new file mode 100644 index 0000000..4c88b55 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessBuilderTest.php @@ -0,0 +1,200 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\ProcessBuilder; + +class ProcessBuilderTest extends \PHPUnit_Framework_TestCase +{ + public function testInheritEnvironmentVars() + { + $snapshot = $_ENV; + $_ENV = $expected = array('foo' => 'bar'); + + $pb = new ProcessBuilder(); + $pb->add('foo')->inheritEnvironmentVariables(); + $proc = $pb->getProcess(); + + $this->assertNull($proc->getEnv(), '->inheritEnvironmentVariables() copies $_ENV'); + + $_ENV = $snapshot; + } + + public function testProcessShouldInheritAndOverrideEnvironmentVars() + { + $snapshot = $_ENV; + $_ENV = array('foo' => 'bar', 'bar' => 'baz'); + $expected = array('foo' => 'foo', 'bar' => 'baz'); + + $pb = new ProcessBuilder(); + $pb->add('foo')->inheritEnvironmentVariables() + ->setEnv('foo', 'foo'); + $proc = $pb->getProcess(); + + $this->assertEquals($expected, $proc->getEnv(), '->inheritEnvironmentVariables() copies $_ENV'); + + $_ENV = $snapshot; + } + + public function testProcessBuilderShouldNotPassEnvArrays() + { + $snapshot = $_ENV; + $_ENV = array('a' => array('b', 'c'), 'd' => 'e', 'f' => 'g'); + $expected = array('d' => 'e', 'f' => 'g'); + + $pb = new ProcessBuilder(); + $pb->add('a')->inheritEnvironmentVariables() + ->setEnv('d', 'e'); + $proc = $pb->getProcess(); + + $this->assertEquals($expected, $proc->getEnv(), '->inheritEnvironmentVariables() removes array values from $_ENV'); + + $_ENV = $snapshot; + } + + public function testInheritEnvironmentVarsByDefault() + { + $pb = new ProcessBuilder(); + $proc = $pb->add('foo')->getProcess(); + + $this->assertNull($proc->getEnv()); + } + + public function testNotReplaceExplicitlySetVars() + { + $snapshot = $_ENV; + $_ENV = array('foo' => 'bar'); + $expected = array('foo' => 'baz'); + + $pb = new ProcessBuilder(); + $pb + ->setEnv('foo', 'baz') + ->inheritEnvironmentVariables() + ->add('foo') + ; + $proc = $pb->getProcess(); + + $this->assertEquals($expected, $proc->getEnv(), '->inheritEnvironmentVariables() copies $_ENV'); + + $_ENV = $snapshot; + } + + /** + * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException + */ + public function testNegativeTimeoutFromSetter() + { + $pb = new ProcessBuilder(); + $pb->setTimeout(-1); + } + + public function testNullTimeout() + { + $pb = new ProcessBuilder(); + $pb->setTimeout(10); + $pb->setTimeout(null); + + $r = new \ReflectionObject($pb); + $p = $r->getProperty('timeout'); + $p->setAccessible(true); + + $this->assertNull($p->getValue($pb)); + } + + public function testShouldSetArguments() + { + $pb = new ProcessBuilder(array('initial')); + $pb->setArguments(array('second')); + + $proc = $pb->getProcess(); + + $this->assertContains("second", $proc->getCommandLine()); + } + + public function testPrefixIsPrependedToAllGeneratedProcess() + { + $pb = new ProcessBuilder(); + $pb->setPrefix('/usr/bin/php'); + + $proc = $pb->setArguments(array('-v'))->getProcess(); + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->assertEquals('"/usr/bin/php" "-v"', $proc->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php' '-v'", $proc->getCommandLine()); + } + + $proc = $pb->setArguments(array('-i'))->getProcess(); + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->assertEquals('"/usr/bin/php" "-i"', $proc->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php' '-i'", $proc->getCommandLine()); + } + } + + public function testShouldEscapeArguments() + { + $pb = new ProcessBuilder(array('%path%', 'foo " bar')); + $proc = $pb->getProcess(); + + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->assertSame('^%"path"^% "foo "\\"" bar"', $proc->getCommandLine()); + } else { + $this->assertSame("'%path%' 'foo \" bar'", $proc->getCommandLine()); + } + } + + public function testShouldEscapeArgumentsAndPrefix() + { + $pb = new ProcessBuilder(array('arg')); + $pb->setPrefix('%prefix%'); + $proc = $pb->getProcess(); + + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->assertSame('^%"prefix"^% "arg"', $proc->getCommandLine()); + } else { + $this->assertSame("'%prefix%' 'arg'", $proc->getCommandLine()); + } + } + + /** + * @expectedException \Symfony\Component\Process\Exception\LogicException + */ + public function testShouldThrowALogicExceptionIfNoPrefixAndNoArgument() + { + ProcessBuilder::create()->getProcess(); + } + + public function testShouldNotThrowALogicExceptionIfNoArgument() + { + $process = ProcessBuilder::create() + ->setPrefix('/usr/bin/php') + ->getProcess(); + + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->assertEquals('"/usr/bin/php"', $process->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php'", $process->getCommandLine()); + } + } + + public function testShouldNotThrowALogicExceptionIfNoPrefix() + { + $process = ProcessBuilder::create(array('/usr/bin/php')) + ->getProcess(); + + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + $this->assertEquals('"/usr/bin/php"', $process->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php'", $process->getCommandLine()); + } + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessFailedExceptionTest.php b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessFailedExceptionTest.php new file mode 100644 index 0000000..5da55e7 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessFailedExceptionTest.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\Exception\ProcessFailedException; + +/** + * @author Sebastian Marek + */ +class ProcessFailedExceptionTest extends \PHPUnit_Framework_TestCase +{ + /** + * tests ProcessFailedException throws exception if the process was successful + */ + public function testProcessFailedExceptionThrowsException() + { + $process = $this->getMock( + 'Symfony\Component\Process\Process', + array('isSuccessful'), + array('php') + ); + $process->expects($this->once()) + ->method('isSuccessful') + ->will($this->returnValue(true)); + + $this->setExpectedException( + '\InvalidArgumentException', + 'Expected a failed process, but the given process was successful.' + ); + + new ProcessFailedException($process); + } + + /** + * tests ProcessFailedException uses information from process output + * to generate exception message + */ + public function testProcessFailedExceptionPopulatesInformationFromProcessOutput() + { + $cmd = 'php'; + $exitCode = 1; + $exitText = 'General error'; + $output = "Command output"; + $errorOutput = "FATAL: Unexpected error"; + + $process = $this->getMock( + 'Symfony\Component\Process\Process', + array('isSuccessful', 'getOutput', 'getErrorOutput', 'getExitCode', 'getExitCodeText'), + array($cmd) + ); + $process->expects($this->once()) + ->method('isSuccessful') + ->will($this->returnValue(false)); + $process->expects($this->once()) + ->method('getOutput') + ->will($this->returnValue($output)); + $process->expects($this->once()) + ->method('getErrorOutput') + ->will($this->returnValue($errorOutput)); + $process->expects($this->once()) + ->method('getExitCode') + ->will($this->returnValue($exitCode)); + $process->expects($this->once()) + ->method('getExitCodeText') + ->will($this->returnValue($exitText)); + + $exception = new ProcessFailedException($process); + + $this->assertEquals( + "The command \"$cmd\" failed.\nExit Code: $exitCode($exitText)\n\nOutput:\n================\n{$output}\n\nError Output:\n================\n{$errorOutput}", + $exception->getMessage() + ); + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessInSigchildEnvironment.php b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessInSigchildEnvironment.php new file mode 100644 index 0000000..3977bcd --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessInSigchildEnvironment.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\Process; + +class ProcessInSigchildEnvironment extends Process +{ + protected function isSigchildEnabled() + { + return true; + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessTestHelper.php b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessTestHelper.php new file mode 100644 index 0000000..25cfb41 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessTestHelper.php @@ -0,0 +1,63 @@ + 0) { + $written = fwrite(STDOUT, (binary) $out, 1024); + if (false === $written) { + die(ERR_WRITE_FAILED); + } + $out = (binary) substr($out, $written); + } + if (null === $read && strlen($out) < 1) { + $write = array_diff($write, array(STDOUT)); + } + + if (in_array(STDERR, $w) && strlen($err) > 0) { + $written = fwrite(STDERR, (binary) $err, 1024); + if (false === $written) { + die(ERR_WRITE_FAILED); + } + $err = (binary) substr($err, $written); + } + if (null === $read && strlen($err) < 1) { + $write = array_diff($write, array(STDERR)); + } + + if ($r) { + $str = fread(STDIN, 1024); + if (false !== $str) { + $out .= $str; + $err .= $str; + } + if (false === $str || feof(STDIN)) { + $read = null; + if (!feof(STDIN)) { + die(ERR_READ_FAILED); + } + } + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessUtilsTest.php b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessUtilsTest.php new file mode 100644 index 0000000..603fac5 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/ProcessUtilsTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\ProcessUtils; + +class ProcessUtilsTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider dataArguments + */ + public function testEscapeArgument($result, $argument) + { + $this->assertSame($result, ProcessUtils::escapeArgument($argument)); + } + + public function dataArguments() + { + if (defined('PHP_WINDOWS_VERSION_BUILD')) { + return array( + array('"foo bar"', 'foo bar'), + array('^%"path"^%', '%path%'), + array('"<|>"\\"" "\\""\'f"', '<|>" "\'f'), + array('""', ''), + ); + } + + return array( + array("'foo bar'", 'foo bar'), + array("'%path%'", '%path%'), + array("'<|>\" \"'\\''f'", '<|>" "\'f'), + array("''", ''), + ); + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php b/vendor/symfony/process/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php new file mode 100644 index 0000000..29f3cd9 --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/SigchildDisabledProcessTest.php @@ -0,0 +1,188 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +class SigchildDisabledProcessTest extends AbstractProcessTest +{ + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testGetExitCode() + { + parent::testGetExitCode(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testGetExitCodeIsNullOnStart() + { + parent::testGetExitCodeIsNullOnStart(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testGetExitCodeIsNullOnWhenStartingAgain() + { + parent::testGetExitCodeIsNullOnWhenStartingAgain(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testExitCodeCommandFailed() + { + parent::testExitCodeCommandFailed(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessIsSignaledIfStopped() + { + parent::testProcessIsSignaledIfStopped(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessWithTermSignal() + { + parent::testProcessWithTermSignal(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessIsNotSignaled() + { + parent::testProcessIsNotSignaled(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessWithoutTermSignal() + { + parent::testProcessWithoutTermSignal(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testCheckTimeoutOnStartedProcess() + { + parent::testCheckTimeoutOnStartedProcess(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testGetPid() + { + parent::testGetPid(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testGetPidIsNullBeforeStart() + { + parent::testGetPidIsNullBeforeStart(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testGetPidIsNullAfterRun() + { + parent::testGetPidIsNullAfterRun(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testExitCodeText() + { + $process = $this->getProcess('qdfsmfkqsdfmqmsd'); + $process->run(); + + $process->getExitCodeText(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testIsSuccessful() + { + parent::testIsSuccessful(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testIsSuccessfulOnlyAfterTerminated() + { + parent::testIsSuccessfulOnlyAfterTerminated(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testIsNotSuccessful() + { + parent::testIsNotSuccessful(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testSignal() + { + parent::testSignal(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessWithoutTermSignalIsNotSignaled() + { + parent::testProcessWithoutTermSignalIsNotSignaled(); + } + + public function testStopWithTimeoutIsActuallyWorking() + { + $this->markTestSkipped('Stopping with signal is not supported in sigchild environment'); + } + + public function testProcessThrowsExceptionWhenExternallySignaled() + { + $this->markTestSkipped('Retrieving Pid is not supported in sigchild environment'); + } + + public function testExitCodeIsAvailableAfterSignal() + { + $this->markTestSkipped('Signal is not supported in sigchild environment'); + } + + /** + * {@inheritdoc} + */ + protected function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array()) + { + $process = new ProcessInSigchildEnvironment($commandline, $cwd, $env, $stdin, $timeout, $options); + $process->setEnhanceSigchildCompatibility(false); + + return $process; + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php b/vendor/symfony/process/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php new file mode 100644 index 0000000..296b00d --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/SigchildEnabledProcessTest.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +class SigchildEnabledProcessTest extends AbstractProcessTest +{ + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessIsSignaledIfStopped() + { + parent::testProcessIsSignaledIfStopped(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessWithTermSignal() + { + parent::testProcessWithTermSignal(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessIsNotSignaled() + { + parent::testProcessIsNotSignaled(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessWithoutTermSignal() + { + parent::testProcessWithoutTermSignal(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testGetPid() + { + parent::testGetPid(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testGetPidIsNullBeforeStart() + { + parent::testGetPidIsNullBeforeStart(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testGetPidIsNullAfterRun() + { + parent::testGetPidIsNullAfterRun(); + } + + public function testExitCodeText() + { + $process = $this->getProcess('qdfsmfkqsdfmqmsd'); + $process->run(); + + $this->assertInternalType('string', $process->getExitCodeText()); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testSignal() + { + parent::testSignal(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testProcessWithoutTermSignalIsNotSignaled() + { + parent::testProcessWithoutTermSignalIsNotSignaled(); + } + + public function testProcessThrowsExceptionWhenExternallySignaled() + { + $this->markTestSkipped('Retrieving Pid is not supported in sigchild environment'); + } + + public function testExitCodeIsAvailableAfterSignal() + { + $this->markTestSkipped('Signal is not supported in sigchild environment'); + } + + /** + * {@inheritdoc} + */ + protected function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array()) + { + $process = new ProcessInSigchildEnvironment($commandline, $cwd, $env, $stdin, $timeout, $options); + $process->setEnhanceSigchildCompatibility(true); + + return $process; + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/Tests/SignalListener.php b/vendor/symfony/process/Symfony/Component/Process/Tests/SignalListener.php new file mode 100644 index 0000000..0bf191e --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/Tests/SignalListener.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use Symfony\Component\Process\Process; + +class SimpleProcessTest extends AbstractProcessTest +{ + private $enabledSigchild = false; + + public function setUp() + { + ob_start(); + phpinfo(INFO_GENERAL); + + $this->enabledSigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); + } + + public function testGetExitCode() + { + $this->skipIfPHPSigchild(); + parent::testGetExitCode(); + } + + public function testExitCodeCommandFailed() + { + $this->skipIfPHPSigchild(); + parent::testExitCodeCommandFailed(); + } + + public function testProcessIsSignaledIfStopped() + { + $this->skipIfPHPSigchild(); + parent::testProcessIsSignaledIfStopped(); + } + + public function testProcessWithTermSignal() + { + $this->skipIfPHPSigchild(); + parent::testProcessWithTermSignal(); + } + + public function testProcessIsNotSignaled() + { + $this->skipIfPHPSigchild(); + parent::testProcessIsNotSignaled(); + } + + public function testProcessWithoutTermSignal() + { + $this->skipIfPHPSigchild(); + parent::testProcessWithoutTermSignal(); + } + + public function testExitCodeText() + { + $this->skipIfPHPSigchild(); + parent::testExitCodeText(); + } + + public function testIsSuccessful() + { + $this->skipIfPHPSigchild(); + parent::testIsSuccessful(); + } + + public function testIsNotSuccessful() + { + $this->skipIfPHPSigchild(); + parent::testIsNotSuccessful(); + } + + public function testGetPid() + { + $this->skipIfPHPSigchild(); + parent::testGetPid(); + } + + public function testGetPidIsNullBeforeStart() + { + $this->skipIfPHPSigchild(); + parent::testGetPidIsNullBeforeStart(); + } + + public function testGetPidIsNullAfterRun() + { + $this->skipIfPHPSigchild(); + parent::testGetPidIsNullAfterRun(); + } + + public function testSignal() + { + $this->skipIfPHPSigchild(); + parent::testSignal(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\LogicException + */ + public function testSignalProcessNotRunning() + { + $this->skipIfPHPSigchild(); + parent::testSignalProcessNotRunning(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testSignalWithWrongIntSignal() + { + $this->skipIfPHPSigchild(); + parent::testSignalWithWrongIntSignal(); + } + + /** + * @expectedException Symfony\Component\Process\Exception\RuntimeException + */ + public function testSignalWithWrongNonIntSignal() + { + $this->skipIfPHPSigchild(); + parent::testSignalWithWrongNonIntSignal(); + } + + /** + * {@inheritdoc} + */ + protected function getProcess($commandline, $cwd = null, array $env = null, $stdin = null, $timeout = 60, array $options = array()) + { + return new Process($commandline, $cwd, $env, $stdin, $timeout, $options); + } + + private function skipIfPHPSigchild() + { + if ($this->enabledSigchild) { + $this->markTestSkipped('Your PHP has been compiled with --enable-sigchild, this test can not be executed'); + } + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/composer.json b/vendor/symfony/process/Symfony/Component/Process/composer.json new file mode 100644 index 0000000..427e63b --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/composer.json @@ -0,0 +1,31 @@ +{ + "name": "symfony/process", + "type": "library", + "description": "Symfony Process Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\Process\\": "" } + }, + "target-dir": "Symfony/Component/Process", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + } +} diff --git a/vendor/symfony/process/Symfony/Component/Process/phpunit.xml.dist b/vendor/symfony/process/Symfony/Component/Process/phpunit.xml.dist new file mode 100644 index 0000000..9d5830f --- /dev/null +++ b/vendor/symfony/process/Symfony/Component/Process/phpunit.xml.dist @@ -0,0 +1,28 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + + + + diff --git a/vendor/symfony/routing/Symfony/Component/Routing/.gitignore b/vendor/symfony/routing/Symfony/Component/Routing/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Annotation/Route.php b/vendor/symfony/routing/Symfony/Component/Routing/Annotation/Route.php new file mode 100644 index 0000000..abdbea2 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Annotation/Route.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Annotation; + +/** + * Annotation class for @Route(). + * + * @Annotation + * + * @author Fabien Potencier + */ +class Route +{ + private $path; + private $name; + private $requirements; + private $options; + private $defaults; + private $host; + private $methods; + private $schemes; + + /** + * Constructor. + * + * @param array $data An array of key/value parameters. + * + * @throws \BadMethodCallException + */ + public function __construct(array $data) + { + $this->requirements = array(); + $this->options = array(); + $this->defaults = array(); + $this->methods = array(); + $this->schemes = array(); + + if (isset($data['value'])) { + $data['path'] = $data['value']; + unset($data['value']); + } + + foreach ($data as $key => $value) { + $method = 'set'.str_replace('_', '', $key); + if (!method_exists($this, $method)) { + throw new \BadMethodCallException(sprintf("Unknown property '%s' on annotation '%s'.", $key, get_class($this))); + } + $this->$method($value); + } + } + + /** + * @deprecated Deprecated in 2.2, to be removed in 3.0. Use setPath instead. + */ + public function setPattern($pattern) + { + $this->path = $pattern; + } + + /** + * @deprecated Deprecated in 2.2, to be removed in 3.0. Use getPath instead. + */ + public function getPattern() + { + return $this->path; + } + + public function setPath($path) + { + $this->path = $path; + } + + public function getPath() + { + return $this->path; + } + + public function setHost($pattern) + { + $this->host = $pattern; + } + + public function getHost() + { + return $this->host; + } + + public function setName($name) + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } + + public function setRequirements($requirements) + { + $this->requirements = $requirements; + } + + public function getRequirements() + { + return $this->requirements; + } + + public function setOptions($options) + { + $this->options = $options; + } + + public function getOptions() + { + return $this->options; + } + + public function setDefaults($defaults) + { + $this->defaults = $defaults; + } + + public function getDefaults() + { + return $this->defaults; + } + + public function setSchemes($schemes) + { + $this->schemes = is_array($schemes) ? $schemes : array($schemes); + } + + public function getSchemes() + { + return $this->schemes; + } + + public function setMethods($methods) + { + $this->methods = is_array($methods) ? $methods : array($methods); + } + + public function getMethods() + { + return $this->methods; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/CHANGELOG.md b/vendor/symfony/routing/Symfony/Component/Routing/CHANGELOG.md new file mode 100644 index 0000000..f0c616d --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/CHANGELOG.md @@ -0,0 +1,162 @@ +CHANGELOG +========= + +2.3.0 +----- + + * added RequestContext::getQueryString() + +2.2.0 +----- + + * [DEPRECATION] Several route settings have been renamed (the old ones will be removed in 3.0): + + * The `pattern` setting for a route has been deprecated in favor of `path` + * The `_scheme` and `_method` requirements have been moved to the `schemes` and `methods` settings + + Before: + + ``` + article_edit: + pattern: /article/{id} + requirements: { '_method': 'POST|PUT', '_scheme': 'https', 'id': '\d+' } + + + POST|PUT + https + \d+ + + + $route = new Route(); + $route->setPattern('/article/{id}'); + $route->setRequirement('_method', 'POST|PUT'); + $route->setRequirement('_scheme', 'https'); + ``` + + After: + + ``` + article_edit: + path: /article/{id} + methods: [POST, PUT] + schemes: https + requirements: { 'id': '\d+' } + + + \d+ + + + $route = new Route(); + $route->setPath('/article/{id}'); + $route->setMethods(array('POST', 'PUT')); + $route->setSchemes('https'); + ``` + + * [BC BREAK] RouteCollection does not behave like a tree structure anymore but as + a flat array of Routes. So when using PHP to build the RouteCollection, you must + make sure to add routes to the sub-collection before adding it to the parent + collection (this is not relevant when using YAML or XML for Route definitions). + + Before: + + ``` + $rootCollection = new RouteCollection(); + $subCollection = new RouteCollection(); + $rootCollection->addCollection($subCollection); + $subCollection->add('foo', new Route('/foo')); + ``` + + After: + + ``` + $rootCollection = new RouteCollection(); + $subCollection = new RouteCollection(); + $subCollection->add('foo', new Route('/foo')); + $rootCollection->addCollection($subCollection); + ``` + + Also one must call `addCollection` from the bottom to the top hierarchy. + So the correct sequence is the following (and not the reverse): + + ``` + $childCollection->->addCollection($grandchildCollection); + $rootCollection->addCollection($childCollection); + ``` + + * [DEPRECATION] The methods `RouteCollection::getParent()` and `RouteCollection::getRoot()` + have been deprecated and will be removed in Symfony 2.3. + * [BC BREAK] Misusing the `RouteCollection::addPrefix` method to add defaults, requirements + or options without adding a prefix is not supported anymore. So if you called `addPrefix` + with an empty prefix or `/` only (both have no relevance), like + `addPrefix('', $defaultsArray, $requirementsArray, $optionsArray)` + you need to use the new dedicated methods `addDefaults($defaultsArray)`, + `addRequirements($requirementsArray)` or `addOptions($optionsArray)` instead. + * [DEPRECATION] The `$options` parameter to `RouteCollection::addPrefix()` has been deprecated + because adding options has nothing to do with adding a path prefix. If you want to add options + to all child routes of a RouteCollection, you can use `addOptions()`. + * [DEPRECATION] The method `RouteCollection::getPrefix()` has been deprecated + because it suggested that all routes in the collection would have this prefix, which is + not necessarily true. On top of that, since there is no tree structure anymore, this method + is also useless. Don't worry about performance, prefix optimization for matching is still done + in the dumper, which was also improved in 2.2.0 to find even more grouping possibilities. + * [DEPRECATION] `RouteCollection::addCollection(RouteCollection $collection)` should now only be + used with a single parameter. The other params `$prefix`, `$default`, `$requirements` and `$options` + will still work, but have been deprecated. The `addPrefix` method should be used for this + use-case instead. + Before: `$parentCollection->addCollection($collection, '/prefix', array(...), array(...))` + After: + ``` + $collection->addPrefix('/prefix', array(...), array(...)); + $parentCollection->addCollection($collection); + ``` + * added support for the method default argument values when defining a @Route + * Adjacent placeholders without separator work now, e.g. `/{x}{y}{z}.{_format}`. + * Characters that function as separator between placeholders are now whitelisted + to fix routes with normal text around a variable, e.g. `/prefix{var}suffix`. + * [BC BREAK] The default requirement of a variable has been changed slightly. + Previously it disallowed the previous and the next char around a variable. Now + it disallows the slash (`/`) and the next char. Using the previous char added + no value and was problematic because the route `/index.{_format}` would be + matched by `/index.ht/ml`. + * The default requirement now uses possessive quantifiers when possible which + improves matching performance by up to 20% because it prevents backtracking + when it's not needed. + * The ConfigurableRequirementsInterface can now also be used to disable the requirements + check on URL generation completely by calling `setStrictRequirements(null)`. It + improves performance in production environment as you should know that params always + pass the requirements (otherwise it would break your link anyway). + * There is no restriction on the route name anymore. So non-alphanumeric characters + are now also allowed. + * [BC BREAK] `RouteCompilerInterface::compile(Route $route)` was made static + (only relevant if you implemented your own RouteCompiler). + * Added possibility to generate relative paths and network paths in the UrlGenerator, e.g. + "../parent-file" and "//example.com/dir/file". The third parameter in + `UrlGeneratorInterface::generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH)` + now accepts more values and you should use the constants defined in `UrlGeneratorInterface` for + claritiy. The old method calls with a Boolean parameter will continue to work because they + equal the signature using the constants. + +2.1.0 +----- + + * added RequestMatcherInterface + * added RequestContext::fromRequest() + * the UrlMatcher does not throw a \LogicException anymore when the required + scheme is not the current one + * added TraceableUrlMatcher + * added the possibility to define options, default values and requirements + for placeholders in prefix, including imported routes + * added RouterInterface::getRouteCollection + * [BC BREAK] the UrlMatcher urldecodes the route parameters only once, they + were decoded twice before. Note that the `urldecode()` calls have been + changed for a single `rawurldecode()` in order to support `+` for input + paths. + * added RouteCollection::getRoot method to retrieve the root of a + RouteCollection tree + * [BC BREAK] made RouteCollection::setParent private which could not have + been used anyway without creating inconsistencies + * [BC BREAK] RouteCollection::remove also removes a route from parent + collections (not only from its children) + * added ConfigurableRequirementsInterface that allows to disable exceptions + (and generate empty URLs instead) when generating a route with an invalid + parameter value diff --git a/vendor/symfony/routing/Symfony/Component/Routing/CompiledRoute.php b/vendor/symfony/routing/Symfony/Component/Routing/CompiledRoute.php new file mode 100644 index 0000000..7878455 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/CompiledRoute.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * CompiledRoutes are returned by the RouteCompiler class. + * + * @author Fabien Potencier + */ +class CompiledRoute +{ + private $variables; + private $tokens; + private $staticPrefix; + private $regex; + private $pathVariables; + private $hostVariables; + private $hostRegex; + private $hostTokens; + + /** + * Constructor. + * + * @param string $staticPrefix The static prefix of the compiled route + * @param string $regex The regular expression to use to match this route + * @param array $tokens An array of tokens to use to generate URL for this route + * @param array $pathVariables An array of path variables + * @param string|null $hostRegex Host regex + * @param array $hostTokens Host tokens + * @param array $hostVariables An array of host variables + * @param array $variables An array of variables (variables defined in the path and in the host patterns) + */ + public function __construct($staticPrefix, $regex, array $tokens, array $pathVariables, $hostRegex = null, array $hostTokens = array(), array $hostVariables = array(), array $variables = array()) + { + $this->staticPrefix = (string) $staticPrefix; + $this->regex = $regex; + $this->tokens = $tokens; + $this->pathVariables = $pathVariables; + $this->hostRegex = $hostRegex; + $this->hostTokens = $hostTokens; + $this->hostVariables = $hostVariables; + $this->variables = $variables; + } + + /** + * Returns the static prefix. + * + * @return string The static prefix + */ + public function getStaticPrefix() + { + return $this->staticPrefix; + } + + /** + * Returns the regex. + * + * @return string The regex + */ + public function getRegex() + { + return $this->regex; + } + + /** + * Returns the host regex + * + * @return string|null The host regex or null + */ + public function getHostRegex() + { + return $this->hostRegex; + } + + /** + * Returns the tokens. + * + * @return array The tokens + */ + public function getTokens() + { + return $this->tokens; + } + + /** + * Returns the host tokens. + * + * @return array The tokens + */ + public function getHostTokens() + { + return $this->hostTokens; + } + + /** + * Returns the variables. + * + * @return array The variables + */ + public function getVariables() + { + return $this->variables; + } + + /** + * Returns the path variables. + * + * @return array The variables + */ + public function getPathVariables() + { + return $this->pathVariables; + } + + /** + * Returns the host variables. + * + * @return array The variables + */ + public function getHostVariables() + { + return $this->hostVariables; + } + +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Exception/ExceptionInterface.php b/vendor/symfony/routing/Symfony/Component/Routing/Exception/ExceptionInterface.php new file mode 100644 index 0000000..5289f52 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Exception/ExceptionInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * ExceptionInterface + * + * @author Alexandre Salomé + * + * @api + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Exception/InvalidParameterException.php b/vendor/symfony/routing/Symfony/Component/Routing/Exception/InvalidParameterException.php new file mode 100644 index 0000000..4f12469 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Exception/InvalidParameterException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when a parameter is not valid + * + * @author Alexandre Salomé + * + * @api + */ +class InvalidParameterException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Exception/MethodNotAllowedException.php b/vendor/symfony/routing/Symfony/Component/Routing/Exception/MethodNotAllowedException.php new file mode 100644 index 0000000..32f1091 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Exception/MethodNotAllowedException.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * The resource was found but the request method is not allowed. + * + * This exception should trigger an HTTP 405 response in your application code. + * + * @author Kris Wallsmith + * + * @api + */ +class MethodNotAllowedException extends \RuntimeException implements ExceptionInterface +{ + /** + * @var array + */ + protected $allowedMethods = array(); + + public function __construct(array $allowedMethods, $message = null, $code = 0, \Exception $previous = null) + { + $this->allowedMethods = array_map('strtoupper', $allowedMethods); + + parent::__construct($message, $code, $previous); + } + + /** + * Gets the allowed HTTP methods. + * + * @return array + */ + public function getAllowedMethods() + { + return $this->allowedMethods; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Exception/MissingMandatoryParametersException.php b/vendor/symfony/routing/Symfony/Component/Routing/Exception/MissingMandatoryParametersException.php new file mode 100644 index 0000000..5a523fa --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Exception/MissingMandatoryParametersException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when a route cannot be generated because of missing + * mandatory parameters. + * + * @author Alexandre Salomé + * + * @api + */ +class MissingMandatoryParametersException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Exception/ResourceNotFoundException.php b/vendor/symfony/routing/Symfony/Component/Routing/Exception/ResourceNotFoundException.php new file mode 100644 index 0000000..362a0d6 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Exception/ResourceNotFoundException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * The resource was not found. + * + * This exception should trigger an HTTP 404 response in your application code. + * + * @author Kris Wallsmith + * + * @api + */ +class ResourceNotFoundException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Exception/RouteNotFoundException.php b/vendor/symfony/routing/Symfony/Component/Routing/Exception/RouteNotFoundException.php new file mode 100644 index 0000000..fd275be --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Exception/RouteNotFoundException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when a route does not exist + * + * @author Alexandre Salomé + * + * @api + */ +class RouteNotFoundException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Generator/ConfigurableRequirementsInterface.php b/vendor/symfony/routing/Symfony/Component/Routing/Generator/ConfigurableRequirementsInterface.php new file mode 100644 index 0000000..5925838 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Generator/ConfigurableRequirementsInterface.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +/** + * ConfigurableRequirementsInterface must be implemented by URL generators that + * can be configured whether an exception should be generated when the parameters + * do not match the requirements. It is also possible to disable the requirements + * check for URL generation completely. + * + * The possible configurations and use-cases: + * - setStrictRequirements(true): Throw an exception for mismatching requirements. This + * is mostly useful in development environment. + * - setStrictRequirements(false): Don't throw an exception but return null as URL for + * mismatching requirements and log the problem. Useful when you cannot control all + * params because they come from third party libs but don't want to have a 404 in + * production environment. It should log the mismatch so one can review it. + * - setStrictRequirements(null): Return the URL with the given parameters without + * checking the requirements at all. When generating an URL you should either trust + * your params or you validated them beforehand because otherwise it would break your + * link anyway. So in production environment you should know that params always pass + * the requirements. Thus this option allows to disable the check on URL generation for + * performance reasons (saving a preg_match for each requirement every time a URL is + * generated). + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +interface ConfigurableRequirementsInterface +{ + /** + * Enables or disables the exception on incorrect parameters. + * Passing null will deactivate the requirements check completely. + * + * @param Boolean|null $enabled + */ + public function setStrictRequirements($enabled); + + /** + * Returns whether to throw an exception on incorrect parameters. + * Null means the requirements check is deactivated completely. + * + * @return Boolean|null + */ + public function isStrictRequirements(); +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Generator/Dumper/GeneratorDumper.php b/vendor/symfony/routing/Symfony/Component/Routing/Generator/Dumper/GeneratorDumper.php new file mode 100644 index 0000000..4739bd8 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Generator/Dumper/GeneratorDumper.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * GeneratorDumper is the base class for all built-in generator dumpers. + * + * @author Fabien Potencier + */ +abstract class GeneratorDumper implements GeneratorDumperInterface +{ + /** + * @var RouteCollection + */ + private $routes; + + /** + * Constructor. + * + * @param RouteCollection $routes The RouteCollection to dump + */ + public function __construct(RouteCollection $routes) + { + $this->routes = $routes; + } + + /** + * {@inheritdoc} + */ + public function getRoutes() + { + return $this->routes; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Generator/Dumper/GeneratorDumperInterface.php b/vendor/symfony/routing/Symfony/Component/Routing/Generator/Dumper/GeneratorDumperInterface.php new file mode 100644 index 0000000..deb0c0a --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Generator/Dumper/GeneratorDumperInterface.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * GeneratorDumperInterface is the interface that all generator dumper classes must implement. + * + * @author Fabien Potencier + * + * @api + */ +interface GeneratorDumperInterface +{ + /** + * Dumps a set of routes to a string representation of executable code + * that can then be used to generate a URL of such a route. + * + * @param array $options An array of options + * + * @return string Executable code + */ + public function dump(array $options = array()); + + /** + * Gets the routes to dump. + * + * @return RouteCollection A RouteCollection instance + */ + public function getRoutes(); +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php b/vendor/symfony/routing/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php new file mode 100644 index 0000000..42cd910 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Generator/Dumper/PhpGeneratorDumper.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\Dumper; + +/** + * PhpGeneratorDumper creates a PHP class able to generate URLs for a given set of routes. + * + * @author Fabien Potencier + * @author Tobias Schultze + * + * @api + */ +class PhpGeneratorDumper extends GeneratorDumper +{ + /** + * Dumps a set of routes to a PHP class. + * + * Available options: + * + * * class: The class name + * * base_class: The base class name + * + * @param array $options An array of options + * + * @return string A PHP class representing the generator class + * + * @api + */ + public function dump(array $options = array()) + { + $options = array_merge(array( + 'class' => 'ProjectUrlGenerator', + 'base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', + ), $options); + + return <<generateDeclaredRoutes()}; + + /** + * Constructor. + */ + public function __construct(RequestContext \$context, LoggerInterface \$logger = null) + { + \$this->context = \$context; + \$this->logger = \$logger; + } + +{$this->generateGenerateMethod()} +} + +EOF; + } + + /** + * Generates PHP code representing an array of defined routes + * together with the routes properties (e.g. requirements). + * + * @return string PHP code + */ + private function generateDeclaredRoutes() + { + $routes = "array(\n"; + foreach ($this->getRoutes()->all() as $name => $route) { + $compiledRoute = $route->compile(); + + $properties = array(); + $properties[] = $compiledRoute->getVariables(); + $properties[] = $route->getDefaults(); + $properties[] = $route->getRequirements(); + $properties[] = $compiledRoute->getTokens(); + $properties[] = $compiledRoute->getHostTokens(); + + $routes .= sprintf(" '%s' => %s,\n", $name, str_replace("\n", '', var_export($properties, true))); + } + $routes .= ' )'; + + return $routes; + } + + /** + * Generates PHP code representing the `generate` method that implements the UrlGeneratorInterface. + * + * @return string PHP code + */ + private function generateGenerateMethod() + { + return <<doGenerate(\$variables, \$defaults, \$requirements, \$tokens, \$parameters, \$name, \$referenceType, \$hostTokens); + } +EOF; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Generator/UrlGenerator.php b/vendor/symfony/routing/Symfony/Component/Routing/Generator/UrlGenerator.php new file mode 100644 index 0000000..f224cb3 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Generator/UrlGenerator.php @@ -0,0 +1,322 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Exception\InvalidParameterException; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\Exception\MissingMandatoryParametersException; +use Psr\Log\LoggerInterface; + +/** + * UrlGenerator can generate a URL or a path for any route in the RouteCollection + * based on the passed parameters. + * + * @author Fabien Potencier + * @author Tobias Schultze + * + * @api + */ +class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInterface +{ + /** + * @var RouteCollection + */ + protected $routes; + + /** + * @var RequestContext + */ + protected $context; + + /** + * @var Boolean|null + */ + protected $strictRequirements = true; + + /** + * @var LoggerInterface|null + */ + protected $logger; + + /** + * This array defines the characters (besides alphanumeric ones) that will not be percent-encoded in the path segment of the generated URL. + * + * PHP's rawurlencode() encodes all chars except "a-zA-Z0-9-._~" according to RFC 3986. But we want to allow some chars + * to be used in their literal form (reasons below). Other chars inside the path must of course be encoded, e.g. + * "?" and "#" (would be interpreted wrongly as query and fragment identifier), + * "'" and """ (are used as delimiters in HTML). + */ + protected $decodedChars = array( + // the slash can be used to designate a hierarchical structure and we want allow using it with this meaning + // some webservers don't allow the slash in encoded form in the path for security reasons anyway + // see http://stackoverflow.com/questions/4069002/http-400-if-2f-part-of-get-url-in-jboss + '%2F' => '/', + // the following chars are general delimiters in the URI specification but have only special meaning in the authority component + // so they can safely be used in the path in unencoded form + '%40' => '@', + '%3A' => ':', + // these chars are only sub-delimiters that have no predefined meaning and can therefore be used literally + // so URI producing applications can use these chars to delimit subcomponents in a path segment without being encoded for better readability + '%3B' => ';', + '%2C' => ',', + '%3D' => '=', + '%2B' => '+', + '%21' => '!', + '%2A' => '*', + '%7C' => '|', + ); + + /** + * Constructor. + * + * @param RouteCollection $routes A RouteCollection instance + * @param RequestContext $context The context + * @param LoggerInterface|null $logger A logger instance + * + * @api + */ + public function __construct(RouteCollection $routes, RequestContext $context, LoggerInterface $logger = null) + { + $this->routes = $routes; + $this->context = $context; + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function setContext(RequestContext $context) + { + $this->context = $context; + } + + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->context; + } + + /** + * {@inheritdoc} + */ + public function setStrictRequirements($enabled) + { + $this->strictRequirements = null === $enabled ? null : (Boolean) $enabled; + } + + /** + * {@inheritdoc} + */ + public function isStrictRequirements() + { + return $this->strictRequirements; + } + + /** + * {@inheritDoc} + */ + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) + { + if (null === $route = $this->routes->get($name)) { + throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); + } + + // the Route has a cache of its own and is not recompiled as long as it does not get modified + $compiledRoute = $route->compile(); + + return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostTokens()); + } + + /** + * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route + * @throws InvalidParameterException When a parameter value for a placeholder is not correct because + * it does not match the requirement + */ + protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens) + { + $variables = array_flip($variables); + $mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters); + + // all params must be given + if ($diff = array_diff_key($variables, $mergedParams)) { + throw new MissingMandatoryParametersException(sprintf('Some mandatory parameters are missing ("%s") to generate a URL for route "%s".', implode('", "', array_keys($diff)), $name)); + } + + $url = ''; + $optional = true; + foreach ($tokens as $token) { + if ('variable' === $token[0]) { + if (!$optional || !array_key_exists($token[3], $defaults) || null !== $mergedParams[$token[3]] && (string) $mergedParams[$token[3]] !== (string) $defaults[$token[3]]) { + // check requirement + if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#', $mergedParams[$token[3]])) { + $message = sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given) to generate a corresponding URL.', $token[3], $name, $token[2], $mergedParams[$token[3]]); + if ($this->strictRequirements) { + throw new InvalidParameterException($message); + } + + if ($this->logger) { + $this->logger->error($message); + } + + return null; + } + + $url = $token[1].$mergedParams[$token[3]].$url; + $optional = false; + } + } else { + // static text + $url = $token[1].$url; + $optional = false; + } + } + + if ('' === $url) { + $url = '/'; + } + + // the contexts base url is already encoded (see Symfony\Component\HttpFoundation\Request) + $url = strtr(rawurlencode($url), $this->decodedChars); + + // the path segments "." and ".." are interpreted as relative reference when resolving a URI; see http://tools.ietf.org/html/rfc3986#section-3.3 + // so we need to encode them as they are not used for this purpose here + // otherwise we would generate a URI that, when followed by a user agent (e.g. browser), does not match this route + $url = strtr($url, array('/../' => '/%2E%2E/', '/./' => '/%2E/')); + if ('/..' === substr($url, -3)) { + $url = substr($url, 0, -2).'%2E%2E'; + } elseif ('/.' === substr($url, -2)) { + $url = substr($url, 0, -1).'%2E'; + } + + $schemeAuthority = ''; + if ($host = $this->context->getHost()) { + $scheme = $this->context->getScheme(); + if (isset($requirements['_scheme']) && ($req = strtolower($requirements['_scheme'])) && $scheme !== $req) { + $referenceType = self::ABSOLUTE_URL; + $scheme = $req; + } + + if ($hostTokens) { + $routeHost = ''; + foreach ($hostTokens as $token) { + if ('variable' === $token[0]) { + if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#', $mergedParams[$token[3]])) { + $message = sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given) to generate a corresponding URL.', $token[3], $name, $token[2], $mergedParams[$token[3]]); + + if ($this->strictRequirements) { + throw new InvalidParameterException($message); + } + + if ($this->logger) { + $this->logger->error($message); + } + + return null; + } + + $routeHost = $token[1].$mergedParams[$token[3]].$routeHost; + } else { + $routeHost = $token[1].$routeHost; + } + } + + if ($routeHost !== $host) { + $host = $routeHost; + if (self::ABSOLUTE_URL !== $referenceType) { + $referenceType = self::NETWORK_PATH; + } + } + } + + if (self::ABSOLUTE_URL === $referenceType || self::NETWORK_PATH === $referenceType) { + $port = ''; + if ('http' === $scheme && 80 != $this->context->getHttpPort()) { + $port = ':'.$this->context->getHttpPort(); + } elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) { + $port = ':'.$this->context->getHttpsPort(); + } + + $schemeAuthority = self::NETWORK_PATH === $referenceType ? '//' : "$scheme://"; + $schemeAuthority .= $host.$port; + } + } + + if (self::RELATIVE_PATH === $referenceType) { + $url = self::getRelativePath($this->context->getPathInfo(), $url); + } else { + $url = $schemeAuthority.$this->context->getBaseUrl().$url; + } + + // add a query string if needed + $extra = array_diff_key($parameters, $variables, $defaults); + if ($extra && $query = http_build_query($extra, '', '&')) { + $url .= '?'.$query; + } + + return $url; + } + + /** + * Returns the target path as relative reference from the base path. + * + * Only the URIs path component (no schema, host etc.) is relevant and must be given, starting with a slash. + * Both paths must be absolute and not contain relative parts. + * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives. + * Furthermore, they can be used to reduce the link size in documents. + * + * Example target paths, given a base path of "/a/b/c/d": + * - "/a/b/c/d" -> "" + * - "/a/b/c/" -> "./" + * - "/a/b/" -> "../" + * - "/a/b/c/other" -> "other" + * - "/a/x/y" -> "../../x/y" + * + * @param string $basePath The base path + * @param string $targetPath The target path + * + * @return string The relative target path + */ + public static function getRelativePath($basePath, $targetPath) + { + if ($basePath === $targetPath) { + return ''; + } + + $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath); + $targetDirs = explode('/', isset($targetPath[0]) && '/' === $targetPath[0] ? substr($targetPath, 1) : $targetPath); + array_pop($sourceDirs); + $targetFile = array_pop($targetDirs); + + foreach ($sourceDirs as $i => $dir) { + if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) { + unset($sourceDirs[$i], $targetDirs[$i]); + } else { + break; + } + } + + $targetDirs[] = $targetFile; + $path = str_repeat('../', count($sourceDirs)).implode('/', $targetDirs); + + // A reference to the same base directory or an empty subdirectory must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name + // (see http://tools.ietf.org/html/rfc3986#section-4.2). + return '' === $path || '/' === $path[0] + || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) + ? "./$path" : $path; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Generator/UrlGeneratorInterface.php b/vendor/symfony/routing/Symfony/Component/Routing/Generator/UrlGeneratorInterface.php new file mode 100644 index 0000000..8e3b277 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Generator/UrlGeneratorInterface.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +use Symfony\Component\Routing\Exception\InvalidParameterException; +use Symfony\Component\Routing\Exception\MissingMandatoryParametersException; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\RequestContextAwareInterface; + +/** + * UrlGeneratorInterface is the interface that all URL generator classes must implement. + * + * The constants in this interface define the different types of resource references that + * are declared in RFC 3986: http://tools.ietf.org/html/rfc3986 + * We are using the term "URL" instead of "URI" as this is more common in web applications + * and we do not need to distinguish them as the difference is mostly semantical and + * less technical. Generating URIs, i.e. representation-independent resource identifiers, + * is also possible. + * + * @author Fabien Potencier + * @author Tobias Schultze + * + * @api + */ +interface UrlGeneratorInterface extends RequestContextAwareInterface +{ + /** + * Generates an absolute URL, e.g. "http://example.com/dir/file". + */ + const ABSOLUTE_URL = true; + + /** + * Generates an absolute path, e.g. "/dir/file". + */ + const ABSOLUTE_PATH = false; + + /** + * Generates a relative path based on the current request path, e.g. "../parent-file". + * @see UrlGenerator::getRelativePath() + */ + const RELATIVE_PATH = 'relative'; + + /** + * Generates a network path, e.g. "//example.com/dir/file". + * Such reference reuses the current scheme but specifies the host. + */ + const NETWORK_PATH = 'network'; + + /** + * Generates a URL or path for a specific route based on the given parameters. + * + * Parameters that reference placeholders in the route pattern will substitute them in the + * path or host. Extra params are added as query string to the URL. + * + * When the passed reference type cannot be generated for the route because it requires a different + * host or scheme than the current one, the method will return a more comprehensive reference + * that includes the required params. For example, when you call this method with $referenceType = ABSOLUTE_PATH + * but the route requires the https scheme whereas the current scheme is http, it will instead return an + * ABSOLUTE_URL with the https scheme and the current host. This makes sure the generated URL matches + * the route in any case. + * + * If there is no route with the given name, the generator must throw the RouteNotFoundException. + * + * @param string $name The name of the route + * @param mixed $parameters An array of parameters + * @param Boolean|string $referenceType The type of reference to be generated (one of the constants) + * + * @return string The generated URL + * + * @throws RouteNotFoundException If the named route doesn't exist + * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route + * @throws InvalidParameterException When a parameter value for a placeholder is not correct because + * it does not match the requirement + * + * @api + */ + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH); +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/LICENSE b/vendor/symfony/routing/Symfony/Component/Routing/LICENSE new file mode 100644 index 0000000..88a57f8 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2013 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Loader/AnnotationClassLoader.php b/vendor/symfony/routing/Symfony/Component/Routing/Loader/AnnotationClassLoader.php new file mode 100644 index 0000000..7abab5a --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Loader/AnnotationClassLoader.php @@ -0,0 +1,246 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Doctrine\Common\Annotations\Reader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\Loader\LoaderResolverInterface; + +/** + * AnnotationClassLoader loads routing information from a PHP class and its methods. + * + * You need to define an implementation for the getRouteDefaults() method. Most of the + * time, this method should define some PHP callable to be called for the route + * (a controller in MVC speak). + * + * The @Route annotation can be set on the class (for global parameters), + * and on each method. + * + * The @Route annotation main value is the route path. The annotation also + * recognizes several parameters: requirements, options, defaults, schemes, + * methods, host, and name. The name parameter is mandatory. + * Here is an example of how you should be able to use it: + * + * /** + * * @Route("/Blog") + * * / + * class Blog + * { + * /** + * * @Route("/", name="blog_index") + * * / + * public function index() + * { + * } + * + * /** + * * @Route("/{id}", name="blog_post", requirements = {"id" = "\d+"}) + * * / + * public function show() + * { + * } + * } + * + * @author Fabien Potencier + */ +abstract class AnnotationClassLoader implements LoaderInterface +{ + /** + * @var Reader + */ + protected $reader; + + /** + * @var string + */ + protected $routeAnnotationClass = 'Symfony\\Component\\Routing\\Annotation\\Route'; + + /** + * @var integer + */ + protected $defaultRouteIndex = 0; + + /** + * Constructor. + * + * @param Reader $reader + */ + public function __construct(Reader $reader) + { + $this->reader = $reader; + } + + /** + * Sets the annotation class to read route properties from. + * + * @param string $class A fully-qualified class name + */ + public function setRouteAnnotationClass($class) + { + $this->routeAnnotationClass = $class; + } + + /** + * Loads from annotations from a class. + * + * @param string $class A class name + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When route can't be parsed + */ + public function load($class, $type = null) + { + if (!class_exists($class)) { + throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); + } + + $globals = array( + 'path' => '', + 'requirements' => array(), + 'options' => array(), + 'defaults' => array(), + 'schemes' => array(), + 'methods' => array(), + 'host' => '', + ); + + $class = new \ReflectionClass($class); + if ($class->isAbstract()) { + throw new \InvalidArgumentException(sprintf('Annotations from class "%s" cannot be read as it is abstract.', $class)); + } + + if ($annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) { + // for BC reasons + if (null !== $annot->getPath()) { + $globals['path'] = $annot->getPath(); + } elseif (null !== $annot->getPattern()) { + $globals['path'] = $annot->getPattern(); + } + + if (null !== $annot->getRequirements()) { + $globals['requirements'] = $annot->getRequirements(); + } + + if (null !== $annot->getOptions()) { + $globals['options'] = $annot->getOptions(); + } + + if (null !== $annot->getDefaults()) { + $globals['defaults'] = $annot->getDefaults(); + } + + if (null !== $annot->getSchemes()) { + $globals['schemes'] = $annot->getSchemes(); + } + + if (null !== $annot->getMethods()) { + $globals['methods'] = $annot->getMethods(); + } + + if (null !== $annot->getHost()) { + $globals['host'] = $annot->getHost(); + } + } + + $collection = new RouteCollection(); + $collection->addResource(new FileResource($class->getFileName())); + + foreach ($class->getMethods() as $method) { + $this->defaultRouteIndex = 0; + foreach ($this->reader->getMethodAnnotations($method) as $annot) { + if ($annot instanceof $this->routeAnnotationClass) { + $this->addRoute($collection, $annot, $globals, $class, $method); + } + } + } + + return $collection; + } + + protected function addRoute(RouteCollection $collection, $annot, $globals, \ReflectionClass $class, \ReflectionMethod $method) + { + $name = $annot->getName(); + if (null === $name) { + $name = $this->getDefaultRouteName($class, $method); + } + + $defaults = array_replace($globals['defaults'], $annot->getDefaults()); + foreach ($method->getParameters() as $param) { + if (!isset($defaults[$param->getName()]) && $param->isOptional()) { + $defaults[$param->getName()] = $param->getDefaultValue(); + } + } + $requirements = array_replace($globals['requirements'], $annot->getRequirements()); + $options = array_replace($globals['options'], $annot->getOptions()); + $schemes = array_replace($globals['schemes'], $annot->getSchemes()); + $methods = array_replace($globals['methods'], $annot->getMethods()); + + $host = $annot->getHost(); + if (null === $host) { + $host = $globals['host']; + } + + $route = new Route($globals['path'].$annot->getPath(), $defaults, $requirements, $options, $host, $schemes, $methods); + + $this->configureRoute($route, $class, $method, $annot); + + $collection->add($name, $route); + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return is_string($resource) && preg_match('/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/', $resource) && (!$type || 'annotation' === $type); + } + + /** + * {@inheritdoc} + */ + public function setResolver(LoaderResolverInterface $resolver) + { + } + + /** + * {@inheritdoc} + */ + public function getResolver() + { + } + + /** + * Gets the default route name for a class method. + * + * @param \ReflectionClass $class + * @param \ReflectionMethod $method + * + * @return string + */ + protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method) + { + $name = strtolower(str_replace('\\', '_', $class->name).'_'.$method->name); + if ($this->defaultRouteIndex > 0) { + $name .= '_'.$this->defaultRouteIndex; + } + $this->defaultRouteIndex++; + + return $name; + } + + abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot); +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Loader/AnnotationDirectoryLoader.php b/vendor/symfony/routing/Symfony/Component/Routing/Loader/AnnotationDirectoryLoader.php new file mode 100644 index 0000000..abd68ed --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Loader/AnnotationDirectoryLoader.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Config\Resource\DirectoryResource; + +/** + * AnnotationDirectoryLoader loads routing information from annotations set + * on PHP classes and methods. + * + * @author Fabien Potencier + */ +class AnnotationDirectoryLoader extends AnnotationFileLoader +{ + /** + * Loads from annotations from a directory. + * + * @param string $path A directory path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When the directory does not exist or its routes cannot be parsed + */ + public function load($path, $type = null) + { + $dir = $this->locator->locate($path); + + $collection = new RouteCollection(); + $collection->addResource(new DirectoryResource($dir, '/\.php$/')); + $files = iterator_to_array(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir), \RecursiveIteratorIterator::LEAVES_ONLY)); + usort($files, function (\SplFileInfo $a, \SplFileInfo $b) { + return (string) $a > (string) $b ? 1 : -1; + }); + + foreach ($files as $file) { + if (!$file->isFile() || '.php' !== substr($file->getFilename(), -4)) { + continue; + } + + if ($class = $this->findClass($file)) { + $refl = new \ReflectionClass($class); + if ($refl->isAbstract()) { + continue; + } + + $collection->addCollection($this->loader->load($class, $type)); + } + } + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + try { + $path = $this->locator->locate($resource); + } catch (\Exception $e) { + return false; + } + + return is_string($resource) && is_dir($path) && (!$type || 'annotation' === $type); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Loader/AnnotationFileLoader.php b/vendor/symfony/routing/Symfony/Component/Routing/Loader/AnnotationFileLoader.php new file mode 100644 index 0000000..33776fd --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Loader/AnnotationFileLoader.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\FileLocatorInterface; + +/** + * AnnotationFileLoader loads routing information from annotations set + * on a PHP class and its methods. + * + * @author Fabien Potencier + */ +class AnnotationFileLoader extends FileLoader +{ + protected $loader; + + /** + * Constructor. + * + * @param FileLocatorInterface $locator A FileLocator instance + * @param AnnotationClassLoader $loader An AnnotationClassLoader instance + * @param string|array $paths A path or an array of paths where to look for resources + * + * @throws \RuntimeException + */ + public function __construct(FileLocatorInterface $locator, AnnotationClassLoader $loader, $paths = array()) + { + if (!function_exists('token_get_all')) { + throw new \RuntimeException('The Tokenizer extension is required for the routing annotation loaders.'); + } + + parent::__construct($locator, $paths); + + $this->loader = $loader; + } + + /** + * Loads from annotations from a file. + * + * @param string $file A PHP file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When the file does not exist or its routes cannot be parsed + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + $collection = new RouteCollection(); + if ($class = $this->findClass($path)) { + $collection->addResource(new FileResource($path)); + $collection->addCollection($this->loader->load($class, $type)); + } + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'annotation' === $type); + } + + /** + * Returns the full class name for the first class in the file. + * + * @param string $file A PHP file path + * + * @return string|false Full class name if found, false otherwise + */ + protected function findClass($file) + { + $class = false; + $namespace = false; + $tokens = token_get_all(file_get_contents($file)); + for ($i = 0, $count = count($tokens); $i < $count; $i++) { + $token = $tokens[$i]; + + if (!is_array($token)) { + continue; + } + + if (true === $class && T_STRING === $token[0]) { + return $namespace.'\\'.$token[1]; + } + + if (true === $namespace && T_STRING === $token[0]) { + $namespace = ''; + do { + $namespace .= $token[1]; + $token = $tokens[++$i]; + } while ($i < $count && is_array($token) && in_array($token[0], array(T_NS_SEPARATOR, T_STRING))); + } + + if (T_CLASS === $token[0]) { + $class = true; + } + + if (T_NAMESPACE === $token[0]) { + $namespace = true; + } + } + + return false; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Loader/ClosureLoader.php b/vendor/symfony/routing/Symfony/Component/Routing/Loader/ClosureLoader.php new file mode 100644 index 0000000..8212c29 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Loader/ClosureLoader.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\Routing\RouteCollection; + +/** + * ClosureLoader loads routes from a PHP closure. + * + * The Closure must return a RouteCollection instance. + * + * @author Fabien Potencier + * + * @api + */ +class ClosureLoader extends Loader +{ + /** + * Loads a Closure. + * + * @param \Closure $closure A Closure + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @api + */ + public function load($closure, $type = null) + { + return call_user_func($closure); + } + + /** + * {@inheritdoc} + * + * @api + */ + public function supports($resource, $type = null) + { + return $resource instanceof \Closure && (!$type || 'closure' === $type); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Loader/PhpFileLoader.php b/vendor/symfony/routing/Symfony/Component/Routing/Loader/PhpFileLoader.php new file mode 100644 index 0000000..98b7efb --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Loader/PhpFileLoader.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\RouteCollection; + +/** + * PhpFileLoader loads routes from a PHP file. + * + * The file must return a RouteCollection instance. + * + * @author Fabien Potencier + * + * @api + */ +class PhpFileLoader extends FileLoader +{ + /** + * Loads a PHP file. + * + * @param string $file A PHP file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @api + */ + public function load($file, $type = null) + { + // the loader variable is exposed to the included file below + $loader = $this; + + $path = $this->locator->locate($file); + $this->setCurrentDir(dirname($path)); + + $collection = include $path; + $collection->addResource(new FileResource($path)); + + return $collection; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function supports($resource, $type = null) + { + return is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'php' === $type); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Loader/XmlFileLoader.php b/vendor/symfony/routing/Symfony/Component/Routing/Loader/XmlFileLoader.php new file mode 100644 index 0000000..da7b33d --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Loader/XmlFileLoader.php @@ -0,0 +1,238 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Util\XmlUtils; + +/** + * XmlFileLoader loads XML routing files. + * + * @author Fabien Potencier + * @author Tobias Schultze + * + * @api + */ +class XmlFileLoader extends FileLoader +{ + const NAMESPACE_URI = 'http://symfony.com/schema/routing'; + const SCHEME_PATH = '/schema/routing/routing-1.0.xsd'; + + /** + * Loads an XML file. + * + * @param string $file An XML file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When the file cannot be loaded or when the XML cannot be + * parsed because it does not validate against the scheme. + * + * @api + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + $xml = $this->loadFile($path); + + $collection = new RouteCollection(); + $collection->addResource(new FileResource($path)); + + // process routes and imports + foreach ($xml->documentElement->childNodes as $node) { + if (!$node instanceof \DOMElement) { + continue; + } + + $this->parseNode($collection, $node, $path, $file); + } + + return $collection; + } + + /** + * Parses a node from a loaded XML file. + * + * @param RouteCollection $collection Collection to associate with the node + * @param \DOMElement $node Element to parse + * @param string $path Full path of the XML file being processed + * @param string $file Loaded file name + * + * @throws \InvalidArgumentException When the XML is invalid + */ + protected function parseNode(RouteCollection $collection, \DOMElement $node, $path, $file) + { + if (self::NAMESPACE_URI !== $node->namespaceURI) { + return; + } + + switch ($node->localName) { + case 'route': + $this->parseRoute($collection, $node, $path); + break; + case 'import': + $this->parseImport($collection, $node, $path, $file); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "route" or "import".', $node->localName, $path)); + } + } + + /** + * {@inheritdoc} + * + * @api + */ + public function supports($resource, $type = null) + { + return is_string($resource) && 'xml' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'xml' === $type); + } + + /** + * Parses a route and adds it to the RouteCollection. + * + * @param RouteCollection $collection RouteCollection instance + * @param \DOMElement $node Element to parse that represents a Route + * @param string $path Full path of the XML file being processed + * + * @throws \InvalidArgumentException When the XML is invalid + */ + protected function parseRoute(RouteCollection $collection, \DOMElement $node, $path) + { + if ('' === ($id = $node->getAttribute('id')) || (!$node->hasAttribute('pattern') && !$node->hasAttribute('path'))) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have an "id" and a "path" attribute.', $path)); + } + + if ($node->hasAttribute('pattern')) { + if ($node->hasAttribute('path')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" cannot define both a "path" and a "pattern" attribute. Use only "path".', $path)); + } + + $node->setAttribute('path', $node->getAttribute('pattern')); + $node->removeAttribute('pattern'); + } + + $schemes = preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY); + $methods = preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY); + + list($defaults, $requirements, $options) = $this->parseConfigs($node, $path); + + $route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods); + $collection->add($id, $route); + } + + /** + * Parses an import and adds the routes in the resource to the RouteCollection. + * + * @param RouteCollection $collection RouteCollection instance + * @param \DOMElement $node Element to parse that represents a Route + * @param string $path Full path of the XML file being processed + * @param string $file Loaded file name + * + * @throws \InvalidArgumentException When the XML is invalid + */ + protected function parseImport(RouteCollection $collection, \DOMElement $node, $path, $file) + { + if ('' === $resource = $node->getAttribute('resource')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "resource" attribute.', $path)); + } + + $type = $node->getAttribute('type'); + $prefix = $node->getAttribute('prefix'); + $host = $node->hasAttribute('host') ? $node->getAttribute('host') : null; + $schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY) : null; + $methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY) : null; + + list($defaults, $requirements, $options) = $this->parseConfigs($node, $path); + + $this->setCurrentDir(dirname($path)); + + $subCollection = $this->import($resource, ('' !== $type ? $type : null), false, $file); + /* @var $subCollection RouteCollection */ + $subCollection->addPrefix($prefix); + if (null !== $host) { + $subCollection->setHost($host); + } + if (null !== $schemes) { + $subCollection->setSchemes($schemes); + } + if (null !== $methods) { + $subCollection->setMethods($methods); + } + $subCollection->addDefaults($defaults); + $subCollection->addRequirements($requirements); + $subCollection->addOptions($options); + + $collection->addCollection($subCollection); + } + + /** + * Loads an XML file. + * + * @param string $file An XML file path + * + * @return \DOMDocument + * + * @throws \InvalidArgumentException When loading of XML file fails because of syntax errors + * or when the XML structure is not as expected by the scheme - + * see validate() + */ + protected function loadFile($file) + { + return XmlUtils::loadFile($file, __DIR__.static::SCHEME_PATH); + } + + /** + * Parses the config elements (default, requirement, option). + * + * @param \DOMElement $node Element to parse that contains the configs + * @param string $path Full path of the XML file being processed + * + * @return array An array with the defaults as first item, requirements as second and options as third. + * + * @throws \InvalidArgumentException When the XML is invalid + */ + private function parseConfigs(\DOMElement $node, $path) + { + $defaults = array(); + $requirements = array(); + $options = array(); + + foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) { + switch ($n->localName) { + case 'default': + if ($n->hasAttribute('xsi:nil') && 'true' == $n->getAttribute('xsi:nil')) { + $defaults[$n->getAttribute('key')] = null; + } else { + $defaults[$n->getAttribute('key')] = trim($n->textContent); + } + + break; + case 'requirement': + $requirements[$n->getAttribute('key')] = trim($n->textContent); + break; + case 'option': + $options[$n->getAttribute('key')] = trim($n->textContent); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "default", "requirement" or "option".', $n->localName, $path)); + } + } + + return array($defaults, $requirements, $options); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Loader/YamlFileLoader.php b/vendor/symfony/routing/Symfony/Component/Routing/Loader/YamlFileLoader.php new file mode 100644 index 0000000..9deea7f --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Loader/YamlFileLoader.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Yaml\Parser as YamlParser; +use Symfony\Component\Config\Loader\FileLoader; + +/** + * YamlFileLoader loads Yaml routing files. + * + * @author Fabien Potencier + * @author Tobias Schultze + * + * @api + */ +class YamlFileLoader extends FileLoader +{ + private static $availableKeys = array( + 'resource', 'type', 'prefix', 'pattern', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', + ); + private $yamlParser; + + /** + * Loads a Yaml file. + * + * @param string $file A Yaml file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When a route can't be parsed because YAML is invalid + * + * @api + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + if (!stream_is_local($path)) { + throw new \InvalidArgumentException(sprintf('This is not a local file "%s".', $path)); + } + + if (!file_exists($path)) { + throw new \InvalidArgumentException(sprintf('File "%s" not found.', $path)); + } + + if (null === $this->yamlParser) { + $this->yamlParser = new YamlParser(); + } + + $config = $this->yamlParser->parse(file_get_contents($path)); + + $collection = new RouteCollection(); + $collection->addResource(new FileResource($path)); + + // empty file + if (null === $config) { + return $collection; + } + + // not an array + if (!is_array($config)) { + throw new \InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.', $path)); + } + + foreach ($config as $name => $config) { + if (isset($config['pattern'])) { + if (isset($config['path'])) { + throw new \InvalidArgumentException(sprintf('The file "%s" cannot define both a "path" and a "pattern" attribute. Use only "path".', $path)); + } + + $config['path'] = $config['pattern']; + unset($config['pattern']); + } + + $this->validate($config, $name, $path); + + if (isset($config['resource'])) { + $this->parseImport($collection, $config, $path, $file); + } else { + $this->parseRoute($collection, $name, $config, $path); + } + } + + return $collection; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function supports($resource, $type = null) + { + return is_string($resource) && 'yml' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'yaml' === $type); + } + + /** + * Parses a route and adds it to the RouteCollection. + * + * @param RouteCollection $collection A RouteCollection instance + * @param string $name Route name + * @param array $config Route definition + * @param string $path Full path of the YAML file being processed + */ + protected function parseRoute(RouteCollection $collection, $name, array $config, $path) + { + $defaults = isset($config['defaults']) ? $config['defaults'] : array(); + $requirements = isset($config['requirements']) ? $config['requirements'] : array(); + $options = isset($config['options']) ? $config['options'] : array(); + $host = isset($config['host']) ? $config['host'] : ''; + $schemes = isset($config['schemes']) ? $config['schemes'] : array(); + $methods = isset($config['methods']) ? $config['methods'] : array(); + + $route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods); + + $collection->add($name, $route); + } + + /** + * Parses an import and adds the routes in the resource to the RouteCollection. + * + * @param RouteCollection $collection A RouteCollection instance + * @param array $config Route definition + * @param string $path Full path of the YAML file being processed + * @param string $file Loaded file name + */ + protected function parseImport(RouteCollection $collection, array $config, $path, $file) + { + $type = isset($config['type']) ? $config['type'] : null; + $prefix = isset($config['prefix']) ? $config['prefix'] : ''; + $defaults = isset($config['defaults']) ? $config['defaults'] : array(); + $requirements = isset($config['requirements']) ? $config['requirements'] : array(); + $options = isset($config['options']) ? $config['options'] : array(); + $host = isset($config['host']) ? $config['host'] : null; + $schemes = isset($config['schemes']) ? $config['schemes'] : null; + $methods = isset($config['methods']) ? $config['methods'] : null; + + $this->setCurrentDir(dirname($path)); + + $subCollection = $this->import($config['resource'], $type, false, $file); + /* @var $subCollection RouteCollection */ + $subCollection->addPrefix($prefix); + if (null !== $host) { + $subCollection->setHost($host); + } + if (null !== $schemes) { + $subCollection->setSchemes($schemes); + } + if (null !== $methods) { + $subCollection->setMethods($methods); + } + $subCollection->addDefaults($defaults); + $subCollection->addRequirements($requirements); + $subCollection->addOptions($options); + + $collection->addCollection($subCollection); + } + + /** + * Validates the route configuration. + * + * @param array $config A resource config + * @param string $name The config key + * @param string $path The loaded file path + * + * @throws \InvalidArgumentException If one of the provided config keys is not supported, + * something is missing or the combination is nonsense + */ + protected function validate($config, $name, $path) + { + if (!is_array($config)) { + throw new \InvalidArgumentException(sprintf('The definition of "%s" in "%s" must be a YAML array.', $name, $path)); + } + if ($extraKeys = array_diff(array_keys($config), self::$availableKeys)) { + throw new \InvalidArgumentException(sprintf( + 'The routing file "%s" contains unsupported keys for "%s": "%s". Expected one of: "%s".', + $path, $name, implode('", "', $extraKeys), implode('", "', self::$availableKeys) + )); + } + if (isset($config['resource']) && isset($config['path'])) { + throw new \InvalidArgumentException(sprintf( + 'The routing file "%s" must not specify both the "resource" key and the "path" key for "%s". Choose between an import and a route definition.', + $path, $name + )); + } + if (!isset($config['resource']) && isset($config['type'])) { + throw new \InvalidArgumentException(sprintf( + 'The "type" key for the route definition "%s" in "%s" is unsupported. It is only available for imports in combination with the "resource" key.', + $name, $path + )); + } + if (!isset($config['resource']) && !isset($config['path'])) { + throw new \InvalidArgumentException(sprintf( + 'You must define a "path" for the route "%s" in file "%s".', + $name, $path + )); + } + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd b/vendor/symfony/routing/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd new file mode 100644 index 0000000..daea814 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/ApacheUrlMatcher.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/ApacheUrlMatcher.php new file mode 100644 index 0000000..55aac6b --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/ApacheUrlMatcher.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\Exception\MethodNotAllowedException; + +/** + * ApacheUrlMatcher matches URL based on Apache mod_rewrite matching (see ApacheMatcherDumper). + * + * @author Fabien Potencier + * @author Arnaud Le Blanc + */ +class ApacheUrlMatcher extends UrlMatcher +{ + /** + * Tries to match a URL based on Apache mod_rewrite matching. + * + * Returns false if no route matches the URL. + * + * @param string $pathinfo The pathinfo to be parsed + * + * @return array An array of parameters + * + * @throws MethodNotAllowedException If the current method is not allowed + */ + public function match($pathinfo) + { + $parameters = array(); + $defaults = array(); + $allow = array(); + $route = null; + + foreach ($this->denormalizeValues($_SERVER) as $key => $value) { + $name = $key; + + // skip non-routing variables + // this improves performance when $_SERVER contains many usual + // variables like HTTP_*, DOCUMENT_ROOT, REQUEST_URI, ... + if (false === strpos($name, '_ROUTING_')) { + continue; + } + + while (0 === strpos($name, 'REDIRECT_')) { + $name = substr($name, 9); + } + + // expect _ROUTING__ + // or _ROUTING_ + + if (0 !== strpos($name, '_ROUTING_')) { + continue; + } + if (false !== $pos = strpos($name, '_', 9)) { + $type = substr($name, 9, $pos-9); + $name = substr($name, $pos+1); + } else { + $type = substr($name, 9); + } + + if ('param' === $type) { + if ('' !== $value) { + $parameters[$name] = $value; + } + } elseif ('default' === $type) { + $defaults[$name] = $value; + } elseif ('route' === $type) { + $route = $value; + } elseif ('allow' === $type) { + $allow[] = $name; + } + + unset($_SERVER[$key]); + } + + if (null !== $route) { + $parameters['_route'] = $route; + + return $this->mergeDefaults($parameters, $defaults); + } elseif (0 < count($allow)) { + throw new MethodNotAllowedException($allow); + } else { + return parent::match($pathinfo); + } + } + + /** + * Denormalizes an array of values. + * + * @param string[] $values + * + * @return array + */ + private function denormalizeValues(array $values) + { + $normalizedValues = array(); + foreach ($values as $key => $value) { + if (preg_match('~^(.*)\[(\d+)\]$~', $key, $matches)) { + if (!isset($normalizedValues[$matches[1]])) { + $normalizedValues[$matches[1]] = array(); + } + $normalizedValues[$matches[1]][(int) $matches[2]] = $value; + } else { + $normalizedValues[$key] = $value; + } + } + + return $normalizedValues; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php new file mode 100644 index 0000000..01d8c03 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/ApacheMatcherDumper.php @@ -0,0 +1,274 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\Route; + +/** + * Dumps a set of Apache mod_rewrite rules. + * + * @author Fabien Potencier + * @author Kris Wallsmith + */ +class ApacheMatcherDumper extends MatcherDumper +{ + /** + * Dumps a set of Apache mod_rewrite rules. + * + * Available options: + * + * * script_name: The script name (app.php by default) + * * base_uri: The base URI ("" by default) + * + * @param array $options An array of options + * + * @return string A string to be used as Apache rewrite rules + * + * @throws \LogicException When the route regex is invalid + */ + public function dump(array $options = array()) + { + $options = array_merge(array( + 'script_name' => 'app.php', + 'base_uri' => '', + ), $options); + + $options['script_name'] = self::escape($options['script_name'], ' ', '\\'); + + $rules = array("# skip \"real\" requests\nRewriteCond %{REQUEST_FILENAME} -f\nRewriteRule .* - [QSA,L]"); + $methodVars = array(); + $hostRegexUnique = 0; + $prevHostRegex = ''; + + foreach ($this->getRoutes()->all() as $name => $route) { + + $compiledRoute = $route->compile(); + $hostRegex = $compiledRoute->getHostRegex(); + + if (null !== $hostRegex && $prevHostRegex !== $hostRegex) { + $prevHostRegex = $hostRegex; + $hostRegexUnique++; + + $rule = array(); + + $regex = $this->regexToApacheRegex($hostRegex); + $regex = self::escape($regex, ' ', '\\'); + + $rule[] = sprintf('RewriteCond %%{HTTP:Host} %s', $regex); + + $variables = array(); + $variables[] = sprintf('E=__ROUTING_host_%s:1', $hostRegexUnique); + + foreach ($compiledRoute->getHostVariables() as $i => $variable) { + $variables[] = sprintf('E=__ROUTING_host_%s_%s:%%%d', $hostRegexUnique, $variable, $i+1); + } + + $variables = implode(',', $variables); + + $rule[] = sprintf('RewriteRule .? - [%s]', $variables); + + $rules[] = implode("\n", $rule); + } + + $rules[] = $this->dumpRoute($name, $route, $options, $hostRegexUnique); + + if ($req = $route->getRequirement('_method')) { + $methods = explode('|', strtoupper($req)); + $methodVars = array_merge($methodVars, $methods); + } + } + if (0 < count($methodVars)) { + $rule = array('# 405 Method Not Allowed'); + $methodVars = array_values(array_unique($methodVars)); + if (in_array('GET', $methodVars) && !in_array('HEAD', $methodVars)) { + $methodVars[] = 'HEAD'; + } + foreach ($methodVars as $i => $methodVar) { + $rule[] = sprintf('RewriteCond %%{ENV:_ROUTING__allow_%s} =1%s', $methodVar, isset($methodVars[$i + 1]) ? ' [OR]' : ''); + } + $rule[] = sprintf('RewriteRule .* %s [QSA,L]', $options['script_name']); + + $rules[] = implode("\n", $rule); + } + + return implode("\n\n", $rules)."\n"; + } + + /** + * Dumps a single route + * + * @param string $name Route name + * @param Route $route The route + * @param array $options Options + * @param bool $hostRegexUnique Unique identifier for the host regex + * + * @return string The compiled route + */ + private function dumpRoute($name, $route, array $options, $hostRegexUnique) + { + $compiledRoute = $route->compile(); + + // prepare the apache regex + $regex = $this->regexToApacheRegex($compiledRoute->getRegex()); + $regex = '^'.self::escape(preg_quote($options['base_uri']).substr($regex, 1), ' ', '\\'); + + $methods = $this->getRouteMethods($route); + + $hasTrailingSlash = (!$methods || in_array('HEAD', $methods)) && '/$' === substr($regex, -2) && '^/$' !== $regex; + + $variables = array('E=_ROUTING_route:'.$name); + foreach ($compiledRoute->getHostVariables() as $variable) { + $variables[] = sprintf('E=_ROUTING_param_%s:%%{ENV:__ROUTING_host_%s_%s}', $variable, $hostRegexUnique, $variable); + } + foreach ($compiledRoute->getPathVariables() as $i => $variable) { + $variables[] = 'E=_ROUTING_param_'.$variable.':%'.($i + 1); + } + foreach ($this->normalizeValues($route->getDefaults()) as $key => $value) { + $variables[] = 'E=_ROUTING_default_'.$key.':'.strtr($value, array( + ':' => '\\:', + '=' => '\\=', + '\\' => '\\\\', + ' ' => '\\ ', + )); + } + $variables = implode(',', $variables); + + $rule = array("# $name"); + + // method mismatch + if (0 < count($methods)) { + $allow = array(); + foreach ($methods as $method) { + $allow[] = 'E=_ROUTING_allow_'.$method.':1'; + } + + if ($compiledRoute->getHostRegex()) { + $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_host_%s} =1", $hostRegexUnique); + } + + $rule[] = "RewriteCond %{REQUEST_URI} $regex"; + $rule[] = sprintf("RewriteCond %%{REQUEST_METHOD} !^(%s)$ [NC]", implode('|', $methods)); + $rule[] = sprintf('RewriteRule .* - [S=%d,%s]', $hasTrailingSlash ? 2 : 1, implode(',', $allow)); + } + + // redirect with trailing slash appended + if ($hasTrailingSlash) { + if ($compiledRoute->getHostRegex()) { + $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_host_%s} =1", $hostRegexUnique); + } + + $rule[] = 'RewriteCond %{REQUEST_URI} '.substr($regex, 0, -2).'$'; + $rule[] = 'RewriteRule .* $0/ [QSA,L,R=301]'; + } + + // the main rule + + if ($compiledRoute->getHostRegex()) { + $rule[] = sprintf("RewriteCond %%{ENV:__ROUTING_host_%s} =1", $hostRegexUnique); + } + + $rule[] = "RewriteCond %{REQUEST_URI} $regex"; + $rule[] = "RewriteRule .* {$options['script_name']} [QSA,L,$variables]"; + + return implode("\n", $rule); + } + + /** + * Returns methods allowed for a route + * + * @param Route $route The route + * + * @return array The methods + */ + private function getRouteMethods(Route $route) + { + $methods = array(); + if ($req = $route->getRequirement('_method')) { + $methods = explode('|', strtoupper($req)); + // GET and HEAD are equivalent + if (in_array('GET', $methods) && !in_array('HEAD', $methods)) { + $methods[] = 'HEAD'; + } + } + + return $methods; + } + + /** + * Converts a regex to make it suitable for mod_rewrite + * + * @param string $regex The regex + * + * @return string The converted regex + */ + private function regexToApacheRegex($regex) + { + $regexPatternEnd = strrpos($regex, $regex[0]); + + return preg_replace('/\?P<.+?>/', '', substr($regex, 1, $regexPatternEnd - 1)); + } + + /** + * Escapes a string. + * + * @param string $string The string to be escaped + * @param string $char The character to be escaped + * @param string $with The character to be used for escaping + * + * @return string The escaped string + */ + private static function escape($string, $char, $with) + { + $escaped = false; + $output = ''; + foreach (str_split($string) as $symbol) { + if ($escaped) { + $output .= $symbol; + $escaped = false; + continue; + } + if ($symbol === $char) { + $output .= $with.$char; + continue; + } + if ($symbol === $with) { + $escaped = true; + } + $output .= $symbol; + } + + return $output; + } + + /** + * Normalizes an array of values. + * + * @param array $values + * + * @return string[] + */ + private function normalizeValues(array $values) + { + $normalizedValues = array(); + foreach ($values as $key => $value) { + if (is_array($value)) { + foreach ($value as $index => $bit) { + $normalizedValues[sprintf('%s[%s]', $key, $index)] = $bit; + } + } else { + $normalizedValues[$key] = (string) $value; + } + } + + return $normalizedValues; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php new file mode 100644 index 0000000..612ac0d --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperCollection.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +/** + * Collection of routes. + * + * @author Arnaud Le Blanc + */ +class DumperCollection implements \IteratorAggregate +{ + /** + * @var DumperCollection|null + */ + private $parent; + + /** + * @var (DumperCollection|DumperRoute)[] + */ + private $children = array(); + + /** + * @var array + */ + private $attributes = array(); + + /** + * Returns the children routes and collections. + * + * @return (DumperCollection|DumperRoute)[] Array of DumperCollection|DumperRoute + */ + public function all() + { + return $this->children; + } + + /** + * Adds a route or collection + * + * @param DumperRoute|DumperCollection The route or collection + */ + public function add($child) + { + if ($child instanceof DumperCollection) { + $child->setParent($this); + } + $this->children[] = $child; + } + + /** + * Sets children. + * + * @param array $children The children + */ + public function setAll(array $children) + { + foreach ($children as $child) { + if ($child instanceof DumperCollection) { + $child->setParent($this); + } + } + $this->children = $children; + } + + /** + * Returns an iterator over the children. + * + * @return \Iterator The iterator + */ + public function getIterator() + { + return new \ArrayIterator($this->children); + } + + /** + * Returns the root of the collection. + * + * @return DumperCollection The root collection + */ + public function getRoot() + { + return (null !== $this->parent) ? $this->parent->getRoot() : $this; + } + + /** + * Returns the parent collection. + * + * @return DumperCollection|null The parent collection or null if the collection has no parent + */ + protected function getParent() + { + return $this->parent; + } + + /** + * Sets the parent collection. + * + * @param DumperCollection $parent The parent collection + */ + protected function setParent(DumperCollection $parent) + { + $this->parent = $parent; + } + + /** + * Returns true if the attribute is defined. + * + * @param string $name The attribute name + * + * @return Boolean true if the attribute is defined, false otherwise + */ + public function hasAttribute($name) + { + return array_key_exists($name, $this->attributes); + } + + /** + * Returns an attribute by name. + * + * @param string $name The attribute name + * @param mixed $default Default value is the attribute doesn't exist + * + * @return mixed The attribute value + */ + public function getAttribute($name, $default = null) + { + return $this->hasAttribute($name) ? $this->attributes[$name] : $default; + } + + /** + * Sets an attribute by name. + * + * @param string $name The attribute name + * @param mixed $value The attribute value + */ + public function setAttribute($name, $value) + { + $this->attributes[$name] = $value; + } + + /** + * Sets multiple attributes. + * + * @param array $attributes The attributes + */ + public function setAttributes($attributes) + { + $this->attributes = $attributes; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperPrefixCollection.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperPrefixCollection.php new file mode 100644 index 0000000..26382b0 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperPrefixCollection.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +/** + * Prefix tree of routes preserving routes order. + * + * @author Arnaud Le Blanc + */ +class DumperPrefixCollection extends DumperCollection +{ + /** + * @var string + */ + private $prefix = ''; + + /** + * Returns the prefix. + * + * @return string The prefix + */ + public function getPrefix() + { + return $this->prefix; + } + + /** + * Sets the prefix. + * + * @param string $prefix The prefix + */ + public function setPrefix($prefix) + { + $this->prefix = $prefix; + } + + /** + * Adds a route in the tree. + * + * @param DumperRoute $route The route + * + * @return DumperPrefixCollection The node the route was added to + * + * @throws \LogicException + */ + public function addPrefixRoute(DumperRoute $route) + { + $prefix = $route->getRoute()->compile()->getStaticPrefix(); + + // Same prefix, add to current leave + if ($this->prefix === $prefix) { + $this->add($route); + + return $this; + } + + // Prefix starts with route's prefix + if ('' === $this->prefix || 0 === strpos($prefix, $this->prefix)) { + $collection = new DumperPrefixCollection(); + $collection->setPrefix(substr($prefix, 0, strlen($this->prefix)+1)); + $this->add($collection); + + return $collection->addPrefixRoute($route); + } + + // No match, fallback to parent (recursively) + + if (null === $parent = $this->getParent()) { + throw new \LogicException("The collection root must not have a prefix"); + } + + return $parent->addPrefixRoute($route); + } + + /** + * Merges nodes whose prefix ends with a slash + * + * Children of a node whose prefix ends with a slash are moved to the parent node + */ + public function mergeSlashNodes() + { + $children = array(); + + foreach ($this as $child) { + if ($child instanceof self) { + $child->mergeSlashNodes(); + if ('/' === substr($child->prefix, -1)) { + $children = array_merge($children, $child->all()); + } else { + $children[] = $child; + } + } else { + $children[] = $child; + } + } + + $this->setAll($children); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperRoute.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperRoute.php new file mode 100644 index 0000000..2928cdc --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/DumperRoute.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\Route; + +/** + * Container for a Route. + * + * @author Arnaud Le Blanc + */ +class DumperRoute +{ + /** + * @var string + */ + private $name; + + /** + * @var Route + */ + private $route; + + /** + * Constructor. + * + * @param string $name The route name + * @param Route $route The route + */ + public function __construct($name, Route $route) + { + $this->name = $name; + $this->route = $route; + } + + /** + * Returns the route name. + * + * @return string The route name + */ + public function getName() + { + return $this->name; + } + + /** + * Returns the route. + * + * @return Route The route + */ + public function getRoute() + { + return $this->route; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumper.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumper.php new file mode 100644 index 0000000..52edc01 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumper.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * MatcherDumper is the abstract class for all built-in matcher dumpers. + * + * @author Fabien Potencier + */ +abstract class MatcherDumper implements MatcherDumperInterface +{ + /** + * @var RouteCollection + */ + private $routes; + + /** + * Constructor. + * + * @param RouteCollection $routes The RouteCollection to dump + */ + public function __construct(RouteCollection $routes) + { + $this->routes = $routes; + } + + /** + * {@inheritdoc} + */ + public function getRoutes() + { + return $this->routes; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumperInterface.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumperInterface.php new file mode 100644 index 0000000..f85e4ce --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/MatcherDumperInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +/** + * MatcherDumperInterface is the interface that all matcher dumper classes must implement. + * + * @author Fabien Potencier + */ +interface MatcherDumperInterface +{ + /** + * Dumps a set of routes to a string representation of executable code + * that can then be used to match a request against these routes. + * + * @param array $options An array of options + * + * @return string Executable code + */ + public function dump(array $options = array()); + + /** + * Gets the routes to dump. + * + * @return RouteCollection A RouteCollection instance + */ + public function getRoutes(); +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php new file mode 100644 index 0000000..dc17ffb --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/Dumper/PhpMatcherDumper.php @@ -0,0 +1,378 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * PhpMatcherDumper creates a PHP class able to match URLs for a given set of routes. + * + * @author Fabien Potencier + * @author Tobias Schultze + * @author Arnaud Le Blanc + */ +class PhpMatcherDumper extends MatcherDumper +{ + /** + * Dumps a set of routes to a PHP class. + * + * Available options: + * + * * class: The class name + * * base_class: The base class name + * + * @param array $options An array of options + * + * @return string A PHP class representing the matcher class + */ + public function dump(array $options = array()) + { + $options = array_replace(array( + 'class' => 'ProjectUrlMatcher', + 'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + ), $options); + + // trailing slash support is only enabled if we know how to redirect the user + $interfaces = class_implements($options['base_class']); + $supportsRedirections = isset($interfaces['Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface']); + + return <<context = \$context; + } + +{$this->generateMatchMethod($supportsRedirections)} +} + +EOF; + } + + /** + * Generates the code for the match method implementing UrlMatcherInterface. + * + * @param Boolean $supportsRedirections Whether redirections are supported by the base class + * + * @return string Match method as PHP code + */ + private function generateMatchMethod($supportsRedirections) + { + $code = rtrim($this->compileRoutes($this->getRoutes(), $supportsRedirections), "\n"); + + return <<groupRoutesByHostRegex($routes); + $code = ''; + + foreach ($groups as $collection) { + if (null !== $regex = $collection->getAttribute('host_regex')) { + if (!$fetchedHost) { + $code .= " \$host = \$this->context->getHost();\n\n"; + $fetchedHost = true; + } + + $code .= sprintf(" if (preg_match(%s, \$host, \$hostMatches)) {\n", var_export($regex, true)); + } + + $tree = $this->buildPrefixTree($collection); + $groupCode = $this->compilePrefixRoutes($tree, $supportsRedirections); + + if (null !== $regex) { + // apply extra indention at each line (except empty ones) + $groupCode = preg_replace('/^.{2,}$/m', ' $0', $groupCode); + $code .= $groupCode; + $code .= " }\n\n"; + } else { + $code .= $groupCode; + } + } + + return $code; + } + + /** + * Generates PHP code recursively to match a tree of routes + * + * @param DumperPrefixCollection $collection A DumperPrefixCollection instance + * @param Boolean $supportsRedirections Whether redirections are supported by the base class + * @param string $parentPrefix Prefix of the parent collection + * + * @return string PHP code + */ + private function compilePrefixRoutes(DumperPrefixCollection $collection, $supportsRedirections, $parentPrefix = '') + { + $code = ''; + $prefix = $collection->getPrefix(); + $optimizable = 1 < strlen($prefix) && 1 < count($collection->all()); + $optimizedPrefix = $parentPrefix; + + if ($optimizable) { + $optimizedPrefix = $prefix; + + $code .= sprintf(" if (0 === strpos(\$pathinfo, %s)) {\n", var_export($prefix, true)); + } + + foreach ($collection as $route) { + if ($route instanceof DumperCollection) { + $code .= $this->compilePrefixRoutes($route, $supportsRedirections, $optimizedPrefix); + } else { + $code .= $this->compileRoute($route->getRoute(), $route->getName(), $supportsRedirections, $optimizedPrefix)."\n"; + } + } + + if ($optimizable) { + $code .= " }\n\n"; + // apply extra indention at each line (except empty ones) + $code = preg_replace('/^.{2,}$/m', ' $0', $code); + } + + return $code; + } + + /** + * Compiles a single Route to PHP code used to match it against the path info. + * + * @param Route $route A Route instance + * @param string $name The name of the Route + * @param Boolean $supportsRedirections Whether redirections are supported by the base class + * @param string|null $parentPrefix The prefix of the parent collection used to optimize the code + * + * @return string PHP code + * + * @throws \LogicException + */ + private function compileRoute(Route $route, $name, $supportsRedirections, $parentPrefix = null) + { + $code = ''; + $compiledRoute = $route->compile(); + $conditions = array(); + $hasTrailingSlash = false; + $matches = false; + $hostMatches = false; + $methods = array(); + + if ($req = $route->getRequirement('_method')) { + $methods = explode('|', strtoupper($req)); + // GET and HEAD are equivalent + if (in_array('GET', $methods) && !in_array('HEAD', $methods)) { + $methods[] = 'HEAD'; + } + } + + $supportsTrailingSlash = $supportsRedirections && (!$methods || in_array('HEAD', $methods)); + + if (!count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P.*?)\$\1#', $compiledRoute->getRegex(), $m)) { + if ($supportsTrailingSlash && substr($m['url'], -1) === '/') { + $conditions[] = sprintf("rtrim(\$pathinfo, '/') === %s", var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true)); + $hasTrailingSlash = true; + } else { + $conditions[] = sprintf("\$pathinfo === %s", var_export(str_replace('\\', '', $m['url']), true)); + } + } else { + if ($compiledRoute->getStaticPrefix() && $compiledRoute->getStaticPrefix() !== $parentPrefix) { + $conditions[] = sprintf("0 === strpos(\$pathinfo, %s)", var_export($compiledRoute->getStaticPrefix(), true)); + } + + $regex = $compiledRoute->getRegex(); + if ($supportsTrailingSlash && $pos = strpos($regex, '/$')) { + $regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2); + $hasTrailingSlash = true; + } + $conditions[] = sprintf("preg_match(%s, \$pathinfo, \$matches)", var_export($regex, true)); + + $matches = true; + } + + if ($compiledRoute->getHostVariables()) { + $hostMatches = true; + } + + $conditions = implode(' && ', $conditions); + + $code .= <<context->getMethod() != '$methods[0]') { + \$allow[] = '$methods[0]'; + goto $gotoname; + } + + +EOF; + } else { + $methods = implode("', '", $methods); + $code .= <<context->getMethod(), array('$methods'))) { + \$allow = array_merge(\$allow, array('$methods')); + goto $gotoname; + } + + +EOF; + } + } + + if ($hasTrailingSlash) { + $code .= <<redirect(\$pathinfo.'/', '$name'); + } + + +EOF; + } + + if ($scheme = $route->getRequirement('_scheme')) { + if (!$supportsRedirections) { + throw new \LogicException('The "_scheme" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.'); + } + + $code .= <<context->getScheme() !== '$scheme') { + return \$this->redirect(\$pathinfo, '$name', '$scheme'); + } + + +EOF; + } + + // optimize parameters array + if ($matches || $hostMatches) { + $vars = array(); + if ($hostMatches) { + $vars[] = '$hostMatches'; + } + if ($matches) { + $vars[] = '$matches'; + } + $vars[] = "array('_route' => '$name')"; + + $code .= sprintf(" return \$this->mergeDefaults(array_replace(%s), %s);\n" + , implode(', ', $vars), str_replace("\n", '', var_export($route->getDefaults(), true))); + + } elseif ($route->getDefaults()) { + $code .= sprintf(" return %s;\n", str_replace("\n", '', var_export(array_replace($route->getDefaults(), array('_route' => $name)), true))); + } else { + $code .= sprintf(" return array('_route' => '%s');\n", $name); + } + $code .= " }\n"; + + if ($methods) { + $code .= " $gotoname:\n"; + } + + return $code; + } + + /** + * Groups consecutive routes having the same host regex. + * + * The result is a collection of collections of routes having the same host regex. + * + * @param RouteCollection $routes A flat RouteCollection + * + * @return DumperCollection A collection with routes grouped by host regex in sub-collections + */ + private function groupRoutesByHostRegex(RouteCollection $routes) + { + $groups = new DumperCollection(); + + $currentGroup = new DumperCollection(); + $currentGroup->setAttribute('host_regex', null); + $groups->add($currentGroup); + + foreach ($routes as $name => $route) { + $hostRegex = $route->compile()->getHostRegex(); + if ($currentGroup->getAttribute('host_regex') !== $hostRegex) { + $currentGroup = new DumperCollection(); + $currentGroup->setAttribute('host_regex', $hostRegex); + $groups->add($currentGroup); + } + $currentGroup->add(new DumperRoute($name, $route)); + } + + return $groups; + } + + /** + * Organizes the routes into a prefix tree. + * + * Routes order is preserved such that traversing the tree will traverse the + * routes in the origin order. + * + * @param DumperCollection $collection A collection of routes + * + * @return DumperPrefixCollection + */ + private function buildPrefixTree(DumperCollection $collection) + { + $tree = new DumperPrefixCollection(); + $current = $tree; + + foreach ($collection as $route) { + $current = $current->addPrefixRoute($route); + } + + $tree->mergeSlashNodes(); + + return $tree; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php new file mode 100644 index 0000000..51e8005 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/RedirectableUrlMatcher.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Route; + +/** + * @author Fabien Potencier + * + * @api + */ +abstract class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface +{ + /** + * {@inheritdoc} + */ + public function match($pathinfo) + { + try { + $parameters = parent::match($pathinfo); + } catch (ResourceNotFoundException $e) { + if ('/' === substr($pathinfo, -1) || !in_array($this->context->getMethod(), array('HEAD', 'GET'))) { + throw $e; + } + + try { + parent::match($pathinfo.'/'); + + return $this->redirect($pathinfo.'/', null); + } catch (ResourceNotFoundException $e2) { + throw $e; + } + } + + return $parameters; + } + + /** + * {@inheritDoc} + */ + protected function handleRouteRequirements($pathinfo, $name, Route $route) + { + // check HTTP scheme requirement + $scheme = $route->getRequirement('_scheme'); + if ($scheme && $this->context->getScheme() !== $scheme) { + return array(self::ROUTE_MATCH, $this->redirect($pathinfo, $name, $scheme)); + } + + return array(self::REQUIREMENT_MATCH, null); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/RedirectableUrlMatcherInterface.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/RedirectableUrlMatcherInterface.php new file mode 100644 index 0000000..ea91e07 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/RedirectableUrlMatcherInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +/** + * RedirectableUrlMatcherInterface knows how to redirect the user. + * + * @author Fabien Potencier + * + * @api + */ +interface RedirectableUrlMatcherInterface +{ + /** + * Redirects the user to another URL. + * + * @param string $path The path info to redirect to. + * @param string $route The route name that matched + * @param string|null $scheme The URL scheme (null to keep the current one) + * + * @return array An array of parameters + * + * @api + */ + public function redirect($path, $route, $scheme = null); +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/RequestMatcherInterface.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/RequestMatcherInterface.php new file mode 100644 index 0000000..b5def3d --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/RequestMatcherInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; + +/** + * RequestMatcherInterface is the interface that all request matcher classes must implement. + * + * @author Fabien Potencier + */ +interface RequestMatcherInterface +{ + /** + * Tries to match a request with a set of routes. + * + * If the matcher can not find information, it must throw one of the exceptions documented + * below. + * + * @param Request $request The request to match + * + * @return array An array of parameters + * + * @throws ResourceNotFoundException If no matching resource could be found + * @throws MethodNotAllowedException If a matching resource was found but the request method is not allowed + */ + public function matchRequest(Request $request); +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php new file mode 100644 index 0000000..c09f83e --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/TraceableUrlMatcher.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\Exception\ExceptionInterface; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Matcher\UrlMatcher; + +/** + * TraceableUrlMatcher helps debug path info matching by tracing the match. + * + * @author Fabien Potencier + */ +class TraceableUrlMatcher extends UrlMatcher +{ + const ROUTE_DOES_NOT_MATCH = 0; + const ROUTE_ALMOST_MATCHES = 1; + const ROUTE_MATCHES = 2; + + protected $traces; + + public function getTraces($pathinfo) + { + $this->traces = array(); + + try { + $this->match($pathinfo); + } catch (ExceptionInterface $e) { + } + + return $this->traces; + } + + protected function matchCollection($pathinfo, RouteCollection $routes) + { + foreach ($routes as $name => $route) { + $compiledRoute = $route->compile(); + + if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) { + // does it match without any requirements? + $r = new Route($route->getPath(), $route->getDefaults(), array(), $route->getOptions()); + $cr = $r->compile(); + if (!preg_match($cr->getRegex(), $pathinfo)) { + $this->addTrace(sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route); + + continue; + } + + foreach ($route->getRequirements() as $n => $regex) { + $r = new Route($route->getPath(), $route->getDefaults(), array($n => $regex), $route->getOptions()); + $cr = $r->compile(); + + if (in_array($n, $cr->getVariables()) && !preg_match($cr->getRegex(), $pathinfo)) { + $this->addTrace(sprintf('Requirement for "%s" does not match (%s)', $n, $regex), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue 2; + } + } + + continue; + } + + // check host requirement + $hostMatches = array(); + if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { + $this->addTrace(sprintf('Host "%s" does not match the requirement ("%s")', $this->context->getHost(), $route->getHost()), self::ROUTE_ALMOST_MATCHES, $name, $route); + + return true; + } + + // check HTTP method requirement + if ($req = $route->getRequirement('_method')) { + // HEAD and GET are equivalent as per RFC + if ('HEAD' === $method = $this->context->getMethod()) { + $method = 'GET'; + } + + if (!in_array($method, $req = explode('|', strtoupper($req)))) { + $this->allow = array_merge($this->allow, $req); + + $this->addTrace(sprintf('Method "%s" does not match the requirement ("%s")', $this->context->getMethod(), implode(', ', $req)), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue; + } + } + + // check HTTP scheme requirement + if ($scheme = $route->getRequirement('_scheme')) { + if ($this->context->getScheme() !== $scheme) { + $this->addTrace(sprintf('Scheme "%s" does not match the requirement ("%s"); the user will be redirected', $this->context->getScheme(), $scheme), self::ROUTE_ALMOST_MATCHES, $name, $route); + + return true; + } + } + + $this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route); + + return true; + } + } + + private function addTrace($log, $level = self::ROUTE_DOES_NOT_MATCH, $name = null, $route = null) + { + $this->traces[] = array( + 'log' => $log, + 'name' => $name, + 'level' => $level, + 'path' => null !== $route ? $route->getPath() : null, + ); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/UrlMatcher.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/UrlMatcher.php new file mode 100644 index 0000000..db18ec4 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/UrlMatcher.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Route; + +/** + * UrlMatcher matches URL based on a set of routes. + * + * @author Fabien Potencier + * + * @api + */ +class UrlMatcher implements UrlMatcherInterface +{ + const REQUIREMENT_MATCH = 0; + const REQUIREMENT_MISMATCH = 1; + const ROUTE_MATCH = 2; + + /** + * @var RequestContext + */ + protected $context; + + /** + * @var array + */ + protected $allow = array(); + + /** + * @var RouteCollection + */ + protected $routes; + + /** + * Constructor. + * + * @param RouteCollection $routes A RouteCollection instance + * @param RequestContext $context The context + * + * @api + */ + public function __construct(RouteCollection $routes, RequestContext $context) + { + $this->routes = $routes; + $this->context = $context; + } + + /** + * {@inheritdoc} + */ + public function setContext(RequestContext $context) + { + $this->context = $context; + } + + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->context; + } + + /** + * {@inheritdoc} + */ + public function match($pathinfo) + { + $this->allow = array(); + + if ($ret = $this->matchCollection(rawurldecode($pathinfo), $this->routes)) { + return $ret; + } + + throw 0 < count($this->allow) + ? new MethodNotAllowedException(array_unique(array_map('strtoupper', $this->allow))) + : new ResourceNotFoundException(); + } + + /** + * Tries to match a URL with a set of routes. + * + * @param string $pathinfo The path info to be parsed + * @param RouteCollection $routes The set of routes + * + * @return array An array of parameters + * + * @throws ResourceNotFoundException If the resource could not be found + * @throws MethodNotAllowedException If the resource was found but the request method is not allowed + */ + protected function matchCollection($pathinfo, RouteCollection $routes) + { + foreach ($routes as $name => $route) { + $compiledRoute = $route->compile(); + + // check the static prefix of the URL first. Only use the more expensive preg_match when it matches + if ('' !== $compiledRoute->getStaticPrefix() && 0 !== strpos($pathinfo, $compiledRoute->getStaticPrefix())) { + continue; + } + + if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) { + continue; + } + + $hostMatches = array(); + if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { + continue; + } + + // check HTTP method requirement + if ($req = $route->getRequirement('_method')) { + // HEAD and GET are equivalent as per RFC + if ('HEAD' === $method = $this->context->getMethod()) { + $method = 'GET'; + } + + if (!in_array($method, $req = explode('|', strtoupper($req)))) { + $this->allow = array_merge($this->allow, $req); + + continue; + } + } + + $status = $this->handleRouteRequirements($pathinfo, $name, $route); + + if (self::ROUTE_MATCH === $status[0]) { + return $status[1]; + } + + if (self::REQUIREMENT_MISMATCH === $status[0]) { + continue; + } + + return $this->getAttributes($route, $name, array_replace($matches, $hostMatches)); + } + } + + /** + * Returns an array of values to use as request attributes. + * + * As this method requires the Route object, it is not available + * in matchers that do not have access to the matched Route instance + * (like the PHP and Apache matcher dumpers). + * + * @param Route $route The route we are matching against + * @param string $name The name of the route + * @param array $attributes An array of attributes from the matcher + * + * @return array An array of parameters + */ + protected function getAttributes(Route $route, $name, array $attributes) + { + $attributes['_route'] = $name; + + return $this->mergeDefaults($attributes, $route->getDefaults()); + } + + /** + * Handles specific route requirements. + * + * @param string $pathinfo The path + * @param string $name The route name + * @param Route $route The route + * + * @return array The first element represents the status, the second contains additional information + */ + protected function handleRouteRequirements($pathinfo, $name, Route $route) + { + // check HTTP scheme requirement + $scheme = $route->getRequirement('_scheme'); + $status = $scheme && $scheme !== $this->context->getScheme() ? self::REQUIREMENT_MISMATCH : self::REQUIREMENT_MATCH; + + return array($status, null); + } + + /** + * Get merged default parameters. + * + * @param array $params The parameters + * @param array $defaults The defaults + * + * @return array Merged default parameters + */ + protected function mergeDefaults($params, $defaults) + { + foreach ($params as $key => $value) { + if (!is_int($key)) { + $defaults[$key] = $value; + } + } + + return $defaults; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Matcher/UrlMatcherInterface.php b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/UrlMatcherInterface.php new file mode 100644 index 0000000..dd718b1 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Matcher/UrlMatcherInterface.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\RequestContextAwareInterface; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; + +/** + * UrlMatcherInterface is the interface that all URL matcher classes must implement. + * + * @author Fabien Potencier + * + * @api + */ +interface UrlMatcherInterface extends RequestContextAwareInterface +{ + /** + * Tries to match a URL path with a set of routes. + * + * If the matcher can not find information, it must throw one of the exceptions documented + * below. + * + * @param string $pathinfo The path info to be parsed (raw format, i.e. not urldecoded) + * + * @return array An array of parameters + * + * @throws ResourceNotFoundException If the resource could not be found + * @throws MethodNotAllowedException If the resource was found but the request method is not allowed + * + * @api + */ + public function match($pathinfo); +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/README.md b/vendor/symfony/routing/Symfony/Component/Routing/README.md new file mode 100644 index 0000000..663844a --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/README.md @@ -0,0 +1,34 @@ +Routing Component +================= + +Routing associates a request with the code that will convert it to a response. + +The example below demonstrates how you can set up a fully working routing +system: + + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\Routing\Matcher\UrlMatcher; + use Symfony\Component\Routing\RequestContext; + use Symfony\Component\Routing\RouteCollection; + use Symfony\Component\Routing\Route; + + $routes = new RouteCollection(); + $routes->add('hello', new Route('/hello', array('controller' => 'foo'))); + + $context = new RequestContext(); + + // this is optional and can be done without a Request instance + $context->fromRequest(Request::createFromGlobals()); + + $matcher = new UrlMatcher($routes, $context); + + $parameters = $matcher->match('/hello'); + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/Routing/ + $ composer.phar install --dev + $ phpunit diff --git a/vendor/symfony/routing/Symfony/Component/Routing/RequestContext.php b/vendor/symfony/routing/Symfony/Component/Routing/RequestContext.php new file mode 100644 index 0000000..cb53696 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/RequestContext.php @@ -0,0 +1,315 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\HttpFoundation\Request; + +/** + * Holds information about the current request. + * + * @author Fabien Potencier + * + * @api + */ +class RequestContext +{ + private $baseUrl; + private $pathInfo; + private $method; + private $host; + private $scheme; + private $httpPort; + private $httpsPort; + private $queryString; + + /** + * @var array + */ + private $parameters = array(); + + /** + * Constructor. + * + * @param string $baseUrl The base URL + * @param string $method The HTTP method + * @param string $host The HTTP host name + * @param string $scheme The HTTP scheme + * @param integer $httpPort The HTTP port + * @param integer $httpsPort The HTTPS port + * @param string $path The path + * @param string $queryString The query string + * + * @api + */ + public function __construct($baseUrl = '', $method = 'GET', $host = 'localhost', $scheme = 'http', $httpPort = 80, $httpsPort = 443, $path = '/', $queryString = '') + { + $this->baseUrl = $baseUrl; + $this->method = strtoupper($method); + $this->host = $host; + $this->scheme = strtolower($scheme); + $this->httpPort = $httpPort; + $this->httpsPort = $httpsPort; + $this->pathInfo = $path; + $this->queryString = $queryString; + } + + public function fromRequest(Request $request) + { + $this->setBaseUrl($request->getBaseUrl()); + $this->setPathInfo($request->getPathInfo()); + $this->setMethod($request->getMethod()); + $this->setHost($request->getHost()); + $this->setScheme($request->getScheme()); + $this->setHttpPort($request->isSecure() ? $this->httpPort : $request->getPort()); + $this->setHttpsPort($request->isSecure() ? $request->getPort() : $this->httpsPort); + $this->setQueryString($request->server->get('QUERY_STRING')); + } + + /** + * Gets the base URL. + * + * @return string The base URL + */ + public function getBaseUrl() + { + return $this->baseUrl; + } + + /** + * Sets the base URL. + * + * @param string $baseUrl The base URL + * + * @api + */ + public function setBaseUrl($baseUrl) + { + $this->baseUrl = $baseUrl; + } + + /** + * Gets the path info. + * + * @return string The path info + */ + public function getPathInfo() + { + return $this->pathInfo; + } + + /** + * Sets the path info. + * + * @param string $pathInfo The path info + */ + public function setPathInfo($pathInfo) + { + $this->pathInfo = $pathInfo; + } + + /** + * Gets the HTTP method. + * + * The method is always an uppercased string. + * + * @return string The HTTP method + */ + public function getMethod() + { + return $this->method; + } + + /** + * Sets the HTTP method. + * + * @param string $method The HTTP method + * + * @api + */ + public function setMethod($method) + { + $this->method = strtoupper($method); + } + + /** + * Gets the HTTP host. + * + * @return string The HTTP host + */ + public function getHost() + { + return $this->host; + } + + /** + * Sets the HTTP host. + * + * @param string $host The HTTP host + * + * @api + */ + public function setHost($host) + { + $this->host = $host; + } + + /** + * Gets the HTTP scheme. + * + * @return string The HTTP scheme + */ + public function getScheme() + { + return $this->scheme; + } + + /** + * Sets the HTTP scheme. + * + * @param string $scheme The HTTP scheme + * + * @api + */ + public function setScheme($scheme) + { + $this->scheme = strtolower($scheme); + } + + /** + * Gets the HTTP port. + * + * @return string The HTTP port + */ + public function getHttpPort() + { + return $this->httpPort; + } + + /** + * Sets the HTTP port. + * + * @param string $httpPort The HTTP port + * + * @api + */ + public function setHttpPort($httpPort) + { + $this->httpPort = $httpPort; + } + + /** + * Gets the HTTPS port. + * + * @return string The HTTPS port + */ + public function getHttpsPort() + { + return $this->httpsPort; + } + + /** + * Sets the HTTPS port. + * + * @param string $httpsPort The HTTPS port + * + * @api + */ + public function setHttpsPort($httpsPort) + { + $this->httpsPort = $httpsPort; + } + + /** + * Gets the query string. + * + * @return string The query string + */ + public function getQueryString() + { + return $this->queryString; + } + + /** + * Sets the query string. + * + * @param string $queryString The query string + * + * @api + */ + public function setQueryString($queryString) + { + $this->queryString = $queryString; + } + + /** + * Returns the parameters. + * + * @return array The parameters + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Sets the parameters. + * + * This method implements a fluent interface. + * + * @param array $parameters The parameters + * + * @return Route The current Route instance + */ + public function setParameters(array $parameters) + { + $this->parameters = $parameters; + + return $this; + } + + /** + * Gets a parameter value. + * + * @param string $name A parameter name + * + * @return mixed The parameter value + */ + public function getParameter($name) + { + return isset($this->parameters[$name]) ? $this->parameters[$name] : null; + } + + /** + * Checks if a parameter value is set for the given parameter. + * + * @param string $name A parameter name + * + * @return Boolean true if the parameter value is set, false otherwise + */ + public function hasParameter($name) + { + return array_key_exists($name, $this->parameters); + } + + /** + * Sets a parameter value. + * + * @param string $name A parameter name + * @param mixed $parameter The parameter value + * + * @api + */ + public function setParameter($name, $parameter) + { + $this->parameters[$name] = $parameter; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/RequestContextAwareInterface.php b/vendor/symfony/routing/Symfony/Component/Routing/RequestContextAwareInterface.php new file mode 100644 index 0000000..daf5254 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/RequestContextAwareInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * @api + */ +interface RequestContextAwareInterface +{ + /** + * Sets the request context. + * + * @param RequestContext $context The context + * + * @api + */ + public function setContext(RequestContext $context); + + /** + * Gets the request context. + * + * @return RequestContext The context + * + * @api + */ + public function getContext(); +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Route.php b/vendor/symfony/routing/Symfony/Component/Routing/Route.php new file mode 100644 index 0000000..5bc535c --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Route.php @@ -0,0 +1,594 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * A Route describes a route and its parameters. + * + * @author Fabien Potencier + * @author Tobias Schultze + * + * @api + */ +class Route implements \Serializable +{ + /** + * @var string + */ + private $path = '/'; + + /** + * @var string + */ + private $host = ''; + + /** + * @var array + */ + private $schemes = array(); + + /** + * @var array + */ + private $methods = array(); + + /** + * @var array + */ + private $defaults = array(); + + /** + * @var array + */ + private $requirements = array(); + + /** + * @var array + */ + private $options = array(); + + /** + * @var null|CompiledRoute + */ + private $compiled; + + /** + * Constructor. + * + * Available options: + * + * * compiler_class: A class name able to compile this route instance (RouteCompiler by default) + * + * @param string $path The path pattern to match + * @param array $defaults An array of default parameter values + * @param array $requirements An array of requirements for parameters (regexes) + * @param array $options An array of options + * @param string $host The host pattern to match + * @param string|array $schemes A required URI scheme or an array of restricted schemes + * @param string|array $methods A required HTTP method or an array of restricted methods + * + * @api + */ + public function __construct($path, array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array()) + { + $this->setPath($path); + $this->setDefaults($defaults); + $this->setRequirements($requirements); + $this->setOptions($options); + $this->setHost($host); + // The conditions make sure that an initial empty $schemes/$methods does not override the corresponding requirement. + // They can be removed when the BC layer is removed. + if ($schemes) { + $this->setSchemes($schemes); + } + if ($methods) { + $this->setMethods($methods); + } + } + + public function serialize() + { + return serialize(array( + 'path' => $this->path, + 'host' => $this->host, + 'defaults' => $this->defaults, + 'requirements' => $this->requirements, + 'options' => $this->options, + 'schemes' => $this->schemes, + 'methods' => $this->methods, + )); + } + + public function unserialize($data) + { + $data = unserialize($data); + $this->path = $data['path']; + $this->host = $data['host']; + $this->defaults = $data['defaults']; + $this->requirements = $data['requirements']; + $this->options = $data['options']; + $this->schemes = $data['schemes']; + $this->methods = $data['methods']; + } + + /** + * Returns the pattern for the path. + * + * @return string The pattern + * + * @deprecated Deprecated in 2.2, to be removed in 3.0. Use getPath instead. + */ + public function getPattern() + { + return $this->path; + } + + /** + * Sets the pattern for the path. + * + * This method implements a fluent interface. + * + * @param string $pattern The path pattern + * + * @return Route The current Route instance + * + * @deprecated Deprecated in 2.2, to be removed in 3.0. Use setPath instead. + */ + public function setPattern($pattern) + { + return $this->setPath($pattern); + } + + /** + * Returns the pattern for the path. + * + * @return string The path pattern + */ + public function getPath() + { + return $this->path; + } + + /** + * Sets the pattern for the path. + * + * This method implements a fluent interface. + * + * @param string $pattern The path pattern + * + * @return Route The current Route instance + */ + public function setPath($pattern) + { + // A pattern must start with a slash and must not have multiple slashes at the beginning because the + // generated path for this route would be confused with a network path, e.g. '//domain.com/path'. + $this->path = '/'.ltrim(trim($pattern), '/'); + $this->compiled = null; + + return $this; + } + + /** + * Returns the pattern for the host. + * + * @return string The host pattern + */ + public function getHost() + { + return $this->host; + } + + /** + * Sets the pattern for the host. + * + * This method implements a fluent interface. + * + * @param string $pattern The host pattern + * + * @return Route The current Route instance + */ + public function setHost($pattern) + { + $this->host = (string) $pattern; + $this->compiled = null; + + return $this; + } + + /** + * Returns the lowercased schemes this route is restricted to. + * So an empty array means that any scheme is allowed. + * + * @return array The schemes + */ + public function getSchemes() + { + return $this->schemes; + } + + /** + * Sets the schemes (e.g. 'https') this route is restricted to. + * So an empty array means that any scheme is allowed. + * + * This method implements a fluent interface. + * + * @param string|array $schemes The scheme or an array of schemes + * + * @return Route The current Route instance + */ + public function setSchemes($schemes) + { + $this->schemes = array_map('strtolower', (array) $schemes); + + // this is to keep BC and will be removed in a future version + if ($this->schemes) { + $this->requirements['_scheme'] = implode('|', $this->schemes); + } else { + unset($this->requirements['_scheme']); + } + + $this->compiled = null; + + return $this; + } + + /** + * Returns the uppercased HTTP methods this route is restricted to. + * So an empty array means that any method is allowed. + * + * @return array The schemes + */ + public function getMethods() + { + return $this->methods; + } + + /** + * Sets the HTTP methods (e.g. 'POST') this route is restricted to. + * So an empty array means that any method is allowed. + * + * This method implements a fluent interface. + * + * @param string|array $methods The method or an array of methods + * + * @return Route The current Route instance + */ + public function setMethods($methods) + { + $this->methods = array_map('strtoupper', (array) $methods); + + // this is to keep BC and will be removed in a future version + if ($this->methods) { + $this->requirements['_method'] = implode('|', $this->methods); + } else { + unset($this->requirements['_method']); + } + + $this->compiled = null; + + return $this; + } + + /** + * Returns the options. + * + * @return array The options + */ + public function getOptions() + { + return $this->options; + } + + /** + * Sets the options. + * + * This method implements a fluent interface. + * + * @param array $options The options + * + * @return Route The current Route instance + */ + public function setOptions(array $options) + { + $this->options = array( + 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler', + ); + + return $this->addOptions($options); + } + + /** + * Adds options. + * + * This method implements a fluent interface. + * + * @param array $options The options + * + * @return Route The current Route instance + */ + public function addOptions(array $options) + { + foreach ($options as $name => $option) { + $this->options[$name] = $option; + } + $this->compiled = null; + + return $this; + } + + /** + * Sets an option value. + * + * This method implements a fluent interface. + * + * @param string $name An option name + * @param mixed $value The option value + * + * @return Route The current Route instance + * + * @api + */ + public function setOption($name, $value) + { + $this->options[$name] = $value; + $this->compiled = null; + + return $this; + } + + /** + * Get an option value. + * + * @param string $name An option name + * + * @return mixed The option value or null when not given + */ + public function getOption($name) + { + return isset($this->options[$name]) ? $this->options[$name] : null; + } + + /** + * Checks if an option has been set + * + * @param string $name An option name + * + * @return Boolean true if the option is set, false otherwise + */ + public function hasOption($name) + { + return array_key_exists($name, $this->options); + } + + /** + * Returns the defaults. + * + * @return array The defaults + */ + public function getDefaults() + { + return $this->defaults; + } + + /** + * Sets the defaults. + * + * This method implements a fluent interface. + * + * @param array $defaults The defaults + * + * @return Route The current Route instance + */ + public function setDefaults(array $defaults) + { + $this->defaults = array(); + + return $this->addDefaults($defaults); + } + + /** + * Adds defaults. + * + * This method implements a fluent interface. + * + * @param array $defaults The defaults + * + * @return Route The current Route instance + */ + public function addDefaults(array $defaults) + { + foreach ($defaults as $name => $default) { + $this->defaults[$name] = $default; + } + $this->compiled = null; + + return $this; + } + + /** + * Gets a default value. + * + * @param string $name A variable name + * + * @return mixed The default value or null when not given + */ + public function getDefault($name) + { + return isset($this->defaults[$name]) ? $this->defaults[$name] : null; + } + + /** + * Checks if a default value is set for the given variable. + * + * @param string $name A variable name + * + * @return Boolean true if the default value is set, false otherwise + */ + public function hasDefault($name) + { + return array_key_exists($name, $this->defaults); + } + + /** + * Sets a default value. + * + * @param string $name A variable name + * @param mixed $default The default value + * + * @return Route The current Route instance + * + * @api + */ + public function setDefault($name, $default) + { + $this->defaults[$name] = $default; + $this->compiled = null; + + return $this; + } + + /** + * Returns the requirements. + * + * @return array The requirements + */ + public function getRequirements() + { + return $this->requirements; + } + + /** + * Sets the requirements. + * + * This method implements a fluent interface. + * + * @param array $requirements The requirements + * + * @return Route The current Route instance + */ + public function setRequirements(array $requirements) + { + $this->requirements = array(); + + return $this->addRequirements($requirements); + } + + /** + * Adds requirements. + * + * This method implements a fluent interface. + * + * @param array $requirements The requirements + * + * @return Route The current Route instance + */ + public function addRequirements(array $requirements) + { + foreach ($requirements as $key => $regex) { + $this->requirements[$key] = $this->sanitizeRequirement($key, $regex); + } + $this->compiled = null; + + return $this; + } + + /** + * Returns the requirement for the given key. + * + * @param string $key The key + * + * @return string|null The regex or null when not given + */ + public function getRequirement($key) + { + return isset($this->requirements[$key]) ? $this->requirements[$key] : null; + } + + /** + * Checks if a requirement is set for the given key. + * + * @param string $key A variable name + * + * @return Boolean true if a requirement is specified, false otherwise + */ + public function hasRequirement($key) + { + return array_key_exists($key, $this->requirements); + } + + /** + * Sets a requirement for the given key. + * + * @param string $key The key + * @param string $regex The regex + * + * @return Route The current Route instance + * + * @api + */ + public function setRequirement($key, $regex) + { + $this->requirements[$key] = $this->sanitizeRequirement($key, $regex); + $this->compiled = null; + + return $this; + } + + /** + * Compiles the route. + * + * @return CompiledRoute A CompiledRoute instance + * + * @throws \LogicException If the Route cannot be compiled because the + * path or host pattern is invalid + * + * @see RouteCompiler which is responsible for the compilation process + */ + public function compile() + { + if (null !== $this->compiled) { + return $this->compiled; + } + + $class = $this->getOption('compiler_class'); + + return $this->compiled = $class::compile($this); + } + + private function sanitizeRequirement($key, $regex) + { + if (!is_string($regex)) { + throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" must be a string.', $key)); + } + + if ('' !== $regex && '^' === $regex[0]) { + $regex = (string) substr($regex, 1); // returns false for a single character + } + + if ('$' === substr($regex, -1)) { + $regex = substr($regex, 0, -1); + } + + if ('' === $regex) { + throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" cannot be empty.', $key)); + } + + // this is to keep BC and will be removed in a future version + if ('_scheme' === $key) { + $this->setSchemes(explode('|', $regex)); + } elseif ('_method' === $key) { + $this->setMethods(explode('|', $regex)); + } + + return $regex; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/RouteCollection.php b/vendor/symfony/routing/Symfony/Component/Routing/RouteCollection.php new file mode 100644 index 0000000..499fe0f --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/RouteCollection.php @@ -0,0 +1,271 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * A RouteCollection represents a set of Route instances. + * + * When adding a route at the end of the collection, an existing route + * with the same name is removed first. So there can only be one route + * with a given name. + * + * @author Fabien Potencier + * @author Tobias Schultze + * + * @api + */ +class RouteCollection implements \IteratorAggregate, \Countable +{ + /** + * @var Route[] + */ + private $routes = array(); + + /** + * @var array + */ + private $resources = array(); + + public function __clone() + { + foreach ($this->routes as $name => $route) { + $this->routes[$name] = clone $route; + } + } + + /** + * Gets the current RouteCollection as an Iterator that includes all routes. + * + * It implements \IteratorAggregate. + * + * @see all() + * + * @return \ArrayIterator An \ArrayIterator object for iterating over routes + */ + public function getIterator() + { + return new \ArrayIterator($this->routes); + } + + /** + * Gets the number of Routes in this collection. + * + * @return int The number of routes + */ + public function count() + { + return count($this->routes); + } + + /** + * Adds a route. + * + * @param string $name The route name + * @param Route $route A Route instance + * + * @api + */ + public function add($name, Route $route) + { + unset($this->routes[$name]); + + $this->routes[$name] = $route; + } + + /** + * Returns all routes in this collection. + * + * @return Route[] An array of routes + */ + public function all() + { + return $this->routes; + } + + /** + * Gets a route by name. + * + * @param string $name The route name + * + * @return Route|null A Route instance or null when not found + */ + public function get($name) + { + return isset($this->routes[$name]) ? $this->routes[$name] : null; + } + + /** + * Removes a route or an array of routes by name from the collection + * + * @param string|array $name The route name or an array of route names + */ + public function remove($name) + { + foreach ((array) $name as $n) { + unset($this->routes[$n]); + } + } + + /** + * Adds a route collection at the end of the current set by appending all + * routes of the added collection. + * + * @param RouteCollection $collection A RouteCollection instance + * + * @api + */ + public function addCollection(RouteCollection $collection) + { + // we need to remove all routes with the same names first because just replacing them + // would not place the new route at the end of the merged array + foreach ($collection->all() as $name => $route) { + unset($this->routes[$name]); + $this->routes[$name] = $route; + } + + $this->resources = array_merge($this->resources, $collection->getResources()); + } + + /** + * Adds a prefix to the path of all child routes. + * + * @param string $prefix An optional prefix to add before each pattern of the route collection + * @param array $defaults An array of default values + * @param array $requirements An array of requirements + * + * @api + */ + public function addPrefix($prefix, array $defaults = array(), array $requirements = array()) + { + $prefix = trim(trim($prefix), '/'); + + if ('' === $prefix) { + return; + } + + foreach ($this->routes as $route) { + $route->setPath('/'.$prefix.$route->getPath()); + $route->addDefaults($defaults); + $route->addRequirements($requirements); + } + } + + /** + * Sets the host pattern on all routes. + * + * @param string $pattern The pattern + * @param array $defaults An array of default values + * @param array $requirements An array of requirements + */ + public function setHost($pattern, array $defaults = array(), array $requirements = array()) + { + foreach ($this->routes as $route) { + $route->setHost($pattern); + $route->addDefaults($defaults); + $route->addRequirements($requirements); + } + } + + /** + * Adds defaults to all routes. + * + * An existing default value under the same name in a route will be overridden. + * + * @param array $defaults An array of default values + */ + public function addDefaults(array $defaults) + { + if ($defaults) { + foreach ($this->routes as $route) { + $route->addDefaults($defaults); + } + } + } + + /** + * Adds requirements to all routes. + * + * An existing requirement under the same name in a route will be overridden. + * + * @param array $requirements An array of requirements + */ + public function addRequirements(array $requirements) + { + if ($requirements) { + foreach ($this->routes as $route) { + $route->addRequirements($requirements); + } + } + } + + /** + * Adds options to all routes. + * + * An existing option value under the same name in a route will be overridden. + * + * @param array $options An array of options + */ + public function addOptions(array $options) + { + if ($options) { + foreach ($this->routes as $route) { + $route->addOptions($options); + } + } + } + + /** + * Sets the schemes (e.g. 'https') all child routes are restricted to. + * + * @param string|array $schemes The scheme or an array of schemes + */ + public function setSchemes($schemes) + { + foreach ($this->routes as $route) { + $route->setSchemes($schemes); + } + } + + /** + * Sets the HTTP methods (e.g. 'POST') all child routes are restricted to. + * + * @param string|array $methods The method or an array of methods + */ + public function setMethods($methods) + { + foreach ($this->routes as $route) { + $route->setMethods($methods); + } + } + + /** + * Returns an array of resources loaded to build this collection. + * + * @return ResourceInterface[] An array of resources + */ + public function getResources() + { + return array_unique($this->resources); + } + + /** + * Adds a resource for this collection. + * + * @param ResourceInterface $resource A resource instance + */ + public function addResource(ResourceInterface $resource) + { + $this->resources[] = $resource; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/RouteCompiler.php b/vendor/symfony/routing/Symfony/Component/Routing/RouteCompiler.php new file mode 100644 index 0000000..7ced4b3 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/RouteCompiler.php @@ -0,0 +1,233 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * RouteCompiler compiles Route instances to CompiledRoute instances. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class RouteCompiler implements RouteCompilerInterface +{ + const REGEX_DELIMITER = '#'; + + /** + * This string defines the characters that are automatically considered separators in front of + * optional placeholders (with default and no static text following). Such a single separator + * can be left out together with the optional placeholder from matching and generating URLs. + */ + const SEPARATORS = '/,;.:-_~+*=@|'; + + /** + * {@inheritDoc} + * + * @throws \LogicException If a variable is referenced more than once + * @throws \DomainException If a variable name is numeric because PHP raises an error for such + * subpatterns in PCRE and thus would break matching, e.g. "(?P<123>.+)". + */ + public static function compile(Route $route) + { + $staticPrefix = null; + $hostVariables = array(); + $pathVariables = array(); + $variables = array(); + $tokens = array(); + $regex = null; + $hostRegex = null; + $hostTokens = array(); + + if ('' !== $host = $route->getHost()) { + $result = self::compilePattern($route, $host, true); + + $hostVariables = $result['variables']; + $variables = array_merge($variables, $hostVariables); + + $hostTokens = $result['tokens']; + $hostRegex = $result['regex']; + } + + $path = $route->getPath(); + + $result = self::compilePattern($route, $path, false); + + $staticPrefix = $result['staticPrefix']; + + $pathVariables = $result['variables']; + $variables = array_merge($variables, $pathVariables); + + $tokens = $result['tokens']; + $regex = $result['regex']; + + return new CompiledRoute( + $staticPrefix, + $regex, + $tokens, + $pathVariables, + $hostRegex, + $hostTokens, + $hostVariables, + array_unique($variables) + ); + } + + private static function compilePattern(Route $route, $pattern, $isHost) + { + $tokens = array(); + $variables = array(); + $matches = array(); + $pos = 0; + $defaultSeparator = $isHost ? '.' : '/'; + + // Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable + // in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself. + preg_match_all('#\{\w+\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + foreach ($matches as $match) { + $varName = substr($match[0][0], 1, -1); + // get all static text preceding the current variable + $precedingText = substr($pattern, $pos, $match[0][1] - $pos); + $pos = $match[0][1] + strlen($match[0][0]); + $precedingChar = strlen($precedingText) > 0 ? substr($precedingText, -1) : ''; + $isSeparator = '' !== $precedingChar && false !== strpos(static::SEPARATORS, $precedingChar); + + if (is_numeric($varName)) { + throw new \DomainException(sprintf('Variable name "%s" cannot be numeric in route pattern "%s". Please use a different name.', $varName, $pattern)); + } + if (in_array($varName, $variables)) { + throw new \LogicException(sprintf('Route pattern "%s" cannot reference variable name "%s" more than once.', $pattern, $varName)); + } + + if ($isSeparator && strlen($precedingText) > 1) { + $tokens[] = array('text', substr($precedingText, 0, -1)); + } elseif (!$isSeparator && strlen($precedingText) > 0) { + $tokens[] = array('text', $precedingText); + } + + $regexp = $route->getRequirement($varName); + if (null === $regexp) { + $followingPattern = (string) substr($pattern, $pos); + // Find the next static character after the variable that functions as a separator. By default, this separator and '/' + // are disallowed for the variable. This default requirement makes sure that optional variables can be matched at all + // and that the generating-matching-combination of URLs unambiguous, i.e. the params used for generating the URL are + // the same that will be matched. Example: new Route('/{page}.{_format}', array('_format' => 'html')) + // If {page} would also match the separating dot, {_format} would never match as {page} will eagerly consume everything. + // Also even if {_format} was not optional the requirement prevents that {page} matches something that was originally + // part of {_format} when generating the URL, e.g. _format = 'mobile.html'. + $nextSeparator = self::findNextSeparator($followingPattern); + $regexp = sprintf( + '[^%s%s]+', + preg_quote($defaultSeparator, self::REGEX_DELIMITER), + $defaultSeparator !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator, self::REGEX_DELIMITER) : '' + ); + if (('' !== $nextSeparator && !preg_match('#^\{\w+\}#', $followingPattern)) || '' === $followingPattern) { + // When we have a separator, which is disallowed for the variable, we can optimize the regex with a possessive + // quantifier. This prevents useless backtracking of PCRE and improves performance by 20% for matching those patterns. + // Given the above example, there is no point in backtracking into {page} (that forbids the dot) when a dot must follow + // after it. This optimization cannot be applied when the next char is no real separator or when the next variable is + // directly adjacent, e.g. '/{x}{y}'. + $regexp .= '+'; + } + } + + $tokens[] = array('variable', $isSeparator ? $precedingChar : '', $regexp, $varName); + $variables[] = $varName; + } + + if ($pos < strlen($pattern)) { + $tokens[] = array('text', substr($pattern, $pos)); + } + + // find the first optional token + $firstOptional = PHP_INT_MAX; + if (!$isHost) { + for ($i = count($tokens) - 1; $i >= 0; $i--) { + $token = $tokens[$i]; + if ('variable' === $token[0] && $route->hasDefault($token[3])) { + $firstOptional = $i; + } else { + break; + } + } + } + + // compute the matching regexp + $regexp = ''; + for ($i = 0, $nbToken = count($tokens); $i < $nbToken; $i++) { + $regexp .= self::computeRegexp($tokens, $i, $firstOptional); + } + + return array( + 'staticPrefix' => 'text' === $tokens[0][0] ? $tokens[0][1] : '', + 'regex' => self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'s', + 'tokens' => array_reverse($tokens), + 'variables' => $variables, + ); + } + + /** + * Returns the next static character in the Route pattern that will serve as a separator. + * + * @param string $pattern The route pattern + * + * @return string The next static character that functions as separator (or empty string when none available) + */ + private static function findNextSeparator($pattern) + { + if ('' == $pattern) { + // return empty string if pattern is empty or false (false which can be returned by substr) + return ''; + } + // first remove all placeholders from the pattern so we can find the next real static character + $pattern = preg_replace('#\{\w+\}#', '', $pattern); + + return isset($pattern[0]) && false !== strpos(static::SEPARATORS, $pattern[0]) ? $pattern[0] : ''; + } + + /** + * Computes the regexp used to match a specific token. It can be static text or a subpattern. + * + * @param array $tokens The route tokens + * @param integer $index The index of the current token + * @param integer $firstOptional The index of the first optional token + * + * @return string The regexp pattern for a single token + */ + private static function computeRegexp(array $tokens, $index, $firstOptional) + { + $token = $tokens[$index]; + if ('text' === $token[0]) { + // Text tokens + return preg_quote($token[1], self::REGEX_DELIMITER); + } else { + // Variable tokens + if (0 === $index && 0 === $firstOptional) { + // When the only token is an optional variable token, the separator is required + return sprintf('%s(?P<%s>%s)?', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]); + } else { + $regexp = sprintf('%s(?P<%s>%s)', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]); + if ($index >= $firstOptional) { + // Enclose each optional token in a subpattern to make it optional. + // "?:" means it is non-capturing, i.e. the portion of the subject string that + // matched the optional subpattern is not passed back. + $regexp = "(?:$regexp"; + $nbTokens = count($tokens); + if ($nbTokens - 1 == $index) { + // Close the optional subpatterns + $regexp .= str_repeat(")?", $nbTokens - $firstOptional - (0 === $firstOptional ? 1 : 0)); + } + } + + return $regexp; + } + } + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/RouteCompilerInterface.php b/vendor/symfony/routing/Symfony/Component/Routing/RouteCompilerInterface.php new file mode 100644 index 0000000..e6f8ee6 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/RouteCompilerInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * RouteCompilerInterface is the interface that all RouteCompiler classes must implement. + * + * @author Fabien Potencier + */ +interface RouteCompilerInterface +{ + /** + * Compiles the current route instance. + * + * @param Route $route A Route instance + * + * @return CompiledRoute A CompiledRoute instance + * + * @throws \LogicException If the Route cannot be compiled because the + * path or host pattern is invalid + */ + public static function compile(Route $route); +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Router.php b/vendor/symfony/routing/Symfony/Component/Routing/Router.php new file mode 100644 index 0000000..d1e2897 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Router.php @@ -0,0 +1,289 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\ConfigCache; +use Psr\Log\LoggerInterface; +use Symfony\Component\Routing\Generator\ConfigurableRequirementsInterface; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; + +/** + * The Router class is an example of the integration of all pieces of the + * routing system for easier use. + * + * @author Fabien Potencier + */ +class Router implements RouterInterface +{ + /** + * @var UrlMatcherInterface|null + */ + protected $matcher; + + /** + * @var UrlGeneratorInterface|null + */ + protected $generator; + + /** + * @var RequestContext + */ + protected $context; + + /** + * @var LoaderInterface + */ + protected $loader; + + /** + * @var RouteCollection|null + */ + protected $collection; + + /** + * @var mixed + */ + protected $resource; + + /** + * @var array + */ + protected $options = array(); + + /** + * @var LoggerInterface|null + */ + protected $logger; + + /** + * Constructor. + * + * @param LoaderInterface $loader A LoaderInterface instance + * @param mixed $resource The main resource to load + * @param array $options An array of options + * @param RequestContext $context The context + * @param LoggerInterface $logger A logger instance + */ + public function __construct(LoaderInterface $loader, $resource, array $options = array(), RequestContext $context = null, LoggerInterface $logger = null) + { + $this->loader = $loader; + $this->resource = $resource; + $this->logger = $logger; + $this->context = null === $context ? new RequestContext() : $context; + $this->setOptions($options); + } + + /** + * Sets options. + * + * Available options: + * + * * cache_dir: The cache directory (or null to disable caching) + * * debug: Whether to enable debugging or not (false by default) + * * resource_type: Type hint for the main resource (optional) + * + * @param array $options An array of options + * + * @throws \InvalidArgumentException When unsupported option is provided + */ + public function setOptions(array $options) + { + $this->options = array( + 'cache_dir' => null, + 'debug' => false, + 'generator_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', + 'generator_base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', + 'generator_dumper_class' => 'Symfony\\Component\\Routing\\Generator\\Dumper\\PhpGeneratorDumper', + 'generator_cache_class' => 'ProjectUrlGenerator', + 'matcher_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + 'matcher_base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + 'matcher_dumper_class' => 'Symfony\\Component\\Routing\\Matcher\\Dumper\\PhpMatcherDumper', + 'matcher_cache_class' => 'ProjectUrlMatcher', + 'resource_type' => null, + 'strict_requirements' => true, + ); + + // check option names and live merge, if errors are encountered Exception will be thrown + $invalid = array(); + foreach ($options as $key => $value) { + if (array_key_exists($key, $this->options)) { + $this->options[$key] = $value; + } else { + $invalid[] = $key; + } + } + + if ($invalid) { + throw new \InvalidArgumentException(sprintf('The Router does not support the following options: "%s".', implode('", "', $invalid))); + } + } + + /** + * Sets an option. + * + * @param string $key The key + * @param mixed $value The value + * + * @throws \InvalidArgumentException + */ + public function setOption($key, $value) + { + if (!array_key_exists($key, $this->options)) { + throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key)); + } + + $this->options[$key] = $value; + } + + /** + * Gets an option value. + * + * @param string $key The key + * + * @return mixed The value + * + * @throws \InvalidArgumentException + */ + public function getOption($key) + { + if (!array_key_exists($key, $this->options)) { + throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key)); + } + + return $this->options[$key]; + } + + /** + * {@inheritdoc} + */ + public function getRouteCollection() + { + if (null === $this->collection) { + $this->collection = $this->loader->load($this->resource, $this->options['resource_type']); + } + + return $this->collection; + } + + /** + * {@inheritdoc} + */ + public function setContext(RequestContext $context) + { + $this->context = $context; + + if (null !== $this->matcher) { + $this->getMatcher()->setContext($context); + } + if (null !== $this->generator) { + $this->getGenerator()->setContext($context); + } + } + + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->context; + } + + /** + * {@inheritdoc} + */ + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) + { + return $this->getGenerator()->generate($name, $parameters, $referenceType); + } + + /** + * {@inheritdoc} + */ + public function match($pathinfo) + { + return $this->getMatcher()->match($pathinfo); + } + + /** + * Gets the UrlMatcher instance associated with this Router. + * + * @return UrlMatcherInterface A UrlMatcherInterface instance + */ + public function getMatcher() + { + if (null !== $this->matcher) { + return $this->matcher; + } + + if (null === $this->options['cache_dir'] || null === $this->options['matcher_cache_class']) { + return $this->matcher = new $this->options['matcher_class']($this->getRouteCollection(), $this->context); + } + + $class = $this->options['matcher_cache_class']; + $cache = new ConfigCache($this->options['cache_dir'].'/'.$class.'.php', $this->options['debug']); + if (!$cache->isFresh($class)) { + $dumper = new $this->options['matcher_dumper_class']($this->getRouteCollection()); + + $options = array( + 'class' => $class, + 'base_class' => $this->options['matcher_base_class'], + ); + + $cache->write($dumper->dump($options), $this->getRouteCollection()->getResources()); + } + + require_once $cache; + + return $this->matcher = new $class($this->context); + } + + /** + * Gets the UrlGenerator instance associated with this Router. + * + * @return UrlGeneratorInterface A UrlGeneratorInterface instance + */ + public function getGenerator() + { + if (null !== $this->generator) { + return $this->generator; + } + + if (null === $this->options['cache_dir'] || null === $this->options['generator_cache_class']) { + $this->generator = new $this->options['generator_class']($this->getRouteCollection(), $this->context, $this->logger); + } else { + $class = $this->options['generator_cache_class']; + $cache = new ConfigCache($this->options['cache_dir'].'/'.$class.'.php', $this->options['debug']); + if (!$cache->isFresh($class)) { + $dumper = new $this->options['generator_dumper_class']($this->getRouteCollection()); + + $options = array( + 'class' => $class, + 'base_class' => $this->options['generator_base_class'], + ); + + $cache->write($dumper->dump($options), $this->getRouteCollection()->getResources()); + } + + require_once $cache; + + $this->generator = new $class($this->context, $this->logger); + } + + if ($this->generator instanceof ConfigurableRequirementsInterface) { + $this->generator->setStrictRequirements($this->options['strict_requirements']); + } + + return $this->generator; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/RouterInterface.php b/vendor/symfony/routing/Symfony/Component/Routing/RouterInterface.php new file mode 100644 index 0000000..a10ae34 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/RouterInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; + +/** + * RouterInterface is the interface that all Router classes must implement. + * + * This interface is the concatenation of UrlMatcherInterface and UrlGeneratorInterface. + * + * @author Fabien Potencier + */ +interface RouterInterface extends UrlMatcherInterface, UrlGeneratorInterface +{ + /** + * Gets the RouteCollection instance associated with this Router. + * + * @return RouteCollection A RouteCollection instance + */ + public function getRouteCollection(); +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Annotation/RouteTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Annotation/RouteTest.php new file mode 100644 index 0000000..b58869f --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Annotation/RouteTest.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Annotation; + +use Symfony\Component\Routing\Annotation\Route; + +class RouteTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \BadMethodCallException + */ + public function testInvalidRouteParameter() + { + $route = new Route(array('foo' => 'bar')); + } + + /** + * @dataProvider getValidParameters + */ + public function testRouteParameters($parameter, $value, $getter) + { + $route = new Route(array($parameter => $value)); + $this->assertEquals($route->$getter(), $value); + } + + public function getValidParameters() + { + return array( + array('value', '/Blog', 'getPattern'), + array('value', '/Blog', 'getPath'), + array('requirements', array('_method' => 'GET'), 'getRequirements'), + array('options', array('compiler_class' => 'RouteCompiler'), 'getOptions'), + array('name', 'blog_index', 'getName'), + array('defaults', array('_controller' => 'MyBlogBundle:Blog:index'), 'getDefaults'), + array('schemes', array('https'), 'getSchemes'), + array('methods', array('GET', 'POST'), 'getMethods'), + array('host', array('{locale}.example.com'), 'getHost') + ); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/CompiledRouteTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/CompiledRouteTest.php new file mode 100644 index 0000000..215ebb7 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/CompiledRouteTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use Symfony\Component\Routing\CompiledRoute; + +class CompiledRouteTest extends \PHPUnit_Framework_TestCase +{ + public function testAccessors() + { + $compiled = new CompiledRoute('prefix', 'regex', array('tokens'), array(), array(), array(), array(), array('variables')); + $this->assertEquals('prefix', $compiled->getStaticPrefix(), '__construct() takes a static prefix as its second argument'); + $this->assertEquals('regex', $compiled->getRegex(), '__construct() takes a regexp as its third argument'); + $this->assertEquals(array('tokens'), $compiled->getTokens(), '__construct() takes an array of tokens as its fourth argument'); + $this->assertEquals(array('variables'), $compiled->getVariables(), '__construct() takes an array of variables as its ninth argument'); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php new file mode 100644 index 0000000..56bcab2 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/AnnotatedClasses/AbstractClass.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses; + +abstract class AbstractClass +{ +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/AnnotatedClasses/BarClass.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/AnnotatedClasses/BarClass.php new file mode 100644 index 0000000..a388277 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/AnnotatedClasses/BarClass.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses; + +class BarClass +{ + public function routeAction($arg1, $arg2 = 'defaultValue2', $arg3 = 'defaultValue3') + { + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/AnnotatedClasses/FooClass.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/AnnotatedClasses/FooClass.php new file mode 100644 index 0000000..320dc35 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/AnnotatedClasses/FooClass.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses; + +class FooClass +{ +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/CustomXmlFileLoader.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/CustomXmlFileLoader.php new file mode 100644 index 0000000..12a5bed --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/CustomXmlFileLoader.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures; + +use Symfony\Component\Routing\Loader\XmlFileLoader; +use Symfony\Component\Config\Util\XmlUtils; + +/** + * XmlFileLoader with schema validation turned off + */ +class CustomXmlFileLoader extends XmlFileLoader +{ + protected function loadFile($file) + { + return XmlUtils::loadFile($file, function() { return true; }); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/RedirectableUrlMatcher.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/RedirectableUrlMatcher.php new file mode 100644 index 0000000..e95c1f2 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/RedirectableUrlMatcher.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Fixtures; + +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\Matcher\RedirectableUrlMatcherInterface; + +/** + * @author Fabien Potencier + */ +class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface +{ + public function redirect($path, $route, $scheme = null) + { + return array( + '_controller' => 'Some controller reference...', + 'path' => $path, + 'scheme' => $scheme, + ); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/annotated.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/annotated.php new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.apache b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.apache new file mode 100644 index 0000000..26a561c --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.apache @@ -0,0 +1,163 @@ +# skip "real" requests +RewriteCond %{REQUEST_FILENAME} -f +RewriteRule .* - [QSA,L] + +# foo +RewriteCond %{REQUEST_URI} ^/foo/(baz|symfony)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:foo,E=_ROUTING_param_bar:%1,E=_ROUTING_default_def:test] + +# foobar +RewriteCond %{REQUEST_URI} ^/foo(?:/([^/]++))?$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:foobar,E=_ROUTING_param_bar:%1,E=_ROUTING_default_bar:toto] + +# bar +RewriteCond %{REQUEST_URI} ^/bar/([^/]++)$ +RewriteCond %{REQUEST_METHOD} !^(GET|HEAD)$ [NC] +RewriteRule .* - [S=1,E=_ROUTING_allow_GET:1,E=_ROUTING_allow_HEAD:1] +RewriteCond %{REQUEST_URI} ^/bar/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:bar,E=_ROUTING_param_foo:%1] + +# baragain +RewriteCond %{REQUEST_URI} ^/baragain/([^/]++)$ +RewriteCond %{REQUEST_METHOD} !^(GET|POST|HEAD)$ [NC] +RewriteRule .* - [S=1,E=_ROUTING_allow_GET:1,E=_ROUTING_allow_POST:1,E=_ROUTING_allow_HEAD:1] +RewriteCond %{REQUEST_URI} ^/baragain/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baragain,E=_ROUTING_param_foo:%1] + +# baz +RewriteCond %{REQUEST_URI} ^/test/baz$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz] + +# baz2 +RewriteCond %{REQUEST_URI} ^/test/baz\.html$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz2] + +# baz3 +RewriteCond %{REQUEST_URI} ^/test/baz3$ +RewriteRule .* $0/ [QSA,L,R=301] +RewriteCond %{REQUEST_URI} ^/test/baz3/$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz3] + +# baz4 +RewriteCond %{REQUEST_URI} ^/test/([^/]++)$ +RewriteRule .* $0/ [QSA,L,R=301] +RewriteCond %{REQUEST_URI} ^/test/([^/]++)/$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz4,E=_ROUTING_param_foo:%1] + +# baz5 +RewriteCond %{REQUEST_URI} ^/test/([^/]++)/$ +RewriteCond %{REQUEST_METHOD} !^(GET|HEAD)$ [NC] +RewriteRule .* - [S=2,E=_ROUTING_allow_GET:1,E=_ROUTING_allow_HEAD:1] +RewriteCond %{REQUEST_URI} ^/test/([^/]++)$ +RewriteRule .* $0/ [QSA,L,R=301] +RewriteCond %{REQUEST_URI} ^/test/([^/]++)/$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz5,E=_ROUTING_param_foo:%1] + +# baz5unsafe +RewriteCond %{REQUEST_URI} ^/testunsafe/([^/]++)/$ +RewriteCond %{REQUEST_METHOD} !^(POST)$ [NC] +RewriteRule .* - [S=1,E=_ROUTING_allow_POST:1] +RewriteCond %{REQUEST_URI} ^/testunsafe/([^/]++)/$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz5unsafe,E=_ROUTING_param_foo:%1] + +# baz6 +RewriteCond %{REQUEST_URI} ^/test/baz$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz6,E=_ROUTING_default_foo:bar\ baz] + +# baz7 +RewriteCond %{REQUEST_URI} ^/te\ st/baz$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz7] + +# baz8 +RewriteCond %{REQUEST_URI} ^/te\\\ st/baz$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz8] + +# baz9 +RewriteCond %{REQUEST_URI} ^/test/(te\\\ st)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:baz9,E=_ROUTING_param_baz:%1] + +RewriteCond %{HTTP:Host} ^a\.example\.com$ +RewriteRule .? - [E=__ROUTING_host_1:1] + +# route1 +RewriteCond %{ENV:__ROUTING_host_1} =1 +RewriteCond %{REQUEST_URI} ^/route1$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route1] + +# route2 +RewriteCond %{ENV:__ROUTING_host_1} =1 +RewriteCond %{REQUEST_URI} ^/c2/route2$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route2] + +RewriteCond %{HTTP:Host} ^b\.example\.com$ +RewriteRule .? - [E=__ROUTING_host_2:1] + +# route3 +RewriteCond %{ENV:__ROUTING_host_2} =1 +RewriteCond %{REQUEST_URI} ^/c2/route3$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route3] + +RewriteCond %{HTTP:Host} ^a\.example\.com$ +RewriteRule .? - [E=__ROUTING_host_3:1] + +# route4 +RewriteCond %{ENV:__ROUTING_host_3} =1 +RewriteCond %{REQUEST_URI} ^/route4$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route4] + +RewriteCond %{HTTP:Host} ^c\.example\.com$ +RewriteRule .? - [E=__ROUTING_host_4:1] + +# route5 +RewriteCond %{ENV:__ROUTING_host_4} =1 +RewriteCond %{REQUEST_URI} ^/route5$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route5] + +# route6 +RewriteCond %{REQUEST_URI} ^/route6$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route6] + +RewriteCond %{HTTP:Host} ^([^\.]++)\.example\.com$ +RewriteRule .? - [E=__ROUTING_host_5:1,E=__ROUTING_host_5_var1:%1] + +# route11 +RewriteCond %{ENV:__ROUTING_host_5} =1 +RewriteCond %{REQUEST_URI} ^/route11$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route11,E=_ROUTING_param_var1:%{ENV:__ROUTING_host_5_var1}] + +# route12 +RewriteCond %{ENV:__ROUTING_host_5} =1 +RewriteCond %{REQUEST_URI} ^/route12$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route12,E=_ROUTING_param_var1:%{ENV:__ROUTING_host_5_var1},E=_ROUTING_default_var1:val] + +# route13 +RewriteCond %{ENV:__ROUTING_host_5} =1 +RewriteCond %{REQUEST_URI} ^/route13/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route13,E=_ROUTING_param_var1:%{ENV:__ROUTING_host_5_var1},E=_ROUTING_param_name:%1] + +# route14 +RewriteCond %{ENV:__ROUTING_host_5} =1 +RewriteCond %{REQUEST_URI} ^/route14/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route14,E=_ROUTING_param_var1:%{ENV:__ROUTING_host_5_var1},E=_ROUTING_param_name:%1,E=_ROUTING_default_var1:val] + +RewriteCond %{HTTP:Host} ^c\.example\.com$ +RewriteRule .? - [E=__ROUTING_host_6:1] + +# route15 +RewriteCond %{ENV:__ROUTING_host_6} =1 +RewriteCond %{REQUEST_URI} ^/route15/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route15,E=_ROUTING_param_name:%1] + +# route16 +RewriteCond %{REQUEST_URI} ^/route16/([^/]++)$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route16,E=_ROUTING_param_name:%1,E=_ROUTING_default_var1:val] + +# route17 +RewriteCond %{REQUEST_URI} ^/route17$ +RewriteRule .* app.php [QSA,L,E=_ROUTING_route:route17] + +# 405 Method Not Allowed +RewriteCond %{ENV:_ROUTING__allow_GET} =1 [OR] +RewriteCond %{ENV:_ROUTING__allow_HEAD} =1 [OR] +RewriteCond %{ENV:_ROUTING__allow_POST} =1 +RewriteRule .* app.php [QSA,L] diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php new file mode 100644 index 0000000..e5f7665 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php @@ -0,0 +1,310 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + + // foo + if (0 === strpos($pathinfo, '/foo') && preg_match('#^/foo/(?Pbaz|symfony)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo')), array ( 'def' => 'test',)); + } + + if (0 === strpos($pathinfo, '/bar')) { + // bar + if (preg_match('#^/bar/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { + $allow = array_merge($allow, array('GET', 'HEAD')); + goto not_bar; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar')), array ()); + } + not_bar: + + // barhead + if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { + $allow = array_merge($allow, array('GET', 'HEAD')); + goto not_barhead; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ()); + } + not_barhead: + + } + + if (0 === strpos($pathinfo, '/test')) { + if (0 === strpos($pathinfo, '/test/baz')) { + // baz + if ($pathinfo === '/test/baz') { + return array('_route' => 'baz'); + } + + // baz2 + if ($pathinfo === '/test/baz.html') { + return array('_route' => 'baz2'); + } + + // baz3 + if ($pathinfo === '/test/baz3/') { + return array('_route' => 'baz3'); + } + + } + + // baz4 + if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array ()); + } + + // baz5 + if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ($this->context->getMethod() != 'POST') { + $allow[] = 'POST'; + goto not_baz5; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array ()); + } + not_baz5: + + // baz.baz6 + if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ($this->context->getMethod() != 'PUT') { + $allow[] = 'PUT'; + goto not_bazbaz6; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ()); + } + not_bazbaz6: + + } + + // foofoo + if ($pathinfo === '/foofoo') { + return array ( 'def' => 'test', '_route' => 'foofoo',); + } + + // quoter + if (preg_match('#^/(?P[\']+)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'quoter')), array ()); + } + + // space + if ($pathinfo === '/spa ce') { + return array('_route' => 'space'); + } + + if (0 === strpos($pathinfo, '/a')) { + if (0 === strpos($pathinfo, '/a/b\'b')) { + // foo1 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo1')), array ()); + } + + // bar1 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar1')), array ()); + } + + } + + // overridden + if (preg_match('#^/a/(?P.*)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'overridden')), array ()); + } + + if (0 === strpos($pathinfo, '/a/b\'b')) { + // foo2 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array ()); + } + + // bar2 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar2')), array ()); + } + + } + + } + + if (0 === strpos($pathinfo, '/multi')) { + // helloWorld + if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?P[^/]++))?$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'helloWorld')), array ( 'who' => 'World!',)); + } + + // overridden2 + if ($pathinfo === '/multi/new') { + return array('_route' => 'overridden2'); + } + + // hey + if ($pathinfo === '/multi/hey/') { + return array('_route' => 'hey'); + } + + } + + // foo3 + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo3')), array ()); + } + + // bar3 + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar3')), array ()); + } + + if (0 === strpos($pathinfo, '/aba')) { + // ababa + if ($pathinfo === '/ababa') { + return array('_route' => 'ababa'); + } + + // foo4 + if (preg_match('#^/aba/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ()); + } + + } + + $host = $this->context->getHost(); + + if (preg_match('#^a\\.example\\.com$#s', $host, $hostMatches)) { + // route1 + if ($pathinfo === '/route1') { + return array('_route' => 'route1'); + } + + // route2 + if ($pathinfo === '/c2/route2') { + return array('_route' => 'route2'); + } + + } + + if (preg_match('#^b\\.example\\.com$#s', $host, $hostMatches)) { + // route3 + if ($pathinfo === '/c2/route3') { + return array('_route' => 'route3'); + } + + } + + if (preg_match('#^a\\.example\\.com$#s', $host, $hostMatches)) { + // route4 + if ($pathinfo === '/route4') { + return array('_route' => 'route4'); + } + + } + + if (preg_match('#^c\\.example\\.com$#s', $host, $hostMatches)) { + // route5 + if ($pathinfo === '/route5') { + return array('_route' => 'route5'); + } + + } + + // route6 + if ($pathinfo === '/route6') { + return array('_route' => 'route6'); + } + + if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#s', $host, $hostMatches)) { + if (0 === strpos($pathinfo, '/route1')) { + // route11 + if ($pathinfo === '/route11') { + return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route11')), array ()); + } + + // route12 + if ($pathinfo === '/route12') { + return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route12')), array ( 'var1' => 'val',)); + } + + // route13 + if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route13')), array ()); + } + + // route14 + if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route14')), array ( 'var1' => 'val',)); + } + + } + + } + + if (preg_match('#^c\\.example\\.com$#s', $host, $hostMatches)) { + // route15 + if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ()); + } + + } + + if (0 === strpos($pathinfo, '/route1')) { + // route16 + if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route16')), array ( 'var1' => 'val',)); + } + + // route17 + if ($pathinfo === '/route17') { + return array('_route' => 'route17'); + } + + } + + if (0 === strpos($pathinfo, '/a')) { + // a + if ($pathinfo === '/a/a...') { + return array('_route' => 'a'); + } + + if (0 === strpos($pathinfo, '/a/b')) { + // b + if (preg_match('#^/a/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'b')), array ()); + } + + // c + if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'c')), array ()); + } + + } + + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.apache b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.apache new file mode 100644 index 0000000..309f2ff --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.apache @@ -0,0 +1,7 @@ +# skip "real" requests +RewriteCond %{REQUEST_FILENAME} -f +RewriteRule .* - [QSA,L] + +# foo +RewriteCond %{REQUEST_URI} ^/foo$ +RewriteRule .* ap\ p_d\ ev.php [QSA,L,E=_ROUTING_route:foo] diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php new file mode 100644 index 0000000..ad15790 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php @@ -0,0 +1,340 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + + // foo + if (0 === strpos($pathinfo, '/foo') && preg_match('#^/foo/(?Pbaz|symfony)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo')), array ( 'def' => 'test',)); + } + + if (0 === strpos($pathinfo, '/bar')) { + // bar + if (preg_match('#^/bar/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { + $allow = array_merge($allow, array('GET', 'HEAD')); + goto not_bar; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar')), array ()); + } + not_bar: + + // barhead + if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?P[^/]++)$#s', $pathinfo, $matches)) { + if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) { + $allow = array_merge($allow, array('GET', 'HEAD')); + goto not_barhead; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'barhead')), array ()); + } + not_barhead: + + } + + if (0 === strpos($pathinfo, '/test')) { + if (0 === strpos($pathinfo, '/test/baz')) { + // baz + if ($pathinfo === '/test/baz') { + return array('_route' => 'baz'); + } + + // baz2 + if ($pathinfo === '/test/baz.html') { + return array('_route' => 'baz2'); + } + + // baz3 + if (rtrim($pathinfo, '/') === '/test/baz3') { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'baz3'); + } + + return array('_route' => 'baz3'); + } + + } + + // baz4 + if (preg_match('#^/test/(?P[^/]++)/?$#s', $pathinfo, $matches)) { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'baz4'); + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz4')), array ()); + } + + // baz5 + if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ($this->context->getMethod() != 'POST') { + $allow[] = 'POST'; + goto not_baz5; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz5')), array ()); + } + not_baz5: + + // baz.baz6 + if (preg_match('#^/test/(?P[^/]++)/$#s', $pathinfo, $matches)) { + if ($this->context->getMethod() != 'PUT') { + $allow[] = 'PUT'; + goto not_bazbaz6; + } + + return $this->mergeDefaults(array_replace($matches, array('_route' => 'baz.baz6')), array ()); + } + not_bazbaz6: + + } + + // foofoo + if ($pathinfo === '/foofoo') { + return array ( 'def' => 'test', '_route' => 'foofoo',); + } + + // quoter + if (preg_match('#^/(?P[\']+)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'quoter')), array ()); + } + + // space + if ($pathinfo === '/spa ce') { + return array('_route' => 'space'); + } + + if (0 === strpos($pathinfo, '/a')) { + if (0 === strpos($pathinfo, '/a/b\'b')) { + // foo1 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo1')), array ()); + } + + // bar1 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar1')), array ()); + } + + } + + // overridden + if (preg_match('#^/a/(?P.*)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'overridden')), array ()); + } + + if (0 === strpos($pathinfo, '/a/b\'b')) { + // foo2 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo2')), array ()); + } + + // bar2 + if (preg_match('#^/a/b\'b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar2')), array ()); + } + + } + + } + + if (0 === strpos($pathinfo, '/multi')) { + // helloWorld + if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?P[^/]++))?$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'helloWorld')), array ( 'who' => 'World!',)); + } + + // overridden2 + if ($pathinfo === '/multi/new') { + return array('_route' => 'overridden2'); + } + + // hey + if (rtrim($pathinfo, '/') === '/multi/hey') { + if (substr($pathinfo, -1) !== '/') { + return $this->redirect($pathinfo.'/', 'hey'); + } + + return array('_route' => 'hey'); + } + + } + + // foo3 + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo3')), array ()); + } + + // bar3 + if (preg_match('#^/(?P<_locale>[^/]++)/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'bar3')), array ()); + } + + if (0 === strpos($pathinfo, '/aba')) { + // ababa + if ($pathinfo === '/ababa') { + return array('_route' => 'ababa'); + } + + // foo4 + if (preg_match('#^/aba/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'foo4')), array ()); + } + + } + + $host = $this->context->getHost(); + + if (preg_match('#^a\\.example\\.com$#s', $host, $hostMatches)) { + // route1 + if ($pathinfo === '/route1') { + return array('_route' => 'route1'); + } + + // route2 + if ($pathinfo === '/c2/route2') { + return array('_route' => 'route2'); + } + + } + + if (preg_match('#^b\\.example\\.com$#s', $host, $hostMatches)) { + // route3 + if ($pathinfo === '/c2/route3') { + return array('_route' => 'route3'); + } + + } + + if (preg_match('#^a\\.example\\.com$#s', $host, $hostMatches)) { + // route4 + if ($pathinfo === '/route4') { + return array('_route' => 'route4'); + } + + } + + if (preg_match('#^c\\.example\\.com$#s', $host, $hostMatches)) { + // route5 + if ($pathinfo === '/route5') { + return array('_route' => 'route5'); + } + + } + + // route6 + if ($pathinfo === '/route6') { + return array('_route' => 'route6'); + } + + if (preg_match('#^(?P[^\\.]++)\\.example\\.com$#s', $host, $hostMatches)) { + if (0 === strpos($pathinfo, '/route1')) { + // route11 + if ($pathinfo === '/route11') { + return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route11')), array ()); + } + + // route12 + if ($pathinfo === '/route12') { + return $this->mergeDefaults(array_replace($hostMatches, array('_route' => 'route12')), array ( 'var1' => 'val',)); + } + + // route13 + if (0 === strpos($pathinfo, '/route13') && preg_match('#^/route13/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route13')), array ()); + } + + // route14 + if (0 === strpos($pathinfo, '/route14') && preg_match('#^/route14/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($hostMatches, $matches, array('_route' => 'route14')), array ( 'var1' => 'val',)); + } + + } + + } + + if (preg_match('#^c\\.example\\.com$#s', $host, $hostMatches)) { + // route15 + if (0 === strpos($pathinfo, '/route15') && preg_match('#^/route15/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route15')), array ()); + } + + } + + if (0 === strpos($pathinfo, '/route1')) { + // route16 + if (0 === strpos($pathinfo, '/route16') && preg_match('#^/route16/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'route16')), array ( 'var1' => 'val',)); + } + + // route17 + if ($pathinfo === '/route17') { + return array('_route' => 'route17'); + } + + } + + if (0 === strpos($pathinfo, '/a')) { + // a + if ($pathinfo === '/a/a...') { + return array('_route' => 'a'); + } + + if (0 === strpos($pathinfo, '/a/b')) { + // b + if (preg_match('#^/a/b/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'b')), array ()); + } + + // c + if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'c')), array ()); + } + + } + + } + + // secure + if ($pathinfo === '/secure') { + if ($this->context->getScheme() !== 'https') { + return $this->redirect($pathinfo, 'secure', 'https'); + } + + return array('_route' => 'secure'); + } + + // nonsecure + if ($pathinfo === '/nonsecure') { + if ($this->context->getScheme() !== 'http') { + return $this->redirect($pathinfo, 'nonsecure', 'http'); + } + + return array('_route' => 'nonsecure'); + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php new file mode 100644 index 0000000..f2f642e --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php @@ -0,0 +1,43 @@ +context = $context; + } + + public function match($pathinfo) + { + $allow = array(); + $pathinfo = rawurldecode($pathinfo); + + if (0 === strpos($pathinfo, '/rootprefix')) { + // static + if ($pathinfo === '/rootprefix/test') { + return array('_route' => 'static'); + } + + // dynamic + if (preg_match('#^/rootprefix/(?P[^/]++)$#s', $pathinfo, $matches)) { + return $this->mergeDefaults(array_replace($matches, array('_route' => 'dynamic')), array ()); + } + + } + + throw 0 < count($allow) ? new MethodNotAllowedException(array_unique($allow)) : new ResourceNotFoundException(); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/empty.yml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/empty.yml new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/foo.xml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/foo.xml new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/foo1.xml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/foo1.xml new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/incomplete.yml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/incomplete.yml new file mode 100644 index 0000000..df64d32 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/incomplete.yml @@ -0,0 +1,2 @@ +blog_show: + defaults: { _controller: MyBlogBundle:Blog:show } diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/missing_id.xml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/missing_id.xml new file mode 100644 index 0000000..4ea4115 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/missing_id.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/missing_path.xml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/missing_path.xml new file mode 100644 index 0000000..ef5bc08 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/missing_path.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/namespaceprefix.xml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/namespaceprefix.xml new file mode 100644 index 0000000..bdd6a47 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/namespaceprefix.xml @@ -0,0 +1,13 @@ + + + + + + MyBundle:Blog:show + \w+ + en|fr|de + RouteCompiler + + diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonesense_resource_plus_path.yml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonesense_resource_plus_path.yml new file mode 100644 index 0000000..a3e9473 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonesense_resource_plus_path.yml @@ -0,0 +1,3 @@ +blog_show: + resource: validpattern.yml + path: /test diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonesense_type_without_resource.yml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonesense_type_without_resource.yml new file mode 100644 index 0000000..547cda3 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonesense_type_without_resource.yml @@ -0,0 +1,3 @@ +blog_show: + path: /blog/{slug} + type: custom diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonvalid.xml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonvalid.xml new file mode 100644 index 0000000..755e443 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonvalid.xml @@ -0,0 +1,11 @@ + + + + + + MyBundle:Blog:show + GET + + diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonvalid.yml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonvalid.yml new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonvalid.yml @@ -0,0 +1 @@ +foo diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonvalid2.yml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonvalid2.yml new file mode 100644 index 0000000..cfa9992 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonvalid2.yml @@ -0,0 +1 @@ +route: string diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonvalidkeys.yml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonvalidkeys.yml new file mode 100644 index 0000000..015e270 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonvalidkeys.yml @@ -0,0 +1,3 @@ +someroute: + resource: path/to/some.yml + name_prefix: test_ diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonvalidnode.xml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonvalidnode.xml new file mode 100644 index 0000000..863ef03 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonvalidnode.xml @@ -0,0 +1,8 @@ + + + + + bar + diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonvalidroute.xml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonvalidroute.xml new file mode 100644 index 0000000..a46961e --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/nonvalidroute.xml @@ -0,0 +1,13 @@ + + + + + + MyBundle:Blog:show + GET + + baz + + diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/special_route_name.yml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/special_route_name.yml new file mode 100644 index 0000000..78be239 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/special_route_name.yml @@ -0,0 +1,2 @@ +"#$péß^a|": + path: "true" diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/validpattern.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/validpattern.php new file mode 100644 index 0000000..b8bbbb5 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/validpattern.php @@ -0,0 +1,23 @@ +add('blog_show', new Route( + '/blog/{slug}', + array('_controller' => 'MyBlogBundle:Blog:show'), + array('locale' => '\w+'), + array('compiler_class' => 'RouteCompiler'), + '{locale}.example.com', + array('https'), + array('GET','POST','put','OpTiOnS') +)); +$collection->add('blog_show_legacy', new Route( + '/blog/{slug}', + array('_controller' => 'MyBlogBundle:Blog:show'), + array('_method' => 'GET|POST|put|OpTiOnS', '_scheme' => 'https', 'locale' => '\w+',), + array('compiler_class' => 'RouteCompiler'), + '{locale}.example.com' +)); + +return $collection; diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/validpattern.xml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/validpattern.xml new file mode 100644 index 0000000..b9f2234 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/validpattern.xml @@ -0,0 +1,21 @@ + + + + + + MyBundle:Blog:show + \w+ + + + + + MyBundle:Blog:show + + GET|POST|put|OpTiOnS + hTTps + \w+ + + + diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml new file mode 100644 index 0000000..4ada883 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/validpattern.yml @@ -0,0 +1,17 @@ +blog_show: + path: /blog/{slug} + defaults: { _controller: "MyBundle:Blog:show" } + host: "{locale}.example.com" + requirements: { 'locale': '\w+' } + methods: ['GET','POST','put','OpTiOnS'] + schemes: ['https'] + options: + compiler_class: RouteCompiler + +blog_show_legacy: + pattern: /blog/{slug} + defaults: { _controller: "MyBundle:Blog:show" } + host: "{locale}.example.com" + requirements: { '_method': 'GET|POST|put|OpTiOnS', _scheme: https, 'locale': '\w+' } + options: + compiler_class: RouteCompiler diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/validresource.xml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/validresource.xml new file mode 100644 index 0000000..295c3cc --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/validresource.xml @@ -0,0 +1,12 @@ + + + + + + 123 + \d+ + + + diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/validresource.yml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/validresource.yml new file mode 100644 index 0000000..495ed85 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/validresource.yml @@ -0,0 +1,7 @@ +_blog: + resource: validpattern.yml + prefix: /{foo} + defaults: { 'foo': '123' } + requirements: { 'foo': '\d+' } + options: { 'foo': 'bar' } + host: "" diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/withdoctype.xml b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/withdoctype.xml new file mode 100644 index 0000000..f217d5b --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Fixtures/withdoctype.xml @@ -0,0 +1,3 @@ + + + diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php new file mode 100644 index 0000000..ab5f4cd --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Generator/Dumper/PhpGeneratorDumperTest.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Generator\Dumper; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\Generator\Dumper\PhpGeneratorDumper; +use Symfony\Component\Routing\RequestContext; + +class PhpGeneratorDumperTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var RouteCollection + */ + private $routeCollection; + + /** + * @var PhpGeneratorDumper + */ + private $generatorDumper; + + /** + * @var string + */ + private $testTmpFilepath; + + protected function setUp() + { + parent::setUp(); + + $this->routeCollection = new RouteCollection(); + $this->generatorDumper = new PhpGeneratorDumper($this->routeCollection); + $this->testTmpFilepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_generator.php'; + @unlink($this->testTmpFilepath); + } + + protected function tearDown() + { + parent::tearDown(); + + @unlink($this->testTmpFilepath); + + $this->routeCollection = null; + $this->generatorDumper = null; + $this->testTmpFilepath = null; + } + + public function testDumpWithRoutes() + { + $this->routeCollection->add('Test', new Route('/testing/{foo}')); + $this->routeCollection->add('Test2', new Route('/testing2')); + + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump()); + include ($this->testTmpFilepath); + + $projectUrlGenerator = new \ProjectUrlGenerator(new RequestContext('/app.php')); + + $absoluteUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), true); + $absoluteUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), true); + $relativeUrlWithParameter = $projectUrlGenerator->generate('Test', array('foo' => 'bar'), false); + $relativeUrlWithoutParameter = $projectUrlGenerator->generate('Test2', array(), false); + + $this->assertEquals($absoluteUrlWithParameter, 'http://localhost/app.php/testing/bar'); + $this->assertEquals($absoluteUrlWithoutParameter, 'http://localhost/app.php/testing2'); + $this->assertEquals($relativeUrlWithParameter, '/app.php/testing/bar'); + $this->assertEquals($relativeUrlWithoutParameter, '/app.php/testing2'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testDumpWithoutRoutes() + { + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'WithoutRoutesUrlGenerator'))); + include ($this->testTmpFilepath); + + $projectUrlGenerator = new \WithoutRoutesUrlGenerator(new RequestContext('/app.php')); + + $projectUrlGenerator->generate('Test', array()); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException + */ + public function testGenerateNonExistingRoute() + { + $this->routeCollection->add('Test', new Route('/test')); + + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'NonExistingRoutesUrlGenerator'))); + include ($this->testTmpFilepath); + + $projectUrlGenerator = new \NonExistingRoutesUrlGenerator(new RequestContext()); + $url = $projectUrlGenerator->generate('NonExisting', array()); + } + + public function testDumpForRouteWithDefaults() + { + $this->routeCollection->add('Test', new Route('/testing/{foo}', array('foo' => 'bar'))); + + file_put_contents($this->testTmpFilepath, $this->generatorDumper->dump(array('class' => 'DefaultRoutesUrlGenerator'))); + include ($this->testTmpFilepath); + + $projectUrlGenerator = new \DefaultRoutesUrlGenerator(new RequestContext()); + $url = $projectUrlGenerator->generate('Test', array()); + + $this->assertEquals($url, '/testing'); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php new file mode 100644 index 0000000..5f8ef49 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Generator/UrlGeneratorTest.php @@ -0,0 +1,635 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Generator; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\RequestContext; + +class UrlGeneratorTest extends \PHPUnit_Framework_TestCase +{ + public function testAbsoluteUrlWithPort80() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array(), true); + + $this->assertEquals('http://localhost/app.php/testing', $url); + } + + public function testAbsoluteSecureUrlWithPort443() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes, array('scheme' => 'https'))->generate('test', array(), true); + + $this->assertEquals('https://localhost/app.php/testing', $url); + } + + public function testAbsoluteUrlWithNonStandardPort() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes, array('httpPort' => 8080))->generate('test', array(), true); + + $this->assertEquals('http://localhost:8080/app.php/testing', $url); + } + + public function testAbsoluteSecureUrlWithNonStandardPort() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes, array('httpsPort' => 8080, 'scheme' => 'https'))->generate('test', array(), true); + + $this->assertEquals('https://localhost:8080/app.php/testing', $url); + } + + public function testRelativeUrlWithoutParameters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array(), false); + + $this->assertEquals('/app.php/testing', $url); + } + + public function testRelativeUrlWithParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}')); + $url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), false); + + $this->assertEquals('/app.php/testing/bar', $url); + } + + public function testRelativeUrlWithNullParameter() + { + $routes = $this->getRoutes('test', new Route('/testing.{format}', array('format' => null))); + $url = $this->getGenerator($routes)->generate('test', array(), false); + + $this->assertEquals('/app.php/testing', $url); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testRelativeUrlWithNullParameterButNotOptional() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}/bar', array('foo' => null))); + // This must raise an exception because the default requirement for "foo" is "[^/]+" which is not met with these params. + // Generating path "/testing//bar" would be wrong as matching this route would fail. + $this->getGenerator($routes)->generate('test', array(), false); + } + + public function testRelativeUrlWithOptionalZeroParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{page}')); + $url = $this->getGenerator($routes)->generate('test', array('page' => 0), false); + + $this->assertEquals('/app.php/testing/0', $url); + } + + public function testNotPassedOptionalParameterInBetween() + { + $routes = $this->getRoutes('test', new Route('/{slug}/{page}', array('slug' => 'index', 'page' => 0))); + $this->assertSame('/app.php/index/1', $this->getGenerator($routes)->generate('test', array('page' => 1))); + $this->assertSame('/app.php/', $this->getGenerator($routes)->generate('test')); + } + + public function testRelativeUrlWithExtraParameters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), false); + + $this->assertEquals('/app.php/testing?foo=bar', $url); + } + + public function testAbsoluteUrlWithExtraParameters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), true); + + $this->assertEquals('http://localhost/app.php/testing?foo=bar', $url); + } + + public function testUrlWithNullExtraParameters() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $url = $this->getGenerator($routes)->generate('test', array('foo' => null), true); + + $this->assertEquals('http://localhost/app.php/testing', $url); + } + + public function testUrlWithExtraParametersFromGlobals() + { + $routes = $this->getRoutes('test', new Route('/testing')); + $generator = $this->getGenerator($routes); + $context = new RequestContext('/app.php'); + $context->setParameter('bar', 'bar'); + $generator->setContext($context); + $url = $generator->generate('test', array('foo' => 'bar')); + + $this->assertEquals('/app.php/testing?foo=bar', $url); + } + + public function testUrlWithGlobalParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}')); + $generator = $this->getGenerator($routes); + $context = new RequestContext('/app.php'); + $context->setParameter('foo', 'bar'); + $generator->setContext($context); + $url = $generator->generate('test', array()); + + $this->assertEquals('/app.php/testing/bar', $url); + } + + public function testGlobalParameterHasHigherPriorityThanDefault() + { + $routes = $this->getRoutes('test', new Route('/{_locale}', array('_locale' => 'en'))); + $generator = $this->getGenerator($routes); + $context = new RequestContext('/app.php'); + $context->setParameter('_locale', 'de'); + $generator->setContext($context); + $url = $generator->generate('test', array()); + + $this->assertSame('/app.php/de', $url); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\RouteNotFoundException + */ + public function testGenerateWithoutRoutes() + { + $routes = $this->getRoutes('foo', new Route('/testing/{foo}')); + $this->getGenerator($routes)->generate('test', array(), true); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\MissingMandatoryParametersException + */ + public function testGenerateForRouteWithoutMandatoryParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}')); + $this->getGenerator($routes)->generate('test', array(), true); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testGenerateForRouteWithInvalidOptionalParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+'))); + $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), true); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testGenerateForRouteWithInvalidParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => '1|2'))); + $this->getGenerator($routes)->generate('test', array('foo' => '0'), true); + } + + public function testGenerateForRouteWithInvalidOptionalParameterNonStrict() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+'))); + $generator = $this->getGenerator($routes); + $generator->setStrictRequirements(false); + $this->assertNull($generator->generate('test', array('foo' => 'bar'), true)); + } + + public function testGenerateForRouteWithInvalidOptionalParameterNonStrictWithLogger() + { + if (!interface_exists('Psr\Log\LoggerInterface')) { + $this->markTestSkipped('The "psr/log" package is not available'); + } + + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+'))); + $logger = $this->getMock('Psr\Log\LoggerInterface'); + $logger->expects($this->once()) + ->method('error'); + $generator = $this->getGenerator($routes, array(), $logger); + $generator->setStrictRequirements(false); + $this->assertNull($generator->generate('test', array('foo' => 'bar'), true)); + } + + public function testGenerateForRouteWithInvalidParameterButDisabledRequirementsCheck() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array('foo' => '1'), array('foo' => 'd+'))); + $generator = $this->getGenerator($routes); + $generator->setStrictRequirements(null); + $this->assertSame('/app.php/testing/bar', $generator->generate('test', array('foo' => 'bar'))); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testGenerateForRouteWithInvalidMandatoryParameter() + { + $routes = $this->getRoutes('test', new Route('/testing/{foo}', array(), array('foo' => 'd+'))); + $this->getGenerator($routes)->generate('test', array('foo' => 'bar'), true); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testRequiredParamAndEmptyPassed() + { + $routes = $this->getRoutes('test', new Route('/{slug}', array(), array('slug' => '.+'))); + $this->getGenerator($routes)->generate('test', array('slug' => '')); + } + + public function testSchemeRequirementDoesNothingIfSameCurrentScheme() + { + $routes = $this->getRoutes('test', new Route('/', array(), array('_scheme' => 'http'))); + $this->assertEquals('/app.php/', $this->getGenerator($routes)->generate('test')); + + $routes = $this->getRoutes('test', new Route('/', array(), array('_scheme' => 'https'))); + $this->assertEquals('/app.php/', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test')); + } + + public function testSchemeRequirementForcesAbsoluteUrl() + { + $routes = $this->getRoutes('test', new Route('/', array(), array('_scheme' => 'https'))); + $this->assertEquals('https://localhost/app.php/', $this->getGenerator($routes)->generate('test')); + + $routes = $this->getRoutes('test', new Route('/', array(), array('_scheme' => 'http'))); + $this->assertEquals('http://localhost/app.php/', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test')); + } + + public function testPathWithTwoStartingSlashes() + { + $routes = $this->getRoutes('test', new Route('//path-and-not-domain')); + + // this must not generate '//path-and-not-domain' because that would be a network path + $this->assertSame('/path-and-not-domain', $this->getGenerator($routes, array('BaseUrl' => ''))->generate('test')); + } + + public function testNoTrailingSlashForMultipleOptionalParameters() + { + $routes = $this->getRoutes('test', new Route('/category/{slug1}/{slug2}/{slug3}', array('slug2' => null, 'slug3' => null))); + + $this->assertEquals('/app.php/category/foo', $this->getGenerator($routes)->generate('test', array('slug1' => 'foo'))); + } + + public function testWithAnIntegerAsADefaultValue() + { + $routes = $this->getRoutes('test', new Route('/{default}', array('default' => 0))); + + $this->assertEquals('/app.php/foo', $this->getGenerator($routes)->generate('test', array('default' => 'foo'))); + } + + public function testNullForOptionalParameterIsIgnored() + { + $routes = $this->getRoutes('test', new Route('/test/{default}', array('default' => 0))); + + $this->assertEquals('/app.php/test', $this->getGenerator($routes)->generate('test', array('default' => null))); + } + + public function testQueryParamSameAsDefault() + { + $routes = $this->getRoutes('test', new Route('/test', array('default' => 'value'))); + + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('default' => 'foo'))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test', array('default' => 'value'))); + $this->assertSame('/app.php/test', $this->getGenerator($routes)->generate('test')); + } + + public function testGenerateWithSpecialRouteName() + { + $routes = $this->getRoutes('$péß^a|', new Route('/bar')); + + $this->assertSame('/app.php/bar', $this->getGenerator($routes)->generate('$péß^a|')); + } + + public function testUrlEncoding() + { + // This tests the encoding of reserved characters that are used for delimiting of URI components (defined in RFC 3986) + // and other special ASCII chars. These chars are tested as static text path, variable path and query param. + $chars = '@:[]/()*\'" +,;-._~&$<>|{}%\\^`!?foo=bar#id'; + $routes = $this->getRoutes('test', new Route("/$chars/{varpath}", array(), array('varpath' => '.+'))); + $this->assertSame('/app.php/@:%5B%5D/%28%29*%27%22%20+,;-._~%26%24%3C%3E|%7B%7D%25%5C%5E%60!%3Ffoo=bar%23id' + .'/@:%5B%5D/%28%29*%27%22%20+,;-._~%26%24%3C%3E|%7B%7D%25%5C%5E%60!%3Ffoo=bar%23id' + .'?query=%40%3A%5B%5D%2F%28%29%2A%27%22+%2B%2C%3B-._%7E%26%24%3C%3E%7C%7B%7D%25%5C%5E%60%21%3Ffoo%3Dbar%23id', + $this->getGenerator($routes)->generate('test', array( + 'varpath' => $chars, + 'query' => $chars + )) + ); + } + + public function testEncodingOfRelativePathSegments() + { + $routes = $this->getRoutes('test', new Route('/dir/../dir/..')); + $this->assertSame('/app.php/dir/%2E%2E/dir/%2E%2E', $this->getGenerator($routes)->generate('test')); + $routes = $this->getRoutes('test', new Route('/dir/./dir/.')); + $this->assertSame('/app.php/dir/%2E/dir/%2E', $this->getGenerator($routes)->generate('test')); + $routes = $this->getRoutes('test', new Route('/a./.a/a../..a/...')); + $this->assertSame('/app.php/a./.a/a../..a/...', $this->getGenerator($routes)->generate('test')); + } + + public function testAdjacentVariables() + { + $routes = $this->getRoutes('test', new Route('/{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => '\d+'))); + $generator = $this->getGenerator($routes); + $this->assertSame('/app.php/foo123', $generator->generate('test', array('x' => 'foo', 'y' => '123'))); + $this->assertSame('/app.php/foo123bar.xml', $generator->generate('test', array('x' => 'foo', 'y' => '123', 'z' => 'bar', '_format' => 'xml'))); + + // The default requirement for 'x' should not allow the separator '.' in this case because it would otherwise match everything + // and following optional variables like _format could never match. + $this->setExpectedException('Symfony\Component\Routing\Exception\InvalidParameterException'); + $generator->generate('test', array('x' => 'do.t', 'y' => '123', 'z' => 'bar', '_format' => 'xml')); + } + + public function testOptionalVariableWithNoRealSeparator() + { + $routes = $this->getRoutes('test', new Route('/get{what}', array('what' => 'All'))); + $generator = $this->getGenerator($routes); + + $this->assertSame('/app.php/get', $generator->generate('test')); + $this->assertSame('/app.php/getSites', $generator->generate('test', array('what' => 'Sites'))); + } + + public function testRequiredVariableWithNoRealSeparator() + { + $routes = $this->getRoutes('test', new Route('/get{what}Suffix')); + $generator = $this->getGenerator($routes); + + $this->assertSame('/app.php/getSitesSuffix', $generator->generate('test', array('what' => 'Sites'))); + } + + public function testDefaultRequirementOfVariable() + { + $routes = $this->getRoutes('test', new Route('/{page}.{_format}')); + $generator = $this->getGenerator($routes); + + $this->assertSame('/app.php/index.mobile.html', $generator->generate('test', array('page' => 'index', '_format' => 'mobile.html'))); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testDefaultRequirementOfVariableDisallowsSlash() + { + $routes = $this->getRoutes('test', new Route('/{page}.{_format}')); + $this->getGenerator($routes)->generate('test', array('page' => 'index', '_format' => 'sl/ash')); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testDefaultRequirementOfVariableDisallowsNextSeparator() + { + $routes = $this->getRoutes('test', new Route('/{page}.{_format}')); + $this->getGenerator($routes)->generate('test', array('page' => 'do.t', '_format' => 'html')); + } + + public function testWithHostDifferentFromContext() + { + $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com')); + + $this->assertEquals('//fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test', array('name' =>'Fabien', 'locale' => 'fr'))); + } + + public function testWithHostSameAsContext() + { + $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com')); + + $this->assertEquals('/app.php/Fabien', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', array('name' =>'Fabien', 'locale' => 'fr'))); + } + + public function testWithHostSameAsContextAndAbsolute() + { + $routes = $this->getRoutes('test', new Route('/{name}', array(), array(), array(), '{locale}.example.com')); + + $this->assertEquals('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', array('name' =>'Fabien', 'locale' => 'fr'), true)); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testUrlWithInvalidParameterInHost() + { + $routes = $this->getRoutes('test', new Route('/', array(), array('foo' => 'bar'), array(), '{foo}.example.com')); + $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), false); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testUrlWithInvalidParameterInHostWhenParamHasADefaultValue() + { + $routes = $this->getRoutes('test', new Route('/', array('foo' => 'bar'), array('foo' => 'bar'), array(), '{foo}.example.com')); + $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), false); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\InvalidParameterException + */ + public function testUrlWithInvalidParameterEqualsDefaultValueInHost() + { + $routes = $this->getRoutes('test', new Route('/', array('foo' => 'baz'), array('foo' => 'bar'), array(), '{foo}.example.com')); + $this->getGenerator($routes)->generate('test', array('foo' => 'baz'), false); + } + + public function testUrlWithInvalidParameterInHostInNonStrictMode() + { + $routes = $this->getRoutes('test', new Route('/', array(), array('foo' => 'bar'), array(), '{foo}.example.com')); + $generator = $this->getGenerator($routes); + $generator->setStrictRequirements(false); + $this->assertNull($generator->generate('test', array('foo' => 'baz'), false)); + } + + public function testGenerateNetworkPath() + { + $routes = $this->getRoutes('test', new Route('/{name}', array(), array('_scheme' => 'http'), array(), '{locale}.example.com')); + + $this->assertSame('//fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test', + array('name' =>'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::NETWORK_PATH), 'network path with different host' + ); + $this->assertSame('//fr.example.com/app.php/Fabien?query=string', $this->getGenerator($routes, array('host' => 'fr.example.com'))->generate('test', + array('name' =>'Fabien', 'locale' => 'fr', 'query' => 'string'), UrlGeneratorInterface::NETWORK_PATH), 'network path although host same as context' + ); + $this->assertSame('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes, array('scheme' => 'https'))->generate('test', + array('name' =>'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::NETWORK_PATH), 'absolute URL because scheme requirement does not match context' + ); + $this->assertSame('http://fr.example.com/app.php/Fabien', $this->getGenerator($routes)->generate('test', + array('name' =>'Fabien', 'locale' => 'fr'), UrlGeneratorInterface::ABSOLUTE_URL), 'absolute URL with same scheme because it is requested' + ); + } + + public function testGenerateRelativePath() + { + $routes = new RouteCollection(); + $routes->add('article', new Route('/{author}/{article}/')); + $routes->add('comments', new Route('/{author}/{article}/comments')); + $routes->add('host', new Route('/{article}', array(), array(), array(), '{author}.example.com')); + $routes->add('scheme', new Route('/{author}', array(), array('_scheme' => 'https'))); + $routes->add('unrelated', new Route('/about')); + + $generator = $this->getGenerator($routes, array('host' => 'example.com', 'pathInfo' => '/fabien/symfony-is-great/')); + + $this->assertSame('comments', $generator->generate('comments', + array('author' =>'fabien', 'article' => 'symfony-is-great'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('comments?page=2', $generator->generate('comments', + array('author' =>'fabien', 'article' => 'symfony-is-great', 'page' => 2), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('../twig-is-great/', $generator->generate('article', + array('author' =>'fabien', 'article' => 'twig-is-great'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('../../bernhard/forms-are-great/', $generator->generate('article', + array('author' =>'bernhard', 'article' => 'forms-are-great'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('//bernhard.example.com/app.php/forms-are-great', $generator->generate('host', + array('author' =>'bernhard', 'article' => 'forms-are-great'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('https://example.com/app.php/bernhard', $generator->generate('scheme', + array('author' =>'bernhard'), UrlGeneratorInterface::RELATIVE_PATH) + ); + $this->assertSame('../../about', $generator->generate('unrelated', + array(), UrlGeneratorInterface::RELATIVE_PATH) + ); + } + + /** + * @dataProvider provideRelativePaths + */ + public function testGetRelativePath($sourcePath, $targetPath, $expectedPath) + { + $this->assertSame($expectedPath, UrlGenerator::getRelativePath($sourcePath, $targetPath)); + } + + public function provideRelativePaths() + { + return array( + array( + '/same/dir/', + '/same/dir/', + '' + ), + array( + '/same/file', + '/same/file', + '' + ), + array( + '/', + '/file', + 'file' + ), + array( + '/', + '/dir/file', + 'dir/file' + ), + array( + '/dir/file.html', + '/dir/different-file.html', + 'different-file.html' + ), + array( + '/same/dir/extra-file', + '/same/dir/', + './' + ), + array( + '/parent/dir/', + '/parent/', + '../' + ), + array( + '/parent/dir/extra-file', + '/parent/', + '../' + ), + array( + '/a/b/', + '/x/y/z/', + '../../x/y/z/' + ), + array( + '/a/b/c/d/e', + '/a/c/d', + '../../../c/d' + ), + array( + '/a/b/c//', + '/a/b/c/', + '../' + ), + array( + '/a/b/c/', + '/a/b/c//', + './/' + ), + array( + '/root/a/b/c/', + '/root/x/b/c/', + '../../../x/b/c/' + ), + array( + '/a/b/c/d/', + '/a', + '../../../../a' + ), + array( + '/special-chars/sp%20ce/1€/mäh/e=mc²', + '/special-chars/sp%20ce/1€/<µ>/e=mc²', + '../<µ>/e=mc²' + ), + array( + 'not-rooted', + 'dir/file', + 'dir/file' + ), + array( + '//dir/', + '', + '../../' + ), + array( + '/dir/', + '/dir/file:with-colon', + './file:with-colon' + ), + array( + '/dir/', + '/dir/subdir/file:with-colon', + 'subdir/file:with-colon' + ), + array( + '/dir/', + '/dir/:subdir/', + './:subdir/' + ), + ); + } + + protected function getGenerator(RouteCollection $routes, array $parameters = array(), $logger = null) + { + $context = new RequestContext('/app.php'); + foreach ($parameters as $key => $value) { + $method = 'set'.$key; + $context->$method($value); + } + $generator = new UrlGenerator($routes, $context, $logger); + + return $generator; + } + + protected function getRoutes($name, Route $route) + { + $routes = new RouteCollection(); + $routes->add($name, $route); + + return $routes; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/AbstractAnnotationLoaderTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/AbstractAnnotationLoaderTest.php new file mode 100644 index 0000000..c927ae4 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/AbstractAnnotationLoaderTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +abstract class AbstractAnnotationLoaderTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Doctrine\\Common\\Version')) { + $this->markTestSkipped('Doctrine is not available.'); + } + } + + public function getReader() + { + return $this->getMockBuilder('Doctrine\Common\Annotations\Reader') + ->disableOriginalConstructor() + ->getMock() + ; + } + + public function getClassLoader($reader) + { + return $this->getMockBuilder('Symfony\Component\Routing\Loader\AnnotationClassLoader') + ->setConstructorArgs(array($reader)) + ->getMockForAbstractClass() + ; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php new file mode 100644 index 0000000..5b7325c --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use Symfony\Component\Routing\Annotation\Route; + +class AnnotationClassLoaderTest extends AbstractAnnotationLoaderTest +{ + protected $loader; + + protected function setUp() + { + parent::setUp(); + + $this->reader = $this->getReader(); + $this->loader = $this->getClassLoader($this->reader); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLoadMissingClass() + { + $this->loader->load('MissingClass'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testLoadAbstractClass() + { + $this->loader->load('Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\AbstractClass'); + } + + /** + * @dataProvider provideTestSupportsChecksResource + */ + public function testSupportsChecksResource($resource, $expectedSupports) + { + $this->assertSame($expectedSupports, $this->loader->supports($resource), '->supports() returns true if the resource is loadable'); + } + + public function provideTestSupportsChecksResource() + { + return array( + array('class', true), + array('\fully\qualified\class\name', true), + array('namespaced\class\without\leading\slash', true), + array('ÿClassWithLegalSpecialCharacters', true), + array('5', false), + array('foo.foo', false), + array(null, false), + ); + } + + public function testSupportsChecksTypeIfSpecified() + { + $this->assertTrue($this->loader->supports('class', 'annotation'), '->supports() checks the resource type if specified'); + $this->assertFalse($this->loader->supports('class', 'foo'), '->supports() checks the resource type if specified'); + } + + public function getLoadTests() + { + return array( + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('name' => 'route1'), + array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3') + ), + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('name' => 'route1', 'defaults' => array('arg2' => 'foo')), + array('arg2' => 'defaultValue2', 'arg3' => 'defaultValue3') + ), + array( + 'Symfony\Component\Routing\Tests\Fixtures\AnnotatedClasses\BarClass', + array('name' => 'route1', 'defaults' => array('arg2' => 'foobar')), + array('arg2' => false, 'arg3' => 'defaultValue3') + ), + ); + } + + /** + * @dataProvider getLoadTests + */ + public function testLoad($className, $routeDatas = array(), $methodArgs = array()) + { + $routeDatas = array_replace(array( + 'name' => 'route', + 'path' => '/', + 'requirements' => array(), + 'options' => array(), + 'defaults' => array(), + 'schemes' => array(), + 'methods' => array(), + ), $routeDatas); + + $this->reader + ->expects($this->once()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array($this->getAnnotatedRoute($routeDatas)))) + ; + $routeCollection = $this->loader->load($className); + $route = $routeCollection->get($routeDatas['name']); + + $this->assertSame($routeDatas['path'], $route->getPath(), '->load preserves path annotation'); + $this->assertSame($routeDatas['requirements'],$route->getRequirements(), '->load preserves requirements annotation'); + $this->assertCount(0, array_intersect($route->getOptions(), $routeDatas['options']), '->load preserves options annotation'); + $this->assertSame(array_replace($methodArgs, $routeDatas['defaults']), $route->getDefaults(), '->load preserves defaults annotation'); + } + + private function getAnnotatedRoute($datas) + { + return new Route($datas); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/AnnotationDirectoryLoaderTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/AnnotationDirectoryLoaderTest.php new file mode 100644 index 0000000..29126ba --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/AnnotationDirectoryLoaderTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader; +use Symfony\Component\Config\FileLocator; + +class AnnotationDirectoryLoaderTest extends AbstractAnnotationLoaderTest +{ + protected $loader; + protected $reader; + + protected function setUp() + { + parent::setUp(); + + $this->reader = $this->getReader(); + $this->loader = new AnnotationDirectoryLoader(new FileLocator(), $this->getClassLoader($this->reader)); + } + + public function testLoad() + { + $this->reader->expects($this->exactly(2))->method('getClassAnnotation'); + + $this->reader + ->expects($this->any()) + ->method('getMethodAnnotations') + ->will($this->returnValue(array())) + ; + + $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses'); + } + + public function testSupports() + { + $fixturesDir = __DIR__.'/../Fixtures'; + + $this->assertTrue($this->loader->supports($fixturesDir), '->supports() returns true if the resource is loadable'); + $this->assertFalse($this->loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($this->loader->supports($fixturesDir, 'annotation'), '->supports() checks the resource type if specified'); + $this->assertFalse($this->loader->supports($fixturesDir, 'foo'), '->supports() checks the resource type if specified'); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/AnnotationFileLoaderTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/AnnotationFileLoaderTest.php new file mode 100644 index 0000000..f0a8a0e --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/AnnotationFileLoaderTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use Symfony\Component\Routing\Loader\AnnotationFileLoader; +use Symfony\Component\Config\FileLocator; + +class AnnotationFileLoaderTest extends AbstractAnnotationLoaderTest +{ + protected $loader; + protected $reader; + + protected function setUp() + { + parent::setUp(); + + $this->reader = $this->getReader(); + $this->loader = new AnnotationFileLoader(new FileLocator(), $this->getClassLoader($this->reader)); + } + + public function testLoad() + { + $this->reader->expects($this->once())->method('getClassAnnotation'); + + $this->loader->load(__DIR__.'/../Fixtures/AnnotatedClasses/FooClass.php'); + } + + public function testSupports() + { + $fixture = __DIR__.'/../Fixtures/annotated.php'; + + $this->assertTrue($this->loader->supports($fixture), '->supports() returns true if the resource is loadable'); + $this->assertFalse($this->loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($this->loader->supports($fixture, 'annotation'), '->supports() checks the resource type if specified'); + $this->assertFalse($this->loader->supports($fixture, 'foo'), '->supports() checks the resource type if specified'); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php new file mode 100644 index 0000000..64d1b08 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use Symfony\Component\Routing\Loader\ClosureLoader; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +class ClosureLoaderTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\Config\FileLocator')) { + $this->markTestSkipped('The "Config" component is not available'); + } + } + + public function testSupports() + { + $loader = new ClosureLoader(); + + $closure = function () {}; + + $this->assertTrue($loader->supports($closure), '->supports() returns true if the resource is loadable'); + $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($loader->supports($closure, 'closure'), '->supports() checks the resource type if specified'); + $this->assertFalse($loader->supports($closure, 'foo'), '->supports() checks the resource type if specified'); + } + + public function testLoad() + { + $loader = new ClosureLoader(); + + $route = new Route('/'); + $routes = $loader->load(function () use ($route) { + $routes = new RouteCollection(); + + $routes->add('foo', $route); + + return $routes; + }); + + $this->assertEquals($route, $routes->get('foo'), '->load() loads a \Closure resource'); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php new file mode 100644 index 0000000..18b166f --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Loader\PhpFileLoader; + +class PhpFileLoaderTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\Config\FileLocator')) { + $this->markTestSkipped('The "Config" component is not available'); + } + } + + public function testSupports() + { + $loader = new PhpFileLoader($this->getMock('Symfony\Component\Config\FileLocator')); + + $this->assertTrue($loader->supports('foo.php'), '->supports() returns true if the resource is loadable'); + $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($loader->supports('foo.php', 'php'), '->supports() checks the resource type if specified'); + $this->assertFalse($loader->supports('foo.php', 'foo'), '->supports() checks the resource type if specified'); + } + + public function testLoadWithRoute() + { + $loader = new PhpFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validpattern.php'); + $routes = $routeCollection->all(); + + $this->assertCount(2, $routes, 'Two routes are loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + foreach ($routes as $route) { + $this->assertSame('/blog/{slug}', $route->getPath()); + $this->assertSame('MyBlogBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('{locale}.example.com', $route->getHost()); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods()); + $this->assertEquals(array('https'), $route->getSchemes()); + } + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php new file mode 100644 index 0000000..9f038c1 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Loader\XmlFileLoader; +use Symfony\Component\Routing\Tests\Fixtures\CustomXmlFileLoader; + +class XmlFileLoaderTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\Config\FileLocator')) { + $this->markTestSkipped('The "Config" component is not available'); + } + } + + public function testSupports() + { + $loader = new XmlFileLoader($this->getMock('Symfony\Component\Config\FileLocator')); + + $this->assertTrue($loader->supports('foo.xml'), '->supports() returns true if the resource is loadable'); + $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($loader->supports('foo.xml', 'xml'), '->supports() checks the resource type if specified'); + $this->assertFalse($loader->supports('foo.xml', 'foo'), '->supports() checks the resource type if specified'); + } + + public function testLoadWithRoute() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validpattern.xml'); + $routes = $routeCollection->all(); + + $this->assertCount(2, $routes, 'Two routes are loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + foreach ($routes as $route) { + $this->assertSame('/blog/{slug}', $route->getPath()); + $this->assertSame('{locale}.example.com', $route->getHost()); + $this->assertSame('MyBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('\w+', $route->getRequirement('locale')); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods()); + $this->assertEquals(array('https'), $route->getSchemes()); + } + } + + public function testLoadWithNamespacePrefix() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('namespaceprefix.xml'); + + $this->assertCount(1, $routeCollection->all(), 'One route is loaded'); + + $route = $routeCollection->get('blog_show'); + $this->assertSame('/blog/{slug}', $route->getPath()); + $this->assertSame('{_locale}.example.com', $route->getHost()); + $this->assertSame('MyBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('\w+', $route->getRequirement('slug')); + $this->assertSame('en|fr|de', $route->getRequirement('_locale')); + $this->assertSame(null, $route->getDefault('slug')); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + } + + public function testLoadWithImport() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validresource.xml'); + $routes = $routeCollection->all(); + + $this->assertCount(2, $routes, 'Two routes are loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + foreach ($routes as $route) { + $this->assertSame('/{foo}/blog/{slug}', $route->getPath()); + $this->assertSame('123', $route->getDefault('foo')); + $this->assertSame('\d+', $route->getRequirement('foo')); + $this->assertSame('bar', $route->getOption('foo')); + $this->assertSame('', $route->getHost()); + } + } + + /** + * @expectedException \InvalidArgumentException + * @dataProvider getPathsToInvalidFiles + */ + public function testLoadThrowsExceptionWithInvalidFile($filePath) + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $loader->load($filePath); + } + + /** + * @expectedException \InvalidArgumentException + * @dataProvider getPathsToInvalidFiles + */ + public function testLoadThrowsExceptionWithInvalidFileEvenWithoutSchemaValidation($filePath) + { + $loader = new CustomXmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $loader->load($filePath); + } + + public function getPathsToInvalidFiles() + { + return array(array('nonvalidnode.xml'), array('nonvalidroute.xml'), array('nonvalid.xml'), array('missing_id.xml'), array('missing_path.xml')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Document types are not allowed. + */ + public function testDocTypeIsNotAllowed() + { + $loader = new XmlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $loader->load('withdoctype.xml'); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php new file mode 100644 index 0000000..a3e934c --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Loader; + +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Routing\Loader\YamlFileLoader; +use Symfony\Component\Config\Resource\FileResource; + +class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\Config\FileLocator')) { + $this->markTestSkipped('The "Config" component is not available'); + } + + if (!class_exists('Symfony\Component\Yaml\Yaml')) { + $this->markTestSkipped('The "Yaml" component is not available'); + } + } + + public function testSupports() + { + $loader = new YamlFileLoader($this->getMock('Symfony\Component\Config\FileLocator')); + + $this->assertTrue($loader->supports('foo.yml'), '->supports() returns true if the resource is loadable'); + $this->assertFalse($loader->supports('foo.foo'), '->supports() returns true if the resource is loadable'); + + $this->assertTrue($loader->supports('foo.yml', 'yaml'), '->supports() checks the resource type if specified'); + $this->assertFalse($loader->supports('foo.yml', 'foo'), '->supports() checks the resource type if specified'); + } + + public function testLoadDoesNothingIfEmpty() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $collection = $loader->load('empty.yml'); + + $this->assertEquals(array(), $collection->all()); + $this->assertEquals(array(new FileResource(realpath(__DIR__.'/../Fixtures/empty.yml'))), $collection->getResources()); + } + + /** + * @expectedException \InvalidArgumentException + * @dataProvider getPathsToInvalidFiles + */ + public function testLoadThrowsExceptionWithInvalidFile($filePath) + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $loader->load($filePath); + } + + public function getPathsToInvalidFiles() + { + return array(array('nonvalid.yml'), array('nonvalid2.yml'), array('incomplete.yml'), array('nonvalidkeys.yml'), array('nonesense_resource_plus_path.yml'), array('nonesense_type_without_resource.yml')); + } + + public function testLoadSpecialRouteName() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('special_route_name.yml'); + $route = $routeCollection->get('#$péß^a|'); + + $this->assertInstanceOf('Symfony\Component\Routing\Route', $route); + $this->assertSame('/true', $route->getPath()); + } + + public function testLoadWithRoute() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validpattern.yml'); + $routes = $routeCollection->all(); + + $this->assertCount(2, $routes, 'Two routes are loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + foreach ($routes as $route) { + $this->assertSame('/blog/{slug}', $route->getPath()); + $this->assertSame('{locale}.example.com', $route->getHost()); + $this->assertSame('MyBundle:Blog:show', $route->getDefault('_controller')); + $this->assertSame('\w+', $route->getRequirement('locale')); + $this->assertSame('RouteCompiler', $route->getOption('compiler_class')); + $this->assertEquals(array('GET', 'POST', 'PUT', 'OPTIONS'), $route->getMethods()); + $this->assertEquals(array('https'), $route->getSchemes()); + } + } + + public function testLoadWithResource() + { + $loader = new YamlFileLoader(new FileLocator(array(__DIR__.'/../Fixtures'))); + $routeCollection = $loader->load('validresource.yml'); + $routes = $routeCollection->all(); + + $this->assertCount(2, $routes, 'Two routes are loaded'); + $this->assertContainsOnly('Symfony\Component\Routing\Route', $routes); + + foreach ($routes as $route) { + $this->assertSame('/{foo}/blog/{slug}', $route->getPath()); + $this->assertSame('123', $route->getDefault('foo')); + $this->assertSame('\d+', $route->getRequirement('foo')); + $this->assertSame('bar', $route->getOption('foo')); + $this->assertSame('', $route->getHost()); + } + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/ApacheUrlMatcherTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/ApacheUrlMatcherTest.php new file mode 100644 index 0000000..2810cba --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/ApacheUrlMatcherTest.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Matcher\ApacheUrlMatcher; + +class ApacheUrlMatcherTest extends \PHPUnit_Framework_TestCase +{ + protected $server; + + protected function setUp() + { + $this->server = $_SERVER; + } + + protected function tearDown() + { + $_SERVER = $this->server; + } + + /** + * @dataProvider getMatchData + */ + public function testMatch($name, $pathinfo, $server, $expect) + { + $collection = new RouteCollection(); + $context = new RequestContext(); + $matcher = new ApacheUrlMatcher($collection, $context); + + $_SERVER = $server; + + $result = $matcher->match($pathinfo, $server); + $this->assertSame(var_export($expect, true), var_export($result, true)); + } + + public function getMatchData() + { + return array( + array( + 'Simple route', + '/hello/world', + array( + '_ROUTING_route' => 'hello', + '_ROUTING_param__controller' => 'AcmeBundle:Default:index', + '_ROUTING_param_name' => 'world', + ), + array( + '_controller' => 'AcmeBundle:Default:index', + 'name' => 'world', + '_route' => 'hello', + ), + ), + array( + 'Route with params and defaults', + '/hello/hugo', + array( + '_ROUTING_route' => 'hello', + '_ROUTING_param__controller' => 'AcmeBundle:Default:index', + '_ROUTING_param_name' => 'hugo', + '_ROUTING_default_name' => 'world', + ), + array( + 'name' => 'hugo', + '_controller' => 'AcmeBundle:Default:index', + '_route' => 'hello', + ), + ), + array( + 'Route with defaults only', + '/hello', + array( + '_ROUTING_route' => 'hello', + '_ROUTING_param__controller' => 'AcmeBundle:Default:index', + '_ROUTING_default_name' => 'world', + ), + array( + 'name' => 'world', + '_controller' => 'AcmeBundle:Default:index', + '_route' => 'hello', + ), + ), + array( + 'Redirect with many ignored attributes', + '/legacy/{cat1}/{cat2}/{id}.html', + array( + '_ROUTING_route' => 'product_view', + '_ROUTING_param__controller' => 'FrameworkBundle:Redirect:redirect', + '_ROUTING_default_ignoreAttributes[0]' => 'attr_a', + '_ROUTING_default_ignoreAttributes[1]' => 'attr_b', + ), + array( + 'ignoreAttributes' => array('attr_a', 'attr_b'), + '_controller' => 'FrameworkBundle:Redirect:redirect', + '_route' => 'product_view', + ) + ), + array( + 'REDIRECT_ envs', + '/hello/world', + array( + 'REDIRECT__ROUTING_route' => 'hello', + 'REDIRECT__ROUTING_param__controller' => 'AcmeBundle:Default:index', + 'REDIRECT__ROUTING_param_name' => 'world', + ), + array( + '_controller' => 'AcmeBundle:Default:index', + 'name' => 'world', + '_route' => 'hello', + ), + ), + array( + 'REDIRECT_REDIRECT_ envs', + '/hello/world', + array( + 'REDIRECT_REDIRECT__ROUTING_route' => 'hello', + 'REDIRECT_REDIRECT__ROUTING_param__controller' => 'AcmeBundle:Default:index', + 'REDIRECT_REDIRECT__ROUTING_param_name' => 'world', + ), + array( + '_controller' => 'AcmeBundle:Default:index', + 'name' => 'world', + '_route' => 'hello', + ), + ), + array( + 'REDIRECT_REDIRECT_ envs', + '/hello/world', + array( + 'REDIRECT_REDIRECT__ROUTING_route' => 'hello', + 'REDIRECT_REDIRECT__ROUTING_param__controller' => 'AcmeBundle:Default:index', + 'REDIRECT_REDIRECT__ROUTING_param_name' => 'world', + ), + array( + '_controller' => 'AcmeBundle:Default:index', + 'name' => 'world', + '_route' => 'hello', + ), + ) + ); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/Dumper/ApacheMatcherDumperTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/Dumper/ApacheMatcherDumperTest.php new file mode 100644 index 0000000..72bee71 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/Dumper/ApacheMatcherDumperTest.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher\Dumper; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Matcher\Dumper\ApacheMatcherDumper; + +class ApacheMatcherDumperTest extends \PHPUnit_Framework_TestCase +{ + protected static $fixturesPath; + + public static function setUpBeforeClass() + { + self::$fixturesPath = realpath(__DIR__.'/../../Fixtures/'); + } + + public function testDump() + { + $dumper = new ApacheMatcherDumper($this->getRouteCollection()); + + $this->assertStringEqualsFile(self::$fixturesPath.'/dumper/url_matcher1.apache', $dumper->dump(), '->dump() dumps basic routes to the correct apache format.'); + } + + /** + * @dataProvider provideEscapeFixtures + */ + public function testEscapePattern($src, $dest, $char, $with, $message) + { + $r = new \ReflectionMethod(new ApacheMatcherDumper($this->getRouteCollection()), 'escape'); + $r->setAccessible(true); + $this->assertEquals($dest, $r->invoke(null, $src, $char, $with), $message); + } + + public function provideEscapeFixtures() + { + return array( + array('foo', 'foo', ' ', '-', 'Preserve string that should not be escaped'), + array('fo-o', 'fo-o', ' ', '-', 'Preserve string that should not be escaped'), + array('fo o', 'fo- o', ' ', '-', 'Escape special characters'), + array('fo-- o', 'fo--- o', ' ', '-', 'Escape special characters'), + array('fo- o', 'fo- o', ' ', '-', 'Do not escape already escaped string'), + ); + } + + public function testEscapeScriptName() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + $dumper = new ApacheMatcherDumper($collection); + $this->assertStringEqualsFile(self::$fixturesPath.'/dumper/url_matcher2.apache', $dumper->dump(array('script_name' => 'ap p_d\ ev.php'))); + } + + private function getRouteCollection() + { + $collection = new RouteCollection(); + + // defaults and requirements + $collection->add('foo', new Route( + '/foo/{bar}', + array('def' => 'test'), + array('bar' => 'baz|symfony') + )); + // defaults parameters in pattern + $collection->add('foobar', new Route( + '/foo/{bar}', + array('bar' => 'toto') + )); + // method requirement + $collection->add('bar', new Route( + '/bar/{foo}', + array(), + array('_method' => 'GET|head') + )); + // method requirement (again) + $collection->add('baragain', new Route( + '/baragain/{foo}', + array(), + array('_method' => 'get|post') + )); + // simple + $collection->add('baz', new Route( + '/test/baz' + )); + // simple with extension + $collection->add('baz2', new Route( + '/test/baz.html' + )); + // trailing slash + $collection->add('baz3', new Route( + '/test/baz3/' + )); + // trailing slash with variable + $collection->add('baz4', new Route( + '/test/{foo}/' + )); + // trailing slash and safe method + $collection->add('baz5', new Route( + '/test/{foo}/', + array(), + array('_method' => 'get') + )); + // trailing slash and unsafe method + $collection->add('baz5unsafe', new Route( + '/testunsafe/{foo}/', + array(), + array('_method' => 'post') + )); + // complex + $collection->add('baz6', new Route( + '/test/baz', + array('foo' => 'bar baz') + )); + // space in path + $collection->add('baz7', new Route( + '/te st/baz' + )); + // space preceded with \ in path + $collection->add('baz8', new Route( + '/te\\ st/baz' + )); + // space preceded with \ in requirement + $collection->add('baz9', new Route( + '/test/{baz}', + array(), + array( + 'baz' => 'te\\\\ st', + ) + )); + + $collection1 = new RouteCollection(); + + $route1 = new Route('/route1', array(), array(), array(), 'a.example.com'); + $collection1->add('route1', $route1); + + $collection2 = new RouteCollection(); + + $route2 = new Route('/route2', array(), array(), array(), 'a.example.com'); + $collection2->add('route2', $route2); + + $route3 = new Route('/route3', array(), array(), array(), 'b.example.com'); + $collection2->add('route3', $route3); + + $collection2->addPrefix('/c2'); + $collection1->addCollection($collection2); + + $route4 = new Route('/route4', array(), array(), array(), 'a.example.com'); + $collection1->add('route4', $route4); + + $route5 = new Route('/route5', array(), array(), array(), 'c.example.com'); + $collection1->add('route5', $route5); + + $route6 = new Route('/route6', array(), array(), array(), null); + $collection1->add('route6', $route6); + + $collection->addCollection($collection1); + + // host and variables + + $collection1 = new RouteCollection(); + + $route11 = new Route('/route11', array(), array(), array(), '{var1}.example.com'); + $collection1->add('route11', $route11); + + $route12 = new Route('/route12', array('var1' => 'val'), array(), array(), '{var1}.example.com'); + $collection1->add('route12', $route12); + + $route13 = new Route('/route13/{name}', array(), array(), array(), '{var1}.example.com'); + $collection1->add('route13', $route13); + + $route14 = new Route('/route14/{name}', array('var1' => 'val'), array(), array(), '{var1}.example.com'); + $collection1->add('route14', $route14); + + $route15 = new Route('/route15/{name}', array(), array(), array(), 'c.example.com'); + $collection1->add('route15', $route15); + + $route16 = new Route('/route16/{name}', array('var1' => 'val'), array(), array(), null); + $collection1->add('route16', $route16); + + $route17 = new Route('/route17', array(), array(), array(), null); + $collection1->add('route17', $route17); + + $collection->addCollection($collection1); + + return $collection; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperCollectionTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperCollectionTest.php new file mode 100644 index 0000000..54b3772 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperCollectionTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Test\Matcher\Dumper; + +use Symfony\Component\Routing\Matcher\Dumper\DumperCollection; + +class DumperCollectionTest extends \PHPUnit_Framework_TestCase +{ + public function testGetRoot() + { + $a = new DumperCollection(); + + $b = new DumperCollection(); + $a->add($b); + + $c = new DumperCollection(); + $b->add($c); + + $d = new DumperCollection(); + $c->add($d); + + $this->assertSame($a, $c->getRoot()); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperPrefixCollectionTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperPrefixCollectionTest.php new file mode 100644 index 0000000..7b4565c --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/Dumper/DumperPrefixCollectionTest.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher\Dumper; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\Matcher\Dumper\DumperPrefixCollection; +use Symfony\Component\Routing\Matcher\Dumper\DumperRoute; +use Symfony\Component\Routing\Matcher\Dumper\DumperCollection; + +class DumperPrefixCollectionTest extends \PHPUnit_Framework_TestCase +{ + public function testAddPrefixRoute() + { + $coll = new DumperPrefixCollection; + $coll->setPrefix(''); + + $route = new DumperRoute('bar', new Route('/foo/bar')); + $coll = $coll->addPrefixRoute($route); + + $route = new DumperRoute('bar2', new Route('/foo/bar')); + $coll = $coll->addPrefixRoute($route); + + $route = new DumperRoute('qux', new Route('/foo/qux')); + $coll = $coll->addPrefixRoute($route); + + $route = new DumperRoute('bar3', new Route('/foo/bar')); + $coll = $coll->addPrefixRoute($route); + + $route = new DumperRoute('bar4', new Route('')); + $result = $coll->addPrefixRoute($route); + + $expect = <<<'EOF' + |-coll / + | |-coll /f + | | |-coll /fo + | | | |-coll /foo + | | | | |-coll /foo/ + | | | | | |-coll /foo/b + | | | | | | |-coll /foo/ba + | | | | | | | |-coll /foo/bar + | | | | | | | | |-route bar /foo/bar + | | | | | | | | |-route bar2 /foo/bar + | | | | | |-coll /foo/q + | | | | | | |-coll /foo/qu + | | | | | | | |-coll /foo/qux + | | | | | | | | |-route qux /foo/qux + | | | | | |-coll /foo/b + | | | | | | |-coll /foo/ba + | | | | | | | |-coll /foo/bar + | | | | | | | | |-route bar3 /foo/bar + | |-route bar4 / + +EOF; + + $this->assertSame($expect, $this->collectionToString($result->getRoot(), ' ')); + } + + public function testMergeSlashNodes() + { + $coll = new DumperPrefixCollection; + $coll->setPrefix(''); + + $route = new DumperRoute('bar', new Route('/foo/bar')); + $coll = $coll->addPrefixRoute($route); + + $route = new DumperRoute('bar2', new Route('/foo/bar')); + $coll = $coll->addPrefixRoute($route); + + $route = new DumperRoute('qux', new Route('/foo/qux')); + $coll = $coll->addPrefixRoute($route); + + $route = new DumperRoute('bar3', new Route('/foo/bar')); + $result = $coll->addPrefixRoute($route); + + $result->getRoot()->mergeSlashNodes(); + + $expect = <<<'EOF' + |-coll /f + | |-coll /fo + | | |-coll /foo + | | | |-coll /foo/b + | | | | |-coll /foo/ba + | | | | | |-coll /foo/bar + | | | | | | |-route bar /foo/bar + | | | | | | |-route bar2 /foo/bar + | | | |-coll /foo/q + | | | | |-coll /foo/qu + | | | | | |-coll /foo/qux + | | | | | | |-route qux /foo/qux + | | | |-coll /foo/b + | | | | |-coll /foo/ba + | | | | | |-coll /foo/bar + | | | | | | |-route bar3 /foo/bar + +EOF; + + $this->assertSame($expect, $this->collectionToString($result->getRoot(), ' ')); + } + + private function collectionToString(DumperCollection $collection, $prefix) + { + $string = ''; + foreach ($collection as $route) { + if ($route instanceof DumperCollection) { + $string .= sprintf("%s|-coll %s\n", $prefix, $route->getPrefix()); + $string .= $this->collectionToString($route, $prefix.'| '); + } else { + $string .= sprintf("%s|-route %s %s\n", $prefix, $route->getName(), $route->getRoute()->getPath()); + } + } + + return $string; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php new file mode 100644 index 0000000..542ede8 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php @@ -0,0 +1,261 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher\Dumper; + +use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +class PhpMatcherDumperTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \LogicException + */ + public function testDumpWhenSchemeIsUsedWithoutAProperDumper() + { + $collection = new RouteCollection(); + $collection->add('secure', new Route( + '/secure', + array(), + array('_scheme' => 'https') + )); + $dumper = new PhpMatcherDumper($collection); + $dumper->dump(); + } + + /** + * @dataProvider getRouteCollections + */ + public function testDump(RouteCollection $collection, $fixture, $options = array()) + { + $basePath = __DIR__.'/../../Fixtures/dumper/'; + + $dumper = new PhpMatcherDumper($collection); + $this->assertStringEqualsFile($basePath.$fixture, $dumper->dump($options), '->dump() correctly dumps routes as optimized PHP code.'); + } + + public function getRouteCollections() + { + /* test case 1 */ + + $collection = new RouteCollection(); + + $collection->add('overridden', new Route('/overridden')); + + // defaults and requirements + $collection->add('foo', new Route( + '/foo/{bar}', + array('def' => 'test'), + array('bar' => 'baz|symfony') + )); + // method requirement + $collection->add('bar', new Route( + '/bar/{foo}', + array(), + array('_method' => 'GET|head') + )); + // GET method requirement automatically adds HEAD as valid + $collection->add('barhead', new Route( + '/barhead/{foo}', + array(), + array('_method' => 'GET') + )); + // simple + $collection->add('baz', new Route( + '/test/baz' + )); + // simple with extension + $collection->add('baz2', new Route( + '/test/baz.html' + )); + // trailing slash + $collection->add('baz3', new Route( + '/test/baz3/' + )); + // trailing slash with variable + $collection->add('baz4', new Route( + '/test/{foo}/' + )); + // trailing slash and method + $collection->add('baz5', new Route( + '/test/{foo}/', + array(), + array('_method' => 'post') + )); + // complex name + $collection->add('baz.baz6', new Route( + '/test/{foo}/', + array(), + array('_method' => 'put') + )); + // defaults without variable + $collection->add('foofoo', new Route( + '/foofoo', + array('def' => 'test') + )); + // pattern with quotes + $collection->add('quoter', new Route( + '/{quoter}', + array(), + array('quoter' => '[\']+') + )); + // space in pattern + $collection->add('space', new Route( + '/spa ce' + )); + + // prefixes + $collection1 = new RouteCollection(); + $collection1->add('overridden', new Route('/overridden1')); + $collection1->add('foo1', new Route('/{foo}')); + $collection1->add('bar1', new Route('/{bar}')); + $collection1->addPrefix('/b\'b'); + $collection2 = new RouteCollection(); + $collection2->addCollection($collection1); + $collection2->add('overridden', new Route('/{var}', array(), array('var' => '.*'))); + $collection1 = new RouteCollection(); + $collection1->add('foo2', new Route('/{foo1}')); + $collection1->add('bar2', new Route('/{bar1}')); + $collection1->addPrefix('/b\'b'); + $collection2->addCollection($collection1); + $collection2->addPrefix('/a'); + $collection->addCollection($collection2); + + // overridden through addCollection() and multiple sub-collections with no own prefix + $collection1 = new RouteCollection(); + $collection1->add('overridden2', new Route('/old')); + $collection1->add('helloWorld', new Route('/hello/{who}', array('who' => 'World!'))); + $collection2 = new RouteCollection(); + $collection3 = new RouteCollection(); + $collection3->add('overridden2', new Route('/new')); + $collection3->add('hey', new Route('/hey/')); + $collection2->addCollection($collection3); + $collection1->addCollection($collection2); + $collection1->addPrefix('/multi'); + $collection->addCollection($collection1); + + // "dynamic" prefix + $collection1 = new RouteCollection(); + $collection1->add('foo3', new Route('/{foo}')); + $collection1->add('bar3', new Route('/{bar}')); + $collection1->addPrefix('/b'); + $collection1->addPrefix('{_locale}'); + $collection->addCollection($collection1); + + // route between collections + $collection->add('ababa', new Route('/ababa')); + + // collection with static prefix but only one route + $collection1 = new RouteCollection(); + $collection1->add('foo4', new Route('/{foo}')); + $collection1->addPrefix('/aba'); + $collection->addCollection($collection1); + + // prefix and host + + $collection1 = new RouteCollection(); + + $route1 = new Route('/route1', array(), array(), array(), 'a.example.com'); + $collection1->add('route1', $route1); + + $collection2 = new RouteCollection(); + + $route2 = new Route('/c2/route2', array(), array(), array(), 'a.example.com'); + $collection1->add('route2', $route2); + + $route3 = new Route('/c2/route3', array(), array(), array(), 'b.example.com'); + $collection1->add('route3', $route3); + + $route4 = new Route('/route4', array(), array(), array(), 'a.example.com'); + $collection1->add('route4', $route4); + + $route5 = new Route('/route5', array(), array(), array(), 'c.example.com'); + $collection1->add('route5', $route5); + + $route6 = new Route('/route6', array(), array(), array(), null); + $collection1->add('route6', $route6); + + $collection->addCollection($collection1); + + // host and variables + + $collection1 = new RouteCollection(); + + $route11 = new Route('/route11', array(), array(), array(), '{var1}.example.com'); + $collection1->add('route11', $route11); + + $route12 = new Route('/route12', array('var1' => 'val'), array(), array(), '{var1}.example.com'); + $collection1->add('route12', $route12); + + $route13 = new Route('/route13/{name}', array(), array(), array(), '{var1}.example.com'); + $collection1->add('route13', $route13); + + $route14 = new Route('/route14/{name}', array('var1' => 'val'), array(), array(), '{var1}.example.com'); + $collection1->add('route14', $route14); + + $route15 = new Route('/route15/{name}', array(), array(), array(), 'c.example.com'); + $collection1->add('route15', $route15); + + $route16 = new Route('/route16/{name}', array('var1' => 'val'), array(), array(), null); + $collection1->add('route16', $route16); + + $route17 = new Route('/route17', array(), array(), array(), null); + $collection1->add('route17', $route17); + + $collection->addCollection($collection1); + + // multiple sub-collections with a single route and a prefix each + $collection1 = new RouteCollection(); + $collection1->add('a', new Route('/a...')); + $collection2 = new RouteCollection(); + $collection2->add('b', new Route('/{var}')); + $collection3 = new RouteCollection(); + $collection3->add('c', new Route('/{var}')); + $collection3->addPrefix('/c'); + $collection2->addCollection($collection3); + $collection2->addPrefix('/b'); + $collection1->addCollection($collection2); + $collection1->addPrefix('/a'); + $collection->addCollection($collection1); + + /* test case 2 */ + + $redirectCollection = clone $collection; + + // force HTTPS redirection + $redirectCollection->add('secure', new Route( + '/secure', + array(), + array('_scheme' => 'https') + )); + + // force HTTP redirection + $redirectCollection->add('nonsecure', new Route( + '/nonsecure', + array(), + array('_scheme' => 'http') + )); + + /* test case 3 */ + + $rootprefixCollection = new RouteCollection(); + $rootprefixCollection->add('static', new Route('/test')); + $rootprefixCollection->add('dynamic', new Route('/{var}')); + $rootprefixCollection->addPrefix('rootprefix'); + + return array( + array($collection, 'url_matcher1.php', array()), + array($redirectCollection, 'url_matcher2.php', array('base_class' => 'Symfony\Component\Routing\Tests\Fixtures\RedirectableUrlMatcher')), + array($rootprefixCollection, 'url_matcher3.php', array()) + ); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php new file mode 100644 index 0000000..2ad4fc8 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/RedirectableUrlMatcherTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; + +class RedirectableUrlMatcherTest extends \PHPUnit_Framework_TestCase +{ + public function testRedirectWhenNoSlash() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/')); + + $matcher = $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($coll, new RequestContext())); + $matcher->expects($this->once())->method('redirect'); + $matcher->match('/foo'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testRedirectWhenNoSlashForNonSafeMethod() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/')); + + $context = new RequestContext(); + $context->setMethod('POST'); + $matcher = $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($coll, $context)); + $matcher->match('/foo'); + } + + public function testSchemeRedirect() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array('_scheme' => 'https'))); + + $matcher = $this->getMockForAbstractClass('Symfony\Component\Routing\Matcher\RedirectableUrlMatcher', array($coll, new RequestContext())); + $matcher + ->expects($this->once()) + ->method('redirect') + ->with('/foo', 'foo', 'https') + ->will($this->returnValue(array('_route' => 'foo'))) + ; + $matcher->match('/foo'); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/TraceableUrlMatcherTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/TraceableUrlMatcherTest.php new file mode 100644 index 0000000..86d8d95 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/TraceableUrlMatcherTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Matcher\TraceableUrlMatcher; + +class TraceableUrlMatcherTest extends \PHPUnit_Framework_TestCase +{ + public function test() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array('_method' => 'POST'))); + $coll->add('bar', new Route('/bar/{id}', array(), array('id' => '\d+'))); + $coll->add('bar1', new Route('/bar/{name}', array(), array('id' => '\w+', '_method' => 'POST'))); + $coll->add('bar2', new Route('/foo', array(), array(), array(), 'baz')); + $coll->add('bar3', new Route('/foo1', array(), array(), array(), 'baz')); + + $context = new RequestContext(); + $context->setHost('baz'); + + $matcher = new TraceableUrlMatcher($coll, $context); + $traces = $matcher->getTraces('/babar'); + $this->assertEquals(array(0, 0, 0, 0, 0), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/foo'); + $this->assertEquals(array(1, 0, 0, 2), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/bar/12'); + $this->assertEquals(array(0, 2), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/bar/dd'); + $this->assertEquals(array(0, 1, 1, 0, 0), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/foo1'); + $this->assertEquals(array(0, 0, 0, 0, 2), $this->getLevels($traces)); + + $context->setMethod('POST'); + $traces = $matcher->getTraces('/foo'); + $this->assertEquals(array(2), $this->getLevels($traces)); + + $traces = $matcher->getTraces('/bar/dd'); + $this->assertEquals(array(0, 1, 2), $this->getLevels($traces)); + } + + public function getLevels($traces) + { + $levels = array(); + foreach ($traces as $trace) { + $levels[] = $trace['level']; + } + + return $levels; + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php new file mode 100644 index 0000000..8a1428f --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/Matcher/UrlMatcherTest.php @@ -0,0 +1,383 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests\Matcher; + +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RequestContext; + +class UrlMatcherTest extends \PHPUnit_Framework_TestCase +{ + public function testNoMethodSoAllowed() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo')); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher->match('/foo'); + } + + public function testMethodNotAllowed() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array('_method' => 'post'))); + + $matcher = new UrlMatcher($coll, new RequestContext()); + + try { + $matcher->match('/foo'); + $this->fail(); + } catch (MethodNotAllowedException $e) { + $this->assertEquals(array('POST'), $e->getAllowedMethods()); + } + } + + public function testHeadAllowedWhenRequirementContainsGet() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array('_method' => 'get'))); + + $matcher = new UrlMatcher($coll, new RequestContext('', 'head')); + $matcher->match('/foo'); + } + + public function testMethodNotAllowedAggregatesAllowedMethods() + { + $coll = new RouteCollection(); + $coll->add('foo1', new Route('/foo', array(), array('_method' => 'post'))); + $coll->add('foo2', new Route('/foo', array(), array('_method' => 'put|delete'))); + + $matcher = new UrlMatcher($coll, new RequestContext()); + + try { + $matcher->match('/foo'); + $this->fail(); + } catch (MethodNotAllowedException $e) { + $this->assertEquals(array('POST', 'PUT', 'DELETE'), $e->getAllowedMethods()); + } + } + + public function testMatch() + { + // test the patterns are matched and parameters are returned + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo/{bar}')); + $matcher = new UrlMatcher($collection, new RequestContext()); + try { + $matcher->match('/no-match'); + $this->fail(); + } catch (ResourceNotFoundException $e) {} + $this->assertEquals(array('_route' => 'foo', 'bar' => 'baz'), $matcher->match('/foo/baz')); + + // test that defaults are merged + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo/{bar}', array('def' => 'test'))); + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'foo', 'bar' => 'baz', 'def' => 'test'), $matcher->match('/foo/baz')); + + // test that route "method" is ignored if no method is given in the context + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo', array(), array('_method' => 'GET|head'))); + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertInternalType('array', $matcher->match('/foo')); + + // route does not match with POST method context + $matcher = new UrlMatcher($collection, new RequestContext('', 'post')); + try { + $matcher->match('/foo'); + $this->fail(); + } catch (MethodNotAllowedException $e) {} + + // route does match with GET or HEAD method context + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertInternalType('array', $matcher->match('/foo')); + $matcher = new UrlMatcher($collection, new RequestContext('', 'head')); + $this->assertInternalType('array', $matcher->match('/foo')); + + // route with an optional variable as the first segment + $collection = new RouteCollection(); + $collection->add('bar', new Route('/{bar}/foo', array('bar' => 'bar'), array('bar' => 'foo|bar'))); + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'bar', 'bar' => 'bar'), $matcher->match('/bar/foo')); + $this->assertEquals(array('_route' => 'bar', 'bar' => 'foo'), $matcher->match('/foo/foo')); + + $collection = new RouteCollection(); + $collection->add('bar', new Route('/{bar}', array('bar' => 'bar'), array('bar' => 'foo|bar'))); + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'bar', 'bar' => 'foo'), $matcher->match('/foo')); + $this->assertEquals(array('_route' => 'bar', 'bar' => 'bar'), $matcher->match('/')); + + // route with only optional variables + $collection = new RouteCollection(); + $collection->add('bar', new Route('/{foo}/{bar}', array('foo' => 'foo', 'bar' => 'bar'), array())); + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'foo', 'bar' => 'bar'), $matcher->match('/')); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'a', 'bar' => 'bar'), $matcher->match('/a')); + $this->assertEquals(array('_route' => 'bar', 'foo' => 'a', 'bar' => 'b'), $matcher->match('/a/b')); + } + + public function testMatchWithPrefixes() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{foo}')); + $collection->addPrefix('/b'); + $collection->addPrefix('/a'); + + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'foo', 'foo' => 'foo'), $matcher->match('/a/b/foo')); + } + + public function testMatchWithDynamicPrefix() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{foo}')); + $collection->addPrefix('/b'); + $collection->addPrefix('/{_locale}'); + + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_locale' => 'fr', '_route' => 'foo', 'foo' => 'foo'), $matcher->match('/fr/b/foo')); + } + + public function testMatchSpecialRouteName() + { + $collection = new RouteCollection(); + $collection->add('$péß^a|', new Route('/bar')); + + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => '$péß^a|'), $matcher->match('/bar')); + } + + public function testMatchNonAlpha() + { + $collection = new RouteCollection(); + $chars = '!"$%éà &\'()*+,./:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ\\[]^_`abcdefghijklmnopqrstuvwxyz{|}~-'; + $collection->add('foo', new Route('/{foo}/bar', array(), array('foo' => '['.preg_quote($chars).']+'))); + + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'foo', 'foo' => $chars), $matcher->match('/'.rawurlencode($chars).'/bar')); + $this->assertEquals(array('_route' => 'foo', 'foo' => $chars), $matcher->match('/'.strtr($chars, array('%' => '%25')).'/bar')); + } + + public function testMatchWithDotMetacharacterInRequirements() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{foo}/bar', array(), array('foo' => '.+'))); + + $matcher = new UrlMatcher($collection, new RequestContext()); + $this->assertEquals(array('_route' => 'foo', 'foo' => "\n"), $matcher->match('/'.urlencode("\n").'/bar'), 'linefeed character is matched'); + } + + public function testMatchOverriddenRoute() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('foo', new Route('/foo1')); + + $collection->addCollection($collection1); + + $matcher = new UrlMatcher($collection, new RequestContext()); + + $this->assertEquals(array('_route' => 'foo'), $matcher->match('/foo1')); + $this->setExpectedException('Symfony\Component\Routing\Exception\ResourceNotFoundException'); + $this->assertEquals(array(), $matcher->match('/foo')); + } + + public function testMatchRegression() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}')); + $coll->add('bar', new Route('/foo/bar/{foo}')); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $this->assertEquals(array('foo' => 'bar', '_route' => 'bar'), $matcher->match('/foo/bar/bar')); + + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{bar}')); + $matcher = new UrlMatcher($collection, new RequestContext()); + try { + $matcher->match('/'); + $this->fail(); + } catch (ResourceNotFoundException $e) { + } + } + + public function testDefaultRequirementForOptionalVariables() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{page}.{_format}', array('page' => 'index', '_format' => 'html'))); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $this->assertEquals(array('page' => 'my-page', '_format' => 'xml', '_route' => 'test'), $matcher->match('/my-page.xml')); + } + + public function testMatchingIsEager() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{foo}-{bar}-', array(), array('foo' => '.+', 'bar' => '.+'))); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $this->assertEquals(array('foo' => 'text1-text2-text3', 'bar' => 'text4', '_route' => 'test'), $matcher->match('/text1-text2-text3-text4-')); + } + + public function testAdjacentVariables() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{w}{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => 'y|Y'))); + + $matcher = new UrlMatcher($coll, new RequestContext()); + // 'w' eagerly matches as much as possible and the other variables match the remaining chars. + // This also shows that the variables w-z must all exclude the separating char (the dot '.' in this case) by default requirement. + // Otherwise they would also consume '.xml' and _format would never match as it's an optional variable. + $this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'Y', 'z' => 'Z','_format' => 'xml', '_route' => 'test'), $matcher->match('/wwwwwxYZ.xml')); + // As 'y' has custom requirement and can only be of value 'y|Y', it will leave 'ZZZ' to variable z. + // So with carefully chosen requirements adjacent variables, can be useful. + $this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'y', 'z' => 'ZZZ','_format' => 'html', '_route' => 'test'), $matcher->match('/wwwwwxyZZZ')); + // z and _format are optional. + $this->assertEquals(array('w' => 'wwwww', 'x' => 'x', 'y' => 'y', 'z' => 'default-z','_format' => 'html', '_route' => 'test'), $matcher->match('/wwwwwxy')); + + $this->setExpectedException('Symfony\Component\Routing\Exception\ResourceNotFoundException'); + $matcher->match('/wxy.html'); + } + + public function testOptionalVariableWithNoRealSeparator() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/get{what}', array('what' => 'All'))); + $matcher = new UrlMatcher($coll, new RequestContext()); + + $this->assertEquals(array('what' => 'All', '_route' => 'test'), $matcher->match('/get')); + $this->assertEquals(array('what' => 'Sites', '_route' => 'test'), $matcher->match('/getSites')); + + // Usually the character in front of an optional parameter can be left out, e.g. with pattern '/get/{what}' just '/get' would match. + // But here the 't' in 'get' is not a separating character, so it makes no sense to match without it. + $this->setExpectedException('Symfony\Component\Routing\Exception\ResourceNotFoundException'); + $matcher->match('/ge'); + } + + public function testRequiredVariableWithNoRealSeparator() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/get{what}Suffix')); + $matcher = new UrlMatcher($coll, new RequestContext()); + + $this->assertEquals(array('what' => 'Sites', '_route' => 'test'), $matcher->match('/getSitesSuffix')); + } + + public function testDefaultRequirementOfVariable() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{page}.{_format}')); + $matcher = new UrlMatcher($coll, new RequestContext()); + + $this->assertEquals(array('page' => 'index', '_format' => 'mobile.html', '_route' => 'test'), $matcher->match('/index.mobile.html')); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testDefaultRequirementOfVariableDisallowsSlash() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{page}.{_format}')); + $matcher = new UrlMatcher($coll, new RequestContext()); + + $matcher->match('/index.sl/ash'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testDefaultRequirementOfVariableDisallowsNextSeparator() + { + $coll = new RouteCollection(); + $coll->add('test', new Route('/{page}.{_format}', array(), array('_format' => 'html|xml'))); + $matcher = new UrlMatcher($coll, new RequestContext()); + + $matcher->match('/do.t.html'); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testSchemeRequirement() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo', array(), array('_scheme' => 'https'))); + $matcher = new UrlMatcher($coll, new RequestContext()); + $matcher->match('/foo'); + } + + public function testDecodeOnce() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}')); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $this->assertEquals(array('foo' => 'bar%23', '_route' => 'foo'), $matcher->match('/foo/bar%2523')); + } + + public function testCannotRelyOnPrefix() + { + $coll = new RouteCollection(); + + $subColl = new RouteCollection(); + $subColl->add('bar', new Route('/bar')); + $subColl->addPrefix('/prefix'); + // overwrite the pattern, so the prefix is not valid anymore for this route in the collection + $subColl->get('bar')->setPattern('/new'); + + $coll->addCollection($subColl); + + $matcher = new UrlMatcher($coll, new RequestContext()); + $this->assertEquals(array('_route' => 'bar'), $matcher->match('/new')); + } + + public function testWithHost() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}', array(), array(), array(), '{locale}.example.com')); + + $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $this->assertEquals(array('foo' => 'bar', '_route' => 'foo', 'locale' => 'en'), $matcher->match('/foo/bar')); + } + + public function testWithHostOnRouteCollection() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}')); + $coll->add('bar', new Route('/bar/{foo}', array(), array(), array(), '{locale}.example.net')); + $coll->setHost('{locale}.example.com'); + + $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $this->assertEquals(array('foo' => 'bar', '_route' => 'foo', 'locale' => 'en'), $matcher->match('/foo/bar')); + + $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'en.example.com')); + $this->assertEquals(array('foo' => 'bar', '_route' => 'bar', 'locale' => 'en'), $matcher->match('/bar/bar')); + } + + /** + * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + */ + public function testWithOutHostHostDoesNotMatch() + { + $coll = new RouteCollection(); + $coll->add('foo', new Route('/foo/{foo}', array(), array(), array(), '{locale}.example.com')); + + $matcher = new UrlMatcher($coll, new RequestContext('', 'GET', 'example.com')); + $matcher->match('/foo/bar'); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/RouteCollectionTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/RouteCollectionTest.php new file mode 100644 index 0000000..3d78adf --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/RouteCollectionTest.php @@ -0,0 +1,255 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\Route; +use Symfony\Component\Config\Resource\FileResource; + +class RouteCollectionTest extends \PHPUnit_Framework_TestCase +{ + public function testRoute() + { + $collection = new RouteCollection(); + $route = new Route('/foo'); + $collection->add('foo', $route); + $this->assertEquals(array('foo' => $route), $collection->all(), '->add() adds a route'); + $this->assertEquals($route, $collection->get('foo'), '->get() returns a route by name'); + $this->assertNull($collection->get('bar'), '->get() returns null if a route does not exist'); + } + + public function testOverriddenRoute() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + $collection->add('foo', new Route('/foo1')); + + $this->assertEquals('/foo1', $collection->get('foo')->getPath()); + } + + public function testDeepOverriddenRoute() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('foo', new Route('/foo1')); + + $collection2 = new RouteCollection(); + $collection2->add('foo', new Route('/foo2')); + + $collection1->addCollection($collection2); + $collection->addCollection($collection1); + + $this->assertEquals('/foo2', $collection1->get('foo')->getPath()); + $this->assertEquals('/foo2', $collection->get('foo')->getPath()); + } + + public function testIterator() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('bar', $bar = new Route('/bar')); + $collection1->add('foo', $foo = new Route('/foo-new')); + $collection->addCollection($collection1); + $collection->add('last', $last = new Route('/last')); + + $this->assertInstanceOf('\ArrayIterator', $collection->getIterator()); + $this->assertSame(array('bar' => $bar, 'foo' => $foo, 'last' => $last), $collection->getIterator()->getArrayCopy()); + } + + public function testCount() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('bar', new Route('/bar')); + $collection->addCollection($collection1); + + $this->assertCount(2, $collection); + } + + public function testAddCollection() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('bar', $bar = new Route('/bar')); + $collection1->add('foo', $foo = new Route('/foo-new')); + + $collection2 = new RouteCollection(); + $collection2->add('grandchild', $grandchild = new Route('/grandchild')); + + $collection1->addCollection($collection2); + $collection->addCollection($collection1); + $collection->add('last', $last = new Route('/last')); + + $this->assertSame(array('bar' => $bar, 'foo' => $foo, 'grandchild' => $grandchild, 'last' => $last), $collection->all(), + '->addCollection() imports routes of another collection, overrides if necessary and adds them at the end'); + } + + public function testAddCollectionWithResources() + { + if (!class_exists('Symfony\Component\Config\Resource\FileResource')) { + $this->markTestSkipped('The "Config" component is not available'); + } + + $collection = new RouteCollection(); + $collection->addResource($foo = new FileResource(__DIR__.'/Fixtures/foo.xml')); + $collection1 = new RouteCollection(); + $collection1->addResource($foo1 = new FileResource(__DIR__.'/Fixtures/foo1.xml')); + $collection->addCollection($collection1); + $this->assertEquals(array($foo, $foo1), $collection->getResources(), '->addCollection() merges resources'); + } + + public function testAddDefaultsAndRequirementsAndOptions() + { + $collection = new RouteCollection(); + $collection->add('foo', new Route('/{placeholder}')); + $collection1 = new RouteCollection(); + $collection1->add('bar', new Route('/{placeholder}', + array('_controller' => 'fixed', 'placeholder' => 'default'), array('placeholder' => '.+'), array('option' => 'value')) + ); + $collection->addCollection($collection1); + + $collection->addDefaults(array('placeholder' => 'new-default')); + $this->assertEquals(array('placeholder' => 'new-default'), $collection->get('foo')->getDefaults(), '->addDefaults() adds defaults to all routes'); + $this->assertEquals(array('_controller' => 'fixed', 'placeholder' => 'new-default'), $collection->get('bar')->getDefaults(), + '->addDefaults() adds defaults to all routes and overwrites existing ones'); + + $collection->addRequirements(array('placeholder' => '\d+')); + $this->assertEquals(array('placeholder' => '\d+'), $collection->get('foo')->getRequirements(), '->addRequirements() adds requirements to all routes'); + $this->assertEquals(array('placeholder' => '\d+'), $collection->get('bar')->getRequirements(), + '->addRequirements() adds requirements to all routes and overwrites existing ones'); + + $collection->addOptions(array('option' => 'new-value')); + $this->assertEquals( + array('option' => 'new-value', 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler'), + $collection->get('bar')->getOptions(), '->addOptions() adds options to all routes and overwrites existing ones' + ); + } + + public function testAddPrefix() + { + $collection = new RouteCollection(); + $collection->add('foo', $foo = new Route('/foo')); + $collection2 = new RouteCollection(); + $collection2->add('bar', $bar = new Route('/bar')); + $collection->addCollection($collection2); + $collection->addPrefix(' / '); + $this->assertSame('/foo', $collection->get('foo')->getPattern(), '->addPrefix() trims the prefix and a single slash has no effect'); + $collection->addPrefix('/{admin}', array('admin' => 'admin'), array('admin' => '\d+')); + $this->assertEquals('/{admin}/foo', $collection->get('foo')->getPath(), '->addPrefix() adds a prefix to all routes'); + $this->assertEquals('/{admin}/bar', $collection->get('bar')->getPath(), '->addPrefix() adds a prefix to all routes'); + $this->assertEquals(array('admin' => 'admin'), $collection->get('foo')->getDefaults(), '->addPrefix() adds defaults to all routes'); + $this->assertEquals(array('admin' => 'admin'), $collection->get('bar')->getDefaults(), '->addPrefix() adds defaults to all routes'); + $this->assertEquals(array('admin' => '\d+'), $collection->get('foo')->getRequirements(), '->addPrefix() adds requirements to all routes'); + $this->assertEquals(array('admin' => '\d+'), $collection->get('bar')->getRequirements(), '->addPrefix() adds requirements to all routes'); + $collection->addPrefix('0'); + $this->assertEquals('/0/{admin}/foo', $collection->get('foo')->getPattern(), '->addPrefix() ensures a prefix must start with a slash and must not end with a slash'); + $collection->addPrefix('/ /'); + $this->assertSame('/ /0/{admin}/foo', $collection->get('foo')->getPath(), '->addPrefix() can handle spaces if desired'); + $this->assertSame('/ /0/{admin}/bar', $collection->get('bar')->getPath(), 'the route pattern of an added collection is in synch with the added prefix'); + } + + public function testAddPrefixOverridesDefaultsAndRequirements() + { + $collection = new RouteCollection(); + $collection->add('foo', $foo = new Route('/foo')); + $collection->add('bar', $bar = new Route('/bar', array(), array('_scheme' => 'http'))); + $collection->addPrefix('/admin', array(), array('_scheme' => 'https')); + + $this->assertEquals('https', $collection->get('foo')->getRequirement('_scheme'), '->addPrefix() overrides existing requirements'); + $this->assertEquals('https', $collection->get('bar')->getRequirement('_scheme'), '->addPrefix() overrides existing requirements'); + } + + public function testResource() + { + if (!class_exists('Symfony\Component\Config\Resource\FileResource')) { + $this->markTestSkipped('The "Config" component is not available'); + } + + $collection = new RouteCollection(); + $collection->addResource($foo = new FileResource(__DIR__.'/Fixtures/foo.xml')); + $collection->addResource($bar = new FileResource(__DIR__.'/Fixtures/bar.xml')); + $collection->addResource(new FileResource(__DIR__.'/Fixtures/foo.xml')); + + $this->assertEquals(array($foo, $bar), $collection->getResources(), + '->addResource() adds a resource and getResources() only returns unique ones by comparing the string representation'); + } + + public function testUniqueRouteWithGivenName() + { + $collection1 = new RouteCollection(); + $collection1->add('foo', new Route('/old')); + $collection2 = new RouteCollection(); + $collection3 = new RouteCollection(); + $collection3->add('foo', $new = new Route('/new')); + + $collection2->addCollection($collection3); + $collection1->addCollection($collection2); + + $this->assertSame($new, $collection1->get('foo'), '->get() returns new route that overrode previous one'); + // size of 1 because collection1 contains /new but not /old anymore + $this->assertCount(1, $collection1->getIterator(), '->addCollection() removes previous routes when adding new routes with the same name'); + } + + public function testGet() + { + $collection1 = new RouteCollection(); + $collection1->add('a', $a = new Route('/a')); + $collection2 = new RouteCollection(); + $collection2->add('b', $b = new Route('/b')); + $collection1->addCollection($collection2); + $collection1->add('$péß^a|', $c = new Route('/special')); + + $this->assertSame($b, $collection1->get('b'), '->get() returns correct route in child collection'); + $this->assertSame($c, $collection1->get('$péß^a|'), '->get() can handle special characters'); + $this->assertNull($collection2->get('a'), '->get() does not return the route defined in parent collection'); + $this->assertNull($collection1->get('non-existent'), '->get() returns null when route does not exist'); + $this->assertNull($collection1->get(0), '->get() does not disclose internal child RouteCollection'); + } + + public function testRemove() + { + $collection = new RouteCollection(); + $collection->add('foo', $foo = new Route('/foo')); + + $collection1 = new RouteCollection(); + $collection1->add('bar', $bar = new Route('/bar')); + $collection->addCollection($collection1); + $collection->add('last', $last = new Route('/last')); + + $collection->remove('foo'); + $this->assertSame(array('bar' => $bar, 'last' => $last), $collection->all(), '->remove() can remove a single route'); + $collection->remove(array('bar', 'last')); + $this->assertSame(array(), $collection->all(), '->remove() accepts an array and can remove multiple routes at once'); + } + + public function testSetHost() + { + $collection = new RouteCollection(); + $routea = new Route('/a'); + $routeb = new Route('/b', array(), array(), array(), '{locale}.example.net'); + $collection->add('a', $routea); + $collection->add('b', $routeb); + + $collection->setHost('{locale}.example.com'); + + $this->assertEquals('{locale}.example.com', $routea->getHost()); + $this->assertEquals('{locale}.example.com', $routeb->getHost()); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/RouteCompilerTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/RouteCompilerTest.php new file mode 100644 index 0000000..d663ae9 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/RouteCompilerTest.php @@ -0,0 +1,253 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use Symfony\Component\Routing\Route; + +class RouteCompilerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider provideCompileData + */ + public function testCompile($name, $arguments, $prefix, $regex, $variables, $tokens) + { + $r = new \ReflectionClass('Symfony\\Component\\Routing\\Route'); + $route = $r->newInstanceArgs($arguments); + + $compiled = $route->compile(); + $this->assertEquals($prefix, $compiled->getStaticPrefix(), $name.' (static prefix)'); + $this->assertEquals($regex, $compiled->getRegex(), $name.' (regex)'); + $this->assertEquals($variables, $compiled->getVariables(), $name.' (variables)'); + $this->assertEquals($tokens, $compiled->getTokens(), $name.' (tokens)'); + } + + public function provideCompileData() + { + return array( + array( + 'Static route', + array('/foo'), + '/foo', '#^/foo$#s', array(), array( + array('text', '/foo'), + )), + + array( + 'Route with a variable', + array('/foo/{bar}'), + '/foo', '#^/foo/(?P[^/]++)$#s', array('bar'), array( + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + )), + + array( + 'Route with a variable that has a default value', + array('/foo/{bar}', array('bar' => 'bar')), + '/foo', '#^/foo(?:/(?P[^/]++))?$#s', array('bar'), array( + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + )), + + array( + 'Route with several variables', + array('/foo/{bar}/{foobar}'), + '/foo', '#^/foo/(?P[^/]++)/(?P[^/]++)$#s', array('bar', 'foobar'), array( + array('variable', '/', '[^/]++', 'foobar'), + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + )), + + array( + 'Route with several variables that have default values', + array('/foo/{bar}/{foobar}', array('bar' => 'bar', 'foobar' => '')), + '/foo', '#^/foo(?:/(?P[^/]++)(?:/(?P[^/]++))?)?$#s', array('bar', 'foobar'), array( + array('variable', '/', '[^/]++', 'foobar'), + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + )), + + array( + 'Route with several variables but some of them have no default values', + array('/foo/{bar}/{foobar}', array('bar' => 'bar')), + '/foo', '#^/foo/(?P[^/]++)/(?P[^/]++)$#s', array('bar', 'foobar'), array( + array('variable', '/', '[^/]++', 'foobar'), + array('variable', '/', '[^/]++', 'bar'), + array('text', '/foo'), + )), + + array( + 'Route with an optional variable as the first segment', + array('/{bar}', array('bar' => 'bar')), + '', '#^/(?P[^/]++)?$#s', array('bar'), array( + array('variable', '/', '[^/]++', 'bar'), + )), + + array( + 'Route with a requirement of 0', + array('/{bar}', array('bar' => null), array('bar' => '0')), + '', '#^/(?P0)?$#s', array('bar'), array( + array('variable', '/', '0', 'bar'), + )), + + array( + 'Route with an optional variable as the first segment with requirements', + array('/{bar}', array('bar' => 'bar'), array('bar' => '(foo|bar)')), + '', '#^/(?P(foo|bar))?$#s', array('bar'), array( + array('variable', '/', '(foo|bar)', 'bar'), + )), + + array( + 'Route with only optional variables', + array('/{foo}/{bar}', array('foo' => 'foo', 'bar' => 'bar')), + '', '#^/(?P[^/]++)?(?:/(?P[^/]++))?$#s', array('foo', 'bar'), array( + array('variable', '/', '[^/]++', 'bar'), + array('variable', '/', '[^/]++', 'foo'), + )), + + array( + 'Route with a variable in last position', + array('/foo-{bar}'), + '/foo', '#^/foo\-(?P[^/]++)$#s', array('bar'), array( + array('variable', '-', '[^/]++', 'bar'), + array('text', '/foo'), + )), + + array( + 'Route with nested placeholders', + array('/{static{var}static}'), + '/{static', '#^/\{static(?P[^/]+)static\}$#s', array('var'), array( + array('text', 'static}'), + array('variable', '', '[^/]+', 'var'), + array('text', '/{static'), + )), + + array( + 'Route without separator between variables', + array('/{w}{x}{y}{z}.{_format}', array('z' => 'default-z', '_format' => 'html'), array('y' => '(y|Y)')), + '', '#^/(?P[^/\.]+)(?P[^/\.]+)(?P(y|Y))(?:(?P[^/\.]++)(?:\.(?P<_format>[^/]++))?)?$#s', array('w', 'x', 'y', 'z', '_format'), array( + array('variable', '.', '[^/]++', '_format'), + array('variable', '', '[^/\.]++', 'z'), + array('variable', '', '(y|Y)', 'y'), + array('variable', '', '[^/\.]+', 'x'), + array('variable', '/', '[^/\.]+', 'w'), + )), + + array( + 'Route with a format', + array('/foo/{bar}.{_format}'), + '/foo', '#^/foo/(?P[^/\.]++)\.(?P<_format>[^/]++)$#s', array('bar', '_format'), array( + array('variable', '.', '[^/]++', '_format'), + array('variable', '/', '[^/\.]++', 'bar'), + array('text', '/foo'), + )), + ); + } + + /** + * @expectedException \LogicException + */ + public function testRouteWithSameVariableTwice() + { + $route = new Route('/{name}/{name}'); + + $compiled = $route->compile(); + } + + /** + * @dataProvider getNumericVariableNames + * @expectedException \DomainException + */ + public function testRouteWithNumericVariableName($name) + { + $route = new Route('/{'. $name.'}'); + $route->compile(); + } + + public function getNumericVariableNames() + { + return array( + array('09'), + array('123'), + array('1e2') + ); + } + + /** + * @dataProvider provideCompileWithHostData + */ + public function testCompileWithHost($name, $arguments, $prefix, $regex, $variables, $pathVariables, $tokens, $hostRegex, $hostVariables, $hostTokens) + { + $r = new \ReflectionClass('Symfony\\Component\\Routing\\Route'); + $route = $r->newInstanceArgs($arguments); + + $compiled = $route->compile(); + $this->assertEquals($prefix, $compiled->getStaticPrefix(), $name.' (static prefix)'); + $this->assertEquals($regex, str_replace(array("\n", ' '), '', $compiled->getRegex()), $name.' (regex)'); + $this->assertEquals($variables, $compiled->getVariables(), $name.' (variables)'); + $this->assertEquals($pathVariables, $compiled->getPathVariables(), $name.' (path variables)'); + $this->assertEquals($tokens, $compiled->getTokens(), $name.' (tokens)'); + $this->assertEquals($hostRegex, str_replace(array("\n", ' '), '', $compiled->getHostRegex()), $name.' (host regex)'); + $this->assertEquals($hostVariables, $compiled->getHostVariables(), $name.' (host variables)'); + $this->assertEquals($hostTokens, $compiled->getHostTokens(), $name.' (host tokens)'); + } + + public function provideCompileWithHostData() + { + return array( + array( + 'Route with host pattern', + array('/hello', array(), array(), array(), 'www.example.com'), + '/hello', '#^/hello$#s', array(), array(), array( + array('text', '/hello'), + ), + '#^www\.example\.com$#s', array(), array( + array('text', 'www.example.com'), + ), + ), + array( + 'Route with host pattern and some variables', + array('/hello/{name}', array(), array(), array(), 'www.example.{tld}'), + '/hello', '#^/hello/(?P[^/]++)$#s', array('tld', 'name'), array('name'), array( + array('variable', '/', '[^/]++', 'name'), + array('text', '/hello'), + ), + '#^www\.example\.(?P[^\.]++)$#s', array('tld'), array( + array('variable', '.', '[^\.]++', 'tld'), + array('text', 'www.example'), + ), + ), + array( + 'Route with variable at beginning of host', + array('/hello', array(), array(), array(), '{locale}.example.{tld}'), + '/hello', '#^/hello$#s', array('locale', 'tld'), array(), array( + array('text', '/hello'), + ), + '#^(?P[^\.]++)\.example\.(?P[^\.]++)$#s', array('locale', 'tld'), array( + array('variable', '.', '[^\.]++', 'tld'), + array('text', '.example'), + array('variable', '', '[^\.]++', 'locale'), + ), + ), + array( + 'Route with host variables that has a default value', + array('/hello', array('locale' => 'a', 'tld' => 'b'), array(), array(), '{locale}.example.{tld}'), + '/hello', '#^/hello$#s', array('locale', 'tld'), array(), array( + array('text', '/hello'), + ), + '#^(?P[^\.]++)\.example\.(?P[^\.]++)$#s', array('locale', 'tld'), array( + array('variable', '.', '[^\.]++', 'tld'), + array('text', '.example'), + array('variable', '', '[^\.]++', 'locale'), + ), + ), + ); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/RouteTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/RouteTest.php new file mode 100644 index 0000000..31f1066 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/RouteTest.php @@ -0,0 +1,192 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use Symfony\Component\Routing\Route; + +class RouteTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructor() + { + $route = new Route('/{foo}', array('foo' => 'bar'), array('foo' => '\d+'), array('foo' => 'bar'), '{locale}.example.com'); + $this->assertEquals('/{foo}', $route->getPath(), '__construct() takes a path as its first argument'); + $this->assertEquals(array('foo' => 'bar'), $route->getDefaults(), '__construct() takes defaults as its second argument'); + $this->assertEquals(array('foo' => '\d+'), $route->getRequirements(), '__construct() takes requirements as its third argument'); + $this->assertEquals('bar', $route->getOption('foo'), '__construct() takes options as its fourth argument'); + $this->assertEquals('{locale}.example.com', $route->getHost(), '__construct() takes a host pattern as its fifth argument'); + + $route = new Route('/', array(), array(), array(), '', array('Https'), array('POST', 'put')); + $this->assertEquals(array('https'), $route->getSchemes(), '__construct() takes schemes as its sixth argument and lowercases it'); + $this->assertEquals(array('POST', 'PUT'), $route->getMethods(), '__construct() takes methods as its seventh argument and uppercases it'); + + $route = new Route('/', array(), array(), array(), '', 'Https', 'Post'); + $this->assertEquals(array('https'), $route->getSchemes(), '__construct() takes a single scheme as its sixth argument'); + $this->assertEquals(array('POST'), $route->getMethods(), '__construct() takes a single method as its seventh argument'); + } + + public function testPath() + { + $route = new Route('/{foo}'); + $route->setPath('/{bar}'); + $this->assertEquals('/{bar}', $route->getPath(), '->setPath() sets the path'); + $route->setPath(''); + $this->assertEquals('/', $route->getPath(), '->setPath() adds a / at the beginning of the path if needed'); + $route->setPath('bar'); + $this->assertEquals('/bar', $route->getPath(), '->setPath() adds a / at the beginning of the path if needed'); + $this->assertEquals($route, $route->setPath(''), '->setPath() implements a fluent interface'); + $route->setPath('//path'); + $this->assertEquals('/path', $route->getPath(), '->setPath() does not allow two slahes "//" at the beginning of the path as it would be confused with a network path when generating the path from the route'); + } + + public function testOptions() + { + $route = new Route('/{foo}'); + $route->setOptions(array('foo' => 'bar')); + $this->assertEquals(array_merge(array( + 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler', + ), array('foo' => 'bar')), $route->getOptions(), '->setOptions() sets the options'); + $this->assertEquals($route, $route->setOptions(array()), '->setOptions() implements a fluent interface'); + + $route->setOptions(array('foo' => 'foo')); + $route->addOptions(array('bar' => 'bar')); + $this->assertEquals($route, $route->addOptions(array()), '->addOptions() implements a fluent interface'); + $this->assertEquals(array('foo' => 'foo', 'bar' => 'bar', 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler'), $route->getOptions(), '->addDefaults() keep previous defaults'); + } + + public function testDefaults() + { + $route = new Route('/{foo}'); + $route->setDefaults(array('foo' => 'bar')); + $this->assertEquals(array('foo' => 'bar'), $route->getDefaults(), '->setDefaults() sets the defaults'); + $this->assertEquals($route, $route->setDefaults(array()), '->setDefaults() implements a fluent interface'); + + $route->setDefault('foo', 'bar'); + $this->assertEquals('bar', $route->getDefault('foo'), '->setDefault() sets a default value'); + + $route->setDefault('foo2', 'bar2'); + $this->assertEquals('bar2', $route->getDefault('foo2'), '->getDefault() return the default value'); + $this->assertNull($route->getDefault('not_defined'), '->getDefault() return null if default value is not setted'); + + $route->setDefault('_controller', $closure = function () { return 'Hello'; }); + $this->assertEquals($closure, $route->getDefault('_controller'), '->setDefault() sets a default value'); + + $route->setDefaults(array('foo' => 'foo')); + $route->addDefaults(array('bar' => 'bar')); + $this->assertEquals($route, $route->addDefaults(array()), '->addDefaults() implements a fluent interface'); + $this->assertEquals(array('foo' => 'foo', 'bar' => 'bar'), $route->getDefaults(), '->addDefaults() keep previous defaults'); + } + + public function testRequirements() + { + $route = new Route('/{foo}'); + $route->setRequirements(array('foo' => '\d+')); + $this->assertEquals(array('foo' => '\d+'), $route->getRequirements(), '->setRequirements() sets the requirements'); + $this->assertEquals('\d+', $route->getRequirement('foo'), '->getRequirement() returns a requirement'); + $this->assertNull($route->getRequirement('bar'), '->getRequirement() returns null if a requirement is not defined'); + $route->setRequirements(array('foo' => '^\d+$')); + $this->assertEquals('\d+', $route->getRequirement('foo'), '->getRequirement() removes ^ and $ from the path'); + $this->assertEquals($route, $route->setRequirements(array()), '->setRequirements() implements a fluent interface'); + + $route->setRequirements(array('foo' => '\d+')); + $route->addRequirements(array('bar' => '\d+')); + $this->assertEquals($route, $route->addRequirements(array()), '->addRequirements() implements a fluent interface'); + $this->assertEquals(array('foo' => '\d+', 'bar' => '\d+'), $route->getRequirements(), '->addRequirement() keep previous requirements'); + } + + public function testRequirement() + { + $route = new Route('/{foo}'); + $route->setRequirement('foo', '^\d+$'); + $this->assertEquals('\d+', $route->getRequirement('foo'), '->setRequirement() removes ^ and $ from the path'); + } + + /** + * @dataProvider getInvalidRequirements + * @expectedException \InvalidArgumentException + */ + public function testSetInvalidRequirement($req) + { + $route = new Route('/{foo}'); + $route->setRequirement('foo', $req); + } + + public function getInvalidRequirements() + { + return array( + array(''), + array(array()), + array('^$'), + array('^'), + array('$') + ); + } + + public function testHost() + { + $route = new Route('/'); + $route->setHost('{locale}.example.net'); + $this->assertEquals('{locale}.example.net', $route->getHost(), '->setHost() sets the host pattern'); + } + + public function testScheme() + { + $route = new Route('/'); + $this->assertEquals(array(), $route->getSchemes(), 'schemes is initialized with array()'); + $route->setSchemes('hTTp'); + $this->assertEquals(array('http'), $route->getSchemes(), '->setSchemes() accepts a single scheme string and lowercases it'); + $route->setSchemes(array('HttpS', 'hTTp')); + $this->assertEquals(array('https', 'http'), $route->getSchemes(), '->setSchemes() accepts an array of schemes and lowercases them'); + } + + public function testSchemeIsBC() + { + $route = new Route('/'); + $route->setRequirement('_scheme', 'http|https'); + $this->assertEquals('http|https', $route->getRequirement('_scheme')); + $this->assertEquals(array('http', 'https'), $route->getSchemes()); + $route->setSchemes(array('hTTp')); + $this->assertEquals('http', $route->getRequirement('_scheme')); + $route->setSchemes(array()); + $this->assertNull($route->getRequirement('_scheme')); + } + + public function testMethod() + { + $route = new Route('/'); + $this->assertEquals(array(), $route->getMethods(), 'methods is initialized with array()'); + $route->setMethods('gEt'); + $this->assertEquals(array('GET'), $route->getMethods(), '->setMethods() accepts a single method string and uppercases it'); + $route->setMethods(array('gEt', 'PosT')); + $this->assertEquals(array('GET', 'POST'), $route->getMethods(), '->setMethods() accepts an array of methods and uppercases them'); + } + + public function testMethodIsBC() + { + $route = new Route('/'); + $route->setRequirement('_method', 'GET|POST'); + $this->assertEquals('GET|POST', $route->getRequirement('_method')); + $this->assertEquals(array('GET', 'POST'), $route->getMethods()); + $route->setMethods(array('gEt')); + $this->assertEquals('GET', $route->getRequirement('_method')); + $route->setMethods(array()); + $this->assertNull($route->getRequirement('_method')); + } + + public function testCompile() + { + $route = new Route('/{foo}'); + $this->assertInstanceOf('Symfony\Component\Routing\CompiledRoute', $compiled = $route->compile(), '->compile() returns a compiled route'); + $this->assertSame($compiled, $route->compile(), '->compile() only compiled the route once if unchanged'); + $route->setRequirement('foo', '.*'); + $this->assertNotSame($compiled, $route->compile(), '->compile() recompiles if the route was modified'); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/Tests/RouterTest.php b/vendor/symfony/routing/Symfony/Component/Routing/Tests/RouterTest.php new file mode 100644 index 0000000..a3c336e --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/Tests/RouterTest.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Tests; + +use Symfony\Component\Routing\Router; + +class RouterTest extends \PHPUnit_Framework_TestCase +{ + private $router = null; + + private $loader = null; + + protected function setUp() + { + $this->loader = $this->getMock('Symfony\Component\Config\Loader\LoaderInterface'); + $this->router = new Router($this->loader, 'routing.yml'); + } + + public function testSetOptionsWithSupportedOptions() + { + $this->router->setOptions(array( + 'cache_dir' => './cache', + 'debug' => true, + 'resource_type' => 'ResourceType' + )); + + $this->assertSame('./cache', $this->router->getOption('cache_dir')); + $this->assertTrue($this->router->getOption('debug')); + $this->assertSame('ResourceType', $this->router->getOption('resource_type')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The Router does not support the following options: "option_foo", "option_bar" + */ + public function testSetOptionsWithUnsupportedOptions() + { + $this->router->setOptions(array( + 'cache_dir' => './cache', + 'option_foo' => true, + 'option_bar' => 'baz', + 'resource_type' => 'ResourceType' + )); + } + + public function testSetOptionWithSupportedOption() + { + $this->router->setOption('cache_dir', './cache'); + + $this->assertSame('./cache', $this->router->getOption('cache_dir')); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The Router does not support the "option_foo" option + */ + public function testSetOptionWithUnsupportedOption() + { + $this->router->setOption('option_foo', true); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The Router does not support the "option_foo" option + */ + public function testGetOptionWithUnsupportedOption() + { + $this->router->getOption('option_foo', true); + } + + public function testThatRouteCollectionIsLoaded() + { + $this->router->setOption('resource_type', 'ResourceType'); + + $routeCollection = $this->getMock('Symfony\Component\Routing\RouteCollection'); + + $this->loader->expects($this->once()) + ->method('load')->with('routing.yml', 'ResourceType') + ->will($this->returnValue($routeCollection)); + + $this->assertSame($routeCollection, $this->router->getRouteCollection()); + } + + /** + * @dataProvider provideMatcherOptionsPreventingCaching + */ + public function testMatcherIsCreatedIfCacheIsNotConfigured($option) + { + $this->router->setOption($option, null); + + $this->loader->expects($this->once()) + ->method('load')->with('routing.yml', null) + ->will($this->returnValue($this->getMock('Symfony\Component\Routing\RouteCollection'))); + + $this->assertInstanceOf('Symfony\\Component\\Routing\\Matcher\\UrlMatcher', $this->router->getMatcher()); + + } + + public function provideMatcherOptionsPreventingCaching() + { + return array( + array('cache_dir'), + array('matcher_cache_class') + ); + } + + /** + * @dataProvider provideGeneratorOptionsPreventingCaching + */ + public function testGeneratorIsCreatedIfCacheIsNotConfigured($option) + { + $this->router->setOption($option, null); + + $this->loader->expects($this->once()) + ->method('load')->with('routing.yml', null) + ->will($this->returnValue($this->getMock('Symfony\Component\Routing\RouteCollection'))); + + $this->assertInstanceOf('Symfony\\Component\\Routing\\Generator\\UrlGenerator', $this->router->getGenerator()); + + } + + public function provideGeneratorOptionsPreventingCaching() + { + return array( + array('cache_dir'), + array('generator_cache_class') + ); + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/composer.json b/vendor/symfony/routing/Symfony/Component/Routing/composer.json new file mode 100644 index 0000000..9a737c6 --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/composer.json @@ -0,0 +1,42 @@ +{ + "name": "symfony/routing", + "type": "library", + "description": "Symfony Routing Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/config": "~2.2", + "symfony/yaml": "~2.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0" + }, + "suggest": { + "symfony/config": "", + "symfony/yaml": "", + "doctrine/common": "" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\Routing\\": "" } + }, + "target-dir": "Symfony/Component/Routing", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + } +} diff --git a/vendor/symfony/routing/Symfony/Component/Routing/phpunit.xml.dist b/vendor/symfony/routing/Symfony/Component/Routing/phpunit.xml.dist new file mode 100644 index 0000000..830066a --- /dev/null +++ b/vendor/symfony/routing/Symfony/Component/Routing/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./vendor + ./Tests + + + + diff --git a/vendor/symfony/translation/Symfony/Component/Translation/.gitignore b/vendor/symfony/translation/Symfony/Component/Translation/.gitignore new file mode 100644 index 0000000..c49a5d8 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/translation/Symfony/Component/Translation/CHANGELOG.md b/vendor/symfony/translation/Symfony/Component/Translation/CHANGELOG.md new file mode 100644 index 0000000..b8027ab --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/CHANGELOG.md @@ -0,0 +1,27 @@ +CHANGELOG +========= + +2.3.0 +----- + + * added classes to make operations on catalogues (like making a diff or a merge on 2 catalogues) + * added Translator::getFallbackLocales() + * deprecated Translator::setFallbackLocale() in favor of the new Translator::setFallbackLocales() method + +2.2.0 +----- + + * QtTranslationsLoader class renamed to QtFileLoader. QtTranslationsLoader is deprecated and will be removed in 2.3. + * [BC BREAK] uniformized the exception thrown by the load() method when an error occurs. The load() method now + throws Symfony\Component\Translation\Exception\NotFoundResourceException when a resource cannot be found + and Symfony\Component\Translation\Exception\InvalidResourceException when a resource is invalid. + * changed the exception class thrown by some load() methods from \RuntimeException to \InvalidArgumentException + (IcuDatFileLoader, IcuResFileLoader and QtFileLoader) + +2.1.0 +----- + + * added support for more than one fallback locale + * added support for extracting translation messages from templates (Twig and PHP) + * added dumpers for translation catalogs + * added support for QT, gettext, and ResourceBundles diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Catalogue/AbstractOperation.php b/vendor/symfony/translation/Symfony/Component/Translation/Catalogue/AbstractOperation.php new file mode 100644 index 0000000..062056b --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Catalogue/AbstractOperation.php @@ -0,0 +1,146 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Catalogue; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Base catalogues binary operation class. + * + * @author Jean-François Simon + */ +abstract class AbstractOperation implements OperationInterface +{ + /** + * @var MessageCatalogueInterface + */ + protected $source; + + /** + * @var MessageCatalogueInterface + */ + protected $target; + + /** + * @var MessageCatalogue + */ + protected $result; + + /** + * @var null|array + */ + private $domains; + + /** + * @var array + */ + protected $messages; + + /** + * @param MessageCatalogueInterface $source + * @param MessageCatalogueInterface $target + * + * @throws \LogicException + */ + public function __construct(MessageCatalogueInterface $source, MessageCatalogueInterface $target) + { + if ($source->getLocale() !== $target->getLocale()) { + throw new \LogicException('Operated catalogues must belong to the same locale.'); + } + + $this->source = $source; + $this->target = $target; + $this->result = new MessageCatalogue($source->getLocale()); + $this->domains = null; + $this->messages = array(); + } + + /** + * {@inheritdoc} + */ + public function getDomains() + { + if (null === $this->domains) { + $this->domains = array_values(array_unique(array_merge($this->source->getDomains(), $this->target->getDomains()))); + } + + return $this->domains; + } + + /** + * {@inheritdoc} + */ + public function getMessages($domain) + { + if (!in_array($domain, $this->getDomains())) { + throw new \InvalidArgumentException(sprintf('Invalid domain: %s.', $domain)); + } + + if (!isset($this->messages[$domain]['all'])) { + $this->processDomain($domain); + } + + return $this->messages[$domain]['all']; + } + + /** + * {@inheritdoc} + */ + public function getNewMessages($domain) + { + if (!in_array($domain, $this->getDomains())) { + throw new \InvalidArgumentException(sprintf('Invalid domain: %s.', $domain)); + } + + if (!isset($this->messages[$domain]['new'])) { + $this->processDomain($domain); + } + + return $this->messages[$domain]['new']; + } + + /** + * {@inheritdoc} + */ + public function getObsoleteMessages($domain) + { + if (!in_array($domain, $this->getDomains())) { + throw new \InvalidArgumentException(sprintf('Invalid domain: %s.', $domain)); + } + + if (!isset($this->messages[$domain]['obsolete'])) { + $this->processDomain($domain); + } + + return $this->messages[$domain]['obsolete']; + } + + /** + * {@inheritdoc} + */ + public function getResult() + { + foreach ($this->getDomains() as $domain) { + if (!isset($this->messages[$domain])) { + $this->processDomain($domain); + } + } + + return $this->result; + } + + /** + * @param string $domain + */ + abstract protected function processDomain($domain); +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Catalogue/DiffOperation.php b/vendor/symfony/translation/Symfony/Component/Translation/Catalogue/DiffOperation.php new file mode 100644 index 0000000..1672d12 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Catalogue/DiffOperation.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Catalogue; + +/** + * Diff operation between two catalogues. + * + * @author Jean-François Simon + */ +class DiffOperation extends AbstractOperation +{ + /** + * {@inheritdoc} + */ + protected function processDomain($domain) + { + $this->messages[$domain] = array( + 'all' => array(), + 'new' => array(), + 'obsolete' => array(), + ); + + foreach ($this->source->all($domain) as $id => $message) { + if ($this->target->has($id, $domain)) { + $this->messages[$domain]['all'][$id] = $message; + $this->result->add(array($id => $message), $domain); + } else { + $this->messages[$domain]['obsolete'][$id] = $message; + } + } + + foreach ($this->target->all($domain) as $id => $message) { + if (!$this->source->has($id, $domain)) { + $this->messages[$domain]['all'][$id] = $message; + $this->messages[$domain]['new'][$id] = $message; + $this->result->add(array($id => $message), $domain); + } + } + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Catalogue/MergeOperation.php b/vendor/symfony/translation/Symfony/Component/Translation/Catalogue/MergeOperation.php new file mode 100644 index 0000000..0052363 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Catalogue/MergeOperation.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Catalogue; + +/** + * Merge operation between two catalogues. + * + * @author Jean-François Simon + */ +class MergeOperation extends AbstractOperation +{ + /** + * {@inheritdoc} + */ + protected function processDomain($domain) + { + $this->messages[$domain] = array( + 'all' => array(), + 'new' => array(), + 'obsolete' => array(), + ); + + foreach ($this->source->all($domain) as $id => $message) { + $this->messages[$domain]['all'][$id] = $message; + $this->result->add(array($id => $message), $domain); + } + + foreach ($this->target->all($domain) as $id => $message) { + if (!$this->source->has($id, $domain)) { + $this->messages[$domain]['all'][$id] = $message; + $this->messages[$domain]['new'][$id] = $message; + $this->result->add(array($id => $message), $domain); + } + } + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Catalogue/OperationInterface.php b/vendor/symfony/translation/Symfony/Component/Translation/Catalogue/OperationInterface.php new file mode 100644 index 0000000..d72378a --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Catalogue/OperationInterface.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Catalogue; + +use Symfony\Component\Translation\MessageCatalogueInterface; + +/** + * Represents an operation on catalogue(s). + * + * @author Jean-François Simon + */ +interface OperationInterface +{ + /** + * Returns domains affected by operation. + * + * @return array + */ + public function getDomains(); + + /** + * Returns all valid messages after operation. + * + * @param string $domain + * + * @return array + */ + public function getMessages($domain); + + /** + * Returns new messages after operation. + * + * @param string $domain + * + * @return array + */ + public function getNewMessages($domain); + + /** + * Returns obsolete messages after operation. + * + * @param string $domain + * + * @return array + */ + public function getObsoleteMessages($domain); + + /** + * Returns resulting catalogue. + * + * @return MessageCatalogueInterface + */ + public function getResult(); +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Dumper/CsvFileDumper.php b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/CsvFileDumper.php new file mode 100644 index 0000000..0b41190 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/CsvFileDumper.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * CsvFileDumper generates a csv formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class CsvFileDumper extends FileDumper +{ + private $delimiter = ';'; + private $enclosure = '"'; + + /** + * {@inheritDoc} + */ + public function format(MessageCatalogue $messages, $domain = 'messages') + { + $handle = fopen('php://memory', 'rb+'); + + foreach ($messages->all($domain) as $source => $target) { + fputcsv($handle, array($source, $target), $this->delimiter, $this->enclosure); + } + + rewind($handle); + $output = stream_get_contents($handle); + fclose($handle); + + return $output; + } + + /** + * Sets the delimiter and escape character for CSV. + * + * @param string $delimiter delimiter character + * @param string $enclosure enclosure character + */ + public function setCsvControl($delimiter = ';', $enclosure = '"') + { + $this->delimiter = $delimiter; + $this->enclosure = $enclosure; + } + + /** + * {@inheritDoc} + */ + protected function getExtension() + { + return 'csv'; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Dumper/DumperInterface.php b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/DumperInterface.php new file mode 100644 index 0000000..cebc65e --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/DumperInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * DumperInterface is the interface implemented by all translation dumpers. + * There is no common option. + * + * @author Michel Salib + */ +interface DumperInterface +{ + /** + * Dumps the message catalogue. + * + * @param MessageCatalogue $messages The message catalogue + * @param array $options Options that are used by the dumper + */ + public function dump(MessageCatalogue $messages, $options = array()); +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Dumper/FileDumper.php b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/FileDumper.php new file mode 100644 index 0000000..63c1e6c --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/FileDumper.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * FileDumper is an implementation of DumperInterface that dump a message catalogue to file(s). + * Performs backup of already existing files. + * + * Options: + * - path (mandatory): the directory where the files should be saved + * + * @author Michel Salib + */ +abstract class FileDumper implements DumperInterface +{ + /** + * {@inheritDoc} + */ + public function dump(MessageCatalogue $messages, $options = array()) + { + if (!array_key_exists('path', $options)) { + throw new \InvalidArgumentException('The file dumper need a path options.'); + } + + // save a file for each domain + foreach ($messages->getDomains() as $domain) { + $file = $domain.'.'.$messages->getLocale().'.'.$this->getExtension(); + // backup + $fullpath = $options['path'].'/'.$file; + if (file_exists($fullpath)) { + copy($fullpath, $fullpath.'~'); + } + // save file + file_put_contents($fullpath, $this->format($messages, $domain)); + } + } + + /** + * Transforms a domain of a message catalogue to its string representation. + * + * @param MessageCatalogue $messages + * @param string $domain + * + * @return string representation + */ + abstract protected function format(MessageCatalogue $messages, $domain); + + /** + * Gets the file extension of the dumper. + * + * @return string file extension + */ + abstract protected function getExtension(); +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Dumper/IcuResFileDumper.php b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/IcuResFileDumper.php new file mode 100644 index 0000000..979153a --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/IcuResFileDumper.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * IcuResDumper generates an ICU ResourceBundle formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class IcuResFileDumper implements DumperInterface +{ + /** + * {@inheritDoc} + */ + public function dump(MessageCatalogue $messages, $options = array()) + { + if (!array_key_exists('path', $options)) { + throw new \InvalidArgumentException('The file dumper need a path options.'); + } + + // save a file for each domain + foreach ($messages->getDomains() as $domain) { + $file = $messages->getLocale().'.'.$this->getExtension(); + $path = $options['path'].'/'.$domain.'/'; + + if (!file_exists($path)) { + mkdir($path); + } + + // backup + if (file_exists($path.$file)) { + copy($path.$file, $path.$file.'~'); + } + + // save file + file_put_contents($path.$file, $this->format($messages, $domain)); + } + } + + /** + * {@inheritDoc} + */ + public function format(MessageCatalogue $messages, $domain = 'messages') + { + $data = $indexes = $resources = ''; + + foreach ($messages->all($domain) as $source => $target) { + $indexes .= pack('v', strlen($data) + 28); + $data .= $source."\0"; + } + + $data .= $this->writePadding($data); + + $keyTop = $this->getPosition($data); + + foreach ($messages->all($domain) as $source => $target) { + $resources .= pack('V', $this->getPosition($data)); + + $data .= pack('V', strlen($target)) + .mb_convert_encoding($target."\0", 'UTF-16LE', 'UTF-8') + .$this->writePadding($data) + ; + } + + $resOffset = $this->getPosition($data); + + $data .= pack('v', count($messages)) + .$indexes + .$this->writePadding($data) + .$resources + ; + + $bundleTop = $this->getPosition($data); + + $root = pack('V7', + $resOffset + (2 << 28), // Resource Offset + Resource Type + 6, // Index length + $keyTop, // Index keys top + $bundleTop, // Index resources top + $bundleTop, // Index bundle top + count($messages), // Index max table length + 0 // Index attributes + ); + + $header = pack('vC2v4C12@32', + 32, // Header size + 0xDA, 0x27, // Magic number 1 and 2 + 20, 0, 0, 2, // Rest of the header, ..., Size of a char + 0x52, 0x65, 0x73, 0x42, // Data format identifier + 1, 2, 0, 0, // Data version + 1, 4, 0, 0 // Unicode version + ); + + $output = $header + .$root + .$data; + + return $output; + } + + private function writePadding($data) + { + $padding = strlen($data) % 4; + + if ($padding) { + return str_repeat("\xAA", 4 - $padding); + } + } + + private function getPosition($data) + { + $position = (strlen($data) + 28) / 4; + + return $position; + } + + /** + * {@inheritDoc} + */ + protected function getExtension() + { + return 'res'; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Dumper/IniFileDumper.php b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/IniFileDumper.php new file mode 100644 index 0000000..173cf8c --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/IniFileDumper.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * IniFileDumper generates an ini formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class IniFileDumper extends FileDumper +{ + /** + * {@inheritDoc} + */ + public function format(MessageCatalogue $messages, $domain = 'messages') + { + $output = ''; + + foreach ($messages->all($domain) as $source => $target) { + $escapeTarget = str_replace('"', '\"', $target); + $output .= $source.'="'.$escapeTarget."\"\n"; + } + + return $output; + } + + /** + * {@inheritDoc} + */ + protected function getExtension() + { + return 'ini'; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Dumper/MoFileDumper.php b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/MoFileDumper.php new file mode 100644 index 0000000..a3a2a64 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/MoFileDumper.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Loader\MoFileLoader; + +/** + * MoFileDumper generates a gettext formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class MoFileDumper extends FileDumper +{ + /** + * {@inheritDoc} + */ + public function format(MessageCatalogue $messages, $domain = 'messages') + { + $output = $sources = $targets = $sourceOffsets = $targetOffsets = ''; + $offsets = array(); + $size = 0; + + foreach ($messages->all($domain) as $source => $target) { + $offsets[] = array_map('strlen', array($sources, $source, $targets, $target)); + $sources .= "\0".$source; + $targets .= "\0".$target; + ++$size; + } + + $header = array( + 'magicNumber' => MoFileLoader::MO_LITTLE_ENDIAN_MAGIC, + 'formatRevision' => 0, + 'count' => $size, + 'offsetId' => MoFileLoader::MO_HEADER_SIZE, + 'offsetTranslated' => MoFileLoader::MO_HEADER_SIZE + (8 * $size), + 'sizeHashes' => 0, + 'offsetHashes' => MoFileLoader::MO_HEADER_SIZE + (16 * $size), + ); + + $sourcesSize = strlen($sources); + $sourcesStart = $header['offsetHashes'] + 1; + + foreach ($offsets as $offset) { + $sourceOffsets .= $this->writeLong($offset[1]) + .$this->writeLong($offset[0] + $sourcesStart); + $targetOffsets .= $this->writeLong($offset[3]) + .$this->writeLong($offset[2] + $sourcesStart + $sourcesSize); + } + + $output = implode(array_map(array($this, 'writeLong'), $header)) + .$sourceOffsets + .$targetOffsets + .$sources + .$targets + ; + + return $output; + } + + /** + * {@inheritDoc} + */ + protected function getExtension() + { + return 'mo'; + } + + private function writeLong($str) + { + return pack('V*', $str); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Dumper/PhpFileDumper.php b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/PhpFileDumper.php new file mode 100644 index 0000000..8de4a5b --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/PhpFileDumper.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * PhpFileDumper generates php files from a message catalogue. + * + * @author Michel Salib + */ +class PhpFileDumper extends FileDumper +{ + /** + * {@inheritDoc} + */ + protected function format(MessageCatalogue $messages, $domain) + { + $output = "all($domain), true).";\n"; + + return $output; + } + + /** + * {@inheritDoc} + */ + protected function getExtension() + { + return 'php'; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Dumper/PoFileDumper.php b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/PoFileDumper.php new file mode 100644 index 0000000..d957ab9 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/PoFileDumper.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * PoFileDumper generates a gettext formatted string representation of a message catalogue. + * + * @author Stealth35 + */ +class PoFileDumper extends FileDumper +{ + /** + * {@inheritDoc} + */ + public function format(MessageCatalogue $messages, $domain = 'messages') + { + $output = ''; + $newLine = false; + foreach ($messages->all($domain) as $source => $target) { + if ($newLine) { + $output .= "\n"; + } else { + $newLine = true; + } + $output .= sprintf('msgid "%s"'."\n", $this->escape($source)); + $output .= sprintf('msgstr "%s"', $this->escape($target)); + } + + return $output; + } + + /** + * {@inheritDoc} + */ + protected function getExtension() + { + return 'po'; + } + + private function escape($str) + { + return addcslashes($str, "\0..\37\42\134"); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Dumper/QtFileDumper.php b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/QtFileDumper.php new file mode 100644 index 0000000..a1a8480 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/QtFileDumper.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * QtFileDumper generates ts files from a message catalogue. + * + * @author Benjamin Eberlei + */ +class QtFileDumper extends FileDumper +{ + /** + * {@inheritDoc} + */ + public function format(MessageCatalogue $messages, $domain) + { + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = true; + $ts = $dom->appendChild($dom->createElement('TS')); + $context = $ts->appendChild($dom->createElement('context')); + $context->appendChild($dom->createElement('name', $domain)); + + foreach ($messages->all($domain) as $source => $target) { + $message = $context->appendChild($dom->createElement('message')); + $message->appendChild($dom->createElement('source', $source)); + $message->appendChild($dom->createElement('translation', $target)); + } + + return $dom->saveXML(); + } + + /** + * {@inheritDoc} + */ + protected function getExtension() + { + return 'ts'; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Dumper/XliffFileDumper.php b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/XliffFileDumper.php new file mode 100644 index 0000000..0d258aa --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/XliffFileDumper.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * XliffFileDumper generates xliff files from a message catalogue. + * + * @author Michel Salib + */ +class XliffFileDumper extends FileDumper +{ + /** + * {@inheritDoc} + */ + protected function format(MessageCatalogue $messages, $domain) + { + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->formatOutput = true; + + $xliff = $dom->appendChild($dom->createElement('xliff')); + $xliff->setAttribute('version', '1.2'); + $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:1.2'); + + $xliffFile = $xliff->appendChild($dom->createElement('file')); + $xliffFile->setAttribute('source-language', $messages->getLocale()); + $xliffFile->setAttribute('datatype', 'plaintext'); + $xliffFile->setAttribute('original', 'file.ext'); + + $xliffBody = $xliffFile->appendChild($dom->createElement('body')); + foreach ($messages->all($domain) as $source => $target) { + $translation = $dom->createElement('trans-unit'); + + $translation->setAttribute('id', md5($source)); + $translation->setAttribute('resname', $source); + + $s = $translation->appendChild($dom->createElement('source')); + $s->appendChild($dom->createTextNode($source)); + + $t = $translation->appendChild($dom->createElement('target')); + $t->appendChild($dom->createTextNode($target)); + + $xliffBody->appendChild($translation); + } + + return $dom->saveXML(); + } + + /** + * {@inheritDoc} + */ + protected function getExtension() + { + return 'xlf'; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Dumper/YamlFileDumper.php b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/YamlFileDumper.php new file mode 100644 index 0000000..d8072fb --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Dumper/YamlFileDumper.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Yaml\Yaml; + +/** + * YamlFileDumper generates yaml files from a message catalogue. + * + * @author Michel Salib + */ +class YamlFileDumper extends FileDumper +{ + /** + * {@inheritDoc} + */ + protected function format(MessageCatalogue $messages, $domain) + { + return Yaml::dump($messages->all($domain)); + } + + /** + * {@inheritDoc} + */ + protected function getExtension() + { + return 'yml'; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Exception/ExceptionInterface.php b/vendor/symfony/translation/Symfony/Component/Translation/Exception/ExceptionInterface.php new file mode 100644 index 0000000..7757e66 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Exception/ExceptionInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Fabien Potencier + * + * @api + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Exception/InvalidResourceException.php b/vendor/symfony/translation/Symfony/Component/Translation/Exception/InvalidResourceException.php new file mode 100644 index 0000000..6413f1a --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Exception/InvalidResourceException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * Thrown when a resource cannot be loaded. + * + * @author Fabien Potencier + * + * @api + */ +class InvalidResourceException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Exception/NotFoundResourceException.php b/vendor/symfony/translation/Symfony/Component/Translation/Exception/NotFoundResourceException.php new file mode 100644 index 0000000..7826e5c --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Exception/NotFoundResourceException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Exception; + +/** + * Thrown when a resource does not exist. + * + * @author Fabien Potencier + * + * @api + */ +class NotFoundResourceException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Extractor/ChainExtractor.php b/vendor/symfony/translation/Symfony/Component/Translation/Extractor/ChainExtractor.php new file mode 100644 index 0000000..0d07a93 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Extractor/ChainExtractor.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Extractor; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * ChainExtractor extracts translation messages from template files. + * + * @author Michel Salib + */ +class ChainExtractor implements ExtractorInterface +{ + /** + * The extractors. + * + * @var ExtractorInterface[] + */ + private $extractors = array(); + + /** + * Adds a loader to the translation extractor. + * + * @param string $format The format of the loader + * @param ExtractorInterface $extractor The loader + */ + public function addExtractor($format, ExtractorInterface $extractor) + { + $this->extractors[$format] = $extractor; + } + + /** + * {@inheritDoc} + */ + public function setPrefix($prefix) + { + foreach ($this->extractors as $extractor) { + $extractor->setPrefix($prefix); + } + } + + /** + * {@inheritDoc} + */ + public function extract($directory, MessageCatalogue $catalogue) + { + foreach ($this->extractors as $extractor) { + $extractor->extract($directory, $catalogue); + } + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Extractor/ExtractorInterface.php b/vendor/symfony/translation/Symfony/Component/Translation/Extractor/ExtractorInterface.php new file mode 100644 index 0000000..6f877c3 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Extractor/ExtractorInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Extractor; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * Extracts translation messages from a template directory to the catalogue. + * New found messages are injected to the catalogue using the prefix. + * + * @author Michel Salib + */ +interface ExtractorInterface +{ + /** + * Extracts translation messages from a template directory to the catalogue. + * + * @param string $directory The path to look into + * @param MessageCatalogue $catalogue The catalogue + */ + public function extract($directory, MessageCatalogue $catalogue); + + /** + * Sets the prefix that should be used for new found messages. + * + * @param string $prefix The prefix + */ + public function setPrefix($prefix); +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/IdentityTranslator.php b/vendor/symfony/translation/Symfony/Component/Translation/IdentityTranslator.php new file mode 100644 index 0000000..8564aa7 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/IdentityTranslator.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +/** + * IdentityTranslator does not translate anything. + * + * @author Fabien Potencier + * + * @api + */ +class IdentityTranslator implements TranslatorInterface +{ + private $selector; + private $locale; + + /** + * Constructor. + * + * @param MessageSelector $selector The message selector for pluralization + * + * @api + */ + public function __construct(MessageSelector $selector) + { + $this->selector = $selector; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function setLocale($locale) + { + $this->locale = $locale; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function getLocale() + { + return $this->locale ?: \Locale::getDefault(); + } + + /** + * {@inheritdoc} + * + * @api + */ + public function trans($id, array $parameters = array(), $domain = 'messages', $locale = null) + { + return strtr((string) $id, $parameters); + } + + /** + * {@inheritdoc} + * + * @api + */ + public function transChoice($id, $number, array $parameters = array(), $domain = 'messages', $locale = null) + { + return strtr($this->selector->choose((string) $id, (int) $number, $locale ?: $this->getLocale()), $parameters); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Interval.php b/vendor/symfony/translation/Symfony/Component/Translation/Interval.php new file mode 100644 index 0000000..033b018 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Interval.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +/** + * Tests if a given number belongs to a given math interval. + * + * An interval can represent a finite set of numbers: + * + * {1,2,3,4} + * + * An interval can represent numbers between two numbers: + * + * [1, +Inf] + * ]-1,2[ + * + * The left delimiter can be [ (inclusive) or ] (exclusive). + * The right delimiter can be [ (exclusive) or ] (inclusive). + * Beside numbers, you can use -Inf and +Inf for the infinite. + * + * @author Fabien Potencier + * + * @see http://en.wikipedia.org/wiki/Interval_%28mathematics%29#The_ISO_notation + */ +class Interval +{ + /** + * Tests if the given number is in the math interval. + * + * @param integer $number A number + * @param string $interval An interval + * + * @return Boolean + * + * @throws \InvalidArgumentException + */ + public static function test($number, $interval) + { + $interval = trim($interval); + + if (!preg_match('/^'.self::getIntervalRegexp().'$/x', $interval, $matches)) { + throw new \InvalidArgumentException(sprintf('"%s" is not a valid interval.', $interval)); + } + + if ($matches[1]) { + foreach (explode(',', $matches[2]) as $n) { + if ($number == $n) { + return true; + } + } + } else { + $leftNumber = self::convertNumber($matches['left']); + $rightNumber = self::convertNumber($matches['right']); + + return + ('[' === $matches['left_delimiter'] ? $number >= $leftNumber : $number > $leftNumber) + && (']' === $matches['right_delimiter'] ? $number <= $rightNumber : $number < $rightNumber) + ; + } + + return false; + } + + /** + * Returns a Regexp that matches valid intervals. + * + * @return string A Regexp (without the delimiters) + */ + public static function getIntervalRegexp() + { + return <<[\[\]]) + \s* + (?P-Inf|\-?\d+(\.\d+)?) + \s*,\s* + (?P\+?Inf|\-?\d+(\.\d+)?) + \s* + (?P[\[\]]) +EOF; + } + + private static function convertNumber($number) + { + if ('-Inf' === $number) { + return log(0); + } elseif ('+Inf' === $number || 'Inf' === $number) { + return -log(0); + } + + return (float) $number; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/LICENSE b/vendor/symfony/translation/Symfony/Component/Translation/LICENSE new file mode 100644 index 0000000..88a57f8 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2013 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Loader/ArrayLoader.php b/vendor/symfony/translation/Symfony/Component/Translation/Loader/ArrayLoader.php new file mode 100644 index 0000000..99058fb --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Loader/ArrayLoader.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\MessageCatalogue; + +/** + * ArrayLoader loads translations from a PHP array. + * + * @author Fabien Potencier + * + * @api + */ +class ArrayLoader implements LoaderInterface +{ + /** + * {@inheritdoc} + * + * @api + */ + public function load($resource, $locale, $domain = 'messages') + { + $this->flatten($resource); + $catalogue = new MessageCatalogue($locale); + $catalogue->add($resource, $domain); + + return $catalogue; + } + + /** + * Flattens an nested array of translations + * + * The scheme used is: + * 'key' => array('key2' => array('key3' => 'value')) + * Becomes: + * 'key.key2.key3' => 'value' + * + * This function takes an array by reference and will modify it + * + * @param array &$messages The array that will be flattened + * @param array $subnode Current subnode being parsed, used internally for recursive calls + * @param string $path Current path being parsed, used internally for recursive calls + */ + private function flatten(array &$messages, array $subnode = null, $path = null) + { + if (null === $subnode) { + $subnode =& $messages; + } + foreach ($subnode as $key => $value) { + if (is_array($value)) { + $nodePath = $path ? $path.'.'.$key : $key; + $this->flatten($messages, $value, $nodePath); + if (null === $path) { + unset($messages[$key]); + } + } elseif (null !== $path) { + $messages[$path.'.'.$key] = $value; + } + } + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Loader/CsvFileLoader.php b/vendor/symfony/translation/Symfony/Component/Translation/Loader/CsvFileLoader.php new file mode 100644 index 0000000..bc10188 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Loader/CsvFileLoader.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Config\Resource\FileResource; + +/** + * CsvFileLoader loads translations from CSV files. + * + * @author SaÅ¡a Stamenković + * + * @api + */ +class CsvFileLoader extends ArrayLoader implements LoaderInterface +{ + private $delimiter = ';'; + private $enclosure = '"'; + private $escape = '\\'; + + /** + * {@inheritdoc} + * + * @api + */ + public function load($resource, $locale, $domain = 'messages') + { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + $messages = array(); + + try { + $file = new \SplFileObject($resource, 'rb'); + } catch (\RuntimeException $e) { + throw new NotFoundResourceException(sprintf('Error opening file "%s".', $resource), 0, $e); + } + + $file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY); + $file->setCsvControl($this->delimiter, $this->enclosure, $this->escape); + + foreach ($file as $data) { + if (substr($data[0], 0, 1) === '#') { + continue; + } + + if (!isset($data[1])) { + continue; + } + + if (count($data) == 2) { + $messages[$data[0]] = $data[1]; + } else { + continue; + } + } + + $catalogue = parent::load($messages, $locale, $domain); + $catalogue->addResource(new FileResource($resource)); + + return $catalogue; + } + + /** + * Sets the delimiter, enclosure, and escape character for CSV. + * + * @param string $delimiter delimiter character + * @param string $enclosure enclosure character + * @param string $escape escape character + */ + public function setCsvControl($delimiter = ';', $enclosure = '"', $escape = '\\') + { + $this->delimiter = $delimiter; + $this->enclosure = $enclosure; + $this->escape = $escape; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Loader/IcuDatFileLoader.php b/vendor/symfony/translation/Symfony/Component/Translation/Loader/IcuDatFileLoader.php new file mode 100644 index 0000000..104883b --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Loader/IcuDatFileLoader.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Config\Resource\FileResource; + +/** + * IcuResFileLoader loads translations from a resource bundle. + * + * @author stealth35 + */ +class IcuDatFileLoader extends IcuResFileLoader +{ + /** + * {@inheritdoc} + */ + public function load($resource, $locale, $domain = 'messages') + { + if (!stream_is_local($resource.'.dat')) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource.'.dat')) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + $rb = new \ResourceBundle($locale, $resource); + + if (!$rb) { + throw new InvalidResourceException(sprintf('Cannot load resource "%s"', $resource)); + } elseif (intl_is_failure($rb->getErrorCode())) { + throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode()); + } + + $messages = $this->flatten($rb); + $catalogue = new MessageCatalogue($locale); + $catalogue->add($messages, $domain); + $catalogue->addResource(new FileResource($resource.'.dat')); + + return $catalogue; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Loader/IcuResFileLoader.php b/vendor/symfony/translation/Symfony/Component/Translation/Loader/IcuResFileLoader.php new file mode 100644 index 0000000..81b8e0f --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Loader/IcuResFileLoader.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Config\Resource\DirectoryResource; + +/** + * IcuResFileLoader loads translations from a resource bundle. + * + * @author stealth35 + */ +class IcuResFileLoader implements LoaderInterface +{ + /** + * {@inheritdoc} + */ + public function load($resource, $locale, $domain = 'messages') + { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!is_dir($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + $rb = new \ResourceBundle($locale, $resource); + + if (!$rb) { + throw new InvalidResourceException(sprintf('Cannot load resource "%s"', $resource)); + } elseif (intl_is_failure($rb->getErrorCode())) { + throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode()); + } + + $messages = $this->flatten($rb); + $catalogue = new MessageCatalogue($locale); + $catalogue->add($messages, $domain); + $catalogue->addResource(new DirectoryResource($resource)); + + return $catalogue; + } + + /** + * Flattens an ResourceBundle + * + * The scheme used is: + * key { key2 { key3 { "value" } } } + * Becomes: + * 'key.key2.key3' => 'value' + * + * This function takes an array by reference and will modify it + * + * @param \ResourceBundle $rb the ResourceBundle that will be flattened + * @param array $messages used internally for recursive calls + * @param string $path current path being parsed, used internally for recursive calls + * + * @return array the flattened ResourceBundle + */ + protected function flatten(\ResourceBundle $rb, array &$messages = array(), $path = null) + { + foreach ($rb as $key => $value) { + $nodePath = $path ? $path.'.'.$key : $key; + if ($value instanceof \ResourceBundle) { + $this->flatten($value, $messages, $nodePath); + } else { + $messages[$nodePath] = $value; + } + } + + return $messages; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Loader/IniFileLoader.php b/vendor/symfony/translation/Symfony/Component/Translation/Loader/IniFileLoader.php new file mode 100644 index 0000000..616fa7e --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Loader/IniFileLoader.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Config\Resource\FileResource; + +/** + * IniFileLoader loads translations from an ini file. + * + * @author stealth35 + */ +class IniFileLoader extends ArrayLoader implements LoaderInterface +{ + /** + * {@inheritdoc} + */ + public function load($resource, $locale, $domain = 'messages') + { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + $messages = parse_ini_file($resource, true); + + $catalogue = parent::load($messages, $locale, $domain); + $catalogue->addResource(new FileResource($resource)); + + return $catalogue; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Loader/LoaderInterface.php b/vendor/symfony/translation/Symfony/Component/Translation/Loader/LoaderInterface.php new file mode 100644 index 0000000..4d9965c --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Loader/LoaderInterface.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Exception\InvalidResourceException; + +/** + * LoaderInterface is the interface implemented by all translation loaders. + * + * @author Fabien Potencier + * + * @api + */ +interface LoaderInterface +{ + /** + * Loads a locale. + * + * @param mixed $resource A resource + * @param string $locale A locale + * @param string $domain The domain + * + * @return MessageCatalogue A MessageCatalogue instance + * + * @api + * + * @throws NotFoundResourceException when the resource cannot be found + * @throws InvalidResourceException when the resource cannot be loaded + */ + public function load($resource, $locale, $domain = 'messages'); +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Loader/MoFileLoader.php b/vendor/symfony/translation/Symfony/Component/Translation/Loader/MoFileLoader.php new file mode 100644 index 0000000..88c397c --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Loader/MoFileLoader.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Config\Resource\FileResource; + +/** + * @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/) + */ +class MoFileLoader extends ArrayLoader implements LoaderInterface +{ + /** + * Magic used for validating the format of a MO file as well as + * detecting if the machine used to create that file was little endian. + * + * @var float + */ + const MO_LITTLE_ENDIAN_MAGIC = 0x950412de; + + /** + * Magic used for validating the format of a MO file as well as + * detecting if the machine used to create that file was big endian. + * + * @var float + */ + const MO_BIG_ENDIAN_MAGIC = 0xde120495; + + /** + * The size of the header of a MO file in bytes. + * + * @var integer Number of bytes. + */ + const MO_HEADER_SIZE = 28; + + public function load($resource, $locale, $domain = 'messages') + { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + $messages = $this->parse($resource); + + // empty file + if (null === $messages) { + $messages = array(); + } + + // not an array + if (!is_array($messages)) { + throw new InvalidResourceException(sprintf('The file "%s" must contain a valid mo file.', $resource)); + } + + $catalogue = parent::load($messages, $locale, $domain); + $catalogue->addResource(new FileResource($resource)); + + return $catalogue; + } + + /** + * Parses machine object (MO) format, independent of the machine's endian it + * was created on. Both 32bit and 64bit systems are supported. + * + * @param resource $resource + * + * @return array + * @throws InvalidResourceException If stream content has an invalid format. + */ + private function parse($resource) + { + $stream = fopen($resource, 'r'); + + $stat = fstat($stream); + + if ($stat['size'] < self::MO_HEADER_SIZE) { + throw new InvalidResourceException("MO stream content has an invalid format."); + } + $magic = unpack('V1', fread($stream, 4)); + $magic = hexdec(substr(dechex(current($magic)), -8)); + + if ($magic == self::MO_LITTLE_ENDIAN_MAGIC) { + $isBigEndian = false; + } elseif ($magic == self::MO_BIG_ENDIAN_MAGIC) { + $isBigEndian = true; + } else { + throw new InvalidResourceException("MO stream content has an invalid format."); + } + + // formatRevision + $this->readLong($stream, $isBigEndian); + $count = $this->readLong($stream, $isBigEndian); + $offsetId = $this->readLong($stream, $isBigEndian); + $offsetTranslated = $this->readLong($stream, $isBigEndian); + // sizeHashes + $this->readLong($stream, $isBigEndian); + // offsetHashes + $this->readLong($stream, $isBigEndian); + + $messages = array(); + + for ($i = 0; $i < $count; $i++) { + $singularId = $pluralId = null; + $translated = null; + + fseek($stream, $offsetId + $i * 8); + + $length = $this->readLong($stream, $isBigEndian); + $offset = $this->readLong($stream, $isBigEndian); + + if ($length < 1) { + continue; + } + + fseek($stream, $offset); + $singularId = fread($stream, $length); + + if (strpos($singularId, "\000") !== false) { + list($singularId, $pluralId) = explode("\000", $singularId); + } + + fseek($stream, $offsetTranslated + $i * 8); + $length = $this->readLong($stream, $isBigEndian); + $offset = $this->readLong($stream, $isBigEndian); + + fseek($stream, $offset); + $translated = fread($stream, $length); + + if (strpos($translated, "\000") !== false) { + $translated = explode("\000", $translated); + } + + $ids = array('singular' => $singularId, 'plural' => $pluralId); + $item = compact('ids', 'translated'); + + if (is_array($item['translated'])) { + $messages[$item['ids']['singular']] = stripcslashes($item['translated'][0]); + if (isset($item['ids']['plural'])) { + $plurals = array(); + foreach ($item['translated'] as $plural => $translated) { + $plurals[] = sprintf('{%d} %s', $plural, $translated); + } + $messages[$item['ids']['plural']] = stripcslashes(implode('|', $plurals)); + } + } elseif (!empty($item['ids']['singular'])) { + $messages[$item['ids']['singular']] = stripcslashes($item['translated']); + } + } + + fclose($stream); + + return array_filter($messages); + } + + /** + * Reads an unsigned long from stream respecting endianess. + * + * @param resource $stream + * @param boolean $isBigEndian + * @return integer + */ + private function readLong($stream, $isBigEndian) + { + $result = unpack($isBigEndian ? 'N1' : 'V1', fread($stream, 4)); + $result = current($result); + + return (integer) substr($result, -8); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Loader/PhpFileLoader.php b/vendor/symfony/translation/Symfony/Component/Translation/Loader/PhpFileLoader.php new file mode 100644 index 0000000..4737d1b --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Loader/PhpFileLoader.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Config\Resource\FileResource; + +/** + * PhpFileLoader loads translations from PHP files returning an array of translations. + * + * @author Fabien Potencier + * + * @api + */ +class PhpFileLoader extends ArrayLoader implements LoaderInterface +{ + /** + * {@inheritdoc} + * + * @api + */ + public function load($resource, $locale, $domain = 'messages') + { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + $messages = require($resource); + + $catalogue = parent::load($messages, $locale, $domain); + $catalogue->addResource(new FileResource($resource)); + + return $catalogue; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Loader/PoFileLoader.php b/vendor/symfony/translation/Symfony/Component/Translation/Loader/PoFileLoader.php new file mode 100644 index 0000000..58ec6c7 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Loader/PoFileLoader.php @@ -0,0 +1,178 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Config\Resource\FileResource; + +/** + * @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/) + * @copyright Copyright (c) 2012, Clemens Tolboom + */ +class PoFileLoader extends ArrayLoader implements LoaderInterface +{ + public function load($resource, $locale, $domain = 'messages') + { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + $messages = $this->parse($resource); + + // empty file + if (null === $messages) { + $messages = array(); + } + + // not an array + if (!is_array($messages)) { + throw new InvalidResourceException(sprintf('The file "%s" must contain a valid po file.', $resource)); + } + + $catalogue = parent::load($messages, $locale, $domain); + $catalogue->addResource(new FileResource($resource)); + + return $catalogue; + } + + /** + * Parses portable object (PO) format. + * + * From http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files + * we should be able to parse files having: + * + * white-space + * # translator-comments + * #. extracted-comments + * #: reference... + * #, flag... + * #| msgid previous-untranslated-string + * msgid untranslated-string + * msgstr translated-string + * + * extra or different lines are: + * + * #| msgctxt previous-context + * #| msgid previous-untranslated-string + * msgctxt context + * + * #| msgid previous-untranslated-string-singular + * #| msgid_plural previous-untranslated-string-plural + * msgid untranslated-string-singular + * msgid_plural untranslated-string-plural + * msgstr[0] translated-string-case-0 + * ... + * msgstr[N] translated-string-case-n + * + * The definition states: + * - white-space and comments are optional. + * - msgid "" that an empty singleline defines a header. + * + * This parser sacrifices some features of the reference implementation the + * differences to that implementation are as follows. + * - No support for comments spanning multiple lines. + * - Translator and extracted comments are treated as being the same type. + * - Message IDs are allowed to have other encodings as just US-ASCII. + * + * Items with an empty id are ignored. + * + * @param resource $resource + * + * @return array + */ + private function parse($resource) + { + $stream = fopen($resource, 'r'); + + $defaults = array( + 'ids' => array(), + 'translated' => null, + ); + + $messages = array(); + $item = $defaults; + + while ($line = fgets($stream)) { + $line = trim($line); + + if ($line === '') { + // Whitespace indicated current item is done + $this->addMessage($messages, $item); + $item = $defaults; + } elseif (substr($line, 0, 7) === 'msgid "') { + // We start a new msg so save previous + // TODO: this fails when comments or contexts are added + $this->addMessage($messages, $item); + $item = $defaults; + $item['ids']['singular'] = substr($line, 7, -1); + } elseif (substr($line, 0, 8) === 'msgstr "') { + $item['translated'] = substr($line, 8, -1); + } elseif ($line[0] === '"') { + $continues = isset($item['translated']) ? 'translated' : 'ids'; + + if (is_array($item[$continues])) { + end($item[$continues]); + $item[$continues][key($item[$continues])] .= substr($line, 1, -1); + } else { + $item[$continues] .= substr($line, 1, -1); + } + } elseif (substr($line, 0, 14) === 'msgid_plural "') { + $item['ids']['plural'] = substr($line, 14, -1); + } elseif (substr($line, 0, 7) === 'msgstr[') { + $size = strpos($line, ']'); + $item['translated'][(integer) substr($line, 7, 1)] = substr($line, $size + 3, -1); + } + + } + // save last item + $this->addMessage($messages, $item); + fclose($stream); + + return $messages; + } + + /** + * Save a translation item to the messeages. + * + * A .po file could contain by error missing plural indexes. We need to + * fix these before saving them. + * + * @param array $messages + * @param array $item + */ + private function addMessage(array &$messages, array $item) + { + if (is_array($item['translated'])) { + $messages[$item['ids']['singular']] = stripslashes($item['translated'][0]); + if (isset($item['ids']['plural'])) { + $plurals = $item['translated']; + // PO are by definition indexed so sort by index. + ksort($plurals); + // Make sure every index is filled. + end($plurals); + $count = key($plurals); + // Fill missing spots with '-'. + $empties = array_fill(0, $count+1, '-'); + $plurals += $empties; + ksort($plurals); + $messages[$item['ids']['plural']] = stripcslashes(implode('|', $plurals)); + } + } elseif (!empty($item['ids']['singular'])) { + $messages[$item['ids']['singular']] = stripslashes($item['translated']); + } + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Loader/QtFileLoader.php b/vendor/symfony/translation/Symfony/Component/Translation/Loader/QtFileLoader.php new file mode 100644 index 0000000..d64494b --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Loader/QtFileLoader.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Config\Resource\FileResource; + +/** + * QtFileLoader loads translations from QT Translations XML files. + * + * @author Benjamin Eberlei + * + * @api + */ +class QtFileLoader implements LoaderInterface +{ + /** + * {@inheritdoc} + * + * @api + */ + public function load($resource, $locale, $domain = 'messages') + { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + $dom = new \DOMDocument(); + $current = libxml_use_internal_errors(true); + if (!@$dom->load($resource, defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0)) { + throw new InvalidResourceException(implode("\n", $this->getXmlErrors())); + } + + $xpath = new \DOMXPath($dom); + $nodes = $xpath->evaluate('//TS/context/name[text()="'.$domain.'"]'); + + $catalogue = new MessageCatalogue($locale); + if ($nodes->length == 1) { + $translations = $nodes->item(0)->nextSibling->parentNode->parentNode->getElementsByTagName('message'); + foreach ($translations as $translation) { + $catalogue->set( + (string) $translation->getElementsByTagName('source')->item(0)->nodeValue, + (string) $translation->getElementsByTagName('translation')->item(0)->nodeValue, + $domain + ); + $translation = $translation->nextSibling; + } + $catalogue->addResource(new FileResource($resource)); + } + + libxml_use_internal_errors($current); + + return $catalogue; + } + + /** + * Returns the XML errors of the internal XML parser + * + * @return array An array of errors + */ + private function getXmlErrors() + { + $errors = array(); + foreach (libxml_get_errors() as $error) { + $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)', + LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR', + $error->code, + trim($error->message), + $error->file ? $error->file : 'n/a', + $error->line, + $error->column + ); + } + + libxml_clear_errors(); + libxml_use_internal_errors(false); + + return $errors; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Loader/XliffFileLoader.php b/vendor/symfony/translation/Symfony/Component/Translation/Loader/XliffFileLoader.php new file mode 100644 index 0000000..0cafc04 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Loader/XliffFileLoader.php @@ -0,0 +1,163 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Config\Resource\FileResource; + +/** + * XliffFileLoader loads translations from XLIFF files. + * + * @author Fabien Potencier + * + * @api + */ +class XliffFileLoader implements LoaderInterface +{ + /** + * {@inheritdoc} + * + * @api + */ + public function load($resource, $locale, $domain = 'messages') + { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + list($xml, $encoding) = $this->parseFile($resource); + $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:1.2'); + + $catalogue = new MessageCatalogue($locale); + foreach ($xml->xpath('//xliff:trans-unit') as $translation) { + $attributes = $translation->attributes(); + + if (!(isset($attributes['resname']) || isset($translation->source)) || !isset($translation->target)) { + continue; + } + + $source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source; + $target = (string) $translation->target; + + // If the xlf file has another encoding specified, try to convert it because + // simple_xml will always return utf-8 encoded values + if ('UTF-8' !== $encoding && !empty($encoding)) { + if (function_exists('mb_convert_encoding')) { + $target = mb_convert_encoding($target, $encoding, 'UTF-8'); + } elseif (function_exists('iconv')) { + $target = iconv('UTF-8', $encoding, $target); + } else { + throw new \RuntimeException('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).'); + } + } + + $catalogue->set((string) $source, $target, $domain); + } + $catalogue->addResource(new FileResource($resource)); + + return $catalogue; + } + + /** + * Validates and parses the given file into a SimpleXMLElement + * + * @param string $file + * + * @throws \RuntimeException + * + * @return \SimpleXMLElement + * + * @throws InvalidResourceException + */ + private function parseFile($file) + { + $internalErrors = libxml_use_internal_errors(true); + $disableEntities = libxml_disable_entity_loader(true); + libxml_clear_errors(); + + $dom = new \DOMDocument(); + $dom->validateOnParse = true; + if (!@$dom->loadXML(file_get_contents($file), LIBXML_NONET | (defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) { + libxml_disable_entity_loader($disableEntities); + + throw new InvalidResourceException(implode("\n", $this->getXmlErrors($internalErrors))); + } + + libxml_disable_entity_loader($disableEntities); + + foreach ($dom->childNodes as $child) { + if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) { + libxml_use_internal_errors($internalErrors); + + throw new InvalidResourceException('Document types are not allowed.'); + } + } + + $location = str_replace('\\', '/', __DIR__).'/schema/dic/xliff-core/xml.xsd'; + $parts = explode('/', $location); + if (0 === stripos($location, 'phar://')) { + $tmpfile = tempnam(sys_get_temp_dir(), 'sf2'); + if ($tmpfile) { + copy($location, $tmpfile); + $parts = explode('/', str_replace('\\', '/', $tmpfile)); + } + } + $drive = '\\' === DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; + $location = 'file:///'.$drive.implode('/', array_map('rawurlencode', $parts)); + + $source = file_get_contents(__DIR__.'/schema/dic/xliff-core/xliff-core-1.2-strict.xsd'); + $source = str_replace('http://www.w3.org/2001/xml.xsd', $location, $source); + + if (!@$dom->schemaValidateSource($source)) { + throw new InvalidResourceException(implode("\n", $this->getXmlErrors($internalErrors))); + } + + $dom->normalizeDocument(); + + libxml_use_internal_errors($internalErrors); + + return array(simplexml_import_dom($dom), strtoupper($dom->encoding)); + } + + /** + * Returns the XML errors of the internal XML parser + * + * @param Boolean $internalErrors + * + * @return array An array of errors + */ + private function getXmlErrors($internalErrors) + { + $errors = array(); + foreach (libxml_get_errors() as $error) { + $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)', + LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR', + $error->code, + trim($error->message), + $error->file ? $error->file : 'n/a', + $error->line, + $error->column + ); + } + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + + return $errors; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Loader/YamlFileLoader.php b/vendor/symfony/translation/Symfony/Component/Translation/Loader/YamlFileLoader.php new file mode 100644 index 0000000..66ab2ba --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Loader/YamlFileLoader.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Loader; + +use Symfony\Component\Translation\Exception\InvalidResourceException; +use Symfony\Component\Translation\Exception\NotFoundResourceException; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Yaml\Parser as YamlParser; +use Symfony\Component\Yaml\Exception\ParseException; + +/** + * YamlFileLoader loads translations from Yaml files. + * + * @author Fabien Potencier + * + * @api + */ +class YamlFileLoader extends ArrayLoader implements LoaderInterface +{ + private $yamlParser; + + /** + * {@inheritdoc} + * + * @api + */ + public function load($resource, $locale, $domain = 'messages') + { + if (!stream_is_local($resource)) { + throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource)); + } + + if (!file_exists($resource)) { + throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource)); + } + + if (null === $this->yamlParser) { + $this->yamlParser = new YamlParser(); + } + + try { + $messages = $this->yamlParser->parse(file_get_contents($resource)); + } catch (ParseException $e) { + throw new InvalidResourceException('Error parsing YAML.', 0, $e); + } + + // empty file + if (null === $messages) { + $messages = array(); + } + + // not an array + if (!is_array($messages)) { + throw new InvalidResourceException(sprintf('The file "%s" must contain a YAML array.', $resource)); + } + + $catalogue = parent::load($messages, $locale, $domain); + $catalogue->addResource(new FileResource($resource)); + + return $catalogue; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Loader/schema/dic/xliff-core/xliff-core-1.2-strict.xsd b/vendor/symfony/translation/Symfony/Component/Translation/Loader/schema/dic/xliff-core/xliff-core-1.2-strict.xsd new file mode 100644 index 0000000..3ce2a8e --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Loader/schema/dic/xliff-core/xliff-core-1.2-strict.xsd @@ -0,0 +1,2223 @@ + + + + + + + + + + + + + + + Values for the attribute 'context-type'. + + + + + Indicates a database content. + + + + + Indicates the content of an element within an XML document. + + + + + Indicates the name of an element within an XML document. + + + + + Indicates the line number from the sourcefile (see context-type="sourcefile") where the <source> is found. + + + + + Indicates a the number of parameters contained within the <source>. + + + + + Indicates notes pertaining to the parameters in the <source>. + + + + + Indicates the content of a record within a database. + + + + + Indicates the name of a record within a database. + + + + + Indicates the original source file in the case that multiple files are merged to form the original file from which the XLIFF file is created. This differs from the original <file> attribute in that this sourcefile is one of many that make up that file. + + + + + + + Values for the attribute 'count-type'. + + + + + Indicates the count units are items that are used X times in a certain context; example: this is a reusable text unit which is used 42 times in other texts. + + + + + Indicates the count units are translation units existing already in the same document. + + + + + Indicates a total count. + + + + + + + Values for the attribute 'ctype' when used other elements than <ph> or <x>. + + + + + Indicates a run of bolded text. + + + + + Indicates a run of text in italics. + + + + + Indicates a run of underlined text. + + + + + Indicates a run of hyper-text. + + + + + + + Values for the attribute 'ctype' when used with <ph> or <x>. + + + + + Indicates a inline image. + + + + + Indicates a page break. + + + + + Indicates a line break. + + + + + + + + + + + + Values for the attribute 'datatype'. + + + + + Indicates Active Server Page data. + + + + + Indicates C source file data. + + + + + Indicates Channel Definition Format (CDF) data. + + + + + Indicates ColdFusion data. + + + + + Indicates C++ source file data. + + + + + Indicates C-Sharp data. + + + + + Indicates strings from C, ASM, and driver files data. + + + + + Indicates comma-separated values data. + + + + + Indicates database data. + + + + + Indicates portions of document that follows data and contains metadata. + + + + + Indicates portions of document that precedes data and contains metadata. + + + + + Indicates data from standard UI file operations dialogs (e.g., Open, Save, Save As, Export, Import). + + + + + Indicates standard user input screen data. + + + + + Indicates HyperText Markup Language (HTML) data - document instance. + + + + + Indicates content within an HTML document’s <body> element. + + + + + Indicates Windows INI file data. + + + + + Indicates Interleaf data. + + + + + Indicates Java source file data (extension '.java'). + + + + + Indicates Java property resource bundle data. + + + + + Indicates Java list resource bundle data. + + + + + Indicates JavaScript source file data. + + + + + Indicates JScript source file data. + + + + + Indicates information relating to formatting. + + + + + Indicates LISP source file data. + + + + + Indicates information relating to margin formats. + + + + + Indicates a file containing menu. + + + + + Indicates numerically identified string table. + + + + + Indicates Maker Interchange Format (MIF) data. + + + + + Indicates that the datatype attribute value is a MIME Type value and is defined in the mime-type attribute. + + + + + Indicates GNU Machine Object data. + + + + + Indicates Message Librarian strings created by Novell's Message Librarian Tool. + + + + + Indicates information to be displayed at the bottom of each page of a document. + + + + + Indicates information to be displayed at the top of each page of a document. + + + + + Indicates a list of property values (e.g., settings within INI files or preferences dialog). + + + + + Indicates Pascal source file data. + + + + + Indicates Hypertext Preprocessor data. + + + + + Indicates plain text file (no formatting other than, possibly, wrapping). + + + + + Indicates GNU Portable Object file. + + + + + Indicates dynamically generated user defined document. e.g. Oracle Report, Crystal Report, etc. + + + + + Indicates Windows .NET binary resources. + + + + + Indicates Windows .NET Resources. + + + + + Indicates Rich Text Format (RTF) data. + + + + + Indicates Standard Generalized Markup Language (SGML) data - document instance. + + + + + Indicates Standard Generalized Markup Language (SGML) data - Document Type Definition (DTD). + + + + + Indicates Scalable Vector Graphic (SVG) data. + + + + + Indicates VisualBasic Script source file. + + + + + Indicates warning message. + + + + + Indicates Windows (Win32) resources (i.e. resources extracted from an RC script, a message file, or a compiled file). + + + + + Indicates Extensible HyperText Markup Language (XHTML) data - document instance. + + + + + Indicates Extensible Markup Language (XML) data - document instance. + + + + + Indicates Extensible Markup Language (XML) data - Document Type Definition (DTD). + + + + + Indicates Extensible Stylesheet Language (XSL) data. + + + + + Indicates XUL elements. + + + + + + + Values for the attribute 'mtype'. + + + + + Indicates the marked text is an abbreviation. + + + + + ISO-12620 2.1.8: A term resulting from the omission of any part of the full term while designating the same concept. + + + + + ISO-12620 2.1.8.1: An abbreviated form of a simple term resulting from the omission of some of its letters (e.g. 'adj.' for 'adjective'). + + + + + ISO-12620 2.1.8.4: An abbreviated form of a term made up of letters from the full form of a multiword term strung together into a sequence pronounced only syllabically (e.g. 'radar' for 'radio detecting and ranging'). + + + + + ISO-12620: A proper-name term, such as the name of an agency or other proper entity. + + + + + ISO-12620 2.1.18.1: A recurrent word combination characterized by cohesion in that the components of the collocation must co-occur within an utterance or series of utterances, even though they do not necessarily have to maintain immediate proximity to one another. + + + + + ISO-12620 2.1.5: A synonym for an international scientific term that is used in general discourse in a given language. + + + + + Indicates the marked text is a date and/or time. + + + + + ISO-12620 2.1.15: An expression used to represent a concept based on a statement that two mathematical expressions are, for instance, equal as identified by the equal sign (=), or assigned to one another by a similar sign. + + + + + ISO-12620 2.1.7: The complete representation of a term for which there is an abbreviated form. + + + + + ISO-12620 2.1.14: Figures, symbols or the like used to express a concept briefly, such as a mathematical or chemical formula. + + + + + ISO-12620 2.1.1: The concept designation that has been chosen to head a terminological record. + + + + + ISO-12620 2.1.8.3: An abbreviated form of a term consisting of some of the initial letters of the words making up a multiword term or the term elements making up a compound term when these letters are pronounced individually (e.g. 'BSE' for 'bovine spongiform encephalopathy'). + + + + + ISO-12620 2.1.4: A term that is part of an international scientific nomenclature as adopted by an appropriate scientific body. + + + + + ISO-12620 2.1.6: A term that has the same or nearly identical orthographic or phonemic form in many languages. + + + + + ISO-12620 2.1.16: An expression used to represent a concept based on mathematical or logical relations, such as statements of inequality, set relationships, Boolean operations, and the like. + + + + + ISO-12620 2.1.17: A unit to track object. + + + + + Indicates the marked text is a name. + + + + + ISO-12620 2.1.3: A term that represents the same or a very similar concept as another term in the same language, but for which interchangeability is limited to some contexts and inapplicable in others. + + + + + ISO-12620 2.1.17.2: A unique alphanumeric designation assigned to an object in a manufacturing system. + + + + + Indicates the marked text is a phrase. + + + + + ISO-12620 2.1.18: Any group of two or more words that form a unit, the meaning of which frequently cannot be deduced based on the combined sense of the words making up the phrase. + + + + + Indicates the marked text should not be translated. + + + + + ISO-12620 2.1.12: A form of a term resulting from an operation whereby non-Latin writing systems are converted to the Latin alphabet. + + + + + Indicates that the marked text represents a segment. + + + + + ISO-12620 2.1.18.2: A fixed, lexicalized phrase. + + + + + ISO-12620 2.1.8.2: A variant of a multiword term that includes fewer words than the full form of the term (e.g. 'Group of Twenty-four' for 'Intergovernmental Group of Twenty-four on International Monetary Affairs'). + + + + + ISO-12620 2.1.17.1: Stock keeping unit, an inventory item identified by a unique alphanumeric designation assigned to an object in an inventory control system. + + + + + ISO-12620 2.1.19: A fixed chunk of recurring text. + + + + + ISO-12620 2.1.13: A designation of a concept by letters, numerals, pictograms or any combination thereof. + + + + + ISO-12620 2.1.2: Any term that represents the same or a very similar concept as the main entry term in a term entry. + + + + + ISO-12620 2.1.18.3: Phraseological unit in a language that expresses the same semantic content as another phrase in that same language. + + + + + Indicates the marked text is a term. + + + + + ISO-12620 2.1.11: A form of a term resulting from an operation whereby the characters of one writing system are represented by characters from another writing system, taking into account the pronunciation of the characters converted. + + + + + ISO-12620 2.1.10: A form of a term resulting from an operation whereby the characters of an alphabetic writing system are represented by characters from another alphabetic writing system. + + + + + ISO-12620 2.1.8.5: An abbreviated form of a term resulting from the omission of one or more term elements or syllables (e.g. 'flu' for 'influenza'). + + + + + ISO-12620 2.1.9: One of the alternate forms of a term. + + + + + + + Values for the attribute 'restype'. + + + + + Indicates a Windows RC AUTO3STATE control. + + + + + Indicates a Windows RC AUTOCHECKBOX control. + + + + + Indicates a Windows RC AUTORADIOBUTTON control. + + + + + Indicates a Windows RC BEDIT control. + + + + + Indicates a bitmap, for example a BITMAP resource in Windows. + + + + + Indicates a button object, for example a BUTTON control Windows. + + + + + Indicates a caption, such as the caption of a dialog box. + + + + + Indicates the cell in a table, for example the content of the <td> element in HTML. + + + + + Indicates check box object, for example a CHECKBOX control in Windows. + + + + + Indicates a menu item with an associated checkbox. + + + + + Indicates a list box, but with a check-box for each item. + + + + + Indicates a color selection dialog. + + + + + Indicates a combination of edit box and listbox object, for example a COMBOBOX control in Windows. + + + + + Indicates an initialization entry of an extended combobox DLGINIT resource block. (code 0x1234). + + + + + Indicates an initialization entry of a combobox DLGINIT resource block (code 0x0403). + + + + + Indicates a UI base class element that cannot be represented by any other element. + + + + + Indicates a context menu. + + + + + Indicates a Windows RC CTEXT control. + + + + + Indicates a cursor, for example a CURSOR resource in Windows. + + + + + Indicates a date/time picker. + + + + + Indicates a Windows RC DEFPUSHBUTTON control. + + + + + Indicates a dialog box. + + + + + Indicates a Windows RC DLGINIT resource block. + + + + + Indicates an edit box object, for example an EDIT control in Windows. + + + + + Indicates a filename. + + + + + Indicates a file dialog. + + + + + Indicates a footnote. + + + + + Indicates a font name. + + + + + Indicates a footer. + + + + + Indicates a frame object. + + + + + Indicates a XUL grid element. + + + + + Indicates a groupbox object, for example a GROUPBOX control in Windows. + + + + + Indicates a header item. + + + + + Indicates a heading, such has the content of <h1>, <h2>, etc. in HTML. + + + + + Indicates a Windows RC HEDIT control. + + + + + Indicates a horizontal scrollbar. + + + + + Indicates an icon, for example an ICON resource in Windows. + + + + + Indicates a Windows RC IEDIT control. + + + + + Indicates keyword list, such as the content of the Keywords meta-data in HTML, or a K footnote in WinHelp RTF. + + + + + Indicates a label object. + + + + + Indicates a label that is also a HTML link (not necessarily a URL). + + + + + Indicates a list (a group of list-items, for example an <ol> or <ul> element in HTML). + + + + + Indicates a listbox object, for example an LISTBOX control in Windows. + + + + + Indicates an list item (an entry in a list). + + + + + Indicates a Windows RC LTEXT control. + + + + + Indicates a menu (a group of menu-items). + + + + + Indicates a toolbar containing one or more tope level menus. + + + + + Indicates a menu item (an entry in a menu). + + + + + Indicates a XUL menuseparator element. + + + + + Indicates a message, for example an entry in a MESSAGETABLE resource in Windows. + + + + + Indicates a calendar control. + + + + + Indicates an edit box beside a spin control. + + + + + Indicates a catch all for rectangular areas. + + + + + Indicates a standalone menu not necessarily associated with a menubar. + + + + + Indicates a pushbox object, for example a PUSHBOX control in Windows. + + + + + Indicates a Windows RC PUSHBUTTON control. + + + + + Indicates a radio button object. + + + + + Indicates a menuitem with associated radio button. + + + + + Indicates raw data resources for an application. + + + + + Indicates a row in a table. + + + + + Indicates a Windows RC RTEXT control. + + + + + Indicates a user navigable container used to show a portion of a document. + + + + + Indicates a generic divider object (e.g. menu group separator). + + + + + Windows accelerators, shortcuts in resource or property files. + + + + + Indicates a UI control to indicate process activity but not progress. + + + + + Indicates a splitter bar. + + + + + Indicates a Windows RC STATE3 control. + + + + + Indicates a window for providing feedback to the users, like 'read-only', etc. + + + + + Indicates a string, for example an entry in a STRINGTABLE resource in Windows. + + + + + Indicates a layers of controls with a tab to select layers. + + + + + Indicates a display and edits regular two-dimensional tables of cells. + + + + + Indicates a XUL textbox element. + + + + + Indicates a UI button that can be toggled to on or off state. + + + + + Indicates an array of controls, usually buttons. + + + + + Indicates a pop up tool tip text. + + + + + Indicates a bar with a pointer indicating a position within a certain range. + + + + + Indicates a control that displays a set of hierarchical data. + + + + + Indicates a URI (URN or URL). + + + + + Indicates a Windows RC USERBUTTON control. + + + + + Indicates a user-defined control like CONTROL control in Windows. + + + + + Indicates the text of a variable. + + + + + Indicates version information about a resource like VERSIONINFO in Windows. + + + + + Indicates a vertical scrollbar. + + + + + Indicates a graphical window. + + + + + + + Values for the attribute 'size-unit'. + + + + + Indicates a size in 8-bit bytes. + + + + + Indicates a size in Unicode characters. + + + + + Indicates a size in columns. Used for HTML text area. + + + + + Indicates a size in centimeters. + + + + + Indicates a size in dialog units, as defined in Windows resources. + + + + + Indicates a size in 'font-size' units (as defined in CSS). + + + + + Indicates a size in 'x-height' units (as defined in CSS). + + + + + Indicates a size in glyphs. A glyph is considered to be one or more combined Unicode characters that represent a single displayable text character. Sometimes referred to as a 'grapheme cluster' + + + + + Indicates a size in inches. + + + + + Indicates a size in millimeters. + + + + + Indicates a size in percentage. + + + + + Indicates a size in pixels. + + + + + Indicates a size in point. + + + + + Indicates a size in rows. Used for HTML text area. + + + + + + + Values for the attribute 'state'. + + + + + Indicates the terminating state. + + + + + Indicates only non-textual information needs adaptation. + + + + + Indicates both text and non-textual information needs adaptation. + + + + + Indicates only non-textual information needs review. + + + + + Indicates both text and non-textual information needs review. + + + + + Indicates that only the text of the item needs to be reviewed. + + + + + Indicates that the item needs to be translated. + + + + + Indicates that the item is new. For example, translation units that were not in a previous version of the document. + + + + + Indicates that changes are reviewed and approved. + + + + + Indicates that the item has been translated. + + + + + + + Values for the attribute 'state-qualifier'. + + + + + Indicates an exact match. An exact match occurs when a source text of a segment is exactly the same as the source text of a segment that was translated previously. + + + + + Indicates a fuzzy match. A fuzzy match occurs when a source text of a segment is very similar to the source text of a segment that was translated previously (e.g. when the difference is casing, a few changed words, white-space discripancy, etc.). + + + + + Indicates a match based on matching IDs (in addition to matching text). + + + + + Indicates a translation derived from a glossary. + + + + + Indicates a translation derived from existing translation. + + + + + Indicates a translation derived from machine translation. + + + + + Indicates a translation derived from a translation repository. + + + + + Indicates a translation derived from a translation memory. + + + + + Indicates the translation is suggested by machine translation. + + + + + Indicates that the item has been rejected because of incorrect grammar. + + + + + Indicates that the item has been rejected because it is incorrect. + + + + + Indicates that the item has been rejected because it is too long or too short. + + + + + Indicates that the item has been rejected because of incorrect spelling. + + + + + Indicates the translation is suggested by translation memory. + + + + + + + Values for the attribute 'unit'. + + + + + Refers to words. + + + + + Refers to pages. + + + + + Refers to <trans-unit> elements. + + + + + Refers to <bin-unit> elements. + + + + + Refers to glyphs. + + + + + Refers to <trans-unit> and/or <bin-unit> elements. + + + + + Refers to the occurrences of instances defined by the count-type value. + + + + + Refers to characters. + + + + + Refers to lines. + + + + + Refers to sentences. + + + + + Refers to paragraphs. + + + + + Refers to segments. + + + + + Refers to placeables (inline elements). + + + + + + + Values for the attribute 'priority'. + + + + + Highest priority. + + + + + High priority. + + + + + High priority, but not as important as 2. + + + + + High priority, but not as important as 3. + + + + + Medium priority, but more important than 6. + + + + + Medium priority, but less important than 5. + + + + + Low priority, but more important than 8. + + + + + Low priority, but more important than 9. + + + + + Low priority. + + + + + Lowest priority. + + + + + + + + + This value indicates that all properties can be reformatted. This value must be used alone. + + + + + This value indicates that no properties should be reformatted. This value must be used alone. + + + + + + + + + + + + + This value indicates that all information in the coord attribute can be modified. + + + + + This value indicates that the x information in the coord attribute can be modified. + + + + + This value indicates that the y information in the coord attribute can be modified. + + + + + This value indicates that the cx information in the coord attribute can be modified. + + + + + This value indicates that the cy information in the coord attribute can be modified. + + + + + This value indicates that all the information in the font attribute can be modified. + + + + + This value indicates that the name information in the font attribute can be modified. + + + + + This value indicates that the size information in the font attribute can be modified. + + + + + This value indicates that the weight information in the font attribute can be modified. + + + + + This value indicates that the information in the css-style attribute can be modified. + + + + + This value indicates that the information in the style attribute can be modified. + + + + + This value indicates that the information in the exstyle attribute can be modified. + + + + + + + + + + + + + Indicates that the context is informational in nature, specifying for example, how a term should be translated. Thus, should be displayed to anyone editing the XLIFF document. + + + + + Indicates that the context-group is used to specify where the term was found in the translatable source. Thus, it is not displayed. + + + + + Indicates that the context information should be used during translation memory lookups. Thus, it is not displayed. + + + + + + + + + Represents a translation proposal from a translation memory or other resource. + + + + + Represents a previous version of the target element. + + + + + Represents a rejected version of the target element. + + + + + Represents a translation to be used for reference purposes only, for example from a related product or a different language. + + + + + Represents a proposed translation that was used for the translation of the trans-unit, possibly modified. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Values for the attribute 'coord'. + + + + + + + + Version values: 1.0 and 1.1 are allowed for backward compatibility. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Loader/schema/dic/xliff-core/xml.xsd b/vendor/symfony/translation/Symfony/Component/Translation/Loader/schema/dic/xliff-core/xml.xsd new file mode 100644 index 0000000..8fcb991 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Loader/schema/dic/xliff-core/xml.xsd @@ -0,0 +1,309 @@ + + + + + + +
    +

    About the XML namespace

    + +
    +

    + + This schema document describes the XML namespace, in a form + suitable for import by other schema documents. +

    +

    + See + http://www.w3.org/XML/1998/namespace.html and + + http://www.w3.org/TR/REC-xml for information + about this namespace. +

    + +

    + Note that local names in this namespace are intended to be + defined only by the World Wide Web Consortium or its subgroups. + The names currently defined in this namespace are listed below. + They should not be used with conflicting semantics by any Working + Group, specification, or document instance. +

    +

    + See further below in this document for more information about how to refer to this schema document from your own + XSD schema documents and about the + namespace-versioning policy governing this schema document. +

    +
    +
    + +
    +
    + + + + +
    + +

    lang (as an attribute name)

    +

    + + denotes an attribute whose value + is a language code for the natural language of the content of + any element; its value is inherited. This name is reserved + by virtue of its definition in the XML specification.

    + +
    +
    +

    Notes

    +

    + Attempting to install the relevant ISO 2- and 3-letter + codes as the enumerated possible values is probably never + going to be a realistic possibility. +

    +

    + + See BCP 47 at + http://www.rfc-editor.org/rfc/bcp/bcp47.txt + and the IANA language subtag registry at + + http://www.iana.org/assignments/language-subtag-registry + for further information. +

    +

    + + The union allows for the 'un-declaration' of xml:lang with + the empty string. +

    +
    +
    +
    + + + + + + + + + + +
    + + + + + +
    + +

    space (as an attribute name)

    +

    + denotes an attribute whose + value is a keyword indicating what whitespace processing + discipline is intended for the content of the element; its + value is inherited. This name is reserved by virtue of its + definition in the XML specification.

    + +
    +
    +
    + + + + + + + +
    + + + + +
    + +

    base (as an attribute name)

    +

    + denotes an attribute whose value + provides a URI to be used as the base for interpreting any + relative URIs in the scope of the element on which it + appears; its value is inherited. This name is reserved + by virtue of its definition in the XML Base specification.

    + +

    + See http://www.w3.org/TR/xmlbase/ + for information about this attribute. +

    + +
    +
    +
    +
    + + + + +
    + +

    id (as an attribute name)

    +

    + + denotes an attribute whose value + should be interpreted as if declared to be of type ID. + This name is reserved by virtue of its definition in the + xml:id specification.

    + +

    + See http://www.w3.org/TR/xml-id/ + for information about this attribute. +

    +
    +
    +
    + +
    + + + + + + + + + + + +
    + +

    Father (in any context at all)

    + +
    +

    + denotes Jon Bosak, the chair of + the original XML Working Group. This name is reserved by + the following decision of the W3C XML Plenary and + XML Coordination groups: +

    +
    +

    + + In appreciation for his vision, leadership and + dedication the W3C XML Plenary on this 10th day of + February, 2000, reserves for Jon Bosak in perpetuity + the XML name "xml:Father". +

    +
    +
    +
    +
    +
    + + + + +
    +

    About this schema document

    + +
    +

    + This schema defines attributes and an attribute group suitable + for use by schemas wishing to allow xml:base, + xml:lang, xml:space or + xml:id attributes on elements they define. +

    + +

    + To enable this, such a schema must import this schema for + the XML namespace, e.g. as follows: +

    +
    +          <schema.. .>
    +          .. .
    +           <import namespace="http://www.w3.org/XML/1998/namespace"
    +                      schemaLocation="http://www.w3.org/2001/xml.xsd"/>
    +     
    +

    + or +

    +
    +
    +           <import namespace="http://www.w3.org/XML/1998/namespace"
    +                      schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
    +     
    +

    + Subsequently, qualified reference to any of the attributes or the + group defined below will have the desired effect, e.g. +

    +
    +          <type.. .>
    +          .. .
    +           <attributeGroup ref="xml:specialAttrs"/>
    +     
    +

    + will define a type which will schema-validate an instance element + with any of those attributes. +

    + +
    +
    +
    +
    + + + +
    +

    Versioning policy for this schema document

    + +
    +

    + In keeping with the XML Schema WG's standard versioning + policy, this schema document will persist at + + http://www.w3.org/2009/01/xml.xsd. +

    +

    + At the date of issue it can also be found at + + http://www.w3.org/2001/xml.xsd. +

    + +

    + The schema document at that URI may however change in the future, + in order to remain compatible with the latest version of XML + Schema itself, or with the XML namespace itself. In other words, + if the XML Schema or XML namespaces change, the version of this + document at + http://www.w3.org/2001/xml.xsd + + will change accordingly; the version at + + http://www.w3.org/2009/01/xml.xsd + + will not change. +

    +

    + + Previous dated (and unchanging) versions of this schema + document are at: +

    + +
    +
    +
    +
    + +
    diff --git a/vendor/symfony/translation/Symfony/Component/Translation/MessageCatalogue.php b/vendor/symfony/translation/Symfony/Component/Translation/MessageCatalogue.php new file mode 100644 index 0000000..1d8a08d --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/MessageCatalogue.php @@ -0,0 +1,295 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * MessageCatalogue. + * + * @author Fabien Potencier + * + * @api + */ +class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface +{ + private $messages = array(); + private $metadata = array(); + private $resources = array(); + private $locale; + private $fallbackCatalogue; + private $parent; + + /** + * Constructor. + * + * @param string $locale The locale + * @param array $messages An array of messages classified by domain + * + * @api + */ + public function __construct($locale, array $messages = array()) + { + $this->locale = $locale; + $this->messages = $messages; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function getLocale() + { + return $this->locale; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function getDomains() + { + return array_keys($this->messages); + } + + /** + * {@inheritdoc} + * + * @api + */ + public function all($domain = null) + { + if (null === $domain) { + return $this->messages; + } + + return isset($this->messages[$domain]) ? $this->messages[$domain] : array(); + } + + /** + * {@inheritdoc} + * + * @api + */ + public function set($id, $translation, $domain = 'messages') + { + $this->add(array($id => $translation), $domain); + } + + /** + * {@inheritdoc} + * + * @api + */ + public function has($id, $domain = 'messages') + { + if (isset($this->messages[$domain][$id])) { + return true; + } + + if (null !== $this->fallbackCatalogue) { + return $this->fallbackCatalogue->has($id, $domain); + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function defines($id, $domain = 'messages') + { + return isset($this->messages[$domain][$id]); + } + + /** + * {@inheritdoc} + * + * @api + */ + public function get($id, $domain = 'messages') + { + if (isset($this->messages[$domain][$id])) { + return $this->messages[$domain][$id]; + } + + if (null !== $this->fallbackCatalogue) { + return $this->fallbackCatalogue->get($id, $domain); + } + + return $id; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function replace($messages, $domain = 'messages') + { + $this->messages[$domain] = array(); + + $this->add($messages, $domain); + } + + /** + * {@inheritdoc} + * + * @api + */ + public function add($messages, $domain = 'messages') + { + if (!isset($this->messages[$domain])) { + $this->messages[$domain] = $messages; + } else { + $this->messages[$domain] = array_replace($this->messages[$domain], $messages); + } + } + + /** + * {@inheritdoc} + * + * @api + */ + public function addCatalogue(MessageCatalogueInterface $catalogue) + { + if ($catalogue->getLocale() !== $this->locale) { + throw new \LogicException(sprintf('Cannot add a catalogue for locale "%s" as the current locale for this catalogue is "%s"', $catalogue->getLocale(), $this->locale)); + } + + foreach ($catalogue->all() as $domain => $messages) { + $this->add($messages, $domain); + } + + foreach ($catalogue->getResources() as $resource) { + $this->addResource($resource); + } + + if ($catalogue instanceof MetadataAwareInterface) { + $metadata = $catalogue->getMetadata('', ''); + $this->addMetadata($metadata); + } + } + + /** + * {@inheritdoc} + * + * @api + */ + public function addFallbackCatalogue(MessageCatalogueInterface $catalogue) + { + // detect circular references + $c = $this; + do { + if ($c->getLocale() === $catalogue->getLocale()) { + throw new \LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); + } + } while ($c = $c->parent); + + $catalogue->parent = $this; + $this->fallbackCatalogue = $catalogue; + + foreach ($catalogue->getResources() as $resource) { + $this->addResource($resource); + } + } + + /** + * {@inheritdoc} + * + * @api + */ + public function getFallbackCatalogue() + { + return $this->fallbackCatalogue; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function getResources() + { + return array_values($this->resources); + } + + /** + * {@inheritdoc} + * + * @api + */ + public function addResource(ResourceInterface $resource) + { + $this->resources[$resource->__toString()] = $resource; + } + + /** + * {@inheritdoc} + */ + public function getMetadata($key = '', $domain = 'messages') + { + if ('' == $domain) { + return $this->metadata; + } + + if (isset($this->metadata[$domain])) { + if ('' == $key) { + return $this->metadata[$domain]; + } + + if (isset($this->metadata[$domain][$key])) { + return $this->metadata[$domain][$key]; + } + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function setMetadata($key, $value, $domain = 'messages') + { + $this->metadata[$domain][$key] = $value; + } + + /** + * {@inheritdoc} + */ + public function deleteMetadata($key = '', $domain = 'messages') + { + if ('' == $domain) { + $this->metadata = array(); + } elseif ('' == $key) { + unset($this->metadata[$domain]); + } else { + unset($this->metadata[$domain][$key]); + } + } + + /** + * Adds current values with the new values. + * + * @param array $values Values to add + */ + private function addMetadata(array $values) + { + foreach ($values as $domain => $keys) { + foreach ($keys as $key => $value) { + $this->setMetadata($key, $value, $domain); + } + } + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/MessageCatalogueInterface.php b/vendor/symfony/translation/Symfony/Component/Translation/MessageCatalogueInterface.php new file mode 100644 index 0000000..5e9be20 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/MessageCatalogueInterface.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * MessageCatalogueInterface. + * + * @author Fabien Potencier + * + * @api + */ +interface MessageCatalogueInterface +{ + /** + * Gets the catalogue locale. + * + * @return string The locale + * + * @api + */ + public function getLocale(); + + /** + * Gets the domains. + * + * @return array An array of domains + * + * @api + */ + public function getDomains(); + + /** + * Gets the messages within a given domain. + * + * If $domain is null, it returns all messages. + * + * @param string $domain The domain name + * + * @return array An array of messages + * + * @api + */ + public function all($domain = null); + + /** + * Sets a message translation. + * + * @param string $id The message id + * @param string $translation The messages translation + * @param string $domain The domain name + * + * @api + */ + public function set($id, $translation, $domain = 'messages'); + + /** + * Checks if a message has a translation. + * + * @param string $id The message id + * @param string $domain The domain name + * + * @return Boolean true if the message has a translation, false otherwise + * + * @api + */ + public function has($id, $domain = 'messages'); + + /** + * Checks if a message has a translation (it does not take into account the fallback mechanism). + * + * @param string $id The message id + * @param string $domain The domain name + * + * @return Boolean true if the message has a translation, false otherwise + * + * @api + */ + public function defines($id, $domain = 'messages'); + + /** + * Gets a message translation. + * + * @param string $id The message id + * @param string $domain The domain name + * + * @return string The message translation + * + * @api + */ + public function get($id, $domain = 'messages'); + + /** + * Sets translations for a given domain. + * + * @param array $messages An array of translations + * @param string $domain The domain name + * + * @api + */ + public function replace($messages, $domain = 'messages'); + + /** + * Adds translations for a given domain. + * + * @param array $messages An array of translations + * @param string $domain The domain name + * + * @api + */ + public function add($messages, $domain = 'messages'); + + /** + * Merges translations from the given Catalogue into the current one. + * + * The two catalogues must have the same locale. + * + * @param MessageCatalogueInterface $catalogue A MessageCatalogueInterface instance + * + * @api + */ + public function addCatalogue(MessageCatalogueInterface $catalogue); + + /** + * Merges translations from the given Catalogue into the current one + * only when the translation does not exist. + * + * This is used to provide default translations when they do not exist for the current locale. + * + * @param MessageCatalogueInterface $catalogue A MessageCatalogueInterface instance + * + * @api + */ + public function addFallbackCatalogue(MessageCatalogueInterface $catalogue); + + /** + * Gets the fallback catalogue. + * + * @return MessageCatalogueInterface|null A MessageCatalogueInterface instance or null when no fallback has been set + * + * @api + */ + public function getFallbackCatalogue(); + + /** + * Returns an array of resources loaded to build this collection. + * + * @return ResourceInterface[] An array of resources + * + * @api + */ + public function getResources(); + + /** + * Adds a resource for this collection. + * + * @param ResourceInterface $resource A resource instance + * + * @api + */ + public function addResource(ResourceInterface $resource); +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/MessageSelector.php b/vendor/symfony/translation/Symfony/Component/Translation/MessageSelector.php new file mode 100644 index 0000000..8b416e9 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/MessageSelector.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +/** + * MessageSelector. + * + * @author Fabien Potencier + * @author Bernhard Schussek + * + * @api + */ +class MessageSelector +{ + /** + * Given a message with different plural translations separated by a + * pipe (|), this method returns the correct portion of the message based + * on the given number, locale and the pluralization rules in the message + * itself. + * + * The message supports two different types of pluralization rules: + * + * interval: {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples + * indexed: There is one apple|There are %count% apples + * + * The indexed solution can also contain labels (e.g. one: There is one apple). + * This is purely for making the translations more clear - it does not + * affect the functionality. + * + * The two methods can also be mixed: + * {0} There are no apples|one: There is one apple|more: There are %count% apples + * + * @param string $message The message being translated + * @param integer $number The number of items represented for the message + * @param string $locale The locale to use for choosing + * + * @return string + * + * @throws \InvalidArgumentException + * + * @api + */ + public function choose($message, $number, $locale) + { + $parts = explode('|', $message); + $explicitRules = array(); + $standardRules = array(); + foreach ($parts as $part) { + $part = trim($part); + + if (preg_match('/^(?P'.Interval::getIntervalRegexp().')\s*(?P.*?)$/x', $part, $matches)) { + $explicitRules[$matches['interval']] = $matches['message']; + } elseif (preg_match('/^\w+\:\s*(.*?)$/', $part, $matches)) { + $standardRules[] = $matches[1]; + } else { + $standardRules[] = $part; + } + } + + // try to match an explicit rule, then fallback to the standard ones + foreach ($explicitRules as $interval => $m) { + if (Interval::test($number, $interval)) { + return $m; + } + } + + $position = PluralizationRules::get($number, $locale); + + if (!isset($standardRules[$position])) { + // when there's exactly one rule given, and that rule is a standard + // rule, use this rule + if (1 === count($parts) && isset($standardRules[0])) { + return $standardRules[0]; + } + + throw new \InvalidArgumentException(sprintf('Unable to choose a translation for "%s" with locale "%s". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $message, $locale)); + } + + return $standardRules[$position]; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/MetadataAwareInterface.php b/vendor/symfony/translation/Symfony/Component/Translation/MetadataAwareInterface.php new file mode 100644 index 0000000..1c43935 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/MetadataAwareInterface.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +/** + * MetadataAwareInterface. + * + * @author Fabien Potencier + */ +interface MetadataAwareInterface +{ + /** + * Gets metadata for the given domain and key. + * + * Passing an empty domain will return an array with all metadata indexed by + * domain and then by key. Passing an empty key will return an array with all + * metadata for the given domain. + * + * @param string $domain The domain name + * @param string $key The key + * + * @return mixed The value that was set or an array with the domains/keys or null + */ + public function getMetadata($key = '', $domain = 'messages'); + + /** + * Adds metadata to a message domain. + * + * @param string $key The key + * @param mixed $value The value + * @param string $domain The domain name + */ + public function setMetadata($key, $value, $domain = 'messages'); + + /** + * Deletes metadata for the given key and domain. + * + * Passing an empty domain will delete all metadata. Passing an empty key will + * delete all metadata for the given domain. + * + * @param string $domain The domain name + * @param string $key The key + */ + public function deleteMetadata($key = '', $domain = 'messages'); +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/PluralizationRules.php b/vendor/symfony/translation/Symfony/Component/Translation/PluralizationRules.php new file mode 100644 index 0000000..5c63b8d --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/PluralizationRules.php @@ -0,0 +1,219 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +/** + * Returns the plural rules for a given locale. + * + * @author Fabien Potencier + */ +class PluralizationRules +{ + // @codeCoverageIgnoreStart + private static $rules = array(); + + /** + * Returns the plural position to use for the given locale and number. + * + * @param integer $number The number + * @param string $locale The locale + * + * @return integer The plural position + */ + public static function get($number, $locale) + { + if ("pt_BR" == $locale) { + // temporary set a locale for brazilian + $locale = "xbr"; + } + + if (strlen($locale) > 3) { + $locale = substr($locale, 0, -strlen(strrchr($locale, '_'))); + } + + if (isset(self::$rules[$locale])) { + $return = call_user_func(self::$rules[$locale], $number); + + if (!is_int($return) || $return < 0) { + return 0; + } + + return $return; + } + + /* + * The plural rules are derived from code of the Zend Framework (2010-09-25), + * which is subject to the new BSD license (http://framework.zend.com/license/new-bsd). + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + */ + switch ($locale) { + case 'bo': + case 'dz': + case 'id': + case 'ja': + case 'jv': + case 'ka': + case 'km': + case 'kn': + case 'ko': + case 'ms': + case 'th': + case 'tr': + case 'vi': + case 'zh': + return 0; + break; + + case 'af': + case 'az': + case 'bn': + case 'bg': + case 'ca': + case 'da': + case 'de': + case 'el': + case 'en': + case 'eo': + case 'es': + case 'et': + case 'eu': + case 'fa': + case 'fi': + case 'fo': + case 'fur': + case 'fy': + case 'gl': + case 'gu': + case 'ha': + case 'he': + case 'hu': + case 'is': + case 'it': + case 'ku': + case 'lb': + case 'ml': + case 'mn': + case 'mr': + case 'nah': + case 'nb': + case 'ne': + case 'nl': + case 'nn': + case 'no': + case 'om': + case 'or': + case 'pa': + case 'pap': + case 'ps': + case 'pt': + case 'so': + case 'sq': + case 'sv': + case 'sw': + case 'ta': + case 'te': + case 'tk': + case 'ur': + case 'zu': + return ($number == 1) ? 0 : 1; + + case 'am': + case 'bh': + case 'fil': + case 'fr': + case 'gun': + case 'hi': + case 'ln': + case 'mg': + case 'nso': + case 'xbr': + case 'ti': + case 'wa': + return (($number == 0) || ($number == 1)) ? 0 : 1; + + case 'be': + case 'bs': + case 'hr': + case 'ru': + case 'sr': + case 'uk': + return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + + case 'cs': + case 'sk': + return ($number == 1) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2); + + case 'ga': + return ($number == 1) ? 0 : (($number == 2) ? 1 : 2); + + case 'lt': + return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + + case 'sl': + return ($number % 100 == 1) ? 0 : (($number % 100 == 2) ? 1 : ((($number % 100 == 3) || ($number % 100 == 4)) ? 2 : 3)); + + case 'mk': + return ($number % 10 == 1) ? 0 : 1; + + case 'mt': + return ($number == 1) ? 0 : ((($number == 0) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3)); + + case 'lv': + return ($number == 0) ? 0 : ((($number % 10 == 1) && ($number % 100 != 11)) ? 1 : 2); + + case 'pl': + return ($number == 1) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2); + + case 'cy': + return ($number == 1) ? 0 : (($number == 2) ? 1 : ((($number == 8) || ($number == 11)) ? 2 : 3)); + + case 'ro': + return ($number == 1) ? 0 : ((($number == 0) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2); + + case 'ar': + return ($number == 0) ? 0 : (($number == 1) ? 1 : (($number == 2) ? 2 : ((($number >= 3) && ($number <= 10)) ? 3 : ((($number >= 11) && ($number <= 99)) ? 4 : 5)))); + + default: + return 0; + } + } + + /** + * Overrides the default plural rule for a given locale. + * + * @param string $rule A PHP callable + * @param string $locale The locale + * + * @return null + * + * @throws \LogicException + */ + public static function set($rule, $locale) + { + if ("pt_BR" == $locale) { + // temporary set a locale for brazilian + $locale = "xbr"; + } + + if (strlen($locale) > 3) { + $locale = substr($locale, 0, -strlen(strrchr($locale, '_'))); + } + + if (!is_callable($rule)) { + throw new \LogicException('The given rule can not be called'); + } + + self::$rules[$locale] = $rule; + } + + // @codeCoverageIgnoreEnd +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/README.md b/vendor/symfony/translation/Symfony/Component/Translation/README.md new file mode 100644 index 0000000..24e6210 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/README.md @@ -0,0 +1,35 @@ +Translation Component +===================== + +Translation provides tools for loading translation files and generating +translated strings from these including support for pluralization. + + use Symfony\Component\Translation\Translator; + use Symfony\Component\Translation\MessageSelector; + use Symfony\Component\Translation\Loader\ArrayLoader; + + $translator = new Translator('fr_FR', new MessageSelector()); + $translator->setFallbackLocales(array('fr')); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array( + 'Hello World!' => 'Bonjour', + ), 'fr'); + + echo $translator->trans('Hello World!')."\n"; + +Resources +--------- + +Silex integration: + +https://github.com/fabpot/Silex/blob/master/src/Silex/Provider/TranslationServiceProvider.php + +Documentation: + +http://symfony.com/doc/2.3/book/translation.html + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/Translation/ + $ composer.phar install --dev + $ phpunit diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Catalogue/AbstractOperationTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Catalogue/AbstractOperationTest.php new file mode 100644 index 0000000..78023e1 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Catalogue/AbstractOperationTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Test\Catalogue; + +use Symfony\Bundle\FrameworkBundle\Tests\TestCase; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueInterface; + +abstract class AbstractOperationTest extends TestCase +{ + public function testGetEmptyDomains() + { + $this->assertEquals( + array(), + $this->createOperation( + new MessageCatalogue('en'), + new MessageCatalogue('en') + )->getDomains() + ); + } + + public function testGetMergedDomains() + { + $this->assertEquals( + array('a', 'b', 'c'), + $this->createOperation( + new MessageCatalogue('en', array('a' => array(), 'b' => array())), + new MessageCatalogue('en', array('b' => array(), 'c' => array())) + )->getDomains() + ); + } + + public function testGetMessagesFromUnknownDomain() + { + $this->setExpectedException('InvalidArgumentException'); + $this->createOperation( + new MessageCatalogue('en'), + new MessageCatalogue('en') + )->getMessages('domain'); + } + + public function testGetEmptyMessages() + { + $this->assertEquals( + array(), + $this->createOperation( + new MessageCatalogue('en', array('a' => array())), + new MessageCatalogue('en') + )->getMessages('a') + ); + } + + public function testGetEmptyResult() + { + $this->assertEquals( + new MessageCatalogue('en'), + $this->createOperation( + new MessageCatalogue('en'), + new MessageCatalogue('en') + )->getResult() + ); + } + + abstract protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target); +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Catalogue/DiffOperationTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Catalogue/DiffOperationTest.php new file mode 100644 index 0000000..b2e852d --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Catalogue/DiffOperationTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Test\Catalogue; + +use Symfony\Component\Translation\Catalogue\DiffOperation; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueInterface; + +class DiffOperationTest extends AbstractOperationTest +{ + public function testGetMessagesFromSingleDomain() + { + $operation = $this->createOperation( + new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b'))), + new MessageCatalogue('en', array('messages' => array('a' => 'new_a', 'c' => 'new_c'))) + ); + + $this->assertEquals( + array('a' => 'old_a', 'c' => 'new_c'), + $operation->getMessages('messages') + ); + + $this->assertEquals( + array('c' => 'new_c'), + $operation->getNewMessages('messages') + ); + + $this->assertEquals( + array('b' => 'old_b'), + $operation->getObsoleteMessages('messages') + ); + } + + public function testGetResultFromSingleDomain() + { + $this->assertEquals( + new MessageCatalogue('en', array( + 'messages' => array('a' => 'old_a', 'c' => 'new_c') + )), + $this->createOperation( + new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b'))), + new MessageCatalogue('en', array('messages' => array('a' => 'new_a', 'c' => 'new_c'))) + )->getResult() + ); + } + + protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target) + { + return new DiffOperation($source, $target); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Catalogue/MergeOperationTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Catalogue/MergeOperationTest.php new file mode 100644 index 0000000..fa5118a --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Catalogue/MergeOperationTest.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Test\Catalogue; + +use Symfony\Component\Translation\Catalogue\MergeOperation; +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\MessageCatalogueInterface; + +class MergeOperationTest extends AbstractOperationTest +{ + public function testGetMessagesFromSingleDomain() + { + $operation = $this->createOperation( + new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b'))), + new MessageCatalogue('en', array('messages' => array('a' => 'new_a', 'c' => 'new_c'))) + ); + + $this->assertEquals( + array('a' => 'old_a', 'b' => 'old_b', 'c' => 'new_c'), + $operation->getMessages('messages') + ); + + $this->assertEquals( + array('c' => 'new_c'), + $operation->getNewMessages('messages') + ); + + $this->assertEquals( + array(), + $operation->getObsoleteMessages('messages') + ); + } + + public function testGetResultFromSingleDomain() + { + $this->assertEquals( + new MessageCatalogue('en', array( + 'messages' => array('a' => 'old_a', 'b' => 'old_b', 'c' => 'new_c') + )), + $this->createOperation( + new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b'))), + new MessageCatalogue('en', array('messages' => array('a' => 'new_a', 'c' => 'new_c'))) + )->getResult() + ); + } + + protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target) + { + return new MergeOperation($source, $target); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/CsvFileDumperTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/CsvFileDumperTest.php new file mode 100644 index 0000000..29177ff --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/CsvFileDumperTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Dumper\CsvFileDumper; + +class CsvFileDumperTest extends \PHPUnit_Framework_TestCase +{ + public function testDump() + { + $catalogue = new MessageCatalogue('en'); + $catalogue->add(array('foo' => 'bar', 'bar' => 'foo +foo', 'foo;foo' => 'bar')); + + $tempDir = sys_get_temp_dir(); + $dumper = new CsvFileDumper(); + $dumper->dump($catalogue, array('path' => $tempDir)); + + $this->assertEquals(file_get_contents(__DIR__.'/../fixtures/valid.csv'), file_get_contents($tempDir.'/messages.en.csv')); + + unlink($tempDir.'/messages.en.csv'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/IcuResFileDumperTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/IcuResFileDumperTest.php new file mode 100644 index 0000000..d187ef1 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/IcuResFileDumperTest.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Dumper\IcuResFileDumper; + +class IcuResFileDumperTest extends \PHPUnit_Framework_TestCase +{ + public function testDump() + { + if (!extension_loaded('mbstring')) { + $this->markTestSkipped('This test requires mbstring to work.'); + } + + $catalogue = new MessageCatalogue('en'); + $catalogue->add(array('foo' => 'bar')); + + $tempDir = sys_get_temp_dir(); + $dumper = new IcuResFileDumper(); + $dumper->dump($catalogue, array('path' => $tempDir)); + + $this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resourcebundle/res/en.res'), file_get_contents($tempDir.'/messages/en.res')); + + unlink($tempDir.'/messages/en.res'); + rmdir($tempDir.'/messages'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/IniFileDumperTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/IniFileDumperTest.php new file mode 100644 index 0000000..2a2cefd --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/IniFileDumperTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Dumper\IniFileDumper; + +class IniFileDumperTest extends \PHPUnit_Framework_TestCase +{ + public function testDump() + { + $catalogue = new MessageCatalogue('en'); + $catalogue->add(array('foo' => 'bar')); + + $tempDir = sys_get_temp_dir(); + $dumper = new IniFileDumper(); + $dumper->dump($catalogue, array('path' => $tempDir)); + + $this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.ini'), file_get_contents($tempDir.'/messages.en.ini')); + + unlink($tempDir.'/messages.en.ini'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/MoFileDumperTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/MoFileDumperTest.php new file mode 100644 index 0000000..439a25c --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/MoFileDumperTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Dumper\MoFileDumper; + +class MoFileDumperTest extends \PHPUnit_Framework_TestCase +{ + public function testDump() + { + $catalogue = new MessageCatalogue('en'); + $catalogue->add(array('foo' => 'bar')); + + $tempDir = sys_get_temp_dir(); + $dumper = new MoFileDumper(); + $dumper->dump($catalogue, array('path' => $tempDir)); + $this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.mo'), file_get_contents($tempDir.'/messages.en.mo')); + + unlink($tempDir.'/messages.en.mo'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/PhpFileDumperTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/PhpFileDumperTest.php new file mode 100644 index 0000000..18be5a0 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/PhpFileDumperTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Dumper\PhpFileDumper; + +class PhpFileDumperTest extends \PHPUnit_Framework_TestCase +{ + public function testDump() + { + $catalogue = new MessageCatalogue('en'); + $catalogue->add(array('foo' => 'bar')); + + $tempDir = sys_get_temp_dir(); + $dumper = new PhpFileDumper(); + $dumper->dump($catalogue, array('path' => $tempDir)); + + $this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.php'), file_get_contents($tempDir.'/messages.en.php')); + + unlink($tempDir.'/messages.en.php'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/PoFileDumperTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/PoFileDumperTest.php new file mode 100644 index 0000000..0296d6b --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/PoFileDumperTest.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Dumper\PoFileDumper; + +class PoFileDumperTest extends \PHPUnit_Framework_TestCase +{ + public function testDump() + { + $catalogue = new MessageCatalogue('en'); + $catalogue->add(array('foo' => 'bar')); + + $tempDir = sys_get_temp_dir(); + $dumper = new PoFileDumper(); + $dumper->dump($catalogue, array('path' => $tempDir)); + $this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.po'), file_get_contents($tempDir.'/messages.en.po')); + + unlink($tempDir.'/messages.en.po'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/QtFileDumperTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/QtFileDumperTest.php new file mode 100644 index 0000000..d7d8fb7 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/QtFileDumperTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Dumper\QtFileDumper; + +class QtFileDumperTest extends \PHPUnit_Framework_TestCase +{ + public function testDump() + { + $catalogue = new MessageCatalogue('en'); + $catalogue->add(array('foo' => 'bar'), 'resources'); + + $tempDir = sys_get_temp_dir(); + $dumper = new QtFileDumper(); + $dumper->dump($catalogue, array('path' => $tempDir)); + + $this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.ts'), file_get_contents($tempDir.'/resources.en.ts')); + + unlink($tempDir.'/resources.en.ts'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/XliffFileDumperTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/XliffFileDumperTest.php new file mode 100644 index 0000000..bef3135 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/XliffFileDumperTest.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Dumper\XliffFileDumper; + +class XliffFileDumperTest extends \PHPUnit_Framework_TestCase +{ + public function testDump() + { + $catalogue = new MessageCatalogue('en'); + $catalogue->add(array('foo' => 'bar', 'key' => '')); + + $tempDir = sys_get_temp_dir(); + $dumper = new XliffFileDumper(); + $dumper->dump($catalogue, array('path' => $tempDir)); + + $this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources-clean.xlf'), file_get_contents($tempDir.'/messages.en.xlf')); + + unlink($tempDir.'/messages.en.xlf'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/YamlFileDumperTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/YamlFileDumperTest.php new file mode 100644 index 0000000..e4e68e0 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Dumper/YamlFileDumperTest.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Dumper; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Dumper\YamlFileDumper; + +class YamlFileDumperTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\Yaml\Yaml')) { + $this->markTestSkipped('The "Yaml" component is not available'); + } + } + + public function testDump() + { + $catalogue = new MessageCatalogue('en'); + $catalogue->add(array('foo' => 'bar')); + + $tempDir = sys_get_temp_dir(); + $dumper = new YamlFileDumper(); + $dumper->dump($catalogue, array('path' => $tempDir)); + + $this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.yml'), file_get_contents($tempDir.'/messages.en.yml')); + + unlink($tempDir.'/messages.en.yml'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/IdentityTranslatorTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/IdentityTranslatorTest.php new file mode 100644 index 0000000..cec702a --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/IdentityTranslatorTest.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests; + +use Symfony\Component\Translation\IdentityTranslator; +use Symfony\Component\Translation\MessageSelector; + +class IdentityTranslatorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getTransTests + */ + public function testTrans($expected, $id, $parameters) + { + $translator = new IdentityTranslator(new MessageSelector()); + + $this->assertEquals($expected, $translator->trans($id, $parameters)); + } + + /** + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithExplicitLocale($expected, $id, $number, $parameters) + { + $translator = new IdentityTranslator(new MessageSelector()); + $translator->setLocale('en'); + + $this->assertEquals($expected, $translator->transChoice($id, $number, $parameters)); + } + + /** + * @dataProvider getTransChoiceTests + */ + public function testTransChoiceWithDefaultLocale($expected, $id, $number, $parameters) + { + \Locale::setDefault('en'); + + $translator = new IdentityTranslator(new MessageSelector()); + + $this->assertEquals($expected, $translator->transChoice($id, $number, $parameters)); + } + + public function testGetSetLocale() + { + $translator = new IdentityTranslator(new MessageSelector()); + $translator->setLocale('en'); + + $this->assertEquals('en', $translator->getLocale()); + } + + public function testGetLocaleReturnsDefaultLocaleIfNotSet() + { + $translator = new IdentityTranslator(new MessageSelector()); + + \Locale::setDefault('en'); + $this->assertEquals('en', $translator->getLocale()); + + \Locale::setDefault('pt_BR'); + $this->assertEquals('pt_BR', $translator->getLocale()); + } + + public function getTransTests() + { + return array( + array('Symfony2 is great!', 'Symfony2 is great!', array()), + array('Symfony2 is awesome!', 'Symfony2 is %what%!', array('%what%' => 'awesome')), + ); + } + + public function getTransChoiceTests() + { + return array( + array('There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0, array('%count%' => 0)), + array('There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1, array('%count%' => 1)), + array('There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10, array('%count%' => 10)), + array('There are 0 apples', 'There is 1 apple|There are %count% apples', 0, array('%count%' => 0)), + array('There is 1 apple', 'There is 1 apple|There are %count% apples', 1, array('%count%' => 1)), + array('There are 10 apples', 'There is 1 apple|There are %count% apples', 10, array('%count%' => 10)), + // custom validation messages may be coded with a fixed value + array('There are 2 apples', 'There are 2 apples', 2, array('%count%' => 2)), + ); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/IntervalTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/IntervalTest.php new file mode 100644 index 0000000..075c98b --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/IntervalTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests; + +use Symfony\Component\Translation\Interval; + +class IntervalTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getTests + */ + public function testTest($expected, $number, $interval) + { + $this->assertEquals($expected, Interval::test($number, $interval)); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testTestException() + { + Interval::test(1, 'foobar'); + } + + public function getTests() + { + return array( + array(true, 3, '{1,2, 3 ,4}'), + array(false, 10, '{1,2, 3 ,4}'), + array(false, 3, '[1,2]'), + array(true, 1, '[1,2]'), + array(true, 2, '[1,2]'), + array(false, 1, ']1,2['), + array(false, 2, ']1,2['), + array(true, log(0), '[-Inf,2['), + array(true, -log(0), '[-2,+Inf]'), + ); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/CsvFileLoaderTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/CsvFileLoaderTest.php new file mode 100644 index 0000000..59569a3 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/CsvFileLoaderTest.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Loader; + +use Symfony\Component\Translation\Loader\CsvFileLoader; +use Symfony\Component\Config\Resource\FileResource; + +class CsvFileLoaderTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\Config\Loader\Loader')) { + $this->markTestSkipped('The "Config" component is not available'); + } + } + + public function testLoad() + { + $loader = new CsvFileLoader(); + $resource = __DIR__.'/../fixtures/resources.csv'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertEquals(array('foo' => 'bar'), $catalogue->all('domain1')); + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource)), $catalogue->getResources()); + } + + public function testLoadDoesNothingIfEmpty() + { + $loader = new CsvFileLoader(); + $resource = __DIR__.'/../fixtures/empty.csv'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertEquals(array(), $catalogue->all('domain1')); + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource)), $catalogue->getResources()); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException + */ + public function testLoadNonExistingResource() + { + $loader = new CsvFileLoader(); + $resource = __DIR__.'/../fixtures/not-exists.csv'; + $loader->load($resource, 'en', 'domain1'); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException + */ + public function testLoadNonLocalResource() + { + $loader = new CsvFileLoader(); + $resource = 'http://example.com/resources.csv'; + $loader->load($resource, 'en', 'domain1'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/IcuDatFileLoaderTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/IcuDatFileLoaderTest.php new file mode 100644 index 0000000..a3bd67a --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/IcuDatFileLoaderTest.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Loader; + +use Symfony\Component\Translation\Loader\IcuDatFileLoader; +use Symfony\Component\Config\Resource\FileResource; + +class IcuDatFileLoaderTest extends LocalizedTestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\Config\Loader\Loader')) { + $this->markTestSkipped('The "Config" component is not available'); + } + + if (!extension_loaded('intl')) { + $this->markTestSkipped('This test requires intl extension to work.'); + } + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException + */ + public function testLoadInvalidResource() + { + $loader = new IcuDatFileLoader(); + $loader->load(__DIR__.'/../fixtures/resourcebundle/corrupted/resources', 'es', 'domain2'); + } + + public function testDatEnglishLoad() + { + // bundled resource is build using pkgdata command which at least in ICU 4.2 comes in extremely! buggy form + // you must specify an temporary build directory which is not the same as current directory and + // MUST reside on the same partition. pkgdata -p resources -T /srv -d.packagelist.txt + $loader = new IcuDatFileLoader(); + $resource = __DIR__.'/../fixtures/resourcebundle/dat/resources'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertEquals(array('symfony' => 'Symfony 2 is great'), $catalogue->all('domain1')); + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource.'.dat')), $catalogue->getResources()); + } + + public function testDatFrenchLoad() + { + $loader = new IcuDatFileLoader(); + $resource = __DIR__.'/../fixtures/resourcebundle/dat/resources'; + $catalogue = $loader->load($resource, 'fr', 'domain1'); + + $this->assertEquals(array('symfony' => 'Symfony 2 est génial'), $catalogue->all('domain1')); + $this->assertEquals('fr', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource.'.dat')), $catalogue->getResources()); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException + */ + public function testLoadNonExistingResource() + { + $loader = new IcuDatFileLoader(); + $loader->load(__DIR__.'/../fixtures/non-existing.txt', 'en', 'domain1'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/IcuResFileLoaderTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/IcuResFileLoaderTest.php new file mode 100644 index 0000000..233e189 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/IcuResFileLoaderTest.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Loader; + +use Symfony\Component\Translation\Loader\IcuResFileLoader; +use Symfony\Component\Config\Resource\DirectoryResource; + +class IcuResFileLoaderTest extends LocalizedTestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\Config\Loader\Loader')) { + $this->markTestSkipped('The "Config" component is not available'); + } + + if (!extension_loaded('intl')) { + $this->markTestSkipped('This test requires intl extension to work.'); + } + } + + public function testLoad() + { + // resource is build using genrb command + $loader = new IcuResFileLoader(); + $resource = __DIR__.'/../fixtures/resourcebundle/res'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertEquals(array('foo' => 'bar'), $catalogue->all('domain1')); + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new DirectoryResource($resource)), $catalogue->getResources()); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException + */ + public function testLoadNonExistingResource() + { + $loader = new IcuResFileLoader(); + $loader->load(__DIR__.'/../fixtures/non-existing.txt', 'en', 'domain1'); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException + */ + public function testLoadInvalidResource() + { + $loader = new IcuResFileLoader(); + $loader->load(__DIR__.'/../fixtures/resourcebundle/corrupted', 'en', 'domain1'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/IniFileLoaderTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/IniFileLoaderTest.php new file mode 100644 index 0000000..ae1289d --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/IniFileLoaderTest.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Loader; + +use Symfony\Component\Translation\Loader\IniFileLoader; +use Symfony\Component\Config\Resource\FileResource; + +class IniFileLoaderTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\Config\Loader\Loader')) { + $this->markTestSkipped('The "Config" component is not available'); + } + } + + public function testLoad() + { + $loader = new IniFileLoader(); + $resource = __DIR__.'/../fixtures/resources.ini'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertEquals(array('foo' => 'bar'), $catalogue->all('domain1')); + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource)), $catalogue->getResources()); + } + + public function testLoadDoesNothingIfEmpty() + { + $loader = new IniFileLoader(); + $resource = __DIR__.'/../fixtures/empty.ini'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertEquals(array(), $catalogue->all('domain1')); + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource)), $catalogue->getResources()); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException + */ + public function testLoadNonExistingResource() + { + $loader = new IniFileLoader(); + $resource = __DIR__.'/../fixtures/non-existing.ini'; + $loader->load($resource, 'en', 'domain1'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/LocalizedTestCase.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/LocalizedTestCase.php new file mode 100644 index 0000000..9d7c5d7 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/LocalizedTestCase.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Loader; + +abstract class LocalizedTestCase extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!extension_loaded('intl')) { + $this->markTestSkipped('The "intl" extension is not available'); + } + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/MoFileLoaderTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/MoFileLoaderTest.php new file mode 100644 index 0000000..c2616bd --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/MoFileLoaderTest.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Loader; + +use Symfony\Component\Translation\Loader\MoFileLoader; +use Symfony\Component\Config\Resource\FileResource; + +class MoFileLoaderTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\Config\Loader\Loader')) { + $this->markTestSkipped('The "Config" component is not available'); + } + } + + public function testLoad() + { + $loader = new MoFileLoader(); + $resource = __DIR__.'/../fixtures/resources.mo'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertEquals(array('foo' => 'bar'), $catalogue->all('domain1')); + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource)), $catalogue->getResources()); + } + + public function testLoadPlurals() + { + $loader = new MoFileLoader(); + $resource = __DIR__.'/../fixtures/plurals.mo'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertEquals(array('foo' => 'bar', 'foos' => '{0} bar|{1} bars'), $catalogue->all('domain1')); + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource)), $catalogue->getResources()); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException + */ + public function testLoadNonExistingResource() + { + $loader = new MoFileLoader(); + $resource = __DIR__.'/../fixtures/non-existing.mo'; + $loader->load($resource, 'en', 'domain1'); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException + */ + public function testLoadInvalidResource() + { + $loader = new MoFileLoader(); + $resource = __DIR__.'/../fixtures/empty.mo'; + $loader->load($resource, 'en', 'domain1'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/PhpFileLoaderTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/PhpFileLoaderTest.php new file mode 100644 index 0000000..5dfe837 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/PhpFileLoaderTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Loader; + +use Symfony\Component\Translation\Loader\PhpFileLoader; +use Symfony\Component\Config\Resource\FileResource; + +class PhpFileLoaderTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\Config\Loader\Loader')) { + $this->markTestSkipped('The "Config" component is not available'); + } + } + + public function testLoad() + { + $loader = new PhpFileLoader(); + $resource = __DIR__.'/../fixtures/resources.php'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertEquals(array('foo' => 'bar'), $catalogue->all('domain1')); + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource)), $catalogue->getResources()); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException + */ + public function testLoadNonExistingResource() + { + $loader = new PhpFileLoader(); + $resource = __DIR__.'/../fixtures/non-existing.php'; + $loader->load($resource, 'en', 'domain1'); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException + */ + public function testLoadThrowsAnExceptionIfFileNotLocal() + { + $loader = new PhpFileLoader(); + $resource = 'http://example.com/resources.php'; + $loader->load($resource, 'en', 'domain1'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/PoFileLoaderTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/PoFileLoaderTest.php new file mode 100644 index 0000000..cd3d85a --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/PoFileLoaderTest.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Loader; + +use Symfony\Component\Translation\Loader\PoFileLoader; +use Symfony\Component\Config\Resource\FileResource; + +class PoFileLoaderTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\Config\Loader\Loader')) { + $this->markTestSkipped('The "Config" component is not available'); + } + } + + public function testLoad() + { + $loader = new PoFileLoader(); + $resource = __DIR__.'/../fixtures/resources.po'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertEquals(array('foo' => 'bar'), $catalogue->all('domain1')); + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource)), $catalogue->getResources()); + } + + public function testLoadPlurals() + { + $loader = new PoFileLoader(); + $resource = __DIR__.'/../fixtures/plurals.po'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertEquals(array('foo' => 'bar', 'foos' => 'bar|bars'), $catalogue->all('domain1')); + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource)), $catalogue->getResources()); + } + + public function testLoadDoesNothingIfEmpty() + { + $loader = new PoFileLoader(); + $resource = __DIR__.'/../fixtures/empty.po'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertEquals(array(), $catalogue->all('domain1')); + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource)), $catalogue->getResources()); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException + */ + public function testLoadNonExistingResource() + { + $loader = new PoFileLoader(); + $resource = __DIR__.'/../fixtures/non-existing.po'; + $loader->load($resource, 'en', 'domain1'); + } + + public function testLoadEmptyTranslation() + { + $loader = new PoFileLoader(); + $resource = __DIR__.'/../fixtures/empty-translation.po'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertEquals(array('foo' => ''), $catalogue->all('domain1')); + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource)), $catalogue->getResources()); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/QtFileLoaderTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/QtFileLoaderTest.php new file mode 100644 index 0000000..c1dd7b1 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/QtFileLoaderTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Loader; + +use Symfony\Component\Translation\Loader\QtFileLoader; +use Symfony\Component\Config\Resource\FileResource; + +class QtFileLoaderTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\Config\Loader\Loader')) { + $this->markTestSkipped('The "Config" component is not available'); + } + } + + public function testLoad() + { + $loader = new QtFileLoader(); + $resource = __DIR__.'/../fixtures/resources.ts'; + $catalogue = $loader->load($resource, 'en', 'resources'); + + $this->assertEquals(array('foo' => 'bar'), $catalogue->all('resources')); + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource)), $catalogue->getResources()); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException + */ + public function testLoadNonExistingResource() + { + $loader = new QtFileLoader(); + $resource = __DIR__.'/../fixtures/non-existing.ts'; + $loader->load($resource, 'en', 'domain1'); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException + */ + public function testLoadNonLocalResource() + { + $loader = new QtFileLoader(); + $resource = 'http://domain1.com/resources.ts'; + $loader->load($resource, 'en', 'domain1'); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException + */ + public function testLoadInvalidResource() + { + $loader = new QtFileLoader(); + $resource = __DIR__.'/../fixtures/invalid-xml-resources.xlf'; + $loader->load($resource, 'en', 'domain1'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php new file mode 100644 index 0000000..1d58e0e --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/XliffFileLoaderTest.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Loader; + +use Symfony\Component\Translation\Loader\XliffFileLoader; +use Symfony\Component\Config\Resource\FileResource; + +class XliffFileLoaderTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\Config\Loader\Loader')) { + $this->markTestSkipped('The "Config" component is not available'); + } + } + + public function testLoad() + { + $loader = new XliffFileLoader(); + $resource = __DIR__.'/../fixtures/resources.xlf'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource)), $catalogue->getResources()); + } + + public function testLoadWithResname() + { + $loader = new XliffFileLoader(); + $catalogue = $loader->load(__DIR__.'/../fixtures/resname.xlf', 'en', 'domain1'); + + $this->assertEquals(array('foo' => 'bar', 'bar' => 'baz', 'baz' => 'foo'), $catalogue->all('domain1')); + } + + public function testIncompleteResource() + { + $loader = new XliffFileLoader(); + $catalogue = $loader->load(__DIR__.'/../fixtures/resources.xlf', 'en', 'domain1'); + + $this->assertEquals(array('foo' => 'bar', 'key' => '', 'test' => 'with'), $catalogue->all('domain1')); + $this->assertFalse($catalogue->has('extra', 'domain1')); + } + + public function testEncoding() + { + if (!function_exists('iconv') && !function_exists('mb_convert_encoding')) { + $this->markTestSkipped('The iconv and mbstring extensions are not available.'); + } + + $loader = new XliffFileLoader(); + $catalogue = $loader->load(__DIR__.'/../fixtures/encoding.xlf', 'en', 'domain1'); + + $this->assertEquals(utf8_decode('föö'), $catalogue->get('bar', 'domain1')); + $this->assertEquals(utf8_decode('bär'), $catalogue->get('foo', 'domain1')); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException + */ + public function testLoadInvalidResource() + { + $loader = new XliffFileLoader(); + $loader->load(__DIR__.'/../fixtures/resources.php', 'en', 'domain1'); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException + */ + public function testLoadResourceDoesNotValidate() + { + $loader = new XliffFileLoader(); + $loader->load(__DIR__.'/../fixtures/non-valid.xlf', 'en', 'domain1'); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException + */ + public function testLoadNonExistingResource() + { + $loader = new XliffFileLoader(); + $resource = __DIR__.'/../fixtures/non-existing.xlf'; + $loader->load($resource, 'en', 'domain1'); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException + */ + public function testLoadThrowsAnExceptionIfFileNotLocal() + { + $loader = new XliffFileLoader(); + $resource = 'http://example.com/resources.xlf'; + $loader->load($resource, 'en', 'domain1'); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException + * @expectedExceptionMessage Document types are not allowed. + */ + public function testDocTypeIsNotAllowed() + { + $loader = new XliffFileLoader(); + $loader->load(__DIR__.'/../fixtures/withdoctype.xlf', 'en', 'domain1'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/YamlFileLoaderTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/YamlFileLoaderTest.php new file mode 100644 index 0000000..511b127 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/Loader/YamlFileLoaderTest.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests\Loader; + +use Symfony\Component\Translation\Loader\YamlFileLoader; +use Symfony\Component\Config\Resource\FileResource; + +class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + if (!class_exists('Symfony\Component\Config\Loader\Loader')) { + $this->markTestSkipped('The "Config" component is not available'); + } + + if (!class_exists('Symfony\Component\Yaml\Yaml')) { + $this->markTestSkipped('The "Yaml" component is not available'); + } + } + + public function testLoad() + { + $loader = new YamlFileLoader(); + $resource = __DIR__.'/../fixtures/resources.yml'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertEquals(array('foo' => 'bar'), $catalogue->all('domain1')); + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource)), $catalogue->getResources()); + } + + public function testLoadDoesNothingIfEmpty() + { + $loader = new YamlFileLoader(); + $resource = __DIR__.'/../fixtures/empty.yml'; + $catalogue = $loader->load($resource, 'en', 'domain1'); + + $this->assertEquals(array(), $catalogue->all('domain1')); + $this->assertEquals('en', $catalogue->getLocale()); + $this->assertEquals(array(new FileResource($resource)), $catalogue->getResources()); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException + */ + public function testLoadNonExistingResource() + { + $loader = new YamlFileLoader(); + $resource = __DIR__.'/../fixtures/non-existing.yml'; + $loader->load($resource, 'en', 'domain1'); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException + */ + public function testLoadThrowsAnExceptionIfFileNotLocal() + { + $loader = new YamlFileLoader(); + $resource = 'http://example.com/resources.yml'; + $loader->load($resource, 'en', 'domain1'); + } + + /** + * @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException + */ + public function testLoadThrowsAnExceptionIfNotAnArray() + { + $loader = new YamlFileLoader(); + $resource = __DIR__.'/../fixtures/non-valid.yml'; + $loader->load($resource, 'en', 'domain1'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/MessageCatalogueTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/MessageCatalogueTest.php new file mode 100644 index 0000000..aa6f870 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/MessageCatalogueTest.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests; + +use Symfony\Component\Translation\MessageCatalogue; + +class MessageCatalogueTest extends \PHPUnit_Framework_TestCase +{ + public function testGetLocale() + { + $catalogue = new MessageCatalogue('en'); + + $this->assertEquals('en', $catalogue->getLocale()); + } + + public function testGetDomains() + { + $catalogue = new MessageCatalogue('en', array('domain1' => array(), 'domain2' => array())); + + $this->assertEquals(array('domain1', 'domain2'), $catalogue->getDomains()); + } + + public function testAll() + { + $catalogue = new MessageCatalogue('en', $messages = array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); + + $this->assertEquals(array('foo' => 'foo'), $catalogue->all('domain1')); + $this->assertEquals(array(), $catalogue->all('domain88')); + $this->assertEquals($messages, $catalogue->all()); + } + + public function testHas() + { + $catalogue = new MessageCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); + + $this->assertTrue($catalogue->has('foo', 'domain1')); + $this->assertFalse($catalogue->has('bar', 'domain1')); + $this->assertFalse($catalogue->has('foo', 'domain88')); + } + + public function testGetSet() + { + $catalogue = new MessageCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); + $catalogue->set('foo1', 'foo1', 'domain1'); + + $this->assertEquals('foo', $catalogue->get('foo', 'domain1')); + $this->assertEquals('foo1', $catalogue->get('foo1', 'domain1')); + } + + public function testAdd() + { + $catalogue = new MessageCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); + $catalogue->add(array('foo1' => 'foo1'), 'domain1'); + + $this->assertEquals('foo', $catalogue->get('foo', 'domain1')); + $this->assertEquals('foo1', $catalogue->get('foo1', 'domain1')); + + $catalogue->add(array('foo' => 'bar'), 'domain1'); + $this->assertEquals('bar', $catalogue->get('foo', 'domain1')); + $this->assertEquals('foo1', $catalogue->get('foo1', 'domain1')); + + $catalogue->add(array('foo' => 'bar'), 'domain88'); + $this->assertEquals('bar', $catalogue->get('foo', 'domain88')); + } + + public function testReplace() + { + $catalogue = new MessageCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); + $catalogue->replace($messages = array('foo1' => 'foo1'), 'domain1'); + + $this->assertEquals($messages, $catalogue->all('domain1')); + } + + public function testAddCatalogue() + { + if (!class_exists('Symfony\Component\Config\Loader\Loader')) { + $this->markTestSkipped('The "Config" component is not available'); + } + + $r = $this->getMock('Symfony\Component\Config\Resource\ResourceInterface'); + $r->expects($this->any())->method('__toString')->will($this->returnValue('r')); + + $r1 = $this->getMock('Symfony\Component\Config\Resource\ResourceInterface'); + $r1->expects($this->any())->method('__toString')->will($this->returnValue('r1')); + + $catalogue = new MessageCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); + $catalogue->addResource($r); + + $catalogue1 = new MessageCatalogue('en', array('domain1' => array('foo1' => 'foo1'))); + $catalogue1->addResource($r1); + + $catalogue->addCatalogue($catalogue1); + + $this->assertEquals('foo', $catalogue->get('foo', 'domain1')); + $this->assertEquals('foo1', $catalogue->get('foo1', 'domain1')); + + $this->assertEquals(array($r, $r1), $catalogue->getResources()); + } + + public function testAddFallbackCatalogue() + { + if (!class_exists('Symfony\Component\Config\Loader\Loader')) { + $this->markTestSkipped('The "Config" component is not available'); + } + + $r = $this->getMock('Symfony\Component\Config\Resource\ResourceInterface'); + $r->expects($this->any())->method('__toString')->will($this->returnValue('r')); + + $r1 = $this->getMock('Symfony\Component\Config\Resource\ResourceInterface'); + $r1->expects($this->any())->method('__toString')->will($this->returnValue('r1')); + + $catalogue = new MessageCatalogue('en_US', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar'))); + $catalogue->addResource($r); + + $catalogue1 = new MessageCatalogue('en', array('domain1' => array('foo' => 'bar', 'foo1' => 'foo1'))); + $catalogue1->addResource($r1); + + $catalogue->addFallbackCatalogue($catalogue1); + + $this->assertEquals('foo', $catalogue->get('foo', 'domain1')); + $this->assertEquals('foo1', $catalogue->get('foo1', 'domain1')); + + $this->assertEquals(array($r, $r1), $catalogue->getResources()); + } + + /** + * @expectedException LogicException + */ + public function testAddFallbackCatalogueWithCircularReference() + { + $main = new MessageCatalogue('en_US'); + $fallback = new MessageCatalogue('fr_FR'); + + $fallback->addFallbackCatalogue($main); + $main->addFallbackCatalogue($fallback); + } + + /** + * @expectedException LogicException + */ + public function testAddCatalogueWhenLocaleIsNotTheSameAsTheCurrentOne() + { + $catalogue = new MessageCatalogue('en'); + $catalogue->addCatalogue(new MessageCatalogue('fr', array())); + } + + public function testGetAddResource() + { + if (!class_exists('Symfony\Component\Config\Loader\Loader')) { + $this->markTestSkipped('The "Config" component is not available'); + } + + $catalogue = new MessageCatalogue('en'); + $r = $this->getMock('Symfony\Component\Config\Resource\ResourceInterface'); + $r->expects($this->any())->method('__toString')->will($this->returnValue('r')); + $catalogue->addResource($r); + $catalogue->addResource($r); + $r1 = $this->getMock('Symfony\Component\Config\Resource\ResourceInterface'); + $r1->expects($this->any())->method('__toString')->will($this->returnValue('r1')); + $catalogue->addResource($r1); + + $this->assertEquals(array($r, $r1), $catalogue->getResources()); + } + + public function testMetadataDelete() + { + $catalogue = new MessageCatalogue('en'); + $this->assertEquals(array(), $catalogue->getMetadata('', ''), 'Metadata is empty'); + $catalogue->deleteMetadata('key', 'messages'); + $catalogue->deleteMetadata('', 'messages'); + $catalogue->deleteMetadata(); + } + + public function testMetadataSetGetDelete() + { + $catalogue = new MessageCatalogue('en'); + $catalogue->setMetadata('key', 'value'); + $this->assertEquals('value', $catalogue->getMetadata('key', 'messages'), "Metadata 'key' = 'value'"); + + $catalogue->setMetadata('key2', array()); + $this->assertEquals(array(), $catalogue->getMetadata('key2', 'messages'), 'Metadata key2 is array'); + + $catalogue->deleteMetadata('key2', 'messages'); + $this->assertEquals(null, $catalogue->getMetadata('key2', 'messages'), 'Metadata key2 should is deleted.'); + + $catalogue->deleteMetadata('key2', 'domain'); + $this->assertEquals(null, $catalogue->getMetadata('key2', 'domain'), 'Metadata key2 should is deleted.'); + } + + public function testMetadataMerge() + { + $cat1 = new MessageCatalogue('en'); + $cat1->setMetadata('a', 'b'); + $this->assertEquals(array('messages' => array('a' => 'b')), $cat1->getMetadata('', ''), 'Cat1 contains messages metadata.'); + + $cat2 = new MessageCatalogue('en'); + $cat2->setMetadata('b', 'c', 'domain'); + $this->assertEquals(array('domain' => array('b' => 'c')), $cat2->getMetadata('', ''), 'Cat2 contains domain metadata.'); + + $cat1->addCatalogue($cat2); + $this->assertEquals(array('messages' => array('a' => 'b'), 'domain' => array('b' => 'c')), $cat1->getMetadata('', ''), 'Cat1 contains merged metadata.'); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/MessageSelectorTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/MessageSelectorTest.php new file mode 100644 index 0000000..d5a4f3e --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/MessageSelectorTest.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests; + +use Symfony\Component\Translation\MessageSelector; + +class MessageSelectorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getChooseTests + */ + public function testChoose($expected, $id, $number) + { + $selector = new MessageSelector(); + + $this->assertEquals($expected, $selector->choose($id, $number, 'en')); + } + + public function testReturnMessageIfExactlyOneStandardRuleIsGiven() + { + $selector = new MessageSelector(); + + $this->assertEquals('There are two apples', $selector->choose('There are two apples', 2, 'en')); + } + + /** + * @dataProvider getNonMatchingMessages + * @expectedException \InvalidArgumentException + */ + public function testThrowExceptionIfMatchingMessageCannotBeFound($id, $number) + { + $selector = new MessageSelector(); + + $selector->choose($id, $number, 'en'); + } + + public function getNonMatchingMessages() + { + return array( + array('{0} There are no apples|{1} There is one apple', 2), + array('{1} There is one apple|]1,Inf] There are %count% apples', 0), + array('{1} There is one apple|]2,Inf] There are %count% apples', 2), + array('{0} There are no apples|There is one apple', 2), + ); + } + + public function getChooseTests() + { + return array( + array('There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0), + array('There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0), + array('There are no apples', '{0}There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0), + + array('There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1), + + array('There are %count% apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10), + array('There are %count% apples', '{0} There are no apples|{1} There is one apple|]1,Inf]There are %count% apples', 10), + array('There are %count% apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10), + + array('There are %count% apples', 'There is one apple|There are %count% apples', 0), + array('There is one apple', 'There is one apple|There are %count% apples', 1), + array('There are %count% apples', 'There is one apple|There are %count% apples', 10), + + array('There are %count% apples', 'one: There is one apple|more: There are %count% apples', 0), + array('There is one apple', 'one: There is one apple|more: There are %count% apples', 1), + array('There are %count% apples', 'one: There is one apple|more: There are %count% apples', 10), + + array('There are no apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 0), + array('There is one apple', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 1), + array('There are %count% apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 10), + + array('', '{0}|{1} There is one apple|]1,Inf] There are %count% apples', 0), + array('', '{0} There are no apples|{1}|]1,Inf] There are %count% apples', 1), + + // Indexed only tests which are Gettext PoFile* compatible strings. + array('There are %count% apples', 'There is one apple|There are %count% apples', 0), + array('There is one apple', 'There is one apple|There are %count% apples', 1), + array('There are %count% apples', 'There is one apple|There are %count% apples', 2), + + // Tests for float numbers + array('There is almost one apple', '{0} There are no apples|]0,1[ There is almost one apple|{1} There is one apple|[1,Inf] There is more than one apple', 0.7), + array('There is one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1), + array('There is more than one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1.7), + array('There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0), + array('There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0.0), + array('There are no apples', '{0.0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0), + ); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/PluralizationRulesTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/PluralizationRulesTest.php new file mode 100644 index 0000000..26f9e2f --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/PluralizationRulesTest.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests; + +use Symfony\Component\Translation\PluralizationRules; + +/** + * Test should cover all languages mentioned on http://translate.sourceforge.net/wiki/l10n/pluralforms + * and Plural forms mentioned on http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms + * + * See also https://developer.mozilla.org/en/Localization_and_Plurals which mentions 15 rules having a maximum of 6 forms. + * The mozilla code is also interesting to check for. + * + * As mentioned by chx http://drupal.org/node/1273968 we can cover all by testing number from 0 to 199 + * + * The goal to cover all languages is to far fetched so this test case is smaller. + * + * @author Clemens Tolboom clemens@build2be.nl + */ +class PluralizationRulesTest extends \PHPUnit_Framework_TestCase +{ + + /** + * We test failed langcode here. + * + * TODO: The languages mentioned in the data provide need to get fixed somehow within PluralizationRules. + * + * @dataProvider failingLangcodes + */ + public function testFailedLangcodes($nplural, $langCodes) + { + $matrix = $this->generateTestData($nplural, $langCodes); + $this->validateMatrix($nplural, $matrix, false); + } + + /** + * @dataProvider successLangcodes + */ + public function testLangcodes($nplural, $langCodes) + { + $matrix = $this->generateTestData($nplural, $langCodes); + $this->validateMatrix($nplural, $matrix); + } + + /** + * This array should contain all currently known langcodes. + * + * As it is impossible to have this ever complete we should try as hard as possible to have it almost complete. + * + * @return type + */ + public function successLangcodes() + { + return array( + array('1' , array('ay','bo', 'cgg','dz','id', 'ja', 'jbo', 'ka','kk','km','ko','ky')), + array('2' , array('nl', 'fr', 'en', 'de', 'de_GE')), + array('3' , array('be','bs','cs','hr')), + array('4' , array('cy','mt', 'sl')), + array('5' , array()), + array('6' , array('ar')), + ); + } + + /** + * This array should be at least empty within the near future. + * + * This both depends on a complete list trying to add above as understanding + * the plural rules of the current failing languages. + * + * @return array with nplural together with langcodes + */ + public function failingLangcodes() + { + return array( + array('1' , array('fa')), + array('2' , array('jbo')), + array('3' , array('cbs')), + array('4' , array('gd','kw')), + array('5' , array('ga')), + array('6' , array()), + ); + } + + /** + * We validate only on the plural coverage. Thus the real rules is not tested. + * + * @param string $nplural plural expected + * @param array $matrix containing langcodes and their plural index values. + * @param boolean $expectSuccess + */ + protected function validateMatrix($nplural, $matrix, $expectSuccess = true) + { + foreach ($matrix as $langCode => $data) { + $indexes = array_flip($data); + if ($expectSuccess) { + $this->assertEquals($nplural, count($indexes), "Langcode '$langCode' has '$nplural' plural forms."); + } else { + $this->assertNotEquals((int) $nplural, count($indexes), "Langcode '$langCode' has '$nplural' plural forms."); + } + } + } + + protected function generateTestData($plural, $langCodes) + { + $matrix = array(); + foreach ($langCodes as $langCode) { + for ($count=0; $count<200; $count++) { + $plural = PluralizationRules::get($count, $langCode); + $matrix[$langCode][$count] = $plural; + } + } + + return $matrix; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/TranslatorTest.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/TranslatorTest.php new file mode 100644 index 0000000..1283751 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/TranslatorTest.php @@ -0,0 +1,305 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Tests; + +use Symfony\Component\Translation\Translator; +use Symfony\Component\Translation\MessageSelector; +use Symfony\Component\Translation\Loader\ArrayLoader; + +class TranslatorTest extends \PHPUnit_Framework_TestCase +{ + public function testSetGetLocale() + { + $translator = new Translator('en', new MessageSelector()); + + $this->assertEquals('en', $translator->getLocale()); + + $translator->setLocale('fr'); + $this->assertEquals('fr', $translator->getLocale()); + } + + public function testSetFallbackLocales() + { + $translator = new Translator('en', new MessageSelector()); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array('foo' => 'foofoo'), 'en'); + $translator->addResource('array', array('bar' => 'foobar'), 'fr'); + + // force catalogue loading + $translator->trans('bar'); + + $translator->setFallbackLocales(array('fr')); + $this->assertEquals('foobar', $translator->trans('bar')); + } + + public function testSetFallbackLocalesMultiple() + { + $translator = new Translator('en', new MessageSelector()); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array('foo' => 'foo (en)'), 'en'); + $translator->addResource('array', array('bar' => 'bar (fr)'), 'fr'); + + // force catalogue loading + $translator->trans('bar'); + + $translator->setFallbackLocales(array('fr_FR', 'fr')); + $this->assertEquals('bar (fr)', $translator->trans('bar')); + } + + public function testTransWithFallbackLocale() + { + $translator = new Translator('fr_FR', new MessageSelector()); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array('foo' => 'foofoo'), 'en_US'); + $translator->addResource('array', array('bar' => 'foobar'), 'en'); + + $translator->setFallbackLocales(array('en')); + + $this->assertEquals('foobar', $translator->trans('bar')); + } + + public function testAddResourceAfterTrans() + { + $translator = new Translator('fr', new MessageSelector()); + $translator->addLoader('array', new ArrayLoader()); + + $translator->setFallbackLocale(array('en')); + + $translator->addResource('array', array('foo' => 'foofoo'), 'en'); + $this->assertEquals('foofoo', $translator->trans('foo')); + + $translator->addResource('array', array('bar' => 'foobar'), 'en'); + $this->assertEquals('foobar', $translator->trans('bar')); + } + + /** + * @dataProvider getTransFileTests + * @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException + */ + public function testTransWithoutFallbackLocaleFile($format, $loader) + { + $loaderClass = 'Symfony\\Component\\Translation\\Loader\\'.$loader; + $translator = new Translator('en', new MessageSelector()); + $translator->addLoader($format, new $loaderClass()); + $translator->addResource($format, __DIR__.'/fixtures/non-existing', 'en'); + $translator->addResource($format, __DIR__.'/fixtures/resources.'.$format, 'en'); + + // force catalogue loading + $translator->trans('foo'); + } + + /** + * @dataProvider getTransFileTests + */ + public function testTransWithFallbackLocaleFile($format, $loader) + { + $loaderClass = 'Symfony\\Component\\Translation\\Loader\\'.$loader; + $translator = new Translator('en_GB', new MessageSelector()); + $translator->addLoader($format, new $loaderClass()); + $translator->addResource($format, __DIR__.'/fixtures/non-existing', 'en_GB'); + $translator->addResource($format, __DIR__.'/fixtures/resources.'.$format, 'en', 'resources'); + + $this->assertEquals('bar', $translator->trans('foo', array(), 'resources')); + } + + public function testTransWithFallbackLocaleBis() + { + $translator = new Translator('en_US', new MessageSelector()); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array('foo' => 'foofoo'), 'en_US'); + $translator->addResource('array', array('bar' => 'foobar'), 'en'); + $this->assertEquals('foobar', $translator->trans('bar')); + } + + public function testTransWithFallbackLocaleTer() + { + $translator = new Translator('fr_FR', new MessageSelector()); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array('foo' => 'foo (en_US)'), 'en_US'); + $translator->addResource('array', array('bar' => 'bar (en)'), 'en'); + + $translator->setFallbackLocales(array('en_US', 'en')); + + $this->assertEquals('foo (en_US)', $translator->trans('foo')); + $this->assertEquals('bar (en)', $translator->trans('bar')); + } + + public function testTransNonExistentWithFallback() + { + $translator = new Translator('fr', new MessageSelector()); + $translator->setFallbackLocales(array('en')); + $translator->addLoader('array', new ArrayLoader()); + $this->assertEquals('non-existent', $translator->trans('non-existent')); + } + + /** + * @expectedException RuntimeException + */ + public function testWhenAResourceHasNoRegisteredLoader() + { + $translator = new Translator('en', new MessageSelector()); + $translator->addResource('array', array('foo' => 'foofoo'), 'en'); + + $translator->trans('foo'); + } + + /** + * @dataProvider getTransTests + */ + public function testTrans($expected, $id, $translation, $parameters, $locale, $domain) + { + $translator = new Translator('en', new MessageSelector()); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array((string) $id => $translation), $locale, $domain); + + $this->assertEquals($expected, $translator->trans($id, $parameters, $domain, $locale)); + } + + /** + * @dataProvider getFlattenedTransTests + */ + public function testFlattenedTrans($expected, $messages, $id) + { + $translator = new Translator('en', new MessageSelector()); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', $messages, 'fr', ''); + + $this->assertEquals($expected, $translator->trans($id, array(), '', 'fr')); + } + + /** + * @dataProvider getTransChoiceTests + */ + public function testTransChoice($expected, $id, $translation, $number, $parameters, $locale, $domain) + { + $translator = new Translator('en', new MessageSelector()); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array((string) $id => $translation), $locale, $domain); + + $this->assertEquals($expected, $translator->transChoice($id, $number, $parameters, $domain, $locale)); + } + + public function getTransFileTests() + { + return array( + array('csv', 'CsvFileLoader'), + array('ini', 'IniFileLoader'), + array('mo', 'MoFileLoader'), + array('po', 'PoFileLoader'), + array('php', 'PhpFileLoader'), + array('ts', 'QtFileLoader'), + array('xlf', 'XliffFileLoader'), + array('yml', 'YamlFileLoader'), + ); + } + + public function getTransTests() + { + return array( + array('Symfony2 est super !', 'Symfony2 is great!', 'Symfony2 est super !', array(), 'fr', ''), + array('Symfony2 est awesome !', 'Symfony2 is %what%!', 'Symfony2 est %what% !', array('%what%' => 'awesome'), 'fr', ''), + array('Symfony2 est super !', new String('Symfony2 is great!'), 'Symfony2 est super !', array(), 'fr', ''), + ); + } + + public function getFlattenedTransTests() + { + $messages = array( + 'symfony2' => array( + 'is' => array( + 'great' => 'Symfony2 est super!' + ) + ), + 'foo' => array( + 'bar' => array( + 'baz' => 'Foo Bar Baz' + ), + 'baz' => 'Foo Baz', + ), + ); + + return array( + array('Symfony2 est super!', $messages, 'symfony2.is.great'), + array('Foo Bar Baz', $messages, 'foo.bar.baz'), + array('Foo Baz', $messages, 'foo.baz'), + ); + } + + public function getTransChoiceTests() + { + return array( + array('Il y a 0 pomme', '{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''), + array('Il y a 1 pomme', '{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''), + array('Il y a 10 pommes', '{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''), + + array('Il y a 0 pomme', 'There is one apple|There is %count% apples', 'Il y a %count% pomme|Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''), + array('Il y a 1 pomme', 'There is one apple|There is %count% apples', 'Il y a %count% pomme|Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''), + array('Il y a 10 pommes', 'There is one apple|There is %count% apples', 'Il y a %count% pomme|Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''), + + array('Il y a 0 pomme', 'one: There is one apple|more: There is %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''), + array('Il y a 1 pomme', 'one: There is one apple|more: There is %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''), + array('Il y a 10 pommes', 'one: There is one apple|more: There is %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''), + + array('Il n\'y a aucune pomme', '{0} There are no apples|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''), + array('Il y a 1 pomme', '{0} There are no apples|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''), + array('Il y a 10 pommes', '{0} There are no apples|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''), + + array('Il y a 0 pomme', new String('{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples'), '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''), + ); + } + + public function testTransChoiceFallback() + { + $translator = new Translator('ru', new MessageSelector()); + $translator->setFallbackLocales(array('en')); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array('some_message2' => 'one thing|%count% things'), 'en'); + + $this->assertEquals('10 things', $translator->transChoice('some_message2', 10, array('%count%' => 10))); + } + + public function testTransChoiceFallbackBis() + { + $translator = new Translator('ru', new MessageSelector()); + $translator->setFallbackLocales(array('en_US', 'en')); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array('some_message2' => 'one thing|%count% things'), 'en_US'); + + $this->assertEquals('10 things', $translator->transChoice('some_message2', 10, array('%count%' => 10))); + } + + public function testTransChoiceFallbackWithNoTranslation() + { + $translator = new Translator('ru', new MessageSelector()); + $translator->setFallbackLocales(array('en')); + $translator->addLoader('array', new ArrayLoader()); + + // consistent behavior with Translator::trans(), which returns the string + // unchanged if it can't be found + $this->assertEquals('some_message2', $translator->transChoice('some_message2', 10, array('%count%' => 10))); + } +} + +class String +{ + protected $str; + + public function __construct($str) + { + $this->str = $str; + } + + public function __toString() + { + return $this->str; + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/empty-translation.po b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/empty-translation.po new file mode 100644 index 0000000..ff6f22a --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/empty-translation.po @@ -0,0 +1,3 @@ +msgid "foo" +msgstr "" + diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/empty.csv b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/empty.csv new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/empty.ini b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/empty.ini new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/empty.mo b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/empty.mo new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/empty.po b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/empty.po new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/empty.yml b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/empty.yml new file mode 100644 index 0000000..e69de29 diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/encoding.xlf b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/encoding.xlf new file mode 100644 index 0000000..6be901b --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/encoding.xlf @@ -0,0 +1,15 @@ + + + + + + foo + bär + + + bar + föö + + + + diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/invalid-xml-resources.xlf b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/invalid-xml-resources.xlf new file mode 100644 index 0000000..7bf6c98 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/invalid-xml-resources.xlf @@ -0,0 +1,23 @@ + + + + + + foo + bar + + + extra + + + key + + + + test + with + note + + + + diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/non-valid.xlf b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/non-valid.xlf new file mode 100644 index 0000000..734fc97 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/non-valid.xlf @@ -0,0 +1,11 @@ + + + + + + foo + bar + + + + diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/non-valid.yml b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/non-valid.yml new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/non-valid.yml @@ -0,0 +1 @@ +foo diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/plurals.mo b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/plurals.mo new file mode 100644 index 0000000..6445e77 Binary files /dev/null and b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/plurals.mo differ diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/plurals.po b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/plurals.po new file mode 100644 index 0000000..439c41a --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/plurals.po @@ -0,0 +1,5 @@ +msgid "foo" +msgid_plural "foos" +msgstr[0] "bar" +msgstr[1] "bars" + diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resname.xlf b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resname.xlf new file mode 100644 index 0000000..2df16af --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resname.xlf @@ -0,0 +1,19 @@ + + + + + + + bar + + + bar source + baz + + + baz + foo + + + + diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/corrupted/resources.dat b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/corrupted/resources.dat new file mode 100644 index 0000000..391250c --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/corrupted/resources.dat @@ -0,0 +1 @@ +XXX \ No newline at end of file diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/dat/en.res b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/dat/en.res new file mode 100644 index 0000000..1fc1436 Binary files /dev/null and b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/dat/en.res differ diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/dat/en.txt b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/dat/en.txt new file mode 100644 index 0000000..c04a4e8 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/dat/en.txt @@ -0,0 +1,3 @@ +en{ + symfony{"Symfony 2 is great"} +} \ No newline at end of file diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/dat/fr.res b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/dat/fr.res new file mode 100644 index 0000000..f584160 Binary files /dev/null and b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/dat/fr.res differ diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/dat/fr.txt b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/dat/fr.txt new file mode 100644 index 0000000..7e84f67 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/dat/fr.txt @@ -0,0 +1,3 @@ +fr{ + symfony{"Symfony 2 est génial"} +} \ No newline at end of file diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/dat/packagelist.txt b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/dat/packagelist.txt new file mode 100644 index 0000000..c5783ed --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/dat/packagelist.txt @@ -0,0 +1,2 @@ +en.res +fr.res diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/dat/resources.dat b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/dat/resources.dat new file mode 100644 index 0000000..563b0ea Binary files /dev/null and b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/dat/resources.dat differ diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/res/en.res b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/res/en.res new file mode 100644 index 0000000..ad894a9 Binary files /dev/null and b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resourcebundle/res/en.res differ diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources-clean.xlf b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources-clean.xlf new file mode 100644 index 0000000..464b079 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources-clean.xlf @@ -0,0 +1,15 @@ + + + + + + foo + bar + + + key + + + + + diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.csv b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.csv new file mode 100644 index 0000000..374b9eb --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.csv @@ -0,0 +1,4 @@ +"foo"; "bar" +#"bar"; "foo" +"incorrect"; "number"; "columns"; "will"; "be"; "ignored" +"incorrect" \ No newline at end of file diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.ini b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.ini new file mode 100644 index 0000000..4953062 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.ini @@ -0,0 +1 @@ +foo="bar" diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.mo b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.mo new file mode 100644 index 0000000..0a96602 Binary files /dev/null and b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.mo differ diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.php b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.php new file mode 100644 index 0000000..c291398 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.php @@ -0,0 +1,5 @@ + 'bar', +); diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.po b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.po new file mode 100644 index 0000000..da0d5f4 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.po @@ -0,0 +1,2 @@ +msgid "foo" +msgstr "bar" \ No newline at end of file diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.ts b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.ts new file mode 100644 index 0000000..40e1852 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.ts @@ -0,0 +1,10 @@ + + + + resources + + foo + bar + + + diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.xlf b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.xlf new file mode 100644 index 0000000..b0e5988 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.xlf @@ -0,0 +1,23 @@ + + + + + + foo + bar + + + extra + + + key + + + + test + with + note + + + + diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.yml b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.yml new file mode 100644 index 0000000..20e9ff3 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/resources.yml @@ -0,0 +1 @@ +foo: bar diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/valid.csv b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/valid.csv new file mode 100644 index 0000000..59882e5 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/valid.csv @@ -0,0 +1,4 @@ +foo;bar +bar;"foo +foo" +"foo;foo";bar diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/withdoctype.xlf b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/withdoctype.xlf new file mode 100644 index 0000000..f83e834 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Tests/fixtures/withdoctype.xlf @@ -0,0 +1,12 @@ + + + + + + + foo + bar + + + + diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Translator.php b/vendor/symfony/translation/Symfony/Component/Translation/Translator.php new file mode 100644 index 0000000..8e74b79 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Translator.php @@ -0,0 +1,282 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +use Symfony\Component\Translation\Loader\LoaderInterface; +use Symfony\Component\Translation\Exception\NotFoundResourceException; + +/** + * Translator. + * + * @author Fabien Potencier + * + * @api + */ +class Translator implements TranslatorInterface +{ + /** + * @var MessageCatalogueInterface[] + */ + protected $catalogues = array(); + + /** + * @var string + */ + protected $locale; + + /** + * @var array + */ + private $fallbackLocales = array(); + + /** + * @var LoaderInterface[] + */ + private $loaders = array(); + + /** + * @var array + */ + private $resources = array(); + + /** + * @var MessageSelector + */ + private $selector; + + /** + * Constructor. + * + * @param string $locale The locale + * @param MessageSelector|null $selector The message selector for pluralization + * + * @api + */ + public function __construct($locale, MessageSelector $selector = null) + { + $this->locale = $locale; + $this->selector = $selector ?: new MessageSelector(); + } + + /** + * Adds a Loader. + * + * @param string $format The name of the loader (@see addResource()) + * @param LoaderInterface $loader A LoaderInterface instance + * + * @api + */ + public function addLoader($format, LoaderInterface $loader) + { + $this->loaders[$format] = $loader; + } + + /** + * Adds a Resource. + * + * @param string $format The name of the loader (@see addLoader()) + * @param mixed $resource The resource name + * @param string $locale The locale + * @param string $domain The domain + * + * @api + */ + public function addResource($format, $resource, $locale, $domain = null) + { + if (null === $domain) { + $domain = 'messages'; + } + + $this->resources[$locale][] = array($format, $resource, $domain); + + if (in_array($locale, $this->fallbackLocales)) { + $this->catalogues = array(); + } else { + unset($this->catalogues[$locale]); + } + } + + /** + * {@inheritdoc} + * + * @api + */ + public function setLocale($locale) + { + $this->locale = $locale; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function getLocale() + { + return $this->locale; + } + + /** + * Sets the fallback locale(s). + * + * @param string|array $locales The fallback locale(s) + * + * @deprecated since 2.3, to be removed in 3.0. Use setFallbackLocales() instead. + * + * @api + */ + public function setFallbackLocale($locales) + { + $this->setFallbackLocales(is_array($locales) ? $locales : array($locales)); + } + + /** + * Sets the fallback locales. + * + * @param array $locales The fallback locales + * + * @api + */ + public function setFallbackLocales(array $locales) + { + // needed as the fallback locales are linked to the already loaded catalogues + $this->catalogues = array(); + + $this->fallbackLocales = $locales; + } + + /** + * Gets the fallback locales. + * + * @return array $locales The fallback locales + * + * @api + */ + public function getFallbackLocales() + { + return $this->fallbackLocales; + } + + /** + * {@inheritdoc} + * + * @api + */ + public function trans($id, array $parameters = array(), $domain = null, $locale = null) + { + if (null === $locale) { + $locale = $this->getLocale(); + } + + if (null === $domain) { + $domain = 'messages'; + } + + if (!isset($this->catalogues[$locale])) { + $this->loadCatalogue($locale); + } + + return strtr($this->catalogues[$locale]->get((string) $id, $domain), $parameters); + } + + /** + * {@inheritdoc} + * + * @api + */ + public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null) + { + if (null === $locale) { + $locale = $this->getLocale(); + } + + if (null === $domain) { + $domain = 'messages'; + } + + if (!isset($this->catalogues[$locale])) { + $this->loadCatalogue($locale); + } + + $id = (string) $id; + + $catalogue = $this->catalogues[$locale]; + while (!$catalogue->defines($id, $domain)) { + if ($cat = $catalogue->getFallbackCatalogue()) { + $catalogue = $cat; + $locale = $catalogue->getLocale(); + } else { + break; + } + } + + return strtr($this->selector->choose($catalogue->get($id, $domain), (int) $number, $locale), $parameters); + } + + protected function loadCatalogue($locale) + { + try { + $this->doLoadCatalogue($locale); + } catch (NotFoundResourceException $e) { + if (!$this->computeFallbackLocales($locale)) { + throw $e; + } + } + $this->loadFallbackCatalogues($locale); + } + + private function doLoadCatalogue($locale) + { + $this->catalogues[$locale] = new MessageCatalogue($locale); + + if (isset($this->resources[$locale])) { + foreach ($this->resources[$locale] as $resource) { + if (!isset($this->loaders[$resource[0]])) { + throw new \RuntimeException(sprintf('The "%s" translation loader is not registered.', $resource[0])); + } + $this->catalogues[$locale]->addCatalogue($this->loaders[$resource[0]]->load($resource[1], $locale, $resource[2])); + } + } + } + + private function loadFallbackCatalogues($locale) + { + $current = $this->catalogues[$locale]; + + foreach ($this->computeFallbackLocales($locale) as $fallback) { + if (!isset($this->catalogues[$fallback])) { + $this->doLoadCatalogue($fallback); + } + + $current->addFallbackCatalogue($this->catalogues[$fallback]); + $current = $this->catalogues[$fallback]; + } + } + + protected function computeFallbackLocales($locale) + { + $locales = array(); + foreach ($this->fallbackLocales as $fallback) { + if ($fallback === $locale) { + continue; + } + + $locales[] = $fallback; + } + + if (strrchr($locale, '_') !== false) { + array_unshift($locales, substr($locale, 0, -strlen(strrchr($locale, '_')))); + } + + return array_unique($locales); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/TranslatorInterface.php b/vendor/symfony/translation/Symfony/Component/Translation/TranslatorInterface.php new file mode 100644 index 0000000..3dcdd4f --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/TranslatorInterface.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation; + +/** + * TranslatorInterface. + * + * @author Fabien Potencier + * + * @api + */ +interface TranslatorInterface +{ + /** + * Translates the given message. + * + * @param string $id The message id (may also be an object that can be cast to string) + * @param array $parameters An array of parameters for the message + * @param string $domain The domain for the message + * @param string $locale The locale + * + * @return string The translated string + * + * @api + */ + public function trans($id, array $parameters = array(), $domain = null, $locale = null); + + /** + * Translates the given choice message by choosing a translation according to a number. + * + * @param string $id The message id (may also be an object that can be cast to string) + * @param integer $number The number to use to find the indice of the message + * @param array $parameters An array of parameters for the message + * @param string $domain The domain for the message + * @param string $locale The locale + * + * @return string The translated string + * + * @api + */ + public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null); + + /** + * Sets the current locale. + * + * @param string $locale The locale + * + * @api + */ + public function setLocale($locale); + + /** + * Returns the current locale. + * + * @return string The locale + * + * @api + */ + public function getLocale(); +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/Writer/TranslationWriter.php b/vendor/symfony/translation/Symfony/Component/Translation/Writer/TranslationWriter.php new file mode 100644 index 0000000..9d70c12 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/Writer/TranslationWriter.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Translation\Writer; + +use Symfony\Component\Translation\MessageCatalogue; +use Symfony\Component\Translation\Dumper\DumperInterface; + +/** + * TranslationWriter writes translation messages. + * + * @author Michel Salib + */ +class TranslationWriter +{ + /** + * Dumpers used for export. + * + * @var array + */ + private $dumpers = array(); + + /** + * Adds a dumper to the writer. + * + * @param string $format The format of the dumper + * @param DumperInterface $dumper The dumper + */ + public function addDumper($format, DumperInterface $dumper) + { + $this->dumpers[$format] = $dumper; + } + + /** + * Obtains the list of supported formats. + * + * @return array + */ + public function getFormats() + { + return array_keys($this->dumpers); + } + + /** + * Writes translation from the catalogue according to the selected format. + * + * @param MessageCatalogue $catalogue The message catalogue to dump + * @param string $format The format to use to dump the messages + * @param array $options Options that are passed to the dumper + * + * @throws \InvalidArgumentException + */ + public function writeTranslations(MessageCatalogue $catalogue, $format, $options = array()) + { + if (!isset($this->dumpers[$format])) { + throw new \InvalidArgumentException(sprintf('There is no dumper associated with format "%s".', $format)); + } + + // get the right dumper + $dumper = $this->dumpers[$format]; + + // save + $dumper->dump($catalogue, $options); + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/composer.json b/vendor/symfony/translation/Symfony/Component/Translation/composer.json new file mode 100644 index 0000000..96a8e66 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/composer.json @@ -0,0 +1,39 @@ +{ + "name": "symfony/translation", + "type": "library", + "description": "Symfony Translation Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/config": "~2.0", + "symfony/yaml": "~2.2" + }, + "suggest": { + "symfony/config": "", + "symfony/yaml": "" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\Translation\\": "" } + }, + "target-dir": "Symfony/Component/Translation", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + } +} diff --git a/vendor/symfony/translation/Symfony/Component/Translation/phpunit.xml.dist b/vendor/symfony/translation/Symfony/Component/Translation/phpunit.xml.dist new file mode 100644 index 0000000..1b37f21 --- /dev/null +++ b/vendor/symfony/translation/Symfony/Component/Translation/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./vendor + ./Tests + + + +